mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 08:46:55 +08:00
1 parent
6926ceafc9
commit
0b1dc7e6c8
7 files changed
+148
-25
No files matched your search
@@ -18,7 +18,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
"sort"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/go-text/typesetting/font/opentype/tables"
|
"github.com/go-text/typesetting/font/opentype/tables"
|
||||||
@@ -34,10 +34,11 @@ type SFNT struct {
|
|||||||
reverse map[uint16][]rune
|
reverse map[uint16][]rune
|
||||||
}
|
}
|
||||||
|
|
||||||
// sfntCmap 保存编码记录及对应字符映射
|
// sfntCmap 保存编码记录及对应字符映射,标记已解析的共享子表
|
||||||
type sfntCmap struct {
|
type sfntCmap struct {
|
||||||
platform, encoding, format uint16
|
platform, encoding, format uint16
|
||||||
mapping map[rune]uint16
|
mapping map[rune]uint16
|
||||||
|
duplicate bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseSFNT 校验独立字体的必需表、度量范围及字符映射
|
// ParseSFNT 校验独立字体的必需表、度量范围及字符映射
|
||||||
@@ -86,6 +87,9 @@ func ParseSFNTTables(table map[string][]byte) (*SFNT, error) {
|
|||||||
}
|
}
|
||||||
f.cmaps = cmaps
|
f.cmaps = cmaps
|
||||||
for _, cmap := range f.cmaps {
|
for _, cmap := range f.cmaps {
|
||||||
|
if cmap.duplicate {
|
||||||
|
continue
|
||||||
|
}
|
||||||
for _, glyph := range cmap.mapping {
|
for _, glyph := range cmap.mapping {
|
||||||
if glyph >= f.count {
|
if glyph >= f.count {
|
||||||
return nil, fmt.Errorf("cmap glyph index out of range")
|
return nil, fmt.Errorf("cmap glyph index out of range")
|
||||||
@@ -150,7 +154,7 @@ func readSFNTCmaps(data []byte) ([]sfntCmap, error) {
|
|||||||
}
|
}
|
||||||
parsed[offset] = mapping
|
parsed[offset] = mapping
|
||||||
}
|
}
|
||||||
result = append(result, sfntCmap{binary.BigEndian.Uint16(record), binary.BigEndian.Uint16(record[2:]), format, mapping})
|
result = append(result, sfntCmap{platform: binary.BigEndian.Uint16(record), encoding: binary.BigEndian.Uint16(record[2:]), format: format, mapping: mapping, duplicate: found})
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
@@ -164,11 +168,11 @@ func (f *SFNT) UnitsPerEm() uint16 { return f.units }
|
|||||||
func (f *SFNT) NumGlyphs() uint16 { return f.count }
|
func (f *SFNT) NumGlyphs() uint16 { return f.count }
|
||||||
|
|
||||||
// CmapEntries 返回各编码记录的映射项总数,共享子表按记录分别计数
|
// CmapEntries 返回各编码记录的映射项总数,共享子表按记录分别计数
|
||||||
// 返回: int 用于缓存预算的映射项数量
|
// 返回: uint64 用于缓存预算的映射项数量
|
||||||
func (f *SFNT) CmapEntries() int {
|
func (f *SFNT) CmapEntries() uint64 {
|
||||||
count := 0
|
var count uint64
|
||||||
for _, cmap := range f.cmaps {
|
for _, cmap := range f.cmaps {
|
||||||
count += len(cmap.mapping)
|
count += uint64(len(cmap.mapping))
|
||||||
}
|
}
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
@@ -178,6 +182,9 @@ func (f *SFNT) CmapEntries() int {
|
|||||||
// 返回: uint16 字形编号
|
// 返回: uint16 字形编号
|
||||||
func (f *SFNT) GlyphIndex(char rune) uint16 {
|
func (f *SFNT) GlyphIndex(char rune) uint16 {
|
||||||
for _, cmap := range f.cmaps {
|
for _, cmap := range f.cmaps {
|
||||||
|
if cmap.duplicate {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if gid := cmap.mapping[char]; gid != 0 {
|
if gid := cmap.mapping[char]; gid != 0 {
|
||||||
return gid
|
return gid
|
||||||
}
|
}
|
||||||
@@ -193,6 +200,9 @@ func (f *SFNT) glyphCharacters(gid uint16) []rune {
|
|||||||
f.reverse = make(map[uint16][]rune)
|
f.reverse = make(map[uint16][]rune)
|
||||||
seen := make(map[rune]bool)
|
seen := make(map[rune]bool)
|
||||||
for _, cmap := range f.cmaps {
|
for _, cmap := range f.cmaps {
|
||||||
|
if cmap.duplicate {
|
||||||
|
continue
|
||||||
|
}
|
||||||
for char, glyph := range cmap.mapping {
|
for char, glyph := range cmap.mapping {
|
||||||
if glyph != 0 && !seen[char] {
|
if glyph != 0 && !seen[char] {
|
||||||
f.reverse[glyph] = append(f.reverse[glyph], char)
|
f.reverse[glyph] = append(f.reverse[glyph], char)
|
||||||
@@ -201,7 +211,7 @@ func (f *SFNT) glyphCharacters(gid uint16) []rune {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, chars := range f.reverse {
|
for _, chars := range f.reverse {
|
||||||
sort.Slice(chars, func(i, j int) bool { return chars[i] < chars[j] })
|
slices.Sort(chars)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return f.reverse[gid]
|
return f.reverse[gid]
|
||||||
|
|||||||
@@ -134,7 +134,8 @@ func SubsetSFNT(data []byte, glyphs []uint16, mapped bool) ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if subset := SubsetSourceSFNT(fixed, usage); subset != nil {
|
sfnt.Tables = tables
|
||||||
|
if subset := subsetSourceSFNT(sfnt, usage, len(fixed)); subset != nil {
|
||||||
return subset, nil
|
return subset, nil
|
||||||
}
|
}
|
||||||
return fixed, nil
|
return fixed, nil
|
||||||
@@ -147,7 +148,7 @@ func SubsetSFNT(data []byte, glyphs []uint16, mapped bool) ([]byte, error) {
|
|||||||
// 入参: data 字体数据, usage 全包用字记录
|
// 入参: data 字体数据, usage 全包用字记录
|
||||||
// 返回: []byte 更小的字体子集,无确定收益时为空
|
// 返回: []byte 更小的字体子集,无确定收益时为空
|
||||||
func SubsetSourceSFNT(data []byte, usage *Usage) []byte {
|
func SubsetSourceSFNT(data []byte, usage *Usage) []byte {
|
||||||
if usage.Unsafe || len(usage.Characters)+len(usage.Glyphs) == 0 || bytes.HasPrefix(data, []byte("ttcf")) {
|
if usage == nil || usage.Unsafe || len(usage.Characters)+len(usage.Glyphs) == 0 || bytes.HasPrefix(data, []byte("ttcf")) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
fontTables, err := FileTables(data, 0)
|
fontTables, err := FileTables(data, 0)
|
||||||
@@ -162,7 +163,17 @@ func SubsetSourceSFNT(data []byte, usage *Usage) []byte {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
sfnt, err := ParseSFNTTables(fontTables)
|
sfnt, err := ParseSFNTTables(fontTables)
|
||||||
if err != nil || !sfnt.IsTrueType && !sfnt.IsCFF || fontEmbeddingFlags(sfnt)&0x0100 != 0 {
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return subsetSourceSFNT(sfnt, usage, len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// subsetSourceSFNT 复用已校验的字体表裁剪字形,仅返回小于原编码的结果
|
||||||
|
// 入参: sfnt 已解析字体, usage 用字记录, size 原编码长度
|
||||||
|
// 返回: []byte 更小的字体子集,无确定收益时为空
|
||||||
|
func subsetSourceSFNT(sfnt *SFNT, usage *Usage, size int) []byte {
|
||||||
|
if usage == nil || usage.Unsafe || len(usage.Characters)+len(usage.Glyphs) == 0 || !sfnt.IsTrueType && !sfnt.IsCFF || fontEmbeddingFlags(sfnt)&0x0100 != 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var unicodeTables []sfntCmap
|
var unicodeTables []sfntCmap
|
||||||
@@ -178,6 +189,9 @@ func SubsetSourceSFNT(data []byte, usage *Usage) []byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
glyphs := maps.Clone(usage.Glyphs)
|
glyphs := maps.Clone(usage.Glyphs)
|
||||||
|
if glyphs == nil {
|
||||||
|
glyphs = make(map[uint16]bool)
|
||||||
|
}
|
||||||
glyphs[0] = true
|
glyphs[0] = true
|
||||||
mapping := make(map[rune]uint16, len(usage.Characters))
|
mapping := make(map[rune]uint16, len(usage.Characters))
|
||||||
for char := range usage.Characters {
|
for char := range usage.Characters {
|
||||||
@@ -210,7 +224,7 @@ func SubsetSourceSFNT(data []byte, usage *Usage) []byte {
|
|||||||
tables["cmap"] = buildCmapWithVariations(sfnt.NumGlyphs(), mapping, variations)
|
tables["cmap"] = buildCmapWithVariations(sfnt.NumGlyphs(), mapping, variations)
|
||||||
delete(tables, "DSIG")
|
delete(tables, "DSIG")
|
||||||
result, err := SerializeOTF(tables)
|
result, err := SerializeOTF(tables)
|
||||||
if err != nil || len(result) >= len(data) {
|
if err != nil || len(result) >= size {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@@ -258,7 +272,7 @@ func SubsetSourceSFNT(data []byte, usage *Usage) []byte {
|
|||||||
clear(tables["head"][8:12])
|
clear(tables["head"][8:12])
|
||||||
delete(tables, "DSIG")
|
delete(tables, "DSIG")
|
||||||
result, err := SerializeOTF(tables)
|
result, err := SerializeOTF(tables)
|
||||||
if err != nil || len(result) >= len(data) {
|
if err != nil || len(result) >= size {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ func rasterImageAxis(first, end, source, limit int, scale, offset float64) []ras
|
|||||||
result := make([]rasterAxisSample, 0, end-first)
|
result := make([]rasterAxisSample, 0, end-first)
|
||||||
argument := math.Min(1, math.Abs(scale))
|
argument := math.Min(1, math.Abs(scale))
|
||||||
support := draw.CatmullRom.Support / argument
|
support := draw.CatmullRom.Support / argument
|
||||||
|
count := 0
|
||||||
for pixel := first; pixel < end; pixel++ {
|
for pixel := first; pixel < end; pixel++ {
|
||||||
center := (float64(pixel) + .5 - offset) / scale
|
center := (float64(pixel) + .5 - offset) / scale
|
||||||
if center < float64(source) || center >= float64(limit) {
|
if center < float64(source) || center >= float64(limit) {
|
||||||
@@ -155,7 +156,18 @@ func rasterImageAxis(first, end, source, limit int, scale, offset float64) []ras
|
|||||||
center -= .5
|
center -= .5
|
||||||
start := int(max(float64(source), math.Floor(center-support)))
|
start := int(max(float64(source), math.Floor(center-support)))
|
||||||
stop := int(min(float64(limit), math.Ceil(center+support)))
|
stop := int(min(float64(limit), math.Ceil(center+support)))
|
||||||
weights := make([]float64, stop-start)
|
count += stop - start
|
||||||
|
result = append(result, rasterAxisSample{pixel: pixel, first: start})
|
||||||
|
}
|
||||||
|
buffer := make([]float64, count)
|
||||||
|
for index := range result {
|
||||||
|
sample := &result[index]
|
||||||
|
center := (float64(sample.pixel)+.5-offset)/scale - .5
|
||||||
|
start := sample.first
|
||||||
|
stop := int(min(float64(limit), math.Ceil(center+support)))
|
||||||
|
n := stop - start
|
||||||
|
weights := buffer[:n:n]
|
||||||
|
buffer = buffer[n:]
|
||||||
total := 0.0
|
total := 0.0
|
||||||
for i := range weights {
|
for i := range weights {
|
||||||
distance := math.Abs((float64(start+i) - center) * argument)
|
distance := math.Abs((float64(start+i) - center) * argument)
|
||||||
@@ -167,7 +179,7 @@ func rasterImageAxis(first, end, source, limit int, scale, offset float64) []ras
|
|||||||
for i := range weights {
|
for i := range weights {
|
||||||
weights[i] /= total
|
weights[i] /= total
|
||||||
}
|
}
|
||||||
result = append(result, rasterAxisSample{pixel: pixel, first: start, weights: weights})
|
sample.weights = weights
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ func (e *Editor) compressResourceReferences(parts map[string][]byte, reader *Rea
|
|||||||
formats := make(map[string]string)
|
formats := make(map[string]string)
|
||||||
seen := make(map[resourceScope]map[[32]byte]string)
|
seen := make(map[resourceScope]map[[32]byte]string)
|
||||||
imageResults := make(map[outputImageKey][]byte)
|
imageResults := make(map[outputImageKey][]byte)
|
||||||
|
imageDigests := make(map[outputImageKey][32]byte)
|
||||||
imageSources := make(map[string]outputImageSource)
|
imageSources := make(map[string]outputImageSource)
|
||||||
ordered := slices.Sorted(maps.Keys(actual))
|
ordered := slices.Sorted(maps.Keys(actual))
|
||||||
sizes := make(map[string]uint64, len(files))
|
sizes := make(map[string]uint64, len(files))
|
||||||
@@ -307,7 +308,12 @@ func (e *Editor) compressResourceReferences(parts map[string][]byte, reader *Rea
|
|||||||
if candidate != nil {
|
if candidate != nil {
|
||||||
data = candidate
|
data = candidate
|
||||||
parts[actual[key]] = data
|
parts[actual[key]] = data
|
||||||
source.key.hash = sha256.Sum256(data)
|
digest, ok := imageDigests[cacheKey]
|
||||||
|
if !ok {
|
||||||
|
digest = sha256.Sum256(data)
|
||||||
|
imageDigests[cacheKey] = digest
|
||||||
|
}
|
||||||
|
source.key.hash = digest
|
||||||
}
|
}
|
||||||
if data != nil {
|
if data != nil {
|
||||||
source.format = outputImageFormat(data)
|
source.format = outputImageFormat(data)
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ func (r *Renderer) cachedTextGlyphPath(face *canvas.FontFace, glyph textGlyph) (
|
|||||||
for _, data := range metrics.Tables {
|
for _, data := range metrics.Tables {
|
||||||
cost += uint64(len(data)) * 8
|
cost += uint64(len(data)) * 8
|
||||||
}
|
}
|
||||||
cost += uint64(metrics.CmapEntries()) * 48
|
cost += metrics.CmapEntries() * 48
|
||||||
cache.put(face.Font, outlines, int(min(cost, uint64(cache.limit)+1)))
|
cache.put(face.Font, outlines, int(min(cost, uint64(cache.limit)+1)))
|
||||||
}
|
}
|
||||||
r.canvasState().fontOutlines[face.Font] = outlines
|
r.canvasState().fontOutlines[face.Font] = outlines
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ type pdfRenderer struct {
|
|||||||
*pdf.PDF
|
*pdf.PDF
|
||||||
glyphPaths renderCache[*canvas.Path, *canvas.Path]
|
glyphPaths renderCache[*canvas.Path, *canvas.Path]
|
||||||
fonts map[*canvas.Font]*canvas.Font
|
fonts map[*canvas.Font]*canvas.Font
|
||||||
|
cffFonts map[*canvas.Font][]*pdfFontSubset
|
||||||
images [][]image.Image
|
images [][]image.Image
|
||||||
exactImages bool
|
exactImages bool
|
||||||
imageError error
|
imageError error
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
package ofdgo
|
package ofdgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/tdewolff/canvas"
|
"github.com/tdewolff/canvas"
|
||||||
canvastext "github.com/tdewolff/canvas/text"
|
canvastext "github.com/tdewolff/canvas/text"
|
||||||
basefont "github.com/xiaoqidun/ofdgo/internal/base/font"
|
basefont "github.com/xiaoqidun/ofdgo/internal/base/font"
|
||||||
@@ -22,6 +25,12 @@ import (
|
|||||||
"github.com/xiaoqidun/pdfgo"
|
"github.com/xiaoqidun/pdfgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// pdfFontSubset 保存独立CFF输出字体及实际用字,包含缺省字形
|
||||||
|
type pdfFontSubset struct {
|
||||||
|
font *canvas.Font
|
||||||
|
glyphs map[uint16]bool
|
||||||
|
}
|
||||||
|
|
||||||
// RenderText 记录后端文字区段,写出时校验数量并关联原文
|
// RenderText 记录后端文字区段,写出时校验数量并关联原文
|
||||||
// 入参: text 画布文字, matrix 文字变换
|
// 入参: text 画布文字, matrix 文字变换
|
||||||
func (r *pdfRenderer) RenderText(text *canvas.Text, matrix canvas.Matrix) {
|
func (r *pdfRenderer) RenderText(text *canvas.Text, matrix canvas.Matrix) {
|
||||||
@@ -68,18 +77,89 @@ func (r *pdfRenderer) glyphText(face *canvas.FontFace, glyph textGlyph) (*canvas
|
|||||||
outputFace := *face
|
outputFace := *face
|
||||||
outputFace.Font = font
|
outputFace.Font = font
|
||||||
face = &outputFace
|
face = &outputFace
|
||||||
|
var text *canvas.Text
|
||||||
if glyph.GlyphID < 0 || glyph.GlyphID > 0xffff {
|
if glyph.GlyphID < 0 || glyph.GlyphID > 0xffff {
|
||||||
return canvas.NewTextLine(face, glyph.Text, canvas.Left), nil
|
text = canvas.NewTextLine(face, glyph.Text, canvas.Left)
|
||||||
|
} else {
|
||||||
|
id := uint16(glyph.GlyphID)
|
||||||
|
text = canvas.NewTextLine(face, " ", canvas.Left)
|
||||||
|
text.WalkLines(func(_ float64, spans []canvas.TextSpan) {
|
||||||
|
for i := range spans {
|
||||||
|
spans[i].Text = glyph.Text
|
||||||
|
spans[i].Glyphs = []canvastext.Glyph{{SFNT: face.Font.SFNT, Size: face.Size, ID: id, XAdvance: int32(face.Font.GlyphAdvance(id)), Text: glyph.Text}}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
id := uint16(glyph.GlyphID)
|
if font.IsCFF {
|
||||||
text := canvas.NewTextLine(face, " ", canvas.Left)
|
if err := r.cffTextFont(font, text); err != nil {
|
||||||
text.WalkLines(func(_ float64, spans []canvas.TextSpan) {
|
return nil, err
|
||||||
for i := range spans {
|
}
|
||||||
spans[i].Text = glyph.Text
|
}
|
||||||
spans[i].Glyphs = []canvastext.Glyph{{SFNT: face.Font.SFNT, Size: face.Size, ID: id, XAdvance: int32(face.Font.GlyphAdvance(id)), Text: glyph.Text}}
|
return text, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cffTextFont 按实际用字隔离CFF子集,避免后端清除字形名后超出ISOAdobe字符集容量
|
||||||
|
// 入参: font 独立输出字体, text 已定位文字
|
||||||
|
// 返回: error 字形数量或字体解析错误
|
||||||
|
func (r *pdfRenderer) cffTextFont(font *canvas.Font, text *canvas.Text) error {
|
||||||
|
const limit = 229
|
||||||
|
ids := []uint16{0}
|
||||||
|
text.WalkSpans(func(_, _ float64, span canvas.TextSpan) {
|
||||||
|
for _, glyph := range span.Glyphs {
|
||||||
|
if !slices.Contains(ids, glyph.ID) {
|
||||||
|
ids = append(ids, glyph.ID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return text, nil
|
if len(ids) > limit {
|
||||||
|
return fmt.Errorf("CFF text glyph count exceeds output subset capacity")
|
||||||
|
}
|
||||||
|
var selected *pdfFontSubset
|
||||||
|
for _, subset := range r.cffFonts[font] {
|
||||||
|
count := len(subset.glyphs)
|
||||||
|
for _, id := range ids {
|
||||||
|
if !subset.glyphs[id] {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count <= limit {
|
||||||
|
selected = subset
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if selected == nil {
|
||||||
|
output := font
|
||||||
|
if len(r.cffFonts[font]) != 0 {
|
||||||
|
loaded, err := loadCanvasFont(basefont.SFNTData(font.Tables), 0, font.Style())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
copy := *font
|
||||||
|
copy.SFNT = loaded.SFNT
|
||||||
|
output = ©
|
||||||
|
}
|
||||||
|
selected = &pdfFontSubset{font: output, glyphs: make(map[uint16]bool)}
|
||||||
|
if r.cffFonts == nil {
|
||||||
|
r.cffFonts = make(map[*canvas.Font][]*pdfFontSubset)
|
||||||
|
}
|
||||||
|
r.cffFonts[font] = append(r.cffFonts[font], selected)
|
||||||
|
}
|
||||||
|
for _, id := range ids {
|
||||||
|
selected.glyphs[id] = true
|
||||||
|
}
|
||||||
|
if selected.font != font {
|
||||||
|
text.WalkLines(func(_ float64, spans []canvas.TextSpan) {
|
||||||
|
for i := range spans {
|
||||||
|
face := *spans[i].Face
|
||||||
|
face.Font = selected.font
|
||||||
|
spans[i].Face = &face
|
||||||
|
for j := range spans[i].Glyphs {
|
||||||
|
spans[i].Glyphs[j].SFNT = selected.font.SFNT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// pdfTextSupported 核对原生文字的字形和未裁剪范围,复用实际绘制的字形缓存
|
// pdfTextSupported 核对原生文字的字形和未裁剪范围,复用实际绘制的字形缓存
|
||||||
|
|||||||
Reference in new issue
Block a user