prawn-svg 0.24.0 → 0.25.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -9
  3. data/lib/prawn/svg/attributes.rb +1 -1
  4. data/lib/prawn/svg/attributes/space.rb +10 -0
  5. data/lib/prawn/svg/attributes/stroke.rb +2 -2
  6. data/lib/prawn/svg/attributes/transform.rb +2 -2
  7. data/lib/prawn/svg/calculators/document_sizing.rb +4 -5
  8. data/lib/prawn/svg/calculators/pixels.rb +27 -3
  9. data/lib/prawn/svg/document.rb +0 -18
  10. data/lib/prawn/svg/elements.rb +2 -2
  11. data/lib/prawn/svg/elements/base.rb +14 -2
  12. data/lib/prawn/svg/elements/circle.rb +1 -1
  13. data/lib/prawn/svg/elements/depth_first_base.rb +52 -0
  14. data/lib/prawn/svg/elements/ellipse.rb +2 -2
  15. data/lib/prawn/svg/elements/image.rb +2 -2
  16. data/lib/prawn/svg/elements/line.rb +4 -4
  17. data/lib/prawn/svg/elements/marker.rb +2 -2
  18. data/lib/prawn/svg/elements/rect.rb +3 -3
  19. data/lib/prawn/svg/elements/root.rb +5 -1
  20. data/lib/prawn/svg/elements/text.rb +47 -85
  21. data/lib/prawn/svg/elements/text_component.rb +186 -0
  22. data/lib/prawn/svg/elements/use.rb +1 -1
  23. data/lib/prawn/svg/elements/viewport.rb +28 -0
  24. data/lib/prawn/svg/interface.rb +20 -12
  25. data/lib/prawn/svg/pathable.rb +18 -2
  26. data/lib/prawn/svg/state.rb +3 -2
  27. data/lib/prawn/svg/version.rb +1 -1
  28. data/spec/prawn/svg/attributes/transform_spec.rb +2 -2
  29. data/spec/prawn/svg/calculators/pixels_spec.rb +72 -0
  30. data/spec/prawn/svg/document_spec.rb +0 -28
  31. data/spec/prawn/svg/elements/base_spec.rb +1 -1
  32. data/spec/prawn/svg/elements/gradient_spec.rb +1 -1
  33. data/spec/prawn/svg/elements/line_spec.rb +1 -1
  34. data/spec/prawn/svg/elements/marker_spec.rb +4 -0
  35. data/spec/prawn/svg/elements/text_spec.rb +104 -15
  36. data/spec/sample_svg/markers_degenerate_cp.svg +19 -0
  37. data/spec/sample_svg/offset_viewport.svg +8 -0
  38. data/spec/sample_svg/subviewports.svg +173 -0
  39. data/spec/sample_svg/subviewports2.svg +16 -0
  40. data/spec/sample_svg/tref01.svg +21 -0
  41. data/spec/sample_svg/tspan05.svg +40 -0
  42. data/spec/sample_svg/tspan91.svg +34 -0
  43. data/spec/spec_helper.rb +6 -0
  44. metadata +22 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3b7ff6138c2cf404469001e4d18217b5758af6c0
4
- data.tar.gz: 15a9474c4bf2f479db7a1e167292c81037d17069
3
+ metadata.gz: bcd6cefab7e10066561afcaee2e9aeed78c9b30f
4
+ data.tar.gz: dbe371b99f2cf4953de90e892a3f95718abdd02e
5
5
  SHA512:
