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,397 @@
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'
38
+
39
+ module HexaPDF
40
+ class Document
41
+
42
+ # This class provides methods for working with classes in the HexaPDF::Layout module.
43
+ #
44
+ # Often times the layout related classes are used through HexaPDF::Composer which makes it easy
45
+ # to create documents. However, sometimes one wants to have a bit more control or do something
46
+ # special and use the HexaPDF::Layout classes directly. This is possible but it is better to use
47
+ # those classes through an instance of this classs because it makes it more convenient and ties
48
+ # everything together. Incidentally, HexaPDF::Composer relies on this class for a good part of
49
+ # its work.
50
+ #
51
+ #
52
+ # == Boxes
53
+ #
54
+ # The main focus of the class is on providing convenience methods for creating box objects. The
55
+ # most often used box classes like HexaPDF::Layout::TextBox or HexaPDF::Layout::ImagebBox can be
56
+ # created through dedicated methods.
57
+ #
58
+ # Other, more general boxes don't have their own method but can be created through the general
59
+ # #box method.
60
+ #
61
+ #
62
+ # == Box Styles
63
+ #
64
+ # All box creation methods accept HexaPDF::Layout::Style objects or names for style objects
65
+ # (defined via #style). This allows one to predefine certain styles (like first level heading,
66
+ # second level heading, paragraph, ...) and consistently use them throughout the document
67
+ # creation process.
68
+ #
69
+ # One style property, HexaPDF::Layout::Style#font, is handled specially:
70
+ #
71
+ # * If no font is set on a style, the font "Times" is automatically set because otherwise there
72
+ # would be problems with text drawing operations (font is the only style property that has no
73
+ # valid default value).
74
+ #
75
+ # * Standard style objects only allow font wrapper objects to be set via the
76
+ # HexaPDF::Layout::Style#font method. This class makes usage easier by allowing strings or an
77
+ # array [name, options_hash] to be used, like with e.g Content::Canvas#font. So to use
78
+ # Helvetica as font, one could just do:
79
+ #
80
+ # style.font = 'Helvetica'
81
+ #
82
+ # And if Helvetica in its bold variant should be used it would be:
83
+ #
84
+ # style.font = ['Helvetica', variant: :bold]
85
+ #
86
+ class Layout
87
+
88
+ # This class is used when a box can contain child boxes and the creation of such boxes should
89
+ # be seemlessly doable when creating the parent node. It is yieled, for example, by Layout#box
90
+ # to collect the children for the created box.
91
+ #
92
+ # A box can be added to the list of collected children in the following ways:
93
+ #
94
+ # #<<:: This appends the given box to the list.
95
+ #
96
+ # text_box, formatted_text_box, image_box, ...:: Any method accepted by the Layout class.
97
+ #
98
+ # text, formatted_text, image, ...:: Any method accepted by the Layout class without the _box
99
+ # suffix.
100
+ #
101
+ # list, column, ...:: Any name registered for the configuration option +layout.boxes.map+.
102
+ #
103
+ # Example:
104
+ #
105
+ # document.layout.box(:list) do |list|
106
+ # list.text_box("Some text here") # layout method
107
+ # list.image(image_path) # layout method without _box suffix
108
+ # list.column(columns: 3) do |column| # registered box name
109
+ # column.text("Text in column")
110
+ # column << document.layout.lorem_ipsum_box # adding a Box instance
111
+ # end
112
+ # end
113
+ class ChildrenCollector
114
+
115
+ # Creates a children collector, yields it and then returns the collected children.
116
+ def self.collect(layout)
117
+ collector = new(layout)
118
+ yield(collector)
119
+ collector.children
120
+ end
121
+
122
+ # The collected children
123
+ attr_reader :children
124
+
125
+ # Create a new ChildrenCollector for the given +layout+ (a HexaPDF::Document::Layout)
126
+ # instance.
127
+ def initialize(layout)
128
+ @layout = layout
129
+ @layout_boxes_map = layout.instance_variable_get(:@document).config['layout.boxes.map']
130
+ @children = []
131
+ end
132
+
133
+ # :nodoc:
134
+ def method_missing(name, *args, **kwargs, &block)
135
+ if @layout.respond_to?(name)
136
+ @children << @layout.send(name, *args, **kwargs, &block)
137
+ elsif @layout.respond_to?("#{name}_box")
138
+ @children << @layout.send("#{name}_box", *args, **kwargs, &block)
139
+ elsif @layout_boxes_map.key?(name)
140
+ @children << @layout.box(name, *args, **kwargs, &block)
141
+ else
142
+ super
143
+ end
144
+ end
145
+
146
+ # :nodoc:
147
+ def respond_to_missing?(name, _private)
148
+ @layout.respond_to?(name) ||
149
+ @layout.respond_to?("#{name}_box") ||
150
+ @layout_boxes_map.key?(name) ||
151
+ super
152
+ end
153
+
154
+ # Appends the given box to the list of collected children.
155
+ def <<(box)
156
+ @children << box
157
+ end
158
+
159
+ # Yields a ChildrenCollector instance and adds the collected children as a single array to
160
+ # the list of collected children.
161
+ def multiple(&block)
162
+ @children << self.class.collect(@layout, &block)
163
+ end
164
+
165
+ end
166
+
167
+ # The mapping of style name (a Symbol) to HexaPDF::Layout::Style instance.
168
+ attr_reader :styles
169
+
170
+ # Creates a new Layout object for the given PDF document.
171
+ def initialize(document)
172
+ @document = document
173
+ @styles = {base: HexaPDF::Layout::Style.new}
174
+ end
175
+
176
+ # :call-seq:
177
+ # layout.style(name) -> style
178
+ # layout.style(name, base: :base, **properties) -> style
179
+ #
180
+ # Creates or updates the HexaPDF::Layout::Style object called +name+ with the given property
181
+ # values and returns it.
182
+ #
183
+ # This method allows convenient access to the stored styles and to update them. Such styles
184
+ # can then be used by name in the various box creation methods, e.g. #text_box or #image_box.
185
+ #
186
+ # If neither +base+ nor any style properties are specified, the style +name+ is just returned.
187
+ #
188
+ # If the style +name+ does not exist yet and the argument +base+ specifies the name of another
189
+ # style, that style is duplicated and used as basis for the style. This also means that the
190
+ # referenced +base+ style needs be defined first!
191
+ #
192
+ # The special name :base should be used for setting the base style which is used when no
193
+ # specific style is set.
194
+ #
195
+ # Note that the style property 'font' is handled specially, see the class documentation for
196
+ # details.
197
+ #
198
+ # Example:
199
+ #
200
+ # layout.style(:base, font_size: 12, leading: 1.2)
201
+ # layout.style(:header, font: 'Helvetica', fill_color: "008")
202
+ # layout.style(:header1, base: :header, font_size: 30)
203
+ #
204
+ # See: HexaPDF::Layout::Style
205
+ def style(name, base: :base, **properties)
206
+ style = @styles[name] ||= (@styles.key?(base) ? @styles[base].dup : HexaPDF::Layout::Style.new)
207
+ style.update(**properties) unless properties.empty?
208
+ style
209
+ end
210
+
211
+ # Creates the named box and returns it.
212
+ #
213
+ # The +name+ argument refers to the registered name of the box class that is looked up in the
214
+ # 'layout.boxes.map' configuration option. The +box_options+ are passed as-is to the
215
+ # initialization method of that box class
216
+ #
217
+ # If a block is provided, a ChildrenCollector is yielded and the collected children are passed
218
+ # to the box initialization method via the :children keyword argument.
219
+ #
220
+ # See #text_box for details on +width+, +height+ and +style+ (note that there is no
221
+ # +style_properties+ argument).
222
+ #
223
+ # Example:
224
+ #
225
+ # doc.layout.box(:column, columns: 2, gap: 15) # => column_box_instance
226
+ # doc.layout.box(:column) do |column| # column box with one child
227
+ # column.lorem_ipsum
228
+ # end
229
+ def box(name, width: 0, height: 0, style: nil, **box_options, &block)
230
+ if block_given? && !box_options.key?(:children)
231
+ box_options[:children] = ChildrenCollector.collect(self, &block)
232
+ end
233
+ box_class_for_name(name).new(width: width, height: height,
234
+ style: retrieve_style(style), **box_options)
235
+ end
236
+
237
+ # Creates a HexaPDF::Layout::TextBox for the given text.
238
+ #
239
+ # This method is of the two main methods for creating text boxes, the other being
240
+ # #formatted_text_box.
241
+ #
242
+ # +width+, +height+::
243
+ # The arguments +width+ and +height+ are used as constraints and are respected when
244
+ # fitting the box. The default value of 0 means that no constraints are set.
245
+ #
246
+ # +style+, +style_properties+::
247
+ # The box and the text are styled using the given +style+. This can either be a style name
248
+ # set via #style or anything HexaPDF::Layout::Style::create accepts. If any additional
249
+ # +style_properties+ are specified, the style is duplicated and the additional styles are
250
+ # applied.
251
+ #
252
+ # +box_style+::
253
+ # Sometimes it is necessary for the box to have a different style than the text, e.g. when
254
+ # using overlays. In such a case use +box_style+ for specifiying the style of the box (a
255
+ # style name set via #style or anything HexaPDF::Layout::Style::create accepts).
256
+ #
257
+ # The +style+ together with the +style_properties+ will be used for the text style.
258
+ #
259
+ # Examples:
260
+ #
261
+ # layout.text("Test " * 15)
262
+ # layout.text("Now " * 7, width: 100)
263
+ # layout.text("Another test", font_size: 15, fill_color: "green")
264
+ # layout.text("Different box style", fill_color: 'white', box_style: {
265
+ # underlays: [->(c, b) { c.rectangle(0, 0, b.content_width, b.content_height).fill }]
266
+ # })
267
+ #
268
+ # See: #formatted_text_box, HexaPDF::Layout::TextBox, HexaPDF::Layout::TextFragment
269
+ def text_box(text, width: 0, height: 0, style: nil, box_style: nil, **style_properties)
270
+ style = retrieve_style(style, style_properties)
271
+ box_style = (box_style ? retrieve_style(box_style) : style)
272
+ box_class_for_name(:text).new(items: [HexaPDF::Layout::TextFragment.create(text, style)],
273
+ width: width, height: height, style: box_style)
274
+ end
275
+
276
+ # Creates a HexaPDF::Layout::TextBox like #text_box but allows parts of the text to be
277
+ # formatted differently.
278
+ #
279
+ # The argument +data+ needs to be an array of String and/or Hash objects:
280
+ #
281
+ # * A String object is treated like {text: data}.
282
+ #
283
+ # * Hashes can contain any style properties and the following special keys:
284
+ #
285
+ # text:: The text to be formatted.
286
+ #
287
+ # link:: A URL that should be linked to. If no text is provided but a link, the link is used
288
+ # as text.
289
+ #
290
+ # style:: The style to be use as base style instead of the style created from the +style+
291
+ # and +style_properties+ arguments. See HexaPDF::Layout::Style::create for allowed
292
+ # values.
293
+ #
294
+ # If any style properties are set, the used style is duplicated and the additional
295
+ # properties applied.
296
+ #
297
+ # See #text_box for details on +width+, +height+, +style+, +style_properties+ and +box_style+.
298
+ #
299
+ # Examples:
300
+ #
301
+ # layout.formatted_text_box(["Some string"])
302
+ # layout.formatted_text_box(["Some ", {text: "string", fill_color: 128}])
303
+ # layout.formatted_text_box(["Some ", {link: "https://example.com",
304
+ # fill_color: 'blue', text: "Example"}])
305
+ # layout.formatted_text_box(["Some ", {text: "string", style: {font_size: 20}}])
306
+ #
307
+ # See: #text_box, HexaPDF::Layout::TextBox, HexaPDF::Layout::TextFragment
308
+ def formatted_text_box(data, width: 0, height: 0, style: nil, box_style: nil, **style_properties)
309
+ style = retrieve_style(style, style_properties)
310
+ box_style = (box_style ? retrieve_style(box_style) : style)
311
+ data.map! do |hash|
312
+ if hash.kind_of?(String)
313
+ HexaPDF::Layout::TextFragment.create(hash, style)
314
+ else
315
+ link = hash.delete(:link)
316
+ (hash[:overlays] ||= []) << [:link, {uri: link}] if link
317
+ text = hash.delete(:text) || link || ""
318
+ HexaPDF::Layout::TextFragment.create(text, retrieve_style(hash.delete(:style) || style, hash))
319
+ end
320
+ end
321
+ box_class_for_name(:text).new(items: data, width: width, height: height, style: box_style)
322
+ end
323
+
324
+ # Creates a HexaPDF::Layout::ImageBox for the given image.
325
+ #
326
+ # The +file+ argument can be anything that is accepted by HexaPDF::Document::Images#add or a
327
+ # HexaPDF::Type::Form object.
328
+ #
329
+ # See #text_box for details on +width+, +height+, +style+ and +style_properties+.
330
+ #
331
+ # Examples:
332
+ #
333
+ # layout.image_box(machu_picchu, border: {width: 3})
334
+ # layout.image_box(machu_picchu, height: 30)
335
+ #
336
+ # See: HexaPDF::Layout::ImageBox
337
+ def image_box(file, width: 0, height: 0, style: nil, **style_properties)
338
+ style = retrieve_style(style, style_properties)
339
+ image = file.kind_of?(HexaPDF::Stream) ? file : @document.images.add(file)
340
+ box_class_for_name(:image).new(image: image, width: width, height: height, style: style)
341
+ end
342
+
343
+ # :nodoc:
344
+ LOREM_IPSUM = [
345
+ "Lorem ipsum dolor sit amet, con\u{00AD}sectetur adipis\u{00AD}cing elit, sed " \
346
+ "do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
347
+ "Ut enim ad minim veniam, quis nostrud exer\u{00AD}citation ullamco laboris nisi ut " \
348
+ "aliquip ex ea commodo consequat. ",
349
+ "Duis aute irure dolor in reprehen\u{00AD}derit in voluptate velit esse cillum dolore " \
350
+ "eu fugiat nulla pariatur. ",
351
+ "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt " \
352
+ "mollit anim id est laborum.",
353
+ ]
354
+
355
+ # Uses #text_box to create +count+ paragraphs of lorem ipsum text.
356
+ #
357
+ # The +text_box_properties+ arguments are passed as is to #text_box.
358
+ def lorem_ipsum_box(sentences: 4, count: 1, **text_box_properties)
359
+ text_box(([LOREM_IPSUM[0, sentences].join(" ")] * count).join("\n\n"), **text_box_properties)
360
+ end
361
+
362
+ private
363
+
364
+ # Returns the configured box class for the given +name+.
365
+ def box_class_for_name(name)
366
+ @document.config.constantize('layout.boxes.map', name) do
367
+ raise HexaPDF::Error, "Couldn't retrieve box class #{name} from configuration"
368
+ end
369
+ end
370
+
371
+ # Retrieves the appropriate HexaPDF::Layout::Style object based on the +style+ and +properties+
372
+ # arguments.
373
+ #
374
+ # The +style+ argument specifies the style to retrieve. It can either be a registered style
375
+ # name (see #style), a hash with style properties or +nil+. In the latter case the registered
376
+ # style :base is used
377
+ #
378
+ # If the +properties+ hash is not empty, the retrieved style is duplicated and the properties
379
+ # hash is applied to it.
380
+ #
381
+ # Finally, a default font is set if necessary to ensure that the style object works in all
382
+ # cases.
383
+ def retrieve_style(style, properties = nil)
384
+ style = HexaPDF::Layout::Style.create(@styles[style] || style || @styles[:base])
385
+ style = style.dup.update(**properties) unless properties.nil? || properties.empty?
386
+ style.font('Times') unless style.font?
387
+ unless style.font.respond_to?(:pdf_object)
388
+ name, options = *style.font
389
+ style.font(@document.fonts.add(name, **(options || {})))
390
+ end
391
+ style
392
+ end
393
+
394
+ end
395
+
396
+ end
397
+ 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
@@ -101,6 +101,22 @@ module HexaPDF
101
101
  @document.catalog.pages.insert_page(index, page)
