prawn-svg 0.12.0.3 → 0.12.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|