rasem 0.6.1 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,222 +1,750 @@
1
- class Rasem::SVGImage
2
- DefaultStyles = {
3
- :text => {:fill=>"black"},
4
- :line => {:stroke=>"black"},
5
- :rect => {:stroke=>"black"},
6
- :circle => {:stroke=>"black"},
7
- :ellipse => {:stroke=>"black"},
8
- :polygon => {:stroke=>"black"},
9
- :polyline => {:stroke=>"black"}
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
- def initialize(width, height, output=nil, &block)
14
- @output = create_output(output)
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
- # Initialize a stack of default styles
17
- @default_styles = []
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
- self.instance_exec(&block)
22
- self.close
85
+ instance_exec &block
23
86
  end
24
87
  end
25
88
 
26
- def set_width(new_width)
27
- if @output.respond_to?(:sub!)
28
- @output.sub!(/<svg width="[^"]+"/, %Q{<svg width="#{new_width}"})
29
- else
30
- raise "Cannot change width after initialization for this output"
31
- end
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
- def set_height(new_height)
35
- if @output.respond_to?(:sub!)
36
- @output.sub!(/<svg width="([^"]+)" height="[^"]+"/, %Q{<svg width="\\1" height="#{new_height}"})
37
- else
38
- raise "Cannot change width after initialization for this output"
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
- # Draw a straight line between the two end points
43
- def line(x1, y1, x2, y2, style=DefaultStyles[:line])
44
- @output << %Q{<line x1="#{x1}" y1="#{y1}" x2="#{x2}" y2="#{y2}"}
45
- write_style(style)
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
- # Draw a circle given a center and a radius
50
- def circle(cx, cy, r, style=DefaultStyles[:circle])
51
- @output << %Q{<circle cx="#{cx}" cy="#{cy}" r="#{r}"}
52
- write_style(style)
53
- @output << %Q{/>}
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
- # Draw a rectangle or rounded rectangle
57
- def rectangle(x, y, width, height, *args)
58
- style = (!args.empty? && args.last.is_a?(Hash)) ? args.pop : DefaultStyles[:rect]
59
- if args.length == 0
60
- rx = ry = 0
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
- @output << %Q{<rect x="#{x}" y="#{y}" width="#{width}" height="#{height}"}
70
- @output << %Q{ rx="#{rx}" ry="#{ry}"} if rx && ry
71
- write_style(style)
72
- @output << %Q{/>}
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
- # Draw an circle given a center and two radii
76
- def ellipse(cx, cy, rx, ry, style=DefaultStyles[:ellipse])
77
- @output << %Q{<ellipse cx="#{cx}" cy="#{cy}" rx="#{rx}" ry="#{ry}"}
78
- write_style(style)
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
- def polygon(*args)
83
- polything("polygon", *args)
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
- def polyline(*args)
87
- polything("polyline", *args)
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
- # Closes the file. No more drawing is possible after this
91
- def close
92
- write_close
93
- @closed = true
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
- def output
97
- @output.to_s
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
- def closed?
101
- @closed
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
- # Merge passed style with current default style
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
- @default_styles.pop
307
+ pop_defaults()
113
308
  end
114
309
 
115
- def group(style={}, &proc)
116
- # Open the group
117
- @output << "<g"
118
- write_style(style)
119
- @output << ">"
120
- # Call the block
121
- self.instance_exec(&proc)
122
- # Close the group
123
- @output << "</g>"
124
- end
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
- private
144
- # Creates an object for ouput out of an argument
145
- def create_output(arg)
146
- if arg.nil?
147
- ""
148
- elsif arg.respond_to?(:<<)
149
- arg
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
- raise "Illegal output object: #{arg.inspect}"
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(width, height)
157
- @output << <<-HEADER
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
- # Write the closing tag of the file
167
- def write_close
168
- @output << "</svg>"
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
- # Draws either a polygon or polyline according to the first parameter
172
- def polything(name, *args)
173
- return if args.empty?
174
- style = (args.last.is_a?(Hash)) ? args.pop : DefaultStyles[name.to_sym]
175
- coords = args.flatten
176
- raise "Illegal number of coordinates (should be even)" if coords.length.odd?
177
- @output << %Q{<#{name} points="}
178
- until coords.empty? do
179
- x = coords.shift
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
- # Return current deafult style
190
- def default_style
191
- @default_styles.last || {}
548
+
549
+ def moveToA(x, y)
550
+ add_d("M#{x},#{y}")
192
551
  end
193
552
 
194
- # Returns a new hash for styles after fixing names to match SVG standard
195
- def fix_style(style)
196
- new_style = {}
197
- style.each_pair do |k, v|
198
- new_k = k.to_s.gsub('_', '-')
199
- new_style[new_k] = v
200
- end
201
- new_style
202
- end
203
-
204
- # Writes styles to current output
205
- # Avaialable styles are:
206
- # fill: Fill color
207
- # stroke-width: stroke width
208
- # stroke: stroke color
209
- # fill-opacity: fill opacity. ranges from 0 to 1
210
- # stroke-opacity: stroke opacity. ranges from 0 to 1
211
- # opacity: Opacity for the whole element
212
- def write_style(style)
213
- style_ = fix_style(default_style.merge(style))
214
- return if style_.empty?
215
- @output << ' style="'
216
- style_.each_pair do |attribute, value|
217
- @output << "#{attribute}:#{value};"
218
- end
219
- @output << '"'
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