svgplot 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1362ed73c8432a4d47876d69af1d926045cc8650
4
- data.tar.gz: 9de3d7f372a01012cdd48a9b4f5612da0a0be8ad
3
+ metadata.gz: c34bf4dae7c76286d0c33dcd17be373d8bd786fe
4
+ data.tar.gz: dc246aa266b423d779bea78b735995cfc0c21cc8
5
5
  SHA512:
6
- metadata.gz: 4e490f441c1323f48dc42dd7031590285fd88c0353eca35839f1de9637fceeeef5c28ae470227a22bec368d3b2e61dd3b77606652cd29ca9f0765349e58d4a33
7
- data.tar.gz: ece9f1a0bf6fc7c539f6777aa62cdc849d76aae33c52f9c150dc00501620a8af177e4c8f448caad925c002986bf59c67845fcaad506e31b2dd60d71fad0dc668
6
+ metadata.gz: 6164dafb06d5e99a8c8bfd93dc7ce0f2891f9a65375ec466e8fec25011e8581318b5571d77b28199a3f5157067498cb245a08d68511c85827310cbfc15f5c44c
7
+ data.tar.gz: 05b792cd73ad32864c3a97dd417d7f9b612cf1908189abdc9d557d4099311fbe68fbf3b5373e11a8684746ba12b1ad93841813c28df86de6578e8cb29bb52c18
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'rubygems'
4
3
  require 'nokogiri'
5
4
  require 'open-uri'
6
5
  require 'pp'
@@ -14,4 +14,10 @@ end
14
14
  require 'svgplot/spec'
15
15
  require 'svgplot/application'
16
16
  require 'svgplot/meta'
17
+ require 'svgplot/parsers'
18
+ require 'svgplot/transform'
19
+ require 'svgplot/defaults'
20
+ require 'svgplot/tag'
21
+ require 'svgplot/path'
22
+ require 'svgplot/gradient'
17
23
  require 'svgplot/plot'
@@ -10,20 +10,19 @@ module SVGPlot
10
10
  def run!
11
11
  @files.each do |file|
12
12
  output = file.sub(/\.svgplot/, '') + '.svg'
13
- File.open(output, 'w') do |fh|
14
- build file, fh
15
- end
13
+ File.open(output, 'w') { |fh| build file, fh }
16
14
  end
17
15
  end
18
16
 
19
17
  private
20
18
 
21
19
  def build(file, fh)
22
- SVGPlot::Plot.new({ width: '100%', height: '100%' }, fh) do
20
+ SVGPlot.new({ width: '100%', height: '100%' }, fh) do
23
21
  begin
24
22
  instance_eval File.read(file), file
25
23
  rescue StandardError => e
26
- backtrace = e.backtrace.grep(Regexp.new(File.expand_path(file)))
24
+ regexp = Regexp.new(File.expand_path(file))
25
+ backtrace = e.backtrace.grep regexp
27
26
  raise e.class, e.message, backtrace
28
27
  end
29
28
  end
@@ -0,0 +1,17 @@
1
+ module SVGPlot
2
+ ##
3
+ # Adds default attributes helpers for Tags
4
+ module Defaults
5
+ attr_writer :defaults
6
+
7
+ def defaults
8
+ @defaults ||= {}
9
+ end
10
+
11
+ def with(defaults, &block)
12
+ @defaults = defaults
13
+ instance_exec(&block)
14
+ @defaults = {}
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ module SVGPlot
2
+ ##
3
+ # SVG base gradient element, with ruby methods to describe the gradient
4
+ class Gradient < ChildTag
5
+ def fill
6
+ "url(##{@attributes[:id]})"
7
+ end
8
+
9
+ def stop(offset, color, opacity)
10
+ append_child(ChildTag.new(
11
+ @img,
12
+ 'stop',
13
+ 'offset' => offset,
14
+ 'stop-color' => color,
15
+ 'stop-opacity' => opacity
16
+ ))
17
+ end
18
+ end
19
+
20
+ ##
21
+ # linear gradient element
22
+ class LinearGradient < Gradient
23
+ def initialize(img, attributes = {}, &block)
24
+ super(img, 'linearGradient', attributes, &block)
25
+ end
26
+ end
27
+
28
+ ##
29
+ # radial gradient element
30
+ class RadialGradient < Gradient
31
+ def initialize(img, attributes = {}, &block)
32
+ super(img, 'radialGradient', attributes, &block)
33
+ end
34
+ end
35
+ end
@@ -1,52 +1,59 @@
1
- SVGPlot::SVG_ALIAS = {
2
- group: :g,
3
- rectangle: :rect
4
- }
1
+ ##
2
+ # Define additional spec pieces outside the generated data
3
+ module SVGPlot
4
+ ##
5
+ # Aliases for common tags
6
+ SVG_ALIAS = {
7
+ group: :g,
8
+ rectangle: :rect
9
+ }
5
10
 
6
- SVGPlot::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
- rect: lambda do |args|
13
- unless [4, 5, 6].include? args.size
14
- fail ArgumentError 'Wrong unnamed argument count'
15
- end
16
- result = {
17
- x: args[0],
18
- y: args[1],
19
- width: args[2],
20
- height: args[3]
21
- }
22
- if args.size > 4
23
- result[:rx] = args[4]
24
- result[:ry] = (args[5] || args[4])
25
- end
26
- result
27
- end,
28
- polygon: lambda do |args|
29
- args.flatten!
30
- if args.length.odd?
31
- fail ArgumentError 'Illegal number of coordinates (should be even)'
32
- end
33
- { points: args }
34
- end,
35
- polyline: lambda do |args|
36
- args.flatten!
11
+ ##
12
+ # Generic lambda for adding points, used below
13
+ POINT_LAMBDA = lambda do |args|
37
14
  if args.length.odd?
