| // 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 sfnt |
| |
| import ( |
| "sort" |
| ) |
| |
| 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 (f *Font) parseGPOSKern(buf []byte) ([]byte, []kernFunc, error) { |
| // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos |
| |
| if f.gpos.length == 0 { |
| return buf, nil, nil |
| } |
| const headerSize = 10 // GPOS header v1.1 is 14 bytes, but we don't support FeatureVariations |
| if f.gpos.length < headerSize { |
| return buf, nil, errInvalidGPOSTable |
| } |
| |
| buf, err := f.src.view(buf, int(f.gpos.offset), headerSize) |
| if err != nil { |
| return buf, nil, err |
| } |
| |
| // check for version 1.0/1.1 |
| if u16(buf) != 1 || u16(buf[2:]) > 1 { |
| return buf, nil, errUnsupportedGPOSTable |
| } |
| scriptListOffset := u16(buf[4:]) |
| featureListOffset := u16(buf[6:]) |
| lookupListOffset := u16(buf[8:]) |
| |
| // get all feature indices for latn script |
| buf, featureIdxs, err := f.parseGPOSScriptFeatures(buf, int(f.gpos.offset)+int(scriptListOffset), hexScriptLatn) |
| if err != nil { |
| return buf, nil, err |
| } |
| if len(featureIdxs) == 0 { |
| // get all feature indices for DFLT script |
| buf, featureIdxs, err = f.parseGPOSScriptFeatures(buf, int(f.gpos.offset)+int(scriptListOffset), hexScriptDFLT) |
| if err != nil { |
| return buf, nil, err |
| } |
| if len(featureIdxs) == 0 { |
| return buf, nil, nil |
| } |
| } |
| |
| // get all lookup indices for kern features |
| buf, lookupIdx, err := f.parseGPOSFeaturesLookup(buf, int(f.gpos.offset)+int(featureListOffset), featureIdxs, hexFeatureKern) |
| |
| // LookupTableList: lookupCount,[]lookups |
| buf, numLookupTables, err := f.src.varLenView(buf, int(f.gpos.offset)+int(lookupListOffset), 2, 0, 2) |
| if err != nil { |
| return buf, nil, err |
| } |
| |
| var kernFuncs []kernFunc |
| |
| lookupTables: |
| for _, n := range lookupIdx { |
| if n > numLookupTables { |
| return buf, nil, errInvalidGPOSTable |
| } |
| tableOffset := int(f.gpos.offset) + int(lookupListOffset) + int(u16(buf[2+n*2:])) |
| |
| // LookupTable: lookupType, lookupFlag, subTableCount, []subtableOffsets, markFilteringSet |
| buf, numSubTables, err := f.src.varLenView(buf, tableOffset, 8, 4, 2) |
| if err != nil { |
| return buf, nil, err |
| } |
| |
| flags := u16(buf[2:]) |
| |
| subTableOffsets := make([]int, numSubTables) |
| for i := 0; i < int(numSubTables); i++ { |
| subTableOffsets[i] = int(tableOffset) + int(u16(buf[6+i*2:])) |
| } |
| |
| switch lookupType := u16(buf); lookupType { |
| case 2: // PairPos table |
| case 9: |
| // Extension Positioning table defines an additional u32 offset |
| // to allow subtables to exceed the 16-bit limit. |
| for i := range subTableOffsets { |
| buf, err = f.src.view(buf, subTableOffsets[i], 8) |
| if err != nil { |
| return buf, nil, err |
| } |
| if format := u16(buf); format != 1 { |
| return buf, nil, errUnsupportedExtensionPosFormat |
| } |
| if lookupType := u16(buf[2:]); lookupType != 2 { |
| continue lookupTables |
| } |
| subTableOffsets[i] += int(u32(buf[4:])) |
| } |
| default: // other types are not supported |
| continue |
| } |
| |
| if flags&0x0010 > 0 { |
| // useMarkFilteringSet enabled, skip as it is not supported |
| continue |
| } |
| |
| for _, subTableOffset := range subTableOffsets { |
| buf, err = f.src.view(buf, int(subTableOffset), 4) |
| if err != nil { |
| return buf, nil, err |
| } |
| format := u16(buf) |
| |
| var lookupIndex indexLookupFunc |
| buf, lookupIndex, err = f.makeCachedCoverageLookup(buf, subTableOffset+int(u16(buf[2:]))) |
| if err != nil { |
| return buf, nil, err |
| } |
| |
| switch format { |
| case 1: // Adjustments for Glyph Pairs |
| buf, kern, err := f.parsePairPosFormat1(buf, subTableOffset, lookupIndex) |
| if err != nil { |
| return buf, nil, err |
| } |
| if kern != nil { |
| kernFuncs = append(kernFuncs, kern) |
| } |
| case 2: // Class Pair Adjustment |
| buf, kern, err := f.parsePairPosFormat2(buf, subTableOffset, lookupIndex) |
| if err != nil { |
| return buf, nil, err |
| } |
| if kern != nil { |
| kernFuncs = append(kernFuncs, kern) |
| } |
| } |
| } |
| } |
| |
| return buf, kernFuncs, nil |
| } |
| |
| func (f *Font) parsePairPosFormat1(buf []byte, offset int, lookupIndex indexLookupFunc) ([]byte, kernFunc, error) { |
| // PairPos Format 1: posFormat, coverageOffset, valueFormat1, |
| // valueFormat2, pairSetCount, []pairSetOffsets |
| var err error |
| var nPairs int |
| buf, nPairs, err = f.src.varLenView(buf, offset, 10, 8, 2) |
| if err != nil { |
| return buf, nil, err |
| } |
| // check valueFormat1 and valueFormat2 flags |
| if u16(buf[4:]) != 0x04 || u16(buf[6:]) != 0x00 { |
| // we only support kerning with X_ADVANCE for first glyph |
| return buf, 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. |
| var lastPairSetOffset int |
| for n := 0; n < nPairs; n++ { |
| pairOffset := int(u16(buf[10+n*2:])) |
| if pairOffset > lastPairSetOffset { |
| lastPairSetOffset = pairOffset |
| } |
| } |
| buf, err = f.src.view(buf, offset+lastPairSetOffset, 2) |
| if err != nil { |
| return buf, nil, err |
| } |
| |
| pairValueCount := int(u16(buf)) |
| // 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). |
| lastPairSetLength := 2 + pairValueCount*4 |
| |
| length := lastPairSetOffset + lastPairSetLength |
| buf, err = f.src.view(buf, offset, length) |
| if err != nil { |
| return buf, nil, err |
| } |
| |
| kern := makeCachedPairPosGlyph(lookupIndex, nPairs, buf) |
| return buf, kern, nil |
| } |
| |
| func (f *Font) parsePairPosFormat2(buf []byte, offset int, lookupIndex indexLookupFunc) ([]byte, kernFunc, error) { |
| // PairPos Format 2: |
| // posFormat, coverageOffset, valueFormat1, valueFormat2, |
| // classDef1Offset, classDef2Offset, class1Count, class2Count, |
| // []class1Records |
| var err error |
| buf, err = f.src.view(buf, offset, 16) |
| if err != nil { |
| return buf, nil, err |
| } |
| // check valueFormat1 and valueFormat2 flags |
| if u16(buf[4:]) != 0x04 || u16(buf[6:]) != 0x00 { |
| // we only support kerning with X_ADVANCE for first glyph |
| return buf, nil, nil |
| } |
| numClass1 := int(u16(buf[12:])) |
| numClass2 := int(u16(buf[14:])) |
| cdef1Offset := offset + int(u16(buf[8:])) |
| cdef2Offset := offset + int(u16(buf[10:])) |
| var cdef1, cdef2 classLookupFunc |
| buf, cdef1, err = f.makeCachedClassLookup(buf, cdef1Offset) |
| if err != nil { |
| return buf, nil, err |
| } |
| buf, cdef2, err = f.makeCachedClassLookup(buf, cdef2Offset) |
| if err != nil { |
| return buf, nil, err |
| } |
| |
| buf, err = f.src.view(buf, offset+16, numClass1*numClass2*2) |
| if err != nil { |
| return buf, nil, err |
| } |
| kern := makeCachedPairPosClass( |
| lookupIndex, |
| numClass1, |
| numClass2, |
| cdef1, |
| cdef2, |
| buf, |
| ) |
| |
| return buf, kern, 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 (f *Font) parseGPOSScriptFeatures(buf []byte, offset int, script uint32) ([]byte, []int, error) { |
| // ScriptList table: scriptCount, []scriptRecords{scriptTag, scriptOffset} |
| buf, numScriptTables, err := f.src.varLenView(buf, offset, 2, 0, 6) |
| if err != nil { |
| return buf, nil, err |
| } |
| |
| // Search ScriptTables for script |
| var scriptTableOffset uint16 |
| for i := 0; i < numScriptTables; i++ { |
| scriptTag := u32(buf[2+i*6:]) |
| if scriptTag == script { |
| scriptTableOffset = u16(buf[2+i*6+4:]) |
| break |
| } |
| } |
| if scriptTableOffset == 0 { |
| return buf, nil, nil |
| } |
| |
| // Script table: defaultLangSys, langSysCount, []langSysRecords{langSysTag, langSysOffset} |
| buf, err = f.src.view(buf, offset+int(scriptTableOffset), 2) |
| if err != nil { |
| return buf, nil, err |
| } |
| defaultLangSysOffset := u16(buf) |
| |
| if defaultLangSysOffset == 0 { |
| return buf, nil, nil |
| } |
| |
| // LangSys table: lookupOrder (reserved), requiredFeatureIndex, featureIndexCount, []featureIndices |
| buf, numFeatures, err := f.src.varLenView(buf, offset+int(scriptTableOffset)+int(defaultLangSysOffset), 6, 4, 2) |
| |
| featureIdxs := make([]int, numFeatures) |
| for i := range featureIdxs { |
| featureIdxs[i] = int(u16(buf[6+i*2:])) |
| } |
| return buf, featureIdxs, nil |
| } |
| |
| func (f *Font) parseGPOSFeaturesLookup(buf []byte, offset int, featureIdxs []int, feature uint32) ([]byte, []int, error) { |
| // FeatureList table: featureCount, []featureRecords{featureTag, featureOffset} |
| buf, numFeatureTables, err := f.src.varLenView(buf, offset, 2, 0, 6) |
| if err != nil { |
| return buf, nil, err |
| } |
| |
| lookupIdx := make([]int, 0, 4) |
| |
| for _, fidx := range featureIdxs { |
| if fidx > numFeatureTables { |
| return buf, nil, errInvalidGPOSTable |
| } |
| featureTag := u32(buf[2+fidx*6:]) |
| if featureTag != feature { |
| continue |
| } |
| featureOffset := u16(buf[2+fidx*6+4:]) |
| |
| buf, numLookups, err := f.src.varLenView(nil, offset+int(featureOffset), 4, 2, 2) |
| if err != nil { |
| return buf, nil, err |
| } |
| |
| for i := 0; i < numLookups; i++ { |
| lookupIdx = append(lookupIdx, int(u16(buf[4+i*2:]))) |
| } |
| } |
| |
| return buf, lookupIdx, nil |
| } |
| |
| func makeCachedPairPosGlyph(cov indexLookupFunc, num int, buf []byte) kernFunc { |
| glyphs := make([]byte, len(buf)) |
| copy(glyphs, buf) |
| return func(a, b GlyphIndex) (int16, error) { |
| idx, found := cov(a) |
| if !found { |
| return 0, ErrNotFound |
| } |
| if idx >= num { |
| return 0, ErrNotFound |
| } |
| offset := int(u16(glyphs[10+idx*2:])) |
| if offset+1 >= len(glyphs) { |
| return 0, errInvalidGPOSTable |
| } |
| |
| count := int(u16(glyphs[offset:])) |
| for i := 0; i < count; i++ { |
| secondGlyphIndex := GlyphIndex(int(u16(glyphs[offset+2+i*4:]))) |
| if secondGlyphIndex == b { |
| return int16(u16(glyphs[offset+2+i*4+2:])), nil |
| } |
| if secondGlyphIndex > b { |
| return 0, ErrNotFound |
| } |
| } |
| |
| return 0, ErrNotFound |
| } |
| } |
| |
| func makeCachedPairPosClass(cov indexLookupFunc, num1, num2 int, cdef1, cdef2 classLookupFunc, buf []byte) kernFunc { |
| glyphs := make([]byte, len(buf)) |
| copy(glyphs, buf) |
| return func(a, b GlyphIndex) (int16, error) { |
| // check coverage to avoid selection of default class 0 |
| _, found := cov(a) |
| if !found { |
| return 0, ErrNotFound |
| } |
| idxa := cdef1(a) |
| idxb := cdef2(b) |
| return int16(u16(glyphs[(idxb+idxa*num2)*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 (f *Font) makeCachedCoverageLookup(buf []byte, offset int) ([]byte, indexLookupFunc, error) { |
| var err error |
| buf, err = f.src.view(buf, offset, 2) |
| if err != nil { |
| return buf, nil, err |
| } |
| switch u16(buf) { |
| case 1: |
| // Coverage Format 1: coverageFormat, glyphCount, []glyphArray |
| buf, _, err = f.src.varLenView(buf, offset, 4, 2, 2) |
| if err != nil { |
| return buf, nil, err |
| } |
| return buf, makeCachedCoverageList(buf[2:]), nil |
| case 2: |
| // Coverage Format 2: coverageFormat, rangeCount, []rangeRecords{startGlyphID, endGlyphID, startCoverageIndex} |
| buf, _, err = f.src.varLenView(buf, offset, 4, 2, 6) |
| if err != nil { |
| return buf, nil, err |
| } |
| return buf, makeCachedCoverageRange(buf[2:]), nil |
| default: |
| return buf, nil, errUnsupportedCoverageFormat |
| } |
| } |
| |
| func makeCachedCoverageList(buf []byte) indexLookupFunc { |
| num := int(u16(buf)) |
| list := make([]byte, len(buf)-2) |
| copy(list, buf[2:]) |
| return func(gi GlyphIndex) (int, bool) { |
| idx := sort.Search(num, func(i int) bool { |
| return gi <= GlyphIndex(u16(list[i*2:])) |
| }) |
| if idx < num && GlyphIndex(u16(list[idx*2:])) == gi { |
| return idx, true |
| } |
| |
| return 0, false |
| } |
| } |
| |
| func makeCachedCoverageRange(buf []byte) indexLookupFunc { |
| num := int(u16(buf)) |
| ranges := make([]byte, len(buf)-2) |
| copy(ranges, buf[2:]) |
| return func(gi GlyphIndex) (int, bool) { |
| if num == 0 { |
| return 0, 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 preceeding ranges |
| |
| idx := sort.Search(num, func(i int) bool { |
| return gi <= GlyphIndex(u16(ranges[i*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 result |
| if idx < num { |
| if start := u16(ranges[idx*6:]); gi == GlyphIndex(start) { |
| return int(u16(ranges[idx*6+4:])), true |
| } |
| } |
| // check if gi is in previous range |
| if idx > 0 { |
| idx-- |
| start, end := u16(ranges[idx*6:]), u16(ranges[idx*6+2:]) |
| if gi >= GlyphIndex(start) && gi <= GlyphIndex(end) { |
| return int(u16(ranges[idx*6+4:]) + uint16(gi) - start), true |
| } |
| } |
| |
| return 0, 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) int |
| |
| func (f *Font) makeCachedClassLookup(buf []byte, offset int) ([]byte, classLookupFunc, error) { |
| var err error |
| buf, err = f.src.view(buf, offset, 2) |
| if err != nil { |
| return buf, nil, err |
| } |
| switch u16(buf) { |
| case 1: |
| // ClassDefFormat 1: classFormat, startGlyphID, glyphCount, []classValueArray |
| buf, _, err = f.src.varLenView(buf, offset, 6, 4, 2) |
| if err != nil { |
| return buf, nil, err |
| } |
| return buf, makeCachedClassLookupFormat1(buf), nil |
| case 2: |
| // ClassDefFormat 2: classFormat, classRangeCount, []classRangeRecords |
| buf, _, err = f.src.varLenView(buf, offset, 4, 2, 6) |
| if err != nil { |
| return buf, nil, err |
| } |
| return buf, makeCachedClassLookupFormat2(buf), nil |
| default: |
| return buf, nil, errUnsupportedClassDefFormat |
| } |
| } |
| |
| func makeCachedClassLookupFormat1(buf []byte) classLookupFunc { |
| startGI := u16(buf[2:]) |
| num := u16(buf[4:]) |
| classIDs := make([]byte, len(buf)-4) |
| copy(classIDs, buf[6:]) |
| |
| return func(gi GlyphIndex) int { |
| // classIDs is an array of target class IDs. gi is the index into that array (minus startGI). |
| if gi < GlyphIndex(startGI) || gi >= GlyphIndex(startGI+num) { |
| // default to class 0 |
| return 0 |
| } |
| return int(u16(classIDs[(int(gi)-int(startGI))*2:])) |
| } |
| } |
| |
| func makeCachedClassLookupFormat2(buf []byte) classLookupFunc { |
| num := int(u16(buf[2:])) |
| classRanges := make([]byte, len(buf)-2) |
| copy(classRanges, buf[4:]) |
| |
| return func(gi GlyphIndex) int { |
| if num == 0 { |
| return 0 // 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 |
| |
| idx := sort.Search(num, func(i int) bool { |
| return gi <= GlyphIndex(u16(classRanges[i*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 result |
| if idx < num { |
| if start := u16(classRanges[idx*6:]); gi == GlyphIndex(start) { |
| return int(u16(classRanges[idx*6+4:])) |
| } |
| } |
| // check if gi is in previous range |
| if idx > 0 { |
| idx-- |
| start, end := u16(classRanges[idx*6:]), u16(classRanges[idx*6+2:]) |
| if gi >= GlyphIndex(start) && gi <= GlyphIndex(end) { |
| return int(u16(classRanges[idx*6+4:])) |
| } |
| } |
| // default to class 0 |
| return 0 |
| } |
| } |