hexapdf 0.44.0 → 0.46.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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +106 -47
  3. data/examples/019-acro_form.rb +5 -0
  4. data/examples/027-composer_optional_content.rb +6 -4
  5. data/examples/030-pdfa.rb +12 -11
  6. data/lib/hexapdf/cli/inspect.rb +5 -0
  7. data/lib/hexapdf/composer.rb +23 -1
  8. data/lib/hexapdf/configuration.rb +8 -0
  9. data/lib/hexapdf/content/canvas.rb +3 -3
  10. data/lib/hexapdf/content/canvas_composer.rb +1 -0
  11. data/lib/hexapdf/digital_signature/cms_handler.rb +31 -3
  12. data/lib/hexapdf/digital_signature/signing/default_handler.rb +9 -1
  13. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +5 -1
  14. data/lib/hexapdf/document/layout.rb +63 -30
  15. data/lib/hexapdf/document.rb +24 -2
  16. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  17. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  18. data/lib/hexapdf/importer.rb +15 -5
  19. data/lib/hexapdf/layout/box.rb +48 -36
  20. data/lib/hexapdf/layout/column_box.rb +3 -11
  21. data/lib/hexapdf/layout/container_box.rb +4 -4
  22. data/lib/hexapdf/layout/frame.rb +7 -6
  23. data/lib/hexapdf/layout/inline_box.rb +17 -23
  24. data/lib/hexapdf/layout/list_box.rb +27 -42
  25. data/lib/hexapdf/layout/page_style.rb +23 -16
  26. data/lib/hexapdf/layout/style.rb +5 -5
  27. data/lib/hexapdf/layout/table_box.rb +14 -10
  28. data/lib/hexapdf/layout/text_box.rb +60 -36
  29. data/lib/hexapdf/layout/text_fragment.rb +1 -1
  30. data/lib/hexapdf/layout/text_layouter.rb +7 -8
  31. data/lib/hexapdf/parser.rb +5 -1
  32. data/lib/hexapdf/rectangle.rb +4 -4
  33. data/lib/hexapdf/revisions.rb +1 -1
  34. data/lib/hexapdf/stream.rb +3 -3
  35. data/lib/hexapdf/tokenizer.rb +3 -2
  36. data/lib/hexapdf/type/acro_form/button_field.rb +2 -0
  37. data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
  38. data/lib/hexapdf/type/acro_form/field.rb +8 -0
  39. data/lib/hexapdf/type/acro_form/form.rb +2 -1
  40. data/lib/hexapdf/type/acro_form/text_field.rb +2 -0
  41. data/lib/hexapdf/type/form.rb +2 -2
  42. data/lib/hexapdf/version.rb +1 -1
  43. data/test/hexapdf/content/test_canvas_composer.rb +13 -8
  44. data/test/hexapdf/digital_signature/common.rb +66 -84
  45. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +7 -0
  46. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +9 -0
  47. data/test/hexapdf/digital_signature/test_cms_handler.rb +41 -1
  48. data/test/hexapdf/digital_signature/test_handler.rb +2 -1
  49. data/test/hexapdf/document/test_layout.rb +44 -5
  50. data/test/hexapdf/layout/test_box.rb +23 -5
  51. data/test/hexapdf/layout/test_frame.rb +21 -2
  52. data/test/hexapdf/layout/test_inline_box.rb +17 -28
  53. data/test/hexapdf/layout/test_list_box.rb +8 -8
  54. data/test/hexapdf/layout/test_page_style.rb +7 -2
  55. data/test/hexapdf/layout/test_table_box.rb +8 -1
  56. data/test/hexapdf/layout/test_text_box.rb +51 -29
  57. data/test/hexapdf/layout/test_text_layouter.rb +0 -3
  58. data/test/hexapdf/test_composer.rb +14 -5
  59. data/test/hexapdf/test_document.rb +27 -0
  60. data/test/hexapdf/test_importer.rb +17 -0
  61. data/test/hexapdf/test_revisions.rb +54 -41
  62. data/test/hexapdf/test_serializer.rb +1 -0
  63. data/test/hexapdf/type/acro_form/test_form.rb +9 -0
  64. metadata +2 -2
@@ -65,13 +65,13 @@ describe HexaPDF::Layout::ListBox do
65
65
  describe "fit" do
66
66
  [:default, :flow].each do |position|
67
67
  it "respects the set initial width, position #{position}" do
