prawn-svg 0.25.2 → 0.26.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: bc0f179818ef67c8d26893204ed003aade48b1e2
4
- data.tar.gz: 061d2ab83b313f9973d2a480e51f2fd5d288fb0e
3
+ metadata.gz: fc43729a870492246519b983633bc96720650d74
4
+ data.tar.gz: d577a115a0cf7fcb8fc8d9f389d83755dbc5acda
5
5
  SHA512:
6
- metadata.gz: 1df75e7819f4b9c3d016e386ca48bf4041d490da015f808880ff5bc78f40659b37313eb31bbf4106e1d17212e4965bb43bb0b32899ea6fd3003a7e8e3bf3270f
7
- data.tar.gz: a693b8ed00d9ee605dc924a0fce2160936662fa11f5555b98b02cf480e8057808f2b6ae2b5ad07d2b42939777ce3f60fb1123d481ea8f2f1e5b97ade42293b82
6
+ metadata.gz: 97c5a9deaf6d7429c9f6f532ca97044d2bd73658009dbb16dd396d5c5b37b8465e9c2366f35e6eee7156b4f9e8450a44e5e284fe65f8b3c4b273515fc86bbb01
7
+ data.tar.gz: ddc2a03914c5614d9ddb3c90e70a71983546b6e554cfe073ad6faca81e6a1afed7293de0b906f0eda3af02f8b14a775eedd9e5d69de7737aaee23751052d1a2f
data/.gitignore CHANGED
@@ -5,3 +5,5 @@ Gemfile.lock
5
5
  .rvmrc
6
6
  .*.swp
7
7
  .ruby-version
8
+ vendor/bundle/
9
+ .bundle/
data/README.md CHANGED
@@ -106,7 +106,7 @@ prawn-svg uses the css_parser gem to parse CSS <tt>&lt;style&gt;</tt> blocks. I
106
106
 
107
107
  ## Not supported
108
108
 
109
- prawn-svg does not support radial gradients or patterns.
109
+ prawn-svg does not support radial gradients, patterns or filters.
110
110
 
111
111
  ## Configuration
112
112
 
@@ -4,7 +4,7 @@ module Prawn::SVG::Calculators
4
4
  attr_reader :width, :height, :x, :y
5
5
 
6
6
  def initialize(value, container_dimensions, object_dimensions)
7
- values = (value || "xMidYMid meet").strip.split(/\s+/)
7
+ values = (value || "xMidYMid meet").split(' ')
8
8
  @x = @y = 0
9
9
 
10
10
  if values.first == "defer"
@@ -2,12 +2,19 @@ module Prawn::SVG::Calculators::Pixels
2
2
  class Measurement
3
3
  extend Prawn::Measurements
4
4
 
5
- def self.to_pixels(value, axis_length = nil)
5
+ def self.to_pixels(value, axis_length = nil, font_size: Prawn::SVG::Properties::EM)
6
6
  if value.is_a?(String)
7
- if match = value.match(/\d(cm|dm|ft|in|m|mm|yd)$/)
8
- send("#{match[1]}2pt", value.to_f)
9
- elsif match = value.match(/\dpc$/)
10
- value.to_f * 15 # according to http://www.w3.org/TR/SVG11/coords.html
7
+ if match = value.match(/\d(em|ex|pc|cm|mm|in)$/)
8
+ case match[1]
9
+ when 'em'
10
+ value.to_f * font_size
11
+ when 'ex'
12
+ value.to_f * (font_size / 2.0) # we don't have access to the x-height, so this is an approximation approved by the CSS spec
13
+ when 'pc'
14
+ value.to_f * 15 # according to http://www.w3.org/TR/SVG11/coords.html
15
+ else
16
+ send("#{match[1]}2pt", value.to_f)
17
+ end
11
18
  elsif value[-1..-1] == "%"
12
19
  value.to_f * axis_length / 100.0
13
20
  else
@@ -32,14 +39,14 @@ module Prawn::SVG::Calculators::Pixels
32
39
  end
