prawnx 0.1.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.
- checksums.yaml +7 -0
- data/COPYING +2 -0
- data/GPLv2 +339 -0
- data/GPLv3 +674 -0
- data/LICENSE +65 -0
- data/LICENSE-prawn-svg +21 -0
- data/README.md +107 -0
- data/data/fonts/Courier-Bold.afm +342 -0
- data/data/fonts/Courier-BoldOblique.afm +342 -0
- data/data/fonts/Courier-Oblique.afm +342 -0
- data/data/fonts/Courier.afm +342 -0
- data/data/fonts/Helvetica-Bold.afm +2827 -0
- data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/data/fonts/Helvetica-Oblique.afm +3051 -0
- data/data/fonts/Helvetica.afm +3051 -0
- data/data/fonts/MustRead.html +19 -0
- data/data/fonts/Symbol.afm +213 -0
- data/data/fonts/Times-Bold.afm +2588 -0
- data/data/fonts/Times-BoldItalic.afm +2384 -0
- data/data/fonts/Times-Italic.afm +2667 -0
- data/data/fonts/Times-Roman.afm +2419 -0
- data/data/fonts/ZapfDingbats.afm +225 -0
- data/lib/pdf/core.rb +4 -0
- data/lib/prawn/measurement_extensions.rb +4 -0
- data/lib/prawn/svg/attributes/clip_path.rb +46 -0
- data/lib/prawn/svg/attributes/mask.rb +16 -0
- data/lib/prawn/svg/attributes/opacity.rb +30 -0
- data/lib/prawn/svg/attributes/space.rb +10 -0
- data/lib/prawn/svg/attributes/stroke.rb +50 -0
- data/lib/prawn/svg/attributes/transform.rb +10 -0
- data/lib/prawn/svg/attributes.rb +6 -0
- data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +57 -0
- data/lib/prawn/svg/calculators/aspect_ratio.rb +60 -0
- data/lib/prawn/svg/calculators/document_sizing.rb +103 -0
- data/lib/prawn/svg/calculators/path_length.rb +168 -0
- data/lib/prawn/svg/calculators/pixels.rb +57 -0
- data/lib/prawn/svg/color.rb +240 -0
- data/lib/prawn/svg/css/font_face_parser.rb +72 -0
- data/lib/prawn/svg/css/font_family_parser.rb +39 -0
- data/lib/prawn/svg/css/font_parser.rb +46 -0
- data/lib/prawn/svg/css/selector_parser.rb +178 -0
- data/lib/prawn/svg/css/stylesheets.rb +233 -0
- data/lib/prawn/svg/css/values_parser.rb +68 -0
- data/lib/prawn/svg/document.rb +170 -0
- data/lib/prawn/svg/elements/anchor.rb +10 -0
- data/lib/prawn/svg/elements/base.rb +312 -0
- data/lib/prawn/svg/elements/bbox_scaling.rb +78 -0
- data/lib/prawn/svg/elements/call_duplicator.rb +37 -0
- data/lib/prawn/svg/elements/circle.rb +25 -0
- data/lib/prawn/svg/elements/clip_path.rb +26 -0
- data/lib/prawn/svg/elements/container.rb +10 -0
- data/lib/prawn/svg/elements/direct_render_base.rb +27 -0
- data/lib/prawn/svg/elements/ellipse.rb +22 -0
- data/lib/prawn/svg/elements/gradient.rb +184 -0
- data/lib/prawn/svg/elements/ignored.rb +5 -0
- data/lib/prawn/svg/elements/image.rb +177 -0
- data/lib/prawn/svg/elements/line.rb +27 -0
- data/lib/prawn/svg/elements/marker.rb +72 -0
- data/lib/prawn/svg/elements/mask.rb +65 -0
- data/lib/prawn/svg/elements/path.rb +294 -0
- data/lib/prawn/svg/elements/pattern.rb +150 -0
- data/lib/prawn/svg/elements/polygon.rb +25 -0
- data/lib/prawn/svg/elements/polyline.rb +23 -0
- data/lib/prawn/svg/elements/rect.rb +64 -0
- data/lib/prawn/svg/elements/root.rb +22 -0
- data/lib/prawn/svg/elements/switch.rb +92 -0
- data/lib/prawn/svg/elements/text.rb +99 -0
- data/lib/prawn/svg/elements/text_component.rb +346 -0
- data/lib/prawn/svg/elements/text_node.rb +308 -0
- data/lib/prawn/svg/elements/text_path.rb +153 -0
- data/lib/prawn/svg/elements/use.rb +142 -0
- data/lib/prawn/svg/elements/viewport.rb +29 -0
- data/lib/prawn/svg/elements.rb +46 -0
- data/lib/prawn/svg/extension.rb +25 -0
- data/lib/prawn/svg/font.rb +20 -0
- data/lib/prawn/svg/font_metrics.rb +144 -0
- data/lib/prawn/svg/font_registry.rb +315 -0
- data/lib/prawn/svg/funciri.rb +14 -0
- data/lib/prawn/svg/gradient_renderer.rb +332 -0
- data/lib/prawn/svg/gradients.rb +53 -0
- data/lib/prawn/svg/interface.rb +72 -0
- data/lib/prawn/svg/length.rb +43 -0
- data/lib/prawn/svg/link_renderer.rb +40 -0
- data/lib/prawn/svg/loaders/data.rb +17 -0
- data/lib/prawn/svg/loaders/file.rb +111 -0
- data/lib/prawn/svg/loaders/web.rb +24 -0
- data/lib/prawn/svg/paint.rb +67 -0
- data/lib/prawn/svg/pathable.rb +153 -0
- data/lib/prawn/svg/pattern_renderer.rb +112 -0
- data/lib/prawn/svg/pdfmatrix.rb +37 -0
- data/lib/prawn/svg/percentage.rb +24 -0
- data/lib/prawn/svg/properties.rb +329 -0
- data/lib/prawn/svg/renderer.rb +249 -0
- data/lib/prawn/svg/state.rb +24 -0
- data/lib/prawn/svg/transform_parser.rb +78 -0
- data/lib/prawn/svg/ttc.rb +35 -0
- data/lib/prawn/svg/ttf.rb +95 -0
- data/lib/prawn/svg/url_loader.rb +46 -0
- data/lib/prawn/svg/version.rb +5 -0
- data/lib/prawn/table.rb +4 -0
- data/lib/prawn-svg.rb +53 -0
- data/lib/prawn.rb +10 -0
- data/lib/prawnx/annotations.rb +21 -0
- data/lib/prawnx/document/bounding_box.rb +193 -0
- data/lib/prawnx/document/column_box.rb +91 -0
- data/lib/prawnx/document/internals.rb +144 -0
- data/lib/prawnx/document/span.rb +25 -0
- data/lib/prawnx/document.rb +253 -0
- data/lib/prawnx/encoding.rb +29 -0
- data/lib/prawnx/errors.rb +37 -0
- data/lib/prawnx/font.rb +214 -0
- data/lib/prawnx/fonts/afm.rb +171 -0
- data/lib/prawnx/fonts/otf.rb +50 -0
- data/lib/prawnx/fonts/ttf.rb +410 -0
- data/lib/prawnx/graphic_state.rb +41 -0
- data/lib/prawnx/graphics/color.rb +148 -0
- data/lib/prawnx/graphics/patterns.rb +100 -0
- data/lib/prawnx/graphics/style.rb +171 -0
- data/lib/prawnx/graphics.rb +208 -0
- data/lib/prawnx/grid.rb +216 -0
- data/lib/prawnx/images/image.rb +244 -0
- data/lib/prawnx/images/png_decoder.rb +65 -0
- data/lib/prawnx/images.rb +115 -0
- data/lib/prawnx/measurement_extensions.rb +40 -0
- data/lib/prawnx/measurements.rb +62 -0
- data/lib/prawnx/outline.rb +146 -0
- data/lib/prawnx/page.rb +125 -0
- data/lib/prawnx/page_geometry.rb +27 -0
- data/lib/prawnx/pdf/outline_node.rb +53 -0
- data/lib/prawnx/pdf/reference.rb +31 -0
- data/lib/prawnx/pdf/writer.rb +105 -0
- data/lib/prawnx/pdf.rb +66 -0
- data/lib/prawnx/pdf_a/srgb_profile.rb +61 -0
- data/lib/prawnx/pdf_a.rb +130 -0
- data/lib/prawnx/pdf_core.rb +25 -0
- data/lib/prawnx/repeater.rb +79 -0
- data/lib/prawnx/security.rb +108 -0
- data/lib/prawnx/stamp.rb +74 -0
- data/lib/prawnx/table/cell/kinds.rb +246 -0
- data/lib/prawnx/table/cell.rb +331 -0
- data/lib/prawnx/table/cells.rb +208 -0
- data/lib/prawnx/table.rb +296 -0
- data/lib/prawnx/text/box.rb +16 -0
- data/lib/prawnx/text/formatted/arranger.rb +217 -0
- data/lib/prawnx/text/formatted/box.rb +312 -0
- data/lib/prawnx/text/formatted/fragment.rb +205 -0
- data/lib/prawnx/text/formatted/line_wrap.rb +204 -0
- data/lib/prawnx/text/formatted/parser.rb +149 -0
- data/lib/prawnx/text/formatted/wrap.rb +103 -0
- data/lib/prawnx/text.rb +281 -0
- data/lib/prawnx/transformation_stack.rb +43 -0
- data/lib/prawnx/true_type/cff_subset.rb +157 -0
- data/lib/prawnx/true_type/file.rb +371 -0
- data/lib/prawnx/true_type/open_type_layout.rb +189 -0
- data/lib/prawnx/true_type/simple_subsets.rb +94 -0
- data/lib/prawnx/true_type/subset.rb +187 -0
- data/lib/prawnx/version.rb +7 -0
- data/lib/prawnx/view.rb +28 -0
- data/lib/prawnx.rb +102 -0
- metadata +251 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
|
|
2
|
+
include Prawn::SVG::Calculators::ArcToBezierCurve
|
|
3
|
+
include Prawn::SVG::Pathable
|
|
4
|
+
|
|
5
|
+
INSIDE_SPACE_REGEXP = /[ \t\r\n,]*/.freeze
|
|
6
|
+
OUTSIDE_SPACE_REGEXP = /[ \t\r\n]*/.freeze
|
|
7
|
+
INSIDE_REGEXP = /#{INSIDE_SPACE_REGEXP}(?>([+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:(?<=[0-9])[eE][+-]?[0-9]+)?))/.freeze
|
|
8
|
+
FLAG_REGEXP = /#{INSIDE_SPACE_REGEXP}([01])/.freeze
|
|
9
|
+
COMMAND_REGEXP = /^#{OUTSIDE_SPACE_REGEXP}([A-Za-z])((?:#{INSIDE_REGEXP})*)#{OUTSIDE_SPACE_REGEXP}/.freeze
|
|
10
|
+
|
|
11
|
+
A_PARAMETERS_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{FLAG_REGEXP}#{FLAG_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}/.freeze
|
|
12
|
+
ONE_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}/.freeze
|
|
13
|
+
TWO_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}/.freeze
|
|
14
|
+
FOUR_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}/.freeze
|
|
15
|
+
SIX_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}/.freeze
|
|
16
|
+
|
|
17
|
+
COMMAND_MATCH_MAP = {
|
|
18
|
+
'A' => A_PARAMETERS_REGEXP,
|
|
19
|
+
'C' => SIX_PARAMETER_REGEXP,
|
|
20
|
+
'H' => ONE_PARAMETER_REGEXP,
|
|
21
|
+
'L' => TWO_PARAMETER_REGEXP,
|
|
22
|
+
'M' => TWO_PARAMETER_REGEXP,
|
|
23
|
+
'Q' => FOUR_PARAMETER_REGEXP,
|
|
24
|
+
'S' => FOUR_PARAMETER_REGEXP,
|
|
25
|
+
'T' => TWO_PARAMETER_REGEXP,
|
|
26
|
+
'V' => ONE_PARAMETER_REGEXP,
|
|
27
|
+
'Z' => //
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
PARAMETERLESS_COMMANDS = COMMAND_MATCH_MAP.select { |_, v| v == // }.map(&:first)
|
|
31
|
+
|
|
32
|
+
FLOAT_ERROR_DELTA = 1e-10
|
|
33
|
+
|
|
34
|
+
attr_reader :commands
|
|
35
|
+
|
|
36
|
+
def parse
|
|
37
|
+
require_attributes 'd'
|
|
38
|
+
|
|
39
|
+
@commands = []
|
|
40
|
+
@last_point = nil
|
|
41
|
+
|
|
42
|
+
data = attributes['d'].gsub(/#{OUTSIDE_SPACE_REGEXP}$/, '')
|
|
43
|
+
|
|
44
|
+
matched_commands = match_all(data, COMMAND_REGEXP)
|
|
45
|
+
raise SkipElementError, 'Invalid/unsupported syntax for SVG path data' if matched_commands.nil?
|
|
46
|
+
|
|
47
|
+
matched_commands.each do |(command, parameters)|
|
|
48
|
+
regexp = COMMAND_MATCH_MAP[command.upcase] or break
|
|
49
|
+
matched_values = match_all(parameters, regexp) or break
|
|
50
|
+
values = matched_values.map { |value| value.map(&:to_f) }
|
|
51
|
+
break if values.empty? && !PARAMETERLESS_COMMANDS.include?(command.upcase)
|
|
52
|
+
|
|
53
|
+
parse_path_command(command, values)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def apply
|
|
58
|
+
apply_commands
|
|
59
|
+
apply_markers
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
protected
|
|
63
|
+
|
|
64
|
+
def parse_path_command(command, values)
|
|
65
|
+
upcase_command = command.upcase
|
|
66
|
+
relative = command != upcase_command
|
|
67
|
+
|
|
68
|
+
case upcase_command
|
|
69
|
+
when 'M' # moveto
|
|
70
|
+
x, y = values.shift
|
|
71
|
+
|
|
72
|
+
if relative && @last_point
|
|
73
|
+
x += @last_point.first
|
|
74
|
+
y += @last_point.last
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
@subpath_initial_point = [x, y]
|
|
78
|
+
push_command Prawn::SVG::Pathable::Move.new(@subpath_initial_point)
|
|
79
|
+
|
|
80
|
+
return parse_path_command(relative ? 'l' : 'L', values) if values.any?
|
|
81
|
+
|
|
82
|
+
when 'Z' # closepath
|
|
83
|
+
push_command Prawn::SVG::Pathable::Close.new(@subpath_initial_point) if @subpath_initial_point
|
|
84
|
+
|
|
85
|
+
when 'L' # lineto
|
|
86
|
+
while values.any?
|
|
87
|
+
x, y = values.shift
|
|
88
|
+
if relative && @last_point
|
|
89
|
+
x += @last_point.first
|
|
90
|
+
y += @last_point.last
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
push_command Prawn::SVG::Pathable::Line.new([x, y])
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
when 'H' # horizontal lineto
|
|
97
|
+
while values.any?
|
|
98
|
+
x = values.shift.first
|
|
99
|
+
x += @last_point.first if relative && @last_point
|
|
100
|
+
push_command Prawn::SVG::Pathable::Line.new([x, @last_point.last])
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
when 'V' # vertical lineto
|
|
104
|
+
while values.any?
|
|
105
|
+
y = values.shift.first
|
|
106
|
+
y += @last_point.last if relative && @last_point
|
|
107
|
+
push_command Prawn::SVG::Pathable::Line.new([@last_point.first, y])
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
when 'C' # curveto
|
|
111
|
+
while values.any?
|
|
112
|
+
x1, y1, x2, y2, x, y = values.shift
|
|
113
|
+
|
|
114
|
+
if relative && @last_point
|
|
115
|
+
x += @last_point.first
|
|
116
|
+
x1 += @last_point.first
|
|
117
|
+
x2 += @last_point.first
|
|
118
|
+
y += @last_point.last
|
|
119
|
+
y1 += @last_point.last
|
|
120
|
+
y2 += @last_point.last
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
@previous_control_point = [x2, y2]
|
|
124
|
+
push_command Prawn::SVG::Pathable::Curve.new([x, y], [x1, y1], [x2, y2])
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
when 'S' # shorthand/smooth curveto
|
|
128
|
+
while values.any?
|
|
129
|
+
x2, y2, x, y = values.shift
|
|
130
|
+
|
|
131
|
+
if relative && @last_point
|
|
132
|
+
x += @last_point.first
|
|
133
|
+
x2 += @last_point.first
|
|
134
|
+
y += @last_point.last
|
|
135
|
+
y2 += @last_point.last
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
if @previous_control_point
|
|
139
|
+
x1 = (2 * @last_point.first) - @previous_control_point.first
|
|
140
|
+
y1 = (2 * @last_point.last) - @previous_control_point.last
|
|
141
|
+
else
|
|
142
|
+
x1, y1 = @last_point
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
@previous_control_point = [x2, y2]
|
|
146
|
+
push_command Prawn::SVG::Pathable::Curve.new([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 = values.shift
|
|
153
|
+
else
|
|
154
|
+
x1, y1, x, y = 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
|
+
@previous_quadratic_control_point = [x1, y1]
|
|
180
|
+
|
|
181
|
+
push_command Prawn::SVG::Pathable::Curve.new([x, y], [cx1, cy1], [cx2, cy2])
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
when 'A'
|
|
185
|
+
return unless @last_point
|
|
186
|
+
|
|
187
|
+
while values.any?
|
|
188
|
+
rx, ry, phi, fa, fs, x2, y2 = values.shift
|
|
189
|
+
|
|
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
|
+
push_command Prawn::SVG::Pathable::Line.new([x2, y2])
|
|
210
|
+
return
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# We need to get the center co-ordinates, as well as the angles from the X axis to the start and end
|
|
214
|
+
# points. To do this, we use the algorithm documented in the SVG specification section F.6.5.
|
|
215
|
+
|
|
216
|
+
# F.6.5.1
|
|
217
|
+
xp1 = (Math.cos(phi) * ((x1 - x2) / 2.0)) + (Math.sin(phi) * ((y1 - y2) / 2.0))
|
|
218
|
+
yp1 = (-Math.sin(phi) * ((x1 - x2) / 2.0)) + (Math.cos(phi) * ((y1 - y2) / 2.0))
|
|
219
|
+
|
|
220
|
+
# F.6.6.2
|
|
221
|
+
r2x = rx * rx
|
|
222
|
+
r2y = ry * ry
|
|
223
|
+
hat = (xp1 * xp1 / r2x) + (yp1 * yp1 / r2y)
|
|
224
|
+
if hat > 1
|
|
225
|
+
rx *= Math.sqrt(hat)
|
|
226
|
+
ry *= Math.sqrt(hat)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# F.6.5.2
|
|
230
|
+
r2x = rx * rx
|
|
231
|
+
r2y = ry * ry
|
|
232
|
+
square = ((r2x * r2y) - (r2x * yp1 * yp1) - (r2y * xp1 * xp1)) / ((r2x * yp1 * yp1) + (r2y * xp1 * xp1))
|
|
233
|
+
square = 0 if square.negative? && square > -FLOAT_ERROR_DELTA # catch rounding errors
|
|
234
|
+
base = Math.sqrt(square)
|
|
235
|
+
base *= -1 if fa == fs
|
|
236
|
+
cpx = base * rx * yp1 / ry
|
|
237
|
+
cpy = base * -ry * xp1 / rx
|
|
238
|
+
|
|
239
|
+
# F.6.5.3
|
|
240
|
+
cx = (Math.cos(phi) * cpx) + (-Math.sin(phi) * cpy) + ((x1 + x2) / 2)
|
|
241
|
+
cy = (Math.sin(phi) * cpx) + (Math.cos(phi) * cpy) + ((y1 + y2) / 2)
|
|
242
|
+
|
|
243
|
+
# F.6.5.5 - compute the eccentric anomaly (eta) of the start point
|
|
244
|
+
vx = (xp1 - cpx) / rx
|
|
245
|
+
vy = (yp1 - cpy) / ry
|
|
246
|
+
eta_1 = Math.atan2(vy, vx)
|
|
247
|
+
|
|
248
|
+
# F.6.5.6 - compute the sweep angle between start and end points
|
|
249
|
+
ux = vx
|
|
250
|
+
uy = vy
|
|
251
|
+
vx = (-xp1 - cpx) / rx
|
|
252
|
+
vy = (-yp1 - cpy) / ry
|
|
253
|
+
|
|
254
|
+
d_eta = Math.atan2((ux * vy) - (uy * vx), (ux * vx) + (uy * vy))
|
|
255
|
+
|
|
256
|
+
# Adjust range
|
|
257
|
+
if fs.zero?
|
|
258
|
+
d_eta -= 2 * Math::PI if d_eta.positive?
|
|
259
|
+
elsif d_eta.negative?
|
|
260
|
+
d_eta += 2 * Math::PI
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
eta_2 = eta_1 + d_eta
|
|
264
|
+
|
|
265
|
+
calculate_bezier_curve_points_for_arc(cx, cy, rx, ry, eta_1, eta_2, phi).each do |points|
|
|
266
|
+
push_command Prawn::SVG::Pathable::Curve.new(points[:p2], points[:q1], points[:q2])
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
@previous_control_point = nil unless %w[C S].include?(upcase_command)
|
|
272
|
+
@previous_quadratic_control_point = nil unless %w[Q T].include?(upcase_command)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def within_float_delta?(a, b)
|
|
276
|
+
(a - b).abs < FLOAT_ERROR_DELTA
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# regexp must start with ^
|
|
280
|
+
def match_all(string, regexp)
|
|
281
|
+
result = []
|
|
282
|
+
while string != ''
|
|
283
|
+
matches = string.match(regexp) or return
|
|
284
|
+
result << matches.captures
|
|
285
|
+
string = matches.post_match
|
|
286
|
+
end
|
|
287
|
+
result
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def push_command(command)
|
|
291
|
+
@commands << command
|
|
292
|
+
@last_point = command.destination
|
|
293
|
+
end
|
|
294
|
+
end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
class Prawn::SVG::Elements::Pattern < Prawn::SVG::Elements::Base
|
|
2
|
+
attr_reader :parent_pattern
|
|
3
|
+
|
|
4
|
+
def parse
|
|
5
|
+
raise SkipElementQuietly if attributes['id'].nil?
|
|
6
|
+
|
|
7
|
+
@parent_pattern = document.gradients[href_attribute[1..]] if href_attribute && href_attribute[0] == '#'
|
|
8
|
+
|
|
9
|
+
set_display_none
|
|
10
|
+
|
|
11
|
+
document.gradients[attributes['id']] = self
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def container?
|
|
15
|
+
true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def pattern_arguments(element)
|
|
19
|
+
bbox = element.bounding_box
|
|
20
|
+
units = derive_attribute('patternUnits') || 'objectBoundingBox'
|
|
21
|
+
content_units = derive_attribute('patternContentUnits') || 'userSpaceOnUse'
|
|
22
|
+
view_box_attr = derive_attribute('viewBox')
|
|
23
|
+
par_attr = derive_attribute('preserveAspectRatio')
|
|
24
|
+
|
|
25
|
+
tile = compute_tile(units, bbox)
|
|
26
|
+
return nil unless tile
|
|
27
|
+
|
|
28
|
+
tile_x, tile_y_bottom, tile_w, tile_h = tile
|
|
29
|
+
|
|
30
|
+
transform = Matrix.identity(3)
|
|
31
|
+
if (transform_attr = derive_attribute('patternTransform'))
|
|
32
|
+
transform = parse_transform_attribute(transform_attr, space: :svg)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
content_calls = build_content_calls(content_units, view_box_attr, par_attr, tile_w, tile_h, bbox, units, tile_x, tile_y_bottom)
|
|
36
|
+
return nil if content_calls.nil?
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
tile_x: tile_x,
|
|
40
|
+
tile_y: tile_y_bottom,
|
|
41
|
+
tile_width: tile_w,
|
|
42
|
+
tile_height: tile_h,
|
|
43
|
+
transform: transform,
|
|
44
|
+
calls: content_calls
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def derive_attribute(name)
|
|
49
|
+
attributes[name] || parent_pattern&.derive_attribute(name)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def compute_tile(units, bbox)
|
|
55
|
+
if units == 'objectBoundingBox'
|
|
56
|
+
return nil unless bbox
|
|
57
|
+
|
|
58
|
+
x_frac = percentage_or_proportion(derive_attribute('x'), 0.0)
|
|
59
|
+
y_frac = percentage_or_proportion(derive_attribute('y'), 0.0)
|
|
60
|
+
w_frac = percentage_or_proportion(derive_attribute('width'), 0.0)
|
|
61
|
+
h_frac = percentage_or_proportion(derive_attribute('height'), 0.0)
|
|
62
|
+
|
|
63
|
+
return nil if w_frac <= 0 || h_frac <= 0
|
|
64
|
+
|
|
65
|
+
bbox_left, bbox_top, bbox_right, bbox_bottom = *bbox
|
|
66
|
+
bbox_w = bbox_right - bbox_left
|
|
67
|
+
bbox_h = bbox_top - bbox_bottom
|
|
68
|
+
|
|
69
|
+
tile_w = w_frac * bbox_w
|
|
70
|
+
tile_h = h_frac * bbox_h
|
|
71
|
+
tile_x = bbox_left + (x_frac * bbox_w)
|
|
72
|
+
tile_y_top = bbox_top - (y_frac * bbox_h)
|
|
73
|
+
tile_y_bottom = tile_y_top - tile_h
|
|
74
|
+
|
|
75
|
+
else
|
|
76
|
+
tile_x = x_pixels(derive_attribute('x') || '0')
|
|
77
|
+
tile_y_svg = y_pixels(derive_attribute('y') || '0')
|
|
78
|
+
tile_w = x_pixels(derive_attribute('width') || '0')
|
|
79
|
+
tile_h = y_pixels(derive_attribute('height') || '0')
|
|
80
|
+
|
|
81
|
+
return nil if tile_w <= 0 || tile_h <= 0
|
|
82
|
+
|
|
83
|
+
tile_y_bottom = document.sizing.output_height - tile_y_svg - tile_h
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
[tile_x, tile_y_bottom, tile_w, tile_h]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def build_content_calls(content_units, view_box_attr, par_attr, tile_w, tile_h, bbox, units, tile_x, tile_y_bottom)
|
|
90
|
+
if view_box_attr
|
|
91
|
+
build_viewbox_content_calls(view_box_attr, par_attr, tile_w, tile_h)
|
|
92
|
+
elsif content_units == 'objectBoundingBox'
|
|
93
|
+
return nil unless bbox
|
|
94
|
+
|
|
95
|
+
build_object_bounding_box_calls(bbox, content_source)
|
|
96
|
+
else
|
|
97
|
+
calls = content_source_calls
|
|
98
|
+
shift_content_to_tile_origin(calls, units, tile_x, tile_y_bottom, tile_h)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def build_viewbox_content_calls(view_box_attr, par_attr, tile_w, tile_h)
|
|
103
|
+
vb_values = view_box_attr.strip.split(COMMA_WSP_REGEXP).map(&:to_f)
|
|
104
|
+
return content_source_calls if vb_values.length != 4
|
|
105
|
+
|
|
106
|
+
vb_x, vb_y, vb_w, vb_h = vb_values
|
|
107
|
+
return content_source_calls if vb_w <= 0 || vb_h <= 0
|
|
108
|
+
|
|
109
|
+
aspect = Prawn::SVG::Calculators::AspectRatio.new(par_attr, [tile_w, tile_h], [vb_w, vb_h])
|
|
110
|
+
|
|
111
|
+
x_scale = aspect.width / vb_w
|
|
112
|
+
y_scale = aspect.height / vb_h
|
|
113
|
+
|
|
114
|
+
calls = []
|
|
115
|
+
calls << ['transformation_matrix', [x_scale, 0, 0, y_scale, 0, 0], {}, []]
|
|
116
|
+
calls << ['transformation_matrix', [1, 0, 0, 1, -vb_x + (aspect.x / x_scale), vb_y - (aspect.y / y_scale)], {}, []]
|
|
117
|
+
calls.concat(content_source_calls)
|
|
118
|
+
calls
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# SVG spec: pattern content coordinates have their origin at the pattern's (x,y).
|
|
122
|
+
# Since prawn-svg processes children in document coordinates, we shift the content
|
|
123
|
+
# to the tile origin so it fills the pattern BBox correctly.
|
|
124
|
+
def shift_content_to_tile_origin(calls, units, tile_x, tile_y_bottom, tile_h)
|
|
125
|
+
if units == 'userSpaceOnUse'
|
|
126
|
+
tile_y_svg = document.sizing.output_height - tile_y_bottom - tile_h
|
|
127
|
+
return calls if tile_x.zero? && tile_y_svg.zero?
|
|
128
|
+
|
|
129
|
+
[['transformation_matrix', [1, 0, 0, 1, tile_x, -tile_y_svg], {}, calls]]
|
|
130
|
+
else
|
|
131
|
+
calls
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def content_source
|
|
136
|
+
if parent_pattern && source.elements.none? { |e| ['', SVG_NAMESPACE].include?(e.namespace) }
|
|
137
|
+
parent_pattern.send(:content_source)
|
|
138
|
+
else
|
|
139
|
+
source
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def content_source_calls
|
|
144
|
+
if parent_pattern && base_calls.empty?
|
|
145
|
+
duplicate_calls(parent_pattern.base_calls)
|
|
146
|
+
else
|
|
147
|
+
duplicate_calls(base_calls)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class Prawn::SVG::Elements::Polygon < Prawn::SVG::Elements::Base
|
|
2
|
+
include Prawn::SVG::Pathable
|
|
3
|
+
|
|
4
|
+
def parse
|
|
5
|
+
require_attributes('points')
|
|
6
|
+
@points = parse_points(attributes['points'])
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def apply
|
|
10
|
+
apply_commands
|
|
11
|
+
apply_markers
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
protected
|
|
15
|
+
|
|
16
|
+
def commands
|
|
17
|
+
@commands ||= [
|
|
18
|
+
Prawn::SVG::Pathable::Move.new(@points[0])
|
|
19
|
+
] + @points[1..].map do |point|
|
|
20
|
+
Prawn::SVG::Pathable::Line.new(point)
|
|
21
|
+
end + [
|
|
22
|
+
Prawn::SVG::Pathable::Close.new(@points[0])
|
|
23
|
+
]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class Prawn::SVG::Elements::Polyline < Prawn::SVG::Elements::Base
|
|
2
|
+
include Prawn::SVG::Pathable
|
|
3
|
+
|
|
4
|
+
def parse
|
|
5
|
+
require_attributes('points')
|
|
6
|
+
@points = parse_points(attributes['points'])
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def apply
|
|
10
|
+
apply_commands
|
|
11
|
+
apply_markers
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
protected
|
|
15
|
+
|
|
16
|
+
def commands
|
|
17
|
+
@commands ||= [
|
|
18
|
+
Prawn::SVG::Pathable::Move.new(@points[0])
|
|
19
|
+
] + @points[1..].map do |point|
|
|
20
|
+
Prawn::SVG::Pathable::Line.new(point)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
class Prawn::SVG::Elements::Rect < Prawn::SVG::Elements::Base
|
|
2
|
+
include Prawn::SVG::Calculators::ArcToBezierCurve
|
|
3
|
+
|
|
4
|
+
def parse
|
|
5
|
+
require_attributes 'width', 'height'
|
|
6
|
+
|
|
7
|
+
@x = x(attributes['x'] || '0')
|
|
8
|
+
@y = y(attributes['y'] || '0')
|
|
9
|
+
@width = x_pixels(attributes['width'])
|
|
10
|
+
@height = y_pixels(attributes['height'])
|
|
11
|
+
|
|
12
|
+
require_positive_value @width, @height
|
|
13
|
+
|
|
14
|
+
raw_rx = x_pixels(attributes['rx'])
|
|
15
|
+
raw_ry = y_pixels(attributes['ry'])
|
|
16
|
+
|
|
17
|
+
if raw_rx || raw_ry
|
|
18
|
+
@rx = (raw_rx || raw_ry).clamp(0, @width / 2.0)
|
|
19
|
+
@ry = (raw_ry || raw_rx).clamp(0, @height / 2.0)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def apply
|
|
24
|
+
if @rx && @ry
|
|
25
|
+
if @rx == @ry
|
|
26
|
+
add_call 'rounded_rectangle', [@x, @y], @width, @height, @rx
|
|
27
|
+
else
|
|
28
|
+
apply_elliptical_rounded_rectangle
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
add_call 'rectangle', [@x, @y], @width, @height
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def bounding_box
|
|
36
|
+
[@x, @y, @x + @width, @y - @height]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def apply_elliptical_rounded_rectangle
|
|
42
|
+
x1 = @x
|
|
43
|
+
y1 = @y
|
|
44
|
+
x2 = @x + @width
|
|
45
|
+
y2 = @y - @height
|
|
46
|
+
|
|
47
|
+
add_call 'move_to', [x1 + @rx, y1]
|
|
48
|
+
add_call 'line_to', [x2 - @rx, y1]
|
|
49
|
+
add_arc x2 - @rx, y1 - @ry, Math::PI / 2, 0
|
|
50
|
+
add_call 'line_to', [x2, y2 + @ry]
|
|
51
|
+
add_arc x2 - @rx, y2 + @ry, 0, -Math::PI / 2
|
|
52
|
+
add_call 'line_to', [x1 + @rx, y2]
|
|
53
|
+
add_arc x1 + @rx, y2 + @ry, -Math::PI / 2, -Math::PI
|
|
54
|
+
add_call 'line_to', [x1, y1 - @ry]
|
|
55
|
+
add_arc x1 + @rx, y1 - @ry, Math::PI, Math::PI / 2
|
|
56
|
+
add_call 'close_path'
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def add_arc(cx, cy, start_angle, end_angle)
|
|
60
|
+
calculate_bezier_curve_points_for_arc(cx, cy, @rx, @ry, start_angle, end_angle, 0).each do |points|
|
|
61
|
+
add_call 'curve_to', points[:p2], bounds: [points[:q1], points[:q2]]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class Prawn::SVG::Elements::Root < Prawn::SVG::Elements::Base
|
|
2
|
+
def initialize(document, source = document.root, parent_calls = [], state = ::Prawn::SVG::State.new)
|
|
3
|
+
super
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def parse
|
|
7
|
+
state.viewport_sizing = @document.sizing
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def apply
|
|
11
|
+
if [nil, 'inherit'].include?(properties.fill) || properties.fill.none? || properties.fill.color == :currentcolor
|
|
12
|
+
add_call 'fill_color', Prawn::SVG::Color.default_color(@document.color_mode).value
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
add_call 'transformation_matrix', @document.sizing.x_scale, 0, 0, @document.sizing.y_scale, 0, 0
|
|
16
|
+
add_call 'transformation_matrix', 1, 0, 0, 1, -@document.sizing.x_offset, @document.sizing.y_offset
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def container?
|
|
20
|
+
true
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
class Prawn::SVG::Elements::Switch < Prawn::SVG::Elements::Base
|
|
2
|
+
FEATURE_PREFIX = 'http://www.w3.org/TR/SVG11/feature#'.freeze
|
|
3
|
+
|
|
4
|
+
SUPPORTED_FEATURES = Set.new(
|
|
5
|
+
%w[
|
|
6
|
+
SVG
|
|
7
|
+
SVG-static
|
|
8
|
+
CoreAttribute
|
|
9
|
+
Structure
|
|
10
|
+
BasicStructure
|
|
11
|
+
ConditionalProcessing
|
|
12
|
+
Image
|
|
13
|
+
Style
|
|
14
|
+
Shape
|
|
15
|
+
Text
|
|
16
|
+
BasicText
|
|
17
|
+
PaintAttribute
|
|
18
|
+
BasicPaintAttribute
|
|
19
|
+
OpacityAttribute
|
|
20
|
+
BasicGraphicsAttribute
|
|
21
|
+
Marker
|
|
22
|
+
Gradient
|
|
23
|
+
Pattern
|
|
24
|
+
Clip
|
|
25
|
+
BasicClip
|
|
26
|
+
Mask
|
|
27
|
+
Hyperlinking
|
|
28
|
+
XlinkAttribute
|
|
29
|
+
].map { |name| "#{FEATURE_PREFIX}#{name}" }
|
|
30
|
+
).freeze
|
|
31
|
+
|
|
32
|
+
def container?
|
|
33
|
+
true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def process_child_elements
|
|
37
|
+
return unless source
|
|
38
|
+
|
|
39
|
+
elem = svg_child_elements.find do |e|
|
|
40
|
+
passes_conditional_processing?(e) && Prawn::SVG::Elements::TAG_CLASS_MAPPING[e.name.to_sym]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
return unless elem
|
|
44
|
+
|
|
45
|
+
element_class = Prawn::SVG::Elements::TAG_CLASS_MAPPING[elem.name.to_sym]
|
|
46
|
+
add_call 'save'
|
|
47
|
+
child = element_class.new(@document, elem, @calls, state.dup)
|
|
48
|
+
child.process
|
|
49
|
+
add_call 'restore'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def passes_conditional_processing?(element)
|
|
55
|
+
passes_required_features?(element) &&
|
|
56
|
+
passes_required_extensions?(element) &&
|
|
57
|
+
passes_system_language?(element)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def passes_required_features?(element)
|
|
61
|
+
value = element.attributes['requiredFeatures']
|
|
62
|
+
return true if value.nil?
|
|
63
|
+
|
|
64
|
+
features = value.strip.split(/\s+/)
|
|
65
|
+
return false if features.empty?
|
|
66
|
+
|
|
67
|
+
features.all? { |feature| SUPPORTED_FEATURES.include?(feature) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def passes_required_extensions?(element)
|
|
71
|
+
value = element.attributes['requiredExtensions']
|
|
72
|
+
return true if value.nil?
|
|
73
|
+
|
|
74
|
+
# We don't support any extensions
|
|
75
|
+
false
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def passes_system_language?(element)
|
|
79
|
+
value = element.attributes['systemLanguage']
|
|
80
|
+
return true if value.nil?
|
|
81
|
+
|
|
82
|
+
languages = value.strip.split(/\s*,\s*/)
|
|
83
|
+
return false if languages.empty? || languages == ['']
|
|
84
|
+
|
|
85
|
+
user_language = (document.instance_variable_get(:@options)[:language] || 'en').downcase
|
|
86
|
+
|
|
87
|
+
languages.any? do |lang|
|
|
88
|
+
lang = lang.strip.downcase
|
|
89
|
+
user_language == lang || lang.start_with?("#{user_language}-")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|