hexapdf 1.2.0 → 1.3.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/README.md +1 -1
  4. data/lib/hexapdf/cli/inspect.rb +13 -4
  5. data/lib/hexapdf/composer.rb +14 -0
  6. data/lib/hexapdf/configuration.rb +5 -0
  7. data/lib/hexapdf/document/annotations.rb +60 -2
  8. data/lib/hexapdf/document/layout.rb +45 -6
  9. data/lib/hexapdf/error.rb +11 -3
  10. data/lib/hexapdf/font/true_type/subsetter.rb +15 -2
  11. data/lib/hexapdf/layout/style.rb +101 -7
  12. data/lib/hexapdf/object.rb +2 -2
  13. data/lib/hexapdf/pdf_array.rb +25 -3
  14. data/lib/hexapdf/tokenizer.rb +4 -1
  15. data/lib/hexapdf/type/acro_form/appearance_generator.rb +57 -8
  16. data/lib/hexapdf/type/acro_form/field.rb +1 -0
  17. data/lib/hexapdf/type/acro_form/form.rb +7 -6
  18. data/lib/hexapdf/type/annotation.rb +12 -0
  19. data/lib/hexapdf/type/annotations/appearance_generator.rb +75 -0
  20. data/lib/hexapdf/type/annotations/border_effect.rb +99 -0
  21. data/lib/hexapdf/type/annotations/circle.rb +65 -0
  22. data/lib/hexapdf/type/annotations/interior_color.rb +84 -0
  23. data/lib/hexapdf/type/annotations/line.rb +4 -35
  24. data/lib/hexapdf/type/annotations/square.rb +65 -0
  25. data/lib/hexapdf/type/annotations/square_circle.rb +77 -0
  26. data/lib/hexapdf/type/annotations/widget.rb +50 -20
  27. data/lib/hexapdf/type/annotations.rb +5 -0
  28. data/lib/hexapdf/version.rb +1 -1
  29. data/test/hexapdf/document/test_annotations.rb +22 -0
  30. data/test/hexapdf/document/test_layout.rb +24 -2
  31. data/test/hexapdf/font/true_type/test_subsetter.rb +10 -0
  32. data/test/hexapdf/layout/test_style.rb +27 -2
  33. data/test/hexapdf/test_composer.rb +7 -0
  34. data/test/hexapdf/test_object.rb +1 -1
  35. data/test/hexapdf/test_pdf_array.rb +36 -3
  36. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +78 -3
  37. data/test/hexapdf/type/acro_form/test_button_field.rb +7 -6
  38. data/test/hexapdf/type/acro_form/test_field.rb +5 -0
  39. data/test/hexapdf/type/acro_form/test_form.rb +17 -1
  40. data/test/hexapdf/type/annotations/test_appearance_generator.rb +84 -0
  41. data/test/hexapdf/type/annotations/test_border_effect.rb +59 -0
  42. data/test/hexapdf/type/annotations/test_interior_color.rb +37 -0
  43. data/test/hexapdf/type/annotations/test_line.rb +0 -20
  44. data/test/hexapdf/type/annotations/test_widget.rb +35 -0
  45. metadata +9 -2
@@ -395,4 +395,88 @@ describe HexaPDF::Type::Annotations::AppearanceGenerator do
395
395
  end
396
396
  end
397
397
  end