33
40
 
34
41
  def pixels(value)
35
- value && Measurement.to_pixels(value, state.viewport_sizing.viewport_diagonal)
42
+ value && Measurement.to_pixels(value, state.viewport_sizing.viewport_diagonal, font_size: computed_properties.numerical_font_size)
36
43
  end
37
44
 
38
45
  def x_pixels(value)
39
- value && Measurement.to_pixels(value, state.viewport_sizing.viewport_width)
46
+ value && Measurement.to_pixels(value, state.viewport_sizing.viewport_width, font_size: computed_properties.numerical_font_size)
40
47
  end
41
48
 
42
49
  def y_pixels(value)
43
- value && Measurement.to_pixels(value, state.viewport_sizing.viewport_height)
50
+ value && Measurement.to_pixels(value, state.viewport_sizing.viewport_height, font_size: computed_properties.numerical_font_size)
44
51
  end
45
52
  end
@@ -14,7 +14,7 @@ class Prawn::SVG::Document
14
14
  :url_loader,
15
15
  :css_parser, :elements_by_id, :gradients
16
16
 
17
- def initialize(data, bounds, options, font_registry: nil)
17
+ def initialize(data, bounds, options, font_registry: nil, css_parser: CssParser::Parser.new)
18
18
  @root = REXML::Document.new(data).root
19
19
 
20
20
  if @root.nil?
@@ -31,6 +31,7 @@ class Prawn::SVG::Document
31
31
  @gradients = {}
32
32
  @fallback_font_name = options.fetch(:fallback_font_name, DEFAULT_FALLBACK_FONT_NAME)
33
33
  @font_registry = font_registry
34
+ @css_parser = css_parser
34
35
 
35
36
  @url_loader = Prawn::SVG::UrlLoader.new(
36
37
  enable_cache: options[:cache_images],
@@ -41,7 +42,7 @@ class Prawn::SVG::Document
41
42
  @sizing = Prawn::SVG::Calculators::DocumentSizing.new(bounds, @root.attributes)
42
43
  calculate_sizing(requested_width: options[:width], requested_height: options[:height])
43
44
 
44
- @css_parser = CssParser::Parser.new
45
+ parse_style_elements
45
46
 
46
47
  yield self if block_given?
47
48
  end
@@ -51,4 +52,16 @@ class Prawn::SVG::Document
51
52
  sizing.requested_height = requested_height
52
53
  sizing.calculate
53
54
  end
55
+
56
+ private
57
+
58
+ # <style> elements specified anywhere in the document apply to the entire
59
+ # document. Because of this, we load all <style> elements before parsing
60
+ # the rest of the document.
61
+ def parse_style_elements
62
+ REXML::XPath.match(root, '//style').each do |source|
63
+ data = source.texts.map(&:value).join
64
+ css_parser.add_block!(data)
65
+ end
66
+ end
54
67
  end
@@ -4,7 +4,7 @@ end
4
4
 
5
5
  require 'prawn/svg/elements/call_duplicator'
6
6
 
7
- %w(base depth_first_base root container viewport style text text_component line polyline polygon circle ellipse rect path use image gradient marker ignored).each do |filename|
7
+ %w(base depth_first_base root container viewport text text_component line polyline polygon circle ellipse rect path use image gradient marker ignored).each do |filename|
8
8
  require "prawn/svg/elements/#{filename}"
9
9
  end
10
10
 
@@ -16,7 +16,6 @@ module Prawn::SVG::Elements
16
16
  clipPath: Prawn::SVG::Elements::Container,
17
17
  switch: Prawn::SVG::Elements::Container,
18
18
  svg: Prawn::SVG::Elements::Viewport,
19
- style: Prawn::SVG::Elements::Style,
20
19
  text: Prawn::SVG::Elements::Text,
21
20
  line: Prawn::SVG::Elements::Line,
22
21
  polyline: Prawn::SVG::Elements::Polyline,
@@ -29,10 +28,12 @@ module Prawn::SVG::Elements
29
28
  image: Prawn::SVG::Elements::Image,
30
29
  linearGradient: Prawn::SVG::Elements::Gradient,
31
30
  marker: Prawn::SVG::Elements::Marker,
31
+ style: Prawn::SVG::Elements::Ignored, # because it is pre-parsed by Document
32
32
  title: Prawn::SVG::Elements::Ignored,
33
33
  desc: Prawn::SVG::Elements::Ignored,
34
34
  metadata: Prawn::SVG::Elements::Ignored,
35
35
  foreignObject: Prawn::SVG::Elements::Ignored,
36
36
  :"font-face" => Prawn::SVG::Elements::Ignored,
37
+ filter: Prawn::SVG::Elements::Ignored, # unsupported
37
38
  }
