prawn-svg 0.17.0 → 0.18.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: 84d18077060035c9ff8e187e66224d5f8096442f
4
- data.tar.gz: 5f5dfebe80da4952bb7abf990dae113ab6964f5e
3
+ metadata.gz: 13276a5db1c1dd04e219c4e8082205e49dca13ad
4
+ data.tar.gz: fc5446141d6e7d6ef9f0a393f64bc975d26e453e
5
5
  SHA512:
6
- metadata.gz: 138d046a81065b80ac7ee63d249d4874deae0e4bfeb18630b2ff117ee93392823cb675b1fc21372f16a199c0efc7ae68f45e3236c0c8559c33c07423a7a31e8d
7
- data.tar.gz: 477eb736af2b1b0f4cbd1789d1afb5e0807cac8a5d578790c99d5aaa75787c2413ebb571920e42b4004261401163684e82dd34be1c31af6a4434d85ec573c8b0
6
+ metadata.gz: 345b0a1c89e7bd4aad297bbcc398322ea7d72ca51a3ac13ea22fdfa62ed0c42ea60344940880f9c570e60f0d21b669e7b3f73ff3791aff16fef87c21c92181d6
7
+ data.tar.gz: 23391e2209dde7f293c12f4f36af60fc421bea3e05a74b6acd5249bb81c249b0f9bcd8f3c66898f696febf7102ac6157ab836d7d98c20764e0074d2544d6a5f3
data/README.md CHANGED
@@ -6,7 +6,7 @@ This will take an SVG file as input and render it into your PDF. Find out more
6
6
 
7
7
  http://wiki.github.com/sandal/prawn/
8
8
 
9
- prawn-svg is compatible with all versions of Prawn from 0.8.4 onwards, including the version 1.0 series.
9
+ prawn-svg is compatible with all versions of Prawn from 0.8.4 onwards, including the 1.x and 2.x series.
10
10
 
11
11
  ## Using prawn-svg
12
12
 
@@ -53,6 +53,8 @@ prawn-svg does not support the full SVG specification. It currently supports:
53
53
 
54
54
  - attributes/styles: <tt>fill</tt>, <tt>stroke</tt>, <tt>stroke-width</tt>, <tt>opacity</tt>, <tt>fill-opacity</tt>, <tt>stroke-opacity</tt>, <tt>transform</tt>, <tt>clip-path</tt>
55
55
 
56
+ - attribute <tt>stroke-linecap</tt>, but only when <tt>fill="none"</tt> due to a PDF limitation
57
+
56
58
  - the <tt>viewBox</tt> attribute on the <tt>&lt;svg&gt;</tt> tag
57
59
 
58
60
  - the <tt>preserveAspectRatio</tt> attribute on the <tt>&lt;svg&gt;</tt> and <tt>&lt;image&gt;</tt> tags
@@ -67,8 +69,7 @@ prawn-svg does not support the full SVG specification. It currently supports:
67
69
 
68
70
  ## CSS
69
71
 
70
- If the css_parser gem is installed, it will handle CSS style definitions, but only simple tag, class or id definitions. It's very basic
71
- so do not expect too much of it.
72
+ prawn-svg uses the css_parser gem to parse CSS <tt>&lt;style&gt;</tt> blocks. It only handles simple tag, class or id selectors; attribute and other advanced selectors are not supported.
72
73
 
73
74
  ## Not supported
74
75
 
@@ -5,7 +5,7 @@ module Prawn::Svg::Calculators
5
5
  attr_writer :requested_width, :requested_height
6
6
 
7
7
  attr_reader :x_offset, :y_offset, :x_scale, :y_scale
8
- attr_reader :viewport_width, :viewport_height, :output_width, :output_height
8
+ attr_reader :viewport_width, :viewport_height, :viewport_diagonal, :output_width, :output_height
9
9
 
10
10
  def initialize(bounds, attributes = nil)
11
11
  @bounds = bounds
@@ -47,6 +47,9 @@ module Prawn::Svg::Calculators
47
47
  @viewport_width ||= width
48
48
  @viewport_height ||= height
49
49
 
50
+ # SVG 1.1 section 7.10
51
+ @viewport_diagonal = Math.sqrt(@viewport_width**2 + @viewport_height**2) / Math.sqrt(2)
52
+
50
53
  if @requested_width
51
54
  scale = @requested_width / width
52
55
  width = @requested_width
@@ -32,6 +32,8 @@ class Prawn::Svg::Document
32
32
  sizing.requested_height = options[:height]
