hexapdf 0.7.0 → 0.8.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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -1
  3. data/CONTRIBUTERS +1 -1
  4. data/LICENSE +3 -0
  5. data/README.md +2 -1
  6. data/Rakefile +3 -1
  7. data/VERSION +1 -1
  8. data/examples/{hello_world.rb → 001-hello_world.rb} +0 -0
  9. data/examples/{graphics.rb → 002-graphics.rb} +1 -1
  10. data/examples/{arc.rb → 003-arcs.rb} +2 -2
  11. data/examples/{optimizing.rb → 004-optimizing.rb} +0 -0
  12. data/examples/{merging.rb → 005-merging.rb} +0 -0
  13. data/examples/{standard_pdf_fonts.rb → 006-standard_pdf_fonts.rb} +0 -0
  14. data/examples/{truetype.rb → 007-truetype.rb} +0 -0
  15. data/examples/{show_char_bboxes.rb → 008-show_char_bboxes.rb} +0 -0
  16. data/examples/{text_layouter_alignment.rb → 009-text_layouter_alignment.rb} +3 -3
  17. data/examples/{text_layouter_inline_boxes.rb → 010-text_layouter_inline_boxes.rb} +7 -9
  18. data/examples/{text_layouter_line_wrapping.rb → 011-text_layouter_line_wrapping.rb} +6 -5
  19. data/examples/{text_layouter_styling.rb → 012-text_layouter_styling.rb} +6 -8
  20. data/examples/013-text_layouter_shapes.rb +176 -0
  21. data/examples/014-text_in_polygon.rb +60 -0
  22. data/examples/{boxes.rb → 015-boxes.rb} +29 -21
  23. data/examples/016-frame_automatic_box_placement.rb +90 -0
  24. data/examples/017-frame_text_flow.rb +60 -0
  25. data/lib/hexapdf/cli/command.rb +4 -3
  26. data/lib/hexapdf/cli/files.rb +1 -1
  27. data/lib/hexapdf/cli/inspect.rb +0 -1
  28. data/lib/hexapdf/cli/merge.rb +1 -1
  29. data/lib/hexapdf/cli/modify.rb +1 -1
  30. data/lib/hexapdf/configuration.rb +2 -0
  31. data/lib/hexapdf/content/canvas.rb +3 -3
  32. data/lib/hexapdf/content/graphic_object.rb +1 -0
  33. data/lib/hexapdf/content/graphic_object/geom2d.rb +132 -0
  34. data/lib/hexapdf/dictionary.rb +7 -1
  35. data/lib/hexapdf/dictionary_fields.rb +35 -83
  36. data/lib/hexapdf/document.rb +9 -5
  37. data/lib/hexapdf/document/fonts.rb +1 -1
  38. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  39. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  40. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  41. data/lib/hexapdf/font/cmap/writer.rb +2 -2
  42. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  43. data/lib/hexapdf/font/true_type/table.rb +1 -1
  44. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  45. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +3 -3
  46. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  47. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  48. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  49. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  50. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  51. data/lib/hexapdf/image_loader/png.rb +2 -2
  52. data/lib/hexapdf/layout.rb +3 -0
  53. data/lib/hexapdf/layout/box.rb +64 -46
  54. data/lib/hexapdf/layout/frame.rb +348 -0
  55. data/lib/hexapdf/layout/inline_box.rb +2 -2
  56. data/lib/hexapdf/layout/line.rb +3 -3
  57. data/lib/hexapdf/layout/style.rb +81 -14
  58. data/lib/hexapdf/layout/text_box.rb +84 -0
  59. data/lib/hexapdf/layout/text_fragment.rb +8 -8
  60. data/lib/hexapdf/layout/text_layouter.rb +278 -169
  61. data/lib/hexapdf/layout/width_from_polygon.rb +246 -0
  62. data/lib/hexapdf/rectangle.rb +9 -9
  63. data/lib/hexapdf/stream.rb +2 -2
  64. data/lib/hexapdf/type.rb +1 -0
  65. data/lib/hexapdf/type/action.rb +1 -1
  66. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  67. data/lib/hexapdf/type/catalog.rb +1 -1
  68. data/lib/hexapdf/type/cid_font.rb +2 -1
  69. data/lib/hexapdf/type/font.rb +0 -1
  70. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  71. data/lib/hexapdf/type/font_simple.rb +3 -3
  72. data/lib/hexapdf/type/font_true_type.rb +8 -0
  73. data/lib/hexapdf/type/font_type0.rb +2 -1
  74. data/lib/hexapdf/type/font_type1.rb +7 -1
  75. data/lib/hexapdf/type/font_type3.rb +61 -0
  76. data/lib/hexapdf/type/graphics_state_parameter.rb +8 -8
  77. data/lib/hexapdf/type/image.rb +10 -0
  78. data/lib/hexapdf/type/page.rb +83 -10
  79. data/lib/hexapdf/version.rb +1 -1
  80. data/test/hexapdf/common_tokenizer_tests.rb +2 -2
  81. data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
  82. data/test/hexapdf/encryption/test_standard_security_handler.rb +1 -1
  83. data/test/hexapdf/font/test_true_type_wrapper.rb +1 -1
  84. data/test/hexapdf/font/test_type1_wrapper.rb +1 -1
  85. data/test/hexapdf/font/true_type/table/test_cmap.rb +1 -1
  86. data/test/hexapdf/font/true_type/table/test_directory.rb +1 -1
  87. data/test/hexapdf/font/true_type/table/test_head.rb +7 -3
  88. data/test/hexapdf/layout/test_box.rb +57 -15
  89. data/test/hexapdf/layout/test_frame.rb +313 -0
  90. data/test/hexapdf/layout/test_inline_box.rb +1 -1
  91. data/test/hexapdf/layout/test_style.rb +74 -0
  92. data/test/hexapdf/layout/test_text_box.rb +77 -0
  93. data/test/hexapdf/layout/test_text_layouter.rb +220 -239
  94. data/test/hexapdf/layout/test_width_from_polygon.rb +108 -0
  95. data/test/hexapdf/test_dictionary_fields.rb +22 -26
  96. data/test/hexapdf/test_document.rb +3 -3
  97. data/test/hexapdf/test_reference.rb +1 -0
  98. data/test/hexapdf/test_writer.rb +2 -2
  99. data/test/hexapdf/type/test_font_true_type.rb +25 -0
  100. data/test/hexapdf/type/test_font_type1.rb +6 -0
  101. data/test/hexapdf/type/test_font_type3.rb +26 -0
  102. data/test/hexapdf/type/test_image.rb +10 -0
  103. data/test/hexapdf/type/test_page.rb +114 -0
  104. data/test/test_helper.rb +1 -1
  105. metadata +65 -17
  106. data/examples/text_layouter_shapes.rb +0 -170
