hexapdf 0.32.0 → 0.32.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -0
- data/lib/hexapdf/cli.rb +4 -0
- data/lib/hexapdf/content/canvas.rb +1 -0
- data/lib/hexapdf/encryption/security_handler.rb +1 -1
- data/lib/hexapdf/layout/style.rb +7 -1
- data/lib/hexapdf/parser.rb +1 -1
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +3 -1
- data/lib/hexapdf/type/font_simple.rb +7 -0
- data/lib/hexapdf/type/font_type0.rb +5 -0
- data/lib/hexapdf/type/object_stream.rb +5 -2
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_canvas.rb +26 -0
- data/test/hexapdf/encryption/test_security_handler.rb +5 -0
- data/test/hexapdf/layout/test_style.rb +12 -3
- data/test/hexapdf/test_parser.rb +1 -1
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +13 -0
- data/test/hexapdf/type/test_font_simple.rb +8 -4
- data/test/hexapdf/type/test_font_type0.rb +6 -2
- data/test/hexapdf/type/test_object_stream.rb +9 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f0907362b897b39d61fa8f859510ccfb31ebd5ad88b94a9e36ff62c62fae813a
|
4
|
+
data.tar.gz: 469465941253bad5e769fc0dd5a57e437c578fdecda52a0fea908370ef8b7169
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2fc77d33ddc2c6fb377a778b82f92f01fed93d9a4e494cd57c97dc0a00fce4baa7cab185776f8a3303507d68b544e4404ac2f33b72c3b1a7cc7c65ee838c7dd7
|
7
|
+
data.tar.gz: 21a08984f424b91594a2d8e6fc705a7f9458a990918ee77adef469e758ae2fee976ba905a7a4a696c5c2728831d02f8845dc9b5316c28de4ff5c7f062521a7e3
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,38 @@
|
|
1
|
+
## 0.32.2 - 2023-05-06
|
2
|
+
|
3
|
+
### Changed
|
4
|
+
|
5
|
+
* Cross-reference table reconstruction to be more relaxed concerning the
|
6
|
+
`endobj` keyword
|
7
|
+
|
8
|
+
### Fixed
|
9
|
+
|
10
|
+
* [HexaPDF::Type::ObjectStream] to not compress any encryption dictionary
|
11
|
+
instead of only the current one
|
12
|
+
|
13
|
+
|
14
|
+
## 0.32.1 - 2023-04-20
|
15
|
+
|
16
|
+
### Added
|
17
|
+
|
18
|
+
* [HexaPDF::Type::FontType0#font_descriptor] and
|
19
|
+
[HexaPDF::Type::FontSimple#font_descriptor] for easy access to the font
|
20
|
+
descriptor
|
21
|
+
|
22
|
+
### Changed
|
23
|
+
|
24
|
+
* [HexaPDF::Content::Canvas#color_from_specification] to allow strings and color
|
25
|
+
objects without a wrapping array
|
26
|
+
|
27
|
+
### Fixed
|
28
|
+
|
29
|
+
* AES 128bit encryption to include unnecessary field in encryption dictionary to
|
30
|
+
work around buggy PDF libraries
|
31
|
+
* [HexaPDF::Layout::Style::LinkLayer] to correctly process the border color
|
32
|
+
* [HexaPDF::Type::AcroForm::AppearanceGenerator] to use fallback for font cap
|
33
|
+
height value when necessary
|
34
|
+
|
35
|
+
|
1
36
|
## 0.32.0 - 2023-03-08
|
2
37
|
|
3
38
|
### Added
|
data/lib/hexapdf/cli.rb
CHANGED
@@ -62,6 +62,10 @@ module HexaPDF
|
|
62
62
|
Application.new.parse(args)
|
63
63
|
rescue StandardError => e
|
64
64
|
$stderr.puts "Problem encountered: #{e.message}"
|
65
|
+
unless e.kind_of?(HexaPDF::Error)
|
66
|
+
$stderr.puts "--> The problem might indicate a faulty PDF or a bug in HexaPDF."
|
67
|
+
$stderr.puts "--> Please report this at https://github.com/gettalong/hexapdf/issues - thanks!"
|
68
|
+
end
|
65
69
|
exit(1)
|
66
70
|
end
|
67
71
|
|
@@ -2363,6 +2363,7 @@ module HexaPDF
|
|
2363
2363
|
# This utility method is meant for use by higher-level methods that need to convert a color
|
2364
2364
|
# specification into a color object for this Canvas object.
|
2365
2365
|
def color_from_specification(spec)
|
2366
|
+
spec = Array(spec)
|
2366
2367
|
if spec.length == 1 && spec[0].kind_of?(String)
|
2367
2368
|
ColorSpace.device_color_from_specification(spec)
|
2368
2369
|
elsif spec.length == 1 && spec[0].respond_to?(:color_space)
|
@@ -358,7 +358,7 @@ module HexaPDF
|
|
358
358
|
raise(HexaPDF::UnsupportedEncryptionError,
|
359
359
|
"Invalid key length #{key_length} specified")
|
360
360
|
end
|
361
|
-
dict[:Length] = key_length if dict[:V] == 2
|
361
|
+
dict[:Length] = key_length if dict[:V] == 4 || dict[:V] == 2
|
362
362
|
|
363
363
|
if ![:aes, :arc4].include?(algorithm)
|
364
364
|
raise(HexaPDF::UnsupportedEncryptionError,
|
data/lib/hexapdf/layout/style.rb
CHANGED
@@ -508,6 +508,12 @@ module HexaPDF
|
|
508
508
|
*matrix.evaluate(box.width, box.height), *matrix.evaluate(0, box.height)]
|
509
509
|
x_minmax = quad_points.values_at(0, 2, 4, 6).minmax
|
510
510
|
y_minmax = quad_points.values_at(1, 3, 5, 7).minmax
|
511
|
+
border_color = case @border_color
|
512
|
+
when [], nil
|
513
|
+
@border_color
|
514
|
+
else
|
515
|
+
canvas.color_from_specification(@border_color).components
|
516
|
+
end
|
511
517
|
annot = {
|
512
518
|
Subtype: :Link,
|
513
519
|
Rect: [x_minmax[0], y_minmax[0], x_minmax[1], y_minmax[1]],
|
@@ -515,7 +521,7 @@ module HexaPDF
|
|
515
521
|
Dest: @dest,
|
516
522
|
A: @action,
|
517
523
|
Border: @border,
|
518
|
-
C:
|
524
|
+
C: border_color,
|
519
525
|
}
|
520
526
|
(page[:Annots] ||= []) << page.document.add(annot)
|
521
527
|
end
|
data/lib/hexapdf/parser.rb
CHANGED
@@ -458,7 +458,7 @@ module HexaPDF
|
|
458
458
|
linearized = obj.kind_of?(Hash) && obj.key?(:Linearized)
|
459
459
|
@tokenizer.pos = pos
|
460
460
|
end
|
461
|
-
@tokenizer.scan_until(
|
461
|
+
@tokenizer.scan_until(/\bendobj\b/)
|
462
462
|
end
|
463
463
|
elsif token.kind_of?(Tokenizer::Token) && token == 'trailer'
|
464
464
|
obj = @tokenizer.next_object rescue nil
|
@@ -395,7 +395,9 @@ module HexaPDF
|
|
395
395
|
|
396
396
|
# Adobe seems to be vertically centering based on the cap height, if enough space is
|
397
397
|
# available
|
398
|
-
|
398
|
+
tmp_cap_height = style.font.wrapped_font.cap_height ||
|
399
|
+
style.font.pdf_object.font_descriptor&.[](:CapHeight)
|
400
|
+
cap_height = tmp_cap_height * style.font.scaling_factor / 1000.0 *
|
399
401
|
style.font_size
|
400
402
|
y = padding + (height - 2 * padding - cap_height) / 2.0
|
401
403
|
y = padding - style.scaled_font_descender if y < 0
|
@@ -53,6 +53,13 @@ module HexaPDF
|
|
53
53
|
define_field :FontDescriptor, type: :FontDescriptor, indirect: true
|
54
54
|
define_field :Encoding, type: [Dictionary, Symbol]
|
55
55
|
|
56
|
+
# Returns the font descriptor. May be +nil+ for a standard 14 font.
|
57
|
+
#
|
58
|
+
# The font descriptor is required except for the standard 14 fonts in PDF version up to 1.7.
|
59
|
+
def font_descriptor
|
60
|
+
self[:FontDescriptor]
|
61
|
+
end
|
62
|
+
|
56
63
|
# Returns the encoding object used for this font.
|
57
64
|
#
|
58
65
|
# Note that the encoding is cached internally when accessed the first time.
|
@@ -63,6 +63,11 @@ module HexaPDF
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
+
# Returns the font descriptor of the descendant font.
|
67
|
+
def font_descriptor
|
68
|
+
descendant_font[:FontDescriptor]
|
69
|
+
end
|
70
|
+
|
66
71
|
# Returns the writing mode which is either :horizontal or :vertical.
|
67
72
|
def writing_mode
|
68
73
|
cmap.wmode == 0 ? :horizontal : :vertical
|
@@ -172,13 +172,16 @@ module HexaPDF
|
|
172
172
|
serializer = Serializer.new
|
173
173
|
obj_to_stm = {}
|
174
174
|
|
175
|
-
|
175
|
+
is_encrypt_dict = document.revisions.each.with_object({}) do |rev, hash|
|
176
|
+
hash[rev.trailer[:Encrypt]] = true
|
177
|
+
end
|
176
178
|
while index < objects.size / 2
|
177
179
|
obj = revision.object(objects[index])
|
178
180
|
|
179
181
|
# Due to a bug in Adobe Acrobat, the Catalog may not be in an object stream if the
|
180
182
|
# document is encrypted
|
181
|
-
if obj.nil? || obj.null? || obj.gen != 0 || obj.kind_of?(Stream) ||
|
183
|
+
if obj.nil? || obj.null? || obj.gen != 0 || obj.kind_of?(Stream) ||
|
184
|
+
is_encrypt_dict[obj] ||
|
182
185
|
obj.type == :Catalog ||
|
183
186
|
obj.type == :Sig || obj.type == :DocTimeStamp ||
|
184
187
|
(obj.respond_to?(:key?) && obj.key?(:ByteRange) && obj.key?(:Contents))
|
data/lib/hexapdf/version.rb
CHANGED
@@ -1280,4 +1280,30 @@ describe HexaPDF::Content::Canvas do
|
|
1280
1280
|
end
|
1281
1281
|
end
|
1282
1282
|
end
|
1283
|
+
|
1284
|
+
describe "color_from_specification "do
|
1285
|
+
it "accepts a color string" do
|
1286
|
+
assert_equal([1, 0, 0], @canvas.color_from_specification("red").components)
|
1287
|
+
end
|
1288
|
+
|
1289
|
+
it "accepts a color string wrapped in an array" do
|
1290
|
+
assert_equal([1, 0, 0], @canvas.color_from_specification(["red"]).components)
|
1291
|
+
end
|
1292
|
+
|
1293
|
+
it "accepts a color object" do
|
1294
|
+
color = @canvas.color_from_specification("red")
|
1295
|
+
assert_equal(color, @canvas.color_from_specification(color))
|
1296
|
+
end
|
1297
|
+
|
1298
|
+
it "accepts a color object wrapped in an array" do
|
1299
|
+
color = @canvas.color_from_specification("red")
|
1300
|
+
assert_equal(color, @canvas.color_from_specification([color]))
|
1301
|
+
end
|
1302
|
+
|
1303
|
+
it "accepts an array with 1, 3, or 4 color values" do
|
1304
|
+
assert_equal([1], @canvas.color_from_specification([255]).components)
|
1305
|
+
assert_equal([1, 0, 0], @canvas.color_from_specification([255, 0, 0]).components)
|
1306
|
+
assert_equal([1, 0, 0, 0], @canvas.color_from_specification([100, 0, 0, 0]).components)
|
1307
|
+
end
|
1308
|
+
end
|
1283
1309
|
end
|
@@ -141,6 +141,11 @@ describe HexaPDF::Encryption::SecurityHandler do
|
|
141
141
|
@handler.set_up_encryption(key_length: key_length, algorithm: algorithm)
|
142
142
|
assert(result == @handler.dict[:Length])
|
143
143
|
end
|
144
|
+
|
145
|
+
# Work-around buggy software
|
146
|
+
@handler.set_up_encryption(key_length: 128, algorithm: :aes)
|
147
|
+
assert_equal(4, @handler.dict[:V])
|
148
|
+
assert_equal(128, @handler.dict[:Length])
|
144
149
|
end
|
145
150
|
|
146
151
|
it "calls the prepare_encryption method" do
|
@@ -573,10 +573,19 @@ describe HexaPDF::Layout::Style::LinkLayer do
|
|
573
573
|
assert_equal([0, 0, 1], annot[:Border].value)
|
574
574
|
end
|
575
575
|
|
576
|
-
it "uses the specified border
|
577
|
-
annot = call_link(dest: true, border: [10, 10, 2]
|
576
|
+
it "uses the specified border" do
|
577
|
+
annot = call_link(dest: true, border: [10, 10, 2])
|
578
578
|
assert_equal([10, 10, 2], annot[:Border].value)
|
579
|
-
|
579
|
+
end
|
580
|
+
|
581
|
+
it "uses the specified border color" do
|
582
|
+
annot = call_link(dest: true, border_color: "red")
|
583
|
+
assert_equal([1.0, 0, 0], annot[:C].value)
|
584
|
+
end
|
585
|
+
|
586
|
+
it "works when the border color is transparent" do
|
587
|
+
annot = call_link(dest: true, border_color: [])
|
588
|
+
assert_equal([], annot[:C].value)
|
580
589
|
end
|
581
590
|
|
582
591
|
it "works for simple destinations" do
|
data/test/hexapdf/test_parser.rb
CHANGED
@@ -609,7 +609,7 @@ describe HexaPDF::Parser do
|
|
609
609
|
end
|
610
610
|
|
611
611
|
it "serially parses the contents" do
|
612
|
-
create_parser("1 0 obj\n5\
|
612
|
+
create_parser("1 0 obj\n5 endobj\n1 0 obj\n6\nendobj\ntrailer\n<</Size 1>>")
|
613
613
|
assert_equal(6, @parser.load_object(@xref).value)
|
614
614
|
end
|
615
615
|
|
data/test/hexapdf/test_writer.rb
CHANGED
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
|
|
40
40
|
219
|
41
41
|
%%EOF
|
42
42
|
3 0 obj
|
43
|
-
<</Producer(HexaPDF version 0.32.
|
43
|
+
<</Producer(HexaPDF version 0.32.2)>>
|
44
44
|
endobj
|
45
45
|
xref
|
46
46
|
3 1
|
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
|
|
72
72
|
141
|
73
73
|
%%EOF
|
74
74
|
6 0 obj
|
75
|
-
<</Producer(HexaPDF version 0.32.
|
75
|
+
<</Producer(HexaPDF version 0.32.2)>>
|
76
76
|
endobj
|
77
77
|
2 0 obj
|
78
78
|
<</Length 10>>stream
|
@@ -214,7 +214,7 @@ describe HexaPDF::Writer do
|
|
214
214
|
<</Type/Page/MediaBox[0 0 595 842]/Parent 2 0 R/Resources<<>>>>
|
215
215
|
endobj
|
216
216
|
5 0 obj
|
217
|
-
<</Producer(HexaPDF version 0.32.
|
217
|
+
<</Producer(HexaPDF version 0.32.2)>>
|
218
218
|
endobj
|
219
219
|
4 0 obj
|
220
220
|
<</Root 1 0 R/Info 5 0 R/Size 6/Type/XRef/W[1 1 2]/Index[0 6]/Filter/FlateDecode/DecodeParms<</Columns 4/Predictor 12>>/Length 33>>stream
|
@@ -519,6 +519,19 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
519
519
|
range: 7)
|
520
520
|
end
|
521
521
|
|
522
|
+
it "falls back to the cap height in the font descriptor for vertical alignment" do
|
523
|
+
font_metrics = @form.default_resources.font(:F1).font_wrapper.wrapped_font.metrics
|
524
|
+
cap_height = font_metrics.cap_height
|
525
|
+
font_metrics.cap_height = nil
|
526
|
+
|
527
|
+
@generator.create_appearances
|
528
|
+
assert_operators(@widget[:AP][:N].stream,
|
529
|
+
[:set_text_matrix, [1, 0, 0, 1, 2, 6.41]],
|
530
|
+
range: 7)
|
531
|
+
ensure
|
532
|
+
font_metrics.cap_height = cap_height
|
533
|
+
end
|
534
|
+
|
522
535
|
it "vertically aligns to the font descender if the text is too high" do
|
523
536
|
@widget[:Rect].height = 5
|
524
537
|
@generator.create_appearances
|
@@ -13,15 +13,19 @@ describe HexaPDF::Type::FontSimple do
|
|
13
13
|
<22> <0042>
|
14
14
|
endbfchar
|
15
15
|
EOF
|
16
|
-
font_descriptor = @doc.add({Type: :FontDescriptor, FontName: :Embedded, Flags: 0b100,
|
17
|
-
|
18
|
-
|
16
|
+
@font_descriptor = @doc.add({Type: :FontDescriptor, FontName: :Embedded, Flags: 0b100,
|
17
|
+
FontBBox: [0, 1, 2, 3], ItalicAngle: 0, Ascent: 900,
|
18
|
+
MissingWidth: 500, Descent: -100, CapHeight: 800, StemV: 20})
|
19
19
|
@font = @doc.add({Type: :Font, Encoding: :WinAnsiEncoding,
|
20
|
-
BaseFont: :Embedded, FontDescriptor: font_descriptor, ToUnicode: cmap,
|
20
|
+
BaseFont: :Embedded, FontDescriptor: @font_descriptor, ToUnicode: cmap,
|
21
21
|
FirstChar: 32, LastChar: 34, Widths: [600, 0, 700]},
|
22
22
|
type: HexaPDF::Type::FontSimple)
|
23
23
|
end
|
24
24
|
|
25
|
+
it "returns the font descriptor of the font" do
|
26
|
+
assert_same(@font_descriptor, @font.font_descriptor)
|
27
|
+
end
|
28
|
+
|
25
29
|
describe "encoding" do
|
26
30
|
it "fails if /Encoding is absent because encoding_from_font is not implemented" do
|
27
31
|
@font.delete(:Encoding)
|
@@ -7,8 +7,8 @@ require 'hexapdf/type/font_type0'
|
|
7
7
|
describe HexaPDF::Type::FontType0 do
|
8
8
|
before do
|
9
9
|
@doc = HexaPDF::Document.new
|
10
|
-
fd = @doc.add({Type: :FontDescriptor, FontBBox: [0, 1, 2, 3]})
|
11
|
-
@cid_font = @doc.wrap({Type: :Font, Subtype: :CIDFontType2, W: [633, [100]], FontDescriptor: fd,
|
10
|
+
@fd = @doc.add({Type: :FontDescriptor, FontBBox: [0, 1, 2, 3]})
|
11
|
+
@cid_font = @doc.wrap({Type: :Font, Subtype: :CIDFontType2, W: [633, [100]], FontDescriptor: @fd,
|
12
12
|
CIDSystemInfo: {Registry: 'Adobe', Ordering: 'Japan1', Supplement: 1}})
|
13
13
|
@font = @doc.wrap({Type: :Font, Subtype: :Type0, Encoding: :H, DescendantFonts: [@cid_font]})
|
14
14
|
end
|
@@ -26,6 +26,10 @@ describe HexaPDF::Type::FontType0 do
|
|
26
26
|
assert_equal(@cid_font.value, @font.descendant_font.value)
|
27
27
|
end
|
28
28
|
|
29
|
+
it "returns the font descriptor of the descendant font" do
|
30
|
+
assert_same(@fd, @font.font_descriptor)
|
31
|
+
end
|
32
|
+
|
29
33
|
it "uses the descendant font for getting the width of a code point" do
|
30
34
|
assert_equal(100, @font.width(0x2121))
|
31
35
|
end
|
@@ -23,9 +23,14 @@ describe HexaPDF::Type::ObjectStream do
|
|
23
23
|
before do
|
24
24
|
@doc = Object.new
|
25
25
|
@doc.instance_variable_set(:@version, '1.5')
|
26
|
-
|
27
|
-
|
26
|
+
@doc.define_singleton_method(:revisions) do
|
27
|
+
rev1 = Object.new
|
28
|
+
def rev1.trailer; {Encrypt: HexaPDF::Object.new({}, oid: 10)}; end
|
29
|
+
rev2 = Object.new
|
30
|
+
def rev2.trailer; {Encrypt: HexaPDF::Object.new({}, oid: 9)}; end
|
31
|
+
@revisions ||= [rev1, rev2]
|
28
32
|
end
|
33
|
+
@doc.define_singleton_method(:trailer) { revisions.last.trailer }
|
29
34
|
@obj = HexaPDF::Type::ObjectStream.new({N: 2, First: 8}, oid: 1, document: @doc,
|
30
35
|
stream: "1 0 5 2 5 [1 2]")
|
31
36
|
end
|
@@ -96,8 +101,9 @@ describe HexaPDF::Type::ObjectStream do
|
|
96
101
|
assert_equal("", @obj.stream)
|
97
102
|
end
|
98
103
|
|
99
|
-
it "doesn't allow
|
104
|
+
it "doesn't allow an encryption dictionary to be compressed" do
|
100
105
|
@obj.add_object(@doc.trailer[:Encrypt])
|
106
|
+
@obj.add_object(@doc.revisions[0].trailer[:Encrypt])
|
101
107
|
@obj.write_objects(@revision)
|
102
108
|
assert_equal(0, @obj.value[:N])
|
103
109
|
assert_equal(0, @obj.value[:First])
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hexapdf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.32.
|
4
|
+
version: 0.32.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Leitner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-05-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cmdparse
|