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.
- checksums.yaml +4 -4
- data/README.md +13 -9
- data/lib/prawn/svg/attributes.rb +1 -1
- data/lib/prawn/svg/attributes/space.rb +10 -0
- data/lib/prawn/svg/attributes/stroke.rb +2 -2
- data/lib/prawn/svg/attributes/transform.rb +2 -2
- data/lib/prawn/svg/calculators/document_sizing.rb +4 -5
- data/lib/prawn/svg/calculators/pixels.rb +27 -3
- data/lib/prawn/svg/document.rb +0 -18
- data/lib/prawn/svg/elements.rb +2 -2
- data/lib/prawn/svg/elements/base.rb +14 -2
- data/lib/prawn/svg/elements/circle.rb +1 -1
- data/lib/prawn/svg/elements/depth_first_base.rb +52 -0
- data/lib/prawn/svg/elements/ellipse.rb +2 -2
- data/lib/prawn/svg/elements/image.rb +2 -2
- data/lib/prawn/svg/elements/line.rb +4 -4
- data/lib/prawn/svg/elements/marker.rb +2 -2
- data/lib/prawn/svg/elements/rect.rb +3 -3
- data/lib/prawn/svg/elements/root.rb +5 -1
- data/lib/prawn/svg/elements/text.rb +47 -85
- data/lib/prawn/svg/elements/text_component.rb +186 -0
- data/lib/prawn/svg/elements/use.rb +1 -1
- data/lib/prawn/svg/elements/viewport.rb +28 -0
- data/lib/prawn/svg/interface.rb +20 -12
- data/lib/prawn/svg/pathable.rb +18 -2
- data/lib/prawn/svg/state.rb +3 -2
- data/lib/prawn/svg/version.rb +1 -1
- data/spec/prawn/svg/attributes/transform_spec.rb +2 -2
- data/spec/prawn/svg/calculators/pixels_spec.rb +72 -0
- data/spec/prawn/svg/document_spec.rb +0 -28
- data/spec/prawn/svg/elements/base_spec.rb +1 -1
- data/spec/prawn/svg/elements/gradient_spec.rb +1 -1
- data/spec/prawn/svg/elements/line_spec.rb +1 -1
- data/spec/prawn/svg/elements/marker_spec.rb +4 -0
- data/spec/prawn/svg/elements/text_spec.rb +104 -15
- data/spec/sample_svg/markers_degenerate_cp.svg +19 -0
- data/spec/sample_svg/offset_viewport.svg +8 -0
- data/spec/sample_svg/subviewports.svg +173 -0
- data/spec/sample_svg/subviewports2.svg +16 -0
- data/spec/sample_svg/tref01.svg +21 -0
- data/spec/sample_svg/tspan05.svg +40 -0
- data/spec/sample_svg/tspan91.svg +34 -0
- data/spec/spec_helper.rb +6 -0
- metadata +22 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bcd6cefab7e10066561afcaee2e9aeed78c9b30f
|
4
|
+
data.tar.gz: dbe371b99f2cf4953de90e892a3f95718abdd02e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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><path></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
|
-
-
|
64
|
-
|
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><svg></tt>, <tt><g></tt> and <tt><symbol></tt>
|
67
67
|
|
68
68
|
- <tt><use></tt>
|
69
69
|
|
70
|
-
- <tt><style></tt>
|
70
|
+
- <tt><style></tt> (see CSS section below)
|
71
71
|
|
72
72
|
- <tt><image></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
|
-
-
|
85
|
+
- properties: `clip-path`, `color`, `display`, `fill-opacity`, `fill`, `opacity`, `overflow`, `stroke`, `stroke-dasharray`, `stroke-linecap`, `stroke-opacity`, `stroke-width`
|
86
86
|
|
87
|
-
-
|
87
|
+
- properties on lines, polylines, polygons and paths: `marker-end`, `marker-mid`, `marker-start`
|
88
88
|
|
89
|
-
-
|
89
|
+
- attributes on all elements: `class`, `id`, `style`, `transform`, `xml:space`
|
90
90
|
|
91
|
-
-
|
91
|
+
- the <tt>viewBox</tt> attribute on <tt><svg></tt> and `<marker>` elements
|
92
|
+
|
93
|
+
- the <tt>preserveAspectRatio</tt> attribute on <tt><svg></tt>, <tt><image></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><style></tt> blocks. I
|
|
102
106
|
|
103
107
|
## Not supported
|
104
108
|
|
105
|
-
prawn-svg does not support
|
109
|
+
prawn-svg does not support radial gradients or patterns.
|
106
110
|
|
107
111
|
## Configuration
|
108
112
|
|
data/lib/prawn/svg/attributes.rb
CHANGED
@@ -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 =
|
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|
|
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,
|
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,
|
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
|
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
|
data/lib/prawn/svg/document.rb
CHANGED
@@ -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
|
data/lib/prawn/svg/elements.rb
CHANGED
@@ -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, :
|
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 &&
|
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?
|
@@ -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 =
|
10
|
-
@ry =
|
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 =
|
26
|
-
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 =
|
6
|
-
@y1 =
|
7
|
-
@x2 =
|
8
|
-
@y2 =
|
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 =
|
45
|
-
ref_y =
|
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 =
|
8
|
-
@height =
|
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 =
|
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,
|
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::
|
1
|
+
class Prawn::SVG::Elements::Text < Prawn::SVG::Elements::DepthFirstBase
|
2
2
|
def parse
|
3
|
-
|
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
|
-
|
13
|
-
|
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
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
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
|
-
|
68
|
-
|
18
|
+
def drawable?
|
19
|
+
false
|
20
|
+
end
|
69
21
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
27
|
+
def reintroduce_trailing_and_leading_whitespace
|
28
|
+
printables = []
|
29
|
+
built_printable_queue(printables, @text_root)
|
76
30
|
|
77
|
-
|
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
|
-
|
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
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
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
|
106
|
-
|
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
|