hexapdf 0.23.0 → 0.24.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 (243) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -0
  3. data/LICENSE +1 -1
  4. data/Rakefile +1 -1
  5. data/examples/016-frame_automatic_box_placement.rb +7 -2
  6. data/examples/017-frame_text_flow.rb +10 -18
  7. data/examples/020-column_box.rb +40 -0
  8. data/examples/021-list_box.rb +26 -0
  9. data/lib/hexapdf/cli/batch.rb +1 -1
  10. data/lib/hexapdf/cli/command.rb +1 -1
  11. data/lib/hexapdf/cli/files.rb +1 -1
  12. data/lib/hexapdf/cli/fonts.rb +1 -1
  13. data/lib/hexapdf/cli/form.rb +2 -2
  14. data/lib/hexapdf/cli/image2pdf.rb +1 -1
  15. data/lib/hexapdf/cli/images.rb +1 -1
  16. data/lib/hexapdf/cli/info.rb +2 -2
  17. data/lib/hexapdf/cli/inspect.rb +2 -2
  18. data/lib/hexapdf/cli/merge.rb +1 -1
  19. data/lib/hexapdf/cli/modify.rb +1 -1
  20. data/lib/hexapdf/cli/optimize.rb +1 -1
  21. data/lib/hexapdf/cli/split.rb +1 -1
  22. data/lib/hexapdf/cli/watermark.rb +1 -1
  23. data/lib/hexapdf/cli.rb +1 -1
  24. data/lib/hexapdf/composer.rb +45 -126
  25. data/lib/hexapdf/configuration.rb +17 -1
  26. data/lib/hexapdf/content/canvas.rb +1 -1
  27. data/lib/hexapdf/content/color_space.rb +1 -1
  28. data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
  29. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
  30. data/lib/hexapdf/content/graphic_object/geom2d.rb +2 -1
  31. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  32. data/lib/hexapdf/content/graphic_object.rb +1 -1
  33. data/lib/hexapdf/content/graphics_state.rb +1 -1
  34. data/lib/hexapdf/content/operator.rb +1 -1
  35. data/lib/hexapdf/content/parser.rb +1 -1
  36. data/lib/hexapdf/content/processor.rb +1 -1
  37. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  38. data/lib/hexapdf/content.rb +1 -1
  39. data/lib/hexapdf/data_dir.rb +1 -1
  40. data/lib/hexapdf/dictionary.rb +1 -1
  41. data/lib/hexapdf/dictionary_fields.rb +1 -1
  42. data/lib/hexapdf/document/files.rb +1 -1
  43. data/lib/hexapdf/document/fonts.rb +1 -1
  44. data/lib/hexapdf/document/images.rb +1 -1
  45. data/lib/hexapdf/document/layout.rb +397 -0
  46. data/lib/hexapdf/document/pages.rb +17 -1
  47. data/lib/hexapdf/document/signatures.rb +5 -4
  48. data/lib/hexapdf/document.rb +8 -1
  49. data/lib/hexapdf/encryption/aes.rb +1 -1
  50. data/lib/hexapdf/encryption/arc4.rb +1 -1
  51. data/lib/hexapdf/encryption/fast_aes.rb +1 -1
  52. data/lib/hexapdf/encryption/fast_arc4.rb +30 -21
  53. data/lib/hexapdf/encryption/identity.rb +1 -1
  54. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  55. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  56. data/lib/hexapdf/encryption/security_handler.rb +1 -1
  57. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  58. data/lib/hexapdf/encryption.rb +1 -1
  59. data/lib/hexapdf/error.rb +1 -1
  60. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  61. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  62. data/lib/hexapdf/filter/crypt.rb +1 -1
  63. data/lib/hexapdf/filter/encryption.rb +1 -1
  64. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  65. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  66. data/lib/hexapdf/filter/pass_through.rb +1 -1
  67. data/lib/hexapdf/filter/predictor.rb +1 -1
  68. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  69. data/lib/hexapdf/filter.rb +1 -1
  70. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  71. data/lib/hexapdf/font/cmap/writer.rb +1 -1
  72. data/lib/hexapdf/font/cmap.rb +1 -1
  73. data/lib/hexapdf/font/encoding/base.rb +1 -1
  74. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  75. data/lib/hexapdf/font/encoding/glyph_list.rb +2 -2
  76. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  77. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
  78. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  79. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  80. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
  81. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  82. data/lib/hexapdf/font/encoding.rb +1 -1
  83. data/lib/hexapdf/font/invalid_glyph.rb +1 -1
  84. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  85. data/lib/hexapdf/font/true_type/font.rb +1 -1
  86. data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
  87. data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
  88. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  89. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
  90. data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
  91. data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
  92. data/lib/hexapdf/font/true_type/table/head.rb +1 -1
  93. data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
  94. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
  95. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  96. data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
  97. data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
  98. data/lib/hexapdf/font/true_type/table/name.rb +1 -1
  99. data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
  100. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  101. data/lib/hexapdf/font/true_type/table.rb +1 -1
  102. data/lib/hexapdf/font/true_type.rb +1 -1
  103. data/lib/hexapdf/font/true_type_wrapper.rb +1 -1
  104. data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
  105. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  106. data/lib/hexapdf/font/type1/font.rb +1 -1
  107. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  108. data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
  109. data/lib/hexapdf/font/type1.rb +1 -1
  110. data/lib/hexapdf/font/type1_wrapper.rb +1 -1
  111. data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
  112. data/lib/hexapdf/font_loader/from_file.rb +1 -1
  113. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  114. data/lib/hexapdf/font_loader.rb +1 -1
  115. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  116. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  117. data/lib/hexapdf/image_loader/png.rb +1 -1
  118. data/lib/hexapdf/image_loader.rb +1 -1
  119. data/lib/hexapdf/importer.rb +1 -1
  120. data/lib/hexapdf/layout/box.rb +121 -22
  121. data/lib/hexapdf/layout/box_fitter.rb +136 -0
  122. data/lib/hexapdf/layout/column_box.rb +247 -0
  123. data/lib/hexapdf/layout/frame.rb +155 -139
  124. data/lib/hexapdf/layout/image_box.rb +19 -4
  125. data/lib/hexapdf/layout/inline_box.rb +1 -1
  126. data/lib/hexapdf/layout/line.rb +1 -1
  127. data/lib/hexapdf/layout/list_box.rb +355 -0
  128. data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
  129. data/lib/hexapdf/layout/style.rb +5 -1
  130. data/lib/hexapdf/layout/text_box.rb +20 -9
  131. data/lib/hexapdf/layout/text_fragment.rb +3 -2
  132. data/lib/hexapdf/layout/text_layouter.rb +17 -2
  133. data/lib/hexapdf/layout/text_shaper.rb +1 -1
  134. data/lib/hexapdf/layout/width_from_polygon.rb +12 -7
  135. data/lib/hexapdf/layout.rb +4 -1
  136. data/lib/hexapdf/name_tree_node.rb +1 -1
  137. data/lib/hexapdf/number_tree_node.rb +1 -1
  138. data/lib/hexapdf/object.rb +1 -1
  139. data/lib/hexapdf/parser.rb +1 -8
  140. data/lib/hexapdf/pdf_array.rb +1 -1
  141. data/lib/hexapdf/rectangle.rb +1 -1
  142. data/lib/hexapdf/reference.rb +1 -1
  143. data/lib/hexapdf/revision.rb +1 -1
  144. data/lib/hexapdf/revisions.rb +1 -1
  145. data/lib/hexapdf/serializer.rb +1 -1
  146. data/lib/hexapdf/stream.rb +1 -1
  147. data/lib/hexapdf/task/dereference.rb +1 -1
  148. data/lib/hexapdf/task/optimize.rb +1 -1
  149. data/lib/hexapdf/task.rb +1 -1
  150. data/lib/hexapdf/tokenizer.rb +1 -1
  151. data/lib/hexapdf/type/acro_form/appearance_generator.rb +1 -1
  152. data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
  153. data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
  154. data/lib/hexapdf/type/acro_form/field.rb +1 -1
  155. data/lib/hexapdf/type/acro_form/form.rb +1 -1
  156. data/lib/hexapdf/type/acro_form/signature_field.rb +1 -1
  157. data/lib/hexapdf/type/acro_form/text_field.rb +1 -1
  158. data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
  159. data/lib/hexapdf/type/acro_form.rb +1 -1
  160. data/lib/hexapdf/type/action.rb +1 -1
  161. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  162. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  163. data/lib/hexapdf/type/actions/launch.rb +1 -1
  164. data/lib/hexapdf/type/actions/uri.rb +1 -1
  165. data/lib/hexapdf/type/actions.rb +1 -1
  166. data/lib/hexapdf/type/annotation.rb +1 -1
  167. data/lib/hexapdf/type/annotations/link.rb +1 -1
  168. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  169. data/lib/hexapdf/type/annotations/text.rb +1 -1
  170. data/lib/hexapdf/type/annotations/widget.rb +1 -1
  171. data/lib/hexapdf/type/annotations.rb +1 -1
  172. data/lib/hexapdf/type/catalog.rb +1 -1
  173. data/lib/hexapdf/type/cid_font.rb +1 -1
  174. data/lib/hexapdf/type/embedded_file.rb +1 -1
  175. data/lib/hexapdf/type/file_specification.rb +1 -1
  176. data/lib/hexapdf/type/font.rb +1 -1
  177. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  178. data/lib/hexapdf/type/font_simple.rb +1 -1
  179. data/lib/hexapdf/type/font_true_type.rb +1 -1
  180. data/lib/hexapdf/type/font_type0.rb +1 -1
  181. data/lib/hexapdf/type/font_type1.rb +1 -1
  182. data/lib/hexapdf/type/font_type3.rb +1 -1
  183. data/lib/hexapdf/type/form.rb +1 -1
  184. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  185. data/lib/hexapdf/type/icon_fit.rb +1 -1
  186. data/lib/hexapdf/type/image.rb +1 -1
  187. data/lib/hexapdf/type/info.rb +1 -1
  188. data/lib/hexapdf/type/names.rb +1 -1
  189. data/lib/hexapdf/type/object_stream.rb +1 -1
  190. data/lib/hexapdf/type/page.rb +1 -1
  191. data/lib/hexapdf/type/page_tree_node.rb +19 -2
  192. data/lib/hexapdf/type/resources.rb +1 -1
  193. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +1 -1
  194. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +1 -1
  195. data/lib/hexapdf/type/signature/handler.rb +1 -1
  196. data/lib/hexapdf/type/signature/verification_result.rb +1 -1
  197. data/lib/hexapdf/type/signature.rb +1 -1
  198. data/lib/hexapdf/type/trailer.rb +2 -2
  199. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  200. data/lib/hexapdf/type/xref_stream.rb +1 -1
  201. data/lib/hexapdf/type.rb +1 -1
  202. data/lib/hexapdf/utils/bit_field.rb +1 -1
  203. data/lib/hexapdf/utils/bit_stream.rb +1 -1
  204. data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
  205. data/lib/hexapdf/utils/lru_cache.rb +1 -1
  206. data/lib/hexapdf/utils/math_helpers.rb +1 -1
  207. data/lib/hexapdf/utils/object_hash.rb +1 -1
  208. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  209. data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
  210. data/lib/hexapdf/version.rb +2 -2
  211. data/lib/hexapdf/writer.rb +9 -7
  212. data/lib/hexapdf/xref_section.rb +1 -1
  213. data/lib/hexapdf.rb +1 -1
  214. data/test/hexapdf/content/graphic_object/test_geom2d.rb +1 -1
  215. data/test/hexapdf/document/test_destinations.rb +1 -1
  216. data/test/hexapdf/document/test_images.rb +1 -1
  217. data/test/hexapdf/document/test_layout.rb +264 -0
  218. data/test/hexapdf/document/test_pages.rb +9 -0
  219. data/test/hexapdf/document/test_signatures.rb +10 -3
  220. data/test/hexapdf/encryption/test_security_handler.rb +1 -1
  221. data/test/hexapdf/font/encoding/test_glyph_list.rb +4 -0
  222. data/test/hexapdf/layout/test_box.rb +53 -3
  223. data/test/hexapdf/layout/test_box_fitter.rb +62 -0
  224. data/test/hexapdf/layout/test_column_box.rb +159 -0
  225. data/test/hexapdf/layout/test_frame.rb +99 -38
  226. data/test/hexapdf/layout/test_image_box.rb +1 -1
  227. data/test/hexapdf/layout/test_list_box.rb +249 -0
  228. data/test/hexapdf/layout/test_text_box.rb +17 -2
  229. data/test/hexapdf/layout/test_text_fragment.rb +1 -1
  230. data/test/hexapdf/layout/test_text_layouter.rb +42 -17
  231. data/test/hexapdf/layout/test_width_from_polygon.rb +13 -0
  232. data/test/hexapdf/test_composer.rb +11 -0
  233. data/test/hexapdf/test_dictionary_fields.rb +9 -9
  234. data/test/hexapdf/test_document.rb +4 -4
  235. data/test/hexapdf/test_filter.rb +1 -1
  236. data/test/hexapdf/test_parser.rb +0 -2
  237. data/test/hexapdf/test_revisions.rb +2 -2
  238. data/test/hexapdf/test_serializer.rb +1 -5
  239. data/test/hexapdf/test_writer.rb +58 -3
  240. data/test/hexapdf/type/test_page_tree_node.rb +21 -1
  241. data/test/hexapdf/type/test_trailer.rb +3 -3
  242. data/test/test_helper.rb +5 -1
  243. metadata +28 -3
