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.
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