prawn-svg 0.15.0.0 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -0
  3. data/README.md +51 -20
  4. data/lib/prawn/svg/attributes/clip_path.rb +17 -0
  5. data/lib/prawn/svg/attributes/color.rb +5 -0
  6. data/lib/prawn/svg/attributes/display.rb +5 -0
  7. data/lib/prawn/svg/attributes/font.rb +38 -0
  8. data/lib/prawn/svg/attributes/opacity.rb +15 -0
  9. data/lib/prawn/svg/attributes/stroke.rb +35 -0
  10. data/lib/prawn/svg/attributes/transform.rb +50 -0
  11. data/lib/prawn/svg/attributes.rb +6 -0
  12. data/lib/prawn/svg/calculators/aspect_ratio.rb +58 -0
  13. data/lib/prawn/svg/calculators/document_sizing.rb +99 -0
  14. data/lib/prawn/svg/calculators/pixels.rb +21 -0
  15. data/lib/prawn/svg/color.rb +197 -12
  16. data/lib/prawn/svg/css.rb +40 -0
  17. data/lib/prawn/svg/document.rb +37 -48
  18. data/lib/prawn/svg/elements/base.rb +238 -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 +120 -0
  23. data/lib/prawn/svg/elements/ignored.rb +5 -0
  24. data/lib/prawn/svg/elements/image.rb +81 -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 +13 -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/elements.rb +33 -0
  35. data/lib/prawn/svg/extension.rb +4 -4
  36. data/lib/prawn/svg/font.rb +10 -92
  37. data/lib/prawn/svg/font_registry.rb +73 -0
  38. data/lib/prawn/svg/interface.rb +91 -31
  39. data/lib/prawn/svg/loaders/data.rb +18 -0
  40. data/lib/prawn/svg/loaders/file.rb +66 -0
  41. data/lib/prawn/svg/loaders/web.rb +28 -0
  42. data/lib/prawn/svg/state.rb +39 -0
  43. data/lib/prawn/svg/ttf.rb +61 -0
  44. data/lib/prawn/svg/url_loader.rb +46 -0
  45. data/lib/prawn/svg/version.rb +2 -2
  46. data/lib/prawn-svg.rb +20 -6
  47. data/prawn-svg.gemspec +8 -5
  48. data/spec/integration_spec.rb +141 -0
  49. data/spec/prawn/svg/attributes/font_spec.rb +52 -0
  50. data/spec/prawn/svg/attributes/transform_spec.rb +56 -0
  51. data/spec/prawn/svg/calculators/aspect_ratio_spec.rb +95 -0
  52. data/spec/prawn/svg/calculators/document_sizing_spec.rb +128 -0
  53. data/spec/prawn/svg/color_spec.rb +43 -8
  54. data/spec/prawn/svg/css_spec.rb +24 -0
  55. data/spec/prawn/svg/document_spec.rb +48 -19
  56. data/spec/prawn/svg/elements/base_spec.rb +147 -0
  57. data/spec/prawn/svg/elements/gradient_spec.rb +61 -0
  58. data/spec/prawn/svg/elements/path_spec.rb +123 -0
  59. data/spec/prawn/svg/elements/style_spec.rb +23 -0
  60. data/spec/prawn/svg/elements/text_spec.rb +61 -0
  61. data/spec/prawn/svg/font_registry_spec.rb +54 -0
  62. data/spec/prawn/svg/font_spec.rb +1 -28
  63. data/spec/prawn/svg/interface_spec.rb +94 -0
  64. data/spec/prawn/svg/loaders/data_spec.rb +55 -0
  65. data/spec/prawn/svg/loaders/file_spec.rb +84 -0
  66. data/spec/prawn/svg/loaders/web_spec.rb +37 -0
  67. data/spec/prawn/svg/ttf_spec.rb +32 -0
  68. data/spec/prawn/svg/url_loader_spec.rb +112 -0
  69. data/spec/sample_images/mushroom-long.jpg +0 -0
  70. data/spec/sample_images/mushroom-wide.jpg +0 -0
  71. data/spec/sample_svg/cap_styles.svg +13 -0
  72. data/spec/sample_svg/display_none.svg +13 -0
  73. data/spec/sample_svg/gistfile1.svg +36 -0
  74. data/spec/sample_svg/gradients.svg +40 -0
  75. data/spec/sample_svg/hidden_paths.svg +6 -0
  76. data/spec/sample_svg/image01.svg +31 -31
  77. data/spec/sample_svg/image03.svg +30 -0
  78. data/spec/sample_svg/negminy.svg +25 -0
  79. data/spec/sample_svg/no_width_or_height.svg +4 -0
  80. data/spec/sample_svg/path.svg +5 -0
  81. data/spec/sample_svg/pie_piece.svg +7 -0
  82. data/spec/sample_svg/preserve-space.svg +19 -0
  83. data/spec/sample_svg/rect02.svg +8 -11
  84. data/spec/sample_svg/tspan03-cc.svg +21 -0
  85. data/spec/sample_svg/viewbox.svg +4 -0
  86. data/spec/sample_svg/viewport.svg +23 -0
  87. data/spec/sample_ttf/OpenSans-SemiboldItalic.ttf +0 -0
  88. data/spec/spec_helper.rb +24 -2
  89. metadata +150 -36
  90. data/lib/prawn/svg/element.rb +0 -237
  91. data/lib/prawn/svg/parser/image.rb +0 -134
  92. data/lib/prawn/svg/parser/path.rb +0 -374
  93. data/lib/prawn/svg/parser/text.rb +0 -66
  94. data/lib/prawn/svg/parser.rb +0 -233
  95. data/spec/lib/parser_spec.rb +0 -55
  96. data/spec/lib/path_spec.rb +0 -54
  97. data/spec/lib/svg_spec.rb +0 -47
  98. data/spec/prawn/svg/element_spec.rb +0 -36
  99. data/spec/prawn/svg/parser/text_spec.rb +0 -4
