mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 13:56:32 +08:00
This commit is contained in:
1 parent
2cc6c121f8
commit
7f79d1e70a
18 files changed
+757
-129
No files matched your search
+229
-32
@@ -60,9 +60,11 @@ type editorSourcePage struct {
|
||||
// ObjectCapabilities 已有对象可执行的操作,Reason说明受限原因
|
||||
// Update表示完整替换内容,ReplaceFont表示可显式替换文字字体,Transform表示保留文字定位的几何变换,Order仅限同一图层或末级页块
|
||||
// Arrange表示可准确度量对齐与旋转范围;Reflow表示可重排文字,LayoutKnown表示段落选项可恢复
|
||||
// Paint表示可独立修改纯色填充和描边,不要求重新排版文字
|
||||
// MissingGlyphs提供缺字导致操作受限时的结构化诊断
|
||||
type ObjectCapabilities struct {
|
||||
Update bool
|
||||
Paint bool
|
||||
ReplaceFont bool
|
||||
Reflow bool
|
||||
LayoutKnown bool
|
||||
@@ -128,9 +130,9 @@ func (r *Reader) Editor() (*Editor, error) {
|
||||
}
|
||||
e := NewEditor()
|
||||
e.Info, e.maxID = cloneEditorData(*info), maximum
|
||||
directory := path.Join(reader.RootDir, "Edit")
|
||||
directory := path.Join(cleanPackagePath(reader.RootDir), "Edit")
|
||||
for i := 1; editorDirectoryExists(reader, directory); i++ {
|
||||
directory = path.Join(reader.RootDir, "Edit_"+strconv.Itoa(i))
|
||||
directory = path.Join(cleanPackagePath(reader.RootDir), "Edit_"+strconv.Itoa(i))
|
||||
}
|
||||
e.source = &editorSource{reader: reader, document: doc, info: cloneEditorData(*info), directory: directory, pages: make(map[string]*editorSourcePage), origins: make(map[string]*editorObjectOrigin)}
|
||||
for _, page := range doc.Pages.Page {
|
||||
@@ -322,7 +324,7 @@ func (e *Editor) loadSourcePage(index int) error {
|
||||
if node := nodes[id]; node != nil {
|
||||
origin := &editorObjectOrigin{page: source, node: node, object: object}
|
||||
e.source.origins[id] = origin
|
||||
if editorXMLSupported(node) {
|
||||
if editorXMLTransformable(node) {
|
||||
resolved, err := e.resolveEditorStyle(object, layer.DrawParam)
|
||||
if err != nil {
|
||||
origin.reason = err
|
||||
@@ -339,7 +341,7 @@ func (e *Editor) loadSourcePage(index int) error {
|
||||
}
|
||||
|
||||
// ObjectCapabilities 获取对象可执行的操作,不支持的内容保持原文,不强制转换
|
||||
// 字体缺失或子集不包含原文时仍可移动、删除,不允许重新排版或复制
|
||||
// 字体缺失或子集不包含原文时仍可保真移动、复制和删除,不自动重新排版
|
||||
// 显式提供可用字体后可通过UpdateObject替换;原文布局未知时不自动推断段落选项
|
||||
// 入参: page 页面索引, id 对象标识
|
||||
// 返回: ObjectCapabilities 操作能力, error 错误信息
|
||||
@@ -349,47 +351,51 @@ func (e *Editor) ObjectCapabilities(page int, id string) (ObjectCapabilities, er
|
||||
return ObjectCapabilities{}, err
|
||||
}
|
||||
object := layer.Objects[index]
|
||||
all := ObjectCapabilities{Update: true, ReplaceFont: object.Type == "TextObject", Reflow: object.Type == "TextObject" && object.TextObject.ReadDirection == 0 && object.TextObject.CharDirection == 0, LayoutKnown: object.Type == "TextObject" && (e.objectOrigin(id) == nil || object.TextObject.layout != nil), Transform: true, Arrange: true, Copy: true, Delete: true, Order: true}
|
||||
if !e.originalPage(page) {
|
||||
all := ObjectCapabilities{Update: true, Paint: object.Type == "TextObject" || object.Type == "PathObject", ReplaceFont: object.Type == "TextObject", Reflow: object.Type == "TextObject" && object.TextObject.ReadDirection == 0 && object.TextObject.CharDirection == 0, LayoutKnown: object.Type == "TextObject" && (e.objectOrigin(id) == nil || object.TextObject.layout != nil), Transform: true, Arrange: true, Copy: true, Delete: true, Order: true}
|
||||
origin := e.objectOrigin(id)
|
||||
if origin == nil {
|
||||
return all, nil
|
||||
}
|
||||
source := e.source.pages[e.pages[page].ID]
|
||||
node := source.nodes[id]
|
||||
if node == nil {
|
||||
return all, nil
|
||||
}
|
||||
if !editorXMLSupported(node) {
|
||||
node := origin.node
|
||||
if !editorXMLTransformable(node) {
|
||||
return ObjectCapabilities{Reason: "object uses unsupported editing features", ReasonCode: EditUnsupportedObject}, nil
|
||||
}
|
||||
if origin := e.objectOrigin(id); origin.reason != nil {
|
||||
return ObjectCapabilities{Reason: origin.reason.Error(), ReasonCode: editReason(origin.reason)}, nil
|
||||
if origin.reason != nil && editReason(origin.reason) == EditUnsupportedStyle {
|
||||
return ObjectCapabilities{Reason: origin.reason.Error(), ReasonCode: EditUnsupportedStyle}, nil
|
||||
}
|
||||
for parent := node.parent; parent.name.Local != "Content"; parent = parent.parent {
|
||||
allowed := "ID"
|
||||
if parent.name.Local == "Layer" {
|
||||
allowed += " Type DrawParam"
|
||||
}
|
||||
if !editorXMLAttributes(parent, allowed) {
|
||||
return ObjectCapabilities{Reason: "object container uses unsupported editing features", ReasonCode: EditUnsupportedContainer}, nil
|
||||
}
|
||||
if !editorXMLContainersSupported(node) {
|
||||
return ObjectCapabilities{Reason: "object container uses unsupported editing features", ReasonCode: EditUnsupportedContainer}, nil
|
||||
}
|
||||
if err := validateEditorGeometry(object); err != nil {
|
||||
return ObjectCapabilities{Reason: err.Error(), ReasonCode: EditInvalidObject}, nil
|
||||
}
|
||||
all.Order = editorContainerOrderable(node.parent)
|
||||
if _, err := e.prepareObject(id, object); err != nil {
|
||||
all.Update, all.Reflow, all.Copy, all.Reason = false, false, false, err.Error()
|
||||
all.Copy = editorXMLCopyable(node) && origin.reason == nil
|
||||
if object.Type == "CompositeObject" || object.Type == "CompositeGraphicUnit" {
|
||||
for parent := node.parent; parent != nil; parent = parent.parent {
|
||||
if parent.name.Local == "Layer" && parent.attr("DrawParam") != "" {
|
||||
all.Copy = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if origin.reason != nil {
|
||||
all.Update, all.Reflow, all.ReplaceFont, all.Paint = false, false, false, false
|
||||
all.Reason, all.ReasonCode = origin.reason.Error(), editReason(origin.reason)
|
||||
} else if !editorXMLSupported(node) {
|
||||
all.Update, all.Reflow, all.ReplaceFont = false, false, false
|
||||
all.Reason, all.ReasonCode = "content can only be edited with its original structure preserved", EditUnsupportedObject
|
||||
} else if _, err := e.prepareObject(id, object); err != nil {
|
||||
all.Update, all.Reflow, all.Reason = false, false, err.Error()
|
||||
all.ReasonCode = editReason(err)
|
||||
errors.As(err, &all.MissingGlyphs)
|
||||
}
|
||||
if !e.sourceRGB() {
|
||||
all.ReplaceFont = false
|
||||
all.Update, all.Reflow, all.Copy, all.Reason = false, false, false, "document uses a non-RGB default color space"
|
||||
all.MissingGlyphs = nil
|
||||
all.ReasonCode = EditUnsupportedColor
|
||||
all.Paint = all.Paint && e.editorPaintable(object)
|
||||
if object.Type == "ImageObject" && object.ImageObject.Border != nil {
|
||||
all.Transform, all.Arrange = false, false
|
||||
all.Reason, all.ReasonCode = "image border transformations are not supported", EditUnsupportedObject
|
||||
}
|
||||
if object.Type == "TextObject" {
|
||||
all.Arrange = all.Update
|
||||
all.Arrange = all.Update || e.editorTextMeasurable(object.TextObject)
|
||||
if _, err := object.TextObject.TextFrame(); err != nil {
|
||||
all.Reflow = false
|
||||
}
|
||||
@@ -407,12 +413,147 @@ func (e *Editor) objectOrigin(id string) *editorObjectOrigin {
|
||||
return e.source.origins[id]
|
||||
}
|
||||
|
||||
// editorPreservedObject 判断对象是否仅改变标识、几何和可独立写回的外观字段
|
||||
// 入参: before 原始快照, after 新快照
|
||||
// 返回: bool 是否保留原内容
|
||||
func editorPreservedObject(before, after GraphicObject) bool {
|
||||
if before.Type != after.Type {
|
||||
return false
|
||||
}
|
||||
before = cloneEditorData(before)
|
||||
switch before.Type {
|
||||
case "TextObject":
|
||||
a, b := &before.TextObject, after.TextObject
|
||||
a.ID, a.Boundary, a.CTM, a.Alpha = b.ID, b.Boundary, b.CTM, b.Alpha
|
||||
a.Fill, a.Stroke, a.FillColor, a.StrokeColor, a.LineWidth = b.Fill, b.Stroke, b.FillColor, b.StrokeColor, b.LineWidth
|
||||
case "PathObject":
|
||||
a, b := &before.PathObject, after.PathObject
|
||||
a.ID, a.Boundary, a.CTM, a.Alpha = b.ID, b.Boundary, b.CTM, b.Alpha
|
||||
a.Fill, a.Stroke, a.FillColor, a.StrokeColor, a.LineWidth = b.Fill, b.Stroke, b.FillColor, b.StrokeColor, b.LineWidth
|
||||
a.DashOffset, a.DashPattern, a.Cap, a.Join = b.DashOffset, b.DashPattern, b.Cap, b.Join
|
||||
case "ImageObject":
|
||||
a, b := &before.ImageObject, after.ImageObject
|
||||
a.ID, a.Boundary, a.CTM, a.Alpha = b.ID, b.Boundary, b.CTM, b.Alpha
|
||||
case "CompositeObject", "CompositeGraphicUnit":
|
||||
a, b := &before.CompositeGraphicUnit, after.CompositeGraphicUnit
|
||||
a.ID, a.Boundary, a.CTM, a.Alpha = b.ID, b.Boundary, b.CTM, b.Alpha
|
||||
default:
|
||||
return false
|
||||
}
|
||||
a, b := *editorObjectClips(&before), *editorObjectClips(&after)
|
||||
if a == nil {
|
||||
*editorObjectClips(&before) = b
|
||||
} else if b != nil && len(a.Clip) <= len(b.Clip) {
|
||||
a.Clip = append(a.Clip, b.Clip[len(a.Clip):]...)
|
||||
for i := range a.Clip {
|
||||
if len(a.Clip[i].Area) == len(b.Clip[i].Area) {
|
||||
for j := range a.Clip[i].Area {
|
||||
a.Clip[i].Area[j].CTM = b.Clip[i].Area[j].CTM
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return reflect.DeepEqual(before, after)
|
||||
}
|
||||
|
||||
// prepareCopiedObject 保真复制原对象,不重新生成字形或丢弃未修改字段
|
||||
// 入参: id 新标识, object 对象快照
|
||||
// 返回: GraphicObject 独立副本, error 错误信息
|
||||
func (e *Editor) prepareCopiedObject(id string, object GraphicObject) (GraphicObject, error) {
|
||||
origin := e.objectOrigin(editorObjectID(object))
|
||||
if origin == nil {
|
||||
return e.prepareObject(id, object)
|
||||
}
|
||||
if !editorXMLTransformable(origin.node) || !editorXMLContainersSupported(origin.node) || !editorXMLCopyable(origin.node) || origin.reason != nil {
|
||||
return GraphicObject{}, fmt.Errorf("object cannot be copied without changing its original structure")
|
||||
}
|
||||
var drawParam string
|
||||
for parent := origin.node.parent; parent != nil; parent = parent.parent {
|
||||
if parent.name.Local == "Layer" {
|
||||
drawParam = parent.attr("DrawParam")
|
||||
break
|
||||
}
|
||||
}
|
||||
before, err := e.resolveEditorStyle(origin.object, drawParam)
|
||||
if err != nil {
|
||||
return GraphicObject{}, err
|
||||
}
|
||||
if drawParam != "" && (object.Type == "CompositeObject" || object.Type == "CompositeGraphicUnit") {
|
||||
return GraphicObject{}, fmt.Errorf("copy requires preserving the composite layer draw parameters")
|
||||
}
|
||||
if !editorPreservedObject(before, object) {
|
||||
if !editorXMLSupported(origin.node) {
|
||||
return GraphicObject{}, fmt.Errorf("copy would replace unsupported object content")
|
||||
}
|
||||
return e.prepareObject(id, object)
|
||||
}
|
||||
if err := validateEditorGeometry(object); err != nil {
|
||||
return GraphicObject{}, err
|
||||
}
|
||||
if err := e.validatePreservedAppearance(before, object); err != nil {
|
||||
return GraphicObject{}, err
|
||||
}
|
||||
result := cloneEditorData(object)
|
||||
switch result.Type {
|
||||
case "TextObject":
|
||||
result.TextObject.ID = id
|
||||
case "PathObject":
|
||||
result.PathObject.ID = id
|
||||
case "ImageObject":
|
||||
result.ImageObject.ID = id
|
||||
case "CompositeObject", "CompositeGraphicUnit":
|
||||
result.CompositeGraphicUnit.ID = id
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// validatePreservedAppearance 校验保真对象实际修改的颜色、描边和裁剪,不重新解释未修改字段
|
||||
// 入参: before 原对象, after 新对象
|
||||
// 返回: error 错误信息
|
||||
func (e *Editor) validatePreservedAppearance(before, after GraphicObject) error {
|
||||
old := []*FillColor{before.TextObject.FillColor, (*FillColor)(before.TextObject.StrokeColor), before.PathObject.FillColor, (*FillColor)(before.PathObject.StrokeColor)}
|
||||
next := []*FillColor{after.TextObject.FillColor, (*FillColor)(after.TextObject.StrokeColor), after.PathObject.FillColor, (*FillColor)(after.PathObject.StrokeColor)}
|
||||
for i, value := range next {
|
||||
if !reflect.DeepEqual(old[i], value) {
|
||||
if err := e.editorColor(value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
a, b := *editorObjectClips(&before), *editorObjectClips(&after)
|
||||
if b != nil {
|
||||
count := 0
|
||||
if a != nil {
|
||||
count = len(a.Clip)
|
||||
}
|
||||
for i, clip := range b.Clip {
|
||||
if i >= count {
|
||||
if err := e.validateObjectClips(&Clips{Clip: []Clip{clip}}); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
for j, area := range clip.Area {
|
||||
if area.CTM != a.Clip[i].Area[j].CTM && area.CTM != "" {
|
||||
if _, err := creationNumbers(area.CTM, 6); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if after.Type == "PathObject" {
|
||||
return validateEditorStroke(after.PathObject)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// editorContainerOrderable 判断容器的直接对象能否在保留页块的前提下排序
|
||||
// 入参: container 图层或页块
|
||||
// 返回: bool 是否可排序
|
||||
func editorContainerOrderable(container *editorXML) bool {
|
||||
for _, child := range container.children {
|
||||
if !editorXMLSupported(child) {
|
||||
if !editorXMLTransformable(child) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -430,6 +571,8 @@ func editorGeometry(object GraphicObject) (string, string) {
|
||||
return object.PathObject.Boundary, object.PathObject.CTM
|
||||
case "ImageObject":
|
||||
return object.ImageObject.Boundary, object.ImageObject.CTM
|
||||
case "CompositeObject", "CompositeGraphicUnit":
|
||||
return object.CompositeGraphicUnit.Boundary, object.CompositeGraphicUnit.CTM
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
@@ -456,6 +599,46 @@ func (e *Editor) editorFont(id string) (*font.SFNT, error) {
|
||||
return nil, &EditError{Code: EditFontUnavailable, Err: fmt.Errorf("embedded font %q not found", id)}
|
||||
}
|
||||
|
||||
// editorTextMeasurable 判断原字形能否在不使用回退字体的情况下度量
|
||||
// 入参: text 原文字对象
|
||||
// 返回: bool 是否可准确度量
|
||||
func (e *Editor) editorTextMeasurable(text TextObject) bool {
|
||||
sfnt, err := e.editorFont(text.Font)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
transforms := make(map[int]CGTransform, len(text.CGTransform))
|
||||
for _, transform := range text.CGTransform {
|
||||
transforms[transform.CodePosition] = transform
|
||||
}
|
||||
offset := 0
|
||||
for _, code := range text.TextCode {
|
||||
runes := textCodeRunes(code.Value)
|
||||
for i := 0; i < len(runes); {
|
||||
if transform, ok := transforms[offset+i]; ok {
|
||||
ids := parseInts(transform.Glyphs)
|
||||
count := max(1, transform.CodeCount)
|
||||
if len(ids) == 0 || count > len(runes)-i {
|
||||
return false
|
||||
}
|
||||
for _, id := range ids {
|
||||
if id <= 0 || id >= int(sfnt.NumGlyphs()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
i += count
|
||||
} else {
|
||||
if sfnt.GlyphIndex(runes[i]) == 0 {
|
||||
return false
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
offset += len(runes)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// editorImage 按需读取原图片尺寸,不解码完整像素
|
||||
// 入参: id 图片资源标识
|
||||
// 返回: image.Point 图片尺寸, error 错误信息
|
||||
@@ -541,9 +724,23 @@ func validateEditorGeometry(object GraphicObject) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var alpha *int
|
||||
switch object.Type {
|
||||
case "TextObject":
|
||||
alpha = object.TextObject.Alpha
|
||||
case "PathObject":
|
||||
alpha = object.PathObject.Alpha
|
||||
case "ImageObject":
|
||||
alpha = object.ImageObject.Alpha
|
||||
case "CompositeObject", "CompositeGraphicUnit":
|
||||
alpha = object.CompositeGraphicUnit.Alpha
|
||||
}
|
||||
if alpha != nil && (*alpha < 0 || *alpha > 255) {
|
||||
return fmt.Errorf("alpha must be between 0 and 255")
|
||||
}
|
||||
if object.Type == "TextObject" {
|
||||
obj := object.TextObject
|
||||
if !finite(obj.Size) || obj.Size <= 0 || !finite(obj.LineWidth) {
|
||||
if !finite(obj.Size) || obj.Size <= 0 || !finite(obj.LineWidth) || obj.LineWidth < 0 {
|
||||
return fmt.Errorf("invalid text geometry")
|
||||
}
|
||||
for _, code := range obj.TextCode {
|
||||
|
||||
Reference in new issue
Block a user