prawn-svg 0.15.0.0 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -12
  3. data/lib/prawn/svg/calculators/aspect_ratio.rb +58 -0
  4. data/lib/prawn/svg/calculators/document_sizing.rb +75 -0
  5. data/lib/prawn/svg/calculators/pixels.rb +21 -0
  6. data/lib/prawn/svg/document.rb +16 -43
  7. data/lib/prawn/svg/element.rb +71 -18
  8. data/lib/prawn/svg/extension.rb +3 -3
  9. data/lib/prawn/svg/interface.rb +80 -19
  10. data/lib/prawn/svg/parser/image.rb +9 -62
  11. data/lib/prawn/svg/parser/path.rb +19 -1
  12. data/lib/prawn/svg/parser/text.rb +2 -0
  13. data/lib/prawn/svg/parser.rb +53 -18
  14. data/lib/prawn/svg/url_loader.rb +34 -0
  15. data/lib/prawn/svg/version.rb +1 -1
  16. data/lib/prawn-svg.rb +4 -0
  17. data/prawn-svg.gemspec +7 -4
  18. data/spec/integration_spec.rb +83 -0
  19. data/spec/prawn/svg/calculators/aspect_ratio_spec.rb +95 -0
  20. data/spec/prawn/svg/calculators/document_sizing_spec.rb +73 -0
  21. data/spec/prawn/svg/document_spec.rb +21 -17
  22. data/spec/prawn/svg/element_spec.rb +1 -1
  23. data/spec/prawn/svg/interface_spec.rb +59 -0
  24. data/spec/prawn/svg/parser/path_spec.rb +89 -0
  25. data/spec/prawn/svg/url_loader_spec.rb +46 -0
  26. data/spec/sample_images/mushroom-long.jpg +0 -0
  27. data/spec/sample_images/mushroom-wide.jpg +0 -0
  28. data/spec/sample_svg/cap_styles.svg +13 -0
  29. data/spec/sample_svg/display_none.svg +13 -0
  30. data/spec/sample_svg/gistfile1.svg +36 -0
  31. data/spec/sample_svg/hidden_paths.svg +6 -0
  32. data/spec/sample_svg/image01.svg +31 -31
  33. data/spec/sample_svg/negminy.svg +25 -0
  34. data/spec/sample_svg/path.svg +5 -0
  35. data/spec/sample_svg/pie_piece.svg +7 -0
  36. data/spec/sample_svg/viewbox.svg +4 -0
  37. data/spec/sample_svg/viewport.svg +23 -0
  38. data/spec/spec_helper.rb +7 -0
  39. metadata +81 -25
  40. data/spec/lib/path_spec.rb +0 -54
  41. data/spec/lib/svg_spec.rb +0 -47
  42. /data/spec/{lib → prawn/svg}/parser_spec.rb +0 -0
@@ -5,6 +5,8 @@
5
5
  module Prawn
6
6
  module Svg
7
7
  class Interface
8
+ VALID_OPTIONS = [:at, :position, :vposition, :width, :height, :cache_images, :fallback_font_name]
9
+
8
10
  DEFAULT_FONT_PATHS = ["/Library/Fonts", "/System/Library/Fonts", "#{ENV["HOME"]}/Library/Fonts", "/usr/share/fonts/truetype"]
9
11
 
10
12
  @font_path = []
@@ -19,51 +21,99 @@ module Prawn
19
21
  #
20
22
  # +data+ is the SVG data to convert. +prawn+ is your Prawn::Document object.
21
23
  #
22
- # +options+ must contain the key :at, which takes a tuple of x and y co-ordinates.
24
+ # Options:
25
+ # <tt>:at</tt>:: an array [x,y] specifying the location of the top-left corner of the SVG.
26
+ # <tt>:position</tt>:: one of (nil, :left, :center, :right) or an x-offset
27
+ # <tt>:vposition</tt>:: one of (nil, :top, :center, :bottom) or a y-offset
28
+ # <tt>:width</tt>:: the width that the SVG is to be rendered
29
+ # <tt>:height</tt>:: the height that the SVG is to be rendered
30
+ #
31
+ # If <tt>:at</tt> is provided, the SVG will be placed in the current page but
32
+ # the text position will not be changed.
23
33
  #
