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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  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.afp.fonts;
  19. import java.io.File;
  20. import java.io.FileNotFoundException;
  21. import java.io.FilenameFilter;
  22. import java.io.IOException;
  23. import java.io.InputStream;
  24. import java.net.MalformedURLException;
  25. import java.net.URL;
  26. import java.util.List;
  27. import java.util.Map;
  28. import org.apache.commons.logging.Log;
  29. import org.apache.commons.logging.LogFactory;
  30. import org.apache.fop.afp.AFPConstants;
  31. import org.apache.fop.afp.util.StructuredFieldReader;
  32. /**
  33. * The AFPFontReader is responsible for reading the font attributes from binary
  34. * code page files and the character set metric files. In IBM font structure, a
  35. * code page maps each character of text to the characters in a character set.
  36. * Each character is translated into a code point. When the character is
  37. * printed, each code point is matched to a character ID on the code page
  38. * specified. The character ID is then matched to the image (raster pattern or
  39. * outline pattern) of the character in the character set specified. The image
  40. * in the character set is the image that is printed in the document. To be a
  41. * valid code page for a particular character set, all character IDs in the code
  42. * page must be included in that character set. <p/>This class will read the
  43. * font information from the binary code page files and character set metric
  44. * files in order to determine the correct metrics to use when rendering the
  45. * formatted object. <p/>
  46. *
  47. * @author <a href="mailto:pete@townsend.uk.com">Pete Townsend </a>
  48. */
  49. public final class AFPFontReader {
  50. /**
  51. * Static logging instance
  52. */
  53. protected static final Log log = LogFactory.getLog("org.apache.xmlgraphics.afp.fonts");
  54. /**
  55. * Template used to convert lists to arrays.
  56. */
  57. private static final CharacterSetOrientation[] EMPTY_CSO_ARRAY = new CharacterSetOrientation[0];
  58. /** Codepage MO:DCA structured field. */
  59. private static final byte[] CODEPAGE_SF = new byte[] {
  60. (byte) 0xD3, (byte) 0xA8, (byte) 0x87};
  61. /** Character table MO:DCA structured field. */
  62. private static final byte[] CHARACTER_TABLE_SF = new byte[] {
  63. (byte) 0xD3, (byte) 0x8C, (byte) 0x87};
  64. /** Font control MO:DCA structured field. */
  65. private static final byte[] FONT_CONTROL_SF = new byte[] {
  66. (byte) 0xD3, (byte) 0xA7, (byte) 0x89 };
  67. /** Font orientation MO:DCA structured field. */
  68. private static final byte[] FONT_ORIENTATION_SF = new byte[] {
  69. (byte) 0xD3, (byte) 0xAE, (byte) 0x89 };
  70. /** Font position MO:DCA structured field. */
  71. private static final byte[] FONT_POSITION_SF = new byte[] {
  72. (byte) 0xD3, (byte) 0xAC, (byte) 0x89 };
  73. /** Font index MO:DCA structured field. */
  74. private static final byte[] FONT_INDEX_SF = new byte[] {
  75. (byte) 0xD3, (byte) 0x8C, (byte) 0x89 };
  76. /**
  77. * The conversion factor to millipoints for 240 dpi
  78. */
  79. private static final int FOP_100_DPI_FACTOR = 1;
  80. /**
  81. * The conversion factor to millipoints for 240 dpi
  82. */
  83. private static final int FOP_240_DPI_FACTOR = 300000;
  84. /**
  85. * The conversion factor to millipoints for 300 dpi
  86. */
  87. private static final int FOP_300_DPI_FACTOR = 240000;
  88. /**
  89. * The encoding to use to convert from EBCIDIC to ASCII
  90. */
  91. private static final String ASCII_ENCODING = "UTF8";
  92. /**
  93. * The collection of code pages
  94. */
  95. private final Map/*<String, Map<String, String>>*/ codePages
  96. = new java.util.HashMap/*<String, Map<String, String>>*/();
  97. /**
  98. * Returns an InputStream to a given file path and filename
  99. *
  100. * @param path the file path
  101. * @param filename the file name
  102. * @return an inputStream
  103. *
  104. * @throws IOException in the event that an I/O exception of some sort has occurred
  105. */
  106. private InputStream openInputStream(String path, String filename) throws IOException {
  107. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  108. if (classLoader == null) {
  109. classLoader = AFPFontReader.class.getClassLoader();
  110. }
  111. URL url = classLoader.getResource(path);
  112. if (url == null) {
  113. try {
  114. File file = new File(path);
  115. url = file.toURI().toURL();
  116. if (url == null) {
  117. String msg = "file not found " + filename + " in classpath: " + path;
  118. log.error(msg);
  119. throw new FileNotFoundException(msg);
  120. }
  121. } catch (MalformedURLException ex) {
  122. String msg = "file not found " + filename + " in classpath: " + path;
  123. log.error(msg);
  124. throw new FileNotFoundException(msg);
  125. }
  126. }
  127. File directory = new File(url.getPath());
  128. if (!directory.canRead()) {
  129. String msg = "Failed to read directory " + url.getPath();
  130. log.error(msg);
  131. throw new FileNotFoundException(msg);
  132. }
  133. final String filterpattern = filename.trim();
  134. FilenameFilter filter = new FilenameFilter() {
  135. public boolean accept(File dir, String name) {
  136. return name.startsWith(filterpattern);
  137. }
  138. };
  139. File[] files = directory.listFiles(filter);
  140. if (files.length < 1) {
  141. String msg = "file search for " + filename + " located "
  142. + files.length + " files";
  143. log.error(msg);
  144. throw new FileNotFoundException(msg);
  145. } else if (files.length > 1) {
  146. String msg = "file search for " + filename + " located "
  147. + files.length + " files";
  148. log.warn(msg);
  149. }
  150. InputStream inputStream = files[0].toURI().toURL().openStream();
  151. if (inputStream == null) {
  152. String msg = "AFPFontReader:: getInputStream():: file not found for " + filename;
  153. log.error(msg);
  154. throw new FileNotFoundException(msg);
  155. }
  156. return inputStream;
  157. }
  158. /**
  159. * Closes the inputstream
  160. *
  161. * @param inputStream the inputstream to close
  162. */
  163. private void closeInputStream(InputStream inputStream) {
  164. try {
  165. if (inputStream != null) {
  166. inputStream.close();
  167. }
  168. } catch (Exception ex) {
  169. // Lets log at least!
  170. log.error(ex.getMessage());
  171. }
  172. }
  173. /**
  174. * Load the font details and metrics into the CharacterSetMetric object,
  175. * this will use the actual afp code page and character set files to load
  176. * the object with the necessary metrics.
  177. *
  178. * @param characterSet the CharacterSetMetric object to populate
  179. * @throws IOException if an I/O exception of some sort has occurred.
  180. */
  181. public void loadCharacterSetMetric(CharacterSet characterSet) throws IOException {
  182. InputStream inputStream = null;
  183. try {
  184. /**
  185. * Get the code page which contains the character mapping
  186. * information to map the unicode character id to the graphic
  187. * chracter global identifier.
  188. */
  189. String codePageId = new String(characterSet.getCodePage());
  190. String path = characterSet.getPath();
  191. Map/*<String,String>*/ codePage = (Map/*<String,String>*/)codePages.get(codePageId);
  192. if (codePage == null) {
  193. codePage = loadCodePage(codePageId, characterSet.getEncoding(), path);
  194. codePages.put(codePageId, codePage);
  195. }
  196. /**
  197. * Load the character set metric information, no need to cache this
  198. * information as it should be cached by the objects that wish to
  199. * load character set metric information.
  200. */
  201. final String characterSetName = characterSet.getName();
  202. inputStream = openInputStream(path, characterSetName);
  203. StructuredFieldReader structuredFieldReader = new StructuredFieldReader(inputStream);
  204. // Process D3A789 Font Control
  205. FontControl fontControl = processFontControl(structuredFieldReader);
  206. if (fontControl != null) {
  207. //process D3AE89 Font Orientation
  208. CharacterSetOrientation[] characterSetOrientations
  209. = processFontOrientation(structuredFieldReader);
  210. int dpi = fontControl.getDpi();
  211. //process D3AC89 Font Position
  212. processFontPosition(structuredFieldReader, characterSetOrientations, dpi);
  213. //process D38C89 Font Index (per orientation)
  214. for (int i = 0; i < characterSetOrientations.length; i++) {
  215. processFontIndex(structuredFieldReader,
  216. characterSetOrientations[i], codePage, dpi);
  217. characterSet.addCharacterSetOrientation(characterSetOrientations[i]);
  218. }
  219. } else {
  220. throw new IOException(
  221. "Failed to read font control structured field in character set "
  222. + characterSetName);
  223. }
  224. } finally {
  225. closeInputStream(inputStream);
  226. }
  227. }
  228. /**
  229. * Load the code page information from the appropriate file. The file name
  230. * to load is determined by the code page name and the file extension 'CDP'.
  231. *
  232. * @param codePage
  233. * the code page identifier
  234. * @param encoding
  235. * the encoding to use for the character decoding
  236. * @returns a code page mapping
  237. */
  238. private Map/*<String,String>*/ loadCodePage(String codePage, String encoding,
  239. String path) throws IOException {
  240. // Create the HashMap to store code page information
  241. Map/*<String,String>*/ codePages = new java.util.HashMap/*<String,String>*/();
  242. InputStream inputStream = null;
  243. try {
  244. inputStream = openInputStream(path, codePage.trim());
  245. StructuredFieldReader structuredFieldReader = new StructuredFieldReader(inputStream);
  246. byte[] data = structuredFieldReader.getNext(CHARACTER_TABLE_SF);
  247. int position = 0;
  248. byte[] gcgiBytes = new byte[8];
  249. byte[] charBytes = new byte[1];
  250. // Read data, ignoring bytes 0 - 2
  251. for (int index = 3; index < data.length; index++) {
  252. if (position < 8) {
  253. // Build the graphic character global identifier key
  254. gcgiBytes[position] = data[index];
  255. position++;
  256. } else if (position == 9) {
  257. position = 0;
  258. // Set the character
  259. charBytes[0] = data[index];
  260. String gcgiString = new String(gcgiBytes,
  261. AFPConstants.EBCIDIC_ENCODING);
  262. String charString = new String(charBytes, encoding);
  263. // int value = charString.charAt(0);
  264. codePages.put(gcgiString, charString);
  265. } else {
  266. position++;
  267. }
  268. }
  269. } finally {
  270. closeInputStream(inputStream);
  271. }
  272. return codePages;
  273. }
  274. /**
  275. * Process the font control details using the structured field reader.
  276. *
  277. * @param structuredFieldReader
  278. * the structured field reader
  279. */
  280. private FontControl processFontControl(StructuredFieldReader structuredFieldReader)
  281. throws IOException {
  282. byte[] fncData = structuredFieldReader.getNext(FONT_CONTROL_SF);
  283. // int position = 0;
  284. FontControl fontControl = null;
  285. if (fncData != null) {
  286. fontControl = new FontControl();
  287. if (fncData[7] == (byte) 0x02) {
  288. fontControl.setRelative(true);
  289. }
  290. int dpi = (((fncData[9] & 0xFF) << 8) + (fncData[10] & 0xFF)) / 10;
  291. fontControl.setDpi(dpi);
  292. }
  293. return fontControl;
  294. }
  295. /**
  296. * Process the font orientation details from using the structured field
  297. * reader.
  298. *
  299. * @param structuredFieldReader
  300. * the structured field reader
  301. */
  302. private CharacterSetOrientation[] processFontOrientation(
  303. StructuredFieldReader structuredFieldReader) throws IOException {
  304. byte[] data = structuredFieldReader.getNext(FONT_ORIENTATION_SF);
  305. int position = 0;
  306. byte[] fnoData = new byte[26];
  307. List orientations = new java.util.ArrayList();
  308. // Read data, ignoring bytes 0 - 2
  309. for (int index = 3; index < data.length; index++) {
  310. // Build the font orientation record
  311. fnoData[position] = data[index];
  312. position++;
  313. if (position == 26) {
  314. position = 0;
  315. int orientation = 0;
  316. switch (fnoData[2]) {
  317. case 0x00:
  318. orientation = 0;
  319. break;
  320. case 0x2D:
  321. orientation = 90;
  322. break;
  323. case 0x5A:
  324. orientation = 180;
  325. break;
  326. case (byte) 0x87:
  327. orientation = 270;
  328. break;
  329. default:
  330. System.out.println("ERROR: Oriantation");
  331. }
  332. CharacterSetOrientation cso = new CharacterSetOrientation(
  333. orientation);
  334. orientations.add(cso);
  335. }
  336. }
  337. return (CharacterSetOrientation[]) orientations
  338. .toArray(EMPTY_CSO_ARRAY);
  339. }
  340. /**
  341. * Populate the CharacterSetOrientation object in the suplied array with the
  342. * font position details using the supplied structured field reader.
  343. *
  344. * @param structuredFieldReader
  345. * the structured field reader
  346. * @param characterSetOrientations
  347. * the array of CharacterSetOrientation objects
  348. */
  349. private void processFontPosition(StructuredFieldReader structuredFieldReader,
  350. CharacterSetOrientation[] characterSetOrientations, int dpi) throws IOException {
  351. byte[] data = structuredFieldReader.getNext(FONT_POSITION_SF);
  352. int position = 0;
  353. byte[] fpData = new byte[26];
  354. int characterSetOrientationIndex = 0;
  355. int fopFactor = 0;
  356. switch (dpi) {
  357. case 100:
  358. fopFactor = FOP_100_DPI_FACTOR;
  359. break;
  360. case 240:
  361. fopFactor = FOP_240_DPI_FACTOR;
  362. break;
  363. case 300:
  364. fopFactor = FOP_300_DPI_FACTOR;
  365. break;
  366. default:
  367. String msg = "Unsupported font resolution of " + dpi + " dpi.";
  368. log.error(msg);
  369. throw new IOException(msg);
  370. }
  371. // Read data, ignoring bytes 0 - 2
  372. for (int index = 3; index < data.length; index++) {
  373. if (position < 22) {
  374. // Build the font orientation record
  375. fpData[position] = data[index];
  376. } else if (position == 22) {
  377. position = 0;
  378. CharacterSetOrientation characterSetOrientation
  379. = characterSetOrientations[characterSetOrientationIndex];
  380. int xHeight = ((fpData[2] & 0xFF) << 8) + (fpData[3] & 0xFF);
  381. int capHeight = ((fpData[4] & 0xFF) << 8) + (fpData[5] & 0xFF);
  382. int ascHeight = ((fpData[6] & 0xFF) << 8) + (fpData[7] & 0xFF);
  383. int dscHeight = ((fpData[8] & 0xFF) << 8) + (fpData[9] & 0xFF);
  384. dscHeight = dscHeight * -1;
  385. characterSetOrientation.setXHeight(xHeight * fopFactor);
  386. characterSetOrientation.setCapHeight(capHeight * fopFactor);
  387. characterSetOrientation.setAscender(ascHeight * fopFactor);
  388. characterSetOrientation.setDescender(dscHeight * fopFactor);
  389. characterSetOrientationIndex++;
  390. fpData[position] = data[index];
  391. }
  392. position++;
  393. }
  394. }
  395. /**
  396. * Process the font index details for the character set orientation.
  397. *
  398. * @param structuredFieldReader
  399. * the structured field reader
  400. * @param cso
  401. * the CharacterSetOrientation object to populate
  402. * @param codepage
  403. * the map of code pages
  404. */
  405. private void processFontIndex(StructuredFieldReader structuredFieldReader,
  406. CharacterSetOrientation cso, Map/*<String,String>*/ codepage, int dpi)
  407. throws IOException {
  408. byte[] data = structuredFieldReader.getNext(FONT_INDEX_SF);
  409. int fopFactor = 0;
  410. switch (dpi) {
  411. case 100:
  412. fopFactor = FOP_100_DPI_FACTOR;
  413. break;
  414. case 240:
  415. fopFactor = FOP_240_DPI_FACTOR;
  416. break;
  417. case 300:
  418. fopFactor = FOP_300_DPI_FACTOR;
  419. break;
  420. default:
  421. String msg = "Unsupported font resolution of " + dpi + " dpi.";
  422. log.error(msg);
  423. throw new IOException(msg);
  424. }
  425. int position = 0;
  426. byte[] gcgid = new byte[8];
  427. byte[] fiData = new byte[20];
  428. int lowest = 255;
  429. int highest = 0;
  430. // Read data, ignoring bytes 0 - 2
  431. for (int index = 3; index < data.length; index++) {
  432. if (position < 8) {
  433. gcgid[position] = data[index];
  434. position++;
  435. } else if (position < 27) {
  436. fiData[position - 8] = data[index];
  437. position++;
  438. } else if (position == 27) {
  439. fiData[position - 8] = data[index];
  440. position = 0;
  441. String gcgiString = new String(gcgid, AFPConstants.EBCIDIC_ENCODING);
  442. String idx = (String) codepage.get(gcgiString);
  443. if (idx != null) {
  444. int cidx = idx.charAt(0);
  445. int width = ((fiData[0] & 0xFF) << 8) + (fiData[1] & 0xFF);
  446. if (cidx < lowest) {
  447. lowest = cidx;
  448. }
  449. if (cidx > highest) {
  450. highest = cidx;
  451. }
  452. int a = (width * fopFactor);
  453. cso.setWidth(cidx, a);
  454. }
  455. }
  456. }
  457. cso.setFirstChar(lowest);
  458. cso.setLastChar(highest);
  459. }
  460. private class FontControl {
  461. private int dpi;
  462. private boolean isRelative = false;
  463. public int getDpi() {
  464. return dpi;
  465. }
  466. public void setDpi(int i) {
  467. dpi = i;
  468. }
  469. public boolean isRelative() {
  470. return isRelative;
  471. }
  472. public void setRelative(boolean b) {
  473. isRelative = b;
  474. }
  475. }
  476. }