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.
Files changed (62) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +6 -4
  3. data/LICENSE +1 -1
  4. data/README.md +23 -9
  5. data/lib/prawn-svg.rb +7 -1
  6. data/lib/prawn/svg/attributes/opacity.rb +4 -4
  7. data/lib/prawn/svg/attributes/transform.rb +2 -44
  8. data/lib/prawn/svg/calculators/document_sizing.rb +2 -2
  9. data/lib/prawn/svg/{css.rb → css/font_family_parser.rb} +3 -4
  10. data/lib/prawn/svg/css/selector_parser.rb +174 -0
  11. data/lib/prawn/svg/css/stylesheets.rb +146 -0
  12. data/lib/prawn/svg/document.rb +3 -15
  13. data/lib/prawn/svg/elements.rb +4 -2
  14. data/lib/prawn/svg/elements/base.rb +26 -23
  15. data/lib/prawn/svg/elements/clip_path.rb +12 -0
  16. data/lib/prawn/svg/elements/container.rb +1 -3
  17. data/lib/prawn/svg/elements/gradient.rb +83 -25
  18. data/lib/prawn/svg/elements/image.rb +2 -2
  19. data/lib/prawn/svg/elements/path.rb +42 -29
  20. data/lib/prawn/svg/elements/root.rb +4 -1
  21. data/lib/prawn/svg/elements/text.rb +1 -1
  22. data/lib/prawn/svg/elements/text_component.rb +63 -14
  23. data/lib/prawn/svg/elements/use.rb +23 -7
  24. data/lib/prawn/svg/extensions/additional_gradient_transforms.rb +23 -0
  25. data/lib/prawn/svg/font_registry.rb +4 -3
  26. data/lib/prawn/svg/interface.rb +26 -2
  27. data/lib/prawn/svg/loaders/data.rb +1 -1
  28. data/lib/prawn/svg/loaders/file.rb +4 -2
  29. data/lib/prawn/svg/properties.rb +2 -0
  30. data/lib/prawn/svg/state.rb +6 -3
  31. data/lib/prawn/svg/transform_parser.rb +72 -0
  32. data/lib/prawn/svg/version.rb +1 -1
  33. data/prawn-svg.gemspec +3 -4
  34. data/spec/prawn/svg/attributes/opacity_spec.rb +85 -0
  35. data/spec/prawn/svg/attributes/transform_spec.rb +30 -35
  36. data/spec/prawn/svg/calculators/document_sizing_spec.rb +4 -4
  37. data/spec/prawn/svg/{css_spec.rb → css/font_family_parser_spec.rb} +3 -3
  38. data/spec/prawn/svg/css/selector_parser_spec.rb +33 -0
  39. data/spec/prawn/svg/css/stylesheets_spec.rb +142 -0
  40. data/spec/prawn/svg/document_spec.rb +0 -33
  41. data/spec/prawn/svg/elements/base_spec.rb +58 -2
  42. data/spec/prawn/svg/elements/gradient_spec.rb +79 -4
  43. data/spec/prawn/svg/elements/path_spec.rb +29 -17
  44. data/spec/prawn/svg/elements/text_spec.rb +74 -16
  45. data/spec/prawn/svg/font_registry_spec.rb +30 -0
  46. data/spec/prawn/svg/interface_spec.rb +33 -1
  47. data/spec/prawn/svg/loaders/data_spec.rb +8 -0
  48. data/spec/prawn/svg/transform_parser_spec.rb +94 -0
  49. data/spec/sample_output/{directory → .keep} +0 -0
  50. data/spec/sample_svg/double_opacity.svg +6 -0
  51. data/spec/sample_svg/gradient_transform.svg +19 -0
  52. data/spec/sample_svg/links.svg +18 -0
  53. data/spec/sample_svg/radgrad01-bounding.svg +26 -0
  54. data/spec/sample_svg/radgrad01.svg +26 -0
  55. data/spec/sample_svg/svg_fill.svg +5 -0
  56. data/spec/sample_svg/text-decoration.svg +4 -0
  57. data/spec/sample_svg/text_stroke.svg +41 -0
  58. data/spec/sample_svg/transform.svg +20 -0
  59. data/spec/sample_svg/use_disordered.svg +17 -0
  60. data/spec/sample_svg/warning-radioactive.svg +98 -0
  61. data/spec/spec_helper.rb +2 -2
  62. metadata +137 -15