38
15
  fail ArgumentError 'Illegal number of coordinates (should be even)'
39
16
  end
40
- { points: args }
17
+ { points: args.each_slice(2).map { |x| x.join(',') }.join(' ') }
41
18
  end
42
- }
43
19
 
44
- # TODO: move to documentation?
45
- SVGPlot::SVG_TRANSFORM = [
46
- :matrix, # expects an array of 6 elements
47
- :translate, # [tx, ty?]
48
- :scale, # [sx, sy?]
49
- :rotate, # [angle, (cx, cy)?]
50
- :skewX, # angle
51
- :skewY # angle
52
- ]
20
+ ##
21
+ # Expansion definitions for unnamed args
22
+ SVG_EXPANSION = {
23
+ line: [:x1, :y1, :x2, :y2],
24
+ circle: [:cx, :cy, :r],
25
+ image: [:x, :y, :width, :height, :'xlink:href'],
26
+ ellipse: [:cx, :cy, :rx, :ry],
27
+ text: [:x, :y],
28
+ rect: lambda do |args|
29
+ unless [4, 5, 6].include? args.size
30
+ fail ArgumentError 'Wrong unnamed argument count'
31
+ end
32
+ result = Hash[[:x, :y, :width, :height].zip(args)]
33
+ if args.size > 4
34
+ result[:rx] = args[4]
35
+ result[:ry] = args[5] || args[4]
36
+ end
37
+ result
38
+ end,
39
+ polygon: POINT_LAMBDA,
40
+ polyline: POINT_LAMBDA
41
+ }
42
+
43
+ ##
44
+ # Define processing for Expansion constants
45
+ module Expansion
46
+ def expand(tag, args)
47
+ expansion = SVG_EXPANSION[tag.to_sym]
48
+ fail("Unnamed parameters for #{tag} are not allowed!") unless expansion
49
+
50
+ if expansion.is_a? Array
51
+ parse_args(tag, expansion, args)
52
+ elsif expansion.is_a? Proc
53
+ expansion.call(args)
54
+ else
55
+ fail "Unexpected expansion mechanism: #{expansion.class}"
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,80 @@
1
+ module SVGPlot
2
+ module Parsers
3
+ ##
4
+ # Add parsing methods for various attributes
5
+ module Tag
6
+ private
7
+
8
+ def parse_tag(tag)
9
+ return tag.to_sym if SVGPlot::SVG_ELEMENTS.include?(tag.to_sym)
10
+ fail "#{tag} is not a valid tag"
11
+ end
12
+
13
+ def parse_args(tag, keys, values)
14
+ if keys.size != values.size
15
+ fail("Arg mismatch for #{tag}: got #{values.size}, not #{keys.size}")
16
+ end
17
+ Hash[keys.zip(values)]
18
+ end
19
+
20
+ def valid_attribute?(attribute)
21
+ allowed = SVGPlot::SVG_STRUCTURE[@tag.to_sym][:attributes]
22
+ return true if allowed.include?(attribute.to_sym)
23
+ false
24
+ end
25
+
26
+ def validate_attribute(attribute)
27
+ return attribute.to_sym if valid_attribute? attribute
28
+ fail "#{@tag} does not support attribute #{attribute}"
29
+ end
30
+
31
+ def parse_attributes(raw)
32
+ clean = {
33
+ transform: parse_transforms(raw.delete(:transform)),
34
+ style: parse_styles(raw.delete(:style))
35
+ }
36
+ raw.delete(:data) { Hash.new }.each do |key, value|
37
+ clean["data-#{key}".to_sym] = value
38
+ end
39
+ raw.each_key { |k| validate_attribute k }
40
+ clean.reject { |_, v| v.nil? }.merge raw
41
+ end
42
+
43
+ def parse_transforms(transforms)
44
+ return nil unless transforms && valid_attribute?(:transform)
45
+ transforms.each_with_object('') do |(attr, value), str|
46
+ str << "#{attr}(#{value.is_a?(Array) ? value.join(',') : value }) "
47
+ end
48
+ end
49
+
50
+ def parse_styles(styles)
51
+ return nil unless styles && valid_attribute?(:style)
52
+ styles.each_with_object('') { |(k, v), str| str << "#{k}:#{v};" }
53
+ end
54
+
55
+ def parse_method_name(method)
56
+ check = /^(?<name>.*)(?<op>=|\?)$/.match(method)
57
+ return check if check && valid_attribute?(check[:name])
58
+ end
59
+
60
+ def parse_method_op(op, attr, args, &block)
61
+ fail('Invalid attribute name') unless valid_attribute? attr
62
+ fail('Passing a block to setter or getter is not permitted') if block
63
+ return @attributes[attr] if op == '?'
64
+ return @attributes[attr] = args.first if args.size == 1
65
+ fail('Setting an attribute with multiple values is not permitted!')
66
+ end
67
+
68
+ def parse_child_name(name)
69
+ name = SVG_ALIAS[name.to_sym] if SVG_ALIAS[name.to_sym]
70
+
71
+ if SVG_STRUCTURE[@tag.to_sym][:elements].include?(name.to_sym)
72
+ return name.to_sym
73
+ elsif SVG_ELEMENTS.include?(name.to_sym)
74
+ fail "#{@tag} should not contain child #{name}"
75
+ end
76
+ nil
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,132 @@
1
+ # rubocop:disable Metrics/ParameterLists
2
+ module SVGPlot
3
+ # SVG Path element, with ruby methods to describe the path
4
+ class Path < ChildTag
5
+ def initialize(img, attributes = {})
6
+ attributes[:d] ||= ''
7
+ super(img, 'path', attributes)
8
+ end
9
+
10
+ ##
11
+ # Moves pen to specified point x,y without drawing.
12
+ def move_to(x, y)
13
+ add_d("m#{x},#{y}")
14
+ end
15
+
16
+ def move_to_a(x, y)
17
+ add_d("M#{x},#{y}")
18
+ end
19
+
20
+ ##
21
+ # Draws a line from current pen location to specified point x,y.
22
+ def line_to(x, y)
23
+ add_d("l#{x},#{y}")
24
+ end
25
+
26
+ def line_to_a(x, y)
27
+ add_d("L#{x},#{y}")
28
+ end
29
+
30
+ ##
31
+ # Draws a horizontal line to the point defined by x.
32
+ def hline_to(x)
33
+ add_d("h#{x}")
34
+ end
35
+
36
+ def hline_to_a(x)
37
+ add_d("H#{x}")
38
+ end
39
+
40
+ ##
41
+ # Draws a vertical line to the point defined by y.
42
+ def vline_to(y)
43
+ add_d("v#{y}")
44
+ end
45
+
46
+ def vline_to_a(y)
47
+ add_d("V#{y}")
48
+ end
49
+
50
+ ##
51
+ # Draws a cubic Bezier curve from current pen point to dx,dy.
52
+ # x1,y1 and x2,y2 are start and end control points of the curve,
53
+ # controlling how it bends.
54
+ def curve_to(dx, dy, x1, y1, x2, y2)
55
+ add_d("c#{x1},#{y1} #{x2},#{y2} #{dx},#{dy}")
56
+ end
57
+
58
+ def curve_to_a(dx, dy, x1, y1, x2, y2)
59
+ add_d("C#{x1},#{y1} #{x2},#{y2} #{dx},#{dy}")
60
+ end
61
+
62
+ ##
63
+ # Draws a cubic Bezier curve from current pen point to dx,dy. x2,y2 is the
64
+ # end control point. The start control point is is assumed to be the same
65
+ # as the end control point of the previous curve.
66
+ def scurve_to(dx, dy, x2, y2)
67
+ add_d("s#{x2},#{y2} #{dx},#{dy}")
68
+ end
69
+
70
+ def scurve_to_a(dx, dy, x2, y2)
71
+ add_d("S#{x2},#{y2} #{dx},#{dy}")
72
+ end
73
+
74
+ ##
75
+ # Draws a quadratic Bezier curve from current pen point to dx,dy. x1,y1 is
76
+ # the control point controlling how the curve bends.
77
+ def qcurve_to(dx, dy, x1, y1)
78
+ add_d("q#{x1},#{y1} #{dx},#{dy}")
79
+ end
80
+
81
+ def qcurve_to_a(dx, dy, x1, y1)
82
+ add_d("Q#{x1},#{y1} #{dx},#{dy}")
83
+ end
84
+
85
+ ##
86
+ # Draws a quadratic Bezier curve from current pen point to dx,dy. The
87
+ # control point is assumed to be the same as the last control point used.
88
+ def sqcurve_to(dx, dy)
89
+ add_d("t#{dx},#{dy}")
90
+ end
91
+
92
+ def sqcurve_to_a(dx, dy)
93
+ add_d("T#{dx},#{dy}")
94
+ end
95
+
96
+ ##
97
+ # Draws an elliptical arc from the current point to the point x,y. rx and ry
98
+ # are the elliptical radius in x and y direction.
99
+ # The x-rotation determines how much the arc is to be rotated around the
100
+ # x-axis. It only has an effect when rx and ry have different values.
101
+ # The large-arc-flag doesn't seem to be used (can be either 0 or 1). Neither
102
+ # value (0 or 1) changes the arc.
103
+ # The sweep-flag determines the direction to draw the arc in.
104
+ def arc_to(dx, dy, rx, ry, axis_rot, large_arc_flag, sweep_flag)
105
+ add_d(
106
+ "a#{rx},#{ry} #{axis_rot} #{large_arc_flag},#{sweep_flag} #{dx},#{dy}"
107
+ )
108
+ end
109
+
110
+ def arc_to_a(dx, dy, rx, ry, axis_rot, large_arc_flag, sweep_flag)
111
+ add_d(
112
+ "A#{rx},#{ry} #{axis_rot} #{large_arc_flag},#{sweep_flag} #{dx},#{dy}"
113
+ )
114
+ end
115
+
116
+ ##
117
+ # close path command
118
+ #
119
+ # Closes the path by drawing a line from current point to first point.
120
+ #
121
+ ##
122
+ def close
123
+ add_d('Z')
124
+ end
125
+
126
+ private
127
+
128
+ def add_d(op)
129
+ @attributes[:d] << " #{op}"
130
+ end
131
+ end
132
+ end
@@ -1,636 +1,66 @@
1
- # rubocop:disable all
2
1
  module SVGPlot
3
- class SVGTag
4
- attr_reader :tag, :attributes, :children
5
-
6
- def initialize(tag, attributes={}, &block)
7
- @tag = validate_tag(tag)
8
- @attributes = validate_attributes(attributes)
9
- @children = []
10
-
11
- if block
12
- instance_exec &block
13
- end
14
- end
15
-
16
- ##
17
- # Provide methods for SVG transformations
18
- ##
19
-
20
- def translate(tx, ty = 0)
21
- add_transform(:translate, "#{tx}, #{ty}")
22
- self
23
- end
24
-
25
- def scale(sx, sy = 1)
26
- add_transform(:scale, "#{sx}, #{sy}")
27
- self
28
- end
29
-
30
- def rotate(angle, cx = nil, cy = nil)
31
- add_transform(:rotate, "#{angle}#{(cx.nil? or cy.nil?) ? "" : ", #{cx}, #{cy}"}")
32
- self
33
- end
34
-
35
- def skewX(angle)
36
- add_transform(:skewX, "#{angle}")
37
- self
38
- end
39
-
40
- def skewY(angle)
41
- add_transform(:skewY, "#{angle}")
42
- self
43
- end
44
-
45
- def matrix(a, b, c, d, e, f)
46
- add_transform(:matrix, "#{a}, #{b}, #{c}, #{d}, #{e}, #{f}")
47
- self
48
- end
49
-
50
- def validate_tag(tag)
51
- raise "#{tag} is not a valid tag" unless SVGPlot::SVG_ELEMENTS.include?(tag.to_sym)
52
- tag.to_sym
53
- end
54
-
55
- def validate_attributes(attributes)
56
- clean_attributes = {}
57
-
58
- transforms = {}
59
- attributes.delete(:transform) { Hash.new }.each do |key, value|
60
- transforms[key] = value
61
- end
62
- unless transforms.empty?
63
- str = ""
64
- write_transforms(transforms, str)
65
- clean_attributes[validate_attribute(:transform)] = str
66
- end
67
-
68
- styles = {}
69
- attributes.delete(:style) { Hash.new }.each { |k, v| styles[k] = v }
70
- unless styles.empty?
71
- clean_attributes[validate_attribute(:style)] = styles
72
- end
73
-
74
- attributes.delete(:data) { Hash.new }.each do |key, value|
75
- clean_attributes["data-#{key.to_s}".to_sym] = value
76
- end
77
-
78
- attributes.each do |key, value|
79
- clean_attributes[validate_attribute(key)] = value
80
- end
81
-
82
- clean_attributes
83
- end
84
-
85
- def validate_attribute(attribute)
86
- raise "#{@tag} does not support attribute #{attribute}" unless SVGPlot::SVG_STRUCTURE[@tag.to_sym][:attributes].include?(attribute.to_sym)
87
- attribute.to_sym
88
- end
89
-
90
- def write_styles(styles, output)
91
- styles.each do |attribute, value|
92
- attribute = attribute.to_s
93
- attribute.gsub!('_','-')
94
- output << "#{attribute}:#{value};"
95
- end
96
- end
97
-
98
- def write_transforms(transforms, output)
99
- transforms.each do |attribute, value|
100
- value = [value] unless value.is_a?(Array)
101
- output << "#{attribute.to_s}(#{value.join(',')}) "
102
- end
103
- end
104
-
105
- def write_points(points, output)
106
- points.each_with_index do |value, index|
107
- output << value.to_s
108
- output << ',' if index.even?
109
- output << ' ' if (index.odd? and (index != points.size-1))
110
- end
111
- end
112
-
113
- #special case for raw blocks.
114
- def raw(data)
115
- append_child SVGPlot::SVGRaw.new(@img, data)
116
- end
117
-
118
- #special case for path block
119
- def path(attributes = {}, &block)
120
- append_child SVGPlot::SVGPath.new(@img, attributes, &block)
121
- end
122
-
123
- #special case for use block
124
- def use(id, attributes = {})
125
- id = id.attributes[:id] if id.is_a? SVGPlot::SVGTag
126
- append_child SVGPlot::SVGTagWithParent.new(@img, "use", attributes.merge("xlink:href" => "##{id}"))
127
- end
128
-
129
-
130
- #special case for linearGradient
131
- def linearGradient(id, attributes={}, if_exists = :skip, &block)
132
- raise "image reference isn't set, cannot use 'defs' (and thus linearGradient) !" if @img.nil?
133
- @img.add_def(id, SVGPlot::SVGLinearGradient.new(@img, attributes), if_exists, &block)
134
- end
135
-
136
-
137
- #special case for radialGradient
138
- def radialGradient(id, attributes={}, if_exists = :skip, &block)
139
- raise "image reference isn't set, cannot use 'defs' (and thus radialGradient) !" if @img.nil?
140
- @img.add_def(id, SVGPlot::SVGRadialGradient.new(@img, attributes), if_exists, &block)
141
- end
142
-
143
-
144
-
145
-
146
- def spawn_child(tag, *args, &block)
147
- #expected args: nil, [hash], [...]
148
- parameters = {} if args.size == 0
149
-
150
- unless parameters #are empty
151
- parameters = args[0] if args[0].is_a? Hash
152
- end
153
-
154
- unless parameters #are set
155
- #try to find args expansion rule
156
- expansion = SVGPlot::SVG_EXPANSION[tag.to_sym]
157
- raise "Unnamed parameters for #{tag} are not allowed!" unless expansion
158
-
159
- if expansion.is_a? Array
160
- 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)
161
- parameters = Hash[expansion.zip(args)]
162
- if args.last.is_a? Hash
163
- parameters.merge! args.last
164
- end
165
- elsif expansion.is_a? Proc
166
- hash = args.pop if args.last.is_a? Hash
167
- parameters = expansion.call(args)
168
- parameters.merge! hash if hash
169
- else
170
- raise "Unexpected expansion mechanism: #{expansion.class}"
171
- end
172
- end
173
-
174
- # add default parameters if they are not overwritten
175
- merge_defaults().each do |key, value|
176
- parameters[key] = value unless parameters[key]
177
- end if @defaults
178
-
179
- append_child(SVGPlot::SVGTagWithParent.new(@img, tag, parameters, &block))
180
- end
181
-
182
-
183
- def append_child(child)
184
- @children.push(child)
185
- child.push_defaults(merge_defaults()) if @defaults
186
- child
187
- end
188
-
189
-
190
- def merge_defaults()
191
- result = {}
192
- return result if @defaults.empty?
193
- @defaults.each { |d| result.merge!(d) }
194
- result
195
- end
196
-
197
-
198
- def push_defaults(defaults)
199
- @defaults = [] unless @defaults
200
- @defaults.push(defaults)
201
- end
202
-
203
-
204
- def pop_defaults()
205
- @defaults.pop()
206
- end
207
-
208
-
209
- def with_style(style={}, &proc)
210
- push_defaults(style)
211
- # Call the block
212
- self.instance_exec(&proc)
213
- # Pop style again to revert changes
214
- pop_defaults()
215
- end
216
-
217
-
218
- def validate_child_name(name)
219
- #aliases the name (like, group instead of g)
220
- name = SVGPlot::SVG_ALIAS[name.to_sym] if SVGPlot::SVG_ALIAS[name.to_sym]
221
-
222
- #raises only if given name is an actual svg tag. In other case -- assumes user just mistyped.
223
- if SVGPlot::SVG_STRUCTURE[@tag.to_sym][:elements].include?(name.to_sym)
224
- name.to_sym
225
- elsif SVGPlot::SVG_ELEMENTS.include?(name.to_sym)
226
- raise "#{@tag} should not contain child #{name}"
227
- end
228
- end
229
-
230
-
231
- def method_missing(meth, *args, &block)
232
- #if method is a setter or a getter, check valid attributes:
233
- check = /^(?<name>.*)(?<op>=|\?)$/.match(meth)
234
- if check
235
- raise "Passing a code block to setter or getter is not permited!" if block
236
- name = validate_attribute(check[:name].to_sym)
237
- if check[:op] == '?'
238
- @attributes[name]
239
- elsif check[:op] == '='
240
- raise "Setting an attribute with multiple values is not permited!" if args.size > 1
241
- @attributes[name] = args[0]
242
- end
243
- elsif child = validate_child_name(meth)
244
- spawn_child(child, *args, &block)
245
- else
246
- super
247
- end
248
- end
249
-
250
-
251
- def write(output)
252
- raise "Can not write to given output!" unless output.respond_to?(:<<)
253
- output << "<#{@tag.to_s}"
254
- @attributes.each do
255
- |attribute, value|
256
- output << " #{attribute.to_s}=\""
257
- if attribute == :style
258
- write_styles(value, output)
259
- elsif attribute == :points
260
- write_points(value, output)
261
- else
262
- output << "#{value.to_s}"
263
- end
264
- output << "\""
265
- end
266
-
267
- if @children.empty?
268
- output << "/>"
269
- else
270
- output << ">"
271
- @children.each { |c| c.write(output) }
272
- output << "</#{@tag.to_s}>"
273
- end
274
- end
275
-
276
- def to_s
277
- str = ""
278
- write(str)
279
- return str
280
- end
281
-
282
-
283
- private
284
-
285
- def add_transform(type, params)
286
- attr_name = validate_attribute(:transform)
287
- @attributes[attr_name] = "" if @attributes[attr_name].nil?
288
- @attributes[attr_name] = @attributes[attr_name] + "#{type}(#{params})"
289
- end
290
-
291
- end
292
- end
293
-
294
-
295
- #Extension of SVGTag to provide a reference to the parent img (used for defs)
296
- module SVGPlot
297
- class SVGTagWithParent < SVGPlot::SVGTag
298
- attr_reader :img
299
-
300
- def initialize(img, tag, params = {}, output=nil, &block)
301
- @img = img
302
- super(tag, params, &block)
303
- end
304
- end
305
- end
306
-
307
-
308
-
309
- #inherit from tag for basic functionality, control raw data using the write method
310
- module SVGPlot
311
- class SVGRaw < SVGPlot::SVGTagWithParent
312
-
313
- def initialize(img, data)
314
- @img = img
315
- @data = data
316
- end
317
-
318
- def write(output)
319
- output << @data.to_s
320
- end
321
- end
322
- end
323
-
324
-
325
-
326
- module SVGPlot
327
- class Plot < SVGPlot::SVGTagWithParent
328
- def initialize(params = {}, output=nil, &block)
329
- @defs = nil
330
- @defs_ids = {}
331
-
332
- params[:"version"] = "1.1" unless params[:"version"]
333
- params[:"xmlns"] = "http://www.w3.org/2000/svg" unless params[:"xmlns"]
334
- params[:"xmlns:xlink"] = "http://www.w3.org/1999/xlink" unless params[:"xmlns:xlink"]
335
- super(self, "svg", params, &block)
336
-
337
- @output = (output or "")
338
- validate_output(@output) if output
2
+ ##
3
+ # Main Plot object
4
+ class Plot < ChildTag
5
+ DEFAULTS = {
6
+ version: 1.1,
7
+ xmlns: 'http://www.w3.org/2000/svg',
8
+ :'xmlns:xlink' => 'http://www.w3.org/1999/xlink'
9
+ }
339
10
 
