diff --git a/gpu/internal/rendertest/clip_test.go b/gpu/internal/rendertest/clip_test.go index 0820639f..4275a7cf 100644 --- a/gpu/internal/rendertest/clip_test.go +++ b/gpu/internal/rendertest/clip_test.go @@ -227,3 +227,27 @@ func TestPathReuse(t *testing.T) { }, func(r result) { }) } + +func TestPathInterleave(t *testing.T) { + t.Run("interleave op in clip.Path", func(t *testing.T) { + defer func() { + if err := recover(); err == nil { + t.Error("expected panic did not occur") + } + }() + ops := new(op.Ops) + var path clip.Path + path.Begin(ops) + path.LineTo(f32.Point{X: 123, Y: 456}) + paint.ColorOp{}.Add(ops) + path.End() + }) + t.Run("use ops after clip.Path", func(t *testing.T) { + ops := new(op.Ops) + var path clip.Path + path.Begin(ops) + path.LineTo(f32.Point{X: 123, Y: 456}) + path.End() + paint.ColorOp{}.Add(ops) + }) +} diff --git a/internal/ops/ops.go b/internal/ops/ops.go index fece6d6a..ce076947 100644 --- a/internal/ops/ops.go +++ b/internal/ops/ops.go @@ -22,6 +22,8 @@ type Ops struct { // nextStateID is the id allocated for the next // StateOp. nextStateID int + // multipOp indicates a multi-op such as clip.Path is being added. + multipOp bool macroStack stack stacks [5]stack @@ -192,6 +194,31 @@ func Reset(o *Ops) { } func Write(o *Ops, n int) []byte { + if o.multipOp { + panic("cannot mix multi ops with single ones") + } + o.data = append(o.data, make([]byte, n)...) + return o.data[len(o.data)-n:] +} + +func BeginMulti(o *Ops) { + if o.multipOp { + panic("cannot interleave multi ops") + } + o.multipOp = true +} + +func EndMulti(o *Ops) { + if !o.multipOp { + panic("cannot end non multi ops") + } + o.multipOp = false +} + +func WriteMulti(o *Ops, n int) []byte { + if !o.multipOp { + panic("cannot use multi ops in single ops") + } o.data = append(o.data, make([]byte, n)...) return o.data[len(o.data)-n:] } diff --git a/op/clip/clip.go b/op/clip/clip.go index 5b4dad09..c94e1a0b 100644 --- a/op/clip/clip.go +++ b/op/clip/clip.go @@ -126,7 +126,8 @@ func (p *Path) Begin(o *op.Ops) { contour: 1, } p.hash.SetSeed(pathSeed) - data := ops.Write(p.ops, ops.TypeAuxLen) + ops.BeginMulti(p.ops) + data := ops.WriteMulti(p.ops, ops.TypeAuxLen) data[0] = byte(ops.TypeAux) } @@ -134,6 +135,7 @@ func (p *Path) Begin(o *op.Ops) { func (p *Path) End() PathSpec { p.gap() c := p.macro.Stop() + ops.EndMulti(p.ops) return PathSpec{ spec: c, hasSegments: p.hasSegments, @@ -163,7 +165,7 @@ func (p *Path) gap() { if p.pen != p.start { // A closed contour starts and ends in the same point. // This move creates a gap in the contour, register it. - data := ops.Write(p.ops, scene.CommandSize+4) + data := ops.WriteMulti(p.ops, scene.CommandSize+4) bo := binary.LittleEndian bo.PutUint32(data[0:], uint32(p.contour)) p.cmd(data[4:], scene.Gap(p.pen, p.start)) @@ -183,7 +185,7 @@ func (p *Path) Line(delta f32.Point) { // LineTo moves the pen to the absolute point specified, recording a line. func (p *Path) LineTo(to f32.Point) { - data := ops.Write(p.ops, scene.CommandSize+4) + data := ops.WriteMulti(p.ops, scene.CommandSize+4) bo := binary.LittleEndian bo.PutUint32(data[0:], uint32(p.contour)) p.cmd(data[4:], scene.Line(p.pen, to)) @@ -251,7 +253,7 @@ func (p *Path) Quad(ctrl, to f32.Point) { // QuadTo records a quadratic Bézier from the pen to end // with the control point ctrl, with absolute coordinates. func (p *Path) QuadTo(ctrl, to f32.Point) { - data := ops.Write(p.ops, scene.CommandSize+4) + data := ops.WriteMulti(p.ops, scene.CommandSize+4) bo := binary.LittleEndian bo.PutUint32(data[0:], uint32(p.contour)) p.cmd(data[4:], scene.Quad(p.pen, ctrl, to)) @@ -297,7 +299,7 @@ func (p *Path) CubeTo(ctrl0, ctrl1, to f32.Point) { if ctrl0 == p.pen && ctrl1 == p.pen && to == p.pen { return } - data := ops.Write(p.ops, scene.CommandSize+4) + data := ops.WriteMulti(p.ops, scene.CommandSize+4) bo := binary.LittleEndian bo.PutUint32(data[0:], uint32(p.contour)) p.cmd(data[4:], scene.Cubic(p.pen, ctrl0, ctrl1, to))