prawn-svg 0.35.1 → 0.36.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +3 -5
  3. data/README.md +4 -7
  4. data/lib/prawn/svg/attributes/opacity.rb +23 -8
  5. data/lib/prawn/svg/attributes/stroke.rb +7 -9
  6. data/lib/prawn/svg/attributes/transform.rb +1 -1
  7. data/lib/prawn/svg/calculators/pixels.rb +9 -22
  8. data/lib/prawn/svg/color.rb +58 -59
  9. data/lib/prawn/svg/css/font_parser.rb +46 -0
  10. data/lib/prawn/svg/document.rb +5 -1
  11. data/lib/prawn/svg/elements/base.rb +22 -30
  12. data/lib/prawn/svg/elements/gradient.rb +99 -74
  13. data/lib/prawn/svg/elements/line.rb +1 -1
  14. data/lib/prawn/svg/elements/marker.rb +2 -0
  15. data/lib/prawn/svg/elements/root.rb +1 -1
  16. data/lib/prawn/svg/elements/text_component.rb +3 -3
  17. data/lib/prawn/svg/funciri.rb +14 -0
  18. data/lib/prawn/svg/gradient_renderer.rb +313 -0
  19. data/lib/prawn/svg/length.rb +43 -0
  20. data/lib/prawn/svg/paint.rb +67 -0
  21. data/lib/prawn/svg/percentage.rb +24 -0
  22. data/lib/prawn/svg/properties.rb +208 -104
  23. data/lib/prawn/svg/renderer.rb +5 -0
  24. data/lib/prawn/svg/state.rb +5 -3
  25. data/lib/prawn/svg/transform_parser.rb +19 -13
  26. data/lib/prawn/svg/transform_utils.rb +37 -0
  27. data/lib/prawn/svg/version.rb +1 -1
  28. data/lib/prawn-svg.rb +7 -3
  29. data/prawn-svg.gemspec +4 -4
  30. data/spec/prawn/svg/attributes/opacity_spec.rb +27 -20
  31. data/spec/prawn/svg/attributes/transform_spec.rb +5 -2
  32. data/spec/prawn/svg/calculators/pixels_spec.rb +1 -2
  33. data/spec/prawn/svg/color_spec.rb +22 -52
  34. data/spec/prawn/svg/elements/base_spec.rb +9 -10
  35. data/spec/prawn/svg/elements/gradient_spec.rb +92 -36
  36. data/spec/prawn/svg/elements/marker_spec.rb +13 -15
  37. data/spec/prawn/svg/funciri_spec.rb +59 -0
  38. data/spec/prawn/svg/length_spec.rb +89 -0
  39. data/spec/prawn/svg/paint_spec.rb +96 -0
  40. data/spec/prawn/svg/pathable_spec.rb +3 -3
  41. data/spec/prawn/svg/pdfmatrix_spec.rb +60 -0
  42. data/spec/prawn/svg/percentage_spec.rb +60 -0
  43. data/spec/prawn/svg/properties_spec.rb +124 -107
  44. data/spec/prawn/svg/transform_parser_spec.rb +13 -13
  45. data/spec/sample_svg/gradient_stress_test.svg +115 -0
  46. metadata +17 -5
  47. data/lib/prawn/svg/extensions/additional_gradient_transforms.rb +0 -23
@@ -10,6 +10,10 @@ describe Prawn::SVG::Attributes::Opacity do
10
10
  @properties = ::Prawn::SVG::Properties.new
11
11
  @state = ::Prawn::SVG::State.new
12
12
  end
13
+
14
+ def computed_properties
15
+ @state.computed_properties
16
+ end
13
17
  end
14
18
 
15
19
  let(:element) { OpacityTestElement.new }
@@ -26,55 +30,58 @@ describe Prawn::SVG::Attributes::Opacity do
26
30
 
27
31
  context 'with opacity' do
28
32
  it 'sets fill and stroke opacity' do
29
- element.properties.opacity = '0.4'
33
+ element.computed_properties.opacity = '0.4'
30
34
 
31
35
  expect(element).to receive(:add_call_and_enter).with('transparent', 0.4, 0.4)
32
36
  subject
33
37
 
34
- expect(element.state.fill_opacity).to eq 0.4
35
- expect(element.state.stroke_opacity).to eq 0.4
38
+ expect(element.state.opacity).to eq 0.4
39
+ expect(element.state.last_fill_opacity).to eq 0.4
40
+ expect(element.state.last_stroke_opacity).to eq 0.4
36
41
  end
37
42
  end
