hexapdf 0.26.2 → 0.28.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 +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
|