hexapdf 0.22.0 → 0.23.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 +50 -0
- data/lib/hexapdf/cli/form.rb +26 -3
- data/lib/hexapdf/cli/inspect.rb +12 -3
- data/lib/hexapdf/cli/modify.rb +23 -3
- data/lib/hexapdf/composer.rb +24 -2
- data/lib/hexapdf/document/destinations.rb +396 -0
- data/lib/hexapdf/document.rb +38 -89
- data/lib/hexapdf/layout/frame.rb +8 -9
- data/lib/hexapdf/layout/style.rb +280 -7
- data/lib/hexapdf/layout/text_box.rb +10 -2
- data/lib/hexapdf/layout/text_layouter.rb +6 -1
- data/lib/hexapdf/revision.rb +8 -1
- data/lib/hexapdf/revisions.rb +151 -50
- data/lib/hexapdf/task/optimize.rb +21 -11
- data/lib/hexapdf/type/acro_form/text_field.rb +8 -0
- data/lib/hexapdf/type/catalog.rb +9 -1
- data/lib/hexapdf/type/names.rb +13 -0
- data/lib/hexapdf/type/xref_stream.rb +2 -1
- data/lib/hexapdf/utils/sorted_tree_node.rb +3 -1
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +15 -2
- data/test/hexapdf/document/test_destinations.rb +338 -0
- data/test/hexapdf/encryption/test_security_handler.rb +2 -2
- data/test/hexapdf/layout/test_frame.rb +15 -1
- data/test/hexapdf/layout/test_text_box.rb +16 -0
- data/test/hexapdf/layout/test_text_layouter.rb +7 -0
- data/test/hexapdf/task/test_optimize.rb +17 -4
- data/test/hexapdf/test_composer.rb +24 -1
- data/test/hexapdf/test_document.rb +30 -133
- data/test/hexapdf/test_parser.rb +1 -1
- data/test/hexapdf/test_revision.rb +14 -0
- data/test/hexapdf/test_revisions.rb +137 -29
- data/test/hexapdf/test_writer.rb +43 -14
- data/test/hexapdf/type/acro_form/test_text_field.rb +17 -0
- data/test/hexapdf/type/test_catalog.rb +8 -0
- data/test/hexapdf/type/test_names.rb +20 -0
- data/test/hexapdf/type/test_xref_stream.rb +2 -1
- data/test/hexapdf/utils/test_sorted_tree_node.rb +11 -1
- metadata +5 -2
data/lib/hexapdf/document.rb
CHANGED
@@ -106,6 +106,7 @@ module HexaPDF
|
|
106
106
|
autoload(:Images, 'hexapdf/document/images')
|
107
107
|
autoload(:Files, 'hexapdf/document/files')
|
108
108
|
autoload(:Signatures, 'hexapdf/document/signatures')
|
109
|
+
autoload(:Destinations, 'hexapdf/document/destinations')
|
109
110
|
|
110
111
|
# :call-seq:
|
111
112
|
# Document.open(filename, **docargs) -> doc
|
@@ -184,22 +185,9 @@ module HexaPDF
|
|
184
185
|
# For references to unknown objects, +nil+ is returned but free objects are represented by a
|
185
186
|
# PDF Null object, not by +nil+!
|
186
187
|
#
|
187
|
-
# See:
|
188
|
+
# See: Revisions#object
|
188
189
|
def object(ref)
|
189
|
-
|
190
|
-
while i >= 0
|
191
|
-
return @revisions[i].object(ref) if @revisions[i].object?(ref)
|
192
|
-
i -= 1
|
193
|
-
end
|
194
|
-
nil
|
195
|
-
end
|
196
|
-
|
197
|
-
# Dereferences the given object.
|
198
|
-
#
|
199
|
-
# Return the object itself if it is not a reference, or the indirect object specified by the
|
200
|
-
# reference.
|
201
|
-
def deref(obj)
|
202
|
-
obj.kind_of?(Reference) ? object(obj) : obj
|
190
|
+
@revisions.object(ref)
|
203
191
|
end
|
204
192
|
|
205
193
|
# :call-seq:
|
@@ -212,74 +200,51 @@ module HexaPDF
|
|
212
200
|
# Even though this method might return +true+ for some references, #object may return +nil+
|
213
201
|
# because this method takes *all* revisions into account. Also see the discussion on #each for
|
214
202
|
# more information.
|
203
|
+
#
|
204
|
+
# See: Revisions#object?
|
215
205
|
def object?(ref)
|
216
|
-
@revisions.
|
206
|
+
@revisions.object?(ref)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Dereferences the given object.
|
210
|
+
#
|
211
|
+
# Return the object itself if it is not a reference, or the indirect object specified by the
|
212
|
+
# reference.
|
213
|
+
def deref(obj)
|
214
|
+
obj.kind_of?(Reference) ? object(obj) : obj
|
217
215
|
end
|
218
216
|
|
219
217
|
# :call-seq:
|
220
|
-
# doc.add(obj,
|
218
|
+
# doc.add(obj, **wrap_opts) -> indirect_object
|
221
219
|
#
|
222
|
-
# Adds the object to the
|
223
|
-
# object.
|
220
|
+
# Adds the object to the document and returns the wrapped indirect object.
|
224
221
|
#
|
225
222
|
# The object can either be a native Ruby object (Hash, Array, Integer, ...) or a
|
226
223
|
# HexaPDF::Object. If it is not the latter, #wrap is called with the object and the
|
227
224
|
# additional keyword arguments.
|
228
225
|
#
|
229
|
-
#
|
230
|
-
|
231
|
-
def add(obj, revision: :current, **wrap_opts)
|
226
|
+
# See: Revisions#add_object
|
227
|
+
def add(obj, **wrap_opts)
|
232
228
|
obj = wrap(obj, **wrap_opts) unless obj.kind_of?(HexaPDF::Object)
|
233
229
|
|
234
|
-
revision = (revision == :current ? @revisions.current : @revisions.revision(revision))
|
235
|
-
if revision.nil?
|
236
|
-
raise ArgumentError, "Invalid revision index specified"
|
237
|
-
end
|
238
|
-
|
239
230
|
if obj.document? && obj.document != self
|
240
231
|
raise HexaPDF::Error, "Can't add object that is already attached to another document"
|
241
232
|
end
|
242
233
|
obj.document = self
|
243
234
|
|
244
|
-
|
245
|
-
if rev_obj.equal?(obj)
|
246
|
-
return obj
|
247
|
-
else
|
248
|
-
raise HexaPDF::Error, "Can't add object because the specified revision already has " \
|
249
|
-
"an object with object number #{obj.oid}"
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
obj.oid = @revisions.map(&:next_free_oid).max unless obj.indirect?
|
254
|
-
|
255
|
-
revision.add(obj)
|
235
|
+
@revisions.add_object(obj)
|
256
236
|
end
|
257
237
|
|
258
238
|
# :call-seq:
|
259
|
-
# doc.delete(ref
|
260
|
-
# doc.delete(oid
|
239
|
+
# doc.delete(ref)
|
240
|
+
# doc.delete(oid)
|
261
241
|
#
|
262
242
|
# Deletes the indirect object specified by an exact reference or by an object number from the
|
263
243
|
# document.
|
264
244
|
#
|
265
|
-
#
|
266
|
-
|
267
|
-
|
268
|
-
#
|
269
|
-
# :all:: Delete the object from all revisions.
|
270
|
-
# :current:: Delete the object only from the current revision.
|
271
|
-
#
|
272
|
-
# mark_as_free:: If +true+, objects are only marked as free objects instead of being actually
|
273
|
-
# deleted.
|
274
|
-
def delete(ref, revision: :all, mark_as_free: true)
|
275
|
-
case revision
|
276
|
-
when :current
|
277
|
-
@revisions.current.delete(ref, mark_as_free: mark_as_free)
|
278
|
-
when :all
|
279
|
-
@revisions.each {|rev| rev.delete(ref, mark_as_free: mark_as_free) }
|
280
|
-
else
|
281
|
-
raise ArgumentError, "Unsupported option revision: #{revision}"
|
282
|
-
end
|
245
|
+
# See: Revisions#delete_object
|
246
|
+
def delete(ref)
|
247
|
+
@revisions.delete_object(ref)
|
283
248
|
end
|
284
249
|
|
285
250
|
# :call-seq:
|
@@ -414,42 +379,20 @@ module HexaPDF
|
|
414
379
|
end
|
415
380
|
|
416
381
|
# :call-seq:
|
417
|
-
# doc.each(only_current: true, only_loaded: false) {|obj| block }
|
418
|
-
# doc.each(only_current: true, only_loaded: false) {|obj, rev| block }
|
382
|
+
# doc.each(only_current: true, only_loaded: false) {|obj| block }
|
383
|
+
# doc.each(only_current: true, only_loaded: false) {|obj, rev| block }
|
419
384
|
# doc.each(only_current: true, only_loaded: false) -> Enumerator
|
420
385
|
#
|
421
|
-
#
|
422
|
-
# object in the PDF document. The block may either accept only the object or the object and the
|
423
|
-
# revision it is in.
|
424
|
-
#
|
425
|
-
# By default, only the current version of each object is returned which implies that each object
|
426
|
-
# number is yielded exactly once. If the +only_current+ option is +false+, all stored objects
|
427
|
-
# from newest to oldest are returned, not only the current version of each object.
|
386
|
+
# Yields every object and the revision it is in.
|
428
387
|
#
|
429
|
-
#
|
430
|
-
# revisions
|
388
|
+
# If +only_current+ is +true+, only the current version of each object is yielded, otherwise
|
389
|
+
# all objects from all revisions.
|
431
390
|
#
|
432
|
-
#
|
433
|
-
# two (different) objects with oid/gen [3,0].
|
391
|
+
# If +only_loaded+ is +true+, only the already loaded objects are yielded.
|
434
392
|
#
|
435
|
-
#
|
436
|
-
# generation numbers in different revisions, e.g. one object with oid/gen [3,0] and one with
|
437
|
-
# oid/gen [3,1].
|
393
|
+
# For details see Revisions#each_object
|
438
394
|
def each(only_current: true, only_loaded: false, &block)
|
439
|
-
|
440
|
-
return to_enum(__method__, only_current: only_current, only_loaded: only_loaded)
|
441
|
-
end
|
442
|
-
|
443
|
-
yield_rev = (block.arity == 2)
|
444
|
-
oids = {}
|
445
|
-
@revisions.reverse_each do |rev|
|
446
|
-
rev.each(only_loaded: only_loaded) do |obj|
|
447
|
-
next if only_current && oids.include?(obj.oid)
|
448
|
-
(yield_rev ? yield(obj, rev) : yield(obj))
|
449
|
-
oids[obj.oid] = true
|
450
|
-
end
|
451
|
-
end
|
452
|
-
self
|
395
|
+
@revisions.each_object(only_current: only_current, only_loaded: only_loaded, &block)
|
453
396
|
end
|
454
397
|
|
455
398
|
# :call-seq:
|
@@ -529,6 +472,12 @@ module HexaPDF
|
|
529
472
|
@fonts ||= Fonts.new(self)
|
530
473
|
end
|
531
474
|
|
475
|
+
# Returns the Destinations object that provides convenience methods for working with destination
|
476
|
+
# objects.
|
477
|
+
def destinations
|
478
|
+
@destinations ||= Destinations.new(self)
|
479
|
+
end
|
480
|
+
|
532
481
|
# Returns the main AcroForm object for dealing with interactive forms.
|
533
482
|
#
|
534
483
|
# See HexaPDF::Type::Catalog#acro_form for details on the arguments.
|
data/lib/hexapdf/layout/frame.rb
CHANGED
@@ -252,14 +252,6 @@ module HexaPDF
|
|
252
252
|
else
|
253
253
|
create_rectangle(x, y, x + width, y + height)
|
254
254
|
end
|
255
|
-
when :float
|
256
|
-
x = @x + @fit_data.margin_left
|
257
|
-
x += @fit_data.available_width - width if box.style.position_hint == :right
|
258
|
-
y = @y - height - @fit_data.margin_top
|
259
|
-
# We use the real margins from the box because they either have the desired effect or just
|
260
|
-
# extend the rectangle outside the frame.
|
261
|
-
rectangle = create_rectangle(x - (margin&.left || 0), y - (margin&.bottom || 0),
|
262
|
-
x + width + (margin&.right || 0), @y)
|
263
255
|
when :flow
|
264
256
|
x = 0
|
265
257
|
y = @y - height
|
@@ -280,7 +272,14 @@ module HexaPDF
|
|
280
272
|
@x + @fit_data.margin_left
|
281
273
|
end
|
282
274
|
y = @y - height - @fit_data.margin_top
|
283
|
-
rectangle =
|
275
|
+
rectangle = if box.style.position == :float
|
276
|
+
# We use the real margins from the box because they either have the desired
|
277
|
+
# effect or just extend the rectangle outside the frame.
|
278
|
+
create_rectangle(x - (margin&.left || 0), y - (margin&.bottom || 0),
|
279
|
+
x + width + (margin&.right || 0), @y)
|
280
|
+
else
|
281
|
+
create_rectangle(left, y - (margin&.bottom || 0), left + self.width, @y)
|
282
|
+
end
|
284
283
|
end
|
285
284
|
|
286
285
|
box.draw(canvas, x, y)
|