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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -0
- data/Rakefile +1 -1
- data/lib/hexapdf/configuration.rb +3 -0
- data/lib/hexapdf/content/canvas_composer.rb +1 -1
- data/lib/hexapdf/document/layout.rb +15 -6
- data/lib/hexapdf/document/metadata.rb +488 -0
- data/lib/hexapdf/document.rb +18 -3
- data/lib/hexapdf/filter.rb +2 -2
- data/lib/hexapdf/layout/box.rb +104 -29
- data/lib/hexapdf/layout/container_box.rb +159 -0
- data/lib/hexapdf/layout/frame.rb +7 -4
- data/lib/hexapdf/layout/list_box.rb +2 -1
- data/lib/hexapdf/layout.rb +1 -0
- data/lib/hexapdf/type/annotation.rb +71 -0
- data/lib/hexapdf/type/catalog.rb +1 -1
- data/lib/hexapdf/type/font_simple.rb +2 -1
- data/lib/hexapdf/type/metadata.rb +63 -0
- data/lib/hexapdf/type.rb +1 -0
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/document/test_layout.rb +6 -0
- data/test/hexapdf/document/test_metadata.rb +192 -0
- data/test/hexapdf/layout/test_box.rb +39 -13
- data/test/hexapdf/layout/test_container_box.rb +84 -0
- data/test/hexapdf/layout/test_frame.rb +3 -2
- data/test/hexapdf/test_filter.rb +12 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/test_font_simple.rb +9 -1
- metadata +8 -3
data/lib/hexapdf/layout/box.rb
CHANGED
@@ -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 #
|
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
|
-
#
|
72
|
-
#
|
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
|
-
#
|
75
|
-
#
|
76
|
-
#
|
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
|
-
#
|
80
|
-
#
|
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
|
-
#
|
83
|
-
#
|
84
|
-
#
|
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
|
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
|
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
|
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
|
193
|
-
# if they were initially set to 0. Otherwise the specified dimensions are
|
194
|
-
|
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
|
-
#
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
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
|
data/lib/hexapdf/layout/frame.rb
CHANGED
@@ -128,17 +128,20 @@ module HexaPDF
|
|
128
128
|
@success
|
129
129
|
end
|
130
130
|
|
131
|
-
# Draws the #box onto the canvas at (#x
|
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.
|
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
|
-
|
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
|
data/lib/hexapdf/layout.rb
CHANGED
@@ -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},
|
data/lib/hexapdf/type/catalog.rb
CHANGED
@@ -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:
|
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)
|
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
|
|
data/lib/hexapdf/version.rb
CHANGED
@@ -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
|