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 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  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.HashMap;
  24. import java.util.Map;
  25. import org.apache.commons.io.IOUtils;
  26. import org.apache.commons.logging.Log;
  27. import org.apache.commons.logging.LogFactory;
  28. import org.apache.xmlgraphics.fonts.Glyphs;
  29. /**
  30. * This class represents a PFM file (or parts of it) as a Java object.
  31. */
  32. public class PFMFile {
  33. // Header stuff
  34. private String windowsName;
  35. private String postscriptName;
  36. private short dfItalic;
  37. //private int dfWeight;
  38. private short dfCharSet;
  39. private short dfPitchAndFamily;
  40. private int dfAvgWidth;
  41. private int dfMaxWidth;
  42. private int dfMinWidth;
  43. private short dfFirstChar;
  44. private short dfLastChar;
  45. // Extension stuff
  46. // ---
  47. // Extend Text Metrics
  48. private int etmCapHeight;
  49. private int etmXHeight;
  50. private int etmLowerCaseAscent;
  51. private int etmLowerCaseDescent;
  52. // Extent table
  53. private int[] extentTable;
  54. private Map<Integer, Map<Integer, Integer>> kerningTab = new HashMap<Integer, Map<Integer, Integer>>();
  55. /**
  56. * logging instance
  57. */
  58. protected Log log = LogFactory.getLog(PFMFile.class);
  59. /**
  60. * Parses a PFM file
  61. *
  62. * @param inStream The stream from which to read the PFM file.
  63. * @throws IOException In case of an I/O problem
  64. */
  65. public void load(InputStream inStream) throws IOException {
  66. byte[] pfmBytes = IOUtils.toByteArray(inStream);
  67. InputStream bufin = inStream;
  68. bufin = new ByteArrayInputStream(pfmBytes);
  69. PFMInputStream in = new PFMInputStream(bufin);
  70. bufin.mark(512);
  71. short sh1 = in.readByte();
  72. short sh2 = in.readByte();
  73. if (sh1 == 128 && sh2 == 1) {
  74. //Found the first section header of a PFB file!
  75. IOUtils.closeQuietly(in);
  76. throw new IOException("Cannot parse PFM file. You probably specified the PFB file"
  77. + " of a Type 1 font as parameter instead of the PFM.");
  78. }
  79. bufin.reset();
  80. byte[] b = new byte[16];
  81. if ((bufin.read(b) == b.length) && new String(b, "US-ASCII").equalsIgnoreCase("StartFontMetrics")) {
  82. //Found the header of a AFM file!
  83. IOUtils.closeQuietly(in);
  84. throw new IOException("Cannot parse PFM file. You probably specified the AFM file"
  85. + " of a Type 1 font as parameter instead of the PFM.");
  86. }
  87. bufin.reset();
  88. final int version = in.readShort();
  89. if (version != 256) {
  90. log.warn("PFM version expected to be '256' but got '" + version + "'."
  91. + " Please make sure you specify the PFM as parameter"
  92. + " and not the PFB or the AFM.");
  93. }
  94. //final long filesize = in.readInt();
  95. bufin.reset();
  96. loadHeader(in);
  97. loadExtension(in);
  98. }
  99. /**
  100. * Parses the header of the PFM file.
  101. *
  102. * @param inStream The stream from which to read the PFM file.
  103. * @throws IOException In case of an I/O problem
  104. */
  105. private void loadHeader(PFMInputStream inStream) throws IOException {
  106. if (inStream.skip(80) != 80) {
  107. throw new IOException("premature EOF when skipping 80 bytes");
  108. }
  109. dfItalic = inStream.readByte();
  110. if (inStream.skip(2) != 2) {
  111. throw new IOException("premature EOF when skipping 2 bytes");
  112. }
  113. inStream.readShort(); // dfWeight =
  114. dfCharSet = inStream.readByte();
  115. if (inStream.skip(4) != 4) {
  116. throw new IOException("premature EOF when skipping 4 bytes");
  117. }
  118. dfPitchAndFamily = inStream.readByte();
  119. dfAvgWidth = inStream.readShort();
  120. dfMaxWidth = inStream.readShort();
  121. dfFirstChar = inStream.readByte();
  122. dfLastChar = inStream.readByte();
  123. if (inStream.skip(8) != 8) {
  124. throw new IOException("premature EOF when skipping 8 bytes");
  125. }
  126. long faceOffset = inStream.readInt();
  127. inStream.reset();
  128. if (inStream.skip(faceOffset) != faceOffset) {
  129. throw new IOException("premature EOF when skipping faceOffset bytes");
  130. }
  131. windowsName = inStream.readString();
  132. inStream.reset();
  133. if (inStream.skip(117) != 117) {
  134. throw new IOException("premature EOF when skipping 117 bytes");
  135. }
  136. }
  137. /**
  138. * Parses the extension part of the PFM file.
  139. *
  140. * @param inStream The stream from which to read the PFM file.
  141. */
  142. private void loadExtension(PFMInputStream inStream) throws IOException {
  143. final int size = inStream.readShort();
  144. if (size != 30) {
  145. log.warn("Size of extension block was expected to be "
  146. + "30 bytes, but was " + size + " bytes.");
  147. }
  148. final long extMetricsOffset = inStream.readInt();
  149. final long extentTableOffset = inStream.readInt();
  150. if (inStream.skip(4) != 4) { //Skip dfOriginTable
  151. throw new IOException("premature EOF when skipping dfOriginTable bytes");
  152. }
  153. final long kernPairOffset = inStream.readInt();
  154. if (inStream.skip(4) != 4) { //Skip dfTrackKernTable
  155. throw new IOException("premature EOF when skipping dfTrackKernTable bytes");
  156. }
  157. long driverInfoOffset = inStream.readInt();
  158. if (kernPairOffset > 0) {
  159. inStream.reset();
  160. if (inStream.skip(kernPairOffset) != kernPairOffset) {
  161. throw new IOException("premature EOF when skipping kernPairOffset bytes");
  162. }
  163. loadKernPairs(inStream);
  164. }
  165. inStream.reset();
  166. if (inStream.skip(driverInfoOffset) != driverInfoOffset) {
  167. throw new IOException("premature EOF when skipping driverInfoOffset bytes");
  168. }
  169. postscriptName = inStream.readString();
  170. if (extMetricsOffset != 0) {
  171. inStream.reset();
  172. if (inStream.skip(extMetricsOffset) != extMetricsOffset) {
  173. throw new IOException("premature EOF when skipping extMetricsOffset bytes");
  174. }
  175. loadExtMetrics(inStream);
  176. }
  177. if (extentTableOffset != 0) {
  178. inStream.reset();
  179. if (inStream.skip(extentTableOffset) != extentTableOffset) {
  180. throw new IOException("premature EOF when skipping extentTableOffset bytes");
  181. }
  182. loadExtentTable(inStream);
  183. }
  184. }
  185. /**
  186. * Parses the kernPairs part of the pfm file
  187. *
  188. * @param inStream The stream from which to read the PFM file.
  189. */
  190. private void loadKernPairs(PFMInputStream inStream) throws IOException {
  191. int i = inStream.readShort();
  192. if (log.isTraceEnabled()) {
  193. log.trace(i + " kerning pairs");
  194. }
  195. while (i > 0) {
  196. int g1 = (int) inStream.readByte();
  197. i--;
  198. int g2 = (int) inStream.readByte();
  199. int adj = inStream.readShort();
  200. if (adj > 0x8000) {
  201. adj = -(0x10000 - adj);
  202. }
  203. if (log.isTraceEnabled()) {
  204. log.trace("Char no: (" + g1 + ", " + g2 + ") kern: " + adj);
  205. final String glyph1 = Glyphs.TEX8R_GLYPH_NAMES[g1];
  206. final String glyph2 = Glyphs.TEX8R_GLYPH_NAMES[g2];
  207. log.trace("glyphs: " + glyph1 + ", " + glyph2);
  208. }
  209. Map<Integer, Integer> adjTab = kerningTab.get(g1);
  210. if (adjTab == null) {
  211. adjTab = new HashMap<Integer, Integer>();
  212. }
  213. adjTab.put(g2, adj);
  214. kerningTab.put(g1, adjTab);
  215. }
  216. }
  217. /**
  218. * Parses the extended metrics part of the PFM file.
  219. *
  220. * @param inStream The stream from which to read the PFM file.
  221. */
  222. private void loadExtMetrics(PFMInputStream inStream) throws IOException {
  223. final int size = inStream.readShort();
  224. if (size != 52) {
  225. log.warn("Size of extension block was expected to be "
  226. + "52 bytes, but was " + size + " bytes.");
  227. }
  228. if (inStream.skip(12) != 12) { //Skip etmPointSize, etmOrientation, etmMasterHeight,
  229. //etmMinScale, etmMaxScale, emtMasterUnits
  230. throw new IOException("premature EOF when skipping etmPointSize, ... bytes");
  231. }
  232. etmCapHeight = inStream.readShort();
  233. etmXHeight = inStream.readShort();
  234. etmLowerCaseAscent = inStream.readShort();
  235. etmLowerCaseDescent = -(inStream.readShort());
  236. //Ignore the rest of the values
  237. }
  238. /**
  239. * Parses the extent table of the PFM file.
  240. *
  241. * @param inStream The stream from which to read the PFM file.
  242. */
  243. private void loadExtentTable(PFMInputStream inStream) throws IOException {
  244. extentTable = new int[dfLastChar - dfFirstChar + 1];
  245. dfMinWidth = dfMaxWidth;
  246. for (short i = dfFirstChar; i <= dfLastChar; i++) {
  247. extentTable[i - dfFirstChar] = inStream.readShort();
  248. if (extentTable[i - dfFirstChar] < dfMinWidth) {
  249. dfMinWidth = extentTable[i - dfFirstChar];
  250. }
  251. }
  252. }
  253. /**
  254. * Returns the Windows name of the font.
  255. *
  256. * @return The Windows name.
  257. */
  258. public String getWindowsName() {
  259. return windowsName;
  260. }
  261. /**
  262. * Return the kerning table. The kerning table is a Map with
  263. * strings with glyphnames as keys, containing Maps as value.
  264. * The value map contains a glyph name string key and an Integer value
  265. *
  266. * @return A Map containing the kerning table
  267. */
  268. public Map<Integer, Map<Integer, Integer>> getKerning() {
  269. return kerningTab;
  270. }
  271. /**
  272. * Returns the Postscript name of the font.
  273. *
  274. * @return The Postscript name.
  275. */
  276. public String getPostscriptName() {
  277. return postscriptName;
  278. }
  279. /**
  280. * Returns the charset used for the font.
  281. *
  282. * @return The charset (0=WinAnsi).
  283. */
  284. public short getCharSet() {
  285. return dfCharSet;
  286. }
  287. /**
  288. * Returns the charset of the font as a string.
  289. *
  290. * @return The name of the charset.
  291. */
  292. public String getCharSetName() {
  293. //TODO Had to remove the detection for Expert(Subset) encoding. The PFM is not suitable
  294. //for detecting these character sets. We have to parse the AFM for that.
  295. switch (dfCharSet) {
  296. case 0:
  297. return "WinAnsi"; // AKA ISOAdobe
  298. case 2:
  299. if ("Symbol".equals(getPostscriptName())) {
  300. return "Symbol";
  301. }
  302. break;
  303. case 128:
  304. return "Shift-JIS (Japanese)";
  305. default:
  306. log.warn("Unknown charset detected (" + dfCharSet
  307. + ", 0x" + Integer.toHexString(dfCharSet)
  308. + "). Trying fallback to WinAnsi.");
  309. }
  310. return "WinAnsi";
  311. }
  312. /**
  313. * Returns the number of the character that defines
  314. * the first entry in the widths list.
  315. *
  316. * @return The number of the first character.
  317. */
  318. public short getFirstChar() {
  319. return dfFirstChar;
  320. }
  321. /**
  322. * Returns the number of the character that defines
  323. * the last entry in the widths list.
  324. *
  325. * @return The number of the last character.
  326. */
  327. public short getLastChar() {
  328. return dfLastChar;
  329. }
  330. /**
  331. * Returns the CapHeight parameter for the font (height of uppercase H).
  332. *
  333. * @return The CapHeight parameter.
  334. */
  335. public int getCapHeight() {
  336. return etmCapHeight;
  337. }
  338. /**
  339. * Returns the XHeight parameter for the font (height of lowercase x).
  340. *
  341. * @return The CapHeight parameter.
  342. */
  343. public int getXHeight() {
  344. return etmXHeight;
  345. }
  346. /**
  347. * Returns the LowerCaseAscent parameter for the font (height of lowercase d).
  348. *
  349. * @return The LowerCaseAscent parameter.
  350. */
  351. public int getLowerCaseAscent() {
  352. return etmLowerCaseAscent;
  353. }
  354. /**
  355. * Returns the LowerCaseDescent parameter for the font (height of lowercase p).
  356. *
  357. * @return The LowerCaseDescent parameter.
  358. */
  359. public int getLowerCaseDescent() {
  360. return etmLowerCaseDescent;
  361. }
  362. /**
  363. * Tells whether the font has proportional character spacing.
  364. *
  365. * @return ex. true for Times, false for Courier.
  366. */
  367. public boolean getIsProportional() {
  368. return ((dfPitchAndFamily & 1) == 1);
  369. }
  370. /**
  371. * Returns the bounding box for the font.
  372. * Note: this value is just an approximation,
  373. * it does not really exist in the PFM file.
  374. *
  375. * @return The calculated Font BBox.
  376. */
  377. public int[] getFontBBox() {
  378. int[] bbox = new int[4];
  379. // Just guessing....
  380. if (!getIsProportional() && (dfAvgWidth == dfMaxWidth)) {
  381. bbox[0] = -20;
  382. } else {
  383. bbox[0] = -100;
  384. }
  385. bbox[1] = getLowerCaseDescent() - 5;
  386. bbox[2] = dfMaxWidth + 10;
  387. bbox[3] = getLowerCaseAscent() + 5;
  388. return bbox;
  389. }
  390. /**
  391. * Indicates whether the font is non-symbolic (Font uses the Adobe standard Latin character
  392. * set or a subset of it).
  393. * @return true if the font is non-symbolic
  394. */
  395. public boolean isNonSymbolic() {
  396. return (dfCharSet != 2); //!= Symbol fonts
  397. }
  398. /**
  399. * Returns the characteristics flags for the font as
  400. * needed for a PDF font descriptor (See PDF specs).
  401. *
  402. * @return The characteristics flags.
  403. */
  404. public int getFlags() {
  405. int flags = 0;
  406. if (!getIsProportional()) {
  407. flags |= 1; //bit 1: FixedPitch
  408. }
  409. if (isNonSymbolic()) {
  410. flags |= 32; //bit 6: Nonsymbolic
  411. } else {
  412. flags |= 4; //bit 3: Symbolic
  413. }
  414. //int serif = dfPitchAndFamily & 0xFFFE;
  415. if ((dfPitchAndFamily & 16) != 0) {
  416. flags |= 2; //bit 2: Serif
  417. }
  418. if ((dfPitchAndFamily & 64) != 0) {
  419. flags |= 8; //bit 4: Script
  420. }
  421. if (dfItalic != 0) {
  422. flags |= 64; //bit 7: Italic
  423. }
  424. return flags;
  425. }
  426. /**
  427. * Returns the width of the dominant vertical stems of the font.
  428. * Note: this value is just an approximation,
  429. * it does not really exist in the PFM file.
  430. *
  431. * @return The vertical stem width.
  432. */
  433. public int getStemV() {
  434. // Just guessing....
  435. if (dfItalic != 0) {
  436. return (int) Math.round(dfMinWidth * 0.25);
  437. } else {
  438. return (int) Math.round(dfMinWidth * 0.6);
  439. }
  440. }
  441. /**
  442. * Returns the italic angle of the font.
  443. * Note: this value is just an approximation,
  444. * it does not really exist in the PFM file.
  445. *
  446. * @return The italic angle.
  447. */
  448. public int getItalicAngle() {
  449. if (dfItalic != 0) {
  450. return -16; // Just guessing....
  451. } else {
  452. return 0;
  453. }
  454. }
  455. /**
  456. * Returns the width of a character
  457. *
  458. * @param which The number of the character for which the width is requested.
  459. * @return The width of a character.
  460. */
  461. public int getCharWidth(short which) {
  462. if (extentTable != null) {
  463. return extentTable[which - dfFirstChar];
  464. } else {
  465. //Fixed-width font (PFM may have no extent table)
  466. //we'll just use the average width
  467. return this.dfAvgWidth;
  468. }
  469. }
  470. }