hexapdf 1.1.0 → 1.2.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/lib/hexapdf/cli/command.rb +63 -63
  4. data/lib/hexapdf/cli/inspect.rb +1 -1
  5. data/lib/hexapdf/cli/modify.rb +0 -1
  6. data/lib/hexapdf/cli/optimize.rb +5 -5
  7. data/lib/hexapdf/configuration.rb +21 -0
  8. data/lib/hexapdf/content/graphics_state.rb +1 -1
  9. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +1 -1
  10. data/lib/hexapdf/document/annotations.rb +115 -0
  11. data/lib/hexapdf/document.rb +30 -7
  12. data/lib/hexapdf/font/true_type_wrapper.rb +1 -0
  13. data/lib/hexapdf/font/type1_wrapper.rb +1 -0
  14. data/lib/hexapdf/type/acro_form/java_script_actions.rb +9 -2
  15. data/lib/hexapdf/type/acro_form/text_field.rb +9 -2
  16. data/lib/hexapdf/type/annotation.rb +59 -1
  17. data/lib/hexapdf/type/annotations/appearance_generator.rb +273 -0
  18. data/lib/hexapdf/type/annotations/border_styling.rb +160 -0
  19. data/lib/hexapdf/type/annotations/line.rb +521 -0
  20. data/lib/hexapdf/type/annotations/widget.rb +2 -96
  21. data/lib/hexapdf/type/annotations.rb +3 -0
  22. data/lib/hexapdf/type/form.rb +2 -2
  23. data/lib/hexapdf/version.rb +1 -1
  24. data/lib/hexapdf/writer.rb +0 -1
  25. data/lib/hexapdf/xref_section.rb +7 -4
  26. data/test/hexapdf/content/test_graphics_state.rb +2 -3
  27. data/test/hexapdf/content/test_operator.rb +4 -5
  28. data/test/hexapdf/digital_signature/test_cms_handler.rb +7 -8
  29. data/test/hexapdf/digital_signature/test_handler.rb +2 -3
  30. data/test/hexapdf/digital_signature/test_pkcs1_handler.rb +1 -2
  31. data/test/hexapdf/document/test_annotations.rb +33 -0
  32. data/test/hexapdf/font/test_true_type_wrapper.rb +7 -0
  33. data/test/hexapdf/font/test_type1_wrapper.rb +7 -0
  34. data/test/hexapdf/task/test_optimize.rb +1 -1
  35. data/test/hexapdf/test_document.rb +11 -3
  36. data/test/hexapdf/test_stream.rb +1 -2
  37. data/test/hexapdf/test_xref_section.rb +1 -1
  38. data/test/hexapdf/type/acro_form/test_java_script_actions.rb +21 -0
  39. data/test/hexapdf/type/acro_form/test_text_field.rb +7 -1
  40. data/test/hexapdf/type/annotations/test_appearance_generator.rb +398 -0
  41. data/test/hexapdf/type/annotations/test_border_styling.rb +114 -0
  42. data/test/hexapdf/type/annotations/test_line.rb +189 -0
  43. data/test/hexapdf/type/annotations/test_widget.rb +0 -81
  44. data/test/hexapdf/type/test_annotation.rb +55 -0
  45. data/test/hexapdf/type/test_form.rb +6 -0
  46. metadata +10 -2
