hexapdf 0.35.1 → 0.37.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.
@@ -60,28 +60,59 @@ module HexaPDF
60
60
  # instantiated from the common convenience method HexaPDF::Document::Layout#box. To use this
61
61
  # facility subclasses need to be registered with the configuration option 'layout.boxes.map'.
62
62
  #
63
- # The methods #fit, #supports_position_flow?, #split or #split_content, #empty?, and #draw or
64
- # #draw_content need to be customized according to the subclass's use case.
65
- #
66
- # #fit:: This method should return +true+ if fitting was successful. Additionally, the
67
- # @fit_successful instance variable needs to be set to the fit result as it is used in
68
- # #split.
63
+ # The methods #supports_position_flow?, #empty?, #fit or #fit_content, #split or #split_content,
64
+ # and #draw or #draw_content need to be customized according to the subclass's use case (also
65
+ # see the documentation of the methods besides the informatione below):
69
66
  #
70
67
  # #supports_position_flow?::
71
- # If the subclass supports the value :flow of the 'position' style property, this method
72
- # needs to be overridden to return +true+.
68
+ # If the subclass supports the value :flow of the 'position' style property, this method
69
+ # needs to be overridden to return +true+.
70
+ #
71
+ # #empty?::
72
+ # This method should return +true+ if the subclass won't draw anything when #draw is called.
73
+ #
74
+ # #fit::
75
+ # This method should return +true+ if fitting was successful. Additionally, the
76
+ # @fit_successful instance variable needs to be set to the fit result as it is used in
77
+ # #split.
78
+ #
79
+ # The default implementation provides code common to most use-cases and delegates the
80
+ # specifics to the #fit_content method which needs to return +true+ if fitting was
81
+ # successful.
82
+ #
83
+ # #split::
84
+ # This method splits the content so that the current region is used as good as possible. The
85
+ # default implementation should be fine for most use-cases, so only #split_content needs to
86
+ # be implemented. The method #create_split_box should be used for getting a basic cloned
87
+ # box.
88
+ #
89
+ # #draw::
90
+ # This method draws the content and the default implementation already handles things like
91
+ # drawing the border and background. So it should not be overridden. The box specific
92
+ # drawing commands should be implemented in the #draw_content method.
93
+ #
94
+ # This base class provides various private helper methods for use in the above methods:
95
+ #
96
+ # +reserved_width+, +reserved_height+::
97
+ # Returns the width respectively the height of the reserved space inside the box that is
98
+ # used for the border and padding.
99
+ #
100
+ # +reserved_width_left+, +reserved_width_right+, +reserved_height_top+,
101
+ # +reserved_height_bottom+::
102
+ # Returns the reserved space inside the box at the specified edge (left, right, top,
103
+ # bottom).
73
104
  #
74
- # #split:: This method splits the content so that the current region is used as good as
75
- # possible. The default implementation should be fine for most use-cases, so only
76
- # #split_content needs to be implemented. The method #create_split_box should be used
77
- # for getting a basic cloned box.
105
+ # +update_content_width+, +update_content_height+::
106
+ # Takes a block that should return the content width respectively height and sets the box's
107
+ # width respectively height accordingly.
78
108
  #
79
- # #empty?:: This method should return +true+ if the subclass won't draw anything when #draw is
80
- # called.
109
+ # +create_split_box+::
110
+ # Creates a new box based on this one and resets the internal data back to their original
111
+ # values.
81
112
  #
82
- # #draw:: This method draws the content and the default implementation already handles things
83
- # like drawing the border and background. Therefore it's best to implement #draw_content
84
- # which should just draw the content.
113
+ # The keyword argument +split_box_value+ (defaults to +true+) is used to set the
114
+ # +@split_box+ variable to make the new box aware that it is a split box. This can be set to
115
+ # any other truthy value to convey more meaning.
85
116
  class Box
86
117
 
87
118
  include HexaPDF::Utils
@@ -162,7 +193,8 @@ module HexaPDF
162
193
  @split_box = false