38
43
 
39
44
  context 'with just fill opacity' do
40
45
  it 'sets fill opacity and sets stroke opacity to 1' do
41
- element.properties.fill_opacity = '0.4'
46
+ element.computed_properties.fill_opacity = '0.4'
42
47
 
43
48
  expect(element).to receive(:add_call_and_enter).with('transparent', 0.4, 1)
44
49
  subject
45
50
 
46
- expect(element.state.fill_opacity).to eq 0.4
47
- expect(element.state.stroke_opacity).to eq 1
51
+ expect(element.state.opacity).to eq 1
52
+ expect(element.state.last_fill_opacity).to eq 0.4
53
+ expect(element.state.last_stroke_opacity).to eq 1
48
54
  end
49
55
  end
50
56
 
51
- context 'with an existing fill/stroke opacity' do
57
+ context 'with an existing stroke opacity' do
52
58
  it 'multiplies the new opacity by the old' do
53
- element.state.fill_opacity = 0.5
54
- element.state.stroke_opacity = 0.8
59
+ element.state.opacity = 0.5
55
60
 
56
- element.properties.fill_opacity = '0.4'
57
- element.properties.stroke_opacity = '0.5'
61
+ element.computed_properties.fill_opacity = '0.4'
62
+ element.computed_properties.stroke_opacity = '0.5'
58
63
 
59
- expect(element).to receive(:add_call_and_enter).with('transparent', 0.2, 0.4)
64
+ expect(element).to receive(:add_call_and_enter).with('transparent', 0.2, 0.25)
60
65
  subject
61
66
 
62
- expect(element.state.fill_opacity).to eq 0.2
63
- expect(element.state.stroke_opacity).to eq 0.4
67
+ expect(element.state.opacity).to eq 0.5
68
+ expect(element.state.last_fill_opacity).to eq 0.2
69
+ expect(element.state.last_stroke_opacity).to eq 0.25
64
70
  end
65
71
  end
66
72
 
67
73
  context 'with stroke, fill, and opacity all specified' do
68
74
  it 'choses the lower of them' do
69
- element.properties.fill_opacity = '0.4'
70
- element.properties.stroke_opacity = '0.6'
71
- element.properties.opacity = '0.5'
75
+ element.computed_properties.fill_opacity = '0.4'
76
+ element.computed_properties.stroke_opacity = '0.6'
77
+ element.computed_properties.opacity = '0.5'
72
78
 
73
- expect(element).to receive(:add_call_and_enter).with('transparent', 0.4, 0.5)
79
+ expect(element).to receive(:add_call_and_enter).with('transparent', 0.2, 0.3)
74
80
  subject
75
81
 
76
- expect(element.state.fill_opacity).to eq 0.4
77
- expect(element.state.stroke_opacity).to eq 0.5
82
+ expect(element.state.opacity).to eq 0.5
83
+ expect(element.state.last_fill_opacity).to eq 0.2
84
+ expect(element.state.last_stroke_opacity).to eq 0.3
78
85
  end
79
86
  end
80
87
  end
@@ -20,7 +20,8 @@ describe Prawn::SVG::Attributes::Transform do
20
20
  let(:transform) { 'translate(-5.5)' }
21
21
 
22
22
  it 'passes the transform and executes the returned matrix' do
23
- expect(element).to receive(:parse_transform_attribute).with(transform).and_return([1, 2, 3, 4, 5, 6])
23
+ expect(element).to receive(:parse_transform_attribute).with(transform)
24
+ expect(element).to receive(:matrix_for_pdf).and_return([1, 2, 3, 4, 5, 6])
24
25
  expect(element).to receive(:add_call_and_enter).with('transformation_matrix', 1, 2, 3, 4, 5, 6)
25
26
 
26
27
  element.attributes['transform'] = transform
@@ -32,7 +33,8 @@ describe Prawn::SVG::Attributes::Transform do
32
33
  let(:transform) { 'translate(0)' }
33
34
 
34
35
  it 'does not execute any commands' do
35
- expect(element).to receive(:parse_transform_attribute).with(transform).and_return([1, 0, 0, 1, 0, 0])
36
+ expect(element).to receive(:parse_transform_attribute).with(transform)
37
+ expect(element).to receive(:matrix_for_pdf).and_return([1, 0, 0, 1, 0, 0])
36
38
  expect(element).not_to receive(:add_call_and_enter)
37
39
 
38
40
  element.attributes['transform'] = transform
@@ -43,6 +45,7 @@ describe Prawn::SVG::Attributes::Transform do
43
45
  context 'when transform is blank' do
