prawn-svg 0.15.0.0 → 0.16.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: 61181b453d5e28bba30c87c430975b21e5e9876d
4
- data.tar.gz: c1c1d6ec4e6bb4d60af290a836ed25574dbca760
3
+ metadata.gz: 2d7cbba75e8c3a5128d1c5136977b4b91d091407
4
+ data.tar.gz: e1996ddcb667032eae7777fea1decb58b470a600
5
5
  SHA512:
6
- metadata.gz: 766a6cb2dc207b26e433509f1dbdce22055106c05806ff327f4203dc478d6ecfe99308502f2b1964c442d8ac2ecba1df5739593144ff2328e3c5e94aad74d440
7
- data.tar.gz: 2281ec02f05246f40f4c68497d23f34da10b51ba16c60e76bd1e6d2467b0e1d80326ff76cc05aa244b794784be855f2996ceeeb6ce28465ffcecf66bee469b50
6
+ metadata.gz: 3fc7f25cb50723695bcb95485daad73b89e158662f5c0af06f10a062395d8c903b8bea7447c7dca2b4ed15c58bcbcc8115e3dc564b62ae8b6b22af62d2907f9b
7
+ data.tar.gz: 312b2b093fec372fa444c61175b843592cac404c233f54cd3ec156ea85f4615337b5e8d7bb1372819c457df9476fca1ece3fbefff41a3415ae733676e92706c6
data/README.md CHANGED
@@ -6,6 +6,8 @@ This will take an SVG file as input and render it into your PDF. Find out more
6
6
 
7
7
  http://wiki.github.com/sandal/prawn/
8
8
 
9
+ prawn-svg is compatible with all versions of Prawn from 0.8.4 onwards, including the version 1.0 series.
10
+
9
11
  ## Using prawn-svg
10
12
 