@@ -78,6 +78,10 @@ module HexaPDF
78
78
  # If a Symbol object instead of a class is provided, the class is looked up using the
79
79
  # 'object.type_map' global configuration option when necessary to support lazy loading.
80
80
  #
81
+ # Note that if multiple types are allowed and one of the allowed types is Dictionary (or
82
+ # a Symbol), it has to be the first in the list. Otherwise automatic type conversion
83
+ # functions won't work correctly.
84
+ #
81
85
  # required:: Specifies whether this field is required.
82
86
  #
83
87
  # default:: Specifies the default value for the field, if any.
@@ -156,7 +160,9 @@ module HexaPDF
156
160
  if data.class == HexaPDF::Object || (data.kind_of?(HexaPDF::Object) && data.value.nil?)
157
161
  data = data.value
158
162
  end
159
- self[name] = data = field.convert(data, document) if field&.convert?(data)
163
+ if (result = field&.convert(data, document))
164
+ self[name] = data = result
165
+ end
160
166
  data
161
167
  end
162
168
 
@@ -55,13 +55,10 @@ module HexaPDF
55
55
  # Should return +nil+, a single type class or an array of type classes which will additionally
56
56
  # be allowed for the field.
57
57
  #
58
- # convert?(data, type)::
59
- # Should return +true+ if the given +data+ object can be converted. The +type+ argument is the
60
- # result of the Field#type method call.
61
- #
62
58
  # convert(data, type, document)::
63
- # Should return the +converted+ data. The +type+ argument is the result of the Field#type method
64
- # call and +document+ is the HexaPDF::Document for which the data should be converted.
59
+ # Should return the +converted+ data if conversion is possible and +nil+ otherwise. The +type+
60
+ # argument is the result of the Field#type method call and +document+ is the HexaPDF::Document
61
+ # for which the data should be converted.
65
62
  module DictionaryFields
