hexapdf 0.8.0 → 0.9.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 +52 -1
- data/CONTRIBUTERS +1 -1
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/examples/018-composer.rb +44 -0
- data/lib/hexapdf/cli.rb +2 -0
- data/lib/hexapdf/cli/command.rb +2 -2
- data/lib/hexapdf/cli/optimize.rb +1 -1
- data/lib/hexapdf/cli/split.rb +82 -0
- data/lib/hexapdf/composer.rb +303 -0
- data/lib/hexapdf/configuration.rb +2 -2
- data/lib/hexapdf/content/canvas.rb +3 -6
- data/lib/hexapdf/dictionary.rb +0 -3
- data/lib/hexapdf/document.rb +30 -22
- data/lib/hexapdf/document/files.rb +1 -1
- data/lib/hexapdf/document/images.rb +1 -1
- data/lib/hexapdf/filter/predictor.rb +8 -8
- data/lib/hexapdf/layout.rb +1 -0
- data/lib/hexapdf/layout/box.rb +55 -12
- data/lib/hexapdf/layout/frame.rb +143 -46
- data/lib/hexapdf/layout/image_box.rb +96 -0
- data/lib/hexapdf/layout/inline_box.rb +10 -0
- data/lib/hexapdf/layout/line.rb +1 -1
- data/lib/hexapdf/layout/style.rb +55 -3
- data/lib/hexapdf/layout/text_box.rb +38 -8
- data/lib/hexapdf/layout/text_layouter.rb +66 -52
- data/lib/hexapdf/object.rb +3 -2
- data/lib/hexapdf/parser.rb +6 -1
- data/lib/hexapdf/rectangle.rb +6 -0
- data/lib/hexapdf/reference.rb +4 -6
- data/lib/hexapdf/revision.rb +34 -8
- data/lib/hexapdf/revisions.rb +12 -2
- data/lib/hexapdf/stream.rb +18 -0
- data/lib/hexapdf/task.rb +1 -1
- data/lib/hexapdf/task/dereference.rb +1 -1
- data/lib/hexapdf/task/optimize.rb +2 -2
- data/lib/hexapdf/type/form.rb +10 -0
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +25 -5
- data/man/man1/hexapdf.1 +17 -0
- data/test/hexapdf/layout/test_box.rb +7 -1
- data/test/hexapdf/layout/test_frame.rb +38 -1
- data/test/hexapdf/layout/test_image_box.rb +73 -0
- data/test/hexapdf/layout/test_inline_box.rb +8 -0
- data/test/hexapdf/layout/test_line.rb +1 -1
- data/test/hexapdf/layout/test_style.rb +58 -1
- data/test/hexapdf/layout/test_text_box.rb +57 -12
- data/test/hexapdf/layout/test_text_layouter.rb +14 -4
- data/test/hexapdf/task/test_optimize.rb +3 -3
- data/test/hexapdf/test_composer.rb +258 -0
- data/test/hexapdf/test_document.rb +29 -3
- data/test/hexapdf/test_importer.rb +8 -2
- data/test/hexapdf/test_object.rb +3 -1
- data/test/hexapdf/test_parser.rb +6 -0
- data/test/hexapdf/test_rectangle.rb +7 -0
- data/test/hexapdf/test_reference.rb +3 -1
- data/test/hexapdf/test_revision.rb +13 -0
- data/test/hexapdf/test_revisions.rb +1 -0
- data/test/hexapdf/test_stream.rb +13 -0
- data/test/hexapdf/test_writer.rb +13 -2
- data/test/hexapdf/type/test_annotation.rb +1 -1
- data/test/hexapdf/type/test_form.rb +13 -2
- metadata +10 -4
data/lib/hexapdf/object.rb
CHANGED
@@ -293,9 +293,10 @@ module HexaPDF
|
|
293
293
|
(oid == other.oid ? gen <=> other.gen : oid <=> other.oid)
|
294
294
|
end
|
295
295
|
|
296
|
-
# Returns +true+ if the other object is an Object and wraps the same #data structure
|
296
|
+
# Returns +true+ if the other object is an Object and wraps the same #data structure, or if the
|
297
|
+
# other object is a Reference with the same oid/gen.
|
297
298
|
def ==(other)
|
298
|
-
other.kind_of?(Object) && data == other.data
|
299
|
+
(other.kind_of?(Object) && data == other.data) || (other.kind_of?(Reference) && other == self)
|
299
300
|
end
|
300
301
|
|
301
302
|
# Returns +true+ if the other object references the same PDF object as this object.
|
data/lib/hexapdf/parser.rb
CHANGED
@@ -45,6 +45,9 @@ module HexaPDF
|
|
45
45
|
# See: PDF1.7 s7
|
46
46
|
class Parser
|
47
47
|
|
48
|
+
# The IO stream which is parsed.
|
49
|
+
attr_reader :io
|
50
|
+
|
48
51
|
# Creates a new parser for the given IO object.
|
49
52
|
#
|
50
53
|
# PDF references are resolved using the associated Document object.
|
@@ -270,6 +273,8 @@ module HexaPDF
|
|
270
273
|
#
|
271
274
|
# See: PDF1.7 s7.5.5, ADB1.7 sH.3-3.4.4
|
272
275
|
def startxref_offset
|
276
|
+
return @startxref_offset if defined?(@startxref_offset)
|
277
|
+
|
273
278
|
@io.seek(0, IO::SEEK_END)
|
274
279
|
step_size = 1024
|
275
280
|
pos = @io.pos
|
@@ -301,7 +306,7 @@ module HexaPDF
|
|
301
306
|
force: eof_index < 2 || lines[eof_index - 2].strip != "startxref")
|
302
307
|
end
|
303
308
|
|
304
|
-
lines[eof_index - 1].to_i
|
309
|
+
@startxref_offset = lines[eof_index - 1].to_i
|
305
310
|
end
|
306
311
|
|
307
312
|
# Returns the PDF version number that is stored in the file header.
|
data/lib/hexapdf/rectangle.rb
CHANGED
@@ -81,6 +81,12 @@ module HexaPDF
|
|
81
81
|
value[3] - value[1]
|
82
82
|
end
|
83
83
|
|
84
|
+
# Compares this rectangle to +other+ like in Object#== but also allows comparison to simple
|
85
|
+
# arrays if the rectangle is a direct object.
|
86
|
+
def ==(other)
|
87
|
+
super || (other.kind_of?(Array) && !indirect? && other == data.value)
|
88
|
+
end
|
89
|
+
|
84
90
|
private
|
85
91
|
|
86
92
|
# Ensures that the value is an array containing four numbers that specify the bottom left and
|
data/lib/hexapdf/reference.rb
CHANGED
@@ -73,16 +73,14 @@ module HexaPDF
|
|
73
73
|
(oid == other.oid ? gen <=> other.gen : oid <=> other.oid)
|
74
74
|
end
|
75
75
|
|
76
|
-
# Returns +true+ if the other object is a Reference and has the same object and generation
|
77
|
-
# numbers.
|
78
|
-
def ==(other)
|
79
|
-
other.kind_of?(Reference) && oid == other.oid && gen == other.gen
|
80
|
-
end
|
81
|
-
|
82
76
|
# Returns +true+ if the other object references the same PDF object as this reference object.
|
77
|
+
#
|
78
|
+
# This is necessary so that Object and Reference objects can be used as interchangable hash
|
79
|
+
# keys and can be compared.
|
83
80
|
def eql?(other)
|
84
81
|
other.respond_to?(:oid) && oid == other.oid && other.respond_to?(:gen) && gen == other.gen
|
85
82
|
end
|
83
|
+
alias == eql?
|
86
84
|
|
87
85
|
# Computes the hash value based on the object and generation numbers.
|
88
86
|
def hash
|
data/lib/hexapdf/revision.rb
CHANGED
@@ -164,17 +164,18 @@ module HexaPDF
|
|
164
164
|
end
|
165
165
|
|
166
166
|
# :call-seq:
|
167
|
-
# revision.each {|obj| block } -> revision
|
168
|
-
# revision.each -> Enumerator
|
167
|
+
# revision.each(only_loaded: false) {|obj| block } -> revision
|
168
|
+
# revision.each(only_loaded: false) -> Enumerator
|
169
169
|
#
|
170
|
-
# Calls the given block
|
170
|
+
# Calls the given block for every object of the revision, or, if +only_loaded+ is +true+, for
|
171
|
+
# every already loaded object.
|
171
172
|
#
|
172
|
-
# Objects that are loadable via an associated cross-reference section but are currently not
|
173
|
-
# are loaded automatically
|
174
|
-
def each
|
175
|
-
return to_enum(__method__) unless block_given?
|
173
|
+
# Objects that are loadable via an associated cross-reference section but are currently not
|
174
|
+
# loaded, are loaded automatically if +only_loaded+ is +false+.
|
175
|
+
def each(only_loaded: false)
|
176
|
+
return to_enum(__method__, only_loaded: only_loaded) unless block_given?
|
176
177
|
|
177
|
-
if defined?(@all_objects_loaded)
|
178
|
+
if defined?(@all_objects_loaded) || only_loaded
|
178
179
|
@objects.each {|_oid, _gen, data| yield(data) }
|
179
180
|
else
|
180
181
|
seen = {}
|
@@ -189,6 +190,31 @@ module HexaPDF
|
|
189
190
|
self
|
190
191
|
end
|
191
192
|
|
193
|
+
# :call-seq:
|
194
|
+
# revision.each_modified_object {|obj| block } -> revision
|
195
|
+
# revision.each_modified_object -> Enumerator
|
196
|
+
#
|
197
|
+
# Calls the given block once for each object that has been modified since it was loaded.
|
198
|
+
#
|
199
|
+
# Note that this also means that for revisions without an associated cross-reference section all
|
200
|
+
# loaded objects will be yielded.
|
201
|
+
def each_modified_object
|
202
|
+
return to_enum(__method__) unless block_given?
|
203
|
+
|
204
|
+
@objects.each do |oid, gen, obj|
|
205
|
+
if @xref_section.entry?(oid, gen)
|
206
|
+
stored_obj = @loader.call(@xref_section[oid, gen])
|
207
|
+
if obj.data.value != stored_obj.data.value || obj.data.stream != stored_obj.data.stream
|
208
|
+
yield(obj)
|
209
|
+
end
|
210
|
+
else
|
211
|
+
yield(obj)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
self
|
216
|
+
end
|
217
|
+
|
192
218
|
private
|
193
219
|
|
194
220
|
# Loads a single object from the associated cross-reference section.
|
data/lib/hexapdf/revisions.rb
CHANGED
@@ -88,13 +88,16 @@ module HexaPDF
|
|
88
88
|
end
|
89
89
|
|
90
90
|
document.version = parser.file_header_version
|
91
|
-
new(document, initial_revisions: revisions)
|
91
|
+
new(document, initial_revisions: revisions, parser: parser)
|
92
92
|
end
|
93
93
|
|
94
94
|
end
|
95
95
|
|
96
96
|
include Enumerable
|
97
97
|
|
98
|
+
# The Parser instance used for reading the initial revisions.
|
99
|
+
attr_reader :parser
|
100
|
+
|
98
101
|
# Creates a new revisions object for the given PDF document.
|
99
102
|
#
|
100
103
|
# Options:
|
@@ -102,8 +105,15 @@ module HexaPDF
|
|
102
105
|
# initial_revisions::
|
103
106
|
# An array of revisions that should initially be used. If this option is not specified, a
|
104
107
|
# single empty revision is added.
|
105
|
-
|
108
|
+
#
|
109
|
+
# parser::
|
110
|
+
# The parser with which the initial revisions were read. If this option is not specified
|
111
|
+
# even though the document was read from an IO stream, some parts may not work, like
|
112
|
+
# incremental writing.
|
113
|
+
def initialize(document, initial_revisions: nil, parser: nil)
|
106
114
|
@document = document
|
115
|
+
@parser = parser
|
116
|
+
|
107
117
|
@revisions = []
|
108
118
|
if initial_revisions
|
109
119
|
@revisions += initial_revisions
|
data/lib/hexapdf/stream.rb
CHANGED
@@ -96,6 +96,24 @@ module HexaPDF
|
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
99
|
+
# Returns whether this stream data object is equal to the other stream data object.
|
100
|
+
def ==(other)
|
101
|
+
other.kind_of?(StreamData) &&
|
102
|
+
source == other.source && offset == other.offset && length == other.length &&
|
103
|
+
filter == other.filter && decode_parms == other.decode_parms
|
104
|
+
end
|
105
|
+
|
106
|
+
protected
|
107
|
+
|
108
|
+
# The source.
|
109
|
+
attr_reader :source
|
110
|
+
|
111
|
+
# The optional offset into the bytes provided by source.
|
112
|
+
attr_reader :offset
|
113
|
+
|
114
|
+
# The optional number of bytes to use starting from offset.
|
115
|
+
attr_reader :length
|
116
|
+
|
99
117
|
end
|
100
118
|
|
101
119
|
# Implements Stream objects of the PDF object system.
|
data/lib/hexapdf/task.rb
CHANGED
@@ -55,7 +55,7 @@ module HexaPDF
|
|
55
55
|
#
|
56
56
|
# doc = HexaPDF::Document.new
|
57
57
|
# doc.config['task.map'][:validate] = lambda do |doc|
|
58
|
-
# doc.each(
|
58
|
+
# doc.each(only_current: false) {|obj| obj.validate || raise "Invalid object #{obj}"}
|
59
59
|
# end
|
60
60
|
module Task
|
61
61
|
|
@@ -69,7 +69,7 @@ module HexaPDF
|
|
69
69
|
else
|
70
70
|
dereference(@doc.trailer)
|
71
71
|
@result = []
|
72
|
-
@doc.each(
|
72
|
+
@doc.each(only_current: false) do |obj|
|
73
73
|
if !@seen.key?(obj.data) && obj.type != :ObjStm && obj.type != :XRef
|
74
74
|
@result << obj
|
75
75
|
elsif obj.kind_of?(HexaPDF::Stream) && (val = obj.value[:Length]) &&
|
@@ -78,7 +78,7 @@ module HexaPDF
|
|
78
78
|
elsif xref_streams != :preserve
|
79
79
|
process_xref_streams(doc, xref_streams)
|
80
80
|
else
|
81
|
-
doc.each(
|
81
|
+
doc.each(only_current: false, &method(:delete_fields_with_defaults))
|
82
82
|
end
|
83
83
|
|
84
84
|
compress_pages(doc) if compress_pages
|
@@ -169,7 +169,7 @@ module HexaPDF
|
|
169
169
|
def self.process_xref_streams(doc, method)
|
170
170
|
case method
|
171
171
|
when :delete
|
172
|
-
doc.each(
|
172
|
+
doc.each(only_current: false) do |obj, rev|
|
173
173
|
if obj.type == :XRef
|
174
174
|
rev.delete(obj)
|
175
175
|
else
|
data/lib/hexapdf/type/form.rb
CHANGED
@@ -72,6 +72,16 @@ module HexaPDF
|
|
72
72
|
self[:BBox]
|
73
73
|
end
|
74
74
|
|
75
|
+
# Returns the width of the bounding box (see #box).
|
76
|
+
def width
|
77
|
+
box.width
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns the height of the bounding box (see #box).
|
81
|
+
def height
|
82
|
+
box.height
|
83
|
+
end
|
84
|
+
|
75
85
|
# Returns the contents of the form XObject.
|
76
86
|
#
|
77
87
|
# Note: This is the same as #stream but here for interface compatibility with Page.
|
data/lib/hexapdf/version.rb
CHANGED
data/lib/hexapdf/writer.rb
CHANGED
@@ -41,9 +41,14 @@ module HexaPDF
|
|
41
41
|
# Writes the contents of a PDF document to an IO stream.
|
42
42
|
class Writer
|
43
43
|
|
44
|
-
# Writes the document to the IO object.
|
45
|
-
|
46
|
-
|
44
|
+
# Writes the document to the IO object. If +incremental+ is +true+ and the document was created
|
45
|
+
# from an existing PDF file, the changes are appended to a full copy of the source document.
|
46
|
+
def self.write(document, io, incremental: false)
|
47
|
+
if incremental && document.revisions.parser
|
48
|
+
new(document, io).write_incremental
|
49
|
+
else
|
50
|
+
new(document, io).write
|
51
|
+
end
|
47
52
|
end
|
48
53
|
|
49
54
|
# Creates a new writer object for the given HexaPDF document that gets written to the IO
|
@@ -56,13 +61,12 @@ module HexaPDF
|
|
56
61
|
@io.seek(0, IO::SEEK_SET) # TODO: incremental update!
|
57
62
|
|
58
63
|
@serializer = Serializer.new
|
64
|
+
@serializer.encrypter = @document.encrypted? ? @document.security_handler : nil
|
59
65
|
@rev_size = 0
|
60
66
|
end
|
61
67
|
|
62
68
|
# Writes the document to the IO object.
|
63
69
|
def write
|
64
|
-
@serializer.encrypter = @document.encrypted? ? @document.security_handler : nil
|
65
|
-
|
66
70
|
write_file_header
|
67
71
|
|
68
72
|
pos = nil
|
@@ -72,6 +76,22 @@ module HexaPDF
|
|
72
76
|
end
|
73
77
|
end
|
74
78
|
|
79
|
+
# Writes the complete source document and one revision containing all changes to the IO.
|
80
|
+
#
|
81
|
+
# For this method to work the document must have been created from an existing file.
|
82
|
+
def write_incremental
|
83
|
+
@document.revisions.parser.io.seek(0, IO::SEEK_SET)
|
84
|
+
IO.copy_stream(@document.revisions.parser.io, @io)
|
85
|
+
|
86
|
+
@rev_size = @document.revisions.current.next_free_oid
|
87
|
+
|
88
|
+
revision = Revision.new(@document.revisions.current.trailer)
|
89
|
+
@document.revisions.each do |rev|
|
90
|
+
rev.each_modified_object {|obj| revision.send(:add_without_check, obj) }
|
91
|
+
end
|
92
|
+
write_revision(revision, @document.revisions.parser.startxref_offset)
|
93
|
+
end
|
94
|
+
|
75
95
|
private
|
76
96
|
|
77
97
|
# Writes the PDF file header.
|
data/man/man1/hexapdf.1
CHANGED
@@ -29,6 +29,8 @@ Modifying an existing PDF file (see the \fBmodify\fP command)
|
|
29
29
|
.IP \(bu 4
|
30
30
|
Optimizing the file size of a PDF file (see the \fBoptimize\fP command)
|
31
31
|
.IP \(bu 4
|
32
|
+
Splitting a PDF file into individual pages (see the \fBsplit\fP command)
|
33
|
+
.IP \(bu 4
|
32
34
|
Batch execution of a command on multiple PDF files (see the \fBbatch\fP command)
|
33
35
|
.PD
|
34
36
|
.P
|
@@ -316,6 +318,17 @@ By default, all strategies except page compression are used since page compressi
|
|
316
318
|
The password to decrypt the \fIINPUT\fP\&\. Use \fB\-\fP for \fIPASSWORD\fP for reading it from standard input\.
|
317
319
|
.P
|
318
320
|
The \fBOptimization Options\fP can be used with this command\. Note that the defaults are changed to provide good compression out of the box\.
|
321
|
+
.SS "split"
|
322
|
+
Synopsis: \fBsplit\fP [\fBOPTIONS\fP] \fIINPUT\fP [\fIOUTPUT_SPEC\fP]
|
323
|
+
.P
|
324
|
+
This command splits the input file into multiple output files, each containing one page\.
|
325
|
+
.P
|
326
|
+
If no \fIOUTPUT_SPEC\fP is given, files of the form \fIINPUT_0001\.pdf\fP, \fIINPUT_0002\.pdf\fP, \.\.\. and so on are created (only the name \fIINPUT\fP without the file extension is used)\. Otherwise \fIOUTPUT_SPEC\fP determines the file names of the created files, with a printf\-style format string like \[u2018]%04d\[u2019] being replaced by the page number\. For example, if the files should be named \fIpage_01\.pdf\fP, \fIpage_02\.pdf\fP and so on, use \fIpage_%02d\.pdf\fP for the \fIOUTPUT_SPEC\fP\&\.
|
327
|
+
.TP
|
328
|
+
\fB\-p\fP \fIPASSWORD\fP, \fB\-\-password\fP \fIPASSWORD\fP
|
329
|
+
The password to decrypt the \fIINPUT\fP\&\. Use \fB\-\fP for \fIPASSWORD\fP for reading it from standard input\.
|
330
|
+
.P
|
331
|
+
Additionally, the \fBOptimization Options\fP and \fBEncryption Options\fP can be used\.
|
319
332
|
.SS "version"
|
320
333
|
This command shows the version of the hexapdf application\. It is an alternative to using the global \fB\-\-version\fP option\.
|
321
334
|
.SH "PAGES SPECIFICATION"
|
@@ -392,6 +405,10 @@ Encryption removal: Create the \fBoutput\.pdf\fP as copy of \fBinput\.pdf\fP but
|
|
392
405
|
\fBhexapdf optimize input\.pdf output\.pdf\fP
|
393
406
|
.P
|
394
407
|
Optimization: Compress the \fBinput\.pdf\fP to get a smaller file size\.
|
408
|
+
.SS "split"
|
409
|
+
\fBhexapdf split input\.pdf out_%02d\.pdf\fP
|
410
|
+
.P
|
411
|
+
Split the \fBinput\.pdf\fP into individual pages, naming the output files \fBout_01\.pdf\fP, \fBout_02\.pdf\fP, and so on\.
|
395
412
|
.SS "files"
|
396
413
|
\fBhexapdf files input\.pdf\fP
|
397
414
|
.br
|
@@ -24,7 +24,8 @@ describe HexaPDF::Layout::Box do
|
|
24
24
|
|
25
25
|
it "takes content width and height" do
|
26
26
|
box = HexaPDF::Layout::Box.create(width: 100, height: 200, content_box: true,
|
27
|
-
padding: 10,
|
27
|
+
padding: [10, 8, 6, 4],
|
28
|
+
border: {width: [10, 8, 6, 4]})
|
28
29
|
assert_equal(100, box.content_width)
|
29
30
|
assert_equal(200, box.content_height)
|
30
31
|
end
|
@@ -78,6 +79,11 @@ describe HexaPDF::Layout::Box do
|
|
78
79
|
end
|
79
80
|
end
|
80
81
|
|
82
|
+
it "can't be split into two parts" do
|
83
|
+
box = create_box(width: 100, height: 100)
|
84
|
+
assert_equal([nil, box], box.split(50, 50, nil))
|
85
|
+
end
|
86
|
+
|
81
87
|
describe "draw" do
|
82
88
|
it "draws the box onto the canvas" do
|
83
89
|
box = create_box(width: 150, height: 130) do |canvas, _|
|
@@ -40,7 +40,7 @@ describe HexaPDF::Layout::Frame do
|
|
40
40
|
assert_kind_of(HexaPDF::Layout::WidthFromPolygon, ws)
|
41
41
|
end
|
42
42
|
|
43
|
-
describe "draw" do
|
43
|
+
describe "fit and draw" do
|
44
44
|
before do
|
45
45
|
@frame = HexaPDF::Layout::Frame.new(10, 10, 100, 100)
|
46
46
|
@canvas = Minitest::Mock.new
|
@@ -80,6 +80,14 @@ describe HexaPDF::Layout::Frame do
|
|
80
80
|
)
|
81
81
|
end
|
82
82
|
|
83
|
+
it "determines the available space for #fit by using the space to the right and above" do
|
84
|
+
check_box(
|
85
|
+
{position: :absolute, position_hint: [10, 10]},
|
86
|
+
[20, 20],
|
87
|
+
[[[10, 10], [110, 10], [110, 20], [20, 20], [20, 110], [10, 110]]]
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
83
91
|
it "always removes the whole margin box from the frame" do
|
84
92
|
check_box(
|
85
93
|
{width: 50, height: 50, position: :absolute, position_hint: [10, 10],
|
@@ -238,6 +246,26 @@ describe HexaPDF::Layout::Frame do
|
|
238
246
|
box = HexaPDF::Layout::Box.create(width: 150, height: 50)
|
239
247
|
refute(@frame.draw(@canvas, box))
|
240
248
|
end
|
249
|
+
|
250
|
+
it "can't fit the box if there is no available space" do
|
251
|
+
@frame.remove_area(Geom2D::Polygon([0, 0], [110, 0], [110, 110], [0, 110]))
|
252
|
+
box = HexaPDF::Layout::Box.create
|
253
|
+
refute(@frame.fit(box))
|
254
|
+
end
|
255
|
+
|
256
|
+
it "draws the box even if the box's height is zero" do
|
257
|
+
box = HexaPDF::Layout::Box.create
|
258
|
+
box.define_singleton_method(:height) { 0 }
|
259
|
+
assert(@frame.draw(@canvas, box))
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
describe "split" do
|
264
|
+
it "splits the box if necessary" do
|
265
|
+
box = HexaPDF::Layout::Box.create(width: 10, height: 10)
|
266
|
+
assert_equal([nil, box], @frame.split(box))
|
267
|
+
assert_nil(@frame.instance_variable_get(:@fit_data).box)
|
268
|
+
end
|
241
269
|
end
|
242
270
|
|
243
271
|
describe "find_next_region" do
|
@@ -310,4 +338,13 @@ describe HexaPDF::Layout::Frame do
|
|
310
338
|
end
|
311
339
|
end
|
312
340
|
|
341
|
+
describe "remove_area" do
|
342
|
+
it "recalculates the contour line only if necessary" do
|
343
|
+
contour = Geom2D::Polygon([10, 10], [10, 90], [90, 90], [90, 10])
|
344
|
+
frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100, contour_line: contour)
|
345
|
+
frame.remove_area(Geom2D::Polygon([0, 0], [20, 0], [20, 100], [0, 100]))
|
346
|
+
assert_equal([[[20, 10], [90, 10], [90, 90], [20, 90]]],
|
347
|
+
frame.contour_line.polygons.map(&:to_a))
|
348
|
+
end
|
349
|
+
end
|
313
350
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require_relative '../content/common'
|
5
|
+
require 'hexapdf/document'
|
6
|
+
require 'hexapdf/layout/image_box'
|
7
|
+
|
8
|
+
describe HexaPDF::Layout::ImageBox do
|
9
|
+
before do
|
10
|
+
@image = HexaPDF::Stream.new({Subtype: :Image}, stream: '')
|
11
|
+
@image.define_singleton_method(:width) { 40 }
|
12
|
+
@image.define_singleton_method(:height) { 20 }
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_box(**kwargs)
|
16
|
+
HexaPDF::Layout::ImageBox.new(@image, kwargs)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "initialize" do
|
20
|
+
it "takes the image to be displayed" do
|
21
|
+
box = create_box
|
22
|
+
assert_equal(@image, box.image)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "fit" do
|
27
|
+
it "fits with fixed dimensions" do
|
28
|
+
box = create_box(width: 50, height: 30, style: {padding: [10, 4, 6, 2]})
|
29
|
+
assert(box.fit(100, 100, nil))
|
30
|
+
assert_equal(50, box.width)
|
31
|
+
assert_equal(30, box.height)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "fits with a fixed width" do
|
35
|
+
box = create_box(width: 60, style: {padding: [10, 4, 6, 2]})
|
36
|
+
assert(box.fit(100, 100, nil))
|
37
|
+
assert_equal(60, box.width)
|
38
|
+
assert_equal(43, box.height)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "fits with a fixed height" do
|
42
|
+
box = create_box(height: 40, style: {padding: [10, 4, 6, 2]})
|
43
|
+
assert(box.fit(100, 100, nil))
|
44
|
+
assert_equal(54, box.width)
|
45
|
+
assert_equal(40, box.height)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "fits with auto-scaling to available space" do
|
49
|
+
box = create_box(style: {padding: [10, 4, 6, 2]})
|
50
|
+
assert(box.fit(100, 100, nil))
|
51
|
+
assert_equal(100, box.width)
|
52
|
+
assert_equal(63, box.height)
|
53
|
+
|
54
|
+
assert(box.fit(100, 30, nil))
|
55
|
+
assert_equal(34, box.width)
|
56
|
+
assert_equal(30, box.height)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "draw" do
|
61
|
+
it "draws the image" do
|
62
|
+
box = create_box(height: 40, style: {padding: [10, 4, 6, 2]})
|
63
|
+
box.fit(100, 100, nil)
|
64
|
+
|
65
|
+
@canvas = HexaPDF::Document.new.pages.add.canvas
|
66
|
+
box.draw(@canvas, 0, 0)
|
67
|
+
assert_operators(@canvas.contents, [[:save_graphics_state],
|
68
|
+
[:concatenate_matrix, [48, 0, 0, 24, 2, 6]],
|
69
|
+
[:paint_xobject, [:XO1]],
|
70
|
+
[:restore_graphics_state]])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|