68
- box = create_box(children: @text_boxes[0, 2], width: 50, style: {position: position})
69
- check_box(box, 50, 80)
68
+ box = create_box(children: @text_boxes[0, 2], width: 55, style: {position: position})
69
+ check_box(box, 55, 80)
70
70
  end
71
71
 
72
72
  it "respects the set initial height, position #{position}" do
73
- box = create_box(children: @text_boxes[0, 2], height: 50, style: {position: position})
74
- check_box(box, 100, 40)
73
+ box = create_box(children: @text_boxes[0, 2], height: 55, style: {position: position})
74
+ check_box(box, 100, 55)
75
75
  end
76
76
 
77
77
  it "respects the set initial height even when it doesn't fit completely" do
@@ -123,7 +123,7 @@ describe HexaPDF::Layout::ListBox do
123
123
 
124
124
  it "fails if not even a part of the first list item fits" do
125
125
  box = create_box(children: @text_boxes[0, 2], height: 5)
126
- check_box(box, 100, 0, status: :failure)
126
+ check_box(box, 100, 5, status: :failure)
127
127
  end
128
128
 
129
129
  it "fails for unknown marker types" do
@@ -134,13 +134,13 @@ describe HexaPDF::Layout::ListBox do
134
134
 
135
135
  describe "split" do
136
136
  it "splits before a list item if no part of it will fit" do
137
- box = create_box(children: @text_boxes[0, 2])
138
- assert(box.fit(100, 20, @frame).overflow?)
137
+ box = create_box(children: @text_boxes[0, 3])
138
+ assert(box.fit(100, 22, @frame).overflow?)
139
139
  box_a, box_b = box.split
140
140
  assert_same(box, box_a)
141
141
  assert_equal(:show_first_marker, box_b.split_box?)
142
142
  assert_equal(1, box_a.instance_variable_get(:@results)[0].box_fitter.fit_results.size)
143
- assert_equal(1, box_b.children.size)
143
+ assert_equal(2, box_b.children.size)
144
144
  assert_equal(2, box_b.start_number)
145
145
  end
146
146
 
@@ -44,8 +44,13 @@ describe HexaPDF::Layout::PageStyle do
44
44
 
45
45
  it "works when no template is set" do
46
46
  style = HexaPDF::Layout::PageStyle.new
47
- page = style.create_page(@doc)
48
- assert_equal("", page.contents)
47
+ page1 = style.create_page(@doc)
48
+ frame1 = style.frame
49
+ assert_equal("", page1.contents)
50
+ assert_equal(523.275591, style.frame.width)
51
+
52
+ page2 = style.create_page(@doc)
53
+ refute_same(frame1, style.frame)
49
54
  end
50
55
 
51
56
  it "creates a default frame if none is set beforehand or during template execution" do
@@ -116,7 +116,14 @@ describe HexaPDF::Layout::TableBox::Cell do
116
116
  assert_equal(12, cell.preferred_height)
117
117
  end
118
118
 
119
- it "doesn't fit anything if the available width or height are too small" do
119
+ it "doesn't fit children that are too big" do
120
+ cell = create_cell(children: HexaPDF::Layout::Box.create(width: 300, height: 20))
121
+ assert(cell.fit(100, 100, @frame).failure?)
122
+ cell = create_cell(children: [HexaPDF::Layout::Box.create(width: 300, height: 20)])
123
+ assert(cell.fit(100, 100, @frame).failure?)
124
+ end
125
+
126
+ it "doesn't fit anything if the available width or height are too small even if there are no children" do
120
127
  cell = create_cell(children: nil)
121
128
  assert(cell.fit(10, 100, @frame).failure?)
122
129
  assert(cell.fit(100, 10, @frame).failure?)
@@ -42,31 +42,32 @@ describe HexaPDF::Layout::TextBox do
42
42
  end
43
43
 
44
44
  it "respects the set width and height" do
45
- box = create_box([@inline_box], width: 40, height: 50, style: {padding: 10})
45
+ box = create_box([@inline_box] * 5, width: 44, height: 50,
46
+ style: {padding: 10, text_align: :right, text_valign: :bottom})
46
47
  assert(box.fit(100, 100, @frame).success?)
47
- assert_equal(40, box.width)
48
+ assert_equal(44, box.width)
48
49
  assert_equal(50, box.height)
