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: SingleByteFont.java 1805173 2017-08-16 10:50:04Z ssteiner $ */ 19 20 package org.apache.fop.fonts; 21 22 import java.awt.Rectangle; 23 import java.util.Collections; 24 import java.util.HashMap; 25 import java.util.LinkedHashMap; 26 import java.util.Map; 27 import java.util.Set; 28 import java.util.TreeSet; 29 30 import org.apache.commons.logging.Log; 31 import org.apache.commons.logging.LogFactory; 32 33 import org.apache.xmlgraphics.fonts.Glyphs; 34 35 import org.apache.fop.apps.io.InternalResourceResolver; 36 import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion; 37 import org.apache.fop.util.CharUtilities; 38 39 /** 40 * Generic SingleByte font 41 */ 42 public class SingleByteFont extends CustomFont { 43 44 /** logger */ 45 private static Log log = LogFactory.getLog(SingleByteFont.class); 46 47 protected SingleByteEncoding mapping; 48 private boolean useNativeEncoding; 49 50 protected int[] width; 51 52 private Rectangle[] boundingBoxes; 53 54 private Map<Character, Character> alternativeCodes; 55 56 private PostScriptVersion ttPostScriptVersion; 57 58 private int usedGlyphsCount; 59 private LinkedHashMap<Integer, String> usedGlyphNames; 60 private Map<Integer, Integer> usedGlyphs; 61 private Map<Integer, Character> usedCharsIndex; 62 private Map<Character, Integer> charGIDMappings; 63 SingleByteFont(InternalResourceResolver resourceResolver)64 public SingleByteFont(InternalResourceResolver resourceResolver) { 65 super(resourceResolver); 66 setEncoding(CodePointMapping.WIN_ANSI_ENCODING); 67 } 68 SingleByteFont(InternalResourceResolver resourceResolver, EmbeddingMode embeddingMode)69 public SingleByteFont(InternalResourceResolver resourceResolver, EmbeddingMode embeddingMode) { 70 this(resourceResolver); 71 setEmbeddingMode(embeddingMode); 72 if (embeddingMode != EmbeddingMode.FULL) { 73 usedGlyphNames = new LinkedHashMap<Integer, String>(); 74 usedGlyphs = new HashMap<Integer, Integer>(); 75 usedCharsIndex = new HashMap<Integer, Character>(); 76 charGIDMappings = new HashMap<Character, Integer>(); 77 78 // The zeroth value is reserved for .notdef 79 usedGlyphs.put(0, 0); 80 usedGlyphsCount++; 81 } 82 } 83 84 /** {@inheritDoc} */ isEmbeddable()85 public boolean isEmbeddable() { 86 return (!(getEmbedFileURI() == null 87 && getEmbedResourceName() == null)); 88 } 89 90 /** {@inheritDoc} */ isSubsetEmbedded()91 public boolean isSubsetEmbedded() { 92 return getEmbeddingMode() != EmbeddingMode.FULL; 93 } 94 95 /** {@inheritDoc} */ getEncodingName()96 public String getEncodingName() { 97 return this.mapping.getName(); 98 } 99 100 /** 101 * Returns the code point mapping (encoding) of this font. 102 * @return the code point mapping 103 */ getEncoding()104 public SingleByteEncoding getEncoding() { 105 return this.mapping; 106 } 107 108 /** {@inheritDoc} */ getWidth(int i, int size)109 public int getWidth(int i, int size) { 110 if (i < 256) { 111 int idx = i - getFirstChar(); 112 if (idx >= 0 && idx < width.length) { 113 return size * width[idx]; 114 } 115 } else if (this.additionalEncodings != null) { 116 int encodingIndex = (i / 256) - 1; 117 SimpleSingleByteEncoding encoding = getAdditionalEncoding(encodingIndex); 118 int codePoint = i % 256; 119 NamedCharacter nc = encoding.getCharacterForIndex(codePoint); 120 UnencodedCharacter uc 121 = this.unencodedCharacters.get(nc.getSingleUnicodeValue()); 122 return size * uc.getWidth(); 123 } 124 return 0; 125 } 126 127 /** {@inheritDoc} */ getWidths()128 public int[] getWidths() { 129 int[] arr = new int[width.length]; 130 System.arraycopy(width, 0, arr, 0, width.length); 131 return arr; 132 } 133 getBoundingBox(int glyphIndex, int size)134 public Rectangle getBoundingBox(int glyphIndex, int size) { 135 Rectangle bbox = null; 136 if (glyphIndex < 256) { 137 int idx = glyphIndex - getFirstChar(); 138 if (idx >= 0 && idx < boundingBoxes.length) { 139 bbox = boundingBoxes[idx]; 140 } 141 } else if (this.additionalEncodings != null) { 142 int encodingIndex = (glyphIndex / 256) - 1; 143 SimpleSingleByteEncoding encoding = getAdditionalEncoding(encodingIndex); 144 int codePoint = glyphIndex % 256; 145 NamedCharacter nc = encoding.getCharacterForIndex(codePoint); 146 UnencodedCharacter uc = this.unencodedCharacters.get(nc.getSingleUnicodeValue()); 147 bbox = uc.getBBox(); 148 } 149 return bbox == null ? null : new Rectangle(bbox.x * size, bbox.y * size, bbox.width * size, bbox.height * size); 150 } 151 152 /** 153 * Lookup a character using its alternative names. If found, cache it so we 154 * can speed up lookups. 155 * @param c the character 156 * @return the suggested alternative character present in the font 157 */ findAlternative(char c)158 private char findAlternative(char c) { 159 char d; 160 if (alternativeCodes == null) { 161 alternativeCodes = new java.util.HashMap<Character, Character>(); 162 } else { 163 Character alternative = alternativeCodes.get(c); 164 if (alternative != null) { 165 return alternative; 166 } 167 } 168 String charName = Glyphs.charToGlyphName(c); 169 String[] charNameAlternatives = Glyphs.getCharNameAlternativesFor(charName); 170 if (charNameAlternatives != null && charNameAlternatives.length > 0) { 171 for (String charNameAlternative : charNameAlternatives) { 172 if (log.isDebugEnabled()) { 173 log.debug("Checking alternative for char " + c + " (charname=" 174 + charName + "): " + charNameAlternative); 175 } 176 String s = Glyphs.getUnicodeSequenceForGlyphName(charNameAlternative); 177 if (s != null) { 178 d = lookupChar(s.charAt(0)); 179 if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) { 180 alternativeCodes.put(c, d); 181 return d; 182 } 183 } 184 } 185 } 186 187 return SingleByteEncoding.NOT_FOUND_CODE_POINT; 188 } 189 lookupChar(char c)190 private char lookupChar(char c) { 191 char d = mapping.mapChar(c); 192 if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) { 193 return d; 194 } 195 196 // Check unencoded characters which are available in the font by 197 // character name 198 d = mapUnencodedChar(c); 199 return d; 200 } 201 isSubset()202 private boolean isSubset() { 203 return getEmbeddingMode() == EmbeddingMode.SUBSET; 204 } 205 206 /** {@inheritDoc} */ 207 @Override mapChar(char c)208 public char mapChar(char c) { 209 notifyMapOperation(); 210 char d = lookupChar(c); 211 if (d == SingleByteEncoding.NOT_FOUND_CODE_POINT) { 212 // Check for alternative 213 d = findAlternative(c); 214 if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) { 215 return d; 216 } else { 217 this.warnMissingGlyph(c); 218 return Typeface.NOT_FOUND; 219 } 220 } 221 if (isEmbeddable() && isSubset()) { 222 mapChar(d, c); 223 } 224 return d; 225 } 226 mapChar(int glyphIndex, char unicode)227 private int mapChar(int glyphIndex, char unicode) { 228 // Reencode to a new subset font or get the reencoded value 229 // IOW, accumulate the accessed characters and build a character map for them 230 Integer subsetCharSelector = usedGlyphs.get(glyphIndex); 231 if (subsetCharSelector == null) { 232 int selector = usedGlyphsCount; 233 usedGlyphs.put(glyphIndex, selector); 234 usedCharsIndex.put(selector, unicode); 235 charGIDMappings.put(unicode, glyphIndex); 236 usedGlyphsCount++; 237 return selector; 238 } else { 239 return subsetCharSelector; 240 } 241 } 242 getUnicode(int index)243 private char getUnicode(int index) { 244 Character mapValue = usedCharsIndex.get(index); 245 if (mapValue != null) { 246 return mapValue; 247 } else { 248 return CharUtilities.NOT_A_CHARACTER; 249 } 250 } 251 252 /** {@inheritDoc} */ 253 @Override hasChar(char c)254 public boolean hasChar(char c) { 255 char d = mapping.mapChar(c); 256 if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) { 257 return true; 258 } 259 //Check unencoded characters which are available in the font by character name 260 d = mapUnencodedChar(c); 261 if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) { 262 return true; 263 } 264 // Check if an alternative exists 265 d = findAlternative(c); 266 if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) { 267 return true; 268 } 269 return false; 270 } 271 272 /* ---- single byte font specific setters --- */ 273 274 /** 275 * Updates the mapping variable based on the encoding. 276 * @param encoding the name of the encoding 277 */ updateMapping(String encoding)278 protected void updateMapping(String encoding) { 279 try { 280 this.mapping = CodePointMapping.getMapping(encoding); 281 } catch (UnsupportedOperationException e) { 282 log.error("Font '" + super.getFontName() + "': " + e.getMessage()); 283 } 284 } 285 286 /** 287 * Sets the encoding of the font. 288 * @param encoding the encoding (ex. "WinAnsiEncoding" or "SymbolEncoding") 289 */ setEncoding(String encoding)290 public void setEncoding(String encoding) { 291 updateMapping(encoding); 292 } 293 294 /** 295 * Sets the encoding of the font. 296 * @param encoding the encoding information 297 */ setEncoding(CodePointMapping encoding)298 public void setEncoding(CodePointMapping encoding) { 299 this.mapping = encoding; 300 } 301 302 /** 303 * Controls whether the font is configured to use its native encoding or if it 304 * may need to be re-encoded for the target format. 305 * @param value true indicates that the configured encoding is the font's native encoding 306 */ setUseNativeEncoding(boolean value)307 public void setUseNativeEncoding(boolean value) { 308 this.useNativeEncoding = value; 309 } 310 311 /** 312 * Indicates whether this font is configured to use its native encoding. This 313 * method is used to determine whether the font needs to be re-encoded. 314 * @return true if the font uses its native encoding. 315 */ isUsingNativeEncoding()316 public boolean isUsingNativeEncoding() { 317 return this.useNativeEncoding; 318 } 319 320 /** 321 * Sets a width for a character. 322 * @param index index of the character 323 * @param w the width of the character 324 */ setWidth(int index, int w)325 public void setWidth(int index, int w) { 326 if (this.width == null) { 327 this.width = new int[getLastChar() - getFirstChar() + 1]; 328 } 329 this.width[index - getFirstChar()] = w; 330 } 331 setBoundingBox(int index, Rectangle bbox)332 public void setBoundingBox(int index, Rectangle bbox) { 333 if (this.boundingBoxes == null) { 334 this.boundingBoxes = new Rectangle[getLastChar() - getFirstChar() + 1]; 335 } 336 this.boundingBoxes[index - getFirstChar()] = bbox; 337 } 338 339 /** 340 * Adds an unencoded character (one that is not supported by the primary encoding). 341 * @param ch the named character 342 * @param width the width of the character 343 */ addUnencodedCharacter(NamedCharacter ch, int width, Rectangle bbox)344 public void addUnencodedCharacter(NamedCharacter ch, int width, Rectangle bbox) { 345 if (this.unencodedCharacters == null) { 346 this.unencodedCharacters = new HashMap<Character, UnencodedCharacter>(); 347 } 348 if (ch.hasSingleUnicodeValue()) { 349 UnencodedCharacter uc = new UnencodedCharacter(ch, width, bbox); 350 this.unencodedCharacters.put(ch.getSingleUnicodeValue(), uc); 351 } else { 352 //Cannot deal with unicode sequences, so ignore this character 353 } 354 } 355 356 /** 357 * Makes all unencoded characters available through additional encodings. This method 358 * is used in cases where the fonts need to be encoded in the target format before 359 * all text of the document is processed (for example in PostScript when resource optimization 360 * is disabled). 361 */ encodeAllUnencodedCharacters()362 public void encodeAllUnencodedCharacters() { 363 if (this.unencodedCharacters != null) { 364 Set<Character> sortedKeys = new TreeSet<Character>(this.unencodedCharacters.keySet()); 365 for (Character ch : sortedKeys) { 366 char mapped = mapChar(ch); 367 assert mapped != Typeface.NOT_FOUND; 368 } 369 } 370 } 371 372 /** 373 * Returns an array with the widths for an additional encoding. 374 * @param index the index of the additional encoding 375 * @return the width array 376 */ getAdditionalWidths(int index)377 public int[] getAdditionalWidths(int index) { 378 SimpleSingleByteEncoding enc = getAdditionalEncoding(index); 379 int[] arr = new int[enc.getLastChar() - enc.getFirstChar() + 1]; 380 for (int i = 0, c = arr.length; i < c; i++) { 381 NamedCharacter nc = enc.getCharacterForIndex(enc.getFirstChar() + i); 382 UnencodedCharacter uc = this.unencodedCharacters.get( 383 nc.getSingleUnicodeValue()); 384 arr[i] = uc.getWidth(); 385 } 386 return arr; 387 } 388 389 protected static final class UnencodedCharacter { 390 391 private final NamedCharacter character; 392 private final int width; 393 private final Rectangle bbox; 394 UnencodedCharacter(NamedCharacter character, int width, Rectangle bbox)395 public UnencodedCharacter(NamedCharacter character, int width, Rectangle bbox) { 396 this.character = character; 397 this.width = width; 398 this.bbox = bbox; 399 } 400 getCharacter()401 public NamedCharacter getCharacter() { 402 return this.character; 403 } 404 getWidth()405 public int getWidth() { 406 return this.width; 407 } 408 getBBox()409 public Rectangle getBBox() { 410 return bbox; 411 } 412 413 /** {@inheritDoc} */ 414 @Override toString()415 public String toString() { 416 return getCharacter().toString(); 417 } 418 } 419 420 /** 421 * Sets the version of the PostScript table stored in the TrueType font represented by 422 * this instance. 423 * 424 * @param version version of the post table 425 */ setTrueTypePostScriptVersion(PostScriptVersion version)426 public void setTrueTypePostScriptVersion(PostScriptVersion version) { 427 ttPostScriptVersion = version; 428 } 429 430 /** 431 * Returns the version of the PostScript table stored in the TrueType font represented by 432 * this instance. 433 * 434 * @return the version of the post table 435 */ getTrueTypePostScriptVersion()436 public PostScriptVersion getTrueTypePostScriptVersion() { 437 assert getFontType() == FontType.TRUETYPE; 438 return ttPostScriptVersion; 439 } 440 441 /** 442 * Returns a Map of used Glyphs. 443 * @return Map Map of used Glyphs 444 */ getUsedGlyphs()445 public Map<Integer, Integer> getUsedGlyphs() { 446 return Collections.unmodifiableMap(usedGlyphs); 447 } 448 getUnicodeFromSelector(int selector)449 public char getUnicodeFromSelector(int selector) { 450 return getUnicode(selector); 451 } 452 getGIDFromChar(char ch)453 public int getGIDFromChar(char ch) { 454 return charGIDMappings.get(ch); 455 } 456 getUnicodeFromGID(int glyphIndex)457 public char getUnicodeFromGID(int glyphIndex) { 458 int selector = usedGlyphs.get(glyphIndex); 459 return usedCharsIndex.get(selector); 460 } 461 mapUsedGlyphName(int gid, String value)462 public void mapUsedGlyphName(int gid, String value) { 463 usedGlyphNames.put(gid, value); 464 } 465 getUsedGlyphNames()466 public Map<Integer, String> getUsedGlyphNames() { 467 return usedGlyphNames; 468 } 469 getGlyphName(int idx)470 public String getGlyphName(int idx) { 471 if (idx < mapping.getCharNameMap().length) { 472 return mapping.getCharNameMap()[idx]; 473 } else { 474 int selector = usedGlyphs.get(idx); 475 char theChar = usedCharsIndex.get(selector); 476 return unencodedCharacters.get(theChar).getCharacter().getName(); 477 } 478 } 479 } 480 481