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