66
63
 
67
64
  # This constant should *always* be used for boolean fields.
@@ -112,14 +109,14 @@ module HexaPDF
112
109
  @type = [type].flatten
113
110
  @type_mapped = false
114
111
  @required, @default, @indirect, @version = required, default, indirect, version
115
- @converter = self.class.converter_for(type)
112
+ @converters = @type.map {|t| self.class.converter_for(t) }.compact
116
113
  end
117
114
 
118
115
  # Returns the array with valid types for this field.
119
116
  def type
120
117
  return @type if @type_mapped
121
118
  @type_mapped = true
122
- @type.concat(Array(@converter.additional_types))
119
+ @type.concat(@converters.map(&:additional_types).compact.flatten)
123
120
  @type.map! do |type|
124
121
  if type.kind_of?(Symbol)
125
122
  HexaPDF::GlobalConfiguration.constantize('object.type_map', type)
@@ -149,7 +146,7 @@ module HexaPDF
149
146
 
150
147
  # Returns +true+ if the default value can safely be duplicated with #dup.
151
148
  def duplicatable_default?
152
- @cached_dupdefault ||= HexaPDF::Object::NOT_DUPLICATABLE_CLASSES.none? do |klass|
149
+ @duplicatable_default ||= HexaPDF::Object::NOT_DUPLICATABLE_CLASSES.none? do |klass|
153
150
  @default.kind_of?(klass)
154
151
  end
155
152
  end
@@ -161,39 +158,13 @@ module HexaPDF
161
158
  (obj.kind_of?(HexaPDF::Object) && type.any? {|t| obj.value.kind_of?(t) })
162
159
  end
163
160
 
164
- # If a converter was defined, it is used. Otherwise +false+ is returned.
165
- #
166
- # See: #convert
167
- def convert?(data)
168
- @converter.convert?(data, type)
169
- end
170
-
171
- # If a converter was defined, it is used for converting the data. Otherwise this is a Noop -
172
- # it just returns the data.
173
- #
174
- # See: #convert?
161
+ # Converts the data into a useful object if possible. Otherwise returns +nil+.
175
162
  def convert(data, document)
176
- @converter.convert(data, type, document)
177
- end
178
-
179
- end
180
-
181
- # Does nothing.
182
- module IdentityConverter
183
-
184
- def self.usable_for?(_type) #:nodoc:
185
- true
186
- end
187
-
188
- def self.additional_types #:nodoc:
189
- end
190
-
191
- def self.convert?(_data, _type) #:nodoc:
192
- false
193
- end
194
-
195
- def self.convert(data, _type, _document) #:nodoc:
196
- data
163
+ @converters.each do |converter|
164
+ result = converter.convert(data, type, document)
165
+ return result unless result.nil?
166
+ end
167
+ nil
197
168
  end
198
169
 
199
170
  end
@@ -214,15 +185,11 @@ module HexaPDF
214
185
  Hash
215
186
  end
216
187
 
217
- # Returns +true+ if the given data value can be converted to the Dictionary subclass
218
- # specified by type (see Field#type).
219
- def self.convert?(data, type)
220
- !data.kind_of?(type.first) && (data.kind_of?(Hash) ||
221
- data.kind_of?(HexaPDF::Dictionary))
222
- end
223
-
224
- # Wraps the given data value in the PDF specific type class.
188
+ # Wraps the given data value in the PDF specific type class if it can be converted. Otherwise
189
+ # returns +nil+.
225
190
  def self.convert(data, type, document)
191
+ return if data.kind_of?(type.first) || !(data.kind_of?(Hash) ||
192
+ data.kind_of?(HexaPDF::Dictionary))
226
193
  document.wrap(data, type: type.first)
227
194
  end
228
195
 
@@ -240,13 +207,11 @@ module HexaPDF
240
207
  def self.additional_types
241
208
  end
242
209
 
