hexapdf 0.33.0 → 0.34.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 +42 -1
- data/examples/026-optional_content.rb +55 -0
- data/examples/027-composer_optional_content.rb +83 -0
- data/lib/hexapdf/cli/command.rb +7 -1
- data/lib/hexapdf/cli/fonts.rb +1 -1
- data/lib/hexapdf/cli/inspect.rb +2 -4
- data/lib/hexapdf/composer.rb +2 -1
- data/lib/hexapdf/configuration.rb +21 -1
- data/lib/hexapdf/content/canvas.rb +52 -0
- data/lib/hexapdf/content/operator.rb +2 -0
- data/lib/hexapdf/dictionary.rb +1 -0
- data/lib/hexapdf/dictionary_fields.rb +1 -2
- data/lib/hexapdf/digital_signature/verification_result.rb +1 -2
- data/lib/hexapdf/document/layout.rb +3 -0
- data/lib/hexapdf/document/pages.rb +1 -1
- data/lib/hexapdf/document.rb +7 -0
- data/lib/hexapdf/encryption/ruby_aes.rb +10 -20
- data/lib/hexapdf/layout/box.rb +23 -3
- data/lib/hexapdf/layout/column_box.rb +2 -1
- data/lib/hexapdf/layout/frame.rb +23 -6
- data/lib/hexapdf/layout/inline_box.rb +20 -9
- data/lib/hexapdf/layout/list_box.rb +34 -20
- data/lib/hexapdf/layout/page_style.rb +2 -1
- data/lib/hexapdf/layout/style.rb +46 -6
- data/lib/hexapdf/layout/table_box.rb +9 -7
- data/lib/hexapdf/layout/text_box.rb +9 -2
- data/lib/hexapdf/layout/text_fragment.rb +28 -2
- data/lib/hexapdf/layout/text_layouter.rb +21 -5
- data/lib/hexapdf/stream.rb +1 -2
- data/lib/hexapdf/type/actions/set_ocg_state.rb +86 -0
- data/lib/hexapdf/type/actions.rb +1 -0
- data/lib/hexapdf/type/annotations/text.rb +1 -2
- data/lib/hexapdf/type/catalog.rb +10 -1
- data/lib/hexapdf/type/cid_font.rb +15 -1
- data/lib/hexapdf/type/form.rb +75 -5
- data/lib/hexapdf/type/optional_content_configuration.rb +170 -0
- data/lib/hexapdf/type/optional_content_group.rb +370 -0
- data/lib/hexapdf/type/optional_content_membership.rb +63 -0
- data/lib/hexapdf/type/optional_content_properties.rb +158 -0
- data/lib/hexapdf/type/page.rb +27 -11
- data/lib/hexapdf/type/page_label.rb +4 -8
- data/lib/hexapdf/type.rb +4 -0
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +0 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_canvas.rb +49 -0
- data/test/hexapdf/document/test_layout.rb +7 -2
- data/test/hexapdf/document/test_pages.rb +6 -6
- data/test/hexapdf/layout/test_box.rb +13 -4
- data/test/hexapdf/layout/test_frame.rb +13 -1
- data/test/hexapdf/layout/test_inline_box.rb +17 -8
- data/test/hexapdf/layout/test_list_box.rb +48 -31
- data/test/hexapdf/layout/test_style.rb +10 -0
- data/test/hexapdf/layout/test_table_box.rb +32 -26
- data/test/hexapdf/layout/test_text_box.rb +8 -0
- data/test/hexapdf/layout/test_text_fragment.rb +33 -0
- data/test/hexapdf/layout/test_text_layouter.rb +32 -5
- data/test/hexapdf/test_composer.rb +10 -0
- data/test/hexapdf/test_dictionary.rb +10 -0
- data/test/hexapdf/test_document.rb +4 -0
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/actions/test_set_ocg_state.rb +40 -0
- data/test/hexapdf/type/test_catalog.rb +11 -0
- data/test/hexapdf/type/test_form.rb +119 -0
- data/test/hexapdf/type/test_optional_content_configuration.rb +112 -0
- data/test/hexapdf/type/test_optional_content_group.rb +158 -0
- data/test/hexapdf/type/test_optional_content_properties.rb +109 -0
- data/test/hexapdf/type/test_page.rb +2 -2
- metadata +14 -3
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# -*- encoding: utf-8; frozen_string_literal: true -*-
|
|
2
|
+
#
|
|
3
|
+
#--
|
|
4
|
+
# This file is part of HexaPDF.
|
|
5
|
+
#
|
|
6
|
+
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
+
# Copyright (C) 2014-2023 Thomas Leitner
|
|
8
|
+
#
|
|
9
|
+
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
|
+
# under the terms of the GNU Affero General Public License version 3 as
|
|
11
|
+
# published by the Free Software Foundation with the addition of the
|
|
12
|
+
# following permission added to Section 15 as permitted in Section 7(a):
|
|
13
|
+
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
|
|
14
|
+
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
|
|
15
|
+
# INFRINGEMENT OF THIRD PARTY RIGHTS.
|
|
16
|
+
#
|
|
17
|
+
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
|
|
18
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
20
|
+
# License for more details.
|
|
21
|
+
#
|
|
22
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
23
|
+
# along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
|
|
24
|
+
#
|
|
25
|
+
# The interactive user interfaces in modified source and object code
|
|
26
|
+
# versions of HexaPDF must display Appropriate Legal Notices, as required
|
|
27
|
+
# under Section 5 of the GNU Affero General Public License version 3.
|
|
28
|
+
#
|
|
29
|
+
# In accordance with Section 7(b) of the GNU Affero General Public
|
|
30
|
+
# License, a covered work must retain the producer line in every PDF that
|
|
31
|
+
# is created or manipulated using HexaPDF.
|
|
32
|
+
#
|
|
33
|
+
# If the GNU Affero General Public License doesn't fit your need,
|
|
34
|
+
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
|
35
|
+
#++
|
|
36
|
+
|
|
37
|
+
require 'hexapdf/type/action'
|
|
38
|
+
|
|
39
|
+
module HexaPDF
|
|
40
|
+
module Type
|
|
41
|
+
module Actions
|
|
42
|
+
|
|
43
|
+
# A Set-OCG-state action changes the state of one or more optional content groups.
|
|
44
|
+
#
|
|
45
|
+
# See: PDF2.0 s12.6.4.13, HexaPDF::Type::OptionalContentGroup
|
|
46
|
+
class SetOCGState < Action
|
|
47
|
+
|
|
48
|
+
define_field :S, type: Symbol, required: true, default: :SetOCGState
|
|
49
|
+
define_field :State, type: PDFArray, required: true, default: []
|
|
50
|
+
define_field :PreserveRB, type: Boolean, default: true
|
|
51
|
+
|
|
52
|
+
STATE_TYPE_MAPPING = {on: :ON, ON: :ON, off: :OFF, OFF: :OFF, # :nodoc:
|
|
53
|
+
toggle: :Toggle, Toggle: :Toggle}
|
|
54
|
+
|
|
55
|
+
# Adds a state changing sequence to the /State array.
|
|
56
|
+
#
|
|
57
|
+
# The +type+ argument specifies how the state of the given optional content groups should be
|
|
58
|
+
# changed.
|
|
59
|
+
#
|
|
60
|
+
# +type+:: The type of sequence to add, either :on/:ON (for turning the OCGs on) , :off/:OFF
|
|
61
|
+
# (for turning the OCGs off), or :toggle/:Toggle (for toggling the state of the
|
|
62
|
+
# OCGs).
|
|
63
|
+
#
|
|
64
|
+
# +ocgs+:: A single optional content group or an array of optional content groups to which
|
|
65
|
+
# the state change defined with +type+ should be applied. The OCGs can be specified
|
|
66
|
+
# via their dictionary or by name which uses the first found OCG with that name.
|
|
67
|
+
def add_state_change(type, ocgs)
|
|
68
|
+
type = STATE_TYPE_MAPPING.fetch(type) do
|
|
69
|
+
raise ArgumentError, "Invalid type #{type} specified, should be one of :on, :off or :toggle"
|
|
70
|
+
end
|
|
71
|
+
state = self[:State]
|
|
72
|
+
state << type
|
|
73
|
+
Array(ocgs).each do |ocg|
|
|
74
|
+
if (ocg_name = ocg).kind_of?(String)
|
|
75
|
+
ocg = document.optional_content.ocg(ocg_name, create: false)
|
|
76
|
+
raise HexaPDF::Error, "Invalid OCG named '#{ocg_name}' specified" unless ocg
|
|
77
|
+
end
|
|
78
|
+
state << ocg
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
data/lib/hexapdf/type/actions.rb
CHANGED
|
@@ -55,8 +55,7 @@ module HexaPDF
|
|
|
55
55
|
|
|
56
56
|
private
|
|
57
57
|
|
|
58
|
-
# :nodoc:
|
|
59
|
-
STATE_TO_STATE_MODEL = {
|
|
58
|
+
STATE_TO_STATE_MODEL = { # :nodoc:
|
|
60
59
|
"Marked" => "Marked", "Unmarked" => "Marked",
|
|
61
60
|
"Accepted" => "Review", "Rejected" => "Review", "Cancelled" => "Review",
|
|
62
61
|
"Completed" => "Review", "None" => "Review"
|
data/lib/hexapdf/type/catalog.rb
CHANGED
|
@@ -78,7 +78,7 @@ module HexaPDF
|
|
|
78
78
|
define_field :SpiderInfo, type: Dictionary, version: '1.3'
|
|
79
79
|
define_field :OutputIntents, type: PDFArray, version: '1.4'
|
|
80
80
|
define_field :PieceInfo, type: Dictionary, version: '1.4'
|
|
81
|
-
define_field :OCProperties, type:
|
|
81
|
+
define_field :OCProperties, type: :XXOCProperties, version: '1.5'
|
|
82
82
|
define_field :Perms, type: Dictionary, version: '1.5'
|
|
83
83
|
define_field :Legal, type: Dictionary, version: '1.5'
|
|
84
84
|
define_field :Requirements, type: PDFArray, version: '1.7'
|
|
@@ -112,6 +112,15 @@ module HexaPDF
|
|
|
112
112
|
self[:Outlines] ||= document.add({}, type: :Outlines)
|
|
113
113
|
end
|
|
114
114
|
|
|
115
|
+
# Returns the optional content properties dictionary, creating it if needed.
|
|
116
|
+
#
|
|
117
|
+
# This is the main entry point for working with optional content, a.k.a. layers.
|
|
118
|
+
#
|
|
119
|
+
# See: OptionalContentProperties
|
|
120
|
+
def optional_content
|
|
121
|
+
self[:OCProperties] ||= document.add({OCGs: [], D: {Creator: 'HexaPDF'}}, type: :XXOCProperties)
|
|
122
|
+
end
|
|
123
|
+
|
|
115
124
|
# Returns the main AcroForm object.
|
|
116
125
|
#
|
|
117
126
|
# * If an AcroForm object exists, the +create+ argument is not used.
|
|
@@ -45,10 +45,24 @@ module HexaPDF
|
|
|
45
45
|
# See: PDF2.0 s9.7.4
|
|
46
46
|
class CIDFont < Font
|
|
47
47
|
|
|
48
|
+
# Describes the CIDSystemInfo dictionary specifying the character collection assumed by the
|
|
49
|
+
# CIDFont.
|
|
50
|
+
#
|
|
51
|
+
# See: PDF2.0 s9.7.3
|
|
52
|
+
class CIDSystemInfo < Dictionary
|
|
53
|
+
|
|
54
|
+
define_type :XXCIDSystemInfo
|
|
55
|
+
|
|
56
|
+
define_field :Registry, type: String, required: true
|
|
57
|
+
define_field :Ordering, type: String, required: true
|
|
58
|
+
define_field :Supplement, type: Integer, required: true
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
|
|
48
62
|
DEFAULT_WIDTH = 1000 # :nodoc:
|
|
49
63
|
|
|
50
64
|
define_field :BaseFont, type: Symbol, required: true
|
|
51
|
-
define_field :CIDSystemInfo, type:
|
|
65
|
+
define_field :CIDSystemInfo, type: :XXCIDSystemInfo, required: true
|
|
52
66
|
define_field :FontDescriptor, type: :FontDescriptor, indirect: true, required: true
|
|
53
67
|
define_field :DW, type: Integer, default: DEFAULT_WIDTH
|
|
54
68
|
define_field :W, type: PDFArray
|
data/lib/hexapdf/type/form.rb
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
|
35
35
|
#++
|
|
36
36
|
|
|
37
|
+
require 'stringio'
|
|
37
38
|
require 'hexapdf/stream'
|
|
38
39
|
require 'hexapdf/content'
|
|
39
40
|
|
|
@@ -45,6 +46,32 @@ module HexaPDF
|
|
|
45
46
|
# See: PDF2.0 s8.10
|
|
46
47
|
class Form < Stream
|
|
47
48
|
|
|
49
|
+
# Represents a group attribute dictionary.
|
|
50
|
+
#
|
|
51
|
+
# See: PDF2.0 s8.10.3
|
|
52
|
+
class Group < Dictionary
|
|
53
|
+
|
|
54
|
+
define_type :Group
|
|
55
|
+
|
|
56
|
+
define_field :Type, type: Symbol, default: type
|
|
57
|
+
define_field :S, type: Symbol, required: true
|
|
58
|
+
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Represents a reference dictionary which allows an XObject to refer to content in an embedded
|
|
62
|
+
# or linked PDF document.
|
|
63
|
+
#
|
|
64
|
+
# See: PDF2.0 s8.10.4
|
|
65
|
+
class Reference < Dictionary
|
|
66
|
+
|
|
67
|
+
define_type :XXReference
|
|
68
|
+
|
|
69
|
+
define_field :F, type: :Filespec, required: true
|
|
70
|
+
define_field :Page, type: [Integer, String], required: true
|
|
71
|
+
define_field :ID, type: PDFArray
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
|
|
48
75
|
define_type :XObject
|
|
49
76
|
|
|
50
77
|
define_field :Type, type: Symbol, default: type
|
|
@@ -53,8 +80,8 @@ module HexaPDF
|
|
|
53
80
|
define_field :BBox, type: Rectangle, required: true
|
|
54
81
|
define_field :Matrix, type: PDFArray, default: [1, 0, 0, 1, 0, 0]
|
|
55
82
|
define_field :Resources, type: :XXResources, version: '1.2'
|
|
56
|
-
define_field :Group, type:
|
|
57
|
-
define_field :Ref, type:
|
|
83
|
+
define_field :Group, type: :Group, version: '1.4'
|
|
84
|
+
define_field :Ref, type: :XXReference, version: '1.4'
|
|
58
85
|
define_field :Metadata, type: Stream, version: '1.4'
|
|
59
86
|
define_field :PieceInfo, type: Dictionary, version: '1.3'
|
|
60
87
|
define_field :LastModified, type: PDFDate, version: '1.3'
|
|
@@ -115,14 +142,15 @@ module HexaPDF
|
|
|
115
142
|
#
|
|
116
143
|
# See: HexaPDF::Content::Processor
|
|
117
144
|
def process_contents(processor, original_resources: nil)
|
|
118
|
-
|
|
119
|
-
|
|
145
|
+
form = referenced_content || self
|
|
146
|
+
processor.resources = if form[:Resources]
|
|
147
|
+
form[:Resources]
|
|
120
148
|
elsif original_resources
|
|
121
149
|
original_resources
|
|
122
150
|
else
|
|
123
151
|
document.wrap({}, type: :XXResources)
|
|
124
152
|
end
|
|
125
|
-
Content::Parser.parse(contents, processor)
|
|
153
|
+
Content::Parser.parse(form.contents, processor)
|
|
126
154
|
end
|
|
127
155
|
|
|
128
156
|
# Returns the canvas for the form XObject.
|
|
@@ -152,6 +180,48 @@ module HexaPDF
|
|
|
152
180
|
end
|
|
153
181
|
end
|
|
154
182
|
|
|
183
|
+
# Returns +true+ if the Form XObject is a reference XObject.
|
|
184
|
+
def reference_xobject?
|
|
185
|
+
!self[:Ref].nil?
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Returns the referenced page as Form XObject, if this Form XObject is a Reference XObject and
|
|
189
|
+
# the referenced page is found. Otherwise returns +nil+.
|
|
190
|
+
def referenced_content
|
|
191
|
+
return unless (ref = self[:Ref])
|
|
192
|
+
|
|
193
|
+
doc = if ref[:F].embedded_file?
|
|
194
|
+
HexaPDF::Document.new(io: StringIO.new(ref[:F].embedded_file_stream.stream))
|
|
195
|
+
elsif File.exist?(ref[:F].path)
|
|
196
|
+
HexaPDF::Document.open(ref[:F].path)
|
|
197
|
+
end
|
|
198
|
+
return unless doc
|
|
199
|
+
|
|
200
|
+
page = ref[:Page]
|
|
201
|
+
if page.kind_of?(Integer)
|
|
202
|
+
page = doc.pages[page]
|
|
203
|
+
else
|
|
204
|
+
labels = []
|
|
205
|
+
doc.pages.each_labelling_range do |first_index, count, label|
|
|
206
|
+
count.times {|i| labels << label.construct_label(i) }
|
|
207
|
+
end
|
|
208
|
+
index = labels.index(page)
|
|
209
|
+
page = index && doc.pages[index]
|
|
210
|
+
end
|
|
211
|
+
return unless page
|
|
212
|
+
|
|
213
|
+
# See PDF2.0 s8.10.4.3
|
|
214
|
+
print_annots = page.each_annotation.select {|annot| annot.flagged?(:print) }
|
|
215
|
+
page.flatten_annotations(print_annots) unless print_annots.empty?
|
|
216
|
+
|
|
217
|
+
obj = page.to_form_xobject
|
|
218
|
+
obj[:BBox] = self[:BBox].dup
|
|
219
|
+
obj[:Matrix] = self[:Matrix].dup
|
|
220
|
+
obj
|
|
221
|
+
rescue
|
|
222
|
+
nil
|
|
223
|
+
end
|
|
224
|
+
|
|
155
225
|
end
|
|
156
226
|
|
|
157
227
|
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# -*- encoding: utf-8; frozen_string_literal: true -*-
|
|
2
|
+
#
|
|
3
|
+
#--
|
|
4
|
+
# This file is part of HexaPDF.
|
|
5
|
+
#
|
|
6
|
+
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
+
# Copyright (C) 2014-2023 Thomas Leitner
|
|
8
|
+
#
|
|
9
|
+
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
|
+
# under the terms of the GNU Affero General Public License version 3 as
|
|
11
|
+
# published by the Free Software Foundation with the addition of the
|
|
12
|
+
# following permission added to Section 15 as permitted in Section 7(a):
|
|
13
|
+
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
|
|
14
|
+
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
|
|
15
|
+
# INFRINGEMENT OF THIRD PARTY RIGHTS.
|
|
16
|
+
#
|
|
17
|
+
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
|
|
18
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
20
|
+
# License for more details.
|
|
21
|
+
#
|
|
22
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
23
|
+
# along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
|
|
24
|
+
#
|
|
25
|
+
# The interactive user interfaces in modified source and object code
|
|
26
|
+
# versions of HexaPDF must display Appropriate Legal Notices, as required
|
|
27
|
+
# under Section 5 of the GNU Affero General Public License version 3.
|
|
28
|
+
#
|
|
29
|
+
# In accordance with Section 7(b) of the GNU Affero General Public
|
|
30
|
+
# License, a covered work must retain the producer line in every PDF that
|
|
31
|
+
# is created or manipulated using HexaPDF.
|
|
32
|
+
#
|
|
33
|
+
# If the GNU Affero General Public License doesn't fit your need,
|
|
34
|
+
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
|
35
|
+
#++
|
|
36
|
+
|
|
37
|
+
require 'hexapdf/dictionary'
|
|
38
|
+
|
|
39
|
+
module HexaPDF
|
|
40
|
+
module Type
|
|
41
|
+
|
|
42
|
+
# Represents an optional content configuration dictionary.
|
|
43
|
+
#
|
|
44
|
+
# This dictionary is used for the /D and /Configs entries in the optional content properties
|
|
45
|
+
# dictionary. It configures the states of the OCGs as well as defines how those states may be
|
|
46
|
+
# changed by a PDF processor.
|
|
47
|
+
#
|
|
48
|
+
# See: PDF2.0 s8.11.4.3
|
|
49
|
+
class OptionalContentConfiguration < Dictionary
|
|
50
|
+
|
|
51
|
+
# Represents an optional content usage application dictionary.
|
|
52
|
+
#
|
|
53
|
+
# This dictionary is used for the elements in the /AS array of an optional content
|
|
54
|
+
# configuration dictionary. It specifies how a PDF processor should use the usage entries of
|
|
55
|
+
# OCGs to automatically change their state based on external factors (like magnifacation
|
|
56
|
+
# factor or language).
|
|
57
|
+
#
|
|
58
|
+
# See: PDF2.0 s8.11.4.4
|
|
59
|
+
class UsageApplication < Dictionary
|
|
60
|
+
define_type :XXOCUsageApplication
|
|
61
|
+
define_field :Event, type: Symbol, required: true, allowed_values: [:View, :Print, :Export]
|
|
62
|
+
define_field :OCGs, type: PDFArray, default: []
|
|
63
|
+
define_field :Category, type: PDFArray, required: true
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
define_type :XXOCConfiguration
|
|
67
|
+
|
|
68
|
+
define_field :Name, type: String
|
|
69
|
+
define_field :Creator, type: String
|
|
70
|
+
define_field :BaseState, type: Symbol, default: :ON, allowed_values: [:ON, :OFF, :Unchanged]
|
|
71
|
+
define_field :ON, type: PDFArray
|
|
72
|
+
define_field :OFF, type: PDFArray
|
|
73
|
+
define_field :Intent, type: [Symbol, PDFArray], default: :View
|
|
74
|
+
define_field :AS, type: PDFArray
|
|
75
|
+
define_field :Order, type: PDFArray
|
|
76
|
+
define_field :ListMode, type: Symbol, default: :AllPages,
|
|
77
|
+
allowed_values: [:AllPages, :VisiblePages]
|
|
78
|
+
define_field :RBGroups, type: PDFArray
|
|
79
|
+
define_field :Locked, type: PDFArray, default: []
|
|
80
|
+
|
|
81
|
+
# :call-seq:
|
|
82
|
+
# configuration.ocg_state(ocg) -> state
|
|
83
|
+
# configuration.ocg_state(ocg, state) -> state
|
|
84
|
+
#
|
|
85
|
+
# Returns the state (+:on+, +:off+ or +nil+) of the optional content group if the +state+
|
|
86
|
+
# argument is not given. Otherwise sets the state of the OCG to the given state value
|
|
87
|
+
# (+:on+/+:ON+ or +:off+/+:OFF+).
|
|
88
|
+
#
|
|
89
|
+
# The value +nil+ is only returned if the state is not defined by the configuration dictionary
|
|
90
|
+
# (which may only be the case if the configuration dictionary is not the default configuration
|
|
91
|
+
# dictionary).
|
|
92
|
+
def ocg_state(ocg, state = nil)
|
|
93
|
+
if state.nil?
|
|
94
|
+
case self[:BaseState]
|
|
95
|
+
when :ON then self[:OFF]&.include?(ocg) ? :off : :on
|
|
96
|
+
when :OFF then self[:ON]&.include?(ocg) ? :on : :off
|
|
97
|
+
else self[:OFF]&.include?(ocg) ? :off : (self[:ON]&.include?(ocg) ? :on : nil)
|
|
98
|
+
end
|
|
99
|
+
elsif state&.downcase == :on
|
|
100
|
+
(self[:ON] ||= []) << ocg unless self[:ON]&.include?(ocg)
|
|
101
|
+
self[:OFF].delete(ocg) if key?(:OFF)
|
|
102
|
+
elsif state&.downcase == :off
|
|
103
|
+
(self[:OFF] ||= []) << ocg unless self[:OFF]&.include?(ocg)
|
|
104
|
+
self[:ON].delete(ocg) if key?(:ON)
|
|
105
|
+
else
|
|
106
|
+
raise ArgumentError, "Invalid value #{state.inspect} for state argument"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Returns +true+ if the given optional content group is on.
|
|
111
|
+
def ocg_on?(ocg)
|
|
112
|
+
ocg_state(ocg) == :on
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Makes the given optional content group visible in an interactive PDF processor's user
|
|
116
|
+
# interface.
|
|
117
|
+
#
|
|
118
|
+
# The OCG is always added to the end of the specified +path+ or, if +path+ is not specified,
|
|
119
|
+
# the top level.
|
|
120
|
+
#
|
|
121
|
+
# The optional argument +path+ specifies the strings or OCGs under which the given OCG should
|
|
122
|
+
# hierarchically be nested. A string is used as a non-selectable label, an OCG reflects an
|
|
123
|
+
# actual nesting of the involved OCGs.
|
|
124
|
+
#
|
|
125
|
+
# Examples:
|
|
126
|
+
#
|
|
127
|
+
# configuration.add_ocg_to_ui(ocg) # Add the OCG as top-level item
|
|
128
|
+
# configuration.add_ocg_to_ui(ocg, path: 'Debug') # Add the OCG under the label 'Debug'
|
|
129
|
+
# # Add the OCG under the label 'Page1' which is under the label 'Debug'
|
|
130
|
+
# configuration.add_ocg_to_ui(ocg, path: ['Debug', 'Page1'])
|
|
131
|
+
# configuration.add_ocg_to_ui(ocg, path: other_ocg) # Add the OCG under the other OCG
|
|
132
|
+
def add_ocg_to_ui(ocg, path: nil)
|
|
133
|
+
array = self[:Order] ||= []
|
|
134
|
+
path = Array(path)
|
|
135
|
+
until path.empty?
|
|
136
|
+
item = path.shift
|
|
137
|
+
index = array.index do |entry|
|
|
138
|
+
if (entry.kind_of?(Array) || entry.kind_of?(PDFArray)) && item.kind_of?(String)
|
|
139
|
+
entry.first == item
|
|
140
|
+
else
|
|
141
|
+
entry == item
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
if item.kind_of?(String)
|
|
146
|
+
unless index
|
|
147
|
+
array << [item]
|
|
148
|
+
index = -1
|
|
149
|
+
end
|
|
150
|
+
array = array[index]
|
|
151
|
+
else
|
|
152
|
+
unless index
|
|
153
|
+
array << item << []
|
|
154
|
+
index = -2
|
|
155
|
+
end
|
|
156
|
+
if array[index + 1].kind_of?(Array) || array[index + 1].kind_of?(PDFArray)
|
|
157
|
+
array = array[index + 1]
|
|
158
|
+
else
|
|
159
|
+
array.insert(index + 1, [])
|
|
160
|
+
array = array[index + 1]
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
array << ocg
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
end
|
|
170
|
+
end
|