prawn-svg 0.23.1 → 0.24.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -3
  3. data/README.md +15 -9
  4. data/lib/prawn-svg.rb +3 -0
  5. data/lib/prawn/svg/attributes.rb +1 -1
  6. data/lib/prawn/svg/attributes/clip_path.rb +6 -7
  7. data/lib/prawn/svg/attributes/opacity.rb +3 -3
  8. data/lib/prawn/svg/attributes/stroke.rb +6 -4
  9. data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +114 -0
  10. data/lib/prawn/svg/elements.rb +4 -1
  11. data/lib/prawn/svg/elements/base.rb +76 -69
  12. data/lib/prawn/svg/elements/container.rb +5 -6
  13. data/lib/prawn/svg/elements/gradient.rb +4 -4
  14. data/lib/prawn/svg/elements/image.rb +1 -1
  15. data/lib/prawn/svg/elements/line.rb +15 -7
  16. data/lib/prawn/svg/elements/marker.rb +72 -0
  17. data/lib/prawn/svg/elements/path.rb +23 -147
  18. data/lib/prawn/svg/elements/polygon.rb +14 -6
  19. data/lib/prawn/svg/elements/polyline.rb +12 -11
  20. data/lib/prawn/svg/elements/root.rb +3 -1
  21. data/lib/prawn/svg/elements/text.rb +38 -17
  22. data/lib/prawn/svg/font.rb +6 -6
  23. data/lib/prawn/svg/interface.rb +3 -0
  24. data/lib/prawn/svg/pathable.rb +130 -0
  25. data/lib/prawn/svg/properties.rb +122 -0
  26. data/lib/prawn/svg/state.rb +7 -29
  27. data/lib/prawn/svg/version.rb +1 -1
  28. data/spec/prawn/svg/elements/base_spec.rb +19 -32
  29. data/spec/prawn/svg/elements/line_spec.rb +37 -0
  30. data/spec/prawn/svg/elements/marker_spec.rb +90 -0
  31. data/spec/prawn/svg/elements/path_spec.rb +10 -10
  32. data/spec/prawn/svg/elements/polygon_spec.rb +49 -0
  33. data/spec/prawn/svg/elements/polyline_spec.rb +47 -0
  34. data/spec/prawn/svg/elements/style_spec.rb +1 -1
  35. data/spec/prawn/svg/elements/text_spec.rb +37 -5
  36. data/spec/prawn/svg/pathable_spec.rb +92 -0
  37. data/spec/prawn/svg/properties_spec.rb +186 -0
  38. data/spec/sample_svg/arrows.svg +73 -0
  39. data/spec/sample_svg/marker.svg +32 -0
  40. data/spec/sample_svg/polygon01.svg +25 -5
  41. metadata +23 -8
  42. data/lib/prawn/svg/attributes/color.rb +0 -5
  43. data/lib/prawn/svg/attributes/display.rb +0 -5
  44. data/lib/prawn/svg/attributes/font.rb +0 -38
  45. data/spec/prawn/svg/attributes/font_spec.rb +0 -52
@@ -1,5 +1,5 @@
1
1
  module Prawn
2
2
  module SVG
3
- VERSION = '0.23.1'
3
+ VERSION = '0.24.0'
4
4
  end
5
5
  end
@@ -25,7 +25,7 @@ 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" font-family="Helvetica"/>
28
+ <something transform="rotate(90)" fill-opacity="0.5" fill="red" stroke="blue" stroke-width="5"/>
29
29
  SVG
30
30
  end
31
31
 
@@ -37,9 +37,7 @@ describe Prawn::SVG::Elements::Base do
37
37
  ["fill_color", ["ff0000"], []],
38
38
  ["stroke_color", ["0000ff"], []],
39
39
  ["line_width", [5.0], []],
40
- ["font", ["Helvetica", {:style=>:normal}], [
41
- ["fill_and_stroke", [], []]
42
- ]]
40
+ ["fill_and_stroke", [], []]
43
41
  ]]
