prawn-svg 0.21.0 → 0.22.1

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 (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