mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-08-30 12:12:40 +08:00
+2
-1
@@ -58,7 +58,7 @@ type GraphicObject struct {
|
|||||||
|
|
||||||
// Clips 裁剪区域集合
|
// Clips 裁剪区域集合
|
||||||
type Clips struct {
|
type Clips struct {
|
||||||
TransFlag bool `xml:"TransFlag,attr"`
|
TransFlag *bool `xml:"TransFlag,attr"`
|
||||||
Clip []Clip `xml:"Clip"`
|
Clip []Clip `xml:"Clip"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,6 +210,7 @@ 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"`
|
||||||
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"`
|
||||||
|
|||||||
+113
-5
@@ -22,11 +22,13 @@ import (
|
|||||||
|
|
||||||
"github.com/tdewolff/canvas"
|
"github.com/tdewolff/canvas"
|
||||||
canvasimage "github.com/tdewolff/canvas/image"
|
canvasimage "github.com/tdewolff/canvas/image"
|
||||||
|
"github.com/tdewolff/canvas/renderers/rasterizer"
|
||||||
|
"golang.org/x/image/draw"
|
||||||
)
|
)
|
||||||
|
|
||||||
// renderImage 渲染图片
|
// renderImage 渲染图片
|
||||||
// 入参: ctx 画布上下文, obj 图片对象, pageH 页面高度, parentCTM 父级CTM, boundaryInCTM 边界是否参与父级CTM
|
// 入参: ctx 画布上下文, obj 图片对象, pageH 页面高度, parentCTM 父级CTM, boundaryInCTM 边界是否参与父级CTM, parentClip 父级裁剪路径
|
||||||
func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float64, parentCTM *Matrix, boundaryInCTM bool) {
|
func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float64, parentCTM *Matrix, boundaryInCTM bool, parentClip *canvas.Path) {
|
||||||
if obj.Visible != nil && !*obj.Visible {
|
if obj.Visible != nil && !*obj.Visible {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -39,18 +41,25 @@ func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
box, _ := ParseBox(obj.Boundary)
|
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()
|
imgBounds := img.Bounds()
|
||||||
imgW, imgH := float64(imgBounds.Dx()), float64(imgBounds.Dy())
|
imgW, imgH := float64(imgBounds.Dx()), float64(imgBounds.Dy())
|
||||||
if imgW <= 0 || imgH <= 0 {
|
if imgW <= 0 || imgH <= 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
img = imageWithAlpha(img, obj.Alpha)
|
|
||||||
pad := 0
|
|
||||||
img, pad = imageWithTransparentEdge(img)
|
|
||||||
ctm := NewMatrix(obj.CTM)
|
ctm := NewMatrix(obj.CTM)
|
||||||
if obj.CTM == "" {
|
if obj.CTM == "" {
|
||||||
ctm = Matrix{a: box.W, d: box.H}
|
ctm = Matrix{a: box.W, d: box.H}
|
||||||
}
|
}
|
||||||
|
objectCTM := ctm
|
||||||
|
if parentCTM != nil {
|
||||||
|
objectCTM = parentCTM.Multiply(ctm)
|
||||||
|
}
|
||||||
var m canvas.Matrix
|
var m canvas.Matrix
|
||||||
if boundaryInCTM && parentCTM != nil {
|
if boundaryInCTM && parentCTM != nil {
|
||||||
x0 := box.X + ctm.c + ctm.e
|
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},
|
{-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 {
|
if pad > 0 {
|
||||||
p := float64(pad)
|
p := float64(pad)
|
||||||
m[0][2] -= m[0][0]*p + m[0][1]*p
|
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))
|
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 解码图片资源
|
// decodeImageResource 解码图片资源
|
||||||
// 入参: resPath 图片资源路径
|
// 入参: resPath 图片资源路径
|
||||||
// 返回: image.Image 图片对象, error 错误信息
|
// 返回: image.Image 图片对象, error 错误信息
|
||||||
|
|||||||
+22
-20
@@ -30,7 +30,7 @@ func (r *Renderer) renderAnnotations(ctx *canvas.Context, pageID string, pageH f
|
|||||||
box, _ := ParseBox(annot.Appearance.Boundary)
|
box, _ := ParseBox(annot.Appearance.Boundary)
|
||||||
ctm := Matrix{a: 1, d: 1, e: box.X, f: box.Y}
|
ctm := Matrix{a: 1, d: 1, e: box.X, f: box.Y}
|
||||||
for _, obj := range annot.Appearance.Objects {
|
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)
|
defaultFill, defaultStroke, defaultLW = r.drawParamDefaults(layer.DrawParam, defaultFill, defaultStroke, defaultLW)
|
||||||
if len(layer.Objects) > 0 {
|
if len(layer.Objects) > 0 {
|
||||||
for _, obj := range layer.Objects {
|
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
|
return
|
||||||
}
|
}
|
||||||
for _, textObj := range layer.TextObject {
|
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 {
|
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 {
|
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 {
|
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 渲染复合图元
|
// renderCompositeGraphicUnit 渲染复合图元
|
||||||
// 入参: ctx 画布上下文, cgu 复合图元对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM, boundaryInCTM 边界是否参与CTM变换
|
// 入参: 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) {
|
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 {
|
if cgu.Visible != nil && !*cgu.Visible {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -99,53 +99,55 @@ func (r *Renderer) renderCompositeGraphicUnit(ctx *canvas.Context, cgu Composite
|
|||||||
if parentCTM != nil {
|
if parentCTM != nil {
|
||||||
currentCTM = parentCTM.Multiply(currentCTM)
|
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 cgu.ResourceID != "" {
|
||||||
if ref, ok := r.CompositeGraphicUnits[cgu.ResourceID]; ok {
|
if ref, ok := r.CompositeGraphicUnits[cgu.ResourceID]; ok {
|
||||||
refCopy := *ref
|
refCopy := *ref
|
||||||
refCopy.Alpha = mergeAlpha(refCopy.Alpha, cgu.Alpha)
|
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)
|
defaultFill, defaultStroke, defaultLW = r.drawParamDefaults(cgu.DrawParam, defaultFill, defaultStroke, defaultLW)
|
||||||
if len(cgu.Objects) > 0 {
|
if len(cgu.Objects) > 0 {
|
||||||
for _, obj := range cgu.Objects {
|
for _, obj := range cgu.Objects {
|
||||||
obj = mergeGraphicObjectAlpha(obj, cgu.Alpha)
|
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()
|
ctx.Pop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, imgObj := range cgu.ImageObject {
|
for _, imgObj := range cgu.ImageObject {
|
||||||
imgObj.Alpha = mergeAlpha(imgObj.Alpha, cgu.Alpha)
|
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 {
|
for _, pathObj := range cgu.PathObject {
|
||||||
pathObj.Alpha = mergeAlpha(pathObj.Alpha, cgu.Alpha)
|
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 {
|
for _, textObj := range cgu.TextObject {
|
||||||
textObj.Alpha = mergeAlpha(textObj.Alpha, cgu.Alpha)
|
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 {
|
for _, subCgu := range cgu.CompositeGraphicUnit {
|
||||||
subCgu.Alpha = mergeAlpha(subCgu.Alpha, cgu.Alpha)
|
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()
|
ctx.Pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderObject 渲染图形对象
|
// renderObject 渲染图形对象
|
||||||
// 入参: ctx 画布上下文, obj 图形对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM, boundaryInCTM 边界是否参与CTM变换
|
// 入参: 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) {
|
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 {
|
switch obj.Type {
|
||||||
case "TextObject":
|
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":
|
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":
|
case "ImageObject":
|
||||||
r.renderImage(ctx, obj.ImageObject, pageH, parentCTM, boundaryInCTM)
|
r.renderImage(ctx, obj.ImageObject, pageH, parentCTM, boundaryInCTM, parentClip)
|
||||||
case "CompositeGraphicUnit", "CompositeObject":
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+120
-10
@@ -163,8 +163,8 @@ func (s *pathStyle) scale(ctm Matrix) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// renderPath 渲染路径
|
// renderPath 渲染路径
|
||||||
// 入参: ctx 画布上下文, obj 路径对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM, boundaryInCTM 边界是否参与CTM变换
|
// 入参: 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) {
|
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 {
|
if obj.Visible != nil && !*obj.Visible {
|
||||||
return
|
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 {
|
if rectPath := r.buildTinyFillRectPath(obj, pageH, ctm, bx, by); rectPath != nil {
|
||||||
p = rectPath
|
p = rectPath
|
||||||
}
|
}
|
||||||
clipPath := r.buildClipPath(obj.Clips, pageH, bx, by, ctm)
|
clipPath := intersectClipPath(parentClip, r.buildClipPath(obj.Clips, pageH, bx, by, ctm))
|
||||||
shouldFill := false
|
shouldFill := false
|
||||||
if obj.Fill != nil {
|
if obj.Fill != nil {
|
||||||
shouldFill = *obj.Fill
|
shouldFill = *obj.Fill
|
||||||
@@ -204,7 +204,7 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64
|
|||||||
if clipPath != nil {
|
if clipPath != nil {
|
||||||
fp = p.Copy()
|
fp = p.Copy()
|
||||||
fp.Close()
|
fp.Close()
|
||||||
fp = fp.And(clipPath)
|
fp = applyClipPath(fp, clipPath)
|
||||||
}
|
}
|
||||||
r.renderPattern(ctx, style.fillPattern, style.fillPatternColor, pageH, fp, ctm, bx, by)
|
r.renderPattern(ctx, style.fillPattern, style.fillPatternColor, pageH, fp, ctm, bx, by)
|
||||||
} else if shouldFill && style.fillPaint != nil {
|
} else if shouldFill && style.fillPaint != nil {
|
||||||
@@ -214,7 +214,7 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64
|
|||||||
if clipPath != nil {
|
if clipPath != nil {
|
||||||
fp = p.Copy()
|
fp = p.Copy()
|
||||||
fp.Close()
|
fp.Close()
|
||||||
fp = fp.And(clipPath)
|
fp = applyClipPath(fp, clipPath)
|
||||||
}
|
}
|
||||||
ctx.DrawPath(0, 0, fp)
|
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.Dash(style.dashOffset, style.dashPattern...)
|
||||||
}
|
}
|
||||||
sp = sp.Stroke(style.lineWidth, style.lineCap, style.lineJoin, canvas.Tolerance)
|
sp = sp.Stroke(style.lineWidth, style.lineCap, style.lineJoin, canvas.Tolerance)
|
||||||
sp = sp.And(clipPath)
|
sp = applyClipPath(sp, clipPath)
|
||||||
ctx.SetFill(style.strokePaint)
|
ctx.SetFill(style.strokePaint)
|
||||||
ctx.SetStrokeColor(canvas.Transparent)
|
ctx.SetStrokeColor(canvas.Transparent)
|
||||||
ctx.DrawPath(0, 0, sp)
|
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++ {
|
for iy := startY; iy <= endY; iy++ {
|
||||||
tileCTM := patternCTM.Multiply(TranslationMatrix(float64(ix)*xStep, float64(iy)*yStep))
|
tileCTM := patternCTM.Multiply(TranslationMatrix(float64(ix)*xStep, float64(iy)*yStep))
|
||||||
for _, obj := range pattern.CellContent.Objects {
|
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
|
var clipPath *canvas.Path
|
||||||
for _, area := range clip.Area {
|
for _, area := range clip.Area {
|
||||||
areaCTM := NewMatrix(area.CTM)
|
areaCTM := NewMatrix(area.CTM)
|
||||||
if clips.TransFlag {
|
if clips.TransFlag == nil || *clips.TransFlag {
|
||||||
areaCTM = objectCTM.Multiply(areaCTM)
|
areaCTM = objectCTM.Multiply(areaCTM)
|
||||||
}
|
}
|
||||||
for _, pathObj := range area.Path {
|
for _, pathObj := range area.Path {
|
||||||
@@ -422,7 +422,7 @@ func (r *Renderer) buildClipPath(clips *Clips, pageH float64, bx, by float64, ob
|
|||||||
if clipPath == nil {
|
if clipPath == nil {
|
||||||
clipPath = cp
|
clipPath = cp
|
||||||
} else {
|
} 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 {
|
if p == nil {
|
||||||
p = clipPath
|
p = clipPath
|
||||||
} else {
|
} else {
|
||||||
p = p.And(clipPath)
|
p = intersectClipPath(p, clipPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return p
|
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 构建微小填充矩形路径
|
// buildTinyFillRectPath 构建微小填充矩形路径
|
||||||
// 入参: obj 路径对象, pageH 页面高度, ctm 变换矩阵, bx 边界X坐标, by 边界Y坐标
|
// 入参: obj 路径对象, pageH 页面高度, ctm 变换矩阵, bx 边界X坐标, by 边界Y坐标
|
||||||
// 返回: *canvas.Path 路径对象
|
// 返回: *canvas.Path 路径对象
|
||||||
|
|||||||
+31
-3
@@ -24,8 +24,8 @@ import (
|
|||||||
const ptPerMM = 72.0 / 25.4
|
const ptPerMM = 72.0 / 25.4
|
||||||
|
|
||||||
// renderText 渲染文本
|
// renderText 渲染文本
|
||||||
// 入参: ctx 画布上下文, obj 文本对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, parentCTM 父级CTM, boundaryInCTM 边界是否参与父级CTM
|
// 入参: 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) {
|
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 {
|
if obj.Visible != nil && !*obj.Visible {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -41,6 +41,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
|
|||||||
if parentCTM != nil {
|
if parentCTM != nil {
|
||||||
ctm = parentCTM.Multiply(ctm)
|
ctm = parentCTM.Multiply(ctm)
|
||||||
}
|
}
|
||||||
|
clipPath := intersectClipPath(parentClip, r.buildClipPath(obj.Clips, pageH, bx, by, ctm))
|
||||||
var dp *DrawParam
|
var dp *DrawParam
|
||||||
if obj.DrawParam != "" {
|
if obj.DrawParam != "" {
|
||||||
dp = r.getDrawParam(obj.DrawParam, nil)
|
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)
|
ff := r.loadFont(fontID)
|
||||||
if ff == nil {
|
if ff == nil {
|
||||||
|
ctx.Pop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
face := ff.Face(sizePt, fillPaint, fontStyle, canvas.FontNormal)
|
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)
|
dxs, dys := parseFloats(tc.DeltaX), parseFloats(tc.DeltaY)
|
||||||
xs, ys := parseFloats(tc.X), parseFloats(tc.Y)
|
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
|
cx, cy := 0.0, 0.0
|
||||||
if len(xs) > 0 {
|
if len(xs) > 0 {
|
||||||
cx = 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)
|
advanceLimit := textGlyphAdvanceLimit(dxs, dys, xs, i, len(glyphs), cx)
|
||||||
if glyphFillPaint != nil {
|
if glyphFillPaint != nil {
|
||||||
ctx.SetFill(glyphFillPaint)
|
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) {
|
drawGlyph := func(x, y float64) {
|
||||||
if drawAsGlyphPath {
|
if drawAsGlyphPath {
|
||||||
scaleX := hScale
|
scaleX := hScale
|
||||||
|
|||||||
Reference in New Issue
Block a user