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 18KB

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