mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-08 02:45:38 +00:00
all: introduce Outline and Stroke builders
This CL introduces 2 new path builders:
- Outline which takes a PathSpec to be outlined
- Stroke which takes a PathSpec and a stroke style, to stroke a path.
typically, code like this:
var p clip.Path
...
p.Outline().Add(o)
should be replaced with:
var p clip.Path
...
clip.Outline{Path: p.End()}.Op().Add(o)
similarly, stroking should be modified from:
var p clip.Path
...
p.Stroke(width, clip.StrokeStyle{...}).Add(o)
to:
var p clip.Path
...
clip.Stroke{Path: p.End(), Style: clip.StrokeStyle{Width:...}}.Op().Add(o)
here are tentative 'rf' scripts (see rsc.io/rf for more details):
```
ex {
import "gioui.org/op";
import "gioui.org/op/clip";
var p clip.Path;
var o *op.Ops;
p.Outline().Add(o) -> clip.Outline{Path:p.End()}.Op().Add(o);
}
ex {
import "gioui.org/op";
import "gioui.org/op/clip";
var o *op.Ops;
var p clip.Path;
var sty clip.StrokeStyle;
var width float32;
p.Stroke(width, sty).Add(o) -> \
clip.Stroke{ \
Path:p.End(), \
Style: clip.StrokeStyle{ \
Width: width, \
}}.Op().Add(o);
}
```
Signed-off-by: Sebastien Binet <s@sbinet.org>
This commit is contained in:
committed by
Elias Naur
parent
7c5bcd3db8
commit
be89f8b945
@@ -323,7 +323,9 @@ func textPath(buf *sfnt.Buffer, ppem fixed.Int26_6, fonts []*opentype, str text.
|
|||||||
x += str.Advances[rune]
|
x += str.Advances[rune]
|
||||||
rune++
|
rune++
|
||||||
}
|
}
|
||||||
builder.Outline().Add(ops)
|
clip.Outline{
|
||||||
|
Path: builder.End(),
|
||||||
|
}.Op().Add(ops)
|
||||||
return m.Stop()
|
return m.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+79
-40
@@ -109,6 +109,35 @@ type imageOp struct {
|
|||||||
place placement
|
place placement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeStrokeOp(data []byte) clip.StrokeStyle {
|
||||||
|
_ = data[10]
|
||||||
|
if opconst.OpType(data[0]) != opconst.TypeStroke {
|
||||||
|
panic("invalid op")
|
||||||
|
}
|
||||||
|
bo := binary.LittleEndian
|
||||||
|
return clip.StrokeStyle{
|
||||||
|
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 {
|
type material struct {
|
||||||
material materialType
|
material materialType
|
||||||
opaque bool
|
opaque bool
|
||||||
@@ -125,9 +154,8 @@ type material struct {
|
|||||||
// clipOp is the shadow of clip.Op.
|
// clipOp is the shadow of clip.Op.
|
||||||
type clipOp struct {
|
type clipOp struct {
|
||||||
// TODO: Use image.Rectangle?
|
// TODO: Use image.Rectangle?
|
||||||
bounds f32.Rectangle
|
bounds f32.Rectangle
|
||||||
width float32
|
outline bool
|
||||||
style clip.StrokeStyle
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// imageOpData is the shadow of paint.ImageOp.
|
// imageOpData is the shadow of paint.ImageOp.
|
||||||
@@ -159,13 +187,8 @@ func (op *clipOp) decode(data []byte) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
*op = clipOp{
|
*op = clipOp{
|
||||||
bounds: layout.FRect(r),
|
bounds: layout.FRect(r),
|
||||||
width: math.Float32frombits(bo.Uint32(data[17:])),
|
outline: data[17] == 1,
|
||||||
style: clip.StrokeStyle{
|
|
||||||
Cap: clip.StrokeCap(data[21]),
|
|
||||||
Join: clip.StrokeJoin(data[22]),
|
|
||||||
Miter: math.Float32frombits(bo.Uint32(data[23:])),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -786,8 +809,10 @@ func splitTransform(t f32.Affine2D) (srs f32.Affine2D, offset f32.Point) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *drawOps) collectOps(r *ops.Reader, state drawState) int {
|
func (d *drawOps) collectOps(r *ops.Reader, state drawState) int {
|
||||||
var aux []byte
|
var (
|
||||||
var auxKey ops.Key
|
quads quadsOp
|
||||||
|
stroke clip.StrokeStyle
|
||||||
|
)
|
||||||
loop:
|
loop:
|
||||||
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
|
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
|
||||||
switch opconst.OpType(encOp.Data[0]) {
|
switch opconst.OpType(encOp.Data[0]) {
|
||||||
@@ -796,38 +821,51 @@ loop:
|
|||||||
case opconst.TypeTransform:
|
case opconst.TypeTransform:
|
||||||
dop := ops.DecodeTransform(encOp.Data)
|
dop := ops.DecodeTransform(encOp.Data)
|
||||||
state.t = state.t.Mul(dop)
|
state.t = state.t.Mul(dop)
|
||||||
case opconst.TypeAux:
|
|
||||||
aux = encOp.Data[opconst.TypeAuxLen:]
|
case opconst.TypeStroke:
|
||||||
auxKey = encOp.Key
|
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:
|
case opconst.TypeClip:
|
||||||
var op clipOp
|
var op clipOp
|
||||||
op.decode(encOp.Data)
|
op.decode(encOp.Data)
|
||||||
bounds := op.bounds
|
bounds := op.bounds
|
||||||
trans, off := splitTransform(state.t)
|
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
|
// 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
|
// cache key such that it will be equal only if the transform is the
|
||||||
// same also. Use cached data if we have it.
|
// same also. Use cached data if we have it.
|
||||||
auxKey = auxKey.SetTransform(trans)
|
quads.key = quads.key.SetTransform(trans)
|
||||||
if v, ok := d.pathCache.get(auxKey); ok {
|
if v, ok := d.pathCache.get(quads.key); ok {
|
||||||
// Since the GPU data exists in the cache aux will not be used.
|
// Since the GPU data exists in the cache aux will not be used.
|
||||||
// Why is this not used for the offset shapes?
|
// Why is this not used for the offset shapes?
|
||||||
op.bounds = v.bounds
|
op.bounds = v.bounds
|
||||||
} else {
|
} else {
|
||||||
aux, op.bounds = d.buildVerts(aux, trans, op.width, op.style)
|
quads.aux, op.bounds = d.buildVerts(quads.aux, trans, op.outline, stroke)
|
||||||
// add it to the cache, without GPU data, so the transform can be
|
// add it to the cache, without GPU data, so the transform can be
|
||||||
// reused.
|
// reused.
|
||||||
d.pathCache.put(auxKey, opCacheValue{bounds: op.bounds})
|
d.pathCache.put(quads.key, opCacheValue{bounds: op.bounds})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
aux, op.bounds, _ = d.boundsForTransformedRect(bounds, trans)
|
quads.aux, op.bounds, _ = d.boundsForTransformedRect(bounds, trans)
|
||||||
auxKey = encOp.Key
|
quads.key = encOp.Key
|
||||||
auxKey.SetTransform(trans)
|
quads.key.SetTransform(trans)
|
||||||
}
|
}
|
||||||
state.clip = state.clip.Intersect(op.bounds.Add(off))
|
state.clip = state.clip.Intersect(op.bounds.Add(off))
|
||||||
d.addClipPath(&state, aux, auxKey, op.bounds, off)
|
d.addClipPath(&state, quads.aux, quads.key, op.bounds, off)
|
||||||
aux = nil
|
quads = quadsOp{}
|
||||||
auxKey = ops.Key{}
|
stroke = clip.StrokeStyle{}
|
||||||
|
|
||||||
case opconst.TypeColor:
|
case opconst.TypeColor:
|
||||||
state.matType = materialColor
|
state.matType = materialColor
|
||||||
state.color = decodeColorOp(encOp.Data)
|
state.color = decodeColorOp(encOp.Data)
|
||||||
@@ -1213,7 +1251,7 @@ func (d *drawOps) writeVertCache(n int) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// transform, split paths as needed, calculate maxY, bounds and create GPU vertices.
|
// 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.StrokeStyle) (verts []byte, bounds f32.Rectangle) {
|
||||||
inf := float32(math.Inf(+1))
|
inf := float32(math.Inf(+1))
|
||||||
d.qs.bounds = f32.Rectangle{
|
d.qs.bounds = f32.Rectangle{
|
||||||
Min: f32.Point{X: inf, Y: inf},
|
Min: f32.Point{X: inf, Y: inf},
|
||||||
@@ -1224,18 +1262,7 @@ func (d *drawOps) buildVerts(aux []byte, tr f32.Affine2D, width float32, sty cli
|
|||||||
startLength := len(d.vertCache)
|
startLength := len(d.vertCache)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
default:
|
case stroke.Width > 0:
|
||||||
// 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:
|
|
||||||
// Stroke path.
|
// Stroke path.
|
||||||
quads := make(strokeQuads, 0, 2*len(aux)/(ops.QuadSize+4))
|
quads := make(strokeQuads, 0, 2*len(aux)/(ops.QuadSize+4))
|
||||||
for qi := 0; len(aux) >= (ops.QuadSize + 4); qi++ {
|
for qi := 0; len(aux) >= (ops.QuadSize + 4); qi++ {
|
||||||
@@ -1246,13 +1273,25 @@ func (d *drawOps) buildVerts(aux []byte, tr f32.Affine2D, width float32, sty cli
|
|||||||
quads = append(quads, quad)
|
quads = append(quads, quad)
|
||||||
aux = aux[ops.QuadSize+4:]
|
aux = aux[ops.QuadSize+4:]
|
||||||
}
|
}
|
||||||
quads = quads.stroke(width, sty)
|
quads = quads.stroke(stroke)
|
||||||
for _, quad := range quads {
|
for _, quad := range quads {
|
||||||
d.qs.contour = quad.contour
|
d.qs.contour = quad.contour
|
||||||
quad.quad = quad.quad.Transform(tr)
|
quad.quad = quad.quad.Transform(tr)
|
||||||
|
|
||||||
d.qs.splitAndEncode(quad.quad)
|
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:])
|
fillMaxY(d.vertCache[startLength:])
|
||||||
|
|||||||
+21
-20
@@ -106,14 +106,14 @@ func (qs strokeQuads) split() []strokeQuads {
|
|||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qs strokeQuads) stroke(width float32, sty clip.StrokeStyle) strokeQuads {
|
func (qs strokeQuads) stroke(stroke clip.StrokeStyle) strokeQuads {
|
||||||
var (
|
var (
|
||||||
o strokeQuads
|
o strokeQuads
|
||||||
hw = 0.5 * width
|
hw = 0.5 * stroke.Width
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, ps := range qs.split() {
|
for _, ps := range qs.split() {
|
||||||
rhs, lhs := ps.offset(hw, sty)
|
rhs, lhs := ps.offset(hw, stroke)
|
||||||
switch lhs {
|
switch lhs {
|
||||||
case nil:
|
case nil:
|
||||||
o = o.append(rhs)
|
o = o.append(rhs)
|
||||||
@@ -132,13 +132,14 @@ func (qs strokeQuads) stroke(width float32, sty clip.StrokeStyle) strokeQuads {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
// offset returns the right-hand and left-hand sides of the path, offset by
|
// offset returns the right-hand and left-hand sides of the path, offset by
|
||||||
// the half-width hw.
|
// the half-width hw.
|
||||||
// The stroke style sty handles how segments are joined and ends are capped.
|
// The stroke 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, stroke clip.StrokeStyle) (rhs, lhs strokeQuads) {
|
||||||
var (
|
var (
|
||||||
states []strokeState
|
states []strokeState
|
||||||
beg = qs[0].quad.From
|
beg = qs[0].quad.From
|
||||||
@@ -180,7 +181,7 @@ func (qs strokeQuads) offset(hw float32, sty clip.StrokeStyle) (rhs, lhs strokeQ
|
|||||||
next = states[0]
|
next = states[0]
|
||||||
}
|
}
|
||||||
if state.n1 != next.n0 {
|
if state.n1 != next.n0 {
|
||||||
strokePathJoin(sty, &rhs, &lhs, hw, state.p1, state.n1, next.n0, state.r1, next.r0)
|
strokePathJoin(stroke, &rhs, &lhs, hw, state.p1, state.n1, next.n0, state.r1, next.r0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,10 +197,10 @@ func (qs strokeQuads) offset(hw float32, sty clip.StrokeStyle) (rhs, lhs strokeQ
|
|||||||
|
|
||||||
// Default to counter-clockwise direction.
|
// Default to counter-clockwise direction.
|
||||||
lhs = lhs.reverse()
|
lhs = lhs.reverse()
|
||||||
strokePathCap(sty, &rhs, hw, qend.p1, qend.n1)
|
strokePathCap(stroke, &rhs, hw, qend.p1, qend.n1)
|
||||||
|
|
||||||
rhs = rhs.append(lhs)
|
rhs = rhs.append(lhs)
|
||||||
strokePathCap(sty, &rhs, hw, qbeg.p0, qbeg.n0.Mul(-1))
|
strokePathCap(stroke, &rhs, hw, qbeg.p0, qbeg.n0.Mul(-1))
|
||||||
|
|
||||||
rhs.close()
|
rhs.close()
|
||||||
|
|
||||||
@@ -421,13 +422,13 @@ 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
|
// strokePathJoin joins the two paths rhs and lhs, according to the provided
|
||||||
// stroke style sty.
|
// stroke operation.
|
||||||
func strokePathJoin(sty clip.StrokeStyle, rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
|
func strokePathJoin(stroke clip.StrokeStyle, rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
|
||||||
if sty.Miter > 0 {
|
if stroke.Miter > 0 {
|
||||||
strokePathMiterJoin(sty, rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
strokePathMiterJoin(stroke, rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch sty.Join {
|
switch stroke.Join {
|
||||||
case clip.BevelJoin:
|
case clip.BevelJoin:
|
||||||
strokePathBevelJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
strokePathBevelJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
||||||
case clip.RoundJoin:
|
case clip.RoundJoin:
|
||||||
@@ -468,14 +469,14 @@ 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(stroke clip.StrokeStyle, rhs, lhs *strokeQuads, hw float32, pivot, n0, n1 f32.Point, r0, r1 float32) {
|
||||||
if n0 == n1.Mul(-1) {
|
if n0 == n1.Mul(-1) {
|
||||||
strokePathBevelJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
strokePathBevelJoin(rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is to handle nearly linear joints that would be clipped otherwise.
|
// This is to handle nearly linear joints that would be clipped otherwise.
|
||||||
limit := math.Max(float64(sty.Miter), 1.001)
|
limit := math.Max(float64(stroke.Miter), 1.001)
|
||||||
|
|
||||||
cw := dotPt(rot90CW(n0), n1) >= 0.0
|
cw := dotPt(rot90CW(n0), n1) >= 0.0
|
||||||
if cw {
|
if cw {
|
||||||
@@ -489,8 +490,8 @@ func strokePathMiterJoin(sty clip.StrokeStyle, rhs, lhs *strokeQuads, hw float32
|
|||||||
cos := math.Sqrt(0.5 * (1 + float64(cosPt(n0, n1))))
|
cos := math.Sqrt(0.5 * (1 + float64(cosPt(n0, n1))))
|
||||||
d := hw64 / cos
|
d := hw64 / cos
|
||||||
if math.Abs(limit*hw64) < math.Abs(d) {
|
if math.Abs(limit*hw64) < math.Abs(d) {
|
||||||
sty.Miter = 0 // Set miter to zero to disable the miter joint.
|
stroke.Miter = 0 // Set miter to zero to disable the miter joint.
|
||||||
strokePathJoin(sty, rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
strokePathJoin(stroke, rhs, lhs, hw, pivot, n0, n1, r0, r1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mid := pivot.Add(normPt(n0.Add(n1), float32(d)))
|
mid := pivot.Add(normPt(n0.Add(n1), float32(d)))
|
||||||
@@ -509,9 +510,9 @@ func strokePathMiterJoin(sty clip.StrokeStyle, rhs, lhs *strokeQuads, hw float32
|
|||||||
lhs.lineTo(lp)
|
lhs.lineTo(lp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// strokePathCap caps the provided path qs, according to the provided stroke style sty.
|
// strokePathCap caps the provided path qs, according to the provided stroke operation.
|
||||||
func strokePathCap(sty clip.StrokeStyle, qs *strokeQuads, hw float32, pivot, n0 f32.Point) {
|
func strokePathCap(stroke clip.StrokeStyle, qs *strokeQuads, hw float32, pivot, n0 f32.Point) {
|
||||||
switch sty.Cap {
|
switch stroke.Cap {
|
||||||
case clip.FlatCap:
|
case clip.FlatCap:
|
||||||
strokePathFlatCap(qs, hw, pivot, n0)
|
strokePathFlatCap(qs, hw, pivot, n0)
|
||||||
case clip.SquareCap:
|
case clip.SquareCap:
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ const (
|
|||||||
TypeClip
|
TypeClip
|
||||||
TypeProfile
|
TypeProfile
|
||||||
TypeCursor
|
TypeCursor
|
||||||
|
TypePath
|
||||||
|
TypeStroke
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -54,9 +56,11 @@ const (
|
|||||||
TypePushLen = 1
|
TypePushLen = 1
|
||||||
TypePopLen = 1
|
TypePopLen = 1
|
||||||
TypeAuxLen = 1
|
TypeAuxLen = 1
|
||||||
TypeClipLen = 1 + 4*4 + 4 + 2 + 4
|
TypeClipLen = 1 + 4*4 + 1
|
||||||
TypeProfileLen = 1
|
TypeProfileLen = 1
|
||||||
TypeCursorLen = 1 + 1
|
TypeCursorLen = 1 + 1
|
||||||
|
TypePathLen = 1 + 4
|
||||||
|
TypeStrokeLen = 1 + 4 + 4 + 1 + 1
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t OpType) Size() int {
|
func (t OpType) Size() int {
|
||||||
@@ -84,6 +88,8 @@ func (t OpType) Size() int {
|
|||||||
TypeClipLen,
|
TypeClipLen,
|
||||||
TypeProfileLen,
|
TypeProfileLen,
|
||||||
TypeCursorLen,
|
TypeCursorLen,
|
||||||
|
TypePathLen,
|
||||||
|
TypeStrokeLen,
|
||||||
}[t-firstOpIndex]
|
}[t-firstOpIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+132
-132
@@ -68,7 +68,9 @@ func TestPaintArc(t *testing.T) {
|
|||||||
p.Arc(f32.Pt(-10, -20), f32.Pt(10, -5), math.Pi)
|
p.Arc(f32.Pt(-10, -20), f32.Pt(10, -5), math.Pi)
|
||||||
p.Line(f32.Pt(0, -10))
|
p.Line(f32.Pt(0, -10))
|
||||||
p.Line(f32.Pt(-50, 0))
|
p.Line(f32.Pt(-50, 0))
|
||||||
p.Outline().Add(o)
|
clip.Outline{
|
||||||
|
Path: p.End(),
|
||||||
|
}.Op().Add(o)
|
||||||
|
|
||||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 128, 128)).Op())
|
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 128, 128)).Op())
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -87,7 +89,9 @@ func TestPaintAbsolute(t *testing.T) {
|
|||||||
p.MoveTo(f32.Pt(20, 20))
|
p.MoveTo(f32.Pt(20, 20))
|
||||||
p.LineTo(f32.Pt(80, 20))
|
p.LineTo(f32.Pt(80, 20))
|
||||||
p.QuadTo(f32.Pt(80, 80), f32.Pt(20, 80))
|
p.QuadTo(f32.Pt(80, 80), f32.Pt(20, 80))
|
||||||
p.Outline().Add(o)
|
clip.Outline{
|
||||||
|
Path: p.End(),
|
||||||
|
}.Op().Add(o)
|
||||||
|
|
||||||
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 128, 128)).Op())
|
paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 128, 128)).Op())
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -125,23 +129,15 @@ func TestPaintClippedTexture(t *testing.T) {
|
|||||||
|
|
||||||
func TestStrokedPathBevelFlat(t *testing.T) {
|
func TestStrokedPathBevelFlat(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
const width = 2.5
|
p := newStrokedPath(o)
|
||||||
sty := clip.StrokeStyle{
|
clip.Stroke{
|
||||||
Cap: clip.FlatCap,
|
Path: p,
|
||||||
Join: clip.BevelJoin,
|
Style: clip.StrokeStyle{
|
||||||
}
|
Width: 2.5,
|
||||||
|
Cap: clip.FlatCap,
|
||||||
p := new(clip.Path)
|
Join: clip.BevelJoin,
|
||||||
p.Begin(o)
|
},
|
||||||
p.Move(f32.Pt(10, 50))
|
}.Op().Add(o)
|
||||||
p.Line(f32.Pt(10, 0))
|
|
||||||
p.Arc(f32.Pt(10, 0), f32.Pt(20, 0), math.Pi)
|
|
||||||
p.Line(f32.Pt(10, 0))
|
|
||||||
p.Line(f32.Pt(10, 10))
|
|
||||||
p.Arc(f32.Pt(0, 30), f32.Pt(0, 30), 2*math.Pi)
|
|
||||||
p.Line(f32.Pt(-20, 0))
|
|
||||||
p.Quad(f32.Pt(-10, -10), f32.Pt(-30, 30))
|
|
||||||
p.Stroke(width, sty).Add(o)
|
|
||||||
|
|
||||||
paint.Fill(o, red)
|
paint.Fill(o, red)
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -152,23 +148,15 @@ func TestStrokedPathBevelFlat(t *testing.T) {
|
|||||||
|
|
||||||
func TestStrokedPathBevelRound(t *testing.T) {
|
func TestStrokedPathBevelRound(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
const width = 2.5
|
p := newStrokedPath(o)
|
||||||
sty := clip.StrokeStyle{
|
clip.Stroke{
|
||||||
Cap: clip.RoundCap,
|
Path: p,
|
||||||
Join: clip.BevelJoin,
|
Style: clip.StrokeStyle{
|
||||||
}
|
Width: 2.5,
|
||||||
|
Cap: clip.RoundCap,
|
||||||
p := new(clip.Path)
|
Join: clip.BevelJoin,
|
||||||
p.Begin(o)
|
},
|
||||||
p.Move(f32.Pt(10, 50))
|
}.Op().Add(o)
|
||||||
p.Line(f32.Pt(10, 0))
|
|
||||||
p.Arc(f32.Pt(10, 0), f32.Pt(20, 0), math.Pi)
|
|
||||||
p.Line(f32.Pt(10, 0))
|
|
||||||
p.Line(f32.Pt(10, 10))
|
|
||||||
p.Arc(f32.Pt(0, 30), f32.Pt(0, 30), 2*math.Pi)
|
|
||||||
p.Line(f32.Pt(-20, 0))
|
|
||||||
p.Quad(f32.Pt(-10, -10), f32.Pt(-30, 30))
|
|
||||||
p.Stroke(width, sty).Add(o)
|
|
||||||
|
|
||||||
paint.Fill(o, red)
|
paint.Fill(o, red)
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -179,23 +167,15 @@ func TestStrokedPathBevelRound(t *testing.T) {
|
|||||||
|
|
||||||
func TestStrokedPathBevelSquare(t *testing.T) {
|
func TestStrokedPathBevelSquare(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
const width = 2.5
|
p := newStrokedPath(o)
|
||||||
sty := clip.StrokeStyle{
|
clip.Stroke{
|
||||||
Cap: clip.SquareCap,
|
Path: p,
|
||||||
Join: clip.BevelJoin,
|
Style: clip.StrokeStyle{
|
||||||
}
|
Width: 2.5,
|
||||||
|
Cap: clip.SquareCap,
|
||||||
p := new(clip.Path)
|
Join: clip.BevelJoin,
|
||||||
p.Begin(o)
|
},
|
||||||
p.Move(f32.Pt(10, 50))
|
}.Op().Add(o)
|
||||||
p.Line(f32.Pt(10, 0))
|
|
||||||
p.Arc(f32.Pt(10, 0), f32.Pt(20, 0), math.Pi)
|
|
||||||
p.Line(f32.Pt(10, 0))
|
|
||||||
p.Line(f32.Pt(10, 10))
|
|
||||||
p.Arc(f32.Pt(0, 30), f32.Pt(0, 30), 2*math.Pi)
|
|
||||||
p.Line(f32.Pt(-20, 0))
|
|
||||||
p.Quad(f32.Pt(-10, -10), f32.Pt(-30, 30))
|
|
||||||
p.Stroke(width, sty).Add(o)
|
|
||||||
|
|
||||||
paint.Fill(o, red)
|
paint.Fill(o, red)
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -206,23 +186,15 @@ func TestStrokedPathBevelSquare(t *testing.T) {
|
|||||||
|
|
||||||
func TestStrokedPathRoundRound(t *testing.T) {
|
func TestStrokedPathRoundRound(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
const width = 2.5
|
p := newStrokedPath(o)
|
||||||
sty := clip.StrokeStyle{
|
clip.Stroke{
|
||||||
Cap: clip.RoundCap,
|
Path: p,
|
||||||
Join: clip.RoundJoin,
|
Style: clip.StrokeStyle{
|
||||||
}
|
Width: 2.5,
|
||||||
|
Cap: clip.RoundCap,
|
||||||
p := new(clip.Path)
|
Join: clip.RoundJoin,
|
||||||
p.Begin(o)
|
},
|
||||||
p.Move(f32.Pt(10, 50))
|
}.Op().Add(o)
|
||||||
p.Line(f32.Pt(10, 0))
|
|
||||||
p.Arc(f32.Pt(10, 0), f32.Pt(20, 0), math.Pi)
|
|
||||||
p.Line(f32.Pt(10, 0))
|
|
||||||
p.Line(f32.Pt(10, 10))
|
|
||||||
p.Arc(f32.Pt(0, 30), f32.Pt(0, 30), 2*math.Pi)
|
|
||||||
p.Line(f32.Pt(-20, 0))
|
|
||||||
p.Quad(f32.Pt(-10, -10), f32.Pt(-30, 30))
|
|
||||||
p.Stroke(width, sty).Add(o)
|
|
||||||
|
|
||||||
paint.Fill(o, red)
|
paint.Fill(o, red)
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -233,40 +205,32 @@ func TestStrokedPathRoundRound(t *testing.T) {
|
|||||||
|
|
||||||
func TestStrokedPathFlatMiter(t *testing.T) {
|
func TestStrokedPathFlatMiter(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
const width = 10
|
|
||||||
sty := clip.StrokeStyle{
|
|
||||||
Cap: clip.FlatCap,
|
|
||||||
Join: clip.BevelJoin,
|
|
||||||
Miter: 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
beg := f32.Pt(40, 10)
|
|
||||||
{
|
{
|
||||||
p := new(clip.Path)
|
stk := op.Push(o)
|
||||||
p.Begin(o)
|
p := newZigZagPath(o)
|
||||||
p.Move(beg)
|
clip.Stroke{
|
||||||
p.Line(f32.Pt(50, 0))
|
Path: p,
|
||||||
p.Line(f32.Pt(-50, 50))
|
Style: clip.StrokeStyle{
|
||||||
p.Line(f32.Pt(50, 0))
|
Width: 10,
|
||||||
p.Quad(f32.Pt(-50, 20), f32.Pt(-50, 50))
|
Cap: clip.FlatCap,
|
||||||
p.Line(f32.Pt(50, 0))
|
Join: clip.BevelJoin,
|
||||||
|
Miter: 5,
|
||||||
p.Stroke(width, sty).Add(o)
|
},
|
||||||
|
}.Op().Add(o)
|
||||||
paint.Fill(o, red)
|
paint.Fill(o, red)
|
||||||
|
stk.Pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
p := new(clip.Path)
|
stk := op.Push(o)
|
||||||
p.Begin(o)
|
p := newZigZagPath(o)
|
||||||
p.Move(beg)
|
clip.Stroke{
|
||||||
p.Line(f32.Pt(50, 0))
|
Path: p,
|
||||||
p.Line(f32.Pt(-50, 50))
|
Style: clip.StrokeStyle{
|
||||||
p.Line(f32.Pt(50, 0))
|
Width: 2,
|
||||||
p.Quad(f32.Pt(-50, 20), f32.Pt(-50, 50))
|
},
|
||||||
p.Line(f32.Pt(50, 0))
|
}.Op().Add(o)
|
||||||
|
|
||||||
p.Stroke(2, clip.StrokeStyle{}).Add(o)
|
|
||||||
paint.Fill(o, black)
|
paint.Fill(o, black)
|
||||||
|
stk.Pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -278,40 +242,32 @@ func TestStrokedPathFlatMiter(t *testing.T) {
|
|||||||
|
|
||||||
func TestStrokedPathFlatMiterInf(t *testing.T) {
|
func TestStrokedPathFlatMiterInf(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
const width = 10
|
|
||||||
sty := clip.StrokeStyle{
|
|
||||||
Cap: clip.FlatCap,
|
|
||||||
Join: clip.BevelJoin,
|
|
||||||
Miter: float32(math.Inf(+1)),
|
|
||||||
}
|
|
||||||
|
|
||||||
beg := f32.Pt(40, 10)
|
|
||||||
{
|
{
|
||||||
p := new(clip.Path)
|
stk := op.Push(o)
|
||||||
p.Begin(o)
|
p := newZigZagPath(o)
|
||||||
p.Move(beg)
|
clip.Stroke{
|
||||||
p.Line(f32.Pt(50, 0))
|
Path: p,
|
||||||
p.Line(f32.Pt(-50, 50))
|
Style: clip.StrokeStyle{
|
||||||
p.Line(f32.Pt(50, 0))
|
Width: 10,
|
||||||
p.Quad(f32.Pt(-50, 20), f32.Pt(-50, 50))
|
Cap: clip.FlatCap,
|
||||||
p.Line(f32.Pt(50, 0))
|
Join: clip.BevelJoin,
|
||||||
|
Miter: float32(math.Inf(+1)),
|
||||||
p.Stroke(width, sty).Add(o)
|
},
|
||||||
|
}.Op().Add(o)
|
||||||
paint.Fill(o, red)
|
paint.Fill(o, red)
|
||||||
|
stk.Pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
p := new(clip.Path)
|
stk := op.Push(o)
|
||||||
p.Begin(o)
|
p := newZigZagPath(o)
|
||||||
p.Move(beg)
|
clip.Stroke{
|
||||||
p.Line(f32.Pt(50, 0))
|
Path: p,
|
||||||
p.Line(f32.Pt(-50, 50))
|
Style: clip.StrokeStyle{
|
||||||
p.Line(f32.Pt(50, 0))
|
Width: 2,
|
||||||
p.Quad(f32.Pt(-50, 20), f32.Pt(-50, 50))
|
},
|
||||||
p.Line(f32.Pt(50, 0))
|
}.Op().Add(o)
|
||||||
|
|
||||||
p.Stroke(2, clip.StrokeStyle{}).Add(o)
|
|
||||||
paint.Fill(o, black)
|
paint.Fill(o, black)
|
||||||
|
stk.Pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -323,26 +279,35 @@ func TestStrokedPathFlatMiterInf(t *testing.T) {
|
|||||||
|
|
||||||
func TestStrokedPathZeroWidth(t *testing.T) {
|
func TestStrokedPathZeroWidth(t *testing.T) {
|
||||||
run(t, func(o *op.Ops) {
|
run(t, func(o *op.Ops) {
|
||||||
const width = 2
|
|
||||||
var sty clip.StrokeStyle
|
|
||||||
{
|
{
|
||||||
|
stk := op.Push(o)
|
||||||
p := new(clip.Path)
|
p := new(clip.Path)
|
||||||
p.Begin(o)
|
p.Begin(o)
|
||||||
p.Move(f32.Pt(10, 50))
|
p.Move(f32.Pt(10, 50))
|
||||||
p.Line(f32.Pt(50, 0))
|
p.Line(f32.Pt(50, 0))
|
||||||
p.Stroke(width, sty).Add(o)
|
clip.Stroke{
|
||||||
|
Path: p.End(),
|
||||||
|
Style: clip.StrokeStyle{
|
||||||
|
Width: 2,
|
||||||
|
},
|
||||||
|
}.Op().Add(o)
|
||||||
|
|
||||||
paint.Fill(o, black)
|
paint.Fill(o, black)
|
||||||
|
stk.Pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
stk := op.Push(o)
|
||||||
p := new(clip.Path)
|
p := new(clip.Path)
|
||||||
p.Begin(o)
|
p.Begin(o)
|
||||||
p.Move(f32.Pt(10, 50))
|
p.Move(f32.Pt(10, 50))
|
||||||
p.Line(f32.Pt(30, 0))
|
p.Line(f32.Pt(30, 0))
|
||||||
p.Stroke(0, sty).Add(o) // width=0, disable stroke
|
clip.Stroke{
|
||||||
|
Path: p.End(),
|
||||||
|
}.Op().Add(o) // width=0, disable stroke
|
||||||
|
|
||||||
paint.Fill(o, red)
|
paint.Fill(o, red)
|
||||||
|
stk.Pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
@@ -352,3 +317,38 @@ func TestStrokedPathZeroWidth(t *testing.T) {
|
|||||||
r.expect(65, 50, colornames.White)
|
r.expect(65, 50, colornames.White)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newStrokedPath(o *op.Ops) clip.PathSpec {
|
||||||
|
p := new(clip.Path)
|
||||||
|
p.Begin(o)
|
||||||
|
p.Move(f32.Pt(10, 50))
|
||||||
|
p.Line(f32.Pt(10, 0))
|
||||||
|
p.Arc(f32.Pt(10, 0), f32.Pt(20, 0), math.Pi)
|
||||||
|
p.Line(f32.Pt(10, 0))
|
||||||
|
p.Line(f32.Pt(10, 10))
|
||||||
|
p.Arc(f32.Pt(0, 30), f32.Pt(0, 30), 2*math.Pi)
|
||||||
|
p.Line(f32.Pt(-20, 0))
|
||||||
|
p.Quad(f32.Pt(-10, -10), f32.Pt(-30, 30))
|
||||||
|
return p.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newZigZagPath(o *op.Ops) clip.PathSpec {
|
||||||
|
p := new(clip.Path)
|
||||||
|
p.Begin(o)
|
||||||
|
p.Move(f32.Pt(40, 10))
|
||||||
|
p.Line(f32.Pt(50, 0))
|
||||||
|
p.Line(f32.Pt(-50, 50))
|
||||||
|
p.Line(f32.Pt(50, 0))
|
||||||
|
p.Quad(f32.Pt(-50, 20), f32.Pt(-50, 50))
|
||||||
|
p.Line(f32.Pt(50, 0))
|
||||||
|
return p.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEllipsePath(o *op.Ops) clip.PathSpec {
|
||||||
|
p := new(clip.Path)
|
||||||
|
p.Begin(o)
|
||||||
|
p.Move(f32.Pt(0, 65))
|
||||||
|
p.Line(f32.Pt(20, 0))
|
||||||
|
p.Arc(f32.Pt(20, 0), f32.Pt(70, 0), 2*math.Pi)
|
||||||
|
return p.End()
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -71,7 +71,10 @@ func TestRepeatedPaintsZ(t *testing.T) {
|
|||||||
builder.Line(f32.Pt(0, 10))
|
builder.Line(f32.Pt(0, 10))
|
||||||
builder.Line(f32.Pt(-10, 0))
|
builder.Line(f32.Pt(-10, 0))
|
||||||
builder.Line(f32.Pt(0, -10))
|
builder.Line(f32.Pt(0, -10))
|
||||||
builder.Outline().Add(o)
|
p := builder.End()
|
||||||
|
clip.Outline{
|
||||||
|
Path: p,
|
||||||
|
}.Op().Add(o)
|
||||||
paint.Fill(o, red)
|
paint.Fill(o, red)
|
||||||
}, func(r result) {
|
}, func(r result) {
|
||||||
r.expect(5, 5, colornames.Red)
|
r.expect(5, 5, colornames.Red)
|
||||||
@@ -109,7 +112,8 @@ func constSqPath() op.CallOp {
|
|||||||
builder.Line(f32.Pt(0, 10))
|
builder.Line(f32.Pt(0, 10))
|
||||||
builder.Line(f32.Pt(-10, 0))
|
builder.Line(f32.Pt(-10, 0))
|
||||||
builder.Line(f32.Pt(0, -10))
|
builder.Line(f32.Pt(0, -10))
|
||||||
builder.Outline().Add(innerOps)
|
p := builder.End()
|
||||||
|
clip.Outline{Path: p}.Op().Add(innerOps)
|
||||||
return m.Stop()
|
return m.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+72
-49
@@ -13,6 +13,52 @@ import (
|
|||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Op represents a clip area. Op intersects the current clip area with
|
||||||
|
// itself.
|
||||||
|
type Op struct {
|
||||||
|
bounds image.Rectangle
|
||||||
|
path PathSpec
|
||||||
|
|
||||||
|
outline bool
|
||||||
|
stroke StrokeStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Op) Add(o *op.Ops) {
|
||||||
|
if p.path.quads > 0 {
|
||||||
|
data := o.Write(opconst.TypePathLen)
|
||||||
|
data[0] = byte(opconst.TypePath)
|
||||||
|
bo := binary.LittleEndian
|
||||||
|
bo.PutUint32(data[1:], p.path.quads)
|
||||||
|
p.path.spec.Add(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.stroke.Width > 0 {
|
||||||
|
data := o.Write(opconst.TypeStrokeLen)
|
||||||
|
data[0] = byte(opconst.TypeStroke)
|
||||||
|
bo := binary.LittleEndian
|
||||||
|
bo.PutUint32(data[1:], math.Float32bits(p.stroke.Width))
|
||||||
|
bo.PutUint32(data[5:], math.Float32bits(p.stroke.Miter))
|
||||||
|
data[9] = uint8(p.stroke.Cap)
|
||||||
|
data[10] = uint8(p.stroke.Join)
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
if p.outline {
|
||||||
|
data[17] = byte(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PathSpec struct {
|
||||||
|
spec op.CallOp
|
||||||
|
quads uint32 // quads is the number Bézier segments in that path.
|
||||||
|
}
|
||||||
|
|
||||||
// Path constructs a Op clip path described by lines and
|
// Path constructs a Op clip path described by lines and
|
||||||
// Bézier curves, where drawing outside the Path is discarded.
|
// Bézier curves, where drawing outside the Path is discarded.
|
||||||
// The inside-ness of a pixel is determines by the non-zero winding rule,
|
// The inside-ness of a pixel is determines by the non-zero winding rule,
|
||||||
@@ -26,38 +72,12 @@ type Path struct {
|
|||||||
pen f32.Point
|
pen f32.Point
|
||||||
macro op.MacroOp
|
macro op.MacroOp
|
||||||
start f32.Point
|
start f32.Point
|
||||||
|
quads uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pos returns the current pen position.
|
// Pos returns the current pen position.
|
||||||
func (p *Path) Pos() f32.Point { return p.pen }
|
func (p *Path) Pos() f32.Point { return p.pen }
|
||||||
|
|
||||||
// Op sets the current clip to the intersection of
|
|
||||||
// the existing clip with this clip.
|
|
||||||
//
|
|
||||||
// If you need to reset the clip to its previous values after
|
|
||||||
// applying a Op, use op.StackOp.
|
|
||||||
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)
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Begin the path, storing the path data and final Op into ops.
|
// Begin the path, storing the path data and final Op into ops.
|
||||||
func (p *Path) Begin(ops *op.Ops) {
|
func (p *Path) Begin(ops *op.Ops) {
|
||||||
p.ops = ops
|
p.ops = ops
|
||||||
@@ -67,6 +87,15 @@ func (p *Path) Begin(ops *op.Ops) {
|
|||||||
data[0] = byte(opconst.TypeAux)
|
data[0] = byte(opconst.TypeAux)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// End returns a PathSpec ready to use in clipping operations.
|
||||||
|
func (p *Path) End() PathSpec {
|
||||||
|
c := p.macro.Stop()
|
||||||
|
return PathSpec{
|
||||||
|
spec: c,
|
||||||
|
quads: p.quads,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Move moves the pen by the amount specified by delta.
|
// Move moves the pen by the amount specified by delta.
|
||||||
func (p *Path) Move(delta f32.Point) {
|
func (p *Path) Move(delta f32.Point) {
|
||||||
to := delta.Add(p.pen)
|
to := delta.Add(p.pen)
|
||||||
@@ -120,6 +149,7 @@ func (p *Path) QuadTo(ctrl, to f32.Point) {
|
|||||||
To: to,
|
To: to,
|
||||||
})
|
})
|
||||||
p.pen = to
|
p.pen = to
|
||||||
|
p.quads++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arc adds an elliptical arc to the path. The implied ellipse is defined
|
// Arc adds an elliptical arc to the path. The implied ellipse is defined
|
||||||
@@ -329,32 +359,22 @@ func (p *Path) approxCubeTo(splits int, maxDist float32, ctrl0, ctrl1, to f32.Po
|
|||||||
return splits
|
return splits
|
||||||
}
|
}
|
||||||
|
|
||||||
// Outline closes the path and returns a clip operation that represents it.
|
// Close closes the path.
|
||||||
func (p *Path) Outline() Op {
|
func (p *Path) Close() {
|
||||||
p.end()
|
p.end()
|
||||||
c := p.macro.Stop()
|
|
||||||
return Op{
|
|
||||||
call: c,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stroke returns a stroked path with the specified width
|
// Outline represents the area inside of a path, according to the
|
||||||
// and configuration.
|
// non-zero winding rule.
|
||||||
// If the provided width is <= 0, the path won't be stroked.
|
type Outline struct {
|
||||||
func (p *Path) Stroke(width float32, sty StrokeStyle) Op {
|
Path PathSpec
|
||||||
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()
|
// Op returns a clip operation representing the outline.
|
||||||
|
func (o Outline) Op() Op {
|
||||||
return Op{
|
return Op{
|
||||||
call: c,
|
path: o.Path,
|
||||||
width: width,
|
outline: true,
|
||||||
style: sty,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,7 +383,10 @@ type Rect image.Rectangle
|
|||||||
|
|
||||||
// Op returns the op for the rectangle.
|
// Op returns the op for the rectangle.
|
||||||
func (r Rect) Op() Op {
|
func (r Rect) Op() Op {
|
||||||
return Op{bounds: image.Rectangle(r)}
|
return Op{
|
||||||
|
bounds: image.Rectangle(r),
|
||||||
|
outline: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the clip operation.
|
// Add the clip operation.
|
||||||
|
|||||||
+10
-4
@@ -36,7 +36,10 @@ func (rr RRect) Op(ops *op.Ops) Op {
|
|||||||
p.Begin(ops)
|
p.Begin(ops)
|
||||||
p.Move(rr.Rect.Min)
|
p.Move(rr.Rect.Min)
|
||||||
roundRect(&p, rr.Rect.Size(), rr.SE, rr.SW, rr.NW, rr.NE)
|
roundRect(&p, rr.Rect.Size(), rr.SE, rr.SW, rr.NW, rr.NE)
|
||||||
return p.Outline()
|
|
||||||
|
return Outline{
|
||||||
|
Path: p.End(),
|
||||||
|
}.Op()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the rectangle clip.
|
// Add the rectangle clip.
|
||||||
@@ -49,7 +52,6 @@ type Border struct {
|
|||||||
// Rect is the bounds of the border.
|
// Rect is the bounds of the border.
|
||||||
Rect f32.Rectangle
|
Rect f32.Rectangle
|
||||||
Width float32
|
Width float32
|
||||||
Style StrokeStyle
|
|
||||||
// The corner radii.
|
// The corner radii.
|
||||||
SE, SW, NW, NE float32
|
SE, SW, NW, NE float32
|
||||||
}
|
}
|
||||||
@@ -58,11 +60,15 @@ type Border struct {
|
|||||||
func (b Border) Op(ops *op.Ops) Op {
|
func (b Border) Op(ops *op.Ops) Op {
|
||||||
var p Path
|
var p Path
|
||||||
p.Begin(ops)
|
p.Begin(ops)
|
||||||
|
|
||||||
p.Move(b.Rect.Min)
|
p.Move(b.Rect.Min)
|
||||||
roundRect(&p, b.Rect.Size(), b.SE, b.SW, b.NW, b.NE)
|
roundRect(&p, b.Rect.Size(), b.SE, b.SW, b.NW, b.NE)
|
||||||
|
|
||||||
return p.Stroke(b.Width, b.Style)
|
return Stroke{
|
||||||
|
Path: p.End(),
|
||||||
|
Style: StrokeStyle{
|
||||||
|
Width: b.Width,
|
||||||
|
},
|
||||||
|
}.Op()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the border clip.
|
// Add the border clip.
|
||||||
|
|||||||
+18
-5
@@ -2,17 +2,30 @@
|
|||||||
|
|
||||||
package clip
|
package clip
|
||||||
|
|
||||||
// StrokeStyle describes how a stroked path should be drawn.
|
// Stroke represents a stroked path.
|
||||||
// The zero value of StrokeStyle represents bevel-joined and flat-capped
|
type Stroke struct {
|
||||||
// strokes.
|
Path PathSpec
|
||||||
|
Style StrokeStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Op returns a clip operation representing the stroke.
|
||||||
|
func (s Stroke) Op() Op {
|
||||||
|
return Op{
|
||||||
|
path: s.Path,
|
||||||
|
stroke: s.Style,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrokeStyle describes how a path should be stroked.
|
||||||
type StrokeStyle struct {
|
type StrokeStyle struct {
|
||||||
Cap StrokeCap
|
Width float32 // Width of the stroked path.
|
||||||
Join StrokeJoin
|
|
||||||
|
|
||||||
// Miter is the limit to apply to a miter joint.
|
// Miter is the limit to apply to a miter joint.
|
||||||
// The zero Miter disables the miter joint; setting Miter to +∞
|
// The zero Miter disables the miter joint; setting Miter to +∞
|
||||||
// unconditionally enables the miter joint.
|
// unconditionally enables the miter joint.
|
||||||
Miter float32
|
Miter float32
|
||||||
|
Cap StrokeCap // Cap describes the head or tail of a stroked path.
|
||||||
|
Join StrokeJoin // Join describes how stroked paths are collated.
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrokeCap describes the head or tail of a stroked path.
|
// StrokeCap describes the head or tail of a stroked path.
|
||||||
|
|||||||
@@ -67,15 +67,17 @@ func clipLoader(ops *op.Ops, startAngle, endAngle, radius float64) {
|
|||||||
pen = f32.Pt(float32(vx), float32(vy)).Mul(float32(radius))
|
pen = f32.Pt(float32(vx), float32(vy)).Mul(float32(radius))
|
||||||
center = f32.Pt(0, 0).Sub(pen)
|
center = f32.Pt(0, 0).Sub(pen)
|
||||||
|
|
||||||
style = clip.StrokeStyle{
|
|
||||||
Cap: clip.FlatCap,
|
|
||||||
}
|
|
||||||
|
|
||||||
p clip.Path
|
p clip.Path
|
||||||
)
|
)
|
||||||
|
|
||||||
p.Begin(ops)
|
p.Begin(ops)
|
||||||
p.Move(pen)
|
p.Move(pen)
|
||||||
p.Arc(center, center, delta)
|
p.Arc(center, center, delta)
|
||||||
p.Stroke(width, style).Add(ops)
|
clip.Stroke{
|
||||||
|
Path: p.End(),
|
||||||
|
Style: clip.StrokeStyle{
|
||||||
|
Width: width,
|
||||||
|
Cap: clip.FlatCap,
|
||||||
|
},
|
||||||
|
}.Op().Add(ops)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user