prawn-svg 0.23.1 → 0.24.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -3
  3. data/README.md +15 -9
  4. data/lib/prawn-svg.rb +3 -0
  5. data/lib/prawn/svg/attributes.rb +1 -1
  6. data/lib/prawn/svg/attributes/clip_path.rb +6 -7
  7. data/lib/prawn/svg/attributes/opacity.rb +3 -3
  8. data/lib/prawn/svg/attributes/stroke.rb +6 -4
  9. data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +114 -0
  10. data/lib/prawn/svg/elements.rb +4 -1
  11. data/lib/prawn/svg/elements/base.rb +76 -69
  12. data/lib/prawn/svg/elements/container.rb +5 -6
  13. data/lib/prawn/svg/elements/gradient.rb +4 -4
  14. data/lib/prawn/svg/elements/image.rb +1 -1
  15. data/lib/prawn/svg/elements/line.rb +15 -7
  16. data/lib/prawn/svg/elements/marker.rb +72 -0
  17. data/lib/prawn/svg/elements/path.rb +23 -147
  18. data/lib/prawn/svg/elements/polygon.rb +14 -6
  19. data/lib/prawn/svg/elements/polyline.rb +12 -11
  20. data/lib/prawn/svg/elements/root.rb +3 -1
  21. data/lib/prawn/svg/elements/text.rb +38 -17
  22. data/lib/prawn/svg/font.rb +6 -6
  23. data/lib/prawn/svg/interface.rb +3 -0
  24. data/lib/prawn/svg/pathable.rb +130 -0
  25. data/lib/prawn/svg/properties.rb +122 -0
  26. data/lib/prawn/svg/state.rb +7 -29
  27. data/lib/prawn/svg/version.rb +1 -1
  28. data/spec/prawn/svg/elements/base_spec.rb +19 -32
  29. data/spec/prawn/svg/elements/line_spec.rb +37 -0
  30. data/spec/prawn/svg/elements/marker_spec.rb +90 -0
  31. data/spec/prawn/svg/elements/path_spec.rb +10 -10
  32. data/spec/prawn/svg/elements/polygon_spec.rb +49 -0
  33. data/spec/prawn/svg/elements/polyline_spec.rb +47 -0
  34. data/spec/prawn/svg/elements/style_spec.rb +1 -1
  35. data/spec/prawn/svg/elements/text_spec.rb +37 -5
  36. data/spec/prawn/svg/pathable_spec.rb +92 -0
  37. data/spec/prawn/svg/properties_spec.rb +186 -0
  38. data/spec/sample_svg/arrows.svg +73 -0
  39. data/spec/sample_svg/marker.svg +32 -0
  40. data/spec/sample_svg/polygon01.svg +25 -5
  41. metadata +23 -8
  42. data/lib/prawn/svg/attributes/color.rb +0 -5
  43. data/lib/prawn/svg/attributes/display.rb +0 -5
  44. data/lib/prawn/svg/attributes/font.rb +0 -38
  45. data/spec/prawn/svg/attributes/font_spec.rb +0 -52
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6f44a25b66d4e198d2f841ad98d277200cec4296
4
- data.tar.gz: 6ce1562107ec65b0d8453826401c894dac0bb57b
3
+ metadata.gz: 3b7ff6138c2cf404469001e4d18217b5758af6c0
4
+ data.tar.gz: 15a9474c4bf2f479db7a1e167292c81037d17069
5
5
  SHA512:
