mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 13:56:32 +08:00
1 parent
5d599dc835
commit
290fc57c34
17 files changed
+220
-38
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-20261004025424-ce051c731285
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20261004052854-254c40ac6764
|
||||
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-20261004025424-ce051c731285 h1:KApg/AQoRuFj6UzFyNY5yOmb1di749tzYuOx9VqY0vU=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20261004025424-ce051c731285/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
||||
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/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=
|
||||
|
||||
+30
-12
@@ -155,26 +155,16 @@ func (r *Reader) parseAnnotations(doc *Document) error {
|
||||
if doc.Annotations == "" {
|
||||
return nil
|
||||
}
|
||||
annPath := r.ResPath(doc.Annotations)
|
||||
f, err := r.openFile(annPath)
|
||||
annotations, err := r.annotationIndex(doc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
var annotations Annotations
|
||||
if err := xml.NewDecoder(f).Decode(&annotations); err != nil {
|
||||
return err
|
||||
}
|
||||
if r.Annots == nil {
|
||||
r.Annots = make(map[string][]Annotation)
|
||||
}
|
||||
r.annotationFiles = make(map[string][]string)
|
||||
annPath := r.ResPath(doc.Annotations)
|
||||
for _, page := range annotations.Page {
|
||||
annotPath := resolveResourcePath(annPath, "", page.FileLoc)
|
||||
if file, ok := r.packageFile(annotPath); ok {
|
||||
annotPath = cleanPackagePath(file.Name)
|
||||
}
|
||||
r.annotationFiles[annotPath] = append(r.annotationFiles[annotPath], page.PageID)
|
||||
af, err := r.openFile(annotPath)
|
||||
if err != nil {
|
||||
continue
|
||||
@@ -189,3 +179,31 @@ func (r *Reader) parseAnnotations(doc *Document) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// annotationIndex 读取注解文件索引,不解析页面外观
|
||||
// 入参: doc 文档结构
|
||||
// 返回: Annotations 注解索引, error 错误信息
|
||||
func (r *Reader) annotationIndex(doc *Document) (Annotations, error) {
|
||||
if doc.Annotations == "" {
|
||||
return Annotations{}, nil
|
||||
}
|
||||
annPath := r.ResPath(doc.Annotations)
|
||||
f, err := r.openFile(annPath)
|
||||
if err != nil {
|
||||
return Annotations{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
var annotations Annotations
|
||||
if err := xml.NewDecoder(f).Decode(&annotations); err != nil {
|
||||
return Annotations{}, err
|
||||
}
|
||||
r.annotationFiles = make(map[string][]string)
|
||||
for _, page := range annotations.Page {
|
||||
annotPath := resolveResourcePath(annPath, "", page.FileLoc)
|
||||
if file, ok := r.packageFile(annotPath); ok {
|
||||
annotPath = cleanPackagePath(file.Name)
|
||||
}
|
||||
r.annotationFiles[annotPath] = append(r.annotationFiles[annotPath], page.PageID)
|
||||
}
|
||||
return annotations, nil
|
||||
}
|
||||
+29
-2
@@ -35,6 +35,9 @@ import (
|
||||
"github.com/xiaoqidun/jbig2"
|
||||
)
|
||||
|
||||
// editorPathValidationLimit 限制同次对象校验保留的路径数量
|
||||
const editorPathValidationLimit = 1024
|
||||
|
||||
// Editor 编辑OFD文档,长度单位为毫米,页面索引从0开始,实例需串行使用
|
||||
// Info可修改文档元数据,通过方法管理页面、对象和资源,WriteTo另存结果,不覆盖输入
|
||||
// OnWriteProgress可选,按fonts、pages、references、resources、compress、write阶段同步回报进度,返回错误则停止保存
|
||||
@@ -785,7 +788,14 @@ func (e *Editor) findObject(page int, id string) (*Layer, int, error) {
|
||||
// 入参: id 对象标识, object 对象内容
|
||||
// 返回: GraphicObject 对象内容, error 错误信息
|
||||
func (e *Editor) prepareObject(id string, object GraphicObject) (GraphicObject, error) {
|
||||
object, err := (&editorValidation{Editor: e}).prepareObject(id, object)
|
||||
return (&editorValidation{Editor: e}).prepareObjectCopy(id, object)
|
||||
}
|
||||
|
||||
// prepareObjectCopy 校验对象并隔离输入数据,同次校验共用几何缓存
|
||||
// 入参: id 对象标识, object 对象内容
|
||||
// 返回: GraphicObject 独立副本, error 错误信息
|
||||
func (v *editorValidation) prepareObjectCopy(id string, object GraphicObject) (GraphicObject, error) {
|
||||
object, err := v.prepareObject(id, object)
|
||||
if err != nil {
|
||||
return GraphicObject{}, err
|
||||
}
|
||||
@@ -819,7 +829,7 @@ func (v *editorValidation) prepareObject(id string, object GraphicObject) (Graph
|
||||
fill, stroke = obj.FillColor, (*FillColor)(obj.StrokeColor)
|
||||
case "PathObject":
|
||||
obj := &object.PathObject
|
||||
if err := creationPath(obj.AbbreviatedData); err != nil {
|
||||
if err := v.creationPath(obj.AbbreviatedData); err != nil {
|
||||
return GraphicObject{}, err
|
||||
}
|
||||
if obj.Rule != "" && obj.Rule != "NonZero" && obj.Rule != "Even-Odd" {
|
||||
@@ -1185,6 +1195,23 @@ func creationPath(value string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// creationPath 在同次校验中复用相同路径的成功结果,不缓存错误或依赖可变资源
|
||||
// 入参: value 路径数据
|
||||
// 返回: error 错误信息
|
||||
func (v *editorValidation) creationPath(value string) error {
|
||||
if v.paths[value] {
|
||||
return nil
|
||||
}
|
||||
if err := creationPath(value); err != nil {
|
||||
return err
|
||||
}
|
||||
if v.paths == nil || len(v.paths) >= editorPathValidationLimit {
|
||||
v.paths = make(map[string]bool)
|
||||
}
|
||||
v.paths[value] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// creationNumbers 解析创建接口的有限十进制数值序列
|
||||
// 入参: value 数值序列, count 数值数量
|
||||
// 返回: []float64 数值, error 错误信息
|
||||
|
||||
@@ -33,6 +33,9 @@ func (e *Editor) Annotations(index int) ([]AnnotationInfo, error) {
|
||||
if e.source == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if _, err := e.source.reader.Doc(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.source.reader.annotationInfos(index, e.pages[index].ID), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -395,10 +395,11 @@ func (e *Editor) commitAnnotationParts(base *editorSource, parts map[string][]by
|
||||
if err := reader.initRoot(); err != nil {
|
||||
return err
|
||||
}
|
||||
doc, err := reader.Doc()
|
||||
doc, err := reader.docStructure()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = reader.annotationIndex(doc)
|
||||
before, after := e.source, *base
|
||||
after.reader, after.document = reader, doc
|
||||
if before == nil {
|
||||
|
||||
@@ -90,9 +90,10 @@ func (e *Editor) CopyObjects(page int, objects []GraphicObject, dx, dy float64)
|
||||
origins := make([]*editorObjectOrigin, len(objects))
|
||||
result := make([]string, len(objects))
|
||||
maximum := e.maxID + len(objects)
|
||||
validation := &editorValidation{Editor: e}
|
||||
for i, object := range objects {
|
||||
result[i] = strconv.Itoa(e.maxID + i + 1)
|
||||
object, err := e.prepareCopiedObject(result[i], object)
|
||||
object, err := validation.prepareCopiedObject(result[i], object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -124,13 +125,18 @@ func (e *Editor) CopyObjects(page int, objects []GraphicObject, dx, dy float64)
|
||||
if target < len(layers) {
|
||||
target = len(layers) - 1
|
||||
}
|
||||
for i, object := range prepared {
|
||||
for i := 0; i < len(prepared); {
|
||||
style := e.copiedLayerStyle(objects[i])
|
||||
if target == len(layers) || layers[target].DrawParam != style {
|
||||
layers = append(layers, Layer{ID: e.nextID(), Type: "Body", DrawParam: style})
|
||||
target = len(layers) - 1
|
||||
}
|
||||
layers[target].Objects = append(layers[target].Objects, object)
|
||||
end := i + 1
|
||||
for end < len(prepared) && e.copiedLayerStyle(objects[end]) == style {
|
||||
end++
|
||||
}
|
||||
layers[target].Objects = append(layers[target].Objects, prepared[i:end]...)
|
||||
i = end
|
||||
}
|
||||
e.replaceLayers(page, layers)
|
||||
return result, nil
|
||||
|
||||
@@ -32,12 +32,13 @@ type PatternStyle struct {
|
||||
CTM *string
|
||||
}
|
||||
|
||||
// editorValidation 保存同次对象校验的底纹和矢量引用路径,允许共享但拒绝循环
|
||||
// editorValidation 保存同次对象校验的引用路径及已验证几何,允许共享但拒绝循环
|
||||
type editorValidation struct {
|
||||
*Editor
|
||||
patterns map[*Pattern]bool
|
||||
composites map[string]bool
|
||||
vectors map[string]*editorCompositeNode
|
||||
paths map[string]bool
|
||||
}
|
||||
|
||||
// GradientStops 解析当前文档的渐变分段,保留顺序并合并透明度
|
||||
|
||||
@@ -64,9 +64,10 @@ func (e *Editor) CopyObjectsToComposite(page int, path ObjectPath, objects []Gra
|
||||
}
|
||||
}
|
||||
var content []byte
|
||||
validation := &editorValidation{Editor: e}
|
||||
for _, source := range objects {
|
||||
id := e.nextID()
|
||||
object, err := e.prepareCopiedObject(id, source)
|
||||
object, err := validation.prepareCopiedObject(id, source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -672,7 +672,8 @@ func editorPreservedObject(before, after GraphicObject) bool {
|
||||
// prepareCopiedObject 保真复制原对象,不重新生成字形或丢弃未修改字段
|
||||
// 入参: id 新标识, object 对象快照
|
||||
// 返回: GraphicObject 独立副本, error 错误信息
|
||||
func (e *Editor) prepareCopiedObject(id string, object GraphicObject) (GraphicObject, error) {
|
||||
func (v *editorValidation) prepareCopiedObject(id string, object GraphicObject) (GraphicObject, error) {
|
||||
e := v.Editor
|
||||
origin := e.snapshotOrigin(object)
|
||||
if origin == nil && object.origin != nil {
|
||||
references := make(map[string]bool)
|
||||
@@ -684,7 +685,7 @@ func (e *Editor) prepareCopiedObject(id string, object GraphicObject) (GraphicOb
|
||||
}
|
||||
object.origin = nil
|
||||
if origin == nil {
|
||||
return e.prepareObject(id, object)
|
||||
return v.prepareObjectCopy(id, object)
|
||||
}
|
||||
if !editorXMLTransformable(origin.node) || !editorXMLContainersSupported(origin.node) || !editorXMLCopyable(origin.node) || origin.reason != nil && (object.Type != "PathObject" || editReason(origin.reason) != EditUnsupportedColor) {
|
||||
return GraphicObject{}, fmt.Errorf("object cannot be copied without changing its original structure")
|
||||
@@ -704,7 +705,7 @@ func (e *Editor) prepareCopiedObject(id string, object GraphicObject) (GraphicOb
|
||||
if !editorXMLSupported(origin.node) {
|
||||
return GraphicObject{}, fmt.Errorf("copy would replace unsupported object content")
|
||||
}
|
||||
return e.prepareObject(id, object)
|
||||
return v.prepareObjectCopy(id, object)
|
||||
}
|
||||
if err := validateEditorGeometry(object); err != nil {
|
||||
return GraphicObject{}, err
|
||||
|
||||
+35
-1
@@ -391,6 +391,18 @@ func (e *Editor) sourcePageXML(index int, source *editorSourcePage) ([]byte, err
|
||||
// 入参: layer 图层
|
||||
// 返回: []byte 图层XML, error 错误信息
|
||||
func (e *Editor) sourceLayerXML(layer Layer) ([]byte, error) {
|
||||
if !slices.ContainsFunc(layer.Objects, func(object GraphicObject) bool { return e.objectOrigin(editorObjectID(object)) != nil }) {
|
||||
data, err := encodeOFDXML(func(x *ofdXML) {
|
||||
attrs := ofdAttrs{{Name: xml.Name{Local: "ID"}, Value: layer.ID}, {Name: xml.Name{Local: "Type"}, Value: layer.Type}}
|
||||
attrs.add("DrawParam", layer.DrawParam)
|
||||
x.root("Layer", attrs)
|
||||
for _, object := range layer.Objects {
|
||||
x.object(object, false)
|
||||
}
|
||||
x.end("Layer")
|
||||
})
|
||||
return bytes.TrimPrefix(data, []byte(xml.Header)), err
|
||||
}
|
||||
var content []byte
|
||||
for _, object := range layer.Objects {
|
||||
var data []byte
|
||||
@@ -417,6 +429,17 @@ func (e *Editor) sourceLayerXML(layer Layer) ([]byte, error) {
|
||||
// 入参: page 页面
|
||||
// 返回: []byte 页面XML, error 错误信息
|
||||
func (e *Editor) sourceNewPageXML(page PageContent) ([]byte, error) {
|
||||
preserved := false
|
||||
for _, layer := range page.Content.Layer {
|
||||
if slices.ContainsFunc(layer.Objects, func(object GraphicObject) bool { return e.objectOrigin(editorObjectID(object)) != nil }) {
|
||||
preserved = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !preserved {
|
||||
data, err := encodeOFDXML(func(x *ofdXML) { x.page(page) })
|
||||
return bytes.TrimPrefix(data, []byte(xml.Header)), err
|
||||
}
|
||||
area, err := editorXMLContainer("Area", nil, editorXMLText("PhysicalBox", page.Area.PhysicalBox))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -433,7 +456,15 @@ func (e *Editor) sourceNewPageXML(page PageContent) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return editorXMLContainer("Page", nil, append(area, content...))
|
||||
data := append(area, content...)
|
||||
if len(page.Actions) != 0 {
|
||||
actions, err := encodeOFDXML(func(x *ofdXML) { x.actions(page.Actions) })
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = append(data, bytes.TrimPrefix(actions, []byte(xml.Header))...)
|
||||
}
|
||||
return editorXMLContainer("Page", nil, data)
|
||||
}
|
||||
|
||||
// writeSource 将未修改ZIP条目直接复制到新包,逐项写入改动,不持有原资源解压副本
|
||||
@@ -585,6 +616,9 @@ func (e *Editor) sourceReader(progress editorProgress) (*Reader, error) {
|
||||
delete(reader.pageHeaderCache, name)
|
||||
}
|
||||
}
|
||||
if _, err := reader.Doc(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return reader, nil
|
||||
}
|
||||
files := maps.Clone(e.source.reader.files)
|
||||
|
||||
@@ -45,6 +45,13 @@ type FontOutlineBatch interface {
|
||||
GlyphOutlines(glyphs []uint16, size float64) ([]GeometryPath, error)
|
||||
}
|
||||
|
||||
// FontGlyphBounds 提供与字形轮廓一致的边界,字号及边界单位为毫米
|
||||
// 不使用字体全局边界替代单个字形,空轮廓返回零边界
|
||||
type FontGlyphBounds interface {
|
||||
FontOutlines
|
||||
GlyphBounds(glyph uint16, size float64) (Box, error)
|
||||
}
|
||||
|
||||
// FontGlyphDiagnostics 提供已提取字形的恢复提示,不将提示作为轮廓解析失败
|
||||
// 字形不存在或不支持的指令仍由GlyphOutline返回错误
|
||||
type FontGlyphDiagnostics interface {
|
||||
|
||||
+10
-1
@@ -27,18 +27,24 @@ import (
|
||||
// 入参: ctx 取消上下文, page PDF页面, strict 严格检查开关
|
||||
// 返回: error 错误信息
|
||||
func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page, strict bool) error {
|
||||
annotations, err := page.Annotations()
|
||||
annotations, err := page.AnnotationsContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parents := make(map[pdfgo.Reference]int)
|
||||
for i, annotation := range annotations {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if annotation.Reference != (pdfgo.Reference{}) && annotation.Subtype != "Popup" {
|
||||
parents[annotation.Reference] = i
|
||||
}
|
||||
}
|
||||
attached := make(map[int]bool)
|
||||
for i, annotation := range annotations {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if annotation.Subtype != "Popup" {
|
||||
continue
|
||||
}
|
||||
@@ -61,6 +67,9 @@ func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page, strict
|
||||
}
|
||||
}
|
||||
for i, annotation := range annotations {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if attached[i] {
|
||||
continue
|
||||
}
|
||||
|
||||
+16
-7
@@ -313,8 +313,11 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
|
||||
var glyphPaths []pdfgo.Path
|
||||
const unit = 25.4 / 72
|
||||
m := p.matrix.Mul(mark.Matrix).Mul(pdfgo.Matrix{1 / unit, 0, 0, -1 / unit, 0, 0})
|
||||
var points []pdfgo.Point
|
||||
object := TextObject{Font: id, Size: mark.Size * unit, HScale: mark.HorizontalScale}
|
||||
points := make([]pdfgo.Point, 0, 4*len(mark.Glyphs))
|
||||
object := TextObject{Font: id, Size: mark.Size * unit, HScale: mark.HorizontalScale, TextCode: make([]TextCode, 0, len(mark.Glyphs))}
|
||||
if embedded {
|
||||
object.CGTransform = make([]CGTransform, 0, len(mark.Glyphs))
|
||||
}
|
||||
position := 0
|
||||
for n, glyph := range mark.Glyphs {
|
||||
if err := p.ctx.Err(); err != nil {
|
||||
@@ -360,7 +363,17 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
|
||||
position += count
|
||||
}
|
||||
if embedded || outlineStroke {
|
||||
path, err := outline.GlyphOutline(gid, object.Size)
|
||||
var path GeometryPath
|
||||
var bounds Box
|
||||
var err error
|
||||
if provider, ok := outline.(FontGlyphBounds); ok && !outlineStroke {
|
||||
bounds, err = provider.GlyphBounds(gid, object.Size)
|
||||
} else {
|
||||
path, err = outline.GlyphOutline(gid, object.Size)
|
||||
if err == nil {
|
||||
bounds, err = path.Bounds()
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("PDF font %s outline: %w", font.Name, err)
|
||||
}
|
||||
@@ -378,10 +391,6 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
bounds, err := path.Bounds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, point := range []pdfgo.Point{{X: bounds.X, Y: bounds.Y}, {X: bounds.X + bounds.W, Y: bounds.Y}, {X: bounds.X, Y: bounds.Y + bounds.H}, {X: bounds.X + bounds.W, Y: bounds.Y + bounds.H}} {
|
||||
points = append(points, m.Apply(pdfgo.Point{X: origin.X*unit + point.X*mark.HorizontalScale, Y: -origin.Y*unit + point.Y}))
|
||||
}
|
||||
|
||||
+17
-2
@@ -46,6 +46,7 @@ type Reader struct {
|
||||
compositeGraphicUnitCache map[string]*CompositeGraphicUnit
|
||||
pageHeaderCache map[string]PageContent
|
||||
doc *Document
|
||||
relatedPending bool
|
||||
documentIndex int
|
||||
Stamps map[string][]Stamp
|
||||
Annots map[string][]Annotation
|
||||
@@ -128,6 +129,21 @@ func (r *Reader) Document(index int) (*Reader, error) {
|
||||
// Doc 获取当前文档结构
|
||||
// 返回: *Document 文档结构, error 错误信息
|
||||
func (r *Reader) Doc() (*Document, error) {
|
||||
doc, err := r.docStructure()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.relatedPending {
|
||||
r.relatedPending = false
|
||||
_ = r.parseAnnotations(doc)
|
||||
_ = r.parseSignatures(doc)
|
||||
}
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
// docStructure 读取文档及资源索引,关联注解和签名由Doc按需解析
|
||||
// 返回: *Document 文档结构, error 错误信息
|
||||
func (r *Reader) docStructure() (*Document, error) {
|
||||
if r.doc != nil {
|
||||
return r.doc, nil
|
||||
}
|
||||
@@ -155,8 +171,7 @@ func (r *Reader) Doc() (*Document, error) {
|
||||
r.loadRes(res)
|
||||
}
|
||||
r.doc = &doc
|
||||
_ = r.parseAnnotations(&doc)
|
||||
_ = r.parseSignatures(&doc)
|
||||
r.relatedPending = true
|
||||
return r.doc, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -16,16 +16,33 @@ package ofdgo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/tdewolff/canvas"
|
||||
"github.com/tdewolff/font"
|
||||
)
|
||||
|
||||
// canvasGlyphBoundsLimit 限制单个字体保留的字形与字号边界数量
|
||||
const canvasGlyphBoundsLimit = 4096
|
||||
|
||||
// canvasFontMetrics 保留默认字体度量,写出时不更新原始时间
|
||||
type canvasFontMetrics struct {
|
||||
*font.SFNT
|
||||
*fontShaper
|
||||
outlines *sfntOutliner
|
||||
bounds *canvasFontBounds
|
||||
}
|
||||
|
||||
// canvasFontBounds 缓存已成功提取的字形边界,不保留轮廓或失败结果
|
||||
type canvasFontBounds struct {
|
||||
mu sync.Mutex
|
||||
values map[canvasGlyphSize]Box
|
||||
}
|
||||
|
||||
// canvasGlyphSize 区分字形编号及精确毫米字号
|
||||
type canvasGlyphSize struct {
|
||||
glyph uint16
|
||||
size float64
|
||||
}
|
||||
|
||||
// ResolveFont 按需加载字体资源,保留内嵌数据及外部字体的名称、样式和集合索引匹配
|
||||
@@ -43,7 +60,7 @@ func (CanvasBackend) OpenFont(data []byte) (FontMetrics, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metrics := &canvasFontMetrics{SFNT: sfnt, outlines: &sfntOutliner{font: sfnt}}
|
||||
metrics := &canvasFontMetrics{SFNT: sfnt, outlines: &sfntOutliner{font: sfnt}, bounds: &canvasFontBounds{}}
|
||||
metrics.fontShaper = &fontShaper{metrics: metrics}
|
||||
return metrics, nil
|
||||
}
|
||||
@@ -68,6 +85,38 @@ func (f canvasFontMetrics) GlyphOutline(glyph uint16, size float64) (GeometryPat
|
||||
return *geometryFromCanvasPath(path), nil
|
||||
}
|
||||
|
||||
// GlyphBounds 返回字形轮廓的精确边界,重复编号及字号复用有限缓存
|
||||
// 入参: glyph 字形编号, size 字号,单位为毫米
|
||||
// 返回: Box 字形边界, error 字形解析错误
|
||||
func (f canvasFontMetrics) GlyphBounds(glyph uint16, size float64) (Box, error) {
|
||||
key := canvasGlyphSize{glyph: glyph, size: size}
|
||||
if f.bounds != nil {
|
||||
f.bounds.mu.Lock()
|
||||
value, ok := f.bounds.values[key]
|
||||
f.bounds.mu.Unlock()
|
||||
if ok {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
outline, err := f.GlyphOutline(glyph, size)
|
||||
if err != nil {
|
||||
return Box{}, err
|
||||
}
|
||||
value, err := outline.Bounds()
|
||||
if err != nil {
|
||||
return Box{}, err
|
||||
}
|
||||
if f.bounds != nil {
|
||||
f.bounds.mu.Lock()
|
||||
if f.bounds.values == nil || len(f.bounds.values) >= canvasGlyphBoundsLimit {
|
||||
f.bounds.values = make(map[canvasGlyphSize]Box)
|
||||
}
|
||||
f.bounds.values[key] = value
|
||||
f.bounds.mu.Unlock()
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// GlyphWarning 返回字形指令栈下溢后保留设计轮廓的提示,原始字体不变
|
||||
// 入参: glyph 字形编号
|
||||
// 返回: error 恢复原因,无恢复时为空
|
||||
|
||||
@@ -517,6 +517,7 @@ func (x *ofdXML) layer(layer Layer) {
|
||||
var attrs ofdAttrs
|
||||
attrs.add("ID", layer.ID)
|
||||
attrs.add("Type", layer.Type)
|
||||
attrs.add("DrawParam", layer.DrawParam)
|
||||
x.start("Layer", attrs)
|
||||
for _, object := range layer.Objects {
|
||||
x.object(object, false)
|
||||
|
||||
Reference in new issue
Block a user