hexapdf 0.33.0 → 0.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -1
  3. data/examples/026-optional_content.rb +55 -0
  4. data/examples/027-composer_optional_content.rb +83 -0
  5. data/lib/hexapdf/cli/command.rb +7 -1
  6. data/lib/hexapdf/cli/fonts.rb +1 -1
  7. data/lib/hexapdf/cli/inspect.rb +2 -4
  8. data/lib/hexapdf/composer.rb +2 -1
  9. data/lib/hexapdf/configuration.rb +21 -1
  10. data/lib/hexapdf/content/canvas.rb +52 -0
  11. data/lib/hexapdf/content/operator.rb +2 -0
  12. data/lib/hexapdf/dictionary.rb +1 -0
  13. data/lib/hexapdf/dictionary_fields.rb +1 -2
  14. data/lib/hexapdf/digital_signature/verification_result.rb +1 -2
  15. data/lib/hexapdf/document/layout.rb +3 -0
  16. data/lib/hexapdf/document/pages.rb +1 -1
  17. data/lib/hexapdf/document.rb +7 -0
  18. data/lib/hexapdf/encryption/ruby_aes.rb +10 -20
  19. data/lib/hexapdf/layout/box.rb +23 -3
  20. data/lib/hexapdf/layout/column_box.rb +2 -1
  21. data/lib/hexapdf/layout/frame.rb +23 -6
  22. data/lib/hexapdf/layout/inline_box.rb +20 -9
  23. data/lib/hexapdf/layout/list_box.rb +34 -20
  24. data/lib/hexapdf/layout/page_style.rb +2 -1
  25. data/lib/hexapdf/layout/style.rb +46 -6
  26. data/lib/hexapdf/layout/table_box.rb +9 -7
  27. data/lib/hexapdf/layout/text_box.rb +9 -2
  28. data/lib/hexapdf/layout/text_fragment.rb +28 -2
  29. data/lib/hexapdf/layout/text_layouter.rb +21 -5
  30. data/lib/hexapdf/stream.rb +1 -2
  31. data/lib/hexapdf/type/actions/set_ocg_state.rb +86 -0
  32. data/lib/hexapdf/type/actions.rb +1 -0
  33. data/lib/hexapdf/type/annotations/text.rb +1 -2
  34. data/lib/hexapdf/type/catalog.rb +10 -1
  35. data/lib/hexapdf/type/cid_font.rb +15 -1
  36. data/lib/hexapdf/type/form.rb +75 -5
  37. data/lib/hexapdf/type/optional_content_configuration.rb +170 -0
  38. data/lib/hexapdf/type/optional_content_group.rb +370 -0
  39. data/lib/hexapdf/type/optional_content_membership.rb +63 -0
  40. data/lib/hexapdf/type/optional_content_properties.rb +158 -0
  41. data/lib/hexapdf/type/page.rb +27 -11
  42. data/lib/hexapdf/type/page_label.rb +4 -8
  43. data/lib/hexapdf/type.rb +4 -0
  44. data/lib/hexapdf/utils/pdf_doc_encoding.rb +0 -1
  45. data/lib/hexapdf/version.rb +1 -1
  46. data/test/hexapdf/content/test_canvas.rb +49 -0
  47. data/test/hexapdf/document/test_layout.rb +7 -2
  48. data/test/hexapdf/document/test_pages.rb +6 -6
  49. data/test/hexapdf/layout/test_box.rb +13 -4
  50. data/test/hexapdf/layout/test_frame.rb +13 -1
  51. data/test/hexapdf/layout/test_inline_box.rb +17 -8
  52. data/test/hexapdf/layout/test_list_box.rb +48 -31
  53. data/test/hexapdf/layout/test_style.rb +10 -0
  54. data/test/hexapdf/layout/test_table_box.rb +32 -26
  55. data/test/hexapdf/layout/test_text_box.rb +8 -0
  56. data/test/hexapdf/layout/test_text_fragment.rb +33 -0
  57. data/test/hexapdf/layout/test_text_layouter.rb +32 -5
  58. data/test/hexapdf/test_composer.rb +10 -0
  59. data/test/hexapdf/test_dictionary.rb +10 -0
  60. data/test/hexapdf/test_document.rb +4 -0
  61. data/test/hexapdf/test_writer.rb +3 -3
  62. data/test/hexapdf/type/actions/test_set_ocg_state.rb +40 -0
  63. data/test/hexapdf/type/test_catalog.rb +11 -0
  64. data/test/hexapdf/type/test_form.rb +119 -0
  65. data/test/hexapdf/type/test_optional_content_configuration.rb +112 -0
  66. data/test/hexapdf/type/test_optional_content_group.rb +158 -0
  67. data/test/hexapdf/type/test_optional_content_properties.rb +109 -0
  68. data/test/hexapdf/type/test_page.rb +2 -2
  69. metadata +14 -3
