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