hexapdf 1.0.0 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|