prawn-svg 0.29.1 → 0.32.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 +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
|
|