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

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