hexapdf 0.32.2 → 0.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (221) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +104 -1
  3. data/README.md +9 -0
  4. data/examples/002-graphics.rb +15 -17
  5. data/examples/003-arcs.rb +9 -9
  6. data/examples/009-text_layouter_alignment.rb +1 -1
  7. data/examples/010-text_layouter_inline_boxes.rb +2 -2
  8. data/examples/011-text_layouter_line_wrapping.rb +1 -1
  9. data/examples/012-text_layouter_styling.rb +7 -7
  10. data/examples/013-text_layouter_shapes.rb +1 -1
  11. data/examples/014-text_in_polygon.rb +1 -1
  12. data/examples/015-boxes.rb +8 -7
  13. data/examples/016-frame_automatic_box_placement.rb +2 -2
  14. data/examples/017-frame_text_flow.rb +2 -1
  15. data/examples/018-composer.rb +1 -1
  16. data/examples/020-column_box.rb +2 -1
  17. data/examples/025-table_box.rb +46 -0
  18. data/examples/026-optional_content.rb +55 -0
  19. data/examples/027-composer_optional_content.rb +83 -0
  20. data/lib/hexapdf/cli/command.rb +12 -3
  21. data/lib/hexapdf/cli/fonts.rb +1 -1
  22. data/lib/hexapdf/cli/form.rb +5 -5
  23. data/lib/hexapdf/cli/inspect.rb +5 -7
  24. data/lib/hexapdf/composer.rb +106 -53
  25. data/lib/hexapdf/configuration.rb +65 -40
  26. data/lib/hexapdf/content/canvas.rb +445 -267
  27. data/lib/hexapdf/content/color_space.rb +72 -25
  28. data/lib/hexapdf/content/graphic_object/arc.rb +57 -24
  29. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -23
  30. data/lib/hexapdf/content/graphic_object/geom2d.rb +47 -6
  31. data/lib/hexapdf/content/graphic_object/solid_arc.rb +58 -36
  32. data/lib/hexapdf/content/graphic_object.rb +6 -7
  33. data/lib/hexapdf/content/graphics_state.rb +54 -45
  34. data/lib/hexapdf/content/operator.rb +54 -54
  35. data/lib/hexapdf/content/parser.rb +2 -2
  36. data/lib/hexapdf/content/processor.rb +15 -15
  37. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  38. data/lib/hexapdf/content.rb +5 -0
  39. data/lib/hexapdf/dictionary.rb +7 -5
  40. data/lib/hexapdf/dictionary_fields.rb +43 -16
  41. data/lib/hexapdf/digital_signature/cms_handler.rb +2 -2
  42. data/lib/hexapdf/digital_signature/handler.rb +1 -1
  43. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +2 -3
  44. data/lib/hexapdf/digital_signature/signature.rb +6 -6
  45. data/lib/hexapdf/digital_signature/signatures.rb +13 -12
  46. data/lib/hexapdf/digital_signature/signing/default_handler.rb +14 -5
  47. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +2 -4
  48. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +4 -4
  49. data/lib/hexapdf/digital_signature/signing.rb +4 -0
  50. data/lib/hexapdf/digital_signature/verification_result.rb +3 -4
  51. data/lib/hexapdf/digital_signature.rb +7 -2
  52. data/lib/hexapdf/document/destinations.rb +12 -11
  53. data/lib/hexapdf/document/files.rb +1 -1
  54. data/lib/hexapdf/document/fonts.rb +1 -1
  55. data/lib/hexapdf/document/layout.rb +170 -39
  56. data/lib/hexapdf/document/pages.rb +4 -3
  57. data/lib/hexapdf/document.rb +96 -55
  58. data/lib/hexapdf/encryption/aes.rb +5 -5
  59. data/lib/hexapdf/encryption/arc4.rb +1 -1
  60. data/lib/hexapdf/encryption/fast_aes.rb +2 -2
  61. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  62. data/lib/hexapdf/encryption/identity.rb +1 -1
  63. data/lib/hexapdf/encryption/ruby_aes.rb +11 -21
  64. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  65. data/lib/hexapdf/encryption/security_handler.rb +31 -24
  66. data/lib/hexapdf/encryption/standard_security_handler.rb +45 -36
  67. data/lib/hexapdf/encryption.rb +7 -2
  68. data/lib/hexapdf/error.rb +18 -0
  69. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  70. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  71. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  72. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  73. data/lib/hexapdf/filter/pass_through.rb +1 -1
  74. data/lib/hexapdf/filter/predictor.rb +1 -1
  75. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  76. data/lib/hexapdf/filter.rb +55 -6
  77. data/lib/hexapdf/font/cmap/parser.rb +2 -2
  78. data/lib/hexapdf/font/cmap.rb +1 -1
  79. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  80. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  81. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +2 -2
  82. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  83. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  84. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +3 -3
  85. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  86. data/lib/hexapdf/font/invalid_glyph.rb +3 -0
  87. data/lib/hexapdf/font/true_type_wrapper.rb +17 -4
  88. data/lib/hexapdf/font/type1_wrapper.rb +19 -4
  89. data/lib/hexapdf/font_loader/from_configuration.rb +5 -2
  90. data/lib/hexapdf/font_loader/from_file.rb +5 -5
  91. data/lib/hexapdf/font_loader/standard14.rb +3 -3
  92. data/lib/hexapdf/font_loader.rb +3 -0
  93. data/lib/hexapdf/image_loader/jpeg.rb +2 -2
  94. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  95. data/lib/hexapdf/image_loader/png.rb +2 -2
  96. data/lib/hexapdf/image_loader.rb +1 -1
  97. data/lib/hexapdf/importer.rb +13 -0
  98. data/lib/hexapdf/layout/box.rb +32 -5
  99. data/lib/hexapdf/layout/box_fitter.rb +2 -2
  100. data/lib/hexapdf/layout/column_box.rb +20 -5
  101. data/lib/hexapdf/layout/frame.rb +53 -18
  102. data/lib/hexapdf/layout/image_box.rb +5 -0
  103. data/lib/hexapdf/layout/inline_box.rb +21 -9
  104. data/lib/hexapdf/layout/list_box.rb +50 -20
  105. data/lib/hexapdf/layout/page_style.rb +6 -5
  106. data/lib/hexapdf/layout/style.rb +64 -9
  107. data/lib/hexapdf/layout/table_box.rb +684 -0
  108. data/lib/hexapdf/layout/text_box.rb +12 -3
  109. data/lib/hexapdf/layout/text_fragment.rb +29 -3
  110. data/lib/hexapdf/layout/text_layouter.rb +32 -8
  111. data/lib/hexapdf/layout.rb +1 -0
  112. data/lib/hexapdf/name_tree_node.rb +1 -1
  113. data/lib/hexapdf/number_tree_node.rb +1 -1
  114. data/lib/hexapdf/object.rb +18 -7
  115. data/lib/hexapdf/parser.rb +7 -7
  116. data/lib/hexapdf/pdf_array.rb +1 -1
  117. data/lib/hexapdf/rectangle.rb +1 -1
  118. data/lib/hexapdf/reference.rb +1 -1
  119. data/lib/hexapdf/revision.rb +1 -1
  120. data/lib/hexapdf/revisions.rb +3 -3
  121. data/lib/hexapdf/serializer.rb +15 -15
  122. data/lib/hexapdf/stream.rb +5 -4
  123. data/lib/hexapdf/tokenizer.rb +14 -14
  124. data/lib/hexapdf/type/acro_form/appearance_generator.rb +22 -22
  125. data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
  126. data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
  127. data/lib/hexapdf/type/acro_form/field.rb +2 -2
  128. data/lib/hexapdf/type/acro_form/form.rb +1 -1
  129. data/lib/hexapdf/type/acro_form/signature_field.rb +4 -4
  130. data/lib/hexapdf/type/acro_form/text_field.rb +1 -1
  131. data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
  132. data/lib/hexapdf/type/acro_form.rb +1 -1
  133. data/lib/hexapdf/type/action.rb +1 -1
  134. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  135. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  136. data/lib/hexapdf/type/actions/launch.rb +1 -1
  137. data/lib/hexapdf/type/actions/set_ocg_state.rb +86 -0
  138. data/lib/hexapdf/type/actions/uri.rb +1 -1
  139. data/lib/hexapdf/type/actions.rb +2 -1
  140. data/lib/hexapdf/type/annotation.rb +3 -3
  141. data/lib/hexapdf/type/annotations/link.rb +1 -1
  142. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  143. data/lib/hexapdf/type/annotations/text.rb +2 -3
  144. data/lib/hexapdf/type/annotations/widget.rb +2 -2
  145. data/lib/hexapdf/type/annotations.rb +1 -1
  146. data/lib/hexapdf/type/catalog.rb +11 -2
  147. data/lib/hexapdf/type/cid_font.rb +18 -4
  148. data/lib/hexapdf/type/embedded_file.rb +1 -1
  149. data/lib/hexapdf/type/file_specification.rb +2 -2
  150. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  151. data/lib/hexapdf/type/font_simple.rb +2 -2
  152. data/lib/hexapdf/type/font_type0.rb +3 -3
  153. data/lib/hexapdf/type/font_type3.rb +1 -1
  154. data/lib/hexapdf/type/form.rb +76 -6
  155. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  156. data/lib/hexapdf/type/icon_fit.rb +1 -1
  157. data/lib/hexapdf/type/image.rb +1 -1
  158. data/lib/hexapdf/type/info.rb +1 -1
  159. data/lib/hexapdf/type/mark_information.rb +1 -1
  160. data/lib/hexapdf/type/names.rb +2 -2
  161. data/lib/hexapdf/type/object_stream.rb +2 -1
  162. data/lib/hexapdf/type/optional_content_configuration.rb +170 -0
  163. data/lib/hexapdf/type/optional_content_group.rb +370 -0
  164. data/lib/hexapdf/type/optional_content_membership.rb +63 -0
  165. data/lib/hexapdf/type/optional_content_properties.rb +158 -0
  166. data/lib/hexapdf/type/outline.rb +1 -1
  167. data/lib/hexapdf/type/outline_item.rb +1 -1
  168. data/lib/hexapdf/type/page.rb +46 -21
  169. data/lib/hexapdf/type/page_label.rb +5 -9
  170. data/lib/hexapdf/type/page_tree_node.rb +1 -1
  171. data/lib/hexapdf/type/resources.rb +1 -1
  172. data/lib/hexapdf/type/trailer.rb +2 -2
  173. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  174. data/lib/hexapdf/type/xref_stream.rb +2 -2
  175. data/lib/hexapdf/type.rb +4 -0
  176. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -2
  177. data/lib/hexapdf/version.rb +1 -1
  178. data/lib/hexapdf/writer.rb +4 -4
  179. data/lib/hexapdf/xref_section.rb +2 -2
  180. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +11 -1
  181. data/test/hexapdf/content/graphic_object/test_geom2d.rb +7 -0
  182. data/test/hexapdf/content/test_canvas.rb +49 -1
  183. data/test/hexapdf/digital_signature/test_signatures.rb +22 -0
  184. data/test/hexapdf/document/test_files.rb +2 -2
  185. data/test/hexapdf/document/test_layout.rb +105 -2
  186. data/test/hexapdf/document/test_pages.rb +6 -6
  187. data/test/hexapdf/encryption/test_security_handler.rb +12 -11
  188. data/test/hexapdf/encryption/test_standard_security_handler.rb +35 -23
  189. data/test/hexapdf/font/test_true_type_wrapper.rb +18 -1
  190. data/test/hexapdf/font/test_type1_wrapper.rb +15 -1
  191. data/test/hexapdf/layout/test_box.rb +14 -5
  192. data/test/hexapdf/layout/test_column_box.rb +65 -21
  193. data/test/hexapdf/layout/test_frame.rb +27 -15
  194. data/test/hexapdf/layout/test_image_box.rb +4 -0
  195. data/test/hexapdf/layout/test_inline_box.rb +17 -3
  196. data/test/hexapdf/layout/test_list_box.rb +84 -33
  197. data/test/hexapdf/layout/test_page_style.rb +3 -2
  198. data/test/hexapdf/layout/test_style.rb +60 -0
  199. data/test/hexapdf/layout/test_table_box.rb +728 -0
  200. data/test/hexapdf/layout/test_text_box.rb +26 -0
  201. data/test/hexapdf/layout/test_text_fragment.rb +33 -0
  202. data/test/hexapdf/layout/test_text_layouter.rb +36 -5
  203. data/test/hexapdf/test_composer.rb +10 -0
  204. data/test/hexapdf/test_dictionary.rb +10 -0
  205. data/test/hexapdf/test_dictionary_fields.rb +4 -1
  206. data/test/hexapdf/test_document.rb +5 -0
  207. data/test/hexapdf/test_filter.rb +8 -0
  208. data/test/hexapdf/test_importer.rb +9 -0
  209. data/test/hexapdf/test_object.rb +16 -5
  210. data/test/hexapdf/test_stream.rb +7 -0
  211. data/test/hexapdf/test_writer.rb +3 -3
  212. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +13 -5
  213. data/test/hexapdf/type/acro_form/test_form.rb +4 -3
  214. data/test/hexapdf/type/actions/test_set_ocg_state.rb +40 -0
  215. data/test/hexapdf/type/test_catalog.rb +11 -0
  216. data/test/hexapdf/type/test_form.rb +119 -0
  217. data/test/hexapdf/type/test_optional_content_configuration.rb +112 -0
  218. data/test/hexapdf/type/test_optional_content_group.rb +158 -0
  219. data/test/hexapdf/type/test_optional_content_properties.rb +109 -0
  220. data/test/hexapdf/type/test_page.rb +20 -6
  221. metadata +28 -8
