From f11e00631ed3c44c0ed5229a3842a7ae054f2d19 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Mon, 28 Sep 2026 14:05:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=B8=B2=E6=9F=93=E8=83=BD=E5=8A=9B):=20?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=AD=97=E5=BD=A2=E8=A7=A3=E6=9E=90=E4=B8=8E?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E8=B7=B3=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ofdgo_font_glyf.go | 37 ++++++++++++++++- ofdgo_font_resource_sfnt.go | 5 +++ ofdgo_font_type1.go | 81 ++++++++++++++++++++++++++++++++++--- ofdgo_pdf_link.go | 25 ++++++++++++ 4 files changed, 141 insertions(+), 7 deletions(-) diff --git a/ofdgo_font_glyf.go b/ofdgo_font_glyf.go index 40bae33..632df4e 100644 --- a/ofdgo_font_glyf.go +++ b/ofdgo_font_glyf.go @@ -114,7 +114,10 @@ func (f *sfntOutliner) glyph(id uint16, active map[uint16]bool) (ttGlyph, error) } active[id] = true defer delete(active, id) - data := f.font.Glyf.Get(id) + data, err := trueTypeGlyphData(f.font.Tables, id) + if err != nil { + return result, err + } if len(data) == 0 { data = make([]byte, 12) } @@ -123,7 +126,6 @@ func (f *sfntOutliner) glyph(id uint16, active map[uint16]bool) (ttGlyph, error) } n := int(int16(binary.BigEndian.Uint16(data))) var program []byte - var err error if n >= 0 { result, program, err = readTTSimple(data, n) } else { @@ -173,6 +175,37 @@ func (f *sfntOutliner) glyph(id uint16, active map[uint16]bool) (ttGlyph, error) return result, nil } +// trueTypeGlyphData 按loca索引读取字形,拒绝倒序或越界的数据范围 +// 入参: tables 字体表, id 字形编号 +// 返回: []byte 字形数据, error 索引错误 +func trueTypeGlyphData(tables map[string][]byte, id uint16) ([]byte, error) { + head, loca, glyf := tables["head"], tables["loca"], tables["glyf"] + if len(head) < 52 { + return nil, fmt.Errorf("truncated TrueType head") + } + var start, end uint64 + switch binary.BigEndian.Uint16(head[50:]) { + case 0: + offset := int(id) * 2 + if offset+4 > len(loca) { + return nil, fmt.Errorf("truncated TrueType loca") + } + start, end = uint64(binary.BigEndian.Uint16(loca[offset:]))*2, uint64(binary.BigEndian.Uint16(loca[offset+2:]))*2 + case 1: + offset := int(id) * 4 + if offset+8 > len(loca) { + return nil, fmt.Errorf("truncated TrueType loca") + } + start, end = uint64(binary.BigEndian.Uint32(loca[offset:])), uint64(binary.BigEndian.Uint32(loca[offset+4:])) + default: + return nil, fmt.Errorf("invalid TrueType loca format") + } + if start > end || end > uint64(len(glyf)) { + return nil, fmt.Errorf("invalid TrueType glyph %d range", id) + } + return glyf[start:end], nil +} + // recordGlyphWarning 记录字形恢复提示,允许并发提取轮廓 // 入参: id 字形编号, warning 恢复原因 func (f *sfntOutliner) recordGlyphWarning(id uint16, warning error) { diff --git a/ofdgo_font_resource_sfnt.go b/ofdgo_font_resource_sfnt.go index 0b2a399..0ec8234 100644 --- a/ofdgo_font_resource_sfnt.go +++ b/ofdgo_font_resource_sfnt.go @@ -203,6 +203,11 @@ func subsetSourceSFNTFont(data []byte, usage *editorFontUsage) []byte { mapping[char] = id } } + for id := 0; id < int(sfnt.NumGlyphs()); id++ { + if _, err := trueTypeGlyphData(sfnt.Tables, uint16(id)); err != nil { + return nil + } + } for id := range glyphs { if id >= sfnt.NumGlyphs() { return nil diff --git a/ofdgo_font_type1.go b/ofdgo_font_type1.go index 013ac13..dfa5772 100644 --- a/ofdgo_font_type1.go +++ b/ofdgo_font_type1.go @@ -306,10 +306,15 @@ func type1BinaryStart(token string) bool { // type1Outline 保存字形指令的操作数栈及轮廓位置 type type1Outline struct { program *type1Program + active map[string]bool args []float64 postscript []float64 flexPoints [][2]float64 width float64 + sideX float64 + sideY float64 + originX float64 + originY float64 x float64 y float64 pointX float64 @@ -324,7 +329,7 @@ type type1Outline struct { // 入参: glyph 字形数据 // 返回: []byte Type2字形程序, error 错误信息 func (p *type1Program) outline(glyph type1Glyph) ([]byte, error) { - state := type1Outline{program: p} + state := type1Outline{program: p, active: map[string]bool{glyph.name: true}} ended, err := state.run(glyph.data, 0) if err != nil { return nil, fmt.Errorf("Type1 glyph %s: %w", glyph.name, err) @@ -412,6 +417,9 @@ func (s *type1Outline) run(data []byte, depth int) (bool, error) { if err := s.operator(op); err != nil { return false, err } + if op == 1206 { + return true, nil + } } return false, nil } @@ -433,11 +441,15 @@ func (s *type1Outline) operator(op int) error { return fmt.Errorf("invalid Type1 hsbw") } s.x, s.width, s.defined = a[0], a[1], true + s.sideX = s.x + s.x, s.y = s.x+s.originX, s.originY case 1207: if s.defined || len(a) != 4 || a[3] != 0 { return fmt.Errorf("invalid Type1 sbw") } s.x, s.y, s.width, s.defined = a[0], a[1], a[2], true + s.sideX, s.sideY = s.x, s.y + s.x, s.y = s.x+s.originX, s.y+s.originY case 4, 21, 22: if !s.defined || op == 4 && len(a) != 1 || op == 21 && len(a) != 2 || op == 22 && len(a) != 1 { return fmt.Errorf("invalid Type1 move") @@ -542,10 +554,10 @@ func (s *type1Outline) operator(op int) error { if err := s.curve([6]float64{first[4][0], first[4][1], first[5][0], first[5][1], first[6][0], first[6][1]}); err != nil { return err } - if s.x != a[1] || s.y != a[2] { + if s.x != a[1]+s.originX || s.y != a[2]+s.originY { return fmt.Errorf("Type1 Flex endpoint differs from outline") } - s.postscript = []float64{s.x, s.y} + s.postscript = []float64{s.x - s.originX, s.y - s.originY} s.flexPoints = nil s.flex = false case 1: @@ -576,15 +588,74 @@ func (s *type1Outline) operator(op int) error { if len(a) != 2 { return fmt.Errorf("invalid Type1 setcurrentpoint") } - s.x, s.y = a[0], a[1] + s.x, s.y = a[0]+s.originX, a[1]+s.originY case 1206: - return fmt.Errorf("unsupported Type1 seac") + if !s.defined || len(a) != 5 || s.path.Len() != 0 { + return fmt.Errorf("invalid Type1 seac") + } + if err := s.component(a[3], s.originX, s.originY); err != nil { + return err + } + return s.component(a[4], s.originX+s.sideX+a[1]-a[0], s.originY+s.sideY+a[2]) default: return fmt.Errorf("unsupported Type1 operator %d", op) } return nil } +// component 将标准编码引用的字形展开为当前Type2轮廓,保留复合字形字宽 +// 入参: code 标准编码, x 横向原点, y 纵向原点 +// 返回: error 字形缺失、循环或指令错误 +func (s *type1Outline) component(code, x, y float64) error { + if code < 0 || code > 255 || code != math.Trunc(code) { + return fmt.Errorf("invalid Type1 component code") + } + name := type1StandardNames[byte(code)] + if name == "" { + return fmt.Errorf("undefined Type1 component code %g", code) + } + if s.active[name] { + return fmt.Errorf("cyclic Type1 component %s", name) + } + for _, glyph := range s.program.glyphs { + if glyph.name != name { + continue + } + child := type1Outline{program: s.program, active: s.active, originX: x, originY: y, pointX: s.pointX, pointY: s.pointY} + s.active[name] = true + ended, err := child.run(glyph.data, 0) + delete(s.active, name) + if err != nil { + return err + } + if !ended || !child.defined { + return fmt.Errorf("incomplete Type1 component %s", name) + } + s.path.Write(child.path.Bytes()) + s.pointX, s.pointY = child.pointX, child.pointY + return nil + } + return fmt.Errorf("missing Type1 component %s", name) +} + +// type1StandardNames 保存Adobe标准编码的字形名称,用于复合字形引用 +var type1StandardNames = func() [256]string { + var names [256]string + copy(names[32:127], cffStandardStrings[1:96]) + for code, name := range map[byte]string{ + 161: "exclamdown", 162: "cent", 163: "sterling", 164: "fraction", 165: "yen", 166: "florin", 167: "section", 168: "currency", + 169: "quotesingle", 170: "quotedblleft", 171: "guillemotleft", 172: "guilsinglleft", 173: "guilsinglright", 174: "fi", 175: "fl", + 177: "endash", 178: "dagger", 179: "daggerdbl", 180: "periodcentered", 182: "paragraph", 183: "bullet", 184: "quotesinglbase", + 185: "quotedblbase", 186: "quotedblright", 187: "guillemotright", 188: "ellipsis", 189: "perthousand", 191: "questiondown", + 193: "grave", 194: "acute", 195: "circumflex", 196: "tilde", 197: "macron", 198: "breve", 199: "dotaccent", 200: "dieresis", + 202: "ring", 203: "cedilla", 205: "hungarumlaut", 206: "ogonek", 207: "caron", 208: "emdash", 225: "AE", 227: "ordfeminine", + 232: "Lslash", 233: "Oslash", 234: "OE", 235: "ordmasculine", 241: "ae", 245: "dotlessi", 248: "lslash", 249: "oslash", 250: "oe", 251: "germandbls", + } { + names[code] = name + } + return names +}() + // move 在Type2字形程序中建立新轮廓 // 返回: error 错误信息 func (s *type1Outline) move() error { diff --git a/ofdgo_pdf_link.go b/ofdgo_pdf_link.go index fe401c7..d456e7f 100644 --- a/ofdgo_pdf_link.go +++ b/ofdgo_pdf_link.go @@ -249,6 +249,31 @@ func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page, strict dest.OmitLeft, dest.OmitTop = retained[1], retained[0] } dest.OmitZoom = retained[2] || values[2] == 0 + } else if destination.Mode == "FitH" || destination.Mode == "FitV" { + var coordinate float64 + switch value := destination.Parameters[0].(type) { + case pdfgo.Integer: + coordinate = float64(value) + case pdfgo.Real: + coordinate = float64(value) + } + point := pdfgo.Point{X: coordinate} + if destination.Mode == "FitH" { + point = pdfgo.Point{Y: coordinate} + } + point = matrix.Apply(point) + if targetPage.Rotation == 90 || targetPage.Rotation == 270 { + if dest.Type == "FitH" { + dest.Type = "FitV" + } else { + dest.Type = "FitH" + } + } + if dest.Type == "FitH" { + dest.Top, dest.OmitTop = point.Y, destination.Parameters[0] == nil + } else { + dest.Left, dest.OmitLeft = point.X, destination.Parameters[0] == nil + } } else if destination.Mode == "FitR" { values := [4]float64{} for n, v := range destination.Parameters {