@@ -0,0 +1,355 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
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-2022 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
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/layout/box'
38
+ require 'hexapdf/layout/box_fitter'
39
+ require 'hexapdf/layout/text_box'
40
+ require 'hexapdf/layout/text_fragment'
41
+
42
+ module HexaPDF
43
+ module Layout
44
+
45
+ # A ListBox arranges its children as unordered or ordered list items.
46
+ #
47
+ # The indentation of the contents from the left (#content_indentation) as well as the type of
48
+ # item (#item_type) can be specified. Additionally, it is possible to define the start number
49
+ # for ordered lists (#start_number) and the amount of spacing between items (#item_spacing).
50
+ #
51
+ # If the list box has padding and/or borders specified, they are handled like with any other
52
+ # box. This means they are around all items and their contents and are not used separately for
53
+ # each item.
54
+ #
55
+ # The following style properties are used (additionally to those used by the parent class):
56
+ #
57
+ # Style#position::
58
+ # If this is set to :flow, the frames created for the list items will take the shape of the
59
+ # frame into account. This also means that the +available_width+ and +available_height+
60
+ # arguments are ignored.
61
+ class ListBox < Box
62
+
63
+ # The child boxes of this ListBox. They need to be finalized before #fit is called.
64
+ attr_reader :children
65
+
66
+ # The type of list item marker to be rendered before the list item contents.
67
+ #
68
+ # The following values are supported (and :disc is the default):
69
+ #
70
+ # :disc::
71
+ #
72
+ # Draws a filled disc for the items of the unordered list.
73
+ #
74
+ # #>pdf-composer100
75
+ # composer.box(:list, item_type: :disc) do |list|
76
+ # list.lorem_ipsum_box(sentences: 1)
77
+ # end
78
+ #
79
+ # :circle::
80
+ #
81
+ # Draws an unfilled circle for the items of the unordered list.
82
+ #
83
+ # #>pdf-composer100
84
+ # composer.box(:list, item_type: :circle) do |list|
85
+ # list.lorem_ipsum_box(sentences: 1)
86
+ # end
87
+ #
88
+ # :square::
89
+ #
90
+ # Draws a filled square for the items of the unordered list.
91
+ #
92
+ # #>pdf-composer100
93
+ # composer.box(:list, item_type: :square) do |list|
94
+ # list.lorem_ipsum_box(sentences: 1)
95
+ # end
96
+ #
97
+ # :decimal::
98
+ #
99
+ # Draws the numbers in decimal form, starting from #start_number) for the items of
100
+ # the ordered list.
101
+ #
102
+ # #>pdf-composer100
103
+ # composer.box(:list, item_type: :decimal) do |list|
104
+ # 5.times { list.lorem_ipsum_box(sentences: 1) }
105
+ # end
106
+ #
107
+ # custom marker::
108
+ #
109
+ # Additionally, it is possible to specify an object as value that responds to
110
+ # #call(document, box, index) where +document+ is the HexaPDF::Document, +box+ is the list
111
+ # box, and +index+ is the current item index, starting at 0. The return value needs to be a
112
+ # Box object which is then fit into the content indentation area and drawn.
113
+ #
114
+ # #>pdf-composer100
115
+ # image = lambda do |document, box, index|
116
+ # document.layout.image_box(machu_picchu, height: box.style.font_size)
117
+ # end
118
+ # composer.box(:list, item_type: image) do |list|
119
+ # 2.times { list.lorem_ipsum_box(sentences: 1) }
120
+ # end
121
+ attr_reader :item_type
122
+
123
+ # The start number when using an #item_type that represents an ordered list.
124
+ #
125
+ # The default value for this is 1.
126
+ #
127
+ # Example:
128
+ #
129
+ # #>pdf-composer100
130
+ # composer.box(:list, item_type: :decimal, start_number: 3) do |list|
131
+ # 2.times { list.lorem_ipsum_box(sentences: 1) }
132
+ # end
133
+ attr_reader :start_number
134
+
135
+ # The indentation of the list content in PDF points. The item marker will be inside this
136
+ # indentation.
137
+ #
138
+ # The default value is two times the font size.
139
+ #
140
+ # Example:
141
+ #
142
+ # #>pdf-composer100
143
+ # composer.box(:list) {|list| list.lorem_ipsum_box(sentences: 1) }
144
+ # composer.box(:list, content_indentation: 50) do |list|
145
+ # list.lorem_ipsum_box(sentences: 1)
146
+ # end
147
+ attr_reader :content_indentation
148
+
149
+ # The spacing between two consecutive list items.
150
+ #
151
+ # The default value is zero.
152
+ #
153
+ # Example:
154
+ #
155
+ # #>pdf-composer
156
+ # composer.box(:list, item_spacing: 10) do |list|
157
+ # 3.times { list.lorem_ipsum_box(sentences: 1) }
158
+ # end
159
+ attr_reader :item_spacing
160
+
161
+ # Creates a new ListBox object for the given child boxes in +children+.
162
+ def initialize(children: [], item_type: :disc, content_indentation: nil, start_number: 1,
163
+ item_spacing: 0, **kwargs)
164
+ super(**kwargs)
165
+ @children = children
166
+ @item_type = item_type
167
+ @content_indentation = content_indentation || 2 * style.font_size
168
+ @start_number = start_number
169
+ @item_spacing = item_spacing
170
+
171
+ @results = nil
172
+ @results_item_marker_x = nil
173
+ end
174
+
175
+ # Returns +true+ as the 'position' style property value :flow is supported.
176
+ def supports_position_flow?
177
+ true
178
+ end
179
+
180
+ # Fits the list box into the available space.
181
+ def fit(available_width, available_height, frame)
182
+ @width = if @initial_width > 0
183
+ @initial_width
184
+ else
185
+ (style.position == :flow ? frame.width : available_width)
186
+ end
187
+ height = if @initial_height > 0
188
+ @initial_height - reserved_height
189
+ else
190
+ (style.position == :flow ? frame.y - frame.bottom : available_height) - reserved_height
191
+ end
192
+
193
+ width = @width - reserved_width
194
+ left = (style.position == :flow ? frame.left : frame.x) + reserved_width_left
195
+ top = frame.y - reserved_height_top
196
+
197
+ # The left side of the frame of an item is always indented, regardless of style.position
198
+ item_frame_left = left + @content_indentation
199
+ item_frame_width = width - @content_indentation
200
+
201
+ # We can remove the content indentation for a rectangle by just modifying left and width
202
+ unless style.position == :flow
203
+ left = item_frame_left
204
+ width = item_frame_width
205
+ end
206
+
207
+ @results = []
208
+ @results_item_marker_x = []
209
+
210
+ @children.each_with_index do |child, index|
211
+ shape = Geom2D::Polygon([left, top - height],
212
+ [left + width, top - height],
213
+ [left + width, top],
214
+ [left, top])
215
+ if style.position == :flow
216
+ shape = Geom2D::Algorithms::PolygonOperation.run(frame.shape, shape, :intersection)
217
+ remove_indent_from_frame_shape(shape) unless shape.polygons.empty?
218
+ end
219
+
220
+ #p [:list, left, width, shape]
221
+
222
+ item_frame = Frame.new(item_frame_left, top - height, item_frame_width, height, shape: shape)
223
+
224
+ #p [index, item_frame.x, @results_item_marker_x]
225
+ @results_item_marker_x << item_frame.x - content_indentation
226
+
227
+ box_fitter = BoxFitter.new([item_frame])
228
+ Array(child).each {|box| box_fitter.fit(box) }
229
+ @results << box_fitter
230
+
231
+ top -= box_fitter.content_heights[0] + item_spacing
232
+ height -= box_fitter.content_heights[0] + item_spacing
233
+
234
+ break if !box_fitter.fit_successful? || height <= 0
235
+ end
236
+
237
+ @height = @results.sum {|box_fitter| box_fitter.content_heights[0] } +
238
+ (@results.count - 1) * item_spacing +
239
+ reserved_height
240
+
241
+ @fit_successful = @results.all?(&:fit_successful?) && @results.size == @children.size
242
+ end
243
+
244
+ private
245
+
246
+ # Removes the +content_indentation+ from the left side of the given shape (a Geom2D::PolygonSet).
247
+ def remove_indent_from_frame_shape(shape)
248
+ polygon_index = 0
249
+ data = []
250
+
251
+ # Determine the lower-left-most and upper-left-most vertices and their indices, together
252
+ # with the polygon index that holds them and the direction wrt to the indices from
253
+ # upper-left-most to lower-left-most.
254
+ shape.polygons.each_with_index do |polygon, pindex|
255
+ lower_vertex = upper_vertex = polygon[0]
256
+ lower_index = upper_index = 0
257
+ 1.upto(polygon.nr_of_vertices - 1) do |i|
258
+ v = polygon[i]
259
+ if v.y < lower_vertex.y || (v.y == lower_vertex.y && v.x <= lower_vertex.x)
260
+ lower_vertex = v
261
+ lower_index = i
262
+ elsif v.y > upper_vertex.y || (v.y == upper_vertex.y && v.x <= upper_vertex.x)
263
+ upper_vertex = v
264
+ upper_index = i
265
+ end
266
+ end
267
+ direction = upper_vertex.x == polygon[(upper_index + 1) % polygon.nr_of_vertices].x ? 1 : -1
268
+ if data.empty? || data[0].x > lower_vertex.x
269
+ polygon_index = pindex
270
+ data = [lower_vertex, lower_index, upper_vertex, upper_index, direction]
271
+ end
272
+ end
273
+
274
+ # Now we have all the data to remove the indentation on the left side of the polygon. This
275
+ # is done by shifting all vertices between and including the lower-left-most and
276
+ # upper-left-most vertices to the right.
277
+ vertices = shape.polygons[polygon_index].to_a
278
+ point = data[2]
279
+ index = data[3]
280
+ while point != data[0]
281
+ vertices[index] = Geom2D::Point(point.x + content_indentation, point.y)
282
+ index = (index + data[4]) % vertices.size
283
+ point = vertices[index]
284
+ end
285
+ vertices[data[1]] = Geom2D::Point(data[0].x + content_indentation, data[0].y)
286
+
287
+ shape.polygons[polygon_index] = Geom2D::Polygon(*vertices)
288
+ end
289
+
290
+ # Splits the content of the list box. This method is called from Box#split.
291
+ def split_content(_available_width, _available_height, _frame)
292
+ remaining_boxes = @results[-1].remaining_boxes
293
+ first_is_split_box = remaining_boxes.first&.split_box?
294
+ children = (remaining_boxes.empty? ? [] : [remaining_boxes]) + @children[@results.size..-1]
295
+
296
+ box = create_split_box(split_box_value: first_is_split_box ? :hide_first_marker : :show_first_marker)
297
+ box.instance_variable_set(:@children, children)
298
+ box.instance_variable_set(:@start_number,
299
+ @start_number + @results.size + (first_is_split_box ? -1 : 0))
300
+ box.instance_variable_set(:@results, [])
301
+ box.instance_variable_set(:@results_item_marker_x, [])
302
+
303
+ [self, box]
304
+ end
305
+
306
+ # Creates a box for the item marker at the given item index, using #item_style to decide on
307
+ # its contents.
308
+ def item_marker_box(document, index)
309
+ return @item_type.call(document, self, index) if @item_type.kind_of?(Proc)
310
+ return @item_marker_box if defined?(@item_marker_box)
311
+
312
+ fragment = case @item_type
313
+ when :disc
314
+ TextFragment.create("•", font: document.fonts.add("Times"),
315
+ font_size: style.font_size)
316
+ when :circle
317
+ TextFragment.create("❍", font: document.fonts.add("ZapfDingbats"),
318
+ font_size: style.font_size / 2.0,
319
+ text_rise: -style.font_size / 1.8)
320
+ when :square
321
+ TextFragment.create("■", font: document.fonts.add("ZapfDingbats"),
322
+ font_size: style.font_size / 2.0,
323
+ text_rise: -style.font_size / 1.8)
324
+ when :decimal
325
+ text = (@start_number + index).to_s << "."
326
+ decimal_style = {
327
+ font: (style.font? ? style.font : document.fonts.add("Times")),
328
+ font_size: style.font_size || 10,
329
+ }
330
+ TextFragment.create(text, decimal_style)
331
+ else
332
+ raise HexaPDF::Error, "Unknown list item type #{@item_type.inspect}"
333
+ end
334
+ box = TextBox.new(items: [fragment], style: {align: :right, padding: [0, 5, 0, 0]})
335
+ @item_marker_box = box unless @item_type == :decimal
336
+ box
337
+ end
338
+
339
+ # Draws the list items onto the canvas at position [x, y].
340
+ def draw_content(canvas, _x, _y)
341
+ @results.each_with_index do |box_fitter, index|
342
+ if index != 0 || !split_box? || @split_box == :show_first_marker
343
+ box = item_marker_box(canvas.context.document, index)
344
+ box.fit(content_indentation, box_fitter.content_heights[0], nil)
345
+ box.draw(canvas, @results_item_marker_x[index],
346
+ box_fitter.frames[0].bottom + box_fitter.frames[0].height - box.height)
347
+ end
348
+ box_fitter.fit_results.each {|result| result.draw(canvas) }
349
+ end
350
+ end
351
+
352
+ end
353
+
354
+ end
355
+ end
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2021 Thomas Leitner
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2021 Thomas Leitner
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -1145,6 +1145,10 @@ module HexaPDF
1145
1145
  #
