Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

MultiByteFont.java 26KB

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