38
39
  end
@@ -13,6 +13,7 @@ class Prawn::SVG::Elements::Base
13
13
 
14
14
  PAINT_TYPES = %w(fill stroke)
15
15
  COMMA_WSP_REGEXP = Prawn::SVG::Elements::COMMA_WSP_REGEXP
16
+ SVG_NAMESPACE = "http://www.w3.org/2000/svg"
16
17
 
17
18
  SkipElementQuietly = Class.new(StandardError)
18
19
  SkipElementError = Class.new(StandardError)
@@ -33,8 +34,11 @@ class Prawn::SVG::Elements::Base
33
34
  @attributes = {}
34
35
  @properties = Prawn::SVG::Properties.new
35
36
 
36
- if source && id = source.attributes["id"]
37
- document.elements_by_id[id] = self
37
+ if source && !state.inside_use
38
+ id = source.attributes["id"]
39
+ id = id.strip if id
40
+
41
+ document.elements_by_id[id] = self if id && id != ''
38
42
  end
39
43
  end
40
44
 
@@ -121,7 +125,7 @@ class Prawn::SVG::Elements::Base
121
125
  def process_child_elements
122
126
  return unless source
123
127
 
124
- source.elements.each do |elem|
128
+ svg_child_elements.each do |elem|
125
129
  if element_class = Prawn::SVG::Elements::TAG_CLASS_MAPPING[elem.name.to_sym]
126
130
  add_call "save"
127
131
 
@@ -135,6 +139,14 @@ class Prawn::SVG::Elements::Base
135
139
  end
136
140
  end
137
141
 
142
+ def svg_child_elements
143
+ source.elements.select do |elem|
144
+ # To be strict, we shouldn't treat namespace-less elements as SVG, but for
145
+ # backwards compatibility, and because it doesn't hurt, we will.
146
+ elem.namespace == SVG_NAMESPACE || elem.namespace == ''
147
+ end
148
+ end
149
+
138
150
  def apply_calls_from_standard_attributes
139
151
  parse_transform_attribute_and_call
140
152
  parse_opacity_attributes_and_call
@@ -200,7 +212,7 @@ class Prawn::SVG::Elements::Base
200
212
  id_style = @document.css_parser.find_by_selector("##{source.attributes["id"]}") if source.attributes["id"]
201
213
 
202
214
  if classes = source.attributes["class"]
203
- class_styles = classes.strip.split(/\s+/).collect do |class_name|
215
+ class_styles = classes.split(' ').collect do |class_name|
204
216
  @document.css_parser.find_by_selector(".#{class_name}")
205
217
  end
206
218
  end
@@ -2,13 +2,18 @@ class Prawn::SVG::Elements::Container < Prawn::SVG::Elements::Base
2
2
  def parse
3
3
  state.disable_drawing = true if name == 'clipPath'
4
4
 
5
- if %w(symbol defs clipPath).include?(name)
6
- properties.display = 'none'
7
- computed_properties.display = 'none'
8
- end
5
+ set_display_none if name == 'symbol' && !state.inside_use
6
+ set_display_none if %w(defs clipPath).include?(name)
9
7
  end
10
8
 
11
9
  def container?
12
10
  true
