hexapdf 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/CONTRIBUTERS +1 -1
  4. data/README.md +5 -5
  5. data/VERSION +1 -1
  6. data/examples/emoji-smile.png +0 -0
  7. data/examples/emoji-wink.png +0 -0
  8. data/examples/graphics.rb +9 -8
  9. data/examples/standard_pdf_fonts.rb +2 -1
  10. data/examples/text_box_alignment.rb +47 -0
  11. data/examples/text_box_inline_boxes.rb +56 -0
  12. data/examples/text_box_line_wrapping.rb +57 -0
  13. data/examples/text_box_shapes.rb +166 -0
  14. data/examples/text_box_styling.rb +72 -0
  15. data/examples/truetype.rb +3 -4
  16. data/lib/hexapdf/cli/optimize.rb +2 -2
  17. data/lib/hexapdf/configuration.rb +8 -6
  18. data/lib/hexapdf/content/canvas.rb +8 -5
  19. data/lib/hexapdf/content/parser.rb +3 -2
  20. data/lib/hexapdf/content/processor.rb +14 -3
  21. data/lib/hexapdf/document.rb +1 -0
  22. data/lib/hexapdf/document/fonts.rb +2 -1
  23. data/lib/hexapdf/document/pages.rb +23 -0
  24. data/lib/hexapdf/font/invalid_glyph.rb +78 -0
  25. data/lib/hexapdf/font/true_type/font.rb +14 -3
  26. data/lib/hexapdf/font/true_type/table.rb +1 -0
  27. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  28. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -0
  29. data/lib/hexapdf/font/true_type/table/glyf.rb +4 -0
  30. data/lib/hexapdf/font/true_type/table/kern.rb +170 -0
  31. data/lib/hexapdf/font/true_type/table/post.rb +5 -1
  32. data/lib/hexapdf/font/true_type_wrapper.rb +71 -24
  33. data/lib/hexapdf/font/type1/afm_parser.rb +3 -2
  34. data/lib/hexapdf/font/type1/character_metrics.rb +0 -9
  35. data/lib/hexapdf/font/type1/font.rb +11 -0
  36. data/lib/hexapdf/font/type1/font_metrics.rb +6 -1
  37. data/lib/hexapdf/font/type1_wrapper.rb +51 -7
  38. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  39. data/lib/hexapdf/layout.rb +51 -0
  40. data/lib/hexapdf/layout/inline_box.rb +95 -0
  41. data/lib/hexapdf/layout/line_fragment.rb +333 -0
  42. data/lib/hexapdf/layout/numeric_refinements.rb +56 -0
  43. data/lib/hexapdf/layout/style.rb +365 -0
  44. data/lib/hexapdf/layout/text_box.rb +727 -0
  45. data/lib/hexapdf/layout/text_fragment.rb +206 -0
  46. data/lib/hexapdf/layout/text_shaper.rb +155 -0
  47. data/lib/hexapdf/task.rb +0 -1
  48. data/lib/hexapdf/task/dereference.rb +1 -1
  49. data/lib/hexapdf/tokenizer.rb +3 -2
  50. data/lib/hexapdf/type/font_descriptor.rb +2 -1
  51. data/lib/hexapdf/type/font_type0.rb +3 -1
  52. data/lib/hexapdf/type/form.rb +12 -4
  53. data/lib/hexapdf/version.rb +1 -1
  54. data/test/hexapdf/common_tokenizer_tests.rb +7 -0
  55. data/test/hexapdf/content/common.rb +8 -0
  56. data/test/hexapdf/content/test_canvas.rb +10 -22
  57. data/test/hexapdf/content/test_processor.rb +4 -1
  58. data/test/hexapdf/document/test_pages.rb +16 -0
  59. data/test/hexapdf/font/test_invalid_glyph.rb +34 -0
  60. data/test/hexapdf/font/test_true_type_wrapper.rb +25 -11
  61. data/test/hexapdf/font/test_type1_wrapper.rb +26 -10
  62. data/test/hexapdf/font/true_type/table/common.rb +27 -0
  63. data/test/hexapdf/font/true_type/table/test_cmap.rb +14 -20
  64. data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +7 -0
  65. data/test/hexapdf/font/true_type/table/test_glyf.rb +8 -6
  66. data/test/hexapdf/font/true_type/table/test_head.rb +9 -13
  67. data/test/hexapdf/font/true_type/table/test_hhea.rb +16 -23
  68. data/test/hexapdf/font/true_type/table/test_hmtx.rb +4 -7
  69. data/test/hexapdf/font/true_type/table/test_kern.rb +61 -0
  70. data/test/hexapdf/font/true_type/table/test_loca.rb +7 -13
  71. data/test/hexapdf/font/true_type/table/test_maxp.rb +4 -9
  72. data/test/hexapdf/font/true_type/table/test_name.rb +14 -17
  73. data/test/hexapdf/font/true_type/table/test_os2.rb +3 -5
  74. data/test/hexapdf/font/true_type/table/test_post.rb +21 -19
  75. data/test/hexapdf/font/true_type/test_font.rb +4 -0
  76. data/test/hexapdf/font/type1/common.rb +6 -0
  77. data/test/hexapdf/font/type1/test_afm_parser.rb +9 -0
  78. data/test/hexapdf/font/type1/test_font.rb +6 -0
  79. data/test/hexapdf/layout/test_inline_box.rb +40 -0
  80. data/test/hexapdf/layout/test_line_fragment.rb +206 -0
  81. data/test/hexapdf/layout/test_style.rb +143 -0
  82. data/test/hexapdf/layout/test_text_box.rb +640 -0
  83. data/test/hexapdf/layout/test_text_fragment.rb +208 -0
  84. data/test/hexapdf/layout/test_text_shaper.rb +64 -0
  85. data/test/hexapdf/task/test_dereference.rb +1 -0
  86. data/test/hexapdf/test_writer.rb +2 -2
  87. data/test/hexapdf/type/test_font_descriptor.rb +4 -2
  88. data/test/hexapdf/type/test_font_type0.rb +7 -0
  89. data/test/hexapdf/type/test_form.rb +12 -0
  90. metadata +29 -2