@@ -5,6 +5,10 @@ require 'hexapdf/document'
5
5
  require 'hexapdf/layout/table_box'
6
6
 
7
7
  describe HexaPDF::Layout::TableBox::Cell do
8
+ before do
9
+ @frame = HexaPDF::Layout::Frame.new(0, 0, 0, 0)
10
+ end
11
+
8
12
  def create_cell(**kwargs)
9
13
  HexaPDF::Layout::TableBox::Cell.new(row: 1, column: 1, **kwargs)
10
14
  end
@@ -46,7 +50,7 @@ describe HexaPDF::Layout::TableBox::Cell do
46
50
 
47
51
  it "returns true if the cell has no content" do
48
52
  cell = create_cell(children: nil, style: {border: {width: 0}})
49
- cell.fit(100, 100, nil)
53
+ cell.fit(100, 100, @frame)
50
54
  assert(cell.empty?)
51
55
  end
52
56
  end
@@ -54,7 +58,7 @@ describe HexaPDF::Layout::TableBox::Cell do
54
58
  describe "update_height" do
55
59
  it "updates the height to the correct one" do
56
60
  cell = create_cell(children: HexaPDF::Layout::Box.create(width: 10, height: 10))
57
- cell.fit(100, 100, nil)
61
+ cell.fit(100, 100, @frame)
58
62
  assert_equal(22, cell.height)
59
63
  cell.update_height(50)
60
64
  assert_equal(50, cell.height)
@@ -62,7 +66,7 @@ describe HexaPDF::Layout::TableBox::Cell do
62
66
 
63
67
  it "fails if the given height is smaller than the one determined during #fit" do
64
68
  cell = create_cell(children: HexaPDF::Layout::Box.create(width: 10, height: 10))
65
- cell.fit(100, 100, nil)
69
+ cell.fit(100, 100, @frame)
66
70
  err = assert_raises(HexaPDF::Error) { cell.update_height(5) }
67
71
  assert_match(/at least as big/, err.message)
68
72
  end
@@ -71,7 +75,7 @@ describe HexaPDF::Layout::TableBox::Cell do
71
75
  describe "fit" do
72
76
  it "fits a single box" do
73
77
  cell = create_cell(children: HexaPDF::Layout::Box.create(width: 20, height: 10))
74
- cell.fit(100, 100, nil)
78
+ cell.fit(100, 100, @frame)
75
79
  assert_equal(100, cell.width)
76
80
  assert_equal(22, cell.height)
77
81
  assert_equal(32, cell.preferred_width)
@@ -80,7 +84,7 @@ describe HexaPDF::Layout::TableBox::Cell do
80
84
 
81
85
  it "fits a single box with horizontal aligning not being :left" do
82
86
  cell = create_cell(children: HexaPDF::Layout::Box.create(width: 20, height: 10, position_hint: :center))
83
- cell.fit(100, 100, nil)
87
+ cell.fit(100, 100, @frame)
84
88
  assert_equal(66, cell.preferred_width)
85
89
  end
86
90
 
@@ -88,7 +92,7 @@ describe HexaPDF::Layout::TableBox::Cell do
88
92
  box1 = HexaPDF::Layout::Box.create(width: 20, height: 10)
89
93
  box2 = HexaPDF::Layout::Box.create(width: 50, height: 15)
90
94
  cell = create_cell(children: [box1, box2])
91
- cell.fit(100, 100, nil)
95
+ cell.fit(100, 100, @frame)
92
96
  assert_equal(100, cell.width)
93
97
  assert_equal(37, cell.height)
94
98
  assert_equal(62, cell.preferred_width)