@@ -71,21 +71,20 @@ module HexaPDF
71
71
  #
72
72
  # == Box Styles
73
73
  #
74
- # All box creation methods accept HexaPDF::Layout::Style objects or names for style objects
75
- # (defined via #style). This allows one to predefine certain styles (like first level heading,
76
- # second level heading, paragraph, ...) and consistently use them throughout the document
77
- # creation process.
74
+ # All box creation methods accept Layout::Style objects or names for style objects (defined via
75
+ # #style). This allows one to predefine certain styles (like first level heading, second level
76
+ # heading, paragraph, ...) and consistently use them throughout the document creation process.
78
77
  #
79
- # One style property, HexaPDF::Layout::Style#font, is handled specially:
78
+ # One style property, Layout::Style#font, is handled specially:
80
79
  #
81
80
  # * If no font is set on a style, the font "Times" is automatically set because otherwise there
82
81
  # would be problems with text drawing operations (font is the only style property that has no
83
82
  # valid default value).
84
83
  #
85
- # * Standard style objects only allow font wrapper objects to be set via the
86
- # HexaPDF::Layout::Style#font method. This class makes usage easier by allowing strings or an
87
- # array [name, options_hash] to be used, like with e.g Content::Canvas#font. So to use
88
- # Helvetica as font, one could just do:
84
+ # * Standard style objects only allow font wrapper objects to be set via the Layout::Style#font
85
+ # method. This class makes usage easier by allowing strings or an array [name, options_hash]
86
+ # to be used, like with e.g Content::Canvas#font. So to use Helvetica as font, one could just
87
+ # do:
89
88
  #