13
11
  end
12
+
13
+ private
14
+
15
+ def set_display_none
16
+ properties.display = 'none'
17
+ computed_properties.display = 'none'
18
+ end
14
19
  end
@@ -35,7 +35,7 @@ class Prawn::SVG::Elements::DepthFirstBase < Prawn::SVG::Elements::Base
35
35
  def parse_child_elements
36
36
  return unless source
37
37
 
38
- source.elements.each do |elem|
38
+ svg_child_elements.each do |elem|
39
39
  if element_class = Prawn::SVG::Elements::TAG_CLASS_MAPPING[elem.name.to_sym]
40
40
  child = element_class.new(@document, elem, @calls, state.dup)
41
41
  child.parse_step
@@ -4,7 +4,7 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
4
4
 
5
5
  INSIDE_SPACE_REGEXP = /[ \t\r\n,]*/
6
6
  OUTSIDE_SPACE_REGEXP = /[ \t\r\n]*/
7
- INSIDE_REGEXP = /#{INSIDE_SPACE_REGEXP}([+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:(?<=[0-9])e[+-]?[0-9]+)?)/
7
+ INSIDE_REGEXP = /#{INSIDE_SPACE_REGEXP}([+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:(?<=[0-9])[eE][+-]?[0-9]+)?)/
8
8
  VALUES_REGEXP = /^#{INSIDE_REGEXP}/
9
9
  COMMAND_REGEXP = /^#{OUTSIDE_SPACE_REGEXP}([A-Za-z])((?:#{INSIDE_REGEXP})*)#{OUTSIDE_SPACE_REGEXP}/
10
10
 
@@ -1,5 +1,12 @@
1
1
  class Prawn::SVG::Elements::Text < Prawn::SVG::Elements::DepthFirstBase
2
- def parse
2
+ private
3
+
4
+ # This class doesn't represent an element in the SVG document; it's here
5
+ # to hold together text information. Because of this, we overload the
6
+ # parse_step and apply_step methods, and bypass all the normal processing
7
+ # of the element, delegating it to our root text component.
8
+
9
+ def parse_step
3
10
  state.text = Prawn::SVG::Elements::TextComponent::PositionsList.new([], [], [], [], [], nil)
4
11
 
5
12
  @text_root = Prawn::SVG::Elements::TextComponent.new(document, source, nil, state.dup)
@@ -8,22 +15,16 @@ class Prawn::SVG::Elements::Text < Prawn::SVG::Elements::DepthFirstBase
8
15
  reintroduce_trailing_and_leading_whitespace
9
16
  end
10
17
 
11
- def apply
18
+ def apply_step(calls)
19
+ @base_calls = @calls = calls
12
20
  add_call_and_enter "text_group"
13
- @text_root.apply_step(calls)
21
+ @text_root.apply_step(@calls)
14
22
  end
15
23
 
16
- private
17
-
18
24
  def drawable?
19
25
  false
20
26
  end
21
27
 
22
- def apply_calls_from_standard_attributes
23
- # overridden because we want the attributes to be applied in the TextComponent root,
24
- # which is a duplicate of this element.
25
- end
26
-
27
28
  def reintroduce_trailing_and_leading_whitespace
28
29
  printables = []
29
30
  built_printable_queue(printables, @text_root)
@@ -13,7 +13,7 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
13
13
 
14
14
  @commands = []
15
15
 
16
- text_children.each do |child|
16
+ svg_text_children.each do |child|
17
17
  if child.node_type == :text
18
18
  append_text(child)
19
19
  else
@@ -148,6 +148,12 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
148
148
  end
149
149
  end
150
150
 
151
+ def svg_text_children
152
+ text_children.select do |child|
153
+ child.node_type == :text || child.namespace == SVG_NAMESPACE || child.namespace == ''
154
+ end
155
+ end
156
+
151
157
  def text_children
152
158
  if name == 'tref'
153
159
  reference = find_referenced_element
@@ -1,4 +1,6 @@
1
1
  class Prawn::SVG::Elements::Use < Prawn::SVG::Elements::Base
2
+ attr_reader :referenced_element
3
+
2
4
  def parse
3
5
  require_attributes 'xlink:href'
4
6
 
@@ -9,21 +11,34 @@ class Prawn::SVG::Elements::Use < Prawn::SVG::Elements::Base
9
11
  end
10
12
 
11
13
  id = href[1..-1]
12
- @definition_element = @document.elements_by_id[id]
14
+ @referenced_element = @document.elements_by_id[id]
13
15
 
14
- if @definition_element.nil?
16
+ if referenced_element.nil?
15
17
  raise SkipElementError, "no tag with ID '#{id}' was found, referenced by use tag"
16
18
  end
17
19
 
20
+ state.inside_use = true
21
+
18
22
  @x = attributes['x']
19
23
  @y = attributes['y']
20
24
  end
21
25
 
26
+ def container?
27
+ true
28
+ end
29
+
22
30
  def apply
23
31
  if @x || @y
24
32
  add_call_and_enter "translate", x_pixels(@x || 0), -y_pixels(@y || 0)
25
33
  end
34
+ end
35
+
36
+ def process_child_elements
37
+ add_call "save"
38
+
39
+ child = referenced_element.class.new(referenced_element.document, referenced_element.source, calls, state.dup)
40
+ child.process
26
41
 
27
- add_calls_from_element @definition_element
42
+ add_call "restore"
28
43
  end
29
44
  end
@@ -3,7 +3,8 @@ class Prawn::SVG::State
3
3
  :text, :preserve_space,
4
4
  :fill_opacity, :stroke_opacity, :stroke_width,
5
5
  :computed_properties,
6
- :viewport_sizing
6
+ :viewport_sizing,
7
+ :inside_use
7
8
 
8
9
  def initialize
9
10
  @stroke_width = 1
@@ -1,5 +1,5 @@
1
1
  module Prawn
2
2
  module SVG
3
- VERSION = '0.25.2'
3
+ VERSION = '0.26.0'
4
4
  end
5
5
  end
@@ -4,6 +4,10 @@ describe Prawn::SVG::Calculators::Pixels do
4
4
  class TestPixelsCalculator
5
5
  include Prawn::SVG::Calculators::Pixels
6
6
 
7
+ def computed_properties
8
+ Struct.new(:numerical_font_size).new(16)
9
+ end
10
+
7
11
  [:x, :y, :pixels, :x_pixels, :y_pixels].each { |method| public method }
8
12
  end
9
13
 
@@ -31,13 +35,14 @@ describe Prawn::SVG::Calculators::Pixels do
31
35
  expect(subject.pixels(32.0)).to eq 32.0
32
36
  expect(subject.pixels("32")).to eq 32.0
33
37
  expect(subject.pixels("32unknown")).to eq 32.0
38
+ expect(subject.pixels("32px")).to eq 32.0
34
39
  expect(subject.pixels("32pt")).to eq 32.0
35
40
  expect(subject.pixels("32in")).to eq 32.0 * 72
36
- expect(subject.pixels("32ft")).to eq 32.0 * 72 * 12
37
41
  expect(subject.pixels("32pc")).to eq 32.0 * 15
42
+ expect(subject.pixels("4em")).to eq 4 * 16
43
+ expect(subject.pixels("4ex")).to eq 4 * 8
38
44
  expect(subject.pixels("32mm")).to be_within(0.0001).of(32 * 72 * 0.0393700787)
39
45
  expect(subject.pixels("32cm")).to be_within(0.0001).of(32 * 72 * 0.393700787)
40
- expect(subject.pixels("32m")).to be_within(0.0001).of(32 * 72 * 39.3700787)
41
46
  expect(subject.pixels("50%")).to eq 250
42
47
  end
43
48
  end
@@ -25,4 +25,37 @@ describe Prawn::SVG::Document do
25
25
  end
26
26
  end
27
27
  end