102
102
  end
103
103
 
104
+ # :call-seq:
105
+ # pages.move(page, to_index)
106
+ # pages.move(index, to_index)
107
+ #
108
+ # Moves the given page or the page at the position specified by the zero-based index to the
109
+ # +to_index+ position.
110
+ #
111
+ # If the page that should be moved, doesn't exist or is invalid, an error is raised.
112
+ #
113
+ # Negative indices count backwards from the end, i.e. -1 is the last page. When using a
114
+ # negative index, the page will be moved after that element. So using an index of -1 will
115
+ # move the page after the last page.
116
+ def move(page, to_index)
117
+ @document.catalog.pages.move_page(page, to_index)
118
+ end
119
+
104
120
  # Deletes the given page object from the document's page tree and the document.
105
121
  #
106
122
  # Also see: HexaPDF::Type::PageTreeNode#delete_page
@@ -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
@@ -239,7 +239,7 @@ module HexaPDF
239
239
  signature[:M] = Time.now
240
240
 
241
241
  io = if file_or_io.kind_of?(String)
242
- File.open(file_or_io, 'w+')
242
+ File.open(file_or_io, 'wb+')
243
243
  else
244
244
  file_or_io
245
245
  end
@@ -247,8 +247,9 @@ module HexaPDF
247
247
  # Save the current state so that we can determine the correct /ByteRange value and set the