24
- # +options+ can optionally contain the key :width or :height. If both are
25
- # specified, only :width will be used.
34
+ # If both <tt>:width</tt> and <tt>:height</tt> are specified, only the width will be used.
26
35
  #
27
- def initialize(data, prawn, options)
36
+ def initialize(data, prawn, options, &block)
37
+ Prawn.verify_options VALID_OPTIONS, options
38
+
28
39
  @data = data
29
40
  @prawn = prawn
30
41
  @options = options
31
42
 
32
- @options[:at] or raise "options[:at] must be specified"
33
-
34
43
  Prawn::Svg::Font.load_external_fonts(prawn.font_families)
35
44
 
36
- @document = Document.new(data, [prawn.bounds.width, prawn.bounds.height], options)
45
+ @document = Document.new(data, [prawn.bounds.width, prawn.bounds.height], options, &block)
37
46
  end
38
47
 
39
48
  #
40
49
  # Draws the SVG to the Prawn::Document object.
41
50
  #
42
51
  def draw
43
- prawn.bounding_box(@options[:at], :width => @document.width, :height => @document.height) do
52
+ prawn.bounding_box(position, :width => @document.sizing.output_width, :height => @document.sizing.output_height) do
44
53
  prawn.save_graphics_state do
45
- clip_rectangle 0, 0, @document.width, @document.height
54
+ clip_rectangle 0, 0, @document.sizing.output_width, @document.sizing.output_height
46
55
  proc_creator(prawn, Parser.new(@document).parse).call
47
56
  end
48
57
  end
49
58
  end
50
59
 
60
+ def position
61
+ @options[:at] || [x_based_on_requested_alignment, y_based_on_requested_alignment]
62
+ end
51
63
 
52
64
  private
65
+
66
+ def x_based_on_requested_alignment
67
+ case options[:position]
68
+ when :left, nil
69
+ 0
70
+ when :center, :centre
71
+ (@document.sizing.bounds[0] - @document.sizing.output_width) / 2.0
72
+ when :right
73
+ @document.sizing.bounds[0] - @document.sizing.output_width
74
+ when Numeric
75
+ options[:position]
76
+ else
77
+ raise ArgumentError, "options[:position] must be one of nil, :left, :right, :center or a number"
78
+ end
79
+ end
80
+
81
+ def y_based_on_requested_alignment
82
+ case options[:vposition]
83
+ when nil
84
+ prawn.cursor
85
+ when :top
86
+ @document.sizing.bounds[1]
87
+ when :center, :centre
88
+ @document.sizing.bounds[1] - (@document.sizing.bounds[1] - @document.sizing.output_height) / 2.0
89
+ when :bottom
90
+ @document.sizing.output_height
91
+ when Numeric
92
+ @document.sizing.bounds[1] - options[:vposition]
93
+ else
94
+ raise ArgumentError, "options[:vposition] must be one of nil, :top, :right, :bottom or a number"
95
+ end
96
+ end
97
+
53
98
  def proc_creator(prawn, calls)
54
99
  Proc.new {issue_prawn_command(prawn, calls)}
55
100
  end
56
101
 
57
102
  def issue_prawn_command(prawn, calls)
58
103
  calls.each do |call, arguments, children|
59
- if rewrite_call_arguments(prawn, call, arguments) == false
104
+ skip = false
105
+
106
+ rewrite_call_arguments(prawn, call, arguments) do
60
107
  issue_prawn_command(prawn, children) if children.any?
