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,184 @@
1
+ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
2
+ attr_reader :parent_gradient
3
+ attr_reader :x1, :y1, :x2, :y2, :cx, :cy, :r, :fx, :fy, :fr, :units, :stops, :transform_matrix, :wrap
4
+
5
+ TAG_NAME_TO_TYPE = {
6
+ 'linearGradient' => :linear,
7
+ 'radialGradient' => :radial
8
+ }.freeze
9
+
10
+ def parse
11
+ # A gradient tag without an ID is inaccessible and can never be used
12
+ raise SkipElementQuietly if attributes['id'].nil?
13
+
14
+ @parent_gradient = document.gradients[href_attribute[1..]] if href_attribute && href_attribute[0] == '#'
15
+ @transform_matrix = Matrix.identity(3)
16
+ @wrap = :pad
17
+
18
+ assert_compatible_prawn_version
19
+ load_gradient_configuration
20
+ load_coordinates
21
+ load_stops
22
+
23
+ document.gradients[attributes['id']] = self
24
+
25
+ raise SkipElementQuietly # we don't want anything pushed onto the call stack
26
+ end
27
+
28
+ def gradient_arguments(element)
29
+ bbox = element.bounding_box
30
+
31
+ stroke_width = element.stroke_width
32
+ bbox_with_stroke = bbox&.zip([-stroke_width, stroke_width, stroke_width, -stroke_width])&.map(&:sum)
33
+
34
+ if type == :radial
35
+ {
36
+ from: [fx, fy],
37
+ r1: fr,
38
+ to: [cx, cy],
39
+ r2: r,
40
+ stops: stops,
41
+ matrix: matrix_for_bounding_box(bbox),
42
+ wrap: wrap,
43
+ bounding_box: bbox_with_stroke
44
+ }
45
+ else
46
+ {
47
+ from: [x1, y1],
48
+ to: [x2, y2],
49
+ stops: stops,
50
+ matrix: matrix_for_bounding_box(bbox),
51
+ wrap: wrap,
52
+ bounding_box: bbox_with_stroke
53
+ }
54
+ end
55
+ end
56
+
57
+ def derive_attribute(name)
58
+ attributes[name] || parent_gradient&.derive_attribute(name)
59
+ end
60
+
61
+ private
62
+
63
+ def matrix_for_bounding_box(bbox)
64
+ if bbox && units == :bounding_box
65
+ bounding_x1, bounding_y1, bounding_x2, bounding_y2 = *bbox
66
+
67
+ width = bounding_x2 - bounding_x1
68
+ height = bounding_y1 - bounding_y2
69
+
70
+ bounding_box_to_user_space_matrix = Matrix[
71
+ [width, 0.0, bounding_x1],
72
+ [0.0, height, document.sizing.output_height - bounding_y1],
73
+ [0.0, 0.0, 1.0]
74
+ ]
75
+
76
+ svg_to_pdf_matrix * bounding_box_to_user_space_matrix * transform_matrix
77
+ else
78
+ svg_to_pdf_matrix * transform_matrix
79
+ end
80
+ end
81
+
82
+ def svg_to_pdf_matrix
83
+ @svg_to_pdf_matrix ||= Matrix[[1.0, 0.0, 0.0], [0.0, -1.0, document.sizing.output_height], [0.0, 0.0, 1.0]]
84
+ end
85
+
86
+ def type
87
+ TAG_NAME_TO_TYPE.fetch(name)
88
+ end
89
+
90
+ def assert_compatible_prawn_version
91
+ if (Prawn::VERSION.split('.').map(&:to_i) <=> [2, 2, 0]) == -1
92
+ raise SkipElementError, "Prawn 2.2.0+ must be used if you'd like prawn-svg to render gradients"
93
+ end
94
+ end
95
+
96
+ def load_gradient_configuration
97
+ @units = derive_attribute('gradientUnits') == 'userSpaceOnUse' ? :user_space : :bounding_box
98
+
99
+ if (transform = derive_attribute('gradientTransform'))
100
+ @transform_matrix = parse_transform_attribute(transform, space: :svg)
101
+ end
102
+
103
+ if (spread_method = derive_attribute('spreadMethod'))
104
+ spread_method = spread_method.to_sym
105
+ @wrap = [:pad, :reflect, :repeat].include?(spread_method) ? spread_method : :pad
106
+ end
107
+ end
108
+
109
+ def load_coordinates
110
+ case [type, units]
111
+ when [:linear, :bounding_box]
112
+ @x1 = percentage_or_proportion(derive_attribute('x1'), 0.0)
113
+ @y1 = percentage_or_proportion(derive_attribute('y1'), 0.0)
114
+ @x2 = percentage_or_proportion(derive_attribute('x2'), 1.0)
115
+ @y2 = percentage_or_proportion(derive_attribute('y2'), 0.0)
116
+
117
+ when [:linear, :user_space]
118
+ @x1 = x(derive_attribute('x1'))
119
+ @y1 = y_pixels(derive_attribute('y1'))
120
+ @x2 = x(derive_attribute('x2'))
121
+ @y2 = y_pixels(derive_attribute('y2'))
122
+
123
+ when [:radial, :bounding_box]
124
+ @cx = percentage_or_proportion(derive_attribute('cx'), 0.5)
125
+ @cy = percentage_or_proportion(derive_attribute('cy'), 0.5)
126
+ @r = percentage_or_proportion(derive_attribute('r'), 0.5)
127
+ @fx = percentage_or_proportion(derive_attribute('fx'), cx)
128
+ @fy = percentage_or_proportion(derive_attribute('fy'), cy)
129
+ @fr = percentage_or_proportion(derive_attribute('fr'), 0.0)
130
+
131
+ when [:radial, :user_space]
132
+ @cx = x(derive_attribute('cx') || '50%')
133
+ @cy = y_pixels(derive_attribute('cy') || '50%')
134
+ @r = pixels(derive_attribute('r') || '50%')
135
+ @fx = x(derive_attribute('fx') || derive_attribute('cx'))
136
+ @fy = y_pixels(derive_attribute('fy') || derive_attribute('cy'))
137
+ @fr = pixels(derive_attribute('fr') || '0%')
138
+
139
+ else
140
+ raise 'unexpected type/unit system'
141
+ end
142
+ end
143
+
144
+ def load_stops
145
+ stop_elements = source.elements.map do |child|
146
+ element = Prawn::SVG::Elements::Base.new(document, child, [], Prawn::SVG::State.new)
147
+ element.process
148
+ element
149
+ end.select do |element|
150
+ element.name == 'stop' && element.attributes['offset']
151
+ end
152
+
153
+ @stops = stop_elements.each_with_object([]) do |child, result|
154
+ offset = percentage_or_proportion(child.attributes['offset']).clamp(0.0, 1.0)
155
+
156
+ # Offsets must be strictly increasing (SVG 13.2.4)
157
+ offset = result.last[:offset] if result.last && result.last[:offset] > offset
158
+
159
+ if (color = child.properties.stop_color&.value)
160
+ result << { offset: offset, color: color, opacity: (child.properties.stop_opacity || 1.0).clamp(0.0, 1.0) }
161
+ end
162
+ end
163
+
164
+ if stops.empty?
165
+ if parent_gradient.nil? || parent_gradient.stops.empty?
166
+ raise SkipElementError, 'gradient does not have any valid stops'
167
+ end
168
+
169
+ @stops = parent_gradient.stops
170
+ else
171
+ if stops.first[:offset].positive?
172
+ start_stop = stops.first.dup
173
+ start_stop[:offset] = 0
174
+ stops.unshift(start_stop)
175
+ end
176
+
177
+ if stops.last[:offset] < 1
178
+ end_stop = stops.last.dup
179
+ end_stop[:offset] = 1
180
+ stops.push(end_stop)
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,5 @@
1
+ class Prawn::SVG::Elements::Ignored < Prawn::SVG::Elements::Base
2
+ def parse
3
+ raise SkipElementQuietly
4
+ end
5
+ end
@@ -0,0 +1,177 @@
1
+ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
2
+ class FakeIO
3
+ def initialize(data)
4
+ @data = data
5
+ end
6
+
7
+ def read
8
+ @data
9
+ end
10
+
11
+ def rewind; end
12
+ end
13
+
14
+ ImageData = Struct.new(:dimensions, :document)
15
+
16
+ def parse
17
+ require_attributes 'width', 'height'
18
+
19
+ raise SkipElementQuietly if state.computed_properties.display == 'none'
20
+
21
+ @url = href_attribute
22
+ raise SkipElementError, 'image tag must have an href or xlink:href' if @url.nil?
23
+
24
+ @url, @fragment = extract_fragment(@url)
25
+
26
+ x = x(attributes['x'] || 0)
27
+ y = y(attributes['y'] || 0)
28
+ width = x_pixels(attributes['width'])
29
+ height = y_pixels(attributes['height'])
30
+ preserve_aspect_ratio = attributes['preserveAspectRatio']
31
+
32
+ raise SkipElementQuietly if width.zero? || height.zero?
33
+
34
+ require_positive_value width, height
35
+
36
+ @image = begin
37
+ @document.url_loader.load(@url)
38
+ rescue Prawn::SVG::UrlLoader::Error => e
39
+ raise SkipElementError, "Error retrieving URL #{@url}: #{e.message}"
40
+ end
41
+
42
+ @image_data = process_image(@image, width, height, preserve_aspect_ratio)
43
+
44
+ @aspect = Prawn::SVG::Calculators::AspectRatio.new(preserve_aspect_ratio, [width, height], @image_data.dimensions)
45
+
46
+ @clip_x = x
47
+ @clip_y = y
48
+ @clip_width = width
49
+ @clip_height = height
50
+
51
+ @width = @aspect.width
52
+ @height = @aspect.height
53
+ @x = x + @aspect.x
54
+ @y = y - @aspect.y
55
+ end
56
+
57
+ def apply
58
+ if @aspect.slice?
59
+ add_call 'save'
60
+ add_call 'rectangle', [@clip_x, @clip_y], @clip_width, @clip_height
61
+ add_call 'clip'
62
+ end
63
+
64
+ if (document = @image_data.document)
65
+ add_call_and_enter 'translate', @x, @y
66
+ add_call 'svg:render_sub_document', document
67
+ else
68
+ options = { width: @width, height: @height, at: [@x, @y] }
69
+
70
+ add_call 'image', FakeIO.new(@image), options
71
+ end
72
+
73
+ add_call 'restore' if @aspect.slice?
74
+ end
75
+
76
+ def bounding_box
77
+ [@x, @y, @x + @width, @y - @height]
78
+ end
79
+
80
+ protected
81
+
82
+ def process_image(data, width, height, preserve_aspect_ratio)
83
+ if (handler = find_image_handler(data))
84
+ image = handler.new(data)
85
+ ImageData.new([image.width.to_f, image.height.to_f], nil)
86
+
87
+ elsif potentially_svg?(data)
88
+ attribute_overrides = {}
89
+ attribute_overrides['preserveAspectRatio'] = preserve_aspect_ratio if preserve_aspect_ratio
90
+
91
+ if @fragment
92
+ view_attrs = resolve_svg_fragment(data, @fragment)
93
+ attribute_overrides.merge!(view_attrs) if view_attrs
94
+ end
95
+
96
+ has_web_loader = @document.url_loader.loaders.any? { |l| l.is_a?(Prawn::SVG::Loaders::Web) }
97
+ document = Prawn::SVG::Document.new(
98
+ data, [width, height], { width: width, height: height, enable_web_requests: has_web_loader },
99
+ attribute_overrides: attribute_overrides
100
+ )
101
+
102
+ dimensions = [document.sizing.output_width, document.sizing.output_height]
103
+ ImageData.new(dimensions, document)
104
+
105
+ else
106
+ raise_invalid_image_type
107
+ end
108
+ rescue Prawn::SVG::Document::InvalidSVGData
109
+ raise_invalid_image_type
110
+ end
111
+
112
+ def extract_fragment(url)
113
+ return [url, nil] if url.start_with?('data:')
114
+
115
+ if url.include?('#')
116
+ index = url.rindex('#')
117
+ [url[0...index], url[(index + 1)..]]
118
+ else
119
+ [url, nil]
120
+ end
121
+ end
122
+
123
+ def resolve_svg_fragment(data, fragment)
124
+ if fragment.start_with?('svgView(') && fragment.end_with?(')')
125
+ parse_svg_view_specification(fragment)
126
+ else
127
+ find_view_element_attributes(data, fragment)
128
+ end
129
+ end
130
+
131
+ SVG_VIEW_PARAM_REGEXP = /(\w+)\(([^)]*)\)/.freeze
132
+
133
+ def parse_svg_view_specification(fragment)
134
+ spec = fragment[8..-2]
135
+ attrs = {}
136
+
137
+ spec.split(';').each do |part|
138
+ next unless (match = part.strip.match(SVG_VIEW_PARAM_REGEXP))
139
+
140
+ case match[1]
141
+ when 'viewBox' then attrs['viewBox'] = match[2]
142
+ when 'preserveAspectRatio' then attrs['preserveAspectRatio'] = match[2]
143
+ end
144
+ end
145
+
146
+ attrs.empty? ? nil : attrs
147
+ end
148
+
149
+ def find_view_element_attributes(data, fragment)
150
+ root = REXML::Document.new(data).root
151
+ return unless root
152
+
153
+ element = REXML::XPath.match(root, %(//*[@id="#{fragment.gsub('"', '\\"')}"])).first
154
+ return unless element&.name == 'view'
155
+
156
+ attrs = {}
157
+ attrs['viewBox'] = element.attributes['viewBox'] if element.attributes['viewBox']
158
+ attrs['preserveAspectRatio'] = element.attributes['preserveAspectRatio'] if element.attributes['preserveAspectRatio']
159
+ attrs.empty? ? nil : attrs
160
+ rescue REXML::ParseException
161
+ nil
162
+ end
163
+
164
+ def find_image_handler(data)
165
+ Prawn.image_handler.find(data)
166
+ rescue StandardError
167
+ nil
168
+ end
169
+
170
+ def potentially_svg?(data)
171
+ data.include?('<svg')
172
+ end
173
+
174
+ def raise_invalid_image_type
175
+ raise SkipElementError, 'Unsupported image type supplied to image tag'
176
+ end
177
+ end
@@ -0,0 +1,27 @@
1
+ class Prawn::SVG::Elements::Line < Prawn::SVG::Elements::Base
2
+ include Prawn::SVG::Pathable
3
+
4
+ def parse
5
+ # Lines are one dimensional, so cannot be filled.
6
+ computed_properties.fill = Prawn::SVG::Paint.none
7
+
8
+ @x1 = x_pixels(attributes['x1'] || 0)
9
+ @y1 = y_pixels(attributes['y1'] || 0)
10
+ @x2 = x_pixels(attributes['x2'] || 0)
11
+ @y2 = y_pixels(attributes['y2'] || 0)
12
+ end
13
+
14
+ def apply
15
+ apply_commands
16
+ apply_markers
17
+ end
18
+
19
+ protected
20
+
21
+ def commands
22
+ @commands ||= [
23
+ Prawn::SVG::Pathable::Move.new([@x1, @y1]),
24
+ Prawn::SVG::Pathable::Line.new([@x2, @y2])
25
+ ]
26
+ end
27
+ end
@@ -0,0 +1,72 @@
1
+ class Prawn::SVG::Elements::Marker < Prawn::SVG::Elements::Base
2
+ def parse
3
+ set_display_none
4
+ end
5
+
6
+ def container?
7
+ true
8
+ end
9
+
10
+ def apply_marker(element, point: nil, angle: 0)
11
+ sizing = Prawn::SVG::Calculators::DocumentSizing.new([0, 0], attributes)
12
+ sizing.document_width = attributes['markerWidth'] || 3
13
+ sizing.document_height = attributes['markerHeight'] || 3
14
+ sizing.calculate
15
+
16
+ if sizing.invalid?
17
+ document.warnings << '<marker> cannot be rendered due to invalid sizing information'
18
+ return
19
+ end
20
+
21
+ element.new_call_context_from_base do
22
+ element.add_call 'save'
23
+
24
+ # LATER : these will probably change when we separate out properties from attributes
25
+ element.parse_transform_attribute_and_call
26
+ element.parse_opacity_attributes_and_call
27
+ element.parse_clip_path_attribute_and_call
28
+
29
+ element.add_call 'transformation_matrix', 1, 0, 0, 1, point[0], -point[1]
30
+
31
+ if attributes['orient'] != 'auto'
32
+ angle = attributes['orient'].to_f # defaults to 0 if not specified
33
+ end
34
+
35
+ element.push_call_position
36
+ element.add_call_and_enter 'rotate', -angle, origin: [0, y('0')] if angle != 0
37
+
38
+ if attributes['markerUnits'] != 'userSpaceOnUse'
39
+ scale = element.state.stroke_width
40
+ element.add_call 'transformation_matrix', scale, 0, 0, scale, 0, 0
41
+ end
42
+
43
+ ref_x = x_pixels(attributes['refX']) || 0
44
+ ref_y = y_pixels(attributes['refY']) || 0
45
+
46
+ element.add_call 'transformation_matrix', 1, 0, 0, 1, -ref_x * sizing.x_scale, ref_y * sizing.y_scale
47
+
48
+ if overflow_hidden?
49
+ point = [sizing.x_offset * sizing.x_scale, y(sizing.y_offset * sizing.y_scale)]
50
+ element.add_call 'rectangle', point, sizing.output_width, sizing.output_height
51
+ element.add_call 'clip'
52
+ end
53
+
54
+ element.add_call 'transformation_matrix', sizing.x_scale, 0, 0, sizing.y_scale, 0, 0
55
+
56
+ new_state = state.dup
57
+ new_state.computed_properties = computed_properties.dup
58
+ new_state.last_fill_opacity = element.state.last_fill_opacity
59
+ new_state.last_stroke_opacity = element.state.last_stroke_opacity
60
+
61
+ container = Prawn::SVG::Elements::Container.new(document, nil, [], new_state)
62
+ container.properties.compute_properties(new_state.computed_properties)
63
+ container.parse_and_apply
64
+ container.add_calls_from_element(self)
65
+
66
+ element.add_calls_from_element(container)
67
+
68
+ element.pop_call_position
69
+ element.add_call 'restore'
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,65 @@
1
+ class Prawn::SVG::Elements::Mask < Prawn::SVG::Elements::Base
2
+ def parse
3
+ set_display_none
4
+ end
5
+
6
+ def container?
7
+ true
8
+ end
9
+
10
+ def build_mask_calls(element)
11
+ bbox = element.bounding_box
12
+ mask_units = attributes['maskUnits'] || 'objectBoundingBox'
13
+ content_units = attributes['maskContentUnits'] || 'userSpaceOnUse'
14
+
15
+ if content_units == 'objectBoundingBox' && bbox.nil?
16
+ document.warnings << 'mask with maskContentUnits="objectBoundingBox" requires element to have a bounding box'
17
+ return []
18
+ end
19
+
20
+ calls = []
21
+
22
+ calls.concat(build_clip_calls(bbox, mask_units)) if bbox || mask_units == 'userSpaceOnUse'
23
+
24
+ if content_units == 'objectBoundingBox'
25
+ calls.concat(build_object_bounding_box_calls(bbox))
26
+ else
27
+ calls.concat(duplicate_calls(base_calls))
28
+ end
29
+
30
+ calls
31
+ end
32
+
33
+ private
34
+
35
+ def build_clip_calls(bbox, mask_units)
36
+ if mask_units == 'objectBoundingBox'
37
+ mask_x = Float(attributes['x'] || '-0.1')
38
+ mask_y = Float(attributes['y'] || '-0.1')
39
+ mask_w = Float(attributes['width'] || '1.2')
40
+ mask_h = Float(attributes['height'] || '1.2')
41
+
42
+ bbox_left = bbox[0]
43
+ bbox_top = bbox[1]
44
+ bbox_right = bbox[2]
45
+ bbox_bottom = bbox[3]
46
+ bbox_w = bbox_right - bbox_left
47
+ bbox_h = bbox_top - bbox_bottom
48
+
49
+ clip_left = bbox_left + (mask_x * bbox_w)
50
+ clip_top = bbox_top - (mask_y * bbox_h)
51
+ clip_width = mask_w * bbox_w
52
+ clip_height = mask_h * bbox_h
53
+ else
54
+ clip_left = x_pixels(attributes['x'] || '-10%')
55
+ clip_top = y(attributes['y'] || '-10%')
56
+ clip_width = x_pixels(attributes['width'] || '120%')
57
+ clip_height = y_pixels(attributes['height'] || '120%')
58
+ end
59
+
60
+ [
61
+ ['rectangle', [[clip_left, clip_top], clip_width, clip_height], {}, []],
62
+ ['clip', [], {}, []]
63
+ ]
64
+ end
65
+ end