hexapdf 1.0.1 → 1.0.3
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 +24 -0
- data/lib/hexapdf/cli/inspect.rb +5 -2
- data/lib/hexapdf/configuration.rb +1 -0
- data/lib/hexapdf/encryption/arc4.rb +2 -2
- 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/test_utils.rb +2 -1
- data/lib/hexapdf/type/acro_form/form.rb +1 -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/version.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +3 -3
- data/test/hexapdf/encryption/common.rb +1 -1
- data/test/hexapdf/encryption/test_aes.rb +1 -1
- data/test/hexapdf/encryption/test_arc4.rb +2 -2
- data/test/hexapdf/encryption/test_security_handler.rb +1 -1
- data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
- data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
- data/test/hexapdf/filter/test_flate_decode.rb +2 -3
- data/test/hexapdf/font/cmap/test_writer.rb +2 -2
- data/test/hexapdf/font/encoding/test_glyph_list.rb +1 -1
- data/test/hexapdf/font/test_true_type_wrapper.rb +7 -2
- data/test/hexapdf/font/test_type1_wrapper.rb +1 -1
- 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/task/test_merge_acro_form.rb +1 -1
- data/test/hexapdf/test_filter.rb +1 -1
- data/test/hexapdf/test_parser.rb +10 -10
- data/test/hexapdf/test_revisions.rb +1 -1
- data/test/hexapdf/test_serializer.rb +2 -3
- data/test/hexapdf/test_tokenizer.rb +1 -1
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_form.rb +8 -0
- data/test/hexapdf/type/test_image.rb +1 -1
- data/test/hexapdf/type/test_page_tree_node.rb +2 -2
- 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: 7a94d8744657f89cf855604bdb363426637190a5db9615bbcb78e033f9aa5b0f
|
4
|
+
data.tar.gz: b5ea3789c402ce1affb937eca574aba6c3cd21864484f56015aa2aef4acb9b86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c7a881cc83213116e3f818c4df6063a9ebc758fe79d4120e5163d7bd78509ad358a32c122db0ffdfd6b1def50a6ec53a83e429ece1355aff02751b3a886e9a3
|
7
|
+
data.tar.gz: eb82b47d523c96403fd64afcbd5a3a867b74e059890819c816dadd71b8fac0595a431c9ff926dea8a45c2d949e8802fa9a2dccb776a24acb04922f677cac2b66
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,27 @@
|
|
1
|
+
## 1.0.3 - 2024-12-04
|
2
|
+
|
3
|
+
### Fixed
|
4
|
+
|
5
|
+
* Offsets and lengths of revisions shown using the `inspect rev` CLI command for
|
6
|
+
linearized PDF files
|
7
|
+
* [HexaPDF::Type::AcroForm::Form#recalculate_fields] to only consider real
|
8
|
+
fields
|
9
|
+
|
10
|
+
|
11
|
+
## 1.0.2 - 2024-11-05
|
12
|
+
|
13
|
+
### Added
|
14
|
+
|
15
|
+
* [HexaPDF::Type::CMap] for representing CMap streams
|
16
|
+
|
17
|
+
### Fixed
|
18
|
+
|
19
|
+
* Checksum calculation for TrueType tables
|
20
|
+
* Automatic wrapping of dictionary entry /CIDToGIDMap for CID fonts
|
21
|
+
* Performance regression when encoding char codes for TrueType fonts
|
22
|
+
* PDF/A validation regression for PDFs using TrueType fonts
|
23
|
+
|
24
|
+
|
1
25
|
## 1.0.1 - 2024-11-04
|
2
26
|
|
3
27
|
### Changed
|
data/lib/hexapdf/cli/inspect.rb
CHANGED
@@ -395,9 +395,12 @@ module HexaPDF
|
|
395
395
|
end
|
396
396
|
io = @doc.revisions.parser.io
|
397
397
|
|
398
|
-
startxrefs = @doc.revisions.map {|rev| rev.trailer[:Prev] }
|
399
398
|
io.seek(0, IO::SEEK_END)
|
400
|
-
startxrefs
|
399
|
+
startxrefs = @doc.revisions.map {|rev| rev.trailer[:Prev] } <<
|
400
|
+
@doc.revisions.parser.startxref_offset <<
|
401
|
+
io.pos
|
402
|
+
startxrefs.sort!
|
403
|
+
startxrefs.shift
|
401
404
|
|
402
405
|
@doc.revisions.each_with_index.map do |rev, index|
|
403
406
|
end_index = 0
|
@@ -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 => {
|
@@ -66,14 +66,14 @@ module HexaPDF
|
|
66
66
|
# Encrypts the given +data+ with the +key+.
|
67
67
|
#
|
68
68
|
# See: PDF2.0 s7.6.3
|
69
|
-
def encrypt(key, data)
|
69
|
+
def encrypt(key, data, &_block)
|
70
70
|
new(key).process(data)
|
71
71
|
end
|
72
72
|
alias decrypt encrypt
|
73
73
|
|
74
74
|
# Returns a Fiber object that encrypts the data from the given source fiber with the
|
75
75
|
# +key+.
|
76
|
-
def encryption_fiber(key, source)
|
76
|
+
def encryption_fiber(key, source, &_block)
|
77
77
|
Fiber.new do
|
78
78
|
algorithm = new(key)
|
79
79
|
while source.alive? && (data = source.resume)
|
@@ -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/test_utils.rb
CHANGED
@@ -92,8 +92,9 @@ module HexaPDF
|
|
92
92
|
# Creates a fiber that yields the given string in +len+ length parts.
|
93
93
|
def feeder(string, len = string.length)
|
94
94
|
Fiber.new do
|
95
|
+
string = string.b
|
95
96
|
until string.empty?
|
96
|
-
Fiber.yield(string.slice!(0, len)
|
97
|
+
Fiber.yield(string.slice!(0, len))
|
97
98
|
end
|
98
99
|
end
|
99
100
|
end
|
@@ -517,7 +517,7 @@ module HexaPDF
|
|
517
517
|
#
|
518
518
|
# See: JavaScriptActions
|
519
519
|
def recalculate_fields
|
520
|
-
self[:CO]
|
520
|
+
(each_field.to_a & self[:CO].to_a).each do |field|
|
521
521
|
field = Field.wrap(document, field)
|
522
522
|
next unless field && (calculation_action = field[:AA]&.[](:C))
|
523
523
|
result = JavaScriptActions.calculate(self, calculation_action)
|
@@ -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
|
|
data/lib/hexapdf/version.rb
CHANGED
@@ -65,7 +65,7 @@ module CommonTokenizerTests
|
|
65
65
|
:'The_Key_of_F#_Minor', :AB, :"",
|
66
66
|
'[', 5, 6, :Name, ']', '[', 5, 6, :Name, ']',
|
67
67
|
'<<', :Name, 5, '>>'
|
68
|
-
].
|
68
|
+
].map {|t| t.respond_to?(:force_encoding) ? t.b : t }
|
69
69
|
|
70
70
|
until expected_tokens.empty?
|
71
71
|
expected_token = expected_tokens.shift
|
@@ -127,7 +127,7 @@ module CommonTokenizerTests
|
|
127
127
|
end
|
128
128
|
|
129
129
|
it "next_token: should not fail when reading super long numbers" do
|
130
|
-
create_tokenizer("1"
|
130
|
+
create_tokenizer("1" + "0" * 10_000)
|
131
131
|
assert_equal(10**10_000, @tokenizer.next_token)
|
132
132
|
end
|
133
133
|
|
@@ -182,7 +182,7 @@ module CommonTokenizerTests
|
|
182
182
|
end
|
183
183
|
|
184
184
|
it "returns the correct position on operations" do
|
185
|
-
create_tokenizer("hallo du"
|
185
|
+
create_tokenizer("hallo du" + " " * 50000 + "hallo du")
|
186
186
|
@tokenizer.next_token
|
187
187
|
assert_equal(5, @tokenizer.pos)
|
188
188
|
|
@@ -67,7 +67,7 @@ module ARC4EncryptionTests
|
|
67
67
|
super
|
68
68
|
@encrypted = ['BBF316E8D940AF0AD3', '1021BF0420', '45A01F645FC35B383552544B9BF5'].
|
69
69
|
map {|c| [c].pack('H*') }
|
70
|
-
@plain = ['Plaintext', 'pedia', 'Attack at dawn']
|
70
|
+
@plain = ['Plaintext'.b, 'pedia'.b, 'Attack at dawn'.b]
|
71
71
|
@keys = ['Key', 'Wiki', 'Secret']
|
72
72
|
end
|
73
73
|
|
@@ -141,7 +141,7 @@ describe HexaPDF::Encryption::AES do
|
|
141
141
|
collector(@algorithm_class.decryption_fiber('some' * 4, Fiber.new { 'a' * 40 }))
|
142
142
|
end
|
143
143
|
assert_raises(HexaPDF::EncryptionError) do
|
144
|
-
collector(@algorithm_class.decryption_fiber('some' * 4, Fiber.new { 'a' * 40 })
|
144
|
+
collector(@algorithm_class.decryption_fiber('some' * 4, Fiber.new { 'a' * 40 }) { true })
|
145
145
|
end
|
146
146
|
end
|
147
147
|
end
|
@@ -11,13 +11,13 @@ describe HexaPDF::Encryption::ARC4 do
|
|
11
11
|
prepend HexaPDF::Encryption::ARC4
|
12
12
|
|
13
13
|
def initialize(key)
|
14
|
-
@data = key
|
14
|
+
@data = +key
|
15
15
|
end
|
16
16
|
|
17
17
|
def process(data)
|
18
18
|
raise if data.empty?
|
19
19
|
result = @data << data
|
20
|
-
@data = ''
|
20
|
+
@data = +''
|
21
21
|
result
|
22
22
|
end
|
23
23
|
end
|
@@ -236,7 +236,7 @@ describe HexaPDF::Encryption::SecurityHandler do
|
|
236
236
|
dict[:Filter] = :Test
|
237
237
|
@enc.strf = alg
|
238
238
|
@enc.set_up_encryption(key_length: length, algorithm: (alg == :identity ? :aes : alg))
|
239
|
-
@obj[:X] = @enc.encrypt_string('data', @obj)
|
239
|
+
@obj[:X] = @enc.encrypt_string(+'data', @obj)
|
240
240
|
@handler.set_up_decryption(dict)
|
241
241
|
assert_equal('data', @handler.decrypt(@obj)[:X])
|
242
242
|
end
|
@@ -33,7 +33,7 @@ describe HexaPDF::Filter::ASCII85Decode do
|
|
33
33
|
end
|
34
34
|
|
35
35
|
it "ignores data after the EOD marker" do
|
36
|
-
assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded
|
36
|
+
assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded + "~>abcdefg"))))
|
37
37
|
end
|
38
38
|
|
39
39
|
it "fails if the input contains invalid characters" do
|
@@ -24,7 +24,7 @@ describe HexaPDF::Filter::ASCIIHexDecode do
|
|
24
24
|
end
|
25
25
|
|
26
26
|
it "ignores data after the EOD marker" do
|
27
|
-
assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded
|
27
|
+
assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded + '4e6f7gzz'))))
|
28
28
|
end
|
29
29
|
|
30
30
|
it "assumes the missing char is '0' if the input length is odd" do
|
@@ -8,11 +8,10 @@ describe HexaPDF::Filter::FlateDecode do
|
|
8
8
|
|
9
9
|
before do
|
10
10
|
@obj = HexaPDF::Filter::FlateDecode
|
11
|
-
@all_test_cases = [["abcdefg".
|
12
|
-
"x\xDAKLJNIMK\a\x00\n\xDB\x02\xBD".force_encoding(Encoding::BINARY)]]
|
11
|
+
@all_test_cases = [["abcdefg".b, "x\xDAKLJNIMK\a\x00\n\xDB\x02\xBD".b]]
|
13
12
|
@decoded = @all_test_cases[0][0]
|
14
13
|
@encoded = @all_test_cases[0][1]
|
15
|
-
@encoded_predictor = "x\xDAcJdbD@\x00\x05\x8F\x00v".
|
14
|
+
@encoded_predictor = "x\xDAcJdbD@\x00\x05\x8F\x00v".b
|
16
15
|
@predictor_opts = {Predictor: 12}
|
17
16
|
end
|
18
17
|
|
@@ -5,7 +5,7 @@ require 'hexapdf/font/cmap/writer'
|
|
5
5
|
|
6
6
|
describe HexaPDF::Font::CMap::Writer do
|
7
7
|
before do
|
8
|
-
@to_unicode_cmap_data =
|
8
|
+
@to_unicode_cmap_data = +<<~EOF
|
9
9
|
/CIDInit /ProcSet findresource begin
|
10
10
|
12 dict begin
|
11
11
|
begincmap
|
@@ -32,7 +32,7 @@ describe HexaPDF::Font::CMap::Writer do
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
EOF
|
35
|
-
@cid_cmap_data =
|
35
|
+
@cid_cmap_data = +<<~EOF
|
36
36
|
%!PS-Adobe-3.0 Resource-CMap
|
37
37
|
%%DocumentNeededResources: ProcSet (CIDInit)
|
38
38
|
%%IncludeResource: ProcSet (CIDInit)
|
@@ -32,7 +32,7 @@ describe HexaPDF::Font::Encoding::GlyphList do
|
|
32
32
|
|
33
33
|
it "maps special uXXXX[XX] names to unicode values" do
|
34
34
|
assert_equal("A", @list.name_to_unicode(:u0041))
|
35
|
-
assert_equal(
|
35
|
+
assert_equal(+'' << "1F000".hex, @list.name_to_unicode(:u1F000))
|
36
36
|
end
|
37
37
|
|
38
38
|
it "maps Zapf Dingbats glyph names to their unicode" do
|
@@ -51,7 +51,7 @@ describe HexaPDF::Font::TrueTypeWrapper do
|
|
51
51
|
glyphs = @font_wrapper.decode_utf8("😁")
|
52
52
|
assert_equal(1, glyphs.length)
|
53
53
|
assert_kind_of(HexaPDF::Font::InvalidGlyph, glyphs.first)
|
54
|
-
assert_equal('' << 128_513, glyphs.first.str)
|
54
|
+
assert_equal(+'' << 128_513, glyphs.first.str)
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
@@ -81,7 +81,7 @@ describe HexaPDF::Font::TrueTypeWrapper do
|
|
81
81
|
glyph = @font_wrapper.glyph(9999)
|
82
82
|
assert_kind_of(HexaPDF::Font::InvalidGlyph, glyph)
|
83
83
|
assert_equal(0, glyph.id)
|
84
|
-
assert_equal('' << 0xFFFD, glyph.str)
|
84
|
+
assert_equal(+'' << 0xFFFD, glyph.str)
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
@@ -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)
|
@@ -56,7 +56,7 @@ describe HexaPDF::Font::Type1Wrapper do
|
|
56
56
|
glyphs = @times_wrapper.decode_utf8("😁")
|
57
57
|
assert_equal(1, glyphs.length)
|
58
58
|
assert_kind_of(HexaPDF::Font::InvalidGlyph, glyphs.first)
|
59
|
-
assert_equal('' << 128_513, glyphs.first.str)
|
59
|
+
assert_equal(+'' << 128_513, glyphs.first.str)
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
@@ -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)
|
@@ -81,7 +81,7 @@ describe HexaPDF::Task::MergeAcroForm do
|
|
81
81
|
end
|
82
82
|
|
83
83
|
it "updates the /DA entries of widgets and fields" do
|
84
|
-
@pages[0][:Annots][0][:DA] = '/F1 10 Tf'
|
84
|
+
@pages[0][:Annots][0][:DA] = +'/F1 10 Tf'
|
85
85
|
@doc.task(:merge_acro_form, source: @doc1, pages: @pages)
|
86
86
|
field = @doc.acro_form.field_by_name('merged_1.Text')
|
87
87
|
assert_equal('0.0 g /F2 0 Tf', field[:DA])
|
data/test/hexapdf/test_filter.rb
CHANGED
data/test/hexapdf/test_parser.rb
CHANGED
@@ -11,7 +11,7 @@ describe HexaPDF::Parser do
|
|
11
11
|
@document.config['parser.try_xref_reconstruction'] = false
|
12
12
|
@document.add(@document.wrap(10, oid: 1, gen: 0))
|
13
13
|
|
14
|
-
create_parser(
|
14
|
+
create_parser(+<<~EOF)
|
15
15
|
%PDF-1.7
|
16
16
|
|
17
17
|
1 0 obj
|
@@ -354,7 +354,7 @@ describe HexaPDF::Parser do
|
|
354
354
|
describe "startxref_offset" do
|
355
355
|
it "caches the offset value" do
|
356
356
|
assert_equal(330, @parser.startxref_offset)
|
357
|
-
@parser.instance_eval { @io
|
357
|
+
@parser.instance_eval { @io.string = @io.string.sub(/330\n/, "309\n") }
|
358
358
|
assert_equal(330, @parser.startxref_offset)
|
359
359
|
end
|
360
360
|
|
@@ -363,7 +363,7 @@ describe HexaPDF::Parser do
|
|
363
363
|
end
|
364
364
|
|
365
365
|
it "ignores garbage at the end of the file" do
|
366
|
-
create_parser("startxref\n5\n%%EOF"
|
366
|
+
create_parser("startxref\n5\n%%EOF" + "\nhallo" * 150)
|
367
367
|
assert_equal(5, @parser.startxref_offset)
|
368
368
|
end
|
369
369
|
|
@@ -373,17 +373,17 @@ describe HexaPDF::Parser do
|
|
373
373
|
end
|
374
374
|
|
375
375
|
it "finds the startxref anywhere in file" do
|
376
|
-
create_parser("startxref\n5\n%%EOF"
|
376
|
+
create_parser("startxref\n5\n%%EOF" + "\nhallo" * 5000)
|
377
377
|
assert_equal(5, @parser.startxref_offset)
|
378
378
|
end
|
379
379
|
|
380
380
|
it "handles the case where %%EOF is the on the 1. line of the 1024 byte search block" do
|
381
|
-
create_parser("startxref\n5\n%%EOF\n"
|
381
|
+
create_parser("startxref\n5\n%%EOF\n" + "h" * 1018)
|
382
382
|
assert_equal(5, @parser.startxref_offset)
|
383
383
|
end
|
384
384
|
|
385
385
|
it "handles the case where %%EOF is the on the 2. line of the 1024 byte search block" do
|
386
|
-
create_parser("startxref\n5\n%%EOF\n"
|
386
|
+
create_parser("startxref\n5\n%%EOF\n" + "h" * 1017)
|
387
387
|
assert_equal(5, @parser.startxref_offset)
|
388
388
|
end
|
389
389
|
|
@@ -421,7 +421,7 @@ describe HexaPDF::Parser do
|
|
421
421
|
|
422
422
|
it "fails on strict parsing if the startxref is not in the last part of the file" do
|
423
423
|
@document.config['parser.on_correctable_error'] = proc { true }
|
424
|
-
create_parser("startxref\n5\n%%EOF"
|
424
|
+
create_parser("startxref\n5\n%%EOF" + "\nhallo" * 5000)
|
425
425
|
exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
|
426
426
|
assert_match(/end-of-file marker not found/, exp.message)
|
427
427
|
end
|
@@ -459,7 +459,7 @@ describe HexaPDF::Parser do
|
|
459
459
|
end
|
460
460
|
|
461
461
|
it "ignores junk at the beginning of the file and correctly calculates offset" do
|
462
|
-
create_parser("junk" * 200
|
462
|
+
create_parser("junk" * 200 + "\n%PDF-1.4\n")
|
463
463
|
assert_equal('1.4', @parser.file_header_version)
|
464
464
|
assert_equal(801, @parser.instance_variable_get(:@header_offset))
|
465
465
|
end
|
@@ -670,13 +670,13 @@ describe HexaPDF::Parser do
|
|
670
670
|
end
|
671
671
|
|
672
672
|
it "handles cases where the line contains an invalid string that exceeds the read buffer" do
|
673
|
-
create_parser("(1"
|
673
|
+
create_parser("(1" + "(abc" * 32188 + "\n1 0 obj\n6\nendobj\ntrailer\n<</Size 1>>")
|
674
674
|
assert_equal(6, @parser.load_object(@xref).value)
|
675
675
|
end
|
676
676
|
|
677
677
|
it "handles pathalogical cases which contain many opened literal strings" do
|
678
678
|
time = Time.now
|
679
|
-
create_parser("(1"
|
679
|
+
create_parser("(1" + "(abc\n" * 10000 + "\n1 0 obj\n6\nendobj\ntrailer\n<</Size 1>>")
|
680
680
|
assert_equal(6, @parser.load_object(@xref).value)
|
681
681
|
assert(Time.now - time < 0.5, "Xref reconstruction takes too long")
|
682
682
|
end
|
@@ -88,7 +88,7 @@ describe HexaPDF::Serializer do
|
|
88
88
|
assert_serialized('/The_Key_of_F#23_Minor', :'The_Key_of_F#_Minor')
|
89
89
|
assert_serialized('/ ', :"")
|
90
90
|
assert_serialized('/H#c3#b6#c3#9fgang', :Hößgang)
|
91
|
-
assert_serialized('/H#e8lp', "H\xE8lp".
|
91
|
+
assert_serialized('/H#e8lp', "H\xE8lp".b.intern)
|
92
92
|
assert_serialized('/#00#09#0a#0c#0d#20', :"\x00\t\n\f\r ")
|
93
93
|
end
|
94
94
|
|
@@ -105,8 +105,7 @@ describe HexaPDF::Serializer do
|
|
105
105
|
it "serializes strings" do
|
106
106
|
assert_serialized("(Hallo)", "Hallo")
|
107
107
|
assert_serialized("(Hallo\\r\n\t\\(\\)\\\\)", "Hallo\r\n\t()\\")
|
108
|
-
assert_serialized("(\xFE\xFF\x00H\x00a\x00l\x00\f\x00\b\x00\\()".
|
109
|
-
"Hal\f\b(")
|
108
|
+
assert_serialized("(\xFE\xFF\x00H\x00a\x00l\x00\f\x00\b\x00\\()".b, "Hal\f\b(")
|
110
109
|
end
|
111
110
|
|
112
111
|
it "serializes time like objects" do
|
@@ -38,7 +38,7 @@ describe HexaPDF::Tokenizer do
|
|
38
38
|
end
|
39
39
|
|
40
40
|
it "next_token: should not fail for strings due to use of an internal buffer" do
|
41
|
-
create_tokenizer("("
|
41
|
+
create_tokenizer("(" + ("a" * 8189) + "\\006)")
|
42
42
|
assert_equal("a" * 8189 << "\x06", @tokenizer.next_token)
|
43
43
|
end
|
44
44
|
|
data/test/hexapdf/test_writer.rb
CHANGED
@@ -98,7 +98,7 @@ describe HexaPDF::Writer do
|
|
98
98
|
def assert_document_conversion(input_io)
|
99
99
|
document = HexaPDF::Document.new(io: input_io)
|
100
100
|
document.trailer.info[:Producer] = "unknown"
|
101
|
-
output_io = StringIO.new(''.
|
101
|
+
output_io = StringIO.new(''.b)
|
102
102
|
start_xref_offset, xref_section = HexaPDF::Writer.write(document, output_io)
|
103
103
|
assert_kind_of(HexaPDF::XRefSection, xref_section)
|
104
104
|
assert_kind_of(Integer, start_xref_offset)
|
@@ -206,7 +206,7 @@ describe HexaPDF::Writer do
|
|
206
206
|
|
207
207
|
it "doesn't create an xref stream if one was just used for an XRefStm entry" do
|
208
208
|
# The following document's structure is built like a typical MS Word created PDF
|
209
|
-
input = StringIO.new(<<~EOF.
|
209
|
+
input = StringIO.new(<<~EOF.b)
|
210
210
|
%PDF-1.2
|
211
211
|
%\xCF\xEC\xFF\xE8\xD7\xCB\xCD
|
212
212
|
1 0 obj
|
@@ -494,6 +494,14 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
494
494
|
@acro_form.recalculate_fields
|
495
495
|
assert_equal("10", @text3.field_value)
|
496
496
|
end
|
497
|
+
|
498
|
+
it "ensures that only entries in /CO that are actually fields are used" do
|
499
|
+
@text1.field_value = "10"
|
500
|
+
@text3.set_calculate_action(:sfn, fields: 'text1')
|
501
|
+
@acro_form[:CO] = [nil, 5, [:some, :array], @doc.pages.root, @text3]
|
502
|
+
@acro_form.recalculate_fields
|
503
|
+
assert_equal("10", @text3.field_value)
|
504
|
+
end
|
497
505
|
end
|
498
506
|
|
499
507
|
describe "perform_validation" do
|
@@ -171,7 +171,7 @@ describe HexaPDF::Type::Image do
|
|
171
171
|
|
172
172
|
def assert_valid_png(filename, original = nil)
|
173
173
|
if PNG_CHECK_AVAILABLE
|
174
|
-
result = `pngcheck -q #{filename}`
|
174
|
+
result = `pngcheck -q #{filename} 2>/dev/null`
|
175
175
|
assert(result.empty?, "pngcheck error: #{result}")
|
176
176
|
else
|
177
177
|
skip("Skipping PNG output validity check because pngcheck executable is missing")
|
@@ -326,9 +326,9 @@ describe HexaPDF::Type::PageTreeNode do
|
|
326
326
|
assert(@root.validate(auto_correct: false) {|m, _| p m })
|
327
327
|
|
328
328
|
@doc.delete(@pages[3])
|
329
|
-
refute(@root.validate(auto_correct: false)
|
329
|
+
refute(@root.validate(auto_correct: false) do |msg, _|
|
330
330
|
assert_match(/invalid object/i, msg)
|
331
|
-
end
|
331
|
+
end)
|
332
332
|
assert(@root.validate)
|
333
333
|
assert_equal(2, @kid12[:Count])
|
334
334
|
assert_equal([@pages[2], @pages[4]], @kid12[:Kids].value)
|
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.3
|
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-12-04 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
|