49
- assert_equal([10], box.instance_variable_get(:@result).lines.map(&:width))
50
+ assert_equal([20, 20, 10], box.instance_variable_get(:@result).lines.map(&:width))
50
51
  end
51
52
 
52
- it "fits into the frame's outline" do
53
- @frame.remove_area(Geom2D::Rectangle(0, 80, 20, 20))
54
- @frame.remove_area(Geom2D::Rectangle(80, 70, 20, 20))
55
- box = create_box([@inline_box] * 20, style: {position: :flow})
56
- assert(box.fit(100, 100, @frame).success?)
57
- assert_equal(100, box.width)
58
- assert_equal(30, box.height)
59
- end
53
+ describe "style option last_line_gap" do
54
+ it "is taken into account" do
55
+ box = create_box([@inline_box] * 5, style: {last_line_gap: true, line_spacing: :double})
56
+ assert(box.fit(100, 100, @frame).success?)
57
+ assert_equal(50, box.width)
58
+ assert_equal(20, box.height)
59
+ end
60
60
 
61
- it "takes the style option last_line_gap into account" do
62
- box = create_box([@inline_box] * 5, style: {last_line_gap: true, line_spacing: :double})
63
- assert(box.fit(100, 100, @frame).success?)
64
- assert_equal(50, box.width)
65
- assert_equal(20, box.height)
61
+ it "will have no effect for fixed-height boxes" do
62
+ box = create_box([@inline_box] * 5, height: 40, style: {last_line_gap: true, line_spacing: :double})
63
+ assert(box.fit(100, 100, @frame).success?)
64
+ assert_equal(50, box.width)
65
+ assert_equal(40, box.height)
66
+ end
66
67
  end
67
68
 
68
- it "uses the whole available width when aligning to the center or right" do
69
- [:center, :right].each do |align|
69
+ it "uses the whole available width when aligning to the center, right or justified" do
70
+ [:center, :right, :justify].each do |align|
70
71
  box = create_box([@inline_box], style: {text_align: align})
71
72
  assert(box.fit(100, 100, @frame).success?)
72
73
  assert_equal(100, box.width)
@@ -103,6 +104,33 @@ describe HexaPDF::Layout::TextBox do
103
104
  assert(box.fit(100, 100, @frame).success?)
104
105
  end
105
106
 
107
+ describe "position :flow" do
108
+ it "fits into the frame's outline" do
109
+ @frame.remove_area(Geom2D::Rectangle(0, 80, 20, 20))
110
+ @frame.remove_area(Geom2D::Rectangle(80, 70, 20, 20))
111
+ box = create_box([@inline_box] * 20, style: {position: :flow})
112
+ assert(box.fit(100, 100, @frame).success?)
113
+ assert_equal(100, box.width)
114
+ assert_equal(30, box.height)
115
+ end
116
+
117
+ it "respects a set initial height" do
118
+ box = create_box([@inline_box] * 20, height: 13, style: {position: :flow})
119
+ assert(box.fit(100, 100, @frame).overflow?)
120
+ assert_equal(100, box.width)
121
+ assert_equal(13, box.height)
122
+ end
123
+
124
+ it "respects top/bottom padding/border" do
125
+ @frame.remove_area(Geom2D::Rectangle(0, 80, 20, 20))
126
+ box = create_box([@inline_box] * 20, style: {position: :flow, padding: 10, border: {width: 2}})
127
+ assert(box.fit(100, 100, @frame).success?)
128
+ assert_equal(124, box.width)
129
+ assert_equal(54, box.height)
130
+ assert_equal([80, 100, 20], box.instance_variable_get(:@result).lines.map(&:width))
131
+ end
132
+ end
133
+
106
134
  it "fails if no item of the text box fits due to the width" do
107
135
  box = create_box([@inline_box])
108
136
  assert(box.fit(5, 20, @frame).failure?)
@@ -150,15 +178,12 @@ describe HexaPDF::Layout::TextBox do
150
178
  assert_operators(@canvas.contents, [[:save_graphics_state],
151
179
  [:restore_graphics_state],
152
180
  [:save_graphics_state],
153
- [:concatenate_matrix, [1, 0, 0, 1, 5, 10]],
154
- [:save_graphics_state],
155
- [:append_rectangle, [0, 0, 10, 10]],
181
+ [:append_rectangle, [5, 10, 10, 10]],
156
182
  [:clip_path_non_zero],
157
183
  [:end_path],
158
- [:append_rectangle, [0.5, 0.5, 9.0, 9.0]],
184
+ [:append_rectangle, [5.5, 10.5, 9.0, 9.0]],
159
185
  [:stroke_path],
160
186
  [:restore_graphics_state],
161
- [:restore_graphics_state],
162
187
  [:save_graphics_state],
163
188
  [:restore_graphics_state]])