@@ -99,13 +103,13 @@ describe HexaPDF::Layout::TableBox::Cell do
99
103
  box1 = HexaPDF::Layout::Box.create(width: 20, height: 10, position_hint: :center)
100
104
  box2 = HexaPDF::Layout::Box.create(width: 50, height: 15)
101
105
  cell = create_cell(children: [box1, box2])
102
- cell.fit(100, 100, nil)
106
+ cell.fit(100, 100, @frame)
103
107
  assert_equal(66, cell.preferred_width)
104
108
  end
105
109
 
106
110
  it "fits the cell even if it has no content" do
107
111
  cell = create_cell(children: nil)
108
- cell.fit(100, 100, nil)
112
+ cell.fit(100, 100, @frame)
109
113
  assert_equal(100, cell.width)
110
114
  assert_equal(12, cell.height)
111
115
  assert_equal(12, cell.preferred_width)
@@ -114,8 +118,8 @@ describe HexaPDF::Layout::TableBox::Cell do
114
118
 
115
119
  it "doesn't fit anything if the available width or height are too small" do
116
120
  cell = create_cell(children: nil)
117
- refute(cell.fit(10, 100, nil))
118
- refute(cell.fit(100, 10, nil))
121
+ refute(cell.fit(10, 100, @frame))
122
+ refute(cell.fit(100, 10, @frame))
119
123
  end
120
124
  end
121
125
 
@@ -129,7 +133,7 @@ describe HexaPDF::Layout::TableBox::Cell do
129
133
  box1 = HexaPDF::Layout::Box.create(width: 20, height: 10, position_hint: :center, &draw_block)
130
134
  box2 = HexaPDF::Layout::Box.create(width: 50, height: 15, &draw_block)
131
135
  box = create_cell(children: [box1, box2])
132
- box.fit(100, 100, nil)
136
+ box.fit(100, 100, @frame)
133
137
  box.draw(@canvas, 10, 75)
