hexapdf 0.42.0 → 0.44.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 +46 -0
- data/Rakefile +1 -1
- data/examples/030-pdfa.rb +1 -0
- data/lib/hexapdf/composer.rb +1 -0
- data/lib/hexapdf/dictionary.rb +3 -3
- data/lib/hexapdf/document/files.rb +7 -2
- data/lib/hexapdf/document/metadata.rb +12 -1
- data/lib/hexapdf/document.rb +14 -1
- data/lib/hexapdf/encryption.rb +17 -0
- data/lib/hexapdf/layout/box.rb +161 -61
- data/lib/hexapdf/layout/box_fitter.rb +4 -3
- data/lib/hexapdf/layout/column_box.rb +23 -25
- data/lib/hexapdf/layout/container_box.rb +3 -3
- data/lib/hexapdf/layout/frame.rb +13 -95
- data/lib/hexapdf/layout/image_box.rb +4 -4
- data/lib/hexapdf/layout/line.rb +4 -0
- data/lib/hexapdf/layout/list_box.rb +12 -20
- data/lib/hexapdf/layout/style.rb +5 -1
- data/lib/hexapdf/layout/table_box.rb +48 -55
- data/lib/hexapdf/layout/text_box.rb +38 -39
- data/lib/hexapdf/parser.rb +23 -17
- data/lib/hexapdf/type/acro_form/form.rb +78 -27
- data/lib/hexapdf/type/file_specification.rb +9 -5
- data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/document/test_files.rb +5 -0
- data/test/hexapdf/document/test_metadata.rb +21 -0
- data/test/hexapdf/layout/test_box.rb +82 -37
- data/test/hexapdf/layout/test_box_fitter.rb +10 -3
- data/test/hexapdf/layout/test_column_box.rb +7 -13
- data/test/hexapdf/layout/test_container_box.rb +1 -1
- data/test/hexapdf/layout/test_frame.rb +0 -48
- data/test/hexapdf/layout/test_image_box.rb +14 -6
- data/test/hexapdf/layout/test_list_box.rb +25 -26
- data/test/hexapdf/layout/test_table_box.rb +39 -53
- data/test/hexapdf/layout/test_text_box.rb +65 -67
- data/test/hexapdf/test_composer.rb +6 -0
- data/test/hexapdf/test_dictionary.rb +6 -4
- data/test/hexapdf/test_parser.rb +20 -0
- data/test/hexapdf/type/acro_form/test_form.rb +63 -2
- data/test/hexapdf/type/test_file_specification.rb +2 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b30b54b90222fbcc5469b23153efc4b15083797f527af6c5b34b3f6e161832d
|
4
|
+
data.tar.gz: 969c67ab85591e3b7ccad17023bd3bc820ea5effa4c01e53d254915ab1626d71
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b60934dd6534ccd019953b278fa188b77996634b3e3878332302b9a2acccfd13b493b57432cff2113b7cc7727f028f24301c2d050f9b908c1b43cf66fbdeebfd
|
7
|
+
data.tar.gz: fdd792109306bc7cf0e30bb2da770e58e81e333843e3c785a9a31873a296b7d027121b467b54a80405332735e99bdaf6b0d167c9eaf08dbc04a26922b890bb51
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,49 @@
|
|
1
|
+
## 0.44.0 - 2024-06-05
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* Support for specifying the MIME type when embedding files
|
6
|
+
* Support for adding custom XMP metadata
|
7
|
+
|
8
|
+
### Changed
|
9
|
+
|
10
|
+
* **Breaking change**: Refactored the box implementation of the document layout
|
11
|
+
system
|
12
|
+
|
13
|
+
### Fixed
|
14
|
+
|
15
|
+
* Parsing of invalid files with garbage bytes at the end
|
16
|
+
|
17
|
+
|
18
|
+
## 0.43.0 - 2024-05-26
|
19
|
+
|
20
|
+
### Added
|
21
|
+
|
22
|
+
* [HexaPDF::Type::AcroForm::Form#create_namespace_field] for creating a pure
|
23
|
+
namespace field
|
24
|
+
* [HexaPDF::Type::AcroForm::Form#delete_field] for deleting fields
|
25
|
+
|
26
|
+
### Changed
|
27
|
+
|
28
|
+
* Minimum Ruby version to be 3.0
|
29
|
+
* **Breaking change**: Renamed `HexaPDF::Layout::BoxFitter#fit_successful?` to
|
30
|
+
[HexaPDF::Layout::BoxFitter#success?]
|
31
|
+
* **Breaking Change**: Removed HexaPDF::Dictionary#to_h
|
32
|
+
* Form field creation methods of [HexaPDF::Type::AcroForm::Form] to
|
33
|
+
automatically create parent fields as namespace fields
|
34
|
+
|
35
|
+
### Fixed
|
36
|
+
|
37
|
+
* [HexaPDF::Layout::TextBox#fit] to correctly calculate width in case of flowing
|
38
|
+
text around other boxes
|
39
|
+
* [HexaPDF::Layout::TextBox#draw] to correctly draw border, background... on
|
40
|
+
boxes using position 'flow'
|
41
|
+
* Comparison of Hash with [HexaPDF::Dictionary] objects by implementing
|
42
|
+
`#to_hash`
|
43
|
+
* Parsing of invalid files having multiple end-of-file markers with the last one
|
44
|
+
being invalid
|
45
|
+
|
46
|
+
|
1
47
|
## 0.42.0 - 2024-05-12
|
2
48
|
|
3
49
|
### Added
|
data/Rakefile
CHANGED
@@ -47,7 +47,7 @@ namespace :dev do
|
|
47
47
|
end
|
48
48
|
|
49
49
|
task :test_all do
|
50
|
-
versions = `rbenv versions --bare | grep -i ^
|
50
|
+
versions = `rbenv versions --bare | grep -i ^3.`.split("\n")
|
51
51
|
versions.each do |version|
|
52
52
|
sh "eval \"$(rbenv init -)\"; rbenv shell #{version} && ruby -v && rake test"
|
53
53
|
end
|
data/examples/030-pdfa.rb
CHANGED
data/lib/hexapdf/composer.rb
CHANGED
data/lib/hexapdf/dictionary.rb
CHANGED
@@ -228,9 +228,9 @@ module HexaPDF
|
|
228
228
|
value.empty?
|
229
229
|
end
|
230
230
|
|
231
|
-
# Returns a
|
232
|
-
def
|
233
|
-
value.
|
231
|
+
# Returns a hash containing the preprocessed values (like in #[]).
|
232
|
+
def to_hash
|
233
|
+
value.each_with_object({}) {|(k, _), h| h[k] = self[k] }
|
234
234
|
end
|
235
235
|
|
236
236
|
private
|
@@ -70,6 +70,9 @@ module HexaPDF
|
|
70
70
|
# description::
|
71
71
|
# A description of the file.
|
72
72
|
#
|
73
|
+
# mime_type::
|
74
|
+
# The MIME type that should be set for embedded files (so only used if +embed+ is +true+).
|
75
|
+
#
|
73
76
|
# embed::
|
74
77
|
# When an IO object is given, it is always embedded and this option is ignored.
|
75
78
|
#
|
@@ -77,7 +80,7 @@ module HexaPDF
|
|
77
80
|
# only a reference to it is stored.
|
78
81
|
#
|
79
82
|
# See: HexaPDF::Type::FileSpecification
|
80
|
-
def add(file_or_io, name: nil, description: nil, embed: true)
|
83
|
+
def add(file_or_io, name: nil, description: nil, mime_type: nil, embed: true)
|
81
84
|
name ||= File.basename(file_or_io) if file_or_io.kind_of?(String)
|
82
85
|
if name.nil?
|
83
86
|
raise ArgumentError, "The name argument is mandatory when given an IO object"
|
@@ -86,7 +89,9 @@ module HexaPDF
|
|
86
89
|
spec = @document.add({Type: :Filespec})
|
87
90
|
spec.path = name
|
88
91
|
spec[:Desc] = description if description
|
89
|
-
|
92
|
+
if embed || !file_or_io.kind_of?(String)
|
93
|
+
spec.embed(file_or_io, name: name, mime_type: mime_type, register: true)
|
94
|
+
end
|
90
95
|
spec
|
91
96
|
end
|
92
97
|
|
@@ -161,6 +161,7 @@ module HexaPDF
|
|
161
161
|
@properties = PREDEFINED_PROPERTIES.transform_values(&:dup)
|
162
162
|
@default_language = document.catalog[:Lang] || 'x-default'
|
163
163
|
@metadata = Hash.new {|h, k| h[k] = {} }
|
164
|
+
@custom_metadata = []
|
164
165
|
write_info_dict(true)
|
165
166
|
write_metadata_stream(true)
|
166
167
|
@document.register_listener(:complete_objects, &method(:write_metadata))
|
@@ -248,6 +249,16 @@ module HexaPDF
|
|
248
249
|
end
|
249
250
|
end
|
250
251
|
|
252
|
+
# Adds the given +data+ string as custom metadata to the XMP document.
|
253
|
+
#
|
254
|
+
# The +data+ string must contain a fully valid 'rdf:Description' element.
|
255
|
+
#
|
256
|
+
# Using this method allows adding metadata like PDF/A schema definitions for which there is no
|
257
|
+
# direct support by HexaPDF.
|
258
|
+
def custom_metadata(data)
|
259
|
+
@custom_metadata << data
|
260
|
+
end
|
261
|
+
|
251
262
|
# :call-seq:
|
252
263
|
# metadata.delete
|
253
264
|
# metadata.delete(ns_prefix)
|
@@ -469,7 +480,7 @@ module HexaPDF
|
|
469
480
|
<?xpacket begin="\u{FEFF}" id="#{SecureRandom.uuid.tr('-', '')}"?>
|
470
481
|
<x:xmpmeta xmlns:x="adobe:ns:meta/">
|
471
482
|
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
472
|
-
#{data}
|
483
|
+
#{data}#{@custom_metadata.empty? ? '' : "\n#{@custom_metadata.join("\n")}"}
|
473
484
|
</rdf:RDF>
|
474
485
|
</x:xmpmeta>
|
475
486
|
<?xpacket end="r"?>
|
data/lib/hexapdf/document.rb
CHANGED
@@ -278,6 +278,14 @@ module HexaPDF
|
|
278
278
|
# If the same argument is provided in multiple invocations, the import is done only once and
|
279
279
|
# the previously imported object is returned.
|
280
280
|
#
|
281
|
+
# Note: If you first create a PDF document from scratch and then want to import objects from it
|
282
|
+
# into another PDF document, you need to run the following on the source document:
|
283
|
+
#
|
284
|
+
# doc.dispatch_message(:complete_objects)
|
285
|
+
# doc.validate
|
286
|
+
#
|
287
|
+
# This ensures that the source document has all the necessary PDF structures set-up correctly.
|
288
|
+
#
|
281
289
|
# See: Importer
|
282
290
|
def import(obj)
|
283
291
|
source = (obj.kind_of?(HexaPDF::Object) ? obj.document : nil)
|
@@ -617,13 +625,18 @@ module HexaPDF
|
|
617
625
|
# writing the document.
|
618
626
|
#
|
619
627
|
# The security handler used for encrypting is selected via the +name+ argument. All other
|
620
|
-
# arguments are passed on the security handler.
|
628
|
+
# arguments are passed on to the security handler.
|
621
629
|
#
|
622
630
|
# If the document should not be encrypted, the +name+ argument has to be set to +nil+. This
|
623
631
|
# removes the security handler and deletes the trailer's Encrypt dictionary.
|
624
632
|
#
|
625
633
|
# See: Encryption::SecurityHandler#set_up_encryption and
|
626
634
|
# Encryption::StandardSecurityHandler::EncryptionOptions for possible encryption options.
|
635
|
+
#
|
636
|
+
# Examples:
|
637
|
+
#
|
638
|
+
# document.encrypt(name: nil) # remove the existing encryption
|
639
|
+
# document.encrypt(algorithm: :aes, key_length: 256, permissions: [:print, :extract_content]
|
627
640
|
def encrypt(name: :Standard, **options)
|
628
641
|
if name.nil?
|
629
642
|
trailer.delete(:Encrypt)
|
data/lib/hexapdf/encryption.rb
CHANGED
@@ -46,6 +46,23 @@ module HexaPDF
|
|
46
46
|
#
|
47
47
|
# This module contains all encryption and security related code to facilitate PDF encryption.
|
48
48
|
#
|
49
|
+
# === Working With Encrypted Documents
|
50
|
+
#
|
51
|
+
# When a PDF document is opened, an encryption password can be specified. This is necessary if a
|
52
|
+
# user password is set on the file and optional otherwise (because the default password is
|
53
|
+
# automatically tried):
|
54
|
+
#
|
55
|
+
# HexaPDF::Document.open(filename, decryption_opts: {password: 'somepassword'}) do |doc|
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# To remove the encryption from a PDF document, use the following:
|
59
|
+
#
|
60
|
+
# document.encrypt(name: nil)
|
61
|
+
#
|
62
|
+
# To encrypt a PDF document, use the same method but specify the required encryption options:
|
63
|
+
#
|
64
|
+
# document.encrypt(algorithm: :aes, key_length: 256)
|
65
|
+
#
|
49
66
|
#
|
50
67
|
# === Security Handlers
|
51
68
|
#
|
data/lib/hexapdf/layout/box.rb
CHANGED
@@ -35,6 +35,7 @@
|
|
35
35
|
#++
|
36
36
|
require 'hexapdf/layout/style'
|
37
37
|
require 'geom2d/utils'
|
38
|
+
require 'hexapdf/utils'
|
38
39
|
|
39
40
|
module HexaPDF
|
40
41
|
module Layout
|
@@ -60,9 +61,9 @@ module HexaPDF
|
|
60
61
|
# instantiated from the common convenience method HexaPDF::Document::Layout#box. To use this
|
61
62
|
# facility subclasses need to be registered with the configuration option 'layout.boxes.map'.
|
62
63
|
#
|
63
|
-
# The methods #supports_position_flow?, #empty?, #
|
64
|
-
#
|
65
|
-
#
|
64
|
+
# The methods #supports_position_flow?, #empty?, #fit_content, #split_content, and #draw_content
|
65
|
+
# need to be customized according to the subclass's use case (also see the documentation of the
|
66
|
+
# methods besides the information below):
|
66
67
|
#
|
67
68
|
# #supports_position_flow?::
|
68
69
|
# If the subclass supports the value :flow of the 'position' style property, this method
|
@@ -71,25 +72,22 @@ module HexaPDF
|
|
71
72
|
# #empty?::
|
72
73
|
# This method should return +true+ if the subclass won't draw anything when #draw is called.
|
73
74
|
#
|
74
|
-
# #
|
75
|
-
# This method
|
76
|
-
#
|
77
|
-
# #split.
|
75
|
+
# #fit_content::
|
76
|
+
# This method determines whether the box fits into the available region and should set the
|
77
|
+
# status of #fit_result appropriately.
|
78
78
|
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
79
|
+
# It is called from the #fit method which should not be overridden in most cases. The
|
80
|
+
# default implementations of both methods provide code common to all use-cases and delegates
|
81
|
+
# the specifics to the subclass-specific #fit_content method.
|
82
82
|
#
|
83
|
-
# #
|
84
|
-
# This method
|
85
|
-
#
|
86
|
-
#
|
87
|
-
# box.
|
83
|
+
# #split_content::
|
84
|
+
# This method is called from #split which handles the common cases based on the status of
|
85
|
+
# the #fit_result. It needs to handle the case when only some part of the box fits. The
|
86
|
+
# method #create_split_box should be used for getting a basic cloned box.
|
88
87
|
#
|
89
|
-
# #
|
90
|
-
# This method draws the content and
|
91
|
-
# drawing the border and background. So
|
92
|
-
# drawing commands should be implemented in the #draw_content method.
|
88
|
+
# #draw_content::
|
89
|
+
# This method draws the box specific content and is called from #draw which already handles
|
90
|
+
# things like drawing the border and background. So #draw should usually not be overridden.
|
93
91
|
#
|
94
92
|
# This base class provides various private helper methods for use in the above methods:
|
95
93
|
#
|
@@ -117,6 +115,104 @@ module HexaPDF
|
|
117
115
|
|
118
116
|
include HexaPDF::Utils
|
119
117
|
|
118
|
+
# Stores the result of fitting a box in a frame.
|
119
|
+
class FitResult
|
120
|
+
|
121
|
+
# The box that was fitted into the frame.
|
122
|
+
attr_accessor :box
|
123
|
+
|
124
|
+
# The frame into which the box was fitted.
|
125
|
+
attr_accessor :frame
|
126
|
+
|
127
|
+
# The horizontal position where the box will be drawn.
|
128
|
+
attr_accessor :x
|
129
|
+
|
130
|
+
# The vertical position where the box will be drawn.
|
131
|
+
attr_accessor :y
|
132
|
+
|
133
|
+
# The rectangle (a Geom2D::Rectangle object) that will be removed from the frame when
|
134
|
+
# drawing the box.
|
135
|
+
attr_accessor :mask
|
136
|
+
|
137
|
+
# The status result of fitting the box in the frame.
|
138
|
+
#
|
139
|
+
# Allowed values are:
|
140
|
+
#
|
141
|
+
# +:failure+:: (default) Indicates fitting the box has failed.
|
142
|
+
# +:success+:: Indicates that the box was completely fitted.
|
143
|
+
# +:overflow+:: Indicates that only a part of the box was fitted.
|
144
|
+
attr_reader :status
|
145
|
+
|
146
|
+
# Initializes the result object for the given box and, optionally, frame.
|
147
|
+
def initialize(box, frame: nil)
|
148
|
+
@box = box
|
149
|
+
reset(frame)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Resets the result object.
|
153
|
+
def reset(frame)
|
154
|
+
@frame = frame
|
155
|
+
@x = @y = @mask = nil
|
156
|
+
@status = :failure
|
157
|
+
self
|
158
|
+
end
|
159
|
+
|
160
|
+
# Sets the result status to success.
|
161
|
+
def success!
|
162
|
+
@status = :success
|
163
|
+
end
|
164
|
+
|
165
|
+
# Returns +true+ if fitting was successful.
|
166
|
+
def success?
|
167
|
+
@status == :success
|
168
|
+
end
|
169
|
+
|
170
|
+
# Sets the result status to overflow.
|
171
|
+
def overflow!
|
172
|
+
@status = :overflow
|
173
|
+
end
|
174
|
+
|
175
|
+
# Returns +true+ if only parts of the box were fitted.
|
176
|
+
def overflow?
|
177
|
+
@status == :overflow
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns +true+ if fitting was a failure.
|
181
|
+
def failure?
|
182
|
+
@status == :failure
|
183
|
+
end
|
184
|
+
|
185
|
+
# Draws the #box onto the canvas at (#x + *dx*, #y + *dy*).
|
186
|
+
#
|
187
|
+
# The relative offset (dx, dy) is useful when rendering results that were accumulated and
|
188
|
+
# then need to be moved because the container holding them changes its position.
|
189
|
+
#
|
190
|
+
# The configuration option "debug" can be used to add visual debug output with respect to
|
191
|
+
# box placement.
|
192
|
+
def draw(canvas, dx: 0, dy: 0)
|
193
|
+
return if box.height == 0 || box.width == 0
|
194
|
+
doc = canvas.context.document
|
195
|
+
if doc.config['debug']
|
196
|
+
name = (frame.parent_boxes + [box]).map do |box|
|
197
|
+
box.class.to_s.sub(/.*::/, '')
|
198
|
+
end.join('-') << "##{box.object_id}"
|
199
|
+
name = "#{name} (#{(x + dx).to_i},#{(y + dy).to_i}-#{mask.width.to_i}x#{mask.height.to_i})"
|
200
|
+
ocg = doc.optional_content.ocg(name)
|
201
|
+
canvas.optional_content(ocg) do
|
202
|
+
canvas.translate(dx, dy) do
|
203
|
+
canvas.fill_color("green").stroke_color("darkgreen").
|
204
|
+
opacity(fill_alpha: 0.1, stroke_alpha: 0.2).
|
205
|
+
draw(:geom2d, object: mask, path_only: true).fill_stroke
|
206
|
+
end
|
207
|
+
end
|
208
|
+
page = "Page #{canvas.context.index + 1}" rescue "XObject"
|
209
|
+
doc.optional_content.default_configuration.add_ocg_to_ui(ocg, path: ['Debug', page])
|
210
|
+
end
|
211
|
+
box.draw(canvas, x + dx, y + dy)
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
|
120
216
|
# Creates a new Box object, using the provided block as drawing block (see ::new).
|
121
217
|
#
|
122
218
|
# If +content_box+ is +true+, the width and height are taken to mean the content width and
|
@@ -142,10 +238,15 @@ module HexaPDF
|
|
142
238
|
# The height of the box, including padding and/or borders.
|
143
239
|
attr_reader :height
|
144
240
|
|
241
|
+
# The FitResult instance holding the result after a call to #fit.
|
242
|
+
attr_reader :fit_result
|
243
|
+
|
145
244
|
# The style to be applied.
|
146
245
|
#
|
147
246
|
# Only the following properties are used:
|
148
247
|
#
|
248
|
+
# * Style#position
|
249
|
+
# * Style#overflow
|
149
250
|
# * Style#background_color
|
150
251
|
# * Style#background_alpha
|
151
252
|
# * Style#padding
|
@@ -189,7 +290,7 @@ module HexaPDF
|
|
189
290
|
@style = Style.create(style)
|
190
291
|
@properties = properties || {}
|
191
292
|
@draw_block = block
|
192
|
-
@
|
293
|
+
@fit_result = FitResult.new(self)
|
193
294
|
@split_box = false
|
194
295
|
end
|
195
296
|
|
@@ -216,7 +317,7 @@ module HexaPDF
|
|
216
317
|
height < 0 ? 0 : height
|
217
318
|
end
|
218
319
|
|
219
|
-
# Fits the box into the *frame* and returns
|
320
|
+
# Fits the box into the *frame* and returns the #fit_result.
|
220
321
|
#
|
221
322
|
# The arguments +available_width+ and +available_height+ are the width and height of the
|
222
323
|
# current region of the frame, adjusted for this box. The frame itself is provided as third
|
@@ -224,70 +325,68 @@ module HexaPDF
|
|
224
325
|
#
|
225
326
|
# The default implementation uses the given available width and height for the box width and
|
226
327
|
# height if they were initially set to 0. Otherwise the intially specified dimensions are
|
227
|
-
# used.
|
328
|
+
# used. The method returns early if the thus configured box already doesn't fit. Otherwise,
|
329
|
+
# the #fit_content method is called which allows sub-classes to fit their content.
|
228
330
|
#
|
229
331
|
# The following variables are set that may later be used during splitting or drawing:
|
230
332
|
#
|
231
333
|
# * (@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 #
|
233
|
-
# * @fit_successful: +true+ if fitting was successful.
|
334
|
+
# used to adjust the drawing position in #draw_content if necessary.
|
234
335
|
def fit(available_width, available_height, frame)
|
336
|
+
@fit_result.reset(frame)
|
235
337
|
@width = (@initial_width > 0 ? @initial_width : available_width)
|
236
338
|
@height = (@initial_height > 0 ? @initial_height : available_height)
|
237
|
-
@
|
238
|
-
|
239
|
-
return unless @fit_successful
|
339
|
+
return @fit_result if style.position != :flow && (float_compare(@width, available_width) > 0 ||
|
340
|
+
float_compare(@height, available_height) > 0)
|
240
341
|
|
241
|
-
|
342
|
+
fit_content(available_width, available_height, frame)
|
242
343
|
|
243
344
|
@fit_x = frame.x + reserved_width_left
|
244
345
|
@fit_y = frame.y - @height + reserved_height_bottom
|
245
346
|
|
246
|
-
@
|
347
|
+
@fit_result
|
247
348
|
end
|
248
349
|
|
249
350
|
# Tries to split the box into two, the first of which needs to fit into the current region of
|
250
|
-
# the frame, and returns the parts as array.
|
351
|
+
# the frame, and returns the parts as array. The method #fit needs to be called before this
|
352
|
+
# method to correctly set-up the #fit_result.
|
251
353
|
#
|
252
354
|
# If the first item in the result array is not +nil+, it needs to be this box and it means
|
253
355
|
# that even when #fit fails, a part of the box may still fit. Note that #fit should not be
|
254
|
-
# called before #draw on the first box since it is already fitted. If not even a part of
|
255
|
-
# box fits into the current region, +nil+ should be returned as the first array element.
|
356
|
+
# called again before #draw on the first box since it is already fitted. If not even a part of
|
357
|
+
# this box fits into the current region, +nil+ should be returned as the first array element.
|
256
358
|
#
|
257
359
|
# Possible return values:
|
258
360
|
#
|
259
|
-
# [self]:: The box fully fits into the current region.
|
361
|
+
# [self, nil]:: The box fully fits into the current region.
|
260
362
|
# [nil, self]:: The box can't be split or no part of the box fits into the current region.
|
261
363
|
# [self, new_box]:: A part of the box fits and a new box is returned for the rest.
|
262
364
|
#
|
263
|
-
# This default implementation provides the basic functionality based on the
|
264
|
-
# should be sufficient for most subclasses; only #split_content needs to be
|
265
|
-
# necessary.
|
266
|
-
def split
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
float_compare(@height, available_height) > 0)) ||
|
272
|
-
content_height == 0 || content_width == 0
|
273
|
-
[nil, self]
|
274
|
-
else
|
275
|
-
split_content(available_width, available_height, frame)
|
365
|
+
# This default implementation provides the basic functionality based on the status of the
|
366
|
+
# #fit_result that should be sufficient for most subclasses; only #split_content needs to be
|
367
|
+
# implemented if necessary.
|
368
|
+
def split
|
369
|
+
case @fit_result.status
|
370
|
+
when :overflow then (@initial_height > 0 ? [self, nil] : split_content)
|
371
|
+
when :failure then [nil, self]
|
372
|
+
when :success then [self, nil]
|
276
373
|
end
|
277
374
|
end
|
278
375
|
|
279
376
|
# Draws the content of the box onto the canvas at the position (x, y).
|
280
377
|
#
|
281
|
-
#
|
282
|
-
#
|
378
|
+
# When +@draw_block+ is used (the block specified when creating the box), the coordinate
|
379
|
+
# system is translated so that the origin is at the bottom left corner of the **content box**.
|
283
380
|
#
|
284
|
-
#
|
285
|
-
#
|
286
|
-
#
|
287
|
-
# operations when the block does nothing.
|
288
|
-
#
|
289
|
-
# Alternatively, if a #draw_content method is defined, this method is called.
|
381
|
+
# Subclasses should not rely on the +@draw_block+ but implement the #draw_content method. The
|
382
|
+
# coordinates passed to it are also modified to represent the bottom left corner of the
|
383
|
+
# content box but the coordinate system is not translated.
|
290
384
|
def draw(canvas, x, y)
|
385
|
+
if @fit_result.overflow? && @initial_height > 0 && style.overflow == :error
|
386
|
+
raise HexaPDF::Error, "Box with limited height doesn't completely fit and " \
|
387
|
+
"style property overflow is set to :error"
|
388
|
+
end
|
389
|
+
|
291
390
|
if (oc = properties['optional_content'])
|
292
391
|
canvas.optional_content(oc)
|
293
392
|
end
|
@@ -380,12 +479,13 @@ module HexaPDF
|
|
380
479
|
|
381
480
|
# Fits the content of the box and returns whether fitting was successful.
|
382
481
|
#
|
383
|
-
# This is just a stub implementation that
|
384
|
-
# provide the box
|
482
|
+
# This is just a stub implementation that sets the #fit_result status to success if the
|
483
|
+
# content rectangle is not degenerate. Subclasses should override it to provide the box
|
484
|
+
# specific behaviour.
|
385
485
|
#
|
386
486
|
# See #fit for details.
|
387
487
|
def fit_content(_available_width, _available_height, _frame)
|
388
|
-
|
488
|
+
fit_result.success! if content_width > 0 && content_height > 0
|
389
489
|
end
|
390
490
|
|
391
491
|
# Splits the content of the box.
|
@@ -394,12 +494,12 @@ module HexaPDF
|
|
394
494
|
# the content when it didn't fit.
|
395
495
|
#
|
396
496
|
# Subclasses that support splitting content need to provide an appropriate implementation and
|
397
|
-
# use #create_split_box to create a cloned box to supply as the second argument.
|
398
|
-
def split_content
|
497
|
+
# use #create_split_box to create a cloned box to supply as the second return argument.
|
498
|
+
def split_content
|
399
499
|
[nil, self]
|
400
500
|
end
|
401
501
|
|
402
|
-
# Draws the content of the box at position [x, y] which is the bottom
|
502
|
+
# Draws the content of the box at position [x, y] which is the bottom left corner of the
|
403
503
|
# content box.
|
404
504
|
#
|
405
505
|
# This implementation uses the drawing block provided on initialization, if set, to draw the
|
@@ -421,7 +521,7 @@ module HexaPDF
|
|
421
521
|
box = clone
|
422
522
|
box.instance_variable_set(:@width, @initial_width)
|
423
523
|
box.instance_variable_set(:@height, @initial_height)
|
424
|
-
box.instance_variable_set(:@
|
524
|
+
box.instance_variable_set(:@fit_result, FitResult.new(box))
|
425
525
|
box.instance_variable_set(:@split_box, split_box_value)
|
426
526
|
box
|
427
527
|
end
|
@@ -47,8 +47,8 @@ module HexaPDF
|
|
47
47
|
#
|
48
48
|
# * Then use the #fit method to fit boxes one after the other. No drawing is done.
|
49
49
|
#
|
50
|
-
# * Once all boxes have been fitted, the #fit_results, #remaining_boxes and #
|
51
|
-
#
|
50
|
+
# * Once all boxes have been fitted, the #fit_results, #remaining_boxes and #success? methods
|
51
|
+
# can be used to get the result:
|
52
52
|
#
|
53
53
|
# - If there are no remaining boxes, all boxes were successfully fitted into the frames.
|
54
54
|
# - If there are remaining boxes but no fit results, the first box could not be fitted.
|
@@ -111,6 +111,7 @@ module HexaPDF
|
|
111
111
|
@content_heights[@frame_index] = [@content_heights[@frame_index],
|
112
112
|
@initial_frame_y[@frame_index] - result.mask.y].max
|
113
113
|
@fit_results << result
|
114
|
+
break unless box
|
114
115
|
elsif !current_frame.find_next_region
|
115
116
|
@frame_index += 1
|
116
117
|
end
|
@@ -126,7 +127,7 @@ module HexaPDF
|
|
126
127
|
end
|
127
128
|
|
128
129
|
# Returns +true+ if all boxes were successfully fitted.
|
129
|
-
def
|
130
|
+
def success?
|
130
131
|
@remaining_boxes.empty?
|
131
132
|
end
|
132
133
|
|