clip: check Path ops are not mixed with others

Multiple operations Op, such as clip.Path, cannot
be interleaved with other ops. This patch adds a
mechanism to ensure that is the case by starting
multi ops with ops.BeginMulti and ending them with
ops.EndMulti while operations are written to op.Ops
with ops.WriteMulti.
This mechanism is applied to clip.Path.

Fixes: https://todo.sr.ht/~eliasnaur/gio/336
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
This commit is contained in:
Pierre Curto
2022-01-15 18:52:51 +01:00
committed by Elias Naur
parent 2879fe8b41
commit 2ce9ad36af
3 changed files with 58 additions and 5 deletions
+24
View File
@@ -227,3 +227,27 @@ func TestPathReuse(t *testing.T) {
}, func(r result) { }, 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)
})
}
+27
View File
@@ -22,6 +22,8 @@ type Ops struct {
// nextStateID is the id allocated for the next // nextStateID is the id allocated for the next
// StateOp. // StateOp.
nextStateID int nextStateID int
// multipOp indicates a multi-op such as clip.Path is being added.
multipOp bool
macroStack stack macroStack stack
stacks [5]stack stacks [5]stack
@@ -192,6 +194,31 @@ func Reset(o *Ops) {
} }
func Write(o *Ops, n int) []byte { 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)...) o.data = append(o.data, make([]byte, n)...)
return o.data[len(o.data)-n:] return o.data[len(o.data)-n:]
} }
+7 -5
View File
@@ -126,7 +126,8 @@ func (p *Path) Begin(o *op.Ops) {
contour: 1, contour: 1,
} }
p.hash.SetSeed(pathSeed) 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) data[0] = byte(ops.TypeAux)
} }
@@ -134,6 +135,7 @@ func (p *Path) Begin(o *op.Ops) {
func (p *Path) End() PathSpec { func (p *Path) End() PathSpec {
p.gap() p.gap()
c := p.macro.Stop() c := p.macro.Stop()
ops.EndMulti(p.ops)
return PathSpec{ return PathSpec{
spec: c, spec: c,
hasSegments: p.hasSegments, hasSegments: p.hasSegments,
@@ -163,7 +165,7 @@ func (p *Path) gap() {
if p.pen != p.start { if p.pen != p.start {
// A closed contour starts and ends in the same point. // A closed contour starts and ends in the same point.
// This move creates a gap in the contour, register it. // 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 := binary.LittleEndian
bo.PutUint32(data[0:], uint32(p.contour)) bo.PutUint32(data[0:], uint32(p.contour))
p.cmd(data[4:], scene.Gap(p.pen, p.start)) 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. // LineTo moves the pen to the absolute point specified, recording a line.
func (p *Path) LineTo(to f32.Point) { 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 := binary.LittleEndian
bo.PutUint32(data[0:], uint32(p.contour)) bo.PutUint32(data[0:], uint32(p.contour))
p.cmd(data[4:], scene.Line(p.pen, to)) 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 // QuadTo records a quadratic Bézier from the pen to end
// with the control point ctrl, with absolute coordinates. // with the control point ctrl, with absolute coordinates.
func (p *Path) QuadTo(ctrl, to f32.Point) { 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 := binary.LittleEndian
bo.PutUint32(data[0:], uint32(p.contour)) bo.PutUint32(data[0:], uint32(p.contour))
p.cmd(data[4:], scene.Quad(p.pen, ctrl, to)) 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 { if ctrl0 == p.pen && ctrl1 == p.pen && to == p.pen {
return return
} }
data := ops.Write(p.ops, scene.CommandSize+4) data := ops.WriteMulti(p.ops, scene.CommandSize+4)
bo := binary.LittleEndian bo := binary.LittleEndian
bo.PutUint32(data[0:], uint32(p.contour)) bo.PutUint32(data[0:], uint32(p.contour))
p.cmd(data[4:], scene.Cubic(p.pen, ctrl0, ctrl1, to)) p.cmd(data[4:], scene.Cubic(p.pen, ctrl0, ctrl1, to))