mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 08:46:55 +08:00
feat(归并文件): 归并根包文件
This commit is contained in:
1 parent
ec24b6a17d
commit
1a8de5a3f1
23 files changed
+1724
-1981
No files matched your search
@@ -20,6 +20,7 @@ import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
@@ -74,6 +75,14 @@ type resourcePageFrame struct {
|
||||
link bool
|
||||
}
|
||||
|
||||
// ResourceLocation 保存资源引用的页面实例范围,Page从1开始,Bounds单位为毫米
|
||||
// Object为承载引用的图元标识;隐藏或完全裁剪的图元不产生定位结果
|
||||
type ResourceLocation struct {
|
||||
Page int `json:"page"`
|
||||
Object string `json:"object"`
|
||||
Bounds Box `json:"bounds"`
|
||||
}
|
||||
|
||||
// Resources 列出当前文档及所选版本的资源,标记缺失文件,不读取二进制内容或执行媒体动作
|
||||
// 重复查询复用已完成的扫描,返回值及引用位置与阅读器相互独立
|
||||
// 入参: ctx 取消上下文
|
||||
@@ -485,3 +494,186 @@ func findResource(items []ResourceInfo, kind, id string) (ResourceInfo, error) {
|
||||
}
|
||||
return ResourceInfo{}, fmt.Errorf("resource not found: %s/%s", kind, id)
|
||||
}
|
||||
|
||||
// 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]
|
||||
doc, err := r.Reader.DocContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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)
|
||||
if owner != "" && resourceColorNode(node) && node.attr("ColorSpace") == "" && doc.CommonData.DefaultCS != 0 {
|
||||
dependency := fmt.Sprint(doc.CommonData.DefaultCS)
|
||||
users[dependency] = append(users[dependency], owner)
|
||||
if ref.Role == "DefaultCS" && sameResourceID(dependency, item.ID) {
|
||||
wanted[owner] = true
|
||||
}
|
||||
}
|
||||
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)))
|
||||
}
|
||||
parent := node.parent
|
||||
for parent != nil && parent.matchesOFD("PageBlock") {
|
||||
parent = parent.parent
|
||||
}
|
||||
if owner != "" && parent != nil && parent.matchesOFD("Layer") {
|
||||
if layer := editorResourceID(parent.attr("ID")); layer != "" && layer != owner {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
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, boundsOnly: true}
|
||||
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
|
||||
}
|
||||
|
||||
// Media 获取已加载的音视频资源,页面资源须先读取所属页面
|
||||
// 不加载注解或签名
|
||||
// 入参: id 资源标识
|
||||
// 返回: MultiMedia 资源信息, error 资源缺失或类型错误
|
||||
func (r *Reader) Media(id string) (MultiMedia, error) {
|
||||
if _, err := r.docStructure(); err != nil {
|
||||
return MultiMedia{}, err
|
||||
}
|
||||
media, ok := resourceValue(r.mediaCache, id)
|
||||
if !ok || media.Type != "Audio" && media.Type != "Video" {
|
||||
return MultiMedia{}, fmt.Errorf("audio or video resource %q is unavailable", id)
|
||||
}
|
||||
return media, nil
|
||||
}
|
||||
|
||||
// OpenMedia 打开文档内音视频原始数据,不解码或执行播放
|
||||
// 入参: id 资源标识
|
||||
// 返回: io.ReadCloser 数据流,由调用方关闭, error 资源或文件读取错误
|
||||
func (r *Reader) OpenMedia(id string) (io.ReadCloser, error) {
|
||||
media, err := r.Media(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.openFile(r.ResPath(media.MediaFile))
|
||||
}
|
||||
Reference in new issue
Block a user