prawn-svg 0.24.0 → 0.25.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.
- 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
|