You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PFMFile.java 15KB

Improved font auto-detection and handling of AWT-supplied fonts in order to achieve better results when using multiple output formats. Whenever possible, the font names appearing in the operating system can also be used in XSL-FO. Better distinction between Font Family Name ("Arial"), Full Font Name ("Arial Bold") and PostScript Name ("Arial-BoldMT"). This allows a better generation of FontTriplets. The same is done for AWT fonts where I have switch from font-family detection to enumerating all java.awt.Font instances so I can extract Family Name, Full Name and PostScript Name. FontInfoFinder and AWT's FontSetup are synchronized as well as possible at this time. Register "extra-bold" (weight 800) and "light" (weight 200) in triplets when detected. Tweaked FontInfo.fontLookup() for better fallback behaviour. This approach is rapidly nearing its flexibility limits. We should rethink the FontTriplet structure. Fixed font-autodetection so fonts with uppercase extensions are detected, too. Made the way TrueType fonts are embedded in PDF compliant to the specification so viewers correctly identify subset fonts. The name prefix in MultiByteFont was incorrect. Support the detection of the special Type 1 Symbol font. Symbol used to be detected with "ExpertSubsetEncoding" instead of "SymbolEncoding". Type1FontLoader tries to construct a "full name" from the PostScript name. This is a temporary hack until we have a PFB or PFA parser. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@593189 13f79535-47bb-0310-9956-ffa450edef68
16 years ago
Improved font auto-detection and handling of AWT-supplied fonts in order to achieve better results when using multiple output formats. Whenever possible, the font names appearing in the operating system can also be used in XSL-FO. Better distinction between Font Family Name ("Arial"), Full Font Name ("Arial Bold") and PostScript Name ("Arial-BoldMT"). This allows a better generation of FontTriplets. The same is done for AWT fonts where I have switch from font-family detection to enumerating all java.awt.Font instances so I can extract Family Name, Full Name and PostScript Name. FontInfoFinder and AWT's FontSetup are synchronized as well as possible at this time. Register "extra-bold" (weight 800) and "light" (weight 200) in triplets when detected. Tweaked FontInfo.fontLookup() for better fallback behaviour. This approach is rapidly nearing its flexibility limits. We should rethink the FontTriplet structure. Fixed font-autodetection so fonts with uppercase extensions are detected, too. Made the way TrueType fonts are embedded in PDF compliant to the specification so viewers correctly identify subset fonts. The name prefix in MultiByteFont was incorrect. Support the detection of the special Type 1 Symbol font. Symbol used to be detected with "ExpertSubsetEncoding" instead of "SymbolEncoding". Type1FontLoader tries to construct a "full name" from the PostScript name. This is a temporary hack until we have a PFB or PFA parser. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@593189 13f79535-47bb-0310-9956-ffa450edef68
16 years ago
Improved font auto-detection and handling of AWT-supplied fonts in order to achieve better results when using multiple output formats. Whenever possible, the font names appearing in the operating system can also be used in XSL-FO. Better distinction between Font Family Name ("Arial"), Full Font Name ("Arial Bold") and PostScript Name ("Arial-BoldMT"). This allows a better generation of FontTriplets. The same is done for AWT fonts where I have switch from font-family detection to enumerating all java.awt.Font instances so I can extract Family Name, Full Name and PostScript Name. FontInfoFinder and AWT's FontSetup are synchronized as well as possible at this time. Register "extra-bold" (weight 800) and "light" (weight 200) in triplets when detected. Tweaked FontInfo.fontLookup() for better fallback behaviour. This approach is rapidly nearing its flexibility limits. We should rethink the FontTriplet structure. Fixed font-autodetection so fonts with uppercase extensions are detected, too. Made the way TrueType fonts are embedded in PDF compliant to the specification so viewers correctly identify subset fonts. The name prefix in MultiByteFont was incorrect. Support the detection of the special Type 1 Symbol font. Symbol used to be detected with "ExpertSubsetEncoding" instead of "SymbolEncoding". Type1FontLoader tries to construct a "full name" from the PostScript name. This is a temporary hack until we have a PFB or PFA parser. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@593189 13f79535-47bb-0310-9956-ffa450edef68
16 years ago
Improved font auto-detection and handling of AWT-supplied fonts in order to achieve better results when using multiple output formats. Whenever possible, the font names appearing in the operating system can also be used in XSL-FO. Better distinction between Font Family Name ("Arial"), Full Font Name ("Arial Bold") and PostScript Name ("Arial-BoldMT"). This allows a better generation of FontTriplets. The same is done for AWT fonts where I have switch from font-family detection to enumerating all java.awt.Font instances so I can extract Family Name, Full Name and PostScript Name. FontInfoFinder and AWT's FontSetup are synchronized as well as possible at this time. Register "extra-bold" (weight 800) and "light" (weight 200) in triplets when detected. Tweaked FontInfo.fontLookup() for better fallback behaviour. This approach is rapidly nearing its flexibility limits. We should rethink the FontTriplet structure. Fixed font-autodetection so fonts with uppercase extensions are detected, too. Made the way TrueType fonts are embedded in PDF compliant to the specification so viewers correctly identify subset fonts. The name prefix in MultiByteFont was incorrect. Support the detection of the special Type 1 Symbol font. Symbol used to be detected with "ExpertSubsetEncoding" instead of "SymbolEncoding". Type1FontLoader tries to construct a "full name" from the PostScript name. This is a temporary hack until we have a PFB or PFA parser. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@593189 13f79535-47bb-0310-9956-ffa450edef68
16 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.fonts.type1;
  19. // Java
  20. import java.io.ByteArrayInputStream;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.util.Map;
  24. import org.apache.commons.io.IOUtils;
  25. import org.apache.commons.logging.Log;
  26. import org.apache.commons.logging.LogFactory;
  27. import org.apache.xmlgraphics.fonts.Glyphs;
  28. /**
  29. * This class represents a PFM file (or parts of it) as a Java object.
  30. */
  31. public class PFMFile {
  32. // Header stuff
  33. private String windowsName;
  34. private String postScriptName;
  35. private short dfItalic;
  36. private int dfWeight;
  37. private short dfCharSet;
  38. private short dfPitchAndFamily;
  39. private int dfAvgWidth;
  40. private int dfMaxWidth;
  41. private int dfMinWidth;
  42. private short dfFirstChar;
  43. private short dfLastChar;
  44. // Extension stuff
  45. // ---
  46. // Extend Text Metrics
  47. private int etmCapHeight;
  48. private int etmXHeight;
  49. private int etmLowerCaseAscent;
  50. private int etmLowerCaseDescent;
  51. // Extent table
  52. private int[] extentTable;
  53. private Map kerningTab = new java.util.HashMap();
  54. /**
  55. * logging instance
  56. */
  57. protected Log log = LogFactory.getLog(PFMFile.class);
  58. /**
  59. * Parses a PFM file
  60. *
  61. * @param inStream The stream from which to read the PFM file.
  62. * @throws IOException In case of an I/O problem
  63. */
  64. public void load(InputStream inStream) throws IOException {
  65. byte[] pfmBytes = IOUtils.toByteArray(inStream);
  66. InputStream bufin = inStream;
  67. bufin = new ByteArrayInputStream(pfmBytes);
  68. PFMInputStream in = new PFMInputStream(bufin);
  69. bufin.mark(512);
  70. short sh1 = in.readByte();
  71. short sh2 = in.readByte();
  72. if (sh1 == 128 && sh2 == 1) {
  73. //Found the first section header of a PFB file!
  74. throw new IOException("Cannot parse PFM file. You probably specified the PFB file"
  75. + " of a Type 1 font as parameter instead of the PFM.");
  76. }
  77. bufin.reset();
  78. byte[] b = new byte[16];
  79. bufin.read(b);
  80. if (new String(b, "US-ASCII").equalsIgnoreCase("StartFontMetrics")) {
  81. //Found the header of a AFM file!
  82. throw new IOException("Cannot parse PFM file. You probably specified the AFM file"
  83. + " of a Type 1 font as parameter instead of the PFM.");
  84. }
  85. bufin.reset();
  86. final int version = in.readShort();
  87. if (version != 256) {
  88. log.warn("PFM version expected to be '256' but got '" + version + "'."
  89. + " Please make sure you specify the PFM as parameter"
  90. + " and not the PFB or the AFM.");
  91. }
  92. //final long filesize = in.readInt();
  93. bufin.reset();
  94. loadHeader(in);
  95. loadExtension(in);
  96. }
  97. /**
  98. * Parses the header of the PFM file.
  99. *
  100. * @param inStream The stream from which to read the PFM file.
  101. * @throws IOException In case of an I/O problem
  102. */
  103. private void loadHeader(PFMInputStream inStream) throws IOException {
  104. inStream.skip(80);
  105. dfItalic = inStream.readByte();
  106. inStream.skip(2);
  107. dfWeight = inStream.readShort();
  108. dfCharSet = inStream.readByte();
  109. inStream.skip(4);
  110. dfPitchAndFamily = inStream.readByte();
  111. dfAvgWidth = inStream.readShort();
  112. dfMaxWidth = inStream.readShort();
  113. dfFirstChar = inStream.readByte();
  114. dfLastChar = inStream.readByte();
  115. inStream.skip(8);
  116. long faceOffset = inStream.readInt();
  117. inStream.reset();
  118. inStream.skip(faceOffset);
  119. windowsName = inStream.readString();
  120. inStream.reset();
  121. inStream.skip(117);
  122. }
  123. /**
  124. * Parses the extension part of the PFM file.
  125. *
  126. * @param inStream The stream from which to read the PFM file.
  127. */
  128. private void loadExtension(PFMInputStream inStream) throws IOException {
  129. final int size = inStream.readShort();
  130. if (size != 30) {
  131. log.warn("Size of extension block was expected to be "
  132. + "30 bytes, but was " + size + " bytes.");
  133. }
  134. final long extMetricsOffset = inStream.readInt();
  135. final long extentTableOffset = inStream.readInt();
  136. inStream.skip(4); //Skip dfOriginTable
  137. final long kernPairOffset = inStream.readInt();
  138. inStream.skip(4); //Skip dfTrackKernTable
  139. long driverInfoOffset = inStream.readInt();
  140. if (kernPairOffset > 0) {
  141. inStream.reset();
  142. inStream.skip(kernPairOffset);
  143. loadKernPairs(inStream);
  144. }
  145. inStream.reset();
  146. inStream.skip(driverInfoOffset);
  147. postScriptName = inStream.readString();
  148. if (extMetricsOffset != 0) {
  149. inStream.reset();
  150. inStream.skip(extMetricsOffset);
  151. loadExtMetrics(inStream);
  152. }
  153. if (extentTableOffset != 0) {
  154. inStream.reset();
  155. inStream.skip(extentTableOffset);
  156. loadExtentTable(inStream);
  157. }
  158. }
  159. /**
  160. * Parses the kernPairs part of the pfm file
  161. *
  162. * @param inStream The stream from which to read the PFM file.
  163. */
  164. private void loadKernPairs(PFMInputStream inStream) throws IOException {
  165. int i = inStream.readShort();
  166. if (log.isTraceEnabled()) {
  167. log.trace(i + " kerning pairs");
  168. }
  169. while (i > 0) {
  170. int g1 = (int)inStream.readByte();
  171. i--;
  172. int g2 = (int)inStream.readByte();
  173. int adj = inStream.readShort();
  174. if (adj > 0x8000) {
  175. adj = -(0x10000 - adj);
  176. }
  177. if (log.isTraceEnabled()) {
  178. log.trace("Char no: (" + g1 + ", " + g2 + ") kern: " + adj);
  179. final String glyph1 = Glyphs.TEX8R_GLYPH_NAMES[g1];
  180. final String glyph2 = Glyphs.TEX8R_GLYPH_NAMES[g2];
  181. log.trace("glyphs: " + glyph1 + ", " + glyph2);
  182. }
  183. Map adjTab = (Map)kerningTab.get(new Integer(g1));
  184. if (adjTab == null) {
  185. adjTab = new java.util.HashMap();
  186. }
  187. adjTab.put(new Integer(g2), new Integer(adj));
  188. kerningTab.put(new Integer(g1), adjTab);
  189. }
  190. }
  191. /**
  192. * Parses the extended metrics part of the PFM file.
  193. *
  194. * @param inStream The stream from which to read the PFM file.
  195. */
  196. private void loadExtMetrics(PFMInputStream inStream) throws IOException {
  197. final int size = inStream.readShort();
  198. if (size != 52) {
  199. log.warn("Size of extension block was expected to be "
  200. + "52 bytes, but was " + size + " bytes.");
  201. }
  202. inStream.skip(12); //Skip etmPointSize, etmOrientation, etmMasterHeight,
  203. //etmMinScale, etmMaxScale, emtMasterUnits
  204. etmCapHeight = inStream.readShort();
  205. etmXHeight = inStream.readShort();
  206. etmLowerCaseAscent = inStream.readShort();
  207. etmLowerCaseDescent = -(inStream.readShort());
  208. //Ignore the rest of the values
  209. }
  210. /**
  211. * Parses the extent table of the PFM file.
  212. *
  213. * @param inStream The stream from which to read the PFM file.
  214. */
  215. private void loadExtentTable(PFMInputStream inStream) throws IOException {
  216. extentTable = new int[dfLastChar - dfFirstChar + 1];
  217. dfMinWidth = dfMaxWidth;
  218. for (short i = dfFirstChar; i <= dfLastChar; i++) {
  219. extentTable[i - dfFirstChar] = inStream.readShort();
  220. if (extentTable[i - dfFirstChar] < dfMinWidth) {
  221. dfMinWidth = extentTable[i - dfFirstChar];
  222. }
  223. }
  224. }
  225. /**
  226. * Returns the Windows name of the font.
  227. *
  228. * @return The Windows name.
  229. */
  230. public String getWindowsName() {
  231. return windowsName;
  232. }
  233. /**
  234. * Return the kerning table. The kerning table is a Map with
  235. * strings with glyphnames as keys, containing Maps as value.
  236. * The value map contains a glyph name string key and an Integer value
  237. *
  238. * @return A Map containing the kerning table
  239. */
  240. public Map getKerning() {
  241. return kerningTab;
  242. }
  243. /**
  244. * Returns the Postscript name of the font.
  245. *
  246. * @return The Postscript name.
  247. */
  248. public String getPostScriptName() {
  249. return postScriptName;
  250. }
  251. /**
  252. * Returns the charset used for the font.
  253. *
  254. * @return The charset (0=WinAnsi).
  255. */
  256. public short getCharSet() {
  257. return dfCharSet;
  258. }
  259. /**
  260. * Returns the charset of the font as a string.
  261. *
  262. * @return The name of the charset.
  263. */
  264. public String getCharSetName() {
  265. //TODO Had to remove the detection for Expert(Subset) encoding. The PFM is not suitable
  266. //for detecting these character sets. We have to parse the AFM for that.
  267. switch (dfCharSet) {
  268. case 0:
  269. return "WinAnsi"; // AKA ISOAdobe
  270. case 2:
  271. if ("Symbol".equals(getPostScriptName())) {
  272. return "Symbol";
  273. }
  274. break;
  275. case 128:
  276. return "Shift-JIS (Japanese)";
  277. default:
  278. log.warn("Unknown charset detected (" + dfCharSet
  279. + ", 0x" + Integer.toHexString(dfCharSet)
  280. + "). Trying fallback to WinAnsi.");
  281. }
  282. return "WinAnsi";
  283. }
  284. /**
  285. * Returns the number of the character that defines
  286. * the first entry in the widths list.
  287. *
  288. * @return The number of the first character.
  289. */
  290. public short getFirstChar() {
  291. return dfFirstChar;
  292. }
  293. /**
  294. * Returns the number of the character that defines
  295. * the last entry in the widths list.
  296. *
  297. * @return The number of the last character.
  298. */
  299. public short getLastChar() {
  300. return dfLastChar;
  301. }
  302. /**
  303. * Returns the CapHeight parameter for the font (height of uppercase H).
  304. *
  305. * @return The CapHeight parameter.
  306. */
  307. public int getCapHeight() {
  308. return etmCapHeight;
  309. }
  310. /**
  311. * Returns the XHeight parameter for the font (height of lowercase x).
  312. *
  313. * @return The CapHeight parameter.
  314. */
  315. public int getXHeight() {
  316. return etmXHeight;
  317. }
  318. /**
  319. * Returns the LowerCaseAscent parameter for the font (height of lowercase d).
  320. *
  321. * @return The LowerCaseAscent parameter.
  322. */
  323. public int getLowerCaseAscent() {
  324. return etmLowerCaseAscent;
  325. }
  326. /**
  327. * Returns the LowerCaseDescent parameter for the font (height of lowercase p).
  328. *
  329. * @return The LowerCaseDescent parameter.
  330. */
  331. public int getLowerCaseDescent() {
  332. return etmLowerCaseDescent;
  333. }
  334. /**
  335. * Tells whether the font has proportional character spacing.
  336. *
  337. * @return ex. true for Times, false for Courier.
  338. */
  339. public boolean getIsProportional() {
  340. return ((dfPitchAndFamily & 1) == 1);
  341. }
  342. /**
  343. * Returns the bounding box for the font.
  344. * Note: this value is just an approximation,
  345. * it does not really exist in the PFM file.
  346. *
  347. * @return The calculated Font BBox.
  348. */
  349. public int[] getFontBBox() {
  350. int[] bbox = new int[4];
  351. // Just guessing....
  352. if (!getIsProportional() && (dfAvgWidth == dfMaxWidth)) {
  353. bbox[0] = -20;
  354. } else {
  355. bbox[0] = -100;
  356. }
  357. bbox[1] = getLowerCaseDescent() - 5;
  358. bbox[2] = dfMaxWidth + 10;
  359. bbox[3] = getLowerCaseAscent() + 5;
  360. return bbox;
  361. }
  362. /**
  363. * Indicates whether the font is non-symbolic (Font uses the Adobe standard Latin character
  364. * set or a subset of it).
  365. * @return true if the font is non-symbolic
  366. */
  367. public boolean isNonSymbolic() {
  368. return (dfCharSet != 2); //!= Symbol fonts
  369. }
  370. /**
  371. * Returns the characteristics flags for the font as
  372. * needed for a PDF font descriptor (See PDF specs).
  373. *
  374. * @return The characteristics flags.
  375. */
  376. public int getFlags() {
  377. int flags = 0;
  378. if (!getIsProportional()) {
  379. flags |= 1; //bit 1: FixedPitch
  380. }
  381. if (isNonSymbolic()) {
  382. flags |= 32; //bit 6: Nonsymbolic
  383. } else {
  384. flags |= 4; //bit 3: Symbolic
  385. }
  386. //int serif = dfPitchAndFamily & 0xFFFE;
  387. if ((dfPitchAndFamily & 16) != 0) {
  388. flags |= 2; //bit 2: Serif
  389. }
  390. if ((dfPitchAndFamily & 64) != 0) {
  391. flags |= 8; //bit 4: Script
  392. }
  393. if (dfItalic != 0) {
  394. flags |= 64; //bit 7: Italic
  395. }
  396. return flags;
  397. }
  398. /**
  399. * Returns the width of the dominant vertical stems of the font.
  400. * Note: this value is just an approximation,
  401. * it does not really exist in the PFM file.
  402. *
  403. * @return The vertical stem width.
  404. */
  405. public int getStemV() {
  406. // Just guessing....
  407. if (dfItalic != 0) {
  408. return (int)Math.round(dfMinWidth * 0.25);
  409. } else {
  410. return (int)Math.round(dfMinWidth * 0.6);
  411. }
  412. }
  413. /**
  414. * Returns the italic angle of the font.
  415. * Note: this value is just an approximation,
  416. * it does not really exist in the PFM file.
  417. *
  418. * @return The italic angle.
  419. */
  420. public int getItalicAngle() {
  421. if (dfItalic != 0) {
  422. return -16; // Just guessing....
  423. } else {
  424. return 0;
  425. }
  426. }
  427. /**
  428. * Returns the width of a character
  429. *
  430. * @param which The number of the character for which the width is requested.
  431. * @return The width of a character.
  432. */
  433. public int getCharWidth(short which) {
  434. if (extentTable != null) {
  435. return extentTable[which - dfFirstChar];
  436. } else {
  437. //Fixed-width font (PFM may have no extent table)
  438. //we'll just use the average width
  439. return this.dfAvgWidth;
  440. }
  441. }
  442. }