398
+
399
+ describe "square/circle" do
400
+ before do
401
+ @square = @doc.add({Type: :Annot, Subtype: :Square, Rect: [100, 100, 200, 150], C: [0],
402
+ BS: {W: 2}})
403
+ @generator = HexaPDF::Type::Annotations::AppearanceGenerator.new(@square)
404
+ end
405
+
406
+ it "sets the print flag and unsets the hidden flag" do
407
+ @square.flag(:hidden)
408
+ @generator.create_appearance
409
+ assert(@square.flagged?(:print))
410
+ refute(@square.flagged?(:hidden))
411
+ end
412
+
413
+ it "creates the /RD entry if it doesn't exist and adjusts the /Rect" do
414
+ @generator.create_appearance
415
+ assert_equal([99, 99, 201, 151], @square[:Rect])
416
+ assert_equal([0, 0, 102, 52], @square.appearance[:BBox])
417
+ assert_operators(@square.appearance.stream,
418
+ [[:set_line_width, [2]],
419
+ [:append_rectangle, [1, 1, 100, 50]],
420
+ [:stroke_path]])
421
+ end
422
+
423
+ it "uses an existing /RD entry" do
424
+ @square[:RD] = [2, 4, 6, 8]
425
+ @generator.create_appearance
426
+ assert_equal([100, 100, 200, 150], @square[:Rect])
427
+ assert_equal([0, 0, 100, 50], @square.appearance[:BBox])
428
+ assert_operators(@square.appearance.stream,
429
+ [[:set_line_width, [2]],
430
+ [:append_rectangle, [3, 9, 90, 36]],
431
+ [:stroke_path]])
432
+ end
433
+
434
+ it "can apply just a fill color without a stroke color" do
435
+ @square.delete(:C)
436
+ @square.interior_color(255, 0, 0)
437
+ @generator.create_appearance
438
+ assert_operators(@square.appearance.stream,
439
+ [[:set_device_rgb_non_stroking_color, [1, 0, 0]],
440
+ [:set_line_width, [2]],
441
+ [:append_rectangle, [1, 1, 100, 50]],
442
+ [:fill_path_non_zero]])
443
+ end
444
+
445
+ it "applies all set styling options" do
446
+ @square.border_style(color: [255, 0, 0], width: 10, style: [2, 1])
447
+ @square.interior_color(0, 255, 0)
448
+ @square.opacity(fill_alpha: 0.5, stroke_alpha: 0.5)
449
+ @generator.create_appearance
450
+ assert_operators(@square.appearance.stream,
451
+ [[:set_graphics_state_parameters, [:GS1]],
452
+ [:set_device_rgb_stroking_color, [1, 0, 0]],
453
+ [:set_device_rgb_non_stroking_color, [0, 1, 0]],
454
+ [:set_line_width, [10]],
455
+ [:set_line_dash_pattern, [[2, 1], 0]],
456
+ [:append_rectangle, [5, 5, 100, 50]],
457
+ [:fill_and_stroke_path_non_zero]])
458
+ end
459
+
460
+ it "doesn't draw anything if neither stroke nor fill color is set" do
461
+ @square.delete(:C)
462
+ @generator.create_appearance
463
+ assert_operators(@square.appearance.stream, [])
464
+ end
465
+
466
+ it "draws an ellipse" do
467
+ @square[:Subtype] = :Circle
468
+ @generator.create_appearance
469
+ assert_operators(@square.appearance.stream,
470
+ [[:set_line_width, [2]],
471
+ [:move_to, [101.0, 26.0]],
472
+ [:curve_to, [101.0, 34.920552, 91.45085, 43.190359, 76.0, 47.650635]],
473
+ [:curve_to, [60.54915, 52.110911, 41.45085, 52.110911, 26.0, 47.650635]],
474
+ [:curve_to, [10.54915, 43.190359, 1.0, 34.920552, 1.0, 26.0]],
475
+ [:curve_to, [1.0, 17.079448, 10.54915, 8.809641, 26.0, 4.349365]],
476
+ [:curve_to, [41.45085, -0.110911, 60.54915, -0.110911, 76.0, 4.349365]],
477
+ [:curve_to, [91.45085, 8.809641, 101.0, 17.079448, 101.0, 26.0]],
478
+ [:close_subpath],
479
+ [:stroke_path]])
480
+ end
481
+ end
398
482
  end
