prawn-svg 0.20.0 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fb790776a1eaf53a702045a42ea22fce3e8afa51
4
- data.tar.gz: d90d2245d141bc749ec04d0b6e9239a9e2d8b48c
3
+ metadata.gz: 40c0c884ffef68848fa7314e085647ffc907a003
4
+ data.tar.gz: 87341061bdfcb99e9cf8157cfb3c84f72ec3bb2b
5
5
  SHA512:
6
- metadata.gz: a0a11f3a0a93c36d838ed2bcb012cfa010f4968de481795e8f46fde61460c71c1f491586e477c24d1c55792f9293d2d52f4dfb9f28c7447cd4c3f87623e17c7f
7
- data.tar.gz: 5885cb0acea63c8edd59fe563e4b6859f0d536537324026e8e18bb390fe1a0e335be7c2eaf07ef1d529ea752804a4fa8387a0f61d3b7ea362f76df60b13f0d5c
6
+ metadata.gz: 002745c7063d0013609343b4e383372058cde46835e240496f813855ea489287d6aad117ab98d89c3367e02d64a502deb038cdf00a7d9d381238e9d02f66149f
7
+ data.tar.gz: ce3f448a22be98027ad0be5e154924839e080969cfeeb27322c57e017d249c767b4a073b2faed164a290d102cda0e18c80aa9665840385a6cbfc124714f76808
@@ -1,17 +1,164 @@
1
1
  class Prawn::Svg::Color