340
- if block
341
- write(@output)
342
- end
11
+ def initialize(params = {}, output = nil, &block)
12
+ params = DEFAULTS.dup.merge params
13
+ super(self, 'svg', params, &block)
14
+ @output = output || ''
15
+ write(@output) if block
343
16
  end
344
17
 
345
18
  def add_def(id, child, if_exists = :skip, &block)
346
- #init on the fly if needed
347
- @defs = SVGPlot::SVGTagWithParent.new(@img, "defs") if @defs.nil?
348
-
349
- #raise an error if id is already present and if_exists is :fail
350
- raise "Definition '#{id}' already exists" if @defs_ids.has_key? id and if_exists == :fail
19
+ @defs ||= ChildTag.new(@img, 'defs')
20
+ old_id = check_conflicts(id, if_exists) if @defs_ids.key? id
21
+ return old_id if old_id
351
22
 
352
- #return the existing element if id is already present and if_exists is :skip
353
- return @defs_ids[id] if if_exists == :skip and @defs_ids.has_key? id
354
-
355
- #search for the existing element
356
- if @defs_ids[id]
357
- old_idx = nil
358
- @defs.children.each_with_index { |c,i| if c.attributes[:id] == id then old_idx = i ; break end }
359
- end
360
-
361
- #force the id, append the child to definitions and call the given block to fill the group
362
23
  child.attributes[:id] = id