164
189
  end
@@ -167,21 +192,18 @@ describe HexaPDF::Layout::TextBox do
167
192
  @frame.remove_area(Geom2D::Rectangle(0, 0, 40, 100))
168
193
  box = create_box([@inline_box], style: {position: :flow, border: {width: 1}})
169
194
  box.fit(60, 100, @frame)
170
- box.draw(@canvas, 0, 90)
195
+ box.draw(@canvas, 40, 88)
171
196
  assert_operators(@canvas.contents, [[:save_graphics_state],
172
- [:append_rectangle, [40, 90, 10, 10]],
197
+ [:append_rectangle, [40, 88, 12, 12]],
173
198
  [:clip_path_non_zero],
174
199
  [:end_path],
175
- [:append_rectangle, [40.5, 90.5, 9.0, 9.0]],
200
+ [:append_rectangle, [40.5, 88.5, 11.0, 11.0]],
176
201
  [:stroke_path],
177
202
  [:restore_graphics_state],
178
203
  [:save_graphics_state],
179
204
  [:restore_graphics_state],
180
205
  [:save_graphics_state],
181
206
  [:concatenate_matrix, [1, 0, 0, 1, 41, 89]],
182
- [:save_graphics_state],
183
- [:concatenate_matrix, [1, 0, 0, 1, 0, 0]],
184
- [:restore_graphics_state],
185
207
  [:restore_graphics_state],
186
208
  [:save_graphics_state],
187
209
  [:restore_graphics_state]])
@@ -812,11 +812,8 @@ describe HexaPDF::Layout::TextLayouter do
812
812
  [:restore_graphics_state],
813
813
  [:save_graphics_state],
814
814
  [:concatenate_matrix, [1, 0, 0, 1, 10, -40]],
815
- [:save_graphics_state],
816
- [:concatenate_matrix, [1, 0, 0, 1, 0, 0]],
817
815
  [:set_line_width, [2]],
818
816
  [:restore_graphics_state],
819
- [:restore_graphics_state],
820
817
  [:save_graphics_state],
821
818
  [:restore_graphics_state]])
822
819
  end
@@ -119,6 +119,13 @@ describe HexaPDF::Composer do
119
119
  end
120
120
  end
121
121
 
122
+ describe "styles" do
123
+ it "delegates to layout.styles" do
124
+ @composer.styles(base: {font_size: 30}, other: {font_size: 40})
125
+ assert_equal([:base, :other], @composer.document.layout.styles.keys)
126
+ end
127
+ end
128
+
122
129
  describe "page_style" do
123
130
  it "returns the page style if no argument or block is given" do
124
131
  page_style = @composer.page_style(:default)
@@ -225,8 +232,9 @@ describe HexaPDF::Composer do
225
232
  first_page_contents = @composer.canvas.contents
226
233
  @composer.draw_box(create_box(height: 400))
227
234
 
228
- box = create_box(height: 400)
229
- box.define_singleton_method(:split) do |*|
235
+ box = create_box
236
+ box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
237
+ box.define_singleton_method(:split_content) do |*|
230
238
  [box, HexaPDF::Layout::Box.new(height: 100) {}]
231
239
  end
232
240
  @composer.draw_box(box)
@@ -235,7 +243,7 @@ describe HexaPDF::Composer do
235
243
  [:concatenate_matrix, [1, 0, 0, 1, 36, 405.889764]],
236
244
  [:restore_graphics_state],
237
245
  [:save_graphics_state],
238
- [:concatenate_matrix, [1, 0, 0, 1, 36, 5.889764]],
246
+ [:concatenate_matrix, [1, 0, 0, 1, 36, 36]],
239
247
  [:restore_graphics_state]])