90
89
  # style.font = 'Helvetica'
91
90
  #
@@ -108,11 +107,14 @@ module HexaPDF
108
107
  # text, formatted_text, image, ...:: Any method accepted by the Layout class without the _box
109
108
  # suffix.
110
109
  #
111
- # list, column, ...:: Any name registered for the configuration option +layout.boxes.map+.
110
+ # list, column, ...:: Any name registered with the configuration option +layout.boxes.map+.
111
+ #
112
+ # The special method #multiple allows adding multiple boxes as a single array to the collected
113
+ # children.
112
114
  #
113
115
  # Example:
114
116
  #
115
- # document.layout.box(:list) do |list|
117
+ # document.layout.box(:list) do |list| # list is a ChildrenCollector
116
118
  # list.text_box("Some text here") # layout method
117
119
  # list.image(image_path) # layout method without _box suffix
118
120
  # list.column(columns: 3) do |column| # registered box name
@@ -166,7 +168,7 @@ module HexaPDF
166
168
 
167
169
  end
168
170
 
169
- # The mapping of style name (a Symbol) to HexaPDF::Layout::Style instance.
171
+ # The mapping of style name (a Symbol) to Layout::Style instance.
170
172
  attr_reader :styles
171
173
 
172
174
  # Creates a new Layout object for the given PDF document.