44
42
  ]]
45
43
  ]
@@ -70,59 +68,47 @@ describe Prawn::SVG::Elements::Base do
70
68
  end
71
69
  end
72
70
 
73
- describe "#parse_fill_and_stroke_attributes_and_call" do
71
+ describe "#apply_colors" do
74
72
  before do
75
- element.send(:combine_attributes_and_style_declarations)
73
+ element.send(:extract_attributes_and_properties)
76
74
  end
77
75
 
78
- subject { element.send :parse_fill_and_stroke_attributes_and_call }
76
+ subject { element.send :apply_colors }
79
77
 
80
78
  it "doesn't change anything if no fill attribute provided" do
79
+ expect(element).to_not receive(:add_call)
81
80
  subject
82
- expect(element.state.fill).to be true
83
-
84
- element.state.fill = false
85
- subject
86
- expect(element.state.fill).to be false
87
81
  end
88
82
 
89
83
  it "doesn't change anything if 'inherit' fill attribute provided" do
90
- element.attributes['fill'] = 'inherit'
91
- subject
92
- expect(element.state.fill).to be true
93
-
94
- element.state.fill = false
84
+ element.properties.fill = 'inherit'
85
+ expect(element).to_not receive(:add_call)
95
86
  subject
96
- expect(element.state.fill).to be false
97
87
  end
98
88
 
99
- it "turns off filling if 'none' fill attribute provided" do
100
- element.attributes['fill'] = 'none'
89
+ it "doesn't change anything if 'none' fill attribute provided" do
90
+ element.properties.fill = 'none'
91
+ expect(element).to_not receive(:add_call)
101
92
  subject
102
- expect(element.state.fill).to be false
103
93
  end
104
94
 
105
95
  it "uses the fill attribute's color" do
106
96
  expect(element).to receive(:add_call).with('fill_color', 'ff0000')
107
- element.attributes['fill'] = 'red'
97
+ element.properties.fill = 'red'
108
98
  subject
109
- expect(element.state.fill).to be true
110
99
  end
111
100
 
112
101
  it "uses black if the fill attribute's color is unparseable" do
113
102
  expect(element).to receive(:add_call).with('fill_color', '000000')
114
- element.attributes['fill'] = 'blarble'
103
+ element.properties.fill = 'blarble'
115
104
  subject
116
- expect(element.state.fill).to be true
117
105
  end
118
106
 
119
107
  it "uses the color attribute if 'currentColor' fill attribute provided" do
120
108
  expect(element).to receive(:add_call).with('fill_color', 'ff0000')
121
- element.attributes['fill'] = 'currentColor'
122
- element.attributes['color'] = 'red'
123
- element.send :parse_color_attribute
109
+ element.properties.fill = 'currentColor'
110
+ element.state.computed_properties.color = 'red'
124
111
  subject
125
- expect(element.state.fill).to be true
126
112
  end
127
113
 
128
114
  context "with a color attribute defined on a parent element" do
@@ -138,10 +124,11 @@ describe Prawn::SVG::Elements::Base do
138
124
  end
139
125
  end
140
126
 
141
- it "turns off filling if UnresolvableURLWithNoFallbackError is raised" do
142
- element.attributes['fill'] = 'url()'
127
+ it "computes to 'none' if UnresolvableURLWithNoFallbackError is raised" do
128
+ expect(element).to_not receive(:add_call)
129
+ element.properties.fill = 'url()'
143
130
  subject
144
- expect(element.state.fill).to be false
131
+ expect(element.computed_properties.fill).to eq 'none'
145
132
  end
146
133
  end
147
134
  end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Prawn::SVG::Elements::Line do
