prawn-svg 0.21.0 → 0.22.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -0
  3. data/README.md +11 -4
  4. data/lib/prawn-svg.rb +9 -6
  5. data/lib/prawn/svg/attributes.rb +6 -0
  6. data/lib/prawn/svg/attributes/clip_path.rb +17 -0
  7. data/lib/prawn/svg/attributes/display.rb +5 -0
  8. data/lib/prawn/svg/attributes/font.rb +38 -0
  9. data/lib/prawn/svg/attributes/opacity.rb +15 -0
  10. data/lib/prawn/svg/attributes/stroke.rb +35 -0
  11. data/lib/prawn/svg/attributes/transform.rb +50 -0
  12. data/lib/prawn/svg/calculators/aspect_ratio.rb +1 -1
  13. data/lib/prawn/svg/calculators/document_sizing.rb +2 -2
  14. data/lib/prawn/svg/calculators/pixels.rb +1 -1
  15. data/lib/prawn/svg/color.rb +44 -14
  16. data/lib/prawn/svg/document.rb +6 -5
  17. data/lib/prawn/svg/elements.rb +33 -0
  18. data/lib/prawn/svg/elements/base.rb +228 -0
  19. data/lib/prawn/svg/elements/circle.rb +25 -0
  20. data/lib/prawn/svg/elements/container.rb +15 -0
  21. data/lib/prawn/svg/elements/ellipse.rb +23 -0
  22. data/lib/prawn/svg/elements/gradient.rb +117 -0
  23. data/lib/prawn/svg/elements/ignored.rb +5 -0
  24. data/lib/prawn/svg/elements/image.rb +85 -0
  25. data/lib/prawn/svg/elements/line.rb +16 -0
  26. data/lib/prawn/svg/elements/path.rb +405 -0
  27. data/lib/prawn/svg/elements/polygon.rb +17 -0
  28. data/lib/prawn/svg/elements/polyline.rb +22 -0
  29. data/lib/prawn/svg/elements/rect.rb +33 -0
  30. data/lib/prawn/svg/elements/root.rb +9 -0
  31. data/lib/prawn/svg/elements/style.rb +10 -0
  32. data/lib/prawn/svg/elements/text.rb +87 -0
  33. data/lib/prawn/svg/elements/use.rb +29 -0
  34. data/lib/prawn/svg/extension.rb +2 -2
  35. data/lib/prawn/svg/font.rb +3 -3
  36. data/lib/prawn/svg/interface.rb +12 -5
  37. data/lib/prawn/svg/url_loader.rb +1 -1
  38. data/lib/prawn/svg/version.rb +2 -2
  39. data/prawn-svg.gemspec +3 -3
  40. data/spec/integration_spec.rb +59 -2
  41. data/spec/prawn/svg/attributes/font_spec.rb +49 -0
  42. data/spec/prawn/svg/attributes/transform_spec.rb +56 -0
  43. data/spec/prawn/svg/calculators/aspect_ratio_spec.rb +2 -2
  44. data/spec/prawn/svg/calculators/document_sizing_spec.rb +3 -3
  45. data/spec/prawn/svg/color_spec.rb +36 -15
  46. data/spec/prawn/svg/document_spec.rb +4 -4
  47. data/spec/prawn/svg/elements/base_spec.rb +125 -0
  48. data/spec/prawn/svg/elements/gradient_spec.rb +61 -0
  49. data/spec/prawn/svg/elements/path_spec.rb +123 -0
  50. data/spec/prawn/svg/elements/style_spec.rb +23 -0
  51. data/spec/prawn/svg/{parser → elements}/text_spec.rb +7 -8
  52. data/spec/prawn/svg/font_spec.rb +12 -12
  53. data/spec/prawn/svg/interface_spec.rb +7 -7
  54. data/spec/prawn/svg/url_loader_spec.rb +2 -2
  55. data/spec/sample_svg/gradients.svg +40 -0
  56. data/spec/sample_svg/rect02.svg +8 -11
  57. data/spec/spec_helper.rb +1 -1
  58. metadata +46 -18
  59. data/lib/prawn/svg/element.rb +0 -304
  60. data/lib/prawn/svg/parser.rb +0 -268
  61. data/lib/prawn/svg/parser/image.rb +0 -81
  62. data/lib/prawn/svg/parser/path.rb +0 -392
  63. data/lib/prawn/svg/parser/text.rb +0 -80
  64. data/spec/prawn/svg/element_spec.rb +0 -127
  65. data/spec/prawn/svg/parser/path_spec.rb +0 -89
  66. data/spec/prawn/svg/parser_spec.rb +0 -55