134
138
  operators = [[:save_graphics_state],
135
139
  [:append_rectangle, [9.5, 74.5, 101.0, 38.0]],
@@ -153,7 +157,7 @@ describe HexaPDF::Layout::TableBox::Cell do
153
157
 
154
158
  it "works for a cell without content" do
155
159
  box = create_cell(children: nil, style: {border: {width: 0}})
156
- box.fit(100, 100, nil)
160
+ box.fit(100, 100, @frame)
157
161
  box.draw(@canvas, 10, 75)
158
162
  assert_operators(@canvas.contents, [])
159
163
  end
@@ -355,7 +359,9 @@ end
355
359
 
356
360
  describe HexaPDF::Layout::TableBox do
357
361
  before do
358
- @frame = HexaPDF::Layout::Frame.new(0, 0, 160, 100)
362
+ @doc = HexaPDF::Document.new
363
+ @page = @doc.pages.add
364
+ @frame = HexaPDF::Layout::Frame.new(0, 0, 160, 100, context: @page)
359
365
  draw_block = lambda {|canvas, _box| canvas.move_to(0, 0).end_path }
360
366
  @fixed_size_boxes = 15.times.map { HexaPDF::Layout::Box.new(width: 20, height: 10, &draw_block) }
361
367
  end
@@ -425,7 +431,7 @@ describe HexaPDF::Layout::TableBox do
425
431
  footer = lambda {|_| [[nil]] }
426
432
  box = create_box(header: header, footer: footer, cells: [[nil], [nil]],
427
433
  cell_style: {background_color: 'black'})
428
- refute(box.fit(100, 40, nil))
434
+ refute(box.fit(100, 40, @frame))
429
435
  box_a, box_b = box.split(100, 40, nil)
430
436
  assert_same(box_a, box)
431
437
  assert_equal('black', box_b.header_cells[0, 0].style.background_color)
@@ -544,8 +550,8 @@ describe HexaPDF::Layout::TableBox do
544
550
  describe "split" do
545
551
  it "splits the table if some rows could not be fit into the available region" do
546
552
  box = create_box
547
- refute(box.fit(100, 25, nil))
548
- box_a, box_b = box.split(100, 25, nil)
553
+ refute(box.fit(100, 25, @frame))
554
+ box_a, box_b = box.split(100, 25, @frame)
549
555
  assert_same(box_a, box)
550
556
  assert(box_b.split_box?)
551
557
 
@@ -560,8 +566,8 @@ describe HexaPDF::Layout::TableBox do
560
566
  [{header: cells_creator}, {footer: cells_creator}].each do |args|
561
567
  box = create_box(**args)
562
568
  box.cells.style(padding: 0, border: {width: 0})
563
- refute(box.fit(100, 25, nil))
564
- box_a, box_b = box.split(100, 25, nil)
569
+ refute(box.fit(100, 25, @frame))
570
+ box_a, box_b = box.split(100, 25, @frame)
565
571
  assert_nil(box_a)
566
572
  assert_same(box_b, box)
567
573
  end
@@ -571,8 +577,8 @@ describe HexaPDF::Layout::TableBox do
571
577
  cells_creator = lambda {|_| [@fixed_size_boxes[10, 2]] }
572
578
  [{header: cells_creator}, {footer: cells_creator}].each do |args|
573
579
  box = create_box(**args)
574
- refute(box.fit(100, 50, nil))
575
- box_a, box_b = box.split(100, 50, nil)
580
+ refute(box.fit(100, 50, @frame))
581
+ box_a, box_b = box.split(100, 50, @frame)
576
582
  assert_same(box_a, box)
577
583
 
578
584
  assert_equal(0, box_a.start_row_index)
@@ -591,12 +597,12 @@ describe HexaPDF::Layout::TableBox do
591
597
 
592
598
  describe "draw_content" do
593
599
  before do
594
- @canvas = HexaPDF::Document.new.pages.add.canvas
600
+ @canvas = @page.canvas
595
601
  end
596
602
 
597
603
  it "draws the result onto the canvas" do
598
604
  box = create_box
599
- box.fit(100, 100, nil)
605
+ box.fit(100, 100, @frame)
600
606
  box.draw(@canvas, 20, 10)
601
607
  operators = [[:save_graphics_state],
602
608
  [:append_rectangle, [20.0, 32.0, 50.5, 23.0]],
@@ -651,9 +657,9 @@ describe HexaPDF::Layout::TableBox do
651
657
 
652
658
  it "correctly works for split boxes" do
653
659
  box = create_box(cell_style: {padding: 0, border: {width: 0}})
654
- refute(box.fit(100, 10, nil))
655
- _, split_box = box.split(100, 10, nil)
656
- assert(split_box.fit(100, 100, nil))
660
+ refute(box.fit(100, 10, @frame))
661
+ _, split_box = box.split(100, 10, @frame)
662
+ assert(split_box.fit(100, 100, @frame))
657
663
 
658
664
  box.draw(@canvas, 20, 10)
659
665
  split_box.draw(@canvas, 0, 50)
@@ -684,7 +690,7 @@ describe HexaPDF::Layout::TableBox do
684
690
  box = create_box(header: lambda {|_| [@fixed_size_boxes[10, 1]] },
685
691
  footer: lambda {|_| [@fixed_size_boxes[12, 1]] },
686
692
  cell_style: {padding: 0, border: {width: 0}})
687
- box.fit(100, 100, nil)
693
+ box.fit(100, 100, @frame)
688
694
  box.draw(@canvas, 20, 10)
689
695
  operators = [[:save_graphics_state],
690
696
  [:concatenate_matrix, [1, 0, 0, 1, 20, 40]],
@@ -25,6 +25,14 @@ describe HexaPDF::Layout::TextBox do
25
25
  end
26
26
  end
27
27
 
28
+ it "returns the text contents as string" do
29
+ doc = HexaPDF::Document.new
30
+ font = doc.fonts.add("Times")
31
+ box = create_box([HexaPDF::Layout::TextFragment.create('Test ', font: font), @inline_box,
32
+ HexaPDF::Layout::TextFragment.create('here', font: font)])
33
+ assert_equal('Test here', box.text)
34
+ end
35
+
28
36
  describe "fit" do
29
37
  it "fits into a rectangular area" do
30
38
  box = create_box([@inline_box] * 5, style: {padding: 10})
@@ -51,6 +51,10 @@ describe HexaPDF::Layout::TextFragment do
51
51
  end
52
52
  end
53
53
 
54
+ it "returns the text value of the items as string" do
55
+ assert_equal("Hal lo\u{00a0}d\n", setup_fragment(@font.decode_utf8("Hal lo\u{00a0}d\n")).text)
56
+ end
57
+
54
58
  it "allows duplicating with only its attributes while also setting new items" do
55
59
  setup_fragment([20])
56
60
  @fragment.properties['key'] = :value
@@ -392,6 +396,35 @@ describe HexaPDF::Layout::TextFragment do
392
396
  end
393
397
  end
394
398
 
399
+ describe "fill_horizontal!" do
400
+ before do
401
+ @fragment = HexaPDF::Layout::TextFragment.create('ab', fill_horizontal: 1, font: @font)
402
+ end
403
+
404
+ it "returns the fragment if the given width is too small" do
405
+ assert_same(@fragment, @fragment.fill_horizontal!(0.1))
406
+ end
407
+
408
+ it "repeats all items of the fragment" do
409
+ fragment = @fragment.fill_horizontal!(@fragment.width * 2)
410
+ assert_equal([*(@fragment.items * 2), 0], fragment.items)
411
+ assert_in_delta(@fragment.width * 2, fragment.width)
412
+ end
413
+
414
+ it "adds, after repeating, items from the start of the fragment to fill the available space" do
415
+ fragment = @fragment.fill_horizontal!(90)
416
+ assert_equal([*(@fragment.items * 9), @fragment.items[0], 3.3333333333332673], fragment.items)
417
+ assert_in_delta(90, fragment.width)
418
+ end
419
+
420
+ it "sets the character spacing correctly to account for the remaining space after filling with items" do
421
+ fragment = @fragment.fill_horizontal!(90)
422
+ refute_same(@fragment.style, fragment.style)
423
+ assert_equal(0.033333333333332674, fragment.style.character_spacing)
424
+ assert_in_delta(90, fragment.width)
425
+ end
426
+ end
427
+
395
428
  it "can be inspected" do
396
429
  frag = setup_fragment(@font.decode_utf8("H") << 5)
397
430
  assert_match(/:H/, frag.inspect)
@@ -262,7 +262,7 @@ module CommonLineWrappingTests
262
262
  end
263
263
 
264
264
  it "handles prohibited breakpoint penalties with non-zero width" do
265
- item = boxes(20).first
265
+ item = boxes(20).first.item
266
266
  result = call(boxes(70) + [glue(10)] + boxes(10) + [penalty(5000, item)] + boxes(30))
267
267
  assert_line_wrapping(result, [70, 60])
268
268
  end
@@ -295,11 +295,34 @@ module CommonLineWrappingTests
295
295
  assert_equal(2, lines.count)
296
296
  end
297
297
 
298
+ it "handles items with fill_horizontal correctly" do
299
+ doc = HexaPDF::Document.new
300
+ font = doc.fonts.add("Times")
301
+ box1 = HexaPDF::Layout::TextLayouter::Box.new(
302
+ HexaPDF::Layout::TextFragment.create('.', font: font, fill_horizontal: 1)
303
+ )
304
+ box2 = HexaPDF::Layout::TextLayouter::Box.new(
305
+ HexaPDF::Layout::TextFragment.create('.', font: font, fill_horizontal: 2)
306
+ )
307
+ items = [box1, *boxes(10), box2]
308
+ rest, lines = call(items, 40)
309
+ assert_equal(0, rest.size)
310
+ assert_equal(1, lines.size)
311
+
312
+ line = lines.first
313
+ refute_same(items[0].item, line.items[0])
314
+ assert_same(items[1].item, line.items[1])
315
+ refute_same(items[2].item, line.items[2])
316
+ assert_equal(10, line.items[0].width)
317
+ assert_equal(10, line.items[1].width)
318
+ assert_equal(20, line.items[2].width)
319
+ end
298
320
  end
299
321
 
300
322
  describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
301
323
  before do
302
324
  @obj = HexaPDF::Layout::TextLayouter::SimpleLineWrapping
325
+ @mock_frame = nil
303
326
  end
304
327
 
305
328
  describe "fixed width wrapping" do
@@ -308,7 +331,9 @@ describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
308
331
  def call(items, width = 100, &block)
309
332
  lines = []
310
333
  block ||= proc { true }
311
- rest = @obj.call(items, proc { width }) {|line, item| lines << line; block.call(line, item) }
334
+ rest = @obj.call(items, proc { width }, @mock_frame) do |line, item|
335
+ lines << line; block.call(line, item)
336
+ end
312
337
  [rest, lines]
313
338
  end
314
339
  end
@@ -319,7 +344,9 @@ describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
319
344
  def call(items, width = 100, &block)
320
345
  lines = []
321
346
  block ||= proc { true }
322
- rest = @obj.call(items, proc {|_| width }) {|line, i| lines << line; block.call(line, i) }
347
+ rest = @obj.call(items, proc {|_| width }, @mock_frame) do |line, i|
348
+ lines << line; block.call(line, i)
349
+ end
323
350
  [rest, lines]
324
351
  end
325
352
 
@@ -334,7 +361,7 @@ describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
334
361
  end
335
362
  end
336
363
  lines = []
337
- rest = @obj.call(boxes([20, 10], [10, 10], [20, 15], [40, 10]), width_block) do |line|
364
+ rest = @obj.call(boxes([20, 10], [10, 10], [20, 15], [40, 10]), width_block, @mock_frame) do |line|
338
365
  height += line.height
339
366
  lines << line
340
367
  true
@@ -357,7 +384,7 @@ describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
357
384
  lines = []
358
385
  item = HexaPDF::Layout::InlineBox.create(width: 20, height: 10) {}
359
386
  items = boxes([20, 10]) + [penalty(0, item)] + boxes([40, 15])
360
- rest = @obj.call(items, width_block) do |line|
387
+ rest = @obj.call(items, width_block, @mock_frame) do |line|
361
388
  height += line.height
362
389
  lines << line
363
390
  true
@@ -243,6 +243,16 @@ describe HexaPDF::Composer do
243
243
  [:restore_graphics_state]])
244
244
  end
245
245
 
246
+ it "returns the last drawn box" do
247
+ box = create_box(height: 400)
248
+ assert_same(box, @composer.draw_box(box))
249
+
250
+ box = create_box(height: 400)
251
+ split_box = create_box(height: 100)
252
+ box.define_singleton_method(:split) {|*| [box, split_box] }
253
+ assert_same(split_box, @composer.draw_box(box))
254
+ end
255
+
246
256
  it "raises an error if a box doesn't fit onto an empty page" do
247
257
  assert_raises(HexaPDF::Error) do
248
258
  @composer.draw_box(create_box(height: 800))
@@ -94,6 +94,16 @@ describe HexaPDF::Dictionary do
94
94
  obj = @test_class.new(nil)
95
95
  assert_equal(:MyType, obj.value[:Type])
96
96
  end
97
+
98
+ it "doesn't set the default values for required fields if the type class might be wrong" do
99
+ @test_class.define_type(:MyType)
100
+ obj = @test_class.new({})
101
+ assert_equal([], obj.value[:Array])
102
+ obj = @test_class.new({Type: :MyType})
103
+ assert_equal([], obj.value[:Array])
104
+ obj = @test_class.new({Type: :OtherType})
105
+ refute(obj.key?(:Array))
106
+ end
97
107
  end
98
108
 
99
109
  describe "[]" do
@@ -561,6 +561,10 @@ describe HexaPDF::Document do
561
561
  assert_kind_of(HexaPDF::Type::Outline, @doc.outline)
562
562
  end
563
563
 
564
+ it "returns the optional content properties" do
565
+ assert_kind_of(HexaPDF::Type::OptionalContentProperties, @doc.optional_content)
566
+ end
567
+
564
568
  it "can be inspected and the output is not too large" do
565
569
  assert_match(/HexaPDF::Document:\d+/, @doc.inspect)
566
570
  end
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.33.0)>>
43
+ <</Producer(HexaPDF version 0.34.0)>>
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.33.0)>>
75
+ <</Producer(HexaPDF version 0.34.0)>>
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.33.0)>>
217
+ <</Producer(HexaPDF version 0.34.0)>>
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
@@ -0,0 +1,40 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/actions/set_ocg_state'
6
+
7
+ describe HexaPDF::Type::Actions::SetOCGState do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @action = HexaPDF::Type::Actions::SetOCGState.new({}, document: @doc)
11
+ @ocg = @doc.optional_content.add_ocg('Test')
12
+ end
13
+
14
+ describe "add_state_change" do
15
+ it "allows using Ruby-esque and PDF type names for the state change type" do
16
+ @action.add_state_change(:on, @ocg)
17
+ @action.add_state_change(:ON, @ocg)
18
+ @action.add_state_change(:off, @ocg)
19
+ @action.add_state_change(:OFF, @ocg)
20
+ @action.add_state_change(:toggle, @ocg)
21
+ @action.add_state_change(:Toggle, @ocg)
22
+ assert_equal([:ON, @ocg, :ON, @ocg, :OFF, @ocg, :OFF, @ocg, :Toggle, @ocg, :Toggle, @ocg],
23
+ @action[:State].value)
24
+ end
25
+
26
+ it "allows specifying more than one OCG" do
27
+ @action.add_state_change(:on, [@ocg, @doc.optional_content.add_ocg('Test2')])
28
+ assert_equal([:ON, @ocg, @doc.optional_content.ocg('Test2')], @action[:State].value)
29
+ end
30
+
31
+ it "raises an error if the provide state change type is invalid" do
32
+ assert_raises(ArgumentError) { @action.add_state_change(:unknown, nil) }
33
+ end
34
+
35
+ it "raises an error if an OCG specified via a string does not exist" do
36
+ error = assert_raises(HexaPDF::Error) { @action.add_state_change(:on, "Unknown") }
37
+ assert_match(/Invalid OCG.*Unknown.*specified/, error.message)
38
+ end
39
+ end
40
+ end
@@ -39,6 +39,17 @@ describe HexaPDF::Type::Catalog do
39
39
  assert_same(outline, @catalog.outline)