11
13
  ```ruby
@@ -16,8 +18,9 @@ end
16
18
 
17
19
  <tt>:at</tt> must be specified.
18
20
 
19
- <tt>:width</tt>, <tt>:height</tt>, or neither may be specified; if neither is present,
20
- the resolution specified in the SVG will be used.
21
+ Either <tt>:width</tt>, <tt>:height</tt>, or neither may be specified; if neither is present,
22
+ the dimensions specified in the SVG will be used, or if the dimensions aren't specified, it'll
23
+ fit to the space available on the page.
21
24
 
22
25
  <tt>:cache_images</tt>, if set to true, will cache images per document based on their URL.
23
26
 
@@ -50,11 +53,15 @@ prawn-svg does not support the full SVG specification. It currently supports:
50
53
 
51
54
  - attributes/styles: <tt>fill</tt>, <tt>stroke</tt>, <tt>stroke-width</tt>, <tt>opacity</tt>, <tt>fill-opacity</tt>, <tt>stroke-opacity</tt>, <tt>transform</tt>, <tt>clip-path</tt>
52
55
 
56
+ - the <tt>viewBox</tt> attribute on the <tt>&lt;svg&gt;</tt> tag
57
+
58
+ - the <tt>preserveAspectRatio</tt> attribute on the <tt>&lt;svg&gt;</tt> and <tt>&lt;image&gt;</tt> tags
59
+
53
60
  - transform methods: <tt>translate</tt>, <tt>rotate</tt>, <tt>scale</tt>, <tt>matrix</tt>
54
61
 
55
62
  - colors: HTML standard names, <tt>#xxx</tt>, <tt>#xxxxxx</tt>, <tt>rgb(1, 2, 3)</tt>, <tt>rgb(1%, 2%, 3%)</tt>
56
63
 
57
- - measurements specified in <tt>pt</tt>, <tt>cm</tt>, <tt>dm</tt>, <tt>ft</tt>, <tt>in</tt>, <tt>m</tt>, <tt>mm</tt>, <tt>yd</tt>, <tt>%</tt>
64
+ - measurements specified in <tt>pt</tt>, <tt>cm</tt>, <tt>dm</tt>, <tt>ft</tt>, <tt>in</tt>, <tt>m</tt>, <tt>mm</tt>, <tt>yd</tt>, <tt>pc</tt>, <tt>%</tt>
58
65
 
59
66
  - fonts: generic CSS fonts, built in PDF fonts, and any TTF fonts in your fonts path
60
67
 
@@ -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'
@@ -0,0 +1,58 @@
1
+ module Prawn::Svg::Calculators
2
+ class AspectRatio
3
+ attr_reader :align, :defer
4
+ attr_reader :width, :height, :x, :y
5
+
6
+ def initialize(value, container_dimensions, object_dimensions)
7
+ values = (value || "xMidYMid meet").strip.split(/\s+/)
8
+ @x = @y = 0
9
+
10
+ if values.first == "defer"
11
+ @defer = true
12
+ values.shift
13
+ end
14
+
15
+ @align, @meet_or_slice = values
16
+
17
+ w_container, h_container = container_dimensions
18
+ w_object, h_object = object_dimensions
19
+
20
+ container_ratio = w_container / h_container.to_f
21
+ object_ratio = w_object / h_object.to_f
22
+
23
+ if @align == "none"
24
+ @width, @height = container_dimensions
25
+ else
26
+ matches = @align.to_s.strip.match(/\Ax(Min|Mid|Max)Y(Min|Mid|Max)\z/i) || [nil, "Mid", "Mid"]
27
+
28
+ if (container_ratio > object_ratio) == slice?
29
+ @width, @height = [w_container, w_container / object_ratio]
30
+ @y = case matches[2].downcase
31
+ when "min" then 0
32
+ when "mid" then (h_container - w_container/object_ratio)/2
33
+ when "max" then h_container - w_container/object_ratio
34
+ end
35
+ else
36
+ @width, @height = [h_container * object_ratio, h_container]
37
+ @x = case matches[1].downcase
38
+ when "min" then 0
39
+ when "mid" then (w_container - h_container*object_ratio)/2
40
+ when "max" then w_container - h_container*object_ratio
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def slice?
47
+ @meet_or_slice == "slice"
48
+ end
49
+
50
+ def meet?
51
+ @meet_or_slice != "slice"
52
+ end
53
+
54
+ def inspect
55
+ "[AspectRatio: #{@width},#{@height} offset #{@x},#{@y}]"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,71 @@
1
+ module Prawn::Svg::Calculators
2
+ class DocumentSizing
3
+ attr_writer :document_width, :document_height
4
+ attr_writer :view_box, :preserve_aspect_ratio
5
+ attr_writer :requested_width, :requested_height
6
+
7
+ attr_reader :x_offset, :y_offset, :x_scale, :y_scale
8
+ attr_reader :viewport_width, :viewport_height, :output_width, :output_height
9
+
10
+ def initialize(bounds, attributes = nil)
11
+ @bounds = bounds
12
+ set_from_attributes(attributes) if attributes
13
+ end
14
+
15
+ def set_from_attributes(attributes)
16
+ @document_width = attributes['width']
17
+ @document_height = attributes['height']
18
+ @view_box = attributes['viewBox']
19
+ @preserve_aspect_ratio = attributes['preserveAspectRatio']
20
+ end
21
+
22
+ def calculate
23
+ @x_offset = @y_offset = 0
24
+ @x_scale = @y_scale = 1
25
+
26
+ width = Prawn::Svg::Calculators::Pixels.to_pixels(@document_width, @bounds[0])
27
+ height = Prawn::Svg::Calculators::Pixels.to_pixels(@document_height, @bounds[1])
28
+
29
+ default_aspect_ratio = "x#{width ? "Mid" : "Min"}Y#{height ? "Mid" : "Min"} meet"
30
+
31
+ width ||= @bounds[0]
32
+ height ||= @bounds[1]
33
+
34
+ if @view_box
35
+ values = @view_box.strip.split(/\s+/)
36
+ @x_offset, @y_offset, @viewport_width, @viewport_height = values.map {|value| value.to_f}
37
+ @x_offset = -@x_offset
38
+
39
+ @preserve_aspect_ratio ||= default_aspect_ratio
40
+ aspect = Prawn::Svg::Calculators::AspectRatio.new(@preserve_aspect_ratio, [width, height], [@viewport_width, @viewport_height])
41
+ @x_scale = aspect.width / @viewport_width
42
+ @y_scale = aspect.height / @viewport_height
43
+ @x_offset -= aspect.x
44
+ @y_offset -= aspect.y
45
+ end
46
+
47
+ @viewport_width ||= width
48
+ @viewport_height ||= height
49
+
50
+ if @requested_width
51
+ scale = @requested_width / width
52
+ width = @requested_width
53
+ height *= scale
54
+ @x_scale *= scale
55
+ @y_scale *= scale
56
+
57
+ elsif @requested_height
58
+ scale = @requested_height / height
59
+ height = @requested_height
60
+ width *= scale
61
+ @x_scale *= scale
62
+ @y_scale *= scale
63
+ end
64
+
65
+ @output_width = width
66
+ @output_height = height
67
+
68
+ self
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,21 @@
1
+ module Prawn::Svg::Calculators
2
+ class Pixels
3
+ extend Prawn::Measurements
4
+
5
+ def self.to_pixels(value, axis_length)
6
+ if value.is_a?(String)
7
+ if match = value.match(/\d(cm|dm|ft|in|m|mm|yd)$/)
8
+ send("#{match[1]}2pt", value.to_f)
9
+ elsif match = value.match(/\dpc$/)
10
+ value.to_f * 15 # according to http://www.w3.org/TR/SVG11/coords.html
11
+ elsif value[-1..-1] == "%"
12
+ value.to_f * axis_length / 100.0
13
+ else
14
+ value.to_f
15
+ end
16
+ elsif value
17
+ value.to_f
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,6 +1,4 @@
1
1
  class Prawn::Svg::Document
2
- include Prawn::Measurements
3
-
4
2
  begin
5
3
  require 'css_parser'
6
4
  CSS_PARSER_LOADED = true
@@ -8,18 +6,14 @@ class Prawn::Svg::Document
8
6
  CSS_PARSER_LOADED = false
9
7
  end
10
8
 
11
- DEFAULT_WIDTH = 640
12
- DEFAULT_HEIGHT = 480
13
9
  DEFAULT_FALLBACK_FONT_NAME = "Times-Roman"
14
10
 
15
11
  # An +Array+ of warnings that occurred while parsing the SVG data.
16
12
  attr_reader :warnings
17
-
18
- # The scaling factor, as determined by the :width or :height options.
19
- attr_accessor :scale
13
+ attr_writer :url_cache
20
14
 
21
15
  attr_reader :root,
22
- :actual_width, :actual_height, :width, :height, :x_offset, :y_offset,
16
+ :sizing,
23
17
  :cache_images, :fallback_font_name,
24
18
  :css_parser, :elements_by_id
25
19
 
@@ -32,55 +26,32 @@ class Prawn::Svg::Document
32
26
  @elements_by_id = {}
33
27
  @cache_images = options[:cache_images]
34
28
  @fallback_font_name = options.fetch(:fallback_font_name, DEFAULT_FALLBACK_FONT_NAME)
35
- @actual_width, @actual_height = bounds # set this first so % width/heights can be used
36
-
37
- if vb = @root.attributes['viewBox']
38
- x1, y1, x2, y2 = vb.strip.split(/\s+/)
39
- @x_offset, @y_offset = [x1.to_f, y1.to_f]
40
- @actual_width, @actual_height = [x2.to_f - x1.to_f, y2.to_f - y1.to_f]
41
- else
42
- @x_offset, @y_offset = [0, 0]
43
- @actual_width = points(@root.attributes['width'] || DEFAULT_WIDTH, :x)
44
- @actual_height = points(@root.attributes['height'] || DEFAULT_HEIGHT, :y)
45
- end
46
29
 
47
- if @options[:width]
48
- @width = @options[:width]
49
- @scale = @options[:width] / @actual_width.to_f
50
- elsif @options[:height]
51
- @height = @options[:height]
52
- @scale = @options[:height] / @actual_height.to_f
53
- else
54
- @scale = 1
55
- end
30
+ @sizing = Prawn::Svg::Calculators::DocumentSizing.new(bounds, @root.attributes)
31
+ sizing.requested_width = options[:width]
32
+ sizing.requested_height = options[:height]
33
+ sizing.calculate
56
34
 
57
- @width ||= @actual_width * @scale
58
- @height ||= @actual_height * @scale
35
+ yield self if block_given?
59
36
  end
60
37
 
61
38
  def x(value)
62
- (points(value, :x) - @x_offset) * scale
39
+ points(value, :x)
63
40
  end
64
41
 
65
42
  def y(value)
66
- (@actual_height - (points(value, :y) - @y_offset)) * scale
43
+ sizing.output_height - points(value, :y)
67
44
  end
68
45
 
69
46
  def distance(value, axis = nil)
70
- value && (points(value, axis) * scale)
47
+ value && points(value, axis)
71
48
  end
72
49
 
73
50
  def points(value, axis = nil)
74
- if value.is_a?(String)
75
- if match = value.match(/\d(cm|dm|ft|in|m|mm|yd)$/)
76
- send("#{match[1]}2pt", value.to_f)
77
- elsif value[-1..-1] == "%"
78
- value.to_f * (axis == :y ? @actual_height : @actual_width) / 100.0
79
- else
80
- value.to_f
81
- end
82
- else
83
- value.to_f
84
- end
51
+ Prawn::Svg::Calculators::Pixels.to_pixels(value, axis == :y ? sizing.viewport_height : sizing.viewport_width)
52
+ end
53
+
54
+ def url_loader
55
+ @url_loader ||= Prawn::Svg::UrlLoader.new(:enable_cache => cache_images)
85
56
  end
86
57
  end
@@ -57,12 +57,23 @@ class Prawn::Svg::Element
57
57
  draw_types = parse_fill_and_stroke_attributes_and_call
58
58
  parse_stroke_width_attribute_and_call
59
59
  parse_font_attributes_and_call
60
+ apply_drawing_call(draw_types)
61
+ end
60
62
 
61
- if draw_types.length > 0 && !@state[:disable_drawing] && !Prawn::Svg::Parser::CONTAINER_TAGS.include?(element.name)
62
- add_call_and_enter(draw_types.join("_and_"))
63
+ def apply_drawing_call(draw_types)
64
+ if !@state[:disable_drawing] && !container?
65
+ if draw_types.empty?
66
+ add_call_and_enter("end_path")
67
+ else
68
+ add_call_and_enter(draw_types.join("_and_"))
69
+ end
63
70
  end
64
71
  end
65
72
 
73
+ def container?
74
+ Prawn::Svg::Parser::CONTAINER_TAGS.include?(name)
75
+ end
76
+
66
77
  def parse_transform_attribute_and_call
67
78
  return unless transform = @attributes['transform']
68
79
 
@@ -129,21 +140,19 @@ class Prawn::Svg::Element
129
140
  end
130
141
 
131
142
  def parse_fill_and_stroke_attributes_and_call
132
- draw_types = []
133
- [:fill, :stroke].each do |type|
134
- dec = @attributes[type.to_s]
143
+ ["fill", "stroke"].select do |type|
144
+ dec = @attributes[type]
135
145
  if dec == "none"
136
- state[type] = false
146
+ state[type.to_sym] = false
137
147
  elsif dec
138
- state[type] = true
148
+ state[type.to_sym] = true
139
149
  if color = Prawn::Svg::Color.color_to_hex(dec)
140
150
  add_call "#{type}_color", color
141
151
  end
142
152
  end
143
153
 
144
- draw_types << type.to_s if state[type]
154
+ state[type.to_sym]
145
155
  end
146
- draw_types
147
156
  end
148
157
 
149
158
  def parse_stroke_width_attribute_and_call
@@ -152,7 +161,7 @@ class Prawn::Svg::Element
152
161
 
153
162
  def parse_font_attributes_and_call
154
163
  if size = @attributes['font-size']
155
- @state[:font_size] = size.to_f * @document.scale
164
+ @state[:font_size] = size.to_f
156
165
  end
157
166
  if weight = @attributes['font-weight']
158
167
  font_updated = true
@@ -14,10 +14,10 @@ module Prawn
14
14
  #
15
15
  # svg IO.read("example.svg"), :at => [100, 300], :width => 600
16
16
  #
17
- def svg(data, options={})
18
- svg = Prawn::Svg::Interface.new(data, self, options)
17
+ def svg(data, options = {}, &block)
18
+ svg = Prawn::Svg::Interface.new(data, self, options, &block)
19
19
  svg.draw
20
- {:warnings => svg.document.warnings, :width => svg.document.width, :height => svg.document.height}
20
+ {:warnings => svg.document.warnings, :width => svg.document.sizing.output_width, :height => svg.document.sizing.output_height}
21
21
  end
22
22
  end
23
23
  end
@@ -24,7 +24,7 @@ module Prawn
24
24
  # +options+ can optionally contain the key :width or :height. If both are
25
25
  # specified, only :width will be used.
26
26
  #
27
- def initialize(data, prawn, options)
27
+ def initialize(data, prawn, options, &block)
28
28
  @data = data
29
29
  @prawn = prawn
30
30
  @options = options
@@ -33,16 +33,16 @@ module Prawn
33
33
 
34
34
  Prawn::Svg::Font.load_external_fonts(prawn.font_families)
35
35
 
36
- @document = Document.new(data, [prawn.bounds.width, prawn.bounds.height], options)
36
+ @document = Document.new(data, [prawn.bounds.width, prawn.bounds.height], options, &block)
37
37
  end
38
38
 
39
39
  #
40
40
  # Draws the SVG to the Prawn::Document object.
41
41
  #
42
42
  def draw
43
- prawn.bounding_box(@options[:at], :width => @document.width, :height => @document.height) do
43
+ prawn.bounding_box(@options[:at], :width => @document.sizing.output_width, :height => @document.sizing.output_height) do
44
44
  prawn.save_graphics_state do
45
- clip_rectangle 0, 0, @document.width, @document.height
45
+ clip_rectangle 0, 0, @document.sizing.output_width, @document.sizing.output_height
46
46
  proc_creator(prawn, Parser.new(@document).parse).call
47
47
  end
48
48
  end
@@ -56,14 +56,18 @@ module Prawn
56
56
 
57
57
  def issue_prawn_command(prawn, calls)
58
58
  calls.each do |call, arguments, children|
59
- if rewrite_call_arguments(prawn, call, arguments) == false
59
+ if call == "end_path"
60
60
  issue_prawn_command(prawn, children) if children.any?
61
+ prawn.add_content "n" # end path
62
+
63
+ elsif rewrite_call_arguments(prawn, call, arguments) == false
64
+ issue_prawn_command(prawn, children) if children.any?
65
+
66
+ elsif children.empty?
67
+ prawn.send(call, *arguments)
68
+
61
69
  else
62
- if children.empty?
63
- prawn.send(call, *arguments)
64
- else
65
- prawn.send(call, *arguments, &proc_creator(prawn, children))
66
- end
70
+ prawn.send(call, *arguments, &proc_creator(prawn, children))
67
71
  end
68
72
  end
69
73
  end
@@ -42,6 +42,19 @@ class Prawn::Svg::Parser
42
42
  @document.warnings.clear
43
43
 
44
44
  calls = [['fill_color', '000000', []]]
45
+
46
+ calls << [
47
+ 'transformation_matrix',
48
+ [@document.sizing.x_scale, 0, 0, @document.sizing.y_scale, 0, 0],
49
+ []
50
+ ]
51
+
52
+ calls << [
53
+ 'transformation_matrix',
54
+ [1, 0, 0, 1, @document.sizing.x_offset, @document.sizing.y_offset],
55
+ []
56
+ ]
57
+
45
58
  root_element = Prawn::Svg::Element.new(@document, @document.root, calls, :ids => {}, :fill => true)
46
59
 
47
60
  parse_element(root_element)