hexapdf 1.3.0 → 1.4.1

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -0
  3. data/lib/hexapdf/cli/form.rb +10 -5
  4. data/lib/hexapdf/cli/images.rb +5 -1
  5. data/lib/hexapdf/cli.rb +3 -0
  6. data/lib/hexapdf/configuration.rb +10 -0
  7. data/lib/hexapdf/dictionary_fields.rb +1 -1
  8. data/lib/hexapdf/digital_signature/signing/default_handler.rb +1 -2
  9. data/lib/hexapdf/document/annotations.rb +47 -0
  10. data/lib/hexapdf/document/layout.rb +73 -33
  11. data/lib/hexapdf/document/metadata.rb +10 -3
  12. data/lib/hexapdf/document.rb +10 -1
  13. data/lib/hexapdf/encryption/standard_security_handler.rb +7 -2
  14. data/lib/hexapdf/font/encoding/base.rb +27 -0
  15. data/lib/hexapdf/font/type1_wrapper.rb +1 -3
  16. data/lib/hexapdf/layout/box.rb +5 -0
  17. data/lib/hexapdf/layout/container_box.rb +63 -28
  18. data/lib/hexapdf/layout/style.rb +28 -13
  19. data/lib/hexapdf/layout/table_box.rb +20 -2
  20. data/lib/hexapdf/serializer.rb +7 -7
  21. data/lib/hexapdf/type/annotations/appearance_generator.rb +94 -16
  22. data/lib/hexapdf/type/annotations/interior_color.rb +1 -1
  23. data/lib/hexapdf/type/annotations/line.rb +1 -157
  24. data/lib/hexapdf/type/annotations/line_ending_styling.rb +208 -0
  25. data/lib/hexapdf/type/annotations/markup_annotation.rb +0 -1
  26. data/lib/hexapdf/type/annotations/polygon.rb +64 -0
  27. data/lib/hexapdf/type/annotations/polygon_polyline.rb +109 -0
  28. data/lib/hexapdf/type/annotations/polyline.rb +64 -0
  29. data/lib/hexapdf/type/annotations.rb +4 -0
  30. data/lib/hexapdf/type/font_type1.rb +12 -1
  31. data/lib/hexapdf/type/measure.rb +57 -0
  32. data/lib/hexapdf/type.rb +1 -0
  33. data/lib/hexapdf/utils/sorted_tree_node.rb +4 -1
  34. data/lib/hexapdf/version.rb +1 -1
  35. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +0 -1
  36. data/test/hexapdf/document/test_annotations.rb +20 -0
  37. data/test/hexapdf/document/test_layout.rb +16 -10
  38. data/test/hexapdf/document/test_metadata.rb +13 -1
  39. data/test/hexapdf/encryption/test_standard_security_handler.rb +2 -1
  40. data/test/hexapdf/font/encoding/test_base.rb +20 -0
  41. data/test/hexapdf/layout/test_box.rb +8 -0
  42. data/test/hexapdf/layout/test_container_box.rb +34 -6
  43. data/test/hexapdf/layout/test_page_style.rb +1 -1
  44. data/test/hexapdf/layout/test_style.rb +20 -1
  45. data/test/hexapdf/layout/test_table_box.rb +14 -1
  46. data/test/hexapdf/test_dictionary_fields.rb +1 -0
  47. data/test/hexapdf/test_document.rb +1 -0
  48. data/test/hexapdf/test_serializer.rb +2 -1
  49. data/test/hexapdf/type/annotations/test_appearance_generator.rb +126 -0
  50. data/test/hexapdf/type/annotations/test_line.rb +0 -25
  51. data/test/hexapdf/type/annotations/test_line_ending_styling.rb +42 -0
  52. data/test/hexapdf/type/annotations/test_polygon_polyline.rb +29 -0
  53. data/test/hexapdf/type/annotations/test_widget.rb +8 -0
  54. data/test/hexapdf/type/test_font_type1.rb +14 -0
  55. data/test/hexapdf/utils/test_sorted_tree_node.rb +11 -1
  56. metadata +9 -2
@@ -13,6 +13,17 @@ describe HexaPDF::Document::Metadata do
13
13
 
14
14
  it "parses the info dictionary on creation" do
15
15
  assert_equal('Title', @metadata.title)