1146
1146
  # :flow:: Flows the content of the box inside the frame around objects.
1147
1147
  #
1148
+ # A box needs to indicate whether it supports this value by implementing the
1149
+ # #supports_position_flow? method and returning +true+ if it does or +false+ if it
1150
+ # doesn't.
1151
+ #
1148
1152
  # :absolute:: Position the box at an absolute position relative to the frame. The coordinates
1149
1153
  # are given via the position hint.
1150
1154
  #
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2021 Thomas Leitner
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -47,18 +47,23 @@ module HexaPDF
47
47
 
48
48
  # Creates a new TextBox object with the given inline items (e.g. TextFragment and InlineBox
49
49
  # objects).
50
- def initialize(items, **kwargs)
50
+ def initialize(items:, **kwargs)
51
51
  super(**kwargs)
52
52
  @tl = TextLayouter.new(style)
53
53
  @items = items
54
54
  @result = nil
55
55
  end
56
56
 
57
+ # Returns +true+ as the 'position' style property value :flow is supported.
58
+ def supports_position_flow?
59
+ true
60
+ end
61
+
57
62
  # Fits the text box into the Frame.
58
63
  #
59
64
  # Depending on the 'position' style property, the text is either fit into the rectangular area
60
65
  # given by +available_width+ and +available_height+, or fit to the outline of the frame