33
33
  sizing.calculate
34
34
 
35
+ @axis_to_size = {:x => sizing.viewport_width, :y => sizing.viewport_height}
36
+
35
37
  yield self if block_given?
36
38
  end
37
39
 
@@ -48,7 +50,7 @@ class Prawn::Svg::Document
48
50
  end
49
51
 
50
52
  def points(value, axis = nil)
51
- Prawn::Svg::Calculators::Pixels.to_pixels(value, axis == :y ? sizing.viewport_height : sizing.viewport_width)
53
+ Prawn::Svg::Calculators::Pixels.to_pixels(value, @axis_to_size.fetch(axis, sizing.viewport_diagonal))
52
54
  end
53
55
 
54
56
  def url_loader
@@ -55,7 +55,7 @@ class Prawn::Svg::Element
55
55
  parse_opacity_attributes_and_call
56
56
  parse_clip_path_attribute_and_call
57
57
  draw_types = parse_fill_and_stroke_attributes_and_call
58
- parse_stroke_width_attribute_and_call
58
+ parse_stroke_attributes_and_call
59
59
  parse_font_attributes_and_call
60
60
  parse_display_attribute
61
61
  apply_drawing_call(draw_types)
@@ -82,7 +82,7 @@ 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), -@document.distance(y)
85
+ add_call_and_enter name, @document.distance(x, :x), -@document.distance(y, :y)
86
86
 
87
87
  when 'rotate'
88
88
  r, x, y = arguments.collect {|a| a.to_f}
@@ -102,7 +102,7 @@ class Prawn::Svg::Element
102
102
  @document.warnings << "transform 'matrix' must have six arguments"
103
103
  else
104
104
  a, b, c, d, e, f = arguments.collect {|argument| argument.to_f}
105
- add_call_and_enter "transformation_matrix", a, -b, -c, d, @document.distance(e), -@document.distance(f)
105
+ add_call_and_enter "transformation_matrix", a, -b, -c, d, @document.distance(e, :x), -@document.distance(f, :y)
106
106
  end
107
107
  else
108
108
  @document.warnings << "Unknown transformation '#{name}'; ignoring"
@@ -156,8 +156,38 @@ class Prawn::Svg::Element
156
156
  end
157
157
  end
158
158
 
159
- def parse_stroke_width_attribute_and_call
160
- add_call('line_width', @document.distance(@attributes['stroke-width'])) if @attributes['stroke-width']
159
+ CAP_STYLE_TRANSLATIONS = {"butt" => :butt, "round" => :round, "square" => :projecting_square}
160
+
161
+ def parse_stroke_attributes_and_call
162
+ if width = @attributes['stroke-width']
163
+ add_call('line_width', @document.distance(width))
164
+ end
165
+
166
+ if (linecap = attribute_value_as_keyword('stroke-linecap')) && linecap != 'inherit'
167
+ add_call('cap_style', CAP_STYLE_TRANSLATIONS.fetch(linecap, :butt))
168
+ end
169
+
170
+ if dasharray = attribute_value_as_keyword('stroke-dasharray')
171
+ case dasharray
172
+ when 'inherit'
173
+ # don't do anything
174
+ when 'none'
175
+ add_call('undash')
176
+ else
177
+ array = dasharray.split(Prawn::Svg::Parser::COMMA_WSP_REGEXP)
178
+ array *= 2 if array.length % 2 == 1
179
+ number_array = array.map {|value| @document.distance(value)}
180
+
181
+ if number_array.any? {|number| number < 0}
182
+ @document.warnings << "stroke-dasharray cannot have negative numbers; treating as 'none'"
183
+ add_call('undash')
184
+ elsif number_array.inject(0) {|a, b| a + b} == 0
185
+ add_call('undash')
186
+ else
187
+ add_call('dash', number_array)
188
+ end
189
+ end
190
+ end
161
191
  end
162
192
 
163
193
  def parse_font_attributes_and_call
@@ -231,7 +261,11 @@ class Prawn::Svg::Element
231
261
  end
232
262
 
233
263
  @attributes = parse_css_declarations(style)
234
- element.attributes.each {|n,v| @attributes[n] = v unless @attributes[n]}
264
+
265
+ element.attributes.each do |name, value|
266
+ name = name.downcase
267
+ @attributes[name] = value unless @attributes[name]
268
+ end
235
269
  end
236
270
 
237
271
  def parse_css_declarations(declarations)
@@ -241,10 +275,16 @@ class Prawn::Svg::Element
241
275
  output = {}
