gamba 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +80 -0
  3. data/COPYING +2 -0
  4. data/GPLv2 +339 -0
  5. data/GPLv3 +674 -0
  6. data/LICENSE +65 -0
  7. data/LICENSE-prawn-svg +21 -0
  8. data/README.md +148 -0
  9. data/data/fonts/Courier-Bold.afm +342 -0
  10. data/data/fonts/Courier-BoldOblique.afm +342 -0
  11. data/data/fonts/Courier-Oblique.afm +342 -0
  12. data/data/fonts/Courier.afm +342 -0
  13. data/data/fonts/Helvetica-Bold.afm +2827 -0
  14. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  15. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  16. data/data/fonts/Helvetica.afm +3051 -0
  17. data/data/fonts/MustRead.html +19 -0
  18. data/data/fonts/Symbol.afm +213 -0
  19. data/data/fonts/Times-Bold.afm +2588 -0
  20. data/data/fonts/Times-BoldItalic.afm +2384 -0
  21. data/data/fonts/Times-Italic.afm +2667 -0
  22. data/data/fonts/Times-Roman.afm +2419 -0
  23. data/data/fonts/ZapfDingbats.afm +225 -0
  24. data/lib/gamba/annotations.rb +21 -0
  25. data/lib/gamba/combine.rb +307 -0
  26. data/lib/gamba/document/bounding_box.rb +193 -0
  27. data/lib/gamba/document/column_box.rb +91 -0
  28. data/lib/gamba/document/internals.rb +264 -0
  29. data/lib/gamba/document/span.rb +25 -0
  30. data/lib/gamba/document.rb +268 -0
  31. data/lib/gamba/encoding.rb +13 -0
  32. data/lib/gamba/errors.rb +39 -0
  33. data/lib/gamba/font.rb +228 -0
  34. data/lib/gamba/fonts/afm.rb +171 -0
  35. data/lib/gamba/fonts/otf.rb +50 -0
  36. data/lib/gamba/fonts/ttf.rb +413 -0
  37. data/lib/gamba/graphic_state.rb +84 -0
  38. data/lib/gamba/graphics/color.rb +174 -0
  39. data/lib/gamba/graphics/patterns.rb +100 -0
  40. data/lib/gamba/graphics/style.rb +159 -0
  41. data/lib/gamba/graphics.rb +212 -0
  42. data/lib/gamba/grid.rb +216 -0
  43. data/lib/gamba/images/image.rb +271 -0
  44. data/lib/gamba/images/png_decoder.rb +65 -0
  45. data/lib/gamba/images.rb +115 -0
  46. data/lib/gamba/measurement_extensions.rb +40 -0
  47. data/lib/gamba/measurements.rb +62 -0
  48. data/lib/gamba/outline.rb +148 -0
  49. data/lib/gamba/page.rb +125 -0
  50. data/lib/gamba/page_geometry.rb +27 -0
  51. data/lib/gamba/pdf/decryption.rb +165 -0
  52. data/lib/gamba/pdf/outline_node.rb +53 -0
  53. data/lib/gamba/pdf/reader.rb +616 -0
  54. data/lib/gamba/pdf/reference.rb +31 -0
  55. data/lib/gamba/pdf/writer.rb +105 -0
  56. data/lib/gamba/pdf.rb +87 -0
  57. data/lib/gamba/pdf_a/srgb_profile.rb +61 -0
  58. data/lib/gamba/pdf_a.rb +132 -0
  59. data/lib/gamba/repeater.rb +79 -0
  60. data/lib/gamba/security.rb +111 -0
  61. data/lib/gamba/stamp.rb +76 -0
  62. data/lib/gamba/table/cell/kinds.rb +246 -0
  63. data/lib/gamba/table/cell.rb +331 -0
  64. data/lib/gamba/table/cells.rb +208 -0
  65. data/lib/gamba/table.rb +298 -0
  66. data/lib/gamba/text/box.rb +16 -0
  67. data/lib/gamba/text/formatted/arranger.rb +217 -0
  68. data/lib/gamba/text/formatted/box.rb +312 -0
  69. data/lib/gamba/text/formatted/fragment.rb +205 -0
  70. data/lib/gamba/text/formatted/line_wrap.rb +205 -0
  71. data/lib/gamba/text/formatted/parser.rb +150 -0
  72. data/lib/gamba/text/formatted/wrap.rb +103 -0
  73. data/lib/gamba/text.rb +310 -0
  74. data/lib/gamba/transformation_stack.rb +43 -0
  75. data/lib/gamba/true_type/cff_subset.rb +157 -0
  76. data/lib/gamba/true_type/file.rb +372 -0
  77. data/lib/gamba/true_type/open_type_layout.rb +189 -0
  78. data/lib/gamba/true_type/simple_subsets.rb +108 -0
  79. data/lib/gamba/true_type/subset.rb +206 -0
  80. data/lib/gamba/version.rb +10 -0
  81. data/lib/gamba/view.rb +28 -0
  82. data/lib/gamba.rb +124 -0
  83. data/lib/pdf/core.rb +4 -0
  84. data/lib/prawn/measurement_extensions.rb +4 -0
  85. data/lib/prawn/svg/attributes/clip_path.rb +46 -0
  86. data/lib/prawn/svg/attributes/mask.rb +16 -0
  87. data/lib/prawn/svg/attributes/opacity.rb +30 -0
  88. data/lib/prawn/svg/attributes/space.rb +10 -0
  89. data/lib/prawn/svg/attributes/stroke.rb +50 -0
  90. data/lib/prawn/svg/attributes/transform.rb +10 -0
  91. data/lib/prawn/svg/attributes.rb +6 -0
  92. data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +57 -0
  93. data/lib/prawn/svg/calculators/aspect_ratio.rb +60 -0
  94. data/lib/prawn/svg/calculators/document_sizing.rb +103 -0
  95. data/lib/prawn/svg/calculators/path_length.rb +168 -0
  96. data/lib/prawn/svg/calculators/pixels.rb +57 -0
  97. data/lib/prawn/svg/color.rb +240 -0
  98. data/lib/prawn/svg/css/font_face_parser.rb +72 -0
  99. data/lib/prawn/svg/css/font_family_parser.rb +39 -0
  100. data/lib/prawn/svg/css/font_parser.rb +46 -0
  101. data/lib/prawn/svg/css/selector_parser.rb +180 -0
  102. data/lib/prawn/svg/css/stylesheets.rb +233 -0
  103. data/lib/prawn/svg/css/values_parser.rb +68 -0
  104. data/lib/prawn/svg/document.rb +169 -0
  105. data/lib/prawn/svg/elements/anchor.rb +10 -0
  106. data/lib/prawn/svg/elements/base.rb +315 -0
  107. data/lib/prawn/svg/elements/bbox_scaling.rb +78 -0
  108. data/lib/prawn/svg/elements/call_duplicator.rb +37 -0
  109. data/lib/prawn/svg/elements/circle.rb +19 -0
  110. data/lib/prawn/svg/elements/clip_path.rb +26 -0
  111. data/lib/prawn/svg/elements/container.rb +10 -0
  112. data/lib/prawn/svg/elements/direct_render_base.rb +28 -0
  113. data/lib/prawn/svg/elements/ellipse.rb +20 -0
  114. data/lib/prawn/svg/elements/gradient.rb +183 -0
  115. data/lib/prawn/svg/elements/ignored.rb +5 -0
  116. data/lib/prawn/svg/elements/image.rb +178 -0
  117. data/lib/prawn/svg/elements/line.rb +27 -0
  118. data/lib/prawn/svg/elements/marker.rb +72 -0
  119. data/lib/prawn/svg/elements/mask.rb +65 -0
  120. data/lib/prawn/svg/elements/path.rb +294 -0
  121. data/lib/prawn/svg/elements/pattern.rb +150 -0
  122. data/lib/prawn/svg/elements/polygon.rb +25 -0
  123. data/lib/prawn/svg/elements/polyline.rb +23 -0
  124. data/lib/prawn/svg/elements/rect.rb +64 -0
  125. data/lib/prawn/svg/elements/root.rb +22 -0
  126. data/lib/prawn/svg/elements/switch.rb +92 -0
  127. data/lib/prawn/svg/elements/text.rb +99 -0
  128. data/lib/prawn/svg/elements/text_component.rb +352 -0
  129. data/lib/prawn/svg/elements/text_node.rb +308 -0
  130. data/lib/prawn/svg/elements/text_path.rb +153 -0
  131. data/lib/prawn/svg/elements/use.rb +142 -0
  132. data/lib/prawn/svg/elements/viewport.rb +29 -0
  133. data/lib/prawn/svg/elements.rb +46 -0
  134. data/lib/prawn/svg/extension.rb +25 -0
  135. data/lib/prawn/svg/font.rb +20 -0
  136. data/lib/prawn/svg/font_metrics.rb +144 -0
  137. data/lib/prawn/svg/font_registry.rb +315 -0
  138. data/lib/prawn/svg/funciri.rb +14 -0
  139. data/lib/prawn/svg/gradient_renderer.rb +332 -0
  140. data/lib/prawn/svg/gradients.rb +53 -0
  141. data/lib/prawn/svg/interface.rb +72 -0
  142. data/lib/prawn/svg/length.rb +43 -0
  143. data/lib/prawn/svg/link_renderer.rb +40 -0
  144. data/lib/prawn/svg/loaders/data.rb +17 -0
  145. data/lib/prawn/svg/loaders/file.rb +111 -0
  146. data/lib/prawn/svg/loaders/web.rb +24 -0
  147. data/lib/prawn/svg/paint.rb +67 -0
  148. data/lib/prawn/svg/pathable.rb +153 -0
  149. data/lib/prawn/svg/pattern_renderer.rb +112 -0
  150. data/lib/prawn/svg/pdfmatrix.rb +39 -0
  151. data/lib/prawn/svg/percentage.rb +24 -0
  152. data/lib/prawn/svg/properties.rb +329 -0
  153. data/lib/prawn/svg/renderer.rb +249 -0
  154. data/lib/prawn/svg/state.rb +24 -0
  155. data/lib/prawn/svg/transform_parser.rb +78 -0
  156. data/lib/prawn/svg/ttc.rb +35 -0
  157. data/lib/prawn/svg/ttf.rb +95 -0
  158. data/lib/prawn/svg/url_loader.rb +46 -0
  159. data/lib/prawn/svg/version.rb +5 -0
  160. data/lib/prawn/table.rb +4 -0
  161. data/lib/prawn-svg.rb +52 -0
  162. data/lib/prawn.rb +3 -0
  163. metadata +263 -0