61
- # starting from the top.
66
+ # starting from the top (when 'position' is set to :flow).
62
67
  #
63
68
  # The spacing after the last line can be controlled via the style property +last_line_gap+.
64
69
  #
@@ -69,7 +74,7 @@ module HexaPDF
69
74
 
70
75
  @width = @height = 0
71
76
  @result = if style.position == :flow
72
- @tl.fit(@items, frame.width_specification, frame.contour_line.bbox.height)
77
+ @tl.fit(@items, frame.width_specification, frame.shape.bbox.height)
73
78
  else
74
79
  @width = reserved_width
75
80
  @height = reserved_height
@@ -97,17 +102,15 @@ module HexaPDF
97
102
  # Splits the text box into two boxes if necessary and possible.
98
103
  def split(available_width, available_height, frame)
99
104
  fit(available_width, available_height, frame) unless @result
100
- if @width > available_width || @height > available_height
105
+
106
+ if style.position != :flow && (@width > available_width || @height > available_height)
101
107
  [nil, self]
102
108
  elsif @result.remaining_items.empty?
103
109
  [self]
104
110
  elsif @result.lines.empty?
105
111
  [nil, self]
106
112
  else
107
- box = clone
108
- box.instance_variable_set(:@result, nil)
109
- box.instance_variable_set(:@items, @result.remaining_items)
110
- [self, box]
113
+ [self, create_box_for_remaining_items]
111
114
  end
