svgplot 0.1.1 → 0.2.0

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