16
+
17
+ time = Time.now
18
+ @doc.trailer.info[:ModDate] = ''
19
+ assert_nil(HexaPDF::Document::Metadata.new(@doc).modification_date)
20
+ @doc.trailer.info[:ModDate] = time
21
+ assert_equal(time, HexaPDF::Document::Metadata.new(@doc).modification_date)
22
+ @doc.trailer.info[:CreationDate] = ''
23
+ assert_nil(HexaPDF::Document::Metadata.new(@doc).creation_date)
24
+ @doc.trailer.info[:CreationDate] = time
25
+ assert_equal(time, HexaPDF::Document::Metadata.new(@doc).creation_date)
26
+
16
27
  @doc.trailer.info[:Trapped] = :Unknown
17
28
  assert_nil(HexaPDF::Document::Metadata.new(@doc).trapped)
18
29
  @doc.trailer.info[:Trapped] = :True
@@ -213,6 +224,7 @@ describe HexaPDF::Document::Metadata do
213
224
  title.language = 'de'
214
225
  @metadata.title(['Title', title])
215
226
  @metadata.author(['Author 1', 'Author 2'])
227
+ @metadata.creation_date('')
216
228
  @metadata.register_property_type('dc', 'other', 'URI')
217
229
  @metadata.property('dc', 'other', 'https://test.org/example')
218
230
  @metadata.property('pdfaid', 'part', 3)
@@ -243,7 +255,7 @@ describe HexaPDF::Document::Metadata do
243
255
  </rdf:Description>
244
256
  <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">
245
257
  <xmp:CreatorTool>Creator</xmp:CreatorTool>
246
- <xmp:CreateDate>#{@metadata.send(:xmp_date, @time)}</xmp:CreateDate>
258
+ <xmp:CreateDate></xmp:CreateDate>
247
259
  <xmp:ModifyDate>#{@metadata.send(:xmp_date, @time)}</xmp:ModifyDate>
248
260
  </rdf:Description>
249
261
  <rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">
@@ -188,10 +188,11 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
188
188
  refute(dict.value.key?(:Perms))
189
189
  crypt_filter.call(dict, 4, :AESV2, 16)
190
190
 
191
- dict = @handler.set_up_encryption(key_length: 256, algorithm: :aes)
191
+ dict = @handler.set_up_encryption(key_length: 256, algorithm: :aes, owner_password: 'hexapdf')
192
192
  assert_equal(32, dict[:UE].length)
193
193
  assert_equal(32, dict[:OE].length)
194
194
  assert_equal(16, dict[:Perms].length)
195
+ assert(@handler.send(:owner_password_valid?, 'hexapdf'))
195
196
  crypt_filter.call(dict, 6, :AESV3, 32)
196
197
  end
197
198
 
@@ -1,6 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  require 'test_helper'
4
+ require 'hexapdf/font/encoding'
4
5
  require 'hexapdf/font/encoding/base'
5
6
 
6
7
  describe HexaPDF::Font::Encoding::Base do
@@ -42,4 +43,23 @@ describe HexaPDF::Font::Encoding::Base do
42
43
  assert_nil(@base.code(:Unknown))
43
44
  end
44
45
  end
46
+
47
+ describe "to_compact_array" do
48
+ before do
49
+ @base.code_to_name[66] = :B
50
+ @base.code_to_name[67] = :C
51
+ @base.code_to_name[20] = :space
52
+ @base.code_to_name[28] = :D
53
+ @base.code_to_name[29] = :E
54
+ end
55
+
56
+ it "returns the difference array" do
57
+ assert_equal([20, :space, 28, :D, :E, 65, :A, :B, :C], @base.to_compact_array)
58
+ end
59
+
60
+ it "ignores the codes that are the same in the base encoding" do
61
+ std_encoding = HexaPDF::Font::Encoding.for_name(:StandardEncoding)
62
+ assert_equal([20, :space, 28, :D, :E, ], @base.to_compact_array(base_encoding: std_encoding))
63
+ end
64
+ end
45
65
  end
@@ -5,6 +5,14 @@ require 'hexapdf/document'
5
5
  require 'hexapdf/layout/box'
6
6
 
7
7
  describe HexaPDF::Layout::Box::FitResult do