@@ -0,0 +1,128 @@
1
+ require File.dirname(__FILE__) + '/../../../spec_helper'
2
+
3
+ describe Prawn::SVG::Calculators::DocumentSizing do
4
+ let(:attributes) do
5
+ {"width" => "150", "height" => "200", "viewBox" => "0 -30 300 800", "preserveAspectRatio" => "xMaxYMid meet"}
6
+ end
7
+
8
+ let(:bounds) { [1200, 800] }
9
+
10
+ let(:sizing) { Prawn::SVG::Calculators::DocumentSizing.new(bounds, attributes) }
11
+
12
+ describe "#initialize" do
13
+ it "takes bounds and a set of attributes and calls set_from_attributes" do
14
+ expect(sizing.instance_variable_get :@bounds).to eq bounds
15
+ expect(sizing.instance_variable_get :@document_width).to eq "150"
16
+ end
17
+ end
18
+
19
+ describe "#set_from_attributes" do
20
+ let(:sizing) { Prawn::SVG::Calculators::DocumentSizing.new(bounds) }
21
+
22
+ it "sets ivars from the passed-in attributes hash" do
23
+ sizing.set_from_attributes(attributes)
24
+ expect(sizing.instance_variable_get :@document_width).to eq "150"
25
+ expect(sizing.instance_variable_get :@document_height).to eq "200"
26
+ expect(sizing.instance_variable_get :@view_box).to eq "0 -30 300 800"
27
+ expect(sizing.instance_variable_get :@preserve_aspect_ratio).to eq "xMaxYMid meet"
28
+ end
29
+ end
30
+
31
+ describe "#calculate" do
32
+ it "calculates the document sizing measurements for a given set of inputs" do
33
+ sizing.calculate
34
+ expect(sizing.x_offset).to eq -75 / 0.25
35
+ expect(sizing.y_offset).to eq -30
36
+ expect(sizing.x_scale).to eq 0.25
37
+ expect(sizing.y_scale).to eq 0.25
38
+ expect(sizing.viewport_width).to eq 300
39
+ expect(sizing.viewport_height).to eq 800
40
+ expect(sizing.output_width).to eq 150
41
+ expect(sizing.output_height).to eq 200
42
+ end
43
+
44
+ it "scales again based on requested width" do
45
+ sizing.requested_width = 75
46
+ sizing.calculate
47
+ expect(sizing.x_scale).to eq 0.125
48
+ expect(sizing.y_scale).to eq 0.125
49
+ expect(sizing.viewport_width).to eq 300
50
+ expect(sizing.viewport_height).to eq 800
51
+ expect(sizing.output_width).to eq 75
52
+ expect(sizing.output_height).to eq 100
53
+ end
54
+
55
+ it "scales again based on requested height" do
56
+ sizing.requested_height = 100
57
+ sizing.calculate
58
+ expect(sizing.x_scale).to eq 0.125
59
+ expect(sizing.y_scale).to eq 0.125
60
+ expect(sizing.viewport_width).to eq 300
61
+ expect(sizing.viewport_height).to eq 800
62
+ expect(sizing.output_width).to eq 75
63
+ expect(sizing.output_height).to eq 100
64
+ end
65
+
66
+ it "correctly handles % values being passed in" do
67
+ sizing.document_width = sizing.document_height = "50%"
68
+ sizing.calculate
69
+ expect(sizing.output_width).to eq 600
70
+ expect(sizing.output_height).to eq 400
71
+ end
72
+
73
+ context "when SVG does not specify width and height" do
74
+ context "when a viewBox is specified" do
75
+ let(:attributes) { {"viewBox" => "0 0 100 200"} }
76
+
77
+ it "defaults to 100% width and the width times aspect ratio in height" do
78
+ sizing.calculate
79
+ expect(sizing.viewport_width).to eq 100
80
+ expect(sizing.viewport_height).to eq 200
81
+ expect(sizing.output_width).to eq 1200
82
+ expect(sizing.output_height).to eq 2400
83
+ end
84
+ end
85
+
86
+ context "when a requested width and height are supplied" do
87
+ let(:attributes) { {} }
88
+
89
+ it "uses the requested width and height" do
90
+ sizing.requested_width = 550
91
+ sizing.requested_height = 400
92
+ sizing.calculate
93
+
94
+ expect(sizing.viewport_width).to eq 550
95
+ expect(sizing.viewport_height).to eq 400
96
+ expect(sizing.output_width).to eq 550
97
+ expect(sizing.output_height).to eq 400
98
+ end
99
+ end
100
+
101
+ context "when a viewBox and a requested width/height are supplied" do
102
+ let(:attributes) { {"viewBox" => "0 0 100 200"} }
103
+
104
+ it "uses the requested width and height" do
105
+ sizing.requested_width = 550
106
+ sizing.requested_height = 400
107
+ sizing.calculate
108
+
109
+ expect(sizing.viewport_width).to eq 100
110
+ expect(sizing.viewport_height).to eq 200
111
+ expect(sizing.output_width).to eq 550
112
+ expect(sizing.output_height).to eq 400
113
+ end
114
+ end
115
+
116
+ context "when neither viewBox nor requested width/height specified" do
117
+ let(:attributes) { {} }
118
+
119
+ it "defaults to 300x150, because that's what HTML does" do
120
+ sizing.calculate
121
+
122
+ expect(sizing.output_width).to eq 300
123
+ expect(sizing.output_height).to eq 150
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -1,26 +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
+ end
25
+
26
+ it "ignores url()s" do
27
+ expect(Prawn::SVG::Color.color_to_hex("url(#someplace) red")).to eq 'ff0000'
28
+ end
29
+
30
+ it "returns black if the color doesn't exist" do
31
+ expect(Prawn::SVG::Color.color_to_hex("blurble")).to eq '000000'
32
+ end
33
+
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
+ ]
24
59
  end
