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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/lib/hexapdf/cli/inspect.rb +5 -2
  4. data/lib/hexapdf/configuration.rb +1 -0
  5. data/lib/hexapdf/encryption/arc4.rb +2 -2
  6. data/lib/hexapdf/font/true_type/subsetter.rb +2 -15
  7. data/lib/hexapdf/font/true_type/table.rb +6 -1
  8. data/lib/hexapdf/font/true_type_wrapper.rb +10 -1
  9. data/lib/hexapdf/test_utils.rb +2 -1
  10. data/lib/hexapdf/type/acro_form/form.rb +1 -1
  11. data/lib/hexapdf/type/cid_font.rb +1 -1
  12. data/lib/hexapdf/type/cmap.rb +58 -0
  13. data/lib/hexapdf/type.rb +1 -0
  14. data/lib/hexapdf/version.rb +1 -1
  15. data/test/hexapdf/common_tokenizer_tests.rb +3 -3
  16. data/test/hexapdf/encryption/common.rb +1 -1
  17. data/test/hexapdf/encryption/test_aes.rb +1 -1
  18. data/test/hexapdf/encryption/test_arc4.rb +2 -2
  19. data/test/hexapdf/encryption/test_security_handler.rb +1 -1
  20. data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
  21. data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
  22. data/test/hexapdf/filter/test_flate_decode.rb +2 -3
  23. data/test/hexapdf/font/cmap/test_writer.rb +2 -2
  24. data/test/hexapdf/font/encoding/test_glyph_list.rb +1 -1
  25. data/test/hexapdf/font/test_true_type_wrapper.rb +7 -2
  26. data/test/hexapdf/font/test_type1_wrapper.rb +1 -1
  27. data/test/hexapdf/font/true_type/test_subsetter.rb +0 -10
  28. data/test/hexapdf/font/true_type/test_table.rb +12 -0
  29. data/test/hexapdf/task/test_merge_acro_form.rb +1 -1
  30. data/test/hexapdf/test_filter.rb +1 -1
  31. data/test/hexapdf/test_parser.rb +10 -10
  32. data/test/hexapdf/test_revisions.rb +1 -1
  33. data/test/hexapdf/test_serializer.rb +2 -3
  34. data/test/hexapdf/test_tokenizer.rb +1 -1
  35. data/test/hexapdf/test_writer.rb +2 -2
  36. data/test/hexapdf/type/acro_form/test_form.rb +8 -0
  37. data/test/hexapdf/type/test_image.rb +1 -1
  38. data/test/hexapdf/type/test_page_tree_node.rb +2 -2
  39. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 61b0fb56c6522f2af82eb8ffb10570c45bb11460cf4c048c1bdfe8d9daf71afe
4
- data.tar.gz: 91cb053019c367825ac0799a84e4ddad837fe283a6ab2bc6df16ee9ed9f2456d
3
+ metadata.gz: 7a94d8744657f89cf855604bdb363426637190a5db9615bbcb78e033f9aa5b0f
4
+ data.tar.gz: b5ea3789c402ce1affb937eca574aba6c3cd21864484f56015aa2aef4acb9b86
5
5
  SHA512:
6
- metadata.gz: 9a71ee1e9307f0ef67c9dec108c7f68db45166a62f9b6ec60915ce2c089cf0e9ec5bfcd8d74e8b31b63238a09c820a0798689a84e5ea0b1577e2492e5a1d425e
7
- data.tar.gz: b20043cead03f7fc7fe527fdbcb3674ab2d1da06b546bac9c1549b6eb6d143232453132709d93ae008d78a83bff36cf85fd0dbc0938da848e7847a1830e6011e
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
@@ -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.push(@doc.revisions.parser.startxref_offset, io.pos).shift
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.kind_of?(Symbol) ? 0 : 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 do |gid|
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
- data.unpack('N*').inject(0) {|sum, long| sum + long } % 2**32
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({}, stream: stream)
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
@@ -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).force_encoding('BINARY'))
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]&.each do |field|
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: [Symbol, Stream]
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
 
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '1.0.1'
40
+ VERSION = '1.0.3'
41
41
 
42
42
  end
@@ -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
- ].each {|t| t.force_encoding('BINARY') if t.respond_to?(:force_encoding) }
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" << "0" * 10_000)
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" << " " * 50000 << "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'].each {|s| s.force_encoding('BINARY') }
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 })) { true }
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 << "~>abcdefg"))))
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 << '4e6f7gzz'))))
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".force_encoding(Encoding::BINARY),
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".force_encoding(Encoding::BINARY)
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 = <<~EOF
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 = <<~EOF
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("" << "1F000".hex, @list.name_to_unicode(:u1F000))
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])
@@ -8,7 +8,7 @@ require 'tempfile'
8
8
  describe HexaPDF::Filter do
9
9
  before do
10
10
  @obj = HexaPDF::Filter
11
- @str = ''
11
+ @str = +''
12
12
  40.times { @str << [rand(2**32)].pack('N') }
13
13
  end
14
14
 
@@ -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(<<~EOF)
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 }.string.sub!(/330\n/, "309\n")
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" << "\nhallo" * 150)
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" << "\nhallo" * 5000)
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" << "h" * 1018)
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" << "h" * 1017)
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" << "\nhallo" * 5000)
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 << "\n%PDF-1.4\n")
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" << "(abc" * 32188 << "\n1 0 obj\n6\nendobj\ntrailer\n<</Size 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" << "(abc\n" * 10000 << "\n1 0 obj\n6\nendobj\ntrailer\n<</Size 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
@@ -359,7 +359,7 @@ describe HexaPDF::Revisions do
359
359
 
360
360
  describe "linearzied PDFs" do
361
361
  before do
362
- @io = StringIO.new(<<~EOF)
362
+ @io = StringIO.new(+<<~EOF)
363
363
  %PDF-1.2
364
364
  5 0 obj
365
365
  <</Linearized 1>>
@@ -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".force_encoding('BINARY').intern)
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\\()".force_encoding('BINARY'),
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("(" << ("a" * 8189) << "\\006)")
41
+ create_tokenizer("(" + ("a" * 8189) + "\\006)")
42
42
  assert_equal("a" * 8189 << "\x06", @tokenizer.next_token)
43
43
  end
44
44
 
@@ -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(''.force_encoding(Encoding::BINARY))
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.force_encoding(Encoding::BINARY))
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)) do |msg, _|
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.1
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-04 00:00:00.000000000 Z
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