44
46
  it 'does nothing' do
45
47
  expect(element).not_to receive(:parse_transform_attribute)
48
+ expect(element).not_to receive(:matrix_for_pdf)
46
49
  expect(element).not_to receive(:add_call_and_enter)
47
50
 
48
51
  subject
@@ -5,7 +5,7 @@ describe Prawn::SVG::Calculators::Pixels do
5
5
  include Prawn::SVG::Calculators::Pixels
6
6
 
7
7
  def computed_properties
8
- Struct.new(:numerical_font_size).new(16)
8
+ Struct.new(:numeric_font_size).new(16)
9
9
  end
10
10
 
11
11
  [:x, :y, :pixels, :x_pixels, :y_pixels].each { |method| public method }
@@ -35,7 +35,6 @@ describe Prawn::SVG::Calculators::Pixels do
35
35
  expect(subject.pixels(32)).to eq 32.0
36
36
  expect(subject.pixels(32.0)).to eq 32.0
37
37
  expect(subject.pixels('32')).to eq 32.0
38
- expect(subject.pixels('32unknown')).to eq 32.0
39
38
  expect(subject.pixels('32px')).to eq 32.0
40
39
  expect(subject.pixels('32pt')).to eq 32.0
41
40
  expect(subject.pixels('32in')).to eq 32.0 * 72
@@ -1,74 +1,44 @@
1
1
  require "#{File.dirname(__FILE__)}/../../spec_helper"
2
2
 
3
3
  describe Prawn::SVG::Color do
4
- describe '::css_color_to_prawn_color' do
5
- it 'converts #xxx to a hex value' do
6
- Prawn::SVG::Color.css_color_to_prawn_color('#9ab').should == '99aabb'
7
- end
8
-
9
- it 'converts #xxxxxx to a hex value' do
10
- Prawn::SVG::Color.css_color_to_prawn_color('#9ab123').should == '9ab123'
11
- end
12
-
13
- it 'converts an html colour name to a hex value' do
14
- Prawn::SVG::Color.css_color_to_prawn_color('White').should == 'ffffff'
15
- end
16
-
17
- it 'converts an rgb string to a hex value' do
18
- Prawn::SVG::Color.css_color_to_prawn_color('rgb(16, 32, 48)').should
19
- Prawn::SVG::Color.css_color_to_prawn_color('rgb(-5, 50%, 120%)').should == '007fff'
4
+ describe '::parse' do
5
+ def parse(value)
6
+ values = Prawn::SVG::CSS::ValuesParser.parse(value)
7
+ Prawn::SVG::Color.parse(values[0])
20
8
  end
21
9
 
22
- it 'converts a CMYK string to an array of numbers' do
23
- expect(Prawn::SVG::Color.css_color_to_prawn_color('device-cmyk(0, 0.32, 0.48, 1)')).to eq [0, 32, 48, 100]
24
- expect(Prawn::SVG::Color.css_color_to_prawn_color('device-cmyk(-5, 50%, 120%, -5%)')).to eq [0, 50, 100, 0]
10
+ it 'converts #xxx to an RGB color' do
11
+ expect(parse('#9ab')).to eq Prawn::SVG::Color::RGB.new('99aabb')
25
12
  end
26
13
 
27
- it 'scans the string and finds the first colour it can parse' do
28
- Prawn::SVG::Color.css_color_to_prawn_color('function(#someurl, 0) nonexistent rgb( 3 ,4,5 ) white').should == '030405'
14
+ it 'converts #xxxxxx to an RGB color' do
15
+ expect(parse('#9ab123')).to eq Prawn::SVG::Color::RGB.new('9ab123')
29
16
  end
30
17
 
31
- it 'ignores url()s' do
32
- expect(Prawn::SVG::Color.css_color_to_prawn_color('url(#someplace) red')).to eq 'ff0000'
18
+ it 'converts an html colour name to an RGB color' do
19
+ expect(parse('White')).to eq Prawn::SVG::Color::RGB.new('ffffff')
33
20
  end
34
21
 
35
- it "returns black if the color doesn't exist" do
36
- expect(Prawn::SVG::Color.css_color_to_prawn_color('blurble')).to eq '000000'
22
+ it 'converts an rgb function to an RGB color' do
23
+ expect(parse('rgb(16, 32, 48)')).to eq Prawn::SVG::Color::RGB.new('102030')
24
+ expect(parse('rgb(-5, 50%, 120%)')).to eq Prawn::SVG::Color::RGB.new('007fff')
37
25
  end