363
24
  @defs.append_child child
364
25
  @defs_ids[id] = child
365
- child.instance_exec block
366
-
367
- #remove the old element if present
368
- @defs.children.delete_at old_idx if old_idx
26
+ child.instance_exec block if block
369
27
 
370
- return child
28
+ child
371
29
  end
372
30
 
373
31
  def def_group(id, if_exists = :skip, &block)
374
- g = SVGPlot::SVGTagWithParent.new(@img, "g", :id => id)
375
- return add_def(id, g, if_exists, &block)
32
+ g = SVGPlot::SVGTagWithParent.new(@img, 'g', id: id)
33
+ add_def(id, g, if_exists, &block)
376
34
  end
377
35
 
378
- #def text(x, y, text, style=DefaultStyles[:text])
379
- # @output << %Q{<text x="#{x}" y="#{y}"}
380
- # style = fix_style(default_style.merge(style))
381
- # @output << %Q{ font-family="#{style.delete "font-family"}"} if style["font-family"]
382
- # @output << %Q{ font-size="#{style.delete "font-size"}"} if style["font-size"]
383
- # write_style style
384
- # @output << ">"
385
- # dy = 0 # First line should not be shifted
386
- # text.each_line do |line|
387
- # @output << %Q{<tspan x="#{x}" dy="#{dy}em">}
388
- # dy = 1 # Next lines should be shifted
389
- # @output << line.rstrip
390
- # @output << "</tspan>"
391
- # end
392
- # @output << "</text>"
393
- #end
394
-
395
-
396
36
  def write(output)
