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.

GlyphTable.java 51KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311
  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.Arrays;
  21. import java.util.HashMap;
  22. import java.util.Iterator;
  23. import java.util.LinkedHashMap;
  24. import java.util.LinkedList;
  25. import java.util.List;
  26. import java.util.ListIterator;
  27. import java.util.Map;
  28. import java.util.Set;
  29. import java.util.TreeSet;
  30. import org.apache.commons.logging.Log;
  31. import org.apache.commons.logging.LogFactory;
  32. import org.apache.fop.complexscripts.util.GlyphSequence;
  33. import org.apache.fop.complexscripts.util.ScriptContextTester;
  34. // CSOFF: LineLengthCheck
  35. /**
  36. * <p>Base class for all advanced typographic glyph tables.</p>
  37. *
  38. * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
  39. */
  40. public class GlyphTable {
  41. /** logging instance */
  42. private static final Log log = LogFactory.getLog(GlyphTable.class);
  43. /** substitution glyph table type */
  44. public static final int GLYPH_TABLE_TYPE_SUBSTITUTION = 1;
  45. /** positioning glyph table type */
  46. public static final int GLYPH_TABLE_TYPE_POSITIONING = 2;
  47. /** justification glyph table type */
  48. public static final int GLYPH_TABLE_TYPE_JUSTIFICATION = 3;
  49. /** baseline glyph table type */
  50. public static final int GLYPH_TABLE_TYPE_BASELINE = 4;
  51. /** definition glyph table type */
  52. public static final int GLYPH_TABLE_TYPE_DEFINITION = 5;
  53. // (optional) glyph definition table in table types other than glyph definition table
  54. private GlyphTable gdef;
  55. // map from lookup specs to lists of strings, each of which identifies a lookup table (consisting of one or more subtables)
  56. private Map/*<LookupSpec,List<String>>*/ lookups;
  57. // map from lookup identifiers to lookup tables
  58. private Map/*<String,LookupTable>*/ lookupTables;
  59. // cache for lookups matching
  60. private Map/*<LookupSpec,Map<LookupSpec,List<LookupTable>>>*/ matchedLookups;
  61. // if true, then prevent further subtable addition
  62. private boolean frozen;
  63. /**
  64. * Instantiate glyph table with specified lookups.
  65. * @param gdef glyph definition table that applies
  66. * @param lookups map from lookup specs to lookup tables
  67. */
  68. public GlyphTable(GlyphTable gdef, Map/*<LookupSpec,List<String>>*/ lookups) {
  69. if ((gdef != null) && !(gdef instanceof GlyphDefinitionTable)) {
  70. throw new AdvancedTypographicTableFormatException("bad glyph definition table");
  71. } else if (lookups == null) {
  72. throw new AdvancedTypographicTableFormatException("lookups must be non-null map");
  73. } else {
  74. this.gdef = gdef;
  75. this.lookups = lookups;
  76. this.lookupTables = new LinkedHashMap/*<String,List<LookupTable>>*/();
  77. this.matchedLookups = new HashMap/*<LookupSpec,Map<LookupSpec,List<LookupTable>>>*/();
  78. }
  79. }
  80. /**
  81. * Obtain glyph definition table.
  82. * @return (possibly null) glyph definition table
  83. */
  84. public GlyphDefinitionTable getGlyphDefinitions() {
  85. return (GlyphDefinitionTable) gdef;
  86. }
  87. /**
  88. * Obtain list of all lookup specifications.
  89. * @return (possibly empty) list of all lookup specifications
  90. */
  91. public List/*<LookupSpec>*/ getLookups() {
  92. return matchLookupSpecs("*", "*", "*");
  93. }
  94. /**
  95. * Obtain ordered list of all lookup tables, where order is by lookup identifier, which
  96. * lexicographic ordering follows the lookup list order.
  97. * @return (possibly empty) ordered list of all lookup tables
  98. */
  99. public List/*<LookupTable>*/ getLookupTables() {
  100. TreeSet/*<String>*/ lids = new TreeSet/*<String>*/(lookupTables.keySet());
  101. List/*<LookupTable>*/ ltl = new ArrayList/*<LookupTable>*/(lids.size());
  102. for (Iterator it = lids.iterator(); it.hasNext(); ) {
  103. String lid = (String) it.next();
  104. ltl.add(lookupTables.get(lid));
  105. }
  106. return ltl;
  107. }
  108. /**
  109. * Obtain lookup table by lookup id. This method is used by test code, and provides
  110. * access to embedded lookups not normally accessed by {script, language, feature} lookup spec.
  111. * @param lid lookup id
  112. * @return table associated with lookup id or null if none
  113. */
  114. public LookupTable getLookupTable(String lid) {
  115. return (LookupTable) lookupTables.get(lid);
  116. }
  117. /**
  118. * Add a subtable.
  119. * @param subtable a (non-null) glyph subtable
  120. */
  121. protected void addSubtable(GlyphSubtable subtable) {
  122. // ensure table is not frozen
  123. if (frozen) {
  124. throw new IllegalStateException("glyph table is frozen, subtable addition prohibited");
  125. }
  126. // set subtable's table reference to this table
  127. subtable.setTable(this);
  128. // add subtable to this table's subtable collection
  129. String lid = subtable.getLookupId();
  130. if (lookupTables.containsKey(lid)) {
  131. LookupTable lt = (LookupTable) lookupTables.get(lid);
  132. lt.addSubtable(subtable);
  133. } else {
  134. LookupTable lt = new LookupTable(lid, subtable);
  135. lookupTables.put(lid, lt);
  136. }
  137. }
  138. /**
  139. * Freeze subtables, i.e., do not allow further subtable addition, and
  140. * create resulting cached state.
  141. */
  142. protected void freezeSubtables() {
  143. if (!frozen) {
  144. for (Iterator it = lookupTables.values().iterator(); it.hasNext(); ) {
  145. LookupTable lt = (LookupTable) it.next();
  146. lt.freezeSubtables(lookupTables);
  147. }
  148. frozen = true;
  149. }
  150. }
  151. /**
  152. * Match lookup specifications according to <script,language,feature> tuple, where
  153. * '*' is a wildcard for a tuple component.
  154. * @param script a script identifier
  155. * @param language a language identifier
  156. * @param feature a feature identifier
  157. * @return a (possibly empty) array of matching lookup specifications
  158. */
  159. public List/*<LookupSpec>*/ matchLookupSpecs(String script, String language, String feature) {
  160. Set/*<LookupSpec>*/ keys = lookups.keySet();
  161. List/*<LookupSpec>*/ matches = new ArrayList/*<LookupSpec>*/();
  162. for (Iterator it = keys.iterator(); it.hasNext();) {
  163. LookupSpec ls = (LookupSpec) it.next();
  164. if (!"*".equals(script)) {
  165. if (!ls.getScript().equals(script)) {
  166. continue;
  167. }
  168. }
  169. if (!"*".equals(language)) {
  170. if (!ls.getLanguage().equals(language)) {
  171. continue;
  172. }
  173. }
  174. if (!"*".equals(feature)) {
  175. if (!ls.getFeature().equals(feature)) {
  176. continue;
  177. }
  178. }
  179. matches.add(ls);
  180. }
  181. return matches;
  182. }
  183. /**
  184. * Match lookup specifications according to <script,language,feature> tuple, where
  185. * '*' is a wildcard for a tuple component.
  186. * @param script a script identifier
  187. * @param language a language identifier
  188. * @param feature a feature identifier
  189. * @return a (possibly empty) map from matching lookup specifications to lists of corresponding lookup tables
  190. */
  191. public Map/*<LookupSpec,List<LookupTable>>*/ matchLookups(String script, String language, String feature) {
  192. LookupSpec lsm = new LookupSpec(script, language, feature, true, true);
  193. Map/*<LookupSpec,List<LookupTable>>*/ lm = (Map/*<LookupSpec,List<LookupTable>>*/) matchedLookups.get(lsm);
  194. if (lm == null) {
  195. lm = new LinkedHashMap();
  196. List/*<LookupSpec>*/ lsl = matchLookupSpecs(script, language, feature);
  197. for (Iterator it = lsl.iterator(); it.hasNext(); ) {
  198. LookupSpec ls = (LookupSpec) it.next();
  199. lm.put(ls, findLookupTables(ls));
  200. }
  201. matchedLookups.put(lsm, lm);
  202. }
  203. return lm;
  204. }
  205. /**
  206. * Obtain ordered list of glyph lookup tables that match a specific lookup specification.
  207. * @param ls a (non-null) lookup specification
  208. * @return a (possibly empty) ordered list of lookup tables whose corresponding lookup specifications match the specified lookup spec
  209. */
  210. public List/*<LookupTable>*/ findLookupTables(LookupSpec ls) {
  211. TreeSet/*<LookupTable>*/ lts = new TreeSet/*<LookupTable>*/();
  212. List/*<String>*/ ids;
  213. if ((ids = (List/*<String>*/) lookups.get(ls)) != null) {
  214. for (Iterator it = ids.iterator(); it.hasNext();) {
  215. String lid = (String) it.next();
  216. LookupTable lt;
  217. if ((lt = (LookupTable) lookupTables.get(lid)) != null) {
  218. lts.add(lt);
  219. }
  220. }
  221. }
  222. return new ArrayList/*<LookupTable>*/(lts);
  223. }
  224. /**
  225. * Assemble ordered array of lookup table use specifications according to the specified features and candidate lookups,
  226. * where the order of the array is in accordance to the order of the applicable lookup list.
  227. * @param features array of feature identifiers to apply
  228. * @param lookups a mapping from lookup specifications to lists of look tables from which to select lookup tables according to the specified features
  229. * @return ordered array of assembled lookup table use specifications
  230. */
  231. public UseSpec[] assembleLookups(String[] features, Map/*<LookupSpec,List<LookupTable>>*/ lookups) {
  232. TreeSet/*<UseSpec>*/ uss = new TreeSet/*<UseSpec>*/();
  233. for (int i = 0, n = features.length; i < n; i++) {
  234. String feature = features[i];
  235. for (Iterator it = lookups.entrySet().iterator(); it.hasNext(); ) {
  236. Map.Entry/*<LookupSpec,List<LookupTable>>*/ e = (Map.Entry/*<LookupSpec,List<LookupTable>>*/) it.next();
  237. LookupSpec ls = (LookupSpec) e.getKey();
  238. if (ls.getFeature().equals(feature)) {
  239. List/*<LookupTable>*/ ltl = (List/*<LookupTable>*/) e.getValue();
  240. if (ltl != null) {
  241. for (Iterator ltit = ltl.iterator(); ltit.hasNext(); ) {
  242. LookupTable lt = (LookupTable) ltit.next();
  243. uss.add(new UseSpec(lt, feature));
  244. }
  245. }
  246. }
  247. }
  248. }
  249. return (UseSpec[]) uss.toArray(new UseSpec [ uss.size() ]);
  250. }
  251. /** {@inheritDoc} */
  252. public String toString() {
  253. StringBuffer sb = new StringBuffer(super.toString());
  254. sb.append("{");
  255. sb.append("lookups={");
  256. sb.append(lookups.toString());
  257. sb.append("},lookupTables={");
  258. sb.append(lookupTables.toString());
  259. sb.append("}}");
  260. return sb.toString();
  261. }
  262. /**
  263. * Obtain glyph table type from name.
  264. * @param name of table type to map to type value
  265. * @return glyph table type (as an integer constant)
  266. */
  267. public static int getTableTypeFromName(String name) {
  268. int t;
  269. String s = name.toLowerCase();
  270. if ("gsub".equals(s)) {
  271. t = GLYPH_TABLE_TYPE_SUBSTITUTION;
  272. } else if ("gpos".equals(s)) {
  273. t = GLYPH_TABLE_TYPE_POSITIONING;
  274. } else if ("jstf".equals(s)) {
  275. t = GLYPH_TABLE_TYPE_JUSTIFICATION;
  276. } else if ("base".equals(s)) {
  277. t = GLYPH_TABLE_TYPE_BASELINE;
  278. } else if ("gdef".equals(s)) {
  279. t = GLYPH_TABLE_TYPE_DEFINITION;
  280. } else {
  281. t = -1;
  282. }
  283. return t;
  284. }
  285. /**
  286. * Resolve references to lookup tables in a collection of rules sets.
  287. * @param rsa array of rule sets
  288. * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
  289. */
  290. public static void resolveLookupReferences(RuleSet[] rsa, Map/*<String,LookupTable>*/ lookupTables) {
  291. if ((rsa != null) && (lookupTables != null)) {
  292. for (int i = 0, n = rsa.length; i < n; i++) {
  293. RuleSet rs = rsa [ i ];
  294. if (rs != null) {
  295. rs.resolveLookupReferences(lookupTables);
  296. }
  297. }
  298. }
  299. }
  300. /**
  301. * A structure class encapsulating a lookup specification as a <script,language,feature> tuple.
  302. */
  303. public static class LookupSpec implements Comparable {
  304. private final String script;
  305. private final String language;
  306. private final String feature;
  307. /**
  308. * Instantiate lookup spec.
  309. * @param script a script identifier
  310. * @param language a language identifier
  311. * @param feature a feature identifier
  312. */
  313. public LookupSpec(String script, String language, String feature) {
  314. this (script, language, feature, false, false);
  315. }
  316. /**
  317. * Instantiate lookup spec.
  318. * @param script a script identifier
  319. * @param language a language identifier
  320. * @param feature a feature identifier
  321. * @param permitEmpty if true the permit empty script, language, or feature
  322. * @param permitWildcard if true the permit wildcard script, language, or feature
  323. */
  324. LookupSpec(String script, String language, String feature, boolean permitEmpty, boolean permitWildcard) {
  325. if ((script == null) || (!permitEmpty && (script.length() == 0))) {
  326. throw new AdvancedTypographicTableFormatException("script must be non-empty string");
  327. } else if ((language == null) || (!permitEmpty && (language.length() == 0))) {
  328. throw new AdvancedTypographicTableFormatException("language must be non-empty string");
  329. } else if ((feature == null) || (!permitEmpty && (feature.length() == 0))) {
  330. throw new AdvancedTypographicTableFormatException("feature must be non-empty string");
  331. } else if (!permitWildcard && script.equals("*")) {
  332. throw new AdvancedTypographicTableFormatException("script must not be wildcard");
  333. } else if (!permitWildcard && language.equals("*")) {
  334. throw new AdvancedTypographicTableFormatException("language must not be wildcard");
  335. } else if (!permitWildcard && feature.equals("*")) {
  336. throw new AdvancedTypographicTableFormatException("feature must not be wildcard");
  337. }
  338. this.script = script.trim();
  339. this.language = language.trim();
  340. this.feature = feature.trim();
  341. }
  342. /** @return script identifier */
  343. public String getScript() {
  344. return script;
  345. }
  346. /** @return language identifier */
  347. public String getLanguage() {
  348. return language;
  349. }
  350. /** @return feature identifier */
  351. public String getFeature() {
  352. return feature;
  353. }
  354. /** {@inheritDoc} */
  355. public int hashCode() {
  356. int hc = 0;
  357. hc = 7 * hc + (hc ^ script.hashCode());
  358. hc = 11 * hc + (hc ^ language.hashCode());
  359. hc = 17 * hc + (hc ^ feature.hashCode());
  360. return hc;
  361. }
  362. /** {@inheritDoc} */
  363. public boolean equals(Object o) {
  364. if (o instanceof LookupSpec) {
  365. LookupSpec l = (LookupSpec) o;
  366. if (!l.script.equals(script)) {
  367. return false;
  368. } else if (!l.language.equals(language)) {
  369. return false;
  370. } else {
  371. return l.feature.equals(feature);
  372. }
  373. } else {
  374. return false;
  375. }
  376. }
  377. /** {@inheritDoc} */
  378. public int compareTo(Object o) {
  379. int d;
  380. if (o instanceof LookupSpec) {
  381. LookupSpec ls = (LookupSpec) o;
  382. if ((d = script.compareTo(ls.script)) == 0) {
  383. if ((d = language.compareTo(ls.language)) == 0) {
  384. if ((d = feature.compareTo(ls.feature)) == 0) {
  385. d = 0;
  386. }
  387. }
  388. }
  389. } else {
  390. d = -1;
  391. }
  392. return d;
  393. }
  394. /** {@inheritDoc} */
  395. public String toString() {
  396. StringBuffer sb = new StringBuffer(super.toString());
  397. sb.append("{");
  398. sb.append("<'" + script + "'");
  399. sb.append(",'" + language + "'");
  400. sb.append(",'" + feature + "'");
  401. sb.append(">}");
  402. return sb.toString();
  403. }
  404. }
  405. /**
  406. * The <code>LookupTable</code> class comprising an identifier and an ordered list
  407. * of glyph subtables, each of which employ the same lookup identifier.
  408. */
  409. public static class LookupTable implements Comparable {
  410. private final String id; // lookup identifier
  411. private final int idOrdinal; // parsed lookup identifier ordinal
  412. private final List/*<GlyphSubtable>*/ subtables; // list of subtables
  413. private boolean doesSub; // performs substitutions
  414. private boolean doesPos; // performs positioning
  415. private boolean frozen; // if true, then don't permit further subtable additions
  416. // frozen state
  417. private GlyphSubtable[] subtablesArray;
  418. private static GlyphSubtable[] subtablesArrayEmpty = new GlyphSubtable[0];
  419. /**
  420. * Instantiate a LookupTable.
  421. * @param id the lookup table's identifier
  422. * @param subtable an initial subtable (or null)
  423. */
  424. public LookupTable(String id, GlyphSubtable subtable) {
  425. this (id, makeSingleton(subtable));
  426. }
  427. /**
  428. * Instantiate a LookupTable.
  429. * @param id the lookup table's identifier
  430. * @param subtables a pre-poplated list of subtables or null
  431. */
  432. public LookupTable(String id, List/*<GlyphSubtable>*/ subtables) {
  433. assert id != null;
  434. assert id.length() != 0;
  435. assert id.startsWith("lu");
  436. this.id = id;
  437. this.idOrdinal = Integer.parseInt(id.substring(2));
  438. this.subtables = new LinkedList/*<GlyphSubtable>*/();
  439. if (subtables != null) {
  440. for (Iterator it = subtables.iterator(); it.hasNext(); ) {
  441. GlyphSubtable st = (GlyphSubtable) it.next();
  442. addSubtable(st);
  443. }
  444. }
  445. }
  446. /** @return the subtables as an array */
  447. public GlyphSubtable[] getSubtables() {
  448. if (frozen) {
  449. return (subtablesArray != null) ? subtablesArray : subtablesArrayEmpty;
  450. } else {
  451. if (doesSub) {
  452. return (GlyphSubtable[]) subtables.toArray(new GlyphSubstitutionSubtable [ subtables.size() ]);
  453. } else if (doesPos) {
  454. return (GlyphSubtable[]) subtables.toArray(new GlyphPositioningSubtable [ subtables.size() ]);
  455. } else {
  456. return null;
  457. }
  458. }
  459. }
  460. /**
  461. * Add a subtable into this lookup table's collecion of subtables according to its
  462. * natural order.
  463. * @param subtable to add
  464. * @return true if subtable was not already present, otherwise false
  465. */
  466. public boolean addSubtable(GlyphSubtable subtable) {
  467. boolean added = false;
  468. // ensure table is not frozen
  469. if (frozen) {
  470. throw new IllegalStateException("glyph table is frozen, subtable addition prohibited");
  471. }
  472. // validate subtable to ensure consistency with current subtables
  473. validateSubtable(subtable);
  474. // insert subtable into ordered list
  475. for (ListIterator/*<GlyphSubtable>*/ lit = subtables.listIterator(0); lit.hasNext(); ) {
  476. GlyphSubtable st = (GlyphSubtable) lit.next();
  477. int d;
  478. if ((d = subtable.compareTo(st)) < 0) {
  479. // insert within list
  480. lit.set(subtable);
  481. lit.add(st);
  482. added = true;
  483. } else if (d == 0) {
  484. // duplicate entry is ignored
  485. added = false;
  486. subtable = null;
  487. }
  488. }
  489. // append at end of list
  490. if (!added && (subtable != null)) {
  491. subtables.add(subtable);
  492. added = true;
  493. }
  494. return added;
  495. }
  496. private void validateSubtable(GlyphSubtable subtable) {
  497. if (subtable == null) {
  498. throw new AdvancedTypographicTableFormatException("subtable must be non-null");
  499. }
  500. if (subtable instanceof GlyphSubstitutionSubtable) {
  501. if (doesPos) {
  502. throw new AdvancedTypographicTableFormatException("subtable must be positioning subtable, but is: " + subtable);
  503. } else {
  504. doesSub = true;
  505. }
  506. }
  507. if (subtable instanceof GlyphPositioningSubtable) {
  508. if (doesSub) {
  509. throw new AdvancedTypographicTableFormatException("subtable must be substitution subtable, but is: " + subtable);
  510. } else {
  511. doesPos = true;
  512. }
  513. }
  514. if (subtables.size() > 0) {
  515. GlyphSubtable st = (GlyphSubtable) subtables.get(0);
  516. if (!st.isCompatible(subtable)) {
  517. throw new AdvancedTypographicTableFormatException("subtable " + subtable + " is not compatible with subtable " + st);
  518. }
  519. }
  520. }
  521. /**
  522. * Freeze subtables, i.e., do not allow further subtable addition, and
  523. * create resulting cached state. In addition, resolve any references to
  524. * lookup tables that appear in this lookup table's subtables.
  525. * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
  526. */
  527. public void freezeSubtables(Map/*<String,LookupTable>*/ lookupTables) {
  528. if (!frozen) {
  529. GlyphSubtable[] sta = getSubtables();
  530. resolveLookupReferences(sta, lookupTables);
  531. this.subtablesArray = sta;
  532. this.frozen = true;
  533. }
  534. }
  535. private void resolveLookupReferences(GlyphSubtable[] subtables, Map/*<String,LookupTable>*/ lookupTables) {
  536. if (subtables != null) {
  537. for (int i = 0, n = subtables.length; i < n; i++) {
  538. GlyphSubtable st = subtables [ i ];
  539. if (st != null) {
  540. st.resolveLookupReferences(lookupTables);
  541. }
  542. }
  543. }
  544. }
  545. /**
  546. * Determine if this glyph table performs substitution.
  547. * @return true if it performs substitution
  548. */
  549. public boolean performsSubstitution() {
  550. return doesSub;
  551. }
  552. /**
  553. * Perform substitution processing using this lookup table's subtables.
  554. * @param gs an input glyph sequence
  555. * @param script a script identifier
  556. * @param language a language identifier
  557. * @param feature a feature identifier
  558. * @param sct a script specific context tester (or null)
  559. * @return the substituted (output) glyph sequence
  560. */
  561. public GlyphSequence substitute(GlyphSequence gs, String script, String language, String feature, ScriptContextTester sct) {
  562. if (performsSubstitution()) {
  563. return GlyphSubstitutionSubtable.substitute(gs, script, language, feature, (GlyphSubstitutionSubtable[]) subtablesArray, sct);
  564. } else {
  565. return gs;
  566. }
  567. }
  568. /**
  569. * Perform substitution processing on an existing glyph substitution state object using this lookup table's subtables.
  570. * @param ss a glyph substitution state object
  571. * @param sequenceIndex if non negative, then apply subtables only at specified sequence index
  572. * @return the substituted (output) glyph sequence
  573. */
  574. public GlyphSequence substitute(GlyphSubstitutionState ss, int sequenceIndex) {
  575. if (performsSubstitution()) {
  576. return GlyphSubstitutionSubtable.substitute(ss, (GlyphSubstitutionSubtable[]) subtablesArray, sequenceIndex);
  577. } else {
  578. return ss.getInput();
  579. }
  580. }
  581. /**
  582. * Determine if this glyph table performs positioning.
  583. * @return true if it performs positioning
  584. */
  585. public boolean performsPositioning() {
  586. return doesPos;
  587. }
  588. /**
  589. * Perform positioning processing using this lookup table's subtables.
  590. * @param gs an input glyph sequence
  591. * @param script a script identifier
  592. * @param language a language identifier
  593. * @param feature a feature identifier
  594. * @param fontSize size in device units
  595. * @param widths array of default advancements for each glyph in font
  596. * @param adjustments accumulated adjustments array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
  597. * with one 4-tuple for each element of glyph sequence
  598. * @param sct a script specific context tester (or null)
  599. * @return true if some adjustment is not zero; otherwise, false
  600. */
  601. public boolean position(GlyphSequence gs, String script, String language, String feature, int fontSize, int[] widths, int[][] adjustments, ScriptContextTester sct) {
  602. if (performsPositioning()) {
  603. return GlyphPositioningSubtable.position(gs, script, language, feature, fontSize, (GlyphPositioningSubtable[]) subtablesArray, widths, adjustments, sct);
  604. } else {
  605. return false;
  606. }
  607. }
  608. /**
  609. * Perform positioning processing on an existing glyph positioning state object using this lookup table's subtables.
  610. * @param ps a glyph positioning state object
  611. * @param sequenceIndex if non negative, then apply subtables only at specified sequence index
  612. * @return true if some adjustment is not zero; otherwise, false
  613. */
  614. public boolean position(GlyphPositioningState ps, int sequenceIndex) {
  615. if (performsPositioning()) {
  616. return GlyphPositioningSubtable.position(ps, (GlyphPositioningSubtable[]) subtablesArray, sequenceIndex);
  617. } else {
  618. return false;
  619. }
  620. }
  621. /** {@inheritDoc} */
  622. public int hashCode() {
  623. return idOrdinal;
  624. }
  625. /**
  626. * {@inheritDoc}
  627. * @return true if identifier of the specified lookup table is the same
  628. * as the identifier of this lookup table
  629. */
  630. public boolean equals(Object o) {
  631. if (o instanceof LookupTable) {
  632. LookupTable lt = (LookupTable) o;
  633. return idOrdinal == lt.idOrdinal;
  634. } else {
  635. return false;
  636. }
  637. }
  638. /**
  639. * {@inheritDoc}
  640. * @return the result of comparing the identifier of the specified lookup table with
  641. * the identifier of this lookup table; lookup table identifiers take the form
  642. * "lu(DIGIT)+", with comparison based on numerical ordering of numbers expressed by
  643. * (DIGIT)+.
  644. */
  645. public int compareTo(Object o) {
  646. if (o instanceof LookupTable) {
  647. LookupTable lt = (LookupTable) o;
  648. int i = idOrdinal;
  649. int j = lt.idOrdinal;
  650. if (i < j) {
  651. return -1;
  652. } else if (i > j) {
  653. return 1;
  654. } else {
  655. return 0;
  656. }
  657. } else {
  658. return -1;
  659. }
  660. }
  661. /** {@inheritDoc} */
  662. public String toString() {
  663. StringBuffer sb = new StringBuffer();
  664. sb.append("{ ");
  665. sb.append("id = " + id);
  666. sb.append(", subtables = " + subtables);
  667. sb.append(" }");
  668. return sb.toString();
  669. }
  670. private static List/*<GlyphSubtable>*/ makeSingleton(GlyphSubtable subtable) {
  671. if (subtable == null) {
  672. return null;
  673. } else {
  674. List/*<GlyphSubtable>*/ stl = new ArrayList/*<GlyphSubtable>*/(1);
  675. stl.add(subtable);
  676. return stl;
  677. }
  678. }
  679. }
  680. /**
  681. * The <code>UseSpec</code> class comprises a lookup table reference
  682. * and the feature that selected the lookup table.
  683. */
  684. public static class UseSpec implements Comparable {
  685. /** lookup table to apply */
  686. private final LookupTable lookupTable;
  687. /** feature that caused selection of the lookup table */
  688. private final String feature;
  689. /**
  690. * Construct a glyph lookup table use specification.
  691. * @param lookupTable a glyph lookup table
  692. * @param feature a feature that caused lookup table selection
  693. */
  694. public UseSpec(LookupTable lookupTable, String feature) {
  695. this.lookupTable = lookupTable;
  696. this.feature = feature;
  697. }
  698. /** @return the lookup table */
  699. public LookupTable getLookupTable() {
  700. return lookupTable;
  701. }
  702. /** @return the feature that selected this lookup table */
  703. public String getFeature() {
  704. return feature;
  705. }
  706. /**
  707. * Perform substitution processing using this use specification's lookup table.
  708. * @param gs an input glyph sequence
  709. * @param script a script identifier
  710. * @param language a language identifier
  711. * @param sct a script specific context tester (or null)
  712. * @return the substituted (output) glyph sequence
  713. */
  714. public GlyphSequence substitute(GlyphSequence gs, String script, String language, ScriptContextTester sct) {
  715. return lookupTable.substitute(gs, script, language, feature, sct);
  716. }
  717. /**
  718. * Perform positioning processing using this use specification's lookup table.
  719. * @param gs an input glyph sequence
  720. * @param script a script identifier
  721. * @param language a language identifier
  722. * @param fontSize size in device units
  723. * @param widths array of default advancements for each glyph in font
  724. * @param adjustments accumulated adjustments array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
  725. * with one 4-tuple for each element of glyph sequence
  726. * @param sct a script specific context tester (or null)
  727. * @return true if some adjustment is not zero; otherwise, false
  728. */
  729. public boolean position(GlyphSequence gs, String script, String language, int fontSize, int[] widths, int[][] adjustments, ScriptContextTester sct) {
  730. return lookupTable.position(gs, script, language, feature, fontSize, widths, adjustments, sct);
  731. }
  732. /** {@inheritDoc} */
  733. public int hashCode() {
  734. return lookupTable.hashCode();
  735. }
  736. /** {@inheritDoc} */
  737. public boolean equals(Object o) {
  738. if (o instanceof UseSpec) {
  739. UseSpec u = (UseSpec) o;
  740. return lookupTable.equals(u.lookupTable);
  741. } else {
  742. return false;
  743. }
  744. }
  745. /** {@inheritDoc} */
  746. public int compareTo(Object o) {
  747. if (o instanceof UseSpec) {
  748. UseSpec u = (UseSpec) o;
  749. return lookupTable.compareTo(u.lookupTable);
  750. } else {
  751. return -1;
  752. }
  753. }
  754. }
  755. /**
  756. * The <code>RuleLookup</code> class implements a rule lookup record, comprising
  757. * a glyph sequence index and a lookup table index (in an applicable lookup list).
  758. */
  759. public static class RuleLookup {
  760. private final int sequenceIndex; // index into input glyph sequence
  761. private final int lookupIndex; // lookup list index
  762. private LookupTable lookup; // resolved lookup table
  763. /**
  764. * Instantiate a RuleLookup.
  765. * @param sequenceIndex the index into the input sequence
  766. * @param lookupIndex the lookup table index
  767. */
  768. public RuleLookup(int sequenceIndex, int lookupIndex) {
  769. this.sequenceIndex = sequenceIndex;
  770. this.lookupIndex = lookupIndex;
  771. this.lookup = null;
  772. }
  773. /** @return the sequence index */
  774. public int getSequenceIndex() {
  775. return sequenceIndex;
  776. }
  777. /** @return the lookup index */
  778. public int getLookupIndex() {
  779. return lookupIndex;
  780. }
  781. /** @return the lookup table */
  782. public LookupTable getLookup() {
  783. return lookup;
  784. }
  785. /**
  786. * Resolve references to lookup tables.
  787. * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
  788. */
  789. public void resolveLookupReferences(Map/*<String,LookupTable>*/ lookupTables) {
  790. if (lookupTables != null) {
  791. String lid = "lu" + Integer.toString(lookupIndex);
  792. LookupTable lt = (LookupTable) lookupTables.get(lid);
  793. if (lt != null) {
  794. this.lookup = lt;
  795. } else {
  796. log.warn("unable to resolve glyph lookup table reference '" + lid + "' amongst lookup tables: " + lookupTables.values());
  797. }
  798. }
  799. }
  800. /** {@inheritDoc} */
  801. public String toString() {
  802. return "{ sequenceIndex = " + sequenceIndex + ", lookupIndex = " + lookupIndex + " }";
  803. }
  804. }
  805. /**
  806. * The <code>Rule</code> class implements an array of rule lookup records.
  807. */
  808. public abstract static class Rule {
  809. private final RuleLookup[] lookups; // rule lookups
  810. private final int inputSequenceLength; // input sequence length
  811. /**
  812. * Instantiate a Rule.
  813. * @param lookups the rule's lookups
  814. * @param inputSequenceLength the number of glyphs in the input sequence for this rule
  815. */
  816. protected Rule(RuleLookup[] lookups, int inputSequenceLength) {
  817. assert lookups != null;
  818. this.lookups = lookups;
  819. this.inputSequenceLength = inputSequenceLength;
  820. }
  821. /** @return the lookups */
  822. public RuleLookup[] getLookups() {
  823. return lookups;
  824. }
  825. /** @return the input sequence length */
  826. public int getInputSequenceLength() {
  827. return inputSequenceLength;
  828. }
  829. /**
  830. * Resolve references to lookup tables, e.g., in RuleLookup, to the lookup tables themselves.
  831. * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
  832. */
  833. public void resolveLookupReferences(Map/*<String,LookupTable>*/ lookupTables) {
  834. if (lookups != null) {
  835. for (int i = 0, n = lookups.length; i < n; i++) {
  836. RuleLookup l = lookups [ i ];
  837. if (l != null) {
  838. l.resolveLookupReferences(lookupTables);
  839. }
  840. }
  841. }
  842. }
  843. /** {@inheritDoc} */
  844. public String toString() {
  845. return "{ lookups = " + Arrays.toString(lookups) + ", inputSequenceLength = " + inputSequenceLength + " }";
  846. }
  847. }
  848. /**
  849. * The <code>GlyphSequenceRule</code> class implements a subclass of <code>Rule</code>
  850. * that supports matching on a specific glyph sequence.
  851. */
  852. public static class GlyphSequenceRule extends Rule {
  853. private final int[] glyphs; // glyphs
  854. /**
  855. * Instantiate a GlyphSequenceRule.
  856. * @param lookups the rule's lookups
  857. * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
  858. * @param glyphs the rule's glyph sequence to match, starting with second glyph in sequence
  859. */
  860. public GlyphSequenceRule(RuleLookup[] lookups, int inputSequenceLength, int[] glyphs) {
  861. super(lookups, inputSequenceLength);
  862. assert glyphs != null;
  863. this.glyphs = glyphs;
  864. }
  865. /**
  866. * Obtain glyphs. N.B. that this array starts with the second
  867. * glyph of the input sequence.
  868. * @return the glyphs
  869. */
  870. public int[] getGlyphs() {
  871. return glyphs;
  872. }
  873. /**
  874. * Obtain glyphs augmented by specified first glyph entry.
  875. * @param firstGlyph to fill in first glyph entry
  876. * @return the glyphs augmented by first glyph
  877. */
  878. public int[] getGlyphs(int firstGlyph) {
  879. int[] ga = new int [ glyphs.length + 1 ];
  880. ga [ 0 ] = firstGlyph;
  881. System.arraycopy(glyphs, 0, ga, 1, glyphs.length);
  882. return ga;
  883. }
  884. /** {@inheritDoc} */
  885. public String toString() {
  886. StringBuffer sb = new StringBuffer();
  887. sb.append("{ ");
  888. sb.append("lookups = " + Arrays.toString(getLookups()));
  889. sb.append(", glyphs = " + Arrays.toString(glyphs));
  890. sb.append(" }");
  891. return sb.toString();
  892. }
  893. }
  894. /**
  895. * The <code>ClassSequenceRule</code> class implements a subclass of <code>Rule</code>
  896. * that supports matching on a specific glyph class sequence.
  897. */
  898. public static class ClassSequenceRule extends Rule {
  899. private final int[] classes; // glyph classes
  900. /**
  901. * Instantiate a ClassSequenceRule.
  902. * @param lookups the rule's lookups
  903. * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
  904. * @param classes the rule's glyph class sequence to match, starting with second glyph in sequence
  905. */
  906. public ClassSequenceRule(RuleLookup[] lookups, int inputSequenceLength, int[] classes) {
  907. super(lookups, inputSequenceLength);
  908. assert classes != null;
  909. this.classes = classes;
  910. }
  911. /**
  912. * Obtain glyph classes. N.B. that this array starts with the class of the second
  913. * glyph of the input sequence.
  914. * @return the classes
  915. */
  916. public int[] getClasses() {
  917. return classes;
  918. }
  919. /**
  920. * Obtain glyph classes augmented by specified first class entry.
  921. * @param firstClass to fill in first class entry
  922. * @return the classes augmented by first class
  923. */
  924. public int[] getClasses(int firstClass) {
  925. int[] ca = new int [ classes.length + 1 ];
  926. ca [ 0 ] = firstClass;
  927. System.arraycopy(classes, 0, ca, 1, classes.length);
  928. return ca;
  929. }
  930. /** {@inheritDoc} */
  931. public String toString() {
  932. StringBuffer sb = new StringBuffer();
  933. sb.append("{ ");
  934. sb.append("lookups = " + Arrays.toString(getLookups()));
  935. sb.append(", classes = " + Arrays.toString(classes));
  936. sb.append(" }");
  937. return sb.toString();
  938. }
  939. }
  940. /**
  941. * The <code>CoverageSequenceRule</code> class implements a subclass of <code>Rule</code>
  942. * that supports matching on a specific glyph coverage sequence.
  943. */
  944. public static class CoverageSequenceRule extends Rule {
  945. private final GlyphCoverageTable[] coverages; // glyph coverages
  946. /**
  947. * Instantiate a ClassSequenceRule.
  948. * @param lookups the rule's lookups
  949. * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
  950. * @param coverages the rule's glyph coverage sequence to match, starting with first glyph in sequence
  951. */
  952. public CoverageSequenceRule(RuleLookup[] lookups, int inputSequenceLength, GlyphCoverageTable[] coverages) {
  953. super(lookups, inputSequenceLength);
  954. assert coverages != null;
  955. this.coverages = coverages;
  956. }
  957. /** @return the coverages */
  958. public GlyphCoverageTable[] getCoverages() {
  959. return coverages;
  960. }
  961. /** {@inheritDoc} */
  962. public String toString() {
  963. StringBuffer sb = new StringBuffer();
  964. sb.append("{ ");
  965. sb.append("lookups = " + Arrays.toString(getLookups()));
  966. sb.append(", coverages = " + Arrays.toString(coverages));
  967. sb.append(" }");
  968. return sb.toString();
  969. }
  970. }
  971. /**
  972. * The <code>ChainedGlyphSequenceRule</code> class implements a subclass of <code>GlyphSequenceRule</code>
  973. * that supports matching on a specific glyph sequence in a specific chained contextual.
  974. */
  975. public static class ChainedGlyphSequenceRule extends GlyphSequenceRule {
  976. private final int[] backtrackGlyphs; // backtrack glyphs
  977. private final int[] lookaheadGlyphs; // lookahead glyphs
  978. /**
  979. * Instantiate a ChainedGlyphSequenceRule.
  980. * @param lookups the rule's lookups
  981. * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
  982. * @param glyphs the rule's input glyph sequence to match, starting with second glyph in sequence
  983. * @param backtrackGlyphs the rule's backtrack glyph sequence to match, starting with first glyph in sequence
  984. * @param lookaheadGlyphs the rule's lookahead glyph sequence to match, starting with first glyph in sequence
  985. */
  986. public ChainedGlyphSequenceRule(RuleLookup[] lookups, int inputSequenceLength, int[] glyphs, int[] backtrackGlyphs, int[] lookaheadGlyphs) {
  987. super(lookups, inputSequenceLength, glyphs);
  988. assert backtrackGlyphs != null;
  989. assert lookaheadGlyphs != null;
  990. this.backtrackGlyphs = backtrackGlyphs;
  991. this.lookaheadGlyphs = lookaheadGlyphs;
  992. }
  993. /** @return the backtrack glyphs */
  994. public int[] getBacktrackGlyphs() {
  995. return backtrackGlyphs;
  996. }
  997. /** @return the lookahead glyphs */
  998. public int[] getLookaheadGlyphs() {
  999. return lookaheadGlyphs;
  1000. }
  1001. /** {@inheritDoc} */
  1002. public String toString() {
  1003. StringBuffer sb = new StringBuffer();
  1004. sb.append("{ ");
  1005. sb.append("lookups = " + Arrays.toString(getLookups()));
  1006. sb.append(", glyphs = " + Arrays.toString(getGlyphs()));
  1007. sb.append(", backtrackGlyphs = " + Arrays.toString(backtrackGlyphs));
  1008. sb.append(", lookaheadGlyphs = " + Arrays.toString(lookaheadGlyphs));
  1009. sb.append(" }");
  1010. return sb.toString();
  1011. }
  1012. }
  1013. /**
  1014. * The <code>ChainedClassSequenceRule</code> class implements a subclass of <code>ClassSequenceRule</code>
  1015. * that supports matching on a specific glyph class sequence in a specific chained contextual.
  1016. */
  1017. public static class ChainedClassSequenceRule extends ClassSequenceRule {
  1018. private final int[] backtrackClasses; // backtrack classes
  1019. private final int[] lookaheadClasses; // lookahead classes
  1020. /**
  1021. * Instantiate a ChainedClassSequenceRule.
  1022. * @param lookups the rule's lookups
  1023. * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
  1024. * @param classes the rule's input glyph class sequence to match, starting with second glyph in sequence
  1025. * @param backtrackClasses the rule's backtrack glyph class sequence to match, starting with first glyph in sequence
  1026. * @param lookaheadClasses the rule's lookahead glyph class sequence to match, starting with first glyph in sequence
  1027. */
  1028. public ChainedClassSequenceRule(RuleLookup[] lookups, int inputSequenceLength, int[] classes, int[] backtrackClasses, int[] lookaheadClasses) {
  1029. super(lookups, inputSequenceLength, classes);
  1030. assert backtrackClasses != null;
  1031. assert lookaheadClasses != null;
  1032. this.backtrackClasses = backtrackClasses;
  1033. this.lookaheadClasses = lookaheadClasses;
  1034. }
  1035. /** @return the backtrack classes */
  1036. public int[] getBacktrackClasses() {
  1037. return backtrackClasses;
  1038. }
  1039. /** @return the lookahead classes */
  1040. public int[] getLookaheadClasses() {
  1041. return lookaheadClasses;
  1042. }
  1043. /** {@inheritDoc} */
  1044. public String toString() {
  1045. StringBuffer sb = new StringBuffer();
  1046. sb.append("{ ");
  1047. sb.append("lookups = " + Arrays.toString(getLookups()));
  1048. sb.append(", classes = " + Arrays.toString(getClasses()));
  1049. sb.append(", backtrackClasses = " + Arrays.toString(backtrackClasses));
  1050. sb.append(", lookaheadClasses = " + Arrays.toString(lookaheadClasses));
  1051. sb.append(" }");
  1052. return sb.toString();
  1053. }
  1054. }
  1055. /**
  1056. * The <code>ChainedCoverageSequenceRule</code> class implements a subclass of <code>CoverageSequenceRule</code>
  1057. * that supports matching on a specific glyph class sequence in a specific chained contextual.
  1058. */
  1059. public static class ChainedCoverageSequenceRule extends CoverageSequenceRule {
  1060. private final GlyphCoverageTable[] backtrackCoverages; // backtrack coverages
  1061. private final GlyphCoverageTable[] lookaheadCoverages; // lookahead coverages
  1062. /**
  1063. * Instantiate a ChainedCoverageSequenceRule.
  1064. * @param lookups the rule's lookups
  1065. * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
  1066. * @param coverages the rule's input glyph class sequence to match, starting with first glyph in sequence
  1067. * @param backtrackCoverages the rule's backtrack glyph class sequence to match, starting with first glyph in sequence
  1068. * @param lookaheadCoverages the rule's lookahead glyph class sequence to match, starting with first glyph in sequence
  1069. */
  1070. public ChainedCoverageSequenceRule(RuleLookup[] lookups, int inputSequenceLength, GlyphCoverageTable[] coverages, GlyphCoverageTable[] backtrackCoverages, GlyphCoverageTable[] lookaheadCoverages) {
  1071. super(lookups, inputSequenceLength, coverages);
  1072. assert backtrackCoverages != null;
  1073. assert lookaheadCoverages != null;
  1074. this.backtrackCoverages = backtrackCoverages;
  1075. this.lookaheadCoverages = lookaheadCoverages;
  1076. }
  1077. /** @return the backtrack coverages */
  1078. public GlyphCoverageTable[] getBacktrackCoverages() {
  1079. return backtrackCoverages;
  1080. }
  1081. /** @return the lookahead coverages */
  1082. public GlyphCoverageTable[] getLookaheadCoverages() {
  1083. return lookaheadCoverages;
  1084. }
  1085. /** {@inheritDoc} */
  1086. public String toString() {
  1087. StringBuffer sb = new StringBuffer();
  1088. sb.append("{ ");
  1089. sb.append("lookups = " + Arrays.toString(getLookups()));
  1090. sb.append(", coverages = " + Arrays.toString(getCoverages()));
  1091. sb.append(", backtrackCoverages = " + Arrays.toString(backtrackCoverages));
  1092. sb.append(", lookaheadCoverages = " + Arrays.toString(lookaheadCoverages));
  1093. sb.append(" }");
  1094. return sb.toString();
  1095. }
  1096. }
  1097. /**
  1098. * The <code>RuleSet</code> class implements a collection of rules, which
  1099. * may or may not be the same rule type.
  1100. */
  1101. public static class RuleSet {
  1102. private final Rule[] rules; // set of rules
  1103. /**
  1104. * Instantiate a Rule Set.
  1105. * @param rules the rules
  1106. * @throws AdvancedTypographicTableFormatException if rules or some element of rules is null
  1107. */
  1108. public RuleSet(Rule[] rules) throws AdvancedTypographicTableFormatException {
  1109. // enforce rules array instance
  1110. if (rules == null) {
  1111. throw new AdvancedTypographicTableFormatException("rules[] is null");
  1112. }
  1113. this.rules = rules;
  1114. }
  1115. /** @return the rules */
  1116. public Rule[] getRules() {
  1117. return rules;
  1118. }
  1119. /**
  1120. * Resolve references to lookup tables, e.g., in RuleLookup, to the lookup tables themselves.
  1121. * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
  1122. */
  1123. public void resolveLookupReferences(Map/*<String,LookupTable>*/ lookupTables) {
  1124. if (rules != null) {
  1125. for (int i = 0, n = rules.length; i < n; i++) {
  1126. Rule r = rules [ i ];
  1127. if (r != null) {
  1128. r.resolveLookupReferences(lookupTables);
  1129. }
  1130. }
  1131. }
  1132. }
  1133. /** {@inheritDoc} */
  1134. public String toString() {
  1135. return "{ rules = " + Arrays.toString(rules) + " }";
  1136. }
  1137. }
  1138. /**
  1139. * The <code>HomogenousRuleSet</code> class implements a collection of rules, which
  1140. * must be the same rule type (i.e., same concrete rule class) or null.
  1141. */
  1142. public static class HomogeneousRuleSet extends RuleSet {
  1143. /**
  1144. * Instantiate a Homogeneous Rule Set.
  1145. * @param rules the rules
  1146. * @throws AdvancedTypographicTableFormatException if some rule[i] is not an instance of rule[0]
  1147. */
  1148. public HomogeneousRuleSet(Rule[] rules) throws AdvancedTypographicTableFormatException {
  1149. super(rules);
  1150. // find first non-null rule
  1151. Rule r0 = null;
  1152. for (int i = 1, n = rules.length; (r0 == null) && (i < n); i++) {
  1153. if (rules[i] != null) {
  1154. r0 = rules[i];
  1155. }
  1156. }
  1157. // enforce rule instance homogeneity
  1158. if (r0 != null) {
  1159. Class c = r0.getClass();
  1160. for (int i = 1, n = rules.length; i < n; i++) {
  1161. Rule r = rules[i];
  1162. if ((r != null) && !c.isInstance(r)) {
  1163. throw new AdvancedTypographicTableFormatException("rules[" + i + "] is not an instance of " + c.getName());
  1164. }
  1165. }
  1166. }
  1167. }
  1168. }
  1169. }