prawn-svg 0.15.0.0 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +13 -5
- data/lib/prawn/svg/calculators/aspect_ratio.rb +58 -0
- data/lib/prawn/svg/calculators/document_sizing.rb +74 -0
- data/lib/prawn/svg/calculators/pixels.rb +21 -0
- data/lib/prawn/svg/document.rb +16 -43
- data/lib/prawn/svg/element.rb +71 -18
- data/lib/prawn/svg/extension.rb +3 -3
- data/lib/prawn/svg/interface.rb +30 -14
- data/lib/prawn/svg/parser/image.rb +9 -62
- data/lib/prawn/svg/parser/path.rb +19 -1
- data/lib/prawn/svg/parser/text.rb +2 -0
- data/lib/prawn/svg/parser.rb +53 -18
- data/lib/prawn/svg/url_loader.rb +34 -0
- data/lib/prawn/svg/version.rb +1 -1
- data/lib/prawn-svg.rb +4 -0
- data/prawn-svg.gemspec +7 -4
- data/spec/prawn/svg/calculators/aspect_ratio_spec.rb +95 -0
- data/spec/prawn/svg/calculators/document_sizing_spec.rb +73 -0
- data/spec/prawn/svg/document_spec.rb +21 -17
- data/spec/prawn/svg/element_spec.rb +1 -1
- data/spec/{lib/svg_spec.rb → prawn/svg/interface_spec.rb} +7 -2
- data/spec/prawn/svg/parser/path_spec.rb +89 -0
- data/spec/prawn/svg/url_loader_spec.rb +46 -0
- data/spec/sample_images/mushroom-long.jpg +0 -0
- data/spec/sample_images/mushroom-wide.jpg +0 -0
- data/spec/sample_svg/cap_styles.svg +13 -0
- data/spec/sample_svg/display_none.svg +13 -0
- data/spec/sample_svg/gistfile1.svg +36 -0
- data/spec/sample_svg/hidden_paths.svg +6 -0
- data/spec/sample_svg/image01.svg +31 -31
- data/spec/sample_svg/negminy.svg +25 -0
- data/spec/sample_svg/path.svg +5 -0
- data/spec/sample_svg/pie_piece.svg +7 -0
- data/spec/sample_svg/viewbox.svg +4 -0
- data/spec/sample_svg/viewport.svg +23 -0
- data/spec/spec_helper.rb +7 -0
- metadata +79 -25
- data/spec/lib/path_spec.rb +0 -54
- /data/spec/{lib → prawn/svg}/parser_spec.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 13276a5db1c1dd04e219c4e8082205e49dca13ad
|
4
|
+
data.tar.gz: fc5446141d6e7d6ef9f0a393f64bc975d26e453e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 345b0a1c89e7bd4aad297bbcc398322ea7d72ca51a3ac13ea22fdfa62ed0c42ea60344940880f9c570e60f0d21b669e7b3f73ff3791aff16fef87c21c92181d6
|
7
|
+
data.tar.gz: 23391e2209dde7f293c12f4f36af60fc421bea3e05a74b6acd5249bb81c249b0f9bcd8f3c66898f696febf7102ac6157ab836d7d98c20764e0074d2544d6a5f3
|
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 1.x and 2.x 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
|
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,18 +53,23 @@ 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
|
+
- attribute <tt>stroke-linecap</tt>, but only when <tt>fill="none"</tt> due to a PDF limitation
|
57
|
+
|
58
|
+
- the <tt>viewBox</tt> attribute on the <tt><svg></tt> tag
|
59
|
+
|
60
|
+
- the <tt>preserveAspectRatio</tt> attribute on the <tt><svg></tt> and <tt><image></tt> tags
|
61
|
+
|
53
62
|
- transform methods: <tt>translate</tt>, <tt>rotate</tt>, <tt>scale</tt>, <tt>matrix</tt>
|
54
63
|
|
55
64
|
- colors: HTML standard names, <tt>#xxx</tt>, <tt>#xxxxxx</tt>, <tt>rgb(1, 2, 3)</tt>, <tt>rgb(1%, 2%, 3%)</tt>
|
56
65
|
|
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>
|
66
|
+
- 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
67
|
|
59
68
|
- fonts: generic CSS fonts, built in PDF fonts, and any TTF fonts in your fonts path
|
60
69
|
|
61
70
|
## CSS
|
62
71
|
|
63
|
-
|
64
|
-
so do not expect too much of it.
|
72
|
+
prawn-svg uses the css_parser gem to parse CSS <tt><style></tt> blocks. It only handles simple tag, class or id selectors; attribute and other advanced selectors are not supported.
|
65
73
|
|
66
74
|
## Not supported
|
67
75
|
|
@@ -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,74 @@
|
|
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, :viewport_diagonal, :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
|
+
# SVG 1.1 section 7.10
|
51
|
+
@viewport_diagonal = Math.sqrt(@viewport_width**2 + @viewport_height**2) / Math.sqrt(2)
|
52
|
+
|
53
|
+
if @requested_width
|
54
|
+
scale = @requested_width / width
|
55
|
+
width = @requested_width
|
56
|
+
height *= scale
|
57
|
+
@x_scale *= scale
|
58
|
+
@y_scale *= scale
|
59
|
+
|
60
|
+
elsif @requested_height
|
61
|
+
scale = @requested_height / height
|
62
|
+
height = @requested_height
|
63
|
+
width *= scale
|
64
|
+
@x_scale *= scale
|
65
|
+
@y_scale *= scale
|
66
|
+
end
|
67
|
+
|
68
|
+
@output_width = width
|
69
|
+
@output_height = height
|
70
|
+
|
71
|
+
self
|
72
|
+
end
|
73
|
+
end
|
74
|
+
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
|
data/lib/prawn/svg/document.rb
CHANGED
@@ -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
|
-
:
|
16
|
+
:sizing,
|
23
17
|
:cache_images, :fallback_font_name,
|
24
18
|
:css_parser, :elements_by_id
|
25
19
|
|
@@ -32,55 +26,34 @@ 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
29
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
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
|
46
34
|
|
47
|
-
|
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
|
35
|
+
@axis_to_size = {:x => sizing.viewport_width, :y => sizing.viewport_height}
|
56
36
|
|
57
|
-
|
58
|
-
@height ||= @actual_height * @scale
|
37
|
+
yield self if block_given?
|
59
38
|
end
|
60
39
|
|
61
40
|
def x(value)
|
62
|
-
|
41
|
+
points(value, :x)
|
63
42
|
end
|
64
43
|
|
65
44
|
def y(value)
|
66
|
-
|
45
|
+
sizing.output_height - points(value, :y)
|
67
46
|
end
|
68
47
|
|
69
48
|
def distance(value, axis = nil)
|
70
|
-
value &&
|
49
|
+
value && points(value, axis)
|
71
50
|
end
|
72
51
|
|
73
52
|
def points(value, axis = nil)
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
else
|
80
|
-
value.to_f
|
81
|
-
end
|
82
|
-
else
|
83
|
-
value.to_f
|
84
|
-
end
|
53
|
+
Prawn::Svg::Calculators::Pixels.to_pixels(value, @axis_to_size.fetch(axis, sizing.viewport_diagonal))
|
54
|
+
end
|
55
|
+
|
56
|
+
def url_loader
|
57
|
+
@url_loader ||= Prawn::Svg::UrlLoader.new(:enable_cache => cache_images)
|
85
58
|
end
|
86
59
|
end
|
data/lib/prawn/svg/element.rb
CHANGED
@@ -55,14 +55,26 @@ class Prawn::Svg::Element
|
|
55
55
|
parse_opacity_attributes_and_call
|
56
56
|
parse_clip_path_attribute_and_call
|
57
57
|
draw_types = parse_fill_and_stroke_attributes_and_call
|
58
|
-
|
58
|
+
parse_stroke_attributes_and_call
|
59
59
|
parse_font_attributes_and_call
|
60
|
+
parse_display_attribute
|
61
|
+
apply_drawing_call(draw_types)
|
62
|
+
end
|
60
63
|
|
61
|
-
|
62
|
-
|
64
|
+
def apply_drawing_call(draw_types)
|
65
|
+
if !@state[:disable_drawing] && !container?
|
66
|
+
if draw_types.empty? || @state[:display] == "none"
|
67
|
+
add_call_and_enter("end_path")
|
68
|
+
else
|
69
|
+
add_call_and_enter(draw_types.join("_and_"))
|
70
|
+
end
|
63
71
|
end
|
64
72
|
end
|
65
73
|
|
74
|
+
def container?
|
75
|
+
Prawn::Svg::Parser::CONTAINER_TAGS.include?(name)
|
76
|
+
end
|
77
|
+
|
66
78
|
def parse_transform_attribute_and_call
|
67
79
|
return unless transform = @attributes['transform']
|
68
80
|
|
@@ -70,7 +82,7 @@ class Prawn::Svg::Element
|
|
70
82
|
case name
|
71
83
|
when 'translate'
|
72
84
|
x, y = arguments
|
73
|
-
add_call_and_enter name, @document.distance(x), -@document.distance(y)
|
85
|
+
add_call_and_enter name, @document.distance(x, :x), -@document.distance(y, :y)
|
74
86
|
|
75
87
|
when 'rotate'
|
76
88
|
r, x, y = arguments.collect {|a| a.to_f}
|
@@ -90,7 +102,7 @@ class Prawn::Svg::Element
|
|
90
102
|
@document.warnings << "transform 'matrix' must have six arguments"
|
91
103
|
else
|
92
104
|
a, b, c, d, e, f = arguments.collect {|argument| argument.to_f}
|
93
|
-
add_call_and_enter "transformation_matrix", a, -b, -c, d, @document.distance(e), -@document.distance(f)
|
105
|
+
add_call_and_enter "transformation_matrix", a, -b, -c, d, @document.distance(e, :x), -@document.distance(f, :y)
|
94
106
|
end
|
95
107
|
else
|
96
108
|
@document.warnings << "Unknown transformation '#{name}'; ignoring"
|
@@ -129,30 +141,58 @@ class Prawn::Svg::Element
|
|
129
141
|
end
|
130
142
|
|
131
143
|
def parse_fill_and_stroke_attributes_and_call
|
132
|
-
|
133
|
-
|
134
|
-
dec = @attributes[type.to_s]
|
144
|
+
["fill", "stroke"].select do |type|
|
145
|
+
dec = @attributes[type]
|
135
146
|
if dec == "none"
|
136
|
-
state[type] = false
|
147
|
+
state[type.to_sym] = false
|
137
148
|
elsif dec
|
138
|
-
state[type] = true
|
149
|
+
state[type.to_sym] = true
|
139
150
|
if color = Prawn::Svg::Color.color_to_hex(dec)
|
140
151
|
add_call "#{type}_color", color
|
141
152
|
end
|
142
153
|
end
|
143
154
|
|
144
|
-
|
155
|
+
state[type.to_sym]
|
145
156
|
end
|
146
|
-
draw_types
|
147
157
|
end
|
148
158
|
|
149
|
-
|
150
|
-
|
159
|
+
CAP_STYLE_TRANSLATIONS = {"butt" => :butt, "round" => :round, "square" => :projecting_square}
|
160
|
+
|
161
|
+
def parse_stroke_attributes_and_call
|
162
|
+
if width = @attributes['stroke-width']
|
163
|
+
add_call('line_width', @document.distance(width))
|
164
|
+
end
|
165
|
+
|
166
|
+
if (linecap = attribute_value_as_keyword('stroke-linecap')) && linecap != 'inherit'
|
167
|
+
add_call('cap_style', CAP_STYLE_TRANSLATIONS.fetch(linecap, :butt))
|
168
|
+
end
|
169
|
+
|
170
|
+
if dasharray = attribute_value_as_keyword('stroke-dasharray')
|
171
|
+
case dasharray
|
172
|
+
when 'inherit'
|
173
|
+
# don't do anything
|
174
|
+
when 'none'
|
175
|
+
add_call('undash')
|
176
|
+
else
|
177
|
+
array = dasharray.split(Prawn::Svg::Parser::COMMA_WSP_REGEXP)
|
178
|
+
array *= 2 if array.length % 2 == 1
|
179
|
+
number_array = array.map {|value| @document.distance(value)}
|
180
|
+
|
181
|
+
if number_array.any? {|number| number < 0}
|
182
|
+
@document.warnings << "stroke-dasharray cannot have negative numbers; treating as 'none'"
|
183
|
+
add_call('undash')
|
184
|
+
elsif number_array.inject(0) {|a, b| a + b} == 0
|
185
|
+
add_call('undash')
|
186
|
+
else
|
187
|
+
add_call('dash', number_array)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
151
191
|
end
|
152
192
|
|
153
193
|
def parse_font_attributes_and_call
|
154
194
|
if size = @attributes['font-size']
|
155
|
-
@state[:font_size] = size.to_f
|
195
|
+
@state[:font_size] = size.to_f
|
156
196
|
end
|
157
197
|
if weight = @attributes['font-weight']
|
158
198
|
font_updated = true
|
@@ -184,6 +224,9 @@ class Prawn::Svg::Element
|
|
184
224
|
end
|
185
225
|
end
|
186
226
|
|
227
|
+
def parse_display_attribute
|
228
|
+
@state[:display] = @attributes['display'].strip if @attributes['display']
|
229
|
+
end
|
187
230
|
|
188
231
|
def parse_css_method_calls(string)
|
189
232
|
string.scan(/\s*(\w+)\(([^)]+)\)\s*/).collect do |call|
|
@@ -218,7 +261,11 @@ class Prawn::Svg::Element
|
|
218
261
|
end
|
219
262
|
|
220
263
|
@attributes = parse_css_declarations(style)
|
221
|
-
|
264
|
+
|
265
|
+
element.attributes.each do |name, value|
|
266
|
+
name = name.downcase
|
267
|
+
@attributes[name] = value unless @attributes[name]
|
268
|
+
end
|
222
269
|
end
|
223
270
|
|
224
271
|
def parse_css_declarations(declarations)
|
@@ -228,10 +275,16 @@ class Prawn::Svg::Element
|
|
228
275
|
output = {}
|
229
276
|
declarations.split(/[\;$]+/m).each do |decs|
|
230
277
|
if matches = decs.match(/\s*(.[^:]*)\s*\:\s*(.[^;]*)\s*(;|\Z)/i)
|
231
|
-
property, value,
|
232
|
-
output[property] = value
|
278
|
+
property, value, _ = matches.captures
|
279
|
+
output[property.downcase] = value
|
233
280
|
end
|
234
281
|
end
|
235
282
|
output
|
236
283
|
end
|
284
|
+
|
285
|
+
def attribute_value_as_keyword(name)
|
286
|
+
if value = @attributes[name]
|
287
|
+
value.strip.downcase
|
288
|
+
end
|
289
|
+
end
|
237
290
|
end
|
data/lib/prawn/svg/extension.rb
CHANGED
@@ -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.
|
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
|
data/lib/prawn/svg/interface.rb
CHANGED
@@ -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.
|
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.
|
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,19 @@ module Prawn
|
|
56
56
|
|
57
57
|
def issue_prawn_command(prawn, calls)
|
58
58
|
calls.each do |call, arguments, children|
|
59
|
-
|
59
|
+
skip = false
|
60
|
+
|
61
|
+
rewrite_call_arguments(prawn, call, arguments) do
|
60
62
|
issue_prawn_command(prawn, children) if children.any?
|
63
|
+
skip = true
|
64
|
+
end
|
65
|
+
|
66
|
+
if skip
|
67
|
+
# the call has been overridden
|
68
|
+
elsif children.empty?
|
69
|
+
prawn.send(call, *arguments)
|
61
70
|
else
|
62
|
-
|
63
|
-
prawn.send(call, *arguments)
|
64
|
-
else
|
65
|
-
prawn.send(call, *arguments, &proc_creator(prawn, children))
|
66
|
-
end
|
71
|
+
prawn.send(call, *arguments, &proc_creator(prawn, children))
|
67
72
|
end
|
68
73
|
end
|
69
74
|
end
|
@@ -77,7 +82,7 @@ module Prawn
|
|
77
82
|
case call
|
78
83
|
when 'text_group'
|
79
84
|
@relative_text_position = nil
|
80
|
-
|
85
|
+
yield
|
81
86
|
|
82
87
|
when 'draw_text'
|
83
88
|
text, options = arguments
|
@@ -100,15 +105,26 @@ module Prawn
|
|
100
105
|
|
101
106
|
when 'clip'
|
102
107
|
prawn.add_content "W n" # clip to path
|
103
|
-
|
108
|
+
yield
|
104
109
|
|
105
110
|
when 'save'
|
106
111
|
prawn.save_graphics_state
|
107
|
-
|
112
|
+
yield
|
108
113
|
|
109
114
|
when 'restore'
|
110
115
|
prawn.restore_graphics_state
|
111
|
-
|
116
|
+
yield
|
117
|
+
|
118
|
+
when "end_path"
|
119
|
+
yield
|
120
|
+
prawn.add_content "n" # end path
|
121
|
+
|
122
|
+
when 'fill_and_stroke'
|
123
|
+
yield
|
124
|
+
# prawn (as at 2.0.1 anyway) uses 'b' for its fill_and_stroke. 'b' is 'h' (closepath) + 'B', and we
|
125
|
+
# never want closepath to be automatically run as it stuffs up many drawing operations, such as dashes
|
126
|
+
# and line caps, and makes paths close that we didn't ask to be closed when fill is specified.
|
127
|
+
prawn.add_content 'B'
|
112
128
|
end
|
113
129
|
end
|
114
130
|
|