@@ -179,20 +181,20 @@ module HexaPDF
179
181
  # layout.style(name) -> style
180
182
  # layout.style(name, base: :base, **properties) -> style
181
183
  #
182
- # Creates or updates the HexaPDF::Layout::Style object called +name+ with the given property
183
- # values and returns it.
184
+ # Creates or updates the Layout::Style object called +name+ with the given property values and
185
+ # returns it.
186
+ #
187
+ # If neither +base+ nor any style properties are specified, the style +name+ is just returned.
184
188
  #
185
189
  # This method allows convenient access to the stored styles and to update them. Such styles
186
190
  # can then be used by name in the various box creation methods, e.g. #text_box or #image_box.
187
191
  #
188
- # If neither +base+ nor any style properties are specified, the style +name+ is just returned.
189
- #
190
192
  # If the style +name+ does not exist yet and the argument +base+ specifies the name of another
191
193
  # style, that style is duplicated and used as basis for the style. This also means that the
192
194
  # referenced +base+ style needs be defined first!
193
195
  #
194
- # The special name :base should be used for setting the base style which is used when no
195
- # specific style is set.
196
+ # The special name :base should be used for setting the base style which is used for the
197
+ # +base+ argument when no specific style is specified.
196
198
  #
197
199
  # Note that the style property 'font' is handled specially, see the class documentation for
