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.

SingleByteFont.java 16KB

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