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.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -2
  3. data/CONTRIBUTERS +1 -1
  4. data/Rakefile +1 -1
  5. data/VERSION +1 -1
  6. data/examples/boxes.rb +68 -0
  7. data/examples/graphics.rb +12 -12
  8. data/examples/{text_box_alignment.rb → text_layouter_alignment.rb} +14 -14
  9. data/examples/text_layouter_inline_boxes.rb +66 -0
  10. data/examples/{text_box_line_wrapping.rb → text_layouter_line_wrapping.rb} +9 -10
  11. data/examples/{text_box_shapes.rb → text_layouter_shapes.rb} +58 -54
  12. data/examples/text_layouter_styling.rb +125 -0
  13. data/examples/truetype.rb +5 -7
  14. data/lib/hexapdf/cli/command.rb +1 -0
  15. data/lib/hexapdf/configuration.rb +170 -106
  16. data/lib/hexapdf/content/canvas.rb +41 -36
  17. data/lib/hexapdf/content/graphics_state.rb +15 -0
  18. data/lib/hexapdf/content/operator.rb +1 -1
  19. data/lib/hexapdf/dictionary.rb +20 -8
  20. data/lib/hexapdf/dictionary_fields.rb +8 -6
  21. data/lib/hexapdf/document.rb +25 -26
  22. data/lib/hexapdf/document/fonts.rb +4 -4
  23. data/lib/hexapdf/document/images.rb +2 -2
  24. data/lib/hexapdf/document/pages.rb +16 -16
  25. data/lib/hexapdf/encryption/security_handler.rb +41 -9
  26. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  27. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  28. data/lib/hexapdf/filter/predictor.rb +7 -1
  29. data/lib/hexapdf/font/true_type/font.rb +20 -0
  30. data/lib/hexapdf/font/type1/font.rb +23 -0
  31. data/lib/hexapdf/font_loader.rb +1 -0
  32. data/lib/hexapdf/font_loader/from_configuration.rb +2 -3
  33. data/lib/hexapdf/font_loader/from_file.rb +65 -0
  34. data/lib/hexapdf/image_loader/png.rb +2 -2
  35. data/lib/hexapdf/layout.rb +3 -2
  36. data/lib/hexapdf/layout/box.rb +146 -0
  37. data/lib/hexapdf/layout/inline_box.rb +40 -31
  38. data/lib/hexapdf/layout/{line_fragment.rb → line.rb} +12 -13
  39. data/lib/hexapdf/layout/style.rb +630 -41
  40. data/lib/hexapdf/layout/text_fragment.rb +80 -12
  41. data/lib/hexapdf/layout/{text_box.rb → text_layouter.rb} +164 -109
  42. data/lib/hexapdf/number_tree_node.rb +1 -1
  43. data/lib/hexapdf/parser.rb +4 -1
  44. data/lib/hexapdf/revisions.rb +11 -4
  45. data/lib/hexapdf/stream.rb +8 -9
  46. data/lib/hexapdf/tokenizer.rb +5 -3
  47. data/lib/hexapdf/type.rb +3 -0
  48. data/lib/hexapdf/type/action.rb +56 -0
  49. data/lib/hexapdf/type/actions.rb +52 -0
  50. data/lib/hexapdf/type/actions/go_to.rb +52 -0
  51. data/lib/hexapdf/type/actions/go_to_r.rb +54 -0
  52. data/lib/hexapdf/type/actions/launch.rb +73 -0
  53. data/lib/hexapdf/type/actions/uri.rb +65 -0
  54. data/lib/hexapdf/type/annotation.rb +85 -0
  55. data/lib/hexapdf/type/annotations.rb +51 -0
  56. data/lib/hexapdf/type/annotations/link.rb +70 -0
  57. data/lib/hexapdf/type/annotations/markup_annotation.rb +70 -0
  58. data/lib/hexapdf/type/annotations/text.rb +81 -0
  59. data/lib/hexapdf/type/catalog.rb +3 -1
  60. data/lib/hexapdf/type/embedded_file.rb +6 -11
  61. data/lib/hexapdf/type/file_specification.rb +4 -6
  62. data/lib/hexapdf/type/font.rb +3 -1
  63. data/lib/hexapdf/type/font_descriptor.rb +18 -16
  64. data/lib/hexapdf/type/form.rb +3 -1
  65. data/lib/hexapdf/type/graphics_state_parameter.rb +3 -1
  66. data/lib/hexapdf/type/image.rb +4 -2
  67. data/lib/hexapdf/type/info.rb +2 -5
  68. data/lib/hexapdf/type/names.rb +2 -5
  69. data/lib/hexapdf/type/object_stream.rb +2 -1
  70. data/lib/hexapdf/type/page.rb +14 -1
  71. data/lib/hexapdf/type/page_tree_node.rb +9 -6
  72. data/lib/hexapdf/type/resources.rb +2 -5
  73. data/lib/hexapdf/type/trailer.rb +2 -5
  74. data/lib/hexapdf/type/viewer_preferences.rb +2 -5
  75. data/lib/hexapdf/type/xref_stream.rb +3 -1
  76. data/lib/hexapdf/version.rb +1 -1
  77. data/test/hexapdf/common_tokenizer_tests.rb +3 -1
  78. data/test/hexapdf/content/test_canvas.rb +29 -3
  79. data/test/hexapdf/content/test_graphics_state.rb +11 -0
  80. data/test/hexapdf/content/test_operator.rb +3 -2
  81. data/test/hexapdf/document/test_fonts.rb +8 -8
  82. data/test/hexapdf/document/test_images.rb +4 -12
  83. data/test/hexapdf/document/test_pages.rb +7 -7
  84. data/test/hexapdf/encryption/test_security_handler.rb +1 -5
  85. data/test/hexapdf/filter/test_predictor.rb +40 -12
  86. data/test/hexapdf/font/true_type/test_font.rb +16 -0
  87. data/test/hexapdf/font/type1/test_font.rb +30 -0
  88. data/test/hexapdf/font_loader/test_from_file.rb +29 -0
  89. data/test/hexapdf/font_loader/test_standard14.rb +4 -3
  90. data/test/hexapdf/layout/test_box.rb +104 -0
  91. data/test/hexapdf/layout/test_inline_box.rb +24 -10
  92. data/test/hexapdf/layout/{test_line_fragment.rb → test_line.rb} +9 -9
  93. data/test/hexapdf/layout/test_style.rb +519 -31
  94. data/test/hexapdf/layout/test_text_fragment.rb +136 -15
  95. data/test/hexapdf/layout/{test_text_box.rb → test_text_layouter.rb} +224 -144
  96. data/test/hexapdf/layout/test_text_shaper.rb +1 -1
  97. data/test/hexapdf/test_configuration.rb +12 -6
  98. data/test/hexapdf/test_dictionary.rb +27 -2
  99. data/test/hexapdf/test_dictionary_fields.rb +10 -1
  100. data/test/hexapdf/test_document.rb +14 -13
  101. data/test/hexapdf/test_parser.rb +12 -0
  102. data/test/hexapdf/test_revisions.rb +34 -0
  103. data/test/hexapdf/test_stream.rb +1 -1
  104. data/test/hexapdf/test_type.rb +18 -0
  105. data/test/hexapdf/test_writer.rb +2 -2
  106. data/test/hexapdf/type/actions/test_launch.rb +24 -0
  107. data/test/hexapdf/type/actions/test_uri.rb +23 -0
  108. data/test/hexapdf/type/annotations/test_link.rb +19 -0
  109. data/test/hexapdf/type/annotations/test_markup_annotation.rb +22 -0
  110. data/test/hexapdf/type/annotations/test_text.rb +38 -0
  111. data/test/hexapdf/type/test_annotation.rb +38 -0
  112. data/test/hexapdf/type/test_file_specification.rb +0 -7
  113. data/test/hexapdf/type/test_info.rb +0 -5
  114. data/test/hexapdf/type/test_page.rb +14 -0
  115. data/test/hexapdf/type/test_page_tree_node.rb +4 -1
  116. data/test/hexapdf/type/test_trailer.rb +0 -4
  117. data/test/test_helper.rb +6 -3
  118. metadata +36 -15
  119. data/examples/text_box_inline_boxes.rb +0 -56
  120. data/examples/text_box_styling.rb +0 -72
  121. data/test/hexapdf/type/test_embedded_file.rb +0 -16
  122. data/test/hexapdf/type/test_names.rb +0 -9
