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.

MultiByteFont.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  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.nio.CharBuffer;
  21. import java.nio.IntBuffer;
  22. import java.util.BitSet;
  23. import java.util.LinkedHashMap;
  24. import java.util.Map;
  25. import org.apache.commons.logging.Log;
  26. import org.apache.commons.logging.LogFactory;
  27. import org.apache.fop.apps.io.InternalResourceResolver;
  28. import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
  29. import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
  30. import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable;
  31. import org.apache.fop.complexscripts.fonts.Positionable;
  32. import org.apache.fop.complexscripts.fonts.Substitutable;
  33. import org.apache.fop.complexscripts.util.GlyphSequence;
  34. import org.apache.fop.util.CharUtilities;
  35. /**
  36. * Generic MultiByte (CID) font
  37. */
  38. public class MultiByteFont extends CIDFont implements Substitutable, Positionable {
  39. /** logging instance */
  40. private static final Log log
  41. = LogFactory.getLog(MultiByteFont.class);
  42. private String ttcName;
  43. private String encoding = "Identity-H";
  44. private int defaultWidth;
  45. private CIDFontType cidType = CIDFontType.CIDTYPE2;
  46. private final CIDSet cidSet;
  47. /* advanced typographic support */
  48. private GlyphDefinitionTable gdef;
  49. private GlyphSubstitutionTable gsub;
  50. private GlyphPositioningTable gpos;
  51. /* dynamic private use (character) mappings */
  52. private int numMapped;
  53. private int numUnmapped;
  54. private int nextPrivateUse = 0xE000;
  55. private int firstPrivate;
  56. private int lastPrivate;
  57. private int firstUnmapped;
  58. private int lastUnmapped;
  59. /** Contains the character bounding boxes for all characters in the font */
  60. protected Rectangle[] boundingBoxes;
  61. private boolean isOTFFile = false;
  62. // since for most users the most likely glyphs are in the first cmap segments we store their mapping.
  63. private static final int NUM_MOST_LIKELY_GLYPHS = 256;
  64. private int[] mostLikelyGlyphs = new int[NUM_MOST_LIKELY_GLYPHS];
  65. //A map to store each used glyph from the CID set against the glyph name.
  66. private LinkedHashMap<Integer, String> usedGlyphNames = new LinkedHashMap<Integer, String>();
  67. /**
  68. * Default constructor
  69. */
  70. public MultiByteFont(InternalResourceResolver resourceResolver, EmbeddingMode embeddingMode) {
  71. super(resourceResolver);
  72. setFontType(FontType.TYPE0);
  73. setEmbeddingMode(embeddingMode);
  74. if (embeddingMode != EmbeddingMode.FULL) {
  75. cidSet = new CIDSubset(this);
  76. } else {
  77. cidSet = new CIDFull(this);
  78. }
  79. }
  80. /** {@inheritDoc} */
  81. @Override
  82. public int getDefaultWidth() {
  83. return defaultWidth;
  84. }
  85. /** {@inheritDoc} */
  86. @Override
  87. public String getRegistry() {
  88. return "Adobe";
  89. }
  90. /** {@inheritDoc} */
  91. @Override
  92. public String getOrdering() {
  93. return "UCS";
  94. }
  95. /** {@inheritDoc} */
  96. @Override
  97. public int getSupplement() {
  98. return 0;
  99. }
  100. /** {@inheritDoc} */
  101. @Override
  102. public CIDFontType getCIDType() {
  103. return cidType;
  104. }
  105. public void setIsOTFFile(boolean isOTFFile) {
  106. this.isOTFFile = isOTFFile;
  107. }
  108. public boolean isOTFFile() {
  109. return this.isOTFFile;
  110. }
  111. /**
  112. * Sets the CIDType.
  113. * @param cidType The cidType to set
  114. */
  115. public void setCIDType(CIDFontType cidType) {
  116. this.cidType = cidType;
  117. }
  118. /** {@inheritDoc} */
  119. @Override
  120. public String getEmbedFontName() {
  121. if (isEmbeddable()) {
  122. return FontUtil.stripWhiteSpace(super.getFontName());
  123. } else {
  124. return super.getFontName();
  125. }
  126. }
  127. /** {@inheritDoc} */
  128. public boolean isEmbeddable() {
  129. return !(getEmbedFileURI() == null && getEmbedResourceName() == null);
  130. }
  131. public boolean isSubsetEmbedded() {
  132. if (getEmbeddingMode() == EmbeddingMode.FULL) {
  133. return false;
  134. }
  135. return true;
  136. }
  137. /** {@inheritDoc} */
  138. @Override
  139. public CIDSet getCIDSet() {
  140. return this.cidSet;
  141. }
  142. public void mapUsedGlyphName(int gid, String value) {
  143. usedGlyphNames.put(gid, value);
  144. }
  145. public LinkedHashMap<Integer, String> getUsedGlyphNames() {
  146. return usedGlyphNames;
  147. }
  148. /** {@inheritDoc} */
  149. @Override
  150. public String getEncodingName() {
  151. return encoding;
  152. }
  153. /** {@inheritDoc} */
  154. public int getWidth(int i, int size) {
  155. if (isEmbeddable()) {
  156. int glyphIndex = cidSet.getOriginalGlyphIndex(i);
  157. return size * width[glyphIndex];
  158. } else {
  159. return size * width[i];
  160. }
  161. }
  162. /** {@inheritDoc} */
  163. public int[] getWidths() {
  164. int[] arr = new int[width.length];
  165. System.arraycopy(width, 0, arr, 0, width.length);
  166. return arr;
  167. }
  168. public Rectangle getBoundingBox(int glyphIndex, int size) {
  169. int index = isEmbeddable() ? cidSet.getOriginalGlyphIndex(glyphIndex) : glyphIndex;
  170. Rectangle bbox = boundingBoxes[index];
  171. return new Rectangle(bbox.x * size, bbox.y * size, bbox.width * size, bbox.height * size);
  172. }
  173. /**
  174. * Returns the glyph index for a Unicode character. The method returns 0 if there's no
  175. * such glyph in the character map.
  176. * @param c the Unicode character index
  177. * @return the glyph index (or 0 if the glyph is not available)
  178. */
  179. // [TBD] - needs optimization, i.e., change from linear search to binary search
  180. public int findGlyphIndex(int c) {
  181. int idx = c;
  182. int retIdx = SingleByteEncoding.NOT_FOUND_CODE_POINT;
  183. // for most users the most likely glyphs are in the first cmap segments (meaning the one with
  184. // the lowest unicode start values)
  185. if (idx < NUM_MOST_LIKELY_GLYPHS && mostLikelyGlyphs[idx] != 0) {
  186. return mostLikelyGlyphs[idx];
  187. }
  188. for (int i = 0; (i < cmap.length) && retIdx == 0; i++) {
  189. if (cmap[i].getUnicodeStart() <= idx
  190. && cmap[i].getUnicodeEnd() >= idx) {
  191. retIdx = cmap[i].getGlyphStartIndex()
  192. + idx
  193. - cmap[i].getUnicodeStart();
  194. if (idx < NUM_MOST_LIKELY_GLYPHS) {
  195. mostLikelyGlyphs[idx] = retIdx;
  196. }
  197. }
  198. }
  199. return retIdx;
  200. }
  201. /**
  202. * Add a private use mapping {PU,GI} to the existing character map.
  203. * N.B. Does not insert in order, merely appends to end of existing map.
  204. */
  205. private synchronized void addPrivateUseMapping(int pu, int gi) {
  206. assert findGlyphIndex(pu) == SingleByteEncoding.NOT_FOUND_CODE_POINT;
  207. CMapSegment[] oldCmap = cmap;
  208. int cmapLength = oldCmap.length;
  209. CMapSegment[] newCmap = new CMapSegment [ cmapLength + 1 ];
  210. System.arraycopy(oldCmap, 0, newCmap, 0, cmapLength);
  211. newCmap [ cmapLength ] = new CMapSegment(pu, pu, gi);
  212. cmap = newCmap;
  213. }
  214. /**
  215. * Given a glyph index, create a new private use mapping, augmenting the bfentries
  216. * table. This is needed to accommodate the presence of an (output) glyph index in a
  217. * complex script glyph substitution that does not correspond to a character in the
  218. * font's CMAP. The creation of such private use mappings is deferred until an
  219. * attempt is actually made to perform the reverse lookup from the glyph index. This
  220. * is necessary in order to avoid exhausting the private use space on fonts containing
  221. * many such non-mapped glyph indices, if these mappings had been created statically
  222. * at font load time.
  223. * @param gi glyph index
  224. * @returns unicode scalar value
  225. */
  226. private int createPrivateUseMapping(int gi) {
  227. while ((nextPrivateUse < 0xF900)
  228. && (findGlyphIndex(nextPrivateUse) != SingleByteEncoding.NOT_FOUND_CODE_POINT)) {
  229. nextPrivateUse++;
  230. }
  231. if (nextPrivateUse < 0xF900) {
  232. int pu = nextPrivateUse;
  233. addPrivateUseMapping(pu, gi);
  234. if (firstPrivate == 0) {
  235. firstPrivate = pu;
  236. }
  237. lastPrivate = pu;
  238. numMapped++;
  239. if (log.isDebugEnabled()) {
  240. log.debug("Create private use mapping from "
  241. + CharUtilities.format(pu)
  242. + " to glyph index " + gi
  243. + " in font '" + getFullName() + "'");
  244. }
  245. return pu;
  246. } else {
  247. if (firstUnmapped == 0) {
  248. firstUnmapped = gi;
  249. }
  250. lastUnmapped = gi;
  251. numUnmapped++;
  252. log.warn("Exhausted private use area: unable to map "
  253. + numUnmapped + " glyphs in glyph index range ["
  254. + firstUnmapped + "," + lastUnmapped
  255. + "] (inclusive) of font '" + getFullName() + "'");
  256. return 0;
  257. }
  258. }
  259. /**
  260. * Returns the Unicode scalar value that corresponds to the glyph index. If more than
  261. * one correspondence exists, then the first one is returned (ordered by bfentries[]).
  262. * @param gi glyph index
  263. * @returns unicode scalar value
  264. */
  265. // [TBD] - needs optimization, i.e., change from linear search to binary search
  266. private int findCharacterFromGlyphIndex(int gi, boolean augment) {
  267. int cc = 0;
  268. for (int i = 0, n = cmap.length; i < n; i++) {
  269. CMapSegment segment = cmap [ i ];
  270. int s = segment.getGlyphStartIndex();
  271. int e = s + (segment.getUnicodeEnd() - segment.getUnicodeStart());
  272. if ((gi >= s) && (gi <= e)) {
  273. cc = segment.getUnicodeStart() + (gi - s);
  274. break;
  275. }
  276. }
  277. if ((cc == 0) && augment) {
  278. cc = createPrivateUseMapping(gi);
  279. }
  280. return cc;
  281. }
  282. private int findCharacterFromGlyphIndex(int gi) {
  283. return findCharacterFromGlyphIndex(gi, true);
  284. }
  285. protected BitSet getGlyphIndices() {
  286. BitSet bitset = new BitSet();
  287. bitset.set(0);
  288. bitset.set(1);
  289. bitset.set(2);
  290. for (int i = 0; i < cmap.length; i++) {
  291. int start = cmap[i].getUnicodeStart();
  292. int end = cmap[i].getUnicodeEnd();
  293. int glyphIndex = cmap[i].getGlyphStartIndex();
  294. while (start++ < end + 1) {
  295. bitset.set(glyphIndex++);
  296. }
  297. }
  298. return bitset;
  299. }
  300. protected char[] getChars() {
  301. // the width array is set when the font is built
  302. char[] chars = new char[width.length];
  303. for (int i = 0; i < cmap.length; i++) {
  304. int start = cmap[i].getUnicodeStart();
  305. int end = cmap[i].getUnicodeEnd();
  306. int glyphIndex = cmap[i].getGlyphStartIndex();
  307. while (start < end + 1) {
  308. chars[glyphIndex++] = (char) start++;
  309. }
  310. }
  311. return chars;
  312. }
  313. /** {@inheritDoc} */
  314. @Override
  315. public char mapChar(char c) {
  316. notifyMapOperation();
  317. int glyphIndex = findGlyphIndex(c);
  318. if (glyphIndex == SingleByteEncoding.NOT_FOUND_CODE_POINT) {
  319. warnMissingGlyph(c);
  320. if (!isOTFFile) {
  321. glyphIndex = findGlyphIndex(Typeface.NOT_FOUND);
  322. }
  323. }
  324. if (isEmbeddable()) {
  325. glyphIndex = cidSet.mapChar(glyphIndex, c);
  326. }
  327. return (char) glyphIndex;
  328. }
  329. /** {@inheritDoc} */
  330. @Override
  331. public boolean hasChar(char c) {
  332. return (findGlyphIndex(c) != SingleByteEncoding.NOT_FOUND_CODE_POINT);
  333. }
  334. /**
  335. * Sets the defaultWidth.
  336. * @param defaultWidth The defaultWidth to set
  337. */
  338. public void setDefaultWidth(int defaultWidth) {
  339. this.defaultWidth = defaultWidth;
  340. }
  341. /**
  342. * Returns the TrueType Collection Name.
  343. * @return the TrueType Collection Name
  344. */
  345. public String getTTCName() {
  346. return ttcName;
  347. }
  348. /**
  349. * Sets the the TrueType Collection Name.
  350. * @param ttcName the TrueType Collection Name
  351. */
  352. public void setTTCName(String ttcName) {
  353. this.ttcName = ttcName;
  354. }
  355. /**
  356. * Sets the width array.
  357. * @param wds array of widths.
  358. */
  359. public void setWidthArray(int[] wds) {
  360. this.width = wds;
  361. }
  362. /**
  363. * Sets the bounding boxes array.
  364. * @param boundingBoxes array of bounding boxes.
  365. */
  366. public void setBBoxArray(Rectangle[] boundingBoxes) {
  367. this.boundingBoxes = boundingBoxes;
  368. }
  369. /**
  370. * Returns a Map of used Glyphs.
  371. * @return Map Map of used Glyphs
  372. */
  373. public Map<Integer, Integer> getUsedGlyphs() {
  374. return cidSet.getGlyphs();
  375. }
  376. /**
  377. * Establishes the glyph definition table.
  378. * @param gdef the glyph definition table to be used by this font
  379. */
  380. public void setGDEF(GlyphDefinitionTable gdef) {
  381. if ((this.gdef == null) || (gdef == null)) {
  382. this.gdef = gdef;
  383. } else {
  384. throw new IllegalStateException("font already associated with GDEF table");
  385. }
  386. }
  387. /**
  388. * Obtain glyph definition table.
  389. * @return glyph definition table or null if none is associated with font
  390. */
  391. public GlyphDefinitionTable getGDEF() {
  392. return gdef;
  393. }
  394. /**
  395. * Establishes the glyph substitution table.
  396. * @param gsub the glyph substitution table to be used by this font
  397. */
  398. public void setGSUB(GlyphSubstitutionTable gsub) {
  399. if ((this.gsub == null) || (gsub == null)) {
  400. this.gsub = gsub;
  401. } else {
  402. throw new IllegalStateException("font already associated with GSUB table");
  403. }
  404. }
  405. /**
  406. * Obtain glyph substitution table.
  407. * @return glyph substitution table or null if none is associated with font
  408. */
  409. public GlyphSubstitutionTable getGSUB() {
  410. return gsub;
  411. }
  412. /**
  413. * Establishes the glyph positioning table.
  414. * @param gpos the glyph positioning table to be used by this font
  415. */
  416. public void setGPOS(GlyphPositioningTable gpos) {
  417. if ((this.gpos == null) || (gpos == null)) {
  418. this.gpos = gpos;
  419. } else {
  420. throw new IllegalStateException("font already associated with GPOS table");
  421. }
  422. }
  423. /**
  424. * Obtain glyph positioning table.
  425. * @return glyph positioning table or null if none is associated with font
  426. */
  427. public GlyphPositioningTable getGPOS() {
  428. return gpos;
  429. }
  430. /** {@inheritDoc} */
  431. public boolean performsSubstitution() {
  432. return gsub != null;
  433. }
  434. /** {@inheritDoc} */
  435. public CharSequence performSubstitution(CharSequence cs, String script, String language) {
  436. if (gsub != null) {
  437. GlyphSequence igs = mapCharsToGlyphs(cs);
  438. GlyphSequence ogs = gsub.substitute(igs, script, language);
  439. CharSequence ocs = mapGlyphsToChars(ogs);
  440. return ocs;
  441. } else {
  442. return cs;
  443. }
  444. }
  445. /** {@inheritDoc} */
  446. public CharSequence reorderCombiningMarks(
  447. CharSequence cs, int[][] gpa, String script, String language) {
  448. if (gdef != null) {
  449. GlyphSequence igs = mapCharsToGlyphs(cs);
  450. GlyphSequence ogs = gdef.reorderCombiningMarks(igs, gpa, script, language);
  451. CharSequence ocs = mapGlyphsToChars(ogs);
  452. return ocs;
  453. } else {
  454. return cs;
  455. }
  456. }
  457. /** {@inheritDoc} */
  458. public boolean performsPositioning() {
  459. return gpos != null;
  460. }
  461. /** {@inheritDoc} */
  462. public int[][]
  463. performPositioning(CharSequence cs, String script, String language, int fontSize) {
  464. if (gpos != null) {
  465. GlyphSequence gs = mapCharsToGlyphs(cs);
  466. int[][] adjustments = new int [ gs.getGlyphCount() ] [ 4 ];
  467. if (gpos.position(gs, script, language, fontSize, this.width, adjustments)) {
  468. return scaleAdjustments(adjustments, fontSize);
  469. } else {
  470. return null;
  471. }
  472. } else {
  473. return null;
  474. }
  475. }
  476. /** {@inheritDoc} */
  477. public int[][] performPositioning(CharSequence cs, String script, String language) {
  478. throw new UnsupportedOperationException();
  479. }
  480. private int[][] scaleAdjustments(int[][] adjustments, int fontSize) {
  481. if (adjustments != null) {
  482. for (int i = 0, n = adjustments.length; i < n; i++) {
  483. int[] gpa = adjustments [ i ];
  484. for (int k = 0; k < 4; k++) {
  485. gpa [ k ] = (gpa [ k ] * fontSize) / 1000;
  486. }
  487. }
  488. return adjustments;
  489. } else {
  490. return null;
  491. }
  492. }
  493. /**
  494. * Map sequence CS, comprising a sequence of UTF-16 encoded Unicode Code Points, to
  495. * an output character sequence GS, comprising a sequence of Glyph Indices. N.B. Unlike
  496. * mapChar(), this method does not make use of embedded subset encodings.
  497. * @param cs a CharSequence containing UTF-16 encoded Unicode characters
  498. * @returns a CharSequence containing glyph indices
  499. */
  500. private GlyphSequence mapCharsToGlyphs(CharSequence cs) {
  501. IntBuffer cb = IntBuffer.allocate(cs.length());
  502. IntBuffer gb = IntBuffer.allocate(cs.length());
  503. int gi;
  504. int giMissing = findGlyphIndex(Typeface.NOT_FOUND);
  505. for (int i = 0, n = cs.length(); i < n; i++) {
  506. int cc = cs.charAt(i);
  507. if ((cc >= 0xD800) && (cc < 0xDC00)) {
  508. if ((i + 1) < n) {
  509. int sh = cc;
  510. int sl = cs.charAt(++i);
  511. if ((sl >= 0xDC00) && (sl < 0xE000)) {
  512. cc = 0x10000 + ((sh - 0xD800) << 10) + ((sl - 0xDC00) << 0);
  513. } else {
  514. throw new IllegalArgumentException(
  515. "ill-formed UTF-16 sequence, "
  516. + "contains isolated high surrogate at index " + i);
  517. }
  518. } else {
  519. throw new IllegalArgumentException(
  520. "ill-formed UTF-16 sequence, "
  521. + "contains isolated high surrogate at end of sequence");
  522. }
  523. } else if ((cc >= 0xDC00) && (cc < 0xE000)) {
  524. throw new IllegalArgumentException(
  525. "ill-formed UTF-16 sequence, "
  526. + "contains isolated low surrogate at index " + i);
  527. }
  528. notifyMapOperation();
  529. gi = findGlyphIndex(cc);
  530. if (gi == SingleByteEncoding.NOT_FOUND_CODE_POINT) {
  531. warnMissingGlyph((char) cc);
  532. gi = giMissing;
  533. }
  534. cb.put(cc);
  535. gb.put(gi);
  536. }
  537. cb.flip();
  538. gb.flip();
  539. return new GlyphSequence(cb, gb, null);
  540. }
  541. /**
  542. * Map sequence GS, comprising a sequence of Glyph Indices, to output sequence CS,
  543. * comprising a sequence of UTF-16 encoded Unicode Code Points.
  544. * @param gs a GlyphSequence containing glyph indices
  545. * @returns a CharSequence containing UTF-16 encoded Unicode characters
  546. */
  547. private CharSequence mapGlyphsToChars(GlyphSequence gs) {
  548. int ng = gs.getGlyphCount();
  549. CharBuffer cb = CharBuffer.allocate(ng);
  550. int ccMissing = Typeface.NOT_FOUND;
  551. for (int i = 0, n = ng; i < n; i++) {
  552. int gi = gs.getGlyph(i);
  553. int cc = findCharacterFromGlyphIndex(gi);
  554. if ((cc == 0) || (cc > 0x10FFFF)) {
  555. cc = ccMissing;
  556. log.warn("Unable to map glyph index " + gi
  557. + " to Unicode scalar in font '"
  558. + getFullName() + "', substituting missing character '"
  559. + (char) cc + "'");
  560. }
  561. if (cc > 0x00FFFF) {
  562. int sh;
  563. int sl;
  564. cc -= 0x10000;
  565. sh = ((cc >> 10) & 0x3FF) + 0xD800;
  566. sl = ((cc >> 0) & 0x3FF) + 0xDC00;
  567. cb.put((char) sh);
  568. cb.put((char) sl);
  569. } else {
  570. cb.put((char) cc);
  571. }
  572. }
  573. cb.flip();
  574. return cb;
  575. }
  576. }