hexapdf 0.33.0 → 0.34.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -1
- data/examples/026-optional_content.rb +55 -0
- data/examples/027-composer_optional_content.rb +83 -0
- data/lib/hexapdf/cli/command.rb +7 -1
- data/lib/hexapdf/cli/fonts.rb +1 -1
- data/lib/hexapdf/cli/inspect.rb +2 -4
- data/lib/hexapdf/composer.rb +2 -1
- data/lib/hexapdf/configuration.rb +21 -1
- data/lib/hexapdf/content/canvas.rb +52 -0
- data/lib/hexapdf/content/operator.rb +2 -0
- data/lib/hexapdf/dictionary.rb +1 -0
- data/lib/hexapdf/dictionary_fields.rb +1 -2
- data/lib/hexapdf/digital_signature/verification_result.rb +1 -2
- data/lib/hexapdf/document/layout.rb +3 -0
- data/lib/hexapdf/document/pages.rb +1 -1
- data/lib/hexapdf/document.rb +7 -0
- data/lib/hexapdf/encryption/ruby_aes.rb +10 -20
- data/lib/hexapdf/layout/box.rb +23 -3
- data/lib/hexapdf/layout/column_box.rb +2 -1
- data/lib/hexapdf/layout/frame.rb +23 -6
- data/lib/hexapdf/layout/inline_box.rb +20 -9
- data/lib/hexapdf/layout/list_box.rb +34 -20
- data/lib/hexapdf/layout/page_style.rb +2 -1
- data/lib/hexapdf/layout/style.rb +46 -6
- data/lib/hexapdf/layout/table_box.rb +9 -7
- data/lib/hexapdf/layout/text_box.rb +9 -2
- data/lib/hexapdf/layout/text_fragment.rb +28 -2
- data/lib/hexapdf/layout/text_layouter.rb +21 -5
- data/lib/hexapdf/stream.rb +1 -2
- data/lib/hexapdf/type/actions/set_ocg_state.rb +86 -0
- data/lib/hexapdf/type/actions.rb +1 -0
- data/lib/hexapdf/type/annotations/text.rb +1 -2
- data/lib/hexapdf/type/catalog.rb +10 -1
- data/lib/hexapdf/type/cid_font.rb +15 -1
- data/lib/hexapdf/type/form.rb +75 -5
- data/lib/hexapdf/type/optional_content_configuration.rb +170 -0
- data/lib/hexapdf/type/optional_content_group.rb +370 -0
- data/lib/hexapdf/type/optional_content_membership.rb +63 -0
- data/lib/hexapdf/type/optional_content_properties.rb +158 -0
- data/lib/hexapdf/type/page.rb +27 -11
- data/lib/hexapdf/type/page_label.rb +4 -8
- data/lib/hexapdf/type.rb +4 -0
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +0 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_canvas.rb +49 -0
- data/test/hexapdf/document/test_layout.rb +7 -2
- data/test/hexapdf/document/test_pages.rb +6 -6
- data/test/hexapdf/layout/test_box.rb +13 -4
- data/test/hexapdf/layout/test_frame.rb +13 -1
- data/test/hexapdf/layout/test_inline_box.rb +17 -8
- data/test/hexapdf/layout/test_list_box.rb +48 -31
- data/test/hexapdf/layout/test_style.rb +10 -0
- data/test/hexapdf/layout/test_table_box.rb +32 -26
- data/test/hexapdf/layout/test_text_box.rb +8 -0
- data/test/hexapdf/layout/test_text_fragment.rb +33 -0
- data/test/hexapdf/layout/test_text_layouter.rb +32 -5
- data/test/hexapdf/test_composer.rb +10 -0
- data/test/hexapdf/test_dictionary.rb +10 -0
- data/test/hexapdf/test_document.rb +4 -0
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/actions/test_set_ocg_state.rb +40 -0
- data/test/hexapdf/type/test_catalog.rb +11 -0
- data/test/hexapdf/type/test_form.rb +119 -0
- data/test/hexapdf/type/test_optional_content_configuration.rb +112 -0
- data/test/hexapdf/type/test_optional_content_group.rb +158 -0
- data/test/hexapdf/type/test_optional_content_properties.rb +109 -0
- data/test/hexapdf/type/test_page.rb +2 -2
- 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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
118
|
-
refute(cell.fit(100, 10,
|
|
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,
|
|
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,
|
|
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
|
-
@
|
|
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,
|
|
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,
|
|
548
|
-
box_a, box_b = box.split(100, 25,
|
|
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,
|
|
564
|
-
box_a, box_b = box.split(100, 25,
|
|
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,
|
|
575
|
-
box_a, box_b = box.split(100, 50,
|
|
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 =
|
|
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,
|
|
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,
|
|
655
|
-
_, split_box = box.split(100, 10,
|
|
656
|
-
assert(split_box.fit(100, 100,
|
|
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,
|
|
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 })
|
|
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 })
|
|
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
|
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.
|
|
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.
|
|
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.
|
|
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
|