163
194
  end
164
195
 
165
- # Returns +true+ if this is a split box, i.e. the rest of another box after it was split.
196
+ # Returns the set truthy value if this is a split box, i.e. the rest of another box after it
197
+ # was split.
166
198
  def split_box?
167
199
  @split_box
168
200
  end
@@ -184,18 +216,34 @@ module HexaPDF
184
216
  height < 0 ? 0 : height
185
217
  end
186
218
 
187
- # Fits the box into the Frame and returns +true+ if fitting was successful.
219
+ # Fits the box into the *frame* and returns +true+ if fitting was successful.
188
220
  #
189
221
  # The arguments +available_width+ and +available_height+ are the width and height of the
190
- # current region of the frame. The frame itself is provided as third argument.
222
+ # current region of the frame, adjusted for this box. The frame itself is provided as third
223
+ # argument.
191
224
  #
192
- # The default implementation uses the available width and height for the box width and height
193
- # if they were initially set to 0. Otherwise the specified dimensions are used.
194
- def fit(available_width, available_height, _frame)
225
+ # The default implementation uses the given available width and height for the box width and
226
+ # height if they were initially set to 0. Otherwise the intially specified dimensions are
227
+ # used. Then the #fit_content method is called which allows sub-classes to fit their content.
228
+ #
229
+ # The following variables are set that may later be used during splitting or drawing:
230
+ #
231
+ # * (@fit_x, @fit_y): The lower-left corner of the content box where fitting was done. Can be
232
+ # used to adjust the drawing position in #draw/#draw_content if necessary.
233
+ # * @fit_successful: +true+ if fitting was successful.
234
+ def fit(available_width, available_height, frame)
195
235
  @width = (@initial_width > 0 ? @initial_width : available_width)
196
236
  @height = (@initial_height > 0 ? @initial_height : available_height)
197
237
  @fit_successful = (float_compare(@width, available_width) <= 0 &&
198
238
  float_compare(@height, available_height) <= 0)
239
+ return unless @fit_successful
240
+
241
+ @fit_successful = fit_content(available_width, available_height, frame)
242
+
243
+ @fit_x = frame.x + reserved_width_left
244
+ @fit_y = frame.y - @height + reserved_height_bottom
245
+
246
+ @fit_successful
199
247
  end
200
248
 
201
249
  # Tries to split the box into two, the first of which needs to fit into the current region of
@@ -237,6 +285,8 @@ module HexaPDF
237
285
  # arguments. Subclasses can specify an on-demand drawing method by setting the +@draw_block+
238
286
  # instance variable to +nil+ or a valid block. This is useful to avoid unnecessary set-up
239
287
  # operations when the block does nothing.
288
+ #
289
+ # Alternatively, if a #draw_content method is defined, this method is called.
240
290
  def draw(canvas, x, y)
241
291
  if (oc = properties['optional_content'])
242
292
  canvas.optional_content(oc)
@@ -316,12 +366,26 @@ module HexaPDF
316
366
  result
317
367
  end
318
368
 
319
- # Draws the content of the box at position [x, y] which is the bottom-left corner of the
320
- # content box.
321
- def draw_content(canvas, x, y)
322
- if @draw_block
323
- canvas.translate(x, y) { @draw_block.call(canvas, self) }
324
- end
369
+ # Updates the width of the box using the content width returned by the block.
370
+ def update_content_width
371
+ return if @initial_width > 0
372
+ @width = yield + reserved_width
373
+ end
374
+
375
+ # Updates the height of the box using the content height returned by the block.
376
+ def update_content_height
377
+ return if @initial_height > 0
378
+ @height = yield + reserved_height
379
+ end
380
+
381
+ # Fits the content of the box and returns whether fitting was successful.
382
+ #
383
+ # This is just a stub implementation that returns +true+. Subclasses should override it to
384
+ # provide the box specific behaviour.
385
+ #
386
+ # See #fit for details.
387
+ def fit_content(available_width, available_height, frame)
388
+ true
325
389
  end
326
390
 
327
391
  # Splits the content of the box.
@@ -335,6 +399,17 @@ module HexaPDF
335
399
  [nil, self]
336
400
  end
337
401
 
402
+ # Draws the content of the box at position [x, y] which is the bottom-left corner of the
403
+ # content box.
404
+ #
405
+ # This implementation uses the drawing block provided on initialization, if set, to draw the
406
+ # contents. Subclasses should override it to provide box specific behaviour.
407
+ def draw_content(canvas, x, y)
408
+ if @draw_block
409
+ canvas.translate(x, y) { @draw_block.call(canvas, self) }
410
+ end
411
+ end
412
+
338
413
  # Creates a new box based on this one and resets the data back to their original values.
339
414
  #
340
415
  # The variable +@split_box+ is set to +split_box_value+ (defaults to +true+) to make the new
@@ -0,0 +1,159 @@
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-2023 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
+ require 'hexapdf/layout/box'
37
+ require 'hexapdf/layout/box_fitter'
38
+ require 'hexapdf/layout/frame'
39
+
40
+ module HexaPDF
41
+ module Layout
42
+
43
+ # This is a simple container box for laying out a number of boxes together. It is registered
44
+ # under the :container name.
45
+ #
46
+ # The box does not support the value :flow for the style property position, so the child boxes
47
+ # are laid out in the current region only. Since the boxes should be laid out together, if any
48
+ # box doesn't fit, the whole container doesn't fit. Splitting the container is also not possible
49
+ # for the same reason.
50
+ #
51
+ # By default the child boxes are laid out from top to bottom by default. By appropriately
52
+ # setting the style properties 'mask_mode', 'align' and 'valign', it is possible to lay out the
53
+ # children bottom to top, left to right, or right to left:
54
+ #
55
+ # * The standard top to bottom layout:
56
+ #
57
+ # #>pdf-composer100
58
+ # composer.container do |container|
59
+ # container.box(:base, height: 20, style: {background_color: "hp-blue-dark"})
60
+ # container.box(:base, height: 20, style: {background_color: "hp-blue"})
61
+ # container.box(:base, height: 20, style: {background_color: "hp-blue-light"})
62
+ # end
63
+ #
64
+ # * The bottom to top layout (using valign = :bottom to fill up from the bottom and mask_mode =
65
+ # :fill_horizontal to only remove the area to the left and right of the box):
66
+ #
67
+ # #>pdf-composer100
68
+ # composer.container do |container|
69
+ # container.box(:base, height: 20, style: {background_color: "hp-blue-dark",
70
+ # mask_mode: :fill_horizontal, valign: :bottom})
71
+ # container.box(:base, height: 20, style: {background_color: "hp-blue",
72
+ # mask_mode: :fill_horizontal, valign: :bottom})
73
+ # container.box(:base, height: 20, style: {background_color: "hp-blue-light",
74
+ # mask_mode: :fill_horizontal, valign: :bottom})
75
+ # end
76
+ #
77
+ # * The left to right layout (using mask_mode = :fill_vertical to fill the area to the top and
78
+ # bottom of the box):
79
+ #
80
+ # #>pdf-composer100
81
+ # composer.container do |container|
82
+ # container.box(:base, width: 20, style: {background_color: "hp-blue-dark",
83
+ # mask_mode: :fill_vertical})
84
+ # container.box(:base, width: 20, style: {background_color: "hp-blue",
85
+ # mask_mode: :fill_vertical})
86
+ # container.box(:base, width: 20, style: {background_color: "hp-blue-light",
87
+ # mask_mode: :fill_vertical})
88
+ # end
89
+ #
90
+ # * The right to left layout (using align = :right to fill up from the right and mask_mode =
91
+ # :fill_vertical to fill the area to the top and bottom of the box):
92
+ #
93
+ # #>pdf-composer100
94
+ # composer.container do |container|
95
+ # container.box(:base, width: 20, style: {background_color: "hp-blue-dark",
96
+ # mask_mode: :fill_vertical, align: :right})
97
+ # container.box(:base, width: 20, style: {background_color: "hp-blue",
98
+ # mask_mode: :fill_vertical, align: :right})
99
+ # container.box(:base, width: 20, style: {background_color: "hp-blue-light",
100
+ # mask_mode: :fill_vertical, align: :right})
101
+ # end
102
+ class ContainerBox < Box
103
+
104
+ # The child boxes of this ContainerBox. They need to be finalized before #fit is called.
105
+ attr_reader :children
106
+
107
+ # Creates a new container box, optionally accepting an array of child boxes.
108
+ #
109
+ # Example:
110
+ #
111
+ # #>pdf-composer100
112
+ # composer.text("A paragraph here")
113
+ # composer.container(height: 40, style: {border: {width: 1}, padding: 5,
114
+ # align: :center}) do |container|
115
+ # container.text("Some", mask_mode: :fill_vertical)
116
+ # container.text("text", mask_mode: :fill_vertical, valign: :center)
117
+ # container.text("here", mask_mode: :fill_vertical, valign: :bottom)
118
+ # end
119
+ # composer.text("Another paragraph")
120
+ def initialize(children: [], **kwargs)
121
+ super(**kwargs)
122
+ @children = children
123
+ end
124
+
125
+ # Returns +true+ if no box was fitted into the container.
126
+ def empty?
127
+ super && (!@box_fitter || @box_fitter.fit_results.empty?)
128
+ end
129
+
130
+ private
131
+
132
+ # Fits the children into the container.
133
+ def fit_content(available_width, available_height, frame)
134
+ my_frame = Frame.new(frame.x + reserved_width_left, frame.y - @height + reserved_height_bottom,
135
+ content_width, content_height, context: frame.context)
136
+ @box_fitter = BoxFitter.new([my_frame])
137
+ children.each {|box| @box_fitter.fit(box) }
138
+
139
+ if @box_fitter.fit_successful?
140
+ update_content_width do
141
+ result = @box_fitter.fit_results.max_by {|result| result.mask.x + result.mask.width }
142
+ children.size > 0 ? result.mask.x + result.mask.width - my_frame.left : 0
143
+ end
144
+ update_content_height { @box_fitter.content_heights.max }
145
+ true
146
+ end
147
+ end
148
+
149
+ # Draws the image onto the canvas at position [x, y].
150
+ def draw_content(canvas, x, y)
151
+ dx = x - @fit_x
152
+ dy = y - @fit_y
153
+ @box_fitter.fit_results.each {|result| result.draw(canvas, dx: dx, dy: dy) }
154
+ end
155
+
156
+ end
157
+
158
+ end
159
+ end
@@ -128,17 +128,20 @@ module HexaPDF
128
128
  @success