38
26
 
39
- it "returns nil if there's no fallback after a url()" do
40
- expect(Prawn::SVG::Color.css_color_to_prawn_color('url(#someplace)')).to be nil
27
+ it 'converts a CMYK string to an array of numbers' do
28
+ expect(parse('device-cmyk(0, 0.32, 0.48, 1.2)')).to eq Prawn::SVG::Color::CMYK.new([0, 32, 48, 100])
29
+ expect(parse('device-cmyk(0, 50%, 120%, -5%)')).to eq Prawn::SVG::Color::CMYK.new([0, 50, 100, 0])
41
30
  end
42
- end
43
-
44
- describe '::parse' do
45
- let(:gradients) { { 'flan' => flan_gradient, 'drob' => drob_gradient } }
46
- let(:flan_gradient) { double }
47
- let(:drob_gradient) { double }
48
31
 
49
- it 'returns a list of all colors parsed, ignoring impossible or non-existent colors' do
50
- results = Prawn::SVG::Color.parse('url(#nope) url(#flan) blurble green #123', gradients, :rgb)
51
- expect(results).to eq [
52
- flan_gradient,
53
- Prawn::SVG::Color::RGB.new('008000'),
54
- Prawn::SVG::Color::RGB.new('112233')
55
- ]
32
+ it "returns nil if the color doesn't exist" do
33
+ expect(parse('blurble')).to be nil
56
34
  end
57
35
 
58
- it "appends black to the list if there aren't any url() references" do
59
- results = Prawn::SVG::Color.parse('blurble green', gradients, :rgb)
60
- expect(results).to eq [
61
- Prawn::SVG::Color::RGB.new('008000'),
62
- Prawn::SVG::Color::RGB.new('000000')
63
- ]
36
+ it 'returns nil if the function has the wrong number of arguments' do
37
+ expect(parse('rgb(-1, 0)')).to be nil
64
38
  end
65
39
 
66
- it 'works in CMYK color mode' do
67
- results = Prawn::SVG::Color.parse('blurble green', gradients, :cmyk)
68
- expect(results).to eq [
69
- Prawn::SVG::Color::CMYK.new([100, 0, 100, 50]),
70
- Prawn::SVG::Color::CMYK.new([0, 0, 0, 100])
71
- ]
40
+ it "returns nil if it doesn't recognise the function" do
41
+ expect(parse('hsl(0, 0, 0)')).to be nil
72
42
  end
73
43
  end
74
44
  end
@@ -120,27 +120,27 @@ describe Prawn::SVG::Elements::Base do
120
120
  end
121
121
 
122
122
  it "doesn't change anything if 'none' fill attribute provided" do
123
- element.properties.fill = 'none'
123
+ element.properties.fill = Prawn::SVG::Paint.parse('none')
124
124
  expect(element).to_not receive(:add_call)
125
125
  subject
126
126
  end
127
127
 
128
128
  it "uses the fill attribute's color" do
129
129
  expect(element).to receive(:add_call).with('fill_color', 'ff0000')
130
- element.properties.fill = 'red'
130
+ element.properties.fill = Prawn::SVG::Paint.parse('red')
131
131
  subject
132
132
  end
133
133
 
134
- it "uses black if the fill attribute's color is unparseable" do
135
- expect(element).to receive(:add_call).with('fill_color', '000000')
136
- element.properties.fill = 'blarble'
134
+ it "ignores the instruction if the fill attribute's color is unparseable" do
135
+ element.properties.fill = Prawn::SVG::Paint.parse('blarble')
136
+ expect(element).to_not receive(:add_call)
137
137
  subject
138
138
  end
139
139
 
140
140
  it "uses the color attribute if 'currentColor' fill attribute provided" do
141
+ element.properties.fill = Prawn::SVG::Paint.parse('currentColor')
142
+ element.state.computed_properties.color = Prawn::SVG::Color::RGB.new('ff0000')
141
143
  expect(element).to receive(:add_call).with('fill_color', 'ff0000')
142
- element.properties.fill = 'currentColor'
143
- element.state.computed_properties.color = 'red'
144
144
  subject
145
145
  end
146
146
 
@@ -159,11 +159,10 @@ describe Prawn::SVG::Elements::Base do
159
159
  end
160
160
  end
161
161
 
162
- it "computes to 'none' if UnresolvableURLWithNoFallbackError is raised" do
162
+ it 'specifies no color if the URL is unresolvable' do
163
163
  expect(element).to_not receive(:add_call)