4
+ let(:document) { Prawn::SVG::Document.new(svg, [800, 600], {width: 800, height: 600}) }
5
+
6
+ subject do
7
+ Prawn::SVG::Elements::Line.new(document, document.root, [], Prawn::SVG::State.new)
8
+ end
9
+
10
+ context "with attributes specified" do
11
+ let(:svg) { '<line x1="5" y1="10" x2="15" y2="20" />' }
12
+
13
+ it "renders the line" do
14
+ subject.process
15
+ expect(subject.base_calls).to eq [
16
+ ["fill", [], [
17
+ ["move_to", [[5.0, 590.0]], []],
18
+ ["line_to", [[15.0, 580.0]], []]]
19
+ ]
20
+ ]
21
+ end
22
+ end
23
+
24
+ context "with no attributes specified" do
25
+ let(:svg) { '<line />' }
26
+
27
+ it "draws a line from 0,0 to 0,0" do
28
+ subject.process
29
+ expect(subject.base_calls).to eq [
30
+ ["fill", [], [
31
+ ["move_to", [[0, 600]], []],
32
+ ["line_to", [[0, 600]], []]]
33
+ ]
34
+ ]
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Prawn::SVG::Elements::Marker do
4
+ let(:svg) do
5
+ <<-SVG
6
+ <svg>
7
+ <marker id="Triangle"
8
+ viewBox="0 0 10 10" refX="0" refY="5"
9
+ markerUnits="strokeWidth"
10
+ markerWidth="4" markerHeight="3"
11
+ orient="auto">
12
+ <path d="M 0 0 L 10 5 L 0 10 z" />
13
+ </marker>
14
+
15
+ <line x2="10" y2="10" stroke-width="100" />
16
+ </svg>
17
+ SVG
18
+ end
19
+
20
+ let(:document) { Prawn::SVG::Document.new(svg, [800, 600], {width: 800, height: 600}) }
21
+ let(:state) { Prawn::SVG::State.new }
22
+
23
+ let(:line_element) do
24
+ Prawn::SVG::Elements::Line.new(document, document.root.elements[2], [], state)
25
+ end
26
+
27
+ subject do
28
+ Prawn::SVG::Elements::Marker.new(document, document.root.elements[1], [], state)
29
+ end
30
+
31
+ describe "#parse" do
32
+ it "forces display none" do
33
+ subject.parse
34
+ expect(subject.properties.display).to eq 'none'
35
+ end
36
+ end
37
+
38
+ describe "#apply_marker" do
39
+ it "adds the line and its marker to the call stack" do
40
+ subject.process
41
+ line_element.process
42
+
43
+ # We didn't use a marker-* attribute on the <line> tag, that's
44
+ # why the apply_marker method wasn't automatically called as part
45
+ # of the line_element.process call above.
46
+
47
+ subject.apply_marker(line_element, point: [10, 10], angle: 45)
48
+
49
+ # This example follows the example in the SVG 1.1 documentation
50
+ # in section 11.6.3.
51
+
52
+ expect(line_element.base_calls).to eq [
53
+ ["line_width", [100.0], []],
54
+ ["fill", [], [
55
+ ["move_to", [[0.0, 600.0]], []],
56
+ ["line_to", [[10.0, 590.0]], []]
57
+ ]
58
+ ],
59
+ ["save", [], []],
60
+ ["transformation_matrix", [1, 0, 0, 1, 10, -10], []],
61
+ ["rotate", [-45, {origin: [0, 600.0]}], [
62
+ ["transformation_matrix", [100.0, 0, 0, 100.0, 0, 0], []],
63
+ ["transformation_matrix", [1, 0, 0, 1, -0.0, 1.5], []],
64
+ ["rectangle", [[-0.5, 600.0], 4.0, 3.0], []],
65
+ ["clip", [], []],
66
+ ["transformation_matrix", [0.3, 0, 0, 0.3, 0, 0], []],
67
+ ["transparent", [1.0, 1.0], [
68
+ ["fill_color", ["000000"], []],
69
+ ["line_width", [100.0], []],
70
+ ["cap_style", [:butt], []],
71
+ ["undash", [], []],
72
+ ["save", [], []],
73
+ ["fill", [], [
74
+ ["join_style", [:bevel], []],
75
+ ["move_to", [[0.0, 600.0]], []],
76
+ ["line_to", [[10.0, 595.0]], []],
77
+ ["line_to", [[0.0, 590.0]], []],
78
+ ["close_path", [], []]
79
+ ]
80
+ ],
81
+ ["restore", [], []],
82
+ ]
83
+ ]
84
+ ]
85
+ ],
86
+ ["restore", [], []]
87
+ ]
88
+ end
89
+ end
90
+ end
@@ -14,7 +14,7 @@ describe Prawn::SVG::Elements::Path do
14
14
 
