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.

GlyphSubstitutionTable.java 64KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482
  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.complexscripts.fonts;
  19. import java.util.ArrayList;
  20. import java.util.Iterator;
  21. import java.util.List;
  22. import java.util.Map;
  23. import org.apache.commons.logging.Log;
  24. import org.apache.commons.logging.LogFactory;
  25. import org.apache.fop.complexscripts.scripts.ScriptProcessor;
  26. import org.apache.fop.complexscripts.util.CharAssociation;
  27. import org.apache.fop.complexscripts.util.GlyphSequence;
  28. import org.apache.fop.complexscripts.util.GlyphTester;
  29. // CSOFF: LineLengthCheck
  30. /**
  31. * <p>The <code>GlyphSubstitutionTable</code> class is a glyph table that implements
  32. * <code>GlyphSubstitution</code> functionality.</p>
  33. *
  34. * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
  35. */
  36. public class GlyphSubstitutionTable extends GlyphTable {
  37. /** logging instance */
  38. private static final Log log = LogFactory.getLog(GlyphSubstitutionTable.class);
  39. /** single substitution subtable type */
  40. public static final int GSUB_LOOKUP_TYPE_SINGLE = 1;
  41. /** multiple substitution subtable type */
  42. public static final int GSUB_LOOKUP_TYPE_MULTIPLE = 2;
  43. /** alternate substitution subtable type */
  44. public static final int GSUB_LOOKUP_TYPE_ALTERNATE = 3;
  45. /** ligature substitution subtable type */
  46. public static final int GSUB_LOOKUP_TYPE_LIGATURE = 4;
  47. /** contextual substitution subtable type */
  48. public static final int GSUB_LOOKUP_TYPE_CONTEXTUAL = 5;
  49. /** chained contextual substitution subtable type */
  50. public static final int GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL = 6;
  51. /** extension substitution substitution subtable type */
  52. public static final int GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION = 7;
  53. /** reverse chained contextual single substitution subtable type */
  54. public static final int GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE = 8;
  55. /**
  56. * Instantiate a <code>GlyphSubstitutionTable</code> object using the specified lookups
  57. * and subtables.
  58. * @param gdef glyph definition table that applies
  59. * @param lookups a map of lookup specifications to subtable identifier strings
  60. * @param subtables a list of identified subtables
  61. */
  62. public GlyphSubstitutionTable(GlyphDefinitionTable gdef, Map lookups, List subtables) {
  63. super(gdef, lookups);
  64. if ((subtables == null) || (subtables.size() == 0)) {
  65. throw new AdvancedTypographicTableFormatException("subtables must be non-empty");
  66. } else {
  67. for (Iterator it = subtables.iterator(); it.hasNext();) {
  68. Object o = it.next();
  69. if (o instanceof GlyphSubstitutionSubtable) {
  70. addSubtable((GlyphSubtable) o);
  71. } else {
  72. throw new AdvancedTypographicTableFormatException("subtable must be a glyph substitution subtable");
  73. }
  74. }
  75. freezeSubtables();
  76. }
  77. }
  78. /**
  79. * Perform substitution processing using all matching lookups.
  80. * @param gs an input glyph sequence
  81. * @param script a script identifier
  82. * @param language a language identifier
  83. * @return the substituted (output) glyph sequence
  84. */
  85. public GlyphSequence substitute(GlyphSequence gs, String script, String language) {
  86. GlyphSequence ogs;
  87. Map/*<LookupSpec,List<LookupTable>>*/ lookups = matchLookups(script, language, "*");
  88. if ((lookups != null) && (lookups.size() > 0)) {
  89. ScriptProcessor sp = ScriptProcessor.getInstance(script);
  90. ogs = sp.substitute(this, gs, script, language, lookups);
  91. } else {
  92. ogs = gs;
  93. }
  94. return ogs;
  95. }
  96. /**
  97. * Map a lookup type name to its constant (integer) value.
  98. * @param name lookup type name
  99. * @return lookup type
  100. */
  101. public static int getLookupTypeFromName(String name) {
  102. int t;
  103. String s = name.toLowerCase();
  104. if ("single".equals(s)) {
  105. t = GSUB_LOOKUP_TYPE_SINGLE;
  106. } else if ("multiple".equals(s)) {
  107. t = GSUB_LOOKUP_TYPE_MULTIPLE;
  108. } else if ("alternate".equals(s)) {
  109. t = GSUB_LOOKUP_TYPE_ALTERNATE;
  110. } else if ("ligature".equals(s)) {
  111. t = GSUB_LOOKUP_TYPE_LIGATURE;
  112. } else if ("contextual".equals(s)) {
  113. t = GSUB_LOOKUP_TYPE_CONTEXTUAL;
  114. } else if ("chainedcontextual".equals(s)) {
  115. t = GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL;
  116. } else if ("extensionsubstitution".equals(s)) {
  117. t = GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION;
  118. } else if ("reversechainiingcontextualsingle".equals(s)) {
  119. t = GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE;
  120. } else {
  121. t = -1;
  122. }
  123. return t;
  124. }
  125. /**
  126. * Map a lookup type constant (integer) value to its name.
  127. * @param type lookup type
  128. * @return lookup type name
  129. */
  130. public static String getLookupTypeName(int type) {
  131. String tn = null;
  132. switch (type) {
  133. case GSUB_LOOKUP_TYPE_SINGLE:
  134. tn = "single";
  135. break;
  136. case GSUB_LOOKUP_TYPE_MULTIPLE:
  137. tn = "multiple";
  138. break;
  139. case GSUB_LOOKUP_TYPE_ALTERNATE:
  140. tn = "alternate";
  141. break;
  142. case GSUB_LOOKUP_TYPE_LIGATURE:
  143. tn = "ligature";
  144. break;
  145. case GSUB_LOOKUP_TYPE_CONTEXTUAL:
  146. tn = "contextual";
  147. break;
  148. case GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL:
  149. tn = "chainedcontextual";
  150. break;
  151. case GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION:
  152. tn = "extensionsubstitution";
  153. break;
  154. case GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE:
  155. tn = "reversechainiingcontextualsingle";
  156. break;
  157. default:
  158. tn = "unknown";
  159. break;
  160. }
  161. return tn;
  162. }
  163. /**
  164. * Create a substitution subtable according to the specified arguments.
  165. * @param type subtable type
  166. * @param id subtable identifier
  167. * @param sequence subtable sequence
  168. * @param flags subtable flags
  169. * @param format subtable format
  170. * @param coverage subtable coverage table
  171. * @param entries subtable entries
  172. * @return a glyph subtable instance
  173. */
  174. public static GlyphSubtable createSubtable(int type, String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  175. GlyphSubtable st = null;
  176. switch (type) {
  177. case GSUB_LOOKUP_TYPE_SINGLE:
  178. st = SingleSubtable.create(id, sequence, flags, format, coverage, entries);
  179. break;
  180. case GSUB_LOOKUP_TYPE_MULTIPLE:
  181. st = MultipleSubtable.create(id, sequence, flags, format, coverage, entries);
  182. break;
  183. case GSUB_LOOKUP_TYPE_ALTERNATE:
  184. st = AlternateSubtable.create(id, sequence, flags, format, coverage, entries);
  185. break;
  186. case GSUB_LOOKUP_TYPE_LIGATURE:
  187. st = LigatureSubtable.create(id, sequence, flags, format, coverage, entries);
  188. break;
  189. case GSUB_LOOKUP_TYPE_CONTEXTUAL:
  190. st = ContextualSubtable.create(id, sequence, flags, format, coverage, entries);
  191. break;
  192. case GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL:
  193. st = ChainedContextualSubtable.create(id, sequence, flags, format, coverage, entries);
  194. break;
  195. case GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE:
  196. st = ReverseChainedSingleSubtable.create(id, sequence, flags, format, coverage, entries);
  197. break;
  198. default:
  199. break;
  200. }
  201. return st;
  202. }
  203. /**
  204. * Create a substitution subtable according to the specified arguments.
  205. * @param type subtable type
  206. * @param id subtable identifier
  207. * @param sequence subtable sequence
  208. * @param flags subtable flags
  209. * @param format subtable format
  210. * @param coverage list of coverage table entries
  211. * @param entries subtable entries
  212. * @return a glyph subtable instance
  213. */
  214. public static GlyphSubtable createSubtable(int type, String id, int sequence, int flags, int format, List coverage, List entries) {
  215. return createSubtable(type, id, sequence, flags, format, GlyphCoverageTable.createCoverageTable(coverage), entries);
  216. }
  217. private abstract static class SingleSubtable extends GlyphSubstitutionSubtable {
  218. SingleSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  219. super(id, sequence, flags, format, coverage);
  220. }
  221. /** {@inheritDoc} */
  222. public int getType() {
  223. return GSUB_LOOKUP_TYPE_SINGLE;
  224. }
  225. /** {@inheritDoc} */
  226. public boolean isCompatible(GlyphSubtable subtable) {
  227. return subtable instanceof SingleSubtable;
  228. }
  229. /** {@inheritDoc} */
  230. public boolean substitute(GlyphSubstitutionState ss) {
  231. int gi = ss.getGlyph();
  232. int ci;
  233. if ((ci = getCoverageIndex(gi)) < 0) {
  234. return false;
  235. } else {
  236. int go = getGlyphForCoverageIndex(ci, gi);
  237. if ((go < 0) || (go > 65535)) {
  238. go = 65535;
  239. }
  240. ss.putGlyph(go, ss.getAssociation(), Boolean.TRUE);
  241. ss.consume(1);
  242. return true;
  243. }
  244. }
  245. /**
  246. * Obtain glyph for coverage index.
  247. * @param ci coverage index
  248. * @param gi original glyph index
  249. * @return substituted glyph value
  250. * @throws IllegalArgumentException if coverage index is not valid
  251. */
  252. public abstract int getGlyphForCoverageIndex(int ci, int gi) throws IllegalArgumentException;
  253. static GlyphSubstitutionSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  254. if (format == 1) {
  255. return new SingleSubtableFormat1(id, sequence, flags, format, coverage, entries);
  256. } else if (format == 2) {
  257. return new SingleSubtableFormat2(id, sequence, flags, format, coverage, entries);
  258. } else {
  259. throw new UnsupportedOperationException();
  260. }
  261. }
  262. }
  263. private static class SingleSubtableFormat1 extends SingleSubtable {
  264. private int delta;
  265. private int ciMax;
  266. SingleSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  267. super(id, sequence, flags, format, coverage, entries);
  268. populate(entries);
  269. }
  270. /** {@inheritDoc} */
  271. public List getEntries() {
  272. List entries = new ArrayList(1);
  273. entries.add(delta);
  274. return entries;
  275. }
  276. /** {@inheritDoc} */
  277. public int getGlyphForCoverageIndex(int ci, int gi) throws IllegalArgumentException {
  278. if (ci <= ciMax) {
  279. return gi + delta;
  280. } else {
  281. throw new IllegalArgumentException("coverage index " + ci + " out of range, maximum coverage index is " + ciMax);
  282. }
  283. }
  284. private void populate(List entries) {
  285. if ((entries == null) || (entries.size() != 1)) {
  286. throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null and contain exactly one entry");
  287. } else {
  288. Object o = entries.get(0);
  289. int delta = 0;
  290. if (o instanceof Integer) {
  291. delta = (Integer) o;
  292. } else {
  293. throw new AdvancedTypographicTableFormatException("illegal entries entry, must be Integer, but is: " + o);
  294. }
  295. this.delta = delta;
  296. this.ciMax = getCoverageSize() - 1;
  297. }
  298. }
  299. }
  300. private static class SingleSubtableFormat2 extends SingleSubtable {
  301. private int[] glyphs;
  302. SingleSubtableFormat2(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  303. super(id, sequence, flags, format, coverage, entries);
  304. populate(entries);
  305. }
  306. /** {@inheritDoc} */
  307. public List getEntries() {
  308. List entries = new ArrayList(glyphs.length);
  309. for (int i = 0, n = glyphs.length; i < n; i++) {
  310. entries.add(glyphs[i]);
  311. }
  312. return entries;
  313. }
  314. /** {@inheritDoc} */
  315. public int getGlyphForCoverageIndex(int ci, int gi) throws IllegalArgumentException {
  316. if (glyphs == null) {
  317. return -1;
  318. } else if (ci >= glyphs.length) {
  319. throw new IllegalArgumentException("coverage index " + ci + " out of range, maximum coverage index is " + glyphs.length);
  320. } else {
  321. return glyphs [ ci ];
  322. }
  323. }
  324. private void populate(List entries) {
  325. int i = 0;
  326. int n = entries.size();
  327. int[] glyphs = new int [ n ];
  328. for (Iterator it = entries.iterator(); it.hasNext();) {
  329. Object o = it.next();
  330. if (o instanceof Integer) {
  331. int gid = (Integer) o;
  332. if ((gid >= 0) && (gid < 65536)) {
  333. glyphs [ i++ ] = gid;
  334. } else {
  335. throw new AdvancedTypographicTableFormatException("illegal glyph index: " + gid);
  336. }
  337. } else {
  338. throw new AdvancedTypographicTableFormatException("illegal entries entry, must be Integer: " + o);
  339. }
  340. }
  341. assert i == n;
  342. assert this.glyphs == null;
  343. this.glyphs = glyphs;
  344. }
  345. }
  346. private abstract static class MultipleSubtable extends GlyphSubstitutionSubtable {
  347. public MultipleSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  348. super(id, sequence, flags, format, coverage);
  349. }
  350. /** {@inheritDoc} */
  351. public int getType() {
  352. return GSUB_LOOKUP_TYPE_MULTIPLE;
  353. }
  354. /** {@inheritDoc} */
  355. public boolean isCompatible(GlyphSubtable subtable) {
  356. return subtable instanceof MultipleSubtable;
  357. }
  358. /** {@inheritDoc} */
  359. public boolean substitute(GlyphSubstitutionState ss) {
  360. int gi = ss.getGlyph();
  361. int ci;
  362. if ((ci = getCoverageIndex(gi)) < 0) {
  363. return false;
  364. } else {
  365. int[] ga = getGlyphsForCoverageIndex(ci, gi);
  366. if (ga != null) {
  367. ss.putGlyphs(ga, CharAssociation.replicate(ss.getAssociation(), ga.length), Boolean.TRUE);
  368. ss.consume(1);
  369. }
  370. return true;
  371. }
  372. }
  373. /**
  374. * Obtain glyph sequence for coverage index.
  375. * @param ci coverage index
  376. * @param gi original glyph index
  377. * @return sequence of glyphs to substitute for input glyph
  378. * @throws IllegalArgumentException if coverage index is not valid
  379. */
  380. public abstract int[] getGlyphsForCoverageIndex(int ci, int gi) throws IllegalArgumentException;
  381. static GlyphSubstitutionSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  382. if (format == 1) {
  383. return new MultipleSubtableFormat1(id, sequence, flags, format, coverage, entries);
  384. } else {
  385. throw new UnsupportedOperationException();
  386. }
  387. }
  388. }
  389. private static class MultipleSubtableFormat1 extends MultipleSubtable {
  390. private int[][] gsa; // glyph sequence array, ordered by coverage index
  391. MultipleSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  392. super(id, sequence, flags, format, coverage, entries);
  393. populate(entries);
  394. }
  395. /** {@inheritDoc} */
  396. public List getEntries() {
  397. if (gsa != null) {
  398. List entries = new ArrayList(1);
  399. entries.add(gsa);
  400. return entries;
  401. } else {
  402. return null;
  403. }
  404. }
  405. /** {@inheritDoc} */
  406. public int[] getGlyphsForCoverageIndex(int ci, int gi) throws IllegalArgumentException {
  407. if (gsa == null) {
  408. return null;
  409. } else if (ci >= gsa.length) {
  410. throw new IllegalArgumentException("coverage index " + ci + " out of range, maximum coverage index is " + gsa.length);
  411. } else {
  412. return gsa [ ci ];
  413. }
  414. }
  415. private void populate(List entries) {
  416. if (entries == null) {
  417. throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null");
  418. } else if (entries.size() != 1) {
  419. throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry");
  420. } else {
  421. Object o;
  422. if (((o = entries.get(0)) == null) || !(o instanceof int[][])) {
  423. throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an int[][], but is: " + ((o != null) ? o.getClass() : null));
  424. } else {
  425. gsa = (int[][]) o;
  426. }
  427. }
  428. }
  429. }
  430. private abstract static class AlternateSubtable extends GlyphSubstitutionSubtable {
  431. public AlternateSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  432. super(id, sequence, flags, format, coverage);
  433. }
  434. /** {@inheritDoc} */
  435. public int getType() {
  436. return GSUB_LOOKUP_TYPE_ALTERNATE;
  437. }
  438. /** {@inheritDoc} */
  439. public boolean isCompatible(GlyphSubtable subtable) {
  440. return subtable instanceof AlternateSubtable;
  441. }
  442. /** {@inheritDoc} */
  443. public boolean substitute(GlyphSubstitutionState ss) {
  444. int gi = ss.getGlyph();
  445. int ci;
  446. if ((ci = getCoverageIndex(gi)) < 0) {
  447. return false;
  448. } else {
  449. int[] ga = getAlternatesForCoverageIndex(ci, gi);
  450. int ai = ss.getAlternatesIndex(ci);
  451. int go;
  452. if ((ai < 0) || (ai >= ga.length)) {
  453. go = gi;
  454. } else {
  455. go = ga [ ai ];
  456. }
  457. if ((go < 0) || (go > 65535)) {
  458. go = 65535;
  459. }
  460. ss.putGlyph(go, ss.getAssociation(), Boolean.TRUE);
  461. ss.consume(1);
  462. return true;
  463. }
  464. }
  465. /**
  466. * Obtain glyph alternates for coverage index.
  467. * @param ci coverage index
  468. * @param gi original glyph index
  469. * @return sequence of glyphs to substitute for input glyph
  470. * @throws IllegalArgumentException if coverage index is not valid
  471. */
  472. public abstract int[] getAlternatesForCoverageIndex(int ci, int gi) throws IllegalArgumentException;
  473. static GlyphSubstitutionSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  474. if (format == 1) {
  475. return new AlternateSubtableFormat1(id, sequence, flags, format, coverage, entries);
  476. } else {
  477. throw new UnsupportedOperationException();
  478. }
  479. }
  480. }
  481. private static class AlternateSubtableFormat1 extends AlternateSubtable {
  482. private int[][] gaa; // glyph alternates array, ordered by coverage index
  483. AlternateSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  484. super(id, sequence, flags, format, coverage, entries);
  485. populate(entries);
  486. }
  487. /** {@inheritDoc} */
  488. public List getEntries() {
  489. List entries = new ArrayList(gaa.length);
  490. for (int i = 0, n = gaa.length; i < n; i++) {
  491. entries.add(gaa[i]);
  492. }
  493. return entries;
  494. }
  495. /** {@inheritDoc} */
  496. public int[] getAlternatesForCoverageIndex(int ci, int gi) throws IllegalArgumentException {
  497. if (gaa == null) {
  498. return null;
  499. } else if (ci >= gaa.length) {
  500. throw new IllegalArgumentException("coverage index " + ci + " out of range, maximum coverage index is " + gaa.length);
  501. } else {
  502. return gaa [ ci ];
  503. }
  504. }
  505. private void populate(List entries) {
  506. int i = 0;
  507. int n = entries.size();
  508. int[][] gaa = new int [ n ][];
  509. for (Iterator it = entries.iterator(); it.hasNext();) {
  510. Object o = it.next();
  511. if (o instanceof int[]) {
  512. gaa [ i++ ] = (int[]) o;
  513. } else {
  514. throw new AdvancedTypographicTableFormatException("illegal entries entry, must be int[]: " + o);
  515. }
  516. }
  517. assert i == n;
  518. assert this.gaa == null;
  519. this.gaa = gaa;
  520. }
  521. }
  522. private abstract static class LigatureSubtable extends GlyphSubstitutionSubtable {
  523. public LigatureSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  524. super(id, sequence, flags, format, coverage);
  525. }
  526. /** {@inheritDoc} */
  527. public int getType() {
  528. return GSUB_LOOKUP_TYPE_LIGATURE;
  529. }
  530. /** {@inheritDoc} */
  531. public boolean isCompatible(GlyphSubtable subtable) {
  532. return subtable instanceof LigatureSubtable;
  533. }
  534. /** {@inheritDoc} */
  535. public boolean substitute(GlyphSubstitutionState ss) {
  536. int gi = ss.getGlyph();
  537. int ci;
  538. if ((ci = getCoverageIndex(gi)) < 0) {
  539. return false;
  540. } else {
  541. LigatureSet ls = getLigatureSetForCoverageIndex(ci, gi);
  542. if (ls != null) {
  543. boolean reverse = false;
  544. GlyphTester ignores = ss.getIgnoreDefault();
  545. int[] counts = ss.getGlyphsAvailable(0, reverse, ignores);
  546. int nga = counts[0];
  547. int ngi;
  548. if (nga > 1) {
  549. int[] iga = ss.getGlyphs(0, nga, reverse, ignores, null, counts);
  550. Ligature l = findLigature(ls, iga);
  551. if (l != null) {
  552. int go = l.getLigature();
  553. if ((go < 0) || (go > 65535)) {
  554. go = 65535;
  555. }
  556. int nmg = 1 + l.getNumComponents();
  557. // fetch matched number of component glyphs to determine matched and ignored count
  558. ss.getGlyphs(0, nmg, reverse, ignores, null, counts);
  559. nga = counts[0];
  560. ngi = counts[1];
  561. // fetch associations of matched component glyphs
  562. CharAssociation[] laa = ss.getAssociations(0, nga);
  563. // output ligature glyph and its association
  564. ss.putGlyph(go, CharAssociation.join(laa), Boolean.TRUE);
  565. // fetch and output ignored glyphs (if necessary)
  566. if (ngi > 0) {
  567. ss.putGlyphs(ss.getIgnoredGlyphs(0, ngi), ss.getIgnoredAssociations(0, ngi), null);
  568. }
  569. ss.consume(nga + ngi);
  570. }
  571. }
  572. }
  573. return true;
  574. }
  575. }
  576. private Ligature findLigature(LigatureSet ls, int[] glyphs) {
  577. Ligature[] la = ls.getLigatures();
  578. int k = -1;
  579. int maxComponents = -1;
  580. for (int i = 0, n = la.length; i < n; i++) {
  581. Ligature l = la [ i ];
  582. if (l.matchesComponents(glyphs)) {
  583. int nc = l.getNumComponents();
  584. if (nc > maxComponents) {
  585. maxComponents = nc;
  586. k = i;
  587. }
  588. }
  589. }
  590. if (k >= 0) {
  591. return la [ k ];
  592. } else {
  593. return null;
  594. }
  595. }
  596. /**
  597. * Obtain ligature set for coverage index.
  598. * @param ci coverage index
  599. * @param gi original glyph index
  600. * @return ligature set (or null if none defined)
  601. * @throws IllegalArgumentException if coverage index is not valid
  602. */
  603. public abstract LigatureSet getLigatureSetForCoverageIndex(int ci, int gi) throws IllegalArgumentException;
  604. static GlyphSubstitutionSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  605. if (format == 1) {
  606. return new LigatureSubtableFormat1(id, sequence, flags, format, coverage, entries);
  607. } else {
  608. throw new UnsupportedOperationException();
  609. }
  610. }
  611. }
  612. private static class LigatureSubtableFormat1 extends LigatureSubtable {
  613. private LigatureSet[] ligatureSets;
  614. public LigatureSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  615. super(id, sequence, flags, format, coverage, entries);
  616. populate(entries);
  617. }
  618. /** {@inheritDoc} */
  619. public List getEntries() {
  620. List entries = new ArrayList(ligatureSets.length);
  621. for (int i = 0, n = ligatureSets.length; i < n; i++) {
  622. entries.add(ligatureSets[i]);
  623. }
  624. return entries;
  625. }
  626. /** {@inheritDoc} */
  627. public LigatureSet getLigatureSetForCoverageIndex(int ci, int gi) throws IllegalArgumentException {
  628. if (ligatureSets == null) {
  629. return null;
  630. } else if (ci >= ligatureSets.length) {
  631. throw new IllegalArgumentException("coverage index " + ci + " out of range, maximum coverage index is " + ligatureSets.length);
  632. } else {
  633. return ligatureSets [ ci ];
  634. }
  635. }
  636. private void populate(List entries) {
  637. int i = 0;
  638. int n = entries.size();
  639. LigatureSet[] ligatureSets = new LigatureSet [ n ];
  640. for (Iterator it = entries.iterator(); it.hasNext();) {
  641. Object o = it.next();
  642. if (o instanceof LigatureSet) {
  643. ligatureSets [ i++ ] = (LigatureSet) o;
  644. } else {
  645. throw new AdvancedTypographicTableFormatException("illegal ligatures entry, must be LigatureSet: " + o);
  646. }
  647. }
  648. assert i == n;
  649. assert this.ligatureSets == null;
  650. this.ligatureSets = ligatureSets;
  651. }
  652. }
  653. private abstract static class ContextualSubtable extends GlyphSubstitutionSubtable {
  654. public ContextualSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  655. super(id, sequence, flags, format, coverage);
  656. }
  657. /** {@inheritDoc} */
  658. public int getType() {
  659. return GSUB_LOOKUP_TYPE_CONTEXTUAL;
  660. }
  661. /** {@inheritDoc} */
  662. public boolean isCompatible(GlyphSubtable subtable) {
  663. return subtable instanceof ContextualSubtable;
  664. }
  665. /** {@inheritDoc} */
  666. public boolean substitute(GlyphSubstitutionState ss) {
  667. int gi = ss.getGlyph();
  668. int ci;
  669. if ((ci = getCoverageIndex(gi)) < 0) {
  670. return false;
  671. } else {
  672. int[] rv = new int[1];
  673. RuleLookup[] la = getLookups(ci, gi, ss, rv);
  674. if (la != null) {
  675. ss.apply(la, rv[0]);
  676. }
  677. return true;
  678. }
  679. }
  680. /**
  681. * Obtain rule lookups set associated current input glyph context.
  682. * @param ci coverage index of glyph at current position
  683. * @param gi glyph index of glyph at current position
  684. * @param ss glyph substitution state
  685. * @param rv array of ints used to receive multiple return values, must be of length 1 or greater,
  686. * where the first entry is used to return the input sequence length of the matched rule
  687. * @return array of rule lookups or null if none applies
  688. */
  689. public abstract RuleLookup[] getLookups(int ci, int gi, GlyphSubstitutionState ss, int[] rv);
  690. static GlyphSubstitutionSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  691. if (format == 1) {
  692. return new ContextualSubtableFormat1(id, sequence, flags, format, coverage, entries);
  693. } else if (format == 2) {
  694. return new ContextualSubtableFormat2(id, sequence, flags, format, coverage, entries);
  695. } else if (format == 3) {
  696. return new ContextualSubtableFormat3(id, sequence, flags, format, coverage, entries);
  697. } else {
  698. throw new UnsupportedOperationException();
  699. }
  700. }
  701. }
  702. private static class ContextualSubtableFormat1 extends ContextualSubtable {
  703. private RuleSet[] rsa; // rule set array, ordered by glyph coverage index
  704. ContextualSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  705. super(id, sequence, flags, format, coverage, entries);
  706. populate(entries);
  707. }
  708. /** {@inheritDoc} */
  709. public List getEntries() {
  710. if (rsa != null) {
  711. List entries = new ArrayList(1);
  712. entries.add(rsa);
  713. return entries;
  714. } else {
  715. return null;
  716. }
  717. }
  718. /** {@inheritDoc} */
  719. public void resolveLookupReferences(Map/*<String,LookupTable>*/ lookupTables) {
  720. GlyphTable.resolveLookupReferences(rsa, lookupTables);
  721. }
  722. /** {@inheritDoc} */
  723. public RuleLookup[] getLookups(int ci, int gi, GlyphSubstitutionState ss, int[] rv) {
  724. assert ss != null;
  725. assert (rv != null) && (rv.length > 0);
  726. assert rsa != null;
  727. if (rsa.length > 0) {
  728. RuleSet rs = rsa [ 0 ];
  729. if (rs != null) {
  730. Rule[] ra = rs.getRules();
  731. for (int i = 0, n = ra.length; i < n; i++) {
  732. Rule r = ra [ i ];
  733. if ((r != null) && (r instanceof ChainedGlyphSequenceRule)) {
  734. ChainedGlyphSequenceRule cr = (ChainedGlyphSequenceRule) r;
  735. int[] iga = cr.getGlyphs(gi);
  736. if (matches(ss, iga, 0, rv)) {
  737. return r.getLookups();
  738. }
  739. }
  740. }
  741. }
  742. }
  743. return null;
  744. }
  745. static boolean matches(GlyphSubstitutionState ss, int[] glyphs, int offset, int[] rv) {
  746. if ((glyphs == null) || (glyphs.length == 0)) {
  747. return true; // match null or empty glyph sequence
  748. } else {
  749. boolean reverse = offset < 0;
  750. GlyphTester ignores = ss.getIgnoreDefault();
  751. int[] counts = ss.getGlyphsAvailable(offset, reverse, ignores);
  752. int nga = counts[0];
  753. int ngm = glyphs.length;
  754. if (nga < ngm) {
  755. return false; // insufficient glyphs available to match
  756. } else {
  757. int[] ga = ss.getGlyphs(offset, ngm, reverse, ignores, null, counts);
  758. for (int k = 0; k < ngm; k++) {
  759. if (ga [ k ] != glyphs [ k ]) {
  760. return false; // match fails at ga [ k ]
  761. }
  762. }
  763. if (rv != null) {
  764. rv[0] = counts[0] + counts[1];
  765. }
  766. return true; // all glyphs match
  767. }
  768. }
  769. }
  770. private void populate(List entries) {
  771. if (entries == null) {
  772. throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null");
  773. } else if (entries.size() != 1) {
  774. throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry");
  775. } else {
  776. Object o;
  777. if (((o = entries.get(0)) == null) || !(o instanceof RuleSet[])) {
  778. throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null));
  779. } else {
  780. rsa = (RuleSet[]) o;
  781. }
  782. }
  783. }
  784. }
  785. private static class ContextualSubtableFormat2 extends ContextualSubtable {
  786. private GlyphClassTable cdt; // class def table
  787. private int ngc; // class set count
  788. private RuleSet[] rsa; // rule set array, ordered by class number [0...ngc - 1]
  789. ContextualSubtableFormat2(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  790. super(id, sequence, flags, format, coverage, entries);
  791. populate(entries);
  792. }
  793. /** {@inheritDoc} */
  794. public List getEntries() {
  795. if (rsa != null) {
  796. List entries = new ArrayList(3);
  797. entries.add(cdt);
  798. entries.add(ngc);
  799. entries.add(rsa);
  800. return entries;
  801. } else {
  802. return null;
  803. }
  804. }
  805. /** {@inheritDoc} */
  806. public void resolveLookupReferences(Map/*<String,LookupTable>*/ lookupTables) {
  807. GlyphTable.resolveLookupReferences(rsa, lookupTables);
  808. }
  809. /** {@inheritDoc} */
  810. public RuleLookup[] getLookups(int ci, int gi, GlyphSubstitutionState ss, int[] rv) {
  811. assert ss != null;
  812. assert (rv != null) && (rv.length > 0);
  813. assert rsa != null;
  814. if (rsa.length > 0) {
  815. RuleSet rs = rsa [ 0 ];
  816. if (rs != null) {
  817. Rule[] ra = rs.getRules();
  818. for (int i = 0, n = ra.length; i < n; i++) {
  819. Rule r = ra [ i ];
  820. if ((r != null) && (r instanceof ChainedClassSequenceRule)) {
  821. ChainedClassSequenceRule cr = (ChainedClassSequenceRule) r;
  822. int[] ca = cr.getClasses(cdt.getClassIndex(gi, ss.getClassMatchSet(gi)));
  823. if (matches(ss, cdt, ca, 0, rv)) {
  824. return r.getLookups();
  825. }
  826. }
  827. }
  828. }
  829. }
  830. return null;
  831. }
  832. static boolean matches(GlyphSubstitutionState ss, GlyphClassTable cdt, int[] classes, int offset, int[] rv) {
  833. if ((cdt == null) || (classes == null) || (classes.length == 0)) {
  834. return true; // match null class definitions, null or empty class sequence
  835. } else {
  836. boolean reverse = offset < 0;
  837. GlyphTester ignores = ss.getIgnoreDefault();
  838. int[] counts = ss.getGlyphsAvailable(offset, reverse, ignores);
  839. int nga = counts[0];
  840. int ngm = classes.length;
  841. if (nga < ngm) {
  842. return false; // insufficient glyphs available to match
  843. } else {
  844. int[] ga = ss.getGlyphs(offset, ngm, reverse, ignores, null, counts);
  845. for (int k = 0; k < ngm; k++) {
  846. int gi = ga [ k ];
  847. int ms = ss.getClassMatchSet(gi);
  848. int gc = cdt.getClassIndex(gi, ms);
  849. if ((gc < 0) || (gc >= cdt.getClassSize(ms))) {
  850. return false; // none or invalid class fails mat ch
  851. } else if (gc != classes [ k ]) {
  852. return false; // match fails at ga [ k ]
  853. }
  854. }
  855. if (rv != null) {
  856. rv[0] = counts[0] + counts[1];
  857. }
  858. return true; // all glyphs match
  859. }
  860. }
  861. }
  862. private void populate(List entries) {
  863. if (entries == null) {
  864. throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null");
  865. } else if (entries.size() != 3) {
  866. throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 3 entries");
  867. } else {
  868. Object o;
  869. if (((o = entries.get(0)) == null) || !(o instanceof GlyphClassTable)) {
  870. throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphClassTable, but is: " + ((o != null) ? o.getClass() : null));
  871. } else {
  872. cdt = (GlyphClassTable) o;
  873. }
  874. if (((o = entries.get(1)) == null) || !(o instanceof Integer)) {
  875. throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null));
  876. } else {
  877. ngc = (Integer) (o);
  878. }
  879. if (((o = entries.get(2)) == null) || !(o instanceof RuleSet[])) {
  880. throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null));
  881. } else {
  882. rsa = (RuleSet[]) o;
  883. if (rsa.length != ngc) {
  884. throw new AdvancedTypographicTableFormatException("illegal entries, RuleSet[] length is " + rsa.length + ", but expected " + ngc + " glyph classes");
  885. }
  886. }
  887. }
  888. }
  889. }
  890. private static class ContextualSubtableFormat3 extends ContextualSubtable {
  891. private RuleSet[] rsa; // rule set array, containing a single rule set
  892. ContextualSubtableFormat3(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  893. super(id, sequence, flags, format, coverage, entries);
  894. populate(entries);
  895. }
  896. /** {@inheritDoc} */
  897. public List getEntries() {
  898. if (rsa != null) {
  899. List entries = new ArrayList(1);
  900. entries.add(rsa);
  901. return entries;
  902. } else {
  903. return null;
  904. }
  905. }
  906. /** {@inheritDoc} */
  907. public void resolveLookupReferences(Map/*<String,LookupTable>*/ lookupTables) {
  908. GlyphTable.resolveLookupReferences(rsa, lookupTables);
  909. }
  910. /** {@inheritDoc} */
  911. public RuleLookup[] getLookups(int ci, int gi, GlyphSubstitutionState ss, int[] rv) {
  912. assert ss != null;
  913. assert (rv != null) && (rv.length > 0);
  914. assert rsa != null;
  915. if (rsa.length > 0) {
  916. RuleSet rs = rsa [ 0 ];
  917. if (rs != null) {
  918. Rule[] ra = rs.getRules();
  919. for (int i = 0, n = ra.length; i < n; i++) {
  920. Rule r = ra [ i ];
  921. if ((r != null) && (r instanceof ChainedCoverageSequenceRule)) {
  922. ChainedCoverageSequenceRule cr = (ChainedCoverageSequenceRule) r;
  923. GlyphCoverageTable[] gca = cr.getCoverages();
  924. if (matches(ss, gca, 0, rv)) {
  925. return r.getLookups();
  926. }
  927. }
  928. }
  929. }
  930. }
  931. return null;
  932. }
  933. static boolean matches(GlyphSubstitutionState ss, GlyphCoverageTable[] gca, int offset, int[] rv) {
  934. if ((gca == null) || (gca.length == 0)) {
  935. return true; // match null or empty coverage array
  936. } else {
  937. boolean reverse = offset < 0;
  938. GlyphTester ignores = ss.getIgnoreDefault();
  939. int[] counts = ss.getGlyphsAvailable(offset, reverse, ignores);
  940. int nga = counts[0];
  941. int ngm = gca.length;
  942. if (nga < ngm) {
  943. return false; // insufficient glyphs available to match
  944. } else {
  945. int[] ga = ss.getGlyphs(offset, ngm, reverse, ignores, null, counts);
  946. for (int k = 0; k < ngm; k++) {
  947. GlyphCoverageTable ct = gca [ k ];
  948. if (ct != null) {
  949. if (ct.getCoverageIndex(ga [ k ]) < 0) {
  950. return false; // match fails at ga [ k ]
  951. }
  952. }
  953. }
  954. if (rv != null) {
  955. rv[0] = counts[0] + counts[1];
  956. }
  957. return true; // all glyphs match
  958. }
  959. }
  960. }
  961. private void populate(List entries) {
  962. if (entries == null) {
  963. throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null");
  964. } else if (entries.size() != 1) {
  965. throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry");
  966. } else {
  967. Object o;
  968. if (((o = entries.get(0)) == null) || !(o instanceof RuleSet[])) {
  969. throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null));
  970. } else {
  971. rsa = (RuleSet[]) o;
  972. }
  973. }
  974. }
  975. }
  976. private abstract static class ChainedContextualSubtable extends GlyphSubstitutionSubtable {
  977. public ChainedContextualSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  978. super(id, sequence, flags, format, coverage);
  979. }
  980. /** {@inheritDoc} */
  981. public int getType() {
  982. return GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL;
  983. }
  984. /** {@inheritDoc} */
  985. public boolean isCompatible(GlyphSubtable subtable) {
  986. return subtable instanceof ChainedContextualSubtable;
  987. }
  988. /** {@inheritDoc} */
  989. public boolean substitute(GlyphSubstitutionState ss) {
  990. int gi = ss.getGlyph();
  991. int ci;
  992. if ((ci = getCoverageIndex(gi)) < 0) {
  993. return false;
  994. } else {
  995. int[] rv = new int[1];
  996. RuleLookup[] la = getLookups(ci, gi, ss, rv);
  997. if (la != null) {
  998. ss.apply(la, rv[0]);
  999. return true;
  1000. } else {
  1001. return false;
  1002. }
  1003. }
  1004. }
  1005. /**
  1006. * Obtain rule lookups set associated current input glyph context.
  1007. * @param ci coverage index of glyph at current position
  1008. * @param gi glyph index of glyph at current position
  1009. * @param ss glyph substitution state
  1010. * @param rv array of ints used to receive multiple return values, must be of length 1 or greater
  1011. * @return array of rule lookups or null if none applies
  1012. */
  1013. public abstract RuleLookup[] getLookups(int ci, int gi, GlyphSubstitutionState ss, int[] rv);
  1014. static GlyphSubstitutionSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  1015. if (format == 1) {
  1016. return new ChainedContextualSubtableFormat1(id, sequence, flags, format, coverage, entries);
  1017. } else if (format == 2) {
  1018. return new ChainedContextualSubtableFormat2(id, sequence, flags, format, coverage, entries);
  1019. } else if (format == 3) {
  1020. return new ChainedContextualSubtableFormat3(id, sequence, flags, format, coverage, entries);
  1021. } else {
  1022. throw new UnsupportedOperationException();
  1023. }
  1024. }
  1025. }
  1026. private static class ChainedContextualSubtableFormat1 extends ChainedContextualSubtable {
  1027. private RuleSet[] rsa; // rule set array, ordered by glyph coverage index
  1028. ChainedContextualSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  1029. super(id, sequence, flags, format, coverage, entries);
  1030. populate(entries);
  1031. }
  1032. /** {@inheritDoc} */
  1033. public List getEntries() {
  1034. if (rsa != null) {
  1035. List entries = new ArrayList(1);
  1036. entries.add(rsa);
  1037. return entries;
  1038. } else {
  1039. return null;
  1040. }
  1041. }
  1042. /** {@inheritDoc} */
  1043. public void resolveLookupReferences(Map/*<String,LookupTable>*/ lookupTables) {
  1044. GlyphTable.resolveLookupReferences(rsa, lookupTables);
  1045. }
  1046. /** {@inheritDoc} */
  1047. public RuleLookup[] getLookups(int ci, int gi, GlyphSubstitutionState ss, int[] rv) {
  1048. assert ss != null;
  1049. assert (rv != null) && (rv.length > 0);
  1050. assert rsa != null;
  1051. if (rsa.length > 0) {
  1052. RuleSet rs = rsa [ 0 ];
  1053. if (rs != null) {
  1054. Rule[] ra = rs.getRules();
  1055. for (int i = 0, n = ra.length; i < n; i++) {
  1056. Rule r = ra [ i ];
  1057. if ((r != null) && (r instanceof ChainedGlyphSequenceRule)) {
  1058. ChainedGlyphSequenceRule cr = (ChainedGlyphSequenceRule) r;
  1059. int[] iga = cr.getGlyphs(gi);
  1060. if (matches(ss, iga, 0, rv)) {
  1061. int[] bga = cr.getBacktrackGlyphs();
  1062. if (matches(ss, bga, -1, null)) {
  1063. int[] lga = cr.getLookaheadGlyphs();
  1064. if (matches(ss, lga, rv[0], null)) {
  1065. return r.getLookups();
  1066. }
  1067. }
  1068. }
  1069. }
  1070. }
  1071. }
  1072. }
  1073. return null;
  1074. }
  1075. private boolean matches(GlyphSubstitutionState ss, int[] glyphs, int offset, int[] rv) {
  1076. return ContextualSubtableFormat1.matches(ss, glyphs, offset, rv);
  1077. }
  1078. private void populate(List entries) {
  1079. if (entries == null) {
  1080. throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null");
  1081. } else if (entries.size() != 1) {
  1082. throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry");
  1083. } else {
  1084. Object o;
  1085. if (((o = entries.get(0)) == null) || !(o instanceof RuleSet[])) {
  1086. throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null));
  1087. } else {
  1088. rsa = (RuleSet[]) o;
  1089. }
  1090. }
  1091. }
  1092. }
  1093. private static class ChainedContextualSubtableFormat2 extends ChainedContextualSubtable {
  1094. private GlyphClassTable icdt; // input class def table
  1095. private GlyphClassTable bcdt; // backtrack class def table
  1096. private GlyphClassTable lcdt; // lookahead class def table
  1097. private int ngc; // class set count
  1098. private RuleSet[] rsa; // rule set array, ordered by class number [0...ngc - 1]
  1099. ChainedContextualSubtableFormat2(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  1100. super(id, sequence, flags, format, coverage, entries);
  1101. populate(entries);
  1102. }
  1103. /** {@inheritDoc} */
  1104. public List getEntries() {
  1105. if (rsa != null) {
  1106. List entries = new ArrayList(5);
  1107. entries.add(icdt);
  1108. entries.add(bcdt);
  1109. entries.add(lcdt);
  1110. entries.add(ngc);
  1111. entries.add(rsa);
  1112. return entries;
  1113. } else {
  1114. return null;
  1115. }
  1116. }
  1117. /** {@inheritDoc} */
  1118. public RuleLookup[] getLookups(int ci, int gi, GlyphSubstitutionState ss, int[] rv) {
  1119. assert ss != null;
  1120. assert (rv != null) && (rv.length > 0);
  1121. assert rsa != null;
  1122. if (rsa.length > 0) {
  1123. RuleSet rs = rsa [ 0 ];
  1124. if (rs != null) {
  1125. Rule[] ra = rs.getRules();
  1126. for (int i = 0, n = ra.length; i < n; i++) {
  1127. Rule r = ra [ i ];
  1128. if ((r != null) && (r instanceof ChainedClassSequenceRule)) {
  1129. ChainedClassSequenceRule cr = (ChainedClassSequenceRule) r;
  1130. int[] ica = cr.getClasses(icdt.getClassIndex(gi, ss.getClassMatchSet(gi)));
  1131. if (matches(ss, icdt, ica, 0, rv)) {
  1132. int[] bca = cr.getBacktrackClasses();
  1133. if (matches(ss, bcdt, bca, -1, null)) {
  1134. int[] lca = cr.getLookaheadClasses();
  1135. if (matches(ss, lcdt, lca, rv[0], null)) {
  1136. return r.getLookups();
  1137. }
  1138. }
  1139. }
  1140. }
  1141. }
  1142. }
  1143. }
  1144. return null;
  1145. }
  1146. private boolean matches(GlyphSubstitutionState ss, GlyphClassTable cdt, int[] classes, int offset, int[] rv) {
  1147. return ContextualSubtableFormat2.matches(ss, cdt, classes, offset, rv);
  1148. }
  1149. /** {@inheritDoc} */
  1150. public void resolveLookupReferences(Map/*<String,LookupTable>*/ lookupTables) {
  1151. GlyphTable.resolveLookupReferences(rsa, lookupTables);
  1152. }
  1153. private void populate(List entries) {
  1154. if (entries == null) {
  1155. throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null");
  1156. } else if (entries.size() != 5) {
  1157. throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 5 entries");
  1158. } else {
  1159. Object o;
  1160. if (((o = entries.get(0)) == null) || !(o instanceof GlyphClassTable)) {
  1161. throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphClassTable, but is: " + ((o != null) ? o.getClass() : null));
  1162. } else {
  1163. icdt = (GlyphClassTable) o;
  1164. }
  1165. if (((o = entries.get(1)) != null) && !(o instanceof GlyphClassTable)) {
  1166. throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an GlyphClassTable, but is: " + o.getClass());
  1167. } else {
  1168. bcdt = (GlyphClassTable) o;
  1169. }
  1170. if (((o = entries.get(2)) != null) && !(o instanceof GlyphClassTable)) {
  1171. throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be an GlyphClassTable, but is: " + o.getClass());
  1172. } else {
  1173. lcdt = (GlyphClassTable) o;
  1174. }
  1175. if (((o = entries.get(3)) == null) || !(o instanceof Integer)) {
  1176. throw new AdvancedTypographicTableFormatException("illegal entries, fourth entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null));
  1177. } else {
  1178. ngc = (Integer) (o);
  1179. }
  1180. if (((o = entries.get(4)) == null) || !(o instanceof RuleSet[])) {
  1181. throw new AdvancedTypographicTableFormatException("illegal entries, fifth entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null));
  1182. } else {
  1183. rsa = (RuleSet[]) o;
  1184. if (rsa.length != ngc) {
  1185. throw new AdvancedTypographicTableFormatException("illegal entries, RuleSet[] length is " + rsa.length + ", but expected " + ngc + " glyph classes");
  1186. }
  1187. }
  1188. }
  1189. }
  1190. }
  1191. private static class ChainedContextualSubtableFormat3 extends ChainedContextualSubtable {
  1192. private RuleSet[] rsa; // rule set array, containing a single rule set
  1193. ChainedContextualSubtableFormat3(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  1194. super(id, sequence, flags, format, coverage, entries);
  1195. populate(entries);
  1196. }
  1197. /** {@inheritDoc} */
  1198. public List getEntries() {
  1199. if (rsa != null) {
  1200. List entries = new ArrayList(1);
  1201. entries.add(rsa);
  1202. return entries;
  1203. } else {
  1204. return null;
  1205. }
  1206. }
  1207. /** {@inheritDoc} */
  1208. public void resolveLookupReferences(Map/*<String,LookupTable>*/ lookupTables) {
  1209. GlyphTable.resolveLookupReferences(rsa, lookupTables);
  1210. }
  1211. /** {@inheritDoc} */
  1212. public RuleLookup[] getLookups(int ci, int gi, GlyphSubstitutionState ss, int[] rv) {
  1213. assert ss != null;
  1214. assert (rv != null) && (rv.length > 0);
  1215. assert rsa != null;
  1216. if (rsa.length > 0) {
  1217. RuleSet rs = rsa [ 0 ];
  1218. if (rs != null) {
  1219. Rule[] ra = rs.getRules();
  1220. for (int i = 0, n = ra.length; i < n; i++) {
  1221. Rule r = ra [ i ];
  1222. if ((r != null) && (r instanceof ChainedCoverageSequenceRule)) {
  1223. ChainedCoverageSequenceRule cr = (ChainedCoverageSequenceRule) r;
  1224. GlyphCoverageTable[] igca = cr.getCoverages();
  1225. if (matches(ss, igca, 0, rv)) {
  1226. GlyphCoverageTable[] bgca = cr.getBacktrackCoverages();
  1227. if (matches(ss, bgca, -1, null)) {
  1228. GlyphCoverageTable[] lgca = cr.getLookaheadCoverages();
  1229. if (matches(ss, lgca, rv[0], null)) {
  1230. return r.getLookups();
  1231. }
  1232. }
  1233. }
  1234. }
  1235. }
  1236. }
  1237. }
  1238. return null;
  1239. }
  1240. private boolean matches(GlyphSubstitutionState ss, GlyphCoverageTable[] gca, int offset, int[] rv) {
  1241. return ContextualSubtableFormat3.matches(ss, gca, offset, rv);
  1242. }
  1243. private void populate(List entries) {
  1244. if (entries == null) {
  1245. throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null");
  1246. } else if (entries.size() != 1) {
  1247. throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry");
  1248. } else {
  1249. Object o;
  1250. if (((o = entries.get(0)) == null) || !(o instanceof RuleSet[])) {
  1251. throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null));
  1252. } else {
  1253. rsa = (RuleSet[]) o;
  1254. }
  1255. }
  1256. }
  1257. }
  1258. private abstract static class ReverseChainedSingleSubtable extends GlyphSubstitutionSubtable {
  1259. public ReverseChainedSingleSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  1260. super(id, sequence, flags, format, coverage);
  1261. }
  1262. /** {@inheritDoc} */
  1263. public int getType() {
  1264. return GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE;
  1265. }
  1266. /** {@inheritDoc} */
  1267. public boolean isCompatible(GlyphSubtable subtable) {
  1268. return subtable instanceof ReverseChainedSingleSubtable;
  1269. }
  1270. /** {@inheritDoc} */
  1271. public boolean usesReverseScan() {
  1272. return true;
  1273. }
  1274. static GlyphSubstitutionSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  1275. if (format == 1) {
  1276. return new ReverseChainedSingleSubtableFormat1(id, sequence, flags, format, coverage, entries);
  1277. } else {
  1278. throw new UnsupportedOperationException();
  1279. }
  1280. }
  1281. }
  1282. private static class ReverseChainedSingleSubtableFormat1 extends ReverseChainedSingleSubtable {
  1283. ReverseChainedSingleSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
  1284. super(id, sequence, flags, format, coverage, entries);
  1285. populate(entries);
  1286. }
  1287. /** {@inheritDoc} */
  1288. public List getEntries() {
  1289. return null;
  1290. }
  1291. private void populate(List entries) {
  1292. }
  1293. }
  1294. /**
  1295. * The <code>Ligature</code> class implements a ligature lookup result in terms of
  1296. * a ligature glyph (code) and the <emph>N+1...</emph> components that comprise the ligature,
  1297. * where the <emph>Nth</emph> component was consumed in the coverage table lookup mapping to
  1298. * this ligature instance.
  1299. */
  1300. public static class Ligature {
  1301. private final int ligature; // (resulting) ligature glyph
  1302. private final int[] components; // component glyph codes (note that first component is implied)
  1303. /**
  1304. * Instantiate a ligature.
  1305. * @param ligature glyph id
  1306. * @param components sequence of <emph>N+1...</emph> component glyph (or character) identifiers
  1307. */
  1308. public Ligature(int ligature, int[] components) {
  1309. if ((ligature < 0) || (ligature > 65535)) {
  1310. throw new AdvancedTypographicTableFormatException("invalid ligature glyph index: " + ligature);
  1311. } else if (components == null) {
  1312. throw new AdvancedTypographicTableFormatException("invalid ligature components, must be non-null array");
  1313. } else {
  1314. for (int i = 0, n = components.length; i < n; i++) {
  1315. int gc = components [ i ];
  1316. if ((gc < 0) || (gc > 65535)) {
  1317. throw new AdvancedTypographicTableFormatException("invalid component glyph index: " + gc);
  1318. }
  1319. }
  1320. this.ligature = ligature;
  1321. this.components = components;
  1322. }
  1323. }
  1324. /** @return ligature glyph id */
  1325. public int getLigature() {
  1326. return ligature;
  1327. }
  1328. /** @return array of <emph>N+1...</emph> components */
  1329. public int[] getComponents() {
  1330. return components;
  1331. }
  1332. /** @return components count */
  1333. public int getNumComponents() {
  1334. return components.length;
  1335. }
  1336. /**
  1337. * Determine if input sequence at offset matches ligature's components.
  1338. * @param glyphs array of glyph components to match (including first, implied glyph)
  1339. * @return true if matches
  1340. */
  1341. public boolean matchesComponents(int[] glyphs) {
  1342. if (glyphs.length < (components.length + 1)) {
  1343. return false;
  1344. } else {
  1345. for (int i = 0, n = components.length; i < n; i++) {
  1346. if (glyphs [ i + 1 ] != components [ i ]) {
  1347. return false;
  1348. }
  1349. }
  1350. return true;
  1351. }
  1352. }
  1353. /** {@inheritDoc} */
  1354. public String toString() {
  1355. StringBuffer sb = new StringBuffer();
  1356. sb.append("{components={");
  1357. for (int i = 0, n = components.length; i < n; i++) {
  1358. if (i > 0) {
  1359. sb.append(',');
  1360. }
  1361. sb.append(Integer.toString(components[i]));
  1362. }
  1363. sb.append("},ligature=");
  1364. sb.append(Integer.toString(ligature));
  1365. sb.append("}");
  1366. return sb.toString();
  1367. }
  1368. }
  1369. /**
  1370. * The <code>LigatureSet</code> class implements a set of ligatures.
  1371. */
  1372. public static class LigatureSet {
  1373. private final Ligature[] ligatures; // set of ligatures all of which share the first (implied) component
  1374. private final int maxComponents; // maximum number of components (including first)
  1375. /**
  1376. * Instantiate a set of ligatures.
  1377. * @param ligatures collection of ligatures
  1378. */
  1379. public LigatureSet(List ligatures) {
  1380. this ((Ligature[]) ligatures.toArray(new Ligature [ ligatures.size() ]));
  1381. }
  1382. /**
  1383. * Instantiate a set of ligatures.
  1384. * @param ligatures array of ligatures
  1385. */
  1386. public LigatureSet(Ligature[] ligatures) {
  1387. if (ligatures == null) {
  1388. throw new AdvancedTypographicTableFormatException("invalid ligatures, must be non-null array");
  1389. } else {
  1390. this.ligatures = ligatures;
  1391. int ncMax = -1;
  1392. for (int i = 0, n = ligatures.length; i < n; i++) {
  1393. Ligature l = ligatures [ i ];
  1394. int nc = l.getNumComponents() + 1;
  1395. if (nc > ncMax) {
  1396. ncMax = nc;
  1397. }
  1398. }
  1399. maxComponents = ncMax;
  1400. }
  1401. }
  1402. /** @return array of ligatures in this ligature set */
  1403. public Ligature[] getLigatures() {
  1404. return ligatures;
  1405. }
  1406. /** @return count of ligatures in this ligature set */
  1407. public int getNumLigatures() {
  1408. return ligatures.length;
  1409. }
  1410. /** @return maximum number of components in one ligature (including first component) */
  1411. public int getMaxComponents() {
  1412. return maxComponents;
  1413. }
  1414. /** {@inheritDoc} */
  1415. public String toString() {
  1416. StringBuffer sb = new StringBuffer();
  1417. sb.append("{ligs={");
  1418. for (int i = 0, n = ligatures.length; i < n; i++) {
  1419. if (i > 0) {
  1420. sb.append(',');
  1421. }
  1422. sb.append(ligatures[i]);
  1423. }
  1424. sb.append("}}");
  1425. return sb.toString();
  1426. }
  1427. }
  1428. }