prawn-svg 0.15.0.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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)