github.com/andybalholm/giopdf@v0.0.0-20220317170119-aad9a095ad48/path.go (about) 1 package giopdf 2 3 import ( 4 "gioui.org/f32" 5 "gioui.org/op" 6 "gioui.org/op/clip" 7 ) 8 9 // A PathElement represents a segement of a path. 10 type PathElement struct { 11 // Op is the PDF path construction operator corresponding to this 12 // PathElement. Possible values are: 13 // 14 // m moveto 15 // l lineto 16 // c curveto 17 // h closepath 18 Op byte 19 20 // CP1 and CP2 are the control points for a bezier curve segment ('c'). 21 CP1, CP2 f32.Point 22 End f32.Point 23 } 24 25 type PathBuilder struct { 26 Path []PathElement 27 currentPoint f32.Point 28 lastMoveTo f32.Point 29 } 30 31 // MoveTo starts a new subpath at (x, y). 32 func (p *PathBuilder) MoveTo(x, y float32) { 33 pt := f32.Point{x, y} 34 p.currentPoint = pt 35 p.lastMoveTo = pt 36 37 if len(p.Path) > 0 && p.Path[len(p.Path)-1].Op == 'm' { 38 p.Path[len(p.Path)-1].End = pt 39 return 40 } 41 42 p.Path = append(p.Path, PathElement{Op: 'm', End: pt}) 43 } 44 45 // LineTo adds a line segment to the path, ending at (x, y). 46 func (p *PathBuilder) LineTo(x, y float32) { 47 pt := f32.Point{x, y} 48 p.currentPoint = pt 49 p.Path = append(p.Path, PathElement{Op: 'l', End: pt}) 50 } 51 52 // CurveTo adds a cubic Bezier curve to the path. It ends at (x3, y3) and has 53 // (x1, y1) and (x2, y2) for control points. 54 func (p *PathBuilder) CurveTo(x1, y1, x2, y2, x3, y3 float32) { 55 e := PathElement{ 56 Op: 'c', 57 CP1: f32.Point{x1, y1}, 58 CP2: f32.Point{x2, y2}, 59 End: f32.Point{x3, y3}, 60 } 61 p.currentPoint = e.End 62 p.Path = append(p.Path, e) 63 } 64 65 // CurveV adds a cubic Bezier curve to the path. It ends at (x3, y3) and has 66 // the current point and (x2, y2) for control points. 67 func (p *PathBuilder) CurveV(x2, y2, x3, y3 float32) { 68 e := PathElement{ 69 Op: 'c', 70 CP1: p.currentPoint, 71 CP2: f32.Point{x2, y2}, 72 End: f32.Point{x3, y3}, 73 } 74 p.currentPoint = e.End 75 p.Path = append(p.Path, e) 76 } 77 78 // CurveY adds a cubic Bezier curve to the path. It ends at (x3, y3) and has 79 // (x1, y1) and (x3, y3) for control points. 80 func (p *PathBuilder) CurveY(x1, y1, x3, y3 float32) { 81 e := PathElement{ 82 Op: 'c', 83 CP1: f32.Point{x1, y1}, 84 CP2: f32.Point{x3, y3}, 85 End: f32.Point{x3, y3}, 86 } 87 p.currentPoint = e.End 88 p.Path = append(p.Path, e) 89 } 90 91 // QuadraticCurveTo adds a quadratic Bezier curve to the path. It ends at 92 // (x2, y2) and has (x1, y1) for its control point. 93 func (p *PathBuilder) QuadraticCurveTo(x1, y1, x2, y2 float32) { 94 // Convert the quadratic curve to a cubic one. 95 c1x := p.currentPoint.X + (x1-p.currentPoint.X)*2/3 96 c1y := p.currentPoint.Y + (y1-p.currentPoint.Y)*2/3 97 c2x := x2 + (x1-x2)*2/3 98 c2y := y2 + (y1-y2)*2/3 99 p.CurveTo(c1x, c1y, c2x, c2y, x2, y2) 100 } 101 102 // ClosePath closes the path, ensuring that it ends at the same point where it 103 // began. 104 func (p *PathBuilder) ClosePath() { 105 if len(p.Path) == 0 || p.Path[len(p.Path)-1].Op == 'h' { 106 return 107 } 108 p.Path = append(p.Path, PathElement{Op: 'h'}) 109 p.currentPoint = p.lastMoveTo 110 } 111 112 // Rectangle creates a new subpath containing a rectangle of the specified 113 // dimensions. 114 func (p *PathBuilder) Rectangle(x, y, width, height float32) { 115 p.MoveTo(x, y) 116 p.LineTo(x+width, y) 117 p.LineTo(x+width, y+height) 118 p.LineTo(x, y+height) 119 p.ClosePath() 120 } 121 122 func toPathSpec(ops *op.Ops, p []PathElement, alwaysClose bool) clip.PathSpec { 123 var path clip.Path 124 path.Begin(ops) 125 closed := true 126 127 for _, e := range p { 128 switch e.Op { 129 case 'm': 130 if alwaysClose && !closed { 131 path.Close() 132 } 133 path.MoveTo(e.End) 134 closed = false 135 136 case 'l': 137 path.LineTo(e.End) 138 closed = false 139 140 case 'c': 141 path.CubeTo(e.CP1, e.CP2, e.End) 142 closed = false 143 144 case 'h': 145 path.Close() 146 closed = true 147 } 148 } 149 150 if alwaysClose && !closed { 151 path.Close() 152 } 153 154 return path.End() 155 } 156 157 func transformPath(p []PathElement, matrix f32.Affine2D) []PathElement { 158 result := make([]PathElement, len(p)) 159 for i, e := range p { 160 result[i] = PathElement{ 161 Op: e.Op, 162 CP1: matrix.Transform(e.CP1), 163 CP2: matrix.Transform(e.CP2), 164 End: matrix.Transform(e.End), 165 } 166 } 167 return result 168 }