prawn-svg 0.9.1
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.
- data/LICENSE +21 -0
- data/README +17 -0
- data/lib/prawn-svg.rb +3 -0
- data/lib/prawn/svg/svg.rb +320 -0
- data/lib/prawn/svg/svg_path.rb +167 -0
- data/lib/prawn/svg_document.rb +7 -0
- metadata +67 -0
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright 2010 Roger Nesbitt
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
The very start of an SVG renderer for Prawn.
|
2
|
+
|
3
|
+
This will take an SVG file as input and render it into your PDF. Find out more about the Prawn PDF library at:
|
4
|
+
|
5
|
+
http://wiki.github.com/sandal/prawn/
|
6
|
+
|
7
|
+
Using prawn-svg:
|
8
|
+
|
9
|
+
Prawn::Document.generate("svg.pdf") do
|
10
|
+
svg svg_data, :at => [x, y], :width => w
|
11
|
+
end
|
12
|
+
|
13
|
+
:at must be specified. :width, :height, or neither may be specified; if neither is present,
|
14
|
+
the resolution specified in the SVG will be used.
|
15
|
+
|
16
|
+
Note that only a very small subset of SVG is currently supported. It's just enough so that
|
17
|
+
it renders a simple graph made by Scruffy.
|
data/lib/prawn-svg.rb
ADDED
@@ -0,0 +1,320 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'prawn'
|
3
|
+
|
4
|
+
class Prawn::Svg
|
5
|
+
include Prawn::Measurements
|
6
|
+
|
7
|
+
attr_reader :data, :prawn, :options
|
8
|
+
attr_accessor :scale
|
9
|
+
|
10
|
+
def initialize(data, prawn, options)
|
11
|
+
@data = data
|
12
|
+
@prawn = prawn
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def draw
|
17
|
+
root = parse_document
|
18
|
+
calculate_dimensions
|
19
|
+
|
20
|
+
prawn.bounding_box(@options[:at], :width => @width, :height => @height) do
|
21
|
+
prawn.save_graphics_state do
|
22
|
+
call_tree = generate_call_tree(root)
|
23
|
+
proc_creator(prawn, call_tree).call
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def generate_call_tree(element)
|
29
|
+
[].tap {|calls| parse_element(element, calls, {})}
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
protected
|
34
|
+
def proc_creator(prawn, calls)
|
35
|
+
Proc.new {issue_prawn_command(prawn, calls)}
|
36
|
+
end
|
37
|
+
|
38
|
+
def issue_prawn_command(prawn, calls)
|
39
|
+
calls.each do |call, arguments, children|
|
40
|
+
if children.empty?
|
41
|
+
rewrite_call_arguments(prawn, call, arguments)
|
42
|
+
prawn.send(call, *arguments)
|
43
|
+
else
|
44
|
+
prawn.send(call, *arguments, &proc_creator(prawn, children))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def rewrite_call_arguments(prawn, call, arguments)
|
50
|
+
if call == 'text_box'
|
51
|
+
if (anchor = arguments.last.delete(:text_anchor)) && %w(middle end).include?(anchor)
|
52
|
+
width = prawn.width_of(*arguments)
|
53
|
+
width /= 2 if anchor == 'middle'
|
54
|
+
arguments.last[:at][0] -= width
|
55
|
+
end
|
56
|
+
|
57
|
+
arguments.last[:at][1] += prawn.height_of(*arguments) / 3 * 2
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse_document
|
62
|
+
REXML::Document.new(@data).root.tap do |root|
|
63
|
+
if vb = root.attributes['viewBox']
|
64
|
+
x1, y1, x2, y2 = vb.strip.split(/\s+/)
|
65
|
+
@x_offset, @y_offset = [x1.to_f, y1.to_f]
|
66
|
+
@actual_width, @actual_height = [x2.to_f - x1.to_f, y2.to_f - y1.to_f]
|
67
|
+
else
|
68
|
+
@x_offset, @y_offset = [0, 0]
|
69
|
+
@actual_width = root.attributes['width'].to_f
|
70
|
+
@actual_height = root.attributes['height'].to_f
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def parse_element(element, calls, state)
|
76
|
+
attrs = element.attributes
|
77
|
+
|
78
|
+
if transform = attrs['transform']
|
79
|
+
parse_css_method_calls(transform).each do |name, arguments|
|
80
|
+
case name
|
81
|
+
when 'translate'
|
82
|
+
x, y = arguments
|
83
|
+
x, y = x.split(/\s+/) if y.nil?
|
84
|
+
calls << [name, [distance(x), -distance(y)], []]
|
85
|
+
calls = calls.last.last
|
86
|
+
when 'rotate'
|
87
|
+
calls << [name, [-arguments.first.to_f, {:origin => [0, y('0')]}], []]
|
88
|
+
calls = calls.last.last
|
89
|
+
when 'scale'
|
90
|
+
calls << [name, [arguments.first.to_f], []]
|
91
|
+
calls = calls.last.last
|
92
|
+
else
|
93
|
+
#raise "unknown transformation '#{name}'"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
calls, style_attrs, draw_type = apply_styles(attrs, calls, state)
|
99
|
+
|
100
|
+
state[:draw_type] = draw_type if draw_type != ""
|
101
|
+
if state[:draw_type] && !%w(g svg).include?(element.name)
|
102
|
+
calls << [state[:draw_type], [], []]
|
103
|
+
calls = calls.last.last
|
104
|
+
end
|
105
|
+
|
106
|
+
case element.name
|
107
|
+
when 'defs', 'desc'
|
108
|
+
# ignore these tags
|
109
|
+
|
110
|
+
when 'g', 'svg'
|
111
|
+
element.elements.each do |child|
|
112
|
+
parse_element(child, calls, state.dup)
|
113
|
+
end
|
114
|
+
|
115
|
+
when 'text'
|
116
|
+
# very primitive support for fonts
|
117
|
+
if (font = style_attrs['font-family']) && !font.match(/[\/\\]/)
|
118
|
+
font = font.strip
|
119
|
+
if font != ""
|
120
|
+
calls << ['font', [font], []]
|
121
|
+
calls = calls.last.last
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
opts = {:at => [x(attrs['x']), y(attrs['y'])]}
|
126
|
+
if size = style_attrs['font-size']
|
127
|
+
opts[:size] = size.to_f * @scale
|
128
|
+
end
|
129
|
+
|
130
|
+
# This is not a prawn option but we can't work out how to render it here - it's handled by #rewrite_call
|
131
|
+
if anchor = style_attrs['text-anchor']
|
132
|
+
opts[:text_anchor] = anchor
|
133
|
+
end
|
134
|
+
|
135
|
+
calls << ['text_box', [element.text, opts], []]
|
136
|
+
|
137
|
+
when 'line'
|
138
|
+
calls << ['line', [x(attrs['x1']), y(attrs['y1']), x(attrs['x2']), y(attrs['y2'])], []]
|
139
|
+
|
140
|
+
when 'polyline'
|
141
|
+
points = attrs['points'].split(/\s+/)
|
142
|
+
x, y = points.shift.split(",")
|
143
|
+
calls << ['move_to', [x(x), y(y)], []]
|
144
|
+
calls << ['stroke', [], []]
|
145
|
+
calls = calls.last.last
|
146
|
+
points.each do |point|
|
147
|
+
x, y = point.split(",")
|
148
|
+
calls << ["line_to", [x(x), y(y)], []]
|
149
|
+
end
|
150
|
+
|
151
|
+
when 'polygon'
|
152
|
+
points = attrs['points'].split(/\s+/).collect do |point|
|
153
|
+
x, y = point.split(",")
|
154
|
+
[x(x), y(y)]
|
155
|
+
end
|
156
|
+
calls << ["polygon", points, []]
|
157
|
+
|
158
|
+
when 'circle'
|
159
|
+
calls << ["circle_at",
|
160
|
+
[[x(attrs['cx'] || "0"), y(attrs['cy'] || "0")], {:radius => distance(attrs['r'])}],
|
161
|
+
[]]
|
162
|
+
|
163
|
+
when 'ellipse'
|
164
|
+
calls << ["ellipse_at",
|
165
|
+
[[x(attrs['cx'] || "0"), y(attrs['cy'] || "0")], distance(attrs['rx']), distance(attrs['ry'])],
|
166
|
+
[]]
|
167
|
+
|
168
|
+
when 'rect'
|
169
|
+
radius = distance(attrs['rx'] || attrs['ry'])
|
170
|
+
args = [[x(attrs['x']), y(attrs['y'])], distance(attrs['width']), distance(attrs['height'])]
|
171
|
+
if radius
|
172
|
+
# n.b. does not support both rx and ry being specified with different values
|
173
|
+
calls << ["rounded_rectangle", args + [radius], []]
|
174
|
+
else
|
175
|
+
calls << ["rectangle", args, []]
|
176
|
+
end
|
177
|
+
|
178
|
+
when 'path'
|
179
|
+
@svg_path ||= Path.new
|
180
|
+
@svg_path.parse(attrs['d']).each do |command, args|
|
181
|
+
point_to = [x(args[0]), y(args[1])]
|
182
|
+
if command == 'curve_to'
|
183
|
+
bounds = [[x(args[2]), y(args[3])], [x(args[4]), y(args[5])]]
|
184
|
+
calls << [command, [point_to, {:bounds => bounds}], []]
|
185
|
+
else
|
186
|
+
calls << [command, point_to, []]
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
else
|
191
|
+
#raise "unknown tag #{element.name}"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def parse_css_declarations(declarations)
|
196
|
+
# copied from css_parser
|
197
|
+
declarations.gsub!(/(^[\s]*)|([\s]*$)/, '')
|
198
|
+
|
199
|
+
{}.tap do |o|
|
200
|
+
declarations.split(/[\;$]+/m).each do |decs|
|
201
|
+
if matches = decs.match(/\s*(.[^:]*)\s*\:\s*(.[^;]*)\s*(;|\Z)/i)
|
202
|
+
property, value, end_of_declaration = matches.captures
|
203
|
+
o[property] = value
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def apply_styles(attrs, calls, state)
|
210
|
+
draw_types = []
|
211
|
+
|
212
|
+
decs = attrs["style"] ? parse_css_declarations(attrs["style"]) : {}
|
213
|
+
attrs.each {|n,v| decs[n] = v unless decs[n]}
|
214
|
+
|
215
|
+
# Opacity:
|
216
|
+
# We can't do nested opacities quite like the SVG requires, but this is close enough.
|
217
|
+
fill_opacity = stroke_opacity = clamp(decs['opacity'].to_f, 0, 1) if decs['opacity']
|
218
|
+
fill_opacity = clamp(decs['fill-opacity'].to_f, 0, 1) if decs['fill-opacity']
|
219
|
+
stroke_opacity = clamp(decs['stroke-opacity'].to_f, 0, 1) if decs['stroke-opacity']
|
220
|
+
|
221
|
+
if fill_opacity || stroke_opacity
|
222
|
+
state[:fill_opacity] = (state[:fill_opacity] || 1) * (fill_opacity || 1)
|
223
|
+
state[:stroke_opacity] = (state[:stroke_opacity] || 1) * (stroke_opacity || 1)
|
224
|
+
|
225
|
+
calls << ['transparent', [state[:fill_opacity], state[:stroke_opacity]], []]
|
226
|
+
calls = calls.last.last
|
227
|
+
end
|
228
|
+
|
229
|
+
if decs['fill'] && decs['fill'] != "none"
|
230
|
+
if color = color_to_hex(decs['fill'])
|
231
|
+
calls << ['fill_color', [color], []]
|
232
|
+
end
|
233
|
+
draw_types << 'fill'
|
234
|
+
end
|
235
|
+
|
236
|
+
if decs['stroke'] && decs['stroke'] != "none"
|
237
|
+
if color = color_to_hex(decs['stroke'])
|
238
|
+
calls << ['stroke_color', [color], []]
|
239
|
+
end
|
240
|
+
draw_types << 'stroke'
|
241
|
+
end
|
242
|
+
|
243
|
+
calls << ['line_width', [distance(decs['stroke-width'])], []] if decs['stroke-width']
|
244
|
+
|
245
|
+
[calls, decs, draw_types.join("_and_")]
|
246
|
+
end
|
247
|
+
|
248
|
+
def parse_css_method_calls(string)
|
249
|
+
string.scan(/\s*(\w+)\(([^)]+)\)\s*/).collect do |call|
|
250
|
+
name, argument_string = call
|
251
|
+
arguments = argument_string.split(",").collect(&:strip)
|
252
|
+
[name, arguments]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# TODO : use http://www.w3.org/TR/SVG11/types.html#ColorKeywords
|
257
|
+
HTML_COLORS = {
|
258
|
+
'black' => "000000", 'green' => "008000", 'silver' => "c0c0c0", 'lime' => "00ff00",
|
259
|
+
'gray' => "808080", 'olive' => "808000", 'white' => "ffffff", 'yellow' => "ffff00",
|
260
|
+
'maroon' => "800000", 'navy' => "000080", 'red' => "ff0000", 'blue' => "0000ff",
|
261
|
+
'purple' => "800080", 'teal' => "008080", 'fuchsia' => "ff00ff", 'aqua' => "00ffff"
|
262
|
+
}.freeze
|
263
|
+
|
264
|
+
def color_to_hex(color_string)
|
265
|
+
color_string.scan(/([^(\s]+(\([^)]*\))?)/).detect do |color, *_|
|
266
|
+
if m = color.match(/\A#([0-9a-f])([0-9a-f])([0-9a-f])\z/i)
|
267
|
+
break "#{m[1] * 2}#{m[2] * 2}#{m[3] * 2}"
|
268
|
+
elsif color.match(/\A#[0-9a-f]{6}\z/i)
|
269
|
+
break color[1..6]
|
270
|
+
elsif hex = HTML_COLORS[color.downcase]
|
271
|
+
break hex
|
272
|
+
elsif m = color.match(/\Argb\(\s*(-?[0-9.]+%?)\s*,\s*(-?[0-9.]+%?)\s*,\s*(-?[0-9.]+%?)\s*\)\z/i)
|
273
|
+
break (1..3).collect do |n|
|
274
|
+
value = m[n].to_f
|
275
|
+
value *= 2.55 if m[n][-1..-1] == '%'
|
276
|
+
"%02x" % clamp(value.round, 0, 255)
|
277
|
+
end.join
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def x(value)
|
283
|
+
(pixels(value) - @x_offset) * scale
|
284
|
+
end
|
285
|
+
|
286
|
+
def y(value)
|
287
|
+
(@actual_height - (pixels(value) - @y_offset)) * scale
|
288
|
+
end
|
289
|
+
|
290
|
+
def distance(value)
|
291
|
+
value && (pixels(value) * scale)
|
292
|
+
end
|
293
|
+
|
294
|
+
def pixels(value)
|
295
|
+
if value.is_a?(String) && match = value.match(/\d(cm|dm|ft|in|m|mm|yd)$/)
|
296
|
+
send("#{match[1]}2pt", value.to_f)
|
297
|
+
else
|
298
|
+
value.to_f
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def calculate_dimensions
|
303
|
+
if @options[:width]
|
304
|
+
@width = @options[:width]
|
305
|
+
@scale = @options[:width] / @actual_width.to_f
|
306
|
+
elsif @options[:height]
|
307
|
+
@height = @options[:height]
|
308
|
+
@scale = @options[:height] / @actual_height.to_f
|
309
|
+
else
|
310
|
+
@scale = 1
|
311
|
+
end
|
312
|
+
|
313
|
+
@width ||= @actual_width * @scale
|
314
|
+
@height ||= @actual_height * @scale
|
315
|
+
end
|
316
|
+
|
317
|
+
def clamp(value, min_value, max_value)
|
318
|
+
[[value, min_value].max, max_value].min
|
319
|
+
end
|
320
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
class Prawn::Svg::Path
|
2
|
+
def parse(data)
|
3
|
+
cmd = values = value = nil
|
4
|
+
@subpath_initial_point = @last_point = nil
|
5
|
+
@previous_control_point = @previous_quadratic_control_point = nil
|
6
|
+
@calls = []
|
7
|
+
|
8
|
+
data.each_char do |c|
|
9
|
+
if c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z'
|
10
|
+
run_path_command(cmd, values) if cmd
|
11
|
+
cmd = c
|
12
|
+
values = []
|
13
|
+
value = ""
|
14
|
+
elsif c >= '0' && c <= '9' || c == '.' || c == "-"
|
15
|
+
value << c
|
16
|
+
elsif c == ' ' || c == "\t" || c == "\r" || c == "\n" || c == ","
|
17
|
+
if value != ""
|
18
|
+
values << value.to_f
|
19
|
+
value = ""
|
20
|
+
end
|
21
|
+
else
|
22
|
+
raise "invalid character '#{c}' in path data"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
values << value.to_f if value != ""
|
27
|
+
run_path_command(cmd, values) if cmd
|
28
|
+
|
29
|
+
@calls
|
30
|
+
end
|
31
|
+
|
32
|
+
def run_path_command(command, values)
|
33
|
+
upcase_command = command.upcase
|
34
|
+
relative = command != upcase_command
|
35
|
+
|
36
|
+
case upcase_command
|
37
|
+
when 'M' # moveto
|
38
|
+
x = values.shift
|
39
|
+
y = values.shift
|
40
|
+
|
41
|
+
if relative && @last_point
|
42
|
+
x += @last_point.first
|
43
|
+
y += @last_point.last
|
44
|
+
end
|
45
|
+
|
46
|
+
@last_point = @subpath_initial_point = [x, y]
|
47
|
+
@calls << ["move_to", @last_point]
|
48
|
+
|
49
|
+
return run_path_command('L', values) if values.any?
|
50
|
+
|
51
|
+
when 'Z' # closepath
|
52
|
+
if @subpath_initial_point
|
53
|
+
@calls << ["line_to", @subpath_initial_point]
|
54
|
+
@last_point = @subpath_initial_point
|
55
|
+
end
|
56
|
+
|
57
|
+
when 'L' # lineto
|
58
|
+
while values.any?
|
59
|
+
x = values.shift
|
60
|
+
y = values.shift
|
61
|
+
if relative && @last_point
|
62
|
+
x += @last_point.first
|
63
|
+
y += @last_point.last
|
64
|
+
end
|
65
|
+
@last_point = [x, y]
|
66
|
+
@calls << ["line_to", @last_point]
|
67
|
+
end
|
68
|
+
|
69
|
+
when 'H' # horizontal lineto
|
70
|
+
while values.any?
|
71
|
+
x = values.shift
|
72
|
+
x += @last_point.first if relative && @last_point
|
73
|
+
@last_point = [x, @last_point.last]
|
74
|
+
@calls << ["line_to", @last_point]
|
75
|
+
end
|
76
|
+
|
77
|
+
when 'V' # vertical lineto
|
78
|
+
while values.any?
|
79
|
+
y = values.shift
|
80
|
+
y += @last_point.last if relative && @last_point
|
81
|
+
@last_point = [@last_point.first, y]
|
82
|
+
@calls << ["line_to", @last_point]
|
83
|
+
end
|
84
|
+
|
85
|
+
when 'C' # curveto
|
86
|
+
while values.any?
|
87
|
+
x1, y1, x2, y2, x, y = (1..6).collect {values.shift}
|
88
|
+
if relative && @last_point
|
89
|
+
x += @last_point.first
|
90
|
+
x1 += @last_point.first
|
91
|
+
x2 += @last_point.first
|
92
|
+
y += @last_point.last
|
93
|
+
y1 += @last_point.last
|
94
|
+
y2 += @last_point.last
|
95
|
+
end
|
96
|
+
|
97
|
+
@last_point = [x, y]
|
98
|
+
@previous_control_point = [x2, y2]
|
99
|
+
@calls << ["curve_to", [x, y, x1, y1, x2, y2]]
|
100
|
+
end
|
101
|
+
|
102
|
+
when 'S' # shorthand/smooth curveto
|
103
|
+
while values.any?
|
104
|
+
x2, y2, x, y = (1..4).collect {values.shift}
|
105
|
+
if relative && @last_point
|
106
|
+
x += @last_point.first
|
107
|
+
x2 += @last_point.first
|
108
|
+
y += @last_point.last
|
109
|
+
y2 += @last_point.last
|
110
|
+
end
|
111
|
+
|
112
|
+
if @previous_control_point
|
113
|
+
x1 = 2 * @last_point.first - @previous_control_point.first
|
114
|
+
y1 = 2 * @last_point.last - @previous_control_point.last
|
115
|
+
else
|
116
|
+
x1, y1 = @last_point
|
117
|
+
end
|
118
|
+
|
119
|
+
@last_point = [x, y]
|
120
|
+
@previous_control_point = [x2, y2]
|
121
|
+
@calls << ["curve_to", [x, y, x1, y1, x2, y2]]
|
122
|
+
end
|
123
|
+
|
124
|
+
when 'Q', 'T' # quadratic curveto
|
125
|
+
while values.any?
|
126
|
+
if shorthand = upcase_command == 'T'
|
127
|
+
x, y = (1..2).collect {values.shift}
|
128
|
+
else
|
129
|
+
x1, y1, x, y = (1..4).collect {values.shift}
|
130
|
+
end
|
131
|
+
|
132
|
+
if relative && @last_point
|
133
|
+
x += @last_point.first
|
134
|
+
x1 += @last_point.first if x1
|
135
|
+
y += @last_point.last
|
136
|
+
y1 += @last_point.last if y1
|
137
|
+
end
|
138
|
+
|
139
|
+
if shorthand
|
140
|
+
if @previous_quadratic_control_point
|
141
|
+
x1 = 2 * @last_point.first - @previous_quadratic_control_point.first
|
142
|
+
y1 = 2 * @last_point.last - @previous_quadratic_control_point.last
|
143
|
+
else
|
144
|
+
x1, y1 = @last_point
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# convert from quadratic to cubic
|
149
|
+
cx1 = @last_point.first + (x1 - @last_point.first) * 2 / 3.0
|
150
|
+
cy1 = @last_point.last + (y1 - @last_point.last) * 2 / 3.0
|
151
|
+
cx2 = cx1 + (x - @last_point.first) / 3.0
|
152
|
+
cy2 = cy1 + (y - @last_point.last) / 3.0
|
153
|
+
|
154
|
+
@last_point = [x, y]
|
155
|
+
@previous_quadratic_control_point = [x1, y1]
|
156
|
+
|
157
|
+
@calls << ["curve_to", [x, y, cx1, cy1, cx2, cy2]]
|
158
|
+
end
|
159
|
+
|
160
|
+
when 'A'
|
161
|
+
# unsupported
|
162
|
+
end
|
163
|
+
|
164
|
+
@previous_control_point = nil unless %w(C S).include?(upcase_command)
|
165
|
+
@previous_quadratic_control_point = nil unless %w(Q T).include?(upcase_command)
|
166
|
+
end
|
167
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: prawn-svg
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 9
|
8
|
+
- 1
|
9
|
+
version: 0.9.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Roger Nesbitt
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-03-26 00:00:00 +13:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: SVG renderer for Prawn PDF library
|
22
|
+
email: roger@seriousorange.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- README
|
31
|
+
- LICENSE
|
32
|
+
- lib/prawn/svg/svg.rb
|
33
|
+
- lib/prawn/svg/svg_path.rb
|
34
|
+
- lib/prawn/svg_document.rb
|
35
|
+
- lib/prawn-svg.rb
|
36
|
+
has_rdoc: true
|
37
|
+
homepage:
|
38
|
+
licenses: []
|
39
|
+
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
segments:
|
50
|
+
- 0
|
51
|
+
version: "0"
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.3.6
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: SVG renderer for Prawn PDF library
|
66
|
+
test_files: []
|
67
|
+
|