@@ -1,8 +1,11 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  require 'test_helper'
4
+ require_relative '../content/common'
5
+ require 'hexapdf/document'
4
6
  require 'hexapdf/layout/style'
5
- require 'hexapdf/layout/text_box'
7
+ require 'hexapdf/layout/text_layouter'
8
+ require 'hexapdf/layout/box'
6
9
 
7
10
  describe HexaPDF::Layout::Style::LineSpacing do
8
11
  before do
@@ -47,7 +50,7 @@ describe HexaPDF::Layout::Style::LineSpacing do
47
50
  assert_equal(:fixed, obj.type)
48
51
  assert_equal(7, obj.value)
49
52
  assert_equal(7, obj.baseline_distance(@line1, @line2))
50
- assert_equal(7 - 1 - 4, obj.gap(@line1, @line2))
53
+ assert_equal(7 - 1 - 4, obj.gap(@line1, @line2))
51
54
  end
52
55
 
53
56
  it "allows line spacing using a leading value" do
@@ -72,6 +75,412 @@ describe HexaPDF::Layout::Style::LineSpacing do
72
75
  end
73
76
  end
74
77
 
78
+ describe HexaPDF::Layout::Style::Quad do
79
+ def create_quad(val)
80
+ HexaPDF::Layout::Style::Quad.new(val)
81
+ end
82
+
83
+ describe "initialize" do
84
+ it "works with a single value" do
85
+ quad = create_quad(5)
86
+ assert_equal(5, quad.top)
87
+ assert_equal(5, quad.right)
88
+ assert_equal(5, quad.bottom)
89
+ assert_equal(5, quad.left)
90
+ end
91
+
92
+ it "works with two values" do
93
+ quad = create_quad([5, 2])
94
+ assert_equal(5, quad.top)
95
+ assert_equal(2, quad.right)
96
+ assert_equal(5, quad.bottom)
97
+ assert_equal(2, quad.left)
98
+ end
99
+
100
+ it "works with three values" do
101
+ quad = create_quad([5, 2, 7])
102
+ assert_equal(5, quad.top)
103
+ assert_equal(2, quad.right)
104
+ assert_equal(7, quad.bottom)
105
+ assert_equal(2, quad.left)
106
+ end
107
+
108
+ it "works with four or more values" do
109
+ quad = create_quad([5, 2, 7, 1, 9])
110
+ assert_equal(5, quad.top)
111
+ assert_equal(2, quad.right)
112
+ assert_equal(7, quad.bottom)
113
+ assert_equal(1, quad.left)
114
+ end
115
+
116
+ it "works with a Quad as value" do
117
+ quad = create_quad([5, 2, 7, 1])
118
+ new_quad = create_quad(quad)
119
+ assert_equal(new_quad.top, quad.top)
120
+ assert_equal(new_quad.right, quad.right)
121
+ assert_equal(new_quad.bottom, quad.bottom)
122
+ assert_equal(new_quad.left, quad.left)
123
+ end
124
+ end
125
+
126
+ it "can be asked if it contains only a single value" do
127
+ assert(create_quad(5).simple?)
128
+ refute(create_quad([5, 2]).simple?)
129
+ end
130
+ end
131
+
132
+ describe HexaPDF::Layout::Style::Border do
133
+ def create_border(*args)
134
+ HexaPDF::Layout::Style::Border.new(*args)
135
+ end
136
+
137
+ it "has accessors for with, color and style that return Quads" do
138
+ border = create_border
139
+ assert_kind_of(HexaPDF::Layout::Style::Quad, border.width)
140
+ assert_kind_of(HexaPDF::Layout::Style::Quad, border.color)
141
+ assert_kind_of(HexaPDF::Layout::Style::Quad, border.style)
142
+ end
143
+
144
+ it "can be asked whether a border is defined" do
145
+ assert(create_border.none?)
146
+ refute(create_border(width: 5).none?)
147
+ end
148
+
149
+ describe "draw" do
150
+ before do
151
+ @canvas = HexaPDF::Document.new.pages.add.canvas
152
+ end
153
+
154
+ describe "simple - same width, color and style on all sides" do
155
+ it "works with style solid" do
156
+ border = create_border(width: 10, color: 0.5, style: :solid)
157
+ border.draw(@canvas, 0, 0, 100, 100)
158
+ assert_operators(@canvas.contents, [[:save_graphics_state],
159
+ [:set_device_gray_stroking_color, [0.5]],
160
+ [:set_line_width, [10]],
161
+ [:append_rectangle, [5, 5, 90, 90]],
162
+ [:stroke_path],
163
+ [:restore_graphics_state]])
164
+ end
165
+
166
+ it "works with style dashed" do
167
+ border = create_border(width: 10, color: 0.5, style: :dashed)
168
+ border.draw(@canvas, 0, 0, 200, 300)
169
+ ops = [[:save_graphics_state],
170
+ [:set_device_gray_stroking_color, [0.5]],
171
+ [:set_line_width, [10]],
172
+ [:set_line_cap_style, [2]],
173
+ [:append_rectangle, [0, 0, 200, 300]],
174
+ [:clip_path_non_zero], [:end_path],
175
+ [:set_line_dash_pattern, [[10, 20], 25]],
176
+ [:move_to, [0, 295]], [:line_to, [200, 295]],
177
+ [:move_to, [200, 5]], [:line_to, [0, 5]],
178
+ [:stroke_path],
179
+ [:set_line_dash_pattern, [[10, 18], 23]],
180
+ [:move_to, [195, 300]], [:line_to, [195, 0]],
181
+ [:move_to, [5, 0]], [:line_to, [5, 300]],
182
+ [:stroke_path],
183
+ [:restore_graphics_state]]
184
+ assert_operators(@canvas.contents, ops)
185
+ end
186
+
187
+ it "works with style dashed_round" do
188
+ border = create_border(width: 10, color: 0.5, style: :dashed_round)
189
+ border.draw(@canvas, 0, 0, 200, 300)
190
+ ops = [[:save_graphics_state],
191
+ [:set_device_gray_stroking_color, [0.5]],
192
+ [:set_line_width, [10]],
193
+ [:set_line_cap_style, [1]],
194
+ [:append_rectangle, [0, 0, 200, 300]],
195
+ [:clip_path_non_zero], [:end_path],
196
+ [:set_line_dash_pattern, [[10, 20], 25]],
197
+ [:move_to, [0, 295]], [:line_to, [200, 295]],
198
+ [:move_to, [200, 5]], [:line_to, [0, 5]],
199
+ [:stroke_path],
200
+ [:set_line_dash_pattern, [[10, 18], 23]],
201
+ [:move_to, [195, 300]], [:line_to, [195, 0]],
202
+ [:move_to, [5, 0]], [:line_to, [5, 300]],
203
+ [:stroke_path],
204
+ [:restore_graphics_state]]
205
+ assert_operators(@canvas.contents, ops)
206
+ end
207
+
208
+ it "works with style dotted" do
209
+ border = create_border(width: 10, color: 0.5, style: :dotted)
210
+ border.draw(@canvas, 0, 0, 100, 200)
211
+ ops = [[:save_graphics_state],
212
+ [:set_device_gray_stroking_color, [0.5]],
213
+ [:set_line_width, [10]],
214
+ [:set_line_cap_style, [1]],
215
+ [:append_rectangle, [0, 0, 100, 200]],
216
+ [:clip_path_non_zero], [:end_path],
217
+ [:set_line_dash_pattern, [[0, 18], 13]],
218
+ [:move_to, [0, 195]], [:line_to, [100, 195]],
219
+ [:move_to, [100, 5]], [:line_to, [0, 5]],
220
+ [:stroke_path],
221
+ [:set_line_dash_pattern, [[0, 19], 14]],
222
+ [:move_to, [95, 200]], [:line_to, [95, 0]],
223
+ [:move_to, [5, 0]], [:line_to, [5, 200]],
224
+ [:stroke_path],
225
+ [:restore_graphics_state]]
226
+ assert_operators(@canvas.contents, ops)
227
+ end
228
+ end
229
+
230
+ describe "complex borders where edges have different width/color/style values" do
231
+ it "works correctly for the top border" do
232
+ border = create_border(width: [10, 0, 0, 0], color: 0.5, style: :dashed)
233
+ border.draw(@canvas, 0, 0, 200, 300)
234
+ ops = [[:save_graphics_state],
235
+ [:save_graphics_state],
236
+ [:move_to, [0, 300]], [:line_to, [200, 300]],
237
+ [:line_to, [200, 290]], [:line_to, [0, 290]],
238
+ [:clip_path_non_zero], [:end_path],
239
+ [:set_device_gray_stroking_color, [0.5]],
240
+ [:set_line_width, [10]],
241
+ [:set_line_cap_style, [2]],
242
+ [:set_line_dash_pattern, [[10, 20], 25]],
243
+ [:move_to, [0, 295]], [:line_to, [200, 295]],
244
+ [:stroke_path],
245
+ [:restore_graphics_state],
246
+ [:restore_graphics_state]]
247
+ assert_operators(@canvas.contents, ops)
248
+ end
249
+
250
+ it "works correctly for the right border" do
251
+ border = create_border(width: [0, 10, 0, 0], color: 0.5, style: :dashed)
252
+ border.draw(@canvas, 0, 0, 200, 300)
253
+ ops = [[:save_graphics_state],
254
+ [:save_graphics_state],
255
+ [:move_to, [200, 300]], [:line_to, [200, 0]],
256
+ [:line_to, [190, 0]], [:line_to, [190, 300]],
257
+ [:clip_path_non_zero], [:end_path],
258
+ [:set_device_gray_stroking_color, [0.5]],
259
+ [:set_line_width, [10]],
260
+ [:set_line_cap_style, [2]],
261
+ [:set_line_dash_pattern, [[10, 18], 23]],
262
+ [:move_to, [195, 300]], [:line_to, [195, 0]],
263
+ [:stroke_path],
264
+ [:restore_graphics_state],
265
+ [:restore_graphics_state]]
266
+ assert_operators(@canvas.contents, ops)
267
+ end
268
+
269
+ it "works correctly for the bottom border" do
270
+ border = create_border(width: [0, 0, 10, 0], color: 0.5, style: :dashed)
271
+ border.draw(@canvas, 0, 0, 200, 300)
272
+ ops = [[:save_graphics_state],
273
+ [:save_graphics_state],
274
+ [:move_to, [200, 0]], [:line_to, [0, 0]],
275
+ [:line_to, [0, 10]], [:line_to, [200, 10]],
276
+ [:clip_path_non_zero], [:end_path],
277
+ [:set_device_gray_stroking_color, [0.5]],
278
+ [:set_line_width, [10]],
279
+ [:set_line_cap_style, [2]],
280
+ [:set_line_dash_pattern, [[10, 20], 25]],
281
+ [:move_to, [200, 5]], [:line_to, [0, 5]],
282
+ [:stroke_path],
283
+ [:restore_graphics_state],
284
+ [:restore_graphics_state]]
285
+ assert_operators(@canvas.contents, ops)
286
+ end
287
+
288
+ it "works correctly for the left border" do
289
+ border = create_border(width: [0, 0, 0, 10], color: 0.5, style: :dashed)
290
+ border.draw(@canvas, 0, 0, 200, 300)
291
+ ops = [[:save_graphics_state],
292
+ [:save_graphics_state],
293
+ [:move_to, [0, 0]], [:line_to, [0, 300]],
294
+ [:line_to, [10, 300]], [:line_to, [10, 0]],
295
+ [:clip_path_non_zero], [:end_path],
296
+ [:set_device_gray_stroking_color, [0.5]],
297
+ [:set_line_width, [10]],
298
+ [:set_line_cap_style, [2]],
299
+ [:set_line_dash_pattern, [[10, 18], 23]],
300
+ [:move_to, [5, 0]], [:line_to, [5, 300]],
301
+ [:stroke_path],
302
+ [:restore_graphics_state],
303
+ [:restore_graphics_state]]
304
+ assert_operators(@canvas.contents, ops)
305
+ end
306
+
307
+ it "works with all values combined" do
308
+ border = create_border(width: [20, 10, 40, 30], color: [0, 0.25, 0.5, 0.75],
309
+ style: [:solid, :dashed, :dashed_round, :dotted])
310
+ border.draw(@canvas, 0, 0, 100, 200)
311
+ ops = [[:save_graphics_state],
312
+ [:save_graphics_state],
313
+ [:move_to, [0, 200]], [:line_to, [100, 200]],
314
+ [:line_to, [90, 180]], [:line_to, [30, 180]], [:clip_path_non_zero], [:end_path],
315
+ [:set_line_width, [20]],
316
+ [:move_to, [0, 190]], [:line_to, [100, 190]], [:stroke_path],
317
+ [:restore_graphics_state], [:save_graphics_state],
318
+ [:move_to, [100, 200]], [:line_to, [100, 0]],
319
+ [:line_to, [90, 40]], [:line_to, [90, 180]], [:clip_path_non_zero], [:end_path],
320
+ [:set_device_gray_stroking_color, [0.25]], [:set_line_width, [10]],
321
+ [:set_line_cap_style, [2]], [:set_line_dash_pattern, [[10, 20], 25]],
322
+ [:move_to, [95, 200]], [:line_to, [95, 0]], [:stroke_path],
323
+ [:restore_graphics_state], [:save_graphics_state],
324
+ [:move_to, [100, 0]], [:line_to, [0, 0]],
325
+ [:line_to, [30, 40]], [:line_to, [90, 40]], [:clip_path_non_zero], [:end_path],
326
+ [:set_device_gray_stroking_color, [0.5]], [:set_line_width, [40]],
327
+ [:set_line_cap_style, [1]], [:set_line_dash_pattern, [[40, 0], 20]],
328
+ [:move_to, [100, 20]], [:line_to, [0, 20]], [:stroke_path],
329
+ [:restore_graphics_state], [:save_graphics_state],
330
+ [:move_to, [0, 0]], [:line_to, [0, 200]],
331
+ [:line_to, [30, 180]], [:line_to, [30, 40]], [:clip_path_non_zero], [:end_path],
332
+ [:set_device_gray_stroking_color, [0.75]], [:set_line_width, [30]],
333
+ [:set_line_cap_style, [1]], [:set_line_dash_pattern, [[0, 42.5], 27.5]],
334
+ [:move_to, [15, 0]], [:line_to, [15, 200]], [:stroke_path],
335
+ [:restore_graphics_state], [:restore_graphics_state]]
336
+ assert_operators(@canvas.contents, ops)
337
+ end
338
+ end
339
+
340
+ it "raises an error if an invalid style is provided" do
341
+ assert_raises(ArgumentError) do
342
+ create_border(width: 1, color: 0, style: :unknown).draw(@canvas, 0, 0, 10, 10)
343
+ end
344
+ end
345
+ end
346
+ end
347
+
348
+ describe HexaPDF::Layout::Style::Layers do
349
+ before do
350
+ @layers = HexaPDF::Layout::Style::Layers.new
351
+ end
352
+
353
+ it "can be initialized with an array of layers" do
354
+ data = [-> {}]
355
+ layers = HexaPDF::Layout::Style::Layers.new(data)
356
+ assert_equal(data, layers.enum_for(:each, {}).to_a)
357
+ end
358
+
359
+ describe "add and each" do
360
+ it "can use a given block" do
361
+ block = proc { true}
362
+ @layers.add(&block)
363
+ assert_equal([block], @layers.enum_for(:each, {}).to_a)
364
+ end
365
+
366
+ it "can store a reference" do
367
+ @layers.add(:link, option: :value)
368
+ value = Object.new
369
+ value.define_singleton_method(:new) {|*args| :new }
370
+ config = Object.new
371
+ config.define_singleton_method(:constantize) {|*args| value }
372
+ assert_equal([:new], @layers.enum_for(:each, config).to_a)
373
+ end
374
+
375
+ it "fails if neither a block nor a name is given when adding a layer" do
376
+ assert_raises(ArgumentError) { @layers.add }
377
+ end
378
+ end
379
+
380
+ it "can determine whether layers are defined" do
381
+ assert(@layers.none?)
382
+ @layers.add {}
383
+ refute(@layers.none?)
384
+ end
385
+
386
+ it "draws the layers onto a canvas" do
387
+ box = Object.new
388
+ value = nil
389
+ klass = Class.new
390
+ klass.send(:define_method, :initialize) {|**args| @args = args }
391
+ klass.send(:define_method, :call) do |canvas, _|
392
+ value = @args
393
+ canvas.line_width(5)
394
+ end
395
+ canvas = HexaPDF::Document.new.pages.add.canvas
396
+ canvas.context.document.config['style.layers_map'][:test] = klass
397
+
398
+ @layers.add {|canv, ibox| assert_equal(box, ibox); canv.line_width(10)}
399
+ @layers.add(:test, option: :value)
400
+ @layers.draw(canvas, 10, 15, box)
401
+ ops = [[:save_graphics_state],
402
+ [:concatenate_matrix, [1, 0, 0, 1, 10, 15]],
403
+ [:save_graphics_state],
404
+ [:set_line_width, [10]],
405
+ [:restore_graphics_state],
406
+ [:save_graphics_state],
407
+ [:set_line_width, [5]],
408
+ [:restore_graphics_state],
409
+ [:restore_graphics_state]]
410
+ assert_operators(canvas.contents, ops)
411
+ assert_equal({option: :value}, value)
412
+ end
413
+ end
414
+
415
+ describe HexaPDF::Layout::Style::LinkLayer do
416
+ describe "initialize" do
417
+ it "fails if more than one possible target is chosen" do
418
+ assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(dest: true, uri: true) }
419
+ assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(dest: true, file: true) }
420
+ assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(uri: true, file: true) }
421
+ end
422
+
423
+ it "fails if an invalid border is provided" do
424
+ assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(border: 5) }
425
+ end
426
+ end
427
+
428
+ describe "call" do
429
+ before do
430
+ @canvas = HexaPDF::Document.new.pages.add.canvas
431
+ @canvas.translate(10, 10)
432
+ @box = HexaPDF::Layout::Box.new(width: 15, height: 10)
433
+ end
434
+
435
+ def call_link(hash)
436
+ link = HexaPDF::Layout::Style::LinkLayer.new(hash)
437
+ link.call(@canvas, @box)
438
+ @canvas.context[:Annots][0]
439
+ end
440
+
441
+ it "sets general values like /Rect and /QuadPoints" do
442
+ annot = call_link(dest: true)
443
+ assert_equal(:Link, annot[:Subtype])
444
+ assert_equal([10, 10, 25, 20], annot[:Rect].value)
445
+ assert_equal([10, 10, 25, 10, 25, 20, 10, 20], annot[:QuadPoints])
446
+ end
447
+
448
+ it "removes the border by default" do
449
+ annot = call_link(dest: true)
450
+ assert_equal([0, 0, 0], annot[:Border])
451
+ end
452
+
453
+ it "uses a default border if no specific border style is specified" do
454
+ annot = call_link(dest: true, border: true)
455
+ assert_equal([0, 0, 1], annot[:Border])
456
+ end
457
+
458
+ it "uses the specified border and border color" do
459
+ annot = call_link(dest: true, border: [10, 10, 2], border_color: [255])
460
+ assert_equal([10, 10, 2], annot[:Border])
461
+ assert_equal([1.0], annot[:C])
462
+ end
463
+
464
+ it "works for simple destinations" do
465
+ annot = call_link(dest: [@canvas.context, :FitH])
466
+ assert_equal([@canvas.context, :FitH], annot[:Dest])
467
+ assert_nil(annot[:A])
468
+ end
469
+
470
+ it "works for URIs" do
471
+ annot = call_link(uri: "test.html")
472
+ assert_equal({S: :URI, URI: "test.html"}, annot[:A].value)
473
+ assert_nil(annot[:Dest])
474
+ end
475
+
476
+ it "works for files" do
477
+ annot = call_link(file: "local-file.pdf")
478
+ assert_equal({S: :Launch, F: "local-file.pdf", NewWindow: true}, annot[:A].value)
479
+ assert_nil(annot[:Dest])
480
+ end
481
+ end
482
+ end
483
+
75
484
  describe HexaPDF::Layout::Style do
