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

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