25
60
  end
26
61
  end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Prawn::SVG::CSS do
4
+ describe "#parse_font_family_string" do
5
+ it "correctly handles quotes and escaping" do
6
+ tests = {
7
+ "" => [],
8
+ "font" => ["font"],
9
+ "font name, other font" => ["font name", "other font"],
10
+ "'font name', other font" => ["font name", "other font"],
11
+ "'font, name', other font" => ["font, name", "other font"],
12
+ '"font name", other font' => ["font name", "other font"],
13
+ '"font, name", other font' => ["font, name", "other font"],
14
+ 'weird \\" name' => ['weird " name'],
15
+ 'weird\\, name' => ["weird, name"],
16
+ ' stupid , spacing ' => ["stupid", "spacing"],
17
+ }
18
+
19
+ tests.each do |string, expected|
20
+ expect(Prawn::SVG::CSS.parse_font_family_string(string)).to eq expected
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,27 +1,56 @@
1
1
  require File.dirname(__FILE__) + '/../../spec_helper'
2
2
 
3
- describe Prawn::Svg::Document do
4
- before(:each) do
5
- @document = Prawn::Svg::Document.new("<svg></svg>", [100, 100], {})
3
+ describe Prawn::SVG::Document do
4
+ let(:bounds) { [100, 100] }
5
+ let(:options) { {} }
6
+
7
+ describe "#initialize" do
8
+ context "when unparsable XML is provided" do
9
+ let(:svg) { "this isn't SVG data" }
10
+
11
+ it "raises an exception" do
12
+ expect {
13
+ Prawn::SVG::Document.new(svg, bounds, options)
14
+ }.to raise_error Prawn::SVG::Document::InvalidSVGData, "The data supplied is not a valid SVG document."
15
+ end
16
+ end
17
+
18
+ context "when the user passes in a filename instead of SVG data" do
19
+ let(:svg) { "some_file.svg" }
20
+
21
+ it "raises an exception letting them know what they've done" do
22
+ expect {
23
+ Prawn::SVG::Document.new(svg, bounds, options)
24
+ }.to raise_error Prawn::SVG::Document::InvalidSVGData, "The data supplied is not a valid SVG document. It looks like you've supplied a filename instead; use IO.read(filename) to get the data before you pass it to prawn-svg."
25
+ end
26
+ end
6
27
  end
