hexapdf 0.21.1 → 0.24.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (253) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +137 -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 +20 -37
  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 +31 -4
  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 +19 -6
  18. data/lib/hexapdf/cli/merge.rb +1 -1
  19. data/lib/hexapdf/cli/modify.rb +24 -4
  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 +66 -125
  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 +2 -2
  42. data/lib/hexapdf/document/destinations.rb +396 -0
  43. data/lib/hexapdf/document/files.rb +1 -1
  44. data/lib/hexapdf/document/fonts.rb +1 -1
  45. data/lib/hexapdf/document/images.rb +1 -1
  46. data/lib/hexapdf/document/layout.rb +397 -0
  47. data/lib/hexapdf/document/pages.rb +17 -1
  48. data/lib/hexapdf/document/signatures.rb +5 -4
  49. data/lib/hexapdf/document.rb +46 -90
  50. data/lib/hexapdf/encryption/aes.rb +1 -1
  51. data/lib/hexapdf/encryption/arc4.rb +1 -1
  52. data/lib/hexapdf/encryption/fast_aes.rb +1 -1
  53. data/lib/hexapdf/encryption/fast_arc4.rb +30 -21
  54. data/lib/hexapdf/encryption/identity.rb +1 -1
  55. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  56. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  57. data/lib/hexapdf/encryption/security_handler.rb +1 -1
  58. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  59. data/lib/hexapdf/encryption.rb +1 -1
  60. data/lib/hexapdf/error.rb +1 -1
  61. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  62. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  63. data/lib/hexapdf/filter/crypt.rb +1 -1
  64. data/lib/hexapdf/filter/encryption.rb +1 -1
  65. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  66. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  67. data/lib/hexapdf/filter/pass_through.rb +1 -1
  68. data/lib/hexapdf/filter/predictor.rb +1 -1
  69. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  70. data/lib/hexapdf/filter.rb +1 -1
  71. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  72. data/lib/hexapdf/font/cmap/writer.rb +1 -1
  73. data/lib/hexapdf/font/cmap.rb +1 -1
  74. data/lib/hexapdf/font/encoding/base.rb +1 -1
  75. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  76. data/lib/hexapdf/font/encoding/glyph_list.rb +2 -2
  77. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  78. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
  79. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  80. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  81. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
  82. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  83. data/lib/hexapdf/font/encoding.rb +1 -1
  84. data/lib/hexapdf/font/invalid_glyph.rb +1 -1
  85. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  86. data/lib/hexapdf/font/true_type/font.rb +1 -1
  87. data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
  88. data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
  89. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  90. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
  91. data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
  92. data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
  93. data/lib/hexapdf/font/true_type/table/head.rb +1 -1
  94. data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
  95. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
  96. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  97. data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
  98. data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
  99. data/lib/hexapdf/font/true_type/table/name.rb +1 -1
  100. data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
  101. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  102. data/lib/hexapdf/font/true_type/table.rb +1 -1
  103. data/lib/hexapdf/font/true_type.rb +1 -1
  104. data/lib/hexapdf/font/true_type_wrapper.rb +1 -1
  105. data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
  106. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  107. data/lib/hexapdf/font/type1/font.rb +1 -1
  108. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  109. data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
  110. data/lib/hexapdf/font/type1.rb +1 -1
  111. data/lib/hexapdf/font/type1_wrapper.rb +1 -1
  112. data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
  113. data/lib/hexapdf/font_loader/from_file.rb +1 -1
  114. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  115. data/lib/hexapdf/font_loader.rb +1 -1
  116. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  117. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  118. data/lib/hexapdf/image_loader/png.rb +1 -1
  119. data/lib/hexapdf/image_loader.rb +1 -1
  120. data/lib/hexapdf/importer.rb +1 -1
  121. data/lib/hexapdf/layout/box.rb +121 -22
  122. data/lib/hexapdf/layout/box_fitter.rb +136 -0
  123. data/lib/hexapdf/layout/column_box.rb +168 -89
  124. data/lib/hexapdf/layout/frame.rb +155 -140
  125. data/lib/hexapdf/layout/image_box.rb +19 -4
  126. data/lib/hexapdf/layout/inline_box.rb +1 -1
  127. data/lib/hexapdf/layout/line.rb +1 -1
  128. data/lib/hexapdf/layout/list_box.rb +355 -0
  129. data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
  130. data/lib/hexapdf/layout/style.rb +285 -8
  131. data/lib/hexapdf/layout/text_box.rb +30 -11
  132. data/lib/hexapdf/layout/text_fragment.rb +3 -2
  133. data/lib/hexapdf/layout/text_layouter.rb +23 -3
  134. data/lib/hexapdf/layout/text_shaper.rb +1 -1
  135. data/lib/hexapdf/layout/width_from_polygon.rb +12 -7
  136. data/lib/hexapdf/layout.rb +4 -1
  137. data/lib/hexapdf/name_tree_node.rb +1 -1
  138. data/lib/hexapdf/number_tree_node.rb +1 -1
  139. data/lib/hexapdf/object.rb +1 -1
  140. data/lib/hexapdf/parser.rb +1 -8
  141. data/lib/hexapdf/pdf_array.rb +1 -1
  142. data/lib/hexapdf/rectangle.rb +1 -1
  143. data/lib/hexapdf/reference.rb +1 -1
  144. data/lib/hexapdf/revision.rb +9 -2
  145. data/lib/hexapdf/revisions.rb +152 -51
  146. data/lib/hexapdf/serializer.rb +1 -1
  147. data/lib/hexapdf/stream.rb +1 -1
  148. data/lib/hexapdf/task/dereference.rb +1 -1
  149. data/lib/hexapdf/task/optimize.rb +22 -12
  150. data/lib/hexapdf/task.rb +1 -1
  151. data/lib/hexapdf/tokenizer.rb +1 -1
  152. data/lib/hexapdf/type/acro_form/appearance_generator.rb +1 -1
  153. data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
  154. data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
  155. data/lib/hexapdf/type/acro_form/field.rb +1 -1
  156. data/lib/hexapdf/type/acro_form/form.rb +12 -6
  157. data/lib/hexapdf/type/acro_form/signature_field.rb +1 -1
  158. data/lib/hexapdf/type/acro_form/text_field.rb +9 -1
  159. data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
  160. data/lib/hexapdf/type/acro_form.rb +1 -1
  161. data/lib/hexapdf/type/action.rb +1 -1
  162. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  163. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  164. data/lib/hexapdf/type/actions/launch.rb +1 -1
  165. data/lib/hexapdf/type/actions/uri.rb +1 -1
  166. data/lib/hexapdf/type/actions.rb +1 -1
  167. data/lib/hexapdf/type/annotation.rb +1 -1
  168. data/lib/hexapdf/type/annotations/link.rb +1 -1
  169. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  170. data/lib/hexapdf/type/annotations/text.rb +1 -1
  171. data/lib/hexapdf/type/annotations/widget.rb +1 -1
  172. data/lib/hexapdf/type/annotations.rb +1 -1
  173. data/lib/hexapdf/type/catalog.rb +10 -2
  174. data/lib/hexapdf/type/cid_font.rb +1 -1
  175. data/lib/hexapdf/type/embedded_file.rb +1 -1
  176. data/lib/hexapdf/type/file_specification.rb +1 -1
  177. data/lib/hexapdf/type/font.rb +1 -1
  178. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  179. data/lib/hexapdf/type/font_simple.rb +1 -1
  180. data/lib/hexapdf/type/font_true_type.rb +1 -1
  181. data/lib/hexapdf/type/font_type0.rb +1 -1
  182. data/lib/hexapdf/type/font_type1.rb +1 -1
  183. data/lib/hexapdf/type/font_type3.rb +1 -1
  184. data/lib/hexapdf/type/form.rb +1 -1
  185. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  186. data/lib/hexapdf/type/icon_fit.rb +1 -1
  187. data/lib/hexapdf/type/image.rb +48 -4
  188. data/lib/hexapdf/type/info.rb +1 -1
  189. data/lib/hexapdf/type/names.rb +14 -1
  190. data/lib/hexapdf/type/object_stream.rb +1 -1
  191. data/lib/hexapdf/type/page.rb +1 -1
  192. data/lib/hexapdf/type/page_tree_node.rb +19 -2
  193. data/lib/hexapdf/type/resources.rb +1 -1
  194. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +1 -1
  195. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +1 -1
  196. data/lib/hexapdf/type/signature/handler.rb +1 -1
  197. data/lib/hexapdf/type/signature/verification_result.rb +1 -1
  198. data/lib/hexapdf/type/signature.rb +1 -1
  199. data/lib/hexapdf/type/trailer.rb +2 -2
  200. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  201. data/lib/hexapdf/type/xref_stream.rb +3 -2
  202. data/lib/hexapdf/type.rb +1 -1
  203. data/lib/hexapdf/utils/bit_field.rb +1 -1
  204. data/lib/hexapdf/utils/bit_stream.rb +1 -1
  205. data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
  206. data/lib/hexapdf/utils/lru_cache.rb +1 -1
  207. data/lib/hexapdf/utils/math_helpers.rb +1 -1
  208. data/lib/hexapdf/utils/object_hash.rb +1 -1
  209. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  210. data/lib/hexapdf/utils/sorted_tree_node.rb +4 -2
  211. data/lib/hexapdf/version.rb +2 -2
  212. data/lib/hexapdf/writer.rb +23 -8
  213. data/lib/hexapdf/xref_section.rb +1 -1
  214. data/lib/hexapdf.rb +1 -1
  215. data/test/hexapdf/content/graphic_object/test_geom2d.rb +1 -1
  216. data/test/hexapdf/document/test_destinations.rb +338 -0
  217. data/test/hexapdf/document/test_images.rb +1 -1
  218. data/test/hexapdf/document/test_layout.rb +264 -0
  219. data/test/hexapdf/document/test_pages.rb +9 -0
  220. data/test/hexapdf/document/test_signatures.rb +10 -3
  221. data/test/hexapdf/encryption/test_security_handler.rb +3 -3
  222. data/test/hexapdf/font/encoding/test_glyph_list.rb +4 -0
  223. data/test/hexapdf/layout/test_box.rb +53 -3
  224. data/test/hexapdf/layout/test_box_fitter.rb +62 -0
  225. data/test/hexapdf/layout/test_column_box.rb +159 -0
  226. data/test/hexapdf/layout/test_frame.rb +114 -39
  227. data/test/hexapdf/layout/test_image_box.rb +1 -1
  228. data/test/hexapdf/layout/test_list_box.rb +249 -0
  229. data/test/hexapdf/layout/test_text_box.rb +33 -2
  230. data/test/hexapdf/layout/test_text_fragment.rb +1 -1
  231. data/test/hexapdf/layout/test_text_layouter.rb +49 -17
  232. data/test/hexapdf/layout/test_width_from_polygon.rb +13 -0
  233. data/test/hexapdf/task/test_optimize.rb +17 -4
  234. data/test/hexapdf/test_composer.rb +35 -1
  235. data/test/hexapdf/test_dictionary_fields.rb +10 -10
  236. data/test/hexapdf/test_document.rb +33 -136
  237. data/test/hexapdf/test_filter.rb +1 -1
  238. data/test/hexapdf/test_parser.rb +1 -3
  239. data/test/hexapdf/test_revision.rb +14 -0
  240. data/test/hexapdf/test_revisions.rb +137 -29
  241. data/test/hexapdf/test_serializer.rb +1 -5
  242. data/test/hexapdf/test_writer.rb +99 -15
  243. data/test/hexapdf/type/acro_form/test_form.rb +2 -1
  244. data/test/hexapdf/type/acro_form/test_text_field.rb +17 -0
  245. data/test/hexapdf/type/test_catalog.rb +8 -0
  246. data/test/hexapdf/type/test_image.rb +45 -9
  247. data/test/hexapdf/type/test_names.rb +20 -0
  248. data/test/hexapdf/type/test_page_tree_node.rb +21 -1
  249. data/test/hexapdf/type/test_trailer.rb +3 -3
  250. data/test/hexapdf/type/test_xref_stream.rb +2 -1
  251. data/test/hexapdf/utils/test_sorted_tree_node.rb +11 -1
  252. data/test/test_helper.rb +5 -1
  253. metadata +29 -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