129
129
  end
130
130
 
131
- # Draws the #box onto the canvas at (#x, #y).
131
+ # Draws the #box onto the canvas at (#x + *dx*, #y + *dy*).
132
+ #
133
+ # The relative offset (dx, dy) is useful when rendering results that were accumulated and
134
+ # then need to be moved because the container holding them changes its position.
132
135
  #
133
136
  # The configuration option "debug" can be used to add visual debug output with respect to
134
137
  # box placement.
135
- def draw(canvas)
138
+ def draw(canvas, dx: 0, dy: 0)
136
139
  doc = canvas.context.document
137
140
  if doc.config['debug']
138
141
  name = "#{box.class} (#{x.to_i},#{y.to_i}-#{box.width.to_i}x#{box.height.to_i})"
139
142
  ocg = doc.optional_content.ocg(name)
140
143
  canvas.optional_content(ocg) do
141
- canvas.save_graphics_state do
144
+ canvas.translate(dx, dy) do
142
145
  canvas.fill_color("green").stroke_color("darkgreen").
143
146
  opacity(fill_alpha: 0.1, stroke_alpha: 0.2).
144
147
  draw(:geom2d, object: mask, path_only: true).fill_stroke
@@ -146,7 +149,7 @@ module HexaPDF
146
149
  end
