From 0c145b381568d3059f46cc662063d35a43815a81 Mon Sep 17 00:00:00 2001 From: Walter Werner SCHNEIDER Date: Tue, 15 Jul 2025 18:40:31 +0300 Subject: [PATCH] op/clip: add bounds expansion after move After moving the pen, the next action should update the bounds for the start position. References: https://todo.sr.ht/~eliasnaur/gio/451 Signed-off-by: Walter Werner SCHNEIDER --- op/clip/clip.go | 9 ++- op/clip/clip_internal_test.go | 106 ++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 op/clip/clip_internal_test.go diff --git a/op/clip/clip.go b/op/clip/clip.go index 81b7861f..6d70ec44 100644 --- a/op/clip/clip.go +++ b/op/clip/clip.go @@ -214,8 +214,9 @@ func (p *Path) LineTo(to f32.Point) { bo := binary.LittleEndian bo.PutUint32(data[0:], uint32(p.contour)) p.cmd(data[4:], scene.Line(p.pen, to)) - p.pen = to + p.expand(p.pen) p.expand(to) + p.pen = to } func (p *Path) cmd(data []byte, c scene.Command) { @@ -263,9 +264,10 @@ func (p *Path) QuadTo(ctrl, to f32.Point) { bo := binary.LittleEndian bo.PutUint32(data[0:], uint32(p.contour)) p.cmd(data[4:], scene.Quad(p.pen, ctrl, to)) - p.pen = to + p.expand(p.pen) p.expand(ctrl) p.expand(to) + p.pen = to } // ArcTo adds an elliptical arc to the path. The implied ellipse is defined @@ -307,10 +309,11 @@ func (p *Path) CubeTo(ctrl0, ctrl1, to f32.Point) { bo := binary.LittleEndian bo.PutUint32(data[0:], uint32(p.contour)) p.cmd(data[4:], scene.Cubic(p.pen, ctrl0, ctrl1, to)) - p.pen = to + p.expand(p.pen) p.expand(ctrl0) p.expand(ctrl1) p.expand(to) + p.pen = to } // Close closes the path contour. diff --git a/op/clip/clip_internal_test.go b/op/clip/clip_internal_test.go new file mode 100644 index 00000000..05484dbd --- /dev/null +++ b/op/clip/clip_internal_test.go @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +package clip + +import ( + "gioui.org/f32" + "gioui.org/op" + "math" + "testing" +) + +func TestPath_MoveTo_LineTo(t *testing.T) { + var ops op.Ops + p := Path{} + p.Begin(&ops) + startPoint := f32.Pt(32, 32) + endPoint := f32.Pt(64, 64) + p.MoveTo(startPoint) + p.LineTo(endPoint) + pathSpec := p.End() + + minPoint := f32.Pt(32, 32) + maxPoint := f32.Pt(64, 64) + if pathSpec.bounds.Min == pathSpec.bounds.Max { + t.Errorf("zero path") + } + if pathSpec.bounds.Min != minPoint.Round() { + t.Errorf("pathSpec.bounds.Min = %v, want %v", pathSpec.bounds.Min, minPoint) + } + if pathSpec.bounds.Max != maxPoint.Round() { + t.Errorf("pathSpec.bounds.Max = %v, want %v", pathSpec.bounds.Max, maxPoint) + } +} + +func TestPath_MoveTo_QuadTo(t *testing.T) { + var ops op.Ops + p := Path{} + p.Begin(&ops) + startPoint := f32.Pt(32, 32) + midPoint := f32.Pt(60, 60) + p.MoveTo(startPoint) + p.QuadTo(midPoint.Sub(f32.Pt(-4, 0)), midPoint.Sub(f32.Pt(0, -4))) + pathSpec := p.End() + + minPoint := f32.Pt(32, 32) + maxPoint := f32.Pt(64, 64) + if pathSpec.bounds.Min == pathSpec.bounds.Max { + t.Errorf("zero path") + } + if pathSpec.bounds.Min != minPoint.Round() { + t.Errorf("pathSpec.bounds.Min = %v, want %v", pathSpec.bounds.Min, minPoint) + } + if pathSpec.bounds.Max != maxPoint.Round() { + t.Errorf("pathSpec.bounds.Max = %v, want %v", pathSpec.bounds.Max, maxPoint) + } +} + +func TestPath_MoveTo_ArcTo(t *testing.T) { + // We need a tolerance here because of rounding errors. + tolerance := f32.Pt(1, 1) + + var ops op.Ops + p := Path{} + p.Begin(&ops) + arcStartPoint := f32.Pt(48, 32) + arcCenterPoint := f32.Pt(48, 48) + p.MoveTo(arcStartPoint) + p.ArcTo(arcCenterPoint, arcCenterPoint, math.Pi*2) + pathSpec := p.End() + + minPoint := f32.Pt(32, 32).Sub(tolerance).Round() + maxPoint := f32.Pt(64, 64).Add(tolerance).Round() + if pathSpec.bounds.Min == pathSpec.bounds.Max { + t.Errorf("zero path") + } + if pathSpec.bounds.Min.X < minPoint.X || pathSpec.bounds.Min.Y < minPoint.Y { + t.Errorf("pathSpec.bounds.Min = %v, want %v", pathSpec.bounds.Min, minPoint) + } + if pathSpec.bounds.Max.X > maxPoint.X || pathSpec.bounds.Max.Y > maxPoint.Y { + t.Errorf("pathSpec.bounds.Max = %v, want %v", pathSpec.bounds.Max, maxPoint) + } +} + +func TestPath_MoveTo_CubeTo(t *testing.T) { + var ops op.Ops + p := Path{} + p.Begin(&ops) + startPoint := f32.Pt(32, 32) + midPoint := f32.Pt(48, 48) + endPoint := f32.Pt(64, 64) + p.MoveTo(startPoint) + p.CubeTo(midPoint.Sub(f32.Pt(-4, 0)), midPoint.Sub(f32.Pt(0, -4)), endPoint) + pathSpec := p.End() + + minPoint := f32.Pt(32, 32) + maxPoint := f32.Pt(64, 64) + if pathSpec.bounds.Min == pathSpec.bounds.Max { + t.Errorf("zero path") + } + if pathSpec.bounds.Min != minPoint.Round() { + t.Errorf("pathSpec.bounds.Min = %v, want %v", pathSpec.bounds.Min, minPoint) + } + if pathSpec.bounds.Max != maxPoint.Round() { + t.Errorf("pathSpec.bounds.Max = %v, want %v", pathSpec.bounds.Max, maxPoint) + } +}