76
485
  before do
77
486
  @style = HexaPDF::Layout::Style.new
@@ -82,7 +491,7 @@ describe HexaPDF::Layout::Style do
82
491
  assert_equal(10, style.font_size)
83
492
  end
84
493
 
85
- it "has several dynamically generated properties with default values" do
494
+ it "has several simple and dynamically generated properties with default values" do
86
495
  assert_raises(HexaPDF::Error) { @style.font }
87
496
  assert_equal(10, @style.font_size)
88
497
  assert_equal(0, @style.character_spacing)
@@ -90,47 +499,126 @@ describe HexaPDF::Layout::Style do
90
499
  assert_equal(100, @style.horizontal_scaling)
91
500
  assert_equal(0, @style.text_rise)
92
501
  assert_equal({}, @style.font_features)
502
+ assert_equal(:fill, @style.text_rendering_mode)
503
+ assert_equal([0], @style.fill_color.components)
504
+ assert_equal(1, @style.fill_alpha)
505
+ assert_equal([0], @style.stroke_color.components)
506
+ assert_equal(1, @style.stroke_alpha)
507
+ assert_equal(1, @style.stroke_width)
508
+ assert_equal(:butt, @style.stroke_cap_style)
509
+ assert_equal(:miter, @style.stroke_join_style)
510
+ assert_equal(10.0, @style.stroke_miter_limit)
93
511
  assert_equal(:left, @style.align)
