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