@@ -0,0 +1,59 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/annotations/border_effect'
6
+
7
+ describe HexaPDF::Type::Annotations::BorderEffect do
8
+ class TestAnnot < HexaPDF::Type::Annotation
9
+ define_field :BE, type: :XXBorderEffect
10
+ include HexaPDF::Type::Annotations::BorderEffect
11
+ end
12
+
13
+ before do
14
+ @doc = HexaPDF::Document.new
15
+ @annot = @doc.wrap({Type: :Annot}, type: TestAnnot)
16
+ end
17
+
18
+ describe "border_effect" do
19
+ it "returns :none if no border effect is set" do
20
+ assert_equal(:none, @annot.border_effect)
21
+ @annot[:BE] = {}
22
+ assert_equal(:none, @annot.border_effect)
23
+ @annot[:BE] = {S: :S}
24
+ assert_equal(:none, @annot.border_effect)
25
+ @annot[:BE] = {S: :K}
26
+ assert_equal(:none, @annot.border_effect)
27
+ end
28
+
29
+ it "returns cloud(y|ier|iest) if /S is /C and depending on /I" do
30
+ @annot[:BE] = {S: :C}
31
+ assert_equal(:cloudy, @annot.border_effect)
32
+ @annot[:BE][:I] = 0
33
+ assert_equal(:cloudy, @annot.border_effect)
34
+ @annot[:BE][:I] = 1
35
+ assert_equal(:cloudier, @annot.border_effect)
36
+ @annot[:BE][:I] = 2
37
+ assert_equal(:cloudiest, @annot.border_effect)
38
+ @annot[:BE][:I] = 3
39
+ assert_equal(:cloudy, @annot.border_effect)
40
+ end
41
+
42
+ it "sets the /BE entry appropriately" do
43
+ @annot.border_effect(:none)
44
+ refute(@annot.key?(:BE))
45
+ @annot.border_effect(nil)
46
+ refute(@annot.key?(:BE))
47
+ @annot.border_effect(:cloudy)
48
+ assert_equal({S: :C, I: 0}, @annot[:BE])
49
+ @annot.border_effect(:cloudier)
50
+ assert_equal({S: :C, I: 1}, @annot[:BE])
51
+ @annot.border_effect(:cloudiest)
52
+ assert_equal({S: :C, I: 2}, @annot[:BE])
53
+ end
54
+
55
+ it "raises an error if the given type is unknown" do
56
+ assert_raises(ArgumentError) { @annot.border_effect(:unknown) }
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/annotations/interior_color'
6
+
7
+ describe HexaPDF::Type::Annotations::InteriorColor do
8
+ class TestAnnot < HexaPDF::Type::Annotation
9
+ define_field :IC, type: HexaPDF::PDFArray
10
+ include HexaPDF::Type::Annotations::InteriorColor
11
+ end
12
+
13
+ before do
14
+ @doc = HexaPDF::Document.new
15
+ @annot = @doc.wrap({Type: :Annot}, type: TestAnnot)
16
+ end
17
+
18
+ describe "interior_color" do
19
+ it "returns the interior color" do
20
+ assert_nil(@annot.interior_color)
21
+ @annot[:IC] = []
22
+ assert_nil(@annot.interior_color)
23
+ @annot[:IC] = [0.5]
24
+ assert_equal(HexaPDF::Content::ColorSpace.device_color_from_specification(0.5),
25
+ @annot.interior_color)
26
+ end
27
+
28
+ it "sets the interior color" do
29
+ @annot.interior_color(255)
30
+ assert_equal([1.0], @annot[:IC])
31
+ @annot.interior_color(255, 255, 0)
32
+ assert_equal([1.0, 1.0, 0], @annot[:IC])
33
+ @annot.interior_color(:transparent)
34
+ assert_equal([], @annot[:IC])
35
+ end
36
+ end
37
+ end
@@ -51,26 +51,6 @@ describe HexaPDF::Type::Annotations::Line do
51
51
  end
52
52
  end
53
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
54
  describe "leader_line_length" do
75
55
  it "returns the leader line length" do
76
56
  assert_equal(0, @line.leader_line_length)
