hexapdf 0.7.0 → 0.8.0

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