hexapdf 0.8.0 → 0.9.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 +52 -1
- data/CONTRIBUTERS +1 -1
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/examples/018-composer.rb +44 -0
- data/lib/hexapdf/cli.rb +2 -0
- data/lib/hexapdf/cli/command.rb +2 -2
- data/lib/hexapdf/cli/optimize.rb +1 -1
- data/lib/hexapdf/cli/split.rb +82 -0
- data/lib/hexapdf/composer.rb +303 -0
- data/lib/hexapdf/configuration.rb +2 -2
- data/lib/hexapdf/content/canvas.rb +3 -6
- data/lib/hexapdf/dictionary.rb +0 -3
- data/lib/hexapdf/document.rb +30 -22
- data/lib/hexapdf/document/files.rb +1 -1
- data/lib/hexapdf/document/images.rb +1 -1
- data/lib/hexapdf/filter/predictor.rb +8 -8
- data/lib/hexapdf/layout.rb +1 -0
- data/lib/hexapdf/layout/box.rb +55 -12
- data/lib/hexapdf/layout/frame.rb +143 -46
- data/lib/hexapdf/layout/image_box.rb +96 -0
- data/lib/hexapdf/layout/inline_box.rb +10 -0
- data/lib/hexapdf/layout/line.rb +1 -1
- data/lib/hexapdf/layout/style.rb +55 -3
- data/lib/hexapdf/layout/text_box.rb +38 -8
- data/lib/hexapdf/layout/text_layouter.rb +66 -52
- data/lib/hexapdf/object.rb +3 -2
- data/lib/hexapdf/parser.rb +6 -1
- data/lib/hexapdf/rectangle.rb +6 -0
- data/lib/hexapdf/reference.rb +4 -6
- data/lib/hexapdf/revision.rb +34 -8
- data/lib/hexapdf/revisions.rb +12 -2
- data/lib/hexapdf/stream.rb +18 -0
- data/lib/hexapdf/task.rb +1 -1
- data/lib/hexapdf/task/dereference.rb +1 -1
- data/lib/hexapdf/task/optimize.rb +2 -2
- data/lib/hexapdf/type/form.rb +10 -0
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +25 -5
- data/man/man1/hexapdf.1 +17 -0
- data/test/hexapdf/layout/test_box.rb +7 -1
- data/test/hexapdf/layout/test_frame.rb +38 -1
- data/test/hexapdf/layout/test_image_box.rb +73 -0
- data/test/hexapdf/layout/test_inline_box.rb +8 -0
- data/test/hexapdf/layout/test_line.rb +1 -1
- data/test/hexapdf/layout/test_style.rb +58 -1
- data/test/hexapdf/layout/test_text_box.rb +57 -12
- data/test/hexapdf/layout/test_text_layouter.rb +14 -4
- data/test/hexapdf/task/test_optimize.rb +3 -3
- data/test/hexapdf/test_composer.rb +258 -0
- data/test/hexapdf/test_document.rb +29 -3
- data/test/hexapdf/test_importer.rb +8 -2
- data/test/hexapdf/test_object.rb +3 -1
- data/test/hexapdf/test_parser.rb +6 -0
- data/test/hexapdf/test_rectangle.rb +7 -0
- data/test/hexapdf/test_reference.rb +3 -1
- data/test/hexapdf/test_revision.rb +13 -0
- data/test/hexapdf/test_revisions.rb +1 -0
- data/test/hexapdf/test_stream.rb +13 -0
- data/test/hexapdf/test_writer.rb +13 -2
- data/test/hexapdf/type/test_annotation.rb +1 -1
- data/test/hexapdf/type/test_form.rb +13 -2
- metadata +10 -4
@@ -60,4 +60,12 @@ describe HexaPDF::Layout::InlineBox do
|
|
60
60
|
it "returns width for x_max" do
|
61
61
|
assert_equal(@box.width, @box.x_max)
|
62
62
|
end
|
63
|
+
|
64
|
+
it "returns 0 for y_min" do
|
65
|
+
assert_equal(0, @box.y_min)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns height for y_max" do
|
69
|
+
assert_equal(@box.height, @box.y_max)
|
70
|
+
end
|
63
71
|
end
|
@@ -12,7 +12,7 @@ describe HexaPDF::Layout::Line::HeightCalculator do
|
|
12
12
|
@calc << HexaPDF::Layout::InlineBox.create(width: 10, height: 20, valign: :baseline) {}
|
13
13
|
assert_equal([0, 20, 0, 0], @calc.result)
|
14
14
|
new_item = HexaPDF::Layout::InlineBox.create(width: 10, height: 30, valign: :top) {}
|
15
|
-
assert_equal(30, @calc.simulate_height(new_item))
|
15
|
+
assert_equal([-10, 20, 30], @calc.simulate_height(new_item))
|
16
16
|
assert_equal([0, 20, 0, 0], @calc.result)
|
17
17
|
end
|
18
18
|
end
|
@@ -18,7 +18,7 @@ describe HexaPDF::Layout::Style::LineSpacing do
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def line_spacing(type, value = nil)
|
21
|
-
HexaPDF::Layout::Style::LineSpacing.new(type, value: value)
|
21
|
+
HexaPDF::Layout::Style::LineSpacing.new(type: type, value: value)
|
22
22
|
end
|
23
23
|
|
24
24
|
it "allows single line spacing" do
|
@@ -147,6 +147,17 @@ describe HexaPDF::Layout::Style::Border do
|
|
147
147
|
assert_kind_of(HexaPDF::Layout::Style::Quad, border.style)
|
148
148
|
end
|
149
149
|
|
150
|
+
it "can be duplicated" do
|
151
|
+
border = create_border
|
152
|
+
copy = border.dup
|
153
|
+
border.width.top = 10
|
154
|
+
border.color.top = :red
|
155
|
+
border.style.top = :dotted
|
156
|
+
assert_equal(0, copy.width.top)
|
157
|
+
assert_equal(0, copy.color.top)
|
158
|
+
assert_equal(:solid, copy.style.top)
|
159
|
+
end
|
160
|
+
|
150
161
|
it "can be asked whether a border is defined" do
|
151
162
|
assert(create_border.none?)
|
152
163
|
refute(create_border(width: 5).none?)
|
@@ -436,6 +447,12 @@ describe HexaPDF::Layout::Style::Layers do
|
|
436
447
|
assert_equal(data, layers.enum_for(:each, {}).to_a)
|
437
448
|
end
|
438
449
|
|
450
|
+
it "can be duplicated" do
|
451
|
+
copy = @layers.dup
|
452
|
+
@layers.add(lambda {})
|
453
|
+
assert(copy.none?)
|
454
|
+
end
|
455
|
+
|
439
456
|
describe "add and each" do
|
440
457
|
it "can use a given block" do
|
441
458
|
block = proc { true }
|
@@ -577,6 +594,45 @@ describe HexaPDF::Layout::Style do
|
|
577
594
|
assert_equal(10, style.font_size)
|
578
595
|
end
|
579
596
|
|
597
|
+
describe "initialize_copy" do
|
598
|
+
it "can be duplicated" do
|
599
|
+
@style.font_features[:kerning] = true
|
600
|
+
@style.padding.top = 10
|
601
|
+
@style.margin.top = 10
|
602
|
+
@style.border.width.top = 10
|
603
|
+
@style.overlays.add(lambda {})
|
604
|
+
@style.underlays.add(lambda {})
|
605
|
+
|
606
|
+
copy = @style.dup
|
607
|
+
@style.font_features[:kerning] = false
|
608
|
+
@style.padding.top = 5
|
609
|
+
@style.margin.top = 5
|
610
|
+
@style.border.width.top = 5
|
611
|
+
@style.overlays.add(lambda {})
|
612
|
+
@style.underlays.add(lambda {})
|
613
|
+
|
614
|
+
assert_equal({kerning: true}, copy.font_features)
|
615
|
+
assert_equal(10, copy.padding.top)
|
616
|
+
assert_equal(10, copy.margin.top)
|
617
|
+
assert_equal(10, copy.border.width.top)
|
618
|
+
assert_equal(1, copy.underlays.instance_variable_get(:@layers).size)
|
619
|
+
assert_equal(1, copy.overlays.instance_variable_get(:@layers).size)
|
620
|
+
end
|
621
|
+
|
622
|
+
it "resets the cache" do
|
623
|
+
@style.horizontal_scaling(200)
|
624
|
+
assert_equal(2.0, @style.scaled_horizontal_scaling)
|
625
|
+
assert_equal(-1.06, @style.scaled_item_width(53))
|
626
|
+
|
627
|
+
style = @style.dup
|
628
|
+
style.horizontal_scaling(100)
|
629
|
+
assert_equal(2.0, @style.scaled_horizontal_scaling)
|
630
|
+
assert_equal(-1.06, @style.scaled_item_width(53))
|
631
|
+
assert_equal(1.0, style.scaled_horizontal_scaling)
|
632
|
+
assert_equal(-0.53, style.scaled_item_width(53))
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
580
636
|
it "has several simple and dynamically generated properties with default values" do
|
581
637
|
assert_raises(HexaPDF::Error) { @style.font }
|
582
638
|
assert_equal(10, @style.font_size)
|
@@ -607,6 +663,7 @@ describe HexaPDF::Layout::Style do
|
|
607
663
|
assert_equal([:proportional, 1], [@style.line_spacing.type, @style.line_spacing.value])
|
608
664
|
refute(@style.subscript)
|
609
665
|
refute(@style.superscript)
|
666
|
+
refute(@style.last_line_gap)
|
610
667
|
assert_kind_of(HexaPDF::Layout::Style::Layers, @style.underlays)
|
611
668
|
assert_kind_of(HexaPDF::Layout::Style::Layers, @style.overlays)
|
612
669
|
end
|
@@ -8,6 +8,7 @@ require 'hexapdf/layout/text_box'
|
|
8
8
|
describe HexaPDF::Layout::TextBox do
|
9
9
|
before do
|
10
10
|
@frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100)
|
11
|
+
@inline_box = HexaPDF::Layout::InlineBox.create(width: 10, height: 10) {}
|
11
12
|
end
|
12
13
|
|
13
14
|
def create_box(items, **kwargs)
|
@@ -22,15 +23,19 @@ describe HexaPDF::Layout::TextBox do
|
|
22
23
|
end
|
23
24
|
|
24
25
|
describe "fit" do
|
25
|
-
|
26
|
-
|
26
|
+
it "fits into a rectangular area" do
|
27
|
+
box = create_box([@inline_box] * 5, style: {padding: 10})
|
28
|
+
assert(box.fit(100, 100, @frame))
|
29
|
+
assert_equal(70, box.width)
|
30
|
+
assert_equal(30, box.height)
|
27
31
|
end
|
28
32
|
|
29
|
-
it "
|
30
|
-
box = create_box([@inline_box] * 5)
|
33
|
+
it "respects the set width and height" do
|
34
|
+
box = create_box([@inline_box] * 5, width: 40, height: 50, style: {padding: 10})
|
31
35
|
assert(box.fit(100, 100, @frame))
|
32
|
-
assert_equal(
|
33
|
-
assert_equal(
|
36
|
+
assert_equal(40, box.width)
|
37
|
+
assert_equal(50, box.height)
|
38
|
+
assert_equal([20, 20, 10], box.instance_variable_get(:@result).lines.map(&:width))
|
34
39
|
end
|
35
40
|
|
36
41
|
it "fits into the frame's outline" do
|
@@ -40,30 +45,59 @@ describe HexaPDF::Layout::TextBox do
|
|
40
45
|
assert_equal(100, box.width)
|
41
46
|
assert_equal(20, box.height)
|
42
47
|
end
|
48
|
+
|
49
|
+
it "takes the style option last_line_gap into account" do
|
50
|
+
box = create_box([@inline_box] * 5, style: {last_line_gap: true, line_spacing: :double})
|
51
|
+
assert(box.fit(100, 100, @frame))
|
52
|
+
assert_equal(50, box.width)
|
53
|
+
assert_equal(20, box.height)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "split" do
|
58
|
+
it "works for an empty text box" do
|
59
|
+
box = create_box([])
|
60
|
+
assert_equal([box], box.split(100, 100, @frame))
|
61
|
+
end
|
62
|
+
|
63
|
+
it "doesn't need to split the box if it completely fits" do
|
64
|
+
box = create_box([@inline_box] * 5)
|
65
|
+
assert_equal([box], box.split(50, 100, @frame))
|
66
|
+
end
|
67
|
+
|
68
|
+
it "works if no item of the text box fits" do
|
69
|
+
box = create_box([@inline_box])
|
70
|
+
assert_equal([nil, box], box.split(5, 20, @frame))
|
71
|
+
end
|
72
|
+
|
73
|
+
it "splits the box if necessary" do
|
74
|
+
box = create_box([@inline_box] * 10)
|
75
|
+
boxes = box.split(50, 10, @frame)
|
76
|
+
assert_equal(2, boxes.length)
|
77
|
+
assert_equal(box, boxes[0])
|
78
|
+
assert_equal(5, boxes[1].instance_variable_get(:@items).length)
|
79
|
+
end
|
43
80
|
end
|
44
81
|
|
45
82
|
describe "draw" do
|
46
83
|
it "draws the layed out inline items onto the canvas" do
|
47
84
|
inline_box = HexaPDF::Layout::InlineBox.create(width: 10, height: 10,
|
48
85
|
border: {width: 1})
|
49
|
-
box = create_box([inline_box], width: 100, height: 10)
|
86
|
+
box = create_box([inline_box], width: 100, height: 30, style: {padding: [10, 5]})
|
50
87
|
box.fit(100, 100, nil)
|
51
88
|
|
52
89
|
@canvas = HexaPDF::Document.new.pages.add.canvas
|
53
90
|
box.draw(@canvas, 0, 0)
|
54
91
|
assert_operators(@canvas.contents, [[:save_graphics_state],
|
55
|
-
[:concatenate_matrix, [1, 0, 0, 1, 0, 0]],
|
56
|
-
[:save_graphics_state],
|
57
92
|
[:restore_graphics_state],
|
58
93
|
[:save_graphics_state],
|
59
|
-
[:append_rectangle, [
|
94
|
+
[:append_rectangle, [5, 10, 10, 10]],
|
60
95
|
[:clip_path_non_zero],
|
61
96
|
[:end_path],
|
62
|
-
[:append_rectangle, [
|
97
|
+
[:append_rectangle, [5.5, 10.5, 9, 9]],
|
63
98
|
[:stroke_path],
|
64
99
|
[:restore_graphics_state],
|
65
100
|
[:save_graphics_state],
|
66
|
-
[:restore_graphics_state],
|
67
101
|
[:restore_graphics_state]])
|
68
102
|
end
|
69
103
|
|
@@ -74,4 +108,15 @@ describe HexaPDF::Layout::TextBox do
|
|
74
108
|
assert_operators(@canvas.contents, [])
|
75
109
|
end
|
76
110
|
end
|
111
|
+
|
112
|
+
it "is empty if there is a result without any text lines" do
|
113
|
+
box = create_box([])
|
114
|
+
assert(box.empty?)
|
115
|
+
box.fit(100, 100, @frame)
|
116
|
+
assert(box.empty?)
|
117
|
+
|
118
|
+
box = create_box([@inline_box])
|
119
|
+
box.fit(100, 100, @frame)
|
120
|
+
refute(box.empty?)
|
121
|
+
end
|
77
122
|
end
|
@@ -308,8 +308,8 @@ describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
|
|
308
308
|
|
309
309
|
it "handles changing widths" do
|
310
310
|
height = 0
|
311
|
-
width_block = lambda do |
|
312
|
-
case height +
|
311
|
+
width_block = lambda do |line|
|
312
|
+
case height + line.height
|
313
313
|
when 0..10 then 60
|
314
314
|
when 11..20 then 40
|
315
315
|
when 21..30 then 20
|
@@ -331,8 +331,8 @@ describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
|
|
331
331
|
|
332
332
|
it "handles changing widths when breaking on a penalty" do
|
333
333
|
height = 0
|
334
|
-
width_block = lambda do |
|
335
|
-
case height +
|
334
|
+
width_block = lambda do |line|
|
335
|
+
case height + line.height
|
336
336
|
when 0..10 then 80
|
337
337
|
else 50
|
338
338
|
end
|
@@ -419,6 +419,16 @@ describe HexaPDF::Layout::TextLayouter do
|
|
419
419
|
assert_equal(20 * (5 + 4), result.height)
|
420
420
|
end
|
421
421
|
|
422
|
+
it "takes line spacing into account with variable width" do
|
423
|
+
@style.line_spacing = :double
|
424
|
+
width_block = lambda {|l, h| l + h <= 90 ? 40 : 20 }
|
425
|
+
result = @layouter.fit(boxes(*([[20, 20]] * 6)), width_block, 170)
|
426
|
+
assert(result.remaining_items.empty?)
|
427
|
+
assert_equal(:success, result.status)
|
428
|
+
assert_line_wrapping([[], result.lines], [40, 40, 20, 20])
|
429
|
+
assert_equal(140, result.height)
|
430
|
+
end
|
431
|
+
|
422
432
|
it "handles empty lines" do
|
423
433
|
items = boxes([20, 20]) + [penalty(-5000)] + boxes([30, 20]) + [penalty(-5000)] * 2 +
|
424
434
|
boxes([20, 20]) + [penalty(-5000)] * 2
|
@@ -32,11 +32,11 @@ describe HexaPDF::Task::Optimize do
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def assert_no_objstms
|
35
|
-
assert(@doc.each(
|
35
|
+
assert(@doc.each(only_current: false).all? {|obj| obj.type != :ObjStm })
|
36
36
|
end
|
37
37
|
|
38
38
|
def assert_no_xrefstms
|
39
|
-
assert(@doc.each(
|
39
|
+
assert(@doc.each(only_current: false).all? {|obj| obj.type != :XRef })
|
40
40
|
end
|
41
41
|
|
42
42
|
def assert_default_deleted
|
@@ -47,7 +47,7 @@ describe HexaPDF::Task::Optimize do
|
|
47
47
|
it "compacts the document" do
|
48
48
|
@doc.task(:optimize, compact: true)
|
49
49
|
assert_equal(1, @doc.revisions.size)
|
50
|
-
assert_equal(2, @doc.each(
|
50
|
+
assert_equal(2, @doc.each(only_current: false).to_a.size)
|
51
51
|
refute_equal(@obj2, @doc.object(@obj2))
|
52
52
|
refute_equal(@obj3, @doc.object(@obj3))
|
53
53
|
assert_default_deleted
|
@@ -0,0 +1,258 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require_relative 'content/common'
|
5
|
+
require 'hexapdf/document'
|
6
|
+
require 'hexapdf/composer'
|
7
|
+
require 'stringio'
|
8
|
+
|
9
|
+
describe HexaPDF::Composer do
|
10
|
+
before do
|
11
|
+
@composer = HexaPDF::Composer.new
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "initialize" do
|
15
|
+
it "creates a composer object with default values" do
|
16
|
+
assert_kind_of(HexaPDF::Document, @composer.document)
|
17
|
+
assert_kind_of(HexaPDF::Type::Page, @composer.page)
|
18
|
+
assert_equal(36, @composer.frame.left)
|
19
|
+
assert_equal(36, @composer.frame.bottom)
|
20
|
+
assert_equal(523, @composer.frame.width)
|
21
|
+
assert_equal(770, @composer.frame.height)
|
22
|
+
assert_equal("Times", @composer.base_style.font)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "allows the customization of the page size" do
|
26
|
+
composer = HexaPDF::Composer.new(page_size: [0, 0, 100, 100])
|
27
|
+
assert_equal([0, 0, 100, 100], composer.page.box.value)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "allows the customization of the page orientation" do
|
31
|
+
composer = HexaPDF::Composer.new(page_orientation: :landscape)
|
32
|
+
assert_equal([0, 0, 842, 595], composer.page.box.value)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "allows the customization of the margin" do
|
36
|
+
composer = HexaPDF::Composer.new(margin: [100, 80, 60, 40])
|
37
|
+
assert_equal(40, composer.frame.left)
|
38
|
+
assert_equal(60, composer.frame.bottom)
|
39
|
+
assert_equal(475, composer.frame.width)
|
40
|
+
assert_equal(682, composer.frame.height)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "yields itself" do
|
44
|
+
yielded = nil
|
45
|
+
composer = HexaPDF::Composer.new {|c| yielded = c }
|
46
|
+
assert_same(composer, yielded)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "::create" do
|
51
|
+
it "creates, yields, and writes a document" do
|
52
|
+
io = StringIO.new
|
53
|
+
HexaPDF::Composer.create(io, &:new_page)
|
54
|
+
io.rewind
|
55
|
+
assert_equal(2, HexaPDF::Document.new(io: io).pages.count)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "new_page" do
|
60
|
+
it "creates a new page with the stored information" do
|
61
|
+
c = HexaPDF::Composer.new(page_size: [0, 0, 50, 100], margin: 10)
|
62
|
+
c.new_page
|
63
|
+
assert_equal([0, 0, 50, 100], c.page.box.value)
|
64
|
+
assert_equal(10, c.frame.left)
|
65
|
+
assert_equal(10, c.frame.bottom)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "uses the provided information for the new and all following pages" do
|
69
|
+
@composer.new_page(page_size: [0, 0, 50, 100], margin: 10)
|
70
|
+
assert_equal([0, 0, 50, 100], @composer.page.box.value)
|
71
|
+
assert_equal(10, @composer.frame.left)
|
72
|
+
assert_equal(10, @composer.frame.bottom)
|
73
|
+
@composer.new_page
|
74
|
+
assert_same(@composer.document.pages[2], @composer.page)
|
75
|
+
assert_equal([0, 0, 50, 100], @composer.page.box.value)
|
76
|
+
assert_equal(10, @composer.frame.left)
|
77
|
+
assert_equal(10, @composer.frame.bottom)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it "returns the current x-position" do
|
82
|
+
assert_equal(36, @composer.x)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "returns the current y-position" do
|
86
|
+
assert_equal(806, @composer.y)
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "text" do
|
90
|
+
it "creates a text box and draws it on the canvas" do
|
91
|
+
box = nil
|
92
|
+
@composer.define_singleton_method(:draw_box) {|arg| box = arg }
|
93
|
+
|
94
|
+
@composer.text("Test", width: 10, height: 15)
|
95
|
+
assert_equal(10, box.width)
|
96
|
+
assert_equal(15, box.height)
|
97
|
+
assert_same(@composer.document.fonts.add("Times"), box.style.font)
|
98
|
+
items = box.instance_variable_get(:@items)
|
99
|
+
assert_equal(1, items.length)
|
100
|
+
assert_same(box.style, items.first.style)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "allows setting of a custom style" do
|
104
|
+
box = nil
|
105
|
+
@composer.define_singleton_method(:draw_box) {|arg| box = arg }
|
106
|
+
|
107
|
+
@composer.text("Test", style: HexaPDF::Layout::Style.new(font_size: 20))
|
108
|
+
assert_same(@composer.document.fonts.add("Times"), box.style.font)
|
109
|
+
assert_equal(20, box.style.font_size)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "updates the used style with the provided options" do
|
113
|
+
box = nil
|
114
|
+
@composer.define_singleton_method(:draw_box) {|arg| box = arg }
|
115
|
+
|
116
|
+
@composer.text("Test", style: HexaPDF::Layout::Style.new, font_size: 20)
|
117
|
+
assert_equal(20, box.style.font_size)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "formatted_text" do
|
122
|
+
it "creates a text box with the formatted text and draws it on the canvas" do
|
123
|
+
box = nil
|
124
|
+
@composer.define_singleton_method(:draw_box) {|arg| box = arg }
|
125
|
+
|
126
|
+
@composer.formatted_text(["Test"], width: 10, height: 15)
|
127
|
+
assert_equal(10, box.width)
|
128
|
+
assert_equal(15, box.height)
|
129
|
+
assert_equal(1, box.instance_variable_get(:@items).length)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "a hash can be used for custom style properties" do
|
133
|
+
box = nil
|
134
|
+
@composer.define_singleton_method(:draw_box) {|arg| box = arg }
|
135
|
+
|
136
|
+
@composer.formatted_text([{text: "Test", font_size: 20}], align: :center)
|
137
|
+
items = box.instance_variable_get(:@items)
|
138
|
+
assert_equal(1, items.length)
|
139
|
+
assert_equal(20, items.first.style.font_size)
|
140
|
+
assert_equal(:center, items.first.style.align)
|
141
|
+
assert_equal(10, box.style.font_size)
|
142
|
+
end
|
143
|
+
|
144
|
+
it "a hash can be used to provide a custom style" do
|
145
|
+
box = nil
|
146
|
+
@composer.define_singleton_method(:draw_box) {|arg| box = arg }
|
147
|
+
|
148
|
+
@composer.formatted_text([{text: "Test", style: HexaPDF::Layout::Style.new(fill_color: 128),
|
149
|
+
font_size: 20}], align: :center)
|
150
|
+
items = box.instance_variable_get(:@items)
|
151
|
+
assert_equal(20, items.first.style.font_size)
|
152
|
+
assert_equal(128, items.first.style.fill_color)
|
153
|
+
assert_equal(:center, items.first.style.align)
|
154
|
+
end
|
155
|
+
|
156
|
+
it "a hash can be used to link to an URL" do
|
157
|
+
box = nil
|
158
|
+
@composer.define_singleton_method(:draw_box) {|arg| box = arg }
|
159
|
+
|
160
|
+
@composer.formatted_text([{text: "Test", link: "URI"}, {link: "URI"}])
|
161
|
+
items = box.instance_variable_get(:@items)
|
162
|
+
assert_equal(2, items.length)
|
163
|
+
assert_equal(4, items[0].items.length)
|
164
|
+
assert_equal(3, items[1].items.length)
|
165
|
+
assert_equal([:link, {uri: 'URI'}], items[0].style.overlays.instance_variable_get(:@layers)[0])
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe "image" do
|
170
|
+
it "creates an image box and draws it on the canvas" do
|
171
|
+
box = nil
|
172
|
+
@composer.define_singleton_method(:draw_box) {|arg| box = arg }
|
173
|
+
image_path = File.join(TEST_DATA_DIR, 'images', 'gray.jpg')
|
174
|
+
|
175
|
+
@composer.image(image_path, width: 10, height: 15)
|
176
|
+
assert_equal(10, box.width)
|
177
|
+
assert_equal(15, box.height)
|
178
|
+
assert_same(@composer.document.images.add(image_path), box.image)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
describe "draw_box" do
|
183
|
+
def create_box(**kwargs)
|
184
|
+
HexaPDF::Layout::Box.new(kwargs) {}
|
185
|
+
end
|
186
|
+
|
187
|
+
it "draws the box if it completely fits" do
|
188
|
+
@composer.draw_box(create_box(height: 100))
|
189
|
+
@composer.draw_box(create_box)
|
190
|
+
assert_operators(@composer.canvas.contents,
|
191
|
+
[[:save_graphics_state],
|
192
|
+
[:concatenate_matrix, [1, 0, 0, 1, 36, 706]],
|
193
|
+
[:restore_graphics_state],
|
194
|
+
[:save_graphics_state],
|
195
|
+
[:concatenate_matrix, [1, 0, 0, 1, 36, 36]],
|
196
|
+
[:restore_graphics_state]])
|
197
|
+
end
|
198
|
+
|
199
|
+
it "draws the box on a new page if the frame is already full" do
|
200
|
+
first_page_canvas = @composer.canvas
|
201
|
+
@composer.draw_box(create_box)
|
202
|
+
@composer.draw_box(create_box)
|
203
|
+
refute_same(first_page_canvas, @composer.canvas)
|
204
|
+
assert_operators(@composer.canvas.contents,
|
205
|
+
[[:save_graphics_state],
|
206
|
+
[:concatenate_matrix, [1, 0, 0, 1, 36, 36]],
|
207
|
+
[:restore_graphics_state]])
|
208
|
+
end
|
209
|
+
|
210
|
+
it "splits the box across two pages" do
|
211
|
+
first_page_contents = @composer.canvas.contents
|
212
|
+
@composer.draw_box(create_box(height: 400))
|
213
|
+
|
214
|
+
box = create_box(height: 400)
|
215
|
+
box.define_singleton_method(:split) do |*|
|
216
|
+
[box, HexaPDF::Layout::Box.new(height: 100) {}]
|
217
|
+
end
|
218
|
+
@composer.draw_box(box)
|
219
|
+
assert_operators(first_page_contents,
|
220
|
+
[[:save_graphics_state],
|
221
|
+
[:concatenate_matrix, [1, 0, 0, 1, 36, 406]],
|
222
|
+
[:restore_graphics_state],
|
223
|
+
[:save_graphics_state],
|
224
|
+
[:concatenate_matrix, [1, 0, 0, 1, 36, 6]],
|
225
|
+
[:restore_graphics_state]])
|
226
|
+
assert_operators(@composer.canvas.contents,
|
227
|
+
[[:save_graphics_state],
|
228
|
+
[:concatenate_matrix, [1, 0, 0, 1, 36, 706]],
|
229
|
+
[:restore_graphics_state]])
|
230
|
+
end
|
231
|
+
|
232
|
+
it "finds a new region if splitting didn't work" do
|
233
|
+
first_page_contents = @composer.canvas.contents
|
234
|
+
@composer.draw_box(create_box(height: 400))
|
235
|
+
@composer.draw_box(create_box(height: 100, width: 300, style: {position: :float}))
|
236
|
+
|
237
|
+
box = create_box(width: 400, height: 400)
|
238
|
+
@composer.draw_box(box)
|
239
|
+
assert_operators(first_page_contents,
|
240
|
+
[[:save_graphics_state],
|
241
|
+
[:concatenate_matrix, [1, 0, 0, 1, 36, 406]],
|
242
|
+
[:restore_graphics_state],
|
243
|
+
[:save_graphics_state],
|
244
|
+
[:concatenate_matrix, [1, 0, 0, 1, 36, 306]],
|
245
|
+
[:restore_graphics_state]])
|
246
|
+
assert_operators(@composer.canvas.contents,
|
247
|
+
[[:save_graphics_state],
|
248
|
+
[:concatenate_matrix, [1, 0, 0, 1, 36, 406]],
|
249
|
+
[:restore_graphics_state]])
|
250
|
+
end
|
251
|
+
|
252
|
+
it "raises an error if a box doesn't fit onto an empty page" do
|
253
|
+
assert_raises(HexaPDF::Error) do
|
254
|
+
@composer.draw_box(create_box(height: 800))
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|