hexapdf 0.32.0 → 0.32.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c74f0e152b612b628321f312413a0eb88e52a41a809f7b37e993acb9e0436b2
4
- data.tar.gz: 9dfe8cfaaa10527695b70d9019636ed9cc6059367125a45a3dacde7bc1d8c3bb
3
+ metadata.gz: f0907362b897b39d61fa8f859510ccfb31ebd5ad88b94a9e36ff62c62fae813a
4
+ data.tar.gz: 469465941253bad5e769fc0dd5a57e437c578fdecda52a0fea908370ef8b7169
5
5
  SHA512:
6
- metadata.gz: f9ee0dbe507a6d31cbf024d2f34e69c15e491bc05a64a0f4039f38bcf6d52192245d6c7d2ac4cb65ae1eb110f8af735afcf13366e15975d96bcc17dda8e2a618
7
- data.tar.gz: 846ce0611e5f125011aa0535d2de69c15c356f6164f9b09f0a314f9968e34e4addda5396c03d82cd70dffc96eaa0408e1a6dd7a6fc55ce59646be5c3f8b4f9a6
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,
@@ -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: @border_color && canvas.color_from_specification(@border_color).components,
524
+ C: border_color,
519
525
  }
520
526
  (page[:Annots] ||= []) << page.document.add(annot)
521
527
  end
@@ -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(/(?:\n|\r\n?)endobj\b/)
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
- cap_height = style.font.wrapped_font.cap_height * style.font.scaling_factor / 1000.0 *
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
- encrypt_dict = document.trailer[:Encrypt]
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) || obj == encrypt_dict ||
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))
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '0.32.0'
40
+ VERSION = '0.32.2'
41
41
 
42
42
  end
@@ -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 and border color" do
577
- annot = call_link(dest: true, border: [10, 10, 2], border_color: [255])
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
- assert_equal([1.0], annot[:C].value)
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
@@ -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\nendobj\n1 0 obj\n6\nendobj\ntrailer\n<</Size 1>>")
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
 
@@ -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.0)>>
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.0)>>
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.0)>>
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
- FontBBox: [0, 1, 2, 3], ItalicAngle: 0, Ascent: 900,
18
- MissingWidth: 500, Descent: -100, CapHeight: 800, StemV: 20})
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
- def (@doc).trailer
27
- @trailer ||= {Encrypt: HexaPDF::Object.new({}, oid: 9)}
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 the encryption dictionary to be compressed" do
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.0
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-03-08 00:00:00.000000000 Z
11
+ date: 2023-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmdparse