hexapdf 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -1
  3. data/CONTRIBUTERS +1 -1
  4. data/LICENSE +3 -0
  5. data/README.md +2 -1
  6. data/Rakefile +3 -1
  7. data/VERSION +1 -1
  8. data/examples/{hello_world.rb → 001-hello_world.rb} +0 -0
  9. data/examples/{graphics.rb → 002-graphics.rb} +1 -1
  10. data/examples/{arc.rb → 003-arcs.rb} +2 -2
  11. data/examples/{optimizing.rb → 004-optimizing.rb} +0 -0
  12. data/examples/{merging.rb → 005-merging.rb} +0 -0
  13. data/examples/{standard_pdf_fonts.rb → 006-standard_pdf_fonts.rb} +0 -0
  14. data/examples/{truetype.rb → 007-truetype.rb} +0 -0
  15. data/examples/{show_char_bboxes.rb → 008-show_char_bboxes.rb} +0 -0
  16. data/examples/{text_layouter_alignment.rb → 009-text_layouter_alignment.rb} +3 -3
  17. data/examples/{text_layouter_inline_boxes.rb → 010-text_layouter_inline_boxes.rb} +7 -9
  18. data/examples/{text_layouter_line_wrapping.rb → 011-text_layouter_line_wrapping.rb} +6 -5
  19. data/examples/{text_layouter_styling.rb → 012-text_layouter_styling.rb} +6 -8
  20. data/examples/013-text_layouter_shapes.rb +176 -0
  21. data/examples/014-text_in_polygon.rb +60 -0
  22. data/examples/{boxes.rb → 015-boxes.rb} +29 -21
  23. data/examples/016-frame_automatic_box_placement.rb +90 -0
  24. data/examples/017-frame_text_flow.rb +60 -0
  25. data/lib/hexapdf/cli/command.rb +4 -3
  26. data/lib/hexapdf/cli/files.rb +1 -1
  27. data/lib/hexapdf/cli/inspect.rb +0 -1
  28. data/lib/hexapdf/cli/merge.rb +1 -1
  29. data/lib/hexapdf/cli/modify.rb +1 -1
  30. data/lib/hexapdf/configuration.rb +2 -0
  31. data/lib/hexapdf/content/canvas.rb +3 -3
  32. data/lib/hexapdf/content/graphic_object.rb +1 -0
  33. data/lib/hexapdf/content/graphic_object/geom2d.rb +132 -0
  34. data/lib/hexapdf/dictionary.rb +7 -1
  35. data/lib/hexapdf/dictionary_fields.rb +35 -83
  36. data/lib/hexapdf/document.rb +9 -5
  37. data/lib/hexapdf/document/fonts.rb +1 -1
  38. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  39. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  40. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  41. data/lib/hexapdf/font/cmap/writer.rb +2 -2
  42. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  43. data/lib/hexapdf/font/true_type/table.rb +1 -1
  44. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  45. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +3 -3
  46. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  47. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  48. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  49. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  50. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  51. data/lib/hexapdf/image_loader/png.rb +2 -2
  52. data/lib/hexapdf/layout.rb +3 -0
  53. data/lib/hexapdf/layout/box.rb +64 -46
  54. data/lib/hexapdf/layout/frame.rb +348 -0
  55. data/lib/hexapdf/layout/inline_box.rb +2 -2
  56. data/lib/hexapdf/layout/line.rb +3 -3
  57. data/lib/hexapdf/layout/style.rb +81 -14
  58. data/lib/hexapdf/layout/text_box.rb +84 -0
  59. data/lib/hexapdf/layout/text_fragment.rb +8 -8
  60. data/lib/hexapdf/layout/text_layouter.rb +278 -169
  61. data/lib/hexapdf/layout/width_from_polygon.rb +246 -0
  62. data/lib/hexapdf/rectangle.rb +9 -9
  63. data/lib/hexapdf/stream.rb +2 -2
  64. data/lib/hexapdf/type.rb +1 -0
  65. data/lib/hexapdf/type/action.rb +1 -1
  66. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  67. data/lib/hexapdf/type/catalog.rb +1 -1
  68. data/lib/hexapdf/type/cid_font.rb +2 -1
  69. data/lib/hexapdf/type/font.rb +0 -1
  70. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  71. data/lib/hexapdf/type/font_simple.rb +3 -3
  72. data/lib/hexapdf/type/font_true_type.rb +8 -0
  73. data/lib/hexapdf/type/font_type0.rb +2 -1
  74. data/lib/hexapdf/type/font_type1.rb +7 -1
  75. data/lib/hexapdf/type/font_type3.rb +61 -0
  76. data/lib/hexapdf/type/graphics_state_parameter.rb +8 -8
  77. data/lib/hexapdf/type/image.rb +10 -0
  78. data/lib/hexapdf/type/page.rb +83 -10
  79. data/lib/hexapdf/version.rb +1 -1
  80. data/test/hexapdf/common_tokenizer_tests.rb +2 -2
  81. data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
  82. data/test/hexapdf/encryption/test_standard_security_handler.rb +1 -1
  83. data/test/hexapdf/font/test_true_type_wrapper.rb +1 -1
  84. data/test/hexapdf/font/test_type1_wrapper.rb +1 -1
  85. data/test/hexapdf/font/true_type/table/test_cmap.rb +1 -1
  86. data/test/hexapdf/font/true_type/table/test_directory.rb +1 -1
  87. data/test/hexapdf/font/true_type/table/test_head.rb +7 -3
  88. data/test/hexapdf/layout/test_box.rb +57 -15
  89. data/test/hexapdf/layout/test_frame.rb +313 -0
  90. data/test/hexapdf/layout/test_inline_box.rb +1 -1
  91. data/test/hexapdf/layout/test_style.rb +74 -0
  92. data/test/hexapdf/layout/test_text_box.rb +77 -0
  93. data/test/hexapdf/layout/test_text_layouter.rb +220 -239
  94. data/test/hexapdf/layout/test_width_from_polygon.rb +108 -0
  95. data/test/hexapdf/test_dictionary_fields.rb +22 -26
  96. data/test/hexapdf/test_document.rb +3 -3
  97. data/test/hexapdf/test_reference.rb +1 -0
  98. data/test/hexapdf/test_writer.rb +2 -2
  99. data/test/hexapdf/type/test_font_true_type.rb +25 -0
  100. data/test/hexapdf/type/test_font_type1.rb +6 -0
  101. data/test/hexapdf/type/test_font_type3.rb +26 -0
  102. data/test/hexapdf/type/test_image.rb +10 -0
  103. data/test/hexapdf/type/test_page.rb +114 -0
  104. data/test/test_helper.rb +1 -1
  105. metadata +65 -17
  106. 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, style: {margin: [15, 10]})
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