prawn-svg 0.17.0 → 0.18.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.
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