@@ -1,8 +1,8 @@
1
1
  require File.dirname(__FILE__) + '/../../../spec_helper'
2
2
 
3
- describe Prawn::Svg::Calculators::AspectRatio do
3
+ describe Prawn::SVG::Calculators::AspectRatio do
4
4
  def test(*args)
5
- aspect = Prawn::Svg::Calculators::AspectRatio.new(*args)
5
+ aspect = Prawn::SVG::Calculators::AspectRatio.new(*args)
6
6
  [[aspect.width, aspect.height], [aspect.x, aspect.y]]
7
7
  end
8
8
 
@@ -1,13 +1,13 @@
1
1
  require File.dirname(__FILE__) + '/../../../spec_helper'
2
2
 
3
- describe Prawn::Svg::Calculators::DocumentSizing do
3
+ describe Prawn::SVG::Calculators::DocumentSizing do
4
4
  let(:attributes) do
5
5
  {"width" => "150", "height" => "200", "viewBox" => "0 -30 300 800", "preserveAspectRatio" => "xMaxYMid meet"}
6
6
  end
7
7
 
8
8
  let(:bounds) { [1200, 800] }
9
9
 
10
- let(:sizing) { Prawn::Svg::Calculators::DocumentSizing.new(bounds, attributes) }
10
+ let(:sizing) { Prawn::SVG::Calculators::DocumentSizing.new(bounds, attributes) }
11
11
 
12
12
  describe "#initialize" do
13
13
  it "takes bounds and a set of attributes and calls set_from_attributes" do
@@ -17,7 +17,7 @@ describe Prawn::Svg::Calculators::DocumentSizing do
17
17
  end
18
18
 
19
19
  describe "#set_from_attributes" do
20
- let(:sizing) { Prawn::Svg::Calculators::DocumentSizing.new(bounds) }
20
+ let(:sizing) { Prawn::SVG::Calculators::DocumentSizing.new(bounds) }
21
21
 
22
22
  it "sets ivars from the passed-in attributes hash" do
23
23
  sizing.set_from_attributes(attributes)
@@ -1,40 +1,61 @@
1
1
  require File.dirname(__FILE__) + '/../../spec_helper'
2
2
 
3
- describe Prawn::Svg::Color do
4
- describe :color_to_hex do
3
+ describe Prawn::SVG::Color do
4
+ describe "::color_to_hex" do
5
5
  it "converts #xxx to a hex value" do
6
- Prawn::Svg::Color.color_to_hex("#9ab").should == "99aabb"
6
+ Prawn::SVG::Color.color_to_hex("#9ab").should == "99aabb"
7
7
  end
8
8
 
9
9
  it "converts #xxxxxx to a hex value" do
10
- Prawn::Svg::Color.color_to_hex("#9ab123").should == "9ab123"
10
+ Prawn::SVG::Color.color_to_hex("#9ab123").should == "9ab123"
11
11
  end
12
12
 
13
13
  it "converts an html colour name to a hex value" do
14
- Prawn::Svg::Color.color_to_hex("White").should == "ffffff"
14
+ Prawn::SVG::Color.color_to_hex("White").should == "ffffff"
15
15
  end
16
16
 
17
17
  it "converts an rgb string to a hex value" do
18
- Prawn::Svg::Color.color_to_hex("rgb(16, 32, 48)").should == "102030"
19
- Prawn::Svg::Color.color_to_hex("rgb(-5, 50%, 120%)").should == "007fff"
18
+ Prawn::SVG::Color.color_to_hex("rgb(16, 32, 48)").should == "102030"
19
+ Prawn::SVG::Color.color_to_hex("rgb(-5, 50%, 120%)").should == "007fff"
20
20
  end