112
115
  end
113
116
 
@@ -124,6 +127,14 @@ module HexaPDF
124
127
  @result.draw(canvas, x, y + content_height)
125
128
  end
126
129
 
130
+ # Creates a new TextBox instance for the items remaining after fitting the box.
131
+ def create_box_for_remaining_items
132
+ box = create_split_box
133
+ box.instance_variable_set(:@result, nil)
134
+ box.instance_variable_set(:@items, @result.remaining_items)
135
+ box
136
+ end
137
+
127
138
  end
128
139
 
129
140
  end
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2021 Thomas Leitner
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -273,7 +273,8 @@ module HexaPDF
273
273
 
274
274
  # :nodoc:
275
275
  def inspect
276
- "#<#{self.class.name} #{items.inspect}>"
276
+ "#<#{self.class.name} #{items.reject {|i| i.kind_of?(Numeric) }.map(&:str).join.inspect} " \
277
+ "#{items.inspect}>"
277
278
  end
278
279
 
279
280
  private
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2021 Thomas Leitner
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -100,6 +100,10 @@ module HexaPDF
100
100
  :box
101
101
  end
102
102
 
103
+ def inspect #:nodoc:
104
+ "Box[#{@item.inspect}]"
105
+ end
106
+
103
107
  end
104
108
 
