prawn-svg 0.32.0 → 0.34.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/.github/workflows/test.yml +20 -3
- data/.gitignore +1 -0
- data/README.md +32 -24
- data/lib/prawn/svg/attributes/stroke.rb +5 -0
- data/lib/prawn/svg/color.rb +46 -22
- data/lib/prawn/svg/css/selector_parser.rb +11 -11
- data/lib/prawn/svg/css/stylesheets.rb +4 -4
- data/lib/prawn/svg/document.rb +20 -4
- data/lib/prawn/svg/elements/base.rb +10 -11
- data/lib/prawn/svg/elements/gradient.rb +2 -2
- data/lib/prawn/svg/elements/image.rb +54 -21
- data/lib/prawn/svg/elements/path.rb +0 -2
- data/lib/prawn/svg/elements/root.rb +1 -1
- data/lib/prawn/svg/elements/text_component.rb +12 -3
- data/lib/prawn/svg/elements/use.rb +20 -12
- data/lib/prawn/svg/elements.rb +1 -0
- data/lib/prawn/svg/gradients.rb +48 -0
- data/lib/prawn/svg/interface.rb +17 -187
- data/lib/prawn/svg/loaders/data.rb +4 -3
- data/lib/prawn/svg/properties.rb +2 -0
- data/lib/prawn/svg/renderer.rb +236 -0
- data/lib/prawn/svg/version.rb +1 -1
- data/lib/prawn-svg.rb +2 -0
- data/prawn-svg.gemspec +18 -16
- data/spec/prawn/svg/color_spec.rb +29 -16
- data/spec/prawn/svg/document_spec.rb +17 -0
- data/spec/prawn/svg/elements/marker_spec.rb +1 -1
- data/spec/prawn/svg/elements/root_spec.rb +28 -0
- data/spec/prawn/svg/elements/text_spec.rb +27 -0
- data/spec/prawn/svg/interface_spec.rb +10 -2
- data/spec/sample_images/image_svg_embed.svg +6 -0
- data/spec/sample_svg/cmyk.svg +5 -0
- data/spec/sample_svg/gradients-cmyk.svg +40 -0
- data/spec/sample_svg/image_svg.svg +9 -0
- data/spec/sample_svg/ordering.svg +12 -0
- data/spec/sample_svg/symbol.svg +12 -0
- data/spec/spec_helper.rb +29 -5
- metadata +67 -23
@@ -9,6 +9,10 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
9
9
|
raise SkipElementError, "<text> elements are not supported in clip paths"
|
10
10
|
end
|
11
11
|
|
12
|
+
if state.text.nil?
|
13
|
+
raise SkipElementError, "attempted to <use> an component inside a text element, this is not supported"
|
14
|
+
end
|
15
|
+
|
12
16
|
state.text.x = (attributes['x'] || "").split(COMMA_WSP_REGEXP).collect { |n| x(n) }
|
13
17
|
state.text.y = (attributes['y'] || "").split(COMMA_WSP_REGEXP).collect { |n| y(n) }
|
14
18
|
state.text.dx = (attributes['dx'] || "").split(COMMA_WSP_REGEXP).collect { |n| x_pixels(n) }
|
@@ -41,14 +45,15 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
41
45
|
font = select_font
|
42
46
|
apply_font(font) if font
|
43
47
|
|
44
|
-
# text_anchor
|
45
|
-
# and so we handle
|
48
|
+
# text_anchor and dominant_baseline aren't Prawn options; we have to do some math to support them
|
49
|
+
# and so we handle them in Prawn::SVG::Interface#rewrite_call_arguments
|
46
50
|
opts = {
|
47
51
|
size: computed_properties.numerical_font_size,
|
48
52
|
style: font && font.subfamily,
|
49
53
|
text_anchor: computed_properties.text_anchor,
|
50
54
|
}
|
51
55
|
|
56
|
+
opts[:dominant_baseline] = computed_properties.dominant_baseline unless computed_properties.dominant_baseline == 'auto'
|
52
57
|
opts[:decoration] = computed_properties.text_decoration unless computed_properties.text_decoration == 'none'
|
53
58
|
|
54
59
|
if state.text.parent
|
@@ -171,7 +176,11 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
171
176
|
|
172
177
|
def svg_text_children
|
173
178
|
text_children.select do |child|
|
174
|
-
child.node_type == :text ||
|
179
|
+
child.node_type == :text || (
|
180
|
+
child.node_type == :element && (
|
181
|
+
child.namespace == SVG_NAMESPACE || child.namespace == ''
|
182
|
+
)
|
183
|
+
)
|
175
184
|
end
|
176
185
|
end
|
177
186
|
|
@@ -1,15 +1,12 @@
|
|
1
1
|
class Prawn::SVG::Elements::Use < Prawn::SVG::Elements::Base
|
2
|
-
attr_reader :referenced_element_class
|
3
|
-
attr_reader :referenced_element_source
|
2
|
+
attr_reader :referenced_element_class, :referenced_element_source
|
4
3
|
|
5
4
|
def parse
|
6
5
|
href = href_attribute
|
7
|
-
if href.nil?
|
8
|
-
raise SkipElementError, "use tag must have an href or xlink:href"
|
9
|
-
end
|
6
|
+
raise SkipElementError, 'use tag must have an href or xlink:href' if href.nil?
|
10
7
|
|
11
8
|
if href[0..0] != '#'
|
12
|
-
raise SkipElementError,
|
9
|
+
raise SkipElementError, 'use tag has an href that is not a reference to an id; this is not supported'
|
13
10
|
end
|
14
11
|
|
15
12
|
id = href[1..-1]
|
@@ -29,14 +26,18 @@ class Prawn::SVG::Elements::Use < Prawn::SVG::Elements::Base
|
|
29
26
|
end
|
30
27
|
end
|
31
28
|
|
32
|
-
if referenced_element_class.nil?
|
33
|
-
|
29
|
+
raise SkipElementError, "no tag with ID '#{id}' was found, referenced by use tag" if referenced_element_class.nil?
|
30
|
+
|
31
|
+
if referenced_element_source.name == 'symbol'
|
32
|
+
@referenced_element_class = Prawn::SVG::Elements::Viewport
|
34
33
|
end
|
35
34
|
|
36
35
|
state.inside_use = true
|
37
36
|
|
38
37
|
@x = attributes['x']
|
39
38
|
@y = attributes['y']
|
39
|
+
@width = attributes['width']
|
40
|
+
@height = attributes['height']
|
40
41
|
end
|
41
42
|
|
42
43
|
def container?
|
@@ -45,16 +46,23 @@ class Prawn::SVG::Elements::Use < Prawn::SVG::Elements::Base
|
|
45
46
|
|
46
47
|
def apply
|
47
48
|
if @x || @y
|
48
|
-
add_call_and_enter
|
49
|
+
add_call_and_enter 'translate', x_pixels(@x || 0), -y_pixels(@y || 0)
|
49
50
|
end
|
50
51
|
end
|
51
52
|
|
52
53
|
def process_child_elements
|
53
|
-
add_call
|
54
|
+
add_call 'save'
|
55
|
+
|
56
|
+
source = referenced_element_source.dup
|
57
|
+
|
58
|
+
if referenced_element_class == Prawn::SVG::Elements::Viewport
|
59
|
+
source.attributes['width'] = @width || '100%'
|
60
|
+
source.attributes['height'] = @height || '100%'
|
61
|
+
end
|
54
62
|
|
55
|
-
child = referenced_element_class.new(document,
|
63
|
+
child = referenced_element_class.new(document, source, calls, state.dup)
|
56
64
|
child.process
|
57
65
|
|
58
|
-
add_call
|
66
|
+
add_call 'restore'
|
59
67
|
end
|
60
68
|
end
|
data/lib/prawn/svg/elements.rb
CHANGED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Prawn::SVG
|
2
|
+
class Gradients
|
3
|
+
def initialize(document)
|
4
|
+
@document = document
|
5
|
+
@gradients_by_id = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def [](id)
|
9
|
+
id &&= id.strip
|
10
|
+
return unless id && id != ''
|
11
|
+
|
12
|
+
if element = @gradients_by_id[id]
|
13
|
+
element
|
14
|
+
elsif raw_element = find_raw_gradient_element_by_id(id)
|
15
|
+
create_gradient_element(raw_element)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def []=(id, gradient)
|
20
|
+
@gradients_by_id[id] = gradient
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def find_raw_gradient_element_by_id(id)
|
26
|
+
raw_element = find_raw_element_by_id(id)
|
27
|
+
raw_element if gradient_element?(raw_element)
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_gradient_element(raw_element)
|
31
|
+
Elements::Gradient.new(@document, raw_element, [], new_state).tap(&:process)
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_raw_element_by_id(id)
|
35
|
+
REXML::XPath.match(@document.root, %(//*[@id="#{id.gsub('"', '\"')}"])).first
|
36
|
+
end
|
37
|
+
|
38
|
+
def gradient_element?(raw_element)
|
39
|
+
Elements::TAG_CLASS_MAPPING[raw_element.name.to_sym] == Elements::Gradient
|
40
|
+
end
|
41
|
+
|
42
|
+
def new_state
|
43
|
+
State.new.tap do |state|
|
44
|
+
state.viewport_sizing = @document.sizing
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/prawn/svg/interface.rb
CHANGED
@@ -5,7 +5,15 @@
|
|
5
5
|
module Prawn
|
6
6
|
module SVG
|
7
7
|
class Interface
|
8
|
-
VALID_OPTIONS = [
|
8
|
+
VALID_OPTIONS = %i[
|
9
|
+
at position vposition width height cache_images enable_web_requests
|
10
|
+
enable_file_requests_with_root fallback_font_name color_mode
|
11
|
+
]
|
12
|
+
|
13
|
+
INHERITABLE_OPTIONS = %i[
|
14
|
+
enable_web_requests enable_file_requests_with_root
|
15
|
+
cache_images fallback_font_name color_mode
|
16
|
+
]
|
9
17
|
|
10
18
|
attr_reader :data, :prawn, :document, :options
|
11
19
|
|
@@ -25,33 +33,19 @@ module Prawn
|
|
25
33
|
|
26
34
|
font_registry = Prawn::SVG::FontRegistry.new(prawn.font_families)
|
27
35
|
|
28
|
-
@document = Document.new(
|
36
|
+
@document = Document.new(
|
37
|
+
data, [prawn.bounds.width, prawn.bounds.height], options,
|
38
|
+
font_registry: font_registry, &block
|
39
|
+
)
|
40
|
+
|
41
|
+
@renderer = Renderer.new(prawn, document, options)
|
29
42
|
end
|
30
43
|
|
31
44
|
#
|
32
45
|
# Draws the SVG to the Prawn::Document object.
|
33
46
|
#
|
34
47
|
def draw
|
35
|
-
|
36
|
-
@document.warnings << "Zero or negative sizing data means this SVG cannot be rendered"
|
37
|
-
return
|
38
|
-
end
|
39
|
-
|
40
|
-
@document.warnings.clear
|
41
|
-
|
42
|
-
prawn.save_font do
|
43
|
-
prawn.bounding_box(position, :width => @document.sizing.output_width, :height => @document.sizing.output_height) do
|
44
|
-
prawn.save_graphics_state do
|
45
|
-
clip_rectangle 0, 0, @document.sizing.output_width, @document.sizing.output_height
|
46
|
-
|
47
|
-
calls = []
|
48
|
-
root_element = Prawn::SVG::Elements::Root.new(@document, @document.root, calls)
|
49
|
-
root_element.process
|
50
|
-
|
51
|
-
proc_creator(prawn, calls).call
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
48
|
+
@renderer.draw
|
55
49
|
end
|
56
50
|
|
57
51
|
def sizing
|
@@ -63,176 +57,12 @@ module Prawn
|
|
63
57
|
end
|
64
58
|
|
65
59
|
def position
|
66
|
-
@
|
60
|
+
@renderer.position
|
67
61
|
end
|
68
62
|
|
69
63
|
def self.font_path # backwards support for when the font_path used to be stored on this class
|
70
64
|
Prawn::SVG::FontRegistry.font_path
|
71
65
|
end
|
72
|
-
|
73
|
-
private
|
74
|
-
|
75
|
-
def x_based_on_requested_alignment
|
76
|
-
case options[:position]
|
77
|
-
when :left, nil
|
78
|
-
0
|
79
|
-
when :center, :centre
|
80
|
-
(@document.sizing.bounds[0] - @document.sizing.output_width) / 2.0
|
81
|
-
when :right
|
82
|
-
@document.sizing.bounds[0] - @document.sizing.output_width
|
83
|
-
when Numeric
|
84
|
-
options[:position]
|
85
|
-
else
|
86
|
-
raise ArgumentError, "options[:position] must be one of nil, :left, :right, :center or a number"
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def y_based_on_requested_alignment
|
91
|
-
case options[:vposition]
|
92
|
-
when nil
|
93
|
-
prawn.cursor
|
94
|
-
when :top
|
95
|
-
@document.sizing.bounds[1]
|
96
|
-
when :center, :centre
|
97
|
-
@document.sizing.bounds[1] - (@document.sizing.bounds[1] - @document.sizing.output_height) / 2.0
|
98
|
-
when :bottom
|
99
|
-
@document.sizing.output_height
|
100
|
-
when Numeric
|
101
|
-
@document.sizing.bounds[1] - options[:vposition]
|
102
|
-
else
|
103
|
-
raise ArgumentError, "options[:vposition] must be one of nil, :top, :right, :bottom or a number"
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def proc_creator(prawn, calls)
|
108
|
-
Proc.new {issue_prawn_command(prawn, calls)}
|
109
|
-
end
|
110
|
-
|
111
|
-
def issue_prawn_command(prawn, calls)
|
112
|
-
calls.each do |call, arguments, kwarguments, children|
|
113
|
-
skip = false
|
114
|
-
|
115
|
-
rewrite_call_arguments(prawn, call, arguments, kwarguments) do
|
116
|
-
issue_prawn_command(prawn, children) if children.any?
|
117
|
-
skip = true
|
118
|
-
end
|
119
|
-
|
120
|
-
if skip
|
121
|
-
# the call has been overridden
|
122
|
-
elsif children.empty? && call != 'transparent' # some prawn calls complain if they aren't supplied a block
|
123
|
-
if RUBY_VERSION >= '2.7' || !kwarguments.empty?
|
124
|
-
prawn.send(call, *arguments, **kwarguments)
|
125
|
-
else
|
126
|
-
prawn.send(call, *arguments)
|
127
|
-
end
|
128
|
-
else
|
129
|
-
if RUBY_VERSION >= '2.7' || !kwarguments.empty?
|
130
|
-
prawn.send(call, *arguments, **kwarguments, &proc_creator(prawn, children))
|
131
|
-
else
|
132
|
-
prawn.send(call, *arguments, &proc_creator(prawn, children))
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
def rewrite_call_arguments(prawn, call, arguments, kwarguments)
|
139
|
-
case call
|
140
|
-
when 'text_group'
|
141
|
-
@cursor = [0, document.sizing.output_height]
|
142
|
-
yield
|
143
|
-
|
144
|
-
when 'draw_text'
|
145
|
-
text, options = arguments.first, kwarguments
|
146
|
-
|
147
|
-
at = options.fetch(:at)
|
148
|
-
|
149
|
-
at[0] = @cursor[0] if at[0] == :relative
|
150
|
-
at[1] = @cursor[1] if at[1] == :relative
|
151
|
-
|
152
|
-
if offset = options.delete(:offset)
|
153
|
-
at[0] += offset[0]
|
154
|
-
at[1] -= offset[1]
|
155
|
-
end
|
156
|
-
|
157
|
-
width = prawn.width_of(text, options.merge(kerning: true))
|
158
|
-
|
159
|
-
if stretch_to_width = options.delete(:stretch_to_width)
|
160
|
-
factor = stretch_to_width.to_f * 100 / width.to_f
|
161
|
-
prawn.add_content "#{factor} Tz"
|
162
|
-
width = stretch_to_width.to_f
|
163
|
-
end
|
164
|
-
|
165
|
-
if pad_to_width = options.delete(:pad_to_width)
|
166
|
-
padding_required = pad_to_width.to_f - width.to_f
|
167
|
-
padding_per_character = padding_required / text.length.to_f
|
168
|
-
prawn.add_content "#{padding_per_character} Tc"
|
169
|
-
width = pad_to_width.to_f
|
170
|
-
end
|
171
|
-
|
172
|
-
case options.delete(:text_anchor)
|
173
|
-
when 'middle'
|
174
|
-
at[0] -= width / 2
|
175
|
-
@cursor = [at[0] + width / 2, at[1]]
|
176
|
-
when 'end'
|
177
|
-
at[0] -= width
|
178
|
-
@cursor = at.dup
|
179
|
-
else
|
180
|
-
@cursor = [at[0] + width, at[1]]
|
181
|
-
end
|
182
|
-
|
183
|
-
decoration = options.delete(:decoration)
|
184
|
-
if decoration == 'underline'
|
185
|
-
prawn.save_graphics_state do
|
186
|
-
prawn.line_width 1
|
187
|
-
prawn.line [at[0], at[1] - 1.25], [at[0] + width, at[1] - 1.25]
|
188
|
-
prawn.stroke
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
when 'transformation_matrix'
|
193
|
-
left = prawn.bounds.absolute_left
|
194
|
-
top = prawn.bounds.absolute_top
|
195
|
-
arguments[4] += left - (left * arguments[0] + top * arguments[2])
|
196
|
-
arguments[5] += top - (left * arguments[1] + top * arguments[3])
|
197
|
-
|
198
|
-
when 'clip'
|
199
|
-
prawn.add_content "W n" # clip to path
|
200
|
-
yield
|
201
|
-
|
202
|
-
when 'save'
|
203
|
-
prawn.save_graphics_state
|
204
|
-
yield
|
205
|
-
|
206
|
-
when 'restore'
|
207
|
-
prawn.restore_graphics_state
|
208
|
-
yield
|
209
|
-
|
210
|
-
when "end_path"
|
211
|
-
yield
|
212
|
-
prawn.add_content "n" # end path
|
213
|
-
|
214
|
-
when 'fill_and_stroke'
|
215
|
-
yield
|
216
|
-
# prawn (as at 2.0.1 anyway) uses 'b' for its fill_and_stroke. 'b' is 'h' (closepath) + 'B', and we
|
217
|
-
# never want closepath to be automatically run as it stuffs up many drawing operations, such as dashes
|
218
|
-
# and line caps, and makes paths close that we didn't ask to be closed when fill is specified.
|
219
|
-
even_odd = kwarguments[:fill_rule] == :even_odd
|
220
|
-
content = even_odd ? 'B*' : 'B'
|
221
|
-
prawn.add_content content
|
222
|
-
|
223
|
-
when 'noop'
|
224
|
-
yield
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
def clip_rectangle(x, y, width, height)
|
229
|
-
prawn.move_to x, y
|
230
|
-
prawn.line_to x + width, y
|
231
|
-
prawn.line_to x + width, y + height
|
232
|
-
prawn.line_to x, y + height
|
233
|
-
prawn.close_path
|
234
|
-
prawn.add_content "W n" # clip to path
|
235
|
-
end
|
236
66
|
end
|
237
67
|
end
|
238
68
|
end
|
@@ -2,14 +2,15 @@ require 'base64'
|
|
2
2
|
|
3
3
|
module Prawn::SVG::Loaders
|
4
4
|
class Data
|
5
|
-
REGEXP = %r
|
5
|
+
REGEXP = %r{\Adata:image/(png|jpeg|svg\+xml);base64(;[a-z0-9]+)*,}i
|
6
6
|
|
7
7
|
def from_url(url)
|
8
|
-
return if url[0..4].downcase !=
|
8
|
+
return if url[0..4].downcase != 'data:'
|
9
9
|
|
10
10
|
matches = url.match(REGEXP)
|
11
11
|
if matches.nil?
|
12
|
-
raise Prawn::SVG::UrlLoader::Error,
|
12
|
+
raise Prawn::SVG::UrlLoader::Error,
|
13
|
+
'prawn-svg only supports base64-encoded image/png, image/jpeg, and image/svg+xml data URLs'
|
13
14
|
end
|
14
15
|
|
15
16
|
Base64.decode64(matches.post_match)
|
data/lib/prawn/svg/properties.rb
CHANGED
@@ -34,10 +34,12 @@ class Prawn::SVG::Properties
|
|
34
34
|
"stroke" => Config.new("none", true, %w(inherit none currentColor)),
|
35
35
|
"stroke-dasharray" => Config.new("none", true, %w(inherit none)),
|
36
36
|
"stroke-linecap" => Config.new("butt", true, %w(inherit butt round square), true),
|
37
|
+
"stroke-linejoin" => Config.new("miter", true, %w(inherit miter round bevel), true),
|
37
38
|
"stroke-opacity" => Config.new("1", true),
|
38
39
|
"stroke-width" => Config.new("1", true),
|
39
40
|
"text-anchor" => Config.new("start", true, %w(inherit start middle end), true),
|
40
41
|
'text-decoration' => Config.new('none', true, %w(inherit none underline), true),
|
42
|
+
"dominant-baseline" => Config.new("auto", true, %w(inherit auto middle), true),
|
41
43
|
}.freeze
|
42
44
|
|
43
45
|
PROPERTIES.each do |name, value|
|
@@ -0,0 +1,236 @@
|
|
1
|
+
module Prawn
|
2
|
+
module SVG
|
3
|
+
class Renderer
|
4
|
+
attr_reader :prawn, :document, :options
|
5
|
+
|
6
|
+
#
|
7
|
+
# Creates a Prawn::SVG object.
|
8
|
+
#
|
9
|
+
# +data+ is the SVG data to convert. +prawn+ is your Prawn::Document object.
|
10
|
+
#
|
11
|
+
# See README.md for the options that can be passed to this method.
|
12
|
+
#
|
13
|
+
def initialize(prawn, document, options)
|
14
|
+
@prawn = prawn
|
15
|
+
@document = document
|
16
|
+
@options = options
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Draws the SVG to the Prawn::Document object.
|
21
|
+
#
|
22
|
+
def draw
|
23
|
+
if sizing.invalid?
|
24
|
+
document.warnings << 'Zero or negative sizing data means this SVG cannot be rendered'
|
25
|
+
return
|
26
|
+
end
|
27
|
+
|
28
|
+
document.warnings.clear
|
29
|
+
|
30
|
+
prawn.save_font do
|
31
|
+
prawn.bounding_box(position, width: sizing.output_width, height: sizing.output_height) do
|
32
|
+
prawn.save_graphics_state do
|
33
|
+
clip_rectangle 0, 0, sizing.output_width, sizing.output_height
|
34
|
+
|
35
|
+
calls = []
|
36
|
+
root_element = Prawn::SVG::Elements::Root.new(document, document.root, calls)
|
37
|
+
root_element.process
|
38
|
+
|
39
|
+
proc_creator(prawn, calls).call
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def sizing
|
46
|
+
document.sizing
|
47
|
+
end
|
48
|
+
|
49
|
+
def position
|
50
|
+
options[:at] || [x_based_on_requested_alignment, y_based_on_requested_alignment]
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def x_based_on_requested_alignment
|
56
|
+
case options[:position]
|
57
|
+
when :left, nil
|
58
|
+
0
|
59
|
+
when :center, :centre
|
60
|
+
(sizing.bounds[0] - sizing.output_width) / 2.0
|
61
|
+
when :right
|
62
|
+
sizing.bounds[0] - sizing.output_width
|
63
|
+
when Numeric
|
64
|
+
options[:position]
|
65
|
+
else
|
66
|
+
raise ArgumentError, 'options[:position] must be one of nil, :left, :right, :center or a number'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def y_based_on_requested_alignment
|
71
|
+
case options[:vposition]
|
72
|
+
when nil
|
73
|
+
prawn.cursor
|
74
|
+
when :top
|
75
|
+
sizing.bounds[1]
|
76
|
+
when :center, :centre
|
77
|
+
sizing.bounds[1] - (sizing.bounds[1] - sizing.output_height) / 2.0
|
78
|
+
when :bottom
|
79
|
+
sizing.output_height
|
80
|
+
when Numeric
|
81
|
+
sizing.bounds[1] - options[:vposition]
|
82
|
+
else
|
83
|
+
raise ArgumentError, 'options[:vposition] must be one of nil, :top, :right, :bottom or a number'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def proc_creator(prawn, calls)
|
88
|
+
proc { issue_prawn_command(prawn, calls) }
|
89
|
+
end
|
90
|
+
|
91
|
+
def issue_prawn_command(prawn, calls)
|
92
|
+
calls.each do |call, arguments, kwarguments, children|
|
93
|
+
skip = false
|
94
|
+
|
95
|
+
rewrite_call_arguments(prawn, call, arguments, kwarguments) do
|
96
|
+
issue_prawn_command(prawn, children) if children.any?
|
97
|
+
skip = true
|
98
|
+
end
|
99
|
+
|
100
|
+
if skip
|
101
|
+
# the call has been overridden
|
102
|
+
elsif children.empty? && call != 'transparent' # some prawn calls complain if they aren't supplied a block
|
103
|
+
if RUBY_VERSION >= '2.7' || !kwarguments.empty?
|
104
|
+
prawn.send(call, *arguments, **kwarguments)
|
105
|
+
else
|
106
|
+
prawn.send(call, *arguments)
|
107
|
+
end
|
108
|
+
elsif RUBY_VERSION >= '2.7' || !kwarguments.empty?
|
109
|
+
prawn.send(call, *arguments, **kwarguments, &proc_creator(prawn, children))
|
110
|
+
else
|
111
|
+
prawn.send(call, *arguments, &proc_creator(prawn, children))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def rewrite_call_arguments(prawn, call, arguments, kwarguments)
|
117
|
+
case call
|
118
|
+
when 'text_group'
|
119
|
+
@cursor = [0, sizing.output_height]
|
120
|
+
yield
|
121
|
+
|
122
|
+
when 'draw_text'
|
123
|
+
text = arguments.first
|
124
|
+
options = kwarguments
|
125
|
+
|
126
|
+
at = options.fetch(:at)
|
127
|
+
|
128
|
+
at[0] = @cursor[0] if at[0] == :relative
|
129
|
+
at[1] = @cursor[1] if at[1] == :relative
|
130
|
+
|
131
|
+
case options.delete(:dominant_baseline)
|
132
|
+
when 'middle'
|
133
|
+
height = prawn.font.height
|
134
|
+
at[1] -= height / 2.0
|
135
|
+
@cursor = [at[0], at[1]]
|
136
|
+
end
|
137
|
+
|
138
|
+
if offset = options.delete(:offset)
|
139
|
+
at[0] += offset[0]
|
140
|
+
at[1] -= offset[1]
|
141
|
+
end
|
142
|
+
|
143
|
+
width = prawn.width_of(text, options.merge(kerning: true))
|
144
|
+
|
145
|
+
if stretch_to_width = options.delete(:stretch_to_width)
|
146
|
+
factor = stretch_to_width.to_f * 100 / width.to_f
|
147
|
+
prawn.add_content "#{factor} Tz"
|
148
|
+
width = stretch_to_width.to_f
|
149
|
+
end
|
150
|
+
|
151
|
+
if pad_to_width = options.delete(:pad_to_width)
|
152
|
+
padding_required = pad_to_width.to_f - width.to_f
|
153
|
+
padding_per_character = padding_required / text.length.to_f
|
154
|
+
prawn.add_content "#{padding_per_character} Tc"
|
155
|
+
width = pad_to_width.to_f
|
156
|
+
end
|
157
|
+
|
158
|
+
case options.delete(:text_anchor)
|
159
|
+
when 'middle'
|
160
|
+
at[0] -= width / 2
|
161
|
+
@cursor = [at[0] + width / 2, at[1]]
|
162
|
+
when 'end'
|
163
|
+
at[0] -= width
|
164
|
+
@cursor = at.dup
|
165
|
+
else
|
166
|
+
@cursor = [at[0] + width, at[1]]
|
167
|
+
end
|
168
|
+
|
169
|
+
decoration = options.delete(:decoration)
|
170
|
+
if decoration == 'underline'
|
171
|
+
prawn.save_graphics_state do
|
172
|
+
prawn.line_width 1
|
173
|
+
prawn.line [at[0], at[1] - 1.25], [at[0] + width, at[1] - 1.25]
|
174
|
+
prawn.stroke
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
when 'transformation_matrix'
|
179
|
+
left = prawn.bounds.absolute_left
|
180
|
+
top = prawn.bounds.absolute_top
|
181
|
+
arguments[4] += left - (left * arguments[0] + top * arguments[2])
|
182
|
+
arguments[5] += top - (left * arguments[1] + top * arguments[3])
|
183
|
+
|
184
|
+
when 'clip'
|
185
|
+
prawn.add_content 'W n' # clip to path
|
186
|
+
yield
|
187
|
+
|
188
|
+
when 'save'
|
189
|
+
prawn.save_graphics_state
|
190
|
+
yield
|
191
|
+
|
192
|
+
when 'restore'
|
193
|
+
prawn.restore_graphics_state
|
194
|
+
yield
|
195
|
+
|
196
|
+
when 'end_path'
|
197
|
+
yield
|
198
|
+
prawn.add_content 'n' # end path
|
199
|
+
|
200
|
+
when 'fill_and_stroke'
|
201
|
+
yield
|
202
|
+
# prawn (as at 2.0.1 anyway) uses 'b' for its fill_and_stroke. 'b' is 'h' (closepath) + 'B', and we
|
203
|
+
# never want closepath to be automatically run as it stuffs up many drawing operations, such as dashes
|
204
|
+
# and line caps, and makes paths close that we didn't ask to be closed when fill is specified.
|
205
|
+
even_odd = kwarguments[:fill_rule] == :even_odd
|
206
|
+
content = even_odd ? 'B*' : 'B'
|
207
|
+
prawn.add_content content
|
208
|
+
|
209
|
+
when 'noop'
|
210
|
+
yield
|
211
|
+
|
212
|
+
when 'svg:render_sub_document'
|
213
|
+
sub_document = arguments.first
|
214
|
+
sub_options = inheritable_options.merge({ at: [0, 0] })
|
215
|
+
|
216
|
+
Renderer.new(prawn, sub_document, sub_options).draw
|
217
|
+
document.warnings.concat(sub_document.warnings)
|
218
|
+
yield
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def inheritable_options
|
223
|
+
(options || {}).slice(Prawn::SVG::Interface::INHERITABLE_OPTIONS)
|
224
|
+
end
|
225
|
+
|
226
|
+
def clip_rectangle(x, y, width, height)
|
227
|
+
prawn.move_to x, y
|
228
|
+
prawn.line_to x + width, y
|
229
|
+
prawn.line_to x + width, y + height
|
230
|
+
prawn.line_to x, y + height
|
231
|
+
prawn.close_path
|
232
|
+
prawn.add_content 'W n' # clip to path
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
data/lib/prawn/svg/version.rb
CHANGED
data/lib/prawn-svg.rb
CHANGED
@@ -21,12 +21,14 @@ require 'prawn/svg/properties'
|
|
21
21
|
require 'prawn/svg/pathable'
|
22
22
|
require 'prawn/svg/elements'
|
23
23
|
require 'prawn/svg/extension'
|
24
|
+
require 'prawn/svg/renderer'
|
24
25
|
require 'prawn/svg/interface'
|
25
26
|
require 'prawn/svg/css/font_family_parser'
|
26
27
|
require 'prawn/svg/css/selector_parser'
|
27
28
|
require 'prawn/svg/css/stylesheets'
|
28
29
|
require 'prawn/svg/ttf'
|
29
30
|
require 'prawn/svg/font'
|
31
|
+
require 'prawn/svg/gradients'
|
30
32
|
require 'prawn/svg/document'
|
31
33
|
require 'prawn/svg/state'
|
32
34
|
|