15
15
  it "correctly parses" do
16
16
  calls = []
17
- path.stub(:run_path_command) {|*args| calls << args}
17
+ path.stub(:parse_path_command) {|*args| calls << args}
18
18
  path.parse
19
19
 
20
20
  calls.should == [
@@ -40,7 +40,7 @@ describe Prawn::SVG::Elements::Path do
40
40
  ["m", [5,6,7,8]],
41
41
  ["l", [7,8]]
42
42
  ].each do |args|
43
- path.should_receive(:run_path_command).with(*args).and_call_original
43
+ path.should_receive(:parse_path_command).with(*args).and_call_original
44
44
  end
45
45
 
46
46
  path.parse
@@ -81,9 +81,9 @@ describe Prawn::SVG::Elements::Path do
81
81
 
82
82
  it "uses bezier curves to approximate an arc path" do
83
83
  expect(subject).to eq [
84
- ["move_to", [100.0, 200.0]],
85
- ["curve_to", [150.0, 150.0, 100.0, 172.57081148225683, 122.57081148225683, 150.0]],
86
- ["curve_to", [200.0, 200.0, 177.42918851774317, 150.0, 200.0, 172.57081148225683]]
84
+ Prawn::SVG::Elements::Path::Move.new([100.0, 200.0]),
85
+ Prawn::SVG::Elements::Path::Curve.new([150.0, 150.0], [100.0, 172.57081148225683], [122.57081148225683, 150.0]),
86
+ Prawn::SVG::Elements::Path::Curve.new([200.0, 200.0], [177.42918851774317, 150.0], [200.0, 172.57081148225683])
87
87
  ]
88
88
  end
89
89
  end
@@ -93,7 +93,7 @@ describe Prawn::SVG::Elements::Path do
93
93
 
94
94
  it "ignores the path" do
95
95
  expect(subject).to eq [
96
- ["move_to", [100.0, 200.0]]
96
+ Prawn::SVG::Elements::Path::Move.new([100.0, 200.0]),
97
97
  ]
98
98
  end
99
99
  end
@@ -103,8 +103,8 @@ describe Prawn::SVG::Elements::Path do
103
103
 
104
104
  it "substitutes a line_to" do
105
105
  expect(subject).to eq [
106
- ["move_to", [100.0, 200.0]],
107
- ["line_to", [200.0, 200.0]]
106
+ Prawn::SVG::Elements::Path::Move.new([100.0, 200.0]),
107
+ Prawn::SVG::Elements::Path::Line.new([200.0, 200.0])
108
108
  ]
109
109
  end
110
110
  end
@@ -114,8 +114,8 @@ describe Prawn::SVG::Elements::Path do
114
114
 
115
115
  it "substitutes a line_to" do
116
116
  expect(subject).to eq [
117
- ["move_to", [100.0, 200.0]],
118
- ["line_to", [200.0, 200.0]]
117
+ Prawn::SVG::Elements::Path::Move.new([100.0, 200.0]),
118
+ Prawn::SVG::Elements::Path::Line.new([200.0, 200.0])
119
119
  ]
120
120
  end
121
121
  end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Prawn::SVG::Elements::Polygon do
