prawn-svg 0.12.0.3 → 0.12.0.4
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.
- data/README.md +6 -4
- data/lib/prawn/svg/document.rb +10 -9
- data/lib/prawn/svg/element.rb +51 -32
- data/lib/prawn/svg/extension.rb +2 -2
- data/lib/prawn/svg/font.rb +14 -14
- data/lib/prawn/svg/interface.rb +20 -16
- data/lib/prawn/svg/parser.rb +34 -34
- data/lib/prawn/svg/parser/image.rb +14 -3
- data/lib/prawn/svg/parser/path.rb +39 -39
- data/lib/prawn/svg/parser/text.rb +9 -9
- data/lib/prawn/svg/version.rb +1 -1
- data/spec/lib/parser_spec.rb +6 -6
- data/spec/lib/path_spec.rb +7 -7
- data/spec/lib/svg_spec.rb +3 -3
- data/spec/sample_svg/clip_path.svg +10 -0
- data/spec/sample_svg/image01.svg +67 -1
- data/spec/sample_svg/text_entities.svg +11 -0
- metadata +6 -2
data/README.md
CHANGED
@@ -50,9 +50,11 @@ prawn-svg is in its infancy and does not support the full SVG specifications. I
|
|
50
50
|
|
51
51
|
- <tt>style</tt> tag, if css_parser gem is installed on the system (see CSS section below)
|
52
52
|
|
53
|
-
- <tt>image</tt> tag, only with http/https schemes
|
54
|
-
|
55
|
-
-
|
53
|
+
- <tt>image</tt> tag, only with http/https schemes
|
54
|
+
|
55
|
+
- <tt>clipPath</tt> tag
|
56
|
+
|
57
|
+
- 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>
|
56
58
|
|
57
59
|
- transform methods: <tt>translate</tt>, <tt>rotate</tt>, <tt>scale</tt>, <tt>matrix</tt>
|
58
60
|
|
@@ -66,7 +68,7 @@ By default, prawn-svg has a fonts path of <tt>["/Library/Fonts", "/usr/share/fon
|
|
66
68
|
Mac OS X and Debian Linux users. You can add to the font path:
|
67
69
|
|
68
70
|
```ruby
|
69
|
-
Prawn::Svg.font_path << "/my/font/directory"
|
71
|
+
Prawn::Svg::Interface.font_path << "/my/font/directory"
|
70
72
|
```
|
71
73
|
|
72
74
|
CSS
|
data/lib/prawn/svg/document.rb
CHANGED
@@ -6,11 +6,11 @@ class Prawn::Svg::Document
|
|
6
6
|
CSS_PARSER_LOADED = true
|
7
7
|
rescue LoadError
|
8
8
|
CSS_PARSER_LOADED = false
|
9
|
-
end
|
10
|
-
|
9
|
+
end
|
10
|
+
|
11
11
|
DEFAULT_WIDTH = 640
|
12
12
|
DEFAULT_HEIGHT = 480
|
13
|
-
|
13
|
+
|
14
14
|
# An +Array+ of warnings that occurred while parsing the SVG data.
|
15
15
|
attr_reader :warnings
|
16
16
|
|
@@ -19,14 +19,15 @@ class Prawn::Svg::Document
|
|
19
19
|
|
20
20
|
attr_reader :root,
|
21
21
|
:actual_width, :actual_height, :width, :height, :x_offset, :y_offset, :cache_images,
|
22
|
-
:css_parser
|
23
|
-
|
22
|
+
:css_parser, :elements_by_id
|
23
|
+
|
24
24
|
def initialize(data, bounds, options)
|
25
25
|
@css_parser = CssParser::Parser.new if CSS_PARSER_LOADED
|
26
26
|
|
27
27
|
@root = REXML::Document.new(data).root
|
28
28
|
@warnings = []
|
29
29
|
@options = options
|
30
|
+
@elements_by_id = {}
|
30
31
|
@cache_images = options[:cache_images]
|
31
32
|
@actual_width, @actual_height = bounds # set this first so % width/heights can be used
|
32
33
|
|
@@ -39,9 +40,9 @@ class Prawn::Svg::Document
|
|
39
40
|
@actual_width = points(@root.attributes['width'] || DEFAULT_WIDTH, :x)
|
40
41
|
@actual_height = points(@root.attributes['height'] || DEFAULT_HEIGHT, :y)
|
41
42
|
end
|
42
|
-
|
43
|
+
|
43
44
|
if @options[:width]
|
44
|
-
@width = @options[:width]
|
45
|
+
@width = @options[:width]
|
45
46
|
@scale = @options[:width] / @actual_width.to_f
|
46
47
|
elsif @options[:height]
|
47
48
|
@height = @options[:height]
|
@@ -51,9 +52,9 @@ class Prawn::Svg::Document
|
|
51
52
|
end
|
52
53
|
|
53
54
|
@width ||= @actual_width * @scale
|
54
|
-
@height ||= @actual_height * @scale
|
55
|
+
@height ||= @actual_height * @scale
|
55
56
|
end
|
56
|
-
|
57
|
+
|
57
58
|
def x(value)
|
58
59
|
(points(value, :x) - @x_offset) * scale
|
59
60
|
end
|
data/lib/prawn/svg/element.rb
CHANGED
@@ -1,50 +1,54 @@
|
|
1
1
|
class Prawn::Svg::Element
|
2
2
|
attr_reader :document, :element, :parent_calls, :base_calls, :state, :attributes
|
3
3
|
attr_accessor :calls
|
4
|
-
|
4
|
+
|
5
5
|
def initialize(document, element, parent_calls, state)
|
6
6
|
@document = document
|
7
7
|
@element = element
|
8
8
|
@parent_calls = parent_calls
|
9
9
|
@state = state
|
10
10
|
@base_calls = @calls = []
|
11
|
-
|
11
|
+
|
12
12
|
combine_attributes_and_style_declarations
|
13
13
|
apply_styles
|
14
|
-
|
14
|
+
|
15
15
|
if id = @attributes["id"]
|
16
|
-
|
16
|
+
document.elements_by_id[id] = self
|
17
17
|
end
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
def name
|
21
21
|
@name ||= element.name
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
def each_child_element
|
25
25
|
element.elements.each do |e|
|
26
26
|
yield self.class.new(@document, e, @calls, @state.dup)
|
27
27
|
end
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
def warnings
|
31
31
|
@document.warnings
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
def add_call(name, *arguments)
|
35
35
|
@calls << [name.to_s, arguments, []]
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
def add_call_and_enter(name, *arguments)
|
39
39
|
@calls << [name.to_s, arguments, []]
|
40
40
|
@calls = @calls.last.last
|
41
|
-
end
|
42
|
-
|
41
|
+
end
|
42
|
+
|
43
43
|
def append_calls_to_parent
|
44
44
|
@parent_calls.concat(@base_calls)
|
45
45
|
end
|
46
|
-
|
47
|
-
|
46
|
+
|
47
|
+
def add_calls_from_element(other)
|
48
|
+
@calls.concat other.base_calls
|
49
|
+
end
|
50
|
+
|
51
|
+
|
48
52
|
protected
|
49
53
|
def apply_styles
|
50
54
|
# Transform
|
@@ -87,15 +91,30 @@ class Prawn::Svg::Element
|
|
87
91
|
fill_opacity = clamp(@attributes['fill-opacity'].to_f, 0, 1) if @attributes['fill-opacity']
|
88
92
|
stroke_opacity = clamp(@attributes['stroke-opacity'].to_f, 0, 1) if @attributes['stroke-opacity']
|
89
93
|
|
90
|
-
if fill_opacity || stroke_opacity
|
94
|
+
if fill_opacity || stroke_opacity
|
91
95
|
state[:fill_opacity] = (state[:fill_opacity] || 1) * (fill_opacity || 1)
|
92
96
|
state[:stroke_opacity] = (state[:stroke_opacity] || 1) * (stroke_opacity || 1)
|
93
97
|
|
94
98
|
add_call_and_enter 'transparent', state[:fill_opacity], state[:stroke_opacity]
|
95
99
|
end
|
96
100
|
|
101
|
+
# Clip path
|
102
|
+
if clip_path = @attributes['clip-path']
|
103
|
+
if (matches = clip_path.strip.match(/\Aurl\(#(.*)\)\z/)).nil?
|
104
|
+
document.warnings << "Only clip-path attributes with the form 'url(#xxx)' are supported"
|
105
|
+
elsif (clip_path_element = @document.elements_by_id[matches[1]]).nil?
|
106
|
+
document.warnings << "clip-path ID '#{matches[1]}' not defined"
|
107
|
+
elsif clip_path_element.element.name != "clipPath"
|
108
|
+
document.warnings << "clip-path ID '#{matches[1]}' does not point to a clipPath tag"
|
109
|
+
else
|
110
|
+
add_call_and_enter 'save_graphics_state'
|
111
|
+
add_calls_from_element clip_path_element
|
112
|
+
add_call "clip"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
97
116
|
# Fill and stroke
|
98
|
-
draw_types = []
|
117
|
+
draw_types = []
|
99
118
|
[:fill, :stroke].each do |type|
|
100
119
|
dec = @attributes[type.to_s]
|
101
120
|
if dec == "none"
|
@@ -109,11 +128,11 @@ class Prawn::Svg::Element
|
|
109
128
|
|
110
129
|
draw_types << type.to_s if state[type]
|
111
130
|
end
|
112
|
-
|
131
|
+
|
113
132
|
# Stroke width
|
114
|
-
add_call('line_width', @document.distance(@attributes['stroke-width'])) if @attributes['stroke-width']
|
133
|
+
add_call('line_width', @document.distance(@attributes['stroke-width'])) if @attributes['stroke-width']
|
115
134
|
|
116
|
-
# Fonts
|
135
|
+
# Fonts
|
117
136
|
if size = @attributes['font-size']
|
118
137
|
@state[:font_size] = size.to_f * @document.scale
|
119
138
|
end
|
@@ -125,20 +144,20 @@ class Prawn::Svg::Element
|
|
125
144
|
font_updated = true
|
126
145
|
@state[:font_family] = family
|
127
146
|
end
|
128
|
-
|
147
|
+
|
129
148
|
if @state[:font_family] && font_updated
|
130
149
|
if pdf_font = Prawn::Svg::Font.map_font_family_to_pdf_font(@state[:font_family], @state[:font_style])
|
131
150
|
add_call_and_enter 'font', pdf_font
|
132
151
|
else
|
133
152
|
@document.warnings << "Font family '#{@state[:font_family]}' style '#{@state[:font_style] || 'normal'}' is not a known font."
|
134
153
|
end
|
135
|
-
end
|
136
|
-
|
154
|
+
end
|
155
|
+
|
137
156
|
# Call fill, stroke, or both
|
138
157
|
draw_type = draw_types.join("_and_")
|
139
|
-
if draw_type != "" && !Prawn::Svg::Parser::CONTAINER_TAGS.include?(element.name)
|
140
|
-
add_call_and_enter
|
141
|
-
end
|
158
|
+
if draw_type != "" && !@state[:disable_drawing] && !Prawn::Svg::Parser::CONTAINER_TAGS.include?(element.name)
|
159
|
+
add_call_and_enter(draw_type)
|
160
|
+
end
|
142
161
|
end
|
143
162
|
|
144
163
|
def parse_css_method_calls(string)
|
@@ -150,7 +169,7 @@ class Prawn::Svg::Element
|
|
150
169
|
end
|
151
170
|
|
152
171
|
# TODO : use http://www.w3.org/TR/SVG11/types.html#ColorKeywords
|
153
|
-
HTML_COLORS = {
|
172
|
+
HTML_COLORS = {
|
154
173
|
'black' => "000000", 'green' => "008000", 'silver' => "c0c0c0", 'lime' => "00ff00",
|
155
174
|
'gray' => "808080", 'olive' => "808000", 'white' => "ffffff", 'yellow' => "ffff00",
|
156
175
|
'maroon' => "800000", 'navy' => "000080", 'red' => "ff0000", 'blue' => "0000ff",
|
@@ -173,26 +192,26 @@ class Prawn::Svg::Element
|
|
173
192
|
value = m[n].to_f
|
174
193
|
value *= 2.55 if m[n][-1..-1] == '%'
|
175
194
|
"%02x" % clamp(value.round, 0, 255)
|
176
|
-
end.join
|
177
|
-
end
|
195
|
+
end.join
|
196
|
+
end
|
178
197
|
end
|
179
198
|
end
|
180
199
|
|
181
200
|
def clamp(value, min_value, max_value)
|
182
201
|
[[value, min_value].max, max_value].min
|
183
|
-
end
|
184
|
-
|
202
|
+
end
|
203
|
+
|
185
204
|
def combine_attributes_and_style_declarations
|
186
205
|
if @document && @document.css_parser
|
187
206
|
tag_style = @document.css_parser.find_by_selector(element.name)
|
188
207
|
id_style = @document.css_parser.find_by_selector("##{element.attributes["id"]}") if element.attributes["id"]
|
189
|
-
|
208
|
+
|
190
209
|
if classes = element.attributes["class"]
|
191
210
|
class_styles = classes.strip.split(/\s+/).collect do |class_name|
|
192
211
|
@document.css_parser.find_by_selector(".#{class_name}")
|
193
212
|
end
|
194
213
|
end
|
195
|
-
|
214
|
+
|
196
215
|
element_style = element.attributes['style']
|
197
216
|
|
198
217
|
style = [tag_style, class_styles, id_style, element_style].flatten.collect do |s|
|
@@ -204,7 +223,7 @@ class Prawn::Svg::Element
|
|
204
223
|
|
205
224
|
@attributes = parse_css_declarations(style)
|
206
225
|
element.attributes.each {|n,v| @attributes[n] = v unless @attributes[n]}
|
207
|
-
end
|
226
|
+
end
|
208
227
|
|
209
228
|
def parse_css_declarations(declarations)
|
210
229
|
# copied from css_parser
|
data/lib/prawn/svg/extension.rb
CHANGED
@@ -6,7 +6,7 @@ module Prawn
|
|
6
6
|
#
|
7
7
|
# +options+ must contain the key :at, which takes a tuple of x and y co-ordinates.
|
8
8
|
#
|
9
|
-
# +options+ can optionally contain the key :width or :height. If both are
|
9
|
+
# +options+ can optionally contain the key :width or :height. If both are
|
10
10
|
# specified, only :width will be used. If neither are specified, the resolution
|
11
11
|
# given in the SVG will be used.
|
12
12
|
#
|
@@ -18,7 +18,7 @@ module Prawn
|
|
18
18
|
svg = Prawn::Svg::Interface.new(data, self, options)
|
19
19
|
svg.draw
|
20
20
|
{:warnings => svg.document.warnings, :width => svg.document.width, :height => svg.document.height}
|
21
|
-
end
|
21
|
+
end
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
data/lib/prawn/svg/font.rb
CHANGED
@@ -7,7 +7,7 @@ class Prawn::Svg::Font
|
|
7
7
|
"cursive" => "Times-Roman",
|
8
8
|
"fantasy" => "Times-Roman",
|
9
9
|
"monospace" => "Courier"}
|
10
|
-
|
10
|
+
|
11
11
|
def self.map_font_family_to_pdf_font(font_family, font_style = nil)
|
12
12
|
font_family.split(",").detect do |font|
|
13
13
|
font = font.gsub(/['"]/, '').gsub(/\s{2,}/, ' ').strip.downcase
|
@@ -17,25 +17,25 @@ class Prawn::Svg::Font
|
|
17
17
|
|
18
18
|
generic_font = GENERIC_CSS_FONT_MAPPING[font]
|
19
19
|
break generic_font if generic_font
|
20
|
-
|
20
|
+
|
21
21
|
break font.downcase if font_installed?(font, font_style)
|
22
22
|
end
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def self.font_path(font_family, font_style = nil)
|
26
26
|
font_style = :normal if font_style.nil?
|
27
27
|
if installed_styles = installed_fonts[font_family.downcase]
|
28
28
|
installed_styles[font_style]
|
29
29
|
end
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
def self.font_installed?(font_family, font_style = nil)
|
33
33
|
!font_path(font_family, font_style).nil?
|
34
34
|
end
|
35
35
|
|
36
36
|
def self.installed_fonts
|
37
37
|
return @installed_fonts if @installed_fonts
|
38
|
-
|
38
|
+
|
39
39
|
fonts = {}
|
40
40
|
Prawn::Svg::Interface.font_path.uniq.collect {|path| Dir["#{path}/*"]}.flatten.each do |filename|
|
41
41
|
information = font_information(filename) rescue nil
|
@@ -46,14 +46,14 @@ class Prawn::Svg::Font
|
|
46
46
|
when 'Bold Italic' then :bold_italic
|
47
47
|
else :normal
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
(fonts[font_name.downcase] ||= {})[font_style] = filename
|
51
51
|
end
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
@installed_fonts = fonts
|
55
55
|
end
|
56
|
-
|
56
|
+
|
57
57
|
def self.font_information(filename)
|
58
58
|
File.open(filename, "r") do |f|
|
59
59
|
x = f.read(12)
|
@@ -66,22 +66,22 @@ class Prawn::Svg::Font
|
|
66
66
|
break tables[start+8..start+15].unpack("NN")
|
67
67
|
end
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
return unless length
|
71
71
|
f.seek(offset)
|
72
72
|
data = f.read(length)
|
73
|
-
|
73
|
+
|
74
74
|
format, name_count, string_offset = data[0..5].unpack("nnn")
|
75
|
-
|
75
|
+
|
76
76
|
names = {}
|
77
77
|
name_count.times do |index|
|
78
78
|
start = 6 + index * 12
|
79
|
-
platform_id, platform_specific_id, language_id, name_id, length, offset = data[start..start+11].unpack("nnnnnn")
|
79
|
+
platform_id, platform_specific_id, language_id, name_id, length, offset = data[start..start+11].unpack("nnnnnn")
|
80
80
|
next unless language_id == 0 # English
|
81
81
|
next unless name_id == 1 || name_id == 2
|
82
|
-
|
82
|
+
|
83
83
|
offset += string_offset
|
84
|
-
field = data[offset..offset+length-1]
|
84
|
+
field = data[offset..offset+length-1]
|
85
85
|
names[name_id] = if platform_id == 0
|
86
86
|
begin
|
87
87
|
if field.respond_to?(:encode)
|
data/lib/prawn/svg/interface.rb
CHANGED
@@ -9,11 +9,11 @@ module Prawn
|
|
9
9
|
|
10
10
|
@font_path = []
|
11
11
|
DEFAULT_FONT_PATHS.each {|path| @font_path << path if File.exists?(path)}
|
12
|
-
|
12
|
+
|
13
13
|
class << self; attr_accessor :font_path; end
|
14
|
-
|
14
|
+
|
15
15
|
attr_reader :data, :prawn, :document, :options
|
16
|
-
|
16
|
+
|
17
17
|
#
|
18
18
|
# Creates a Prawn::Svg object.
|
19
19
|
#
|
@@ -21,19 +21,19 @@ module Prawn
|
|
21
21
|
#
|
22
22
|
# +options+ must contain the key :at, which takes a tuple of x and y co-ordinates.
|
23
23
|
#
|
24
|
-
# +options+ can optionally contain the key :width or :height. If both are
|
24
|
+
# +options+ can optionally contain the key :width or :height. If both are
|
25
25
|
# specified, only :width will be used.
|
26
26
|
#
|
27
27
|
def initialize(data, prawn, options)
|
28
28
|
@data = data
|
29
29
|
@prawn = prawn
|
30
30
|
@options = options
|
31
|
-
|
31
|
+
|
32
32
|
@options[:at] or raise "options[:at] must be specified"
|
33
33
|
|
34
34
|
prawn.font_families.update(Prawn::Svg::Font.installed_fonts)
|
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)
|
37
37
|
end
|
38
38
|
|
39
39
|
#
|
@@ -47,12 +47,12 @@ module Prawn
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
|
51
|
-
private
|
50
|
+
|
51
|
+
private
|
52
52
|
def proc_creator(prawn, calls)
|
53
53
|
Proc.new {issue_prawn_command(prawn, calls)}
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
def issue_prawn_command(prawn, calls)
|
57
57
|
calls.each do |call, arguments, children|
|
58
58
|
if rewrite_call_arguments(prawn, call, arguments) == false
|
@@ -66,21 +66,21 @@ module Prawn
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
def rewrite_call_arguments(prawn, call, arguments)
|
71
71
|
if call == 'relative_draw_text'
|
72
72
|
call.replace "draw_text"
|
73
73
|
arguments.last[:at][0] = @relative_text_position if @relative_text_position
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
case call
|
77
77
|
when 'text_group'
|
78
78
|
@relative_text_position = nil
|
79
79
|
false
|
80
|
-
|
80
|
+
|
81
81
|
when 'draw_text'
|
82
82
|
text, options = arguments
|
83
|
-
|
83
|
+
|
84
84
|
width = prawn.width_of(text, options.merge(:kerning => true))
|
85
85
|
|
86
86
|
if (anchor = options.delete(:text_anchor)) && %w(middle end).include?(anchor)
|
@@ -90,17 +90,21 @@ module Prawn
|
|
90
90
|
|
91
91
|
space_width = prawn.width_of("n", options)
|
92
92
|
@relative_text_position = options[:at][0] + width + space_width
|
93
|
-
|
93
|
+
|
94
94
|
when 'transformation_matrix'
|
95
95
|
x = prawn.bounds.absolute_left
|
96
96
|
y = prawn.bounds.absolute_top
|
97
97
|
arguments[4] += x - (x * arguments[0] - y * arguments[1])
|
98
98
|
arguments[5] += y - (x * arguments[1] + y * arguments[0])
|
99
|
-
|
99
|
+
|
100
|
+
when 'clip'
|
101
|
+
prawn.add_content "W n" # clip to path
|
102
|
+
false
|
103
|
+
|
100
104
|
when 'save'
|
101
105
|
prawn.save_graphics_state
|
102
106
|
false
|
103
|
-
|
107
|
+
|
104
108
|
when 'restore'
|
105
109
|
prawn.restore_graphics_state
|
106
110
|
false
|
data/lib/prawn/svg/parser.rb
CHANGED
@@ -12,12 +12,12 @@ require 'rexml/document'
|
|
12
12
|
# SVG to another format.
|
13
13
|
#
|
14
14
|
class Prawn::Svg::Parser
|
15
|
-
CONTAINER_TAGS = %w(g svg symbol defs)
|
16
|
-
|
15
|
+
CONTAINER_TAGS = %w(g svg symbol defs clipPath)
|
16
|
+
|
17
17
|
#
|
18
|
-
# Construct a Parser object.
|
18
|
+
# Construct a Parser object.
|
19
19
|
#
|
20
|
-
# The +data+ argument is SVG data.
|
20
|
+
# The +data+ argument is SVG data.
|
21
21
|
#
|
22
22
|
# +bounds+ is a tuple [width, height] that specifies the bounds of the drawing space in points.
|
23
23
|
#
|
@@ -40,16 +40,16 @@ class Prawn::Svg::Parser
|
|
40
40
|
#
|
41
41
|
def parse
|
42
42
|
@document.warnings.clear
|
43
|
-
|
43
|
+
|
44
44
|
calls = [['fill_color', '000000', []]]
|
45
45
|
root_element = Prawn::Svg::Element.new(@document, @document.root, calls, :ids => {}, :fill => true)
|
46
|
-
|
46
|
+
|
47
47
|
parse_element(root_element)
|
48
48
|
calls
|
49
49
|
end
|
50
50
|
|
51
51
|
|
52
|
-
private
|
52
|
+
private
|
53
53
|
REQUIRED_ATTRIBUTES = {
|
54
54
|
"polyline" => %w(points),
|
55
55
|
"polygon" => %w(points),
|
@@ -57,9 +57,9 @@ class Prawn::Svg::Parser
|
|
57
57
|
"ellipse" => %w(rx ry),
|
58
58
|
"rect" => %w(width height),
|
59
59
|
"path" => %w(d),
|
60
|
-
"image" => %w(width height)
|
60
|
+
"image" => %w(width height)
|
61
61
|
}
|
62
|
-
|
62
|
+
|
63
63
|
USE_NEW_CIRCLE_CALL = Prawn::Document.new.respond_to?(:circle)
|
64
64
|
USE_NEW_ELLIPSE_CALL = Prawn::Document.new.respond_to?(:ellipse)
|
65
65
|
|
@@ -69,17 +69,18 @@ class Prawn::Svg::Parser
|
|
69
69
|
if required_attributes = REQUIRED_ATTRIBUTES[element.name]
|
70
70
|
return unless check_attrs_present(element, required_attributes)
|
71
71
|
end
|
72
|
-
|
72
|
+
|
73
73
|
case element.name
|
74
74
|
when *CONTAINER_TAGS
|
75
|
+
do_not_append_calls = %w(symbol defs clipPath).include?(element.name)
|
76
|
+
element.state[:disable_drawing] = true if element.name == "clipPath"
|
77
|
+
|
75
78
|
element.each_child_element do |child|
|
76
79
|
element.add_call "save"
|
77
80
|
parse_element(child)
|
78
81
|
element.add_call "restore"
|
79
82
|
end
|
80
|
-
|
81
|
-
do_not_append_calls = %w(symbol defs).include?(element.name)
|
82
|
-
|
83
|
+
|
83
84
|
when 'style'
|
84
85
|
load_css_styles(element)
|
85
86
|
|
@@ -107,20 +108,20 @@ class Prawn::Svg::Parser
|
|
107
108
|
[x(x), y(y)]
|
108
109
|
end
|
109
110
|
element.add_call "polygon", *points
|
110
|
-
|
111
|
+
|
111
112
|
when 'circle'
|
112
113
|
if USE_NEW_CIRCLE_CALL
|
113
|
-
element.add_call "circle",
|
114
|
+
element.add_call "circle",
|
114
115
|
[x(attrs['cx'] || "0"), y(attrs['cy'] || "0")], distance(attrs['r'])
|
115
116
|
else
|
116
|
-
element.add_call "circle_at",
|
117
|
+
element.add_call "circle_at",
|
117
118
|
[x(attrs['cx'] || "0"), y(attrs['cy'] || "0")], :radius => distance(attrs['r'])
|
118
119
|
end
|
119
|
-
|
120
|
+
|
120
121
|
when 'ellipse'
|
121
|
-
element.add_call USE_NEW_ELLIPSE_CALL ? "ellipse" : "ellipse_at",
|
122
|
+
element.add_call USE_NEW_ELLIPSE_CALL ? "ellipse" : "ellipse_at",
|
122
123
|
[x(attrs['cx'] || "0"), y(attrs['cy'] || "0")], distance(attrs['rx']), distance(attrs['ry'])
|
123
|
-
|
124
|
+
|
124
125
|
when 'rect'
|
125
126
|
radius = distance(attrs['rx'] || attrs['ry'])
|
126
127
|
args = [[x(attrs['x'] || '0'), y(attrs['y'] || '0')], distance(attrs['width']), distance(attrs['height'])]
|
@@ -130,18 +131,18 @@ class Prawn::Svg::Parser
|
|
130
131
|
else
|
131
132
|
element.add_call "rectangle", *args
|
132
133
|
end
|
133
|
-
|
134
|
+
|
134
135
|
when 'path'
|
135
136
|
parse_path(element)
|
136
|
-
|
137
|
+
|
137
138
|
when 'use'
|
138
139
|
parse_use(element)
|
139
140
|
|
140
141
|
when 'title', 'desc', 'metadata'
|
141
142
|
# ignore
|
142
143
|
do_not_append_calls = true
|
143
|
-
|
144
|
-
when 'font-face'
|
144
|
+
|
145
|
+
when 'font-face'
|
145
146
|
# not supported
|
146
147
|
do_not_append_calls = true
|
147
148
|
|
@@ -152,11 +153,11 @@ class Prawn::Svg::Parser
|
|
152
153
|
else
|
153
154
|
@document.warnings << "Unknown tag '#{element.name}'; ignoring"
|
154
155
|
end
|
155
|
-
|
156
|
+
|
156
157
|
element.append_calls_to_parent unless do_not_append_calls
|
157
158
|
end
|
158
159
|
|
159
|
-
|
160
|
+
|
160
161
|
def parse_path(element)
|
161
162
|
@svg_path ||= Path.new
|
162
163
|
|
@@ -179,21 +180,20 @@ class Prawn::Svg::Parser
|
|
179
180
|
else
|
180
181
|
element.add_call command
|
181
182
|
end
|
182
|
-
end
|
183
|
+
end
|
183
184
|
end
|
184
|
-
|
185
|
+
|
185
186
|
def parse_use(element)
|
186
187
|
if href = element.attributes['xlink:href']
|
187
188
|
if href[0..0] == '#'
|
188
189
|
id = href[1..-1]
|
189
|
-
if
|
190
|
+
if definition_element = @document.elements_by_id[id]
|
190
191
|
x = element.attributes['x']
|
191
192
|
y = element.attributes['y']
|
192
193
|
if x || y
|
193
194
|
element.add_call_and_enter "translate", distance(x || 0), -distance(y || 0)
|
194
195
|
end
|
195
|
-
|
196
|
-
element.calls.concat(id_calls)
|
196
|
+
element.add_calls_from_element definition_element
|
197
197
|
else
|
198
198
|
@document.warnings << "no tag with ID '#{id}' was found, referenced by use tag"
|
199
199
|
end
|
@@ -203,7 +203,7 @@ class Prawn::Svg::Parser
|
|
203
203
|
else
|
204
204
|
@document.warnings << "no xlink:href specified on use tag"
|
205
205
|
end
|
206
|
-
end
|
206
|
+
end
|
207
207
|
|
208
208
|
####################################################################################################################
|
209
209
|
|
@@ -215,8 +215,8 @@ class Prawn::Svg::Parser
|
|
215
215
|
element.element.text
|
216
216
|
end
|
217
217
|
|
218
|
-
@document.css_parser.add_block!(data)
|
219
|
-
end
|
218
|
+
@document.css_parser.add_block!(data)
|
219
|
+
end
|
220
220
|
end
|
221
221
|
|
222
222
|
def check_attrs_present(element, attrs)
|
@@ -226,7 +226,7 @@ class Prawn::Svg::Parser
|
|
226
226
|
end
|
227
227
|
missing_attrs.empty?
|
228
228
|
end
|
229
|
-
|
229
|
+
|
230
230
|
%w(x y distance).each do |method|
|
231
231
|
define_method(method) {|*a| @document.send(method, *a)}
|
232
232
|
end
|
@@ -44,20 +44,30 @@ class Prawn::Svg::Parser::Image
|
|
44
44
|
par = (attrs['preserveAspectRatio'] || "xMidYMid meet").strip.split(/\s+/)
|
45
45
|
par.shift if par.first == "defer"
|
46
46
|
align, meet_or_slice = par
|
47
|
-
|
47
|
+
slice = meet_or_slice == "slice"
|
48
|
+
|
49
|
+
if slice
|
50
|
+
element.add_call "save"
|
51
|
+
element.add_call "rectangle", [x, y], width, height
|
52
|
+
element.add_call "clip"
|
53
|
+
end
|
48
54
|
|
49
55
|
options = {}
|
50
56
|
case align
|
51
57
|
when /\Ax(Min|Mid|Max)Y(Min|Mid|Max)\z/
|
52
|
-
options[:fit] = [width, height]
|
53
58
|
ratio = image_ratio(image)
|
54
|
-
|
59
|
+
|
60
|
+
options[:fit] = [width, height] unless slice
|
61
|
+
|
62
|
+
if (width/height > ratio) == slice
|
63
|
+
options[:width] = width if slice
|
55
64
|
y -= case $2
|
56
65
|
when "Min" then 0
|
57
66
|
when "Mid" then (height - width/ratio)/2
|
58
67
|
when "Max" then height - width/ratio
|
59
68
|
end
|
60
69
|
else
|
70
|
+
options[:height] = height if slice
|
61
71
|
x += case $1
|
62
72
|
when "Min" then 0
|
63
73
|
when "Mid" then (width - height*ratio)/2
|
@@ -74,6 +84,7 @@ class Prawn::Svg::Parser::Image
|
|
74
84
|
options[:at] = [x, y]
|
75
85
|
|
76
86
|
element.add_call "image", FakeIO.new(image), options
|
87
|
+
element.add_call "restore" if slice
|
77
88
|
rescue Error => e
|
78
89
|
@document.warnings << e.message
|
79
90
|
end
|
@@ -38,19 +38,19 @@ module Prawn
|
|
38
38
|
raise InvalidError, "Invalid character '#{c}' in SVG path data"
|
39
39
|
end
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
values << value.to_f if value != ""
|
43
43
|
run_path_command(cmd, values) if cmd
|
44
|
-
|
44
|
+
|
45
45
|
@calls
|
46
46
|
end
|
47
|
-
|
48
|
-
|
47
|
+
|
48
|
+
|
49
49
|
private
|
50
50
|
def run_path_command(command, values)
|
51
51
|
upcase_command = command.upcase
|
52
52
|
relative = command != upcase_command
|
53
|
-
|
53
|
+
|
54
54
|
case upcase_command
|
55
55
|
when 'M' # moveto
|
56
56
|
x = values.shift
|
@@ -60,19 +60,19 @@ module Prawn
|
|
60
60
|
x += @last_point.first
|
61
61
|
y += @last_point.last
|
62
62
|
end
|
63
|
-
|
63
|
+
|
64
64
|
@last_point = @subpath_initial_point = [x, y]
|
65
65
|
@calls << ["move_to", @last_point]
|
66
|
-
|
66
|
+
|
67
67
|
return run_path_command('L', values) if values.any?
|
68
|
-
|
68
|
+
|
69
69
|
when 'Z' # closepath
|
70
70
|
if @subpath_initial_point
|
71
71
|
#@calls << ["line_to", @subpath_initial_point]
|
72
72
|
@calls << ["close_path"]
|
73
73
|
@last_point = @subpath_initial_point
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
when 'L' # lineto
|
77
77
|
while values.any?
|
78
78
|
x = values.shift
|
@@ -84,7 +84,7 @@ module Prawn
|
|
84
84
|
@last_point = [x, y]
|
85
85
|
@calls << ["line_to", @last_point]
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
when 'H' # horizontal lineto
|
89
89
|
while values.any?
|
90
90
|
x = values.shift
|
@@ -100,89 +100,89 @@ module Prawn
|
|
100
100
|
@last_point = [@last_point.first, y]
|
101
101
|
@calls << ["line_to", @last_point]
|
102
102
|
end
|
103
|
-
|
103
|
+
|
104
104
|
when 'C' # curveto
|
105
105
|
while values.any?
|
106
106
|
x1, y1, x2, y2, x, y = (1..6).collect {values.shift}
|
107
107
|
if relative && @last_point
|
108
108
|
x += @last_point.first
|
109
109
|
x1 += @last_point.first
|
110
|
-
x2 += @last_point.first
|
111
|
-
y += @last_point.last
|
112
|
-
y1 += @last_point.last
|
113
|
-
y2 += @last_point.last
|
110
|
+
x2 += @last_point.first
|
111
|
+
y += @last_point.last
|
112
|
+
y1 += @last_point.last
|
113
|
+
y2 += @last_point.last
|
114
114
|
end
|
115
|
-
|
115
|
+
|
116
116
|
@last_point = [x, y]
|
117
117
|
@previous_control_point = [x2, y2]
|
118
118
|
@calls << ["curve_to", [x, y, x1, y1, x2, y2]]
|
119
|
-
end
|
119
|
+
end
|
120
120
|
|
121
121
|
when 'S' # shorthand/smooth curveto
|
122
122
|
while values.any?
|
123
123
|
x2, y2, x, y = (1..4).collect {values.shift}
|
124
124
|
if relative && @last_point
|
125
125
|
x += @last_point.first
|
126
|
-
x2 += @last_point.first
|
127
|
-
y += @last_point.last
|
128
|
-
y2 += @last_point.last
|
126
|
+
x2 += @last_point.first
|
127
|
+
y += @last_point.last
|
128
|
+
y2 += @last_point.last
|
129
129
|
end
|
130
|
-
|
130
|
+
|
131
131
|
if @previous_control_point
|
132
132
|
x1 = 2 * @last_point.first - @previous_control_point.first
|
133
133
|
y1 = 2 * @last_point.last - @previous_control_point.last
|
134
134
|
else
|
135
135
|
x1, y1 = @last_point
|
136
136
|
end
|
137
|
-
|
137
|
+
|
138
138
|
@last_point = [x, y]
|
139
|
-
@previous_control_point = [x2, y2]
|
139
|
+
@previous_control_point = [x2, y2]
|
140
140
|
@calls << ["curve_to", [x, y, x1, y1, x2, y2]]
|
141
141
|
end
|
142
|
-
|
142
|
+
|
143
143
|
when 'Q', 'T' # quadratic curveto
|
144
144
|
while values.any?
|
145
145
|
if shorthand = upcase_command == 'T'
|
146
|
-
x, y = (1..2).collect {values.shift}
|
147
|
-
else
|
146
|
+
x, y = (1..2).collect {values.shift}
|
147
|
+
else
|
148
148
|
x1, y1, x, y = (1..4).collect {values.shift}
|
149
149
|
end
|
150
|
-
|
150
|
+
|
151
151
|
if relative && @last_point
|
152
152
|
x += @last_point.first
|
153
153
|
x1 += @last_point.first if x1
|
154
|
-
y += @last_point.last
|
154
|
+
y += @last_point.last
|
155
155
|
y1 += @last_point.last if y1
|
156
156
|
end
|
157
|
-
|
158
|
-
if shorthand
|
157
|
+
|
158
|
+
if shorthand
|
159
159
|
if @previous_quadratic_control_point
|
160
160
|
x1 = 2 * @last_point.first - @previous_quadratic_control_point.first
|
161
161
|
y1 = 2 * @last_point.last - @previous_quadratic_control_point.last
|
162
162
|
else
|
163
163
|
x1, y1 = @last_point
|
164
164
|
end
|
165
|
-
end
|
166
|
-
|
165
|
+
end
|
166
|
+
|
167
167
|
# convert from quadratic to cubic
|
168
168
|
cx1 = @last_point.first + (x1 - @last_point.first) * 2 / 3.0
|
169
169
|
cy1 = @last_point.last + (y1 - @last_point.last) * 2 / 3.0
|
170
170
|
cx2 = cx1 + (x - @last_point.first) / 3.0
|
171
|
-
cy2 = cy1 + (y - @last_point.last) / 3.0
|
171
|
+
cy2 = cy1 + (y - @last_point.last) / 3.0
|
172
172
|
|
173
173
|
@last_point = [x, y]
|
174
174
|
@previous_quadratic_control_point = [x1, y1]
|
175
|
-
|
175
|
+
|
176
176
|
@calls << ["curve_to", [x, y, cx1, cy1, cx2, cy2]]
|
177
|
-
end
|
178
|
-
|
177
|
+
end
|
178
|
+
|
179
179
|
when 'A'
|
180
|
-
# unsupported
|
180
|
+
# unsupported
|
181
181
|
end
|
182
|
-
|
182
|
+
|
183
183
|
@previous_control_point = nil unless %w(C S).include?(upcase_command)
|
184
184
|
@previous_quadratic_control_point = nil unless %w(Q T).include?(upcase_command)
|
185
185
|
end
|
186
186
|
end
|
187
|
-
end
|
187
|
+
end
|
188
188
|
end
|
@@ -3,17 +3,17 @@ class Prawn::Svg::Parser::Text
|
|
3
3
|
element.add_call_and_enter "text_group"
|
4
4
|
internal_parse(element, [element.document.x(0)], [element.document.y(0)], false)
|
5
5
|
end
|
6
|
-
|
6
|
+
|
7
7
|
protected
|
8
8
|
def internal_parse(element, x_positions, y_positions, relative)
|
9
9
|
attrs = element.attributes
|
10
|
-
|
10
|
+
|
11
11
|
if attrs['x'] || attrs['y']
|
12
12
|
relative = false
|
13
13
|
x_positions = attrs['x'].split(/[\s,]+/).collect {|n| element.document.x(n)} if attrs['x']
|
14
14
|
y_positions = attrs['y'].split(/[\s,]+/).collect {|n| element.document.y(n)} if attrs['y']
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
if attrs['dx'] || attrs['dy']
|
18
18
|
element.add_call_and_enter "translate", element.document.distance(attrs['dx'] || 0), -element.document.distance(attrs['dy'] || 0)
|
19
19
|
end
|
@@ -25,18 +25,18 @@ class Prawn::Svg::Parser::Text
|
|
25
25
|
if style = element.state[:font_style]
|
26
26
|
opts[:style] = style
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
# This is not a prawn option but we can't work out how to render it here -
|
30
30
|
# it's handled by Svg#rewrite_call_arguments
|
31
31
|
if anchor = attrs['text-anchor']
|
32
|
-
opts[:text_anchor] = anchor
|
32
|
+
opts[:text_anchor] = anchor
|
33
33
|
end
|
34
34
|
|
35
35
|
element.element.children.each do |child|
|
36
36
|
if child.node_type == :text
|
37
|
-
text = child.
|
37
|
+
text = child.value.strip.gsub(/\s+/, " ")
|
38
38
|
|
39
|
-
while text != ""
|
39
|
+
while text != ""
|
40
40
|
opts[:at] = [x_positions.first, y_positions.first]
|
41
41
|
|
42
42
|
if x_positions.length > 1 || y_positions.length > 1
|
@@ -59,10 +59,10 @@ class Prawn::Svg::Parser::Text
|
|
59
59
|
internal_parse(child_element, x_positions, y_positions, relative)
|
60
60
|
child_element.append_calls_to_parent
|
61
61
|
element.add_call 'restore'
|
62
|
-
|
62
|
+
|
63
63
|
else
|
64
64
|
element.warnings << "Unknown tag '#{child.name}' inside text tag; ignoring"
|
65
65
|
end
|
66
|
-
end
|
66
|
+
end
|
67
67
|
end
|
68
68
|
end
|
data/lib/prawn/svg/version.rb
CHANGED
data/spec/lib/parser_spec.rb
CHANGED
@@ -8,7 +8,7 @@ describe Prawn::Svg::Parser do
|
|
8
8
|
<line x1="10%" y1="10%" x2="90%" y2="90%" />
|
9
9
|
</svg>
|
10
10
|
SVG
|
11
|
-
|
11
|
+
|
12
12
|
document = Prawn::Svg::Document.new(svg, [2000, 2000], {})
|
13
13
|
Prawn::Svg::Parser.new(document).parse[-2][-1].should == [["line", [100.0, 900.0, 900.0, 100.0], []]]
|
14
14
|
end
|
@@ -19,18 +19,18 @@ describe Prawn::Svg::Parser do
|
|
19
19
|
<line x1="1in" y1="1in" x2="9in" y2="9in" />
|
20
20
|
</svg>
|
21
21
|
SVG
|
22
|
-
|
22
|
+
|
23
23
|
document = Prawn::Svg::Document.new(svg, [2000, 2000], {})
|
24
24
|
Prawn::Svg::Parser.new(document).parse[-2][-1].should == [["line", [72.0, 720.0 - 72.0, 720.0 - 72.0, 72.0], []]]
|
25
25
|
end
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
describe :parse_element do
|
29
29
|
before(:each) do
|
30
30
|
@document = Prawn::Svg::Document.new("<svg></svg>", [100, 100], {})
|
31
31
|
@parser = Prawn::Svg::Parser.new(@document)
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
def mock_element(name, attributes = {})
|
35
35
|
e = mock
|
36
36
|
e.stub!(:name).and_return(name)
|
@@ -38,7 +38,7 @@ describe Prawn::Svg::Parser do
|
|
38
38
|
|
39
39
|
Prawn::Svg::Element.new(@document, e, [], {})
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
it "ignores tags it doesn't know about" do
|
43
43
|
calls = []
|
44
44
|
@parser.send :parse_element, mock_element("unknown")
|
@@ -46,7 +46,7 @@ describe Prawn::Svg::Parser do
|
|
46
46
|
@document.warnings.length.should == 1
|
47
47
|
@document.warnings.first.should include("Unknown tag")
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
it "ignores tags that don't have all required attributes set" do
|
51
51
|
calls = []
|
52
52
|
@parser.send :parse_element, mock_element("ellipse", "rx" => "1")
|
data/spec/lib/path_spec.rb
CHANGED
@@ -4,13 +4,13 @@ describe Prawn::Svg::Parser::Path do
|
|
4
4
|
before :each do
|
5
5
|
@path = Prawn::Svg::Parser::Path.new
|
6
6
|
end
|
7
|
-
|
8
|
-
describe "command parsing" do
|
7
|
+
|
8
|
+
describe "command parsing" do
|
9
9
|
it "correctly parses a valid path" do
|
10
10
|
calls = []
|
11
11
|
@path.stub!(:run_path_command) {|*args| calls << args}
|
12
12
|
@path.parse("A12.34 -56.78 89B4 5 C 6,7 T QX 0 Z")
|
13
|
-
|
13
|
+
|
14
14
|
calls.should == [
|
15
15
|
["A", [12.34, -56.78, 89]],
|
16
16
|
["B", [4, 5]],
|
@@ -19,19 +19,19 @@ describe Prawn::Svg::Parser::Path do
|
|
19
19
|
["Q", []],
|
20
20
|
["X", [0]],
|
21
21
|
["Z", []]
|
22
|
-
]
|
22
|
+
]
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
it "correctly parses an empty path" do
|
26
26
|
@path.should_not_receive(:run_path_command)
|
27
27
|
@path.parse("").should == []
|
28
28
|
@path.parse(" ").should == []
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
it "raises on invalid characters in the path" do
|
32
32
|
lambda {@path.parse("M 10 % 20")}.should raise_error(Prawn::Svg::Parser::Path::InvalidError)
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
it "raises on numerical data before a command letter" do
|
36
36
|
lambda {@path.parse("10 P")}.should raise_error(Prawn::Svg::Parser::Path::InvalidError)
|
37
37
|
end
|
data/spec/lib/svg_spec.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Prawn::Svg::Interface do
|
3
|
+
describe Prawn::Svg::Interface do
|
4
4
|
describe "sample file rendering" do
|
5
5
|
root = "#{File.dirname(__FILE__)}/../.."
|
6
6
|
files = Dir["#{root}/spec/sample_svg/*.svg"]
|
7
|
-
|
7
|
+
|
8
8
|
it "has at least 10 SVG sample files to test" do
|
9
9
|
files.length.should >= 10
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
files.each do |file|
|
13
13
|
it "renders the #{File.basename file} sample file without warnings or crashing" do
|
14
14
|
warnings = nil
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<svg width="120" height="120" viewPort="0 0 120 120" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
2
|
+
<defs>
|
3
|
+
<clipPath id="myClip">
|
4
|
+
<circle cx="30" cy="30" r="20"/>
|
5
|
+
<circle cx="70" cy="70" r="20"/>
|
6
|
+
</clipPath>
|
7
|
+
</defs>
|
8
|
+
|
9
|
+
<rect x="10" y="10" width="100" height="100" fill="red" clip-path="url(#myClip)"/>
|
10
|
+
</svg>
|
data/spec/sample_svg/image01.svg
CHANGED
@@ -4,7 +4,19 @@
|
|
4
4
|
<svg width="210mm" height="297mm" viewBox="0 0 1050 1485"
|
5
5
|
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
|
6
6
|
|
7
|
+
<g transform="translate(0 50)">
|
8
|
+
<text y="50" font-size="12pt">mid/mid</text>
|
9
|
+
<text y="200" font-size="12pt">mid/mid</text>
|
10
|
+
<text y="350" font-size="12pt">mid/mid</text>
|
11
|
+
<text y="550" font-size="12pt">min/min</text>
|
12
|
+
<text y="700" font-size="12pt">min/min</text>
|
13
|
+
<text y="900" font-size="12pt">max/max</text>
|
14
|
+
<text y="1050" font-size="12pt">max/max</text>
|
15
|
+
</g>
|
16
|
+
|
7
17
|
<g transform="translate(50 0)">
|
18
|
+
<text y="25" font-size="12pt">wide image, aspect ratio meet</text>
|
19
|
+
|
8
20
|
<rect y="50" width="100" height="100" fill="none" stroke="blue" stroke-width="2"/>
|
9
21
|
<image y="50" width="100" height="100" preserveAspectRatio="xMidYMid" xlink:href="http://files.myopera.com/baby2u/albums/423302/smiley-cool.jpg"></image>
|
10
22
|
|
@@ -28,6 +40,33 @@
|
|
28
40
|
</g>
|
29
41
|
|
30
42
|
<g transform="translate(250 0)">
|
43
|
+
<text y="25" font-size="12pt">wide image, aspect ratio slice</text>
|
44
|
+
|
45
|
+
<rect y="50" width="100" height="100" fill="none" stroke="blue" stroke-width="2"/>
|
46
|
+
<image y="50" width="100" height="100" preserveAspectRatio="xMidYMid slice" xlink:href="http://files.myopera.com/baby2u/albums/423302/smiley-cool.jpg"></image>
|
47
|
+
|
48
|
+
<rect y="200" width="150" height="100" fill="none" stroke="blue" stroke-width="2"/>
|
49
|
+
<image y="200" width="150" height="100" preserveAspectRatio="xMidYMid slice" xlink:href="http://files.myopera.com/baby2u/albums/423302/smiley-cool.jpg"></image>
|
50
|
+
|
51
|
+
<rect y="350" width="100" height="150" fill="none" stroke="blue" stroke-width="2"/>
|
52
|
+
<image y="350" width="100" height="150" preserveAspectRatio="xMidYMid slice" xlink:href="http://files.myopera.com/baby2u/albums/423302/smiley-cool.jpg"></image>
|
53
|
+
|
54
|
+
<rect y="550" width="150" height="100" fill="none" stroke="blue" stroke-width="2"/>
|
55
|
+
<image y="550" width="150" height="100" preserveAspectRatio="xMinYMin slice" xlink:href="http://files.myopera.com/baby2u/albums/423302/smiley-cool.jpg"></image>
|
56
|
+
|
57
|
+
<rect y="700" width="100" height="150" fill="none" stroke="blue" stroke-width="2"/>
|
58
|
+
<image y="700" width="100" height="150" preserveAspectRatio="xMinYMin slice" xlink:href="http://files.myopera.com/baby2u/albums/423302/smiley-cool.jpg"></image>
|
59
|
+
|
60
|
+
<rect y="900" width="150" height="100" fill="none" stroke="blue" stroke-width="2"/>
|
61
|
+
<image y="900" width="150" height="100" preserveAspectRatio="xMaxYMax slice" xlink:href="http://files.myopera.com/baby2u/albums/423302/smiley-cool.jpg"></image>
|
62
|
+
|
63
|
+
<rect y="1050" width="100" height="150" fill="none" stroke="blue" stroke-width="2"/>
|
64
|
+
<image y="1050" width="100" height="150" preserveAspectRatio="xMaxYMax slice" xlink:href="http://files.myopera.com/baby2u/albums/423302/smiley-cool.jpg"></image>
|
65
|
+
</g>
|
66
|
+
|
67
|
+
<g transform="translate(450 0)">
|
68
|
+
<text y="25" font-size="12pt">long image, aspect ratio meet</text>
|
69
|
+
|
31
70
|
<rect y="50" width="100" height="100" fill="none" stroke="blue" stroke-width="2"/>
|
32
71
|
<image y="50" width="100" height="100" xlink:href="http://imalbum.aufeminin.com/album/D20090809/583007_KWWH1ZQ8FU5ATJXSR84C5MRBUWW33L_989174388_H143954_L.jpg"></image>
|
33
72
|
|
@@ -50,7 +89,34 @@
|
|
50
89
|
<image y="1050" width="100" height="200" preserveAspectRatio="xMaxYMax" xlink:href="http://imalbum.aufeminin.com/album/D20090809/583007_KWWH1ZQ8FU5ATJXSR84C5MRBUWW33L_989174388_H143954_L.jpg"></image>
|
51
90
|
</g>
|
52
91
|
|
53
|
-
<g transform="translate(
|
92
|
+
<g transform="translate(650 0)">
|
93
|
+
<text y="25" font-size="12pt">long image, aspect ratio slice</text>
|
94
|
+
|
95
|
+
<rect y="50" width="100" height="100" fill="none" stroke="blue" stroke-width="2"/>
|
96
|
+
<image y="50" width="100" height="100" preserveAspectRatio="xMidYMid slice" xlink:href="http://imalbum.aufeminin.com/album/D20090809/583007_KWWH1ZQ8FU5ATJXSR84C5MRBUWW33L_989174388_H143954_L.jpg"></image>
|
97
|
+
|
98
|
+
<rect y="200" width="150" height="100" fill="none" stroke="blue" stroke-width="2"/>
|
99
|
+
<image y="200" width="150" height="100" preserveAspectRatio="xMidYMid slice" xlink:href="http://imalbum.aufeminin.com/album/D20090809/583007_KWWH1ZQ8FU5ATJXSR84C5MRBUWW33L_989174388_H143954_L.jpg"></image>
|
100
|
+
|
101
|
+
<rect y="350" width="100" height="200" fill="none" stroke="blue" stroke-width="2"/>
|
102
|
+
<image y="350" width="100" height="200" preserveAspectRatio="xMidYMid slice" xlink:href="http://imalbum.aufeminin.com/album/D20090809/583007_KWWH1ZQ8FU5ATJXSR84C5MRBUWW33L_989174388_H143954_L.jpg"></image>
|
103
|
+
|
104
|
+
<rect y="550" width="150" height="100" fill="none" stroke="blue" stroke-width="2"/>
|
105
|
+
<image y="550" width="150" height="100" preserveAspectRatio="xMinYMin slice" xlink:href="http://imalbum.aufeminin.com/album/D20090809/583007_KWWH1ZQ8FU5ATJXSR84C5MRBUWW33L_989174388_H143954_L.jpg"></image>
|
106
|
+
|
107
|
+
<rect y="700" width="100" height="200" fill="none" stroke="blue" stroke-width="2"/>
|
108
|
+
<image y="700" width="100" height="200" preserveAspectRatio="xMinYMin slice" xlink:href="http://imalbum.aufeminin.com/album/D20090809/583007_KWWH1ZQ8FU5ATJXSR84C5MRBUWW33L_989174388_H143954_L.jpg"></image>
|
109
|
+
|
110
|
+
<rect y="900" width="150" height="100" fill="none" stroke="blue" stroke-width="2"/>
|
111
|
+
<image y="900" width="150" height="100" preserveAspectRatio="xMaxYMax slice" xlink:href="http://imalbum.aufeminin.com/album/D20090809/583007_KWWH1ZQ8FU5ATJXSR84C5MRBUWW33L_989174388_H143954_L.jpg"></image>
|
112
|
+
|
113
|
+
<rect y="1050" width="100" height="200" fill="none" stroke="blue" stroke-width="2"/>
|
114
|
+
<image y="1050" width="100" height="200" preserveAspectRatio="xMaxYMax slice" xlink:href="http://imalbum.aufeminin.com/album/D20090809/583007_KWWH1ZQ8FU5ATJXSR84C5MRBUWW33L_989174388_H143954_L.jpg"></image>
|
115
|
+
</g>
|
116
|
+
|
117
|
+
<g transform="translate(850 0)">
|
118
|
+
<text y="25" font-size="12pt">aspect ratio preservation disabled</text>
|
119
|
+
|
54
120
|
<rect y="50" width="100" height="100" fill="none" stroke="blue" stroke-width="2"/>
|
55
121
|
<image y="50" width="100" height="100" preserveAspectRatio="none" xlink:href="http://imalbum.aufeminin.com/album/D20090809/583007_KWWH1ZQ8FU5ATJXSR84C5MRBUWW33L_989174388_H143954_L.jpg"></image>
|
56
122
|
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<?xml version="1.0" standalone="no"?>
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
3
|
+
<svg width="10cm" height="3cm" viewBox="0 0 1000 300" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
4
|
+
<g font-family="Verdana" font-size="24">
|
5
|
+
<text x="200" y="150" fill="blue">
|
6
|
+
More & more people say "Less is < and
|
7
|
+
more is >."
|
8
|
+
</text>
|
9
|
+
</g>
|
10
|
+
<rect x="1" y="1" width="998" height="298" fill="none" stroke="blue" stroke-width="2" />
|
11
|
+
</svg>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prawn-svg
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.12.0.
|
4
|
+
version: 0.12.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-04-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: prawn
|
@@ -93,6 +93,7 @@ files:
|
|
93
93
|
- spec/sample_output/directory
|
94
94
|
- spec/sample_svg/arcs01.svg
|
95
95
|
- spec/sample_svg/circle01.svg
|
96
|
+
- spec/sample_svg/clip_path.svg
|
96
97
|
- spec/sample_svg/close_path.svg
|
97
98
|
- spec/sample_svg/cubic01.svg
|
98
99
|
- spec/sample_svg/cubic01a.svg
|
@@ -112,6 +113,7 @@ files:
|
|
112
113
|
- spec/sample_svg/rect02.svg
|
113
114
|
- spec/sample_svg/rotate_scale.svg
|
114
115
|
- spec/sample_svg/scruffy_graph.svg
|
116
|
+
- spec/sample_svg/text_entities.svg
|
115
117
|
- spec/sample_svg/triangle01.svg
|
116
118
|
- spec/sample_svg/tspan01.svg
|
117
119
|
- spec/sample_svg/tspan02.svg
|
@@ -154,6 +156,7 @@ test_files:
|
|
154
156
|
- spec/sample_output/directory
|
155
157
|
- spec/sample_svg/arcs01.svg
|
156
158
|
- spec/sample_svg/circle01.svg
|
159
|
+
- spec/sample_svg/clip_path.svg
|
157
160
|
- spec/sample_svg/close_path.svg
|
158
161
|
- spec/sample_svg/cubic01.svg
|
159
162
|
- spec/sample_svg/cubic01a.svg
|
@@ -173,6 +176,7 @@ test_files:
|
|
173
176
|
- spec/sample_svg/rect02.svg
|
174
177
|
- spec/sample_svg/rotate_scale.svg
|
175
178
|
- spec/sample_svg/scruffy_graph.svg
|
179
|
+
- spec/sample_svg/text_entities.svg
|
176
180
|
- spec/sample_svg/triangle01.svg
|
177
181
|
- spec/sample_svg/tspan01.svg
|
178
182
|
- spec/sample_svg/tspan02.svg
|