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
@@ -62,41 +62,41 @@ module HexaPDF
62
62
  # * ISO sizes: A0x4, A0x2, A0-A10, B0-B10, C0-C10
63
63
  # * Letter, Legal, Ledger, Tabloid, Executive
64
64
  PAPER_SIZE = {
65
- A0x4: [0, 0, 4768, 6741].freeze,
66
- A0x2: [0, 0, 3370, 4768].freeze,
67
- A0: [0, 0, 2384, 3370].freeze,
68
- A1: [0, 0, 1684, 2384].freeze,
69
- A2: [0, 0, 1191, 1684].freeze,
70
- A3: [0, 0, 842, 1191].freeze,
71
- A4: [0, 0, 595, 842].freeze,
72
- A5: [0, 0, 420, 595].freeze,
73
- A6: [0, 0, 298, 420].freeze,
74
- A7: [0, 0, 210, 298].freeze,
75
- A8: [0, 0, 147, 210].freeze,
76
- A9: [0, 0, 105, 147].freeze,
77
- A10: [0, 0, 74, 105].freeze,
78
- B0: [0, 0, 2835, 4008].freeze,
79
- B1: [0, 0, 2004, 2835].freeze,
80
- B2: [0, 0, 1417, 2004].freeze,
81
- B3: [0, 0, 1001, 1417].freeze,
82
- B4: [0, 0, 709, 1001].freeze,
83
- B5: [0, 0, 499, 709].freeze,
84
- B6: [0, 0, 354, 499].freeze,
85
- B7: [0, 0, 249, 354].freeze,
86
- B8: [0, 0, 176, 249].freeze,
87
- B9: [0, 0, 125, 176].freeze,
88
- B10: [0, 0, 88, 125].freeze,
89
- C0: [0, 0, 2599, 3677].freeze,
90
- C1: [0, 0, 1837, 2599].freeze,
91
- C2: [0, 0, 1298, 1837].freeze,
92
- C3: [0, 0, 918, 1298].freeze,
93
- C4: [0, 0, 649, 918].freeze,
94
- C5: [0, 0, 459, 649].freeze,
95
- C6: [0, 0, 323, 459].freeze,
96
- C7: [0, 0, 230, 323].freeze,
97
- C8: [0, 0, 162, 230].freeze,
98
- C9: [0, 0, 113, 162].freeze,
99
- C10: [0, 0, 79, 113].freeze,
65
+ A0x4: [0, 0, 4767.874016, 6740.787402].freeze,
66
+ A0x2: [0, 0, 3370.393701, 4767.874016].freeze,
67
+ A0: [0, 0, 2383.937008, 3370.393701].freeze,
68
+ A1: [0, 0, 1683.779528, 2383.937008].freeze,
69
+ A2: [0, 0, 1190.551181, 1683.779528].freeze,
70
+ A3: [0, 0, 841.889764, 1190.551181].freeze,
71
+ A4: [0, 0, 595.275591, 841.889764].freeze,
72
+ A5: [0, 0, 419.527559, 595.275591].freeze,
73
+ A6: [0, 0, 297.637795, 419.527559].freeze,
74
+ A7: [0, 0, 209.76378, 297.637795].freeze,
75
+ A8: [0, 0, 147.401575, 209.76378].freeze,
76
+ A9: [0, 0, 104.88189, 147.401575].freeze,
77
+ A10: [0, 0, 73.700787, 104.88189].freeze,
78
+ B0: [0, 0, 2834.645669, 4008.188976].freeze,
79
+ B1: [0, 0, 2004.094488, 2834.645669].freeze,
80
+ B2: [0, 0, 1417.322835, 2004.094488].freeze,
81
+ B3: [0, 0, 1000.629921, 1417.322835].freeze,
82
+ B4: [0, 0, 708.661417, 1000.629921].freeze,
83
+ B5: [0, 0, 498.897638, 708.661417].freeze,
84
+ B6: [0, 0, 354.330709, 498.897638].freeze,
85
+ B7: [0, 0, 249.448819, 354.330709].freeze,
86
+ B8: [0, 0, 175.748031, 249.448819].freeze,
87
+ B9: [0, 0, 124.724409, 175.748031].freeze,
88
+ B10: [0, 0, 87.874016, 124.724409].freeze,
89
+ C0: [0, 0, 2599.370079, 3676.535433].freeze,
90
+ C1: [0, 0, 1836.850394, 2599.370079].freeze,
91
+ C2: [0, 0, 1298.267717, 1836.850394].freeze,
92
+ C3: [0, 0, 918.425197, 1298.267717].freeze,
93
+ C4: [0, 0, 649.133858, 918.425197].freeze,
94
+ C5: [0, 0, 459.212598, 649.133858].freeze,
95
+ C6: [0, 0, 323.149606, 459.212598].freeze,
96
+ C7: [0, 0, 229.606299, 323.149606].freeze,
97
+ C8: [0, 0, 161.574803, 229.606299].freeze,
98
+ C9: [0, 0, 113.385827, 161.574803].freeze,
99
+ C10: [0, 0, 79.370079, 113.385827].freeze,
100
100
  Letter: [0, 0, 612, 792].freeze,
101
101
  Legal: [0, 0, 612, 1008].freeze,
102
102
  Ledger: [0, 0, 792, 1224].freeze,
@@ -307,16 +307,6 @@ module HexaPDF
307
307
  super
308
308
  container_name = leaf_node_container_name
309
309
 
310
- # All kids entries must be indirect objects
311
- if key?(:Kids)
312
- self[:Kids].each_with_index do |kid, index|
313
- unless kid.kind_of?(HexaPDF::Object) && kid.indirect?
314
- yield("Child entries of sorted tree nodes must be indirect objects", true)
315
- value[:Kids][index] = document.add(kid)
316
- end
317
- end
318
- end
319
-
320
310
  # All keys of the container must be lexically ordered strings and the container must be
321
311
  # correctly formatted
322
312
  if key?(container_name)
@@ -0,0 +1,66 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2023 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'geom2d/utils'
38
+
39
+ module HexaPDF
40
+ module Utils
41
+
42
+ # The precision with which to compare floating point numbers.
43
+ #
44
+ # This is chosen with respect to precision that is used for serializing floating point numbers.
45
+ EPSILON = 1e-6
46
+
47
+ # Best effort of setting Geom2D's precision to the one used by HexaPDF.
48
+ ::Geom2D::Utils.precision = EPSILON
49
+
50
+ module_function
51
+
52
+ private
53
+
54
+ # Compares two floats whether they are equal using the FLOAT_EPSILON precision.
55
+ def float_equal(a, b)
56
+ (a - b).abs < EPSILON
57
+ end
58
+
59
+ # Compares two floats like the <=> operator but using the FLOAT_EPSILON precision for detecting
60
+ # whether they are equal.
61
+ def float_compare(a, b)
62
+ (a - b).abs < EPSILON ? 0 : a <=> b
63
+ end
64
+
65
+ end
66
+ end
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '0.34.1'
40
+ VERSION = '0.35.1'
41
41
 
42
42
  end
@@ -756,6 +756,28 @@ describe HexaPDF::Content::Canvas do
756
756
  end
757
757
  end
758
758
 
759
+ describe "form" do
760
+ it "uses the context dimensions if none are given" do
761
+ form = @canvas.form
762
+ assert_equal(@canvas.context.box.value, form.box.value)
763
+ end
764
+
765
+ it "uses the provided dimensions" do
766
+ form = @canvas.form(300, 200)
767
+ assert_equal([0, 0, 300, 200], form.box.value)
768
+ end
769
+
770
+ it "yields the canvas for defining the form's content" do
771
+ yielded_canvas = nil
772
+ form = @canvas.form {|canvas| yielded_canvas = canvas }
773
+ assert_equal(form.canvas, yielded_canvas)
774
+ end
775
+
776
+ it "raises an ArgumentError if only one of width/height is provided" do
777
+ assert_raises(ArgumentError) { @canvas.form(20) }
778
+ end
779
+ end
780
+
759
781
  describe "graphic_object" do
760
782
  it "returns a new graphic object given a name" do
761
783
  arc = @canvas.graphic_object(:arc)
@@ -1329,6 +1351,21 @@ describe HexaPDF::Content::Canvas do
1329
1351
  end
1330
1352
  end
1331
1353
 
1354
+ describe "composer" do
1355
+ it "creates a CanvasComposer, yields it and returns it" do
1356
+ comp1 = nil
1357
+ comp2 = @canvas.composer {|composer| comp1 = composer }
1358
+ assert_kind_of(HexaPDF::Content::CanvasComposer, comp1)
1359
+ assert_same(comp1, comp2)
1360
+ assert_same(@canvas, comp1.canvas)
1361
+ end
1362
+
1363
+ it "passes on the margin argument" do
1364
+ comp = @canvas.composer(margin: 20)
1365
+ assert_equal(20, comp.frame.x)
1366
+ end
1367
+ end
1368
+
1332
1369
  describe "color_from_specification "do
1333
1370
  it "accepts a color string" do
1334
1371
  assert_equal([1, 0, 0], @canvas.color_from_specification("red").components)
@@ -0,0 +1,112 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/content/canvas_composer'
5
+ require 'hexapdf/document'
6
+
7
+ describe HexaPDF::Content::CanvasComposer do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @page = @doc.pages.add
11
+ @canvas = @page.canvas
12
+ @composer = @canvas.composer
13
+ end
14
+
15
+ describe "initialize" do
16
+ it "creates the necessary objects like frame for doing the work" do
17
+ assert_equal(@page.box.width, @composer.frame.width)
18
+ assert_equal(@page.box.height, @composer.frame.height)
19
+ end
20
+
21
+ it 'allows specifying a value for the margin' do
22
+ composer = @canvas.composer(margin: [10, 30])
23
+ assert_equal(@page.box.width - 60, composer.frame.width)
24
+ assert_equal(@page.box.height - 20, composer.frame.height)
25
+ end
26
+ end
27
+
28
+ it "provides easy access to the global styles" do
29
+ assert_same(@doc.layout.style(:base), @composer.style(:base))
30
+ end
31
+
32
+ describe "draw_box" do
33
+ def create_box(**kwargs)
34
+ HexaPDF::Layout::Box.new(**kwargs) {}
35
+ end
36
+
37
+ it "draws the box if it completely fits" do
38
+ @composer.draw_box(create_box(height: 100))
39
+ @composer.draw_box(create_box)
40
+ assert_operators(@composer.canvas.contents,
41
+ [[:save_graphics_state],
42
+ [:concatenate_matrix, [1, 0, 0, 1, 0, 741.889764]],
43
+ [:restore_graphics_state],
44
+ [:save_graphics_state],
45
+ [:concatenate_matrix, [1, 0, 0, 1, 0, 0]],
46
+ [:restore_graphics_state]])
47
+ end
48
+
49
+ it "splits the box if possible" do
50
+ @composer.draw_box(create_box(width: 400, style: {position: :float}))
51
+ box = create_box(width: 400, height: 100)
52
+ box.define_singleton_method(:split) do |*|
53
+ [box, HexaPDF::Layout::Box.new(height: 100) {}]
54
+ end
55
+ @composer.draw_box(box)
56
+ assert_operators(@composer.canvas.contents,
57
+ [[:save_graphics_state],
58
+ [:concatenate_matrix, [1, 0, 0, 1, 0, 0]],
59
+ [:restore_graphics_state],
60
+ [:save_graphics_state],
61
+ [:concatenate_matrix, [1, 0, 0, 1, 400, 741.889764]],
62
+ [:restore_graphics_state],
63
+ [:save_graphics_state],
64
+ [:concatenate_matrix, [1, 0, 0, 1, 400, 641.889764]],
65
+ [:restore_graphics_state]])
66
+ end
67
+
68
+ it "finds a new region if splitting doesn't work" do
69
+ @composer.draw_box(create_box(width: 400, height: 100, style: {position: :float}))
70
+ @composer.draw_box(create_box(width: 400, height: 100))
71
+ assert_operators(@composer.canvas.contents,
72
+ [[:save_graphics_state],
73
+ [:concatenate_matrix, [1, 0, 0, 1, 0, 741.889764]],
74
+ [:restore_graphics_state],
75
+ [:save_graphics_state],
76
+ [:concatenate_matrix, [1, 0, 0, 1, 0, 641.889764]],
77
+ [:restore_graphics_state]])
78
+ end
79
+
80
+ it "returns the last drawn box" do
81
+ box = create_box(height: 400)
82
+ assert_same(box, @composer.draw_box(box))
83
+ end
84
+
85
+ it "raises an error if the frame is full" do
86
+ @composer.draw_box(create_box)
87
+ exception = assert_raises(HexaPDF::Error) { @composer.draw_box(create_box(height: 10)) }
88
+ assert_match(/Frame.*full/, exception.message)
89
+ end
90
+
91
+ it "raises an error if a new region cannot be found after splitting" do
92
+ @composer.draw_box(create_box(height: 400))
93
+ exception = assert_raises(HexaPDF::Error) { @composer.draw_box(create_box(height: 500)) }
94
+ assert_match(/Frame.*full/, exception.message)
95
+ end
96
+ end
97
+
98
+ describe "method_missing" do
99
+ it "delegates box methods to @document.layout" do
100
+ box = @composer.column(width: 100)
101
+ assert_equal(100, box.width)
102
+ end
103
+
104
+ it "fails for missing methods that can't be delegated to @document.layout" do
105
+ assert_raises(NameError) { @composer.unknown_box }
106
+ end
107
+ end
108
+
109
+ it "can be asked whether a missing method is supported" do
110
+ assert(@composer.respond_to?(:column))
111
+ end
112
+ end
@@ -110,6 +110,9 @@ end
110
110
  describe HexaPDF::Document::Layout do
111
111
  before do
112
112
  @doc = HexaPDF::Document.new
113
+ @doc.config['font.on_invalid_glyph'] = lambda do |codepoint, invalid_glyph|
114
+ [@doc.fonts.add('ZapfDingbats').decode_codepoint(codepoint)]
115
+ end
113
116
  @layout = @doc.layout
114
117
  end
115
118
 
@@ -177,14 +180,37 @@ describe HexaPDF::Document::Layout do
177
180
  end
178
181
  end
179
182
 
183
+ describe "text_fragments" do
184
+ it "creates an array of text fragments with fallback glyph support" do
185
+ result = @layout.text_fragments("Tomāœ‚")
186
+ assert_equal(2, result.size)
187
+ assert_equal(@doc.fonts.add('ZapfDingbats'), result[1].style.font)
188
+
189
+ @doc.config['font.on_invalid_glyph'] = nil
190
+ assert_equal(1, @layout.text_fragments("Tomāœ‚").size)
191
+ end
192
+
193
+ it "uses the standard rules for creating the style object" do
194
+ @layout.style(:named, font_size: 20)
195
+ result = @layout.text_fragments("Test", style: :named)
196
+ assert_equal(20, result[0].style.font_size)
197
+ end
198
+
199
+ it "optionally assigns the properties to all fragments" do
200
+ result = @layout.text_fragments("Tomāœ‚", properties: {key: :value})
201
+ assert_equal(:value, result[0].properties[:key])
202
+ assert_equal(:value, result[1].properties[:key])
203
+ end
204
+ end
205
+
180
206
  describe "text_box" do
181
207
  it "creates a text box" do
182
- box = @layout.text_box("Test", width: 10, height: 15, properties: {key: :value})
208
+ box = @layout.text_box("Testāœ‚", width: 10, height: 15, properties: {key: :value})
183
209
  assert_equal(10, box.width)
184
210
  assert_equal(15, box.height)
185
211
  assert_same(@doc.fonts.add("Times"), box.style.font)
186
212
  items = box.instance_variable_get(:@items)
187
- assert_equal(1, items.length)
213
+ assert_equal(2, items.length)
188
214
  assert_same(box.style, items.first.style)
189
215
  assert_equal({key: :value}, box.properties)
190
216
  end
@@ -227,20 +253,22 @@ describe HexaPDF::Document::Layout do
227
253
 
228
254
  describe "formatted_text" do
229
255
  it "creates a text box with the given text" do
230
- box = @layout.formatted_text_box(["Test"], width: 10, height: 15)
256
+ box = @layout.formatted_text_box(["Testāœ‚"], width: 10, height: 15)
231
257
  assert_equal(10, box.width)
232
258
  assert_equal(15, box.height)
233
- assert_equal(1, box.instance_variable_get(:@items).length)
259
+ assert_equal(2, box.instance_variable_get(:@items).length)
234
260
  end
235
261
 
236
262
  it "allows setting custom properties on the whole box" do
237
- box = @layout.formatted_text_box(["Test"], properties: {key: :value})
263
+ box = @layout.formatted_text_box([{text: "Test", properties: {key: :novalue}}],
264
+ properties: {key: :value})
238
265
  assert_equal({key: :value}, box.properties)
239
266
  end
240
267
 
241
268
  it "allows using a hash with :text key instead of a simple string" do
242
- box = @layout.formatted_text_box([{text: "Test"}])
269
+ box = @layout.formatted_text_box([{text: "Testāœ‚"}])
243
270
  items = box.instance_variable_get(:@items)
271
+ assert_equal(2, items.length)
244
272
  assert_equal(4, items[0].items.length)
245
273
  end
246
274
 
@@ -259,29 +287,29 @@ describe HexaPDF::Document::Layout do
259
287
  end
260
288
 
261
289
  it "allows using custom style properties for a single part" do
262
- box = @layout.formatted_text_box([{text: "Test", font_size: 20}, "test"], align: :center)
290
+ box = @layout.formatted_text_box([{text: "Test", font_size: 20}, "test"], text_align: :center)
263
291
  items = box.instance_variable_get(:@items)
264
292
  assert_equal(10, box.style.font_size)
265
293
 
266
294
  assert_equal(20, items[0].style.font_size)
267
- assert_equal(:center, items[0].style.align)
295
+ assert_equal(:center, items[0].style.text_align)
268
296
 
269
297
  assert_equal(10, items[1].style.font_size)
270
- assert_equal(:center, items[1].style.align)
298
+ assert_equal(:center, items[1].style.text_align)
271
299
  end
272
300
 
273
301
  it "allows using a custom style as basis for a single part" do
274
302
  box = @layout.formatted_text_box([{text: "Test", style: {font_size: 20}, subscript: true},
275
- "test"], align: :center)
303
+ "test"], text_align: :center)
276
304
  items = box.instance_variable_get(:@items)