28
+
29
+ describe "#parse_style_elements" do
30
+ let(:svg) do
31
+ <<-SVG
32
+ <svg>
33
+ <some-tag>
34
+ <style>a
35
+ before&gt;
36
+ x <![CDATA[ y
37
+ inside <>&gt;
38
+ k ]]> j
39
+ after
40
+ z</style>
41
+ </some-tag>
42
+
43
+ <other-tag>
44
+ <more-tag>
45
+ <style>hello</style>
46
+ </more-tag>
47
+ </other-tag>
48
+ </svg>
49
+ SVG
50
+ end
51
+
52
+ it "scans the document for style tags and adds the style information to the css parser" do
53
+ css_parser = instance_double(CssParser::Parser)
54
+
55
+ expect(css_parser).to receive(:add_block!).with("a\n before>\n x y\n inside <>&gt;\n k j\n after\nz")
56
+ expect(css_parser).to receive(:add_block!).with("hello")
57
+
58
+ Prawn::SVG::Document.new(svg, bounds, options, css_parser: css_parser)
59
+ end
60
+ end
28
61
  end
@@ -2,7 +2,8 @@ require 'spec_helper'
2
2
 
3
3
  describe Prawn::SVG::Elements::Path do
4
4
  let(:source) { double(name: "path", attributes: {}) }
5
- let(:path) { Prawn::SVG::Elements::Path.new(nil, source, [], {}) }
5
+ let(:state) { Prawn::SVG::State.new }
6
+ let(:path) { Prawn::SVG::Elements::Path.new(nil, source, [], state) }
6
7
 
7
8
  before do
8
9
  allow(path).to receive(:attributes).and_return("d" => d)
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.25.2
4
+ version: 0.26.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: 2016-09-10 00:00:00.000000000 Z
11
+ date: 2016-12-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prawn
@@ -118,7 +118,6 @@ files:
118
118
  - lib/prawn/svg/elements/polyline.rb
119
119
  - lib/prawn/svg/elements/rect.rb
120
120
  - lib/prawn/svg/elements/root.rb
121
- - lib/prawn/svg/elements/style.rb
122
121
  - lib/prawn/svg/elements/text.rb
123
122
  - lib/prawn/svg/elements/text_component.rb
124
123
  - lib/prawn/svg/elements/use.rb
@@ -152,7 +151,6 @@ files:
152
151
  - spec/prawn/svg/elements/path_spec.rb
153
152
  - spec/prawn/svg/elements/polygon_spec.rb
154
153
  - spec/prawn/svg/elements/polyline_spec.rb
155
- - spec/prawn/svg/elements/style_spec.rb
156
154
  - spec/prawn/svg/elements/text_spec.rb
157
155
  - spec/prawn/svg/font_registry_spec.rb
158
156
  - spec/prawn/svg/font_spec.rb
@@ -1,10 +0,0 @@
1
- class Prawn::SVG::Elements::Style < Prawn::SVG::Elements::Base
2
- def parse
3
- if @document.css_parser
4
- data = source.texts.map(&:value).join
5
- @document.css_parser.add_block!(data)
6
- end
7
-
8
- raise SkipElementQuietly
9
- end
10
- end
@@ -1,23 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Prawn::SVG::Elements::Style do
4
- let(:svg) do
5
- <<-SVG
6
- <style>a
7
- before&gt;
8
- x <![CDATA[ y
9
- inside <>&gt;
10
- k ]]> j
11
- after
12
- z</style>
13
- SVG
14
- end
15
-
16
- let(:document) { Prawn::SVG::Document.new(svg, [800, 600], {}) }
17
- let(:element) { Prawn::SVG::Elements::Style.new(document, document.root, [], Prawn::SVG::State.new) }
18
-
19
- it "correctly collects the style information in a <style> tag" do
20
- expect(document.css_parser).to receive(:add_block!).with("a\n before>\n x y\n inside <>&gt;\n k j\n after\nz")
21
- element.process
22
- end
23
- end