242
276
  declarations.split(/[\;$]+/m).each do |decs|
243
277
  if matches = decs.match(/\s*(.[^:]*)\s*\:\s*(.[^;]*)\s*(;|\Z)/i)
244
- property, value, end_of_declaration = matches.captures
245
- output[property] = value
278
+ property, value, _ = matches.captures
279
+ output[property.downcase] = value
246
280
  end
247
281
  end
248
282
  output
249
283
  end
284
+
285
+ def attribute_value_as_keyword(name)
286
+ if value = @attributes[name]
287
+ value.strip.downcase
288
+ end
289
+ end
250
290
  end
@@ -56,16 +56,17 @@ module Prawn
56
56
 
57
57
  def issue_prawn_command(prawn, calls)
58
58
  calls.each do |call, arguments, children|
59
- if call == "end_path"
60
- issue_prawn_command(prawn, children) if children.any?
61
- prawn.add_content "n" # end path
59
+ skip = false
62
60
 
63
- elsif rewrite_call_arguments(prawn, call, arguments) == false
61
+ rewrite_call_arguments(prawn, call, arguments) do
64
62
  issue_prawn_command(prawn, children) if children.any?
63
+ skip = true
64
+ end
65
65
 
66
+ if skip
67
+ # the call has been overridden
66
68
  elsif children.empty?
67
69
  prawn.send(call, *arguments)
68
-
69
70
  else
70
71
  prawn.send(call, *arguments, &proc_creator(prawn, children))
71
72
  end
@@ -81,7 +82,7 @@ module Prawn
81
82
  case call
82
83
  when 'text_group'
83
84
  @relative_text_position = nil
84
- false
85
+ yield
85
86
 
86
87
  when 'draw_text'
87
88
  text, options = arguments
@@ -104,15 +105,26 @@ module Prawn
104
105
 
105
106
  when 'clip'
106
107
  prawn.add_content "W n" # clip to path
107
- false
108
+ yield
108
109
 
109
110
  when 'save'
110
111
  prawn.save_graphics_state
111
- false
112
+ yield
112
113
 
113
114
  when 'restore'
114
115
  prawn.restore_graphics_state
115
- false
116
+ yield
117
+
118
+ when "end_path"
119
+ yield
120
+ prawn.add_content "n" # end path
121
+
122
+ when 'fill_and_stroke'
123
+ yield
124
+ # prawn (as at 2.0.1 anyway) uses 'b' for its fill_and_stroke. 'b' is 'h' (closepath) + 'B', and we
125
+ # never want closepath to be automatically run as it stuffs up many drawing operations, such as dashes
126
+ # and line caps, and makes paths close that we didn't ask to be closed when fill is specified.
127
+ prawn.add_content 'B'
116
128
  end
117
129
  end
118
130
 
@@ -13,6 +13,7 @@ require 'rexml/document'
13
13
  #
14
14
  class Prawn::Svg::Parser
15
15
  CONTAINER_TAGS = %w(g svg symbol defs clipPath)
16
+ COMMA_WSP_REGEXP = /(?:\s+,?\s*|,\s*)/
16
17
 
17
18
  #
18
19
  # Construct a Parser object.
@@ -132,7 +133,7 @@ class Prawn::Svg::Parser
132
133
  end
133
134
 
134
135
  when 'ellipse'
135
- xy, rx, ry = [x(attrs['cx'] || "0"), y(attrs['cy'] || "0")], distance(attrs['rx']), distance(attrs['ry'])
136
+ xy, rx, ry = [x(attrs['cx'] || "0"), y(attrs['cy'] || "0")], distance(attrs['rx'], :x), distance(attrs['ry'], :y)
136
137
 
137
138
  return if zero_argument?(rx, ry)
138
139
 
@@ -140,7 +141,7 @@ class Prawn::Svg::Parser
140
141
 
141
142
  when 'rect'
142
143
  xy = [x(attrs['x'] || '0'), y(attrs['y'] || '0')]
143
- width, height = distance(attrs['width']), distance(attrs['height'])
144
+ width, height = distance(attrs['width'], :x), distance(attrs['height'], :y)
144
145
  radius = distance(attrs['rx'] || attrs['ry'])
145
146
 
146
147
  return if zero_argument?(width, height)
@@ -211,7 +212,7 @@ class Prawn::Svg::Parser
211
212
  x = element.attributes['x']
212
213
  y = element.attributes['y']
213
214
  if x || y
