// Copyright 2019 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package sfntimport ()const ( hexScriptLatn = uint32(0x6c61746e) // latn hexScriptDFLT = uint32(0x44464c54) // DFLT hexFeatureKern = uint32(0x6b65726e) // kern)// kernFunc returns the unscaled kerning value for kerning pair a+b.// Returns ErrNotFound if no kerning is specified for this pair.type kernFunc func(a, b GlyphIndex) (int16, error)func ( *Font) ( []byte) ([]byte, []kernFunc, error) {// https://docs.microsoft.com/en-us/typography/opentype/spec/gposif .gpos.length == 0 {return , nil, nil }const = 10// GPOS header v1.1 is 14 bytes, but we don't support FeatureVariationsif .gpos.length < {return , nil, errInvalidGPOSTable } , := .src.view(, int(.gpos.offset), )if != nil {return , nil, }// check for version 1.0/1.1ifu16() != 1 || u16([2:]) > 1 {return , nil, errUnsupportedGPOSTable } := u16([4:]) := u16([6:]) := u16([8:])// get all feature indices for latn script , , := .parseGPOSScriptFeatures(, int(.gpos.offset)+int(), hexScriptLatn)if != nil {return , nil, }iflen() == 0 {// get all feature indices for DFLT script , , = .parseGPOSScriptFeatures(, int(.gpos.offset)+int(), hexScriptDFLT)if != nil {return , nil, }iflen() == 0 {return , nil, nil } }// get all lookup indices for kern features , , := .parseGPOSFeaturesLookup(, int(.gpos.offset)+int(), , hexFeatureKern)if != nil {return , nil, }// LookupTableList: lookupCount,[]lookups , , := .src.varLenView(, int(.gpos.offset)+int(), 2, 0, 2)if != nil {return , nil, }var []kernFunc:for , := range {if > {return , nil, errInvalidGPOSTable } := int(.gpos.offset) + int() + int(u16([2+*2:]))// LookupTable: lookupType, lookupFlag, subTableCount, []subtableOffsets, markFilteringSet , , := .src.varLenView(, , 8, 4, 2)if != nil {return , nil, } := u16([2:]) := make([]int, )for := 0; < int(); ++ { [] = int() + int(u16([6+*2:])) }switch := u16(); {case2: // PairPos tablecase9:// Extension Positioning table defines an additional u32 offset // to allow subtables to exceed the 16-bit limit.for := range { , = .src.view(, [], 8)if != nil {return , nil, }if := u16(); != 1 {return , nil, errUnsupportedExtensionPosFormat }if := u16([2:]); != 2 {continue } [] += int(u32([4:])) }default: // other types are not supportedcontinue }if &0x0010 > 0 {// useMarkFilteringSet enabled, skip as it is not supportedcontinue }for , := range { , = .src.view(, int(), 4)if != nil {return , nil, } := u16()varindexLookupFunc , , = .makeCachedCoverageLookup(, +int(u16([2:])))if != nil {return , nil, }switch {case1: // Adjustments for Glyph Pairs , , := .parsePairPosFormat1(, , )if != nil {return , nil, }if != nil { = append(, ) }case2: // Class Pair Adjustment , , := .parsePairPosFormat2(, , )if != nil {return , nil, }if != nil { = append(, ) } } } }return , , nil}func ( *Font) ( []byte, int, indexLookupFunc) ([]byte, kernFunc, error) {// PairPos Format 1: posFormat, coverageOffset, valueFormat1, // valueFormat2, pairSetCount, []pairSetOffsetsvarerrorvarint , , = .src.varLenView(, , 10, 8, 2)if != nil {return , nil, }// check valueFormat1 and valueFormat2 flagsifu16([4:]) != 0x04 || u16([6:]) != 0x00 {// we only support kerning with X_ADVANCE for first glyphreturn , nil, nil }// PairPos table contains an array of offsets to PairSet // tables, which contains an array of PairValueRecords. // Calculate length of complete PairPos table by jumping to // last PairSet. // We need to iterate all offsets to find the last pair as // offsets are not sorted and can be repeated.varintfor := 0; < ; ++ { := int(u16([10+*2:]))if > { = } } , = .src.view(, +, 2)if != nil {return , nil, } := int(u16())// Each PairSet contains the secondGlyph (u16) and one or more value records (all u16). // We only support lookup tables with one value record (X_ADVANCE, see valueFormat1/2 above). := 2 + *4 := + , = .src.view(, , )if != nil {return , nil, } := makeCachedPairPosGlyph(, , )return , , nil}func ( *Font) ( []byte, int, indexLookupFunc) ([]byte, kernFunc, error) {// PairPos Format 2: // posFormat, coverageOffset, valueFormat1, valueFormat2, // classDef1Offset, classDef2Offset, class1Count, class2Count, // []class1Recordsvarerror , = .src.view(, , 16)if != nil {return , nil, }// check valueFormat1 and valueFormat2 flagsifu16([4:]) != 0x04 || u16([6:]) != 0x00 {// we only support kerning with X_ADVANCE for first glyphreturn , nil, nil } := int(u16([12:])) := int(u16([14:])) := + int(u16([8:])) := + int(u16([10:]))var , classLookupFunc , , = .makeCachedClassLookup(, )if != nil {return , nil, } , , = .makeCachedClassLookup(, )if != nil {return , nil, } , = .src.view(, +16, **2)if != nil {return , nil, } := makeCachedPairPosClass( , , , , , , )return , , nil}// parseGPOSScriptFeatures returns all indices of features in FeatureTable that// are valid for the given script.// Returns features from DefaultLangSys, different languages are not supported.// However, all observed fonts either do not use different languages or use the// same features as DefaultLangSys.func ( *Font) ( []byte, int, uint32) ([]byte, []int, error) {// ScriptList table: scriptCount, []scriptRecords{scriptTag, scriptOffset} , , := .src.varLenView(, , 2, 0, 6)if != nil {return , nil, }// Search ScriptTables for scriptvaruint16for := 0; < ; ++ { := u32([2+*6:])if == { = u16([2+*6+4:])break } }if == 0 {return , nil, nil }// Script table: defaultLangSys, langSysCount, []langSysRecords{langSysTag, langSysOffset} , = .src.view(, +int(), 2)if != nil {return , nil, } := u16()if == 0 {return , nil, nil }// LangSys table: lookupOrder (reserved), requiredFeatureIndex, featureIndexCount, []featureIndices , , := .src.varLenView(, +int()+int(), 6, 4, 2)if != nil {return , nil, } := make([]int, )for := range { [] = int(u16([6+*2:])) }return , , nil}func ( *Font) ( []byte, int, []int, uint32) ([]byte, []int, error) {// FeatureList table: featureCount, []featureRecords{featureTag, featureOffset} , , := .src.varLenView(, , 2, 0, 6)if != nil {return , nil, } := make([]int, 0, 4)for , := range {if > {return , nil, errInvalidGPOSTable } := u32([2+*6:])if != {continue } := u16([2+*6+4:]) , , := .src.varLenView(nil, +int(), 4, 2, 2)if != nil {return , nil, }for := 0; < ; ++ { = append(, int(u16([4+*2:]))) } }return , , nil}func makeCachedPairPosGlyph( indexLookupFunc, int, []byte) kernFunc { := make([]byte, len())copy(, )returnfunc(, GlyphIndex) (int16, error) { , := ()if ! {return0, ErrNotFound }if >= {return0, ErrNotFound } := int(u16([10+*2:]))if +1 >= len() {return0, errInvalidGPOSTable } := int(u16([:]))for := 0; < ; ++ { := GlyphIndex(int(u16([+2+*4:])))if == {returnint16(u16([+2+*4+2:])), nil }if > {return0, ErrNotFound } }return0, ErrNotFound }}func makeCachedPairPosClass( indexLookupFunc, , int, , classLookupFunc, []byte) kernFunc { := make([]byte, len())copy(, )returnfunc(, GlyphIndex) (int16, error) {// check coverage to avoid selection of default class 0 , := ()if ! {return0, ErrNotFound } := () := ()returnint16(u16([(+*)*2:])), nil }}// indexLookupFunc returns the index into a PairPos table for the provided glyph.// Returns false if the glyph is not covered by this lookup.type indexLookupFunc func(GlyphIndex) (int, bool)func ( *Font) ( []byte, int) ([]byte, indexLookupFunc, error) {varerror , = .src.view(, , 2)if != nil {return , nil, }switchu16() {case1:// Coverage Format 1: coverageFormat, glyphCount, []glyphArray , _, = .src.varLenView(, , 4, 2, 2)if != nil {return , nil, }return , makeCachedCoverageList([2:]), nilcase2:// Coverage Format 2: coverageFormat, rangeCount, []rangeRecords{startGlyphID, endGlyphID, startCoverageIndex} , _, = .src.varLenView(, , 4, 2, 6)if != nil {return , nil, }return , makeCachedCoverageRange([2:]), nildefault:return , nil, errUnsupportedCoverageFormat }}func makeCachedCoverageList( []byte) indexLookupFunc { := int(u16()) := make([]byte, len()-2)copy(, [2:])returnfunc( GlyphIndex) (int, bool) { := sort.Search(, func( int) bool {return <= GlyphIndex(u16([*2:])) })if < && GlyphIndex(u16([*2:])) == {return , true }return0, false }}func makeCachedCoverageRange( []byte) indexLookupFunc { := int(u16()) := make([]byte, len()-2)copy(, [2:])returnfunc( GlyphIndex) (int, bool) {if == 0 {return0, false }// ranges is an array of startGlyphID, endGlyphID and startCoverageIndex // Ranges are non-overlapping. // The following GlyphIDs/index pairs are stored as follows: // pairs: 130=0, 131=1, 132=2, 133=3, 134=4, 135=5, 137=6 // ranges: 130, 135, 0 137, 137, 6 // startCoverageIndex is used to calculate the index without counting // the length of the preceding ranges := sort.Search(, func( int) bool {return <= GlyphIndex(u16([*6:])) })// idx either points to a matching start, or to the next range (or idx==num) // e.g. with the range example from above: 130 points to 130-135 range, 133 points to 137-137 range// check if gi is the start of a range, but only if sort.Search returned a valid resultif < {if := u16([*6:]); == GlyphIndex() {returnint(u16([*6+4:])), true } }// check if gi is in previous rangeif > 0 { -- , := u16([*6:]), u16([*6+2:])if >= GlyphIndex() && <= GlyphIndex() {returnint(u16([*6+4:]) + uint16() - ), true } }return0, false }}// classLookupFunc returns the class ID for the provided glyph. Returns 0// (default class) for glyphs not covered by this lookup.type classLookupFunc func(GlyphIndex) intfunc ( *Font) ( []byte, int) ([]byte, classLookupFunc, error) {varerror , = .src.view(, , 2)if != nil {return , nil, }switchu16() {case1:// ClassDefFormat 1: classFormat, startGlyphID, glyphCount, []classValueArray , _, = .src.varLenView(, , 6, 4, 2)if != nil {return , nil, }return , makeCachedClassLookupFormat1(), nilcase2:// ClassDefFormat 2: classFormat, classRangeCount, []classRangeRecords , _, = .src.varLenView(, , 4, 2, 6)if != nil {return , nil, }return , makeCachedClassLookupFormat2(), nildefault:return , nil, errUnsupportedClassDefFormat }}func makeCachedClassLookupFormat1( []byte) classLookupFunc { := u16([2:]) := u16([4:]) := make([]byte, len()-4)copy(, [6:])returnfunc( GlyphIndex) int {// classIDs is an array of target class IDs. gi is the index into that array (minus startGI).if < GlyphIndex() || >= GlyphIndex(+) {// default to class 0return0 }returnint(u16([(int()-int())*2:])) }}func makeCachedClassLookupFormat2( []byte) classLookupFunc { := int(u16([2:])) := make([]byte, len()-2)copy(, [4:])returnfunc( GlyphIndex) int {if == 0 {return0// default to class 0 }// classRange is an array of startGlyphID, endGlyphID and target class ID. // Ranges are non-overlapping. // E.g. 130, 135, 1 137, 137, 5 etc := sort.Search(, func( int) bool {return <= GlyphIndex(u16([*6:])) })// idx either points to a matching start, or to the next range (or idx==num) // e.g. with the range example from above: 130 points to 130-135 range, 133 points to 137-137 range// check if gi is the start of a range, but only if sort.Search returned a valid resultif < {if := u16([*6:]); == GlyphIndex() {returnint(u16([*6+4:])) } }// check if gi is in previous rangeif > 0 { -- , := u16([*6:]), u16([*6+2:])if >= GlyphIndex() && <= GlyphIndex() {returnint(u16([*6+4:])) } }// default to class 0return0 }}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.