198
200
  # details.
@@ -269,26 +271,26 @@ module HexaPDF
269
271
  #
270
272
  # +style+, +style_properties+::
271
273
  # The box and the text are styled using the given +style+. This can either be a style name
272
- # set via #style or anything HexaPDF::Layout::Style::create accepts. If any additional
274
+ # set via #style or anything Layout::Style::create accepts. If any additional
273
275
  # +style_properties+ are specified, the style is duplicated and the additional styles are
274
276
  # applied.
275
277
  #
276
278
  # +properties+::
277
- # This can be used to set custom properties on the created text box. See Box#properties
278
- # for details and usage.
279
+ # This can be used to set custom properties on the created text box. See
280
+ # Layout::Box#properties for details and usage.
279
281
  #
280
282
  # +box_style+::
281
283
  # Sometimes it is necessary for the box to have a different style than the text, e.g. when
282
284
  # using overlays. In such a case use +box_style+ for specifiying the style of the box (a
283
- # style name set via #style or anything HexaPDF::Layout::Style::create accepts).
285
+ # style name set via #style or anything Layout::Style::create accepts).
284
286
  #
285
287
  # The +style+ together with the +style_properties+ will be used for the text style.
286
288
  #
287
289
  # Examples:
288
290
  #
289
- # layout.text_box("Test " * 15)
291
+ # layout.text_box("Test is on " * 15)
290
292
  # layout.text_box("Now " * 7, width: 100)
291
- # layout.text_box("Another test", font_size: 15, fill_color: "green")
293
+ # layout.text_box("Another test", font_size: 15, fill_color: "hp-blue")
292
294
  # layout.text_box("Different box style", fill_color: 'white', box_style: {
293
295
  # underlays: [->(c, b) { c.rectangle(0, 0, b.content_width, b.content_height).fill }]
294
296
  # })
@@ -307,7 +309,8 @@ module HexaPDF
307
309
  # formatted differently.
308
310
  #
309
311
  # The argument +data+ needs to be an array of String, HexaPDF::Layout::InlineBox and/or Hash
310
- # objects and is transformed so that it is suitable as argument for the text box:
312
+ # objects and is transformed so that it is suitable as argument for the text box
313
+ # initialization method.
311
314
  #
312
315
  # * A String object is treated like {text: data}.
313
316
  #
@@ -322,9 +325,9 @@ module HexaPDF
322
325
  # for the text. If this is set and :box is not, the hash will be transformed into a
