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

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