hexapdf 0.5.0 → 0.6.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 +76 -2
- data/CONTRIBUTERS +1 -1
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/examples/boxes.rb +68 -0
- data/examples/graphics.rb +12 -12
- data/examples/{text_box_alignment.rb → text_layouter_alignment.rb} +14 -14
- data/examples/text_layouter_inline_boxes.rb +66 -0
- data/examples/{text_box_line_wrapping.rb → text_layouter_line_wrapping.rb} +9 -10
- data/examples/{text_box_shapes.rb → text_layouter_shapes.rb} +58 -54
- data/examples/text_layouter_styling.rb +125 -0
- data/examples/truetype.rb +5 -7
- data/lib/hexapdf/cli/command.rb +1 -0
- data/lib/hexapdf/configuration.rb +170 -106
- data/lib/hexapdf/content/canvas.rb +41 -36
- data/lib/hexapdf/content/graphics_state.rb +15 -0
- data/lib/hexapdf/content/operator.rb +1 -1
- data/lib/hexapdf/dictionary.rb +20 -8
- data/lib/hexapdf/dictionary_fields.rb +8 -6
- data/lib/hexapdf/document.rb +25 -26
- data/lib/hexapdf/document/fonts.rb +4 -4
- data/lib/hexapdf/document/images.rb +2 -2
- data/lib/hexapdf/document/pages.rb +16 -16
- data/lib/hexapdf/encryption/security_handler.rb +41 -9
- data/lib/hexapdf/filter/flate_decode.rb +1 -1
- data/lib/hexapdf/filter/lzw_decode.rb +1 -1
- data/lib/hexapdf/filter/predictor.rb +7 -1
- data/lib/hexapdf/font/true_type/font.rb +20 -0
- data/lib/hexapdf/font/type1/font.rb +23 -0
- data/lib/hexapdf/font_loader.rb +1 -0
- data/lib/hexapdf/font_loader/from_configuration.rb +2 -3
- data/lib/hexapdf/font_loader/from_file.rb +65 -0
- data/lib/hexapdf/image_loader/png.rb +2 -2
- data/lib/hexapdf/layout.rb +3 -2
- data/lib/hexapdf/layout/box.rb +146 -0
- data/lib/hexapdf/layout/inline_box.rb +40 -31
- data/lib/hexapdf/layout/{line_fragment.rb → line.rb} +12 -13
- data/lib/hexapdf/layout/style.rb +630 -41
- data/lib/hexapdf/layout/text_fragment.rb +80 -12
- data/lib/hexapdf/layout/{text_box.rb → text_layouter.rb} +164 -109
- data/lib/hexapdf/number_tree_node.rb +1 -1
- data/lib/hexapdf/parser.rb +4 -1
- data/lib/hexapdf/revisions.rb +11 -4
- data/lib/hexapdf/stream.rb +8 -9
- data/lib/hexapdf/tokenizer.rb +5 -3
- data/lib/hexapdf/type.rb +3 -0
- data/lib/hexapdf/type/action.rb +56 -0
- data/lib/hexapdf/type/actions.rb +52 -0
- data/lib/hexapdf/type/actions/go_to.rb +52 -0
- data/lib/hexapdf/type/actions/go_to_r.rb +54 -0
- data/lib/hexapdf/type/actions/launch.rb +73 -0
- data/lib/hexapdf/type/actions/uri.rb +65 -0
- data/lib/hexapdf/type/annotation.rb +85 -0
- data/lib/hexapdf/type/annotations.rb +51 -0
- data/lib/hexapdf/type/annotations/link.rb +70 -0
- data/lib/hexapdf/type/annotations/markup_annotation.rb +70 -0
- data/lib/hexapdf/type/annotations/text.rb +81 -0
- data/lib/hexapdf/type/catalog.rb +3 -1
- data/lib/hexapdf/type/embedded_file.rb +6 -11
- data/lib/hexapdf/type/file_specification.rb +4 -6
- data/lib/hexapdf/type/font.rb +3 -1
- data/lib/hexapdf/type/font_descriptor.rb +18 -16
- data/lib/hexapdf/type/form.rb +3 -1
- data/lib/hexapdf/type/graphics_state_parameter.rb +3 -1
- data/lib/hexapdf/type/image.rb +4 -2
- data/lib/hexapdf/type/info.rb +2 -5
- data/lib/hexapdf/type/names.rb +2 -5
- data/lib/hexapdf/type/object_stream.rb +2 -1
- data/lib/hexapdf/type/page.rb +14 -1
- data/lib/hexapdf/type/page_tree_node.rb +9 -6
- data/lib/hexapdf/type/resources.rb +2 -5
- data/lib/hexapdf/type/trailer.rb +2 -5
- data/lib/hexapdf/type/viewer_preferences.rb +2 -5
- data/lib/hexapdf/type/xref_stream.rb +3 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +3 -1
- data/test/hexapdf/content/test_canvas.rb +29 -3
- data/test/hexapdf/content/test_graphics_state.rb +11 -0
- data/test/hexapdf/content/test_operator.rb +3 -2
- data/test/hexapdf/document/test_fonts.rb +8 -8
- data/test/hexapdf/document/test_images.rb +4 -12
- data/test/hexapdf/document/test_pages.rb +7 -7
- data/test/hexapdf/encryption/test_security_handler.rb +1 -5
- data/test/hexapdf/filter/test_predictor.rb +40 -12
- data/test/hexapdf/font/true_type/test_font.rb +16 -0
- data/test/hexapdf/font/type1/test_font.rb +30 -0
- data/test/hexapdf/font_loader/test_from_file.rb +29 -0
- data/test/hexapdf/font_loader/test_standard14.rb +4 -3
- data/test/hexapdf/layout/test_box.rb +104 -0
- data/test/hexapdf/layout/test_inline_box.rb +24 -10
- data/test/hexapdf/layout/{test_line_fragment.rb → test_line.rb} +9 -9
- data/test/hexapdf/layout/test_style.rb +519 -31
- data/test/hexapdf/layout/test_text_fragment.rb +136 -15
- data/test/hexapdf/layout/{test_text_box.rb → test_text_layouter.rb} +224 -144
- data/test/hexapdf/layout/test_text_shaper.rb +1 -1
- data/test/hexapdf/test_configuration.rb +12 -6
- data/test/hexapdf/test_dictionary.rb +27 -2
- data/test/hexapdf/test_dictionary_fields.rb +10 -1
- data/test/hexapdf/test_document.rb +14 -13
- data/test/hexapdf/test_parser.rb +12 -0
- data/test/hexapdf/test_revisions.rb +34 -0
- data/test/hexapdf/test_stream.rb +1 -1
- data/test/hexapdf/test_type.rb +18 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/actions/test_launch.rb +24 -0
- data/test/hexapdf/type/actions/test_uri.rb +23 -0
- data/test/hexapdf/type/annotations/test_link.rb +19 -0
- data/test/hexapdf/type/annotations/test_markup_annotation.rb +22 -0
- data/test/hexapdf/type/annotations/test_text.rb +38 -0
- data/test/hexapdf/type/test_annotation.rb +38 -0
- data/test/hexapdf/type/test_file_specification.rb +0 -7
- data/test/hexapdf/type/test_info.rb +0 -5
- data/test/hexapdf/type/test_page.rb +14 -0
- data/test/hexapdf/type/test_page_tree_node.rb +4 -1
- data/test/hexapdf/type/test_trailer.rb +0 -4
- data/test/test_helper.rb +6 -3
- metadata +36 -15
- data/examples/text_box_inline_boxes.rb +0 -56
- data/examples/text_box_styling.rb +0 -72
- data/test/hexapdf/type/test_embedded_file.rb +0 -16
- data/test/hexapdf/type/test_names.rb +0 -9
|
@@ -9,7 +9,7 @@ require 'hexapdf/layout/text_fragment'
|
|
|
9
9
|
describe HexaPDF::Layout::TextFragment do
|
|
10
10
|
before do
|
|
11
11
|
@doc = HexaPDF::Document.new
|
|
12
|
-
@font = @doc.fonts.
|
|
12
|
+
@font = @doc.fonts.add("Times", custom_encoding: true)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def setup_fragment(items, text_rise = 0)
|
|
@@ -27,24 +27,139 @@ describe HexaPDF::Layout::TextFragment do
|
|
|
27
27
|
assert_equal(13.66 + 4.34, frag.height)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
describe "initialize" do
|
|
31
|
+
before do
|
|
32
|
+
@items = @font.decode_utf8("Tom")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "can use a Style object" do
|
|
36
|
+
style = HexaPDF::Layout::Style.new(font: @font, font_size: 20)
|
|
37
|
+
frag = HexaPDF::Layout::TextFragment.new(items: @items, style: style)
|
|
38
|
+
assert_equal(20, frag.style.font_size)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "can use a style options" do
|
|
42
|
+
frag = HexaPDF::Layout::TextFragment.new(items: @items, style: {font: @font, font_size: 20})
|
|
43
|
+
assert_equal(20, frag.style.font_size)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
30
47
|
it "returns :text for valign" do
|
|
31
48
|
assert_equal(:text, setup_fragment([]).valign)
|
|
32
49
|
end
|
|
33
50
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
describe "draw" do
|
|
52
|
+
def setup_with_style(**styles)
|
|
53
|
+
setup_fragment(@font.decode_utf8('H'), 2)
|
|
54
|
+
styles.each {|name, value| @fragment.style.send(name, value)}
|
|
55
|
+
@canvas = @doc.pages.add.canvas
|
|
56
|
+
@fragment.draw(@canvas, 10, 15)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def assert_draw_operators(*args, front: [], middle: args, back: [])
|
|
60
|
+
ops = [
|
|
61
|
+
*front,
|
|
62
|
+
[:begin_text],
|
|
63
|
+
[:set_text_matrix, [1, 0, 0, 1, 10, 15]],
|
|
64
|
+
[:set_font_and_size, [:F1, 20]],
|
|
65
|
+
[:set_leading, [24.0]],
|
|
66
|
+
[:set_horizontal_scaling, [200]],
|
|
67
|
+
[:set_character_spacing, [1]],
|
|
68
|
+
[:set_word_spacing, [2]],
|
|
69
|
+
[:set_text_rise, [2]],
|
|
70
|
+
*middle,
|
|
71
|
+
[:show_text_with_positioning, [['!']]],
|
|
72
|
+
*back,
|
|
73
|
+
].compact
|
|
74
|
+
assert_operators(@canvas.contents, ops)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "draws text onto the canvas" do
|
|
78
|
+
setup_with_style
|
|
79
|
+
assert_draw_operators
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "draws styled filled text" do
|
|
83
|
+
setup_with_style(fill_color: 0.5, fill_alpha: 0.5)
|
|
84
|
+
assert_draw_operators([:set_graphics_state_parameters, [:GS1]],
|
|
85
|
+
[:set_device_gray_non_stroking_color, [0.5]])
|
|
86
|
+
assert_equal({Type: :ExtGState, CA: 1, ca: 0.5}, @canvas.resources[:ExtGState][:GS1])
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "draws style stroked text" do
|
|
90
|
+
setup_with_style(text_rendering_mode: :stroke,
|
|
91
|
+
stroke_color: [1.0, 0, 0], stroke_alpha: 0.5, stroke_width: 2,
|
|
92
|
+
stroke_cap_style: :round, stroke_join_style: :round, stroke_miter_limit: 5,
|
|
93
|
+
stroke_dash_pattern: [1, 2, 3])
|
|
94
|
+
assert_draw_operators([:set_text_rendering_mode, [1]],
|
|
95
|
+
[:set_graphics_state_parameters, [:GS1]],
|
|
96
|
+
[:set_device_rgb_stroking_color, [1, 0, 0]],
|
|
97
|
+
[:set_line_width, [2]],
|
|
98
|
+
[:set_line_cap_style, [1]],
|
|
99
|
+
[:set_line_join_style, [1]],
|
|
100
|
+
[:set_miter_limit, [5]],
|
|
101
|
+
[:set_line_dash_pattern, [[1, 2, 3], 0]])
|
|
102
|
+
assert_equal({Type: :ExtGState, CA: 0.5, ca: 1}, @canvas.resources[:ExtGState][:GS1])
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "invokes the underlays" do
|
|
106
|
+
setup_with_style(underlays: [proc { @canvas.stroke_color(0.5) }])
|
|
107
|
+
assert_draw_operators(front: [[:save_graphics_state],
|
|
108
|
+
[:concatenate_matrix, [1, 0, 0, 1, 10, 15 + @fragment.y_min]],
|
|
109
|
+
[:save_graphics_state],
|
|
110
|
+
[:set_device_gray_stroking_color, [0.5]],
|
|
111
|
+
[:restore_graphics_state],
|
|
112
|
+
[:restore_graphics_state]])
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "invokes the overlays" do
|
|
116
|
+
setup_with_style(overlays: [proc { @canvas.stroke_color(0.5) }])
|
|
117
|
+
assert_draw_operators(back: [[:end_text],
|
|
118
|
+
[:save_graphics_state],
|
|
119
|
+
[:concatenate_matrix, [1, 0, 0, 1, 10, 15 + @fragment.y_min]],
|
|
120
|
+
[:save_graphics_state],
|
|
121
|
+
[:set_device_gray_stroking_color, [0.5]],
|
|
122
|
+
[:restore_graphics_state],
|
|
123
|
+
[:restore_graphics_state]])
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it "draws the underline" do
|
|
127
|
+
setup_with_style(underline: true, text_rendering_mode: :stroke,
|
|
128
|
+
stroke_width: 5, stroke_color: [0.5], stroke_cap_style: :round,
|
|
129
|
+
stroke_dash_pattern: 5)
|
|
130
|
+
assert_draw_operators(middle: [[:set_text_rendering_mode, [1]],
|
|
131
|
+
[:set_device_gray_stroking_color, [0.5]],
|
|
132
|
+
[:set_line_width, [5]],
|
|
133
|
+
[:set_line_cap_style, [1]],
|
|
134
|
+
[:set_line_dash_pattern, [[5], 0]]],
|
|
135
|
+
back: [[:set_device_gray_stroking_color, [0]],
|
|
136
|
+
[:set_line_width, [@fragment.style.calculated_underline_thickness]],
|
|
137
|
+
[:set_line_cap_style, [0]],
|
|
138
|
+
[:set_line_dash_pattern, [[], 0]],
|
|
139
|
+
[:end_text],
|
|
140
|
+
[:move_to, [10, 15]],
|
|
141
|
+
[:line_to, [40.88, 15]],
|
|
142
|
+
[:stroke_path]])
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it "draws the strikeout line" do
|
|
146
|
+
setup_with_style(strikeout: true, text_rendering_mode: :stroke,
|
|
147
|
+
stroke_width: 5, stroke_color: [0.5], stroke_cap_style: :round,
|
|
148
|
+
stroke_dash_pattern: 5)
|
|
149
|
+
assert_draw_operators(middle: [[:set_text_rendering_mode, [1]],
|
|
150
|
+
[:set_device_gray_stroking_color, [0.5]],
|
|
151
|
+
[:set_line_width, [5]],
|
|
152
|
+
[:set_line_cap_style, [1]],
|
|
153
|
+
[:set_line_dash_pattern, [[5], 0]]],
|
|
154
|
+
back: [[:set_device_gray_stroking_color, [0]],
|
|
155
|
+
[:set_line_width, [@fragment.style.calculated_strikeout_thickness]],
|
|
156
|
+
[:set_line_cap_style, [0]],
|
|
157
|
+
[:set_line_dash_pattern, [[], 0]],
|
|
158
|
+
[:end_text],
|
|
159
|
+
[:move_to, [10, 21.01]],
|
|
160
|
+
[:line_to, [40.88, 21.01]],
|
|
161
|
+
[:stroke_path]])
|
|
162
|
+
end
|
|
48
163
|
end
|
|
49
164
|
|
|
50
165
|
describe "empty fragment" do
|
|
@@ -75,6 +190,12 @@ describe HexaPDF::Layout::TextFragment do
|
|
|
75
190
|
it "calculates the height" do
|
|
76
191
|
assert_equal(13.66 + 4.34, @fragment.height)
|
|
77
192
|
end
|
|
193
|
+
|
|
194
|
+
it "draws nothing" do
|
|
195
|
+
canvas = @doc.pages.add.canvas
|
|
196
|
+
@fragment.draw(canvas, 10, 15)
|
|
197
|
+
assert_operators(canvas.contents, [])
|
|
198
|
+
end
|
|
78
199
|
end
|
|
79
200
|
|
|
80
201
|
describe "normal text" do
|
|
@@ -5,28 +5,28 @@ require 'hexapdf/layout'
|
|
|
5
5
|
require 'hexapdf/document'
|
|
6
6
|
require_relative "../content/common"
|
|
7
7
|
|
|
8
|
-
module
|
|
8
|
+
module TestTextLayouterHelpers
|
|
9
9
|
def boxes(*dims)
|
|
10
10
|
dims.map do |width, height|
|
|
11
|
-
box = HexaPDF::Layout::InlineBox.
|
|
12
|
-
HexaPDF::Layout::
|
|
11
|
+
box = HexaPDF::Layout::InlineBox.create(width: width, height: height || 0) {}
|
|
12
|
+
HexaPDF::Layout::TextLayouter::Box.new(box)
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def glue(width)
|
|
17
|
-
HexaPDF::Layout::
|
|
17
|
+
HexaPDF::Layout::TextLayouter::Glue.new(HexaPDF::Layout::InlineBox.create(width: width) {})
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def penalty(penalty, item = nil)
|
|
21
21
|
if item
|
|
22
|
-
HexaPDF::Layout::
|
|
22
|
+
HexaPDF::Layout::TextLayouter::Penalty.new(penalty, item.width, item: item)
|
|
23
23
|
else
|
|
24
|
-
HexaPDF::Layout::
|
|
24
|
+
HexaPDF::Layout::TextLayouter::Penalty.new(penalty)
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def assert_box(obj, item)
|
|
29
|
-
assert_kind_of(HexaPDF::Layout::
|
|
29
|
+
assert_kind_of(HexaPDF::Layout::TextLayouter::Box, obj)
|
|
30
30
|
if obj.item.kind_of?(HexaPDF::Layout::InlineBox)
|
|
31
31
|
assert_same(item, obj.item)
|
|
32
32
|
else
|
|
@@ -36,12 +36,12 @@ module TestTextBoxHelpers
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def assert_glue(obj, fragment)
|
|
39
|
-
assert_kind_of(HexaPDF::Layout::
|
|
39
|
+
assert_kind_of(HexaPDF::Layout::TextLayouter::Glue, obj)
|
|
40
40
|
assert_same(fragment.style, obj.item.style)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def assert_penalty(obj, penalty, item = nil)
|
|
44
|
-
assert_kind_of(HexaPDF::Layout::
|
|
44
|
+
assert_kind_of(HexaPDF::Layout::TextLayouter::Penalty, obj)
|
|
45
45
|
assert_equal(penalty, obj.penalty)
|
|
46
46
|
if item
|
|
47
47
|
assert_same(item.style, obj.item.style)
|
|
@@ -50,13 +50,13 @@ module TestTextBoxHelpers
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
describe HexaPDF::Layout::
|
|
54
|
-
include
|
|
53
|
+
describe HexaPDF::Layout::TextLayouter::SimpleTextSegmentation do
|
|
54
|
+
include TestTextLayouterHelpers
|
|
55
55
|
|
|
56
56
|
before do
|
|
57
57
|
@doc = HexaPDF::Document.new
|
|
58
|
-
@font = @doc.fonts.
|
|
59
|
-
@obj = HexaPDF::Layout::
|
|
58
|
+
@font = @doc.fonts.add("Times")
|
|
59
|
+
@obj = HexaPDF::Layout::TextLayouter::SimpleTextSegmentation
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
def setup_fragment(text, style = nil)
|
|
@@ -68,7 +68,7 @@ describe HexaPDF::Layout::TextBox::SimpleTextSegmentation do
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
it "handles InlineBox objects" do
|
|
71
|
-
input = HexaPDF::Layout::InlineBox.
|
|
71
|
+
input = HexaPDF::Layout::InlineBox.create(width: 10, height: 10) { }
|
|
72
72
|
result = @obj.call([input, input])
|
|
73
73
|
assert_equal(2, result.size)
|
|
74
74
|
assert_box(result[0], input)
|
|
@@ -103,13 +103,16 @@ describe HexaPDF::Layout::TextBox::SimpleTextSegmentation do
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
it "insert a mandatory break when an Unicode line boundary characters is encountered" do
|
|
106
|
-
frag = setup_fragment("A\rB\r\nC\nD\vE\fF\u{85}G\u{
|
|
106
|
+
frag = setup_fragment("A\rB\r\nC\nD\vE\fF\u{85}G\u{2029}H\u{2028}I")
|
|
107
107
|
|
|
108
108
|
result = @obj.call([frag])
|
|
109
109
|
assert_equal(17, result.size)
|
|
110
|
-
[1, 3, 5, 7, 9, 11, 13
|
|
111
|
-
assert_penalty(result[index],
|
|
110
|
+
[1, 3, 5, 7, 9, 11, 13].each do |index|
|
|
111
|
+
assert_penalty(result[index],
|
|
112
|
+
HexaPDF::Layout::TextLayouter::Penalty::MandatoryParagraphBreak.penalty)
|
|
112
113
|
end
|
|
114
|
+
assert_penalty(result[15],
|
|
115
|
+
HexaPDF::Layout::TextLayouter::Penalty::MandatoryLineBreak.penalty)
|
|
113
116
|
end
|
|
114
117
|
|
|
115
118
|
it "insert a standard penalty after a hyphen" do
|
|
@@ -118,7 +121,7 @@ describe HexaPDF::Layout::TextBox::SimpleTextSegmentation do
|
|
|
118
121
|
result = @obj.call([frag])
|
|
119
122
|
assert_equal(12, result.size)
|
|
120
123
|
[1, 3, 5, 9].each do |index|
|
|
121
|
-
assert_penalty(result[index], HexaPDF::Layout::
|
|
124
|
+
assert_penalty(result[index], HexaPDF::Layout::TextLayouter::Penalty::Standard.penalty)
|
|
122
125
|
end
|
|
123
126
|
end
|
|
124
127
|
|
|
@@ -137,7 +140,7 @@ describe HexaPDF::Layout::TextBox::SimpleTextSegmentation do
|
|
|
137
140
|
|
|
138
141
|
result = @obj.call([frag])
|
|
139
142
|
assert_equal(3, result.size)
|
|
140
|
-
assert_penalty(result[1], HexaPDF::Layout::
|
|
143
|
+
assert_penalty(result[1], HexaPDF::Layout::TextLayouter::Penalty::Standard.penalty, hyphen)
|
|
141
144
|
end
|
|
142
145
|
|
|
143
146
|
it "insert a prohibited break penalty for non-breaking spaces" do
|
|
@@ -146,16 +149,17 @@ describe HexaPDF::Layout::TextBox::SimpleTextSegmentation do
|
|
|
146
149
|
|
|
147
150
|
result = @obj.call([frag])
|
|
148
151
|
assert_equal(3, result.size)
|
|
149
|
-
assert_penalty(result[1], HexaPDF::Layout::
|
|
152
|
+
assert_penalty(result[1], HexaPDF::Layout::TextLayouter::Penalty::ProhibitedBreak.penalty, space)
|
|
150
153
|
end
|
|
151
154
|
end
|
|
152
155
|
|
|
153
156
|
# Common tests for fixed and variable width line wrapping. The including class needs to define a
|
|
154
|
-
# #call(items, width = 100) method with a default with of 100.
|
|
157
|
+
# #call(items, width = 100) method with a default with of 100. The optional block is called after a
|
|
158
|
+
# line has been yielded by the line wrapping algorithm.
|
|
155
159
|
module CommonLineWrappingTests
|
|
156
160
|
extend Minitest::Spec::DSL
|
|
157
161
|
|
|
158
|
-
include
|
|
162
|
+
include TestTextLayouterHelpers
|
|
159
163
|
|
|
160
164
|
it "breaks before a box if it doesn't fit onto the line anymore" do
|
|
161
165
|
rest, lines = call(boxes(25, 50, 25, 10))
|
|
@@ -226,7 +230,7 @@ module CommonLineWrappingTests
|
|
|
226
230
|
end
|
|
227
231
|
|
|
228
232
|
it "handles breaking at penalties with non-zero width if they fit on the line" do
|
|
229
|
-
item = HexaPDF::Layout::InlineBox.
|
|
233
|
+
item = HexaPDF::Layout::InlineBox.create(width: 20) {}
|
|
230
234
|
rest, lines = call(boxes(20, 60, 30).insert(1, penalty(0, item)).insert(-2, penalty(0, item)))
|
|
231
235
|
assert(rest.empty?)
|
|
232
236
|
assert_equal(2, lines.count)
|
|
@@ -236,7 +240,7 @@ module CommonLineWrappingTests
|
|
|
236
240
|
end
|
|
237
241
|
|
|
238
242
|
it "handles penalties with non-zero width if they don't fit on the line" do
|
|
239
|
-
item = HexaPDF::Layout::InlineBox.
|
|
243
|
+
item = HexaPDF::Layout::InlineBox.create(width: 20) {}
|
|
240
244
|
rest, lines = call(boxes(70) + [glue(10)] + boxes(10) + [penalty(0, item)] + boxes(30))
|
|
241
245
|
assert(rest.empty?)
|
|
242
246
|
assert_equal(2, lines.count)
|
|
@@ -245,7 +249,7 @@ module CommonLineWrappingTests
|
|
|
245
249
|
end
|
|
246
250
|
|
|
247
251
|
it "handles breaking at prohibited breakpoints by back-tracking to the last valid breakpoint " do
|
|
248
|
-
item = HexaPDF::Layout::InlineBox.
|
|
252
|
+
item = HexaPDF::Layout::InlineBox.create(width: 20) {}
|
|
249
253
|
rest, lines = call(boxes(70) + [glue(10)] + boxes(10) + [penalty(5000, item)] + boxes(30))
|
|
250
254
|
assert(rest.empty?)
|
|
251
255
|
assert_equal(2, lines.count)
|
|
@@ -254,8 +258,8 @@ module CommonLineWrappingTests
|
|
|
254
258
|
end
|
|
255
259
|
|
|
256
260
|
it "stops when nil is returned by the block: last item is a box" do
|
|
257
|
-
|
|
258
|
-
rest =
|
|
261
|
+
done = false
|
|
262
|
+
rest, lines = call(boxes(20, 20, 20), 20) { done ? nil : done = true }
|
|
259
263
|
assert_equal(2, rest.count)
|
|
260
264
|
assert_equal(2, lines.count)
|
|
261
265
|
end
|
|
@@ -263,37 +267,38 @@ module CommonLineWrappingTests
|
|
|
263
267
|
it "stops when nil is returned by the block: last item is a glue" do
|
|
264
268
|
done = false
|
|
265
269
|
items = boxes(20, 15, 20).insert(-2, glue(10))
|
|
266
|
-
rest =
|
|
270
|
+
rest, = call(items, 20) { done ? nil : done = true }
|
|
267
271
|
assert_equal(3, rest.count)
|
|
268
272
|
assert_equal(15, rest[0].width)
|
|
269
273
|
end
|
|
270
274
|
|
|
271
275
|
it "stops when nil is returned by the block: last item is a mandatory break penalty" do
|
|
272
276
|
items = boxes(20, 20).insert(-2, penalty(-5000))
|
|
273
|
-
rest =
|
|
277
|
+
rest, = call(items, 20) { nil }
|
|
274
278
|
assert_equal(3, rest.count)
|
|
275
279
|
end
|
|
276
280
|
|
|
277
281
|
it "stops when nil is returned by the block: works for the last line" do
|
|
278
|
-
|
|
279
|
-
rest =
|
|
282
|
+
done = false
|
|
283
|
+
rest, lines = call(boxes(20, 20), 20) { done ? nil : done = true }
|
|
280
284
|
assert_equal(1, rest.count)
|
|
281
285
|
assert_equal(2, lines.count)
|
|
282
286
|
end
|
|
283
287
|
|
|
284
288
|
end
|
|
285
289
|
|
|
286
|
-
describe HexaPDF::Layout::
|
|
290
|
+
describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
|
|
287
291
|
before do
|
|
288
|
-
@obj = HexaPDF::Layout::
|
|
292
|
+
@obj = HexaPDF::Layout::TextLayouter::SimpleLineWrapping
|
|
289
293
|
end
|
|
290
294
|
|
|
291
295
|
describe "fixed width wrapping" do
|
|
292
296
|
include CommonLineWrappingTests
|
|
293
297
|
|
|
294
|
-
def call(items, width = 100)
|
|
298
|
+
def call(items, width = 100, &block)
|
|
295
299
|
lines = []
|
|
296
|
-
|
|
300
|
+
block ||= proc { true }
|
|
301
|
+
rest = @obj.call(items, proc { width }) {|line, item| lines << line; block.call(line, item) }
|
|
297
302
|
[rest, lines]
|
|
298
303
|
end
|
|
299
304
|
end
|
|
@@ -301,9 +306,10 @@ describe HexaPDF::Layout::TextBox::SimpleLineWrapping do
|
|
|
301
306
|
describe "variable width wrapping" do
|
|
302
307
|
include CommonLineWrappingTests
|
|
303
308
|
|
|
304
|
-
def call(items, width =
|
|
309
|
+
def call(items, width = 100, &block)
|
|
305
310
|
lines = []
|
|
306
|
-
|
|
311
|
+
block ||= proc { true }
|
|
312
|
+
rest = @obj.call(items, proc {|_| width }) {|line, i| lines << line; block.call(line, i) }
|
|
307
313
|
[rest, lines]
|
|
308
314
|
end
|
|
309
315
|
|
|
@@ -339,7 +345,7 @@ describe HexaPDF::Layout::TextBox::SimpleLineWrapping do
|
|
|
339
345
|
end
|
|
340
346
|
end
|
|
341
347
|
lines = []
|
|
342
|
-
item = HexaPDF::Layout::InlineBox.
|
|
348
|
+
item = HexaPDF::Layout::InlineBox.create(width: 20, height: 10) {}
|
|
343
349
|
items = boxes([20, 10]) + [penalty(0, item)] + boxes([40, 15])
|
|
344
350
|
rest = @obj.call(items, width_block) do |line|
|
|
345
351
|
height += line.height
|
|
@@ -355,93 +361,121 @@ describe HexaPDF::Layout::TextBox::SimpleLineWrapping do
|
|
|
355
361
|
end
|
|
356
362
|
end
|
|
357
363
|
|
|
358
|
-
describe HexaPDF::Layout::
|
|
359
|
-
include
|
|
364
|
+
describe HexaPDF::Layout::TextLayouter do
|
|
365
|
+
include TestTextLayouterHelpers
|
|
360
366
|
|
|
361
367
|
before do
|
|
362
368
|
@doc = HexaPDF::Document.new
|
|
363
|
-
@font = @doc.fonts.
|
|
369
|
+
@font = @doc.fonts.add("Times")
|
|
364
370
|
@style = HexaPDF::Layout::Style.new(font: @font)
|
|
365
371
|
end
|
|
366
372
|
|
|
367
373
|
it "creates an instance from text and options" do
|
|
368
|
-
|
|
369
|
-
assert_equal(1,
|
|
370
|
-
assert_equal(@font.decode_utf8("T"),
|
|
374
|
+
layouter = HexaPDF::Layout::TextLayouter.create("T", font: @font, width: 100, height: 100)
|
|
375
|
+
assert_equal(1, layouter.items.length)
|
|
376
|
+
assert_equal(@font.decode_utf8("T"), layouter.items[0].item.items)
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
describe "initialize" do
|
|
380
|
+
it "can use a Style object" do
|
|
381
|
+
style = HexaPDF::Layout::Style.new(font: @font, font_size: 20)
|
|
382
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: [], width: 10, style: style)
|
|
383
|
+
assert_equal(20, layouter.style.font_size)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
it "can use a style options" do
|
|
387
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: [], width: 10, style:
|
|
388
|
+
{font: @font, font_size: 20})
|
|
389
|
+
assert_equal(20, layouter.style.font_size)
|
|
390
|
+
end
|
|
371
391
|
end
|
|
372
392
|
|
|
373
393
|
it "doesn't run the text segmentation algorithm on already segmented items" do
|
|
374
|
-
item = HexaPDF::Layout::InlineBox.
|
|
375
|
-
|
|
376
|
-
items =
|
|
394
|
+
item = HexaPDF::Layout::InlineBox.create(width: 20) {}
|
|
395
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: [item], width: 100, height: 100)
|
|
396
|
+
items = layouter.items
|
|
377
397
|
assert_equal(1, items.length)
|
|
378
398
|
assert_box(items[0], item)
|
|
379
399
|
|
|
380
|
-
|
|
381
|
-
assert_same(items,
|
|
400
|
+
layouter.items = items
|
|
401
|
+
assert_same(items, layouter.items)
|
|
382
402
|
end
|
|
383
403
|
|
|
384
404
|
describe "fit" do
|
|
385
405
|
it "handles text indentation" do
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
406
|
+
items = boxes([20, 20], [20, 20], [20, 20]) +
|
|
407
|
+
[HexaPDF::Layout::TextLayouter::Penalty::MandatoryParagraphBreak] +
|
|
408
|
+
boxes([40, 20]) + [glue(20)] +
|
|
409
|
+
boxes(*([[20, 20]] * 4)) + [HexaPDF::Layout::TextLayouter::Penalty::MandatoryLineBreak] +
|
|
410
|
+
boxes(*([[20, 20]] * 4))
|
|
411
|
+
@style.text_indent = 20
|
|
412
|
+
|
|
413
|
+
[60, proc { 60 }].each do |width|
|
|
414
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: items, width: width, style: @style)
|
|
415
|
+
rest, reason = layouter.fit
|
|
416
|
+
assert_equal([40, 20, 40, 60, 20, 60, 20], layouter.lines.map(&:width))
|
|
417
|
+
assert_equal([20, 0, 20, 0, 0, 0, 0], layouter.lines.map(&:x_offset))
|
|
418
|
+
assert(rest.empty?)
|
|
419
|
+
assert_equal(:success, reason)
|
|
420
|
+
end
|
|
394
421
|
end
|
|
395
422
|
|
|
396
423
|
it "fits using unlimited height" do
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
rest,
|
|
424
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: boxes(*([[20, 20]] * 100)), width: 20,
|
|
425
|
+
style: @style)
|
|
426
|
+
rest, reason = layouter.fit
|
|
400
427
|
assert(rest.empty?)
|
|
401
|
-
assert_equal(
|
|
428
|
+
assert_equal(:success, reason)
|
|
429
|
+
assert_equal(20 * 100, layouter.actual_height)
|
|
402
430
|
end
|
|
403
431
|
|
|
404
432
|
it "fits using a limited height" do
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
rest,
|
|
433
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: boxes(*([[20, 20]] * 100)), width: 20,
|
|
434
|
+
height: 100, style: @style)
|
|
435
|
+
rest, reason = layouter.fit
|
|
408
436
|
assert_equal(95, rest.count)
|
|
409
|
-
assert_equal(
|
|
437
|
+
assert_equal(:height, reason)
|
|
438
|
+
assert_equal(100, layouter.actual_height)
|
|
410
439
|
end
|
|
411
440
|
|
|
412
441
|
it "takes line spacing into account when calculating the height" do
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
442
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: boxes(*([[20, 20]] * 5)), width: 20,
|
|
443
|
+
style: @style)
|
|
444
|
+
layouter.style.line_spacing = :double
|
|
445
|
+
rest, reason = layouter.fit
|
|
416
446
|
assert(rest.empty?)
|
|
417
|
-
assert_equal(
|
|
447
|
+
assert_equal(:success, reason)
|
|
448
|
+
assert_equal(20 * (5 + 4), layouter.actual_height)
|
|
418
449
|
end
|
|
419
450
|
|
|
420
451
|
it "handles empty lines" do
|
|
421
452
|
items = boxes([20, 20]) + [penalty(-5000)] + boxes([30, 20]) + [penalty(-5000)] * 2 +
|
|
422
453
|
boxes([20, 20]) + [penalty(-5000)] * 2
|
|
423
|
-
|
|
424
|
-
rest,
|
|
454
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: items, width: 30, style: @style)
|
|
455
|
+
rest, reason = layouter.fit
|
|
425
456
|
assert(rest.empty?)
|
|
426
|
-
assert_equal(
|
|
427
|
-
assert_equal(
|
|
457
|
+
assert_equal(:success, reason)
|
|
458
|
+
assert_equal(5, layouter.lines.count)
|
|
459
|
+
assert_equal(20 + 20 + 9 + 20 + 9, layouter.actual_height)
|
|
428
460
|
end
|
|
429
461
|
|
|
430
462
|
describe "fixed width" do
|
|
431
463
|
it "stops if an item is wider than the available width, with unlimited height" do
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
rest,
|
|
464
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: boxes([20, 20], [50, 20]), width: 30,
|
|
465
|
+
style: @style)
|
|
466
|
+
rest, reason = layouter.fit
|
|
435
467
|
assert_equal(1, rest.count)
|
|
436
|
-
assert_equal(
|
|
468
|
+
assert_equal(:box, reason)
|
|
469
|
+
assert_equal(20, layouter.actual_height)
|
|
437
470
|
end
|
|
438
471
|
|
|
439
|
-
it "stops if
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
rest,
|
|
472
|
+
it "stops if a box item is wider than the available width, with limited height" do
|
|
473
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: boxes([20, 20], [50, 20]), width: 30,
|
|
474
|
+
height: 100, style: @style)
|
|
475
|
+
rest, reason = layouter.fit
|
|
443
476
|
assert_equal(1, rest.count)
|
|
444
|
-
assert_equal(
|
|
477
|
+
assert_equal(:box, reason)
|
|
478
|
+
assert_equal(20, layouter.actual_height)
|
|
445
479
|
end
|
|
446
480
|
end
|
|
447
481
|
|
|
@@ -453,13 +487,14 @@ describe HexaPDF::Layout::TextBox do
|
|
|
453
487
|
else 40
|
|
454
488
|
end
|
|
455
489
|
end
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
rest,
|
|
490
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: boxes([20, 18]), width: width_block,
|
|
491
|
+
height: 100, style: @style)
|
|
492
|
+
rest, reason = layouter.fit
|
|
459
493
|
assert(rest.empty?)
|
|
460
|
-
assert_equal(
|
|
461
|
-
assert_equal(
|
|
462
|
-
assert_equal(
|
|
494
|
+
assert_equal(:success, reason)
|
|
495
|
+
assert_equal(1, layouter.lines.count)
|
|
496
|
+
assert_equal(24, layouter.lines[0].y_offset)
|
|
497
|
+
assert_equal(42, layouter.actual_height)
|
|
463
498
|
end
|
|
464
499
|
|
|
465
500
|
it "searches for a vertical offset if an item is wider than the available width" do
|
|
@@ -470,52 +505,75 @@ describe HexaPDF::Layout::TextBox do
|
|
|
470
505
|
40
|
|
471
506
|
end
|
|
472
507
|
end
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
rest,
|
|
508
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: boxes(*([[20, 18]] * 7)),
|
|
509
|
+
width: width_block, height: 100, style: @style)
|
|
510
|
+
rest, reason = layouter.fit
|
|
476
511
|
assert_equal(1, rest.count)
|
|
477
|
-
assert_equal(
|
|
478
|
-
assert_equal(
|
|
479
|
-
assert_equal(
|
|
480
|
-
assert_equal(
|
|
481
|
-
assert_equal(
|
|
512
|
+
assert_equal(:height, reason)
|
|
513
|
+
assert_equal(3, layouter.lines.count)
|
|
514
|
+
assert_equal(0, layouter.lines[0].y_offset)
|
|
515
|
+
assert_equal(18, layouter.lines[1].y_offset)
|
|
516
|
+
assert_equal(48, layouter.lines[2].y_offset)
|
|
517
|
+
assert_equal(84, layouter.actual_height)
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
it "breaks a text fragment into parts if it is wider than the available width" do
|
|
522
|
+
[100, nil].each do |height|
|
|
523
|
+
str = " Thisisaverylongstring"
|
|
524
|
+
frag = HexaPDF::Layout::TextFragment.create(str, font: @font)
|
|
525
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: [frag], width: 20, height: height,
|
|
526
|
+
style: @style)
|
|
527
|
+
rest, reason = layouter.fit
|
|
528
|
+
assert(rest.empty?)
|
|
529
|
+
assert_equal(:success, reason)
|
|
530
|
+
assert_equal(str.strip.length, layouter.lines.sum {|l| l.items.sum {|i| i.items.count}})
|
|
531
|
+
assert_equal(45, layouter.actual_height)
|
|
532
|
+
|
|
533
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: [frag], width: 1, height: height,
|
|
534
|
+
style: @style)
|
|
535
|
+
rest, reason = layouter.fit
|
|
536
|
+
assert_equal(str.strip.length, rest.count)
|
|
537
|
+
assert_equal(:box, reason)
|
|
482
538
|
end
|
|
483
539
|
end
|
|
484
540
|
|
|
485
541
|
it "post-processes lines for justification if needed" do
|
|
486
542
|
frag10 = HexaPDF::Layout::TextFragment.create(" ", font: @font)
|
|
487
543
|
frag10.items.freeze
|
|
488
|
-
frag10b = HexaPDF::Layout::
|
|
544
|
+
frag10b = HexaPDF::Layout::TextLayouter::Box.new(frag10)
|
|
489
545
|
frag20 = HexaPDF::Layout::TextFragment.create(" ", font: @font, font_size: 20)
|
|
490
|
-
frag20b = HexaPDF::Layout::
|
|
546
|
+
frag20b = HexaPDF::Layout::TextLayouter::Box.new(frag20)
|
|
491
547
|
items = boxes(20, 20, 20, 20, 30).insert(1, frag10b).insert(3, frag20b).insert(5, frag10b)
|
|
492
548
|
# Width of spaces: 2.5 * 2 + 5 = 10 (from AFM file, adjusted for font size)
|
|
493
549
|
# Line width: 20 * 4 + width_of_spaces = 90
|
|
494
550
|
# Missing width: 100 - 90 = 10
|
|
495
551
|
# -> Each space must be doubled!
|
|
496
552
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
rest,
|
|
553
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: items, width: 100)
|
|
554
|
+
layouter.style.align = :justify
|
|
555
|
+
rest, reason = layouter.fit
|
|
500
556
|
assert(rest.empty?)
|
|
501
|
-
assert_equal(
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
assert_equal(-250,
|
|
505
|
-
assert_equal(-250,
|
|
506
|
-
assert_equal(
|
|
557
|
+
assert_equal(:success, reason)
|
|
558
|
+
assert_equal(9, layouter.lines[0].items.count)
|
|
559
|
+
assert_in_delta(100, layouter.lines[0].width)
|
|
560
|
+
assert_equal(-250, layouter.lines[0].items[1].items[0])
|
|
561
|
+
assert_equal(-250, layouter.lines[0].items[4].items[0])
|
|
562
|
+
assert_equal(-250, layouter.lines[0].items[6].items[0])
|
|
563
|
+
assert_equal(30, layouter.lines[1].width)
|
|
507
564
|
end
|
|
508
565
|
|
|
509
566
|
it "applies the optional horizontal offsets if set" do
|
|
510
567
|
x_offsets = lambda {|height, line_height| height + line_height}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
rest,
|
|
568
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: boxes(*([[20, 10]] * 7)), width: 60,
|
|
569
|
+
x_offsets: x_offsets, height: 100, style: @style)
|
|
570
|
+
rest, reason = layouter.fit
|
|
514
571
|
assert(rest.empty?)
|
|
515
|
-
assert_equal(
|
|
516
|
-
assert_equal(
|
|
517
|
-
assert_equal(
|
|
518
|
-
assert_equal(
|
|
572
|
+
assert_equal(:success, reason)
|
|
573
|
+
assert_equal(30, layouter.actual_height)
|
|
574
|
+
assert_equal(10, layouter.lines[0].x_offset)
|
|
575
|
+
assert_equal(20, layouter.lines[1].x_offset)
|
|
576
|
+
assert_equal(30, layouter.lines[2].x_offset)
|
|
519
577
|
end
|
|
520
578
|
end
|
|
521
579
|
|
|
@@ -535,7 +593,7 @@ describe HexaPDF::Layout::TextBox do
|
|
|
535
593
|
@frag = HexaPDF::Layout::TextFragment.create("This is some more text.\n" \
|
|
536
594
|
"This is some more text.", font: @font)
|
|
537
595
|
@width = HexaPDF::Layout::TextFragment.create("This is some ", font: @font).width
|
|
538
|
-
@
|
|
596
|
+
@layouter = HexaPDF::Layout::TextLayouter.new(items: [@frag], width: @width)
|
|
539
597
|
@canvas = @doc.pages.add.canvas
|
|
540
598
|
|
|
541
599
|
@line1w = HexaPDF::Layout::TextFragment.create("This is some", font: @font).width
|
|
@@ -544,8 +602,8 @@ describe HexaPDF::Layout::TextBox do
|
|
|
544
602
|
|
|
545
603
|
it "can horizontally align the contents to the left" do
|
|
546
604
|
top = 100
|
|
547
|
-
@
|
|
548
|
-
@
|
|
605
|
+
@layouter.style.align = :left
|
|
606
|
+
@layouter.draw(@canvas, 5, top)
|
|
549
607
|
assert_positions(@canvas.contents,
|
|
550
608
|
[[5, top - @frag.y_max],
|
|
551
609
|
[5, top - @frag.y_max - @frag.height],
|
|
@@ -555,8 +613,8 @@ describe HexaPDF::Layout::TextBox do
|
|
|
555
613
|
|
|
556
614
|
it "can horizontally align the contents to the center" do
|
|
557
615
|
top = 100
|
|
558
|
-
@
|
|
559
|
-
@
|
|
616
|
+
@layouter.style.align = :center
|
|
617
|
+
@layouter.draw(@canvas, 5, top)
|
|
560
618
|
assert_positions(@canvas.contents,
|
|
561
619
|
[[5 + (@width - @line1w) / 2, top - @frag.y_max],
|
|
562
620
|
[5 + (@width - @line2w) / 2, top - @frag.y_max - @frag.height],
|
|
@@ -566,8 +624,8 @@ describe HexaPDF::Layout::TextBox do
|
|
|
566
624
|
|
|
567
625
|
it "can horizontally align the contents to the right" do
|
|
568
626
|
top = 100
|
|
569
|
-
@
|
|
570
|
-
@
|
|
627
|
+
@layouter.style.align = :right
|
|
628
|
+
@layouter.draw(@canvas, 5, top)
|
|
571
629
|
assert_positions(@canvas.contents,
|
|
572
630
|
[[5 + @width - @line1w, top - @frag.y_max],
|
|
573
631
|
[5 + @width - @line2w, top - @frag.y_max - @frag.height],
|
|
@@ -577,32 +635,32 @@ describe HexaPDF::Layout::TextBox do
|
|
|
577
635
|
|
|
578
636
|
it "can justify the contents" do
|
|
579
637
|
top = 100
|
|
580
|
-
@
|
|
581
|
-
@
|
|
638
|
+
@layouter.style.align = :justify
|
|
639
|
+
@layouter.draw(@canvas, 5, top)
|
|
582
640
|
assert_positions(@canvas.contents,
|
|
583
641
|
[[5, top - @frag.y_max],
|
|
584
642
|
[5, top - @frag.y_max - @frag.height],
|
|
585
643
|
[5, top - @frag.y_max - @frag.height * 2],
|
|
586
644
|
[5, top - @frag.y_max - @frag.height * 3]])
|
|
587
|
-
assert_in_delta(@width, @
|
|
588
|
-
assert_in_delta(@width, @
|
|
645
|
+
assert_in_delta(@width, @layouter.lines[0].width, 0.0001)
|
|
646
|
+
assert_in_delta(@width, @layouter.lines[2].width, 0.0001)
|
|
589
647
|
end
|
|
590
648
|
|
|
591
649
|
it "doesn't justify lines ending in a mandatory break or the last line" do
|
|
592
|
-
@
|
|
593
|
-
@
|
|
594
|
-
assert_equal(@line2w, @
|
|
595
|
-
assert_equal(@line2w, @
|
|
650
|
+
@layouter.style.align = :justify
|
|
651
|
+
@layouter.draw(@canvas, 5, 100)
|
|
652
|
+
assert_equal(@line2w, @layouter.lines[1].width, 0.0001)
|
|
653
|
+
assert_equal(@line2w, @layouter.lines[3].width, 0.0001)
|
|
596
654
|
end
|
|
597
655
|
|
|
598
656
|
it "can vertically align the contents in the center" do
|
|
599
657
|
top = 100
|
|
600
|
-
@
|
|
601
|
-
@
|
|
658
|
+
@layouter = HexaPDF::Layout::TextLayouter.new(items: [@frag], width: @width, height: top)
|
|
659
|
+
@layouter.style.valign = :center
|
|
602
660
|
|
|
603
|
-
|
|
604
|
-
initial_baseline = top - ((top -
|
|
605
|
-
@
|
|
661
|
+
@layouter.fit
|
|
662
|
+
initial_baseline = top - ((top - @layouter.actual_height) / 2) - @frag.y_max
|
|
663
|
+
@layouter.draw(@canvas, 5, top)
|
|
606
664
|
assert_positions(@canvas.contents,
|
|
607
665
|
[[5, initial_baseline],
|
|
608
666
|
[5, initial_baseline - @frag.height],
|
|
@@ -612,12 +670,12 @@ describe HexaPDF::Layout::TextBox do
|
|
|
612
670
|
|
|
613
671
|
it "can vertically align the contents to the bottom" do
|
|
614
672
|
top = 100
|
|
615
|
-
@
|
|
616
|
-
@
|
|
673
|
+
@layouter = HexaPDF::Layout::TextLayouter.new(items: [@frag], width: @width, height: top)
|
|
674
|
+
@layouter.style.valign = :bottom
|
|
617
675
|
|
|
618
|
-
|
|
619
|
-
initial_baseline =
|
|
620
|
-
@
|
|
676
|
+
@layouter.fit
|
|
677
|
+
initial_baseline = @layouter.actual_height - @frag.y_max
|
|
678
|
+
@layouter.draw(@canvas, 5, top)
|
|
621
679
|
assert_positions(@canvas.contents,
|
|
622
680
|
[[5, initial_baseline],
|
|
623
681
|
[5, initial_baseline - @frag.height],
|
|
@@ -626,15 +684,37 @@ describe HexaPDF::Layout::TextBox do
|
|
|
626
684
|
end
|
|
627
685
|
|
|
628
686
|
it "raises an error if vertical alignment is :center/:bottom and an unlimited height is used" do
|
|
629
|
-
@
|
|
687
|
+
@layouter = HexaPDF::Layout::TextLayouter.new(items: [@frag], width: @width)
|
|
630
688
|
assert_raises(HexaPDF::Error) do
|
|
631
|
-
@
|
|
632
|
-
@
|
|
689
|
+
@layouter.style.valign = :center
|
|
690
|
+
@layouter.draw(@canvas, 0, 0)
|
|
633
691
|
end
|
|
634
692
|
assert_raises(HexaPDF::Error) do
|
|
635
|
-
@
|
|
636
|
-
@
|
|
693
|
+
@layouter.style.valign = :bottom
|
|
694
|
+
@layouter.draw(@canvas, 0, 0)
|
|
637
695
|
end
|
|
638
696
|
end
|
|
697
|
+
|
|
698
|
+
it "makes sure that text fragments don't pollute the graphics state for inline boxes" do
|
|
699
|
+
frag = HexaPDF::Layout::TextFragment.create("Demo", font: @font)
|
|
700
|
+
inline_box = HexaPDF::Layout::InlineBox.create(width: 10, height: 10) {|c, _| c.text("A")}
|
|
701
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: [frag, inline_box], width: 200)
|
|
702
|
+
assert_raises(HexaPDF::Error) { layouter.draw(@canvas, 0, 0) }
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
it "doesn't do unnecessary work for placeholder boxes" do
|
|
706
|
+
box1 = HexaPDF::Layout::InlineBox.create(width: 10, height: 20)
|
|
707
|
+
box2 = HexaPDF::Layout::InlineBox.create(width: 30, height: 40) { @canvas.line_width(2) }
|
|
708
|
+
layouter = HexaPDF::Layout::TextLayouter.new(items: [box1, box2], width: 200)
|
|
709
|
+
layouter.draw(@canvas, 0, 0)
|
|
710
|
+
assert_operators(@canvas.contents, [[:save_graphics_state],
|
|
711
|
+
[:restore_graphics_state],
|
|
712
|
+
[:save_graphics_state],
|
|
713
|
+
[:concatenate_matrix, [1, 0, 0, 1, 10, -40]],
|
|
714
|
+
[:set_line_width, [2]],
|
|
715
|
+
[:restore_graphics_state],
|
|
716
|
+
[:save_graphics_state],
|
|
717
|
+
[:restore_graphics_state]])
|
|
718
|
+
end
|
|
639
719
|
end
|
|
640
720
|
end
|