108
+ skip = true
109
+ end
110
+
111
+ if skip
112
+ # the call has been overridden
113
+ elsif children.empty?
114
+ prawn.send(call, *arguments)
61
115
  else
62
- if children.empty?
63
- prawn.send(call, *arguments)
64
- else
65
- prawn.send(call, *arguments, &proc_creator(prawn, children))
66
- end
116
+ prawn.send(call, *arguments, &proc_creator(prawn, children))
67
117
  end
68
118
  end
69
119
  end
@@ -77,7 +127,7 @@ module Prawn
77
127
  case call
78
128
  when 'text_group'
79
129
  @relative_text_position = nil
80
- false
130
+ yield
81
131
 
82
132
  when 'draw_text'
83
133
  text, options = arguments
@@ -100,15 +150,26 @@ module Prawn
100
150
 
101
151
  when 'clip'
102
152
  prawn.add_content "W n" # clip to path
103
- false
153
+ yield
104
154
 
105
155
  when 'save'
106
156
  prawn.save_graphics_state
107
- false
157
+ yield
108
158
 
109
159
  when 'restore'
110
160
  prawn.restore_graphics_state
111
- false
161
+ yield
162
+
163
+ when "end_path"
164
+ yield
165
+ prawn.add_content "n" # end path
166
+
167
+ when 'fill_and_stroke'
168
+ yield
169
+ # prawn (as at 2.0.1 anyway) uses 'b' for its fill_and_stroke. 'b' is 'h' (closepath) + 'B', and we
170
+ # never want closepath to be automatically run as it stuffs up many drawing operations, such as dashes
171
+ # and line caps, and makes paths close that we didn't ask to be closed when fill is specified.
172
+ prawn.add_content 'B'
112
173
  end
113
174
  end
114
175
 
@@ -1,12 +1,6 @@
1
- require 'open-uri'
2
- require 'base64'
3
-
4
1
  class Prawn::Svg::Parser::Image
5
2
  Error = Class.new(StandardError)
6
3
 
7
- DATAURL_REGEXP = /(data:image\/(png|jpg);base64(;[a-z0-9]+)*,)/
8
- URL_REGEXP = /^https?:\/\/|#{DATAURL_REGEXP}/
9
-
10
4
  class FakeIO
11
5
  def initialize(data)
12
6
  @data = data
@@ -24,18 +18,20 @@ class Prawn::Svg::Parser::Image
24
18
  end
25
19
 
26
20
  def parse(element)
21
+ return if element.state[:display] == "none"
22
+
27
23
  attrs = element.attributes
28
24
  url = attrs['xlink:href'] || attrs['href']
29
25
  if url.nil?
30
26
  raise Error, "image tag must have an xlink:href"
31
27
  end
32
28
 
33
- if !url.match(URL_REGEXP)
29
+ if !@document.url_loader.valid?(url)
34
30
  raise Error, "image tag xlink:href attribute must use http, https or data scheme"
35
31
  end
36
32
 
37
33
  image = begin
38
- retrieve_data_from_url(url)
34
+ @document.url_loader.load(url)
39
35
  rescue => e
40
36
  raise Error, "Error retrieving URL #{url}: #{e.message}"
41
37
  end
@@ -48,50 +44,18 @@ class Prawn::Svg::Parser::Image
48
44
  return if width.zero? || height.zero?
49
45
  raise Error, "width and height must be 0 or higher" if width < 0 || height < 0
50
46
 
51
- par = (attrs['preserveAspectRatio'] || "xMidYMid meet").strip.split(/\s+/)
52
- par.shift if par.first == "defer"
53
- align, meet_or_slice = par
54
- slice = meet_or_slice == "slice"
47
+ aspect = Prawn::Svg::Calculators::AspectRatio.new(attrs['preserveAspectRatio'], [width, height], image_dimensions(image))
55
48
 
56
- if slice
49
+ if aspect.slice?
57
50
  element.add_call "save"
58
51
  element.add_call "rectangle", [x, y], width, height