@@ -98,6 +98,20 @@ describe HexaPDF::Type::Annotations::Widget do
98
98
  end
99
99
  end
100
100
 
101
+ it "uses the correct default style" do
102
+ @widget.form_field.initialize_as_check_box
103
+ @widget.marker_style(size: 10)
104
+ assert_equal('4', @widget[:MK][:CA])
105
+
106
+ @widget.form_field.initialize_as_radio_button
107
+ @widget.marker_style(size: 10)
108
+ assert_equal('l', @widget[:MK][:CA])
109
+
110
+ @widget.form_field.initialize_as_push_button
111
+ @widget.marker_style(size: 10)
112
+ assert_equal('', @widget[:MK][:CA])
113
+ end
114
+
101
115
  it "fails if an invalid argument is provided" do
102
116
  assert_raises(ArgumentError) { @widget.marker_style(style: 5) }
103
117
  end
@@ -144,5 +158,26 @@ describe HexaPDF::Type::Annotations::Widget do
144
158
  assert_equal([1, 0.2, 1, 1], @widget.marker_style.color.components)
145
159
  end
146
160
  end
161
+
162
+ describe "font_name" do
163
+ it "returns the font_name" do
164
+ @widget.form_field[:DA] = "/F1 15 Tf"
165
+ assert_equal(:F1, @widget.marker_style.font_name)
166
+ @widget[:DA] = "/F2 10 Tf"
167
+ assert_equal(:F2, @widget.marker_style.font_name)
168
+ end
169
+
170
+ it "returns nil if none is set" do
171
+ assert_nil(@widget.marker_style.font_name)
172
+ @widget.form_field[:DA] = "0.0 g"
173
+ assert_nil(@widget.marker_style.font_name)
174
+ end
175
+
176
+ it "sets the given font_name" do
177
+ @widget.form_field.initialize_as_push_button
178
+ @widget.marker_style(font_name: 'Helvetica', size: 10)
179
+ assert_equal('/F1 10 Tf 0.0 g', @widget[:DA])
180
+ end
181
+ end
147
182
  end
148
183
  end
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.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-02-10 00:00:00.000000000 Z
10
+ date: 2025-04-23 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: cmdparse
@@ -509,10 +509,15 @@ files:
509
509
  - lib/hexapdf/type/annotation.rb
510
510
  - lib/hexapdf/type/annotations.rb
511
511
  - lib/hexapdf/type/annotations/appearance_generator.rb
512
+ - lib/hexapdf/type/annotations/border_effect.rb
512
513
  - lib/hexapdf/type/annotations/border_styling.rb
514
+ - lib/hexapdf/type/annotations/circle.rb
515
+ - lib/hexapdf/type/annotations/interior_color.rb
513
516
  - lib/hexapdf/type/annotations/line.rb
514
517
  - lib/hexapdf/type/annotations/link.rb
515
518
  - lib/hexapdf/type/annotations/markup_annotation.rb
519
+ - lib/hexapdf/type/annotations/square.rb
520
+ - lib/hexapdf/type/annotations/square_circle.rb
516
521
  - lib/hexapdf/type/annotations/text.rb
517
522
  - lib/hexapdf/type/annotations/widget.rb
518
523
  - lib/hexapdf/type/catalog.rb
@@ -791,7 +796,9 @@ files:
791
796
  - test/hexapdf/type/actions/test_set_ocg_state.rb
792
797
  - test/hexapdf/type/actions/test_uri.rb
793
798
  - test/hexapdf/type/annotations/test_appearance_generator.rb
799
+ - test/hexapdf/type/annotations/test_border_effect.rb
794
800
  - test/hexapdf/type/annotations/test_border_styling.rb
801
+ - test/hexapdf/type/annotations/test_interior_color.rb
795
802
  - test/hexapdf/type/annotations/test_line.rb
796
803
  - test/hexapdf/type/annotations/test_markup_annotation.rb
797
804
  - test/hexapdf/type/annotations/test_text.rb