@@ -106,6 +106,8 @@ module HexaPDF
106
106
  autoload(:Images, 'hexapdf/document/images')
107
107
  autoload(:Files, 'hexapdf/document/files')
108
108
  autoload(:Signatures, 'hexapdf/document/signatures')
109
+ autoload(:Destinations, 'hexapdf/document/destinations')
110
+ autoload(:Layout, 'hexapdf/document/layout')
109
111
 
110
112
  # :call-seq:
111
113
  # Document.open(filename, **docargs) -> doc
@@ -184,22 +186,9 @@ module HexaPDF
184
186
  # For references to unknown objects, +nil+ is returned but free objects are represented by a
185
187
  # PDF Null object, not by +nil+!
186
188
  #
187
- # See: PDF1.7 s7.3.9
189
+ # See: Revisions#object
188
190
  def object(ref)
189
- i = @revisions.size - 1
190
- while i >= 0
191
- return @revisions[i].object(ref) if @revisions[i].object?(ref)
192
- i -= 1
193
- end
194
- nil
195
- end
196
-
197
- # Dereferences the given object.
198
- #
199
- # Return the object itself if it is not a reference, or the indirect object specified by the
200
- # reference.
201
- def deref(obj)
202
- obj.kind_of?(Reference) ? object(obj) : obj
191
+ @revisions.object(ref)
203
192
  end