21
21
 
22
22
  it "scans the string and finds the first colour it can parse" do
23
- Prawn::Svg::Color.color_to_hex("function(#someurl, 0) nonexistent rgb( 3 ,4,5 ) white").should == "030405"
23
+ Prawn::SVG::Color.color_to_hex("function(#someurl, 0) nonexistent rgb( 3 ,4,5 ) white").should == "030405"
24
24
  end
25
25
 
26
26
  it "ignores url()s" do
27
- expect(Prawn::Svg::Color.color_to_hex("url(#someplace) red")).to eq 'ff0000'
27
+ expect(Prawn::SVG::Color.color_to_hex("url(#someplace) red")).to eq 'ff0000'
28
28
  end
29
29
 
30
- it "returns nil if the color doesn't exist" do
31
- expect(Prawn::Svg::Color.color_to_hex("blurble")).to be nil
30
+ it "returns black if the color doesn't exist" do
31
+ expect(Prawn::SVG::Color.color_to_hex("blurble")).to eq '000000'
32
32
  end
33
33
 
34
- it "raises UnresolvableURLWithNoFallbackError if there's no fallback after a url()" do
35
- expect {
36
- Prawn::Svg::Color.color_to_hex("url(#someplace)")
37
- }.to raise_error(Prawn::Svg::Color::UnresolvableURLWithNoFallbackError)
34
+ it "returns nil if there's no fallback after a url()" do
35
+ expect(Prawn::SVG::Color.color_to_hex("url(#someplace)")).to be nil
36
+ end
37
+ end
38
+
39
+ describe "::parse" do
40
+ let(:gradients) { {"flan" => flan_gradient, "drob" => drob_gradient} }
41
+ let(:flan_gradient) { double }
42
+ let(:drob_gradient) { double }
43
+
44
+ it "returns a list of all colors parsed, ignoring impossible or non-existent colors" do
45
+ results = Prawn::SVG::Color.parse("url(#nope) url(#flan) blurble green #123", gradients)
46
+ expect(results).to eq [
47
+ flan_gradient,
48
+ Prawn::SVG::Color::Hex.new("008000"),
49
+ Prawn::SVG::Color::Hex.new("112233")
50
+ ]
51
+ end
52
+
53
+ it "appends black to the list if there aren't any url() references" do
54
+ results = Prawn::SVG::Color.parse("blurble green", gradients)
55
+ expect(results).to eq [
56
+ Prawn::SVG::Color::Hex.new("008000"),
57
+ Prawn::SVG::Color::Hex.new("000000")
58
+ ]
38
59
  end
39
60
  end
40
61
  end
@@ -1,13 +1,13 @@
1
1
  require File.dirname(__FILE__) + '/../../spec_helper'
2
2
 
3
- describe Prawn::Svg::Document do
3
+ describe Prawn::SVG::Document do
4
4
  before do
5
- sizing = instance_double(Prawn::Svg::Calculators::DocumentSizing, viewport_width: 600, viewport_height: 400, viewport_diagonal: 500, :requested_width= => nil, :requested_height= => nil)
5
+ sizing = instance_double(Prawn::SVG::Calculators::DocumentSizing, viewport_width: 600, viewport_height: 400, viewport_diagonal: 500, :requested_width= => nil, :requested_height= => nil)
6
6
  expect(sizing).to receive(:calculate)
7
- expect(Prawn::Svg::Calculators::DocumentSizing).to receive(:new).and_return(sizing)
7
+ expect(Prawn::SVG::Calculators::DocumentSizing).to receive(:new).and_return(sizing)
8
8
  end
9
9
 
10
- let(:document) { Prawn::Svg::Document.new("<svg></svg>", [100, 100], {}) }
10
+ let(:document) { Prawn::SVG::Document.new("<svg></svg>", [100, 100], {}) }
11
11
 
12
12
  describe :points do
13
13
  it "converts a variety of measurement units to points" do
