xrvg 0.0.7 → 0.0.8
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/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/render.rb
ADDED
@@ -0,0 +1,266 @@
|
|
1
|
+
#
|
2
|
+
# Render file. See
|
3
|
+
# - +Render+ for render "abstraction"
|
4
|
+
# - +SVGRender+ for effective SVG render
|
5
|
+
|
6
|
+
require 'color'
|
7
|
+
require 'style'
|
8
|
+
|
9
|
+
module XRVG
|
10
|
+
# Render abstract class
|
11
|
+
#
|
12
|
+
# Is pretty useless for the moment
|
13
|
+
class Render
|
14
|
+
include Attributable
|
15
|
+
attr_accessor :width
|
16
|
+
attr_accessor :height
|
17
|
+
end
|
18
|
+
|
19
|
+
# SVG Render class
|
20
|
+
#
|
21
|
+
# In charge of generating a svg output file from different object passed to it
|
22
|
+
# = Use
|
23
|
+
# Canonical use of the class
|
24
|
+
# render = SVGRender[ :filename, "essai.svg" ]
|
25
|
+
# render.add( Circle[] )
|
26
|
+
# render.end
|
27
|
+
# = Improvements
|
28
|
+
# Allows also the "with" syntax
|
29
|
+
# = Attributes
|
30
|
+
# attribute :filename, "", String
|
31
|
+
# attribute :imagesize, "2cm", String
|
32
|
+
# attribute :background, Color.white, [Color, String]
|
33
|
+
class SVGRender < Render
|
34
|
+
attribute :filename, "", String
|
35
|
+
attribute :imagesize, "2cm", String
|
36
|
+
attribute :background, "white", [Color, String]
|
37
|
+
attr_reader :viewbox
|
38
|
+
|
39
|
+
# SVGRender builder
|
40
|
+
#
|
41
|
+
# Allows to pass a block, to avoid using .end
|
42
|
+
# SVGRender.[] do |render|
|
43
|
+
# render.add( Circle[] )
|
44
|
+
# end
|
45
|
+
def SVGRender.[](*args,&block)
|
46
|
+
result = self.new( *args )
|
47
|
+
if block
|
48
|
+
yield result
|
49
|
+
result.end
|
50
|
+
end
|
51
|
+
return result
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def initialize ( *args, &block ) #:nodoc:
|
56
|
+
super( *args )
|
57
|
+
@layers = {}
|
58
|
+
@defs = ""
|
59
|
+
@ngradients = 0
|
60
|
+
if @filename.length == 0
|
61
|
+
@filename = $0.split(".")[0..-2].join(".") + ".svg"
|
62
|
+
Trace("filename is #{filename}")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def layers=( backtofront ) #:nodoc:
|
67
|
+
@sortlayers = backtofront
|
68
|
+
@sortlayers.each do |key|
|
69
|
+
@layers[ key ] = ""
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_content (string, layer) #:nodoc:
|
74
|
+
if not @layers.key? layer
|
75
|
+
@layers[ layer ] = ""
|
76
|
+
end
|
77
|
+
@layers[ layer ] += string
|
78
|
+
end
|
79
|
+
|
80
|
+
def svg_template #:nodoc:
|
81
|
+
return '<?xml version="1.0" standalone="no"?>
|
82
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
83
|
+
<svg width="%SIZE%" height="%SIZE%" %VIEWBOX% version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
84
|
+
%DEFS%
|
85
|
+
%BACKGROUND%
|
86
|
+
%CONTENT%
|
87
|
+
</svg>'
|
88
|
+
end
|
89
|
+
|
90
|
+
def add_def (object) #:nodoc:
|
91
|
+
@defs += object
|
92
|
+
end
|
93
|
+
|
94
|
+
def add_gradient( gradient ) #:nodoc:
|
95
|
+
id = "gradient#{@ngradients}"
|
96
|
+
add_def( gradient.svgdef.subreplace( {"%ID%" => id} ) )
|
97
|
+
@ngradients += 1
|
98
|
+
return id
|
99
|
+
end
|
100
|
+
|
101
|
+
# render fundamental method
|
102
|
+
#
|
103
|
+
# used to render an object, with a particular style, on an optional layer.
|
104
|
+
# render.add( Circle[], Style[ :fill, Color.black ], 1 )
|
105
|
+
# If style is not provided, render asked to object its default_style, if any
|
106
|
+
def add(object, style=nil, layer=0, type=:object)
|
107
|
+
add_content( render( object, style ), layer)
|
108
|
+
refresh_viewbox( object )
|
109
|
+
end
|
110
|
+
|
111
|
+
def render (object, style=nil) #:nodoc:
|
112
|
+
owidth, oheight = object.size
|
113
|
+
|
114
|
+
res = 0.0000001
|
115
|
+
if owidth < res and oheight < res
|
116
|
+
return ""
|
117
|
+
end
|
118
|
+
|
119
|
+
if not style or style == :default
|
120
|
+
style = object.default_style
|
121
|
+
end
|
122
|
+
|
123
|
+
result = "<g #{style.svgline}>\n"
|
124
|
+
result += object.svg + "\n"
|
125
|
+
result += "</g>\n"
|
126
|
+
|
127
|
+
# puts "result #{result}"
|
128
|
+
|
129
|
+
if style.fill.is_a? Gradient
|
130
|
+
gradientID = add_gradient( style.fill )
|
131
|
+
result = result.subreplace( {"%fillgradient%" => "url(##{gradientID})"} )
|
132
|
+
end
|
133
|
+
|
134
|
+
if style.stroke.is_a? Gradient
|
135
|
+
gradientID = add_gradient( style.stroke )
|
136
|
+
result = result.subreplace( {"%strokegradient%" => "url(##{gradientID})"} )
|
137
|
+
end
|
138
|
+
return result
|
139
|
+
end
|
140
|
+
|
141
|
+
def viewbox #:nodoc:
|
142
|
+
return @viewbox
|
143
|
+
end
|
144
|
+
|
145
|
+
def size #:nodoc:
|
146
|
+
xmin, ymin, xmax, ymax = viewbox
|
147
|
+
return [xmax - xmin, ymax - ymin]
|
148
|
+
end
|
149
|
+
|
150
|
+
def refresh_viewbox (object) #:nodoc:
|
151
|
+
newviewbox = object.viewbox
|
152
|
+
if newviewbox.length > 0
|
153
|
+
if @viewbox == nil
|
154
|
+
@viewbox = newviewbox
|
155
|
+
else
|
156
|
+
newxmin, newymin, newxmax, newymax = newviewbox
|
157
|
+
xmin, ymin, xmax, ymax = viewbox
|
158
|
+
|
159
|
+
if newxmin < xmin
|
160
|
+
xmin = newxmin
|
161
|
+
end
|
162
|
+
if newymin < ymin
|
163
|
+
ymin = newymin
|
164
|
+
end
|
165
|
+
if newxmax > xmax
|
166
|
+
xmax = newxmax
|
167
|
+
end
|
168
|
+
if newymax > ymax
|
169
|
+
ymax = newymax
|
170
|
+
end
|
171
|
+
|
172
|
+
@viewbox = [xmin, ymin, xmax, ymax]
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def get_background_svg #:nodoc:
|
178
|
+
xmin, ymin, width, height = get_carre_viewbox( get_final_viewbox() )
|
179
|
+
template = '<rect x="%x%" y="%y%" width="%width%" height="%height%" fill="%fill%"/>'
|
180
|
+
bg = self.background
|
181
|
+
if bg.respond_to? :svg
|
182
|
+
bg = bg.svg
|
183
|
+
end
|
184
|
+
return template.subreplace( {"%x%" => xmin,
|
185
|
+
"%y%" => ymin,
|
186
|
+
"%width%" => width,
|
187
|
+
"%height%" => height,
|
188
|
+
"%fill%" => bg} )
|
189
|
+
end
|
190
|
+
|
191
|
+
def get_final_viewbox #:nodoc:
|
192
|
+
marginfactor = 0.2
|
193
|
+
xmin, ymin, xmax, ymax = viewbox()
|
194
|
+
width, height = size()
|
195
|
+
|
196
|
+
xcenter = (xmin + xmax)/2.0
|
197
|
+
ycenter = (ymin + ymax)/2.0
|
198
|
+
|
199
|
+
width *= 1.0 + marginfactor
|
200
|
+
height *= 1.0 + marginfactor
|
201
|
+
|
202
|
+
if width == 0.0
|
203
|
+
width = 1.0
|
204
|
+
end
|
205
|
+
if height == 0.0
|
206
|
+
height = 1.0
|
207
|
+
end
|
208
|
+
|
209
|
+
xmin = xcenter - width / 2.0
|
210
|
+
ymin = ycenter - height / 2.0
|
211
|
+
|
212
|
+
return xmin, ymin, width, height
|
213
|
+
end
|
214
|
+
|
215
|
+
def get_viewbox_svg #:nodoc:
|
216
|
+
return viewbox_svg( get_final_viewbox() )
|
217
|
+
end
|
218
|
+
|
219
|
+
def get_carre_viewbox( viewbox ) #:nodoc:
|
220
|
+
xmin, ymin, width, height = viewbox
|
221
|
+
xcenter = xmin + width / 2.0
|
222
|
+
ycenter = ymin + height / 2.0
|
223
|
+
maxsize = width < height ? height : width
|
224
|
+
return [xcenter - maxsize/2.0, ycenter - maxsize/2.0, maxsize, maxsize]
|
225
|
+
end
|
226
|
+
|
227
|
+
def viewbox_svg( viewbox ) #:nodoc:
|
228
|
+
xmin, ymin, width, height = viewbox
|
229
|
+
return "viewBox=\"#{xmin} #{ymin} #{width} #{height}\""
|
230
|
+
end
|
231
|
+
|
232
|
+
def content #:nodoc:
|
233
|
+
keys = @sortlayers ? @sortlayers : @layers.keys.sort
|
234
|
+
return keys.inject("") {|result,key| result += @layers[key]}
|
235
|
+
end
|
236
|
+
|
237
|
+
def svgdef #:nodoc:
|
238
|
+
return "<defs>\n#{@defs}\n</defs>\n"
|
239
|
+
end
|
240
|
+
|
241
|
+
def end () #:nodoc:
|
242
|
+
svgcontent = content()
|
243
|
+
svgviewbox = get_viewbox_svg()
|
244
|
+
svgbackground = get_background_svg()
|
245
|
+
|
246
|
+
content = svg_template().subreplace( {"%VIEWBOX%" => svgviewbox,
|
247
|
+
"%SIZE%" => @imagesize,
|
248
|
+
"%DEFS%" => svgdef,
|
249
|
+
"%BACKGROUND%" => svgbackground,
|
250
|
+
"%CONTENT%" => svgcontent})
|
251
|
+
|
252
|
+
File.open(filename(), "w") do |f|
|
253
|
+
f << content
|
254
|
+
end
|
255
|
+
|
256
|
+
puts "render #{filename()} OK"; # necessary for Emacs to get output name !!!!
|
257
|
+
end
|
258
|
+
|
259
|
+
def raster () #:nodoc:
|
260
|
+
# bg = background.format255
|
261
|
+
|
262
|
+
# Kernel.system( "ruby", "svg2png.rb", filename(), "2.0" )
|
263
|
+
# Kernel.system( "i_view32", filename().subreplace( ".svg" => ".png" ), "/fs" )
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
data/lib/samplation.rb
CHANGED
@@ -300,13 +300,56 @@ module Splittable
|
|
300
300
|
# apply_register( :split, :apply_splits )
|
301
301
|
end
|
302
302
|
|
303
|
+
# -------------------------------------------------------------
|
304
|
+
# Samplation synchronisation
|
305
|
+
# -------------------------------------------------------------
|
306
|
+
class SyncS
|
307
|
+
attr_accessor :items
|
308
|
+
|
309
|
+
include Samplable
|
310
|
+
include Splittable
|
311
|
+
|
312
|
+
# FloatFunctor overloading to synchronize content sampling and splitting
|
313
|
+
def compute( inputs, type, &block )
|
314
|
+
return self.items.map {|v| v.compute( inputs, type )}.forzip(nil,&block)
|
315
|
+
end
|
316
|
+
|
317
|
+
def addfilter( newfilter )
|
318
|
+
self.items.each {|v| v.addfilter( newfilter )}
|
319
|
+
return self
|
320
|
+
end
|
321
|
+
|
322
|
+
|
323
|
+
# create a new samplation synchronizer
|
324
|
+
# syncs = SyncS[ Range.O, Range.O.geo( 5.0 )]
|
325
|
+
def SyncS.[]( *args )
|
326
|
+
return SyncS.new(*args)
|
327
|
+
end
|
328
|
+
|
329
|
+
# builder
|
330
|
+
def initialize( *args )
|
331
|
+
newitems = []
|
332
|
+
args.each do |item|
|
333
|
+
if item.is_a? Array
|
334
|
+
item = Roller[ *item ]
|
335
|
+
end
|
336
|
+
newitems << item
|
337
|
+
end
|
338
|
+
@items = newitems
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
|
343
|
+
# -------------------------------------------------------------
|
344
|
+
# Filter interface and classes
|
345
|
+
# -------------------------------------------------------------
|
303
346
|
|
304
347
|
# Filter class
|
305
348
|
# = Intro
|
306
349
|
# Filter class allows to call generically a method with arg between 0.0..0.1 on a given object, for use in a +FloatFunctor+ context.
|
307
350
|
# = Example
|
308
351
|
# this allow to do for example
|
309
|
-
# [bezier1.filter(:point), bezier1.filter(:tangent), palette].samples( 10 ) do |point, tangent, color|
|
352
|
+
# SyncS[bezier1.filter(:point), bezier1.filter(:tangent), palette].samples( 10 ) do |point, tangent, color|
|
310
353
|
class Filter
|
311
354
|
include Samplable
|
312
355
|
|