hexapdf 1.0.0 → 1.0.2
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 +29 -0
- data/lib/hexapdf/cli.rb +14 -1
- data/lib/hexapdf/configuration.rb +1 -0
- data/lib/hexapdf/font/true_type/subsetter.rb +2 -15
- data/lib/hexapdf/font/true_type/table.rb +6 -1
- data/lib/hexapdf/font/true_type_wrapper.rb +10 -1
- data/lib/hexapdf/parser.rb +5 -1
- data/lib/hexapdf/type/cid_font.rb +1 -1
- data/lib/hexapdf/type/cmap.rb +58 -0
- data/lib/hexapdf/type.rb +1 -0
- data/lib/hexapdf/utils/sorted_tree_node.rb +12 -2
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +1 -0
- data/lib/hexapdf/xref_section.rb +20 -4
- data/test/hexapdf/font/test_true_type_wrapper.rb +5 -0
- data/test/hexapdf/font/true_type/test_subsetter.rb +0 -10
- data/test/hexapdf/font/true_type/test_table.rb +12 -0
- data/test/hexapdf/test_parser.rb +16 -6
- data/test/hexapdf/test_writer.rb +5 -5
- data/test/hexapdf/test_xref_section.rb +15 -0
- data/test/hexapdf/utils/test_sorted_tree_node.rb +7 -6
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44eb3afec42bc0c5e0645c0ff6eb48f211083694866fb7c355a0b4ca3e3ad24a
|
4
|
+
data.tar.gz: 864792c029e975b0d8167d80326815ea6a7ebff90bc360c4d550c8a2d4a28003
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19d415762cb49abfaf31df0d6dc8455c5a6a8b1eee40f18cc4d56b7a18e4d9e9aded8d5826a4f5a265bbc639d61f5a2fe081b1e672b626984d4be719115f4c9a
|
7
|
+
data.tar.gz: 6c8d31294566e9c408cb0fe8512ca4898d583edebe750191fda6d42fcb0a6cb44d83de90359abcac6cca1cf7b47f38e3515b5c94246456b485091d3d72c0be5c
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,32 @@
|
|
1
|
+
## 1.0.1 - 2024-11-05
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* [HexaPDF::Type::CMap] for representing CMap streams
|
6
|
+
|
7
|
+
### Fixed
|
8
|
+
|
9
|
+
* Checksum calculation for TrueType tables
|
10
|
+
* Automatic wrapping of dictionary entry /CIDToGIDMap for CID fonts
|
11
|
+
* Performance regression when encoding char codes for TrueType fonts
|
12
|
+
* PDF/A validation regression for PDFs using TrueType fonts
|
13
|
+
|
14
|
+
|
15
|
+
## 1.0.1 - 2024-11-04
|
16
|
+
|
17
|
+
### Changed
|
18
|
+
|
19
|
+
* Informational output on errors when running CLI commands to provide more
|
20
|
+
details
|
21
|
+
|
22
|
+
### Fixed
|
23
|
+
|
24
|
+
* Parsing of indirect objects the value of which is an indirect reference
|
25
|
+
* Writing of the initial cross-reference section to ensure a single subsection
|
26
|
+
* [HexaPDF::Utils::SortedTreeNode] to wrap all /Kids entries with the correct
|
27
|
+
type class
|
28
|
+
|
29
|
+
|
1
30
|
## 1.0.0 - 2024-10-26
|
2
31
|
|
3
32
|
### Added
|
data/lib/hexapdf/cli.rb
CHANGED
@@ -64,8 +64,21 @@ module HexaPDF
|
|
64
64
|
rescue StandardError => e
|
65
65
|
$stderr.puts "Problem encountered: #{e.message}"
|
66
66
|
unless e.kind_of?(HexaPDF::Error)
|
67
|
+
$stderr.puts "Backtrace (last 10 lines):"
|
68
|
+
$stderr.puts e.backtrace[0, 10]
|
69
|
+
$stderr.puts
|
67
70
|
$stderr.puts "--> The problem might indicate a faulty PDF or a bug in HexaPDF."
|
68
|
-
$stderr.puts "--> Please report this at
|
71
|
+
$stderr.puts "--> Please report this at"
|
72
|
+
$stderr.puts "-->"
|
73
|
+
$stderr.puts "--> https://github.com/gettalong/hexapdf/issues"
|
74
|
+
$stderr.puts "-->"
|
75
|
+
$stderr.puts "--> and include the information above as well as the output of running"
|
76
|
+
$stderr.puts "--> the following command on the input PDF:"
|
77
|
+
$stderr.puts "-->"
|
78
|
+
$stderr.puts "--> hexapdf info --check INPUT.PDF"
|
79
|
+
$stderr.puts "-->"
|
80
|
+
$stderr.puts "--> If possible, please also provide the input PDF."
|
81
|
+
$stderr.puts "--> Thanks!"
|
69
82
|
end
|
70
83
|
exit(1)
|
71
84
|
end
|
@@ -722,6 +722,7 @@ module HexaPDF
|
|
722
722
|
OutputIntent: 'HexaPDF::Type::OutputIntent',
|
723
723
|
XXDestOutputProfileRef: 'HexaPDF::Type::OutputIntent::DestOutputProfileRef',
|
724
724
|
ExData: 'HexaPDF::Type::Annotations::MarkupAnnotation::ExData',
|
725
|
+
CMap: 'HexaPDF::Type::CMap',
|
725
726
|
},
|
726
727
|
'object.subtype_map' => {
|
727
728
|
nil => {
|
@@ -63,16 +63,6 @@ module HexaPDF
|
|
63
63
|
def use_glyph(glyph_id)
|
64
64
|
return @glyph_map[glyph_id] if @glyph_map.key?(glyph_id)
|
65
65
|
@last_id += 1
|
66
|
-
# Handle codes for ASCII characters \r (13), (, ) (40, 41) and \ (92) specially so that
|
67
|
-
# they never appear in the output (PDF serialization would need to escape them)
|
68
|
-
if @last_id == 13 || @last_id == 40 || @last_id == 92
|
69
|
-
@glyph_map[:"s#{@last_id}"] = @last_id
|
70
|
-
if @last_id == 40
|
71
|
-
@last_id += 1
|
72
|
-
@glyph_map[:"s#{@last_id}"] = @last_id
|
73
|
-
end
|
74
|
-
@last_id += 1
|
75
|
-
end
|
76
66
|
@glyph_map[glyph_id] = @last_id
|
77
67
|
end
|
78
68
|
|
@@ -117,7 +107,7 @@ module HexaPDF
|
|
117
107
|
locations = []
|
118
108
|
|
119
109
|
@glyph_map.each_key do |old_gid|
|
120
|
-
glyph = orig_glyf[old_gid
|
110
|
+
glyph = orig_glyf[old_gid]
|
121
111
|
locations << table.size
|
122
112
|
data = glyph.raw_data
|
123
113
|
if glyph.compound?
|
@@ -176,10 +166,7 @@ module HexaPDF
|
|
176
166
|
# Adds the components of compound glyphs to the subset.
|
177
167
|
def add_glyph_components
|
178
168
|
glyf = @font[:glyf]
|
179
|
-
@glyph_map.keys.each
|
180
|
-
next if gid.kind_of?(Symbol)
|
181
|
-
glyf[gid].components&.each {|cgid| use_glyph(cgid) }
|
182
|
-
end
|
169
|
+
@glyph_map.keys.each {|gid| glyf[gid].components&.each {|cgid| use_glyph(cgid) } }
|
183
170
|
end
|
184
171
|
|
185
172
|
end
|
@@ -63,7 +63,12 @@ module HexaPDF
|
|
63
63
|
|
64
64
|
# Calculates the checksum for the given data.
|
65
65
|
def self.calculate_checksum(data)
|
66
|
-
|
66
|
+
checksum = 0
|
67
|
+
if (remainder_length = data.length % 4) != 0
|
68
|
+
checksum = (data[-remainder_length, remainder_length] << "\0" * (4 - remainder_length)).
|
69
|
+
unpack1('N')
|
70
|
+
end
|
71
|
+
checksum + data.unpack('N*').inject(0) {|sum, long| sum + long } % 2**32
|
67
72
|
end
|
68
73
|
|
69
74
|
# The TrueType font object associated with this table.
|
@@ -239,6 +239,11 @@ module HexaPDF
|
|
239
239
|
raise HexaPDF::MissingGlyphError.new(glyph) if glyph.kind_of?(InvalidGlyph)
|
240
240
|
@subsetter.use_glyph(glyph.id) if @subsetter
|
241
241
|
@last_char_code += 1
|
242
|
+
# Handle codes for ASCII characters \r (13), (, ) (40, 41) and \ (92) specially so that
|
243
|
+
# they never appear in the output (PDF serialization would need to escape them)
|
244
|
+
if @last_char_code == 13 || @last_char_code == 40 || @last_char_code == 92
|
245
|
+
@last_char_code += (@last_char_code == 40 ? 2 : 1)
|
246
|
+
end
|
242
247
|
[[@last_char_code].pack('n'), @last_char_code]
|
243
248
|
end)[0]
|
244
249
|
end
|
@@ -376,7 +381,11 @@ module HexaPDF
|
|
376
381
|
dict[:Encoding] = :'Identity-H'
|
377
382
|
else
|
378
383
|
stream = HexaPDF::StreamData.new { HexaPDF::Font::CMap.create_cid_cmap(mapping) }
|
379
|
-
stream_obj = document.add({
|
384
|
+
stream_obj = document.add({Type: :CMap,
|
385
|
+
CMapName: :Custom,
|
386
|
+
CIDSystemInfo: {Registry: "Adobe", Ordering: "Identity",
|
387
|
+
Supplement: 0},
|
388
|
+
}, stream: stream)
|
380
389
|
stream_obj.set_filter(:FlateDecode)
|
381
390
|
dict[:Encoding] = stream_obj
|
382
391
|
end
|
data/lib/hexapdf/parser.rb
CHANGED
@@ -116,7 +116,11 @@ module HexaPDF
|
|
116
116
|
"the values (#{xref_entry.oid},#{xref_entry.gen}) from the xref")
|
117
117
|
end
|
118
118
|
|
119
|
-
|
119
|
+
if obj.kind_of?(Reference)
|
120
|
+
@document.deref(obj)
|
121
|
+
else
|
122
|
+
@document.wrap(obj, oid: oid, gen: gen, stream: stream)
|
123
|
+
end
|
120
124
|
rescue HexaPDF::MalformedPDFError
|
121
125
|
reconstructed_revision.object(xref_entry) ||
|
122
126
|
@document.wrap(nil, oid: xref_entry.oid, gen: xref_entry.gen)
|
@@ -70,7 +70,7 @@ module HexaPDF
|
|
70
70
|
define_field :W, type: PDFArray
|
71
71
|
define_field :DW2, type: PDFArray, default: [880, -1100]
|
72
72
|
define_field :W2, type: PDFArray
|
73
|
-
define_field :CIDToGIDMap, type: [
|
73
|
+
define_field :CIDToGIDMap, type: [Stream, Symbol]
|
74
74
|
|
75
75
|
# Returns the unscaled width of the given CID in glyph units, or 0 if the width for the CID is
|
76
76
|
# missing.
|
@@ -0,0 +1,58 @@
|
|
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-2024 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/stream'
|
38
|
+
|
39
|
+
module HexaPDF
|
40
|
+
module Type
|
41
|
+
|
42
|
+
# Represents an embedded CMap file.
|
43
|
+
#
|
44
|
+
# See: PDF2.0 s9.7.5.3
|
45
|
+
class CMap < Stream
|
46
|
+
|
47
|
+
define_type :CMap
|
48
|
+
|
49
|
+
define_field :Type, type: Symbol, required: true, default: type
|
50
|
+
define_field :CMapName, type: Symbol, required: true
|
51
|
+
define_field :CIDSystemInfo, type: :XXCIDSystemInfo, required: true
|
52
|
+
define_field :WMode, type: Integer
|
53
|
+
define_field :UseCMap, type: [Stream, Symbol]
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
data/lib/hexapdf/type.rb
CHANGED
@@ -82,6 +82,7 @@ module HexaPDF
|
|
82
82
|
autoload(:OptionalContentConfiguration, 'hexapdf/type/optional_content_configuration')
|
83
83
|
autoload(:Metadata, 'hexapdf/type/metadata')
|
84
84
|
autoload(:OutputIntent, 'hexapdf/type/output_intent')
|
85
|
+
autoload(:CMap, 'hexapdf/type/cmap')
|
85
86
|
|
86
87
|
end
|
87
88
|
|
@@ -174,6 +174,7 @@ module HexaPDF
|
|
174
174
|
elsif node.key?(:Kids)
|
175
175
|
index = find_in_intermediate_node(node[:Kids], key)
|
176
176
|
node = node[:Kids][index]
|
177
|
+
node = document.wrap(node, type: self.class) if node
|
177
178
|
break unless node && key >= node[:Limits][0] && key <= node[:Limits][1]
|
178
179
|
else
|
179
180
|
break
|
@@ -194,7 +195,7 @@ module HexaPDF
|
|
194
195
|
container_name = leaf_node_container_name
|
195
196
|
stack = [self]
|
196
197
|
until stack.empty?
|
197
|
-
node = stack.pop
|
198
|
+
node = document.wrap(stack.pop, type: self.class)
|
198
199
|
if node.key?(container_name)
|
199
200
|
data = node[container_name]
|
200
201
|
index = 0
|
@@ -217,7 +218,7 @@ module HexaPDF
|
|
217
218
|
def path_to_key(node, key, stack)
|
218
219
|
return unless node.key?(:Kids)
|
219
220
|
index = find_in_intermediate_node(node[:Kids], key)
|
220
|
-
stack << node[:Kids][index]
|
221
|
+
stack << document.wrap(node[:Kids][index], type: self.class)
|
221
222
|
path_to_key(stack.last, key, stack)
|
222
223
|
end
|
223
224
|
|
@@ -307,6 +308,15 @@ module HexaPDF
|
|
307
308
|
super
|
308
309
|
container_name = leaf_node_container_name
|
309
310
|
|
311
|
+
if key?(:Kids)
|
312
|
+
self[:Kids].each do |kid|
|
313
|
+
unless kid.indirect?
|
314
|
+
yield("Children of sorted tree nodes must be indirect", true)
|
315
|
+
document.add(kid)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
310
320
|
# All keys of the container must be lexically ordered strings and the container must be
|
311
321
|
# correctly formatted
|
312
322
|
if key?(container_name)
|
data/lib/hexapdf/version.rb
CHANGED
data/lib/hexapdf/writer.rb
CHANGED
@@ -149,6 +149,7 @@ module HexaPDF
|
|
149
149
|
obj_to_stm = object_streams.each_with_object({}) {|stm, m| m.update(stm.write_objects(rev)) }
|
150
150
|
|
151
151
|
xref_section = XRefSection.new
|
152
|
+
xref_section.mark_as_initial_section! unless previous_xref_pos
|
152
153
|
xref_section.add_free_entry(0, 65535) if previous_xref_pos.nil?
|
153
154
|
rev.each do |obj|
|
154
155
|
if obj.null?
|
data/lib/hexapdf/xref_section.rb
CHANGED
@@ -111,6 +111,13 @@ module HexaPDF
|
|
111
111
|
# used.
|
112
112
|
private :'[]='
|
113
113
|
|
114
|
+
# Marks this XRefSection object as being the first cross-reference section in a PDF file.
|
115
|
+
#
|
116
|
+
# This has the consequence that only a single sub-section is created.
|
117
|
+
def mark_as_initial_section!
|
118
|
+
@initial_section = true
|
119
|
+
end
|
120
|
+
|
114
121
|
# Adds an in-use entry to the cross-reference section.
|
115
122
|
#
|
116
123
|
# See: ::in_use_entry
|
@@ -147,15 +154,24 @@ module HexaPDF
|
|
147
154
|
# If this section contains no objects, a single empty array is yielded (corresponding to a
|
148
155
|
# subsection with zero elements).
|
149
156
|
#
|
150
|
-
# The subsections are dynamically generated based on the object numbers in this section.
|
157
|
+
# The subsections are dynamically generated based on the object numbers in this section. In case
|
158
|
+
# the section was marked as the initial section (see #mark_as_initial_section!) only a single
|
159
|
+
# subsection is yielded.
|
151
160
|
def each_subsection
|
152
161
|
return to_enum(__method__) unless block_given?
|
153
162
|
|
154
163
|
temp = []
|
155
164
|
oids.sort.each do |oid|
|
156
|
-
|
157
|
-
|
158
|
-
|
165
|
+
expected_next_oid = !temp.empty? && temp[-1].oid + 1
|
166
|
+
if expected_next_oid && expected_next_oid != oid
|
167
|
+
if @initial_section
|
168
|
+
expected_next_oid.upto(oid - 1) do |free_oid|
|
169
|
+
temp << self.class.free_entry(free_oid, 0)
|
170
|
+
end
|
171
|
+
else
|
172
|
+
yield(temp)
|
173
|
+
temp = []
|
174
|
+
end
|
159
175
|
end
|
160
176
|
temp << self[oid]
|
161
177
|
end
|
@@ -119,6 +119,11 @@ describe HexaPDF::Font::TrueTypeWrapper do
|
|
119
119
|
assert_equal([3].pack('n'), code)
|
120
120
|
end
|
121
121
|
|
122
|
+
it "doesn't use char codes 13, 40, 41 and 92 because they would need to be escaped" do
|
123
|
+
codes = 1.upto(93).map {|i| @font_wrapper.encode(@font_wrapper.glyph(i)) }.join
|
124
|
+
assert_equal([1..12, 14..39, 42..91, 93..97].flat_map(&:to_a).pack('n*'), codes)
|
125
|
+
end
|
126
|
+
|
122
127
|
it "raises an error if an InvalidGlyph is encoded" do
|
123
128
|
exp = assert_raises(HexaPDF::MissingGlyphError) do
|
124
129
|
@font_wrapper.encode(@font_wrapper.decode_utf8("ö").first)
|
@@ -27,16 +27,6 @@ describe HexaPDF::Font::TrueType::Subsetter do
|
|
27
27
|
assert_equal(value, @subsetter.subset_glyph_id(5))
|
28
28
|
end
|
29
29
|
|
30
|
-
it "doesn't use certain subset glyph IDs for performance reasons" do
|
31
|
-
1.upto(93) {|i| @subsetter.use_glyph(i) }
|
32
|
-
# glyph 0, 93 used glyph, 4 special glyphs
|
33
|
-
assert_equal(1 + 93 + 4, @subsetter.instance_variable_get(:@glyph_map).size)
|
34
|
-
1.upto(12) {|i| assert_equal(i, @subsetter.subset_glyph_id(i), "id=#{i}") }
|
35
|
-
13.upto(38) {|i| assert_equal(i + 1, @subsetter.subset_glyph_id(i), "id=#{i}") }
|
36
|
-
39.upto(88) {|i| assert_equal(i + 3, @subsetter.subset_glyph_id(i), "id=#{i}") }
|
37
|
-
89.upto(93) {|i| assert_equal(i + 4, @subsetter.subset_glyph_id(i), "id=#{i}") }
|
38
|
-
end
|
39
|
-
|
40
30
|
it "creates the subset font file" do
|
41
31
|
gid = @font[:cmap].preferred_table[0x41]
|
42
32
|
@subsetter.use_glyph(gid)
|
@@ -13,6 +13,18 @@ describe HexaPDF::Font::TrueType::Table do
|
|
13
13
|
@entry = HexaPDF::Font::TrueType::Table::Directory::Entry.new('tagg', 0, 0, @file.io.string.length)
|
14
14
|
end
|
15
15
|
|
16
|
+
describe "self.calculate_checksum" do
|
17
|
+
it "works for data with a length divisible by four" do
|
18
|
+
klass = HexaPDF::Font::TrueType::Table
|
19
|
+
assert_equal(256, klass.calculate_checksum("\x00\x00\x00\x01\x00\x00\x00\xFF"))
|
20
|
+
end
|
21
|
+
|
22
|
+
it "works for data with a length not divisible by four" do
|
23
|
+
klass = HexaPDF::Font::TrueType::Table
|
24
|
+
assert_equal(512, klass.calculate_checksum("\x00\x00\x00\x01\x00\x00\x00\xFF\x00\x00\x01"))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
16
28
|
describe "initialize" do
|
17
29
|
it "reads the data from the associated file" do
|
18
30
|
table = TrueTypeTestTable.new(@file, @entry)
|
data/test/hexapdf/test_parser.rb
CHANGED
@@ -33,18 +33,23 @@ describe HexaPDF::Parser do
|
|
33
33
|
endstream
|
34
34
|
endobj
|
35
35
|
|
36
|
+
5 0 obj
|
37
|
+
1 0 R
|
38
|
+
endobj
|
39
|
+
|
36
40
|
xref
|
37
41
|
0 4
|
38
42
|
0000000000 65535 f
|
39
43
|
0000000010 00000 n
|
40
44
|
0000000029 00000 n
|
41
45
|
0000000000 65535 f
|
42
|
-
3
|
46
|
+
3 2
|
43
47
|
0000000556 00000 n
|
48
|
+
0000000308 00000 n
|
44
49
|
trailer
|
45
50
|
<< /Test (now) >>
|
46
51
|
startxref
|
47
|
-
|
52
|
+
330
|
48
53
|
%%EOF
|
49
54
|
EOF
|
50
55
|
end
|
@@ -305,6 +310,11 @@ describe HexaPDF::Parser do
|
|
305
310
|
assert_equal(0, obj.gen)
|
306
311
|
end
|
307
312
|
|
313
|
+
it "handles the case of the value of an indirect object being an indirect reference" do
|
314
|
+
obj = @parser.load_object(HexaPDF::XRefSection.in_use_entry(5, 0, 308))
|
315
|
+
assert_equal(1, obj.oid)
|
316
|
+
end
|
317
|
+
|
308
318
|
describe "with strict parsing" do
|
309
319
|
it "raises an error if an indirect object has an offset of 0" do
|
310
320
|
@document.config['parser.on_correctable_error'] = proc { true }
|
@@ -343,13 +353,13 @@ describe HexaPDF::Parser do
|
|
343
353
|
|
344
354
|
describe "startxref_offset" do
|
345
355
|
it "caches the offset value" do
|
346
|
-
assert_equal(
|
347
|
-
@parser.instance_eval { @io }.string.sub!(/
|
348
|
-
assert_equal(
|
356
|
+
assert_equal(330, @parser.startxref_offset)
|
357
|
+
@parser.instance_eval { @io }.string.sub!(/330\n/, "309\n")
|
358
|
+
assert_equal(330, @parser.startxref_offset)
|
349
359
|
end
|
350
360
|
|
351
361
|
it "returns the correct offset" do
|
352
|
-
assert_equal(
|
362
|
+
assert_equal(330, @parser.startxref_offset)
|
353
363
|
end
|
354
364
|
|
355
365
|
it "ignores garbage at the end of the file" do
|
data/test/hexapdf/test_writer.rb
CHANGED
@@ -53,8 +53,8 @@ describe HexaPDF::Writer do
|
|
53
53
|
EOF
|
54
54
|
|
55
55
|
xref_stream = case HexaPDF::VERSION.length
|
56
|
-
when 5 then "x\
|
57
|
-
when 6 then "x\
|
56
|
+
when 5 then "x\xDAcbdlg``b`\xB0\x04\x93\x93\x19\x18\x00\f\x1E\x01\\"
|
57
|
+
when 6 then "x\xDAcbd\xEC```b`\xB0\x04\x93\x93\x18\x18\x00\f*\x01\\"
|
58
58
|
else fail
|
59
59
|
end
|
60
60
|
@compressed_input_io = StringIO.new(<<~EOF.force_encoding(Encoding::BINARY))
|
@@ -69,8 +69,8 @@ describe HexaPDF::Writer do
|
|
69
69
|
20
|
70
70
|
endobj
|
71
71
|
3 0 obj
|
72
|
-
<</Size 6/Type/XRef/W[1 1 2]/Index[0
|
73
|
-
x\xDAcb`\xF8\xFF\x9F\x89\x89\x95\x91\x91\xE9\x7F\x19\x03\x03\x13\x83\x10\
|
72
|
+
<</Size 6/Type/XRef/W[1 1 2]/Index[0 6]/Filter/FlateDecode/DecodeParms<</Columns 4/Predictor 12>>/Length 36>>stream
|
73
|
+
x\xDAcb`\xF8\xFF\x9F\x89\x89\x95\x91\x91\xE9\x7F\x19\x03\x03\x13\x83\x10\x90\xF8_\f\x14c\x14bd\x04\x00lk\a
|
74
74
|
endstream
|
75
75
|
endobj
|
76
76
|
startxref
|
@@ -90,7 +90,7 @@ describe HexaPDF::Writer do
|
|
90
90
|
endstream
|
91
91
|
endobj
|
92
92
|
startxref
|
93
|
-
#{
|
93
|
+
#{443 + HexaPDF::VERSION.length}
|
94
94
|
%%EOF
|
95
95
|
EOF
|
96
96
|
end
|
@@ -57,5 +57,20 @@ describe HexaPDF::XRefSection do
|
|
57
57
|
@xref_section.add_in_use_entry(20, 0, 0)
|
58
58
|
assert_subsections([[1, 2], [10, 11], [20]])
|
59
59
|
end
|
60
|
+
|
61
|
+
it "yields a single subsection if the section was marked as the initial one" do
|
62
|
+
@xref_section.mark_as_initial_section!
|
63
|
+
@xref_section.add_in_use_entry(6, 0, 0)
|
64
|
+
@xref_section.add_in_use_entry(7, 0, 0)
|
65
|
+
@xref_section.add_in_use_entry(9, 0, 0)
|
66
|
+
@xref_section.add_in_use_entry(1, 0, 0)
|
67
|
+
@xref_section.add_in_use_entry(2, 0, 0)
|
68
|
+
result = @xref_section.each_subsection.map {|s| s.map {|e| [e.oid, e.type] }}
|
69
|
+
assert_equal([[[1, :in_use], [2, :in_use],
|
70
|
+
[3, :free], [4, :free], [5, :free],
|
71
|
+
[6, :in_use], [7, :in_use],
|
72
|
+
[8, :free],
|
73
|
+
[9, :in_use]]], result)
|
74
|
+
end
|
60
75
|
end
|
61
76
|
end
|
@@ -12,10 +12,12 @@ describe HexaPDF::Utils::SortedTreeNode do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def add_multilevel_entries
|
15
|
-
|
15
|
+
item = @doc.add(1)
|
16
|
+
@item_ref = HexaPDF::Reference.new(item.oid, item.gen)
|
17
|
+
@kid11 = @doc.add({Limits: ['c', 'f'], Names: ['c', @item_ref, 'f', 1]}, type: HexaPDF::NameTreeNode)
|
16
18
|
@kid12 = @doc.add({Limits: ['i', 'm'], Names: ['i', 1, 'm', 1]}, type: HexaPDF::NameTreeNode)
|
17
19
|
ref = HexaPDF::Reference.new(@kid11.oid, @kid11.gen)
|
18
|
-
@kid1 = @doc.add({Limits: ['c', 'm'], Kids: [ref, @kid12]}
|
20
|
+
@kid1 = @doc.add({Limits: ['c', 'm'], Kids: [ref, @kid12]})
|
19
21
|
@kid21 = @doc.add({Limits: ['o', 'q'], Names: ['o', 1, 'q', 1]}, type: HexaPDF::NameTreeNode)
|
20
22
|
@kid221 = @doc.add({Limits: ['s', 'u'], Names: ['s', 1, 'u', 1]}, type: HexaPDF::NameTreeNode)
|
21
23
|
@kid22 = @doc.add({Limits: ['s', 'u'], Kids: [@kid221]}, type: HexaPDF::NameTreeNode)
|
@@ -75,7 +77,7 @@ describe HexaPDF::Utils::SortedTreeNode do
|
|
75
77
|
@root.add_entry('v', 1)
|
76
78
|
assert_equal(['a', 'm'], @kid1[:Limits].value)
|
77
79
|
assert_equal(['a', 'f'], @kid11[:Limits].value)
|
78
|
-
assert_equal(['a', 1, 'c',
|
80
|
+
assert_equal(['a', 1, 'c', @item_ref, 'e', 1, 'f', 1], @kid11[:Names].value)
|
79
81
|
assert_equal(['g', 'm'], @kid12[:Limits].value)
|
80
82
|
assert_equal(['g', 1, 'i', 1, 'j', 1, 'm', 1], @kid12[:Names].value)
|
81
83
|
assert_equal(['n', 'v'], @kid2[:Limits].value)
|
@@ -203,13 +205,12 @@ describe HexaPDF::Utils::SortedTreeNode do
|
|
203
205
|
end
|
204
206
|
|
205
207
|
it "checks that all kid objects are indirect objects" do
|
206
|
-
@root[:Kids][0] = ref = HexaPDF::Reference.new(@kid1.oid, @kid1.gen)
|
207
208
|
assert(@root.validate)
|
208
209
|
|
209
|
-
@root[:Kids][0] =
|
210
|
+
@root[:Kids][0] = @kid1
|
210
211
|
@kid1.oid = 0
|
211
212
|
assert(@root.validate do |message, c|
|
212
|
-
assert_match(/must be
|
213
|
+
assert_match(/children.*must be indirect/i, message)
|
213
214
|
assert(c)
|
214
215
|
end)
|
215
216
|
assert(@kid1.indirect?)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hexapdf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Leitner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cmdparse
|
@@ -500,6 +500,7 @@ files:
|
|
500
500
|
- lib/hexapdf/type/annotations/widget.rb
|
501
501
|
- lib/hexapdf/type/catalog.rb
|
502
502
|
- lib/hexapdf/type/cid_font.rb
|
503
|
+
- lib/hexapdf/type/cmap.rb
|
503
504
|
- lib/hexapdf/type/embedded_file.rb
|
504
505
|
- lib/hexapdf/type/file_specification.rb
|
505
506
|
- lib/hexapdf/type/font.rb
|