mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 13:56:32 +08:00
1 parent
7e774f99df
commit
5a88a9f31f
8 files changed
+78
-22
No files matched your search
+8
-2
@@ -49,6 +49,7 @@ type Editor struct {
|
||||
pages []PageContent
|
||||
resources []editorResource
|
||||
fonts map[string]*FontResource
|
||||
fontRegistration editorFontRegistration
|
||||
fontDirs []string
|
||||
fontFS []fs.FS
|
||||
fontRenderer *Renderer
|
||||
@@ -663,7 +664,8 @@ func (e *Editor) TransformObject(page int, id string, dx, dy, scale float64) err
|
||||
}
|
||||
|
||||
// ReplaceImage 替换图片资源,保留对象标识和绘制顺序,一次撤销恢复资源及布局
|
||||
// 入参: page 页面索引, id 图片对象标识, data PNG或JPEG数据, fit 为contain、cover或空,空值保留原变换和裁剪
|
||||
// 更换主图资源时清除旧替换图引用,保留蒙版及其他绘制属性
|
||||
// 入参: page 页面索引, id 图片对象标识, data AddImage支持的图片数据, fit 为contain、cover或空,空值保留原变换和裁剪
|
||||
// 返回: error 错误信息
|
||||
func (e *Editor) ReplaceImage(page int, id string, data []byte, fit string) error {
|
||||
object, err := e.Object(page, id)
|
||||
@@ -682,10 +684,14 @@ func (e *Editor) ReplaceImage(page int, id string, data []byte, fit string) erro
|
||||
return err
|
||||
}
|
||||
}
|
||||
object.ImageObject.ResourceID, err = e.AddImage(data)
|
||||
resource, err := e.AddImage(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !sameResourceID(object.ImageObject.ResourceID, resource) {
|
||||
object.ImageObject.Substitution = ""
|
||||
}
|
||||
object.ImageObject.ResourceID = resource
|
||||
if fit != "" {
|
||||
if err := e.fitImage(&object.ImageObject, fit); err != nil {
|
||||
return err
|
||||
|
||||
@@ -973,6 +973,7 @@ func compositeBoundary(object GraphicObject, parent Matrix, inward bool) Graphic
|
||||
}
|
||||
|
||||
// writeCompositeNode 保留原文逐级合并内部修改,仅复制发生变化的资源分支
|
||||
// 内容变化的矢量资源移除旧缩略图和替换图,不影响原资源及其引用者
|
||||
// 入参: node 实例节点
|
||||
// 返回: []byte 新片段, bool 是否变化, error 错误信息
|
||||
func (e *Editor) writeCompositeNode(node *editorCompositeNode) ([]byte, bool, error) {
|
||||
@@ -993,8 +994,15 @@ func (e *Editor) writeCompositeNode(node *editorCompositeNode) ([]byte, bool, er
|
||||
maps.Copy(states, child.states)
|
||||
}
|
||||
}
|
||||
data := editorPatchXML(node.data, patches)
|
||||
changed := node.changed || len(patches) != 0
|
||||
if changed && node.node.name.Local == "CompositeGraphicUnit" {
|
||||
for _, name := range []string{"Thumbnail", "Substitution"} {
|
||||
if child := node.node.child(name); child != nil {
|
||||
patches = append(patches, editorXMLPatch{start: child.start, end: child.end})
|
||||
}
|
||||
}
|
||||
}
|
||||
data := editorPatchXML(node.data, patches)
|
||||
node.states = states
|
||||
if node.ref != nil {
|
||||
fragment, dirty, err := e.writeCompositeNode(node.ref)
|
||||
|
||||
@@ -112,7 +112,8 @@ func (e *Editor) OrderCompositeObjects(page int, path ObjectPath, indexes []int,
|
||||
}
|
||||
|
||||
// ReplaceCompositeImage 替换内部图片数据,保留变换、裁剪、蒙版、边框和透明度
|
||||
// 入参: page 页面索引, path 父复合路径, index 图片序号, data PNG或JPEG数据
|
||||
// 更换主图资源时清除旧替换图引用,不修改其他共享实例
|
||||
// 入参: page 页面索引, path 父复合路径, index 图片序号, data AddImage支持的图片数据
|
||||
// 返回: error 错误信息
|
||||
func (e *Editor) ReplaceCompositeImage(page int, path ObjectPath, index int, data []byte) error {
|
||||
return e.editCompositeObjects(page, path, []int{index}, func(renderer *Renderer, nodes []*editorCompositeNode, _ []CompositeMember) error {
|
||||
@@ -138,6 +139,9 @@ func (e *Editor) ReplaceCompositeImage(page int, path ObjectPath, index int, dat
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !sameResourceID(object.ImageObject.ResourceID, id) {
|
||||
object.ImageObject.Substitution = ""
|
||||
}
|
||||
object.ImageObject.ResourceID = id
|
||||
return node.update(object)
|
||||
})
|
||||
|
||||
+20
-1
@@ -40,6 +40,14 @@ type editorFontSubset struct {
|
||||
mapped bool
|
||||
}
|
||||
|
||||
// editorFontRegistration 记录最近一次成功注册的输入与标识,不保留原始文件
|
||||
type editorFontRegistration struct {
|
||||
checksum [32]byte
|
||||
name string
|
||||
index int
|
||||
id string
|
||||
}
|
||||
|
||||
// editorSourceFontKey 区分原字体内容及完整用字集合
|
||||
type editorSourceFontKey struct {
|
||||
source [32]byte
|
||||
@@ -52,6 +60,7 @@ type editorSourceFontCache map[editorSourceFontKey][]byte
|
||||
// clearFontResourceCache 清除字体后端生成的解析与裁剪缓存,保留原始资源
|
||||
func (e *Editor) clearFontResourceCache() {
|
||||
e.fonts = make(map[string]*FontResource)
|
||||
e.fontRegistration = editorFontRegistration{}
|
||||
for i := range e.resources {
|
||||
e.resources[i].subset = nil
|
||||
}
|
||||
@@ -105,11 +114,21 @@ func (e *Editor) AddFont(file FontFile, index int) (string, error) {
|
||||
if e.backends.FontResources == nil {
|
||||
return "", fmt.Errorf("font resources: %w", ErrBackendUnavailable)
|
||||
}
|
||||
checksum := sha256.Sum256(file.Data)
|
||||
previous := e.fontRegistration
|
||||
if previous.id != "" && previous.checksum == checksum && previous.name == file.Name && previous.index == index {
|
||||
return previous.id, nil
|
||||
}
|
||||
e.fontRegistration = editorFontRegistration{}
|
||||
parsed, err := e.backends.FontResources.OpenFontResource(file, index)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return e.addFontResource(parsed, sha256.Sum256(parsed.Data))
|
||||
id, err := e.addFontResource(parsed, sha256.Sum256(parsed.Data))
|
||||
if err == nil {
|
||||
e.fontRegistration = editorFontRegistration{checksum: checksum, name: file.Name, index: index, id: id}
|
||||
}
|
||||
return id, err
|
||||
}
|
||||
|
||||
// addFontResource 注册已解析的只读字体,各文档独立分配标识和包内路径
|
||||
|
||||
@@ -202,6 +202,9 @@ func (e *Editor) retainDocumentResources(source *Editor) {
|
||||
maps.Copy(e.fonts, source.fonts)
|
||||
maps.Copy(e.images, source.images)
|
||||
maps.Copy(e.resourceID, source.resourceID)
|
||||
if sameBackend(e.backends.FontResources, source.backends.FontResources) {
|
||||
e.fontRegistration = source.fontRegistration
|
||||
}
|
||||
e.maxID = max(e.maxID, source.maxID)
|
||||
e.fontRenderer, e.fontMetrics = source.fontRenderer, source.fontMetrics
|
||||
}
|
||||
|
||||
+20
-13
@@ -17,6 +17,7 @@ package ofdgo
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"image"
|
||||
"io"
|
||||
@@ -43,6 +44,7 @@ type ImagePageOptions struct {
|
||||
|
||||
// ImportImages 将PNG、JPEG、BMP、单页TIFF或JBIG2逐张建页并插入指定位置,一次操作计入一条撤销记录
|
||||
// 保留像素及透明度,方向通过对象变换实现,不重新采样;图片压缩沿用AddImage规则
|
||||
// 同一批次的相同图片复用解码校验结果,不额外保留像素数据
|
||||
// 失败或取消时不修改文档,读取期间仍需容纳单张图片的解码数据
|
||||
// 入参: ctx 取消上下文, sources 按页面顺序排列的图片, at 插入位置, options 建页选项
|
||||
// 返回: error 错误信息
|
||||
@@ -80,6 +82,7 @@ func (e *Editor) ImportImages(ctx context.Context, sources []ImagePageSource, at
|
||||
}
|
||||
return e.Transaction(func(e *Editor) error {
|
||||
indexes := make([]int, 0, len(sources))
|
||||
verified := make(map[[32]byte]bool)
|
||||
for i, source := range sources {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
@@ -91,7 +94,7 @@ func (e *Editor) ImportImages(ctx context.Context, sources []ImagePageSource, at
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
page, err := e.addImagePage(ctx, data, options)
|
||||
page, err := e.addImagePage(ctx, data, options, verified)
|
||||
if err != nil {
|
||||
return fmt.Errorf("image %q: %w", source.Name, err)
|
||||
}
|
||||
@@ -108,9 +111,9 @@ func (e *Editor) ImportImages(ctx context.Context, sources []ImagePageSource, at
|
||||
}
|
||||
|
||||
// addImagePage 保留原图并按方向、物理尺寸与纸张选项建页
|
||||
// 入参: ctx 取消上下文, data 图片数据, options 建页选项
|
||||
// 入参: ctx 取消上下文, data 图片数据, options 建页选项, verified 本批次已成功解码的图片摘要
|
||||
// 返回: int 从0开始的页面索引, error 错误信息
|
||||
func (e *Editor) addImagePage(ctx context.Context, data []byte, options ImagePageOptions) (int, error) {
|
||||
func (e *Editor) addImagePage(ctx context.Context, data []byte, options ImagePageOptions, verified map[[32]byte]bool) (int, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -122,16 +125,20 @@ func (e *Editor) addImagePage(ctx context.Context, data []byte, options ImagePag
|
||||
return 0, fmt.Errorf("only PNG, JPEG, BMP, single-page TIFF and JBIG2 images are supported")
|
||||
}
|
||||
var decoded image.Image
|
||||
if format == "bmp" || format == "tiff" {
|
||||
decoded, _, err = decodeImageDataContext(ctx, data)
|
||||
} else {
|
||||
decoded, _, err = image.Decode(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx})
|
||||
}
|
||||
if cancelErr := ctx.Err(); cancelErr != nil {
|
||||
return 0, cancelErr
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
checksum := sha256.Sum256(data)
|
||||
if !verified[checksum] {
|
||||
if format == "bmp" || format == "tiff" {
|
||||
decoded, _, err = decodeImageDataContext(ctx, data)
|
||||
} else {
|
||||
decoded, _, err = image.Decode(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx})
|
||||
}
|
||||
if cancelErr := ctx.Err(); cancelErr != nil {
|
||||
return 0, cancelErr
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
verified[checksum] = true
|
||||
}
|
||||
metadata, err := imagePageMetadataContext(ctx, data, format, options.DPI)
|
||||
if err != nil {
|
||||
|
||||
@@ -48,7 +48,7 @@ type editorResourceRefs struct {
|
||||
|
||||
// compactSourceReferences 合并自产页面引用,版本文档按当前版本范围扫描
|
||||
// 按GB/T33190-2016附录A检查标识和路径引用,保留扫描范围内孤立XML中的引用
|
||||
// 多文档压缩按文档归集引用,未知扩展或无法解析的XML不清理资源
|
||||
// 多文档按文档归集引用,未知扩展或无法解析的XML不清理资源
|
||||
// 入参: parts 输出条目, progress 保存进度回调, generated 自产页面及引用
|
||||
// 返回: map[string]bool 可移除条目, error 读取或取消错误
|
||||
func (e *Editor) compactSourceReferences(parts map[string][]byte, progress editorProgress, generated map[string]editorGeneratedReferences) (map[string]bool, error) {
|
||||
@@ -61,10 +61,10 @@ func (e *Editor) compactSourceReferences(parts map[string][]byte, progress edito
|
||||
func (e *Editor) compactResourceReferences(parts map[string][]byte, reader *Reader, progress editorProgress, generated map[string]editorGeneratedReferences, retired map[string]bool) (map[string]bool, error) {
|
||||
multiple := reader != nil && reader.DocumentCount() > 1
|
||||
prune := e.output != nil && e.output.options.Mode != CompressionUnchanged
|
||||
if e.output != nil && e.output.protected || multiple && (e.output == nil || e.output.options.Mode == CompressionUnchanged) {
|
||||
if e.output != nil && e.output.protected {
|
||||
return nil, nil
|
||||
}
|
||||
if len(parts) == 0 && e.revision == 0 && (e.output == nil || e.output.options.Mode == CompressionUnchanged) {
|
||||
if !multiple && len(parts) == 0 && e.revision == 0 && (e.output == nil || e.output.options.Mode == CompressionUnchanged) {
|
||||
return nil, nil
|
||||
}
|
||||
generated = e.generatedVectorReferences(parts, generated)
|
||||
@@ -72,9 +72,18 @@ func (e *Editor) compactResourceReferences(parts map[string][]byte, reader *Read
|
||||
if e.output != nil {
|
||||
ctx = e.output.ctx
|
||||
}
|
||||
if multiple {
|
||||
protected, err := reader.hasOutputSignatures(ctx)
|
||||
if err != nil || protected {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
progress = progress.withContext(ctx)
|
||||
_, scope, err := reader.versionResourceFiles(ctx, parts, generated)
|
||||
if err != nil {
|
||||
if multiple && !prune {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
names := make(map[string]bool)
|
||||
|
||||
@@ -224,7 +224,7 @@ func subsetSourceSFNTFont(data []byte, usage *editorFontUsage) []byte {
|
||||
}
|
||||
for tag := range sfnt.Tables {
|
||||
switch tag {
|
||||
case "cmap", "head", "hhea", "hmtx", "maxp", "OS/2", "post", "name", "glyf", "loca", "CFF ", "cvt ", "fpgm", "prep", "gasp", "kern", "vhea", "vmtx", "VORG", "hdmx", "LTSH", "VDMX", "GDEF", "GPOS", "GSUB", "meta", "DSIG", "FFTM":
|
||||
case "cmap", "head", "hhea", "hmtx", "maxp", "OS/2", "post", "name", "glyf", "loca", "CFF ", "cvt ", "fpgm", "prep", "gasp", "kern", "vhea", "vmtx", "VORG", "hdmx", "LTSH", "VDMX", "GDEF", "GPOS", "GSUB", "meta", "DSIG", "FFTM", "PCLT":
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user