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.
Files changed (160) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +2 -0
  3. data/GPLv2 +339 -0
  4. data/GPLv3 +674 -0
  5. data/LICENSE +65 -0
  6. data/LICENSE-prawn-svg +21 -0
  7. data/README.md +107 -0
  8. data/data/fonts/Courier-Bold.afm +342 -0
  9. data/data/fonts/Courier-BoldOblique.afm +342 -0
  10. data/data/fonts/Courier-Oblique.afm +342 -0
  11. data/data/fonts/Courier.afm +342 -0
  12. data/data/fonts/Helvetica-Bold.afm +2827 -0
  13. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  14. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  15. data/data/fonts/Helvetica.afm +3051 -0
  16. data/data/fonts/MustRead.html +19 -0
  17. data/data/fonts/Symbol.afm +213 -0
  18. data/data/fonts/Times-Bold.afm +2588 -0
  19. data/data/fonts/Times-BoldItalic.afm +2384 -0
  20. data/data/fonts/Times-Italic.afm +2667 -0
  21. data/data/fonts/Times-Roman.afm +2419 -0
  22. data/data/fonts/ZapfDingbats.afm +225 -0
  23. data/lib/pdf/core.rb +4 -0
  24. data/lib/prawn/measurement_extensions.rb +4 -0
  25. data/lib/prawn/svg/attributes/clip_path.rb +46 -0
  26. data/lib/prawn/svg/attributes/mask.rb +16 -0
  27. data/lib/prawn/svg/attributes/opacity.rb +30 -0
  28. data/lib/prawn/svg/attributes/space.rb +10 -0
  29. data/lib/prawn/svg/attributes/stroke.rb +50 -0
  30. data/lib/prawn/svg/attributes/transform.rb +10 -0
  31. data/lib/prawn/svg/attributes.rb +6 -0
  32. data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +57 -0
  33. data/lib/prawn/svg/calculators/aspect_ratio.rb +60 -0
  34. data/lib/prawn/svg/calculators/document_sizing.rb +103 -0
  35. data/lib/prawn/svg/calculators/path_length.rb +168 -0
  36. data/lib/prawn/svg/calculators/pixels.rb +57 -0
  37. data/lib/prawn/svg/color.rb +240 -0
  38. data/lib/prawn/svg/css/font_face_parser.rb +72 -0
  39. data/lib/prawn/svg/css/font_family_parser.rb +39 -0
  40. data/lib/prawn/svg/css/font_parser.rb +46 -0
  41. data/lib/prawn/svg/css/selector_parser.rb +178 -0
  42. data/lib/prawn/svg/css/stylesheets.rb +233 -0
  43. data/lib/prawn/svg/css/values_parser.rb +68 -0
  44. data/lib/prawn/svg/document.rb +170 -0
  45. data/lib/prawn/svg/elements/anchor.rb +10 -0
  46. data/lib/prawn/svg/elements/base.rb +312 -0
  47. data/lib/prawn/svg/elements/bbox_scaling.rb +78 -0
  48. data/lib/prawn/svg/elements/call_duplicator.rb +37 -0
  49. data/lib/prawn/svg/elements/circle.rb +25 -0
  50. data/lib/prawn/svg/elements/clip_path.rb +26 -0
  51. data/lib/prawn/svg/elements/container.rb +10 -0
  52. data/lib/prawn/svg/elements/direct_render_base.rb +27 -0
  53. data/lib/prawn/svg/elements/ellipse.rb +22 -0
  54. data/lib/prawn/svg/elements/gradient.rb +184 -0
  55. data/lib/prawn/svg/elements/ignored.rb +5 -0
  56. data/lib/prawn/svg/elements/image.rb +177 -0
  57. data/lib/prawn/svg/elements/line.rb +27 -0
  58. data/lib/prawn/svg/elements/marker.rb +72 -0
  59. data/lib/prawn/svg/elements/mask.rb +65 -0
  60. data/lib/prawn/svg/elements/path.rb +294 -0
  61. data/lib/prawn/svg/elements/pattern.rb +150 -0
  62. data/lib/prawn/svg/elements/polygon.rb +25 -0
  63. data/lib/prawn/svg/elements/polyline.rb +23 -0
  64. data/lib/prawn/svg/elements/rect.rb +64 -0
  65. data/lib/prawn/svg/elements/root.rb +22 -0
  66. data/lib/prawn/svg/elements/switch.rb +92 -0
  67. data/lib/prawn/svg/elements/text.rb +99 -0
  68. data/lib/prawn/svg/elements/text_component.rb +346 -0
  69. data/lib/prawn/svg/elements/text_node.rb +308 -0
  70. data/lib/prawn/svg/elements/text_path.rb +153 -0
  71. data/lib/prawn/svg/elements/use.rb +142 -0
  72. data/lib/prawn/svg/elements/viewport.rb +29 -0
  73. data/lib/prawn/svg/elements.rb +46 -0
  74. data/lib/prawn/svg/extension.rb +25 -0
  75. data/lib/prawn/svg/font.rb +20 -0
  76. data/lib/prawn/svg/font_metrics.rb +144 -0
  77. data/lib/prawn/svg/font_registry.rb +315 -0
  78. data/lib/prawn/svg/funciri.rb +14 -0
  79. data/lib/prawn/svg/gradient_renderer.rb +332 -0
  80. data/lib/prawn/svg/gradients.rb +53 -0
  81. data/lib/prawn/svg/interface.rb +72 -0
  82. data/lib/prawn/svg/length.rb +43 -0
  83. data/lib/prawn/svg/link_renderer.rb +40 -0
  84. data/lib/prawn/svg/loaders/data.rb +17 -0
  85. data/lib/prawn/svg/loaders/file.rb +111 -0
  86. data/lib/prawn/svg/loaders/web.rb +24 -0
  87. data/lib/prawn/svg/paint.rb +67 -0
  88. data/lib/prawn/svg/pathable.rb +153 -0
  89. data/lib/prawn/svg/pattern_renderer.rb +112 -0
  90. data/lib/prawn/svg/pdfmatrix.rb +37 -0
  91. data/lib/prawn/svg/percentage.rb +24 -0
  92. data/lib/prawn/svg/properties.rb +329 -0
  93. data/lib/prawn/svg/renderer.rb +249 -0
  94. data/lib/prawn/svg/state.rb +24 -0
  95. data/lib/prawn/svg/transform_parser.rb +78 -0
  96. data/lib/prawn/svg/ttc.rb +35 -0
  97. data/lib/prawn/svg/ttf.rb +95 -0
  98. data/lib/prawn/svg/url_loader.rb +46 -0
  99. data/lib/prawn/svg/version.rb +5 -0
  100. data/lib/prawn/table.rb +4 -0
  101. data/lib/prawn-svg.rb +53 -0
  102. data/lib/prawn.rb +10 -0
  103. data/lib/prawnx/annotations.rb +21 -0
  104. data/lib/prawnx/document/bounding_box.rb +193 -0
  105. data/lib/prawnx/document/column_box.rb +91 -0
  106. data/lib/prawnx/document/internals.rb +144 -0
  107. data/lib/prawnx/document/span.rb +25 -0
  108. data/lib/prawnx/document.rb +253 -0
  109. data/lib/prawnx/encoding.rb +29 -0
  110. data/lib/prawnx/errors.rb +37 -0
  111. data/lib/prawnx/font.rb +214 -0
  112. data/lib/prawnx/fonts/afm.rb +171 -0
  113. data/lib/prawnx/fonts/otf.rb +50 -0
  114. data/lib/prawnx/fonts/ttf.rb +410 -0
  115. data/lib/prawnx/graphic_state.rb +41 -0
  116. data/lib/prawnx/graphics/color.rb +148 -0
  117. data/lib/prawnx/graphics/patterns.rb +100 -0
  118. data/lib/prawnx/graphics/style.rb +171 -0
  119. data/lib/prawnx/graphics.rb +208 -0
  120. data/lib/prawnx/grid.rb +216 -0
  121. data/lib/prawnx/images/image.rb +244 -0
  122. data/lib/prawnx/images/png_decoder.rb +65 -0
  123. data/lib/prawnx/images.rb +115 -0
  124. data/lib/prawnx/measurement_extensions.rb +40 -0
  125. data/lib/prawnx/measurements.rb +62 -0
  126. data/lib/prawnx/outline.rb +146 -0
  127. data/lib/prawnx/page.rb +125 -0
  128. data/lib/prawnx/page_geometry.rb +27 -0
  129. data/lib/prawnx/pdf/outline_node.rb +53 -0
  130. data/lib/prawnx/pdf/reference.rb +31 -0
  131. data/lib/prawnx/pdf/writer.rb +105 -0
  132. data/lib/prawnx/pdf.rb +66 -0
  133. data/lib/prawnx/pdf_a/srgb_profile.rb +61 -0
  134. data/lib/prawnx/pdf_a.rb +130 -0
  135. data/lib/prawnx/pdf_core.rb +25 -0
  136. data/lib/prawnx/repeater.rb +79 -0
  137. data/lib/prawnx/security.rb +108 -0
  138. data/lib/prawnx/stamp.rb +74 -0
  139. data/lib/prawnx/table/cell/kinds.rb +246 -0
  140. data/lib/prawnx/table/cell.rb +331 -0
  141. data/lib/prawnx/table/cells.rb +208 -0
  142. data/lib/prawnx/table.rb +296 -0
  143. data/lib/prawnx/text/box.rb +16 -0
  144. data/lib/prawnx/text/formatted/arranger.rb +217 -0
  145. data/lib/prawnx/text/formatted/box.rb +312 -0
  146. data/lib/prawnx/text/formatted/fragment.rb +205 -0
  147. data/lib/prawnx/text/formatted/line_wrap.rb +204 -0
  148. data/lib/prawnx/text/formatted/parser.rb +149 -0
  149. data/lib/prawnx/text/formatted/wrap.rb +103 -0
  150. data/lib/prawnx/text.rb +281 -0
  151. data/lib/prawnx/transformation_stack.rb +43 -0
  152. data/lib/prawnx/true_type/cff_subset.rb +157 -0
  153. data/lib/prawnx/true_type/file.rb +371 -0
  154. data/lib/prawnx/true_type/open_type_layout.rb +189 -0
  155. data/lib/prawnx/true_type/simple_subsets.rb +94 -0
  156. data/lib/prawnx/true_type/subset.rb +187 -0
  157. data/lib/prawnx/version.rb +7 -0
  158. data/lib/prawnx/view.rb +28 -0
  159. data/lib/prawnx.rb +102 -0
  160. 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