323
326
  # text fragment with an appropriate link overlay.
324
327
  #
325
- # style:: The style to be use as base style instead of the style created from the +style+
326
- # and +style_properties+ arguments. See HexaPDF::Layout::Style::create for allowed
327
- # values.
328
+ # style:: The style to use as base style instead of the style created from the +style+ and
329
+ # +style_properties+ arguments. This can either be a style name set via #style or
330
+ # anything HexaPDF::Layout::Style::create allows.
328
331
  #
329
332
  # If any style properties are set, the used style is duplicated and the additional
330
333
  # properties applied.
@@ -336,23 +339,42 @@ module HexaPDF
336
339
  #
337
340
  # The value must be one or more (as an array) positional arguments to be used with the
338
341
  # #inline_box method. The rest of the hash keys are passed as keyword arguments to
339
- # #inline_box except for :block that value of which would be passed as the block.
342
+ # #inline_box except for :block which would be passed as the block.
340
343
  #
341
344
  # See #text_box for details on +width+, +height+, +style+, +style_properties+, +properties+
342
345
  # and +box_style+.
343
346
  #
344
347
  # Examples:
345
348
  #
349
+ # # Text without special styling
346
350
  # layout.formatted_text_box(["Some string"])
347
- # layout.formatted_text_box(["Some ", {text: "string", fill_color: 128}])
348
- # layout.formatted_text_box(["Some ", {link: "https://example.com",
349
- # fill_color: 'blue', text: "Example"}])
350
- # layout.formatted_text_box(["Some ", {text: "string", style: {font_size: 20}}])
351
- # layout.formatted_text_box(["Some ", {box: [:text, "string"], valign: :top}])
351
+ #
352
+ # # A predefined inline box
353
+ # ibox = layout.inline_box(:text, 'Hello')
354
+ # layout.formatted_text_box([ibox])
355
+ #
356
+ # # Text with styling properties
357
+ # layout.formatted_text_box([{text: "string", fill_color: 128}])
358
+ #
359
+ # # Text referencing a base style
360
+ # layout.formatted_text_box([{text: "string", style: :bold}])
361
+ #
362
+ # # Text with a link
363
+ # layout.formatted_text_box([{link: "https://example.com",
364
+ # fill_color: 'blue', text: "Example"}])
365
+ #
366
+ # # Inline boxes created from the given data
367
+ # layout.formatted_text_box([{box: [:text, "string"], valign: :top}])
352
368
  # block = lambda {|list| list.text("First item"); list.text("Second item") }
353
369
  # layout.formatted_text_box(["Some ", {box: :list, item_spacing: 10, block: block}])
354
370
  #
355
- # See: #text_box, HexaPDF::Layout::TextBox, HexaPDF::Layout::TextFragment
371
+ # # Combining the above variants
372
+ # layout.formatted_text_box(["Hello", {box: [:text, 'World!']}, "Here comes a ",
373
+ # {link: 'https://example.com', text: 'link'}, '!',
374
+ # {text: 'And more!', style: :bold, font_size: 20}])
375
+ #
376
+ # See: #text_box, #inline_box, HexaPDF::Layout::TextBox, HexaPDF::Layout::TextFragment,
377
+ # HexaPDF::Layout::InlineBox
356
378
  def formatted_text_box(data, width: 0, height: 0, style: nil, properties: nil, box_style: nil,
357
379
  **style_properties)
358
380
  style = retrieve_style(style, style_properties)
@@ -406,25 +428,131 @@ module HexaPDF
406
428
  properties: properties, style: style)
407
429
  end
408
430
 