@@ -1,12 +1,11 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  require File.expand_path('../lib/prawn/svg/version', __FILE__)
3
3
 
4
- spec = Gem::Specification.new do |gem|
4
+ Gem::Specification.new do |gem|
5
5
  gem.name = 'prawn-svg'
6
6
  gem.version = Prawn::SVG::VERSION
7
7
  gem.summary = "SVG renderer for Prawn PDF library"
8
8
  gem.description = "This gem allows you to render SVG directly into a PDF using the 'prawn' gem. Since PDF is vector-based, you'll get nice scaled graphics if you use SVG instead of an image."
9
- gem.has_rdoc = false
10
9
  gem.author = "Roger Nesbitt"
11
10
  gem.email = "roger@seriousorange.com"
12
11
  gem.homepage = "http://github.com/mogest/prawn-svg"
@@ -21,7 +20,7 @@ spec = Gem::Specification.new do |gem|
21
20
  gem.required_ruby_version = '>= 2.1.0'
22
21
 
23
22
  gem.add_runtime_dependency "prawn", ">= 0.11.1", "< 3"
24
- gem.add_runtime_dependency "css_parser", "~> 1.3"
23
+ gem.add_runtime_dependency "css_parser", "~> 1.6"
25
24
  gem.add_development_dependency "rspec", "~> 3.0"
26
- gem.add_development_dependency "rake", "~> 10.1"
25
+ gem.add_development_dependency "rake", "~> 13.0"
27
26
  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
- describe "#parse_transform_attribute_and_call" do
18
- subject { element.send :parse_transform_attribute_and_call }
19
-
20
- describe "translate" do
21
- it "handles a missing y argument" do
22
- expect(element).to receive(:add_call_and_enter).with('translate', -5.5, 0)
23
- expect(element).to receive(:x_pixels).with(-5.5).and_return(-5.5)
24
- expect(element).to receive(:y_pixels).with(0.0).and_return(0.0)
25
-
26
- element.attributes['transform'] = 'translate(-5.5)'
27
- subject
28
- end
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
- describe "rotate" do
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
@@ -31,8 +31,8 @@ describe Prawn::SVG::Calculators::DocumentSizing do
31
31
  describe "#calculate" do
32
32
  it "calculates the document sizing measurements for a given set of inputs" do
33
33
  sizing.calculate
34
- expect(sizing.x_offset).to eq -75 / 0.25
35
- expect(sizing.y_offset).to eq -30
34
+ expect(sizing.x_offset).to eq(-75 / 0.25)
35
+ expect(sizing.y_offset).to eq(-30)
36
36
  expect(sizing.x_scale).to eq 0.25
37
37
  expect(sizing.y_scale).to eq 0.25
38
38
  expect(sizing.viewport_width).to eq 300
@@ -74,12 +74,12 @@ describe Prawn::SVG::Calculators::DocumentSizing do
74
74
  context "when a viewBox is specified" do
75
75
  let(:attributes) { {"viewBox" => "0 0 100 200"} }
76
76
 
77
- it "defaults to 100% width and height" do
77
+ it "defaults to 100% width and uses the viewbox ratio for height" do
78
78
  sizing.calculate
79
79
  expect(sizing.viewport_width).to eq 100
80
80
  expect(sizing.viewport_height).to eq 200
81
81
  expect(sizing.output_width).to eq 1200
82
- expect(sizing.output_height).to eq 800
82
+ expect(sizing.output_height).to eq 2400
83
83
  end
84
84
  end
85
85
 
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
- RSpec.describe Prawn::SVG::CSS do
4
- describe "#parse_font_family_string" do
3
+ RSpec.describe Prawn::SVG::CSS::FontFamilyParser do
4
+ describe "#parse" do
5
5
  it "correctly handles quotes and escaping" do
