mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 08:46:55 +08:00
1 parent
290fc57c34
commit
60eb5ac6a1
12 files changed
+504
-87
No files matched your search
@@ -10,7 +10,7 @@ require (
|
||||
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
|
||||
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
|
||||
github.com/xiaoqidun/jbig2 v1.1.1
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20261004052854-254c40ac6764
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20261004082616-4fa2e8738fca
|
||||
golang.org/x/crypto v0.57.0
|
||||
golang.org/x/image v0.46.0
|
||||
golang.org/x/text v0.42.0
|
||||
|
||||
@@ -62,8 +62,8 @@ github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk=
|
||||
github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||
github.com/xiaoqidun/jbig2 v1.1.1 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI=
|
||||
github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20261004052854-254c40ac6764 h1:YKsnM25IZ2xqdcm6Gl30boDID7ctPxNa/QPxUoCjmKQ=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20261004052854-254c40ac6764/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20261004082616-4fa2e8738fca h1:9Q47dgBr5UXsOcXZ9X5xYO4xzxaJYldslT07WRiFCak=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20261004082616-4fa2e8738fca/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
||||
|
||||
@@ -16,6 +16,7 @@ package ofdgo
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"slices"
|
||||
@@ -73,6 +74,16 @@ func (e *Editor) Objects(page int, ids []string) ([]GraphicObject, error) {
|
||||
// 入参: page 目标页面索引, objects 对象快照, dx、dy 毫米位移
|
||||
// 返回: []string 按绘制顺序排列的新对象标识, error 错误信息
|
||||
func (e *Editor) CopyObjects(page int, objects []GraphicObject, dx, dy float64) ([]string, error) {
|
||||
return e.CopyObjectsContext(context.Background(), page, objects, dx, dy)
|
||||
}
|
||||
|
||||
// CopyObjectsContext 复制对象快照,准备期间取消不提交对象或撤销记录
|
||||
// 入参: ctx 取消上下文, page 目标页, objects 对象快照, dx、dy 毫米位移
|
||||
// 返回: []string 新对象标识, error 校验或取消错误
|
||||
func (e *Editor) CopyObjectsContext(ctx context.Context, page int, objects []GraphicObject, dx, dy float64) ([]string, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !finite(dx) || !finite(dy) {
|
||||
return nil, fmt.Errorf("copy requires finite offsets")
|
||||
}
|
||||
@@ -86,12 +97,18 @@ func (e *Editor) CopyObjects(page int, objects []GraphicObject, dx, dy float64)
|
||||
if err := e.prepareSourceIDs(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prepared := make([]GraphicObject, len(objects))
|
||||
origins := make([]*editorObjectOrigin, len(objects))
|
||||
result := make([]string, len(objects))
|
||||
maximum := e.maxID + len(objects)
|
||||
validation := &editorValidation{Editor: e}
|
||||
for i, object := range objects {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[i] = strconv.Itoa(e.maxID + i + 1)
|
||||
object, err := validation.prepareCopiedObject(result[i], object)
|
||||
if err != nil {
|
||||
@@ -111,6 +128,9 @@ func (e *Editor) CopyObjects(page int, objects []GraphicObject, dx, dy float64)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.maxID = maximum
|
||||
for i, origin := range origins {
|
||||
if origin != nil {
|
||||
|
||||
+85
-29
@@ -99,7 +99,10 @@ func normalizeCFFCharstrings(data []byte) ([]byte, error) {
|
||||
if topData == nil || topStart+topSize > len(data) {
|
||||
return nil, fmt.Errorf("invalid CFF top dictionary")
|
||||
}
|
||||
dict := parseCFFDict(topData)
|
||||
dict, err := readCFFDict(topData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
charOffset := dict[17]
|
||||
if len(charOffset) != 1 {
|
||||
return nil, fmt.Errorf("missing CFF charstrings")
|
||||
@@ -216,7 +219,10 @@ func sanitizeCFF(data []byte) ([]byte, error) {
|
||||
}
|
||||
_, glbSz := getCFFIndexCount(data, offset)
|
||||
globalSubrIndexData := data[offset : offset+glbSz]
|
||||
topDict := parseCFFDict(topDictData)
|
||||
topDict, err := readCFFDict(topDictData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, isCID := topDict[1230]; !isCID {
|
||||
return data, nil
|
||||
}
|
||||
@@ -233,7 +239,10 @@ func sanitizeCFF(data []byte) ([]byte, error) {
|
||||
return sanitizeMultiFDCFF(data, hdrSize, nameIndexData, topDict, stringIndexData, globalSubrIndexData, fdArrOff, fdCount)
|
||||
}
|
||||
fontDictData, _ := getCFFIndexData(data, fdArrOff)
|
||||
fontDict := parseCFFDict(fontDictData)
|
||||
fontDict, err := readCFFDict(fontDictData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fdMat, ok := fontDict[1207]; ok && len(fdMat) == 6 {
|
||||
topMat, hasTop := topDict[1207]
|
||||
if !hasTop || len(topMat) != 6 {
|
||||
@@ -255,7 +264,10 @@ func sanitizeCFF(data []byte) ([]byte, error) {
|
||||
}
|
||||
var subrsOffRel int
|
||||
if len(privDictData) > 0 {
|
||||
pDict := parseCFFDict(privDictData)
|
||||
pDict, err := readCFFDict(privDictData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sVals, ok := pDict[19]; ok && len(sVals) > 0 {
|
||||
subrsOffRel = int(sVals[0])
|
||||
}
|
||||
@@ -284,7 +296,10 @@ func sanitizeCFF(data []byte) ([]byte, error) {
|
||||
delete(topDict, 16)
|
||||
var finalPrivData []byte
|
||||
if len(privDictData) > 0 {
|
||||
pDict := parseCFFDict(privDictData)
|
||||
pDict, err := readCFFDict(privDictData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := normalizeCFFPrivate(pDict); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -358,7 +373,11 @@ func sanitizeMultiFDCFF(data []byte, hdrSize int, nameIndexData []byte, topDict
|
||||
privateDicts := make([]cffDict, fdCount)
|
||||
for fd, item := range fdItems {
|
||||
fonts[fd] = type2Font{data: data, chars: charStrings, globals: globalSubrs, seed: 1}
|
||||
privateDicts[fd], err = readType2Private(data, parseCFFDict(item)[18], &fonts[fd])
|
||||
fontDict, dictErr := readCFFDict(item)
|
||||
if dictErr != nil {
|
||||
return nil, dictErr
|
||||
}
|
||||
privateDicts[fd], err = readType2Private(data, fontDict[18], &fonts[fd])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CFF font dictionary %d: %w", fd, err)
|
||||
}
|
||||
@@ -452,7 +471,10 @@ func parseCFFAndCountGlyphs(data []byte) (int, error) {
|
||||
}
|
||||
topDictData, _ := getCFFIndexData(data, offset)
|
||||
if topDictData != nil {
|
||||
dict := parseCFFDict(topDictData)
|
||||
dict, err := readCFFDict(topDictData)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if offsetVals, ok := dict[17]; ok && len(offsetVals) > 0 {
|
||||
charStrOff := int(offsetVals[0])
|
||||
if charStrOff > 0 && charStrOff < len(data) {
|
||||
@@ -478,62 +500,87 @@ func multiplyAffine(a, b []float64) []float64 {
|
||||
}
|
||||
}
|
||||
|
||||
// parseCFFDict 解析CFF字典数据
|
||||
// parseCFFDict 读取可选CFF字典,编码无效时返回nil
|
||||
// 入参: data 字典数据
|
||||
// 返回: cffDict 解析后的字典映射
|
||||
func parseCFFDict(data []byte) cffDict {
|
||||
dict, _ := readCFFDict(data)
|
||||
return dict
|
||||
}
|
||||
|
||||
// readCFFDict 校验CFF字典编码、数值和48项操作数上限
|
||||
// 入参: data 字典数据
|
||||
// 返回: cffDict 字典映射, error 编码或操作数错误
|
||||
func readCFFDict(data []byte) (cffDict, error) {
|
||||
dict := make(cffDict)
|
||||
var operands []float64
|
||||
i := 0
|
||||
for i < len(data) {
|
||||
b := data[i]
|
||||
i++
|
||||
if b <= 27 {
|
||||
if b <= 21 {
|
||||
op := int(b)
|
||||
if b == 12 {
|
||||
if i >= len(data) {
|
||||
break
|
||||
return nil, fmt.Errorf("truncated CFF operator")
|
||||
}
|
||||
op = 1200 + int(data[i])
|
||||
i++
|
||||
}
|
||||
if len(operands) == 0 {
|
||||
return nil, fmt.Errorf("missing CFF dictionary operands")
|
||||
}
|
||||
dict[op] = operands
|
||||
operands = nil
|
||||
} else if b == 28 {
|
||||
if i+1 < len(data) {
|
||||
val := int(int16(binary.BigEndian.Uint16(data[i:])))
|
||||
operands = append(operands, float64(val))
|
||||
i += 2
|
||||
if len(data)-i < 2 {
|
||||
return nil, fmt.Errorf("truncated CFF integer")
|
||||
}
|
||||
val := int(int16(binary.BigEndian.Uint16(data[i:])))
|
||||
operands = append(operands, float64(val))
|
||||
i += 2
|
||||
} else if b == 29 {
|
||||
if i+3 < len(data) {
|
||||
val := int(int32(binary.BigEndian.Uint32(data[i:])))
|
||||
operands = append(operands, float64(val))
|
||||
i += 4
|
||||
if len(data)-i < 4 {
|
||||
return nil, fmt.Errorf("truncated CFF integer")
|
||||
}
|
||||
val := int(int32(binary.BigEndian.Uint32(data[i:])))
|
||||
operands = append(operands, float64(val))
|
||||
i += 4
|
||||
} else if b == 30 {
|
||||
s, n := parseCFFReal(data[i:])
|
||||
if f, err := strconv.ParseFloat(s, 64); err == nil {
|
||||
operands = append(operands, f)
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if n == 0 || err != nil || !finite(f) {
|
||||
return nil, fmt.Errorf("invalid CFF real")
|
||||
}
|
||||
operands = append(operands, f)
|
||||
i += n
|
||||
} else if b >= 32 && b <= 246 {
|
||||
operands = append(operands, float64(int(b)-139))
|
||||
} else if b >= 247 && b <= 250 {
|
||||
if i < len(data) {
|
||||
b1 := int(data[i])
|
||||
i++
|
||||
operands = append(operands, float64((int(b)-247)*256+b1+108))
|
||||
if i == len(data) {
|
||||
return nil, fmt.Errorf("truncated CFF integer")
|
||||
}
|
||||
b1 := int(data[i])
|
||||
i++
|
||||
operands = append(operands, float64((int(b)-247)*256+b1+108))
|
||||
} else if b >= 251 && b <= 254 {
|
||||
if i < len(data) {
|
||||
b1 := int(data[i])
|
||||
i++
|
||||
operands = append(operands, float64(-(int(b)-251)*256-b1-108))
|
||||
if i == len(data) {
|
||||
return nil, fmt.Errorf("truncated CFF integer")
|
||||
}
|
||||
b1 := int(data[i])
|
||||
i++
|
||||
operands = append(operands, float64(-(int(b)-251)*256-b1-108))
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid CFF dictionary byte")
|
||||
}
|
||||
if len(operands) > 48 {
|
||||
return nil, fmt.Errorf("CFF dictionary operand limit exceeded")
|
||||
}
|
||||
}
|
||||
return dict
|
||||
if len(operands) != 0 {
|
||||
return nil, fmt.Errorf("unused CFF dictionary operands")
|
||||
}
|
||||
return dict, nil
|
||||
}
|
||||
|
||||
// parseCFFReal 解析CFF实数编码
|
||||
@@ -549,6 +596,9 @@ func parseCFFReal(data []byte) (string, int) {
|
||||
nibbles := []byte{b >> 4, b & 0x0F}
|
||||
for _, n := range nibbles {
|
||||
if n == 0xF {
|
||||
if b>>4 == 15 && b&15 != 15 {
|
||||
return "", 0
|
||||
}
|
||||
done = true
|
||||
break
|
||||
}
|
||||
@@ -567,8 +617,14 @@ func parseCFFReal(data []byte) (string, int) {
|
||||
if n == 0xE {
|
||||
sb.WriteString("-")
|
||||
}
|
||||
if n == 0xD {
|
||||
return "", 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if !done {
|
||||
return "", 0
|
||||
}
|
||||
return sb.String(), i
|
||||
}
|
||||
|
||||
|
||||
+8
-2
@@ -122,7 +122,10 @@ func readType2Font(data []byte) (*type2Font, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dict := parseCFFDict(tops[0])
|
||||
dict, err := readCFFDict(tops[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if value, ok := dict[1206]; ok && (len(value) != 1 || value[0] != 2) {
|
||||
return nil, fmt.Errorf("unsupported CFF charstring type")
|
||||
}
|
||||
@@ -158,7 +161,10 @@ func readType2Private(data []byte, values []float64, font *type2Font) (cffDict,
|
||||
return nil, fmt.Errorf("invalid CFF private range")
|
||||
}
|
||||
start := int(values[1])
|
||||
private = parseCFFDict(data[start : start+int(values[0])])
|
||||
private, err = readCFFDict(data[start : start+int(values[0])])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for op, target := range map[int]*float64{20: &font.def, 21: &font.nominal} {
|
||||
if value, ok := private[op]; ok {
|
||||
if len(value) != 1 || !finite(value[0]) {
|
||||
|
||||
+73
-18
@@ -75,6 +75,7 @@ type pdfImporter struct {
|
||||
fonts map[*pdfgo.Font]*pdfImportedFont
|
||||
fontWarnings map[string]bool
|
||||
clipTexts map[*pdfgo.TextClip][]TextObject
|
||||
clipPaths *renderCache[[32]byte, pdfClipPath]
|
||||
cmykSpace string
|
||||
objects []GraphicObject
|
||||
pages map[pdfgo.Reference]*pdfgo.Page
|
||||
@@ -204,6 +205,7 @@ func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFI
|
||||
importer.compositeNodes = nil
|
||||
importer.compositeSpace = nil
|
||||
importer.clipTexts = make(map[*pdfgo.TextClip][]TextObject)
|
||||
importer.clipPaths = nil
|
||||
importer.annotationData = nil
|
||||
if err := importer.compositePage(nil, func(visitor pdfgo.Visitor) error { return reader.WalkPage(ctx, page, visitor) }); err != nil {
|
||||
return fmt.Errorf("import PDF page %d: %w", index+1, err)
|
||||
@@ -279,7 +281,7 @@ func (p *pdfImporter) commitObjects() error {
|
||||
if len(p.objects) == 0 {
|
||||
return nil
|
||||
}
|
||||
if _, err := p.editor.CopyObjects(p.page, p.objects, 0, 0); err != nil {
|
||||
if _, err := p.editor.CopyObjectsContext(p.ctx, p.page, p.objects, 0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
p.objects = nil
|
||||
@@ -340,6 +342,40 @@ func pdfBounds(points []pdfgo.Point) Box {
|
||||
return Box{minX, minY, math.Max(maxX-minX, 1e-6), math.Max(maxY-minY, 1e-6)}
|
||||
}
|
||||
|
||||
// pathBoundsContext 逐段计算变换后的控制点边界,不创建临时点集合
|
||||
// 入参: ctx 取消上下文, path PDF路径
|
||||
// 返回: Box 控制点边界,空路径返回零值, error 取消错误
|
||||
func (p *pdfImporter) pathBoundsContext(ctx context.Context, path pdfgo.Path) (Box, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return Box{}, err
|
||||
}
|
||||
minX, minY, maxX, maxY := math.Inf(1), math.Inf(1), math.Inf(-1), math.Inf(-1)
|
||||
found := false
|
||||
for _, segment := range path.Segments {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return Box{}, err
|
||||
}
|
||||
for index, point := range segment.Points {
|
||||
if index&255 == 0 {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return Box{}, err
|
||||
}
|
||||
}
|
||||
point = p.matrix.Apply(point)
|
||||
minX, minY = math.Min(minX, point.X), math.Min(minY, point.Y)
|
||||
maxX, maxY = math.Max(maxX, point.X), math.Max(maxY, point.Y)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return Box{}, err
|
||||
}
|
||||
if !found {
|
||||
return Box{}, nil
|
||||
}
|
||||
return Box{minX, minY, math.Max(maxX-minX, 1e-6), math.Max(maxY-minY, 1e-6)}, nil
|
||||
}
|
||||
|
||||
// color 保留设备色彩分量并复用文档颜色空间
|
||||
// 入参: paint PDF颜色与不透明度
|
||||
// 返回: *FillColor OFD颜色
|
||||
@@ -360,8 +396,22 @@ func (p *pdfImporter) color(paint pdfgo.Paint) *FillColor {
|
||||
// 入参: path PDF路径, origin 对象边界
|
||||
// 返回: string OFD路径数据
|
||||
func (p *pdfImporter) pathData(path pdfgo.Path, origin Box) string {
|
||||
data, _ := p.pathDataContext(context.Background(), path, origin)
|
||||
return data
|
||||
}
|
||||
|
||||
// pathDataContext 转换路径并在分段处理时检查取消
|
||||
// 入参: ctx 取消上下文, path PDF路径, origin 对象边界
|
||||
// 返回: string OFD路径数据, error 取消错误
|
||||
func (p *pdfImporter) pathDataContext(ctx context.Context, path pdfgo.Path, origin Box) (string, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
data := make([]byte, 0, len(path.Segments)*48)
|
||||
for index, segment := range path.Segments {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if index != 0 {
|
||||
data = append(data, ' ')
|
||||
}
|
||||
@@ -374,7 +424,10 @@ func (p *pdfImporter) pathData(path pdfgo.Path, origin Box) string {
|
||||
data = strconv.AppendFloat(data, point.Y-origin.Y, 'g', -1, 64)
|
||||
}
|
||||
}
|
||||
return string(data)
|
||||
if err := ctx.Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// clips 保持裁剪路径的交集,不随对象CTM重复变换
|
||||
@@ -387,20 +440,21 @@ func (p *pdfImporter) clips(paths []pdfgo.Path, origin Box) (*Clips, error) {
|
||||
flag := false
|
||||
clips := &Clips{TransFlag: &flag}
|
||||
for _, path := range paths {
|
||||
area := ClipArea{}
|
||||
var points []pdfgo.Point
|
||||
for _, segment := range path.Segments {
|
||||
for _, point := range segment.Points {
|
||||
points = append(points, p.matrix.Apply(point))
|
||||
}
|
||||
if err := p.ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
area := ClipArea{}
|
||||
if len(path.Segments) != 0 {
|
||||
box := pdfBounds(points)
|
||||
encoded, err := p.clipPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
box := encoded.box
|
||||
rule := "NonZero"
|
||||
if path.EvenOdd {
|
||||
rule = "Even-Odd"
|
||||
}
|
||||
area.Path = append(area.Path, PathObject{Boundary: pdfBoundary(Box{box.X - origin.X, box.Y - origin.Y, box.W, box.H}), AbbreviatedData: p.pathData(path, box), Rule: rule})
|
||||
area.Path = append(area.Path, PathObject{Boundary: pdfBoundary(Box{box.X - origin.X, box.Y - origin.Y, box.W, box.H}), AbbreviatedData: encoded.data, Rule: rule})
|
||||
}
|
||||
for _, mark := range path.Text {
|
||||
objects, ok := p.clipTexts[mark]
|
||||
@@ -491,16 +545,13 @@ func (p *pdfImporter) appendPath(mark pdfgo.PathMark) error {
|
||||
if p.warning != nil && (mark.Fill && pdfGradientError(mark.Style.Fill) != nil || mark.Stroke && pdfGradientError(mark.Style.Stroke) != nil) {
|
||||
return p.gradientPath(mark)
|
||||
}
|
||||
var points []pdfgo.Point
|
||||
for _, segment := range mark.Path.Segments {
|
||||
for _, point := range segment.Points {
|
||||
points = append(points, p.matrix.Apply(point))
|
||||
}
|
||||
box, err := p.pathBoundsContext(p.ctx, mark.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(points) == 0 {
|
||||
if box == (Box{}) {
|
||||
return fmt.Errorf("empty PDF painted path")
|
||||
}
|
||||
box := pdfBounds(points)
|
||||
scale := math.Hypot(p.matrix[0], p.matrix[1])
|
||||
drawing, origin, ctm := *p, box, ""
|
||||
marginScale := scale
|
||||
@@ -548,7 +599,11 @@ func (p *pdfImporter) appendPath(mark pdfgo.PathMark) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
object := PathObject{Boundary: pdfBoundary(box), CTM: ctm, AbbreviatedData: drawing.pathData(mark.Path, origin), Fill: &mark.Fill, Stroke: &mark.Stroke, FillColor: fillColor, StrokeColor: &strokeColor, LineWidth: mark.Style.LineWidth * scale, Cap: []string{"Butt", "Round", "Square"}[mark.Style.Cap], Join: []string{"Miter", "Round", "Bevel"}[mark.Style.Join], MiterLimit: mark.Style.MiterLimit, Clips: clips}
|
||||
data, err := drawing.pathDataContext(p.ctx, mark.Path, origin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
object := PathObject{Boundary: pdfBoundary(box), CTM: ctm, AbbreviatedData: data, Fill: &mark.Fill, Stroke: &mark.Stroke, FillColor: fillColor, StrokeColor: &strokeColor, LineWidth: mark.Style.LineWidth * scale, Cap: []string{"Butt", "Round", "Square"}[mark.Style.Cap], Join: []string{"Miter", "Round", "Bevel"}[mark.Style.Join], MiterLimit: mark.Style.MiterLimit, Clips: clips}
|
||||
object.LineWidthSet = object.LineWidth == 0
|
||||
if mark.Path.EvenOdd {
|
||||
object.Rule = "Even-Odd"
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
|
||||
"github.com/xiaoqidun/pdfgo"
|
||||
)
|
||||
|
||||
// pdfClipPath 保存不可变的裁剪路径编码,不缓存对象位移或文字裁剪
|
||||
type pdfClipPath struct {
|
||||
box Box
|
||||
data string
|
||||
}
|
||||
|
||||
// pdfClipPathKey 按路径完整内容与变换生成键,不依赖可变切片的地址
|
||||
// 入参: ctx 取消上下文, path 裁剪路径, matrix 页面变换
|
||||
// 返回: [32]byte 内容摘要, error 取消错误
|
||||
func pdfClipPathKey(ctx context.Context, path pdfgo.Path, matrix pdfgo.Matrix) ([32]byte, error) {
|
||||
hash := sha256.New()
|
||||
var value [16]byte
|
||||
for _, number := range matrix {
|
||||
binary.LittleEndian.PutUint64(value[:8], math.Float64bits(number))
|
||||
hash.Write(value[:8])
|
||||
}
|
||||
binary.LittleEndian.PutUint64(value[:8], uint64(len(path.Segments)))
|
||||
value[8] = 0
|
||||
if path.EvenOdd {
|
||||
value[8] = 1
|
||||
}
|
||||
hash.Write(value[:9])
|
||||
for _, segment := range path.Segments {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return [32]byte{}, err
|
||||
}
|
||||
binary.LittleEndian.PutUint64(value[:8], uint64(len(segment.Operator)))
|
||||
binary.LittleEndian.PutUint64(value[8:], uint64(len(segment.Points)))
|
||||
hash.Write(value[:])
|
||||
hash.Write([]byte(segment.Operator))
|
||||
for index, point := range segment.Points {
|
||||
if index&255 == 0 {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return [32]byte{}, err
|
||||
}
|
||||
}
|
||||
binary.LittleEndian.PutUint64(value[:8], math.Float64bits(point.X))
|
||||
binary.LittleEndian.PutUint64(value[8:], math.Float64bits(point.Y))
|
||||
hash.Write(value[:])
|
||||
}
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return [32]byte{}, err
|
||||
}
|
||||
var key [32]byte
|
||||
hash.Sum(key[:0])
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// clipPath 复用页面内重复路径编码,按估算字节预算淘汰且不缓存错误
|
||||
// 入参: path 裁剪路径
|
||||
// 返回: pdfClipPath 变换后的边界及编码, error 取消错误
|
||||
func (p *pdfImporter) clipPath(path pdfgo.Path) (pdfClipPath, error) {
|
||||
key, err := pdfClipPathKey(p.ctx, path, p.matrix)
|
||||
if err != nil {
|
||||
return pdfClipPath{}, err
|
||||
}
|
||||
if p.clipPaths == nil {
|
||||
p.clipPaths = &renderCache[[32]byte, pdfClipPath]{limit: 8 << 20}
|
||||
}
|
||||
if cached, ok := p.clipPaths.get(key); ok {
|
||||
return cached, nil
|
||||
}
|
||||
box, err := p.pathBoundsContext(p.ctx, path)
|
||||
if err != nil {
|
||||
return pdfClipPath{}, err
|
||||
}
|
||||
data, err := p.pathDataContext(p.ctx, path, box)
|
||||
if err != nil {
|
||||
return pdfClipPath{}, err
|
||||
}
|
||||
result := pdfClipPath{box: box, data: data}
|
||||
p.clipPaths.put(key, result, len(data)+256)
|
||||
return result, nil
|
||||
}
|
||||
+36
-1
@@ -242,6 +242,8 @@ func (p *pdfImporter) compositeTextBounds(mark pdfgo.TextMark, stroke bool) Box
|
||||
// 返回: []pdfCompositeNode 原始图元, error 解析错误
|
||||
func (p *pdfImporter) collectCompositeNodes(walk func(pdfgo.Visitor) error) ([]pdfCompositeNode, error) {
|
||||
active := make(map[*pdfgo.Font]map[string]bool)
|
||||
var externalNames map[string]bool
|
||||
var embeddedFonts map[*pdfgo.Font]bool
|
||||
var collect func(func(pdfgo.Visitor) error) ([]pdfCompositeNode, error)
|
||||
collect = func(walk func(pdfgo.Visitor) error) ([]pdfCompositeNode, error) {
|
||||
var nodes []pdfCompositeNode
|
||||
@@ -257,6 +259,24 @@ func (p *pdfImporter) collectCompositeNodes(walk func(pdfgo.Visitor) error) ([]p
|
||||
return nil
|
||||
}
|
||||
v.Text = func(mark pdfgo.TextMark) error {
|
||||
if mark.Font.Subtype != "Type3" {
|
||||
if len(mark.Font.Program) == 0 {
|
||||
if externalNames == nil {
|
||||
externalNames = make(map[string]bool)
|
||||
}
|
||||
name := pdfEmbeddedFontName(mark.Font.Name)
|
||||
if !externalNames[fontNormalizeName(name)] {
|
||||
for _, candidate := range fontExactCandidateNames(name) {
|
||||
externalNames[fontNormalizeName(candidate)] = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if embeddedFonts == nil {
|
||||
embeddedFonts = make(map[*pdfgo.Font]bool)
|
||||
}
|
||||
embeddedFonts[mark.Font] = true
|
||||
}
|
||||
}
|
||||
var text []pdfCompositeNode
|
||||
if mark.Font.Subtype != "Type3" {
|
||||
if err := p.halftone(mark.Style); err != nil {
|
||||
@@ -327,7 +347,22 @@ func (p *pdfImporter) collectCompositeNodes(walk func(pdfgo.Visitor) error) ([]p
|
||||
err := walk(v)
|
||||
return nodes, err
|
||||
}
|
||||
return collect(walk)
|
||||
nodes, err := collect(walk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for font := range embeddedFonts {
|
||||
if !externalNames[fontNormalizeName(pdfEmbeddedFontName(font.Name))] {
|
||||
continue
|
||||
}
|
||||
if err := p.ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := p.importedFont(font); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// compositePage 保留独立不透明对象,仅将需要背景参与的效果在局部区域合成
|
||||
|
||||
@@ -36,6 +36,55 @@ type pdfImportedFont struct {
|
||||
repairLimit uint16
|
||||
}
|
||||
|
||||
// embeddedTextFont 匹配同名内嵌字体,仅复用唯一且覆盖全部字符的字形程序
|
||||
// 入参: source 外部字体, glyphs 当前文字字形
|
||||
// 返回: *pdfImportedFont 可复用字体,无可靠匹配时为空
|
||||
func (p *pdfImporter) embeddedTextFont(source *pdfgo.Font, glyphs []pdfgo.Glyph) *pdfImportedFont {
|
||||
name := pdfEmbeddedFontName(source.Name)
|
||||
if name == "" || len(glyphs) == 0 {
|
||||
return nil
|
||||
}
|
||||
var matched *pdfImportedFont
|
||||
for _, imported := range p.fonts {
|
||||
if pdfEmbeddedFontName(imported.resource.Font.FontName) != name {
|
||||
continue
|
||||
}
|
||||
if matched != nil && matched.checksum != imported.checksum {
|
||||
return nil
|
||||
}
|
||||
matched = imported
|
||||
}
|
||||
if matched == nil {
|
||||
return nil
|
||||
}
|
||||
for _, glyph := range glyphs {
|
||||
if utf8.RuneCountInString(glyph.Text) != 1 {
|
||||
return nil
|
||||
}
|
||||
char, _ := utf8.DecodeRuneInString(glyph.Text)
|
||||
id := matched.metrics.GlyphIndex(char)
|
||||
if id == 0 || id >= matched.metrics.NumGlyphs() || matched.repairLimit != 0 && id >= matched.repairLimit {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return matched
|
||||
}
|
||||
|
||||
// pdfEmbeddedFontName 去除标准六位大写子集前缀,保留字体名称和样式后缀
|
||||
// 入参: name PDF或内嵌字体名称
|
||||
// 返回: string 原字体名称
|
||||
func pdfEmbeddedFontName(name string) string {
|
||||
if len(name) <= 7 || name[6] != '+' {
|
||||
return name
|
||||
}
|
||||
for i := range 6 {
|
||||
if name[i] < 'A' || name[i] > 'Z' {
|
||||
return name
|
||||
}
|
||||
}
|
||||
return name[7:]
|
||||
}
|
||||
|
||||
// importedFont 按原PDF字体复用包装和解析结果,失败结果不进入缓存
|
||||
// 入参: source 源字体
|
||||
// 返回: *pdfImportedFont 只读导入字体, error 字体程序或后端错误
|
||||
|
||||
+56
-10
@@ -146,10 +146,17 @@ func (p *pdfImporter) groupPaths(mark pdfgo.GroupMark, walk func(pdfgo.Visitor)
|
||||
Group: func(pdfgo.GroupMark, func(pdfgo.Visitor) error) error { return errPDFGroupRaster },
|
||||
}
|
||||
collect.Path = func(path pdfgo.PathMark) error {
|
||||
if err := p.ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if path.Style.SoftMask != nil || path.Style.BlendMode != "" && path.Style.BlendMode != "Normal" && path.Style.BlendMode != "Compatible" {
|
||||
return &pdfgo.UnsupportedError{Feature: "masked or blended transparent group content"}
|
||||
}
|
||||
shape, err := ParseGeometryPath(p.pathData(path.Path, Box{}))
|
||||
data, err := p.pathDataContext(p.ctx, path.Path, Box{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
shape, err := ParseGeometryPath(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -191,7 +198,11 @@ func (p *pdfImporter) groupPaths(mark pdfgo.GroupMark, walk func(pdfgo.Visitor)
|
||||
return fmt.Errorf("invalid PDF stroke matrix")
|
||||
}
|
||||
local := pdfImporter{matrix: inverse}
|
||||
strokeShape, err = ParseGeometryPath(local.pathData(path.Path, Box{}))
|
||||
data, err = local.pathDataContext(p.ctx, path.Path, Box{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
strokeShape, err = ParseGeometryPath(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -219,6 +230,9 @@ func (p *pdfImporter) groupPaths(mark pdfgo.GroupMark, walk func(pdfgo.Visitor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
combined = append(combined, outline...)
|
||||
}
|
||||
return nil
|
||||
@@ -226,6 +240,9 @@ func (p *pdfImporter) groupPaths(mark pdfgo.GroupMark, walk func(pdfgo.Visitor)
|
||||
if err := walk(collect); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(combined) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -238,6 +255,9 @@ func (p *pdfImporter) groupPaths(mark pdfgo.GroupMark, walk func(pdfgo.Visitor)
|
||||
}
|
||||
var covered GeometryPath
|
||||
for i := len(regions) - 1; i >= 0; i-- {
|
||||
if err := p.ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
region := regions[i]
|
||||
visible := region.path
|
||||
if opaque && len(covered) > 0 {
|
||||
@@ -344,10 +364,16 @@ func (p *pdfImporter) compileGroup(build func(*pdfImporter) error) (*RasterPage,
|
||||
if p.fonts == nil {
|
||||
p.fonts = make(map[*pdfgo.Font]*pdfImportedFont)
|
||||
}
|
||||
local := pdfImporter{ctx: p.ctx, reader: p.reader, editor: editor, renderer: p.renderer, matrix: p.matrix, pageBox: p.pageBox, pageWidth: p.pageWidth, pageHeight: p.pageHeight, rasterDPI: p.rasterDPI, warning: p.warning, rasterWarned: true, fontIDs: map[*pdfgo.Font]string{}, fonts: p.fonts}
|
||||
if p.clipPaths == nil {
|
||||
p.clipPaths = &renderCache[[32]byte, pdfClipPath]{limit: 8 << 20}
|
||||
}
|
||||
local := pdfImporter{ctx: p.ctx, reader: p.reader, editor: editor, renderer: p.renderer, matrix: p.matrix, pageBox: p.pageBox, pageWidth: p.pageWidth, pageHeight: p.pageHeight, rasterDPI: p.rasterDPI, warning: p.warning, rasterWarned: true, fontIDs: map[*pdfgo.Font]string{}, fonts: p.fonts, clipPaths: p.clipPaths}
|
||||
if err := build(&local); err != nil {
|
||||
return nil, box, err
|
||||
}
|
||||
if err := p.ctx.Err(); err != nil {
|
||||
return nil, box, err
|
||||
}
|
||||
if err := local.flushPath(); err != nil {
|
||||
return nil, box, err
|
||||
}
|
||||
@@ -357,19 +383,19 @@ func (p *pdfImporter) compileGroup(build func(*pdfImporter) error) (*RasterPage,
|
||||
if err := local.commitObjects(); err != nil {
|
||||
return nil, box, err
|
||||
}
|
||||
reader, err := editor.Reader()
|
||||
reader, err := editor.resourceReader()
|
||||
if err != nil {
|
||||
return nil, box, err
|
||||
}
|
||||
defer reader.Close()
|
||||
page, err := reader.PageContentByIndex(0)
|
||||
if err != nil {
|
||||
return nil, box, err
|
||||
}
|
||||
page := &editor.pages[0]
|
||||
renderer := p.renderer.childRenderer(reader)
|
||||
renderer.TransparentBackground = true
|
||||
for _, layer := range page.Content.Layer {
|
||||
for _, object := range layer.Objects {
|
||||
if err := p.ctx.Err(); err != nil {
|
||||
return nil, box, err
|
||||
}
|
||||
bounds, err := renderer.ObjectBounds(object, layer.DrawParam)
|
||||
if err != nil {
|
||||
return nil, box, err
|
||||
@@ -388,6 +414,9 @@ func (p *pdfImporter) compileGroup(build func(*pdfImporter) error) (*RasterPage,
|
||||
return nil, box, nil
|
||||
}
|
||||
scene, err := renderer.CompilePage(page)
|
||||
if canceled := p.ctx.Err(); canceled != nil {
|
||||
return nil, box, canceled
|
||||
}
|
||||
return scene, box, err
|
||||
}
|
||||
|
||||
@@ -521,6 +550,9 @@ func pdfClippedVisitor(visitor pdfgo.Visitor, clip pdfgo.Path) pdfgo.Visitor {
|
||||
// 入参: mask PDF蒙版
|
||||
// 返回: pdfgo.Path 页面坐标裁剪路径, error 不可精确转换或几何错误
|
||||
func (p *pdfImporter) maskClip(mask *pdfgo.SoftMask) (pdfgo.Path, error) {
|
||||
if err := p.ctx.Err(); err != nil {
|
||||
return pdfgo.Path{}, err
|
||||
}
|
||||
if mask.Subtype != "Luminosity" || mask.ColorSpace == nil || mask.ColorSpace.Model != "DeviceGray" || mask.ColorSpace.Calibrated() {
|
||||
return pdfgo.Path{}, &pdfgo.UnsupportedError{Feature: "nonbinary luminosity mask"}
|
||||
}
|
||||
@@ -576,6 +608,9 @@ func (p *pdfImporter) maskClip(mask *pdfgo.SoftMask) (pdfgo.Path, error) {
|
||||
return walk(visitor)
|
||||
}
|
||||
visitor.Path = func(mark pdfgo.PathMark) error {
|
||||
if err := p.ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
paint := mark.Style.Fill
|
||||
if !mark.Fill || mark.Stroke || paint.Alpha != 1 || paint.CMYK != nil || paint.Axial != nil || paint.Radial != nil || paint.Function != nil || paint.Mesh != nil || paint.Tiling != nil || paint.Space != nil && paint.Space.Calibrated() || paint.RGB[0] != paint.RGB[1] || paint.RGB[1] != paint.RGB[2] || mark.Style.SoftMask != nil || !pdfNormalBlend(mark.Style.BlendMode) {
|
||||
return &pdfgo.UnsupportedError{Feature: "nonbinary mask graphic"}
|
||||
@@ -584,7 +619,11 @@ func (p *pdfImporter) maskClip(mask *pdfgo.SoftMask) (pdfgo.Path, error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
shape, err := ParseGeometryPath(identity.pathData(mark.Path, Box{}))
|
||||
data, err := identity.pathDataContext(p.ctx, mark.Path, Box{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
shape, err := ParseGeometryPath(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -596,7 +635,11 @@ func (p *pdfImporter) maskClip(mask *pdfgo.SoftMask) (pdfgo.Path, error) {
|
||||
if len(clip.Text) != 0 {
|
||||
return &pdfgo.UnsupportedError{Feature: "text-clipped binary mask"}
|
||||
}
|
||||
c, err := ParseGeometryPath(identity.pathData(clip, Box{}))
|
||||
data, err := identity.pathDataContext(p.ctx, clip, Box{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := ParseGeometryPath(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -621,6 +664,9 @@ func (p *pdfImporter) maskClip(mask *pdfgo.SoftMask) (pdfgo.Path, error) {
|
||||
}
|
||||
path := pdfgo.Path{}
|
||||
for _, segment := range region {
|
||||
if err := p.ctx.Err(); err != nil {
|
||||
return pdfgo.Path{}, err
|
||||
}
|
||||
point := func(p Point) pdfgo.Point { return pdfgo.Point{X: p.X, Y: p.Y} }
|
||||
switch segment.Verb {
|
||||
case GeometryMove:
|
||||
|
||||
+21
-5
@@ -21,6 +21,7 @@ import (
|
||||
"image/color"
|
||||
"math"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
|
||||
@@ -120,11 +121,11 @@ func (p *pdfImporter) image(mark pdfgo.ImageMark) error {
|
||||
}
|
||||
}
|
||||
if id == "" {
|
||||
jbig2Original, err := source.JBIG2File()
|
||||
jbig2Original, err := source.JBIG2FileContext(p.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jpegOriginal, err := source.JPEGFile()
|
||||
jpegOriginal, err := source.JPEGFileContext(p.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -192,7 +193,7 @@ func (p *pdfImporter) appendImage(mark pdfgo.ImageMark, id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// text 保留内嵌字形及逐字基线,外部字体保留名称引用
|
||||
// text 保留字形及逐字基线,外部字体优先复用同名内嵌资源
|
||||
// 入参: mark PDF文字绘制信息
|
||||
// 返回: error 错误信息
|
||||
func (p *pdfImporter) text(mark pdfgo.TextMark) error {
|
||||
@@ -256,14 +257,29 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
|
||||
return &pdfgo.UnsupportedError{Feature: "font program " + string(font.ProgramType)}
|
||||
}
|
||||
var imported *pdfImportedFont
|
||||
reused := false
|
||||
if embedded {
|
||||
imported, err = p.importedFont(font)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if imported = p.embeddedTextFont(font, mark.Glyphs); imported != nil {
|
||||
reused, embedded = true, true
|
||||
mark.Glyphs = slices.Clone(mark.Glyphs)
|
||||
for index := range mark.Glyphs {
|
||||
char, _ := utf8.DecodeRuneInString(mark.Glyphs[index].Text)
|
||||
mark.Glyphs[index].ID = imported.metrics.GlyphIndex(char)
|
||||
mark.Glyphs[index].HasID = true
|
||||
}
|
||||
}
|
||||
id := p.fontIDs[font]
|
||||
if id == "" {
|
||||
if reused {
|
||||
id, err = p.editor.addFontResource(imported.resource, imported.checksum)
|
||||
if err != nil {
|
||||
return fmt.Errorf("PDF font %s resource: %w", font.Name, err)
|
||||
}
|
||||
p.editor.fontMetrics[id] = imported.metrics
|
||||
} else if id == "" {
|
||||
var err error
|
||||
if embedded {
|
||||
if imported.repairLimit != 0 {
|
||||
@@ -324,7 +340,7 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
|
||||
return err
|
||||
}
|
||||
gid := glyph.ID
|
||||
if embedded && font.ProgramType == "FontFile" {
|
||||
if embedded && !reused && font.ProgramType == "FontFile" {
|
||||
gid = imported.type1Glyphs[glyph.Name]
|
||||
glyph.HasID = true
|
||||
}
|
||||
|
||||
+53
-19
@@ -241,6 +241,31 @@ func (e *Editor) reader(progress editorProgress) (*Reader, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// resourceReader 为临时图元编译准备资源索引,不序列化页面或作为公共快照
|
||||
// 返回: *Reader 资源阅读器, error 资源错误
|
||||
func (e *Editor) resourceReader() (*Reader, error) {
|
||||
if e.source != nil || len(e.origins) != 0 {
|
||||
return nil, fmt.Errorf("resource reader requires created objects")
|
||||
}
|
||||
if err := e.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &Reader{files: make(map[string][]byte)}
|
||||
if err := e.writePartsWithPages(func(name string, data []byte, _ bool) error {
|
||||
r.files[name] = data
|
||||
return nil
|
||||
}, nil, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.initRoot(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := r.Doc(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// validate 校验文档保存条件
|
||||
// 返回: error 错误信息
|
||||
func (e *Editor) validate() error {
|
||||
@@ -269,6 +294,13 @@ func (e *Editor) validate() error {
|
||||
// 入参: write 条目写入方法, progress 保存进度回调,预览时为nil
|
||||
// 返回: error 错误信息
|
||||
func (e *Editor) writeParts(write func(string, []byte, bool) error, progress editorProgress) error {
|
||||
return e.writePartsWithPages(write, progress, true)
|
||||
}
|
||||
|
||||
// writePartsWithPages 统一生成包条目,临时编译可省去页面XML
|
||||
// 入参: write 条目写入方法, progress 保存进度回调, includePages 是否写出页面
|
||||
// 返回: error 写入错误
|
||||
func (e *Editor) writePartsWithPages(write func(string, []byte, bool) error, progress editorProgress, includePages bool) error {
|
||||
if len(e.removedPages) != 0 {
|
||||
output := write
|
||||
write = func(name string, data []byte, binary bool) error {
|
||||
@@ -359,27 +391,29 @@ func (e *Editor) writeParts(write func(string, []byte, bool) error, progress edi
|
||||
return err
|
||||
}
|
||||
}
|
||||
for i, page := range e.pages {
|
||||
if err := progress.report("pages", i, len(e.pages)); err != nil {
|
||||
if includePages {
|
||||
for i, page := range e.pages {
|
||||
if err := progress.report("pages", i, len(e.pages)); err != nil {
|
||||
return err
|
||||
}
|
||||
name := packagePagePath(e.packageDirectory(), page.ID)
|
||||
if len(e.origins) == 0 {
|
||||
if err := writeXML(name, func(x *ofdXML) { x.page(page) }); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
data, err := e.sourceNewPageXML(page)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := write(name, data, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := progress.report("pages", len(e.pages), len(e.pages)); err != nil {
|
||||
return err
|
||||
}
|
||||
name := packagePagePath(e.packageDirectory(), page.ID)
|
||||
if len(e.origins) == 0 {
|
||||
if err := writeXML(name, func(x *ofdXML) { x.page(page) }); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
data, err := e.sourceNewPageXML(page)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := write(name, data, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := progress.report("pages", len(e.pages), len(e.pages)); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, resources := range [][]editorResource{fonts, images} {
|
||||
for _, resource := range resources {
|
||||
|
||||
Reference in new issue
Block a user