94
512
  assert_equal(:top, @style.valign)
513
+ assert_equal(0, @style.text_indent)
514
+ assert_nil(@style.background_color)
515
+ assert(@style.padding.simple?)
516
+ assert_equal(0, @style.padding.top)
517
+ assert(@style.margin.simple?)
518
+ assert_equal(0, @style.margin.top)
519
+ assert(@style.border.none?)
520
+ assert_equal([[], 0], @style.stroke_dash_pattern.to_operands)
521
+ assert_equal([:proportional, 1], [@style.line_spacing.type, @style.line_spacing.value])
522
+ refute(@style.subscript)
523
+ refute(@style.superscript)
524
+ assert_kind_of(HexaPDF::Layout::Style::Layers, @style.underlays)
525
+ assert_kind_of(HexaPDF::Layout::Style::Layers, @style.overlays)
95
526
  end
96
527
 
97
- it "can set and retrieve line spacing objects" do
98
- assert_equal([:proportional, 1], [@style.line_spacing.type, @style.line_spacing.value])
99
- @style.line_spacing = :double
100
- assert_equal([:proportional, 2], [@style.line_spacing.type, @style.line_spacing.value])
528
+ it "allows using a non-standard setter for generated properties" do
529
+ @style.padding = [5, 3]
530
+ assert_equal(5, @style.padding.top)
531
+ assert_equal(3, @style.padding.left)
532
+
533
+ @style.stroke_dash_pattern(5, 2)
534
+ assert_equal([[5], 2], @style.stroke_dash_pattern.to_operands)
535
+ end
536
+
537
+ it "allows checking whether a property has been set or accessed" do
538
+ refute(@style.align?)
539
+ assert_equal(:left, @style.align)
540
+ assert(@style.align?)
541
+
542
+ refute(@style.valign?)
543
+ @style.valign = :bottom
544
+ assert(@style.valign?)
101
545
  end