214
- element.add_call_and_enter "translate", distance(x || 0), -distance(y || 0)
215
+ element.add_call_and_enter "translate", distance(x || 0, :x), -distance(y || 0, :y)
215
216
  end
216
217
  element.add_calls_from_element definition_element
217
218
  else
@@ -260,7 +261,7 @@ class Prawn::Svg::Parser
260
261
  to_s.
261
262
  strip.
262
263
  gsub(/(\d)-(\d)/, '\1 -\2').
263
- split(/(?:\s+,?\s*|,\s*)/).
264
+ split(COMMA_WSP_REGEXP).
264
265
  each_slice(2).
265
266
  to_a
266
267
  end
@@ -1,5 +1,5 @@
1
1
  module Prawn
2
2
  module Svg
3
- VERSION = '0.17.0'
3
+ VERSION = '0.18.0'
4
4
  end
5
5
  end
data/prawn-svg.gemspec CHANGED
@@ -18,8 +18,10 @@ spec = Gem::Specification.new do |gem|
18
18
  gem.name = "prawn-svg"
19
19
  gem.require_paths = ["lib"]
20
20
 
21
+ gem.required_ruby_version = '>= 1.9.3'
22
+
21
23
  gem.add_runtime_dependency "prawn", ">= 0.8.4", "< 3"
22
24
  gem.add_runtime_dependency "css_parser", "~> 1.3"
23
- gem.add_development_dependency "rspec", "~> 2.14"
25
+ gem.add_development_dependency "rspec", "~> 3.0"
24
26
  gem.add_development_dependency "rake", "~> 10.1"
25
27
  end
@@ -1,29 +1,31 @@
1
1
  require File.dirname(__FILE__) + '/../../spec_helper'
2
2
 
3
3
  describe Prawn::Svg::Document do
4
- before(:each) do
5
- @document = Prawn::Svg::Document.new("<svg></svg>", [100, 100], {})
4
+ before do
5
+ sizing = instance_double(Prawn::Svg::Calculators::DocumentSizing, viewport_width: 600, viewport_height: 400, viewport_diagonal: 500, :requested_width= => nil, :requested_height= => nil)
6
+ expect(sizing).to receive(:calculate)
7
+ expect(Prawn::Svg::Calculators::DocumentSizing).to receive(:new).and_return(sizing)
6
8
  end
7
9
 
10
+ let(:document) { Prawn::Svg::Document.new("<svg></svg>", [100, 100], {}) }
11
+
8
12
  describe :points do
9
13
  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, "32pc").should == 32.0 * 15
18
- @document.send(:points, "32mm").should be_within(0.0001).of(32 * 72 * 0.0393700787)
19
- @document.send(:points, "32cm").should be_within(0.0001).of(32 * 72 * 0.393700787)
20
- @document.send(:points, "32m").should be_within(0.0001).of(32 * 72 * 39.3700787)
21
-
22
- @document.sizing.send :instance_variable_set, "@viewport_width", 600
23
- @document.sizing.send :instance_variable_set, "@viewport_height", 400
24
- @document.send(:points, "50%").should == 300
25
- @document.send(:points, "50%", :x).should == 300
26
- @document.send(:points, "50%", :y).should == 200
14
+ document.send(:points, 32).should == 32.0
15
+ document.send(:points, 32.0).should == 32.0
16
+ document.send(:points, "32").should == 32.0
17
+ document.send(:points, "32unknown").should == 32.0
18
+ document.send(:points, "32pt").should == 32.0
19
+ document.send(:points, "32in").should == 32.0 * 72
20
+ document.send(:points, "32ft").should == 32.0 * 72 * 12
21
+ document.send(:points, "32pc").should == 32.0 * 15
22
+ document.send(:points, "32mm").should be_within(0.0001).of(32 * 72 * 0.0393700787)
23
+ document.send(:points, "32cm").should be_within(0.0001).of(32 * 72 * 0.393700787)
24
+ document.send(:points, "32m").should be_within(0.0001).of(32 * 72 * 39.3700787)
25
+
26
+ document.send(:points, "50%").should == 250
27
+ document.send(:points, "50%", :x).should == 300
28
+ document.send(:points, "50%", :y).should == 200
27
29
  end
28
30
  end
29
31
  end