59
52
  element.add_call "clip"
60
53
  end
61
54
 
62
- options = {}
63
- case align
64
- when /\Ax(Min|Mid|Max)Y(Min|Mid|Max)\z/
65
- ratio = image_ratio(image)
66
-
67
- options[:fit] = [width, height] unless slice
68
-
69
- if (width/height > ratio) == slice
70
- options[:width] = width if slice
71
- y -= case $2
72
- when "Min" then 0
73
- when "Mid" then (height - width/ratio)/2
74
- when "Max" then height - width/ratio
75
- end
76
- else
77
- options[:height] = height if slice
78
- x += case $1
79
- when "Min" then 0
80
- when "Mid" then (width - height*ratio)/2
81
- when "Max" then width - height*ratio
82
- end
83
- end
84
- when 'none'
85
- options[:width] = width
86
- options[:height] = height
87
- else
88
- raise Error, "unknown preserveAspectRatio align keyword; ignoring image"
89
- end
90
-
91
- options[:at] = [x, y]
55
+ options = {:width => aspect.width, :height => aspect.height, :at => [x + aspect.x, y - aspect.y]}
92
56
 
93
57
  element.add_call "image", FakeIO.new(image), options
94
- element.add_call "restore" if slice
58
+ element.add_call "restore" if aspect.slice?
95
59
  rescue Error => e
96
60
  @document.warnings << e.message
97
61
  end
@@ -108,24 +72,7 @@ class Prawn::Svg::Parser::Image
108
72
  end
109
73
 
110
74
  image = handler.new(data)
111
- [image.width, image.height]
112
- end
113
-
114
- def image_ratio(data)
115
- w, h = image_dimensions(data)
116
- w.to_f / h.to_f
117
- end
118
-
119
- def retrieve_data_from_url(url)
120
- @url_cache[url] || begin
121
- if m = url.match(DATAURL_REGEXP)
122
- data = Base64.decode64(url[m[0].length .. -1])
123
- else
124
- data = open(url).read
125
- end
126
- @url_cache[url] = data if @document.cache_images
127
- data
128
- end
75
+ [image.width.to_f, image.height.to_f]
129
76
  end
130
77
 
131
78
  %w(x y distance).each do |method|
@@ -10,6 +10,7 @@ module Prawn
10
10
  VALUES_REGEXP = /^#{INSIDE_REGEXP}/
11
11
  COMMAND_REGEXP = /^#{OUTSIDE_SPACE_REGEXP}([A-Za-z])((?:#{INSIDE_REGEXP})*)#{OUTSIDE_SPACE_REGEXP}/
12
12
 
13
+ FLOAT_ERROR_DELTA = 1e-10
13
14
 
14
15
  #
15
16
  # Parses an SVG path and returns a Prawn-compatible call tree.
@@ -173,15 +174,28 @@ module Prawn
173
174
  rx, ry, phi, fa, fs, x2, y2 = (1..7).collect {values.shift}
174
175
  x1, y1 = @last_point
175
176
 
177
+ return if rx.zero? && ry.zero?
178
+
176
179
  if relative
177
180
  x2 += x1
178
181
  y2 += y1
179
182
  end
180
183
 
184
+ # Normalise values as per F.6.2
181
185
  rx = rx.abs
182
186
  ry = ry.abs
183
187
  phi = (phi % 360) * 2 * Math::PI / 360.0
184
188
 
189
+ # F.6.2: If the endpoints (x1, y1) and (x2, y2) are identical, then this is equivalent to omitting the elliptical arc segment entirely.
190
+ return if within_float_delta?(x1, x2) && within_float_delta?(y1, y2)
191
+
192
+ # F.6.2: If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto") joining the endpoints.
193
+ if within_float_delta?(rx, 0) || within_float_delta?(ry, 0)
194
+ @last_point = [x2, y2]
195
+ @calls << ["line_to", @last_point]
196
+ return
197
+ end
198
+
185
199
  # We need to get the center co-ordinates, as well as the angles from the X axis to the start and end