431
+ # This helper class is used by Layout#table_box to allow specifying the keyword arguments used
432
+ # when converting cell data to box instances.
433
+ class CellArgumentCollector
434
+
435
+ # Stores a single keyword argument definition for a number of rows/columns.
436
+ ArgumentInfo = Struct.new(:rows, :cols, :args)
437
+
438
+ # Returns all stored ArgumentInfo instances.
439
+ attr_reader :argument_infos
440
+
441
+ # Creates a new instance, providing the number of rows and columns of the table.
442
+ def initialize(number_of_rows, number_of_columns)
443
+ @argument_infos = []
444
+ @number_of_rows = number_of_rows
445
+ @number_of_columns = number_of_columns
446
+ end
447
+
448
+ # Stores the keyword arguments in +args+ for the given 0-based rows and columns which can
449
+ # either be a single number or a range of numbers.
450
+ def []=(rows = 0..-1, cols = 0..-1, args)
451
+ rows = adjust_range(rows.kind_of?(Integer) ? rows..rows : rows, @number_of_rows)
452
+ cols = adjust_range(cols.kind_of?(Integer) ? cols..cols : cols, @number_of_columns)
453
+ @argument_infos << ArgumentInfo.new(rows, cols, args)
454
+ end
455
+
456
+ # Retrieves the merged keyword arguments for the cell in +row+ and +col+.
457
+ #
458
+ # Earlier defined arguments are overridden by later ones.
459
+ def retrieve_arguments_for(row, col)
460
+ @argument_infos.each_with_object({}) do |arg_info, result|
461
+ next unless arg_info.rows.cover?(row) && arg_info.cols.cover?(col)
462
+ result.update(arg_info.args)
463
+ end
464
+ end
465
+
466
+ private
467
+
468
+ # Adjusts the +range+ so that both the begin and the end of the range are zero or positive
469
+ # integers smaller than +max+.
470
+ def adjust_range(range, max)
471
+ (range.begin % max)..(range.end % max)
472
+ end
473
+
474
+ end
475
+
476
+ # Creates a HexaPDF::Layout::TableBox for the given table data.
477
+ #
478
+ # This method is a small wrapper around the actual class and mainly facilitates transforming
479
+ # the contents of the +data+ into the box instances needed by the table box implementation.
480
+ #
481
+ # In addition to everything the table box implementation allows for +data+, it is also
482
+ # possible to specify strings as cell contents. Those strings will be converted to text boxes
483
+ # by using the #text_box method. *Note* that this functionality is *not* available for the
484
+ # header and footer!
485
+ #
486
+ # Additional arguments for the #text_box invocations can be specified using the optional block
487
+ # that yields a CellArgumentCollector instance. This allows customization of the text boxes.
488
+ # By specifying the special key +:cell+ it is also possible to assign style properties to the
489
+ # cells themselves.
490
+ #
491
+ # See HexaPDF::Layout::TableBox::new for details on +column_widths+, +header+, +footer+, and
492
+ # +cell_style+.
493
+ #
494
+ # See #text_box for details on +width+, +height+, +style+, +style_properties+ and
495
+ # +properties+.
496
+ #
497
+ # Examples:
498
+ #
499
+ # layout.table_box([[layout.text('A'), layout.text('B')],
500
+ # [layout.image(image_path), layout.text('D')]]
501
+ # layout.table_box([['A', 'B'], [layout.image(image_path), 'D]]) # same as above
502
+ #
503
+ # layout.table_box([['A', 'B'], ['C', 'D]]) do |args|
504
+ # # assign the predefined style :cell_text to all texts
505
+ # args[] = {style: :cell_text}
506
+ # # row 0 has a grey background and bold text
507
+ # args[0] = {font: ['Helvetica', variant: :bold], cell: {background_color: 'eee'}}
508
+ # # text in last column is right aligned
509
+ # args[0..-1, -1] = {align: :right}
510
+ # end
511
+ #
512
+ # See: HexaPDF::Layout::TableBox
513
+ def table_box(data, column_widths: nil, header: nil, footer: nil, cell_style: nil,
514
+ width: 0, height: 0, style: nil, properties: nil, **style_properties)
515
+ style = retrieve_style(style, style_properties)
516
+ cells = HexaPDF::Layout::TableBox::Cells.new(data, cell_style: cell_style)
517
+ collector = CellArgumentCollector.new(cells.number_of_rows, cells.number_of_columns)
518
+ yield(collector) if block_given?
519
+ cells.style do |cell|
520
+ args = collector.retrieve_arguments_for(cell.row, cell.column)
521
+ cstyle = args.delete(:cell)
522
+ result = case cell.children
523
+ when Array, HexaPDF::Layout::Box
524
+ cell.children
525
+ else
526
+ text_box(cell.children.to_s, **args)
527
+ end
528
+ cell.children = result
529
+ cell.style.update(**cstyle) if cstyle
530
+ end
531
+ box_class_for_name(:table).new(cells: cells, column_widths: column_widths, header: header,
532
+ footer: footer, cell_style: cell_style, width: width,
533
+ height: height, properties: properties, style: style)
534
+ end
535
+
409
536
  LOREM_IPSUM = [ # :nodoc:
410
537
  "Lorem ipsum dolor sit amet, con\u{00AD}sectetur adipis\u{00AD}cing elit, sed " \
411
538
  "do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
412
539
  "Ut enim ad minim veniam, quis nostrud exer\u{00AD}citation ullamco laboris nisi ut " \
413
- "aliquip ex ea commodo consequat. ",
540
+ "aliquip ex ea commodo consequat.",
414
541
  "Duis aute irure dolor in reprehen\u{00AD}derit in voluptate velit esse cillum dolore " \
415
- "eu fugiat nulla pariatur. ",
542
+ "eu fugiat nulla pariatur.",
416
543
  "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt " \
417
544
  "mollit anim id est laborum.",
418
545
  ]