4
+ let(:document) { Prawn::SVG::Document.new(svg, [800, 600], {width: 800, height: 600}) }
5
+
6
+ subject do
7
+ Prawn::SVG::Elements::Polygon.new(document, document.root, [], Prawn::SVG::State.new)
8
+ end
9
+
10
+ context "with a valid points attribute" do
11
+ let(:svg) { '<polygon points="10 10 20,20 30,30" />' }
12
+
13
+ it "renders the polygon" do
14
+ subject.process
15
+ expect(subject.base_calls).to eq [
16
+ ["fill", [], [
17
+ ["move_to", [[10.0, 590.0]], []],
18
+ ["line_to", [[20.0, 580.0]], []],
19
+ ["line_to", [[30.0, 570.0]], []],
20
+ ["close_path", [], []]]
21
+ ]
22
+ ]
23
+ end
24
+ end
25
+
26
+ context "with a polygon that has an odd number of arguments" do
27
+ let(:svg) { '<polygon points="10 10 20,20 30" />' }
28
+
29
+ it "ignores the last one" do
30
+ subject.process
31
+ expect(subject.base_calls).to eq [
32
+ ["fill", [], [
33
+ ["move_to", [[10.0, 590.0]], []],
34
+ ["line_to", [[20.0, 580.0]], []],
35
+ ["close_path", [], []]]
36
+ ]
37
+ ]
38
+ end
39
+ end
40
+
41
+ context "with a polygon that has no arguments" do
42
+ let(:svg) { '<polygon points="" />' }
43
+
44
+ it "renders nothing" do
45
+ subject.process
46
+ expect(subject.base_calls).to eq []
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Prawn::SVG::Elements::Polyline do
4
+ let(:document) { Prawn::SVG::Document.new(svg, [800, 600], {width: 800, height: 600}) }
5
+
6
+ subject do
7
+ Prawn::SVG::Elements::Polyline.new(document, document.root, [], Prawn::SVG::State.new)
8
+ end
9
+
10
+ context "with a valid points attribute" do
11
+ let(:svg) { '<polyline points="10 10 20,20 30,30" />' }
12
+
13
+ it "renders the polyline" do
14
+ subject.process
15
+ expect(subject.base_calls).to eq [
16
+ ["fill", [], [
17
+ ["move_to", [[10.0, 590.0]], []],
18
+ ["line_to", [[20.0, 580.0]], []],
19
+ ["line_to", [[30.0, 570.0]], []]]
20
+ ]
21
+ ]
22
+ end
23
+ end
24
+
25
+ context "with a polyline that has an odd number of arguments" do
26
+ let(:svg) { '<polyline points="10 10 20,20 30" />' }
27
+
28
+ it "ignores the last one" do
29
+ subject.process
30
+ expect(subject.base_calls).to eq [
31
+ ["fill", [], [
32
+ ["move_to", [[10.0, 590.0]], []],
33
+ ["line_to", [[20.0, 580.0]], []]]
34
+ ]
35
+ ]
36
+ end
37
+ end
38
+
39
+ context "with a polyline that has no arguments" do
40
+ let(:svg) { '<polyline points="" />' }
41
+
42
+ it "renders nothing" do
43
+ subject.process
44
+ expect(subject.base_calls).to eq []
45
+ end
46
+ end
47
+ end
@@ -14,7 +14,7 @@ z</style>
14
14
  end
15
15
 
16
16
  let(:document) { Prawn::SVG::Document.new(svg, [800, 600], {}) }
17
- let(:element) { Prawn::SVG::Elements::Style.new(document, document.root, [], {}) }
17
+ let(:element) { Prawn::SVG::Elements::Style.new(document, document.root, [], Prawn::SVG::State.new) }
18
18
 
19
19
  it "correctly collects the style information in a <style> tag" do
20
20
  expect(document.css_parser).to receive(:add_block!).with("a\n before>\n x y\n inside <>&gt;\n k j\n after\nz")
@@ -1,7 +1,7 @@
1
1
  require File.dirname(__FILE__) + '/../../../spec_helper'
2
2
 
3
3
  describe Prawn::SVG::Elements::Text do
