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

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