102
546
 
103
- it "can set and retrieve text segmentation algorithms" do
104
- assert_equal(HexaPDF::Layout::TextBox::SimpleTextSegmentation,
547
+ it "has several dynamically generated properties with default values that take blocks" do
548
+ assert_equal(HexaPDF::Layout::TextLayouter::SimpleTextSegmentation,
105
549
  @style.text_segmentation_algorithm)
550
+ assert_equal(HexaPDF::Layout::TextLayouter::SimpleLineWrapping,
551
+ @style.text_line_wrapping_algorithm)
552
+
106
553
  block = proc { :y }
107
554
  @style.text_segmentation_algorithm(&block)
108
555
  assert_equal(block, @style.text_segmentation_algorithm)
109
- end
110
556
 
111
- it "can set and retrieve line wrapping algorithms" do
112
- assert_equal(HexaPDF::Layout::TextBox::SimpleLineWrapping,
113
- @style.text_line_wrapping_algorithm)
114
- @style.text_line_wrapping_algorithm(:callable)
115
- assert_equal(:callable, @style.text_line_wrapping_algorithm)
557
+ @style.text_segmentation_algorithm(:callable)
558
+ assert_equal(:callable, @style.text_segmentation_algorithm)
116
559
  end
117
560
 
118
- it "has methods for some derived and cached values" do
119
- assert_equal(0.01, @style.scaled_font_size)
120
- assert_equal(0, @style.scaled_character_spacing)
121
- assert_equal(0, @style.scaled_word_spacing)
122
- assert_equal(1, @style.scaled_horizontal_scaling)
123
-
124
- wrapped_font = Object.new
125
- wrapped_font.define_singleton_method(:ascender) { 600 }
126
- wrapped_font.define_singleton_method(:descender) { -100 }
127
- font = Object.new
128
- font.define_singleton_method(:scaling_factor) { 1 }
129
- font.define_singleton_method(:wrapped_font) { wrapped_font }
130
- @style.font = font
131
-
132
- assert_equal(6, @style.scaled_font_ascender)
133
- assert_equal(-1, @style.scaled_font_descender)
561
+ describe "methods for some derived and cached values" do
562
+ before do
563
+ wrapped_font = Object.new
564
+ wrapped_font.define_singleton_method(:ascender) { 600 }
565
+ wrapped_font.define_singleton_method(:descender) { -100 }
566
+ font = Object.new
567
+ font.define_singleton_method(:scaling_factor) { 1 }
568
+ font.define_singleton_method(:wrapped_font) { wrapped_font }
569
+ @style.font = font
570
+ end
571
+
572
+ it "computes them correctly" do
573
+ @style.horizontal_scaling(200).character_spacing(1).word_spacing(2)
574
+ assert_equal(0.02, @style.scaled_font_size)
575
+ assert_equal(2, @style.scaled_character_spacing)
576
+ assert_equal(4, @style.scaled_word_spacing)
577
+ assert_equal(2, @style.scaled_horizontal_scaling)
578
+
579
+ assert_equal(6, @style.scaled_font_ascender)
580
+ assert_equal(-1, @style.scaled_font_descender)
581
+ end
582
+
583
+ it "computes item widths correctly" do
584
+ @style.horizontal_scaling(200).character_spacing(1).word_spacing(2)
585
+
586
+ assert_equal(-1.0, @style.scaled_item_width(50))
587
+
588
+ obj = Object.new
589
+ obj.define_singleton_method(:width) { 100 }
590
+ obj.define_singleton_method(:apply_word_spacing?) { true }
591
+ assert_equal(8, @style.scaled_item_width(obj))
592
+ end
593
+
594
+ it "handles subscript" do
595
+ @style.subscript = true
596
+ assert_in_delta(5.83, @style.calculated_font_size)
597
+ assert_in_delta(0.00583, @style.scaled_font_size, 0.000001)
598
+ assert_in_delta(-2.00, @style.calculated_text_rise)
599
+ end
600
+
601
+ it "handles superscript" do
602
+ @style.superscript = true
603
+ assert_in_delta(5.83, @style.calculated_font_size)
604
+ assert_in_delta(3.30, @style.calculated_text_rise)
605
+ end
606
+
607
+ it "handles underline" do
608
+ @style.font.wrapped_font.define_singleton_method(:underline_position) { -100 }
609
+ @style.font.wrapped_font.define_singleton_method(:underline_thickness) { 10 }
610
+ @style.text_rise = 10
611
+ assert_in_delta(-1.05 + 10, @style.calculated_underline_position)
612
+ assert_equal(0.1, @style.calculated_underline_thickness)
613
+ end
614
+
615
+ it "handles strikeout" do
616
+ @style.font.wrapped_font.define_singleton_method(:strikeout_position) { 300 }
617
+ @style.font.wrapped_font.define_singleton_method(:strikeout_thickness) { 10 }
618
+ @style.text_rise = 10
619
+ assert_in_delta(2.95 + 10, @style.calculated_strikeout_position)
620
+ assert_equal(0.1, @style.calculated_strikeout_thickness)
621
+ end
134
622
  end
135
623
 
136
624
  it "can clear cached values" do