prawn-svg 0.23.1 → 0.24.0

Sign up to get free protection for your applications and to get access to all the features.
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