| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* $Id$ */ |
| |
| package org.apache.fop.complexscripts.fonts; |
| |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import org.apache.fop.fonts.truetype.FontFileReader; |
| import org.apache.fop.fonts.truetype.OFDirTabEntry; |
| import org.apache.fop.fonts.truetype.OFTableName; |
| import org.apache.fop.fonts.truetype.OpenFont; |
| |
| // CSOFF: LineLengthCheck |
| |
| /** |
| * <p>OpenType Font (OTF) advanced typographic table reader. Used by @{Link org.apache.fop.fonts.truetype.TTFFile} |
| * to read advanced typographic tables (GDEF, GSUB, GPOS).</p> |
| * |
| * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p> |
| */ |
| public final class OTFAdvancedTypographicTableReader { |
| |
| // logging state |
| private static Log log = LogFactory.getLog(OTFAdvancedTypographicTableReader.class); |
| // instance state |
| private OpenFont otf; // parent font file reader |
| private FontFileReader in; // input reader |
| private GlyphDefinitionTable gdef; // glyph definition table |
| private GlyphSubstitutionTable gsub; // glyph substitution table |
| private GlyphPositioningTable gpos; // glyph positioning table |
| // transient parsing state |
| private transient Map/*<String,Object[3]>*/ seScripts; // script-tag => Object[3] : { default-language-tag, List(language-tag), seLanguages } |
| private transient Map/*<String,Object[2]>*/ seLanguages; // language-tag => Object[2] : { "f<required-feature-index>", List("f<feature-index>") |
| private transient Map/*<String,List<String>>*/ seFeatures; // "f<feature-index>" => Object[2] : { feature-tag, List("lu<lookup-index>") } |
| private transient GlyphMappingTable seMapping; // subtable entry mappings |
| private transient List seEntries; // subtable entry entries |
| private transient List seSubtables; // subtable entry subtables |
| |
| /** |
| * Construct an <code>OTFAdvancedTypographicTableReader</code> instance. |
| * @param ttf parent font file reader (must be non-null) |
| * @param in font file reader (must be non-null) |
| */ |
| public OTFAdvancedTypographicTableReader(OpenFont otf, FontFileReader in) { |
| assert otf != null; |
| assert in != null; |
| this.otf = otf; |
| this.in = in; |
| } |
| |
| /** |
| * Read all advanced typographic tables. |
| * @throws AdvancedTypographicTableFormatException if ATT table has invalid format |
| */ |
| public void readAll() throws AdvancedTypographicTableFormatException { |
| try { |
| readGDEF(); |
| readGSUB(); |
| readGPOS(); |
| } catch (AdvancedTypographicTableFormatException e) { |
| resetATStateAll(); |
| throw e; |
| } catch (IOException e) { |
| resetATStateAll(); |
| throw new AdvancedTypographicTableFormatException(e.getMessage(), e); |
| } finally { |
| resetATState(); |
| } |
| } |
| |
| /** |
| * Determine if advanced (typographic) table is present. |
| * @return true if advanced (typographic) table is present |
| */ |
| public boolean hasAdvancedTable() { |
| return (gdef != null) || (gsub != null) || (gpos != null); |
| } |
| |
| /** |
| * Returns the GDEF table or null if none present. |
| * @return the GDEF table |
| */ |
| public GlyphDefinitionTable getGDEF() { |
| return gdef; |
| } |
| |
| /** |
| * Returns the GSUB table or null if none present. |
| * @return the GSUB table |
| */ |
| public GlyphSubstitutionTable getGSUB() { |
| return gsub; |
| } |
| |
| /** |
| * Returns the GPOS table or null if none present. |
| * @return the GPOS table |
| */ |
| public GlyphPositioningTable getGPOS() { |
| return gpos; |
| } |
| |
| private void readLangSysTable(OFTableName tableTag, long langSysTable, String langSysTag) |
| throws IOException { |
| in.seekSet(langSysTable); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lang sys table: " + langSysTag); |
| } |
| // read lookup order (reorder) table offset |
| int lo = in.readTTFUShort(); |
| // read required feature index |
| int rf = in.readTTFUShort(); |
| String rfi; |
| if (rf != 65535) { |
| rfi = "f" + rf; |
| } else { |
| rfi = null; |
| } |
| // read (non-required) feature count |
| int nf = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lang sys table reorder table: " + lo); |
| log.debug(tableTag + " lang sys table required feature index: " + rf); |
| log.debug(tableTag + " lang sys table non-required feature count: " + nf); |
| } |
| // read (non-required) feature indices |
| int[] fia = new int[nf]; |
| List fl = new java.util.ArrayList(); |
| for (int i = 0; i < nf; i++) { |
| int fi = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lang sys table non-required feature index: " + fi); |
| } |
| fia[i] = fi; |
| fl.add("f" + fi); |
| } |
| if (seLanguages == null) { |
| seLanguages = new java.util.LinkedHashMap(); |
| } |
| seLanguages.put(langSysTag, new Object[] { rfi, fl }); |
| } |
| |
| private static String defaultTag = "dflt"; |
| |
| private void readScriptTable(OFTableName tableTag, long scriptTable, String scriptTag) throws IOException { |
| in.seekSet(scriptTable); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " script table: " + scriptTag); |
| } |
| // read default language system table offset |
| int dl = in.readTTFUShort(); |
| String dt = defaultTag; |
| if (dl > 0) { |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " default lang sys tag: " + dt); |
| log.debug(tableTag + " default lang sys table offset: " + dl); |
| } |
| } |
| // read language system record count |
| int nl = in.readTTFUShort(); |
| List ll = new java.util.ArrayList(); |
| if (nl > 0) { |
| String[] lta = new String[nl]; |
| int[] loa = new int[nl]; |
| // read language system records |
| for (int i = 0, n = nl; i < n; i++) { |
| String lt = in.readTTFString(4); |
| int lo = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lang sys tag: " + lt); |
| log.debug(tableTag + " lang sys table offset: " + lo); |
| } |
| lta[i] = lt; |
| loa[i] = lo; |
| if (dl == lo) { |
| dl = 0; |
| dt = lt; |
| } |
| ll.add(lt); |
| } |
| // read non-default language system tables |
| for (int i = 0, n = nl; i < n; i++) { |
| readLangSysTable(tableTag, scriptTable + loa [ i ], lta [ i ]); |
| } |
| } |
| // read default language system table (if specified) |
| if (dl > 0) { |
| readLangSysTable(tableTag, scriptTable + dl, dt); |
| } else if (dt != null) { |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lang sys default: " + dt); |
| } |
| } |
| seScripts.put(scriptTag, new Object[] { dt, ll, seLanguages }); |
| seLanguages = null; |
| } |
| |
| private void readScriptList(OFTableName tableTag, long scriptList) throws IOException { |
| in.seekSet(scriptList); |
| // read script record count |
| int ns = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " script list record count: " + ns); |
| } |
| if (ns > 0) { |
| String[] sta = new String[ns]; |
| int[] soa = new int[ns]; |
| // read script records |
| for (int i = 0, n = ns; i < n; i++) { |
| String st = in.readTTFString(4); |
| int so = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " script tag: " + st); |
| log.debug(tableTag + " script table offset: " + so); |
| } |
| sta[i] = st; |
| soa[i] = so; |
| } |
| // read script tables |
| for (int i = 0, n = ns; i < n; i++) { |
| seLanguages = null; |
| readScriptTable(tableTag, scriptList + soa [ i ], sta [ i ]); |
| } |
| } |
| } |
| |
| private void readFeatureTable(OFTableName tableTag, long featureTable, String featureTag, int featureIndex) throws IOException { |
| in.seekSet(featureTable); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " feature table: " + featureTag); |
| } |
| // read feature params offset |
| int po = in.readTTFUShort(); |
| // read lookup list indices count |
| int nl = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " feature table parameters offset: " + po); |
| log.debug(tableTag + " feature table lookup list index count: " + nl); |
| } |
| // read lookup table indices |
| int[] lia = new int[nl]; |
| List lul = new java.util.ArrayList(); |
| for (int i = 0; i < nl; i++) { |
| int li = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " feature table lookup index: " + li); |
| } |
| lia[i] = li; |
| lul.add("lu" + li); |
| } |
| seFeatures.put("f" + featureIndex, new Object[] { featureTag, lul }); |
| } |
| |
| private void readFeatureList(OFTableName tableTag, long featureList) throws IOException { |
| in.seekSet(featureList); |
| // read feature record count |
| int nf = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " feature list record count: " + nf); |
| } |
| if (nf > 0) { |
| String[] fta = new String[nf]; |
| int[] foa = new int[nf]; |
| // read feature records |
| for (int i = 0, n = nf; i < n; i++) { |
| String ft = in.readTTFString(4); |
| int fo = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " feature tag: " + ft); |
| log.debug(tableTag + " feature table offset: " + fo); |
| } |
| fta[i] = ft; |
| foa[i] = fo; |
| } |
| // read feature tables |
| for (int i = 0, n = nf; i < n; i++) { |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " feature index: " + i); |
| } |
| readFeatureTable(tableTag, featureList + foa [ i ], fta [ i ], i); |
| } |
| } |
| } |
| |
| static final class GDEFLookupType { |
| static final int GLYPH_CLASS = 1; |
| static final int ATTACHMENT_POINT = 2; |
| static final int LIGATURE_CARET = 3; |
| static final int MARK_ATTACHMENT = 4; |
| private GDEFLookupType() { |
| } |
| public static int getSubtableType(int lt) { |
| int st; |
| switch (lt) { |
| case GDEFLookupType.GLYPH_CLASS: |
| st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_GLYPH_CLASS; |
| break; |
| case GDEFLookupType.ATTACHMENT_POINT: |
| st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_ATTACHMENT_POINT; |
| break; |
| case GDEFLookupType.LIGATURE_CARET: |
| st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_LIGATURE_CARET; |
| break; |
| case GDEFLookupType.MARK_ATTACHMENT: |
| st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_MARK_ATTACHMENT; |
| break; |
| default: |
| st = -1; |
| break; |
| } |
| return st; |
| } |
| public static String toString(int type) { |
| String s; |
| switch (type) { |
| case GLYPH_CLASS: |
| s = "GlyphClass"; |
| break; |
| case ATTACHMENT_POINT: |
| s = "AttachmentPoint"; |
| break; |
| case LIGATURE_CARET: |
| s = "LigatureCaret"; |
| break; |
| case MARK_ATTACHMENT: |
| s = "MarkAttachment"; |
| break; |
| default: |
| s = "?"; |
| break; |
| } |
| return s; |
| } |
| } |
| |
| static final class GSUBLookupType { |
| static final int SINGLE = 1; |
| static final int MULTIPLE = 2; |
| static final int ALTERNATE = 3; |
| static final int LIGATURE = 4; |
| static final int CONTEXTUAL = 5; |
| static final int CHAINED_CONTEXTUAL = 6; |
| static final int EXTENSION = 7; |
| static final int REVERSE_CHAINED_SINGLE = 8; |
| private GSUBLookupType() { |
| } |
| public static int getSubtableType(int lt) { |
| int st; |
| switch (lt) { |
| case GSUBLookupType.SINGLE: |
| st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_SINGLE; |
| break; |
| case GSUBLookupType.MULTIPLE: |
| st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_MULTIPLE; |
| break; |
| case GSUBLookupType.ALTERNATE: |
| st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_ALTERNATE; |
| break; |
| case GSUBLookupType.LIGATURE: |
| st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_LIGATURE; |
| break; |
| case GSUBLookupType.CONTEXTUAL: |
| st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CONTEXTUAL; |
| break; |
| case GSUBLookupType.CHAINED_CONTEXTUAL: |
| st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL; |
| break; |
| case GSUBLookupType.EXTENSION: |
| st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION; |
| break; |
| case GSUBLookupType.REVERSE_CHAINED_SINGLE: |
| st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE; |
| break; |
| default: |
| st = -1; |
| break; |
| } |
| return st; |
| } |
| public static String toString(int type) { |
| String s; |
| switch (type) { |
| case SINGLE: |
| s = "Single"; |
| break; |
| case MULTIPLE: |
| s = "Multiple"; |
| break; |
| case ALTERNATE: |
| s = "Alternate"; |
| break; |
| case LIGATURE: |
| s = "Ligature"; |
| break; |
| case CONTEXTUAL: |
| s = "Contextual"; |
| break; |
| case CHAINED_CONTEXTUAL: |
| s = "ChainedContextual"; |
| break; |
| case EXTENSION: |
| s = "Extension"; |
| break; |
| case REVERSE_CHAINED_SINGLE: |
| s = "ReverseChainedSingle"; |
| break; |
| default: |
| s = "?"; |
| break; |
| } |
| return s; |
| } |
| } |
| |
| static final class GPOSLookupType { |
| static final int SINGLE = 1; |
| static final int PAIR = 2; |
| static final int CURSIVE = 3; |
| static final int MARK_TO_BASE = 4; |
| static final int MARK_TO_LIGATURE = 5; |
| static final int MARK_TO_MARK = 6; |
| static final int CONTEXTUAL = 7; |
| static final int CHAINED_CONTEXTUAL = 8; |
| static final int EXTENSION = 9; |
| private GPOSLookupType() { |
| } |
| public static String toString(int type) { |
| String s; |
| switch (type) { |
| case SINGLE: |
| s = "Single"; |
| break; |
| case PAIR: |
| s = "Pair"; |
| break; |
| case CURSIVE: |
| s = "Cursive"; |
| break; |
| case MARK_TO_BASE: |
| s = "MarkToBase"; |
| break; |
| case MARK_TO_LIGATURE: |
| s = "MarkToLigature"; |
| break; |
| case MARK_TO_MARK: |
| s = "MarkToMark"; |
| break; |
| case CONTEXTUAL: |
| s = "Contextual"; |
| break; |
| case CHAINED_CONTEXTUAL: |
| s = "ChainedContextual"; |
| break; |
| case EXTENSION: |
| s = "Extension"; |
| break; |
| default: |
| s = "?"; |
| break; |
| } |
| return s; |
| } |
| } |
| |
| static final class LookupFlag { |
| static final int RIGHT_TO_LEFT = 0x0001; |
| static final int IGNORE_BASE_GLYPHS = 0x0002; |
| static final int IGNORE_LIGATURE = 0x0004; |
| static final int IGNORE_MARKS = 0x0008; |
| static final int USE_MARK_FILTERING_SET = 0x0010; |
| static final int MARK_ATTACHMENT_TYPE = 0xFF00; |
| private LookupFlag() { |
| } |
| public static String toString(int flags) { |
| StringBuffer sb = new StringBuffer(); |
| boolean first = true; |
| if ((flags & RIGHT_TO_LEFT) != 0) { |
| if (first) { |
| first = false; |
| } else { |
| sb.append('|'); |
| } |
| sb.append("RightToLeft"); |
| } |
| if ((flags & IGNORE_BASE_GLYPHS) != 0) { |
| if (first) { |
| first = false; |
| } else { |
| sb.append('|'); |
| } |
| sb.append("IgnoreBaseGlyphs"); |
| } |
| if ((flags & IGNORE_LIGATURE) != 0) { |
| if (first) { |
| first = false; |
| } else { |
| sb.append('|'); |
| } |
| sb.append("IgnoreLigature"); |
| } |
| if ((flags & IGNORE_MARKS) != 0) { |
| if (first) { |
| first = false; |
| } else { |
| sb.append('|'); |
| } |
| sb.append("IgnoreMarks"); |
| } |
| if ((flags & USE_MARK_FILTERING_SET) != 0) { |
| if (first) { |
| first = false; |
| } else { |
| sb.append('|'); |
| } |
| sb.append("UseMarkFilteringSet"); |
| } |
| if (sb.length() == 0) { |
| sb.append('-'); |
| } |
| return sb.toString(); |
| } |
| } |
| |
| private GlyphCoverageTable readCoverageTableFormat1(String label, long tableOffset, int coverageFormat) throws IOException { |
| List entries = new java.util.ArrayList(); |
| in.seekSet(tableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| int[] ga = new int[ng]; |
| for (int i = 0, n = ng; i < n; i++) { |
| int g = in.readTTFUShort(); |
| ga[i] = g; |
| entries.add(Integer.valueOf(g)); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(label + " glyphs: " + toString(ga)); |
| } |
| return GlyphCoverageTable.createCoverageTable(entries); |
| } |
| |
| private GlyphCoverageTable readCoverageTableFormat2(String label, long tableOffset, int coverageFormat) throws IOException { |
| List entries = new java.util.ArrayList(); |
| in.seekSet(tableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read range record count |
| int nr = in.readTTFUShort(); |
| for (int i = 0, n = nr; i < n; i++) { |
| // read range start |
| int s = in.readTTFUShort(); |
| // read range end |
| int e = in.readTTFUShort(); |
| // read range coverage (mapping) index |
| int m = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(label + " range[" + i + "]: [" + s + "," + e + "]: " + m); |
| } |
| entries.add(new GlyphCoverageTable.MappingRange(s, e, m)); |
| } |
| return GlyphCoverageTable.createCoverageTable(entries); |
| } |
| |
| private GlyphCoverageTable readCoverageTable(String label, long tableOffset) throws IOException { |
| GlyphCoverageTable gct; |
| long cp = in.getCurrentPos(); |
| in.seekSet(tableOffset); |
| // read coverage table format |
| int cf = in.readTTFUShort(); |
| if (cf == 1) { |
| gct = readCoverageTableFormat1(label, tableOffset, cf); |
| } else if (cf == 2) { |
| gct = readCoverageTableFormat2(label, tableOffset, cf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported coverage table format: " + cf); |
| } |
| in.seekSet(cp); |
| return gct; |
| } |
| |
| private GlyphClassTable readClassDefTableFormat1(String label, long tableOffset, int classFormat) throws IOException { |
| List entries = new java.util.ArrayList(); |
| in.seekSet(tableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read start glyph |
| int sg = in.readTTFUShort(); |
| entries.add(Integer.valueOf(sg)); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| // read glyph classes |
| int[] ca = new int[ng]; |
| for (int i = 0, n = ng; i < n; i++) { |
| int gc = in.readTTFUShort(); |
| ca[i] = gc; |
| entries.add(Integer.valueOf(gc)); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(label + " glyph classes: " + toString(ca)); |
| } |
| return GlyphClassTable.createClassTable(entries); |
| } |
| |
| private GlyphClassTable readClassDefTableFormat2(String label, long tableOffset, int classFormat) throws IOException { |
| List entries = new java.util.ArrayList(); |
| in.seekSet(tableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read range record count |
| int nr = in.readTTFUShort(); |
| for (int i = 0, n = nr; i < n; i++) { |
| // read range start |
| int s = in.readTTFUShort(); |
| // read range end |
| int e = in.readTTFUShort(); |
| // read range glyph class (mapping) index |
| int m = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(label + " range[" + i + "]: [" + s + "," + e + "]: " + m); |
| } |
| entries.add(new GlyphClassTable.MappingRange(s, e, m)); |
| } |
| return GlyphClassTable.createClassTable(entries); |
| } |
| |
| private GlyphClassTable readClassDefTable(String label, long tableOffset) throws IOException { |
| GlyphClassTable gct; |
| long cp = in.getCurrentPos(); |
| in.seekSet(tableOffset); |
| // read class table format |
| int cf = in.readTTFUShort(); |
| if (cf == 1) { |
| gct = readClassDefTableFormat1(label, tableOffset, cf); |
| } else if (cf == 2) { |
| gct = readClassDefTableFormat2(label, tableOffset, cf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported class definition table format: " + cf); |
| } |
| in.seekSet(cp); |
| return gct; |
| } |
| |
| private void readSingleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read delta glyph |
| int dg = in.readTTFShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " single substitution subtable format: " + subtableFormat + " (delta)"); |
| log.debug(tableTag + " single substitution coverage table offset: " + co); |
| log.debug(tableTag + " single substitution delta: " + dg); |
| } |
| // read coverage table |
| seMapping = readCoverageTable(tableTag + " single substitution coverage", subtableOffset + co); |
| seEntries.add(Integer.valueOf(dg)); |
| } |
| |
| private void readSingleSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " single substitution subtable format: " + subtableFormat + " (mapped)"); |
| log.debug(tableTag + " single substitution coverage table offset: " + co); |
| log.debug(tableTag + " single substitution glyph count: " + ng); |
| } |
| // read coverage table |
| seMapping = readCoverageTable(tableTag + " single substitution coverage", subtableOffset + co); |
| // read glyph substitutions |
| int[] gsa = new int[ng]; |
| for (int i = 0, n = ng; i < n; i++) { |
| int gs = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " single substitution glyph[" + i + "]: " + gs); |
| } |
| gsa[i] = gs; |
| seEntries.add(Integer.valueOf(gs)); |
| } |
| } |
| |
| private int readSingleSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read substitution subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readSingleSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); |
| } else if (sf == 2) { |
| readSingleSubTableFormat2(lookupType, lookupFlags, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported single substitution subtable format: " + sf); |
| } |
| return sf; |
| } |
| |
| private void readMultipleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read sequence count |
| int ns = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " multiple substitution subtable format: " + subtableFormat + " (mapped)"); |
| log.debug(tableTag + " multiple substitution coverage table offset: " + co); |
| log.debug(tableTag + " multiple substitution sequence count: " + ns); |
| } |
| // read coverage table |
| seMapping = readCoverageTable(tableTag + " multiple substitution coverage", subtableOffset + co); |
| // read sequence table offsets |
| int[] soa = new int[ns]; |
| for (int i = 0, n = ns; i < n; i++) { |
| soa[i] = in.readTTFUShort(); |
| } |
| // read sequence tables |
| int[][] gsa = new int [ ns ] []; |
| for (int i = 0, n = ns; i < n; i++) { |
| int so = soa[i]; |
| int[] ga; |
| if (so > 0) { |
| in.seekSet(subtableOffset + so); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| ga = new int[ng]; |
| for (int j = 0; j < ng; j++) { |
| ga[j] = in.readTTFUShort(); |
| } |
| } else { |
| ga = null; |
| } |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " multiple substitution sequence[" + i + "]: " + toString(ga)); |
| } |
| gsa [ i ] = ga; |
| } |
| seEntries.add(gsa); |
| } |
| |
| private int readMultipleSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read substitution subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readMultipleSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported multiple substitution subtable format: " + sf); |
| } |
| return sf; |
| } |
| |
| private void readAlternateSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read alternate set count |
| int ns = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " alternate substitution subtable format: " + subtableFormat + " (mapped)"); |
| log.debug(tableTag + " alternate substitution coverage table offset: " + co); |
| log.debug(tableTag + " alternate substitution alternate set count: " + ns); |
| } |
| // read coverage table |
| seMapping = readCoverageTable(tableTag + " alternate substitution coverage", subtableOffset + co); |
| // read alternate set table offsets |
| int[] soa = new int[ns]; |
| for (int i = 0, n = ns; i < n; i++) { |
| soa[i] = in.readTTFUShort(); |
| } |
| // read alternate set tables |
| for (int i = 0, n = ns; i < n; i++) { |
| int so = soa[i]; |
| in.seekSet(subtableOffset + so); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| int[] ga = new int[ng]; |
| for (int j = 0; j < ng; j++) { |
| int gs = in.readTTFUShort(); |
| ga[j] = gs; |
| } |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " alternate substitution alternate set[" + i + "]: " + toString(ga)); |
| } |
| seEntries.add(ga); |
| } |
| } |
| |
| private int readAlternateSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read substitution subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readAlternateSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported alternate substitution subtable format: " + sf); |
| } |
| return sf; |
| } |
| |
| private void readLigatureSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read ligature set count |
| int ns = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " ligature substitution subtable format: " + subtableFormat + " (mapped)"); |
| log.debug(tableTag + " ligature substitution coverage table offset: " + co); |
| log.debug(tableTag + " ligature substitution ligature set count: " + ns); |
| } |
| // read coverage table |
| seMapping = readCoverageTable(tableTag + " ligature substitution coverage", subtableOffset + co); |
| // read ligature set table offsets |
| int[] soa = new int[ns]; |
| for (int i = 0, n = ns; i < n; i++) { |
| soa[i] = in.readTTFUShort(); |
| } |
| // read ligature set tables |
| for (int i = 0, n = ns; i < n; i++) { |
| int so = soa[i]; |
| in.seekSet(subtableOffset + so); |
| // read ligature table count |
| int nl = in.readTTFUShort(); |
| int[] loa = new int[nl]; |
| for (int j = 0; j < nl; j++) { |
| loa[j] = in.readTTFUShort(); |
| } |
| List ligs = new java.util.ArrayList(); |
| for (int j = 0; j < nl; j++) { |
| int lo = loa[j]; |
| in.seekSet(subtableOffset + so + lo); |
| // read ligature glyph id |
| int lg = in.readTTFUShort(); |
| // read ligature (input) component count |
| int nc = in.readTTFUShort(); |
| int[] ca = new int [ nc - 1 ]; |
| // read ligature (input) component glyph ids |
| for (int k = 0; k < nc - 1; k++) { |
| ca[k] = in.readTTFUShort(); |
| } |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " ligature substitution ligature set[" + i + "]: ligature(" + lg + "), components: " + toString(ca)); |
| } |
| ligs.add(new GlyphSubstitutionTable.Ligature(lg, ca)); |
| } |
| seEntries.add(new GlyphSubstitutionTable.LigatureSet(ligs)); |
| } |
| } |
| |
| private int readLigatureSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read substitution subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readLigatureSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported ligature substitution subtable format: " + sf); |
| } |
| return sf; |
| } |
| |
| private GlyphTable.RuleLookup[] readRuleLookups(int numLookups, String header) throws IOException { |
| GlyphTable.RuleLookup[] la = new GlyphTable.RuleLookup [ numLookups ]; |
| for (int i = 0, n = numLookups; i < n; i++) { |
| int sequenceIndex = in.readTTFUShort(); |
| int lookupIndex = in.readTTFUShort(); |
| la [ i ] = new GlyphTable.RuleLookup(sequenceIndex, lookupIndex); |
| // dump info if debugging and header is non-null |
| if (log.isDebugEnabled() && (header != null)) { |
| log.debug(header + "lookup[" + i + "]: " + la[i]); |
| } |
| } |
| return la; |
| } |
| |
| private void readContextualSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read rule set count |
| int nrs = in.readTTFUShort(); |
| // read rule set offsets |
| int[] rsoa = new int [ nrs ]; |
| for (int i = 0; i < nrs; i++) { |
| rsoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyphs)"); |
| log.debug(tableTag + " contextual substitution coverage table offset: " + co); |
| log.debug(tableTag + " contextual substitution rule set count: " + nrs); |
| for (int i = 0; i < nrs; i++) { |
| log.debug(tableTag + " contextual substitution rule set offset[" + i + "]: " + rsoa[i]); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct; |
| if (co > 0) { |
| ct = readCoverageTable(tableTag + " contextual substitution coverage", subtableOffset + co); |
| } else { |
| ct = null; |
| } |
| // read rule sets |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ]; |
| String header = null; |
| for (int i = 0; i < nrs; i++) { |
| GlyphTable.RuleSet rs; |
| int rso = rsoa [ i ]; |
| if (rso > 0) { |
| // seek to rule set [ i ] |
| in.seekSet(subtableOffset + rso); |
| // read rule count |
| int nr = in.readTTFUShort(); |
| // read rule offsets |
| int[] roa = new int [ nr ]; |
| GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; |
| for (int j = 0; j < nr; j++) { |
| roa [ j ] = in.readTTFUShort(); |
| } |
| // read glyph sequence rules |
| for (int j = 0; j < nr; j++) { |
| GlyphTable.GlyphSequenceRule r; |
| int ro = roa [ j ]; |
| if (ro > 0) { |
| // seek to rule [ j ] |
| in.seekSet(subtableOffset + rso + ro); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| // read rule lookup count |
| int nl = in.readTTFUShort(); |
| // read glyphs |
| int[] glyphs = new int [ ng - 1 ]; |
| for (int k = 0, nk = glyphs.length; k < nk; k++) { |
| glyphs [ k ] = in.readTTFUShort(); |
| } |
| // read rule lookups |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); |
| r = new GlyphTable.GlyphSequenceRule(lookups, ng, glyphs); |
| } else { |
| r = null; |
| } |
| ra [ j ] = r; |
| } |
| rs = new GlyphTable.HomogeneousRuleSet(ra); |
| } else { |
| rs = null; |
| } |
| rsa [ i ] = rs; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add(rsa); |
| } |
| |
| private void readContextualSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read class def table offset |
| int cdo = in.readTTFUShort(); |
| // read class rule set count |
| int ngc = in.readTTFUShort(); |
| // read class rule set offsets |
| int[] csoa = new int [ ngc ]; |
| for (int i = 0; i < ngc; i++) { |
| csoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyph classes)"); |
| log.debug(tableTag + " contextual substitution coverage table offset: " + co); |
| log.debug(tableTag + " contextual substitution class set count: " + ngc); |
| for (int i = 0; i < ngc; i++) { |
| log.debug(tableTag + " contextual substitution class set offset[" + i + "]: " + csoa[i]); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct; |
| if (co > 0) { |
| ct = readCoverageTable(tableTag + " contextual substitution coverage", subtableOffset + co); |
| } else { |
| ct = null; |
| } |
| // read class definition table |
| GlyphClassTable cdt; |
| if (cdo > 0) { |
| cdt = readClassDefTable(tableTag + " contextual substitution class definition", subtableOffset + cdo); |
| } else { |
| cdt = null; |
| } |
| // read rule sets |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ]; |
| String header = null; |
| for (int i = 0; i < ngc; i++) { |
| int cso = csoa [ i ]; |
| GlyphTable.RuleSet rs; |
| if (cso > 0) { |
| // seek to rule set [ i ] |
| in.seekSet(subtableOffset + cso); |
| // read rule count |
| int nr = in.readTTFUShort(); |
| // read rule offsets |
| int[] roa = new int [ nr ]; |
| GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; |
| for (int j = 0; j < nr; j++) { |
| roa [ j ] = in.readTTFUShort(); |
| } |
| // read glyph sequence rules |
| for (int j = 0; j < nr; j++) { |
| int ro = roa [ j ]; |
| GlyphTable.ClassSequenceRule r; |
| if (ro > 0) { |
| // seek to rule [ j ] |
| in.seekSet(subtableOffset + cso + ro); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| // read rule lookup count |
| int nl = in.readTTFUShort(); |
| // read classes |
| int[] classes = new int [ ng - 1 ]; |
| for (int k = 0, nk = classes.length; k < nk; k++) { |
| classes [ k ] = in.readTTFUShort(); |
| } |
| // read rule lookups |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); |
| r = new GlyphTable.ClassSequenceRule(lookups, ng, classes); |
| } else { |
| assert ro > 0 : "unexpected null subclass rule offset"; |
| r = null; |
| } |
| ra [ j ] = r; |
| } |
| rs = new GlyphTable.HomogeneousRuleSet(ra); |
| } else { |
| rs = null; |
| } |
| rsa [ i ] = rs; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add(cdt); |
| seEntries.add(Integer.valueOf(ngc)); |
| seEntries.add(rsa); |
| } |
| |
| private void readContextualSubTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read glyph (input sequence length) count |
| int ng = in.readTTFUShort(); |
| // read substitution lookup count |
| int nl = in.readTTFUShort(); |
| // read glyph coverage offsets, one per glyph input sequence length count |
| int[] gcoa = new int [ ng ]; |
| for (int i = 0; i < ng; i++) { |
| gcoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyph sets)"); |
| log.debug(tableTag + " contextual substitution glyph input sequence length count: " + ng); |
| log.debug(tableTag + " contextual substitution lookup count: " + nl); |
| for (int i = 0; i < ng; i++) { |
| log.debug(tableTag + " contextual substitution coverage table offset[" + i + "]: " + gcoa[i]); |
| } |
| } |
| // read coverage tables |
| GlyphCoverageTable[] gca = new GlyphCoverageTable [ ng ]; |
| for (int i = 0; i < ng; i++) { |
| int gco = gcoa [ i ]; |
| GlyphCoverageTable gct; |
| if (gco > 0) { |
| gct = readCoverageTable(tableTag + " contextual substitution coverage[" + i + "]", subtableOffset + gco); |
| } else { |
| gct = null; |
| } |
| gca [ i ] = gct; |
| } |
| // read rule lookups |
| String header = null; |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual substitution lookups: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); |
| // construct rule, rule set, and rule set array |
| GlyphTable.Rule r = new GlyphTable.CoverageSequenceRule(lookups, ng, gca); |
| GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet(new GlyphTable.Rule[] {r}); |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs}; |
| // store results |
| assert (gca != null) && (gca.length > 0); |
| seMapping = gca[0]; |
| seEntries.add(rsa); |
| } |
| |
| private int readContextualSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read substitution subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readContextualSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); |
| } else if (sf == 2) { |
| readContextualSubTableFormat2(lookupType, lookupFlags, subtableOffset, sf); |
| } else if (sf == 3) { |
| readContextualSubTableFormat3(lookupType, lookupFlags, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported contextual substitution subtable format: " + sf); |
| } |
| return sf; |
| } |
| |
| private void readChainedContextualSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read rule set count |
| int nrs = in.readTTFUShort(); |
| // read rule set offsets |
| int[] rsoa = new int [ nrs ]; |
| for (int i = 0; i < nrs; i++) { |
| rsoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyphs)"); |
| log.debug(tableTag + " chained contextual substitution coverage table offset: " + co); |
| log.debug(tableTag + " chained contextual substitution rule set count: " + nrs); |
| for (int i = 0; i < nrs; i++) { |
| log.debug(tableTag + " chained contextual substitution rule set offset[" + i + "]: " + rsoa[i]); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct; |
| if (co > 0) { |
| ct = readCoverageTable(tableTag + " chained contextual substitution coverage", subtableOffset + co); |
| } else { |
| ct = null; |
| } |
| // read rule sets |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ]; |
| String header = null; |
| for (int i = 0; i < nrs; i++) { |
| GlyphTable.RuleSet rs; |
| int rso = rsoa [ i ]; |
| if (rso > 0) { |
| // seek to rule set [ i ] |
| in.seekSet(subtableOffset + rso); |
| // read rule count |
| int nr = in.readTTFUShort(); |
| // read rule offsets |
| int[] roa = new int [ nr ]; |
| GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; |
| for (int j = 0; j < nr; j++) { |
| roa [ j ] = in.readTTFUShort(); |
| } |
| // read glyph sequence rules |
| for (int j = 0; j < nr; j++) { |
| GlyphTable.ChainedGlyphSequenceRule r; |
| int ro = roa [ j ]; |
| if (ro > 0) { |
| // seek to rule [ j ] |
| in.seekSet(subtableOffset + rso + ro); |
| // read backtrack glyph count |
| int nbg = in.readTTFUShort(); |
| // read backtrack glyphs |
| int[] backtrackGlyphs = new int [ nbg ]; |
| for (int k = 0, nk = backtrackGlyphs.length; k < nk; k++) { |
| backtrackGlyphs [ k ] = in.readTTFUShort(); |
| } |
| // read input glyph count |
| int nig = in.readTTFUShort(); |
| // read glyphs |
| int[] glyphs = new int [ nig - 1 ]; |
| for (int k = 0, nk = glyphs.length; k < nk; k++) { |
| glyphs [ k ] = in.readTTFUShort(); |
| } |
| // read lookahead glyph count |
| int nlg = in.readTTFUShort(); |
| // read lookahead glyphs |
| int[] lookaheadGlyphs = new int [ nlg ]; |
| for (int k = 0, nk = lookaheadGlyphs.length; k < nk; k++) { |
| lookaheadGlyphs [ k ] = in.readTTFUShort(); |
| } |
| // read rule lookup count |
| int nl = in.readTTFUShort(); |
| // read rule lookups |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); |
| r = new GlyphTable.ChainedGlyphSequenceRule(lookups, nig, glyphs, backtrackGlyphs, lookaheadGlyphs); |
| } else { |
| r = null; |
| } |
| ra [ j ] = r; |
| } |
| rs = new GlyphTable.HomogeneousRuleSet(ra); |
| } else { |
| rs = null; |
| } |
| rsa [ i ] = rs; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add(rsa); |
| } |
| |
| private void readChainedContextualSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read backtrack class def table offset |
| int bcdo = in.readTTFUShort(); |
| // read input class def table offset |
| int icdo = in.readTTFUShort(); |
| // read lookahead class def table offset |
| int lcdo = in.readTTFUShort(); |
| // read class set count |
| int ngc = in.readTTFUShort(); |
| // read class set offsets |
| int[] csoa = new int [ ngc ]; |
| for (int i = 0; i < ngc; i++) { |
| csoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyph classes)"); |
| log.debug(tableTag + " chained contextual substitution coverage table offset: " + co); |
| log.debug(tableTag + " chained contextual substitution class set count: " + ngc); |
| for (int i = 0; i < ngc; i++) { |
| log.debug(tableTag + " chained contextual substitution class set offset[" + i + "]: " + csoa[i]); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct; |
| if (co > 0) { |
| ct = readCoverageTable(tableTag + " chained contextual substitution coverage", subtableOffset + co); |
| } else { |
| ct = null; |
| } |
| // read backtrack class definition table |
| GlyphClassTable bcdt; |
| if (bcdo > 0) { |
| bcdt = readClassDefTable(tableTag + " contextual substitution backtrack class definition", subtableOffset + bcdo); |
| } else { |
| bcdt = null; |
| } |
| // read input class definition table |
| GlyphClassTable icdt; |
| if (icdo > 0) { |
| icdt = readClassDefTable(tableTag + " contextual substitution input class definition", subtableOffset + icdo); |
| } else { |
| icdt = null; |
| } |
| // read lookahead class definition table |
| GlyphClassTable lcdt; |
| if (lcdo > 0) { |
| lcdt = readClassDefTable(tableTag + " contextual substitution lookahead class definition", subtableOffset + lcdo); |
| } else { |
| lcdt = null; |
| } |
| // read rule sets |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ]; |
| String header = null; |
| for (int i = 0; i < ngc; i++) { |
| int cso = csoa [ i ]; |
| GlyphTable.RuleSet rs; |
| if (cso > 0) { |
| // seek to rule set [ i ] |
| in.seekSet(subtableOffset + cso); |
| // read rule count |
| int nr = in.readTTFUShort(); |
| // read rule offsets |
| int[] roa = new int [ nr ]; |
| GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; |
| for (int j = 0; j < nr; j++) { |
| roa [ j ] = in.readTTFUShort(); |
| } |
| // read glyph sequence rules |
| for (int j = 0; j < nr; j++) { |
| int ro = roa [ j ]; |
| GlyphTable.ChainedClassSequenceRule r; |
| if (ro > 0) { |
| // seek to rule [ j ] |
| in.seekSet(subtableOffset + cso + ro); |
| // read backtrack glyph class count |
| int nbc = in.readTTFUShort(); |
| // read backtrack glyph classes |
| int[] backtrackClasses = new int [ nbc ]; |
| for (int k = 0, nk = backtrackClasses.length; k < nk; k++) { |
| backtrackClasses [ k ] = in.readTTFUShort(); |
| } |
| // read input glyph class count |
| int nic = in.readTTFUShort(); |
| // read input glyph classes |
| int[] classes = new int [ nic - 1 ]; |
| for (int k = 0, nk = classes.length; k < nk; k++) { |
| classes [ k ] = in.readTTFUShort(); |
| } |
| // read lookahead glyph class count |
| int nlc = in.readTTFUShort(); |
| // read lookahead glyph classes |
| int[] lookaheadClasses = new int [ nlc ]; |
| for (int k = 0, nk = lookaheadClasses.length; k < nk; k++) { |
| lookaheadClasses [ k ] = in.readTTFUShort(); |
| } |
| // read rule lookup count |
| int nl = in.readTTFUShort(); |
| // read rule lookups |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); |
| r = new GlyphTable.ChainedClassSequenceRule(lookups, nic, classes, backtrackClasses, lookaheadClasses); |
| } else { |
| r = null; |
| } |
| ra [ j ] = r; |
| } |
| rs = new GlyphTable.HomogeneousRuleSet(ra); |
| } else { |
| rs = null; |
| } |
| rsa [ i ] = rs; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add(icdt); |
| seEntries.add(bcdt); |
| seEntries.add(lcdt); |
| seEntries.add(Integer.valueOf(ngc)); |
| seEntries.add(rsa); |
| } |
| |
| private void readChainedContextualSubTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read backtrack glyph count |
| int nbg = in.readTTFUShort(); |
| // read backtrack glyph coverage offsets |
| int[] bgcoa = new int [ nbg ]; |
| for (int i = 0; i < nbg; i++) { |
| bgcoa [ i ] = in.readTTFUShort(); |
| } |
| // read input glyph count |
| int nig = in.readTTFUShort(); |
| // read input glyph coverage offsets |
| int[] igcoa = new int [ nig ]; |
| for (int i = 0; i < nig; i++) { |
| igcoa [ i ] = in.readTTFUShort(); |
| } |
| // read lookahead glyph count |
| int nlg = in.readTTFUShort(); |
| // read lookahead glyph coverage offsets |
| int[] lgcoa = new int [ nlg ]; |
| for (int i = 0; i < nlg; i++) { |
| lgcoa [ i ] = in.readTTFUShort(); |
| } |
| // read substitution lookup count |
| int nl = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyph sets)"); |
| log.debug(tableTag + " chained contextual substitution backtrack glyph count: " + nbg); |
| for (int i = 0; i < nbg; i++) { |
| log.debug(tableTag + " chained contextual substitution backtrack coverage table offset[" + i + "]: " + bgcoa[i]); |
| } |
| log.debug(tableTag + " chained contextual substitution input glyph count: " + nig); |
| for (int i = 0; i < nig; i++) { |
| log.debug(tableTag + " chained contextual substitution input coverage table offset[" + i + "]: " + igcoa[i]); |
| } |
| log.debug(tableTag + " chained contextual substitution lookahead glyph count: " + nlg); |
| for (int i = 0; i < nlg; i++) { |
| log.debug(tableTag + " chained contextual substitution lookahead coverage table offset[" + i + "]: " + lgcoa[i]); |
| } |
| log.debug(tableTag + " chained contextual substitution lookup count: " + nl); |
| } |
| // read backtrack coverage tables |
| GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg]; |
| for (int i = 0; i < nbg; i++) { |
| int bgco = bgcoa [ i ]; |
| GlyphCoverageTable bgct; |
| if (bgco > 0) { |
| bgct = readCoverageTable(tableTag + " chained contextual substitution backtrack coverage[" + i + "]", subtableOffset + bgco); |
| } else { |
| bgct = null; |
| } |
| bgca[i] = bgct; |
| } |
| // read input coverage tables |
| GlyphCoverageTable[] igca = new GlyphCoverageTable[nig]; |
| for (int i = 0; i < nig; i++) { |
| int igco = igcoa [ i ]; |
| GlyphCoverageTable igct; |
| if (igco > 0) { |
| igct = readCoverageTable(tableTag + " chained contextual substitution input coverage[" + i + "]", subtableOffset + igco); |
| } else { |
| igct = null; |
| } |
| igca[i] = igct; |
| } |
| // read lookahead coverage tables |
| GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg]; |
| for (int i = 0; i < nlg; i++) { |
| int lgco = lgcoa [ i ]; |
| GlyphCoverageTable lgct; |
| if (lgco > 0) { |
| lgct = readCoverageTable(tableTag + " chained contextual substitution lookahead coverage[" + i + "]", subtableOffset + lgco); |
| } else { |
| lgct = null; |
| } |
| lgca[i] = lgct; |
| } |
| // read rule lookups |
| String header = null; |
| if (log.isDebugEnabled()) { |
| header = tableTag + " chained contextual substitution lookups: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); |
| // construct rule, rule set, and rule set array |
| GlyphTable.Rule r = new GlyphTable.ChainedCoverageSequenceRule(lookups, nig, igca, bgca, lgca); |
| GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet(new GlyphTable.Rule[] {r}); |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs}; |
| // store results |
| assert (igca != null) && (igca.length > 0); |
| seMapping = igca[0]; |
| seEntries.add(rsa); |
| } |
| |
| private int readChainedContextualSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read substitution subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readChainedContextualSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); |
| } else if (sf == 2) { |
| readChainedContextualSubTableFormat2(lookupType, lookupFlags, subtableOffset, sf); |
| } else if (sf == 3) { |
| readChainedContextualSubTableFormat3(lookupType, lookupFlags, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported chained contextual substitution subtable format: " + sf); |
| } |
| return sf; |
| } |
| |
| private void readExtensionSubTableFormat1(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read extension lookup type |
| int lt = in.readTTFUShort(); |
| // read extension offset |
| long eo = in.readTTFULong(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " extension substitution subtable format: " + subtableFormat); |
| log.debug(tableTag + " extension substitution lookup type: " + lt); |
| log.debug(tableTag + " extension substitution lookup table offset: " + eo); |
| } |
| // read referenced subtable from extended offset |
| readGSUBSubtable(lt, lookupFlags, lookupSequence, subtableSequence, subtableOffset + eo); |
| } |
| |
| private int readExtensionSubTable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read substitution subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readExtensionSubTableFormat1(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported extension substitution subtable format: " + sf); |
| } |
| return sf; |
| } |
| |
| private void readReverseChainedSingleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read backtrack glyph count |
| int nbg = in.readTTFUShort(); |
| // read backtrack glyph coverage offsets |
| int[] bgcoa = new int [ nbg ]; |
| for (int i = 0; i < nbg; i++) { |
| bgcoa [ i ] = in.readTTFUShort(); |
| } |
| // read lookahead glyph count |
| int nlg = in.readTTFUShort(); |
| // read backtrack glyph coverage offsets |
| int[] lgcoa = new int [ nlg ]; |
| for (int i = 0; i < nlg; i++) { |
| lgcoa [ i ] = in.readTTFUShort(); |
| } |
| // read substitution (output) glyph count |
| int ng = in.readTTFUShort(); |
| // read substitution (output) glyphs |
| int[] glyphs = new int [ ng ]; |
| for (int i = 0, n = ng; i < n; i++) { |
| glyphs [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " reverse chained contextual substitution format: " + subtableFormat); |
| log.debug(tableTag + " reverse chained contextual substitution coverage table offset: " + co); |
| log.debug(tableTag + " reverse chained contextual substitution backtrack glyph count: " + nbg); |
| for (int i = 0; i < nbg; i++) { |
| log.debug(tableTag + " reverse chained contextual substitution backtrack coverage table offset[" + i + "]: " + bgcoa[i]); |
| } |
| log.debug(tableTag + " reverse chained contextual substitution lookahead glyph count: " + nlg); |
| for (int i = 0; i < nlg; i++) { |
| log.debug(tableTag + " reverse chained contextual substitution lookahead coverage table offset[" + i + "]: " + lgcoa[i]); |
| } |
| log.debug(tableTag + " reverse chained contextual substitution glyphs: " + toString(glyphs)); |
| } |
| // read coverage table |
| GlyphCoverageTable ct = readCoverageTable(tableTag + " reverse chained contextual substitution coverage", subtableOffset + co); |
| // read backtrack coverage tables |
| GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg]; |
| for (int i = 0; i < nbg; i++) { |
| int bgco = bgcoa[i]; |
| GlyphCoverageTable bgct; |
| if (bgco > 0) { |
| bgct = readCoverageTable(tableTag + " reverse chained contextual substitution backtrack coverage[" + i + "]", subtableOffset + bgco); |
| } else { |
| bgct = null; |
| } |
| bgca[i] = bgct; |
| } |
| // read lookahead coverage tables |
| GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg]; |
| for (int i = 0; i < nlg; i++) { |
| int lgco = lgcoa[i]; |
| GlyphCoverageTable lgct; |
| if (lgco > 0) { |
| lgct = readCoverageTable(tableTag + " reverse chained contextual substitution lookahead coverage[" + i + "]", subtableOffset + lgco); |
| } else { |
| lgct = null; |
| } |
| lgca[i] = lgct; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add(bgca); |
| seEntries.add(lgca); |
| seEntries.add(glyphs); |
| } |
| |
| private int readReverseChainedSingleSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read substitution subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readReverseChainedSingleSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported reverse chained single substitution subtable format: " + sf); |
| } |
| return sf; |
| } |
| |
| private void readGSUBSubtable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException { |
| initATSubState(); |
| int subtableFormat = -1; |
| switch (lookupType) { |
| case GSUBLookupType.SINGLE: |
| subtableFormat = readSingleSubTable(lookupType, lookupFlags, subtableOffset); |
| break; |
| case GSUBLookupType.MULTIPLE: |
| subtableFormat = readMultipleSubTable(lookupType, lookupFlags, subtableOffset); |
| break; |
| case GSUBLookupType.ALTERNATE: |
| subtableFormat = readAlternateSubTable(lookupType, lookupFlags, subtableOffset); |
| break; |
| case GSUBLookupType.LIGATURE: |
| subtableFormat = readLigatureSubTable(lookupType, lookupFlags, subtableOffset); |
| break; |
| case GSUBLookupType.CONTEXTUAL: |
| subtableFormat = readContextualSubTable(lookupType, lookupFlags, subtableOffset); |
| break; |
| case GSUBLookupType.CHAINED_CONTEXTUAL: |
| subtableFormat = readChainedContextualSubTable(lookupType, lookupFlags, subtableOffset); |
| break; |
| case GSUBLookupType.REVERSE_CHAINED_SINGLE: |
| subtableFormat = readReverseChainedSingleSubTable(lookupType, lookupFlags, subtableOffset); |
| break; |
| case GSUBLookupType.EXTENSION: |
| subtableFormat = readExtensionSubTable(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset); |
| break; |
| default: |
| break; |
| } |
| extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableFormat); |
| resetATSubState(); |
| } |
| |
| private GlyphPositioningTable.DeviceTable readPosDeviceTable(long subtableOffset, long deviceTableOffset) throws IOException { |
| long cp = in.getCurrentPos(); |
| in.seekSet(subtableOffset + deviceTableOffset); |
| // read start size |
| int ss = in.readTTFUShort(); |
| // read end size |
| int es = in.readTTFUShort(); |
| // read delta format |
| int df = in.readTTFUShort(); |
| int s1; |
| int m1; |
| int dm; |
| int dd; |
| int s2; |
| if (df == 1) { |
| s1 = 14; |
| m1 = 0x3; |
| dm = 1; |
| dd = 4; |
| s2 = 2; |
| } else if (df == 2) { |
| s1 = 12; |
| m1 = 0xF; |
| dm = 7; |
| dd = 16; |
| s2 = 4; |
| } else if (df == 3) { |
| s1 = 8; |
| m1 = 0xFF; |
| dm = 127; |
| dd = 256; |
| s2 = 8; |
| } else { |
| log.debug("unsupported device table delta format: " + df + ", ignoring device table"); |
| return null; |
| } |
| // read deltas |
| int n = (es - ss) + 1; |
| if (n < 0) { |
| log.debug("invalid device table delta count: " + n + ", ignoring device table"); |
| return null; |
| } |
| int[] da = new int [ n ]; |
| for (int i = 0; (i < n) && (s2 > 0);) { |
| int p = in.readTTFUShort(); |
| for (int j = 0, k = 16 / s2; j < k; j++) { |
| int d = (p >> s1) & m1; |
| if (d > dm) { |
| d -= dd; |
| } |
| if (i < n) { |
| da [ i++ ] = d; |
| } else { |
| break; |
| } |
| p <<= s2; |
| } |
| } |
| in.seekSet(cp); |
| return new GlyphPositioningTable.DeviceTable(ss, es, da); |
| } |
| |
| private GlyphPositioningTable.Value readPosValue(long subtableOffset, int valueFormat) throws IOException { |
| // XPlacement |
| int xp; |
| if ((valueFormat & GlyphPositioningTable.Value.X_PLACEMENT) != 0) { |
| xp = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); |
| } else { |
| xp = 0; |
| } |
| // YPlacement |
| int yp; |
| if ((valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT) != 0) { |
| yp = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); |
| } else { |
| yp = 0; |
| } |
| // XAdvance |
| int xa; |
| if ((valueFormat & GlyphPositioningTable.Value.X_ADVANCE) != 0) { |
| xa = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); |
| } else { |
| xa = 0; |
| } |
| // YAdvance |
| int ya; |
| if ((valueFormat & GlyphPositioningTable.Value.Y_ADVANCE) != 0) { |
| ya = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); |
| } else { |
| ya = 0; |
| } |
| // XPlaDevice |
| GlyphPositioningTable.DeviceTable xpd; |
| if ((valueFormat & GlyphPositioningTable.Value.X_PLACEMENT_DEVICE) != 0) { |
| int xpdo = in.readTTFUShort(); |
| xpd = readPosDeviceTable(subtableOffset, xpdo); |
| } else { |
| xpd = null; |
| } |
| // YPlaDevice |
| GlyphPositioningTable.DeviceTable ypd; |
| if ((valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT_DEVICE) != 0) { |
| int ypdo = in.readTTFUShort(); |
| ypd = readPosDeviceTable(subtableOffset, ypdo); |
| } else { |
| ypd = null; |
| } |
| // XAdvDevice |
| GlyphPositioningTable.DeviceTable xad; |
| if ((valueFormat & GlyphPositioningTable.Value.X_ADVANCE_DEVICE) != 0) { |
| int xado = in.readTTFUShort(); |
| xad = readPosDeviceTable(subtableOffset, xado); |
| } else { |
| xad = null; |
| } |
| // YAdvDevice |
| GlyphPositioningTable.DeviceTable yad; |
| if ((valueFormat & GlyphPositioningTable.Value.Y_ADVANCE_DEVICE) != 0) { |
| int yado = in.readTTFUShort(); |
| yad = readPosDeviceTable(subtableOffset, yado); |
| } else { |
| yad = null; |
| } |
| return new GlyphPositioningTable.Value(xp, yp, xa, ya, xpd, ypd, xad, yad); |
| } |
| |
| private void readSinglePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read value format |
| int vf = in.readTTFUShort(); |
| // read value |
| GlyphPositioningTable.Value v = readPosValue(subtableOffset, vf); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " single positioning subtable format: " + subtableFormat + " (delta)"); |
| log.debug(tableTag + " single positioning coverage table offset: " + co); |
| log.debug(tableTag + " single positioning value: " + v); |
| } |
| // read coverage table |
| GlyphCoverageTable ct = readCoverageTable(tableTag + " single positioning coverage", subtableOffset + co); |
| // store results |
| seMapping = ct; |
| seEntries.add(v); |
| } |
| |
| private void readSinglePosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read value format |
| int vf = in.readTTFUShort(); |
| // read value count |
| int nv = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " single positioning subtable format: " + subtableFormat + " (mapped)"); |
| log.debug(tableTag + " single positioning coverage table offset: " + co); |
| log.debug(tableTag + " single positioning value count: " + nv); |
| } |
| // read coverage table |
| GlyphCoverageTable ct = readCoverageTable(tableTag + " single positioning coverage", subtableOffset + co); |
| // read positioning values |
| GlyphPositioningTable.Value[] pva = new GlyphPositioningTable.Value[nv]; |
| for (int i = 0, n = nv; i < n; i++) { |
| GlyphPositioningTable.Value pv = readPosValue(subtableOffset, vf); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " single positioning value[" + i + "]: " + pv); |
| } |
| pva[i] = pv; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add(pva); |
| } |
| |
| private int readSinglePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positionining subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readSinglePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); |
| } else if (sf == 2) { |
| readSinglePosTableFormat2(lookupType, lookupFlags, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported single positioning subtable format: " + sf); |
| } |
| return sf; |
| } |
| |
| private GlyphPositioningTable.PairValues readPosPairValues(long subtableOffset, boolean hasGlyph, int vf1, int vf2) throws IOException { |
| // read glyph (if present) |
| int glyph; |
| if (hasGlyph) { |
| glyph = in.readTTFUShort(); |
| } else { |
| glyph = 0; |
| } |
| // read first value (if present) |
| GlyphPositioningTable.Value v1; |
| if (vf1 != 0) { |
| v1 = readPosValue(subtableOffset, vf1); |
| } else { |
| v1 = null; |
| } |
| // read second value (if present) |
| GlyphPositioningTable.Value v2; |
| if (vf2 != 0) { |
| v2 = readPosValue(subtableOffset, vf2); |
| } else { |
| v2 = null; |
| } |
| return new GlyphPositioningTable.PairValues(glyph, v1, v2); |
| } |
| |
| private GlyphPositioningTable.PairValues[] readPosPairSetTable(long subtableOffset, int pairSetTableOffset, int vf1, int vf2) throws IOException { |
| String tableTag = "GPOS"; |
| long cp = in.getCurrentPos(); |
| in.seekSet(subtableOffset + pairSetTableOffset); |
| // read pair values count |
| int npv = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " pair set table offset: " + pairSetTableOffset); |
| log.debug(tableTag + " pair set table values count: " + npv); |
| } |
| // read pair values |
| GlyphPositioningTable.PairValues[] pva = new GlyphPositioningTable.PairValues [ npv ]; |
| for (int i = 0, n = npv; i < n; i++) { |
| GlyphPositioningTable.PairValues pv = readPosPairValues(subtableOffset, true, vf1, vf2); |
| pva [ i ] = pv; |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " pair set table value[" + i + "]: " + pv); |
| } |
| } |
| in.seekSet(cp); |
| return pva; |
| } |
| |
| private void readPairPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read value format for first glyph |
| int vf1 = in.readTTFUShort(); |
| // read value format for second glyph |
| int vf2 = in.readTTFUShort(); |
| // read number (count) of pair sets |
| int nps = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " pair positioning subtable format: " + subtableFormat + " (glyphs)"); |
| log.debug(tableTag + " pair positioning coverage table offset: " + co); |
| log.debug(tableTag + " pair positioning value format #1: " + vf1); |
| log.debug(tableTag + " pair positioning value format #2: " + vf2); |
| } |
| // read coverage table |
| GlyphCoverageTable ct = readCoverageTable(tableTag + " pair positioning coverage", subtableOffset + co); |
| // read pair value matrix |
| GlyphPositioningTable.PairValues[][] pvm = new GlyphPositioningTable.PairValues [ nps ][]; |
| for (int i = 0, n = nps; i < n; i++) { |
| // read pair set offset |
| int pso = in.readTTFUShort(); |
| // read pair set table at offset |
| pvm [ i ] = readPosPairSetTable(subtableOffset, pso, vf1, vf2); |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add(pvm); |
| } |
| |
| private void readPairPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read value format for first glyph |
| int vf1 = in.readTTFUShort(); |
| // read value format for second glyph |
| int vf2 = in.readTTFUShort(); |
| // read class def 1 offset |
| int cd1o = in.readTTFUShort(); |
| // read class def 2 offset |
| int cd2o = in.readTTFUShort(); |
| // read number (count) of classes in class def 1 table |
| int nc1 = in.readTTFUShort(); |
| // read number (count) of classes in class def 2 table |
| int nc2 = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " pair positioning subtable format: " + subtableFormat + " (glyph classes)"); |
| log.debug(tableTag + " pair positioning coverage table offset: " + co); |
| log.debug(tableTag + " pair positioning value format #1: " + vf1); |
| log.debug(tableTag + " pair positioning value format #2: " + vf2); |
| log.debug(tableTag + " pair positioning class def table #1 offset: " + cd1o); |
| log.debug(tableTag + " pair positioning class def table #2 offset: " + cd2o); |
| log.debug(tableTag + " pair positioning class #1 count: " + nc1); |
| log.debug(tableTag + " pair positioning class #2 count: " + nc2); |
| } |
| // read coverage table |
| GlyphCoverageTable ct = readCoverageTable(tableTag + " pair positioning coverage", subtableOffset + co); |
| // read class definition table #1 |
| GlyphClassTable cdt1 = readClassDefTable(tableTag + " pair positioning class definition #1", subtableOffset + cd1o); |
| // read class definition table #2 |
| GlyphClassTable cdt2 = readClassDefTable(tableTag + " pair positioning class definition #2", subtableOffset + cd2o); |
| // read pair value matrix |
| GlyphPositioningTable.PairValues[][] pvm = new GlyphPositioningTable.PairValues [ nc1 ] [ nc2 ]; |
| for (int i = 0; i < nc1; i++) { |
| for (int j = 0; j < nc2; j++) { |
| GlyphPositioningTable.PairValues pv = readPosPairValues(subtableOffset, false, vf1, vf2); |
| pvm [ i ] [ j ] = pv; |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " pair set table value[" + i + "][" + j + "]: " + pv); |
| } |
| } |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add(cdt1); |
| seEntries.add(cdt2); |
| seEntries.add(Integer.valueOf(nc1)); |
| seEntries.add(Integer.valueOf(nc2)); |
| seEntries.add(pvm); |
| } |
| |
| private int readPairPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positioning subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readPairPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); |
| } else if (sf == 2) { |
| readPairPosTableFormat2(lookupType, lookupFlags, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported pair positioning subtable format: " + sf); |
| } |
| return sf; |
| } |
| |
| private GlyphPositioningTable.Anchor readPosAnchor(long anchorTableOffset) throws IOException { |
| GlyphPositioningTable.Anchor a; |
| long cp = in.getCurrentPos(); |
| in.seekSet(anchorTableOffset); |
| // read anchor table format |
| int af = in.readTTFUShort(); |
| if (af == 1) { |
| // read x coordinate |
| int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); |
| // read y coordinate |
| int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); |
| a = new GlyphPositioningTable.Anchor(x, y); |
| } else if (af == 2) { |
| // read x coordinate |
| int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); |
| // read y coordinate |
| int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); |
| // read anchor point index |
| int ap = in.readTTFUShort(); |
| a = new GlyphPositioningTable.Anchor(x, y, ap); |
| } else if (af == 3) { |
| // read x coordinate |
| int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); |
| // read y coordinate |
| int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); |
| // read x device table offset |
| int xdo = in.readTTFUShort(); |
| // read y device table offset |
| int ydo = in.readTTFUShort(); |
| // read x device table (if present) |
| GlyphPositioningTable.DeviceTable xd; |
| if (xdo != 0) { |
| xd = readPosDeviceTable(cp, xdo); |
| } else { |
| xd = null; |
| } |
| // read y device table (if present) |
| GlyphPositioningTable.DeviceTable yd; |
| if (ydo != 0) { |
| yd = readPosDeviceTable(cp, ydo); |
| } else { |
| yd = null; |
| } |
| a = new GlyphPositioningTable.Anchor(x, y, xd, yd); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported positioning anchor format: " + af); |
| } |
| in.seekSet(cp); |
| return a; |
| } |
| |
| private void readCursivePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read entry/exit count |
| int ec = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " cursive positioning subtable format: " + subtableFormat); |
| log.debug(tableTag + " cursive positioning coverage table offset: " + co); |
| log.debug(tableTag + " cursive positioning entry/exit count: " + ec); |
| } |
| // read coverage table |
| GlyphCoverageTable ct = readCoverageTable(tableTag + " cursive positioning coverage", subtableOffset + co); |
| // read entry/exit records |
| GlyphPositioningTable.Anchor[] aa = new GlyphPositioningTable.Anchor [ ec * 2 ]; |
| for (int i = 0, n = ec; i < n; i++) { |
| // read entry anchor offset |
| int eno = in.readTTFUShort(); |
| // read exit anchor offset |
| int exo = in.readTTFUShort(); |
| // read entry anchor |
| GlyphPositioningTable.Anchor ena; |
| if (eno > 0) { |
| ena = readPosAnchor(subtableOffset + eno); |
| } else { |
| ena = null; |
| } |
| // read exit anchor |
| GlyphPositioningTable.Anchor exa; |
| if (exo > 0) { |
| exa = readPosAnchor(subtableOffset + exo); |
| } else { |
| exa = null; |
| } |
| aa [ (i * 2) + 0 ] = ena; |
| aa [ (i * 2) + 1 ] = exa; |
| if (log.isDebugEnabled()) { |
| if (ena != null) { |
| log.debug(tableTag + " cursive entry anchor [" + i + "]: " + ena); |
| } |
| if (exa != null) { |
| log.debug(tableTag + " cursive exit anchor [" + i + "]: " + exa); |
| } |
| } |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add(aa); |
| } |
| |
| private int readCursivePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positioning subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readCursivePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported cursive positioning subtable format: " + sf); |
| } |
| return sf; |
| } |
| |
| private void readMarkToBasePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read mark coverage offset |
| int mco = in.readTTFUShort(); |
| // read base coverage offset |
| int bco = in.readTTFUShort(); |
| // read mark class count |
| int nmc = in.readTTFUShort(); |
| // read mark array offset |
| int mao = in.readTTFUShort(); |
| // read base array offset |
| int bao = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-base positioning subtable format: " + subtableFormat); |
| log.debug(tableTag + " mark-to-base positioning mark coverage table offset: " + mco); |
| log.debug(tableTag + " mark-to-base positioning base coverage table offset: " + bco); |
| log.debug(tableTag + " mark-to-base positioning mark class count: " + nmc); |
| log.debug(tableTag + " mark-to-base positioning mark array offset: " + mao); |
| log.debug(tableTag + " mark-to-base positioning base array offset: " + bao); |
| } |
| // read mark coverage table |
| GlyphCoverageTable mct = readCoverageTable(tableTag + " mark-to-base positioning mark coverage", subtableOffset + mco); |
| // read base coverage table |
| GlyphCoverageTable bct = readCoverageTable(tableTag + " mark-to-base positioning base coverage", subtableOffset + bco); |
| // read mark anchor array |
| // seek to mark array |
| in.seekSet(subtableOffset + mao); |
| // read mark count |
| int nm = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-base positioning mark count: " + nm); |
| } |
| // read mark anchor array, where i:{0...markCount} |
| GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm ]; |
| for (int i = 0; i < nm; i++) { |
| // read mark class |
| int mc = in.readTTFUShort(); |
| // read mark anchor offset |
| int ao = in.readTTFUShort(); |
| GlyphPositioningTable.Anchor a; |
| if (ao > 0) { |
| a = readPosAnchor(subtableOffset + mao + ao); |
| } else { |
| a = null; |
| } |
| GlyphPositioningTable.MarkAnchor ma; |
| if (a != null) { |
| ma = new GlyphPositioningTable.MarkAnchor(mc, a); |
| } else { |
| ma = null; |
| } |
| maa [ i ] = ma; |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-base positioning mark anchor[" + i + "]: " + ma); |
| } |
| |
| } |
| // read base anchor matrix |
| // seek to base array |
| in.seekSet(subtableOffset + bao); |
| // read base count |
| int nb = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-base positioning base count: " + nb); |
| } |
| // read anchor matrix, where i:{0...baseCount - 1}, j:{0...markClassCount - 1} |
| GlyphPositioningTable.Anchor[][] bam = new GlyphPositioningTable.Anchor [ nb ] [ nmc ]; |
| for (int i = 0; i < nb; i++) { |
| for (int j = 0; j < nmc; j++) { |
| // read base anchor offset |
| int ao = in.readTTFUShort(); |
| GlyphPositioningTable.Anchor a; |
| if (ao > 0) { |
| a = readPosAnchor(subtableOffset + bao + ao); |
| } else { |
| a = null; |
| } |
| bam [ i ] [ j ] = a; |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-base positioning base anchor[" + i + "][" + j + "]: " + a); |
| } |
| } |
| } |
| // store results |
| seMapping = mct; |
| seEntries.add(bct); |
| seEntries.add(Integer.valueOf(nmc)); |
| seEntries.add(maa); |
| seEntries.add(bam); |
| } |
| |
| private int readMarkToBasePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positioning subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readMarkToBasePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported mark-to-base positioning subtable format: " + sf); |
| } |
| return sf; |
| } |
| |
| private void readMarkToLigaturePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read mark coverage offset |
| int mco = in.readTTFUShort(); |
| // read ligature coverage offset |
| int lco = in.readTTFUShort(); |
| // read mark class count |
| int nmc = in.readTTFUShort(); |
| // read mark array offset |
| int mao = in.readTTFUShort(); |
| // read ligature array offset |
| int lao = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-ligature positioning subtable format: " + subtableFormat); |
| log.debug(tableTag + " mark-to-ligature positioning mark coverage table offset: " + mco); |
| log.debug(tableTag + " mark-to-ligature positioning ligature coverage table offset: " + lco); |
| log.debug(tableTag + " mark-to-ligature positioning mark class count: " + nmc); |
| log.debug(tableTag + " mark-to-ligature positioning mark array offset: " + mao); |
| log.debug(tableTag + " mark-to-ligature positioning ligature array offset: " + lao); |
| } |
| // read mark coverage table |
| GlyphCoverageTable mct = readCoverageTable(tableTag + " mark-to-ligature positioning mark coverage", subtableOffset + mco); |
| // read ligature coverage table |
| GlyphCoverageTable lct = readCoverageTable(tableTag + " mark-to-ligature positioning ligature coverage", subtableOffset + lco); |
| // read mark anchor array |
| // seek to mark array |
| in.seekSet(subtableOffset + mao); |
| // read mark count |
| int nm = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-ligature positioning mark count: " + nm); |
| } |
| // read mark anchor array, where i:{0...markCount} |
| GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm ]; |
| for (int i = 0; i < nm; i++) { |
| // read mark class |
| int mc = in.readTTFUShort(); |
| // read mark anchor offset |
| int ao = in.readTTFUShort(); |
| GlyphPositioningTable.Anchor a; |
| if (ao > 0) { |
| a = readPosAnchor(subtableOffset + mao + ao); |
| } else { |
| a = null; |
| } |
| GlyphPositioningTable.MarkAnchor ma; |
| if (a != null) { |
| ma = new GlyphPositioningTable.MarkAnchor(mc, a); |
| } else { |
| ma = null; |
| } |
| maa [ i ] = ma; |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-ligature positioning mark anchor[" + i + "]: " + ma); |
| } |
| } |
| // read ligature anchor matrix |
| // seek to ligature array |
| in.seekSet(subtableOffset + lao); |
| // read ligature count |
| int nl = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-ligature positioning ligature count: " + nl); |
| } |
| // read ligature attach table offsets |
| int[] laoa = new int [ nl ]; |
| for (int i = 0; i < nl; i++) { |
| laoa [ i ] = in.readTTFUShort(); |
| } |
| // iterate over ligature attach tables, recording maximum component count |
| int mxc = 0; |
| for (int i = 0; i < nl; i++) { |
| int lato = laoa [ i ]; |
| in.seekSet(subtableOffset + lao + lato); |
| // read component count |
| int cc = in.readTTFUShort(); |
| if (cc > mxc) { |
| mxc = cc; |
| } |
| } |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-ligature positioning maximum component count: " + mxc); |
| } |
| // read anchor matrix, where i:{0...ligatureCount - 1}, j:{0...maxComponentCount - 1}, k:{0...markClassCount - 1} |
| GlyphPositioningTable.Anchor[][][] lam = new GlyphPositioningTable.Anchor [ nl ][][]; |
| for (int i = 0; i < nl; i++) { |
| int lato = laoa [ i ]; |
| // seek to ligature attach table for ligature[i] |
| in.seekSet(subtableOffset + lao + lato); |
| // read component count |
| int cc = in.readTTFUShort(); |
| GlyphPositioningTable.Anchor[][] lcm = new GlyphPositioningTable.Anchor [ cc ] [ nmc ]; |
| for (int j = 0; j < cc; j++) { |
| for (int k = 0; k < nmc; k++) { |
| // read ligature anchor offset |
| int ao = in.readTTFUShort(); |
| GlyphPositioningTable.Anchor a; |
| if (ao > 0) { |
| a = readPosAnchor(subtableOffset + lao + lato + ao); |
| } else { |
| a = null; |
| } |
| lcm [ j ] [ k ] = a; |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-ligature positioning ligature anchor[" + i + "][" + j + "][" + k + "]: " + a); |
| } |
| } |
| } |
| lam [ i ] = lcm; |
| } |
| // store results |
| seMapping = mct; |
| seEntries.add(lct); |
| seEntries.add(Integer.valueOf(nmc)); |
| seEntries.add(Integer.valueOf(mxc)); |
| seEntries.add(maa); |
| seEntries.add(lam); |
| } |
| |
| private int readMarkToLigaturePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positioning subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readMarkToLigaturePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported mark-to-ligature positioning subtable format: " + sf); |
| } |
| return sf; |
| } |
| |
| private void readMarkToMarkPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read mark #1 coverage offset |
| int m1co = in.readTTFUShort(); |
| // read mark #2 coverage offset |
| int m2co = in.readTTFUShort(); |
| // read mark class count |
| int nmc = in.readTTFUShort(); |
| // read mark #1 array offset |
| int m1ao = in.readTTFUShort(); |
| // read mark #2 array offset |
| int m2ao = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-mark positioning subtable format: " + subtableFormat); |
| log.debug(tableTag + " mark-to-mark positioning mark #1 coverage table offset: " + m1co); |
| log.debug(tableTag + " mark-to-mark positioning mark #2 coverage table offset: " + m2co); |
| log.debug(tableTag + " mark-to-mark positioning mark class count: " + nmc); |
| log.debug(tableTag + " mark-to-mark positioning mark #1 array offset: " + m1ao); |
| log.debug(tableTag + " mark-to-mark positioning mark #2 array offset: " + m2ao); |
| } |
| // read mark #1 coverage table |
| GlyphCoverageTable mct1 = readCoverageTable(tableTag + " mark-to-mark positioning mark #1 coverage", subtableOffset + m1co); |
| // read mark #2 coverage table |
| GlyphCoverageTable mct2 = readCoverageTable(tableTag + " mark-to-mark positioning mark #2 coverage", subtableOffset + m2co); |
| // read mark #1 anchor array |
| // seek to mark array |
| in.seekSet(subtableOffset + m1ao); |
| // read mark count |
| int nm1 = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-mark positioning mark #1 count: " + nm1); |
| } |
| // read mark anchor array, where i:{0...mark1Count} |
| GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm1 ]; |
| for (int i = 0; i < nm1; i++) { |
| // read mark class |
| int mc = in.readTTFUShort(); |
| // read mark anchor offset |
| int ao = in.readTTFUShort(); |
| GlyphPositioningTable.Anchor a; |
| if (ao > 0) { |
| a = readPosAnchor(subtableOffset + m1ao + ao); |
| } else { |
| a = null; |
| } |
| GlyphPositioningTable.MarkAnchor ma; |
| if (a != null) { |
| ma = new GlyphPositioningTable.MarkAnchor(mc, a); |
| } else { |
| ma = null; |
| } |
| maa [ i ] = ma; |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-mark positioning mark #1 anchor[" + i + "]: " + ma); |
| } |
| } |
| // read mark #2 anchor matrix |
| // seek to mark #2 array |
| in.seekSet(subtableOffset + m2ao); |
| // read mark #2 count |
| int nm2 = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-mark positioning mark #2 count: " + nm2); |
| } |
| // read anchor matrix, where i:{0...mark2Count - 1}, j:{0...markClassCount - 1} |
| GlyphPositioningTable.Anchor[][] mam = new GlyphPositioningTable.Anchor [ nm2 ] [ nmc ]; |
| for (int i = 0; i < nm2; i++) { |
| for (int j = 0; j < nmc; j++) { |
| // read mark anchor offset |
| int ao = in.readTTFUShort(); |
| GlyphPositioningTable.Anchor a; |
| if (ao > 0) { |
| a = readPosAnchor(subtableOffset + m2ao + ao); |
| } else { |
| a = null; |
| } |
| mam [ i ] [ j ] = a; |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-mark positioning mark #2 anchor[" + i + "][" + j + "]: " + a); |
| } |
| } |
| } |
| // store results |
| seMapping = mct1; |
| seEntries.add(mct2); |
| seEntries.add(Integer.valueOf(nmc)); |
| seEntries.add(maa); |
| seEntries.add(mam); |
| } |
| |
| private int readMarkToMarkPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positioning subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readMarkToMarkPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported mark-to-mark positioning subtable format: " + sf); |
| } |
| return sf; |
| } |
| |
| private void readContextualPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read rule set count |
| int nrs = in.readTTFUShort(); |
| // read rule set offsets |
| int[] rsoa = new int [ nrs ]; |
| for (int i = 0; i < nrs; i++) { |
| rsoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyphs)"); |
| log.debug(tableTag + " contextual positioning coverage table offset: " + co); |
| log.debug(tableTag + " contextual positioning rule set count: " + nrs); |
| for (int i = 0; i < nrs; i++) { |
| log.debug(tableTag + " contextual positioning rule set offset[" + i + "]: " + rsoa[i]); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct; |
| if (co > 0) { |
| ct = readCoverageTable(tableTag + " contextual positioning coverage", subtableOffset + co); |
| } else { |
| ct = null; |
| } |
| // read rule sets |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ]; |
| String header = null; |
| for (int i = 0; i < nrs; i++) { |
| GlyphTable.RuleSet rs; |
| int rso = rsoa [ i ]; |
| if (rso > 0) { |
| // seek to rule set [ i ] |
| in.seekSet(subtableOffset + rso); |
| // read rule count |
| int nr = in.readTTFUShort(); |
| // read rule offsets |
| int[] roa = new int [ nr ]; |
| GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; |
| for (int j = 0; j < nr; j++) { |
| roa [ j ] = in.readTTFUShort(); |
| } |
| // read glyph sequence rules |
| for (int j = 0; j < nr; j++) { |
| GlyphTable.GlyphSequenceRule r; |
| int ro = roa [ j ]; |
| if (ro > 0) { |
| // seek to rule [ j ] |
| in.seekSet(subtableOffset + rso + ro); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| // read rule lookup count |
| int nl = in.readTTFUShort(); |
| // read glyphs |
| int[] glyphs = new int [ ng - 1 ]; |
| for (int k = 0, nk = glyphs.length; k < nk; k++) { |
| glyphs [ k ] = in.readTTFUShort(); |
| } |
| // read rule lookups |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); |
| r = new GlyphTable.GlyphSequenceRule(lookups, ng, glyphs); |
| } else { |
| r = null; |
| } |
| ra [ j ] = r; |
| } |
| rs = new GlyphTable.HomogeneousRuleSet(ra); |
| } else { |
| rs = null; |
| } |
| rsa [ i ] = rs; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add(rsa); |
| } |
| |
| private void readContextualPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read class def table offset |
| int cdo = in.readTTFUShort(); |
| // read class rule set count |
| int ngc = in.readTTFUShort(); |
| // read class rule set offsets |
| int[] csoa = new int [ ngc ]; |
| for (int i = 0; i < ngc; i++) { |
| csoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyph classes)"); |
| log.debug(tableTag + " contextual positioning coverage table offset: " + co); |
| log.debug(tableTag + " contextual positioning class set count: " + ngc); |
| for (int i = 0; i < ngc; i++) { |
| log.debug(tableTag + " contextual positioning class set offset[" + i + "]: " + csoa[i]); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct; |
| if (co > 0) { |
| ct = readCoverageTable(tableTag + " contextual positioning coverage", subtableOffset + co); |
| } else { |
| ct = null; |
| } |
| // read class definition table |
| GlyphClassTable cdt; |
| if (cdo > 0) { |
| cdt = readClassDefTable(tableTag + " contextual positioning class definition", subtableOffset + cdo); |
| } else { |
| cdt = null; |
| } |
| // read rule sets |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ]; |
| String header = null; |
| for (int i = 0; i < ngc; i++) { |
| int cso = csoa [ i ]; |
| GlyphTable.RuleSet rs; |
| if (cso > 0) { |
| // seek to rule set [ i ] |
| in.seekSet(subtableOffset + cso); |
| // read rule count |
| int nr = in.readTTFUShort(); |
| // read rule offsets |
| int[] roa = new int [ nr ]; |
| GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; |
| for (int j = 0; j < nr; j++) { |
| roa [ j ] = in.readTTFUShort(); |
| } |
| // read glyph sequence rules |
| for (int j = 0; j < nr; j++) { |
| int ro = roa [ j ]; |
| GlyphTable.ClassSequenceRule r; |
| if (ro > 0) { |
| // seek to rule [ j ] |
| in.seekSet(subtableOffset + cso + ro); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| // read rule lookup count |
| int nl = in.readTTFUShort(); |
| // read classes |
| int[] classes = new int [ ng - 1 ]; |
| for (int k = 0, nk = classes.length; k < nk; k++) { |
| classes [ k ] = in.readTTFUShort(); |
| } |
| // read rule lookups |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); |
| r = new GlyphTable.ClassSequenceRule(lookups, ng, classes); |
| } else { |
| r = null; |
| } |
| ra [ j ] = r; |
| } |
| rs = new GlyphTable.HomogeneousRuleSet(ra); |
| } else { |
| rs = null; |
| } |
| rsa [ i ] = rs; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add(cdt); |
| seEntries.add(Integer.valueOf(ngc)); |
| seEntries.add(rsa); |
| } |
| |
| private void readContextualPosTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read glyph (input sequence length) count |
| int ng = in.readTTFUShort(); |
| // read positioning lookup count |
| int nl = in.readTTFUShort(); |
| // read glyph coverage offsets, one per glyph input sequence length count |
| int[] gcoa = new int [ ng ]; |
| for (int i = 0; i < ng; i++) { |
| gcoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyph sets)"); |
| log.debug(tableTag + " contextual positioning glyph input sequence length count: " + ng); |
| log.debug(tableTag + " contextual positioning lookup count: " + nl); |
| for (int i = 0; i < ng; i++) { |
| log.debug(tableTag + " contextual positioning coverage table offset[" + i + "]: " + gcoa[i]); |
| } |
| } |
| // read coverage tables |
| GlyphCoverageTable[] gca = new GlyphCoverageTable [ ng ]; |
| for (int i = 0; i < ng; i++) { |
| int gco = gcoa [ i ]; |
| GlyphCoverageTable gct; |
| if (gco > 0) { |
| gct = readCoverageTable(tableTag + " contextual positioning coverage[" + i + "]", subtableOffset + gcoa[i]); |
| } else { |
| gct = null; |
| } |
| gca [ i ] = gct; |
| } |
| // read rule lookups |
| String header = null; |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual positioning lookups: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); |
| // construct rule, rule set, and rule set array |
| GlyphTable.Rule r = new GlyphTable.CoverageSequenceRule(lookups, ng, gca); |
| GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet(new GlyphTable.Rule[] {r}); |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs}; |
| // store results |
| assert (gca != null) && (gca.length > 0); |
| seMapping = gca[0]; |
| seEntries.add(rsa); |
| } |
| |
| private int readContextualPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positioning subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readContextualPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); |
| } else if (sf == 2) { |
| readContextualPosTableFormat2(lookupType, lookupFlags, subtableOffset, sf); |
| } else if (sf == 3) { |
| readContextualPosTableFormat3(lookupType, lookupFlags, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported contextual positioning subtable format: " + sf); |
| } |
| return sf; |
| } |
| |
| private void readChainedContextualPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read rule set count |
| int nrs = in.readTTFUShort(); |
| // read rule set offsets |
| int[] rsoa = new int [ nrs ]; |
| for (int i = 0; i < nrs; i++) { |
| rsoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyphs)"); |
| log.debug(tableTag + " chained contextual positioning coverage table offset: " + co); |
| log.debug(tableTag + " chained contextual positioning rule set count: " + nrs); |
| for (int i = 0; i < nrs; i++) { |
| log.debug(tableTag + " chained contextual positioning rule set offset[" + i + "]: " + rsoa[i]); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct; |
| if (co > 0) { |
| ct = readCoverageTable(tableTag + " chained contextual positioning coverage", subtableOffset + co); |
| } else { |
| ct = null; |
| } |
| // read rule sets |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ]; |
| String header = null; |
| for (int i = 0; i < nrs; i++) { |
| GlyphTable.RuleSet rs; |
| int rso = rsoa [ i ]; |
| if (rso > 0) { |
| // seek to rule set [ i ] |
| in.seekSet(subtableOffset + rso); |
| // read rule count |
| int nr = in.readTTFUShort(); |
| // read rule offsets |
| int[] roa = new int [ nr ]; |
| GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; |
| for (int j = 0; j < nr; j++) { |
| roa [ j ] = in.readTTFUShort(); |
| } |
| // read glyph sequence rules |
| for (int j = 0; j < nr; j++) { |
| GlyphTable.ChainedGlyphSequenceRule r; |
| int ro = roa [ j ]; |
| if (ro > 0) { |
| // seek to rule [ j ] |
| in.seekSet(subtableOffset + rso + ro); |
| // read backtrack glyph count |
| int nbg = in.readTTFUShort(); |
| // read backtrack glyphs |
| int[] backtrackGlyphs = new int [ nbg ]; |
| for (int k = 0, nk = backtrackGlyphs.length; k < nk; k++) { |
| backtrackGlyphs [ k ] = in.readTTFUShort(); |
| } |
| // read input glyph count |
| int nig = in.readTTFUShort(); |
| // read glyphs |
| int[] glyphs = new int [ nig - 1 ]; |
| for (int k = 0, nk = glyphs.length; k < nk; k++) { |
| glyphs [ k ] = in.readTTFUShort(); |
| } |
| // read lookahead glyph count |
| int nlg = in.readTTFUShort(); |
| // read lookahead glyphs |
| int[] lookaheadGlyphs = new int [ nlg ]; |
| for (int k = 0, nk = lookaheadGlyphs.length; k < nk; k++) { |
| lookaheadGlyphs [ k ] = in.readTTFUShort(); |
| } |
| // read rule lookup count |
| int nl = in.readTTFUShort(); |
| // read rule lookups |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); |
| r = new GlyphTable.ChainedGlyphSequenceRule(lookups, nig, glyphs, backtrackGlyphs, lookaheadGlyphs); |
| } else { |
| r = null; |
| } |
| ra [ j ] = r; |
| } |
| rs = new GlyphTable.HomogeneousRuleSet(ra); |
| } else { |
| rs = null; |
| } |
| rsa [ i ] = rs; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add(rsa); |
| } |
| |
| private void readChainedContextualPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read backtrack class def table offset |
| int bcdo = in.readTTFUShort(); |
| // read input class def table offset |
| int icdo = in.readTTFUShort(); |
| // read lookahead class def table offset |
| int lcdo = in.readTTFUShort(); |
| // read class set count |
| int ngc = in.readTTFUShort(); |
| // read class set offsets |
| int[] csoa = new int [ ngc ]; |
| for (int i = 0; i < ngc; i++) { |
| csoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyph classes)"); |
| log.debug(tableTag + " chained contextual positioning coverage table offset: " + co); |
| log.debug(tableTag + " chained contextual positioning class set count: " + ngc); |
| for (int i = 0; i < ngc; i++) { |
| log.debug(tableTag + " chained contextual positioning class set offset[" + i + "]: " + csoa[i]); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct; |
| if (co > 0) { |
| ct = readCoverageTable(tableTag + " chained contextual positioning coverage", subtableOffset + co); |
| } else { |
| ct = null; |
| } |
| // read backtrack class definition table |
| GlyphClassTable bcdt; |
| if (bcdo > 0) { |
| bcdt = readClassDefTable(tableTag + " contextual positioning backtrack class definition", subtableOffset + bcdo); |
| } else { |
| bcdt = null; |
| } |
| // read input class definition table |
| GlyphClassTable icdt; |
| if (icdo > 0) { |
| icdt = readClassDefTable(tableTag + " contextual positioning input class definition", subtableOffset + icdo); |
| } else { |
| icdt = null; |
| } |
| // read lookahead class definition table |
| GlyphClassTable lcdt; |
| if (lcdo > 0) { |
| lcdt = readClassDefTable(tableTag + " contextual positioning lookahead class definition", subtableOffset + lcdo); |
| } else { |
| lcdt = null; |
| } |
| // read rule sets |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ]; |
| String header = null; |
| for (int i = 0; i < ngc; i++) { |
| int cso = csoa [ i ]; |
| GlyphTable.RuleSet rs; |
| if (cso > 0) { |
| // seek to rule set [ i ] |
| in.seekSet(subtableOffset + cso); |
| // read rule count |
| int nr = in.readTTFUShort(); |
| // read rule offsets |
| int[] roa = new int [ nr ]; |
| GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; |
| for (int j = 0; j < nr; j++) { |
| roa [ j ] = in.readTTFUShort(); |
| } |
| // read glyph sequence rules |
| for (int j = 0; j < nr; j++) { |
| GlyphTable.ChainedClassSequenceRule r; |
| int ro = roa [ j ]; |
| if (ro > 0) { |
| // seek to rule [ j ] |
| in.seekSet(subtableOffset + cso + ro); |
| // read backtrack glyph class count |
| int nbc = in.readTTFUShort(); |
| // read backtrack glyph classes |
| int[] backtrackClasses = new int [ nbc ]; |
| for (int k = 0, nk = backtrackClasses.length; k < nk; k++) { |
| backtrackClasses [ k ] = in.readTTFUShort(); |
| } |
| // read input glyph class count |
| int nic = in.readTTFUShort(); |
| // read input glyph classes |
| int[] classes = new int [ nic - 1 ]; |
| for (int k = 0, nk = classes.length; k < nk; k++) { |
| classes [ k ] = in.readTTFUShort(); |
| } |
| // read lookahead glyph class count |
| int nlc = in.readTTFUShort(); |
| // read lookahead glyph classes |
| int[] lookaheadClasses = new int [ nlc ]; |
| for (int k = 0, nk = lookaheadClasses.length; k < nk; k++) { |
| lookaheadClasses [ k ] = in.readTTFUShort(); |
| } |
| // read rule lookup count |
| int nl = in.readTTFUShort(); |
| // read rule lookups |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); |
| r = new GlyphTable.ChainedClassSequenceRule(lookups, nic, classes, backtrackClasses, lookaheadClasses); |
| } else { |
| r = null; |
| } |
| ra [ j ] = r; |
| } |
| rs = new GlyphTable.HomogeneousRuleSet(ra); |
| } else { |
| rs = null; |
| } |
| rsa [ i ] = rs; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add(icdt); |
| seEntries.add(bcdt); |
| seEntries.add(lcdt); |
| seEntries.add(Integer.valueOf(ngc)); |
| seEntries.add(rsa); |
| } |
| |
| private void readChainedContextualPosTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read backtrack glyph count |
| int nbg = in.readTTFUShort(); |
| // read backtrack glyph coverage offsets |
| int[] bgcoa = new int [ nbg ]; |
| for (int i = 0; i < nbg; i++) { |
| bgcoa [ i ] = in.readTTFUShort(); |
| } |
| // read input glyph count |
| int nig = in.readTTFUShort(); |
| // read backtrack glyph coverage offsets |
| int[] igcoa = new int [ nig ]; |
| for (int i = 0; i < nig; i++) { |
| igcoa [ i ] = in.readTTFUShort(); |
| } |
| // read lookahead glyph count |
| int nlg = in.readTTFUShort(); |
| // read backtrack glyph coverage offsets |
| int[] lgcoa = new int [ nlg ]; |
| for (int i = 0; i < nlg; i++) { |
| lgcoa [ i ] = in.readTTFUShort(); |
| } |
| // read positioning lookup count |
| int nl = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyph sets)"); |
| log.debug(tableTag + " chained contextual positioning backtrack glyph count: " + nbg); |
| for (int i = 0; i < nbg; i++) { |
| log.debug(tableTag + " chained contextual positioning backtrack coverage table offset[" + i + "]: " + bgcoa[i]); |
| } |
| log.debug(tableTag + " chained contextual positioning input glyph count: " + nig); |
| for (int i = 0; i < nig; i++) { |
| log.debug(tableTag + " chained contextual positioning input coverage table offset[" + i + "]: " + igcoa[i]); |
| } |
| log.debug(tableTag + " chained contextual positioning lookahead glyph count: " + nlg); |
| for (int i = 0; i < nlg; i++) { |
| log.debug(tableTag + " chained contextual positioning lookahead coverage table offset[" + i + "]: " + lgcoa[i]); |
| } |
| log.debug(tableTag + " chained contextual positioning lookup count: " + nl); |
| } |
| // read backtrack coverage tables |
| GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg]; |
| for (int i = 0; i < nbg; i++) { |
| int bgco = bgcoa [ i ]; |
| GlyphCoverageTable bgct; |
| if (bgco > 0) { |
| bgct = readCoverageTable(tableTag + " chained contextual positioning backtrack coverage[" + i + "]", subtableOffset + bgco); |
| } else { |
| bgct = null; |
| } |
| bgca[i] = bgct; |
| } |
| // read input coverage tables |
| GlyphCoverageTable[] igca = new GlyphCoverageTable[nig]; |
| for (int i = 0; i < nig; i++) { |
| int igco = igcoa [ i ]; |
| GlyphCoverageTable igct; |
| if (igco > 0) { |
| igct = readCoverageTable(tableTag + " chained contextual positioning input coverage[" + i + "]", subtableOffset + igco); |
| } else { |
| igct = null; |
| } |
| igca[i] = igct; |
| } |
| // read lookahead coverage tables |
| GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg]; |
| for (int i = 0; i < nlg; i++) { |
| int lgco = lgcoa [ i ]; |
| GlyphCoverageTable lgct; |
| if (lgco > 0) { |
| lgct = readCoverageTable(tableTag + " chained contextual positioning lookahead coverage[" + i + "]", subtableOffset + lgco); |
| } else { |
| lgct = null; |
| } |
| lgca[i] = lgct; |
| } |
| // read rule lookups |
| String header = null; |
| if (log.isDebugEnabled()) { |
| header = tableTag + " chained contextual positioning lookups: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header); |
| // construct rule, rule set, and rule set array |
| GlyphTable.Rule r = new GlyphTable.ChainedCoverageSequenceRule(lookups, nig, igca, bgca, lgca); |
| GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet(new GlyphTable.Rule[] {r}); |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs}; |
| // store results |
| assert (igca != null) && (igca.length > 0); |
| seMapping = igca[0]; |
| seEntries.add(rsa); |
| } |
| |
| private int readChainedContextualPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positioning subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readChainedContextualPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); |
| } else if (sf == 2) { |
| readChainedContextualPosTableFormat2(lookupType, lookupFlags, subtableOffset, sf); |
| } else if (sf == 3) { |
| readChainedContextualPosTableFormat3(lookupType, lookupFlags, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported chained contextual positioning subtable format: " + sf); |
| } |
| return sf; |
| } |
| |
| private void readExtensionPosTableFormat1(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read extension lookup type |
| int lt = in.readTTFUShort(); |
| // read extension offset |
| long eo = in.readTTFULong(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " extension positioning subtable format: " + subtableFormat); |
| log.debug(tableTag + " extension positioning lookup type: " + lt); |
| log.debug(tableTag + " extension positioning lookup table offset: " + eo); |
| } |
| // read referenced subtable from extended offset |
| readGPOSSubtable(lt, lookupFlags, lookupSequence, subtableSequence, subtableOffset + eo); |
| } |
| |
| private int readExtensionPosTable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positioning subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readExtensionPosTableFormat1(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported extension positioning subtable format: " + sf); |
| } |
| return sf; |
| } |
| |
| private void readGPOSSubtable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException { |
| initATSubState(); |
| int subtableFormat = -1; |
| switch (lookupType) { |
| case GPOSLookupType.SINGLE: |
| subtableFormat = readSinglePosTable(lookupType, lookupFlags, subtableOffset); |
| break; |
| case GPOSLookupType.PAIR: |
| subtableFormat = readPairPosTable(lookupType, lookupFlags, subtableOffset); |
| break; |
| case GPOSLookupType.CURSIVE: |
| subtableFormat = readCursivePosTable(lookupType, lookupFlags, subtableOffset); |
| break; |
| case GPOSLookupType.MARK_TO_BASE: |
| subtableFormat = readMarkToBasePosTable(lookupType, lookupFlags, subtableOffset); |
| break; |
| case GPOSLookupType.MARK_TO_LIGATURE: |
| subtableFormat = readMarkToLigaturePosTable(lookupType, lookupFlags, subtableOffset); |
| break; |
| case GPOSLookupType.MARK_TO_MARK: |
| subtableFormat = readMarkToMarkPosTable(lookupType, lookupFlags, subtableOffset); |
| break; |
| case GPOSLookupType.CONTEXTUAL: |
| subtableFormat = readContextualPosTable(lookupType, lookupFlags, subtableOffset); |
| break; |
| case GPOSLookupType.CHAINED_CONTEXTUAL: |
| subtableFormat = readChainedContextualPosTable(lookupType, lookupFlags, subtableOffset); |
| break; |
| case GPOSLookupType.EXTENSION: |
| subtableFormat = readExtensionPosTable(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset); |
| break; |
| default: |
| break; |
| } |
| extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_POSITIONING, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableFormat); |
| resetATSubState(); |
| } |
| |
| private void readLookupTable(OFTableName tableTag, int lookupSequence, long lookupTable) throws IOException { |
| boolean isGSUB = tableTag.equals(OFTableName.GSUB); |
| boolean isGPOS = tableTag.equals(OFTableName.GPOS); |
| in.seekSet(lookupTable); |
| // read lookup type |
| int lt = in.readTTFUShort(); |
| // read lookup flags |
| int lf = in.readTTFUShort(); |
| // read sub-table count |
| int ns = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| String lts; |
| if (isGSUB) { |
| lts = GSUBLookupType.toString(lt); |
| } else if (isGPOS) { |
| lts = GPOSLookupType.toString(lt); |
| } else { |
| lts = "?"; |
| } |
| log.debug(tableTag + " lookup table type: " + lt + " (" + lts + ")"); |
| log.debug(tableTag + " lookup table flags: " + lf + " (" + LookupFlag.toString(lf) + ")"); |
| log.debug(tableTag + " lookup table subtable count: " + ns); |
| } |
| // read subtable offsets |
| int[] soa = new int[ns]; |
| for (int i = 0; i < ns; i++) { |
| int so = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lookup table subtable offset: " + so); |
| } |
| soa[i] = so; |
| } |
| // read mark filtering set |
| if ((lf & LookupFlag.USE_MARK_FILTERING_SET) != 0) { |
| // read mark filtering set |
| int fs = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lookup table mark filter set: " + fs); |
| } |
| } |
| // read subtables |
| for (int i = 0; i < ns; i++) { |
| int so = soa[i]; |
| if (isGSUB) { |
| readGSUBSubtable(lt, lf, lookupSequence, i, lookupTable + so); |
| } else if (isGPOS) { |
| readGPOSSubtable(lt, lf, lookupSequence, i, lookupTable + so); |
| } |
| } |
| } |
| |
| private void readLookupList(OFTableName tableTag, long lookupList) throws IOException { |
| in.seekSet(lookupList); |
| // read lookup record count |
| int nl = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lookup list record count: " + nl); |
| } |
| if (nl > 0) { |
| int[] loa = new int[nl]; |
| // read lookup records |
| for (int i = 0, n = nl; i < n; i++) { |
| int lo = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lookup table offset: " + lo); |
| } |
| loa[i] = lo; |
| } |
| // read lookup tables |
| for (int i = 0, n = nl; i < n; i++) { |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lookup index: " + i); |
| } |
| readLookupTable(tableTag, i, lookupList + loa [ i ]); |
| } |
| } |
| } |
| |
| /** |
| * Read the common layout tables (used by GSUB and GPOS). |
| * @param tableTag tag of table being read |
| * @param scriptList offset to script list from beginning of font file |
| * @param featureList offset to feature list from beginning of font file |
| * @param lookupList offset to lookup list from beginning of font file |
| * @throws IOException In case of a I/O problem |
| */ |
| private void readCommonLayoutTables(OFTableName tableTag, long scriptList, long featureList, long lookupList) throws IOException { |
| if (scriptList > 0) { |
| readScriptList(tableTag, scriptList); |
| } |
| if (featureList > 0) { |
| readFeatureList(tableTag, featureList); |
| } |
| if (lookupList > 0) { |
| readLookupList(tableTag, lookupList); |
| } |
| } |
| |
| private void readGDEFClassDefTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { |
| initATSubState(); |
| in.seekSet(subtableOffset); |
| // subtable is a bare class definition table |
| GlyphClassTable ct = readClassDefTable(tableTag + " glyph class definition table", subtableOffset); |
| // store results |
| seMapping = ct; |
| // extract subtable |
| extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.GLYPH_CLASS, 0, lookupSequence, 0, 1); |
| resetATSubState(); |
| } |
| |
| private void readGDEFAttachmentTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { |
| initATSubState(); |
| in.seekSet(subtableOffset); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " attachment point coverage table offset: " + co); |
| } |
| // read coverage table |
| GlyphCoverageTable ct = readCoverageTable(tableTag + " attachment point coverage", subtableOffset + co); |
| // store results |
| seMapping = ct; |
| // extract subtable |
| extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.ATTACHMENT_POINT, 0, lookupSequence, 0, 1); |
| resetATSubState(); |
| } |
| |
| private void readGDEFLigatureCaretTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { |
| initATSubState(); |
| in.seekSet(subtableOffset); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read ligature glyph count |
| int nl = in.readTTFUShort(); |
| // read ligature glyph table offsets |
| int[] lgto = new int [ nl ]; |
| for (int i = 0; i < nl; i++) { |
| lgto [ i ] = in.readTTFUShort(); |
| } |
| |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " ligature caret coverage table offset: " + co); |
| log.debug(tableTag + " ligature caret ligature glyph count: " + nl); |
| for (int i = 0; i < nl; i++) { |
| log.debug(tableTag + " ligature glyph table offset[" + i + "]: " + lgto[i]); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct = readCoverageTable(tableTag + " ligature caret coverage", subtableOffset + co); |
| // store results |
| seMapping = ct; |
| // extract subtable |
| extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.LIGATURE_CARET, 0, lookupSequence, 0, 1); |
| resetATSubState(); |
| } |
| |
| private void readGDEFMarkAttachmentTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { |
| initATSubState(); |
| in.seekSet(subtableOffset); |
| // subtable is a bare class definition table |
| GlyphClassTable ct = readClassDefTable(tableTag + " glyph class definition table", subtableOffset); |
| // store results |
| seMapping = ct; |
| // extract subtable |
| extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.MARK_ATTACHMENT, 0, lookupSequence, 0, 1); |
| resetATSubState(); |
| } |
| |
| private void readGDEFMarkGlyphsTableFormat1(OFTableName tableTag, int lookupSequence, long subtableOffset, int subtableFormat) throws IOException { |
| initATSubState(); |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip(2); |
| // read mark set class count |
| int nmc = in.readTTFUShort(); |
| long[] mso = new long [ nmc ]; |
| // read mark set coverage offsets |
| for (int i = 0; i < nmc; i++) { |
| mso [ i ] = in.readTTFULong(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark set subtable format: " + subtableFormat + " (glyph sets)"); |
| log.debug(tableTag + " mark set class count: " + nmc); |
| for (int i = 0; i < nmc; i++) { |
| log.debug(tableTag + " mark set coverage table offset[" + i + "]: " + mso[i]); |
| } |
| } |
| // read mark set coverage tables, one per class |
| GlyphCoverageTable[] msca = new GlyphCoverageTable[nmc]; |
| for (int i = 0; i < nmc; i++) { |
| msca[i] = readCoverageTable(tableTag + " mark set coverage[" + i + "]", subtableOffset + mso[i]); |
| } |
| // create combined class table from per-class coverage tables |
| GlyphClassTable ct = GlyphClassTable.createClassTable(Arrays.asList(msca)); |
| // store results |
| seMapping = ct; |
| // extract subtable |
| extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.MARK_ATTACHMENT, 0, lookupSequence, 0, 1); |
| resetATSubState(); |
| } |
| |
| private void readGDEFMarkGlyphsTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read mark set subtable format |
| int sf = in.readTTFUShort(); |
| if (sf == 1) { |
| readGDEFMarkGlyphsTableFormat1(tableTag, lookupSequence, subtableOffset, sf); |
| } else { |
| throw new AdvancedTypographicTableFormatException("unsupported mark glyph sets subtable format: " + sf); |
| } |
| } |
| |
| /** |
| * Read the GDEF table. |
| * @throws IOException In case of a I/O problem |
| */ |
| private void readGDEF() throws IOException { |
| OFTableName tableTag = OFTableName.GDEF; |
| // Initialize temporary state |
| initATState(); |
| // Read glyph definition (GDEF) table |
| OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag); |
| if (gdef != null) { |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + ": ignoring duplicate table"); |
| } |
| } else if (dirTab != null) { |
| otf.seekTab(in, tableTag, 0); |
| long version = in.readTTFULong(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536)); |
| } |
| // glyph class definition table offset (may be null) |
| int cdo = in.readTTFUShort(); |
| // attach point list offset (may be null) |
| int apo = in.readTTFUShort(); |
| // ligature caret list offset (may be null) |
| int lco = in.readTTFUShort(); |
| // mark attach class definition table offset (may be null) |
| int mao = in.readTTFUShort(); |
| // mark glyph sets definition table offset (may be null) |
| int mgo; |
| if (version >= 0x00010002) { |
| mgo = in.readTTFUShort(); |
| } else { |
| mgo = 0; |
| } |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " glyph class definition table offset: " + cdo); |
| log.debug(tableTag + " attachment point list offset: " + apo); |
| log.debug(tableTag + " ligature caret list offset: " + lco); |
| log.debug(tableTag + " mark attachment class definition table offset: " + mao); |
| log.debug(tableTag + " mark glyph set definitions table offset: " + mgo); |
| } |
| // initialize subtable sequence number |
| int seqno = 0; |
| // obtain offset to start of gdef table |
| long to = dirTab.getOffset(); |
| // (optionally) read glyph class definition subtable |
| if (cdo != 0) { |
| readGDEFClassDefTable(tableTag, seqno++, to + cdo); |
| } |
| // (optionally) read glyph attachment point subtable |
| if (apo != 0) { |
| readGDEFAttachmentTable(tableTag, seqno++, to + apo); |
| } |
| // (optionally) read ligature caret subtable |
| if (lco != 0) { |
| readGDEFLigatureCaretTable(tableTag, seqno++, to + lco); |
| } |
| // (optionally) read mark attachment class subtable |
| if (mao != 0) { |
| readGDEFMarkAttachmentTable(tableTag, seqno++, to + mao); |
| } |
| // (optionally) read mark glyph sets subtable |
| if (mgo != 0) { |
| readGDEFMarkGlyphsTable(tableTag, seqno++, to + mgo); |
| } |
| GlyphDefinitionTable gdef; |
| if ((gdef = constructGDEF()) != null) { |
| this.gdef = gdef; |
| } |
| } |
| } |
| |
| /** |
| * Read the GSUB table. |
| * @throws IOException In case of a I/O problem |
| */ |
| private void readGSUB() throws IOException { |
| OFTableName tableTag = OFTableName.GSUB; |
| // Initialize temporary state |
| initATState(); |
| // Read glyph substitution (GSUB) table |
| OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag); |
| if (gpos != null) { |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + ": ignoring duplicate table"); |
| } |
| } else if (dirTab != null) { |
| otf.seekTab(in, tableTag, 0); |
| int version = in.readTTFLong(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536)); |
| } |
| int slo = in.readTTFUShort(); |
| int flo = in.readTTFUShort(); |
| int llo = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " script list offset: " + slo); |
| log.debug(tableTag + " feature list offset: " + flo); |
| log.debug(tableTag + " lookup list offset: " + llo); |
| } |
| long to = dirTab.getOffset(); |
| readCommonLayoutTables(tableTag, to + slo, to + flo, to + llo); |
| GlyphSubstitutionTable gsub; |
| if ((gsub = constructGSUB()) != null) { |
| this.gsub = gsub; |
| } |
| } |
| } |
| |
| /** |
| * Read the GPOS table. |
| * @throws IOException In case of a I/O problem |
| */ |
| private void readGPOS() throws IOException { |
| OFTableName tableTag = OFTableName.GPOS; |
| // Initialize temporary state |
| initATState(); |
| // Read glyph positioning (GPOS) table |
| OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag); |
| if (gpos != null) { |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + ": ignoring duplicate table"); |
| } |
| } else if (dirTab != null) { |
| otf.seekTab(in, tableTag, 0); |
| int version = in.readTTFLong(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536)); |
| } |
| int slo = in.readTTFUShort(); |
| int flo = in.readTTFUShort(); |
| int llo = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " script list offset: " + slo); |
| log.debug(tableTag + " feature list offset: " + flo); |
| log.debug(tableTag + " lookup list offset: " + llo); |
| } |
| long to = dirTab.getOffset(); |
| readCommonLayoutTables(tableTag, to + slo, to + flo, to + llo); |
| GlyphPositioningTable gpos; |
| if ((gpos = constructGPOS()) != null) { |
| this.gpos = gpos; |
| } |
| } |
| } |
| |
| /** |
| * Construct the (internal representation of the) GDEF table based on previously |
| * parsed state. |
| * @returns glyph definition table or null if insufficient or invalid state |
| */ |
| private GlyphDefinitionTable constructGDEF() { |
| GlyphDefinitionTable gdef = null; |
| List subtables; |
| if ((subtables = constructGDEFSubtables()) != null) { |
| if (subtables.size() > 0) { |
| gdef = new GlyphDefinitionTable(subtables); |
| } |
| } |
| resetATState(); |
| return gdef; |
| } |
| |
| /** |
| * Construct the (internal representation of the) GSUB table based on previously |
| * parsed state. |
| * @returns glyph substitution table or null if insufficient or invalid state |
| */ |
| private GlyphSubstitutionTable constructGSUB() { |
| GlyphSubstitutionTable gsub = null; |
| Map lookups; |
| if ((lookups = constructLookups()) != null) { |
| List subtables; |
| if ((subtables = constructGSUBSubtables()) != null) { |
| if ((lookups.size() > 0) && (subtables.size() > 0)) { |
| gsub = new GlyphSubstitutionTable(gdef, lookups, subtables); |
| } |
| } |
| } |
| resetATState(); |
| return gsub; |
| } |
| |
| /** |
| * Construct the (internal representation of the) GPOS table based on previously |
| * parsed state. |
| * @returns glyph positioning table or null if insufficient or invalid state |
| */ |
| private GlyphPositioningTable constructGPOS() { |
| GlyphPositioningTable gpos = null; |
| Map lookups; |
| if ((lookups = constructLookups()) != null) { |
| List subtables; |
| if ((subtables = constructGPOSSubtables()) != null) { |
| if ((lookups.size() > 0) && (subtables.size() > 0)) { |
| gpos = new GlyphPositioningTable(gdef, lookups, subtables); |
| } |
| } |
| } |
| resetATState(); |
| return gpos; |
| } |
| |
| private void constructLookupsFeature(Map lookups, String st, String lt, String fid) { |
| Object[] fp = (Object[]) seFeatures.get(fid); |
| if (fp != null) { |
| assert fp.length == 2; |
| String ft = (String) fp[0]; // feature tag |
| List/*<String>*/ lul = (List) fp[1]; // list of lookup table ids |
| if ((ft != null) && (lul != null) && (lul.size() > 0)) { |
| GlyphTable.LookupSpec ls = new GlyphTable.LookupSpec(st, lt, ft); |
| lookups.put(ls, lul); |
| } |
| } |
| } |
| |
| private void constructLookupsFeatures(Map lookups, String st, String lt, List/*<String>*/ fids) { |
| for (Iterator fit = fids.iterator(); fit.hasNext();) { |
| String fid = (String) fit.next(); |
| constructLookupsFeature(lookups, st, lt, fid); |
| } |
| } |
| |
| private void constructLookupsLanguage(Map lookups, String st, String lt, Map/*<String,Object[2]>*/ languages) { |
| Object[] lp = (Object[]) languages.get(lt); |
| if (lp != null) { |
| assert lp.length == 2; |
| if (lp[0] != null) { // required feature id |
| constructLookupsFeature(lookups, st, lt, (String) lp[0]); |
| } |
| if (lp[1] != null) { // non-required features ids |
| constructLookupsFeatures(lookups, st, lt, (List) lp[1]); |
| } |
| } |
| } |
| |
| private void constructLookupsLanguages(Map lookups, String st, List/*<String>*/ ll, Map/*<String,Object[2]>*/ languages) { |
| for (Iterator lit = ll.iterator(); lit.hasNext();) { |
| String lt = (String) lit.next(); |
| constructLookupsLanguage(lookups, st, lt, languages); |
| } |
| } |
| |
| private Map constructLookups() { |
| Map/*<GlyphTable.LookupSpec,List<String>>*/ lookups = new java.util.LinkedHashMap(); |
| for (Iterator sit = seScripts.keySet().iterator(); sit.hasNext();) { |
| String st = (String) sit.next(); |
| Object[] sp = (Object[]) seScripts.get(st); |
| if (sp != null) { |
| assert sp.length == 3; |
| Map/*<String,Object[2]>*/ languages = (Map) sp[2]; |
| if (sp[0] != null) { // default language |
| constructLookupsLanguage(lookups, st, (String) sp[0], languages); |
| } |
| if (sp[1] != null) { // non-default languages |
| constructLookupsLanguages(lookups, st, (List) sp[1], languages); |
| } |
| } |
| } |
| return lookups; |
| } |
| |
| private List constructGDEFSubtables() { |
| List/*<GlyphDefinitionSubtable>*/ subtables = new java.util.ArrayList(); |
| if (seSubtables != null) { |
| for (Iterator it = seSubtables.iterator(); it.hasNext();) { |
| Object[] stp = (Object[]) it.next(); |
| GlyphSubtable st; |
| if ((st = constructGDEFSubtable(stp)) != null) { |
| subtables.add(st); |
| } |
| } |
| } |
| return subtables; |
| } |
| |
| private GlyphSubtable constructGDEFSubtable(Object[] stp) { |
| GlyphSubtable st = null; |
| assert (stp != null) && (stp.length == 8); |
| Integer tt = (Integer) stp[0]; // table type |
| Integer lt = (Integer) stp[1]; // lookup type |
| Integer ln = (Integer) stp[2]; // lookup sequence number |
| Integer lf = (Integer) stp[3]; // lookup flags |
| Integer sn = (Integer) stp[4]; // subtable sequence number |
| Integer sf = (Integer) stp[5]; // subtable format |
| GlyphMappingTable mapping = (GlyphMappingTable) stp[6]; |
| List entries = (List) stp[7]; |
| if (tt.intValue() == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION) { |
| int type = GDEFLookupType.getSubtableType(lt.intValue()); |
| String lid = "lu" + ln.intValue(); |
| int sequence = sn.intValue(); |
| int flags = lf.intValue(); |
| int format = sf.intValue(); |
| st = GlyphDefinitionTable.createSubtable(type, lid, sequence, flags, format, mapping, entries); |
| } |
| return st; |
| } |
| |
| private List constructGSUBSubtables() { |
| List/*<GlyphSubtable>*/ subtables = new java.util.ArrayList(); |
| if (seSubtables != null) { |
| for (Iterator it = seSubtables.iterator(); it.hasNext();) { |
| Object[] stp = (Object[]) it.next(); |
| GlyphSubtable st; |
| if ((st = constructGSUBSubtable(stp)) != null) { |
| subtables.add(st); |
| } |
| } |
| } |
| return subtables; |
| } |
| |
| private GlyphSubtable constructGSUBSubtable(Object[] stp) { |
| GlyphSubtable st = null; |
| assert (stp != null) && (stp.length == 8); |
| Integer tt = (Integer) stp[0]; // table type |
| Integer lt = (Integer) stp[1]; // lookup type |
| Integer ln = (Integer) stp[2]; // lookup sequence number |
| Integer lf = (Integer) stp[3]; // lookup flags |
| Integer sn = (Integer) stp[4]; // subtable sequence number |
| Integer sf = (Integer) stp[5]; // subtable format |
| GlyphCoverageTable coverage = (GlyphCoverageTable) stp[6]; |
| List entries = (List) stp[7]; |
| if (tt.intValue() == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION) { |
| int type = GSUBLookupType.getSubtableType(lt.intValue()); |
| String lid = "lu" + ln.intValue(); |
| int sequence = sn.intValue(); |
| int flags = lf.intValue(); |
| int format = sf.intValue(); |
| st = GlyphSubstitutionTable.createSubtable(type, lid, sequence, flags, format, coverage, entries); |
| } |
| return st; |
| } |
| |
| private List constructGPOSSubtables() { |
| List/*<GlyphSubtable>*/ subtables = new java.util.ArrayList(); |
| if (seSubtables != null) { |
| for (Iterator it = seSubtables.iterator(); it.hasNext();) { |
| Object[] stp = (Object[]) it.next(); |
| GlyphSubtable st; |
| if ((st = constructGPOSSubtable(stp)) != null) { |
| subtables.add(st); |
| } |
| } |
| } |
| return subtables; |
| } |
| |
| private GlyphSubtable constructGPOSSubtable(Object[] stp) { |
| GlyphSubtable st = null; |
| assert (stp != null) && (stp.length == 8); |
| Integer tt = (Integer) stp[0]; // table type |
| Integer lt = (Integer) stp[1]; // lookup type |
| Integer ln = (Integer) stp[2]; // lookup sequence number |
| Integer lf = (Integer) stp[3]; // lookup flags |
| Integer sn = (Integer) stp[4]; // subtable sequence number |
| Integer sf = (Integer) stp[5]; // subtable format |
| GlyphCoverageTable coverage = (GlyphCoverageTable) stp[6]; |
| List entries = (List) stp[7]; |
| if (tt.intValue() == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING) { |
| int type = GSUBLookupType.getSubtableType(lt.intValue()); |
| String lid = "lu" + ln.intValue(); |
| int sequence = sn.intValue(); |
| int flags = lf.intValue(); |
| int format = sf.intValue(); |
| st = GlyphPositioningTable.createSubtable(type, lid, sequence, flags, format, coverage, entries); |
| } |
| return st; |
| } |
| |
| private void initATState() { |
| seScripts = new java.util.LinkedHashMap(); |
| seLanguages = new java.util.LinkedHashMap(); |
| seFeatures = new java.util.LinkedHashMap(); |
| seSubtables = new java.util.ArrayList(); |
| resetATSubState(); |
| } |
| |
| private void resetATState() { |
| seScripts = null; |
| seLanguages = null; |
| seFeatures = null; |
| seSubtables = null; |
| resetATSubState(); |
| } |
| |
| private void initATSubState() { |
| seMapping = null; |
| seEntries = new java.util.ArrayList(); |
| } |
| |
| private void extractSESubState(int tableType, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, int subtableFormat) { |
| if (seEntries != null) { |
| if ((tableType == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION) || (seEntries.size() > 0)) { |
| if (seSubtables != null) { |
| Integer tt = Integer.valueOf(tableType); |
| Integer lt = Integer.valueOf(lookupType); |
| Integer ln = Integer.valueOf(lookupSequence); |
| Integer lf = Integer.valueOf(lookupFlags); |
| Integer sn = Integer.valueOf(subtableSequence); |
| Integer sf = Integer.valueOf(subtableFormat); |
| seSubtables.add(new Object[] { tt, lt, ln, lf, sn, sf, seMapping, seEntries }); |
| } |
| } |
| } |
| } |
| |
| private void resetATSubState() { |
| seMapping = null; |
| seEntries = null; |
| } |
| |
| private void resetATStateAll() { |
| resetATState(); |
| gdef = null; |
| gsub = null; |
| gpos = null; |
| } |
| |
| /** helper method for formatting an integer array for output */ |
| private String toString(int[] ia) { |
| StringBuffer sb = new StringBuffer(); |
| if ((ia == null) || (ia.length == 0)) { |
| sb.append('-'); |
| } else { |
| boolean first = true; |
| for (int i = 0; i < ia.length; i++) { |
| if (!first) { |
| sb.append(' '); |
| } else { |
| first = false; |
| } |
| sb.append(ia[i]); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| } |