hexapdf 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|