@@ -0,0 +1,398 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+
6
+ describe HexaPDF::Type::Annotations::AppearanceGenerator do
7
+ before do
8
+ @doc = HexaPDF::Document.new
9
+ end
10
+
11
+ describe "create" do
12
+ it "fails for unsupported annotation types" do
13
+ annot = @doc.add({Type: :Annot, Subtype: :Unknown})
14
+ error = assert_raises(HexaPDF::Error) do
15
+ HexaPDF::Type::Annotations::AppearanceGenerator.new(annot).create_appearance
16
+ end
17
+ assert_match(/Unknown.*not yet supported/, error.message)
18
+ end
19
+ end
20
+
21
+ describe "line" do
22
+ before do
23
+ @line = @doc.add({Type: :Annot, Subtype: :Line, L: [100, 100, 200, 100], C: [0]})
24
+ @generator = HexaPDF::Type::Annotations::AppearanceGenerator.new(@line)
25
+ end
26
+
27
+ it "sets the print flag and unsets the hidden flag" do
28
+ @line.flag(:hidden)
29
+ @generator.create_appearance
30
+ assert(@line.flagged?(:print))
31
+ refute(@line.flagged?(:hidden))
32
+ end
33
+
34
+ it "creates a simple line" do
35
+ @generator.create_appearance
36
+ assert_equal([96, 96, 204, 104], @line[:Rect])
37
+ assert_equal([96, 96, 204, 104], @line.appearance[:BBox])
38
+ assert_operators(@line.appearance.stream,
39
+ [[:concatenate_matrix, [1.0, 0.0, -0.0, 1.0, 100, 100]],
40
+ [:move_to, [0, 0]],
41
+ [:line_to, [100.0, 0]],
42
+ [:stroke_path]])
43
+ end
44
+
45
+ it "creates a rotated line" do
46
+ @line.line(100, 100, 50, 150)
47
+ @generator.create_appearance
48
+ assert_equal([46, 96, 104, 154], @line[:Rect])
49
+ assert_operators(@line.appearance.stream,
50
+ [[:concatenate_matrix, [-0.707107, 0.707107, -0.707107, -0.707107, 100, 100]],
51
+ [:move_to, [0, 0]],
52
+ [:line_to, [70.710678, 0]],
53
+ [:stroke_path]])
54
+ end
55
+
56
+ describe "stroke color" do
57
+ it "uses the specified border color for stroking operations" do
58
+ @line.border_style(color: "red")
59
+ @generator.create_appearance
60
+ assert_operators(@line.appearance.stream,
61
+ [:set_device_rgb_stroking_color, [1, 0, 0]], range: 0)
62
+ end
63
+
64
+ it "works with a transparent border" do
65
+ @line.border_style(color: :transparent, width: 1)
66
+ @generator.create_appearance
67
+ assert_operators(@line.appearance.stream, [:end_path], range: 3)
68
+ end
69
+ end
70
+
71
+ it "uses the specified interior color for non-stroking operations" do
72
+ @line.interior_color("red")
73
+ @generator.create_appearance
74
+ assert_operators(@line.appearance.stream,
75
+ [:set_device_rgb_non_stroking_color, [1, 0, 0]], range: 0)
76
+ end
77
+
78
+ it "sets the specified border line width" do
79
+ @line.border_style(width: 2)
80
+ @generator.create_appearance
81
+ assert_operators(@line.appearance.stream,
82
+ [:set_line_width, [2]], range: 0)
83
+ end
84
+
85
+ it "sets the specified line dash pattern if it is an array" do
86
+ @line.border_style(style: [5, 2])
87
+ @generator.create_appearance
88
+ assert_operators(@line.appearance.stream,
89
+ [:set_line_dash_pattern, [[5, 2], 0]], range: 0)
90
+ end
91
+
92
+ describe "leader lines" do
93
+ it "works for positive leader line length values" do
94
+ @line.leader_line_length(10)
95
+ @generator.create_appearance
96
+ assert_operators(@line.appearance.stream,
97
+ [[:concatenate_matrix, [1.0, 0.0, -0.0, 1.0, 100, 100]],
98
+ [:move_to, [0, 0]],
99
+ [:line_to, [0, 10]],
100
+ [:move_to, [100, 0]],
101
+ [:line_to, [100, 10]],
102
+ [:move_to, [0, 10]],
103
+ [:line_to, [100.0, 10]],
104
+ [:stroke_path]])
105
+ end
106
+
107
+ it "works for negative leader line length values" do
108
+ @line.leader_line_length(-10)
109
+ @generator.create_appearance
110
+ assert_operators(@line.appearance.stream,
111
+ [[:concatenate_matrix, [1.0, 0.0, -0.0, 1.0, 100, 100]],
112
+ [:move_to, [0, 0]],
113
+ [:line_to, [0, -10]],
114
+ [:move_to, [100, 0]],
115
+ [:line_to, [100, -10]],
116
+ [:move_to, [0, -10]],
117
+ [:line_to, [100.0, -10]],
118
+ [:stroke_path]])
119
+ end
120
+
121
+ it "works when using an offset and a positive leader line length" do
122
+ @line.leader_line_length(10)
123
+ @line.leader_line_offset(5)
124
+ @generator.create_appearance
125
+ assert_operators(@line.appearance.stream,
126
+ [[:concatenate_matrix, [1.0, 0.0, -0.0, 1.0, 100, 100]],
127
+ [:move_to, [0, 5]],
128
+ [:line_to, [0, 15]],
129
+ [:move_to, [100, 5]],
130
+ [:line_to, [100, 15]],
131
+ [:move_to, [0, 15]],
132
+ [:line_to, [100.0, 15]],
133
+ [:stroke_path]])
134
+ end
135
+
136
+ it "works when using an offset and a negative leader line length" do
137
+ @line.leader_line_length(-10)
138
+ @line.leader_line_offset(5)
139
+ @generator.create_appearance
140
+ assert_operators(@line.appearance.stream,
141
+ [[:concatenate_matrix, [1.0, 0.0, -0.0, 1.0, 100, 100]],
142
+ [:move_to, [0, -5]],
143
+ [:line_to, [0, -15]],
144
+ [:move_to, [100, -5]],
145
+ [:line_to, [100, -15]],
146
+ [:move_to, [0, -15]],
147
+ [:line_to, [100.0, -15]],
148
+ [:stroke_path]])
149
+ end
150
+
151
+ it "works when using leader line extensions" do
152
+ @line.leader_line_length(10)
153
+ @line.leader_line_extension_length(5)
154
+ @generator.create_appearance
155
+ assert_operators(@line.appearance.stream,
156
+ [[:concatenate_matrix, [1.0, 0.0, -0.0, 1.0, 100, 100]],
157
+ [:move_to, [0, 0]],
158
+ [:line_to, [0, 15]],
159
+ [:move_to, [100, 0]],
160
+ [:line_to, [100, 15]],
161
+ [:move_to, [0, 10]],
162
+ [:line_to, [100.0, 10]],
163
+ [:stroke_path]])
164
+ end
165
+ end
166
+
167
+ describe "line ending styles" do
168
+ before do
169
+ @line.border_style(width: 2)
170
+ @line.interior_color("red")
171
+ end
172
+
173
+ it "works correctly for a transparent border" do
174
+ @line.line_ending_style(start_style: :square, end_style: :square)
175
+ @line.border_style(color: :transparent)
176
+ @generator.create_appearance
177
+ assert_operators(@line.appearance.stream,
178
+ [[:append_rectangle, [-3, -3, 6, 6]],
179
+ [:fill_path_non_zero],
180
+ [:append_rectangle, [97, -3, 6, 6]],
181
+ [:fill_path_non_zero]], range: 5..-1)
182
+ end
183
+
184
+ it "works for a square" do
185
+ @line.line_ending_style(start_style: :square, end_style: :square)
186
+ @generator.create_appearance
187
+ assert_operators(@line.appearance.stream,
188
+ [[:append_rectangle, [-6, -6, 12, 12]],
189
+ [:fill_and_stroke_path_non_zero],
190
+ [:append_rectangle, [94, -6, 12, 12]],
191
+ [:fill_and_stroke_path_non_zero]], range: 6..-1)
192
+ end
193
+
194
+ it "works for a circle" do
195
+ @line.line_ending_style(start_style: :circle, end_style: :circle)
196
+ @generator.create_appearance
197
+ assert_operators(@line.appearance.stream,
198
+ [[:move_to, [6.0, 0.0]],
199
+ [:curve_to, [6.0, 2.140933, 4.854102, 4.125686, 3.0, 5.196152]],
200
+ [:curve_to, [1.145898, 6.266619, -1.145898, 6.266619, -3.0, 5.196152]],
201
+ [:curve_to, [-4.854102, 4.125686, -6.0, 2.140933, -6.0, 0.0]],
202
+ [:curve_to, [-6.0, -2.140933, -4.854102, -4.125686, -3.0, -5.196152]],
203
+ [:curve_to, [-1.145898, -6.266619, 1.145898, -6.266619, 3.0, -5.196152]],
204
+ [:curve_to, [4.854102, -4.125686, 6.0, -2.140933, 6.0, -0.0]],
205
+ [:close_subpath],
206
+ [:fill_and_stroke_path_non_zero],
207
+ [:move_to, [106.0, 0.0]],
208
+ [:curve_to, [106.0, 2.140933, 104.854102, 4.125686, 103.0, 5.196152]],
209
+ [:curve_to, [101.145898, 6.266619, 98.854102, 6.266619, 97.0, 5.196152]],
210
+ [:curve_to, [95.145898, 4.125686, 94.0, 2.140933, 94.0, 0.0]],
211
+ [:curve_to, [94.0, -2.140933, 95.145898, -4.125686, 97.0, -5.196152]],
212
+ [:curve_to, [98.854102, -6.266619, 101.145898, -6.266619, 103.0, -5.196152]],
213
+ [:curve_to, [104.854102, -4.125686, 106.0, -2.140933, 106.0, -0.0]],
214
+ [:close_subpath],
215
+ [:fill_and_stroke_path_non_zero]], range: 6..-1)
216
+ end
217
+
218
+ it "works for a diamond" do
219
+ @line.line_ending_style(start_style: :diamond, end_style: :diamond)
220
+ @generator.create_appearance
221
+ assert_operators(@line.appearance.stream,
222
+ [[:move_to, [6, 0]],
223
+ [:line_to, [0, 6]],
224
+ [:line_to, [-6, 0]],
225
+ [:line_to, [0, -6]],
226
+ [:close_subpath],
227
+ [:fill_and_stroke_path_non_zero],
228
+ [:move_to, [106.0, 0]],
229
+ [:line_to, [100.0, 6]],
230
+ [:line_to, [94.0, 0]],
231
+ [:line_to, [100.0, -6]],
232
+ [:close_subpath],
233
+ [:fill_and_stroke_path_non_zero]], range: 6..-1)
234
+ end
235
+
236
+ it "works for open and closed as well as reversed open and closed arrows" do
237
+ dx = 15.588457
238
+ [:open_arrow, :closed_arrow, :ropen_arrow, :rclosed_arrow].each do |style|
239
+ @line.line_ending_style(start_style: style, end_style: style)
240
+ @generator.create_appearance
241
+ used_dx = (style == :ropen_arrow || style == :rclosed_arrow ? -dx : dx)
242
+ ops = [[:move_to, [used_dx, 9.0]],
243
+ [:line_to, [0, 0]],
244
+ [:line_to, [used_dx, -9.0]],
245
+ [:move_to, [100 - used_dx, -9.0]],
246
+ [:line_to, [100.0, 0]],
247
+ [:line_to, [100 - used_dx, 9.0]]]
248
+ if style == :closed_arrow || style == :rclosed_arrow
249
+ ops.insert(3, [:close_subpath], [:fill_and_stroke_path_non_zero])
250
+ ops.insert(-1, [:close_subpath], [:fill_and_stroke_path_non_zero])
251
+ else
252
+ ops.insert(3, [:stroke_path])
253
+ ops.insert(-1, [:stroke_path])
254
+ end
255
+ assert_operators(@line.appearance.stream, ops, range: 6..-1)
256
+ end
257
+ end
258
+
259
+ it "works for butt" do
260
+ @line.line_ending_style(start_style: :butt, end_style: :butt)
261
+ @generator.create_appearance
262
+ assert_operators(@line.appearance.stream,
263
+ [[:move_to, [0, 6]],
264
+ [:line_to, [0, -6]],
265
+ [:stroke_path],
266
+ [:move_to, [100.0, 6]],
267
+ [:line_to, [100.0, -6]],
268
+ [:stroke_path]], range: 6..-1)
269
+ end
270
+
271
+ it "works for slash" do
272
+ @line.line_ending_style(start_style: :slash, end_style: :slash)
273
+ @generator.create_appearance
274
+ assert_operators(@line.appearance.stream,
275
+ [[:move_to, [3, 5.196152]],
276
+ [:line_to, [-3, -5.196152]],
277
+ [:stroke_path],
278
+ [:move_to, [103.0, 5.196152]],
279
+ [:line_to, [97.0, -5.196152]],
280
+ [:stroke_path]], range: 6..-1)
281
+ end
282
+ end
283
+
284
+ describe "caption" do
285
+ before do
286
+ @line.captioned(true)
287
+ @line.contents("Test")
288
+ end
289
+
290
+ it "adjusts the annotation's /Rect entry" do
291
+ @line.contents("This is some eeeeextra long text")
292
+ @generator.create_appearance
293
+ assert_equal([80.2225, 91.83749999999999, 219.7775, 108.1625], @line[:Rect])
294
+ end
295
+
296
+ it "puts the caption inline" do
297
+ @generator.create_appearance
298
+ assert_operators(@line.appearance.stream,
299
+ [[:move_to, [0, 0]],
300
+ [:line_to, [40.2475, 0]],
301
+ [:move_to, [59.7525, 0]],
302
+ [:line_to, [100, 0]],
303
+ [:stroke_path],
304
+ [:save_graphics_state],
305
+ [:set_font_and_size, [:F1, 9]],
306
+ [:begin_text],
307
+ [:move_text, [41.2475, -2.2995]],
308
+ [:show_text, ["Test"]],
309
+ [:end_text]], range: 1..-2)
310
+ end
311
+
312
+ it "puts the caption inline with an offset" do
313
+ @line.caption_offset(20, 5)
314
+ @generator.create_appearance
315
+ assert_operators(@line.appearance.stream,
316
+ [[:move_to, [0, 0]],
317
+ [:line_to, [60.2475, 0]],
318
+ [:move_to, [79.7525, 0]],
319
+ [:line_to, [100, 0]],
320
+ [:stroke_path],
321
+ [:save_graphics_state],
322
+ [:set_font_and_size, [:F1, 9]],
323
+ [:begin_text],
324
+ [:move_text, [61.2475, 2.7005]],
325
+ [:show_text, ["Test"]],
326
+ [:end_text]], range: 1..-2)
327
+ end
328
+
329
+ it "handles too long inline captions" do
330
+ @line.contents('This inline text is so long that no line is shown')
331
+ @generator.create_appearance
332
+ assert_operators(@line.appearance.stream,
333
+ [[:move_to, [0, 0]],
334
+ [:line_to, [0, 0]],
335
+ [:move_to, [100, 0]],
336
+ [:line_to, [100, 0]],
337
+ [:stroke_path],
338
+ [:save_graphics_state],
339
+ [:set_font_and_size, [:F1, 9]],
340
+ [:begin_text],
341
+ [:move_text, [-41.0395, -2.2995]],
342
+ [:show_text, ["This inline text is so long that no line is shown"]],
343
+ [:end_text]], range: 1..-2)
344
+ end
345
+
346
+ it "puts the caption on top of the line" do
347
+ @line.caption_position(:top)
348
+ @generator.create_appearance
349
+ assert_operators(@line.appearance.stream,
350
+ [[:move_to, [0, 0]],
351
+ [:line_to, [100, 0]],
352
+ [:stroke_path],
353
+ [:save_graphics_state],
354
+ [:set_font_and_size, [:F1, 9]],
355
+ [:begin_text],
356
+ [:move_text, [41.2475, 3.863]],
357
+ [:show_text, ["Test"]],
358
+ [:end_text]], range: 1..-2)
359
+ end
360
+
361
+ it "puts the caption on top of the line" do
362
+ @line.caption_position(:top)
363
+ @line.caption_offset(-20, -5)
364
+ @generator.create_appearance
365
+ assert_operators(@line.appearance.stream,
366
+ [[:move_to, [0, 0]],
367
+ [:line_to, [100, 0]],
368
+ [:stroke_path],
369
+ [:save_graphics_state],
370
+ [:set_font_and_size, [:F1, 9]],
371
+ [:begin_text],
372
+ [:move_text, [21.2475, -1.137]],
373
+ [:show_text, ["Test"]],
374
+ [:end_text]], range: 1..-2)
375
+ end
376
+
377
+ it "handles text with line breaks" do
378
+ @line.contents("This inline text\ris long")
379
+ @generator.create_appearance
380
+ assert_operators(@line.appearance.stream,
381
+ [[:move_to, [0, 0]],
382
+ [:line_to, [20.2405, 0]],
383
+ [:move_to, [79.7595, 0]],
384
+ [:line_to, [100, 0]],
385
+ [:stroke_path],
386
+ [:save_graphics_state],
387
+ [:set_leading, [10.40625]],
388
+ [:set_font_and_size, [:F1, 9]],
389
+ [:begin_text],
390
+ [:move_text, [21.2405, 2.903625]],
391
+ [:show_text, ["This inline text"]],
392
+ [:move_text_next_line],
393
+ [:show_text, ["is long"]],
394
+ [:end_text]], range: 1..-2)
395
+ end
396
+ end
397
+ end
398
+ end
@@ -0,0 +1,114 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/annotations/border_styling'
6
+
7
+ describe HexaPDF::Type::Annotations::BorderStyling do
8
+ class TestAnnot < HexaPDF::Type::Annotation
9
+ define_field :BS, type: :Border
10
+ define_field :MK, type: :XXAppearanceCharacteristics
11
+ include HexaPDF::Type::Annotations::BorderStyling
12
+ end
13
+
14
+ before do
15
+ @doc = HexaPDF::Document.new
16
+ @annot = @doc.wrap({Type: :Annot}, type: TestAnnot)
17
+ @color = HexaPDF::Content::ColorSpace.prenormalized_device_color([1, 0, 1])
18
+ end
19
+
20
+ describe "border_style" do
21
+ describe "getter" do
22
+ it "no /Border, /BS or /C|/MK set" do
23
+ @annot.delete(:MK)
24
+ assert_equal([1, nil, :solid, 0, 0], @annot.border_style.to_a)
25
+ end
26
+
27
+ it "no /Border, /BS but with /MK empty" do
28
+ @annot[:Subtype] = :Widget
29
+ @annot[:MK] = {}
30
+ assert_equal([1, nil, :solid, 0, 0], @annot.border_style.to_a)
31
+ end
32
+
33
+ it "uses the color from /C" do
34
+ @annot[:C] = [1, 0, 1]
35
+ assert_equal([1, @color, :solid, 0, 0], @annot.border_style.to_a)
36
+ @annot[:C] = []
37
+ assert_equal([1, nil, :solid, 0, 0], @annot.border_style.to_a)
38
+ end
39
+
40
+ it "uses the color from /MK" do
41
+ @annot[:Subtype] = :Widget
42
+ @annot[:MK] = {BC: [1, 0, 1]}
43
+ assert_equal([1, @color, :solid, 0, 0], @annot.border_style.to_a)
44
+ @annot[:MK][:BC] = []
45
+ assert_equal([1, nil, :solid, 0, 0], @annot.border_style.to_a)
46
+ end
47
+
48
+ it "uses the data from /Border" do
49
+ @annot[:Border] = [1, 2, 3, [1, 2]]
50
+ assert_equal([3, nil, [1, 2], 1, 2], @annot.border_style.to_a)
51
+ end
52
+
53
+ it "uses the data from /BS, overriding /Border values" do
54
+ @annot[:Border] = [1, 2, 3, [1, 2]]
55
+ @annot[:BS] = {W: 5, S: :D, D: [5, 6]}
56
+ assert_equal([5, nil, [5, 6], 0, 0], @annot.border_style.to_a)
57
+
58
+ [[:S, :solid], [:D, [5, 6]], [:B, :beveled], [:I, :inset],
59
+ [:U, :underlined], [:Unknown, :solid]].each do |val, result|
60
+ @annot[:BS] = {S: val, D: [5, 6]}
61
+ assert_equal([1, nil, result, 0, 0], @annot.border_style.to_a)
62
+ end
63
+ end
64
+ end
65
+
66
+ describe "setter" do
67
+ it "returns self" do
68
+ assert_equal(@annot, @annot.border_style(width: 1))
69
+ end
70
+
71
+ it "sets the color" do
72
+ @annot.border_style(color: [1.0, 51, 1.0])
73
+ assert_equal([1, 0.2, 1], @annot[:C])
74
+
75
+ @annot.border_style(color: :transparent)
76
+ assert_equal([], @annot[:C])
77
+ end
78
+
79
+ it "sets the color on a widget using /MK" do
80
+ @annot[:Subtype] = :Widget
81
+ @annot.border_style(color: [1.0, 51, 1.0])
82
+ assert_equal([1, 0.2, 1], @annot[:MK][:BC])
83
+
84
+ @annot.border_style(color: :transparent)
85
+ assert_equal([], @annot[:MK][:BC])
86
+ end
87
+
88
+ it "sets the width" do
89
+ @annot.border_style(width: 2)
90
+ assert_equal(2, @annot[:BS][:W])
91
+ end
92
+
93
+ it "sets the style" do
94
+ [[:solid, :S], [[5, 6], :D], [:beveled, :B], [:inset, :I], [:underlined, :U]].each do |val, r|
95
+ @annot.border_style(style: val)
96
+ assert_equal(r, @annot[:BS][:S])
97
+ assert_equal(val, @annot[:BS][:D]) if r == :D
98
+ end
99
+ end
100
+
101
+ it "overrides all priorly set values" do
102
+ @annot.border_style(width: 3, style: :inset, color: [1])
103
+ @annot.border_style(width: 5)
104
+ border_style = @annot.border_style
105
+ assert_equal(:solid, border_style.style)
106
+ assert_equal([0], border_style.color.components)
107
+ end
108
+
109
+ it "raises an error for an unknown style" do
110
+ assert_raises(ArgumentError) { @annot.border_style(style: :unknown) }
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,189 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/annotations/line'
6
+
7
+ describe HexaPDF::Type::Annotations::Line do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @line = @doc.add({Type: :Annot, Subtype: :Line, Rect: [0, 0, 0, 0], L: [0, 0, 1, 1]})
11
+ end
12
+
13
+ describe "line" do
14
+ it "returns the coordinates of the start and end points" do
15
+ @line[:L] = [10, 20, 30, 40]
16
+ assert_equal([10, 20, 30, 40], @line.line)
17
+ end
18
+
19
+ it "sets the line points" do
20
+ assert_equal(@line, @line.line(1, 2, 3, 4))
21
+ assert_equal([1, 2, 3, 4], @line[:L])
22
+ end
23
+
24
+ it "raises an ArgumentError if not all arguments are provided" do
25
+ assert_raises(ArgumentError) { @line.line(1, 2, 3) }
26
+ end
27
+ end
28
+
29
+ describe "line_ending_style" do
30
+ it "returns the current style" do
31
+ assert_kind_of(HexaPDF::Type::Annotations::Line::LineEndingStyle, @line.line_ending_style)
32
+ assert_equal([:none, :none], @line.line_ending_style.to_a)
33
+ @line[:LE] = [:Diamond, :OpenArrow]
34
+ assert_equal([:diamond, :open_arrow], @line.line_ending_style.to_a)
35
+ @line[:LE] = [:Diamond, :Unknown]
36
+ assert_equal([:diamond, :none], @line.line_ending_style.to_a)
37
+ end
38
+
39
+ it "sets the style" do
40
+ assert_same(@line, @line.line_ending_style(start_style: :OpenArrow))
41
+ assert_equal([:OpenArrow, :None], @line[:LE])
42
+ assert_same(@line, @line.line_ending_style(end_style: :open_arrow))
43
+ assert_equal([:OpenArrow, :OpenArrow], @line[:LE])
44
+ assert_same(@line, @line.line_ending_style(start_style: :circle, end_style: :ClosedArrow))
45
+ assert_equal([:Circle, :ClosedArrow], @line[:LE])
46
+ end
47
+
48
+ it "raises an error for unknown styles" do
49
+ assert_raises(ArgumentError) { @line.line_ending_style(start_style: :unknown) }
50
+ assert_raises(ArgumentError) { @line.line_ending_style(end_style: :unknown) }
51
+ end
52
+ end
53
+
54
+ describe "interior_color" do
55
+ it "returns the interior color" do
56
+ assert_nil(@line.interior_color)
57
+ @line[:IC] = []
58
+ assert_nil(@line.interior_color)
59
+ @line[:IC] = [0.5]
60
+ assert_equal(HexaPDF::Content::ColorSpace.device_color_from_specification(0.5),
61
+ @line.interior_color)
62
+ end
63
+
64
+ it "sets the interior color" do
65
+ @line.interior_color(255)
66
+ assert_equal([1.0], @line[:IC])
67
+ @line.interior_color(255, 255, 0)
68
+ assert_equal([1.0, 1.0, 0], @line[:IC])
69
+ @line.interior_color(:transparent)
70
+ assert_equal([], @line[:IC])
71
+ end
72
+ end
73
+
74
+ describe "leader_line_length" do
75
+ it "returns the leader line length" do
76
+ assert_equal(0, @line.leader_line_length)
77
+ @line[:LL] = 10
78
+ assert_equal(10, @line.leader_line_length)
79
+ end
80
+
81
+ it "sets the leader line length" do
82
+ assert_equal(@line, @line.leader_line_length(10))
83
+ assert_equal(10, @line[:LL])
84
+ end
85
+ end
86
+
87
+ describe "leader_line_extension_length" do
88
+ it "returns the leader line extension length" do
89
+ assert_equal(0, @line.leader_line_extension_length)
90
+ @line[:LLE] = 10
91
+ assert_equal(10, @line.leader_line_extension_length)
92
+ end
93
+
94
+ it "sets the leader line extension length" do
95
+ assert_equal(@line, @line.leader_line_extension_length(10))
96
+ assert_equal(10, @line[:LLE])
97
+ end
98
+
99
+ it "raises an error for negative numbers" do
100
+ assert_raises(ArgumentError) { @line.leader_line_extension_length(-10) }
101
+ end
102
+ end
103
+
104
+ describe "leader_line_offset" do
105
+ it "returns the leader line offset" do
106
+ assert_equal(0, @line.leader_line_offset)
107
+ @line[:LLO] = 10
108
+ assert_equal(10, @line.leader_line_offset)
109
+ end
110
+
111
+ it "sets the leader line offset" do
112
+ assert_equal(@line, @line.leader_line_offset(10))
113
+ assert_equal(10, @line[:LLO])
114
+ end
115
+ end
116
+
117
+ describe "captioned" do
118
+ it "returns whether a caption is shown" do
119
+ refute(@line.captioned)
120
+ @line[:Cap] = true
121
+ assert(@line.captioned)
122
+ end
123
+
124
+ it "sets whether a caption should be shown" do
125
+ assert_equal(@line, @line.captioned(true))
126
+ assert_equal(true, @line[:Cap])
127
+ end
128
+ end
129
+
130
+ describe "caption_position" do
131
+ it "returns the caption position" do
132
+ assert_equal(:inline, @line.caption_position)
133
+ @line[:CP] = :Top
134
+ assert_equal(:top, @line.caption_position)
135
+ end
136
+
137
+ it "sets the caption position" do
138
+ assert_equal(@line, @line.caption_position(:top))
139
+ assert_equal(:Top, @line[:CP])
140
+ end
141
+
142
+ it "raises an error on invalid caption positions" do
143
+ assert_raises(ArgumentError) { @line.caption_position(:unknown) }
144
+ end
145
+ end
146
+
147
+ describe "caption_offset" do
148
+ it "returns the caption offset" do
149
+ assert_equal([0, 0], @line.caption_offset)
150
+ @line[:CO] = [10, 5]
151
+ assert_equal([10, 5], @line.caption_offset)
152
+ end
153
+
154
+ it "sets the caption offset" do
155
+ assert_equal(@line, @line.caption_offset(5, 10))
156
+ assert_equal([5, 10], @line[:CO])
157
+ assert_equal(@line, @line.caption_offset(5))
158
+ assert_equal([5, 0], @line[:CO])
159
+ assert_equal(@line, @line.caption_offset(nil, 5))
160
+ assert_equal([0, 5], @line[:CO])
161
+ end
162
+ end
163
+
164
+ describe "perform_validation" do
165
+ it "validates that leader line length is set if extension length is set" do
166
+ @line[:LLE] = 10
167
+ m = nil
168
+ refute(@line.validate {|msg| m = msg })
169
+ assert_match(/\/LL required to be non-zero/, m)
170
+ end
171
+
172
+ it "ensures leader line extension length is non-negative" do
173
+ @line[:LL] = 10
174
+ @line[:LLE] = -10
175
+ m = nil
176
+ assert(@line.validate {|msg| m = msg })
177
+ assert_equal(10, @line[:LLE])
178
+ assert_match(/non-negative/, m)
179
+ end
180
+
181
+ it "ensures leader line offset is non-negative" do
182
+ @line[:LLO] = -10
183
+ m = nil
184
+ assert(@line.validate {|msg| m = msg })
185
+ assert_equal(10, @line[:LLO])
186
+ assert_match(/\/LLO must be a non-negative/, m)
187
+ end
188
+ end
189
+ end