feat(渲染注释): 渲染注释,可配置不渲染

This commit is contained in:
2026-06-26 11:26:09 +08:00
parent 1446705bb6
commit 94fa027446
8 changed files with 283 additions and 19 deletions
+10
View File
@@ -67,6 +67,7 @@ func NewRenderer(reader *Reader, opts ...RendererOption) *Renderer {
r := &Renderer{ r := &Renderer{
Reader: reader, Reader: reader,
DPI: 300.0, DPI: 300.0,
RenderAnnotations: true,
DrawParams: reader.drawParamCache, DrawParams: reader.drawParamCache,
CompositeGraphicUnits: reader.compositeGraphicUnitCache, CompositeGraphicUnits: reader.compositeGraphicUnitCache,
FontMap: make(map[string]*canvas.FontFamily), FontMap: make(map[string]*canvas.FontFamily),
@@ -87,6 +88,15 @@ func WithDPI(dpi float64) RendererOption {
} }
} }
// WithAnnotations 设置是否渲染注释外观
// 入参: enable 是否渲染
// 返回: RendererOption 渲染选项
func WithAnnotations(enable bool) RendererOption {
return func(r *Renderer) {
r.RenderAnnotations = enable
}
}
// WithFontDirs 设置外部字体查找目录 // WithFontDirs 设置外部字体查找目录
// 入参: dirs 字体目录列表 // 入参: dirs 字体目录列表
// 返回: RendererOption 渲染选项 // 返回: RendererOption 渲染选项
+58 -4
View File
@@ -38,15 +38,69 @@ type Annotations struct {
// AnnPage 页面注释引用 // AnnPage 页面注释引用
type AnnPage struct { type AnnPage struct {
PageID string `xml:"PageID,attr"` PageID string `xml:"PageID,attr"`
Annotation []Annotation `xml:"Annotation"` FileLoc string `xml:"FileLoc"`
} }
// Annotation 注释定义 // PageAnnot 页面注释集合
type PageAnnot struct {
XMLName xml.Name `xml:"PageAnnot"`
Annot []Annotation `xml:"Annot"`
}
// Annotation 页面注释
type Annotation struct { type Annotation struct {
ID string `xml:"ID,attr"` ID string `xml:"ID,attr"`
Type string `xml:"Type,attr"` Type string `xml:"Type,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"`
Loc string `xml:"Loc,attr"` Appearance Appearance
}
// Appearance 注释外观
type Appearance struct {
Boundary string `xml:"Boundary,attr"`
Objects []GraphicObject `xml:"-"`
TextObject []TextObject `xml:"TextObject"`
PathObject []PathObject `xml:"PathObject"`
ImageObject []ImageObject `xml:"ImageObject"`
CompositeGraphicUnit []CompositeGraphicUnit `xml:"CompositeGraphicUnit"`
}
// parseAnnotations 解析注释文件
// 入参: doc 文档结构
// 返回: error 错误信息
func (r *Reader) parseAnnotations(doc *Document) error {
if doc.Annotations == "" {
return nil
}
annPath := r.ResPath(doc.Annotations)
f, err := r.openFile(annPath)
if err != nil {
return err
}
defer f.Close()
var annotations Annotations
if err := xml.NewDecoder(f).Decode(&annotations); err != nil {
return err
}
if r.Annots == nil {
r.Annots = make(map[string][]Annotation)
}
for _, page := range annotations.Page {
annotPath := resolveResourcePath(annPath, "", page.FileLoc)
af, err := r.openFile(annotPath)
if err != nil {
continue
}
var pageAnnot PageAnnot
err = xml.NewDecoder(af).Decode(&pageAnnot)
_ = af.Close()
if err != nil {
continue
}
r.Annots[page.PageID] = append(r.Annots[page.PageID], pageAnnot.Annot...)
}
return nil
} }
+92
View File
@@ -123,6 +123,11 @@ func (c *CompositeGraphicUnit) UnmarshalXML(d *xml.Decoder, start xml.StartEleme
c.Alpha = &alpha c.Alpha = &alpha
} }
} }
if value := attrValue(start, "Visible"); value != "" {
if visible, err := strconv.ParseBool(value); err == nil {
c.Visible = &visible
}
}
for { for {
tok, err := d.Token() tok, err := d.Token()
if err != nil { if err != nil {
@@ -210,6 +215,93 @@ func (c *CompositeGraphicUnit) decodePageBlock(d *xml.Decoder, start xml.StartEl
} }
} }
// UnmarshalXML 解析注释外观并保留对象顺序
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (a *Appearance) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
*a = Appearance{}
a.Boundary = attrValue(start, "Boundary")
for {
tok, err := d.Token()
if err != nil {
return err
}
switch node := tok.(type) {
case xml.StartElement:
if err := a.decodeObject(d, node); err != nil {
return err
}
case xml.EndElement:
if node.Name.Local == start.Name.Local {
return nil
}
}
}
}
// decodeObject 解析注释外观子对象
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (a *Appearance) decodeObject(d *xml.Decoder, start xml.StartElement) error {
switch start.Name.Local {
case "TextObject":
var obj TextObject
if err := d.DecodeElement(&obj, &start); err != nil {
return err
}
a.TextObject = append(a.TextObject, obj)
a.Objects = append(a.Objects, GraphicObject{Type: start.Name.Local, TextObject: obj})
case "PathObject":
var obj PathObject
if err := d.DecodeElement(&obj, &start); err != nil {
return err
}
a.PathObject = append(a.PathObject, obj)
a.Objects = append(a.Objects, GraphicObject{Type: start.Name.Local, PathObject: obj})
case "ImageObject":
var obj ImageObject
if err := d.DecodeElement(&obj, &start); err != nil {
return err
}
a.ImageObject = append(a.ImageObject, obj)
a.Objects = append(a.Objects, GraphicObject{Type: start.Name.Local, ImageObject: obj})
case "CompositeGraphicUnit", "CompositeObject":
var obj CompositeGraphicUnit
if err := d.DecodeElement(&obj, &start); err != nil {
return err
}
a.CompositeGraphicUnit = append(a.CompositeGraphicUnit, obj)
a.Objects = append(a.Objects, GraphicObject{Type: start.Name.Local, CompositeGraphicUnit: obj})
case "PageBlock":
return a.decodePageBlock(d, start)
default:
return d.Skip()
}
return nil
}
// decodePageBlock 解析注释外观页块子对象
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (a *Appearance) decodePageBlock(d *xml.Decoder, start xml.StartElement) error {
for {
tok, err := d.Token()
if err != nil {
return err
}
switch node := tok.(type) {
case xml.StartElement:
if err := a.decodeObject(d, node); err != nil {
return err
}
case xml.EndElement:
if node.Name.Local == start.Name.Local {
return nil
}
}
}
}
// attrValue 获取XML属性值 // attrValue 获取XML属性值
// 入参: start 起始节点, name 属性名 // 入参: start 起始节点, name 属性名
// 返回: string 属性值 // 返回: string 属性值
+6
View File
@@ -88,6 +88,8 @@ type TextObject struct {
HScale float64 `xml:"HScale,attr"` HScale float64 `xml:"HScale,attr"`
VScale float64 `xml:"VScale,attr"` VScale float64 `xml:"VScale,attr"`
CTM string `xml:"CTM,attr"` CTM string `xml:"CTM,attr"`
Alpha *int `xml:"Alpha,attr"`
Visible *bool `xml:"Visible,attr"`
Fill *bool `xml:"Fill,attr"` Fill *bool `xml:"Fill,attr"`
Stroke *bool `xml:"Stroke,attr"` Stroke *bool `xml:"Stroke,attr"`
StrokeColor *StrokeColor `xml:"StrokeColor"` StrokeColor *StrokeColor `xml:"StrokeColor"`
@@ -135,6 +137,8 @@ type PathObject struct {
DashPattern string `xml:"DashPattern,attr"` DashPattern string `xml:"DashPattern,attr"`
MiterLimit float64 `xml:"MiterLimit,attr"` MiterLimit float64 `xml:"MiterLimit,attr"`
CTM string `xml:"CTM,attr"` CTM string `xml:"CTM,attr"`
Alpha *int `xml:"Alpha,attr"`
Visible *bool `xml:"Visible,attr"`
Stroke *bool `xml:"Stroke,attr"` Stroke *bool `xml:"Stroke,attr"`
Fill *bool `xml:"Fill,attr"` Fill *bool `xml:"Fill,attr"`
StrokeColor *StrokeColor `xml:"StrokeColor"` StrokeColor *StrokeColor `xml:"StrokeColor"`
@@ -187,5 +191,7 @@ type ImageObject struct {
Boundary string `xml:"Boundary,attr"` Boundary string `xml:"Boundary,attr"`
ResourceID string `xml:"ResourceID,attr"` ResourceID string `xml:"ResourceID,attr"`
CTM string `xml:"CTM,attr"` CTM string `xml:"CTM,attr"`
Alpha *int `xml:"Alpha,attr"`
Visible *bool `xml:"Visible,attr"`
Clips *Clips `xml:"Clips"` Clips *Clips `xml:"Clips"`
} }
+2
View File
@@ -36,6 +36,7 @@ type Reader struct {
compositeGraphicUnitCache map[string]*CompositeGraphicUnit compositeGraphicUnitCache map[string]*CompositeGraphicUnit
doc *Document doc *Document
Stamps map[string][]Stamp Stamps map[string][]Stamp
Annots map[string][]Annotation
fileIndex map[string]*zip.File fileIndex map[string]*zip.File
} }
@@ -138,6 +139,7 @@ func (r *Reader) Doc() (*Document, error) {
r.loadRes(doc.CommonData.PublicRes) r.loadRes(doc.CommonData.PublicRes)
} }
r.doc = &doc r.doc = &doc
_ = r.parseAnnotations(&doc)
_ = r.parseSignatures(&doc) _ = r.parseSignatures(&doc)
return r.doc, nil return r.doc, nil
} }
+49 -14
View File
@@ -38,6 +38,7 @@ import (
type Renderer struct { type Renderer struct {
Reader *Reader Reader *Reader
DPI float64 DPI float64
RenderAnnotations bool
fontFamily *canvas.FontFamily fontFamily *canvas.FontFamily
DrawParams map[string]*DrawParam DrawParams map[string]*DrawParam
CompositeGraphicUnits map[string]*CompositeGraphicUnit CompositeGraphicUnits map[string]*CompositeGraphicUnit
@@ -127,6 +128,9 @@ func (r *Renderer) renderPageToContext(ctx *canvas.Context, page *PageContent, d
} }
} }
} }
if r.RenderAnnotations {
r.renderAnnotations(ctx, page.ID, pageH)
}
if stamps, ok := r.Reader.Stamps[page.ID]; ok { if stamps, ok := r.Reader.Stamps[page.ID]; ok {
for _, stamp := range stamps { for _, stamp := range stamps {
r.renderStamp(ctx, stamp, pageH) r.renderStamp(ctx, stamp, pageH)
@@ -232,6 +236,21 @@ func (r *Renderer) RenderToMultiPagePDF(writer io.Writer) error {
return p.Close() return p.Close()
} }
// renderAnnotations 渲染页面注释外观
// 入参: ctx 画布上下文, pageID 页面ID, pageH 页面高度
func (r *Renderer) renderAnnotations(ctx *canvas.Context, pageID string, pageH float64) {
for _, annot := range r.Reader.Annots[pageID] {
if strings.EqualFold(annot.Type, "Link") || len(annot.Appearance.Objects) == 0 {
continue
}
box, _ := ParseBox(annot.Appearance.Boundary)
ctm := Matrix{a: 1, d: 1, e: box.X, f: box.Y}
for _, obj := range annot.Appearance.Objects {
r.renderObject(ctx, obj, pageH, nil, nil, 0, &ctm)
}
}
}
// renderTemplate 渲染模板 // renderTemplate 渲染模板
// 入参: ctx 画布上下文, templateID 模板ID, pageH 页面高度 // 入参: ctx 画布上下文, templateID 模板ID, pageH 页面高度
func (r *Renderer) renderTemplate(ctx *canvas.Context, templateID string, pageH float64) { func (r *Renderer) renderTemplate(ctx *canvas.Context, templateID string, pageH float64) {
@@ -295,6 +314,9 @@ func (r *Renderer) renderLayer(ctx *canvas.Context, layer Layer, pageH float64,
// renderCompositeGraphicUnit 渲染复合图元 // renderCompositeGraphicUnit 渲染复合图元
// 入参: ctx 画布上下文, cgu 复合图元对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM // 入参: ctx 画布上下文, cgu 复合图元对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM
func (r *Renderer) renderCompositeGraphicUnit(ctx *canvas.Context, cgu CompositeGraphicUnit, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix) { func (r *Renderer) renderCompositeGraphicUnit(ctx *canvas.Context, cgu CompositeGraphicUnit, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix) {
if cgu.Visible != nil && !*cgu.Visible {
return
}
ctx.Push() ctx.Push()
currentCTM := NewMatrix(cgu.CTM) currentCTM := NewMatrix(cgu.CTM)
if parentCTM != nil { if parentCTM != nil {
@@ -432,6 +454,9 @@ func (r *Renderer) initCommon() {
// renderImage 渲染图片 // renderImage 渲染图片
// 入参: ctx 画布上下文, obj 图片对象, pageH 页面高度, parentCTM 父级CTM // 入参: ctx 画布上下文, obj 图片对象, pageH 页面高度, parentCTM 父级CTM
func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float64, parentCTM *Matrix) { func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float64, parentCTM *Matrix) {
if obj.Visible != nil && !*obj.Visible {
return
}
resPath, ok := r.Reader.ResMap[obj.ResourceID] resPath, ok := r.Reader.ResMap[obj.ResourceID]
if !ok { if !ok {
return return
@@ -470,6 +495,9 @@ func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float
// renderPath 渲染路径 // renderPath 渲染路径
// 入参: ctx 画布上下文, obj 路径对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM // 入参: ctx 画布上下文, obj 路径对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM
func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix) { func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix) {
if obj.Visible != nil && !*obj.Visible {
return
}
ctx.Push() ctx.Push()
bx, by := 0.0, 0.0 bx, by := 0.0, 0.0
if obj.Boundary != "" { if obj.Boundary != "" {
@@ -499,12 +527,14 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64
lineWidth = dp.LineWidth lineWidth = dp.LineWidth
} }
if dp.FillColor != nil { if dp.FillColor != nil {
fillColor = parseFillColor(dp.FillColor) fillColorNode := withFillAlpha(dp.FillColor, obj.Alpha)
fillPaint = parseFillPaint(dp.FillColor, bx, by, pageH, 0, 0) fillColor = parseFillColor(fillColorNode)
fillPaint = parseFillPaint(fillColorNode, bx, by, pageH, 0, 0)
} }
if dp.StrokeColor != nil { if dp.StrokeColor != nil {
strokeColor = parseStrokeColor(dp.StrokeColor) strokeColorNode := withStrokeAlpha(dp.StrokeColor, obj.Alpha)
strokePaint = parseStrokePaint(dp.StrokeColor, bx, by, pageH, 0, 0) strokeColor = parseStrokeColor(strokeColorNode)
strokePaint = parseStrokePaint(strokeColorNode, bx, by, pageH, 0, 0)
} }
if dp.Cap == "Round" { if dp.Cap == "Round" {
lineCap = canvas.RoundCap lineCap = canvas.RoundCap
@@ -526,12 +556,14 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64
lineWidth = obj.LineWidth lineWidth = obj.LineWidth
} }
if obj.FillColor != nil { if obj.FillColor != nil {
fillColor = parseFillColor(obj.FillColor) fillColorNode := withFillAlpha(obj.FillColor, obj.Alpha)
fillPaint = parseFillPaint(obj.FillColor, bx, by, pageH, 0, 0) fillColor = parseFillColor(fillColorNode)
fillPaint = parseFillPaint(fillColorNode, bx, by, pageH, 0, 0)
} }
if obj.StrokeColor != nil { if obj.StrokeColor != nil {
strokeColor = parseStrokeColor(obj.StrokeColor) strokeColorNode := withStrokeAlpha(obj.StrokeColor, obj.Alpha)
strokePaint = parseStrokePaint(obj.StrokeColor, bx, by, pageH, 0, 0) strokeColor = parseStrokeColor(strokeColorNode)
strokePaint = parseStrokePaint(strokeColorNode, bx, by, pageH, 0, 0)
} }
if obj.Cap != "" { if obj.Cap != "" {
if obj.Cap == "Round" { if obj.Cap == "Round" {
@@ -624,6 +656,9 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64
// renderText 渲染文本 // renderText 渲染文本
// 入参: ctx 画布上下文, obj 文本对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, parentCTM 父级CTM // 入参: ctx 画布上下文, obj 文本对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, parentCTM 父级CTM
func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64, defaultFill, defaultStroke color.Color, parentCTM *Matrix) { func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64, defaultFill, defaultStroke color.Color, parentCTM *Matrix) {
if obj.Visible != nil && !*obj.Visible {
return
}
ctx.Push() ctx.Push()
bx, by := 0.0, 0.0 bx, by := 0.0, 0.0
if obj.Boundary != "" { if obj.Boundary != "" {
@@ -662,14 +697,14 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
var fillPaint any = fillColor var fillPaint any = fillColor
var fillColorNode *FillColor var fillColorNode *FillColor
if dp != nil && dp.FillColor != nil { if dp != nil && dp.FillColor != nil {
fillColor = parseFillColor(dp.FillColor) fillColorNode = withFillAlpha(dp.FillColor, obj.Alpha)
fillColorNode = dp.FillColor fillColor = parseFillColor(fillColorNode)
fillPaint = parseFillPaint(dp.FillColor, bx, by, pageH, 0, 0) fillPaint = parseFillPaint(fillColorNode, bx, by, pageH, 0, 0)
} }
if obj.FillColor != nil { if obj.FillColor != nil {
fillColor = parseFillColor(obj.FillColor) fillColorNode = withFillAlpha(obj.FillColor, obj.Alpha)
fillColorNode = obj.FillColor fillColor = parseFillColor(fillColorNode)
fillPaint = parseFillPaint(obj.FillColor, bx, by, pageH, 0, 0) fillPaint = parseFillPaint(fillColorNode, bx, by, pageH, 0, 0)
} }
if fillPaint == nil { if fillPaint == nil {
fillPaint = fillColor fillPaint = fillColor
+1
View File
@@ -97,6 +97,7 @@ type CompositeGraphicUnit struct {
CTM string `xml:"CTM,attr"` CTM string `xml:"CTM,attr"`
DrawParam string `xml:"DrawParam,attr"` DrawParam string `xml:"DrawParam,attr"`
Alpha *int `xml:"Alpha,attr"` Alpha *int `xml:"Alpha,attr"`
Visible *bool `xml:"Visible,attr"`
Objects []GraphicObject `xml:"-"` Objects []GraphicObject `xml:"-"`
TextObject []TextObject `xml:"TextObject"` TextObject []TextObject `xml:"TextObject"`
PathObject []PathObject `xml:"PathObject"` PathObject []PathObject `xml:"PathObject"`
+65 -1
View File
@@ -42,11 +42,72 @@ func parseColorWithAlpha(val string, alpha *int) color.Color {
if alpha != nil { if alpha != nil {
a = *alpha a = *alpha
} }
return color.RGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: uint8(a)} a = clampColor(a)
return color.RGBA{
R: uint8(clampColor(r) * a / 255),
G: uint8(clampColor(g) * a / 255),
B: uint8(clampColor(b) * a / 255),
A: uint8(a),
}
} }
return color.Black return color.Black
} }
// clampColor 限制颜色分量范围
// 入参: v 颜色分量
// 返回: int 颜色分量
func clampColor(v int) int {
if v < 0 {
return 0
}
if v > 255 {
return 255
}
return v
}
// mergeAlpha 合并透明度
// 入参: colorAlpha 颜色透明度, objectAlpha 对象透明度
// 返回: *int 合并后的透明度
func mergeAlpha(colorAlpha, objectAlpha *int) *int {
if colorAlpha == nil && objectAlpha == nil {
return nil
}
alpha := 255
if colorAlpha != nil {
alpha = *colorAlpha
}
if objectAlpha != nil {
alpha = alpha * *objectAlpha / 255
}
alpha = clampColor(alpha)
return &alpha
}
// withFillAlpha 合并填充色透明度
// 入参: fillColor 填充颜色节点, alpha 对象透明度
// 返回: *FillColor 合并后的填充颜色节点
func withFillAlpha(fillColor *FillColor, alpha *int) *FillColor {
if fillColor == nil || alpha == nil {
return fillColor
}
merged := *fillColor
merged.Alpha = mergeAlpha(fillColor.Alpha, alpha)
return &merged
}
// withStrokeAlpha 合并勾边色透明度
// 入参: strokeColor 勾边颜色节点, alpha 对象透明度
// 返回: *StrokeColor 合并后的勾边颜色节点
func withStrokeAlpha(strokeColor *StrokeColor, alpha *int) *StrokeColor {
if strokeColor == nil || alpha == nil {
return strokeColor
}
merged := *strokeColor
merged.Alpha = mergeAlpha(strokeColor.Alpha, alpha)
return &merged
}
// parseFillColor 解析填充颜色 // parseFillColor 解析填充颜色
// 入参: fillColor 填充颜色节点 // 入参: fillColor 填充颜色节点
// 返回: color.Color 颜色对象 // 返回: color.Color 颜色对象
@@ -228,6 +289,9 @@ func parseRadialShdGradient(radialShd *RadialShd, alpha *int, x, y, pageH, origi
// 入参: c 颜色对象 // 入参: c 颜色对象
// 返回: color.RGBA RGBA颜色 // 返回: color.RGBA RGBA颜色
func colorToRGBA(c color.Color) color.RGBA { func colorToRGBA(c color.Color) color.RGBA {
if rgba, ok := c.(color.RGBA); ok {
return rgba
}
r, g, b, a := c.RGBA() r, g, b, a := c.RGBA()
return color.RGBA{R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8), A: uint8(a >> 8)} return color.RGBA{R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8), A: uint8(a >> 8)}
} }