diff --git a/ofdgo.go b/ofdgo.go index 9ca57b6..1c556cb 100644 --- a/ofdgo.go +++ b/ofdgo.go @@ -67,6 +67,7 @@ func NewRenderer(reader *Reader, opts ...RendererOption) *Renderer { r := &Renderer{ Reader: reader, DPI: 300.0, + RenderAnnotations: true, DrawParams: reader.drawParamCache, CompositeGraphicUnits: reader.compositeGraphicUnitCache, 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 设置外部字体查找目录 // 入参: dirs 字体目录列表 // 返回: RendererOption 渲染选项 diff --git a/ofdgo_annot.go b/ofdgo_annot.go index 2721f04..63e4116 100644 --- a/ofdgo_annot.go +++ b/ofdgo_annot.go @@ -38,15 +38,69 @@ type Annotations struct { // AnnPage 页面注释引用 type AnnPage struct { - PageID string `xml:"PageID,attr"` - Annotation []Annotation `xml:"Annotation"` + PageID string `xml:"PageID,attr"` + FileLoc string `xml:"FileLoc"` } -// Annotation 注释定义 +// PageAnnot 页面注释集合 +type PageAnnot struct { + XMLName xml.Name `xml:"PageAnnot"` + Annot []Annotation `xml:"Annot"` +} + +// Annotation 页面注释 type Annotation struct { ID string `xml:"ID,attr"` Type string `xml:"Type,attr"` + Subtype string `xml:"Subtype,attr"` Creator string `xml:"Creator,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 } diff --git a/ofdgo_object.go b/ofdgo_object.go index cf1aa7f..ba640da 100644 --- a/ofdgo_object.go +++ b/ofdgo_object.go @@ -123,6 +123,11 @@ func (c *CompositeGraphicUnit) UnmarshalXML(d *xml.Decoder, start xml.StartEleme c.Alpha = &alpha } } + if value := attrValue(start, "Visible"); value != "" { + if visible, err := strconv.ParseBool(value); err == nil { + c.Visible = &visible + } + } for { tok, err := d.Token() 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属性值 // 入参: start 起始节点, name 属性名 // 返回: string 属性值 diff --git a/ofdgo_page.go b/ofdgo_page.go index b065d6c..1bedf39 100644 --- a/ofdgo_page.go +++ b/ofdgo_page.go @@ -88,6 +88,8 @@ type TextObject struct { HScale float64 `xml:"HScale,attr"` VScale float64 `xml:"VScale,attr"` CTM string `xml:"CTM,attr"` + Alpha *int `xml:"Alpha,attr"` + Visible *bool `xml:"Visible,attr"` Fill *bool `xml:"Fill,attr"` Stroke *bool `xml:"Stroke,attr"` StrokeColor *StrokeColor `xml:"StrokeColor"` @@ -135,6 +137,8 @@ type PathObject struct { DashPattern string `xml:"DashPattern,attr"` MiterLimit float64 `xml:"MiterLimit,attr"` CTM string `xml:"CTM,attr"` + Alpha *int `xml:"Alpha,attr"` + Visible *bool `xml:"Visible,attr"` Stroke *bool `xml:"Stroke,attr"` Fill *bool `xml:"Fill,attr"` StrokeColor *StrokeColor `xml:"StrokeColor"` @@ -187,5 +191,7 @@ type ImageObject struct { Boundary string `xml:"Boundary,attr"` ResourceID string `xml:"ResourceID,attr"` CTM string `xml:"CTM,attr"` + Alpha *int `xml:"Alpha,attr"` + Visible *bool `xml:"Visible,attr"` Clips *Clips `xml:"Clips"` } diff --git a/ofdgo_reader.go b/ofdgo_reader.go index 03d119e..1a29212 100644 --- a/ofdgo_reader.go +++ b/ofdgo_reader.go @@ -36,6 +36,7 @@ type Reader struct { compositeGraphicUnitCache map[string]*CompositeGraphicUnit doc *Document Stamps map[string][]Stamp + Annots map[string][]Annotation fileIndex map[string]*zip.File } @@ -138,6 +139,7 @@ func (r *Reader) Doc() (*Document, error) { r.loadRes(doc.CommonData.PublicRes) } r.doc = &doc + _ = r.parseAnnotations(&doc) _ = r.parseSignatures(&doc) return r.doc, nil } diff --git a/ofdgo_renderer.go b/ofdgo_renderer.go index 374e502..6f73454 100644 --- a/ofdgo_renderer.go +++ b/ofdgo_renderer.go @@ -38,6 +38,7 @@ import ( type Renderer struct { Reader *Reader DPI float64 + RenderAnnotations bool fontFamily *canvas.FontFamily DrawParams map[string]*DrawParam 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 { for _, stamp := range stamps { r.renderStamp(ctx, stamp, pageH) @@ -232,6 +236,21 @@ func (r *Renderer) RenderToMultiPagePDF(writer io.Writer) error { 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 渲染模板 // 入参: ctx 画布上下文, templateID 模板ID, pageH 页面高度 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 渲染复合图元 // 入参: 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) { + if cgu.Visible != nil && !*cgu.Visible { + return + } ctx.Push() currentCTM := NewMatrix(cgu.CTM) if parentCTM != nil { @@ -432,6 +454,9 @@ func (r *Renderer) initCommon() { // renderImage 渲染图片 // 入参: ctx 画布上下文, obj 图片对象, pageH 页面高度, parentCTM 父级CTM 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] if !ok { return @@ -470,6 +495,9 @@ func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float // renderPath 渲染路径 // 入参: 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) { + if obj.Visible != nil && !*obj.Visible { + return + } ctx.Push() bx, by := 0.0, 0.0 if obj.Boundary != "" { @@ -499,12 +527,14 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 lineWidth = dp.LineWidth } if dp.FillColor != nil { - fillColor = parseFillColor(dp.FillColor) - fillPaint = parseFillPaint(dp.FillColor, bx, by, pageH, 0, 0) + fillColorNode := withFillAlpha(dp.FillColor, obj.Alpha) + fillColor = parseFillColor(fillColorNode) + fillPaint = parseFillPaint(fillColorNode, bx, by, pageH, 0, 0) } if dp.StrokeColor != nil { - strokeColor = parseStrokeColor(dp.StrokeColor) - strokePaint = parseStrokePaint(dp.StrokeColor, bx, by, pageH, 0, 0) + strokeColorNode := withStrokeAlpha(dp.StrokeColor, obj.Alpha) + strokeColor = parseStrokeColor(strokeColorNode) + strokePaint = parseStrokePaint(strokeColorNode, bx, by, pageH, 0, 0) } if dp.Cap == "Round" { lineCap = canvas.RoundCap @@ -526,12 +556,14 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 lineWidth = obj.LineWidth } if obj.FillColor != nil { - fillColor = parseFillColor(obj.FillColor) - fillPaint = parseFillPaint(obj.FillColor, bx, by, pageH, 0, 0) + fillColorNode := withFillAlpha(obj.FillColor, obj.Alpha) + fillColor = parseFillColor(fillColorNode) + fillPaint = parseFillPaint(fillColorNode, bx, by, pageH, 0, 0) } if obj.StrokeColor != nil { - strokeColor = parseStrokeColor(obj.StrokeColor) - strokePaint = parseStrokePaint(obj.StrokeColor, bx, by, pageH, 0, 0) + strokeColorNode := withStrokeAlpha(obj.StrokeColor, obj.Alpha) + strokeColor = parseStrokeColor(strokeColorNode) + strokePaint = parseStrokePaint(strokeColorNode, bx, by, pageH, 0, 0) } if obj.Cap != "" { if obj.Cap == "Round" { @@ -624,6 +656,9 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 // renderText 渲染文本 // 入参: ctx 画布上下文, obj 文本对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, parentCTM 父级CTM 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() bx, by := 0.0, 0.0 if obj.Boundary != "" { @@ -662,14 +697,14 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 var fillPaint any = fillColor var fillColorNode *FillColor if dp != nil && dp.FillColor != nil { - fillColor = parseFillColor(dp.FillColor) - fillColorNode = dp.FillColor - fillPaint = parseFillPaint(dp.FillColor, bx, by, pageH, 0, 0) + fillColorNode = withFillAlpha(dp.FillColor, obj.Alpha) + fillColor = parseFillColor(fillColorNode) + fillPaint = parseFillPaint(fillColorNode, bx, by, pageH, 0, 0) } if obj.FillColor != nil { - fillColor = parseFillColor(obj.FillColor) - fillColorNode = obj.FillColor - fillPaint = parseFillPaint(obj.FillColor, bx, by, pageH, 0, 0) + fillColorNode = withFillAlpha(obj.FillColor, obj.Alpha) + fillColor = parseFillColor(fillColorNode) + fillPaint = parseFillPaint(fillColorNode, bx, by, pageH, 0, 0) } if fillPaint == nil { fillPaint = fillColor diff --git a/ofdgo_res.go b/ofdgo_res.go index 81b323b..982e70a 100644 --- a/ofdgo_res.go +++ b/ofdgo_res.go @@ -97,6 +97,7 @@ type CompositeGraphicUnit struct { CTM string `xml:"CTM,attr"` DrawParam string `xml:"DrawParam,attr"` Alpha *int `xml:"Alpha,attr"` + Visible *bool `xml:"Visible,attr"` Objects []GraphicObject `xml:"-"` TextObject []TextObject `xml:"TextObject"` PathObject []PathObject `xml:"PathObject"` diff --git a/ofdgo_text.go b/ofdgo_text.go index 2116bfc..2447f87 100644 --- a/ofdgo_text.go +++ b/ofdgo_text.go @@ -42,11 +42,72 @@ func parseColorWithAlpha(val string, alpha *int) color.Color { if alpha != nil { 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 } +// 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 解析填充颜色 // 入参: fillColor 填充颜色节点 // 返回: color.Color 颜色对象 @@ -228,6 +289,9 @@ func parseRadialShdGradient(radialShd *RadialShd, alpha *int, x, y, pageH, origi // 入参: c 颜色对象 // 返回: color.RGBA RGBA颜色 func colorToRGBA(c color.Color) color.RGBA { + if rgba, ok := c.(color.RGBA); ok { + return 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)} }