mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
feat(渲染能力): 完善字形解析与页面跳转
This commit is contained in:
1 parent
e20167bc3a
commit
f11e00631e
4 files changed
+141
-7
No files matched your search
+35
-2
@@ -114,7 +114,10 @@ func (f *sfntOutliner) glyph(id uint16, active map[uint16]bool) (ttGlyph, error)
|
|||||||
}
|
}
|
||||||
active[id] = true
|
active[id] = true
|
||||||
defer delete(active, id)
|
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 {
|
if len(data) == 0 {
|
||||||
data = make([]byte, 12)
|
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)))
|
n := int(int16(binary.BigEndian.Uint16(data)))
|
||||||
var program []byte
|
var program []byte
|
||||||
var err error
|
|
||||||
if n >= 0 {
|
if n >= 0 {
|
||||||
result, program, err = readTTSimple(data, n)
|
result, program, err = readTTSimple(data, n)
|
||||||
} else {
|
} else {
|
||||||
@@ -173,6 +175,37 @@ func (f *sfntOutliner) glyph(id uint16, active map[uint16]bool) (ttGlyph, error)
|
|||||||
return result, nil
|
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 记录字形恢复提示,允许并发提取轮廓
|
// recordGlyphWarning 记录字形恢复提示,允许并发提取轮廓
|
||||||
// 入参: id 字形编号, warning 恢复原因
|
// 入参: id 字形编号, warning 恢复原因
|
||||||
func (f *sfntOutliner) recordGlyphWarning(id uint16, warning error) {
|
func (f *sfntOutliner) recordGlyphWarning(id uint16, warning error) {
|
||||||
|
|||||||
@@ -203,6 +203,11 @@ func subsetSourceSFNTFont(data []byte, usage *editorFontUsage) []byte {
|
|||||||
mapping[char] = id
|
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 {
|
for id := range glyphs {
|
||||||
if id >= sfnt.NumGlyphs() {
|
if id >= sfnt.NumGlyphs() {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+76
-5
@@ -306,10 +306,15 @@ func type1BinaryStart(token string) bool {
|
|||||||
// type1Outline 保存字形指令的操作数栈及轮廓位置
|
// type1Outline 保存字形指令的操作数栈及轮廓位置
|
||||||
type type1Outline struct {
|
type type1Outline struct {
|
||||||
program *type1Program
|
program *type1Program
|
||||||
|
active map[string]bool
|
||||||
args []float64
|
args []float64
|
||||||
postscript []float64
|
postscript []float64
|
||||||
flexPoints [][2]float64
|
flexPoints [][2]float64
|
||||||
width float64
|
width float64
|
||||||
|
sideX float64
|
||||||
|
sideY float64
|
||||||
|
originX float64
|
||||||
|
originY float64
|
||||||
x float64
|
x float64
|
||||||
y float64
|
y float64
|
||||||
pointX float64
|
pointX float64
|
||||||
@@ -324,7 +329,7 @@ type type1Outline struct {
|
|||||||
// 入参: glyph 字形数据
|
// 入参: glyph 字形数据
|
||||||
// 返回: []byte Type2字形程序, error 错误信息
|
// 返回: []byte Type2字形程序, error 错误信息
|
||||||
func (p *type1Program) outline(glyph type1Glyph) ([]byte, 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)
|
ended, err := state.run(glyph.data, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Type1 glyph %s: %w", glyph.name, err)
|
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 {
|
if err := s.operator(op); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
if op == 1206 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@@ -433,11 +441,15 @@ func (s *type1Outline) operator(op int) error {
|
|||||||
return fmt.Errorf("invalid Type1 hsbw")
|
return fmt.Errorf("invalid Type1 hsbw")
|
||||||
}
|
}
|
||||||
s.x, s.width, s.defined = a[0], a[1], true
|
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:
|
case 1207:
|
||||||
if s.defined || len(a) != 4 || a[3] != 0 {
|
if s.defined || len(a) != 4 || a[3] != 0 {
|
||||||
return fmt.Errorf("invalid Type1 sbw")
|
return fmt.Errorf("invalid Type1 sbw")
|
||||||
}
|
}
|
||||||
s.x, s.y, s.width, s.defined = a[0], a[1], a[2], true
|
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:
|
case 4, 21, 22:
|
||||||
if !s.defined || op == 4 && len(a) != 1 || op == 21 && len(a) != 2 || op == 22 && len(a) != 1 {
|
if !s.defined || op == 4 && len(a) != 1 || op == 21 && len(a) != 2 || op == 22 && len(a) != 1 {
|
||||||
return fmt.Errorf("invalid Type1 move")
|
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 {
|
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
|
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")
|
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.flexPoints = nil
|
||||||
s.flex = false
|
s.flex = false
|
||||||
case 1:
|
case 1:
|
||||||
@@ -576,15 +588,74 @@ func (s *type1Outline) operator(op int) error {
|
|||||||
if len(a) != 2 {
|
if len(a) != 2 {
|
||||||
return fmt.Errorf("invalid Type1 setcurrentpoint")
|
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:
|
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:
|
default:
|
||||||
return fmt.Errorf("unsupported Type1 operator %d", op)
|
return fmt.Errorf("unsupported Type1 operator %d", op)
|
||||||
}
|
}
|
||||||
return nil
|
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字形程序中建立新轮廓
|
// move 在Type2字形程序中建立新轮廓
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
func (s *type1Outline) move() error {
|
func (s *type1Outline) move() error {
|
||||||
|
|||||||
@@ -249,6 +249,31 @@ func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page, strict
|
|||||||
dest.OmitLeft, dest.OmitTop = retained[1], retained[0]
|
dest.OmitLeft, dest.OmitTop = retained[1], retained[0]
|
||||||
}
|
}
|
||||||
dest.OmitZoom = retained[2] || values[2] == 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" {
|
} else if destination.Mode == "FitR" {
|
||||||
values := [4]float64{}
|
values := [4]float64{}
|
||||||
for n, v := range destination.Parameters {
|
for n, v := range destination.Parameters {
|
||||||
|
|||||||
Reference in new issue
Block a user