hexapdf 0.34.1 → 0.35.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -0
  3. data/examples/009-text_layouter_alignment.rb +7 -7
  4. data/examples/010-text_layouter_inline_boxes.rb +1 -1
  5. data/examples/011-text_layouter_line_wrapping.rb +2 -4
  6. data/examples/013-text_layouter_shapes.rb +9 -11
  7. data/examples/014-text_in_polygon.rb +2 -2
  8. data/examples/016-frame_automatic_box_placement.rb +6 -7
  9. data/examples/017-frame_text_flow.rb +2 -2
  10. data/examples/018-composer.rb +5 -6
  11. data/examples/020-column_box.rb +2 -2
  12. data/examples/021-list_box.rb +1 -1
  13. data/examples/027-composer_optional_content.rb +5 -5
  14. data/examples/028-frame_mask_mode.rb +23 -0
  15. data/examples/029-composer_fallback_fonts.rb +22 -0
  16. data/lib/hexapdf/cli/info.rb +1 -0
  17. data/lib/hexapdf/cli/inspect.rb +55 -2
  18. data/lib/hexapdf/composer.rb +4 -3
  19. data/lib/hexapdf/configuration.rb +61 -1
  20. data/lib/hexapdf/content/canvas.rb +63 -0
  21. data/lib/hexapdf/content/canvas_composer.rb +142 -0
  22. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +2 -8
  23. data/lib/hexapdf/content.rb +1 -0
  24. data/lib/hexapdf/dictionary.rb +14 -3
  25. data/lib/hexapdf/document/layout.rb +35 -13
  26. data/lib/hexapdf/document.rb +1 -0
  27. data/lib/hexapdf/encryption/standard_security_handler.rb +15 -0
  28. data/lib/hexapdf/error.rb +2 -1
  29. data/lib/hexapdf/font/invalid_glyph.rb +22 -6
  30. data/lib/hexapdf/font/true_type_wrapper.rb +48 -20
  31. data/lib/hexapdf/font/type1_wrapper.rb +48 -24
  32. data/lib/hexapdf/layout/box.rb +14 -10
  33. data/lib/hexapdf/layout/column_box.rb +5 -3
  34. data/lib/hexapdf/layout/frame.rb +78 -40
  35. data/lib/hexapdf/layout/image_box.rb +3 -3
  36. data/lib/hexapdf/layout/list_box.rb +20 -19
  37. data/lib/hexapdf/layout/style.rb +173 -68
  38. data/lib/hexapdf/layout/table_box.rb +3 -3
  39. data/lib/hexapdf/layout/text_box.rb +5 -5
  40. data/lib/hexapdf/layout/text_fragment.rb +50 -0
  41. data/lib/hexapdf/layout/text_layouter.rb +7 -6
  42. data/lib/hexapdf/layout/width_from_polygon.rb +1 -1
  43. data/lib/hexapdf/object.rb +5 -2
  44. data/lib/hexapdf/pdf_array.rb +5 -0
  45. data/lib/hexapdf/type/acro_form/appearance_generator.rb +16 -11
  46. data/lib/hexapdf/type/icon_fit.rb +2 -2
  47. data/lib/hexapdf/type/page.rb +35 -35
  48. data/lib/hexapdf/utils/sorted_tree_node.rb +0 -10
  49. data/lib/hexapdf/utils.rb +66 -0
  50. data/lib/hexapdf/version.rb +1 -1
  51. data/test/hexapdf/content/test_canvas.rb +37 -0
  52. data/test/hexapdf/content/test_canvas_composer.rb +112 -0
  53. data/test/hexapdf/document/test_layout.rb +40 -12
  54. data/test/hexapdf/document/test_pages.rb +5 -5
  55. data/test/hexapdf/encryption/test_standard_security_handler.rb +43 -0
  56. data/test/hexapdf/font/test_invalid_glyph.rb +13 -1
  57. data/test/hexapdf/font/test_true_type_wrapper.rb +15 -2
  58. data/test/hexapdf/font/test_type1_wrapper.rb +21 -2
  59. data/test/hexapdf/layout/test_box.rb +7 -0
  60. data/test/hexapdf/layout/test_column_box.rb +18 -4
  61. data/test/hexapdf/layout/test_frame.rb +181 -95
  62. data/test/hexapdf/layout/test_list_box.rb +7 -7
  63. data/test/hexapdf/layout/test_page_style.rb +5 -5
  64. data/test/hexapdf/layout/test_style.rb +14 -10
  65. data/test/hexapdf/layout/test_table_box.rb +3 -3
  66. data/test/hexapdf/layout/test_text_box.rb +2 -2
  67. data/test/hexapdf/layout/test_text_fragment.rb +37 -0
  68. data/test/hexapdf/layout/test_text_layouter.rb +10 -10
  69. data/test/hexapdf/test_composer.rb +13 -13
  70. data/test/hexapdf/test_configuration.rb +49 -0
  71. data/test/hexapdf/test_dictionary.rb +1 -1
  72. data/test/hexapdf/test_object.rb +13 -12
  73. data/test/hexapdf/test_pdf_array.rb +9 -0
  74. data/test/hexapdf/test_utils.rb +20 -0
  75. data/test/hexapdf/test_writer.rb +3 -3
  76. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +41 -13
  77. data/test/hexapdf/type/test_page.rb +3 -3
  78. data/test/hexapdf/type/test_page_tree_node.rb +1 -1
  79. data/test/hexapdf/utils/test_sorted_tree_node.rb +1 -1
  80. metadata +9 -3
