hexapdf 0.7.0 → 0.8.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 +39 -1
- data/CONTRIBUTERS +1 -1
- data/LICENSE +3 -0
- data/README.md +2 -1
- data/Rakefile +3 -1
- data/VERSION +1 -1
- data/examples/{hello_world.rb → 001-hello_world.rb} +0 -0
- data/examples/{graphics.rb → 002-graphics.rb} +1 -1
- data/examples/{arc.rb → 003-arcs.rb} +2 -2
- data/examples/{optimizing.rb → 004-optimizing.rb} +0 -0
- data/examples/{merging.rb → 005-merging.rb} +0 -0
- data/examples/{standard_pdf_fonts.rb → 006-standard_pdf_fonts.rb} +0 -0
- data/examples/{truetype.rb → 007-truetype.rb} +0 -0
- data/examples/{show_char_bboxes.rb → 008-show_char_bboxes.rb} +0 -0
- data/examples/{text_layouter_alignment.rb → 009-text_layouter_alignment.rb} +3 -3
- data/examples/{text_layouter_inline_boxes.rb → 010-text_layouter_inline_boxes.rb} +7 -9
- data/examples/{text_layouter_line_wrapping.rb → 011-text_layouter_line_wrapping.rb} +6 -5
- data/examples/{text_layouter_styling.rb → 012-text_layouter_styling.rb} +6 -8
- data/examples/013-text_layouter_shapes.rb +176 -0
- data/examples/014-text_in_polygon.rb +60 -0
- data/examples/{boxes.rb → 015-boxes.rb} +29 -21
- data/examples/016-frame_automatic_box_placement.rb +90 -0
- data/examples/017-frame_text_flow.rb +60 -0
- data/lib/hexapdf/cli/command.rb +4 -3
- data/lib/hexapdf/cli/files.rb +1 -1
- data/lib/hexapdf/cli/inspect.rb +0 -1
- data/lib/hexapdf/cli/merge.rb +1 -1
- data/lib/hexapdf/cli/modify.rb +1 -1
- data/lib/hexapdf/configuration.rb +2 -0
- data/lib/hexapdf/content/canvas.rb +3 -3
- data/lib/hexapdf/content/graphic_object.rb +1 -0
- data/lib/hexapdf/content/graphic_object/geom2d.rb +132 -0
- data/lib/hexapdf/dictionary.rb +7 -1
- data/lib/hexapdf/dictionary_fields.rb +35 -83
- data/lib/hexapdf/document.rb +9 -5
- data/lib/hexapdf/document/fonts.rb +1 -1
- data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
- data/lib/hexapdf/font/cmap/writer.rb +2 -2
- data/lib/hexapdf/font/true_type/builder.rb +1 -1
- data/lib/hexapdf/font/true_type/table.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +3 -3
- data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
- data/lib/hexapdf/font/true_type/table/post.rb +1 -1
- data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
- data/lib/hexapdf/image_loader/jpeg.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +2 -2
- data/lib/hexapdf/layout.rb +3 -0
- data/lib/hexapdf/layout/box.rb +64 -46
- data/lib/hexapdf/layout/frame.rb +348 -0
- data/lib/hexapdf/layout/inline_box.rb +2 -2
- data/lib/hexapdf/layout/line.rb +3 -3
- data/lib/hexapdf/layout/style.rb +81 -14
- data/lib/hexapdf/layout/text_box.rb +84 -0
- data/lib/hexapdf/layout/text_fragment.rb +8 -8
- data/lib/hexapdf/layout/text_layouter.rb +278 -169
- data/lib/hexapdf/layout/width_from_polygon.rb +246 -0
- data/lib/hexapdf/rectangle.rb +9 -9
- data/lib/hexapdf/stream.rb +2 -2
- data/lib/hexapdf/type.rb +1 -0
- data/lib/hexapdf/type/action.rb +1 -1
- data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
- data/lib/hexapdf/type/catalog.rb +1 -1
- data/lib/hexapdf/type/cid_font.rb +2 -1
- data/lib/hexapdf/type/font.rb +0 -1
- data/lib/hexapdf/type/font_descriptor.rb +1 -1
- data/lib/hexapdf/type/font_simple.rb +3 -3
- data/lib/hexapdf/type/font_true_type.rb +8 -0
- data/lib/hexapdf/type/font_type0.rb +2 -1
- data/lib/hexapdf/type/font_type1.rb +7 -1
- data/lib/hexapdf/type/font_type3.rb +61 -0
- data/lib/hexapdf/type/graphics_state_parameter.rb +8 -8
- data/lib/hexapdf/type/image.rb +10 -0
- data/lib/hexapdf/type/page.rb +83 -10
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +2 -2
- data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +1 -1
- data/test/hexapdf/font/test_true_type_wrapper.rb +1 -1
- data/test/hexapdf/font/test_type1_wrapper.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_cmap.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_directory.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_head.rb +7 -3
- data/test/hexapdf/layout/test_box.rb +57 -15
- data/test/hexapdf/layout/test_frame.rb +313 -0
- data/test/hexapdf/layout/test_inline_box.rb +1 -1
- data/test/hexapdf/layout/test_style.rb +74 -0
- data/test/hexapdf/layout/test_text_box.rb +77 -0
- data/test/hexapdf/layout/test_text_layouter.rb +220 -239
- data/test/hexapdf/layout/test_width_from_polygon.rb +108 -0
- data/test/hexapdf/test_dictionary_fields.rb +22 -26
- data/test/hexapdf/test_document.rb +3 -3
- data/test/hexapdf/test_reference.rb +1 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/test_font_true_type.rb +25 -0
- data/test/hexapdf/type/test_font_type1.rb +6 -0
- data/test/hexapdf/type/test_font_type3.rb +26 -0
- data/test/hexapdf/type/test_image.rb +10 -0
- data/test/hexapdf/type/test_page.rb +114 -0
- data/test/test_helper.rb +1 -1
- metadata +65 -17
- data/examples/text_layouter_shapes.rb +0 -170
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'hexapdf/layout/frame'
|
|
5
|
+
require 'hexapdf/layout/box'
|
|
6
|
+
|
|
7
|
+
describe HexaPDF::Layout::Frame do
|
|
8
|
+
before do
|
|
9
|
+
@frame = HexaPDF::Layout::Frame.new(5, 10, 100, 150)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "allows access to the bounding box attributes" do
|
|
13
|
+
assert_equal(5, @frame.left)
|
|
14
|
+
assert_equal(10, @frame.bottom)
|
|
15
|
+
assert_equal(100, @frame.width)
|
|
16
|
+
assert_equal(150, @frame.height)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "allows access to the current region attributes" do
|
|
20
|
+
assert_equal(5, @frame.x)
|
|
21
|
+
assert_equal(160, @frame.y)
|
|
22
|
+
assert_equal(100, @frame.available_width)
|
|
23
|
+
assert_equal(150, @frame.available_height)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe "contour_line" do
|
|
27
|
+
it "has a contour line equal to the bounding box by default" do
|
|
28
|
+
assert_equal([[5, 10], [105, 10], [105, 160], [5, 160]], @frame.contour_line.polygons[0].to_a)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "can have a custom contour line polygon" do
|
|
32
|
+
contour_line = Geom2D::Polygon([0, 0], [10, 10], [10, 0])
|
|
33
|
+
frame = HexaPDF::Layout::Frame.new(0, 0, 10, 10, contour_line: contour_line)
|
|
34
|
+
assert_same(contour_line, frame.contour_line)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "returns an appropriate width specification object" do
|
|
39
|
+
ws = @frame.width_specification(10)
|
|
40
|
+
assert_kind_of(HexaPDF::Layout::WidthFromPolygon, ws)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe "draw" do
|
|
44
|
+
before do
|
|
45
|
+
@frame = HexaPDF::Layout::Frame.new(10, 10, 100, 100)
|
|
46
|
+
@canvas = Minitest::Mock.new
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Creates a box with the given option, storing it in @box, and draws it inside @frame. It is
|
|
50
|
+
# checked whether the box coordinates are pos and whether the frame has the shape given by
|
|
51
|
+
# points.
|
|
52
|
+
def check_box(box_opts, pos, points)
|
|
53
|
+
@box = HexaPDF::Layout::Box.create(box_opts) {}
|
|
54
|
+
@canvas.expect(:translate, nil, pos)
|
|
55
|
+
assert(@frame.draw(@canvas, @box))
|
|
56
|
+
assert_equal(points, @frame.shape.polygons.map(&:to_a))
|
|
57
|
+
@canvas.verify
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Removes a 10pt area from the :left, :right or :top.
|
|
61
|
+
def remove_area(*areas)
|
|
62
|
+
areas.each do |area|
|
|
63
|
+
@frame.remove_area(
|
|
64
|
+
case area
|
|
65
|
+
when :left then Geom2D::Polygon([10, 10], [10, 110], [20, 110], [20, 10])
|
|
66
|
+
when :right then Geom2D::Polygon([100, 10], [100, 110], [110, 110], [110, 10])
|
|
67
|
+
when :top then Geom2D::Polygon([10, 110], [110, 110], [110, 100], [10, 100])
|
|
68
|
+
end
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe "absolute position" do
|
|
74
|
+
it "draws the box at the given absolute position" do
|
|
75
|
+
check_box(
|
|
76
|
+
{width: 50, height: 50, position: :absolute, position_hint: [10, 10]},
|
|
77
|
+
[20, 20],
|
|
78
|
+
[[[10, 10], [110, 10], [110, 110], [10, 110]],
|
|
79
|
+
[[20, 20], [70, 20], [70, 70], [20, 70]]]
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "always removes the whole margin box from the frame" do
|
|
84
|
+
check_box(
|
|
85
|
+
{width: 50, height: 50, position: :absolute, position_hint: [10, 10],
|
|
86
|
+
margin: [10, 20, 30, 40]},
|
|
87
|
+
[20, 20],
|
|
88
|
+
[[[10, 80], [90, 80], [90, 10], [110, 10], [110, 110], [10, 110]]]
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe "default position" do
|
|
94
|
+
it "draws the box on the left side" do
|
|
95
|
+
check_box({width: 50, height: 50},
|
|
96
|
+
[10, 60],
|
|
97
|
+
[[[10, 10], [110, 10], [110, 60], [10, 60]]])
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "draws the box on the right side" do
|
|
101
|
+
check_box({width: 50, height: 50, position_hint: :right},
|
|
102
|
+
[60, 60],
|
|
103
|
+
[[[10, 10], [110, 10], [110, 60], [10, 60]]])
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "draws the box in the center" do
|
|
107
|
+
check_box({width: 50, height: 50, position_hint: :center},
|
|
108
|
+
[35, 60],
|
|
109
|
+
[[[10, 10], [110, 10], [110, 60], [10, 60]]])
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
describe "with margin" do
|
|
113
|
+
[:left, :center, :right].each do |hint|
|
|
114
|
+
it "ignores all margins if the box fills the whole frame, with position hint #{hint}" do
|
|
115
|
+
check_box({margin: 10, position_hint: hint},
|
|
116
|
+
[10, 10], [])
|
|
117
|
+
assert_equal(100, @box.width)
|
|
118
|
+
assert_equal(100, @box.height)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "ignores the left/top/right margin if the available bounds coincide with the " \
|
|
122
|
+
"frame's, with position hint #{hint}" do
|
|
123
|
+
check_box({height: 50, margin: 10, position_hint: hint},
|
|
124
|
+
[10, 60],
|
|
125
|
+
[[[10, 10], [110, 10], [110, 50], [10, 50]]])
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "doesn't ignore top margin if the available bounds' top doesn't coincide with the " \
|
|
129
|
+
"frame's top, with position hint #{hint}" do
|
|
130
|
+
remove_area(:top)
|
|
131
|
+
check_box({height: 50, margin: 10, position_hint: hint},
|
|
132
|
+
[10, 40],
|
|
133
|
+
[[[10, 10], [110, 10], [110, 30], [10, 30]]])
|
|
134
|
+
assert_equal(100, @box.width)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "doesn't ignore left margin if the available bounds' left doesn't coincide with the " \
|
|
138
|
+
"frame's left, with position hint #{hint}" do
|
|
139
|
+
remove_area(:left)
|
|
140
|
+
check_box({height: 50, margin: 10, position_hint: hint},
|
|
141
|
+
[30, 60],
|
|
142
|
+
[[[20, 10], [110, 10], [110, 50], [20, 50]]])
|
|
143
|
+
assert_equal(80, @box.width)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it "doesn't ignore right margin if the available bounds' right doesn't coincide with " \
|
|
147
|
+
"the frame's right, with position hint #{hint}" do
|
|
148
|
+
remove_area(:right)
|
|
149
|
+
check_box({height: 50, margin: 10, position_hint: hint},
|
|
150
|
+
[10, 60],
|
|
151
|
+
[[[10, 10], [100, 10], [100, 50], [10, 50]]])
|
|
152
|
+
assert_equal(80, @box.width)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it "perfectly centers a box if possible, margins ignored" do
|
|
157
|
+
check_box({width: 50, height: 10, margin: [10, 10, 10, 20], position_hint: :center},
|
|
158
|
+
[35, 100],
|
|
159
|
+
[[[10, 10], [110, 10], [110, 90], [10, 90]]])
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it "perfectly centers a box if possible, margins not ignored" do
|
|
163
|
+
remove_area(:left, :right)
|
|
164
|
+
check_box({width: 40, height: 10, margin: [10, 10, 10, 20], position_hint: :center},
|
|
165
|
+
[40, 100],
|
|
166
|
+
[[[20, 10], [100, 10], [100, 90], [20, 90]]])
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it "centers a box as good as possible when margins aren't equal" do
|
|
170
|
+
remove_area(:left, :right)
|
|
171
|
+
check_box({width: 20, height: 10, margin: [10, 10, 10, 40], position_hint: :center},
|
|
172
|
+
[65, 100],
|
|
173
|
+
[[[20, 10], [100, 10], [100, 90], [20, 90]]])
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
describe "floating boxes" do
|
|
179
|
+
it "draws the box on the left side" do
|
|
180
|
+
check_box({width: 50, height: 50, position: :float},
|
|
181
|
+
[10, 60],
|
|
182
|
+
[[[10, 10], [110, 10], [110, 110], [60, 110], [60, 60], [10, 60]]])
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it "draws the box on the right side" do
|
|
186
|
+
check_box({width: 50, height: 50, position: :float, position_hint: :right},
|
|
187
|
+
[60, 60],
|
|
188
|
+
[[[10, 10], [110, 10], [110, 60], [60, 60], [60, 110], [10, 110]]])
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
describe "with margin" do
|
|
192
|
+
[:left, :right].each do |hint|
|
|
193
|
+
it "ignores all margins if the box fills the whole frame, with position hint #{hint}" do
|
|
194
|
+
check_box({margin: 10, position: :float, position_hint: hint},
|
|
195
|
+
[10, 10], [])
|
|
196
|
+
assert_equal(100, @box.width)
|
|
197
|
+
assert_equal(100, @box.height)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
it "ignores the left, but not the right margin if aligned left to the frame border" do
|
|
202
|
+
check_box({width: 50, height: 50, margin: 10, position: :float, position_hint: :left},
|
|
203
|
+
[10, 60],
|
|
204
|
+
[[[10, 10], [110, 10], [110, 110], [70, 110], [70, 50], [10, 50]]])
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
it "uses the left and the right margin if aligned left and not to the frame border" do
|
|
208
|
+
remove_area(:left)
|
|
209
|
+
check_box({width: 50, height: 50, margin: 10, position: :float, position_hint: :left},
|
|
210
|
+
[30, 60],
|
|
211
|
+
[[[20, 10], [110, 10], [110, 110], [90, 110], [90, 50], [20, 50]]])
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it "ignores the right, but not the left margin if aligned right to the frame border" do
|
|
215
|
+
check_box({width: 50, height: 50, margin: 10, position: :float, position_hint: :right},
|
|
216
|
+
[60, 60],
|
|
217
|
+
[[[10, 10], [110, 10], [110, 50], [50, 50], [50, 110], [10, 110]]])
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it "uses the left and the right margin if aligned right and not to the frame border" do
|
|
221
|
+
remove_area(:right)
|
|
222
|
+
check_box({width: 50, height: 50, margin: 10, position: :float, position_hint: :right},
|
|
223
|
+
[40, 60],
|
|
224
|
+
[[[10, 10], [100, 10], [100, 50], [30, 50], [30, 110], [10, 110]]])
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
describe "flowing boxes" do
|
|
230
|
+
it "flows inside the frame's outline" do
|
|
231
|
+
check_box({width: 10, height: 20, position: :flow},
|
|
232
|
+
[0, 90],
|
|
233
|
+
[[[10, 10], [110, 10], [110, 90], [10, 90]]])
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
it "doesn't draw the box if it doesn't fit into the available space" do
|
|
238
|
+
box = HexaPDF::Layout::Box.create(width: 150, height: 50)
|
|
239
|
+
refute(@frame.draw(@canvas, box))
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
describe "find_next_region" do
|
|
244
|
+
# Checks all availability regions of the frame
|
|
245
|
+
def check_regions(frame, regions)
|
|
246
|
+
regions.each_with_index do |region, index|
|
|
247
|
+
assert_equal(region[0], frame.x, "region #{index} invalid x")
|
|
248
|
+
assert_equal(region[1], frame.y, "region #{index} invalid y")
|
|
249
|
+
assert_equal(region[2], frame.available_width, "region #{index} invalid available width")
|
|
250
|
+
assert_equal(region[3], frame.available_height, "region #{index} invalid available height")
|
|
251
|
+
frame.find_next_region
|
|
252
|
+
end
|
|
253
|
+
assert_equal(0, frame.x)
|
|
254
|
+
assert_equal(0, frame.y)
|
|
255
|
+
assert_equal(0, frame.available_width)
|
|
256
|
+
assert_equal(0, frame.available_height)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# o------+
|
|
260
|
+
# | |
|
|
261
|
+
# | |
|
|
262
|
+
# | |
|
|
263
|
+
# +------+
|
|
264
|
+
it "works for a rectangular region" do
|
|
265
|
+
frame = HexaPDF::Layout::Frame.new(0, 0, 100, 300)
|
|
266
|
+
check_regions(frame, [[0, 300, 100, 300]])
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# o--------+
|
|
270
|
+
# | |
|
|
271
|
+
# | +--+ |
|
|
272
|
+
# | | | |
|
|
273
|
+
# | +--+ |
|
|
274
|
+
# | |
|
|
275
|
+
# +--------+
|
|
276
|
+
it "works for a region with a hole" do
|
|
277
|
+
frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100)
|
|
278
|
+
frame.remove_area(Geom2D::Polygon([20, 20], [80, 20], [80, 80], [20, 80]))
|
|
279
|
+
check_regions(frame, [[0, 100, 100, 20], [0, 100, 20, 100],
|
|
280
|
+
[0, 80, 20, 80], [0, 20, 100, 20]])
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# o--+ +--+
|
|
284
|
+
# | | | |
|
|
285
|
+
# | +--+ |
|
|
286
|
+
# | |
|
|
287
|
+
# +--------+
|
|
288
|
+
it "works for a u-shaped frame" do
|
|
289
|
+
frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100)
|
|
290
|
+
frame.remove_area(Geom2D::Polygon([30, 100], [70, 100], [70, 60], [30, 60]))
|
|
291
|
+
check_regions(frame, [[0, 100, 30, 100], [0, 60, 100, 60]])
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# o---+ +--+
|
|
295
|
+
# | | +--+ |
|
|
296
|
+
# | +--+ |
|
|
297
|
+
# | |
|
|
298
|
+
# +----+ |
|
|
299
|
+
# +----+ |
|
|
300
|
+
# | |
|
|
301
|
+
# +------------+
|
|
302
|
+
it "works for a complicated frame" do
|
|
303
|
+
frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100)
|
|
304
|
+
top_cut = Geom2D::Polygon([20, 100], [20, 80], [40, 80], [40, 90], [60, 90], [60, 100])
|
|
305
|
+
left_cut = Geom2D::Polygon([0, 20], [30, 20], [30, 40], [0, 40])
|
|
306
|
+
frame.remove_area(Geom2D::PolygonSet(top_cut, left_cut))
|
|
307
|
+
|
|
308
|
+
check_regions(frame, [[0, 100, 20, 60], [0, 90, 20, 50], [0, 80, 100, 40],
|
|
309
|
+
[30, 40, 70, 40], [0, 20, 100, 20]])
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
end
|
|
@@ -5,7 +5,7 @@ require 'hexapdf/layout/inline_box'
|
|
|
5
5
|
|
|
6
6
|
describe HexaPDF::Layout::InlineBox do
|
|
7
7
|
before do
|
|
8
|
-
@box = HexaPDF::Layout::InlineBox.create(width: 10, height: 15,
|
|
8
|
+
@box = HexaPDF::Layout::InlineBox.create(width: 10, height: 15, margin: [15, 10])
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
it "needs a box to wrap and an optional alignment on initialization" do
|
|
@@ -157,6 +157,12 @@ describe HexaPDF::Layout::Style::Border do
|
|
|
157
157
|
@canvas = HexaPDF::Document.new.pages.add.canvas
|
|
158
158
|
end
|
|
159
159
|
|
|
160
|
+
it "draws nothing if no border is defined" do
|
|
161
|
+
border = create_border
|
|
162
|
+
border.draw(@canvas, 0, 0, 100, 100)
|
|
163
|
+
assert_operators(@canvas.contents, [])
|
|
164
|
+
end
|
|
165
|
+
|
|
160
166
|
describe "simple - same width, color and style on all sides" do
|
|
161
167
|
it "works with style solid" do
|
|
162
168
|
border = create_border(width: 10, color: 0.5, style: :solid)
|
|
@@ -164,6 +170,8 @@ describe HexaPDF::Layout::Style::Border do
|
|
|
164
170
|
assert_operators(@canvas.contents, [[:save_graphics_state],
|
|
165
171
|
[:set_device_gray_stroking_color, [0.5]],
|
|
166
172
|
[:set_line_width, [10]],
|
|
173
|
+
[:append_rectangle, [0, 0, 100, 100]],
|
|
174
|
+
[:clip_path_non_zero], [:end_path],
|
|
167
175
|
[:append_rectangle, [5, 5, 90, 90]],
|
|
168
176
|
[:stroke_path],
|
|
169
177
|
[:restore_graphics_state]])
|
|
@@ -343,6 +351,72 @@ describe HexaPDF::Layout::Style::Border do
|
|
|
343
351
|
end
|
|
344
352
|
end
|
|
345
353
|
|
|
354
|
+
describe "border width greater than edge length" do
|
|
355
|
+
it "works for solid borders" do
|
|
356
|
+
border = create_border(width: 100, style: :solid)
|
|
357
|
+
border.draw(@canvas, 0, 0, 10, 10)
|
|
358
|
+
assert_operators(@canvas.contents, [[:save_graphics_state],
|
|
359
|
+
[:set_line_width, [100]],
|
|
360
|
+
[:append_rectangle, [0, 0, 10, 10]],
|
|
361
|
+
[:clip_path_non_zero], [:end_path],
|
|
362
|
+
[:append_rectangle, [50, 50, -90, -90]],
|
|
363
|
+
[:stroke_path],
|
|
364
|
+
[:restore_graphics_state]])
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
it "works for dashed borders" do
|
|
368
|
+
border = create_border(width: 100, style: :dashed)
|
|
369
|
+
border.draw(@canvas, 0, 0, 10, 10)
|
|
370
|
+
assert_operators(@canvas.contents, [[:save_graphics_state],
|
|
371
|
+
[:set_line_width, [100]],
|
|
372
|
+
[:set_line_cap_style, [2]],
|
|
373
|
+
[:append_rectangle, [0, 0, 10, 10]],
|
|
374
|
+
[:clip_path_non_zero], [:end_path],
|
|
375
|
+
[:set_line_dash_pattern, [[100, 0], 50]],
|
|
376
|
+
[:move_to, [0, -40]], [:line_to, [10, -40]],
|
|
377
|
+
[:move_to, [10, 50]], [:line_to, [0, 50]],
|
|
378
|
+
[:stroke_path],
|
|
379
|
+
[:move_to, [-40, 10]], [:line_to, [-40, 0]],
|
|
380
|
+
[:move_to, [50, 0]], [:line_to, [50, 10]],
|
|
381
|
+
[:stroke_path],
|
|
382
|
+
[:restore_graphics_state]])
|
|
383
|
+
end
|
|
384
|
+
it "works for dashed-round borders" do
|
|
385
|
+
border = create_border(width: 100, style: :dashed_round)
|
|
386
|
+
border.draw(@canvas, 0, 0, 10, 10)
|
|
387
|
+
assert_operators(@canvas.contents, [[:save_graphics_state],
|
|
388
|
+
[:set_line_width, [100]],
|
|
389
|
+
[:set_line_cap_style, [1]],
|
|
390
|
+
[:append_rectangle, [0, 0, 10, 10]],
|
|
391
|
+
[:clip_path_non_zero], [:end_path],
|
|
392
|
+
[:set_line_dash_pattern, [[100, 0], 50]],
|
|
393
|
+
[:move_to, [0, -40]], [:line_to, [10, -40]],
|
|
394
|
+
[:move_to, [10, 50]], [:line_to, [0, 50]],
|
|
395
|
+
[:stroke_path],
|
|
396
|
+
[:move_to, [-40, 10]], [:line_to, [-40, 0]],
|
|
397
|
+
[:move_to, [50, 0]], [:line_to, [50, 10]],
|
|
398
|
+
[:stroke_path],
|
|
399
|
+
[:restore_graphics_state]])
|
|
400
|
+
end
|
|
401
|
+
it "works for dotted borders" do
|
|
402
|
+
border = create_border(width: 100, style: :dotted)
|
|
403
|
+
border.draw(@canvas, 0, 0, 10, 10)
|
|
404
|
+
assert_operators(@canvas.contents, [[:save_graphics_state],
|
|
405
|
+
[:set_line_width, [100]],
|
|
406
|
+
[:set_line_cap_style, [1]],
|
|
407
|
+
[:append_rectangle, [0, 0, 10, 10]],
|
|
408
|
+
[:clip_path_non_zero], [:end_path],
|
|
409
|
+
[:set_line_dash_pattern, [[0, 1], 0]],
|
|
410
|
+
[:move_to, [0, -40]], [:line_to, [10, -40]],
|
|
411
|
+
[:move_to, [10, 50]], [:line_to, [0, 50]],
|
|
412
|
+
[:stroke_path],
|
|
413
|
+
[:move_to, [-40, 10]], [:line_to, [-40, 0]],
|
|
414
|
+
[:move_to, [50, 0]], [:line_to, [50, 10]],
|
|
415
|
+
[:stroke_path],
|
|
416
|
+
[:restore_graphics_state]])
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
346
420
|
it "raises an error if an invalid style is provided" do
|
|
347
421
|
assert_raises(ArgumentError) do
|
|
348
422
|
create_border(width: 1, color: 0, style: :unknown).draw(@canvas, 0, 0, 10, 10)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require_relative '../content/common'
|
|
5
|
+
require 'hexapdf/document'
|
|
6
|
+
require 'hexapdf/layout/text_box'
|
|
7
|
+
|
|
8
|
+
describe HexaPDF::Layout::TextBox do
|
|
9
|
+
before do
|
|
10
|
+
@frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create_box(items, **kwargs)
|
|
14
|
+
HexaPDF::Layout::TextBox.new(items, kwargs)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe "initialize" do
|
|
18
|
+
it "takes the inline items to be layed out in the box" do
|
|
19
|
+
box = create_box([], width: 100)
|
|
20
|
+
assert_equal(100, box.width)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "fit" do
|
|
25
|
+
before do
|
|
26
|
+
@inline_box = HexaPDF::Layout::InlineBox.create(width: 10, height: 10) {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "fits into a rectangular area" do
|
|
30
|
+
box = create_box([@inline_box] * 5)
|
|
31
|
+
assert(box.fit(100, 100, @frame))
|
|
32
|
+
assert_equal(50, box.width)
|
|
33
|
+
assert_equal(10, box.height)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "fits into the frame's outline" do
|
|
37
|
+
inline_box = HexaPDF::Layout::InlineBox.create(width: 10, height: 10) {}
|
|
38
|
+
box = create_box([inline_box] * 20, style: {position: :flow})
|
|
39
|
+
assert(box.fit(100, 100, @frame))
|
|
40
|
+
assert_equal(100, box.width)
|
|
41
|
+
assert_equal(20, box.height)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe "draw" do
|
|
46
|
+
it "draws the layed out inline items onto the canvas" do
|
|
47
|
+
inline_box = HexaPDF::Layout::InlineBox.create(width: 10, height: 10,
|
|
48
|
+
border: {width: 1})
|
|
49
|
+
box = create_box([inline_box], width: 100, height: 10)
|
|
50
|
+
box.fit(100, 100, nil)
|
|
51
|
+
|
|
52
|
+
@canvas = HexaPDF::Document.new.pages.add.canvas
|
|
53
|
+
box.draw(@canvas, 0, 0)
|
|
54
|
+
assert_operators(@canvas.contents, [[:save_graphics_state],
|
|
55
|
+
[:concatenate_matrix, [1, 0, 0, 1, 0, 0]],
|
|
56
|
+
[:save_graphics_state],
|
|
57
|
+
[:restore_graphics_state],
|
|
58
|
+
[:save_graphics_state],
|
|
59
|
+
[:append_rectangle, [0, 0, 10, 10]],
|
|
60
|
+
[:clip_path_non_zero],
|
|
61
|
+
[:end_path],
|
|
62
|
+
[:append_rectangle, [0.5, 0.5, 9, 9]],
|
|
63
|
+
[:stroke_path],
|
|
64
|
+
[:restore_graphics_state],
|
|
65
|
+
[:save_graphics_state],
|
|
66
|
+
[:restore_graphics_state],
|
|
67
|
+
[:restore_graphics_state]])
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "draws nothing onto the canvas if the box is empty" do
|
|
71
|
+
@canvas = HexaPDF::Document.new.pages.add.canvas
|
|
72
|
+
box = create_box([])
|
|
73
|
+
box.draw(@canvas, 5, 5)
|
|
74
|
+
assert_operators(@canvas.contents, [])
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|