Skip to content

Commit

Permalink
Fix view transformed strokes for PDF, TeX, and HTMLCanvas, see #74
Browse files Browse the repository at this point in the history
  • Loading branch information
tdewolff committed Jun 17, 2021
1 parent e36919c commit 89de3b3
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 31 deletions.
Binary file modified examples/html-canvas/lib.wasm
Binary file not shown.
1 change: 1 addition & 0 deletions path_stroke.go
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@ func (p *Path) Offset(w float64, fillRule FillRule) *Path {

// Stroke converts a path into a stroke of width w and returns a new path. It uses cr to cap the start and end of the path, and jr to join all path elements. If the path closes itself, it will use a join between the start and end instead of capping them. The tolerance is the maximum deviation from the original path when flattening Béziers and optimizing the stroke.
func (p *Path) Stroke(w float64, cr Capper, jr Joiner) *Path {
// TODO: start first point at intersection between last and first segment. This allows a rectangle to have a stroke with twice 1xM, 3xL and one z command, just like a rectangle itself.
q := &Path{}
halfWidth := w / 2.0
for _, ps := range p.Split() {
Expand Down
65 changes: 49 additions & 16 deletions renderers/htmlcanvas/htmlcanvas.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
package htmlcanvas

import (
"fmt"
"image"
"math"
"syscall/js"
Expand All @@ -28,12 +27,14 @@ func New(c js.Value, width, height, dpm float64) *HTMLCanvas {
ctx.Call("clearRect", 0, 0, width*dpm, height*dpm)
ctx.Set("imageSmoothingEnabled", true)
ctx.Set("imageSmoothingQuality", "high")
style := canvas.DefaultStyle
style.StrokeWidth = 0
return &HTMLCanvas{
ctx: ctx,
width: width * dpm,
height: height * dpm,
dpm: dpm,
style: canvas.DefaultStyle,
style: style,
}
}

Expand All @@ -42,15 +43,7 @@ func (r *HTMLCanvas) Size() (float64, float64) {
return r.width / r.dpm, r.height / r.dpm
}

// RenderPath renders a path to the canvas using a style and a transformation matrix.
func (r *HTMLCanvas) RenderPath(path *canvas.Path, style canvas.Style, m canvas.Matrix) {
fmt.Println(style.Dashes, r.style.Dashes, style.HasStroke())
if path.Empty() {
return
}
path = path.Transform(m)
path = path.ReplaceArcs()

func (r *HTMLCanvas) writePath(path *canvas.Path) {
r.ctx.Call("beginPath")
for _, seg := range path.Segments() {
end := seg.End
Expand All @@ -71,16 +64,40 @@ func (r *HTMLCanvas) RenderPath(path *canvas.Path, style canvas.Style, m canvas.
r.ctx.Call("closePath")
}
}
}

// RenderPath renders a path to the canvas using a style and a transformation matrix.
func (r *HTMLCanvas) RenderPath(path *canvas.Path, style canvas.Style, m canvas.Matrix) {
if path.Empty() {
return
}

strokeUnsupported := false
if m.IsSimilarity() {
scale := math.Sqrt(m.Det())
style.StrokeWidth *= scale
style.DashOffset *= scale
dashes := make([]float64, len(style.Dashes))
for i := range style.Dashes {
dashes[i] = style.Dashes[i] * scale
}
style.Dashes = dashes
} else {
strokeUnsupported = true
}

if style.HasFill() || style.HasStroke() && !strokeUnsupported {
r.writePath(path.Transform(m).ReplaceArcs())
}

if style.HasFill() {
if style.FillColor != r.style.FillColor {
r.ctx.Set("fillStyle", canvas.CSSColor(style.FillColor).String())
r.style.FillColor = style.FillColor
}
r.ctx.Call("fill")
r.style.FillColor = style.FillColor
r.style.FillRule = style.FillRule
}
if style.HasStroke() {
if style.HasStroke() && !strokeUnsupported {
if style.StrokeCapper != r.style.StrokeCapper {
if _, ok := style.StrokeCapper.(canvas.RoundCapper); ok {
r.ctx.Set("lineCap", "round")
Expand All @@ -91,6 +108,7 @@ func (r *HTMLCanvas) RenderPath(path *canvas.Path, style canvas.Style, m canvas.
} else {
panic("HTML Canvas: line cap not support")
}
r.style.StrokeCapper = style.StrokeCapper
}

if style.StrokeJoiner != r.style.StrokeJoiner {
Expand All @@ -104,6 +122,7 @@ func (r *HTMLCanvas) RenderPath(path *canvas.Path, style canvas.Style, m canvas.
} else {
panic("HTML Canvas: line join not support")
}
r.style.StrokeJoiner = style.StrokeJoiner
}

dashesEqual := len(style.Dashes) == len(r.style.Dashes)
Expand All @@ -123,21 +142,35 @@ func (r *HTMLCanvas) RenderPath(path *canvas.Path, style canvas.Style, m canvas.
}
jsDashes := js.Global().Get("Array").New(dashes...)
r.ctx.Call("setLineDash", jsDashes)
fmt.Println("write")
r.style.Dashes = style.Dashes
}

if style.DashOffset != r.style.DashOffset {
r.ctx.Set("lineDashOffset", style.DashOffset*r.dpm)
r.style.DashOffset = style.DashOffset
}

if style.StrokeWidth != r.style.StrokeWidth {
r.ctx.Set("lineWidth", style.StrokeWidth*r.dpm)
r.style.StrokeWidth = style.StrokeWidth
}
if style.StrokeColor != r.style.StrokeColor {
r.ctx.Set("strokeStyle", canvas.CSSColor(style.StrokeColor).String())
r.style.StrokeColor = style.StrokeColor
}
r.ctx.Call("stroke")
r.style = style
} else if style.HasStroke() {
// stroke settings unsupported by HTML Canvas, draw stroke explicitly
if style.IsDashed() {
path = path.Dash(style.DashOffset, style.Dashes...)
}
path = path.Stroke(style.StrokeWidth, style.StrokeCapper, style.StrokeJoiner)
r.writePath(path.Transform(m).ReplaceArcs())
if style.StrokeColor != r.style.FillColor {
r.ctx.Set("fillStyle", canvas.CSSColor(style.StrokeColor).String())
r.style.FillColor = style.StrokeColor
}
r.ctx.Call("fill")
}
}

Expand Down
3 changes: 1 addition & 2 deletions renderers/pdf/pdf.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,11 +211,10 @@ func (r *PDF) RenderPath(path *canvas.Path, style canvas.Style, m canvas.Matrix)
path = path.Dash(style.DashOffset, style.Dashes...)
}
path = path.Stroke(style.StrokeWidth, style.StrokeCapper, style.StrokeJoiner)
path = path.Transform(m)

r.w.SetFillColor(style.StrokeColor)
r.w.Write([]byte(" "))
r.w.Write([]byte(path.ToPDF()))
r.w.Write([]byte(path.Transform(m).ToPDF()))
r.w.Write([]byte(" f"))
if style.FillRule == canvas.EvenOdd {
r.w.Write([]byte("*"))
Expand Down
42 changes: 29 additions & 13 deletions renderers/tex/tex.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ type TeX struct {
// New returns a TeX/PGF renderer.
func New(w io.Writer, width, height float64) *TeX {
fmt.Fprintf(w, "\\begin{pgfpicture}")
style := canvas.DefaultStyle
style.StrokeWidth = 0.0
return &TeX{
w: w,
width: width,
height: height,
style: canvas.DefaultStyle,
style: style,
colors: map[color.RGBA]string{},
}
}
Expand Down Expand Up @@ -101,9 +103,6 @@ func (r *TeX) RenderPath(path *canvas.Path, style canvas.Style, m canvas.Matrix)
return
}

stroke := path
path = path.Transform(m)

strokeUnsupported := false
if m.IsSimilarity() {
scale := math.Sqrt(m.Det())
Expand All @@ -117,14 +116,21 @@ func (r *TeX) RenderPath(path *canvas.Path, style canvas.Style, m canvas.Matrix)
} else {
strokeUnsupported = true
}
r.writePath(path)

if style.HasFill() || style.HasStroke() && !strokeUnsupported {
r.writePath(path.Transform(m))
}

if style.HasFill() {
if style.FillColor.R != r.style.FillColor.R || style.FillColor.G != r.style.FillColor.G || style.FillColor.B != r.style.FillColor.B {
fmt.Fprintf(r.w, "\n\\pgfsetfillcolor{%v}", r.getColor(style.FillColor))
r.style.FillColor.R = style.FillColor.R
r.style.FillColor.G = style.FillColor.G
r.style.FillColor.B = style.FillColor.B
}
if style.FillColor.A != r.style.FillColor.A {
fmt.Fprintf(r.w, "\n\\pgfsetfillopacity{%v}", dec(float64(style.FillColor.A)/255.0))
r.style.FillColor.A = style.FillColor.A
}
}

Expand All @@ -139,6 +145,7 @@ func (r *TeX) RenderPath(path *canvas.Path, style canvas.Style, m canvas.Matrix)
} else {
panic("TeX: line cap not support")
}
r.style.StrokeCapper = style.StrokeCapper
}

if style.StrokeJoiner != r.style.StrokeJoiner {
Expand All @@ -152,6 +159,7 @@ func (r *TeX) RenderPath(path *canvas.Path, style canvas.Style, m canvas.Matrix)
} else {
panic("TeX: line join not support")
}
r.style.StrokeJoiner = style.StrokeJoiner
}

if !float64sEqual(style.Dashes, r.style.Dashes) || style.DashOffset != r.style.DashOffset {
Expand All @@ -164,17 +172,24 @@ func (r *TeX) RenderPath(path *canvas.Path, style canvas.Style, m canvas.Matrix)
} else {
fmt.Fprintf(r.w, "\n\\pgfsetdash{}{0}")
}
r.style.DashOffset = style.DashOffset
r.style.Dashes = style.Dashes
}

if style.StrokeWidth != r.style.StrokeWidth {
fmt.Fprintf(r.w, "\n\\pgfsetlinewidth{%vmm}", dec(style.StrokeWidth))
r.style.StrokeWidth = style.StrokeWidth
}

if style.StrokeColor.R != r.style.StrokeColor.R || style.StrokeColor.G != r.style.StrokeColor.G || style.StrokeColor.B != r.style.StrokeColor.B {
fmt.Fprintf(r.w, "\n\\pgfsetstrokecolor{%v}", r.getColor(style.StrokeColor))
r.style.StrokeColor.R = style.StrokeColor.R
r.style.StrokeColor.G = style.StrokeColor.G
r.style.StrokeColor.B = style.StrokeColor.B
}
if style.StrokeColor.A != r.style.StrokeColor.A {
fmt.Fprintf(r.w, "\n\\pgfsetstrokeopacity{%v}", dec(float64(style.StrokeColor.A)/255.0))
r.style.StrokeColor.A = style.StrokeColor.A
}
}
if style.HasFill() && style.HasStroke() && !strokeUnsupported {
Expand All @@ -188,21 +203,22 @@ func (r *TeX) RenderPath(path *canvas.Path, style canvas.Style, m canvas.Matrix)
if style.HasStroke() && strokeUnsupported {
// stroke settings unsupported by TeX, draw stroke explicitly
if style.IsDashed() {
stroke = stroke.Dash(style.DashOffset, style.Dashes...)
path = path.Dash(style.DashOffset, style.Dashes...)
}
stroke = stroke.Stroke(style.StrokeWidth, style.StrokeCapper, style.StrokeJoiner)
stroke = stroke.Transform(m)
r.writePath(stroke)
if style.StrokeColor.R != r.style.StrokeColor.R || style.StrokeColor.G != r.style.StrokeColor.G || style.StrokeColor.B != r.style.StrokeColor.B {
path = path.Stroke(style.StrokeWidth, style.StrokeCapper, style.StrokeJoiner)
r.writePath(path.Transform(m))
if style.StrokeColor.R != r.style.FillColor.R || style.StrokeColor.G != r.style.FillColor.G || style.StrokeColor.B != r.style.FillColor.B {
fmt.Fprintf(r.w, "\n\\pgfsetfillcolor{%v}", r.getColor(style.StrokeColor))
r.style.FillColor.R = style.StrokeColor.R
r.style.FillColor.G = style.StrokeColor.G
r.style.FillColor.B = style.StrokeColor.B
}
if style.StrokeColor.A != r.style.StrokeColor.A {
if style.StrokeColor.A != r.style.FillColor.A {
fmt.Fprintf(r.w, "\n\\pgfsetfillopacity{%v}", dec(float64(style.StrokeColor.A)/255.0))
r.style.FillColor.A = style.StrokeColor.A
}
fmt.Fprintf(r.w, "\n\\pgfusepath{fill}")

}
r.style = style
}

// RenderText renders a text object to the canvas using a transformation matrix.
Expand Down

0 comments on commit 89de3b3

Please sign in to comment.