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

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