7
28
 
8
- describe :points do
29
+ describe "#points" do
30
+ before do
31
+ sizing = instance_double(Prawn::SVG::Calculators::DocumentSizing, viewport_width: 600, viewport_height: 400, viewport_diagonal: 500, :requested_width= => nil, :requested_height= => nil)
32
+ expect(sizing).to receive(:calculate)
33
+ expect(Prawn::SVG::Calculators::DocumentSizing).to receive(:new).and_return(sizing)
34
+ end
35
+
36
+ let(:document) { Prawn::SVG::Document.new("<svg></svg>", [100, 100], {}) }
37
+
9
38
  it "converts a variety of measurement units to points" do
10
- @document.send(:points, 32).should == 32.0
11
- @document.send(:points, 32.0).should == 32.0
12
- @document.send(:points, "32").should == 32.0
13
- @document.send(:points, "32unknown").should == 32.0
14
- @document.send(:points, "32pt").should == 32.0
15
- @document.send(:points, "32in").should == 32.0 * 72
16
- @document.send(:points, "32ft").should == 32.0 * 72 * 12
17
- @document.send(:points, "32mm").should be_within(0.0001).of(32 * 72 * 0.0393700787)
18
- @document.send(:points, "32cm").should be_within(0.0001).of(32 * 72 * 0.393700787)
19
- @document.send(:points, "32m").should be_within(0.0001).of(32 * 72 * 39.3700787)
20
-
21
- @document.send :instance_variable_set, "@actual_width", 600
22
- @document.send :instance_variable_set, "@actual_height", 400
23
- @document.send(:points, "50%").should == 300
24
- @document.send(:points, "50%", :y).should == 200
39
+ document.send(:points, 32).should == 32.0
40
+ document.send(:points, 32.0).should == 32.0
41
+ document.send(:points, "32").should == 32.0
42
+ document.send(:points, "32unknown").should == 32.0
43
+ document.send(:points, "32pt").should == 32.0
44
+ document.send(:points, "32in").should == 32.0 * 72
45
+ document.send(:points, "32ft").should == 32.0 * 72 * 12
46
+ document.send(:points, "32pc").should == 32.0 * 15
47
+ document.send(:points, "32mm").should be_within(0.0001).of(32 * 72 * 0.0393700787)
48
+ document.send(:points, "32cm").should be_within(0.0001).of(32 * 72 * 0.393700787)
49
+ document.send(:points, "32m").should be_within(0.0001).of(32 * 72 * 39.3700787)
50
+
51
+ document.send(:points, "50%").should == 250
52
+ document.send(:points, "50%", :x).should == 300
53
+ document.send(:points, "50%", :y).should == 200
25
54
  end
