processing 0.5.34 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ChangeLog.md +18 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +2 -0
- data/Rakefile +5 -5
- data/VERSION +1 -1
- data/lib/processing/all.rb +4 -1
- data/lib/processing/context.rb +0 -1
- data/lib/processing/graphics_context.rb +209 -41
- data/lib/processing/image.rb +0 -5
- data/lib/processing/shape.rb +119 -26
- data/lib/processing/svg.rb +248 -0
- data/processing.gemspec +4 -4
- data/test/{p5.rb → browser.rb} +37 -3
- data/test/helper.rb +40 -7
- data/test/test_color.rb +94 -0
- data/test/test_graphics_context.rb +26 -59
- data/test/test_shape.rb +129 -6
- data/test/test_svg.rb +257 -0
- metadata +17 -12
data/lib/processing/shape.rb
CHANGED
@@ -9,9 +9,10 @@ module Processing
|
|
9
9
|
|
10
10
|
# @private
|
11
11
|
def initialize(polygon = nil, children = nil, context: nil)
|
12
|
-
@polygon, @children
|
13
|
-
@context
|
14
|
-
@visible
|
12
|
+
@polygon, @children = polygon, children
|
13
|
+
@context = context || Context.context__
|
14
|
+
@visible = true
|
15
|
+
@fill = @stroke = @strokeWeight = @strokeCap = @strokeJoin = @matrix = nil
|
15
16
|
@type = @points = @curvePoints = @colors = @texcoords = @close = nil
|
16
17
|
@contours = @contourPoints = @contourColors = @contourTexCoords = nil
|
17
18
|
end
|
@@ -72,6 +73,7 @@ module Processing
|
|
72
73
|
#
|
73
74
|
def beginShape(type = nil)
|
74
75
|
raise "beginShape() cannot be called twice" if drawingShape__
|
76
|
+
@fill = @stroke = @strokeWeight = @strokeCap = @strokeJoin = nil
|
75
77
|
@type = type
|
76
78
|
@points ||= []
|
77
79
|
@curvePoints = []
|
@@ -91,7 +93,13 @@ module Processing
|
|
91
93
|
#
|
92
94
|
def endShape(close = nil)
|
93
95
|
raise "endShape() must be called after beginShape()" unless drawingShape__
|
94
|
-
|
96
|
+
painter = @context.getPainter__
|
97
|
+
@fill ||= painter.fill
|
98
|
+
@stroke ||= painter.stroke
|
99
|
+
@strokeWeight ||= painter.stroke_width
|
100
|
+
@strokeCap ||= painter.stroke_cap
|
101
|
+
@strokeJoin ||= painter.stroke_join
|
102
|
+
@close = close == GraphicsContext::CLOSE || @contours.size > 0
|
95
103
|
if @close && @curvePoints.size >= 8
|
96
104
|
x, y = @curvePoints[0, 2]
|
97
105
|
2.times {curveVertex x, y}
|
@@ -147,7 +155,7 @@ module Processing
|
|
147
155
|
raise "Either 'u' or 'v' is missing" if (u == nil) != (v == nil)
|
148
156
|
u ||= x
|
149
157
|
v ||= y
|
150
|
-
color = @fill || @context.
|
158
|
+
color = @fill || @context.getPainter__.fill
|
151
159
|
if drawingContour__
|
152
160
|
@contourPoints << x << y
|
153
161
|
@contourColors << color
|
@@ -250,7 +258,30 @@ module Processing
|
|
250
258
|
# @see https://p5js.org/reference/#/p5/fill
|
251
259
|
#
|
252
260
|
def fill(*args)
|
253
|
-
@fill = @context.
|
261
|
+
@fill = @context.toRawColor__(*args)
|
262
|
+
nil
|
263
|
+
end
|
264
|
+
|
265
|
+
# Sets stroke color.
|
266
|
+
#
|
267
|
+
# @overload stroke(gray)
|
268
|
+
# @overload stroke(gray, alpha)
|
269
|
+
# @overload stroke(r, g, b)
|
270
|
+
# @overload stroke(r, g, b, alpha)
|
271
|
+
#
|
272
|
+
# @param gray [Integer] gray value (0..255)
|
273
|
+
# @param r [Integer] red value (0..255)
|
274
|
+
# @param g [Integer] green value (0..255)
|
275
|
+
# @param b [Integer] blue value (0..255)
|
276
|
+
# @param alpha [Integer] alpha value (0..255)
|
277
|
+
#
|
278
|
+
# @return [nil] nil
|
279
|
+
#
|
280
|
+
# @see https://processing.org/reference/stroke_.html
|
281
|
+
# @see https://p5js.org/reference/#/p5/stroke
|
282
|
+
#
|
283
|
+
def stroke(*args)
|
284
|
+
@stroke = @context.toRawColor__(*args)
|
254
285
|
nil
|
255
286
|
end
|
256
287
|
|
@@ -300,12 +331,12 @@ module Processing
|
|
300
331
|
@points.size / 2
|
301
332
|
end
|
302
333
|
|
303
|
-
# Sets the fill color.
|
334
|
+
# Sets the fill color for all vertices.
|
304
335
|
#
|
305
|
-
# @overload
|
306
|
-
# @overload
|
307
|
-
# @overload
|
308
|
-
# @overload
|
336
|
+
# @overload setFill(gray)
|
337
|
+
# @overload setFill(gray, alpha)
|
338
|
+
# @overload setFill(r, g, b)
|
339
|
+
# @overload setFill(r, g, b, alpha)
|
309
340
|
#
|
310
341
|
# @param gray [Integer] gray value (0..255)
|
311
342
|
# @param r [Integer] red value (0..255)
|
@@ -318,26 +349,76 @@ module Processing
|
|
318
349
|
# @see https://processing.org/reference/PShape_setFill_.html
|
319
350
|
#
|
320
351
|
def setFill(*args)
|
321
|
-
|
352
|
+
fill(*args)
|
322
353
|
count = getVertexCount
|
323
354
|
if count > 0
|
324
355
|
if @colors
|
325
|
-
@colors.fill
|
356
|
+
@colors.fill @fill
|
326
357
|
else
|
327
|
-
@colors = [
|
358
|
+
@colors = [@fill] * count
|
328
359
|
end
|
329
360
|
clearCache__
|
330
361
|
elsif @polygon
|
331
362
|
@polygon = @polygon.transform do |polylines|
|
332
|
-
polylines.map {|pl| pl.with colors: pl.points.map {
|
363
|
+
polylines.map {|pl| pl.with colors: pl.points.map {@fill}}
|
333
364
|
end
|
334
365
|
end
|
335
366
|
nil
|
336
367
|
end
|
337
368
|
|
338
|
-
#
|
339
|
-
|
340
|
-
|
369
|
+
# Sets the stroke color.
|
370
|
+
#
|
371
|
+
# @overload setStroke(gray)
|
372
|
+
# @overload setStroke(gray, alpha)
|
373
|
+
# @overload setStroke(r, g, b)
|
374
|
+
# @overload setStroke(r, g, b, alpha)
|
375
|
+
#
|
376
|
+
# @param gray [Integer] gray value (0..255)
|
377
|
+
# @param r [Integer] red value (0..255)
|
378
|
+
# @param g [Integer] green value (0..255)
|
379
|
+
# @param b [Integer] blue value (0..255)
|
380
|
+
# @param alpha [Integer] alpha value (0..255)
|
381
|
+
#
|
382
|
+
# @return [nil] nil
|
383
|
+
#
|
384
|
+
# @see https://processing.org/reference/PShape_setStroke_.html
|
385
|
+
#
|
386
|
+
def setStroke(*args)
|
387
|
+
stroke(*args)
|
388
|
+
nil
|
389
|
+
end
|
390
|
+
|
391
|
+
# Sets the stroke weight.
|
392
|
+
#
|
393
|
+
# @param weight [Numeric] stroke weight
|
394
|
+
#
|
395
|
+
# @return [nil] nil
|
396
|
+
#
|
397
|
+
def setStrokeWeight(weight)
|
398
|
+
@strokeWeight = weight
|
399
|
+
nil
|
400
|
+
end
|
401
|
+
|
402
|
+
# Sets the stroke cap.
|
403
|
+
#
|
404
|
+
# @param cap [ROUND, SQUARE, PROJECT] stroke cap
|
405
|
+
#
|
406
|
+
# @return [nil] nil
|
407
|
+
#
|
408
|
+
def setStrokeCap(cap)
|
409
|
+
@strokeCap = cap
|
410
|
+
nil
|
411
|
+
end
|
412
|
+
|
413
|
+
# Sets the stroke join.
|
414
|
+
#
|
415
|
+
# @param join [MITER, BEVEL, ROUND] stroke join
|
416
|
+
#
|
417
|
+
# @return [nil] nil
|
418
|
+
#
|
419
|
+
def setStrokeJoin(join)
|
420
|
+
@strokeJoin = join
|
421
|
+
nil
|
341
422
|
end
|
342
423
|
|
343
424
|
# Adds a new child shape.
|
@@ -505,24 +586,36 @@ module Processing
|
|
505
586
|
|
506
587
|
# @private
|
507
588
|
def draw__(painter, x, y, w = nil, h = nil)
|
508
|
-
poly = getInternal__
|
589
|
+
p, poly = painter, getInternal__
|
509
590
|
|
510
|
-
|
591
|
+
matrix_ = nil
|
511
592
|
if @matrix && (poly || @children)
|
512
|
-
|
513
|
-
|
593
|
+
matrix_ = p.matrix
|
594
|
+
p.matrix = matrix_ * @matrix
|
514
595
|
end
|
515
596
|
|
516
597
|
if poly
|
598
|
+
f_ = s_ = sw_ = sc_ = sj_ = nil
|
599
|
+
f_, p.fill = p.fill, '#fff' if @fill
|
600
|
+
s_, p.stroke = p.stroke, @stroke if @stroke
|
601
|
+
sw_, p.stroke_width = p.stroke_width, @strokeWeight if @strokeWeight
|
602
|
+
sc_, p.stroke_cap = p.stroke_cap, @strokeCap if @strokeCap
|
603
|
+
sj_, p.stroke_join = p.stroke_join, @strokeJoin if @strokeJoin
|
517
604
|
if w || h
|
518
|
-
|
605
|
+
p.polygon poly, x, y, w,h
|
519
606
|
else
|
520
|
-
|
607
|
+
p.polygon poly, x, y
|
521
608
|
end
|
609
|
+
p.fill = f_ if f_
|
610
|
+
p.stroke = s_ if s_
|
611
|
+
p.stroke_width = sw_ if sw_
|
612
|
+
p.stroke_cap = sc_ if sc_
|
613
|
+
p.stroke_join = sj_ if sj_
|
522
614
|
end
|
523
|
-
@children&.each {|o| o.draw__ painter, x, y, w, h}
|
524
615
|
|
525
|
-
|
616
|
+
@children&.each {|o| o.draw__ p, x, y, w, h}
|
617
|
+
|
618
|
+
p.matrix = matrix_ if matrix_
|
526
619
|
end
|
527
620
|
|
528
621
|
# @private
|
@@ -0,0 +1,248 @@
|
|
1
|
+
module Processing
|
2
|
+
|
3
|
+
|
4
|
+
# @private
|
5
|
+
class SVGLoader
|
6
|
+
|
7
|
+
def initialize(context)
|
8
|
+
@c, @cc = context, context.class
|
9
|
+
end
|
10
|
+
|
11
|
+
def load(filename)
|
12
|
+
parse File.read(filename)
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse(xml)
|
16
|
+
addGroup nil, REXML::Document.new(xml).elements.first
|
17
|
+
end
|
18
|
+
|
19
|
+
def addGroup(parent, e, **attribs)
|
20
|
+
group = @c.createShape @cc::GROUP
|
21
|
+
attribs = getAttribs e, attribs
|
22
|
+
e.elements.each do |child|
|
23
|
+
case child.name.to_sym
|
24
|
+
when :g, :a then addGroup group, child, **attribs
|
25
|
+
when :line then addLine group, child, **attribs
|
26
|
+
when :rect then addRect group, child, **attribs
|
27
|
+
when :circle then addCircle group, child, **attribs
|
28
|
+
when :ellipse then addEllipse group, child, **attribs
|
29
|
+
when :polyline then addPolyline group, child, **attribs
|
30
|
+
when :polygon then addPolyline group, child, true, **attribs
|
31
|
+
when :path then addPath group, child, **attribs
|
32
|
+
end
|
33
|
+
end
|
34
|
+
parent.addChild group if parent
|
35
|
+
group
|
36
|
+
end
|
37
|
+
|
38
|
+
def addLine(parent, e, **attribs)
|
39
|
+
x1, y1 = float(e, :x1), float(e, :y1)
|
40
|
+
x2, y2 = float(e, :x2), float(e, :y2)
|
41
|
+
s = @c.createLineShape__ x1, y1, x2, y2
|
42
|
+
applyAttribs s, e, attribs
|
43
|
+
parent.addChild s
|
44
|
+
end
|
45
|
+
|
46
|
+
def addRect(parent, e, **attribs)
|
47
|
+
x, y = float(e, :x), float(e, :y)
|
48
|
+
w, h = float(e, :width), float(e, :height)
|
49
|
+
rx, ry = float(e, :rx), float(e, :ry)
|
50
|
+
s = @c.createRectShape__ x, y, w, h, (rx || ry), mode: @cc::CORNER
|
51
|
+
applyAttribs s, e, attribs
|
52
|
+
parent.addChild s
|
53
|
+
end
|
54
|
+
|
55
|
+
def addCircle(parent, e, **attribs)
|
56
|
+
cx, cy = float(e, :cx), float(e, :cy)
|
57
|
+
r = float(e, :r)
|
58
|
+
s = @c.createEllipseShape__ cx, cy, r * 2, r * 2, mode: @cc::CENTER
|
59
|
+
applyAttribs s, e, attribs
|
60
|
+
parent.addChild s
|
61
|
+
end
|
62
|
+
|
63
|
+
def addEllipse(parent, e, **attribs)
|
64
|
+
cx, cy = float(e, :cx), float(e, :cy)
|
65
|
+
rx, ry = float(e, :rx), float(e, :ry)
|
66
|
+
s = @c.createEllipseShape__ cx, cy, rx * 2, ry * 2, mode: @cc::CENTER
|
67
|
+
applyAttribs s, e, attribs
|
68
|
+
parent.addChild s
|
69
|
+
end
|
70
|
+
|
71
|
+
def addPolyline(parent, e, close = false, **attribs)
|
72
|
+
points = e[:points] or raise Error, "missing 'points'"
|
73
|
+
scanner = StringScanner.new points
|
74
|
+
child = @c.createShape
|
75
|
+
child.beginShape
|
76
|
+
|
77
|
+
skipSpaces scanner
|
78
|
+
until scanner.eos?
|
79
|
+
child.vertex(*nextPos(scanner))
|
80
|
+
skipSpaces scanner
|
81
|
+
end
|
82
|
+
|
83
|
+
child.endShape close ? @cc::CLOSE : @cc::OPEN
|
84
|
+
applyAttribs child, e, attribs
|
85
|
+
parent.addChild child
|
86
|
+
end
|
87
|
+
|
88
|
+
def applyAttribs(shape, e, attribs)
|
89
|
+
a = getAttribs e, attribs
|
90
|
+
shape.setFill a[:fill] || :black
|
91
|
+
shape.setStroke a[:stroke] || :none
|
92
|
+
shape.setStrokeWeight a[:strokeWeight] || 1
|
93
|
+
shape.setStrokeCap a[:strokeCap] || @cc::SQUARE
|
94
|
+
shape.setStrokeJoin a[:strokeJoin] || @cc::MITER
|
95
|
+
end
|
96
|
+
|
97
|
+
def getAttribs(e, attribs)
|
98
|
+
@caps ||= {
|
99
|
+
'butt' => @cc::SQUARE,
|
100
|
+
'round' => @cc::ROUND,
|
101
|
+
'square' => @cc::PROJECT
|
102
|
+
}
|
103
|
+
@joins ||= {
|
104
|
+
'miter' => @cc::MITER,
|
105
|
+
'miter-clip' => @cc::MITER,
|
106
|
+
'round' => @cc::ROUND,
|
107
|
+
'bevel' => @cc::BEVEL,
|
108
|
+
'arcs' => @cc::MITER
|
109
|
+
}
|
110
|
+
attribs.merge({
|
111
|
+
fill: e[:fill],
|
112
|
+
stroke: e[:stroke],
|
113
|
+
strokeWeight: e[:'stroke-width'],
|
114
|
+
strokeCap: @caps[ e[:'stroke-linecap']],
|
115
|
+
strokeJoin: @joins[e[:'stroke-linejoin']]
|
116
|
+
}.compact)
|
117
|
+
end
|
118
|
+
|
119
|
+
def int(e, key, defval = 0)
|
120
|
+
e[key]&.to_i || defval
|
121
|
+
end
|
122
|
+
|
123
|
+
def float(e, key, defval = 0)
|
124
|
+
e[key]&.to_f || defval
|
125
|
+
end
|
126
|
+
|
127
|
+
def addPath(parent, e, **attribs)
|
128
|
+
data = e[:d] or raise Error, "missing 'd'"
|
129
|
+
scanner = StringScanner.new data
|
130
|
+
skipSpaces scanner
|
131
|
+
|
132
|
+
child = nil
|
133
|
+
close = false
|
134
|
+
beginChild = -> {
|
135
|
+
close = false
|
136
|
+
child = @c.createShape
|
137
|
+
child.beginShape
|
138
|
+
}
|
139
|
+
endChild = -> {
|
140
|
+
if child# && child.getVertexCount >= 2
|
141
|
+
child.endShape close ? @cc::CLOSE : @cc::OPEN
|
142
|
+
applyAttribs child, e, attribs
|
143
|
+
parent.addChild child
|
144
|
+
end
|
145
|
+
}
|
146
|
+
|
147
|
+
lastCommand = nil
|
148
|
+
lastX, lastY = 0, 0
|
149
|
+
lastCX, lastCY = 0, 0
|
150
|
+
until scanner.eos?
|
151
|
+
command = nextCommand scanner
|
152
|
+
if command =~ /^[Mm]$/
|
153
|
+
endChild.call
|
154
|
+
beginChild.call
|
155
|
+
end
|
156
|
+
raise Error, "no leading 'M' or 'm'" unless child
|
157
|
+
|
158
|
+
command ||= lastCommand
|
159
|
+
case command
|
160
|
+
when 'M', 'm'
|
161
|
+
lastX, lastY = nextPos scanner, lastX, lastY, command == 'm'
|
162
|
+
child.vertex lastX, lastY
|
163
|
+
when 'L', 'l'
|
164
|
+
lastX, lastY = nextPos scanner, lastX, lastY, command == 'l'
|
165
|
+
child.vertex lastX, lastY
|
166
|
+
when 'H', 'h'
|
167
|
+
lastX = nextNum scanner, lastX, command == 'h'
|
168
|
+
child.vertex lastX, lastY
|
169
|
+
when 'V', 'v'
|
170
|
+
lastY = nextNum scanner, lastY, command == 'v'
|
171
|
+
child.vertex lastX, lastY
|
172
|
+
when 'Q', 'q'
|
173
|
+
relative = command == 'q'
|
174
|
+
lastCX, lastCY = nextPos scanner, lastX, lastY, relative
|
175
|
+
lastX, lastY = nextPos scanner, lastX, lastY, relative
|
176
|
+
child.quadraticVertex lastCX, lastCY, lastX, lastY
|
177
|
+
when 'T', 't'
|
178
|
+
lastCX, lastCY =
|
179
|
+
if lastCommand =~ /[QqTt]/
|
180
|
+
[lastX + (lastX - lastCX), lastY + (lastY - lastCY)]
|
181
|
+
else
|
182
|
+
[lastX, lastY]
|
183
|
+
end
|
184
|
+
lastX, lastY = nextPos scanner, lastX, lastY, command == 't'
|
185
|
+
child.quadraticVertex lastCX, lastCY, lastX, lastY
|
186
|
+
when 'C', 'c'
|
187
|
+
relative = command == 'c'
|
188
|
+
cx, cy = nextPos scanner, lastX, lastY, relative
|
189
|
+
lastCX, lastCY = nextPos scanner, lastX, lastY, relative
|
190
|
+
lastX, lastY = nextPos scanner, lastX, lastY, relative
|
191
|
+
child.bezierVertex cx, cy, lastCX, lastCY, lastX, lastY
|
192
|
+
when 'S', 's'
|
193
|
+
cx, cy =
|
194
|
+
if lastCommand =~ /[CcSs]/
|
195
|
+
[lastX + (lastX - lastCX), lastY + (lastY - lastCY)]
|
196
|
+
else
|
197
|
+
[lastX, lastY]
|
198
|
+
end
|
199
|
+
relative = command == 's'
|
200
|
+
lastCX, lastCY = nextPos scanner, lastX, lastY, relative
|
201
|
+
lastX, lastY = nextPos scanner, lastX, lastY, relative
|
202
|
+
child.bezierVertex cx, cy, lastCX, lastCY, lastX, lastY
|
203
|
+
when 'A', 'a'
|
204
|
+
rx, ry = nextPos scanner
|
205
|
+
a, b, c = nextNum(scanner), nextNum(scanner), nextNum(scanner)
|
206
|
+
lastX, lastY = nextPos scanner, lastX, lastY, command == 'a'
|
207
|
+
child.vertex lastX, lastY
|
208
|
+
when 'Z', 'z'
|
209
|
+
v0 = child.getVertex 0
|
210
|
+
lastX, lastY = v0 ? [v0.x, v0.y] : [0, 0]
|
211
|
+
close = true
|
212
|
+
end
|
213
|
+
lastCommand = command
|
214
|
+
end
|
215
|
+
endChild.call
|
216
|
+
end
|
217
|
+
|
218
|
+
def nextCommand(scanner)
|
219
|
+
w = scanner.scan(/[[:alpha:]]/)
|
220
|
+
skipSpaces scanner
|
221
|
+
w
|
222
|
+
end
|
223
|
+
|
224
|
+
def nextNum(scanner, base = 0, relative = true)
|
225
|
+
n = scanner.scan(/(?:[\+\-]\s*)?\d*(?:\.\d+)?/)&.strip&.to_f
|
226
|
+
raise Error, 'invalid path' unless n
|
227
|
+
skipSpaces scanner
|
228
|
+
n + (relative ? base : 0)
|
229
|
+
end
|
230
|
+
|
231
|
+
def nextPos(scanner, baseX = 0, baseY = 0, relative = true)
|
232
|
+
[nextNum(scanner, baseX, relative), nextNum(scanner, baseY, relative)]
|
233
|
+
end
|
234
|
+
|
235
|
+
def skipSpaces(scanner)
|
236
|
+
scanner.scan(/\s*,?\s*/)
|
237
|
+
end
|
238
|
+
|
239
|
+
class Error < StandardError
|
240
|
+
def initialize(message = "error")
|
241
|
+
super "SVG: #{message}"
|
242
|
+
end
|
243
|
+
end# Error
|
244
|
+
|
245
|
+
end# SVG
|
246
|
+
|
247
|
+
|
248
|
+
end# Processing
|
data/processing.gemspec
CHANGED
@@ -25,10 +25,10 @@ Gem::Specification.new do |s|
|
|
25
25
|
s.platform = Gem::Platform::RUBY
|
26
26
|
s.required_ruby_version = '>= 3.0.0'
|
27
27
|
|
28
|
-
s.add_runtime_dependency 'xot', '~> 0.
|
29
|
-
s.add_runtime_dependency 'rucy', '~> 0.
|
30
|
-
s.add_runtime_dependency 'rays', '~> 0.
|
31
|
-
s.add_runtime_dependency 'reflexion', '~> 0.
|
28
|
+
s.add_runtime_dependency 'xot', '~> 0.2'
|
29
|
+
s.add_runtime_dependency 'rucy', '~> 0.2'
|
30
|
+
s.add_runtime_dependency 'rays', '~> 0.2'
|
31
|
+
s.add_runtime_dependency 'reflexion', '~> 0.2'
|
32
32
|
|
33
33
|
s.files = `git ls-files`.split $/
|
34
34
|
s.test_files = s.files.grep %r{^(test|spec|features)/}
|
data/test/{p5.rb → browser.rb}
RENAMED
@@ -13,6 +13,30 @@ def browser(width, height, headless: true)
|
|
13
13
|
hash[key] ||= Ferrum::Browser.new headless: headless, window_size: [width, height]
|
14
14
|
end
|
15
15
|
|
16
|
+
def get_svg_html(width, height, svg_xml)
|
17
|
+
<<~END
|
18
|
+
<html>
|
19
|
+
<head>
|
20
|
+
<style type="text/css">
|
21
|
+
body {margin: 0;}
|
22
|
+
</style>
|
23
|
+
<script type="text/javascript">
|
24
|
+
window.onload = function() {
|
25
|
+
let e = document.createElement("span");
|
26
|
+
e.id = 'completed';
|
27
|
+
document.body.appendChild(e);
|
28
|
+
}
|
29
|
+
</script>
|
30
|
+
</head>
|
31
|
+
<body>
|
32
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 #{width} #{height}">
|
33
|
+
#{svg_xml}
|
34
|
+
</svg>
|
35
|
+
</body>
|
36
|
+
</html>
|
37
|
+
END
|
38
|
+
end
|
39
|
+
|
16
40
|
def get_p5rb_html(width, height, draw_src, webgl: false)
|
17
41
|
<<~END
|
18
42
|
<html>
|
@@ -57,17 +81,17 @@ def sleep_until(try: 3, timeout: 10, &block)
|
|
57
81
|
limit = now[timeout]
|
58
82
|
try -= 1
|
59
83
|
next if try > 0
|
60
|
-
raise 'Drawing timed out in
|
84
|
+
raise 'Drawing timed out in browser'
|
61
85
|
end
|
62
86
|
sleep 0.1
|
63
87
|
end
|
64
88
|
end
|
65
89
|
|
66
|
-
def
|
90
|
+
def draw_on_browser(width, height, path, html, headless: true)
|
67
91
|
b = browser width, height, headless: headless
|
68
92
|
unless File.exist? path
|
69
93
|
b.reset
|
70
|
-
b.main_frame.content =
|
94
|
+
b.main_frame.content = html
|
71
95
|
sleep_until do
|
72
96
|
b.evaluate 'document.querySelector("#completed") != null'
|
73
97
|
end
|
@@ -75,3 +99,13 @@ def draw_p5rb(width, height, draw_src, path, headless: true, webgl: false)
|
|
75
99
|
end
|
76
100
|
b.device_pixel_ratio
|
77
101
|
end
|
102
|
+
|
103
|
+
def draw_svg(width, height, svg_xml, path, headless: true)
|
104
|
+
html = get_svg_html width, height, svg_xml
|
105
|
+
draw_on_browser width, height, path, html, headless: headless
|
106
|
+
end
|
107
|
+
|
108
|
+
def draw_p5rb(width, height, draw_src, path, headless: true, webgl: false)
|
109
|
+
html = get_p5rb_html width, height, draw_src, webgl: webgl
|
110
|
+
draw_on_browser width, height, path, html, headless: headless
|
111
|
+
end
|
data/test/helper.rb
CHANGED
@@ -20,9 +20,11 @@ DEFAULT_DRAW_HEADER = <<~END
|
|
20
20
|
strokeWeight 50
|
21
21
|
END
|
22
22
|
|
23
|
+
THRESHOLD_TO_BE_FIXED = 0.0
|
23
24
|
|
24
|
-
|
25
|
-
|
25
|
+
|
26
|
+
def test_with_browser?()
|
27
|
+
(ENV['TEST_WITH_BROWSER'] || '0') != '0'
|
26
28
|
end
|
27
29
|
|
28
30
|
def md5(s)
|
@@ -107,16 +109,47 @@ def assert_equal_draw(
|
|
107
109
|
assert_equal_pixels e, a, threshold: threshold
|
108
110
|
end
|
109
111
|
|
112
|
+
def assert_svg_draw(
|
113
|
+
svg_xml,
|
114
|
+
width: 1000, height: 1000, threshold: 0.99, label: test_label, **kwargs)
|
115
|
+
|
116
|
+
source = <<~END
|
117
|
+
background 255
|
118
|
+
shape Processing::SVGLoader.new(self).parse <<~SVG
|
119
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
120
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
121
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
122
|
+
viewBox="0 0 100 100">
|
123
|
+
#{svg_xml}
|
124
|
+
</svg>
|
125
|
+
SVG
|
126
|
+
END
|
127
|
+
assert_draw_on_browser(
|
128
|
+
source, width, height, threshold, label, **kwargs
|
129
|
+
) do |path|
|
130
|
+
draw_svg width, height, svg_xml, path, **kwargs
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
110
134
|
def assert_p5_draw(
|
111
135
|
*sources, default_header: DEFAULT_DRAW_HEADER,
|
112
136
|
width: 1000, height: 1000, threshold: 0.99, label: test_label, **kwargs)
|
113
137
|
|
114
|
-
return unless test_with_p5?
|
115
|
-
|
116
138
|
source = [default_header, *sources].compact.join("\n")
|
117
|
-
|
139
|
+
assert_draw_on_browser(
|
140
|
+
source, width, height, threshold, label, **kwargs
|
141
|
+
) do |path|
|
142
|
+
draw_p5rb width, height, source, path, **kwargs
|
143
|
+
end
|
144
|
+
end
|
118
145
|
|
119
|
-
|
146
|
+
def assert_draw_on_browser(
|
147
|
+
source, width, height, threshold, label, **kwargs, &draw_on_browser)
|
148
|
+
|
149
|
+
return unless test_with_browser?
|
150
|
+
|
151
|
+
path = draw_output_path "#{label}_expected", source
|
152
|
+
pd = draw_on_browser.call path
|
120
153
|
actual = test_draw source, width: width, height: height, pixelDensity: pd
|
121
154
|
actual.save path.sub('_expected', '_actual')
|
122
155
|
|
@@ -136,4 +169,4 @@ def assert_p5_fill_stroke(*sources, **kwargs)
|
|
136
169
|
end
|
137
170
|
|
138
171
|
|
139
|
-
require_relative '
|
172
|
+
require_relative 'browser' if test_with_browser?
|