204
193
 
205
194
  # :call-seq:
@@ -212,74 +201,51 @@ module HexaPDF
212
201
  # Even though this method might return +true+ for some references, #object may return +nil+
213
202
  # because this method takes *all* revisions into account. Also see the discussion on #each for
214
203
  # more information.
204
+ #
205
+ # See: Revisions#object?
215
206
  def object?(ref)
216
- @revisions.any? {|rev| rev.object?(ref) }
207
+ @revisions.object?(ref)
208
+ end
209
+
210
+ # Dereferences the given object.
211
+ #
212
+ # Return the object itself if it is not a reference, or the indirect object specified by the
213
+ # reference.
214
+ def deref(obj)
215
+ obj.kind_of?(Reference) ? object(obj) : obj
217
216
  end
218
217
 
219
218
  # :call-seq:
220
- # doc.add(obj, revision: :current, **wrap_opts) -> indirect_object
219
+ # doc.add(obj, **wrap_opts) -> indirect_object
221
220
  #
222
- # Adds the object to the specified revision of the document and returns the wrapped indirect
223
- # object.
221
+ # Adds the object to the document and returns the wrapped indirect object.
224
222
  #
225
223
  # The object can either be a native Ruby object (Hash, Array, Integer, ...) or a
226
224
  # HexaPDF::Object. If it is not the latter, #wrap is called with the object and the
