prawn-svg 0.27.1 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.travis.yml +6 -4
- data/LICENSE +1 -1
- data/README.md +23 -9
- data/lib/prawn-svg.rb +7 -1
- data/lib/prawn/svg/attributes/opacity.rb +4 -4
- data/lib/prawn/svg/attributes/transform.rb +2 -44
- data/lib/prawn/svg/calculators/document_sizing.rb +2 -2
- data/lib/prawn/svg/{css.rb → css/font_family_parser.rb} +3 -4
- data/lib/prawn/svg/css/selector_parser.rb +174 -0
- data/lib/prawn/svg/css/stylesheets.rb +146 -0
- data/lib/prawn/svg/document.rb +3 -15
- data/lib/prawn/svg/elements.rb +4 -2
- data/lib/prawn/svg/elements/base.rb +26 -23
- data/lib/prawn/svg/elements/clip_path.rb +12 -0
- data/lib/prawn/svg/elements/container.rb +1 -3
- data/lib/prawn/svg/elements/gradient.rb +83 -25
- data/lib/prawn/svg/elements/image.rb +2 -2
- data/lib/prawn/svg/elements/path.rb +42 -29
- data/lib/prawn/svg/elements/root.rb +4 -1
- data/lib/prawn/svg/elements/text.rb +1 -1
- data/lib/prawn/svg/elements/text_component.rb +63 -14
- data/lib/prawn/svg/elements/use.rb +23 -7
- data/lib/prawn/svg/extensions/additional_gradient_transforms.rb +23 -0
- data/lib/prawn/svg/font_registry.rb +4 -3
- data/lib/prawn/svg/interface.rb +26 -2
- data/lib/prawn/svg/loaders/data.rb +1 -1
- data/lib/prawn/svg/loaders/file.rb +4 -2
- data/lib/prawn/svg/properties.rb +2 -0
- data/lib/prawn/svg/state.rb +6 -3
- data/lib/prawn/svg/transform_parser.rb +72 -0
- data/lib/prawn/svg/version.rb +1 -1
- data/prawn-svg.gemspec +3 -4
- data/spec/prawn/svg/attributes/opacity_spec.rb +85 -0
- data/spec/prawn/svg/attributes/transform_spec.rb +30 -35
- data/spec/prawn/svg/calculators/document_sizing_spec.rb +4 -4
- data/spec/prawn/svg/{css_spec.rb → css/font_family_parser_spec.rb} +3 -3
- data/spec/prawn/svg/css/selector_parser_spec.rb +33 -0
- data/spec/prawn/svg/css/stylesheets_spec.rb +142 -0
- data/spec/prawn/svg/document_spec.rb +0 -33
- data/spec/prawn/svg/elements/base_spec.rb +58 -2
- data/spec/prawn/svg/elements/gradient_spec.rb +79 -4
- data/spec/prawn/svg/elements/path_spec.rb +29 -17
- data/spec/prawn/svg/elements/text_spec.rb +74 -16
- data/spec/prawn/svg/font_registry_spec.rb +30 -0
- data/spec/prawn/svg/interface_spec.rb +33 -1
- data/spec/prawn/svg/loaders/data_spec.rb +8 -0
- data/spec/prawn/svg/transform_parser_spec.rb +94 -0
- data/spec/sample_output/{directory → .keep} +0 -0
- data/spec/sample_svg/double_opacity.svg +6 -0
- data/spec/sample_svg/gradient_transform.svg +19 -0
- data/spec/sample_svg/links.svg +18 -0
- data/spec/sample_svg/radgrad01-bounding.svg +26 -0
- data/spec/sample_svg/radgrad01.svg +26 -0
- data/spec/sample_svg/svg_fill.svg +5 -0
- data/spec/sample_svg/text-decoration.svg +4 -0
- data/spec/sample_svg/text_stroke.svg +41 -0
- data/spec/sample_svg/transform.svg +20 -0
- data/spec/sample_svg/use_disordered.svg +17 -0
- data/spec/sample_svg/warning-radioactive.svg +98 -0
- data/spec/spec_helper.rb +2 -2
- metadata +137 -15
@@ -8,7 +8,10 @@ class Prawn::SVG::Elements::Root < Prawn::SVG::Elements::Base
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def apply
|
11
|
-
|
11
|
+
if [nil, 'inherit', 'none', 'currentColor'].include?(properties.fill)
|
12
|
+
add_call 'fill_color', '000000'
|
13
|
+
end
|
14
|
+
|
12
15
|
add_call 'transformation_matrix', @document.sizing.x_scale, 0, 0, @document.sizing.y_scale, 0, 0
|
13
16
|
add_call 'transformation_matrix', 1, 0, 0, 1, -@document.sizing.x_offset, @document.sizing.y_offset
|
14
17
|
end
|
@@ -7,7 +7,7 @@ class Prawn::SVG::Elements::Text < Prawn::SVG::Elements::DepthFirstBase
|
|
7
7
|
# of the element, delegating it to our root text component.
|
8
8
|
|
9
9
|
def parse_step
|
10
|
-
state.text = Prawn::SVG::Elements::TextComponent::
|
10
|
+
state.text = Prawn::SVG::Elements::TextComponent::TextState.new
|
11
11
|
|
12
12
|
@text_root = Prawn::SVG::Elements::TextComponent.new(document, source, nil, state.dup)
|
13
13
|
@text_root.parse_step
|
@@ -2,14 +2,22 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
2
2
|
attr_reader :commands
|
3
3
|
|
4
4
|
Printable = Struct.new(:element, :text, :leading_space?, :trailing_space?)
|
5
|
-
|
5
|
+
TextState = Struct.new(:parent, :x, :y, :dx, :dy, :rotation, :spacing, :mode, :text_length, :length_adjust)
|
6
6
|
|
7
7
|
def parse
|
8
|
-
state.
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
state.text.
|
8
|
+
if state.inside_clip_path
|
9
|
+
raise SkipElementError, "<text> elements are not supported in clip paths"
|
10
|
+
end
|
11
|
+
|
12
|
+
state.text.x = (attributes['x'] || "").split(COMMA_WSP_REGEXP).collect { |n| x(n) }
|
13
|
+
state.text.y = (attributes['y'] || "").split(COMMA_WSP_REGEXP).collect { |n| y(n) }
|
14
|
+
state.text.dx = (attributes['dx'] || "").split(COMMA_WSP_REGEXP).collect { |n| x_pixels(n) }
|
15
|
+
state.text.dy = (attributes['dy'] || "").split(COMMA_WSP_REGEXP).collect { |n| y_pixels(n) }
|
16
|
+
state.text.rotation = (attributes['rotate'] || "").split(COMMA_WSP_REGEXP).collect(&:to_f)
|
17
|
+
state.text.text_length = normalize_length(attributes['textLength'])
|
18
|
+
state.text.length_adjust = attributes['lengthAdjust']
|
19
|
+
state.text.spacing = calculate_character_spacing
|
20
|
+
state.text.mode = calculate_text_rendering_mode
|
13
21
|
|
14
22
|
@commands = []
|
15
23
|
|
@@ -38,13 +46,18 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
38
46
|
opts = {
|
39
47
|
size: computed_properties.numerical_font_size,
|
40
48
|
style: font && font.subfamily,
|
41
|
-
text_anchor: computed_properties.text_anchor
|
49
|
+
text_anchor: computed_properties.text_anchor,
|
42
50
|
}
|
43
51
|
|
44
|
-
|
45
|
-
spacing = spacing == 'normal' ? 0 : pixels(spacing)
|
52
|
+
opts[:decoration] = computed_properties.text_decoration unless computed_properties.text_decoration == 'none'
|
46
53
|
|
47
|
-
|
54
|
+
if state.text.parent
|
55
|
+
add_call_and_enter 'character_spacing', state.text.spacing unless state.text.spacing == state.text.parent.spacing
|
56
|
+
add_call_and_enter 'text_rendering_mode', state.text.mode unless state.text.mode == state.text.parent.mode
|
57
|
+
else
|
58
|
+
add_call_and_enter 'character_spacing', state.text.spacing unless state.text.spacing == 0
|
59
|
+
add_call_and_enter 'text_rendering_mode', state.text.mode unless state.text.mode == :fill
|
60
|
+
end
|
48
61
|
|
49
62
|
@commands.each do |command|
|
50
63
|
case command
|
@@ -59,8 +72,8 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
59
72
|
end
|
60
73
|
end
|
61
74
|
|
62
|
-
# It's possible there was no text to render. In that case, add a 'noop' so
|
63
|
-
#
|
75
|
+
# It's possible there was no text to render. In that case, add a 'noop' so character_spacing/text_rendering_mode
|
76
|
+
# don't blow up when they find they don't have a block to execute.
|
64
77
|
add_call 'noop' if calls.empty?
|
65
78
|
end
|
66
79
|
|
@@ -81,7 +94,7 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
81
94
|
|
82
95
|
def append_child(child)
|
83
96
|
new_state = state.dup
|
84
|
-
new_state.text =
|
97
|
+
new_state.text = TextState.new(state.text)
|
85
98
|
|
86
99
|
element = self.class.new(document, child, calls, new_state)
|
87
100
|
@commands << element
|
@@ -124,6 +137,14 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
124
137
|
opts.delete(:rotate)
|
125
138
|
end
|
126
139
|
|
140
|
+
if state.text.text_length
|
141
|
+
if state.text.length_adjust == 'spacingAndGlyphs'
|
142
|
+
opts[:stretch_to_width] = state.text.text_length
|
143
|
+
else
|
144
|
+
opts[:pad_to_width] = state.text.text_length
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
127
148
|
if remaining
|
128
149
|
add_call 'draw_text', text[0..0], opts.dup
|
129
150
|
text = text[1..-1]
|
@@ -164,7 +185,7 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
164
185
|
end
|
165
186
|
|
166
187
|
def find_referenced_element
|
167
|
-
href =
|
188
|
+
href = href_attribute
|
168
189
|
|
169
190
|
if href && href[0..0] == '#'
|
170
191
|
element = document.elements_by_id[href[1..-1]]
|
@@ -189,4 +210,32 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
189
210
|
def apply_font(font)
|
190
211
|
add_call 'font', font.name, style: font.subfamily
|
191
212
|
end
|
213
|
+
|
214
|
+
def calculate_text_rendering_mode
|
215
|
+
fill = computed_properties.fill != 'none'
|
216
|
+
stroke = computed_properties.stroke != 'none'
|
217
|
+
|
218
|
+
if fill && stroke
|
219
|
+
:fill_stroke
|
220
|
+
elsif fill
|
221
|
+
:fill
|
222
|
+
elsif stroke
|
223
|
+
:stroke
|
224
|
+
else
|
225
|
+
:invisible
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def calculate_character_spacing
|
230
|
+
spacing = computed_properties.letter_spacing
|
231
|
+
spacing == 'normal' ? 0 : pixels(spacing)
|
232
|
+
end
|
233
|
+
|
234
|
+
# overridden, we don't want to call fill/stroke as draw_text does this for us
|
235
|
+
def apply_drawing_call
|
236
|
+
end
|
237
|
+
|
238
|
+
def normalize_length(length)
|
239
|
+
x_pixels(length) if length && length.match(/\d/)
|
240
|
+
end
|
192
241
|
end
|
@@ -1,19 +1,35 @@
|
|
1
1
|
class Prawn::SVG::Elements::Use < Prawn::SVG::Elements::Base
|
2
|
-
attr_reader :
|
2
|
+
attr_reader :referenced_element_class
|
3
|
+
attr_reader :referenced_element_source
|
3
4
|
|
4
5
|
def parse
|
5
|
-
|
6
|
-
|
7
|
-
|
6
|
+
href = href_attribute
|
7
|
+
if href.nil?
|
8
|
+
raise SkipElementError, "use tag must have an href or xlink:href"
|
9
|
+
end
|
8
10
|
|
9
11
|
if href[0..0] != '#'
|
10
12
|
raise SkipElementError, "use tag has an href that is not a reference to an id; this is not supported"
|
11
13
|
end
|
12
14
|
|
13
15
|
id = href[1..-1]
|
14
|
-
|
16
|
+
referenced_element = @document.elements_by_id[id]
|
17
|
+
|
18
|
+
if referenced_element
|
19
|
+
@referenced_element_class = referenced_element.class
|
20
|
+
@referenced_element_source = referenced_element.source
|
21
|
+
else
|
22
|
+
# Perhaps the element is defined further down in the document. This is not recommended but still valid SVG,
|
23
|
+
# so we'll support it with an exception case that's not particularly performant.
|
24
|
+
raw_element = REXML::XPath.match(@document.root, %(//*[@id="#{id.gsub('"', '\"')}"])).first
|
25
|
+
|
26
|
+
if raw_element
|
27
|
+
@referenced_element_class = Prawn::SVG::Elements::TAG_CLASS_MAPPING[raw_element.name.to_sym]
|
28
|
+
@referenced_element_source = raw_element
|
29
|
+
end
|
30
|
+
end
|
15
31
|
|
16
|
-
if
|
32
|
+
if referenced_element_class.nil?
|
17
33
|
raise SkipElementError, "no tag with ID '#{id}' was found, referenced by use tag"
|
18
34
|
end
|
19
35
|
|
@@ -36,7 +52,7 @@ class Prawn::SVG::Elements::Use < Prawn::SVG::Elements::Base
|
|
36
52
|
def process_child_elements
|
37
53
|
add_call "save"
|
38
54
|
|
39
|
-
child =
|
55
|
+
child = referenced_element_class.new(document, referenced_element_source, calls, state.dup)
|
40
56
|
child.process
|
41
57
|
|
42
58
|
add_call "restore"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Prawn::SVG::Extensions
|
2
|
+
module AdditionalGradientTransforms
|
3
|
+
def gradient_coordinates(gradient)
|
4
|
+
# As of Prawn 2.2.0, apply_transformations is used as purely a boolean.
|
5
|
+
#
|
6
|
+
# Here we're using it to optionally pass in a 6-tuple transformation matrix that gets applied to the
|
7
|
+
# gradient. This should be added to Prawn properly, and then this monkey patch will not be necessary.
|
8
|
+
|
9
|
+
if gradient.apply_transformations.is_a?(Array)
|
10
|
+
x1, y1, x2, y2, transformation = super
|
11
|
+
a, b, c, d, e, f = transformation
|
12
|
+
na, nb, nc, nd, ne, nf = gradient.apply_transformations
|
13
|
+
|
14
|
+
matrix = Matrix[[a, c, e], [b, d, f], [0, 0, 1]] * Matrix[[na, nc, ne], [nb, nd, nf], [0, 0, 1]]
|
15
|
+
new_transformation = matrix.to_a[0..1].transpose.flatten
|
16
|
+
|
17
|
+
[x1, y1, x2, y2, new_transformation]
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -24,7 +24,7 @@ class Prawn::SVG::FontRegistry
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def load(family, weight = nil, style = nil)
|
27
|
-
Prawn::SVG::CSS.
|
27
|
+
Prawn::SVG::CSS::FontFamilyParser.parse(family).detect do |name|
|
28
28
|
name = name.gsub(/\s{2,}/, ' ').downcase
|
29
29
|
|
30
30
|
font = Prawn::SVG::Font.new(name, weight, style, font_registry: self)
|
@@ -37,8 +37,9 @@ class Prawn::SVG::FontRegistry
|
|
37
37
|
def merge_external_fonts
|
38
38
|
if @font_case_mapping.nil?
|
39
39
|
self.class.load_external_fonts unless self.class.external_font_families
|
40
|
-
@font_families.merge!(self.class.external_font_families)
|
41
|
-
|
40
|
+
@font_families.merge!(self.class.external_font_families) do |key, v1, v2|
|
41
|
+
v1
|
42
|
+
end
|
42
43
|
@font_case_mapping = @font_families.keys.each.with_object({}) do |key, result|
|
43
44
|
result[key.downcase] = key
|
44
45
|
end
|
data/lib/prawn/svg/interface.rb
CHANGED
@@ -119,7 +119,7 @@ module Prawn
|
|
119
119
|
|
120
120
|
if skip
|
121
121
|
# the call has been overridden
|
122
|
-
elsif children.empty?
|
122
|
+
elsif children.empty? && call != 'transparent' # some prawn calls complain if they aren't supplied a block
|
123
123
|
prawn.send(call, *arguments)
|
124
124
|
else
|
125
125
|
prawn.send(call, *arguments, &proc_creator(prawn, children))
|
@@ -148,6 +148,19 @@ module Prawn
|
|
148
148
|
|
149
149
|
width = prawn.width_of(text, options.merge(kerning: true))
|
150
150
|
|
151
|
+
if stretch_to_width = options.delete(:stretch_to_width)
|
152
|
+
factor = stretch_to_width.to_f * 100 / width.to_f
|
153
|
+
prawn.add_content "#{factor} Tz"
|
154
|
+
width = stretch_to_width.to_f
|
155
|
+
end
|
156
|
+
|
157
|
+
if pad_to_width = options.delete(:pad_to_width)
|
158
|
+
padding_required = pad_to_width.to_f - width.to_f
|
159
|
+
padding_per_character = padding_required / text.length.to_f
|
160
|
+
prawn.add_content "#{padding_per_character} Tc"
|
161
|
+
width = pad_to_width.to_f
|
162
|
+
end
|
163
|
+
|
151
164
|
case options.delete(:text_anchor)
|
152
165
|
when 'middle'
|
153
166
|
at[0] -= width / 2
|
@@ -159,6 +172,15 @@ module Prawn
|
|
159
172
|
@cursor = [at[0] + width, at[1]]
|
160
173
|
end
|
161
174
|
|
175
|
+
decoration = options.delete(:decoration)
|
176
|
+
if decoration == 'underline'
|
177
|
+
prawn.save_graphics_state do
|
178
|
+
prawn.line_width 1
|
179
|
+
prawn.line [at[0], at[1] - 1.25], [at[0] + width, at[1] - 1.25]
|
180
|
+
prawn.stroke
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
162
184
|
when 'transformation_matrix'
|
163
185
|
left = prawn.bounds.absolute_left
|
164
186
|
top = prawn.bounds.absolute_top
|
@@ -186,7 +208,9 @@ module Prawn
|
|
186
208
|
# prawn (as at 2.0.1 anyway) uses 'b' for its fill_and_stroke. 'b' is 'h' (closepath) + 'B', and we
|
187
209
|
# never want closepath to be automatically run as it stuffs up many drawing operations, such as dashes
|
188
210
|
# and line caps, and makes paths close that we didn't ask to be closed when fill is specified.
|
189
|
-
|
211
|
+
even_odd = arguments[0].is_a?(Hash) && arguments[0][:fill_rule] == :even_odd
|
212
|
+
content = even_odd ? 'B*' : 'B'
|
213
|
+
prawn.add_content content
|
190
214
|
|
191
215
|
when 'noop'
|
192
216
|
yield
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'addressable/uri'
|
2
|
+
|
1
3
|
#
|
2
4
|
# Load a file from disk.
|
3
5
|
#
|
@@ -56,7 +58,7 @@ module Prawn::SVG::Loaders
|
|
56
58
|
private
|
57
59
|
|
58
60
|
def load_file(path)
|
59
|
-
path = URI.
|
61
|
+
path = Addressable::URI.unencode(path)
|
60
62
|
path = build_absolute_and_expand_path(path)
|
61
63
|
assert_valid_path!(path)
|
62
64
|
assert_file_exists!(path)
|
@@ -85,7 +87,7 @@ module Prawn::SVG::Loaders
|
|
85
87
|
end
|
86
88
|
|
87
89
|
def assert_valid_file_uri!(uri)
|
88
|
-
|
90
|
+
unless uri.host.nil? || uri.host.empty?
|
89
91
|
raise Prawn::SVG::UrlLoader::Error, "prawn-svg does not suport file: URLs with a host. Your URL probably doesn't start with three slashes, and it should."
|
90
92
|
end
|
91
93
|
end
|
data/lib/prawn/svg/properties.rb
CHANGED
@@ -18,6 +18,7 @@ class Prawn::SVG::Properties
|
|
18
18
|
"display" => Config.new("inline", false, %w(inherit inline none), true),
|
19
19
|
"fill" => Config.new("black", true, %w(inherit none currentColor)),
|
20
20
|
"fill-opacity" => Config.new("1", true),
|
21
|
+
"fill-rule" => Config.new("nonzero", true, %w(inherit nonzero evenodd)),
|
21
22
|
"font-family" => Config.new("sans-serif", true),
|
22
23
|
"font-size" => Config.new("medium", true, %w(inherit xx-small x-small small medium large x-large xx-large larger smaller)),
|
23
24
|
"font-style" => Config.new("normal", true, %w(inherit normal italic oblique), true),
|
@@ -36,6 +37,7 @@ class Prawn::SVG::Properties
|
|
36
37
|
"stroke-opacity" => Config.new("1", true),
|
37
38
|
"stroke-width" => Config.new("1", true),
|
38
39
|
"text-anchor" => Config.new("start", true, %w(inherit start middle end), true),
|
40
|
+
'text-decoration' => Config.new('none', true, %w(inherit none underline), true),
|
39
41
|
}.freeze
|
40
42
|
|
41
43
|
PROPERTIES.each do |name, value|
|
data/lib/prawn/svg/state.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
class Prawn::SVG::State
|
2
|
-
attr_accessor :
|
3
|
-
:text, :preserve_space,
|
2
|
+
attr_accessor :text, :preserve_space,
|
4
3
|
:fill_opacity, :stroke_opacity, :stroke_width,
|
5
4
|
:computed_properties,
|
6
5
|
:viewport_sizing,
|
7
|
-
:inside_use
|
6
|
+
:inside_use, :inside_clip_path
|
8
7
|
|
9
8
|
def initialize
|
10
9
|
@stroke_width = 1
|
@@ -16,4 +15,8 @@ class Prawn::SVG::State
|
|
16
15
|
def initialize_dup(other)
|
17
16
|
@computed_properties = @computed_properties.dup
|
18
17
|
end
|
18
|
+
|
19
|
+
def disable_drawing
|
20
|
+
inside_clip_path
|
21
|
+
end
|
19
22
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Prawn::SVG::TransformParser
|
2
|
+
def parse_transform_attribute(transform)
|
3
|
+
matrix = Matrix.identity(3)
|
4
|
+
|
5
|
+
parse_css_method_calls(transform).each do |name, arguments|
|
6
|
+
case name
|
7
|
+
when 'translate'
|
8
|
+
x, y = arguments
|
9
|
+
matrix *= Matrix[[1, 0, x_pixels(x.to_f)], [0, 1, -y_pixels(y.to_f)], [0, 0, 1]]
|
10
|
+
|
11
|
+
when 'translateX'
|
12
|
+
x = arguments.first
|
13
|
+
matrix *= Matrix[[1, 0, x_pixels(x.to_f)], [0, 1, 0], [0, 0, 1]]
|
14
|
+
|
15
|
+
when 'translateY'
|
16
|
+
y = arguments.first
|
17
|
+
matrix *= Matrix[[1, 0, 0], [0, 1, -y_pixels(y.to_f)], [0, 0, 1]]
|
18
|
+
|
19
|
+
when 'rotate'
|
20
|
+
angle, x, y = arguments.collect { |a| a.to_f }
|
21
|
+
angle = angle * Math::PI / 180.0
|
22
|
+
|
23
|
+
case arguments.length
|
24
|
+
when 1
|
25
|
+
matrix *= Matrix[[Math.cos(angle), Math.sin(angle), 0], [-Math.sin(angle), Math.cos(angle), 0], [0, 0, 1]]
|
26
|
+
when 3
|
27
|
+
matrix *= Matrix[[1, 0, x_pixels(x.to_f)], [0, 1, -y_pixels(y.to_f)], [0, 0, 1]]
|
28
|
+
matrix *= Matrix[[Math.cos(angle), Math.sin(angle), 0], [-Math.sin(angle), Math.cos(angle), 0], [0, 0, 1]]
|
29
|
+
matrix *= Matrix[[1, 0, -x_pixels(x.to_f)], [0, 1, y_pixels(y.to_f)], [0, 0, 1]]
|
30
|
+
else
|
31
|
+
warnings << "transform 'rotate' must have either one or three arguments"
|
32
|
+
end
|
33
|
+
|
34
|
+
when 'scale'
|
35
|
+
x_scale = arguments[0].to_f
|
36
|
+
y_scale = (arguments[1] || x_scale).to_f
|
37
|
+
matrix *= Matrix[[x_scale, 0, 0], [0, y_scale, 0], [0, 0, 1]]
|
38
|
+
|
39
|
+
when 'skewX'
|
40
|
+
angle = arguments[0].to_f * Math::PI / 180.0
|
41
|
+
matrix *= Matrix[[1, -Math.tan(angle), 0], [0, 1, 0], [0, 0, 1]]
|
42
|
+
|
43
|
+
when 'skewY'
|
44
|
+
angle = arguments[0].to_f * Math::PI / 180.0
|
45
|
+
matrix *= Matrix[[1, 0, 0], [-Math.tan(angle), 1, 0], [0, 0, 1]]
|
46
|
+
|
47
|
+
when 'matrix'
|
48
|
+
if arguments.length != 6
|
49
|
+
warnings << "transform 'matrix' must have six arguments"
|
50
|
+
else
|
51
|
+
a, b, c, d, e, f = arguments.collect { |argument| argument.to_f }
|
52
|
+
matrix *= Matrix[[a, -c, e], [-b, d, -f], [0, 0, 1]]
|
53
|
+
end
|
54
|
+
|
55
|
+
else
|
56
|
+
warnings << "Unknown/unsupported transformation '#{name}'; ignoring"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
matrix.to_a[0..1].transpose.flatten
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def parse_css_method_calls(string)
|
66
|
+
string.scan(/\s*(\w+)\(([^)]+)\)\s*/).collect do |call|
|
67
|
+
name, argument_string = call
|
68
|
+
arguments = argument_string.strip.split(/\s*[,\s]\s*/)
|
69
|
+
[name, arguments]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/prawn/svg/version.rb
CHANGED