prawn-svg 0.35.0 → 0.36.0

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