397
- validate_output(output)
398
- write_header(output)
399
-
37
+ fail("Illegal output: #{@output.inspect}") unless @output.respond_to? :<<
38
+ output << header
400
39
  @children.unshift @defs if @defs
401
40
  super(output)
402
41
  @children.shift if @defs
403
42
  end
404
43
 
405
-
406
- # how to define output << image ?
407
- #def <<(output)
408
- # write(output)
409
- #end
410
-
411
44
  private
412
45
 
413
- def validate_output(output)
414
- raise "Illegal output object: #{output.inspect}" unless output.respond_to?(:<<)
46
+ def check_conflicts(id, if_exists)
47
+ case if_exists
48
+ when :fail
49
+ fail("Definition '#{id}' already exists")
50
+ when :skip
51
+ return @defs_ids[id]
52
+ else
53
+ @defs.children.reject! { |x| x.attributes[:id] == id }
54
+ nil
55
+ end
415
56
  end
416
57
 
417
- # Writes file header
418
- def write_header(output)
419
- output << <<-HEADER
420
- <?xml version="1.0" standalone="no"?>
421
- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
422
- HEADER
58
+ def header
59
+ [
60
+ %(<?xml version="1.0" standalone="no"?>\n),
61
+ %(<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ),
62
+ %("http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">)
63
+ ].join
423
64
  end
424
65
  end
425
66
  end