248
248
  # values
249
249
  handler.finalize_objects(signature_field, signature)
250
- section = @document.write(io, incremental: true, **write_options)
251
- data = section.map {|oid, _gen, entry| [entry.pos, oid] if entry.in_use? }.compact.sort
250
+ start_xref_position, section = @document.write(io, incremental: true, **write_options)
251
+ data = section.map {|oid, _gen, entry| [entry.pos, oid] if entry.in_use? }.compact.sort <<
252
+ [start_xref_position, nil]
252
253
  index = data.index {|_pos, oid| oid == signature.oid }
253
254
  signature_offset = data[index][0]
254
255
  signature_length = data[index + 1][0] - data[index][0]
@@ -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
@@ -107,6 +107,7 @@ module HexaPDF
107
107
  autoload(:Files, 'hexapdf/document/files')
108
108
  autoload(:Signatures, 'hexapdf/document/signatures')
109
109
  autoload(:Destinations, 'hexapdf/document/destinations')
110
+ autoload(:Layout, 'hexapdf/document/layout')
110
111
 
111
112
  # :call-seq:
112
113
  # Document.open(filename, **docargs) -> doc
@@ -478,6 +479,12 @@ module HexaPDF
478
479
  @destinations ||= Destinations.new(self)
479
480
  end
480
481
 
482
+ # Returns the Layout object that provides convenience methods for working with the
483
+ # HexaPDF::Layout classes for document layout.
484
+ def layout
485
+ @layout ||= Layout.new(self)
486
+ end
487
+
481
488
  # Returns the main AcroForm object for dealing with interactive forms.
482
489
  #
483
490
  # See HexaPDF::Type::Catalog#acro_form for details on the arguments.
@@ -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
@@ -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
@@ -40,30 +40,39 @@ require 'hexapdf/encryption/arc4'
40
40
  module HexaPDF
41
41
  module Encryption
42
42
 
43
- # Implementation of the general encryption algorithm ARC4 using OpenSSL as backend.
44
- #
45
- # See: PDF1.7 s7.6.2
46
- class FastARC4
43
+ begin
44
+ OpenSSL::Cipher.new('rc4')
47
45
 
48
- prepend ARC4
46
+ # Implementation of the general encryption algorithm ARC4 using OpenSSL as backend.
47
+ #
48
+ # See: PDF1.7 s7.6.2
49
+ class FastARC4
49
50
 
50
- # Creates a new FastARC4 object using the given encryption key.
51
- def initialize(key)
52
- @cipher = OpenSSL::Cipher.new('rc4')
53
- @cipher.key_len = key.length
54
- @cipher.key = key
55
- end
51
+ prepend ARC4
56
52
 
57
- # Processes the given data.
58
- #
59
- # Since this is a symmetric algorithm, the same method can be used for encryption and
60
- # decryption.
61
- def process(data)
62
- @cipher.update(data)
63
- end
64
- alias decrypt process
65
- alias encrypt process
53
+ # Creates a new FastARC4 object using the given encryption key.
54
+ def initialize(key)
55
+ @cipher = OpenSSL::Cipher.new('rc4')
56
+ @cipher.key_len = key.length
57
+ @cipher.key = key
58
+ end
59
+
60
+ # Processes the given data.
61
+ #
62
+ # Since this is a symmetric algorithm, the same method can be used for encryption and
63
+ # decryption.
64
+ def process(data)
65
+ @cipher.update(data)
66
+ end
67
+ alias decrypt process
68
+ alias encrypt process
66
69
 
70
+ end
71
+ rescue OpenSSL::Cipher::CipherError
72
+ # Ruby OpenSSL 3.0 needs a special configuration file that enables the legacy provider so that
73
+ # RC4 works. This would need to be done by each user. So we need the fallback.
74
+ require 'hexapdf/encryption/ruby_arc4'
75
+ FastARC4 = RubyARC4
67
76
  end
68
77
 
69
78
  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
@@ -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
@@ -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
data/lib/hexapdf/error.rb CHANGED
@@ -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