277
305
  assert_equal(10, box.style.font_size)
278
306
 
279
307
  assert_equal(20, items[0].style.font_size)
280
- assert_equal(:left, items[0].style.align)
308
+ assert_equal(:left, items[0].style.text_align)
281
309
  assert(items[0].style.subscript)
282
310
 
283
311
  assert_equal(10, items[1].style.font_size)
284
- assert_equal(:center, items[1].style.align)
312
+ assert_equal(:center, items[1].style.text_align)
285
313
  refute(items[1].style.subscript)
286
314
  end
287
315
 
@@ -17,20 +17,20 @@ describe HexaPDF::Document::Pages do
17
17
  describe "create" do
18
18
  it "uses the defaults from the configuration for missing arguments" do
19
19
  page = @doc.pages.create
20
- assert_equal([0, 0, 595, 842], page.box(:media).value)
20
+ assert_equal([0, 0, 595.275591, 841.889764], page.box(:media).value)
21
21
  end
22
22
 
23
23
  it "allows specifying a reference to a predefined page size" do
24
24
  page = @doc.pages.create(media_box: :A3)
25
- assert_equal([0, 0, 842, 1191], page.box(:media).value)
25
+ assert_equal([0, 0, 841.889764, 1190.551181], page.box(:media).value)
26
26
  end
