~eliasnaur/gio-patches

4 2

Re: [PATCH gio v3 2/2] gpu,op/clip: split xyz-ops

Details
Message ID
<9zr6x2Evsp0l6kXc5bUQILPPZmPNazFASnlrbd4uA4lSbihQKEnCIl4ivIFthL26sg6K17LEakieisjUiUPqOFfJG8IO_p5qqKbGLUChZOc=@sbinet.org>
DKIM signature
missing
Download raw message
hi,

I've kept the original implementation of dashed stroked paths as a separate commit and then the split into various xyzOp structures.

I am not completely happy with this v3 though:
- it's way too easy to forget a clip.xyzOp
- it's a bit more verbose
- I've kept the clip.Path.Close (but made a mental note of the need to get rid of it for a v4)

ie: it's more of a WIP, plea for comments than anything else.

cheers,
-s

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐

On Tuesday, November 24th, 2020 at 6:10 PM, Sebastien Binet <s@sbinet.org> wrote:

> Signed-off-by: Sebastien Binet s@sbinet.org
>
> font/opentype/opentype.go | 5 +-
>
> gpu/dash.go | 64 ++--
>
> gpu/gpu.go | 159 ++++++---
>
> gpu/stroke.go | 18 +-
>
> internal/opconst/ops.go | 14 +-
>
> internal/ops/reader.go | 6 -
>
> internal/rendertest/clip_test.go | 327 +++++++++++-------
>
> .../refs/TestStrokedPathFlatMiter.png | Bin 2309 -> 2258 bytes
>
> .../refs/TestStrokedPathFlatMiterInf.png | Bin 2304 -> 2262 bytes
>
> internal/rendertest/render_test.go | 10 +-
>
> op/clip/clip.go | 75 ++--
>
> op/clip/shapes.go | 15 +-
>
> op/clip/stroke.go | 107 +++++-
>
> widget/material/loader.go | 8 +-
>
> 14 files changed, 522 insertions(+), 286 deletions(-)
>
> diff --git a/font/opentype/opentype.go b/font/opentype/opentype.go
>
> index 3e89cef..de96d2b 100644
>
> --- a/font/opentype/opentype.go
>
> +++ b/font/opentype/opentype.go
>
> @@ -323,7 +323,10 @@ func textPath(buf *sfnt.Buffer, ppem fixed.Int26_6, fonts []*opentype, str text.
>
> x += str.Advances[rune]
>
> rune++
>
> }
>
> -   builder.Outline().Add(ops)
>
> -   builder.Close()
> -   builder.Op().Add(ops)
> -   clip.OutlineOp{}.Add(ops)
> -   clip.Op{}.Add(ops)
>
>     return m.Stop()
>
>     }
>
>     diff --git a/gpu/dash.go b/gpu/dash.go
>
>     index 1460228..323298f 100644
>
>     --- a/gpu/dash.go
>
>     +++ b/gpu/dash.go
>
>     @@ -12,29 +12,27 @@ import (
>
>     "gioui.org/f32"
>
>     "gioui.org/internal/ops"
>
> -   "gioui.org/op/clip"
>
>     )
>
>     -func isSolidLine(sty clip.DashStyle) bool {
> -   return sty.Offset == 0 && len(sty.Dashes) == 0
>
>     +func isSolidLine(sty dashOp) bool {
>
> -   return sty.phase == 0 && len(sty.dashes) == 0
>
>     }
>
>     -func (qs strokeQuads) dash(sty clip.DashStyle) strokeQuads {
>
>     +func (qs strokeQuads) dash(sty dashOp) strokeQuads {
>
>     sty = dashCanonical(sty)
>
>     switch {
>
> -   case len(sty.Dashes) == 0:
>
> -   case len(sty.dashes) == 0:
>
>     return qs
>
> -   case len(sty.Dashes) == 1 && sty.Dashes[0] == 0.0:
> -         var o strokeQuads
>
>
> -         return o
>
>
>
> -   case len(sty.dashes) == 1 && sty.dashes[0] == 0.0:
> -         return strokeQuads{}
>
>
>     }
>
> -   if len(sty.Dashes)%2 == 1 {
>
> -   if len(sty.dashes)%2 == 1 {
>
>     // If the dash pattern is of uneven length, dash and space lengths
>
>     // alternate. The following duplicates the pattern so that uneven
>
>     // indices are always spaces.
>
> -         sty.Dashes = append(sty.Dashes, sty.Dashes...)
>
>
>
> -         sty.dashes = append(sty.dashes, sty.dashes...)
>
>
>     }
>
>     var (
>
>     @@ -51,13 +49,13 @@ func (qs strokeQuads) dash(sty clip.DashStyle) strokeQuads {
>
>     t []float64
>
>     length = ps.len()
>
>     )
>
> -         for pos+sty.Dashes[i] < length {
>
>
> -         	pos += sty.Dashes[i]
>
>
>
> -         for pos+sty.dashes[i] < length {
>
>
> -         	pos += sty.dashes[i]
>           	if 0.0 < pos {
>           		t = append(t, float64(pos))
>           	}
>           	i++
>
>
>
> -         	if i == len(sty.Dashes) {
>
>
>
> -         	if i == len(sty.dashes) {
>           		i = 0
>           	}
>           }
>
>
>
> @@ -88,13 +86,13 @@ func (qs strokeQuads) dash(sty clip.DashStyle) strokeQuads {
>
> return out
>
> }
>
> -func dashCanonical(sty clip.DashStyle) clip.DashStyle {
>
> +func dashCanonical(sty dashOp) dashOp {
>
> var (
>
> o = sty
>
> -         ds = o.Dashes
>
>
>
> -         ds = o.dashes
>
>
>     )
>
> -   if len(sty.Dashes) == 0 {
>
> -   if len(sty.dashes) == 0 {
>
>     return sty
>
>     }
>
>     @@ -110,12 +108,12 @@ func dashCanonical(sty clip.DashStyle) clip.DashStyle {
>
>     // Remove first zero, collapse with second and last.
>
>     if f32Eq(ds[0], 0.0) {
>
>     if len(ds) < 3 {
>
> -         	return clip.DashStyle{
>
>
> -         		Offset: 0.0,
>
>
> -         		Dashes: []float32{0.0},
>
>
>
> -         	return dashOp{
>
>
> -         		phase: 0.0,
>
>
> -         		dashes: []float32{0.0},
>           	}
>           }
>
>
>
> -         o.Offset -= ds[1]
>
>
>
> -         o.phase -= ds[1]
>           ds[len(ds)-1] += ds[1]
>           ds = ds[2:]
>
>
>     }
>
>     @@ -123,9 +121,9 @@ func dashCanonical(sty clip.DashStyle) clip.DashStyle {
>
>     // Remove last zero, collapse with fist and second to last.
>
>     if f32Eq(ds[len(ds)-1], 0.0) {
>
>     if len(ds) < 3 {
>
> -         	return clip.DashStyle{}
>
>
>
> -         	return dashOp{}
>           }
>
>
>
> -         o.Offset += ds[len(ds)-2]
>
>
>
> -         o.phase += ds[len(ds)-2]
>           ds[0] += ds[len(ds)-2]
>           ds = ds[:len(ds)-2]
>
>
>     }
>
>     @@ -133,9 +131,9 @@ func dashCanonical(sty clip.DashStyle) clip.DashStyle {
>
>     // If there are zeros or negatives, don't draw dashes.
>
>     for i := 0; i < len(ds); i++ {
>
>     if ds[i] < 0.0 || f32Eq(ds[i], 0.0) {
>
> -         	return clip.DashStyle{
>
>
> -         		Offset: 0.0,
>
>
> -         		Dashes: []float32{0.0},
>
>
>
> -         	return dashOp{
>
>
> -         		phase: 0.0,
>
>
> -         		dashes: []float32{0.0},
>           	}
>           }
>
>
>     }
>
>     @@ -154,23 +152,23 @@ loop:
>
>     return o
>
>     }
>
>     -func dashStart(sty clip.DashStyle) (int, float32) {
>
>     +func dashStart(sty dashOp) (int, float32) {
>
>     i0 := 0 // i0 is the index into dashes.
>
> -   for sty.Dashes[i0] <= sty.Offset {
> -         sty.Offset -= sty.Dashes[i0]
>
>
>
> -   for sty.dashes[i0] <= sty.phase {
> -         sty.phase -= sty.dashes[i0]
>           i0++
>
>
>
> -         if i0 == len(sty.Dashes) {
>
>
>
> -         if i0 == len(sty.dashes) {
>           	i0 = 0
>           }
>
>
>     }
>
>     // pos0 may be negative if the offset lands halfway into dash.
>
> -   pos0 := -sty.Offset
> -   if sty.Offset < 0.0 {
>
> -   pos0 := -sty.phase
> -   if sty.phase < 0.0 {
>
>     var sum float32
>
> -         for _, d := range sty.Dashes {
>
>
>
> -         for _, d := range sty.dashes {
>           	sum += d
>           }
>
>
>
> -         pos0 = -(sum + sty.Offset) // handle negative offsets
>
>
>
> -         pos0 = -(sum + sty.phase) // handle negative offsets
>
>
>     }
>
>     return i0, pos0
>
>     }
>
>     diff --git a/gpu/gpu.go b/gpu/gpu.go
>
>     index 5f46862..08484d6 100644
>
>     --- a/gpu/gpu.go
>
>     +++ b/gpu/gpu.go
>
>     @@ -109,6 +109,52 @@ type imageOp struct {
>
>     place placement
>
>     }
>
>     +type dashOp struct {
> -   phase float32
> -   dashes []float32
>
>     +}
>
> +func decodeDashOp(data []byte) dashOp {
>
> -   _ = data[5]
> -   if opconst.OpType(data[0]) != opconst.TypeDash {
> -         panic("invalid op")
>
>
> -   }
> -   bo := binary.LittleEndian
> -   return dashOp{
> -         phase:  math.Float32frombits(bo.Uint32(data[1:])),
>
>
> -         dashes: make([]float32, data[5]),
>
>
> -   }
>
>     +}
>
> +func decodeStrokeOp(data []byte) clip.StrokeOp {
>
> -   _ = data[10]
> -   if opconst.OpType(data[0]) != opconst.TypeStroke {
> -         panic("invalid op")
>
>
> -   }
> -   bo := binary.LittleEndian
> -   return clip.StrokeOp{
> -         Width: math.Float32frombits(bo.Uint32(data[1:])),
>
>
> -         Miter: math.Float32frombits(bo.Uint32(data[5:])),
>
>
> -         Cap:   clip.StrokeCap(data[9]),
>
>
> -         Join:  clip.StrokeJoin(data[10]),
>
>
> -   }
>
>     +}
>
> +type quadsOp struct {
>
> -   quads uint32
> -   key ops.Key
> -   aux []byte
>
>     +}
>
> +func decodeQuadsOp(data []byte) uint32 {
>
> -   _ = data[:1+4]
> -   if opconst.OpType(data[0]) != opconst.TypePath {
> -         panic("invalid op")
>
>
> -   }
> -   bo := binary.LittleEndian
> -   return bo.Uint32(data[1:])
>
>     +}
>
> type material struct {
>
> material materialType
>
> opaque bool
>
> @@ -126,8 +172,6 @@ type material struct {
>
> type clipOp struct {
>
> // TODO: Use image.Rectangle?
>
> bounds f32.Rectangle
>
> -   width float32
> -   style clip.StrokeStyle
>
>     }
>
>     // imageOpData is the shadow of paint.ImageOp.
>
>     @@ -160,18 +204,6 @@ func (op *clipOp) decode(data []byte) {
>
>     }
>
>     *op = clipOp{
>
>     bounds: layout.FRect(r),
> -         width:  math.Float32frombits(bo.Uint32(data[17:])),
>
>
> -         style: clip.StrokeStyle{
>
>
> -         	Cap:   clip.StrokeCap(data[21]),
>
>
> -         	Join:  clip.StrokeJoin(data[22]),
>
>
> -         	Miter: math.Float32frombits(bo.Uint32(data[23:])),
>
>
> -         },
>
>
> -   }
> -   op.style.Line.Offset = math.Float32frombits(bo.Uint32(data[27:]))
> -   op.style.Line.Dashes = make([]float32, data[31])
> -   dashes := data[32:]
> -   for i := range op.style.Line.Dashes {
> -         op.style.Line.Dashes[i] = math.Float32frombits(bo.Uint32(dashes[i*4:]))
>
>
>     }
>
>     }
>
>     @@ -792,8 +824,12 @@ func splitTransform(t f32.Affine2D) (srs f32.Affine2D, offset f32.Point) {
>
>     }
>
>     func (d *drawOps) collectOps(r *ops.Reader, state drawState) int {
> -   var aux []byte
> -   var auxKey ops.Key
>
> -   var (
> -         quads   quadsOp
>
>
> -         outline bool
>
>
> -         stroke  clip.StrokeOp
>
>
> -         dashes  dashOp
>
>
> -   )
>
>     loop:
>
>     for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
>
>     switch opconst.OpType(encOp.Data[0]) {
>
>     @@ -802,38 +838,72 @@ loop:
>
>     case opconst.TypeTransform:
>
>     dop := ops.DecodeTransform(encOp.Data)
>
>     state.t = state.t.Mul(dop)
>
> -         case opconst.TypeAux:
>
>
> -         	aux = encOp.Data[opconst.TypeAuxLen:]
>
>
> -         	auxKey = encOp.Key
>
>
>
> -         case opconst.TypeDash:
>
>
> -         	dashes = decodeDashOp(encOp.Data)
>
>
> -         	if len(dashes.dashes) > 0 {
>
>
> -         		encOp, ok = r.Decode()
>
>
> -         		if !ok {
>
>
> -         			break loop
>
>
> -         		}
>
>
> -         		data := encOp.Data[1:]
>
>
> -         		bo := binary.LittleEndian
>
>
> -         		for i := range dashes.dashes {
>
>
> -         			dashes.dashes[i] = math.Float32frombits(bo.Uint32(
>
>
> -         				data[i*4:],
>
>
> -         			))
>
>
> -         		}
>
>
> -         	}
>
>
>
> -         case opconst.TypeOutline:
>
>
> -         	outline = true
>
>
>
> -         case opconst.TypeStroke:
>
>
> -         	stroke = decodeStrokeOp(encOp.Data)
>
>
>
> -         case opconst.TypePath:
>
>
> -         	quads.quads = decodeQuadsOp(encOp.Data)
>
>
> -         	if quads.quads > 0 {
>
>
> -         		encOp, ok = r.Decode()
>
>
> -         		if !ok {
>
>
> -         			break loop
>
>
> -         		}
>
>
> -         		quads.aux = encOp.Data[opconst.TypeAuxLen:]
>
>
> -         		quads.key = encOp.Key
>
>
> -         	}
>
>
> -         case opconst.TypeClip:
>           	var op clipOp
>           	op.decode(encOp.Data)
>           	bounds := op.bounds
>           	trans, off := splitTransform(state.t)
>
>
>
> -         	if len(aux) > 0 {
>
>
>
> -         	if len(quads.aux) > 0 {
>
>           		// There is a clipping path, build the gpu data and update the
>           		// cache key such that it will be equal only if the transform is the
>           		// same also. Use cached data if we have it.
>
>
>
> -         		auxKey = auxKey.SetTransform(trans)
>
>
> -         		if v, ok := d.pathCache.get(auxKey); ok {
>
>
>
> -         		quads.key = quads.key.SetTransform(trans)
>
>
> -         		if v, ok := d.pathCache.get(quads.key); ok {
>           			// Since the GPU data exists in the cache aux will not be used.
>           			// Why is this not used for the offset shapes?
>           			op.bounds = v.bounds
>           		} else {
>
>
>
> -         			aux, op.bounds = d.buildVerts(aux, trans, op.width, op.style)
>
>
>
> -         			quads.aux, op.bounds = d.buildVerts(quads.aux, trans, outline, stroke, dashes)
>           			// add it to the cache, without GPU data, so the transform can be
>           			// reused.
>
>
>
> -         			d.pathCache.put(auxKey, opCacheValue{bounds: op.bounds})
>
>
>
> -         			d.pathCache.put(quads.key, opCacheValue{bounds: op.bounds})
>           		}
>           	} else {
>
>
>
> -         		aux, op.bounds, _ = d.boundsForTransformedRect(bounds, trans)
>
>
> -         		auxKey = encOp.Key
>
>
> -         		auxKey.SetTransform(trans)
>
>
>
> -         		quads.aux, op.bounds, _ = d.boundsForTransformedRect(bounds, trans)
>
>
> -         		quads.key = encOp.Key
>
>
> -         		quads.key.SetTransform(trans)
>           	}
>           	state.clip = state.clip.Intersect(op.bounds.Add(off))
>
>
>
> -         	d.addClipPath(&state, aux, auxKey, op.bounds, off)
>
>
> -         	aux = nil
>
>
> -         	auxKey = ops.Key{}
>
>
>
> -         	d.addClipPath(&state, quads.aux, quads.key, op.bounds, off)
>
>
> -         	quads = quadsOp{}
>
>
> -         	outline = false
>
>
> -         	stroke = clip.StrokeOp{}
>
>
> -         	dashes = dashOp{}
>
>
> -         case opconst.TypeColor:
>           	state.matType = materialColor
>           	state.color = decodeColorOp(encOp.Data)
>
>
>
> @@ -1219,7 +1289,7 @@ func (d *drawOps) writeVertCache(n int) []byte {
>
> }
>
> // transform, split paths as needed, calculate maxY, bounds and create GPU vertices.
>
> -func (d *drawOps) buildVerts(aux []byte, tr f32.Affine2D, width float32, sty clip.StrokeStyle) (verts []byte, bounds f32.Rectangle) {
>
> +func (d *drawOps) buildVerts(aux []byte, tr f32.Affine2D, outline bool, stroke clip.StrokeOp, dashes dashOp) (verts []byte, bounds f32.Rectangle) {
>
> inf := float32(math.Inf(+1))
>
> d.qs.bounds = f32.Rectangle{
>
> Min: f32.Point{X: inf, Y: inf},
>
> @@ -1230,18 +1300,7 @@ func (d *drawOps) buildVerts(aux []byte, tr f32.Affine2D, width float32, sty cli
>
> startLength := len(d.vertCache)
>
> switch {
>
> -   default:
>
> -         // Outline path.
>
>
> -         for qi := 0; len(aux) >= (ops.QuadSize + 4); qi++ {
>
>
> -         	d.qs.contour = bo.Uint32(aux)
>
>
> -         	quad := ops.DecodeQuad(aux[4:])
>
>
> -         	quad = quad.Transform(tr)
>
>
>
> -         	d.qs.splitAndEncode(quad)
>
>
>
> -         	aux = aux[ops.QuadSize+4:]
>
>
> -         }
>
>
> -   case width > 0:
>
>
> -   case stroke.Width > 0:
>
>         // Stroke path.
>         quads := make(strokeQuads, 0, 2*len(aux)/(ops.QuadSize+4))
>         for qi := 0; len(aux) >= (ops.QuadSize + 4); qi++ {
>
>
>
> @@ -1252,13 +1311,25 @@ func (d *drawOps) buildVerts(aux []byte, tr f32.Affine2D, width float32, sty cli
>
> quads = append(quads, quad)
>
> aux = aux[ops.QuadSize+4:]
>
> }
>
> -         quads = quads.stroke(width, sty)
>
>
>
> -         quads = quads.stroke(stroke, dashes)
>           for _, quad := range quads {
>           	d.qs.contour = quad.contour
>           	quad.quad = quad.quad.Transform(tr)
>
>
>
> d.qs.splitAndEncode(quad.quad)
>
> }
>
> +
>
> -   case outline:
>
> -         // Outline path.
>
>
> -         for qi := 0; len(aux) >= (ops.QuadSize + 4); qi++ {
>
>
> -         	d.qs.contour = bo.Uint32(aux)
>
>
> -         	quad := ops.DecodeQuad(aux[4:])
>
>
> -         	quad = quad.Transform(tr)
>
>
>
> -         	d.qs.splitAndEncode(quad)
>
>
>
> -         	aux = aux[ops.QuadSize+4:]
>
>
> -         }
>
>
>     }
>
>     fillMaxY(d.vertCache[startLength:])
>
>     diff --git a/gpu/stroke.go b/gpu/stroke.go
>
>     index 7db08ae..6f3fa48 100644
>
>     --- a/gpu/stroke.go
>
>     +++ b/gpu/stroke.go
>
>     @@ -118,18 +118,18 @@ func (qs strokeQuads) split() []strokeQuads {
>
>     return o
>
>     }
>
>     -func (qs strokeQuads) stroke(width float32, sty clip.StrokeStyle) strokeQuads {
>
>
> -   if !isSolidLine(sty.Line) {
> -         qs = qs.dash(sty.Line)
>
>
>
> +func (qs strokeQuads) stroke(stroke clip.StrokeOp, dashes dashOp) strokeQuads {
>
> -   if !isSolidLine(dashes) {
> -         qs = qs.dash(dashes)
>
>
>     }
>
>     var (
>
>     o strokeQuads
>
> -         hw = 0.5 * width
>
>
>
> -         hw = 0.5 * stroke.Width
>
>
>     )
>
>     for _, ps := range qs.split() {
>
> -         rhs, lhs := ps.offset(hw, sty)
>
>
>
> -         rhs, lhs := ps.offset(hw, stroke)
>           switch lhs {
>           case nil:
>           	o = o.append(rhs)
>
>
>
> @@ -155,7 +155,7 @@ func (qs strokeQuads) stroke(width float32, sty clip.StrokeStyle) strokeQuads {
>
> // offset returns the right-hand and left-hand sides of the path, offset by
>
> // the half-width hw.
>
> // The stroke style sty handles how segments are joined and ends are capped.
>
> -func (qs strokeQuads) offset(hw float32, sty clip.StrokeStyle) (rhs, lhs strokeQuads) {
>
> +func (qs strokeQuads) offset(hw float32, sty clip.StrokeOp) (rhs, lhs strokeQuads) {
>
> var (
>
> states []strokeState
>
> beg = qs[0].quad.From
>
> @@ -466,7 +466,7 @@ func quadBezierSplit(p0, p1, p2 f32.Point, t float32) (f32.Point, f32.Point, f32
>
> // strokePathJoin joins the two paths rhs and lhs, according to the provided
>
> // stroke style sty.
>
> -func strokePathJoin(sty clip.StrokeStyle, rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
>
> +func strokePathJoin(sty clip.StrokeOp, rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
>
> if sty.Miter > 0 {
>
>     	strokePathMiterJoin(sty, rhs, lhs, hw, pivot, n0, n1, r0, r1)
>     	return
>
>
> @@ -512,7 +512,7 @@ func strokePathRoundJoin(rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Po
>
> }
>
> }
>
> -func strokePathMiterJoin(sty clip.StrokeStyle, rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
>
> +func strokePathMiterJoin(sty clip.StrokeOp, rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
>
> if n0 == n1.Mul(-1) {
>
> strokePathBevelJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
>
> return
>
> @@ -554,7 +554,7 @@ func strokePathMiterJoin(sty clip.StrokeStyle, rhs, lhs *strokeQuads, hw float32
>
> }
>
> // strokePathCap caps the provided path qs, according to the provided stroke style sty.
>
> -func strokePathCap(sty clip.StrokeStyle, qs *strokeQuads, hw float32, pivot, n0 f32.Point) {
>
> +func strokePathCap(sty clip.StrokeOp, qs *strokeQuads, hw float32, pivot, n0 f32.Point) {
>
> switch sty.Cap {
>
> case clip.FlatCap:
>
> strokePathFlatCap(qs, hw, pivot, n0)
>
> diff --git a/internal/opconst/ops.go b/internal/opconst/ops.go
>
> index b2c76ad..1cb828a 100644
>
> --- a/internal/opconst/ops.go
>
> +++ b/internal/opconst/ops.go
>
> @@ -27,6 +27,10 @@ const (
>
> TypeAux
>
> TypeClip
>
> TypeProfile
>
> -   TypePath
> -   TypeOutline
> -   TypeStroke
> -   TypeDash
>
>     )
>
>     const (
>
>     @@ -47,8 +51,12 @@ const (
>
>     TypePushLen = 1
>
>     TypePopLen = 1
>
>     TypeAuxLen = 1
>
> -   TypeClipLen = 1 + 4*4 + (4 + 2 + 4 + (4 + 1))
>
> -   TypeClipLen = 1 + 4*4
>
>     TypeProfileLen = 1
> -   TypePathLen = 1 + 4
> -   TypeOutlineLen = 1
> -   TypeStrokeLen = 1 + 4 + 4 + 1 + 1
> -   TypeDashLen = 1 + 4 + 1
>
>     )
>
>     func (t OpType) Size() int {
>
>     @@ -72,6 +80,10 @@ func (t OpType) Size() int {
>
>     TypeAuxLen,
>
>     TypeClipLen,
>
>     TypeProfileLen,
> -         TypePathLen,
>
>
> -         TypeOutlineLen,
>
>
> -         TypeStrokeLen,
>
>
> -         TypeDashLen,
>
>
>     }[t-firstOpIndex]
>
>     }
>
>     diff --git a/internal/ops/reader.go b/internal/ops/reader.go
>
>     index 5376dc2..0bab3a0 100644
>
>     --- a/internal/ops/reader.go
>
>     +++ b/internal/ops/reader.go
>
>     @@ -98,12 +98,6 @@ func (r *Reader) Decode() (EncodedOp, bool) {
>
>     refs = refs[r.pc.refs:]
>
>     refs = refs[:nrefs]
>
>     switch t {
>
> -         case opconst.TypeClip:
>
>
> -         	// A Clip operation may have trailing dashes float32 data.
>
>
> -         	// The last element of the fixed-length clip data is the number
>
>
> -         	// of elements describing the dashes pattern.
>
>
> -         	n += 4 * int(data[len(data)-1])
>
>
> -         	data = data[:n]
>           case opconst.TypeAux:
>           	// An Aux operations is always wrapped in a macro, and
>           	// its length is the remaining space.
>
>
>
> diff --git a/internal/rendertest/clip_test.go b/internal/rendertest/clip_test.go
>
> index efc98b7..18faf4c 100644
>
> --- a/internal/rendertest/clip_test.go
>
> +++ b/internal/rendertest/clip_test.go
>
> @@ -68,7 +68,10 @@ func TestPaintArc(t *testing.T) {
>
> p.Arc(f32.Pt(-10, -20), f32.Pt(10, -5), math.Pi)
>
> p.Line(f32.Pt(0, -10))
>
> p.Line(f32.Pt(-50, 0))
>
> -         p.Outline().Add(o)
>
>
>
> -         p.Close()
>
>
> -         p.Op().Add(o)
>
>
> -         clip.OutlineOp{}.Add(o)
>
>
> -         clip.Op{}.Add(o)
>
>
>
> paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 128, 128)).Op())
>
> }, func(r result) {
>
> @@ -87,7 +90,10 @@ func TestPaintAbsolute(t *testing.T) {
>
> p.MoveTo(f32.Pt(20, 20))
>
> p.LineTo(f32.Pt(80, 20))
>
> p.QuadTo(f32.Pt(80, 80), f32.Pt(20, 80))
>
> -         p.Outline().Add(o)
>
>
>
> -         p.Close()
>
>
> -         p.Op().Add(o)
>
>
> -         clip.OutlineOp{}.Add(o)
>
>
> -         clip.Op{}.Add(o)
>
>
>
> paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 128, 128)).Op())
>
> }, func(r result) {
>
> @@ -125,13 +131,13 @@ func TestPaintClippedTexture(t *testing.T) {
>
> func TestStrokedPathBevelFlat(t *testing.T) {
>
> run(t, func(o *op.Ops) {
>
> -         const width = 2.5
>
>
> -         sty := clip.StrokeStyle{
>
>
> -         	Cap:  clip.FlatCap,
>
>
> -         	Join: clip.BevelJoin,
>
>
> -         }
>
>
>
> -         newStrokedPath(o).Stroke(width, sty).Add(o)
>
>
>
> -         newStrokedPath(o).Op().Add(o)
>
>
> -         clip.StrokeOp{
>
>
> -         	Width: 2.5,
>
>
> -         	Cap:   clip.FlatCap,
>
>
> -         	Join:  clip.BevelJoin,
>
>
> -         }.Add(o)
>
>
> -         clip.Op{}.Add(o)
>
>
>
> paint.Fill(o, red)
>
> }, func(r result) {
>
> @@ -142,13 +148,13 @@ func TestStrokedPathBevelFlat(t *testing.T) {
>
> func TestStrokedPathBevelRound(t *testing.T) {
>
> run(t, func(o *op.Ops) {
>
> -         const width = 2.5
>
>
> -         sty := clip.StrokeStyle{
>
>
> -         	Cap:  clip.RoundCap,
>
>
> -         	Join: clip.BevelJoin,
>
>
> -         }
>
>
>
> -         newStrokedPath(o).Stroke(width, sty).Add(o)
>
>
>
> -         newStrokedPath(o).Op().Add(o)
>
>
> -         clip.StrokeOp{
>
>
> -         	Width: 2.5,
>
>
> -         	Cap:   clip.RoundCap,
>
>
> -         	Join:  clip.BevelJoin,
>
>
> -         }.Add(o)
>
>
> -         clip.Op{}.Add(o)
>
>
>
> paint.Fill(o, red)
>
> }, func(r result) {
>
> @@ -159,13 +165,13 @@ func TestStrokedPathBevelRound(t *testing.T) {
>
> func TestStrokedPathBevelSquare(t *testing.T) {
>
> run(t, func(o *op.Ops) {
>
> -         const width = 2.5
>
>
> -         sty := clip.StrokeStyle{
>
>
> -         	Cap:  clip.SquareCap,
>
>
> -         	Join: clip.BevelJoin,
>
>
> -         }
>
>
>
> -         newStrokedPath(o).Stroke(width, sty).Add(o)
>
>
>
> -         newStrokedPath(o).Op().Add(o)
>
>
> -         clip.StrokeOp{
>
>
> -         	Width: 2.5,
>
>
> -         	Cap:   clip.SquareCap,
>
>
> -         	Join:  clip.BevelJoin,
>
>
> -         }.Add(o)
>
>
> -         clip.Op{}.Add(o)
>
>
>
> paint.Fill(o, red)
>
> }, func(r result) {
>
> @@ -176,13 +182,13 @@ func TestStrokedPathBevelSquare(t *testing.T) {
>
> func TestStrokedPathRoundRound(t *testing.T) {
>
> run(t, func(o *op.Ops) {
>
> -         const width = 2.5
>
>
> -         sty := clip.StrokeStyle{
>
>
> -         	Cap:  clip.RoundCap,
>
>
> -         	Join: clip.RoundJoin,
>
>
> -         }
>
>
>
> -         newStrokedPath(o).Stroke(width, sty).Add(o)
>
>
>
> -         newStrokedPath(o).Op().Add(o)
>
>
> -         clip.StrokeOp{
>
>
> -         	Width: 2.5,
>
>
> -         	Cap:   clip.RoundCap,
>
>
> -         	Join:  clip.RoundJoin,
>
>
> -         }.Add(o)
>
>
> -         clip.Op{}.Add(o)
>
>
>
> paint.Fill(o, red)
>
> }, func(r result) {
>
> @@ -193,18 +199,29 @@ func TestStrokedPathRoundRound(t *testing.T) {
>
> func TestStrokedPathFlatMiter(t *testing.T) {
>
> run(t, func(o *op.Ops) {
>
> -         const width = 10
>
>
> -         sty := clip.StrokeStyle{
>
>
> -         	Cap:   clip.FlatCap,
>
>
> -         	Join:  clip.BevelJoin,
>
>
> -         	Miter: 5,
>
>
>
> -         {
>
>
> -         	stk := op.Push(o)
>
>
> -         	newZigZagPath(o).Op().Add(o)
>
>
> -         	clip.StrokeOp{
>
>
> -         		Width: 10,
>
>
> -         		Cap:   clip.FlatCap,
>
>
> -         		Join:  clip.BevelJoin,
>
>
> -         		Miter: 5,
>
>
> -         	}.Add(o)
>
>
> -         	clip.Op{}.Add(o)
>
>
> -         	paint.Fill(o, red)
>
>
> -         	stk.Pop()
>
>
> -         }
>
>
> -         {
>
>
> -         	stk := op.Push(o)
>
>
> -         	newZigZagPath(o).Op().Add(o)
>
>
> -         	clip.StrokeOp{
>
>
> -         		Width: 2,
>
>
> -         	}.Add(o)
>
>
> -         	clip.Op{}.Add(o)
>
>
> -         	paint.Fill(o, black)
>
>
> -         	stk.Pop()
>           }
>
>
>
> -         newZigZagPath(o).Stroke(width, sty).Add(o)
>
>
> -         paint.Fill(o, red)
>
>
>
> -         newZigZagPath(o).Stroke(2, clip.StrokeStyle{}).Add(o)
>
>
> -         paint.Fill(o, black)
>
>
>
> }, func(r result) {
>
> r.expect(0, 0, colornames.White)
>
> @@ -215,18 +232,29 @@ func TestStrokedPathFlatMiter(t *testing.T) {
>
> func TestStrokedPathFlatMiterInf(t *testing.T) {
>
> run(t, func(o *op.Ops) {
>
> -         const width = 10
>
>
> -         sty := clip.StrokeStyle{
>
>
> -         	Cap:   clip.FlatCap,
>
>
> -         	Join:  clip.BevelJoin,
>
>
> -         	Miter: float32(math.Inf(+1)),
>
>
>
> -         {
>
>
> -         	stk := op.Push(o)
>
>
> -         	newZigZagPath(o).Op().Add(o)
>
>
> -         	clip.StrokeOp{
>
>
> -         		Width: 10,
>
>
> -         		Cap:   clip.FlatCap,
>
>
> -         		Join:  clip.BevelJoin,
>
>
> -         		Miter: float32(math.Inf(+1)),
>
>
> -         	}.Add(o)
>
>
> -         	clip.Op{}.Add(o)
>
>
> -         	paint.Fill(o, red)
>
>
> -         	stk.Pop()
>
>
> -         }
>
>
> -         {
>
>
> -         	stk := op.Push(o)
>
>
> -         	newZigZagPath(o).Op().Add(o)
>
>
> -         	clip.StrokeOp{
>
>
> -         		Width: 2,
>
>
> -         	}.Add(o)
>
>
> -         	clip.Op{}.Add(o)
>
>
> -         	paint.Fill(o, black)
>
>
> -         	stk.Pop()
>           }
>
>
>
> -         newZigZagPath(o).Stroke(width, sty).Add(o)
>
>
> -         paint.Fill(o, red)
>
>
>
> -         newZigZagPath(o).Stroke(2, clip.StrokeStyle{}).Add(o)
>
>
> -         paint.Fill(o, black)
>
>
>
> }, func(r result) {
>
> r.expect(0, 0, colornames.White)
>
> @@ -237,26 +265,32 @@ func TestStrokedPathFlatMiterInf(t *testing.T) {
>
> func TestStrokedPathZeroWidth(t *testing.T) {
>
> run(t, func(o *op.Ops) {
>
> -         const width = 2
>
>
> -         var sty clip.StrokeStyle
>           {
>
>
>
> -         	stk := op.Push(o)
>           	p := new(clip.Path)
>           	p.Begin(o)
>           	p.Move(f32.Pt(10, 50))
>           	p.Line(f32.Pt(50, 0))
>
>
>
> -         	p.Stroke(width, sty).Add(o)
>
>
>
> -         	p.Op().Add(o)
>
>
> -         	clip.StrokeOp{Width: 2}.Add(o)
>
>
>
> -         	clip.Op{}.Add(o)
>           	paint.Fill(o, black)
>
>
> -         	stk.Pop()
>           }
>
>
>
> {
>
> -         	stk := op.Push(o)
>           	p := new(clip.Path)
>           	p.Begin(o)
>           	p.Move(f32.Pt(10, 50))
>           	p.Line(f32.Pt(30, 0))
>
>
>
> -         	p.Stroke(0, sty).Add(o) // width=0, disable stroke
>
>
>
> -         	p.Op().Add(o)
>
>
> -         	clip.StrokeOp{}.Add(o) // width=0, disable stroke
>
>
>
> -         	clip.Op{}.Add(o)
>           	paint.Fill(o, red)
>
>
> -         	stk.Pop()
>           }
>
>
>
> }, func(r result) {
>
> @@ -269,25 +303,41 @@ func TestStrokedPathZeroWidth(t *testing.T) {
>
> func TestDashedPathFlatCapEllipse(t *testing.T) {
>
> run(t, func(o *op.Ops) {
>
> -         const width = 10
>
>
> -         sty := clip.StrokeStyle{
>
>
> -         	Cap:   clip.FlatCap,
>
>
> -         	Join:  clip.BevelJoin,
>
>
> -         	Miter: float32(math.Inf(+1)),
>
>
> -         	Line: clip.DashStyle{
>
>
> -         		Dashes: []float32{5, 3},
>
>
> -         	},
>
>
>
> -         {
>
>
> -         	stk := op.Push(o)
>
>
> -         	newEllipsePath(o).Op().Add(o)
>
>
> -         	var dash clip.Dash
>
>
> -         	dash.Begin(o)
>
>
> -         	dash.Dash(5)
>
>
> -         	dash.Dash(3)
>
>
> -         	dash.Op().Add(o)
>
>
>
> -         	clip.StrokeOp{
>
>
> -         		Width: 10,
>
>
> -         		Cap:   clip.FlatCap,
>
>
> -         		Join:  clip.BevelJoin,
>
>
> -         		Miter: float32(math.Inf(+1)),
>
>
> -         	}.Add(o)
>
>
>
> -         	clip.Op{}.Add(o)
>
>
>
> -         	paint.Fill(
>
>
> -         		o,
>
>
> -         		red,
>
>
> -         	)
>
>
> -         	stk.Pop()
>
>
> -         }
>
>
> -         {
>
>
> -         	stk := op.Push(o)
>
>
> -         	newEllipsePath(o).Op().Add(o)
>
>
> -         	clip.StrokeOp{Width: 2}.Add(o)
>
>
> -         	clip.Op{}.Add(o)
>
>
> -         	paint.Fill(
>
>
> -         		o,
>
>
> -         		black,
>
>
> -         	)
>
>
> -         	stk.Pop()
>           }
>
>
>
> -         paint.FillShape(
>
>
> -         	o,
>
>
> -         	colornames.Red,
>
>
> -         	newEllipsePath(o).Stroke(width, sty),
>
>
> -         )
>
>
> -         paint.FillShape(
>
>
> -         	o,
>
>
> -         	colornames.Black,
>
>
> -         	newEllipsePath(o).Stroke(2, clip.StrokeStyle{}),
>
>
> -         )
>
>
>
> }, func(r result) {
>
> r.expect(0, 0, colornames.White)
>
> @@ -298,26 +348,33 @@ func TestDashedPathFlatCapEllipse(t *testing.T) {
>
> func TestDashedPathFlatCapZ(t *testing.T) {
>
> run(t, func(o *op.Ops) {
>
> -         const width = 10
>
>
> -         sty := clip.StrokeStyle{
>
>
> -         	Cap:   clip.FlatCap,
>
>
> -         	Join:  clip.BevelJoin,
>
>
> -         	Miter: float32(math.Inf(+1)),
>
>
> -         	Line: clip.DashStyle{
>
>
> -         		Dashes: []float32{5, 3},
>
>
> -         	},
>
>
>
> -         {
>
>
> -         	stk := op.Push(o)
>
>
> -         	newZigZagPath(o).Op().Add(o)
>
>
> -         	var dash clip.Dash
>
>
> -         	dash.Begin(o)
>
>
> -         	dash.Dash(5)
>
>
> -         	dash.Dash(3)
>
>
> -         	dash.Op().Add(o)
>
>
> -         	clip.StrokeOp{
>
>
> -         		Width: 10,
>
>
> -         		Cap:   clip.FlatCap,
>
>
> -         		Join:  clip.BevelJoin,
>
>
> -         		Miter: float32(math.Inf(+1)),
>
>
> -         	}.Add(o)
>
>
> -         	clip.Op{}.Add(o)
>
>
> -         	paint.Fill(o, red)
>
>
> -         	stk.Pop()
>           }
>
>
>
> -         paint.FillShape(
>
>
> -         	o,
>
>
> -         	colornames.Red,
>
>
> -         	newZigZagPath(o).Stroke(width, sty),
>
>
> -         )
>
>
> -         paint.FillShape(
>
>
> -         	o,
>
>
> -         	colornames.Black,
>
>
> -         	newZigZagPath(o).Stroke(2, clip.StrokeStyle{}),
>
>
> -         )
>
>
>
> -         {
>
>
> -         	stk := op.Push(o)
>
>
> -         	newZigZagPath(o).Op().Add(o)
>
>
> -         	clip.StrokeOp{Width: 2}.Add(o)
>
>
> -         	clip.Op{}.Add(o)
>
>
> -         	paint.Fill(o, black)
>
>
> -         	stk.Pop()
>
>
> -         }
>
>
>     }, func(r result) {
>
>     r.expect(0, 0, colornames.White)
>
>     r.expect(40, 10, colornames.Black)
>
>     @@ -328,26 +385,31 @@ func TestDashedPathFlatCapZ(t *testing.T) {
>
>     func TestDashedPathFlatCapZNoDash(t *testing.T) {
>
>     run(t, func(o *op.Ops) {
>
> -         const width = 10
>
>
> -         sty := clip.StrokeStyle{
>
>
> -         	Cap:   clip.FlatCap,
>
>
> -         	Join:  clip.BevelJoin,
>
>
> -         	Miter: float32(math.Inf(+1)),
>
>
> -         	Line: clip.DashStyle{
>
>
> -         		Offset: 1,
>
>
> -         	},
>
>
>
> -         {
>
>
> -         	stk := op.Push(o)
>
>
> -         	newZigZagPath(o).Op().Add(o)
>
>
> -         	clip.StrokeOp{
>
>
> -         		Width: 10,
>
>
> -         		Cap:   clip.FlatCap,
>
>
> -         		Join:  clip.BevelJoin,
>
>
> -         		Miter: float32(math.Inf(+1)),
>
>
> -         	}.Add(o)
>
>
> -         	var dash clip.Dash
>
>
> -         	dash.Begin(o)
>
>
> -         	dash.Phase(1)
>
>
> -         	dash.Op().Add(o)
>
>
> -         	clip.Op{}.Add(o)
>
>
> -         	paint.Fill(o, red)
>
>
> -         	stk.Pop()
>
>
> -         }
>
>
> -         {
>
>
> -         	stk := op.Push(o)
>
>
> -         	newZigZagPath(o).Op().Add(o)
>
>
> -         	clip.StrokeOp{Width: 2}.Add(o)
>
>
> -         	clip.Op{}.Add(o)
>
>
> -         	paint.Fill(o, black)
>
>
> -         	stk.Pop()
>           }
>
>
>
> -         paint.FillShape(
>
>
> -         	o,
>
>
> -         	colornames.Red,
>
>
> -         	newZigZagPath(o).Stroke(width, sty),
>
>
> -         )
>
>
> -         paint.FillShape(
>
>
> -         	o,
>
>
> -         	colornames.Black,
>
>
> -         	newZigZagPath(o).Stroke(2, clip.StrokeStyle{}),
>
>
> -         )
>
>
> -   }, func(r result) {
>
>     r.expect(0, 0, colornames.White)
>
>     r.expect(40, 10, colornames.Black)
>
>     @@ -358,26 +420,31 @@ func TestDashedPathFlatCapZNoDash(t *testing.T) {
>
>     func TestDashedPathFlatCapZNoPath(t *testing.T) {
>
>     run(t, func(o *op.Ops) {
> -         const width = 10
>
>
> -         sty := clip.StrokeStyle{
>
>
> -         	Cap:   clip.FlatCap,
>
>
> -         	Join:  clip.BevelJoin,
>
>
> -         	Miter: float32(math.Inf(+1)),
>
>
> -         	Line: clip.DashStyle{
>
>
> -         		Dashes: []float32{0},
>
>
> -         	},
>
>
>
> -         {
>
>
> -         	stk := op.Push(o)
>
>
> -         	newZigZagPath(o).Op().Add(o)
>
>
> -         	clip.StrokeOp{
>
>
> -         		Width: 10,
>
>
> -         		Cap:   clip.FlatCap,
>
>
> -         		Join:  clip.BevelJoin,
>
>
> -         		Miter: float32(math.Inf(+1)),
>
>
> -         	}.Add(o)
>
>
> -         	var dash clip.Dash
>
>
> -         	dash.Begin(o)
>
>
> -         	dash.Dash(0)
>
>
> -         	dash.Op().Add(o)
>
>
> -         	clip.Op{}.Add(o)
>
>
> -         	paint.Fill(o, red)
>
>
> -         	stk.Pop()
>
>
> -         }
>
>
> -         {
>
>
> -         	stk := op.Push(o)
>
>
> -         	newZigZagPath(o).Op().Add(o)
>
>
> -         	clip.StrokeOp{Width: 2}.Add(o)
>
>
> -         	clip.Op{}.Add(o)
>
>
> -         	paint.Fill(o, black)
>
>
> -         	stk.Pop()
>           }
>
>
>
> -         paint.FillShape(
>
>
> -         	o,
>
>
> -         	colornames.Red,
>
>
> -         	newZigZagPath(o).Stroke(width, sty),
>
>
> -         )
>
>
> -         paint.FillShape(
>
>
> -         	o,
>
>
> -         	colornames.Black,
>
>
> -         	newZigZagPath(o).Stroke(2, clip.StrokeStyle{}),
>
>
> -         )
>
>
> -   }, func(r result) {
>
>     r.expect(0, 0, colornames.White)
>
>     r.expect(40, 10, colornames.Black)
>
>     diff --git a/internal/rendertest/refs/TestStrokedPathFlatMiter.png b/internal/rendertest/refs/TestStrokedPathFlatMiter.png
>
>     index 95014b280da83d4b58cf51cbba059e88b1d21570..7ad77e3ac26cbe3742bff59c286895561d5dcecc 100644
>
>     GIT binary patch
>
>     delta 2246
>
>     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
>
> U`oS2KBLDyZ07qoM6N<$g0L}3aR2}S
>
> delta 2297
>
> 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 T*FHTp00000NkvXXu0mjfH;Zu( diff --git a/internal/rendertest/refs/TestStrokedPathFlatMiterInf.png b/internal/rendertest/refs/TestStrokedPathFlatMiterInf.png index d859637985a9bb897fa0cce522e98f75a1f3bba0..23a13c3b1013304c74b3b15320a9995aa346b9b8 100644 GIT binary patch delta 2250 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
>
> Y|Bc<h&s)CMr2qf`07qoM6N<$f~1E|oB#j-
>
> delta 2292
>
> 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
>
> O0000<MNUMnLSTZnC1y_m
>
> diff --git a/internal/rendertest/render_test.go b/internal/rendertest/render_test.go
>
> index 7557615..f913b21 100644
>
> --- a/internal/rendertest/render_test.go
>
> +++ b/internal/rendertest/render_test.go
>
> @@ -71,7 +71,10 @@ func TestRepeatedPaintsZ(t *testing.T) {
>
> builder.Line(f32.Pt(0, 10))
>
> builder.Line(f32.Pt(-10, 0))
>
> builder.Line(f32.Pt(0, -10))
>
> -         builder.Outline().Add(o)
>
>
>
> -         builder.Close()
>
>
> -         builder.Op().Add(o)
>
>
> -         clip.OutlineOp{}.Add(o)
>
>
> -         clip.Op{}.Add(o)
>           paint.Fill(o, red)
>
>
>     }, func(r result) {
>
>     r.expect(5, 5, colornames.Red)
>
>     @@ -109,7 +112,10 @@ func constSqPath() op.CallOp {
>
>     builder.Line(f32.Pt(0, 10))
>
>     builder.Line(f32.Pt(-10, 0))
>
>     builder.Line(f32.Pt(0, -10))
>
> -   builder.Outline().Add(innerOps)
>
> -   builder.Close()
> -   builder.Op().Add(innerOps)
> -   clip.OutlineOp{}.Add(innerOps)
> -   clip.Op{}.Add(innerOps)
>
>     return m.Stop()
>
>     }
>
>     diff --git a/op/clip/clip.go b/op/clip/clip.go
>
>     index f33ef3a..7bc659e 100644
>
>     --- a/op/clip/clip.go
>
>     +++ b/op/clip/clip.go
>
>     @@ -26,11 +26,26 @@ type Path struct {
>
>     pen f32.Point
>
>     macro op.MacroOp
>
>     start f32.Point
> -   quads uint32
>
>     }
>
>     // Pos returns the current pen position.
>
>     func (p *Path) Pos() f32.Point { return p.pen }
>
>     +// PathOp sets the current path. Construct PathOps with Path.
>
>     +type PathOp struct {
> -   call op.CallOp
> -   quads uint32
>
>     +}
>
> +func (op PathOp) Add(o *op.Ops) {
>
> -   data := o.Write(opconst.TypePathLen)
> -   data[0] = byte(opconst.TypePath)
> -   bo := binary.LittleEndian
> -   bo.PutUint32(data[1:], op.quads)
> -   op.call.Add(o)
>
>     +}
>
> // Op sets the current clip to the intersection of
>
> // the existing clip with this clip.
>
> //
>
> @@ -39,29 +54,17 @@ func (p *Path) Pos() f32.Point { return p.pen }
>
> type Op struct {
>
> call op.CallOp
>
> bounds image.Rectangle
>
> -   width float32 // Width of the stroked path, 0 for outline paths.
> -   style StrokeStyle // Style of the stroked path, zero for outline paths.
>
>     }
>
>     func (p Op) Add(o *op.Ops) {
>
>     p.call.Add(o)
> -   data := o.Write(opconst.TypeClipLen + len(p.style.Line.Dashes)*4)
>
> -   data := o.Write(opconst.TypeClipLen)
>
>     data[0] = byte(opconst.TypeClip)
>
>     bo := binary.LittleEndian
>
>     bo.PutUint32(data[1:], uint32(p.bounds.Min.X))
>
>     bo.PutUint32(data[5:], uint32(p.bounds.Min.Y))
>
>     bo.PutUint32(data[9:], uint32(p.bounds.Max.X))
>
>     bo.PutUint32(data[13:], uint32(p.bounds.Max.Y))
>
> -   bo.PutUint32(data[17:], math.Float32bits(p.width))
> -   data[21] = uint8(p.style.Cap)
> -   data[22] = uint8(p.style.Join)
> -   bo.PutUint32(data[23:], math.Float32bits(p.style.Miter))
> -   bo.PutUint32(data[27:], math.Float32bits(p.style.Line.Offset))
> -   data[31] = uint8(len(p.style.Line.Dashes))
> -   dashes := data[32:]
> -   for i, v := range p.style.Line.Dashes {
> -         bo.PutUint32(dashes[i*4:], math.Float32bits(v))
>
>
> -   }
>
>     }
>
>     // Begin the path, storing the path data and final Op into ops.
>
>     @@ -126,6 +129,7 @@ func (p *Path) QuadTo(ctrl, to f32.Point) {
>
>     To: to,
>
>     })
>
>     p.pen = to
>
> -   p.quads++
>
>     }
>
>     // Arc adds an elliptical arc to the path. The implied ellipse is defined
>
>     @@ -332,34 +336,39 @@ func (p *Path) approxCubeTo(splits int, maxDist float32, ctrl0, ctrl1, to f32.Po
>
>     return splits
>
>     }
>
>     -// Outline closes the path and returns a clip operation that represents it.
>
>     -func (p *Path) Outline() Op {
>
>     +// Close closes the path.
>
>     +func (p *Path) Close() {
>
>     p.end()
>
>     +}
>
> +// Op completes the path and sets the current path.
>
> +func (p *Path) Op() PathOp {
>
> c := p.macro.Stop()
>
> -   return Op{
> -         call: c,
>
>
>
> -   return PathOp{
> -         call:  c,
>
>
> -         quads: p.quads,
>
>
>     }
>
>     }
>
>     // Stroke returns a stroked path with the specified width
>
>     // and configuration.
>
>     // If the provided width is <= 0, the path won't be stroked.
>
>     -func (p *Path) Stroke(width float32, sty StrokeStyle) Op {
>
> -   if width <= 0 {
> -         // Explicitly discard the macro to ignore the path.
>
>
> -         p.macro.Stop()
>
>
> -         return Op{
>
>
> -         	call: op.Record(p.ops).Stop(),
>
>
> -         }
>
>
> -   }
>
> -   c := p.macro.Stop()
> -   return Op{
> -         call:  c,
>
>
> -         width: width,
>
>
> -         style: sty,
>
>
> -   }
>
>     -}
>
>     +//func (p *Path) Stroke(width float32, sty StrokeStyle) Op {
>
>     +// if width <= 0 {
>
>     +// // Explicitly discard the macro to ignore the path.
>
>     +// p.macro.Stop()
>
>     +// return Op{
>
>     +// call: op.Record(p.ops).Stop(),
>
>     +// }
>
>     +// }
>
>     +//
>
>     +// c := p.macro.Stop()
>
>     +// return Op{
>
>     +// call: c,
>
>     +// width: width,
>
>     +// style: sty,
>
>     +// }
>
>     +//}
>
>     // Rect represents the clip area of a pixel-aligned rectangle.
>
>     type Rect image.Rectangle
>
>     diff --git a/op/clip/shapes.go b/op/clip/shapes.go
>
>     index c5f551d..c93184a 100644
>
>     --- a/op/clip/shapes.go
>
>     +++ b/op/clip/shapes.go
>
>     @@ -36,7 +36,13 @@ func (rr RRect) Op(ops *op.Ops) Op {
>
>     p.Begin(ops)
>
>     p.Move(rr.Rect.Min)
>
>     roundRect(&p, rr.Rect.Size(), rr.SE, rr.SW, rr.NW, rr.NE)
> -   return p.Outline()
>
> -   p.Close()
>
> -   p.Op().Add(ops)
>
> -   OutlineOp{}.Add(ops)
>
> -   return Op{}
>
>     }
>
>     // Add the rectangle clip.
>
>     @@ -49,7 +55,6 @@ type Border struct {
>
>     // Rect is the bounds of the border.
>
>     Rect f32.Rectangle
>
>     Width float32
>
> -   Style StrokeStyle
>
>     // The corner radii.
>
>     SE, SW, NW, NE float32
>
>     }
>
>     @@ -58,11 +63,13 @@ type Border struct {
>
>     func (b Border) Op(ops *op.Ops) Op {
>
>     var p Path
>
>     p.Begin(ops)
> -   p.Move(b.Rect.Min)
>
>     roundRect(&p, b.Rect.Size(), b.SE, b.SW, b.NW, b.NE)
>
> -   p.Close()
>
> -   return p.Stroke(b.Width, b.Style)
>
> -   p.Op().Add(ops)
> -   StrokeOp{Width: b.Width}.Add(ops)
> -   return Op{}
>
>     }
>
>     // Add the border clip.
>
>     diff --git a/op/clip/stroke.go b/op/clip/stroke.go
>
>     index 7bf7929..fb38242 100644
>
>     --- a/op/clip/stroke.go
>
>     +++ b/op/clip/stroke.go
>
>     @@ -2,20 +2,13 @@
>
>     package clip
>
>     -// StrokeStyle describes how a stroked path should be drawn.
>
>     -// The zero value of StrokeStyle represents bevel-joined and flat-capped
>
>     -// strokes with a solid line.
>
>     -type StrokeStyle struct {
>
> -   Cap StrokeCap
> -   Join StrokeJoin
>
>     +import (
>
> -   "encoding/binary"
> -   "math"
>
> -   // Miter is the limit to apply to a miter joint.
> -   // The zero Miter disables the miter joint; setting Miter to +∞
> -   // unconditionally enables the miter joint.
> -   Miter float32
>
> -   Line DashStyle // Line defines the stroked path line.
>
>     -}
>
> -   "gioui.org/internal/opconst"
> -   "gioui.org/op"
>
>     +)
>
>     // StrokeCap describes the head or tail of a stroked path.
>
>     type StrokeCap uint8
>
>     @@ -47,9 +40,87 @@ const (
>
>     RoundJoin
>
>     )
>
>     -// DashStyle describes how a stroked path line should be drawn.
>
>     -// DashStyle zero value draws a solid line.
>
>     -type DashStyle struct {
>
> -   Offset float32 // Offset before the dash pattern.
> -   Dashes []float32 // Dashes is the sequence of lengths of [dash,space,...]
>
>     +// StrokeOp applies a stroke to the current path.
>
>     +type StrokeOp struct {
>
> -   Width float32 // Width of the stroked path, 0 for outline paths.
>
> -   // Miter is the limit to apply to a miter joint.
> -   // The zero Miter disables the miter joint; setting Miter to +∞
> -   // unconditionally enables the miter joint.
> -   Miter float32
> -   Cap StrokeCap
> -   Join StrokeJoin
>
>     +}
>
> +func (op StrokeOp) Add(o *op.Ops) {
>
> -   data := o.Write(opconst.TypeStrokeLen)
> -   data[0] = byte(opconst.TypeStroke)
> -   bo := binary.LittleEndian
> -   bo.PutUint32(data[1:], math.Float32bits(op.Width))
> -   bo.PutUint32(data[5:], math.Float32bits(op.Miter))
> -   data[9] = uint8(op.Cap)
> -   data[10] = uint8(op.Join)
>
>     +}
>
> +// OutlineOp fills the current path according to the non-zero
>
> +// winding rule.
>
> +type OutlineOp struct {
>
> +}
>
> +
>
> +func (op OutlineOp) Add(o *op.Ops) {
>
> -   data := o.Write(opconst.TypeOutlineLen)
> -   data[0] = byte(opconst.TypeOutline)
>
>     +}
>
> +// DashOp configures dashing for a stroked path. Construct a DashOp
>
> +// with Dashes and use it in combination with StrokeOp.
>
> +type DashOp struct {
>
> -   call op.CallOp
> -   phase float32
> -   size int // size of the pattern
>
>     +}
>
> +func (op DashOp) Add(o *op.Ops) {
>
> -   data := o.Write(opconst.TypeDashLen)
> -   data[0] = byte(opconst.TypeDash)
> -   bo := binary.LittleEndian
> -   bo.PutUint32(data[1:], math.Float32bits(op.phase))
> -   data[5] = uint8(op.size) // FIXME(sbinet) uint16? uint32?
> -   op.call.Add(o)
>
>     +}
>
> +// Dash records dashes' lengths and phase for a stroked path.
>
> +type Dash struct {
>
> -   ops *op.Ops
> -   macro op.MacroOp
> -   phase float32
> -   size int
>
>     +}
>
> +func (d *Dash) Begin(ops *op.Ops) {
>
> -   d.ops = ops
> -   d.macro = op.Record(ops)
> -   // Write the TypeAux opcode
> -   data := ops.Write(opconst.TypeAuxLen)
> -   data[0] = byte(opconst.TypeAux)
>
>     +}
>
> +func (d *Dash) Phase(v float32) {
>
> -   d.phase = v
>
>     +}
>
> +func (d *Dash) Dash(length float32) {
>
> -   data := d.ops.Write(4)
> -   bo := binary.LittleEndian
> -   bo.PutUint32(data[0:], math.Float32bits(length))
> -   d.size++
>
>     +}
>
> +func (d *Dash) Op() DashOp {
>
> -   c := d.macro.Stop()
> -   return DashOp{
> -         call:  c,
>
>
> -         phase: d.phase,
>
>
> -         size:  d.size,
>
>
> -   }
>
>     }
>
>     diff --git a/widget/material/loader.go b/widget/material/loader.go
>
>     index 48a4b6f..65efd5d 100644
>
>     --- a/widget/material/loader.go
>
>     +++ b/widget/material/loader.go
>
>     @@ -67,15 +67,13 @@ func clipLoader(ops *op.Ops, startAngle, endAngle, radius float64) {
>
>     pen = f32.Pt(float32(vx), float32(vy)).Mul(float32(radius))
>
>     center = f32.Pt(0, 0).Sub(pen)
>
> -         style = clip.StrokeStyle{
>
>
> -         	Cap: clip.FlatCap,
>
>
> -         }
>
>
> -         p clip.Path
>
>
>     )
>
>     p.Begin(ops)
>
>     p.Move(pen)
>
>     p.Arc(center, center, delta)
> -   p.Stroke(width, style).Add(ops)
>
> -   p.Op().Add(ops)
>
> -   clip.StrokeOp{Width: width, Cap: clip.FlatCap}.Add(ops)
>
>     }
>
>     --
>
>     2.29.2

Re: [PATCH gio v3 2/2] gpu,op/clip: split xyz-ops

Details
Message ID
<C7DZHKVX4XOQ.3U5NCLB6F6WQ5@themachine>
In-Reply-To
<9zr6x2Evsp0l6kXc5bUQILPPZmPNazFASnlrbd4uA4lSbihQKEnCIl4ivIFthL26sg6K17LEakieisjUiUPqOFfJG8IO_p5qqKbGLUChZOc=@sbinet.org> (view parent)
DKIM signature
pass
Download raw message
On Tue Nov 24, 2020 at 17:14, Sebastien Binet wrote:
> hi,
>
> I've kept the original implementation of dashed stroked paths as a separate commit and then the split into various xyzOp structures.
>
> I am not completely happy with this v3 though:
> - it's way too easy to forget a clip.xyzOp
> - it's a bit more verbose
> - I've kept the clip.Path.Close (but made a mental note of the need to get rid of it for a v4)
>

Thanks for summarizing the issues. I'm going to use the opentype.go change
as example:

- builder.Outline().Add(ops)
+ builder.Close()
+ builder.Op().Add(ops)
+ clip.OutlineOp{}.Add(ops)
+ clip.Op{}.Add(ops)

On the face of it, that is more verbose and easy to forget one of the
operations. However, with your point (3) in mind, and letting
"outline-mode" be the default, I think you can reduce the change to
just

- builder.Outline().Add(ops)
+ builder.Op().Add(ops)
+ clip.Op{}.Add(ops)

which is only one more line.

For filling a shape with a color there is the one-line
op/paint.FillShape function.

It's true that a stroke will need an additional operation, but that
seems fair because you need to specify the stroke parameters (width
etc.) anyway. The same applies to dashed strokes, which need even more
parameters.

Speaking of FillShape, I think its shape parameter should change to a
PathOp type, and clip.Rect.Op should return a PathOp (specialized to a
pixel-aligned rectangle).

I took a quick look at the patches; please correct me if I missed
something.

> ie: it's more of a WIP, plea for comments than anything else.
>
> cheers,
> -s
>
> ‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
>
> On Tuesday, November 24th, 2020 at 6:10 PM, Sebastien Binet <s@sbinet.org> wrote:
>
> > Signed-off-by: Sebastien Binet s@sbinet.org
> >
> > font/opentype/opentype.go | 5 +-
> >
> > gpu/dash.go | 64 ++--
> >
> > gpu/gpu.go | 159 ++++++---
> >
> > gpu/stroke.go | 18 +-
> >
> > internal/opconst/ops.go | 14 +-
> >
> > internal/ops/reader.go | 6 -
> >
> > internal/rendertest/clip_test.go | 327 +++++++++++-------
> >
> > .../refs/TestStrokedPathFlatMiter.png | Bin 2309 -> 2258 bytes
> >
> > .../refs/TestStrokedPathFlatMiterInf.png | Bin 2304 -> 2262 bytes
> >
> > internal/rendertest/render_test.go | 10 +-
> >
> > op/clip/clip.go | 75 ++--
> >
> > op/clip/shapes.go | 15 +-
> >
> > op/clip/stroke.go | 107 +++++-
> >
> > widget/material/loader.go | 8 +-
> >
> > 14 files changed, 522 insertions(+), 286 deletions(-)
> >
> > diff --git a/font/opentype/opentype.go b/font/opentype/opentype.go
> >
> > index 3e89cef..de96d2b 100644
> >
> > --- a/font/opentype/opentype.go
> >
> > +++ b/font/opentype/opentype.go
> >
> > @@ -323,7 +323,10 @@ func textPath(buf *sfnt.Buffer, ppem fixed.Int26_6, fonts []*opentype, str text.
> >
> > x += str.Advances[rune]
> >
> > rune++
> >
> > }
> >
> > -   builder.Outline().Add(ops)
> >
> > -   builder.Close()
> > -   builder.Op().Add(ops)
> > -   clip.OutlineOp{}.Add(ops)
> > -   clip.Op{}.Add(ops)
> >
> >     return m.Stop()
> >
> >     }
> >
> >     diff --git a/gpu/dash.go b/gpu/dash.go
> >
> >     index 1460228..323298f 100644
> >
> >     --- a/gpu/dash.go
> >
> >     +++ b/gpu/dash.go
> >
> >     @@ -12,29 +12,27 @@ import (
> >
> >     "gioui.org/f32"
> >
> >     "gioui.org/internal/ops"
> >
> > -   "gioui.org/op/clip"
> >
> >     )
> >
> >     -func isSolidLine(sty clip.DashStyle) bool {
> > -   return sty.Offset == 0 && len(sty.Dashes) == 0
> >
> >     +func isSolidLine(sty dashOp) bool {
> >
> > -   return sty.phase == 0 && len(sty.dashes) == 0
> >
> >     }
> >
> >     -func (qs strokeQuads) dash(sty clip.DashStyle) strokeQuads {
> >
> >     +func (qs strokeQuads) dash(sty dashOp) strokeQuads {
> >
> >     sty = dashCanonical(sty)
> >
> >     switch {
> >
> > -   case len(sty.Dashes) == 0:
> >
> > -   case len(sty.dashes) == 0:
> >
> >     return qs
> >
> > -   case len(sty.Dashes) == 1 && sty.Dashes[0] == 0.0:
> > -         var o strokeQuads
> >
> >
> > -         return o
> >
> >
> >
> > -   case len(sty.dashes) == 1 && sty.dashes[0] == 0.0:
> > -         return strokeQuads{}
> >
> >
> >     }
> >
> > -   if len(sty.Dashes)%2 == 1 {
> >
> > -   if len(sty.dashes)%2 == 1 {
> >
> >     // If the dash pattern is of uneven length, dash and space lengths
> >
> >     // alternate. The following duplicates the pattern so that uneven
> >
> >     // indices are always spaces.
> >
> > -         sty.Dashes = append(sty.Dashes, sty.Dashes...)
> >
> >
> >
> > -         sty.dashes = append(sty.dashes, sty.dashes...)
> >
> >
> >     }
> >
> >     var (
> >
> >     @@ -51,13 +49,13 @@ func (qs strokeQuads) dash(sty clip.DashStyle) strokeQuads {
> >
> >     t []float64
> >
> >     length = ps.len()
> >
> >     )
> >
> > -         for pos+sty.Dashes[i] < length {
> >
> >
> > -         	pos += sty.Dashes[i]
> >
> >
> >
> > -         for pos+sty.dashes[i] < length {
> >
> >
> > -         	pos += sty.dashes[i]
> >           	if 0.0 < pos {
> >           		t = append(t, float64(pos))
> >           	}
> >           	i++
> >
> >
> >
> > -         	if i == len(sty.Dashes) {
> >
> >
> >
> > -         	if i == len(sty.dashes) {
> >           		i = 0
> >           	}
> >           }
> >
> >
> >
> > @@ -88,13 +86,13 @@ func (qs strokeQuads) dash(sty clip.DashStyle) strokeQuads {
> >
> > return out
> >
> > }
> >
> > -func dashCanonical(sty clip.DashStyle) clip.DashStyle {
> >
> > +func dashCanonical(sty dashOp) dashOp {
> >
> > var (
> >
> > o = sty
> >
> > -         ds = o.Dashes
> >
> >
> >
> > -         ds = o.dashes
> >
> >
> >     )
> >
> > -   if len(sty.Dashes) == 0 {
> >
> > -   if len(sty.dashes) == 0 {
> >
> >     return sty
> >
> >     }
> >
> >     @@ -110,12 +108,12 @@ func dashCanonical(sty clip.DashStyle) clip.DashStyle {
> >
> >     // Remove first zero, collapse with second and last.
> >
> >     if f32Eq(ds[0], 0.0) {
> >
> >     if len(ds) < 3 {
> >
> > -         	return clip.DashStyle{
> >
> >
> > -         		Offset: 0.0,
> >
> >
> > -         		Dashes: []float32{0.0},
> >
> >
> >
> > -         	return dashOp{
> >
> >
> > -         		phase: 0.0,
> >
> >
> > -         		dashes: []float32{0.0},
> >           	}
> >           }
> >
> >
> >
> > -         o.Offset -= ds[1]
> >
> >
> >
> > -         o.phase -= ds[1]
> >           ds[len(ds)-1] += ds[1]
> >           ds = ds[2:]
> >
> >
> >     }
> >
> >     @@ -123,9 +121,9 @@ func dashCanonical(sty clip.DashStyle) clip.DashStyle {
> >
> >     // Remove last zero, collapse with fist and second to last.
> >
> >     if f32Eq(ds[len(ds)-1], 0.0) {
> >
> >     if len(ds) < 3 {
> >
> > -         	return clip.DashStyle{}
> >
> >
> >
> > -         	return dashOp{}
> >           }
> >
> >
> >
> > -         o.Offset += ds[len(ds)-2]
> >
> >
> >
> > -         o.phase += ds[len(ds)-2]
> >           ds[0] += ds[len(ds)-2]
> >           ds = ds[:len(ds)-2]
> >
> >
> >     }
> >
> >     @@ -133,9 +131,9 @@ func dashCanonical(sty clip.DashStyle) clip.DashStyle {
> >
> >     // If there are zeros or negatives, don't draw dashes.
> >
> >     for i := 0; i < len(ds); i++ {
> >
> >     if ds[i] < 0.0 || f32Eq(ds[i], 0.0) {
> >
> > -         	return clip.DashStyle{
> >
> >
> > -         		Offset: 0.0,
> >
> >
> > -         		Dashes: []float32{0.0},
> >
> >
> >
> > -         	return dashOp{
> >
> >
> > -         		phase: 0.0,
> >
> >
> > -         		dashes: []float32{0.0},
> >           	}
> >           }
> >
> >
> >     }
> >
> >     @@ -154,23 +152,23 @@ loop:
> >
> >     return o
> >
> >     }
> >
> >     -func dashStart(sty clip.DashStyle) (int, float32) {
> >
> >     +func dashStart(sty dashOp) (int, float32) {
> >
> >     i0 := 0 // i0 is the index into dashes.
> >
> > -   for sty.Dashes[i0] <= sty.Offset {
> > -         sty.Offset -= sty.Dashes[i0]
> >
> >
> >
> > -   for sty.dashes[i0] <= sty.phase {
> > -         sty.phase -= sty.dashes[i0]
> >           i0++
> >
> >
> >
> > -         if i0 == len(sty.Dashes) {
> >
> >
> >
> > -         if i0 == len(sty.dashes) {
> >           	i0 = 0
> >           }
> >
> >
> >     }
> >
> >     // pos0 may be negative if the offset lands halfway into dash.
> >
> > -   pos0 := -sty.Offset
> > -   if sty.Offset < 0.0 {
> >
> > -   pos0 := -sty.phase
> > -   if sty.phase < 0.0 {
> >
> >     var sum float32
> >
> > -         for _, d := range sty.Dashes {
> >
> >
> >
> > -         for _, d := range sty.dashes {
> >           	sum += d
> >           }
> >
> >
> >
> > -         pos0 = -(sum + sty.Offset) // handle negative offsets
> >
> >
> >
> > -         pos0 = -(sum + sty.phase) // handle negative offsets
> >
> >
> >     }
> >
> >     return i0, pos0
> >
> >     }
> >
> >     diff --git a/gpu/gpu.go b/gpu/gpu.go
> >
> >     index 5f46862..08484d6 100644
> >
> >     --- a/gpu/gpu.go
> >
> >     +++ b/gpu/gpu.go
> >
> >     @@ -109,6 +109,52 @@ type imageOp struct {
> >
> >     place placement
> >
> >     }
> >
> >     +type dashOp struct {
> > -   phase float32
> > -   dashes []float32
> >
> >     +}
> >
> > +func decodeDashOp(data []byte) dashOp {
> >
> > -   _ = data[5]
> > -   if opconst.OpType(data[0]) != opconst.TypeDash {
> > -         panic("invalid op")
> >
> >
> > -   }
> > -   bo := binary.LittleEndian
> > -   return dashOp{
> > -         phase:  math.Float32frombits(bo.Uint32(data[1:])),
> >
> >
> > -         dashes: make([]float32, data[5]),
> >
> >
> > -   }
> >
> >     +}
> >
> > +func decodeStrokeOp(data []byte) clip.StrokeOp {
> >
> > -   _ = data[10]
> > -   if opconst.OpType(data[0]) != opconst.TypeStroke {
> > -         panic("invalid op")
> >
> >
> > -   }
> > -   bo := binary.LittleEndian
> > -   return clip.StrokeOp{
> > -         Width: math.Float32frombits(bo.Uint32(data[1:])),
> >
> >
> > -         Miter: math.Float32frombits(bo.Uint32(data[5:])),
> >
> >
> > -         Cap:   clip.StrokeCap(data[9]),
> >
> >
> > -         Join:  clip.StrokeJoin(data[10]),
> >
> >
> > -   }
> >
> >     +}
> >
> > +type quadsOp struct {
> >
> > -   quads uint32
> > -   key ops.Key
> > -   aux []byte
> >
> >     +}
> >
> > +func decodeQuadsOp(data []byte) uint32 {
> >
> > -   _ = data[:1+4]
> > -   if opconst.OpType(data[0]) != opconst.TypePath {
> > -         panic("invalid op")
> >
> >
> > -   }
> > -   bo := binary.LittleEndian
> > -   return bo.Uint32(data[1:])
> >
> >     +}
> >
> > type material struct {
> >
> > material materialType
> >
> > opaque bool
> >
> > @@ -126,8 +172,6 @@ type material struct {
> >
> > type clipOp struct {
> >
> > // TODO: Use image.Rectangle?
> >
> > bounds f32.Rectangle
> >
> > -   width float32
> > -   style clip.StrokeStyle
> >
> >     }
> >
> >     // imageOpData is the shadow of paint.ImageOp.
> >
> >     @@ -160,18 +204,6 @@ func (op *clipOp) decode(data []byte) {
> >
> >     }
> >
> >     *op = clipOp{
> >
> >     bounds: layout.FRect(r),
> > -         width:  math.Float32frombits(bo.Uint32(data[17:])),
> >
> >
> > -         style: clip.StrokeStyle{
> >
> >
> > -         	Cap:   clip.StrokeCap(data[21]),
> >
> >
> > -         	Join:  clip.StrokeJoin(data[22]),
> >
> >
> > -         	Miter: math.Float32frombits(bo.Uint32(data[23:])),
> >
> >
> > -         },
> >
> >
> > -   }
> > -   op.style.Line.Offset = math.Float32frombits(bo.Uint32(data[27:]))
> > -   op.style.Line.Dashes = make([]float32, data[31])
> > -   dashes := data[32:]
> > -   for i := range op.style.Line.Dashes {
> > -         op.style.Line.Dashes[i] = math.Float32frombits(bo.Uint32(dashes[i*4:]))
> >
> >
> >     }
> >
> >     }
> >
> >     @@ -792,8 +824,12 @@ func splitTransform(t f32.Affine2D) (srs f32.Affine2D, offset f32.Point) {
> >
> >     }
> >
> >     func (d *drawOps) collectOps(r *ops.Reader, state drawState) int {
> > -   var aux []byte
> > -   var auxKey ops.Key
> >
> > -   var (
> > -         quads   quadsOp
> >
> >
> > -         outline bool
> >
> >
> > -         stroke  clip.StrokeOp
> >
> >
> > -         dashes  dashOp
> >
> >
> > -   )
> >
> >     loop:
> >
> >     for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
> >
> >     switch opconst.OpType(encOp.Data[0]) {
> >
> >     @@ -802,38 +838,72 @@ loop:
> >
> >     case opconst.TypeTransform:
> >
> >     dop := ops.DecodeTransform(encOp.Data)
> >
> >     state.t = state.t.Mul(dop)
> >
> > -         case opconst.TypeAux:
> >
> >
> > -         	aux = encOp.Data[opconst.TypeAuxLen:]
> >
> >
> > -         	auxKey = encOp.Key
> >
> >
> >
> > -         case opconst.TypeDash:
> >
> >
> > -         	dashes = decodeDashOp(encOp.Data)
> >
> >
> > -         	if len(dashes.dashes) > 0 {
> >
> >
> > -         		encOp, ok = r.Decode()
> >
> >
> > -         		if !ok {
> >
> >
> > -         			break loop
> >
> >
> > -         		}
> >
> >
> > -         		data := encOp.Data[1:]
> >
> >
> > -         		bo := binary.LittleEndian
> >
> >
> > -         		for i := range dashes.dashes {
> >
> >
> > -         			dashes.dashes[i] = math.Float32frombits(bo.Uint32(
> >
> >
> > -         				data[i*4:],
> >
> >
> > -         			))
> >
> >
> > -         		}
> >
> >
> > -         	}
> >
> >
> >
> > -         case opconst.TypeOutline:
> >
> >
> > -         	outline = true
> >
> >
> >
> > -         case opconst.TypeStroke:
> >
> >
> > -         	stroke = decodeStrokeOp(encOp.Data)
> >
> >
> >
> > -         case opconst.TypePath:
> >
> >
> > -         	quads.quads = decodeQuadsOp(encOp.Data)
> >
> >
> > -         	if quads.quads > 0 {
> >
> >
> > -         		encOp, ok = r.Decode()
> >
> >
> > -         		if !ok {
> >
> >
> > -         			break loop
> >
> >
> > -         		}
> >
> >
> > -         		quads.aux = encOp.Data[opconst.TypeAuxLen:]
> >
> >
> > -         		quads.key = encOp.Key
> >
> >
> > -         	}
> >
> >
> > -         case opconst.TypeClip:
> >           	var op clipOp
> >           	op.decode(encOp.Data)
> >           	bounds := op.bounds
> >           	trans, off := splitTransform(state.t)
> >
> >
> >
> > -         	if len(aux) > 0 {
> >
> >
> >
> > -         	if len(quads.aux) > 0 {
> >
> >           		// There is a clipping path, build the gpu data and update the
> >           		// cache key such that it will be equal only if the transform is the
> >           		// same also. Use cached data if we have it.
> >
> >
> >
> > -         		auxKey = auxKey.SetTransform(trans)
> >
> >
> > -         		if v, ok := d.pathCache.get(auxKey); ok {
> >
> >
> >
> > -         		quads.key = quads.key.SetTransform(trans)
> >
> >
> > -         		if v, ok := d.pathCache.get(quads.key); ok {
> >           			// Since the GPU data exists in the cache aux will not be used.
> >           			// Why is this not used for the offset shapes?
> >           			op.bounds = v.bounds
> >           		} else {
> >
> >
> >
> > -         			aux, op.bounds = d.buildVerts(aux, trans, op.width, op.style)
> >
> >
> >
> > -         			quads.aux, op.bounds = d.buildVerts(quads.aux, trans, outline, stroke, dashes)
> >           			// add it to the cache, without GPU data, so the transform can be
> >           			// reused.
> >
> >
> >
> > -         			d.pathCache.put(auxKey, opCacheValue{bounds: op.bounds})
> >
> >
> >
> > -         			d.pathCache.put(quads.key, opCacheValue{bounds: op.bounds})
> >           		}
> >           	} else {
> >
> >
> >
> > -         		aux, op.bounds, _ = d.boundsForTransformedRect(bounds, trans)
> >
> >
> > -         		auxKey = encOp.Key
> >
> >
> > -         		auxKey.SetTransform(trans)
> >
> >
> >
> > -         		quads.aux, op.bounds, _ = d.boundsForTransformedRect(bounds, trans)
> >
> >
> > -         		quads.key = encOp.Key
> >
> >
> > -         		quads.key.SetTransform(trans)
> >           	}
> >           	state.clip = state.clip.Intersect(op.bounds.Add(off))
> >
> >
> >
> > -         	d.addClipPath(&state, aux, auxKey, op.bounds, off)
> >
> >
> > -         	aux = nil
> >
> >
> > -         	auxKey = ops.Key{}
> >
> >
> >
> > -         	d.addClipPath(&state, quads.aux, quads.key, op.bounds, off)
> >
> >
> > -         	quads = quadsOp{}
> >
> >
> > -         	outline = false
> >
> >
> > -         	stroke = clip.StrokeOp{}
> >
> >
> > -         	dashes = dashOp{}
> >
> >
> > -         case opconst.TypeColor:
> >           	state.matType = materialColor
> >           	state.color = decodeColorOp(encOp.Data)
> >
> >
> >
> > @@ -1219,7 +1289,7 @@ func (d *drawOps) writeVertCache(n int) []byte {
> >
> > }
> >
> > // transform, split paths as needed, calculate maxY, bounds and create GPU vertices.
> >
> > -func (d *drawOps) buildVerts(aux []byte, tr f32.Affine2D, width float32, sty clip.StrokeStyle) (verts []byte, bounds f32.Rectangle) {
> >
> > +func (d *drawOps) buildVerts(aux []byte, tr f32.Affine2D, outline bool, stroke clip.StrokeOp, dashes dashOp) (verts []byte, bounds f32.Rectangle) {
> >
> > inf := float32(math.Inf(+1))
> >
> > d.qs.bounds = f32.Rectangle{
> >
> > Min: f32.Point{X: inf, Y: inf},
> >
> > @@ -1230,18 +1300,7 @@ func (d *drawOps) buildVerts(aux []byte, tr f32.Affine2D, width float32, sty cli
> >
> > startLength := len(d.vertCache)
> >
> > switch {
> >
> > -   default:
> >
> > -         // Outline path.
> >
> >
> > -         for qi := 0; len(aux) >= (ops.QuadSize + 4); qi++ {
> >
> >
> > -         	d.qs.contour = bo.Uint32(aux)
> >
> >
> > -         	quad := ops.DecodeQuad(aux[4:])
> >
> >
> > -         	quad = quad.Transform(tr)
> >
> >
> >
> > -         	d.qs.splitAndEncode(quad)
> >
> >
> >
> > -         	aux = aux[ops.QuadSize+4:]
> >
> >
> > -         }
> >
> >
> > -   case width > 0:
> >
> >
> > -   case stroke.Width > 0:
> >
> >         // Stroke path.
> >         quads := make(strokeQuads, 0, 2*len(aux)/(ops.QuadSize+4))
> >         for qi := 0; len(aux) >= (ops.QuadSize + 4); qi++ {
> >
> >
> >
> > @@ -1252,13 +1311,25 @@ func (d *drawOps) buildVerts(aux []byte, tr f32.Affine2D, width float32, sty cli
> >
> > quads = append(quads, quad)
> >
> > aux = aux[ops.QuadSize+4:]
> >
> > }
> >
> > -         quads = quads.stroke(width, sty)
> >
> >
> >
> > -         quads = quads.stroke(stroke, dashes)
> >           for _, quad := range quads {
> >           	d.qs.contour = quad.contour
> >           	quad.quad = quad.quad.Transform(tr)
> >
> >
> >
> > d.qs.splitAndEncode(quad.quad)
> >
> > }
> >
> > +
> >
> > -   case outline:
> >
> > -         // Outline path.
> >
> >
> > -         for qi := 0; len(aux) >= (ops.QuadSize + 4); qi++ {
> >
> >
> > -         	d.qs.contour = bo.Uint32(aux)
> >
> >
> > -         	quad := ops.DecodeQuad(aux[4:])
> >
> >
> > -         	quad = quad.Transform(tr)
> >
> >
> >
> > -         	d.qs.splitAndEncode(quad)
> >
> >
> >
> > -         	aux = aux[ops.QuadSize+4:]
> >
> >
> > -         }
> >
> >
> >     }
> >
> >     fillMaxY(d.vertCache[startLength:])
> >
> >     diff --git a/gpu/stroke.go b/gpu/stroke.go
> >
> >     index 7db08ae..6f3fa48 100644
> >
> >     --- a/gpu/stroke.go
> >
> >     +++ b/gpu/stroke.go
> >
> >     @@ -118,18 +118,18 @@ func (qs strokeQuads) split() []strokeQuads {
> >
> >     return o
> >
> >     }
> >
> >     -func (qs strokeQuads) stroke(width float32, sty clip.StrokeStyle) strokeQuads {
> >
> >
> > -   if !isSolidLine(sty.Line) {
> > -         qs = qs.dash(sty.Line)
> >
> >
> >
> > +func (qs strokeQuads) stroke(stroke clip.StrokeOp, dashes dashOp) strokeQuads {
> >
> > -   if !isSolidLine(dashes) {
> > -         qs = qs.dash(dashes)
> >
> >
> >     }
> >
> >     var (
> >
> >     o strokeQuads
> >
> > -         hw = 0.5 * width
> >
> >
> >
> > -         hw = 0.5 * stroke.Width
> >
> >
> >     )
> >
> >     for _, ps := range qs.split() {
> >
> > -         rhs, lhs := ps.offset(hw, sty)
> >
> >
> >
> > -         rhs, lhs := ps.offset(hw, stroke)
> >           switch lhs {
> >           case nil:
> >           	o = o.append(rhs)
> >
> >
> >
> > @@ -155,7 +155,7 @@ func (qs strokeQuads) stroke(width float32, sty clip.StrokeStyle) strokeQuads {
> >
> > // offset returns the right-hand and left-hand sides of the path, offset by
> >
> > // the half-width hw.
> >
> > // The stroke style sty handles how segments are joined and ends are capped.
> >
> > -func (qs strokeQuads) offset(hw float32, sty clip.StrokeStyle) (rhs, lhs strokeQuads) {
> >
> > +func (qs strokeQuads) offset(hw float32, sty clip.StrokeOp) (rhs, lhs strokeQuads) {
> >
> > var (
> >
> > states []strokeState
> >
> > beg = qs[0].quad.From
> >
> > @@ -466,7 +466,7 @@ func quadBezierSplit(p0, p1, p2 f32.Point, t float32) (f32.Point, f32.Point, f32
> >
> > // strokePathJoin joins the two paths rhs and lhs, according to the provided
> >
> > // stroke style sty.
> >
> > -func strokePathJoin(sty clip.StrokeStyle, rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
> >
> > +func strokePathJoin(sty clip.StrokeOp, rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
> >
> > if sty.Miter > 0 {
> >
> >     	strokePathMiterJoin(sty, rhs, lhs, hw, pivot, n0, n1, r0, r1)
> >     	return
> >
> >
> > @@ -512,7 +512,7 @@ func strokePathRoundJoin(rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Po
> >
> > }
> >
> > }
> >
> > -func strokePathMiterJoin(sty clip.StrokeStyle, rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
> >
> > +func strokePathMiterJoin(sty clip.StrokeOp, rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
> >
> > if n0 == n1.Mul(-1) {
> >
> > strokePathBevelJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
> >
> > return
> >
> > @@ -554,7 +554,7 @@ func strokePathMiterJoin(sty clip.StrokeStyle, rhs, lhs *strokeQuads, hw float32
> >
> > }
> >
> > // strokePathCap caps the provided path qs, according to the provided stroke style sty.
> >
> > -func strokePathCap(sty clip.StrokeStyle, qs *strokeQuads, hw float32, pivot, n0 f32.Point) {
> >
> > +func strokePathCap(sty clip.StrokeOp, qs *strokeQuads, hw float32, pivot, n0 f32.Point) {
> >
> > switch sty.Cap {
> >
> > case clip.FlatCap:
> >
> > strokePathFlatCap(qs, hw, pivot, n0)
> >
> > diff --git a/internal/opconst/ops.go b/internal/opconst/ops.go
> >
> > index b2c76ad..1cb828a 100644
> >
> > --- a/internal/opconst/ops.go
> >
> > +++ b/internal/opconst/ops.go
> >
> > @@ -27,6 +27,10 @@ const (
> >
> > TypeAux
> >
> > TypeClip
> >
> > TypeProfile
> >
> > -   TypePath
> > -   TypeOutline
> > -   TypeStroke
> > -   TypeDash
> >
> >     )
> >
> >     const (
> >
> >     @@ -47,8 +51,12 @@ const (
> >
> >     TypePushLen = 1
> >
> >     TypePopLen = 1
> >
> >     TypeAuxLen = 1
> >
> > -   TypeClipLen = 1 + 4*4 + (4 + 2 + 4 + (4 + 1))
> >
> > -   TypeClipLen = 1 + 4*4
> >
> >     TypeProfileLen = 1
> > -   TypePathLen = 1 + 4
> > -   TypeOutlineLen = 1
> > -   TypeStrokeLen = 1 + 4 + 4 + 1 + 1
> > -   TypeDashLen = 1 + 4 + 1
> >
> >     )
> >
> >     func (t OpType) Size() int {
> >
> >     @@ -72,6 +80,10 @@ func (t OpType) Size() int {
> >
> >     TypeAuxLen,
> >
> >     TypeClipLen,
> >
> >     TypeProfileLen,
> > -         TypePathLen,
> >
> >
> > -         TypeOutlineLen,
> >
> >
> > -         TypeStrokeLen,
> >
> >
> > -         TypeDashLen,
> >
> >
> >     }[t-firstOpIndex]
> >
> >     }
> >
> >     diff --git a/internal/ops/reader.go b/internal/ops/reader.go
> >
> >     index 5376dc2..0bab3a0 100644
> >
> >     --- a/internal/ops/reader.go
> >
> >     +++ b/internal/ops/reader.go
> >
> >     @@ -98,12 +98,6 @@ func (r *Reader) Decode() (EncodedOp, bool) {
> >
> >     refs = refs[r.pc.refs:]
> >
> >     refs = refs[:nrefs]
> >
> >     switch t {
> >
> > -         case opconst.TypeClip:
> >
> >
> > -         	// A Clip operation may have trailing dashes float32 data.
> >
> >
> > -         	// The last element of the fixed-length clip data is the number
> >
> >
> > -         	// of elements describing the dashes pattern.
> >
> >
> > -         	n += 4 * int(data[len(data)-1])
> >
> >
> > -         	data = data[:n]
> >           case opconst.TypeAux:
> >           	// An Aux operations is always wrapped in a macro, and
> >           	// its length is the remaining space.
> >
> >
> >
> > diff --git a/internal/rendertest/clip_test.go b/internal/rendertest/clip_test.go
> >
> > index efc98b7..18faf4c 100644
> >
> > --- a/internal/rendertest/clip_test.go
> >
> > +++ b/internal/rendertest/clip_test.go
> >
> > @@ -68,7 +68,10 @@ func TestPaintArc(t *testing.T) {
> >
> > p.Arc(f32.Pt(-10, -20), f32.Pt(10, -5), math.Pi)
> >
> > p.Line(f32.Pt(0, -10))
> >
> > p.Line(f32.Pt(-50, 0))
> >
> > -         p.Outline().Add(o)
> >
> >
> >
> > -         p.Close()
> >
> >
> > -         p.Op().Add(o)
> >
> >
> > -         clip.OutlineOp{}.Add(o)
> >
> >
> > -         clip.Op{}.Add(o)
> >
> >
> >
> > paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 128, 128)).Op())
> >
> > }, func(r result) {
> >
> > @@ -87,7 +90,10 @@ func TestPaintAbsolute(t *testing.T) {
> >
> > p.MoveTo(f32.Pt(20, 20))
> >
> > p.LineTo(f32.Pt(80, 20))
> >
> > p.QuadTo(f32.Pt(80, 80), f32.Pt(20, 80))
> >
> > -         p.Outline().Add(o)
> >
> >
> >
> > -         p.Close()
> >
> >
> > -         p.Op().Add(o)
> >
> >
> > -         clip.OutlineOp{}.Add(o)
> >
> >
> > -         clip.Op{}.Add(o)
> >
> >
> >
> > paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 128, 128)).Op())
> >
> > }, func(r result) {
> >
> > @@ -125,13 +131,13 @@ func TestPaintClippedTexture(t *testing.T) {
> >
> > func TestStrokedPathBevelFlat(t *testing.T) {
> >
> > run(t, func(o *op.Ops) {
> >
> > -         const width = 2.5
> >
> >
> > -         sty := clip.StrokeStyle{
> >
> >
> > -         	Cap:  clip.FlatCap,
> >
> >
> > -         	Join: clip.BevelJoin,
> >
> >
> > -         }
> >
> >
> >
> > -         newStrokedPath(o).Stroke(width, sty).Add(o)
> >
> >
> >
> > -         newStrokedPath(o).Op().Add(o)
> >
> >
> > -         clip.StrokeOp{
> >
> >
> > -         	Width: 2.5,
> >
> >
> > -         	Cap:   clip.FlatCap,
> >
> >
> > -         	Join:  clip.BevelJoin,
> >
> >
> > -         }.Add(o)
> >
> >
> > -         clip.Op{}.Add(o)
> >
> >
> >
> > paint.Fill(o, red)
> >
> > }, func(r result) {
> >
> > @@ -142,13 +148,13 @@ func TestStrokedPathBevelFlat(t *testing.T) {
> >
> > func TestStrokedPathBevelRound(t *testing.T) {
> >
> > run(t, func(o *op.Ops) {
> >
> > -         const width = 2.5
> >
> >
> > -         sty := clip.StrokeStyle{
> >
> >
> > -         	Cap:  clip.RoundCap,
> >
> >
> > -         	Join: clip.BevelJoin,
> >
> >
> > -         }
> >
> >
> >
> > -         newStrokedPath(o).Stroke(width, sty).Add(o)
> >
> >
> >
> > -         newStrokedPath(o).Op().Add(o)
> >
> >
> > -         clip.StrokeOp{
> >
> >
> > -         	Width: 2.5,
> >
> >
> > -         	Cap:   clip.RoundCap,
> >
> >
> > -         	Join:  clip.BevelJoin,
> >
> >
> > -         }.Add(o)
> >
> >
> > -         clip.Op{}.Add(o)
> >
> >
> >
> > paint.Fill(o, red)
> >
> > }, func(r result) {
> >
> > @@ -159,13 +165,13 @@ func TestStrokedPathBevelRound(t *testing.T) {
> >
> > func TestStrokedPathBevelSquare(t *testing.T) {
> >
> > run(t, func(o *op.Ops) {
> >
> > -         const width = 2.5
> >
> >
> > -         sty := clip.StrokeStyle{
> >
> >
> > -         	Cap:  clip.SquareCap,
> >
> >
> > -         	Join: clip.BevelJoin,
> >
> >
> > -         }
> >
> >
> >
> > -         newStrokedPath(o).Stroke(width, sty).Add(o)
> >
> >
> >
> > -         newStrokedPath(o).Op().Add(o)
> >
> >
> > -         clip.StrokeOp{
> >
> >
> > -         	Width: 2.5,
> >
> >
> > -         	Cap:   clip.SquareCap,
> >
> >
> > -         	Join:  clip.BevelJoin,
> >
> >
> > -         }.Add(o)
> >
> >
> > -         clip.Op{}.Add(o)
> >
> >
> >
> > paint.Fill(o, red)
> >
> > }, func(r result) {
> >
> > @@ -176,13 +182,13 @@ func TestStrokedPathBevelSquare(t *testing.T) {
> >
> > func TestStrokedPathRoundRound(t *testing.T) {
> >
> > run(t, func(o *op.Ops) {
> >
> > -         const width = 2.5
> >
> >
> > -         sty := clip.StrokeStyle{
> >
> >
> > -         	Cap:  clip.RoundCap,
> >
> >
> > -         	Join: clip.RoundJoin,
> >
> >
> > -         }
> >
> >
> >
> > -         newStrokedPath(o).Stroke(width, sty).Add(o)
> >
> >
> >
> > -         newStrokedPath(o).Op().Add(o)
> >
> >
> > -         clip.StrokeOp{
> >
> >
> > -         	Width: 2.5,
> >
> >
> > -         	Cap:   clip.RoundCap,
> >
> >
> > -         	Join:  clip.RoundJoin,
> >
> >
> > -         }.Add(o)
> >
> >
> > -         clip.Op{}.Add(o)
> >
> >
> >
> > paint.Fill(o, red)
> >
> > }, func(r result) {
> >
> > @@ -193,18 +199,29 @@ func TestStrokedPathRoundRound(t *testing.T) {
> >
> > func TestStrokedPathFlatMiter(t *testing.T) {
> >
> > run(t, func(o *op.Ops) {
> >
> > -         const width = 10
> >
> >
> > -         sty := clip.StrokeStyle{
> >
> >
> > -         	Cap:   clip.FlatCap,
> >
> >
> > -         	Join:  clip.BevelJoin,
> >
> >
> > -         	Miter: 5,
> >
> >
> >
> > -         {
> >
> >
> > -         	stk := op.Push(o)
> >
> >
> > -         	newZigZagPath(o).Op().Add(o)
> >
> >
> > -         	clip.StrokeOp{
> >
> >
> > -         		Width: 10,
> >
> >
> > -         		Cap:   clip.FlatCap,
> >
> >
> > -         		Join:  clip.BevelJoin,
> >
> >
> > -         		Miter: 5,
> >
> >
> > -         	}.Add(o)
> >
> >
> > -         	clip.Op{}.Add(o)
> >
> >
> > -         	paint.Fill(o, red)
> >
> >
> > -         	stk.Pop()
> >
> >
> > -         }
> >
> >
> > -         {
> >
> >
> > -         	stk := op.Push(o)
> >
> >
> > -         	newZigZagPath(o).Op().Add(o)
> >
> >
> > -         	clip.StrokeOp{
> >
> >
> > -         		Width: 2,
> >
> >
> > -         	}.Add(o)
> >
> >
> > -         	clip.Op{}.Add(o)
> >
> >
> > -         	paint.Fill(o, black)
> >
> >
> > -         	stk.Pop()
> >           }
> >
> >
> >
> > -         newZigZagPath(o).Stroke(width, sty).Add(o)
> >
> >
> > -         paint.Fill(o, red)
> >
> >
> >
> > -         newZigZagPath(o).Stroke(2, clip.StrokeStyle{}).Add(o)
> >
> >
> > -         paint.Fill(o, black)
> >
> >
> >
> > }, func(r result) {
> >
> > r.expect(0, 0, colornames.White)
> >
> > @@ -215,18 +232,29 @@ func TestStrokedPathFlatMiter(t *testing.T) {
> >
> > func TestStrokedPathFlatMiterInf(t *testing.T) {
> >
> > run(t, func(o *op.Ops) {
> >
> > -         const width = 10
> >
> >
> > -         sty := clip.StrokeStyle{
> >
> >
> > -         	Cap:   clip.FlatCap,
> >
> >
> > -         	Join:  clip.BevelJoin,
> >
> >
> > -         	Miter: float32(math.Inf(+1)),
> >
> >
> >
> > -         {
> >
> >
> > -         	stk := op.Push(o)
> >
> >
> > -         	newZigZagPath(o).Op().Add(o)
> >
> >
> > -         	clip.StrokeOp{
> >
> >
> > -         		Width: 10,
> >
> >
> > -         		Cap:   clip.FlatCap,
> >
> >
> > -         		Join:  clip.BevelJoin,
> >
> >
> > -         		Miter: float32(math.Inf(+1)),
> >
> >
> > -         	}.Add(o)
> >
> >
> > -         	clip.Op{}.Add(o)
> >
> >
> > -         	paint.Fill(o, red)
> >
> >
> > -         	stk.Pop()
> >
> >
> > -         }
> >
> >
> > -         {
> >
> >
> > -         	stk := op.Push(o)
> >
> >
> > -         	newZigZagPath(o).Op().Add(o)
> >
> >
> > -         	clip.StrokeOp{
> >
> >
> > -         		Width: 2,
> >
> >
> > -         	}.Add(o)
> >
> >
> > -         	clip.Op{}.Add(o)
> >
> >
> > -         	paint.Fill(o, black)
> >
> >
> > -         	stk.Pop()
> >           }
> >
> >
> >
> > -         newZigZagPath(o).Stroke(width, sty).Add(o)
> >
> >
> > -         paint.Fill(o, red)
> >
> >
> >
> > -         newZigZagPath(o).Stroke(2, clip.StrokeStyle{}).Add(o)
> >
> >
> > -         paint.Fill(o, black)
> >
> >
> >
> > }, func(r result) {
> >
> > r.expect(0, 0, colornames.White)
> >
> > @@ -237,26 +265,32 @@ func TestStrokedPathFlatMiterInf(t *testing.T) {
> >
> > func TestStrokedPathZeroWidth(t *testing.T) {
> >
> > run(t, func(o *op.Ops) {
> >
> > -         const width = 2
> >
> >
> > -         var sty clip.StrokeStyle
> >           {
> >
> >
> >
> > -         	stk := op.Push(o)
> >           	p := new(clip.Path)
> >           	p.Begin(o)
> >           	p.Move(f32.Pt(10, 50))
> >           	p.Line(f32.Pt(50, 0))
> >
> >
> >
> > -         	p.Stroke(width, sty).Add(o)
> >
> >
> >
> > -         	p.Op().Add(o)
> >
> >
> > -         	clip.StrokeOp{Width: 2}.Add(o)
> >
> >
> >
> > -         	clip.Op{}.Add(o)
> >           	paint.Fill(o, black)
> >
> >
> > -         	stk.Pop()
> >           }
> >
> >
> >
> > {
> >
> > -         	stk := op.Push(o)
> >           	p := new(clip.Path)
> >           	p.Begin(o)
> >           	p.Move(f32.Pt(10, 50))
> >           	p.Line(f32.Pt(30, 0))
> >
> >
> >
> > -         	p.Stroke(0, sty).Add(o) // width=0, disable stroke
> >
> >
> >
> > -         	p.Op().Add(o)
> >
> >
> > -         	clip.StrokeOp{}.Add(o) // width=0, disable stroke
> >
> >
> >
> > -         	clip.Op{}.Add(o)
> >           	paint.Fill(o, red)
> >
> >
> > -         	stk.Pop()
> >           }
> >
> >
> >
> > }, func(r result) {
> >
> > @@ -269,25 +303,41 @@ func TestStrokedPathZeroWidth(t *testing.T) {
> >
> > func TestDashedPathFlatCapEllipse(t *testing.T) {
> >
> > run(t, func(o *op.Ops) {
> >
> > -         const width = 10
> >
> >
> > -         sty := clip.StrokeStyle{
> >
> >
> > -         	Cap:   clip.FlatCap,
> >
> >
> > -         	Join:  clip.BevelJoin,
> >
> >
> > -         	Miter: float32(math.Inf(+1)),
> >
> >
> > -         	Line: clip.DashStyle{
> >
> >
> > -         		Dashes: []float32{5, 3},
> >
> >
> > -         	},
> >
> >
> >
> > -         {
> >
> >
> > -         	stk := op.Push(o)
> >
> >
> > -         	newEllipsePath(o).Op().Add(o)
> >
> >
> > -         	var dash clip.Dash
> >
> >
> > -         	dash.Begin(o)
> >
> >
> > -         	dash.Dash(5)
> >
> >
> > -         	dash.Dash(3)
> >
> >
> > -         	dash.Op().Add(o)
> >
> >
> >
> > -         	clip.StrokeOp{
> >
> >
> > -         		Width: 10,
> >
> >
> > -         		Cap:   clip.FlatCap,
> >
> >
> > -         		Join:  clip.BevelJoin,
> >
> >
> > -         		Miter: float32(math.Inf(+1)),
> >
> >
> > -         	}.Add(o)
> >
> >
> >
> > -         	clip.Op{}.Add(o)
> >
> >
> >
> > -         	paint.Fill(
> >
> >
> > -         		o,
> >
> >
> > -         		red,
> >
> >
> > -         	)
> >
> >
> > -         	stk.Pop()
> >
> >
> > -         }
> >
> >
> > -         {
> >
> >
> > -         	stk := op.Push(o)
> >
> >
> > -         	newEllipsePath(o).Op().Add(o)
> >
> >
> > -         	clip.StrokeOp{Width: 2}.Add(o)
> >
> >
> > -         	clip.Op{}.Add(o)
> >
> >
> > -         	paint.Fill(
> >
> >
> > -         		o,
> >
> >
> > -         		black,
> >
> >
> > -         	)
> >
> >
> > -         	stk.Pop()
> >           }
> >
> >
> >
> > -         paint.FillShape(
> >
> >
> > -         	o,
> >
> >
> > -         	colornames.Red,
> >
> >
> > -         	newEllipsePath(o).Stroke(width, sty),
> >
> >
> > -         )
> >
> >
> > -         paint.FillShape(
> >
> >
> > -         	o,
> >
> >
> > -         	colornames.Black,
> >
> >
> > -         	newEllipsePath(o).Stroke(2, clip.StrokeStyle{}),
> >
> >
> > -         )
> >
> >
> >
> > }, func(r result) {
> >
> > r.expect(0, 0, colornames.White)
> >
> > @@ -298,26 +348,33 @@ func TestDashedPathFlatCapEllipse(t *testing.T) {
> >
> > func TestDashedPathFlatCapZ(t *testing.T) {
> >
> > run(t, func(o *op.Ops) {
> >
> > -         const width = 10
> >
> >
> > -         sty := clip.StrokeStyle{
> >
> >
> > -         	Cap:   clip.FlatCap,
> >
> >
> > -         	Join:  clip.BevelJoin,
> >
> >
> > -         	Miter: float32(math.Inf(+1)),
> >
> >
> > -         	Line: clip.DashStyle{
> >
> >
> > -         		Dashes: []float32{5, 3},
> >
> >
> > -         	},
> >
> >
> >
> > -         {
> >
> >
> > -         	stk := op.Push(o)
> >
> >
> > -         	newZigZagPath(o).Op().Add(o)
> >
> >
> > -         	var dash clip.Dash
> >
> >
> > -         	dash.Begin(o)
> >
> >
> > -         	dash.Dash(5)
> >
> >
> > -         	dash.Dash(3)
> >
> >
> > -         	dash.Op().Add(o)
> >
> >
> > -         	clip.StrokeOp{
> >
> >
> > -         		Width: 10,
> >
> >
> > -         		Cap:   clip.FlatCap,
> >
> >
> > -         		Join:  clip.BevelJoin,
> >
> >
> > -         		Miter: float32(math.Inf(+1)),
> >
> >
> > -         	}.Add(o)
> >
> >
> > -         	clip.Op{}.Add(o)
> >
> >
> > -         	paint.Fill(o, red)
> >
> >
> > -         	stk.Pop()
> >           }
> >
> >
> >
> > -         paint.FillShape(
> >
> >
> > -         	o,
> >
> >
> > -         	colornames.Red,
> >
> >
> > -         	newZigZagPath(o).Stroke(width, sty),
> >
> >
> > -         )
> >
> >
> > -         paint.FillShape(
> >
> >
> > -         	o,
> >
> >
> > -         	colornames.Black,
> >
> >
> > -         	newZigZagPath(o).Stroke(2, clip.StrokeStyle{}),
> >
> >
> > -         )
> >
> >
> >
> > -         {
> >
> >
> > -         	stk := op.Push(o)
> >
> >
> > -         	newZigZagPath(o).Op().Add(o)
> >
> >
> > -         	clip.StrokeOp{Width: 2}.Add(o)
> >
> >
> > -         	clip.Op{}.Add(o)
> >
> >
> > -         	paint.Fill(o, black)
> >
> >
> > -         	stk.Pop()
> >
> >
> > -         }
> >
> >
> >     }, func(r result) {
> >
> >     r.expect(0, 0, colornames.White)
> >
> >     r.expect(40, 10, colornames.Black)
> >
> >     @@ -328,26 +385,31 @@ func TestDashedPathFlatCapZ(t *testing.T) {
> >
> >     func TestDashedPathFlatCapZNoDash(t *testing.T) {
> >
> >     run(t, func(o *op.Ops) {
> >
> > -         const width = 10
> >
> >
> > -         sty := clip.StrokeStyle{
> >
> >
> > -         	Cap:   clip.FlatCap,
> >
> >
> > -         	Join:  clip.BevelJoin,
> >
> >
> > -         	Miter: float32(math.Inf(+1)),
> >
> >
> > -         	Line: clip.DashStyle{
> >
> >
> > -         		Offset: 1,
> >
> >
> > -         	},
> >
> >
> >
> > -         {
> >
> >
> > -         	stk := op.Push(o)
> >
> >
> > -         	newZigZagPath(o).Op().Add(o)
> >
> >
> > -         	clip.StrokeOp{
> >
> >
> > -         		Width: 10,
> >
> >
> > -         		Cap:   clip.FlatCap,
> >
> >
> > -         		Join:  clip.BevelJoin,
> >
> >
> > -         		Miter: float32(math.Inf(+1)),
> >
> >
> > -         	}.Add(o)
> >
> >
> > -         	var dash clip.Dash
> >
> >
> > -         	dash.Begin(o)
> >
> >
> > -         	dash.Phase(1)
> >
> >
> > -         	dash.Op().Add(o)
> >
> >
> > -         	clip.Op{}.Add(o)
> >
> >
> > -         	paint.Fill(o, red)
> >
> >
> > -         	stk.Pop()
> >
> >
> > -         }
> >
> >
> > -         {
> >
> >
> > -         	stk := op.Push(o)
> >
> >
> > -         	newZigZagPath(o).Op().Add(o)
> >
> >
> > -         	clip.StrokeOp{Width: 2}.Add(o)
> >
> >
> > -         	clip.Op{}.Add(o)
> >
> >
> > -         	paint.Fill(o, black)
> >
> >
> > -         	stk.Pop()
> >           }
> >
> >
> >
> > -         paint.FillShape(
> >
> >
> > -         	o,
> >
> >
> > -         	colornames.Red,
> >
> >
> > -         	newZigZagPath(o).Stroke(width, sty),
> >
> >
> > -         )
> >
> >
> > -         paint.FillShape(
> >
> >
> > -         	o,
> >
> >
> > -         	colornames.Black,
> >
> >
> > -         	newZigZagPath(o).Stroke(2, clip.StrokeStyle{}),
> >
> >
> > -         )
> >
> >
> > -   }, func(r result) {
> >
> >     r.expect(0, 0, colornames.White)
> >
> >     r.expect(40, 10, colornames.Black)
> >
> >     @@ -358,26 +420,31 @@ func TestDashedPathFlatCapZNoDash(t *testing.T) {
> >
> >     func TestDashedPathFlatCapZNoPath(t *testing.T) {
> >
> >     run(t, func(o *op.Ops) {
> > -         const width = 10
> >
> >
> > -         sty := clip.StrokeStyle{
> >
> >
> > -         	Cap:   clip.FlatCap,
> >
> >
> > -         	Join:  clip.BevelJoin,
> >
> >
> > -         	Miter: float32(math.Inf(+1)),
> >
> >
> > -         	Line: clip.DashStyle{
> >
> >
> > -         		Dashes: []float32{0},
> >
> >
> > -         	},
> >
> >
> >
> > -         {
> >
> >
> > -         	stk := op.Push(o)
> >
> >
> > -         	newZigZagPath(o).Op().Add(o)
> >
> >
> > -         	clip.StrokeOp{
> >
> >
> > -         		Width: 10,
> >
> >
> > -         		Cap:   clip.FlatCap,
> >
> >
> > -         		Join:  clip.BevelJoin,
> >
> >
> > -         		Miter: float32(math.Inf(+1)),
> >
> >
> > -         	}.Add(o)
> >
> >
> > -         	var dash clip.Dash
> >
> >
> > -         	dash.Begin(o)
> >
> >
> > -         	dash.Dash(0)
> >
> >
> > -         	dash.Op().Add(o)
> >
> >
> > -         	clip.Op{}.Add(o)
> >
> >
> > -         	paint.Fill(o, red)
> >
> >
> > -         	stk.Pop()
> >
> >
> > -         }
> >
> >
> > -         {
> >
> >
> > -         	stk := op.Push(o)
> >
> >
> > -         	newZigZagPath(o).Op().Add(o)
> >
> >
> > -         	clip.StrokeOp{Width: 2}.Add(o)
> >
> >
> > -         	clip.Op{}.Add(o)
> >
> >
> > -         	paint.Fill(o, black)
> >
> >
> > -         	stk.Pop()
> >           }
> >
> >
> >
> > -         paint.FillShape(
> >
> >
> > -         	o,
> >
> >
> > -         	colornames.Red,
> >
> >
> > -         	newZigZagPath(o).Stroke(width, sty),
> >
> >
> > -         )
> >
> >
> > -         paint.FillShape(
> >
> >
> > -         	o,
> >
> >
> > -         	colornames.Black,
> >
> >
> > -         	newZigZagPath(o).Stroke(2, clip.StrokeStyle{}),
> >
> >
> > -         )
> >
> >
> > -   }, func(r result) {
> >
> >     r.expect(0, 0, colornames.White)
> >
> >     r.expect(40, 10, colornames.Black)
> >
> >     diff --git a/internal/rendertest/refs/TestStrokedPathFlatMiter.png b/internal/rendertest/refs/TestStrokedPathFlatMiter.png
> >
> >     index 95014b280da83d4b58cf51cbba059e88b1d21570..7ad77e3ac26cbe3742bff59c286895561d5dcecc 100644
> >
> >     GIT binary patch
> >
> >     delta 2246
> >
> >     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
> >
> > U`oS2KBLDyZ07qoM6N<$g0L}3aR2}S
> >
> > delta 2297
> >
> > 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 T*FHTp00000NkvXXu0mjfH;Zu( diff --git a/internal/rendertest/refs/TestStrokedPathFlatMiterInf.png b/internal/rendertest/refs/TestStrokedPathFlatMiterInf.png index d859637985a9bb897fa0cce522e98f75a1f3bba0..23a13c3b1013304c74b3b15320a9995aa346b9b8 100644 GIT binary patch delta 2250 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
> >
> > Y|Bc<h&s)CMr2qf`07qoM6N<$f~1E|oB#j-
> >
> > delta 2292
> >
> > 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
> >
> > O0000<MNUMnLSTZnC1y_m
> >
> > diff --git a/internal/rendertest/render_test.go b/internal/rendertest/render_test.go
> >
> > index 7557615..f913b21 100644
> >
> > --- a/internal/rendertest/render_test.go
> >
> > +++ b/internal/rendertest/render_test.go
> >
> > @@ -71,7 +71,10 @@ func TestRepeatedPaintsZ(t *testing.T) {
> >
> > builder.Line(f32.Pt(0, 10))
> >
> > builder.Line(f32.Pt(-10, 0))
> >
> > builder.Line(f32.Pt(0, -10))
> >
> > -         builder.Outline().Add(o)
> >
> >
> >
> > -         builder.Close()
> >
> >
> > -         builder.Op().Add(o)
> >
> >
> > -         clip.OutlineOp{}.Add(o)
> >
> >
> > -         clip.Op{}.Add(o)
> >           paint.Fill(o, red)
> >
> >
> >     }, func(r result) {
> >
> >     r.expect(5, 5, colornames.Red)
> >
> >     @@ -109,7 +112,10 @@ func constSqPath() op.CallOp {
> >
> >     builder.Line(f32.Pt(0, 10))
> >
> >     builder.Line(f32.Pt(-10, 0))
> >
> >     builder.Line(f32.Pt(0, -10))
> >
> > -   builder.Outline().Add(innerOps)
> >
> > -   builder.Close()
> > -   builder.Op().Add(innerOps)
> > -   clip.OutlineOp{}.Add(innerOps)
> > -   clip.Op{}.Add(innerOps)
> >
> >     return m.Stop()
> >
> >     }
> >
> >     diff --git a/op/clip/clip.go b/op/clip/clip.go
> >
> >     index f33ef3a..7bc659e 100644
> >
> >     --- a/op/clip/clip.go
> >
> >     +++ b/op/clip/clip.go
> >
> >     @@ -26,11 +26,26 @@ type Path struct {
> >
> >     pen f32.Point
> >
> >     macro op.MacroOp
> >
> >     start f32.Point
> > -   quads uint32
> >
> >     }
> >
> >     // Pos returns the current pen position.
> >
> >     func (p *Path) Pos() f32.Point { return p.pen }
> >
> >     +// PathOp sets the current path. Construct PathOps with Path.
> >
> >     +type PathOp struct {
> > -   call op.CallOp
> > -   quads uint32
> >
> >     +}
> >
> > +func (op PathOp) Add(o *op.Ops) {
> >
> > -   data := o.Write(opconst.TypePathLen)
> > -   data[0] = byte(opconst.TypePath)
> > -   bo := binary.LittleEndian
> > -   bo.PutUint32(data[1:], op.quads)
> > -   op.call.Add(o)
> >
> >     +}
> >
> > // Op sets the current clip to the intersection of
> >
> > // the existing clip with this clip.
> >
> > //
> >
> > @@ -39,29 +54,17 @@ func (p *Path) Pos() f32.Point { return p.pen }
> >
> > type Op struct {
> >
> > call op.CallOp
> >
> > bounds image.Rectangle
> >
> > -   width float32 // Width of the stroked path, 0 for outline paths.
> > -   style StrokeStyle // Style of the stroked path, zero for outline paths.
> >
> >     }
> >
> >     func (p Op) Add(o *op.Ops) {
> >
> >     p.call.Add(o)
> > -   data := o.Write(opconst.TypeClipLen + len(p.style.Line.Dashes)*4)
> >
> > -   data := o.Write(opconst.TypeClipLen)
> >
> >     data[0] = byte(opconst.TypeClip)
> >
> >     bo := binary.LittleEndian
> >
> >     bo.PutUint32(data[1:], uint32(p.bounds.Min.X))
> >
> >     bo.PutUint32(data[5:], uint32(p.bounds.Min.Y))
> >
> >     bo.PutUint32(data[9:], uint32(p.bounds.Max.X))
> >
> >     bo.PutUint32(data[13:], uint32(p.bounds.Max.Y))
> >
> > -   bo.PutUint32(data[17:], math.Float32bits(p.width))
> > -   data[21] = uint8(p.style.Cap)
> > -   data[22] = uint8(p.style.Join)
> > -   bo.PutUint32(data[23:], math.Float32bits(p.style.Miter))
> > -   bo.PutUint32(data[27:], math.Float32bits(p.style.Line.Offset))
> > -   data[31] = uint8(len(p.style.Line.Dashes))
> > -   dashes := data[32:]
> > -   for i, v := range p.style.Line.Dashes {
> > -         bo.PutUint32(dashes[i*4:], math.Float32bits(v))
> >
> >
> > -   }
> >
> >     }
> >
> >     // Begin the path, storing the path data and final Op into ops.
> >
> >     @@ -126,6 +129,7 @@ func (p *Path) QuadTo(ctrl, to f32.Point) {
> >
> >     To: to,
> >
> >     })
> >
> >     p.pen = to
> >
> > -   p.quads++
> >
> >     }
> >
> >     // Arc adds an elliptical arc to the path. The implied ellipse is defined
> >
> >     @@ -332,34 +336,39 @@ func (p *Path) approxCubeTo(splits int, maxDist float32, ctrl0, ctrl1, to f32.Po
> >
> >     return splits
> >
> >     }
> >
> >     -// Outline closes the path and returns a clip operation that represents it.
> >
> >     -func (p *Path) Outline() Op {
> >
> >     +// Close closes the path.
> >
> >     +func (p *Path) Close() {
> >
> >     p.end()
> >
> >     +}
> >
> > +// Op completes the path and sets the current path.
> >
> > +func (p *Path) Op() PathOp {
> >
> > c := p.macro.Stop()
> >
> > -   return Op{
> > -         call: c,
> >
> >
> >
> > -   return PathOp{
> > -         call:  c,
> >
> >
> > -         quads: p.quads,
> >
> >
> >     }
> >
> >     }
> >
> >     // Stroke returns a stroked path with the specified width
> >
> >     // and configuration.
> >
> >     // If the provided width is <= 0, the path won't be stroked.
> >
> >     -func (p *Path) Stroke(width float32, sty StrokeStyle) Op {
> >
> > -   if width <= 0 {
> > -         // Explicitly discard the macro to ignore the path.
> >
> >
> > -         p.macro.Stop()
> >
> >
> > -         return Op{
> >
> >
> > -         	call: op.Record(p.ops).Stop(),
> >
> >
> > -         }
> >
> >
> > -   }
> >
> > -   c := p.macro.Stop()
> > -   return Op{
> > -         call:  c,
> >
> >
> > -         width: width,
> >
> >
> > -         style: sty,
> >
> >
> > -   }
> >
> >     -}
> >
> >     +//func (p *Path) Stroke(width float32, sty StrokeStyle) Op {
> >
> >     +// if width <= 0 {
> >
> >     +// // Explicitly discard the macro to ignore the path.
> >
> >     +// p.macro.Stop()
> >
> >     +// return Op{
> >
> >     +// call: op.Record(p.ops).Stop(),
> >
> >     +// }
> >
> >     +// }
> >
> >     +//
> >
> >     +// c := p.macro.Stop()
> >
> >     +// return Op{
> >
> >     +// call: c,
> >
> >     +// width: width,
> >
> >     +// style: sty,
> >
> >     +// }
> >
> >     +//}
> >
> >     // Rect represents the clip area of a pixel-aligned rectangle.
> >
> >     type Rect image.Rectangle
> >
> >     diff --git a/op/clip/shapes.go b/op/clip/shapes.go
> >
> >     index c5f551d..c93184a 100644
> >
> >     --- a/op/clip/shapes.go
> >
> >     +++ b/op/clip/shapes.go
> >
> >     @@ -36,7 +36,13 @@ func (rr RRect) Op(ops *op.Ops) Op {
> >
> >     p.Begin(ops)
> >
> >     p.Move(rr.Rect.Min)
> >
> >     roundRect(&p, rr.Rect.Size(), rr.SE, rr.SW, rr.NW, rr.NE)
> > -   return p.Outline()
> >
> > -   p.Close()
> >
> > -   p.Op().Add(ops)
> >
> > -   OutlineOp{}.Add(ops)
> >
> > -   return Op{}
> >
> >     }
> >
> >     // Add the rectangle clip.
> >
> >     @@ -49,7 +55,6 @@ type Border struct {
> >
> >     // Rect is the bounds of the border.
> >
> >     Rect f32.Rectangle
> >
> >     Width float32
> >
> > -   Style StrokeStyle
> >
> >     // The corner radii.
> >
> >     SE, SW, NW, NE float32
> >
> >     }
> >
> >     @@ -58,11 +63,13 @@ type Border struct {
> >
> >     func (b Border) Op(ops *op.Ops) Op {
> >
> >     var p Path
> >
> >     p.Begin(ops)
> > -   p.Move(b.Rect.Min)
> >
> >     roundRect(&p, b.Rect.Size(), b.SE, b.SW, b.NW, b.NE)
> >
> > -   p.Close()
> >
> > -   return p.Stroke(b.Width, b.Style)
> >
> > -   p.Op().Add(ops)
> > -   StrokeOp{Width: b.Width}.Add(ops)
> > -   return Op{}
> >
> >     }
> >
> >     // Add the border clip.
> >
> >     diff --git a/op/clip/stroke.go b/op/clip/stroke.go
> >
> >     index 7bf7929..fb38242 100644
> >
> >     --- a/op/clip/stroke.go
> >
> >     +++ b/op/clip/stroke.go
> >
> >     @@ -2,20 +2,13 @@
> >
> >     package clip
> >
> >     -// StrokeStyle describes how a stroked path should be drawn.
> >
> >     -// The zero value of StrokeStyle represents bevel-joined and flat-capped
> >
> >     -// strokes with a solid line.
> >
> >     -type StrokeStyle struct {
> >
> > -   Cap StrokeCap
> > -   Join StrokeJoin
> >
> >     +import (
> >
> > -   "encoding/binary"
> > -   "math"
> >
> > -   // Miter is the limit to apply to a miter joint.
> > -   // The zero Miter disables the miter joint; setting Miter to +∞
> > -   // unconditionally enables the miter joint.
> > -   Miter float32
> >
> > -   Line DashStyle // Line defines the stroked path line.
> >
> >     -}
> >
> > -   "gioui.org/internal/opconst"
> > -   "gioui.org/op"
> >
> >     +)
> >
> >     // StrokeCap describes the head or tail of a stroked path.
> >
> >     type StrokeCap uint8
> >
> >     @@ -47,9 +40,87 @@ const (
> >
> >     RoundJoin
> >
> >     )
> >
> >     -// DashStyle describes how a stroked path line should be drawn.
> >
> >     -// DashStyle zero value draws a solid line.
> >
> >     -type DashStyle struct {
> >
> > -   Offset float32 // Offset before the dash pattern.
> > -   Dashes []float32 // Dashes is the sequence of lengths of [dash,space,...]
> >
> >     +// StrokeOp applies a stroke to the current path.
> >
> >     +type StrokeOp struct {
> >
> > -   Width float32 // Width of the stroked path, 0 for outline paths.
> >
> > -   // Miter is the limit to apply to a miter joint.
> > -   // The zero Miter disables the miter joint; setting Miter to +∞
> > -   // unconditionally enables the miter joint.
> > -   Miter float32
> > -   Cap StrokeCap
> > -   Join StrokeJoin
> >
> >     +}
> >
> > +func (op StrokeOp) Add(o *op.Ops) {
> >
> > -   data := o.Write(opconst.TypeStrokeLen)
> > -   data[0] = byte(opconst.TypeStroke)
> > -   bo := binary.LittleEndian
> > -   bo.PutUint32(data[1:], math.Float32bits(op.Width))
> > -   bo.PutUint32(data[5:], math.Float32bits(op.Miter))
> > -   data[9] = uint8(op.Cap)
> > -   data[10] = uint8(op.Join)
> >
> >     +}
> >
> > +// OutlineOp fills the current path according to the non-zero
> >
> > +// winding rule.
> >
> > +type OutlineOp struct {
> >
> > +}
> >
> > +
> >
> > +func (op OutlineOp) Add(o *op.Ops) {
> >
> > -   data := o.Write(opconst.TypeOutlineLen)
> > -   data[0] = byte(opconst.TypeOutline)
> >
> >     +}
> >
> > +// DashOp configures dashing for a stroked path. Construct a DashOp
> >
> > +// with Dashes and use it in combination with StrokeOp.
> >
> > +type DashOp struct {
> >
> > -   call op.CallOp
> > -   phase float32
> > -   size int // size of the pattern
> >
> >     +}
> >
> > +func (op DashOp) Add(o *op.Ops) {
> >
> > -   data := o.Write(opconst.TypeDashLen)
> > -   data[0] = byte(opconst.TypeDash)
> > -   bo := binary.LittleEndian
> > -   bo.PutUint32(data[1:], math.Float32bits(op.phase))
> > -   data[5] = uint8(op.size) // FIXME(sbinet) uint16? uint32?
> > -   op.call.Add(o)
> >
> >     +}
> >
> > +// Dash records dashes' lengths and phase for a stroked path.
> >
> > +type Dash struct {
> >
> > -   ops *op.Ops
> > -   macro op.MacroOp
> > -   phase float32
> > -   size int
> >
> >     +}
> >
> > +func (d *Dash) Begin(ops *op.Ops) {
> >
> > -   d.ops = ops
> > -   d.macro = op.Record(ops)
> > -   // Write the TypeAux opcode
> > -   data := ops.Write(opconst.TypeAuxLen)
> > -   data[0] = byte(opconst.TypeAux)
> >
> >     +}
> >
> > +func (d *Dash) Phase(v float32) {
> >
> > -   d.phase = v
> >
> >     +}
> >
> > +func (d *Dash) Dash(length float32) {
> >
> > -   data := d.ops.Write(4)
> > -   bo := binary.LittleEndian
> > -   bo.PutUint32(data[0:], math.Float32bits(length))
> > -   d.size++
> >
> >     +}
> >
> > +func (d *Dash) Op() DashOp {
> >
> > -   c := d.macro.Stop()
> > -   return DashOp{
> > -         call:  c,
> >
> >
> > -         phase: d.phase,
> >
> >
> > -         size:  d.size,
> >
> >
> > -   }
> >
> >     }
> >
> >     diff --git a/widget/material/loader.go b/widget/material/loader.go
> >
> >     index 48a4b6f..65efd5d 100644
> >
> >     --- a/widget/material/loader.go
> >
> >     +++ b/widget/material/loader.go
> >
> >     @@ -67,15 +67,13 @@ func clipLoader(ops *op.Ops, startAngle, endAngle, radius float64) {
> >
> >     pen = f32.Pt(float32(vx), float32(vy)).Mul(float32(radius))
> >
> >     center = f32.Pt(0, 0).Sub(pen)
> >
> > -         style = clip.StrokeStyle{
> >
> >
> > -         	Cap: clip.FlatCap,
> >
> >
> > -         }
> >
> >
> > -         p clip.Path
> >
> >
> >     )
> >
> >     p.Begin(ops)
> >
> >     p.Move(pen)
> >
> >     p.Arc(center, center, delta)
> > -   p.Stroke(width, style).Add(ops)
> >
> > -   p.Op().Add(ops)
> >
> > -   clip.StrokeOp{Width: width, Cap: clip.FlatCap}.Add(ops)
> >
> >     }
> >
> >     --
> >
> >     2.29.2

Re: [PATCH gio v3 2/2] gpu,op/clip: split xyz-ops

Details
Message ID
<TqGrOE_1IuHDZcCCCnwibgqPo-I_FIXxgUo3qBxi-53l2XW5U5ve1P5wKXJzmWdzNfwMUraZz0Whe49pVrAlv8BBv6BD1Yr-1mgcRbODtQM=@sbinet.org>
In-Reply-To
<C7DZHKVX4XOQ.3U5NCLB6F6WQ5@themachine> (view parent)
DKIM signature
missing
Download raw message
hi,

apologies for the belated answer.

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐

On Friday, November 27th, 2020 at 11:45 AM, Elias Naur <mail@eliasnaur.com> wrote:

> On Tue Nov 24, 2020 at 17:14, Sebastien Binet wrote:
>
> > hi,
> >
> > I've kept the original implementation of dashed stroked paths as a separate commit and then the split into various xyzOp structures.
> >
> > I am not completely happy with this v3 though:
> >
> > -   it's way too easy to forget a clip.xyzOp
> > -   it's a bit more verbose
> > -   I've kept the clip.Path.Close (but made a mental note of the need to get rid of it for a v4)
>
> Thanks for summarizing the issues. I'm going to use the opentype.go change
>
> as example:
>
> -   builder.Outline().Add(ops)
>
> -   builder.Close()
> -   builder.Op().Add(ops)
> -   clip.OutlineOp{}.Add(ops)
> -   clip.Op{}.Add(ops)
>
>     On the face of it, that is more verbose and easy to forget one of the
>
>     operations. However, with your point (3) in mind, and letting
>
>     "outline-mode" be the default, I think you can reduce the change to
>
>     just
>
> -   builder.Outline().Add(ops)
>
> -   builder.Op().Add(ops)
> -   clip.Op{}.Add(ops)
>
>     which is only one more line.
>
>     For filling a shape with a color there is the one-line
>
>     op/paint.FillShape function.
>
>     It's true that a stroke will need an additional operation, but that
>
>     seems fair because you need to specify the stroke parameters (width
>
>     etc.) anyway. The same applies to dashed strokes, which need even more
>
>     parameters.
>
>     Speaking of FillShape, I think its shape parameter should change to a
>
>     PathOp type, and clip.Rect.Op should return a PathOp (specialized to a
>
>     pixel-aligned rectangle).
>
>     I took a quick look at the patches; please correct me if I missed
>
>     something.

see the new v4 version.

I've kept the stack of patches pretty unmodified (but once we are happy with the API,
I'll re-order to first apply the split-op and then the dashed business.)

I think I've applied all your suggestions, except for the FillShape API change.
it wasn't obvious to me how to transfer the boundaries of the clip.Op of clip.Rect(...).Op()
into clip.PathOp (and then, presumably, back to clip.Op).

cheers,
-s

Re: [PATCH gio v3 2/2] gpu,op/clip: split xyz-ops

Details
Message ID
<C7K23YGPH4QQ.C65UZJ9GC2EL@themachine>
In-Reply-To
<TqGrOE_1IuHDZcCCCnwibgqPo-I_FIXxgUo3qBxi-53l2XW5U5ve1P5wKXJzmWdzNfwMUraZz0Whe49pVrAlv8BBv6BD1Yr-1mgcRbODtQM=@sbinet.org> (view parent)
DKIM signature
pass
Download raw message
On Fri Dec 4, 2020 at 11:57, Sebastien Binet wrote:
> hi,
>
> apologies for the belated answer.
>
> ‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
>
> On Friday, November 27th, 2020 at 11:45 AM, Elias Naur <mail@eliasnaur.com> wrote:
>
> > On Tue Nov 24, 2020 at 17:14, Sebastien Binet wrote:
> >
> > > hi,
> > >
> > > I've kept the original implementation of dashed stroked paths as a separate commit and then the split into various xyzOp structures.
> > >
> > > I am not completely happy with this v3 though:
> > >
> > > -   it's way too easy to forget a clip.xyzOp
> > > -   it's a bit more verbose
> > > -   I've kept the clip.Path.Close (but made a mental note of the need to get rid of it for a v4)
> >
> > Thanks for summarizing the issues. I'm going to use the opentype.go change
> >
> > as example:
> >
> > -   builder.Outline().Add(ops)
> >
> > -   builder.Close()
> > -   builder.Op().Add(ops)
> > -   clip.OutlineOp{}.Add(ops)
> > -   clip.Op{}.Add(ops)
> >
> >     On the face of it, that is more verbose and easy to forget one of the
> >
> >     operations. However, with your point (3) in mind, and letting
> >
> >     "outline-mode" be the default, I think you can reduce the change to
> >
> >     just
> >
> > -   builder.Outline().Add(ops)
> >
> > -   builder.Op().Add(ops)
> > -   clip.Op{}.Add(ops)
> >
> >     which is only one more line.
> >
> >     For filling a shape with a color there is the one-line
> >
> >     op/paint.FillShape function.
> >
> >     It's true that a stroke will need an additional operation, but that
> >
> >     seems fair because you need to specify the stroke parameters (width
> >
> >     etc.) anyway. The same applies to dashed strokes, which need even more
> >
> >     parameters.
> >
> >     Speaking of FillShape, I think its shape parameter should change to a
> >
> >     PathOp type, and clip.Rect.Op should return a PathOp (specialized to a
> >
> >     pixel-aligned rectangle).
> >
> >     I took a quick look at the patches; please correct me if I missed
> >
> >     something.
>
> see the new v4 version.
>
> I've kept the stack of patches pretty unmodified (but once we are happy with the API,
> I'll re-order to first apply the split-op and then the dashed business.)
>

Looks good, the important change is removing Path.Close and outline being
the default.

May I suggest another simplification: get rid of clip.Op, and
re-instate OutlineOp; then change OutlineOp and StrokeOp to imply a clip.Op.

I believe you can then avoid one op per stroke. For example,

PathOp path = ...

path.Add(o)
clip.StrokeOp{Width: 2}.Add(o) // Apply stroke style.
clip.Op{}.Add(o) // Set clip.
paint.Fill(o, black)

becomes

op.Add(o)
clip.StrokeOp{Width: 2}.Add(o) // Apply stroke style and set clip.
paint.Fill(o, black)

Filling an outline would become

path.Add(o)
clip.OutlineOp{}.Add(o)
paint.Fill(o, black)

WDYT?

> I think I've applied all your suggestions, except for the FillShape API change.
> it wasn't obvious to me how to transfer the boundaries of the clip.Op of clip.Rect(...).Op()
> into clip.PathOp (and then, presumably, back to clip.Op).
>

Let's deal with that detail after cleaning up the patchset.

Elias

Re: [PATCH gio v3 2/2] gpu,op/clip: split xyz-ops

Details
Message ID
<xh5aSqiFR2F3u1WgYKkA65h1PSgHXipdzuEjYXltW8@cp4-web-030.plabs.ch>
In-Reply-To
<C7K23YGPH4QQ.C65UZJ9GC2EL@themachine> (view parent)
DKIM signature
missing
Download raw message
On Fri Dec 4, 2020 at 3:05 PM CET, Elias Naur wrote:
> --58685a5995089de82e7a2c12a405832265c40b97770abeaae4c5dd16033e
> Content-Transfer-Encoding: quoted-printable
> Content-Type: text/plain
>
> On Fri Dec 4, 2020 at 11:57, Sebastien Binet wrote:
> > hi,
> >
> > apologies for the belated answer.
> >
> > =E2=80=90=E2=80=90=E2=80=90=E2=80=90=E2=80=90=E2=80=90=E2=80=90 Original =
> Message =E2=80=90=E2=80=90=E2=80=90=E2=80=90=E2=80=90=E2=80=90=E2=80=90
> >
> > On Friday, November 27th, 2020 at 11:45 AM, Elias Naur <mail@eliasnaur.co=
> m> wrote:
> >
> > > On Tue Nov 24, 2020 at 17:14, Sebastien Binet wrote:
> > >
> > > > hi,
> > > >
> > > > I've kept the original implementation of dashed stroked paths as a se=
> parate commit and then the split into various xyzOp structures.
> > > >
> > > > I am not completely happy with this v3 though:
> > > >
> > > > -   it's way too easy to forget a clip.xyzOp
> > > > -   it's a bit more verbose
> > > > -   I've kept the clip.Path.Close (but made a mental note of the need=
> to get rid of it for a v4)
> > >
> > > Thanks for summarizing the issues. I'm going to use the opentype.go cha=
> nge
> > >
> > > as example:
> > >
> > > -   builder.Outline().Add(ops)
> > >
> > > -   builder.Close()
> > > -   builder.Op().Add(ops)
> > > -   clip.OutlineOp{}.Add(ops)
> > > -   clip.Op{}.Add(ops)
> > >
> > >     On the face of it, that is more verbose and easy to forget one of t=
> he
> > >
> > >     operations. However, with your point (3) in mind, and letting
> > >
> > >     "outline-mode" be the default, I think you can reduce the change to
> > >
> > >     just
> > >
> > > -   builder.Outline().Add(ops)
> > >
> > > -   builder.Op().Add(ops)
> > > -   clip.Op{}.Add(ops)
> > >
> > >     which is only one more line.
> > >
> > >     For filling a shape with a color there is the one-line
> > >
> > >     op/paint.FillShape function.
> > >
> > >     It's true that a stroke will need an additional operation, but that
> > >
> > >     seems fair because you need to specify the stroke parameters (width
> > >
> > >     etc.) anyway. The same applies to dashed strokes, which need even m=
> ore
> > >
> > >     parameters.
> > >
> > >     Speaking of FillShape, I think its shape parameter should change to=
> a
> > >
> > >     PathOp type, and clip.Rect.Op should return a PathOp (specialized t=
> o a
> > >
> > >     pixel-aligned rectangle).
> > >
> > >     I took a quick look at the patches; please correct me if I missed
> > >
> > >     something.
> >
> > see the new v4 version.
> >
> > I've kept the stack of patches pretty unmodified (but once we are happy w=
> ith the API,
> > I'll re-order to first apply the split-op and then the dashed business.)
> >
>
> Looks good, the important change is removing Path.Close and outline
> being
> the default.
>
> May I suggest another simplification: get rid of clip.Op, and
> re-instate OutlineOp; then change OutlineOp and StrokeOp to imply a
> clip.Op=
> .
>
> I believe you can then avoid one op per stroke. For example,
>
> PathOp path =3D ...
>
> path.Add(o)
> clip.StrokeOp{Width: 2}.Add(o) // Apply stroke style.
> clip.Op{}.Add(o) // Set clip.
> paint.Fill(o, black)
>
> becomes
>
> op.Add(o)
> clip.StrokeOp{Width: 2}.Add(o) // Apply stroke style and set clip.
> paint.Fill(o, black)
>
> Filling an outline would become
>
> path.Add(o)
> clip.OutlineOp{}.Add(o)
> paint.Fill(o, black)
>
> WDYT?

sure.
there's one wrinkle though: with that scheme, a clip.DashOp would need
to be put on stack *before* a clip.StrokeOp{...}.Add(o).
I guess we could live with that.

-s
Reply to thread Export thread (mbox)