prawn-svg 0.23.1 → 0.24.0

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