186
200
  # points. To do this, we use the algorithm documented in the SVG specification section F.6.5.
187
201
 
@@ -202,7 +216,7 @@ module Prawn
202
216
  r2x = rx * rx
203
217
  r2y = ry * ry
204
218
  square = (r2x * r2y - r2x * yp1 * yp1 - r2y * xp1 * xp1) / (r2x * yp1 * yp1 + r2y * xp1 * xp1)
205
- square = 0 if square < 0 && square > -1e-10 # catch rounding errors
219
+ square = 0 if square < 0 && square > -FLOAT_ERROR_DELTA # catch rounding errors
206
220
  base = Math.sqrt(square)
207
221
  base *= -1 if fa == fs
208
222
  cpx = base * rx * yp1 / ry
@@ -252,6 +266,10 @@ module Prawn
252
266
  @previous_quadratic_control_point = nil unless %w(Q T).include?(upcase_command)
253
267
  end
254
268
 
269
+ def within_float_delta?(a, b)
270
+ (a - b).abs < FLOAT_ERROR_DELTA
271
+ end
272
+
255
273
  def match_all(string, regexp) # regexp must start with ^
256
274
  result = []
257
275
  while string != ""
@@ -6,6 +6,8 @@ class Prawn::Svg::Parser::Text
6
6
 
7
7
  protected
8
8
  def internal_parse(element, x_positions, y_positions, relative)
9
+ return if element.state[:display] == "none"
10
+
9
11
  attrs = element.attributes
10
12
 
11
13
  if attrs['x'] || attrs['y']
@@ -13,6 +13,7 @@ require 'rexml/document'
13
13
  #
14
14
  class Prawn::Svg::Parser
15
15
  CONTAINER_TAGS = %w(g svg symbol defs clipPath)
16
+ COMMA_WSP_REGEXP = /(?:\s+,?\s*|,\s*)/
16
17
 
17
18
  #
18
19
  # Construct a Parser object.
@@ -42,6 +43,19 @@ class Prawn::Svg::Parser
42
43
  @document.warnings.clear
43
44
 
44
45
  calls = [['fill_color', '000000', []]]
46
+
47
+ calls << [
48
+ 'transformation_matrix',
49
+ [@document.sizing.x_scale, 0, 0, @document.sizing.y_scale, 0, 0],
50
+ []
51
+ ]
52
+
53
+ calls << [
54
+ 'transformation_matrix',
55
+ [1, 0, 0, 1, @document.sizing.x_offset, @document.sizing.y_offset],
56
+ []
57
+ ]
58
+
45
59
  root_element = Prawn::Svg::Element.new(@document, @document.root, calls, :ids => {}, :fill => true)
46
60
 
47
61
  parse_element(root_element)
@@ -92,44 +106,51 @@ class Prawn::Svg::Parser
92
106
  element.add_call 'line', x(attrs['x1'] || '0'), y(attrs['y1'] || '0'), x(attrs['x2'] || '0'), y(attrs['y2'] || '0')
93
107
 
94
108
  when 'polyline'
95
- points = attrs['points'].split(/\s+/)
96
- return unless base_point = points.shift
97
- x, y = base_point.split(",")
109
+ points = parse_points(attrs['points'])
110
+ return unless points.length > 0
111
+ x, y = points.shift
98
112
  element.add_call 'move_to', x(x), y(y)
99
113
  element.add_call_and_enter 'stroke'
100
- points.each do |point|
101
- x, y = point.split(",")
114
+ points.each do |x, y|
102
115
  element.add_call "line_to", x(x), y(y)
103
116
  end
104
117
 
105
118
  when 'polygon'
106
- points = attrs['points'].split(/\s+/).collect do |point|
107
- x, y = point.split(",")
119
+ points = parse_points(attrs['points']).collect do |x, y|
108
120
  [x(x), y(y)]