@@ -0,0 +1,13 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1100" height="210">
2
+ <rect x="100" y="100" width="900" height="100" fill="none" stroke="blue" stroke-width="2" />
3
+
4
+ <g stroke="green" stroke-width="10">
5
+ <line x1="120" y1="110" x2="120" y2="190" />
6
+ <line x1="140" y1="110" x2="140" y2="190" stroke-linecap="butt" />
7
+ <line x1="160" y1="110" x2="160" y2="190" stroke-linecap="round" />
8
+ <line x1="180" y1="110" x2="180" y2="190" stroke-linecap="square" />
9
+ <line x1="200" y1="110" x2="200" y2="190" />
10
+ <line x1="220" y1="110" x2="220" y2="190" stroke-linecap="invalid" />
11
+ </g>
12
+ </svg>
13
+
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2
2
  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="205pt" style="width:362px;height:205px;" version="1.1" viewBox="0 0 362 205" width="362pt">
3
3
  <g>
4
- <line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="146" x2="146" y1="35.4883" y2="204.041"/>
4
+ <line style="stroke: blue; stroke-width: 1.0; stroke-dasharray: 20,40;" x1="146" x2="146" y1="35.4883" y2="204.041"/>
5
5
  <line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="204" x2="204" y1="35.4883" y2="204.041"/>
6
6
  <rect fill="#FEFECE" height="30.4883" style="stroke: #A80036; stroke-width: 1.5;" width="47" x="121" y="0"/>
7
7
  <text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="33" x="128" y="20.5352">Alice</text>
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1100" height="400">
2
+ <rect x="100" y="100" width="900" height="300" fill="none" stroke="blue" stroke-width="2" />
3
+
4
+ <path d="M200 200 L800 300 L800 200" stroke="green" stroke-width="10" fill="red" />
5
+ </svg>
data/spec/spec_helper.rb CHANGED
@@ -6,6 +6,13 @@ Bundler.require(:default, :development)
6
6
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
7
7
 
8
8
  RSpec.configure do |config|
9
+ config.mock_with :rspec do |c|
10
+ c.syntax = [:should, :expect]
11
+ end
12
+
13
+ config.expect_with :rspec do |c|
14
+ c.syntax = [:should, :expect]
15
+ end
9
16
  end
10
17
 
11
18
  Prawn::Svg::Font.load_external_fonts(Prawn::Document.new.font_families)
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.17.0
4
+ version: 0.18.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-04-29 00:00:00.000000000 Z
11
+ date: 2015-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prawn
@@ -50,14 +50,14 @@ dependencies:
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '2.14'
53
+ version: '3.0'
54
54
  type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '2.14'
60
+ version: '3.0'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: rake
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -118,6 +118,7 @@ files:
118
118
  - spec/sample_images/mushroom-wide.jpg
119
119
  - spec/sample_output/directory
120
120
  - spec/sample_svg/arcs01.svg
121
+ - spec/sample_svg/cap_styles.svg
121
122
  - spec/sample_svg/circle01.svg
122
123
  - spec/sample_svg/clip_path.svg
123
124
  - spec/sample_svg/close_path.svg
@@ -139,6 +140,7 @@ files:
139
140
  - spec/sample_svg/negminy.svg
140
141
  - spec/sample_svg/omnigraffle.svg
141
142
  - spec/sample_svg/opacity01.svg
143
+ - spec/sample_svg/path.svg
142
144
  - spec/sample_svg/pie_piece.svg
143
145
  - spec/sample_svg/polygon01.svg
144
146
  - spec/sample_svg/polyline01.svg
@@ -171,7 +173,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
171
173
  requirements:
172
174
  - - ">="
173
175
  - !ruby/object:Gem::Version
174
- version: '0'
176
+ version: 1.9.3
175
177
  required_rubygems_version: !ruby/object:Gem::Requirement
176
178
  requirements:
177
179
  - - ">="
@@ -199,6 +201,7 @@ test_files:
199
201
  - spec/sample_images/mushroom-wide.jpg
200
202
  - spec/sample_output/directory
201
203
  - spec/sample_svg/arcs01.svg
204
+ - spec/sample_svg/cap_styles.svg
202
205
  - spec/sample_svg/circle01.svg
203
206
  - spec/sample_svg/clip_path.svg
204
207
  - spec/sample_svg/close_path.svg
@@ -220,6 +223,7 @@ test_files:
220
223
  - spec/sample_svg/negminy.svg
221
224
  - spec/sample_svg/omnigraffle.svg
222
225
  - spec/sample_svg/opacity01.svg
226
+ - spec/sample_svg/path.svg
223
227
  - spec/sample_svg/pie_piece.svg
224
228
  - spec/sample_svg/polygon01.svg
225
229
  - spec/sample_svg/polyline01.svg