hexapdf 0.26.2 → 0.28.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 +115 -1
- data/README.md +1 -1
- data/examples/013-text_layouter_shapes.rb +8 -8
- data/examples/016-frame_automatic_box_placement.rb +3 -3
- data/examples/017-frame_text_flow.rb +3 -3
- data/examples/019-acro_form.rb +14 -3
- data/examples/020-column_box.rb +3 -3
- data/examples/023-images.rb +30 -0
- data/lib/hexapdf/cli/info.rb +5 -1
- data/lib/hexapdf/cli/inspect.rb +2 -2
- data/lib/hexapdf/cli/split.rb +8 -8
- data/lib/hexapdf/cli/watermark.rb +2 -2
- data/lib/hexapdf/configuration.rb +3 -2
- data/lib/hexapdf/content/canvas.rb +8 -3
- data/lib/hexapdf/dictionary.rb +4 -17
- data/lib/hexapdf/document/destinations.rb +42 -5
- data/lib/hexapdf/document/signatures.rb +265 -48
- data/lib/hexapdf/document.rb +6 -10
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/importer.rb +35 -27
- data/lib/hexapdf/layout/list_box.rb +1 -5
- data/lib/hexapdf/object.rb +5 -0
- data/lib/hexapdf/parser.rb +14 -0
- data/lib/hexapdf/revision.rb +15 -12
- data/lib/hexapdf/revisions.rb +7 -1
- data/lib/hexapdf/tokenizer.rb +15 -9
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +174 -128
- data/lib/hexapdf/type/acro_form/button_field.rb +5 -3
- data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/field.rb +11 -5
- data/lib/hexapdf/type/acro_form/form.rb +61 -8
- data/lib/hexapdf/type/acro_form/signature_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/text_field.rb +12 -2
- data/lib/hexapdf/type/annotations/widget.rb +3 -0
- data/lib/hexapdf/type/catalog.rb +1 -1
- data/lib/hexapdf/type/font_true_type.rb +14 -0
- data/lib/hexapdf/type/object_stream.rb +2 -2
- data/lib/hexapdf/type/outline.rb +19 -1
- data/lib/hexapdf/type/outline_item.rb +72 -14
- data/lib/hexapdf/type/page.rb +95 -64
- data/lib/hexapdf/type/resources.rb +13 -17
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +16 -2
- data/lib/hexapdf/type/signature.rb +10 -0
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +5 -3
- data/test/hexapdf/content/test_canvas.rb +5 -0
- data/test/hexapdf/document/test_destinations.rb +41 -0
- data/test/hexapdf/document/test_pages.rb +2 -2
- data/test/hexapdf/document/test_signatures.rb +139 -19
- data/test/hexapdf/encryption/test_aes.rb +1 -1
- data/test/hexapdf/filter/test_predictor.rb +0 -1
- data/test/hexapdf/layout/test_box.rb +2 -1
- data/test/hexapdf/layout/test_column_box.rb +1 -1
- data/test/hexapdf/layout/test_list_box.rb +1 -1
- data/test/hexapdf/test_document.rb +2 -8
- data/test/hexapdf/test_importer.rb +27 -6
- data/test/hexapdf/test_parser.rb +19 -2
- data/test/hexapdf/test_revision.rb +15 -14
- data/test/hexapdf/test_revisions.rb +63 -12
- data/test/hexapdf/test_stream.rb +1 -1
- data/test/hexapdf/test_tokenizer.rb +10 -1
- data/test/hexapdf/test_writer.rb +11 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +135 -56
- data/test/hexapdf/type/acro_form/test_button_field.rb +6 -1
- data/test/hexapdf/type/acro_form/test_choice_field.rb +4 -0
- data/test/hexapdf/type/acro_form/test_field.rb +4 -4
- data/test/hexapdf/type/acro_form/test_form.rb +65 -0
- data/test/hexapdf/type/acro_form/test_signature_field.rb +4 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +13 -0
- data/test/hexapdf/type/signature/common.rb +54 -0
- data/test/hexapdf/type/signature/test_adbe_pkcs7_detached.rb +21 -0
- data/test/hexapdf/type/test_catalog.rb +5 -2
- data/test/hexapdf/type/test_font_true_type.rb +20 -0
- data/test/hexapdf/type/test_object_stream.rb +2 -1
- data/test/hexapdf/type/test_outline.rb +4 -1
- data/test/hexapdf/type/test_outline_item.rb +62 -1
- data/test/hexapdf/type/test_page.rb +103 -45
- data/test/hexapdf/type/test_page_tree_node.rb +4 -2
- data/test/hexapdf/type/test_resources.rb +0 -5
- data/test/hexapdf/type/test_signature.rb +8 -0
- data/test/test_helper.rb +1 -1
- metadata +61 -4
|
@@ -84,6 +84,8 @@ module HexaPDF
|
|
|
84
84
|
# See: PDF1.7 s12.7.4.2
|
|
85
85
|
class ButtonField < Field
|
|
86
86
|
|
|
87
|
+
define_type :XXAcroFormField
|
|
88
|
+
|
|
87
89
|
define_field :Opt, type: PDFArray, version: '1.4'
|
|
88
90
|
|
|
89
91
|
# All inheritable dictionary fields for button fields.
|
|
@@ -206,7 +208,7 @@ module HexaPDF
|
|
|
206
208
|
# Note that this will only return useful values if there is at least one correctly set-up
|
|
207
209
|
# widget.
|
|
208
210
|
def allowed_values
|
|
209
|
-
(each_widget.
|
|
211
|
+
(each_widget.with_object([]) do |widget, result|
|
|
210
212
|
keys = widget.appearance_dict&.normal_appearance&.value&.keys
|
|
211
213
|
result.concat(keys) if keys
|
|
212
214
|
end - [:Off]).uniq
|
|
@@ -254,14 +256,14 @@ module HexaPDF
|
|
|
254
256
|
normal_appearance = widget.appearance_dict&.normal_appearance
|
|
255
257
|
next if !force && normal_appearance &&
|
|
256
258
|
((!push_button? && normal_appearance.value.length == 2 &&
|
|
257
|
-
normal_appearance.
|
|
259
|
+
normal_appearance.each.all? {|_, v| v.kind_of?(HexaPDF::Stream) }) ||
|
|
258
260
|
(push_button? && normal_appearance.kind_of?(HexaPDF::Stream)))
|
|
259
261
|
if check_box?
|
|
260
262
|
appearance_generator_class.new(widget).create_check_box_appearances
|
|
261
263
|
elsif radio_button?
|
|
262
264
|
appearance_generator_class.new(widget).create_radio_button_appearances
|
|
263
265
|
else
|
|
264
|
-
|
|
266
|
+
appearance_generator_class.new(widget).create_push_button_appearances
|
|
265
267
|
end
|
|
266
268
|
end
|
|
267
269
|
end
|
|
@@ -242,8 +242,8 @@ module HexaPDF
|
|
|
242
242
|
end
|
|
243
243
|
|
|
244
244
|
# :call-seq:
|
|
245
|
-
# field.each_widget {|widget| block} -> field
|
|
246
|
-
# field.each_widget -> Enumerator
|
|
245
|
+
# field.each_widget(direct_only: true) {|widget| block} -> field
|
|
246
|
+
# field.each_widget(direct_only: true) -> Enumerator
|
|
247
247
|
#
|
|
248
248
|
# Yields each widget, i.e. visual representation, of this field.
|
|
249
249
|
#
|
|
@@ -253,11 +253,17 @@ module HexaPDF
|
|
|
253
253
|
# 2. One or more widgets are defined as children of this field.
|
|
254
254
|
# 3. Widgets of *another field instance with the same full field name*.
|
|
255
255
|
#
|
|
256
|
-
#
|
|
257
|
-
#
|
|
256
|
+
# With the default of +direct_only+ being +true+, only the usual cases 1 and 2 are handled/
|
|
257
|
+
# If case 3 also needs to be handled, set +direct_only+ to +false+ or run the validation on
|
|
258
|
+
# the main AcroForm object (HexaPDF::Document#acro_form) before using this method (this will
|
|
259
|
+
# reduce case 3 to case 2).
|
|
260
|
+
#
|
|
261
|
+
# *Note*: Setting +direct_only+ to +false+ will have a severe performance impact since all
|
|
262
|
+
# fields of the form have to be searched to check whether there is another field with the
|
|
263
|
+
# same full field name.
|
|
258
264
|
#
|
|
259
265
|
# See: HexaPDF::Type::Annotations::Widget
|
|
260
|
-
def each_widget(direct_only:
|
|
266
|
+
def each_widget(direct_only: true, &block) # :yields: widget
|
|
261
267
|
return to_enum(__method__, direct_only: direct_only) unless block_given?
|
|
262
268
|
|
|
263
269
|
if embedded_widget?
|
|
@@ -125,14 +125,22 @@ module HexaPDF
|
|
|
125
125
|
def each_field(terminal_only: true)
|
|
126
126
|
return to_enum(__method__, terminal_only: terminal_only) unless block_given?
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
128
|
+
process_field_array = lambda do |array|
|
|
129
|
+
array.each_with_index do |field, index|
|
|
130
|
+
unless field.respond_to?(:type) && field.type == :XXAcroFormField
|
|
131
|
+
array[index] = field = document.wrap(field, type: :XXAcroFormField,
|
|
132
|
+
subtype: Field.inherited_value(field, :FT))
|
|
133
|
+
end
|
|
134
|
+
if field.terminal_field?
|
|
135
|
+
yield(field)
|
|
136
|
+
else
|
|
137
|
+
yield(field) unless terminal_only
|
|
138
|
+
process_field_array.call(field[:Kids])
|
|
139
|
+
end
|
|
140
|
+
end
|
|
133
141
|
end
|
|
134
142
|
|
|
135
|
-
|
|
143
|
+
process_field_array.call(root_fields)
|
|
136
144
|
self
|
|
137
145
|
end
|
|
138
146
|
|
|
@@ -393,9 +401,10 @@ module HexaPDF
|
|
|
393
401
|
fields.each {|field| field.create_appearances if field.respond_to?(:create_appearances) }
|
|
394
402
|
end
|
|
395
403
|
|
|
396
|
-
not_flattened = fields.map {|field| field.each_widget.to_a }.flatten
|
|
404
|
+
not_flattened = fields.map {|field| field.each_widget(direct_only: true).to_a }.flatten
|
|
397
405
|
document.pages.each {|page| not_flattened = page.flatten_annotations(not_flattened) }
|
|
398
|
-
|
|
406
|
+
not_flattened.map!(&:form_field)
|
|
407
|
+
fields -= not_flattened
|
|
399
408
|
|
|
400
409
|
fields.each do |field|
|
|
401
410
|
(field[:Parent]&.[](:Kids) || self[:Fields]).delete(field)
|
|
@@ -448,6 +457,50 @@ module HexaPDF
|
|
|
448
457
|
def perform_validation # :nodoc:
|
|
449
458
|
super
|
|
450
459
|
|
|
460
|
+
seen = {} # used for combining field
|
|
461
|
+
|
|
462
|
+
validate_array = lambda do |parent, container|
|
|
463
|
+
container.reject! do |field|
|
|
464
|
+
if !field.kind_of?(HexaPDF::Object) || !field.kind_of?(HexaPDF::Dictionary) || field.null?
|
|
465
|
+
yield("Invalid object in AcroForm field hierarchy", true)
|
|
466
|
+
next true
|
|
467
|
+
end
|
|
468
|
+
next false unless field.key?(:T) # Skip widgets
|
|
469
|
+
|
|
470
|
+
field = document.wrap(field, type: :XXAcroFormField,
|
|
471
|
+
subtype: Field.inherited_value(field, :FT))
|
|
472
|
+
reject = false
|
|
473
|
+
if field[:Parent] != parent
|
|
474
|
+
yield("Parent entry of field (#{field.oid},#{field.gen}) invalid", true)
|
|
475
|
+
if field[:Parent].nil?
|
|
476
|
+
root_fields << field
|
|
477
|
+
reject = true
|
|
478
|
+
else
|
|
479
|
+
field[:Parent] = parent
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# Combine fields with same name
|
|
484
|
+
name = field.full_field_name
|
|
485
|
+
if (other_field = seen[name])
|
|
486
|
+
kids = other_field[:Kids] ||= []
|
|
487
|
+
kids << other_field.send(:extract_widget) if other_field.embedded_widget?
|
|
488
|
+
widgets = field.embedded_widget? ? [field.send(:extract_widget)] : field.each_widget.to_a
|
|
489
|
+
widgets.each do |widget|
|
|
490
|
+
widget[:Parent] = other_field
|
|
491
|
+
kids << widget
|
|
492
|
+
end
|
|
493
|
+
reject = true
|
|
494
|
+
elsif !reject
|
|
495
|
+
seen[name] = field
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
validate_array.call(field, field[:Kids]) if field.key?(:Kids)
|
|
499
|
+
reject
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
validate_array.call(nil, root_fields)
|
|
503
|
+
|
|
451
504
|
if (da = self[:DA])
|
|
452
505
|
unless self[:DR]
|
|
453
506
|
yield("When the field /DA is present, the field /DR must also be present")
|
|
@@ -73,6 +73,8 @@ module HexaPDF
|
|
|
73
73
|
# See: PDF1.7 s12.7.4.3
|
|
74
74
|
class TextField < VariableTextField
|
|
75
75
|
|
|
76
|
+
define_type :XXAcroFormField
|
|
77
|
+
|
|
76
78
|
define_field :MaxLen, type: Integer
|
|
77
79
|
|
|
78
80
|
# All inheritable dictionary fields for text fields.
|
|
@@ -220,7 +222,15 @@ module HexaPDF
|
|
|
220
222
|
current_value = field_value
|
|
221
223
|
appearance_generator_class = document.config.constantize('acro_form.appearance_generator')
|
|
222
224
|
each_widget do |widget|
|
|
223
|
-
|
|
225
|
+
is_cached = widget.cached?(:last_value)
|
|
226
|
+
unless force
|
|
227
|
+
if is_cached && widget.cache(:last_value) == current_value
|
|
228
|
+
next
|
|
229
|
+
elsif !is_cached && widget.appearance?
|
|
230
|
+
widget.cache(:last_value, current_value, update: true)
|
|
231
|
+
next
|
|
232
|
+
end
|
|
233
|
+
end
|
|
224
234
|
widget.cache(:last_value, current_value, update: true)
|
|
225
235
|
appearance_generator_class.new(widget).create_text_appearances
|
|
226
236
|
end
|
|
@@ -228,7 +238,7 @@ module HexaPDF
|
|
|
228
238
|
|
|
229
239
|
# Updates the widgets so that they reflect the current field value.
|
|
230
240
|
def update_widgets
|
|
231
|
-
create_appearances
|
|
241
|
+
create_appearances(force: true)
|
|
232
242
|
end
|
|
233
243
|
|
|
234
244
|
private
|
|
@@ -224,6 +224,9 @@ module HexaPDF
|
|
|
224
224
|
# The kind of marker that is shown inside the widget. Can either be one of the symbols
|
|
225
225
|
# +:check+, +:circle+, +:cross+, +:diamond+, +:square+ or +:star+, or a one character
|
|
226
226
|
# string. The latter is interpreted using the ZapfDingbats font.
|
|
227
|
+
#
|
|
228
|
+
# If an empty string is set, it is treated as if +nil+ was set, i.e. it shows the default
|
|
229
|
+
# marker for the field type.
|
|
227
230
|
attr_reader :style
|
|
228
231
|
|
|
229
232
|
# The size of the marker in PDF points that is shown inside the widget. The special value
|
data/lib/hexapdf/type/catalog.rb
CHANGED
|
@@ -65,7 +65,7 @@ module HexaPDF
|
|
|
65
65
|
:TwoPageLeft, :TwoPageRight]
|
|
66
66
|
define_field :PageMode, type: Symbol, default: :UseNone,
|
|
67
67
|
allowed_values: [:UseNone, :UseOutlines, :UseThumbs, :FullScreen, :UseOC, :UseAttachments]
|
|
68
|
-
define_field :Outlines, type:
|
|
68
|
+
define_field :Outlines, type: :Outlines, indirect: true
|
|
69
69
|
define_field :Threads, type: PDFArray, version: '1.1'
|
|
70
70
|
define_field :OpenAction, type: [Dictionary, PDFArray], version: '1.1'
|
|
71
71
|
define_field :AA, type: Dictionary, version: '1.4'
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
#++
|
|
36
36
|
|
|
37
37
|
require 'hexapdf/type/font_simple'
|
|
38
|
+
require 'hexapdf/font/true_type_wrapper'
|
|
38
39
|
|
|
39
40
|
module HexaPDF
|
|
40
41
|
module Type
|
|
@@ -45,6 +46,19 @@ module HexaPDF
|
|
|
45
46
|
define_field :Subtype, type: Symbol, required: true, default: :TrueType
|
|
46
47
|
define_field :BaseFont, type: Symbol, required: true
|
|
47
48
|
|
|
49
|
+
# Overrides the default to provide a font wrapper in case none is set and a complete TrueType
|
|
50
|
+
# is embedded.
|
|
51
|
+
#
|
|
52
|
+
# See: Font#font_wrapper
|
|
53
|
+
def font_wrapper
|
|
54
|
+
if (tmp = super)
|
|
55
|
+
tmp
|
|
56
|
+
elsif (font_file = self.font_file) && self[:BaseFont].to_s !~ /\A[A-Z]{6}\+/
|
|
57
|
+
font = HexaPDF::Font::TrueType::Font.new(StringIO.new(font_file.stream))
|
|
58
|
+
@font_wrapper = HexaPDF::Font::TrueTypeWrapper.new(document, font, subset: true)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
48
62
|
private
|
|
49
63
|
|
|
50
64
|
def perform_validation
|
|
@@ -179,7 +179,7 @@ module HexaPDF
|
|
|
179
179
|
# Due to a bug in Adobe Acrobat, the Catalog may not be in an object stream if the
|
|
180
180
|
# document is encrypted
|
|
181
181
|
if obj.nil? || obj.null? || obj.gen != 0 || obj.kind_of?(Stream) || obj == encrypt_dict ||
|
|
182
|
-
|
|
182
|
+
obj.type == :Catalog ||
|
|
183
183
|
obj.type == :Sig || obj.type == :DocTimeStamp ||
|
|
184
184
|
(obj.respond_to?(:key?) && obj.key?(:ByteRange) && obj.key?(:Contents))
|
|
185
185
|
delete_object(objects[index])
|
|
@@ -220,7 +220,7 @@ module HexaPDF
|
|
|
220
220
|
|
|
221
221
|
# Returns the container with the to-be-stored objects.
|
|
222
222
|
def objects
|
|
223
|
-
@objects ||=
|
|
223
|
+
@objects ||=
|
|
224
224
|
begin
|
|
225
225
|
@objects = {}
|
|
226
226
|
parse_stream
|
data/lib/hexapdf/type/outline.rb
CHANGED
|
@@ -50,6 +50,8 @@ module HexaPDF
|
|
|
50
50
|
# The outline dictionary is linked via the /Outlines entry from the Type::Catalog and can
|
|
51
51
|
# directly be accessed via HexaPDF::Document#outline.
|
|
52
52
|
#
|
|
53
|
+
# == Examples
|
|
54
|
+
#
|
|
53
55
|
# Here is an example for creating an outline:
|
|
54
56
|
#
|
|
55
57
|
# doc = HexaPDF::Document.new
|
|
@@ -62,6 +64,22 @@ module HexaPDF
|
|
|
62
64
|
# end
|
|
63
65
|
# end
|
|
64
66
|
#
|
|
67
|
+
# Here is one for copying the complete outline from one PDF to another:
|
|
68
|
+
#
|
|
69
|
+
# doc = HexaPDF::Document.open(ARGV[0])
|
|
70
|
+
# target = HexaPDF::Document.new
|
|
71
|
+
# stack = [target.outline]
|
|
72
|
+
# doc.outline.each_item do |item, level|
|
|
73
|
+
# if stack.size < level
|
|
74
|
+
# stack << stack.last[:Last]
|
|
75
|
+
# elsif stack.size > level
|
|
76
|
+
# (stack.size - level).times { stack.pop }
|
|
77
|
+
# end
|
|
78
|
+
# stack.last.add_item(target.import(item))
|
|
79
|
+
# end
|
|
80
|
+
# # Copying all the pages so that the references work.
|
|
81
|
+
# doc.pages.each {|page| target.pages << target.import(page) }
|
|
82
|
+
#
|
|
65
83
|
# See: PDF1.7 s12.3.3
|
|
66
84
|
class Outline < Dictionary
|
|
67
85
|
|
|
@@ -110,7 +128,7 @@ module HexaPDF
|
|
|
110
128
|
node, dir = first ? [first, :Next] : [last, :Prev]
|
|
111
129
|
node = node[dir] while node.key?(dir)
|
|
112
130
|
self[dir == :Next ? :Last : :First] = node
|
|
113
|
-
elsif !first && !last && self[:Count]
|
|
131
|
+
elsif !first && !last && self[:Count] && self[:Count] != 0
|
|
114
132
|
yield('Outline dictionary key /Count set but no items exist', true)
|
|
115
133
|
delete(:Count)
|
|
116
134
|
end
|
|
@@ -126,6 +126,11 @@ module HexaPDF
|
|
|
126
126
|
lister: "flags", getter: "flagged?", setter: "flag", unsetter: "unflag",
|
|
127
127
|
value_getter: "self[:F]", value_setter: "self[:F]")
|
|
128
128
|
|
|
129
|
+
# Returns +true+ since outline items must always be indirect objects.
|
|
130
|
+
def must_be_indirect?
|
|
131
|
+
true
|
|
132
|
+
end
|
|
133
|
+
|
|
129
134
|
# :call-seq:
|
|
130
135
|
# item.title -> title
|
|
131
136
|
# item.title(value) -> title
|
|
@@ -198,9 +203,46 @@ module HexaPDF
|
|
|
198
203
|
end
|
|
199
204
|
end
|
|
200
205
|
|
|
206
|
+
# Returns the outline level this item is one.
|
|
207
|
+
#
|
|
208
|
+
# The level of the items in the main outline dictionary, the root level, is 1.
|
|
209
|
+
#
|
|
210
|
+
# Here is an illustrated example of items contained in a document outline with their
|
|
211
|
+
# associated level:
|
|
212
|
+
#
|
|
213
|
+
# Outline dictionary 0
|
|
214
|
+
# Outline item 1 1
|
|
215
|
+
# |- Sub item 1 2
|
|
216
|
+
# |- Sub item 2 2
|
|
217
|
+
# |- Sub sub item 1 3
|
|
218
|
+
# |- Sub item 3 2
|
|
219
|
+
# Outline item 2 1
|
|
220
|
+
def level
|
|
221
|
+
count = 0
|
|
222
|
+
temp = self
|
|
223
|
+
count += 1 while (temp = temp[:Parent])
|
|
224
|
+
count
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Returns the destination page if there is any.
|
|
228
|
+
#
|
|
229
|
+
# * If a destination is set, the associated page is returned.
|
|
230
|
+
# * If an action is set and it is a GoTo action, the associated page is returned.
|
|
231
|
+
# * Otherwise +nil+ is returned.
|
|
232
|
+
def destination_page
|
|
233
|
+
dest = self[:Dest]
|
|
234
|
+
dest = action[:D] if !dest && (action = self[:A]) && action[:S] == :GoTo
|
|
235
|
+
document.destinations.resolve(dest)&.page
|
|
236
|
+
end
|
|
237
|
+
|
|
201
238
|
# Adds, as child to this item, a new outline item with the given title that performs the
|
|
202
239
|
# provided action on clicking. Returns the newly added item.
|
|
203
240
|
#
|
|
241
|
+
# Alternatively, it is possible to provide an already initialized outline item instead of the
|
|
242
|
+
# title. If so, the only other argument that is used is +position+. Existing fields /Prev,
|
|
243
|
+
# /Next, /First, /Last, /Parent and /Count are deleted from the given item and set
|
|
244
|
+
# appropriately.
|
|
245
|
+
#
|
|
204
246
|
# If neither :destination nor :action is specified, the outline item has no associated action.
|
|
205
247
|
# This is only meaningful if the new item will have children as it then acts just as a
|
|
206
248
|
# container.
|
|
@@ -251,16 +293,30 @@ module HexaPDF
|
|
|
251
293
|
# end
|
|
252
294
|
def add_item(title, destination: nil, action: nil, position: :last, open: true,
|
|
253
295
|
text_color: nil, flags: nil) # :yield: item
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
item.
|
|
296
|
+
if title.kind_of?(HexaPDF::Object) && title.type == :XXOutlineItem
|
|
297
|
+
item = title
|
|
298
|
+
item.delete(:Prev)
|
|
299
|
+
item.delete(:Next)
|
|
300
|
+
item.delete(:First)
|
|
301
|
+
item.delete(:Last)
|
|
302
|
+
if item[:Count] && item[:Count] >= 0
|
|
303
|
+
item[:Count] = 0
|
|
304
|
+
else
|
|
305
|
+
item.delete(:Count)
|
|
306
|
+
end
|
|
307
|
+
item[:Parent] = self
|
|
258
308
|
else
|
|
259
|
-
item.
|
|
309
|
+
item = document.add({Parent: self}, type: :XXOutlineItem)
|
|
310
|
+
item.title(title)
|
|
311
|
+
if action
|
|
312
|
+
item.action(action)
|
|
313
|
+
else
|
|
314
|
+
item.destination(destination)
|
|
315
|
+
end
|
|
316
|
+
item.text_color(text_color) if text_color
|
|
317
|
+
item.flag(*flags) if flags
|
|
318
|
+
item[:Count] = 0 if open # Count=0 means open if items are later added
|
|
260
319
|
end
|
|
261
|
-
item.text_color(text_color) if text_color
|
|
262
|
-
item.flag(*flags) if flags
|
|
263
|
-
item[:Count] = 0 if open # Count=0 means open if items are later added
|
|
264
320
|
|
|
265
321
|
unless position == :last || position == :first || position.kind_of?(Integer)
|
|
266
322
|
raise ArgumentError, "position must be :first, :last, or an integer"
|
|
@@ -311,18 +367,20 @@ module HexaPDF
|
|
|
311
367
|
end
|
|
312
368
|
|
|
313
369
|
# :call-seq:
|
|
314
|
-
# item.each_item {|descendant_item| block } -> item
|
|
315
|
-
# item.each_item
|
|
370
|
+
# item.each_item {|descendant_item, level| block } -> item
|
|
371
|
+
# item.each_item -> Enumerator
|
|
316
372
|
#
|
|
317
373
|
# Iterates over all descendant items of this one.
|
|
318
374
|
#
|
|
319
|
-
# The items are yielded in-order, yielding first the item itself and then its
|
|
375
|
+
# The descendant items are yielded in-order, yielding first the item itself and then its
|
|
376
|
+
# descendants.
|
|
320
377
|
def each_item(&block)
|
|
321
378
|
return to_enum(__method__) unless block_given?
|
|
379
|
+
return self unless (item = self[:First])
|
|
322
380
|
|
|
323
|
-
|
|
381
|
+
level = self.level + 1
|
|
324
382
|
while item
|
|
325
|
-
yield(item)
|
|
383
|
+
yield(item, level)
|
|
326
384
|
item.each_item(&block)
|
|
327
385
|
item = item[:Next]
|
|
328
386
|
end
|
|
@@ -341,7 +399,7 @@ module HexaPDF
|
|
|
341
399
|
node, dir = first ? [first, :Next] : [last, :Prev]
|
|
342
400
|
node = node[dir] while node.key?(dir)
|
|
343
401
|
self[dir == :Next ? :Last : :First] = node
|
|
344
|
-
elsif !first && !last && self[:Count] != 0
|
|
402
|
+
elsif !first && !last && self[:Count] && self[:Count] != 0
|
|
345
403
|
yield('Outline item dictionary key /Count set but no descendants exist', true)
|
|
346
404
|
delete(:Count)
|
|
347
405
|
end
|