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

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