40
40
  end
41
41
 
42
+ it "uses or creates the optional content properties dictionary on access" do
43
+ @catalog[:OCProperties] = hash = {}
44
+ assert_equal(:XXOCProperties, @catalog.optional_content.type)
45
+ assert_same(hash, @catalog.optional_content.value)
46
+
47
+ @catalog.delete(:OCProperties)
48
+ oc = @catalog.optional_content
49
+ assert_equal([], oc[:OCGs])
50
+ assert_equal(:XXOCConfiguration, oc[:D].type)
51
+ end
52
+
42
53
  describe "acro_form" do
43
54
  it "returns an existing form object" do
44
55
  @catalog[:AcroForm] = :test
@@ -1,6 +1,8 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  require 'test_helper'
4
+ require 'stringio'
5
+ require 'tempfile'
4
6
  require 'hexapdf/document'
5
7
  require 'hexapdf/type/form'
6
8
 
@@ -85,6 +87,18 @@ describe HexaPDF::Type::Form do
85
87
  @form.process_contents(processor, original_resources: resources)
86
88
  assert_same(resources, processor.resources)
87
89
  end
90
+
91
+ it "uses the referenced content in case of a Reference XObject" do
92
+ @form[:Ref] = @doc.add({F: {}})
93
+ io = StringIO.new
94
+ HexaPDF::Document.new.tap {|d| d.pages.add.canvas.line_width(5) }.write(io)
95
+ @form[:Ref][:F].embed(io, name: 'test')
96
+ @form[:Ref][:Page] = 0
97
+
98
+ processor = HexaPDF::TestUtils::OperatorRecorder.new
99
+ @form.process_contents(processor)
100
+ assert_equal([[:set_line_width, [5]]], processor.recorded_ops)
101
+ end
88
102
  end