6
- metadata.gz: 5646e98d81e04aabeca34ae15444c7a6f9f210bae25e3217ee411a479c861544f43521fcafe272f3bdcc774a99b19327b5446857de6c633b7605fa86e98e3937
7
- data.tar.gz: 266cb1f5721dfd3cc363650c9bd27a601e52f76f79f672e2da0d04ca97ed505fb9a03d50f6f3bbfacfd768ca92153b01e49a1062ad748aee9c20b724435e118c
6
+ metadata.gz: a446a4b93bc4e590eb76ad93c3b3df2786a300a54c4fd0d28a6e2c1360ea7e7f5ff05ed7759e61cd723c6c16048639d2bc44f6f6be3126d43d59a3259b698465
7
+ data.tar.gz: e383d553e521457d5b248f6043086dcf0a9573060c50044e9d631dc5b5e2deff7ed0ad39aaae5c0291f9225608beb038fe488c90390a56a44042c1cd94da72e9
data/.travis.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 2.0.0
4
- - 2.1.6
5
- - 2.2.2
6
-
4
+ - 2.1.8
5
+ - 2.2.4
6
+ - 2.3.0
data/README.md CHANGED
@@ -7,7 +7,7 @@ An SVG renderer for the Prawn PDF library.
7
7
 
8
8
  This will take an SVG document as input and render it into your PDF. Find out more about the Prawn PDF library at:
9
9
 
10
- http://github.com/prawnpdf/prawn
10
+ http://github.com/prawnpdf/prawn
11
11
 
12
12
  prawn-svg is compatible with all versions of Prawn from 0.11.1 onwards, including the 1.x and 2.x series.
13
13
  The minimum Ruby version required is 2.0.0.
@@ -69,18 +69,24 @@ prawn-svg supports most but not all of the full SVG 1.1 specification. It curre
69
69
 
70
70
  - <tt>&lt;style&gt;</tt> plus <tt>id</tt>, <tt>class</tt> and <tt>style</tt> attributes (see CSS section below)
71
71
 
72
- - <tt>&lt;image&gt;</tt> with <tt>http:</tt>, <tt>https:</tt> and <tt>data:image/\*;base64</tt> schemes
72
+ - <tt>&lt;image&gt;</tt> with <tt>http:</tt>, <tt>https:</tt>, <tt>data:image/\*;base64</tt> and `file:` schemes
73
+ (`file:` is disabled by default for security reasons, see Options section above)
73
74
 
74
75
  - <tt>&lt;clipPath&gt;</tt>
75
76
 
76
- - <tt>&lt;linearGradient&gt;</tt> but only with Prawn 2.0.4+. gradientTransform, spreadMethod and stop-opacity are
77
- unimplemented.
77
+ - `<marker>`
78
78
 
79
- - attributes/styles: <tt>fill</tt>, <tt>stroke</tt>, <tt>stroke-width</tt>, <tt>stroke-linecap</tt>, <tt>stroke-dasharray</tt>, <tt>opacity</tt>, <tt>fill-opacity</tt>, <tt>stroke-opacity</tt>, <tt>transform</tt>, <tt>clip-path</tt>, <tt>display</tt>
79
+ - <tt>&lt;linearGradient&gt;</tt> is implemented but not currently working as we are waiting for a pull request to be accepted
80
+ into Prawn. (gradientTransform, spreadMethod and stop-opacity are unimplemented.)
80
81
 
81
- - the <tt>viewBox</tt> attribute on the <tt>&lt;svg&gt;</tt> tag
82
+ - `<switch>` and `<foreignObject>`, although prawn-svg cannot handle any data that is not SVG so `<foreignObject>`
83
+ tags are always ignored.
82
84
 
83
- - the <tt>preserveAspectRatio</tt> attribute on the <tt>&lt;svg&gt;</tt> and <tt>&lt;image&gt;</tt> tags
85
+ - attributes/styles: <tt>fill</tt>, <tt>stroke</tt>, <tt>stroke-width</tt>, <tt>stroke-linecap</tt>, <tt>stroke-dasharray</tt>, <tt>opacity</tt>, <tt>fill-opacity</tt>, <tt>stroke-opacity</tt>, <tt>transform</tt>, <tt>clip-path</tt>, <tt>display</tt>, `marker-start`, `marker-mid`, `marker-end`
86
+
87
+ - the <tt>viewBox</tt> attribute on <tt>&lt;svg&gt;</tt> and `<marker>` tags
88
+
89
+ - the <tt>preserveAspectRatio</tt> attribute on <tt>&lt;svg&gt;</tt>, <tt>&lt;image&gt;</tt> and `<marker>` tags
84
90
 
85
91
  - transform methods: <tt>translate</tt>, <tt>rotate</tt>, <tt>scale</tt>, <tt>matrix</tt>
86
92
 
@@ -92,11 +98,11 @@ prawn-svg supports most but not all of the full SVG 1.1 specification. It curre
92
98
 
93
99
  ## CSS
94
100
 
95
- 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.
101
+ 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 by the gem.
96
102
 
97
103
  ## Not supported
98
104
 
99
- prawn-svg does not support external <tt>url()</tt> references, measurements in <tt>en</tt> or <tt>em</tt>, sub-viewports, radial gradients, patterns or markers.
105
+ prawn-svg does not support sub-viewports, radial gradients, or patterns.
100
106
 
101
107
  ## Configuration
102
108
 
data/lib/prawn-svg.rb CHANGED
@@ -6,6 +6,7 @@ require 'prawn/svg/version'
6
6
  require 'css_parser'
7
7
 
8
8
  require 'prawn/svg/font_registry'
9
+ require 'prawn/svg/calculators/arc_to_bezier_curve'
9
10
  require 'prawn/svg/calculators/aspect_ratio'
10
11
  require 'prawn/svg/calculators/document_sizing'
11
12
  require 'prawn/svg/calculators/pixels'
@@ -15,6 +16,8 @@ require 'prawn/svg/loaders/file'
15
16
  require 'prawn/svg/loaders/web'
16
17
  require 'prawn/svg/color'
17
18
  require 'prawn/svg/attributes'
19
+ require 'prawn/svg/properties'
20
+ require 'prawn/svg/pathable'
18
21
  require 'prawn/svg/elements'
19
22
  require 'prawn/svg/extension'
20
23
  require 'prawn/svg/interface'
@@ -1,6 +1,6 @@
1
1
  module Prawn::SVG::Attributes
2
2
  end
3
3
 
4
- %w(transform opacity clip_path stroke font display color).each do |name|
4
+ %w(transform opacity clip_path stroke).each do |name|
5
5
  require "prawn/svg/attributes/#{name}"
6
6
  end
@@ -1,13 +1,12 @@
1
1
  module Prawn::SVG::Attributes::ClipPath
2
2
  def parse_clip_path_attribute_and_call
3
- return unless clip_path = attributes['clip-path']
3
+ return unless clip_path = properties.clip_path
4
+ return if clip_path == 'none'
4
5
 
5
- if (matches = clip_path.strip.match(/\Aurl\(#(.*)\)\z/)).nil?
6
- document.warnings << "Only clip-path attributes with the form 'url(#xxx)' are supported"
7
- elsif (clip_path_element = @document.elements_by_id[matches[1]]).nil?
8
- document.warnings << "clip-path ID '#{matches[1]}' not defined"
9
- elsif clip_path_element.source.name != "clipPath"
10
- document.warnings << "clip-path ID '#{matches[1]}' does not point to a clipPath tag"
6
+ clip_path_element = extract_element_from_url_id_reference(clip_path, 'clipPath')
7
+
8
+ if clip_path_element.nil?
9
+ document.warnings << "Could not resolve clip-path URI to a clipPath element"
11
10
  else
12
11
  add_call_and_enter 'save_graphics_state'
13
12
  add_calls_from_element clip_path_element
@@ -1,9 +1,9 @@
1
1
  module Prawn::SVG::Attributes::Opacity
2
2
  def parse_opacity_attributes_and_call
3
3
  # We can't do nested opacities quite like the SVG requires, but this is close enough.
4
- fill_opacity = stroke_opacity = clamp(attributes['opacity'].to_f, 0, 1) if attributes['opacity']
5
- fill_opacity = clamp(attributes['fill-opacity'].to_f, 0, 1) if attributes['fill-opacity']
6
- stroke_opacity = clamp(attributes['stroke-opacity'].to_f, 0, 1) if attributes['stroke-opacity']
4
+ fill_opacity = stroke_opacity = clamp(properties.opacity.to_f, 0, 1) if properties.opacity
5
+ fill_opacity = clamp(properties.fill_opacity.to_f, 0, 1) if properties.fill_opacity
6
+ stroke_opacity = clamp(properties.stroke_opacity.to_f, 0, 1) if properties.stroke_opacity
7
7
 
8
8
  if fill_opacity || stroke_opacity
9
9
  state.fill_opacity *= fill_opacity || 1
@@ -2,15 +2,17 @@ module Prawn::SVG::Attributes::Stroke
2
2
  CAP_STYLE_TRANSLATIONS = {"butt" => :butt, "round" => :round, "square" => :projecting_square}
3
3
 
4
4
  def parse_stroke_attributes_and_call
5
- if width = attributes['stroke-width']
6
- add_call('line_width', distance(width))
5
+ if width_string = properties.stroke_width
6
+ width = distance(width_string)
7
+ state.stroke_width = width
8
+ add_call('line_width', width)
7
9
  end
8
10
 
9
- if (linecap = attribute_value_as_keyword('stroke-linecap')) && linecap != 'inherit'
11
+ if (linecap = properties.stroke_linecap) && linecap != 'inherit'
10
12
  add_call('cap_style', CAP_STYLE_TRANSLATIONS.fetch(linecap, :butt))
11
13
  end
12
14
 
13
- if dasharray = attribute_value_as_keyword('stroke-dasharray')
15
+ if dasharray = properties.stroke_dasharray
14
16
  case dasharray
15
17
  when 'inherit'
16
18
  # don't do anything
@@ -0,0 +1,114 @@
1
+ module Prawn::SVG::Calculators
2
+ module ArcToBezierCurve
3
+ protected
4
+
5
+ # Convert the elliptical arc to a cubic bézier curve using this algorithm:
6
+ # http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf
7
+ def calculate_bezier_curve_points_for_arc(cx, cy, a, b, lambda_1, lambda_2, theta)
8
+ e = lambda do |eta|
9
+ [
10
+ cx + a * Math.cos(theta) * Math.cos(eta) - b * Math.sin(theta) * Math.sin(eta),
11
+ cy + a * Math.sin(theta) * Math.cos(eta) + b * Math.cos(theta) * Math.sin(eta)
12
+ ]
13
+ end
14
+
15
+ ep = lambda do |eta|
16
+ [
17
+ -a * Math.cos(theta) * Math.sin(eta) - b * Math.sin(theta) * Math.cos(eta),
18
+ -a * Math.sin(theta) * Math.sin(eta) + b * Math.cos(theta) * Math.cos(eta)
19
+ ]
20
+ end
21
+
22
+ iterations = 1
23
+ d_lambda = lambda_2 - lambda_1
24
+
25
+ while iterations < 1024
26
+ if d_lambda.abs <= Math::PI / 2.0
27
+ # TODO : run error algorithm, see whether it meets threshold or not
28
+ # puts "error = #{calculate_curve_approximation_error(a, b, eta1, eta1 + d_eta)}"
29
+ break
30
+ end
31
+ iterations *= 2
32
+ d_lambda = (lambda_2 - lambda_1) / iterations
33
+ end
34
+
35
+ (0...iterations).collect do |iteration|
36
+ eta_a, eta_b = calculate_eta_from_lambda(a, b, lambda_1+iteration*d_lambda, lambda_1+(iteration+1)*d_lambda)
37
+ d_eta = eta_b - eta_a
38
+
39
+ alpha = Math.sin(d_eta) * ((Math.sqrt(4 + 3 * Math.tan(d_eta / 2) ** 2) - 1) / 3)
40
+
41
+ x1, y1 = e[eta_a]
42
+ x2, y2 = e[eta_b]
43
+
44
+ ep_eta1_x, ep_eta1_y = ep[eta_a]
45
+ q1_x = x1 + alpha * ep_eta1_x
46
+ q1_y = y1 + alpha * ep_eta1_y
47
+
48
+ ep_eta2_x, ep_eta2_y = ep[eta_b]
49
+ q2_x = x2 - alpha * ep_eta2_x
50
+ q2_y = y2 - alpha * ep_eta2_y
51
+
52
+ {:p2 => [x2, y2], :q1 => [q1_x, q1_y], :q2 => [q2_x, q2_y]}
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ ERROR_COEFFICIENTS_A = [
59
+ [
60
+ [3.85268, -21.229, -0.330434, 0.0127842],
61
+ [-1.61486, 0.706564, 0.225945, 0.263682],
62
+ [-0.910164, 0.388383, 0.00551445, 0.00671814],
63
+ [-0.630184, 0.192402, 0.0098871, 0.0102527]
64
+ ],
65
+ [
66
+ [-0.162211, 9.94329, 0.13723, 0.0124084],
67
+ [-0.253135, 0.00187735, 0.0230286, 0.01264],
68
+ [-0.0695069, -0.0437594, 0.0120636, 0.0163087],
69
+ [-0.0328856, -0.00926032, -0.00173573, 0.00527385]
70
+ ]
71
+ ]
72
+
73
+ ERROR_COEFFICIENTS_B = [
74
+ [
75
+ [0.0899116, -19.2349, -4.11711, 0.183362],
76
+ [0.138148, -1.45804, 1.32044, 1.38474],
77
+ [0.230903, -0.450262, 0.219963, 0.414038],
78
+ [0.0590565, -0.101062, 0.0430592, 0.0204699]
79
+ ],
80
+ [
81
+ [0.0164649, 9.89394, 0.0919496, 0.00760802],
82
+ [0.0191603, -0.0322058, 0.0134667, -0.0825018],
83
+ [0.0156192, -0.017535, 0.00326508, -0.228157],
84
+ [-0.0236752, 0.0405821, -0.0173086, 0.176187]
85
+ ]
86
+ ]
87
+
88
+ def calculate_curve_approximation_error(a, b, eta1, eta2)
89
+ b_over_a = b / a
90
+ coefficents = b_over_a < 0.25 ? ERROR_COEFFICIENTS_A : ERROR_COEFFICIENTS_B
91
+
92
+ c = lambda do |i|
93
+ (0..3).inject(0) do |accumulator, j|
94
+ coef = coefficents[i][j]
95
+ accumulator + ((coef[0] * b_over_a**2 + coef[1] * b_over_a + coef[2]) / (b_over_a * coef[3])) * Math.cos(j * (eta1 + eta2))
96
+ end
97
+ end
98
+
99
+ ((0.001 * b_over_a**2 + 4.98 * b_over_a + 0.207) / (b_over_a * 0.0067)) * a * Math.exp(c[0] + c[1] * (eta2 - eta1))
100
+ end
101
+
102
+ def calculate_eta_from_lambda(a, b, lambda_1, lambda_2)
103
+ # 2.2.1
104
+ eta1 = Math.atan2(Math.sin(lambda_1) / b, Math.cos(lambda_1) / a)
105
+ eta2 = Math.atan2(Math.sin(lambda_2) / b, Math.cos(lambda_2) / a)
106
+
107
+ # ensure eta1 <= eta2 <= eta1 + 2*PI
108
+ eta2 -= 2 * Math::PI * ((eta2 - eta1) / (2 * Math::PI)).floor
109
+ eta2 += 2 * Math::PI if lambda_2 - lambda_1 > Math::PI && eta2 - eta1 < Math::PI
110
+
111
+ [eta1, eta2]
112
+ end
113
+ end
114
+ end
@@ -2,7 +2,7 @@ module Prawn::SVG::Elements
2
2
  COMMA_WSP_REGEXP = /(?:\s+,?\s*|,\s*)/
3
3
  end
4
4
 
5
- %w(base root container style text line polyline polygon circle ellipse rect path use image gradient ignored).each do |filename|
5
+ %w(base root container style text line polyline polygon circle ellipse rect path use image gradient marker ignored).each do |filename|
6
6
  require "prawn/svg/elements/#{filename}"
7
7
  end
8
8
 
@@ -13,6 +13,7 @@ module Prawn::SVG::Elements
13
13
  symbol: Prawn::SVG::Elements::Container,
14
14
  defs: Prawn::SVG::Elements::Container,
15
15
  clipPath: Prawn::SVG::Elements::Container,
16
+ switch: Prawn::SVG::Elements::Container,
16
17
  style: Prawn::SVG::Elements::Style,
17
18
  text: Prawn::SVG::Elements::Text,
18
19
  line: Prawn::SVG::Elements::Line,
@@ -25,9 +26,11 @@ module Prawn::SVG::Elements
25
26
  use: Prawn::SVG::Elements::Use,
26
27
  image: Prawn::SVG::Elements::Image,
27
28
  linearGradient: Prawn::SVG::Elements::Gradient,
29
+ marker: Prawn::SVG::Elements::Marker,
28
30
  title: Prawn::SVG::Elements::Ignored,
29
31
  desc: Prawn::SVG::Elements::Ignored,
30
32
  metadata: Prawn::SVG::Elements::Ignored,
33
+ foreignObject: Prawn::SVG::Elements::Ignored,
31
34
  :"font-face" => Prawn::SVG::Elements::Ignored,
32
35
  }
33
36
  end
@@ -5,20 +5,19 @@ class Prawn::SVG::Elements::Base
5
5
  include Prawn::SVG::Attributes::Opacity
6
6
  include Prawn::SVG::Attributes::ClipPath
7
7
  include Prawn::SVG::Attributes::Stroke
8
- include Prawn::SVG::Attributes::Font
9
- include Prawn::SVG::Attributes::Display
10
- include Prawn::SVG::Attributes::Color
11
8
 
9
+ PAINT_TYPES = %w(fill stroke)
12
10
  COMMA_WSP_REGEXP = Prawn::SVG::Elements::COMMA_WSP_REGEXP
13
11
 
14
12
  SkipElementQuietly = Class.new(StandardError)
15
13
  SkipElementError = Class.new(StandardError)
16
14
  MissingAttributesError = Class.new(SkipElementError)
17
15
 
18
- attr_reader :document, :source, :parent_calls, :base_calls, :state, :attributes
16
+ attr_reader :document, :source, :parent_calls, :base_calls, :state, :attributes, :properties
19
17
  attr_accessor :calls
20
18
 
21
19
  def_delegators :@document, :x, :y, :distance, :points, :warnings
20
+ def_delegator :@state, :computed_properties
22
21
 
23
22
  def initialize(document, source, parent_calls, state)
24
23
  @document = document
@@ -26,28 +25,35 @@ class Prawn::SVG::Elements::Base
26
25
  @parent_calls = parent_calls
27
26
  @state = state
28
27
  @base_calls = @calls = []
28
+ @attributes = {}
29
+ @properties = Prawn::SVG::Properties.new
29
30
 
30
- if id = source.attributes["id"]
31
+ if source && id = source.attributes["id"]
31
32
  document.elements_by_id[id] = self
32
33
  end
33
34
  end
34
35
 
35
36
  def process
36
- combine_attributes_and_style_declarations
37
- parse_standard_attributes
37
+ extract_attributes_and_properties
38
+ parse_and_apply
39
+ end
40
+
41
+ def parse_and_apply
38
42
  parse
39
43
 
40
44
  apply_calls_from_standard_attributes
41
45
  apply
42
46
 
43
- append_calls_to_parent
47
+ process_child_elements if container?
48
+
49
+ append_calls_to_parent unless computed_properties.display == 'none'
44
50
  rescue SkipElementQuietly
45
51
  rescue SkipElementError => e
46
52
  @document.warnings << e.message
47
53
  end
48
54
 
49
55
  def name
50
- @name ||= source.name
56
+ @name ||= source ? source.name : "???"
51
57
  end
52
58
 
53
59
  protected
@@ -74,6 +80,15 @@ class Prawn::SVG::Elements::Base
74
80
  @calls = @calls.last.last
75
81
  end
76
82
 
83
+ def push_call_position
84
+ @call_positions ||= []
85
+ @call_positions << @calls
86
+ end
87
+
88
+ def pop_call_position
89
+ @calls = @call_positions.pop
90
+ end
91
+
77
92
  def append_calls_to_parent
78
93
  @parent_calls.concat(@base_calls)
79
94
  end
@@ -82,7 +97,16 @@ class Prawn::SVG::Elements::Base
82
97
  @calls.concat other.base_calls
83
98
  end
84
99
 
100
+ def new_call_context_from_base
101
+ old_calls = @calls
102
+ @calls = @base_calls
103
+ yield
104
+ @calls = old_calls
105
+ end
106
+
85
107
  def process_child_elements
108
+ return unless source
109
+
86
110
  source.elements.each do |elem|
87
111
  if element_class = Prawn::SVG::Elements::TAG_CLASS_MAPPING[elem.name.to_sym]
88
112
  add_call "save"
@@ -101,16 +125,16 @@ class Prawn::SVG::Elements::Base
101
125
  parse_transform_attribute_and_call
102
126
  parse_opacity_attributes_and_call
103
127
  parse_clip_path_attribute_and_call
104
- draw_types = parse_fill_and_stroke_attributes_and_call
128
+ apply_colors
105
129
  parse_stroke_attributes_and_call
106
- parse_font_attributes_and_call
107
- parse_display_attribute
108
- apply_drawing_call(draw_types)
130
+ apply_drawing_call
109
131
  end
110
132
 
111
- def apply_drawing_call(draw_types)
133
+ def apply_drawing_call
112
134
  if !state.disable_drawing && !container?
113
- if draw_types.empty? || state.display == "none"
135
+ draw_types = PAINT_TYPES.select { |property| computed_properties.send(property) != 'none' }
136
+
137
+ if draw_types.empty?
114
138
  add_call_and_enter("end_path")
115
139
  else
116
140
  add_call_and_enter(draw_types.join("_and_"))
@@ -118,46 +142,37 @@ class Prawn::SVG::Elements::Base
118
142
  end
119
143
  end
120
144
 
121
- def parse_standard_attributes
122
- parse_color_attribute
123
- end
145
+ def apply_colors
146
+ PAINT_TYPES.each do |type|
147
+ color = properties.send(type)
124
148
 
125
- def parse_fill_and_stroke_attributes_and_call
126
- ["fill", "stroke"].select do |type|
127
- case keyword = attribute_value_as_keyword(type)
128
- when nil
129
- when 'inherit'
130
- when 'none'
131
- state.disable_draw_type(type)
132
- else
133
- state.disable_draw_type(type)
149
+ next if [nil, 'inherit', 'none'].include?(color)
134
150
 
135
- if keyword == 'currentcolor'
136
- color = state.color
137
- else
138
- color = @attributes[type]
139
- end
151
+ if color == 'currentColor'
152
+ color = computed_properties.color
153
+ end
140
154
 
141
- results = Prawn::SVG::Color.parse(color, document.gradients)
142
-
143
- results.each do |result|
144
- case result
145
- when Prawn::SVG::Color::Hex
146
- state.enable_draw_type(type)
147
- add_call "#{type}_color", result.value
148
- break
149
- when Prawn::SVG::Elements::Gradient
150
- arguments = result.gradient_arguments(self)
151
- if arguments
152
- state.enable_draw_type(type)
153
- add_call "#{type}_gradient", **arguments
154
- break
155
- end
155
+ results = Prawn::SVG::Color.parse(color, document.gradients)
156
+
157
+ success = results.detect do |result|
158
+ case result
159
+ when Prawn::SVG::Color::Hex
160
+ add_call "#{type}_color", result.value
161
+ true
162
+ when Prawn::SVG::Elements::Gradient
163
+ arguments = result.gradient_arguments(self)
164
+ if arguments
165
+ add_call "#{type}_gradient", **arguments
166
+ true
156
167
  end
157
168
  end
158
169
  end
159
170
 
160
- state.draw_type(type)
171
+ # If we were unable to find a suitable color candidate,
172
+ # we turn off this type of paint.
173
+ if success.nil?
174
+ computed_properties.set(type, 'none')
175
+ end
161
176
  end
162
177
  end
163
178
 
@@ -165,7 +180,7 @@ class Prawn::SVG::Elements::Base
165
180
  [[value, min_value].max, max_value].min
166
181
  end
167
182
 
168
- def combine_attributes_and_style_declarations
183
+ def extract_attributes_and_properties
169
184
  if @document && @document.css_parser
170
185
  tag_style = @document.css_parser.find_by_selector(source.name)
171
186
  id_style = @document.css_parser.find_by_selector("##{source.attributes["id"]}") if source.attributes["id"]
@@ -185,12 +200,14 @@ class Prawn::SVG::Elements::Base
185
200
  style = source.attributes['style'] || ""
186
201
  end
187
202
 
188
- @attributes = parse_css_declarations(style)
189
-
190
203
  source.attributes.each do |name, value|
191
- name = name.downcase # TODO : this is incorrect; attributes are case sensitive
192
- @attributes[name] = value unless @attributes[name]
204
+ # Properties#set returns nil if it's not a recognised property name
205
+ @properties.set(name, value) or @attributes[name] = value
193
206
  end
207
+
208
+ @properties.load_hash(parse_css_declarations(style))
209
+
210
+ state.computed_properties.compute_properties(@properties)
194
211
  end
195
212
 
196
213
  def parse_css_declarations(declarations)
@@ -207,22 +224,6 @@ class Prawn::SVG::Elements::Base
207
224
  output
208
225
  end
209
226
 
210
- def attribute_value_as_keyword(name)
211
- if value = @attributes[name]
212
- value.strip.downcase
213
- end
214
- end
215
-
216
- def parse_points(points_string)
217
- points_string.
218
- to_s.
219
- strip.
220
- gsub(/(\d)-(\d)/, '\1 -\2').
221
- split(COMMA_WSP_REGEXP).
222
- each_slice(2).
223
- map {|x, y| [x(x), y(y)]}
224
- end
225
-
226
227
  def require_attributes(*names)
227
228
  missing_attrs = names - attributes.keys
228
229
  if missing_attrs.any?
@@ -235,4 +236,10 @@ class Prawn::SVG::Elements::Base
235
236
  raise SkipElementError, "Invalid attributes on tag #{name}; skipping tag"
236
237
  end
237
238
  end
239
+
240
+ def extract_element_from_url_id_reference(value, expected_type = nil)
241
+ matches = value.strip.match(/\Aurl\(\s*#(\S+)\s*\)\z/i) if value
242
+ element = document.elements_by_id[matches[1]] if matches
243
+ element if element && (expected_type.nil? || element.name == expected_type)
244
+ end
238
245
  end