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.

CharacterSetBuilder.java 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  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.FileNotFoundException;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.net.URI;
  23. import java.net.URISyntaxException;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.WeakHashMap;
  27. import org.apache.commons.logging.Log;
  28. import org.apache.commons.logging.LogFactory;
  29. import org.apache.xmlgraphics.image.loader.util.SoftMapCache;
  30. import org.apache.fop.afp.AFPConstants;
  31. import org.apache.fop.afp.util.ResourceAccessor;
  32. import org.apache.fop.afp.util.StructuredFieldReader;
  33. import org.apache.fop.fonts.Typeface;
  34. /**
  35. * The CharacterSetBuilder is responsible building the a CharacterSet instance that holds
  36. * the font metric data. The data is either read from disk and passed to a CharacterSet (*)
  37. * or a FopCharacterSet is instantiated that is composed of a Typeface instance configured
  38. * with this data.<p/>
  39. * -*- For referenced fonts CharacterSetBuilder is responsible for reading the font attributes
  40. * from binary code page files and the character set metric files. In IBM font structure, a
  41. * code page maps each character of text to the characters in a character set.
  42. * Each character is translated into a code point. When the character is
  43. * printed, each code point is matched to a character ID on the code page
  44. * specified. The character ID is then matched to the image (raster pattern or
  45. * outline pattern) of the character in the character set specified. The image
  46. * in the character set is the image that is printed in the document. To be a
  47. * valid code page for a particular character set, all character IDs in the code
  48. * page must be included in that character set. <p/>This class will read the
  49. * font information from the binary code page files and character set metric
  50. * files in order to determine the correct metrics to use when rendering the
  51. * formatted object. <p/>
  52. *
  53. */
  54. public class CharacterSetBuilder {
  55. /**
  56. * Static logging instance
  57. */
  58. protected static final Log LOG = LogFactory.getLog(CharacterSetBuilder.class);
  59. /**
  60. * Singleton reference
  61. */
  62. private static CharacterSetBuilder instance;
  63. /**
  64. * Template used to convert lists to arrays.
  65. */
  66. private static final CharacterSetOrientation[] EMPTY_CSO_ARRAY = new CharacterSetOrientation[0];
  67. /** Codepage MO:DCA structured field. */
  68. private static final byte[] CODEPAGE_SF = new byte[] {
  69. (byte) 0xD3, (byte) 0xA8, (byte) 0x87};
  70. /** Character table MO:DCA structured field. */
  71. private static final byte[] CHARACTER_TABLE_SF = new byte[] {
  72. (byte) 0xD3, (byte) 0x8C, (byte) 0x87};
  73. /** Font descriptor MO:DCA structured field. */
  74. private static final byte[] FONT_DESCRIPTOR_SF = new byte[] {
  75. (byte) 0xD3, (byte) 0xA6, (byte) 0x89 };
  76. /** Font control MO:DCA structured field. */
  77. private static final byte[] FONT_CONTROL_SF = new byte[] {
  78. (byte) 0xD3, (byte) 0xA7, (byte) 0x89 };
  79. /** Font orientation MO:DCA structured field. */
  80. private static final byte[] FONT_ORIENTATION_SF = new byte[] {
  81. (byte) 0xD3, (byte) 0xAE, (byte) 0x89 };
  82. /** Font position MO:DCA structured field. */
  83. private static final byte[] FONT_POSITION_SF = new byte[] {
  84. (byte) 0xD3, (byte) 0xAC, (byte) 0x89 };
  85. /** Font index MO:DCA structured field. */
  86. private static final byte[] FONT_INDEX_SF = new byte[] {
  87. (byte) 0xD3, (byte) 0x8C, (byte) 0x89 };
  88. /**
  89. * The collection of code pages
  90. */
  91. private final Map/*<String, Map<String, String>>*/ codePagesCache
  92. = new WeakHashMap/*<String, Map<String, String>>*/();
  93. /**
  94. * Cache of charactersets
  95. */
  96. private final SoftMapCache characterSetsCache = new SoftMapCache(true);
  97. /** Default constructor. */
  98. protected CharacterSetBuilder() {
  99. }
  100. /**
  101. * Factory method for the single-byte implementation of AFPFontReader.
  102. * @return AFPFontReader
  103. */
  104. public static CharacterSetBuilder getInstance() {
  105. if (instance == null) {
  106. instance = new CharacterSetBuilder();
  107. }
  108. return instance;
  109. }
  110. /**
  111. * Factory method for the double-byte (CID Keyed font (Type 0)) implementation of AFPFontReader.
  112. * @return AFPFontReader
  113. */
  114. public static CharacterSetBuilder getDoubleByteInstance() {
  115. return new DoubleByteLoader();
  116. }
  117. /**
  118. * Returns an InputStream to a given file path and filename
  119. *
  120. * * @param accessor the resource accessor
  121. * @param filename the file name
  122. * @return an inputStream
  123. *
  124. * @throws IOException in the event that an I/O exception of some sort has occurred
  125. */
  126. protected InputStream openInputStream(ResourceAccessor accessor, String filename)
  127. throws IOException {
  128. URI uri;
  129. try {
  130. uri = new URI(filename.trim());
  131. } catch (URISyntaxException e) {
  132. throw new FileNotFoundException("Invalid filename: "
  133. + filename + " (" + e.getMessage() + ")");
  134. }
  135. if (LOG.isDebugEnabled()) {
  136. LOG.debug("Opening " + uri);
  137. }
  138. InputStream inputStream = accessor.createInputStream(uri);
  139. return inputStream;
  140. }
  141. /**
  142. * Closes the inputstream
  143. *
  144. * @param inputStream the inputstream to close
  145. */
  146. protected void closeInputStream(InputStream inputStream) {
  147. try {
  148. if (inputStream != null) {
  149. inputStream.close();
  150. }
  151. } catch (Exception ex) {
  152. // Lets log at least!
  153. LOG.error(ex.getMessage());
  154. }
  155. }
  156. /**
  157. * Load the font details and metrics into the CharacterSetMetric object,
  158. * this will use the actual afp code page and character set files to load
  159. * the object with the necessary metrics.
  160. * @param characterSetName name of the characterset
  161. * @param codePageName name of the code page file
  162. * @param encoding encoding name
  163. * @param accessor used to load codepage and characterset
  164. * @return CharacterSet object
  165. * @throws IOException if an I/O error occurs
  166. */
  167. public CharacterSet build(String characterSetName, String codePageName,
  168. String encoding, ResourceAccessor accessor) throws IOException {
  169. // check for cached version of the characterset
  170. String descriptor = characterSetName + "_" + encoding + "_" + codePageName;
  171. CharacterSet characterSet = (CharacterSet)characterSetsCache.get(descriptor);
  172. if (characterSet != null) {
  173. return characterSet;
  174. }
  175. // characterset not in the cache, so recreating
  176. characterSet = new CharacterSet(
  177. codePageName, encoding, characterSetName, accessor);
  178. InputStream inputStream = null;
  179. try {
  180. /**
  181. * Get the code page which contains the character mapping
  182. * information to map the unicode character id to the graphic
  183. * chracter global identifier.
  184. */
  185. Map/*<String,String>*/ codePage
  186. = (Map/*<String,String>*/)codePagesCache.get(codePageName);
  187. if (codePage == null) {
  188. codePage = loadCodePage(codePageName, encoding, accessor);
  189. codePagesCache.put(codePageName, codePage);
  190. }
  191. inputStream = openInputStream(accessor, characterSetName);
  192. StructuredFieldReader structuredFieldReader = new StructuredFieldReader(inputStream);
  193. // Process D3A689 Font Descriptor
  194. FontDescriptor fontDescriptor = processFontDescriptor(structuredFieldReader);
  195. characterSet.setNominalVerticalSize(fontDescriptor.getNominalFontSizeInMillipoints());
  196. // Process D3A789 Font Control
  197. FontControl fontControl = processFontControl(structuredFieldReader);
  198. if (fontControl != null) {
  199. //process D3AE89 Font Orientation
  200. CharacterSetOrientation[] characterSetOrientations
  201. = processFontOrientation(structuredFieldReader);
  202. int metricNormalizationFactor;
  203. if (fontControl.isRelative()) {
  204. metricNormalizationFactor = 1;
  205. } else {
  206. int dpi = fontControl.getDpi();
  207. metricNormalizationFactor = 1000 * 72000
  208. / fontDescriptor.getNominalFontSizeInMillipoints() / dpi;
  209. }
  210. //process D3AC89 Font Position
  211. processFontPosition(structuredFieldReader, characterSetOrientations,
  212. metricNormalizationFactor);
  213. //process D38C89 Font Index (per orientation)
  214. for (int i = 0; i < characterSetOrientations.length; i++) {
  215. processFontIndex(structuredFieldReader,
  216. characterSetOrientations[i], codePage, metricNormalizationFactor);
  217. characterSet.addCharacterSetOrientation(characterSetOrientations[i]);
  218. }
  219. } else {
  220. throw new IOException("Missing D3AE89 Font Control structured field.");
  221. }
  222. } finally {
  223. closeInputStream(inputStream);
  224. }
  225. characterSetsCache.put(descriptor, characterSet);
  226. return characterSet;
  227. }
  228. /**
  229. * Load the font details and metrics into the CharacterSetMetric object,
  230. * this will use the actual afp code page and character set files to load
  231. * the object with the necessary metrics.
  232. *
  233. * @param characterSetName the CharacterSetMetric object to populate
  234. * @param codePageName the name of the code page to use
  235. * @param encoding name of the encoding in use
  236. * @param typeface base14 font name
  237. * @return CharacterSet object
  238. */
  239. public CharacterSet build(String characterSetName, String codePageName,
  240. String encoding, Typeface typeface) {
  241. return new FopCharacterSet(codePageName, encoding, characterSetName, typeface);
  242. }
  243. /**
  244. * Load the code page information from the appropriate file. The file name
  245. * to load is determined by the code page name and the file extension 'CDP'.
  246. *
  247. * @param codePage
  248. * the code page identifier
  249. * @param encoding
  250. * the encoding to use for the character decoding
  251. * @param accessor the resource accessor
  252. * @return a code page mapping (key: GCGID, value: Unicode character)
  253. * @throws IOException if an I/O exception of some sort has occurred.
  254. */
  255. protected Map/*<String,String>*/ loadCodePage(String codePage, String encoding,
  256. ResourceAccessor accessor) throws IOException {
  257. // Create the HashMap to store code page information
  258. Map/*<String,String>*/ codePages = new java.util.HashMap/*<String,String>*/();
  259. InputStream inputStream = null;
  260. try {
  261. inputStream = openInputStream(accessor, codePage.trim());
  262. StructuredFieldReader structuredFieldReader = new StructuredFieldReader(inputStream);
  263. byte[] data = structuredFieldReader.getNext(CHARACTER_TABLE_SF);
  264. int position = 0;
  265. byte[] gcgiBytes = new byte[8];
  266. byte[] charBytes = new byte[1];
  267. // Read data, ignoring bytes 0 - 2
  268. for (int index = 3; index < data.length; index++) {
  269. if (position < 8) {
  270. // Build the graphic character global identifier key
  271. gcgiBytes[position] = data[index];
  272. position++;
  273. } else if (position == 9) {
  274. position = 0;
  275. // Set the character
  276. charBytes[0] = data[index];
  277. String gcgiString = new String(gcgiBytes,
  278. AFPConstants.EBCIDIC_ENCODING);
  279. //Use the 8-bit char index to find the Unicode character using the Java encoding
  280. //given in the configuration. If the code page and the Java encoding don't
  281. //match, a wrong Unicode character will be associated with the AFP GCGID.
  282. //Idea: we could use IBM's GCGID to Unicode map and build code pages ourselves.
  283. String charString = new String(charBytes, encoding);
  284. codePages.put(gcgiString, charString);
  285. } else {
  286. position++;
  287. }
  288. }
  289. } finally {
  290. closeInputStream(inputStream);
  291. }
  292. return codePages;
  293. }
  294. /**
  295. * Process the font descriptor details using the structured field reader.
  296. *
  297. * @param structuredFieldReader the structured field reader
  298. * @return a class representing the font descriptor
  299. * @throws IOException if an I/O exception of some sort has occurred.
  300. */
  301. protected static FontDescriptor processFontDescriptor(
  302. StructuredFieldReader structuredFieldReader)
  303. throws IOException {
  304. byte[] fndData = structuredFieldReader.getNext(FONT_DESCRIPTOR_SF);
  305. return new FontDescriptor(fndData);
  306. }
  307. /**
  308. * Process the font control details using the structured field reader.
  309. *
  310. * @param structuredFieldReader
  311. * the structured field reader
  312. * @return the FontControl
  313. * @throws IOException if an I/O exception of some sort has occurred.
  314. */
  315. protected FontControl processFontControl(StructuredFieldReader structuredFieldReader)
  316. throws IOException {
  317. byte[] fncData = structuredFieldReader.getNext(FONT_CONTROL_SF);
  318. FontControl fontControl = null;
  319. if (fncData != null) {
  320. fontControl = new FontControl();
  321. if (fncData[7] == (byte) 0x02) {
  322. fontControl.setRelative(true);
  323. }
  324. int metricResolution = getUBIN(fncData, 9);
  325. if (metricResolution == 1000) {
  326. //Special case: 1000 units per em (rather than dpi)
  327. fontControl.setUnitsPerEm(1000);
  328. } else {
  329. fontControl.setDpi(metricResolution / 10);
  330. }
  331. }
  332. return fontControl;
  333. }
  334. /**
  335. * Process the font orientation details from using the structured field
  336. * reader.
  337. *
  338. * @param structuredFieldReader
  339. * the structured field reader
  340. * @return CharacterSetOrientation array
  341. * @throws IOException if an I/O exception of some sort has occurred.
  342. */
  343. protected CharacterSetOrientation[] processFontOrientation(
  344. StructuredFieldReader structuredFieldReader) throws IOException {
  345. byte[] data = structuredFieldReader.getNext(FONT_ORIENTATION_SF);
  346. int position = 0;
  347. byte[] fnoData = new byte[26];
  348. List orientations = new java.util.ArrayList();
  349. // Read data, ignoring bytes 0 - 2
  350. for (int index = 3; index < data.length; index++) {
  351. // Build the font orientation record
  352. fnoData[position] = data[index];
  353. position++;
  354. if (position == 26) {
  355. position = 0;
  356. int orientation = determineOrientation(fnoData[2]);
  357. // Space Increment
  358. int space = ((fnoData[8] & 0xFF ) << 8) + (fnoData[9] & 0xFF);
  359. // Em-Space Increment
  360. int em = ((fnoData[14] & 0xFF ) << 8) + (fnoData[15] & 0xFF);
  361. CharacterSetOrientation cso = new CharacterSetOrientation(orientation);
  362. cso.setSpaceIncrement(space);
  363. cso.setEmSpaceIncrement(em);
  364. orientations.add(cso);
  365. }
  366. }
  367. return (CharacterSetOrientation[]) orientations
  368. .toArray(EMPTY_CSO_ARRAY);
  369. }
  370. /**
  371. * Populate the CharacterSetOrientation object in the suplied array with the
  372. * font position details using the supplied structured field reader.
  373. *
  374. * @param structuredFieldReader
  375. * the structured field reader
  376. * @param characterSetOrientations
  377. * the array of CharacterSetOrientation objects
  378. * @param metricNormalizationFactor factor to apply to the metrics to get normalized
  379. * font metric values
  380. * @throws IOException if an I/O exception of some sort has occurred.
  381. */
  382. protected void processFontPosition(StructuredFieldReader structuredFieldReader,
  383. CharacterSetOrientation[] characterSetOrientations, double metricNormalizationFactor)
  384. throws IOException {
  385. byte[] data = structuredFieldReader.getNext(FONT_POSITION_SF);
  386. int position = 0;
  387. byte[] fpData = new byte[26];
  388. int characterSetOrientationIndex = 0;
  389. // Read data, ignoring bytes 0 - 2
  390. for (int index = 3; index < data.length; index++) {
  391. if (position < 22) {
  392. // Build the font orientation record
  393. fpData[position] = data[index];
  394. if (position == 9) {
  395. CharacterSetOrientation characterSetOrientation
  396. = characterSetOrientations[characterSetOrientationIndex];
  397. int xHeight = getSBIN(fpData, 2);
  398. int capHeight = getSBIN(fpData, 4);
  399. int ascHeight = getSBIN(fpData, 6);
  400. int dscHeight = getSBIN(fpData, 8);
  401. dscHeight = dscHeight * -1;
  402. characterSetOrientation.setXHeight(
  403. (int)Math.round(xHeight * metricNormalizationFactor));
  404. characterSetOrientation.setCapHeight(
  405. (int)Math.round(capHeight * metricNormalizationFactor));
  406. characterSetOrientation.setAscender(
  407. (int)Math.round(ascHeight * metricNormalizationFactor));
  408. characterSetOrientation.setDescender(
  409. (int)Math.round(dscHeight * metricNormalizationFactor));
  410. }
  411. } else if (position == 22) {
  412. position = 0;
  413. characterSetOrientationIndex++;
  414. fpData[position] = data[index];
  415. }
  416. position++;
  417. }
  418. }
  419. /**
  420. * Process the font index details for the character set orientation.
  421. *
  422. * @param structuredFieldReader the structured field reader
  423. * @param cso the CharacterSetOrientation object to populate
  424. * @param codepage the map of code pages
  425. * @param metricNormalizationFactor factor to apply to the metrics to get normalized
  426. * font metric values
  427. * @throws IOException if an I/O exception of some sort has occurred.
  428. */
  429. protected void processFontIndex(StructuredFieldReader structuredFieldReader,
  430. CharacterSetOrientation cso, Map/*<String,String>*/ codepage,
  431. double metricNormalizationFactor)
  432. throws IOException {
  433. byte[] data = structuredFieldReader.getNext(FONT_INDEX_SF);
  434. int position = 0;
  435. byte[] gcgid = new byte[8];
  436. byte[] fiData = new byte[20];
  437. char lowest = 255;
  438. char highest = 0;
  439. String firstABCMismatch = null;
  440. // Read data, ignoring bytes 0 - 2
  441. for (int index = 3; index < data.length; index++) {
  442. if (position < 8) {
  443. gcgid[position] = data[index];
  444. position++;
  445. } else if (position < 27) {
  446. fiData[position - 8] = data[index];
  447. position++;
  448. } else if (position == 27) {
  449. fiData[position - 8] = data[index];
  450. position = 0;
  451. String gcgiString = new String(gcgid, AFPConstants.EBCIDIC_ENCODING);
  452. String idx = (String) codepage.get(gcgiString);
  453. if (idx != null) {
  454. char cidx = idx.charAt(0);
  455. int width = getUBIN(fiData, 0);
  456. int a = getSBIN(fiData, 10);
  457. int b = getUBIN(fiData, 12);
  458. int c = getSBIN(fiData, 14);
  459. int abc = a + b + c;
  460. int diff = Math.abs(abc - width);
  461. if (diff != 0 && width != 0) {
  462. double diffPercent = 100 * diff / (double)width;
  463. if (diffPercent > 2) {
  464. if (LOG.isTraceEnabled()) {
  465. LOG.trace(gcgiString + ": "
  466. + a + " + " + b + " + " + c + " = " + (a + b + c)
  467. + " but found: " + width);
  468. }
  469. if (firstABCMismatch == null) {
  470. firstABCMismatch = gcgiString;
  471. }
  472. }
  473. }
  474. if (cidx < lowest) {
  475. lowest = cidx;
  476. }
  477. if (cidx > highest) {
  478. highest = cidx;
  479. }
  480. int normalizedWidth = (int)Math.round(width * metricNormalizationFactor);
  481. cso.setWidth(cidx, normalizedWidth);
  482. }
  483. }
  484. }
  485. cso.setFirstChar(lowest);
  486. cso.setLastChar(highest);
  487. if (LOG.isDebugEnabled() && firstABCMismatch != null) {
  488. //Debug level because it usually is no problem.
  489. LOG.debug("Font has metrics inconsitencies where A+B+C doesn't equal the"
  490. + " character increment. The first such character found: "
  491. + firstABCMismatch);
  492. }
  493. }
  494. private static int getUBIN(byte[] data, int start) {
  495. return ((data[start] & 0xFF) << 8) + (data[start + 1] & 0xFF);
  496. }
  497. private static int getSBIN(byte[] data, int start) {
  498. int ubin = ((data[start] & 0xFF) << 8) + (data[start + 1] & 0xFF);
  499. if ((ubin & 0x8000) != 0) {
  500. //extend sign
  501. return ubin | 0xFFFF0000;
  502. } else {
  503. return ubin;
  504. }
  505. }
  506. private class FontControl {
  507. private int dpi;
  508. private int unitsPerEm;
  509. private boolean isRelative = false;
  510. public int getDpi() {
  511. return dpi;
  512. }
  513. public void setDpi(int i) {
  514. dpi = i;
  515. }
  516. public int getUnitsPerEm() {
  517. return this.unitsPerEm;
  518. }
  519. public void setUnitsPerEm(int value) {
  520. this.unitsPerEm = value;
  521. }
  522. public boolean isRelative() {
  523. return isRelative;
  524. }
  525. public void setRelative(boolean b) {
  526. isRelative = b;
  527. }
  528. }
  529. private static class FontDescriptor {
  530. private byte[] data;
  531. public FontDescriptor(byte[] data) {
  532. this.data = data;
  533. }
  534. public int getNominalFontSizeInMillipoints() {
  535. int nominalFontSize = 100 * getUBIN(data, 39);
  536. return nominalFontSize;
  537. }
  538. }
  539. /**
  540. * Double-byte (CID Keyed font (Type 0)) implementation of AFPFontReader.
  541. */
  542. private static class DoubleByteLoader extends CharacterSetBuilder {
  543. protected Map/*<String,String>*/ loadCodePage(String codePage, String encoding,
  544. ResourceAccessor accessor) throws IOException {
  545. // Create the HashMap to store code page information
  546. Map/*<String,String>*/ codePages = new java.util.HashMap/*<String,String>*/();
  547. InputStream inputStream = null;
  548. try {
  549. inputStream = openInputStream(accessor, codePage.trim());
  550. StructuredFieldReader structuredFieldReader
  551. = new StructuredFieldReader(inputStream);
  552. byte[] data;
  553. while ((data = structuredFieldReader.getNext(CHARACTER_TABLE_SF)) != null) {
  554. int position = 0;
  555. byte[] gcgiBytes = new byte[8];
  556. byte[] charBytes = new byte[2];
  557. // Read data, ignoring bytes 0 - 2
  558. for (int index = 3; index < data.length; index++) {
  559. if (position < 8) {
  560. // Build the graphic character global identifier key
  561. gcgiBytes[position] = data[index];
  562. position++;
  563. } else if (position == 9) {
  564. // Set the character
  565. charBytes[0] = data[index];
  566. position++;
  567. } else if (position == 10) {
  568. position = 0;
  569. // Set the character
  570. charBytes[1] = data[index];
  571. String gcgiString = new String(gcgiBytes,
  572. AFPConstants.EBCIDIC_ENCODING);
  573. String charString = new String(charBytes, encoding);
  574. codePages.put(gcgiString, charString);
  575. }
  576. else {
  577. position++;
  578. }
  579. }
  580. }
  581. } finally {
  582. closeInputStream(inputStream);
  583. }
  584. return codePages;
  585. }
  586. }
  587. private static int determineOrientation(byte orientation) {
  588. int degrees = 0;
  589. switch (orientation) {
  590. case 0x00:
  591. degrees = 0;
  592. break;
  593. case 0x2D:
  594. degrees = 90;
  595. break;
  596. case 0x5A:
  597. degrees = 180;
  598. break;
  599. case (byte) 0x87:
  600. degrees = 270;
  601. break;
  602. default:
  603. throw new IllegalStateException("Invalid orientation: " + orientation);
  604. }
  605. return degrees;
  606. }
  607. }