hexapdf 0.27.0 → 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 +59 -1
- data/examples/019-acro_form.rb +14 -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 +2 -2
- data/lib/hexapdf/configuration.rb +1 -2
- data/lib/hexapdf/content/canvas.rb +8 -3
- data/lib/hexapdf/dictionary.rb +1 -5
- data/lib/hexapdf/document.rb +6 -10
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/importer.rb +32 -27
- data/lib/hexapdf/layout/list_box.rb +1 -5
- data/lib/hexapdf/object.rb +5 -0
- data/lib/hexapdf/parser.rb +13 -0
- data/lib/hexapdf/revision.rb +15 -12
- data/lib/hexapdf/revisions.rb +4 -0
- data/lib/hexapdf/tokenizer.rb +14 -8
- 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 +33 -7
- 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/font_true_type.rb +14 -0
- data/lib/hexapdf/type/object_stream.rb +2 -2
- data/lib/hexapdf/type/outline.rb +1 -1
- data/lib/hexapdf/type/page.rb +56 -46
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +2 -3
- data/test/hexapdf/content/test_canvas.rb +5 -0
- data/test/hexapdf/document/test_pages.rb +2 -2
- 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 +13 -6
- data/test/hexapdf/test_parser.rb +17 -0
- data/test/hexapdf/test_revision.rb +15 -14
- data/test/hexapdf/test_revisions.rb +43 -0
- data/test/hexapdf/test_stream.rb +1 -1
- data/test/hexapdf/test_tokenizer.rb +3 -4
- data/test/hexapdf/test_writer.rb +3 -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 +18 -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 +3 -1
- 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 +3 -0
- data/test/hexapdf/type/test_page.rb +67 -30
- data/test/hexapdf/type/test_page_tree_node.rb +4 -2
- metadata +46 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 874e09b094ea4e793d1d123cfbaded6d1cc5ba93af3b57587e9faf402786a30f
|
4
|
+
data.tar.gz: c1eed6a778936cd360b4f1878a18abc3e5727c1f36b5eab4838a9a72817dff7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b66a7587a239acbeb9ebbb20f851b2fa7738c5a4c2f8a95ae7ae3d7419b84ae37b5d1fb48a6b7023bff0df3e1728c395b5351c9c42c05707fabd5a1722e2b88a
|
7
|
+
data.tar.gz: 62ac7d070bb8ae3426685af497a047096dd32dc16a102baa961512652222b388198561776fc1227be69bdd0713183d91fa25e411262eec34a29227aae9723d5c
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,61 @@
|
|
1
|
+
## 0.28.0 - 2022-12-30
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* [HexaPDF::Type::AcroForm::AppearanceGenerator#create_push_button_appearances]
|
6
|
+
to allow customizing the behaviour
|
7
|
+
* [HexaPDF::Parser#linearized?] for determining whether a document is linearized
|
8
|
+
* Information on linearization to `hexapdf info` output
|
9
|
+
* Support for `AFNumber_Format` Javascript method to the form field appearance
|
10
|
+
generator
|
11
|
+
* Support for using fully embedded, simple TrueType fonts for drawing operations
|
12
|
+
|
13
|
+
### Changed
|
14
|
+
|
15
|
+
* **Breaking change**: `HexaPDF::Revision#reset_objects` has been removed
|
16
|
+
* **Breaking change**: Method signature of [HexaPDF::Importer::for] has been
|
17
|
+
changed
|
18
|
+
* **Breaking change**: [HexaPDF::Type::AcroForm::Field#each_widget] now has the
|
19
|
+
default value of the argument `direct_only` set to `true` instead of `false`
|
20
|
+
* [HexaPDF::Revision#each_modified_object] to allow deleting the modified
|
21
|
+
objects from the active objects' container
|
22
|
+
* [HexaPDF::Revision#each_modified_object] to allow ignoring added object and
|
23
|
+
cross-reference stream objects
|
24
|
+
* [HexaPDF::Revisions::from_io] to merge the two revisions of a linearized PDF
|
25
|
+
* [HexaPDF::Importer] and [HexaPDF::Document#import] to make working with them
|
26
|
+
easier by allowing the import of arbitrary objects
|
27
|
+
* `HexaPDF::Type::AcroForm::Form#perform_validation` to combine fields with the
|
28
|
+
same name
|
29
|
+
|
30
|
+
### Fixed
|
31
|
+
|
32
|
+
* [HexaPDF::Type::AcroForm::AppearanceGenerator#create_check_box_appearances] to
|
33
|
+
correctly handle a field value of `nil`
|
34
|
+
* Return value of `#type` method for all AcroForm field classes
|
35
|
+
* [HexaPDF::Type::Page#flatten_annotations] to work correctly in case no
|
36
|
+
annotations are on the page
|
37
|
+
* [HexaPDF::Type::AcroForm::ButtonField#create_appearances] to avoid creating
|
38
|
+
appearances in case of as-yet unresolved references to existing appearances
|
39
|
+
* [HexaPDF::Type::AcroForm::TextField#create_appearances] to avoid creating
|
40
|
+
appearances in case of pre-existing ones
|
41
|
+
* `HexaPDF::Tokenizer#parse_number` to treat invalid indirect object references
|
42
|
+
with an object number of 0 as null values
|
43
|
+
* [HexaPDF::Type::AcroForm::AppearanceGenerator] to handle empty appearance
|
44
|
+
characteristics dictionary marker style strings
|
45
|
+
* Writing of encrypted files containing two or more revisions
|
46
|
+
* Generation of object streams to never allow storing the catalog object to
|
47
|
+
avoid problems with certain viewers
|
48
|
+
* `HexaPDF::Type::Outline#perform_validation` to not show validation error when
|
49
|
+
`/Count` is zero
|
50
|
+
* Writing of documents with two or more revisions in non-incremental mode when
|
51
|
+
`optimize: true` is used and the original document used cross-reference tables
|
52
|
+
* [HexaPDF::Type::AcroForm::AppearanceGenerator] to take a widget's rotation
|
53
|
+
value into account
|
54
|
+
* [HexaPDF::Type::Page#flatten_annotations] to correctly flatten all
|
55
|
+
annotations, including ones with custom rotations
|
56
|
+
* [HexaPDF::Type::Page#rotate] to also rotate annotations
|
57
|
+
|
58
|
+
|
1
59
|
## 0.27.0 - 2022-11-18
|
2
60
|
|
3
61
|
### Added
|
@@ -236,7 +294,7 @@
|
|
236
294
|
### Added
|
237
295
|
|
238
296
|
- [HexaPDF::Composer#create_stamp] for creating a form Xobject
|
239
|
-
-
|
297
|
+
- `HexaPDF::Revision#reset_objects` for deleting all live loaded and added
|
240
298
|
objects
|
241
299
|
- Support for removing or flattening annotations to the `hexapdf modify` command
|
242
300
|
- Option to CLI command `hexapdf form` to allow generation of a template file
|
data/examples/019-acro_form.rb
CHANGED
@@ -6,6 +6,9 @@
|
|
6
6
|
# This example show-cases how to create the various form field types and their
|
7
7
|
# possible standard appearances.
|
8
8
|
#
|
9
|
+
# Note the 'number format' text field which uses a JavaScript function for
|
10
|
+
# formatting a number.
|
11
|
+
#
|
9
12
|
# Usage:
|
10
13
|
# : `ruby acro_form.rb`
|
11
14
|
#
|
@@ -42,13 +45,21 @@ rb = form.create_radio_button("Radio")
|
|
42
45
|
end
|
43
46
|
rb.field_value = :button0
|
44
47
|
|
45
|
-
canvas.text("Text fields", at: [50,
|
48
|
+
canvas.text("Text fields", at: [50, 480])
|
46
49
|
|
47
|
-
canvas.text("Single line", at: [70,
|
50
|
+
canvas.text("Single line", at: [70, 450])
|
48
51
|
tx = form.create_text_field("Single Line", font_size: 16)
|
49
|
-
widget = tx.create_widget(page, Rect: [200,
|
52
|
+
widget = tx.create_widget(page, Rect: [200, 445, 500, 465])
|
50
53
|
tx.field_value = "A sample test string!"
|
51
54
|
|
55
|
+
canvas.text("Number format", at: [70, 420])
|
56
|
+
tx = form.create_text_field("Number format", font_size: 16)
|
57
|
+
widget = tx.create_widget(page, Rect: [200, 415, 500, 435])
|
58
|
+
widget[:AA] = {
|
59
|
+
F: {S: :JavaScript, JS: 'AFNumber_Format(2, 2, 0, 0, "EUR ", true);'},
|
60
|
+
}
|
61
|
+
tx.field_value = "123456,789"
|
62
|
+
|
52
63
|
canvas.text("Multiline", at: [70, 390])
|
53
64
|
tx = form.create_multiline_text_field("Multiline", font_size: 0, align: :right)
|
54
65
|
widget = tx.create_widget(page, Rect: [200, 325, 500, 405])
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# # Images
|
2
|
+
#
|
3
|
+
# This example shows how to embed images into a PDF document, directly on a
|
4
|
+
# page's canvas and through the high-level [HexaPDF::Composer].
|
5
|
+
#
|
6
|
+
# Usage:
|
7
|
+
# : `ruby images.rb`
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'hexapdf'
|
11
|
+
|
12
|
+
file = File.join(__dir__, 'machupicchu.jpg')
|
13
|
+
|
14
|
+
doc = HexaPDF::Document.new
|
15
|
+
# Image only added to PDF once though used multiple times
|
16
|
+
canvas = doc.pages.add.canvas
|
17
|
+
canvas.image(file, at: [100, 500]) # auto-size based on image size
|
18
|
+
canvas.image(file, at: [100, 300], width: 100) # height based on w/h ratio
|
19
|
+
canvas.image(file, at: [300, 300], height: 100) # width based on w/h ratio
|
20
|
+
canvas.image(file, at: [100, 100], width: 300, height: 100)
|
21
|
+
|
22
|
+
HexaPDF::Composer.create('images.pdf') do |composer|
|
23
|
+
composer.image(file) # fill current rectangular region
|
24
|
+
composer.image(file, width: 100) # height based on w/h ratio
|
25
|
+
composer.image(file, height: 100) # width based on w/h ratio
|
26
|
+
composer.image(file, width: 300, height: 100)
|
27
|
+
|
28
|
+
# Add the page created above as second page
|
29
|
+
composer.document.pages << composer.document.import(doc.pages[0])
|
30
|
+
end
|
data/lib/hexapdf/cli/info.rb
CHANGED
@@ -131,6 +131,10 @@ module HexaPDF
|
|
131
131
|
output_line("Encrypted", "yes (no or wrong password given)")
|
132
132
|
end
|
133
133
|
|
134
|
+
if doc.revisions.parser.linearized?
|
135
|
+
output_line("Linearized", "yes")
|
136
|
+
end
|
137
|
+
|
134
138
|
signatures = doc.signatures.to_a
|
135
139
|
unless signatures.empty?
|
136
140
|
nr_sigs = signatures.count
|
@@ -186,7 +190,7 @@ module HexaPDF
|
|
186
190
|
end
|
187
191
|
|
188
192
|
def output_line(header, text) #:nodoc:
|
189
|
-
puts(
|
193
|
+
puts("#{header}:".ljust(COLUMN_WIDTH) << text.to_s)
|
190
194
|
end
|
191
195
|
|
192
196
|
end
|
data/lib/hexapdf/cli/inspect.rb
CHANGED
@@ -335,9 +335,9 @@ module HexaPDF
|
|
335
335
|
# - The signature dictionary if this revision was signed
|
336
336
|
# - The byte offset from the start of the file to the end of the revision
|
337
337
|
def revision_information
|
338
|
-
signatures = @doc.signatures.
|
338
|
+
signatures = @doc.signatures.to_h do |sig|
|
339
339
|
[@doc.revisions.find {|rev| rev.object(sig) == sig }, sig]
|
340
|
-
end
|
340
|
+
end
|
341
341
|
io = @doc.revisions.parser.io
|
342
342
|
|
343
343
|
startxrefs = @doc.revisions.map {|rev| rev.trailer[:Prev] }
|
data/lib/hexapdf/cli/split.rb
CHANGED
@@ -131,8 +131,8 @@ module HexaPDF
|
|
131
131
|
@page_name_cache ||= {}
|
132
132
|
return @page_name_cache[box] if @page_name_cache.key?(box)
|
133
133
|
|
134
|
-
paper_size = HexaPDF::Type::Page::PAPER_SIZE.find do |_name,
|
135
|
-
|
134
|
+
paper_size = HexaPDF::Type::Page::PAPER_SIZE.find do |_name, paper_box|
|
135
|
+
paper_box.each_with_index.all? {|entry, index| (entry - paper_box[index]).abs < 5 }
|
136
136
|
end
|
137
137
|
|
138
138
|
@page_name_cache[box] =
|
@@ -422,8 +422,7 @@ module HexaPDF
|
|
422
422
|
'encryption.filter_map' => {
|
423
423
|
Standard: 'HexaPDF::Encryption::StandardSecurityHandler',
|
424
424
|
},
|
425
|
-
'encryption.sub_filter_map' => {
|
426
|
-
},
|
425
|
+
'encryption.sub_filter_map' => {},
|
427
426
|
'filter.map' => {
|
428
427
|
ASCIIHexDecode: 'HexaPDF::Filter::ASCIIHexDecode',
|
429
428
|
AHx: 'HexaPDF::Filter::ASCIIHexDecode',
|
@@ -1626,17 +1626,22 @@ module HexaPDF
|
|
1626
1626
|
end
|
1627
1627
|
return obj if obj.width == 0 || obj.height == 0
|
1628
1628
|
|
1629
|
+
left, bottom = *at
|
1629
1630
|
width, height = calculate_dimensions(obj.width, obj.height,
|
1630
1631
|
rwidth: width, rheight: height)
|
1631
1632
|
if obj[:Subtype] != :Image
|
1632
1633
|
width /= obj.box.width.to_f
|
1633
1634
|
height /= obj.box.height.to_f
|
1634
|
-
|
1635
|
-
|
1635
|
+
left -= obj.box.left
|
1636
|
+
bottom -= obj.box.bottom
|
1636
1637
|
end
|
1637
1638
|
|
1638
|
-
|
1639
|
+
if left == 0 && bottom == 0 && width == 1 && height == 1
|
1639
1640
|
invoke1(:Do, resources.add_xobject(obj))
|
1641
|
+
else
|
1642
|
+
transform(width, 0, 0, height, left, bottom) do
|
1643
|
+
invoke1(:Do, resources.add_xobject(obj))
|
1644
|
+
end
|
1640
1645
|
end
|
1641
1646
|
|
1642
1647
|
obj
|
data/lib/hexapdf/dictionary.rb
CHANGED
@@ -108,11 +108,7 @@ module HexaPDF
|
|
108
108
|
# The ancestor classes are also searched for such a field entry if none is found for the
|
109
109
|
# current class.
|
110
110
|
def self.field(name)
|
111
|
-
|
112
|
-
@fields[name]
|
113
|
-
elsif superclass.respond_to?(:field)
|
114
|
-
superclass.field(name)
|
115
|
-
end
|
111
|
+
@fields&.[](name) || superclass.field(name)
|
116
112
|
end
|
117
113
|
|
118
114
|
# :call-seq:
|
data/lib/hexapdf/document.rb
CHANGED
@@ -164,6 +164,8 @@ module HexaPDF
|
|
164
164
|
def initialize(io: nil, decryption_opts: {}, config: {})
|
165
165
|
@config = Configuration.with_defaults(config)
|
166
166
|
@version = '1.2'
|
167
|
+
@cache = Hash.new {|h, k| h[k] = {} }
|
168
|
+
@listeners = {}
|
167
169
|
|
168
170
|
@revisions = Revisions.from_io(self, io)
|
169
171
|
@security_handler = if encrypted? && @config['document.auto_decrypt']
|
@@ -171,9 +173,6 @@ module HexaPDF
|
|
171
173
|
else
|
172
174
|
nil
|
173
175
|
end
|
174
|
-
|
175
|
-
@listeners = {}
|
176
|
-
@cache = Hash.new {|h, k| h[k] = {} }
|
177
176
|
end
|
178
177
|
|
179
178
|
# :call-seq:
|
@@ -251,19 +250,16 @@ module HexaPDF
|
|
251
250
|
# :call-seq:
|
252
251
|
# doc.import(obj) -> imported_object
|
253
252
|
#
|
254
|
-
# Imports the given
|
253
|
+
# Imports the given object from a different HexaPDF::Document instance and returns the imported
|
255
254
|
# object.
|
256
255
|
#
|
257
256
|
# If the same argument is provided in multiple invocations, the import is done only once and
|
258
|
-
# the previously
|
257
|
+
# the previously imported object is returned.
|
259
258
|
#
|
260
259
|
# See: Importer
|
261
260
|
def import(obj)
|
262
|
-
|
263
|
-
|
264
|
-
"with another document"
|
265
|
-
end
|
266
|
-
HexaPDF::Importer.for(source: obj.document, destination: self).import(obj)
|
261
|
+
source = (obj.kind_of?(HexaPDF::Object) ? obj.document : nil)
|
262
|
+
HexaPDF::Importer.for(self).import(obj, source: source)
|
267
263
|
end
|
268
264
|
|
269
265
|
# Wraps the given object inside a HexaPDF::Object class which allows one to use
|
data/lib/hexapdf/importer.rb
CHANGED
@@ -60,64 +60,69 @@ module HexaPDF
|
|
60
60
|
|
61
61
|
end
|
62
62
|
|
63
|
-
# Returns the Importer object for copying objects
|
64
|
-
|
65
|
-
def self.for(source:, destination:)
|
63
|
+
# Returns the Importer object for copying objects to the +destination+ document.
|
64
|
+
def self.for(destination)
|
66
65
|
@map ||= {}
|
67
|
-
@map.keep_if {|_, v| v.
|
68
|
-
source = NullableWeakRef.new(source)
|
66
|
+
@map.keep_if {|_, v| v.destination.weakref_alive? }
|
69
67
|
destination = NullableWeakRef.new(destination)
|
70
|
-
@map[
|
68
|
+
@map[destination.hash] ||= new(destination)
|
71
69
|
end
|
72
70
|
|
73
71
|
private_class_method :new
|
74
72
|
|
75
|
-
attr_reader :
|
73
|
+
attr_reader :destination #:nodoc:
|
76
74
|
|
77
|
-
# Initializes a new importer that can import objects
|
78
|
-
|
79
|
-
def initialize(source:, destination:)
|
80
|
-
@source = source
|
75
|
+
# Initializes a new importer that can import objects to the +destination+ document.
|
76
|
+
def initialize(destination)
|
81
77
|
@destination = destination
|
82
78
|
@mapper = {}
|
83
79
|
end
|
84
80
|
|
85
|
-
|
86
|
-
|
81
|
+
SourceWrapper = Struct.new(:source) #:nodoc:
|
82
|
+
|
83
|
+
# Imports the given +object+ to the destination object and returns the imported object.
|
87
84
|
#
|
88
85
|
# Note: Indirect objects are automatically added to the destination document but direct or
|
89
86
|
# simple objects are not.
|
90
87
|
#
|
91
|
-
#
|
92
|
-
|
88
|
+
# The +source+ argument should be +nil+ or set to the source document of the imported object. If
|
89
|
+
# it is +nil+, the source document is dynamically identified. If this identification is not
|
90
|
+
# possible and the source document would be needed, an error is raised.
|
91
|
+
def import(object, source: nil)
|
92
|
+
internal_import(object, SourceWrapper.new(source))
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
# Does the actual importing of the given +object+, using +wrapper+ to store/use the source
|
98
|
+
# document.
|
99
|
+
def internal_import(object, wrapper)
|
93
100
|
mapped_object = @mapper[object.data]&.__getobj__ if object.kind_of?(HexaPDF::Object)
|
94
|
-
if
|
95
|
-
raise HexaPDF::Error, "Import error: Incorrect document object for importer"
|
96
|
-
elsif mapped_object && !mapped_object.null?
|
101
|
+
if mapped_object && !mapped_object.null?
|
97
102
|
if object.class != mapped_object.class
|
98
103
|
mapped_object = @destination.wrap(mapped_object, type: object.class)
|
99
104
|
end
|
100
105
|
mapped_object
|
101
106
|
else
|
102
|
-
duplicate(object)
|
107
|
+
duplicate(object, wrapper)
|
103
108
|
end
|
104
109
|
end
|
105
110
|
|
106
|
-
private
|
107
|
-
|
108
111
|
# Recursively duplicates the object.
|
109
112
|
#
|
110
113
|
# PDF objects are automatically added to the destination document if they are indirect objects
|
111
114
|
# in the source document.
|
112
|
-
def duplicate(object)
|
115
|
+
def duplicate(object, wrapper)
|
113
116
|
case object
|
114
117
|
when Hash
|
115
|
-
object.transform_values {|v| duplicate(v) }
|
118
|
+
object.transform_values {|v| duplicate(v, wrapper) }
|
116
119
|
when Array
|
117
|
-
object.map {|v| duplicate(v) }
|
120
|
+
object.map {|v| duplicate(v, wrapper) }
|
118
121
|
when HexaPDF::Reference
|
119
|
-
|
122
|
+
raise HexaPDF::Error, "Import error: No source document specified" unless wrapper.source
|
123
|
+
internal_import(wrapper.source.object(object), wrapper)
|
120
124
|
when HexaPDF::Object
|
125
|
+
wrapper.source ||= object.document
|
121
126
|
if object.type == :Catalog || object.type == :Pages
|
122
127
|
@mapper[object.data] = nil
|
123
128
|
elsif (mapped_object = @mapper[object.data]&.__getobj__) && !mapped_object.null?
|
@@ -132,8 +137,8 @@ module HexaPDF
|
|
132
137
|
@destination.add(obj) if object.indirect?
|
133
138
|
|
134
139
|
obj.data.stream = obj.data.stream.dup if obj.data.stream.kind_of?(String)
|
135
|
-
obj.data.value = duplicate(obj.data.value)
|
136
|
-
obj.data.value.update(duplicate(object.copy_inherited_values)) if object.type == :Page
|
140
|
+
obj.data.value = duplicate(obj.data.value, wrapper)
|
141
|
+
obj.data.value.update(duplicate(object.copy_inherited_values, wrapper)) if object.type == :Page
|
137
142
|
obj
|
138
143
|
end
|
139
144
|
when String
|
@@ -207,7 +207,7 @@ module HexaPDF
|
|
207
207
|
@results = []
|
208
208
|
@results_item_marker_x = []
|
209
209
|
|
210
|
-
@children.
|
210
|
+
@children.each do |child|
|
211
211
|
shape = Geom2D::Polygon([left, top - height],
|
212
212
|
[left + width, top - height],
|
213
213
|
[left + width, top],
|
@@ -217,11 +217,7 @@ module HexaPDF
|
|
217
217
|
remove_indent_from_frame_shape(shape) unless shape.polygons.empty?
|
218
218
|
end
|
219
219
|
|
220
|
-
#p [:list, left, width, shape]
|
221
|
-
|
222
220
|
item_frame = Frame.new(item_frame_left, top - height, item_frame_width, height, shape: shape)
|
223
|
-
|
224
|
-
#p [index, item_frame.x, @results_item_marker_x]
|
225
221
|
@results_item_marker_x << item_frame.x - content_indentation
|
226
222
|
|
227
223
|
box_fitter = BoxFitter.new([item_frame])
|
data/lib/hexapdf/object.rb
CHANGED
@@ -159,6 +159,11 @@ module HexaPDF
|
|
159
159
|
object
|
160
160
|
end
|
161
161
|
|
162
|
+
# Returns +nil+ to end the recursion for field searching in Dictionary.field.
|
163
|
+
def self.field(_name)
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
|
162
167
|
# The wrapped HexaPDF::PDFData value.
|
163
168
|
#
|
164
169
|
# This attribute is not part of the public API!
|
data/lib/hexapdf/parser.rb
CHANGED
@@ -70,6 +70,19 @@ module HexaPDF
|
|
70
70
|
!@reconstructed_revision.nil?
|
71
71
|
end
|
72
72
|
|
73
|
+
# Returns +true+ if the PDF file is a linearized file.
|
74
|
+
def linearized?
|
75
|
+
@linearized ||=
|
76
|
+
begin
|
77
|
+
@tokenizer.pos = @header_offset
|
78
|
+
3.times { @tokenizer.next_token } # parse: oid gen obj
|
79
|
+
obj = @tokenizer.next_object
|
80
|
+
obj.kind_of?(Hash) && obj.key?(:Linearized)
|
81
|
+
rescue MalformedPDFError
|
82
|
+
false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
73
86
|
# Loads the indirect (potentially compressed) object specified by the given cross-reference
|
74
87
|
# entry.
|
75
88
|
#
|
data/lib/hexapdf/revision.rb
CHANGED
@@ -229,16 +229,22 @@ module HexaPDF
|
|
229
229
|
end
|
230
230
|
|
231
231
|
# :call-seq:
|
232
|
-
# revision.each_modified_object {|obj| block } -> revision
|
233
|
-
# revision.each_modified_object -> Enumerator
|
232
|
+
# revision.each_modified_object(delete: false, all: all) {|obj| block } -> revision
|
233
|
+
# revision.each_modified_object(delete: false, all: all) -> Enumerator
|
234
234
|
#
|
235
|
-
# Calls the given block once for each object that has been modified since it was loaded.
|
236
|
-
# object and cross-reference streams are ignored.
|
235
|
+
# Calls the given block once for each object that has been modified since it was loaded. Added
|
236
|
+
# or eleted object and cross-reference streams as well as signature dictionaries are ignored.
|
237
|
+
#
|
238
|
+
# +delete+:: If the +delete+ argument is set to +true+, each modified object is deleted from the
|
239
|
+
# active objects.
|
240
|
+
#
|
241
|
+
# +all+:: If the +all+ argument is set to +true+, added object and cross-reference streams are
|
242
|
+
# also yielded.
|
237
243
|
#
|
238
244
|
# Note that this also means that for revisions without an associated cross-reference section all
|
239
245
|
# loaded objects will be yielded.
|
240
|
-
def each_modified_object
|
241
|
-
return to_enum(__method__) unless block_given?
|
246
|
+
def each_modified_object(delete: false, all: false)
|
247
|
+
return to_enum(__method__, delete: delete, all: all) unless block_given?
|
242
248
|
|
243
249
|
@objects.each do |oid, gen, obj|
|
244
250
|
if @xref_section.entry?(oid, gen)
|
@@ -259,20 +265,17 @@ module HexaPDF
|
|
259
265
|
end
|
260
266
|
next if values_unchanged && streams_are_same
|
261
267
|
end
|
268
|
+
elsif !all && (obj.type == :XRef || obj.type == :ObjStm)
|
269
|
+
next
|
262
270
|
end
|
263
271
|
|
264
272
|
yield(obj)
|
273
|
+
@objects.delete(oid) if delete
|
265
274
|
end
|
266
275
|
|
267
276
|
self
|
268
277
|
end
|
269
278
|
|
270
|
-
# Resets the revision by deleting all loaded and added objects from it.
|
271
|
-
def reset_objects
|
272
|
-
@objects = HexaPDF::Utils::ObjectHash.new
|
273
|
-
@all_objects_loaded = false
|
274
|
-
end
|
275
|
-
|
276
279
|
private
|
277
280
|
|
278
281
|
# Loads a single object from the associated cross-reference section.
|
data/lib/hexapdf/revisions.rb
CHANGED
@@ -93,6 +93,10 @@ module HexaPDF
|
|
93
93
|
seen_xref_offsets[stm] = true
|
94
94
|
end
|
95
95
|
|
96
|
+
if parser.linearized? && !trailer.key?(:Prev)
|
97
|
+
merge_revision = offset
|
98
|
+
end
|
99
|
+
|
96
100
|
if merge_revision == offset
|
97
101
|
xref_section.merge!(revisions.first.xref_section)
|
98
102
|
offset = trailer[:Prev] # Get possible next offset before overwriting trailer
|
data/lib/hexapdf/tokenizer.rb
CHANGED
@@ -285,7 +285,14 @@ module HexaPDF
|
|
285
285
|
tmp = val.to_i
|
286
286
|
# Handle object references, see PDF1.7 s7.3.10
|
287
287
|
prepare_string_scanner(10)
|
288
|
-
|
288
|
+
if @ss.scan(REFERENCE_RE)
|
289
|
+
tmp = if tmp > 0
|
290
|
+
Reference.new(tmp, @ss[1].to_i)
|
291
|
+
else
|
292
|
+
maybe_raise("Invalid indirect object reference (#{tmp},#{@ss[1].to_i})")
|
293
|
+
nil
|
294
|
+
end
|
295
|
+
end
|
289
296
|
tmp
|
290
297
|
elsif val.match?(/\A[+-]?(?:\d+\.\d*|\.\d+)\z/)
|
291
298
|
val << '0' if val.getbyte(-1) == 46 # dot '.'
|
@@ -315,21 +322,20 @@ module HexaPDF
|
|
315
322
|
parentheses = 1
|
316
323
|
|
317
324
|
while parentheses != 0
|
318
|
-
data = scan_until(/
|
319
|
-
char = @ss[1]
|
325
|
+
data = scan_until(/[()\\\r]/)
|
320
326
|
unless data
|
321
327
|
raise HexaPDF::MalformedPDFError.new("Unclosed literal string found", pos: pos)
|
322
328
|
end
|
323
329
|
|
324
330
|
str << data
|
325
331
|
prepare_string_scanner if @ss.eos?
|
326
|
-
case
|
327
|
-
when
|
328
|
-
when
|
329
|
-
when
|
332
|
+
case @ss.string.getbyte(@ss.pos - 1)
|
333
|
+
when 41 then parentheses -= 1 # )
|
334
|
+
when 40 then parentheses += 1 # (
|
335
|
+
when 13 # \r
|
330
336
|
str[-1] = "\n"
|
331
337
|
@ss.pos += 1 if @ss.peek(1) == "\n"
|
332
|
-
when
|
338
|
+
when 92 # \\
|
333
339
|
str.chop!
|
334
340
|
byte = @ss.get_byte
|
335
341
|
if (data = LITERAL_STRING_ESCAPE_MAP[byte])
|