@@ -88,7 +88,7 @@ module HexaPDF
88
88
  return nil if name.nil?
89
89
 
90
90
  file = File.join(HexaPDF.data_dir, 'afm', "#{name}.afm")
91
- font = HexaPDF::Font::Type1::Font.from_afm(file)
91
+ font = (@afm_font_cache ||= {})[file] ||= HexaPDF::Font::Type1::Font.from_afm(file)
92
92
  HexaPDF::Font::Type1Wrapper.new(document, font, custom_encoding: custom_encoding)
93
93
  end
94
94
 
@@ -0,0 +1,51 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #++
33
+
34
+ module HexaPDF
35
+
36
+ # == Overview
37
+ #
38
+ # The Layout module contains advanced text and layouting facilities that are built on top of the
39
+ # standard PDF functionality provided by the Content module.
40
+ module Layout
41
+
42
+ autoload(:Style, 'hexapdf/layout/style')
43
+ autoload(:TextFragment, 'hexapdf/layout/text_fragment')
44
+ autoload(:InlineBox, 'hexapdf/layout/inline_box')
45
+ autoload(:LineFragment, 'hexapdf/layout/line_fragment')
46
+ autoload(:TextShaper, 'hexapdf/layout/text_shaper')
47
+ autoload(:TextBox, 'hexapdf/layout/text_box')
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,95 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #++
33
+
34
+ module HexaPDF
35
+ module Layout
36
+
37
+ # An InlineBox can be used as an item for a LineFragment so that inline graphics are possible.
38
+ # The box *must* have a fixed size!
39
+ class InlineBox
40
+
41
+ # The width of the box.
42
+ attr_reader :width
43
+
44
+ # The height of the box.
45
+ attr_reader :height
46
+
47
+ # The vertical alignment of the box.
48
+ #
49
+ # Can be any supported value except :text - see LineFragment for all possible values.
50
+ attr_reader :valign
51
+
52
+ # :call-seq:
53
+ # InlineBox.new(width, height, valign: :baseline) {|box, canvas| block} -> inline_box
54
+ #
55
+ # Creates a new InlineBox object that uses the provided block when it is asked to draw itself
56
+ # on a canvas (see #draw).
57
+ #
58
+ # Since the final location of the box is not known beforehand, the drawing operations inside
59
+ # the block should draw inside the rectangle (0, 0, width, height).
60
+ #
61
+ # The +valign+ argument can be used to specify the vertical alignment of the box relative to
62
+ # other items in the LineFragment - see #valign and LineFragment.
63
+ def initialize(width, height, valign: :baseline, &block)
64
+ @width = width
65
+ @height = height
66
+ @valign = valign
67
+ @draw_block = block
68
+ end
69
+
70
+ # :call-seq:
71
+ # box.draw(canvas, x, y) -> block_result
72
+ #
73
+ # Draws the contents of the box onto the canvas at the position (x, y), and returns the result
74
+ # of the drawing block (see #initialize).
75
+ #
76
+ # The coordinate system is translated so that the origin is at (x, y) during the drawing
77
+ # operations.
78
+ def draw(canvas, x, y)
79
+ canvas.translate(x, y) { @draw_block.call(self, canvas) }
80
+ end
81
+
82
+ # The minimum x-coordinate which is always 0.
83
+ def x_min
84
+ 0
85
+ end
86
+
87
+ # The maximum x-coordinate which is equivalent to the width of the box.
88
+ def x_max
89
+ width
90
+ end
91
+
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,333 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #++
33
+
34
+ require 'hexapdf/error'
35
+ require 'hexapdf/layout/text_fragment'
36
+
37
+ module HexaPDF
38
+ module Layout
39
+
40
+ # A LineFragment describes a line of text and can contain TextFragment objects or InlineBox
41
+ # objects.
42
+ #
43
+ # The items of a line fragment are aligned along the x-axis which coincides with the text
44
+ # baseline. The vertical alignment is determined by the value of the #valign method:
45
+ #
46
+ # :text_top::
47
+ # Align the top of the box with the top of the text of the LineFragment.
48
+ #
49
+ # :text_bottom::
50
+ # Align the bottom of the box with the bottom of the text of the LineFragment.
51
+ #
52
+ # :baseline::
53
+ # Align the bottom of the box with the baseline of the LineFragment.
54
+ #
55
+ # :top::
56
+ # Align the top of the box with the top of the LineFragment.
57
+ #
58
+ # :bottom::
59
+ # Align the bottom of the box with the bottom of the LineFragment.
60
+ #
61
+ # :text::
62
+ # This is a special alignment value for text fragment objects. The text fragment is aligned
63
+ # on the baseline and its minimum and maximum y-coordinates are used when calculating the
64
+ # line's #text_y_min and #text_y_max.
65
+ #
66
+ # This value may be used by other objects if they should be handled similar to text
67
+ # fragments, e.g. graphical representation of characters (think: emoji fonts).
68
+ #
69
+ # == Item Requirements
70
+ #
71
+ # Each item of a line fragment has to respond to the following methods:
72
+ #
73
+ # #x_min:: The minimum x-coordinate of the item.
74
+ # #x_max:: The maximum x-coordinate of the item.
75
+ # #width:: The width of the item.
76
+ # #valign:: The vertical alignment of the item (see above).
77
+ # #draw(canvas, x, y):: Should draw the item onto the canvas at the position (x, y).
78
+ #
79
+ # If an item has a vertical alignment of :text, it additionally has to respond to the following
80
+ # methods:
81
+ #
82
+ # #y_min:: The minimum y-coordinate of the item.
83
+ # #y_max:: The maximum y-coordinate of the item.
84
+ #
85
+ # Otherwise (i.e. a vertical alignment different from :text), the following method must be
86
+ # implemented:
87
+ #
88
+ # #height:: The height of the item.
89
+ class LineFragment
90
+
91
+ # Helper class for calculating the needed vertical dimensions of a line.
92
+ class HeightCalculator
93
+
94
+ # Creates a new calculator with the given initial items.
95
+ def initialize(items = [])
96
+ reset
97
+ items.each {|item| add(item)}
98
+ end
99
+
100
+ # Adds a new item to be considered when calculating the various dimensions.
101
+ def add(item)
102
+ case item.valign
103
+ when :text
104
+ @text_y_min = item.y_min if item.y_min < @text_y_min
105
+ @text_y_max = item.y_max if item.y_max > @text_y_max
106
+ when :baseline
107
+ @max_base_height = item.height if @max_base_height < item.height
108
+ when :top
109
+ @max_top_height = item.height if @max_top_height < item.height
110
+ when :text_top
111
+ @max_text_top_height = item.height if @max_text_top_height < item.height
112
+ when :bottom
113
+ @max_bottom_height = item.height if @max_bottom_height < item.height
114
+ when :text_bottom
115
+ @max_text_bottom_height = item.height if @max_text_bottom_height < item.height
116
+ else
117
+ raise HexaPDF::Error, "Unknown inline box alignment #{item.valign}"
118
+ end
119
+ self
120
+ end
121
+ alias_method :<<, :add
122
+
123
+ # Returns the result of the calculations, the array [y_min, y_max, text_y_min, text_y_max].
124
+ #
125
+ # See LineFragment for their meaning.
126
+ def result
127
+ y_min = [@text_y_max - @max_text_top_height, @text_y_min].min
128
+ y_max = [@text_y_min + @max_text_bottom_height, @max_base_height, @text_y_max].max
129
+ y_min = [y_max - @max_top_height, y_min].min
130
+ y_max = [y_min + @max_bottom_height, y_max].max
131
+
132
+ [y_min, y_max, @text_y_min, @text_y_max]
133
+ end
134
+
135
+ # Resets the calculation.
136
+ def reset
137
+ @text_y_min = 0
138
+ @text_y_max = 0
139
+ @max_base_height = 0
140
+ @max_top_height = 0
141
+ @max_text_top_height = 0
142
+ @max_bottom_height = 0
143
+ @max_text_bottom_height = 0
144
+ end
145
+
146
+ # Returns the height of the line as if +item+ was part of it but doesn't change the internal
147
+ # state.
148
+ def simulate_height(item)
149
+ text_y_min = @text_y_min
150
+ text_y_max = @text_y_max
151
+ max_base_height = @max_base_height
152
+ max_top_height = @max_top_height
153
+ max_text_top_height = @max_text_top_height
154
+ max_bottom_height = @max_bottom_height
155
+ max_text_bottom_height = @max_text_bottom_height
156
+ y_min, y_max, = add(item).result
157
+ y_max - y_min
158
+ ensure
159
+ @text_y_min = text_y_min
160
+ @text_y_max = text_y_max
161
+ @max_base_height = max_base_height
162
+ @max_top_height = max_top_height
163
+ @max_text_top_height = max_text_top_height
164
+ @max_bottom_height = max_bottom_height
165
+ @max_text_bottom_height = max_text_bottom_height
166
+ end
167
+
168
+ end
169
+
170
+ # The items: TextFragment and InlineBox objects
171
+ attr_accessor :items
172
+
173
+ # An optional horizontal offset that should be taken into account when positioning the line.
174
+ attr_accessor :x_offset
175
+
176
+ # An optional vertical offset that should be taken into account when positioning the line.
177
+ #
178
+ # For the first line in a paragraph this describes the offset from the top of the box to the
179
+ # top of the line. For all other lines it describes the offset from the previous baseline to
180
+ # the baseline of this line.
181
+ attr_accessor :y_offset
182
+
183
+ # Creates a new LineFragment object, adding all given items to it.
184
+ def initialize(items = [])
185
+ @items = []
186
+ items.each {|i| add(i)}
187
+ @x_offset = 0
188
+ @y_offset = 0
189
+ end
190
+
191
+ # Adds the given item at the end of the item list.
192
+ #
193
+ # If both the item and the last item in the item list are TextFragment objects and they have
194
+ # the same style, they are combined.
195
+ #
196
+ # Note: The cache is not cleared!
197
+ def add(item)
198
+ last = @items.last
199
+ if last.class == item.class && item.kind_of?(TextFragment) && last.style == item.style
200
+ if last.items.frozen?
201
+ @items[-1] = last = last.dup
202
+ last.items = last.items.dup
203
+ end
204
+ last.items[last.items.length, 0] = item.items
205
+ last.clear_cache
206
+ else
207
+ @items << item
208
+ end
209
+ self
210
+ end
211
+ alias :<< :add
212
+
213
+ # :call-seq:
214
+ # line_fragment.each {|item, x, y| block }
215
+ #
216
+ # Yields each item together with its horizontal offset from 0 and vertical offset from the
217
+ # baseline.
218
+ def each
219
+ x = 0
220
+ @items.each do |item|
221
+ y = case item.valign
222
+ when :text, :baseline then 0
223
+ when :top then y_max - item.height
224
+ when :text_top then text_y_max - item.height
225
+ when :text_bottom then text_y_min
226
+ when :bottom then y_min
227
+ else
228
+ raise HexaPDF::Error, "Unknown inline box alignment #{item.valign}"
229
+ end
230
+ yield(item, x, y)
231
+ x += item.width
232
+ end
233
+ end
234
+
235
+ # The minimum x-coordinate of the whole line.
236
+ def x_min
237
+ @items[0].x_min
238
+ end
239
+
240
+ # The maximum x-coordinate of the whole line.
241
+ def x_max
242
+ @x_max ||= width + (items[-1].x_max - items[-1].width)
243
+ end
244
+
245
+ # The minimum y-coordinate of any item of the line.
246
+ #
247
+ # It is always lower than or equal to zero.
248
+ def y_min
249
+ @y_min ||= calculate_y_dimensions[0]
250
+ end
251
+
252
+ # The minimum y-coordinate of any TextFragment item of the line.
253
+ def text_y_min
254
+ @text_y_min ||= calculate_y_dimensions[2]
255
+ end
256
+
257
+ # The maximum y-coordinate of any item of the line.
258
+ #
259
+ # It is always greater than or equal to zero.
260
+ def y_max
261
+ @y_max ||= calculate_y_dimensions[1]
262
+ end
263
+
264
+ # The maximum y-coordinate of any TextFragment item of the line.
265
+ def text_y_max
266
+ @text_y_max ||= calculate_y_dimensions[3]
267
+ end
268
+
269
+ # The width of the line fragment.
270
+ def width
271
+ @width ||= @items.sum(&:width)
272
+ end
273
+
274
+ # The height of the line fragment.
275
+ def height
276
+ y_max - y_min
277
+ end
278
+
279
+ # Specifies that this line should not be justified if line justification is used.
280
+ def ignore_justification!
281
+ @ignore_justification = true
282
+ end
283
+
284
+ # Returns +true+ if justification should be ignored for this line.
285
+ def ignore_justification?
286
+ defined?(@ignore_justification) && @ignore_justification
287
+ end
288
+
289
+ # :call-seq:
290
+ # line_fragment.clear_cache -> line_fragment
291
+ #
292
+ # Clears all cached values.
293
+ #
294
+ # This method needs to be called if the fragment's items are changed!
295
+ def clear_cache
296
+ @x_max = @y_min = @y_max = @text_y_min = @text_y_max = @width = nil
297
+ self
298
+ end
299
+
300
+ private
301
+
302
+ # :call-seq:
303
+ # line_fragment.calculate_y_dimensions -> [y_min, y_max, text_y_min, text_y_max]
304
+ #
305
+ # Calculates all y-values and returns them as array.
306
+ #
307
+ # The following algorithm is used for the calculations:
308
+ #
309
+ # 1. Calculate #text_y_min and #text_y_max by using only the items with valign :text.
310
+ #
311
+ # 2. Calculate the temporary #y_min by using either the maximum height of all items with
312
+ # valign :text_top subtraced from #text_y_max, or #text_y_min, whichever is smaller.
313
+ #
314
+ # For the temporary #y_max, use either the maximum height of all items with valign equal to
315
+ # :text_bottom added to #text_y_min, or the maximum height of all items with valign
316
+ # :baseline, or #text_y_max, whichever is larger.
317
+ #
318
+ # 3. Calculate the final #y_min by using either the maximum height of all items with valign
319
+ # :top subtracted from the temporary #y_min, or the temporary #y_min, whichever is smaller.
320
+ #
321
+ # Calculate the final #y_max by using either the maximum height of all items with valign
322
+ # :bottom added to #y_min, or the temporary #y_max, whichever is larger.
323
+ #
324
+ # In certain cases there is no unique solution to the values of #y_min and #y_max, for
325
+ # example, it depends on the order of the calculations in part 3.
326
+ def calculate_y_dimensions
327
+ @y_min, @y_max, @text_y_min, @text_y_max = HeightCalculator.new(@items).result
328
+ end
329
+
330
+ end
331
+
332
+ end
333
+ end