164
- element.properties.fill = 'url()'
164
+ element.properties.fill = Prawn::SVG::Paint.parse('url(bad)')
165
165
  subject
166
- expect(element.computed_properties.fill).to eq 'none'
167
166
  end
168
167
  end
169
168
 
@@ -2,11 +2,11 @@ require 'spec_helper'
2
2
 
3
3
  describe Prawn::SVG::Elements::Gradient do
4
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, [], fake_state) }
5
+ let(:root_element) { Prawn::SVG::Elements::Root.new(document, document.root, []) }
6
+ let(:element) { document.gradients['flag'] }
6
7
 
7
8
  before do
8
- allow(element).to receive(:assert_compatible_prawn_version)
9
- element.process
9
+ root_element.process
10
10
  end
11
11
 
12
12
  describe 'object bounding box with linear gradient' do
@@ -27,17 +27,20 @@ describe Prawn::SVG::Elements::Gradient do
27
27
  it 'returns correct gradient arguments for an element' do
28
28
  arguments = element.gradient_arguments(double(bounding_box: [100, 100, 200, 0]))
29
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
- apply_transformations: true
30
+ from: [0.0, 0.0],
31
+ to: [0.2, 1.0],
32
+ wrap: :pad,
33
+ matrix: Matrix[[100.0, 0.0, 100.0], [0.0, -100.0, 100.0], [0.0, 0.0, 1.0]],
34
+ bounding_box: [100, 100, 200, 0],
35
+ stops: [
36
+ { offset: 0, color: 'ff0000', opacity: 1.0 },
37
+ { offset: 0.25, color: 'ff0000', opacity: 1.0 },
38
+ { offset: 0.5, color: 'ffffff', opacity: 1.0 },
39
+ { offset: 0.75, color: '0000ff', opacity: 1.0 },
40
+ { offset: 1, color: '0000ff', opacity: 1.0 }
41
+ ]
34
42
  )
35
43
  end
36
-
37
- it "returns nil if the element doesn't have a bounding box" do
38
- arguments = element.gradient_arguments(double(bounding_box: nil))
39
- expect(arguments).to be nil
40
- end
41
44
  end
42
45
 
43
46
  describe 'object bounding box with radial gradient' do
@@ -58,12 +61,20 @@ describe Prawn::SVG::Elements::Gradient do
58
61
  it 'returns correct gradient arguments for an element' do
59
62
  arguments = element.gradient_arguments(double(bounding_box: [100, 100, 200, 0]))
60
63
  expect(arguments).to eq(
61
- from: [150, 80],
62
- to: [100, 80],
63
- r1: 0,
64
- r2: Math.sqrt(((0.8 * 100)**2) + ((0.8 * 100)**2)),
65
- stops: [[0, 'ff0000'], [0.25, 'ff0000'], [0.5, 'ffffff'], [0.75, '0000ff'], [1, '0000ff']],
66
- apply_transformations: true
64
+ from: [0.5, 0.2],
65
+ to: [0.0, 0.2],
66
+ r1: 0,
67
+ r2: 0.8,
68
+ wrap: :pad,
69
+ matrix: Matrix[[100.0, 0.0, 100.0], [0.0, -100.0, 100.0], [0.0, 0.0, 1.0]],
70
+ bounding_box: [100, 100, 200, 0],
71
+ stops: [
72
+ { offset: 0, color: 'ff0000', opacity: 1.0 },
73
+ { offset: 0.25, color: 'ff0000', opacity: 1.0 },
74
+ { offset: 0.5, color: 'ffffff', opacity: 1.0 },
75
+ { offset: 0.75, color: '0000ff', opacity: 1.0 },
76
+ { offset: 1, color: '0000ff', opacity: 1.0 }
77
+ ]
67
78
  )
68
79
  end
69
80
  end
@@ -79,12 +90,14 @@ describe Prawn::SVG::Elements::Gradient do
79
90
  end
80
91
 
81
92
  it 'returns correct gradient arguments for an element' do
82
- arguments = element.gradient_arguments(double)
93
+ arguments = element.gradient_arguments(double(bounding_box: [100, 100, 200, 0]))
83
94
  expect(arguments).to eq(
84
- from: [100.0, 100.0],
85
- to: [200.0, 0.0],
86
- stops: [[0, 'ff0000'], [1, '0000ff']],
87
- apply_transformations: true
95
+ from: [100.0, 500.0],
96
+ to: [200.0, 600.0],
97
+ stops: [{ offset: 0, color: 'ff0000', opacity: 1.0 }, { offset: 1, color: '0000ff', opacity: 1.0 }],
98
+ matrix: Matrix[[1.0, 0.0, 0.0], [0.0, -1.0, 600.0], [0.0, 0.0, 1.0]],
99
+ wrap: :pad,
100
+ bounding_box: [100, 100, 200, 0]
88
101
  )