243
- # Returns +true+ if the given data should be converted to a UTF-8 encoded string.
244
- def self.convert?(data, _type)
245
- data.kind_of?(String) && data.encoding == Encoding::BINARY
246
- end
247
-
248
- # Converts the string into UTF-8 encoding, assuming it is currently a binary string.
210
+ # Converts the string into UTF-8 encoding, assuming it is a binary string. Otherwise +nil+ is
211
+ # returned.
249
212
  def self.convert(str, _type, _document)
213
+ return unless str.kind_of?(String) && str.encoding == Encoding::BINARY
214
+
250
215
  if str.getbyte(0) == 254 && str.getbyte(1) == 255
251
216
  str[2..-1].force_encoding(Encoding::UTF_16BE).encode(Encoding::UTF_8)
252
217
  else
@@ -270,13 +235,10 @@ module HexaPDF
270
235
  String
271
236
  end
272
237
 
273
- # Returns +true+ if the given data should be converted to a UTF-8 encoded string.
274
- def self.convert?(data, _type)
275
- data.kind_of?(String) && data.encoding != Encoding::BINARY
276
- end
277
-
278
- # Converts the string into UTF-8 encoding, assuming it is currently a binary string.
238
+ # Converts the string into binary encoding, assuming it is a non-binary string. Otherwise
239
+ # returns +nil+.
279
240
  def self.convert(str, _type, _document)
241
+ return if !str.kind_of?(String) || str.encoding == Encoding::BINARY
280
242
  str.force_encoding(Encoding::BINARY)
281
243
  end
282
244
 
@@ -304,14 +266,11 @@ module HexaPDF
304
266
  # :nodoc:
305
267
  DATE_RE = /\AD:(\d{4})(\d\d)?(\d\d)?(\d\d)?(\d\d)?(\d\d)?([Z+-])?(?:(\d\d)(?:'|'(\d\d)'?|\z)?)?\z/n
306
268
 
307
- # Returns +true+ if the given data should be converted to a Time object.
308
- def self.convert?(data, _type)
309
- data.kind_of?(String) && data.match?(DATE_RE)
310
- end
311
-
312
- # Converts the string into a Time object.
269
+ # Checks if the given object is a string and converts into a Time object if possible.
270
+ # Otherwise returns +nil+.
313
271
  def self.convert(str, _type, _document)
314
- m = DATE_RE.match(str)
272
+ return unless str.kind_of?(String) && (m = str.match(DATE_RE))
273
+
315
274
  utc_offset = (m[7].nil? || m[7] == 'Z' ? 0 : "#{m[7]}#{m[8]}:#{m[9] || '00'}")
316
275
  Time.new(m[1].to_i, (m[2] ? m[2].to_i : 1), (m[3] ? m[3].to_i : 1),
317
276
  m[4].to_i, m[5].to_i, m[6].to_i, utc_offset)
@@ -333,14 +292,12 @@ module HexaPDF
333
292
  [Hash, String]
334
293
  end
335
294
 
336
- # Returns +true+ if the given data is a string file specification.
337
- def self.convert?(data, type)
338
- !data.kind_of?(type.first) &&
339
- (data.kind_of?(Hash) || data.kind_of?(HexaPDF::Dictionary) || data.kind_of?(String))
340
- end
341
-
342
- # Converts a string file specification or a hash into a full file specification.
295
+ # Converts a string file specification or a hash into a full file specification. Otherwise
296
+ # returns +nil+.
343
297
  def self.convert(data, type, document)
298
+ return if data.kind_of?(type.first) ||
299
+ !(data.kind_of?(Hash) || data.kind_of?(HexaPDF::Dictionary) || data.kind_of?(String))
300
+
344
301
  data = {F: data} if data.kind_of?(String)
345
302
  document.wrap(data, type: type.first)
346
303
  end
@@ -360,21 +317,16 @@ module HexaPDF
360
317
  Array
361
318
  end
362
319
 
363
- # Returns +true+ if the given data value is an Array.
364
- def self.convert?(data, _type)
365
- data.kind_of?(Array)
366
- end
367
-
368
- # Wraps the given data value in the Rectangle class.
320
+ # Wraps a given array in the Rectangle class. Otherwise returns +nil+.
369
321
  def self.convert(data, _type, document)
322
+ return unless data.kind_of?(Array)
370
323
  document.wrap(data, type: Rectangle)
371
324
  end
372
325
 
373
326
  end
374
327
 
375
328
  Field.converters.replace([FileSpecificationConverter, DictionaryConverter, StringConverter,
376
- PDFByteStringConverter, DateConverter, RectangleConverter,
377
- IdentityConverter])
329
+ PDFByteStringConverter, DateConverter, RectangleConverter])
378
330
 
