prawn-svg 0.27.1 → 0.31.0

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