89
102
  end
90
103
  end
@@ -100,14 +113,16 @@ describe Prawn::SVG::Elements::Gradient do
100
113
  end
101
114
 
102
115
  it 'returns correct gradient arguments for an element' do
103
- arguments = element.gradient_arguments(double)
116
+ arguments = element.gradient_arguments(double(bounding_box: [100, 100, 200, 0]))
104
117
  expect(arguments).to eq(
105
- from: [100.0, 100.0],
106
- to: [200.0, 0.0],
107
- r1: 0,
108
- r2: 150,
109
- stops: [[0, 'ff0000'], [1, '0000ff']],
110
- apply_transformations: true
118
+ from: [100.0, 500.0],
119
+ to: [200.0, 600.0],
120
+ r1: 0,
121
+ r2: 150.0,
122
+ stops: [{ offset: 0, color: 'ff0000', opacity: 1.0 }, { offset: 1, color: '0000ff', opacity: 1.0 }],
123
+ matrix: Matrix[[1.0, 0.0, 0.0], [0.0, -1.0, 600.0], [0.0, 0.0, 1.0]],
124
+ wrap: :pad,
125
+ bounding_box: [100, 100, 200, 0]
111
126
  )
112
127
  end
113
128
  end
@@ -115,7 +130,7 @@ describe Prawn::SVG::Elements::Gradient do
115
130
  context 'when gradientTransform is specified' do
116
131
  let(:svg) do
117
132
  <<-SVG
118
- <linearGradient id="flag" gradientTransform="translateX(10) scale(2)" x1="0" y1="0" x2="10" y2="10">
133
+ <linearGradient id="flag" gradientTransform="translateX(0.5) scale(2)" x1="0" y1="0" x2="1" y2="1">
119
134
  <stop offset="0" stop-color="red"/>
120
135
  <stop offset="1" stop-color="blue"/>
121
136
  </linearGradient>
@@ -123,13 +138,54 @@ describe Prawn::SVG::Elements::Gradient do
123
138
  end
124
139
 
125
140
  it 'passes in the transform via the apply_transformations option' do
126
- arguments = element.gradient_arguments(double(bounding_box: [0, 0, 10, 10]))
141
+ arguments = element.gradient_arguments(double(bounding_box: [100, 100, 200, 0]))
142
+
143
+ expect(arguments).to eq(
144
+ from: [0.0, 0.0],
145
+ to: [1.0, 1.0],
146
+ stops: [{ offset: 0, color: 'ff0000', opacity: 1.0 }, { offset: 1, color: '0000ff', opacity: 1.0 }],
147
+ matrix: Matrix[[200.0, 0.0, 150.0], [0.0, -200.0, 100.0], [0.0, 0.0, 1.0]],
148
+ wrap: :pad,
149
+ bounding_box: [100, 100, 200, 0]
150
+ )
151
+ end
152
+ end
153
+
154
+ context 'when a gradient is linked to another' do
155
+ let(:svg) do
156
+ <<-SVG
157
+ <svg>
158
+ <linearGradient id="flag" gradientUnits="userSpaceOnUse" x1="100" y1="500" x2="200" y2="600">
159
+ <stop offset="0" stop-color="red"/>
160
+ <stop offset="1" stop-color="blue"/>
161
+ </linearGradient>
162
+
163
+ <linearGradient id="flag-2" href="#flag" x1="150" x2="220" />
164
+
165
+ <linearGradient id="flag-3" href="#flag-2" x1="170" />
166
+ </svg>
167
+ SVG
168
+ end
169
+
170
+ it 'correctly inherits the attributes from the parent element' do
171
+ arguments = document.gradients['flag-2'].gradient_arguments(double(bounding_box: [100, 100, 200, 0]))
172
+ expect(arguments).to eq(
173
+ from: [150.0, 500.0],
174
+ to: [220.0, 600.0],
175
+ stops: [{ offset: 0, color: 'ff0000', opacity: 1.0 }, { offset: 1, color: '0000ff', opacity: 1.0 }],
176
+ matrix: Matrix[[1.0, 0.0, 0.0], [0.0, -1.0, 600.0], [0.0, 0.0, 1.0]],
177
+ wrap: :pad,
178
+ bounding_box: [100, 100, 200, 0]
179
+ )
127
180
 