6
- metadata.gz: a446a4b93bc4e590eb76ad93c3b3df2786a300a54c4fd0d28a6e2c1360ea7e7f5ff05ed7759e61cd723c6c16048639d2bc44f6f6be3126d43d59a3259b698465
7
- data.tar.gz: e383d553e521457d5b248f6043086dcf0a9573060c50044e9d631dc5b5e2deff7ed0ad39aaae5c0291f9225608beb038fe488c90390a56a44042c1cd94da72e9
6
+ metadata.gz: 7c567a3f24cebf73c40ec247799f78e730b5f3479be3b486791b7bba9cf7140611db88b00e49422b6bb151613a09296e167e08b30b9f2bd73acf34a9ff49f193
7
+ data.tar.gz: b2e8135844d8e09dbabdf153794775531c6f3b15a6ca796b7a3a765a135b16837fa9127d5d88c616d07709868d296210eba044605540afe010d68e42a63e36c7
data/README.md CHANGED
@@ -60,14 +60,14 @@ prawn-svg supports most but not all of the full SVG 1.1 specification. It curre
60
60
  - <tt>&lt;path&gt;</tt> supports all commands defined in SVG 1.1, although the
61
61
  implementation of elliptical arc is a bit rough at the moment.
62
62
 
63
- - <tt>&lt;text&gt;</tt> and <tt>&lt;tspan&gt;</tt> with attributes
64
- <tt>text-anchor</tt>, <tt>font-size</tt>, <tt>font-family</tt>, <tt>font-weight</tt>, <tt>font-style</tt>, <tt>letter-spacing</tt>, <tt>dx</tt>, <tt>dy</tt>
63
+ - `<text>`, `<tspan>` and `<tref>` with attributes `x`, `y`, `dx`, `dy`, `rotate`, and with extra properties
64
+ `text-anchor`, `font-size`, `font-family`, `font-weight`, `font-style`, `letter-spacing`
65
65
 
66
66
  - <tt>&lt;svg&gt;</tt>, <tt>&lt;g&gt;</tt> and <tt>&lt;symbol&gt;</tt>
67
67
 
68
68
  - <tt>&lt;use&gt;</tt>
69
69
 
70
- - <tt>&lt;style&gt;</tt> plus <tt>id</tt>, <tt>class</tt> and <tt>style</tt> attributes (see CSS section below)
70
+ - <tt>&lt;style&gt;</tt> (see CSS section below)
71
71
 
72
72
  - <tt>&lt;image&gt;</tt> with <tt>http:</tt>, <tt>https:</tt>, <tt>data:image/\*;base64</tt> and `file:` schemes
73
73
  (`file:` is disabled by default for security reasons, see Options section above)
@@ -82,19 +82,23 @@ prawn-svg supports most but not all of the full SVG 1.1 specification. It curre
82
82
  - `<switch>` and `<foreignObject>`, although prawn-svg cannot handle any data that is not SVG so `<foreignObject>`
83
83
  tags are always ignored.