147
150
  doc.optional_content.default_configuration.add_ocg_to_ui(ocg, path: 'Debug')
148
151
  end
149
- box.draw(canvas, x, y)
152
+ box.draw(canvas, x + dx, y + dy)
150
153
  end
151
154
 
152
155
  end
@@ -232,7 +232,8 @@ module HexaPDF
232
232
 
233
233
  if index != 0 || !split_box? || @split_box == :show_first_marker
234
234
  box = item_marker_box(frame.document, index)
235
- break unless box.fit(content_indentation, height, nil)
235
+ marker_frame = Frame.new(0, 0, content_indentation, height, context: frame.context)
236
+ break unless box.fit(content_indentation, height, marker_frame)
236
237
  item_result.marker = box
237
238
  item_result.marker_pos_x = item_frame.x - content_indentation
238
239
  item_result.height = box.height
@@ -58,6 +58,7 @@ module HexaPDF
58
58
  autoload(:ListBox, 'hexapdf/layout/list_box')
59
59
  autoload(:PageStyle, 'hexapdf/layout/page_style')
60
60
  autoload(:TableBox, 'hexapdf/layout/table_box')
61
+ autoload(:ContainerBox, 'hexapdf/layout/container_box')
61
62
 
62
63
  end
63
64
 
@@ -132,6 +132,77 @@ module HexaPDF
132
132
  define_field :StructParent, type: Integer, version: '1.3'
133
133
  define_field :OC, type: Dictionary, version: '1.5'
134
134
 