105
109
  # Used for layouting. Describes a glue item, i.e. an item describing white space that could
@@ -132,6 +136,10 @@ module HexaPDF
132
136
  :glue
133
137
  end
134
138
 
139
+ def inspect #:nodoc:
140
+ "Glue[#{@item.inspect}]"
141
+ end
142
+
135
143
  end
136
144
 
137
145
  # Used for layouting. Describes a penalty item, i.e. a point where a break is allowed.
@@ -173,6 +181,10 @@ module HexaPDF
173
181
  :penalty
174
182
  end
175
183
 
184
+ def inspect #:nodoc:
185
+ "Penalty[#{penalty} #{width} #{@item.inspect}]"
186
+ end
187
+
176
188
  # Singleton object describing a Penalty for a prohibited break.
177
189
  ProhibitedBreak = new(Penalty::INFINITY)
178
190
 
@@ -759,6 +771,9 @@ module HexaPDF
759
771
 
760
772
  # item didn't fit into first part, find next available part
761
773
  if line.items.empty? && line_fragments.empty?
774
+ # item didn't fit because no more height is available
775
+ next nil if actual_height + item.height > height
776
+
762
777
  old_height = actual_height
763
778
  while item.width > width_block.call(item.item) && actual_height <= height
764
779
  width_spec_index += 1