109
121
  end
110
122
  element.add_call "polygon", *points
111
123
 
112
124
  when 'circle'
125
+ xy, r = [x(attrs['cx'] || "0"), y(attrs['cy'] || "0")], distance(attrs['r'])
126
+
127
+ return if zero_argument?(r)
128
+
113
129
  if USE_NEW_CIRCLE_CALL
114
- element.add_call "circle",
115
- [x(attrs['cx'] || "0"), y(attrs['cy'] || "0")], distance(attrs['r'])
130
+ element.add_call "circle", xy, r
116
131
  else
117
- element.add_call "circle_at",
118
- [x(attrs['cx'] || "0"), y(attrs['cy'] || "0")], :radius => distance(attrs['r'])
132
+ element.add_call "circle_at", xy, :radius => r
119
133
  end
120
134
 
121
135
  when 'ellipse'
122
- element.add_call USE_NEW_ELLIPSE_CALL ? "ellipse" : "ellipse_at",
123
- [x(attrs['cx'] || "0"), y(attrs['cy'] || "0")], distance(attrs['rx']), distance(attrs['ry'])
136
+ xy, rx, ry = [x(attrs['cx'] || "0"), y(attrs['cy'] || "0")], distance(attrs['rx'], :x), distance(attrs['ry'], :y)
137
+
138
+ return if zero_argument?(rx, ry)
139
+
140
+ element.add_call USE_NEW_ELLIPSE_CALL ? "ellipse" : "ellipse_at", xy, rx, ry
124
141
 
125
142
  when 'rect'
126
- radius = distance(attrs['rx'] || attrs['ry'])
127
- args = [[x(attrs['x'] || '0'), y(attrs['y'] || '0')], distance(attrs['width']), distance(attrs['height'])]
143
+ xy = [x(attrs['x'] || '0'), y(attrs['y'] || '0')]
144
+ width, height = distance(attrs['width'], :x), distance(attrs['height'], :y)
145
+ radius = distance(attrs['rx'] || attrs['ry'])
146
+
147
+ return if zero_argument?(width, height)
148
+
128
149
  if radius
129
150
  # n.b. does not support both rx and ry being specified with different values
130
- element.add_call "rounded_rectangle", *(args + [radius])
151
+ element.add_call "rounded_rectangle", xy, width, height, radius
131
152
  else
132
- element.add_call "rectangle", *args
153
+ element.add_call "rectangle", xy, width, height
133
154
  end
134
155
 
135
156
  when 'path'
@@ -191,7 +212,7 @@ class Prawn::Svg::Parser
191
212
  x = element.attributes['x']
192
213
  y = element.attributes['y']
193
214
  if x || y
194
- element.add_call_and_enter "translate", distance(x || 0), -distance(y || 0)
215
+ element.add_call_and_enter "translate", distance(x || 0, :x), -distance(y || 0, :y)
195
216
  end
196
217
  element.add_calls_from_element definition_element
197
218
  else
@@ -227,7 +248,21 @@ class Prawn::Svg::Parser
227
248
  missing_attrs.empty?
228
249
  end
229
250
 
251
+ def zero_argument?(*args)
252
+ args.any? {|arg| arg.nil? || arg <= 0}
253
+ end
254
+
230
255
  %w(x y distance).each do |method|
231
256
  define_method(method) {|*a| @document.send(method, *a)}
232
257
  end
258
+
259
+ def parse_points(points_string)
260
+ points_string.
261
+ to_s.
262
+ strip.
263
+ gsub(/(\d)-(\d)/, '\1 -\2').
264
+ split(COMMA_WSP_REGEXP).
265
+ each_slice(2).
266
+ to_a
267
+ end
233
268
  end