@@ -34,6 +34,43 @@ describe HexaPDF::Layout::TextFragment do
34
34
  end
35
35
  end
36
36
 
37
+ describe "create_with_fallback_glyphs" do
38
+ it "creates an array with a single fragment object if no block is given" do
39
+ frags = HexaPDF::Layout::TextFragment.create_with_fallback_glyphs("Tom", font: @font, font_size: 20)
40
+ assert_equal(1, frags.size)
41
+ assert_equal(37.78, frags[0].width)
42
+ end
43
+
44
+ it "allows using a style object instead of directly specifying style properties" do
45
+ style = HexaPDF::Layout::Style.new(font: @font, font_size: 20)
46
+ frags = HexaPDF::Layout::TextFragment.create_with_fallback_glyphs("Tom", style)
47
+ assert_equal(37.78, frags[0].width)
48
+ end
49
+
50
+ it "replaces invalid glyphs with the result of the block" do
51
+ zapf_dingbats = @doc.fonts.add('ZapfDingbats')
52
+ i = 0
53
+ fallback = lambda do |codepoint, invalid_glyph|
54
+ case (i += 1) % 3
55
+ when 0 then []
56
+ when 1 then [zapf_dingbats.decode_codepoint(codepoint)]
57
+ when 2 then @font.decode_utf8("Tom")
58
+ end
59
+ end
60
+ style = HexaPDF::Layout::Style.new(font: @font, font_size: 20, font_features: {kern: true})
61
+
62
+ frags = HexaPDF::Layout::TextFragment.create_with_fallback_glyphs("✂Tom✂✂Tom✂", style, &fallback)
63
+ assert_equal(5, frags.size)
64
+ assert_equal(zapf_dingbats, frags[0].style.font)
65
+ assert_equal(:a2, frags[0].items[0].name)
66
+ assert_equal(@font, frags[2].style.font)
67
+
68
+ frags = HexaPDF::Layout::TextFragment.create_with_fallback_glyphs("Tom✂Tom", style, &fallback)
69
+ assert_equal(3, frags.size)
70
+ assert_equal(frags[0].width, frags[1].width)
71
+ end
72
+ end
73
+
37
74
  describe "initialize" do
38
75
  before do
39
76
  @items = @font.decode_utf8("Tom")
@@ -651,7 +651,7 @@ describe HexaPDF::Layout::TextLayouter do
651
651
  end
652
652
 
653
653
  it "aligns the contents to the left" do
654
- @style.align = :left
654
+ @style.text_align = :left
655
655
  result = @layouter.fit(@items, 100, 100)
656
656
  assert_equal(0, result.lines[0].x_offset)
657
657
  assert_equal(80, result.lines[0].width)
@@ -661,7 +661,7 @@ describe HexaPDF::Layout::TextLayouter do
661
661
  end
662
662
 
663
663
  it "aligns the contents to the center" do
664
- @style.align = :center
664
+ @style.text_align = :center
665
665
  result = @layouter.fit(@items, 100, 100)
666
666
  assert_equal(10, result.lines[0].x_offset)
667
667
  result = @layouter.fit(@items, proc { 100 }, 100)
@@ -669,7 +669,7 @@ describe HexaPDF::Layout::TextLayouter do
669
669
  end
670
670
 
671
671
  it "aligns the contents to the right" do