27
27
 
28
28
  it "allows specifying the orientation for a predefined page size" do
29
29
  page = @doc.pages.create(media_box: :A4, orientation: :landscape)
30
- assert_equal([0, 0, 842, 595], page.box(:media).value)
30
+ assert_equal([0, 0, 841.889764, 595.275591], page.box(:media).value)
31
31
 
32
32
  page = @doc.pages.create(orientation: :landscape)
33
- assert_equal([0, 0, 842, 595], page.box(:media).value)
33
+ assert_equal([0, 0, 841.889764, 595.275591], page.box(:media).value)
34
34
  end
35
35
 
36
36
  it "allows using a media box array" do
@@ -54,7 +54,7 @@ describe HexaPDF::Document::Pages do
54
54
  it "adds a new empty page with the given page format" do
55
55
  page = @doc.pages.add(:A4, orientation: :landscape)
56
56
  assert_same(page, @doc.pages[0])
57
- assert_equal([0, 0, 842, 595], @doc.pages[0].box(:media).value)
57
+ assert_equal([0, 0, 841.889764, 595.275591], @doc.pages[0].box(:media).value)
58
58
  end
59
59
 
60
60
  it "adds the given page to the end" do
@@ -301,6 +301,49 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
301
301
  assert_equal([:copy_content, :extract_content, :modify_content], @handler.permissions.sort)
302
302
  end
303
303
 
304
+ test_files = Dir[File.join(TEST_DATA_DIR, 'standard-security-handler', '*.pdf')].sort
305
+ user_password = 'uhexapdf'
306
+ owner_password = 'ohexapdf'
307
+
308
+ describe "decryption_password_type" do
309
+ it "doesn't need a password for encrypted files without a password" do
310
+ file = test_files.find {|name| name =~ /nopwd-aes-256bit-V5.pdf/}
311
+ HexaPDF::Document.open(file) do |doc|
312
+ assert_equal(:none, doc.security_handler.decryption_password_type)
313
+ end
314
+ end
315
+
316
+ it "doesn't need a password for owner encrypted files" do
317
+ file = test_files.find {|name| name =~ /ownerpwd-aes-256bit-V5.pdf/}
318
+ HexaPDF::Document.open(file) do |doc|
319
+ assert_equal(:none, doc.security_handler.decryption_password_type)
320
+ end
321
+ end
322
+
323
+ it "needs the user password for user encrypted files" do
324
+ file = test_files.find {|name| name =~ /userpwd-aes-256bit-V5.pdf/}
325
+ HexaPDF::Document.open(file, decryption_opts: {password: user_password}) do |doc|
326
+ assert_equal(:user, doc.security_handler.decryption_password_type)
327
+ end
328
+ end
329
+
330
+ it "can user either the user or owner password for user+owner encrypted files" do
331
+ file = test_files.find {|name| name =~ /bothpwd-aes-256bit-V5.pdf/}
332
+ HexaPDF::Document.open(file, decryption_opts: {password: user_password}) do |doc|
333
+ assert_equal(:user, doc.security_handler.decryption_password_type)
334
+ end
335
+ HexaPDF::Document.open(file, decryption_opts: {password: owner_password}) do |doc|
336
+ assert_equal(:owner, doc.security_handler.decryption_password_type)
337
+ end
338
+ end
339
+
340
+ it "returns :unknown for loaded or created and then encrypted PDF documents" do
341
+ doc = HexaPDF::Document.new
342
+ doc.encrypt
343
+ assert_equal(:unknown, doc.security_handler.decryption_password_type)
344
+ end
345
+ end
346
+
304
347
  describe "handling of metadata streams" do
