hexapdf 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -1
- data/CONTRIBUTERS +1 -1
- data/LICENSE +3 -0
- data/README.md +2 -1
- data/Rakefile +3 -1
- data/VERSION +1 -1
- data/examples/{hello_world.rb → 001-hello_world.rb} +0 -0
- data/examples/{graphics.rb → 002-graphics.rb} +1 -1
- data/examples/{arc.rb → 003-arcs.rb} +2 -2
- data/examples/{optimizing.rb → 004-optimizing.rb} +0 -0
- data/examples/{merging.rb → 005-merging.rb} +0 -0
- data/examples/{standard_pdf_fonts.rb → 006-standard_pdf_fonts.rb} +0 -0
- data/examples/{truetype.rb → 007-truetype.rb} +0 -0
- data/examples/{show_char_bboxes.rb → 008-show_char_bboxes.rb} +0 -0
- data/examples/{text_layouter_alignment.rb → 009-text_layouter_alignment.rb} +3 -3
- data/examples/{text_layouter_inline_boxes.rb → 010-text_layouter_inline_boxes.rb} +7 -9
- data/examples/{text_layouter_line_wrapping.rb → 011-text_layouter_line_wrapping.rb} +6 -5
- data/examples/{text_layouter_styling.rb → 012-text_layouter_styling.rb} +6 -8
- data/examples/013-text_layouter_shapes.rb +176 -0
- data/examples/014-text_in_polygon.rb +60 -0
- data/examples/{boxes.rb → 015-boxes.rb} +29 -21
- data/examples/016-frame_automatic_box_placement.rb +90 -0
- data/examples/017-frame_text_flow.rb +60 -0
- data/lib/hexapdf/cli/command.rb +4 -3
- data/lib/hexapdf/cli/files.rb +1 -1
- data/lib/hexapdf/cli/inspect.rb +0 -1
- data/lib/hexapdf/cli/merge.rb +1 -1
- data/lib/hexapdf/cli/modify.rb +1 -1
- data/lib/hexapdf/configuration.rb +2 -0
- data/lib/hexapdf/content/canvas.rb +3 -3
- data/lib/hexapdf/content/graphic_object.rb +1 -0
- data/lib/hexapdf/content/graphic_object/geom2d.rb +132 -0
- data/lib/hexapdf/dictionary.rb +7 -1
- data/lib/hexapdf/dictionary_fields.rb +35 -83
- data/lib/hexapdf/document.rb +9 -5
- data/lib/hexapdf/document/fonts.rb +1 -1
- data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
- data/lib/hexapdf/font/cmap/writer.rb +2 -2
- data/lib/hexapdf/font/true_type/builder.rb +1 -1
- data/lib/hexapdf/font/true_type/table.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +3 -3
- data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
- data/lib/hexapdf/font/true_type/table/post.rb +1 -1
- data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
- data/lib/hexapdf/image_loader/jpeg.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +2 -2
- data/lib/hexapdf/layout.rb +3 -0
- data/lib/hexapdf/layout/box.rb +64 -46
- data/lib/hexapdf/layout/frame.rb +348 -0
- data/lib/hexapdf/layout/inline_box.rb +2 -2
- data/lib/hexapdf/layout/line.rb +3 -3
- data/lib/hexapdf/layout/style.rb +81 -14
- data/lib/hexapdf/layout/text_box.rb +84 -0
- data/lib/hexapdf/layout/text_fragment.rb +8 -8
- data/lib/hexapdf/layout/text_layouter.rb +278 -169
- data/lib/hexapdf/layout/width_from_polygon.rb +246 -0
- data/lib/hexapdf/rectangle.rb +9 -9
- data/lib/hexapdf/stream.rb +2 -2
- data/lib/hexapdf/type.rb +1 -0
- data/lib/hexapdf/type/action.rb +1 -1
- data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
- data/lib/hexapdf/type/catalog.rb +1 -1
- data/lib/hexapdf/type/cid_font.rb +2 -1
- data/lib/hexapdf/type/font.rb +0 -1
- data/lib/hexapdf/type/font_descriptor.rb +1 -1
- data/lib/hexapdf/type/font_simple.rb +3 -3
- data/lib/hexapdf/type/font_true_type.rb +8 -0
- data/lib/hexapdf/type/font_type0.rb +2 -1
- data/lib/hexapdf/type/font_type1.rb +7 -1
- data/lib/hexapdf/type/font_type3.rb +61 -0
- data/lib/hexapdf/type/graphics_state_parameter.rb +8 -8
- data/lib/hexapdf/type/image.rb +10 -0
- data/lib/hexapdf/type/page.rb +83 -10
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +2 -2
- data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +1 -1
- data/test/hexapdf/font/test_true_type_wrapper.rb +1 -1
- data/test/hexapdf/font/test_type1_wrapper.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_cmap.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_directory.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_head.rb +7 -3
- data/test/hexapdf/layout/test_box.rb +57 -15
- data/test/hexapdf/layout/test_frame.rb +313 -0
- data/test/hexapdf/layout/test_inline_box.rb +1 -1
- data/test/hexapdf/layout/test_style.rb +74 -0
- data/test/hexapdf/layout/test_text_box.rb +77 -0
- data/test/hexapdf/layout/test_text_layouter.rb +220 -239
- data/test/hexapdf/layout/test_width_from_polygon.rb +108 -0
- data/test/hexapdf/test_dictionary_fields.rb +22 -26
- data/test/hexapdf/test_document.rb +3 -3
- data/test/hexapdf/test_reference.rb +1 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/test_font_true_type.rb +25 -0
- data/test/hexapdf/type/test_font_type1.rb +6 -0
- data/test/hexapdf/type/test_font_type3.rb +26 -0
- data/test/hexapdf/type/test_image.rb +10 -0
- data/test/hexapdf/type/test_page.rb +114 -0
- data/test/test_helper.rb +1 -1
- metadata +65 -17
- data/examples/text_layouter_shapes.rb +0 -170
data/lib/hexapdf/dictionary.rb
CHANGED
@@ -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
|
-
|
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
|
64
|
-
# call and +document+ is the HexaPDF::Document
|
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
|
-
@
|
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(
|
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
|
-
@
|
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
|
-
#
|
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
|
-
@
|
177
|
-
|
178
|
-
|
179
|
-
|
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
|
-
#
|
218
|
-
#
|
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
|
-
#
|
244
|
-
|
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
|
-
#
|
274
|
-
|
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
|
-
#
|
308
|
-
|
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 =
|
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
|
-
#
|
337
|
-
|
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
|
-
#
|
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
|
|
data/lib/hexapdf/document.rb
CHANGED
@@ -554,8 +554,8 @@ module HexaPDF
|
|
554
554
|
end
|
555
555
|
|
556
556
|
# :call-seq:
|
557
|
-
# doc.validate(auto_correct: true)
|
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
|
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
|
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].
|
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)).
|
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.
|
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]).
|
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]).
|
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.
|
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
|
@@ -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).
|
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).
|
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).
|
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).
|
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).
|
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
|
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
|
-
#
|
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).
|
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).
|
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).
|
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
|
data/lib/hexapdf/layout.rb
CHANGED
@@ -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
|
|