6
6
  tests = {
7
7
  "" => [],
@@ -17,7 +17,7 @@ RSpec.describe Prawn::SVG::CSS do
17
17
  }
18
18
 
19
19
  tests.each do |string, expected|
20
- expect(Prawn::SVG::CSS.parse_font_family_string(string)).to eq expected
20
+ expect(Prawn::SVG::CSS::FontFamilyParser.parse(string)).to eq expected
21
21
  end
22
22
  end
23
23
  end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Prawn::SVG::CSS::SelectorParser do
4
+ describe "::parse" do
5
+ it "parses a simple selector" do
6
+ expect(described_class.parse("div")).to eq [{name: "div"}]
7
+ expect(described_class.parse(".c1")).to eq [{class: ["c1"]}]
8
+ end
9
+
10
+ it "parses a complex selector" do
11
+ result = described_class.parse("div#count .c1.c2 > span.large + div~.other:first-child *:nth-child(3)")
12
+ expect(result).to eq [
13
+ {name: "div", id: ["count"]},
14
+ {combinator: :descendant, class: ["c1", "c2"]},
15
+ {combinator: :child, name: "span", class: ["large"]},
16
+ {combinator: :adjacent, name: "div"},
17
+ {combinator: :siblings, class: ["other"], pseudo_class: ["first-child"]},
18
+ {combinator: :descendant, name: "*", pseudo_class: ["nth-child(3)"]},
19
+ ]
20
+ end
21
+
22
+ it "parses attributes" do
23
+ expect(described_class.parse("[abc]")).to eq [{attribute: [["abc", nil, nil]]}]
24
+ expect(described_class.parse("[abc=123]")).to eq [{attribute: [["abc", '=', '123']]}]
25
+ expect(described_class.parse("[abc^=123]")).to eq [{attribute: [["abc", '^=', '123']]}]
26
+ expect(described_class.parse("[ abc ^= 123 ]")).to eq [{attribute: [["abc", '^=', '123']]}]
27
+ expect(described_class.parse("[abc^='123']")).to eq [{attribute: [["abc", '^=', '123']]}]
28
+ expect(described_class.parse("[abc^= '123' ]")).to eq [{attribute: [["abc", '^=', '123']]}]
29
+ expect(described_class.parse("[abc^= '123\\'456' ]")).to eq [{attribute: [["abc", '^=', '123\'456']]}]
30
+ expect(described_class.parse('[abc^= "123\\"456" ]')).to eq [{attribute: [["abc", '^=', '123"456']]}]
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,142 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Prawn::SVG::CSS::Stylesheets do
4
+ describe "typical usage" do
5
+ let(:svg) { <<-SVG }
6
+ <svg>
7
+ <style>
8
+ #inner rect { fill: #0000ff; }
9
+ #outer { fill: #220000; }
10
+ .hero > rect { fill: #00ff00; }
11
+ rect { fill: #ff0000; }
12
+ rect ~ rect { fill: #330000; }
13
+ rect + rect { fill: #440000; }
14
+ rect:first-child:last-child { fill: #441234; }
15
+
16
+ circle:first-child { fill: #550000; }
17
+ circle:nth-child(2) { fill: #660000; }
18
+ circle:last-child { fill: #770000; }
19
+
20
+ square[chocolate] { fill: #880000; }
21
+ square[abc=def] { fill: #990000; }
22
+ square[abc^=ghi] { fill: #aa0000; }
23
+ square[abc$=jkl] { fill: #bb0000; }
24
+ square[abc*=mno] { fill: #cc0000; }
25
+ square[abc~=pqr] { fill: #dd0000; }
26
+ square[abc|=stu] { fill: #ee0000; }
27
+ </style>
28
+
29
+ <rect width="1" height="1" />
30
+ <rect width="2" height="2" id="outer" />
31
+
32
+ <g class="hero large">
33
+ <rect width="3" height="3" />
34
+ <rect width="4" height="4" style="fill: #777777;" />
35
+ <rect width="5" height="5" />
36
+
37
+ <g id="inner">
38
+ <rect width="6" height="6" />
39
+ </g>
40
+
41
+ <circle width="100" />
42
+
43
+ <g id="circles">
44
+ <circle width="7" />
45
+ <circle width="8" />
46
+ <circle width="9" />
47
+ </g>
48
+ </g>
49
+
50
+ <square width="10" chocolate="hi there" />
51
+ <square width="11" abc="def" />
52
+ <square width="12" abc="ghidef" />
53
+ <square width="13" abc="aghidefjkl" />
54
+ <square width="14" abc="agmnohidefjklx" />
55
+ <square width="15" abc="aeo cnj pqr" />
56
+ <square width="16" abc="eij-stu-asd" />
57
+ </svg>
58
+ SVG
59
+
60
+ it "associates styles with elements" do
61
+ result = Prawn::SVG::CSS::Stylesheets.new(CssParser::Parser.new, REXML::Document.new(svg)).load
62
+ width_and_styles = result.map { |k, v| [k.attributes["width"].to_i, v] }.sort_by(&:first)
63
+
64
+ expected = [
65
+ [1, [["fill", "#ff0000", false]]],
66
+ [2, [["fill", "#ff0000", false], ["fill", "#330000", false], ["fill", "#440000", false], ["fill", "#220000", false]]],
67
+ [3, [["fill", "#ff0000", false], ["fill", "#00ff00", false]]],
68
+ [4, [["fill", "#ff0000", false], ["fill", "#330000", false], ["fill", "#440000", false], ["fill", "#00ff00", false]]],
69
+ ]
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
90
+
91
+ expected.concat [
92
+ [6, [["fill", "#ff0000", false], ["fill", "#441234", false], ["fill", "#0000ff", false]]],
93
+ [7, [["fill", "#550000", false]]],
94
+ [8, [["fill", "#660000", false]]],
95
+ [9, [["fill", "#770000", false]]],
96
+ [10, [["fill", "#880000", false]]],
97
+ [11, [["fill", "#990000", false]]],
98
+ [12, [["fill", "#aa0000", false]]],
99
+ [13, [["fill", "#bb0000", false]]],
100
+ [14, [["fill", "#cc0000", false]]],
101
+ [15, [["fill", "#dd0000", false]]],
102
+ [16, [["fill", "#ee0000", false]]],
103
+ ]
104
+
105
+ expect(width_and_styles).to eq(expected)
106
+ end
107
+ end
108
+
109
+ describe "style tag parsing" do
110
+ let(:svg) do
111
+ <<-SVG
112
+ <svg>
113
+ <some-tag>
114
+ <style>a
115
+ before&gt;
116
+ x <![CDATA[ y
117
+ inside <>&gt;
118
+ k ]]> j
119
+ after
120
+ z</style>
121
+ </some-tag>
122
+
123
+ <other-tag>
124
+ <more-tag>
125
+ <style>hello</style>
126
+ </more-tag>
127
+ </other-tag>
128
+ </svg>
129
+ SVG
130
+ end
131
+
132
+ it "scans the document for style tags and adds the style information to the css parser" do
133
+ css_parser = instance_double(CssParser::Parser)
134
+
135
+ expect(css_parser).to receive(:add_block!).with("a\n before>\n x y\n inside <>&gt;\n k j\n after\nz")
136
+ expect(css_parser).to receive(:add_block!).with("hello")
137
+ allow(css_parser).to receive(:each_rule_set)
138
+
139
+ Prawn::SVG::CSS::Stylesheets.new(css_parser, REXML::Document.new(svg)).load
140
+ end
141
+ end
142
+ end
@@ -25,37 +25,4 @@ describe Prawn::SVG::Document do
25
25
  end
26
26
  end
27
27
  end
28
-
29
- describe "#parse_style_elements" do
30
- let(:svg) do
31
- <<-SVG
32
- <svg>
33
- <some-tag>
34
- <style>a
35
- before&gt;
36
- x <![CDATA[ y
37
- inside <>&gt;
38
- k ]]> j
39
- after
40
- z</style>
41
- </some-tag>
42
-
43
- <other-tag>
44
- <more-tag>
45
- <style>hello</style>
46
- </more-tag>
47
- </other-tag>
48
- </svg>
49
- SVG
50
- end
51
-
52
- it "scans the document for style tags and adds the style information to the css parser" do
53
- css_parser = instance_double(CssParser::Parser)
54
-
55
- expect(css_parser).to receive(:add_block!).with("a\n before>\n x y\n inside <>&gt;\n k j\n after\nz")
56
- expect(css_parser).to receive(:add_block!).with("hello")
57
-
58
- Prawn::SVG::Document.new(svg, bounds, options, css_parser: css_parser)
59
- end
60
- end
61
28
  end
@@ -25,14 +25,14 @@ 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="rotate(90)" fill-opacity="0.5" fill="red" stroke="blue" stroke-width="5"/>
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
- ["rotate", [-90.0, {origin: [0, 600.0]}], [
35
+ ["transformation_matrix", [2, 0, 0, 2, 0, 0], [
36
36
  ["transparent", [0.5, 1], [
37
37
  ["fill_color", ["ff0000"], []],
38
38
  ["stroke_color", ["0000ff"], []],
@@ -44,6 +44,36 @@ describe Prawn::SVG::Elements::Base do
44
44
  end
45
45
  end
46
46
 
47
+ describe "fills and strokes" do
48
+ before { element.process }
49
+ subject { element.base_calls.last }
50
+
51
+ context "with neither fill nor stroke" do
52
+ let(:svg) { '<rect style="fill: none;"></rect>' }
53
+ it { is_expected.to eq ['end_path', [], []] }
54
+ end
55
+
56
+ context "with a fill only" do
57
+ let(:svg) { '<rect style="fill: black;"></rect>' }
58
+ it { is_expected.to eq ['fill', [], []] }
59
+ end
60
+
61
+ context "with a stroke only" do
62
+ let(:svg) { '<rect style="fill: none; stroke: black;"></rect>' }
63
+ it { is_expected.to eq ['stroke', [], []] }
64
+ end
65
+
66
+ context "with fill and stroke" do
67
+ let(:svg) { '<rect style="fill: black; stroke: black;"></rect>' }
68
+ it { is_expected.to eq ['fill_and_stroke', [], []] }
69
+ end
70
+
71
+ context "with fill with evenodd fill rule" do
72
+ let(:svg) { '<rect style="fill: black; fill-rule: evenodd;"></rect>' }
73
+ it { is_expected.to eq ['fill', [{fill_rule: :even_odd}], []] }
74
+ end
75
+ end
76
+
47
77
  it "appends calls to the parent element" do
48
78
  expect(element).to receive(:apply) do
49
79
  element.send :add_call, "test", "argument"
@@ -131,4 +161,30 @@ describe Prawn::SVG::Elements::Base do
131
161
  expect(element.computed_properties.fill).to eq 'none'
132
162
  end
133
163
  end
164
+
165
+ describe "stylesheets" do
166
+ let(:svg) { <<-SVG }
167
+ <svg>
168
+ <style>
169
+ .special rect { fill: green; }
170
+ rect { fill: red; }
171
+ </style>
172
+ <rect width="100" height="100"></rect>
173
+ <g class="special">
174
+ <rect width="100" height="100"></rect>
175
+ <rect width="100" height="100" style="fill: yellow;"></rect>
176
+ </g>
177
+ </svg>
178
+ SVG
179
+
180
+ it "applies stylesheet styling but style attributes take precedence" do
181
+ document = Prawn::SVG::Document.new(svg, [100, 100], {})
182
+ calls = []
183
+ element = Prawn::SVG::Elements::Root.new(document, document.root, calls)
184
+ element.process
185
+
186
+ fill_colors = calls.select { |cmd, _, _| cmd == 'fill_color' }.map { |_, args, _| args.first }
187
+ expect(fill_colors).to eq ['000000', 'ff0000', '008000', 'ffff00']
188
+ end
189
+ end
134
190
  end