@@ -0,0 +1,315 @@
1
+ class Prawn::SVG::Elements::Base
2
+ extend Forwardable
3
+
4
+ include Prawn::SVG::Elements::CallDuplicator
5
+ include Prawn::SVG::Elements::BboxScaling
6
+
7
+ include Prawn::SVG::Calculators::Pixels
8
+
9
+ include Prawn::SVG::Attributes::Transform
10
+ include Prawn::SVG::Attributes::Opacity
11
+ include Prawn::SVG::Attributes::ClipPath
12
+ include Prawn::SVG::Attributes::Mask
13
+ include Prawn::SVG::Attributes::Stroke
14
+ include Prawn::SVG::Attributes::Space
15
+
16
+ include Prawn::SVG::TransformParser
17
+
18
+ PAINT_TYPES = %w[fill stroke].freeze
19
+ COMMA_WSP_REGEXP = Prawn::SVG::Elements::COMMA_WSP_REGEXP
20
+ SVG_NAMESPACE = "http://www.w3.org/2000/svg".freeze
21
+
22
+ SkipElementQuietly = Class.new(StandardError)
23
+ SkipElementError = Class.new(StandardError)
24
+ MissingAttributesError = Class.new(SkipElementError)
25
+
26
+ attr_reader :document, :source, :parent_calls, :base_calls, :state, :attributes, :properties
27
+ attr_accessor :calls
28
+
29
+ def_delegators :@document, :warnings
30
+ def_delegator :@state, :computed_properties
31
+
32
+ def initialize(document, source, parent_calls, state)
33
+ @document = document
34
+ @source = source
35
+ @parent_calls = parent_calls
36
+ @state = state
37
+ @base_calls = @calls = []
38
+ @attributes = {}
39
+ @properties = Prawn::SVG::Properties.new
40
+
41
+ if source && add_to_elements_by_id? && !state.inside_use
42
+ id = source.attributes["id"]
43
+ id = id.strip if id
44
+
45
+ document.elements_by_id[id] = self if id && id != ""
46
+ end
47
+ end
48
+
49
+ def process
50
+ extract_attributes_and_properties
51
+ parse_and_apply
52
+ end
53
+
54
+ def parse_and_apply
55
+ parse_standard_attributes
56
+ parse
57
+
58
+ apply_calls_from_standard_attributes
59
+ apply
60
+
61
+ if state.anchor_href && bounding_box
62
+ add_call("svg:add_link", state.anchor_href, bounding_box)
63
+ end
64
+
65
+ process_child_elements if container?
66
+
67
+ append_calls_to_parent unless computed_properties.display == "none"
68
+ rescue SkipElementQuietly
69
+ rescue SkipElementError => e
70
+ @document.warnings << e.message
71
+ end
72
+
73
+ def name
74
+ @name ||= source ? source.name : "???"
75
+ end
76
+
77
+ protected
78
+
79
+ def parse
80
+ end
81
+
82
+ def apply
83
+ end
84
+
85
+ def bounding_box
86
+ end
87
+
88
+ def container?
89
+ false
90
+ end
91
+
92
+ def drawable?
93
+ !container?
94
+ end
95
+
96
+ def parse_standard_attributes
97
+ parse_xml_space_attribute
98
+ end
99
+
100
+ def add_call(name, *arguments, **kwarguments)
101
+ @calls << [name.to_s, arguments, kwarguments, []]
102
+ end
103
+
104
+ def add_call_and_enter(name, *arguments, **kwarguments)
105
+ @calls << [name.to_s, arguments, kwarguments, []]
106
+ @calls = @calls.last.last
107
+ end
108
+
109
+ def push_call_position
110
+ @call_positions ||= []
111
+ @call_positions << @calls
112
+ end
113
+
114
+ def pop_call_position
115
+ @calls = @call_positions.pop
116
+ end
117
+
118
+ def append_calls_to_parent
119
+ @parent_calls.concat(@base_calls)
120
+ end
121
+
122
+ def add_calls_from_element(other)
123
+ @calls.concat duplicate_calls(other.base_calls)
124
+ end
125
+
126
+ def add_yield_call(&block)
127
+ add_call("svg:yield", block)
128
+ end
129
+
130
+ def new_call_context_from_base
131
+ old_calls = @calls
132
+ @calls = @base_calls
133
+ yield
134
+ @calls = old_calls
135
+ end
136
+
137
+ def process_child_elements
138
+ return unless source
139
+
140
+ svg_child_elements.each do |elem|
141
+ if (element_class = Prawn::SVG::Elements::TAG_CLASS_MAPPING[elem.name.to_sym])
142
+ add_call "save"
143
+
144
+ child = element_class.new(@document, elem, @calls, state.dup)
145
+ child.process
146
+
147
+ add_call "restore"
148
+ else
149
+ @document.warnings << "Unknown tag '#{elem.name}'; ignoring"
150
+ end
151
+ end
152
+ end
153
+
154
+ def svg_child_elements
155
+ source.elements.select do |elem|
156
+ # To be strict, we shouldn't treat namespace-less elements as SVG, but for
157
+ # backwards compatibility, and because it doesn't hurt, we will.
158
+ [SVG_NAMESPACE, ""].include?(elem.namespace)
159
+ end
160
+ end
161
+
162
+ def apply_calls_from_standard_attributes
163
+ parse_transform_attribute_and_call
164
+ parse_opacity_attributes_and_call
165
+ parse_clip_path_attribute_and_call
166
+ parse_mask_attribute_and_call
167
+ apply_colors
168
+ parse_stroke_attributes_and_call
169
+ apply_drawing_call
170
+ end
171
+
172
+ def apply_drawing_call
173
+ return if state.disable_drawing || !drawable?
174
+
175
+ fill = !computed_properties.fill.none? # rubocop:disable Style/InverseMethods
176
+ stroke = !computed_properties.stroke.none? # rubocop:disable Style/InverseMethods
177
+
178
+ if fill
179
+ command = stroke ? "fill_and_stroke" : "fill"
180
+
181
+ if computed_properties.fill_rule == "evenodd"
182
+ add_call_and_enter(command, fill_rule: :even_odd)
183
+ else
184
+ add_call_and_enter(command)
185
+ end
186
+ elsif stroke
187
+ add_call_and_enter("stroke")
188
+ else
189
+ add_call_and_enter("end_path")
190
+ end
191
+ end
192
+
193
+ def apply_colors
194
+ PAINT_TYPES.each do |type|
195
+ paint = properties.send(type)
196
+
197
+ case paint
198
+ when nil, "inherit"
199
+ next
200
+ when Prawn::SVG::Paint
201
+ color = paint.resolve(document.gradients, computed_properties.color, document.color_mode)
202
+
203
+ case color
204
+ when Prawn::SVG::Color::RGB, Prawn::SVG::Color::CMYK
205
+ add_call "#{type}_color", color.value
206
+ when Prawn::SVG::Elements::Gradient
207
+ add_call "svg:render_gradient", type.to_sym, **color.gradient_arguments(self)
208
+ when Prawn::SVG::Elements::Pattern
209
+ args = color.pattern_arguments(self)
210
+ add_call "svg:render_pattern", type.to_sym, **args if args
211
+ when nil
212
+ nil
213
+ else
214
+ raise "Unknown resolved color type: #{color.inspect}"
215
+ end
216
+ else
217
+ raise "Unknown paint type: #{paint.inspect}"
218
+ end
219
+ end
220
+ end
221
+
222
+ def extract_attributes_and_properties
223
+ # Apply user agent stylesheet
224
+ if %w[svg symbol image marker pattern foreignObject].include?(source.name)
225
+ @properties.set("overflow", "hidden")
226
+ end
227
+
228
+ # Apply presentation attributes, and set attributes that aren't presentation attributes
229
+ source.attributes.each do |name, value|
230
+ # Properties#set returns nil if it's not a recognised property name
231
+ @properties.set(name, value) or @attributes[name] = value
232
+ end
233
+
234
+ # Apply stylesheet styles
235
+ if (styles = document.element_styles[source])
236
+ styles.each do |name, value, important|
237
+ @properties.set(name, value, important:)
238
+ end
239
+ end
240
+
241
+ # Apply inline styles
242
+ @properties.load_hash(parse_css_declarations(source.attributes["style"] || ""))
243
+
244
+ state.computed_properties.compute_properties(@properties)
245
+ end
246
+
247
+ def parse_css_declarations(declarations)
248
+ # copied from css_parser
249
+ declarations
250
+ .gsub(/(^\s*)|(\s*$)/, "")
251
+ .split(/[;$]+/m)
252
+ .each_with_object({}) do |decs, output|
253
+ if (matches = decs.match(/\s*(.[^:]*)\s*:\s*(.[^;]*)\s*(;|\Z)/i))
254
+ property, value, = matches.captures
255
+ output[property.downcase] = value
256
+ end
257
+ end
258
+ end
259
+
260
+ def require_attributes(*names)
261
+ missing_attrs = names - attributes.keys
262
+ if missing_attrs.any?
263
+ raise MissingAttributesError, "Must have attributes #{missing_attrs.join(", ")} on tag #{name}; skipping tag"
264
+ end
265
+ end
266
+
267
+ def require_positive_value(*args)
268
+ if args.any? { |arg| arg.nil? || arg <= 0 }
269
+ raise SkipElementError, "Invalid attributes on tag #{name}; skipping tag"
270
+ end
271
+ end
272
+
273
+ def extract_element_from_url_id_reference(value, expected_type = nil)
274
+ case value
275
+ when Prawn::SVG::FuncIRI
276
+ element = document.elements_by_id[value.url[1..]] if value.url.start_with?("#") # rubocop:disable Performance/ArraySemiInfiniteRangeSlice -- String
277
+ element if element && (expected_type.nil? || element.name == expected_type)
278
+ end
279
+ end
280
+
281
+ def href_attribute
282
+ attributes["xlink:href"] || attributes["href"]
283
+ end
284
+
285
+ def overflow_hidden?
286
+ ["hidden", "scroll"].include?(computed_properties.overflow)
287
+ end
288
+
289
+ def transformable?
290
+ true
291
+ end
292
+
293
+ def add_to_elements_by_id?
294
+ true
295
+ end
296
+
297
+ def stroke_width
298
+ if computed_properties.stroke.none?
299
+ 0
300
+ else
301
+ pixels(properties.stroke_width) || 1.0
302
+ end
303
+ end
304
+
305
+ def clone_element_source(source)
306
+ new_source = source.dup
307
+ document.element_styles[new_source] = document.element_styles[source]
308
+ new_source
309
+ end
310
+
311
+ def set_display_none
312
+ properties.display = "none"
313
+ computed_properties.display = "none"
314
+ end
315
+ end
@@ -0,0 +1,78 @@
1
+ module Prawn::SVG::Elements::BboxScaling
2
+ private
3
+
4
+ def build_object_bounding_box_calls(bbox, source_element = source)
5
+ bbox_left = bbox[0]
6
+ bbox_top = bbox[1]
7
+ bbox_right = bbox[2]
8
+ bbox_bottom = bbox[3]
9
+ bbox_w = bbox_right - bbox_left
10
+ bbox_h = bbox_top - bbox_bottom
11
+
12
+ unit_sizing = Prawn::SVG::Calculators::DocumentSizing.new([1, 1])
13
+ unit_sizing.document_width = 1
14
+ unit_sizing.document_height = 1
15
+ unit_sizing.calculate
16
+
17
+ result_calls = document.with_sizing(unit_sizing) do
18
+ new_state = state.dup
19
+ new_state.viewport_sizing = unit_sizing
20
+ new_state.inside_use = true
21
+
22
+ container = Prawn::SVG::Elements::Container.new(document, source_element, [], new_state)
23
+ container.process
24
+
25
+ container.base_calls
26
+ end
27
+
28
+ scale_calls_to_bbox(duplicate_calls(result_calls), bbox_left, bbox_top, bbox_w, bbox_h)
29
+ end
30
+
31
+ def scale_calls_to_bbox(calls, bbox_left, bbox_top, bbox_w, bbox_h)
32
+ calls.map do |name, args, kwargs, children|
33
+ new_args = case name
34
+ when "rectangle"
35
+ point, width, height = args
36
+ [scale_point(point, bbox_left, bbox_top, bbox_w, bbox_h), width * bbox_w, height * bbox_h]
37
+ when "rounded_rectangle"
38
+ point, width, height, radius = args
39
+ [scale_point(point, bbox_left, bbox_top, bbox_w, bbox_h), width * bbox_w, height * bbox_h, radius * bbox_w]
40
+ when "move_to", "line_to"
41
+ [scale_point(args[0], bbox_left, bbox_top, bbox_w, bbox_h)]
42
+ when "circle"
43
+ point, radius = args
44
+ scaled_point = scale_point(point, bbox_left, bbox_top, bbox_w, bbox_h)
45
+ if bbox_w == bbox_h
46
+ [scaled_point, radius * bbox_w]
47
+ else
48
+ new_children = children.any? ? scale_calls_to_bbox(children, bbox_left, bbox_top, bbox_w, bbox_h) : children
49
+ next ["ellipse", [scaled_point, radius * bbox_w, radius * bbox_h], kwargs, new_children]
50
+ end
51
+ when "ellipse"
52
+ point, rx, ry = args
53
+ [scale_point(point, bbox_left, bbox_top, bbox_w, bbox_h), rx * bbox_w, ry * bbox_h]
54
+ when "curve_to"
55
+ dest = scale_point(args[0], bbox_left, bbox_top, bbox_w, bbox_h)
56
+ [dest]
57
+ else
58
+ args
59
+ end
60
+
61
+ new_kwargs = if name == "curve_to" && kwargs[:bounds]
62
+ b = kwargs[:bounds]
63
+ {bounds: b.map { |p| scale_point(p, bbox_left, bbox_top, bbox_w, bbox_h) }}
64
+ else
65
+ kwargs
66
+ end
67
+
68
+ new_children = children.any? ? scale_calls_to_bbox(children, bbox_left, bbox_top, bbox_w, bbox_h) : children
69
+ [name, new_args, new_kwargs, new_children]
70
+ end
71
+ end
72
+
73
+ def scale_point(point, bbox_left, bbox_top, bbox_w, bbox_h)
74
+ x = bbox_left + (point[0] * bbox_w)
75
+ y = bbox_top - ((1.0 - point[1]) * bbox_h)
76
+ [x, y]
77
+ end
78
+ end
@@ -0,0 +1,37 @@
1
+ #
2
+ # Unfortunately, prawn mutates arguments passed in to it.
3
+ # When we make a copy of one of the call stacks, we need to make a deep
4
+ # duplicate of it so that the first time prawn mutates the arguments, it
5
+ # won't affect the subsequent calls.
6
+ #
7
+ module Prawn::SVG::Elements::CallDuplicator
8
+ private
9
+
10
+ def duplicate_calls(calls)
11
+ calls.map { |call| duplicate_call(call) }
12
+ end
13
+
14
+ def duplicate_call(call)
15
+ [call[0], duplicate_array(call[1]), duplicate_hash(call[2]), duplicate_calls(call[3])]
16
+ end
17
+
18
+ def duplicate_array(array)
19
+ array.map do |value|
20
+ case value
21
+ when Array then duplicate_array(value)
22
+ when Hash then duplicate_hash(value)
23
+ else value
24
+ end
25
+ end
26
+ end
27
+
28
+ def duplicate_hash(hash)
29
+ hash.each.with_object({}) do |(key, value), result|
30
+ result[key] = case value
31
+ when Array then duplicate_array(value)
32
+ when Hash then duplicate_hash(value)
33
+ else value
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,19 @@
1
+ class Prawn::SVG::Elements::Circle < Prawn::SVG::Elements::Base
2
+ def parse
3
+ require_attributes "r"
4
+
5
+ @x = x(attributes["cx"] || "0")
6
+ @y = y(attributes["cy"] || "0")
7
+ @r = pixels(attributes["r"])
8
+
9
+ require_positive_value @r
10
+ end
11
+
12
+ def apply
13
+ add_call "circle", [@x, @y], @r
14
+ end
15
+
16
+ def bounding_box
17
+ [@x - @r, @y + @r, @x + @r, @y - @r]
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ class Prawn::SVG::Elements::ClipPath < Prawn::SVG::Elements::Base
2
+ def parse
3
+ state.inside_clip_path = true
4
+ set_display_none
5
+ end
6
+
7
+ def container?
8
+ true
9
+ end
10
+
11
+ def build_clip_calls(element)
12
+ units = attributes["clipPathUnits"] || "userSpaceOnUse"
13
+
14
+ if units == "objectBoundingBox"
15
+ bbox = element.bounding_box
16
+ if bbox.nil?
17
+ document.warnings << 'clipPath with clipPathUnits="objectBoundingBox" requires element to have a bounding box'
18
+ return nil
19
+ end
20
+
21
+ build_object_bounding_box_calls(bbox)
22
+ else
23
+ duplicate_calls(base_calls)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,10 @@
1
+ class Prawn::SVG::Elements::Container < Prawn::SVG::Elements::Base
2
+ def parse
3
+ set_display_none if name == "symbol" && !state.inside_use
4
+ set_display_none if name == "defs"
5
+ end
6
+
7
+ def container?
8
+ true
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ #
2
+ # This element base class is used for elements that render directly to the Prawn canvas.
3
+ #
4
+ # Initially when I wrote prawn-svg, I was expecting to have multiple renderers and thought separating the codebase
5
+ # from the Prawn renderer would be a good idea. However, it turns out that the Prawn renderer was the only one
6
+ # that was targeted, and it ended up being tightly coupled with the Prawn library.
7
+ #
8
+ # This class is probably how I should have written it. Direct render is required to do text rendering properly
9
+ # because we need to know the width of all the things we print. As of the time of this comment it's the only
10
+ # system that uses DirectRenderBase in prawn-svg.
11
+ #
12
+ class Prawn::SVG::Elements::DirectRenderBase < Prawn::SVG::Elements::Base
13
+ # Called by Renderer when it finds the svg:render call added below.
14
+ def render(prawn, renderer)
15
+ end
16
+
17
+ protected
18
+
19
+ def parse_and_apply
20
+ parse_standard_attributes
21
+ parse
22
+ apply_calls_from_standard_attributes
23
+ @parent_calls << ["svg:render", [self], [], []] unless computed_properties.display == "none"
24
+ rescue SkipElementQuietly
25
+ rescue SkipElementError => e
26
+ @document.warnings << e.message
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ class Prawn::SVG::Elements::Ellipse < Prawn::SVG::Elements::Base
2
+ def parse
3
+ require_attributes "rx", "ry"
4
+
5
+ @x = x(attributes["cx"] || "0")
6
+ @y = y(attributes["cy"] || "0")
7
+ @rx = x_pixels(attributes["rx"])
8
+ @ry = y_pixels(attributes["ry"])
9
+
10
+ require_positive_value @rx, @ry
11
+ end
12
+
13
+ def apply
14
+ add_call "ellipse", [@x, @y], @rx, @ry
15
+ end
16
+
17
+ def bounding_box
18
+ [@x - @rx, @y + @ry, @x + @rx, @y - @ry]
19
+ end
20
+ end