84
84
 
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`
85
+ - properties: `clip-path`, `color`, `display`, `fill-opacity`, `fill`, `opacity`, `overflow`, `stroke`, `stroke-dasharray`, `stroke-linecap`, `stroke-opacity`, `stroke-width`
86
86
 
87
- - the <tt>viewBox</tt> attribute on <tt>&lt;svg&gt;</tt> and `<marker>` tags
87
+ - properties on lines, polylines, polygons and paths: `marker-end`, `marker-mid`, `marker-start`
88
88
 
89
- - the <tt>preserveAspectRatio</tt> attribute on <tt>&lt;svg&gt;</tt>, <tt>&lt;image&gt;</tt> and `<marker>` tags
89
+ - attributes on all elements: `class`, `id`, `style`, `transform`, `xml:space`
90
90
 
91
- - transform methods: <tt>translate</tt>, <tt>rotate</tt>, <tt>scale</tt>, <tt>matrix</tt>
91
+ - the <tt>viewBox</tt> attribute on <tt>&lt;svg&gt;</tt> and `<marker>` elements
92
+
93
+ - the <tt>preserveAspectRatio</tt> attribute on <tt>&lt;svg&gt;</tt>, <tt>&lt;image&gt;</tt> and `<marker>` elements
94
+
95
+ - transform methods: <tt>translate()</tt>, <tt>rotate()</tt>, <tt>scale()</tt>, <tt>matrix()</tt>
92
96
 
93
97
  - colors: HTML standard names, <tt>#xxx</tt>, <tt>#xxxxxx</tt>, <tt>rgb(1, 2, 3)</tt>, <tt>rgb(1%, 2%, 3%)</tt>
94
98
 
95
99
  - measurements specified in <tt>pt</tt>, <tt>cm</tt>, <tt>dm</tt>, <tt>ft</tt>, <tt>in</tt>, <tt>m</tt>, <tt>mm</tt>, <tt>yd</tt>, <tt>pc</tt>, <tt>%</tt>
96
100
 
97
- - fonts: generic CSS fonts, built-in PDF fonts, and any TTF fonts in your fonts path
101
+ - fonts: generic CSS fonts, built-in PDF fonts, and any TTF fonts in your fonts path, specified in any of the measurements above plus `em` or `rem`
98
102
 
99
103
  ## CSS
100
104
 
@@ -102,7 +106,7 @@ prawn-svg uses the css_parser gem to parse CSS <tt>&lt;style&gt;</tt> blocks. I
102
106
 
103
107
  ## Not supported
104
108
 
105
- prawn-svg does not support sub-viewports, radial gradients, or patterns.
109
+ prawn-svg does not support radial gradients or patterns.
106
110
 
107
111
  ## Configuration
108
112
 
@@ -1,6 +1,6 @@
1
1
  module Prawn::SVG::Attributes
2
2
  end
3
3
 
4
- %w(transform opacity clip_path stroke).each do |name|
4
+ %w(transform opacity clip_path stroke space).each do |name|
5
5
  require "prawn/svg/attributes/#{name}"
6
6
  end
@@ -0,0 +1,10 @@
1
+ module Prawn::SVG::Attributes::Space
2
+ def parse_xml_space_attribute
3
+ case attributes['xml:space']
4
+ when 'preserve'
5
+ state.preserve_space = true
6
+ when 'default'
7
+ state.preserve_space = false
8
+ end
9
+ end
10
+ end
@@ -3,7 +3,7 @@ module Prawn::SVG::Attributes::Stroke
3
3
 
4
4
  def parse_stroke_attributes_and_call
5
5
  if width_string = properties.stroke_width
6
- width = distance(width_string)
6
+ width = pixels(width_string)
7
7
  state.stroke_width = width
8
8
  add_call('line_width', width)
9
9
  end
@@ -21,7 +21,7 @@ module Prawn::SVG::Attributes::Stroke
21
21
  else
22
22
  array = dasharray.split(Prawn::SVG::Elements::COMMA_WSP_REGEXP)
23
23
  array *= 2 if array.length % 2 == 1
24
- number_array = array.map {|value| distance(value)}
24
+ number_array = array.map {|value| pixels(value)}
25
25
 
26
26
  if number_array.any? {|number| number < 0}
27
27
  @document.warnings << "stroke-dasharray cannot have negative numbers; treating as 'none'"
@@ -6,7 +6,7 @@ module Prawn::SVG::Attributes::Transform
6
6
  case name
7
7
  when 'translate'
8
8
  x, y = arguments
9
- add_call_and_enter name, distance(x.to_f, :x), -distance(y.to_f, :y)
9
+ add_call_and_enter name, x_pixels(x.to_f), -y_pixels(y.to_f)
10
10
 
11
11
  when 'rotate'
12
12
  r, x, y = arguments.collect {|a| a.to_f}
@@ -29,7 +29,7 @@ module Prawn::SVG::Attributes::Transform
29
29
  warnings << "transform 'matrix' must have six arguments"
30
30
  else
31
31
  a, b, c, d, e, f = arguments.collect {|argument| argument.to_f}
32
- add_call_and_enter "transformation_matrix", a, -b, -c, d, distance(e, :x), -distance(f, :y)
32
+ add_call_and_enter "transformation_matrix", a, -b, -c, d, x_pixels(e), -y_pixels(f)
33
33
  end
34
34
 
35
35
  else
@@ -30,13 +30,12 @@ module Prawn::SVG::Calculators
30
30
  container_width = @requested_width || @bounds[0]
31
31
  container_height = @requested_height || @bounds[1]
32
32
 
33
- @output_width = Pixels.to_pixels(@document_width || @requested_width, container_width)
34
- @output_height = Pixels.to_pixels(@document_height || @requested_height, container_height)
33
+ @output_width = Pixels::Measurement.to_pixels(@document_width || @requested_width, container_width)
34
+ @output_height = Pixels::Measurement.to_pixels(@document_height || @requested_height, container_height)
35
35
 
36
36
  if @view_box
37
37
  values = @view_box.strip.split(Prawn::SVG::Elements::COMMA_WSP_REGEXP)
38
38
  @x_offset, @y_offset, @viewport_width, @viewport_height = values.map {|value| value.to_f}
39
- @x_offset = -@x_offset
40
39
 
41
40
  if @viewport_width > 0 && @viewport_height > 0
42
41
  @output_width ||= container_width
@@ -49,8 +48,8 @@ module Prawn::SVG::Calculators
49
48
  @y_offset -= aspect.y / @y_scale
50
49
  end
51
50
  else
52
- @output_width ||= Pixels.to_pixels(DEFAULT_WIDTH, container_width)
53
- @output_height ||= Pixels.to_pixels(DEFAULT_HEIGHT, container_height)
51
+ @output_width ||= Pixels::Measurement.to_pixels(DEFAULT_WIDTH, container_width)
52
+ @output_height ||= Pixels::Measurement.to_pixels(DEFAULT_HEIGHT, container_height)
54
53
 
55
54
  @viewport_width = @output_width
56
55
  @viewport_height = @output_height
@@ -1,8 +1,8 @@
1
- module Prawn::SVG::Calculators
2
- class Pixels
1
+ module Prawn::SVG::Calculators::Pixels
2
+ class Measurement
3
3
  extend Prawn::Measurements
4
4
 
5
- def self.to_pixels(value, axis_length)
5
+ def self.to_pixels(value, axis_length = nil)
6
6
  if value.is_a?(String)
7
7
  if match = value.match(/\d(cm|dm|ft|in|m|mm|yd)$/)
8
8
  send("#{match[1]}2pt", value.to_f)
@@ -18,4 +18,28 @@ module Prawn::SVG::Calculators
18
18
  end
19
19
  end
20
20
  end
21
+
22
+ protected
23
+
24
+ def x(value)
25
+ x_pixels(value)
26
+ end
27
+
28
+ def y(value)
29
+ # This uses document.sizing, not state.viewport_sizing, because we always
30
+ # want to subtract from the total height of the document.
31
+ document.sizing.output_height - y_pixels(value)
32
+ end
33
+
34
+ def pixels(value)
35
+ value && Measurement.to_pixels(value, state.viewport_sizing.viewport_diagonal)
36
+ end
37
+
38
+ def x_pixels(value)
39
+ value && Measurement.to_pixels(value, state.viewport_sizing.viewport_width)
40
+ end
41
+
42
+ def y_pixels(value)
43
+ value && Measurement.to_pixels(value, state.viewport_sizing.viewport_height)
44
+ end
21
45
  end
@@ -43,26 +43,8 @@ class Prawn::SVG::Document
43
43
  sizing.requested_height = options[:height]
44
44
  sizing.calculate
45
45
 
46
- @axis_to_size = {:x => sizing.viewport_width, :y => sizing.viewport_height}
47
-
48
46
  @css_parser = CssParser::Parser.new
49
47
 
50
48
  yield self if block_given?
51
49
  end
52
-
53
- def x(value)
54
- points(value, :x)
55
- end
56
-
57
- def y(value)
58
- sizing.output_height - points(value, :y)
59
- end
60
-
61
- def distance(value, axis = nil)
62
- value && points(value, axis)
63
- end
64
-
65
- def points(value, axis = nil)
66
- Prawn::SVG::Calculators::Pixels.to_pixels(value, @axis_to_size.fetch(axis, sizing.viewport_diagonal))
67
- end
68
50
  end
@@ -2,18 +2,18 @@ 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 marker ignored).each do |filename|
5
+ %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|
6
6
  require "prawn/svg/elements/#{filename}"
7
7
  end
8
8
 
9
9
  module Prawn::SVG::Elements
10
10
  TAG_CLASS_MAPPING = {
11
- svg: Prawn::SVG::Elements::Container,
12
11
  g: Prawn::SVG::Elements::Container,
13
12
  symbol: Prawn::SVG::Elements::Container,
14
13
  defs: Prawn::SVG::Elements::Container,
15
14
  clipPath: Prawn::SVG::Elements::Container,
16
15
  switch: Prawn::SVG::Elements::Container,
16
+ svg: Prawn::SVG::Elements::Viewport,
17
17
  style: Prawn::SVG::Elements::Style,
18
18
  text: Prawn::SVG::Elements::Text,
19
19
  line: Prawn::SVG::Elements::Line,
@@ -1,10 +1,13 @@
1
1
  class Prawn::SVG::Elements::Base
2
2
  extend Forwardable
3
3
 
4
+ include Prawn::SVG::Calculators::Pixels
5
+
4
6
  include Prawn::SVG::Attributes::Transform
5
7
  include Prawn::SVG::Attributes::Opacity
6
8
  include Prawn::SVG::Attributes::ClipPath
7
9
  include Prawn::SVG::Attributes::Stroke
10
+ include Prawn::SVG::Attributes::Space
8
11
 
9
12
  PAINT_TYPES = %w(fill stroke)
10
13
  COMMA_WSP_REGEXP = Prawn::SVG::Elements::COMMA_WSP_REGEXP
@@ -16,7 +19,7 @@ class Prawn::SVG::Elements::Base
16
19
  attr_reader :document, :source, :parent_calls, :base_calls, :state, :attributes, :properties
17
20
  attr_accessor :calls
18
21
 
19
- def_delegators :@document, :x, :y, :distance, :points, :warnings
22
+ def_delegators :@document, :warnings
20
23
  def_delegator :@state, :computed_properties
21
24
 
22
25
  def initialize(document, source, parent_calls, state)
@@ -39,6 +42,7 @@ class Prawn::SVG::Elements::Base
39
42
  end
40
43
 
41
44
  def parse_and_apply
45
+ parse_standard_attributes
42
46
  parse
43
47
 
44
48
  apply_calls_from_standard_attributes
@@ -71,6 +75,14 @@ class Prawn::SVG::Elements::Base
71
75
  false
72
76
  end
73
77
 
78
+ def drawable?
79
+ !container?
80
+ end
81
+
82
+ def parse_standard_attributes
83
+ parse_xml_space_attribute
84
+ end
85
+
74
86
  def add_call(name, *arguments)
75
87
  @calls << [name.to_s, arguments, []]
76
88
  end
@@ -131,7 +143,7 @@ class Prawn::SVG::Elements::Base
131
143
  end
132
144
 
133
145
  def apply_drawing_call
134
- if !state.disable_drawing && !container?
146
+ if !state.disable_drawing && drawable?
135
147
  draw_types = PAINT_TYPES.select { |property| computed_properties.send(property) != 'none' }
136
148
 
137
149
  if draw_types.empty?
@@ -6,7 +6,7 @@ class Prawn::SVG::Elements::Circle < Prawn::SVG::Elements::Base
6
6
 
7
7
  @x = x(attributes['cx'] || "0")
8
8
  @y = y(attributes['cy'] || "0")
9
- @r = distance(attributes['r'])
9
+ @r = pixels(attributes['r'])
10
10
 
11
11
  require_positive_value @r
12
12
  end
@@ -0,0 +1,52 @@
1
+ class Prawn::SVG::Elements::DepthFirstBase < Prawn::SVG::Elements::Base
2
+ def initialize(document, source, parent_calls, state)
3
+ super
4
+ @base_calls = @calls = @parent_calls
5
+ end
6
+
7
+ def process
8
+ parse_step
9
+ apply_step(calls)
10
+ rescue SkipElementQuietly
11
+ rescue SkipElementError => e
12
+ @document.warnings << e.message
13
+ end
14
+
15
+ def parse_and_apply
16
+ raise "unsupported"
17
+ end
18
+
19
+ protected
20
+
21
+ def parse_step
22
+ extract_attributes_and_properties
23
+ parse_standard_attributes
24
+ parse
25
+ parse_child_elements if container?
26
+ end
27
+
28
+ def apply_step(calls)
29
+ @base_calls = @calls = calls
30
+ apply_calls_from_standard_attributes
31
+ apply
32
+ apply_child_elements if container?
33
+ end
34
+
35
+ def parse_child_elements
36
+ return unless source
37
+
38
+ source.elements.each do |elem|
39
+ if element_class = Prawn::SVG::Elements::TAG_CLASS_MAPPING[elem.name.to_sym]
40
+ child = element_class.new(@document, elem, @calls, state.dup)
41
+ child.parse_step
42
+ @children << child
43
+ end
44
+ end
45
+ end
46
+
47
+ def apply_child_elements
48
+ @children.each do |child|
49
+ child.apply_step(calls)
50
+ end
51
+ end
52
+ end
@@ -6,8 +6,8 @@ class Prawn::SVG::Elements::Ellipse < Prawn::SVG::Elements::Base
6
6
 
7
7
  @x = x(attributes['cx'] || "0")
8
8
  @y = y(attributes['cy'] || "0")
9
- @rx = distance(attributes['rx'], :x)
10
- @ry = distance(attributes['ry'], :y)
9
+ @rx = x_pixels(attributes['rx'])
10
+ @ry = y_pixels(attributes['ry'])
11
11
 
12
12
  require_positive_value @rx, @ry
13
13
  end
@@ -22,8 +22,8 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
22
22
 
23
23
  x = x(attributes['x'] || 0)
24
24
  y = y(attributes['y'] || 0)
25
- width = distance(attributes['width'])
26
- height = distance(attributes['height'])
25
+ width = x_pixels(attributes['width'])
26
+ height = y_pixels(attributes['height'])
27
27
 
28
28
  raise SkipElementQuietly if width.zero? || height.zero?
29
29
  require_positive_value width, height
@@ -2,10 +2,10 @@ class Prawn::SVG::Elements::Line < Prawn::SVG::Elements::Base
2
2
  include Prawn::SVG::Pathable
3
3
 
4
4
  def parse
5
- @x1 = points(attributes['x1'] || '0', :x)
6
- @y1 = points(attributes['y1'] || '0', :y)
7
- @x2 = points(attributes['x2'] || '0', :x)
8
- @y2 = points(attributes['y2'] || '0', :y)
5
+ @x1 = x_pixels(attributes['x1'] || 0)
6
+ @y1 = y_pixels(attributes['y1'] || 0)
7
+ @x2 = x_pixels(attributes['x2'] || 0)
8
+ @y2 = y_pixels(attributes['y2'] || 0)
9
9
  end
10
10
 
11
11
  def apply
@@ -41,8 +41,8 @@ class Prawn::SVG::Elements::Marker < Prawn::SVG::Elements::Base
41
41
  element.add_call 'transformation_matrix', scale, 0, 0, scale, 0, 0
42
42
  end
43
43
 
44
- ref_x = document.distance(attributes['refX']) || 0
45
- ref_y = document.distance(attributes['refY']) || 0
44
+ ref_x = x_pixels(attributes['refX']) || 0
45
+ ref_y = y_pixels(attributes['refY']) || 0
46
46
 
47
47
  element.add_call 'transformation_matrix', 1, 0, 0, 1, -ref_x * sizing.x_scale, ref_y * sizing.y_scale
48
48
 
@@ -4,12 +4,12 @@ class Prawn::SVG::Elements::Rect < Prawn::SVG::Elements::Base
4
4
 
5
5
  @x = x(attributes['x'] || '0')
6
6
  @y = y(attributes['y'] || '0')
7
- @width = distance(attributes['width'], :x)
8
- @height = distance(attributes['height'], :y)
7
+ @width = x_pixels(attributes['width'])
8
+ @height = y_pixels(attributes['height'])
9
9
 
10
10
  require_positive_value @width, @height
11
11
 
12
- @radius = distance(attributes['rx'] || attributes['ry'])
12
+ @radius = x_pixels(attributes['rx']) || y_pixels(attributes['ry'])
13
13
  if @radius
14
14
  # If you implement separate rx and ry in the future, you'll want to change this
15
15
  # so that rx is constrained to @width/2 and ry is constrained to @height/2.
@@ -3,10 +3,14 @@ class Prawn::SVG::Elements::Root < Prawn::SVG::Elements::Base
3
3
  super
4
4
  end
5
5
 
6
+ def parse
7
+ state.viewport_sizing = @document.sizing
8
+ end
9
+
6
10
  def apply
7
11
  add_call 'fill_color', '000000'
8
12
  add_call 'transformation_matrix', @document.sizing.x_scale, 0, 0, @document.sizing.y_scale, 0, 0
9
- add_call 'transformation_matrix', 1, 0, 0, 1, @document.sizing.x_offset, @document.sizing.y_offset
13
+ add_call 'transformation_matrix', 1, 0, 0, 1, -@document.sizing.x_offset, @document.sizing.y_offset
10
14
  end
11
15
 
12
16
  def container?
@@ -1,108 +1,70 @@
1
- class Prawn::SVG::Elements::Text < Prawn::SVG::Elements::Base
1
+ class Prawn::SVG::Elements::Text < Prawn::SVG::Elements::DepthFirstBase
2
2
  def parse
3
- case attributes['xml:space']
4
- when 'preserve'
5
- state.preserve_space = true
6
- when 'default'
7
- state.preserve_space = false
8
- end
9
-
10
- @relative = state.text_relative || false
3
+ state.text = Prawn::SVG::Elements::TextComponent::PositionsList.new([], [], [], [], [], nil)
11
4
 
12
- if attributes['x'] || attributes['y']
13
- @relative = false
14
- @x_positions = attributes['x'].split(COMMA_WSP_REGEXP).collect {|n| document.x(n)} if attributes['x']
15
- @y_positions = attributes['y'].split(COMMA_WSP_REGEXP).collect {|n| document.y(n)} if attributes['y']
16
- end
5
+ @text_root = Prawn::SVG::Elements::TextComponent.new(document, source, nil, state.dup)
6
+ @text_root.parse_step
17
7
 
18
- @x_positions ||= state.text_x_positions || [document.x(0)]
19
- @y_positions ||= state.text_y_positions || [document.y(0)]
8
+ reintroduce_trailing_and_leading_whitespace
20
9
  end
21
10
 
22
11
  def apply
23
- raise SkipElementQuietly if computed_properties.display == "none"
24
-
25
- font = select_font
26
- apply_font(font) if font
27
-
28
- add_call_and_enter "text_group" if name == 'text'
29
-
30
- if attributes['dx'] || attributes['dy']
31
- add_call_and_enter "translate", document.distance(attributes['dx'] || 0), -document.distance(attributes['dy'] || 0)
32
- end
33
-
34
- # text_anchor isn't a Prawn option; we have to do some math to support it
35
- # and so we handle this in Prawn::SVG::Interface#rewrite_call_arguments
36
- opts = {
37
- size: computed_properties.numerical_font_size,
38
- style: font && font.subfamily,
39
- text_anchor: computed_properties.text_anchor
40
- }
41
-
42
- spacing = computed_properties.letter_spacing
43
- spacing = spacing == 'normal' ? 0 : document.points(spacing)
44
-
45
- add_call_and_enter 'character_spacing', spacing
46
-
47
- source.children.each do |child|
48
- if child.node_type == :text
49
- text = child.value.strip.gsub(state.preserve_space ? /[\n\t]/ : /\s+/, " ")
50
-
51
- while text != ""
52
- opts[:at] = [@x_positions.first, @y_positions.first]
53
-
54
- if @x_positions.length > 1 || @y_positions.length > 1
55
- add_call 'draw_text', text[0..0], opts.dup
56
- text = text[1..-1]
12
+ add_call_and_enter "text_group"
13
+ @text_root.apply_step(calls)
14
+ end
57
15
 
58
- @x_positions.shift if @x_positions.length > 1
59
- @y_positions.shift if @y_positions.length > 1
60
- else
61
- add_call @relative ? 'relative_draw_text' : 'draw_text', text, opts.dup
62
- @relative = true
63
- break
64
- end
65
- end
16
+ private
66
17
 
67
- elsif child.name == "tspan"
68
- add_call 'save'
18
+ def drawable?
19
+ false
20
+ end
69
21
 
70
- new_state = state.dup
71
- new_state.text_x_positions = @x_positions
72
- new_state.text_y_positions = @y_positions
73
- new_state.text_relative = @relative
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
74
26
 
75
- Prawn::SVG::Elements::Text.new(document, child, calls, new_state).process
27
+ def reintroduce_trailing_and_leading_whitespace
28
+ printables = []
29
+ built_printable_queue(printables, @text_root)
76
30
 
77
- add_call 'restore'
31
+ remove_whitespace_only_printables_and_start_and_end(printables)
32
+ remove_printables_that_are_completely_empty(printables)
33
+ apportion_leading_and_trailing_spaces(printables)
34
+ end
78
35
 
36
+ def built_printable_queue(queue, component)
37
+ component.commands.each do |command|
38
+ case command
39
+ when Prawn::SVG::Elements::TextComponent::Printable
40
+ queue << command
79
41
  else
80
- warnings << "Unknown tag '#{child.name}' inside text tag; ignoring"
42
+ built_printable_queue(queue, command)
81
43
  end
82
44
  end
83
-
84
- # It's possible there was no text to render. In that case, add a 'noop' so
85
- # character_spacing doesn't blow up when it finds it doesn't have a block to execute.
86
- add_call 'noop' if calls.empty?
87
45
  end
88
46
 
89
- private
90
-
91
- def select_font
92
- font_families = [computed_properties.font_family, document.fallback_font_name]
93
- font_style = :italic if computed_properties.font_style == 'italic'
94
- font_weight = Prawn::SVG::Font.weight_for_css_font_weight(computed_properties.font_weight)
47
+ def remove_whitespace_only_printables_and_start_and_end(printables)
48
+ printables.pop while printables.last && printables.last.text.empty?
49
+ printables.shift while printables.first && printables.first.text.empty?
50
+ end
95
51
 
96
- font_families.compact.each do |name|
97
- font = document.font_registry.load(name, font_weight, font_style)
98
- return font if font
52
+ def remove_printables_that_are_completely_empty(printables)
53
+ printables.reject! do |printable|
54
+ printable.text.empty? && !printable.trailing_space? && !printable.leading_space?
99
55
  end
100
-
101
- warnings << "Font family '#{computed_properties.font_family}' style '#{computed_properties.font_style}' is not a known font, and the fallback font could not be found."
102
- nil
103
56
  end
104
57
 
105
- def apply_font(font)
106
- add_call 'font', font.name, style: font.subfamily
58
+ def apportion_leading_and_trailing_spaces(printables)
59
+ printables.each_cons(2) do |a, b|
60
+ if a.text.empty?
61
+ # Empty strings can only get a leading space from the previous non-empty text,
62
+ # and never get a trailing space
63
+ elsif a.trailing_space?
64
+ a.text += ' '
65
+ elsif b.leading_space?
66
+ b.text = " #{b.text}"
67
+ end
68
+ end
107
69
  end
108
70
  end