@@ -0,0 +1,34 @@
1
+ require 'open-uri'
2
+ require 'base64'
3
+
4
+ class Prawn::Svg::UrlLoader
5
+ attr_accessor :enable_cache, :enable_web
6
+ attr_reader :url_cache
7
+
8
+ DATAURL_REGEXP = /(data:image\/(png|jpg);base64(;[a-z0-9]+)*,)/
9
+ URL_REGEXP = /^https?:\/\/|#{DATAURL_REGEXP}/
10
+
11
+ def initialize(opts = {})
12
+ @url_cache = {}
13
+ @enable_cache = opts.fetch(:enable_cache, false)
14
+ @enable_web = opts.fetch(:enable_web, true)
15
+ end
16
+
17
+ def valid?(url)
18
+ !!url.match(URL_REGEXP)
19
+ end
20
+
21
+ def load(url)
22
+ @url_cache[url] || begin
23
+ if m = url.match(DATAURL_REGEXP)
24
+ data = Base64.decode64(url[m[0].length .. -1])
25
+ elsif enable_web
26
+ data = open(url).read
27
+ else
28
+ raise "No handler available to retrieve URL #{url}"
29
+ end
30
+ @url_cache[url] = data if enable_cache
31
+ data
32
+ end
33
+ end
34
+ end
@@ -1,5 +1,5 @@
1
1
  module Prawn
2
2
  module Svg
3
- VERSION = '0.15.0.0'
3
+ VERSION = '0.19.0'
4
4
  end
5
5
  end
data/lib/prawn-svg.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  require 'prawn'
2
2
  require 'prawn/svg/version'
3
3
 
4
+ require 'prawn/svg/calculators/aspect_ratio'
5
+ require 'prawn/svg/calculators/document_sizing'
6
+ require 'prawn/svg/calculators/pixels'
7
+ require 'prawn/svg/url_loader'
4
8
  require 'prawn/svg/color'
5
9
  require 'prawn/svg/extension'
6
10
  require 'prawn/svg/interface'
data/prawn-svg.gemspec CHANGED
@@ -5,7 +5,7 @@ spec = Gem::Specification.new do |gem|
5
5
  gem.name = 'prawn-svg'
6
6
  gem.version = Prawn::Svg::VERSION
7
7
  gem.summary = "SVG renderer for Prawn PDF library"
8
- gem.description = "SVG renderer for Prawn PDF library"
8
+ gem.description = "This gem allows you to render SVG directly into a PDF using the 'prawn' gem. Since PDF is vector-based, you'll get nice scaled graphics if you use SVG instead of an image."
9
9
  gem.has_rdoc = false
10
10
  gem.author = "Roger Nesbitt"
11
11
  gem.email = "roger@seriousorange.com"
@@ -18,7 +18,10 @@ spec = Gem::Specification.new do |gem|
18
18
  gem.name = "prawn-svg"
19
19
  gem.require_paths = ["lib"]
20
20
 
