hexapdf 0.22.0 → 0.23.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 +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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d6f11a38da2472389966c1d103b86e46ba8cd858d2927b1a65f441f56e2e7dd5
|
4
|
+
data.tar.gz: 74ff92dcb6ede9f137303afc7c8b9d08e0baa42b0ab0b7d4436777e6db617ea4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 468e0a5a24e06574b4a802408daafbdb59b3a1dc460ad2b6bf9f543088d1210d8295b843847819a872436ea4c380df85240822e28b548dd88b55f74b30ff8ab3
|
7
|
+
data.tar.gz: 29e95def1e7ccf0d55bca3cf5ad4d95a070d5910a0e1b8645d2884e0e5022367c63e14e2df3ecc0bac7cdfc329d88698c01060dc8005e1c1025a0c8337367afc
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,53 @@
|
|
1
|
+
## 0.23.0 - 2022-05-26
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
- [HexaPDF::Composer#create_stamp] for creating a form Xobject
|
6
|
+
- [HexaPDF::Revision#reset_objects] for deleting all live loaded and added
|
7
|
+
objects
|
8
|
+
- Support for removing or flattening annotations to the `hexapdf modify` command
|
9
|
+
- Option to CLI command `hexapdf form` to allow generation of a template file
|
10
|
+
- Support for centering a floating box in [HexaPDF::Layout::Frame]
|
11
|
+
- [HexaPDF::Type::Catalog#names] for easier access to the name dictionary
|
12
|
+
- [HexaPDF::Type::Names#destinations] for easier access to the destinations name
|
13
|
+
tree
|
14
|
+
- [HexaPDF::Document::Destinations], accessible via
|
15
|
+
[HexaPDF::Document#destinations], as convenience interface for working with
|
16
|
+
destination arrays
|
17
|
+
|
18
|
+
### Changed
|
19
|
+
|
20
|
+
- **Breaking change**: Refactored the [HexaPDF::Document] interface for working
|
21
|
+
with objects and move parts into [HexaPDF::Revisions]
|
22
|
+
- **Breaking change**: [HexaPDF::Layout::TextBox] to use whole available width
|
23
|
+
when aligning to the center or right
|
24
|
+
- **Breaking change**: [HexaPDF::Layout::TextBox] to use whole available height
|
25
|
+
when vertically aligning to the center or bottom
|
26
|
+
- CLI command `hexapdf inspect` to show the type of revisions, as well as the
|
27
|
+
number of objects per revision
|
28
|
+
- [HexaPDF::Task::Optimize] to allow skipping invalid content stream operations
|
29
|
+
- [HexaPDF::Composer#image] to allow using a form xobject in place of the image
|
30
|
+
|
31
|
+
### Fixed
|
32
|
+
|
33
|
+
- [HexaPDF::Writer#write] to write modified objects into the correct revision
|
34
|
+
- [HexaPDF::Revisions::from_io] to correctly handle hybrid-reference files
|
35
|
+
- [HexaPDF::Writer] to assign a valid object number to a created cross-reference
|
36
|
+
stream in all cases
|
37
|
+
* [HexaPDF::Type::AcroForm::TextField] to validate the existence of a /MaxLen
|
38
|
+
value for comb text fields
|
39
|
+
* [HexaPDF::Type::AcroForm::TextField#field_value=] to check for the existence
|
40
|
+
of /MaxLen when setting a value for a comb text field
|
41
|
+
* [HexaPDF::Type::AcroForm::TextField#field_value=] to check the value against
|
42
|
+
/MaxLen
|
43
|
+
* [HexaPDF::Layout::TextLayouter#fit] to not use style valign when doing
|
44
|
+
variable width layouting
|
45
|
+
* [HexaPDF::Utils::SortedTreeNode#find_entry] to work in case of a node without
|
46
|
+
a container name or kids key
|
47
|
+
* CLI command `hexapdf form` to allow setting array values when using a template
|
48
|
+
* CLI command `hexapdf form` to allow setting file select fields
|
49
|
+
|
50
|
+
|
1
51
|
## 0.22.0 - 2022-03-26
|
2
52
|
|
3
53
|
### Added
|
data/lib/hexapdf/cli/form.rb
CHANGED
@@ -70,6 +70,9 @@ module HexaPDF
|
|
70
70
|
@template = template
|
71
71
|
@fill = true
|
72
72
|
end
|
73
|
+
options.on('--generate-template', 'Print a template for use with --template') do
|
74
|
+
@generate_template = true
|
75
|
+
end
|
73
76
|
options.on('--flatten', 'Flatten the form fields') do
|
74
77
|
@flatten = true
|
75
78
|
end
|
@@ -85,6 +88,7 @@ module HexaPDF
|
|
85
88
|
@password = nil
|
86
89
|
@fill = false
|
87
90
|
@flatten = false
|
91
|
+
@generate_template = false
|
88
92
|
@template = nil
|
89
93
|
@need_appearances = nil
|
90
94
|
@incremental = true
|
@@ -117,6 +121,15 @@ module HexaPDF
|
|
117
121
|
doc.catalog.delete(:AcroForm)
|
118
122
|
doc.delete(doc.acro_form)
|
119
123
|
end
|
124
|
+
elsif @generate_template
|
125
|
+
unsupported_fields = [:signature_field, :password_field]
|
126
|
+
each_field(doc) do |_, _, field, _|
|
127
|
+
next if unsupported_fields.include?(field.concrete_field_type)
|
128
|
+
name = field.full_field_name.gsub(':', "\\:")
|
129
|
+
Array(field.field_value).each do |val|
|
130
|
+
puts "#{name}: #{val.to_s.gsub(/(\r|\r\n|\n)/, '\1 ')}"
|
131
|
+
end
|
132
|
+
end
|
120
133
|
else
|
121
134
|
list_form_fields(doc)
|
122
135
|
end
|
@@ -220,8 +233,16 @@ module HexaPDF
|
|
220
233
|
field_name = scanner.scan(/(\\:|[^:])*?:/)
|
221
234
|
break unless field_name
|
222
235
|
field_name.gsub!(/\\:/, ':')
|
236
|
+
field_name.chop!
|
223
237
|
field_value = scanner.scan(/.*?(?=^\S|\z)/m)
|
224
|
-
|
238
|
+
next unless field_value
|
239
|
+
field_value = field_value.strip.gsub(/^\s*/, '')
|
240
|
+
if data.key?(field_name)
|
241
|
+
data[field_name] = [data[field_name]] unless data[field_name].kind_of?(Array)
|
242
|
+
data[field_name] << field_value
|
243
|
+
else
|
244
|
+
data[field_name] = field_value
|
245
|
+
end
|
225
246
|
end
|
226
247
|
if !scanner.eos? && command_parser.verbosity_warning?
|
227
248
|
$stderr.puts "Warning: Some template could not be parsed"
|
@@ -232,8 +253,8 @@ module HexaPDF
|
|
232
253
|
# Applies the given value to the field.
|
233
254
|
def apply_field_value(field, value)
|
234
255
|
case field.concrete_field_type
|
235
|
-
when :single_line_text_field, :multiline_text_field, :comb_text_field, :
|
236
|
-
:list_box, :editable_combo_box
|
256
|
+
when :single_line_text_field, :multiline_text_field, :comb_text_field, :file_select_field,
|
257
|
+
:combo_box, :list_box, :editable_combo_box
|
237
258
|
field.field_value = value
|
238
259
|
when :check_box
|
239
260
|
field.field_value = case value
|
@@ -249,6 +270,8 @@ module HexaPDF
|
|
249
270
|
else
|
250
271
|
raise "Field type #{field.concrete_field_type} not yet supported"
|
251
272
|
end
|
273
|
+
rescue
|
274
|
+
raise "Error while setting '#{field.full_field_name}': #{$!.message}"
|
252
275
|
end
|
253
276
|
|
254
277
|
# Iterates over all non-push button fields in page order. If a field appears on multiple
|
data/lib/hexapdf/cli/inspect.rb
CHANGED
@@ -229,10 +229,19 @@ module HexaPDF
|
|
229
229
|
end
|
230
230
|
IO.copy_stream(@doc.revisions.parser.io, $stdout, length, 0)
|
231
231
|
else
|
232
|
-
puts "Document has #{@doc.revisions.
|
233
|
-
revision_information do |
|
232
|
+
puts "Document has #{@doc.revisions.count} revision#{@doc.revisions.count == 1 ? '' : 's'}"
|
233
|
+
revision_information do |rev, index, count, signature, end_offset|
|
234
|
+
type = if rev.trailer[:XRefStm]
|
235
|
+
"xref table + stream"
|
236
|
+
elsif rev.trailer[:Type] == :XRef
|
237
|
+
"xref stream"
|
238
|
+
else
|
239
|
+
"xref table"
|
240
|
+
end
|
234
241
|
puts "Revision #{index + 1}"
|
242
|
+
puts " Type : #{type}"
|
235
243
|
puts " Objects : #{count}"
|
244
|
+
puts " Size : #{rev.trailer[:Size]}"
|
236
245
|
puts " Signed : yes" if signature
|
237
246
|
puts " Byte range: 0-#{end_offset}"
|
238
247
|
end
|
@@ -352,7 +361,7 @@ module HexaPDF
|
|
352
361
|
buffer = buffer[-20..-1]
|
353
362
|
end
|
354
363
|
end
|
355
|
-
yield(rev, index, rev.
|
364
|
+
yield(rev, index, rev.each.count, sig, end_index)
|
356
365
|
end
|
357
366
|
end
|
358
367
|
|
data/lib/hexapdf/cli/modify.rb
CHANGED
@@ -53,14 +53,15 @@ module HexaPDF
|
|
53
53
|
super('modify', takes_commands: false)
|
54
54
|
short_desc("Modify a PDF file")
|
55
55
|
long_desc(<<~EOF)
|
56
|
-
This command modifies a PDF file. It can be used to select pages that should
|
57
|
-
the output file and/or rotate them. The output file can also be
|
58
|
-
optimized in various ways.
|
56
|
+
This command modifies a PDF file. It can be used, for example, to select pages that should
|
57
|
+
appear in the output file and/or rotate them. The output file can also be
|
58
|
+
encrypted/decrypted and optimized in various ways.
|
59
59
|
EOF
|
60
60
|
|
61
61
|
@password = nil
|
62
62
|
@pages = '1-e'
|
63
63
|
@embed_files = []
|
64
|
+
@annotation_mode = nil
|
64
65
|
|
65
66
|
options.on("--password PASSWORD", "-p", String,
|
66
67
|
"The password for decryption. Use - for reading from standard input.") do |pwd|
|
@@ -74,6 +75,10 @@ module HexaPDF
|
|
74
75
|
"used multiple times)") do |file|
|
75
76
|
@embed_files << file
|
76
77
|
end
|
78
|
+
options.on("--annotations MODE", [:remove, :flatten], "Handling of annotations (either " \
|
79
|
+
"remove or flatten)") do |mode|
|
80
|
+
@annotation_mode = mode
|
81
|
+
end
|
77
82
|
define_optimization_options
|
78
83
|
define_encryption_options
|
79
84
|
end
|
@@ -82,6 +87,7 @@ module HexaPDF
|
|
82
87
|
maybe_raise_on_existing_file(out_file)
|
83
88
|
with_document(in_file, password: @password, out_file: out_file) do |doc|
|
84
89
|
arrange_pages(doc) unless @pages == '1-e'
|
90
|
+
handle_annotations(doc)
|
85
91
|
@embed_files.each {|file| doc.files.add(file, embed: true) }
|
86
92
|
apply_encryption_options(doc)
|
87
93
|
apply_optimization_options(doc)
|
@@ -109,6 +115,20 @@ module HexaPDF
|
|
109
115
|
doc.pages.add unless doc.pages.count > 0
|
110
116
|
end
|
111
117
|
|
118
|
+
# Handles the annotations of all selected pages by doing nothing, removing them or flattening
|
119
|
+
# them.
|
120
|
+
def handle_annotations(doc)
|
121
|
+
return unless @annotation_mode
|
122
|
+
|
123
|
+
doc.pages.each do |page|
|
124
|
+
if @annotation_mode == :remove
|
125
|
+
page.delete(:Annots)
|
126
|
+
else
|
127
|
+
page.flatten_annotations
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
112
132
|
end
|
113
133
|
|
114
134
|
end
|
data/lib/hexapdf/composer.rb
CHANGED
@@ -313,7 +313,8 @@ module HexaPDF
|
|
313
313
|
|
314
314
|
# Draws the given image at the current position.
|
315
315
|
#
|
316
|
-
# The +file+ argument can be anything that is accepted by HexaPDF::Document::Images#add
|
316
|
+
# The +file+ argument can be anything that is accepted by HexaPDF::Document::Images#add or a
|
317
|
+
# HexaPDF::Type::Form object.
|
317
318
|
#
|
318
319
|
# See #text for details on +width+, +height+, +style+ and +style_properties+.
|
319
320
|
#
|
@@ -324,7 +325,7 @@ module HexaPDF
|
|
324
325
|
# composer.image(machu_picchu, height: 30)
|
325
326
|
def image(file, width: 0, height: 0, style: nil, **style_properties)
|
326
327
|
style = retrieve_style(style, style_properties)
|
327
|
-
image = document.images.add(file)
|
328
|
+
image = file.kind_of?(HexaPDF::Stream) ? file : document.images.add(file)
|
328
329
|
draw_box(Layout::ImageBox.new(image, width: width, height: height, style: style))
|
329
330
|
end
|
330
331
|
|
@@ -361,6 +362,27 @@ module HexaPDF
|
|
361
362
|
end
|
362
363
|
end
|
363
364
|
|
365
|
+
# Creates a stamp (Form XObject) which can be used like an image multiple times on a single page
|
366
|
+
# or on multiple pages.
|
367
|
+
#
|
368
|
+
# The width and the height of the stamp need to be set (frame.width/height or
|
369
|
+
# page.box.width/height might be good choices).
|
370
|
+
#
|
371
|
+
# Examples:
|
372
|
+
#
|
373
|
+
# #>pdf-composer
|
374
|
+
# stamp = composer.create_stamp(50, 50) do |canvas|
|
375
|
+
# canvas.fill_color("red").line_width(5).
|
376
|
+
# rectangle(10, 10, 30, 30).fill_stroke
|
377
|
+
# end
|
378
|
+
# composer.image(stamp, width: 20, height: 20)
|
379
|
+
# composer.image(stamp, width: 50)
|
380
|
+
def create_stamp(width, height) # :yield: canvas
|
381
|
+
stamp = @document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, width, height]})
|
382
|
+
yield(stamp.canvas) if block_given?
|
383
|
+
stamp
|
384
|
+
end
|
385
|
+
|
364
386
|
private
|
365
387
|
|
366
388
|
# Creates the frame into which boxes are layed out when a new page is created.
|
@@ -0,0 +1,396 @@
|
|
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-2022 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
|
+
require 'hexapdf/error'
|
39
|
+
|
40
|
+
module HexaPDF
|
41
|
+
class Document
|
42
|
+
|
43
|
+
# This class provides methods for creating and managing the destinations of a PDF file.
|
44
|
+
#
|
45
|
+
# A destination describes a particular view of a PDF document, consisting of the page, the view
|
46
|
+
# location and a magnification factor. See Destination for details.
|
47
|
+
#
|
48
|
+
# Such destinations may be directly specified where needed, e.g. for link annotations, or they
|
49
|
+
# may be named and later referenced through the name. This class allows to create destinations
|
50
|
+
# with or without a name.
|
51
|
+
#
|
52
|
+
# See: PDF1.7 s12.3.2
|
53
|
+
class Destinations
|
54
|
+
|
55
|
+
# Wraps an explicit destination array to allow easy access to query its properties.
|
56
|
+
#
|
57
|
+
# A *destination array* has the form
|
58
|
+
#
|
59
|
+
# [page, type, *arguments]
|
60
|
+
#
|
61
|
+
# where +page+ is either a page object or a page number (in case of a destination to a page in
|
62
|
+
# a remote PDF document), +type+ is the destination type (see below) and +arguments+ are the
|
63
|
+
# required arguments for the specific type of destination.
|
64
|
+
#
|
65
|
+
# == Destination Types
|
66
|
+
#
|
67
|
+
# There are eight different types of destinations, each taking different arguments. The
|
68
|
+
# arguments are marked up in the list below and are in the correct order for use in the
|
69
|
+
# destination array. The first name in the list is the PDF internal name, the second one the
|
70
|
+
# explicit, more descriptive one used by HexaPDF:
|
71
|
+
#
|
72
|
+
# :XYZ, :xyz::
|
73
|
+
# Display the page with the given (+left+, +top+) coordinate at the upper-left corner of
|
74
|
+
# the window and the specified magnification (+zoom+) factor. A +nil+ value for any
|
75
|
+
# argument means not changing it from the current value.
|
76
|
+
#
|
77
|
+
# :Fit, :fit_page::
|
78
|
+
# Display the page so that it fits horizontally and vertically within the window.
|
79
|
+
#
|
80
|
+
# :FitH, :fit_page_horizontal::
|
81
|
+
# Display the page so that it fits horizontally within the window, with the given +top+
|
82
|
+
# coordinate being at the top of the window. A +nil+ value for +top+ means not changing it
|
83
|
+
# from the current value.
|
84
|
+
#
|
85
|
+
# :FitV, :fit_page_vertical::
|
86
|
+
# Display the page so that it fits vertically within the window, with the given +left+
|
87
|
+
# coordinate being at the left of the window. A +nil+ value for +left+ means not changing
|
88
|
+
# it from the current value.
|
89
|
+
#
|
90
|
+
# :FitR, :fit_rectangle::
|
91
|
+
# Display the page so that the rectangle specified by (+left+, +bottom+)-(+right+, +top+)
|
92
|
+
# fits horizontally and vertically within the window.
|
93
|
+
#
|
94
|
+
# :FitB, :fit_bounding_box::
|
95
|
+
# Display the page so that its bounding box fits horizontally and vertically within the
|
96
|
+
# window.
|
97
|
+
#
|
98
|
+
# :FitBH, :fit_bounding_box_horizontal::
|
99
|
+
# Display the page so that its bounding box fits horizontally within the window, with the
|
100
|
+
# given +top+ coordinate being at the top of the window. A +nil+ value for +top+ means not
|
101
|
+
# changing it from the current value.
|
102
|
+
#
|
103
|
+
# :FitBV, :fit_bounding_box_vertical::
|
104
|
+
# Display the page so that its bounding box fits vertically within the window, with the
|
105
|
+
# given +left+ coordinate being at the left of the window. A +nil+ value for +left+ means
|
106
|
+
# not changing it from the current value.
|
107
|
+
class Destination
|
108
|
+
|
109
|
+
# :nodoc:
|
110
|
+
TYPE_MAPPING = {
|
111
|
+
XYZ: :xyz,
|
112
|
+
Fit: :fit_page,
|
113
|
+
FitH: :fit_page_horizontal,
|
114
|
+
FitV: :fit_page_vertical,
|
115
|
+
FitR: :fit_rectangle,
|
116
|
+
FitB: :fit_bounding_box,
|
117
|
+
FitBH: :fit_bounding_box_horizontal,
|
118
|
+
FitBV: :fit_bounding_box_vertical,
|
119
|
+
}
|
120
|
+
|
121
|
+
# :nodoc:
|
122
|
+
REVERSE_TYPE_MAPPING = Hash[*TYPE_MAPPING.flatten.reverse]
|
123
|
+
|
124
|
+
# Creates a new Destination for the given +destination+ which may be an explicit destination
|
125
|
+
# array or a dictionary with a /D entry (as allowed for a named destination).
|
126
|
+
def initialize(destination)
|
127
|
+
@destination = (destination.kind_of?(HexaPDF::Dictionary) ? destination[:D] : destination)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns +true+ if the destination references a destination in a remote document.
|
131
|
+
def remote?
|
132
|
+
@destination[0].kind_of?(Numeric)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns the referenced page.
|
136
|
+
#
|
137
|
+
# The return value is either a page object or, in case of a destination to a remote
|
138
|
+
# document, a page number.
|
139
|
+
def page
|
140
|
+
@destination[0]
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns the type of destination.
|
144
|
+
def type
|
145
|
+
TYPE_MAPPING[@destination[1]]
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns the argument +left+ if used by the destination, raises an error otherwise.
|
149
|
+
def left
|
150
|
+
case type
|
151
|
+
when :xyz, :fit_page_vertical, :fit_rectangle, :fit_bounding_box_vertical
|
152
|
+
@destination[2]
|
153
|
+
else
|
154
|
+
raise HexaPDF::Error, "No such argument for destination type #{type}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Returns the argument +top+ if used by the destination, raises an error otherwise.
|
159
|
+
def top
|
160
|
+
case type
|
161
|
+
when :xyz
|
162
|
+
@destination[3]
|
163
|
+
when :fit_page_horizontal, :fit_bounding_box_horizontal
|
164
|
+
@destination[2]
|
165
|
+
when :fit_rectangle
|
166
|
+
@destination[5]
|
167
|
+
else
|
168
|
+
raise HexaPDF::Error, "No such argument for destination type #{type}"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Returns the argument +right+ if used by the destination, raises an error otherwise.
|
173
|
+
def right
|
174
|
+
case type
|
175
|
+
when :fit_rectangle
|
176
|
+
@destination[4]
|
177
|
+
else
|
178
|
+
raise HexaPDF::Error, "No such argument for destination type #{type}"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Returns the argument +bottom+ if used by the destination, raises an error otherwise.
|
183
|
+
def bottom
|
184
|
+
case type
|
185
|
+
when :fit_rectangle
|
186
|
+
@destination[3]
|
187
|
+
else
|
188
|
+
raise HexaPDF::Error, "No such argument for destination type #{type}"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Returns the argument +zoom+ if used by the destination, raises an error otherwise.
|
193
|
+
def zoom
|
194
|
+
case type
|
195
|
+
when :xyz
|
196
|
+
@destination[4]
|
197
|
+
else
|
198
|
+
raise HexaPDF::Error, "No such argument for destination type #{type}"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
include Enumerable
|
205
|
+
|
206
|
+
# Creates a new Destinations object for the given PDF document.
|
207
|
+
def initialize(document)
|
208
|
+
@document = document
|
209
|
+
end
|
210
|
+
|
211
|
+
# :call-seq:
|
212
|
+
# destinations.create_xyz(page, left: nil, top: nil, zoom: nil) -> dest
|
213
|
+
# destinations.create_xyz(page, name: nil, left: nil, top: nil, zoom: nil) -> name
|
214
|
+
#
|
215
|
+
# Creates a new xyz destination array for the given arguments and returns it or, in case
|
216
|
+
# a name is given, the name.
|
217
|
+
#
|
218
|
+
# The arguments +page+, +left+, +top+ and +zoom+ are described in detail in the Destination
|
219
|
+
# class description.
|
220
|
+
#
|
221
|
+
# If the argument +name+ is given, the created destination array is added to the destinations
|
222
|
+
# name tree under that name for reuse later, overwriting an existing entry if there is one.
|
223
|
+
def create_xyz(page, name: nil, left: nil, top: nil, zoom: nil)
|
224
|
+
destination = [page, Destination::REVERSE_TYPE_MAPPING.fetch(:xyz), left, top, zoom]
|
225
|
+
name ? (add(name, destination); name) : destination
|
226
|
+
end
|
227
|
+
|
228
|
+
# :call-seq:
|
229
|
+
# destinations.create_fit_page(page) -> dest
|
230
|
+
# destinations.create_fit_page(page, name: nil) -> name
|
231
|
+
#
|
232
|
+
# Creates a new fit to page destination array for the given arguments and returns it or, in
|
233
|
+
# case a name is given, the name.
|
234
|
+
#
|
235
|
+
# The argument +page+ is described in detail in the Destination class description.
|
236
|
+
#
|
237
|
+
# If the argument +name+ is given, the created destination array is added to the destinations
|
238
|
+
# name tree under that name for reuse later, overwriting an existing entry if there is one.
|
239
|
+
def create_fit_page(page, name: nil)
|
240
|
+
destination = [page, Destination::REVERSE_TYPE_MAPPING.fetch(:fit_page)]
|
241
|
+
name ? (add(name, destination); name) : destination
|
242
|
+
end
|
243
|
+
|
244
|
+
# :call-seq:
|
245
|
+
# destinations.create_fit_page_horizontal(page, top: nil) -> dest
|
246
|
+
# destinations.create_fit_page_horizontal(page, name: nil, top: nil) -> name
|
247
|
+
#
|
248
|
+
# Creates a new fit page horizontal destination array for the given arguments and returns it
|
249
|
+
# or, in case a name is given, the name.
|
250
|
+
#
|
251
|
+
# The arguments +page and +top+ are described in detail in the Destination class description.
|
252
|
+
#
|
253
|
+
# If the argument +name+ is given, the created destination array is added to the destinations
|
254
|
+
# name tree under that name for reuse later, overwriting an existing entry if there is one.
|
255
|
+
def create_fit_page_horizontal(page, name: nil, top: nil)
|
256
|
+
destination = [page, Destination::REVERSE_TYPE_MAPPING.fetch(:fit_page_horizontal), top]
|
257
|
+
name ? (add(name, destination); name) : destination
|
258
|
+
end
|
259
|
+
|
260
|
+
# :call-seq:
|
261
|
+
# destinations.create_fit_page_vertical(page, left: nil) -> dest
|
262
|
+
# destinations.create_fit_page_vertical(page, name: nil, left: nil) -> name
|
263
|
+
#
|
264
|
+
# Creates a new fit page vertical destination array for the given arguments and returns it or,
|
265
|
+
# in case a name is given, the name.
|
266
|
+
#
|
267
|
+
# The arguments +page and +left+ are described in detail in the Destination class description.
|
268
|
+
#
|
269
|
+
# If the argument +name+ is given, the created destination array is added to the destinations
|
270
|
+
# name tree under that name for reuse later, overwriting an existing entry if there is one.
|
271
|
+
def create_fit_page_vertical(page, name: nil, left: nil)
|
272
|
+
destination = [page, Destination::REVERSE_TYPE_MAPPING.fetch(:fit_page_vertical), left]
|
273
|
+
name ? (add(name, destination); name) : destination
|
274
|
+
end
|
275
|
+
|
276
|
+
# :call-seq:
|
277
|
+
# destinations.create_fit_rectangle(page, left:, bottom:, right:, top:) -> dest
|
278
|
+
# destinations.create_fit_rectangle(page, name: nil, left:, bottom:, right:, top:) -> name
|
279
|
+
#
|
280
|
+
# Creates a new fit to rectangle destination array for the given arguments and returns it or,
|
281
|
+
# in case a name is given, the name.
|
282
|
+
#
|
283
|
+
# The arguments +page+, +left+, +bottom+, +right+ and +top+ are described in detail in the
|
284
|
+
# Destination class description.
|
285
|
+
#
|
286
|
+
# If the argument +name+ is given, the created destination array is added to the destinations
|
287
|
+
# name tree under that name for reuse later, overwriting an existing entry if there is one.
|
288
|
+
def create_fit_rectangle(page, left:, bottom:, right:, top:, name: nil)
|
289
|
+
destination = [page, Destination::REVERSE_TYPE_MAPPING.fetch(:fit_rectangle),
|
290
|
+
left, bottom, right, top]
|
291
|
+
name ? (add(name, destination); name) : destination
|
292
|
+
end
|
293
|
+
|
294
|
+
# :call-seq:
|
295
|
+
# destinations.create_fit_bounding_box(page) -> dest
|
296
|
+
# destinations.create_fit_bounding_box(page, name: nil) -> name
|
297
|
+
#
|
298
|
+
# Creates a new fit to bounding box destination array for the given arguments and returns it
|
299
|
+
# or, in case a name is given, the name.
|
300
|
+
#
|
301
|
+
# The argument +page+ is described in detail in the Destination class description.
|
302
|
+
#
|
303
|
+
# If the argument +name+ is given, the created destination array is added to the destinations
|
304
|
+
# name tree under that name for reuse later, overwriting an existing entry if there is one.
|
305
|
+
def create_fit_bounding_box(page, name: nil)
|
306
|
+
destination = [page, Destination::REVERSE_TYPE_MAPPING.fetch(:fit_bounding_box)]
|
307
|
+
name ? (add(name, destination); name) : destination
|
308
|
+
end
|
309
|
+
|
310
|
+
# :call-seq:
|
311
|
+
# destinations.create_fit_bounding_box_horizontal(page, top: nil) -> dest
|
312
|
+
# destinations.create_fit_bounding_box_horizontal(page, name: nil, top: nil) -> name
|
313
|
+
#
|
314
|
+
# Creates a new fit bounding box horizontal destination array for the given arguments and
|
315
|
+
# returns it or, in case a name is given, the name.
|
316
|
+
#
|
317
|
+
# The arguments +page and +top+ are described in detail in the Destination class description.
|
318
|
+
#
|
319
|
+
# If the argument +name+ is given, the created destination array is added to the destinations
|
320
|
+
# name tree under that name for reuse later, overwriting an existing entry if there is one.
|
321
|
+
def create_fit_bounding_box_horizontal(page, name: nil, top: nil)
|
322
|
+
destination = [page, Destination::REVERSE_TYPE_MAPPING.fetch(:fit_bounding_box_horizontal), top]
|
323
|
+
name ? (add(name, destination); name) : destination
|
324
|
+
end
|
325
|
+
|
326
|
+
# :call-seq:
|
327
|
+
# destinations.create_fit_bounding_box_vertical(page, left: nil) -> dest
|
328
|
+
# destinations.create_fit_bounding_box_vertical(page, name: nil, left: nil) -> name
|
329
|
+
#
|
330
|
+
# Creates a new fit bounding box vertical destination array for the given arguments and
|
331
|
+
# returns it or, in case a name is given, the name.
|
332
|
+
#
|
333
|
+
# The arguments +page and +left+ are described in detail in the Destination class description.
|
334
|
+
#
|
335
|
+
# If the argument +name+ is given, the created destination array is added to the destinations
|
336
|
+
# name tree under that name for reuse later, overwriting an existing entry if there is one.
|
337
|
+
def create_fit_bounding_box_vertical(page, name: nil, left: nil)
|
338
|
+
destination = [page, Destination::REVERSE_TYPE_MAPPING.fetch(:fit_bounding_box_vertical), left]
|
339
|
+
name ? (add(name, destination); name) : destination
|
340
|
+
end
|
341
|
+
|
342
|
+
# :call-seq:
|
343
|
+
# destinations.add(name, destination)
|
344
|
+
#
|
345
|
+
# Adds the given +destination+ under +name+ to the destinations name tree.
|
346
|
+
#
|
347
|
+
# If the name does already exist, an error is raised.
|
348
|
+
def add(name, destination)
|
349
|
+
destinations.add_entry(name, destination)
|
350
|
+
end
|
351
|
+
|
352
|
+
# :call-seq:
|
353
|
+
# destinations.delete(name) -> destination
|
354
|
+
#
|
355
|
+
# Deletes the given destination from the destinations name tree and returns it or +nil+ if no
|
356
|
+
# destination was registered under that name.
|
357
|
+
def delete(name)
|
358
|
+
destinations.delete_entry(name)
|
359
|
+
end
|
360
|
+
|
361
|
+
# :call-seq:
|
362
|
+
# destinations[name] -> destination
|
363
|
+
#
|
364
|
+
# Returns the destination registered under the given +name+ or +nil+ if no destination was
|
365
|
+
# registered under that name.
|
366
|
+
def [](name)
|
367
|
+
destinations.find_entry(name)
|
368
|
+
end
|
369
|
+
|
370
|
+
# :call-seq:
|
371
|
+
# destinations.each {|name, dest| block } -> destinations
|
372
|
+
# destinations.each -> Enumerator
|
373
|
+
#
|
374
|
+
# Iterates over all named destinations of the PDF, yielding the name and the destination
|
375
|
+
# wrapped into a Destination object.
|
376
|
+
def each
|
377
|
+
return to_enum(__method__) unless block_given?
|
378
|
+
|
379
|
+
destinations.each_entry do |name, dest|
|
380
|
+
yield(name, Destination.new(dest))
|
381
|
+
end
|
382
|
+
|
383
|
+
self
|
384
|
+
end
|
385
|
+
|
386
|
+
private
|
387
|
+
|
388
|
+
# Returns the root of the destinations name tree.
|
389
|
+
def destinations
|
390
|
+
@document.catalog.names.destinations
|
391
|
+
end
|
392
|
+
|
393
|
+
end
|
394
|
+
|
395
|
+
end
|
396
|
+
end
|