@@ -815,7 +830,7 @@ module HexaPDF
815
830
  if too_wide_box && (too_wide_box.item.kind_of?(TextFragment) &&
816
831
  too_wide_box.item.items.size > 1)
817
832
  rest[0..rest.index(too_wide_box)] = too_wide_box.item.items.map do |item|
818
- Box.new(TextFragment.new([item], too_wide_box.item.style))
833
+ Box.new(TextFragment.new([item].freeze, too_wide_box.item.style))
819
834
  end
820
835
  too_wide_box = nil
821
836
  else
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2021 Thomas Leitner
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2021 Thomas Leitner
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -34,12 +34,16 @@
34
34
  # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
35
  #++
36
36
 
37
+ require 'geom2d/utils'
38
+
37
39
  module HexaPDF
38
40
  module Layout
39
41
 
40
42
  # Utility class for generating width specifications for TextLayouter#fit from polygons.
41
43
  class WidthFromPolygon
42
44
 
45
+ include Geom2D::Utils
46
+
43
47
  # Creates a new object for the given polygon (or polygon set) and immediately prepares it so
44
48
  # that #call can be used.
45
49
  #
@@ -94,14 +98,15 @@ module HexaPDF
94
98
 
95
99
  @polygon_segments.each do |segments|
96
100
  temp_result = []