2
- # TODO : use http://www.w3.org/TR/SVG11/types.html#ColorKeywords
2
+ UnresolvableURLWithNoFallbackError = Class.new(StandardError)
3
+
3
4
  HTML_COLORS = {
4
- 'black' => "000000", 'green' => "008000", 'silver' => "c0c0c0", 'lime' => "00ff00",
5
- 'gray' => "808080", 'olive' => "808000", 'white' => "ffffff", 'yellow' => "ffff00",
6
- 'maroon' => "800000", 'navy' => "000080", 'red' => "ff0000", 'blue' => "0000ff",
7
- 'purple' => "800080", 'teal' => "008080", 'fuchsia' => "ff00ff", 'aqua' => "00ffff"
5
+ 'aliceblue' => 'f0f8ff',
6
+ 'antiquewhite' => 'faebd7',
7
+ 'aqua' => '00ffff',
8
+ 'aquamarine' => '7fffd4',
9
+ 'azure' => 'f0ffff',
10
+ 'beige' => 'f5f5dc',
11
+ 'bisque' => 'ffe4c4',
12
+ 'black' => '000000',
13
+ 'blanchedalmond' => 'ffebcd',
14
+ 'blue' => '0000ff',
15
+ 'blueviolet' => '8a2be2',
16
+ 'brown' => 'a52a2a',
17
+ 'burlywood' => 'deb887',
18
+ 'cadetblue' => '5f9ea0',
19
+ 'chartreuse' => '7fff00',
20
+ 'chocolate' => 'd2691e',
21
+ 'coral' => 'ff7f50',
22
+ 'cornflowerblue' => '6495ed',
23
+ 'cornsilk' => 'fff8dc',
24
+ 'crimson' => 'dc143c',
25
+ 'cyan' => '00ffff',
26
+ 'darkblue' => '00008b',
27
+ 'darkcyan' => '008b8b',
28
+ 'darkgoldenrod' => 'b8860b',
29
+ 'darkgray' => 'a9a9a9',
30
+ 'darkgreen' => '006400',
31
+ 'darkgrey' => 'a9a9a9',
32
+ 'darkkhaki' => 'bdb76b',
33
+ 'darkmagenta' => '8b008b',
34
+ 'darkolivegreen' => '556b2f',
35
+ 'darkorange' => 'ff8c00',
36
+ 'darkorchid' => '9932cc',
37
+ 'darkred' => '8b0000',
38
+ 'darksalmon' => 'e9967a',
39
+ 'darkseagreen' => '8fbc8f',
40
+ 'darkslateblue' => '483d8b',
41
+ 'darkslategray' => '2f4f4f',
42
+ 'darkslategrey' => '2f4f4f',
43
+ 'darkturquoise' => '00ced1',
44
+ 'darkviolet' => '9400d3',
45
+ 'deeppink' => 'ff1493',
46
+ 'deepskyblue' => '00bfff',
47
+ 'dimgray' => '696969',
48
+ 'dimgrey' => '696969',
49
+ 'dodgerblue' => '1e90ff',
50
+ 'firebrick' => 'b22222',
51
+ 'floralwhite' => 'fffaf0',
52
+ 'forestgreen' => '228b22',
53
+ 'fuchsia' => 'ff00ff',
54
+ 'gainsboro' => 'dcdcdc',
55
+ 'ghostwhite' => 'f8f8ff',
56
+ 'gold' => 'ffd700',
57
+ 'goldenrod' => 'daa520',
58
+ 'gray' => '808080',
59
+ 'grey' => '808080',
60
+ 'green' => '008000',
61
+ 'greenyellow' => 'adff2f',
62
+ 'honeydew' => 'f0fff0',
63
+ 'hotpink' => 'ff69b4',
64
+ 'indianred' => 'cd5c5c',
65
+ 'indigo' => '4b0082',
66
+ 'ivory' => 'fffff0',
67
+ 'khaki' => 'f0e68c',
68
+ 'lavender' => 'e6e6fa',
69
+ 'lavenderblush' => 'fff0f5',
70
+ 'lawngreen' => '7cfc00',
71
+ 'lemonchiffon' => 'fffacd',
72
+ 'lightblue' => 'add8e6',
73
+ 'lightcoral' => 'f08080',
74
+ 'lightcyan' => 'e0ffff',
75
+ 'lightgoldenrodyellow' => 'fafad2',
76
+ 'lightgray' => 'd3d3d3',
77
+ 'lightgreen' => '90ee90',
78
+ 'lightgrey' => 'd3d3d3',
79
+ 'lightpink' => 'ffb6c1',
80
+ 'lightsalmon' => 'ffa07a',
81
+ 'lightseagreen' => '20b2aa',
82
+ 'lightskyblue' => '87cefa',
83
+ 'lightslategray' => '778899',
84
+ 'lightslategrey' => '778899',
85
+ 'lightsteelblue' => 'b0c4de',
86
+ 'lightyellow' => 'ffffe0',
87
+ 'lime' => '00ff00',
88
+ 'limegreen' => '32cd32',
89
+ 'linen' => 'faf0e6',
90
+ 'magenta' => 'ff00ff',
91
+ 'maroon' => '800000',
92
+ 'mediumaquamarine' => '66cdaa',
93
+ 'mediumblue' => '0000cd',
94
+ 'mediumorchid' => 'ba55d3',
95
+ 'mediumpurple' => '9370db',
96
+ 'mediumseagreen' => '3cb371',
97
+ 'mediumslateblue' => '7b68ee',
98
+ 'mediumspringgreen' => '00fa9a',
99
+ 'mediumturquoise' => '48d1cc',
100
+ 'mediumvioletred' => 'c71585',
101
+ 'midnightblue' => '191970',
102
+ 'mintcream' => 'f5fffa',
103
+ 'mistyrose' => 'ffe4e1',
104
+ 'moccasin' => 'ffe4b5',
105
+ 'navajowhite' => 'ffdead',
106
+ 'navy' => '000080',
107
+ 'oldlace' => 'fdf5e6',
108
+ 'olive' => '808000',
109
+ 'olivedrab' => '6b8e23',
110
+ 'orange' => 'ffa500',
111
+ 'orangered' => 'ff4500',
112
+ 'orchid' => 'da70d6',
113
+ 'palegoldenrod' => 'eee8aa',
114
+ 'palegreen' => '98fb98',
115
+ 'paleturquoise' => 'afeeee',
116
+ 'palevioletred' => 'db7093',
117
+ 'papayawhip' => 'ffefd5',
118
+ 'peachpuff' => 'ffdab9',
119
+ 'peru' => 'cd853f',
120
+ 'pink' => 'ffc0cb',
121
+ 'plum' => 'dda0dd',
122
+ 'powderblue' => 'b0e0e6',
123
+ 'purple' => '800080',
124
+ 'red' => 'ff0000',
125
+ 'rosybrown' => 'bc8f8f',
126
+ 'royalblue' => '4169e1',
127
+ 'saddlebrown' => '8b4513',
128
+ 'salmon' => 'fa8072',
129
+ 'sandybrown' => 'f4a460',
130
+ 'seagreen' => '2e8b57',
131
+ 'seashell' => 'fff5ee',
132
+ 'sienna' => 'a0522d',
133
+ 'silver' => 'c0c0c0',
134
+ 'skyblue' => '87ceeb',
135
+ 'slateblue' => '6a5acd',
136
+ 'slategray' => '708090',
137
+ 'slategrey' => '708090',
138
+ 'snow' => 'fffafa',
139
+ 'springgreen' => '00ff7f',
140
+ 'steelblue' => '4682b4',
141
+ 'tan' => 'd2b48c',
142
+ 'teal' => '008080',
143
+ 'thistle' => 'd8bfd8',
144
+ 'tomato' => 'ff6347',
145
+ 'turquoise' => '40e0d0',
146
+ 'violet' => 'ee82ee',
147
+ 'wheat' => 'f5deb3',
148
+ 'white' => 'ffffff',
149
+ 'whitesmoke' => 'f5f5f5',
150
+ 'yellow' => 'ffff00',
151
+ 'yellowgreen' => '9acd32'
8
152
  }.freeze
