hexapdf 0.4.0 → 0.5.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 (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