672
- @style.align = :right
672
+ @style.text_align = :right
673
673
  result = @layouter.fit(@items, 100, 100)
674
674
  assert_equal(20, result.lines[0].x_offset)
675
675
  result = @layouter.fit(@items, proc { 100 }, 100)
@@ -683,28 +683,28 @@ describe HexaPDF::Layout::TextLayouter do
683
683
  end
684
684
 
685
685
  it "aligns the contents to the top" do
686
- @style.valign = :top
686
+ @style.text_valign = :top
687
687
  result = @layouter.fit(@items, 40, 100)
688
688
  assert_equal(result.lines[0].y_max, result.lines[0].y_offset)
689
689
  assert_equal(40, result.height)
690
690
  end
691
691
 
692
692
  it "aligns the contents to the center" do
693
- @style.valign = :center
693
+ @style.text_valign = :center
694
694
  result = @layouter.fit(@items, 40, 100)
695
695
  assert_equal((100 - 40) / 2 + 20, result.lines[0].y_offset)
696
696
  assert_equal(70, result.height)
697
697
  end
698
698
 
699
699
  it "aligns the contents to the bottom" do
700
- @style.valign = :bottom
700
+ @style.text_valign = :bottom
701
701
  result = @layouter.fit(@items, 40, 100)
702
702
  assert_equal(100 - 20 * 2 + 20, result.lines[0].y_offset)
703
703
  assert_equal(100, result.height)
704
704
  end
705
705
 
706
706
  it "doesn't vertically align when layouting in variable-width mode" do
707
- @style.valign = :bottom
707
+ @style.text_valign = :bottom
708
708
  result = @layouter.fit(@items, proc { 40 }, 100)
709
709
  assert_equal(result.lines[0].y_max, result.lines[0].y_offset)
710
710
  assert_equal(40, result.height)
@@ -723,7 +723,7 @@ describe HexaPDF::Layout::TextLayouter do
723
723
  # Missing width: 100 - 90 = 10
724
724
  # -> Each space must be doubled!
725
725
 
726
- @style.align = :justify
726
+ @style.text_align = :justify
727
727
  result = @layouter.fit(items, 100, 100)
728
728
  assert(result.remaining_items.empty?)
729
729
  assert_equal(:success, result.status)
@@ -770,8 +770,8 @@ describe HexaPDF::Layout::TextLayouter do
770
770
 
771
771
  it "respects the x- and y-offsets" do
772
772
  top = 100
773
- @layouter.style.valign = :center
774
- @layouter.style.align = :center
773
+ @layouter.style.text_valign = :center
774
+ @layouter.style.text_align = :center
775
775
 
776
776
  result = @layouter.fit([@frag], @width, top)
777
777
  result.draw(@canvas, 5, top)
@@ -16,8 +16,8 @@ describe HexaPDF::Composer do
16
16
  assert_kind_of(HexaPDF::Type::Page, @composer.page)
17
17
  assert_equal(36, @composer.frame.left)
18
18
  assert_equal(36, @composer.frame.bottom)
19
- assert_equal(523, @composer.frame.width)
20
- assert_equal(770, @composer.frame.height)
19
+ assert_equal(523.275591, @composer.frame.width)
20
+ assert_equal(769.889764, @composer.frame.height)
21
21
  assert_kind_of(HexaPDF::Layout::Style, @composer.style(:base))
22
22
  end
23
23
 
@@ -28,15 +28,15 @@ describe HexaPDF::Composer do
28
28
 
29
29
  it "allows the customization of the page orientation" do
30
30
  composer = HexaPDF::Composer.new(page_orientation: :landscape)
31
- assert_equal([0, 0, 842, 595], composer.page.box.value)
31
+ assert_equal([0, 0, 841.889764, 595.275591], composer.page.box.value)
32
32
  end
33
33
 
34
34
  it "allows the customization of the margin" do
35
35
  composer = HexaPDF::Composer.new(margin: [100, 80, 60, 40])
36
36
  assert_equal(40, composer.frame.left)
37
37
  assert_equal(60, composer.frame.bottom)
38
- assert_equal(475, composer.frame.width)
39
- assert_equal(682, composer.frame.height)
38
+ assert_in_delta(475.275591, composer.frame.width)
39
+ assert_equal(681.889764, composer.frame.height)
40
40
  end
41
41
 
42
42
  it "allows skipping the initial page creation" do
@@ -107,7 +107,7 @@ describe HexaPDF::Composer do
107
107
  end
108
108
 