379
331
  end
380
332
 
@@ -554,8 +554,8 @@ module HexaPDF
554
554
  end
555
555
 
556
556
  # :call-seq:
557
- # doc.validate(auto_correct: true) -> true or false
558
- # doc.validate(auto_correct: true) {|msg, correctable| block } -> true or false
557
+ # doc.validate(auto_correct: true) -> true or false
558
+ # doc.validate(auto_correct: true) {|object, msg, correctable| block } -> true or false
559
559
  #
560
560
  # Validates all objects of the document, with optional auto-correction, and returns +true+ if
561
561
  # everything is fine.
@@ -563,9 +563,13 @@ module HexaPDF
563
563
  # If a block is given, it is called on validation problems.
564
564
  #
565
565
  # See HexaPDF::Object#validate for more information.
566
- def validate(auto_correct: true, &block)
566
+ def validate(auto_correct: true)
567
+ cur_obj = trailer
568
+ block = (block_given? ? lambda {|msg, correctable| yield(cur_obj, msg, correctable) } : nil)
569
+
567
570
  result = trailer.validate(auto_correct: auto_correct, &block)
568
571
  each(current: false) do |obj|
572
+ cur_obj = obj
569
573
  result &&= obj.validate(auto_correct: auto_correct, &block)
570
574
  end
571
575
  result
@@ -601,9 +605,9 @@ module HexaPDF
601
605
  end
602
606
 
603
607
  if validate
604
- self.validate(auto_correct: true) do |msg, correctable|
608
+ self.validate(auto_correct: true) do |obj, msg, correctable|
605
609
  next if correctable
606
- raise HexaPDF::Error, "Validation error: #{msg}"
610
+ raise HexaPDF::Error, "Validation error for (#{obj.oid},#{obj.gen}): #{msg}"
607
611
  end
608
612
  end
609
613
 
@@ -50,7 +50,7 @@ module HexaPDF
50
50
  # :call-seq:
51
51
  # fonts.add(name, **options) -> font
52
52
  #
53
- # Adds the font to the document and returns if (using the loaders specified with the
53
+ # Adds the font to the document and returns it (using the loaders specified with the
54
54
  # configuration option 'font_loaders').
55
55
  #
56
56
  # If a font with the same parameters has been loaded before, the cached font object is used.
@@ -524,7 +524,7 @@ module HexaPDF
524
524
  decrypted = aes_algorithm.new(encryption_key, "\0" * 16, :decrypt).process(dict[:Perms])
525
525
  if decrypted[9, 3] != "adb"
526
526
  raise HexaPDF::EncryptionError, "/Perms field cannot be decrypted"
527
- elsif (dict[:P] & 0xFFFFFFFF) != (decrypted[0, 4].unpack('V').first & 0xFFFFFFFF)
527
+ elsif (dict[:P] & 0xFFFFFFFF) != (decrypted[0, 4].unpack1('V') & 0xFFFFFFFF)
528
528
  raise HexaPDF::EncryptionError, "Decrypted permissions don't match /P"
529
529
  elsif decrypted[8] != (dict[:EncryptMetadata] ? 'T' : 'F')
530
530
  raise HexaPDF::EncryptionError, "Decrypted /Perms field doesn't match /EncryptMetadata"
@@ -144,7 +144,7 @@ module HexaPDF
144
144
 
145
145
  if rest
146
146
  rlen = rest.length
147
- num = (rest + "\0" * (4 - rlen)).unpack('N').first
147
+ num = (rest + "\0" * (4 - rlen)).unpack1('N')
148
148
  ((VALUE_TO_CHAR[num / POW85_4 % 85] + VALUE_TO_CHAR[num / POW85_3 % 85] <<
149
149
  VALUE_TO_CHAR[num / POW85_2 % 85] << VALUE_TO_CHAR[num / POW85_1 % 85] <<
150
150
  VALUE_TO_CHAR[num % 85])[0, rlen + 1] << "~>").force_encoding(Encoding::BINARY)
@@ -70,7 +70,7 @@ module HexaPDF
70
70
  def self.encoder(source, _ = nil)
71
71
  Fiber.new do
72
72
  while source.alive? && (data = source.resume)
73
- Fiber.yield(data.unpack('H*').first.force_encoding(Encoding::BINARY))
73
+ Fiber.yield(data.unpack1('H*').force_encoding(Encoding::BINARY))
74
74
  end
75
75
  '>'.b
76
76
  end
@@ -57,14 +57,14 @@ module HexaPDF
57
57
  result = create_sections("bfchar", chars.size / 2) do |index|
58
58
  index *= 2
59
59
  sprintf("<%04X>", chars[index]) << "<" <<
60
- ((+'').force_encoding(::Encoding::UTF_16BE) << chars[index + 1]).unpack('H*').first <<
60
+ ((+'').force_encoding(::Encoding::UTF_16BE) << chars[index + 1]).unpack1('H*') <<
61
61
  ">\n"
62
62
  end
63
63
 
64
64
  result << create_sections("bfrange", ranges.size / 3) do |index|
65
65
  index *= 3
66
66
  sprintf("<%04X><%04X>", ranges[index], ranges[index + 1]) << "<" <<
67
- ((+'').force_encoding(::Encoding::UTF_16BE) << ranges[index + 2]).unpack('H*').first <<
67
+ ((+'').force_encoding(::Encoding::UTF_16BE) << ranges[index + 2]).unpack1('H*') <<
68
68
  ">\n"
69
69
  end
70
70
 
@@ -63,7 +63,7 @@ module HexaPDF
63
63
  table_checksum = Table.calculate_checksum(data)
64
64
  data << "\0" * (4 - original_data_length % 4) if original_data_length % 4 != 0
65
65
  # tag, offset, data.length are all 32bit uint, table_checksum for header and body
66
- checksum += tag.unpack('N').first + 2 * table_checksum + offset + data.length
66
+ checksum += tag.unpack1('N') + 2 * table_checksum + offset + data.length
67
67
  font_data << [tag, table_checksum, offset, original_data_length].pack('a4N3')
68
68
  offset += data.length
69
69
  end
@@ -137,7 +137,7 @@ module HexaPDF
137
137
 
138
138
  # Reads a 16.16-bit signed fixed-point integer and returns a Rational as result.
139
139
  def read_fixed
140
- Rational(io.read(4).unpack('i>').first, 65536)
140
+ Rational(io.read(4).unpack1('i>'), 65536)
141
141
  end
142
142
 
143
143
  end
@@ -56,7 +56,7 @@ module HexaPDF
56
56
  #
57
57
  # A preferred table is always a table mapping Unicode characters.
58
58
  def preferred_table
59
- tables.select(&:unicode?).sort_by(&:format).last
59
+ tables.select(&:unicode?).max_by(&:format)
60
60
  end
61
61
 
62
62
  private
@@ -117,7 +117,7 @@ module HexaPDF
117
117
  # +true+ is returned. Otherwise nothing is done and +false+ is returned.
118
118
  def parse(io, offset)
119
119
  io.pos = offset
120
- @format = io.read(2).unpack('n').first
120
+ @format = io.read(2).unpack1('n')
121
121
  if [8, 10, 12].include?(@format)
122
122
  io.pos += 2
123
123
  length, @language = io.read(8).unpack('N2')
@@ -245,7 +245,7 @@ module HexaPDF
245
245
  #
246
246
  # It is assumed that the first six bytes of the subtable have already been consumed.
247
247
  def self.parse(io, length)
248
- seg_count_x2 = io.read(8).unpack('n').first
248
+ seg_count_x2 = io.read(8).unpack1('n')
249
249
  end_codes = io.read(seg_count_x2).unpack('n*')
250
250
  io.pos += 2
251
251
  start_codes = io.read(seg_count_x2).unpack('n*')
@@ -346,7 +346,7 @@ module HexaPDF
346
346
  #
347
347
  # It is assumed that the first twelve bytes of the subtable have already been consumed.
348
348
  def self.parse(io, _length)
349
- mapper(Array.new(io.read(4).unpack('N').first) { io.read(12).unpack('N3') })
349
+ mapper(Array.new(io.read(4).unpack1('N')) { io.read(12).unpack('N3') })
350
350
  end
351
351
 
352
352
  # The parameter +groups+ is an array containing [start_code, end_code, start_glyph_id]
@@ -152,7 +152,7 @@ module HexaPDF
152
152
  # Parses the format 0 subtable and returns a hash of the form
153
153
  # {left_char: {right_char: kern_value}}
154
154
  def self.parse(io, _length)
155
- number_of_pairs = io.read(8).unpack('n').first
155
+ number_of_pairs = io.read(8).unpack1('n')
156
156
  pairs = Hash.new {|h, k| h[k] = {} }
157
157
  io.read(number_of_pairs * 6).unpack('n*').each_slice(3) do |left, right, value|
158
158
  pairs[left][right] = (value < 0x8000 ? value : -(value ^ 0xffff) - 1)
@@ -160,7 +160,7 @@ module HexaPDF
160
160
  # returns the contained glyph name map.
161
161
  def self.parse(io, length)
162
162
  end_pos = io.pos + length
163
- num_glyphs = io.read(2).unpack('n').first
163
+ num_glyphs = io.read(2).unpack1('n')
164
164
  glyph_name_index = io.read(2 * num_glyphs).unpack('n*')
165
165
  names = []
166
166
  names << io.read(io.getbyte).force_encoding(::Encoding::UTF_8) while io.pos < end_pos
@@ -48,7 +48,7 @@ module HexaPDF
48
48
  attr_accessor :name
49
49
 
50
50
  # Character bounding box as array of four numbers, specifying the x- and y-coordinates of
51
- # the lower-left corner and the x- and y-coordinates of the upper-right corner.
51
+ # the bottom left corner and the x- and y-coordinates of the top right corner.
52
52
  attr_accessor :bbox
53
53
 
54
54
  end
@@ -60,7 +60,7 @@ module HexaPDF
60
60
  attr_accessor :weight
61
61
 
62
62
  # The font bounding box as array of four numbers, specifying the x- and y-coordinates of the
63
- # lower-left corner and the x- and y-coordinates of the upper-right corner.
63
+ # bottom left corner and the x- and y-coordinates of the top right corner.
64
64
  attr_accessor :bounding_box
65
65
 
66
66
  # The y-value of the top of the capital H (or 0 or nil if the font doesn't contain a capital
@@ -116,7 +116,7 @@ module HexaPDF
116
116
 
117
117
  # B1.1.4 - next two bytes are the length of the segment (except for RSTm or TEM markers
118
118
  # but those shouldn't appear here)
119
- length = io.read(2).unpack('n').first
119
+ length = io.read(2).unpack1('n')
120
120
 
121
121
  if code1 == ADOBE_MARKER # Adobe apps invert the colors when using CMYK color space
122
122
  invert_colors = true
@@ -167,12 +167,12 @@ module HexaPDF
167
167
  io.seek(length, IO::SEEK_CUR)
168
168
  end
169
169
  when 'sRGB' # PNG s11.3.3.5
170
- @intent = io.read(length).unpack('C').first
170
+ @intent = io.read(length).unpack1('C')
171
171
  dict[:Intent] = RENDERING_INTENT_MAP[@intent]
172
172
  @chrm = SRGB_CHRM
173
173
  @gamma = 2.2
174
174
  when 'gAMA' # PNG s11.3.3.2
175
- gamma = 100_000.0 / io.read(length).unpack('N').first
175
+ gamma = 100_000.0 / io.read(length).unpack1('N')
176
176
  unless @intent || gamma == 1.0 # sRGB trumps gAMA
177
177
  @gamma = gamma
178
178
  @chrm ||= SRGB_CHRM # don't overwrite data from a cHRM chunk
@@ -46,6 +46,9 @@ module HexaPDF
46
46
  autoload(:TextShaper, 'hexapdf/layout/text_shaper')
47
47
  autoload(:TextLayouter, 'hexapdf/layout/text_layouter')
48
48
  autoload(:Box, 'hexapdf/layout/box')
49
+ autoload(:Frame, 'hexapdf/layout/frame')
50
+ autoload(:WidthFromPolygon, 'hexapdf/layout/width_from_polygon')
51
+ autoload(:TextBox, 'hexapdf/layout/text_box')
49
52
 
50
53
  end
51
54