xrvg 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +10 -2
- data/examples/arcrecurse2.rb +1 -1
- data/examples/gradientgeo.rb +1 -1
- data/examples/hellocrown2.rb +1 -1
- data/examples/palette_circle.rb +1 -1
- data/examples/sample.rb +1 -1
- data/lib/bezier.rb +588 -0
- data/lib/bezierbuilders.rb +210 -0
- data/lib/beziermotifs.rb +121 -0
- data/lib/bezierspline.rb +235 -0
- data/lib/beziertools.rb +245 -0
- data/lib/color.rb +567 -0
- data/lib/fitting.rb +203 -0
- data/lib/frame.rb +33 -0
- data/lib/geovariety.rb +128 -0
- data/lib/interbezier.rb +87 -0
- data/lib/intersection.rb +89 -0
- data/lib/render.rb +266 -0
- data/lib/samplation.rb +44 -1
- data/lib/shape.rb +421 -0
- data/lib/spiral.rb +89 -0
- data/lib/style.rb +76 -0
- data/lib/utils.rb +1 -17
- data/lib/xrvg.rb +47 -0
- data/test/test_attributable.rb +19 -0
- data/test/test_bezier.rb +7 -0
- data/test/test_color.rb +36 -1
- data/test/test_intersection.rb +42 -0
- data/test/test_sample.rb +56 -5
- data/test/test_spiral.rb +8 -0
- data/test/test_utils.rb +1 -32
- metadata +19 -2
data/lib/shape.rb
ADDED
@@ -0,0 +1,421 @@
|
|
1
|
+
# shape.rb file
|
2
|
+
# See
|
3
|
+
# - Shape interface
|
4
|
+
# - Curve interface
|
5
|
+
# - Line class
|
6
|
+
# - Circle class
|
7
|
+
require 'utils'
|
8
|
+
require 'frame'
|
9
|
+
require 'geometry2D'
|
10
|
+
|
11
|
+
|
12
|
+
module XRVG
|
13
|
+
# Shape abstract interface
|
14
|
+
# = Intro
|
15
|
+
# To provide a set of services a shape class must provide
|
16
|
+
class Shape
|
17
|
+
include Attributable
|
18
|
+
|
19
|
+
# must return the contour of the shape, of Curve type
|
20
|
+
#
|
21
|
+
# abstract
|
22
|
+
#
|
23
|
+
# not yet used
|
24
|
+
def contour( *args )
|
25
|
+
raise NotImplementedError.new("#{self.class.name}#contour is an abstract method.")
|
26
|
+
end
|
27
|
+
|
28
|
+
# must return the svg description of the shape
|
29
|
+
#
|
30
|
+
# abstract
|
31
|
+
#
|
32
|
+
# must be defined
|
33
|
+
def svg()
|
34
|
+
raise NotImplementedError.new("#{self.class.name}#svg is an abstract method.")
|
35
|
+
end
|
36
|
+
|
37
|
+
# must return the enclosing box of the shape, that is [xmin, ymin, xmax, ymax]
|
38
|
+
#
|
39
|
+
# abstract
|
40
|
+
#
|
41
|
+
# must be defined
|
42
|
+
def viewbox()
|
43
|
+
raise NotImplementedError.new("#{self.class.name}#viewbox is an abstract method.")
|
44
|
+
end
|
45
|
+
|
46
|
+
# compute size of the shape, from viewbox
|
47
|
+
def size()
|
48
|
+
xmin, ymin, xmax, ymax = self.viewbox
|
49
|
+
return [xmax-xmin, ymax-ymin]
|
50
|
+
end
|
51
|
+
|
52
|
+
# return the default style for a Shape instance
|
53
|
+
#
|
54
|
+
# is done on instance, because for Curve for example, strokewidth is proportional to length
|
55
|
+
def default_style()
|
56
|
+
return Style[:fill, Color.black ]
|
57
|
+
end
|
58
|
+
|
59
|
+
# compute the "surface" of the viewbox of the shape
|
60
|
+
#
|
61
|
+
# use size method
|
62
|
+
def surface
|
63
|
+
width, height = self.size
|
64
|
+
return width * height
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
# Curve abstract interface
|
70
|
+
# = Intro
|
71
|
+
# To define a set of services a curve class must provide
|
72
|
+
class Curve < Shape
|
73
|
+
# must compute the point at curve abscissa
|
74
|
+
#
|
75
|
+
# abstract
|
76
|
+
#
|
77
|
+
# must be defined
|
78
|
+
def point( abscissa, container=nil )
|
79
|
+
raise NotImplementedError.new("#{self.class.name}#curve is an abstract method.")
|
80
|
+
end
|
81
|
+
|
82
|
+
# must compute the tangent at curve abscissa
|
83
|
+
#
|
84
|
+
# abstract
|
85
|
+
#
|
86
|
+
# must be defined
|
87
|
+
def tangent( abscissa, container=nil )
|
88
|
+
raise NotImplementedError.new("#{self.class.name}#tangent is an abstract method.")
|
89
|
+
end
|
90
|
+
|
91
|
+
# must compute the acceleration at curve abscissa
|
92
|
+
#
|
93
|
+
# abstract
|
94
|
+
#
|
95
|
+
# must be defined
|
96
|
+
def acc( abscissa, container=nil )
|
97
|
+
raise NotImplementedError.new("#{self.class.name}#acc is an abstract method.")
|
98
|
+
end
|
99
|
+
|
100
|
+
# must return the length at abscissa, or total length if abscissa nil
|
101
|
+
#
|
102
|
+
# abstract
|
103
|
+
#
|
104
|
+
# must be defined
|
105
|
+
def length(abscissa=nil)
|
106
|
+
raise NotImplementedError.new("#{self.class.name}#length is an abstract method.")
|
107
|
+
end
|
108
|
+
|
109
|
+
# default style of a curve, as stroked with stroke width 1% of length
|
110
|
+
def default_style
|
111
|
+
return Style[ :stroke, Color.black, :strokewidth, self.length / 100.0 ]
|
112
|
+
end
|
113
|
+
|
114
|
+
# compute the rotation at curve abscissa, or directly from tangent (for frame computation speed up),
|
115
|
+
# as angle between tangent0 angle and tangent( abscissa ) (or tangent) angle
|
116
|
+
def rotation( abscissa, tangent=nil )
|
117
|
+
if not tangent
|
118
|
+
tangent = self.tangent( abscissa )
|
119
|
+
end
|
120
|
+
return (tangent.angle - self.tangent0_angle)
|
121
|
+
end
|
122
|
+
|
123
|
+
# must compute the scale at curve abscissa, or directly from tangent (for frame computation speed up)
|
124
|
+
# as ratio between tangent0 size and tangent( abscissa ) (or tangent) size
|
125
|
+
def scale( abscissa, tangent=nil )
|
126
|
+
if not tangent
|
127
|
+
tangent = self.tangent( abscissa )
|
128
|
+
end
|
129
|
+
result = 0.0
|
130
|
+
if not self.tangent0_length == 0.0
|
131
|
+
result = (tangent.r / self.tangent0_length)
|
132
|
+
end
|
133
|
+
return result
|
134
|
+
end
|
135
|
+
|
136
|
+
def tangent0
|
137
|
+
if not @tangent0
|
138
|
+
@tangent0 = self.tangent( 0.0 )
|
139
|
+
end
|
140
|
+
return @tangent0
|
141
|
+
end
|
142
|
+
|
143
|
+
# TODO : must be cached in vector
|
144
|
+
def tangent0_angle
|
145
|
+
if not @tangent0_angle
|
146
|
+
@tangent0_angle = self.tangent0.angle
|
147
|
+
end
|
148
|
+
return @tangent0_angle
|
149
|
+
end
|
150
|
+
|
151
|
+
# TODO : must be cached in vector
|
152
|
+
def tangent0_length
|
153
|
+
if not @tangent0_length
|
154
|
+
@tangent0_length = self.tangent0.r
|
155
|
+
end
|
156
|
+
return @tangent0_length
|
157
|
+
end
|
158
|
+
|
159
|
+
# compute frame vector at abscissa t, that is [curve.point( t ), curve.tangent( t ) ]
|
160
|
+
def framev( t )
|
161
|
+
return [self.point( t ), self.tangent( t ) ]
|
162
|
+
end
|
163
|
+
|
164
|
+
# compute frame at abscissa t
|
165
|
+
def frame( t )
|
166
|
+
point, tangent = self.framev( t )
|
167
|
+
return Frame[ :center, point, :vector, tangent, :rotation, self.rotation( nil, tangent ), :scale, self.scale( nil, tangent ) ]
|
168
|
+
end
|
169
|
+
|
170
|
+
# compute normal at abscissa t
|
171
|
+
#
|
172
|
+
# do tangent.ortho
|
173
|
+
def normal( t )
|
174
|
+
return self.tangent( t ).ortho
|
175
|
+
end
|
176
|
+
|
177
|
+
# compute normal acceleration at abscissa t
|
178
|
+
def acc_normal( t )
|
179
|
+
normal = self.normal( t ).norm
|
180
|
+
result = self.acc( t ).inner_product( normal )
|
181
|
+
return result
|
182
|
+
end
|
183
|
+
|
184
|
+
# compute curvature at abscissa t
|
185
|
+
def curvature( t )
|
186
|
+
acc_normal = self.acc_normal( t )
|
187
|
+
if acc_normal == 0.0
|
188
|
+
return 0.0
|
189
|
+
end
|
190
|
+
return 1.0 / (self.tangent( t ).r / acc_normal )
|
191
|
+
end
|
192
|
+
|
193
|
+
# shortcut method to map frames from abscissas
|
194
|
+
def frames (abscissas)
|
195
|
+
return abscissas.map { |abscissa| self.frame( abscissa ) }
|
196
|
+
end
|
197
|
+
|
198
|
+
# shortcut method to map points from abscissas
|
199
|
+
def points (abscissas)
|
200
|
+
result = abscissas.map { |abscissa| self.point( abscissa ) }
|
201
|
+
return result
|
202
|
+
end
|
203
|
+
|
204
|
+
# shortcut method to map tangents from abscissas
|
205
|
+
def tangents (abscissas)
|
206
|
+
return abscissas.map { |abscissa| self.tangent( abscissa ) }
|
207
|
+
end
|
208
|
+
|
209
|
+
# shortcut method, map of normal
|
210
|
+
def normals( indexes )
|
211
|
+
return indexes.map {|i| self.normal( i )}
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
# Line class
|
218
|
+
# = Intro
|
219
|
+
# Used to draw polylines and polygons
|
220
|
+
# = Attributes
|
221
|
+
# attribute :points, [V2D[0.0, 0.0], V2D[1.0, 1.0]]
|
222
|
+
# WARNING : getter "points" is not defined, because is defined as Curve.points( abscissa ) !!!
|
223
|
+
# = Example
|
224
|
+
# line = Line[ :points, [V2D::O, V2D::X] ]
|
225
|
+
class Line < Curve
|
226
|
+
attribute :points, [V2D[0.0, 0.0], V2D[1.0, 1.0]]
|
227
|
+
|
228
|
+
def initialize (*args) #:nodoc:
|
229
|
+
super( *args )
|
230
|
+
self.init_tangents
|
231
|
+
end
|
232
|
+
|
233
|
+
def init_tangents #:nodoc:
|
234
|
+
index = 0
|
235
|
+
@tangents = Array.new
|
236
|
+
@points.pairs { |p1, p2|
|
237
|
+
@tangents[ index ] = (p2-p1).norm
|
238
|
+
index += 1
|
239
|
+
}
|
240
|
+
end
|
241
|
+
|
242
|
+
# return the total length of the polyline
|
243
|
+
def length
|
244
|
+
if not @length
|
245
|
+
@length = 0.0
|
246
|
+
@points.pairs do |p1, p2|
|
247
|
+
@length += (p1 - p2).r
|
248
|
+
end
|
249
|
+
end
|
250
|
+
return @length
|
251
|
+
end
|
252
|
+
|
253
|
+
# compute line point at abscissa
|
254
|
+
# Line[ :points, [V2D::O, V2D::X] ].point( 0.3 ) => V2D[0.0,0.3]
|
255
|
+
def point (abscissa, container=nil)
|
256
|
+
container ||= V2D[]
|
257
|
+
piece1 = abscissa.to_int
|
258
|
+
if piece1 == @points.size - 1
|
259
|
+
container.xy = @points[-1]
|
260
|
+
else
|
261
|
+
abscissa -= piece1
|
262
|
+
cpoints = @points.slice( piece1, 2 )
|
263
|
+
container.xy = [(cpoints[0].x..cpoints[1].x).sample( abscissa ),
|
264
|
+
(cpoints[0].y..cpoints[1].y).sample( abscissa )]
|
265
|
+
end
|
266
|
+
return container
|
267
|
+
end
|
268
|
+
|
269
|
+
# redefining to discriminate between @points and map.point
|
270
|
+
def points(arg=nil)
|
271
|
+
if not arg
|
272
|
+
return @points
|
273
|
+
else
|
274
|
+
super(arg)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# compute line tangent at abscissa
|
279
|
+
def tangent (abscissa, container=nil)
|
280
|
+
container ||= V2D[]
|
281
|
+
container.xy = @tangents[abscissa.to_int]
|
282
|
+
return container
|
283
|
+
end
|
284
|
+
|
285
|
+
# acc V2D.O
|
286
|
+
def acc( abscissa, container=nil )
|
287
|
+
container ||= V2D[]
|
288
|
+
container.xy = [0.0,0.0]
|
289
|
+
return container
|
290
|
+
end
|
291
|
+
|
292
|
+
# compute viewbox of the line
|
293
|
+
#
|
294
|
+
# simply call V2D.viewbox on :points
|
295
|
+
def viewbox
|
296
|
+
return V2D.viewbox( @points )
|
297
|
+
end
|
298
|
+
|
299
|
+
# translate a line of v offset, v being a vector
|
300
|
+
#
|
301
|
+
# return a new line with every point of :points translated
|
302
|
+
def translate( v )
|
303
|
+
return Line[ :points, @points.map {|ext| ext + v } ]
|
304
|
+
end
|
305
|
+
|
306
|
+
# reverse a line
|
307
|
+
#
|
308
|
+
# return a new line with :points reversed
|
309
|
+
def reverse
|
310
|
+
return Line[ :points, @points.reverse ]
|
311
|
+
end
|
312
|
+
|
313
|
+
# return line svg description
|
314
|
+
def svg
|
315
|
+
path = "M #{points[0].x} #{points[0].y} "
|
316
|
+
@points[1..-1].each { |p|
|
317
|
+
path += "L #{p.x} #{p.y}"
|
318
|
+
}
|
319
|
+
return "<path d=\"" + path + "\"/>"
|
320
|
+
end
|
321
|
+
|
322
|
+
include Samplable
|
323
|
+
alias apply_sample point
|
324
|
+
end
|
325
|
+
|
326
|
+
# Circle class
|
327
|
+
# = Intro
|
328
|
+
# define a circle curve
|
329
|
+
# = Attributes
|
330
|
+
# attribute :center, V2D[0.0,0.0]
|
331
|
+
# attribute :radius, 1.0
|
332
|
+
# attribute :initangle, 0.0
|
333
|
+
# = Example
|
334
|
+
# c = Circle[ :center, V2D::O, :radius, 1.0 ] # equiv Circle[]
|
335
|
+
class Circle < Curve
|
336
|
+
attribute :center, V2D[0.0,0.0]
|
337
|
+
attribute :radius, 1.0
|
338
|
+
attribute :initangle, 0.0
|
339
|
+
|
340
|
+
# Circle builder from points diametraly opposed
|
341
|
+
# Circle.diameter( V2D[-1.0,0.0], V2D[1.0,0.0] ) == Circle[ :center, V2D::O, :radius, 1.0 ]
|
342
|
+
def Circle.diameter( p1, p2 )
|
343
|
+
initangle = ( p1 - p2 ).angle
|
344
|
+
return Circle[ :center, (p1 + p2)/2.0, :radius, (p1 - p2).r/2.0, :initangle, initangle ]
|
345
|
+
end
|
346
|
+
|
347
|
+
# compute length of the circle
|
348
|
+
def length
|
349
|
+
if not @length
|
350
|
+
@length = 2.0 * Math::PI * self.radius
|
351
|
+
end
|
352
|
+
return @length
|
353
|
+
end
|
354
|
+
|
355
|
+
# shortcut method to retun center.x
|
356
|
+
def cx
|
357
|
+
return self.center.x
|
358
|
+
end
|
359
|
+
|
360
|
+
# shortcut method to retun center.y
|
361
|
+
def cy
|
362
|
+
return self.center.y
|
363
|
+
end
|
364
|
+
|
365
|
+
# viewbox of the circle
|
366
|
+
def viewbox
|
367
|
+
return [ self.cx - self.radius,
|
368
|
+
self.cy - self.radius,
|
369
|
+
self.cx + self.radius,
|
370
|
+
self.cy + self.radius ]
|
371
|
+
end
|
372
|
+
|
373
|
+
# size of the circle
|
374
|
+
def size
|
375
|
+
return [ self.radius, self.radius ]
|
376
|
+
end
|
377
|
+
|
378
|
+
def rotate( angle )
|
379
|
+
return Circle[:center, self.center, :radius, self.radius, :initangle, self.initangle + angle]
|
380
|
+
end
|
381
|
+
|
382
|
+
# svg description of the circle
|
383
|
+
def svg
|
384
|
+
template = '<circle cx="%cx%" cy="%cy%" r="%r%"/>'
|
385
|
+
return template.subreplace( {"%cx%" => cx,
|
386
|
+
"%cy%" => cy,
|
387
|
+
"%r%" => radius} )
|
388
|
+
end
|
389
|
+
|
390
|
+
# compute point at abscissa
|
391
|
+
def point (abscissa, container=nil)
|
392
|
+
angle = Range::Angle.sample( abscissa ) + @initangle
|
393
|
+
container ||=V2D[]
|
394
|
+
container.x = self.cx + self.radius * Math.cos( angle )
|
395
|
+
container.y = self.cy + self.radius * Math.sin( angle )
|
396
|
+
return container
|
397
|
+
end
|
398
|
+
|
399
|
+
# compute tangent at abscissa
|
400
|
+
def tangent( abscissa, container=nil )
|
401
|
+
angle = Range::Angle.sample( abscissa ) + @initangle
|
402
|
+
container ||=V2D[]
|
403
|
+
container.x = -self.radius * Math.sin( angle )
|
404
|
+
container.y = self.radius * Math.cos( angle )
|
405
|
+
return container
|
406
|
+
end
|
407
|
+
|
408
|
+
# compute acc at abscissa
|
409
|
+
def acc( abscissa, container=nil )
|
410
|
+
angle = Range::Angle.sample( abscissa ) + @initangle
|
411
|
+
container ||=V2D[]
|
412
|
+
container.x = -self.radius * Math.cos( angle )
|
413
|
+
container.y = -self.radius * Math.sin( angle )
|
414
|
+
return container
|
415
|
+
end
|
416
|
+
|
417
|
+
|
418
|
+
include Samplable
|
419
|
+
alias apply_sample point
|
420
|
+
end
|
421
|
+
end
|
data/lib/spiral.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# File dedicated to Spiral curves
|
2
|
+
# - Spiral
|
3
|
+
# - SpiralParam
|
4
|
+
# - SpiralFrise
|
5
|
+
|
6
|
+
require 'shape'
|
7
|
+
|
8
|
+
module XRVG
|
9
|
+
|
10
|
+
# abstract class to define spiral types
|
11
|
+
#
|
12
|
+
# use compute_radius to compute nsamples reference points, before interpolating with SimpleBezier
|
13
|
+
class GSpiral < BezierBuilder
|
14
|
+
attribute :center, V2D::O, V2D
|
15
|
+
attribute :ext, V2D::O + V2D::X, V2D
|
16
|
+
attribute :curvature, 1.0
|
17
|
+
attribute :nsamples, 100
|
18
|
+
attribute :sens, 1.0
|
19
|
+
|
20
|
+
def compute_radius( r0, angle0, curvature, angle )
|
21
|
+
raise NotImplementedError.new("#{self.class.name}#compute_radius is an abstract method.")
|
22
|
+
end
|
23
|
+
|
24
|
+
def maxangle( r0, angle0, curvature )
|
25
|
+
raise NotImplementedError.new("#{self.class.name}#maxangle is an abstract method.")
|
26
|
+
end
|
27
|
+
|
28
|
+
def compute()
|
29
|
+
points = []
|
30
|
+
r0, angle0 = (@ext - @center).coords(:polar)
|
31
|
+
maxangle = self.maxangle( r0, angle0, @curvature )
|
32
|
+
SyncS[(r0..0.0), (angle0..self.sens * maxangle)].samples( self.nsamples ) do |radius, angle|
|
33
|
+
r = self.compute_radius( r0, angle0, @curvature, angle )
|
34
|
+
point = V2D.polar( r, angle )
|
35
|
+
points.push( @center + point )
|
36
|
+
end
|
37
|
+
return SimpleBezier.build( :support, points ).data
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# at angle angle0, r = r0
|
42
|
+
# at angle angle0 + curvature, r = 0.0
|
43
|
+
class SpiralLinear < GSpiral
|
44
|
+
|
45
|
+
def maxangle( r0, angle0, curvature )
|
46
|
+
return (angle0 + curvature)
|
47
|
+
end
|
48
|
+
|
49
|
+
def compute_radius( r0, angle0, curvature, angle )
|
50
|
+
return r0 * (1.0 - ( 1.0 + (Math.exp( - ( angle - angle0) / curvature ) ) ) * ( angle - angle0 ) /curvature )
|
51
|
+
# return r0 * (1.0 - ( angle - angle0 ) /curvature )
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
# curvature is in number of tour before center % extremity
|
57
|
+
class SpiralLog < GSpiral
|
58
|
+
|
59
|
+
def nsamples
|
60
|
+
return (curvature * 15.0).to_i
|
61
|
+
end
|
62
|
+
|
63
|
+
def compute_radius( r0, angle0, curvature, angle )
|
64
|
+
return r0 * Math.exp( - self.sens * (angle - angle0) / curvature )
|
65
|
+
end
|
66
|
+
|
67
|
+
def maxangle( r0, angle0, curvature )
|
68
|
+
return angle0 - curvature * Math.log( 0.001 )
|
69
|
+
end
|
70
|
+
|
71
|
+
def SpiralLog.tangent( center, ext, curvature )
|
72
|
+
return (center - ext).rotate( -Math.atan( curvature ) )
|
73
|
+
end
|
74
|
+
|
75
|
+
def SpiralLog.fromtangent( tangent, ext, curvature, sens=1.0 )
|
76
|
+
radial = tangent.rotate( sens * Math.atan( curvature ) )
|
77
|
+
newcenter = ext + radial
|
78
|
+
# modelcenter = V2D::O
|
79
|
+
# radial = (modelcenter - ext)
|
80
|
+
# modeltangent = radial.rotate( -Math.atan( curvature ) )
|
81
|
+
# scalefactor = tangent.r / modeltangent.r
|
82
|
+
# angle = V2D.angle( modeltangent, tangent )
|
83
|
+
# newcenter = (modelcenter - ext).rotate( angle ) * scalefactor + ext
|
84
|
+
return SpiralLog[ :center, newcenter, :ext, ext, :curvature, curvature, :sens, sens ]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end #XRVG
|
89
|
+
|
data/lib/style.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
#
|
2
|
+
# See +Style+
|
3
|
+
#
|
4
|
+
require 'attributable'
|
5
|
+
require 'utils'
|
6
|
+
require 'color'
|
7
|
+
|
8
|
+
module XRVG
|
9
|
+
#
|
10
|
+
# Style class
|
11
|
+
#
|
12
|
+
# Used to define the way an object has to be rendered.
|
13
|
+
# For the moment, only the following style attributes are really useful :
|
14
|
+
# - attribute :fill, "none", [String, Color, Gradient]
|
15
|
+
# - attribute :stroke, "none", [String, Color, Gradient]
|
16
|
+
# - attribute :strokewidth, 1.0
|
17
|
+
#
|
18
|
+
# For example :
|
19
|
+
# render.add( Circle[], Style[ :fill, Color.red ] )
|
20
|
+
# render.add( Circle[], Style[ :stroke, Color.red ] )
|
21
|
+
class Style
|
22
|
+
include Attributable
|
23
|
+
# attribute :opacity, 1.0
|
24
|
+
attribute :fill, "none", [String, Color, Gradient]
|
25
|
+
# attribute :fillopacity, 1.0
|
26
|
+
attribute :stroke, "none", [String, Color, Gradient]
|
27
|
+
attribute :strokewidth, 1.0
|
28
|
+
# attribute :strokeopacity, 1.0
|
29
|
+
|
30
|
+
def fillopacity()
|
31
|
+
if @fill.is_a? Color
|
32
|
+
return @fill.a
|
33
|
+
end
|
34
|
+
return 1.0
|
35
|
+
end
|
36
|
+
|
37
|
+
def strokeopacity()
|
38
|
+
if @stroke.is_a? Color
|
39
|
+
return @stroke.a
|
40
|
+
end
|
41
|
+
return 1.0
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def svgfill
|
46
|
+
if fill.is_a? Color
|
47
|
+
return fill.svg
|
48
|
+
elsif fill.is_a? Gradient
|
49
|
+
return "%fillgradient%"
|
50
|
+
else
|
51
|
+
return fill
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def svgstroke
|
56
|
+
if stroke.is_a? Color
|
57
|
+
return stroke.svg
|
58
|
+
elsif stroke.is_a? Gradient
|
59
|
+
return "%strokegradient%"
|
60
|
+
else
|
61
|
+
return stroke
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def svgline
|
66
|
+
template = 'style="opacity:%opacity%;fill:%fill%;fill-opacity:%fillopacity%;stroke:%stroke%;stroke-width:%strokewidth%;stroke-opacity:%strokeopacity%"'
|
67
|
+
|
68
|
+
return template.subreplace( {"%opacity%" => 1.0,
|
69
|
+
"%fill%" => svgfill,
|
70
|
+
"%fillopacity%" => fillopacity,
|
71
|
+
"%stroke%" => svgstroke,
|
72
|
+
"%strokewidth%" => strokewidth,
|
73
|
+
"%strokeopacity%" => strokeopacity} )
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/utils.rb
CHANGED
@@ -277,7 +277,7 @@ class Range
|
|
277
277
|
end
|
278
278
|
|
279
279
|
#
|
280
|
-
# Array extension
|
280
|
+
# Array extension (see SyncS for enumeration synchronisation)
|
281
281
|
# See this[http://xrvg.rubyforge.org/RubyXRVGExtension.html] for presentation
|
282
282
|
#
|
283
283
|
class Array
|
@@ -476,22 +476,6 @@ class Array
|
|
476
476
|
def shuffle
|
477
477
|
return self.sort_by{ rand }
|
478
478
|
end
|
479
|
-
|
480
|
-
# -------------------------------------------------------------
|
481
|
-
# Samplable interface include and overriding
|
482
|
-
# -------------------------------------------------------------
|
483
|
-
include XRVG::Samplable
|
484
|
-
include XRVG::Splittable
|
485
|
-
|
486
|
-
# FloatFunctor overloading to synchronize content sampling and splitting
|
487
|
-
def compute( inputs, type, &block )
|
488
|
-
return self.map {|v| v.compute( inputs, type )}.forzip(nil,&block)
|
489
|
-
end
|
490
|
-
|
491
|
-
def addfilter( newfilter )
|
492
|
-
self.each {|v| v.addfilter( newfilter )}
|
493
|
-
end
|
494
|
-
|
495
479
|
end
|
496
480
|
|
497
481
|
# -------------------------------------------------------------
|
data/lib/xrvg.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# This file has to be included if you want to start a XRVG script
|
2
|
+
#
|
3
|
+
# Please refer to README for XRVG introduction
|
4
|
+
|
5
|
+
# XRVG version (used in rakefile)
|
6
|
+
XRVG_VERSION = "0.0.8"
|
7
|
+
|
8
|
+
# XRVG namespace
|
9
|
+
module XRVG
|
10
|
+
end
|
11
|
+
|
12
|
+
# Standard Ruby extensions
|
13
|
+
require 'enumerator'
|
14
|
+
|
15
|
+
# XRVG Infrastructure
|
16
|
+
require 'trace'
|
17
|
+
|
18
|
+
# XRVG new mixins
|
19
|
+
require 'samplation'
|
20
|
+
require 'attributable'
|
21
|
+
require 'interpolation'
|
22
|
+
require 'parametriclength'
|
23
|
+
|
24
|
+
# XRVG base class extensions
|
25
|
+
require 'utils'
|
26
|
+
require 'geometry2D'
|
27
|
+
require 'intersection'
|
28
|
+
|
29
|
+
# XRVG base classes
|
30
|
+
require 'color'
|
31
|
+
require 'frame'
|
32
|
+
require 'shape'
|
33
|
+
require 'render'
|
34
|
+
require 'bezier'
|
35
|
+
|
36
|
+
# XRVG extensions
|
37
|
+
require 'fitting'
|
38
|
+
require 'bezierbuilders'
|
39
|
+
require 'beziermotifs'
|
40
|
+
require 'beziertools'
|
41
|
+
require 'interbezier'
|
42
|
+
require 'geovariety'
|
43
|
+
require 'spiral'
|
44
|
+
# require 'graph'
|
45
|
+
|
46
|
+
|
47
|
+
|
data/test/test_attributable.rb
CHANGED
@@ -52,5 +52,24 @@ class AttributableTest < Test::Unit::TestCase
|
|
52
52
|
assert_equal( [], obj1.a )
|
53
53
|
end
|
54
54
|
|
55
|
+
def test_error1
|
56
|
+
assert_raise(RuntimeError) {AttrClass[:c, 1, :b, 1.0, :toto, 2]}
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_error2
|
60
|
+
assert_raise(RuntimeError) {AttrClass[:c, 1, :b, 1, :a, 2]}
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_error3
|
64
|
+
assert_raise(RuntimeError) {AttrClass[]}
|
65
|
+
end
|
66
|
+
|
67
|
+
# cannot be tested because cannot declare class in method
|
68
|
+
# class B;
|
69
|
+
# include XRVG::Attributable;
|
70
|
+
# assert_raise(RuntimeError) {attribute :c, nil, :toto}
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
|
55
74
|
end
|
56
75
|
|
data/test/test_bezier.rb
CHANGED
@@ -51,6 +51,13 @@ class BezierTest < Test::Unit::TestCase
|
|
51
51
|
assert_raise(RuntimeError) {Bezier.raws( 1.0, V2D[1.0, 1.0], V2D[1.0, 0.0], V2D[2.0, 0.0] )}
|
52
52
|
end
|
53
53
|
|
54
|
+
def test_builder4
|
55
|
+
b = Bezier.vectorreg( V2D::O, V2D[0.1,0.0], V2D::X, V2D[-0.1,0.0] )
|
56
|
+
c = LinearBezier[]
|
57
|
+
assert_equal( b.pointlist, c.pointlist )
|
58
|
+
end
|
59
|
+
|
60
|
+
|
54
61
|
def test_piece_f
|
55
62
|
piece = BezierSpline[:vector, V2D[0.0, 1.0], V2D[1.0, 1.0], V2D[0.0, 0.0], V2D[1.0, 0.0]]
|
56
63
|
assert_equal( V2D[0.0, 1.0], piece.firstpoint )
|