Created
March 3, 2024 10:39
-
-
Save pratikmmohite/21bbe9d5c10fe32ffde084423b961986 to your computer and use it in GitHub Desktop.
TTF GSUB table parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2017, Pratik Mohite <dev.pratikm@gmail.com> | |
* | |
* Auther: Pratik Mohite (dev.pratikm@gmail.com) | |
* Source: https://github.com/pratikmmohite | |
* | |
* Licensed 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. | |
*/ | |
/* Resources | |
https://learn.microsoft.com/en-us/typography/opentype/spec/gsub | |
https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2 | |
*/ | |
import 'dart:io'; | |
import 'dart:typed_data'; | |
typedef UpdatedPosition = void Function(int nextBytePos); | |
extension ByteDataExtensions on ByteData { | |
String readString(int byteOffset, | |
[int count = 4, UpdatedPosition? nextBytePosition]) { | |
assert(lengthInBytes > byteOffset + count, 'Overflow: lengthInBytes > byteOffset + $count'); | |
var str = ''; | |
for (var i = 0; i < count; i++) { | |
str += String.fromCharCode(getInt8(byteOffset + i)); | |
} | |
if (nextBytePosition != null) { | |
nextBytePosition(byteOffset + count); | |
} | |
return str; | |
} | |
int readUint16(int byteOffset, [UpdatedPosition? nextBytePosition]) { | |
assert(lengthInBytes > byteOffset + 2, 'Overflow: lengthInBytes > byteOffset + 2'); | |
if (nextBytePosition != null) { | |
nextBytePosition(byteOffset + 2); | |
} | |
return getUint16(byteOffset); | |
} | |
int readInt16(int byteOffset, [UpdatedPosition? nextBytePosition]) { | |
assert(lengthInBytes > byteOffset + 2, 'Overflow: lengthInBytes > byteOffset + 2'); | |
if (nextBytePosition != null) { | |
nextBytePosition(byteOffset + 2); | |
} | |
return getInt16(byteOffset); | |
} | |
} | |
import 'dart:io'; | |
class TtfComment{ | |
List<String> comments = []; | |
void putComment(String str, int padLeft){ | |
String prefix = '|${''.padLeft(padLeft*2, '-')}'; | |
var comment = prefix+ str; | |
print(comment); | |
comments.add(comment); | |
} | |
void printComments(){ | |
print(comments.join('\n')); | |
} | |
void saveComments(){ | |
File file = File('ttf_log.txt'); | |
file.writeAsStringSync(comments.join('\n')); | |
} | |
} | |
class GsubTable extends TtfComment { | |
final ByteData byteData; | |
final int gsubTablePos; | |
GsubTable(this.byteData, this.gsubTablePos); | |
void extractGsubTable() { | |
// Read GSUB table header | |
int majorVersion = byteData.getUint16(gsubTablePos); | |
int minorVersion = byteData.getUint16(gsubTablePos + 2); | |
int scriptListOffset = byteData.getUint16(gsubTablePos + 4); | |
int featureListOffset = byteData.getUint16(gsubTablePos + 6); | |
int lookupListOffset = byteData.getUint16(gsubTablePos + 8); | |
putComment('''GSUB Table Header: Version: $majorVersion.$minorVersion , | |
Script List Offset: $scriptListOffset, | |
Feature List Offset: $featureListOffset, | |
Lookup List Offset: $lookupListOffset''', 0); | |
scriptList(byteData, gsubTablePos + scriptListOffset); | |
featureList(byteData, gsubTablePos + featureListOffset); | |
parseLookListTable(byteData, gsubTablePos + lookupListOffset); | |
saveComments(); | |
} | |
//#region ScriptTable | |
void scriptList(ByteData bytes, int scriptListOffset) { | |
int currentPos = scriptListOffset; | |
int padLeft = 1; | |
putComment('Script List:', padLeft); | |
int scriptCount = | |
bytes.readUint16(currentPos, (nextBytePos) => currentPos = nextBytePos); | |
for (int i = 0; i < scriptCount; i++) { | |
var scriptTag = bytes.readString( | |
currentPos, 4, ((nextBytePos) => currentPos = nextBytePos)); | |
int scriptOffset = bytes.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('Script Tag: $scriptTag, Offset: $scriptOffset', padLeft + 1); | |
parserScriptTable(bytes, scriptListOffset + scriptOffset); | |
} | |
} | |
void parserScriptTable(ByteData bytes, int scriptTableOffset) { | |
int currentPos = scriptTableOffset; | |
int padLeft = 2; | |
int defaultLangSysOffset = | |
bytes.readUint16(currentPos, (nextBytePos) => currentPos = nextBytePos); | |
int langSysCount = | |
bytes.readUint16(currentPos, (nextBytePos) => currentPos = nextBytePos); | |
putComment( | |
'defaultLangSysOffset: $defaultLangSysOffset langSysCount: $langSysCount', | |
padLeft); | |
parseLangTable(bytes, scriptTableOffset + defaultLangSysOffset); | |
for (int i = 0; i < langSysCount; i++) { | |
String langSysTag = bytes.readString( | |
currentPos, 4, (nextBytePos) => currentPos = nextBytePos); | |
int langSysOffset = bytes.readUint16( | |
currentPos, (nextBytePos) => currentPos = nextBytePos); | |
putComment( | |
'langSysTag: $langSysTag langSysOffset: $langSysOffset', padLeft + 1); | |
parseLangTable(bytes, scriptTableOffset + langSysOffset); | |
} | |
} | |
void parseLangTable(ByteData bytes, int langSysOffset) { | |
int currentPos = langSysOffset; | |
int padLeft = 3; | |
int lookupOrderOffset = | |
bytes.readUint16(currentPos, (nextBytePos) => currentPos = nextBytePos); | |
int requiredFeatureIndex = | |
bytes.readUint16(currentPos, (nextBytePos) => currentPos = nextBytePos); | |
int featureIndexCount = | |
bytes.readUint16(currentPos, (nextBytePos) => currentPos = nextBytePos); | |
putComment( | |
'lookupOrderOffset: $lookupOrderOffset, requiredFeatureIndex: $requiredFeatureIndex, featureIndexCount: $featureIndexCount', | |
padLeft); | |
for (int i = 0; i < featureIndexCount; i++) { | |
int featureIndice = bytes.readUint16( | |
currentPos, (nextBytePos) => currentPos = nextBytePos); | |
putComment('$featureIndice', padLeft + 1); | |
} | |
} | |
//#endregion ScriptTable | |
//#region FeatureList | |
void featureList(ByteData bytes, int featureListOffset) { | |
int currentPos = featureListOffset; | |
int padLeft = 1; | |
putComment('Feature List:', padLeft); | |
int featureCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
for (int i = 0; i < featureCount; i++) { | |
String featureTag = byteData.readString( | |
currentPos, 4, ((nextBytePos) => currentPos = nextBytePos)); | |
int featureOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('featureTag: $featureTag, featureOffset: $featureOffset', | |
padLeft + 1); | |
parseFeatureTable(bytes, featureListOffset + featureOffset); | |
} | |
} | |
void parseFeatureTable(ByteData bytes, int featureOffset) { | |
int currentPos = featureOffset; | |
int padLeft = 3; | |
int featureParamsOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int lookupIndexCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'featureParamsOffset: $featureParamsOffset, lookupIndexCount: $lookupIndexCount', | |
padLeft); | |
for (int i = 0; i < lookupIndexCount; i++) { | |
int lookupListIndice = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('lookupListIndice: $lookupListIndice', padLeft + 1); | |
} | |
if (featureParamsOffset == 0) { | |
// TODO: Do nothing | |
} | |
} | |
//#endregion FeatureList | |
//#region LookupList | |
void parseLookListTable(ByteData bytes, int lookupListOffset) { | |
int currentPos = lookupListOffset; | |
int padLeft = 1; | |
putComment('Lookup List:', padLeft); | |
int lookupCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('lookupCount: $lookupCount', padLeft + 1); | |
for (int i = 0; i < lookupCount; i++) { | |
int lookupOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseLookupTable(byteData, lookupListOffset + lookupOffset); | |
} | |
} | |
void parseLookupTable(ByteData bytes, int lookupTableOffset) { | |
int currentPos = lookupTableOffset; | |
int padLeft = 3; | |
int lookupType = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int lookupFlag = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int subTableCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'lookupType: $lookupType, lookupFlag: $lookupFlag, subTableCount: $subTableCount', | |
padLeft); | |
for (int i = 0; i < subTableCount; i++) { | |
int subtableOffsets = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
switch (lookupType) { | |
case 1: | |
parseLookupSingleSubstitutionFormat( | |
bytes, lookupTableOffset + subtableOffsets); | |
break; | |
case 2: | |
parseLookupMultipleSubstitutionFormat( | |
bytes, lookupTableOffset + subtableOffsets); | |
break; | |
case 3: | |
parseLookupMultipleAlternateSubstitutionFormat( | |
bytes, lookupTableOffset + subtableOffsets); | |
break; | |
case 4: | |
parseLookupLigatureSubstitutionSubtable( | |
bytes, lookupTableOffset + subtableOffsets); | |
break; | |
case 5: | |
parseLookupContextualSubstitutionSubtable( | |
bytes, lookupTableOffset + subtableOffsets); | |
break; | |
case 6: | |
parseLookupChainedContextsSubstitutionSubtable( | |
bytes, lookupTableOffset + subtableOffsets); | |
break; | |
case 7: | |
parseLookupExtensionSubstitutionSubtable( | |
bytes, lookupTableOffset + subtableOffsets); | |
break; | |
case 8: | |
parseLookupReverseChainingContextualSingleSubstitutionSubtable( | |
bytes, lookupTableOffset + subtableOffsets); | |
break; | |
} | |
} | |
int markFilteringSet = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('markFilteringSet: $markFilteringSet', padLeft); | |
} | |
// Single (format 1.1 1.2) Replace one glyph with one glyph | |
void parseLookupSingleSubstitutionFormat( | |
ByteData bytes, int lookupSubtableOffset) { | |
int currentPos = lookupSubtableOffset; | |
int padLeft = 4; | |
putComment('Single Substitution Format:', padLeft); | |
int substFormat = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int coverageOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseCoverageTable(bytes, lookupSubtableOffset + coverageOffset); | |
switch (substFormat) { | |
case 1: | |
//1.1 Single Substitution Format 1 | |
int deltaGlyphID = byteData.readInt16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'substFormat: $substFormat, coverageOffset:$coverageOffset, deltaGlyphID: $deltaGlyphID', | |
padLeft + 1); | |
break; | |
case 2: | |
//1.2 Single Substitution Format 2 | |
int glyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'substFormat: $substFormat, coverageOffset:$coverageOffset, glyphCount: $glyphCount', | |
padLeft + 1); | |
// Ordered by coverage index | |
for (int i = 0; i < glyphCount; i++) { | |
int substituteGlyphID = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('substituteGlyphID: $substituteGlyphID', padLeft + 2); | |
} | |
break; | |
} | |
} | |
// Multiple (format 2.1) Replace one glyph with more than one glyph | |
void parseLookupMultipleSubstitutionFormat( | |
ByteData bytes, int lookupSubtableOffset) { | |
int currentPos = lookupSubtableOffset; | |
int padLeft = 4; | |
putComment('Multiple Substitution Format:', padLeft); | |
int substFormat = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int coverageOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseCoverageTable(bytes, lookupSubtableOffset + coverageOffset); | |
switch (substFormat) { | |
case 1: | |
int sequenceCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'substFormat: $substFormat, coverageOffset:$coverageOffset, sequenceCount: $sequenceCount', | |
padLeft + 1); | |
// Ordered by coverage index | |
for (int i = 0; i < sequenceCount; i++) { | |
int sequenceOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('sequenceOffset: $sequenceOffset', padLeft + 2); | |
parseSequenceTable(bytes, lookupSubtableOffset + sequenceOffset); | |
} | |
break; | |
} | |
} | |
void parseSequenceTable(ByteData bytes, int sequenceOffset) { | |
int currentPos = sequenceOffset; | |
int padLeft = 7; | |
putComment('SequenceTable:', padLeft); | |
int glyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('glyphCount: $glyphCount', padLeft + 1); | |
for (int i = 0; i < glyphCount; i++) { | |
int substituteGlyphID = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('substituteGlyphID: $substituteGlyphID', padLeft + 2); | |
} | |
} | |
// 3.1 Alternate Substitution Format 1 | |
void parseLookupMultipleAlternateSubstitutionFormat( | |
ByteData bytes, int lookupSubtableOffset) { | |
int padLeft = 4; | |
putComment('Alternate Substitution Form:', padLeft); | |
int currentPos = lookupSubtableOffset; | |
int substFormat = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int coverageOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseCoverageTable(bytes, lookupSubtableOffset + coverageOffset); | |
switch (substFormat) { | |
case 1: | |
int alternateSetCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'substFormat: $substFormat, coverageOffset:$coverageOffset, alternateSetCount: $alternateSetCount', | |
padLeft + 1); | |
// Ordered by coverage index | |
for (int i = 0; i < alternateSetCount; i++) { | |
int alternateSetOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('alternateSetOffset: $alternateSetOffset', padLeft + 2); | |
parseAlternateSetTable( | |
bytes, lookupSubtableOffset + alternateSetOffset); | |
} | |
break; | |
} | |
} | |
void parseAlternateSetTable(ByteData bytes, int sequenceOffset) { | |
int padLeft = 7; | |
putComment('AlternateSetTable:', padLeft); | |
int currentPos = sequenceOffset; | |
int glyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('glyphCount: $glyphCount', padLeft + 1); | |
for (int i = 0; i < glyphCount; i++) { | |
int substituteGlyphID = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('alternateGlyphID: $substituteGlyphID', padLeft + 2); | |
} | |
} | |
// LookupType 4: Ligature Substitution Subtable | |
void parseLookupLigatureSubstitutionSubtable( | |
ByteData bytes, int lookupSubtableOffset) { | |
int padLeft = 4; | |
putComment('Ligature Substitution Subtable:', padLeft); | |
int currentPos = lookupSubtableOffset; | |
int substFormat = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int coverageOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseCoverageTable(bytes, lookupSubtableOffset + coverageOffset); | |
switch (substFormat) { | |
case 1: | |
int ligatureSetCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'substFormat: $substFormat, coverageOffset:$coverageOffset, ligatureSetCount: $ligatureSetCount', | |
padLeft + 1); | |
// Ordered by coverage index | |
for (int i = 0; i < ligatureSetCount; i++) { | |
int ligatureSetOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('ligatureSetOffset: $ligatureSetOffset', padLeft + 2); | |
parseLigatureSetTable( | |
bytes, lookupSubtableOffset + ligatureSetOffset); | |
} | |
break; | |
} | |
} | |
void parseLigatureSetTable(ByteData bytes, int sequenceOffset) { | |
int padLeft = 7; | |
putComment('LigatureSet Table:', padLeft); | |
int currentPos = sequenceOffset; | |
int ligatureCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('ligatureCount: $ligatureCount', padLeft + 1); | |
for (int i = 0; i < ligatureCount; i++) { | |
int ligatureOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseLigatureTable(bytes, sequenceOffset + ligatureOffset); | |
} | |
} | |
void parseLigatureTable(ByteData bytes, int ligatureOffset) { | |
int padLeft = 9; | |
putComment('Ligature Table:', padLeft); | |
int currentPos = ligatureOffset; | |
int ligatureGlyph = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int componentCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('ligatureGlyph: $ligatureGlyph, componentCount: $componentCount', | |
padLeft + 1); | |
for (int i = 0; i < componentCount; i++) { | |
int componentGlyphID = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('componentGlyphID: $componentGlyphID', padLeft + 2); | |
} | |
} | |
// LookupType 5: Contextual Substitution Subtable | |
// Working on | |
void parseLookupContextualSubstitutionSubtable( | |
ByteData bytes, int lookupSubtableOffset) { | |
int padLeft = 4; | |
putComment('Contextual Substitution Subtable:', padLeft); | |
int currentPos = lookupSubtableOffset; | |
int format = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
switch (format) { | |
case 1: | |
int coverageOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'format: $format, coverageOffset: $coverageOffset', padLeft + 1); | |
parseCoverageTable( | |
bytes, lookupSubtableOffset + coverageOffset, padLeft + 1); | |
int seqRuleSetCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('seqRuleSetCount: $seqRuleSetCount', padLeft + 1); | |
for (int i = 0; i < seqRuleSetCount; i++) { | |
int seqRuleSetOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseSequenceRuleSet(bytes, lookupSubtableOffset + seqRuleSetOffset); | |
} | |
break; | |
case 2: | |
int coverageOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'format: $format, coverageOffset: $coverageOffset', padLeft + 1); | |
parseCoverageTable( | |
bytes, lookupSubtableOffset + coverageOffset, padLeft + 1); | |
int classDefOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseClassDefinitionTable(bytes, lookupSubtableOffset + classDefOffset); | |
int classSeqRuleSetCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
for (int i = 0; i < classSeqRuleSetCount; i++) { | |
int classSeqRuleSetOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'classSeqRuleSetOffsets: $classSeqRuleSetOffset', padLeft + 2); | |
switch (classSeqRuleSetOffset) { | |
case 0: | |
putComment('null', padLeft + 3); | |
break; | |
default: | |
parseClassSequenceRuleSet( | |
bytes, lookupSubtableOffset + classSeqRuleSetOffset); | |
break; | |
} | |
} | |
break; | |
case 3: | |
int glyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int seqLookupCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'format: $format glyphCount:$glyphCount, seqLookupCount: $seqLookupCount', | |
padLeft + 1); | |
for (int i = 0; i < glyphCount; i++) { | |
int coverageOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseCoverageTable( | |
bytes, lookupSubtableOffset + coverageOffset, padLeft + 2); | |
} | |
parseSequenceLookupRecord(bytes, currentPos, | |
seqLookupCount: seqLookupCount, | |
nextBytePosition: ((nextBytePos) => currentPos = nextBytePos)); | |
break; | |
} | |
} | |
// LookupType 5.1: Contextual Substitution Subtable Format 1 | |
void parseSequenceRuleSet(ByteData bytes, int seqRuleSetOffset) { | |
int padLeft = 7; | |
putComment('SequenceRuleSet:', padLeft); | |
int currentPos = seqRuleSetOffset; | |
int seqRuleCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('seqRuleCount: $seqRuleCount', padLeft + 1); | |
for (int i = 0; i < seqRuleCount; i++) { | |
int seqRuleOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseSequenceRule(bytes, seqRuleSetOffset + seqRuleOffset); | |
} | |
} | |
void parseSequenceRule(ByteData bytes, int seqRuleOffset) { | |
int padLeft = 9; | |
putComment('Sequence Rule:', padLeft); | |
int currentPos = seqRuleOffset; | |
int glyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int seqLookupCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('GlyphIds', padLeft + 1); | |
putComment('glyphCount: $glyphCount, seqLookupCount: $seqLookupCount', | |
padLeft + 2); | |
for (int i = 0; i < glyphCount - 1; i++) { | |
int inputSequence = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('GlyphId: $inputSequence', padLeft + 3); | |
} | |
putComment('SequenceLookupRecord', padLeft + 1); | |
parseSequenceLookupRecord(bytes, currentPos, | |
seqLookupCount: seqLookupCount, | |
nextBytePosition: ((nextBytePos) => currentPos = nextBytePos)); | |
} | |
// LookupType 5.2: Contextual Substitution Subtable Format 2 | |
// Class Definition Table | |
void parseClassDefinitionTable(ByteData bytes, int classDefOffset) { | |
int padLeft = 10; | |
int currentPos = classDefOffset; | |
putComment('Class Definition Table:', padLeft); | |
int classFormat = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('classFormat: $classFormat', padLeft + 1); | |
switch (classFormat) { | |
case 1: | |
int startGlyphID = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int glyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'startGlyphID: $startGlyphID glyphCount: $glyphCount', padLeft + 1); | |
for (int i = 0; i < glyphCount; i++) { | |
int glyphId = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('glyphId: $glyphId', padLeft + 2); | |
} | |
break; | |
case 2: | |
int classRangeCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('classRangeCount: $classRangeCount', padLeft + 2); | |
for (int i = 0; i < classRangeCount; i++) { | |
int startGlyphID = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int endGlyphID = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int classId = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'startGlyphID: $startGlyphID, endGlyphID: $endGlyphID, classId: $classId', | |
padLeft + 3); | |
} | |
break; | |
} | |
} | |
void parseClassSequenceRuleSet(ByteData bytes, int classSeqRuleSetOffset) { | |
int padLeft = 12; | |
int currentPos = classSeqRuleSetOffset; | |
putComment('Class Sequence RuleSet Table:', padLeft); | |
int classSeqRuleCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('classSeqRuleCount: $classSeqRuleCount', padLeft + 1); | |
for (int i = 0; i < classSeqRuleCount; i++) { | |
int classSeqRuleOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseClassSequenceRule(bytes, classSeqRuleSetOffset + classSeqRuleOffset); | |
} | |
} | |
void parseClassSequenceRule(ByteData bytes, int seqRuleOffset) { | |
int padLeft = 9; | |
putComment('Class Sequence Rule:', padLeft); | |
int currentPos = seqRuleOffset; | |
int glyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int seqLookupCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'InputSequence: glyphCount: $glyphCount, seqLookupCount: $seqLookupCount', | |
padLeft + 2); | |
for (int i = 0; i < glyphCount - 1; i++) { | |
int inputSequence = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('GlyphId: $inputSequence', padLeft + 3); | |
} | |
putComment('SequenceLookupRecord', padLeft + 1); | |
parseSequenceLookupRecord(bytes, currentPos, | |
seqLookupCount: seqLookupCount, | |
nextBytePosition: ((nextBytePos) => currentPos = nextBytePos)); | |
} | |
// LookupType 6: Chained Contexts Substitution Subtable | |
void parseLookupChainedContextsSubstitutionSubtable( | |
ByteData bytes, int lookupSubtableOffset) { | |
int padLeft = 4; | |
putComment('Chained Contexts Substitution Subtable:', padLeft); | |
int currentPos = lookupSubtableOffset; | |
int format = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
switch (format) { | |
case 1: | |
parseChainedSequenceContextFormat1(bytes, currentPos, | |
lookupSubtableOffset, ((nextBytePos) => currentPos = nextBytePos)); | |
break; | |
case 2: | |
parseChainedSequenceContextFormat2(bytes, currentPos, | |
lookupSubtableOffset, ((nextBytePos) => currentPos = nextBytePos)); | |
break; | |
case 3: | |
parseChainedSequenceContextFormat3(bytes, currentPos, | |
lookupSubtableOffset, ((nextBytePos) => currentPos = nextBytePos)); | |
break; | |
} | |
} | |
// Format 1 | |
void parseChainedSequenceContextFormat1( | |
ByteData bytes, int currentPos, int lookupSubtableOffset, | |
[UpdatedPosition? nextBytePosition]) { | |
int padLeft = 6; | |
int coverageOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('coverageOffset: $coverageOffset', padLeft + 1); | |
parseCoverageTable(bytes, lookupSubtableOffset + coverageOffset); | |
int chainedSeqRuleSetCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
for (int i = 0; i < chainedSeqRuleSetCount; i++) { | |
int chainedSeqRuleSetOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
switch (chainedSeqRuleSetOffset) { | |
case 0: | |
putComment('null', padLeft + 2); | |
break; | |
default: | |
parseChainedSequenceRuleSet( | |
bytes, lookupSubtableOffset + chainedSeqRuleSetOffset); | |
break; | |
} | |
} | |
} | |
void parseChainedSequenceRuleSet( | |
ByteData bytes, int chainedSeqRuleSetOffset) { | |
int padLeft = 6; | |
putComment('Chained Sequence Rule Set:', padLeft); | |
int currentPos = chainedSeqRuleSetOffset; | |
int chainedSeqRuleCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('chainedSeqRuleCount: $chainedSeqRuleCount', padLeft + 1); | |
for (int i = 0; i < chainedSeqRuleCount; i++) { | |
int chainedSeqRuleOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseChainedSequenceRule( | |
bytes, chainedSeqRuleSetOffset + chainedSeqRuleOffset); | |
} | |
} | |
void parseChainedSequenceRule(ByteData bytes, int chainedSeqRuleOffset) { | |
int padLeft = 7; | |
putComment('Chained Sequence Rule:', padLeft); | |
int currentPos = chainedSeqRuleOffset; | |
int backtrackGlyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
for (int i = 0; i < backtrackGlyphCount; i++) { | |
int backtrackSequence = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('backtrackSequence GlyphId: $backtrackSequence', padLeft + 1); | |
} | |
int inputGlyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
for (int i = 0; i < inputGlyphCount - 1; i++) { | |
int inputSequence = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('inputSequence GlyphId: $inputSequence', padLeft + 1); | |
} | |
int lookaheadGlyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
for (int i = 0; i < lookaheadGlyphCount; i++) { | |
int lookaheadSequence = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('lookaheadSequence GlyphId: $lookaheadSequence', padLeft + 1); | |
} | |
parseSequenceLookupRecord(bytes, currentPos, | |
nextBytePosition: ((nextBytePos) => currentPos = nextBytePos)); | |
} | |
// Format 2 | |
void parseChainedSequenceContextFormat2( | |
ByteData bytes, int currentPosition, int lookupSubtableOffset, | |
[UpdatedPosition? nextBytePosition]) { | |
int padLeft = 6; | |
int currentPos = currentPosition; | |
int coverageOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('coverageOffset: $coverageOffset', padLeft + 1); | |
parseCoverageTable(bytes, lookupSubtableOffset + coverageOffset); | |
int backtrackClassDefOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'backtrackClassDefOffset: $backtrackClassDefOffset', padLeft + 2); | |
parseClassDefinitionTable( | |
bytes, lookupSubtableOffset + backtrackClassDefOffset); | |
int inputClassDefOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('inputClassDefOffset: $inputClassDefOffset', padLeft + 2); | |
parseClassDefinitionTable( | |
bytes, lookupSubtableOffset + inputClassDefOffset); | |
int lookaheadClassDefOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('lookaheadClassDefOffset: $inputClassDefOffset', padLeft + 2); | |
parseClassDefinitionTable( | |
bytes, lookupSubtableOffset + lookaheadClassDefOffset); | |
int chainedClassSeqRuleSetCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
for (int i = 0; i < chainedClassSeqRuleSetCount; i++) { | |
int chainedClassSeqRuleSetOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
switch (chainedClassSeqRuleSetOffset) { | |
case 0: | |
putComment('null', padLeft + 2); | |
break; | |
default: | |
parseChainedClassSequenceRuleSet( | |
bytes, lookupSubtableOffset + chainedClassSeqRuleSetOffset); | |
break; | |
} | |
} | |
} | |
void parseChainedClassSequenceRuleSet( | |
ByteData bytes, int chainedSeqRuleOffset) { | |
int padLeft = 6; | |
putComment('Chained Class Sequence Rule Set:', padLeft); | |
int currentPos = chainedSeqRuleOffset; | |
int chainedClassSeqRuleCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'chainedClassSeqRuleCount: $chainedClassSeqRuleCount', padLeft + 1); | |
for (int i = 0; i < chainedClassSeqRuleCount; i++) { | |
int chainedClassSeqRuleOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseChainedClassSequenceRule( | |
bytes, chainedSeqRuleOffset + chainedClassSeqRuleOffset); | |
} | |
} | |
void parseChainedClassSequenceRule( | |
ByteData bytes, int chainedClassSeqRuleOffset) { | |
int padLeft = 7; | |
putComment('Chained Class Sequence Rule:', padLeft); | |
int currentPos = chainedClassSeqRuleOffset; | |
int backtrackGlyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
for (int i = 0; i < backtrackGlyphCount; i++) { | |
int backtrackSequence = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('backtrackSequence classes: $backtrackSequence', padLeft + 1); | |
} | |
int inputGlyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
for (int i = 0; i < inputGlyphCount - 1; i++) { | |
int inputSequence = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('inputSequence classes: $inputSequence', padLeft + 1); | |
} | |
int lookaheadGlyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
for (int i = 0; i < lookaheadGlyphCount; i++) { | |
int lookaheadSequence = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('lookaheadSequence classes: $lookaheadSequence', padLeft + 1); | |
} | |
parseSequenceLookupRecord(bytes, currentPos, | |
nextBytePosition: ((nextBytePos) => currentPos = nextBytePos)); | |
} | |
// Format 3 | |
void parseChainedSequenceContextFormat3( | |
ByteData bytes, int currentPosition, int lookupSubtableOffset, | |
[UpdatedPosition? nextBytePosition]) { | |
int padLeft = 6; | |
putComment('ChainedSequenceContextFormat3:', padLeft); | |
int currentPos = currentPosition; | |
int backtrackGlyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
for (int i = 0; i < backtrackGlyphCount; i++) { | |
int backtrackCoverageOffsets = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseCoverageTable( | |
bytes, lookupSubtableOffset + backtrackCoverageOffsets); | |
} | |
int inputGlyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
for (int i = 0; i < inputGlyphCount; i++) { | |
int inputCoverageOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseCoverageTable(bytes, lookupSubtableOffset + inputCoverageOffset); | |
} | |
int lookaheadGlyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
for (int i = 0; i < lookaheadGlyphCount; i++) { | |
int lookaheadCoverageOffsets = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseCoverageTable( | |
bytes, lookupSubtableOffset + lookaheadCoverageOffsets); | |
} | |
parseSequenceLookupRecord(bytes, currentPos, | |
nextBytePosition: ((nextBytePos) => currentPos = nextBytePos)); | |
if (nextBytePosition != null) { | |
nextBytePosition(currentPos); | |
} | |
} | |
// LookupType 7: Extension Substitution Subtable | |
void parseLookupExtensionSubstitutionSubtable( | |
ByteData bytes, int lookupSubtableOffset) { | |
int padLeft = 4; | |
int currentPos = lookupSubtableOffset; | |
putComment('Extension Substitution Subtable:', padLeft); | |
int substFormat = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
switch (substFormat) { | |
case 1: | |
int extensionLookupType = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int extensionOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'substFormat: $substFormat, extensionLookupType: $extensionLookupType, extensionOffset: $extensionOffset', | |
padLeft + 2); | |
break; | |
} | |
} | |
// LookupType 8: Reverse Chaining Contextual Single Substitution Subtable | |
void parseLookupReverseChainingContextualSingleSubstitutionSubtable( | |
ByteData bytes, int lookupSubtableOffset) { | |
int padLeft = 4; | |
putComment( | |
' Reverse Chaining Contextual Single Substitution Subtable:', padLeft); | |
int currentPos = lookupSubtableOffset; | |
int substFormat = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
switch (substFormat) { | |
case 1: | |
int coverageOffset = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('coverageOffset: $coverageOffset', padLeft + 1); | |
parseCoverageTable(bytes, lookupSubtableOffset + coverageOffset); | |
int backtrackGlyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('backtrackGlyphCount: $backtrackGlyphCount', padLeft + 1); | |
for (int i = 0; i < backtrackGlyphCount; i++) { | |
int backtrackCoverageOffsets = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseCoverageTable( | |
bytes, lookupSubtableOffset + backtrackCoverageOffsets); | |
} | |
int lookaheadGlyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('lookaheadGlyphCount: $backtrackGlyphCount', padLeft + 1); | |
for (int i = 0; i < lookaheadGlyphCount; i++) { | |
int lookaheadCoverageOffsets = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
parseCoverageTable( | |
bytes, lookupSubtableOffset + lookaheadCoverageOffsets); | |
} | |
int glyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('glyphCount: $glyphCount', padLeft + 1); | |
for (int i = 0; i < glyphCount; i++) { | |
int substituteGlyphIDs = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('substituteGlyphIDs: $substituteGlyphIDs', padLeft + 2); | |
} | |
break; | |
} | |
} | |
// Coverage Table | |
void parseCoverageTable(ByteData bytes, int coverageOffset, | |
[int basePadLeft = 5]) { | |
int currentPos = coverageOffset; | |
int padLeft = basePadLeft; | |
putComment('Coverage:', padLeft); | |
int coverageFormat = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
switch (coverageFormat) { | |
case 1: | |
int glyphCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('coverageFormat: $coverageFormat glyphCount: $glyphCount', | |
padLeft + 1); | |
for (int i = 0; i < glyphCount; i++) { | |
int glyphId = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('glyphId: $glyphId', padLeft + 2); | |
} | |
break; | |
case 2: | |
int rangeCount = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment('coverageFormat: $coverageFormat rangeCount: $rangeCount', | |
padLeft + 1); | |
for (int i = 0; i < rangeCount; i++) { | |
// RangeRecord | |
int startGlyphID = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int endGlyphID = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int startCoverageIndex = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'startGlyphID: $startGlyphID, endGlyphID: $endGlyphID, startCoverageIndex:$startCoverageIndex', | |
padLeft + 2); | |
} | |
break; | |
} | |
} | |
// Sequence Lookup Record | |
void parseSequenceLookupRecord(ByteData bytes, int currentPos, | |
{UpdatedPosition? nextBytePosition, int? seqLookupCount}) { | |
int padLeft = 8; | |
seqLookupCount ??= byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
for (int i = 0; i < seqLookupCount; i++) { | |
int sequenceIndex = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
int lookupListIndex = byteData.readUint16( | |
currentPos, ((nextBytePos) => currentPos = nextBytePos)); | |
putComment( | |
'sequenceIndex:$sequenceIndex, lookupListIndex: $lookupListIndex', | |
padLeft + 2); | |
} | |
if (nextBytePosition != null) { | |
nextBytePosition(currentPos); | |
} | |
} | |
//#endregion LookupList | |
} | |
class GSub { | |
GSub(); | |
static void parseGsub(ByteData bytes, int baseOffset) { | |
GsubTable gsubTable = GsubTable(bytes, baseOffset); | |
gsubTable.extractGsubTable(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment