From 777d0c7c895ffb9abed5e4e37e8ee8851f450763 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Wed, 9 Sep 2026 19:01:50 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=B8=B2=E6=9F=93=E8=83=BD=E5=8A=9B):=20?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=BA=95=E7=BA=B9=E5=A1=AB=E5=85=85=E3=80=81?= =?UTF-8?q?=E6=8F=8F=E8=BE=B9=E5=8F=8A=E5=9F=BA=E7=A1=80=E9=A2=9C=E8=89=B2?= =?UTF-8?q?=E7=A9=BA=E9=97=B4=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ofdgo_object.go | 24 ++++++ ofdgo_page.go | 28 +++--- ofdgo_paint.go | 187 +++++++++++++++++++--------------------- ofdgo_reader.go | 9 ++ ofdgo_renderer_layer.go | 4 +- ofdgo_renderer_path.go | 98 ++++++++++++--------- ofdgo_renderer_text.go | 67 +++++++++----- ofdgo_res.go | 14 +++ 8 files changed, 253 insertions(+), 178 deletions(-) diff --git a/ofdgo_object.go b/ofdgo_object.go index aaede3c..0c515c5 100644 --- a/ofdgo_object.go +++ b/ofdgo_object.go @@ -19,6 +19,30 @@ import ( "strconv" ) +// UnmarshalXML 解析填充颜色并区分未支持的复杂颜色 +// 入参: d XML解码器, start 起始节点 +// 返回: error 错误信息 +func (c *FillColor) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + type plain FillColor + var value struct { + plain + Other *struct{} `xml:",any"` + } + if err := d.DecodeElement(&value, &start); err != nil { + return err + } + *c = FillColor(value.plain) + c.unsupported = value.Other != nil + return nil +} + +// UnmarshalXML 解析勾边颜色并区分未支持的复杂颜色 +// 入参: d XML解码器, start 起始节点 +// 返回: error 错误信息 +func (c *StrokeColor) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + return (*FillColor)(c).UnmarshalXML(d, start) +} + // graphicObjectTarget 图形对象集合 type graphicObjectTarget struct { objects *[]GraphicObject diff --git a/ofdgo_page.go b/ofdgo_page.go index 6e4e45a..d792e5c 100644 --- a/ofdgo_page.go +++ b/ofdgo_page.go @@ -22,6 +22,7 @@ type PageContent struct { ID string `xml:"-"` Area PageArea `xml:"Area"` Template []Template `xml:"Template"` + PageRes string `xml:"PageRes"` Content Content `xml:"Content"` Actions []Action `xml:"Actions>Action"` } @@ -105,11 +106,14 @@ type TextObject struct { // FillColor 填充颜色 type FillColor struct { - Value string `xml:"Value,attr"` - Alpha *int `xml:"Alpha,attr"` - Pattern *Pattern `xml:"Pattern"` - AxialShd *AxialShd `xml:"AxialShd"` - RadialShd *RadialShd `xml:"RadialShd"` + Value string `xml:"Value,attr"` + Index *int `xml:"Index,attr"` + ColorSpace string `xml:"ColorSpace,attr"` + Alpha *int `xml:"Alpha,attr"` + Pattern *Pattern `xml:"Pattern"` + AxialShd *AxialShd `xml:"AxialShd"` + RadialShd *RadialShd `xml:"RadialShd"` + unsupported bool } // Pattern 图案填充 @@ -119,6 +123,7 @@ type Pattern struct { XStep float64 `xml:"XStep,attr"` YStep float64 `xml:"YStep,attr"` ReflectMethod string `xml:"ReflectMethod,attr"` + RelativeTo string `xml:"RelativeTo,attr"` CTM string `xml:"CTM,attr"` CellContent PatternContent `xml:"CellContent"` } @@ -175,12 +180,7 @@ type PathObject struct { } // StrokeColor 勾边颜色 -type StrokeColor struct { - Value string `xml:"Value,attr"` - Alpha *int `xml:"Alpha,attr"` - AxialShd *AxialShd `xml:"AxialShd"` - RadialShd *RadialShd `xml:"RadialShd"` -} +type StrokeColor FillColor // AxialShd 轴向渐变 type AxialShd struct { @@ -215,8 +215,10 @@ type ShdSegment struct { // ShdColor 渐变颜色 type ShdColor struct { - Value string `xml:"Value,attr"` - Alpha *int `xml:"Alpha,attr"` + Value string `xml:"Value,attr"` + Index *int `xml:"Index,attr"` + ColorSpace string `xml:"ColorSpace,attr"` + Alpha *int `xml:"Alpha,attr"` } // ImageObject 图片对象 diff --git a/ofdgo_paint.go b/ofdgo_paint.go index 6dcd7e8..a163b3f 100644 --- a/ofdgo_paint.go +++ b/ofdgo_paint.go @@ -24,40 +24,66 @@ import ( ) // parseColorWithAlpha 解析带透明度的颜色 -// 入参: val 颜色值, alpha 透明度 +// 入参: value 颜色值, index 调色板索引, space 颜色空间标识, alpha 透明度 // 返回: color.Color 颜色对象 -func parseColorWithAlpha(val string, alpha *int) color.Color { - parts := strings.Fields(val) - if len(parts) >= 3 { - r := parseColorComponent(parts[0]) - g := parseColorComponent(parts[1]) - b := parseColorComponent(parts[2]) - a := 255 - if alpha != nil { - a = *alpha +func (r *Renderer) parseColorWithAlpha(value string, index *int, space string, alpha *int) color.Color { + if space == "" && r.Reader.doc != nil { + space = strconv.Itoa(r.Reader.doc.CommonData.DefaultCS) + } + cs := r.Reader.colorSpaceCache[space] + kind, bits, count := "RGB", 8, 3 + if cs != nil { + kind = cs.Type + switch cs.BitsPerComponent { + case 1, 2, 4, 8, 16: + bits = cs.BitsPerComponent } - 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), + if strings.TrimSpace(value) == "" && index != nil && 0 <= *index && *index < len(cs.Palette) { + value = cs.Palette[*index] } } - return color.Black + switch kind { + case "GRAY": + count = 1 + case "CMYK": + count = 4 + } + parts := strings.Fields(value) + var components [4]uint8 + limit := 1<= count { + for i, part := range parts[:count] { + v, err := parseColorComponent(part) + if err != nil || v < 0 || limit < v { + components = [4]uint8{} + break + } + components[i] = uint8((v*255 + limit/2) / limit) + } + } + red, green, blue := components[0], components[1], components[2] + switch kind { + case "GRAY": + green, blue = red, red + case "CMYK": + red, green, blue = color.CMYKToRGB(components[0], components[1], components[2], components[3]) + } + a := 255 + if alpha != nil { + a = clampColor(*alpha) + } + return color.RGBA{R: uint8(int(red) * a / 255), G: uint8(int(green) * a / 255), B: uint8(int(blue) * a / 255), A: uint8(a)} } // parseColorComponent 解析颜色分量 // 入参: s 颜色分量 -// 返回: int 颜色分量值 -func parseColorComponent(s string) int { - s = strings.TrimSpace(s) +// 返回: int 颜色分量值, error 错误信息 +func parseColorComponent(s string) (int, error) { if strings.HasPrefix(s, "#") { - v, _ := strconv.ParseInt(strings.TrimPrefix(s, "#"), 16, 0) - return int(v) + v, err := strconv.ParseInt(s[1:], 16, 0) + return int(v), err } - v, _ := strconv.Atoi(s) - return v + return strconv.Atoi(s) } // clampColor 限制颜色分量范围 @@ -135,7 +161,7 @@ func colorWithAlpha(c color.Color, alpha *int) color.Color { // parseFillColor 解析填充颜色 // 入参: fillColor 填充颜色节点 // 返回: color.Color 颜色对象 -func parseFillColor(fillColor *FillColor) color.Color { +func (r *Renderer) parseFillColor(fillColor *FillColor) color.Color { if fillColor == nil { return nil } @@ -143,117 +169,81 @@ func parseFillColor(fillColor *FillColor) color.Color { return nil } if fillColor.AxialShd != nil { - return parseShdColor(fillColor.AxialShd.Segment, fillColor.Alpha) + return r.parseShdColor(fillColor.AxialShd.Segment, fillColor.Alpha) } if fillColor.RadialShd != nil { - return parseShdColor(fillColor.RadialShd.Segment, fillColor.Alpha) + return r.parseShdColor(fillColor.RadialShd.Segment, fillColor.Alpha) } - if strings.TrimSpace(fillColor.Value) != "" { - return parseColorWithAlpha(fillColor.Value, fillColor.Alpha) + if fillColor.unsupported && strings.TrimSpace(fillColor.Value) == "" && fillColor.Index == nil { + return nil } - return nil + return r.parseColorWithAlpha(fillColor.Value, fillColor.Index, fillColor.ColorSpace, fillColor.Alpha) } // parseFillPaint 解析填充画刷 // 入参: fillColor 填充颜色节点, x X坐标, y Y坐标, pageH 页面高度 // 返回: any 填充画刷 -func parseFillPaint(fillColor *FillColor, x, y, pageH float64) any { +func (r *Renderer) parseFillPaint(fillColor *FillColor, x, y, pageH float64) any { if fillColor == nil { return nil } if fillColor.Pattern != nil { return nil } - if gradient := parseAxialShdGradient(fillColor.AxialShd, fillColor.Alpha, x, y, pageH); gradient != nil { + if gradient := r.parseAxialShdGradient(fillColor.AxialShd, fillColor.Alpha, x, y, pageH); gradient != nil { return newShdPaint(gradient, fillColor.AxialShd.Extend, fillColor.AxialShd.MapType, fillColor.AxialShd.MapUnit) } - if paint := parseRadialShdPaint(fillColor.RadialShd, fillColor.Alpha, x, y, pageH); paint != nil { + if paint := r.parseRadialShdPaint(fillColor.RadialShd, fillColor.Alpha, x, y, pageH); paint != nil { return paint } - if fillColor.AxialShd != nil { - return parseShdColor(fillColor.AxialShd.Segment, fillColor.Alpha) - } - if fillColor.RadialShd != nil { - return parseShdColor(fillColor.RadialShd.Segment, fillColor.Alpha) - } - if strings.TrimSpace(fillColor.Value) != "" { - return parseColorWithAlpha(fillColor.Value, fillColor.Alpha) - } - return nil + return r.parseFillColor(fillColor) } // parseStrokeColor 解析勾边颜色 // 入参: strokeColor 勾边颜色节点 // 返回: color.Color 颜色对象 -func parseStrokeColor(strokeColor *StrokeColor) color.Color { - if strokeColor == nil { - return nil - } - if strokeColor.AxialShd != nil { - return parseShdColor(strokeColor.AxialShd.Segment, strokeColor.Alpha) - } - if strokeColor.RadialShd != nil { - return parseShdColor(strokeColor.RadialShd.Segment, strokeColor.Alpha) - } - if strings.TrimSpace(strokeColor.Value) != "" { - return parseColorWithAlpha(strokeColor.Value, strokeColor.Alpha) - } - return nil +func (r *Renderer) parseStrokeColor(strokeColor *StrokeColor) color.Color { + return r.parseFillColor((*FillColor)(strokeColor)) } // parseStrokePaint 解析勾边画刷 // 入参: strokeColor 勾边颜色节点, x X坐标, y Y坐标, pageH 页面高度 // 返回: any 勾边画刷 -func parseStrokePaint(strokeColor *StrokeColor, x, y, pageH float64) any { - if strokeColor == nil { - return nil - } - if gradient := parseAxialShdGradient(strokeColor.AxialShd, strokeColor.Alpha, x, y, pageH); gradient != nil { - return newShdPaint(gradient, strokeColor.AxialShd.Extend, strokeColor.AxialShd.MapType, strokeColor.AxialShd.MapUnit) - } - if paint := parseRadialShdPaint(strokeColor.RadialShd, strokeColor.Alpha, x, y, pageH); paint != nil { - return paint - } - if strokeColor.AxialShd != nil { - return parseShdColor(strokeColor.AxialShd.Segment, strokeColor.Alpha) - } - if strokeColor.RadialShd != nil { - return parseShdColor(strokeColor.RadialShd.Segment, strokeColor.Alpha) - } - if strings.TrimSpace(strokeColor.Value) != "" { - return parseColorWithAlpha(strokeColor.Value, strokeColor.Alpha) - } - return nil +func (r *Renderer) parseStrokePaint(strokeColor *StrokeColor, x, y, pageH float64) any { + return r.parseFillPaint((*FillColor)(strokeColor), x, y, pageH) } -// patternColor 获取图案单元默认颜色 -// 入参: fillColor 填充颜色节点 -// 返回: color.Color 默认颜色 -func patternColor(fillColor *FillColor) color.Color { - if fillColor == nil || strings.TrimSpace(fillColor.Value) == "" { +type patternPaint struct { + *Pattern + color color.Color + alpha *int +} + +// parsePatternPaint 解析底纹画刷 +// 入参: pattern 底纹对象, value 颜色值, index 调色板索引, space 颜色空间标识, alpha 透明度 +// 返回: *patternPaint 底纹画刷 +func (r *Renderer) parsePatternPaint(pattern *Pattern, value string, index *int, space string, alpha *int) *patternPaint { + if pattern == nil { return nil } - return parseColorWithAlpha(fillColor.Value, fillColor.Alpha) + return &patternPaint{Pattern: pattern, color: r.parseColorWithAlpha(value, index, space, nil), alpha: alpha} } // parseShdColor 解析渐变颜色 // 入参: segments 渐变分段, alpha 透明度 // 返回: color.Color 颜色对象 -func parseShdColor(segments []ShdSegment, alpha *int) color.Color { - for _, segment := range segments { - if strings.TrimSpace(segment.Color.Value) == "" { - continue - } - segmentAlpha := mergeAlpha(segment.Color.Alpha, alpha) - return parseColorWithAlpha(segment.Color.Value, segmentAlpha) +func (r *Renderer) parseShdColor(segments []ShdSegment, alpha *int) color.Color { + if len(segments) == 0 { + return color.Black } - return color.Black + c := segments[0].Color + return r.parseColorWithAlpha(c.Value, c.Index, c.ColorSpace, mergeAlpha(c.Alpha, alpha)) } // parseShdSegments 解析渐变分段 // 入参: segments 渐变分段, alpha 透明度 // 返回: canvas.Grad 渐变分段 -func parseShdSegments(segments []ShdSegment, alpha *int) canvas.Grad { +func (r *Renderer) parseShdSegments(segments []ShdSegment, alpha *int) canvas.Grad { gradient := canvas.NewGradient() position, step := 0.0, 0.0 for i, segment := range segments { @@ -275,11 +265,8 @@ func parseShdSegments(segments []ShdSegment, alpha *int) canvas.Grad { } offset := position position += step - if strings.TrimSpace(segment.Color.Value) == "" { - continue - } segmentAlpha := mergeAlpha(segment.Color.Alpha, alpha) - gradient.Add(offset, colorToRGBA(parseColorWithAlpha(segment.Color.Value, segmentAlpha))) + gradient.Add(offset, colorToRGBA(r.parseColorWithAlpha(segment.Color.Value, segment.Color.Index, segment.Color.ColorSpace, segmentAlpha))) } if len(gradient) == 0 { return nil @@ -290,7 +277,7 @@ func parseShdSegments(segments []ShdSegment, alpha *int) canvas.Grad { // parseAxialShdGradient 解析轴向渐变 // 入参: axialShd 轴向渐变节点, alpha 透明度, x X坐标, y Y坐标, pageH 页面高度 // 返回: canvas.Gradient 渐变对象 -func parseAxialShdGradient(axialShd *AxialShd, alpha *int, x, y, pageH float64) canvas.Gradient { +func (r *Renderer) parseAxialShdGradient(axialShd *AxialShd, alpha *int, x, y, pageH float64) canvas.Gradient { if axialShd == nil { return nil } @@ -299,7 +286,7 @@ func parseAxialShdGradient(axialShd *AxialShd, alpha *int, x, y, pageH float64) if len(start) < 2 || len(end) < 2 { return nil } - gradient := parseShdSegments(axialShd.Segment, alpha) + gradient := r.parseShdSegments(axialShd.Segment, alpha) if gradient == nil { return nil } @@ -342,7 +329,7 @@ func axialShdClip(ctx *canvas.Context, gradient *canvas.LinearGradient, extend i // parseRadialShdPaint 解析径向渐变画刷 // 入参: radialShd 径向渐变节点, alpha 透明度, x X坐标, y Y坐标, pageH 页面高度 // 返回: *shdPaint 渐变画刷 -func parseRadialShdPaint(radialShd *RadialShd, alpha *int, x, y, pageH float64) *shdPaint { +func (r *Renderer) parseRadialShdPaint(radialShd *RadialShd, alpha *int, x, y, pageH float64) *shdPaint { if radialShd == nil || radialShd.EndRadius <= 0 { return nil } @@ -351,7 +338,7 @@ func parseRadialShdPaint(radialShd *RadialShd, alpha *int, x, y, pageH float64) if len(start) < 2 || len(end) < 2 { return nil } - gradient := parseShdSegments(radialShd.Segment, alpha) + gradient := r.parseShdSegments(radialShd.Segment, alpha) if gradient == nil { return nil } diff --git a/ofdgo_reader.go b/ofdgo_reader.go index 4794caf..7ec0cd7 100644 --- a/ofdgo_reader.go +++ b/ofdgo_reader.go @@ -32,6 +32,7 @@ type Reader struct { RootDir string ResMap map[string]string fontCache map[string]*Font + colorSpaceCache map[string]*ColorSpace drawParamCache map[string]*DrawParam compositeGraphicUnitCache map[string]*CompositeGraphicUnit doc *Document @@ -74,6 +75,7 @@ func (r *Reader) initRoot() error { r.OFD = &ofd r.ResMap = make(map[string]string) r.fontCache = make(map[string]*Font) + r.colorSpaceCache = make(map[string]*ColorSpace) r.drawParamCache = make(map[string]*DrawParam) r.compositeGraphicUnitCache = make(map[string]*CompositeGraphicUnit) return nil @@ -186,6 +188,10 @@ func (r *Reader) loadRes(resPath string) { return } baseLoc := res.BaseLoc + for i := range res.ColorSpaces.ColorSpace { + cs := &res.ColorSpaces.ColorSpace[i] + r.colorSpaceCache[cs.ID] = cs + } for _, mm := range res.MultiMedias.MultiMedia { if mm.MediaFile != "" { if finalPath := resolveResourcePath(resPath, baseLoc, mm.MediaFile); finalPath != "" { @@ -245,6 +251,9 @@ func (r *Reader) PageContent(page Page) (*PageContent, error) { return nil, fmt.Errorf("failed to unmarshal page content: %w", err) } content.ID = page.ID + if content.PageRes != "" { + r.loadRes(resolveResourcePath(page.BaseLoc, "", content.PageRes)) + } return &content, nil } diff --git a/ofdgo_renderer_layer.go b/ofdgo_renderer_layer.go index 4bad270..ca968ac 100644 --- a/ofdgo_renderer_layer.go +++ b/ofdgo_renderer_layer.go @@ -194,10 +194,10 @@ func (r *Renderer) drawParamDefaults(id string, defaultFill, defaultStroke color defaultLW = dp.LineWidth } if dp.FillColor != nil { - defaultFill = parseFillColor(dp.FillColor) + defaultFill = r.parseFillColor(dp.FillColor) } if dp.StrokeColor != nil { - defaultStroke = parseStrokeColor(dp.StrokeColor) + defaultStroke = r.parseStrokeColor(dp.StrokeColor) } return defaultFill, defaultStroke, defaultLW } diff --git a/ofdgo_renderer_path.go b/ofdgo_renderer_path.go index a4e0034..e395b34 100644 --- a/ofdgo_renderer_path.go +++ b/ofdgo_renderer_path.go @@ -26,17 +26,17 @@ import ( const defaultPathLineWidth = 0.353 type pathStyle struct { - fillColor color.Color - strokeColor color.Color - fillPaint any - strokePaint any - fillPattern *Pattern - fillPatternColor color.Color - lineWidth float64 - lineCap canvas.Capper - lineJoin canvas.Joiner - dashOffset float64 - dashPattern []float64 + fillColor color.Color + strokeColor color.Color + fillPaint any + strokePaint any + fillPattern *patternPaint + strokePattern *patternPaint + lineWidth float64 + lineCap canvas.Capper + lineJoin canvas.Joiner + dashOffset float64 + dashPattern []float64 } // newPathStyle 创建路径样式 @@ -59,21 +59,21 @@ func newPathStyle(defaultFill, defaultStroke color.Color, defaultLW float64, alp } // applyFillColor 应用填充颜色 -// 入参: fill 填充颜色, bx 边界X坐标, by 边界Y坐标, pageH 页面高度, alpha 对象透明度 -func (s *pathStyle) applyFillColor(fill *FillColor, bx, by, pageH float64, alpha *int) { +// 入参: r 渲染器, fill 填充颜色, bx 边界X坐标, by 边界Y坐标, pageH 页面高度, alpha 对象透明度 +func (s *pathStyle) applyFillColor(r *Renderer, fill *FillColor, bx, by, pageH float64, alpha *int) { fillColorNode := withFillAlpha(fill, alpha) - s.fillPattern = fillColorNode.Pattern - s.fillPatternColor = patternColor(fillColorNode) - s.fillColor = parseFillColor(fillColorNode) - s.fillPaint = parseFillPaint(fillColorNode, bx, by, pageH) + s.fillPattern = r.parsePatternPaint(fillColorNode.Pattern, fillColorNode.Value, fillColorNode.Index, fillColorNode.ColorSpace, fillColorNode.Alpha) + s.fillColor = r.parseFillColor(fillColorNode) + s.fillPaint = r.parseFillPaint(fillColorNode, bx, by, pageH) } // applyStrokeColor 应用描边颜色 -// 入参: stroke 描边颜色, bx 边界X坐标, by 边界Y坐标, pageH 页面高度, alpha 对象透明度 -func (s *pathStyle) applyStrokeColor(stroke *StrokeColor, bx, by, pageH float64, alpha *int) { +// 入参: r 渲染器, stroke 描边颜色, bx 边界X坐标, by 边界Y坐标, pageH 页面高度, alpha 对象透明度 +func (s *pathStyle) applyStrokeColor(r *Renderer, stroke *StrokeColor, bx, by, pageH float64, alpha *int) { strokeColorNode := withStrokeAlpha(stroke, alpha) - s.strokeColor = parseStrokeColor(strokeColorNode) - s.strokePaint = parseStrokePaint(strokeColorNode, bx, by, pageH) + s.strokePattern = r.parsePatternPaint(strokeColorNode.Pattern, strokeColorNode.Value, strokeColorNode.Index, strokeColorNode.ColorSpace, strokeColorNode.Alpha) + s.strokeColor = r.parseStrokeColor(strokeColorNode) + s.strokePaint = r.parseStrokePaint(strokeColorNode, bx, by, pageH) } // pathLineCap 转换线帽样式 @@ -103,16 +103,16 @@ func pathLineJoin(join string, fallback canvas.Joiner) canvas.Joiner { } // applyDrawParam 应用绘制参数样式 -// 入参: dp 绘制参数, bx 边界X坐标, by 边界Y坐标, pageH 页面高度, alpha 对象透明度 -func (s *pathStyle) applyDrawParam(dp *DrawParam, bx, by, pageH float64, alpha *int) { +// 入参: r 渲染器, dp 绘制参数, bx 边界X坐标, by 边界Y坐标, pageH 页面高度, alpha 对象透明度 +func (s *pathStyle) applyDrawParam(r *Renderer, dp *DrawParam, bx, by, pageH float64, alpha *int) { if dp.LineWidth > 0 { s.lineWidth = dp.LineWidth } if dp.FillColor != nil { - s.applyFillColor(dp.FillColor, bx, by, pageH, alpha) + s.applyFillColor(r, dp.FillColor, bx, by, pageH, alpha) } if dp.StrokeColor != nil { - s.applyStrokeColor(dp.StrokeColor, bx, by, pageH, alpha) + s.applyStrokeColor(r, dp.StrokeColor, bx, by, pageH, alpha) } if dp.Cap != "" { s.lineCap = pathLineCap(dp.Cap, s.lineCap) @@ -127,16 +127,16 @@ func (s *pathStyle) applyDrawParam(dp *DrawParam, bx, by, pageH float64, alpha * } // applyPathObject 应用路径对象样式 -// 入参: obj 路径对象, bx 边界X坐标, by 边界Y坐标, pageH 页面高度 -func (s *pathStyle) applyPathObject(obj PathObject, bx, by, pageH float64) { +// 入参: r 渲染器, obj 路径对象, bx 边界X坐标, by 边界Y坐标, pageH 页面高度 +func (s *pathStyle) applyPathObject(r *Renderer, obj PathObject, bx, by, pageH float64) { if obj.LineWidth > 0 { s.lineWidth = obj.LineWidth } if obj.FillColor != nil { - s.applyFillColor(obj.FillColor, bx, by, pageH, obj.Alpha) + s.applyFillColor(r, obj.FillColor, bx, by, pageH, obj.Alpha) } if obj.StrokeColor != nil { - s.applyStrokeColor(obj.StrokeColor, bx, by, pageH, obj.Alpha) + s.applyStrokeColor(r, obj.StrokeColor, bx, by, pageH, obj.Alpha) } if obj.Cap != "" { s.lineCap = pathLineCap(obj.Cap, canvas.ButtCap) @@ -183,11 +183,15 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 style := newPathStyle(defaultFill, defaultStroke, defaultLW, obj.Alpha) if obj.DrawParam != "" { if dp := r.getDrawParam(obj.DrawParam, nil); dp != nil { - style.applyDrawParam(dp, bx, by, pageH, obj.Alpha) + style.applyDrawParam(r, dp, bx, by, pageH, obj.Alpha) } } - style.applyPathObject(obj, bx, by, pageH) + style.applyPathObject(r, obj, bx, by, pageH) style.scale(ctm) + patternCTM := TranslationMatrix(bx, by).Multiply(ctm) + if boundaryInCTM && parentCTM != nil { + patternCTM = parentCTM.Multiply(TranslationMatrix(bx, by)).Multiply(localCTM) + } pathCTM := ctm if boundaryInCTM { pathCTM = localCTM @@ -227,7 +231,7 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 fp = applyClipPath(fp, fillClip) } if style.fillPattern != nil { - r.renderPattern(ctx, style.fillPattern, style.fillPatternColor, pageH, fp, ctm, bx, by) + r.renderPattern(ctx, style.fillPattern, pageH, fp, patternCTM) } else if style.fillPaint != nil { ctx.SetFillRule(fillRule) ctx.SetFill(fillPaint) @@ -259,13 +263,17 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 ctx.SetDashes(0) } strokeClip := intersectClipPath(clipPath, shadingClip) - if strokeClip != nil || strokeView != canvas.Identity { + if strokeClip != nil || strokeView != canvas.Identity || style.strokePattern != nil { sp = sp.Copy() sp = sp.Stroke(style.lineWidth, style.lineCap, style.lineJoin, canvas.Tolerance) sp = applyClipPath(sp, strokeClip) - ctx.SetFill(strokePaint) - ctx.SetStrokeColor(canvas.Transparent) - drawShdPath(ctx, sp, strokeView) + if style.strokePattern != nil { + r.renderPattern(ctx, style.strokePattern, pageH, sp, patternCTM) + } else { + ctx.SetFill(strokePaint) + ctx.SetStrokeColor(canvas.Transparent) + drawShdPath(ctx, sp, strokeView) + } } else { ctx.DrawPath(0, 0, sp) } @@ -274,22 +282,25 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 } // renderPattern 渲染图案填充 -// 入参: ctx 画布上下文, pattern 图案对象, defaultColor 默认颜色, pageH 页面高度, clip 填充区域, parentCTM 父级CTM, bx 边界X坐标, by 边界Y坐标 -func (r *Renderer) renderPattern(ctx *canvas.Context, pattern *Pattern, defaultColor color.Color, pageH float64, clip *canvas.Path, parentCTM Matrix, bx, by float64) { - if pattern == nil || clip == nil || len(pattern.CellContent.Objects) == 0 { +// 入参: ctx 画布上下文, pattern 底纹画刷, pageH 页面高度, clip 绘制区域, objectCTM 对象变换矩阵 +func (r *Renderer) renderPattern(ctx *canvas.Context, pattern *patternPaint, pageH float64, clip *canvas.Path, objectCTM Matrix) { + if clip == nil || clip.Empty() || len(pattern.CellContent.Objects) == 0 { return } xStep, yStep := pattern.XStep, pattern.YStep - if xStep == 0 { + if xStep < pattern.Width { xStep = pattern.Width } - if yStep == 0 { + if yStep < pattern.Height { yStep = pattern.Height } if xStep <= 0 || yStep <= 0 { return } - patternCTM := TranslationMatrix(bx, by).Multiply(parentCTM).Multiply(NewMatrix(pattern.CTM)) + patternCTM := NewMatrix(pattern.CTM) + if pattern.RelativeTo != "Page" { + patternCTM = objectCTM.Multiply(patternCTM) + } invCTM, ok := patternCTM.Invert() if !ok { return @@ -329,7 +340,8 @@ func (r *Renderer) renderPattern(ctx *canvas.Context, pattern *Pattern, defaultC tileCTM = tileCTM.Multiply(Matrix{a: 1, d: -1, f: pattern.Height}) } for _, obj := range pattern.CellContent.Objects { - r.renderObject(ctx, obj, pageH, defaultColor, defaultColor, 0, &tileCTM, true, clip) + obj = mergeGraphicObjectAlpha(obj, pattern.alpha) + r.renderObject(ctx, obj, pageH, pattern.color, pattern.color, 0, &tileCTM, true, clip) } } } diff --git a/ofdgo_renderer_text.go b/ofdgo_renderer_text.go index 856b35c..39bbc8a 100644 --- a/ofdgo_renderer_text.go +++ b/ofdgo_renderer_text.go @@ -76,15 +76,19 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 fillColor = colorWithAlpha(canvas.Black, obj.Alpha) } var fillPaint any = fillColor + var fillPattern *patternPaint + var fillColorNode *FillColor if dp != nil && dp.FillColor != nil { - fillColorNode := withFillAlpha(dp.FillColor, obj.Alpha) - fillColor = parseFillColor(fillColorNode) - fillPaint = parseFillPaint(fillColorNode, bx, by, pageH) + fillColorNode = dp.FillColor } if obj.FillColor != nil { - fillColorNode := withFillAlpha(obj.FillColor, obj.Alpha) - fillColor = parseFillColor(fillColorNode) - fillPaint = parseFillPaint(fillColorNode, bx, by, pageH) + fillColorNode = obj.FillColor + } + if fillColorNode != nil { + fillColorNode = withFillAlpha(fillColorNode, obj.Alpha) + fillColor = r.parseFillColor(fillColorNode) + fillPaint = r.parseFillPaint(fillColorNode, bx, by, pageH) + fillPattern = r.parsePatternPaint(fillColorNode.Pattern, fillColorNode.Value, fillColorNode.Index, fillColorNode.ColorSpace, fillColorNode.Alpha) } if fillPaint == nil { fillPaint = fillColor @@ -96,10 +100,10 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 strokeView := canvas.Identity if shouldStroke { if dp != nil { - strokeStyle.applyDrawParam(dp, bx, by, pageH, obj.Alpha) + strokeStyle.applyDrawParam(r, dp, bx, by, pageH, obj.Alpha) } if obj.StrokeColor != nil { - strokeStyle.applyStrokeColor(obj.StrokeColor, bx, by, pageH, obj.Alpha) + strokeStyle.applyStrokeColor(r, obj.StrokeColor, bx, by, pageH, obj.Alpha) } if obj.LineWidth > 0 { strokeStyle.lineWidth = obj.LineWidth @@ -111,6 +115,10 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 strokeStyle.strokePaint, shadingClip, strokeView = resolveShdPaint(ctx, strokeStyle.strokePaint) strokeClip = intersectClipPath(strokeClip, shadingClip) } + patternCTM := TranslationMatrix(bx, by).Multiply(ctm) + if boundaryInCTM && parentCTM != nil { + patternCTM = parentCTM.Multiply(TranslationMatrix(bx, by)).Multiply(localCTM) + } fontStyle := canvas.FontRegular weight := obj.Weight if weight == 0 && dp != nil && dp.Weight > 0 { @@ -173,7 +181,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 } dxs, dys := parseFloats(tc.DeltaX), parseFloats(tc.DeltaY) xs, ys := parseFloats(tc.X), parseFloats(tc.Y) - drawAsPath := embeddedFont || textCodePositioned(tc, xs, ys) || fillClip != nil || shadedFill || shouldStroke + drawAsPath := embeddedFont || textCodePositioned(tc, xs, ys) || fillClip != nil || shadedFill || fillPattern != nil || shouldStroke cx, cy := 0.0, 0.0 previousAdvance := 0.0 if len(xs) > 0 { @@ -238,7 +246,9 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 transform = transform.Mul(glyphMatrix) } path := glyphPath.Copy().Transform(transform.Scale(scaleX, 1)) - if shouldFill && fillPaint != nil { + if shouldFill && fillPattern != nil { + r.renderPattern(ctx, fillPattern, pageH, applyClipPath(path.Copy(), fillClip), patternCTM) + } else if shouldFill && fillPaint != nil { ctx.SetFill(fillPaint) ctx.SetStrokeColor(canvas.Transparent) drawShdPath(ctx, applyClipPath(path.Copy(), fillClip), fillView) @@ -247,26 +257,35 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 path = path.Dash(strokeStyle.dashOffset, strokeStyle.dashPattern...) } path = path.Stroke(strokeStyle.lineWidth, strokeStyle.lineCap, strokeStyle.lineJoin, canvas.Tolerance) - ctx.SetFill(strokeStyle.strokePaint) - ctx.SetStrokeColor(canvas.Transparent) - drawShdPath(ctx, applyClipPath(path, strokeClip), strokeView) + path = applyClipPath(path, strokeClip) + if strokeStyle.strokePattern != nil { + r.renderPattern(ctx, strokeStyle.strokePattern, pageH, path, patternCTM) + } else { + ctx.SetFill(strokeStyle.strokePaint) + ctx.SetStrokeColor(canvas.Transparent) + drawShdPath(ctx, path, strokeView) + } if hasUnderline { underline := &canvas.Path{} underline.MoveTo(0, -sizeMM*0.1) underline.LineTo(glyphWidth*scaleX, -sizeMM*0.1) underline = underline.Stroke(sizeMM*0.05, canvas.ButtCap, canvas.MiterJoin, canvas.Tolerance) - if shouldFill { + if shouldFill && fillPattern != nil { + r.renderPattern(ctx, fillPattern, pageH, applyClipPath(underline.Transform(transform), clipPath), patternCTM) + } else if shouldFill { ctx.SetFillColor(fillColor) ctx.DrawPath(0, 0, applyClipPath(underline.Transform(transform), clipPath)) + } else if strokeStyle.strokePattern != nil { + r.renderPattern(ctx, strokeStyle.strokePattern, pageH, applyClipPath(underline.Transform(transform), strokeClip), patternCTM) } else { drawShdPath(ctx, applyClipPath(underline.Transform(transform), strokeClip), strokeView) } } continue } - if fillPaint != nil { + if fillPaint != nil || fillPattern != nil { ctx.SetFill(fillPaint) - if fillClip != nil || shadedFill { + if fillClip != nil || shadedFill || fillPattern != nil { scaleX := hScale if advanceLimit > 0 && glyphWidth*scaleX > advanceLimit { scaleX = advanceLimit / glyphWidth @@ -277,7 +296,11 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 textTransform = textTransform.Mul(glyphMatrix) } glyphPath = applyClipPath(glyphPath.Copy().Transform(textTransform.Scale(scaleX, 1)), fillClip) - drawShdPath(ctx, glyphPath, fillView) + if fillPattern != nil { + r.renderPattern(ctx, fillPattern, pageH, glyphPath, patternCTM) + } else { + drawShdPath(ctx, glyphPath, fillView) + } if hasUnderline { uw := sizeMM * 0.05 off := sizeMM * 0.1 @@ -286,9 +309,13 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 underline.LineTo(textWidth, -off) underline = underline.Stroke(uw, canvas.ButtCap, canvas.MiterJoin, canvas.Tolerance) underline = applyClipPath(underline.Transform(textTransform), clipPath) - ctx.SetFillColor(fillColor) - ctx.SetStrokeColor(canvas.Transparent) - ctx.DrawPath(0, 0, underline) + if fillPattern != nil { + r.renderPattern(ctx, fillPattern, pageH, underline, patternCTM) + } else { + ctx.SetFillColor(fillColor) + ctx.SetStrokeColor(canvas.Transparent) + ctx.DrawPath(0, 0, underline) + } } continue } diff --git a/ofdgo_res.go b/ofdgo_res.go index 5e03948..cc12d86 100644 --- a/ofdgo_res.go +++ b/ofdgo_res.go @@ -20,12 +20,26 @@ import "encoding/xml" type Res struct { XMLName xml.Name `xml:"Res"` BaseLoc string `xml:"BaseLoc,attr"` + ColorSpaces ColorSpaces `xml:"ColorSpaces"` Fonts Fonts `xml:"Fonts"` MultiMedias MultiMedias `xml:"MultiMedias"` DrawParams DrawParams `xml:"DrawParams"` CompositeGraphicUnits CompositeGraphicUnits `xml:"CompositeGraphicUnits"` } +// ColorSpaces 颜色空间集合 +type ColorSpaces struct { + ColorSpace []ColorSpace `xml:"ColorSpace"` +} + +// ColorSpace 颜色空间定义 +type ColorSpace struct { + ID string `xml:"ID,attr"` + Type string `xml:"Type,attr"` + BitsPerComponent int `xml:"BitsPerComponent,attr"` + Palette []string `xml:"Palette>CV"` +} + // Fonts 字体集合 type Fonts struct { Font []Font `xml:"Font"`