181
+ arguments = document.gradients['flag-3'].gradient_arguments(double(bounding_box: [100, 100, 200, 0]))
128
182
  expect(arguments).to eq(
129
- from: [0, 0],
130
- to: [10, 10],
131
- stops: [[0, 'ff0000'], [1, '0000ff']],
132
- apply_transformations: [2, 0, 0, 2, 10, 0]
183
+ from: [170.0, 500.0],
184
+ to: [220.0, 600.0],
185
+ stops: [{ offset: 0, color: 'ff0000', opacity: 1.0 }, { offset: 1, color: '0000ff', opacity: 1.0 }],
186
+ matrix: Matrix[[1.0, 0.0, 0.0], [0.0, -1.0, 600.0], [0.0, 0.0, 1.0]],
187
+ wrap: :pad,
188
+ bounding_box: [100, 100, 200, 0]
133
189
  )
134
190
  end
135
191
  end
@@ -69,21 +69,19 @@ RSpec.describe Prawn::SVG::Elements::Marker do
69
69
  ['rectangle', [[-0.5, 600.0], 4.0, 3.0], {}, []],
70
70
  ['clip', [], {}, []],
71
71
  ['transformation_matrix', [0.3, 0, 0, 0.3, 0, 0], {}, []],
72
- ['transparent', [1.0, 1.0], {}, [
73
- ['fill_color', ['000000'], {}, []],
74
- ['line_width', [1.0], {}, []],
75
- ['cap_style', [:butt], {}, []],
76
- ['join_style', [:miter], {}, []],
77
- ['undash', [], {}, []],
78
- ['save', [], {}, []],
79
- ['fill', [], {}, [
80
- ['move_to', [[0.0, 600.0]], {}, []],
81
- ['line_to', [[10.0, 595.0]], {}, []],
82
- ['line_to', [[0.0, 590.0]], {}, []],
83
- ['close_path', [], {}, []]
84
- ]],
85
- ['restore', [], {}, []]
86
- ]]
72
+ ['fill_color', ['000000'], {}, []],
73
+ ['line_width', [1.0], {}, []],
74
+ ['cap_style', [:butt], {}, []],
75
+ ['join_style', [:miter], {}, []],
76
+ ['undash', [], {}, []],
77
+ ['save', [], {}, []],
78
+ ['fill', [], {}, [
79
+ ['move_to', [[0.0, 600.0]], {}, []],
80
+ ['line_to', [[10.0, 595.0]], {}, []],
81
+ ['line_to', [[0.0, 590.0]], {}, []],
82
+ ['close_path', [], {}, []]
83
+ ]],
84
+ ['restore', [], {}, []]
87
85
  ]],
88
86
  ['restore', [], {}, []]
