hexapdf 0.14.0 → 0.15.0

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +106 -0
  3. data/lib/hexapdf/cli/form.rb +30 -8
  4. data/lib/hexapdf/configuration.rb +19 -4
  5. data/lib/hexapdf/dictionary.rb +8 -2
  6. data/lib/hexapdf/dictionary_fields.rb +1 -1
  7. data/lib/hexapdf/encryption/security_handler.rb +7 -2
  8. data/lib/hexapdf/error.rb +4 -3
  9. data/lib/hexapdf/filter.rb +1 -0
  10. data/lib/hexapdf/filter/crypt.rb +60 -0
  11. data/lib/hexapdf/font/true_type/subsetter.rb +16 -3
  12. data/lib/hexapdf/font/true_type/table/post.rb +15 -10
  13. data/lib/hexapdf/font/type1/afm_parser.rb +2 -1
  14. data/lib/hexapdf/font_loader/from_configuration.rb +2 -2
  15. data/lib/hexapdf/font_loader/from_file.rb +18 -8
  16. data/lib/hexapdf/importer.rb +3 -2
  17. data/lib/hexapdf/parser.rb +55 -15
  18. data/lib/hexapdf/pdf_array.rb +4 -1
  19. data/lib/hexapdf/revision.rb +16 -0
  20. data/lib/hexapdf/serializer.rb +43 -10
  21. data/lib/hexapdf/tokenizer.rb +44 -3
  22. data/lib/hexapdf/type/acro_form.rb +1 -0
  23. data/lib/hexapdf/type/acro_form/appearance_generator.rb +32 -17
  24. data/lib/hexapdf/type/acro_form/button_field.rb +8 -4
  25. data/lib/hexapdf/type/acro_form/field.rb +1 -0
  26. data/lib/hexapdf/type/acro_form/form.rb +37 -0
  27. data/lib/hexapdf/type/acro_form/signature_field.rb +223 -0
  28. data/lib/hexapdf/type/annotation.rb +13 -9
  29. data/lib/hexapdf/type/annotations/widget.rb +3 -1
  30. data/lib/hexapdf/type/font_descriptor.rb +9 -2
  31. data/lib/hexapdf/type/page.rb +81 -0
  32. data/lib/hexapdf/type/resources.rb +4 -0
  33. data/lib/hexapdf/type/xref_stream.rb +7 -0
  34. data/lib/hexapdf/version.rb +1 -1
  35. data/test/hexapdf/encryption/test_security_handler.rb +15 -0
  36. data/test/hexapdf/filter/test_crypt.rb +21 -0
  37. data/test/hexapdf/font/true_type/table/test_post.rb +1 -1
  38. data/test/hexapdf/font/true_type/test_subsetter.rb +10 -0
  39. data/test/hexapdf/font/type1/test_afm_parser.rb +5 -0
  40. data/test/hexapdf/font_loader/test_from_configuration.rb +7 -3
  41. data/test/hexapdf/font_loader/test_from_file.rb +7 -0
  42. data/test/hexapdf/test_dictionary.rb +5 -0
  43. data/test/hexapdf/test_dictionary_fields.rb +7 -0
  44. data/test/hexapdf/test_parser.rb +94 -2
  45. data/test/hexapdf/test_revision.rb +21 -0
  46. data/test/hexapdf/test_serializer.rb +10 -0
  47. data/test/hexapdf/test_tokenizer.rb +50 -0
  48. data/test/hexapdf/test_writer.rb +2 -2
  49. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +24 -3
  50. data/test/hexapdf/type/acro_form/test_button_field.rb +13 -7
  51. data/test/hexapdf/type/acro_form/test_field.rb +5 -0
  52. data/test/hexapdf/type/acro_form/test_form.rb +46 -2
  53. data/test/hexapdf/type/acro_form/test_signature_field.rb +38 -0
  54. data/test/hexapdf/type/annotations/test_widget.rb +2 -0
  55. data/test/hexapdf/type/test_annotation.rb +20 -10
  56. data/test/hexapdf/type/test_font_descriptor.rb +7 -0
  57. data/test/hexapdf/type/test_page.rb +187 -49
  58. data/test/hexapdf/type/test_resources.rb +6 -0
  59. data/test/hexapdf/type/test_xref_stream.rb +7 -0
  60. metadata +7 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0cea16b918ff9aa6e7b32759295ef4ab38c899bcbd227d76ad42e0c971360239
4
- data.tar.gz: 932c5edf01114a59d0a64776f304e29f3c8865a2c2c52c340064180464aabad7
3
+ metadata.gz: bcd3bc77b70872416b1377b4fdf97804de083cf4d7213dfd200738fd8b2adae7
4
+ data.tar.gz: 53a8a850610a744570999cf56c656d6bd65c8ab691a5658b81172111bdd44804
5
5
  SHA512:
6
- metadata.gz: 5883c5788487830b0403459b38b4ed1761c1015688977e9823f3c572f1ad645b06eb0578b185ce26f7f02c560050dd8ec7c09e8524b59cd35df4fd6abd1fb4aa
7
- data.tar.gz: cdda51a089c86f27319fe424c9a74dc599ed60860338ef49958cd6a820141fa87a0624f2c657565e3f1b4a2392300807b89886178da6af62d22fa03fb543e372
6
+ metadata.gz: 54c99dbd44c4ae146496912f295982d47bb5ca297d4d2b76475c1f3151670068cd3be0c2dedee413b0a52e493383229bbd36d819ff2db2a9c04b377731cc107e
7
+ data.tar.gz: 8426e942709633b921f7a644e01b645d555cfcdecbdbd25bae03d0154cf07df3a3cd1f108adb16a8b449801c203014698a6b382133bbb63033a1d590351b61f7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,109 @@
1
+ ## 0.15.0 - 2021-04-12
2
+
3
+ ### Added
4
+
5
+ * [HexaPDF::Type::Page#flatten_annotations] for flattening the annotations of a
6
+ page
7
+ * [HexaPDF::Type::AcroForm::Form#flatten] for flattening interactive forms
8
+ * [HexaPDF::Revision#update] for updating the stored wrapper class of a PDF
9
+ object
10
+ * [HexaPDF::Type::AcroForm::SignatureField] for working with AcroForm signature
11
+ fields
12
+ * Support for form field flattening to the `hexapdf form` CLI command
13
+
14
+ ### Changed
15
+
16
+ * **Breaking change**: Overhauled the interface for accessing appearances of
17
+ annotations to make it more convenient
18
+ * Validation of [HexaPDF::Type::FontDescriptor] to delete invalid `/FontWeight`
19
+ value
20
+ * [HexaPDF::MalformedPDFError#pos] an accessor instead of a reader and update
21
+ the exception message
22
+ * Configuration option 'acro_form.fallback_font' to allow a callable object for
23
+ more advanced fallback font handling
24
+
25
+ ### Fixed
26
+
27
+ * [HexaPDF::Type::Annotations::Widget#background_color] to correctly handle
28
+ empty background color arrays
29
+ * [HexaPDF::Type::AcroForm::Field#delete_widget] to update the wrapper object
30
+ stored in the document in case the widget is embedded
31
+ * Processing of invalid PDF files containing a space,CR,LF combination after
32
+ the 'stream' keyword
33
+ * Cross-reference stream reconstruction with respect to detection of linearized
34
+ files
35
+ * Detection of existing appearances for AcroForm push button fields when
36
+ creating appearances
37
+
38
+
39
+ ## 0.14.4 - 2021-02-27
40
+
41
+ ### Added
42
+
43
+ * Support for the Crypt filters
44
+
45
+ ### Changed
46
+
47
+ * [HexaPDF::MalformedPDFError] to make the `pos` argument optional
48
+
49
+ ### Fixed
50
+
51
+ * Handling of invalid floating point numbers NaN, Inf and -Inf when serializing
52
+ * Processing of invalid PDF files containing NaN and Inf instead of numbers
53
+ * Bug in Type1 font AFM parser that occured if the file doesn't end with a new
54
+ line character
55
+ * Cross-reference table reconstruction to handle the case of an entry specifying
56
+ a non-existent indirect object
57
+ * Cross-reference table reconstruction to handle trailers specified by cross-
58
+ reference streams
59
+ * Cross-reference table reconstruction to use the set security handle for
60
+ decrypting indirect objects
61
+ * Parsing of cross-reference streams where data is missing
62
+
63
+
64
+ ## 0.14.3 - 2021-02-16
65
+
66
+ ### Fixed
67
+
68
+ * Bug in [HexaPDF::Font::TrueType::Subsetter#use_glyph] which lead to corrupt
69
+ text output
70
+ * [HexaPDF::Serializer] to handle infinite recursion problem
71
+ * Cross-reference table reconstruction to avoid an O(n^2) performance problem
72
+ * [HexaPDF::Type::Resources] validation to handle an invalid `/ProcSet` entry
73
+ containing a single value instead of an array
74
+ * Processing of invalid PDF files missing a required value in appearance streams
75
+ * Processing of invalid empty arrays that should be rectangles by converting
76
+ them to PDF null objects
77
+ * Processing of invalid PDF files containing indirect objects with offset 0
78
+ * Processing of invalid PDF files containing a space/CR or space/LF combination
79
+ after the 'stream' keyword
80
+
81
+
82
+ ## 0.14.2 - 2021-01-22
83
+
84
+ ### Fixed
85
+
86
+ * [HexaPDF::Font::TrueType::Subsetter#use_glyph] to really avoid using subset
87
+ glyph ID 41 (`)`)
88
+
89
+
90
+ ## 0.14.1 - 2021-01-21
91
+
92
+ ### Changed
93
+
94
+ * Validation message when checking for allowed values to include the invalid
95
+ object
96
+ * [HexaPDF::FontLoader::FromFile] to allow (re)using an existing font object
97
+ * [HexaPDF::Importer] internals to avoid problems with retained memory
98
+
99
+ ### Fixed
100
+
101
+ * Parsing of invalid PDF files where whitespace is missing after the integer
102
+ value of an indirect object
103
+ * [HexaPDF::Dictionary] so that adding new key-value pairs during validation is
104
+ possible
105
+
106
+
1
107
  ## 0.14.0 - 2020-12-30
2
108
 
3
109
  ### Added
@@ -52,18 +52,26 @@ module HexaPDF
52
52
  If the the output file name is not given, all form fields are listed in page order. Use
53
53
  the global --verbose option to show additional information like field type and location.
54
54
 
55
- If the output file name is given, the fields can be interactively filled out. By
56
- additionally using the --template option, the data for the fields is read from the given
57
- template file instead of the standard input.
55
+ If the output file name is given, the fields can be filled out interactively, via a
56
+ template or just flattened by using the respective options. Form field flattening can also
57
+ be activated in addition to filling out the form. If neither --fill, --template nor
58
+ --flatten is specified, --fill is implied.
58
59
  EOF
59
60
 
60
61
  options.on("--password PASSWORD", "-p", String,
61
62
  "The password for decryption. Use - for reading from standard input.") do |pwd|
62
63
  @password = (pwd == '-' ? read_password : pwd)
63
64
  end
65
+ options.on("--fill", "Fill out the form") do
66
+ @fill = true
67
+ end
64
68
  options.on("--template TEMPLATE_FILE", "-t TEMPLATE_FILE",
65
- "Use the template file for the field values") do |template|
69
+ "Use the template file for the field values (implies --fill)") do |template|
66
70
  @template = template
71
+ @fill = true
72
+ end
73
+ options.on('--flatten', 'Flatten the form fields') do
74
+ @flatten = true
67
75
  end
68
76
  options.on("--[no-]viewer-override", "Let the PDF viewer override the visual " \
69
77
  "appearance. Default: use setting from input PDF") do |need_appearances|
@@ -75,6 +83,8 @@ module HexaPDF
75
83
  end
76
84
 
77
85
  @password = nil
86
+ @fill = false
87
+ @flatten = false
78
88
  @template = nil
79
89
  @need_appearances = nil
80
90
  @incremental = true
@@ -82,16 +92,28 @@ module HexaPDF
82
92
 
83
93
  def execute(in_file, out_file = nil) #:nodoc:
84
94
  maybe_raise_on_existing_file(out_file) if out_file
95
+ if (@fill || @flatten) && !out_file
96
+ raise "Output file missing"
97
+ end
85
98
  with_document(in_file, password: @password, out_file: out_file,
86
99
  incremental: @incremental) do |doc|
87
100
  if !doc.acro_form
88
101
  raise "This PDF doesn't contain an interactive form"
89
102
  elsif out_file
90
103
  doc.acro_form[:NeedAppearances] = @need_appearances unless @need_appearances.nil?
91
- if @template
92
- fill_form_with_template(doc)
93
- else
94
- fill_form(doc)
104
+ if @fill || !@flatten
105
+ if @template
106
+ fill_form_with_template(doc)
107
+ else
108
+ fill_form(doc)
109
+ end
110
+ end
111
+ if @flatten
112
+ unless doc.acro_form.flatten.empty?
113
+ $stderr.puts "Warning: Not all form fields could be flattened"
114
+ doc.catalog.delete(:AcroForm)
115
+ doc.delete(doc.acro_form)
116
+ end
95
117
  end
96
118
  else
97
119
  list_form_fields(doc)
@@ -164,9 +164,20 @@ module HexaPDF
164
164
  # acro_form.fallback_font::
165
165
  # The font that should be used when a variable text field references a font that cannot be used.
166
166
  #
167
- # Can either be the name of a font, like 'Helvetica', or an array consisting of the font name
168
- # and a hash of font options, like ['Helvetica', variant: :italic]. If set to +nil+, the use of
169
- # the fallback font is disabled.
167
+ # Can be one of the following:
168
+ #
169
+ # * The name of a font, like 'Helvetica'.
170
+ #
171
+ # * An array consisting of the font name and a hash of font options, like ['Helvetica',
172
+ # variant: :italic].
173
+ #
174
+ # * A callable object receiving the field and the font object (or +nil+ if no valid font object
175
+ # was found) and which has to return either a font name or an array consisting of the font
176
+ # name and a hash of font options. This way the response can be different depending on the
177
+ # original font and it would also allow e.g. modifying the configured fonts to add custom
178
+ # ones.
179
+ #
180
+ # If set to +nil+, the use of the fallback font is disabled.
170
181
  #
171
182
  # Default is 'Helvetica'.
172
183
  #
@@ -393,7 +404,7 @@ module HexaPDF
393
404
  DCTDecode: 'HexaPDF::Filter::PassThrough',
394
405
  DCT: 'HexaPDF::Filter::PassThrough',
395
406
  JPXDecode: 'HexaPDF::Filter::PassThrough',
396
- Crypt: nil,
407
+ Crypt: 'HexaPDF::Filter::Crypt',
397
408
  Encryption: 'HexaPDF::Filter::Encryption',
398
409
  },
399
410
  'font.map' => {},
@@ -516,6 +527,9 @@ module HexaPDF
516
527
  XXAcroFormField: 'HexaPDF::Type::AcroForm::Field',
517
528
  XXAppearanceDictionary: 'HexaPDF::Type::Annotation::AppearanceDictionary',
518
529
  Border: 'HexaPDF::Type::Annotation::Border',
530
+ SigFieldLock: 'HexaPDF::Type::AcroForm::SignatureField::LockDictionary',
531
+ SV: 'HexaPDF::Type::AcroForm::SignatureField::SeedValueDictionary',
532
+ SVCert: 'HexaPDF::Type::AcroForm::SignatureField::CertificateSeedValueDictionary',
519
533
  },
520
534
  'object.subtype_map' => {
521
535
  nil => {
@@ -561,6 +575,7 @@ module HexaPDF
561
575
  Tx: 'HexaPDF::Type::AcroForm::TextField',
562
576
  Btn: 'HexaPDF::Type::AcroForm::ButtonField',
563
577
  Ch: 'HexaPDF::Type::AcroForm::ChoiceField',
578
+ Sig: 'HexaPDF::Type::AcroForm::SignatureField',
564
579
  },
565
580
  })
566
581
 
@@ -155,6 +155,12 @@ module HexaPDF
155
155
  # available (see ::define_field).
156
156
  #
157
157
  # * Returns the default value if one is specified and no value is available.
158
+ #
159
+ # Note: If field information is available for the entry, a Hash or Array value will always be
160
+ # wrapped by Dictionary or PDFArray. Otherwise, the value will be returned as-is.
161
+ #
162
+ # Note: This method may throw a "can't add a new key into hash during iteration" error in
163
+ # certain cases because it potentially modifies the underlying hash!
158
164
  def [](name)
159
165
  field = self.class.field(name)
160
166
  data = if key?(name)
@@ -255,7 +261,7 @@ module HexaPDF
255
261
 
256
262
  # Iterates over all currently set fields and those that are required.
257
263
  def each_set_key_or_required_field #:yields: name, field
258
- value.each_key {|name| yield(name, self.class.field(name)) }
264
+ value.keys.each {|name| yield(name, self.class.field(name)) }
259
265
  self.class.each_field do |name, field|
260
266
  yield(name, field) if field.required? && !value.key?(name)
261
267
  end
@@ -301,7 +307,7 @@ module HexaPDF
301
307
 
302
308
  # Check the value of the field against the allowed values.
303
309
  if field.allowed_values && !field.allowed_values.include?(obj)
304
- yield("Field #{name} does not contain an allowed value")
310
+ yield("Field #{name} does not contain an allowed value: #{obj.inspect}")
305
311
  end
306
312
 
307
313
  # Check if field value needs to be (in)direct
@@ -344,7 +344,7 @@ module HexaPDF
344
344
  # Wraps a given array in the Rectangle class. Otherwise returns +nil+.
345
345
  def self.convert(data, _type, document)
346
346
  return unless data.kind_of?(Array) || data.kind_of?(HexaPDF::PDFArray)
347
- document.wrap(data, type: Rectangle)
347
+ data.empty? ? document.wrap(nil) : document.wrap(data, type: Rectangle)
348
348
  end
349
349
 
350
350
  end
@@ -268,7 +268,7 @@ module HexaPDF
268
268
  str.replace(string_algorithm.decrypt(key, str))
269
269
  end
270
270
 
271
- if obj.kind_of?(HexaPDF::Stream)
271
+ if obj.kind_of?(HexaPDF::Stream) && obj.raw_stream.filter[0] != :Crypt
272
272
  unless string_algorithm == stream_algorithm
273
273
  key = object_key(obj.oid, obj.gen, stream_algorithm)
274
274
  end
@@ -300,7 +300,12 @@ module HexaPDF
300
300
  obj.raw_stream.key == key && obj.raw_stream.algorithm == stream_algorithm
301
301
  obj.raw_stream.undecrypted_fiber
302
302
  else
303
- stream_algorithm.encryption_fiber(key, result)
303
+ filter = obj[:Filter]
304
+ if filter == :Crypt || (filter.kind_of?(PDFArray) && filter[0] == :Crypt)
305
+ result
306
+ else
307
+ stream_algorithm.encryption_fiber(key, result)
308
+ end
304
309
  end
305
310
  end
306
311
 
data/lib/hexapdf/error.rb CHANGED
@@ -43,12 +43,13 @@ module HexaPDF
43
43
  class MalformedPDFError < Error
44
44
 
45
45
  # The byte position in the PDF file where the error occured.
46
- attr_reader :pos
46
+ attr_accessor :pos
47
47
 
48
48
  # Creates a new malformed PDF error object for the given exception message.
49
49
  #
50
- # The byte position where the error occured has to be given via the +pos+ argument.
51
- def initialize(message, pos:)
50
+ # The byte position where the error occured can either be given via the +pos+ argument or later
51
+ # via the #pos accessor but must be set before the exception message is retrieved.
52
+ def initialize(message, pos: nil)
52
53
  super(message)
53
54
  @pos = pos
54
55
  end
@@ -95,6 +95,7 @@ module HexaPDF
95
95
  autoload(:Predictor, 'hexapdf/filter/predictor')
96
96
 
97
97
  autoload(:Encryption, 'hexapdf/filter/encryption')
98
+ autoload(:Crypt, 'hexapdf/filter/crypt')
98
99
 
99
100
  autoload(:PassThrough, 'hexapdf/filter/pass_through')
100
101
 
@@ -0,0 +1,60 @@
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-2020 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/error'
38
+
39
+ module HexaPDF
40
+ module Filter
41
+
42
+ # This filter module implements the Crypt filter. The only supported part is using the Identity
43
+ # filter.
44
+ module Crypt
45
+
46
+ # See HexaPDF::Filter
47
+ def self.decoder(source, options)
48
+ if !options || !options.key?(:Name) || options[:Name] == :Identity
49
+ source
50
+ else
51
+ raise FilterError, "Handling of Crypt filters besides Identity is not implemented"
52
+ end
53
+ end
54
+
55
+ singleton_class.send(:alias_method, :encoder, :decoder)
56
+
57
+ end
58
+
59
+ end
60
+ end
@@ -63,6 +63,16 @@ 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
66
76
  @glyph_map[glyph_id] = @last_id
67
77
  end
68
78
 
@@ -107,7 +117,7 @@ module HexaPDF
107
117
  locations = []
108
118
 
109
119
  @glyph_map.each_key do |old_gid|
110
- glyph = orig_glyf[old_gid]
120
+ glyph = orig_glyf[old_gid.kind_of?(Symbol) ? 0 : old_gid]
111
121
  locations << table.size
112
122
  data = glyph.raw_data
113
123
  if glyph.compound?
@@ -134,7 +144,7 @@ module HexaPDF
134
144
  hmtx = @font[:hmtx]
135
145
  data = ''.b
136
146
  @glyph_map.each_key do |old_gid|
137
- metric = hmtx[old_gid]
147
+ metric = hmtx[old_gid.kind_of?(Symbol) ? 0 : old_gid]
138
148
  data << [metric.advance_width, metric.left_side_bearing].pack('n2')
139
149
  end
140
150
  data
@@ -166,7 +176,10 @@ module HexaPDF
166
176
  # Adds the components of compound glyphs to the subset.
167
177
  def add_glyph_components
168
178
  glyf = @font[:glyf]
169
- @glyph_map.keys.each {|gid| glyf[gid].components&.each {|cgid| use_glyph(cgid) } }
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
170
183
  end
171
184
 
172
185
  end
@@ -99,18 +99,23 @@ module HexaPDF
99
99
  @max_mem_type42, @min_mem_type1, @max_mem_type1 = read_formatted(24, 's>2N5')
100
100
 
101
101
  sub_table_length = directory_entry.length - 32
102
- @glyph_names = case @format
103
- when 1 then Format1.parse(io, sub_table_length)
104
- when 2 then Format2.parse(io, sub_table_length)
105
- when 3 then Format3.parse(io, sub_table_length)
106
- when 4 then Format4.parse(io, sub_table_length)
107
- else
108
- if font.config['font.true_type.unknown_format'] == :raise
109
- raise HexaPDF::Error, "Unsupported post table format: #{@format}"
102
+ cur_pos = io.pos
103
+ @glyph_names = lambda do |glyph_id|
104
+ io.pos = cur_pos
105
+ @glyph_names = case @format
106
+ when 1 then Format1.parse(io, sub_table_length)
107
+ when 2 then Format2.parse(io, sub_table_length)
108
+ when 3 then Format3.parse(io, sub_table_length)
109
+ when 4 then Format4.parse(io, sub_table_length)
110
110
  else
111
- []
111
+ if font.config['font.true_type.unknown_format'] == :raise
112
+ raise HexaPDF::Error, "Unsupported post table format: #{@format}"
113
+ else
114
+ []
115
+ end
112
116
  end
113
- end
117
+ @glyph_names[glyph_id]
118
+ end
114
119
  end
115
120
 
116
121
  # 'post' table format 1