135
+ ##
136
+ # :method: flags
137
+ #
138
+ # Returns an array of flag names representing the set bit flags for /F.
139
+ #
140
+ # The available flags are:
141
+ #
142
+ # :invisible or 0::
143
+ # Applies only to non-standard annotations. If set, do not render or print the annotation.
144
+ #
145
+ # :hidden or 1::
146
+ # If set, do not render the annotation or allow interactions.
147
+ #
148
+ # :print or 2::
149
+ # If set, print the annotation unless the hidden flag is also set. Otherwise never print
150
+ # the annotation.
151
+ #
152
+ # :no_zoom or 3::
153
+ # If set, do not scale the annotation's appearance to match the magnification of the page.
154
+ #
155
+ # :no_rotate or 4::
156
+ # If set, do not rotate the annotation's appearance to match the rotation of the page.
157
+ #
158
+ # :no_view or 5::
159
+ # If set, do not render the annotation on the screen or allow interactions.
160
+ #
161
+ # :read_only or 6::
162
+ # If set, do not allow user interactions.
163
+ #
164
+ # :locked or 7::
165
+ # If set, do not allow the annotation to be deleted or its properties be modified.
166
+ #
167
+ # :toggle_no_view or 8::
168
+ # If set, invert the interpretation of the :no_view flag for annotation selection and
169
+ # mouse hovering.
170
+ #
171
+ # :locked_contents or 9::
172
+ # If set, do not allow the contents of the annotation to be modified.
173
+ #
174
+
175
+ ##
176
+ # :method: flagged?
177
+ # :call-seq:
178
+ # flagged?(flag)
179
+ #
180
+ # Returns +true+ if the given flag is set on /F. The argument can either be the flag name or
181
+ # the bit index.
182
+ #
183
+ # See #flags for the list of available flags.
184
+ #
185
+
186
+ ##
187
+ # :method: flag
188
+ # :call-seq:
189
+ # flag(*flags, clear_existing: false)
190
+ #
191
+ # Sets the given flags on /F, given as flag names or bit indices. If +clear_existing+ is
192
+ # +true+, all prior flags will be cleared.
193
+ #
194
+ # See #flags for the list of available flags.
195
+ #
196
+
197
+ ##
198
+ # :method: unflag
199
+ # :call-seq:
200
+ # flag(*flags)
201
+ #
202
+ # Clears the given flags from /F, given as flag names or bit indices.
203
+ #
204
+ # See #flags for the list of available flags.
205
+ #
135
206
  bit_field(:flags, {invisible: 0, hidden: 1, print: 2, no_zoom: 3, no_rotate: 4,
136
207
  no_view: 5, read_only: 6, locked: 7, toggle_no_view: 8,
137
208
  locked_contents: 9},
@@ -71,7 +71,7 @@ module HexaPDF
71
71
  define_field :AA, type: Dictionary, version: '1.4'
72
72
  define_field :URI, type: Dictionary, version: '1.1'
73
73
  define_field :AcroForm, type: :XXAcroForm, version: '1.2'
74
- define_field :Metadata, type: Stream, indirect: true, version: '1.4'
74
+ define_field :Metadata, type: :Metadata, indirect: true, version: '1.4'
75
75
  define_field :StructTreeRoot, type: Dictionary, version: '1.3'
76
76
  define_field :MarkInfo, type: :XXMarkInformation, version: '1.4'
77
77
  define_field :Lang, type: String, version: '1.4'
@@ -95,7 +95,8 @@ module HexaPDF
95
95
  # Returns the UTF-8 string for the given character code, or calls the configuration option
96
96
  # 'font.on_missing_unicode_mapping' if no mapping was found.
97
97
  def to_utf8(code)
98
- to_unicode_cmap&.to_unicode(code) || encoding.unicode(code) || missing_unicode_mapping(code)
98
+ to_unicode_cmap&.to_unicode(code) || (encoding.unicode(code) rescue nil) ||
99
+ missing_unicode_mapping(code)
99
100
  end
100
101
 
101
102
  # Returns the unscaled width of the given code point in glyph units, or 0 if the width for
@@ -0,0 +1,63 @@
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-2023 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/stream'
38
+
39
+ module HexaPDF
40
+ module Type
41
+
42
+ # Represents an XMP metadata stream.
43
+ #
44
+ # XMP metadata streams may be attached to most PDF objects, though it only makes sense for some
45
+ # of them.
46
+ #
47
+ # There is also a main XMP metadata stream for the whole document that is accessible via the
48
+ # /Metadata key of the document catalog. That metadata stream should contain the same values as
49
+ # the PDF's info dictionary and may contain additional entries. This can be accomplished via
50
+ # HexaPDF::Document#metadata.
51
+ #
52
+ # See: PDF2.0 s14.3.2
53
+ class Metadata < Stream
54
+
55
+ define_type :Metadata
56
+
57
+ define_field :Type, type: Symbol, default: type, required: true
58
+ define_field :Subtype, type: Symbol, default: :XML, required: true
59
+
60
+ end
61
+
62
+ end
63
+ end
data/lib/hexapdf/type.rb CHANGED
@@ -80,6 +80,7 @@ module HexaPDF
80
80
  autoload(:OptionalContentMembership, 'hexapdf/type/optional_content_membership')
81
81
  autoload(:OptionalContentProperties, 'hexapdf/type/optional_content_properties')
82
82
  autoload(:OptionalContentConfiguration, 'hexapdf/type/optional_content_configuration')
83
+ autoload(:Metadata, 'hexapdf/type/metadata')
83
84
 
84
85
  end
85
86
 
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '0.35.1'
40
+ VERSION = '0.37.0'
41
41
 
42
42
  end
@@ -175,6 +175,12 @@ describe HexaPDF::Document::Layout do
175
175
  assert_equal(2, box.children.size)
176
176
  end
177
177
 
178
+ it "uses the provided block as drawing block for the base box class if name=:base" do
179
+ block = proc {}
180
+ box = @layout.box(width: 100, &block)
181
+ assert_equal(block, box.instance_variable_get(:@draw_block))
182
+ end
183
+
178
184
  it "fails if the name is not registered" do
179
185
  assert_raises(HexaPDF::Error) { @layout.box(:unknown) }
180
186
  end