240
248
  assert_operators(@composer.canvas.contents,
241
249
  [[:save_graphics_state],
@@ -273,9 +281,10 @@ describe HexaPDF::Composer do
273
281
  box = create_box(height: 400)
274
282
  assert_same(box, @composer.draw_box(box))
275
283
 
276
- box = create_box(height: 400)
277
284
  split_box = create_box(height: 100)
278
- box.define_singleton_method(:split) {|*| [box, split_box] }
285
+ box = create_box
286
+ box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
287
+ box.define_singleton_method(:split_content) {|*| [box, split_box] }
279
288
  assert_same(split_box, @composer.draw_box(box))
280
289
  end
281
290
 
@@ -568,4 +568,31 @@ describe HexaPDF::Document do
568
568
  it "can be inspected and the output is not too large" do
569
569
  assert_match(/HexaPDF::Document:\d+/, @doc.inspect)
570
570
  end
571
+
572
+ describe "duplicate" do
573
+ it "creates an in-memory copy" do
574
+ doc = HexaPDF::Document.new
575
+ doc.pages.add.canvas.line_width(10)
576
+ doc.trailer.info[:Author] = 'HexaPDF'
577
+ doc.dispatch_message(:complete_objects)
578
+
579
+ dupped = doc.duplicate
580
+ assert_equal('HexaPDF', dupped.trailer.info[:Author])
581
+ doc.pages[0].canvas.line_cap_style(:round)
582
+ assert_equal("10 w\n", dupped.pages[0].contents)
583
+ end
584
+
585
+ it "doesn't copy the encryption state" do
586
+ doc = HexaPDF::Document.new
587
+ doc.pages.add.canvas.line_width(10)
588
+ doc.encrypt
589
+ io = StringIO.new
590
+ doc.write(io)
591
+
592
+ doc = HexaPDF::Document.new(io: io)
593
+ dupped = doc.duplicate
594
+ assert_equal("10 w\n", dupped.pages[0].contents)
595
+ refute(dupped.encrypted?)
596
+ end
597
+ end
571
598
  end
@@ -47,6 +47,13 @@ describe HexaPDF::Importer do
47
47
  refute_same(obj1, obj2)
48
48
  refute_same(obj1[:ref], obj2[:ref])
49
49
  end
50
+
51
+ it "duplicates the whole document" do
52
+ trailer = HexaPDF::Importer.copy(@dest, @source.trailer, allow_all: true)
53
+ refute_same(@source.catalog, trailer[:Root])
54
+ refute_same(@source.pages.root, trailer[:Root][:Pages])
55
+ assert_equal(90, trailer[:Root][:Pages][:Kids][0][:Rotate])
56
+ end
50
57
  end
51
58
 
52
59
  describe "import" do
@@ -121,6 +128,16 @@ describe HexaPDF::Importer do
121
128
  refute_same(dst_obj.data.stream, src_obj.data.stream)
122
129
  end
123
130
 
131
+ it "duplicates the stream if it is a FiberDoubleForString, e.g. when using Canvas" do
132
+ src_page = @source.pages[0]
133
+ src_page.canvas.line_width(10)
134
+ dst_page = @importer.import(src_page)
135
+ refute_same(dst_page, src_page)
136
+ refute_same(dst_page[:Contents].data.stream, src_page[:Contents].data.stream)
137
+ src_page.canvas.line_width(20)
138
+ assert_equal("10 w\n", dst_page.contents)
139
+ end
140
+
124
141
  it "does not import objects of type Catalog or Pages" do
125
142
  @obj[:catalog] = @source.catalog
126
143
  @obj[:pages] = @source.catalog.pages
@@ -357,46 +357,59 @@ describe HexaPDF::Revisions do
357
357
  end
358
358
  end
359
359
 
360
- it "merges the two revisions of a linearized PDF into one" do
361
- io = StringIO.new(<<~EOF)
362
- %PDF-1.2
363
- 5 0 obj
364
- <</Linearized 1>>
365
- endobj
366
- xref
367
- 5 1
368
- 0000000009 00000 n
369
- trailer
370
- <</ID[(a)(b)]/Info 1 0 R/Root 2 0 R/Size 6/Prev 394>>
371
- %
372
- 1 0 obj
373
- <</ModDate(D:20221205233910+01'00')/Producer(HexaPDF version 0.27.0)>>
374
- endobj
375
- 2 0 obj
376
- <</Type/Catalog/Pages 3 0 R>>
377
- endobj
378
- 3 0 obj
379
- <</Type/Pages/Kids[4 0 R]/Count 1>>
380
- endobj
381
- 4 0 obj
382
- <</Type/Page/MediaBox[0 0 595 842]/Parent 3 0 R/Resources<<>>>>
383
- endobj
384
- xref
385
- 0 5
386
- 0000000000 65535 f
387
- 0000000133 00000 n
388
- 0000000219 00000 n
389
- 0000000264 00000 n
390
- 0000000315 00000 n
391
- trailer
392
- <</ID[(a)(b)]/Info 1 0 R/Root 2 0 R/Size 5>>
393
- startxref
394
- 41
395
- %%EOF
396
- EOF
397
- doc = HexaPDF::Document.new(io: io, config: {'parser.try_xref_reconstruction' => false})
398
- assert(doc.revisions.parser.linearized?)
399
- assert_equal(1, doc.revisions.count)
400
- assert_same(5, doc.revisions.current.xref_section.max_oid)
360
+ describe "linearzied PDFs" do
361
+ before do
362
+ @io = StringIO.new(<<~EOF)
363
+ %PDF-1.2
364
+ 5 0 obj
365
+ <</Linearized 1>>
366
+ endobj
367
+ xref
368
+ 5 1
369
+ 0000000009 00000 n
370
+ trailer
371
+ <</ID[(a)(b)]/Info 1 0 R/Root 2 0 R/Size 6/Prev 394>>
372
+ %
373
+ 1 0 obj
374
+ <</ModDate(D:20221205233910+01'00')/Producer(HexaPDF version 0.27.0)>>
375
+ endobj
376
+ 2 0 obj
377
+ <</Type/Catalog/Pages 3 0 R>>
378
+ endobj
379
+ 3 0 obj
380
+ <</Type/Pages/Kids[4 0 R]/Count 1>>
381
+ endobj
382
+ 4 0 obj
383
+ <</Type/Page/MediaBox[0 0 595 842]/Parent 3 0 R/Resources<<>>>>
384
+ endobj
385
+ xref
386
+ 0 5
387
+ 0000000000 65535 f
388
+ 0000000133 00000 n
389
+ 0000000219 00000 n
390
+ 0000000264 00000 n
391
+ 0000000315 00000 n
392
+ trailer
393
+ <</ID[(a)(b)]/Info 1 0 R/Root 2 0 R/Size 5>>
394
+ startxref
395
+ 41
396
+ %%EOF
397
+ EOF
398
+ end
399
+
400
+ it "merges the two revisions of a linearized PDF into one" do
401
+ doc = HexaPDF::Document.new(io: @io, config: {'parser.try_xref_reconstruction' => false})
402
+ assert(doc.revisions.parser.linearized?)
403
+ assert_equal(1, doc.revisions.count)
404
+ assert_same(5, doc.revisions.current.xref_section.max_oid)
405
+ end
406
+
407
+ it "works for a fake linearized PDF where the first xref section isn't actually used" do
408
+ @io.string[-9..-1] = "394\n%%EOF\n"
409
+ doc = HexaPDF::Document.new(io: @io, config: {'parser.try_xref_reconstruction' => false})
410
+ assert(doc.revisions.parser.linearized?)
411
+ assert_equal(1, doc.revisions.count)
412
+ assert_same(4, doc.revisions.current.xref_section.max_oid)
413
+ end
401
414
  end
402
415
  end
@@ -89,6 +89,7 @@ describe HexaPDF::Serializer do
89
89
  assert_serialized('/ ', :"")
90
90
  assert_serialized('/H#c3#b6#c3#9fgang', :Hößgang)
91
91
  assert_serialized('/H#e8lp', "H\xE8lp".force_encoding('BINARY').intern)
92
+ assert_serialized('/#00#09#0a#0c#0d#20', :"\x00\t\n\f\r ")
92
93
  end
93
94
 
94
95
  it "serializes arrays" do
@@ -274,6 +274,15 @@ describe HexaPDF::Type::AcroForm::Form do
274
274
  assert(obj.null?)
275
275
  end
276
276
 
277
+ it "deletes a field with an embedded widget annotation" do
278
+ widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
279
+ assert_equal(widget, @field)
280
+ refute(@doc.pages[0][:Annots].empty?)
281
+ @acro_form.delete_field(@field)
282
+ assert(@doc.pages[0][:Annots].empty?)
283
+ assert(@field.null?)
284
+ end
285
+
277
286
  it "deletes all widget annotations from the document and the annotation array" do
278
287
  widget1 = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
279
288
  widget2 = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
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.44.0
4
+ version: 0.46.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-05 00:00:00.000000000 Z
11
+ date: 2024-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmdparse