prawn-svg 0.25.2 → 0.26.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: 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