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.
@@ -9,9 +9,10 @@ module Processing
9
9
 
10
10
  # @private
11
11
  def initialize(polygon = nil, children = nil, context: nil)
12
- @polygon, @children = polygon, children
13
- @context = context || Context.context__
14
- @visible, @fill, @matrix = true, nil, nil
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
- @close = close == GraphicsContext::CLOSE || @contours.size > 0
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.getFill__
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.rawColor__(*args)
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 fill(gray)
306
- # @overload fill(gray, alpha)
307
- # @overload fill(r, g, b)
308
- # @overload fill(r, g, b, alpha)
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
- color = @context.rawColor__(*args)
352
+ fill(*args)
322
353
  count = getVertexCount
323
354
  if count > 0
324
355
  if @colors
325
- @colors.fill color
356
+ @colors.fill @fill
326
357
  else
327
- @colors = [color] * count
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 {color}}
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
- # @private
339
- def setStroke__()
340
- raise NotImplementedError
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
- backup = nil
591
+ matrix_ = nil
511
592
  if @matrix && (poly || @children)
512
- backup = painter.matrix
513
- painter.matrix = backup * @matrix
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
- painter.polygon poly, x, y, w,h
605
+ p.polygon poly, x, y, w,h
519
606
  else
520
- painter.polygon poly, x, y
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
- painter.matrix = backup if backup
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.1.42'
29
- s.add_runtime_dependency 'rucy', '~> 0.1.44'
30
- s.add_runtime_dependency 'rays', '~> 0.1.49'
31
- s.add_runtime_dependency 'reflexion', '~> 0.1.57'
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)/}
@@ -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 p5.rb'
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 draw_p5rb(width, height, draw_src, path, headless: true, webgl: false)
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 = get_p5rb_html width, height, draw_src, webgl: webgl
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
- def test_with_p5?()
25
- ENV['TEST_WITH_P5'] == '1'
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
- path = draw_output_path "#{label}_expected", source
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
- pd = draw_p5rb width, height, source, path, **kwargs
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 'p5' if test_with_p5?
172
+ require_relative 'browser' if test_with_browser?