rasem 0.6.1 → 0.7.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 +7 -0
- data/Gemfile +3 -4
- data/LICENSE.txt +2 -0
- data/README.rdoc +108 -16
- data/Rakefile +5 -7
- data/VERSION +1 -1
- data/bin/rasem +0 -0
- data/lib/rasem.rb +1 -0
- data/lib/rasem/application.rb +10 -11
- data/lib/rasem/download_documentation.rb +77 -0
- data/lib/rasem/svg_documentation.rb +5334 -0
- data/lib/rasem/svg_image.rb +689 -161
- data/spec/defs_spec.rb +103 -0
- data/spec/gradient_spec.rb +53 -0
- data/spec/path_spec.rb +227 -0
- data/spec/rasem_spec.rb +324 -116
- data/spec/spec_helper.rb +13 -0
- data/spec/transform_spec.rb +112 -0
- metadata +43 -46
data/lib/rasem/svg_image.rb
CHANGED
@@ -1,222 +1,750 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
1
|
+
Rasem::SVG_ALIAS = {
|
2
|
+
:group => :g,
|
3
|
+
:rectangle => :rect,
|
4
|
+
}
|
5
|
+
|
6
|
+
Rasem::SVG_EXPANSION = {
|
7
|
+
:line => [:x1,:y1,:x2,:y2],
|
8
|
+
:circle => [:cx,:cy,:r],
|
9
|
+
:image => [:x,:y,:width,:height,:"xlink:href"],
|
10
|
+
:ellipse => [:cx,:cy,:rx,:ry],
|
11
|
+
:text => [:x,:y],
|
12
|
+
|
13
|
+
:rect => lambda do |args|
|
14
|
+
raise "Wrong unnamed argument count" unless args.size == 4 or args.size == 5 or args.size == 6
|
15
|
+
result = {
|
16
|
+
:x => args[0],
|
17
|
+
:y => args[1],
|
18
|
+
:width => args[2],
|
19
|
+
:height => args[3],
|
20
|
+
}
|
21
|
+
if (args.size > 4)
|
22
|
+
result[:rx] = args[4]
|
23
|
+
result[:ry] = (args[5] or args[4])
|
24
|
+
end
|
25
|
+
return result
|
26
|
+
end,
|
27
|
+
|
28
|
+
:polygon => lambda do |args|
|
29
|
+
args.flatten!
|
30
|
+
raise "Illegal number of coordinates (should be even)" if args.length.odd?
|
31
|
+
return {
|
32
|
+
:points => args
|
33
|
+
}
|
34
|
+
end,
|
35
|
+
|
36
|
+
:polyline => lambda do |args|
|
37
|
+
args.flatten!
|
38
|
+
raise "Illegal number of coordinates (should be even)" if args.length.odd?
|
39
|
+
return {
|
40
|
+
:points => args
|
10
41
|
}
|
42
|
+
end
|
43
|
+
}
|
11
44
|
|
45
|
+
Rasem::SVG_DEFAULTS = {
|
46
|
+
:text => {:fill=>"black"},
|
47
|
+
:line => {:stroke=>"black"},
|
48
|
+
:rect => {:stroke=>"black"},
|
49
|
+
:circle => {:stroke=>"black"},
|
50
|
+
:ellipse => {:stroke=>"black"},
|
51
|
+
:polygon => {:stroke=>"black"},
|
52
|
+
:polyline => {:stroke=>"black"},
|
53
|
+
}
|
12
54
|
|
13
|
-
|
14
|
-
|
55
|
+
#TODO: move to documentation?
|
56
|
+
Rasem::SVG_TRANSFORM = [
|
57
|
+
:matrix, # expects an array of 6 elements
|
58
|
+
:translate, #[tx, ty?]
|
59
|
+
:scale, #[sx, sy?]
|
60
|
+
:rotate, #[angle, (cx, cy)?]
|
61
|
+
:skewX, # angle
|
62
|
+
:skewY, # angle
|
63
|
+
]
|
15
64
|
|
16
|
-
|
17
|
-
|
65
|
+
Rasem::CSS_STYLE = [
|
66
|
+
:fill,
|
67
|
+
:stroke_width,
|
68
|
+
:stroke,
|
69
|
+
:fill_opacity,
|
70
|
+
:stroke_opacity,
|
71
|
+
:opacity,
|
72
|
+
]
|
73
|
+
|
74
|
+
|
75
|
+
class Rasem::SVGTag
|
76
|
+
|
77
|
+
attr_reader :tag, :attributes, :children
|
78
|
+
|
79
|
+
def initialize(tag, attributes={}, &block)
|
80
|
+
@tag = validate_tag(tag)
|
81
|
+
@attributes = validate_attributes(attributes)
|
82
|
+
@children = []
|
18
83
|
|
19
|
-
write_header(width, height)
|
20
84
|
if block
|
21
|
-
|
22
|
-
self.close
|
85
|
+
instance_exec &block
|
23
86
|
end
|
24
87
|
end
|
25
88
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
89
|
+
|
90
|
+
|
91
|
+
##
|
92
|
+
# Provide methods for SVG transformations
|
93
|
+
##
|
94
|
+
|
95
|
+
def translate(tx, ty = 0)
|
96
|
+
add_transform(:translate, "#{tx}, #{ty}")
|
97
|
+
self
|
32
98
|
end
|
33
99
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
100
|
+
|
101
|
+
def scale(sx, sy = 1)
|
102
|
+
add_transform(:scale, "#{sx}, #{sy}")
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
def rotate(angle, cx = nil, cy = nil)
|
108
|
+
add_transform(:rotate, "#{angle}#{(cx.nil? or cy.nil?) ? "" : ", #{cx}, #{cy}"}")
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
def skewX(angle)
|
114
|
+
add_transform(:skewX, "#{angle}")
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def skewY(angle)
|
120
|
+
add_transform(:skewY, "#{angle}")
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
def matrix(a, b, c, d, e, f)
|
126
|
+
add_transform(:matrix, "#{a}, #{b}, #{c}, #{d}, #{e}, #{f}")
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
def validate_tag(tag)
|
132
|
+
raise "#{tag} is not a valid tag" unless Rasem::SVG_ELEMENTS.include?(tag.to_sym)
|
133
|
+
tag.to_sym
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
def validate_attributes(attributes)
|
138
|
+
clean_attributes = {}
|
139
|
+
transforms = {}
|
140
|
+
styles = {}
|
141
|
+
|
142
|
+
attributes.each do |attribute, value|
|
143
|
+
if Rasem::SVG_TRANSFORM.include? attribute
|
144
|
+
transforms[attribute] = value
|
145
|
+
elsif Rasem::CSS_STYLE.include? attribute
|
146
|
+
styles[attribute] = value
|
147
|
+
else
|
148
|
+
clean_attributes[validate_attribute(attribute)] = value
|
149
|
+
end
|
39
150
|
end
|
151
|
+
|
152
|
+
#always prefer more verbose definition.
|
153
|
+
unless transforms.empty?
|
154
|
+
transforms.merge!(clean_attributes[:transform]) if clean_attributes[:transform]
|
155
|
+
str = ""
|
156
|
+
write_transforms(transforms, str)
|
157
|
+
clean_attributes[validate_attribute(:transform)] = str
|
158
|
+
end
|
159
|
+
|
160
|
+
unless styles.empty?
|
161
|
+
styles.merge!(clean_attributes[:style]) if clean_attributes[:style]
|
162
|
+
clean_attributes[validate_attribute(:style)] = styles
|
163
|
+
end
|
164
|
+
clean_attributes
|
40
165
|
end
|
41
166
|
|
42
|
-
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
@output << %Q{/>}
|
167
|
+
|
168
|
+
def validate_attribute(attribute)
|
169
|
+
raise "#{@tag} does not support attribute #{attribute}" unless Rasem::SVG_STRUCTURE[@tag.to_sym][:attributes].include?(attribute.to_sym)
|
170
|
+
attribute.to_sym
|
47
171
|
end
|
48
172
|
|
49
|
-
|
50
|
-
def
|
51
|
-
|
52
|
-
|
53
|
-
|
173
|
+
|
174
|
+
def write_styles(styles, output)
|
175
|
+
styles.each do |attribute, value|
|
176
|
+
attribute = attribute.to_s
|
177
|
+
attribute.gsub!('_','-')
|
178
|
+
output << "#{attribute}:#{value};"
|
179
|
+
end
|
54
180
|
end
|
55
181
|
|
56
|
-
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
elsif args.length == 1
|
62
|
-
rx = ry = args.pop
|
63
|
-
elsif args.length == 2
|
64
|
-
rx, ry = args
|
65
|
-
else
|
66
|
-
raise "Illegal number of arguments to rectangle"
|
182
|
+
|
183
|
+
def write_transforms(transforms, output)
|
184
|
+
transforms.each do |attribute, value|
|
185
|
+
value = [value] unless value.is_a?(Array)
|
186
|
+
output << "#{attribute.to_s}(#{value.join(',')}) "
|
67
187
|
end
|
188
|
+
end
|
68
189
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
190
|
+
|
191
|
+
def write_points(points, output)
|
192
|
+
points.each_with_index do |value, index|
|
193
|
+
output << value.to_s
|
194
|
+
output << ',' if index.even?
|
195
|
+
output << ' ' if (index.odd? and (index != points.size-1))
|
196
|
+
end
|
73
197
|
end
|
74
198
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
@output << %Q{/>}
|
199
|
+
|
200
|
+
#special case for raw blocks.
|
201
|
+
def raw(data)
|
202
|
+
append_child Rasem::SVGRaw.new(@img, data)
|
80
203
|
end
|
81
204
|
|
82
|
-
|
83
|
-
|
205
|
+
|
206
|
+
#special case for path block
|
207
|
+
def path(attributes = {}, &block)
|
208
|
+
append_child Rasem::SVGPath.new(@img, attributes, &block)
|
84
209
|
end
|
85
210
|
|
86
|
-
|
87
|
-
|
211
|
+
|
212
|
+
#special case for use block
|
213
|
+
def use(id, attributes = {})
|
214
|
+
id = id.attributes[:id] if id.is_a? Rasem::SVGTag
|
215
|
+
append_child Rasem::SVGTagWithParent.new(@img, "use", attributes.merge("xlink:href" => "##{id}"))
|
88
216
|
end
|
89
217
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
218
|
+
|
219
|
+
#special case for linearGradient
|
220
|
+
def linearGradient(id, attributes={}, if_exists = :skip, &block)
|
221
|
+
raise "image reference isn't set, cannot use 'defs' (and thus linearGradient) !" if @img.nil?
|
222
|
+
@img.add_def(id, Rasem::SVGLinearGradient.new(@img, attributes), if_exists, &block)
|
94
223
|
end
|
95
224
|
|
96
|
-
|
97
|
-
|
225
|
+
|
226
|
+
#special case for radialGradient
|
227
|
+
def radialGradient(id, attributes={}, if_exists = :skip, &block)
|
228
|
+
raise "image reference isn't set, cannot use 'defs' (and thus radialGradient) !" if @img.nil?
|
229
|
+
@img.add_def(id, Rasem::SVGRadialGradient.new(@img, attributes), if_exists, &block)
|
98
230
|
end
|
99
231
|
|
100
|
-
|
101
|
-
|
232
|
+
|
233
|
+
|
234
|
+
|
235
|
+
def spawn_child(tag, *args, &block)
|
236
|
+
#expected args: nil, [hash], [...]
|
237
|
+
parameters = {} if args.size == 0
|
238
|
+
|
239
|
+
unless parameters #are empty
|
240
|
+
parameters = args[0] if args[0].is_a? Hash
|
241
|
+
end
|
242
|
+
|
243
|
+
unless parameters #are set
|
244
|
+
#try to find args expansion rule
|
245
|
+
expansion = Rasem::SVG_EXPANSION[tag.to_sym]
|
246
|
+
raise "Unnamed parameters for #{tag} are not allowed!" unless expansion
|
247
|
+
|
248
|
+
if expansion.is_a? Array
|
249
|
+
raise "Bad unnamed parameter count for #{tag}, expecting #{expansion.size} got #{if args.last.is_a? Hash then args.size-1 else args.size end}" unless (args.size == expansion.size and not args.last.is_a? Hash) or (args.size - 1 == expansion.size and args.last.is_a? Hash)
|
250
|
+
parameters = Hash[expansion.zip(args)]
|
251
|
+
if args.last.is_a? Hash
|
252
|
+
parameters.merge! args.last
|
253
|
+
end
|
254
|
+
elsif expansion.is_a? Proc
|
255
|
+
hash = args.pop if args.last.is_a? Hash
|
256
|
+
parameters = expansion.call(args)
|
257
|
+
parameters.merge! hash if hash
|
258
|
+
else
|
259
|
+
raise "Unexpected expansion mechanism: #{expansion.class}"
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# add default parameters if they are not overwritten
|
264
|
+
merge_defaults().each do |key, value|
|
265
|
+
parameters[key] = value unless parameters[key]
|
266
|
+
end if @defaults
|
267
|
+
|
268
|
+
Rasem::SVG_DEFAULTS[tag.to_sym].each do |key, value|
|
269
|
+
parameters[key] = value unless parameters[key]
|
270
|
+
end if Rasem::SVG_DEFAULTS[tag.to_sym]
|
271
|
+
|
272
|
+
append_child(Rasem::SVGTagWithParent.new(@img, tag, parameters, &block))
|
102
273
|
end
|
103
274
|
|
275
|
+
|
276
|
+
def append_child(child)
|
277
|
+
@children.push(child)
|
278
|
+
child.push_defaults(merge_defaults()) if @defaults
|
279
|
+
child
|
280
|
+
end
|
281
|
+
|
282
|
+
|
283
|
+
def merge_defaults()
|
284
|
+
result = {}
|
285
|
+
return result if @defaults.empty?
|
286
|
+
@defaults.each { |d| result.merge!(d) }
|
287
|
+
result
|
288
|
+
end
|
289
|
+
|
290
|
+
|
291
|
+
def push_defaults(defaults)
|
292
|
+
@defaults = [] unless @defaults
|
293
|
+
@defaults.push(defaults)
|
294
|
+
end
|
295
|
+
|
296
|
+
|
297
|
+
def pop_defaults()
|
298
|
+
@defaults.pop()
|
299
|
+
end
|
300
|
+
|
301
|
+
|
104
302
|
def with_style(style={}, &proc)
|
105
|
-
|
106
|
-
updated_style = default_style.merge(style)
|
107
|
-
# Push updated style to the stack
|
108
|
-
@default_styles.push(updated_style)
|
303
|
+
push_defaults(style)
|
109
304
|
# Call the block
|
110
305
|
self.instance_exec(&proc)
|
111
306
|
# Pop style again to revert changes
|
112
|
-
|
307
|
+
pop_defaults()
|
113
308
|
end
|
114
309
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
#
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
def text(x, y, text, style=DefaultStyles[:text])
|
127
|
-
@output << %Q{<text x="#{x}" y="#{y}"}
|
128
|
-
style = fix_style(default_style.merge(style))
|
129
|
-
@output << %Q{ font-family="#{style.delete "font-family"}"} if style["font-family"]
|
130
|
-
@output << %Q{ font-size="#{style.delete "font-size"}"} if style["font-size"]
|
131
|
-
write_style style
|
132
|
-
@output << ">"
|
133
|
-
dy = 0 # First line should not be shifted
|
134
|
-
text.each_line do |line|
|
135
|
-
@output << %Q{<tspan x="#{x}" dy="#{dy}em">}
|
136
|
-
dy = 1 # Next lines should be shifted
|
137
|
-
@output << line.rstrip
|
138
|
-
@output << "</tspan>"
|
310
|
+
|
311
|
+
def validate_child_name(name)
|
312
|
+
#aliases the name (like, group instead of g)
|
313
|
+
name = Rasem::SVG_ALIAS[name.to_sym] if Rasem::SVG_ALIAS[name.to_sym]
|
314
|
+
|
315
|
+
#raises only if given name is an actual svg tag. In other case -- assumes user just mistyped.
|
316
|
+
if Rasem::SVG_STRUCTURE[@tag.to_sym][:elements].include?(name.to_sym)
|
317
|
+
name.to_sym
|
318
|
+
elsif Rasem::SVG_ELEMENTS.include?(name.to_sym)
|
319
|
+
raise "#{@tag} should not contain child #{name}"
|
139
320
|
end
|
140
|
-
@output << "</text>"
|
141
321
|
end
|
142
322
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
323
|
+
|
324
|
+
def method_missing(meth, *args, &block)
|
325
|
+
#if method is a setter or a getter, check valid attributes:
|
326
|
+
check = /^(?<name>.*)(?<op>=|\?)$/.match(meth)
|
327
|
+
if check
|
328
|
+
raise "Passing a code block to setter or getter is not permited!" if block
|
329
|
+
name = validate_attribute(check[:name].to_sym)
|
330
|
+
if check[:op] == '?'
|
331
|
+
@attributes[name]
|
332
|
+
elsif check[:op] == '='
|
333
|
+
raise "Setting an attribute with multiple values is not permited!" if args.size > 1
|
334
|
+
@attributes[name] = args[0]
|
335
|
+
end
|
336
|
+
elsif child = validate_child_name(meth)
|
337
|
+
spawn_child(child, *args, &block)
|
150
338
|
else
|
151
|
-
|
339
|
+
super
|
152
340
|
end
|
153
341
|
end
|
342
|
+
|
343
|
+
|
344
|
+
def write(output)
|
345
|
+
raise "Can not write to given output!" unless output.respond_to?(:<<)
|
346
|
+
output << "<#{@tag.to_s}"
|
347
|
+
@attributes.each do
|
348
|
+
|attribute, value|
|
349
|
+
output << " #{attribute.to_s}=\""
|
350
|
+
if attribute == :style
|
351
|
+
write_styles(value, output)
|
352
|
+
elsif attribute == :points
|
353
|
+
write_points(value, output)
|
354
|
+
else
|
355
|
+
output << "#{value.to_s}"
|
356
|
+
end
|
357
|
+
output << "\""
|
358
|
+
end
|
359
|
+
|
360
|
+
if @children.empty?
|
361
|
+
output << "/>"
|
362
|
+
else
|
363
|
+
output << ">"
|
364
|
+
@children.each { |c| c.write(output) }
|
365
|
+
output << "</#{@tag.to_s}>"
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
def to_s
|
370
|
+
str = ""
|
371
|
+
write(str)
|
372
|
+
return str
|
373
|
+
end
|
374
|
+
|
375
|
+
|
376
|
+
private
|
377
|
+
|
378
|
+
def add_transform(type, params)
|
379
|
+
attr_name = validate_attribute(:transform)
|
380
|
+
@attributes[attr_name] = "" if @attributes[attr_name].nil?
|
381
|
+
@attributes[attr_name] = @attributes[attr_name] + "#{type}(#{params})"
|
382
|
+
end
|
383
|
+
|
384
|
+
end
|
385
|
+
|
386
|
+
|
387
|
+
|
388
|
+
#Extension of SVGTag to provide a reference to the parent img (used for defs)
|
389
|
+
class Rasem::SVGTagWithParent < Rasem::SVGTag
|
390
|
+
|
391
|
+
attr_reader :img
|
392
|
+
|
393
|
+
def initialize(img, tag, params = {}, output=nil, &block)
|
394
|
+
@img = img
|
395
|
+
super(tag, params, &block)
|
396
|
+
end
|
397
|
+
|
398
|
+
end
|
399
|
+
|
400
|
+
|
401
|
+
|
402
|
+
#inherit from tag for basic functionality, control raw data using the write method
|
403
|
+
class Rasem::SVGRaw < Rasem::SVGTagWithParent
|
404
|
+
|
405
|
+
def initialize(img, data)
|
406
|
+
@img = img
|
407
|
+
@data = data
|
408
|
+
end
|
409
|
+
|
410
|
+
|
411
|
+
def write(output)
|
412
|
+
output << @data.to_s
|
413
|
+
end
|
414
|
+
|
415
|
+
end
|
416
|
+
|
417
|
+
|
418
|
+
|
419
|
+
class Rasem::SVGImage < Rasem::SVGTagWithParent
|
420
|
+
|
421
|
+
|
422
|
+
def initialize(params = {}, output=nil, &block)
|
423
|
+
@defs = nil
|
424
|
+
@defs_ids = {}
|
425
|
+
|
426
|
+
params[:"version"] = "1.1" unless params[:"version"]
|
427
|
+
params[:"xmlns"] = "http://www.w3.org/2000/svg" unless params[:"xmlns"]
|
428
|
+
params[:"xmlns:xlink"] = "http://www.w3.org/1999/xlink" unless params[:"xmlns:xlink"]
|
429
|
+
super(self, "svg", params, &block)
|
430
|
+
|
431
|
+
@output = (output or "")
|
432
|
+
validate_output(@output) if output
|
433
|
+
|
434
|
+
if block
|
435
|
+
write(@output)
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
|
440
|
+
def add_def(id, child, if_exists = :skip, &block)
|
441
|
+
#init on the fly if needed
|
442
|
+
@defs = Rasem::SVGTagWithParent.new(@img, "defs") if @defs.nil?
|
443
|
+
|
444
|
+
#raise an error if id is already present and if_exists is :fail
|
445
|
+
raise "Definition '#{id}' already exists" if @defs_ids.has_key? id and if_exists == :fail
|
446
|
+
|
447
|
+
#return the existing element if id is already present and if_exists is :skip
|
448
|
+
return @defs_ids[id] if if_exists == :skip and @defs_ids.has_key? id
|
449
|
+
|
450
|
+
#search for the existing element
|
451
|
+
if @defs_ids[id]
|
452
|
+
old_idx = nil
|
453
|
+
@defs.children.each_with_index { |c,i| if c.attributes[:id] == id then old_idx = i ; break end }
|
454
|
+
end
|
455
|
+
|
456
|
+
#force the id, append the child to definitions and call the given block to fill the group
|
457
|
+
child.attributes[:id] = id
|
458
|
+
@defs.append_child child
|
459
|
+
@defs_ids[id] = child
|
460
|
+
child.instance_exec &block
|
461
|
+
|
462
|
+
#remove the old element if present
|
463
|
+
@defs.children.delete_at old_idx if old_idx
|
464
|
+
|
465
|
+
return child
|
466
|
+
end
|
467
|
+
|
468
|
+
|
469
|
+
def def_group(id, if_exists = :skip, &block)
|
470
|
+
g = Rasem::SVGTagWithParent.new(@img, "g", :id => id)
|
471
|
+
return add_def(id, g, if_exists, &block)
|
472
|
+
end
|
473
|
+
|
474
|
+
|
475
|
+
|
476
|
+
#def text(x, y, text, style=DefaultStyles[:text])
|
477
|
+
# @output << %Q{<text x="#{x}" y="#{y}"}
|
478
|
+
# style = fix_style(default_style.merge(style))
|
479
|
+
# @output << %Q{ font-family="#{style.delete "font-family"}"} if style["font-family"]
|
480
|
+
# @output << %Q{ font-size="#{style.delete "font-size"}"} if style["font-size"]
|
481
|
+
# write_style style
|
482
|
+
# @output << ">"
|
483
|
+
# dy = 0 # First line should not be shifted
|
484
|
+
# text.each_line do |line|
|
485
|
+
# @output << %Q{<tspan x="#{x}" dy="#{dy}em">}
|
486
|
+
# dy = 1 # Next lines should be shifted
|
487
|
+
# @output << line.rstrip
|
488
|
+
# @output << "</tspan>"
|
489
|
+
# end
|
490
|
+
# @output << "</text>"
|
491
|
+
#end
|
492
|
+
|
493
|
+
|
494
|
+
def write(output)
|
495
|
+
validate_output(output)
|
496
|
+
write_header(output)
|
497
|
+
|
498
|
+
@children.unshift @defs if @defs
|
499
|
+
super(output)
|
500
|
+
@children.shift if @defs
|
501
|
+
end
|
502
|
+
|
503
|
+
|
504
|
+
# how to define output << image ?
|
505
|
+
#def <<(output)
|
506
|
+
# write(output)
|
507
|
+
#end
|
508
|
+
|
509
|
+
|
510
|
+
private
|
511
|
+
|
512
|
+
def validate_output(output)
|
513
|
+
raise "Illegal output object: #{output.inspect}" unless output.respond_to?(:<<)
|
514
|
+
end
|
515
|
+
|
154
516
|
|
155
517
|
# Writes file header
|
156
|
-
def write_header(
|
157
|
-
|
518
|
+
def write_header(output)
|
519
|
+
output << <<-HEADER
|
158
520
|
<?xml version="1.0" standalone="no"?>
|
159
|
-
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
160
|
-
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
161
|
-
<svg width="#{width}" height="#{height}" version="1.1"
|
162
|
-
xmlns="http://www.w3.org/2000/svg">
|
521
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
163
522
|
HEADER
|
164
523
|
end
|
165
524
|
|
166
|
-
|
167
|
-
|
168
|
-
|
525
|
+
end
|
526
|
+
|
527
|
+
|
528
|
+
#SVG Path element, with ruby methods to describe the path
|
529
|
+
class Rasem::SVGPath < Rasem::SVGTagWithParent
|
530
|
+
|
531
|
+
|
532
|
+
def initialize(img = nil, attributes={}, &block)
|
533
|
+
attributes.merge!(:d => "") unless attributes.has_key? :d
|
534
|
+
super(img, "path", attributes)
|
169
535
|
end
|
170
536
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
y = coords.shift
|
181
|
-
@output << "#{x},#{y}"
|
182
|
-
@output << " " unless coords.empty?
|
183
|
-
end
|
184
|
-
@output << '"'
|
185
|
-
write_style(style)
|
186
|
-
@output << '/>'
|
537
|
+
|
538
|
+
##
|
539
|
+
# moveTo commands
|
540
|
+
#
|
541
|
+
# Moves pen to specified point x,y without drawing.
|
542
|
+
#
|
543
|
+
##
|
544
|
+
def moveTo(x, y)
|
545
|
+
add_d("m#{x},#{y}")
|
187
546
|
end
|
188
547
|
|
189
|
-
|
190
|
-
def
|
191
|
-
|
548
|
+
|
549
|
+
def moveToA(x, y)
|
550
|
+
add_d("M#{x},#{y}")
|
192
551
|
end
|
193
552
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
553
|
+
|
554
|
+
##
|
555
|
+
# lineTo commands
|
556
|
+
#
|
557
|
+
# Draws a line from current pen location to specified point x,y.
|
558
|
+
#
|
559
|
+
##
|
560
|
+
def lineTo(x, y)
|
561
|
+
add_d("l#{x},#{y}")
|
562
|
+
end
|
563
|
+
|
564
|
+
|
565
|
+
def lineToA(x, y)
|
566
|
+
add_d("L#{x},#{y}")
|
567
|
+
end
|
568
|
+
|
569
|
+
|
570
|
+
##
|
571
|
+
# horizontal lineTo commands
|
572
|
+
#
|
573
|
+
# Draws a horizontal line to the point defined by x.
|
574
|
+
#
|
575
|
+
##
|
576
|
+
def hlineTo(x)
|
577
|
+
add_d("h#{x}")
|
578
|
+
end
|
579
|
+
|
580
|
+
|
581
|
+
def hlineToA(x)
|
582
|
+
add_d("H#{x}")
|
583
|
+
end
|
584
|
+
|
585
|
+
|
586
|
+
##
|
587
|
+
# vertical lineTo commands
|
588
|
+
#
|
589
|
+
# Draws a vertical line to the point defined by y.
|
590
|
+
#
|
591
|
+
##
|
592
|
+
def vlineTo(y)
|
593
|
+
add_d("v#{y}")
|
594
|
+
end
|
595
|
+
|
596
|
+
|
597
|
+
def vlineToA(y)
|
598
|
+
add_d("V#{y}")
|
599
|
+
end
|
600
|
+
|
601
|
+
|
602
|
+
##
|
603
|
+
# curveTo commands
|
604
|
+
#
|
605
|
+
# Draws a cubic Bezier curve from current pen point to dx,dy. x1,y1 and x2,y2
|
606
|
+
# are start and end control points of the curve, controlling how it bends.
|
607
|
+
#
|
608
|
+
##
|
609
|
+
def curveTo(dx, dy, x1, y1, x2, y2)
|
610
|
+
add_d("c#{x1},#{y1} #{x2},#{y2} #{dx},#{dy}")
|
611
|
+
end
|
612
|
+
|
613
|
+
|
614
|
+
def curveToA(dx, dy, x1, y1, x2, y2)
|
615
|
+
add_d("C#{x1},#{y1} #{x2},#{y2} #{dx},#{dy}")
|
616
|
+
end
|
617
|
+
|
618
|
+
|
619
|
+
##
|
620
|
+
# smooth curveTo commands
|
621
|
+
#
|
622
|
+
# Draws a cubic Bezier curve from current pen point to dx,dy. x2,y2 is the
|
623
|
+
# end control point. The start control point is is assumed to be the same
|
624
|
+
# as the end control point of the previous curve.
|
625
|
+
#
|
626
|
+
##
|
627
|
+
def scurveTo(dx, dy, x2, y2)
|
628
|
+
add_d("s#{x2},#{y2} #{dx},#{dy}")
|
629
|
+
end
|
630
|
+
|
631
|
+
|
632
|
+
def scurveToA(dx, dy, x2, y2)
|
633
|
+
add_d("S#{x2},#{y2} #{dx},#{dy}")
|
634
|
+
end
|
635
|
+
|
636
|
+
|
637
|
+
##
|
638
|
+
# quadratic Bezier curveTo commands
|
639
|
+
#
|
640
|
+
# Draws a quadratic Bezier curve from current pen point to dx,dy. x1,y1 is the
|
641
|
+
# control point controlling how the curve bends.
|
642
|
+
#
|
643
|
+
##
|
644
|
+
def qcurveTo(dx, dy, x1, y1)
|
645
|
+
add_d("q#{x1},#{y1} #{dx},#{dy}")
|
646
|
+
end
|
647
|
+
|
648
|
+
|
649
|
+
def qcurveToA(dx, dy, x1, y1)
|
650
|
+
add_d("Q#{x1},#{y1} #{dx},#{dy}")
|
651
|
+
end
|
652
|
+
|
653
|
+
|
654
|
+
##
|
655
|
+
# smooth quadratic Bezier curveTo commands
|
656
|
+
#
|
657
|
+
# Draws a quadratic Bezier curve from current pen point to dx,dy. The control
|
658
|
+
# point is assumed to be the same as the last control point used.
|
659
|
+
#
|
660
|
+
##
|
661
|
+
def sqcurveTo(dx, dy)
|
662
|
+
add_d("t#{dx},#{dy}")
|
663
|
+
end
|
664
|
+
|
665
|
+
|
666
|
+
def sqcurveToA(dx, dy)
|
667
|
+
add_d("T#{dx},#{dy}")
|
668
|
+
end
|
669
|
+
|
670
|
+
|
671
|
+
##
|
672
|
+
# elliptical arc commands
|
673
|
+
#
|
674
|
+
# Draws an elliptical arc from the current point to the point x,y. rx and ry
|
675
|
+
# are the elliptical radius in x and y direction.
|
676
|
+
# The x-rotation determines how much the arc is to be rotated around the
|
677
|
+
# x-axis. It only seems to have an effect when rx and ry have different values.
|
678
|
+
# The large-arc-flag doesn't seem to be used (can be either 0 or 1). Neither
|
679
|
+
# value (0 or 1) changes the arc.
|
680
|
+
# The sweep-flag determines the direction to draw the arc in.
|
681
|
+
#
|
682
|
+
##
|
683
|
+
def arcTo(dx, dy, rx, ry, axis_rotation, large_arc_flag, sweep_flag)
|
684
|
+
add_d("a#{rx},#{ry} #{axis_rotation} #{large_arc_flag},#{sweep_flag} #{dx},#{dy}")
|
685
|
+
end
|
686
|
+
|
687
|
+
|
688
|
+
def arcToA(dx, dy, rx, ry, axis_rotation, large_arc_flag, sweep_flag)
|
689
|
+
add_d("A#{rx},#{ry} #{axis_rotation} #{large_arc_flag},#{sweep_flag} #{dx},#{dy}")
|
690
|
+
end
|
691
|
+
|
692
|
+
|
693
|
+
##
|
694
|
+
# close path command
|
695
|
+
#
|
696
|
+
# Closes the path by drawing a line from current point to first point.
|
697
|
+
#
|
698
|
+
##
|
699
|
+
def close
|
700
|
+
add_d("Z")
|
220
701
|
end
|
702
|
+
|
703
|
+
|
704
|
+
private
|
705
|
+
|
706
|
+
|
707
|
+
def add_d(op)
|
708
|
+
@attributes[:d] = @attributes[:d] + " " + op
|
709
|
+
end
|
710
|
+
|
711
|
+
end
|
712
|
+
|
713
|
+
|
714
|
+
|
715
|
+
#SVG base gradient element, with ruby methods to describe the gradient
|
716
|
+
class Rasem::SVGGradient < Rasem::SVGTagWithParent
|
717
|
+
|
718
|
+
def fill
|
719
|
+
"url(##{@attributes[:id]})"
|
720
|
+
end
|
721
|
+
|
722
|
+
|
723
|
+
def stop(offset, color, opacity)
|
724
|
+
append_child(Rasem::SVGTag.new("stop", "offset" => offset, "stop-color" => color, "stop-opacity" => opacity))
|
725
|
+
end
|
726
|
+
|
727
|
+
end
|
728
|
+
|
729
|
+
|
730
|
+
|
731
|
+
|
732
|
+
#SVG linear gradient element
|
733
|
+
class Rasem::SVGLinearGradient < Rasem::SVGGradient
|
734
|
+
|
735
|
+
def initialize(img, attributes={}, &block)
|
736
|
+
super(img, "linearGradient", attributes, &block)
|
737
|
+
end
|
738
|
+
|
739
|
+
end
|
740
|
+
|
741
|
+
|
742
|
+
#SVG radial gradient element
|
743
|
+
class Rasem::SVGRadialGradient < Rasem::SVGGradient
|
744
|
+
|
745
|
+
def initialize(img, attributes={}, &block)
|
746
|
+
super(img, "radialGradient", attributes, &block)
|
747
|
+
end
|
748
|
+
|
221
749
|
end
|
222
750
|
|