diff --git a/ofdgo_page.go b/ofdgo_page.go index e7fa7f1..43ba002 100644 --- a/ofdgo_page.go +++ b/ofdgo_page.go @@ -58,7 +58,7 @@ type GraphicObject struct { // Clips 裁剪区域集合 type Clips struct { - TransFlag bool `xml:"TransFlag,attr"` + TransFlag *bool `xml:"TransFlag,attr"` Clip []Clip `xml:"Clip"` } @@ -210,6 +210,7 @@ type ImageObject struct { ID string `xml:"ID,attr"` Boundary string `xml:"Boundary,attr"` ResourceID string `xml:"ResourceID,attr"` + ImageMask string `xml:"ImageMask,attr"` CTM string `xml:"CTM,attr"` Alpha *int `xml:"Alpha,attr"` Visible *bool `xml:"Visible,attr"` diff --git a/ofdgo_renderer_image.go b/ofdgo_renderer_image.go index 37b28a1..e0ab8e9 100644 --- a/ofdgo_renderer_image.go +++ b/ofdgo_renderer_image.go @@ -22,11 +22,13 @@ import ( "github.com/tdewolff/canvas" canvasimage "github.com/tdewolff/canvas/image" + "github.com/tdewolff/canvas/renderers/rasterizer" + "golang.org/x/image/draw" ) // renderImage 渲染图片 -// 入参: ctx 画布上下文, obj 图片对象, pageH 页面高度, parentCTM 父级CTM, boundaryInCTM 边界是否参与父级CTM -func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float64, parentCTM *Matrix, boundaryInCTM bool) { +// 入参: ctx 画布上下文, obj 图片对象, pageH 页面高度, parentCTM 父级CTM, boundaryInCTM 边界是否参与父级CTM, parentClip 父级裁剪路径 +func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float64, parentCTM *Matrix, boundaryInCTM bool, parentClip *canvas.Path) { if obj.Visible != nil && !*obj.Visible { return } @@ -39,18 +41,25 @@ func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float return } box, _ := ParseBox(obj.Boundary) + if maskPath, ok := r.Reader.ResMap[obj.ImageMask]; ok { + if mask, err := r.decodeImageResource(maskPath); err == nil { + img = imageWithMask(img, mask) + } + } + img = imageWithAlpha(img, obj.Alpha) imgBounds := img.Bounds() imgW, imgH := float64(imgBounds.Dx()), float64(imgBounds.Dy()) if imgW <= 0 || imgH <= 0 { return } - img = imageWithAlpha(img, obj.Alpha) - pad := 0 - img, pad = imageWithTransparentEdge(img) ctm := NewMatrix(obj.CTM) if obj.CTM == "" { ctm = Matrix{a: box.W, d: box.H} } + objectCTM := ctm + if parentCTM != nil { + objectCTM = parentCTM.Multiply(ctm) + } var m canvas.Matrix if boundaryInCTM && parentCTM != nil { x0 := box.X + ctm.c + ctm.e @@ -68,6 +77,9 @@ func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float {-ctm.b / imgW, ctm.d / imgH, pageH - box.Y - ctm.d - ctm.f}, } } + clipPath := intersectClipPath(parentClip, r.buildClipPath(obj.Clips, pageH, box.X, box.Y, objectCTM)) + img = imageWithClip(img, clipPath, m) + img, pad := imageWithTransparentEdge(img) if pad > 0 { p := float64(pad) m[0][2] -= m[0][0]*p + m[0][1]*p @@ -76,6 +88,102 @@ func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float ctx.RenderImage(img, ctx.CoordSystemView().Mul(ctx.View()).Mul(m)) } +// imageWithMask 应用图片蒙版 +// 入参: img 图片对象, mask 蒙版图片 +// 返回: image.Image 蒙版处理后的图片对象 +func imageWithMask(img, mask image.Image) image.Image { + if img == nil || mask == nil { + return img + } + bounds := img.Bounds() + if bounds.Empty() || mask.Bounds().Empty() { + return img + } + source := imagePixelSource(img) + maskSource := imagePixelSource(mask) + maskBounds := maskSource.Bounds() + var opacity image.Image = maskSource + if bounds.Size() != maskBounds.Size() { + resized := image.NewGray(bounds) + draw.CatmullRom.Scale(resized, bounds, maskSource, maskBounds, draw.Src, nil) + opacity = resized + maskBounds = bounds + } + out := image.NewNRGBA(bounds) + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + mx := maskBounds.Min.X + x - bounds.Min.X + my := maskBounds.Min.Y + y - bounds.Min.Y + a := color.GrayModel.Convert(opacity.At(mx, my)).(color.Gray).Y + c := imageNRGBAAt(source, x, y) + c.A = uint8(int(c.A) * int(a) / 255) + out.SetNRGBA(x, y, c) + } + } + return out +} + +// imageWithClip 应用图片裁剪区域 +// 入参: img 图片对象, clipPath 裁剪路径, m 图片变换矩阵 +// 返回: image.Image 裁剪后的图片对象 +func imageWithClip(img image.Image, clipPath *canvas.Path, m canvas.Matrix) image.Image { + if img == nil || clipPath == nil || m.Det() == 0 { + return img + } + bounds := img.Bounds() + w, h := bounds.Dx(), bounds.Dy() + if w == 0 || h == 0 { + return img + } + p0 := m.Dot(canvas.Point{}) + p1 := m.Dot(canvas.Point{X: float64(w)}) + p2 := m.Dot(canvas.Point{X: float64(w), Y: float64(h)}) + p3 := m.Dot(canvas.Point{Y: float64(h)}) + imagePath := &canvas.Path{} + imagePath.MoveTo(p0.X, p0.Y) + imagePath.LineTo(p1.X, p1.Y) + imagePath.LineTo(p2.X, p2.Y) + imagePath.LineTo(p3.X, p3.Y) + imagePath.Close() + if rect, ok := rectangularPath(clipPath); ok { + if rect.Contains(imagePath.FastBounds()) { + return img + } + } else if clipPath.Contains(imagePath) { + return img + } + clip := clipPath.Copy().Transform(m.Inv()) + clipCanvas := canvas.New(float64(w), float64(h)) + ctx := canvas.NewContext(clipCanvas) + ctx.SetFillColor(canvas.White) + ctx.SetStrokeColor(canvas.Transparent) + ctx.DrawPath(0, 0, clip) + mask := rasterizer.Draw(clipCanvas, canvas.DPMM(1), canvas.DefaultColorSpace) + opaque := true + for y := 0; y < h && opaque; y++ { + for x := 0; x < w; x++ { + if mask.RGBAAt(x, y).A != 255 { + opaque = false + break + } + } + } + if opaque { + return img + } + source := imagePixelSource(img) + out := image.NewNRGBA(bounds) + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + c := imageNRGBAAt(source, x, y) + a := mask.RGBAAt(x-bounds.Min.X, y-bounds.Min.Y).A + c.A = uint8(int(c.A) * int(a) / 255) + out.SetNRGBA(x, y, c) + } + } + return out +} + // decodeImageResource 解码图片资源 // 入参: resPath 图片资源路径 // 返回: image.Image 图片对象, error 错误信息 diff --git a/ofdgo_renderer_layer.go b/ofdgo_renderer_layer.go index 7af7f65..88fefb4 100644 --- a/ofdgo_renderer_layer.go +++ b/ofdgo_renderer_layer.go @@ -30,7 +30,7 @@ func (r *Renderer) renderAnnotations(ctx *canvas.Context, pageID string, pageH f 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, false) + r.renderObject(ctx, obj, pageH, nil, nil, 0, &ctm, false, nil) } } } @@ -70,27 +70,27 @@ func (r *Renderer) renderLayer(ctx *canvas.Context, layer Layer, pageH float64, defaultFill, defaultStroke, defaultLW = r.drawParamDefaults(layer.DrawParam, defaultFill, defaultStroke, defaultLW) if len(layer.Objects) > 0 { for _, obj := range layer.Objects { - r.renderObject(ctx, obj, pageH, defaultFill, defaultStroke, defaultLW, parentCTM, false) + r.renderObject(ctx, obj, pageH, defaultFill, defaultStroke, defaultLW, parentCTM, false, nil) } return } for _, textObj := range layer.TextObject { - r.renderText(ctx, textObj, pageH, defaultFill, defaultStroke, parentCTM, false) + r.renderText(ctx, textObj, pageH, defaultFill, defaultStroke, parentCTM, false, nil) } for _, pathObj := range layer.PathObject { - r.renderPath(ctx, pathObj, pageH, defaultFill, defaultStroke, defaultLW, parentCTM, false) + r.renderPath(ctx, pathObj, pageH, defaultFill, defaultStroke, defaultLW, parentCTM, false, nil) } for _, imgObj := range layer.ImageObject { - r.renderImage(ctx, imgObj, pageH, parentCTM, false) + r.renderImage(ctx, imgObj, pageH, parentCTM, false, nil) } for _, cgu := range layer.CompositeGraphicUnit { - r.renderCompositeGraphicUnit(ctx, cgu, pageH, defaultFill, defaultStroke, defaultLW, parentCTM, false) + r.renderCompositeGraphicUnit(ctx, cgu, pageH, defaultFill, defaultStroke, defaultLW, parentCTM, false, nil) } } // renderCompositeGraphicUnit 渲染复合图元 -// 入参: ctx 画布上下文, cgu 复合图元对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM, boundaryInCTM 边界是否参与CTM变换 -func (r *Renderer) renderCompositeGraphicUnit(ctx *canvas.Context, cgu CompositeGraphicUnit, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix, boundaryInCTM bool) { +// 入参: ctx 画布上下文, cgu 复合图元对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM, boundaryInCTM 边界是否参与CTM变换, parentClip 父级裁剪路径 +func (r *Renderer) renderCompositeGraphicUnit(ctx *canvas.Context, cgu CompositeGraphicUnit, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix, boundaryInCTM bool, parentClip *canvas.Path) { if cgu.Visible != nil && !*cgu.Visible { return } @@ -99,53 +99,55 @@ func (r *Renderer) renderCompositeGraphicUnit(ctx *canvas.Context, cgu Composite if parentCTM != nil { currentCTM = parentCTM.Multiply(currentCTM) } + box, _ := ParseBox(cgu.Boundary) + clipPath := intersectClipPath(parentClip, r.buildClipPath(cgu.Clips, pageH, box.X, box.Y, currentCTM)) if cgu.ResourceID != "" { if ref, ok := r.CompositeGraphicUnits[cgu.ResourceID]; ok { refCopy := *ref refCopy.Alpha = mergeAlpha(refCopy.Alpha, cgu.Alpha) - r.renderCompositeGraphicUnit(ctx, refCopy, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM, true) + r.renderCompositeGraphicUnit(ctx, refCopy, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM, true, clipPath) } } defaultFill, defaultStroke, defaultLW = r.drawParamDefaults(cgu.DrawParam, defaultFill, defaultStroke, defaultLW) if len(cgu.Objects) > 0 { for _, obj := range cgu.Objects { obj = mergeGraphicObjectAlpha(obj, cgu.Alpha) - r.renderObject(ctx, obj, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM, boundaryInCTM) + r.renderObject(ctx, obj, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM, boundaryInCTM, clipPath) } ctx.Pop() return } for _, imgObj := range cgu.ImageObject { imgObj.Alpha = mergeAlpha(imgObj.Alpha, cgu.Alpha) - r.renderImage(ctx, imgObj, pageH, ¤tCTM, boundaryInCTM) + r.renderImage(ctx, imgObj, pageH, ¤tCTM, boundaryInCTM, clipPath) } for _, pathObj := range cgu.PathObject { pathObj.Alpha = mergeAlpha(pathObj.Alpha, cgu.Alpha) - r.renderPath(ctx, pathObj, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM, boundaryInCTM) + r.renderPath(ctx, pathObj, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM, boundaryInCTM, clipPath) } for _, textObj := range cgu.TextObject { textObj.Alpha = mergeAlpha(textObj.Alpha, cgu.Alpha) - r.renderText(ctx, textObj, pageH, defaultFill, defaultStroke, ¤tCTM, boundaryInCTM) + r.renderText(ctx, textObj, pageH, defaultFill, defaultStroke, ¤tCTM, boundaryInCTM, clipPath) } for _, subCgu := range cgu.CompositeGraphicUnit { subCgu.Alpha = mergeAlpha(subCgu.Alpha, cgu.Alpha) - r.renderCompositeGraphicUnit(ctx, subCgu, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM, boundaryInCTM) + r.renderCompositeGraphicUnit(ctx, subCgu, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM, boundaryInCTM, clipPath) } ctx.Pop() } // renderObject 渲染图形对象 -// 入参: ctx 画布上下文, obj 图形对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM, boundaryInCTM 边界是否参与CTM变换 -func (r *Renderer) renderObject(ctx *canvas.Context, obj GraphicObject, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix, boundaryInCTM bool) { +// 入参: ctx 画布上下文, obj 图形对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM, boundaryInCTM 边界是否参与CTM变换, parentClip 父级裁剪路径 +func (r *Renderer) renderObject(ctx *canvas.Context, obj GraphicObject, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix, boundaryInCTM bool, parentClip *canvas.Path) { switch obj.Type { case "TextObject": - r.renderText(ctx, obj.TextObject, pageH, defaultFill, defaultStroke, parentCTM, boundaryInCTM) + r.renderText(ctx, obj.TextObject, pageH, defaultFill, defaultStroke, parentCTM, boundaryInCTM, parentClip) case "PathObject": - r.renderPath(ctx, obj.PathObject, pageH, defaultFill, defaultStroke, defaultLW, parentCTM, boundaryInCTM) + r.renderPath(ctx, obj.PathObject, pageH, defaultFill, defaultStroke, defaultLW, parentCTM, boundaryInCTM, parentClip) case "ImageObject": - r.renderImage(ctx, obj.ImageObject, pageH, parentCTM, boundaryInCTM) + r.renderImage(ctx, obj.ImageObject, pageH, parentCTM, boundaryInCTM, parentClip) case "CompositeGraphicUnit", "CompositeObject": - r.renderCompositeGraphicUnit(ctx, obj.CompositeGraphicUnit, pageH, defaultFill, defaultStroke, defaultLW, parentCTM, boundaryInCTM) + r.renderCompositeGraphicUnit(ctx, obj.CompositeGraphicUnit, pageH, defaultFill, defaultStroke, defaultLW, parentCTM, boundaryInCTM, parentClip) } } diff --git a/ofdgo_renderer_path.go b/ofdgo_renderer_path.go index 137f9a3..5e94691 100644 --- a/ofdgo_renderer_path.go +++ b/ofdgo_renderer_path.go @@ -163,8 +163,8 @@ func (s *pathStyle) scale(ctm Matrix) { } // renderPath 渲染路径 -// 入参: ctx 画布上下文, obj 路径对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM, boundaryInCTM 边界是否参与CTM变换 -func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix, boundaryInCTM bool) { +// 入参: ctx 画布上下文, obj 路径对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM, boundaryInCTM 边界是否参与CTM变换, parentClip 父级裁剪路径 +func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix, boundaryInCTM bool, parentClip *canvas.Path) { if obj.Visible != nil && !*obj.Visible { return } @@ -191,7 +191,7 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 if rectPath := r.buildTinyFillRectPath(obj, pageH, ctm, bx, by); rectPath != nil { p = rectPath } - clipPath := r.buildClipPath(obj.Clips, pageH, bx, by, ctm) + clipPath := intersectClipPath(parentClip, r.buildClipPath(obj.Clips, pageH, bx, by, ctm)) shouldFill := false if obj.Fill != nil { shouldFill = *obj.Fill @@ -204,7 +204,7 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 if clipPath != nil { fp = p.Copy() fp.Close() - fp = fp.And(clipPath) + fp = applyClipPath(fp, clipPath) } r.renderPattern(ctx, style.fillPattern, style.fillPatternColor, pageH, fp, ctm, bx, by) } else if shouldFill && style.fillPaint != nil { @@ -214,7 +214,7 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 if clipPath != nil { fp = p.Copy() fp.Close() - fp = fp.And(clipPath) + fp = applyClipPath(fp, clipPath) } ctx.DrawPath(0, 0, fp) } @@ -243,7 +243,7 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 sp = sp.Dash(style.dashOffset, style.dashPattern...) } sp = sp.Stroke(style.lineWidth, style.lineCap, style.lineJoin, canvas.Tolerance) - sp = sp.And(clipPath) + sp = applyClipPath(sp, clipPath) ctx.SetFill(style.strokePaint) ctx.SetStrokeColor(canvas.Transparent) ctx.DrawPath(0, 0, sp) @@ -304,7 +304,7 @@ func (r *Renderer) renderPattern(ctx *canvas.Context, pattern *Pattern, defaultC for iy := startY; iy <= endY; iy++ { tileCTM := patternCTM.Multiply(TranslationMatrix(float64(ix)*xStep, float64(iy)*yStep)) for _, obj := range pattern.CellContent.Objects { - r.renderObject(ctx, obj, pageH, defaultColor, defaultColor, 0, &tileCTM, false) + r.renderObject(ctx, obj, pageH, defaultColor, defaultColor, 0, &tileCTM, false, clip) } } } @@ -411,7 +411,7 @@ func (r *Renderer) buildClipPath(clips *Clips, pageH float64, bx, by float64, ob var clipPath *canvas.Path for _, area := range clip.Area { areaCTM := NewMatrix(area.CTM) - if clips.TransFlag { + if clips.TransFlag == nil || *clips.TransFlag { areaCTM = objectCTM.Multiply(areaCTM) } for _, pathObj := range area.Path { @@ -422,7 +422,7 @@ func (r *Renderer) buildClipPath(clips *Clips, pageH float64, bx, by float64, ob if clipPath == nil { clipPath = cp } else { - clipPath = clipPath.Or(cp) + clipPath = unionClipPath(clipPath, cp) } } } @@ -430,13 +430,123 @@ func (r *Renderer) buildClipPath(clips *Clips, pageH float64, bx, by float64, ob if p == nil { p = clipPath } else { - p = p.And(clipPath) + p = intersectClipPath(p, clipPath) } } } return p } +// intersectClipPath 求裁剪路径交集 +// 入参: parent 父级裁剪路径, current 当前裁剪路径 +// 返回: *canvas.Path 相交后的裁剪路径 +func intersectClipPath(parent, current *canvas.Path) *canvas.Path { + if parent == nil { + return current + } + if current == nil { + return parent + } + parentRect, parentOK := rectangularPath(parent) + currentRect, currentOK := rectangularPath(current) + if parentOK && currentOK { + return parentRect.And(currentRect).ToPath() + } + if parent.Empty() || current.Empty() { + return &canvas.Path{} + } + if parentOK && parentRect.Contains(current.FastBounds()) { + return current + } + if currentOK && currentRect.Contains(parent.FastBounds()) { + return parent + } + return parent.And(current) +} + +// unionClipPath 合并裁剪区域 +// 入参: left 左侧裁剪路径, right 右侧裁剪路径 +// 返回: *canvas.Path 合并后的裁剪路径 +func unionClipPath(left, right *canvas.Path) *canvas.Path { + leftRect, leftOK := rectangularPath(left) + rightRect, rightOK := rectangularPath(right) + if leftOK && rightOK { + bounds := leftRect.Add(rightRect) + area := leftRect.Area() + rightRect.Area() - leftRect.And(rightRect).Area() + if canvas.Equal(area, bounds.Area()) { + return bounds.ToPath() + } + } + return left.Or(right) +} + +// applyClipPath 应用裁剪路径 +// 入参: path 绘制路径, clip 裁剪路径 +// 返回: *canvas.Path 裁剪后的绘制路径 +func applyClipPath(path, clip *canvas.Path) *canvas.Path { + if path == nil || clip == nil { + return path + } + if path.Empty() || clip.Empty() { + return &canvas.Path{} + } + if rect, ok := rectangularPath(clip); ok { + bounds := path.FastBounds() + if rect.Contains(bounds) { + return path + } + if !rect.Overlaps(bounds) { + return &canvas.Path{} + } + if pathRect, ok := rectangularPath(path); ok { + return pathRect.And(rect).ToPath() + } + return path.Flatten(canvas.Tolerance).Clip(rect.X0, rect.Y0, rect.X1, rect.Y1) + } + return path.And(clip) +} + +// rectangularPath 获取矩形路径区域 +// 入参: path 路径对象 +// 返回: canvas.Rect 矩形区域, bool 是否为矩形 +func rectangularPath(path *canvas.Path) (canvas.Rect, bool) { + if path == nil || path.HasSubpaths() || !path.Closed() { + return canvas.Rect{}, false + } + data := path.Data() + for i := 0; i < len(data); i += 4 { + if i+4 > len(data) || (data[i] != canvas.MoveToCmd && data[i] != canvas.LineToCmd && data[i] != canvas.CloseCmd) { + return canvas.Rect{}, false + } + } + points := path.Coords() + if len(points) != 5 || !points[0].Equals(points[4]) { + return canvas.Rect{}, false + } + rect := path.FastBounds() + corners := 0 + for _, point := range points[:4] { + corner := 0 + switch { + case canvas.Equal(point.X, rect.X0) && canvas.Equal(point.Y, rect.Y0): + corner = 1 + case canvas.Equal(point.X, rect.X1) && canvas.Equal(point.Y, rect.Y0): + corner = 2 + case canvas.Equal(point.X, rect.X1) && canvas.Equal(point.Y, rect.Y1): + corner = 4 + case canvas.Equal(point.X, rect.X0) && canvas.Equal(point.Y, rect.Y1): + corner = 8 + default: + return canvas.Rect{}, false + } + if corners&corner != 0 { + return canvas.Rect{}, false + } + corners |= corner + } + return rect, corners == 15 +} + // buildTinyFillRectPath 构建微小填充矩形路径 // 入参: obj 路径对象, pageH 页面高度, ctm 变换矩阵, bx 边界X坐标, by 边界Y坐标 // 返回: *canvas.Path 路径对象 diff --git a/ofdgo_renderer_text.go b/ofdgo_renderer_text.go index 700a083..d04bf46 100644 --- a/ofdgo_renderer_text.go +++ b/ofdgo_renderer_text.go @@ -24,8 +24,8 @@ import ( const ptPerMM = 72.0 / 25.4 // renderText 渲染文本 -// 入参: ctx 画布上下文, obj 文本对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, parentCTM 父级CTM, boundaryInCTM 边界是否参与父级CTM -func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64, defaultFill, defaultStroke color.Color, parentCTM *Matrix, boundaryInCTM bool) { +// 入参: ctx 画布上下文, obj 文本对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, parentCTM 父级CTM, boundaryInCTM 边界是否参与父级CTM, parentClip 父级裁剪路径 +func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64, defaultFill, defaultStroke color.Color, parentCTM *Matrix, boundaryInCTM bool, parentClip *canvas.Path) { if obj.Visible != nil && !*obj.Visible { return } @@ -41,6 +41,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 if parentCTM != nil { ctm = parentCTM.Multiply(ctm) } + clipPath := intersectClipPath(parentClip, r.buildClipPath(obj.Clips, pageH, bx, by, ctm)) var dp *DrawParam if obj.DrawParam != "" { dp = r.getDrawParam(obj.DrawParam, nil) @@ -115,6 +116,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 } ff := r.loadFont(fontID) if ff == nil { + ctx.Pop() return } face := ff.Face(sizePt, fillPaint, fontStyle, canvas.FontNormal) @@ -134,7 +136,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) + drawAsPath := embeddedFont || textCodePositioned(tc, xs, ys) || clipPath != nil cx, cy := 0.0, 0.0 if len(xs) > 0 { cx = xs[0] @@ -185,6 +187,32 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 advanceLimit := textGlyphAdvanceLimit(dxs, dys, xs, i, len(glyphs), cx) if glyphFillPaint != nil { ctx.SetFill(glyphFillPaint) + if clipPath != nil { + scaleX := hScale + if advanceLimit > 0 && glyphWidth*scaleX > advanceLimit { + scaleX = advanceLimit / glyphWidth + } + textWidth = glyphWidth * scaleX + textTransform := canvas.Identity.Translate(canvasX, canvasY) + if useTextMatrix { + textTransform = textTransform.Mul(textMatrix(ctm)) + } + glyphPath = applyClipPath(glyphPath.Copy().Transform(textTransform.Scale(scaleX, 1)), clipPath) + ctx.DrawPath(0, 0, glyphPath) + if hasUnderline { + uw := sizeMM * 0.05 + off := sizeMM * 0.1 + underline := &canvas.Path{} + underline.MoveTo(0, -off) + 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) + } + continue + } drawGlyph := func(x, y float64) { if drawAsGlyphPath { scaleX := hScale