prawn-svg 0.21.0 → 0.22.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -0
- data/README.md +11 -4
- data/lib/prawn-svg.rb +9 -6
- data/lib/prawn/svg/attributes.rb +6 -0
- data/lib/prawn/svg/attributes/clip_path.rb +17 -0
- data/lib/prawn/svg/attributes/display.rb +5 -0
- data/lib/prawn/svg/attributes/font.rb +38 -0
- data/lib/prawn/svg/attributes/opacity.rb +15 -0
- data/lib/prawn/svg/attributes/stroke.rb +35 -0
- data/lib/prawn/svg/attributes/transform.rb +50 -0
- data/lib/prawn/svg/calculators/aspect_ratio.rb +1 -1
- data/lib/prawn/svg/calculators/document_sizing.rb +2 -2
- data/lib/prawn/svg/calculators/pixels.rb +1 -1
- data/lib/prawn/svg/color.rb +44 -14
- data/lib/prawn/svg/document.rb +6 -5
- data/lib/prawn/svg/elements.rb +33 -0
- data/lib/prawn/svg/elements/base.rb +228 -0
- data/lib/prawn/svg/elements/circle.rb +25 -0
- data/lib/prawn/svg/elements/container.rb +15 -0
- data/lib/prawn/svg/elements/ellipse.rb +23 -0
- data/lib/prawn/svg/elements/gradient.rb +117 -0
- data/lib/prawn/svg/elements/ignored.rb +5 -0
- data/lib/prawn/svg/elements/image.rb +85 -0
- data/lib/prawn/svg/elements/line.rb +16 -0
- data/lib/prawn/svg/elements/path.rb +405 -0
- data/lib/prawn/svg/elements/polygon.rb +17 -0
- data/lib/prawn/svg/elements/polyline.rb +22 -0
- data/lib/prawn/svg/elements/rect.rb +33 -0
- data/lib/prawn/svg/elements/root.rb +9 -0
- data/lib/prawn/svg/elements/style.rb +10 -0
- data/lib/prawn/svg/elements/text.rb +87 -0
- data/lib/prawn/svg/elements/use.rb +29 -0
- data/lib/prawn/svg/extension.rb +2 -2
- data/lib/prawn/svg/font.rb +3 -3
- data/lib/prawn/svg/interface.rb +12 -5
- data/lib/prawn/svg/url_loader.rb +1 -1
- data/lib/prawn/svg/version.rb +2 -2
- data/prawn-svg.gemspec +3 -3
- data/spec/integration_spec.rb +59 -2
- data/spec/prawn/svg/attributes/font_spec.rb +49 -0
- data/spec/prawn/svg/attributes/transform_spec.rb +56 -0
- data/spec/prawn/svg/calculators/aspect_ratio_spec.rb +2 -2
- data/spec/prawn/svg/calculators/document_sizing_spec.rb +3 -3
- data/spec/prawn/svg/color_spec.rb +36 -15
- data/spec/prawn/svg/document_spec.rb +4 -4
- data/spec/prawn/svg/elements/base_spec.rb +125 -0
- data/spec/prawn/svg/elements/gradient_spec.rb +61 -0
- data/spec/prawn/svg/elements/path_spec.rb +123 -0
- data/spec/prawn/svg/elements/style_spec.rb +23 -0
- data/spec/prawn/svg/{parser → elements}/text_spec.rb +7 -8
- data/spec/prawn/svg/font_spec.rb +12 -12
- data/spec/prawn/svg/interface_spec.rb +7 -7
- data/spec/prawn/svg/url_loader_spec.rb +2 -2
- data/spec/sample_svg/gradients.svg +40 -0
- data/spec/sample_svg/rect02.svg +8 -11
- data/spec/spec_helper.rb +1 -1
- metadata +46 -18
- data/lib/prawn/svg/element.rb +0 -304
- data/lib/prawn/svg/parser.rb +0 -268
- data/lib/prawn/svg/parser/image.rb +0 -81
- data/lib/prawn/svg/parser/path.rb +0 -392
- data/lib/prawn/svg/parser/text.rb +0 -80
- data/spec/prawn/svg/element_spec.rb +0 -127
- data/spec/prawn/svg/parser/path_spec.rb +0 -89
- data/spec/prawn/svg/parser_spec.rb +0 -55
@@ -0,0 +1,16 @@
|
|
1
|
+
class Prawn::SVG::Elements::Line < Prawn::SVG::Elements::Base
|
2
|
+
def parse
|
3
|
+
@x1 = x(attributes['x1'] || '0')
|
4
|
+
@y1 = y(attributes['y1'] || '0')
|
5
|
+
@x2 = x(attributes['x2'] || '0')
|
6
|
+
@y2 = y(attributes['y2'] || '0')
|
7
|
+
end
|
8
|
+
|
9
|
+
def apply
|
10
|
+
add_call 'line', @x1, @y1, @x2, @y2
|
11
|
+
end
|
12
|
+
|
13
|
+
def bounding_box
|
14
|
+
[@x1, @y1, @x2, @y2]
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,405 @@
|
|
1
|
+
class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
2
|
+
INSIDE_SPACE_REGEXP = /[ \t\r\n,]*/
|
3
|
+
OUTSIDE_SPACE_REGEXP = /[ \t\r\n]*/
|
4
|
+
INSIDE_REGEXP = /#{INSIDE_SPACE_REGEXP}([+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:(?<=[0-9])e[+-]?[0-9]+)?)/
|
5
|
+
VALUES_REGEXP = /^#{INSIDE_REGEXP}/
|
6
|
+
COMMAND_REGEXP = /^#{OUTSIDE_SPACE_REGEXP}([A-Za-z])((?:#{INSIDE_REGEXP})*)#{OUTSIDE_SPACE_REGEXP}/
|
7
|
+
|
8
|
+
FLOAT_ERROR_DELTA = 1e-10
|
9
|
+
|
10
|
+
attr_reader :commands
|
11
|
+
|
12
|
+
def parse
|
13
|
+
require_attributes 'd'
|
14
|
+
|
15
|
+
@commands = []
|
16
|
+
|
17
|
+
data = attributes["d"].gsub(/#{OUTSIDE_SPACE_REGEXP}$/, '')
|
18
|
+
|
19
|
+
matched_commands = match_all(data, COMMAND_REGEXP)
|
20
|
+
raise SkipElementError, "Invalid/unsupported syntax for SVG path data" if matched_commands.nil?
|
21
|
+
|
22
|
+
matched_commands.each do |matched_command|
|
23
|
+
command = matched_command[1]
|
24
|
+
matched_values = match_all(matched_command[2], VALUES_REGEXP)
|
25
|
+
raise "should be impossible to have invalid inside data, but we ended up here" if matched_values.nil?
|
26
|
+
values = matched_values.collect {|value| value[1].to_f}
|
27
|
+
run_path_command(command, values)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def apply
|
32
|
+
add_call 'join_style', :bevel
|
33
|
+
|
34
|
+
@commands.collect do |command, args|
|
35
|
+
if args && args.length > 0
|
36
|
+
point_to = [x(args[0]), y(args[1])]
|
37
|
+
if command == 'curve_to'
|
38
|
+
opts = {:bounds => [[x(args[2]), y(args[3])], [x(args[4]), y(args[5])]]}
|
39
|
+
end
|
40
|
+
add_call command, point_to, opts
|
41
|
+
else
|
42
|
+
add_call command
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def bounding_box
|
48
|
+
x1, x2 = @commands.map {|_, args| x(args[0]) if args}.compact.minmax
|
49
|
+
y2, y1 = @commands.map {|_, args| y(args[1]) if args}.compact.minmax
|
50
|
+
|
51
|
+
[x1, y1, x2, y2]
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
def run_path_command(command, values)
|
57
|
+
upcase_command = command.upcase
|
58
|
+
relative = command != upcase_command
|
59
|
+
|
60
|
+
case upcase_command
|
61
|
+
when 'M' # moveto
|
62
|
+
x = values.shift
|
63
|
+
y = values.shift
|
64
|
+
|
65
|
+
if relative && @last_point
|
66
|
+
x += @last_point.first
|
67
|
+
y += @last_point.last
|
68
|
+
end
|
69
|
+
|
70
|
+
@last_point = @subpath_initial_point = [x, y]
|
71
|
+
@commands << ["move_to", @last_point]
|
72
|
+
|
73
|
+
return run_path_command(relative ? 'l' : 'L', values) if values.any?
|
74
|
+
|
75
|
+
when 'Z' # closepath
|
76
|
+
if @subpath_initial_point
|
77
|
+
#@commands << ["line_to", @subpath_initial_point]
|
78
|
+
@commands << ["close_path"]
|
79
|
+
@last_point = @subpath_initial_point
|
80
|
+
end
|
81
|
+
|
82
|
+
when 'L' # lineto
|
83
|
+
while values.any?
|
84
|
+
x = values.shift
|
85
|
+
y = values.shift
|
86
|
+
if relative && @last_point
|
87
|
+
x += @last_point.first
|
88
|
+
y += @last_point.last
|
89
|
+
end
|
90
|
+
@last_point = [x, y]
|
91
|
+
@commands << ["line_to", @last_point]
|
92
|
+
end
|
93
|
+
|
94
|
+
when 'H' # horizontal lineto
|
95
|
+
while values.any?
|
96
|
+
x = values.shift
|
97
|
+
x += @last_point.first if relative && @last_point
|
98
|
+
@last_point = [x, @last_point.last]
|
99
|
+
@commands << ["line_to", @last_point]
|
100
|
+
end
|
101
|
+
|
102
|
+
when 'V' # vertical lineto
|
103
|
+
while values.any?
|
104
|
+
y = values.shift
|
105
|
+
y += @last_point.last if relative && @last_point
|
106
|
+
@last_point = [@last_point.first, y]
|
107
|
+
@commands << ["line_to", @last_point]
|
108
|
+
end
|
109
|
+
|
110
|
+
when 'C' # curveto
|
111
|
+
while values.any?
|
112
|
+
x1, y1, x2, y2, x, y = (1..6).collect {values.shift}
|
113
|
+
if relative && @last_point
|
114
|
+
x += @last_point.first
|
115
|
+
x1 += @last_point.first
|
116
|
+
x2 += @last_point.first
|
117
|
+
y += @last_point.last
|
118
|
+
y1 += @last_point.last
|
119
|
+
y2 += @last_point.last
|
120
|
+
end
|
121
|
+
|
122
|
+
@last_point = [x, y]
|
123
|
+
@previous_control_point = [x2, y2]
|
124
|
+
@commands << ["curve_to", [x, y, x1, y1, x2, y2]]
|
125
|
+
end
|
126
|
+
|
127
|
+
when 'S' # shorthand/smooth curveto
|
128
|
+
while values.any?
|
129
|
+
x2, y2, x, y = (1..4).collect {values.shift}
|
130
|
+
if relative && @last_point
|
131
|
+
x += @last_point.first
|
132
|
+
x2 += @last_point.first
|
133
|
+
y += @last_point.last
|
134
|
+
y2 += @last_point.last
|
135
|
+
end
|
136
|
+
|
137
|
+
if @previous_control_point
|
138
|
+
x1 = 2 * @last_point.first - @previous_control_point.first
|
139
|
+
y1 = 2 * @last_point.last - @previous_control_point.last
|
140
|
+
else
|
141
|
+
x1, y1 = @last_point
|
142
|
+
end
|
143
|
+
|
144
|
+
@last_point = [x, y]
|
145
|
+
@previous_control_point = [x2, y2]
|
146
|
+
@commands << ["curve_to", [x, y, x1, y1, x2, y2]]
|
147
|
+
end
|
148
|
+
|
149
|
+
when 'Q', 'T' # quadratic curveto
|
150
|
+
while values.any?
|
151
|
+
if shorthand = upcase_command == 'T'
|
152
|
+
x, y = (1..2).collect {values.shift}
|
153
|
+
else
|
154
|
+
x1, y1, x, y = (1..4).collect {values.shift}
|
155
|
+
end
|
156
|
+
|
157
|
+
if relative && @last_point
|
158
|
+
x += @last_point.first
|
159
|
+
x1 += @last_point.first if x1
|
160
|
+
y += @last_point.last
|
161
|
+
y1 += @last_point.last if y1
|
162
|
+
end
|
163
|
+
|
164
|
+
if shorthand
|
165
|
+
if @previous_quadratic_control_point
|
166
|
+
x1 = 2 * @last_point.first - @previous_quadratic_control_point.first
|
167
|
+
y1 = 2 * @last_point.last - @previous_quadratic_control_point.last
|
168
|
+
else
|
169
|
+
x1, y1 = @last_point
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# convert from quadratic to cubic
|
174
|
+
cx1 = @last_point.first + (x1 - @last_point.first) * 2 / 3.0
|
175
|
+
cy1 = @last_point.last + (y1 - @last_point.last) * 2 / 3.0
|
176
|
+
cx2 = cx1 + (x - @last_point.first) / 3.0
|
177
|
+
cy2 = cy1 + (y - @last_point.last) / 3.0
|
178
|
+
|
179
|
+
@last_point = [x, y]
|
180
|
+
@previous_quadratic_control_point = [x1, y1]
|
181
|
+
|
182
|
+
@commands << ["curve_to", [x, y, cx1, cy1, cx2, cy2]]
|
183
|
+
end
|
184
|
+
|
185
|
+
when 'A'
|
186
|
+
return unless @last_point
|
187
|
+
|
188
|
+
while values.any?
|
189
|
+
rx, ry, phi, fa, fs, x2, y2 = (1..7).collect {values.shift}
|
190
|
+
x1, y1 = @last_point
|
191
|
+
|
192
|
+
return if rx.zero? && ry.zero?
|
193
|
+
|
194
|
+
if relative
|
195
|
+
x2 += x1
|
196
|
+
y2 += y1
|
197
|
+
end
|
198
|
+
|
199
|
+
# Normalise values as per F.6.2
|
200
|
+
rx = rx.abs
|
201
|
+
ry = ry.abs
|
202
|
+
phi = (phi % 360) * 2 * Math::PI / 360.0
|
203
|
+
|
204
|
+
# F.6.2: If the endpoints (x1, y1) and (x2, y2) are identical, then this is equivalent to omitting the elliptical arc segment entirely.
|
205
|
+
return if within_float_delta?(x1, x2) && within_float_delta?(y1, y2)
|
206
|
+
|
207
|
+
# F.6.2: If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto") joining the endpoints.
|
208
|
+
if within_float_delta?(rx, 0) || within_float_delta?(ry, 0)
|
209
|
+
@last_point = [x2, y2]
|
210
|
+
@commands << ["line_to", @last_point]
|
211
|
+
return
|
212
|
+
end
|
213
|
+
|
214
|
+
# We need to get the center co-ordinates, as well as the angles from the X axis to the start and end
|
215
|
+
# points. To do this, we use the algorithm documented in the SVG specification section F.6.5.
|
216
|
+
|
217
|
+
# F.6.5.1
|
218
|
+
xp1 = Math.cos(phi) * ((x1-x2)/2.0) + Math.sin(phi) * ((y1-y2)/2.0)
|
219
|
+
yp1 = -Math.sin(phi) * ((x1-x2)/2.0) + Math.cos(phi) * ((y1-y2)/2.0)
|
220
|
+
|
221
|
+
# F.6.6.2
|
222
|
+
r2x = rx * rx
|
223
|
+
r2y = ry * ry
|
224
|
+
hat = xp1 * xp1 / r2x + yp1 * yp1 / r2y
|
225
|
+
if hat > 1
|
226
|
+
rx *= Math.sqrt(hat)
|
227
|
+
ry *= Math.sqrt(hat)
|
228
|
+
end
|
229
|
+
|
230
|
+
# F.6.5.2
|
231
|
+
r2x = rx * rx
|
232
|
+
r2y = ry * ry
|
233
|
+
square = (r2x * r2y - r2x * yp1 * yp1 - r2y * xp1 * xp1) / (r2x * yp1 * yp1 + r2y * xp1 * xp1)
|
234
|
+
square = 0 if square < 0 && square > -FLOAT_ERROR_DELTA # catch rounding errors
|
235
|
+
base = Math.sqrt(square)
|
236
|
+
base *= -1 if fa == fs
|
237
|
+
cpx = base * rx * yp1 / ry
|
238
|
+
cpy = base * -ry * xp1 / rx
|
239
|
+
|
240
|
+
# F.6.5.3
|
241
|
+
cx = Math.cos(phi) * cpx + -Math.sin(phi) * cpy + (x1 + x2) / 2
|
242
|
+
cy = Math.sin(phi) * cpx + Math.cos(phi) * cpy + (y1 + y2) / 2
|
243
|
+
|
244
|
+
# F.6.5.5
|
245
|
+
vx = (xp1 - cpx) / rx
|
246
|
+
vy = (yp1 - cpy) / ry
|
247
|
+
theta_1 = Math.acos(vx / Math.sqrt(vx * vx + vy * vy))
|
248
|
+
theta_1 *= -1 if vy < 0
|
249
|
+
|
250
|
+
# F.6.5.6
|
251
|
+
ux = vx
|
252
|
+
uy = vy
|
253
|
+
vx = (-xp1 - cpx) / rx
|
254
|
+
vy = (-yp1 - cpy) / ry
|
255
|
+
|
256
|
+
numerator = ux * vx + uy * vy
|
257
|
+
denominator = Math.sqrt(ux * ux + uy * uy) * Math.sqrt(vx * vx + vy * vy)
|
258
|
+
division = numerator / denominator
|
259
|
+
division = -1 if division < -1 # for rounding errors
|
260
|
+
|
261
|
+
d_theta = Math.acos(division) % (2 * Math::PI)
|
262
|
+
d_theta *= -1 if ux * vy - uy * vx < 0
|
263
|
+
|
264
|
+
# Adjust range
|
265
|
+
if fs == 0
|
266
|
+
d_theta -= 2 * Math::PI if d_theta > 0
|
267
|
+
else
|
268
|
+
d_theta += 2 * Math::PI if d_theta < 0
|
269
|
+
end
|
270
|
+
|
271
|
+
theta_2 = theta_1 + d_theta
|
272
|
+
|
273
|
+
calculate_bezier_curve_points_for_arc(cx, cy, rx, ry, theta_1, theta_2, phi).each do |points|
|
274
|
+
@commands << ["curve_to", points[:p2] + points[:q1] + points[:q2]]
|
275
|
+
@last_point = points[:p2]
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
@previous_control_point = nil unless %w(C S).include?(upcase_command)
|
281
|
+
@previous_quadratic_control_point = nil unless %w(Q T).include?(upcase_command)
|
282
|
+
end
|
283
|
+
|
284
|
+
def within_float_delta?(a, b)
|
285
|
+
(a - b).abs < FLOAT_ERROR_DELTA
|
286
|
+
end
|
287
|
+
|
288
|
+
def match_all(string, regexp) # regexp must start with ^
|
289
|
+
result = []
|
290
|
+
while string != ""
|
291
|
+
matches = string.match(regexp)
|
292
|
+
result << matches
|
293
|
+
return if matches.nil?
|
294
|
+
string = matches.post_match
|
295
|
+
end
|
296
|
+
result
|
297
|
+
end
|
298
|
+
|
299
|
+
def calculate_eta_from_lambda(a, b, lambda_1, lambda_2)
|
300
|
+
# 2.2.1
|
301
|
+
eta1 = Math.atan2(Math.sin(lambda_1) / b, Math.cos(lambda_1) / a)
|
302
|
+
eta2 = Math.atan2(Math.sin(lambda_2) / b, Math.cos(lambda_2) / a)
|
303
|
+
|
304
|
+
# ensure eta1 <= eta2 <= eta1 + 2*PI
|
305
|
+
eta2 -= 2 * Math::PI * ((eta2 - eta1) / (2 * Math::PI)).floor
|
306
|
+
eta2 += 2 * Math::PI if lambda_2 - lambda_1 > Math::PI && eta2 - eta1 < Math::PI
|
307
|
+
|
308
|
+
[eta1, eta2]
|
309
|
+
end
|
310
|
+
|
311
|
+
# Convert the elliptical arc to a cubic bézier curve using this algorithm:
|
312
|
+
# http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf
|
313
|
+
def calculate_bezier_curve_points_for_arc(cx, cy, a, b, lambda_1, lambda_2, theta)
|
314
|
+
e = lambda do |eta|
|
315
|
+
[
|
316
|
+
cx + a * Math.cos(theta) * Math.cos(eta) - b * Math.sin(theta) * Math.sin(eta),
|
317
|
+
cy + a * Math.sin(theta) * Math.cos(eta) + b * Math.cos(theta) * Math.sin(eta)
|
318
|
+
]
|
319
|
+
end
|
320
|
+
|
321
|
+
ep = lambda do |eta|
|
322
|
+
[
|
323
|
+
-a * Math.cos(theta) * Math.sin(eta) - b * Math.sin(theta) * Math.cos(eta),
|
324
|
+
-a * Math.sin(theta) * Math.sin(eta) + b * Math.cos(theta) * Math.cos(eta)
|
325
|
+
]
|
326
|
+
end
|
327
|
+
|
328
|
+
iterations = 1
|
329
|
+
d_lambda = lambda_2 - lambda_1
|
330
|
+
|
331
|
+
while iterations < 1024
|
332
|
+
if d_lambda.abs <= Math::PI / 2.0
|
333
|
+
# TODO : run error algorithm, see whether it meets threshold or not
|
334
|
+
# puts "error = #{calculate_curve_approximation_error(a, b, eta1, eta1 + d_eta)}"
|
335
|
+
break
|
336
|
+
end
|
337
|
+
iterations *= 2
|
338
|
+
d_lambda = (lambda_2 - lambda_1) / iterations
|
339
|
+
end
|
340
|
+
|
341
|
+
(0...iterations).collect do |iteration|
|
342
|
+
eta_a, eta_b = calculate_eta_from_lambda(a, b, lambda_1+iteration*d_lambda, lambda_1+(iteration+1)*d_lambda)
|
343
|
+
d_eta = eta_b - eta_a
|
344
|
+
|
345
|
+
alpha = Math.sin(d_eta) * ((Math.sqrt(4 + 3 * Math.tan(d_eta / 2) ** 2) - 1) / 3)
|
346
|
+
|
347
|
+
x1, y1 = e[eta_a]
|
348
|
+
x2, y2 = e[eta_b]
|
349
|
+
|
350
|
+
ep_eta1_x, ep_eta1_y = ep[eta_a]
|
351
|
+
q1_x = x1 + alpha * ep_eta1_x
|
352
|
+
q1_y = y1 + alpha * ep_eta1_y
|
353
|
+
|
354
|
+
ep_eta2_x, ep_eta2_y = ep[eta_b]
|
355
|
+
q2_x = x2 - alpha * ep_eta2_x
|
356
|
+
q2_y = y2 - alpha * ep_eta2_y
|
357
|
+
|
358
|
+
{:p2 => [x2, y2], :q1 => [q1_x, q1_y], :q2 => [q2_x, q2_y]}
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
ERROR_COEFFICIENTS_A = [
|
363
|
+
[
|
364
|
+
[3.85268, -21.229, -0.330434, 0.0127842],
|
365
|
+
[-1.61486, 0.706564, 0.225945, 0.263682],
|
366
|
+
[-0.910164, 0.388383, 0.00551445, 0.00671814],
|
367
|
+
[-0.630184, 0.192402, 0.0098871, 0.0102527]
|
368
|
+
],
|
369
|
+
[
|
370
|
+
[-0.162211, 9.94329, 0.13723, 0.0124084],
|
371
|
+
[-0.253135, 0.00187735, 0.0230286, 0.01264],
|
372
|
+
[-0.0695069, -0.0437594, 0.0120636, 0.0163087],
|
373
|
+
[-0.0328856, -0.00926032, -0.00173573, 0.00527385]
|
374
|
+
]
|
375
|
+
]
|
376
|
+
|
377
|
+
ERROR_COEFFICIENTS_B = [
|
378
|
+
[
|
379
|
+
[0.0899116, -19.2349, -4.11711, 0.183362],
|
380
|
+
[0.138148, -1.45804, 1.32044, 1.38474],
|
381
|
+
[0.230903, -0.450262, 0.219963, 0.414038],
|
382
|
+
[0.0590565, -0.101062, 0.0430592, 0.0204699]
|
383
|
+
],
|
384
|
+
[
|
385
|
+
[0.0164649, 9.89394, 0.0919496, 0.00760802],
|
386
|
+
[0.0191603, -0.0322058, 0.0134667, -0.0825018],
|
387
|
+
[0.0156192, -0.017535, 0.00326508, -0.228157],
|
388
|
+
[-0.0236752, 0.0405821, -0.0173086, 0.176187]
|
389
|
+
]
|
390
|
+
]
|
391
|
+
|
392
|
+
def calculate_curve_approximation_error(a, b, eta1, eta2)
|
393
|
+
b_over_a = b / a
|
394
|
+
coefficents = b_over_a < 0.25 ? ERROR_COEFFICIENTS_A : ERROR_COEFFICIENTS_B
|
395
|
+
|
396
|
+
c = lambda do |i|
|
397
|
+
(0..3).inject(0) do |accumulator, j|
|
398
|
+
coef = coefficents[i][j]
|
399
|
+
accumulator + ((coef[0] * b_over_a**2 + coef[1] * b_over_a + coef[2]) / (b_over_a * coef[3])) * Math.cos(j * (eta1 + eta2))
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
((0.001 * b_over_a**2 + 4.98 * b_over_a + 0.207) / (b_over_a * 0.0067)) * a * Math.exp(c[0] + c[1] * (eta2 - eta1))
|
404
|
+
end
|
405
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Prawn::SVG::Elements::Polygon < Prawn::SVG::Elements::Base
|
2
|
+
def parse
|
3
|
+
require_attributes('points')
|
4
|
+
@points = parse_points(attributes['points'])
|
5
|
+
end
|
6
|
+
|
7
|
+
def apply
|
8
|
+
add_call 'polygon', *@points
|
9
|
+
end
|
10
|
+
|
11
|
+
def bounding_box
|
12
|
+
x1, x2 = @points.map(&:first).minmax
|
13
|
+
y2, y1 = @points.map(&:last).minmax
|
14
|
+
[x1, y1, x2, y2]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Prawn::SVG::Elements::Polyline < Prawn::SVG::Elements::Base
|
2
|
+
def parse
|
3
|
+
require_attributes('points')
|
4
|
+
@points = parse_points(attributes['points'])
|
5
|
+
end
|
6
|
+
|
7
|
+
def apply
|
8
|
+
raise SkipElementQuietly unless @points.length > 0
|
9
|
+
|
10
|
+
add_call 'move_to', *@points[0]
|
11
|
+
add_call_and_enter 'stroke'
|
12
|
+
@points[1..-1].each do |x, y|
|
13
|
+
add_call "line_to", x, y
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def bounding_box
|
18
|
+
x1, x2 = @points.map(&:first).minmax
|
19
|
+
y2, y1 = @points.map(&:last).minmax
|
20
|
+
[x1, y1, x2, y2]
|
21
|
+
end
|
22
|
+
end
|