21
- gem.add_dependency "prawn", ">= 0.8.4"
22
- gem.add_development_dependency "rspec", "~> 2.14.1"
23
- gem.add_development_dependency "rake"
21
+ gem.required_ruby_version = '>= 1.9.3'
22
+
23
+ gem.add_runtime_dependency "prawn", ">= 0.8.4", "< 3"
24
+ gem.add_runtime_dependency "css_parser", "~> 1.3"
25
+ gem.add_development_dependency "rspec", "~> 3.0"
26
+ gem.add_development_dependency "rake", "~> 10.1"
24
27
  end
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+
3
+ describe Prawn::Svg::Interface do
4
+ root = "#{File.dirname(__FILE__)}/.."
5
+
6
+ context "with option :position" do
7
+ let(:svg) { IO.read("#{root}/spec/sample_svg/cubic01a.svg") }
8
+
9
+ it "aligns the image as requested" do
10
+ Prawn::Document.generate("#{root}/spec/sample_output/_with_position.pdf") do |prawn|
11
+ width = prawn.bounds.width / 3
12
+
13
+ prawn.svg svg, :width => width, :position => :left
14
+ prawn.svg svg, :width => width, :position => :center
15
+ prawn.svg svg, :width => width, :position => :right
16
+ prawn.svg svg, :width => width, :position => 50
17
+ prawn.svg svg, :width => width
18
+ end
19
+ end
20
+ end
21
+
22
+ context "with option :vposition" do
23
+ let(:svg) { IO.read("#{root}/spec/sample_svg/cubic01a.svg") }
24
+
25
+ it "aligns the image as requested" do
26
+ Prawn::Document.generate("#{root}/spec/sample_output/_with_vposition.pdf") do |prawn|
27
+ width = prawn.bounds.width / 3
28
+
29
+ prawn.svg svg, :width => width, :position => :left, :vposition => :bottom
30
+ prawn.svg svg, :width => width, :position => :center, :vposition => :center
31
+ prawn.svg svg, :width => width, :position => :right, :vposition => :top
32
+ prawn.svg svg, :width => width, :position => 50, :vposition => 50
33
+ end
34
+ end
35
+ end
36
+
37
+ describe "sample file rendering" do
38
+ files = Dir["#{root}/spec/sample_svg/*.svg"]
39
+
40
+ it "has at least 10 SVG sample files to test" do
41
+ files.length.should >= 10
42
+ end
43
+
44
+ files.each do |file|
45
+ it "renders the #{File.basename file} sample file without warnings or crashing" do
46
+ warnings = nil
47
+ Prawn::Document.generate("#{root}/spec/sample_output/#{File.basename file}.pdf") do |prawn|
48
+ r = prawn.svg IO.read(file), :at => [0, prawn.bounds.top], :width => prawn.bounds.width do |doc|
49
+ doc.url_loader.enable_web = false
50
+ doc.url_loader.url_cache["https://raw.githubusercontent.com/mogest/prawn-svg/master/spec/sample_images/mushroom-wide.jpg"] = IO.read("#{root}/spec/sample_images/mushroom-wide.jpg")
51
+ doc.url_loader.url_cache["https://raw.githubusercontent.com/mogest/prawn-svg/master/spec/sample_images/mushroom-long.jpg"] = IO.read("#{root}/spec/sample_images/mushroom-long.jpg")
52
+ end
53
+
54
+ warnings = r[:warnings].reject {|w| w =~ /Verdana/ && w =~ /is not a known font/ }
55
+ end
56
+ warnings.should == []
57
+ end
58
+ end
59
+ end
60
+
61
+ describe "multiple file rendering" do
62
+ it "renders multiple files on to the same PDF" do
63
+ Prawn::Document.generate("#{root}/spec/sample_output/_multiple.pdf") do |prawn|
64
+ width = prawn.bounds.width
65
+
66
+ y = prawn.bounds.top - 12
67
+ prawn.draw_text "This is multiple SVGs being output to the same PDF", :at => [0, y]
68
+
69
+ y -= 12
70
+ prawn.svg IO.read("#{root}/spec/sample_svg/arcs01.svg"), :at => [0, y], :width => width / 2
71
+ prawn.svg IO.read("#{root}/spec/sample_svg/circle01.svg"), :at => [width / 2, y], :width => width / 2
72
+
73
+ y -= 120
74
+ prawn.draw_text "Here are some more PDFs below", :at => [0, y]
75
+
76
+ y -= 12
77
+ prawn.svg IO.read("#{root}/spec/sample_svg/quad01.svg"), :at => [0, y], :width => width / 3
78
+ prawn.svg IO.read("#{root}/spec/sample_svg/rect01.svg"), :at => [width / 3, y], :width => width / 3
79
+ prawn.svg IO.read("#{root}/spec/sample_svg/rect02.svg"), :at => [width / 3 * 2, y], :width => width / 3
80
+ end
81
+ end
82
+ end
83
+ end