227
225
  # additional keyword arguments.
228
226
  #
229
- # If the +revision+ option is +:current+, the current revision is used. Otherwise +revision+
230
- # should be a revision index.
231
- def add(obj, revision: :current, **wrap_opts)
227
+ # See: Revisions#add_object
228
+ def add(obj, **wrap_opts)
232
229
  obj = wrap(obj, **wrap_opts) unless obj.kind_of?(HexaPDF::Object)
233
230
 
234
- revision = (revision == :current ? @revisions.current : @revisions.revision(revision))
235
- if revision.nil?
236
- raise ArgumentError, "Invalid revision index specified"
237
- end
238
-
239
231
  if obj.document? && obj.document != self
240
232
  raise HexaPDF::Error, "Can't add object that is already attached to another document"
241
233
  end
242
234
  obj.document = self
243
235
 
244
- if obj.indirect? && (rev_obj = revision.object(obj.oid))
245
- if rev_obj.equal?(obj)
246
- return obj
247
- else
248
- raise HexaPDF::Error, "Can't add object because the specified revision already has " \
249
- "an object with object number #{obj.oid}"
250
- end
251
- end
252
-
253
- obj.oid = @revisions.map(&:next_free_oid).max unless obj.indirect?
254
-
255
- revision.add(obj)
236
+ @revisions.add_object(obj)
256
237
  end
257
238
 
258
239
  # :call-seq:
