-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpurplecrayon.go
267 lines (217 loc) · 6.87 KB
/
purplecrayon.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
// Package purplecrayon is a drawing library written in Go.
// While it can support an arbitrary backend, it only supports svg for now.
//
// The main interaction is via the Canvas. A canvas represents the interface between
// the frontend and the backend, allowing the programmer to create primitives like
// rectangles, circles, paths, and gradients. You can create such an SVG canvas via:
// canv, err := pc.NewCanvas("svg", 1920, 1080, w)
// as long as the svg component has been imported via:
// import _ "github.com/jordan-bonecutter/purplecrayon/svg"
//
// Always remember to close your objects with Close() so they're drawn to the canvas!
//
// purplecrayon can be extended to support any other backend by:
//
// 1. Implement the defined interfaces
//
// 2. Registering the library via
// pc.Register("myBackend", NewMyBackendCanvas)
//
// 3. Instantiating your backend with
// pc.NewCanvas("myBackend", width, height, writer)
package purplecrayon
import (
"fmt"
core "github.com/jordan-bonecutter/purplecrayon/core"
"io"
)
// Point is a point on a canvas
type Point = core.Point
// RGB is a color represented in RGB space
type RGB = core.RGB
// RGBA is a color represented in RGBA space
type RGBA = core.RGBA
// Reference is a reference to an object
type Reference = core.Reference
// Canvas is the main interface for drawing with purplecrayon.
// Only one operation should be open at a time, undefined behavior occurs otherwise.
type Canvas interface {
Referrable
// Set parameters for the current backend.
// Returns nil iff no error occurred.
Configure(key string, value interface{}) error
// Return the width of the canvas
Width() float64
// Return the height of the canvas
Height() float64
// Draw a rectangle inside the canvas
Rect() Rect
// Draw a circle inside the canvas
Circle() Circle
// Draw an ellipse inside the canvas
Ellipse() Ellipse
// Begin a path.
Path() Path
// Create a linear gradient
LinearGradient() LinearGradient
// Create a group which draws to a subgroup in this canvas
Group() Group
// Create a mask which draws to a submask in this canvas
Mask() Mask
}
// Group is a collection of objects which can be transformed and painted together.
// All transform / paint operations must be finished before calling Start.
// The canvas returned by start is a "derived canvas", in that it eventually
// refers to the same canvas from which the group was created but through a
// layer of indirection, namely the group.
type Group interface {
Transformable
Paintable
Open() Canvas
}
// Mask objects contain a group of objects which together may be used as a compositing mask.
// Works similarly to the Group.
type Mask interface {
Transformable
Paintable
Open() Canvas
}
// Transformable objects are any object which can be transformed.
// Note: The transform must be finished before working on other attributes!
type Transformable interface {
Transform() Transform
}
// Transform objects are instances of a transformations.
type Transform interface {
Translate(delta Point) Transform
Scale(float64) Transform
Rotate(degrees float64) Transform
// Finish the current transformation.
// This method is not called Close because it doesn't return a reference!
// In other words, transforms are not referrable.
Finish()
}
// Paintable objects are any object which can be painted onto a canvas.
type Paintable interface {
FillTransparent()
FillRGB(RGB)
FillRGBA(RGBA)
Fill(Reference)
StrokeWidth(float64)
StrokeRGB(RGB)
StrokeRGBA(RGBA)
StrokeTransparent()
Stroke(Reference)
CompositeMask(Reference)
}
// Referrable objects are objects which may return a reference to themselves for others to use.
type Referrable interface {
// Closes the object and creates a reference for other objects to refer to it.
Close() Reference
}
// Path objects are generic shapes which can be drawn with cursors.
type Path interface {
Referrable
Transformable
Paintable
Cursor() Cursor
// Finish the path.
Close() Reference
}
// Cursor objects allow arbitrary drawing of shapes on a canvas.
type Cursor interface {
// Move the cursor to an absolute point.
MoveTo(Point) Cursor
// Move the cursor to a relative point.
MoveToRel(Point) Cursor
// Draw a line to an absolute point.
LineTo(Point) Cursor
// Draw a line to a relative point.
LineToRel(Point) Cursor
// Draw an absolute quadratic bezier curve.
QuadTo(Point, Point) Cursor
// Draw a relative quadratic bezier curve.
QuadToRel(Point, Point) Cursor
// Zips up the path to where it started.
Zip() Cursor
// Finishes the cursor movement.
// Not close because it returns no reference!
// The reference is held in the enclosing Path
Finish()
}
// Rect objects are drawn as rectangles on a canvas.
type Rect interface {
Referrable
Transformable
Paintable
TopLeft(p Point) Rect
Width(float64) Rect
Height(float64) Rect
}
// Circle objects are drawn as circles on a canvas.
type Circle interface {
Referrable
Transformable
Paintable
Center(Point) Circle
Radius(float64) Circle
}
// Ellipse objects are drawn as ellpises on a canvas.
type Ellipse interface {
Referrable
Transformable
Paintable
Center(p Point) Ellipse
Radii(p Point) Ellipse
}
// LinearGradient objects return a reference to a linear gradient which can be used
// as a fill or stroke for Paintable objects.
type LinearGradient interface {
Referrable
// Sets the line along which the gradient varies.
// This line is always in a "hypothetical space" where the top left corner
// is at coordinate (0, 0) and the bottom right is at (1, 1).
// Once the gradient is used (0, 0) and (1, 1) are mapped to the bounds
// in an affine manner.
SetLine(p0, p1 Point) LinearGradient
// the object by which stops are added to the gradient.
GradientStops() GradientStops
}
// GradientStop objects represent a fixed color stop in a gradient.
type GradientStop interface {
RGB(RGB) GradientStop
RGBA(RGBA) GradientStop
Position(float64) GradientStop
Finish()
}
// GradientStops objects allow creation of GradientStop objects for a Gradient.
type GradientStops interface {
// Add a gradient stop to the parent gradient
Stop() GradientStop
// Finish adding stops to the gradient
Finish()
}
// Driver functions return Canvas objects which implement the Canvas interface.
type Driver func(width, height float64, w io.Writer) Canvas
// All drivers which have been registered
var registeredDrivers map[string]Driver
// Register a new driver
func Register(name string, d Driver) {
if registeredDrivers == nil {
registeredDrivers = make(map[string]Driver)
}
registeredDrivers[name] = d
}
var errDriverNotFound = fmt.Errorf("Driver not found")
// NewCanvas creates a Canvas for the given regsitered Driver
func NewCanvas(driver string, width, height float64, w io.Writer) (c Canvas, err error) {
if registeredDrivers == nil {
err = errDriverNotFound
}
if lib, ok := registeredDrivers[driver]; ok {
c = lib(width, height, w)
} else {
err = errDriverNotFound
}
return
}