feat(渲染能力): 完善资源加载与页面渲染,修复注释显隐问题
build.yaml / build (push) Successful in 37s

This commit is contained in:
xiaoqidun committed 2026-09-10 00:04:28 +08:00
1 parent bed08b2c4b
commit 0d83cabecc
8 files changed
+133 -32

No files matched your search

+1
View File
@@ -41,6 +41,7 @@ type Annotation struct {
Subtype string `xml:"Subtype,attr"` Subtype string `xml:"Subtype,attr"`
Creator string `xml:"Creator,attr"` Creator string `xml:"Creator,attr"`
LastModDate string `xml:"LastModDate,attr"` LastModDate string `xml:"LastModDate,attr"`
Visible *bool `xml:"Visible,attr"`
Appearance Appearance Appearance Appearance
} }
+2 -2
View File
@@ -103,8 +103,8 @@ type CustomTag struct {
type CommonData struct { type CommonData struct {
MaxUnitID int `xml:"MaxUnitID"` MaxUnitID int `xml:"MaxUnitID"`
PageArea PageArea `xml:"PageArea"` PageArea PageArea `xml:"PageArea"`
PublicRes string `xml:"PublicRes"` PublicRes []string `xml:"PublicRes"`
DocumentRes string `xml:"DocumentRes"` DocumentRes []string `xml:"DocumentRes"`
TemplatePage []TemplatePage `xml:"TemplatePage"` TemplatePage []TemplatePage `xml:"TemplatePage"`
DefaultCS int `xml:"DefaultCS"` DefaultCS int `xml:"DefaultCS"`
} }
+21 -10
View File
@@ -22,7 +22,7 @@ type PageContent struct {
ID string `xml:"-"` ID string `xml:"-"`
Area PageArea `xml:"Area"` Area PageArea `xml:"Area"`
Template []Template `xml:"Template"` Template []Template `xml:"Template"`
PageRes string `xml:"PageRes"` PageRes []string `xml:"PageRes"`
Content Content `xml:"Content"` Content Content `xml:"Content"`
Actions []Action `xml:"Actions>Action"` Actions []Action `xml:"Actions>Action"`
} }
@@ -225,13 +225,24 @@ type ShdColor struct {
// ImageObject 图片对象 // ImageObject 图片对象
type ImageObject struct { type ImageObject struct {
ID string `xml:"ID,attr"` ID string `xml:"ID,attr"`
Boundary string `xml:"Boundary,attr"` Boundary string `xml:"Boundary,attr"`
ResourceID string `xml:"ResourceID,attr"` ResourceID string `xml:"ResourceID,attr"`
ImageMask string `xml:"ImageMask,attr"` ImageMask string `xml:"ImageMask,attr"`
CTM string `xml:"CTM,attr"` CTM string `xml:"CTM,attr"`
Alpha *int `xml:"Alpha,attr"` Alpha *int `xml:"Alpha,attr"`
Visible *bool `xml:"Visible,attr"` Visible *bool `xml:"Visible,attr"`
Clips *Clips `xml:"Clips"` Border *ImageBorder `xml:"Border"`
Actions []Action `xml:"Actions>Action"` Clips *Clips `xml:"Clips"`
Actions []Action `xml:"Actions>Action"`
}
// ImageBorder 图像边框
type ImageBorder struct {
LineWidth *float64 `xml:"LineWidth,attr"`
HorizonalCornerRadius float64 `xml:"HorizonalCornerRadius,attr"`
VerticalCornerRadius float64 `xml:"VerticalCornerRadius,attr"`
DashOffset float64 `xml:"DashOffset,attr"`
DashPattern string `xml:"DashPattern,attr"`
BorderColor *StrokeColor `xml:"BorderColor"`
} }
+65 -13
View File
@@ -160,11 +160,11 @@ func (r *Reader) Doc() (*Document, error) {
if doc.Signatures == "" { if doc.Signatures == "" {
doc.Signatures = docAttr.Signatures doc.Signatures = docAttr.Signatures
} }
if doc.CommonData.DocumentRes != "" { for _, res := range doc.CommonData.DocumentRes {
r.loadRes(doc.CommonData.DocumentRes) r.loadRes(res)
} }
if doc.CommonData.PublicRes != "" { for _, res := range doc.CommonData.PublicRes {
r.loadRes(doc.CommonData.PublicRes) r.loadRes(res)
} }
r.doc = &doc r.doc = &doc
_ = r.parseAnnotations(&doc) _ = r.parseAnnotations(&doc)
@@ -251,8 +251,12 @@ func (r *Reader) PageContent(page Page) (*PageContent, error) {
return nil, fmt.Errorf("failed to unmarshal page content: %w", err) return nil, fmt.Errorf("failed to unmarshal page content: %w", err)
} }
content.ID = page.ID content.ID = page.ID
if content.PageRes != "" { for _, res := range content.PageRes {
r.loadRes(resolveResourcePath(page.BaseLoc, "", content.PageRes)) r.loadRes(resolveResourcePath(page.BaseLoc, "", res))
}
content.Area, err = r.resolvePageArea(content.Area, content.Template)
if err != nil {
return nil, err
} }
return &content, nil return &content, nil
} }
@@ -261,33 +265,81 @@ func (r *Reader) PageContent(page Page) (*PageContent, error) {
// 入参: page 页面对象 // 入参: page 页面对象
// 返回: PageArea 页面区域, error 错误信息 // 返回: PageArea 页面区域, error 错误信息
func (r *Reader) PageArea(page Page) (PageArea, error) { func (r *Reader) PageArea(page Page) (PageArea, error) {
f, err := r.openFile(r.ResPath(page.BaseLoc)) content, err := r.readPageHeader(page)
if err != nil { if err != nil {
return PageArea{}, err return PageArea{}, err
} }
return r.resolvePageArea(content.Area, content.Template)
}
// readPageHeader 读取页面区域和模板引用
// 入参: page 页面对象
// 返回: PageContent 页面头部, error 错误信息
func (r *Reader) readPageHeader(page Page) (PageContent, error) {
f, err := r.openFile(r.ResPath(page.BaseLoc))
if err != nil {
return PageContent{}, err
}
defer f.Close() defer f.Close()
var content PageContent
d := xml.NewDecoder(f) d := xml.NewDecoder(f)
for { for {
token, err := d.Token() token, err := d.Token()
if err == io.EOF { if err == io.EOF {
return PageArea{}, nil return content, nil
} }
if err != nil { if err != nil {
return PageArea{}, err return PageContent{}, err
} }
if start, ok := token.(xml.StartElement); ok { if start, ok := token.(xml.StartElement); ok {
switch start.Name.Local { switch start.Name.Local {
case "Page":
continue
case "Template":
var template Template
if err := d.DecodeElement(&template, &start); err != nil {
return PageContent{}, err
}
content.Template = append(content.Template, template)
case "Area": case "Area":
var area PageArea err := d.DecodeElement(&content.Area, &start)
err := d.DecodeElement(&area, &start) return content, err
return area, err
case "Content": case "Content":
return PageArea{}, nil return content, nil
default:
if err := d.Skip(); err != nil {
return PageContent{}, err
}
} }
} }
} }
} }
// resolvePageArea 解析页面区域的继承关系
// 入参: area 页面区域, templates 模板引用
// 返回: PageArea 页面区域, error 错误信息
func (r *Reader) resolvePageArea(area PageArea, templates []Template) (PageArea, error) {
if area != (PageArea{}) || r.doc == nil {
return area, nil
}
for _, ref := range templates {
for _, template := range r.doc.CommonData.TemplatePage {
if template.ID != ref.TemplateID {
continue
}
content, err := r.readPageHeader(Page{BaseLoc: template.BaseLoc})
if err != nil {
return PageArea{}, err
}
if content.Area != (PageArea{}) {
return content.Area, nil
}
break
}
}
return r.doc.CommonData.PageArea, nil
}
// ResPath 获取资源的完整路径 // ResPath 获取资源的完整路径
// 入参: resLink 资源链接 // 入参: resLink 资源链接
// 返回: string 完整路径 // 返回: string 完整路径
+7 -6
View File
@@ -79,15 +79,16 @@ func (r *Renderer) renderPage(page *PageContent) (*canvas.Canvas, error) {
// 入参: page 页面内容 // 入参: page 页面内容
// 返回: Box 区域, error 错误信息 // 返回: Box 区域, error 错误信息
func (r *Renderer) GetPageBox(page *PageContent) (Box, error) { func (r *Renderer) GetPageBox(page *PageContent) (Box, error) {
boxStr := page.Area.PhysicalBox area, err := r.Reader.resolvePageArea(page.Area, page.Template)
if err != nil {
return Box{}, err
}
boxStr := area.PhysicalBox
if boxStr == "" { if boxStr == "" {
boxStr = page.Area.ApplicationBox boxStr = area.ApplicationBox
} }
if boxStr == "" { if boxStr == "" {
boxStr = page.Area.ContentBox boxStr = area.ContentBox
}
if boxStr == "" && r.Reader.doc != nil {
boxStr = r.Reader.doc.CommonData.PageArea.PhysicalBox
} }
if boxStr == "" { if boxStr == "" {
boxStr = "0 0 210 297" boxStr = "0 0 210 297"
+33
View File
@@ -16,6 +16,7 @@ package ofdgo
import ( import (
"bufio" "bufio"
"fmt"
"image" "image"
"image/color" "image/color"
"io" "io"
@@ -83,6 +84,38 @@ func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float
m[1][2] -= m[1][0]*p + m[1][1]*p m[1][2] -= m[1][0]*p + m[1][1]*p
} }
ctx.RenderImage(img, ctx.CoordSystemView().Mul(ctx.View()).Mul(m)) ctx.RenderImage(img, ctx.CoordSystemView().Mul(ctx.View()).Mul(m))
if obj.Border != nil {
r.renderImageBorder(ctx, obj, box, pageH, parentCTM, boundaryInCTM, clipPath)
}
}
// renderImageBorder 渲染图像边框
// 入参: ctx 画布上下文, obj 图片对象, box 图像边界, pageH 页面高度, parentCTM 父级CTM, boundaryInCTM 边界是否参与父级CTM, clipPath 裁剪路径
func (r *Renderer) renderImageBorder(ctx *canvas.Context, obj ImageObject, box Box, pageH float64, parentCTM *Matrix, boundaryInCTM bool, clipPath *canvas.Path) {
border := obj.Border
width := defaultPathLineWidth
if border.LineWidth != nil {
width = *border.LineWidth
}
if width == 0 {
return
}
w, h := box.W, box.H
data := fmt.Sprintf("M 0 0 L %g 0 L %g %g L 0 %g C", w, w, h, h)
rx, ry := min(border.HorizonalCornerRadius, w/2), min(border.VerticalCornerRadius, h/2)
if rx > 0 && ry > 0 {
data = fmt.Sprintf("M 0 %g A %g %g 0 0 1 %g 0 L %g 0 A %g %g 0 0 1 %g %g L %g %g A %g %g 0 0 1 %g %g L %g %g A %g %g 0 0 1 0 %g C",
ry, rx, ry, rx, w-rx, rx, ry, w, ry, w, h-ry, rx, ry, w-rx, h, rx, h, rx, ry, h-ry)
}
r.renderPath(ctx, PathObject{
Boundary: obj.Boundary,
LineWidth: width,
DashOffset: border.DashOffset,
DashPattern: border.DashPattern,
Alpha: obj.Alpha,
StrokeColor: border.BorderColor,
AbbreviatedData: data,
}, pageH, nil, parentCTM, boundaryInCTM, clipPath)
} }
// imageWithMask 应用图片蒙版 // imageWithMask 应用图片蒙版
+1 -1
View File
@@ -20,7 +20,7 @@ import "github.com/tdewolff/canvas"
// 入参: ctx 画布上下文, pageID 页面ID, pageH 页面高度 // 入参: ctx 画布上下文, pageID 页面ID, pageH 页面高度
func (r *Renderer) renderAnnotations(ctx *canvas.Context, pageID string, pageH float64) { func (r *Renderer) renderAnnotations(ctx *canvas.Context, pageID string, pageH float64) {
for _, annot := range r.Reader.Annots[pageID] { for _, annot := range r.Reader.Annots[pageID] {
if len(annot.Appearance.Objects) == 0 { if annot.Visible != nil && !*annot.Visible {
continue continue
} }
box, _ := ParseBox(annot.Appearance.Boundary) box, _ := ParseBox(annot.Appearance.Boundary)
+3
View File
@@ -184,6 +184,9 @@ func pageActionSources(page pdfPage) []pdfActionSource {
func annotationActionSources(annotations []Annotation) []pdfActionSource { func annotationActionSources(annotations []Annotation) []pdfActionSource {
sources := make([]pdfActionSource, 0) sources := make([]pdfActionSource, 0)
for _, annotation := range annotations { for _, annotation := range annotations {
if annotation.Visible != nil && !*annotation.Visible {
continue
}
box, err := ParseBox(annotation.Appearance.Boundary) box, err := ParseBox(annotation.Appearance.Boundary)
if err != nil { if err != nil {
continue continue