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.io.IOException; 23 import java.util.Arrays; 24 import java.util.HashMap; 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.fonts.truetype.FontFileReader; 33 import org.apache.fop.fonts.truetype.OFDirTabEntry; 34 import org.apache.fop.fonts.truetype.OFTableName; 35 import org.apache.fop.fonts.truetype.OpenFont; 36 37 // CSOFF: LineLengthCheck 38 39 /** 40 * <p>OpenType Font (OTF) advanced typographic table reader. Used by @{Link org.apache.fop.fonts.truetype.TTFFile} 41 * to read advanced typographic tables (GDEF, GSUB, GPOS).</p> 42 * 43 * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p> 44 */ 45 public final class OTFAdvancedTypographicTableReader { 46 47 // logging state 48 private static Log log = LogFactory.getLog(OTFAdvancedTypographicTableReader.class); 49 // instance state 50 private OpenFont otf; // parent font file reader 51 private FontFileReader in; // input reader 52 private GlyphDefinitionTable gdef; // glyph definition table 53 private GlyphSubstitutionTable gsub; // glyph substitution table 54 private GlyphPositioningTable gpos; // glyph positioning table 55 // transient parsing state 56 private transient Map<String, Object> seScripts; // script-tag => Object[3] : { default-language-tag, List(language-tag), seLanguages } 57 private transient Map<String, Object> seLanguages; // language-tag => Object[2] : { "f<required-feature-index>", List("f<feature-index>") 58 private transient Map<String, Object> seFeatures; // "f<feature-index>" => Object[2] : { feature-tag, List("lu<lookup-index>") } 59 private transient GlyphMappingTable seMapping; // subtable entry mappings 60 private transient List seEntries; // subtable entry entries 61 private transient List seSubtables; // subtable entry subtables 62 private Map<String, ScriptProcessor> processors = new HashMap<String, ScriptProcessor>(); 63 64 /** 65 * Construct an <code>OTFAdvancedTypographicTableReader</code> instance. 66 * @param otf parent font file reader (must be non-null) 67 * @param in font file reader (must be non-null) 68 */ OTFAdvancedTypographicTableReader(OpenFont otf, FontFileReader in)69 public OTFAdvancedTypographicTableReader(OpenFont otf, FontFileReader in) { 70 assert otf != null; 71 assert in != null; 72 this.otf = otf; 73 this.in = in; 74 } 75 76 /** 77 * Read all advanced typographic tables. 78 * @throws AdvancedTypographicTableFormatException if ATT table has invalid format 79 */ readAll()80 public void readAll() throws AdvancedTypographicTableFormatException { 81 try { 82 readGDEF(); 83 readGSUB(); 84 readGPOS(); 85 } catch (AdvancedTypographicTableFormatException e) { 86 resetATStateAll(); 87 throw e; 88 } catch (IOException e) { 89 resetATStateAll(); 90 throw new AdvancedTypographicTableFormatException(e.getMessage(), e); 91 } finally { 92 resetATState(); 93 } 94 } 95 96 /** 97 * Determine if advanced (typographic) table is present. 98 * @return true if advanced (typographic) table is present 99 */ hasAdvancedTable()100 public boolean hasAdvancedTable() { 101 return (gdef != null) || (gsub != null) || (gpos != null); 102 } 103 104 /** 105 * Returns the GDEF table or null if none present. 106 * @return the GDEF table 107 */ getGDEF()108 public GlyphDefinitionTable getGDEF() { 109 return gdef; 110 } 111 112 /** 113 * Returns the GSUB table or null if none present. 114 * @return the GSUB table 115 */ getGSUB()116 public GlyphSubstitutionTable getGSUB() { 117 return gsub; 118 } 119 120 /** 121 * Returns the GPOS table or null if none present. 122 * @return the GPOS table 123 */ getGPOS()124 public GlyphPositioningTable getGPOS() { 125 return gpos; 126 } 127 readLangSysTable(OFTableName tableTag, long langSysTable, String langSysTag)128 private void readLangSysTable(OFTableName tableTag, long langSysTable, String langSysTag) 129 throws IOException { 130 in.seekSet(langSysTable); 131 if (log.isDebugEnabled()) { 132 log.debug(tableTag + " lang sys table: " + langSysTag); 133 } 134 // read lookup order (reorder) table offset 135 int lo = in.readTTFUShort(); 136 // read required feature index 137 int rf = in.readTTFUShort(); 138 String rfi; 139 if (rf != 65535) { 140 rfi = "f" + rf; 141 } else { 142 rfi = null; 143 } 144 // read (non-required) feature count 145 int nf = in.readTTFUShort(); 146 // dump info if debugging 147 if (log.isDebugEnabled()) { 148 log.debug(tableTag + " lang sys table reorder table: " + lo); 149 log.debug(tableTag + " lang sys table required feature index: " + rf); 150 log.debug(tableTag + " lang sys table non-required feature count: " + nf); 151 } 152 // read (non-required) feature indices 153 List fl = new java.util.ArrayList(); 154 for (int i = 0; i < nf; i++) { 155 int fi = in.readTTFUShort(); 156 if (log.isDebugEnabled()) { 157 log.debug(tableTag + " lang sys table non-required feature index: " + fi); 158 } 159 fl.add("f" + fi); 160 } 161 if (seLanguages == null) { 162 seLanguages = new java.util.LinkedHashMap(); 163 } 164 seLanguages.put(langSysTag, new Object[] { rfi, fl }); 165 } 166 167 private static String defaultTag = "dflt"; 168 readScriptTable(OFTableName tableTag, long scriptTable, String scriptTag)169 private void readScriptTable(OFTableName tableTag, long scriptTable, String scriptTag) throws IOException { 170 in.seekSet(scriptTable); 171 if (log.isDebugEnabled()) { 172 log.debug(tableTag + " script table: " + scriptTag); 173 } 174 // read default language system table offset 175 int dl = in.readTTFUShort(); 176 String dt = defaultTag; 177 if (dl > 0) { 178 if (log.isDebugEnabled()) { 179 log.debug(tableTag + " default lang sys tag: " + dt); 180 log.debug(tableTag + " default lang sys table offset: " + dl); 181 } 182 } 183 // read language system record count 184 int nl = in.readTTFUShort(); 185 List ll = new java.util.ArrayList(); 186 if (nl > 0) { 187 String[] lta = new String[nl]; 188 int[] loa = new int[nl]; 189 // read language system records 190 for (int i = 0, n = nl; i < n; i++) { 191 String lt = in.readTTFString(4); 192 int lo = in.readTTFUShort(); 193 if (log.isDebugEnabled()) { 194 log.debug(tableTag + " lang sys tag: " + lt); 195 log.debug(tableTag + " lang sys table offset: " + lo); 196 } 197 lta[i] = lt; 198 loa[i] = lo; 199 if (dl == lo) { 200 dl = 0; 201 dt = lt; 202 } 203 ll.add(lt); 204 } 205 // read non-default language system tables 206 for (int i = 0, n = nl; i < n; i++) { 207 readLangSysTable(tableTag, scriptTable + loa [ i ], lta [ i ]); 208 } 209 } 210 // read default language system table (if specified) 211 if (dl > 0) { 212 readLangSysTable(tableTag, scriptTable + dl, dt); 213 } else if (dt != null) { 214 if (log.isDebugEnabled()) { 215 log.debug(tableTag + " lang sys default: " + dt); 216 } 217 } 218 if (seLanguages != null) { 219 seScripts.put(scriptTag, new Object[]{dt, ll, seLanguages}); 220 } 221 seLanguages = null; 222 } 223 readScriptList(OFTableName tableTag, long scriptList)224 private void readScriptList(OFTableName tableTag, long scriptList) throws IOException { 225 in.seekSet(scriptList); 226 // read script record count 227 int ns = in.readTTFUShort(); 228 if (log.isDebugEnabled()) { 229 log.debug(tableTag + " script list record count: " + ns); 230 } 231 if (ns > 0) { 232 String[] sta = new String[ns]; 233 int[] soa = new int[ns]; 234 // read script records 235 for (int i = 0, n = ns; i < n; i++) { 236 String st = in.readTTFString(4); 237 int so = in.readTTFUShort(); 238 if (log.isDebugEnabled()) { 239 log.debug(tableTag + " script tag: " + st); 240 log.debug(tableTag + " script table offset: " + so); 241 } 242 sta[i] = st; 243 soa[i] = so; 244 } 245 // read script tables 246 for (int i = 0, n = ns; i < n; i++) { 247 seLanguages = null; 248 readScriptTable(tableTag, scriptList + soa [ i ], sta [ i ]); 249 } 250 } 251 } 252 readFeatureTable(OFTableName tableTag, long featureTable, String featureTag, int featureIndex)253 private void readFeatureTable(OFTableName tableTag, long featureTable, String featureTag, int featureIndex) throws IOException { 254 in.seekSet(featureTable); 255 if (log.isDebugEnabled()) { 256 log.debug(tableTag + " feature table: " + featureTag); 257 } 258 // read feature params offset 259 int po = in.readTTFUShort(); 260 // read lookup list indices count 261 int nl = in.readTTFUShort(); 262 // dump info if debugging 263 if (log.isDebugEnabled()) { 264 log.debug(tableTag + " feature table parameters offset: " + po); 265 log.debug(tableTag + " feature table lookup list index count: " + nl); 266 } 267 // read lookup table indices 268 List lul = new java.util.ArrayList(); 269 for (int i = 0; i < nl; i++) { 270 int li = in.readTTFUShort(); 271 if (log.isDebugEnabled()) { 272 log.debug(tableTag + " feature table lookup index: " + li); 273 } 274 lul.add("lu" + li); 275 } 276 seFeatures.put("f" + featureIndex, new Object[] { featureTag, lul }); 277 } 278 readFeatureList(OFTableName tableTag, long featureList)279 private void readFeatureList(OFTableName tableTag, long featureList) throws IOException { 280 in.seekSet(featureList); 281 // read feature record count 282 int nf = in.readTTFUShort(); 283 if (log.isDebugEnabled()) { 284 log.debug(tableTag + " feature list record count: " + nf); 285 } 286 if (nf > 0) { 287 String[] fta = new String[nf]; 288 int[] foa = new int[nf]; 289 // read feature records 290 for (int i = 0, n = nf; i < n; i++) { 291 String ft = in.readTTFString(4); 292 int fo = in.readTTFUShort(); 293 if (log.isDebugEnabled()) { 294 log.debug(tableTag + " feature tag: " + ft); 295 log.debug(tableTag + " feature table offset: " + fo); 296 } 297 fta[i] = ft; 298 foa[i] = fo; 299 } 300 // read feature tables 301 for (int i = 0, n = nf; i < n; i++) { 302 if (log.isDebugEnabled()) { 303 log.debug(tableTag + " feature index: " + i); 304 } 305 readFeatureTable(tableTag, featureList + foa [ i ], fta [ i ], i); 306 } 307 } 308 } 309 310 static final class GDEFLookupType { 311 static final int GLYPH_CLASS = 1; 312 static final int ATTACHMENT_POINT = 2; 313 static final int LIGATURE_CARET = 3; 314 static final int MARK_ATTACHMENT = 4; GDEFLookupType()315 private GDEFLookupType() { 316 } getSubtableType(int lt)317 public static int getSubtableType(int lt) { 318 int st; 319 switch (lt) { 320 case GDEFLookupType.GLYPH_CLASS: 321 st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_GLYPH_CLASS; 322 break; 323 case GDEFLookupType.ATTACHMENT_POINT: 324 st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_ATTACHMENT_POINT; 325 break; 326 case GDEFLookupType.LIGATURE_CARET: 327 st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_LIGATURE_CARET; 328 break; 329 case GDEFLookupType.MARK_ATTACHMENT: 330 st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_MARK_ATTACHMENT; 331 break; 332 default: 333 st = -1; 334 break; 335 } 336 return st; 337 } toString(int type)338 public static String toString(int type) { 339 String s; 340 switch (type) { 341 case GLYPH_CLASS: 342 s = "GlyphClass"; 343 break; 344 case ATTACHMENT_POINT: 345 s = "AttachmentPoint"; 346 break; 347 case LIGATURE_CARET: 348 s = "LigatureCaret"; 349 break; 350 case MARK_ATTACHMENT: 351 s = "MarkAttachment"; 352 break; 353 default: 354 s = "?"; 355 break; 356 } 357 return s; 358 } 359 } 360 361 static final class GSUBLookupType { 362 static final int SINGLE = 1; 363 static final int MULTIPLE = 2; 364 static final int ALTERNATE = 3; 365 static final int LIGATURE = 4; 366 static final int CONTEXTUAL = 5; 367 static final int CHAINED_CONTEXTUAL = 6; 368 static final int EXTENSION = 7; 369 static final int REVERSE_CHAINED_SINGLE = 8; GSUBLookupType()370 private GSUBLookupType() { 371 } getSubtableType(int lt)372 public static int getSubtableType(int lt) { 373 int st; 374 switch (lt) { 375 case GSUBLookupType.SINGLE: 376 st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_SINGLE; 377 break; 378 case GSUBLookupType.MULTIPLE: 379 st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_MULTIPLE; 380 break; 381 case GSUBLookupType.ALTERNATE: 382 st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_ALTERNATE; 383 break; 384 case GSUBLookupType.LIGATURE: 385 st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_LIGATURE; 386 break; 387 case GSUBLookupType.CONTEXTUAL: 388 st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CONTEXTUAL; 389 break; 390 case GSUBLookupType.CHAINED_CONTEXTUAL: 391 st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL; 392 break; 393 case GSUBLookupType.EXTENSION: 394 st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION; 395 break; 396 case GSUBLookupType.REVERSE_CHAINED_SINGLE: 397 st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE; 398 break; 399 default: 400 st = -1; 401 break; 402 } 403 return st; 404 } toString(int type)405 public static String toString(int type) { 406 String s; 407 switch (type) { 408 case SINGLE: 409 s = "Single"; 410 break; 411 case MULTIPLE: 412 s = "Multiple"; 413 break; 414 case ALTERNATE: 415 s = "Alternate"; 416 break; 417 case LIGATURE: 418 s = "Ligature"; 419 break; 420 case CONTEXTUAL: 421 s = "Contextual"; 422 break; 423 case CHAINED_CONTEXTUAL: 424 s = "ChainedContextual"; 425 break; 426 case EXTENSION: 427 s = "Extension"; 428 break; 429 case REVERSE_CHAINED_SINGLE: 430 s = "ReverseChainedSingle"; 431 break; 432 default: 433 s = "?"; 434 break; 435 } 436 return s; 437 } 438 } 439 440 static final class GPOSLookupType { 441 static final int SINGLE = 1; 442 static final int PAIR = 2; 443 static final int CURSIVE = 3; 444 static final int MARK_TO_BASE = 4; 445 static final int MARK_TO_LIGATURE = 5; 446 static final int MARK_TO_MARK = 6; 447 static final int CONTEXTUAL = 7; 448 static final int CHAINED_CONTEXTUAL = 8; 449 static final int EXTENSION = 9; GPOSLookupType()450 private GPOSLookupType() { 451 } toString(int type)452 public static String toString(int type) { 453 String s; 454 switch (type) { 455 case SINGLE: 456 s = "Single"; 457 break; 458 case PAIR: 459 s = "Pair"; 460 break; 461 case CURSIVE: 462 s = "Cursive"; 463 break; 464 case MARK_TO_BASE: 465 s = "MarkToBase"; 466 break; 467 case MARK_TO_LIGATURE: 468 s = "MarkToLigature"; 469 break; 470 case MARK_TO_MARK: 471 s = "MarkToMark"; 472 break; 473 case CONTEXTUAL: 474 s = "Contextual"; 475 break; 476 case CHAINED_CONTEXTUAL: 477 s = "ChainedContextual"; 478 break; 479 case EXTENSION: 480 s = "Extension"; 481 break; 482 default: 483 s = "?"; 484 break; 485 } 486 return s; 487 } 488 } 489 490 static final class LookupFlag { 491 static final int RIGHT_TO_LEFT = 0x0001; 492 static final int IGNORE_BASE_GLYPHS = 0x0002; 493 static final int IGNORE_LIGATURE = 0x0004; 494 static final int IGNORE_MARKS = 0x0008; 495 static final int USE_MARK_FILTERING_SET = 0x0010; 496 static final int MARK_ATTACHMENT_TYPE = 0xFF00; LookupFlag()497 private LookupFlag() { 498 } toString(int flags)499 public static String toString(int flags) { 500 StringBuffer sb = new StringBuffer(); 501 boolean first = true; 502 if ((flags & RIGHT_TO_LEFT) != 0) { 503 if (first) { 504 first = false; 505 } else { 506 sb.append('|'); 507 } 508 sb.append("RightToLeft"); 509 } 510 if ((flags & IGNORE_BASE_GLYPHS) != 0) { 511 if (first) { 512 first = false; 513 } else { 514 sb.append('|'); 515 } 516 sb.append("IgnoreBaseGlyphs"); 517 } 518 if ((flags & IGNORE_LIGATURE) != 0) { 519 if (first) { 520 first = false; 521 } else { 522 sb.append('|'); 523 } 524 sb.append("IgnoreLigature"); 525 } 526 if ((flags & IGNORE_MARKS) != 0) { 527 if (first) { 528 first = false; 529 } else { 530 sb.append('|'); 531 } 532 sb.append("IgnoreMarks"); 533 } 534 if ((flags & USE_MARK_FILTERING_SET) != 0) { 535 if (first) { 536 first = false; 537 } else { 538 sb.append('|'); 539 } 540 sb.append("UseMarkFilteringSet"); 541 } 542 if (sb.length() == 0) { 543 sb.append('-'); 544 } 545 return sb.toString(); 546 } 547 } 548 readCoverageTableFormat1(String label, long tableOffset, int coverageFormat)549 private GlyphCoverageTable readCoverageTableFormat1(String label, long tableOffset, int coverageFormat) throws IOException { 550 List entries = new java.util.ArrayList(); 551 in.seekSet(tableOffset); 552 // skip over format (already known) 553 in.skip(2); 554 // read glyph count 555 int ng = in.readTTFUShort(); 556 int[] ga = new int[ng]; 557 for (int i = 0, n = ng; i < n; i++) { 558 int g = in.readTTFUShort(); 559 ga[i] = g; 560 entries.add(g); 561 } 562 // dump info if debugging 563 if (log.isDebugEnabled()) { 564 log.debug(label + " glyphs: " + toString(ga)); 565 } 566 return GlyphCoverageTable.createCoverageTable(entries); 567 } 568 readCoverageTableFormat2(String label, long tableOffset, int coverageFormat)569 private GlyphCoverageTable readCoverageTableFormat2(String label, long tableOffset, int coverageFormat) throws IOException { 570 List entries = new java.util.ArrayList(); 571 in.seekSet(tableOffset); 572 // skip over format (already known) 573 in.skip(2); 574 // read range record count 575 int nr = in.readTTFUShort(); 576 for (int i = 0, n = nr; i < n; i++) { 577 // read range start 578 int s = in.readTTFUShort(); 579 // read range end 580 int e = in.readTTFUShort(); 581 // read range coverage (mapping) index 582 int m = in.readTTFUShort(); 583 // dump info if debugging 584 if (log.isDebugEnabled()) { 585 log.debug(label + " range[" + i + "]: [" + s + "," + e + "]: " + m); 586 } 587 entries.add(new GlyphCoverageTable.MappingRange(s, e, m)); 588 } 589 return GlyphCoverageTable.createCoverageTable(entries); 590 } 591 readCoverageTable(String label, long tableOffset)592 private GlyphCoverageTable readCoverageTable(String label, long tableOffset) throws IOException { 593 GlyphCoverageTable gct; 594 long cp = in.getCurrentPos(); 595 in.seekSet(tableOffset); 596 // read coverage table format 597 int cf = in.readTTFUShort(); 598 if (cf == 1) { 599 gct = readCoverageTableFormat1(label, tableOffset, cf); 600 } else if (cf == 2) { 601 gct = readCoverageTableFormat2(label, tableOffset, cf); 602 } else { 603 throw new AdvancedTypographicTableFormatException("unsupported coverage table format: " + cf); 604 } 605 in.seekSet(cp); 606 return gct; 607 } 608 readClassDefTableFormat1(String label, long tableOffset, int classFormat)609 private GlyphClassTable readClassDefTableFormat1(String label, long tableOffset, int classFormat) throws IOException { 610 List entries = new java.util.ArrayList(); 611 in.seekSet(tableOffset); 612 // skip over format (already known) 613 in.skip(2); 614 // read start glyph 615 int sg = in.readTTFUShort(); 616 entries.add(sg); 617 // read glyph count 618 int ng = in.readTTFUShort(); 619 // read glyph classes 620 int[] ca = new int[ng]; 621 for (int i = 0, n = ng; i < n; i++) { 622 int gc = in.readTTFUShort(); 623 ca[i] = gc; 624 entries.add(gc); 625 } 626 // dump info if debugging 627 if (log.isDebugEnabled()) { 628 log.debug(label + " glyph classes: " + toString(ca)); 629 } 630 return GlyphClassTable.createClassTable(entries); 631 } 632 readClassDefTableFormat2(String label, long tableOffset, int classFormat)633 private GlyphClassTable readClassDefTableFormat2(String label, long tableOffset, int classFormat) throws IOException { 634 List entries = new java.util.ArrayList(); 635 in.seekSet(tableOffset); 636 // skip over format (already known) 637 in.skip(2); 638 // read range record count 639 int nr = in.readTTFUShort(); 640 for (int i = 0, n = nr; i < n; i++) { 641 // read range start 642 int s = in.readTTFUShort(); 643 // read range end 644 int e = in.readTTFUShort(); 645 // read range glyph class (mapping) index 646 int m = in.readTTFUShort(); 647 // dump info if debugging 648 if (log.isDebugEnabled()) { 649 log.debug(label + " range[" + i + "]: [" + s + "," + e + "]: " + m); 650 } 651 entries.add(new GlyphClassTable.MappingRange(s, e, m)); 652 } 653 return GlyphClassTable.createClassTable(entries); 654 } 655 readClassDefTable(String label, long tableOffset)656 private GlyphClassTable readClassDefTable(String label, long tableOffset) throws IOException { 657 GlyphClassTable gct; 658 long cp = in.getCurrentPos(); 659 in.seekSet(tableOffset); 660 // read class table format 661 int cf = in.readTTFUShort(); 662 if (cf == 1) { 663 gct = readClassDefTableFormat1(label, tableOffset, cf); 664 } else if (cf == 2) { 665 gct = readClassDefTableFormat2(label, tableOffset, cf); 666 } else { 667 throw new AdvancedTypographicTableFormatException("unsupported class definition table format: " + cf); 668 } 669 in.seekSet(cp); 670 return gct; 671 } 672 readSingleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)673 private void readSingleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 674 String tableTag = "GSUB"; 675 in.seekSet(subtableOffset); 676 // skip over format (already known) 677 in.skip(2); 678 // read coverage offset 679 int co = in.readTTFUShort(); 680 // read delta glyph 681 int dg = in.readTTFShort(); 682 // dump info if debugging 683 if (log.isDebugEnabled()) { 684 log.debug(tableTag + " single substitution subtable format: " + subtableFormat + " (delta)"); 685 log.debug(tableTag + " single substitution coverage table offset: " + co); 686 log.debug(tableTag + " single substitution delta: " + dg); 687 } 688 // read coverage table 689 seMapping = readCoverageTable(tableTag + " single substitution coverage", subtableOffset + co); 690 seEntries.add(dg); 691 } 692 readSingleSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)693 private void readSingleSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 694 String tableTag = "GSUB"; 695 in.seekSet(subtableOffset); 696 // skip over format (already known) 697 in.skip(2); 698 // read coverage offset 699 int co = in.readTTFUShort(); 700 // read glyph count 701 int ng = in.readTTFUShort(); 702 // dump info if debugging 703 if (log.isDebugEnabled()) { 704 log.debug(tableTag + " single substitution subtable format: " + subtableFormat + " (mapped)"); 705 log.debug(tableTag + " single substitution coverage table offset: " + co); 706 log.debug(tableTag + " single substitution glyph count: " + ng); 707 } 708 // read coverage table 709 seMapping = readCoverageTable(tableTag + " single substitution coverage", subtableOffset + co); 710 // read glyph substitutions 711 for (int i = 0, n = ng; i < n; i++) { 712 int gs = in.readTTFUShort(); 713 if (log.isDebugEnabled()) { 714 log.debug(tableTag + " single substitution glyph[" + i + "]: " + gs); 715 } 716 seEntries.add(gs); 717 } 718 } 719 readSingleSubTable(int lookupType, int lookupFlags, long subtableOffset)720 private int readSingleSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { 721 in.seekSet(subtableOffset); 722 // read substitution subtable format 723 int sf = in.readTTFUShort(); 724 if (sf == 1) { 725 readSingleSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); 726 } else if (sf == 2) { 727 readSingleSubTableFormat2(lookupType, lookupFlags, subtableOffset, sf); 728 } else { 729 throw new AdvancedTypographicTableFormatException("unsupported single substitution subtable format: " + sf); 730 } 731 return sf; 732 } 733 readMultipleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)734 private void readMultipleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 735 String tableTag = "GSUB"; 736 in.seekSet(subtableOffset); 737 // skip over format (already known) 738 in.skip(2); 739 // read coverage offset 740 int co = in.readTTFUShort(); 741 // read sequence count 742 int ns = in.readTTFUShort(); 743 // dump info if debugging 744 if (log.isDebugEnabled()) { 745 log.debug(tableTag + " multiple substitution subtable format: " + subtableFormat + " (mapped)"); 746 log.debug(tableTag + " multiple substitution coverage table offset: " + co); 747 log.debug(tableTag + " multiple substitution sequence count: " + ns); 748 } 749 // read coverage table 750 seMapping = readCoverageTable(tableTag + " multiple substitution coverage", subtableOffset + co); 751 // read sequence table offsets 752 int[] soa = new int[ns]; 753 for (int i = 0, n = ns; i < n; i++) { 754 soa[i] = in.readTTFUShort(); 755 } 756 // read sequence tables 757 int[][] gsa = new int [ ns ] []; 758 for (int i = 0, n = ns; i < n; i++) { 759 int so = soa[i]; 760 int[] ga; 761 if (so > 0) { 762 in.seekSet(subtableOffset + so); 763 // read glyph count 764 int ng = in.readTTFUShort(); 765 ga = new int[ng]; 766 for (int j = 0; j < ng; j++) { 767 ga[j] = in.readTTFUShort(); 768 } 769 } else { 770 ga = null; 771 } 772 if (log.isDebugEnabled()) { 773 log.debug(tableTag + " multiple substitution sequence[" + i + "]: " + toString(ga)); 774 } 775 gsa [ i ] = ga; 776 } 777 seEntries.add(gsa); 778 } 779 readMultipleSubTable(int lookupType, int lookupFlags, long subtableOffset)780 private int readMultipleSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { 781 in.seekSet(subtableOffset); 782 // read substitution subtable format 783 int sf = in.readTTFUShort(); 784 if (sf == 1) { 785 readMultipleSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); 786 } else { 787 throw new AdvancedTypographicTableFormatException("unsupported multiple substitution subtable format: " + sf); 788 } 789 return sf; 790 } 791 readAlternateSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)792 private void readAlternateSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 793 String tableTag = "GSUB"; 794 in.seekSet(subtableOffset); 795 // skip over format (already known) 796 in.skip(2); 797 // read coverage offset 798 int co = in.readTTFUShort(); 799 // read alternate set count 800 int ns = in.readTTFUShort(); 801 // dump info if debugging 802 if (log.isDebugEnabled()) { 803 log.debug(tableTag + " alternate substitution subtable format: " + subtableFormat + " (mapped)"); 804 log.debug(tableTag + " alternate substitution coverage table offset: " + co); 805 log.debug(tableTag + " alternate substitution alternate set count: " + ns); 806 } 807 // read coverage table 808 seMapping = readCoverageTable(tableTag + " alternate substitution coverage", subtableOffset + co); 809 // read alternate set table offsets 810 int[] soa = new int[ns]; 811 for (int i = 0, n = ns; i < n; i++) { 812 soa[i] = in.readTTFUShort(); 813 } 814 // read alternate set tables 815 for (int i = 0, n = ns; i < n; i++) { 816 int so = soa[i]; 817 in.seekSet(subtableOffset + so); 818 // read glyph count 819 int ng = in.readTTFUShort(); 820 int[] ga = new int[ng]; 821 for (int j = 0; j < ng; j++) { 822 int gs = in.readTTFUShort(); 823 ga[j] = gs; 824 } 825 if (log.isDebugEnabled()) { 826 log.debug(tableTag + " alternate substitution alternate set[" + i + "]: " + toString(ga)); 827 } 828 seEntries.add(ga); 829 } 830 } 831 readAlternateSubTable(int lookupType, int lookupFlags, long subtableOffset)832 private int readAlternateSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { 833 in.seekSet(subtableOffset); 834 // read substitution subtable format 835 int sf = in.readTTFUShort(); 836 if (sf == 1) { 837 readAlternateSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); 838 } else { 839 throw new AdvancedTypographicTableFormatException("unsupported alternate substitution subtable format: " + sf); 840 } 841 return sf; 842 } 843 readLigatureSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)844 private void readLigatureSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 845 String tableTag = "GSUB"; 846 in.seekSet(subtableOffset); 847 // skip over format (already known) 848 in.skip(2); 849 // read coverage offset 850 int co = in.readTTFUShort(); 851 // read ligature set count 852 int ns = in.readTTFUShort(); 853 // dump info if debugging 854 if (log.isDebugEnabled()) { 855 log.debug(tableTag + " ligature substitution subtable format: " + subtableFormat + " (mapped)"); 856 log.debug(tableTag + " ligature substitution coverage table offset: " + co); 857 log.debug(tableTag + " ligature substitution ligature set count: " + ns); 858 } 859 // read coverage table 860 seMapping = readCoverageTable(tableTag + " ligature substitution coverage", subtableOffset + co); 861 // read ligature set table offsets 862 int[] soa = new int[ns]; 863 for (int i = 0, n = ns; i < n; i++) { 864 soa[i] = in.readTTFUShort(); 865 } 866 // read ligature set tables 867 for (int i = 0, n = ns; i < n; i++) { 868 int so = soa[i]; 869 in.seekSet(subtableOffset + so); 870 // read ligature table count 871 int nl = in.readTTFUShort(); 872 int[] loa = new int[nl]; 873 for (int j = 0; j < nl; j++) { 874 loa[j] = in.readTTFUShort(); 875 } 876 List ligs = new java.util.ArrayList(); 877 for (int j = 0; j < nl; j++) { 878 int lo = loa[j]; 879 in.seekSet(subtableOffset + so + lo); 880 // read ligature glyph id 881 int lg = in.readTTFUShort(); 882 // read ligature (input) component count 883 int nc = in.readTTFUShort(); 884 int[] ca = new int [ nc - 1 ]; 885 // read ligature (input) component glyph ids 886 for (int k = 0; k < nc - 1; k++) { 887 ca[k] = in.readTTFUShort(); 888 } 889 if (log.isDebugEnabled()) { 890 log.debug(tableTag + " ligature substitution ligature set[" + i + "]: ligature(" + lg + "), components: " + toString(ca)); 891 } 892 ligs.add(new GlyphSubstitutionTable.Ligature(lg, ca)); 893 } 894 seEntries.add(new GlyphSubstitutionTable.LigatureSet(ligs)); 895 } 896 } 897 readLigatureSubTable(int lookupType, int lookupFlags, long subtableOffset)898 private int readLigatureSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { 899 in.seekSet(subtableOffset); 900 // read substitution subtable format 901 int sf = in.readTTFUShort(); 902 if (sf == 1) { 903 readLigatureSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); 904 } else { 905 throw new AdvancedTypographicTableFormatException("unsupported ligature substitution subtable format: " + sf); 906 } 907 return sf; 908 } 909 readRuleLookups(int numLookups, String header)910 private GlyphTable.RuleLookup[] readRuleLookups(int numLookups, String header) throws IOException { 911 GlyphTable.RuleLookup[] la = new GlyphTable.RuleLookup [ numLookups ]; 912 for (int i = 0, n = numLookups; i < n; i++) { 913 int sequenceIndex = in.readTTFUShort(); 914 int lookupIndex = in.readTTFUShort(); 915 la [ i ] = new GlyphTable.RuleLookup(sequenceIndex, lookupIndex); 916 // dump info if debugging and header is non-null 917 if (log.isDebugEnabled() && (header != null)) { 918 log.debug(header + "lookup[" + i + "]: " + la[i]); 919 } 920 } 921 return la; 922 } 923 readContextualSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)924 private void readContextualSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 925 String tableTag = "GSUB"; 926 in.seekSet(subtableOffset); 927 // skip over format (already known) 928 in.skip(2); 929 // read coverage offset 930 int co = in.readTTFUShort(); 931 // read rule set count 932 int nrs = in.readTTFUShort(); 933 // read rule set offsets 934 int[] rsoa = new int [ nrs ]; 935 for (int i = 0; i < nrs; i++) { 936 rsoa [ i ] = in.readTTFUShort(); 937 } 938 // dump info if debugging 939 if (log.isDebugEnabled()) { 940 log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyphs)"); 941 log.debug(tableTag + " contextual substitution coverage table offset: " + co); 942 log.debug(tableTag + " contextual substitution rule set count: " + nrs); 943 for (int i = 0; i < nrs; i++) { 944 log.debug(tableTag + " contextual substitution rule set offset[" + i + "]: " + rsoa[i]); 945 } 946 } 947 // read coverage table 948 GlyphCoverageTable ct; 949 if (co > 0) { 950 ct = readCoverageTable(tableTag + " contextual substitution coverage", subtableOffset + co); 951 } else { 952 ct = null; 953 } 954 // read rule sets 955 GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ]; 956 String header = null; 957 for (int i = 0; i < nrs; i++) { 958 GlyphTable.RuleSet rs; 959 int rso = rsoa [ i ]; 960 if (rso > 0) { 961 // seek to rule set [ i ] 962 in.seekSet(subtableOffset + rso); 963 // read rule count 964 int nr = in.readTTFUShort(); 965 // read rule offsets 966 int[] roa = new int [ nr ]; 967 GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; 968 for (int j = 0; j < nr; j++) { 969 roa [ j ] = in.readTTFUShort(); 970 } 971 // read glyph sequence rules 972 for (int j = 0; j < nr; j++) { 973 GlyphTable.GlyphSequenceRule r; 974 int ro = roa [ j ]; 975 if (ro > 0) { 976 // seek to rule [ j ] 977 in.seekSet(subtableOffset + rso + ro); 978 // read glyph count 979 int ng = in.readTTFUShort(); 980 // read rule lookup count 981 int nl = in.readTTFUShort(); 982 // read glyphs 983 int[] glyphs = new int [ ng - 1 ]; 984 for (int k = 0, nk = glyphs.length; k < nk; k++) { 985 glyphs [ k ] = in.readTTFUShort(); 986 } 987 // read rule lookups 988 if (log.isDebugEnabled()) { 989 header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: "; 990 } 991 GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); 992 r = new GlyphTable.GlyphSequenceRule(lookups, ng, glyphs); 993 } else { 994 r = null; 995 } 996 ra [ j ] = r; 997 } 998 rs = new GlyphTable.HomogeneousRuleSet(ra); 999 } else { 1000 rs = null; 1001 } 1002 rsa [ i ] = rs; 1003 } 1004 // store results 1005 seMapping = ct; 1006 seEntries.add(rsa); 1007 } 1008 readContextualSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)1009 private void readContextualSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 1010 String tableTag = "GSUB"; 1011 in.seekSet(subtableOffset); 1012 // skip over format (already known) 1013 in.skip(2); 1014 // read coverage offset 1015 int co = in.readTTFUShort(); 1016 // read class def table offset 1017 int cdo = in.readTTFUShort(); 1018 // read class rule set count 1019 int ngc = in.readTTFUShort(); 1020 // read class rule set offsets 1021 int[] csoa = new int [ ngc ]; 1022 for (int i = 0; i < ngc; i++) { 1023 csoa [ i ] = in.readTTFUShort(); 1024 } 1025 // dump info if debugging 1026 if (log.isDebugEnabled()) { 1027 log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyph classes)"); 1028 log.debug(tableTag + " contextual substitution coverage table offset: " + co); 1029 log.debug(tableTag + " contextual substitution class set count: " + ngc); 1030 for (int i = 0; i < ngc; i++) { 1031 log.debug(tableTag + " contextual substitution class set offset[" + i + "]: " + csoa[i]); 1032 } 1033 } 1034 // read coverage table 1035 GlyphCoverageTable ct; 1036 if (co > 0) { 1037 ct = readCoverageTable(tableTag + " contextual substitution coverage", subtableOffset + co); 1038 } else { 1039 ct = null; 1040 } 1041 // read class definition table 1042 GlyphClassTable cdt; 1043 if (cdo > 0) { 1044 cdt = readClassDefTable(tableTag + " contextual substitution class definition", subtableOffset + cdo); 1045 } else { 1046 cdt = null; 1047 } 1048 // read rule sets 1049 GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ]; 1050 String header = null; 1051 for (int i = 0; i < ngc; i++) { 1052 int cso = csoa [ i ]; 1053 GlyphTable.RuleSet rs; 1054 if (cso > 0) { 1055 // seek to rule set [ i ] 1056 in.seekSet(subtableOffset + cso); 1057 // read rule count 1058 int nr = in.readTTFUShort(); 1059 // read rule offsets 1060 int[] roa = new int [ nr ]; 1061 GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; 1062 for (int j = 0; j < nr; j++) { 1063 roa [ j ] = in.readTTFUShort(); 1064 } 1065 // read glyph sequence rules 1066 for (int j = 0; j < nr; j++) { 1067 int ro = roa [ j ]; 1068 GlyphTable.ClassSequenceRule r; 1069 if (ro > 0) { 1070 // seek to rule [ j ] 1071 in.seekSet(subtableOffset + cso + ro); 1072 // read glyph count 1073 int ng = in.readTTFUShort(); 1074 // read rule lookup count 1075 int nl = in.readTTFUShort(); 1076 // read classes 1077 int[] classes = new int [ ng - 1 ]; 1078 for (int k = 0, nk = classes.length; k < nk; k++) { 1079 classes [ k ] = in.readTTFUShort(); 1080 } 1081 // read rule lookups 1082 if (log.isDebugEnabled()) { 1083 header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: "; 1084 } 1085 GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); 1086 r = new GlyphTable.ClassSequenceRule(lookups, ng, classes); 1087 } else { 1088 assert ro > 0 : "unexpected null subclass rule offset"; 1089 r = null; 1090 } 1091 ra [ j ] = r; 1092 } 1093 rs = new GlyphTable.HomogeneousRuleSet(ra); 1094 } else { 1095 rs = null; 1096 } 1097 rsa [ i ] = rs; 1098 } 1099 // store results 1100 seMapping = ct; 1101 seEntries.add(cdt); 1102 seEntries.add(ngc); 1103 seEntries.add(rsa); 1104 } 1105 readContextualSubTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)1106 private void readContextualSubTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 1107 String tableTag = "GSUB"; 1108 in.seekSet(subtableOffset); 1109 // skip over format (already known) 1110 in.skip(2); 1111 // read glyph (input sequence length) count 1112 int ng = in.readTTFUShort(); 1113 // read substitution lookup count 1114 int nl = in.readTTFUShort(); 1115 // read glyph coverage offsets, one per glyph input sequence length count 1116 int[] gcoa = new int [ ng ]; 1117 for (int i = 0; i < ng; i++) { 1118 gcoa [ i ] = in.readTTFUShort(); 1119 } 1120 // dump info if debugging 1121 if (log.isDebugEnabled()) { 1122 log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyph sets)"); 1123 log.debug(tableTag + " contextual substitution glyph input sequence length count: " + ng); 1124 log.debug(tableTag + " contextual substitution lookup count: " + nl); 1125 for (int i = 0; i < ng; i++) { 1126 log.debug(tableTag + " contextual substitution coverage table offset[" + i + "]: " + gcoa[i]); 1127 } 1128 } 1129 // read coverage tables 1130 GlyphCoverageTable[] gca = new GlyphCoverageTable [ ng ]; 1131 for (int i = 0; i < ng; i++) { 1132 int gco = gcoa [ i ]; 1133 GlyphCoverageTable gct; 1134 if (gco > 0) { 1135 gct = readCoverageTable(tableTag + " contextual substitution coverage[" + i + "]", subtableOffset + gco); 1136 } else { 1137 gct = null; 1138 } 1139 gca [ i ] = gct; 1140 } 1141 // read rule lookups 1142 String header = null; 1143 if (log.isDebugEnabled()) { 1144 header = tableTag + " contextual substitution lookups: "; 1145 } 1146 GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); 1147 // construct rule, rule set, and rule set array 1148 GlyphTable.Rule r = new GlyphTable.CoverageSequenceRule(lookups, ng, gca); 1149 GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet(new GlyphTable.Rule[] {r}); 1150 GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs}; 1151 // store results 1152 assert (gca != null) && (gca.length > 0); 1153 seMapping = gca[0]; 1154 seEntries.add(rsa); 1155 } 1156 readContextualSubTable(int lookupType, int lookupFlags, long subtableOffset)1157 private int readContextualSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { 1158 in.seekSet(subtableOffset); 1159 // read substitution subtable format 1160 int sf = in.readTTFUShort(); 1161 if (sf == 1) { 1162 readContextualSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); 1163 } else if (sf == 2) { 1164 readContextualSubTableFormat2(lookupType, lookupFlags, subtableOffset, sf); 1165 } else if (sf == 3) { 1166 readContextualSubTableFormat3(lookupType, lookupFlags, subtableOffset, sf); 1167 } else { 1168 throw new AdvancedTypographicTableFormatException("unsupported contextual substitution subtable format: " + sf); 1169 } 1170 return sf; 1171 } 1172 readChainedContextualSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)1173 private void readChainedContextualSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 1174 String tableTag = "GSUB"; 1175 in.seekSet(subtableOffset); 1176 // skip over format (already known) 1177 in.skip(2); 1178 // read coverage offset 1179 int co = in.readTTFUShort(); 1180 // read rule set count 1181 int nrs = in.readTTFUShort(); 1182 // read rule set offsets 1183 int[] rsoa = new int [ nrs ]; 1184 for (int i = 0; i < nrs; i++) { 1185 rsoa [ i ] = in.readTTFUShort(); 1186 } 1187 // dump info if debugging 1188 if (log.isDebugEnabled()) { 1189 log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyphs)"); 1190 log.debug(tableTag + " chained contextual substitution coverage table offset: " + co); 1191 log.debug(tableTag + " chained contextual substitution rule set count: " + nrs); 1192 for (int i = 0; i < nrs; i++) { 1193 log.debug(tableTag + " chained contextual substitution rule set offset[" + i + "]: " + rsoa[i]); 1194 } 1195 } 1196 // read coverage table 1197 GlyphCoverageTable ct; 1198 if (co > 0) { 1199 ct = readCoverageTable(tableTag + " chained contextual substitution coverage", subtableOffset + co); 1200 } else { 1201 ct = null; 1202 } 1203 // read rule sets 1204 GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ]; 1205 String header = null; 1206 for (int i = 0; i < nrs; i++) { 1207 GlyphTable.RuleSet rs; 1208 int rso = rsoa [ i ]; 1209 if (rso > 0) { 1210 // seek to rule set [ i ] 1211 in.seekSet(subtableOffset + rso); 1212 // read rule count 1213 int nr = in.readTTFUShort(); 1214 // read rule offsets 1215 int[] roa = new int [ nr ]; 1216 GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; 1217 for (int j = 0; j < nr; j++) { 1218 roa [ j ] = in.readTTFUShort(); 1219 } 1220 // read glyph sequence rules 1221 for (int j = 0; j < nr; j++) { 1222 GlyphTable.ChainedGlyphSequenceRule r; 1223 int ro = roa [ j ]; 1224 if (ro > 0) { 1225 // seek to rule [ j ] 1226 in.seekSet(subtableOffset + rso + ro); 1227 // read backtrack glyph count 1228 int nbg = in.readTTFUShort(); 1229 // read backtrack glyphs 1230 int[] backtrackGlyphs = new int [ nbg ]; 1231 for (int k = 0, nk = backtrackGlyphs.length; k < nk; k++) { 1232 backtrackGlyphs [ k ] = in.readTTFUShort(); 1233 } 1234 // read input glyph count 1235 int nig = in.readTTFUShort(); 1236 // read glyphs 1237 int[] glyphs = new int [ nig - 1 ]; 1238 for (int k = 0, nk = glyphs.length; k < nk; k++) { 1239 glyphs [ k ] = in.readTTFUShort(); 1240 } 1241 // read lookahead glyph count 1242 int nlg = in.readTTFUShort(); 1243 // read lookahead glyphs 1244 int[] lookaheadGlyphs = new int [ nlg ]; 1245 for (int k = 0, nk = lookaheadGlyphs.length; k < nk; k++) { 1246 lookaheadGlyphs [ k ] = in.readTTFUShort(); 1247 } 1248 // read rule lookup count 1249 int nl = in.readTTFUShort(); 1250 // read rule lookups 1251 if (log.isDebugEnabled()) { 1252 header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: "; 1253 } 1254 GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); 1255 r = new GlyphTable.ChainedGlyphSequenceRule(lookups, nig, glyphs, backtrackGlyphs, lookaheadGlyphs); 1256 } else { 1257 r = null; 1258 } 1259 ra [ j ] = r; 1260 } 1261 rs = new GlyphTable.HomogeneousRuleSet(ra); 1262 } else { 1263 rs = null; 1264 } 1265 rsa [ i ] = rs; 1266 } 1267 // store results 1268 seMapping = ct; 1269 seEntries.add(rsa); 1270 } 1271 readChainedContextualSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)1272 private void readChainedContextualSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 1273 String tableTag = "GSUB"; 1274 in.seekSet(subtableOffset); 1275 // skip over format (already known) 1276 in.skip(2); 1277 // read coverage offset 1278 int co = in.readTTFUShort(); 1279 // read backtrack class def table offset 1280 int bcdo = in.readTTFUShort(); 1281 // read input class def table offset 1282 int icdo = in.readTTFUShort(); 1283 // read lookahead class def table offset 1284 int lcdo = in.readTTFUShort(); 1285 // read class set count 1286 int ngc = in.readTTFUShort(); 1287 // read class set offsets 1288 int[] csoa = new int [ ngc ]; 1289 for (int i = 0; i < ngc; i++) { 1290 csoa [ i ] = in.readTTFUShort(); 1291 } 1292 // dump info if debugging 1293 if (log.isDebugEnabled()) { 1294 log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyph classes)"); 1295 log.debug(tableTag + " chained contextual substitution coverage table offset: " + co); 1296 log.debug(tableTag + " chained contextual substitution class set count: " + ngc); 1297 for (int i = 0; i < ngc; i++) { 1298 log.debug(tableTag + " chained contextual substitution class set offset[" + i + "]: " + csoa[i]); 1299 } 1300 } 1301 // read coverage table 1302 GlyphCoverageTable ct; 1303 if (co > 0) { 1304 ct = readCoverageTable(tableTag + " chained contextual substitution coverage", subtableOffset + co); 1305 } else { 1306 ct = null; 1307 } 1308 // read backtrack class definition table 1309 GlyphClassTable bcdt; 1310 if (bcdo > 0) { 1311 bcdt = readClassDefTable(tableTag + " contextual substitution backtrack class definition", subtableOffset + bcdo); 1312 } else { 1313 bcdt = null; 1314 } 1315 // read input class definition table 1316 GlyphClassTable icdt; 1317 if (icdo > 0) { 1318 icdt = readClassDefTable(tableTag + " contextual substitution input class definition", subtableOffset + icdo); 1319 } else { 1320 icdt = null; 1321 } 1322 // read lookahead class definition table 1323 GlyphClassTable lcdt; 1324 if (lcdo > 0) { 1325 lcdt = readClassDefTable(tableTag + " contextual substitution lookahead class definition", subtableOffset + lcdo); 1326 } else { 1327 lcdt = null; 1328 } 1329 // read rule sets 1330 GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ]; 1331 String header = null; 1332 for (int i = 0; i < ngc; i++) { 1333 int cso = csoa [ i ]; 1334 GlyphTable.RuleSet rs; 1335 if (cso > 0) { 1336 // seek to rule set [ i ] 1337 in.seekSet(subtableOffset + cso); 1338 // read rule count 1339 int nr = in.readTTFUShort(); 1340 // read rule offsets 1341 int[] roa = new int [ nr ]; 1342 GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; 1343 for (int j = 0; j < nr; j++) { 1344 roa [ j ] = in.readTTFUShort(); 1345 } 1346 // read glyph sequence rules 1347 for (int j = 0; j < nr; j++) { 1348 int ro = roa [ j ]; 1349 GlyphTable.ChainedClassSequenceRule r; 1350 if (ro > 0) { 1351 // seek to rule [ j ] 1352 in.seekSet(subtableOffset + cso + ro); 1353 // read backtrack glyph class count 1354 int nbc = in.readTTFUShort(); 1355 // read backtrack glyph classes 1356 int[] backtrackClasses = new int [ nbc ]; 1357 for (int k = 0, nk = backtrackClasses.length; k < nk; k++) { 1358 backtrackClasses [ k ] = in.readTTFUShort(); 1359 } 1360 // read input glyph class count 1361 int nic = in.readTTFUShort(); 1362 // read input glyph classes 1363 int[] classes = new int [ nic - 1 ]; 1364 for (int k = 0, nk = classes.length; k < nk; k++) { 1365 classes [ k ] = in.readTTFUShort(); 1366 } 1367 // read lookahead glyph class count 1368 int nlc = in.readTTFUShort(); 1369 // read lookahead glyph classes 1370 int[] lookaheadClasses = new int [ nlc ]; 1371 for (int k = 0, nk = lookaheadClasses.length; k < nk; k++) { 1372 lookaheadClasses [ k ] = in.readTTFUShort(); 1373 } 1374 // read rule lookup count 1375 int nl = in.readTTFUShort(); 1376 // read rule lookups 1377 if (log.isDebugEnabled()) { 1378 header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: "; 1379 } 1380 GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); 1381 r = new GlyphTable.ChainedClassSequenceRule(lookups, nic, classes, backtrackClasses, lookaheadClasses); 1382 } else { 1383 r = null; 1384 } 1385 ra [ j ] = r; 1386 } 1387 rs = new GlyphTable.HomogeneousRuleSet(ra); 1388 } else { 1389 rs = null; 1390 } 1391 rsa [ i ] = rs; 1392 } 1393 // store results 1394 seMapping = ct; 1395 seEntries.add(icdt); 1396 seEntries.add(bcdt); 1397 seEntries.add(lcdt); 1398 seEntries.add(ngc); 1399 seEntries.add(rsa); 1400 } 1401 readChainedContextualSubTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)1402 private void readChainedContextualSubTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 1403 String tableTag = "GSUB"; 1404 in.seekSet(subtableOffset); 1405 // skip over format (already known) 1406 in.skip(2); 1407 // read backtrack glyph count 1408 int nbg = in.readTTFUShort(); 1409 // read backtrack glyph coverage offsets 1410 int[] bgcoa = new int [ nbg ]; 1411 for (int i = 0; i < nbg; i++) { 1412 bgcoa [ i ] = in.readTTFUShort(); 1413 } 1414 // read input glyph count 1415 int nig = in.readTTFUShort(); 1416 // read input glyph coverage offsets 1417 int[] igcoa = new int [ nig ]; 1418 for (int i = 0; i < nig; i++) { 1419 igcoa [ i ] = in.readTTFUShort(); 1420 } 1421 // read lookahead glyph count 1422 int nlg = in.readTTFUShort(); 1423 // read lookahead glyph coverage offsets 1424 int[] lgcoa = new int [ nlg ]; 1425 for (int i = 0; i < nlg; i++) { 1426 lgcoa [ i ] = in.readTTFUShort(); 1427 } 1428 // read substitution lookup count 1429 int nl = in.readTTFUShort(); 1430 // dump info if debugging 1431 if (log.isDebugEnabled()) { 1432 log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyph sets)"); 1433 log.debug(tableTag + " chained contextual substitution backtrack glyph count: " + nbg); 1434 for (int i = 0; i < nbg; i++) { 1435 log.debug(tableTag + " chained contextual substitution backtrack coverage table offset[" + i + "]: " + bgcoa[i]); 1436 } 1437 log.debug(tableTag + " chained contextual substitution input glyph count: " + nig); 1438 for (int i = 0; i < nig; i++) { 1439 log.debug(tableTag + " chained contextual substitution input coverage table offset[" + i + "]: " + igcoa[i]); 1440 } 1441 log.debug(tableTag + " chained contextual substitution lookahead glyph count: " + nlg); 1442 for (int i = 0; i < nlg; i++) { 1443 log.debug(tableTag + " chained contextual substitution lookahead coverage table offset[" + i + "]: " + lgcoa[i]); 1444 } 1445 log.debug(tableTag + " chained contextual substitution lookup count: " + nl); 1446 } 1447 // read backtrack coverage tables 1448 GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg]; 1449 for (int i = 0; i < nbg; i++) { 1450 int bgco = bgcoa [ i ]; 1451 GlyphCoverageTable bgct; 1452 if (bgco > 0) { 1453 bgct = readCoverageTable(tableTag + " chained contextual substitution backtrack coverage[" + i + "]", subtableOffset + bgco); 1454 } else { 1455 bgct = null; 1456 } 1457 bgca[i] = bgct; 1458 } 1459 // read input coverage tables 1460 GlyphCoverageTable[] igca = new GlyphCoverageTable[nig]; 1461 for (int i = 0; i < nig; i++) { 1462 int igco = igcoa [ i ]; 1463 GlyphCoverageTable igct; 1464 if (igco > 0) { 1465 igct = readCoverageTable(tableTag + " chained contextual substitution input coverage[" + i + "]", subtableOffset + igco); 1466 } else { 1467 igct = null; 1468 } 1469 igca[i] = igct; 1470 } 1471 // read lookahead coverage tables 1472 GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg]; 1473 for (int i = 0; i < nlg; i++) { 1474 int lgco = lgcoa [ i ]; 1475 GlyphCoverageTable lgct; 1476 if (lgco > 0) { 1477 lgct = readCoverageTable(tableTag + " chained contextual substitution lookahead coverage[" + i + "]", subtableOffset + lgco); 1478 } else { 1479 lgct = null; 1480 } 1481 lgca[i] = lgct; 1482 } 1483 // read rule lookups 1484 String header = null; 1485 if (log.isDebugEnabled()) { 1486 header = tableTag + " chained contextual substitution lookups: "; 1487 } 1488 GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); 1489 // construct rule, rule set, and rule set array 1490 GlyphTable.Rule r = new GlyphTable.ChainedCoverageSequenceRule(lookups, nig, igca, bgca, lgca); 1491 GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet(new GlyphTable.Rule[] {r}); 1492 GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs}; 1493 // store results 1494 assert (igca != null) && (igca.length > 0); 1495 seMapping = igca[0]; 1496 seEntries.add(rsa); 1497 } 1498 readChainedContextualSubTable(int lookupType, int lookupFlags, long subtableOffset)1499 private int readChainedContextualSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { 1500 in.seekSet(subtableOffset); 1501 // read substitution subtable format 1502 int sf = in.readTTFUShort(); 1503 if (sf == 1) { 1504 readChainedContextualSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); 1505 } else if (sf == 2) { 1506 readChainedContextualSubTableFormat2(lookupType, lookupFlags, subtableOffset, sf); 1507 } else if (sf == 3) { 1508 readChainedContextualSubTableFormat3(lookupType, lookupFlags, subtableOffset, sf); 1509 } else { 1510 throw new AdvancedTypographicTableFormatException("unsupported chained contextual substitution subtable format: " + sf); 1511 } 1512 return sf; 1513 } 1514 readExtensionSubTableFormat1(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset, int subtableFormat)1515 private void readExtensionSubTableFormat1(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset, int subtableFormat) throws IOException { 1516 String tableTag = "GSUB"; 1517 in.seekSet(subtableOffset); 1518 // skip over format (already known) 1519 in.skip(2); 1520 // read extension lookup type 1521 int lt = in.readTTFUShort(); 1522 // read extension offset 1523 long eo = in.readTTFULong(); 1524 // dump info if debugging 1525 if (log.isDebugEnabled()) { 1526 log.debug(tableTag + " extension substitution subtable format: " + subtableFormat); 1527 log.debug(tableTag + " extension substitution lookup type: " + lt); 1528 log.debug(tableTag + " extension substitution lookup table offset: " + eo); 1529 } 1530 // read referenced subtable from extended offset 1531 readGSUBSubtable(lt, lookupFlags, lookupSequence, subtableSequence, subtableOffset + eo); 1532 } 1533 readExtensionSubTable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset)1534 private int readExtensionSubTable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException { 1535 in.seekSet(subtableOffset); 1536 // read substitution subtable format 1537 int sf = in.readTTFUShort(); 1538 if (sf == 1) { 1539 readExtensionSubTableFormat1(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset, sf); 1540 } else { 1541 throw new AdvancedTypographicTableFormatException("unsupported extension substitution subtable format: " + sf); 1542 } 1543 return sf; 1544 } 1545 readReverseChainedSingleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)1546 private void readReverseChainedSingleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 1547 String tableTag = "GSUB"; 1548 in.seekSet(subtableOffset); 1549 // skip over format (already known) 1550 in.skip(2); 1551 // read coverage offset 1552 int co = in.readTTFUShort(); 1553 // read backtrack glyph count 1554 int nbg = in.readTTFUShort(); 1555 // read backtrack glyph coverage offsets 1556 int[] bgcoa = new int [ nbg ]; 1557 for (int i = 0; i < nbg; i++) { 1558 bgcoa [ i ] = in.readTTFUShort(); 1559 } 1560 // read lookahead glyph count 1561 int nlg = in.readTTFUShort(); 1562 // read backtrack glyph coverage offsets 1563 int[] lgcoa = new int [ nlg ]; 1564 for (int i = 0; i < nlg; i++) { 1565 lgcoa [ i ] = in.readTTFUShort(); 1566 } 1567 // read substitution (output) glyph count 1568 int ng = in.readTTFUShort(); 1569 // read substitution (output) glyphs 1570 int[] glyphs = new int [ ng ]; 1571 for (int i = 0, n = ng; i < n; i++) { 1572 glyphs [ i ] = in.readTTFUShort(); 1573 } 1574 // dump info if debugging 1575 if (log.isDebugEnabled()) { 1576 log.debug(tableTag + " reverse chained contextual substitution format: " + subtableFormat); 1577 log.debug(tableTag + " reverse chained contextual substitution coverage table offset: " + co); 1578 log.debug(tableTag + " reverse chained contextual substitution backtrack glyph count: " + nbg); 1579 for (int i = 0; i < nbg; i++) { 1580 log.debug(tableTag + " reverse chained contextual substitution backtrack coverage table offset[" + i + "]: " + bgcoa[i]); 1581 } 1582 log.debug(tableTag + " reverse chained contextual substitution lookahead glyph count: " + nlg); 1583 for (int i = 0; i < nlg; i++) { 1584 log.debug(tableTag + " reverse chained contextual substitution lookahead coverage table offset[" + i + "]: " + lgcoa[i]); 1585 } 1586 log.debug(tableTag + " reverse chained contextual substitution glyphs: " + toString(glyphs)); 1587 } 1588 // read coverage table 1589 GlyphCoverageTable ct = readCoverageTable(tableTag + " reverse chained contextual substitution coverage", subtableOffset + co); 1590 // read backtrack coverage tables 1591 GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg]; 1592 for (int i = 0; i < nbg; i++) { 1593 int bgco = bgcoa[i]; 1594 GlyphCoverageTable bgct; 1595 if (bgco > 0) { 1596 bgct = readCoverageTable(tableTag + " reverse chained contextual substitution backtrack coverage[" + i + "]", subtableOffset + bgco); 1597 } else { 1598 bgct = null; 1599 } 1600 bgca[i] = bgct; 1601 } 1602 // read lookahead coverage tables 1603 GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg]; 1604 for (int i = 0; i < nlg; i++) { 1605 int lgco = lgcoa[i]; 1606 GlyphCoverageTable lgct; 1607 if (lgco > 0) { 1608 lgct = readCoverageTable(tableTag + " reverse chained contextual substitution lookahead coverage[" + i + "]", subtableOffset + lgco); 1609 } else { 1610 lgct = null; 1611 } 1612 lgca[i] = lgct; 1613 } 1614 // store results 1615 seMapping = ct; 1616 seEntries.add(bgca); 1617 seEntries.add(lgca); 1618 seEntries.add(glyphs); 1619 } 1620 readReverseChainedSingleSubTable(int lookupType, int lookupFlags, long subtableOffset)1621 private int readReverseChainedSingleSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { 1622 in.seekSet(subtableOffset); 1623 // read substitution subtable format 1624 int sf = in.readTTFUShort(); 1625 if (sf == 1) { 1626 readReverseChainedSingleSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); 1627 } else { 1628 throw new AdvancedTypographicTableFormatException("unsupported reverse chained single substitution subtable format: " + sf); 1629 } 1630 return sf; 1631 } 1632 readGSUBSubtable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset)1633 private void readGSUBSubtable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException { 1634 initATSubState(); 1635 int subtableFormat = -1; 1636 switch (lookupType) { 1637 case GSUBLookupType.SINGLE: 1638 subtableFormat = readSingleSubTable(lookupType, lookupFlags, subtableOffset); 1639 break; 1640 case GSUBLookupType.MULTIPLE: 1641 subtableFormat = readMultipleSubTable(lookupType, lookupFlags, subtableOffset); 1642 break; 1643 case GSUBLookupType.ALTERNATE: 1644 subtableFormat = readAlternateSubTable(lookupType, lookupFlags, subtableOffset); 1645 break; 1646 case GSUBLookupType.LIGATURE: 1647 subtableFormat = readLigatureSubTable(lookupType, lookupFlags, subtableOffset); 1648 break; 1649 case GSUBLookupType.CONTEXTUAL: 1650 subtableFormat = readContextualSubTable(lookupType, lookupFlags, subtableOffset); 1651 break; 1652 case GSUBLookupType.CHAINED_CONTEXTUAL: 1653 subtableFormat = readChainedContextualSubTable(lookupType, lookupFlags, subtableOffset); 1654 break; 1655 case GSUBLookupType.REVERSE_CHAINED_SINGLE: 1656 subtableFormat = readReverseChainedSingleSubTable(lookupType, lookupFlags, subtableOffset); 1657 break; 1658 case GSUBLookupType.EXTENSION: 1659 subtableFormat = readExtensionSubTable(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset); 1660 break; 1661 default: 1662 break; 1663 } 1664 extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableFormat); 1665 resetATSubState(); 1666 } 1667 readPosDeviceTable(long subtableOffset, long deviceTableOffset)1668 private GlyphPositioningTable.DeviceTable readPosDeviceTable(long subtableOffset, long deviceTableOffset) throws IOException { 1669 long cp = in.getCurrentPos(); 1670 in.seekSet(subtableOffset + deviceTableOffset); 1671 // read start size 1672 int ss = in.readTTFUShort(); 1673 // read end size 1674 int es = in.readTTFUShort(); 1675 // read delta format 1676 int df = in.readTTFUShort(); 1677 int s1; 1678 int m1; 1679 int dm; 1680 int dd; 1681 int s2; 1682 if (df == 1) { 1683 s1 = 14; 1684 m1 = 0x3; 1685 dm = 1; 1686 dd = 4; 1687 s2 = 2; 1688 } else if (df == 2) { 1689 s1 = 12; 1690 m1 = 0xF; 1691 dm = 7; 1692 dd = 16; 1693 s2 = 4; 1694 } else if (df == 3) { 1695 s1 = 8; 1696 m1 = 0xFF; 1697 dm = 127; 1698 dd = 256; 1699 s2 = 8; 1700 } else { 1701 log.debug("unsupported device table delta format: " + df + ", ignoring device table"); 1702 return null; 1703 } 1704 // read deltas 1705 int n = (es - ss) + 1; 1706 if (n < 0) { 1707 log.debug("invalid device table delta count: " + n + ", ignoring device table"); 1708 return null; 1709 } 1710 int[] da = new int [ n ]; 1711 for (int i = 0; (i < n) && (s2 > 0);) { 1712 int p = in.readTTFUShort(); 1713 for (int j = 0, k = 16 / s2; j < k; j++) { 1714 int d = (p >> s1) & m1; 1715 if (d > dm) { 1716 d -= dd; 1717 } 1718 if (i < n) { 1719 da [ i++ ] = d; 1720 } else { 1721 break; 1722 } 1723 p <<= s2; 1724 } 1725 } 1726 in.seekSet(cp); 1727 return new GlyphPositioningTable.DeviceTable(ss, es, da); 1728 } 1729 readPosValue(long subtableOffset, int valueFormat)1730 private GlyphPositioningTable.Value readPosValue(long subtableOffset, int valueFormat) throws IOException { 1731 // XPlacement 1732 int xp; 1733 if ((valueFormat & GlyphPositioningTable.Value.X_PLACEMENT) != 0) { 1734 xp = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); 1735 } else { 1736 xp = 0; 1737 } 1738 // YPlacement 1739 int yp; 1740 if ((valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT) != 0) { 1741 yp = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); 1742 } else { 1743 yp = 0; 1744 } 1745 // XAdvance 1746 int xa; 1747 if ((valueFormat & GlyphPositioningTable.Value.X_ADVANCE) != 0) { 1748 xa = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); 1749 } else { 1750 xa = 0; 1751 } 1752 // YAdvance 1753 int ya; 1754 if ((valueFormat & GlyphPositioningTable.Value.Y_ADVANCE) != 0) { 1755 ya = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); 1756 } else { 1757 ya = 0; 1758 } 1759 // XPlaDevice 1760 GlyphPositioningTable.DeviceTable xpd; 1761 if ((valueFormat & GlyphPositioningTable.Value.X_PLACEMENT_DEVICE) != 0) { 1762 int xpdo = in.readTTFUShort(); 1763 xpd = readPosDeviceTable(subtableOffset, xpdo); 1764 } else { 1765 xpd = null; 1766 } 1767 // YPlaDevice 1768 GlyphPositioningTable.DeviceTable ypd; 1769 if ((valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT_DEVICE) != 0) { 1770 int ypdo = in.readTTFUShort(); 1771 ypd = readPosDeviceTable(subtableOffset, ypdo); 1772 } else { 1773 ypd = null; 1774 } 1775 // XAdvDevice 1776 GlyphPositioningTable.DeviceTable xad; 1777 if ((valueFormat & GlyphPositioningTable.Value.X_ADVANCE_DEVICE) != 0) { 1778 int xado = in.readTTFUShort(); 1779 xad = readPosDeviceTable(subtableOffset, xado); 1780 } else { 1781 xad = null; 1782 } 1783 // YAdvDevice 1784 GlyphPositioningTable.DeviceTable yad; 1785 if ((valueFormat & GlyphPositioningTable.Value.Y_ADVANCE_DEVICE) != 0) { 1786 int yado = in.readTTFUShort(); 1787 yad = readPosDeviceTable(subtableOffset, yado); 1788 } else { 1789 yad = null; 1790 } 1791 return new GlyphPositioningTable.Value(xp, yp, xa, ya, xpd, ypd, xad, yad); 1792 } 1793 readSinglePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)1794 private void readSinglePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 1795 String tableTag = "GPOS"; 1796 in.seekSet(subtableOffset); 1797 // skip over format (already known) 1798 in.skip(2); 1799 // read coverage offset 1800 int co = in.readTTFUShort(); 1801 // read value format 1802 int vf = in.readTTFUShort(); 1803 // read value 1804 GlyphPositioningTable.Value v = readPosValue(subtableOffset, vf); 1805 // dump info if debugging 1806 if (log.isDebugEnabled()) { 1807 log.debug(tableTag + " single positioning subtable format: " + subtableFormat + " (delta)"); 1808 log.debug(tableTag + " single positioning coverage table offset: " + co); 1809 log.debug(tableTag + " single positioning value: " + v); 1810 } 1811 // read coverage table 1812 GlyphCoverageTable ct = readCoverageTable(tableTag + " single positioning coverage", subtableOffset + co); 1813 // store results 1814 seMapping = ct; 1815 seEntries.add(v); 1816 } 1817 readSinglePosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)1818 private void readSinglePosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 1819 String tableTag = "GPOS"; 1820 in.seekSet(subtableOffset); 1821 // skip over format (already known) 1822 in.skip(2); 1823 // read coverage offset 1824 int co = in.readTTFUShort(); 1825 // read value format 1826 int vf = in.readTTFUShort(); 1827 // read value count 1828 int nv = in.readTTFUShort(); 1829 // dump info if debugging 1830 if (log.isDebugEnabled()) { 1831 log.debug(tableTag + " single positioning subtable format: " + subtableFormat + " (mapped)"); 1832 log.debug(tableTag + " single positioning coverage table offset: " + co); 1833 log.debug(tableTag + " single positioning value count: " + nv); 1834 } 1835 // read coverage table 1836 GlyphCoverageTable ct = readCoverageTable(tableTag + " single positioning coverage", subtableOffset + co); 1837 // read positioning values 1838 GlyphPositioningTable.Value[] pva = new GlyphPositioningTable.Value[nv]; 1839 for (int i = 0, n = nv; i < n; i++) { 1840 GlyphPositioningTable.Value pv = readPosValue(subtableOffset, vf); 1841 if (log.isDebugEnabled()) { 1842 log.debug(tableTag + " single positioning value[" + i + "]: " + pv); 1843 } 1844 pva[i] = pv; 1845 } 1846 // store results 1847 seMapping = ct; 1848 seEntries.add(pva); 1849 } 1850 readSinglePosTable(int lookupType, int lookupFlags, long subtableOffset)1851 private int readSinglePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { 1852 in.seekSet(subtableOffset); 1853 // read positionining subtable format 1854 int sf = in.readTTFUShort(); 1855 if (sf == 1) { 1856 readSinglePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); 1857 } else if (sf == 2) { 1858 readSinglePosTableFormat2(lookupType, lookupFlags, subtableOffset, sf); 1859 } else { 1860 throw new AdvancedTypographicTableFormatException("unsupported single positioning subtable format: " + sf); 1861 } 1862 return sf; 1863 } 1864 readPosPairValues(long subtableOffset, boolean hasGlyph, int vf1, int vf2)1865 private GlyphPositioningTable.PairValues readPosPairValues(long subtableOffset, boolean hasGlyph, int vf1, int vf2) throws IOException { 1866 // read glyph (if present) 1867 int glyph; 1868 if (hasGlyph) { 1869 glyph = in.readTTFUShort(); 1870 } else { 1871 glyph = 0; 1872 } 1873 // read first value (if present) 1874 GlyphPositioningTable.Value v1; 1875 if (vf1 != 0) { 1876 v1 = readPosValue(subtableOffset, vf1); 1877 } else { 1878 v1 = null; 1879 } 1880 // read second value (if present) 1881 GlyphPositioningTable.Value v2; 1882 if (vf2 != 0) { 1883 v2 = readPosValue(subtableOffset, vf2); 1884 } else { 1885 v2 = null; 1886 } 1887 return new GlyphPositioningTable.PairValues(glyph, v1, v2); 1888 } 1889 readPosPairSetTable(long subtableOffset, int pairSetTableOffset, int vf1, int vf2)1890 private GlyphPositioningTable.PairValues[] readPosPairSetTable(long subtableOffset, int pairSetTableOffset, int vf1, int vf2) throws IOException { 1891 String tableTag = "GPOS"; 1892 long cp = in.getCurrentPos(); 1893 in.seekSet(subtableOffset + pairSetTableOffset); 1894 // read pair values count 1895 int npv = in.readTTFUShort(); 1896 // dump info if debugging 1897 if (log.isDebugEnabled()) { 1898 log.debug(tableTag + " pair set table offset: " + pairSetTableOffset); 1899 log.debug(tableTag + " pair set table values count: " + npv); 1900 } 1901 // read pair values 1902 GlyphPositioningTable.PairValues[] pva = new GlyphPositioningTable.PairValues [ npv ]; 1903 for (int i = 0, n = npv; i < n; i++) { 1904 GlyphPositioningTable.PairValues pv = readPosPairValues(subtableOffset, true, vf1, vf2); 1905 pva [ i ] = pv; 1906 if (log.isDebugEnabled()) { 1907 log.debug(tableTag + " pair set table value[" + i + "]: " + pv); 1908 } 1909 } 1910 in.seekSet(cp); 1911 return pva; 1912 } 1913 readPairPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)1914 private void readPairPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 1915 String tableTag = "GPOS"; 1916 in.seekSet(subtableOffset); 1917 // skip over format (already known) 1918 in.skip(2); 1919 // read coverage offset 1920 int co = in.readTTFUShort(); 1921 // read value format for first glyph 1922 int vf1 = in.readTTFUShort(); 1923 // read value format for second glyph 1924 int vf2 = in.readTTFUShort(); 1925 // read number (count) of pair sets 1926 int nps = in.readTTFUShort(); 1927 // dump info if debugging 1928 if (log.isDebugEnabled()) { 1929 log.debug(tableTag + " pair positioning subtable format: " + subtableFormat + " (glyphs)"); 1930 log.debug(tableTag + " pair positioning coverage table offset: " + co); 1931 log.debug(tableTag + " pair positioning value format #1: " + vf1); 1932 log.debug(tableTag + " pair positioning value format #2: " + vf2); 1933 } 1934 // read coverage table 1935 GlyphCoverageTable ct = readCoverageTable(tableTag + " pair positioning coverage", subtableOffset + co); 1936 // read pair value matrix 1937 GlyphPositioningTable.PairValues[][] pvm = new GlyphPositioningTable.PairValues [ nps ][]; 1938 for (int i = 0, n = nps; i < n; i++) { 1939 // read pair set offset 1940 int pso = in.readTTFUShort(); 1941 // read pair set table at offset 1942 pvm [ i ] = readPosPairSetTable(subtableOffset, pso, vf1, vf2); 1943 } 1944 // store results 1945 seMapping = ct; 1946 seEntries.add(pvm); 1947 } 1948 readPairPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)1949 private void readPairPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 1950 String tableTag = "GPOS"; 1951 in.seekSet(subtableOffset); 1952 // skip over format (already known) 1953 in.skip(2); 1954 // read coverage offset 1955 int co = in.readTTFUShort(); 1956 // read value format for first glyph 1957 int vf1 = in.readTTFUShort(); 1958 // read value format for second glyph 1959 int vf2 = in.readTTFUShort(); 1960 // read class def 1 offset 1961 int cd1o = in.readTTFUShort(); 1962 // read class def 2 offset 1963 int cd2o = in.readTTFUShort(); 1964 // read number (count) of classes in class def 1 table 1965 int nc1 = in.readTTFUShort(); 1966 // read number (count) of classes in class def 2 table 1967 int nc2 = in.readTTFUShort(); 1968 // dump info if debugging 1969 if (log.isDebugEnabled()) { 1970 log.debug(tableTag + " pair positioning subtable format: " + subtableFormat + " (glyph classes)"); 1971 log.debug(tableTag + " pair positioning coverage table offset: " + co); 1972 log.debug(tableTag + " pair positioning value format #1: " + vf1); 1973 log.debug(tableTag + " pair positioning value format #2: " + vf2); 1974 log.debug(tableTag + " pair positioning class def table #1 offset: " + cd1o); 1975 log.debug(tableTag + " pair positioning class def table #2 offset: " + cd2o); 1976 log.debug(tableTag + " pair positioning class #1 count: " + nc1); 1977 log.debug(tableTag + " pair positioning class #2 count: " + nc2); 1978 } 1979 // read coverage table 1980 GlyphCoverageTable ct = readCoverageTable(tableTag + " pair positioning coverage", subtableOffset + co); 1981 // read class definition table #1 1982 GlyphClassTable cdt1 = readClassDefTable(tableTag + " pair positioning class definition #1", subtableOffset + cd1o); 1983 // read class definition table #2 1984 GlyphClassTable cdt2 = readClassDefTable(tableTag + " pair positioning class definition #2", subtableOffset + cd2o); 1985 // read pair value matrix 1986 GlyphPositioningTable.PairValues[][] pvm = new GlyphPositioningTable.PairValues [ nc1 ] [ nc2 ]; 1987 for (int i = 0; i < nc1; i++) { 1988 for (int j = 0; j < nc2; j++) { 1989 GlyphPositioningTable.PairValues pv = readPosPairValues(subtableOffset, false, vf1, vf2); 1990 pvm [ i ] [ j ] = pv; 1991 if (log.isDebugEnabled()) { 1992 log.debug(tableTag + " pair set table value[" + i + "][" + j + "]: " + pv); 1993 } 1994 } 1995 } 1996 // store results 1997 seMapping = ct; 1998 seEntries.add(cdt1); 1999 seEntries.add(cdt2); 2000 seEntries.add(nc1); 2001 seEntries.add(nc2); 2002 seEntries.add(pvm); 2003 } 2004 readPairPosTable(int lookupType, int lookupFlags, long subtableOffset)2005 private int readPairPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { 2006 in.seekSet(subtableOffset); 2007 // read positioning subtable format 2008 int sf = in.readTTFUShort(); 2009 if (sf == 1) { 2010 readPairPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); 2011 } else if (sf == 2) { 2012 readPairPosTableFormat2(lookupType, lookupFlags, subtableOffset, sf); 2013 } else { 2014 throw new AdvancedTypographicTableFormatException("unsupported pair positioning subtable format: " + sf); 2015 } 2016 return sf; 2017 } 2018 readPosAnchor(long anchorTableOffset)2019 private GlyphPositioningTable.Anchor readPosAnchor(long anchorTableOffset) throws IOException { 2020 GlyphPositioningTable.Anchor a; 2021 long cp = in.getCurrentPos(); 2022 in.seekSet(anchorTableOffset); 2023 // read anchor table format 2024 int af = in.readTTFUShort(); 2025 if (af == 1) { 2026 // read x coordinate 2027 int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); 2028 // read y coordinate 2029 int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); 2030 a = new GlyphPositioningTable.Anchor(x, y); 2031 } else if (af == 2) { 2032 // read x coordinate 2033 int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); 2034 // read y coordinate 2035 int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); 2036 // read anchor point index 2037 int ap = in.readTTFUShort(); 2038 a = new GlyphPositioningTable.Anchor(x, y, ap); 2039 } else if (af == 3) { 2040 // read x coordinate 2041 int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); 2042 // read y coordinate 2043 int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); 2044 // read x device table offset 2045 int xdo = in.readTTFUShort(); 2046 // read y device table offset 2047 int ydo = in.readTTFUShort(); 2048 // read x device table (if present) 2049 GlyphPositioningTable.DeviceTable xd; 2050 if (xdo != 0) { 2051 xd = readPosDeviceTable(cp, xdo); 2052 } else { 2053 xd = null; 2054 } 2055 // read y device table (if present) 2056 GlyphPositioningTable.DeviceTable yd; 2057 if (ydo != 0) { 2058 yd = readPosDeviceTable(cp, ydo); 2059 } else { 2060 yd = null; 2061 } 2062 a = new GlyphPositioningTable.Anchor(x, y, xd, yd); 2063 } else { 2064 throw new AdvancedTypographicTableFormatException("unsupported positioning anchor format: " + af); 2065 } 2066 in.seekSet(cp); 2067 return a; 2068 } 2069 readCursivePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)2070 private void readCursivePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 2071 String tableTag = "GPOS"; 2072 in.seekSet(subtableOffset); 2073 // skip over format (already known) 2074 in.skip(2); 2075 // read coverage offset 2076 int co = in.readTTFUShort(); 2077 // read entry/exit count 2078 int ec = in.readTTFUShort(); 2079 // dump info if debugging 2080 if (log.isDebugEnabled()) { 2081 log.debug(tableTag + " cursive positioning subtable format: " + subtableFormat); 2082 log.debug(tableTag + " cursive positioning coverage table offset: " + co); 2083 log.debug(tableTag + " cursive positioning entry/exit count: " + ec); 2084 } 2085 // read coverage table 2086 GlyphCoverageTable ct = readCoverageTable(tableTag + " cursive positioning coverage", subtableOffset + co); 2087 // read entry/exit records 2088 GlyphPositioningTable.Anchor[] aa = new GlyphPositioningTable.Anchor [ ec * 2 ]; 2089 for (int i = 0, n = ec; i < n; i++) { 2090 // read entry anchor offset 2091 int eno = in.readTTFUShort(); 2092 // read exit anchor offset 2093 int exo = in.readTTFUShort(); 2094 // read entry anchor 2095 GlyphPositioningTable.Anchor ena; 2096 if (eno > 0) { 2097 ena = readPosAnchor(subtableOffset + eno); 2098 } else { 2099 ena = null; 2100 } 2101 // read exit anchor 2102 GlyphPositioningTable.Anchor exa; 2103 if (exo > 0) { 2104 exa = readPosAnchor(subtableOffset + exo); 2105 } else { 2106 exa = null; 2107 } 2108 aa [ (i * 2) + 0 ] = ena; 2109 aa [ (i * 2) + 1 ] = exa; 2110 if (log.isDebugEnabled()) { 2111 if (ena != null) { 2112 log.debug(tableTag + " cursive entry anchor [" + i + "]: " + ena); 2113 } 2114 if (exa != null) { 2115 log.debug(tableTag + " cursive exit anchor [" + i + "]: " + exa); 2116 } 2117 } 2118 } 2119 // store results 2120 seMapping = ct; 2121 seEntries.add(aa); 2122 } 2123 readCursivePosTable(int lookupType, int lookupFlags, long subtableOffset)2124 private int readCursivePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { 2125 in.seekSet(subtableOffset); 2126 // read positioning subtable format 2127 int sf = in.readTTFUShort(); 2128 if (sf == 1) { 2129 readCursivePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); 2130 } else { 2131 throw new AdvancedTypographicTableFormatException("unsupported cursive positioning subtable format: " + sf); 2132 } 2133 return sf; 2134 } 2135 readMarkToBasePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)2136 private void readMarkToBasePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 2137 String tableTag = "GPOS"; 2138 in.seekSet(subtableOffset); 2139 // skip over format (already known) 2140 in.skip(2); 2141 // read mark coverage offset 2142 int mco = in.readTTFUShort(); 2143 // read base coverage offset 2144 int bco = in.readTTFUShort(); 2145 // read mark class count 2146 int nmc = in.readTTFUShort(); 2147 // read mark array offset 2148 int mao = in.readTTFUShort(); 2149 // read base array offset 2150 int bao = in.readTTFUShort(); 2151 // dump info if debugging 2152 if (log.isDebugEnabled()) { 2153 log.debug(tableTag + " mark-to-base positioning subtable format: " + subtableFormat); 2154 log.debug(tableTag + " mark-to-base positioning mark coverage table offset: " + mco); 2155 log.debug(tableTag + " mark-to-base positioning base coverage table offset: " + bco); 2156 log.debug(tableTag + " mark-to-base positioning mark class count: " + nmc); 2157 log.debug(tableTag + " mark-to-base positioning mark array offset: " + mao); 2158 log.debug(tableTag + " mark-to-base positioning base array offset: " + bao); 2159 } 2160 // read mark coverage table 2161 GlyphCoverageTable mct = readCoverageTable(tableTag + " mark-to-base positioning mark coverage", subtableOffset + mco); 2162 // read base coverage table 2163 GlyphCoverageTable bct = readCoverageTable(tableTag + " mark-to-base positioning base coverage", subtableOffset + bco); 2164 // read mark anchor array 2165 // seek to mark array 2166 in.seekSet(subtableOffset + mao); 2167 // read mark count 2168 int nm = in.readTTFUShort(); 2169 if (log.isDebugEnabled()) { 2170 log.debug(tableTag + " mark-to-base positioning mark count: " + nm); 2171 } 2172 // read mark anchor array, where i:{0...markCount} 2173 GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm ]; 2174 for (int i = 0; i < nm; i++) { 2175 // read mark class 2176 int mc = in.readTTFUShort(); 2177 // read mark anchor offset 2178 int ao = in.readTTFUShort(); 2179 GlyphPositioningTable.Anchor a; 2180 if (ao > 0) { 2181 a = readPosAnchor(subtableOffset + mao + ao); 2182 } else { 2183 a = null; 2184 } 2185 GlyphPositioningTable.MarkAnchor ma; 2186 if (a != null) { 2187 ma = new GlyphPositioningTable.MarkAnchor(mc, a); 2188 } else { 2189 ma = null; 2190 } 2191 maa [ i ] = ma; 2192 if (log.isDebugEnabled()) { 2193 log.debug(tableTag + " mark-to-base positioning mark anchor[" + i + "]: " + ma); 2194 } 2195 2196 } 2197 // read base anchor matrix 2198 // seek to base array 2199 in.seekSet(subtableOffset + bao); 2200 // read base count 2201 int nb = in.readTTFUShort(); 2202 if (log.isDebugEnabled()) { 2203 log.debug(tableTag + " mark-to-base positioning base count: " + nb); 2204 } 2205 // read anchor matrix, where i:{0...baseCount - 1}, j:{0...markClassCount - 1} 2206 GlyphPositioningTable.Anchor[][] bam = new GlyphPositioningTable.Anchor [ nb ] [ nmc ]; 2207 for (int i = 0; i < nb; i++) { 2208 for (int j = 0; j < nmc; j++) { 2209 // read base anchor offset 2210 int ao = in.readTTFUShort(); 2211 GlyphPositioningTable.Anchor a; 2212 if (ao > 0) { 2213 a = readPosAnchor(subtableOffset + bao + ao); 2214 } else { 2215 a = null; 2216 } 2217 bam [ i ] [ j ] = a; 2218 if (log.isDebugEnabled()) { 2219 log.debug(tableTag + " mark-to-base positioning base anchor[" + i + "][" + j + "]: " + a); 2220 } 2221 } 2222 } 2223 // store results 2224 seMapping = mct; 2225 seEntries.add(bct); 2226 seEntries.add(nmc); 2227 seEntries.add(maa); 2228 seEntries.add(bam); 2229 } 2230 readMarkToBasePosTable(int lookupType, int lookupFlags, long subtableOffset)2231 private int readMarkToBasePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { 2232 in.seekSet(subtableOffset); 2233 // read positioning subtable format 2234 int sf = in.readTTFUShort(); 2235 if (sf == 1) { 2236 readMarkToBasePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); 2237 } else { 2238 throw new AdvancedTypographicTableFormatException("unsupported mark-to-base positioning subtable format: " + sf); 2239 } 2240 return sf; 2241 } 2242 readMarkToLigaturePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)2243 private void readMarkToLigaturePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 2244 String tableTag = "GPOS"; 2245 in.seekSet(subtableOffset); 2246 // skip over format (already known) 2247 in.skip(2); 2248 // read mark coverage offset 2249 int mco = in.readTTFUShort(); 2250 // read ligature coverage offset 2251 int lco = in.readTTFUShort(); 2252 // read mark class count 2253 int nmc = in.readTTFUShort(); 2254 // read mark array offset 2255 int mao = in.readTTFUShort(); 2256 // read ligature array offset 2257 int lao = in.readTTFUShort(); 2258 // dump info if debugging 2259 if (log.isDebugEnabled()) { 2260 log.debug(tableTag + " mark-to-ligature positioning subtable format: " + subtableFormat); 2261 log.debug(tableTag + " mark-to-ligature positioning mark coverage table offset: " + mco); 2262 log.debug(tableTag + " mark-to-ligature positioning ligature coverage table offset: " + lco); 2263 log.debug(tableTag + " mark-to-ligature positioning mark class count: " + nmc); 2264 log.debug(tableTag + " mark-to-ligature positioning mark array offset: " + mao); 2265 log.debug(tableTag + " mark-to-ligature positioning ligature array offset: " + lao); 2266 } 2267 // read mark coverage table 2268 GlyphCoverageTable mct = readCoverageTable(tableTag + " mark-to-ligature positioning mark coverage", subtableOffset + mco); 2269 // read ligature coverage table 2270 GlyphCoverageTable lct = readCoverageTable(tableTag + " mark-to-ligature positioning ligature coverage", subtableOffset + lco); 2271 // read mark anchor array 2272 // seek to mark array 2273 in.seekSet(subtableOffset + mao); 2274 // read mark count 2275 int nm = in.readTTFUShort(); 2276 if (log.isDebugEnabled()) { 2277 log.debug(tableTag + " mark-to-ligature positioning mark count: " + nm); 2278 } 2279 // read mark anchor array, where i:{0...markCount} 2280 GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm ]; 2281 for (int i = 0; i < nm; i++) { 2282 // read mark class 2283 int mc = in.readTTFUShort(); 2284 // read mark anchor offset 2285 int ao = in.readTTFUShort(); 2286 GlyphPositioningTable.Anchor a; 2287 if (ao > 0) { 2288 a = readPosAnchor(subtableOffset + mao + ao); 2289 } else { 2290 a = null; 2291 } 2292 GlyphPositioningTable.MarkAnchor ma; 2293 if (a != null) { 2294 ma = new GlyphPositioningTable.MarkAnchor(mc, a); 2295 } else { 2296 ma = null; 2297 } 2298 maa [ i ] = ma; 2299 if (log.isDebugEnabled()) { 2300 log.debug(tableTag + " mark-to-ligature positioning mark anchor[" + i + "]: " + ma); 2301 } 2302 } 2303 // read ligature anchor matrix 2304 // seek to ligature array 2305 in.seekSet(subtableOffset + lao); 2306 // read ligature count 2307 int nl = in.readTTFUShort(); 2308 if (log.isDebugEnabled()) { 2309 log.debug(tableTag + " mark-to-ligature positioning ligature count: " + nl); 2310 } 2311 // read ligature attach table offsets 2312 int[] laoa = new int [ nl ]; 2313 for (int i = 0; i < nl; i++) { 2314 laoa [ i ] = in.readTTFUShort(); 2315 } 2316 // iterate over ligature attach tables, recording maximum component count 2317 int mxc = 0; 2318 for (int i = 0; i < nl; i++) { 2319 int lato = laoa [ i ]; 2320 in.seekSet(subtableOffset + lao + lato); 2321 // read component count 2322 int cc = in.readTTFUShort(); 2323 if (cc > mxc) { 2324 mxc = cc; 2325 } 2326 } 2327 if (log.isDebugEnabled()) { 2328 log.debug(tableTag + " mark-to-ligature positioning maximum component count: " + mxc); 2329 } 2330 // read anchor matrix, where i:{0...ligatureCount - 1}, j:{0...maxComponentCount - 1}, k:{0...markClassCount - 1} 2331 GlyphPositioningTable.Anchor[][][] lam = new GlyphPositioningTable.Anchor [ nl ][][]; 2332 for (int i = 0; i < nl; i++) { 2333 int lato = laoa [ i ]; 2334 // seek to ligature attach table for ligature[i] 2335 in.seekSet(subtableOffset + lao + lato); 2336 // read component count 2337 int cc = in.readTTFUShort(); 2338 GlyphPositioningTable.Anchor[][] lcm = new GlyphPositioningTable.Anchor [ cc ] [ nmc ]; 2339 for (int j = 0; j < cc; j++) { 2340 for (int k = 0; k < nmc; k++) { 2341 // read ligature anchor offset 2342 int ao = in.readTTFUShort(); 2343 GlyphPositioningTable.Anchor a; 2344 if (ao > 0) { 2345 a = readPosAnchor(subtableOffset + lao + lato + ao); 2346 } else { 2347 a = null; 2348 } 2349 lcm [ j ] [ k ] = a; 2350 if (log.isDebugEnabled()) { 2351 log.debug(tableTag + " mark-to-ligature positioning ligature anchor[" + i + "][" + j + "][" + k + "]: " + a); 2352 } 2353 } 2354 } 2355 lam [ i ] = lcm; 2356 } 2357 // store results 2358 seMapping = mct; 2359 seEntries.add(lct); 2360 seEntries.add(nmc); 2361 seEntries.add(mxc); 2362 seEntries.add(maa); 2363 seEntries.add(lam); 2364 } 2365 readMarkToLigaturePosTable(int lookupType, int lookupFlags, long subtableOffset)2366 private int readMarkToLigaturePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { 2367 in.seekSet(subtableOffset); 2368 // read positioning subtable format 2369 int sf = in.readTTFUShort(); 2370 if (sf == 1) { 2371 readMarkToLigaturePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); 2372 } else { 2373 throw new AdvancedTypographicTableFormatException("unsupported mark-to-ligature positioning subtable format: " + sf); 2374 } 2375 return sf; 2376 } 2377 readMarkToMarkPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)2378 private void readMarkToMarkPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 2379 String tableTag = "GPOS"; 2380 in.seekSet(subtableOffset); 2381 // skip over format (already known) 2382 in.skip(2); 2383 // read mark #1 coverage offset 2384 int m1co = in.readTTFUShort(); 2385 // read mark #2 coverage offset 2386 int m2co = in.readTTFUShort(); 2387 // read mark class count 2388 int nmc = in.readTTFUShort(); 2389 // read mark #1 array offset 2390 int m1ao = in.readTTFUShort(); 2391 // read mark #2 array offset 2392 int m2ao = in.readTTFUShort(); 2393 // dump info if debugging 2394 if (log.isDebugEnabled()) { 2395 log.debug(tableTag + " mark-to-mark positioning subtable format: " + subtableFormat); 2396 log.debug(tableTag + " mark-to-mark positioning mark #1 coverage table offset: " + m1co); 2397 log.debug(tableTag + " mark-to-mark positioning mark #2 coverage table offset: " + m2co); 2398 log.debug(tableTag + " mark-to-mark positioning mark class count: " + nmc); 2399 log.debug(tableTag + " mark-to-mark positioning mark #1 array offset: " + m1ao); 2400 log.debug(tableTag + " mark-to-mark positioning mark #2 array offset: " + m2ao); 2401 } 2402 // read mark #1 coverage table 2403 GlyphCoverageTable mct1 = readCoverageTable(tableTag + " mark-to-mark positioning mark #1 coverage", subtableOffset + m1co); 2404 // read mark #2 coverage table 2405 GlyphCoverageTable mct2 = readCoverageTable(tableTag + " mark-to-mark positioning mark #2 coverage", subtableOffset + m2co); 2406 // read mark #1 anchor array 2407 // seek to mark array 2408 in.seekSet(subtableOffset + m1ao); 2409 // read mark count 2410 int nm1 = in.readTTFUShort(); 2411 if (log.isDebugEnabled()) { 2412 log.debug(tableTag + " mark-to-mark positioning mark #1 count: " + nm1); 2413 } 2414 // read mark anchor array, where i:{0...mark1Count} 2415 GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm1 ]; 2416 for (int i = 0; i < nm1; i++) { 2417 // read mark class 2418 int mc = in.readTTFUShort(); 2419 // read mark anchor offset 2420 int ao = in.readTTFUShort(); 2421 GlyphPositioningTable.Anchor a; 2422 if (ao > 0) { 2423 a = readPosAnchor(subtableOffset + m1ao + ao); 2424 } else { 2425 a = null; 2426 } 2427 GlyphPositioningTable.MarkAnchor ma; 2428 if (a != null) { 2429 ma = new GlyphPositioningTable.MarkAnchor(mc, a); 2430 } else { 2431 ma = null; 2432 } 2433 maa [ i ] = ma; 2434 if (log.isDebugEnabled()) { 2435 log.debug(tableTag + " mark-to-mark positioning mark #1 anchor[" + i + "]: " + ma); 2436 } 2437 } 2438 // read mark #2 anchor matrix 2439 // seek to mark #2 array 2440 in.seekSet(subtableOffset + m2ao); 2441 // read mark #2 count 2442 int nm2 = in.readTTFUShort(); 2443 if (log.isDebugEnabled()) { 2444 log.debug(tableTag + " mark-to-mark positioning mark #2 count: " + nm2); 2445 } 2446 // read anchor matrix, where i:{0...mark2Count - 1}, j:{0...markClassCount - 1} 2447 GlyphPositioningTable.Anchor[][] mam = new GlyphPositioningTable.Anchor [ nm2 ] [ nmc ]; 2448 for (int i = 0; i < nm2; i++) { 2449 for (int j = 0; j < nmc; j++) { 2450 // read mark anchor offset 2451 int ao = in.readTTFUShort(); 2452 GlyphPositioningTable.Anchor a; 2453 if (ao > 0) { 2454 a = readPosAnchor(subtableOffset + m2ao + ao); 2455 } else { 2456 a = null; 2457 } 2458 mam [ i ] [ j ] = a; 2459 if (log.isDebugEnabled()) { 2460 log.debug(tableTag + " mark-to-mark positioning mark #2 anchor[" + i + "][" + j + "]: " + a); 2461 } 2462 } 2463 } 2464 // store results 2465 seMapping = mct1; 2466 seEntries.add(mct2); 2467 seEntries.add(nmc); 2468 seEntries.add(maa); 2469 seEntries.add(mam); 2470 } 2471 readMarkToMarkPosTable(int lookupType, int lookupFlags, long subtableOffset)2472 private int readMarkToMarkPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { 2473 in.seekSet(subtableOffset); 2474 // read positioning subtable format 2475 int sf = in.readTTFUShort(); 2476 if (sf == 1) { 2477 readMarkToMarkPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); 2478 } else { 2479 throw new AdvancedTypographicTableFormatException("unsupported mark-to-mark positioning subtable format: " + sf); 2480 } 2481 return sf; 2482 } 2483 readContextualPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)2484 private void readContextualPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 2485 String tableTag = "GPOS"; 2486 in.seekSet(subtableOffset); 2487 // skip over format (already known) 2488 in.skip(2); 2489 // read coverage offset 2490 int co = in.readTTFUShort(); 2491 // read rule set count 2492 int nrs = in.readTTFUShort(); 2493 // read rule set offsets 2494 int[] rsoa = new int [ nrs ]; 2495 for (int i = 0; i < nrs; i++) { 2496 rsoa [ i ] = in.readTTFUShort(); 2497 } 2498 // dump info if debugging 2499 if (log.isDebugEnabled()) { 2500 log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyphs)"); 2501 log.debug(tableTag + " contextual positioning coverage table offset: " + co); 2502 log.debug(tableTag + " contextual positioning rule set count: " + nrs); 2503 for (int i = 0; i < nrs; i++) { 2504 log.debug(tableTag + " contextual positioning rule set offset[" + i + "]: " + rsoa[i]); 2505 } 2506 } 2507 // read coverage table 2508 GlyphCoverageTable ct; 2509 if (co > 0) { 2510 ct = readCoverageTable(tableTag + " contextual positioning coverage", subtableOffset + co); 2511 } else { 2512 ct = null; 2513 } 2514 // read rule sets 2515 GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ]; 2516 String header = null; 2517 for (int i = 0; i < nrs; i++) { 2518 GlyphTable.RuleSet rs; 2519 int rso = rsoa [ i ]; 2520 if (rso > 0) { 2521 // seek to rule set [ i ] 2522 in.seekSet(subtableOffset + rso); 2523 // read rule count 2524 int nr = in.readTTFUShort(); 2525 // read rule offsets 2526 int[] roa = new int [ nr ]; 2527 GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; 2528 for (int j = 0; j < nr; j++) { 2529 roa [ j ] = in.readTTFUShort(); 2530 } 2531 // read glyph sequence rules 2532 for (int j = 0; j < nr; j++) { 2533 GlyphTable.GlyphSequenceRule r; 2534 int ro = roa [ j ]; 2535 if (ro > 0) { 2536 // seek to rule [ j ] 2537 in.seekSet(subtableOffset + rso + ro); 2538 // read glyph count 2539 int ng = in.readTTFUShort(); 2540 // read rule lookup count 2541 int nl = in.readTTFUShort(); 2542 // read glyphs 2543 int[] glyphs = new int [ ng - 1 ]; 2544 for (int k = 0, nk = glyphs.length; k < nk; k++) { 2545 glyphs [ k ] = in.readTTFUShort(); 2546 } 2547 // read rule lookups 2548 if (log.isDebugEnabled()) { 2549 header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: "; 2550 } 2551 GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); 2552 r = new GlyphTable.GlyphSequenceRule(lookups, ng, glyphs); 2553 } else { 2554 r = null; 2555 } 2556 ra [ j ] = r; 2557 } 2558 rs = new GlyphTable.HomogeneousRuleSet(ra); 2559 } else { 2560 rs = null; 2561 } 2562 rsa [ i ] = rs; 2563 } 2564 // store results 2565 seMapping = ct; 2566 seEntries.add(rsa); 2567 } 2568 readContextualPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)2569 private void readContextualPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 2570 String tableTag = "GPOS"; 2571 in.seekSet(subtableOffset); 2572 // skip over format (already known) 2573 in.skip(2); 2574 // read coverage offset 2575 int co = in.readTTFUShort(); 2576 // read class def table offset 2577 int cdo = in.readTTFUShort(); 2578 // read class rule set count 2579 int ngc = in.readTTFUShort(); 2580 // read class rule set offsets 2581 int[] csoa = new int [ ngc ]; 2582 for (int i = 0; i < ngc; i++) { 2583 csoa [ i ] = in.readTTFUShort(); 2584 } 2585 // dump info if debugging 2586 if (log.isDebugEnabled()) { 2587 log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyph classes)"); 2588 log.debug(tableTag + " contextual positioning coverage table offset: " + co); 2589 log.debug(tableTag + " contextual positioning class set count: " + ngc); 2590 for (int i = 0; i < ngc; i++) { 2591 log.debug(tableTag + " contextual positioning class set offset[" + i + "]: " + csoa[i]); 2592 } 2593 } 2594 // read coverage table 2595 GlyphCoverageTable ct; 2596 if (co > 0) { 2597 ct = readCoverageTable(tableTag + " contextual positioning coverage", subtableOffset + co); 2598 } else { 2599 ct = null; 2600 } 2601 // read class definition table 2602 GlyphClassTable cdt; 2603 if (cdo > 0) { 2604 cdt = readClassDefTable(tableTag + " contextual positioning class definition", subtableOffset + cdo); 2605 } else { 2606 cdt = null; 2607 } 2608 // read rule sets 2609 GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ]; 2610 String header = null; 2611 for (int i = 0; i < ngc; i++) { 2612 int cso = csoa [ i ]; 2613 GlyphTable.RuleSet rs; 2614 if (cso > 0) { 2615 // seek to rule set [ i ] 2616 in.seekSet(subtableOffset + cso); 2617 // read rule count 2618 int nr = in.readTTFUShort(); 2619 // read rule offsets 2620 int[] roa = new int [ nr ]; 2621 GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; 2622 for (int j = 0; j < nr; j++) { 2623 roa [ j ] = in.readTTFUShort(); 2624 } 2625 // read glyph sequence rules 2626 for (int j = 0; j < nr; j++) { 2627 int ro = roa [ j ]; 2628 GlyphTable.ClassSequenceRule r; 2629 if (ro > 0) { 2630 // seek to rule [ j ] 2631 in.seekSet(subtableOffset + cso + ro); 2632 // read glyph count 2633 int ng = in.readTTFUShort(); 2634 // read rule lookup count 2635 int nl = in.readTTFUShort(); 2636 // read classes 2637 int[] classes = new int [ ng - 1 ]; 2638 for (int k = 0, nk = classes.length; k < nk; k++) { 2639 classes [ k ] = in.readTTFUShort(); 2640 } 2641 // read rule lookups 2642 if (log.isDebugEnabled()) { 2643 header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: "; 2644 } 2645 GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); 2646 r = new GlyphTable.ClassSequenceRule(lookups, ng, classes); 2647 } else { 2648 r = null; 2649 } 2650 ra [ j ] = r; 2651 } 2652 rs = new GlyphTable.HomogeneousRuleSet(ra); 2653 } else { 2654 rs = null; 2655 } 2656 rsa [ i ] = rs; 2657 } 2658 // store results 2659 seMapping = ct; 2660 seEntries.add(cdt); 2661 seEntries.add(ngc); 2662 seEntries.add(rsa); 2663 } 2664 readContextualPosTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)2665 private void readContextualPosTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 2666 String tableTag = "GPOS"; 2667 in.seekSet(subtableOffset); 2668 // skip over format (already known) 2669 in.skip(2); 2670 // read glyph (input sequence length) count 2671 int ng = in.readTTFUShort(); 2672 // read positioning lookup count 2673 int nl = in.readTTFUShort(); 2674 // read glyph coverage offsets, one per glyph input sequence length count 2675 int[] gcoa = new int [ ng ]; 2676 for (int i = 0; i < ng; i++) { 2677 gcoa [ i ] = in.readTTFUShort(); 2678 } 2679 // dump info if debugging 2680 if (log.isDebugEnabled()) { 2681 log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyph sets)"); 2682 log.debug(tableTag + " contextual positioning glyph input sequence length count: " + ng); 2683 log.debug(tableTag + " contextual positioning lookup count: " + nl); 2684 for (int i = 0; i < ng; i++) { 2685 log.debug(tableTag + " contextual positioning coverage table offset[" + i + "]: " + gcoa[i]); 2686 } 2687 } 2688 // read coverage tables 2689 GlyphCoverageTable[] gca = new GlyphCoverageTable [ ng ]; 2690 for (int i = 0; i < ng; i++) { 2691 int gco = gcoa [ i ]; 2692 GlyphCoverageTable gct; 2693 if (gco > 0) { 2694 gct = readCoverageTable(tableTag + " contextual positioning coverage[" + i + "]", subtableOffset + gcoa[i]); 2695 } else { 2696 gct = null; 2697 } 2698 gca [ i ] = gct; 2699 } 2700 // read rule lookups 2701 String header = null; 2702 if (log.isDebugEnabled()) { 2703 header = tableTag + " contextual positioning lookups: "; 2704 } 2705 GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); 2706 // construct rule, rule set, and rule set array 2707 GlyphTable.Rule r = new GlyphTable.CoverageSequenceRule(lookups, ng, gca); 2708 GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet(new GlyphTable.Rule[] {r}); 2709 GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs}; 2710 // store results 2711 assert (gca != null) && (gca.length > 0); 2712 seMapping = gca[0]; 2713 seEntries.add(rsa); 2714 } 2715 readContextualPosTable(int lookupType, int lookupFlags, long subtableOffset)2716 private int readContextualPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { 2717 in.seekSet(subtableOffset); 2718 // read positioning subtable format 2719 int sf = in.readTTFUShort(); 2720 if (sf == 1) { 2721 readContextualPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); 2722 } else if (sf == 2) { 2723 readContextualPosTableFormat2(lookupType, lookupFlags, subtableOffset, sf); 2724 } else if (sf == 3) { 2725 readContextualPosTableFormat3(lookupType, lookupFlags, subtableOffset, sf); 2726 } else { 2727 throw new AdvancedTypographicTableFormatException("unsupported contextual positioning subtable format: " + sf); 2728 } 2729 return sf; 2730 } 2731 readChainedContextualPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)2732 private void readChainedContextualPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 2733 String tableTag = "GPOS"; 2734 in.seekSet(subtableOffset); 2735 // skip over format (already known) 2736 in.skip(2); 2737 // read coverage offset 2738 int co = in.readTTFUShort(); 2739 // read rule set count 2740 int nrs = in.readTTFUShort(); 2741 // read rule set offsets 2742 int[] rsoa = new int [ nrs ]; 2743 for (int i = 0; i < nrs; i++) { 2744 rsoa [ i ] = in.readTTFUShort(); 2745 } 2746 // dump info if debugging 2747 if (log.isDebugEnabled()) { 2748 log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyphs)"); 2749 log.debug(tableTag + " chained contextual positioning coverage table offset: " + co); 2750 log.debug(tableTag + " chained contextual positioning rule set count: " + nrs); 2751 for (int i = 0; i < nrs; i++) { 2752 log.debug(tableTag + " chained contextual positioning rule set offset[" + i + "]: " + rsoa[i]); 2753 } 2754 } 2755 // read coverage table 2756 GlyphCoverageTable ct; 2757 if (co > 0) { 2758 ct = readCoverageTable(tableTag + " chained contextual positioning coverage", subtableOffset + co); 2759 } else { 2760 ct = null; 2761 } 2762 // read rule sets 2763 GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ]; 2764 String header = null; 2765 for (int i = 0; i < nrs; i++) { 2766 GlyphTable.RuleSet rs; 2767 int rso = rsoa [ i ]; 2768 if (rso > 0) { 2769 // seek to rule set [ i ] 2770 in.seekSet(subtableOffset + rso); 2771 // read rule count 2772 int nr = in.readTTFUShort(); 2773 // read rule offsets 2774 int[] roa = new int [ nr ]; 2775 GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; 2776 for (int j = 0; j < nr; j++) { 2777 roa [ j ] = in.readTTFUShort(); 2778 } 2779 // read glyph sequence rules 2780 for (int j = 0; j < nr; j++) { 2781 GlyphTable.ChainedGlyphSequenceRule r; 2782 int ro = roa [ j ]; 2783 if (ro > 0) { 2784 // seek to rule [ j ] 2785 in.seekSet(subtableOffset + rso + ro); 2786 // read backtrack glyph count 2787 int nbg = in.readTTFUShort(); 2788 // read backtrack glyphs 2789 int[] backtrackGlyphs = new int [ nbg ]; 2790 for (int k = 0, nk = backtrackGlyphs.length; k < nk; k++) { 2791 backtrackGlyphs [ k ] = in.readTTFUShort(); 2792 } 2793 // read input glyph count 2794 int nig = in.readTTFUShort(); 2795 // read glyphs 2796 int[] glyphs = new int [ nig - 1 ]; 2797 for (int k = 0, nk = glyphs.length; k < nk; k++) { 2798 glyphs [ k ] = in.readTTFUShort(); 2799 } 2800 // read lookahead glyph count 2801 int nlg = in.readTTFUShort(); 2802 // read lookahead glyphs 2803 int[] lookaheadGlyphs = new int [ nlg ]; 2804 for (int k = 0, nk = lookaheadGlyphs.length; k < nk; k++) { 2805 lookaheadGlyphs [ k ] = in.readTTFUShort(); 2806 } 2807 // read rule lookup count 2808 int nl = in.readTTFUShort(); 2809 // read rule lookups 2810 if (log.isDebugEnabled()) { 2811 header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: "; 2812 } 2813 GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); 2814 r = new GlyphTable.ChainedGlyphSequenceRule(lookups, nig, glyphs, backtrackGlyphs, lookaheadGlyphs); 2815 } else { 2816 r = null; 2817 } 2818 ra [ j ] = r; 2819 } 2820 rs = new GlyphTable.HomogeneousRuleSet(ra); 2821 } else { 2822 rs = null; 2823 } 2824 rsa [ i ] = rs; 2825 } 2826 // store results 2827 seMapping = ct; 2828 seEntries.add(rsa); 2829 } 2830 readChainedContextualPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)2831 private void readChainedContextualPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 2832 String tableTag = "GPOS"; 2833 in.seekSet(subtableOffset); 2834 // skip over format (already known) 2835 in.skip(2); 2836 // read coverage offset 2837 int co = in.readTTFUShort(); 2838 // read backtrack class def table offset 2839 int bcdo = in.readTTFUShort(); 2840 // read input class def table offset 2841 int icdo = in.readTTFUShort(); 2842 // read lookahead class def table offset 2843 int lcdo = in.readTTFUShort(); 2844 // read class set count 2845 int ngc = in.readTTFUShort(); 2846 // read class set offsets 2847 int[] csoa = new int [ ngc ]; 2848 for (int i = 0; i < ngc; i++) { 2849 csoa [ i ] = in.readTTFUShort(); 2850 } 2851 // dump info if debugging 2852 if (log.isDebugEnabled()) { 2853 log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyph classes)"); 2854 log.debug(tableTag + " chained contextual positioning coverage table offset: " + co); 2855 log.debug(tableTag + " chained contextual positioning class set count: " + ngc); 2856 for (int i = 0; i < ngc; i++) { 2857 log.debug(tableTag + " chained contextual positioning class set offset[" + i + "]: " + csoa[i]); 2858 } 2859 } 2860 // read coverage table 2861 GlyphCoverageTable ct; 2862 if (co > 0) { 2863 ct = readCoverageTable(tableTag + " chained contextual positioning coverage", subtableOffset + co); 2864 } else { 2865 ct = null; 2866 } 2867 // read backtrack class definition table 2868 GlyphClassTable bcdt; 2869 if (bcdo > 0) { 2870 bcdt = readClassDefTable(tableTag + " contextual positioning backtrack class definition", subtableOffset + bcdo); 2871 } else { 2872 bcdt = null; 2873 } 2874 // read input class definition table 2875 GlyphClassTable icdt; 2876 if (icdo > 0) { 2877 icdt = readClassDefTable(tableTag + " contextual positioning input class definition", subtableOffset + icdo); 2878 } else { 2879 icdt = null; 2880 } 2881 // read lookahead class definition table 2882 GlyphClassTable lcdt; 2883 if (lcdo > 0) { 2884 lcdt = readClassDefTable(tableTag + " contextual positioning lookahead class definition", subtableOffset + lcdo); 2885 } else { 2886 lcdt = null; 2887 } 2888 // read rule sets 2889 GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ]; 2890 String header = null; 2891 for (int i = 0; i < ngc; i++) { 2892 int cso = csoa [ i ]; 2893 GlyphTable.RuleSet rs; 2894 if (cso > 0) { 2895 // seek to rule set [ i ] 2896 in.seekSet(subtableOffset + cso); 2897 // read rule count 2898 int nr = in.readTTFUShort(); 2899 // read rule offsets 2900 int[] roa = new int [ nr ]; 2901 GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; 2902 for (int j = 0; j < nr; j++) { 2903 roa [ j ] = in.readTTFUShort(); 2904 } 2905 // read glyph sequence rules 2906 for (int j = 0; j < nr; j++) { 2907 GlyphTable.ChainedClassSequenceRule r; 2908 int ro = roa [ j ]; 2909 if (ro > 0) { 2910 // seek to rule [ j ] 2911 in.seekSet(subtableOffset + cso + ro); 2912 // read backtrack glyph class count 2913 int nbc = in.readTTFUShort(); 2914 // read backtrack glyph classes 2915 int[] backtrackClasses = new int [ nbc ]; 2916 for (int k = 0, nk = backtrackClasses.length; k < nk; k++) { 2917 backtrackClasses [ k ] = in.readTTFUShort(); 2918 } 2919 // read input glyph class count 2920 int nic = in.readTTFUShort(); 2921 // read input glyph classes 2922 int[] classes = new int [ nic - 1 ]; 2923 for (int k = 0, nk = classes.length; k < nk; k++) { 2924 classes [ k ] = in.readTTFUShort(); 2925 } 2926 // read lookahead glyph class count 2927 int nlc = in.readTTFUShort(); 2928 // read lookahead glyph classes 2929 int[] lookaheadClasses = new int [ nlc ]; 2930 for (int k = 0, nk = lookaheadClasses.length; k < nk; k++) { 2931 lookaheadClasses [ k ] = in.readTTFUShort(); 2932 } 2933 // read rule lookup count 2934 int nl = in.readTTFUShort(); 2935 // read rule lookups 2936 if (log.isDebugEnabled()) { 2937 header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: "; 2938 } 2939 GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); 2940 r = new GlyphTable.ChainedClassSequenceRule(lookups, nic, classes, backtrackClasses, lookaheadClasses); 2941 } else { 2942 r = null; 2943 } 2944 ra [ j ] = r; 2945 } 2946 rs = new GlyphTable.HomogeneousRuleSet(ra); 2947 } else { 2948 rs = null; 2949 } 2950 rsa [ i ] = rs; 2951 } 2952 // store results 2953 seMapping = ct; 2954 seEntries.add(icdt); 2955 seEntries.add(bcdt); 2956 seEntries.add(lcdt); 2957 seEntries.add(ngc); 2958 seEntries.add(rsa); 2959 } 2960 readChainedContextualPosTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat)2961 private void readChainedContextualPosTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { 2962 String tableTag = "GPOS"; 2963 in.seekSet(subtableOffset); 2964 // skip over format (already known) 2965 in.skip(2); 2966 // read backtrack glyph count 2967 int nbg = in.readTTFUShort(); 2968 // read backtrack glyph coverage offsets 2969 int[] bgcoa = new int [ nbg ]; 2970 for (int i = 0; i < nbg; i++) { 2971 bgcoa [ i ] = in.readTTFUShort(); 2972 } 2973 // read input glyph count 2974 int nig = in.readTTFUShort(); 2975 // read backtrack glyph coverage offsets 2976 int[] igcoa = new int [ nig ]; 2977 for (int i = 0; i < nig; i++) { 2978 igcoa [ i ] = in.readTTFUShort(); 2979 } 2980 // read lookahead glyph count 2981 int nlg = in.readTTFUShort(); 2982 // read backtrack glyph coverage offsets 2983 int[] lgcoa = new int [ nlg ]; 2984 for (int i = 0; i < nlg; i++) { 2985 lgcoa [ i ] = in.readTTFUShort(); 2986 } 2987 // read positioning lookup count 2988 int nl = in.readTTFUShort(); 2989 // dump info if debugging 2990 if (log.isDebugEnabled()) { 2991 log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyph sets)"); 2992 log.debug(tableTag + " chained contextual positioning backtrack glyph count: " + nbg); 2993 for (int i = 0; i < nbg; i++) { 2994 log.debug(tableTag + " chained contextual positioning backtrack coverage table offset[" + i + "]: " + bgcoa[i]); 2995 } 2996 log.debug(tableTag + " chained contextual positioning input glyph count: " + nig); 2997 for (int i = 0; i < nig; i++) { 2998 log.debug(tableTag + " chained contextual positioning input coverage table offset[" + i + "]: " + igcoa[i]); 2999 } 3000 log.debug(tableTag + " chained contextual positioning lookahead glyph count: " + nlg); 3001 for (int i = 0; i < nlg; i++) { 3002 log.debug(tableTag + " chained contextual positioning lookahead coverage table offset[" + i + "]: " + lgcoa[i]); 3003 } 3004 log.debug(tableTag + " chained contextual positioning lookup count: " + nl); 3005 } 3006 // read backtrack coverage tables 3007 GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg]; 3008 for (int i = 0; i < nbg; i++) { 3009 int bgco = bgcoa [ i ]; 3010 GlyphCoverageTable bgct; 3011 if (bgco > 0) { 3012 bgct = readCoverageTable(tableTag + " chained contextual positioning backtrack coverage[" + i + "]", subtableOffset + bgco); 3013 } else { 3014 bgct = null; 3015 } 3016 bgca[i] = bgct; 3017 } 3018 // read input coverage tables 3019 GlyphCoverageTable[] igca = new GlyphCoverageTable[nig]; 3020 for (int i = 0; i < nig; i++) { 3021 int igco = igcoa [ i ]; 3022 GlyphCoverageTable igct; 3023 if (igco > 0) { 3024 igct = readCoverageTable(tableTag + " chained contextual positioning input coverage[" + i + "]", subtableOffset + igco); 3025 } else { 3026 igct = null; 3027 } 3028 igca[i] = igct; 3029 } 3030 // read lookahead coverage tables 3031 GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg]; 3032 for (int i = 0; i < nlg; i++) { 3033 int lgco = lgcoa [ i ]; 3034 GlyphCoverageTable lgct; 3035 if (lgco > 0) { 3036 lgct = readCoverageTable(tableTag + " chained contextual positioning lookahead coverage[" + i + "]", subtableOffset + lgco); 3037 } else { 3038 lgct = null; 3039 } 3040 lgca[i] = lgct; 3041 } 3042 // read rule lookups 3043 String header = null; 3044 if (log.isDebugEnabled()) { 3045 header = tableTag + " chained contextual positioning lookups: "; 3046 } 3047 GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); 3048 // construct rule, rule set, and rule set array 3049 GlyphTable.Rule r = new GlyphTable.ChainedCoverageSequenceRule(lookups, nig, igca, bgca, lgca); 3050 GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet(new GlyphTable.Rule[] {r}); 3051 GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs}; 3052 // store results 3053 assert (igca != null) && (igca.length > 0); 3054 seMapping = igca[0]; 3055 seEntries.add(rsa); 3056 } 3057 readChainedContextualPosTable(int lookupType, int lookupFlags, long subtableOffset)3058 private int readChainedContextualPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { 3059 in.seekSet(subtableOffset); 3060 // read positioning subtable format 3061 int sf = in.readTTFUShort(); 3062 if (sf == 1) { 3063 readChainedContextualPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); 3064 } else if (sf == 2) { 3065 readChainedContextualPosTableFormat2(lookupType, lookupFlags, subtableOffset, sf); 3066 } else if (sf == 3) { 3067 readChainedContextualPosTableFormat3(lookupType, lookupFlags, subtableOffset, sf); 3068 } else { 3069 throw new AdvancedTypographicTableFormatException("unsupported chained contextual positioning subtable format: " + sf); 3070 } 3071 return sf; 3072 } 3073 readExtensionPosTableFormat1(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset, int subtableFormat)3074 private void readExtensionPosTableFormat1(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset, int subtableFormat) throws IOException { 3075 String tableTag = "GPOS"; 3076 in.seekSet(subtableOffset); 3077 // skip over format (already known) 3078 in.skip(2); 3079 // read extension lookup type 3080 int lt = in.readTTFUShort(); 3081 // read extension offset 3082 long eo = in.readTTFULong(); 3083 // dump info if debugging 3084 if (log.isDebugEnabled()) { 3085 log.debug(tableTag + " extension positioning subtable format: " + subtableFormat); 3086 log.debug(tableTag + " extension positioning lookup type: " + lt); 3087 log.debug(tableTag + " extension positioning lookup table offset: " + eo); 3088 } 3089 // read referenced subtable from extended offset 3090 readGPOSSubtable(lt, lookupFlags, lookupSequence, subtableSequence, subtableOffset + eo); 3091 } 3092 readExtensionPosTable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset)3093 private int readExtensionPosTable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException { 3094 in.seekSet(subtableOffset); 3095 // read positioning subtable format 3096 int sf = in.readTTFUShort(); 3097 if (sf == 1) { 3098 readExtensionPosTableFormat1(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset, sf); 3099 } else { 3100 throw new AdvancedTypographicTableFormatException("unsupported extension positioning subtable format: " + sf); 3101 } 3102 return sf; 3103 } 3104 readGPOSSubtable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset)3105 private void readGPOSSubtable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException { 3106 initATSubState(); 3107 int subtableFormat = -1; 3108 switch (lookupType) { 3109 case GPOSLookupType.SINGLE: 3110 subtableFormat = readSinglePosTable(lookupType, lookupFlags, subtableOffset); 3111 break; 3112 case GPOSLookupType.PAIR: 3113 subtableFormat = readPairPosTable(lookupType, lookupFlags, subtableOffset); 3114 break; 3115 case GPOSLookupType.CURSIVE: 3116 subtableFormat = readCursivePosTable(lookupType, lookupFlags, subtableOffset); 3117 break; 3118 case GPOSLookupType.MARK_TO_BASE: 3119 subtableFormat = readMarkToBasePosTable(lookupType, lookupFlags, subtableOffset); 3120 break; 3121 case GPOSLookupType.MARK_TO_LIGATURE: 3122 subtableFormat = readMarkToLigaturePosTable(lookupType, lookupFlags, subtableOffset); 3123 break; 3124 case GPOSLookupType.MARK_TO_MARK: 3125 subtableFormat = readMarkToMarkPosTable(lookupType, lookupFlags, subtableOffset); 3126 break; 3127 case GPOSLookupType.CONTEXTUAL: 3128 subtableFormat = readContextualPosTable(lookupType, lookupFlags, subtableOffset); 3129 break; 3130 case GPOSLookupType.CHAINED_CONTEXTUAL: 3131 subtableFormat = readChainedContextualPosTable(lookupType, lookupFlags, subtableOffset); 3132 break; 3133 case GPOSLookupType.EXTENSION: 3134 subtableFormat = readExtensionPosTable(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset); 3135 break; 3136 default: 3137 break; 3138 } 3139 extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_POSITIONING, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableFormat); 3140 resetATSubState(); 3141 } 3142 readLookupTable(OFTableName tableTag, int lookupSequence, long lookupTable)3143 private void readLookupTable(OFTableName tableTag, int lookupSequence, long lookupTable) throws IOException { 3144 boolean isGSUB = tableTag.equals(OFTableName.GSUB); 3145 boolean isGPOS = tableTag.equals(OFTableName.GPOS); 3146 in.seekSet(lookupTable); 3147 // read lookup type 3148 int lt = in.readTTFUShort(); 3149 // read lookup flags 3150 int lf = in.readTTFUShort(); 3151 // read sub-table count 3152 int ns = in.readTTFUShort(); 3153 // dump info if debugging 3154 if (log.isDebugEnabled()) { 3155 String lts; 3156 if (isGSUB) { 3157 lts = GSUBLookupType.toString(lt); 3158 } else if (isGPOS) { 3159 lts = GPOSLookupType.toString(lt); 3160 } else { 3161 lts = "?"; 3162 } 3163 log.debug(tableTag + " lookup table type: " + lt + " (" + lts + ")"); 3164 log.debug(tableTag + " lookup table flags: " + lf + " (" + LookupFlag.toString(lf) + ")"); 3165 log.debug(tableTag + " lookup table subtable count: " + ns); 3166 } 3167 // read subtable offsets 3168 int[] soa = new int[ns]; 3169 for (int i = 0; i < ns; i++) { 3170 int so = in.readTTFUShort(); 3171 if (log.isDebugEnabled()) { 3172 log.debug(tableTag + " lookup table subtable offset: " + so); 3173 } 3174 soa[i] = so; 3175 } 3176 // read mark filtering set 3177 if ((lf & LookupFlag.USE_MARK_FILTERING_SET) != 0) { 3178 // read mark filtering set 3179 int fs = in.readTTFUShort(); 3180 // dump info if debugging 3181 if (log.isDebugEnabled()) { 3182 log.debug(tableTag + " lookup table mark filter set: " + fs); 3183 } 3184 } 3185 // read subtables 3186 for (int i = 0; i < ns; i++) { 3187 int so = soa[i]; 3188 if (isGSUB) { 3189 readGSUBSubtable(lt, lf, lookupSequence, i, lookupTable + so); 3190 } else if (isGPOS) { 3191 readGPOSSubtable(lt, lf, lookupSequence, i, lookupTable + so); 3192 } 3193 } 3194 } 3195 readLookupList(OFTableName tableTag, long lookupList)3196 private void readLookupList(OFTableName tableTag, long lookupList) throws IOException { 3197 in.seekSet(lookupList); 3198 // read lookup record count 3199 int nl = in.readTTFUShort(); 3200 if (log.isDebugEnabled()) { 3201 log.debug(tableTag + " lookup list record count: " + nl); 3202 } 3203 if (nl > 0) { 3204 int[] loa = new int[nl]; 3205 // read lookup records 3206 for (int i = 0, n = nl; i < n; i++) { 3207 int lo = in.readTTFUShort(); 3208 if (log.isDebugEnabled()) { 3209 log.debug(tableTag + " lookup table offset: " + lo); 3210 } 3211 loa[i] = lo; 3212 } 3213 // read lookup tables 3214 for (int i = 0, n = nl; i < n; i++) { 3215 if (log.isDebugEnabled()) { 3216 log.debug(tableTag + " lookup index: " + i); 3217 } 3218 readLookupTable(tableTag, i, lookupList + loa [ i ]); 3219 } 3220 } 3221 } 3222 3223 /** 3224 * Read the common layout tables (used by GSUB and GPOS). 3225 * @param tableTag tag of table being read 3226 * @param scriptList offset to script list from beginning of font file 3227 * @param featureList offset to feature list from beginning of font file 3228 * @param lookupList offset to lookup list from beginning of font file 3229 * @throws IOException In case of a I/O problem 3230 */ readCommonLayoutTables(OFTableName tableTag, long scriptList, long featureList, long lookupList)3231 private void readCommonLayoutTables(OFTableName tableTag, long scriptList, long featureList, long lookupList) throws IOException { 3232 if (scriptList > 0) { 3233 readScriptList(tableTag, scriptList); 3234 } 3235 if (featureList > 0) { 3236 readFeatureList(tableTag, featureList); 3237 } 3238 if (lookupList > 0) { 3239 readLookupList(tableTag, lookupList); 3240 } 3241 } 3242 readGDEFClassDefTable(OFTableName tableTag, int lookupSequence, long subtableOffset)3243 private void readGDEFClassDefTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { 3244 initATSubState(); 3245 in.seekSet(subtableOffset); 3246 // subtable is a bare class definition table 3247 GlyphClassTable ct = readClassDefTable(tableTag + " glyph class definition table", subtableOffset); 3248 // store results 3249 seMapping = ct; 3250 // extract subtable 3251 extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.GLYPH_CLASS, 0, lookupSequence, 0, 1); 3252 resetATSubState(); 3253 } 3254 readGDEFAttachmentTable(OFTableName tableTag, int lookupSequence, long subtableOffset)3255 private void readGDEFAttachmentTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { 3256 initATSubState(); 3257 in.seekSet(subtableOffset); 3258 // read coverage offset 3259 int co = in.readTTFUShort(); 3260 // dump info if debugging 3261 if (log.isDebugEnabled()) { 3262 log.debug(tableTag + " attachment point coverage table offset: " + co); 3263 } 3264 // read coverage table 3265 GlyphCoverageTable ct = readCoverageTable(tableTag + " attachment point coverage", subtableOffset + co); 3266 // store results 3267 seMapping = ct; 3268 // extract subtable 3269 extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.ATTACHMENT_POINT, 0, lookupSequence, 0, 1); 3270 resetATSubState(); 3271 } 3272 readGDEFLigatureCaretTable(OFTableName tableTag, int lookupSequence, long subtableOffset)3273 private void readGDEFLigatureCaretTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { 3274 initATSubState(); 3275 in.seekSet(subtableOffset); 3276 // read coverage offset 3277 int co = in.readTTFUShort(); 3278 // read ligature glyph count 3279 int nl = in.readTTFUShort(); 3280 // read ligature glyph table offsets 3281 int[] lgto = new int [ nl ]; 3282 for (int i = 0; i < nl; i++) { 3283 lgto [ i ] = in.readTTFUShort(); 3284 } 3285 3286 // dump info if debugging 3287 if (log.isDebugEnabled()) { 3288 log.debug(tableTag + " ligature caret coverage table offset: " + co); 3289 log.debug(tableTag + " ligature caret ligature glyph count: " + nl); 3290 for (int i = 0; i < nl; i++) { 3291 log.debug(tableTag + " ligature glyph table offset[" + i + "]: " + lgto[i]); 3292 } 3293 } 3294 // read coverage table 3295 GlyphCoverageTable ct = readCoverageTable(tableTag + " ligature caret coverage", subtableOffset + co); 3296 // store results 3297 seMapping = ct; 3298 // extract subtable 3299 extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.LIGATURE_CARET, 0, lookupSequence, 0, 1); 3300 resetATSubState(); 3301 } 3302 readGDEFMarkAttachmentTable(OFTableName tableTag, int lookupSequence, long subtableOffset)3303 private void readGDEFMarkAttachmentTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { 3304 initATSubState(); 3305 in.seekSet(subtableOffset); 3306 // subtable is a bare class definition table 3307 GlyphClassTable ct = readClassDefTable(tableTag + " glyph class definition table", subtableOffset); 3308 // store results 3309 seMapping = ct; 3310 // extract subtable 3311 extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.MARK_ATTACHMENT, 0, lookupSequence, 0, 1); 3312 resetATSubState(); 3313 } 3314 readGDEFMarkGlyphsTableFormat1(OFTableName tableTag, int lookupSequence, long subtableOffset, int subtableFormat)3315 private void readGDEFMarkGlyphsTableFormat1(OFTableName tableTag, int lookupSequence, long subtableOffset, int subtableFormat) throws IOException { 3316 initATSubState(); 3317 in.seekSet(subtableOffset); 3318 // skip over format (already known) 3319 in.skip(2); 3320 // read mark set class count 3321 int nmc = in.readTTFUShort(); 3322 long[] mso = new long [ nmc ]; 3323 // read mark set coverage offsets 3324 for (int i = 0; i < nmc; i++) { 3325 mso [ i ] = in.readTTFULong(); 3326 } 3327 // dump info if debugging 3328 if (log.isDebugEnabled()) { 3329 log.debug(tableTag + " mark set subtable format: " + subtableFormat + " (glyph sets)"); 3330 log.debug(tableTag + " mark set class count: " + nmc); 3331 for (int i = 0; i < nmc; i++) { 3332 log.debug(tableTag + " mark set coverage table offset[" + i + "]: " + mso[i]); 3333 } 3334 } 3335 // read mark set coverage tables, one per class 3336 GlyphCoverageTable[] msca = new GlyphCoverageTable[nmc]; 3337 for (int i = 0; i < nmc; i++) { 3338 msca[i] = readCoverageTable(tableTag + " mark set coverage[" + i + "]", subtableOffset + mso[i]); 3339 } 3340 // create combined class table from per-class coverage tables 3341 GlyphClassTable ct = GlyphClassTable.createClassTable(Arrays.asList(msca)); 3342 // store results 3343 seMapping = ct; 3344 // extract subtable 3345 extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.MARK_ATTACHMENT, 0, lookupSequence, 0, 1); 3346 resetATSubState(); 3347 } 3348 readGDEFMarkGlyphsTable(OFTableName tableTag, int lookupSequence, long subtableOffset)3349 private void readGDEFMarkGlyphsTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { 3350 in.seekSet(subtableOffset); 3351 // read mark set subtable format 3352 int sf = in.readTTFUShort(); 3353 if (sf == 1) { 3354 readGDEFMarkGlyphsTableFormat1(tableTag, lookupSequence, subtableOffset, sf); 3355 } else { 3356 throw new AdvancedTypographicTableFormatException("unsupported mark glyph sets subtable format: " + sf); 3357 } 3358 } 3359 3360 /** 3361 * Read the GDEF table. 3362 * @throws IOException In case of a I/O problem 3363 */ readGDEF()3364 private void readGDEF() throws IOException { 3365 OFTableName tableTag = OFTableName.GDEF; 3366 // Initialize temporary state 3367 initATState(); 3368 // Read glyph definition (GDEF) table 3369 OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag); 3370 if (gdef != null) { 3371 if (log.isDebugEnabled()) { 3372 log.debug(tableTag + ": ignoring duplicate table"); 3373 } 3374 } else if (dirTab != null) { 3375 otf.seekTab(in, tableTag, 0); 3376 long version = in.readTTFULong(); 3377 if (log.isDebugEnabled()) { 3378 log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536)); 3379 } 3380 // glyph class definition table offset (may be null) 3381 int cdo = in.readTTFUShort(); 3382 // attach point list offset (may be null) 3383 int apo = in.readTTFUShort(); 3384 // ligature caret list offset (may be null) 3385 int lco = in.readTTFUShort(); 3386 // mark attach class definition table offset (may be null) 3387 int mao = in.readTTFUShort(); 3388 // mark glyph sets definition table offset (may be null) 3389 int mgo; 3390 if (version >= 0x00010002) { 3391 mgo = in.readTTFUShort(); 3392 } else { 3393 mgo = 0; 3394 } 3395 if (log.isDebugEnabled()) { 3396 log.debug(tableTag + " glyph class definition table offset: " + cdo); 3397 log.debug(tableTag + " attachment point list offset: " + apo); 3398 log.debug(tableTag + " ligature caret list offset: " + lco); 3399 log.debug(tableTag + " mark attachment class definition table offset: " + mao); 3400 log.debug(tableTag + " mark glyph set definitions table offset: " + mgo); 3401 } 3402 // initialize subtable sequence number 3403 int seqno = 0; 3404 // obtain offset to start of gdef table 3405 long to = dirTab.getOffset(); 3406 // (optionally) read glyph class definition subtable 3407 if (cdo != 0) { 3408 readGDEFClassDefTable(tableTag, seqno++, to + cdo); 3409 } 3410 // (optionally) read glyph attachment point subtable 3411 if (apo != 0) { 3412 readGDEFAttachmentTable(tableTag, seqno++, to + apo); 3413 } 3414 // (optionally) read ligature caret subtable 3415 if (lco != 0) { 3416 readGDEFLigatureCaretTable(tableTag, seqno++, to + lco); 3417 } 3418 // (optionally) read mark attachment class subtable 3419 if (mao != 0) { 3420 readGDEFMarkAttachmentTable(tableTag, seqno++, to + mao); 3421 } 3422 // (optionally) read mark glyph sets subtable 3423 if (mgo != 0) { 3424 readGDEFMarkGlyphsTable(tableTag, seqno++, to + mgo); 3425 } 3426 GlyphDefinitionTable gdef; 3427 if ((gdef = constructGDEF()) != null) { 3428 this.gdef = gdef; 3429 } 3430 } 3431 } 3432 3433 /** 3434 * Read the GSUB table. 3435 * @throws IOException In case of a I/O problem 3436 */ readGSUB()3437 private void readGSUB() throws IOException { 3438 OFTableName tableTag = OFTableName.GSUB; 3439 // Initialize temporary state 3440 initATState(); 3441 // Read glyph substitution (GSUB) table 3442 OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag); 3443 if (gpos != null) { 3444 if (log.isDebugEnabled()) { 3445 log.debug(tableTag + ": ignoring duplicate table"); 3446 } 3447 } else if (dirTab != null) { 3448 otf.seekTab(in, tableTag, 0); 3449 int version = in.readTTFLong(); 3450 if (log.isDebugEnabled()) { 3451 log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536)); 3452 } 3453 int slo = in.readTTFUShort(); 3454 int flo = in.readTTFUShort(); 3455 int llo = in.readTTFUShort(); 3456 if (log.isDebugEnabled()) { 3457 log.debug(tableTag + " script list offset: " + slo); 3458 log.debug(tableTag + " feature list offset: " + flo); 3459 log.debug(tableTag + " lookup list offset: " + llo); 3460 } 3461 long to = dirTab.getOffset(); 3462 readCommonLayoutTables(tableTag, to + slo, to + flo, to + llo); 3463 GlyphSubstitutionTable gsub; 3464 if ((gsub = constructGSUB()) != null) { 3465 this.gsub = gsub; 3466 } 3467 } 3468 } 3469 3470 /** 3471 * Read the GPOS table. 3472 * @throws IOException In case of a I/O problem 3473 */ readGPOS()3474 private void readGPOS() throws IOException { 3475 OFTableName tableTag = OFTableName.GPOS; 3476 // Initialize temporary state 3477 initATState(); 3478 // Read glyph positioning (GPOS) table 3479 OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag); 3480 if (gpos != null) { 3481 if (log.isDebugEnabled()) { 3482 log.debug(tableTag + ": ignoring duplicate table"); 3483 } 3484 } else if (dirTab != null) { 3485 otf.seekTab(in, tableTag, 0); 3486 int version = in.readTTFLong(); 3487 if (log.isDebugEnabled()) { 3488 log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536)); 3489 } 3490 int slo = in.readTTFUShort(); 3491 int flo = in.readTTFUShort(); 3492 int llo = in.readTTFUShort(); 3493 if (log.isDebugEnabled()) { 3494 log.debug(tableTag + " script list offset: " + slo); 3495 log.debug(tableTag + " feature list offset: " + flo); 3496 log.debug(tableTag + " lookup list offset: " + llo); 3497 } 3498 long to = dirTab.getOffset(); 3499 readCommonLayoutTables(tableTag, to + slo, to + flo, to + llo); 3500 GlyphPositioningTable gpos; 3501 if ((gpos = constructGPOS()) != null) { 3502 this.gpos = gpos; 3503 } 3504 } 3505 } 3506 3507 /** 3508 * Construct the (internal representation of the) GDEF table based on previously 3509 * parsed state. 3510 * @returns glyph definition table or null if insufficient or invalid state 3511 */ constructGDEF()3512 private GlyphDefinitionTable constructGDEF() { 3513 GlyphDefinitionTable gdef = null; 3514 List subtables; 3515 if ((subtables = constructGDEFSubtables()) != null) { 3516 if (subtables.size() > 0) { 3517 gdef = new GlyphDefinitionTable(subtables, processors); 3518 } 3519 } 3520 resetATState(); 3521 return gdef; 3522 } 3523 3524 /** 3525 * Construct the (internal representation of the) GSUB table based on previously 3526 * parsed state. 3527 * @returns glyph substitution table or null if insufficient or invalid state 3528 */ constructGSUB()3529 private GlyphSubstitutionTable constructGSUB() { 3530 GlyphSubstitutionTable gsub = null; 3531 Map lookups; 3532 if ((lookups = constructLookups()) != null) { 3533 List subtables; 3534 if ((subtables = constructGSUBSubtables()) != null) { 3535 if ((lookups.size() > 0) && (subtables.size() > 0)) { 3536 gsub = new GlyphSubstitutionTable(gdef, lookups, subtables, processors); 3537 } 3538 } 3539 } 3540 resetATState(); 3541 return gsub; 3542 } 3543 3544 /** 3545 * Construct the (internal representation of the) GPOS table based on previously 3546 * parsed state. 3547 * @returns glyph positioning table or null if insufficient or invalid state 3548 */ constructGPOS()3549 private GlyphPositioningTable constructGPOS() { 3550 GlyphPositioningTable gpos = null; 3551 Map lookups; 3552 if ((lookups = constructLookups()) != null) { 3553 List subtables; 3554 if ((subtables = constructGPOSSubtables()) != null) { 3555 if ((lookups.size() > 0) && (subtables.size() > 0)) { 3556 gpos = new GlyphPositioningTable(gdef, lookups, subtables, processors); 3557 } 3558 } 3559 } 3560 resetATState(); 3561 return gpos; 3562 } 3563 constructLookupsFeature(Map lookups, String st, String lt, String fid)3564 private void constructLookupsFeature(Map lookups, String st, String lt, String fid) { 3565 Object[] fp = (Object[]) seFeatures.get(fid); 3566 if (fp != null) { 3567 assert fp.length == 2; 3568 String ft = (String) fp[0]; // feature tag 3569 List<String> lul = (List) fp[1]; // list of lookup table ids 3570 if ((ft != null) && (lul != null) && (lul.size() > 0)) { 3571 GlyphTable.LookupSpec ls = new GlyphTable.LookupSpec(st, lt, ft); 3572 lookups.put(ls, lul); 3573 } 3574 } 3575 } 3576 constructLookupsFeatures(Map lookups, String st, String lt, List<String> fids)3577 private void constructLookupsFeatures(Map lookups, String st, String lt, List<String> fids) { 3578 for (Object fid1 : fids) { 3579 String fid = (String) fid1; 3580 constructLookupsFeature(lookups, st, lt, fid); 3581 } 3582 } 3583 constructLookupsLanguage(Map lookups, String st, String lt, Map<String, Object> languages)3584 private void constructLookupsLanguage(Map lookups, String st, String lt, Map<String, Object> languages) { 3585 Object[] lp = (Object[]) languages.get(lt); 3586 if (lp != null) { 3587 assert lp.length == 2; 3588 if (lp[0] != null) { // required feature id 3589 constructLookupsFeature(lookups, st, lt, (String) lp[0]); 3590 } 3591 if (lp[1] != null) { // non-required features ids 3592 constructLookupsFeatures(lookups, st, lt, (List) lp[1]); 3593 } 3594 } 3595 } 3596 constructLookupsLanguages(Map lookups, String st, List<String> ll, Map<String, Object> languages)3597 private void constructLookupsLanguages(Map lookups, String st, List<String> ll, Map<String, Object> languages) { 3598 for (Object aLl : ll) { 3599 String lt = (String) aLl; 3600 constructLookupsLanguage(lookups, st, lt, languages); 3601 } 3602 } 3603 constructLookups()3604 private Map constructLookups() { 3605 Map<GlyphTable.LookupSpec, List<String>> lookups = new java.util.LinkedHashMap(); 3606 for (Object o : seScripts.keySet()) { 3607 String st = (String) o; 3608 Object[] sp = (Object[]) seScripts.get(st); 3609 if (sp != null) { 3610 assert sp.length == 3; 3611 Map<String, Object> languages = (Map) sp[2]; 3612 if (sp[0] != null) { // default language 3613 constructLookupsLanguage(lookups, st, (String) sp[0], languages); 3614 } 3615 if (sp[1] != null) { // non-default languages 3616 constructLookupsLanguages(lookups, st, (List) sp[1], languages); 3617 } 3618 } 3619 } 3620 return lookups; 3621 } 3622 constructGDEFSubtables()3623 private List constructGDEFSubtables() { 3624 List<GlyphSubtable> subtables = new java.util.ArrayList(); 3625 if (seSubtables != null) { 3626 for (Object seSubtable : seSubtables) { 3627 Object[] stp = (Object[]) seSubtable; 3628 GlyphSubtable st; 3629 if ((st = constructGDEFSubtable(stp)) != null) { 3630 subtables.add(st); 3631 } 3632 } 3633 } 3634 return subtables; 3635 } 3636 constructGDEFSubtable(Object[] stp)3637 private GlyphSubtable constructGDEFSubtable(Object[] stp) { 3638 GlyphSubtable st = null; 3639 assert (stp != null) && (stp.length == 8); 3640 Integer tt = (Integer) stp[0]; // table type 3641 Integer lt = (Integer) stp[1]; // lookup type 3642 Integer ln = (Integer) stp[2]; // lookup sequence number 3643 Integer lf = (Integer) stp[3]; // lookup flags 3644 Integer sn = (Integer) stp[4]; // subtable sequence number 3645 Integer sf = (Integer) stp[5]; // subtable format 3646 GlyphMappingTable mapping = (GlyphMappingTable) stp[6]; 3647 List entries = (List) stp[7]; 3648 if (tt == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION) { 3649 int type = GDEFLookupType.getSubtableType(lt); 3650 String lid = "lu" + ln; 3651 int sequence = sn; 3652 int flags = lf; 3653 int format = sf; 3654 st = GlyphDefinitionTable.createSubtable(type, lid, sequence, flags, format, mapping, entries); 3655 } 3656 return st; 3657 } 3658 constructGSUBSubtables()3659 private List constructGSUBSubtables() { 3660 List<GlyphSubtable> subtables = new java.util.ArrayList(); 3661 if (seSubtables != null) { 3662 for (Object seSubtable : seSubtables) { 3663 Object[] stp = (Object[]) seSubtable; 3664 GlyphSubtable st; 3665 if ((st = constructGSUBSubtable(stp)) != null) { 3666 subtables.add(st); 3667 } 3668 } 3669 } 3670 return subtables; 3671 } 3672 constructGSUBSubtable(Object[] stp)3673 private GlyphSubtable constructGSUBSubtable(Object[] stp) { 3674 GlyphSubtable st = null; 3675 assert (stp != null) && (stp.length == 8); 3676 Integer tt = (Integer) stp[0]; // table type 3677 Integer lt = (Integer) stp[1]; // lookup type 3678 Integer ln = (Integer) stp[2]; // lookup sequence number 3679 Integer lf = (Integer) stp[3]; // lookup flags 3680 Integer sn = (Integer) stp[4]; // subtable sequence number 3681 Integer sf = (Integer) stp[5]; // subtable format 3682 GlyphCoverageTable coverage = (GlyphCoverageTable) stp[6]; 3683 List entries = (List) stp[7]; 3684 if (tt == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION) { 3685 int type = GSUBLookupType.getSubtableType(lt); 3686 String lid = "lu" + ln; 3687 int sequence = sn; 3688 int flags = lf; 3689 int format = sf; 3690 st = GlyphSubstitutionTable.createSubtable(type, lid, sequence, flags, format, coverage, entries); 3691 } 3692 return st; 3693 } 3694 constructGPOSSubtables()3695 private List constructGPOSSubtables() { 3696 List<GlyphSubtable> subtables = new java.util.ArrayList(); 3697 if (seSubtables != null) { 3698 for (Object seSubtable : seSubtables) { 3699 Object[] stp = (Object[]) seSubtable; 3700 GlyphSubtable st; 3701 if ((st = constructGPOSSubtable(stp)) != null) { 3702 subtables.add(st); 3703 } 3704 } 3705 } 3706 return subtables; 3707 } 3708 constructGPOSSubtable(Object[] stp)3709 private GlyphSubtable constructGPOSSubtable(Object[] stp) { 3710 GlyphSubtable st = null; 3711 assert (stp != null) && (stp.length == 8); 3712 Integer tt = (Integer) stp[0]; // table type 3713 Integer lt = (Integer) stp[1]; // lookup type 3714 Integer ln = (Integer) stp[2]; // lookup sequence number 3715 Integer lf = (Integer) stp[3]; // lookup flags 3716 Integer sn = (Integer) stp[4]; // subtable sequence number 3717 Integer sf = (Integer) stp[5]; // subtable format 3718 GlyphCoverageTable coverage = (GlyphCoverageTable) stp[6]; 3719 List entries = (List) stp[7]; 3720 if (tt == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING) { 3721 int type = GSUBLookupType.getSubtableType(lt); 3722 String lid = "lu" + ln; 3723 int sequence = sn; 3724 int flags = lf; 3725 int format = sf; 3726 st = GlyphPositioningTable.createSubtable(type, lid, sequence, flags, format, coverage, entries); 3727 } 3728 return st; 3729 } 3730 initATState()3731 private void initATState() { 3732 seScripts = new java.util.LinkedHashMap(); 3733 seLanguages = new java.util.LinkedHashMap(); 3734 seFeatures = new java.util.LinkedHashMap(); 3735 seSubtables = new java.util.ArrayList(); 3736 resetATSubState(); 3737 } 3738 resetATState()3739 private void resetATState() { 3740 seScripts = null; 3741 seLanguages = null; 3742 seFeatures = null; 3743 seSubtables = null; 3744 resetATSubState(); 3745 } 3746 initATSubState()3747 private void initATSubState() { 3748 seMapping = null; 3749 seEntries = new java.util.ArrayList(); 3750 } 3751 extractSESubState(int tableType, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, int subtableFormat)3752 private void extractSESubState(int tableType, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, int subtableFormat) { 3753 if (seEntries != null) { 3754 if ((tableType == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION) || (seEntries.size() > 0)) { 3755 if (seSubtables != null) { 3756 Integer tt = tableType; 3757 Integer lt = lookupType; 3758 Integer ln = lookupSequence; 3759 Integer lf = lookupFlags; 3760 Integer sn = subtableSequence; 3761 Integer sf = subtableFormat; 3762 seSubtables.add(new Object[] { tt, lt, ln, lf, sn, sf, seMapping, seEntries }); 3763 } 3764 } 3765 } 3766 } 3767 resetATSubState()3768 private void resetATSubState() { 3769 seMapping = null; 3770 seEntries = null; 3771 } 3772 resetATStateAll()3773 private void resetATStateAll() { 3774 resetATState(); 3775 gdef = null; 3776 gsub = null; 3777 gpos = null; 3778 } 3779 3780 /** helper method for formatting an integer array for output */ toString(int[] ia)3781 private String toString(int[] ia) { 3782 StringBuffer sb = new StringBuffer(); 3783 if ((ia == null) || (ia.length == 0)) { 3784 sb.append('-'); 3785 } else { 3786 boolean first = true; 3787 for (int anIa : ia) { 3788 if (!first) { 3789 sb.append(' '); 3790 } else { 3791 first = false; 3792 } 3793 sb.append(anIa); 3794 } 3795 } 3796 return sb.toString(); 3797 } 3798 3799 } 3800