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