9
153
 
10
154
  RGB_VALUE_REGEXP = "\s*(-?[0-9.]+%?)\s*"
11
155
  RGB_REGEXP = /\Argb\(#{RGB_VALUE_REGEXP},#{RGB_VALUE_REGEXP},#{RGB_VALUE_REGEXP}\)\z/i
156
+ URL_REGEXP = /\Aurl\([^)]*\)\z/i
12
157
 
13
158
  def self.color_to_hex(color_string)
14
- color_string.scan(/([^(\s]+(\([^)]*\))?)/).detect do |color, *_|
159
+ url_specified = false
160
+
161
+ result = color_string.strip.scan(/([^(\s]+(\([^)]*\))?)/).detect do |color, *_|
15
162
  if m = color.match(/\A#([0-9a-f])([0-9a-f])([0-9a-f])\z/i)
16
163
  break "#{m[1] * 2}#{m[2] * 2}#{m[3] * 2}"
17
164
  elsif color.match(/\A#[0-9a-f]{6}\z/i)
@@ -24,8 +171,16 @@ class Prawn::Svg::Color
24
171
  value *= 2.55 if m[n][-1..-1] == '%'
25
172
  "%02x" % clamp(value.round, 0, 255)
26
173
  end.join
174
+ elsif color.match(URL_REGEXP)
175
+ url_specified = true
176
+ nil # we can't handle these so we find the next thing that matches
27
177
  end
28
178
  end
179
+
180
+ # http://www.w3.org/TR/SVG/painting.html section 11.2
181
+ raise UnresolvableURLWithNoFallbackError if result.nil? && url_specified
182
+
183
+ result
29
184
  end
30
185
 
31
186
  protected
@@ -82,14 +82,17 @@ class Prawn::Svg::Element
82
82
  case name
83
83
  when 'translate'
84
84
  x, y = arguments
85
- add_call_and_enter name, @document.distance(x, :x), -@document.distance(y, :y)
85
+ add_call_and_enter name, @document.distance(x.to_f, :x), -@document.distance(y.to_f, :y)
86
86
 
87
87
  when 'rotate'
88
88
  r, x, y = arguments.collect {|a| a.to_f}
89
- if arguments.length == 3
89
+ case arguments.length
90
+ when 1
91
+ add_call_and_enter name, -r, :origin => [0, @document.y('0')]
92
+ when 3
90
93
  add_call_and_enter name, -r, :origin => [@document.x(x), @document.y(y)]
91
94
  else
92
- add_call_and_enter name, -r, :origin => [0, @document.y('0')]
95
+ @document.warnings << "transform 'rotate' must have either one or three arguments"
93
96
  end
94
97
 
95
98
  when 'scale'
@@ -142,13 +145,21 @@ class Prawn::Svg::Element
142
145
 
143
146
  def parse_fill_and_stroke_attributes_and_call
144
147
  ["fill", "stroke"].select do |type|
145
- dec = @attributes[type]
146
- if dec == "none"
148
+ case keyword = attribute_value_as_keyword(type)
149
+ when nil
150
+ when 'inherit'
151
+ when 'none'
147
152
  state[type.to_sym] = false
148
- elsif dec
149
- state[type.to_sym] = true
150
- if color = Prawn::Svg::Color.color_to_hex(dec)
151
- add_call "#{type}_color", color
153
+ else
154
+ color_attribute = keyword == 'currentcolor' ? 'color' : type
155
+ color = @attributes[color_attribute]
156
+
157
+ begin
158
+ hex = Prawn::Svg::Color.color_to_hex(color)
159
+ state[type.to_sym] = true
160
+ add_call "#{type}_color", hex || '000000'
161
+ rescue Prawn::Svg::Color::UnresolvableURLWithNoFallbackError
162
+ state[type.to_sym] = false
152
163
  end
153
164
  end
154
165
 
@@ -206,6 +217,9 @@ class Prawn::Svg::Element
206
217
  font_updated = true
207
218
  @state[:font_family] = family
208
219
  end
220
+ if (anchor = @attributes['text-anchor'])
221
+ @state[:text_anchor] = anchor
222
+ end
209
223
 
210
224
  if @state[:font_family] && font_updated
211
225
  usable_font_families = [@state[:font_family], document.fallback_font_name]
@@ -35,7 +35,8 @@ class Prawn::Svg::Parser::Text
35
35
 
36
36
  # This is not a prawn option but we can't work out how to render it here -
37
37
  # it's handled by Svg#rewrite_call_arguments
38
- if anchor = attrs['text-anchor']
38
+ if (anchor = attrs['text-anchor'] || element.state[:text_anchor]) &&
39
+ ['start', 'middle', 'end'].include?(anchor)
39
40
  opts[:text_anchor] = anchor
40
41
  end
41
42
 
@@ -1,5 +1,5 @@
1
1
  module Prawn
2
2
  module Svg
3
- VERSION = '0.20.0'
3
+ VERSION = '0.21.0'
4
4
  end
5
5
  end
@@ -22,5 +22,19 @@ describe Prawn::Svg::Color do
22
22
  it "scans the string and finds the first colour it can parse" do
23
23
  Prawn::Svg::Color.color_to_hex("function(#someurl, 0) nonexistent rgb( 3 ,4,5 ) white").should == "030405"
24
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 nil if the color doesn't exist" do
31
+ expect(Prawn::Svg::Color.color_to_hex("blurble")).to be nil
32
+ end
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)
38
+ end
25
39
  end
26
40
  end
@@ -1,14 +1,16 @@
1
1
  require File.dirname(__FILE__) + '/../../spec_helper'
2
2
 
3
3
  describe Prawn::Svg::Element do
4
- before :each do
5
- e = double(:attributes => {}, :name => "path")
4
+ let(:document) { double(css_parser: nil) }
5
+ let(:e) { double(:attributes => {}, :name => "path") }
6
+ let(:element) { Prawn::Svg::Element.new(document, e, [], {}) }
6
7
 
7
- @document = Struct.new(:fallback_font_name, :css_parser, :warnings).new("Courier", nil, [])
8
- @element = Prawn::Svg::Element.new(@document, e, [], {})
9
- end
8
+ describe "#parse_font_attributes_and_call" do
9
+ before do
10
+ @document = Struct.new(:fallback_font_name, :css_parser, :warnings).new("Courier", nil, [])
11
+ @element = Prawn::Svg::Element.new(@document, e, [], {})
12
+ end
10
13
 
11
- describe :parse_font_attributes_and_call do
12
14
  it "uses a font if it can find it" do
13
15
  @element.should_receive(:add_call_and_enter).with('font', 'Helvetica', :style => :normal)
14
16
 
@@ -33,4 +35,93 @@ describe Prawn::Svg::Element do
33
35
  @document.warnings.length.should == 1
34
36
  end
35
37
  end
38
+
39
+ describe "#parse_fill_and_stroke_attributes_and_call" do
40
+ subject { element.send :parse_fill_and_stroke_attributes_and_call }
41
+
42
+ it "doesn't change anything if no fill attribute provided" do
43
+ subject
44
+ expect(element.state[:fill]).to be nil
45
+ end
46
+
47
+ it "doesn't change anything if 'inherit' fill attribute provided" do
48
+ element.attributes['fill'] = 'inherit'
49
+ subject
50
+ expect(element.state[:fill]).to be nil
51
+ end
52
+
53
+ it "turns off filling if 'none' fill attribute provided" do
54
+ element.attributes['fill'] = 'none'
55
+ subject
56
+ expect(element.state[:fill]).to be false
57
+ end
58
+
59
+ it "uses the fill attribute's color" do
60
+ expect(element).to receive(:add_call).with('fill_color', 'ff0000')
61
+ element.attributes['fill'] = 'red'
62
+ subject
63
+ expect(element.state[:fill]).to be true
64
+ end
65
+
66
+ it "uses black if the fill attribute's color is unparseable" do
67
+ expect(element).to receive(:add_call).with('fill_color', '000000')
68
+ element.attributes['fill'] = 'blarble'
69
+ subject
70
+ expect(element.state[:fill]).to be true
71
+ end
72
+
73
+ it "uses the color attribute if 'currentColor' fill attribute provided" do
74
+ expect(element).to receive(:add_call).with('fill_color', 'ff0000')
75
+ element.attributes['fill'] = 'currentColor'
76
+ element.attributes['color'] = 'red'
77
+ subject
78
+ expect(element.state[:fill]).to be true
79
+ end
80
+
81
+ it "turns off filling if UnresolvableURLWithNoFallbackError is raised" do
82
+ element.attributes['fill'] = 'url()'
83
+ subject
84
+ expect(element.state[:fill]).to be false
85
+ end
86
+ end
87
+
88
+ describe "#parse_transform_attribute_and_call" do
89
+ subject { element.send :parse_transform_attribute_and_call }
90
+
91
+ describe "translate" do
92
+ it "handles a missing y argument" do
93
+ expect(element).to receive(:add_call_and_enter).with('translate', -5.5, 0)
94
+ expect(document).to receive(:distance).with(-5.5, :x).and_return(-5.5)
95
+ expect(document).to receive(:distance).with(0.0, :y).and_return(0.0)
96
+
97
+ element.attributes['transform'] = 'translate(-5.5)'
98
+ subject
99
+ end
100
+ end
101
+
102
+ describe "rotate" do
103
+ it "handles a single angle argument" do
104
+ expect(element).to receive(:add_call_and_enter).with('rotate', -5.5, :origin => [0, 0])
105
+ expect(document).to receive(:y).with('0').and_return(0)
106
+
107
+ element.attributes['transform'] = 'rotate(5.5)'
108
+ subject
109
+ end
110
+
111
+ it "handles three arguments" do
112
+ expect(element).to receive(:add_call_and_enter).with('rotate', -5.5, :origin => [1.0, 2.0])
113
+ expect(document).to receive(:x).with(1.0).and_return(1.0)
114
+ expect(document).to receive(:y).with(2.0).and_return(2.0)
115
+
116
+ element.attributes['transform'] = 'rotate(5.5 1 2)'
117
+ subject
118
+ end
119
+
120
+ it "does nothing and warns if two arguments" do
121
+ expect(document).to receive(:warnings).and_return([])
122
+ element.attributes['transform'] = 'rotate(5.5 1)'
123
+ subject
124
+ end
125
+ end
126
+ end
36
127
  end
@@ -33,6 +33,15 @@ describe Prawn::Svg::Parser::Text do
33
33
  end
34
34
  end
35
35
 
36
+ describe "when text-anchor is specified" do
37
+ let(:svg) { '<g text-anchor="middle" font-family="sans-serif" font-size="12"><text x="50" y="14">Text</text></g>' }
38
+
39
+ it "should inherit text-anchor from parent element" do
40
+ parser.parse(element)
41
+ expect(element.state[:text_anchor]).to eq 'middle'
42
+ end
43
+ end
44
+
36
45
  describe "letter-spacing" do
37
46
  let(:svg) { '<text letter-spacing="5">spaced</text>' }
38
47
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prawn-svg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20.0
4
+ version: 0.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roger Nesbitt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-20 00:00:00.000000000 Z
11
+ date: 2015-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prawn