109
109
  it "returns the current y-position" do
110
- assert_equal(806, @composer.y)
110
+ assert_equal(805.889764, @composer.y)
111
111
  end
112
112
 
113
113
  describe "style" do
@@ -203,7 +203,7 @@ describe HexaPDF::Composer do
203
203
  @composer.draw_box(create_box)
204
204
  assert_operators(@composer.canvas.contents,
205
205
  [[:save_graphics_state],
206
- [:concatenate_matrix, [1, 0, 0, 1, 36, 706]],
206
+ [:concatenate_matrix, [1, 0, 0, 1, 36, 705.889764]],
207
207
  [:restore_graphics_state],
208
208
  [:save_graphics_state],
209
209
  [:concatenate_matrix, [1, 0, 0, 1, 36, 36]],
@@ -232,14 +232,14 @@ describe HexaPDF::Composer do
232
232
  @composer.draw_box(box)
233
233
  assert_operators(first_page_contents,
234
234
  [[:save_graphics_state],
235
- [:concatenate_matrix, [1, 0, 0, 1, 36, 406]],
235
+ [:concatenate_matrix, [1, 0, 0, 1, 36, 405.889764]],
236
236
  [:restore_graphics_state],
237
237
  [:save_graphics_state],
238
- [:concatenate_matrix, [1, 0, 0, 1, 36, 6]],
238
+ [:concatenate_matrix, [1, 0, 0, 1, 36, 5.889764]],
239
239
  [:restore_graphics_state]])
240
240
  assert_operators(@composer.canvas.contents,
241
241
  [[:save_graphics_state],
242
- [:concatenate_matrix, [1, 0, 0, 1, 36, 706]],
242
+ [:concatenate_matrix, [1, 0, 0, 1, 36, 705.889764]],
243
243
  [:restore_graphics_state]])
244
244
  end
245
245
 
@@ -252,14 +252,14 @@ describe HexaPDF::Composer do
252
252
  @composer.draw_box(box)
253
253
  assert_operators(first_page_contents,
254
254
  [[:save_graphics_state],
255
- [:concatenate_matrix, [1, 0, 0, 1, 36, 406]],
255
+ [:concatenate_matrix, [1, 0, 0, 1, 36, 405.889764]],
256
256
  [:restore_graphics_state],
257
257
  [:save_graphics_state],
258
- [:concatenate_matrix, [1, 0, 0, 1, 36, 306]],
258
+ [:concatenate_matrix, [1, 0, 0, 1, 36, 305.889764]],
259
259
  [:restore_graphics_state]])
260
260
  assert_operators(@composer.canvas.contents,
261
261
  [[:save_graphics_state],
262
- [:concatenate_matrix, [1, 0, 0, 1, 36, 406]],
262
+ [:concatenate_matrix, [1, 0, 0, 1, 36, 405.889764]],
263
263
  [:restore_graphics_state]])
264
264
  end
265
265
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'test_helper'
4
4
  require 'hexapdf/configuration'
5
+ require 'hexapdf/document'
5
6
 
6
7
  describe HexaPDF::Configuration do
7
8
  before do
@@ -91,3 +92,51 @@ describe HexaPDF::Configuration do
91
92
  end
92
93
  end
93
94
  end
95
+
96
+ describe "HexaPDF.font_on_invalid_glyph" do
97
+ before do
98
+ @doc = HexaPDF::Document.new
99
+ @font = @doc.fonts.add('Helvetica')
100
+ end
101
+
102
+ def assert_glyph(font, codepoint, font_name, glyph_name)
103
+ invalid_glyph = HexaPDF::Font::InvalidGlyph.new(font, codepoint.chr("UTF-8"))
104
+ glyph = HexaPDF.font_on_invalid_glyph(codepoint, invalid_glyph)
105
+
106
+ assert_equal(1, glyph.size)
107
+ glyph = glyph.first
108
+ assert(glyph.valid?)
109
+ assert_equal(font_name, glyph.font_wrapper.wrapped_font.font_name)
110
+ assert_equal(glyph_name, glyph.name)
111
+ glyph
112
+ end
113
+
114
+ it "tries each fallback font and uses the first valid glyph" do
115
+ assert_glyph(@font, 10102, "ZapfDingbats", :a130)
116
+ assert_glyph(@font, 8855, "Symbol", :circlemultiply)
117
+ end
118
+
119
+ it "takes the font variant into account" do
120
+ @doc.config['font.fallback'] = ['Times']
121
+
122
+ glyph = assert_glyph(@doc.fonts.add("Helvetica", variant: :bold), 65, "Times-Bold", :A)
123
+ assert(glyph.font_wrapper.bold?)
124
+
125
+ glyph = assert_glyph(@doc.fonts.add("Helvetica", variant: :italic), 65, "Times-Italic", :A)
126
+ assert(glyph.font_wrapper.italic?)
127
+
128
+ glyph = assert_glyph(@doc.fonts.add("Helvetica", variant: :bold_italic), 65, "Times-BoldItalic", :A)
129
+ assert(glyph.font_wrapper.bold?)
130
+ assert(glyph.font_wrapper.italic?)
131
+ end
132
+
133
+ it "falls back to the :none variant of a fallback font if a more specific one doesn't exist" do
134
+ assert_glyph(@doc.fonts.add("Helvetica", variant: :bold), 9985, "ZapfDingbats", :a1)
135
+ end
136
+
137
+ it "returns the given invalid glyph if no fallback glyph could be found" do
138
+ @doc.config['font.fallback'] = []
139
+ invalid_glyph = HexaPDF::Font::InvalidGlyph.new(@font, "A")
140
+ assert_equal([invalid_glyph], HexaPDF.font_on_invalid_glyph(65, invalid_glyph))
141
+ end
142
+ end
@@ -204,7 +204,7 @@ describe HexaPDF::Dictionary do
204
204
  end
205
205
  end
206
206
 
207
- describe "validate_fields" do
207
+ describe "perform_validation" do
208
208
  before do
209
209
  @test_class.define_field(:Inherited, type: [Array, Symbol], required: true, indirect: false)
210
210
  @test_class.define_field(:AllowedValues, type: Integer, allowed_values: [1, 5])
@@ -186,6 +186,19 @@ describe HexaPDF::Object do
186
186
  refute(@obj.validate(auto_correct: false))
187
187
  assert_equal([:before], invoked)
188
188
  end
189
+
190
+ it "re-raises caught HexaPDF::Error exceptions" do
191
+ @obj.define_singleton_method(:perform_validation) { raise HexaPDF::Error, "Unknown" }
192
+ invoked = []
193
+ assert_raises(HexaPDF::Error) { @obj.validate {|*a| invoked << a } }
194
+ end
195
+
196
+ it "catches errors raised in perform_validation and produces an appropriate message" do
197
+ @obj.define_singleton_method(:perform_validation) { raise "Unknown" }
198
+ invoked = []
199
+ refute(@obj.validate {|*a| invoked << a })
200
+ assert_equal([["Error: Unexpected value encountered", false, @obj]], invoked)
201
+ end
189
202
  end
190
203
 
191
204
  it "can represent itself during inspection" do
@@ -286,17 +299,5 @@ describe HexaPDF::Object do
286
299
  obj.validate
287
300
  assert_equal(1, obj.oid)
288
301
  end
289
-
290
- it "validates collection values" do
291
- x = HexaPDF::Object.new(:x, document: @doc)
292
- x.must_be_indirect = true
293
- y = HexaPDF::Object.new(:y, document: @doc)
294
- y.must_be_indirect = true
295
-
296
- obj = HexaPDF::Object.new([1, 2, 3, [4, x], {X: [y, 5]}])
297
- obj.validate
298
- assert_equal(1, x.oid)
299
- assert_equal(1, y.oid)
300
- end
301
302
  end
302
303
  end
@@ -166,4 +166,13 @@ describe HexaPDF::PDFArray do
166
166
  it "can be converted to a simple array" do
167
167
  assert_equal([1, :data, "deref", @array[3]], @array.to_ary)
168
168
  end
169
+
170
+ describe "perform_validation" do
171
+ it "validates nested objects" do
172
+ @array.value << HexaPDF::PDFArray.new([HexaPDF::Reference.new(1, 0)], document: self)
173
+ assert(@array.validate)
174
+ assert_kind_of(HexaPDF::Object, @array.value[2])
175
+ assert_kind_of(HexaPDF::Object, @array.value.last.value[0])
176
+ end
177
+ end
169
178
  end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/utils.rb'
5
+
6
+ describe HexaPDF::Utils do
7
+ include HexaPDF::Utils
8
+
9
+ it "checks floats for equality with a certain precision" do
10
+ assert(float_equal(1.0, 1))
11
+ assert(float_equal(1.0, 1.0000003))
12
+ end
13
+
14
+ it "compares floats like the <=> operator" do
15
+ assert_equal(0, float_compare(1.0, 1))
16
+ assert_equal(0, float_compare(1.0, 1.0000003))
17
+ assert_equal(-1, float_compare(1.0, 1.03))
18
+ assert_equal(1, float_compare(1.0, 0.9997))
19
+ end
20
+ end
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.34.1)>>
43
+ <</Producer(HexaPDF version 0.35.1)>>
44
44
  endobj
45
45
  xref
46
46
  3 1
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
72
72
  141
73
73
  %%EOF
74
74
  6 0 obj
75
- <</Producer(HexaPDF version 0.34.1)>>
75
+ <</Producer(HexaPDF version 0.35.1)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -214,7 +214,7 @@ describe HexaPDF::Writer do
214
214
  <</Type/Page/MediaBox[0 0 595 842]/Parent 2 0 R/Resources<<>>>>
215
215
  endobj
216
216
  5 0 obj
217
- <</Producer(HexaPDF version 0.34.1)>>
217
+ <</Producer(HexaPDF version 0.35.1)>>
218
218
  endobj
219
219
  4 0 obj
220
220
  <</Root 1 0 R/Info 5 0 R/Size 6/Type/XRef/W[1 1 2]/Index[0 6]/Filter/FlateDecode/DecodeParms<</Columns 4/Predictor 12>>/Length 33>>stream
@@ -453,7 +453,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
453
453
  before do
454
454
  @widget[:Rect].height = 20
455
455
  @widget[:Rect].width = 100
456
- @field.field_value = ''
456
+ @field[:V] = ''
457
457
  end
458
458
 
459
459
  it "uses the non-zero font size" do
@@ -477,7 +477,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
477
477
  end
478
478
 
479
479
  it "shrinks the font size if necessary to fit the rectangle width" do
480
- @field.field_value = "This is some arbitrary, long text"
480
+ @field[:V] = "This is some arbitrary, long text"
481
481
  @generator.create_appearances
482
482
  assert_operators(@widget[:AP][:N].stream,
483
483
  [:set_font_and_size, [:F1, 6.909955]],
@@ -498,7 +498,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
498
498
  describe "single line text fields" do
499
499
  describe "quadding e.g. text alignment" do
500
500
  before do
501
- @field.field_value = 'Test'
501
+ @field[:V] = 'Test'
502
502
  @field.set_default_appearance_string(font_size: 10)
503
503
  @widget[:Rect].height = 20
504
504
  end
@@ -551,7 +551,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
551
551
 
552
552
  describe "Javascript action AFNumber_Format" do
553
553
  before do
554
- @field.field_value = '1234567.898765'
554
+ @field[:V] = '1234567.898765'
555
555
  @action = {S: :JavaScript, JS: ''}
556
556
  @field[:AA] = {F: @action}
557
557
  end
@@ -576,7 +576,8 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
576
576
  end
577
577
 
578
578
  it "respects the negative value styling" do
579
- @field.field_value = '-1234567.898'
579
+ @field[:V] = '-1234567.898'
580
+ @widget[:Rect].height = 11.25
580
581
  ["-E1234567,90", "E1234567,90", "(E1234567,90)", "(E1234567,90)"].each_with_index do |result, style|
581
582
  assert_format("2, 3, #{style}, 0, \"E\", true",
582
583
  [[:set_device_rgb_non_stroking_color, [style % 2, 0.0, 0.0]],
@@ -597,8 +598,11 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
597
598
  end
598
599
 
599
600
  it "creates the /N appearance stream according to the set string" do
600
- @field.field_value = 'Text'
601
+ @doc.config['font.on_invalid_glyph'] = lambda do |codepoint, _|
602
+ [@doc.fonts.add('ZapfDingbats').decode_codepoint(codepoint)]
603
+ end
601
604
  @field.set_default_appearance_string(font_color: "red")
605
+ @field[:V] = 'Te ✂ xt'
602
606
  @generator.create_appearances
603
607
  assert_operators(@widget[:AP][:N].stream,
604
608
  [[:begin_marked_content, [:Tx]],
@@ -606,11 +610,17 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
606
610
  [:append_rectangle, [1, 1, 98, 9.25]],
607
611
  [:clip_path_non_zero],
608
612
  [:end_path],
609
- [:set_font_and_size, [:F1, 6.801471]],
613
+ [:set_font_and_size, [:F1, 10]],
610
614
  [:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
611
615
  [:begin_text],
612
- [:set_text_matrix, [1, 0, 0, 1, 2, 3.183272]],
613
- [:show_text, ["Text"]],
616
+ [:set_text_matrix, [1, 0, 0, 1, 2, 2.035]],
617
+ [:show_text, ["Te "]],
618
+ [:set_font_and_size, [:F2, 10]],
619
+ [:move_text, [14.45, 0]],
620
+ [:show_text, ["\""]],
621
+ [:set_font_and_size, [:F1, 10]],
622
+ [:move_text, [9.61, 0]],
623
+ [:show_text, [" xt"]],
614
624
  [:end_text],
615
625
  [:restore_graphics_state],
616
626
  [:end_marked_content]])
@@ -656,7 +666,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
656
666
  end
657
667
 
658
668
  it "creates the /N appearance stream according to the set string" do
659
- @field.field_value = "Test\nValue"
669
+ @field[:V] = "Test\nValue"
660
670
  @field.set_default_appearance_string(font_size: 10, font_color: "red")
661
671
  @generator.create_appearances
662
672
  assert_operators(@widget[:AP][:N].stream,
@@ -679,7 +689,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
679
689
  [:restore_graphics_state],
680
690
  [:end_marked_content]])
681
691
 
682
- @field.field_value = "Test\nTest\nTest"
692
+ @field[:V] = "Test\nTest\nTest"
683
693
  @field.set_default_appearance_string(font_size: 0)
684
694
  @generator.create_appearances
685
695
  assert_operators(@widget[:AP][:N].stream,
@@ -744,7 +754,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
744
754
  end
745
755
 
746
756
  it "handles centering like Adobe, e.g. shift left, when text cannot be completely centered" do
747
- @field.field_value = 'Hello'
757
+ @field[:V] = 'Hello'
748
758
  @field.text_alignment(:center)
749
759
  @generator.create_appearances
750
760
  assert_operators(@widget[:AP][:N].stream,
@@ -754,7 +764,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
754
764
  end
755
765
 
756
766
  it "creates the /N appearance stream according to the set string" do
757
- @field.field_value = 'Text'
767
+ @field[:V] = 'Text'
758
768
  @field.set_default_appearance_string(font_size: 10, font_color: "red")
759
769
  @generator.create_appearances
760
770
  assert_operators(@widget[:AP][:N].stream,
@@ -773,11 +783,29 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
773
783
  [:end_marked_content]])
774
784
  end
775
785
 
786
+ it "works for empty strings" do
787
+ @field[:V] = ''
788
+ @generator.create_appearances
789
+ assert_operators(@widget[:AP][:N].stream,
790
+ [[:begin_text],
791
+ [:set_text_matrix, [1, 0, 0, 1, 2, 6.41]],
792
+ [:end_text]], range: 6..8)
793
+ end
794
+
776
795
  it "fails if the /MaxLen key is not set" do
777
796
  @field.delete(:MaxLen)
778
797
  @field[:V] = 't'
779
798
  assert_raises(HexaPDF::Error) { @generator.create_appearances }
780
799
  end
800
+
801
+ it "fails if fallback glyphs are needed for rendering" do
802
+ @doc.config['font.on_invalid_glyph'] = lambda do |codepoint, _|
803
+ [@doc.fonts.add('ZapfDingbats').decode_codepoint(codepoint)]
804
+ end
805
+ @field[:V] = 'Test ✂'
806
+ ex = assert_raises(HexaPDF::Error) { @generator.create_appearances }
807
+ assert_match(/Fallback glyphs/, ex.message)
808
+ end
781
809
  end
782
810
 
783
811
  describe "choice fields" do
@@ -12,11 +12,11 @@ describe HexaPDF::Type::Page do
12
12
 
13
13
  describe "::media_box" do
14
14
  it "returns the media box for a given paper size" do
15
- assert_equal([0, 0, 595, 842], HexaPDF::Type::Page.media_box(:A4))
15
+ assert_equal([0, 0, 595.275591, 841.889764], HexaPDF::Type::Page.media_box(:A4))
16
16
  end
17
17
 
18
18
  it "respects the orientation key" do
19
- assert_equal([0, 0, 842, 595], HexaPDF::Type::Page.media_box(:A4, orientation: :landscape))
19
+ assert_equal([0, 0, 841.889764, 595.275591], HexaPDF::Type::Page.media_box(:A4, orientation: :landscape))
20
20
  end
21
21
 
22
22
  it "works with a paper size array" do
@@ -116,7 +116,7 @@ describe HexaPDF::Type::Page do
116
116
  page1[:MediaBox] = [0, 0, 1, 1]
117
117
  refute(page2.validate(auto_correct: false))
118
118
  assert(page2.validate)
119
- assert_equal([0, 0, 595, 842], page2[:MediaBox])
119
+ assert_equal([0, 0, 595.275591, 841.889764], page2[:MediaBox])
120
120
  end
121
121
  end
122
122
 
@@ -89,7 +89,7 @@ describe HexaPDF::Type::PageTreeNode do
89
89
  assert_equal(:Page, page[:Type])
90
90
  assert_equal(@root, page[:Parent])
91
91
  assert_kind_of(HexaPDF::Rectangle, page[:MediaBox])
92
- assert_equal([0, 0, 842, 595], page[:MediaBox].value)
92
+ assert_equal([0, 0, 841.889764, 595.275591], page[:MediaBox].value)
93
93
  assert_equal({}, page[:Resources].value)
94
94
  refute(@root.value.key?(:Parent))
95
95
  end
@@ -209,7 +209,7 @@ describe HexaPDF::Utils::SortedTreeNode do
209
209
  @root[:Kids][0] = ref
210
210
  @kid1.oid = 0
211
211
  assert(@root.validate do |message, c|
212
- assert_match(/must be indirect objects/, message)
212
+ assert_match(/must be an indirect object/, message)
213
213
  assert(c)
214
214
  end)
215
215
  assert(@kid1.indirect?)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hexapdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.34.1
4
+ version: 0.35.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-01 00:00:00.000000000 Z
11
+ date: 2024-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmdparse
@@ -301,6 +301,8 @@ files:
301
301
  - examples/025-table_box.rb
302
302
  - examples/026-optional_content.rb
303
303
  - examples/027-composer_optional_content.rb
304
+ - examples/028-frame_mask_mode.rb
305
+ - examples/029-composer_fallback_fonts.rb
304
306
  - examples/emoji-smile.png
305
307
  - examples/emoji-wink.png
306
308
  - examples/machupicchu.jpg
@@ -324,6 +326,7 @@ files:
324
326
  - lib/hexapdf/configuration.rb
325
327
  - lib/hexapdf/content.rb
326
328
  - lib/hexapdf/content/canvas.rb
329
+ - lib/hexapdf/content/canvas_composer.rb
327
330
  - lib/hexapdf/content/color_space.rb
328
331
  - lib/hexapdf/content/graphic_object.rb
329
332
  - lib/hexapdf/content/graphic_object/arc.rb
@@ -516,6 +519,7 @@ files:
516
519
  - lib/hexapdf/type/trailer.rb
517
520
  - lib/hexapdf/type/viewer_preferences.rb
518
521
  - lib/hexapdf/type/xref_stream.rb
522
+ - lib/hexapdf/utils.rb
519
523
  - lib/hexapdf/utils/bit_field.rb
520
524
  - lib/hexapdf/utils/bit_stream.rb
521
525
  - lib/hexapdf/utils/graphics_helpers.rb
@@ -603,6 +607,7 @@ files:
603
607
  - test/hexapdf/content/graphic_object/test_geom2d.rb
604
608
  - test/hexapdf/content/graphic_object/test_solid_arc.rb
605
609
  - test/hexapdf/content/test_canvas.rb
610
+ - test/hexapdf/content/test_canvas_composer.rb
606
611
  - test/hexapdf/content/test_color_space.rb
607
612
  - test/hexapdf/content/test_graphics_state.rb
608
613
  - test/hexapdf/content/test_operator.rb
@@ -724,6 +729,7 @@ files:
724
729
  - test/hexapdf/test_stream.rb
725
730
  - test/hexapdf/test_tokenizer.rb
726
731
  - test/hexapdf/test_type.rb
732
+ - test/hexapdf/test_utils.rb
727
733
  - test/hexapdf/test_writer.rb
728
734
  - test/hexapdf/test_xref_section.rb
729
735
  - test/hexapdf/type/acro_form/test_appearance_generator.rb
@@ -795,7 +801,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
795
801
  - !ruby/object:Gem::Version
796
802
  version: '0'
797
803
  requirements: []
798
- rubygems_version: 3.4.10
804
+ rubygems_version: 3.5.3
799
805
  signing_key:
800
806
  specification_version: 4
801
807
  summary: HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby