prawn-svg 0.15.0.0 → 0.19.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.
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