diff options
Diffstat (limited to 'vcl/unx/generic')
-rw-r--r-- | vcl/unx/generic/fontmanager/adobe_encoding_table.hxx | 1078 | ||||
-rw-r--r-- | vcl/unx/generic/fontmanager/afm_keyword_list | 62 | ||||
-rw-r--r-- | vcl/unx/generic/fontmanager/fontcache.cxx | 740 | ||||
-rw-r--r-- | vcl/unx/generic/fontmanager/fontconfig.cxx | 1293 | ||||
-rw-r--r-- | vcl/unx/generic/fontmanager/fontmanager.cxx | 2222 | ||||
-rw-r--r-- | vcl/unx/generic/fontmanager/fontsubst.cxx | 252 | ||||
-rw-r--r-- | vcl/unx/generic/fontmanager/helper.cxx | 390 | ||||
-rw-r--r-- | vcl/unx/generic/fontmanager/parseAFM.cxx | 1466 | ||||
-rw-r--r-- | vcl/unx/generic/fontmanager/parseAFM.hxx | 323 |
9 files changed, 7826 insertions, 0 deletions
diff --git a/vcl/unx/generic/fontmanager/adobe_encoding_table.hxx b/vcl/unx/generic/fontmanager/adobe_encoding_table.hxx new file mode 100644 index 000000000000..f2a7451f9c1a --- /dev/null +++ b/vcl/unx/generic/fontmanager/adobe_encoding_table.hxx @@ -0,0 +1,1078 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +struct AdobeEncEntry { + sal_Unicode aUnicode; + sal_uInt8 aAdobeStandardCode; + const char* const pAdobename; +}; + +static const AdobeEncEntry aAdobeCodes[]= +{ + { 0x0041, 0101, "A" }, + { 0x00C6, 0341, "AE" }, + { 0x01FC, 0, "AEacute" }, + { 0xF7E6, 0, "AEsmall" }, + { 0x00C1, 0, "Aacute" }, + { 0xF7E1, 0, "Aacutesmall" }, + { 0x0102, 0, "Abreve" }, + { 0x00C2, 0, "Acircumflex" }, + { 0xF7E2, 0, "Acircumflexsmall" }, + { 0xF6C9, 0, "Acute" }, + { 0xF7B4, 0, "Acutesmall" }, + { 0x00C4, 0, "Adieresis" }, + { 0xF7E4, 0, "Adieresissmall" }, + { 0x00C0, 0, "Agrave" }, + { 0xF7E0, 0, "Agravesmall" }, + { 0x0391, 0, "Alpha" }, + { 0x0386, 0, "Alphatonos" }, + { 0x0100, 0, "Amacron" }, + { 0x0104, 0, "Aogonek" }, + { 0x00C5, 0, "Aring" }, + { 0x01FA, 0, "Aringacute" }, + { 0xF7E5, 0, "Aringsmall" }, + { 0xF761, 0, "Asmall" }, + { 0x00C3, 0, "Atilde" }, + { 0xF7E3, 0, "Atildesmall" }, + { 0x0042, 0102, "B" }, + { 0x0392, 0, "Beta" }, + { 0xF6F4, 0, "Brevesmall" }, + { 0xF762, 0, "Bsmall" }, + { 0x0043, 0103, "C" }, + { 0x0106, 0, "Cacute" }, + { 0xF6CA, 0, "Caron" }, + { 0xF6F5, 0, "Caronsmall" }, + { 0x010C, 0, "Ccaron" }, + { 0x00C7, 0, "Ccedilla" }, + { 0xF7E7, 0, "Ccedillasmall" }, + { 0x0108, 0, "Ccircumflex" }, + { 0x010A, 0, "Cdotaccent" }, + { 0xF7B8, 0, "Cedillasmall" }, + { 0x03A7, 0, "Chi" }, + { 0xF6F6, 0, "Circumflexsmall" }, + { 0xF763, 0, "Csmall" }, + { 0x0044, 0104, "D" }, + { 0x010E, 0, "Dcaron" }, + { 0x0110, 0, "Dcroat" }, + { 0x2206, 0, "Delta" }, + { 0x0394, 0, "Delta" }, + { 0xF6CB, 0, "Dieresis" }, + { 0xF6CC, 0, "DieresisAcute" }, + { 0xF6CD, 0, "DieresisGrave" }, + { 0xF7A8, 0, "Dieresissmall" }, + { 0xF6F7, 0, "Dotaccentsmall" }, + { 0xF764, 0, "Dsmall" }, + { 0x0045, 0105, "E" }, + { 0x00C9, 0, "Eacute" }, + { 0xF7E9, 0, "Eacutesmall" }, + { 0x0114, 0, "Ebreve" }, + { 0x011A, 0, "Ecaron" }, + { 0x00CA, 0, "Ecircumflex" }, + { 0xF7EA, 0, "Ecircumflexsmall" }, + { 0x00CB, 0, "Edieresis" }, + { 0xF7EB, 0, "Edieresissmall" }, + { 0x0116, 0, "Edotaccent" }, + { 0x00C8, 0, "Egrave" }, + { 0xF7E8, 0, "Egravesmall" }, + { 0x0112, 0, "Emacron" }, + { 0x014A, 0, "Eng" }, + { 0x0118, 0, "Eogonek" }, + { 0x0395, 0, "Epsilon" }, + { 0x0388, 0, "Epsilontonos" }, + { 0xF765, 0, "Esmall" }, + { 0x0397, 0, "Eta" }, + { 0x0389, 0, "Etatonos" }, + { 0x00D0, 0, "Eth" }, + { 0xF7F0, 0, "Ethsmall" }, + { 0x20AC, 0, "Euro" }, + { 0x0046, 0106, "F" }, + { 0xF766, 0, "Fsmall" }, + { 0x0047, 0107, "G" }, + { 0x0393, 0, "Gamma" }, + { 0x011E, 0, "Gbreve" }, + { 0x01E6, 0, "Gcaron" }, + { 0x011C, 0, "Gcircumflex" }, + { 0x0122, 0, "Gcommaaccent" }, + { 0x0120, 0, "Gdotaccent" }, + { 0xF6CE, 0, "Grave" }, + { 0xF760, 0, "Gravesmall" }, + { 0xF767, 0, "Gsmall" }, + { 0x0048, 0110, "H" }, + { 0x25CF, 0, "H18533" }, + { 0x25AA, 0, "H18543" }, + { 0x25AB, 0, "H18551" }, + { 0x25A1, 0, "H22073" }, + { 0x0126, 0, "Hbar" }, + { 0x0124, 0, "Hcircumflex" }, + { 0xF768, 0, "Hsmall" }, + { 0xF6CF, 0, "Hungarumlaut" }, + { 0xF6F8, 0, "Hungarumlautsmall" }, + { 0x0049, 0111, "I" }, + { 0x0132, 0, "IJ" }, + { 0x00CD, 0, "Iacute" }, + { 0xF7ED, 0, "Iacutesmall" }, + { 0x012C, 0, "Ibreve" }, + { 0x00CE, 0, "Icircumflex" }, + { 0xF7EE, 0, "Icircumflexsmall" }, + { 0x00CF, 0, "Idieresis" }, + { 0xF7EF, 0, "Idieresissmall" }, + { 0x0130, 0, "Idotaccent" }, + { 0x2111, 0, "Ifraktur" }, + { 0x00CC, 0, "Igrave" }, + { 0xF7EC, 0, "Igravesmall" }, + { 0x012A, 0, "Imacron" }, + { 0x012E, 0, "Iogonek" }, + { 0x0399, 0, "Iota" }, + { 0x03AA, 0, "Iotadieresis" }, + { 0x038A, 0, "Iotatonos" }, + { 0xF769, 0, "Ismall" }, + { 0x0128, 0, "Itilde" }, + { 0x004A, 0112, "J" }, + { 0x0134, 0, "Jcircumflex" }, + { 0xF76A, 0, "Jsmall" }, + { 0x004B, 0113, "K" }, + { 0x039A, 0, "Kappa" }, + { 0x0136, 0, "Kcommaaccent" }, + { 0xF76B, 0, "Ksmall" }, + { 0x004C, 0114, "L" }, + { 0xF6BF, 0, "LL" }, + { 0x0139, 0, "Lacute" }, + { 0x039B, 0, "Lambda" }, + { 0x013D, 0, "Lcaron" }, + { 0x013B, 0, "Lcommaaccent" }, + { 0x013F, 0, "Ldot" }, + { 0x0141, 0350, "Lslash" }, + { 0xF6F9, 0, "Lslashsmall" }, + { 0xF76C, 0, "Lsmall" }, + { 0x004D, 0115, "M" }, + { 0xF6D0, 0, "Macron" }, + { 0xF7AF, 0, "Macronsmall" }, + { 0xF76D, 0, "Msmall" }, + { 0x039C, 0, "Mu" }, + { 0x004E, 0116, "N" }, + { 0x0143, 0, "Nacute" }, + { 0x0147, 0, "Ncaron" }, + { 0x0145, 0, "Ncommaaccent" }, + { 0xF76E, 0, "Nsmall" }, + { 0x00D1, 0, "Ntilde" }, + { 0xF7F1, 0, "Ntildesmall" }, + { 0x039D, 0, "Nu" }, + { 0x004F, 0117, "O" }, + { 0x0152, 0, "OE" }, + { 0xF6FA, 0, "OEsmall" }, + { 0x00D3, 0, "Oacute" }, + { 0xF7F3, 0, "Oacutesmall" }, + { 0x014E, 0, "Obreve" }, + { 0x00D4, 0, "Ocircumflex" }, + { 0xF7F4, 0, "Ocircumflexsmall" }, + { 0x00D6, 0, "Odieresis" }, + { 0xF7F6, 0, "Odieresissmall" }, + { 0xF6FB, 0, "Ogoneksmall" }, + { 0x00D2, 0, "Ograve" }, + { 0xF7F2, 0, "Ogravesmall" }, + { 0x01A0, 0, "Ohorn" }, + { 0x0150, 0, "Ohungarumlaut" }, + { 0x014C, 0, "Omacron" }, + { 0x2126, 0, "Omega" }, + { 0x03A9, 0, "Omega" }, + { 0x038F, 0, "Omegatonos" }, + { 0x039F, 0, "Omicron" }, + { 0x038C, 0, "Omicrontonos" }, + { 0x00D8, 0351, "Oslash" }, + { 0x01FE, 0, "Oslashacute" }, + { 0xF7F8, 0, "Oslashsmall" }, + { 0xF76F, 0, "Osmall" }, + { 0x00D5, 0, "Otilde" }, + { 0xF7F5, 0, "Otildesmall" }, + { 0x0050, 0120, "P" }, + { 0x03A6, 0, "Phi" }, + { 0x03A0, 0, "Pi" }, + { 0x03A8, 0, "Psi" }, + { 0xF770, 0, "Psmall" }, + { 0x0051, 0121, "Q" }, + { 0xF771, 0, "Qsmall" }, + { 0x0052, 0122, "R" }, + { 0x0154, 0, "Racute" }, + { 0x0158, 0, "Rcaron" }, + { 0x0156, 0, "Rcommaaccent" }, + { 0x211C, 0, "Rfraktur" }, + { 0x03A1, 0, "Rho" }, + { 0xF6FC, 0, "Ringsmall" }, + { 0xF772, 0, "Rsmall" }, + { 0x0053, 0123, "S" }, + { 0x250C, 0, "SF010000" }, + { 0x2514, 0, "SF020000" }, + { 0x2510, 0, "SF030000" }, + { 0x2518, 0, "SF040000" }, + { 0x253C, 0, "SF050000" }, + { 0x252C, 0, "SF060000" }, + { 0x2534, 0, "SF070000" }, + { 0x251C, 0, "SF080000" }, + { 0x2524, 0, "SF090000" }, + { 0x2500, 0, "SF100000" }, + { 0x2502, 0, "SF110000" }, + { 0x2561, 0, "SF190000" }, + { 0x2562, 0, "SF200000" }, + { 0x2556, 0, "SF210000" }, + { 0x2555, 0, "SF220000" }, + { 0x2563, 0, "SF230000" }, + { 0x2551, 0, "SF240000" }, + { 0x2557, 0, "SF250000" }, + { 0x255D, 0, "SF260000" }, + { 0x255C, 0, "SF270000" }, + { 0x255B, 0, "SF280000" }, + { 0x255E, 0, "SF360000" }, + { 0x255F, 0, "SF370000" }, + { 0x255A, 0, "SF380000" }, + { 0x2554, 0, "SF390000" }, + { 0x2569, 0, "SF400000" }, + { 0x2566, 0, "SF410000" }, + { 0x2560, 0, "SF420000" }, + { 0x2550, 0, "SF430000" }, + { 0x256C, 0, "SF440000" }, + { 0x2567, 0, "SF450000" }, + { 0x2568, 0, "SF460000" }, + { 0x2564, 0, "SF470000" }, + { 0x2565, 0, "SF480000" }, + { 0x2559, 0, "SF490000" }, + { 0x2558, 0, "SF500000" }, + { 0x2552, 0, "SF510000" }, + { 0x2553, 0, "SF520000" }, + { 0x256B, 0, "SF530000" }, + { 0x256A, 0, "SF540000" }, + { 0x015A, 0, "Sacute" }, + { 0x0160, 0, "Scaron" }, + { 0xF6FD, 0, "Scaronsmall" }, + { 0x015E, 0, "Scedilla" }, + { 0xF6C1, 0, "Scedilla" }, + { 0x015C, 0, "Scircumflex" }, + { 0x0218, 0, "Scommaaccent" }, + { 0x03A3, 0, "Sigma" }, + { 0xF773, 0, "Ssmall" }, + { 0x0054, 0124, "T" }, + { 0x03A4, 0, "Tau" }, + { 0x0166, 0, "Tbar" }, + { 0x0164, 0, "Tcaron" }, + { 0x0162, 0, "Tcommaaccent" }, + { 0x021A, 0, "Tcommaaccent" }, + { 0x0398, 0, "Theta" }, + { 0x00DE, 0, "Thorn" }, + { 0xF7FE, 0, "Thornsmall" }, + { 0xF6FE, 0, "Tildesmall" }, + { 0xF774, 0, "Tsmall" }, + { 0x0055, 0125, "U" }, + { 0x00DA, 0, "Uacute" }, + { 0xF7FA, 0, "Uacutesmall" }, + { 0x016C, 0, "Ubreve" }, + { 0x00DB, 0, "Ucircumflex" }, + { 0xF7FB, 0, "Ucircumflexsmall" }, + { 0x00DC, 0, "Udieresis" }, + { 0xF7FC, 0, "Udieresissmall" }, + { 0x00D9, 0, "Ugrave" }, + { 0xF7F9, 0, "Ugravesmall" }, + { 0x01AF, 0, "Uhorn" }, + { 0x0170, 0, "Uhungarumlaut" }, + { 0x016A, 0, "Umacron" }, + { 0x0172, 0, "Uogonek" }, + { 0x03A5, 0, "Upsilon" }, + { 0x03D2, 0, "Upsilon1" }, + { 0x03AB, 0, "Upsilondieresis" }, + { 0x038E, 0, "Upsilontonos" }, + { 0x016E, 0, "Uring" }, + { 0xF775, 0, "Usmall" }, + { 0x0168, 0, "Utilde" }, + { 0x0056, 0126, "V" }, + { 0xF776, 0, "Vsmall" }, + { 0x0057, 0127, "W" }, + { 0x1E82, 0, "Wacute" }, + { 0x0174, 0, "Wcircumflex" }, + { 0x1E84, 0, "Wdieresis" }, + { 0x1E80, 0, "Wgrave" }, + { 0xF777, 0, "Wsmall" }, + { 0x0058, 0130, "X" }, + { 0x039E, 0, "Xi" }, + { 0xF778, 0, "Xsmall" }, + { 0x0059, 0131, "Y" }, + { 0x00DD, 0, "Yacute" }, + { 0xF7FD, 0, "Yacutesmall" }, + { 0x0176, 0, "Ycircumflex" }, + { 0x0178, 0, "Ydieresis" }, + { 0xF7FF, 0, "Ydieresissmall" }, + { 0x1EF2, 0, "Ygrave" }, + { 0xF779, 0, "Ysmall" }, + { 0x005A, 0132, "Z" }, + { 0x0179, 0, "Zacute" }, + { 0x017D, 0, "Zcaron" }, + { 0xF6FF, 0, "Zcaronsmall" }, + { 0x017B, 0, "Zdotaccent" }, + { 0x0396, 0, "Zeta" }, + { 0xF77A, 0, "Zsmall" }, + { 0x0061, 0141, "a" }, + { 0x00E1, 0, "aacute" }, + { 0x0103, 0, "abreve" }, + { 0x00E2, 0, "acircumflex" }, + { 0x00B4, 0302, "acute" }, + { 0x0301, 0, "acutecomb" }, + { 0x00E4, 0, "adieresis" }, + { 0x00E6, 0361, "ae" }, + { 0x01FD, 0, "aeacute" }, + { 0x2015, 0, "afii00208" }, + { 0x0410, 0, "afii10017" }, + { 0x0411, 0, "afii10018" }, + { 0x0412, 0, "afii10019" }, + { 0x0413, 0, "afii10020" }, + { 0x0414, 0, "afii10021" }, + { 0x0415, 0, "afii10022" }, + { 0x0401, 0, "afii10023" }, + { 0x0416, 0, "afii10024" }, + { 0x0417, 0, "afii10025" }, + { 0x0418, 0, "afii10026" }, + { 0x0419, 0, "afii10027" }, + { 0x041A, 0, "afii10028" }, + { 0x041B, 0, "afii10029" }, + { 0x041C, 0, "afii10030" }, + { 0x041D, 0, "afii10031" }, + { 0x041E, 0, "afii10032" }, + { 0x041F, 0, "afii10033" }, + { 0x0420, 0, "afii10034" }, + { 0x0421, 0, "afii10035" }, + { 0x0422, 0, "afii10036" }, + { 0x0423, 0, "afii10037" }, + { 0x0424, 0, "afii10038" }, + { 0x0425, 0, "afii10039" }, + { 0x0426, 0, "afii10040" }, + { 0x0427, 0, "afii10041" }, + { 0x0428, 0, "afii10042" }, + { 0x0429, 0, "afii10043" }, + { 0x042A, 0, "afii10044" }, + { 0x042B, 0, "afii10045" }, + { 0x042C, 0, "afii10046" }, + { 0x042D, 0, "afii10047" }, + { 0x042E, 0, "afii10048" }, + { 0x042F, 0, "afii10049" }, + { 0x0490, 0, "afii10050" }, + { 0x0402, 0, "afii10051" }, + { 0x0403, 0, "afii10052" }, + { 0x0404, 0, "afii10053" }, + { 0x0405, 0, "afii10054" }, + { 0x0406, 0, "afii10055" }, + { 0x0407, 0, "afii10056" }, + { 0x0408, 0, "afii10057" }, + { 0x0409, 0, "afii10058" }, + { 0x040A, 0, "afii10059" }, + { 0x040B, 0, "afii10060" }, + { 0x040C, 0, "afii10061" }, + { 0x040E, 0, "afii10062" }, + { 0xF6C4, 0, "afii10063" }, + { 0xF6C5, 0, "afii10064" }, + { 0x0430, 0, "afii10065" }, + { 0x0431, 0, "afii10066" }, + { 0x0432, 0, "afii10067" }, + { 0x0433, 0, "afii10068" }, + { 0x0434, 0, "afii10069" }, + { 0x0435, 0, "afii10070" }, + { 0x0451, 0, "afii10071" }, + { 0x0436, 0, "afii10072" }, + { 0x0437, 0, "afii10073" }, + { 0x0438, 0, "afii10074" }, + { 0x0439, 0, "afii10075" }, + { 0x043A, 0, "afii10076" }, + { 0x043B, 0, "afii10077" }, + { 0x043C, 0, "afii10078" }, + { 0x043D, 0, "afii10079" }, + { 0x043E, 0, "afii10080" }, + { 0x043F, 0, "afii10081" }, + { 0x0440, 0, "afii10082" }, + { 0x0441, 0, "afii10083" }, + { 0x0442, 0, "afii10084" }, + { 0x0443, 0, "afii10085" }, + { 0x0444, 0, "afii10086" }, + { 0x0445, 0, "afii10087" }, + { 0x0446, 0, "afii10088" }, + { 0x0447, 0, "afii10089" }, + { 0x0448, 0, "afii10090" }, + { 0x0449, 0, "afii10091" }, + { 0x044A, 0, "afii10092" }, + { 0x044B, 0, "afii10093" }, + { 0x044C, 0, "afii10094" }, + { 0x044D, 0, "afii10095" }, + { 0x044E, 0, "afii10096" }, + { 0x044F, 0, "afii10097" }, + { 0x0491, 0, "afii10098" }, + { 0x0452, 0, "afii10099" }, + { 0x0453, 0, "afii10100" }, + { 0x0454, 0, "afii10101" }, + { 0x0455, 0, "afii10102" }, + { 0x0456, 0, "afii10103" }, + { 0x0457, 0, "afii10104" }, + { 0x0458, 0, "afii10105" }, + { 0x0459, 0, "afii10106" }, + { 0x045A, 0, "afii10107" }, + { 0x045B, 0, "afii10108" }, + { 0x045C, 0, "afii10109" }, + { 0x045E, 0, "afii10110" }, + { 0x040F, 0, "afii10145" }, + { 0x0462, 0, "afii10146" }, + { 0x0472, 0, "afii10147" }, + { 0x0474, 0, "afii10148" }, + { 0xF6C6, 0, "afii10192" }, + { 0x045F, 0, "afii10193" }, + { 0x0463, 0, "afii10194" }, + { 0x0473, 0, "afii10195" }, + { 0x0475, 0, "afii10196" }, + { 0xF6C7, 0, "afii10831" }, + { 0xF6C8, 0, "afii10832" }, + { 0x04D9, 0, "afii10846" }, + { 0x200E, 0, "afii299" }, + { 0x200F, 0, "afii300" }, + { 0x200D, 0, "afii301" }, + { 0x066A, 0, "afii57381" }, + { 0x060C, 0, "afii57388" }, + { 0x0660, 0, "afii57392" }, + { 0x0661, 0, "afii57393" }, + { 0x0662, 0, "afii57394" }, + { 0x0663, 0, "afii57395" }, + { 0x0664, 0, "afii57396" }, + { 0x0665, 0, "afii57397" }, + { 0x0666, 0, "afii57398" }, + { 0x0667, 0, "afii57399" }, + { 0x0668, 0, "afii57400" }, + { 0x0669, 0, "afii57401" }, + { 0x061B, 0, "afii57403" }, + { 0x061F, 0, "afii57407" }, + { 0x0621, 0, "afii57409" }, + { 0x0622, 0, "afii57410" }, + { 0x0623, 0, "afii57411" }, + { 0x0624, 0, "afii57412" }, + { 0x0625, 0, "afii57413" }, + { 0x0626, 0, "afii57414" }, + { 0x0627, 0, "afii57415" }, + { 0x0628, 0, "afii57416" }, + { 0x0629, 0, "afii57417" }, + { 0x062A, 0, "afii57418" }, + { 0x062B, 0, "afii57419" }, + { 0x062C, 0, "afii57420" }, + { 0x062D, 0, "afii57421" }, + { 0x062E, 0, "afii57422" }, + { 0x062F, 0, "afii57423" }, + { 0x0630, 0, "afii57424" }, + { 0x0631, 0, "afii57425" }, + { 0x0632, 0, "afii57426" }, + { 0x0633, 0, "afii57427" }, + { 0x0634, 0, "afii57428" }, + { 0x0635, 0, "afii57429" }, + { 0x0636, 0, "afii57430" }, + { 0x0637, 0, "afii57431" }, + { 0x0638, 0, "afii57432" }, + { 0x0639, 0, "afii57433" }, + { 0x063A, 0, "afii57434" }, + { 0x0640, 0, "afii57440" }, + { 0x0641, 0, "afii57441" }, + { 0x0642, 0, "afii57442" }, + { 0x0643, 0, "afii57443" }, + { 0x0644, 0, "afii57444" }, + { 0x0645, 0, "afii57445" }, + { 0x0646, 0, "afii57446" }, + { 0x0648, 0, "afii57448" }, + { 0x0649, 0, "afii57449" }, + { 0x064A, 0, "afii57450" }, + { 0x064B, 0, "afii57451" }, + { 0x064C, 0, "afii57452" }, + { 0x064D, 0, "afii57453" }, + { 0x064E, 0, "afii57454" }, + { 0x064F, 0, "afii57455" }, + { 0x0650, 0, "afii57456" }, + { 0x0651, 0, "afii57457" }, + { 0x0652, 0, "afii57458" }, + { 0x0647, 0, "afii57470" }, + { 0x06A4, 0, "afii57505" }, + { 0x067E, 0, "afii57506" }, + { 0x0686, 0, "afii57507" }, + { 0x0698, 0, "afii57508" }, + { 0x06AF, 0, "afii57509" }, + { 0x0679, 0, "afii57511" }, + { 0x0688, 0, "afii57512" }, + { 0x0691, 0, "afii57513" }, + { 0x06BA, 0, "afii57514" }, + { 0x06D2, 0, "afii57519" }, + { 0x06D5, 0, "afii57534" }, + { 0x20AA, 0, "afii57636" }, + { 0x05BE, 0, "afii57645" }, + { 0x05C3, 0, "afii57658" }, + { 0x05D0, 0, "afii57664" }, + { 0x05D1, 0, "afii57665" }, + { 0x05D2, 0, "afii57666" }, + { 0x05D3, 0, "afii57667" }, + { 0x05D4, 0, "afii57668" }, + { 0x05D5, 0, "afii57669" }, + { 0x05D6, 0, "afii57670" }, + { 0x05D7, 0, "afii57671" }, + { 0x05D8, 0, "afii57672" }, + { 0x05D9, 0, "afii57673" }, + { 0x05DA, 0, "afii57674" }, + { 0x05DB, 0, "afii57675" }, + { 0x05DC, 0, "afii57676" }, + { 0x05DD, 0, "afii57677" }, + { 0x05DE, 0, "afii57678" }, + { 0x05DF, 0, "afii57679" }, + { 0x05E0, 0, "afii57680" }, + { 0x05E1, 0, "afii57681" }, + { 0x05E2, 0, "afii57682" }, + { 0x05E3, 0, "afii57683" }, + { 0x05E4, 0, "afii57684" }, + { 0x05E5, 0, "afii57685" }, + { 0x05E6, 0, "afii57686" }, + { 0x05E7, 0, "afii57687" }, + { 0x05E8, 0, "afii57688" }, + { 0x05E9, 0, "afii57689" }, + { 0x05EA, 0, "afii57690" }, + { 0xFB2A, 0, "afii57694" }, + { 0xFB2B, 0, "afii57695" }, + { 0xFB4B, 0, "afii57700" }, + { 0xFB1F, 0, "afii57705" }, + { 0x05F0, 0, "afii57716" }, + { 0x05F1, 0, "afii57717" }, + { 0x05F2, 0, "afii57718" }, + { 0xFB35, 0, "afii57723" }, + { 0x05B4, 0, "afii57793" }, + { 0x05B5, 0, "afii57794" }, + { 0x05B6, 0, "afii57795" }, + { 0x05BB, 0, "afii57796" }, + { 0x05B8, 0, "afii57797" }, + { 0x05B7, 0, "afii57798" }, + { 0x05B0, 0, "afii57799" }, + { 0x05B2, 0, "afii57800" }, + { 0x05B1, 0, "afii57801" }, + { 0x05B3, 0, "afii57802" }, + { 0x05C2, 0, "afii57803" }, + { 0x05C1, 0, "afii57804" }, + { 0x05B9, 0, "afii57806" }, + { 0x05BC, 0, "afii57807" }, + { 0x05BD, 0, "afii57839" }, + { 0x05BF, 0, "afii57841" }, + { 0x05C0, 0, "afii57842" }, + { 0x02BC, 0, "afii57929" }, + { 0x2105, 0, "afii61248" }, + { 0x2113, 0, "afii61289" }, + { 0x2116, 0, "afii61352" }, + { 0x202C, 0, "afii61573" }, + { 0x202D, 0, "afii61574" }, + { 0x202E, 0, "afii61575" }, + { 0x200C, 0, "afii61664" }, + { 0x066D, 0, "afii63167" }, + { 0x02BD, 0, "afii64937" }, + { 0x00E0, 0, "agrave" }, + { 0x2135, 0, "aleph" }, + { 0x03B1, 0, "alpha" }, + { 0x03AC, 0, "alphatonos" }, + { 0x0101, 0, "amacron" }, + { 0x0026, 046, "ampersand" }, + { 0xF726, 0, "ampersandsmall" }, + { 0x2220, 0, "angle" }, + { 0x2329, 0, "angleleft" }, + { 0x232A, 0, "angleright" }, + { 0x0387, 0, "anoteleia" }, + { 0x0105, 0, "aogonek" }, + { 0x2248, 0, "approxequal" }, + { 0x00E5, 0, "aring" }, + { 0x01FB, 0, "aringacute" }, + { 0x2194, 0, "arrowboth" }, + { 0x21D4, 0, "arrowdblboth" }, + { 0x21D3, 0, "arrowdbldown" }, + { 0x21D0, 0, "arrowdblleft" }, + { 0x21D2, 0, "arrowdblright" }, + { 0x21D1, 0, "arrowdblup" }, + { 0x2193, 0, "arrowdown" }, + { 0xF8E7, 0, "arrowhorizex" }, + { 0x2190, 0, "arrowleft" }, + { 0x2192, 0, "arrowright" }, + { 0x2191, 0, "arrowup" }, + { 0x2195, 0, "arrowupdn" }, + { 0x21A8, 0, "arrowupdnbse" }, + { 0xF8E6, 0, "arrowvertex" }, + { 0x005E, 0136, "asciicircum" }, + { 0x007E, 0176, "asciitilde" }, + { 0x002A, 052, "asterisk" }, + { 0x2217, 0, "asteriskmath" }, + { 0xF6E9, 0, "asuperior" }, + { 0x0040, 0100, "at" }, + { 0x00E3, 0, "atilde" }, + { 0x0062, 0142, "b" }, + { 0x005C, 0134, "backslash" }, + { 0x007C, 0174, "bar" }, + { 0x03B2, 0, "beta" }, + { 0x2588, 0, "block" }, + { 0xF8F4, 0, "braceex" }, + { 0x007B, 0173, "braceleft" }, + { 0xF8F3, 0, "braceleftbt" }, + { 0xF8F2, 0, "braceleftmid" }, + { 0xF8F1, 0, "bracelefttp" }, + { 0x007D, 0175, "braceright" }, + { 0xF8FE, 0, "bracerightbt" }, + { 0xF8FD, 0, "bracerightmid" }, + { 0xF8FC, 0, "bracerighttp" }, + { 0x005B, 0133, "bracketleft" }, + { 0xF8F0, 0, "bracketleftbt" }, + { 0xF8EF, 0, "bracketleftex" }, + { 0xF8EE, 0, "bracketlefttp" }, + { 0x005D, 0135, "bracketright" }, + { 0xF8FB, 0, "bracketrightbt" }, + { 0xF8FA, 0, "bracketrightex" }, + { 0xF8F9, 0, "bracketrighttp" }, + { 0x02D8, 0306, "breve" }, + { 0x00A6, 0, "brokenbar" }, + { 0xF6EA, 0, "bsuperior" }, + { 0x2022, 0267, "bullet" }, + { 0x0063, 0143, "c" }, + { 0x0107, 0, "cacute" }, + { 0x02C7, 0317, "caron" }, + { 0x21B5, 0, "carriagereturn" }, + { 0x010D, 0, "ccaron" }, + { 0x00E7, 0, "ccedilla" }, + { 0x0109, 0, "ccircumflex" }, + { 0x010B, 0, "cdotaccent" }, + { 0x00B8, 0313, "cedilla" }, + { 0x00A2, 0242, "cent" }, + { 0xF6DF, 0, "centinferior" }, + { 0xF7A2, 0, "centoldstyle" }, + { 0xF6E0, 0, "centsuperior" }, + { 0x03C7, 0, "chi" }, + { 0x25CB, 0, "circle" }, + { 0x2297, 0, "circlemultiply" }, + { 0x2295, 0, "circleplus" }, + { 0x02C6, 0303, "circumflex" }, + { 0x2663, 0, "club" }, + { 0x003A, 072, "colon" }, + { 0x20A1, 0, "colonmonetary" }, + { 0x002C, 054, "comma" }, + { 0xF6C3, 0, "commaaccent" }, + { 0xF6E1, 0, "commainferior" }, + { 0xF6E2, 0, "commasuperior" }, + { 0x2245, 0, "congruent" }, + { 0x00A9, 0, "copyright" }, + { 0xF8E9, 0, "copyrightsans" }, + { 0xF6D9, 0, "copyrightserif" }, + { 0x00A4, 0250, "currency" }, + { 0xF6D1, 0, "cyrBreve" }, + { 0xF6D2, 0, "cyrFlex" }, + { 0xF6D4, 0, "cyrbreve" }, + { 0xF6D5, 0, "cyrflex" }, + { 0x0064, 0144, "d" }, + { 0x2020, 0262, "dagger" }, + { 0x2021, 0263, "daggerdbl" }, + { 0xF6D3, 0, "dblGrave" }, + { 0xF6D6, 0, "dblgrave" }, + { 0x010F, 0, "dcaron" }, + { 0x0111, 0, "dcroat" }, + { 0x00B0, 0, "degree" }, + { 0x03B4, 0, "delta" }, + { 0x2666, 0, "diamond" }, + { 0x00A8, 0310, "dieresis" }, + { 0xF6D7, 0, "dieresisacute" }, + { 0xF6D8, 0, "dieresisgrave" }, + { 0x0385, 0, "dieresistonos" }, + { 0x00F7, 0, "divide" }, + { 0x2593, 0, "dkshade" }, + { 0x2584, 0, "dnblock" }, + { 0x0024, 044, "dollar" }, + { 0xF6E3, 0, "dollarinferior" }, + { 0xF724, 0, "dollaroldstyle" }, + { 0xF6E4, 0, "dollarsuperior" }, + { 0x20AB, 0, "dong" }, + { 0x02D9, 0307, "dotaccent" }, + { 0x0323, 0, "dotbelowcomb" }, + { 0x0131, 0365, "dotlessi" }, + { 0xF6BE, 0, "dotlessj" }, + { 0x22C5, 0, "dotmath" }, + { 0xF6EB, 0, "dsuperior" }, + { 0x0065, 0145, "e" }, + { 0x00E9, 0, "eacute" }, + { 0x0115, 0, "ebreve" }, + { 0x011B, 0, "ecaron" }, + { 0x00EA, 0, "ecircumflex" }, + { 0x00EB, 0, "edieresis" }, + { 0x0117, 0, "edotaccent" }, + { 0x00E8, 0, "egrave" }, + { 0x0038, 070, "eight" }, + { 0x2088, 0, "eightinferior" }, + { 0xF738, 0, "eightoldstyle" }, + { 0x2078, 0, "eightsuperior" }, + { 0x2208, 0, "element" }, + { 0x2026, 0274, "ellipsis" }, + { 0x0113, 0, "emacron" }, + { 0x2014, 0320, "emdash" }, + { 0x2205, 0, "emptyset" }, + { 0x2013, 0261, "endash" }, + { 0x014B, 0, "eng" }, + { 0x0119, 0, "eogonek" }, + { 0x03B5, 0, "epsilon" }, + { 0x03AD, 0, "epsilontonos" }, + { 0x003D, 075, "equal" }, + { 0x2261, 0, "equivalence" }, + { 0x212E, 0, "estimated" }, + { 0xF6EC, 0, "esuperior" }, + { 0x03B7, 0, "eta" }, + { 0x03AE, 0, "etatonos" }, + { 0x00F0, 0, "eth" }, + { 0x0021, 041, "exclam" }, + { 0x203C, 0, "exclamdbl" }, + { 0x00A1, 0241, "exclamdown" }, + { 0xF7A1, 0, "exclamdownsmall" }, + { 0xF721, 0, "exclamsmall" }, + { 0x2203, 0, "existential" }, + { 0x0066, 0146, "f" }, + { 0x2640, 0, "female" }, + { 0xFB00, 0, "ff" }, + { 0xFB03, 0, "ffi" }, + { 0xFB04, 0, "ffl" }, + { 0xFB01, 0256, "fi" }, + { 0x2012, 0, "figuredash" }, + { 0x25A0, 0, "filledbox" }, + { 0x25AC, 0, "filledrect" }, + { 0x0035, 065, "five" }, + { 0x215D, 0, "fiveeighths" }, + { 0x2085, 0, "fiveinferior" }, + { 0xF735, 0, "fiveoldstyle" }, + { 0x2075, 0, "fivesuperior" }, + { 0xFB02, 0257, "fl" }, + { 0x0192, 0246, "florin" }, + { 0x0034, 064, "four" }, + { 0x2084, 0, "fourinferior" }, + { 0xF734, 0, "fouroldstyle" }, + { 0x2074, 0, "foursuperior" }, + { 0x2044, 0244, "fraction" }, + { 0x2215, 0244, "fraction" }, + { 0x20A3, 0, "franc" }, + { 0x0067, 0147, "g" }, + { 0x03B3, 0, "gamma" }, + { 0x011F, 0, "gbreve" }, + { 0x01E7, 0, "gcaron" }, + { 0x011D, 0, "gcircumflex" }, + { 0x0123, 0, "gcommaaccent" }, + { 0x0121, 0, "gdotaccent" }, + { 0x00DF, 0373, "germandbls" }, + { 0x2207, 0, "gradient" }, + { 0x0060, 0301, "grave" }, + { 0x0300, 0, "gravecomb" }, + { 0x003E, 076, "greater" }, + { 0x2265, 0, "greaterequal" }, + { 0x00AB, 0253, "guillemotleft" }, + { 0x00BB, 0273, "guillemotright" }, + { 0x2039, 0254, "guilsinglleft" }, + { 0x203A, 0255, "guilsinglright" }, + { 0x0068, 0150, "h" }, + { 0x0127, 0, "hbar" }, + { 0x0125, 0, "hcircumflex" }, + { 0x2665, 0, "heart" }, + { 0x0309, 0, "hookabovecomb" }, + { 0x2302, 0, "house" }, + { 0x02DD, 0315, "hungarumlaut" }, + { 0x002D, 055, "hyphen" }, + { 0x00AD, 0, "hyphen" }, + { 0xF6E5, 0, "hypheninferior" }, + { 0xF6E6, 0, "hyphensuperior" }, + { 0x0069, 0151, "i" }, + { 0x00ED, 0, "iacute" }, + { 0x012D, 0, "ibreve" }, + { 0x00EE, 0, "icircumflex" }, + { 0x00EF, 0, "idieresis" }, + { 0x00EC, 0, "igrave" }, + { 0x0133, 0, "ij" }, + { 0x012B, 0, "imacron" }, + { 0x221E, 0, "infinity" }, + { 0x222B, 0, "integral" }, + { 0x2321, 0, "integralbt" }, + { 0xF8F5, 0, "integralex" }, + { 0x2320, 0, "integraltp" }, + { 0x2229, 0, "intersection" }, + { 0x25D8, 0, "invbullet" }, + { 0x25D9, 0, "invcircle" }, + { 0x263B, 0, "invsmileface" }, + { 0x012F, 0, "iogonek" }, + { 0x03B9, 0, "iota" }, + { 0x03CA, 0, "iotadieresis" }, + { 0x0390, 0, "iotadieresistonos" }, + { 0x03AF, 0, "iotatonos" }, + { 0xF6ED, 0, "isuperior" }, + { 0x0129, 0, "itilde" }, + { 0x006A, 0152, "j" }, + { 0x0135, 0, "jcircumflex" }, + { 0x006B, 0153, "k" }, + { 0x03BA, 0, "kappa" }, + { 0x0137, 0, "kcommaaccent" }, + { 0x0138, 0, "kgreenlandic" }, + { 0x006C, 0154, "l" }, + { 0x013A, 0, "lacute" }, + { 0x03BB, 0, "lambda" }, + { 0x013E, 0, "lcaron" }, + { 0x013C, 0, "lcommaaccent" }, + { 0x0140, 0, "ldot" }, + { 0x003C, 074, "less" }, + { 0x2264, 0, "lessequal" }, + { 0x258C, 0, "lfblock" }, + { 0x20A4, 0, "lira" }, + { 0xF6C0, 0, "ll" }, + { 0x2227, 0, "logicaland" }, + { 0x00AC, 0, "logicalnot" }, + { 0x2228, 0, "logicalor" }, + { 0x017F, 0, "longs" }, + { 0x25CA, 0, "lozenge" }, + { 0x0142, 0370, "lslash" }, + { 0xF6EE, 0, "lsuperior" }, + { 0x2591, 0, "ltshade" }, + { 0x006D, 0155, "m" }, + { 0x00AF, 0305, "macron" }, + { 0x02C9, 0305, "macron" }, + { 0x2642, 0, "male" }, + { 0x2212, 0, "minus" }, + { 0x2032, 0, "minute" }, + { 0xF6EF, 0, "msuperior" }, + { 0x00B5, 0, "mu" }, + { 0x03BC, 0, "mu" }, + { 0x00D7, 0, "multiply" }, + { 0x266A, 0, "musicalnote" }, + { 0x266B, 0, "musicalnotedbl" }, + { 0x006E, 0156, "n" }, + { 0x0144, 0, "nacute" }, + { 0x0149, 0, "napostrophe" }, + { 0x0148, 0, "ncaron" }, + { 0x0146, 0, "ncommaaccent" }, + { 0x0039, 071, "nine" }, + { 0x2089, 0, "nineinferior" }, + { 0xF739, 0, "nineoldstyle" }, + { 0x2079, 0, "ninesuperior" }, + { 0x2209, 0, "notelement" }, + { 0x2260, 0, "notequal" }, + { 0x2284, 0, "notsubset" }, + { 0x207F, 0, "nsuperior" }, + { 0x00F1, 0, "ntilde" }, + { 0x03BD, 0, "nu" }, + { 0x0023, 043, "numbersign" }, + { 0x006F, 0157, "o" }, + { 0x00F3, 0, "oacute" }, + { 0x014F, 0, "obreve" }, + { 0x00F4, 0, "ocircumflex" }, + { 0x00F6, 0, "odieresis" }, + { 0x0153, 0372, "oe" }, + { 0x02DB, 0316, "ogonek" }, + { 0x00F2, 0, "ograve" }, + { 0x01A1, 0, "ohorn" }, + { 0x0151, 0, "ohungarumlaut" }, + { 0x014D, 0, "omacron" }, + { 0x03C9, 0, "omega" }, + { 0x03D6, 0, "omega1" }, + { 0x03CE, 0, "omegatonos" }, + { 0x03BF, 0, "omicron" }, + { 0x03CC, 0, "omicrontonos" }, + { 0x0031, 061, "one" }, + { 0x2024, 0, "onedotenleader" }, + { 0x215B, 0, "oneeighth" }, + { 0xF6DC, 0, "onefitted" }, + { 0x00BD, 0, "onehalf" }, + { 0x2081, 0, "oneinferior" }, + { 0xF731, 0, "oneoldstyle" }, + { 0x00BC, 0, "onequarter" }, + { 0x00B9, 0, "onesuperior" }, + { 0x2153, 0, "onethird" }, + { 0x25E6, 0, "openbullet" }, + { 0x00AA, 0343, "ordfeminine" }, + { 0x00BA, 0353, "ordmasculine" }, + { 0x221F, 0, "orthogonal" }, + { 0x00F8, 0371, "oslash" }, + { 0x01FF, 0, "oslashacute" }, + { 0xF6F0, 0, "osuperior" }, + { 0x00F5, 0, "otilde" }, + { 0x0070, 0160, "p" }, + { 0x00B6, 0266, "paragraph" }, + { 0x0028, 050, "parenleft" }, + { 0xF8ED, 0, "parenleftbt" }, + { 0xF8EC, 0, "parenleftex" }, + { 0x208D, 0, "parenleftinferior" }, + { 0x207D, 0, "parenleftsuperior" }, + { 0xF8EB, 0, "parenlefttp" }, + { 0x0029, 051, "parenright" }, + { 0xF8F8, 0, "parenrightbt" }, + { 0xF8F7, 0, "parenrightex" }, + { 0x208E, 0, "parenrightinferior" }, + { 0x207E, 0, "parenrightsuperior" }, + { 0xF8F6, 0, "parenrighttp" }, + { 0x2202, 0, "partialdiff" }, + { 0x0025, 045, "percent" }, + { 0x002E, 056, "period" }, + { 0x00B7, 0264, "periodcentered" }, + { 0x2219, 0, "periodcentered" }, + { 0xF6E7, 0, "periodinferior" }, + { 0xF6E8, 0, "periodsuperior" }, + { 0x22A5, 0, "perpendicular" }, + { 0x2030, 0275, "perthousand" }, + { 0x20A7, 0, "peseta" }, + { 0x03C6, 0, "phi" }, + { 0x03D5, 0, "phi1" }, + { 0x03C0, 0, "pi" }, + { 0x002B, 053, "plus" }, + { 0x00B1, 0, "plusminus" }, + { 0x211E, 0, "prescription" }, + { 0x220F, 0, "product" }, + { 0x2282, 0, "propersubset" }, + { 0x2283, 0, "propersuperset" }, + { 0x221D, 0, "proportional" }, + { 0x03C8, 0, "psi" }, + { 0x0071, 0161, "q" }, + { 0x003F, 077, "question" }, + { 0x00BF, 0277, "questiondown" }, + { 0xF7BF, 0, "questiondownsmall" }, + { 0xF73F, 0, "questionsmall" }, + { 0x0022, 042, "quotedbl" }, + { 0x201E, 0271, "quotedblbase" }, + { 0x201C, 0252, "quotedblleft" }, + { 0x201D, 0272, "quotedblright" }, + { 0x2018, 0140, "quoteleft" }, + { 0x201B, 0, "quotereversed" }, + { 0x2019, 047, "quoteright" }, + { 0x201A, 0270, "quotesinglbase" }, + { 0x0027, 0251, "quotesingle" }, + { 0x0072, 0162, "r" }, + { 0x0155, 0, "racute" }, + { 0x221A, 0, "radical" }, + { 0xF8E5, 0, "radicalex" }, + { 0x0159, 0, "rcaron" }, + { 0x0157, 0, "rcommaaccent" }, + { 0x2286, 0, "reflexsubset" }, + { 0x2287, 0, "reflexsuperset" }, + { 0x00AE, 0, "registered" }, + { 0xF8E8, 0, "registersans" }, + { 0xF6DA, 0, "registerserif" }, + { 0x2310, 0, "revlogicalnot" }, + { 0x03C1, 0, "rho" }, + { 0x02DA, 0312, "ring" }, + { 0xF6F1, 0, "rsuperior" }, + { 0x2590, 0, "rtblock" }, + { 0xF6DD, 0, "rupiah" }, + { 0x0073, 0163, "s" }, + { 0x015B, 0, "sacute" }, + { 0x0161, 0, "scaron" }, + { 0x015F, 0, "scedilla" }, + { 0xF6C2, 0, "scedilla" }, + { 0x015D, 0, "scircumflex" }, + { 0x0219, 0, "scommaaccent" }, + { 0x2033, 0, "second" }, + { 0x00A7, 0247, "section" }, + { 0x003B, 073, "semicolon" }, + { 0x0037, 067, "seven" }, + { 0x215E, 0, "seveneighths" }, + { 0x2087, 0, "seveninferior" }, + { 0xF737, 0, "sevenoldstyle" }, + { 0x2077, 0, "sevensuperior" }, + { 0x2592, 0, "shade" }, + { 0x03C3, 0, "sigma" }, + { 0x03C2, 0, "sigma1" }, + { 0x223C, 0, "similar" }, + { 0x0036, 066, "six" }, + { 0x2086, 0, "sixinferior" }, + { 0xF736, 0, "sixoldstyle" }, + { 0x2076, 0, "sixsuperior" }, + { 0x002F, 057, "slash" }, + { 0x263A, 0, "smileface" }, + { 0x0020, 040, "space" }, + { 0x00A0, 040, "space" }, + { 0x2660, 0, "spade" }, + { 0xF6F2, 0, "ssuperior" }, + { 0x00A3, 0243, "sterling" }, + { 0x220B, 0, "suchthat" }, + { 0x2211, 0, "summation" }, + { 0x263C, 0, "sun" }, + { 0x0074, 0164, "t" }, + { 0x03C4, 0, "tau" }, + { 0x0167, 0, "tbar" }, + { 0x0165, 0, "tcaron" }, + { 0x0163, 0, "tcommaaccent" }, + { 0x021B, 0, "tcommaaccent" }, + { 0x2234, 0, "therefore" }, + { 0x03B8, 0, "theta" }, + { 0x03D1, 0, "theta1" }, + { 0x00FE, 0, "thorn" }, + { 0x0033, 063, "three" }, + { 0x215C, 0, "threeeighths" }, + { 0x2083, 0, "threeinferior" }, + { 0xF733, 0, "threeoldstyle" }, + { 0x00BE, 0, "threequarters" }, + { 0xF6DE, 0, "threequartersemdash" }, + { 0x00B3, 0, "threesuperior" }, + { 0x02DC, 0304, "tilde" }, + { 0x0303, 0, "tildecomb" }, + { 0x0384, 0, "tonos" }, + { 0x2122, 0, "trademark" }, + { 0xF8EA, 0, "trademarksans" }, + { 0xF6DB, 0, "trademarkserif" }, + { 0x25BC, 0, "triagdn" }, + { 0x25C4, 0, "triaglf" }, + { 0x25BA, 0, "triagrt" }, + { 0x25B2, 0, "triagup" }, + { 0xF6F3, 0, "tsuperior" }, + { 0x0032, 062, "two" }, + { 0x2025, 0, "twodotenleader" }, + { 0x2082, 0, "twoinferior" }, + { 0xF732, 0, "twooldstyle" }, + { 0x00B2, 0, "twosuperior" }, + { 0x2154, 0, "twothirds" }, + { 0x0075, 0165, "u" }, + { 0x00FA, 0, "uacute" }, + { 0x016D, 0, "ubreve" }, + { 0x00FB, 0, "ucircumflex" }, + { 0x00FC, 0, "udieresis" }, + { 0x00F9, 0, "ugrave" }, + { 0x01B0, 0, "uhorn" }, + { 0x0171, 0, "uhungarumlaut" }, + { 0x016B, 0, "umacron" }, + { 0x005F, 0137, "underscore" }, + { 0x2017, 0, "underscoredbl" }, + { 0x222A, 0, "union" }, + { 0x2200, 0, "universal" }, + { 0x0173, 0, "uogonek" }, + { 0x2580, 0, "upblock" }, + { 0x03C5, 0, "upsilon" }, + { 0x03CB, 0, "upsilondieresis" }, + { 0x03B0, 0, "upsilondieresistonos" }, + { 0x03CD, 0, "upsilontonos" }, + { 0x016F, 0, "uring" }, + { 0x0169, 0, "utilde" }, + { 0x0076, 0166, "v" }, + { 0x0077, 0167, "w" }, + { 0x1E83, 0, "wacute" }, + { 0x0175, 0, "wcircumflex" }, + { 0x1E85, 0, "wdieresis" }, + { 0x2118, 0, "weierstrass" }, + { 0x1E81, 0, "wgrave" }, + { 0x0078, 0170, "x" }, + { 0x03BE, 0, "xi" }, + { 0x0079, 0171, "y" }, + { 0x00FD, 0, "yacute" }, + { 0x0177, 0, "ycircumflex" }, + { 0x00FF, 0, "ydieresis" }, + { 0x00A5, 0245, "yen" }, + { 0x1EF3, 0, "ygrave" }, + { 0x007A, 0172, "z" }, + { 0x017A, 0, "zacute" }, + { 0x017E, 0, "zcaron" }, + { 0x017C, 0, "zdotaccent" }, + { 0x0030, 060, "zero" }, + { 0x2080, 0, "zeroinferior" }, + { 0xF730, 0, "zerooldstyle" }, + { 0x2070, 0, "zerosuperior" }, + { 0x03B6, 0, "zeta" } +}; diff --git a/vcl/unx/generic/fontmanager/afm_keyword_list b/vcl/unx/generic/fontmanager/afm_keyword_list new file mode 100644 index 000000000000..c9bb13467e3e --- /dev/null +++ b/vcl/unx/generic/fontmanager/afm_keyword_list @@ -0,0 +1,62 @@ +%language=C++ +%global-table +%null-strings +%struct-type +struct hash_entry { const char* name; enum parseKey eKey; }; +%% +Ascender,ASCENDER +Ascent,ASCENT +B,CHARBBOX +C,CODE +CC,COMPCHAR +CH,CODEHEX +CapHeight,CAPHEIGHT +CharWidth,CHARWIDTH +CharacterSet,CHARACTERSET +Characters,CHARACTERS +Comment,COMMENT +Descender,DESCENDER +Descent,DESCENT +Em,EM +EncodingScheme,ENCODINGSCHEME +EndCharMetrics,ENDCHARMETRICS +EndComposites,ENDCOMPOSITES +EndDirection,ENDDIRECTION +EndFontMetrics,ENDFONTMETRICS +EndKernData,ENDKERNDATA +EndKernPairs,ENDKERNPAIRS +EndTrackKern,ENDTRACKKERN +FamilyName,FAMILYNAME +FontBBox,FONTBBOX +FontName,FONTNAME +FullName,FULLNAME +IsBaseFont,ISBASEFONT +IsFixedPitch,ISFIXEDPITCH +ItalicAngle,ITALICANGLE +KP,KERNPAIR +KPX,KERNPAIRXAMT +L,LIGATURE +MappingScheme,MAPPINGSCHEME +MetricsSets,METRICSSETS +N,CHARNAME +Notice,NOTICE +PCC,COMPCHARPIECE +StartCharMetrics,STARTCHARMETRICS +StartComposites,STARTCOMPOSITES +StartDirection,STARTDIRECTION +StartFontMetrics,STARTFONTMETRICS +StartKernData,STARTKERNDATA +StartKernPairs,STARTKERNPAIRS +StartTrackKern,STARTTRACKKERN +StdHW,STDHW +StdVW,STDVW +TrackKern,TRACKKERN +UnderlinePosition,UNDERLINEPOSITION +UnderlineThickness,UNDERLINETHICKNESS +V,VVECTOR +Version,VERSION +W,XYWIDTH +W0X,X0WIDTH +WX,XWIDTH +Weight,WEIGHT +XHeight,XHEIGHT diff --git a/vcl/unx/generic/fontmanager/fontcache.cxx b/vcl/unx/generic/fontmanager/fontcache.cxx new file mode 100644 index 000000000000..84f48f27d1cf --- /dev/null +++ b/vcl/unx/generic/fontmanager/fontcache.cxx @@ -0,0 +1,740 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cstdlib> +#include <cstring> + +#include "fontcache.hxx" + +#include "osl/thread.h" + +#include "unotools/atom.hxx" + +#include "tools/stream.hxx" + +#include <rtl/strbuf.hxx> + +#include <unistd.h> +#include <sys/stat.h> + +#if OSL_DEBUG_LEVEL >1 +#include <cstdio> +#endif + +#define CACHE_MAGIC "LibreOffice PspFontCacheFile format 6" + +using namespace std; +using namespace psp; +using namespace utl; + +/* + * FontCache constructor + */ + +FontCache::FontCache() +{ + m_bDoFlush = false; + m_aCacheFile = getOfficePath( UserPath ); + if( !m_aCacheFile.isEmpty() ) + { + m_aCacheFile += "/user/psprint/pspfontcache"; + read(); + } +} + +/* + * FontCache destructor + */ + +FontCache::~FontCache() +{ + clearCache(); +} + +/* + * FontCache::clearCache + */ +void FontCache::clearCache() +{ + for( FontCacheData::iterator dir_it = m_aCache.begin(); dir_it != m_aCache.end(); ++dir_it ) + { + for( FontDirMap::iterator entry_it = dir_it->second.m_aEntries.begin(); entry_it != dir_it->second.m_aEntries.end(); ++entry_it ) + { + for( FontCacheEntry::iterator font_it = entry_it->second.m_aEntry.begin(); font_it != entry_it->second.m_aEntry.end(); ++font_it ) + delete *font_it; + } + } + m_aCache.clear(); +} + +/* + * FontCache::Commit + */ + +void FontCache::flush() +{ + if( ! m_bDoFlush || m_aCacheFile.isEmpty() ) + return; + + SvFileStream aStream; + aStream.Open( m_aCacheFile, StreamMode::WRITE | StreamMode::TRUNC ); + if( ! (aStream.IsOpen() && aStream.IsWritable()) ) + { +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "FontCache::flush: opening cache file %s failed\n", OUStringToOString(m_aCacheFile, osl_getThreadTextEncoding()).getStr() ); +#endif + return; + } + + aStream.SetLineDelimiter( LINEEND_LF ); + aStream.WriteLine( CACHE_MAGIC ); + + PrintFontManager& rManager( PrintFontManager::get() ); + MultiAtomProvider* pAtoms = rManager.m_pAtoms; + + for( FontCacheData::const_iterator dir_it = m_aCache.begin(); dir_it != m_aCache.end(); ++ dir_it ) + { + const FontDirMap& rDir( dir_it->second.m_aEntries ); + + OString aDirectory(rManager.getDirectory(dir_it->first)); + OStringBuffer aLine("FontCacheDirectory:"); + aLine.append(dir_it->second.m_nTimestamp); + aLine.append(':'); + aLine.append(aDirectory); + if( rDir.empty() && dir_it->second.m_bNoFiles ) + aLine.insert(0, "Empty"); + aStream.WriteLine(aLine.makeStringAndClear()); + + for( FontDirMap::const_iterator entry_it = rDir.begin(); entry_it != rDir.end(); ++entry_it ) + { + // insert cache entries + const FontCacheEntry& rEntry( entry_it->second.m_aEntry ); + if( rEntry.empty() ) + continue; + + aLine.append("File:"); + aLine.append(entry_it->first); + aStream.WriteLine(aLine.makeStringAndClear()); + + int nEntrySize = entry_it->second.m_aEntry.size(); + // write: type;nfonts + aLine.append(static_cast<sal_Int32>(rEntry.front()->m_eType)); + aLine.append(';'); + aLine.append(static_cast<sal_Int32>(nEntrySize)); + aStream.WriteLine(aLine.makeStringAndClear()); + + sal_Int32 nSubEntry = 0; + for( FontCacheEntry::const_iterator it = rEntry.begin(); it != rEntry.end(); ++it, nSubEntry++ ) + { + /* + * for each font entry write: + * name[;name[;name]] + * fontnr;PSName;italic;weight;width;pitch;encoding;ascend;descend;leading;vsubst;gxw;gxh;gyw;gyh;useroverride;embed;antialias[;{metricfile,typeflags}][;stylename] + */ + if( nEntrySize > 1 ) + nSubEntry = static_cast<const PrintFontManager::TrueTypeFontFile*>(*it)->m_nCollectionEntry; + else + nSubEntry = 0; + + aLine.append(OUStringToOString(pAtoms->getString( ATOM_FAMILYNAME, (*it)->m_nFamilyName), RTL_TEXTENCODING_UTF8)); + for( ::std::list< int >::const_iterator name_it = (*it)->m_aAliases.begin(); name_it != (*it)->m_aAliases.end(); ++name_it ) + { + const OUString& rAdd( pAtoms->getString( ATOM_FAMILYNAME, *name_it ) ); + if( !rAdd.isEmpty() ) + { + aLine.append(';'); + aLine.append(OUStringToOString(rAdd, RTL_TEXTENCODING_UTF8)); + } + } + aStream.WriteLine(aLine.makeStringAndClear()); + + const OUString& rPSName( pAtoms->getString( ATOM_PSNAME, (*it)->m_nPSName ) ); + aLine.append(nSubEntry); + aLine.append(';'); + aLine.append(OUStringToOString(rPSName, RTL_TEXTENCODING_UTF8)); + aLine.append(';'); + aLine.append(static_cast<sal_Int32>((*it)->m_eItalic)); + aLine.append(';'); + aLine.append(static_cast<sal_Int32>((*it)->m_eWeight)); + aLine.append(';'); + aLine.append(static_cast<sal_Int32>((*it)->m_eWidth)); + aLine.append(';'); + aLine.append(static_cast<sal_Int32>((*it)->m_ePitch)); + aLine.append(';'); + aLine.append(static_cast<sal_Int32>((*it)->m_aEncoding)); + aLine.append(';'); + aLine.append(static_cast<sal_Int32>((*it)->m_nAscend)); + aLine.append(';'); + aLine.append(static_cast<sal_Int32>((*it)->m_nDescend)); + aLine.append(';'); + aLine.append(static_cast<sal_Int32>((*it)->m_nLeading)); + aLine.append(';'); + aLine.append((*it)->m_bHaveVerticalSubstitutedGlyphs ? '1' : '0'); + aLine.append(';'); + aLine.append(static_cast<sal_Int32>((*it)->m_aGlobalMetricX.width )); + aLine.append(';'); + aLine.append(static_cast<sal_Int32>((*it)->m_aGlobalMetricX.height)); + aLine.append(';'); + aLine.append(static_cast<sal_Int32>((*it)->m_aGlobalMetricY.width )); + aLine.append(';'); + aLine.append(static_cast<sal_Int32>((*it)->m_aGlobalMetricY.height)); + aLine.append(';'); + aLine.append((*it)->m_bUserOverride ? '1' : '0'); + aLine.append(';'); + aLine.append(static_cast<sal_Int32>(0)); + aLine.append(';'); + aLine.append(static_cast<sal_Int32>(0)); + + switch( (*it)->m_eType ) + { + case fonttype::Type1: + aLine.append(';'); + aLine.append(static_cast<const PrintFontManager::Type1FontFile*>(*it)->m_aMetricFile); + break; + case fonttype::TrueType: + aLine.append(';'); + aLine.append(static_cast<sal_Int32>(static_cast<const PrintFontManager::TrueTypeFontFile*>(*it)->m_nTypeFlags)); + break; + default: break; + } + if( !(*it)->m_aStyleName.isEmpty() ) + { + aLine.append(';'); + aLine.append(OUStringToOString((*it)->m_aStyleName, RTL_TEXTENCODING_UTF8)); + } + aStream.WriteLine(aLine.makeStringAndClear()); + } + aStream.WriteLine(OString()); + } + } + m_bDoFlush = false; +} + +/* + * FontCache::read + */ + +void FontCache::read() +{ + PrintFontManager& rManager( PrintFontManager::get() ); + MultiAtomProvider* pAtoms = rManager.m_pAtoms; + + SvFileStream aStream( m_aCacheFile, StreamMode::READ ); + if( ! aStream.IsOpen() ) + { +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "FontCache::read: opening cache file %s failed\n", OUStringToOString(m_aCacheFile, osl_getThreadTextEncoding()).getStr() ); +#endif + return; + } + + OString aLine; + aStream.ReadLine( aLine ); + if ( !(aLine == CACHE_MAGIC) ) + { + #if OSL_DEBUG_LEVEL >1 + fprintf( stderr, "FontCache::read: cache file %s fails magic test\n", OUStringToOString(m_aCacheFile, osl_getThreadTextEncoding()).getStr() ); + #endif + return; + } + + int nDir = 0; + FontDirMap* pDir = nullptr; + bool bKeepOnlyUserOverridden = false; + do + { + aStream.ReadLine( aLine ); + if( aLine.startsWith("FontCacheDirectory:") || + aLine.startsWith("EmptyFontCacheDirectory:") ) + { + bool bEmpty = aLine.startsWith("Empty"); + sal_Int32 nSearchIndex = bEmpty ? 24 : 19; + + OString aDir; + sal_Int64 nTimestamp = 0; + sal_Int32 nTEnd = aLine.indexOf( ':', nSearchIndex ); + if( nTEnd != -1 ) + { + OString aTimeStamp = aLine.copy( nSearchIndex, nTEnd - nSearchIndex ); + nTimestamp = aTimeStamp.toInt64(); + aDir = aLine.copy( nTEnd+1 ); + } + else + { + // invalid format, remove + pDir = nullptr; + nDir = 0; + m_bDoFlush = true; + continue; + } + + // is the directory modified ? + struct stat aStat; + if( stat( aDir.getStr(), &aStat ) || + ! S_ISDIR(aStat.st_mode) ) + { + // remove outdated cache data + pDir = nullptr; + nDir = 0; + m_bDoFlush = true; + continue; + } + else + { + nDir = rManager.getDirectoryAtom( aDir, true ); + m_aCache[ nDir ].m_nTimestamp = (sal_Int64)aStat.st_mtime; + m_aCache[ nDir ].m_bNoFiles = bEmpty; + pDir = bEmpty ? nullptr : &m_aCache[ nDir ].m_aEntries; + bKeepOnlyUserOverridden = ((sal_Int64)aStat.st_mtime != nTimestamp); + m_aCache[ nDir ].m_bUserOverrideOnly = bKeepOnlyUserOverridden; + } + } + else if( pDir && aLine.startsWith("File:") ) + { + OString aFile( aLine.copy( 5 ) ); + aStream.ReadLine( aLine ); + + const char* pLine = aLine.getStr(); + + fonttype::type eType = (fonttype::type)atoi( pLine ); + if( eType != fonttype::TrueType && + eType != fonttype::Type1 ) + continue; + while( *pLine && *pLine != ';' ) + pLine++; + if( *pLine != ';' ) + continue; + + pLine++; + sal_Int32 nFonts = atoi( pLine ); + for( int n = 0; n < nFonts; n++ ) + { + aStream.ReadLine( aLine ); + pLine = aLine.getStr(); + int nLen = aLine.getLength(); + + PrintFontManager::PrintFont* pFont = nullptr; + switch( eType ) + { + case fonttype::TrueType: + pFont = new PrintFontManager::TrueTypeFontFile(); + break; + case fonttype::Type1: + pFont = new PrintFontManager::Type1FontFile(); + break; + default: break; + } + + sal_Int32 nIndex; + + for( nIndex = 0; nIndex < nLen && pLine[nIndex] != ';'; nIndex++ ) + ; + + pFont->m_nFamilyName = pAtoms->getAtom( ATOM_FAMILYNAME, + OUString( pLine, nIndex, RTL_TEXTENCODING_UTF8 ), + true ); + while( nIndex < nLen ) + { + sal_Int32 nLastIndex = nIndex+1; + for( nIndex = nLastIndex ; nIndex < nLen && pLine[nIndex] != ';'; nIndex++ ) + ; + if( nIndex - nLastIndex ) + { + OUString aAlias( pLine+nLastIndex, nIndex-nLastIndex, RTL_TEXTENCODING_UTF8 ); + pFont->m_aAliases.push_back( pAtoms->getAtom( ATOM_FAMILYNAME, aAlias, true ) ); + } + } + aStream.ReadLine( aLine ); + pLine = aLine.getStr(); + nLen = aLine.getLength(); + + // get up to 20 token positions + const int nMaxTokens = 20; + int nTokenPos[nMaxTokens]; + nTokenPos[0] = 0; + int nTokens = 1; + for( int i = 0; i < nLen; i++ ) + { + if( pLine[i] == ';' ) + { + nTokenPos[nTokens++] = i+1; + if( nTokens == nMaxTokens ) + break; + } + } + if( nTokens < 18 ) + { + delete pFont; + continue; + } + int nCollEntry = atoi( pLine ); + pFont->m_nPSName = pAtoms->getAtom( ATOM_PSNAME, OUString( pLine + nTokenPos[1], nTokenPos[2]-nTokenPos[1]-1, RTL_TEXTENCODING_UTF8 ), true ); + pFont->m_eItalic = (FontItalic)atoi( pLine+nTokenPos[2] ); + pFont->m_eWeight = (FontWeight)atoi( pLine+nTokenPos[3] ); + pFont->m_eWidth = (FontWidth)atoi( pLine+nTokenPos[4] ); + pFont->m_ePitch = (FontPitch)atoi( pLine+nTokenPos[5] ); + pFont->m_aEncoding = (rtl_TextEncoding)atoi( pLine+nTokenPos[6] ); + pFont->m_nAscend = atoi( pLine + nTokenPos[7] ); + pFont->m_nDescend = atoi( pLine + nTokenPos[8] ); + pFont->m_nLeading = atoi( pLine + nTokenPos[9] ); + pFont->m_bHaveVerticalSubstitutedGlyphs + = (atoi( pLine + nTokenPos[10] ) != 0); + pFont->m_aGlobalMetricX.width + = atoi( pLine + nTokenPos[11] ); + pFont->m_aGlobalMetricX.height + = atoi( pLine + nTokenPos[12] ); + pFont->m_aGlobalMetricY.width + = atoi( pLine + nTokenPos[13] ); + pFont->m_aGlobalMetricY.height + = atoi( pLine + nTokenPos[14] ); + pFont->m_bUserOverride + = (atoi( pLine + nTokenPos[15] ) != 0); + int nStyleTokenNr = 18; + switch( eType ) + { + case fonttype::TrueType: + static_cast<PrintFontManager::TrueTypeFontFile*>(pFont)->m_nTypeFlags = atoi( pLine + nTokenPos[18] ); + static_cast<PrintFontManager::TrueTypeFontFile*>(pFont)->m_nCollectionEntry = nCollEntry; + static_cast<PrintFontManager::TrueTypeFontFile*>(pFont)->m_nDirectory = nDir; + static_cast<PrintFontManager::TrueTypeFontFile*>(pFont)->m_aFontFile = aFile; + nStyleTokenNr++; + break; + case fonttype::Type1: + { + int nTokLen = (nTokens > 19 ) ? nTokenPos[19]-nTokenPos[18]-1 : nLen - nTokenPos[18]; + static_cast<PrintFontManager::Type1FontFile*>(pFont)->m_aMetricFile = OString( pLine + nTokenPos[18], nTokLen ); + static_cast<PrintFontManager::Type1FontFile*>(pFont)->m_nDirectory = nDir; + static_cast<PrintFontManager::Type1FontFile*>(pFont)->m_aFontFile = aFile; + nStyleTokenNr++; + } + break; + default: break; + } + if( nTokens > nStyleTokenNr ) + pFont->m_aStyleName = OUString::intern( pLine + nTokenPos[nStyleTokenNr], + nLen - nTokenPos[nStyleTokenNr], + RTL_TEXTENCODING_UTF8 ); + + bool bObsolete = false; + if( bKeepOnlyUserOverridden ) + { + if( pFont->m_bUserOverride ) + { + OStringBuffer aFilePath(rManager.getDirectory(nDir)); + aFilePath.append('/').append(aFile); + struct stat aStat; + if( stat( aFilePath.getStr(), &aStat ) || + ! S_ISREG( aStat.st_mode ) || + aStat.st_size < 16 ) + { + bObsolete = true; + } + #if OSL_DEBUG_LEVEL > 2 + else + fprintf( stderr, "keeping file %s in outdated cache entry due to user override\n", + aFilePath.getStr() ); + #endif + } + else + bObsolete = true; + } + if( bObsolete ) + { + m_bDoFlush = true; +#if OSL_DEBUG_LEVEL > 2 + fprintf( stderr, "removing obsolete font %s\n", aFile.getStr() ); +#endif + delete pFont; + continue; + } + + FontCacheEntry& rEntry = (*pDir)[aFile].m_aEntry; + rEntry.push_back( pFont ); + } + } + } while( ! aStream.IsEof() ); +} + +/* + * FontCache::copyPrintFont + */ +void FontCache::copyPrintFont( const PrintFontManager::PrintFont* pFrom, PrintFontManager::PrintFont* pTo ) +{ + if( pFrom->m_eType != pTo->m_eType ) + return; + switch( pFrom->m_eType ) + { + case fonttype::TrueType: + static_cast<PrintFontManager::TrueTypeFontFile*>(pTo)->m_nDirectory = static_cast<const PrintFontManager::TrueTypeFontFile*>(pFrom)->m_nDirectory; + static_cast<PrintFontManager::TrueTypeFontFile*>(pTo)->m_aFontFile = static_cast<const PrintFontManager::TrueTypeFontFile*>(pFrom)->m_aFontFile; + static_cast<PrintFontManager::TrueTypeFontFile*>(pTo)->m_nCollectionEntry = static_cast<const PrintFontManager::TrueTypeFontFile*>(pFrom)->m_nCollectionEntry; + static_cast<PrintFontManager::TrueTypeFontFile*>(pTo)->m_nTypeFlags = static_cast<const PrintFontManager::TrueTypeFontFile*>(pFrom)->m_nTypeFlags; + break; + case fonttype::Type1: + static_cast<PrintFontManager::Type1FontFile*>(pTo)->m_nDirectory = static_cast<const PrintFontManager::Type1FontFile*>(pFrom)->m_nDirectory; + static_cast<PrintFontManager::Type1FontFile*>(pTo)->m_aFontFile = static_cast<const PrintFontManager::Type1FontFile*>(pFrom)->m_aFontFile; + static_cast<PrintFontManager::Type1FontFile*>(pTo)->m_aMetricFile = static_cast<const PrintFontManager::Type1FontFile*>(pFrom)->m_aMetricFile; + break; + default: break; + } + pTo->m_nFamilyName = pFrom->m_nFamilyName; + pTo->m_aStyleName = pFrom->m_aStyleName; + pTo->m_aAliases = pFrom->m_aAliases; + pTo->m_nPSName = pFrom->m_nPSName; + pTo->m_eItalic = pFrom->m_eItalic; + pTo->m_eWeight = pFrom->m_eWeight; + pTo->m_eWidth = pFrom->m_eWidth; + pTo->m_ePitch = pFrom->m_ePitch; + pTo->m_aEncoding = pFrom->m_aEncoding; + pTo->m_aGlobalMetricX = pFrom->m_aGlobalMetricX; + pTo->m_aGlobalMetricY = pFrom->m_aGlobalMetricY; + pTo->m_nAscend = pFrom->m_nAscend; + pTo->m_nDescend = pFrom->m_nDescend; + pTo->m_nLeading = pFrom->m_nLeading; + pTo->m_nXMin = pFrom->m_nXMin; + pTo->m_nYMin = pFrom->m_nYMin; + pTo->m_nXMax = pFrom->m_nXMax; + pTo->m_nYMax = pFrom->m_nYMax; + pTo->m_bHaveVerticalSubstitutedGlyphs = pFrom->m_bHaveVerticalSubstitutedGlyphs; + pTo->m_bUserOverride = pFrom->m_bUserOverride; +} + +/* + * FontCache::equalsPrintFont + */ +bool FontCache::equalsPrintFont( const PrintFontManager::PrintFont* pLeft, PrintFontManager::PrintFont* pRight ) +{ + if( pLeft->m_eType != pRight->m_eType ) + return false; + switch( pLeft->m_eType ) + { + case fonttype::TrueType: + { + const PrintFontManager::TrueTypeFontFile* pLT = static_cast<const PrintFontManager::TrueTypeFontFile*>(pLeft); + const PrintFontManager::TrueTypeFontFile* pRT = static_cast<const PrintFontManager::TrueTypeFontFile*>(pRight); + if( pRT->m_nDirectory != pLT->m_nDirectory || + pRT->m_aFontFile != pLT->m_aFontFile || + pRT->m_nCollectionEntry != pLT->m_nCollectionEntry || + pRT->m_nTypeFlags != pLT->m_nTypeFlags ) + return false; + } + break; + case fonttype::Type1: + { + const PrintFontManager::Type1FontFile* pLT = static_cast<const PrintFontManager::Type1FontFile*>(pLeft); + const PrintFontManager::Type1FontFile* pRT = static_cast<const PrintFontManager::Type1FontFile*>(pRight); + if( pRT->m_nDirectory != pLT->m_nDirectory || + pRT->m_aFontFile != pLT->m_aFontFile || + pRT->m_aMetricFile != pLT->m_aMetricFile ) + return false; + } + break; + default: break; + } + if( pRight->m_nFamilyName != pLeft->m_nFamilyName || + pRight->m_aStyleName != pLeft->m_aStyleName || + pRight->m_nPSName != pLeft->m_nPSName || + pRight->m_eItalic != pLeft->m_eItalic || + pRight->m_eWeight != pLeft->m_eWeight || + pRight->m_eWidth != pLeft->m_eWidth || + pRight->m_ePitch != pLeft->m_ePitch || + pRight->m_aEncoding != pLeft->m_aEncoding || + pRight->m_aGlobalMetricX != pLeft->m_aGlobalMetricX || + pRight->m_aGlobalMetricY != pLeft->m_aGlobalMetricY || + pRight->m_nAscend != pLeft->m_nAscend || + pRight->m_nDescend != pLeft->m_nDescend || + pRight->m_nLeading != pLeft->m_nLeading || + pRight->m_nXMin != pLeft->m_nXMin || + pRight->m_nYMin != pLeft->m_nYMin || + pRight->m_nXMax != pLeft->m_nXMax || + pRight->m_nYMax != pLeft->m_nYMax || + pRight->m_bHaveVerticalSubstitutedGlyphs != pLeft->m_bHaveVerticalSubstitutedGlyphs || + pRight->m_bUserOverride != pLeft->m_bUserOverride + ) + return false; + std::list< int >::const_iterator lit, rit; + for( lit = pLeft->m_aAliases.begin(), rit = pRight->m_aAliases.begin(); + lit != pLeft->m_aAliases.end() && rit != pRight->m_aAliases.end() && (*lit) == (*rit); + ++lit, ++rit ) + ; + return lit == pLeft->m_aAliases.end() && rit == pRight->m_aAliases.end(); +} + +/* + * FontCache::clonePrintFont + */ +PrintFontManager::PrintFont* FontCache::clonePrintFont( const PrintFontManager::PrintFont* pOldFont ) +{ + PrintFontManager::PrintFont* pFont = nullptr; + switch( pOldFont->m_eType ) + { + case fonttype::TrueType: + pFont = new PrintFontManager::TrueTypeFontFile(); + break; + case fonttype::Type1: + pFont = new PrintFontManager::Type1FontFile(); + break; + default: break; + } + if( pFont ) + { + copyPrintFont( pOldFont, pFont ); + } + return pFont; + } + +/* + * FontCache::getFontCacheFile + */ +bool FontCache::getFontCacheFile( int nDirID, const OString& rFile, list< PrintFontManager::PrintFont* >& rNewFonts ) const +{ + bool bSuccess = false; + + FontCacheData::const_iterator dir = m_aCache.find( nDirID ); + if( dir != m_aCache.end() ) + { + FontDirMap::const_iterator entry = dir->second.m_aEntries.find( rFile ); + if( entry != dir->second.m_aEntries.end() ) + { + for( FontCacheEntry::const_iterator font = entry->second.m_aEntry.begin(); font != entry->second.m_aEntry.end(); ++font ) + { + bSuccess = true; + PrintFontManager::PrintFont* pFont = clonePrintFont( *font ); + rNewFonts.push_back( pFont ); + } + } + } + return bSuccess; +} + +/* + * FontCache::updateFontCacheEntry + */ +void FontCache::updateFontCacheEntry( const PrintFontManager::PrintFont* pFont, bool bFlush ) +{ + OString aFile; + int nDirID = 0; + switch( pFont->m_eType ) + { + case fonttype::TrueType: + nDirID = static_cast<const PrintFontManager::TrueTypeFontFile*>(pFont)->m_nDirectory; + aFile = static_cast<const PrintFontManager::TrueTypeFontFile*>(pFont)->m_aFontFile; + break; + case fonttype::Type1: + nDirID = static_cast<const PrintFontManager::Type1FontFile*>(pFont)->m_nDirectory; + aFile = static_cast<const PrintFontManager::Type1FontFile*>(pFont)->m_aFontFile; + break; + default: + return; + } + FontCacheData::const_iterator dir = m_aCache.find( nDirID ); + FontDirMap::const_iterator entry; + FontCacheEntry::const_iterator font; + PrintFontManager::PrintFont* pCacheFont = nullptr; + + if( dir != m_aCache.end() ) + { + entry = dir->second.m_aEntries.find( aFile ); + if( entry != dir->second.m_aEntries.end() ) + { + for( font = entry->second.m_aEntry.begin(); font != entry->second.m_aEntry.end(); ++font ) + { + if( (*font)->m_eType == pFont->m_eType && + ( (*font)->m_eType != fonttype::TrueType || + static_cast<const PrintFontManager::TrueTypeFontFile*>(*font)->m_nCollectionEntry == static_cast<const PrintFontManager::TrueTypeFontFile*>(pFont)->m_nCollectionEntry + ) ) + break; + } + if( font != entry->second.m_aEntry.end() ) + pCacheFont = *font; + } + } + else + createCacheDir( nDirID ); + + if( pCacheFont ) + { + if( ! equalsPrintFont( pFont, pCacheFont ) ) + { + copyPrintFont( pFont, pCacheFont ); + m_bDoFlush = true; + } + } + else + { + pCacheFont = clonePrintFont( pFont ); + m_aCache[nDirID].m_aEntries[aFile].m_aEntry.push_back( pCacheFont ); + m_bDoFlush = true; + } + if( bFlush ) + flush(); +} + +/* + * FontCache::listDirectory + */ +bool FontCache::listDirectory( const OString& rDir, std::list< PrintFontManager::PrintFont* >& rNewFonts ) const +{ + PrintFontManager& rManager( PrintFontManager::get() ); + int nDirID = rManager.getDirectoryAtom( rDir ); + + FontCacheData::const_iterator dir = m_aCache.find( nDirID ); + bool bFound = (dir != m_aCache.end()); + + if( bFound && !dir->second.m_bNoFiles ) + { + for( FontDirMap::const_iterator file = dir->second.m_aEntries.begin(); file != dir->second.m_aEntries.end(); ++file ) + { + for( FontCacheEntry::const_iterator font = file->second.m_aEntry.begin(); font != file->second.m_aEntry.end(); ++font ) + { + PrintFontManager::PrintFont* pFont = clonePrintFont( *font ); + rNewFonts.push_back( pFont ); + } + } + } + return bFound; +} + +/* + * FontCache::listDirectory + */ +bool FontCache::scanAdditionalFiles( const OString& rDir ) +{ + PrintFontManager& rManager( PrintFontManager::get() ); + int nDirID = rManager.getDirectoryAtom( rDir ); + FontCacheData::const_iterator dir = m_aCache.find( nDirID ); + bool bFound = (dir != m_aCache.end()); + + return (bFound && dir->second.m_bUserOverrideOnly); +} + +/* + * FontCache::createCacheDir + */ +void FontCache::createCacheDir( int nDirID ) +{ + PrintFontManager& rManager( PrintFontManager::get() ); + + const OString& rDir = rManager.getDirectory( nDirID ); + struct stat aStat; + if( ! stat( rDir.getStr(), &aStat ) ) + m_aCache[nDirID].m_nTimestamp = (sal_Int64)aStat.st_mtime; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/fontmanager/fontconfig.cxx b/vcl/unx/generic/fontmanager/fontconfig.cxx new file mode 100644 index 000000000000..531e62243f78 --- /dev/null +++ b/vcl/unx/generic/fontmanager/fontconfig.cxx @@ -0,0 +1,1293 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "fontcache.hxx" +#include "impfont.hxx" +#include "fontmanager.hxx" +#include <vcl/svapp.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/vclenum.hxx> +#include <vcl/wrkwin.hxx> +#include "fontinstance.hxx" +#include <i18nlangtag/languagetag.hxx> +#include <i18nutil/unicode.hxx> +#include <rtl/strbuf.hxx> +#include <unicode/uchar.h> +#include <unicode/uscript.h> + +using namespace psp; + +#include <fontconfig/fontconfig.h> +#include <ft2build.h> +#include <fontconfig/fcfreetype.h> + +#if defined(ENABLE_DBUS) && defined(ENABLE_PACKAGEKIT) +#include <dbus/dbus-glib.h> +#endif + +#include <cstdio> +#include <cstdarg> + +#include <unotools/atom.hxx> +#include <unotools/configmgr.hxx> + +#include "osl/module.h" +#include "osl/thread.h" +#include "osl/process.h" + +#include "rtl/ustrbuf.hxx" + +#include <utility> +#include <algorithm> + +using namespace osl; + +namespace +{ + typedef std::pair<FcChar8*, FcChar8*> lang_and_element; +} + +class FontCfgWrapper +{ + FcFontSet* m_pOutlineSet; + + void addFontSet( FcSetName ); + + FontCfgWrapper(); + ~FontCfgWrapper(); + +public: + static FontCfgWrapper& get(); + static void release(); + + FcFontSet* getFontSet(); + + void clear(); + +public: + FcResult LocalizedElementFromPattern(FcPattern* pPattern, FcChar8 **family, + const char *elementtype, const char *elementlangtype); +//to-do, make private and add some cleaner accessor methods + std::unordered_map< OString, OString, OStringHash > m_aFontNameToLocalized; + std::unordered_map< OString, OString, OStringHash > m_aLocalizedToCanonical; +private: + void cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname, const std::vector< lang_and_element > &lang_and_elements); + + LanguageTag* m_pLanguageTag; +}; + +FontCfgWrapper::FontCfgWrapper() + : + m_pOutlineSet( nullptr ), + m_pLanguageTag( nullptr ) +{ + FcInit(); +} + +void FontCfgWrapper::addFontSet( FcSetName eSetName ) +{ + /* + add only acceptable outlined fonts to our config, + for future fontconfig use + */ + FcFontSet* pOrig = FcConfigGetFonts( FcConfigGetCurrent(), eSetName ); + if( !pOrig ) + return; + + // filter the font sets to remove obsolete faces + for( int i = 0; i < pOrig->nfont; ++i ) + { + FcPattern* pPattern = pOrig->fonts[i]; + // #i115131# ignore non-outline fonts + FcBool bOutline = FcFalse; + FcResult eOutRes = FcPatternGetBool( pPattern, FC_OUTLINE, 0, &bOutline ); + if( (eOutRes != FcResultMatch) || (bOutline == FcFalse) ) + continue; + FcPatternReference( pPattern ); + FcFontSetAdd( m_pOutlineSet, pPattern ); + } + + // TODO?: FcFontSetDestroy( pOrig ); +} + +namespace +{ + int compareFontNames(const FcPattern *a, const FcPattern *b) + { + FcChar8 *pNameA=nullptr, *pNameB=nullptr; + + bool bHaveA = FcPatternGetString(a, FC_FAMILY, 0, &pNameA) == FcResultMatch; + bool bHaveB = FcPatternGetString(b, FC_FAMILY, 0, &pNameB) == FcResultMatch; + + if (bHaveA && bHaveB) + return strcmp(reinterpret_cast<const char*>(pNameA), reinterpret_cast<const char*>(pNameB)); + + return int(bHaveA) - int(bHaveB); + } + + //Sort fonts so that fonts with the same family name are side-by-side, with + //those with higher version numbers first + class SortFont : public ::std::binary_function< const FcPattern*, const FcPattern*, bool > + { + public: + bool operator()(const FcPattern *a, const FcPattern *b) + { + int comp = compareFontNames(a, b); + if (comp != 0) + return comp < 0; + + int nVersionA=0, nVersionB=0; + + bool bHaveA = FcPatternGetInteger(a, FC_FONTVERSION, 0, &nVersionA) == FcResultMatch; + bool bHaveB = FcPatternGetInteger(b, FC_FONTVERSION, 0, &nVersionB) == FcResultMatch; + + if (bHaveA && bHaveB) + return nVersionA > nVersionB; + + return bHaveA > bHaveB; + } + }; + + //See fdo#30729 for where an old opensymbol installed system-wide can + //clobber the new opensymbol installed locally + + //See if this font is a duplicate with equal attributes which has already been + //inserted, or if it an older version of an inserted fonts. Depends on FcFontSet + //on being sorted with SortFont + bool isPreviouslyDuplicateOrObsoleted(FcFontSet *pFSet, int i) + { + const FcPattern *a = pFSet->fonts[i]; + + FcPattern* pTestPatternA = FcPatternDuplicate(a); + FcPatternDel(pTestPatternA, FC_FILE); + FcPatternDel(pTestPatternA, FC_CHARSET); + FcPatternDel(pTestPatternA, FC_CAPABILITY); + FcPatternDel(pTestPatternA, FC_FONTVERSION); + FcPatternDel(pTestPatternA, FC_LANG); + + bool bIsDup(false); + + // fdo#66715: loop for case of several font files for same font + for (int j = i - 1; 0 <= j && !bIsDup; --j) + { + const FcPattern *b = pFSet->fonts[j]; + + if (compareFontNames(a, b) != 0) + break; + + FcPattern* pTestPatternB = FcPatternDuplicate(b); + FcPatternDel(pTestPatternB, FC_FILE); + FcPatternDel(pTestPatternB, FC_CHARSET); + FcPatternDel(pTestPatternB, FC_CAPABILITY); + FcPatternDel(pTestPatternB, FC_FONTVERSION); + FcPatternDel(pTestPatternB, FC_LANG); + + bIsDup = FcPatternEqual(pTestPatternA, pTestPatternB); + + FcPatternDestroy(pTestPatternB); + } + + FcPatternDestroy(pTestPatternA); + + return bIsDup; + } +} + +FcFontSet* FontCfgWrapper::getFontSet() +{ + if( !m_pOutlineSet ) + { + m_pOutlineSet = FcFontSetCreate(); + addFontSet( FcSetSystem ); + if( FcGetVersion() > 20400 ) // #i85462# prevent crashes + addFontSet( FcSetApplication ); + + ::std::sort(m_pOutlineSet->fonts,m_pOutlineSet->fonts+m_pOutlineSet->nfont,SortFont()); + } + + return m_pOutlineSet; +} + +FontCfgWrapper::~FontCfgWrapper() +{ + clear(); + //To-Do: get gtk vclplug smoketest to pass + //FcFini(); +} + +static FontCfgWrapper* pOneInstance = nullptr; + +FontCfgWrapper& FontCfgWrapper::get() +{ + if( ! pOneInstance ) + pOneInstance = new FontCfgWrapper(); + return *pOneInstance; +} + +void FontCfgWrapper::release() +{ + if( pOneInstance ) + { + delete pOneInstance; + pOneInstance = nullptr; + } +} + +namespace +{ + static FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag); + + FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag) + { + FcChar8* candidate = elements.begin()->second; + /* FIXME-BCP47: once fontconfig supports language tags this + * language-territory stuff needs to be changed! */ + SAL_INFO_IF( !rLangTag.isIsoLocale(), "i18n", "localizedsorter::bestname - not an ISO locale"); + OString sLangMatch(OUStringToOString(rLangTag.getLanguage().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8)); + OString sFullMatch = sLangMatch; + sFullMatch += OString('-'); + sFullMatch += OUStringToOString(rLangTag.getCountry().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8); + + std::vector<lang_and_element>::const_iterator aEnd = elements.end(); + bool alreadyclosematch = false; + bool found_fallback_englishname = false; + for( std::vector<lang_and_element>::const_iterator aIter = elements.begin(); aIter != aEnd; ++aIter ) + { + const char *pLang = reinterpret_cast<const char*>(aIter->first); + if( rtl_str_compare( pLang, sFullMatch.getStr() ) == 0) + { + // both language and country match + candidate = aIter->second; + break; + } + else if( alreadyclosematch ) + { + // current candidate matches lang of lang-TERRITORY + // override candidate only if there is a full match + continue; + } + else if( rtl_str_compare( pLang, sLangMatch.getStr()) == 0) + { + // just the language matches + candidate = aIter->second; + alreadyclosematch = true; + } + else if( found_fallback_englishname ) + { + // already found an english fallback, don't override candidate + // unless there is a better language match + continue; + } + else if( rtl_str_compare( pLang, "en") == 0) + { + // select a fallback candidate of the first english element + // name + candidate = aIter->second; + found_fallback_englishname = true; + } + } + return candidate; + } +} + +//Set up maps to quickly map between a fonts best UI name and all the rest of its names, and vice versa +void FontCfgWrapper::cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname, + const std::vector< lang_and_element > &lang_and_elements) +{ + std::vector<lang_and_element>::const_iterator aEnd = lang_and_elements.end(); + for (std::vector<lang_and_element>::const_iterator aIter = lang_and_elements.begin(); aIter != aEnd; ++aIter) + { + const char *candidate = reinterpret_cast<const char*>(aIter->second); + if (rtl_str_compare(candidate, reinterpret_cast<const char*>(bestfontname)) != 0) + m_aFontNameToLocalized[OString(candidate)] = OString(reinterpret_cast<const char*>(bestfontname)); + } + if (rtl_str_compare(reinterpret_cast<const char*>(origfontname), reinterpret_cast<const char*>(bestfontname)) != 0) + m_aLocalizedToCanonical[OString(reinterpret_cast<const char*>(bestfontname))] = OString(reinterpret_cast<const char*>(origfontname)); +} + +FcResult FontCfgWrapper::LocalizedElementFromPattern(FcPattern* pPattern, FcChar8 **element, + const char *elementtype, const char *elementlangtype) +{ /* e. g.: ^ FC_FAMILY ^ FC_FAMILYLANG */ + FcChar8 *origelement; + FcResult eElementRes = FcPatternGetString( pPattern, elementtype, 0, &origelement ); + *element = origelement; + + if( eElementRes == FcResultMatch) + { + FcChar8* elementlang = nullptr; + if (FcPatternGetString( pPattern, elementlangtype, 0, &elementlang ) == FcResultMatch) + { + std::vector< lang_and_element > lang_and_elements; + lang_and_elements.push_back(lang_and_element(elementlang, *element)); + int k = 1; + while (true) + { + if (FcPatternGetString( pPattern, elementlangtype, k, &elementlang ) != FcResultMatch) + break; + if (FcPatternGetString( pPattern, elementtype, k, element ) != FcResultMatch) + break; + lang_and_elements.push_back(lang_and_element(elementlang, *element)); + ++k; + } + + //possible to-do, sort by UILocale instead of process locale + if (!m_pLanguageTag) + { + rtl_Locale* pLoc = nullptr; + osl_getProcessLocale(&pLoc); + m_pLanguageTag = new LanguageTag(*pLoc); + } + *element = bestname(lang_and_elements, *m_pLanguageTag); + + //if this element is a fontname, map the other names to this best-name + if (rtl_str_compare(elementtype, FC_FAMILY) == 0) + cacheLocalizedFontNames(origelement, *element, lang_and_elements); + } + } + + return eElementRes; +} + +void FontCfgWrapper::clear() +{ + m_aFontNameToLocalized.clear(); + m_aLocalizedToCanonical.clear(); + if( m_pOutlineSet ) + { + FcFontSetDestroy( m_pOutlineSet ); + m_pOutlineSet = nullptr; + } + delete m_pLanguageTag; + m_pLanguageTag = nullptr; +} + +/* + * PrintFontManager::initFontconfig + */ +void PrintFontManager::initFontconfig() +{ + FontCfgWrapper& rWrapper = FontCfgWrapper::get(); + rWrapper.clear(); +} + +namespace +{ + FontWeight convertWeight(int weight) + { + // set weight + if( weight <= FC_WEIGHT_THIN ) + return WEIGHT_THIN; + else if( weight <= FC_WEIGHT_ULTRALIGHT ) + return WEIGHT_ULTRALIGHT; + else if( weight <= FC_WEIGHT_LIGHT ) + return WEIGHT_LIGHT; + else if( weight <= FC_WEIGHT_BOOK ) + return WEIGHT_SEMILIGHT; + else if( weight <= FC_WEIGHT_NORMAL ) + return WEIGHT_NORMAL; + else if( weight <= FC_WEIGHT_MEDIUM ) + return WEIGHT_MEDIUM; + else if( weight <= FC_WEIGHT_SEMIBOLD ) + return WEIGHT_SEMIBOLD; + else if( weight <= FC_WEIGHT_BOLD ) + return WEIGHT_BOLD; + else if( weight <= FC_WEIGHT_ULTRABOLD ) + return WEIGHT_ULTRABOLD; + return WEIGHT_BLACK; + } + + FontItalic convertSlant(int slant) + { + // set italic + if( slant == FC_SLANT_ITALIC ) + return ITALIC_NORMAL; + else if( slant == FC_SLANT_OBLIQUE ) + return ITALIC_OBLIQUE; + return ITALIC_NONE; + } + + FontPitch convertSpacing(int spacing) + { + // set pitch + if( spacing == FC_MONO || spacing == FC_CHARCELL ) + return PITCH_FIXED; + return PITCH_VARIABLE; + } + + // translation: fontconfig enum -> vcl enum + FontWidth convertWidth(int width) + { + if (width == FC_WIDTH_ULTRACONDENSED) + return WIDTH_ULTRA_CONDENSED; + else if (width == FC_WIDTH_EXTRACONDENSED) + return WIDTH_EXTRA_CONDENSED; + else if (width == FC_WIDTH_CONDENSED) + return WIDTH_CONDENSED; + else if (width == FC_WIDTH_SEMICONDENSED) + return WIDTH_SEMI_CONDENSED; + else if (width == FC_WIDTH_SEMIEXPANDED) + return WIDTH_SEMI_EXPANDED; + else if (width == FC_WIDTH_EXPANDED) + return WIDTH_EXPANDED; + else if (width == FC_WIDTH_EXTRAEXPANDED) + return WIDTH_EXTRA_EXPANDED; + else if (width == FC_WIDTH_ULTRAEXPANDED) + return WIDTH_ULTRA_EXPANDED; + return WIDTH_NORMAL; + } +} + +//FontConfig doesn't come with a way to remove an element from a FontSet as far +//as I can see +static void lcl_FcFontSetRemove(FcFontSet* pFSet, int i) +{ + FcPatternDestroy(pFSet->fonts[i]); + + int nTail = pFSet->nfont - (i + 1); + --pFSet->nfont; + if (!nTail) + return; + memmove(pFSet->fonts + i, pFSet->fonts + i + 1, nTail*sizeof(FcPattern*)); +} + +void PrintFontManager::countFontconfigFonts( std::unordered_map<OString, int, OStringHash>& o_rVisitedPaths ) +{ +#if OSL_DEBUG_LEVEL > 1 + int nFonts = 0; +#endif + FontCfgWrapper& rWrapper = FontCfgWrapper::get(); + + FcFontSet* pFSet = rWrapper.getFontSet(); + const bool bMinimalFontset = utl::ConfigManager::IsAvoidConfig(); + if( pFSet ) + { +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "found %d entries in fontconfig fontset\n", pFSet->nfont ); +#endif + for( int i = 0; i < pFSet->nfont; i++ ) + { + FcChar8* file = nullptr; + FcChar8* family = nullptr; + FcChar8* style = nullptr; + FcChar8* format = nullptr; + int slant = 0; + int weight = 0; + int width = 0; + int spacing = 0; + int nCollectionEntry = -1; + FcBool outline = false; + + FcResult eFileRes = FcPatternGetString(pFSet->fonts[i], FC_FILE, 0, &file); + FcResult eFamilyRes = rWrapper.LocalizedElementFromPattern( pFSet->fonts[i], &family, FC_FAMILY, FC_FAMILYLANG ); + if (bMinimalFontset && strncmp(reinterpret_cast<char*>(family), "Liberation", strlen("Liberation"))) + continue; + FcResult eStyleRes = rWrapper.LocalizedElementFromPattern( pFSet->fonts[i], &style, FC_STYLE, FC_STYLELANG ); + FcResult eSlantRes = FcPatternGetInteger(pFSet->fonts[i], FC_SLANT, 0, &slant); + FcResult eWeightRes = FcPatternGetInteger(pFSet->fonts[i], FC_WEIGHT, 0, &weight); + FcResult eWidthRes = FcPatternGetInteger(pFSet->fonts[i], FC_WIDTH, 0, &width); + FcResult eSpacRes = FcPatternGetInteger(pFSet->fonts[i], FC_SPACING, 0, &spacing); + FcResult eOutRes = FcPatternGetBool(pFSet->fonts[i], FC_OUTLINE, 0, &outline); + FcResult eIndexRes = FcPatternGetInteger(pFSet->fonts[i], FC_INDEX, 0, &nCollectionEntry); + FcResult eFormatRes = FcPatternGetString(pFSet->fonts[i], FC_FONTFORMAT, 0, &format); + + if( eFileRes != FcResultMatch || eFamilyRes != FcResultMatch || eOutRes != FcResultMatch ) + continue; + +#if (OSL_DEBUG_LEVEL > 2) + fprintf( stderr, "found font \"%s\" in file %s\n" + " weight = %d, slant = %d, style = \"%s\"\n" + " width = %d, spacing = %d, outline = %d, format %s\n" + , family, file + , eWeightRes == FcResultMatch ? weight : -1 + , eSpacRes == FcResultMatch ? slant : -1 + , eStyleRes == FcResultMatch ? (const char*) style : "<nil>" + , eWeightRes == FcResultMatch ? width : -1 + , eSpacRes == FcResultMatch ? spacing : -1 + , eOutRes == FcResultMatch ? outline : -1 + , eFormatRes == FcResultMatch ? (const char*)format : "<unknown>" + ); +#endif + +// OSL_ASSERT(eOutRes != FcResultMatch || outline); + + // only outline fonts are usable to psprint anyway + if( eOutRes == FcResultMatch && ! outline ) + continue; + + if (isPreviouslyDuplicateOrObsoleted(pFSet, i)) + { +#if OSL_DEBUG_LEVEL > 2 + fprintf(stderr, "Ditching %s as duplicate/obsolete\n", file); +#endif + continue; + } + + // see if this font is already cached + // update attributes + std::list< PrintFont* > aFonts; + OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) ); + splitPath( aOrgPath, aDir, aBase ); + + o_rVisitedPaths[aDir] = 1; + + int nDirID = getDirectoryAtom( aDir, true ); + if( ! m_pFontCache->getFontCacheFile( nDirID, aBase, aFonts ) ) + { +#if OSL_DEBUG_LEVEL > 2 + fprintf( stderr, "file %s not cached\n", aBase.getStr() ); +#endif + // not known, analyze font file to get attributes + // not described by fontconfig (e.g. alias names, PSName) + if (eFormatRes != FcResultMatch) + format = nullptr; + analyzeFontFile( nDirID, aBase, aFonts, reinterpret_cast<char*>(format) ); +#if OSL_DEBUG_LEVEL > 1 + if( aFonts.empty() ) + fprintf( stderr, "Warning: file \"%s\" is unusable to psprint\n", aOrgPath.getStr() ); +#endif + } + if( aFonts.empty() ) + { + //remove font, reuse index + //we want to remove unusable fonts here, in case there is a usable font + //which duplicates the properties of the unusable one + + //not removing the unusable font will risk the usable font being rejected + //as a duplicate by isPreviouslyDuplicateOrObsoleted + lcl_FcFontSetRemove(pFSet, i--); + continue; + } + + int nFamilyName = m_pAtoms->getAtom( ATOM_FAMILYNAME, OStringToOUString( OString( reinterpret_cast<char*>(family) ), RTL_TEXTENCODING_UTF8 ), true ); + PrintFont* pUpdate = aFonts.front(); + std::list<PrintFont*>::const_iterator second_font = aFonts.begin(); + ++second_font; + if( second_font != aFonts.end() ) // more than one font + { + // a collection entry, get the correct index + if( eIndexRes == FcResultMatch && nCollectionEntry != -1 ) + { + for( std::list< PrintFont* >::iterator it = aFonts.begin(); it != aFonts.end(); ++it ) + { + if( (*it)->m_eType == fonttype::TrueType && + static_cast<TrueTypeFontFile*>(*it)->m_nCollectionEntry == nCollectionEntry ) + { + pUpdate = *it; + break; + } + } + // update collection entry + // additional entries will be created in the cache + // if this is a new index (that is if the loop above + // ran to the end of the list) + if( pUpdate->m_eType == fonttype::TrueType ) // sanity check, this should always be the case here + static_cast<TrueTypeFontFile*>(pUpdate)->m_nCollectionEntry = nCollectionEntry; + } + else + { +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "multiple fonts for file, but no index in fontconfig pattern ! (index res = %d collection entry = %d\nfile will not be used\n", eIndexRes, nCollectionEntry ); +#endif + // we have found more than one font in this file + // but fontconfig will not tell us which index is meant + // -> something is in disorder, do not use this font + pUpdate = nullptr; + } + } + + if( pUpdate ) + { + // set family name + if( pUpdate->m_nFamilyName != nFamilyName ) + { + } + if( eWeightRes == FcResultMatch ) + pUpdate->m_eWeight = convertWeight(weight); + if( eWidthRes == FcResultMatch ) + pUpdate->m_eWidth = convertWidth(width); + if( eSpacRes == FcResultMatch ) + pUpdate->m_ePitch = convertSpacing(spacing); + if( eSlantRes == FcResultMatch ) + pUpdate->m_eItalic = convertSlant(slant); + if( eStyleRes == FcResultMatch ) + { + pUpdate->m_aStyleName = OStringToOUString( OString( reinterpret_cast<char*>(style) ), RTL_TEXTENCODING_UTF8 ); + } + + // update font cache + m_pFontCache->updateFontCacheEntry( pUpdate, false ); + // sort into known fonts + fontID aFont = m_nNextFontID++; + m_aFonts[ aFont ] = pUpdate; + m_aFontFileToFontID[ aBase ].insert( aFont ); +#if OSL_DEBUG_LEVEL > 1 + nFonts++; +#endif +#if OSL_DEBUG_LEVEL > 2 + fprintf( stderr, "inserted font %s as fontID %d\n", family, aFont ); +#endif + } + // clean up the fonts we did not put into the list + for( std::list< PrintFont* >::iterator it = aFonts.begin(); it != aFonts.end(); ++it ) + { + if( *it != pUpdate ) + { + m_pFontCache->updateFontCacheEntry( *it, false ); // prepare a cache entry for a collection item + delete *it; + } + } + } + } + + // how does one get rid of the config ? +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "inserted %d fonts from fontconfig\n", nFonts ); +#endif +} + +void PrintFontManager::deinitFontconfig() +{ + FontCfgWrapper::release(); +} + +bool PrintFontManager::addFontconfigDir( const OString& rDirName ) +{ + // workaround for a stability problems in older FC versions + // when handling application specific fonts + const int nVersion = FcGetVersion(); + if( nVersion <= 20400 ) + return false; + const char* pDirName = rDirName.getStr(); + bool bDirOk = (FcConfigAppFontAddDir(FcConfigGetCurrent(), reinterpret_cast<FcChar8 const *>(pDirName) ) == FcTrue); + +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "FcConfigAppFontAddDir( \"%s\") => %d\n", pDirName, bDirOk ); +#endif + + if( !bDirOk ) + return false; + + // load dir-specific fc-config file too if available + const OString aConfFileName = rDirName + "/fc_local.conf"; + FILE* pCfgFile = fopen( aConfFileName.getStr(), "rb" ); + if( pCfgFile ) + { + fclose( pCfgFile); + bool bCfgOk = FcConfigParseAndLoad(FcConfigGetCurrent(), + reinterpret_cast<FcChar8 const *>(aConfFileName.getStr()), FcTrue); + if( !bCfgOk ) + fprintf( stderr, "FcConfigParseAndLoad( \"%s\") => %d\n", aConfFileName.getStr(), bCfgOk ); + } + + return true; +} + +static void addtopattern(FcPattern *pPattern, + FontItalic eItalic, FontWeight eWeight, FontWidth eWidth, FontPitch ePitch) +{ + if( eItalic != ITALIC_DONTKNOW ) + { + int nSlant = FC_SLANT_ROMAN; + switch( eItalic ) + { + case ITALIC_NORMAL: + nSlant = FC_SLANT_ITALIC; + break; + case ITALIC_OBLIQUE: + nSlant = FC_SLANT_OBLIQUE; + break; + default: + break; + } + FcPatternAddInteger(pPattern, FC_SLANT, nSlant); + } + if( eWeight != WEIGHT_DONTKNOW ) + { + int nWeight = FC_WEIGHT_NORMAL; + switch( eWeight ) + { + case WEIGHT_THIN: nWeight = FC_WEIGHT_THIN;break; + case WEIGHT_ULTRALIGHT: nWeight = FC_WEIGHT_ULTRALIGHT;break; + case WEIGHT_LIGHT: nWeight = FC_WEIGHT_LIGHT;break; + case WEIGHT_SEMILIGHT: nWeight = FC_WEIGHT_BOOK;break; + case WEIGHT_NORMAL: nWeight = FC_WEIGHT_NORMAL;break; + case WEIGHT_MEDIUM: nWeight = FC_WEIGHT_MEDIUM;break; + case WEIGHT_SEMIBOLD: nWeight = FC_WEIGHT_SEMIBOLD;break; + case WEIGHT_BOLD: nWeight = FC_WEIGHT_BOLD;break; + case WEIGHT_ULTRABOLD: nWeight = FC_WEIGHT_ULTRABOLD;break; + case WEIGHT_BLACK: nWeight = FC_WEIGHT_BLACK;break; + default: + break; + } + FcPatternAddInteger(pPattern, FC_WEIGHT, nWeight); + } + if( eWidth != WIDTH_DONTKNOW ) + { + int nWidth = FC_WIDTH_NORMAL; + switch( eWidth ) + { + case WIDTH_ULTRA_CONDENSED: nWidth = FC_WIDTH_ULTRACONDENSED;break; + case WIDTH_EXTRA_CONDENSED: nWidth = FC_WIDTH_EXTRACONDENSED;break; + case WIDTH_CONDENSED: nWidth = FC_WIDTH_CONDENSED;break; + case WIDTH_SEMI_CONDENSED: nWidth = FC_WIDTH_SEMICONDENSED;break; + case WIDTH_NORMAL: nWidth = FC_WIDTH_NORMAL;break; + case WIDTH_SEMI_EXPANDED: nWidth = FC_WIDTH_SEMIEXPANDED;break; + case WIDTH_EXPANDED: nWidth = FC_WIDTH_EXPANDED;break; + case WIDTH_EXTRA_EXPANDED: nWidth = FC_WIDTH_EXTRAEXPANDED;break; + case WIDTH_ULTRA_EXPANDED: nWidth = FC_WIDTH_ULTRAEXPANDED;break; + default: + break; + } + FcPatternAddInteger(pPattern, FC_WIDTH, nWidth); + } + if( ePitch != PITCH_DONTKNOW ) + { + int nSpacing = FC_PROPORTIONAL; + switch( ePitch ) + { + case PITCH_FIXED: nSpacing = FC_MONO;break; + case PITCH_VARIABLE: nSpacing = FC_PROPORTIONAL;break; + default: + break; + } + FcPatternAddInteger(pPattern, FC_SPACING, nSpacing); + if (nSpacing == FC_MONO) + FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>("monospace")); + } +} + +namespace +{ + //Someday fontconfig will hopefully use bcp47, see fdo#19869 + //In the meantime try something that will fit to workaround fdo#35118 + OString mapToFontConfigLangTag(const LanguageTag &rLangTag) + { +#if defined(FC_VERSION) && (FC_VERSION >= 20492) + std::shared_ptr<FcStrSet> xLangSet(FcGetLangs(), FcStrSetDestroy); + OString sLangAttrib; + + sLangAttrib = OUStringToOString(rLangTag.getBcp47(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase(); + if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr()))) + { + return sLangAttrib; + } + + sLangAttrib = OUStringToOString(rLangTag.getLanguageAndScript(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase(); + if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr()))) + { + return sLangAttrib; + } + + OString sLang = OUStringToOString(rLangTag.getLanguage(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase(); + OString sRegion = OUStringToOString(rLangTag.getCountry(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase(); + + if (!sRegion.isEmpty()) + { + sLangAttrib = sLang + OString('-') + sRegion; + if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr()))) + { + return sLangAttrib; + } + } + + if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLang.getStr()))) + { + return sLang; + } + + return OString(); +#else + OString sLangAttrib = OUStringToOString(rLangTag.getLanguageAndScript(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase(); + if (sLangAttrib.equalsIgnoreAsciiCase("pa-in")) + sLangAttrib = "pa"; + return sLangAttrib; +#endif + } + + //returns true if the given code-point couldn't possibly be in rLangTag. + bool isImpossibleCodePointForLang(const LanguageTag &rLangTag, sal_uInt32 currentChar) + { + //a non-default script is set, lets believe it + if (rLangTag.hasScript()) + return false; + + int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT); + UScriptCode eScript = static_cast<UScriptCode>(script); + bool bIsImpossible = false; + OUString sLang = rLangTag.getLanguage(); + switch (eScript) + { + //http://en.wiktionary.org/wiki/Category:Oriya_script_languages + case USCRIPT_ORIYA: + bIsImpossible = + sLang != "or" && + sLang != "kxv"; + break; + //http://en.wiktionary.org/wiki/Category:Telugu_script_languages + case USCRIPT_TELUGU: + bIsImpossible = + sLang != "te" && + sLang != "gon" && + sLang != "kfc"; + break; + //http://en.wiktionary.org/wiki/Category:Bengali_script_languages + case USCRIPT_BENGALI: + bIsImpossible = + sLang != "bn" && + sLang != "as" && + sLang != "bpy" && + sLang != "ctg" && + sLang != "sa"; + break; + default: + break; + } + SAL_WARN_IF(bIsImpossible, "vcl", "In glyph fallback throwing away the language property of " + << sLang << " because the detected script for '0x" + << OUString::number(currentChar, 16) + << "' is " << uscript_getName(eScript) + << " and that language doesn't make sense. Autodetecting instead."); + return bIsImpossible; + } + + LanguageTag getExemplarLangTagForCodePoint(sal_uInt32 currentChar) + { + int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT); + UScriptCode eScript = static_cast<UScriptCode>(script); + OStringBuffer aBuf(unicode::getExemplarLanguageForUScriptCode(eScript)); + const char* pScriptCode = uscript_getShortName(eScript); + if (pScriptCode) + aBuf.append('-').append(pScriptCode); + return LanguageTag(OStringToOUString(aBuf.makeStringAndClear(), RTL_TEXTENCODING_UTF8)); + } + +#if defined(ENABLE_DBUS) && defined(ENABLE_PACKAGEKIT) + guint get_xid_for_dbus() + { + const vcl::Window *pTopWindow = Application::IsHeadlessModeEnabled() ? nullptr : Application::GetActiveTopWindow(); + const SystemEnvData* pEnvData = pTopWindow ? pTopWindow->GetSystemData() : nullptr; + return pEnvData ? pEnvData->aWindow : 0; + } +#endif +} + +#if defined(ENABLE_DBUS) && defined(ENABLE_PACKAGEKIT) +IMPL_LINK_NOARG_TYPED(PrintFontManager, autoInstallFontLangSupport, Timer *, void) +{ + guint xid = get_xid_for_dbus(); + + if (!xid) + return; + + GError *error = nullptr; + /* get the DBUS session connection */ + DBusGConnection *session_connection = dbus_g_bus_get(DBUS_BUS_SESSION, &error); + if (error != nullptr) + { + g_debug ("DBUS cannot connect : %s", error->message); + g_error_free (error); + return; + } + + /* get the proxy with gnome-session-manager */ + DBusGProxy *proxy = dbus_g_proxy_new_for_name(session_connection, + "org.freedesktop.PackageKit", + "/org/freedesktop/PackageKit", + "org.freedesktop.PackageKit.Modify"); + if (proxy == nullptr) + { + g_debug("Could not get DBUS proxy: org.freedesktop.PackageKit"); + return; + } + + gchar **fonts = static_cast<gchar**>(g_malloc((m_aCurrentRequests.size() + 1) * sizeof(gchar*))); + gchar **font = fonts; + for (std::vector<OString>::const_iterator aI = m_aCurrentRequests.begin(); aI != m_aCurrentRequests.end(); ++aI) + *font++ = const_cast<gchar*>(aI->getStr()); + *font = nullptr; + gboolean res = dbus_g_proxy_call(proxy, "InstallFontconfigResources", &error, + G_TYPE_UINT, xid, /* xid */ + G_TYPE_STRV, fonts, /* data */ + G_TYPE_STRING, "hide-finished", /* interaction */ + G_TYPE_INVALID, + G_TYPE_INVALID); + /* check the return value */ + if (!res) + g_debug("InstallFontconfigResources method failed"); + + /* check the error value */ + if (error != nullptr) + { + g_debug("InstallFontconfigResources problem : %s", error->message); + g_error_free(error); + } + + g_free(fonts); + g_object_unref(G_OBJECT (proxy)); + m_aCurrentRequests.clear(); +} +#endif + +bool PrintFontManager::Substitute( FontSelectPattern &rPattern, OUString& rMissingCodes ) +{ + bool bRet = false; + + FontCfgWrapper& rWrapper = FontCfgWrapper::get(); + + // build pattern argument for fontconfig query + FcPattern* pPattern = FcPatternCreate(); + + // Prefer scalable fonts + FcPatternAddBool(pPattern, FC_SCALABLE, FcTrue); + + const OString aTargetName = OUStringToOString( rPattern.maTargetName, RTL_TEXTENCODING_UTF8 ); + const FcChar8* pTargetNameUtf8 = reinterpret_cast<FcChar8 const *>(aTargetName.getStr()); + FcPatternAddString(pPattern, FC_FAMILY, pTargetNameUtf8); + + LanguageTag aLangTag(rPattern.meLanguage); + OString aLangAttrib = mapToFontConfigLangTag(aLangTag); + + // Add required Unicode characters, if any + if ( !rMissingCodes.isEmpty() ) + { + FcCharSet *unicodes = FcCharSetCreate(); + for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); ) + { + // also handle unicode surrogates + const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex ); + FcCharSetAddChar( unicodes, nCode ); + //if the codepoint is impossible for this lang tag, then clear it + //and autodetect something useful + if (!aLangAttrib.isEmpty() && isImpossibleCodePointForLang(aLangTag, nCode)) + aLangAttrib.clear(); + //#i105784#/rhbz#527719 improve selection of fallback font + if (aLangAttrib.isEmpty()) + { + aLangTag = getExemplarLangTagForCodePoint(nCode); + aLangAttrib = mapToFontConfigLangTag(aLangTag); + } + } + FcPatternAddCharSet(pPattern, FC_CHARSET, unicodes); + FcCharSetDestroy(unicodes); + } + + if (!aLangAttrib.isEmpty()) + FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr())); + + addtopattern(pPattern, rPattern.GetSlantType(), rPattern.GetWeight(), + rPattern.GetWidthType(), rPattern.GetPitch()); + + // query fontconfig for a substitute + FcConfigSubstitute(FcConfigGetCurrent(), pPattern, FcMatchPattern); + FcDefaultSubstitute(pPattern); + + // process the result of the fontconfig query + FcResult eResult = FcResultNoMatch; + FcFontSet* pFontSet = rWrapper.getFontSet(); + FcPattern* pResult = FcFontSetMatch(FcConfigGetCurrent(), &pFontSet, 1, pPattern, &eResult); + FcPatternDestroy( pPattern ); + + FcFontSet* pSet = nullptr; + if( pResult ) + { + pSet = FcFontSetCreate(); + // info: destroying the pSet destroys pResult implicitly + // since pResult was "added" to pSet + FcFontSetAdd( pSet, pResult ); + } + + if( pSet ) + { + if( pSet->nfont > 0 ) + { + //extract the closest match + FcChar8* file = nullptr; + FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file); + int nCollectionEntry = 0; + FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nCollectionEntry); + if (eIndexRes != FcResultMatch) + nCollectionEntry = 0; + if( eFileRes == FcResultMatch ) + { + OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) ); + splitPath( aOrgPath, aDir, aBase ); + int nDirID = getDirectoryAtom( aDir, true ); + fontID aFont = findFontFileID( nDirID, aBase, nCollectionEntry ); + if( aFont > 0 ) + { + FastPrintFontInfo aInfo; + bRet = getFontFastInfo( aFont, aInfo ); + rPattern.maSearchName = aInfo.m_aFamilyName; + } + } + + SAL_WARN_IF(!bRet, "vcl", "no FC_FILE found, falling back to name search"); + + if (!bRet) + { + FcChar8* family = nullptr; + FcResult eFamilyRes = FcPatternGetString( pSet->fonts[0], FC_FAMILY, 0, &family ); + + // get the family name + if( eFamilyRes == FcResultMatch ) + { + OString sFamily(reinterpret_cast<char*>(family)); + std::unordered_map< OString, OString, OStringHash >::const_iterator aI = + rWrapper.m_aFontNameToLocalized.find(sFamily); + if (aI != rWrapper.m_aFontNameToLocalized.end()) + sFamily = aI->second; + rPattern.maSearchName = OStringToOUString( sFamily, RTL_TEXTENCODING_UTF8 ); + bRet = true; + } + } + + if (bRet) + { + int val = 0; + if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WEIGHT, 0, &val)) + rPattern.SetWeight( convertWeight(val) ); + if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SLANT, 0, &val)) + rPattern.SetItalic( convertSlant(val) ); + if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SPACING, 0, &val)) + rPattern.SetPitch ( convertSpacing(val) ); + if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WIDTH, 0, &val)) + rPattern.SetWidthType ( convertWidth(val) ); + FcBool bEmbolden; + if (FcResultMatch == FcPatternGetBool(pSet->fonts[0], FC_EMBOLDEN, 0, &bEmbolden)) + rPattern.mbEmbolden = bEmbolden; + FcMatrix *pMatrix = nullptr; + if (FcResultMatch == FcPatternGetMatrix(pSet->fonts[0], FC_MATRIX, 0, &pMatrix)) + { + rPattern.maItalicMatrix.xx = pMatrix->xx; + rPattern.maItalicMatrix.xy = pMatrix->xy; + rPattern.maItalicMatrix.yx = pMatrix->yx; + rPattern.maItalicMatrix.yy = pMatrix->yy; + } + } + + // update rMissingCodes by removing resolved unicodes + if( !rMissingCodes.isEmpty() ) + { + std::unique_ptr<sal_uInt32[]> const pRemainingCodes(new sal_uInt32[rMissingCodes.getLength()]); + int nRemainingLen = 0; + FcCharSet* unicodes; + if (!FcPatternGetCharSet(pSet->fonts[0], FC_CHARSET, 0, &unicodes)) + { + for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); ) + { + // also handle unicode surrogates + const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex ); + if (FcCharSetHasChar(unicodes, nCode) != FcTrue) + pRemainingCodes[ nRemainingLen++ ] = nCode; + } + } + OUString sStillMissing(pRemainingCodes.get(), nRemainingLen); +#if defined(ENABLE_DBUS) && defined(ENABLE_PACKAGEKIT) + if (get_xid_for_dbus()) + { + if (sStillMissing == rMissingCodes) //replaced nothing + { + //It'd be better if we could ask packagekit using the + //missing codepoints or some such rather than using + //"language" as a proxy to how fontconfig considers + //scripts to default to a given language. + for (sal_Int32 i = 0; i < nRemainingLen; ++i) + { + LanguageTag aOurTag = getExemplarLangTagForCodePoint(pRemainingCodes[i]); + OString sTag = OUStringToOString(aOurTag.getBcp47(), RTL_TEXTENCODING_UTF8); + if (m_aPreviousLangSupportRequests.find(sTag) != m_aPreviousLangSupportRequests.end()) + continue; + m_aPreviousLangSupportRequests.insert(sTag); + sTag = mapToFontConfigLangTag(aOurTag); + if (!sTag.isEmpty() && m_aPreviousLangSupportRequests.find(sTag) == m_aPreviousLangSupportRequests.end()) + { + OString sReq = OString(":lang=") + sTag; + m_aCurrentRequests.push_back(sReq); + m_aPreviousLangSupportRequests.insert(sTag); + } + } + } + if (!m_aCurrentRequests.empty()) + { + m_aFontInstallerTimer.Stop(); + m_aFontInstallerTimer.Start(); + } + } +#endif + rMissingCodes = sStillMissing; + } + } + + FcFontSetDestroy( pSet ); + } + + return bRet; +} + +FontConfigFontOptions::~FontConfigFontOptions() +{ + FcPatternDestroy(mpPattern); +} + + void *FontConfigFontOptions::GetPattern(void * face, bool bEmbolden) const + { + FcValue value; + value.type = FcTypeFTFace; + value.u.f = face; + FcPatternDel(mpPattern, FC_FT_FACE); + FcPatternAdd (mpPattern, FC_FT_FACE, value, FcTrue); + FcPatternDel(mpPattern, FC_EMBOLDEN); + FcPatternAddBool(mpPattern, FC_EMBOLDEN, bEmbolden ? FcTrue : FcFalse); +#if 0 + FcPatternDel(mpPattern, FC_VERTICAL_LAYOUT); + FcPatternAddBool(mpPattern, FC_VERTICAL_LAYOUT, bVerticalLayout ? FcTrue : FcFalse); +#endif + return mpPattern; + } + +FontConfigFontOptions* PrintFontManager::getFontOptions( + const FastPrintFontInfo& rInfo, int nSize, void (*subcallback)(void*)) +{ + FontCfgWrapper& rWrapper = FontCfgWrapper::get(); + + FontConfigFontOptions* pOptions = nullptr; + FcConfig* pConfig = FcConfigGetCurrent(); + FcPattern* pPattern = FcPatternCreate(); + + OString sFamily = OUStringToOString( rInfo.m_aFamilyName, RTL_TEXTENCODING_UTF8 ); + + std::unordered_map< OString, OString, OStringHash >::const_iterator aI = rWrapper.m_aLocalizedToCanonical.find(sFamily); + if (aI != rWrapper.m_aLocalizedToCanonical.end()) + sFamily = aI->second; + if( !sFamily.isEmpty() ) + FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(sFamily.getStr())); + + addtopattern(pPattern, rInfo.m_eItalic, rInfo.m_eWeight, rInfo.m_eWidth, rInfo.m_ePitch); + FcPatternAddDouble(pPattern, FC_PIXEL_SIZE, nSize); + + FcBool embitmap = true, antialias = true, autohint = true, hinting = true; + int hintstyle = FC_HINT_FULL; + + FcConfigSubstitute(pConfig, pPattern, FcMatchPattern); + if (subcallback) + subcallback(pPattern); + FcDefaultSubstitute(pPattern); + + FcResult eResult = FcResultNoMatch; + FcFontSet* pFontSet = rWrapper.getFontSet(); + FcPattern* pResult = FcFontSetMatch( pConfig, &pFontSet, 1, pPattern, &eResult ); + if( pResult ) + { + FcResult eEmbeddedBitmap = FcPatternGetBool(pResult, + FC_EMBEDDED_BITMAP, 0, &embitmap); + FcResult eAntialias = FcPatternGetBool(pResult, + FC_ANTIALIAS, 0, &antialias); + FcResult eAutoHint = FcPatternGetBool(pResult, + FC_AUTOHINT, 0, &autohint); + FcResult eHinting = FcPatternGetBool(pResult, + FC_HINTING, 0, &hinting); + (void) FcPatternGetInteger(pResult, + FC_HINT_STYLE, 0, &hintstyle); + + pOptions = new FontConfigFontOptions(pResult); + + if( eEmbeddedBitmap == FcResultMatch ) + pOptions->meEmbeddedBitmap = embitmap ? EMBEDDEDBITMAP_TRUE : EMBEDDEDBITMAP_FALSE; + if( eAntialias == FcResultMatch ) + pOptions->meAntiAlias = antialias ? ANTIALIAS_TRUE : ANTIALIAS_FALSE; + if( eAutoHint == FcResultMatch ) + pOptions->meAutoHint = autohint ? AUTOHINT_TRUE : AUTOHINT_FALSE; + if( eHinting == FcResultMatch ) + pOptions->meHinting = hinting ? HINTING_TRUE : HINTING_FALSE; + switch (hintstyle) + { + case FC_HINT_NONE: pOptions->meHintStyle = HINT_NONE; break; + case FC_HINT_SLIGHT: pOptions->meHintStyle = HINT_SLIGHT; break; + case FC_HINT_MEDIUM: pOptions->meHintStyle = HINT_MEDIUM; break; + default: // fall through + case FC_HINT_FULL: pOptions->meHintStyle = HINT_FULL; break; + } + } + + // cleanup + FcPatternDestroy( pPattern ); + + return pOptions; +} + +bool PrintFontManager::matchFont( FastPrintFontInfo& rInfo, const css::lang::Locale& rLocale ) +{ + FontCfgWrapper& rWrapper = FontCfgWrapper::get(); + + FcConfig* pConfig = FcConfigGetCurrent(); + FcPattern* pPattern = FcPatternCreate(); + + // populate pattern with font characteristics + const LanguageTag aLangTag(rLocale); + const OString aLangAttrib = mapToFontConfigLangTag(aLangTag); + if (!aLangAttrib.isEmpty()) + FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr())); + + OString aFamily = OUStringToOString( rInfo.m_aFamilyName, RTL_TEXTENCODING_UTF8 ); + if( !aFamily.isEmpty() ) + FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(aFamily.getStr())); + + addtopattern(pPattern, rInfo.m_eItalic, rInfo.m_eWeight, rInfo.m_eWidth, rInfo.m_ePitch); + + FcConfigSubstitute(pConfig, pPattern, FcMatchPattern); + FcDefaultSubstitute(pPattern); + FcResult eResult = FcResultNoMatch; + FcFontSet *pFontSet = rWrapper.getFontSet(); + FcPattern* pResult = FcFontSetMatch(pConfig, &pFontSet, 1, pPattern, &eResult); + bool bSuccess = false; + if( pResult ) + { + FcFontSet* pSet = FcFontSetCreate(); + FcFontSetAdd( pSet, pResult ); + if( pSet->nfont > 0 ) + { + //extract the closest match + FcChar8* file = nullptr; + FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file); + int nCollectionEntry = 0; + FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nCollectionEntry); + if (eIndexRes != FcResultMatch) + nCollectionEntry = 0; + if( eFileRes == FcResultMatch ) + { + OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) ); + splitPath( aOrgPath, aDir, aBase ); + int nDirID = getDirectoryAtom( aDir, true ); + fontID aFont = findFontFileID( nDirID, aBase, nCollectionEntry ); + if( aFont > 0 ) + bSuccess = getFontFastInfo( aFont, rInfo ); + } + } + // info: destroying the pSet destroys pResult implicitly + // since pResult was "added" to pSet + FcFontSetDestroy( pSet ); + } + + // cleanup + FcPatternDestroy( pPattern ); + + return bSuccess; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/fontmanager/fontmanager.cxx b/vcl/unx/generic/fontmanager/fontmanager.cxx new file mode 100644 index 000000000000..6145f54c163e --- /dev/null +++ b/vcl/unx/generic/fontmanager/fontmanager.cxx @@ -0,0 +1,2222 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <unistd.h> +#include <sys/stat.h> +#include <dirent.h> +#include <stdlib.h> +#include <osl/thread.h> + +#include "unotools/atom.hxx" + +#include "fontcache.hxx" +#include "fontsubset.hxx" +#include "impfont.hxx" +#include "svdata.hxx" +#include "generic/geninst.h" +#include "fontmanager.hxx" +#include "vcl/strhelper.hxx" +#include "vcl/ppdparser.hxx" +#include <vcl/embeddedfontshelper.hxx> +#include <vcl/fontcharmap.hxx> + +#include "tools/urlobj.hxx" +#include "tools/stream.hxx" +#include "tools/debug.hxx" + +#include "osl/file.hxx" +#include "osl/process.h" + +#include "rtl/tencinfo.h" +#include "rtl/ustrbuf.hxx" +#include "rtl/strbuf.hxx" + +#include <sal/macros.h> + +#include "i18nlangtag/applelangid.hxx" +#include "i18nlangtag/mslangid.hxx" + +#include "parseAFM.hxx" +#include "sft.hxx" + +#if OSL_DEBUG_LEVEL > 1 +#include <sys/times.h> +#include <stdio.h> +#endif + +#include <algorithm> +#include <set> +#include <unordered_set> + +#include "adobe_encoding_table.hxx" + +#ifdef CALLGRIND_COMPILE +#include <valgrind/callgrind.h> +#endif + +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include "com/sun/star/beans/XMaterialHolder.hpp" +#include "com/sun/star/beans/NamedValue.hpp" + +using namespace vcl; +using namespace utl; +using namespace psp; +using namespace osl; +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; +using namespace com::sun::star::lang; + +/* + * static helpers + */ + +inline sal_uInt16 getUInt16BE( const sal_uInt8*& pBuffer ) +{ + sal_uInt16 nRet = (sal_uInt16)pBuffer[1] | + (((sal_uInt16)pBuffer[0]) << 8); + pBuffer+=2; + return nRet; +} + +static FontWeight parseWeight( const OString& rWeight ) +{ + FontWeight eWeight = WEIGHT_DONTKNOW; + if (rWeight.indexOf("bold") != -1) + { + if (rWeight.indexOf("emi") != -1) // semi, demi + eWeight = WEIGHT_SEMIBOLD; + else if (rWeight.indexOf("ultra") != -1) + eWeight = WEIGHT_ULTRABOLD; + else + eWeight = WEIGHT_BOLD; + } + else if (rWeight.indexOf("heavy") != -1) + eWeight = WEIGHT_BOLD; + else if (rWeight.indexOf("light") != -1) + { + if (rWeight.indexOf("emi") != -1) // semi, demi + eWeight = WEIGHT_SEMILIGHT; + else if (rWeight.indexOf("ultra") != -1) + eWeight = WEIGHT_ULTRALIGHT; + else + eWeight = WEIGHT_LIGHT; + } + else if (rWeight.indexOf("black") != -1) + eWeight = WEIGHT_BLACK; + else if (rWeight == "demi") + eWeight = WEIGHT_SEMIBOLD; + else if ((rWeight == "book") || + (rWeight == "semicondensed")) + eWeight = WEIGHT_LIGHT; + else if ((rWeight == "medium") || (rWeight == "roman")) + eWeight = WEIGHT_MEDIUM; + else + eWeight = WEIGHT_NORMAL; + return eWeight; +} + +/* + * PrintFont implementations + */ +PrintFontManager::PrintFont::PrintFont( fonttype::type eType ) : + m_eType( eType ), + m_nFamilyName( 0 ), + m_nPSName( 0 ), + m_eItalic( ITALIC_DONTKNOW ), + m_eWidth( WIDTH_DONTKNOW ), + m_eWeight( WEIGHT_DONTKNOW ), + m_ePitch( PITCH_DONTKNOW ), + m_aEncoding( RTL_TEXTENCODING_DONTKNOW ), + m_bFontEncodingOnly( false ), + m_pMetrics( nullptr ), + m_nAscend( 0 ), + m_nDescend( 0 ), + m_nLeading( 0 ), + m_nXMin( 0 ), + m_nYMin( 0 ), + m_nXMax( 0 ), + m_nYMax( 0 ), + m_bHaveVerticalSubstitutedGlyphs( false ), + m_bUserOverride( false ) +{ +} + +PrintFontManager::PrintFont::~PrintFont() +{ + delete m_pMetrics; +} + +PrintFontManager::Type1FontFile::~Type1FontFile() +{ +} + +PrintFontManager::TrueTypeFontFile::TrueTypeFontFile() +: PrintFont( fonttype::TrueType ) +, m_nDirectory( 0 ) +, m_nCollectionEntry( 0 ) +, m_nTypeFlags( TYPEFLAG_INVALID ) +{} + +PrintFontManager::TrueTypeFontFile::~TrueTypeFontFile() +{ +} + +bool PrintFontManager::Type1FontFile::queryMetricPage( int /*nPage*/, MultiAtomProvider* pProvider ) +{ + return readAfmMetrics( pProvider, false, false ); +} + +bool PrintFontManager::TrueTypeFontFile::queryMetricPage( int nPage, MultiAtomProvider* /*pProvider*/ ) +{ + bool bSuccess = false; + + OString aFile( PrintFontManager::get().getFontFile( this ) ); + + TrueTypeFont* pTTFont = nullptr; + + if( OpenTTFontFile( aFile.getStr(), m_nCollectionEntry, &pTTFont ) == SF_OK ) + { + if( ! m_pMetrics ) + { + m_pMetrics = new PrintFontMetrics; + memset (m_pMetrics->m_aPages, 0, sizeof(m_pMetrics->m_aPages)); + } + m_pMetrics->m_aPages[ nPage/8 ] |= (1 << ( nPage & 7 )); + int i; + sal_uInt16 table[256], table_vert[256]; + + for( i = 0; i < 256; i++ ) + table[ i ] = 256*nPage + i; + + int nCharacters = nPage < 255 ? 256 : 254; + MapString( pTTFont, table, nCharacters, nullptr, false ); + TTSimpleGlyphMetrics* pMetrics = GetTTSimpleCharMetrics( pTTFont, nPage*256, nCharacters, false ); + if( pMetrics ) + { + for( i = 0; i < nCharacters; i++ ) + { + if( table[i] ) + { + CharacterMetric& rChar = m_pMetrics->m_aMetrics[ nPage*256 + i ]; + rChar.width = pMetrics[ i ].adv; + rChar.height = m_aGlobalMetricX.height; + } + } + + free( pMetrics ); + } + + for( i = 0; i < 256; i++ ) + table_vert[ i ] = 256*nPage + i; + MapString( pTTFont, table_vert, nCharacters, nullptr, true ); + pMetrics = GetTTSimpleCharMetrics( pTTFont, nPage*256, nCharacters, true ); + if( pMetrics ) + { + for( i = 0; i < nCharacters; i++ ) + { + if( table_vert[i] ) + { + CharacterMetric& rChar = m_pMetrics->m_aMetrics[ nPage*256 + i + ( 1 << 16 ) ]; + rChar.width = m_aGlobalMetricY.width; + rChar.height = pMetrics[ i ].adv; + if( table_vert[i] != table[i] ) + m_pMetrics->m_bVerticalSubstitutions[ nPage*256 + i ] = true; + } + } + free( pMetrics ); + } + CloseTTFont( pTTFont ); + bSuccess = true; + } + return bSuccess; +} + +/* #i73387# There seem to be fonts with a rather unwell chosen family name +* consider e.g. "Helvetica Narrow" which defines its family as "Helvetica" +* It can really only be distinguished by its PSName and FullName. Both of +* which are not user presentable in OOo. So replace it by something sensible. +* +* If other fonts feature this behaviour, insert them to the map. +*/ +static bool familyNameOverride( const OUString& i_rPSname, OUString& o_rFamilyName ) +{ + static std::unordered_map< OUString, OUString, OUStringHash > aPSNameToFamily( 16 ); + if( aPSNameToFamily.empty() ) // initialization + { + aPSNameToFamily[ "Helvetica-Narrow" ] = "Helvetica Narrow"; + aPSNameToFamily[ "Helvetica-Narrow-Bold" ] = "Helvetica Narrow"; + aPSNameToFamily[ "Helvetica-Narrow-BoldOblique" ] = "Helvetica Narrow"; + aPSNameToFamily[ "Helvetica-Narrow-Oblique" ] = "Helvetica Narrow"; + } + std::unordered_map<OUString,OUString,OUStringHash>::const_iterator it = + aPSNameToFamily.find( i_rPSname ); + bool bReplaced = (it != aPSNameToFamily.end() ); + if( bReplaced ) + o_rFamilyName = it->second; + return bReplaced; +}; + +bool PrintFontManager::PrintFont::readAfmMetrics( MultiAtomProvider* pProvider, bool bFillEncodingvector, bool bOnlyGlobalAttributes ) +{ + PrintFontManager& rManager( PrintFontManager::get() ); + const OString& rFileName = rManager.getAfmFile( this ); + + FontInfo* pInfo = nullptr; + parseFile( rFileName.getStr(), &pInfo, P_ALL ); + if( ! pInfo || ! pInfo->numOfChars ) + { + if( pInfo ) + freeFontInfo( pInfo ); + return false; + } + + m_aEncodingVector.clear(); + m_aEncodingVectorPriority.clear(); + // fill in global info + + // PSName + OUString aPSName( OStringToOUString( pInfo->gfi->fontName, RTL_TEXTENCODING_ISO_8859_1 ) ); + m_nPSName = pProvider->getAtom( ATOM_PSNAME, aPSName, true ); + + // family name (if not already set) + OUString aFamily; + if( ! m_nFamilyName ) + { + aFamily = OStringToOUString( pInfo->gfi->familyName, RTL_TEXTENCODING_ISO_8859_1 ); + if( aFamily.isEmpty() ) + { + aFamily = OStringToOUString( pInfo->gfi->fontName, RTL_TEXTENCODING_ISO_8859_1 ); + sal_Int32 nIndex = 0; + aFamily = aFamily.getToken( 0, '-', nIndex ); + } + familyNameOverride( aPSName, aFamily ); + m_nFamilyName = pProvider->getAtom( ATOM_FAMILYNAME, aFamily, true ); + } + else + aFamily = pProvider->getString( ATOM_FAMILYNAME, m_nFamilyName ); + + // style name: if fullname begins with family name + // interpret the rest of fullname as style + if( m_aStyleName.isEmpty() && pInfo->gfi->fullName && *pInfo->gfi->fullName ) + { + OUString aFullName( OStringToOUString( pInfo->gfi->fullName, RTL_TEXTENCODING_ISO_8859_1 ) ); + if( aFullName.startsWith( aFamily ) ) + m_aStyleName = WhitespaceToSpace( aFullName.copy( aFamily.getLength() ) ); + } + + // italic + if( pInfo->gfi->italicAngle > 0 ) + m_eItalic = ITALIC_OBLIQUE; + else if( pInfo->gfi->italicAngle < 0 ) + m_eItalic = ITALIC_NORMAL; + else + m_eItalic = ITALIC_NONE; + + // weight + OString aWeight( pInfo->gfi->weight ); + m_eWeight = parseWeight( aWeight.toAsciiLowerCase() ); + + // pitch + m_ePitch = pInfo->gfi->isFixedPitch ? PITCH_FIXED : PITCH_VARIABLE; + + // encoding - only set if unknown + int nAdobeEncoding = 0; + if( pInfo->gfi->encodingScheme ) + { + if( !strcmp( pInfo->gfi->encodingScheme, "AdobeStandardEncoding" ) ) + nAdobeEncoding = 1; + else if( !strcmp( pInfo->gfi->encodingScheme, "ISO10646-1" ) ) + { + nAdobeEncoding = 1; + m_aEncoding = RTL_TEXTENCODING_UNICODE; + } + else if( !strcmp( pInfo->gfi->encodingScheme, "Symbol") ) + nAdobeEncoding = 2; + else if( !strcmp( pInfo->gfi->encodingScheme, "FontSpecific") ) + nAdobeEncoding = 3; + + if( m_aEncoding == RTL_TEXTENCODING_DONTKNOW ) + m_aEncoding = nAdobeEncoding == 1 ? + RTL_TEXTENCODING_ADOBE_STANDARD : RTL_TEXTENCODING_SYMBOL; + } + else if( m_aEncoding == RTL_TEXTENCODING_DONTKNOW ) + m_aEncoding = RTL_TEXTENCODING_ADOBE_STANDARD; + + // try to parse the font name and decide whether it might be a + // japanese font. Who invented this PITA ? + OUString aPSNameLastToken( aPSName.copy( aPSName.lastIndexOf( '-' )+1 ) ); + if( aPSNameLastToken == "H" || + aPSNameLastToken == "V" ) + { + static const char* pEncs[] = + { + "EUC", + "RKSJ", + "SJ" + }; + static const rtl_TextEncoding aEncs[] = + { + RTL_TEXTENCODING_EUC_JP, + RTL_TEXTENCODING_SHIFT_JIS, + RTL_TEXTENCODING_JIS_X_0208 + }; + + for( unsigned int enc = 0; enc < SAL_N_ELEMENTS( aEncs ) && m_aEncoding == RTL_TEXTENCODING_DONTKNOW; enc++ ) + { + sal_Int32 nIndex = 0, nOffset = 1; + do + { + OUString aToken( aPSName.getToken( nOffset, '-', nIndex ) ); + if( nIndex == -1 ) + break; + nOffset = 0; + if( aToken.equalsAscii( pEncs[enc] ) ) + { + m_aEncoding = aEncs[ enc ]; + m_bFontEncodingOnly = true; + } + } while( nIndex != -1 ); + } + + // default is jis + if( m_aEncoding == RTL_TEXTENCODING_DONTKNOW ) + m_aEncoding = RTL_TEXTENCODING_JIS_X_0208; +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "Encoding %d for %s\n", m_aEncoding, pInfo->gfi->fontName ); +#endif + } + + // #i37313# check if Fontspecific is not rather some character encoding + if( nAdobeEncoding == 3 && m_aEncoding == RTL_TEXTENCODING_SYMBOL ) + { + bool bYFound = false; + bool bQFound = false; + CharMetricInfo* pChar = pInfo->cmi; + for( int j = 0; j < pInfo->numOfChars && ! (bYFound && bQFound); j++ ) + { + if( pChar[j].name ) + { + if( pChar[j].name[0] == 'Y' && pChar[j].name[1] == 0 ) + bYFound = true; + else if( pChar[j].name[0] == 'Q' && pChar[j].name[1] == 0 ) + bQFound = true; + } + } + if( bQFound && bYFound ) + { + #if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "setting FontSpecific font %s (file %s) to unicode\n", + pInfo->gfi->fontName, + rFileName.getStr() + ); + #endif + nAdobeEncoding = 4; + m_aEncoding = RTL_TEXTENCODING_UNICODE; + bFillEncodingvector = false; // will be filled anyway, don't do the work twice + } + } + + // ascend + m_nAscend = pInfo->gfi->fontBBox.ury; + + // descend + // descends have opposite sign of our definition + m_nDescend = -pInfo->gfi->fontBBox.lly; + + // fallback to ascender, descender + // interesting: the BBox seems to describe Ascender and Descender better + // as we understand it + if( m_nAscend == 0 ) + m_nAscend = pInfo->gfi->ascender; + if( m_nDescend == 0) + m_nDescend = -pInfo->gfi->descender; + + m_nLeading = m_nAscend + m_nDescend - 1000; + + delete m_pMetrics; + m_pMetrics = new PrintFontMetrics; + // mark all pages as queried (or clear if only global font info queried) + memset( m_pMetrics->m_aPages, bOnlyGlobalAttributes ? 0 : 0xff, sizeof( m_pMetrics->m_aPages ) ); + + m_aGlobalMetricX.width = m_aGlobalMetricY.width = + pInfo->gfi->charwidth ? pInfo->gfi->charwidth : pInfo->gfi->fontBBox.urx; + m_aGlobalMetricX.height = m_aGlobalMetricY.height = + pInfo->gfi->capHeight ? pInfo->gfi->capHeight : pInfo->gfi->fontBBox.ury; + + m_nXMin = pInfo->gfi->fontBBox.llx; + m_nYMin = pInfo->gfi->fontBBox.lly; + m_nXMax = pInfo->gfi->fontBBox.urx; + m_nYMax = pInfo->gfi->fontBBox.ury; + + if( bFillEncodingvector || !bOnlyGlobalAttributes ) + { + // fill in character metrics + + // first transform the character codes to unicode + // note: this only works with single byte encodings + std::unique_ptr<sal_Unicode[]> const pUnicodes(new sal_Unicode[pInfo->numOfChars]); + CharMetricInfo* pChar = pInfo->cmi; + int i; + + for( i = 0; i < pInfo->numOfChars; i++, pChar++ ) + { + if( nAdobeEncoding == 4 ) + { + if( pChar->name ) + { + pUnicodes[i] = 0; + std::list< sal_Unicode > aCodes = rManager.getUnicodeFromAdobeName( pChar->name ); + for( std::list< sal_Unicode >::const_iterator it = aCodes.begin(); it != aCodes.end(); ++it ) + { + if( *it != 0 ) + { + m_aEncodingVector[ *it ] = pChar->code; + if( pChar->code == -1 ) + m_aNonEncoded[ *it ] = pChar->name; + if( ! pUnicodes[i] ) // map the first + pUnicodes[i] = *it; + } + } + } + } + else if( pChar->code != -1 ) + { + if( nAdobeEncoding == 3 && m_aEncoding == RTL_TEXTENCODING_SYMBOL ) + { + pUnicodes[i] = pChar->code + 0xf000; + if( bFillEncodingvector ) + { + m_aEncodingVector[ pUnicodes[i] ] = pChar->code; + m_aEncodingVectorPriority.insert(pUnicodes[i]); + } + continue; + } + + if( m_aEncoding == RTL_TEXTENCODING_UNICODE ) + { + pUnicodes[i] = (sal_Unicode)pChar->code; + continue; + } + + OStringBuffer aTranslate; + if( pChar->code & 0xff000000 ) + aTranslate.append((char)(pChar->code >> 24)); + if( pChar->code & 0xffff0000 ) + aTranslate.append((char)((pChar->code & 0x00ff0000) >> 16)); + if( pChar->code & 0xffffff00 ) + aTranslate.append((char)((pChar->code & 0x0000ff00) >> 8 )); + aTranslate.append((char)(pChar->code & 0xff)); + OUString aUni(OStringToOUString(aTranslate.makeStringAndClear(), m_aEncoding)); + pUnicodes[i] = aUni.toChar(); + } + else + pUnicodes[i] = 0; + } + + // now fill in the character metrics + // parseAFM.cxx effectively only supports direction 0 (horizontal) + pChar = pInfo->cmi; + CharacterMetric aMetric; + for( i = 0; i < pInfo->numOfChars; i++, pChar++ ) + { + if( pChar->code == -1 && ! pChar->name ) + continue; + + if( bFillEncodingvector && pChar->name ) + { + std::list< sal_Unicode > aCodes = rManager.getUnicodeFromAdobeName( pChar->name ); + for( std::list< sal_Unicode >::const_iterator it = aCodes.begin(); it != aCodes.end(); ++it ) + { + if( *it != 0 ) + { + m_aEncodingVector[ *it ] = pChar->code; + if( pChar->code == -1 ) + m_aNonEncoded[ *it ] = pChar->name; + } + } + } + + aMetric.width = pChar->wx ? pChar->wx : pChar->charBBox.urx; + aMetric.height = pChar->wy ? pChar->wy : pChar->charBBox.ury - pChar->charBBox.lly; + if( aMetric.width == 0 && aMetric.height == 0 ) + // guess something for e.g. space + aMetric.width = m_aGlobalMetricX.width/4; + + if( ( nAdobeEncoding == 0 ) || + ( ( nAdobeEncoding == 3 ) && ( m_aEncoding != RTL_TEXTENCODING_SYMBOL ) ) ) + { + if( pChar->code != -1 ) + { + m_pMetrics->m_aMetrics[ pUnicodes[i] ] = aMetric; + if( bFillEncodingvector ) + { + m_aEncodingVector[ pUnicodes[i] ] = pChar->code; + m_aEncodingVectorPriority.insert(pUnicodes[i]); + } + } + else if( pChar->name ) + { + std::list< sal_Unicode > aCodes = rManager.getUnicodeFromAdobeName( pChar->name ); + for( std::list< sal_Unicode >::const_iterator it = aCodes.begin(); it != aCodes.end(); ++it ) + { + if( *it != 0 ) + m_pMetrics->m_aMetrics[ *it ] = aMetric; + } + } + } + else if( nAdobeEncoding == 1 || nAdobeEncoding == 2 || nAdobeEncoding == 4) + { + if( pChar->name ) + { + std::list< sal_Unicode > aCodes = rManager.getUnicodeFromAdobeName( pChar->name ); + for( std::list< sal_Unicode >::const_iterator it = aCodes.begin(); it != aCodes.end(); ++it ) + { + if( *it != 0 ) + m_pMetrics->m_aMetrics[ *it ] = aMetric; + } + } + else if( pChar->code != -1 ) + { + ::std::pair< std::unordered_multimap< sal_uInt8, sal_Unicode >::const_iterator, + std::unordered_multimap< sal_uInt8, sal_Unicode >::const_iterator > + aCodes = rManager.getUnicodeFromAdobeCode( pChar->code ); + bool bFirst = true; + while( aCodes.first != aCodes.second ) + { + if( (*aCodes.first).second != 0 ) + { + m_pMetrics->m_aMetrics[ (*aCodes.first).second ] = aMetric; + if( bFillEncodingvector ) + { + m_aEncodingVector[ (*aCodes.first).second ] = pChar->code; + if (bFirst) // arbitrarily prefer the first one + { + m_aEncodingVectorPriority.insert((*aCodes.first).second); + bFirst = false; + } + } + } + ++aCodes.first; + } + } + } + else if( nAdobeEncoding == 3 ) + { + if( pChar->code != -1 ) + { + sal_Unicode code = 0xf000 + pChar->code; + m_pMetrics->m_aMetrics[ code ] = aMetric; + // maybe should try to find the name in the convtabs ? + if( bFillEncodingvector ) + { + m_aEncodingVector[ code ] = pChar->code; + m_aEncodingVectorPriority.insert(code); + } + } + } + } + } + + freeFontInfo( pInfo ); + return true; +} + +/* + * one instance only + */ +PrintFontManager& PrintFontManager::get() +{ + static PrintFontManager* pManager = nullptr; + if( ! pManager ) + { + static PrintFontManager theManager; + pManager = &theManager; + pManager->initialize(); + } + return *pManager; +} + +/* + * the PrintFontManager + */ + +PrintFontManager::PrintFontManager() + : m_nNextFontID( 1 ) + , m_pAtoms( new MultiAtomProvider() ) + , m_nNextDirAtom( 1 ) + , m_pFontCache( nullptr ) +{ + for( unsigned int i = 0; i < SAL_N_ELEMENTS( aAdobeCodes ); i++ ) + { + m_aUnicodeToAdobename.insert( std::unordered_multimap< sal_Unicode, OString >::value_type( aAdobeCodes[i].aUnicode, aAdobeCodes[i].pAdobename ) ); + m_aAdobenameToUnicode.insert( std::unordered_multimap< OString, sal_Unicode, OStringHash >::value_type( aAdobeCodes[i].pAdobename, aAdobeCodes[i].aUnicode ) ); + if( aAdobeCodes[i].aAdobeStandardCode ) + { + m_aUnicodeToAdobecode.insert( std::unordered_multimap< sal_Unicode, sal_uInt8 >::value_type( aAdobeCodes[i].aUnicode, aAdobeCodes[i].aAdobeStandardCode ) ); + m_aAdobecodeToUnicode.insert( std::unordered_multimap< sal_uInt8, sal_Unicode >::value_type( aAdobeCodes[i].aAdobeStandardCode, aAdobeCodes[i].aUnicode ) ); + } + } + +#if defined(ENABLE_DBUS) && defined(ENABLE_PACKAGEKIT) + m_aFontInstallerTimer.SetTimeoutHdl(LINK(this, PrintFontManager, autoInstallFontLangSupport)); + m_aFontInstallerTimer.SetTimeout(5000); +#endif +} + +PrintFontManager::~PrintFontManager() +{ + m_aFontInstallerTimer.Stop(); + deinitFontconfig(); + for( std::unordered_map< fontID, PrintFont* >::const_iterator it = m_aFonts.begin(); it != m_aFonts.end(); ++it ) + delete (*it).second; + delete m_pAtoms; + delete m_pFontCache; +} + +OString PrintFontManager::getDirectory( int nAtom ) const +{ + std::unordered_map< int, OString >::const_iterator it( m_aAtomToDir.find( nAtom ) ); + return it != m_aAtomToDir.end() ? it->second : OString(); +} + +int PrintFontManager::getDirectoryAtom( const OString& rDirectory, bool bCreate ) +{ + int nAtom = 0; + std::unordered_map< OString, int, OStringHash >::const_iterator it + ( m_aDirToAtom.find( rDirectory ) ); + if( it != m_aDirToAtom.end() ) + nAtom = it->second; + else if( bCreate ) + { + nAtom = m_nNextDirAtom++; + m_aDirToAtom[ rDirectory ] = nAtom; + m_aAtomToDir[ nAtom ] = rDirectory; + } + return nAtom; +} + +std::vector<fontID> PrintFontManager::addFontFile( const OString& rFileName ) +{ + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + INetURLObject aPath( OStringToOUString( rFileName, aEncoding ), INetURLObject::FSYS_DETECT ); + OString aName( OUStringToOString( aPath.GetName( INetURLObject::DECODE_WITH_CHARSET, aEncoding ), aEncoding ) ); + OString aDir( OUStringToOString( + INetURLObject::decode( aPath.GetPath(), INetURLObject::DECODE_WITH_CHARSET, aEncoding ), aEncoding ) ); + + int nDirID = getDirectoryAtom( aDir, true ); + std::vector<fontID> aFontIds = findFontFileIDs( nDirID, aName ); + if( aFontIds.empty() ) + { + ::std::list< PrintFont* > aNewFonts; + if( analyzeFontFile( nDirID, aName, aNewFonts ) ) + { + for( ::std::list< PrintFont* >::iterator it = aNewFonts.begin(); + it != aNewFonts.end(); ++it ) + { + fontID nFontId = m_nNextFontID++; + m_aFonts[nFontId] = *it; + m_aFontFileToFontID[ aName ].insert( nFontId ); + m_pFontCache->updateFontCacheEntry( *it, true ); + aFontIds.push_back(nFontId); + } + } + } + return aFontIds; +} + +enum fontFormat +{ + UNKNOWN, TRUETYPE, CFF, TYPE1 +}; + +bool PrintFontManager::analyzeFontFile( int nDirID, const OString& rFontFile, ::std::list< PrintFontManager::PrintFont* >& rNewFonts, const char *pFormat ) const +{ + rNewFonts.clear(); + + OString aDir( getDirectory( nDirID ) ); + + OString aFullPath( aDir ); + aFullPath += "/"; + aFullPath += rFontFile; + + // #i1872# reject unreadable files + if( access( aFullPath.getStr(), R_OK ) ) + return false; + + fontFormat eFormat = UNKNOWN; + if (pFormat) + { + if (!strcmp(pFormat, "TrueType")) + eFormat = TRUETYPE; + else if (!strcmp(pFormat, "CFF")) + eFormat = CFF; + else if (!strcmp(pFormat, "Type 1")) + eFormat = TYPE1; + } + if (eFormat == UNKNOWN) + { + OString aExt( rFontFile.copy( rFontFile.lastIndexOf( '.' )+1 ) ); + if( aExt.equalsIgnoreAsciiCase("pfb") || aExt.equalsIgnoreAsciiCase("pfa") ) + eFormat = TYPE1; + else if( aExt.equalsIgnoreAsciiCase("ttf") + || aExt.equalsIgnoreAsciiCase("ttc") + || aExt.equalsIgnoreAsciiCase("tte") ) // #i33947# for Gaiji support + eFormat = TRUETYPE; + else if( aExt.equalsIgnoreAsciiCase("otf") ) // check for TTF- and PS-OpenType too + eFormat = CFF; + } + + if (eFormat == TYPE1) + { + // check for corresponding afm metric + // first look for an adjacent file + static const char* pSuffix[] = { ".afm", ".AFM" }; + + for( unsigned int i = 0; i < SAL_N_ELEMENTS(pSuffix); i++ ) + { + OString aName = OStringBuffer( + rFontFile.copy(0, rFontFile.getLength() - 4)). + append(pSuffix[i]).makeStringAndClear(); + + OStringBuffer aFilePath(aDir); + aFilePath.append('/').append(aName); + + OString aAfmFile; + if( access( aFilePath.makeStringAndClear().getStr(), R_OK ) ) + { + // try in subdirectory afm instead + aFilePath.append(aDir).append("/afm/").append(aName); + + if (!access(aFilePath.getStr(), R_OK)) + aAfmFile = OString("afm/") + aName; + } + else + aAfmFile = aName; + + if( !aAfmFile.isEmpty() ) + { + Type1FontFile* pFont = new Type1FontFile(); + pFont->m_nDirectory = nDirID; + + pFont->m_aFontFile = rFontFile; + pFont->m_aMetricFile = aAfmFile; + + if( ! pFont->readAfmMetrics( m_pAtoms, false, true ) ) + { + delete pFont; + pFont = nullptr; + } + if( pFont ) + rNewFonts.push_back( pFont ); + break; + } + } + } + else if (eFormat == TRUETYPE || eFormat == CFF) + { + // get number of ttc entries + int nLength = CountTTCFonts( aFullPath.getStr() ); + if (nLength > 0) + { +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "ttc: %s contains %d fonts\n", aFullPath.getStr(), nLength ); +#endif + + sal_uInt64 fileSize = 0; + + OUString aURL; + if (osl::File::getFileURLFromSystemPath(OStringToOUString(aFullPath, osl_getThreadTextEncoding()), + aURL) == osl::File::E_None) + { + osl::File aFile(aURL); + if (aFile.open(osl_File_OpenFlag_Read | osl_File_OpenFlag_NoLock) == osl::File::E_None) + { + osl::DirectoryItem aItem; + osl::DirectoryItem::get( aURL, aItem ); + osl::FileStatus aFileStatus( osl_FileStatus_Mask_FileSize ); + aItem.getFileStatus( aFileStatus ); + fileSize = aFileStatus.getFileSize(); + } + } + + //Feel free to calc the exact max possible number of fonts a file + //could contain given its physical size. But this will clamp it to + //a sane starting point + //http://processingjs.nihongoresources.com/the_smallest_font/ + //https://github.com/grzegorzrolek/null-ttf + const int nMaxFontsPossible = fileSize / 528; + if (nLength > nMaxFontsPossible) + nLength = nMaxFontsPossible; + + for( int i = 0; i < nLength; i++ ) + { + TrueTypeFontFile* pFont = new TrueTypeFontFile(); + pFont->m_nDirectory = nDirID; + pFont->m_aFontFile = rFontFile; + pFont->m_nCollectionEntry = i; + if( ! analyzeTrueTypeFile( pFont ) ) + { + delete pFont; + pFont = nullptr; + } + else + rNewFonts.push_back( pFont ); + } + } + else + { + TrueTypeFontFile* pFont = new TrueTypeFontFile(); + pFont->m_nDirectory = nDirID; + pFont->m_aFontFile = rFontFile; + pFont->m_nCollectionEntry = 0; + + // need to read the font anyway to get aliases inside the font file + if( ! analyzeTrueTypeFile( pFont ) ) + { + delete pFont; + pFont = nullptr; + } + else + rNewFonts.push_back( pFont ); + } + } + return ! rNewFonts.empty(); +} + +fontID PrintFontManager::findFontFileID( int nDirID, const OString& rFontFile, int nFaceIndex ) const +{ + fontID nID = 0; + + std::unordered_map< OString, ::std::set< fontID >, OStringHash >::const_iterator set_it = m_aFontFileToFontID.find( rFontFile ); + if( set_it == m_aFontFileToFontID.end() ) + return nID; + + for( ::std::set< fontID >::const_iterator font_it = set_it->second.begin(); font_it != set_it->second.end() && ! nID; ++font_it ) + { + std::unordered_map< fontID, PrintFont* >::const_iterator it = m_aFonts.find( *font_it ); + if( it == m_aFonts.end() ) + continue; + switch( it->second->m_eType ) + { + case fonttype::Type1: + { + Type1FontFile* const pFont = static_cast< Type1FontFile* const >((*it).second); + if( pFont->m_nDirectory == nDirID && + pFont->m_aFontFile == rFontFile ) + nID = it->first; + } + break; + case fonttype::TrueType: + { + TrueTypeFontFile* const pFont = static_cast< TrueTypeFontFile* const >((*it).second); + if( pFont->m_nDirectory == nDirID && + pFont->m_aFontFile == rFontFile && pFont->m_nCollectionEntry == nFaceIndex ) + nID = it->first; + } + break; + default: + break; + } + } + + return nID; +} + +std::vector<fontID> PrintFontManager::findFontFileIDs( int nDirID, const OString& rFontFile ) const +{ + std::vector<fontID> aIds; + + std::unordered_map< OString, ::std::set< fontID >, OStringHash >::const_iterator set_it = m_aFontFileToFontID.find( rFontFile ); + if( set_it == m_aFontFileToFontID.end() ) + return aIds; + + for( ::std::set< fontID >::const_iterator font_it = set_it->second.begin(); font_it != set_it->second.end(); ++font_it ) + { + std::unordered_map< fontID, PrintFont* >::const_iterator it = m_aFonts.find( *font_it ); + if( it == m_aFonts.end() ) + continue; + switch( it->second->m_eType ) + { + case fonttype::Type1: + { + Type1FontFile* const pFont = static_cast< Type1FontFile* const >((*it).second); + if( pFont->m_nDirectory == nDirID && + pFont->m_aFontFile == rFontFile ) + aIds.push_back(it->first); + } + break; + case fonttype::TrueType: + { + TrueTypeFontFile* const pFont = static_cast< TrueTypeFontFile* const >((*it).second); + if( pFont->m_nDirectory == nDirID && + pFont->m_aFontFile == rFontFile ) + aIds.push_back(it->first); + } + break; + default: + break; + } + } + + return aIds; +} + +OUString PrintFontManager::convertTrueTypeName( void* pRecord ) +{ + NameRecord* pNameRecord = static_cast<NameRecord*>(pRecord); + OUString aValue; + if( + ( pNameRecord->platformID == 3 && ( pNameRecord->encodingID == 0 || pNameRecord->encodingID == 1 ) ) // MS, Unicode + || + ( pNameRecord->platformID == 0 ) // Apple, Unicode + ) + { + OUStringBuffer aName( pNameRecord->slen/2 ); + const sal_uInt8* pNameBuffer = pNameRecord->sptr; + for(int n = 0; n < pNameRecord->slen/2; n++ ) + aName.append( (sal_Unicode)getUInt16BE( pNameBuffer ) ); + aValue = aName.makeStringAndClear(); + } + else if( pNameRecord->platformID == 3 ) + { + if( pNameRecord->encodingID >= 2 && pNameRecord->encodingID <= 6 ) + { + /* + * and now for a special kind of madness: + * some fonts encode their byte value string as BE uint16 + * (leading to stray zero bytes in the string) + * while others code two bytes as a uint16 and swap to BE + */ + OStringBuffer aName; + const sal_uInt8* pNameBuffer = pNameRecord->sptr; + for(int n = 0; n < pNameRecord->slen/2; n++ ) + { + sal_Unicode aCode = (sal_Unicode)getUInt16BE( pNameBuffer ); + sal_Char aChar = aCode >> 8; + if( aChar ) + aName.append( aChar ); + aChar = aCode & 0x00ff; + if( aChar ) + aName.append( aChar ); + } + switch( pNameRecord->encodingID ) + { + case 2: + aValue = OStringToOUString( aName.makeStringAndClear(), RTL_TEXTENCODING_MS_932 ); + break; + case 3: + aValue = OStringToOUString( aName.makeStringAndClear(), RTL_TEXTENCODING_MS_936 ); + break; + case 4: + aValue = OStringToOUString( aName.makeStringAndClear(), RTL_TEXTENCODING_MS_950 ); + break; + case 5: + aValue = OStringToOUString( aName.makeStringAndClear(), RTL_TEXTENCODING_MS_949 ); + break; + case 6: + aValue = OStringToOUString( aName.makeStringAndClear(), RTL_TEXTENCODING_MS_1361 ); + break; + } + } + } + else if( pNameRecord->platformID == 1 ) + { + OString aName(reinterpret_cast<char*>(pNameRecord->sptr), pNameRecord->slen); + rtl_TextEncoding eEncoding = RTL_TEXTENCODING_DONTKNOW; + switch (pNameRecord->encodingID) + { + case 0: + eEncoding = RTL_TEXTENCODING_APPLE_ROMAN; + break; + case 1: + eEncoding = RTL_TEXTENCODING_APPLE_JAPANESE; + break; + case 2: + eEncoding = RTL_TEXTENCODING_APPLE_CHINTRAD; + break; + case 3: + eEncoding = RTL_TEXTENCODING_APPLE_KOREAN; + break; + case 4: + eEncoding = RTL_TEXTENCODING_APPLE_ARABIC; + break; + case 5: + eEncoding = RTL_TEXTENCODING_APPLE_HEBREW; + break; + case 6: + eEncoding = RTL_TEXTENCODING_APPLE_GREEK; + break; + case 7: + eEncoding = RTL_TEXTENCODING_APPLE_CYRILLIC; + break; + case 9: + eEncoding = RTL_TEXTENCODING_APPLE_DEVANAGARI; + break; + case 10: + eEncoding = RTL_TEXTENCODING_APPLE_GURMUKHI; + break; + case 11: + eEncoding = RTL_TEXTENCODING_APPLE_GUJARATI; + break; + case 21: + eEncoding = RTL_TEXTENCODING_APPLE_THAI; + break; + case 25: + eEncoding = RTL_TEXTENCODING_APPLE_CHINSIMP; + break; + case 29: + eEncoding = RTL_TEXTENCODING_APPLE_CENTEURO; + break; + case 32: //Uninterpreted + eEncoding = RTL_TEXTENCODING_UTF8; + break; + default: + SAL_WARN("vcl", "Unimplemented mac encoding " << pNameRecord->encodingID << " to unicode conversion"); + break; + } + if (eEncoding != RTL_TEXTENCODING_DONTKNOW) + aValue = OStringToOUString(aName, eEncoding); + } + + return aValue; +} + +//fdo#33349.There exists an archaic Berling Antiqua font which has a "Times New +//Roman" name field in it. We don't want the "Times New Roman" name to take +//precedence in this case. We take Berling Antiqua as a higher priority name, +//and erase the "Times New Roman" name +namespace +{ + bool isBadTNR(const OUString &rName, ::std::set< OUString >& rSet) + { + bool bRet = false; + if ( rName == "Berling Antiqua" ) + { + ::std::set< OUString >::iterator aEnd = rSet.end(); + ::std::set< OUString >::iterator aI = rSet.find("Times New Roman"); + if (aI != aEnd) + { + bRet = true; + rSet.erase(aI); + } + } + return bRet; + } +} + +void PrintFontManager::analyzeTrueTypeFamilyName( void* pTTFont, ::std::list< OUString >& rNames ) +{ + OUString aFamily; + + rNames.clear(); + ::std::set< OUString > aSet; + + NameRecord* pNameRecords = nullptr; + int nNameRecords = GetTTNameRecords( static_cast<TrueTypeFont*>(pTTFont), &pNameRecords ); + if( nNameRecords && pNameRecords ) + { + LanguageTag aSystem(""); + LanguageType eLang = aSystem.getLanguageType(); + int nLastMatch = -1; + for( int i = 0; i < nNameRecords; i++ ) + { + if( pNameRecords[i].nameID != 1 || pNameRecords[i].sptr == nullptr ) + continue; + int nMatch = -1; + if( pNameRecords[i].platformID == 0 ) // Unicode + nMatch = 4000; + else if( pNameRecords[i].platformID == 3 ) + { + // this bases on the LanguageType actually being a Win LCID + if (pNameRecords[i].languageID == eLang) + nMatch = 8000; + else if( pNameRecords[i].languageID == LANGUAGE_ENGLISH_US ) + nMatch = 2000; + else if( pNameRecords[i].languageID == LANGUAGE_ENGLISH || + pNameRecords[i].languageID == LANGUAGE_ENGLISH_UK ) + nMatch = 1500; + else + nMatch = 1000; + } + else if (pNameRecords[i].platformID == 1) + { + AppleLanguageId aAppleId = static_cast<AppleLanguageId>(pNameRecords[i].languageID); + LanguageTag aApple(makeLanguageTagFromAppleLanguageId(aAppleId)); + if (aApple == aSystem) + nMatch = 8000; + else if (aAppleId == AppleLanguageId::ENGLISH) + nMatch = 2000; + else + nMatch = 1000; + } + OUString aName = convertTrueTypeName( pNameRecords + i ); + aSet.insert( aName ); + if (aName.isEmpty()) + continue; + if( nMatch > nLastMatch || isBadTNR(aName, aSet) ) + { + nLastMatch = nMatch; + aFamily = aName; + } + } + } + DisposeNameRecords( pNameRecords, nNameRecords ); + if( !aFamily.isEmpty() ) + { + rNames.push_front( aFamily ); + for( ::std::set< OUString >::const_iterator it = aSet.begin(); it != aSet.end(); ++it ) + if( *it != aFamily ) + rNames.push_back( *it ); + } + return; +} + +bool PrintFontManager::analyzeTrueTypeFile( PrintFont* pFont ) const +{ + bool bSuccess = false; + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + OString aFile = getFontFile( pFont ); + TrueTypeFont* pTTFont = nullptr; + + TrueTypeFontFile* pTTFontFile = static_cast< TrueTypeFontFile* >(pFont); + if( OpenTTFontFile( aFile.getStr(), pTTFontFile->m_nCollectionEntry, &pTTFont ) == SF_OK ) + { + TTGlobalFontInfo aInfo; + GetTTGlobalFontInfo( pTTFont, & aInfo ); + + ::std::list< OUString > aNames; + analyzeTrueTypeFamilyName( pTTFont, aNames ); + + // set family name from XLFD if possible + if( ! pFont->m_nFamilyName ) + { + if( !aNames.empty() ) + { + pFont->m_nFamilyName = m_pAtoms->getAtom( ATOM_FAMILYNAME, aNames.front(), true ); + aNames.pop_front(); + } + else + { + sal_Int32 dotIndex; + + // poor font does not have a family name + // name it to file name minus the extension + dotIndex = pTTFontFile->m_aFontFile.lastIndexOf( '.' ); + if ( dotIndex == -1 ) + dotIndex = pTTFontFile->m_aFontFile.getLength(); + + pFont->m_nFamilyName = m_pAtoms->getAtom( ATOM_FAMILYNAME, OStringToOUString( pTTFontFile->m_aFontFile.copy( 0, dotIndex ), aEncoding ), true ); + } + } + for( ::std::list< OUString >::iterator it = aNames.begin(); it != aNames.end(); ++it ) + { + if( !it->isEmpty() ) + { + int nAlias = m_pAtoms->getAtom( ATOM_FAMILYNAME, *it, true ); + if( nAlias != pFont->m_nFamilyName ) + { + std::list< int >::const_iterator al_it; + for( al_it = pFont->m_aAliases.begin(); al_it != pFont->m_aAliases.end() && *al_it != nAlias; ++al_it ) + ; + if( al_it == pFont->m_aAliases.end() ) + pFont->m_aAliases.push_back( nAlias ); + } + } + } + + if( aInfo.usubfamily ) + pFont->m_aStyleName = OUString( aInfo.usubfamily ); + + SAL_WARN_IF( !aInfo.psname, "vcl", "No PostScript name in font:" << aFile.getStr() ); + + OUString sPSName = aInfo.psname ? + OUString(aInfo.psname, rtl_str_getLength(aInfo.psname), aEncoding) : + m_pAtoms->getString(ATOM_FAMILYNAME, pFont->m_nFamilyName); // poor font does not have a postscript name + + pFont->m_nPSName = m_pAtoms->getAtom( ATOM_PSNAME, sPSName, true ); + + switch( aInfo.weight ) + { + case FW_THIN: pFont->m_eWeight = WEIGHT_THIN; break; + case FW_EXTRALIGHT: pFont->m_eWeight = WEIGHT_ULTRALIGHT; break; + case FW_LIGHT: pFont->m_eWeight = WEIGHT_LIGHT; break; + case FW_MEDIUM: pFont->m_eWeight = WEIGHT_MEDIUM; break; + case FW_SEMIBOLD: pFont->m_eWeight = WEIGHT_SEMIBOLD; break; + case FW_BOLD: pFont->m_eWeight = WEIGHT_BOLD; break; + case FW_EXTRABOLD: pFont->m_eWeight = WEIGHT_ULTRABOLD; break; + case FW_BLACK: pFont->m_eWeight = WEIGHT_BLACK; break; + + case FW_NORMAL: + default: pFont->m_eWeight = WEIGHT_NORMAL; break; + } + + switch( aInfo.width ) + { + case FWIDTH_ULTRA_CONDENSED: pFont->m_eWidth = WIDTH_ULTRA_CONDENSED; break; + case FWIDTH_EXTRA_CONDENSED: pFont->m_eWidth = WIDTH_EXTRA_CONDENSED; break; + case FWIDTH_CONDENSED: pFont->m_eWidth = WIDTH_CONDENSED; break; + case FWIDTH_SEMI_CONDENSED: pFont->m_eWidth = WIDTH_SEMI_CONDENSED; break; + case FWIDTH_SEMI_EXPANDED: pFont->m_eWidth = WIDTH_SEMI_EXPANDED; break; + case FWIDTH_EXPANDED: pFont->m_eWidth = WIDTH_EXPANDED; break; + case FWIDTH_EXTRA_EXPANDED: pFont->m_eWidth = WIDTH_EXTRA_EXPANDED; break; + case FWIDTH_ULTRA_EXPANDED: pFont->m_eWidth = WIDTH_ULTRA_EXPANDED; break; + + case FWIDTH_NORMAL: + default: pFont->m_eWidth = WIDTH_NORMAL; break; + } + + pFont->m_ePitch = aInfo.pitch ? PITCH_FIXED : PITCH_VARIABLE; + pFont->m_eItalic = aInfo.italicAngle == 0 ? ITALIC_NONE : ( aInfo.italicAngle < 0 ? ITALIC_NORMAL : ITALIC_OBLIQUE ); + // #104264# there are fonts that set italic angle 0 although they are + // italic; use macstyle bit here + if( aInfo.italicAngle == 0 && (aInfo.macStyle & 2) ) + pFont->m_eItalic = ITALIC_NORMAL; + + pFont->m_aEncoding = aInfo.symbolEncoded ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UCS2; + + pFont->m_aGlobalMetricY.width = pFont->m_aGlobalMetricX.width = aInfo.xMax - aInfo.xMin; + pFont->m_aGlobalMetricY.height = pFont->m_aGlobalMetricX.height = aInfo.yMax - aInfo.yMin; + + if( aInfo.winAscent && aInfo.winDescent ) + { + pFont->m_nAscend = aInfo.winAscent; + pFont->m_nDescend = aInfo.winDescent; + pFont->m_nLeading = pFont->m_nAscend + pFont->m_nDescend - 1000; + } + else if( aInfo.typoAscender && aInfo.typoDescender ) + { + pFont->m_nLeading = aInfo.typoLineGap; + pFont->m_nAscend = aInfo.typoAscender; + pFont->m_nDescend = -aInfo.typoDescender; + } + else + { + pFont->m_nLeading = aInfo.linegap; + pFont->m_nAscend = aInfo.ascender; + pFont->m_nDescend = -aInfo.descender; + } + + // last try: font bounding box + if( pFont->m_nAscend == 0 ) + pFont->m_nAscend = aInfo.yMax; + if( pFont->m_nDescend == 0 ) + pFont->m_nDescend = -aInfo.yMin; + if( pFont->m_nLeading == 0 ) + pFont->m_nLeading = 15 * (pFont->m_nAscend+pFont->m_nDescend) / 100; + + if( pFont->m_nAscend ) + pFont->m_aGlobalMetricX.height = pFont->m_aGlobalMetricY.height = pFont->m_nAscend + pFont->m_nDescend; + + // get bounding box + pFont->m_nXMin = aInfo.xMin; + pFont->m_nYMin = aInfo.yMin; + pFont->m_nXMax = aInfo.xMax; + pFont->m_nYMax = aInfo.yMax; + + // get type flags + pTTFontFile->m_nTypeFlags = (unsigned int)aInfo.typeFlags; + + // get vertical substitutions flag + pFont->m_bHaveVerticalSubstitutedGlyphs = DoesVerticalSubstitution( pTTFont, 1 ); + + CloseTTFont( pTTFont ); + bSuccess = true; + } + else + SAL_WARN("vcl", "Could not OpenTTFont \"" << aFile.getStr() << "\"\n"); + + return bSuccess; +} + +static bool AreFCSubstitutionsEnabled() +{ + return (SalGenericInstance::FetchFontSubstitutionFlags() & 3) == 0; +} + +void PrintFontManager::initialize() +{ + #ifdef CALLGRIND_COMPILE + CALLGRIND_TOGGLE_COLLECT(); + CALLGRIND_ZERO_STATS(); + #endif + + if( ! m_pFontCache ) + { +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "creating font cache ... " ); + clock_t aStart; + struct tms tms; + aStart = times( &tms ); +#endif + m_pFontCache = new FontCache(); +#if OSL_DEBUG_LEVEL > 1 + clock_t aStop = times( &tms ); + fprintf( stderr, "done in %lf s\n", (double)(aStop - aStart)/(double)sysconf( _SC_CLK_TCK ) ); +#endif + } + + // initialize can be called more than once, e.g. + // gtk-fontconfig-timestamp changes to reflect new font installed and + // PrintFontManager::initialize called again + { + for( std::unordered_map< fontID, PrintFont* >::const_iterator it = m_aFonts.begin(); it != m_aFonts.end(); ++it ) + delete (*it).second; + m_nNextFontID = 1; + m_aFonts.clear(); + m_aFontDirectories.clear(); + m_aPrivateFontDirectories.clear(); + } + +#if OSL_DEBUG_LEVEL > 1 + clock_t aStart; + clock_t aStep1; + clock_t aStep2; + + struct tms tms; + + aStart = times( &tms ); +#endif + + // first try fontconfig + initFontconfig(); + + // part one - look for downloadable fonts + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + const OUString &rSalPrivatePath = psp::getFontPath(); + + // search for the fonts in SAL_PRIVATE_FONTPATH first; those are + // the fonts installed with the office + if( !rSalPrivatePath.isEmpty() ) + { + OString aPath = OUStringToOString( rSalPrivatePath, aEncoding ); + const bool bAreFCSubstitutionsEnabled = AreFCSubstitutionsEnabled(); + sal_Int32 nIndex = 0; + do + { + OString aToken = aPath.getToken( 0, ';', nIndex ); + normPath( aToken ); + if ( aToken.isEmpty() ) + { + continue; + } + // if registering an app-specific fontdir with fontconfig fails + // and fontconfig-based substitutions are enabled + // then trying to use these app-specific fonts doesn't make sense + if( !addFontconfigDir( aToken ) ) + if( bAreFCSubstitutionsEnabled ) + continue; + m_aFontDirectories.push_back( aToken ); + m_aPrivateFontDirectories.push_back( getDirectoryAtom( aToken, true ) ); + } while( nIndex >= 0 ); + } + + // protect against duplicate paths + std::unordered_map< OString, int, OStringHash > visited_dirs; + + // Don't search directories that fontconfig already did + countFontconfigFonts( visited_dirs ); + + // search for font files in each path + std::list< OString >::iterator dir_it; + for( dir_it = m_aFontDirectories.begin(); dir_it != m_aFontDirectories.end(); ++dir_it ) + { + OString aPath( *dir_it ); + // see if we were here already + if( visited_dirs.find( aPath ) != visited_dirs.end() ) + continue; + visited_dirs[ aPath ] = 1; + + // there may be ":unscaled" directories (see XFree86) + // it should be safe to ignore them since they should not + // contain any of our recognizeable fonts + + // ask the font cache whether it handles this directory + std::list< PrintFont* > aCacheFonts; + if( m_pFontCache->listDirectory( aPath, aCacheFonts ) ) + { +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "adding cache directory: %s\n", aPath.getStr() ); +#endif + for( ::std::list< PrintFont* >::iterator it = aCacheFonts.begin(); it != aCacheFonts.end(); ++it ) + { + fontID aFont = m_nNextFontID++; + m_aFonts[ aFont ] = *it; + if( (*it)->m_eType == fonttype::Type1 ) + m_aFontFileToFontID[ static_cast<Type1FontFile*>(*it)->m_aFontFile ].insert( aFont ); + else if( (*it)->m_eType == fonttype::TrueType ) + m_aFontFileToFontID[ static_cast<TrueTypeFontFile*>(*it)->m_aFontFile ].insert( aFont ); +#if OSL_DEBUG_LEVEL > 1 + else + fprintf(stderr, "Un-cached type '%d'\n", (*it)->m_eType); +#if OSL_DEBUG_LEVEL > 2 + fprintf( stderr, "adding cached font %d: %s\n", aFont, getFontFileSysPath( aFont ).getStr() ); +#endif +#endif + } + if( ! m_pFontCache->scanAdditionalFiles( aPath ) ) + continue; + } + + } + +#if OSL_DEBUG_LEVEL > 1 + aStep1 = times( &tms ); +#endif + + // part three - fill in family styles + std::unordered_map< fontID, PrintFont* >::iterator font_it; + for (font_it = m_aFonts.begin(); font_it != m_aFonts.end(); ++font_it) + { + std::unordered_map< int, FontFamily >::const_iterator it = + m_aFamilyTypes.find( font_it->second->m_nFamilyName ); + if (it != m_aFamilyTypes.end()) + continue; + const OUString& rFamily = + m_pAtoms->getString( ATOM_FAMILYNAME, font_it->second->m_nFamilyName); + FontFamily eType = matchFamilyName( rFamily ); + m_aFamilyTypes[ font_it->second->m_nFamilyName ] = eType; + } + +#if OSL_DEBUG_LEVEL > 1 + aStep2 = times( &tms ); + fprintf( stderr, "PrintFontManager::initialize: collected %" SAL_PRI_SIZET "u fonts\n", m_aFonts.size() ); + double fTick = (double)sysconf( _SC_CLK_TCK ); + fprintf( stderr, "Step 1 took %lf seconds\n", (double)(aStep1 - aStart)/fTick ); + fprintf( stderr, "Step 2 took %lf seconds\n", (double)(aStep2 - aStep1)/fTick ); +#endif + + m_pFontCache->flush(); + + #ifdef CALLGRIND_COMPILE + CALLGRIND_DUMP_STATS(); + CALLGRIND_TOGGLE_COLLECT(); + #endif +} + +void PrintFontManager::getFontList( ::std::list< fontID >& rFontIDs ) +{ + rFontIDs.clear(); + std::unordered_map< fontID, PrintFont* >::const_iterator it; + + for( it = m_aFonts.begin(); it != m_aFonts.end(); ++it ) + rFontIDs.push_back( it->first ); +} + +void PrintFontManager::fillPrintFontInfo( PrintFont* pFont, FastPrintFontInfo& rInfo ) const +{ + std::unordered_map< int, FontFamily >::const_iterator style_it = + m_aFamilyTypes.find( pFont->m_nFamilyName ); + rInfo.m_eType = pFont->m_eType; + rInfo.m_aFamilyName = m_pAtoms->getString( ATOM_FAMILYNAME, pFont->m_nFamilyName ); + rInfo.m_aStyleName = pFont->m_aStyleName; + rInfo.m_eFamilyStyle = style_it != m_aFamilyTypes.end() ? style_it->second : FAMILY_DONTKNOW; + rInfo.m_eItalic = pFont->m_eItalic; + rInfo.m_eWidth = pFont->m_eWidth; + rInfo.m_eWeight = pFont->m_eWeight; + rInfo.m_ePitch = pFont->m_ePitch; + rInfo.m_aEncoding = pFont->m_aEncoding; + + rInfo.m_bEmbeddable = (pFont->m_eType == fonttype::Type1); + rInfo.m_bSubsettable = (pFont->m_eType == fonttype::TrueType); // TODO: rename to SfntType + + rInfo.m_aAliases.clear(); + for( ::std::list< int >::iterator it = pFont->m_aAliases.begin(); it != pFont->m_aAliases.end(); ++it ) + rInfo.m_aAliases.push_back( m_pAtoms->getString( ATOM_FAMILYNAME, *it ) ); +} + +void PrintFontManager::fillPrintFontInfo( PrintFont* pFont, PrintFontInfo& rInfo ) const +{ + if( ( pFont->m_nAscend == 0 && pFont->m_nDescend == 0 ) || + ! pFont->m_pMetrics || pFont->m_pMetrics->isEmpty() + ) + { + // might be a truetype font not analyzed or type1 without metrics read + if( pFont->m_eType == fonttype::Type1 ) + pFont->readAfmMetrics( m_pAtoms, false, false ); + else if( pFont->m_eType == fonttype::TrueType ) + analyzeTrueTypeFile( pFont ); + } + + fillPrintFontInfo( pFont, static_cast< FastPrintFontInfo& >( rInfo ) ); + + rInfo.m_nAscend = pFont->m_nAscend; + rInfo.m_nDescend = pFont->m_nDescend; + rInfo.m_nLeading = pFont->m_nLeading; + rInfo.m_nWidth = pFont->m_aGlobalMetricX.width < pFont->m_aGlobalMetricY.width ? pFont->m_aGlobalMetricY.width : pFont->m_aGlobalMetricX.width; +} + +bool PrintFontManager::getFontInfo( fontID nFontID, PrintFontInfo& rInfo ) const +{ + PrintFont* pFont = getFont( nFontID ); + if( pFont ) + { + rInfo.m_nID = nFontID; + fillPrintFontInfo( pFont, rInfo ); + } + return pFont != nullptr; +} + +bool PrintFontManager::getFontFastInfo( fontID nFontID, FastPrintFontInfo& rInfo ) const +{ + PrintFont* pFont = getFont( nFontID ); + if( pFont ) + { + rInfo.m_nID = nFontID; + fillPrintFontInfo( pFont, rInfo ); + } + return pFont != nullptr; +} + +bool PrintFontManager::getFontBoundingBox( fontID nFontID, int& xMin, int& yMin, int& xMax, int& yMax ) +{ + bool bSuccess = false; + PrintFont* pFont = getFont( nFontID ); + if( pFont ) + { + if( pFont->m_nXMin == 0 && pFont->m_nYMin == 0 && pFont->m_nXMax == 0 && pFont->m_nYMax == 0 ) + { + // might be a truetype font not analyzed or type1 without metrics read + if( pFont->m_eType == fonttype::Type1 ) + pFont->readAfmMetrics( m_pAtoms, false, true ); + else if( pFont->m_eType == fonttype::TrueType ) + analyzeTrueTypeFile( pFont ); + } + bSuccess = true; + xMin = pFont->m_nXMin; + yMin = pFont->m_nYMin; + xMax = pFont->m_nXMax; + yMax = pFont->m_nYMax; + } + return bSuccess; +} + +int PrintFontManager::getFontFaceNumber( fontID nFontID ) const +{ + int nRet = 0; + PrintFont* pFont = getFont( nFontID ); + if( pFont && pFont->m_eType == fonttype::TrueType ) + nRet = static_cast< TrueTypeFontFile* >(pFont)->m_nCollectionEntry; + if (nRet < 0) + nRet = 0; + return nRet; +} + +FontFamily PrintFontManager::matchFamilyName( const OUString& rFamily ) +{ + typedef struct { + const char* mpName; + sal_uInt16 mnLength; + FontFamily meType; + } family_t; + +#define InitializeClass( p, a ) p, sizeof(p) - 1, a + const family_t pFamilyMatch[] = { + { InitializeClass( "arial", FAMILY_SWISS ) }, + { InitializeClass( "arioso", FAMILY_SCRIPT ) }, + { InitializeClass( "avant garde", FAMILY_SWISS ) }, + { InitializeClass( "avantgarde", FAMILY_SWISS ) }, + { InitializeClass( "bembo", FAMILY_ROMAN ) }, + { InitializeClass( "bookman", FAMILY_ROMAN ) }, + { InitializeClass( "conga", FAMILY_ROMAN ) }, + { InitializeClass( "courier", FAMILY_MODERN ) }, + { InitializeClass( "curl", FAMILY_SCRIPT ) }, + { InitializeClass( "fixed", FAMILY_MODERN ) }, + { InitializeClass( "gill", FAMILY_SWISS ) }, + { InitializeClass( "helmet", FAMILY_MODERN ) }, + { InitializeClass( "helvetica", FAMILY_SWISS ) }, + { InitializeClass( "international", FAMILY_MODERN ) }, + { InitializeClass( "lucida", FAMILY_SWISS ) }, + { InitializeClass( "new century schoolbook", FAMILY_ROMAN ) }, + { InitializeClass( "palatino", FAMILY_ROMAN ) }, + { InitializeClass( "roman", FAMILY_ROMAN ) }, + { InitializeClass( "sans serif", FAMILY_SWISS ) }, + { InitializeClass( "sansserif", FAMILY_SWISS ) }, + { InitializeClass( "serf", FAMILY_ROMAN ) }, + { InitializeClass( "serif", FAMILY_ROMAN ) }, + { InitializeClass( "times", FAMILY_ROMAN ) }, + { InitializeClass( "utopia", FAMILY_ROMAN ) }, + { InitializeClass( "zapf chancery", FAMILY_SCRIPT ) }, + { InitializeClass( "zapfchancery", FAMILY_SCRIPT ) } + }; + + OString aFamily = OUStringToOString( rFamily, RTL_TEXTENCODING_ASCII_US ); + sal_uInt32 nLower = 0; + sal_uInt32 nUpper = SAL_N_ELEMENTS(pFamilyMatch); + + while( nLower < nUpper ) + { + sal_uInt32 nCurrent = (nLower + nUpper) / 2; + const family_t* pHaystack = pFamilyMatch + nCurrent; + sal_Int32 nComparison = + rtl_str_compareIgnoreAsciiCase_WithLength + ( + aFamily.getStr(), aFamily.getLength(), + pHaystack->mpName, pHaystack->mnLength + ); + + if( nComparison < 0 ) + nUpper = nCurrent; + else + if( nComparison > 0 ) + nLower = nCurrent + 1; + else + return pHaystack->meType; + } + + return FAMILY_DONTKNOW; +} + +OString PrintFontManager::getAfmFile( PrintFont* pFont ) const +{ + OString aMetricPath; + if( pFont ) + { + switch( pFont->m_eType ) + { + case fonttype::Type1: + { + Type1FontFile* pPSFont = static_cast< Type1FontFile* >(pFont); + aMetricPath = getDirectory( pPSFont->m_nDirectory ); + aMetricPath += "/"; + aMetricPath += pPSFont->m_aMetricFile; + } + break; + default: break; + } + } + return aMetricPath; +} + +OString PrintFontManager::getFontFile( PrintFont* pFont ) const +{ + OString aPath; + + if( pFont && pFont->m_eType == fonttype::Type1 ) + { + Type1FontFile* pPSFont = static_cast< Type1FontFile* >(pFont); + std::unordered_map< int, OString >::const_iterator it = m_aAtomToDir.find( pPSFont->m_nDirectory ); + aPath = it->second; + aPath += "/"; + aPath += pPSFont->m_aFontFile; + } + else if( pFont && pFont->m_eType == fonttype::TrueType ) + { + TrueTypeFontFile* pTTFont = static_cast< TrueTypeFontFile* >(pFont); + std::unordered_map< int, OString >::const_iterator it = m_aAtomToDir.find( pTTFont->m_nDirectory ); + aPath = it->second; + aPath += "/"; + aPath += pTTFont->m_aFontFile; + } + return aPath; +} + +const OUString& PrintFontManager::getPSName( fontID nFontID ) const +{ + PrintFont* pFont = getFont( nFontID ); + if( pFont && pFont->m_nPSName == 0 ) + { + if( pFont->m_eType == fonttype::TrueType ) + analyzeTrueTypeFile( pFont ); + } + + return m_pAtoms->getString( ATOM_PSNAME, pFont ? pFont->m_nPSName : INVALID_ATOM ); +} + +int PrintFontManager::getFontAscend( fontID nFontID ) const +{ + PrintFont* pFont = getFont( nFontID ); + if (pFont && pFont->m_nAscend == 0 && pFont->m_nDescend == 0) + { + // might be a truetype font not yet analyzed + if( pFont->m_eType == fonttype::TrueType ) + analyzeTrueTypeFile( pFont ); + else if( pFont->m_eType == fonttype::Type1 ) + pFont->readAfmMetrics( m_pAtoms, false, true ); + } + return pFont ? pFont->m_nAscend : 0; +} + +int PrintFontManager::getFontDescend( fontID nFontID ) const +{ + PrintFont* pFont = getFont( nFontID ); + if (pFont && pFont->m_nAscend == 0 && pFont->m_nDescend == 0) + { + // might be a truetype font not yet analyzed + if( pFont->m_eType == fonttype::TrueType ) + analyzeTrueTypeFile( pFont ); + else if( pFont->m_eType == fonttype::Type1 ) + pFont->readAfmMetrics( m_pAtoms, false, true ); + } + return pFont ? pFont->m_nDescend : 0; +} + +void PrintFontManager::hasVerticalSubstitutions( fontID nFontID, + const sal_Unicode* pCharacters, int nCharacters, bool* pHasSubst ) const +{ + PrintFont* pFont = getFont( nFontID ); + if (pFont && pFont->m_nAscend == 0 && pFont->m_nDescend == 0) + { + // might be a truetype font not yet analyzed + if( pFont->m_eType == fonttype::TrueType ) + analyzeTrueTypeFile( pFont ); + } + + if (!pFont || !pFont->m_bHaveVerticalSubstitutedGlyphs) + memset( pHasSubst, 0, sizeof(bool)*nCharacters ); + else + { + for( int i = 0; i < nCharacters; i++ ) + { + sal_Unicode code = pCharacters[i]; + if( ! pFont->m_pMetrics || + ! ( pFont->m_pMetrics->m_aPages[ code >> 11 ] & ( 1 << ( ( code >> 8 ) & 7 ) ) ) ) + pFont->queryMetricPage( code >> 8, m_pAtoms ); + std::unordered_map< sal_Unicode, bool >::const_iterator it = pFont->m_pMetrics->m_bVerticalSubstitutions.find( code ); + pHasSubst[i] = it != pFont->m_pMetrics->m_bVerticalSubstitutions.end(); + } + } +} + +bool PrintFontManager::isFontDownloadingAllowedForPrinting( fontID nFont ) const +{ + static const char* pEnable = getenv( "PSPRINT_ENABLE_TTF_COPYRIGHTAWARENESS" ); + bool bRet = true; + + if( pEnable && *pEnable ) + { + PrintFont* pFont = getFont( nFont ); + if( pFont && pFont->m_eType == fonttype::TrueType ) + { + TrueTypeFontFile* pTTFontFile = static_cast<TrueTypeFontFile*>(pFont); + if( pTTFontFile->m_nTypeFlags & TYPEFLAG_INVALID ) + { + TrueTypeFont* pTTFont = nullptr; + OString aFile = getFontFile( pFont ); + if( OpenTTFontFile( aFile.getStr(), pTTFontFile->m_nCollectionEntry, &pTTFont ) == SF_OK ) + { + // get type flags + TTGlobalFontInfo aInfo; + GetTTGlobalFontInfo( pTTFont, & aInfo ); + pTTFontFile->m_nTypeFlags = (unsigned int)aInfo.typeFlags; + CloseTTFont( pTTFont ); + } + } + + unsigned int nCopyrightFlags = pTTFontFile->m_nTypeFlags & TYPEFLAG_COPYRIGHT_MASK; + + // http://www.microsoft.com/typography/tt/ttf_spec/ttch02.doc + // Font embedding is allowed if not restricted completely (only bit 1 set). + // Preview&Print (bit 2), Editable (bit 3) or Installable (==0) fonts are ok. + bRet = ( nCopyrightFlags & 0x02 ) != 0x02; + } + } + return bRet; +} + +bool PrintFontManager::getMetrics( fontID nFontID, const sal_Unicode* pString, int nLen, CharacterMetric* pArray, bool bVertical ) const +{ + PrintFont* pFont = getFont( nFontID ); + if( ! pFont ) + return false; + + if( ( pFont->m_nAscend == 0 && pFont->m_nDescend == 0 ) + || ! pFont->m_pMetrics || pFont->m_pMetrics->isEmpty() + ) + { + // might be a font not yet analyzed + if( pFont->m_eType == fonttype::Type1 ) + pFont->readAfmMetrics( m_pAtoms, false, false ); + else if( pFont->m_eType == fonttype::TrueType ) + analyzeTrueTypeFile( pFont ); + } + + for( int i = 0; i < nLen; i++ ) + { + if( ! pFont->m_pMetrics || + ! ( pFont->m_pMetrics->m_aPages[ pString[i] >> 11 ] & ( 1 << ( ( pString[i] >> 8 ) & 7 ) ) ) ) + pFont->queryMetricPage( pString[i] >> 8, m_pAtoms ); + pArray[i].width = pArray[i].height = -1; + if( pFont->m_pMetrics ) + { + int effectiveCode = pString[i]; + effectiveCode |= bVertical ? 1 << 16 : 0; + std::unordered_map< int, CharacterMetric >::const_iterator it = + pFont->m_pMetrics->m_aMetrics.find( effectiveCode ); + // if no vertical metrics are available assume rotated horizontal metrics + if( bVertical && (it == pFont->m_pMetrics->m_aMetrics.end()) ) + it = pFont->m_pMetrics->m_aMetrics.find( pString[i] ); + // the character metrics are in it->second + if( it != pFont->m_pMetrics->m_aMetrics.end() ) + pArray[ i ] = it->second; + } + } + + return true; +} + +bool PrintFontManager::getMetrics( fontID nFontID, sal_Unicode minCharacter, sal_Unicode maxCharacter, CharacterMetric* pArray, bool bVertical ) const +{ + OSL_PRECOND(minCharacter <= maxCharacter, "invalid char. range"); + if (minCharacter > maxCharacter) + return false; + + PrintFont* pFont = getFont( nFontID ); + if( ! pFont ) + return false; + + if( ( pFont->m_nAscend == 0 && pFont->m_nDescend == 0 ) + || ! pFont->m_pMetrics || pFont->m_pMetrics->isEmpty() + ) + { + // might be a font not yet analyzed + if( pFont->m_eType == fonttype::Type1 ) + pFont->readAfmMetrics( m_pAtoms, false, false ); + else if( pFont->m_eType == fonttype::TrueType ) + analyzeTrueTypeFile( pFont ); + } + + sal_Unicode code = minCharacter; + do + { + if( ! pFont->m_pMetrics || + ! ( pFont->m_pMetrics->m_aPages[ code >> 11 ] & ( 1 << ( ( code >> 8 ) & 7 ) ) ) ) + pFont->queryMetricPage( code >> 8, m_pAtoms ); + pArray[ code - minCharacter ].width = -1; + pArray[ code - minCharacter ].height = -1; + if( pFont->m_pMetrics ) + { + int effectiveCode = code; + effectiveCode |= bVertical ? 1 << 16 : 0; + std::unordered_map< int, CharacterMetric >::const_iterator it = + pFont->m_pMetrics->m_aMetrics.find( effectiveCode ); + // if no vertical metrics are available assume rotated horizontal metrics + if( bVertical && (it == pFont->m_pMetrics->m_aMetrics.end()) ) + it = pFont->m_pMetrics->m_aMetrics.find( code ); + // the character metrics are in it->second + if( it != pFont->m_pMetrics->m_aMetrics.end() ) + pArray[ code - minCharacter ] = it->second; + } + } while( code++ != maxCharacter ); + + return true; +} + +// TODO: move most of this stuff into the central font-subsetting code +bool PrintFontManager::createFontSubset( + FontSubsetInfo& rInfo, + fontID nFont, + const OUString& rOutFile, + const sal_GlyphId* pGlyphIds, + const sal_uInt8* pNewEncoding, + sal_Int32* pWidths, + int nGlyphs, + bool bVertical + ) +{ + PrintFont* pFont = getFont( nFont ); + if( !pFont ) + return false; + + switch( pFont->m_eType ) + { + case psp::fonttype::TrueType: rInfo.m_nFontType = FontSubsetInfo::SFNT_TTF; break; + case psp::fonttype::Type1: rInfo.m_nFontType = FontSubsetInfo::ANY_TYPE1; break; + default: + return false; + } + // TODO: remove when Type1 subsetting gets implemented + if( pFont->m_eType != fonttype::TrueType ) + return false; + + // reshuffle array of requested glyphs to make sure glyph0==notdef + sal_uInt8 pEnc[256]; + sal_uInt16 pGID[256]; + sal_uInt8 pOldIndex[256]; + memset( pEnc, 0, sizeof( pEnc ) ); + memset( pGID, 0, sizeof( pGID ) ); + memset( pOldIndex, 0, sizeof( pOldIndex ) ); + if( nGlyphs > 256 ) + return false; + int nChar = 1; + for( int i = 0; i < nGlyphs; i++ ) + { + if( pNewEncoding[i] == 0 ) + { + pOldIndex[ 0 ] = i; + } + else + { + DBG_ASSERT( !(pGlyphIds[i] & 0x007f0000), "overlong glyph id" ); + DBG_ASSERT( (int)pNewEncoding[i] < nGlyphs, "encoding wrong" ); + DBG_ASSERT( pEnc[pNewEncoding[i]] == 0 && pGID[pNewEncoding[i]] == 0, "duplicate encoded glyph" ); + pEnc[ pNewEncoding[i] ] = pNewEncoding[i]; + pGID[ pNewEncoding[i] ] = (sal_uInt16)pGlyphIds[ i ]; + pOldIndex[ pNewEncoding[i] ] = i; + nChar++; + } + } + nGlyphs = nChar; // either input value or increased by one + + // prepare system name for read access for subset source file + // TODO: since this file is usually already mmapped there is no need to open it again + const OString aFromFile = getFontFile( pFont ); + + TrueTypeFont* pTTFont = nullptr; // TODO: rename to SfntFont + TrueTypeFontFile* pTTFontFile = static_cast< TrueTypeFontFile* >(pFont); + if( OpenTTFontFile( aFromFile.getStr(), pTTFontFile->m_nCollectionEntry, &pTTFont ) != SF_OK ) + return false; + + // prepare system name for write access for subset file target + OUString aSysPath; + if( osl_File_E_None != osl_getSystemPathFromFileURL( rOutFile.pData, &aSysPath.pData ) ) + return false; + const rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + const OString aToFile( OUStringToOString( aSysPath, aEncoding ) ); + + // do CFF subsetting if possible + int nCffLength = 0; + const sal_uInt8* pCffBytes = nullptr; + if( GetSfntTable( pTTFont, O_CFF, &pCffBytes, &nCffLength ) ) + { + rInfo.LoadFont( FontSubsetInfo::CFF_FONT, pCffBytes, nCffLength ); +#if 1 // TODO: remove 16bit->long conversion when related methods handle non-16bit glyphids + sal_GlyphId aRequestedGlyphIds[256]; + for( int i = 0; i < nGlyphs; ++i ) + aRequestedGlyphIds[i] = pGID[i]; +#endif + // create subset file at requested path + FILE* pOutFile = fopen( aToFile.getStr(), "wb" ); + if (!pOutFile) + { + CloseTTFont( pTTFont ); + return false; + } + // create font subset + const char* pGlyphSetName = nullptr; // TODO: better name? + const bool bOK = rInfo.CreateFontSubset( + FontSubsetInfo::TYPE1_PFB, + pOutFile, pGlyphSetName, + aRequestedGlyphIds, pEnc, nGlyphs, pWidths ); + fclose( pOutFile ); + // cleanup before early return + CloseTTFont( pTTFont ); + return bOK; + } + + // do TTF->Type42 or Type3 subsetting + // fill in font info + psp::PrintFontInfo aFontInfo; + if( ! getFontInfo( nFont, aFontInfo ) ) + return false; + + rInfo.m_nAscent = aFontInfo.m_nAscend; + rInfo.m_nDescent = aFontInfo.m_nDescend; + rInfo.m_aPSName = getPSName( nFont ); + + int xMin, yMin, xMax, yMax; + getFontBoundingBox( nFont, xMin, yMin, xMax, yMax ); + rInfo.m_aFontBBox = Rectangle( Point( xMin, yMin ), Size( xMax-xMin, yMax-yMin ) ); + rInfo.m_nCapHeight = yMax; // Well ... + + // fill in glyph advance widths + TTSimpleGlyphMetrics* pMetrics = GetTTSimpleGlyphMetrics( pTTFont, + pGID, + nGlyphs, + bVertical ); + if( pMetrics ) + { + for( int i = 0; i < nGlyphs; i++ ) + pWidths[pOldIndex[i]] = pMetrics[i].adv; + free( pMetrics ); + } + else + { + CloseTTFont( pTTFont ); + return false; + } + + bool bSuccess = ( SF_OK == CreateTTFromTTGlyphs( pTTFont, + aToFile.getStr(), + pGID, + pEnc, + nGlyphs, + 0, + nullptr, + 0 ) ); + CloseTTFont( pTTFont ); + + return bSuccess; +} + +void PrintFontManager::getGlyphWidths( fontID nFont, + bool bVertical, + std::vector< sal_Int32 >& rWidths, + std::map< sal_Unicode, sal_uInt32 >& rUnicodeEnc ) +{ + PrintFont* pFont = getFont( nFont ); + if( !pFont || + (pFont->m_eType != fonttype::TrueType && pFont->m_eType != fonttype::Type1) ) + return; + if( pFont->m_eType == fonttype::TrueType ) + { + TrueTypeFont* pTTFont = nullptr; + TrueTypeFontFile* pTTFontFile = static_cast< TrueTypeFontFile* >(pFont); + OString aFromFile = getFontFile( pFont ); + if( OpenTTFontFile( aFromFile.getStr(), pTTFontFile->m_nCollectionEntry, &pTTFont ) != SF_OK ) + return; + int nGlyphs = GetTTGlyphCount( pTTFont ); + if( nGlyphs > 0 ) + { + rWidths.resize(nGlyphs); + std::vector<sal_uInt16> aGlyphIds(nGlyphs); + for( int i = 0; i < nGlyphs; i++ ) + aGlyphIds[i] = sal_uInt16(i); + TTSimpleGlyphMetrics* pMetrics = GetTTSimpleGlyphMetrics( pTTFont, + &aGlyphIds[0], + nGlyphs, + bVertical ); + if( pMetrics ) + { + for( int i = 0; i< nGlyphs; i++ ) + rWidths[i] = pMetrics[i].adv; + free( pMetrics ); + rUnicodeEnc.clear(); + } + + // fill the unicode map + // TODO: isn't this map already available elsewhere in the fontmanager? + const sal_uInt8* pCmapData = nullptr; + int nCmapSize = 0; + if( GetSfntTable( pTTFont, O_cmap, &pCmapData, &nCmapSize ) ) + { + CmapResult aCmapResult; + if( ParseCMAP( pCmapData, nCmapSize, aCmapResult ) ) + { + FontCharMapPtr xFontCharMap( new FontCharMap(aCmapResult) ); + for( sal_uInt32 cOld = 0;;) + { + // get next unicode covered by font + const sal_uInt32 c = xFontCharMap->GetNextChar( cOld ); + if( c == cOld ) + break; + cOld = c; +#if 1 // TODO: remove when sal_Unicode covers all of unicode + if( c > (sal_Unicode)~0 ) + break; +#endif + // get the matching glyph index + const sal_GlyphId aGlyphId = xFontCharMap->GetGlyphIndex( c ); + // update the requested map + rUnicodeEnc[ (sal_Unicode)c ] = aGlyphId; + } + + xFontCharMap = nullptr; + } + } + } + CloseTTFont( pTTFont ); + } + else if( pFont->m_eType == fonttype::Type1 ) + { + if( pFont->m_aEncodingVector.empty() ) + pFont->readAfmMetrics( m_pAtoms, true, true ); + if( pFont->m_pMetrics ) + { + rUnicodeEnc.clear(); + rWidths.clear(); + rWidths.reserve( pFont->m_pMetrics->m_aMetrics.size() ); + for( std::unordered_map< int, CharacterMetric >::const_iterator it = + pFont->m_pMetrics->m_aMetrics.begin(); + it != pFont->m_pMetrics->m_aMetrics.end(); ++it ) + { + if( (it->first & 0x00010000) == 0 || bVertical ) + { + rUnicodeEnc[ sal_Unicode(it->first & 0x0000ffff) ] = sal_uInt32(rWidths.size()); + rWidths.push_back( it->second.width ); + } + } + } + } +} + +const std::map< sal_Unicode, sal_Int32 >* PrintFontManager::getEncodingMap( fontID nFont, const std::map< sal_Unicode, OString >** pNonEncoded, std::set<sal_Unicode> const** ppPriority ) const +{ + PrintFont* pFont = getFont( nFont ); + if( !pFont || pFont->m_eType != fonttype::Type1 ) + return nullptr; + + if( pFont->m_aEncodingVector.empty() ) + pFont->readAfmMetrics( m_pAtoms, true, true ); + + if( pNonEncoded ) + *pNonEncoded = !pFont->m_aNonEncoded.empty() ? &pFont->m_aNonEncoded : nullptr; + + if (ppPriority) + { + *ppPriority = &pFont->m_aEncodingVectorPriority; + } + + return !pFont->m_aEncodingVector.empty() ? &pFont->m_aEncodingVector : nullptr; +} + +std::list< OString > PrintFontManager::getAdobeNameFromUnicode( sal_Unicode aChar ) const +{ + std::pair< std::unordered_multimap< sal_Unicode, OString >::const_iterator, + std::unordered_multimap< sal_Unicode, OString >::const_iterator > range + = m_aUnicodeToAdobename.equal_range( aChar ); + + std::list< OString > aRet; + for( ; range.first != range.second; ++range.first ) + aRet.push_back( range.first->second ); + + if( aRet.empty() && aChar != 0 ) + { + sal_Char aBuf[8]; + sal_Int32 nChars = snprintf( + aBuf, sizeof(aBuf), "uni%.4hX", sal_uInt16(aChar)); + aRet.push_back( OString( aBuf, nChars ) ); + } + + return aRet; +} + +std::list< sal_Unicode > PrintFontManager::getUnicodeFromAdobeName( const OString& rName ) const +{ + std::pair< std::unordered_multimap< OString, sal_Unicode, OStringHash >::const_iterator, + std::unordered_multimap< OString, sal_Unicode, OStringHash >::const_iterator > range + = m_aAdobenameToUnicode.equal_range( rName ); + + std::list< sal_Unicode > aRet; + for( ; range.first != range.second; ++range.first ) + aRet.push_back( range.first->second ); + + if( aRet.empty() ) + { + if( rName.getLength() == 7 && rName.startsWith( "uni" ) ) + { + sal_Unicode aCode = (sal_Unicode)rName.copy( 3 ).toUInt32( 16 ); + aRet.push_back( aCode ); + } + } + + return aRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/fontmanager/fontsubst.cxx b/vcl/unx/generic/fontmanager/fontsubst.cxx new file mode 100644 index 000000000000..f88944504764 --- /dev/null +++ b/vcl/unx/generic/fontmanager/fontsubst.cxx @@ -0,0 +1,252 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "generic/geninst.h" +#include "generic/genpspgraphics.h" +#include "unx/glyphcache.hxx" + +#include "vcl/sysdata.hxx" +#include "fontinstance.hxx" + +#include "generic/printergfx.hxx" +#include "salbmp.hxx" +#include "impfont.hxx" +#include "outdev.h" +#include "PhysicalFontCollection.hxx" +#include "fontsubset.hxx" +#include "salprn.hxx" + +#include <unotools/fontdefs.hxx> +#include <list> + +// platform specific font substitution hooks + +class FcPreMatchSubstitution +: public ImplPreMatchFontSubstitution +{ +public: + bool FindFontSubstitute( FontSelectPattern& ) const override; + typedef ::std::pair<FontSelectPattern, FontSelectPattern> value_type; +private: + typedef ::std::list<value_type> CachedFontMapType; + mutable CachedFontMapType maCachedFontMap; +}; + +class FcGlyphFallbackSubstitution +: public ImplGlyphFallbackFontSubstitution +{ + // TODO: add a cache +public: + bool FindFontSubstitute( FontSelectPattern&, OUString& rMissingCodes ) const override; +}; + +int SalGenericInstance::FetchFontSubstitutionFlags() +{ + // init font substitution defaults + int nDisableBits = 0; +#ifdef SOLARIS + nDisableBits = 1; // disable "font fallback" here on default +#endif + // apply the environment variable if any + const char* pEnvStr = ::getenv( "SAL_DISABLE_FC_SUBST" ); + if( pEnvStr ) + { + if( (*pEnvStr >= '0') && (*pEnvStr <= '9') ) + nDisableBits = (*pEnvStr - '0'); + else + nDisableBits = ~0U; // no specific bits set: disable all + } + return nDisableBits; +} + +void SalGenericInstance::RegisterFontSubstitutors( PhysicalFontCollection* pFontCollection ) +{ + // init font substitution defaults + int nDisableBits = 0; +#ifdef SOLARIS + nDisableBits = 1; // disable "font fallback" here on default +#endif + // apply the environment variable if any + const char* pEnvStr = ::getenv( "SAL_DISABLE_FC_SUBST" ); + if( pEnvStr ) + { + if( (*pEnvStr >= '0') && (*pEnvStr <= '9') ) + nDisableBits = (*pEnvStr - '0'); + else + nDisableBits = ~0U; // no specific bits set: disable all + } + + // register font fallback substitutions (unless disabled by bit0) + if( (nDisableBits & 1) == 0 ) + { + static FcPreMatchSubstitution aSubstPreMatch; + pFontCollection->SetPreMatchHook( &aSubstPreMatch ); + } + + // register glyph fallback substitutions (unless disabled by bit1) + if( (nDisableBits & 2) == 0 ) + { + static FcGlyphFallbackSubstitution aSubstFallback; + pFontCollection->SetFallbackHook( &aSubstFallback ); + } +} + +static FontSelectPattern GetFcSubstitute(const FontSelectPattern &rFontSelData, OUString& rMissingCodes ) +{ + FontSelectPattern aSubstituted(rFontSelData); + psp::PrintFontManager& rMgr = psp::PrintFontManager::get(); + rMgr.Substitute(aSubstituted, rMissingCodes); + return aSubstituted; +} + +namespace +{ + bool uselessmatch(const FontSelectPattern &rOrig, const FontSelectPattern &rNew) + { + return + ( + rOrig.maTargetName == rNew.maSearchName && + rOrig.GetWeight() == rNew.GetWeight() && + rOrig.GetSlantType() == rNew.GetSlantType() && + rOrig.GetPitch() == rNew.GetPitch() && + rOrig.GetWidthType() == rNew.GetWidthType() + ); + } + + class equal + { + private: + const FontSelectPattern& mrAttributes; + public: + explicit equal(const FontSelectPattern& rAttributes) + : mrAttributes(rAttributes) + { + } + bool operator()(const FcPreMatchSubstitution::value_type& rOther) const + { return rOther.first == mrAttributes; } + }; +} + +bool FcPreMatchSubstitution::FindFontSubstitute( FontSelectPattern &rFontSelData ) const +{ + // We don't actually want to talk to Fontconfig at all for symbol fonts + if( rFontSelData.IsSymbolFont() ) + return false; + // StarSymbol is a unicode font, but it still deserves the symbol flag + if ( IsStarSymbol(rFontSelData.maSearchName) ) + return false; + + //see fdo#41556 and fdo#47636 + //fontconfig can return e.g. an italic font for a non-italic input and/or + //different fonts depending on fontsize, bold, etc settings so don't cache + //just on the name, cache map all the input and all the output not just map + //from original selection to output fontname + FontSelectPattern& rPatternAttributes = rFontSelData; + CachedFontMapType &rCachedFontMap = const_cast<CachedFontMapType &>(maCachedFontMap); + CachedFontMapType::iterator itr = std::find_if(rCachedFontMap.begin(), rCachedFontMap.end(), equal(rPatternAttributes)); + if (itr != rCachedFontMap.end()) + { + // Cached substitution + rFontSelData.copyAttributes(itr->second); + if (itr != rCachedFontMap.begin()) + { + // MRU, move it to the front + rCachedFontMap.splice(rCachedFontMap.begin(), rCachedFontMap, itr); + } + return true; + } + + OUString aDummy; + const FontSelectPattern aOut = GetFcSubstitute( rFontSelData, aDummy ); + + if( aOut.maSearchName.isEmpty() ) + return false; + + const bool bHaveSubstitute = !uselessmatch( rFontSelData, aOut ); + +#ifdef DEBUG + const OString aOrigName(OUStringToOString(rFontSelData.maTargetName, + RTL_TEXTENCODING_UTF8)); + const OString aSubstName(OUStringToOString(aOut.maSearchName, + RTL_TEXTENCODING_UTF8)); + printf( "FcPreMatchSubstitution \"%s\" bipw=%d%d%d%d -> ", + aOrigName.getStr(), rFontSelData.GetWeight(), rFontSelData.GetSlantType(), + rFontSelData.GetPitch(), rFontSelData.GetWidthType() ); + if( !bHaveSubstitute ) + printf( "no substitute available\n" ); + else + printf( "\"%s\" bipw=%d%d%d%d\n", aSubstName.getStr(), + aOut.GetWeight(), aOut.GetSlantType(), aOut.GetPitch(), aOut.GetWidthType() ); +#endif + + if( bHaveSubstitute ) + { + rCachedFontMap.push_front(value_type(rFontSelData, aOut)); + //fairly arbitrary limit in this case, but I recall measuring max 8 + //fonts as the typical max amount of fonts in medium sized documents + if (rCachedFontMap.size() > 8) + rCachedFontMap.pop_back(); + rFontSelData = aOut; + } + + return bHaveSubstitute; +} + +bool FcGlyphFallbackSubstitution::FindFontSubstitute( FontSelectPattern& rFontSelData, + OUString& rMissingCodes ) const +{ + // We don't actually want to talk to Fontconfig at all for symbol fonts + if( rFontSelData.IsSymbolFont() ) + return false; + // StarSymbol is a unicode font, but it still deserves the symbol flag + if ( IsStarSymbol(rFontSelData.maSearchName) ) + return false; + + const FontSelectPattern aOut = GetFcSubstitute( rFontSelData, rMissingCodes ); + // TODO: cache the unicode + srcfont specific result + // FC doing it would be preferable because it knows the invariables + // e.g. FC knows the FC rule that all Arial gets replaced by LiberationSans + // whereas we would have to check for every size or attribute + if( aOut.maSearchName.isEmpty() ) + return false; + + const bool bHaveSubstitute = !uselessmatch( rFontSelData, aOut ); + +#ifdef DEBUG + const OString aOrigName(OUStringToOString(rFontSelData.maTargetName, + RTL_TEXTENCODING_UTF8)); + const OString aSubstName(OUStringToOString(aOut.maSearchName, + RTL_TEXTENCODING_UTF8)); + printf( "FcGFSubstitution \"%s\" bipw=%d%d%d%d ->", + aOrigName.getStr(), rFontSelData.GetWeight(), rFontSelData.GetSlantType(), + rFontSelData.GetPitch(), rFontSelData.GetWidthType() ); + if( !bHaveSubstitute ) + printf( "no substitute available\n" ); + else + printf( "\"%s\" bipw=%d%d%d%d\n", aSubstName.getStr(), + aOut.GetWeight(), aOut.GetSlantType(), aOut.GetPitch(), aOut.GetWidthType() ); +#endif + + if( bHaveSubstitute ) + rFontSelData = aOut; + + return bHaveSubstitute; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/fontmanager/helper.cxx b/vcl/unx/generic/fontmanager/helper.cxx new file mode 100644 index 000000000000..af8ae0aedeaa --- /dev/null +++ b/vcl/unx/generic/fontmanager/helper.cxx @@ -0,0 +1,390 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_folders.h> + +#include <cstring> +#include <sys/stat.h> +#include <unistd.h> +#include <limits.h> +#include <osl/file.hxx> +#include <osl/process.h> +#include <osl/thread.h> +#include <rtl/bootstrap.hxx> +#include <rtl/ustring.hxx> +#include <tools/urlobj.hxx> +#include "vcl/helper.hxx" +#include "vcl/ppdparser.hxx" +#include <memory> + +using ::rtl::Bootstrap; + +namespace psp { + +OUString getOfficePath( enum whichOfficePath ePath ) +{ + static OUString aInstallationRootPath; + static OUString aUserPath; + static OUString aConfigPath; + static OUString aEmpty; + static bool bOnce = false; + + if( ! bOnce ) + { + bOnce = true; + OUString aIni; + Bootstrap::get( "BRAND_BASE_DIR", aInstallationRootPath ); + aIni = aInstallationRootPath + "/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap" ); + Bootstrap aBootstrap( aIni ); + aBootstrap.getFrom( "CustomDataUrl", aConfigPath ); + aBootstrap.getFrom( "UserInstallation", aUserPath ); + OUString aUPath = aUserPath; + + if( aConfigPath.startsWith( "file://" ) ) + { + OUString aSysPath; + if( osl_getSystemPathFromFileURL( aConfigPath.pData, &aSysPath.pData ) == osl_File_E_None ) + aConfigPath = aSysPath; + } + if( aInstallationRootPath.startsWith( "file://" ) ) + { + OUString aSysPath; + if( osl_getSystemPathFromFileURL( aInstallationRootPath.pData, &aSysPath.pData ) == osl_File_E_None ) + aInstallationRootPath = aSysPath; + } + if( aUserPath.startsWith( "file://" ) ) + { + OUString aSysPath; + if( osl_getSystemPathFromFileURL( aUserPath.pData, &aSysPath.pData ) == osl_File_E_None ) + aUserPath = aSysPath; + } + // ensure user path exists + aUPath += "/user/psprint"; + #if OSL_DEBUG_LEVEL > 1 + oslFileError eErr = + #endif + osl_createDirectoryPath( aUPath.pData, nullptr, nullptr ); + #if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "try to create \"%s\" = %d\n", OUStringToOString( aUPath, RTL_TEXTENCODING_UTF8 ).getStr(), eErr ); + #endif + } + + switch( ePath ) + { + case ConfigPath: return aConfigPath; + case InstallationRootPath: return aInstallationRootPath; + case UserPath: return aUserPath; + } + return aEmpty; +} + +static OString getEnvironmentPath( const char* pKey ) +{ + OString aPath; + + const char* pValue = getenv( pKey ); + if( pValue && *pValue ) + { + aPath = OString( pValue ); + } + return aPath; +} + +} // namespace psp + +void psp::getPrinterPathList( std::list< OUString >& rPathList, const char* pSubDir ) +{ + rPathList.clear(); + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + + OUStringBuffer aPathBuffer( 256 ); + + // append net path + aPathBuffer.append( getOfficePath( psp::InstallationRootPath ) ); + if( !aPathBuffer.isEmpty() ) + { + aPathBuffer.append( "/" LIBO_SHARE_FOLDER "/psprint" ); + if( pSubDir ) + { + aPathBuffer.append( '/' ); + aPathBuffer.appendAscii( pSubDir ); + } + rPathList.push_back( aPathBuffer.makeStringAndClear() ); + } + // append user path + aPathBuffer.append( getOfficePath( psp::UserPath ) ); + if( !aPathBuffer.isEmpty() ) + { + aPathBuffer.append( "/user/psprint" ); + if( pSubDir ) + { + aPathBuffer.append( '/' ); + aPathBuffer.appendAscii( pSubDir ); + } + rPathList.push_back( aPathBuffer.makeStringAndClear() ); + } + + OString aPath( getEnvironmentPath("SAL_PSPRINT") ); + sal_Int32 nIndex = 0; + do + { + OString aDir( aPath.getToken( 0, ':', nIndex ) ); + if( aDir.isEmpty() ) + continue; + + if( pSubDir ) + { + aDir += "/"; + aDir += pSubDir; + } + struct stat aStat; + if( stat( aDir.getStr(), &aStat ) || ! S_ISDIR( aStat.st_mode ) ) + continue; + + rPathList.push_back( OStringToOUString( aDir, aEncoding ) ); + } while( nIndex != -1 ); + + #ifdef SYSTEM_PPD_DIR + if( pSubDir && rtl_str_compare( pSubDir, PRINTER_PPDDIR ) == 0 ) + { + rPathList.push_back( OStringToOUString( OString( SYSTEM_PPD_DIR ), RTL_TEXTENCODING_UTF8 ) ); + } + #endif + + if( rPathList.empty() ) + { + // last resort: next to program file (mainly for setup) + OUString aExe; + if( osl_getExecutableFile( &aExe.pData ) == osl_Process_E_None ) + { + INetURLObject aDir( aExe ); + aDir.removeSegment(); + aExe = aDir.GetMainURL( INetURLObject::NO_DECODE ); + OUString aSysPath; + if( osl_getSystemPathFromFileURL( aExe.pData, &aSysPath.pData ) == osl_File_E_None ) + { + rPathList.push_back( aSysPath ); + } + } + } +} + +OUString psp::getFontPath() +{ + static OUString aPath; + + if (aPath.isEmpty()) + { + OUStringBuffer aPathBuffer( 512 ); + + OUString aConfigPath( getOfficePath( psp::ConfigPath ) ); + OUString aInstallationRootPath( getOfficePath( psp::InstallationRootPath ) ); + OUString aUserPath( getOfficePath( psp::UserPath ) ); + if( !aConfigPath.isEmpty() ) + { + // #i53530# Path from CustomDataUrl will completely + // replace net and user paths if the path exists + aPathBuffer.append(aConfigPath); + aPathBuffer.append("/" LIBO_SHARE_FOLDER "/fonts"); + // check existence of config path + struct stat aStat; + if( 0 != stat( OUStringToOString( aPathBuffer.makeStringAndClear(), osl_getThreadTextEncoding() ).getStr(), &aStat ) + || ! S_ISDIR( aStat.st_mode ) ) + aConfigPath.clear(); + else + { + aPathBuffer.append(aConfigPath); + aPathBuffer.append("/" LIBO_SHARE_FOLDER "/fonts"); + } + } + if( aConfigPath.isEmpty() ) + { + if( !aInstallationRootPath.isEmpty() ) + { + aPathBuffer.append( aInstallationRootPath ); + aPathBuffer.append( "/" LIBO_SHARE_FOLDER "/fonts/truetype;"); + aPathBuffer.append( aInstallationRootPath ); + aPathBuffer.append( "/" LIBO_SHARE_FOLDER "/fonts/type1;" ); + } + if( !aUserPath.isEmpty() ) + { + aPathBuffer.append( aUserPath ); + aPathBuffer.append( "/user/fonts" ); + } + } + + aPath = aPathBuffer.makeStringAndClear(); +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "initializing font path to \"%s\"\n", OUStringToOString( aPath, RTL_TEXTENCODING_ISO_8859_1 ).getStr() ); +#endif + } + return aPath; +} + +bool psp::convertPfbToPfa( ::osl::File& rInFile, ::osl::File& rOutFile ) +{ + static const unsigned char hexDigits[] = + { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + bool bSuccess = true; + bool bEof = false; + unsigned char buffer[256]; + sal_uInt64 nSize(0); + rInFile.getSize(nSize); + + while( bSuccess && ! bEof ) + { + sal_uInt64 nRead; + // read leading bytes + bEof = ((0 != rInFile.read( buffer, 6, nRead)) || (nRead != 6)); + if( bEof ) + break; + unsigned int nType = buffer[ 1 ]; + unsigned int nBytesToRead = buffer[2] | buffer[3] << 8 | buffer[4] << 16 | buffer[5] << 24; + if( buffer[0] != 0x80 ) // test for pfb magic number + { + // this might be a pfa font already + if( ! rInFile.read( buffer+6, 9, nRead ) && nRead == 9 && + ( ! std::strncmp( reinterpret_cast<char*>(buffer), "%!FontType1-", 12 ) || + ! std::strncmp( reinterpret_cast<char*>(buffer), "%!PS-AdobeFont-", 15 ) ) ) + { + sal_uInt64 nWrite = 0; + if( rOutFile.write( buffer, 15, nWrite ) || nWrite != 15 ) + bSuccess = false; + while( bSuccess && + ! rInFile.read( buffer, sizeof( buffer ), nRead ) && + nRead != 0 ) + { + if( rOutFile.write( buffer, nRead, nWrite ) || + nWrite != nRead ) + bSuccess = false; + } + bEof = true; + } + else + bSuccess = false; + } + else if( nType == 1 || nType == 2 ) + { + sal_uInt64 nOrgPos(0); + rInFile.getPos(nOrgPos); + nBytesToRead = std::min<sal_uInt64>(nBytesToRead, nSize - nOrgPos); + + std::unique_ptr<unsigned char[]> pBuffer(new unsigned char[nBytesToRead+1]); + pBuffer[nBytesToRead] = 0; + + if( ! rInFile.read( pBuffer.get(), nBytesToRead, nRead ) && nRead == nBytesToRead ) + { + if( nType == 1 ) + { + // ascii data, convert dos lineends( \r\n ) and + // m_ac lineends( \r ) to \n + std::unique_ptr<unsigned char[]> pWriteBuffer(new unsigned char[ nBytesToRead ]); + unsigned int nBytesToWrite = 0; + for( unsigned int i = 0; i < nBytesToRead; i++ ) + { + if( pBuffer[i] != '\r' ) + pWriteBuffer[ nBytesToWrite++ ] = pBuffer[i]; + else if( pBuffer[ i+1 ] == '\n' ) + { + i++; + pWriteBuffer[ nBytesToWrite++ ] = '\n'; + } + else + pWriteBuffer[ nBytesToWrite++ ] = '\n'; + } + if( rOutFile.write( pWriteBuffer.get(), nBytesToWrite, nRead ) || nRead != nBytesToWrite ) + bSuccess = false; + } + else + { + // binary data + unsigned int nBuffer = 0; + for( unsigned int i = 0; i < nBytesToRead && bSuccess; i++ ) + { + buffer[ nBuffer++ ] = hexDigits[ pBuffer[ i ] >> 4 ]; + buffer[ nBuffer++ ] = hexDigits[ pBuffer[ i ] & 15 ]; + if( nBuffer >= 80 ) + { + buffer[ nBuffer++ ] = '\n'; + if( rOutFile.write( buffer, nBuffer, nRead ) || nRead != nBuffer ) + bSuccess = false; + nBuffer = 0; + } + } + if( nBuffer > 0 && bSuccess ) + { + buffer[ nBuffer++ ] = '\n'; + if( rOutFile.write( buffer, nBuffer, nRead ) || nRead != nBuffer ) + bSuccess = false; + } + } + } + else + bSuccess = false; + } + else if( nType == 3 ) + bEof = true; + else + bSuccess = false; + } + + return bSuccess; +} + +void psp::normPath( OString& rPath ) +{ + char buf[PATH_MAX]; + + // double slashes and slash at end are probably + // removed by realpath anyway, but since this runs + // on many different platforms let's play it safe + OString aPath = rPath.replaceAll("//", "/"); + + if( aPath.endsWith("/") ) + aPath = aPath.copy(0, aPath.getLength()-1); + + if( ( aPath.indexOf("./") != -1 || + aPath.indexOf( '~' ) != -1 ) + && realpath( aPath.getStr(), buf ) ) + { + rPath = buf; + } + else + { + rPath = aPath; + } +} + +void psp::splitPath( OString& rPath, OString& rDir, OString& rBase ) +{ + normPath( rPath ); + sal_Int32 nIndex = rPath.lastIndexOf( '/' ); + if( nIndex > 0 ) + rDir = rPath.copy( 0, nIndex ); + else if( nIndex == 0 ) // root dir + rDir = rPath.copy( 0, 1 ); + if( rPath.getLength() > nIndex+1 ) + rBase = rPath.copy( nIndex+1 ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/fontmanager/parseAFM.cxx b/vcl/unx/generic/fontmanager/parseAFM.cxx new file mode 100644 index 000000000000..3db271bab3ba --- /dev/null +++ b/vcl/unx/generic/fontmanager/parseAFM.cxx @@ -0,0 +1,1466 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * (C) 1988, 1989, 1990 by Adobe Systems Incorporated. All rights reserved. + * + * This file may be freely copied and redistributed as long as: + * 1) This entire notice continues to be included in the file, + * 2) If the file has been modified in any way, a notice of such + * modification is conspicuously indicated. + * + * PostScript, Display PostScript, and Adobe are registered trademarks of + * Adobe Systems Incorporated. + * + * ************************************************************************ + * THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO CHANGE WITHOUT + * NOTICE, AND SHOULD NOT BE CONSTRUED AS A COMMITMENT BY ADOBE SYSTEMS + * INCORPORATED. ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY OR + * LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO WARRANTY OF ANY + * KIND (EXPRESS, IMPLIED OR STATUTORY) WITH RESPECT TO THIS INFORMATION, + * AND EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + * ************************************************************************ + */ + +/* + * Changes made for OpenOffice.org + * + * 10/24/2000 pl - changed code to compile with c++-compilers + * - added namespace to avoid symbol clashes + * - replaced BOOL by bool + * - added function to free space allocated by parseFile + * 10/26/2000 pl - added additional keys + * - added ability to parse slightly broken files + * - added charwidth member to GlobalFontInfo + * 04/26/2001 pl - added OpenOffice header + * 10/19/2005 pl - performance increase: + * - fread file in one pass + * - replace file io by buffer access + * 10/20/2005 pl - performance increase: + * - use one table lookup in token() routine + * instead of many conditions + * - return token length in token() routine + * - use hash lookup instead of binary search + * in recognize() routine + */ + +/* parseAFM.c + * + * This file is used in conjunction with the parseAFM.h header file. + * This file contains several procedures that are used to parse AFM + * files. It is intended to work with an application program that needs + * font metric information. The program can be used as is by making a + * procedure call to "parseFile" (passing in the expected parameters) + * and having it fill in a data structure with the data from the + * AFM file, or an application developer may wish to customize this + * code. + * + * There is also a file, parseAFMclient.c, that is a sample application + * showing how to call the "parseFile" procedure and how to use the data + * after "parseFile" has returned. + * + * Please read the comments in parseAFM.h and parseAFMclient.c. + * + * History: + * original: DSM Thu Oct 20 17:39:59 PDT 1988 + * modified: DSM Mon Jul 3 14:17:50 PDT 1989 + * - added 'storageProblem' return code + * - fixed bug of not allocating extra byte for string duplication + * - fixed typos + * modified: DSM Tue Apr 3 11:18:34 PDT 1990 + * - added free(ident) at end of parseFile routine + * modified: DSM Tue Jun 19 10:16:29 PDT 1990 + * - changed (width == 250) to (width = 250) in initializeArray + */ + +#include <string.h> +#include <stdlib.h> +#include <sys/stat.h> + +#include "parseAFM.hxx" +#include "vcl/strhelper.hxx" + +#include "rtl/alloc.h" + +#define lineterm EOL /* line terminating character */ +#define normalEOF 1 /* return code from parsing routines used only */ +/* in this module */ +#define False "false" /* used in string comparison to check the value of */ +/* boolean keys (e.g. IsFixedPitch) */ + +#define MATCH(A,B) (strncmp((A),(B), MAX_NAME) == 0) + +namespace psp { + +class FileInputStream +{ + char* m_pMemory; + unsigned int m_nPos; + unsigned int m_nLen; + public: + explicit FileInputStream( const char* pFilename ); + ~FileInputStream(); + + int getChar() { return (m_nPos < m_nLen) ? int(m_pMemory[m_nPos++]) : -1; } + void ungetChar() + { + if( m_nPos > 0 ) + m_nPos--; + } +}; + +FileInputStream::FileInputStream(const char* pFilename) + : m_pMemory(nullptr) + , m_nPos(0) + , m_nLen(0) +{ + FILE* fp = fopen( pFilename, "r" ); + if( fp ) + { + struct stat aStat; + if (!fstat(fileno(fp), &aStat) && S_ISREG(aStat.st_mode) && aStat.st_size > 0) + { + m_pMemory = static_cast<char*>(rtl_allocateMemory( aStat.st_size )); + m_nLen = (unsigned int)fread( m_pMemory, 1, aStat.st_size, fp ); + } + fclose( fp ); + } +} + +FileInputStream::~FileInputStream() +{ + rtl_freeMemory( m_pMemory ); +} + +/*************************** GLOBALS ***********************/ +/* "shorts" for fast case statement + * The values of each of these enumerated items correspond to an entry in the + * table of strings defined below. Therefore, if you add a new string as + * new keyword into the keyStrings table, you must also add a corresponding + * parseKey AND it MUST be in the same position! + * + * IMPORTANT: since the sorting algorithm is a binary search, the strings of + * keywords must be placed in lexicographical order, below. [Therefore, the + * enumerated items are not necessarily in lexicographical order, depending + * on the name chosen. BUT, they must be placed in the same position as the + * corresponding key string.] The NOPE shall remain in the last position, + * since it does not correspond to any key string, and it is used in the + * "recognize" procedure to calculate how many possible keys there are. + */ + +// some metrics have Ascent, Descent instead Ascender, Descender or Em +// which is not allowed per afm spcification, but let us handle +// this gently +enum parseKey { + ASCENDER, ASCENT, CHARBBOX, CODE, COMPCHAR, CODEHEX, CAPHEIGHT, CHARWIDTH, CHARACTERSET, CHARACTERS, COMMENT, + DESCENDER, DESCENT, EM, ENCODINGSCHEME, ENDCHARMETRICS, ENDCOMPOSITES, ENDDIRECTION, + ENDFONTMETRICS, ENDKERNDATA, ENDKERNPAIRS, ENDTRACKKERN, + FAMILYNAME, FONTBBOX, FONTNAME, FULLNAME, ISBASEFONT, ISFIXEDPITCH, + ITALICANGLE, KERNPAIR, KERNPAIRXAMT, LIGATURE, MAPPINGSCHEME, METRICSSETS, CHARNAME, + NOTICE, COMPCHARPIECE, STARTCHARMETRICS, STARTCOMPOSITES, STARTDIRECTION, + STARTFONTMETRICS, STARTKERNDATA, STARTKERNPAIRS, + STARTTRACKKERN, STDHW, STDVW, TRACKKERN, UNDERLINEPOSITION, + UNDERLINETHICKNESS, VVECTOR, VERSION, XYWIDTH, X0WIDTH, XWIDTH, WEIGHT, XHEIGHT, + NOPE +}; + +/*************************** PARSING ROUTINES **************/ + +/*************************** token *************************/ + +/* A "AFM file Conventions" tokenizer. That means that it will + * return the next token delimited by white space. See also + * the `linetoken' routine, which does a similar thing but + * reads all tokens until the next end-of-line. + */ + +// token white space is ' ', '\n', '\r', ',', '\t', ';' +static const bool is_white_Array[ 256 ] = +{ false, false, false, false, false, false, false, false, // 0-7 + false, true, true, false, false, true, false, false, // 8-15 + false, false, false, false, false, false, false, false, // 16-23 + false, false, false, false, false, false, false, false, // 24-31 + true, false, false, false, false, false, false, false, // 32-39 + false, false, false, false, true, false, false, false, // 40-47 + false, false, false, false, false, false, false, false, // 48-55 + false, false, false, true, false, false, false, false, // 56-63 + + false, false, false, false, false, false, false, false, // 64 - + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, // 127 + + false, false, false, false, false, false, false, false, // 128 - + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, // 191 + + false, false, false, false, false, false, false, false, // 192 - + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, // 255 +}; +// token delimiters are ' ', '\n', '\r', '\t', ':', ';' +static const bool is_delimiter_Array[ 256 ] = +{ false, false, false, false, false, false, false, false, // 0-7 + false, true, true, false, false, true, false, false, // 8-15 + false, false, false, false, false, false, false, false, // 16-23 + false, false, false, false, false, false, false, false, // 24-31 + true, false, false, false, false, false, false, false, // 32-39 + false, false, false, false, false, false, false, false, // 40-47 + false, false, false, false, false, false, false, false, // 48-55 + false, false, true, true, false, false, false, false, // 56-63 + + false, false, false, false, false, false, false, false, // 64 - + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, // 127 + + false, false, false, false, false, false, false, false, // 128 - + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, // 191 + + false, false, false, false, false, false, false, false, // 192 - + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, // 255 +}; +static char *token( FileInputStream* stream, int& rLen ) +{ + static char ident[MAX_NAME]; /* storage buffer for keywords */ + + int ch, idx; + + /* skip over white space */ + // relies on EOF = -1 + while( is_white_Array[ (ch = stream->getChar()) & 255 ] ) + ; + + idx = 0; + while( ch != -1 && ! is_delimiter_Array[ ch & 255 ] && idx < MAX_NAME-1 ) + { + ident[idx++] = ch; + ch = stream->getChar(); + } + + if (ch == -1 && idx < 1) return (nullptr); + if (idx >= 1 && ch != ':' && ch != -1) stream->ungetChar(); + if (idx < 1 ) ident[idx++] = ch; /* single-character token */ + ident[idx] = 0; + rLen = idx; + + return ident; /* returns pointer to the token */ + +} /* token */ + +/*************************** linetoken *************************/ + +/* "linetoken" will get read all tokens until the EOL character from + * the given stream. This is used to get any arguments that can be + * more than one word (like Comment lines and FullName). + */ + +static char *linetoken( FileInputStream* stream ) +{ + static char ident[MAX_NAME]; /* storage buffer for keywords */ + int ch, idx; + + while ((ch = stream->getChar()) == ' ' || ch == '\t' ) ; + + idx = 0; + while (ch != -1 && ch != lineterm && ch != '\r' && idx < MAX_NAME-1 ) + { + ident[idx++] = ch; + ch = stream->getChar(); + } /* while */ + + stream->ungetChar(); + ident[idx] = 0; + + return ident; /* returns pointer to the token */ + +} /* linetoken */ + +/*************************** recognize *************************/ + +/* This function tries to match a string to a known list of + * valid AFM entries (check the keyStrings array above). + * "ident" contains everything from white space through the + * next space, tab, or ":" character. + * + * The algorithm is a standard Knuth binary search. + */ +#if defined __clang__ +#if __has_warning("-Wdeprecated-register") +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-register" +#endif +#endif +#include "afm_hash.hpp" +#if defined __clang__ +#if __has_warning("-Wdeprecated-register") +#pragma GCC diagnostic pop +#endif +#endif + +static inline enum parseKey recognize( char* ident, int len) +{ + const hash_entry* pEntry = AfmKeywordHash::in_word_set( ident, len ); + return pEntry ? pEntry->eKey : NOPE; + +} /* recognize */ + +/************************* parseGlobals *****************************/ + +/* This function is called by "parseFile". It will parse the AFM file + * up to the "StartCharMetrics" keyword, which essentially marks the + * end of the Global Font Information and the beginning of the character + * metrics information. + * + * If the caller of "parseFile" specified that it wanted the Global + * Font Information (as defined by the "AFM file Specification" + * document), then that information will be stored in the returned + * data structure. + * + * Any Global Font Information entries that are not found in a + * given file, will have the usual default initialization value + * for its type (i.e. entries of type int will be 0, etc). + * + * This function returns an error code specifying whether there was + * a premature EOF or a parsing error. This return value is used by + * parseFile to determine if there is more file to parse. + */ + +static int parseGlobals( FileInputStream* fp, GlobalFontInfo* gfi ) +{ + bool cont = true, save = (gfi != nullptr); + int error = ok; + int direction = -1; + int tokenlen; + + while (cont) + { + char *keyword = token(fp, tokenlen); + + if (keyword == nullptr) + /* Have reached an early and unexpected EOF. */ + /* Set flag and stop parsing */ + { + error = earlyEOF; + break; /* get out of loop */ + } + if (!save) + /* get tokens until the end of the Global Font info section */ + /* without saving any of the data */ + switch (recognize(keyword, tokenlen)) + { + case STARTCHARMETRICS: + cont = false; + break; + case ENDFONTMETRICS: + cont = false; + error = normalEOF; + break; + default: + break; + } /* switch */ + else + /* otherwise parse entire global font info section, */ + /* saving the data */ + switch(recognize(keyword, tokenlen)) + { + case STARTFONTMETRICS: + if ((keyword = token(fp,tokenlen)) != nullptr) + gfi->afmVersion = strdup( keyword ); + break; + case COMMENT: + linetoken(fp); + break; + case FONTNAME: + if ((keyword = token(fp,tokenlen)) != nullptr) + gfi->fontName = strdup( keyword ); + break; + case ENCODINGSCHEME: + if ((keyword = token(fp,tokenlen)) != nullptr) + gfi->encodingScheme = strdup( keyword ); + break; + case FULLNAME: + if ((keyword = linetoken(fp)) != nullptr) + gfi->fullName = strdup( keyword ); + break; + case FAMILYNAME: + if ((keyword = linetoken(fp)) != nullptr) + gfi->familyName = strdup( keyword ); + break; + case WEIGHT: + if ((keyword = token(fp,tokenlen)) != nullptr) + gfi->weight = strdup( keyword ); + break; + case ITALICANGLE: + if ((keyword = token(fp,tokenlen)) != nullptr) + gfi->italicAngle = StringToDouble( keyword ); + break; + case ISFIXEDPITCH: + if ((keyword = token(fp,tokenlen)) != nullptr) + { + if (MATCH(keyword, False)) + gfi->isFixedPitch = false; + else + gfi->isFixedPitch = true; + } + break; + case UNDERLINEPOSITION: + if ((keyword = token(fp,tokenlen)) != nullptr) + gfi->underlinePosition = atoi(keyword); + break; + case UNDERLINETHICKNESS: + if ((keyword = token(fp,tokenlen)) != nullptr) + gfi->underlineThickness = atoi(keyword); + break; + case VERSION: + if ((keyword = token(fp,tokenlen)) != nullptr) + gfi->version = strdup( keyword ); + break; + case NOTICE: + if ((keyword = linetoken(fp)) != nullptr) + gfi->notice = strdup( keyword ); + break; + case FONTBBOX: + if ((keyword = token(fp,tokenlen)) != nullptr) + gfi->fontBBox.llx = atoi(keyword); + if ((keyword = token(fp,tokenlen)) != nullptr) + gfi->fontBBox.lly = atoi(keyword); + if ((keyword = token(fp,tokenlen)) != nullptr) + gfi->fontBBox.urx = atoi(keyword); + if ((keyword = token(fp,tokenlen)) != nullptr) + gfi->fontBBox.ury = atoi(keyword); + break; + case CAPHEIGHT: + if ((keyword = token(fp,tokenlen)) != nullptr) + gfi->capHeight = atoi(keyword); + break; + case XHEIGHT: + if ((keyword = token(fp,tokenlen)) != nullptr) + gfi->xHeight = atoi(keyword); + break; + case DESCENT: + if ((keyword = token(fp,tokenlen)) != nullptr) + gfi->descender = -atoi(keyword); + break; + case DESCENDER: + if ((keyword = token(fp,tokenlen)) != nullptr) + gfi->descender = atoi(keyword); + break; + case ASCENT: + case ASCENDER: + if ((keyword = token(fp,tokenlen)) != nullptr) + gfi->ascender = atoi(keyword); + break; + case STARTCHARMETRICS: + cont = false; + break; + case ENDFONTMETRICS: + cont = false; + error = normalEOF; + break; + case EM: + // skip one token + token(fp,tokenlen); + break; + case STARTDIRECTION: + if ((keyword = token(fp,tokenlen)) != nullptr) + direction = atoi(keyword); + break; /* ignore this for now */ + case ENDDIRECTION: + break; /* ignore this for now */ + case MAPPINGSCHEME: + token(fp,tokenlen); + break; /* ignore this for now */ + case CHARACTERS: + token(fp,tokenlen); + break; /* ignore this for now */ + case ISBASEFONT: + token(fp,tokenlen); + break; /* ignore this for now */ + case CHARACTERSET: + token(fp,tokenlen); //ignore + break; + case STDHW: + token(fp,tokenlen); //ignore + break; + case STDVW: + token(fp,tokenlen); //ignore + break; + case CHARWIDTH: + if ((keyword = token(fp,tokenlen)) != nullptr) + { + if (direction == 0) + gfi->charwidth = atoi(keyword); + } + token(fp,tokenlen); + /* ignore y-width for now */ + break; + case METRICSSETS: + token(fp,tokenlen); /*eat token*/ + break; /* ignore this for now */ + case NOPE: + default: + error = parseError; + break; + } /* switch */ + } /* while */ + + return error; + +} /* parseGlobals */ + +/************************* parseCharWidths **************************/ + +/* This function is called by "parseFile". It will parse the AFM file + * up to the "EndCharMetrics" keyword. It will save the character + * width info (as opposed to all of the character metric information) + * if requested by the caller of parseFile. Otherwise, it will just + * parse through the section without saving any information. + * + * If data is to be saved, parseCharWidths is passed in a pointer + * to an array of widths that has already been initialized by the + * standard value for unmapped character codes. This function parses + * the Character Metrics section only storing the width information + * for the encoded characters into the array using the character code + * as the index into that array. + * + * This function returns an error code specifying whether there was + * a premature EOF or a parsing error. This return value is used by + * parseFile to determine if there is more file to parse. + */ + +static int parseCharWidths( FileInputStream* fp, int* cwi) +{ + bool cont = true, save = (cwi != nullptr); + int pos = 0, error = ok, tokenlen; + + while (cont) + { + char *keyword = token(fp,tokenlen); + /* Have reached an early and unexpected EOF. */ + /* Set flag and stop parsing */ + if (keyword == nullptr) + { + error = earlyEOF; + break; /* get out of loop */ + } + if (!save) + /* get tokens until the end of the Char Metrics section without */ + /* saving any of the data*/ + switch (recognize(keyword,tokenlen)) + { + case ENDCHARMETRICS: + cont = false; + break; + case ENDFONTMETRICS: + cont = false; + error = normalEOF; + break; + default: + break; + } /* switch */ + else + /* otherwise parse entire char metrics section, saving */ + /* only the char x-width info */ + switch(recognize(keyword,tokenlen)) + { + case COMMENT: + linetoken(fp); /*eat token*/ + break; + case CODE: + if ((keyword = token(fp,tokenlen)) != nullptr) + pos = atoi(keyword); + break; + case XYWIDTH: + /* PROBLEM: Should be no Y-WIDTH when doing "quick & dirty" */ + token(fp,tokenlen); token(fp,tokenlen); /* eat values */ + error = parseError; + break; + case CODEHEX: + if ((keyword = token(fp,tokenlen)) != nullptr) + sscanf(keyword, "<%x>", &pos); + break; + case X0WIDTH: + (void) token(fp,tokenlen); + break; + case XWIDTH: + if ((keyword = token(fp,tokenlen)) != nullptr) + if (pos >= 0) /* ignore unmapped chars */ + cwi[pos] = atoi(keyword); + break; + case ENDCHARMETRICS: + cont = false; + break; + case ENDFONTMETRICS: + cont = false; + error = normalEOF; + break; + case CHARNAME: /* eat values (so doesn't cause parseError) */ + token(fp,tokenlen); + break; + case CHARBBOX: + token(fp,tokenlen); token(fp,tokenlen); + token(fp,tokenlen); token(fp,tokenlen); + break; + case LIGATURE: + token(fp,tokenlen); token(fp,tokenlen); + break; + case VVECTOR: + token(fp,tokenlen); /*eat token*/ + token(fp,tokenlen); /*eat token*/ + break; + case NOPE: + default: + error = parseError; + break; + } /* switch */ + } /* while */ + + return error; + +} /* parseCharWidths */ + +/* + * number of char metrics is almost always inaccurate, so be gentle and try to + * adapt our internal storage by adjusting the allocated list + */ + +static int +reallocFontMetrics( void **pp_fontmetrics, int *p_oldcount, int n_newcount, unsigned int n_size ) +{ + char *p_tmpmetrics = nullptr; + + if ((pp_fontmetrics == nullptr) || (*pp_fontmetrics == nullptr)) + return storageProblem; + + if (*p_oldcount == n_newcount) + return ok; + + p_tmpmetrics = static_cast<char*>(realloc(*pp_fontmetrics, n_newcount * n_size)); + if (p_tmpmetrics == nullptr) + return storageProblem; + + if ( n_newcount > *p_oldcount ) + { + char *p_inimetrics = p_tmpmetrics + n_size * *p_oldcount; + int n_inimetrics = n_size * (n_newcount - *p_oldcount); + memset( p_inimetrics, 0, n_inimetrics ); + } + + *pp_fontmetrics = p_tmpmetrics; + *p_oldcount = n_newcount; + + return ok; +} + +static unsigned int +enlargeCount( unsigned int n_oldcount ) +{ + unsigned int n_newcount = n_oldcount + n_oldcount / 5; + if (n_oldcount == n_newcount ) + n_newcount = n_oldcount + 5; + + return n_newcount; +} + +/************************* parseCharMetrics ************************/ + +/* This function is called by parseFile if the caller of parseFile + * requested that all character metric information be saved + * (as opposed to only the character width information). + * + * parseCharMetrics is passed in a pointer to an array of records + * to hold information on a per character basis. This function + * parses the Character Metrics section storing all character + * metric information for the ALL characters (mapped and unmapped) + * into the array. + * + * This function returns an error code specifying whether there was + * a premature EOF or a parsing error. This return value is used by + * parseFile to determine if there is more file to parse. + */ + +static int parseCharMetrics( FileInputStream* fp, FontInfo* fi) +{ + bool cont = true, firstTime = true; + int error = ok, count = 0, tokenlen; + CharMetricInfo *temp = fi->cmi; + + while (cont) + { + char *keyword = token(fp,tokenlen); + if (keyword == nullptr) + { + error = earlyEOF; + break; /* get out of loop */ + } + switch(recognize(keyword,tokenlen)) + { + case COMMENT: + linetoken(fp); /*eat token*/ + break; + case CODE: + if (!(count < fi->numOfChars)) + { + reallocFontMetrics( reinterpret_cast<void**>(&fi->cmi), + &(fi->numOfChars), enlargeCount(fi->numOfChars), + sizeof(CharMetricInfo) ); + temp = &(fi->cmi[ count - 1 ]); + } + if (count < fi->numOfChars) + { + if (firstTime) firstTime = false; + else temp++; + if ((keyword = token(fp,tokenlen)) != nullptr) + temp->code = atoi(keyword); + if (fi->gfi && fi->gfi->charwidth) + temp->wx = fi->gfi->charwidth; + count++; + } + else + { + error = parseError; + cont = false; + } + break; + case CODEHEX: + if (!(count < fi->numOfChars )) + { + reallocFontMetrics( reinterpret_cast<void**>(&fi->cmi), + &(fi->numOfChars), enlargeCount(fi->numOfChars), + sizeof(CharMetricInfo) ); + temp = &(fi->cmi[ count - 1 ]); + } + if (count < fi->numOfChars) { + if (firstTime) + firstTime = false; + else + temp++; + if ((keyword = token(fp,tokenlen)) != nullptr) + sscanf(keyword,"<%x>", &temp->code); + if (fi->gfi && fi->gfi->charwidth) + temp->wx = fi->gfi->charwidth; + count++; + } + else { + error = parseError; + cont = false; + } + break; + case XYWIDTH: + if ((keyword = token(fp,tokenlen)) != nullptr) + temp->wx = atoi(keyword); + if ((keyword = token(fp,tokenlen)) != nullptr) + temp->wy = atoi(keyword); + break; + case X0WIDTH: + if ((keyword = token(fp,tokenlen)) != nullptr) + temp->wx = atoi(keyword); + break; + case XWIDTH: + if ((keyword = token(fp,tokenlen)) != nullptr) + temp->wx = atoi(keyword); + break; + case CHARNAME: + if ((keyword = token(fp,tokenlen)) != nullptr) + temp->name = strdup(keyword); + break; + case CHARBBOX: + if ((keyword = token(fp,tokenlen)) != nullptr) + temp->charBBox.llx = atoi(keyword); + if ((keyword = token(fp,tokenlen)) != nullptr) + temp->charBBox.lly = atoi(keyword); + if ((keyword = token(fp,tokenlen)) != nullptr) + temp->charBBox.urx = atoi(keyword); + if ((keyword = token(fp,tokenlen)) != nullptr) + temp->charBBox.ury = atoi(keyword); + break; + case LIGATURE: { + Ligature **tail = &(temp->ligs); + Ligature *node = *tail; + + if (*tail != nullptr) + { + while (node->next != nullptr) + node = node->next; + tail = &(node->next); + } + + *tail = static_cast<Ligature *>(calloc(1, sizeof(Ligature))); + if ((keyword = token(fp,tokenlen)) != nullptr) + (*tail)->succ = strdup(keyword); + if ((keyword = token(fp,tokenlen)) != nullptr) + (*tail)->lig = strdup(keyword); + break; } + case ENDCHARMETRICS: + cont = false; + break; + case ENDFONTMETRICS: + cont = false; + error = normalEOF; + break; + case VVECTOR: + token(fp,tokenlen); /*eat token*/ + token(fp,tokenlen); /*eat token*/ + break; + case NOPE: + default: + error = parseError; + break; + } /* switch */ + } /* while */ + + if ((error == ok) && (count != fi->numOfChars)) + error = reallocFontMetrics( reinterpret_cast<void**>(&fi->cmi), &(fi->numOfChars), + count, sizeof(CharMetricInfo) ); + + if ((error == ok) && (count != fi->numOfChars)) + error = parseError; + + return error; + +} /* parseCharMetrics */ + +/************************* parseTrackKernData ***********************/ + +/* This function is called by "parseFile". It will parse the AFM file + * up to the "EndTrackKern" or "EndKernData" keywords. It will save the + * track kerning data if requested by the caller of parseFile. + * + * parseTrackKernData is passed in a pointer to the FontInfo record. + * If data is to be saved, the FontInfo record will already contain + * a valid pointer to storage for the track kerning data. + * + * This function returns an error code specifying whether there was + * a premature EOF or a parsing error. This return value is used by + * parseFile to determine if there is more file to parse. + */ + +static int parseTrackKernData( FileInputStream* fp, FontInfo* fi) +{ + bool cont = true, save = (fi->tkd != nullptr); + int pos = 0, error = ok, tcount = 0, tokenlen; + + while (cont) + { + char *keyword = token(fp,tokenlen); + + if (keyword == nullptr) + { + error = earlyEOF; + break; /* get out of loop */ + } + if (!save) + /* get tokens until the end of the Track Kerning Data */ + /* section without saving any of the data */ + switch(recognize(keyword,tokenlen)) + { + case ENDTRACKKERN: + case ENDKERNDATA: + cont = false; + break; + case ENDFONTMETRICS: + cont = false; + error = normalEOF; + break; + default: + break; + } /* switch */ + else + /* otherwise parse entire Track Kerning Data section, */ + /* saving the data */ + switch(recognize(keyword,tokenlen)) + { + case COMMENT: + linetoken(fp); /*eat token*/ + break; + case TRACKKERN: + if (!(tcount < fi->numOfTracks)) + { + reallocFontMetrics( reinterpret_cast<void**>(&fi->tkd), &(fi->numOfTracks), + enlargeCount(fi->numOfTracks), sizeof(TrackKernData) ); + } + + if (tcount < fi->numOfTracks) + { + if ((keyword = token(fp,tokenlen)) != nullptr) + fi->tkd[pos].degree = atoi(keyword); + if ((keyword = token(fp,tokenlen)) != nullptr) + fi->tkd[pos].minPtSize = StringToDouble(keyword); + if ((keyword = token(fp,tokenlen)) != nullptr) + fi->tkd[pos].minKernAmt = StringToDouble(keyword); + if ((keyword = token(fp,tokenlen)) != nullptr) + fi->tkd[pos].maxPtSize = StringToDouble(keyword); + if ((keyword = token(fp,tokenlen)) != nullptr) + fi->tkd[pos++].maxKernAmt = StringToDouble(keyword); + tcount++; + } + else + { + error = parseError; + cont = false; + } + break; + case ENDTRACKKERN: + case ENDKERNDATA: + cont = false; + break; + case ENDFONTMETRICS: + cont = false; + error = normalEOF; + break; + case NOPE: + default: + error = parseError; + break; + } /* switch */ + } /* while */ + + if (error == ok && tcount != fi->numOfTracks) + error = reallocFontMetrics( reinterpret_cast<void**>(&fi->tkd), &(fi->numOfTracks), + tcount, sizeof(TrackKernData) ); + + if (error == ok && tcount != fi->numOfTracks) + error = parseError; + + return error; + +} /* parseTrackKernData */ + +/************************* parsePairKernData ************************/ + +/* This function is called by "parseFile". It will parse the AFM file + * up to the "EndKernPairs" or "EndKernData" keywords. It will save + * the pair kerning data if requested by the caller of parseFile. + * + * parsePairKernData is passed in a pointer to the FontInfo record. + * If data is to be saved, the FontInfo record will already contain + * a valid pointer to storage for the pair kerning data. + * + * This function returns an error code specifying whether there was + * a premature EOF or a parsing error. This return value is used by + * parseFile to determine if there is more file to parse. + */ + +static int parsePairKernData( FileInputStream* fp, FontInfo* fi) +{ + bool cont = true, save = (fi->pkd != nullptr); + int pos = 0, error = ok, pcount = 0, tokenlen; + + while (cont) + { + char *keyword = token(fp,tokenlen); + + if (keyword == nullptr) + { + error = earlyEOF; + break; /* get out of loop */ + } + if (!save) + /* get tokens until the end of the Pair Kerning Data */ + /* section without saving any of the data */ + switch(recognize(keyword,tokenlen)) + { + case ENDKERNPAIRS: + case ENDKERNDATA: + cont = false; + break; + case ENDFONTMETRICS: + cont = false; + error = normalEOF; + break; + default: + break; + } /* switch */ + else + /* otherwise parse entire Pair Kerning Data section, */ + /* saving the data */ + switch(recognize(keyword,tokenlen)) + { + case COMMENT: + linetoken(fp); /*eat token*/ + break; + case KERNPAIR: + if (!(pcount < fi->numOfPairs)) + { + reallocFontMetrics( reinterpret_cast<void**>(&fi->pkd), &(fi->numOfPairs), + enlargeCount(fi->numOfPairs), sizeof(PairKernData) ); + } + if (pcount < fi->numOfPairs) + { + if ((keyword = token(fp,tokenlen)) != nullptr) + fi->pkd[pos].name1 = strdup( keyword ); + if ((keyword = token(fp,tokenlen)) != nullptr) + fi->pkd[pos].name2 = strdup( keyword ); + if ((keyword = token(fp,tokenlen)) != nullptr) + fi->pkd[pos].xamt = atoi(keyword); + if ((keyword = token(fp,tokenlen)) != nullptr) + fi->pkd[pos++].yamt = atoi(keyword); + pcount++; + } + else + { + error = parseError; + cont = false; + } + break; + case KERNPAIRXAMT: + if (!(pcount < fi->numOfPairs)) + { + reallocFontMetrics( reinterpret_cast<void**>(&fi->pkd), &(fi->numOfPairs), + enlargeCount(fi->numOfPairs), sizeof(PairKernData) ); + } + if (pcount < fi->numOfPairs) + { + if ((keyword = token(fp,tokenlen)) != nullptr) + fi->pkd[pos].name1 = strdup( keyword ); + if ((keyword = token(fp,tokenlen)) != nullptr) + fi->pkd[pos].name2 = strdup( keyword ); + if ((keyword = token(fp,tokenlen)) != nullptr) + fi->pkd[pos++].xamt = atoi(keyword); + pcount++; + } + else + { + error = parseError; + cont = false; + } + break; + case ENDKERNPAIRS: + case ENDKERNDATA: + cont = false; + break; + case ENDFONTMETRICS: + cont = false; + error = normalEOF; + break; + case NOPE: + default: + error = parseError; + break; + } /* switch */ + } /* while */ + + if ((error == ok) && (pcount != fi->numOfPairs)) + error = reallocFontMetrics( reinterpret_cast<void**>(&fi->pkd), &(fi->numOfPairs), + pcount, sizeof(PairKernData) ); + + if (error == ok && pcount != fi->numOfPairs) + error = parseError; + + return error; + +} /* parsePairKernData */ + +/************************* parseCompCharData **************************/ + +/* This function is called by "parseFile". It will parse the AFM file + * up to the "EndComposites" keyword. It will save the composite + * character data if requested by the caller of parseFile. + * + * parseCompCharData is passed in a pointer to the FontInfo record, and + * a boolean representing if the data should be saved. + * + * This function will create the appropriate amount of storage for + * the composite character data and store a pointer to the storage + * in the FontInfo record. + * + * This function returns an error code specifying whether there was + * a premature EOF or a parsing error. This return value is used by + * parseFile to determine if there is more file to parse. + */ + +static int parseCompCharData( FileInputStream* fp, FontInfo* fi) +{ + bool cont = true, firstTime = true, save = (fi->ccd != nullptr); + int pos = 0, j = 0, error = ok, ccount = 0, pcount = 0, tokenlen; + + while (cont) + { + char *keyword = token(fp,tokenlen); + if (keyword == nullptr) + /* Have reached an early and unexpected EOF. */ + /* Set flag and stop parsing */ + { + error = earlyEOF; + break; /* get out of loop */ + } + if (ccount > fi->numOfComps) + { + reallocFontMetrics( reinterpret_cast<void**>(&fi->ccd), &(fi->numOfComps), + enlargeCount(fi->numOfComps), sizeof(CompCharData) ); + } + if (ccount > fi->numOfComps) + { + error = parseError; + break; /* get out of loop */ + } + if (!save) + /* get tokens until the end of the Composite Character info */ + /* section without saving any of the data */ + switch(recognize(keyword,tokenlen)) + { + case ENDCOMPOSITES: + cont = false; + break; + case ENDFONTMETRICS: + cont = false; + error = normalEOF; + break; + case COMMENT: + case COMPCHAR: + linetoken(fp); + break; + default: + break; + } /* switch */ + else + /* otherwise parse entire Composite Character info section, */ + /* saving the data */ + switch(recognize(keyword,tokenlen)) + { + case COMMENT: + linetoken(fp); /*eat token*/ + break; + case COMPCHAR: + if (!(ccount < fi->numOfComps)) + { + reallocFontMetrics( reinterpret_cast<void**>(&fi->ccd), &(fi->numOfComps), + enlargeCount(fi->numOfComps), sizeof(CompCharData) ); + } + if (ccount < fi->numOfComps) + { + keyword = token(fp,tokenlen); + if (pcount != fi->ccd[pos].numOfPieces) + error = parseError; + pcount = 0; + if (firstTime) firstTime = false; + else pos++; + fi->ccd[pos].ccName = strdup( keyword ); + if ((keyword = token(fp,tokenlen)) != nullptr) + fi->ccd[pos].numOfPieces = atoi(keyword); + fi->ccd[pos].pieces = static_cast<Pcc *>( + calloc(fi->ccd[pos].numOfPieces, sizeof(Pcc))); + j = 0; + ccount++; + } + else + { + error = parseError; + cont = false; + } + break; + case COMPCHARPIECE: + if (pcount < fi->ccd[pos].numOfPieces) + { + if ((keyword = token(fp,tokenlen)) != nullptr) + fi->ccd[pos].pieces[j].pccName = strdup( keyword ); + if ((keyword = token(fp,tokenlen)) != nullptr) + fi->ccd[pos].pieces[j].deltax = atoi(keyword); + if ((keyword = token(fp,tokenlen)) != nullptr) + fi->ccd[pos].pieces[j++].deltay = atoi(keyword); + pcount++; + } + else + error = parseError; + break; + case ENDCOMPOSITES: + cont = false; + break; + case ENDFONTMETRICS: + cont = false; + error = normalEOF; + break; + case NOPE: + default: + error = parseError; + break; + } /* switch */ + } /* while */ + + if (error == ok && ccount != fi->numOfComps) + reallocFontMetrics( reinterpret_cast<void**>(&fi->ccd), &(fi->numOfComps), + ccount, sizeof(CompCharData) ); + + if (error == ok && ccount != fi->numOfComps) + error = parseError; + + return error; + +} /* parseCompCharData */ + +/*************************** 'PUBLIC' FUNCTION ********************/ + +/*************************** parseFile *****************************/ + +/* parseFile is the only 'public' procedure available. It is called + * from an application wishing to get information from an AFM file. + * The caller of this function is responsible for locating and opening + * an AFM file and handling all errors associated with that task. + * + * parseFile expects 3 parameters: a filename pointer, a pointer + * to a (FontInfo *) variable (for which storage will be allocated and + * the data requested filled in), and a mask specifying which + * data from the AFM file should be saved in the FontInfo structure. + * + * The file will be parsed and the requested data will be stored in + * a record of type FontInfo (refer to ParseAFM.h). + * + * parseFile returns an error code as defined in parseAFM.h. + * + * The position of the read/write pointer associated with the file + * pointer upon return of this function is undefined. + */ + +int parseFile( const char* pFilename, FontInfo** fi, FLAGS flags) +{ + FileInputStream aFile( pFilename ); + + int code = ok; /* return code from each of the parsing routines */ + int error = ok; /* used as the return code from this function */ + int tokenlen; + + char *keyword; /* used to store a token */ + + (*fi) = static_cast<FontInfo *>(calloc(1, sizeof(FontInfo))); + if ((*fi) == nullptr) { error = storageProblem; return error; } + + if (flags & P_G) + { + (*fi)->gfi = static_cast<GlobalFontInfo *>(calloc(1, sizeof(GlobalFontInfo))); + if ((*fi)->gfi == nullptr) { error = storageProblem; return error; } + } + + /* The AFM file begins with Global Font Information. This section */ + /* will be parsed whether or not information should be saved. */ + code = parseGlobals(&aFile, (*fi)->gfi); + + if (code < 0) error = code; + + /* The Global Font Information is followed by the Character Metrics */ + /* section. Which procedure is used to parse this section depends on */ + /* how much information should be saved. If all of the metrics info */ + /* is wanted, parseCharMetrics is called. If only the character widths */ + /* is wanted, parseCharWidths is called. parseCharWidths will also */ + /* be called in the case that no character data is to be saved, just */ + /* to parse through the section. */ + + if ((code != normalEOF) && (code != earlyEOF)) + { + if ((keyword = token(&aFile,tokenlen)) != nullptr) + (*fi)->numOfChars = atoi(keyword); + if (flags & (P_M ^ P_W)) + { + (*fi)->cmi = static_cast<CharMetricInfo *>( + calloc((*fi)->numOfChars, sizeof(CharMetricInfo))); + if ((*fi)->cmi == nullptr) { error = storageProblem; return error; } + code = parseCharMetrics(&aFile, *fi); + } + else + { + if (flags & P_W) + { + (*fi)->cwi = static_cast<int *>(calloc(256, sizeof(int))); + if ((*fi)->cwi == nullptr) + { + error = storageProblem; + return error; + } + } + /* parse section regardless */ + code = parseCharWidths(&aFile, (*fi)->cwi); + } /* else */ + } /* if */ + + if ((error != earlyEOF) && (code < 0)) error = code; + + /* The remaining sections of the AFM are optional. This code will */ + /* look at the next keyword in the file to determine what section */ + /* is next, and then allocate the appropriate amount of storage */ + /* for the data (if the data is to be saved) and call the */ + /* appropriate parsing routine to parse the section. */ + + while ((code != normalEOF) && (code != earlyEOF)) + { + keyword = token(&aFile,tokenlen); + if (keyword == nullptr) + /* Have reached an early and unexpected EOF. */ + /* Set flag and stop parsing */ + { + code = earlyEOF; + break; /* get out of loop */ + } + switch(recognize(keyword,tokenlen)) + { + case STARTKERNDATA: + break; + case ENDKERNDATA: + break; + case STARTTRACKKERN: + keyword = token(&aFile,tokenlen); + if ((flags & P_T) && keyword) + { + (*fi)->numOfTracks = atoi(keyword); + (*fi)->tkd = static_cast<TrackKernData *>( + calloc((*fi)->numOfTracks, sizeof(TrackKernData))); + if ((*fi)->tkd == nullptr) + { + error = storageProblem; + return error; + } + } /* if */ + code = parseTrackKernData(&aFile, *fi); + break; + case STARTKERNPAIRS: + keyword = token(&aFile,tokenlen); + if ((flags & P_P) && keyword) + { + (*fi)->numOfPairs = atoi(keyword); + (*fi)->pkd = static_cast<PairKernData *>( + calloc((*fi)->numOfPairs, sizeof(PairKernData))); + if ((*fi)->pkd == nullptr) + { + error = storageProblem; + return error; + } + } /* if */ + code = parsePairKernData(&aFile, *fi); + break; + case STARTCOMPOSITES: + keyword = token(&aFile,tokenlen); + if ((flags & P_C) && keyword) + { + (*fi)->numOfComps = atoi(keyword); + (*fi)->ccd = static_cast<CompCharData *>( + calloc((*fi)->numOfComps, sizeof(CompCharData))); + if ((*fi)->ccd == nullptr) + { + error = storageProblem; + return error; + } + } /* if */ + code = parseCompCharData(&aFile, *fi); + break; + case ENDFONTMETRICS: + code = normalEOF; + break; + case COMMENT: + linetoken(&aFile); + break; + case NOPE: + default: + code = parseError; + break; + } /* switch */ + + if ((error != earlyEOF) && (code < 0)) error = code; + + } /* while */ + + if ((error != earlyEOF) && (code < 0)) error = code; + + return error; + +} /* parseFile */ + +void +freeFontInfo (FontInfo *fi) +{ + int i; + + if (fi->gfi) + { + free (fi->gfi->afmVersion); + free (fi->gfi->fontName); + free (fi->gfi->fullName); + free (fi->gfi->familyName); + free (fi->gfi->weight); + free (fi->gfi->version); + free (fi->gfi->notice); + free (fi->gfi->encodingScheme); + free (fi->gfi); + } + + free (fi->cwi); + + if (fi->cmi) + { + for (i = 0; i < fi->numOfChars; i++) + { + Ligature *ligs; + free (fi->cmi[i].name); + ligs = fi->cmi[i].ligs; + while (ligs) + { + Ligature *tmp; + tmp = ligs; + ligs = ligs->next; + free (tmp->succ); + free (tmp->lig); + free (tmp); + } + } + free (fi->cmi); + } + + free (fi->tkd); + + if (fi->pkd) + { + for ( i = 0; i < fi->numOfPairs; i++) + { + free (fi->pkd[i].name1); + free (fi->pkd[i].name2); + } + free (fi->pkd); + } + + if (fi->ccd) + { + for (i = 0; i < fi->numOfComps; i++) + { + free (fi->ccd[i].ccName); + int j; + for (j = 0; j < fi->ccd[i].numOfPieces; j++) + free (fi->ccd[i].pieces[j].pccName); + + free (fi->ccd[i].pieces); + } + free (fi->ccd); + } + + free (fi); +} + +} // namspace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/fontmanager/parseAFM.hxx b/vcl/unx/generic/fontmanager/parseAFM.hxx new file mode 100644 index 000000000000..a31217b9a14f --- /dev/null +++ b/vcl/unx/generic/fontmanager/parseAFM.hxx @@ -0,0 +1,323 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * (C) 1988, 1989 by Adobe Systems Incorporated. All rights reserved. + * + * This file may be freely copied and redistributed as long as: + * 1) This entire notice continues to be included in the file, + * 2) If the file has been modified in any way, a notice of such + * modification is conspicuously indicated. + * + * PostScript, Display PostScript, and Adobe are registered trademarks of + * Adobe Systems Incorporated. + * + * ************************************************************************ + * THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO CHANGE WITHOUT + * NOTICE, AND SHOULD NOT BE CONSTRUED AS A COMMITMENT BY ADOBE SYSTEMS + * INCORPORATED. ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY OR + * LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO WARRANTY OF ANY + * KIND (EXPRESS, IMPLIED OR STATUTORY) WITH RESPECT TO THIS INFORMATION, + * AND EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + * ************************************************************************ + */ + +/* + * Changes made for OpenOffice.org + * + * 10/24/2000 pl - changed code to compile with c++-compilers + * - added namespace to avoid symbol clashes + * - replaced BOOL by bool + * - added function to free space allocated by parseFile + * 10/26/2000 pl - added additional keys + * - added ability to parse slightly broken files + * - added charwidth member to GlobalFontInfo + * 04/26/2001 pl - added OpenOffice header + * 10/19/2005 pl - changed parseFile to accept a file name instead of a stream + */ + +/* ParseAFM.h + * + * This header file is used in conjunction with the parseAFM.c file. + * Together these files provide the functionality to parse Adobe Font + * Metrics files and store the information in predefined data structures. + * It is intended to work with an application program that needs font metric + * information. The program can be used as is by making a procedure call to + * parse an AFM file and have the data stored, or an application developer + * may wish to customize the code. + * + * This header file defines the data structures used as well as the key + * strings that are currently recognized by this version of the AFM parser. + * This program is based on the document "Adobe Font Metrics Files, + * Specification Version 2.0". + * + * AFM files are separated into distinct sections of different data. Because + * of this, the parseAFM program can parse a specified file to only save + * certain sections of information based on the application's needs. A record + * containing the requested information will be returned to the application. + * + * AFM files are divided into five sections of data: + * 1) The Global Font Information + * 2) The Character Metrics Information + * 3) The Track Kerning Data + * 4) The Pair-Wise Kerning Data + * 5) The Composite Character Data + * + * Basically, the application can request any of these sections independent + * of what other sections are requested. In addition, in recognizing that + * many applications will want ONLY the x-width of characters and not all + * of the other character metrics information, there is a way to receive + * only the width information so as not to pay the storage cost for the + * unwanted data. An application should never request both the + * "quick and dirty" char metrics (widths only) and the Character Metrics + * Information since the Character Metrics Information will contain all + * of the character widths as well. + * + * There is a procedure in parseAFM.c, called parseFile, that can be + * called from any application wishing to get information from the AFM File. + * This procedure expects 3 parameters: a valid file descriptor, a pointer + * to a (FontInfo *) variable (for which space will be allocated and then + * will be filled in with the data requested), and a mask specifying + * which data from the AFM File should be saved in the FontInfo structure. + * + * The flags that can be used to set the appropriate mask are defined below. + * In addition, several commonly used masks have already been defined. + * + * History: + * original: DSM Thu Oct 20 17:39:59 PDT 1988 + * modified: DSM Mon Jul 3 14:17:50 PDT 1989 + * - added 'storageProblem' return code + * - fixed typos + */ + +#ifndef INCLUDED_VCL_GENERIC_FONTMANAGER_PARSEAFM_HXX +#define INCLUDED_VCL_GENERIC_FONTMANAGER_PARSEAFM_HXX + + +namespace psp { + +/* your basic constants */ +#define EOL '\n' /* end-of-line indicator */ +#define MAX_NAME 4096 /* max length for identifiers */ +#define FLAGS int + +/* Flags that can be AND'ed together to specify exactly what + * information from the AFM file should be saved. + */ +#define P_G 0x01 /* 0000 0001 */ /* Global Font Info */ +#define P_W 0x02 /* 0000 0010 */ /* Character Widths ONLY */ +#define P_M 0x06 /* 0000 0110 */ /* All Char Metric Info */ +#define P_P 0x08 /* 0000 1000 */ /* Pair Kerning Info */ +#define P_T 0x10 /* 0001 0000 */ /* Track Kerning Info */ +#define P_C 0x20 /* 0010 0000 */ /* Composite Char Info */ + +/* Commonly used flags + */ +#define P_GW (P_G | P_W) +#define P_GM (P_G | P_M) +#define P_GMP (P_G | P_M | P_P) +#define P_GMK (P_G | P_M | P_P | P_T) +#define P_ALL (P_G | P_M | P_P | P_T | P_C) + +/* Possible return codes from the parseFile procedure. + * + * ok means there were no problems parsing the file. + * + * parseError means that there was some kind of parsing error, but the + * parser went on. This could include problems like the count for any given + * section does not add up to how many entries there actually were, or + * there was a key that was not recognized. The return record may contain + * valid data or it may not. + * + * earlyEOF means that an End of File was encountered before expected. This + * may mean that the AFM file had been truncated, or improperly formed. + * + * storageProblem means that there were problems allocating storage for + * the data structures that would have contained the AFM data. + */ + +enum afmError { ok = 0, parseError = -1, earlyEOF = -2, storageProblem = -3 }; + +/************************* TYPES *********************************/ +/* Below are all of the data structure definitions. These structures + * try to map as closely as possible to grouping and naming of data + * in the AFM Files. + */ + +/* Bounding box definition. Used for the Font BBox as well as the + * Character BBox. + */ +typedef struct +{ + int llx; /* lower left x-position */ + int lly; /* lower left y-position */ + int urx; /* upper right x-position */ + int ury; /* upper right y-position */ +} BBox; + +/* Global Font information. + * The key that each field is associated with is in comments. For an + * explanation about each key and its value please refer to the AFM + * documentation (full title & version given above). + */ +typedef struct +{ + char *afmVersion; /* key: StartFontMetrics */ + char *fontName; /* key: FontName */ + char *fullName; /* key: FullName */ + char *familyName; /* key: FamilyName */ + char *weight; /* key: Weight */ + float italicAngle; /* key: ItalicAngle */ + bool isFixedPitch; /* key: IsFixedPitch */ + BBox fontBBox; /* key: FontBBox */ + int underlinePosition; /* key: UnderlinePosition */ + int underlineThickness; /* key: UnderlineThickness */ + char *version; /* key: Version */ + char *notice; /* key: Notice */ + char *encodingScheme; /* key: EncodingScheme */ + int capHeight; /* key: CapHeight */ + int xHeight; /* key: XHeight */ + int ascender; /* key: Ascender */ + int descender; /* key: Descender */ + int charwidth; /* key: CharWidth */ +} GlobalFontInfo; + +/* Ligature definition is a linked list since any character can have + * any number of ligatures. + */ +typedef struct _t_ligature +{ + char *succ, *lig; + struct _t_ligature *next; +} Ligature; + +/* Character Metric Information. This structure is used only if ALL + * character metric information is requested. If only the character + * widths is requested, then only an array of the character x-widths + * is returned. + * + * The key that each field is associated with is in comments. For an + * explanation about each key and its value please refer to the + * Character Metrics section of the AFM documentation (full title + * & version given above). + */ +typedef struct +{ + int code, /* key: C */ + wx, /* key: WX */ + wy; /* together wx and wy are associated with key: W */ + char *name; /* key: N */ + BBox charBBox; /* key: B */ + Ligature *ligs; /* key: L (linked list; not a fixed number of Ls */ +} CharMetricInfo; + +/* Track kerning data structure. + * The fields of this record are the five values associated with every + * TrackKern entry. + * + * For an explanation about each value please refer to the + * Track Kerning section of the AFM documentation (full title + * & version given above). + */ +typedef struct +{ + int degree; + float minPtSize, + minKernAmt, + maxPtSize, + maxKernAmt; +} TrackKernData; + +/* Pair Kerning data structure. + * The fields of this record are the four values associated with every + * KP entry. For KPX entries, the yamt will be zero. + * + * For an explanation about each value please refer to the + * Pair Kerning section of the AFM documentation (full title + * & version given above). + */ +typedef struct +{ + char *name1; + char *name2; + int xamt, + yamt; +} PairKernData; + +/* PCC is a piece of a composite character. This is a sub structure of a + * compCharData described below. + * These fields will be filled in with the values from the key PCC. + * + * For an explanation about each key and its value please refer to the + * Composite Character section of the AFM documentation (full title + * & version given above). + */ +typedef struct +{ + char *pccName; + int deltax, + deltay; +} Pcc; + +/* Composite Character Information data structure. + * The fields ccName and numOfPieces are filled with the values associated + * with the key CC. The field pieces points to an array (size = numOfPieces) + * of information about each of the parts of the composite character. That + * array is filled in with the values from the key PCC. + * + * For an explanation about each key and its value please refer to the + * Composite Character section of the AFM documentation (full title + * & version given above). + */ +typedef struct +{ + char *ccName; + int numOfPieces; + Pcc *pieces; +} CompCharData; + +/* FontInfo + * Record type containing pointers to all of the other data + * structures containing information about a font. + * A record of this type is filled with data by the + * parseFile function. + */ +typedef struct +{ + GlobalFontInfo *gfi; /* ptr to a GlobalFontInfo record */ + int *cwi; /* ptr to 256 element array of just char widths */ + int numOfChars; /* number of entries in char metrics array */ + CharMetricInfo *cmi; /* ptr to char metrics array */ + int numOfTracks; /* number to entries in track kerning array */ + TrackKernData *tkd; /* ptr to track kerning array */ + int numOfPairs; /* number to entries in pair kerning array */ + PairKernData *pkd; /* ptr to pair kerning array */ + int numOfComps; /* number to entries in comp char array */ + CompCharData *ccd; /* ptr to comp char array */ +} FontInfo; + +/************************* PROCEDURES ****************************/ + +/* Call this procedure to do the grunt work of parsing an AFM file. + * + * "fp" should be a valid file pointer to an AFM file. + * + * "fi" is a pointer to a pointer to a FontInfo record structure + * (defined above). Storage for the FontInfo structure will be + * allocated in parseFile and the structure will be filled in + * with the requested data from the AFM File. + * + * "flags" is a mask with bits set representing what data should + * be saved. Defined above are valid flags that can be used to set + * the mask, as well as a few commonly used masks. + * + * The possible return codes from parseFile are defined above. + */ + +int parseFile( const char* pFilename, FontInfo **fi, FLAGS flags ); +void freeFontInfo(FontInfo *fi); + +} // namespace + +#endif // INCLUDED_VCL_GENERIC_FONTMANAGER_PARSEAFM_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |