hexapdf 0.34.1 → 0.35.1

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