305
348
  before do
306
349
  @doc = HexaPDF::Document.new
@@ -8,7 +8,9 @@ describe HexaPDF::Font::InvalidGlyph do
8
8
  font = Object.new
9
9
  font.define_singleton_method(:missing_glyph_id) { 0 }
10
10
  font.define_singleton_method(:full_name) { "Test Roman" }
11
- @glyph = HexaPDF::Font::InvalidGlyph.new(font, "str")
11
+ font_wrapper = Object.new
12
+ font_wrapper.define_singleton_method(:wrapped_font) { font }
13
+ @glyph = HexaPDF::Font::InvalidGlyph.new(font_wrapper, "str")
12
14
  end
13
15
 
14
16
  it "returns the missing glyph id for id/name" do
@@ -27,6 +29,16 @@ describe HexaPDF::Font::InvalidGlyph do
27
29
  refute(@glyph.apply_word_spacing?)
28
30
  end
29
31
 
32
+ it "returns false when asked whether it is valid" do
33
+ refute(@glyph.valid?)
34
+ end
35
+
36
+ it "returns true if the glyph represents a control character" do
37
+ refute(@glyph.control_char?)
38
+ assert(HexaPDF::Font::InvalidGlyph.new(nil, "\n"))
39
+ assert(HexaPDF::Font::InvalidGlyph.new(nil, "\u{8203}"))
40
+ end
41
+
30
42
  it "can represent itself for debug purposes" do
