prawn-svg 0.29.1 → 0.32.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +18 -0
- data/LICENSE +1 -1
- data/README.md +10 -9
- data/lib/prawn/svg/attributes/opacity.rb +4 -4
- data/lib/prawn/svg/attributes/transform.rb +2 -44
- data/lib/prawn/svg/elements/base.rb +11 -5
- data/lib/prawn/svg/elements/call_duplicator.rb +1 -1
- 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_component.rb +21 -5
- data/lib/prawn/svg/elements/use.rb +23 -7
- data/lib/prawn/svg/elements.rb +2 -0
- data/lib/prawn/svg/extensions/additional_gradient_transforms.rb +23 -0
- data/lib/prawn/svg/interface.rb +38 -8
- data/lib/prawn/svg/loaders/data.rb +1 -1
- data/lib/prawn/svg/loaders/file.rb +3 -1
- data/lib/prawn/svg/properties.rb +1 -0
- data/lib/prawn/svg/transform_parser.rb +72 -0
- data/lib/prawn/svg/version.rb +1 -1
- data/lib/prawn-svg.rb +4 -0
- data/prawn-svg.gemspec +3 -2
- data/spec/integration_spec.rb +24 -24
- data/spec/prawn/svg/attributes/opacity_spec.rb +85 -0
- data/spec/prawn/svg/attributes/transform_spec.rb +30 -35
- data/spec/prawn/svg/css/stylesheets_spec.rb +1 -19
- data/spec/prawn/svg/elements/base_spec.rb +16 -16
- data/spec/prawn/svg/elements/gradient_spec.rb +79 -4
- data/spec/prawn/svg/elements/line_spec.rb +12 -12
- data/spec/prawn/svg/elements/marker_spec.rb +27 -27
- data/spec/prawn/svg/elements/path_spec.rb +29 -17
- data/spec/prawn/svg/elements/polygon_spec.rb +9 -9
- data/spec/prawn/svg/elements/polyline_spec.rb +7 -7
- data/spec/prawn/svg/elements/text_spec.rb +65 -50
- data/spec/prawn/svg/loaders/data_spec.rb +8 -0
- data/spec/prawn/svg/pathable_spec.rb +4 -4
- data/spec/prawn/svg/transform_parser_spec.rb +94 -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/transform.svg +20 -0
- data/spec/sample_svg/use_disordered.svg +17 -0
- data/spec/spec_helper.rb +2 -2
- metadata +48 -10
- data/.travis.yml +0 -8
@@ -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"
|
data/lib/prawn/svg/elements.rb
CHANGED
@@ -13,6 +13,7 @@ module Prawn::SVG::Elements
|
|
13
13
|
g: Prawn::SVG::Elements::Container,
|
14
14
|
symbol: Prawn::SVG::Elements::Container,
|
15
15
|
defs: Prawn::SVG::Elements::Container,
|
16
|
+
a: Prawn::SVG::Elements::Container,
|
16
17
|
clipPath: Prawn::SVG::Elements::ClipPath,
|
17
18
|
switch: Prawn::SVG::Elements::Container,
|
18
19
|
svg: Prawn::SVG::Elements::Viewport,
|
@@ -27,6 +28,7 @@ module Prawn::SVG::Elements
|
|
27
28
|
use: Prawn::SVG::Elements::Use,
|
28
29
|
image: Prawn::SVG::Elements::Image,
|
29
30
|
linearGradient: Prawn::SVG::Elements::Gradient,
|
31
|
+
radialGradient: Prawn::SVG::Elements::Gradient,
|
30
32
|
marker: Prawn::SVG::Elements::Marker,
|
31
33
|
style: Prawn::SVG::Elements::Ignored, # because it is pre-parsed by Document
|
32
34
|
title: Prawn::SVG::Elements::Ignored,
|
@@ -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
|
data/lib/prawn/svg/interface.rb
CHANGED
@@ -109,32 +109,40 @@ module Prawn
|
|
109
109
|
end
|
110
110
|
|
111
111
|
def issue_prawn_command(prawn, calls)
|
112
|
-
calls.each do |call, arguments, children|
|
112
|
+
calls.each do |call, arguments, kwarguments, children|
|
113
113
|
skip = false
|
114
114
|
|
115
|
-
rewrite_call_arguments(prawn, call, arguments) do
|
115
|
+
rewrite_call_arguments(prawn, call, arguments, kwarguments) do
|
116
116
|
issue_prawn_command(prawn, children) if children.any?
|
117
117
|
skip = true
|
118
118
|
end
|
119
119
|
|
120
120
|
if skip
|
121
121
|
# the call has been overridden
|
122
|
-
elsif children.empty?
|
123
|
-
|
122
|
+
elsif children.empty? && call != 'transparent' # some prawn calls complain if they aren't supplied a block
|
123
|
+
if RUBY_VERSION >= '2.7' || !kwarguments.empty?
|
124
|
+
prawn.send(call, *arguments, **kwarguments)
|
125
|
+
else
|
126
|
+
prawn.send(call, *arguments)
|
127
|
+
end
|
124
128
|
else
|
125
|
-
|
129
|
+
if RUBY_VERSION >= '2.7' || !kwarguments.empty?
|
130
|
+
prawn.send(call, *arguments, **kwarguments, &proc_creator(prawn, children))
|
131
|
+
else
|
132
|
+
prawn.send(call, *arguments, &proc_creator(prawn, children))
|
133
|
+
end
|
126
134
|
end
|
127
135
|
end
|
128
136
|
end
|
129
137
|
|
130
|
-
def rewrite_call_arguments(prawn, call, arguments)
|
138
|
+
def rewrite_call_arguments(prawn, call, arguments, kwarguments)
|
131
139
|
case call
|
132
140
|
when 'text_group'
|
133
141
|
@cursor = [0, document.sizing.output_height]
|
134
142
|
yield
|
135
143
|
|
136
144
|
when 'draw_text'
|
137
|
-
text, options = arguments
|
145
|
+
text, options = arguments.first, kwarguments
|
138
146
|
|
139
147
|
at = options.fetch(:at)
|
140
148
|
|
@@ -148,6 +156,19 @@ module Prawn
|
|
148
156
|
|
149
157
|
width = prawn.width_of(text, options.merge(kerning: true))
|
150
158
|
|
159
|
+
if stretch_to_width = options.delete(:stretch_to_width)
|
160
|
+
factor = stretch_to_width.to_f * 100 / width.to_f
|
161
|
+
prawn.add_content "#{factor} Tz"
|
162
|
+
width = stretch_to_width.to_f
|
163
|
+
end
|
164
|
+
|
165
|
+
if pad_to_width = options.delete(:pad_to_width)
|
166
|
+
padding_required = pad_to_width.to_f - width.to_f
|
167
|
+
padding_per_character = padding_required / text.length.to_f
|
168
|
+
prawn.add_content "#{padding_per_character} Tc"
|
169
|
+
width = pad_to_width.to_f
|
170
|
+
end
|
171
|
+
|
151
172
|
case options.delete(:text_anchor)
|
152
173
|
when 'middle'
|
153
174
|
at[0] -= width / 2
|
@@ -159,6 +180,15 @@ module Prawn
|
|
159
180
|
@cursor = [at[0] + width, at[1]]
|
160
181
|
end
|
161
182
|
|
183
|
+
decoration = options.delete(:decoration)
|
184
|
+
if decoration == 'underline'
|
185
|
+
prawn.save_graphics_state do
|
186
|
+
prawn.line_width 1
|
187
|
+
prawn.line [at[0], at[1] - 1.25], [at[0] + width, at[1] - 1.25]
|
188
|
+
prawn.stroke
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
162
192
|
when 'transformation_matrix'
|
163
193
|
left = prawn.bounds.absolute_left
|
164
194
|
top = prawn.bounds.absolute_top
|
@@ -186,7 +216,7 @@ module Prawn
|
|
186
216
|
# prawn (as at 2.0.1 anyway) uses 'b' for its fill_and_stroke. 'b' is 'h' (closepath) + 'B', and we
|
187
217
|
# never want closepath to be automatically run as it stuffs up many drawing operations, such as dashes
|
188
218
|
# and line caps, and makes paths close that we didn't ask to be closed when fill is specified.
|
189
|
-
even_odd =
|
219
|
+
even_odd = kwarguments[:fill_rule] == :even_odd
|
190
220
|
content = even_odd ? 'B*' : 'B'
|
191
221
|
prawn.add_content content
|
192
222
|
|
@@ -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)
|
data/lib/prawn/svg/properties.rb
CHANGED
@@ -37,6 +37,7 @@ class Prawn::SVG::Properties
|
|
37
37
|
"stroke-opacity" => Config.new("1", true),
|
38
38
|
"stroke-width" => Config.new("1", true),
|
39
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),
|
40
41
|
}.freeze
|
41
42
|
|
42
43
|
PROPERTIES.each do |name, value|
|
@@ -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
data/lib/prawn-svg.rb
CHANGED
@@ -10,6 +10,7 @@ require 'prawn/svg/calculators/arc_to_bezier_curve'
|
|
10
10
|
require 'prawn/svg/calculators/aspect_ratio'
|
11
11
|
require 'prawn/svg/calculators/document_sizing'
|
12
12
|
require 'prawn/svg/calculators/pixels'
|
13
|
+
require 'prawn/svg/transform_parser'
|
13
14
|
require 'prawn/svg/url_loader'
|
14
15
|
require 'prawn/svg/loaders/data'
|
15
16
|
require 'prawn/svg/loaders/file'
|
@@ -29,6 +30,9 @@ require 'prawn/svg/font'
|
|
29
30
|
require 'prawn/svg/document'
|
30
31
|
require 'prawn/svg/state'
|
31
32
|
|
33
|
+
require 'prawn/svg/extensions/additional_gradient_transforms'
|
34
|
+
Prawn::Document.prepend Prawn::SVG::Extensions::AdditionalGradientTransforms
|
35
|
+
|
32
36
|
module Prawn
|
33
37
|
Svg = SVG # backwards compatibility
|
34
38
|
end
|
data/prawn-svg.gemspec
CHANGED
@@ -17,10 +17,11 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.name = "prawn-svg"
|
18
18
|
gem.require_paths = ["lib"]
|
19
19
|
|
20
|
-
gem.required_ruby_version = '>= 2.
|
20
|
+
gem.required_ruby_version = '>= 2.3.0'
|
21
21
|
|
22
22
|
gem.add_runtime_dependency "prawn", ">= 0.11.1", "< 3"
|
23
23
|
gem.add_runtime_dependency "css_parser", "~> 1.6"
|
24
|
+
gem.add_runtime_dependency "rexml", "~> 3.2"
|
24
25
|
gem.add_development_dependency "rspec", "~> 3.0"
|
25
|
-
gem.add_development_dependency "rake", "~>
|
26
|
+
gem.add_development_dependency "rake", "~> 13.0"
|
26
27
|
end
|
data/spec/integration_spec.rb
CHANGED
@@ -28,34 +28,34 @@ describe "Integration test" do
|
|
28
28
|
element.process
|
29
29
|
|
30
30
|
expect(element.calls).to eq [
|
31
|
-
["fill_color", ["000000"], []],
|
32
|
-
["transformation_matrix", [1, 0, 0, 1, 0, 0], []],
|
33
|
-
["transformation_matrix", [1, 0, 0, 1, 0, 0], []],
|
34
|
-
["save", [], []], ["restore", [], []],
|
35
|
-
["save", [], []],
|
36
|
-
["fill_color", ["0000ff"], []],
|
37
|
-
["fill", [], [
|
38
|
-
["rectangle", [[0.0, 200.0], 10.0, 10.0], []]
|
31
|
+
["fill_color", ["000000"], {}, []],
|
32
|
+
["transformation_matrix", [1, 0, 0, 1, 0, 0], {}, []],
|
33
|
+
["transformation_matrix", [1, 0, 0, 1, 0, 0], {}, []],
|
34
|
+
["save", [], {}, []], ["restore", [], {}, []],
|
35
|
+
["save", [], {}, []],
|
36
|
+
["fill_color", ["0000ff"], {}, []],
|
37
|
+
["fill", [], {}, [
|
38
|
+
["rectangle", [[0.0, 200.0], 10.0, 10.0], {}, []]
|
39
39
|
]],
|
40
|
-
["restore", [], []],
|
41
|
-
["save", [], []],
|
42
|
-
["fill_color", ["008000"], []],
|
43
|
-
["fill", [], [
|
44
|
-
["rectangle", [[10.0, 200.0], 10.0, 10.0], []]
|
40
|
+
["restore", [], {}, []],
|
41
|
+
["save", [], {}, []],
|
42
|
+
["fill_color", ["008000"], {}, []],
|
43
|
+
["fill", [], {}, [
|
44
|
+
["rectangle", [[10.0, 200.0], 10.0, 10.0], {}, []]
|
45
45
|
]],
|
46
|
-
["restore", [], []],
|
47
|
-
["save", [], []],
|
48
|
-
["fill_color", ["ff0000"], []],
|
49
|
-
["fill", [], [
|
50
|
-
["rectangle", [[20.0, 200.0], 10.0, 10.0], []]
|
46
|
+
["restore", [], {}, []],
|
47
|
+
["save", [], {}, []],
|
48
|
+
["fill_color", ["ff0000"], {}, []],
|
49
|
+
["fill", [], {}, [
|
50
|
+
["rectangle", [[20.0, 200.0], 10.0, 10.0], {}, []]
|
51
51
|
]],
|
52
|
-
["restore", [], []],
|
53
|
-
["save", [], []],
|
54
|
-
["fill_color", ["ffff00"], []],
|
55
|
-
["fill", [], [
|
56
|
-
["rectangle", [[30.0, 200.0], 10.0, 10.0], []]
|
52
|
+
["restore", [], {}, []],
|
53
|
+
["save", [], {}, []],
|
54
|
+
["fill_color", ["ffff00"], {}, []],
|
55
|
+
["fill", [], {}, [
|
56
|
+
["rectangle", [[30.0, 200.0], 10.0, 10.0], {}, []]
|
57
57
|
]],
|
58
|
-
["restore", [], []]
|
58
|
+
["restore", [], {}, []]
|
59
59
|
]
|
60
60
|
end
|
61
61
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Prawn::SVG::Attributes::Opacity do
|
4
|
+
class OpacityTestElement
|
5
|
+
include Prawn::SVG::Attributes::Opacity
|
6
|
+
|
7
|
+
attr_accessor :properties, :state
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@properties = ::Prawn::SVG::Properties.new
|
11
|
+
@state = ::Prawn::SVG::State.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def clamp(value, min_value, max_value)
|
15
|
+
[[value, min_value].max, max_value].min
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:element) { OpacityTestElement.new }
|
20
|
+
|
21
|
+
describe "#parse_opacity_attributes_and_call" do
|
22
|
+
subject { element.parse_opacity_attributes_and_call }
|
23
|
+
|
24
|
+
context "with no opacity specified" do
|
25
|
+
it "does nothing" do
|
26
|
+
expect(element).not_to receive(:add_call_and_enter)
|
27
|
+
subject
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "with opacity" do
|
32
|
+
it "sets fill and stroke opacity" do
|
33
|
+
element.properties.opacity = '0.4'
|
34
|
+
|
35
|
+
expect(element).to receive(:add_call_and_enter).with('transparent', 0.4, 0.4)
|
36
|
+
subject
|
37
|
+
|
38
|
+
expect(element.state.fill_opacity).to eq 0.4
|
39
|
+
expect(element.state.stroke_opacity).to eq 0.4
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with just fill opacity" do
|
44
|
+
it "sets fill opacity and sets stroke opacity to 1" do
|
45
|
+
element.properties.fill_opacity = '0.4'
|
46
|
+
|
47
|
+
expect(element).to receive(:add_call_and_enter).with('transparent', 0.4, 1)
|
48
|
+
subject
|
49
|
+
|
50
|
+
expect(element.state.fill_opacity).to eq 0.4
|
51
|
+
expect(element.state.stroke_opacity).to eq 1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "with an existing fill/stroke opacity" do
|
56
|
+
it "multiplies the new opacity by the old" do
|
57
|
+
element.state.fill_opacity = 0.5
|
58
|
+
element.state.stroke_opacity = 0.8
|
59
|
+
|
60
|
+
element.properties.fill_opacity = '0.4'
|
61
|
+
element.properties.stroke_opacity = '0.5'
|
62
|
+
|
63
|
+
expect(element).to receive(:add_call_and_enter).with('transparent', 0.2, 0.4)
|
64
|
+
subject
|
65
|
+
|
66
|
+
expect(element.state.fill_opacity).to eq 0.2
|
67
|
+
expect(element.state.stroke_opacity).to eq 0.4
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "with stroke, fill, and opacity all specified" do
|
72
|
+
it "choses the lower of them" do
|
73
|
+
element.properties.fill_opacity = '0.4'
|
74
|
+
element.properties.stroke_opacity = '0.6'
|
75
|
+
element.properties.opacity = '0.5'
|
76
|
+
|
77
|
+
expect(element).to receive(:add_call_and_enter).with('transparent', 0.4, 0.5)
|
78
|
+
subject
|
79
|
+
|
80
|
+
expect(element.state.fill_opacity).to eq 0.4
|
81
|
+
expect(element.state.stroke_opacity).to eq 0.5
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -14,43 +14,38 @@ describe Prawn::SVG::Attributes::Transform do
|
|
14
14
|
|
15
15
|
let(:element) { TransformTestElement.new }
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
17
|
+
subject { element.send :parse_transform_attribute_and_call }
|
18
|
+
|
19
|
+
context "when a non-identity matrix is requested" do
|
20
|
+
let(:transform) { 'translate(-5.5)' }
|
21
|
+
|
22
|
+
it "passes the transform and executes the returned matrix" do
|
23
|
+
expect(element).to receive(:parse_transform_attribute).with(transform).and_return([1, 2, 3, 4, 5, 6])
|
24
|
+
expect(element).to receive(:add_call_and_enter).with('transformation_matrix', 1, 2, 3, 4, 5, 6)
|
25
|
+
|
26
|
+
element.attributes['transform'] = transform
|
27
|
+
subject
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "when an identity matrix is requested" do
|
32
|
+
let(:transform) { 'translate(0)' }
|
33
|
+
|
34
|
+
it "does not execute any commands" do
|
35
|
+
expect(element).to receive(:parse_transform_attribute).with(transform).and_return([1, 0, 0, 1, 0, 0])
|
36
|
+
expect(element).not_to receive(:add_call_and_enter)
|
37
|
+
|
38
|
+
element.attributes['transform'] = transform
|
39
|
+
subject
|
29
40
|
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "when transform is blank" do
|
44
|
+
it "does nothing" do
|
45
|
+
expect(element).not_to receive(:parse_transform_attribute)
|
46
|
+
expect(element).not_to receive(:add_call_and_enter)
|
30
47
|
|
31
|
-
|
32
|
-
it "handles a single angle argument" do
|
33
|
-
expect(element).to receive(:add_call_and_enter).with('rotate', -5.5, :origin => [0, 0])
|
34
|
-
expect(element).to receive(:y).with('0').and_return(0)
|
35
|
-
|
36
|
-
element.attributes['transform'] = 'rotate(5.5)'
|
37
|
-
subject
|
38
|
-
end
|
39
|
-
|
40
|
-
it "handles three arguments" do
|
41
|
-
expect(element).to receive(:add_call_and_enter).with('rotate', -5.5, :origin => [1.0, 2.0])
|
42
|
-
expect(element).to receive(:x).with(1.0).and_return(1.0)
|
43
|
-
expect(element).to receive(:y).with(2.0).and_return(2.0)
|
44
|
-
|
45
|
-
element.attributes['transform'] = 'rotate(5.5 1 2)'
|
46
|
-
subject
|
47
|
-
end
|
48
|
-
|
49
|
-
it "does nothing and warns if two arguments" do
|
50
|
-
expect(element).to receive(:warnings).and_return([])
|
51
|
-
element.attributes['transform'] = 'rotate(5.5 1)'
|
52
|
-
subject
|
53
|
-
end
|
48
|
+
subject
|
54
49
|
end
|
55
50
|
end
|
56
51
|
end
|
@@ -68,25 +68,7 @@ RSpec.describe Prawn::SVG::CSS::Stylesheets do
|
|
68
68
|
[4, [["fill", "#ff0000", false], ["fill", "#330000", false], ["fill", "#440000", false], ["fill", "#00ff00", false]]],
|
69
69
|
]
|
70
70
|
|
71
|
-
#
|
72
|
-
# Under ruby < 2.6, a bug in REXML causes the /following-sibling selector to
|
73
|
-
# only pick the first matching sibling. This means the + CSS combinator behaves
|
74
|
-
# incorrectly in the following example:
|
75
|
-
#
|
76
|
-
# <a>
|
77
|
-
# <b a="1" />
|
78
|
-
# <b a="2" />
|
79
|
-
# <b a="3" />
|
80
|
-
# </a>
|
81
|
-
#
|
82
|
-
# The css selector `a b + b` will only pick the second <b>, whereas it should
|
83
|
-
# pick both the second and third <b> elements.
|
84
|
-
#
|
85
|
-
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.6.0')
|
86
|
-
expected << [5, [["fill", "#ff0000", false], ["fill", "#330000", false], ["fill", "#330000", false], ["fill", "#00ff00", false]]]
|
87
|
-
else
|
88
|
-
expected << [5, [["fill", "#ff0000", false], ["fill", "#330000", false], ["fill", "#330000", false], ["fill", "#440000", false], ["fill", "#00ff00", false]]]
|
89
|
-
end
|
71
|
+
expected << [5, [["fill", "#ff0000", false], ["fill", "#330000", false], ["fill", "#330000", false], ["fill", "#440000", false], ["fill", "#00ff00", false]]]
|
90
72
|
|
91
73
|
expected.concat [
|
92
74
|
[6, [["fill", "#ff0000", false], ["fill", "#441234", false], ["fill", "#0000ff", false]]],
|
@@ -25,19 +25,19 @@ describe Prawn::SVG::Elements::Base do
|
|
25
25
|
describe "applying calls from the standard attributes" do
|
26
26
|
let(:svg) do
|
27
27
|
<<-SVG
|
28
|
-
<something transform="
|
28
|
+
<something transform="scale(2)" fill-opacity="0.5" fill="red" stroke="blue" stroke-width="5"/>
|
29
29
|
SVG
|
30
30
|
end
|
31
31
|
|
32
32
|
it "appends the relevant calls" do
|
33
33
|
element.process
|
34
34
|
expect(element.base_calls).to eq [
|
35
|
-
["
|
36
|
-
["transparent", [0.5, 1], [
|
37
|
-
["fill_color", ["ff0000"], []],
|
38
|
-
["stroke_color", ["0000ff"], []],
|
39
|
-
["line_width", [5.0], []],
|
40
|
-
["fill_and_stroke", [], []]
|
35
|
+
["transformation_matrix", [2, 0, 0, 2, 0, 0], {}, [
|
36
|
+
["transparent", [0.5, 1], {}, [
|
37
|
+
["fill_color", ["ff0000"], {}, []],
|
38
|
+
["stroke_color", ["0000ff"], {}, []],
|
39
|
+
["line_width", [5.0], {}, []],
|
40
|
+
["fill_and_stroke", [], {}, []]
|
41
41
|
]]
|
42
42
|
]]
|
43
43
|
]
|
@@ -50,37 +50,37 @@ describe Prawn::SVG::Elements::Base do
|
|
50
50
|
|
51
51
|
context "with neither fill nor stroke" do
|
52
52
|
let(:svg) { '<rect style="fill: none;"></rect>' }
|
53
|
-
it { is_expected.to eq ['end_path', [], []] }
|
53
|
+
it { is_expected.to eq ['end_path', [], {}, []] }
|
54
54
|
end
|
55
55
|
|
56
56
|
context "with a fill only" do
|
57
57
|
let(:svg) { '<rect style="fill: black;"></rect>' }
|
58
|
-
it { is_expected.to eq ['fill', [], []] }
|
58
|
+
it { is_expected.to eq ['fill', [], {}, []] }
|
59
59
|
end
|
60
60
|
|
61
61
|
context "with a stroke only" do
|
62
62
|
let(:svg) { '<rect style="fill: none; stroke: black;"></rect>' }
|
63
|
-
it { is_expected.to eq ['stroke', [], []] }
|
63
|
+
it { is_expected.to eq ['stroke', [], {}, []] }
|
64
64
|
end
|
65
65
|
|
66
66
|
context "with fill and stroke" do
|
67
67
|
let(:svg) { '<rect style="fill: black; stroke: black;"></rect>' }
|
68
|
-
it { is_expected.to eq ['fill_and_stroke', [], []] }
|
68
|
+
it { is_expected.to eq ['fill_and_stroke', [], {}, []] }
|
69
69
|
end
|
70
70
|
|
71
71
|
context "with fill with evenodd fill rule" do
|
72
72
|
let(:svg) { '<rect style="fill: black; fill-rule: evenodd;"></rect>' }
|
73
|
-
it { is_expected.to eq ['fill', [{fill_rule: :even_odd}
|
73
|
+
it { is_expected.to eq ['fill', [], {fill_rule: :even_odd}, []] }
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
77
|
it "appends calls to the parent element" do
|
78
78
|
expect(element).to receive(:apply) do
|
79
|
-
element.send :add_call, "test", "argument"
|
79
|
+
element.send :add_call, "test", "argument", kw: 'argument'
|
80
80
|
end
|
81
81
|
|
82
82
|
element.process
|
83
|
-
expect(element.parent_calls).to eq [["fill", [], [["test", ["argument"], []]]]]
|
83
|
+
expect(element.parent_calls).to eq [["fill", [], {}, [["test", ["argument"], {kw: 'argument'}, []]]]]
|
84
84
|
end
|
85
85
|
|
86
86
|
it "quietly absorbs a SkipElementQuietly exception" do
|
@@ -149,8 +149,8 @@ describe Prawn::SVG::Elements::Base do
|
|
149
149
|
it "uses the parent's color element if 'currentColor' fill attribute provided" do
|
150
150
|
element.process
|
151
151
|
|
152
|
-
expect(flattened_calls).to include ['fill_color', ['ff0000']]
|
153
|
-
expect(flattened_calls).not_to include ['fill_color', ['00ff00']]
|
152
|
+
expect(flattened_calls).to include ['fill_color', ['ff0000'], {}]
|
153
|
+
expect(flattened_calls).not_to include ['fill_color', ['00ff00'], {}]
|
154
154
|
end
|
155
155
|
end
|
156
156
|
|