8
+ it "allows setting the status to failure" do
9
+ result = HexaPDF::Layout::Box::FitResult.new(nil)
10
+ result.overflow!
11
+ refute(result.failure?)
12
+ result.failure!
13
+ assert(result.failure?)
14
+ end
15
+
8
16
  it "shows the box's mask area on #draw when using debug output" do
9
17
  doc = HexaPDF::Document.new(config: {'debug' => true})
10
18
  canvas = doc.pages.add.canvas
@@ -10,6 +10,10 @@ describe HexaPDF::Layout::ContainerBox do
10
10
  @frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100)
11
11
  end
12
12
 
13
+ def child_box(height: 0, width: 0, **style_properties)
14
+ @doc.layout.box(height: height, width: width, style: style_properties)
15
+ end
16
+
13
17
  def create_box(children, **kwargs)
14
18
  HexaPDF::Layout::ContainerBox.new(children: Array(children), **kwargs)
15
19
  end
@@ -48,17 +52,41 @@ describe HexaPDF::Layout::ContainerBox do
48
52
 
49
53
  describe "fit_content" do
50
54
  it "fits the children according to their mask_mode, valign, and align style properties" do
51
- box = create_box([@doc.layout.box(height: 20),
52
- @doc.layout.box(height: 20, style: {valign: :bottom, mask_mode: :fill_horizontal}),
53
- @doc.layout.box(width: 20, style: {align: :right, mask_mode: :fill_vertical})])
55
+ box = create_box([child_box(height: 20),
56
+ child_box(height: 20, valign: :bottom, mask_mode: :fill_horizontal),
57
+ child_box(width: 20, align: :right, mask_mode: :fill_vertical)])
54
58
  check_box(box, 100, 100, [[0, 80], [0, 0], [80, 20]])
55
59
  end
56
60
 
57
61
  it "respects the initially set width/height as well as border/padding" do