89
87
  ]
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Prawn::SVG::FuncIRI do
4
+ describe '.parse' do
5
+ it 'parses a URL' do
6
+ expect(described_class.parse('url(#foo)')).to eq(described_class.new('#foo'))
7
+ end
8
+
9
+ it 'parses a URL with whitespace' do
10
+ expect(described_class.parse('url( #foo )')).to eq(described_class.new('#foo'))
11
+ end
12
+
13
+ it 'parses a URL with quotes' do
14
+ expect(described_class.parse('url("#foo")')).to eq(described_class.new('#foo'))
15
+ end
16
+
17
+ it 'parses a URL with quotes and whitespace' do
18
+ expect(described_class.parse('url( "#foo" )')).to eq(described_class.new('#foo'))
19
+ end
20
+
21
+ it 'parses a URL with double quotes' do
22
+ expect(described_class.parse('url("#foo")')).to eq(described_class.new('#foo'))
23
+ end
24
+
25
+ it 'parses a URL with double quotes and whitespace' do
26
+ expect(described_class.parse('url( "#foo" )')).to eq(described_class.new('#foo'))
27
+ end
28
+
29
+ it 'parses a URL with single quotes' do
30
+ expect(described_class.parse("url('#foo')")).to eq(described_class.new('#foo'))
31
+ end
32
+
33
+ it 'parses a URL with single quotes and whitespace' do
34
+ expect(described_class.parse("url( '#foo' )")).to eq(described_class.new('#foo'))
35
+ end
36
+
37
+ it 'parses a URL with both single and double quotes' do
38
+ expect(described_class.parse('url("#foo")')).to eq(described_class.new('#foo'))
39
+ end
40
+
41
+ it 'parses a URL with both single and double quotes and whitespace' do
42
+ expect(described_class.parse('url( "#foo" )')).to eq(described_class.new('#foo'))
43
+ end
44
+
45
+ it 'parses a URL with both single and double quotes and whitespace' do
46
+ expect(described_class.parse('url( "#foo" )')).to eq(described_class.new('#foo'))
47
+ end
48
+
49
+ it 'parses a URL with escaped quotes' do
50
+ expect(described_class.parse('url("\\#foo")')).to eq(described_class.new('#foo'))
51
+ end
52
+
53
+ it 'ignores a non-URL value' do
54
+ expect(described_class.parse('foo')).to be_nil
55
+ expect(described_class.parse('foo(1)')).to be_nil
56
+ expect(described_class.parse('url(1, 2)')).to be_nil
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ describe Prawn::SVG::Length do
4
+ describe '.parse' do
5
+ it 'parses a length' do
6
+ expect(described_class.parse('1.23em')).to eq(described_class.new(1.23, :em))
7
+ end
8
+
9
+ it 'parses a length with a positive sign' do
10
+ expect(described_class.parse('+1.23em')).to eq(described_class.new(1.23, :em))
11
+ end
12
+
13
+ it 'parses a length with a negative sign' do
14
+ expect(described_class.parse('-1.23em')).to eq(described_class.new(-1.23, :em))
15
+ end
16
+
17
+ it 'parses a length with the unit in caps' do
18
+ expect(described_class.parse('1.23EM')).to eq(described_class.new(1.23, :em))
19
+ end
20
+
21
+ it 'parses a length with no decimal points' do
22
+ expect(described_class.parse('1em')).to eq(described_class.new(1, :em))
23
+ end
24
+
25
+ it 'parses a length with no unit' do
26
+ expect(described_class.parse('1.23')).to eq(described_class.new(1.23, nil))
27
+ end
28
+
29
+ it 'allows numbers without a leading zero' do
30
+ expect(described_class.parse('.23em')).to eq(described_class.new(0.23, :em))
31
+ end
32
+
33
+ it 'does not allow numbers with a trailing dot' do
34
+ expect(described_class.parse('1.em')).to be nil
35
+ end
36
+
37
+ it 'does not allow units it does not recognise' do
38
+ expect(described_class.parse('1.23foo')).to be nil
39
+ end
40
+
41
+ context 'when positive_only is true' do
42
+ it 'does not allow negative numbers' do
43
+ expect(described_class.parse('-1.23em', positive_only: true)).to be nil
44
+ end
45
+
46
+ it 'does allow zero' do
47
+ expect(described_class.parse('0em', positive_only: true)).to eq(described_class.new(0, :em))
48
+ end
49
+
50
+ it 'does allow positive numbers' do
51
+ expect(described_class.parse('1.23em', positive_only: true)).to eq(described_class.new(1.23, :em))
52
+ end
53
+ end
54
+ end
55
+
56
+ describe '#to_pixels' do
57
+ it 'converts a em-unit length to pixels' do
58
+ expect(described_class.new(2.5, :em).to_pixels(nil, 12)).to eq(12 * 2.5)
59
+ end
60
+
61
+ it 'converts a rem-unit length to pixels' do
62
+ expect(described_class.new(2.5, :rem).to_pixels(nil, 12)).to eq(16 * 2.5)
63
+ end
64
+
65
+ it 'converts a ex-unit length to pixels' do
66
+ expect(described_class.new(2.5, :ex).to_pixels(nil, 12)).to eq(6 * 2.5)
67
+ end
68
+
69
+ it 'converts a pc-unit length to pixels' do
70
+ expect(described_class.new(2.5, :pc).to_pixels(nil, 12)).to eq(37.5)
71
+ end
72
+
73
+ it 'converts a in-unit length to pixels' do
74
+ expect(described_class.new(2.5, :in).to_pixels(nil, 12)).to eq(180)
75
+ end
76
+
77
+ it 'converts a cm-unit length to pixels' do
78
+ expect(described_class.new(2.5, :cm).to_pixels(nil, 12)).to be_within(0.001).of(70.866)
79
+ end
80
+
81
+ it 'converts a mm-unit length to pixels' do
82
+ expect(described_class.new(2.5, :mm).to_pixels(nil, 12)).to be_within(0.001).of(7.087)
83
+ end
84
+
85
+ it 'returns the value for an unknown unit' do
86
+ expect(described_class.new(2.5, nil).to_pixels(nil, 12)).to eq(2.5)
87
+ end
88
+ end
89
+ end