426
-
427
- #SVG Path element, with ruby methods to describe the path
428
- class SVGPlot::SVGPath < SVGPlot::SVGTagWithParent
429
- def initialize(img = nil, attributes={}, &block)
430
- attributes.merge!(:d => "") unless attributes.has_key? :d
431
- super(img, "path", attributes)
432
- end
433
-
434
- ##
435
- # moveTo commands
436
- #
437
- # Moves pen to specified point x,y without drawing.
438
- #
439
- ##
440
- def moveTo(x, y)
441
- add_d("m#{x},#{y}")
442
- end
443
-
444
-
445
- def moveToA(x, y)
446
- add_d("M#{x},#{y}")
447
- end
448
-
449
-
450
- ##
451
- # lineTo commands
452
- #
453
- # Draws a line from current pen location to specified point x,y.
454
- #
455
- ##
456
- def lineTo(x, y)
457
- add_d("l#{x},#{y}")
458
- end
459
-
460
-
461
- def lineToA(x, y)
462
- add_d("L#{x},#{y}")
463
- end
464
-
465
-
466
- ##
467
- # horizontal lineTo commands
468
- #
469
- # Draws a horizontal line to the point defined by x.
470
- #
471
- ##
472
- def hlineTo(x)
473
- add_d("h#{x}")
474
- end
475
-
476
-
477
- def hlineToA(x)
478
- add_d("H#{x}")
479
- end
480
-
481
-
482
- ##
483
- # vertical lineTo commands
484
- #
485
- # Draws a vertical line to the point defined by y.
486
- #
487
- ##
488
- def vlineTo(y)
489
- add_d("v#{y}")
490
- end
491
-
492
-
493
- def vlineToA(y)
494
- add_d("V#{y}")
495
- end
496
-
497
-
498
- ##
499
- # curveTo commands
500
- #
501
- # Draws a cubic Bezier curve from current pen point to dx,dy. x1,y1 and x2,y2
502
- # are start and end control points of the curve, controlling how it bends.
503
- #
504
- ##
505
- def curveTo(dx, dy, x1, y1, x2, y2)
506
- add_d("c#{x1},#{y1} #{x2},#{y2} #{dx},#{dy}")
507
- end
508
-
509
-
510
- def curveToA(dx, dy, x1, y1, x2, y2)
511
- add_d("C#{x1},#{y1} #{x2},#{y2} #{dx},#{dy}")
512
- end
513
-
514
-
515
- ##
516
- # smooth curveTo commands
517
- #
518
- # Draws a cubic Bezier curve from current pen point to dx,dy. x2,y2 is the
519
- # end control point. The start control point is is assumed to be the same
520
- # as the end control point of the previous curve.
521
- #
522
- ##
523
- def scurveTo(dx, dy, x2, y2)
524
- add_d("s#{x2},#{y2} #{dx},#{dy}")
525
- end
526
-
527
-
528
- def scurveToA(dx, dy, x2, y2)
529
- add_d("S#{x2},#{y2} #{dx},#{dy}")
530
- end
531
-
532
-
533
- ##
534
- # quadratic Bezier curveTo commands
535
- #
536
- # Draws a quadratic Bezier curve from current pen point to dx,dy. x1,y1 is the
537
- # control point controlling how the curve bends.
538
- #
539
- ##
540
- def qcurveTo(dx, dy, x1, y1)
541
- add_d("q#{x1},#{y1} #{dx},#{dy}")
542
- end
543
-
544
-
545
- def qcurveToA(dx, dy, x1, y1)
546
- add_d("Q#{x1},#{y1} #{dx},#{dy}")
547
- end
548
-
549
-
550
- ##
551
- # smooth quadratic Bezier curveTo commands
552
- #
553
- # Draws a quadratic Bezier curve from current pen point to dx,dy. The control
554
- # point is assumed to be the same as the last control point used.
555
- #
556
- ##
557
- def sqcurveTo(dx, dy)
558
- add_d("t#{dx},#{dy}")
559
- end
560
-
561
-
562
- def sqcurveToA(dx, dy)
563
- add_d("T#{dx},#{dy}")
564
- end
565
-
566
-
567
- ##
568
- # elliptical arc commands
569
- #
570
- # Draws an elliptical arc from the current point to the point x,y. rx and ry
571
- # are the elliptical radius in x and y direction.
572
- # The x-rotation determines how much the arc is to be rotated around the
573
- # x-axis. It only seems to have an effect when rx and ry have different values.
574
- # The large-arc-flag doesn't seem to be used (can be either 0 or 1). Neither
575
- # value (0 or 1) changes the arc.
576
- # The sweep-flag determines the direction to draw the arc in.
577
- #
578
- ##
579
- def arcTo(dx, dy, rx, ry, axis_rotation, large_arc_flag, sweep_flag)
580
- add_d("a#{rx},#{ry} #{axis_rotation} #{large_arc_flag},#{sweep_flag} #{dx},#{dy}")
581
- end
582
-
583
-
584
- def arcToA(dx, dy, rx, ry, axis_rotation, large_arc_flag, sweep_flag)
585
- add_d("A#{rx},#{ry} #{axis_rotation} #{large_arc_flag},#{sweep_flag} #{dx},#{dy}")
586
- end
587
-
588
-
589
- ##
590
- # close path command
591
- #
592
- # Closes the path by drawing a line from current point to first point.
593
- #
594
- ##
595
- def close
596
- add_d("Z")
597
- end
598
-
599
-
600
- private
601
- def add_d(op)
602
- @attributes[:d] = @attributes[:d] + " " + op
603
- end
604
- end
605
-
606
- #SVG base gradient element, with ruby methods to describe the gradient
607
- class SVGPlot::SVGGradient < SVGPlot::SVGTagWithParent
608
-
609
- def fill
610
- "url(##{@attributes[:id]})"
611
- end
612
-
613
-
614
- def stop(offset, color, opacity)
615
- append_child(SVGPlot::SVGTag.new("stop", "offset" => offset, "stop-color" => color, "stop-opacity" => opacity))
616
- end
617
-
618
- end
619
-
620
- #SVG linear gradient element
621
- class SVGPlot::SVGLinearGradient < SVGPlot::SVGGradient
622
-
623
- def initialize(img, attributes={}, &block)
624
- super(img, "linearGradient", attributes, &block)
625
- end
626
-
627
- end
628
-
629
- #SVG radial gradient element
630
- class SVGPlot::SVGRadialGradient < SVGPlot::SVGGradient
631
-
632
- def initialize(img, attributes={}, &block)
633
- super(img, "radialGradient", attributes, &block)
634
- end
635
-
636
- end
@@ -0,0 +1,117 @@
1
+ module SVGPlot
2
+ ##
3
+ # Tag object, represents a single SVG tag
4
+ class Tag
5
+ include Parsers::Tag
6
+ include Transform
7
+ include Defaults
8
+ include Expansion
9
+
10
+ attr_reader :tag, :attributes, :children
11
+
12
+ def initialize(tag, attributes = {}, &block)
13
+ @tag = parse_tag tag
14
+ @attributes = parse_attributes attributes
15
+ @children = []
16
+
17
+ instance_exec(&block) if block
18
+ end
19
+
20
+ def raw(data)
21
+ append_child Raw.new(@img, data)
22
+ end
23
+
24
+ def path(attributes = {}, &block)
25
+ append_child Path.new(@img, attributes, &block)
26
+ end
27
+
28
+ def use(id, attributes = {})
29
+ id = id.attributes[:id] if id.is_a? Tag
30
+ attributes.merge!('xlink:href' => "##{id}")
31
+ append_child ChildTag.new(@img, 'use', attributes)
32
+ end
33
+
34
+ def append_child(child)
35
+ @children.push(child)
36
+ child.defaults = defaults
37
+ child
38
+ end
39
+
40
+ def to_s
41
+ str = ''
42
+ write(str)
43
+ str
44
+ end
45
+
46
+ def write(output)
47
+ output << "<#{@tag}"
48
+ @attributes.each { |attr, value| output << %( #{attr}="#{value}") }
49
+ if @children.empty?
50
+ output << '/>'
51
+ else
52
+ output << '>'
53
+ @children.each { |c| c.write(output) }
54
+ output << "</#{@tag}>"
55
+ end
56
+ end
57
+
58
+ def spawn_child(tag, *args, &block)
59
+ parameters = args.first.is_a?(Hash) ? args.unshift : {}
60
+ if parameters.empty?
61
+ parameters = args.last.is_a?(Hash) ? args.pop : {}
62
+ parameters.merge! expand(tag, args)
63
+ end
64
+ parameters = defaults.dup.merge! parameters
65
+
66
+ append_child ChildTag.new(@img, tag, parameters, &block)
67
+ end
68
+
69
+ def respond_to?(method)
70
+ return true if parse_method_name(method) || parse_child_name(meth)
71
+ super
72
+ end
73
+
74
+ def method_missing(method, *args, &block)
75
+ check = parse_method_name(method)
76
+ if check
77
+ return parse_method_op(check[:op], check[:name], args, &block)
78
+ else
79
+ child_name = parse_child_name(method)
80
+ return spawn_child(child_name, *args, &block) if child_name
81
+ end
82
+ super
83
+ end
84
+ end
85
+
86
+ ##
87
+ # Child tag, used for tags within another tag
88
+ class ChildTag < Tag
89
+ attr_reader :img
90
+
91
+ def initialize(img, tag, params = {}, &block)
92
+ @img = img
93
+ super(tag, params, &block)
94
+ end
95
+
96
+ def linear_gradient(id, attributes = {}, if_exists = :skip, &block)
97
+ @img.add_def(id, LinearGradient.new(@img, attributes), if_exists, &block)
98
+ end
99
+
100
+ def radial_gradient(id, attributes = {}, if_exists = :skip, &block)
101
+ @img.add_def(id, RadialGradient.new(@img, attributes), if_exists, &block)
102
+ end
103
+ end
104
+
105
+ ##
106
+ # Raw child, just prints data provided
107
+ class Raw < ChildTag
108
+ def initialize(img, data)
109
+ @img = img
110
+ @data = data
111
+ end
112
+
113
+ def write(output)
114
+ output << @data.to_s
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,45 @@
1
+ module SVGPlot
2
+ ##
3
+ # Provide methods for SVG transformations
4
+ module Transform
5
+ def translate(tx, ty = 0)
6
+ add_transform(:translate, "#{tx}, #{ty}")
7
+ self
8
+ end
9
+
10
+ def scale(sx, sy = 1)
11
+ add_transform(:scale, "#{sx}, #{sy}")
12
+ self
13
+ end
14
+
15
+ def rotate(angle, cx = nil, cy = nil)
16
+ string = [cx, cy].any?(&:nil?) ? "#{angle}" : "#{angle}, #{cx}, #{cy}"
17
+ add_transform(:rotate, string)
18
+ self
19
+ end
20
+
21
+ def skew_x(angle)
22
+ add_transform(:skewX, "#{angle}")
23
+ self
24
+ end
25
+
26
+ def skew_y(angle)
27
+ add_transform(:skewY, "#{angle}")
28
+ self
29
+ end
30
+
31
+ def matrix(*args)
32
+ fail('matrix takes 6 args') unless args.size == 6
33
+ add_transform(:matrix, args.join(', '))
34
+ self
35
+ end
36
+
37
+ private
38
+
39
+ def add_transform(type, params)
40
+ validate_attribute(:transform)
41
+ @attributes[:transform] ||= ''
42
+ @attributes[:transform] << "#{type}(#{params})"
43
+ end
44
+ end
45
+ end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'svgplot'
3
- s.version = '0.1.1'
3
+ s.version = '0.2.0'
4
4
  s.date = Time.now.strftime("%Y-%m-%d")
5
5
 
6
6
  s.summary = 'SVG Generation Library'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: svgplot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Les Aker
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2015-01-05 00:00:00.000000000 Z
14
+ date: 2015-01-10 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: rubocop
@@ -116,9 +116,15 @@ files:
116
116
  - dev/update_spec.rb
117
117
  - lib/svgplot.rb
118
118
  - lib/svgplot/application.rb
119
+ - lib/svgplot/defaults.rb
120
+ - lib/svgplot/gradient.rb
119
121
  - lib/svgplot/meta.rb
122
+ - lib/svgplot/parsers.rb
123
+ - lib/svgplot/path.rb
120
124
  - lib/svgplot/plot.rb
121
125
  - lib/svgplot/spec.rb
126
+ - lib/svgplot/tag.rb
127
+ - lib/svgplot/transform.rb
122
128
  - spec/spec_helper.rb
123
129
  - spec/svgplot_spec.rb
124
130
  - svgplot.gemspec