Files
ofdgo/ofdgo_resource_location.go
T
2026-10-13 15:16:57 +08:00

177 lines
5.8 KiB
Go

// 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"
"fmt"
"maps"
"slices"
"strings"
)
// ResourceLocation 保存资源引用的页面实例范围,Page从1开始,Bounds单位为毫米
// Object为承载引用的图元标识;隐藏或完全裁剪的图元不产生定位结果
type ResourceLocation struct {
Page int `json:"page"`
Object string `json:"object"`
Bounds Box `json:"bounds"`
}
// ResourceLocations 定位当前文档及版本内的一处资源引用,返回各页面上的可见使用范围
// 模板、图案和复合资源沿引用关系定位承载图元,不将资源声明位置当作页面位置
// 入参: ctx 取消上下文, kind 资源类型, id 资源标识, reference 为Resources返回的引用索引
// 返回: []ResourceLocation 页面位置,无可见图元时为空, error 引用、度量或取消错误
func (r *Renderer) ResourceLocations(ctx context.Context, kind, id string, reference int) ([]ResourceLocation, error) {
catalog, err := r.Reader.resourceCatalog(ctx)
if err != nil {
return nil, err
}
item, err := findResource(catalog.items, kind, id)
if err != nil {
return nil, err
}
if reference < 0 || reference >= len(item.References) {
return nil, fmt.Errorf("resource reference not found")
}
ref := item.References[reference]
wanted := make(map[string]bool)
users := make(map[string][]string)
for _, file := range catalog.scan.files {
root, err := parseEditorXMLContext(ctx, file.data)
if err != nil {
return nil, err
}
var visit func(*editorXML, string) error
visit = func(node *editorXML, object string) error {
if err := ctx.Err(); err != nil {
return err
}
if !node.matchesOFD(node.name.Local) || node.matchesOFD("Attachments") {
return nil
}
if slices.Contains([]string{"TextObject", "PathObject", "ImageObject", "CompositeObject"}, node.name.Local) {
object = node.attr("ID")
}
owner := resourceReferenceOwner(node)
add := func(role, value string) {
if owner == "" {
return
}
if role != "AttachID" {
if dependency := editorResourceID(value); dependency != "" && dependency != owner {
users[dependency] = append(users[dependency], owner)
}
}
match := value == item.ID
if kind != "Attachment" {
match = sameResourceID(value, item.ID)
}
if file.name == ref.File && object == ref.Object && role == ref.Role && match {
wanted[owner] = true
}
}
for _, attr := range node.attrs {
if attr.Name.Space == "" && (editorResourceReferenceKind(attr.Name.Local, root.matchesOFD("Res")) == editorReferenceID || attr.Name.Local == "AttachID") {
add(attr.Name.Local, attr.Value)
}
}
if slices.Contains([]string{"Thumbnail", "Substitution"}, node.name.Local) {
add(node.name.Local, strings.TrimSpace(editorImportText(file.data, node)))
}
if owner != "" && node.parent != nil && node.parent.matchesOFD("Layer") {
layer := editorResourceID(node.parent.attr("ID"))
users[layer] = append(users[layer], owner)
}
for _, child := range node.children {
if err := visit(child, object); err != nil {
return err
}
}
return nil
}
if err := visit(root, ""); err != nil {
return nil, err
}
}
queue := slices.Collect(maps.Keys(wanted))
for len(queue) != 0 {
if err := ctx.Err(); err != nil {
return nil, err
}
id := queue[0]
queue = queue[1:]
for _, user := range users[id] {
if !wanted[user] {
wanted[user] = true
queue = append(queue, user)
}
}
}
doc, err := r.Reader.DocContext(ctx)
if err != nil {
return nil, err
}
view := *r
view.RenderAnnotations, view.pageContext = true, ctx
var result []ResourceLocation
for page := range doc.Pages.Page {
if ref.Page >= 0 && ref.Page != page || len(wanted) == 0 {
continue
}
content, err := r.Reader.PageContentByIndexContext(ctx, page)
if err != nil {
return nil, err
}
targets := make(map[string]*StructureTarget, len(wanted))
for id := range wanted {
targets[id] = &StructureTarget{ID: id}
}
visitor := &structureVisitor{ctx: ctx, renderer: &view, targets: targets}
if err := view.WalkPage(content, visitor); err != nil {
return nil, err
}
for _, id := range slices.Sorted(maps.Keys(targets)) {
box := targets[id].Bounds
if box.W > 0 && box.H > 0 {
result = append(result, ResourceLocation{Page: page + 1, Object: id, Bounds: box})
}
}
}
if err := ctx.Err(); err != nil {
return nil, err
}
return result, nil
}
// resourceReferenceOwner 查找承载引用的资源定义或最外层图元,图层只承接其自身的绘制参数
// 入参: node 引用节点
// 返回: string 图元或资源标识,无法映射时为空
func resourceReferenceOwner(node *editorXML) string {
id := ""
for current := node; current != nil; current = current.parent {
if !current.matchesOFD(current.name.Local) {
return ""
}
if slices.Contains([]string{"TextObject", "PathObject", "ImageObject", "CompositeObject", "CompositeGraphicUnit"}, current.name.Local) || current.parent != nil && current.name.Local == editorResourceElement(current.parent.name.Local) {
id = editorResourceID(current.attr("ID"))
}
if id == "" && current.matchesOFD("Layer") {
id = editorResourceID(current.attr("ID"))
}
}
return id
}