89
103
 
90
104
  describe "canvas" do
@@ -116,4 +130,109 @@ describe HexaPDF::Type::Form do
116
130
  assert_raises(HexaPDF::Error) { @form.canvas }
117
131
  end
118
132
  end
133
+
134
+ describe "reference_xobject?" do
135
+ it "returns true if the form is a reference XObject" do
136
+ refute(@form.reference_xobject?)
137
+ @form[:Ref] = {}
138
+ assert(@form.reference_xobject?)
139
+ end
140
+ end
141
+
142
+ describe "referenced_content" do
143
+ before do
144
+ @form[:BBox] = [10, 10, 110, 60]
145
+ @form[:Matrix] = [1, 0, 0, 1, 10, 20]
146
+ @form[:Ref] = @doc.add({F: {}})
147
+ @ref = @form[:Ref]
148
+ end
149
+
150
+ it "returns a Form XObject with the imported page from an embedded file" do
151
+ io = StringIO.new
152
+ HexaPDF::Document.new.tap {|d| d.pages.add.canvas.line_width(5) }.write(io)
153
+ @ref[:F].embed(io, name: 'test.pdf')
154
+ @ref[:Page] = 0
155
+
156
+ ref_form = @form.referenced_content
157
+ refute_nil(ref_form)
158
+ assert_equal([10, 10, 110, 60], ref_form[:BBox].value)
159
+ assert_equal([1, 0, 0, 1, 10, 20], ref_form[:Matrix].value)
160
+ assert_equal("5 w\n", ref_form.contents)
161
+ end
162
+
163
+ it "returns a Form XObject with the imported page from an external file" do
164
+ file = Tempfile.new('hexapdf')
165
+ HexaPDF::Document.new.tap {|d| d.pages.add.canvas.line_width(5) }.write(file.path)
166
+ @ref[:F].path = file.path
167
+ @ref[:Page] = 0
168
+ assert_equal("5 w\n", @form.referenced_content.contents)
169
+ end
170
+
171
+ it "also works with a page label" do
172
+ file = Tempfile.new('hexapdf')
173
+ HexaPDF::Document.new.tap do |d|
174
+ d.pages.add
175
+ d.pages.add
176
+ d.pages.add.canvas.line_width(5)
177
+ d.pages.add
178
+ d.pages.add_labelling_range(1, numbering_style: :decimal, prefix: 'Test', start_number: 4)
179
+ end.write(file.path)
180
+ @ref[:F].path = file.path
181
+ @ref[:Page] = 'Test5'
182
+ assert_equal("5 w\n", @form.referenced_content.contents)
183
+ end
184
+
185
+ it "flattens printable annotations into the page's content stream" do
186
+ io = StringIO.new
187
+ HexaPDF::Document.new.tap do |d|
188
+ d.pages.add.canvas.line_width(5)
189
+ tf = d.acro_form(create: true).create_text_field('text')
190
+ widget = tf.create_widget(d.pages[0], Rect: [10, 10, 30, 30])
191
+ widget.border_style(color: "black")
192
+ widget = tf.create_widget(d.pages[0], Rect: [40, 10, 70, 30])
193
+ widget.border_style(color: "red")
194
+ tf.field_value = 't'
195
+ widget.unflag(:print)
196
+ end.write(io)
197
+ @ref[:F].embed(io, name: 'Test')
198
+ @ref[:Page] = 0
199
+ assert_equal(" q Q q 5 w\n Q q q\n1.0 0 0 1.0 10.0 10.0 cm\n/XO1 Do\nQ\n Q ",
200
+ @form.referenced_content.contents)
201
+ end
202
+
203
+ it "returns nil if the form is not a reference XObject" do
204
+ @form.delete(:Ref)
205
+ assert_nil(@form.referenced_content)
206
+ end
207
+
208
+ it "returns nil if the file is not embedded and not found" do
209
+ @ref[:F].path = '/tmp/non_existing_path'
210
+ assert_nil(@form.referenced_content)
211
+ end
212
+
213
+ it "returns nil if the page referenced by page number is not found" do
214
+ io = StringIO.new
215
+ HexaPDF::Document.new.tap {|d| d.pages.add; d.pages.add }.write(io)
216
+ @ref[:F].embed(io, name: 'test.pdf')
217
+ @ref[:Page] = 5
218
+ assert_nil(@form.referenced_content)
219
+ end
220
+
221
+ it "returns nil if the page referenced by page label is not found" do
222
+ io = StringIO.new
223
+ HexaPDF::Document.new.tap do |d|
224
+ d.pages.add
225
+ d.pages.add
226
+ d.pages.add_labelling_range(1, numbering_style: :decimal, prefix: 'Test')
227
+ end.write(io)
228
+ @ref[:F].embed(io, name: 'test.pdf')
229
+ @ref[:Page] = 'Test5'
230
+ assert_nil(@form.referenced_content)
231
+ end
232
+
233
+ it "returns nil if an error happens during processing" do
234
+ @ref[:F].embed(StringIO.new('temp'), name: 'test.pdf')
235
+ assert_nil(@form.referenced_content)
236
+ end
237
+ end
119
238
  end