259
- # doc.delete(ref, revision: :all)
260
- # doc.delete(oid, revision: :all)
240
+ # doc.delete(ref)
241
+ # doc.delete(oid)
261
242
  #
262
243
  # Deletes the indirect object specified by an exact reference or by an object number from the
263
244
  # document.
264
245
  #
265
- # Options:
266
- #
267
- # revision:: Specifies from which revisions the object should be deleted:
268
- #
269
- # :all:: Delete the object from all revisions.
270
- # :current:: Delete the object only from the current revision.
271
- #
272
- # mark_as_free:: If +true+, objects are only marked as free objects instead of being actually
273
- # deleted.
274
- def delete(ref, revision: :all, mark_as_free: true)
275
- case revision
276
- when :current
277
- @revisions.current.delete(ref, mark_as_free: mark_as_free)
278
- when :all
279
- @revisions.each {|rev| rev.delete(ref, mark_as_free: mark_as_free) }
280
- else
281
- raise ArgumentError, "Unsupported option revision: #{revision}"
282
- end
246
+ # See: Revisions#delete_object
247
+ def delete(ref)
248
+ @revisions.delete_object(ref)
283
249
  end
284
250
 
285
251
  # :call-seq:
@@ -414,42 +380,20 @@ module HexaPDF
414
380
  end
415
381
 
416
382
  # :call-seq:
417
- # doc.each(only_current: true, only_loaded: false) {|obj| block } -> doc
418
- # doc.each(only_current: true, only_loaded: false) {|obj, rev| block } -> doc
383
+ # doc.each(only_current: true, only_loaded: false) {|obj| block }
384
+ # doc.each(only_current: true, only_loaded: false) {|obj, rev| block }
419
385
  # doc.each(only_current: true, only_loaded: false) -> Enumerator
420
386
  #
421
- # Calls the given block once for every object, or, if +only_loaded+ is +true+, for every loaded
422
- # object in the PDF document. The block may either accept only the object or the object and the
423
- # revision it is in.
424
- #
425
- # By default, only the current version of each object is returned which implies that each object
426
- # number is yielded exactly once. If the +only_current+ option is +false+, all stored objects
427
- # from newest to oldest are returned, not only the current version of each object.
387
+ # Yields every object and the revision it is in.
428
388
  #
429
- # The +only_current+ option can make a difference because the document can contain multiple
430
- # revisions:
389
+ # If +only_current+ is +true+, only the current version of each object is yielded, otherwise
390
+ # all objects from all revisions.
431
391
  #
432
- # * Multiple revisions may contain objects with the same object and generation numbers, e.g.
433
- # two (different) objects with oid/gen [3,0].
392
+ # If +only_loaded+ is +true+, only the already loaded objects are yielded.
434
393
  #
435
- # * Additionally, there may also be objects with the same object number but different
436
- # generation numbers in different revisions, e.g. one object with oid/gen [3,0] and one with
437
- # oid/gen [3,1].
394
+ # For details see Revisions#each_object
438
395
  def each(only_current: true, only_loaded: false, &block)
439
- unless block_given?
440
- return to_enum(__method__, only_current: only_current, only_loaded: only_loaded)
441
- end
442
-
443
- yield_rev = (block.arity == 2)
444
- oids = {}
445
- @revisions.reverse_each do |rev|
446
- rev.each(only_loaded: only_loaded) do |obj|
447
- next if only_current && oids.include?(obj.oid)
448
- (yield_rev ? yield(obj, rev) : yield(obj))
449
- oids[obj.oid] = true
450
- end
451
- end
452
- self
396
+ @revisions.each_object(only_current: only_current, only_loaded: only_loaded, &block)
453
397
  end
454
398
 
455
399
  # :call-seq:
@@ -529,6 +473,18 @@ module HexaPDF
529
473
  @fonts ||= Fonts.new(self)
530
474
  end
531
475
 
476
+ # Returns the Destinations object that provides convenience methods for working with destination
477
+ # objects.
478
+ def destinations
479
+ @destinations ||= Destinations.new(self)
480
+ end
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
+
532
488
  # Returns the main AcroForm object for dealing with interactive forms.
533
489
  #
534
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