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.Collections; 25 import java.util.List; 26 import java.util.Map; 27 28 import org.apache.commons.logging.Log; 29 import org.apache.commons.logging.LogFactory; 30 31 import org.apache.fop.complexscripts.scripts.ScriptProcessor; 32 import org.apache.fop.complexscripts.util.GlyphSequence; 33 import org.apache.fop.complexscripts.util.GlyphTester; 34 35 // CSOFF: LineLengthCheck 36 37 /** 38 * <p>The <code>GlyphPositioningTable</code> class is a glyph table that implements 39 * <code>GlyphPositioning</code> functionality.</p> 40 * 41 * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p> 42 */ 43 public class GlyphPositioningTable extends GlyphTable { 44 45 /** logging instance */ 46 private static final Log log = LogFactory.getLog(GlyphPositioningTable.class); 47 48 /** single positioning subtable type */ 49 public static final int GPOS_LOOKUP_TYPE_SINGLE = 1; 50 /** multiple positioning subtable type */ 51 public static final int GPOS_LOOKUP_TYPE_PAIR = 2; 52 /** cursive positioning subtable type */ 53 public static final int GPOS_LOOKUP_TYPE_CURSIVE = 3; 54 /** mark to base positioning subtable type */ 55 public static final int GPOS_LOOKUP_TYPE_MARK_TO_BASE = 4; 56 /** mark to ligature positioning subtable type */ 57 public static final int GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE = 5; 58 /** mark to mark positioning subtable type */ 59 public static final int GPOS_LOOKUP_TYPE_MARK_TO_MARK = 6; 60 /** contextual positioning subtable type */ 61 public static final int GPOS_LOOKUP_TYPE_CONTEXTUAL = 7; 62 /** chained contextual positioning subtable type */ 63 public static final int GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL = 8; 64 /** extension positioning subtable type */ 65 public static final int GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING = 9; 66 67 /** 68 * Instantiate a <code>GlyphPositioningTable</code> object using the specified lookups 69 * and subtables. 70 * @param gdef glyph definition table that applies 71 * @param lookups a map of lookup specifications to subtable identifier strings 72 * @param subtables a list of identified subtables 73 */ GlyphPositioningTable(GlyphDefinitionTable gdef, Map lookups, List subtables, Map<String, ScriptProcessor> processors)74 public GlyphPositioningTable(GlyphDefinitionTable gdef, Map lookups, List subtables, 75 Map<String, ScriptProcessor> processors) { 76 super(gdef, lookups, processors); 77 if ((subtables == null) || (subtables.size() == 0)) { 78 throw new AdvancedTypographicTableFormatException("subtables must be non-empty"); 79 } else { 80 for (Object o : subtables) { 81 if (o instanceof GlyphPositioningSubtable) { 82 addSubtable((GlyphSubtable) o); 83 } else { 84 throw new AdvancedTypographicTableFormatException("subtable must be a glyph positioning subtable"); 85 } 86 } 87 freezeSubtables(); 88 } 89 } 90 91 /** 92 * Map a lookup type name to its constant (integer) value. 93 * @param name lookup type name 94 * @return lookup type 95 */ getLookupTypeFromName(String name)96 public static int getLookupTypeFromName(String name) { 97 int t; 98 String s = name.toLowerCase(); 99 if ("single".equals(s)) { 100 t = GPOS_LOOKUP_TYPE_SINGLE; 101 } else if ("pair".equals(s)) { 102 t = GPOS_LOOKUP_TYPE_PAIR; 103 } else if ("cursive".equals(s)) { 104 t = GPOS_LOOKUP_TYPE_CURSIVE; 105 } else if ("marktobase".equals(s)) { 106 t = GPOS_LOOKUP_TYPE_MARK_TO_BASE; 107 } else if ("marktoligature".equals(s)) { 108 t = GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE; 109 } else if ("marktomark".equals(s)) { 110 t = GPOS_LOOKUP_TYPE_MARK_TO_MARK; 111 } else if ("contextual".equals(s)) { 112 t = GPOS_LOOKUP_TYPE_CONTEXTUAL; 113 } else if ("chainedcontextual".equals(s)) { 114 t = GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL; 115 } else if ("extensionpositioning".equals(s)) { 116 t = GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING; 117 } else { 118 t = -1; 119 } 120 return t; 121 } 122 123 /** 124 * Map a lookup type constant (integer) value to its name. 125 * @param type lookup type 126 * @return lookup type name 127 */ getLookupTypeName(int type)128 public static String getLookupTypeName(int type) { 129 String tn; 130 switch (type) { 131 case GPOS_LOOKUP_TYPE_SINGLE: 132 tn = "single"; 133 break; 134 case GPOS_LOOKUP_TYPE_PAIR: 135 tn = "pair"; 136 break; 137 case GPOS_LOOKUP_TYPE_CURSIVE: 138 tn = "cursive"; 139 break; 140 case GPOS_LOOKUP_TYPE_MARK_TO_BASE: 141 tn = "marktobase"; 142 break; 143 case GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE: 144 tn = "marktoligature"; 145 break; 146 case GPOS_LOOKUP_TYPE_MARK_TO_MARK: 147 tn = "marktomark"; 148 break; 149 case GPOS_LOOKUP_TYPE_CONTEXTUAL: 150 tn = "contextual"; 151 break; 152 case GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL: 153 tn = "chainedcontextual"; 154 break; 155 case GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING: 156 tn = "extensionpositioning"; 157 break; 158 default: 159 tn = "unknown"; 160 break; 161 } 162 return tn; 163 } 164 165 /** 166 * Create a positioning subtable according to the specified arguments. 167 * @param type subtable type 168 * @param id subtable identifier 169 * @param sequence subtable sequence 170 * @param flags subtable flags 171 * @param format subtable format 172 * @param coverage subtable coverage table 173 * @param entries subtable entries 174 * @return a glyph subtable instance 175 */ createSubtable(int type, String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)176 public static GlyphSubtable createSubtable(int type, String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 177 GlyphSubtable st = null; 178 switch (type) { 179 case GPOS_LOOKUP_TYPE_SINGLE: 180 st = SingleSubtable.create(id, sequence, flags, format, coverage, entries); 181 break; 182 case GPOS_LOOKUP_TYPE_PAIR: 183 st = PairSubtable.create(id, sequence, flags, format, coverage, entries); 184 break; 185 case GPOS_LOOKUP_TYPE_CURSIVE: 186 st = CursiveSubtable.create(id, sequence, flags, format, coverage, entries); 187 break; 188 case GPOS_LOOKUP_TYPE_MARK_TO_BASE: 189 st = MarkToBaseSubtable.create(id, sequence, flags, format, coverage, entries); 190 break; 191 case GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE: 192 st = MarkToLigatureSubtable.create(id, sequence, flags, format, coverage, entries); 193 break; 194 case GPOS_LOOKUP_TYPE_MARK_TO_MARK: 195 st = MarkToMarkSubtable.create(id, sequence, flags, format, coverage, entries); 196 break; 197 case GPOS_LOOKUP_TYPE_CONTEXTUAL: 198 st = ContextualSubtable.create(id, sequence, flags, format, coverage, entries); 199 break; 200 case GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL: 201 st = ChainedContextualSubtable.create(id, sequence, flags, format, coverage, entries); 202 break; 203 default: 204 break; 205 } 206 return st; 207 } 208 209 /** 210 * Create a positioning subtable according to the specified arguments. 211 * @param type subtable type 212 * @param id subtable identifier 213 * @param sequence subtable sequence 214 * @param flags subtable flags 215 * @param format subtable format 216 * @param coverage list of coverage table entries 217 * @param entries subtable entries 218 * @return a glyph subtable instance 219 */ createSubtable(int type, String id, int sequence, int flags, int format, List coverage, List entries)220 public static GlyphSubtable createSubtable(int type, String id, int sequence, int flags, int format, List coverage, List entries) { 221 return createSubtable(type, id, sequence, flags, format, GlyphCoverageTable.createCoverageTable(coverage), entries); 222 } 223 224 /** 225 * Perform positioning processing using all matching lookups. 226 * @param gs an input glyph sequence 227 * @param script a script identifier 228 * @param language a language identifier 229 * @param fontSize size in device units 230 * @param widths array of default advancements for each glyph 231 * @param adjustments accumulated adjustments array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order, 232 * with one 4-tuple for each element of glyph sequence 233 * @return true if some adjustment is not zero; otherwise, false 234 */ position(GlyphSequence gs, String script, String language, int fontSize, int[] widths, int[][] adjustments)235 public boolean position(GlyphSequence gs, String script, String language, int fontSize, int[] widths, int[][] adjustments) { 236 Map<LookupSpec, List<LookupTable>> lookups = matchLookups(script, language, "*"); 237 if ((lookups != null) && (lookups.size() > 0)) { 238 ScriptProcessor sp = ScriptProcessor.getInstance(script, processors); 239 return sp.position(this, gs, script, language, fontSize, lookups, widths, adjustments); 240 } else { 241 return false; 242 } 243 } 244 245 private abstract static class SingleSubtable extends GlyphPositioningSubtable { SingleSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)246 SingleSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 247 super(id, sequence, flags, format, coverage); 248 } 249 /** {@inheritDoc} */ getType()250 public int getType() { 251 return GPOS_LOOKUP_TYPE_SINGLE; 252 } 253 /** {@inheritDoc} */ isCompatible(GlyphSubtable subtable)254 public boolean isCompatible(GlyphSubtable subtable) { 255 return subtable instanceof SingleSubtable; 256 } 257 /** {@inheritDoc} */ position(GlyphPositioningState ps)258 public boolean position(GlyphPositioningState ps) { 259 int gi = ps.getGlyph(); 260 int ci; 261 if ((ci = getCoverageIndex(gi)) < 0) { 262 return false; 263 } else { 264 Value v = getValue(ci, gi); 265 if (v != null) { 266 if (ps.adjust(v)) { 267 ps.setAdjusted(true); 268 } 269 ps.consume(1); 270 } 271 return true; 272 } 273 } 274 /** 275 * Obtain positioning value for coverage index. 276 * @param ci coverage index 277 * @param gi input glyph index 278 * @return positioning value or null if none applies 279 */ getValue(int ci, int gi)280 public abstract Value getValue(int ci, int gi); create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)281 static GlyphPositioningSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 282 if (format == 1) { 283 return new SingleSubtableFormat1(id, sequence, flags, format, coverage, entries); 284 } else if (format == 2) { 285 return new SingleSubtableFormat2(id, sequence, flags, format, coverage, entries); 286 } else { 287 throw new UnsupportedOperationException(); 288 } 289 } 290 } 291 292 private static class SingleSubtableFormat1 extends SingleSubtable { 293 private Value value; 294 private int ciMax; SingleSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)295 SingleSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 296 super(id, sequence, flags, format, coverage, entries); 297 populate(entries); 298 } 299 /** {@inheritDoc} */ getEntries()300 public List getEntries() { 301 if (value != null) { 302 List entries = new ArrayList(1); 303 entries.add(value); 304 return entries; 305 } else { 306 return null; 307 } 308 } 309 /** {@inheritDoc} */ getValue(int ci, int gi)310 public Value getValue(int ci, int gi) { 311 if ((value != null) && (ci <= ciMax)) { 312 return value; 313 } else { 314 return null; 315 } 316 } populate(List entries)317 private void populate(List entries) { 318 if ((entries == null) || (entries.size() != 1)) { 319 throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null and contain exactly one entry"); 320 } else { 321 Value v; 322 Object o = entries.get(0); 323 if (o instanceof Value) { 324 v = (Value) o; 325 } else { 326 throw new AdvancedTypographicTableFormatException("illegal entries entry, must be Value, but is: " + ((o != null) ? o.getClass() : null)); 327 } 328 assert this.value == null; 329 this.value = v; 330 this.ciMax = getCoverageSize() - 1; 331 } 332 } 333 } 334 335 private static class SingleSubtableFormat2 extends SingleSubtable { 336 private Value[] values; SingleSubtableFormat2(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)337 SingleSubtableFormat2(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 338 super(id, sequence, flags, format, coverage, entries); 339 populate(entries); 340 } 341 /** {@inheritDoc} */ getEntries()342 public List getEntries() { 343 if (values != null) { 344 List entries = new ArrayList(values.length); 345 Collections.addAll(entries, values); 346 return entries; 347 } else { 348 return null; 349 } 350 } 351 /** {@inheritDoc} */ getValue(int ci, int gi)352 public Value getValue(int ci, int gi) { 353 if ((values != null) && (ci < values.length)) { 354 return values [ ci ]; 355 } else { 356 return null; 357 } 358 } populate(List entries)359 private void populate(List entries) { 360 if (entries == null) { 361 throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null"); 362 } else if (entries.size() != 1) { 363 throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); 364 } else { 365 Object o; 366 if (((o = entries.get(0)) == null) || !(o instanceof Value[])) { 367 throw new AdvancedTypographicTableFormatException("illegal entries, single entry must be a Value[], but is: " + ((o != null) ? o.getClass() : null)); 368 } else { 369 Value[] va = (Value[]) o; 370 if (va.length != getCoverageSize()) { 371 throw new AdvancedTypographicTableFormatException("illegal values array, " + entries.size() + " values present, but requires " + getCoverageSize() + " values"); 372 } else { 373 assert this.values == null; 374 this.values = va; 375 } 376 } 377 } 378 } 379 } 380 381 private abstract static class PairSubtable extends GlyphPositioningSubtable { PairSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)382 PairSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 383 super(id, sequence, flags, format, coverage); 384 } 385 /** {@inheritDoc} */ getType()386 public int getType() { 387 return GPOS_LOOKUP_TYPE_PAIR; 388 } 389 /** {@inheritDoc} */ isCompatible(GlyphSubtable subtable)390 public boolean isCompatible(GlyphSubtable subtable) { 391 return subtable instanceof PairSubtable; 392 } 393 /** {@inheritDoc} */ position(GlyphPositioningState ps)394 public boolean position(GlyphPositioningState ps) { 395 boolean applied = false; 396 int gi = ps.getGlyph(0); 397 int ci; 398 if ((ci = getCoverageIndex(gi)) >= 0) { 399 int[] counts = ps.getGlyphsAvailable(0); 400 int nga = counts[0]; 401 if (nga > 1) { 402 int[] iga = ps.getGlyphs(0, 2, null, counts); 403 if ((iga != null) && (iga.length == 2)) { 404 PairValues pv = getPairValues(ci, iga[0], iga[1]); 405 if (pv != null) { 406 int offset = 0; 407 int offsetLast = counts[0] + counts[1]; 408 // skip any ignored glyphs prior to first non-ignored glyph 409 for ( ; offset < offsetLast; ++offset) { 410 if (!ps.isIgnoredGlyph(offset)) { 411 break; 412 } else { 413 ps.consume(1); 414 } 415 } 416 // adjust first non-ignored glyph if first value isn't null 417 Value v1 = pv.getValue1(); 418 if (v1 != null) { 419 if (ps.adjust(v1, offset)) { 420 ps.setAdjusted(true); 421 } 422 ps.consume(1); // consume first non-ignored glyph 423 ++offset; 424 } 425 // skip any ignored glyphs prior to second non-ignored glyph 426 for ( ; offset < offsetLast; ++offset) { 427 if (!ps.isIgnoredGlyph(offset)) { 428 break; 429 } else { 430 ps.consume(1); 431 } 432 } 433 // adjust second non-ignored glyph if second value isn't null 434 Value v2 = pv.getValue2(); 435 if (v2 != null) { 436 if (ps.adjust(v2, offset)) { 437 ps.setAdjusted(true); 438 } 439 ps.consume(1); // consume second non-ignored glyph 440 ++offset; 441 } 442 applied = true; 443 } 444 } 445 } 446 } 447 return applied; 448 } 449 /** 450 * Obtain associated pair values. 451 * @param ci coverage index 452 * @param gi1 first input glyph index 453 * @param gi2 second input glyph index 454 * @return pair values or null if none applies 455 */ getPairValues(int ci, int gi1, int gi2)456 public abstract PairValues getPairValues(int ci, int gi1, int gi2); create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)457 static GlyphPositioningSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 458 if (format == 1) { 459 return new PairSubtableFormat1(id, sequence, flags, format, coverage, entries); 460 } else if (format == 2) { 461 return new PairSubtableFormat2(id, sequence, flags, format, coverage, entries); 462 } else { 463 throw new UnsupportedOperationException(); 464 } 465 } 466 } 467 468 private static class PairSubtableFormat1 extends PairSubtable { 469 private PairValues[][] pvm; // pair values matrix PairSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)470 PairSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 471 super(id, sequence, flags, format, coverage, entries); 472 populate(entries); 473 } 474 /** {@inheritDoc} */ getEntries()475 public List getEntries() { 476 if (pvm != null) { 477 List entries = new ArrayList(1); 478 entries.add(pvm); 479 return entries; 480 } else { 481 return null; 482 } 483 } 484 /** {@inheritDoc} */ getPairValues(int ci, int gi1, int gi2)485 public PairValues getPairValues(int ci, int gi1, int gi2) { 486 if ((pvm != null) && (ci < pvm.length)) { 487 PairValues[] pvt = pvm [ ci ]; 488 for (PairValues pv : pvt) { 489 if (pv != null) { 490 int g = pv.getGlyph(); 491 if (g < gi2) { 492 continue; 493 } else if (g == gi2) { 494 return pv; 495 } else { 496 break; 497 } 498 } 499 } 500 } 501 return null; 502 } populate(List entries)503 private void populate(List entries) { 504 if (entries == null) { 505 throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null"); 506 } else if (entries.size() != 1) { 507 throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); 508 } else { 509 Object o; 510 if (((o = entries.get(0)) == null) || !(o instanceof PairValues[][])) { 511 throw new AdvancedTypographicTableFormatException("illegal entries, first (and only) entry must be a PairValues[][], but is: " + ((o != null) ? o.getClass() : null)); 512 } else { 513 pvm = (PairValues[][]) o; 514 } 515 } 516 } 517 } 518 519 private static class PairSubtableFormat2 extends PairSubtable { 520 private GlyphClassTable cdt1; // class def table 1 521 private GlyphClassTable cdt2; // class def table 2 522 private int nc1; // class 1 count 523 private int nc2; // class 2 count 524 private PairValues[][] pvm; // pair values matrix PairSubtableFormat2(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)525 PairSubtableFormat2(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 526 super(id, sequence, flags, format, coverage, entries); 527 populate(entries); 528 } 529 /** {@inheritDoc} */ getEntries()530 public List getEntries() { 531 if (pvm != null) { 532 List entries = new ArrayList(5); 533 entries.add(cdt1); 534 entries.add(cdt2); 535 entries.add(nc1); 536 entries.add(nc2); 537 entries.add(pvm); 538 return entries; 539 } else { 540 return null; 541 } 542 } 543 /** {@inheritDoc} */ getPairValues(int ci, int gi1, int gi2)544 public PairValues getPairValues(int ci, int gi1, int gi2) { 545 if (pvm != null) { 546 int c1 = cdt1.getClassIndex(gi1, 0); 547 if ((c1 >= 0) && (c1 < nc1) && (c1 < pvm.length)) { 548 PairValues[] pvt = pvm [ c1 ]; 549 if (pvt != null) { 550 int c2 = cdt2.getClassIndex(gi2, 0); 551 if ((c2 >= 0) && (c2 < nc2) && (c2 < pvt.length)) { 552 return pvt [ c2 ]; 553 } 554 } 555 } 556 } 557 return null; 558 } populate(List entries)559 private void populate(List entries) { 560 if (entries == null) { 561 throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null"); 562 } else if (entries.size() != 5) { 563 throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 5 entries"); 564 } else { 565 Object o; 566 if (((o = entries.get(0)) == null) || !(o instanceof GlyphClassTable)) { 567 throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphClassTable, but is: " + ((o != null) ? o.getClass() : null)); 568 } else { 569 cdt1 = (GlyphClassTable) o; 570 } 571 if (((o = entries.get(1)) == null) || !(o instanceof GlyphClassTable)) { 572 throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an GlyphClassTable, but is: " + ((o != null) ? o.getClass() : null)); 573 } else { 574 cdt2 = (GlyphClassTable) o; 575 } 576 if (((o = entries.get(2)) == null) || !(o instanceof Integer)) { 577 throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); 578 } else { 579 nc1 = (Integer) (o); 580 } 581 if (((o = entries.get(3)) == null) || !(o instanceof Integer)) { 582 throw new AdvancedTypographicTableFormatException("illegal entries, fourth entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); 583 } else { 584 nc2 = (Integer) (o); 585 } 586 if (((o = entries.get(4)) == null) || !(o instanceof PairValues[][])) { 587 throw new AdvancedTypographicTableFormatException("illegal entries, fifth entry must be a PairValues[][], but is: " + ((o != null) ? o.getClass() : null)); 588 } else { 589 pvm = (PairValues[][]) o; 590 } 591 } 592 } 593 } 594 595 private abstract static class CursiveSubtable extends GlyphPositioningSubtable { CursiveSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)596 CursiveSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 597 super(id, sequence, flags, format, coverage); 598 } 599 /** {@inheritDoc} */ getType()600 public int getType() { 601 return GPOS_LOOKUP_TYPE_CURSIVE; 602 } 603 /** {@inheritDoc} */ isCompatible(GlyphSubtable subtable)604 public boolean isCompatible(GlyphSubtable subtable) { 605 return subtable instanceof CursiveSubtable; 606 } 607 /** {@inheritDoc} */ position(GlyphPositioningState ps)608 public boolean position(GlyphPositioningState ps) { 609 boolean applied = false; 610 int gi = ps.getGlyph(0); 611 int ci; 612 if ((ci = getCoverageIndex(gi)) >= 0) { 613 int[] counts = ps.getGlyphsAvailable(0); 614 int nga = counts[0]; 615 if (nga > 1) { 616 int[] iga = ps.getGlyphs(0, 2, null, counts); 617 if ((iga != null) && (iga.length == 2)) { 618 // int gi1 = gi; 619 int ci1 = ci; 620 int gi2 = iga [ 1 ]; 621 int ci2 = getCoverageIndex(gi2); 622 Anchor[] aa = getExitEntryAnchors(ci1, ci2); 623 if (aa != null) { 624 Anchor exa = aa [ 0 ]; 625 Anchor ena = aa [ 1 ]; 626 // int exw = ps.getWidth ( gi1 ); 627 int enw = ps.getWidth(gi2); 628 if ((exa != null) && (ena != null)) { 629 Value v = ena.getAlignmentAdjustment(exa); 630 v.adjust(-enw, 0, 0, 0); 631 if (ps.adjust(v)) { 632 ps.setAdjusted(true); 633 } 634 } 635 // consume only first glyph of exit/entry glyph pair 636 ps.consume(1); 637 applied = true; 638 } 639 } 640 } 641 } 642 return applied; 643 } 644 /** 645 * Obtain exit anchor for first glyph with coverage index <code>ci1</code> and entry anchor for second 646 * glyph with coverage index <code>ci2</code>. 647 * @param ci1 coverage index of first glyph (may be negative) 648 * @param ci2 coverage index of second glyph (may be negative) 649 * @return array of two anchors or null if either coverage index is negative or corresponding anchor is 650 * missing, where the first entry is the exit anchor of the first glyph and the second entry is the 651 * entry anchor of the second glyph 652 */ getExitEntryAnchors(int ci1, int ci2)653 public abstract Anchor[] getExitEntryAnchors(int ci1, int ci2); create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)654 static GlyphPositioningSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 655 if (format == 1) { 656 return new CursiveSubtableFormat1(id, sequence, flags, format, coverage, entries); 657 } else { 658 throw new UnsupportedOperationException(); 659 } 660 } 661 } 662 663 private static class CursiveSubtableFormat1 extends CursiveSubtable { 664 private Anchor[] aa; // anchor array, where even entries are entry anchors, and odd entries are exit anchors CursiveSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)665 CursiveSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 666 super(id, sequence, flags, format, coverage, entries); 667 populate(entries); 668 } 669 /** {@inheritDoc} */ getEntries()670 public List getEntries() { 671 if (aa != null) { 672 List entries = new ArrayList(1); 673 entries.add(aa); 674 return entries; 675 } else { 676 return null; 677 } 678 } 679 /** {@inheritDoc} */ getExitEntryAnchors(int ci1, int ci2)680 public Anchor[] getExitEntryAnchors(int ci1, int ci2) { 681 if ((ci1 >= 0) && (ci2 >= 0)) { 682 int ai1 = (ci1 * 2) + 1; // ci1 denotes glyph with exit anchor 683 int ai2 = (ci2 * 2) + 0; // ci2 denotes glyph with entry anchor 684 if ((aa != null) && (ai1 < aa.length) && (ai2 < aa.length)) { 685 Anchor exa = aa [ ai1 ]; 686 Anchor ena = aa [ ai2 ]; 687 if ((exa != null) && (ena != null)) { 688 return new Anchor[] { exa, ena }; 689 } 690 } 691 } 692 return null; 693 } populate(List entries)694 private void populate(List entries) { 695 if (entries == null) { 696 throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null"); 697 } else if (entries.size() != 1) { 698 throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); 699 } else { 700 Object o; 701 if (((o = entries.get(0)) == null) || !(o instanceof Anchor[])) { 702 throw new AdvancedTypographicTableFormatException("illegal entries, first (and only) entry must be a Anchor[], but is: " + ((o != null) ? o.getClass() : null)); 703 } else if ((((Anchor[]) o) .length % 2) != 0) { 704 throw new AdvancedTypographicTableFormatException("illegal entries, Anchor[] array must have an even number of entries, but has: " + ((Anchor[]) o) .length); 705 } else { 706 aa = (Anchor[]) o; 707 } 708 } 709 } 710 } 711 712 private abstract static class MarkToBaseSubtable extends GlyphPositioningSubtable { MarkToBaseSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)713 MarkToBaseSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 714 super(id, sequence, flags, format, coverage); 715 } 716 /** {@inheritDoc} */ getType()717 public int getType() { 718 return GPOS_LOOKUP_TYPE_MARK_TO_BASE; 719 } 720 /** {@inheritDoc} */ isCompatible(GlyphSubtable subtable)721 public boolean isCompatible(GlyphSubtable subtable) { 722 return subtable instanceof MarkToBaseSubtable; 723 } 724 /** {@inheritDoc} */ position(GlyphPositioningState ps)725 public boolean position(GlyphPositioningState ps) { 726 boolean applied = false; 727 int giMark = ps.getGlyph(); 728 int ciMark; 729 if ((ciMark = getCoverageIndex(giMark)) >= 0) { 730 MarkAnchor ma = getMarkAnchor(ciMark, giMark); 731 if (ma != null) { 732 for (int i = 0, n = ps.getPosition(); i < n; i++) { 733 int gi = ps.getGlyph(-(i + 1)); 734 int unprocessedGlyph = ps.getUnprocessedGlyph(-(i + 1)); 735 if (ps.isMark(gi) && ps.isMark(unprocessedGlyph)) { 736 continue; 737 } else { 738 Anchor a = getBaseAnchor(gi, ma.getMarkClass()); 739 if (a != null) { 740 Value v = a.getAlignmentAdjustment(ma); 741 // start experimental fix for END OF AYAH in Lateef/Scheherazade 742 int[] aa = ps.getAdjustment(); 743 if (aa[2] == 0) { 744 v.adjust(0, 0, -ps.getWidth(giMark), 0); 745 } 746 // end experimental fix for END OF AYAH in Lateef/Scheherazade 747 if (OTFScript.KHMER.equals(ps.script)) { 748 v.adjust(-ps.getWidth(gi), -v.yPlacement, 0, 0); 749 } 750 if (ps.adjust(v)) { 751 ps.setAdjusted(true); 752 } 753 } 754 ps.consume(1); 755 applied = true; 756 break; 757 } 758 } 759 } 760 } 761 return applied; 762 } 763 /** 764 * Obtain mark anchor associated with mark coverage index. 765 * @param ciMark coverage index 766 * @param giMark input glyph index of mark glyph 767 * @return mark anchor or null if none applies 768 */ getMarkAnchor(int ciMark, int giMark)769 public abstract MarkAnchor getMarkAnchor(int ciMark, int giMark); 770 /** 771 * Obtain anchor associated with base glyph index and mark class. 772 * @param giBase input glyph index of base glyph 773 * @param markClass class number of mark glyph 774 * @return anchor or null if none applies 775 */ getBaseAnchor(int giBase, int markClass)776 public abstract Anchor getBaseAnchor(int giBase, int markClass); create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)777 static GlyphPositioningSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 778 if (format == 1) { 779 return new MarkToBaseSubtableFormat1(id, sequence, flags, format, coverage, entries); 780 } else { 781 throw new UnsupportedOperationException(); 782 } 783 } 784 } 785 786 private static class MarkToBaseSubtableFormat1 extends MarkToBaseSubtable { 787 private GlyphCoverageTable bct; // base coverage table 788 private int nmc; // mark class count 789 private MarkAnchor[] maa; // mark anchor array, ordered by mark coverage index 790 private Anchor[][] bam; // base anchor matrix, ordered by base coverage index, then by mark class MarkToBaseSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)791 MarkToBaseSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 792 super(id, sequence, flags, format, coverage, entries); 793 populate(entries); 794 } 795 /** {@inheritDoc} */ getEntries()796 public List getEntries() { 797 if ((bct != null) && (maa != null) && (nmc > 0) && (bam != null)) { 798 List entries = new ArrayList(4); 799 entries.add(bct); 800 entries.add(nmc); 801 entries.add(maa); 802 entries.add(bam); 803 return entries; 804 } else { 805 return null; 806 } 807 } 808 /** {@inheritDoc} */ getMarkAnchor(int ciMark, int giMark)809 public MarkAnchor getMarkAnchor(int ciMark, int giMark) { 810 if ((maa != null) && (ciMark < maa.length)) { 811 return maa [ ciMark ]; 812 } else { 813 return null; 814 } 815 } 816 /** {@inheritDoc} */ getBaseAnchor(int giBase, int markClass)817 public Anchor getBaseAnchor(int giBase, int markClass) { 818 int ciBase; 819 if ((bct != null) && ((ciBase = bct.getCoverageIndex(giBase)) >= 0)) { 820 if ((bam != null) && (ciBase < bam.length)) { 821 Anchor[] ba = bam [ ciBase ]; 822 if ((ba != null) && (markClass < ba.length)) { 823 return ba [ markClass ]; 824 } 825 } 826 } 827 return null; 828 } populate(List entries)829 private void populate(List entries) { 830 if (entries == null) { 831 throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null"); 832 } else if (entries.size() != 4) { 833 throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 4 entries"); 834 } else { 835 Object o; 836 if (((o = entries.get(0)) == null) || !(o instanceof GlyphCoverageTable)) { 837 throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphCoverageTable, but is: " + ((o != null) ? o.getClass() : null)); 838 } else { 839 bct = (GlyphCoverageTable) o; 840 } 841 if (((o = entries.get(1)) == null) || !(o instanceof Integer)) { 842 throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); 843 } else { 844 nmc = (Integer) (o); 845 } 846 if (((o = entries.get(2)) == null) || !(o instanceof MarkAnchor[])) { 847 throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be a MarkAnchor[], but is: " + ((o != null) ? o.getClass() : null)); 848 } else { 849 maa = (MarkAnchor[]) o; 850 } 851 if (((o = entries.get(3)) == null) || !(o instanceof Anchor[][])) { 852 throw new AdvancedTypographicTableFormatException("illegal entries, fourth entry must be a Anchor[][], but is: " + ((o != null) ? o.getClass() : null)); 853 } else { 854 bam = (Anchor[][]) o; 855 } 856 } 857 } 858 } 859 860 private abstract static class MarkToLigatureSubtable extends GlyphPositioningSubtable { MarkToLigatureSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)861 MarkToLigatureSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 862 super(id, sequence, flags, format, coverage); 863 } 864 /** {@inheritDoc} */ getType()865 public int getType() { 866 return GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE; 867 } 868 /** {@inheritDoc} */ isCompatible(GlyphSubtable subtable)869 public boolean isCompatible(GlyphSubtable subtable) { 870 return subtable instanceof MarkToLigatureSubtable; 871 } 872 /** {@inheritDoc} */ position(GlyphPositioningState ps)873 public boolean position(GlyphPositioningState ps) { 874 boolean applied = false; 875 int giMark = ps.getGlyph(); 876 int ciMark; 877 if ((ciMark = getCoverageIndex(giMark)) >= 0) { 878 MarkAnchor ma = getMarkAnchor(ciMark, giMark); 879 int mxc = getMaxComponentCount(); 880 if (ma != null) { 881 for (int i = 0, n = ps.getPosition(); i < n; i++) { 882 int glyphIndex = ps.getUnprocessedGlyph(-(i + 1)); 883 if (ps.isMark(glyphIndex)) { 884 continue; 885 } else { 886 Anchor anchor = getLigatureAnchor(glyphIndex, mxc, i, ma.getMarkClass()); 887 if (anchor != null) { 888 if (ps.adjust(anchor.getAlignmentAdjustment(ma))) { 889 ps.setAdjusted(true); 890 } 891 } 892 ps.consume(1); 893 applied = true; 894 break; 895 } 896 } 897 } 898 } 899 return applied; 900 } 901 /** 902 * Obtain mark anchor associated with mark coverage index. 903 * @param ciMark coverage index 904 * @param giMark input glyph index of mark glyph 905 * @return mark anchor or null if none applies 906 */ getMarkAnchor(int ciMark, int giMark)907 public abstract MarkAnchor getMarkAnchor(int ciMark, int giMark); 908 /** 909 * Obtain maximum component count. 910 * @return maximum component count (>=0) 911 */ getMaxComponentCount()912 public abstract int getMaxComponentCount(); 913 /** 914 * Obtain anchor associated with ligature glyph index and mark class. 915 * @param giLig input glyph index of ligature glyph 916 * @param maxComponents maximum component count 917 * @param component component number (0...maxComponents-1) 918 * @param markClass class number of mark glyph 919 * @return anchor or null if none applies 920 */ getLigatureAnchor(int giLig, int maxComponents, int component, int markClass)921 public abstract Anchor getLigatureAnchor(int giLig, int maxComponents, int component, int markClass); create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)922 static GlyphPositioningSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 923 if (format == 1) { 924 return new MarkToLigatureSubtableFormat1(id, sequence, flags, format, coverage, entries); 925 } else { 926 throw new UnsupportedOperationException(); 927 } 928 } 929 } 930 931 private static class MarkToLigatureSubtableFormat1 extends MarkToLigatureSubtable { 932 private GlyphCoverageTable lct; // ligature coverage table 933 private int nmc; // mark class count 934 private int mxc; // maximum ligature component count 935 private MarkAnchor[] maa; // mark anchor array, ordered by mark coverage index 936 private Anchor[][][] lam; // ligature anchor matrix, ordered by ligature coverage index, then ligature component, then mark class MarkToLigatureSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)937 MarkToLigatureSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 938 super(id, sequence, flags, format, coverage, entries); 939 populate(entries); 940 } 941 /** {@inheritDoc} */ getEntries()942 public List getEntries() { 943 if (lam != null) { 944 List entries = new ArrayList(5); 945 entries.add(lct); 946 entries.add(nmc); 947 entries.add(mxc); 948 entries.add(maa); 949 entries.add(lam); 950 return entries; 951 } else { 952 return null; 953 } 954 } 955 /** {@inheritDoc} */ getMarkAnchor(int ciMark, int giMark)956 public MarkAnchor getMarkAnchor(int ciMark, int giMark) { 957 if ((maa != null) && (ciMark < maa.length)) { 958 return maa [ ciMark ]; 959 } else { 960 return null; 961 } 962 } 963 /** {@inheritDoc} */ getMaxComponentCount()964 public int getMaxComponentCount() { 965 return mxc; 966 } 967 /** {@inheritDoc} */ getLigatureAnchor(int giLig, int maxComponents, int component, int markClass)968 public Anchor getLigatureAnchor(int giLig, int maxComponents, int component, int markClass) { 969 int ciLig; 970 if ((lct != null) && ((ciLig = lct.getCoverageIndex(giLig)) >= 0)) { 971 if ((lam != null) && (ciLig < lam.length)) { 972 Anchor[][] lcm = lam [ ciLig ]; 973 if (component < maxComponents) { 974 Anchor[] la = lcm [ component ]; 975 if ((la != null) && (markClass < la.length)) { 976 return la [ markClass ]; 977 } 978 } 979 } 980 } 981 return null; 982 } populate(List entries)983 private void populate(List entries) { 984 if (entries == null) { 985 throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null"); 986 } else if (entries.size() != 5) { 987 throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 5 entries"); 988 } else { 989 Object o; 990 if (((o = entries.get(0)) == null) || !(o instanceof GlyphCoverageTable)) { 991 throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphCoverageTable, but is: " + ((o != null) ? o.getClass() : null)); 992 } else { 993 lct = (GlyphCoverageTable) o; 994 } 995 if (((o = entries.get(1)) == null) || !(o instanceof Integer)) { 996 throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); 997 } else { 998 nmc = (Integer) (o); 999 } 1000 if (((o = entries.get(2)) == null) || !(o instanceof Integer)) { 1001 throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); 1002 } else { 1003 mxc = (Integer) (o); 1004 } 1005 if (((o = entries.get(3)) == null) || !(o instanceof MarkAnchor[])) { 1006 throw new AdvancedTypographicTableFormatException("illegal entries, fourth entry must be a MarkAnchor[], but is: " + ((o != null) ? o.getClass() : null)); 1007 } else { 1008 maa = (MarkAnchor[]) o; 1009 } 1010 if (((o = entries.get(4)) == null) || !(o instanceof Anchor[][][])) { 1011 throw new AdvancedTypographicTableFormatException("illegal entries, fifth entry must be a Anchor[][][], but is: " + ((o != null) ? o.getClass() : null)); 1012 } else { 1013 lam = (Anchor[][][]) o; 1014 } 1015 } 1016 } 1017 } 1018 1019 private abstract static class MarkToMarkSubtable extends GlyphPositioningSubtable { MarkToMarkSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)1020 MarkToMarkSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 1021 super(id, sequence, flags, format, coverage); 1022 } 1023 /** {@inheritDoc} */ getType()1024 public int getType() { 1025 return GPOS_LOOKUP_TYPE_MARK_TO_MARK; 1026 } 1027 /** {@inheritDoc} */ isCompatible(GlyphSubtable subtable)1028 public boolean isCompatible(GlyphSubtable subtable) { 1029 return subtable instanceof MarkToMarkSubtable; 1030 } 1031 /** {@inheritDoc} */ position(GlyphPositioningState ps)1032 public boolean position(GlyphPositioningState ps) { 1033 boolean applied = false; 1034 int giMark1 = ps.getGlyph(); 1035 int ciMark1; 1036 if ((ciMark1 = getCoverageIndex(giMark1)) >= 0) { 1037 MarkAnchor ma = getMark1Anchor(ciMark1, giMark1); 1038 if (ma != null) { 1039 if (ps.hasPrev()) { 1040 Anchor anchor = getMark2Anchor(ps.getUnprocessedGlyph(-1), ma.getMarkClass()); 1041 if (anchor != null) { 1042 if (ps.adjust(anchor.getAlignmentAdjustment(ma))) { 1043 ps.setAdjusted(true); 1044 } 1045 } 1046 ps.consume(1); 1047 applied = true; 1048 } 1049 } 1050 } 1051 return applied; 1052 } 1053 /** 1054 * Obtain mark 1 anchor associated with mark 1 coverage index. 1055 * @param ciMark1 mark 1 coverage index 1056 * @param giMark1 input glyph index of mark 1 glyph 1057 * @return mark 1 anchor or null if none applies 1058 */ getMark1Anchor(int ciMark1, int giMark1)1059 public abstract MarkAnchor getMark1Anchor(int ciMark1, int giMark1); 1060 /** 1061 * Obtain anchor associated with mark 2 glyph index and mark 1 class. 1062 * @param giMark2 input glyph index of mark 2 glyph 1063 * @param markClass class number of mark 1 glyph 1064 * @return anchor or null if none applies 1065 */ getMark2Anchor(int giBase, int markClass)1066 public abstract Anchor getMark2Anchor(int giBase, int markClass); create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)1067 static GlyphPositioningSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 1068 if (format == 1) { 1069 return new MarkToMarkSubtableFormat1(id, sequence, flags, format, coverage, entries); 1070 } else { 1071 throw new UnsupportedOperationException(); 1072 } 1073 } 1074 } 1075 1076 private static class MarkToMarkSubtableFormat1 extends MarkToMarkSubtable { 1077 private GlyphCoverageTable mct2; // mark 2 coverage table 1078 private int nmc; // mark class count 1079 private MarkAnchor[] maa; // mark1 anchor array, ordered by mark1 coverage index 1080 private Anchor[][] mam; // mark2 anchor matrix, ordered by mark2 coverage index, then by mark1 class MarkToMarkSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)1081 MarkToMarkSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 1082 super(id, sequence, flags, format, coverage, entries); 1083 populate(entries); 1084 } 1085 /** {@inheritDoc} */ getEntries()1086 public List getEntries() { 1087 if ((mct2 != null) && (maa != null) && (nmc > 0) && (mam != null)) { 1088 List entries = new ArrayList(4); 1089 entries.add(mct2); 1090 entries.add(nmc); 1091 entries.add(maa); 1092 entries.add(mam); 1093 return entries; 1094 } else { 1095 return null; 1096 } 1097 } 1098 /** {@inheritDoc} */ getMark1Anchor(int ciMark1, int giMark1)1099 public MarkAnchor getMark1Anchor(int ciMark1, int giMark1) { 1100 if ((maa != null) && (ciMark1 < maa.length)) { 1101 return maa [ ciMark1 ]; 1102 } else { 1103 return null; 1104 } 1105 } 1106 /** {@inheritDoc} */ getMark2Anchor(int giMark2, int markClass)1107 public Anchor getMark2Anchor(int giMark2, int markClass) { 1108 int ciMark2; 1109 if ((mct2 != null) && ((ciMark2 = mct2.getCoverageIndex(giMark2)) >= 0)) { 1110 if ((mam != null) && (ciMark2 < mam.length)) { 1111 Anchor[] ma = mam [ ciMark2 ]; 1112 if ((ma != null) && (markClass < ma.length)) { 1113 return ma [ markClass ]; 1114 } 1115 } 1116 } 1117 return null; 1118 } populate(List entries)1119 private void populate(List entries) { 1120 if (entries == null) { 1121 throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null"); 1122 } else if (entries.size() != 4) { 1123 throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 4 entries"); 1124 } else { 1125 Object o; 1126 if (((o = entries.get(0)) == null) || !(o instanceof GlyphCoverageTable)) { 1127 throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphCoverageTable, but is: " + ((o != null) ? o.getClass() : null)); 1128 } else { 1129 mct2 = (GlyphCoverageTable) o; 1130 } 1131 if (((o = entries.get(1)) == null) || !(o instanceof Integer)) { 1132 throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); 1133 } else { 1134 nmc = (Integer) (o); 1135 } 1136 if (((o = entries.get(2)) == null) || !(o instanceof MarkAnchor[])) { 1137 throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be a MarkAnchor[], but is: " + ((o != null) ? o.getClass() : null)); 1138 } else { 1139 maa = (MarkAnchor[]) o; 1140 } 1141 if (((o = entries.get(3)) == null) || !(o instanceof Anchor[][])) { 1142 throw new AdvancedTypographicTableFormatException("illegal entries, fourth entry must be a Anchor[][], but is: " + ((o != null) ? o.getClass() : null)); 1143 } else { 1144 mam = (Anchor[][]) o; 1145 } 1146 } 1147 } 1148 } 1149 1150 private abstract static class ContextualSubtable extends GlyphPositioningSubtable { ContextualSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)1151 ContextualSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 1152 super(id, sequence, flags, format, coverage); 1153 } 1154 /** {@inheritDoc} */ getType()1155 public int getType() { 1156 return GPOS_LOOKUP_TYPE_CONTEXTUAL; 1157 } 1158 /** {@inheritDoc} */ isCompatible(GlyphSubtable subtable)1159 public boolean isCompatible(GlyphSubtable subtable) { 1160 return subtable instanceof ContextualSubtable; 1161 } 1162 /** {@inheritDoc} */ position(GlyphPositioningState ps)1163 public boolean position(GlyphPositioningState ps) { 1164 boolean applied = false; 1165 int gi = ps.getGlyph(); 1166 int ci; 1167 if ((ci = getCoverageIndex(gi)) >= 0) { 1168 int[] rv = new int[1]; 1169 RuleLookup[] la = getLookups(ci, gi, ps, rv); 1170 if (la != null) { 1171 ps.apply(la, rv[0]); 1172 applied = true; 1173 } 1174 } 1175 return applied; 1176 } 1177 /** 1178 * Obtain rule lookups set associated current input glyph context. 1179 * @param ci coverage index of glyph at current position 1180 * @param gi glyph index of glyph at current position 1181 * @param ps glyph positioning state 1182 * @param rv array of ints used to receive multiple return values, must be of length 1 or greater, 1183 * where the first entry is used to return the input sequence length of the matched rule 1184 * @return array of rule lookups or null if none applies 1185 */ getLookups(int ci, int gi, GlyphPositioningState ps, int[] rv)1186 public abstract RuleLookup[] getLookups(int ci, int gi, GlyphPositioningState ps, int[] rv); create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)1187 static GlyphPositioningSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 1188 if (format == 1) { 1189 return new ContextualSubtableFormat1(id, sequence, flags, format, coverage, entries); 1190 } else if (format == 2) { 1191 return new ContextualSubtableFormat2(id, sequence, flags, format, coverage, entries); 1192 } else if (format == 3) { 1193 return new ContextualSubtableFormat3(id, sequence, flags, format, coverage, entries); 1194 } else { 1195 throw new UnsupportedOperationException(); 1196 } 1197 } 1198 } 1199 1200 private static class ContextualSubtableFormat1 extends ContextualSubtable { 1201 private RuleSet[] rsa; // rule set array, ordered by glyph coverage index ContextualSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries)1202 ContextualSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 1203 super(id, sequence, flags, format, coverage, entries); 1204 populate(entries); 1205 } 1206 /** {@inheritDoc} */ getEntries()1207 public List getEntries() { 1208 if (rsa != null) { 1209 List entries = new ArrayList(1); 1210 entries.add(rsa); 1211 return entries; 1212 } else { 1213 return null; 1214 } 1215 } 1216 /** {@inheritDoc} */ resolveLookupReferences(Map<String, LookupTable> lookupTables)1217 public void resolveLookupReferences(Map<String, LookupTable> lookupTables) { 1218 GlyphTable.resolveLookupReferences(rsa, lookupTables); 1219 } 1220 /** {@inheritDoc} */ getLookups(int ci, int gi, GlyphPositioningState ps, int[] rv)1221 public RuleLookup[] getLookups(int ci, int gi, GlyphPositioningState ps, int[] rv) { 1222 assert ps != null; 1223 assert (rv != null) && (rv.length > 0); 1224 assert rsa != null; 1225 if (rsa.length > 0) { 1226 RuleSet rs = rsa [ 0 ]; 1227 if (rs != null) { 1228 Rule[] ra = rs.getRules(); 1229 for (Rule r : ra) { 1230 if ((r != null) && (r instanceof ChainedGlyphSequenceRule)) { 1231 ChainedGlyphSequenceRule cr = (ChainedGlyphSequenceRule) r; 1232 int[] iga = cr.getGlyphs(gi); 1233 if (matches(ps, iga, 0, rv)) { 1234 return r.getLookups(); 1235 } 1236 } 1237 } 1238 } 1239 } 1240 return null; 1241 } matches(GlyphPositioningState ps, int[] glyphs, int offset, int[] rv)1242 static boolean matches(GlyphPositioningState ps, int[] glyphs, int offset, int[] rv) { 1243 if ((glyphs == null) || (glyphs.length == 0)) { 1244 return true; // match null or empty glyph sequence 1245 } else { 1246 boolean reverse = offset < 0; 1247 GlyphTester ignores = ps.getIgnoreDefault(); 1248 int[] counts = ps.getGlyphsAvailable(offset, reverse, ignores); 1249 int nga = counts[0]; 1250 int ngm = glyphs.length; 1251 if (nga < ngm) { 1252 return false; // insufficient glyphs available to match 1253 } else { 1254 int[] ga = ps.getGlyphs(offset, ngm, reverse, ignores, null, counts); 1255 for (int k = 0; k < ngm; k++) { 1256 if (ga [ k ] != glyphs [ k ]) { 1257 return false; // match fails at ga [ k ] 1258 } 1259 } 1260 if (rv != null) { 1261 rv[0] = counts[0] + counts[1]; 1262 } 1263 return true; // all glyphs match 1264 } 1265 } 1266 } 1267 private void populate(List entries) { 1268 if (entries == null) { 1269 throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null"); 1270 } else if (entries.size() != 1) { 1271 throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); 1272 } else { 1273 Object o; 1274 if (((o = entries.get(0)) == null) || !(o instanceof RuleSet[])) { 1275 throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); 1276 } else { 1277 rsa = (RuleSet[]) o; 1278 } 1279 } 1280 } 1281 } 1282 1283 private static class ContextualSubtableFormat2 extends ContextualSubtable { 1284 private GlyphClassTable cdt; // class def table 1285 private int ngc; // class set count 1286 private RuleSet[] rsa; // rule set array, ordered by class number [0...ngc - 1] 1287 ContextualSubtableFormat2(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 1288 super(id, sequence, flags, format, coverage, entries); 1289 populate(entries); 1290 } 1291 /** {@inheritDoc} */ 1292 public List getEntries() { 1293 if (rsa != null) { 1294 List entries = new ArrayList(3); 1295 entries.add(cdt); 1296 entries.add(ngc); 1297 entries.add(rsa); 1298 return entries; 1299 } else { 1300 return null; 1301 } 1302 } 1303 /** {@inheritDoc} */ 1304 public void resolveLookupReferences(Map<String, LookupTable> lookupTables) { 1305 GlyphTable.resolveLookupReferences(rsa, lookupTables); 1306 } 1307 /** {@inheritDoc} */ 1308 public RuleLookup[] getLookups(int ci, int gi, GlyphPositioningState ps, int[] rv) { 1309 assert ps != null; 1310 assert (rv != null) && (rv.length > 0); 1311 assert rsa != null; 1312 if (rsa.length > 0) { 1313 RuleSet rs = rsa [ 0 ]; 1314 if (rs != null) { 1315 Rule[] ra = rs.getRules(); 1316 for (Rule r : ra) { 1317 if ((r != null) && (r instanceof ChainedClassSequenceRule)) { 1318 ChainedClassSequenceRule cr = (ChainedClassSequenceRule) r; 1319 int[] ca = cr.getClasses(cdt.getClassIndex(gi, ps.getClassMatchSet(gi))); 1320 if (matches(ps, cdt, ca, 0, rv)) { 1321 return r.getLookups(); 1322 } 1323 } 1324 } 1325 } 1326 } 1327 return null; 1328 } 1329 static boolean matches(GlyphPositioningState ps, GlyphClassTable cdt, int[] classes, int offset, int[] rv) { 1330 if ((cdt == null) || (classes == null) || (classes.length == 0)) { 1331 return true; // match null class definitions, null or empty class sequence 1332 } else { 1333 boolean reverse = offset < 0; 1334 GlyphTester ignores = ps.getIgnoreDefault(); 1335 int[] counts = ps.getGlyphsAvailable(offset, reverse, ignores); 1336 int nga = counts[0]; 1337 int ngm = classes.length; 1338 if (nga < ngm) { 1339 return false; // insufficient glyphs available to match 1340 } else { 1341 int[] ga = ps.getGlyphs(offset, ngm, reverse, ignores, null, counts); 1342 for (int k = 0; k < ngm; k++) { 1343 int gi = ga [ k ]; 1344 int ms = ps.getClassMatchSet(gi); 1345 int gc = cdt.getClassIndex(gi, ms); 1346 if ((gc < 0) || (gc >= cdt.getClassSize(ms))) { 1347 return false; // none or invalid class fails mat ch 1348 } else if (gc != classes [ k ]) { 1349 return false; // match fails at ga [ k ] 1350 } 1351 } 1352 if (rv != null) { 1353 rv[0] = counts[0] + counts[1]; 1354 } 1355 return true; // all glyphs match 1356 } 1357 } 1358 } 1359 private void populate(List entries) { 1360 if (entries == null) { 1361 throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null"); 1362 } else if (entries.size() != 3) { 1363 throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 3 entries"); 1364 } else { 1365 Object o; 1366 if (((o = entries.get(0)) == null) || !(o instanceof GlyphClassTable)) { 1367 throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphClassTable, but is: " + ((o != null) ? o.getClass() : null)); 1368 } else { 1369 cdt = (GlyphClassTable) o; 1370 } 1371 if (((o = entries.get(1)) == null) || !(o instanceof Integer)) { 1372 throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); 1373 } else { 1374 ngc = (Integer) (o); 1375 } 1376 if (((o = entries.get(2)) == null) || !(o instanceof RuleSet[])) { 1377 throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); 1378 } else { 1379 rsa = (RuleSet[]) o; 1380 if (rsa.length != ngc) { 1381 throw new AdvancedTypographicTableFormatException("illegal entries, RuleSet[] length is " + rsa.length + ", but expected " + ngc + " glyph classes"); 1382 } 1383 } 1384 } 1385 } 1386 } 1387 1388 private static class ContextualSubtableFormat3 extends ContextualSubtable { 1389 private RuleSet[] rsa; // rule set array, containing a single rule set 1390 ContextualSubtableFormat3(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 1391 super(id, sequence, flags, format, coverage, entries); 1392 populate(entries); 1393 } 1394 /** {@inheritDoc} */ 1395 public List getEntries() { 1396 if (rsa != null) { 1397 List entries = new ArrayList(1); 1398 entries.add(rsa); 1399 return entries; 1400 } else { 1401 return null; 1402 } 1403 } 1404 /** {@inheritDoc} */ 1405 public void resolveLookupReferences(Map<String, LookupTable> lookupTables) { 1406 GlyphTable.resolveLookupReferences(rsa, lookupTables); 1407 } 1408 /** {@inheritDoc} */ 1409 public RuleLookup[] getLookups(int ci, int gi, GlyphPositioningState ps, int[] rv) { 1410 assert ps != null; 1411 assert (rv != null) && (rv.length > 0); 1412 assert rsa != null; 1413 if (rsa.length > 0) { 1414 RuleSet rs = rsa [ 0 ]; 1415 if (rs != null) { 1416 Rule[] ra = rs.getRules(); 1417 for (Rule r : ra) { 1418 if ((r != null) && (r instanceof ChainedCoverageSequenceRule)) { 1419 ChainedCoverageSequenceRule cr = (ChainedCoverageSequenceRule) r; 1420 GlyphCoverageTable[] gca = cr.getCoverages(); 1421 if (matches(ps, gca, 0, rv)) { 1422 return r.getLookups(); 1423 } 1424 } 1425 } 1426 } 1427 } 1428 return null; 1429 } 1430 static boolean matches(GlyphPositioningState ps, GlyphCoverageTable[] gca, int offset, int[] rv) { 1431 if ((gca == null) || (gca.length == 0)) { 1432 return true; // match null or empty coverage array 1433 } else { 1434 boolean reverse = offset < 0; 1435 GlyphTester ignores = ps.getIgnoreDefault(); 1436 int[] counts = ps.getGlyphsAvailable(offset, reverse, ignores); 1437 int nga = counts[0]; 1438 int ngm = gca.length; 1439 if (nga < ngm) { 1440 return false; // insufficient glyphs available to match 1441 } else { 1442 int[] ga = ps.getGlyphs(offset, ngm, reverse, ignores, null, counts); 1443 for (int k = 0; k < ngm; k++) { 1444 GlyphCoverageTable ct = gca [ k ]; 1445 if (ct != null) { 1446 if (ct.getCoverageIndex(ga [ k ]) < 0) { 1447 return false; // match fails at ga [ k ] 1448 } 1449 } 1450 } 1451 if (rv != null) { 1452 rv[0] = counts[0] + counts[1]; 1453 } 1454 return true; // all glyphs match 1455 } 1456 } 1457 } 1458 private void populate(List entries) { 1459 if (entries == null) { 1460 throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null"); 1461 } else if (entries.size() != 1) { 1462 throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); 1463 } else { 1464 Object o; 1465 if (((o = entries.get(0)) == null) || !(o instanceof RuleSet[])) { 1466 throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); 1467 } else { 1468 rsa = (RuleSet[]) o; 1469 } 1470 } 1471 } 1472 } 1473 1474 private abstract static class ChainedContextualSubtable extends GlyphPositioningSubtable { 1475 ChainedContextualSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 1476 super(id, sequence, flags, format, coverage); 1477 } 1478 /** {@inheritDoc} */ 1479 public int getType() { 1480 return GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL; 1481 } 1482 /** {@inheritDoc} */ 1483 public boolean isCompatible(GlyphSubtable subtable) { 1484 return subtable instanceof ChainedContextualSubtable; 1485 } 1486 /** {@inheritDoc} */ 1487 public boolean position(GlyphPositioningState ps) { 1488 boolean applied = false; 1489 int gi = ps.getGlyph(); 1490 int ci; 1491 if ((ci = getCoverageIndex(gi)) >= 0) { 1492 int[] rv = new int[1]; 1493 RuleLookup[] la = getLookups(ci, gi, ps, rv); 1494 if (la != null) { 1495 ps.apply(la, rv[0]); 1496 applied = true; 1497 } 1498 } 1499 return applied; 1500 } 1501 /** 1502 * Obtain rule lookups set associated current input glyph context. 1503 * @param ci coverage index of glyph at current position 1504 * @param gi glyph index of glyph at current position 1505 * @param ps glyph positioning state 1506 * @param rv array of ints used to receive multiple return values, must be of length 1 or greater, 1507 * where the first entry is used to return the input sequence length of the matched rule 1508 * @return array of rule lookups or null if none applies 1509 */ 1510 public abstract RuleLookup[] getLookups(int ci, int gi, GlyphPositioningState ps, int[] rv); 1511 static GlyphPositioningSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 1512 if (format == 1) { 1513 return new ChainedContextualSubtableFormat1(id, sequence, flags, format, coverage, entries); 1514 } else if (format == 2) { 1515 return new ChainedContextualSubtableFormat2(id, sequence, flags, format, coverage, entries); 1516 } else if (format == 3) { 1517 return new ChainedContextualSubtableFormat3(id, sequence, flags, format, coverage, entries); 1518 } else { 1519 throw new UnsupportedOperationException(); 1520 } 1521 } 1522 } 1523 1524 private static class ChainedContextualSubtableFormat1 extends ChainedContextualSubtable { 1525 private RuleSet[] rsa; // rule set array, ordered by glyph coverage index 1526 ChainedContextualSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 1527 super(id, sequence, flags, format, coverage, entries); 1528 populate(entries); 1529 } 1530 /** {@inheritDoc} */ 1531 public List getEntries() { 1532 if (rsa != null) { 1533 List entries = new ArrayList(1); 1534 entries.add(rsa); 1535 return entries; 1536 } else { 1537 return null; 1538 } 1539 } 1540 /** {@inheritDoc} */ 1541 public void resolveLookupReferences(Map<String, LookupTable> lookupTables) { 1542 GlyphTable.resolveLookupReferences(rsa, lookupTables); 1543 } 1544 /** {@inheritDoc} */ 1545 public RuleLookup[] getLookups(int ci, int gi, GlyphPositioningState ps, int[] rv) { 1546 assert ps != null; 1547 assert (rv != null) && (rv.length > 0); 1548 assert rsa != null; 1549 if (rsa.length > 0) { 1550 RuleSet rs = rsa [ 0 ]; 1551 if (rs != null) { 1552 Rule[] ra = rs.getRules(); 1553 for (Rule r : ra) { 1554 if ((r != null) && (r instanceof ChainedGlyphSequenceRule)) { 1555 ChainedGlyphSequenceRule cr = (ChainedGlyphSequenceRule) r; 1556 int[] iga = cr.getGlyphs(gi); 1557 if (matches(ps, iga, 0, rv)) { 1558 int[] bga = cr.getBacktrackGlyphs(); 1559 if (matches(ps, bga, -1, null)) { 1560 int[] lga = cr.getLookaheadGlyphs(); 1561 if (matches(ps, lga, rv[0], null)) { 1562 return r.getLookups(); 1563 } 1564 } 1565 } 1566 } 1567 } 1568 } 1569 } 1570 return null; 1571 } 1572 private boolean matches(GlyphPositioningState ps, int[] glyphs, int offset, int[] rv) { 1573 return ContextualSubtableFormat1.matches(ps, glyphs, offset, rv); 1574 } 1575 private void populate(List entries) { 1576 if (entries == null) { 1577 throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null"); 1578 } else if (entries.size() != 1) { 1579 throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); 1580 } else { 1581 Object o; 1582 if (((o = entries.get(0)) == null) || !(o instanceof RuleSet[])) { 1583 throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); 1584 } else { 1585 rsa = (RuleSet[]) o; 1586 } 1587 } 1588 } 1589 } 1590 1591 private static class ChainedContextualSubtableFormat2 extends ChainedContextualSubtable { 1592 private GlyphClassTable icdt; // input class def table 1593 private GlyphClassTable bcdt; // backtrack class def table 1594 private GlyphClassTable lcdt; // lookahead class def table 1595 private int ngc; // class set count 1596 private RuleSet[] rsa; // rule set array, ordered by class number [0...ngc - 1] 1597 ChainedContextualSubtableFormat2(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 1598 super(id, sequence, flags, format, coverage, entries); 1599 populate(entries); 1600 } 1601 /** {@inheritDoc} */ 1602 public List getEntries() { 1603 if (rsa != null) { 1604 List entries = new ArrayList(5); 1605 entries.add(icdt); 1606 entries.add(bcdt); 1607 entries.add(lcdt); 1608 entries.add(ngc); 1609 entries.add(rsa); 1610 return entries; 1611 } else { 1612 return null; 1613 } 1614 } 1615 /** {@inheritDoc} */ 1616 public void resolveLookupReferences(Map<String, LookupTable> lookupTables) { 1617 GlyphTable.resolveLookupReferences(rsa, lookupTables); 1618 } 1619 /** {@inheritDoc} */ 1620 public RuleLookup[] getLookups(int ci, int gi, GlyphPositioningState ps, int[] rv) { 1621 assert ps != null; 1622 assert (rv != null) && (rv.length > 0); 1623 assert rsa != null; 1624 if (rsa.length > 0) { 1625 RuleSet rs = rsa [ 0 ]; 1626 if (rs != null) { 1627 Rule[] ra = rs.getRules(); 1628 for (Rule r : ra) { 1629 if ((r != null) && (r instanceof ChainedClassSequenceRule)) { 1630 ChainedClassSequenceRule cr = (ChainedClassSequenceRule) r; 1631 int[] ica = cr.getClasses(icdt.getClassIndex(gi, ps.getClassMatchSet(gi))); 1632 if (matches(ps, icdt, ica, 0, rv)) { 1633 int[] bca = cr.getBacktrackClasses(); 1634 if (matches(ps, bcdt, bca, -1, null)) { 1635 int[] lca = cr.getLookaheadClasses(); 1636 if (matches(ps, lcdt, lca, rv[0], null)) { 1637 return r.getLookups(); 1638 } 1639 } 1640 } 1641 } 1642 } 1643 } 1644 } 1645 return null; 1646 } 1647 private boolean matches(GlyphPositioningState ps, GlyphClassTable cdt, int[] classes, int offset, int[] rv) { 1648 return ContextualSubtableFormat2.matches(ps, cdt, classes, offset, rv); 1649 } 1650 private void populate(List entries) { 1651 if (entries == null) { 1652 throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null"); 1653 } else if (entries.size() != 5) { 1654 throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 5 entries"); 1655 } else { 1656 Object o; 1657 if (((o = entries.get(0)) == null) || !(o instanceof GlyphClassTable)) { 1658 throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphClassTable, but is: " + ((o != null) ? o.getClass() : null)); 1659 } else { 1660 icdt = (GlyphClassTable) o; 1661 } 1662 if (((o = entries.get(1)) != null) && !(o instanceof GlyphClassTable)) { 1663 throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an GlyphClassTable, but is: " + o.getClass()); 1664 } else { 1665 bcdt = (GlyphClassTable) o; 1666 } 1667 if (((o = entries.get(2)) != null) && !(o instanceof GlyphClassTable)) { 1668 throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be an GlyphClassTable, but is: " + o.getClass()); 1669 } else { 1670 lcdt = (GlyphClassTable) o; 1671 } 1672 if (((o = entries.get(3)) == null) || !(o instanceof Integer)) { 1673 throw new AdvancedTypographicTableFormatException("illegal entries, fourth entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); 1674 } else { 1675 ngc = (Integer) (o); 1676 } 1677 if (((o = entries.get(4)) == null) || !(o instanceof RuleSet[])) { 1678 throw new AdvancedTypographicTableFormatException("illegal entries, fifth entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); 1679 } else { 1680 rsa = (RuleSet[]) o; 1681 if (rsa.length != ngc) { 1682 throw new AdvancedTypographicTableFormatException("illegal entries, RuleSet[] length is " + rsa.length + ", but expected " + ngc + " glyph classes"); 1683 } 1684 } 1685 } 1686 } 1687 } 1688 1689 private static class ChainedContextualSubtableFormat3 extends ChainedContextualSubtable { 1690 private RuleSet[] rsa; // rule set array, containing a single rule set 1691 ChainedContextualSubtableFormat3(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { 1692 super(id, sequence, flags, format, coverage, entries); 1693 populate(entries); 1694 } 1695 /** {@inheritDoc} */ 1696 public List getEntries() { 1697 if (rsa != null) { 1698 List entries = new ArrayList(1); 1699 entries.add(rsa); 1700 return entries; 1701 } else { 1702 return null; 1703 } 1704 } 1705 /** {@inheritDoc} */ 1706 public void resolveLookupReferences(Map<String, LookupTable> lookupTables) { 1707 GlyphTable.resolveLookupReferences(rsa, lookupTables); 1708 } 1709 /** {@inheritDoc} */ 1710 public RuleLookup[] getLookups(int ci, int gi, GlyphPositioningState ps, int[] rv) { 1711 assert ps != null; 1712 assert (rv != null) && (rv.length > 0); 1713 assert rsa != null; 1714 if (rsa.length > 0) { 1715 RuleSet rs = rsa [ 0 ]; 1716 if (rs != null) { 1717 Rule[] ra = rs.getRules(); 1718 for (Rule r : ra) { 1719 if ((r != null) && (r instanceof ChainedCoverageSequenceRule)) { 1720 ChainedCoverageSequenceRule cr = (ChainedCoverageSequenceRule) r; 1721 GlyphCoverageTable[] igca = cr.getCoverages(); 1722 if (matches(ps, igca, 0, rv)) { 1723 GlyphCoverageTable[] bgca = cr.getBacktrackCoverages(); 1724 if (matches(ps, bgca, -1, null)) { 1725 GlyphCoverageTable[] lgca = cr.getLookaheadCoverages(); 1726 if (matches(ps, lgca, rv[0], null)) { 1727 return r.getLookups(); 1728 } 1729 } 1730 } 1731 } 1732 } 1733 } 1734 } 1735 return null; 1736 } matches(GlyphPositioningState ps, GlyphCoverageTable[] gca, int offset, int[] rv)1737 private boolean matches(GlyphPositioningState ps, GlyphCoverageTable[] gca, int offset, int[] rv) { 1738 return ContextualSubtableFormat3.matches(ps, gca, offset, rv); 1739 } populate(List entries)1740 private void populate(List entries) { 1741 if (entries == null) { 1742 throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null"); 1743 } else if (entries.size() != 1) { 1744 throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); 1745 } else { 1746 Object o; 1747 if (((o = entries.get(0)) == null) || !(o instanceof RuleSet[])) { 1748 throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); 1749 } else { 1750 rsa = (RuleSet[]) o; 1751 } 1752 } 1753 } 1754 } 1755 1756 /** 1757 * The <code>DeviceTable</code> class implements a positioning device table record, comprising 1758 * adjustments to be made to scaled design units according to the scaled size. 1759 */ 1760 public static class DeviceTable { 1761 1762 private final int startSize; 1763 private final int endSize; 1764 private final int[] deltas; 1765 1766 /** 1767 * Instantiate a DeviceTable. 1768 * @param startSize the 1769 * @param endSize the ending (scaled) size 1770 * @param deltas adjustments for each scaled size 1771 */ DeviceTable(int startSize, int endSize, int[] deltas)1772 public DeviceTable(int startSize, int endSize, int[] deltas) { 1773 assert startSize >= 0; 1774 assert startSize <= endSize; 1775 assert deltas != null; 1776 assert deltas.length == (endSize - startSize) + 1; 1777 this.startSize = startSize; 1778 this.endSize = endSize; 1779 this.deltas = deltas; 1780 } 1781 1782 /** @return the start size */ getStartSize()1783 public int getStartSize() { 1784 return startSize; 1785 } 1786 1787 /** @return the end size */ getEndSize()1788 public int getEndSize() { 1789 return endSize; 1790 } 1791 1792 /** @return the deltas */ getDeltas()1793 public int[] getDeltas() { 1794 return deltas; 1795 } 1796 1797 /** 1798 * Find device adjustment. asf.todo at present, assumes that 1 device unit equals one point 1799 * @param fontSize the font size to search for 1800 * @return an adjustment if font size matches an entry 1801 */ findAdjustment(int fontSize)1802 public int findAdjustment(int fontSize) { 1803 // [TODO] at present, assumes that 1 device unit equals one point 1804 int fs = fontSize / 1000; 1805 if (fs < startSize) { 1806 return 0; 1807 } else if (fs <= endSize) { 1808 return deltas [ fs - startSize ] * 1000; 1809 } else { 1810 return 0; 1811 } 1812 } 1813 1814 /** {@inheritDoc} */ toString()1815 public String toString() { 1816 return "{ start = " + startSize + ", end = " + endSize + ", deltas = " + Arrays.toString(deltas) + "}"; 1817 } 1818 1819 } 1820 1821 /** 1822 * The <code>Value</code> class implements a positioning value record, comprising placement 1823 * and advancement information in X and Y axes, and optionally including device data used to 1824 * perform device (grid-fitted) specific fine grain adjustments. 1825 */ 1826 public static class Value { 1827 1828 /** X_PLACEMENT value format flag */ 1829 public static final int X_PLACEMENT = 0x0001; 1830 /** Y_PLACEMENT value format flag */ 1831 public static final int Y_PLACEMENT = 0x0002; 1832 /** X_ADVANCE value format flag */ 1833 public static final int X_ADVANCE = 0x0004; 1834 /** Y_ADVANCE value format flag */ 1835 public static final int Y_ADVANCE = 0x0008; 1836 /** X_PLACEMENT_DEVICE value format flag */ 1837 public static final int X_PLACEMENT_DEVICE = 0x0010; 1838 /** Y_PLACEMENT_DEVICE value format flag */ 1839 public static final int Y_PLACEMENT_DEVICE = 0x0020; 1840 /** X_ADVANCE_DEVICE value format flag */ 1841 public static final int X_ADVANCE_DEVICE = 0x0040; 1842 /** Y_ADVANCE_DEVICE value format flag */ 1843 public static final int Y_ADVANCE_DEVICE = 0x0080; 1844 1845 /** X_PLACEMENT value index (within adjustments arrays) */ 1846 public static final int IDX_X_PLACEMENT = 0; 1847 /** Y_PLACEMENT value index (within adjustments arrays) */ 1848 public static final int IDX_Y_PLACEMENT = 1; 1849 /** X_ADVANCE value index (within adjustments arrays) */ 1850 public static final int IDX_X_ADVANCE = 2; 1851 /** Y_ADVANCE value index (within adjustments arrays) */ 1852 public static final int IDX_Y_ADVANCE = 3; 1853 1854 private int xPlacement; // x placement 1855 private int yPlacement; // y placement 1856 private int xAdvance; // x advance 1857 private int yAdvance; // y advance 1858 private final DeviceTable xPlaDevice; // x placement device table 1859 private final DeviceTable yPlaDevice; // y placement device table 1860 private final DeviceTable xAdvDevice; // x advance device table 1861 private final DeviceTable yAdvDevice; // x advance device table 1862 1863 /** 1864 * Instantiate a Value. 1865 * @param xPlacement the x placement or zero 1866 * @param yPlacement the y placement or zero 1867 * @param xAdvance the x advance or zero 1868 * @param yAdvance the y advance or zero 1869 * @param xPlaDevice the x placement device table or null 1870 * @param yPlaDevice the y placement device table or null 1871 * @param xAdvDevice the x advance device table or null 1872 * @param yAdvDevice the y advance device table or null 1873 */ Value(int xPlacement, int yPlacement, int xAdvance, int yAdvance, DeviceTable xPlaDevice, DeviceTable yPlaDevice, DeviceTable xAdvDevice, DeviceTable yAdvDevice)1874 public Value(int xPlacement, int yPlacement, int xAdvance, int yAdvance, DeviceTable xPlaDevice, DeviceTable yPlaDevice, DeviceTable xAdvDevice, DeviceTable yAdvDevice) { 1875 this.xPlacement = xPlacement; 1876 this.yPlacement = yPlacement; 1877 this.xAdvance = xAdvance; 1878 this.yAdvance = yAdvance; 1879 this.xPlaDevice = xPlaDevice; 1880 this.yPlaDevice = yPlaDevice; 1881 this.xAdvDevice = xAdvDevice; 1882 this.yAdvDevice = yAdvDevice; 1883 } 1884 1885 /** @return the x placement */ getXPlacement()1886 public int getXPlacement() { 1887 return xPlacement; 1888 } 1889 1890 /** @return the y placement */ getYPlacement()1891 public int getYPlacement() { 1892 return yPlacement; 1893 } 1894 1895 /** @return the x advance */ getXAdvance()1896 public int getXAdvance() { 1897 return xAdvance; 1898 } 1899 1900 /** @return the y advance */ getYAdvance()1901 public int getYAdvance() { 1902 return yAdvance; 1903 } 1904 1905 /** @return the x placement device table */ getXPlaDevice()1906 public DeviceTable getXPlaDevice() { 1907 return xPlaDevice; 1908 } 1909 1910 /** @return the y placement device table */ getYPlaDevice()1911 public DeviceTable getYPlaDevice() { 1912 return yPlaDevice; 1913 } 1914 1915 /** @return the x advance device table */ getXAdvDevice()1916 public DeviceTable getXAdvDevice() { 1917 return xAdvDevice; 1918 } 1919 1920 /** @return the y advance device table */ getYAdvDevice()1921 public DeviceTable getYAdvDevice() { 1922 return yAdvDevice; 1923 } 1924 1925 /** 1926 * Apply value to specific adjustments to without use of device table adjustments. 1927 * @param xPlacement the x placement or zero 1928 * @param yPlacement the y placement or zero 1929 * @param xAdvance the x advance or zero 1930 * @param yAdvance the y advance or zero 1931 */ adjust(int xPlacement, int yPlacement, int xAdvance, int yAdvance)1932 public void adjust(int xPlacement, int yPlacement, int xAdvance, int yAdvance) { 1933 this.xPlacement += xPlacement; 1934 this.yPlacement += yPlacement; 1935 this.xAdvance += xAdvance; 1936 this.yAdvance += yAdvance; 1937 } 1938 1939 /** 1940 * Apply value to adjustments using font size for device table adjustments. 1941 * @param adjustments array of four integers containing X,Y placement and X,Y advance adjustments 1942 * @param fontSize font size for device table adjustments 1943 * @return true if some adjustment was made 1944 */ adjust(int[] adjustments, int fontSize)1945 public boolean adjust(int[] adjustments, int fontSize) { 1946 boolean adjust = false; 1947 int dv; 1948 if ((dv = xPlacement) != 0) { 1949 adjustments [ IDX_X_PLACEMENT ] += dv; 1950 adjust = true; 1951 } 1952 if ((dv = yPlacement) != 0) { 1953 adjustments [ IDX_Y_PLACEMENT ] += dv; 1954 adjust = true; 1955 } 1956 if ((dv = xAdvance) != 0) { 1957 adjustments [ IDX_X_ADVANCE ] += dv; 1958 adjust = true; 1959 } 1960 if ((dv = yAdvance) != 0) { 1961 adjustments [ IDX_Y_ADVANCE ] += dv; 1962 adjust = true; 1963 } 1964 if (fontSize != 0) { 1965 DeviceTable dt; 1966 if ((dt = xPlaDevice) != null) { 1967 if ((dv = dt.findAdjustment(fontSize)) != 0) { 1968 adjustments [ IDX_X_PLACEMENT ] += dv; 1969 adjust = true; 1970 } 1971 } 1972 if ((dt = yPlaDevice) != null) { 1973 if ((dv = dt.findAdjustment(fontSize)) != 0) { 1974 adjustments [ IDX_Y_PLACEMENT ] += dv; 1975 adjust = true; 1976 } 1977 } 1978 if ((dt = xAdvDevice) != null) { 1979 if ((dv = dt.findAdjustment(fontSize)) != 0) { 1980 adjustments [ IDX_X_ADVANCE ] += dv; 1981 adjust = true; 1982 } 1983 } 1984 if ((dt = yAdvDevice) != null) { 1985 if ((dv = dt.findAdjustment(fontSize)) != 0) { 1986 adjustments [ IDX_Y_ADVANCE ] += dv; 1987 adjust = true; 1988 } 1989 } 1990 } 1991 return adjust; 1992 } 1993 1994 /** {@inheritDoc} */ toString()1995 public String toString() { 1996 StringBuffer sb = new StringBuffer(); 1997 boolean first = true; 1998 sb.append("{ "); 1999 if (xPlacement != 0) { 2000 if (!first) { 2001 sb.append(", "); 2002 } else { 2003 first = false; 2004 } 2005 sb.append("xPlacement = " + xPlacement); 2006 } 2007 if (yPlacement != 0) { 2008 if (!first) { 2009 sb.append(", "); 2010 } else { 2011 first = false; 2012 } 2013 sb.append("yPlacement = " + yPlacement); 2014 } 2015 if (xAdvance != 0) { 2016 if (!first) { 2017 sb.append(", "); 2018 } else { 2019 first = false; 2020 } 2021 sb.append("xAdvance = " + xAdvance); 2022 } 2023 if (yAdvance != 0) { 2024 if (!first) { 2025 sb.append(", "); 2026 } else { 2027 first = false; 2028 } 2029 sb.append("yAdvance = " + yAdvance); 2030 } 2031 if (xPlaDevice != null) { 2032 if (!first) { 2033 sb.append(", "); 2034 } else { 2035 first = false; 2036 } 2037 sb.append("xPlaDevice = " + xPlaDevice); 2038 } 2039 if (yPlaDevice != null) { 2040 if (!first) { 2041 sb.append(", "); 2042 } else { 2043 first = false; 2044 } 2045 sb.append("xPlaDevice = " + yPlaDevice); 2046 } 2047 if (xAdvDevice != null) { 2048 if (!first) { 2049 sb.append(", "); 2050 } else { 2051 first = false; 2052 } 2053 sb.append("xAdvDevice = " + xAdvDevice); 2054 } 2055 if (yAdvDevice != null) { 2056 if (!first) { 2057 sb.append(", "); 2058 } else { 2059 first = false; 2060 } 2061 sb.append("xAdvDevice = " + yAdvDevice); 2062 } 2063 sb.append(" }"); 2064 return sb.toString(); 2065 } 2066 2067 } 2068 2069 /** 2070 * The <code>PairValues</code> class implements a pair value record, comprising a glyph id (or zero) 2071 * and two optional positioning values. 2072 */ 2073 public static class PairValues { 2074 2075 private final int glyph; // glyph id (or 0) 2076 private final Value value1; // value for first glyph in pair (or null) 2077 private final Value value2; // value for second glyph in pair (or null) 2078 2079 /** 2080 * Instantiate a PairValues. 2081 * @param glyph the glyph id (or zero) 2082 * @param value1 the value of the first glyph in pair (or null) 2083 * @param value2 the value of the second glyph in pair (or null) 2084 */ PairValues(int glyph, Value value1, Value value2)2085 public PairValues(int glyph, Value value1, Value value2) { 2086 assert glyph >= 0; 2087 this.glyph = glyph; 2088 this.value1 = value1; 2089 this.value2 = value2; 2090 } 2091 2092 /** @return the glyph id */ getGlyph()2093 public int getGlyph() { 2094 return glyph; 2095 } 2096 2097 /** @return the first value */ getValue1()2098 public Value getValue1() { 2099 return value1; 2100 } 2101 2102 /** @return the second value */ getValue2()2103 public Value getValue2() { 2104 return value2; 2105 } 2106 2107 /** {@inheritDoc} */ toString()2108 public String toString() { 2109 StringBuffer sb = new StringBuffer(); 2110 boolean first = true; 2111 sb.append("{ "); 2112 if (glyph != 0) { 2113 if (!first) { 2114 sb.append(", "); 2115 } else { 2116 first = false; 2117 } 2118 sb.append("glyph = " + glyph); 2119 } 2120 if (value1 != null) { 2121 if (!first) { 2122 sb.append(", "); 2123 } else { 2124 first = false; 2125 } 2126 sb.append("value1 = " + value1); 2127 } 2128 if (value2 != null) { 2129 if (!first) { 2130 sb.append(", "); 2131 } else { 2132 first = false; 2133 } 2134 sb.append("value2 = " + value2); 2135 } 2136 sb.append(" }"); 2137 return sb.toString(); 2138 } 2139 2140 } 2141 2142 /** 2143 * The <code>Anchor</code> class implements a anchor record, comprising an X,Y coordinate pair, 2144 * an optional anchor point index (or -1), and optional X or Y device tables (or null if absent). 2145 */ 2146 public static class Anchor { 2147 2148 private final int x; // xCoordinate (in design units) 2149 private final int y; // yCoordinate (in design units) 2150 private final int anchorPoint; // anchor point index (or -1) 2151 private final DeviceTable xDevice; // x device table 2152 private final DeviceTable yDevice; // y device table 2153 2154 /** 2155 * Instantiate an Anchor (format 1). 2156 * @param x the x coordinate 2157 * @param y the y coordinate 2158 */ Anchor(int x, int y)2159 public Anchor(int x, int y) { 2160 this (x, y, -1, null, null); 2161 } 2162 2163 /** 2164 * Instantiate an Anchor (format 2). 2165 * @param x the x coordinate 2166 * @param y the y coordinate 2167 * @param anchorPoint anchor index (or -1) 2168 */ Anchor(int x, int y, int anchorPoint)2169 public Anchor(int x, int y, int anchorPoint) { 2170 this (x, y, anchorPoint, null, null); 2171 } 2172 2173 /** 2174 * Instantiate an Anchor (format 3). 2175 * @param x the x coordinate 2176 * @param y the y coordinate 2177 * @param xDevice the x device table (or null if not present) 2178 * @param yDevice the y device table (or null if not present) 2179 */ Anchor(int x, int y, DeviceTable xDevice, DeviceTable yDevice)2180 public Anchor(int x, int y, DeviceTable xDevice, DeviceTable yDevice) { 2181 this (x, y, -1, xDevice, yDevice); 2182 } 2183 2184 /** 2185 * Instantiate an Anchor based on an existing anchor. 2186 * @param a the existing anchor 2187 */ Anchor(Anchor a)2188 protected Anchor(Anchor a) { 2189 this (a.x, a.y, a.anchorPoint, a.xDevice, a.yDevice); 2190 } 2191 Anchor(int x, int y, int anchorPoint, DeviceTable xDevice, DeviceTable yDevice)2192 private Anchor(int x, int y, int anchorPoint, DeviceTable xDevice, DeviceTable yDevice) { 2193 assert (anchorPoint >= 0) || (anchorPoint == -1); 2194 this.x = x; 2195 this.y = y; 2196 this.anchorPoint = anchorPoint; 2197 this.xDevice = xDevice; 2198 this.yDevice = yDevice; 2199 } 2200 2201 /** @return the x coordinate */ getX()2202 public int getX() { 2203 return x; 2204 } 2205 2206 /** @return the y coordinate */ getY()2207 public int getY() { 2208 return y; 2209 } 2210 2211 /** @return the anchor point index (or -1 if not specified) */ getAnchorPoint()2212 public int getAnchorPoint() { 2213 return anchorPoint; 2214 } 2215 2216 /** @return the x device table (or null if not specified) */ getXDevice()2217 public DeviceTable getXDevice() { 2218 return xDevice; 2219 } 2220 2221 /** @return the y device table (or null if not specified) */ getYDevice()2222 public DeviceTable getYDevice() { 2223 return yDevice; 2224 } 2225 2226 /** 2227 * Obtain adjustment value required to align the specified anchor 2228 * with this anchor. 2229 * @param a the anchor to align 2230 * @return the adjustment value needed to effect alignment 2231 */ getAlignmentAdjustment(Anchor a)2232 public Value getAlignmentAdjustment(Anchor a) { 2233 assert a != null; 2234 // TODO - handle anchor point 2235 // TODO - handle device tables 2236 return new Value(x - a.x, y - a.y, 0, 0, null, null, null, null); 2237 } 2238 2239 /** {@inheritDoc} */ toString()2240 public String toString() { 2241 StringBuffer sb = new StringBuffer(); 2242 sb.append("{ [" + x + "," + y + "]"); 2243 if (anchorPoint != -1) { 2244 sb.append(", anchorPoint = " + anchorPoint); 2245 } 2246 if (xDevice != null) { 2247 sb.append(", xDevice = " + xDevice); 2248 } 2249 if (yDevice != null) { 2250 sb.append(", yDevice = " + yDevice); 2251 } 2252 sb.append(" }"); 2253 return sb.toString(); 2254 } 2255 2256 } 2257 2258 /** 2259 * The <code>MarkAnchor</code> class is a subclass of the <code>Anchor</code> class, adding a mark 2260 * class designation. 2261 */ 2262 public static class MarkAnchor extends Anchor { 2263 2264 private final int markClass; // mark class 2265 2266 /** 2267 * Instantiate a MarkAnchor 2268 * @param markClass the mark class 2269 * @param a the underlying anchor (whose fields are copied) 2270 */ MarkAnchor(int markClass, Anchor a)2271 public MarkAnchor(int markClass, Anchor a) { 2272 super(a); 2273 this.markClass = markClass; 2274 } 2275 2276 /** @return the mark class */ getMarkClass()2277 public int getMarkClass() { 2278 return markClass; 2279 } 2280 2281 /** {@inheritDoc} */ toString()2282 public String toString() { 2283 return "{ markClass = " + markClass + ", anchor = " + super.toString() + " }"; 2284 } 2285 2286 } 2287 2288 } 2289