31
43
  assert_equal('#<HexaPDF::Font::InvalidGlyph font="Test Roman" id=0 "str">',
32
44
  @glyph.inspect)
@@ -24,17 +24,29 @@ describe HexaPDF::Font::TrueTypeWrapper do
24
24
  end
25
25
  end
26
26
 
27
+ it "can be asked whether the font is a bold one" do
28
+ refute(@font_wrapper.bold?)
29
+ end
30
+
31
+ it "can be asked whether the font is an italic one" do
32
+ refute(@font_wrapper.italic?)
33
+ end
34
+
27
35
  it "can be asked whether font wil be subset" do
28
36
  assert(@font_wrapper.subset?)
29
37
  refute(HexaPDF::Font::TrueTypeWrapper.new(@doc, @font, subset: false).subset?)
30
38
  end
31
39
 
32
- describe "decode_utf8" do
33
- it "returns an array of glyph objects" do
40
+ describe "decode_*" do
41
+ it "decode_utf8 returns an array of glyph objects" do
34
42
  assert_equal("Test",
35
43
  @font_wrapper.decode_utf8("Test").map {|g| @cmap.gid_to_code(g.id) }.pack('U*'))
36
44
  end
37
45
 
46
+ it "decode_codepoint returns a single glyph object" do
47
+ assert_equal("A", @font_wrapper.decode_codepoint(65).str)
48
+ end
49
+
38
50
  it "invokes font.on_missing_glyph for UTF-8 characters for which no glyph exists" do
39
51
  glyphs = @font_wrapper.decode_utf8("šŸ˜")
40
52
  assert_equal(1, glyphs.length)
@@ -54,6 +66,7 @@ describe HexaPDF::Font::TrueTypeWrapper do
54
66
  assert_equal(584, glyph.x_max)
55
67
  assert_equal(696, glyph.y_max)
56
68
  refute(glyph.apply_word_spacing?)
69
+ assert(glyph.valid?)
57
70
  assert_equal('#<HexaPDF::Font::TrueTypeWrapper::Glyph font="Ubuntu-Title" id=17 "0">',
58
71
  glyph.inspect)
59
72
  end
@@ -21,15 +21,33 @@ describe HexaPDF::Font::Type1Wrapper do
21
21
  assert_equal("A", wrapper.encode(wrapper.glyph(:B)))
22
22
  end
23
23
 
24
+ it "can be asked whether the font is a bold one" do
25
+ refute(@times_wrapper.bold?)
26
+ refute(@symbol_wrapper.bold?)
27
+ assert(@doc.fonts.add("Times", variant: :bold).bold?)
28
+ refute(@doc.fonts.add("Helvetica").bold?)
29
+ end
30
+
31
+ it "can be asked whether the font is an italic one" do
32
+ refute(@times_wrapper.italic?)
33
+ refute(@symbol_wrapper.italic?)
34
+ assert(@doc.fonts.add("Times", variant: :italic).italic?)
35
+ assert(@doc.fonts.add("Helvetica", variant: :bold_italic).italic?)
36
+ end
37
+
24
38
  it "returns 1 for the scaling factor" do
25
39
  assert_equal(1, @times_wrapper.scaling_factor)
26
40
  end
27
41
 
28
- describe "decode_utf8" do
29
- it "returns an array of glyph objects" do
42
+ describe "decode_*" do
43
+ it "decode_utf8 returns an array of glyph objects" do
30
44
  assert_equal([:T, :e, :s, :t], @times_wrapper.decode_utf8("Test").map(&:name))
31
45
  end
32
46
 
47
+ it "decode_codepoint returns a single glyph object" do
48
+ assert_equal(:A, @times_wrapper.decode_codepoint(65).name)
49
+ end
50
+
33
51
  it "falls back to the internal font encoding if the Unicode codepoint is not mapped" do
34
52
  assert_equal([:Delta, :Delta], @symbol_wrapper.decode_utf8("Dāˆ†").map(&:name))
35
53
  end
@@ -53,6 +71,7 @@ describe HexaPDF::Font::Type1Wrapper do
53
71
  assert_equal(706, glyph.x_max)
54
72
  assert_equal(674, glyph.y_max)
55
73
  refute(glyph.apply_word_spacing?)
74
+ assert(glyph.valid?)
56
75
  assert_equal('#<HexaPDF::Font::Type1Wrapper::Glyph font="Times Roman" id=:A "A">',
57
76
  glyph.inspect)
58
77
  end
@@ -95,6 +95,13 @@ describe HexaPDF::Layout::Box do
95
95
  assert_equal(100, box.height)
96
96
  end
97
97
 
98
+ it "uses float comparison" do
99
+ box = create_box(width: 50.0000002, height: 49.9999996)
100
+ assert(box.fit(50, 50, @frame))
101
+ assert_equal(50.0000002, box.width)
102
+ assert_equal(49.9999996, box.height)
103
+ end
104
+
98
105
  it "returns false if the box doesn't fit" do
99
106
  box = create_box(width: 101)
100
107
  refute(box.fit(100, 100, @frame))