419
546
 
420
- # Uses #text_box to create +count+ paragraphs of lorem ipsum text.
547
+ # Uses #text_box to create +count+ paragraphs with +sentences+ number of sentences (1 to 4) of
548
+ # lorem ipsum text.
421
549
  #
422
550
  # The +text_box_properties+ arguments are passed as is to #text_box.
423
551
  def lorem_ipsum_box(sentences: 4, count: 1, **text_box_properties)
424
552
  text_box(([LOREM_IPSUM[0, sentences].join(" ")] * count).join("\n\n"), **text_box_properties)
425
553
  end
426
554
 
427
- BOX_METHOD_NAMES = [:text, :formatted_text, :image, :lorem_ipsum] #:nodoc:
555
+ BOX_METHOD_NAMES = [:text, :formatted_text, :image, :table, :lorem_ipsum] #:nodoc:
428
556
 
429
557
  # Allows creating boxes using more convenient method names:
430
558
  #
@@ -479,6 +607,9 @@ module HexaPDF
479
607
  # Finally, a default font is set if necessary to ensure that the style object works in all
480
608
  # cases.
481
609
  def retrieve_style(style, properties = nil)
610
+ if style.kind_of?(Symbol) && !@styles.key?(style)
611
+ raise HexaPDF::Error, "Style #{style} not defined"
612
+ end
482
613
  style = HexaPDF::Layout::Style.create(@styles[style] || style || @styles[:base])
483
614
  style = style.dup.update(**properties) unless properties.nil? || properties.empty?
484
615
  style.font('Times') unless style.font?
@@ -39,7 +39,7 @@ require 'hexapdf/error'
39
39
  module HexaPDF
40
40
  class Document
41
41
 
42
- # This class provides methods for managing the pages of a PDF file.
42
+ # This class provides methods for managing the pages and page labels of a PDF file.
43
43
  #
44
44
  # For page manipulation it uses the methods of HexaPDF::Type::PageTreeNode underneath but
45
45
  # provides a more convenient interface.
@@ -178,7 +178,8 @@ module HexaPDF
178
178
  @document.catalog.pages.each_page(&block)
179
179
  end
180
180
 
181
- # Returns the number of pages in the PDF document. May be zero if the document has no pages.
181
+ # Returns the number of pages in the PDF document. May be zero if the document has no pages
182
+ # yet.
182
183
  def count
183
184
  @document.catalog.pages.page_count
184
185
  end
@@ -245,7 +246,7 @@ module HexaPDF
245
246
 
246
247
  labels = @document.catalog.page_labels(create: true)
247
248
  labels.add_entry(start_index, page_label)
248
- labels.add_entry(0, {S: :d}) unless labels.find_entry(0)
249
+ labels.add_entry(0, {S: :D}) unless labels.find_entry(0)
249
250
 
250
251
  page_label
251
252
  end