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.
@@ -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