@@ -0,0 +1,125 @@
1
+ require 'spec_helper'
2
+
3
+ describe Prawn::SVG::Elements::Base do
4
+ let(:svg) { "<svg></svg>" }
5
+ let(:document) { Prawn::SVG::Document.new(svg, [800, 600], {}) }
6
+ let(:parent_calls) { [] }
7
+ let(:element) { Prawn::SVG::Elements::Base.new(document, document.root, parent_calls, {}) }
8
+
9
+ describe "#initialize" do
10
+ let(:svg) { '<something id="hello"/>' }
11
+
12
+ it "adds itself to the elements_by_id hash if an id attribute is supplied" do
13
+ element
14
+ expect(document.elements_by_id["hello"]).to eq element
15
+ end
16
+ end
17
+
18
+ describe "#process" do
19
+ it "calls #parse and #apply so subclasses can parse the element" do
20
+ expect(element).to receive(:parse).ordered
21
+ expect(element).to receive(:apply).ordered
22
+ element.process
23
+ end
24
+
25
+ describe "applying calls from the standard attributes" do
26
+ let(:svg) do
27
+ <<-SVG
28
+ <something transform="rotate(90)" fill-opacity="0.5" fill="red" stroke="blue" stroke-width="5" font-family="Helvetica"/>
29
+ SVG
30
+ end
31
+
32
+ it "appends the relevant calls" do
33
+ element.process
34
+ expect(element.base_calls).to eq [
35
+ ["rotate", [-90.0, {origin: [0, 150.0]}], [
36
+ ["transparent", [0.5, 1], [
37
+ ["fill_color", ["ff0000"], []],
38
+ ["stroke_color", ["0000ff"], []],
39
+ ["line_width", [5.0], []],
40
+ ["font", ["Helvetica", {:style=>:normal}], [
41
+ ["fill_and_stroke", [], []]
42
+ ]]
43
+ ]]
44
+ ]]
45
+ ]
46
+ end
47
+ end
48
+
49
+ it "appends calls to the parent element" do
50
+ expect(element).to receive(:apply) do
51
+ element.send :add_call, "test", "argument"
52
+ end
53
+
54
+ element.process
55
+ expect(element.parent_calls).to eq [["end_path", [], [["test", ["argument"], []]]]]
56
+ end
57
+
58
+ it "quietly absorbs a SkipElementQuietly exception" do
59
+ expect(element).to receive(:parse).and_raise(Prawn::SVG::Elements::Base::SkipElementQuietly)
60
+ expect(element).to_not receive(:apply)
61
+ element.process
62
+ expect(document.warnings).to be_empty
63
+ end
64
+
65
+ it "absorbs a SkipElementError exception, logging a warning" do
66
+ expect(element).to receive(:parse).and_raise(Prawn::SVG::Elements::Base::SkipElementError, "hello")
67
+ expect(element).to_not receive(:apply)
68
+ element.process
69
+ expect(document.warnings).to eq ["hello"]
70
+ end
71
+ end
72
+
73
+ describe "#parse_fill_and_stroke_attributes_and_call" do
74
+ before do
75
+ element.send(:combine_attributes_and_style_declarations)
76
+ end
77
+
78
+ subject { element.send :parse_fill_and_stroke_attributes_and_call }
79
+
80
+ it "doesn't change anything if no fill attribute provided" do
81
+ subject
82
+ expect(element.state[:fill]).to be nil
83
+ end
84
+
85
+ it "doesn't change anything if 'inherit' fill attribute provided" do
86
+ element.attributes['fill'] = 'inherit'
87
+ subject
88
+ expect(element.state[:fill]).to be nil
89
+ end
90
+
91
+ it "turns off filling if 'none' fill attribute provided" do
92
+ element.attributes['fill'] = 'none'
93
+ subject
94
+ expect(element.state[:fill]).to be false
95
+ end
96
+
97
+ it "uses the fill attribute's color" do
98
+ expect(element).to receive(:add_call).with('fill_color', 'ff0000')
99
+ element.attributes['fill'] = 'red'
100
+ subject
101
+ expect(element.state[:fill]).to be true
102
+ end
103
+
104
+ it "uses black if the fill attribute's color is unparseable" do
105
+ expect(element).to receive(:add_call).with('fill_color', '000000')
106
+ element.attributes['fill'] = 'blarble'
107
+ subject
108
+ expect(element.state[:fill]).to be true
109
+ end
110
+
111
+ it "uses the color attribute if 'currentColor' fill attribute provided" do
112
+ expect(element).to receive(:add_call).with('fill_color', 'ff0000')
113
+ element.attributes['fill'] = 'currentColor'
114
+ element.attributes['color'] = 'red'
115
+ subject
116
+ expect(element.state[:fill]).to be true
117
+ end
118
+
119
+ it "turns off filling if UnresolvableURLWithNoFallbackError is raised" do
120
+ element.attributes['fill'] = 'url()'
121
+ subject
122
+ expect(element.state[:fill]).to be false
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe Prawn::SVG::Elements::Gradient do
4
+ let(:document) { Prawn::SVG::Document.new(svg, [800, 600], {width: 800, height: 600}) }
5
+ let(:element) { Prawn::SVG::Elements::Gradient.new(document, document.root, [], {}) }
6
+
7
+ before do
8
+ allow(element).to receive(:assert_compatible_prawn_version)
9
+ element.process
10
+ end
11
+
12
+ describe "object bounding box" do
13
+ let(:svg) do
14
+ <<-SVG
15
+ <linearGradient id="flag" x1="0" x2="0.2" y1="0" y2="1">
16
+ <stop offset="25%" stop-color="red"/>
17
+ <stop offset="50%" stop-color="white"/>
18
+ <stop offset="75%" stop-color="blue"/>
19
+ </linearGradient>
20
+ SVG
21
+ end
22
+
23
+ it "is stored in the document gradients table" do
24
+ expect(document.gradients["flag"]).to eq element
25
+ end
26
+
27
+ it "returns correct gradient arguments for an element" do
28
+ arguments = element.gradient_arguments(double(bounding_box: [100, 100, 200, 0]))
29
+ expect(arguments).to eq(
30
+ from: [100.0, 100.0],
31
+ to: [120.0, 0.0],
32
+ stops: [[0, "ff0000"], [0.25, "ff0000"], [0.5, "ffffff"], [0.75, "0000ff"], [1, "0000ff"]]
33
+ )
34
+ end
35
+
36
+ it "returns nil if the element doesn't have a bounding box" do
37
+ arguments = element.gradient_arguments(double(bounding_box: nil))
38
+ expect(arguments).to be nil
39
+ end
40
+ end
41
+
42
+ describe "user space on use" do
43
+ let(:svg) do
44
+ <<-SVG
45
+ <linearGradient id="flag" gradientUnits="userSpaceOnUse" x1="100" y1="500" x2="200" y2="600">
46
+ <stop offset="0" stop-color="red"/>
47
+ <stop offset="1" stop-color="blue"/>
48
+ </linearGradient>
49
+ SVG
50
+ end
51
+
52
+ it "returns correct gradient arguments for an element" do
53
+ arguments = element.gradient_arguments(double)
54
+ expect(arguments).to eq(
55
+ from: [100.0, 100.0],
56
+ to: [200.0, 0.0],
57
+ stops: [[0, "ff0000"], [1, "0000ff"]]
58
+ )
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,123 @@
1
+ require 'spec_helper'
2
+
3
+ describe Prawn::SVG::Elements::Path do
4
+ let(:source) { double(name: "path", attributes: {}) }
5
+ let(:path) { Prawn::SVG::Elements::Path.new(nil, source, [], {}) }
6
+
7
+ before do
8
+ allow(path).to receive(:attributes).and_return("d" => d)
9
+ end
10
+
11
+ describe "command parsing" do
12
+ context "with a valid path" do
13
+ let(:d) { "A12.34 -56.78 89B4 5 12-34 -.5.7+3 2.3e3 4e4 4e+4 c31,-2e-5C 6,7 T QX 0 Z" }
14
+
15
+ it "correctly parses" do
16
+ calls = []
17
+ path.stub(:run_path_command) {|*args| calls << args}
18
+ path.parse
19
+
20
+ calls.should == [
21
+ ["A", [12.34, -56.78, 89]],
22
+ ["B", [4, 5, 12, -34, -0.5, 0.7, 3, 2.3e3, 4e4, 4e4]],
23
+ ["c", [31, -2e-5]],
24
+ ["C", [6, 7]],
25
+ ["T", []],
26
+ ["Q", []],
27
+ ["X", [0]],
28
+ ["Z", []]
29
+ ]
30
+ end
31
+ end
32
+
33
+ context "with m and M commands" do
34
+ let(:d) { "M 1,2 3,4 m 5,6 7,8" }
35
+
36
+ it "treats subsequent points to m/M command as relative/absolute depending on command" do
37
+ [
38
+ ["M", [1,2,3,4]],
39
+ ["L", [3,4]],
40
+ ["m", [5,6,7,8]],
41
+ ["l", [7,8]]
42
+ ].each do |args|
43
+ path.should_receive(:run_path_command).with(*args).and_call_original
44
+ end
45
+
46
+ path.parse
47
+ end
48
+ end
49
+
50
+ context "with an empty path" do
51
+ let(:d) { "" }
52
+
53
+ it "correctly parses" do
54
+ path.should_not_receive(:run_path_command)
55
+ path.parse
56
+ end
57
+ end
58
+
59
+ context "with a path with invalid characters" do
60
+ let(:d) { "M 10 % 20" }
61
+
62
+ it "raises" do
63
+ expect { path.parse }.to raise_error(Prawn::SVG::Elements::Base::SkipElementError)
64
+ end
65
+ end
66
+
67
+ context "with a path with numerical data before a command letter" do
68
+ let(:d) { "M 10 % 20" }
69
+
70
+ it "raises" do
71
+ expect { path.parse }.to raise_error(Prawn::SVG::Elements::Base::SkipElementError)
72
+ end
73
+ end
74
+ end
75
+
76
+ context "when given an A path" do
77
+ subject { path.parse; path.commands }
78
+
79
+ context "that is pretty normal" do
80
+ let(:d) { "M 100 200 A 10 10 0 0 1 200 200" }
81
+
82
+ it "uses bezier curves to approximate an arc path" do
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]]
87
+ ]
88
+ end
89
+ end
90
+
91
+ context "with an identical start and end point" do
92
+ let(:d) { "M 100 200 A 30 30 0 0 1 100 200" }
93
+
94
+ it "ignores the path" do
95
+ expect(subject).to eq [
96
+ ["move_to", [100.0, 200.0]]
97
+ ]
98
+ end
99
+ end
100
+
101
+ context "with an rx of 0" do
102
+ let(:d) { "M 100 200 A 0 10 0 0 1 200 200" }
103
+
104
+ it "substitutes a line_to" do
105
+ expect(subject).to eq [
106
+ ["move_to", [100.0, 200.0]],
107
+ ["line_to", [200.0, 200.0]]
108
+ ]
109
+ end
110
+ end
111
+
112
+ context "with an ry of 0" do
113
+ let(:d) { "M 100 200 A 10 0 0 0 1 200 200" }
114
+
115
+ it "substitutes a line_to" do
116
+ expect(subject).to eq [
117
+ ["move_to", [100.0, 200.0]],
118
+ ["line_to", [200.0, 200.0]]
119
+ ]
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Prawn::SVG::Elements::Style do
4
+ let(:svg) do
5
+ <<-SVG
6
+ <style>a
7
+ before&gt;
8
+ x <![CDATA[ y
9
+ inside <>&gt;
10
+ k ]]> j
11
+ after
12
+ z</style>
13
+ SVG
14
+ end
15
+
16
+ let(:document) { Prawn::SVG::Document.new(svg, [800, 600], {}) }
17
+ let(:element) { Prawn::SVG::Elements::Style.new(document, document.root, [], {}) }
18
+
19
+ it "correctly collects the style information in a <style> tag" do
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")
21
+ element.process
22
+ end
23
+ end