58
- box = create_box(@doc.layout.box(height: 20), height: 50, width: 40,
62
+ box = create_box(child_box(height: 20), height: 50, width: 40,
59
63
  style: {padding: 2, border: {width: 3}})
60
64
  check_box(box, 40, 50, [[5, 75]])
61
65
  end
66
+
67
+ it "fails if splitting is not allowed and the content is too big" do
68
+ box = create_box([child_box(height: 80), child_box(height: 30)])
69
+ box.fit(@frame.available_width, @frame.available_height, @frame)
70
+ assert(box.fit_result.failure?)
71
+ end
72
+
73
+ it "splits the box if splitting is allowed and the content is too big" do
74
+ box = create_box([child_box(height: 80), child_box(height: 30)], splitable: true)
75
+ box.fit(@frame.available_width, @frame.available_height, @frame)
76
+ assert(box.fit_result.overflow?)
77
+ end
78
+ end
79
+
80
+ describe "split_content" do
81
+ it "assigns the overflown boxes to the split box" do
82
+ box = create_box([child_box(height: 80), child_box(height: 30)], splitable: true)
83
+ box.fit(@frame.available_width, @frame.available_height, @frame)
84
+ assert(box.fit_result.overflow?)
85
+ box_a, box_b = box.split
86
+ assert_same(box, box_a)
87
+ assert(box_b.split_box?)
88
+ assert_equal(1, box_b.children.size)
89
+ end
62
90
  end
63
91
 
64
92
  describe "draw_content" do
@@ -67,10 +95,10 @@ describe HexaPDF::Layout::ContainerBox do
67
95
  end
68
96
 
69
97
  it "draws the result onto the canvas" do
70
- child_box = @doc.layout.box(height: 20) do |canvas, b|
98
+ cbox = @doc.layout.box(height: 20) do |canvas, b|
71
99
  canvas.line(0, 0, b.content_width, b.content_height).end_path
72
100
  end
73
- box = create_box(child_box)
101
+ box = create_box(cbox)
74
102
  box.fit(100, 100, @frame)
75
103
  box.draw(@canvas, 0, 50)
76
104
  assert_operators(@canvas.contents, [[:save_graphics_state],
@@ -49,7 +49,7 @@ describe HexaPDF::Layout::PageStyle do
49
49
  assert_equal("", page1.contents)
50
50
  assert_equal(523.275591, style.frame.width)
51
51
 
52
- page2 = style.create_page(@doc)
52
+ style.create_page(@doc)
53
53
  refute_same(frame1, style.frame)
54
54
  end
55
55
 
@@ -136,6 +136,19 @@ describe HexaPDF::Layout::Style::Quad do
136
136
  assert_equal(new_quad.bottom, quad.bottom)
137
137
  assert_equal(new_quad.left, quad.left)
138
138
  end
139
+
140
+ it "works with a Hash as value" do
141
+ quad = create_quad(top: 5, left: 10)
142
+ assert_equal(5, quad.top)
143
+ assert_equal(0, quad.bottom)
144
+ assert_equal(10, quad.left)
145
+ assert_equal(0, quad.right)
146
+ quad.set(right: 7)
147
+ assert_equal(5, quad.top)
148
+ assert_equal(0, quad.bottom)
149
+ assert_equal(10, quad.left)
150
+ assert_equal(7, quad.right)
151
+ end
139
152
  end
140
153
 
141
154
  it "can be asked if it contains only a single value" do
@@ -744,7 +757,7 @@ describe HexaPDF::Layout::Style do
744
757
  @style.line_spacing = 1.2
745
758
  assert_equal(0.005, @style.scaled_font_size)
746
759
  assert_equal([[:font, @style.font], [:font_size, 5], [:horizontal_scaling, 100],
747
- [:line_spacing, @style.line_spacing], [:subscript, false], [:superscript, false]],
760
+ [:line_spacing, @style.line_spacing]],
748
761
  @style.each_property.to_a.sort)
749
762
  end
750
763
  end
@@ -887,6 +900,9 @@ describe HexaPDF::Layout::Style do
887
900
  end
888
901
 
889
902
  it "handles subscript" do
903
+ @style.subscript = false
904
+ assert_equal(10, @style.calculated_font_size)
905
+ assert_equal(0, @style.calculated_text_rise)
890
906
  @style.subscript = true
891
907
  assert_in_delta(5.83, @style.calculated_font_size)
892
908
  assert_in_delta(0.00583, @style.scaled_font_size, 0.000001)
@@ -894,6 +910,9 @@ describe HexaPDF::Layout::Style do
894
910
  end
895
911
 
896
912
  it "handles superscript" do
913
+ @style.superscript = false
914
+ assert_equal(10, @style.calculated_font_size)
915
+ assert_equal(0, @style.calculated_text_rise)
897
916
  @style.superscript = true
898
917
  assert_in_delta(5.83, @style.calculated_font_size)
899
918
  assert_in_delta(3.30, @style.calculated_text_rise)
@@ -116,6 +116,18 @@ describe HexaPDF::Layout::TableBox::Cell do
116
116
  assert_equal(12, cell.preferred_height)
117
117
  end
118
118
 
119
+ it "respects the set minimum height of the cell" do
120
+ cell = create_cell(children: HexaPDF::Layout::Box.create(width: 20, height: 10), min_height: 30)
121
+ assert(cell.fit(100, 25, @frame).failure?)
122
+
123
+ assert(cell.fit(100, 100, @frame).success?)
124
+ assert_equal(30, cell.height)
125
+
126
+ cell = create_cell(children: HexaPDF::Layout::Box.create(width: 20, height: 20), min_height: 2)
127
+ assert(cell.fit(100, 100, @frame).success?)
128
+ assert_equal(32, cell.height)
129
+ end
130
+
119
131
  it "doesn't fit children that are too big" do
120
132
  cell = create_cell(children: HexaPDF::Layout::Box.create(width: 300, height: 20))
121
133
  assert(cell.fit(100, 100, @frame).failure?)
@@ -262,7 +274,7 @@ describe HexaPDF::Layout::TableBox::Cells do
262
274
  end
263
275
 
264
276
  it "sets the correct information on the created cells" do
265
- cells = create_cells([[:a, {col_span: 2, content: :b}],
277
+ cells = create_cells([[:a, {col_span: 2, content: :b, min_height: 30}],
266
278
  [{col_span: 2, row_span: 2, content: :c}, {row_span: 2, content: :d}]])
267
279
  assert_equal(0, cells[0, 0].row)
268
280
  assert_equal(0, cells[0, 0].column)
@@ -272,6 +284,7 @@ describe HexaPDF::Layout::TableBox::Cells do
272
284
  assert_equal(1, cells[0, 1].column)
273
285
  assert_equal(1, cells[0, 1].row_span)
274
286
  assert_equal(2, cells[0, 1].col_span)
287
+ assert_equal(30, cells[0, 1].instance_variable_get(:@min_height))
275
288
  assert_equal(1, cells[1, 0].row)
276
289
  assert_equal(0, cells[1, 0].column)
277
290
  assert_equal(2, cells[1, 0].row_span)
@@ -193,6 +193,7 @@ describe HexaPDF::DictionaryFields do
193
193
  ["D:19981223195210Z00'00", [1998, 12, 23, 19, 52, 10, "+00:00"]],
194
194
  ["D:19981223195210-08", [1998, 12, 23, 19, 52, 10, "-08:00"]], # missing '
195
195
  ["D:19981223195210-08'00", [1998, 12, 23, 19, 52, 10, "-08:00"]], # no trailing ', as per PDF 2.0
196
+ ["D:19981223195210-08'00''", [1998, 12, 23, 19, 52, 10, "-08:00"]], # two trailing '
196
197
  ["D:19981223195210-54'00", [1998, 12, 23, 19, 52, 10, "-23:59:59"]], # TZ hour too large
197
198
  ["D:19981223195210+10'65", [1998, 12, 23, 19, 52, 10, "+11:05"]], # TZ min too large
198
199
  ["D:19982423195210-08'00'", [1998, 12, 23, 19, 52, 10, "-08:00"]], # months too large
@@ -611,5 +611,6 @@ describe HexaPDF::Document do
611
611
  assert_equal(Encoding::ASCII_8BIT, str.encoding)
612
612
  doc = HexaPDF::Document.new(io: StringIO.new(str))
613
613
  assert_equal(:test, doc.trailer.info[:test])
614
+ assert_nil(doc.trailer.info[:ModDate])
614
615
  end
615
616
  end
@@ -181,7 +181,8 @@ describe HexaPDF::Serializer do
181
181
 
182
182
  it "encrypts strings in indirect PDF objects" do
183
183
  assert_serialized("(enc:1:test)", HexaPDF::Object.new("test", oid: 1))
184
- assert_serialized("<</x[(enc:1:test)]>>", HexaPDF::Object.new({x: ["test"]}, oid: 1))
184
+ assert_serialized("<</x[(enc:1:\xFE\xFF\x00t\x00e\x00s\x00t\x00\xF6)]>>".b,
185
+ HexaPDF::Object.new({x: ["testö"]}, oid: 1))
185
186
  end
186
187
 
187
188
  it "doesn't encrypt strings in direct PDF objects" do
@@ -479,4 +479,130 @@ describe HexaPDF::Type::Annotations::AppearanceGenerator do
479
479
  [:stroke_path]])
480
480
  end
481
481
  end
482
+
483
+ describe "polygon/polyline" do
484
+ before do
485
+ @polyline = @doc.add({Type: :Annot, Subtype: :PolyLine, C: [0],
486
+ Vertices: [100, 100, 200, 150, 210, 80]})
487
+ @generator = HexaPDF::Type::Annotations::AppearanceGenerator.new(@polyline)
488
+ end
489
+
490
+ it "sets the print flag and unsets the hidden flag" do
491
+ @polyline.flag(:hidden)
492
+ @generator.create_appearance
493
+ assert(@polyline.flagged?(:print))
494
+ refute(@polyline.flagged?(:hidden))
495
+ end
496
+
497
+ it "creates a simple polyline" do
498
+ @generator.create_appearance
499
+ assert_equal([96, 76, 214, 154], @polyline[:Rect])
500
+ assert_equal([96, 76, 214, 154], @polyline.appearance[:BBox])
501
+ assert_operators(@polyline.appearance.stream,
502
+ [[:move_to, [100, 100]],
503
+ [:line_to, [200, 150]],
504
+ [:line_to, [210, 80]],
505
+ [:stroke_path]])
506
+ end
507
+
508
+ it "creates a simple polygon" do
509
+ @polyline[:Subtype] = :Polygon
510
+ @generator.create_appearance
511
+ assert_operators(@polyline.appearance.stream,
512
+ [[:move_to, [100, 100]],
513
+ [:line_to, [200, 150]],
514
+ [:line_to, [210, 80]],
515
+ [:close_subpath],
516
+ [:stroke_path]])
517
+ end
518
+
519
+ describe "stroke color" do
520
+ it "uses the specified border color for stroking operations" do
521
+ @polyline.border_style(color: "red")
522
+ @generator.create_appearance
523
+ assert_operators(@polyline.appearance.stream,
524
+ [:set_device_rgb_stroking_color, [1, 0, 0]], range: 0)
525
+ assert_operators(@polyline.appearance.stream,
526
+ [:stroke_path], range: 4)
527
+ end
528
+
529
+ it "works with a transparent border" do
530
+ @polyline.border_style(color: :transparent)
531
+ @generator.create_appearance
532
+ assert_operators(@polyline.appearance.stream, [:end_path], range: 3)
533
+ end
534
+ end
535
+
536
+ describe "interior color" do
537
+ it "uses the specified interior color for non-stroking operations" do
538
+ @polyline[:Subtype] = :Polygon
539
+ @polyline.border_style(color: :transparent)
540
+ @polyline.interior_color("red")
541
+ @generator.create_appearance
542
+ assert_operators(@polyline.appearance.stream,
543
+ [:set_device_rgb_non_stroking_color, [1, 0, 0]], range: 0)
544
+ assert_operators(@polyline.appearance.stream,
545
+ [:fill_path_non_zero], range: 5)
546
+ end
547
+
548
+ it "works together with the stroke color" do
549
+ @polyline[:Subtype] = :Polygon
550
+ @polyline.interior_color("red")
551
+ @generator.create_appearance
552
+ assert_operators(@polyline.appearance.stream,
553
+ [:set_device_rgb_non_stroking_color, [1, 0, 0]], range: 0)
554
+ assert_operators(@polyline.appearance.stream,
555
+ [:fill_and_stroke_path_non_zero], range: 5)
556
+ end
557
+
558
+ it "works if neither interior nor border color is used" do
559
+ @polyline[:Subtype] = :Polygon
560
+ @polyline.interior_color(:transparent)
561
+ @polyline.border_style(color: :transparent)
562
+ @generator.create_appearance
563
+ assert_operators(@polyline.appearance.stream,
564
+ [:end_path], range: 4)
565
+ end
566
+ end
567
+
568
+ it "sets the specified border line width" do
569
+ @polyline.border_style(width: 4)
570
+ @generator.create_appearance
571
+ assert_operators(@polyline.appearance.stream,
572
+ [:set_line_width, [4]], range: 0)
573
+ end
574
+
575
+ it "sets the specified line dash pattern if it is an array" do
576
+ @polyline.border_style(style: [5, 2])
577
+ @generator.create_appearance
578
+ assert_operators(@polyline.appearance.stream,
579
+ [:set_line_dash_pattern, [[5, 2], 0]], range: 0)
580
+ end
581
+
582
+ it "sets the specified opacity" do
583
+ @polyline.opacity(fill_alpha: 0.5, stroke_alpha: 0.5)
584
+ @generator.create_appearance
585
+ assert_operators(@polyline.appearance.stream,
586
+ [:set_graphics_state_parameters, [:GS1]], range: 0)
587
+ end
588
+
589
+ it "draws the specified line ending style" do
590
+ @polyline.line_ending_style(start_style: :open_arrow, end_style: :rclosed_arrow)
591
+ @polyline.border_style(width: 2)
592
+ @polyline.interior_color("red")
593
+ @generator.create_appearance
594
+ assert_equal([86, 52, 238, 158], @polyline[:Rect])
595
+ assert_equal([86, 52, 238, 158], @polyline.appearance[:BBox])
596
+ assert_operators(@polyline.appearance.stream,
597
+ [[:move_to, [109.917818, 115.021215]],
598
+ [:line_to, [100, 100]],
599
+ [:line_to, [117.967662, 98.921525]],
600
+ [:stroke_path],
601
+ [:move_to, [221.114086, 94.158993]],
602
+ [:line_to, [210, 80]],
603
+ [:line_to, [203.294995, 96.704578]],
604
+ [:close_subpath],
605
+ [:fill_and_stroke_path_non_zero]], range: 6..-1)
606
+ end
607
+ end
482
608
  end
@@ -26,31 +26,6 @@ describe HexaPDF::Type::Annotations::Line do
26
26
  end
27
27
  end
28
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
29
  describe "leader_line_length" do
55
30
  it "returns the leader line length" do
56
31
  assert_equal(0, @line.leader_line_length)
@@ -0,0 +1,42 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/annotations/line_ending_styling'
6
+
7
+ describe HexaPDF::Type::Annotations::LineEndingStyling do
8
+ class TestAnnotLineEndingStyling < HexaPDF::Type::Annotation
9
+ define_field :LE, type: HexaPDF::PDFArray, default: [:None, :None]
10
+ include HexaPDF::Type::Annotations::LineEndingStyling
11
+ end
12
+
13
+ before do
14
+ @doc = HexaPDF::Document.new
15
+ @annot = @doc.wrap({Type: :Annot}, type: TestAnnotLineEndingStyling)
16
+ end
17
+
18
+ describe "line_ending_style" do
19
+ it "returns the current style" do
20
+ assert_kind_of(HexaPDF::Type::Annotations::Line::LineEndingStyle, @annot.line_ending_style)
21
+ assert_equal([:none, :none], @annot.line_ending_style.to_a)
22
+ @annot[:LE] = [:Diamond, :OpenArrow]
23
+ assert_equal([:diamond, :open_arrow], @annot.line_ending_style.to_a)
24
+ @annot[:LE] = [:Diamond, :Unknown]
25
+ assert_equal([:diamond, :none], @annot.line_ending_style.to_a)
26
+ end
27
+
28
+ it "sets the style" do
29
+ assert_same(@annot, @annot.line_ending_style(start_style: :OpenArrow))
30
+ assert_equal([:OpenArrow, :None], @annot[:LE])
31
+ assert_same(@annot, @annot.line_ending_style(end_style: :open_arrow))
32
+ assert_equal([:OpenArrow, :OpenArrow], @annot[:LE])
33
+ assert_same(@annot, @annot.line_ending_style(start_style: :circle, end_style: :ClosedArrow))
34
+ assert_equal([:Circle, :ClosedArrow], @annot[:LE])
35
+ end
36
+
37
+ it "raises an error for unknown styles" do
38
+ assert_raises(ArgumentError) { @annot.line_ending_style(start_style: :unknown) }
39
+ assert_raises(ArgumentError) { @annot.line_ending_style(end_style: :unknown) }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/annotations/polygon_polyline'
6
+
7
+ describe HexaPDF::Type::Annotations::PolygonPolyline do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @annot = @doc.add({Type: :Annot, Subtype: :Polyline, Rect: [0, 0, 0, 0]},
11
+ type: HexaPDF::Type::Annotations::PolygonPolyline)
12
+ end
13
+
14
+ describe "vertices" do
15
+ it "returns the coordinates of the vertices" do
16
+ @annot[:Vertices] = [10, 20, 30, 40, 50, 60]
17
+ assert_equal([10, 20, 30, 40, 50, 60], @annot.vertices)
18
+ end
19
+
20
+ it "sets the vertices" do
21
+ assert_same(@annot, @annot.vertices(1, 2, 3, 4, 5, 6))
22
+ assert_equal([1, 2, 3, 4, 5, 6], @annot[:Vertices])
23
+ end
24
+
25
+ it "raises an ArgumentError if an uneven number of arguments is provided" do
26
+ assert_raises(ArgumentError) { @annot.vertices(1, 2, 3) }
27
+ end
28
+ end
29
+ end
@@ -52,6 +52,14 @@ describe HexaPDF::Type::Annotations::Widget do
52
52
  assert_kind_of(HexaPDF::Type::AcroForm::TextField, result)
53
53
  refute_same(@widget.data, result.data)
54
54
  end
55
+
56
+ it "works when the type of the field is defined higher up in the field hierarchy" do
57
+ @widget[:Parent] = {T: 'parent', Kids: [@widget]}
58
+ @widget[:Parent][:Parent] = {FT: :Tx, Kids: [@widget[:Parent]]}
59
+ result = @widget.form_field
60
+ assert_kind_of(HexaPDF::Type::AcroForm::TextField, result)
61
+ refute_same(@widget.data, result.data)
62
+ end
55
63
  end
56
64
 
57
65
  describe "background_color" do
@@ -143,5 +143,19 @@ describe HexaPDF::Type::FontType1 do
143
143
  @font[:Encoding] = :Other
144
144
  refute(@font.validate)
145
145
  end
146
+
147
+ it "works around certain invalid PDFs with a /SymbolEncoding value for /Encoding" do
148
+ @font[:Encoding] = :SymbolEncoding
149
+ @font[:BaseFont] = :Symbol
150
+ assert(@font.validate)
151
+ refute(@font.key?(:Encoding))
152
+ end
153
+
154
+ it "works around certain invalid PDFs with a /StandardEncoding value for /Encoding" do
155
+ @font[:Encoding] = :StandardEncoding
156
+ assert(@font.validate)
157
+ assert(:WinAnsiEncoding, @font[:Encoding][:BaseEncoding])
158
+ assert_equal([39, :quoteright, 96, :quoteleft], @font[:Encoding][:Differences][0, 4])
159
+ end
146
160
  end
147
161
  end
@@ -219,11 +219,21 @@ describe HexaPDF::Utils::SortedTreeNode do
219
219
  it "checks that leaf node containers have an even number of entries" do
220
220
  @kid11[:Names].delete_at(0)
221
221
  refute(@kid11.validate do |message, c|
222
- assert_match(/odd number/, message)
222
+ assert_match(/leaf.*odd number/, message)
223
223
  refute(c)
224
224
  end)
225
225
  end
226
226
 
227
+ it "corrects a root node container with an odd number of entries" do
228
+ @root.value.clear
229
+ @root[:Names] = ['Test']
230
+ assert(@root.validate do |message, c|
231
+ assert_match(/root.*odd number/, message)
232
+ assert(c)
233
+ end)
234
+ assert(@root[:Names].empty?)
235
+ end
236
+
227
237
  it "checks that the keys are of the correct type" do
228
238
  @kid11[:Names][2] = 5
229
239
  refute(@kid11.validate do |message, c|
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hexapdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-04-23 00:00:00.000000000 Z
10
+ date: 2025-09-23 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: cmdparse
@@ -514,8 +514,12 @@ files:
514
514
  - lib/hexapdf/type/annotations/circle.rb
515
515
  - lib/hexapdf/type/annotations/interior_color.rb
516
516
  - lib/hexapdf/type/annotations/line.rb
517
+ - lib/hexapdf/type/annotations/line_ending_styling.rb
517
518
  - lib/hexapdf/type/annotations/link.rb
518
519
  - lib/hexapdf/type/annotations/markup_annotation.rb
520
+ - lib/hexapdf/type/annotations/polygon.rb
521
+ - lib/hexapdf/type/annotations/polygon_polyline.rb
522
+ - lib/hexapdf/type/annotations/polyline.rb
519
523
  - lib/hexapdf/type/annotations/square.rb
520
524
  - lib/hexapdf/type/annotations/square_circle.rb
521
525
  - lib/hexapdf/type/annotations/text.rb
@@ -539,6 +543,7 @@ files:
539
543
  - lib/hexapdf/type/info.rb
540
544
  - lib/hexapdf/type/mark_information.rb
541
545
  - lib/hexapdf/type/marked_content_reference.rb
546
+ - lib/hexapdf/type/measure.rb
542
547
  - lib/hexapdf/type/metadata.rb
543
548
  - lib/hexapdf/type/names.rb
544
549
  - lib/hexapdf/type/namespace.rb
@@ -800,7 +805,9 @@ files:
800
805
  - test/hexapdf/type/annotations/test_border_styling.rb
801
806
  - test/hexapdf/type/annotations/test_interior_color.rb
802
807
  - test/hexapdf/type/annotations/test_line.rb
808
+ - test/hexapdf/type/annotations/test_line_ending_styling.rb
803
809
  - test/hexapdf/type/annotations/test_markup_annotation.rb
810
+ - test/hexapdf/type/annotations/test_polygon_polyline.rb
804
811
  - test/hexapdf/type/annotations/test_text.rb
805
812
  - test/hexapdf/type/annotations/test_widget.rb
806
813
  - test/hexapdf/type/test_annotation.rb