xrvg 0.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.
- data/LICENCE +21 -0
- data/README +88 -0
- data/Rakefile +263 -0
- data/examples/foreach.rb +9 -0
- data/examples/hellocrown.rb +7 -0
- data/examples/hellocrown2.rb +7 -0
- data/examples/hellocrownrecurse.rb +9 -0
- data/examples/helloworld.rb +5 -0
- data/examples/helloworldcompact.rb +5 -0
- data/examples/helloworldexpanded.rb +5 -0
- data/examples/palette_circle.rb +10 -0
- data/examples/sample.rb +10 -0
- data/examples/uplets.rb +9 -0
- data/lib/assertion.rb +14 -0
- data/lib/attributable.rb +152 -0
- data/lib/color.rb +295 -0
- data/lib/frame.rb +32 -0
- data/lib/geometry2D.rb +207 -0
- data/lib/interpolation.rb +59 -0
- data/lib/render.rb +269 -0
- data/lib/samplation.rb +416 -0
- data/lib/shape.rb +338 -0
- data/lib/style.rb +74 -0
- data/lib/trace.rb +28 -0
- data/lib/utils.rb +404 -0
- data/lib/xrvg.rb +37 -0
- data/test/test_attributable.rb +24 -0
- data/test/test_color.rb +79 -0
- data/test/test_frame.rb +12 -0
- data/test/test_geometry2D.rb +76 -0
- data/test/test_render.rb +18 -0
- data/test/test_style.rb +16 -0
- data/test/test_utils.rb +207 -0
- metadata +80 -0
data/lib/shape.rb
ADDED
@@ -0,0 +1,338 @@
|
|
1
|
+
# shape.rb file
|
2
|
+
# See
|
3
|
+
# - Shape interface
|
4
|
+
# - Curve interface
|
5
|
+
# - Line class
|
6
|
+
# - Circle class
|
7
|
+
require 'frame'
|
8
|
+
require 'geometry2D'
|
9
|
+
require 'utils'
|
10
|
+
require 'attributable'
|
11
|
+
|
12
|
+
# Shape abstract interface
|
13
|
+
# = Intro
|
14
|
+
# To provide a set of services a shape class must provide
|
15
|
+
class Shape
|
16
|
+
include Attributable
|
17
|
+
|
18
|
+
# must return the contour of the shape, of Curve type
|
19
|
+
#
|
20
|
+
# abstract
|
21
|
+
#
|
22
|
+
# not yet used
|
23
|
+
def contour( *args )
|
24
|
+
Kernel::raise("Shape::contour must be redefined in subclasses")
|
25
|
+
end
|
26
|
+
|
27
|
+
# must return the svg description of the shape
|
28
|
+
#
|
29
|
+
# abstract
|
30
|
+
#
|
31
|
+
# must be defined
|
32
|
+
def svg()
|
33
|
+
Kernel::raise("Shape::svg must be redefined in subclasses")
|
34
|
+
end
|
35
|
+
|
36
|
+
# must return the enclosing box of the shape, that is [xmin, ymin, xmax, ymax]
|
37
|
+
#
|
38
|
+
# abstract
|
39
|
+
#
|
40
|
+
# must be defined
|
41
|
+
def viewbox()
|
42
|
+
Kernel::raise("Shape::viewbox must be redefined in subclasses")
|
43
|
+
end
|
44
|
+
|
45
|
+
# compute size of the shape, from viewbox
|
46
|
+
def size()
|
47
|
+
xmin, ymin, xmax, ymax = self.viewbox
|
48
|
+
return [xmax-xmin, ymax-ymin]
|
49
|
+
end
|
50
|
+
|
51
|
+
# must return the enclosing box of the shape, that is [xmin, ymin, xmax, ymax]
|
52
|
+
#
|
53
|
+
# abstract
|
54
|
+
def default_style()
|
55
|
+
return Style[:fill, Color.black ]
|
56
|
+
end
|
57
|
+
|
58
|
+
# compute the "surface" of the viewbox of the shape
|
59
|
+
#
|
60
|
+
# use size method
|
61
|
+
def surface
|
62
|
+
width, height = self.size
|
63
|
+
return width * height
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
# Curve abstract interface
|
69
|
+
# = Intro
|
70
|
+
# To provide a set of services a curve class must provide
|
71
|
+
class Curve < Shape
|
72
|
+
# must compute the point at curve abscissa
|
73
|
+
#
|
74
|
+
# abstract
|
75
|
+
#
|
76
|
+
# must be defined
|
77
|
+
def point( abscissa )
|
78
|
+
Kernel::raise("Curve::point must be redefined in subclasses")
|
79
|
+
end
|
80
|
+
|
81
|
+
# must compute the tangent at curve abscissa
|
82
|
+
#
|
83
|
+
# abstract
|
84
|
+
#
|
85
|
+
# must be defined
|
86
|
+
def tangent( abscissa )
|
87
|
+
Kernel::raise("Curve::tangent must be redefined in subclasses")
|
88
|
+
end
|
89
|
+
|
90
|
+
# must compute the acceleration at curve abscissa
|
91
|
+
#
|
92
|
+
# abstract
|
93
|
+
#
|
94
|
+
# must be defined
|
95
|
+
def acc( abscissa )
|
96
|
+
Kernel::raise("Curve::acc must be redefined in subclasses")
|
97
|
+
end
|
98
|
+
|
99
|
+
# must compute the rotation at curve abscissa
|
100
|
+
#
|
101
|
+
# abstract
|
102
|
+
#
|
103
|
+
# must be defined
|
104
|
+
def rotation( abscissa )
|
105
|
+
Kernel::raise("Curve::rotation must be redefined in subclasses")
|
106
|
+
end
|
107
|
+
|
108
|
+
# must compute the scale at curve abscissa
|
109
|
+
#
|
110
|
+
# abstract
|
111
|
+
#
|
112
|
+
# must be defined
|
113
|
+
def scale( abscissa )
|
114
|
+
Kernel::raise("Curve::scale must be redefined in subclasses")
|
115
|
+
end
|
116
|
+
|
117
|
+
# must return the length at abscissa, or total length if abscissa nil
|
118
|
+
#
|
119
|
+
# abstract
|
120
|
+
#
|
121
|
+
# must be defined
|
122
|
+
def length(abscissa=nil)
|
123
|
+
Kernel::raise("Curve::length must be redefined in subclasses")
|
124
|
+
end
|
125
|
+
|
126
|
+
# default style of a curve, as stroked with stroke width 1% of length
|
127
|
+
def default_style
|
128
|
+
return Style[ :stroke, Color.black, :strokewidth, self.length / 100.0 ]
|
129
|
+
end
|
130
|
+
|
131
|
+
# compute frame at abscissa t
|
132
|
+
def frame( t )
|
133
|
+
return Frame[ :center, self.point( t ), :vector, self.tangent( t ), :rotation, self.rotation( t ), :scale, self.scale( t ) ]
|
134
|
+
end
|
135
|
+
|
136
|
+
# compute normal at abscissa t
|
137
|
+
#
|
138
|
+
# do tangent.ortho
|
139
|
+
def normal( t )
|
140
|
+
return self.tangent( t ).ortho
|
141
|
+
end
|
142
|
+
|
143
|
+
# compute normal acceleration at abscissa t
|
144
|
+
def acc_normal( t )
|
145
|
+
normal = self.normal( t ).norm
|
146
|
+
result = self.acc( t ).inner_product( normal )
|
147
|
+
return result
|
148
|
+
end
|
149
|
+
|
150
|
+
# compute curvature at abscissa t
|
151
|
+
def curvature( t )
|
152
|
+
if self.acc_normal( t ) == 0.0
|
153
|
+
return 0.0
|
154
|
+
end
|
155
|
+
return 1.0 / (self.tangent( t ).r / self.acc_normal( t ))
|
156
|
+
end
|
157
|
+
|
158
|
+
# shortcut method to map frames from abscissas
|
159
|
+
def frames (abscissas)
|
160
|
+
return abscissas.map { |abscissa| self.frame( abscissa ) }
|
161
|
+
end
|
162
|
+
|
163
|
+
# shortcut method to map points from abscissas
|
164
|
+
def points (abscissas)
|
165
|
+
result = abscissas.map { |abscissa| self.point( abscissa ) }
|
166
|
+
return result
|
167
|
+
end
|
168
|
+
|
169
|
+
# shortcut method to map tangents from abscissas
|
170
|
+
def tangents (abscissas)
|
171
|
+
return abscissas.map { |abscissa| self.tangent( abscissa ) }
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
# Line class
|
177
|
+
# = Intro
|
178
|
+
# Used to draw polylines and polygons
|
179
|
+
# = Attributes
|
180
|
+
# attribute :exts, [Point[0.0, 0.0], Point[1.0, 1.0]]
|
181
|
+
# = Example
|
182
|
+
# line = Line[ :exts, [Point::O, Point::X] ]
|
183
|
+
class Line < Curve
|
184
|
+
attribute :exts, [Point[0.0, 0.0], Point[1.0, 1.0]]
|
185
|
+
|
186
|
+
def initialize (*args) #:nodoc:
|
187
|
+
super( *args )
|
188
|
+
self.init_tangents
|
189
|
+
end
|
190
|
+
|
191
|
+
def init_tangents #:nodoc:
|
192
|
+
index = 0
|
193
|
+
@tangents = Array.new
|
194
|
+
self.exts.pairs { |p1, p2|
|
195
|
+
@tangents[ index ] = (p2-p1).norm
|
196
|
+
index += 1
|
197
|
+
}
|
198
|
+
end
|
199
|
+
|
200
|
+
# return the total length of the polyline
|
201
|
+
def length
|
202
|
+
if not @length
|
203
|
+
@length = 0.0
|
204
|
+
self.exts.pairs do |p1, p2|
|
205
|
+
@length += (p1 - p2).r
|
206
|
+
end
|
207
|
+
end
|
208
|
+
return @length
|
209
|
+
end
|
210
|
+
|
211
|
+
# compute line point at abscissa
|
212
|
+
# Line[ :exts, [Point::O, Point::X] ].point( 0.3 ) => Vector[0.0,0.3]
|
213
|
+
def point (abscissa)
|
214
|
+
piece1 = abscissa.to_int
|
215
|
+
if piece1 == @exts.size - 1
|
216
|
+
return @exts[-1]
|
217
|
+
end
|
218
|
+
abscissa -= piece1
|
219
|
+
cexts = self.exts.slice( piece1, 2 )
|
220
|
+
return (cexts[0]..cexts[1]).sample( abscissa )
|
221
|
+
end
|
222
|
+
|
223
|
+
# compute line frame at abscissa
|
224
|
+
#
|
225
|
+
# for the moment, frame rotation is always 0.0, and scale always 1.0
|
226
|
+
def frame (abscissa)
|
227
|
+
return Frame[ :center, self.point( abscissa ), :vector, Vector[ 0.0, 0.0 ], :rotation, 0.0, :scale, 1.0 ]
|
228
|
+
end
|
229
|
+
|
230
|
+
# compute line tangent at abscissa
|
231
|
+
def tangent (abscissa)
|
232
|
+
return @tangents[abscissa.to_int].dup
|
233
|
+
end
|
234
|
+
|
235
|
+
# compute viewbox of the line
|
236
|
+
#
|
237
|
+
# simply call Point.viewbox on :exts
|
238
|
+
def viewbox
|
239
|
+
return Point.viewbox( self.exts )
|
240
|
+
end
|
241
|
+
|
242
|
+
# translate a line of v offset, v being a vector
|
243
|
+
#
|
244
|
+
# return a new line with every point of :exts translated
|
245
|
+
def translate( v )
|
246
|
+
return Line[ :exts, @exts.map {|ext| ext.translate( v )} ]
|
247
|
+
end
|
248
|
+
|
249
|
+
# reverse a line
|
250
|
+
#
|
251
|
+
# return a new line with :exts reversed
|
252
|
+
def reverse
|
253
|
+
return Line[ :exts, @exts.reverse ]
|
254
|
+
end
|
255
|
+
|
256
|
+
# return line svg description
|
257
|
+
def svg
|
258
|
+
path = "M #{exts[0].x} #{exts[0].y} "
|
259
|
+
exts[1..-1].each { |p|
|
260
|
+
path += "L #{p.x} #{p.y}"
|
261
|
+
}
|
262
|
+
return "<path d=\"" + path + "\"/>"
|
263
|
+
end
|
264
|
+
|
265
|
+
include Samplable
|
266
|
+
alias apply_sample point
|
267
|
+
end
|
268
|
+
|
269
|
+
# Circle class
|
270
|
+
# = Intro
|
271
|
+
# define a circle curve
|
272
|
+
# = Attributes
|
273
|
+
# attribute :center, Vector[0.0,0.0]
|
274
|
+
# attribute :radius, 1.0
|
275
|
+
# = Example
|
276
|
+
# c = Circle[ :center, Point::O, :radius, 1.0 ] # equiv Circle[]
|
277
|
+
class Circle < Curve
|
278
|
+
attribute :center, Vector[0.0,0.0]
|
279
|
+
attribute :radius, 1.0
|
280
|
+
|
281
|
+
# compute length of the circle
|
282
|
+
def length
|
283
|
+
if not @length
|
284
|
+
@length = 2.0 * Math::PI * self.radius
|
285
|
+
end
|
286
|
+
return @length
|
287
|
+
end
|
288
|
+
|
289
|
+
# shortcut method to retun center.x
|
290
|
+
def cx
|
291
|
+
return self.center.x
|
292
|
+
end
|
293
|
+
|
294
|
+
# shortcut method to retun center.y
|
295
|
+
def cy
|
296
|
+
return self.center.y
|
297
|
+
end
|
298
|
+
|
299
|
+
# viewbox of the circle
|
300
|
+
def viewbox
|
301
|
+
return [ self.cx - self.radius,
|
302
|
+
self.cy - self.radius,
|
303
|
+
self.cx + self.radius,
|
304
|
+
self.cy + self.radius ]
|
305
|
+
end
|
306
|
+
|
307
|
+
# size of the circle
|
308
|
+
def size
|
309
|
+
return [ self.radius, self.radius ]
|
310
|
+
end
|
311
|
+
|
312
|
+
# svg description of the circle
|
313
|
+
def svg
|
314
|
+
template = '<circle cx="%cx%" cy="%cy%" r="%r%"/>'
|
315
|
+
return template.subreplace( {"%cx%" => cx,
|
316
|
+
"%cy%" => cy,
|
317
|
+
"%r%" => radius} )
|
318
|
+
end
|
319
|
+
|
320
|
+
# compute point at abscissa
|
321
|
+
def point (abscissa)
|
322
|
+
angle = Range::Angle.sample( abscissa )
|
323
|
+
return Point[ self.cx + self.radius * Math.cos( angle ),
|
324
|
+
self.cy + self.radius * Math.sin( angle )]
|
325
|
+
end
|
326
|
+
|
327
|
+
# compute tangent at abscissa
|
328
|
+
#
|
329
|
+
# TODO
|
330
|
+
def tangent( abscissa )
|
331
|
+
TODO
|
332
|
+
end
|
333
|
+
|
334
|
+
include Samplable
|
335
|
+
alias apply_sample point
|
336
|
+
|
337
|
+
end
|
338
|
+
|
data/lib/style.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
#
|
2
|
+
# See +Style+
|
3
|
+
#
|
4
|
+
require 'attributable'
|
5
|
+
require 'utils'
|
6
|
+
require 'color'
|
7
|
+
|
8
|
+
#
|
9
|
+
# Style class
|
10
|
+
#
|
11
|
+
# Used to define the way an object has to be rendered.
|
12
|
+
# For the moment, only the following style attributes are really useful :
|
13
|
+
# - attribute :fill, "none", [String, Color, Gradient]
|
14
|
+
# - attribute :stroke, "none", [String, Color, Gradient]
|
15
|
+
# - attribute :strokewidth, 1.0
|
16
|
+
#
|
17
|
+
# For example :
|
18
|
+
# render.add( Circle[], Style[ :fill, Color.red ] )
|
19
|
+
# render.add( Circle[], Style[ :stroke, Color.red ] )
|
20
|
+
class Style
|
21
|
+
include Attributable
|
22
|
+
attribute :opacity, 1.0
|
23
|
+
attribute :fill, "none", [String, Color, Gradient]
|
24
|
+
attribute :fillopacity, 1.0
|
25
|
+
attribute :stroke, "none", [String, Color, Gradient]
|
26
|
+
attribute :strokewidth, 1.0
|
27
|
+
attribute :strokeopacity, 1.0
|
28
|
+
|
29
|
+
def fill=( color )
|
30
|
+
if color.is_a? Color
|
31
|
+
self.fillopacity = color.a
|
32
|
+
end
|
33
|
+
@fill = color
|
34
|
+
end
|
35
|
+
|
36
|
+
def stroke=( color )
|
37
|
+
if color.is_a? Color
|
38
|
+
self.strokeopacity = color.a
|
39
|
+
end
|
40
|
+
@stroke = color
|
41
|
+
end
|
42
|
+
|
43
|
+
def svgfill
|
44
|
+
if fill.is_a? Color
|
45
|
+
return fill.svg
|
46
|
+
elsif fill.is_a? Gradient
|
47
|
+
return "%fillgradient%"
|
48
|
+
else
|
49
|
+
return fill
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def svgstroke
|
54
|
+
if stroke.is_a? Color
|
55
|
+
return stroke.svg
|
56
|
+
elsif stroke.is_a? Gradient
|
57
|
+
return "%strokegradient%"
|
58
|
+
else
|
59
|
+
return stroke
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def svgline
|
64
|
+
template = 'style="opacity:%opacity%;fill:%fill%;fill-opacity:%fillopacity%;stroke:%stroke%;stroke-width:%strokewidth%;stroke-opacity:%strokeopacity%"'
|
65
|
+
|
66
|
+
return template.subreplace( {"%opacity%" => opacity,
|
67
|
+
"%fill%" => svgfill,
|
68
|
+
"%fillopacity%" => fillopacity,
|
69
|
+
"%stroke%" => svgstroke,
|
70
|
+
"%strokewidth%" => strokewidth,
|
71
|
+
"%strokeopacity%" => strokeopacity} )
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
data/lib/trace.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Trace utility
|
2
|
+
#
|
3
|
+
# Consists in a Trace class, and a Trace global method
|
4
|
+
|
5
|
+
# Trace "static" class, to be able to globally inhibit or activate traces
|
6
|
+
# Trace.inihibit
|
7
|
+
# Trace.activate
|
8
|
+
class Trace
|
9
|
+
@@active = true
|
10
|
+
def Trace.active?
|
11
|
+
return @@active
|
12
|
+
end
|
13
|
+
def Trace.inhibit
|
14
|
+
@@active = nil
|
15
|
+
end
|
16
|
+
def Trace.activate
|
17
|
+
@@active = true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Standard trace method
|
22
|
+
# Trace("hello world")
|
23
|
+
# Check before printing the string if traces are active
|
24
|
+
def Trace(string)
|
25
|
+
if Trace.active?
|
26
|
+
puts string
|
27
|
+
end
|
28
|
+
end
|