97
- status = if segments.first[0].start_point.y > y2 || segments.first[0].start_point.y < y1
101
+ status = if float_compare(segments.first[0].start_point.y, y2) >= 0 ||
102
+ float_compare(segments.first[0].start_point.y, y1) <= 0
98
103
  :outside
99
104
  else
100
105
  :inside
101
106
  end
102
107
 
103
108
  segments.each do |_segment, miny, maxy, minyx, maxyx, vertical, slope, intercept|
104
- next unless miny < y2 && maxy > y1
109
+ next unless float_compare(miny, y2) < 0 && float_compare(maxy, y1) > 0
105
110
 
106
111
  if vertical
107
112
  min_x = max_x = minyx
@@ -111,9 +116,9 @@ module HexaPDF
111
116
  min_x, max_x = max_x, min_x if min_x > max_x
112
117
  end
113
118
 
114
- if miny <= y1 && maxy >= y2 # segment crosses both lines
119
+ if float_compare(miny, y1) <= 0 && float_compare(maxy, y2) >= 0 # segment crosses both lines
115
120
  temp_result << [min_x, max_x, :crossed_both]
116
- elsif miny <= y1 # segment crosses bottom line
121
+ elsif float_compare(miny, y1) <= 0 # segment crosses bottom line
117
122
  if status == :outside
118
123
  temp_result << [min_x, max_x, :crossed_bottom]
119
124
  status = :inside
@@ -127,7 +132,7 @@ module HexaPDF
127
132
  temp_result << [min_x, max_x, :crossed_bottom]
128
133
  status = :outside
129
134
  end
130
- elsif maxy >= y2 # segment crosses top line
135
+ elsif float_compare(maxy, y2) >= 0 # segment crosses top line
131
136
  if status == :outside
132
137
  temp_result << [min_x, max_x, :crossed_top]
133
138
  status = :inside
@@ -199,7 +204,7 @@ module HexaPDF
199
204
  # Prepare the segments and other data for later use.
200
205
  def prepare(offset)
201
206
  @max_y = @polygon.bbox.max_y - offset
202
- @polygon_segments = if @polygon.nr_of_contours > 1
207
+ @polygon_segments = if @polygon.respond_to?(:polygons)
203
208
  @polygon.polygons.map {|polygon| process_polygon(polygon) }
204
209
  else
205
210
  [process_polygon(@polygon)]
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2021 Thomas Leitner
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -50,9 +50,12 @@ module HexaPDF
50
50
  autoload(:TextLayouter, 'hexapdf/layout/text_layouter')
51
51
  autoload(:Box, 'hexapdf/layout/box')
52
52
  autoload(:Frame, 'hexapdf/layout/frame')
53
+ autoload(:BoxFitter, 'hexapdf/layout/box_fitter')
53
54
  autoload(:WidthFromPolygon, 'hexapdf/layout/width_from_polygon')
54
55
  autoload(:TextBox, 'hexapdf/layout/text_box')
55
56
  autoload(:ImageBox, 'hexapdf/layout/image_box')
57
+ autoload(:ColumnBox, 'hexapdf/layout/column_box')
58
+ autoload(:ListBox, 'hexapdf/layout/list_box')
56
59
 
57
60
  end
58
61
 
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2021 Thomas Leitner
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2021 Thomas Leitner
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2021 Thomas Leitner
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as