26
55
  end
27
56
  end
@@ -0,0 +1,147 @@
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], {}, font_registry: Prawn::SVG::FontRegistry.new("Helvetica" => {:normal => nil})) }
6
+ let(:parent_calls) { [] }
7
+ let(:element) { Prawn::SVG::Elements::Base.new(document, document.root, parent_calls, Prawn::SVG::State.new) }
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 [["fill", [], [["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 true
83
+
84
+ element.state.fill = false
85
+ subject
86
+ expect(element.state.fill).to be false
87
+ end
88
+
89
+ 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
95
+ subject
96
+ expect(element.state.fill).to be false
97
+ end
98
+
99
+ it "turns off filling if 'none' fill attribute provided" do
100
+ element.attributes['fill'] = 'none'
101
+ subject
102
+ expect(element.state.fill).to be false
103
+ end
104
+
105
+ it "uses the fill attribute's color" do
106
+ expect(element).to receive(:add_call).with('fill_color', 'ff0000')
107
+ element.attributes['fill'] = 'red'
108
+ subject
109
+ expect(element.state.fill).to be true
110
+ end
111
+
112
+ it "uses black if the fill attribute's color is unparseable" do
113
+ expect(element).to receive(:add_call).with('fill_color', '000000')
114
+ element.attributes['fill'] = 'blarble'
115
+ subject
116
+ expect(element.state.fill).to be true
117
+ end
118
+
119
+ it "uses the color attribute if 'currentColor' fill attribute provided" do
120
+ 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
124
+ subject
125
+ expect(element.state.fill).to be true
126
+ end
127
+
128
+ context "with a color attribute defined on a parent element" do
129
+ let(:svg) { '<svg style="color: green;"><g style="color: red;"><rect width="10" height="10" style="fill: currentColor;"></rect></g></svg>' }
130
+ let(:element) { Prawn::SVG::Elements::Root.new(document, document.root, parent_calls) }
131
+ let(:flattened_calls) { flatten_calls(element.base_calls) }
132
+
133
+ it "uses the parent's color element if 'currentColor' fill attribute provided" do
134
+ element.process
135
+
136
+ expect(flattened_calls).to include ['fill_color', ['ff0000']]
137
+ expect(flattened_calls).not_to include ['fill_color', ['00ff00']]
138
+ end
139
+ end
140
+
141
+ it "turns off filling if UnresolvableURLWithNoFallbackError is raised" do
142
+ element.attributes['fill'] = 'url()'
143
+ subject
144
+ expect(element.state.fill).to be false
145
+ end
146
+ end
147
+ 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, [], Prawn::SVG::State.new) }
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