4
- let(:document) { Prawn::SVG::Document.new(svg, [800, 600], {}) }
4
+ let(:document) { Prawn::SVG::Document.new(svg, [800, 600], {}, font_registry: Prawn::SVG::FontRegistry.new("Helvetica" => {:normal => nil}, "Courier" => {normal: nil}, 'Times-Roman' => {normal: nil})) }
5
5
  let(:element) { Prawn::SVG::Elements::Text.new(document, document.root, [], Prawn::SVG::State.new) }
6
6
 
7
7
  describe "xml:space preserve" do
@@ -14,7 +14,7 @@ describe Prawn::SVG::Elements::Text do
14
14
  element.process
15
15
 
16
16
  expect(element.calls).to eq [
17
- ["draw_text", ["some text", {:style=>nil, :at=>[0.0, 150.0]}], []]
17
+ ["draw_text", ["some text", {:size=>16, :style=>:normal, :text_anchor=>'start', :at=>[0.0, 150.0]}], []]
18
18
  ]
19
19
  end
20
20
  end
@@ -26,7 +26,7 @@ describe Prawn::SVG::Elements::Text do
26
26
  element.process
27
27
 
28
28
  expect(element.calls).to eq [
29
- ["draw_text", ["some text", {:style=>nil, :at=>[0.0, 150.0]}], []]
29
+ ["draw_text", ["some text", {:size=>16, :style=>:normal, :text_anchor=>'start', :at=>[0.0, 150.0]}], []]
30
30
  ]
31
31
  end
32
32
  end
@@ -34,10 +34,11 @@ describe Prawn::SVG::Elements::Text do
34
34
 
35
35
  describe "when text-anchor is specified" do
36
36
  let(:svg) { '<g text-anchor="middle" font-size="12"><text x="50" y="14">Text</text></g>' }
37
+ let(:element) { Prawn::SVG::Elements::Container.new(document, document.root, [], Prawn::SVG::State.new) }
37
38
 
38
39
  it "should inherit text-anchor from parent element" do
39
40
  element.process
40
- expect(element.state.text_anchor).to eq 'middle'
41
+ expect(element.calls.flatten).to include(:size => 12.0, :style => :normal, :text_anchor => "middle", :at => [50.0, 136.0])
41
42
  end
42
43
  end
43
44
 
@@ -49,13 +50,44 @@ describe Prawn::SVG::Elements::Text do
49
50
 
50
51
  expect(element.base_calls).to eq [
51
52
  ["fill", [], [
53
+ ["font", ["Helvetica", {style: :normal}], []],
52
54
  ["text_group", [], [
53
55
  ["character_spacing", [5.0], [
54
- ["draw_text", ["spaced", {:style=>nil, :at=>[0.0, 150.0]}], []]
56
+ ["draw_text", ["spaced", {:size=>16, :style=>:normal, :text_anchor=>'start', :at=>[0.0, 150.0]}], []]
55
57
  ]]
56
58
  ]]
57
59
  ]]
58
60
  ]
59
61
  end
60
62
  end
63
+
64
+ describe "font finding" do
65
+ context "with a font that exists" do
66
+ let(:svg) { '<text font-family="monospace">hello</text>' }
67
+
68
+ it "finds the font and uses it" do
69
+ element.process
70
+ expect(element.base_calls[0][2][0]).to eq ['font', ['Courier', {style: :normal}], []]
71
+ end
72
+ end
73
+
74
+ context "with a font that doesn't exist" do
75
+ let(:svg) { '<text font-family="does not exist">hello</text>' }
76
+
77
+ it "uses the fallback font" do
78
+ element.process
79
+ expect(element.base_calls[0][2][0]).to eq ['font', ['Times-Roman', {style: :normal}], []]
80
+ end
81
+
82
+ context "when there is no fallback font" do
83
+ before { document.font_registry.installed_fonts.delete("Times-Roman") }
84
+
85
+ it "doesn't call the font method and logs a warning" do
86
+ element.process
87
+ expect(element.base_calls.flatten).to_not include 'font'
88
+ expect(document.warnings.first).to include "is not a known font"
89
+ end
90
+ end
91
+ end
92
+ end
61
93
  end