hexapdf 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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