diff --git a/ofdgo_font_cff.go b/ofdgo_font_cff.go index ee03b16..d0be8d3 100644 --- a/ofdgo_font_cff.go +++ b/ofdgo_font_cff.go @@ -602,13 +602,20 @@ func parseCFFDict(data []byte) cffDict { // 入参: data 字典数据 // 返回: cffDict 字典映射, error 编码或操作数错误 func readCFFDict(data []byte) (cffDict, error) { + return readCFFDictionary(data, false) +} + +// readCFFDictionary 读取字体字典,CFF2顶层字典允许变化存储偏移操作符 +// 入参: data 字典数据, cff2 是否为CFF2顶层字典 +// 返回: cffDict 字典映射, error 编码或操作数错误 +func readCFFDictionary(data []byte, cff2 bool) (cffDict, error) { dict := make(cffDict) var operands []float64 i := 0 for i < len(data) { b := data[i] i++ - if b <= 21 { + if b <= 21 || cff2 && b == 24 { op := int(b) if b == 12 { if i >= len(data) { diff --git a/ofdgo_font_cff2.go b/ofdgo_font_cff2.go new file mode 100644 index 0000000..e213f66 --- /dev/null +++ b/ofdgo_font_cff2.go @@ -0,0 +1,311 @@ +// Copyright 2025-2026 肖其顿 (XIAO QI DUN) +// +// 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. + +package ofdgo + +import ( + "bytes" + "encoding/binary" + "fmt" + "maps" + "math" + + "github.com/go-text/typesetting/font/cff" + "github.com/go-text/typesetting/font/opentype" + "github.com/tdewolff/font" +) + +// defaultCFF2Font 将CFF2默认实例转换为静态OpenType字体,保留字形编号和三次轮廓 +// 入参: data 独立字体数据 +// 返回: []byte 字体数据,非CFF2原样返回, error 解析或编码错误 +func defaultCFF2Font(data []byte) (result []byte, err error) { + if len(data) < 12 || string(data[:4]) != "OTTO" { + return data, nil + } + found := false + for i, count := 0, int(binary.BigEndian.Uint16(data[4:])); i < count && i < (len(data)-12)/16; i++ { + if string(data[12+i*16:16+i*16]) == "CFF2" { + found = true + break + } + } + if !found { + return data, nil + } + table, err := fontFileTables(data, 0) + if err != nil || table["CFF2"] == nil { + return data, err + } + if table["CFF "] != nil || table["glyf"] != nil || len(table["head"]) < 54 || len(table["maxp"]) < 6 || len(table["hhea"]) < 36 { + return nil, fmt.Errorf("invalid CFF2 font tables") + } + defer func() { + if failure := recover(); failure != nil { + result, err = nil, fmt.Errorf("invalid CFF2 font: %v", failure) + } + }() + cffData := table["CFF2"] + if len(cffData) < 5 || cffData[0] != 2 || cffData[1] != 0 || cffData[2] < 5 || binary.BigEndian.Uint32(table["maxp"]) != 0x5000 { + return nil, fmt.Errorf("invalid CFF2 header") + } + start := int(cffData[2]) + end := start + int(binary.BigEndian.Uint16(cffData[3:])) + if end > len(cffData) { + return nil, fmt.Errorf("invalid CFF2 dictionary length") + } + dict, err := readCFFDictionary(cffData[start:end], true) + if err != nil { + return nil, err + } + matrix, err := readCFFMatrix(dict, [6]float64{.001, 0, 0, .001, 0, 0}) + units := binary.BigEndian.Uint16(table["head"][18:]) + if err != nil || units < 16 || units > 16384 || matrix[0] != matrix[3] || matrix[1] != 0 || matrix[2] != 0 || matrix[4] != 0 || matrix[5] != 0 || math.Abs(matrix[0]*float64(units)-1) > 1e-6 { + return nil, fmt.Errorf("CFF2 FontMatrix does not match design units") + } + program, err := cff.ParseCFF2(table["CFF2"]) + if err != nil { + return nil, err + } + if len(dict[24]) != 0 { + fvar := table["fvar"] + if len(fvar) < 16 || binary.BigEndian.Uint32(fvar) != 0x10000 || int(binary.BigEndian.Uint16(fvar[8:])) != program.VarStore.AxisCount() { + return nil, fmt.Errorf("CFF2 variation axis count mismatch") + } + } + count := int(binary.BigEndian.Uint16(table["maxp"][4:])) + metrics := int(binary.BigEndian.Uint16(table["hhea"][34:])) + if count == 0 || len(program.Charstrings) != count || metrics == 0 || metrics > count || len(table["hmtx"]) < metrics*4+(count-metrics)*2 { + return nil, fmt.Errorf("invalid CFF2 glyph metrics") + } + chars := make([][]byte, count) + for gid := range chars { + segments, _, err := program.LoadGlyph(uint16(gid), nil) + if err != nil { + return nil, fmt.Errorf("CFF2 glyph %d: %w", gid, err) + } + width := binary.BigEndian.Uint16(table["hmtx"][min(gid, metrics-1)*4:]) + chars[gid], err = cffOutlineProgram(segments, width) + if err != nil { + return nil, fmt.Errorf("CFF2 glyph %d: %w", gid, err) + } + } + table["CFF "], err = staticCIDFont(table, chars) + if err != nil { + return nil, err + } + for _, tag := range []string{"CFF2", "fvar", "avar", "STAT", "HVAR", "VVAR", "MVAR", "DSIG"} { + delete(table, tag) + } + return serializeOTF(table) +} + +// cffOutlineProgram 将默认设计轮廓编码为无子程序的Type2字形 +// 入参: segments 字体设计坐标路径, width 水平字宽 +// 返回: []byte 字形程序, error 坐标超出编码范围 +func cffOutlineProgram(segments []opentype.Segment, width uint16) ([]byte, error) { + var output bytes.Buffer + if width < 32768 { + if err := encodeType2Number(&output, float64(width)); err != nil { + return nil, err + } + } else { + for range 2 { + if err := encodeType2Number(&output, float64(width)/2); err != nil { + return nil, err + } + } + output.Write([]byte{12, 10}) + } + var x, y float64 + for _, segment := range segments { + count, op := 1, byte(21) + switch segment.Op { + case opentype.SegmentOpMoveTo: + case opentype.SegmentOpLineTo: + op = 5 + case opentype.SegmentOpCubeTo: + count, op = 3, 8 + default: + return nil, fmt.Errorf("invalid CFF2 path operation") + } + for _, point := range segment.Args[:count] { + nx, ny := float64(point.X), float64(point.Y) + if err := encodeType2Number(&output, nx-x); err != nil { + return nil, err + } + if err := encodeType2Number(&output, ny-y); err != nil { + return nil, err + } + x, y = nx, ny + } + output.WriteByte(op) + } + output.WriteByte(14) + return output.Bytes(), nil +} + +// staticCIDFont 封装独立Type2字形,CID与字形编号一致,不依赖字形名称 +// 入参: table OpenType表, chars 字形程序 +// 返回: []byte CFF字体, error 字体结构错误 +func staticCIDFont(table map[string][]byte, chars [][]byte) ([]byte, error) { + if len(chars) == 0 || len(chars) > 65535 || len(table["head"]) < 54 { + return nil, fmt.Errorf("invalid static CFF glyph count") + } + units := binary.BigEndian.Uint16(table["head"][18:]) + if units < 16 || units > 16384 { + return nil, fmt.Errorf("invalid CFF design units") + } + name := fontFaceInfo(table["name"], 0).PostScriptName + if name == "" { + name = "OpenTypeInstance" + } + names := encodeCFFIndex([][]byte{[]byte(name)}) + strings := encodeCFFIndex([][]byte{[]byte("Adobe"), []byte("Identity")}) + charset := []byte{0} + for gid := 1; gid < len(chars); gid++ { + charset = binary.BigEndian.AppendUint16(charset, uint16(gid)) + } + selects := []byte{3, 0, 1, 0, 0, 0} + selects = binary.BigEndian.AppendUint16(selects, uint16(len(chars))) + fonts := encodeCFFIndex([][]byte{encodeCFFDict(cffDict{18: {0, 0}})}) + charIndex := encodeCFFIndex(chars) + scale := 1 / float64(units) + dict := cffDict{1230: {391, 392, 0}, 1234: {float64(len(chars))}, 1207: {scale, 0, 0, scale, 0, 0}, 15: {0}, 17: {0}, 1236: {0}, 1237: {0}} + for i := 0; i < 4; i++ { + dict[5] = append(dict[5], float64(int16(binary.BigEndian.Uint16(table["head"][36+2*i:])))) + } + var top []byte + for range 16 { + top = encodeCFFIndex([][]byte{encodeCFFDict(dict)}) + offset := 4 + len(names) + len(top) + len(strings) + 2 + if dict[15][0] == float64(offset) { + var output bytes.Buffer + for _, part := range [][]byte{{1, 0, 4, 4}, names, top, strings, {0, 0}, charset, selects, fonts, charIndex} { + output.Write(part) + } + return output.Bytes(), nil + } + dict[15][0] = float64(offset) + dict[1237][0] = float64(offset + len(charset)) + dict[1236][0] = float64(offset + len(charset) + len(selects)) + dict[17][0] = float64(offset + len(charset) + len(selects) + len(fonts)) + } + return nil, fmt.Errorf("CFF offsets did not converge") +} + +// staticCFFPrograms 读取无子程序和私有字典的CID字形,其他CFF保留原有保存策略 +// 入参: sfnt 已解析字体 +// 返回: [][]byte 独立字形程序,不符合条件时为空 +func staticCFFPrograms(sfnt *font.SFNT) [][]byte { + data := sfnt.Tables["CFF "] + if len(data) < 4 { + return nil + } + _, end, err := readType2Index(data, int(data[2])) + if err != nil { + return nil + } + tops, end, err := readType2Index(data, end) + if err != nil || len(tops) != 1 { + return nil + } + _, end, err = readType2Index(data, end) + if err != nil { + return nil + } + globals, _, err := readType2Index(data, end) + if err != nil || len(globals) != 0 { + return nil + } + dict, err := readCFFDict(tops[0]) + if err != nil || len(dict[1230]) != 3 { + return nil + } + matrix, err := readCFFMatrix(dict, [6]float64{.001, 0, 0, .001, 0, 0}) + if err != nil || matrix[0] != matrix[3] || matrix[1] != 0 || matrix[2] != 0 || matrix[4] != 0 || matrix[5] != 0 || math.Abs(matrix[0]*float64(sfnt.UnitsPerEm())-1) > 1e-6 { + return nil + } + offset, err := cffDictOffset(data, dict, 1236) + if err != nil { + return nil + } + fds, _, err := readType2Index(data, offset) + if err != nil || len(fds) != 1 { + return nil + } + fd, err := readCFFDict(fds[0]) + if err != nil || len(fd) != 1 || len(fd[18]) != 2 || fd[18][0] != 0 { + return nil + } + offset, err = cffDictOffset(data, dict, 17) + if err != nil { + return nil + } + chars, _, err := readType2Index(data, offset) + if err != nil || len(chars) != int(sfnt.NumGlyphs()) { + return nil + } + return chars +} + +// subsetStaticCFFFont 裁剪静态CID字形并保留原编号、字符映射和度量 +// 入参: sfnt 已解析字体, glyphs 使用的字形 +// 返回: []byte 字体子集, error 裁剪错误 +func subsetStaticCFFFont(sfnt *font.SFNT, glyphs []uint16) ([]byte, error) { + chars := staticCFFPrograms(sfnt) + if chars == nil { + return nil, fmt.Errorf("CFF font cannot be subset safely") + } + keep := make(map[uint16]bool, len(glyphs)+1) + keep[0] = true + for _, gid := range glyphs { + if int(gid) >= len(chars) { + return nil, fmt.Errorf("CFF glyph index out of range") + } + keep[gid] = true + } + empty := make(map[uint16][]byte) + for gid := range chars { + if !keep[uint16(gid)] { + width := sfnt.GlyphAdvance(uint16(gid)) + program := empty[width] + if program == nil { + var err error + program, err = cffOutlineProgram(nil, width) + if err != nil { + return nil, err + } + empty[width] = program + } + chars[gid] = program + } + } + table := maps.Clone(sfnt.Tables) + var err error + table["CFF "], err = staticCIDFont(table, chars) + if err != nil { + return nil, err + } + mapping := make(map[rune]uint16) + for gid := range keep { + for _, char := range sfnt.Cmap.ToUnicode(gid) { + mapping[char] = gid + } + } + table["cmap"] = buildCmapTable(sfnt.NumGlyphs(), mapping) + for _, tag := range []string{"DSIG", "GSUB", "GPOS", "GDEF"} { + delete(table, tag) + } + return serializeOTF(table) +} diff --git a/ofdgo_font_file.go b/ofdgo_font_file.go index ea4551e..1934c01 100644 --- a/ofdgo_font_file.go +++ b/ofdgo_font_file.go @@ -101,6 +101,10 @@ func (f FontFile) MissingGlyphs(index int, text string) (string, error) { if err != nil { return "", err } + data, err = defaultCFF2Font(data) + if err != nil { + return "", err + } sfnt, err := font.ParseSFNT(data, 0) if err != nil { return "", err diff --git a/ofdgo_font_resource_sfnt.go b/ofdgo_font_resource_sfnt.go index f177d0a..27e5719 100644 --- a/ofdgo_font_resource_sfnt.go +++ b/ofdgo_font_resource_sfnt.go @@ -35,6 +35,7 @@ type sfntResourceMetrics struct{ *font.SFNT } func (SFNTBackend) Name() string { return "sfnt" } // OpenFontResource 提取独立字体并读取名称、样式和裁剪能力 +// CFF2使用默认实例的静态轮廓,不改变原字体文件 // 入参: file 字体文件, index 集合索引 // 返回: *FontResource 字体资源, error 解析错误 func (SFNTBackend) OpenFontResource(file FontFile, index int) (*FontResource, error) { @@ -42,6 +43,10 @@ func (SFNTBackend) OpenFontResource(file FontFile, index int) (*FontResource, er if err != nil { return nil, err } + data, err = defaultCFF2Font(data) + if err != nil { + return nil, err + } sfnt, err := font.ParseSFNT(data, 0) if err != nil { return nil, err @@ -91,7 +96,13 @@ func (f sfntResourceMetrics) Write() []byte { return fontSFNTData(f.SFNT) } // 入参: sfnt 已解析字体 // 返回: bool 是否支持裁剪 func sfntSubsettable(sfnt *font.SFNT) bool { - return sfnt.IsTrueType && fontEmbeddingFlags(sfnt)&0x0100 == 0 && !slices.ContainsFunc([]string{"fvar", "COLR", "CBDT", "sbix", "SVG "}, func(tag string) bool { return sfnt.Tables[tag] != nil }) + if fontEmbeddingFlags(sfnt)&0x0100 != 0 || slices.ContainsFunc([]string{"COLR", "CBDT", "sbix", "SVG "}, func(tag string) bool { return sfnt.Tables[tag] != nil }) { + return false + } + if sfnt.IsCFF { + return sfnt.Tables["fvar"] == nil && staticCFFPrograms(sfnt) != nil + } + return sfnt.IsTrueType && (sfnt.Tables["fvar"] == nil || variableSFNTSubsettable(sfnt)) } // fontEmbeddingFlags 读取OpenType的嵌入标志,未提供OS/2表时返回零 @@ -133,10 +144,14 @@ func sfntFontName(sfnt *font.SFNT, names ...font.NameID) string { return "" } -// subsetSFNTFont 裁剪TrueType字形及复合依赖,保留字符映射、名称、度量和提示指令 +// subsetSFNTFont 按实际用字裁剪字体,保留字符映射、名称及度量 // 入参: data 字体数据, glyphs 已排序的字形编号,包含0, mapped 是否保留显式字形编号 // 返回: []byte 字体子集, error 错误信息 func subsetSFNTFont(data []byte, glyphs []uint16, mapped bool) ([]byte, error) { + data, err := defaultCFF2Font(data) + if err != nil { + return nil, err + } sfnt, err := font.ParseSFNT(bytes.Clone(data), 0) if err != nil { return nil, err @@ -144,6 +159,12 @@ func subsetSFNTFont(data []byte, glyphs []uint16, mapped bool) ([]byte, error) { if fontEmbeddingFlags(sfnt)&0x0100 != 0 { return bytes.Clone(data), nil } + if sfnt.IsCFF { + return subsetStaticCFFFont(sfnt, glyphs) + } + if sfnt.Tables["fvar"] != nil { + return subsetVariableSFNTFont(sfnt, glyphs) + } if mapped { usage := newEditorFontUsage() for _, id := range glyphs { diff --git a/ofdgo_font_ttf.go b/ofdgo_font_ttf.go index 999cc9c..0e6b877 100644 --- a/ofdgo_font_ttf.go +++ b/ofdgo_font_ttf.go @@ -23,6 +23,11 @@ import ( // 入参: data 原始字体数据, fixCmap 是否修复cmap, fixName 是否修复name // 返回: bool 是否修复, []byte 修复后数据, map[rune]uint16 字符映射, bool 是否缺失cmap, error 错误信息 func fixTrueType(data []byte, fixCmap, fixName bool) (bool, []byte, map[rune]uint16, bool, error) { + original := data + data, err := defaultCFF2Font(data) + if err != nil { + return false, original, nil, false, err + } if len(data) < 12 { return false, data, nil, false, nil } @@ -30,7 +35,7 @@ func fixTrueType(data []byte, fixCmap, fixName bool) (bool, []byte, map[rune]uin isCFFSfnt := sfntTag == "OTTO" numTables := binary.BigEndian.Uint16(data[4:6]) existingTables := make(map[string][]byte) - malformedDirectory := false + malformedDirectory := !bytes.Equal(original, data) pos := 12 for i := 0; i < int(numTables); i++ { if len(data) < pos+16 { diff --git a/ofdgo_font_util.go b/ofdgo_font_util.go index 88467b3..6d70749 100644 --- a/ofdgo_font_util.go +++ b/ofdgo_font_util.go @@ -292,7 +292,7 @@ func serializeOTF(tables map[string][]byte) ([]byte, error) { searchRange := 1 << (entrySelector + 4) rangeShift := int(numTables)*16 - searchRange buf := new(bytes.Buffer) - if _, ok := tables["CFF "]; ok { + if tables["CFF "] != nil || tables["CFF2"] != nil { buf.WriteString("OTTO") } else { binary.Write(buf, binary.BigEndian, uint32(0x00010000)) diff --git a/ofdgo_font_variable.go b/ofdgo_font_variable.go new file mode 100644 index 0000000..6bae30e --- /dev/null +++ b/ofdgo_font_variable.go @@ -0,0 +1,148 @@ +// Copyright 2025-2026 肖其顿 (XIAO QI DUN) +// +// 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. + +package ofdgo + +import ( + "encoding/binary" + "fmt" + + "github.com/tdewolff/font" +) + +// variableSFNTSubsettable 核对TrueType可变轴与字形变化表的基本结构 +// 入参: sfnt 已解析字体 +// 返回: bool 是否具备可裁剪的变化数据 +func variableSFNTSubsettable(sfnt *font.SFNT) bool { + fvar, gvar := sfnt.Tables["fvar"], sfnt.Tables["gvar"] + if len(fvar) < 16 || len(gvar) < 20 || binary.BigEndian.Uint32(fvar) != 0x10000 || binary.BigEndian.Uint32(gvar) != 0x10000 { + return false + } + axes := int(binary.BigEndian.Uint16(fvar[8:])) + offset, size := int(binary.BigEndian.Uint16(fvar[4:])), int(binary.BigEndian.Uint16(fvar[10:])) + return axes > 0 && size >= 20 && offset >= 16 && offset <= len(fvar) && axes <= (len(fvar)-offset)/size && + int(binary.BigEndian.Uint16(gvar[4:])) == axes && binary.BigEndian.Uint16(gvar[12:]) == sfnt.NumGlyphs() +} + +// subsetVariableSFNTFont 保留字形编号及可变轴,仅裁剪未使用的轮廓和字形变化数据 +// 入参: sfnt 已解析字体, glyphs 使用的字形 +// 返回: []byte 字体子集, error 字形或变化表错误 +func subsetVariableSFNTFont(sfnt *font.SFNT, glyphs []uint16) ([]byte, error) { + if !variableSFNTSubsettable(sfnt) { + return nil, fmt.Errorf("invalid TrueType variation tables") + } + usage := newEditorFontUsage() + usage.glyphs[0] = true + for _, gid := range glyphs { + if gid >= sfnt.NumGlyphs() { + return nil, fmt.Errorf("variable font glyph index out of range") + } + usage.glyphs[gid] = true + for _, char := range sfnt.Cmap.ToUnicode(gid) { + usage.chars[char] = true + } + } + for gid := range usage.glyphs { + dependencies, err := sfnt.Glyf.Dependencies(gid) + if err != nil { + return nil, err + } + for _, dependency := range dependencies { + usage.glyphs[dependency] = true + } + } + mapping := make(map[rune]uint16, len(usage.chars)) + for char := range usage.chars { + mapping[char] = sfnt.GlyphIndex(char) + } + table := make(map[string][]byte) + for _, tag := range []string{"head", "hhea", "hmtx", "maxp", "OS/2", "post", "name", "glyf", "loca", "cvt ", "fpgm", "prep", "gasp", "vhea", "vmtx"} { + if data := sfnt.Tables[tag]; data != nil { + table[tag] = data + } + } + table["cmap"] = buildCmapTable(sfnt.NumGlyphs(), mapping) + static, err := serializeOTF(table) + if err != nil { + return nil, err + } + if subset := subsetSourceSFNTFont(static, usage); subset != nil { + table, err = fontFileTables(subset, 0) + if err != nil { + return nil, err + } + } + table["gvar"], err = subsetGlyphVariations(sfnt.Tables["gvar"], usage.glyphs) + if err != nil { + return nil, err + } + for _, tag := range []string{"fvar", "avar", "cvar", "HVAR", "VVAR", "MVAR", "STAT"} { + if data := sfnt.Tables[tag]; data != nil { + table[tag] = data + } + } + return serializeOTF(table) +} + +// subsetGlyphVariations 按原字形编号重建gvar偏移表,保留共享元组及使用字形的完整变化数据 +// 入参: data gvar表, glyphs 保留字形集合 +// 返回: []byte 裁剪后的gvar表, error 表结构错误 +func subsetGlyphVariations(data []byte, glyphs map[uint16]bool) ([]byte, error) { + if len(data) < 20 || binary.BigEndian.Uint32(data) != 0x10000 { + return nil, fmt.Errorf("invalid gvar header") + } + axes := uint64(binary.BigEndian.Uint16(data[4:])) + tuples := uint64(binary.BigEndian.Uint16(data[6:])) + shared := uint64(binary.BigEndian.Uint32(data[8:])) + count := int(binary.BigEndian.Uint16(data[12:])) + flags := binary.BigEndian.Uint16(data[14:]) + start := uint64(binary.BigEndian.Uint32(data[16:])) + width := 2 + if flags&1 != 0 { + width = 4 + } + sharedSize := axes * tuples * 2 + headerEnd := uint64(20 + (count+1)*width) + if flags > 1 || headerEnd > uint64(len(data)) || start < headerEnd || start > uint64(len(data)) || shared > uint64(len(data)) || sharedSize > uint64(len(data))-shared || sharedSize != 0 && shared < headerEnd { + return nil, fmt.Errorf("invalid gvar offsets") + } + readOffset := func(index int) uint64 { + if width == 2 { + return uint64(binary.BigEndian.Uint16(data[20+index*2:])) * 2 + } + return uint64(binary.BigEndian.Uint32(data[20+index*4:])) + } + output := make([]byte, 20+(count+1)*4) + copy(output, data[:20]) + binary.BigEndian.PutUint16(output[14:], 1) + binary.BigEndian.PutUint32(output[8:], uint32(len(output))) + output = append(output, data[shared:shared+sharedSize]...) + base := len(output) + binary.BigEndian.PutUint32(output[16:], uint32(base)) + for gid := range count { + first, last := readOffset(gid), readOffset(gid+1) + if last < first || last > uint64(len(data))-start { + return nil, fmt.Errorf("invalid gvar glyph offsets") + } + if last > first && sharedSize != 0 && start+first < shared+sharedSize && shared < start+last { + return nil, fmt.Errorf("overlapping gvar glyph and shared tuple data") + } + binary.BigEndian.PutUint32(output[20+gid*4:], uint32(len(output)-base)) + if glyphs[uint16(gid)] { + output = append(output, data[start+first:start+last]...) + } + } + binary.BigEndian.PutUint32(output[20+count*4:], uint32(len(output)-base)) + return output, nil +} diff --git a/ofdgo_renderer_font_canvas.go b/ofdgo_renderer_font_canvas.go index 263a626..b0dfe48 100644 --- a/ofdgo_renderer_font_canvas.go +++ b/ofdgo_renderer_font_canvas.go @@ -56,6 +56,10 @@ func (b CanvasBackend) ResolveFont(r *Renderer, id string, exact bool) (Resolved // 入参: data 字体数据 // 返回: FontMetrics 字体度量, error 解析错误 func (CanvasBackend) OpenFont(data []byte) (FontMetrics, error) { + data, err := defaultCFF2Font(data) + if err != nil { + return nil, err + } sfnt, err := font.ParseSFNT(data, 0) if err != nil { return nil, err