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,99 @@
1
+ module Prawn::SVG
2
+ class Elements::Text < Elements::DirectRenderBase
3
+ Cursor = Struct.new(:x, :y)
4
+
5
+ def parse
6
+ @root_component = Elements::TextComponent.new(document, source, [], state.dup)
7
+ @root_component.process
8
+
9
+ reintroduce_trailing_and_leading_whitespace if @root_component.children
10
+ end
11
+
12
+ def render(prawn, renderer)
13
+ return unless @root_component.children
14
+
15
+ origin_x = @root_component.x_values.first
16
+ origin_y = @root_component.y_values.first
17
+
18
+ @root_component.lay_out(prawn)
19
+
20
+ translate_x =
21
+ case @root_component.computed_properties.text_anchor
22
+ when "middle"
23
+ -@root_component.calculated_width / 2.0
24
+ when "end"
25
+ -@root_component.calculated_width
26
+ end
27
+
28
+ cursor = Cursor.new(0, document.sizing.output_height)
29
+
30
+ if vertical_writing_mode?
31
+ render_vertical(prawn, renderer, cursor, translate_x, origin_x, origin_y)
32
+ else
33
+ @root_component.render_component(prawn, renderer, cursor, translate_x)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def vertical_writing_mode?
40
+ wm = @root_component.computed_properties.writing_mode
41
+ ["vertical-rl", "vertical-lr"].include?(wm)
42
+ end
43
+
44
+ def render_vertical(prawn, renderer, cursor, translate_x, origin_x, origin_y)
45
+ origin_x ||= 0
46
+ origin_y ||= document.sizing.output_height
47
+
48
+ prawn.rotate(-90, origin: [origin_x, origin_y]) do
49
+ @root_component.render_component(prawn, renderer, cursor, translate_x)
50
+ end
51
+ end
52
+
53
+ def reintroduce_trailing_and_leading_whitespace
54
+ text_nodes = []
55
+ build_text_node_queue(text_nodes, @root_component)
56
+
57
+ remove_whitespace_only_text_nodes_and_start_and_end(text_nodes)
58
+ remove_text_nodes_that_are_completely_empty(text_nodes)
59
+ apportion_leading_and_trailing_spaces(text_nodes)
60
+ end
61
+
62
+ def build_text_node_queue(queue, component)
63
+ return unless component.children
64
+
65
+ component.children.each do |element|
66
+ case element
67
+ when Elements::TextNode
68
+ queue << element
69
+ else
70
+ build_text_node_queue(queue, element)
71
+ end
72
+ end
73
+ end
74
+
75
+ def remove_whitespace_only_text_nodes_and_start_and_end(text_nodes)
76
+ text_nodes.pop while text_nodes.last && text_nodes.last.text.empty?
77
+ text_nodes.shift while text_nodes.first && text_nodes.first.text.empty?
78
+ end
79
+
80
+ def remove_text_nodes_that_are_completely_empty(text_nodes)
81
+ text_nodes.reject! do |text_node|
82
+ text_node.text.empty? && !text_node.trailing_space? && !text_node.leading_space?
83
+ end
84
+ end
85
+
86
+ def apportion_leading_and_trailing_spaces(text_nodes)
87
+ text_nodes.each_cons(2) do |a, b|
88
+ if a.text.empty?
89
+ # Empty strings can only get a leading space from the previous non-empty text,
90
+ # and never get a trailing space
91
+ elsif a.trailing_space?
92
+ a.text += " "
93
+ elsif b.leading_space?
94
+ b.text = " #{b.text}"
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,352 @@
1
+ module Prawn::SVG
2
+ class Elements::TextComponent < Elements::DirectRenderBase
3
+ attr_reader :children, :parent_component
4
+ attr_reader :x_values, :y_values, :dx, :dy, :rotation, :text_length, :length_adjust
5
+ attr_reader :font, :fallback_fonts
6
+ attr_reader :inside_clip_path
7
+
8
+ def initialize(document, source, _calls, state, parent_component = nil)
9
+ if parent_component.nil? && source.name != "text"
10
+ raise SkipElementError, "attempted to <use> a component inside a text element, this is not supported"
11
+ end
12
+
13
+ super(document, source, [], state)
14
+ @parent_component = parent_component
15
+ end
16
+
17
+ def parse
18
+ @inside_clip_path = state.inside_clip_path
19
+
20
+ @x_values = parse_wsp("x").map { |n| x(n) }
21
+ @y_values = parse_wsp("y").map { |n| y(n) }
22
+ @dx = parse_wsp("dx").map { |n| x_pixels(n) }
23
+ @dy = parse_wsp("dy").map { |n| y_pixels(n) }
24
+ @rotation = parse_wsp("rotate").map(&:to_f)
25
+ @text_length = normalize_length(attributes["textLength"])
26
+ @length_adjust = attributes["lengthAdjust"]
27
+
28
+ @font, @fallback_fonts = select_fonts
29
+
30
+ @children = svg_text_children.flat_map do |child|
31
+ if child.node_type == :text
32
+ build_text_node(child)
33
+ else
34
+ case child.name
35
+ when "tspan", "tref"
36
+ build_child(child)
37
+ when "textPath"
38
+ build_text_path(child)
39
+ else
40
+ warnings << "Unknown tag '#{child.name}' inside text tag; ignoring"
41
+ []
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def lay_out(prawn)
48
+ with_svg_fonts(prawn) do
49
+ @children.each do |child|
50
+ child.lay_out(prawn)
51
+ end
52
+ end
53
+
54
+ if @text_length
55
+ flexible_width, fixed_width = total_flexible_and_fixed_width
56
+
57
+ if flexible_width.positive?
58
+ target_width = [@text_length - fixed_width, 0].max
59
+ factor = target_width / flexible_width
60
+ apply_factor_to_base_width(factor)
61
+ end
62
+ end
63
+ end
64
+
65
+ def render_component(prawn, renderer, cursor, translate_x = nil, accumulated_baseline_shift = 0)
66
+ raise SkipElementQuietly if computed_properties.display == "none"
67
+
68
+ add_yield_call do
69
+ prawn.translate(translate_x, 0) if translate_x
70
+
71
+ size = computed_properties.numeric_font_size
72
+
73
+ y_offset = dominant_baseline_offset(prawn, size || prawn.font_size) || 0
74
+
75
+ with_svg_fonts(prawn) do
76
+ total_baseline_shift = accumulated_baseline_shift + baseline_shift_offset(prawn, size)
77
+ y_offset += total_baseline_shift
78
+
79
+ children.each do |child|
80
+ case child
81
+ when Elements::TextNode
82
+ child.render(prawn, size, cursor, y_offset)
83
+ when self.class
84
+ prawn.save_graphics_state
85
+ child.render_component(prawn, renderer, cursor, nil, total_baseline_shift)
86
+ prawn.restore_graphics_state
87
+ else
88
+ raise
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ renderer.render_calls(prawn, base_calls)
95
+ end
96
+
97
+ def calculated_width
98
+ children.reduce(0) { |total, child| total + child.calculated_width }
99
+ end
100
+
101
+ def current_length_adjust_is_scaling?
102
+ if @text_length
103
+ @length_adjust == "spacingAndGlyphs"
104
+ elsif parent_component
105
+ parent_component.current_length_adjust_is_scaling?
106
+ else
107
+ false
108
+ end
109
+ end
110
+
111
+ # Resolves the effective kerning setting from the SVG 1.1 kerning property and the
112
+ # SVG 2 font-kerning property. kerning takes precedence when non-default; otherwise
113
+ # font-kerning is used.
114
+ def kerning_enabled?
115
+ if computed_properties.kerning == "auto"
116
+ computed_properties.font_kerning != "none"
117
+ else
118
+ false
119
+ end
120
+ end
121
+
122
+ def kerning_pixels
123
+ if computed_properties.kerning == "auto"
124
+ nil
125
+ else
126
+ x_pixels(computed_properties.kerning)
127
+ end
128
+ end
129
+
130
+ def letter_spacing_pixels
131
+ if computed_properties.letter_spacing == "normal"
132
+ nil
133
+ else
134
+ x_pixels(computed_properties.letter_spacing)
135
+ end
136
+ end
137
+
138
+ def word_spacing_pixels
139
+ if computed_properties.word_spacing == "normal"
140
+ nil
141
+ else
142
+ x_pixels(computed_properties.word_spacing)
143
+ end
144
+ end
145
+
146
+ protected
147
+
148
+ def build_text_node(child)
149
+ if state.preserve_space
150
+ text = child.value.tr("\n\t", " ")
151
+ else
152
+ text = child.value.tr("\n", "").tr("\t", " ")
153
+ leading = text[0] == " "
154
+ trailing = text[-1] == " "
155
+ text = text.strip.gsub(/ {2,}/, " ")
156
+ end
157
+
158
+ Elements::TextNode.new(self, text, leading, trailing)
159
+ end
160
+
161
+ def build_child(child)
162
+ component = self.class.new(document, child, [], state.dup, self)
163
+ component.process
164
+ component
165
+ end
166
+
167
+ def build_text_path(child)
168
+ component = Elements::TextPath.new(document, child, [], state.dup, self)
169
+ component.process
170
+ return [] unless component.children
171
+
172
+ component
173
+ end
174
+
175
+ def svg_text_children
176
+ text_children.select do |child|
177
+ child.node_type == :text || (
178
+ child.node_type == :element &&
179
+ [SVG_NAMESPACE, ""].include?(child.namespace)
180
+
181
+ )
182
+ end
183
+ end
184
+
185
+ def text_children
186
+ if name == "tref"
187
+ reference = find_referenced_element
188
+ reference ? reference.source.children : []
189
+ else
190
+ source.children
191
+ end
192
+ end
193
+
194
+ def find_referenced_element
195
+ href = href_attribute
196
+
197
+ if href && href[0..0] == "#"
198
+ element = document.elements_by_id[href[1..]] # rubocop:disable Performance/ArraySemiInfiniteRangeSlice -- String
199
+ element if element.name == "text"
200
+ end
201
+ end
202
+
203
+ def select_fonts
204
+ font_style = :italic if computed_properties.font_style == "italic"
205
+ font_weight = computed_properties.font_weight
206
+ font_stretch = computed_properties.font_stretch
207
+ fonts = []
208
+
209
+ font_family_names.each do |name|
210
+ font = document.font_registry.load(name, font_weight, font_style, font_stretch)
211
+ next unless font
212
+ next if fonts.any? { |existing| existing.name == font.name && existing.subfamily == font.subfamily }
213
+
214
+ fonts << font
215
+ end
216
+
217
+ font_names = fonts.map(&:name)
218
+ font_names.uniq!
219
+ _, *fallback_font_names = font_names
220
+
221
+ return [fonts.first, fallback_font_names] if fonts.any?
222
+
223
+ warnings << "Font family '#{computed_properties.font_family}' style '#{computed_properties.font_style}' is not a known font, and the fallback font could not be found."
224
+ []
225
+ end
226
+
227
+ def with_svg_fonts(prawn)
228
+ prawn.save_font do
229
+ if font
230
+ prawn.font(font.name, style: font.subfamily)
231
+ prawn.fallback_fonts(fallback_fonts)
232
+ end
233
+
234
+ yield
235
+ end
236
+ end
237
+
238
+ def font_family_names
239
+ names = CSS::FontFamilyParser.parse(computed_properties.font_family.to_s)
240
+ names << document.fallback_font_name if document.fallback_font_name
241
+ names.filter_map do |family_name|
242
+ name = family_name.strip
243
+ name unless name.empty?
244
+ end
245
+ end
246
+
247
+ def total_flexible_and_fixed_width
248
+ flexible = fixed = 0
249
+ @children.each do |child|
250
+ child.total_flexible_and_fixed_width.tap do |a, b|
251
+ flexible += a
252
+ fixed += b
253
+ end
254
+ end
255
+ [flexible, fixed]
256
+ end
257
+
258
+ def apply_factor_to_base_width(factor)
259
+ @children.each do |child|
260
+ if child.is_a?(Elements::TextNode)
261
+ child.chunks.reject(&:fixed_width).each do |chunk|
262
+ chunk.fixed_width = chunk.base_width * factor
263
+ end
264
+ elsif child.is_a?(self.class)
265
+ child.apply_factor_to_base_width(factor)
266
+ else
267
+ raise
268
+ end
269
+ end
270
+ end
271
+
272
+ # overridden from Base, we don't want to call fill/stroke as draw_text does this for us
273
+ def apply_drawing_call
274
+ end
275
+
276
+ # overridden from Base, transforms can't be applied to tspan elements
277
+ def transformable?
278
+ source.name != "tspan"
279
+ end
280
+
281
+ # overridden from Base, we want the id to point to the Text element
282
+ def add_to_elements_by_id?
283
+ source.name != "text"
284
+ end
285
+
286
+ def dominant_baseline_offset(prawn, size)
287
+ baseline = effective_baseline
288
+ return nil if baseline.nil?
289
+
290
+ case baseline
291
+ when "middle"
292
+ -FontMetrics.x_height_in_points(prawn, size) / 2.0
293
+ when "mathematical"
294
+ -((prawn.font.ascender + prawn.font.descender) / prawn.font_size) * size / 2.0
295
+ when "central"
296
+ -((prawn.font.ascender - prawn.font.descender) / prawn.font_size) * size / 2.0
297
+ when "hanging"
298
+ -(prawn.font.ascender / prawn.font_size) * size
299
+ when "text-before-edge", "before-edge"
300
+ (-(prawn.font.ascender / prawn.font_size) * size) - half_leading(prawn, size)
301
+ when "text-after-edge", "after-edge", "ideographic"
302
+ ((prawn.font.descender / prawn.font_size) * size) + half_leading(prawn, size)
303
+ end
304
+ end
305
+
306
+ def effective_baseline
307
+ ab = computed_properties.alignment_baseline
308
+ if ab && ab != "auto" && ab != "baseline"
309
+ ab
310
+ else
311
+ db = computed_properties.dominant_baseline
312
+ db unless ["auto", "alphabetic"].include?(db)
313
+ end
314
+ end
315
+
316
+ def baseline_shift_offset(prawn, font_size)
317
+ value = computed_properties.baseline_shift
318
+ return 0 if value.nil? || value == "baseline"
319
+
320
+ case value
321
+ when "super"
322
+ sub_super_offset(prawn, font_size)
323
+ when "sub"
324
+ -sub_super_offset(prawn, font_size)
325
+ when Percentage
326
+ parent_font_size = parent_component&.computed_properties&.numeric_font_size || font_size
327
+ value.to_factor * parent_font_size
328
+ when Length
329
+ value.to_pixels(nil, font_size)
330
+ else
331
+ 0
332
+ end
333
+ end
334
+
335
+ def half_leading(prawn, size)
336
+ (size - ((prawn.font.ascender + prawn.font.descender) / prawn.font_size * size)) / 2.0
337
+ end
338
+
339
+ def sub_super_offset(prawn, font_size)
340
+ ascender_ratio = prawn.font.ascender / prawn.font_size
341
+ ascender_ratio * font_size / 1.2
342
+ end
343
+
344
+ def normalize_length(length)
345
+ x_pixels(length) if length&.match(/\d/)
346
+ end
347
+
348
+ def parse_wsp(name)
349
+ (attributes[name] || "").split(COMMA_WSP_REGEXP)
350
+ end
351
+ end
352
+ end