hexapdf 0.32.2 → 0.34.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 (221) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +104 -1
  3. data/README.md +9 -0
  4. data/examples/002-graphics.rb +15 -17
  5. data/examples/003-arcs.rb +9 -9
  6. data/examples/009-text_layouter_alignment.rb +1 -1
  7. data/examples/010-text_layouter_inline_boxes.rb +2 -2
  8. data/examples/011-text_layouter_line_wrapping.rb +1 -1
  9. data/examples/012-text_layouter_styling.rb +7 -7
  10. data/examples/013-text_layouter_shapes.rb +1 -1
  11. data/examples/014-text_in_polygon.rb +1 -1
  12. data/examples/015-boxes.rb +8 -7
  13. data/examples/016-frame_automatic_box_placement.rb +2 -2
  14. data/examples/017-frame_text_flow.rb +2 -1
  15. data/examples/018-composer.rb +1 -1
  16. data/examples/020-column_box.rb +2 -1
  17. data/examples/025-table_box.rb +46 -0
  18. data/examples/026-optional_content.rb +55 -0
  19. data/examples/027-composer_optional_content.rb +83 -0
  20. data/lib/hexapdf/cli/command.rb +12 -3
  21. data/lib/hexapdf/cli/fonts.rb +1 -1
  22. data/lib/hexapdf/cli/form.rb +5 -5
  23. data/lib/hexapdf/cli/inspect.rb +5 -7
  24. data/lib/hexapdf/composer.rb +106 -53
  25. data/lib/hexapdf/configuration.rb +65 -40
  26. data/lib/hexapdf/content/canvas.rb +445 -267
  27. data/lib/hexapdf/content/color_space.rb +72 -25
  28. data/lib/hexapdf/content/graphic_object/arc.rb +57 -24
  29. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -23
  30. data/lib/hexapdf/content/graphic_object/geom2d.rb +47 -6
  31. data/lib/hexapdf/content/graphic_object/solid_arc.rb +58 -36
  32. data/lib/hexapdf/content/graphic_object.rb +6 -7
  33. data/lib/hexapdf/content/graphics_state.rb +54 -45
  34. data/lib/hexapdf/content/operator.rb +54 -54
  35. data/lib/hexapdf/content/parser.rb +2 -2
  36. data/lib/hexapdf/content/processor.rb +15 -15
  37. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  38. data/lib/hexapdf/content.rb +5 -0
  39. data/lib/hexapdf/dictionary.rb +7 -5
  40. data/lib/hexapdf/dictionary_fields.rb +43 -16
  41. data/lib/hexapdf/digital_signature/cms_handler.rb +2 -2
  42. data/lib/hexapdf/digital_signature/handler.rb +1 -1
  43. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +2 -3
  44. data/lib/hexapdf/digital_signature/signature.rb +6 -6
  45. data/lib/hexapdf/digital_signature/signatures.rb +13 -12
  46. data/lib/hexapdf/digital_signature/signing/default_handler.rb +14 -5
  47. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +2 -4
  48. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +4 -4
  49. data/lib/hexapdf/digital_signature/signing.rb +4 -0
  50. data/lib/hexapdf/digital_signature/verification_result.rb +3 -4
  51. data/lib/hexapdf/digital_signature.rb +7 -2
  52. data/lib/hexapdf/document/destinations.rb +12 -11
  53. data/lib/hexapdf/document/files.rb +1 -1
  54. data/lib/hexapdf/document/fonts.rb +1 -1
  55. data/lib/hexapdf/document/layout.rb +170 -39
  56. data/lib/hexapdf/document/pages.rb +4 -3
  57. data/lib/hexapdf/document.rb +96 -55
  58. data/lib/hexapdf/encryption/aes.rb +5 -5
  59. data/lib/hexapdf/encryption/arc4.rb +1 -1
  60. data/lib/hexapdf/encryption/fast_aes.rb +2 -2
  61. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  62. data/lib/hexapdf/encryption/identity.rb +1 -1
  63. data/lib/hexapdf/encryption/ruby_aes.rb +11 -21
  64. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  65. data/lib/hexapdf/encryption/security_handler.rb +31 -24
  66. data/lib/hexapdf/encryption/standard_security_handler.rb +45 -36
  67. data/lib/hexapdf/encryption.rb +7 -2
  68. data/lib/hexapdf/error.rb +18 -0
  69. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  70. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  71. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  72. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  73. data/lib/hexapdf/filter/pass_through.rb +1 -1
  74. data/lib/hexapdf/filter/predictor.rb +1 -1
  75. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  76. data/lib/hexapdf/filter.rb +55 -6
  77. data/lib/hexapdf/font/cmap/parser.rb +2 -2
  78. data/lib/hexapdf/font/cmap.rb +1 -1
  79. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  80. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  81. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +2 -2
  82. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  83. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  84. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +3 -3
  85. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  86. data/lib/hexapdf/font/invalid_glyph.rb +3 -0
  87. data/lib/hexapdf/font/true_type_wrapper.rb +17 -4
  88. data/lib/hexapdf/font/type1_wrapper.rb +19 -4
  89. data/lib/hexapdf/font_loader/from_configuration.rb +5 -2
  90. data/lib/hexapdf/font_loader/from_file.rb +5 -5
  91. data/lib/hexapdf/font_loader/standard14.rb +3 -3
  92. data/lib/hexapdf/font_loader.rb +3 -0
  93. data/lib/hexapdf/image_loader/jpeg.rb +2 -2
  94. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  95. data/lib/hexapdf/image_loader/png.rb +2 -2
  96. data/lib/hexapdf/image_loader.rb +1 -1
  97. data/lib/hexapdf/importer.rb +13 -0
  98. data/lib/hexapdf/layout/box.rb +32 -5
  99. data/lib/hexapdf/layout/box_fitter.rb +2 -2
  100. data/lib/hexapdf/layout/column_box.rb +20 -5
  101. data/lib/hexapdf/layout/frame.rb +53 -18
  102. data/lib/hexapdf/layout/image_box.rb +5 -0
  103. data/lib/hexapdf/layout/inline_box.rb +21 -9
  104. data/lib/hexapdf/layout/list_box.rb +50 -20
  105. data/lib/hexapdf/layout/page_style.rb +6 -5
  106. data/lib/hexapdf/layout/style.rb +64 -9
  107. data/lib/hexapdf/layout/table_box.rb +684 -0
  108. data/lib/hexapdf/layout/text_box.rb +12 -3
  109. data/lib/hexapdf/layout/text_fragment.rb +29 -3
  110. data/lib/hexapdf/layout/text_layouter.rb +32 -8
  111. data/lib/hexapdf/layout.rb +1 -0
  112. data/lib/hexapdf/name_tree_node.rb +1 -1
  113. data/lib/hexapdf/number_tree_node.rb +1 -1
  114. data/lib/hexapdf/object.rb +18 -7
  115. data/lib/hexapdf/parser.rb +7 -7
  116. data/lib/hexapdf/pdf_array.rb +1 -1
  117. data/lib/hexapdf/rectangle.rb +1 -1
  118. data/lib/hexapdf/reference.rb +1 -1
  119. data/lib/hexapdf/revision.rb +1 -1
  120. data/lib/hexapdf/revisions.rb +3 -3
  121. data/lib/hexapdf/serializer.rb +15 -15
  122. data/lib/hexapdf/stream.rb +5 -4
  123. data/lib/hexapdf/tokenizer.rb +14 -14
  124. data/lib/hexapdf/type/acro_form/appearance_generator.rb +22 -22
  125. data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
  126. data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
  127. data/lib/hexapdf/type/acro_form/field.rb +2 -2
  128. data/lib/hexapdf/type/acro_form/form.rb +1 -1
  129. data/lib/hexapdf/type/acro_form/signature_field.rb +4 -4
  130. data/lib/hexapdf/type/acro_form/text_field.rb +1 -1
  131. data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
  132. data/lib/hexapdf/type/acro_form.rb +1 -1
  133. data/lib/hexapdf/type/action.rb +1 -1
  134. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  135. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  136. data/lib/hexapdf/type/actions/launch.rb +1 -1
  137. data/lib/hexapdf/type/actions/set_ocg_state.rb +86 -0
  138. data/lib/hexapdf/type/actions/uri.rb +1 -1
  139. data/lib/hexapdf/type/actions.rb +2 -1
  140. data/lib/hexapdf/type/annotation.rb +3 -3
  141. data/lib/hexapdf/type/annotations/link.rb +1 -1
  142. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  143. data/lib/hexapdf/type/annotations/text.rb +2 -3
  144. data/lib/hexapdf/type/annotations/widget.rb +2 -2
  145. data/lib/hexapdf/type/annotations.rb +1 -1
  146. data/lib/hexapdf/type/catalog.rb +11 -2
  147. data/lib/hexapdf/type/cid_font.rb +18 -4
  148. data/lib/hexapdf/type/embedded_file.rb +1 -1
  149. data/lib/hexapdf/type/file_specification.rb +2 -2
  150. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  151. data/lib/hexapdf/type/font_simple.rb +2 -2
  152. data/lib/hexapdf/type/font_type0.rb +3 -3
  153. data/lib/hexapdf/type/font_type3.rb +1 -1
  154. data/lib/hexapdf/type/form.rb +76 -6
  155. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  156. data/lib/hexapdf/type/icon_fit.rb +1 -1
  157. data/lib/hexapdf/type/image.rb +1 -1
  158. data/lib/hexapdf/type/info.rb +1 -1
  159. data/lib/hexapdf/type/mark_information.rb +1 -1
  160. data/lib/hexapdf/type/names.rb +2 -2
  161. data/lib/hexapdf/type/object_stream.rb +2 -1
  162. data/lib/hexapdf/type/optional_content_configuration.rb +170 -0
  163. data/lib/hexapdf/type/optional_content_group.rb +370 -0
  164. data/lib/hexapdf/type/optional_content_membership.rb +63 -0
  165. data/lib/hexapdf/type/optional_content_properties.rb +158 -0
  166. data/lib/hexapdf/type/outline.rb +1 -1
  167. data/lib/hexapdf/type/outline_item.rb +1 -1
  168. data/lib/hexapdf/type/page.rb +46 -21
  169. data/lib/hexapdf/type/page_label.rb +5 -9
  170. data/lib/hexapdf/type/page_tree_node.rb +1 -1
  171. data/lib/hexapdf/type/resources.rb +1 -1
  172. data/lib/hexapdf/type/trailer.rb +2 -2
  173. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  174. data/lib/hexapdf/type/xref_stream.rb +2 -2
  175. data/lib/hexapdf/type.rb +4 -0
  176. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -2
  177. data/lib/hexapdf/version.rb +1 -1
  178. data/lib/hexapdf/writer.rb +4 -4
  179. data/lib/hexapdf/xref_section.rb +2 -2
  180. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +11 -1
  181. data/test/hexapdf/content/graphic_object/test_geom2d.rb +7 -0
  182. data/test/hexapdf/content/test_canvas.rb +49 -1
  183. data/test/hexapdf/digital_signature/test_signatures.rb +22 -0
  184. data/test/hexapdf/document/test_files.rb +2 -2
  185. data/test/hexapdf/document/test_layout.rb +105 -2
  186. data/test/hexapdf/document/test_pages.rb +6 -6
  187. data/test/hexapdf/encryption/test_security_handler.rb +12 -11
  188. data/test/hexapdf/encryption/test_standard_security_handler.rb +35 -23
  189. data/test/hexapdf/font/test_true_type_wrapper.rb +18 -1
  190. data/test/hexapdf/font/test_type1_wrapper.rb +15 -1
  191. data/test/hexapdf/layout/test_box.rb +14 -5
  192. data/test/hexapdf/layout/test_column_box.rb +65 -21
  193. data/test/hexapdf/layout/test_frame.rb +27 -15
  194. data/test/hexapdf/layout/test_image_box.rb +4 -0
  195. data/test/hexapdf/layout/test_inline_box.rb +17 -3
  196. data/test/hexapdf/layout/test_list_box.rb +84 -33
  197. data/test/hexapdf/layout/test_page_style.rb +3 -2
  198. data/test/hexapdf/layout/test_style.rb +60 -0
  199. data/test/hexapdf/layout/test_table_box.rb +728 -0
  200. data/test/hexapdf/layout/test_text_box.rb +26 -0
  201. data/test/hexapdf/layout/test_text_fragment.rb +33 -0
  202. data/test/hexapdf/layout/test_text_layouter.rb +36 -5
  203. data/test/hexapdf/test_composer.rb +10 -0
  204. data/test/hexapdf/test_dictionary.rb +10 -0
  205. data/test/hexapdf/test_dictionary_fields.rb +4 -1
  206. data/test/hexapdf/test_document.rb +5 -0
  207. data/test/hexapdf/test_filter.rb +8 -0
  208. data/test/hexapdf/test_importer.rb +9 -0
  209. data/test/hexapdf/test_object.rb +16 -5
  210. data/test/hexapdf/test_stream.rb +7 -0
  211. data/test/hexapdf/test_writer.rb +3 -3
  212. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +13 -5
  213. data/test/hexapdf/type/acro_form/test_form.rb +4 -3
  214. data/test/hexapdf/type/actions/test_set_ocg_state.rb +40 -0
  215. data/test/hexapdf/type/test_catalog.rb +11 -0
  216. data/test/hexapdf/type/test_form.rb +119 -0
  217. data/test/hexapdf/type/test_optional_content_configuration.rb +112 -0
  218. data/test/hexapdf/type/test_optional_content_group.rb +158 -0
  219. data/test/hexapdf/type/test_optional_content_properties.rb +109 -0
  220. data/test/hexapdf/type/test_page.rb +20 -6
  221. metadata +28 -8
@@ -196,15 +196,15 @@ describe HexaPDF::Document::Pages do
196
196
  end
197
197
 
198
198
  it "works for multiple page label entries" do
199
- @doc.catalog[:PageLabels] = {Nums: [0, {S: :r}, 2, {S: :d}, 7, {S: :A}]}
199
+ @doc.catalog[:PageLabels] = {Nums: [0, {S: :r}, 2, {S: :D}, 7, {S: :A}]}
200
200
  result = @doc.pages.each_labelling_range.to_a
201
- assert_equal([[0, 2, {S: :r}], [2, 5, {S: :d}], [7, 3, {S: :A}]],
201
+ assert_equal([[0, 2, {S: :r}], [2, 5, {S: :D}], [7, 3, {S: :A}]],
202
202
  result.map {|s, c, l| [s, c, l.value] })
203
203
  end
204
204
 
205
205
  it "returns a zero or negative count for the last range if there aren't enough pages" do
206
206
  assert_equal(10, @doc.pages.count)
207
- @doc.catalog[:PageLabels] = {Nums: [0, {S: :d}, 10, {S: :r}]}
207
+ @doc.catalog[:PageLabels] = {Nums: [0, {S: :D}, 10, {S: :r}]}
208
208
  assert_equal(0, @doc.pages.each_labelling_range.to_a[-1][1])
209
209
  @doc.catalog[:PageLabels][:Nums][2] = 11
210
210
  assert_equal(-1, @doc.pages.each_labelling_range.to_a[-1][1])
@@ -221,19 +221,19 @@ describe HexaPDF::Document::Pages do
221
221
 
222
222
  it "adds an entry for the range starting at 0 if it doesn't exist" do
223
223
  label = @doc.pages.add_labelling_range(5)
224
- assert_equal([{S: :d}, label],
224
+ assert_equal([{S: :D}, label],
225
225
  @doc.catalog.page_labels[:Nums].value.values_at(1, 3))
226
226
  end
227
227
  end
228
228
 
229
229
  describe "delete_labelling_range" do
230
230
  before do
231
- @doc.catalog[:PageLabels] = {Nums: [0, {S: :r}, 5, {S: :d}]}
231
+ @doc.catalog[:PageLabels] = {Nums: [0, {S: :r}, 5, {S: :D}]}
232
232
  end
233
233
 
234
234
  it "deletes the labelling range for a given start index" do
235
235
  label = @doc.pages.delete_labelling_range(5)
236
- assert_equal({S: :d}, label)
236
+ assert_equal({S: :D}, label)
237
237
  end
238
238
 
239
239
  it "deletes the labelling range for 0 if it is the last, together with the number tree" do
@@ -17,13 +17,6 @@ describe HexaPDF::Encryption::EncryptionDictionary do
17
17
  assert(@dict.must_be_indirect?)
18
18
  end
19
19
 
20
- it "validates the /V value" do
21
- @dict[:V] = 1
22
- assert(@dict.validate)
23
- @dict[:V] = 3
24
- refute(@dict.validate)
25
- end
26
-
27
20
  it "validates the /Length field when /V=2" do
28
21
  @dict[:V] = 2
29
22
  refute(@dict.validate)
@@ -240,6 +233,7 @@ describe HexaPDF::Encryption::SecurityHandler do
240
233
  [:identity, 128, {V: 4, StrF: :Mine, CF: {Mine: {CFM: :None}}}],
241
234
  [:identity, 128, {V: 4, CF: {Mine: {CFM: :AESV2}}}],
242
235
  ].each do |alg, length, dict|
236
+ dict[:Filter] = :Test
243
237
  @enc.strf = alg
244
238
  @enc.set_up_encryption(key_length: length, algorithm: (alg == :identity ? :aes : alg))
245
239
  @obj[:X] = @enc.encrypt_string('data', @obj)
@@ -249,7 +243,7 @@ describe HexaPDF::Encryption::SecurityHandler do
249
243
  end
250
244
 
251
245
  it "selects the correct algorithm for string, stream and embedded file decryption" do
252
- @handler.set_up_decryption({V: 4, StrF: :Mine, StmF: :Mine, EFF: :Mine,
246
+ @handler.set_up_decryption({Filter: :Test, V: 4, StrF: :Mine, StmF: :Mine, EFF: :Mine,
253
247
  CF: {Mine: {CFM: :V2}}})
254
248
  assert_equal(HexaPDF::Encryption::FastARC4, @handler.send(:embedded_file_algorithm))
255
249
  assert_equal(HexaPDF::Encryption::FastARC4, @handler.send(:string_algorithm))
@@ -263,16 +257,23 @@ describe HexaPDF::Encryption::SecurityHandler do
263
257
  @handler.encryption_details)
264
258
  end
265
259
 
260
+ it "fails if the encryption dictionary is not valid" do
261
+ exp = assert_raises(HexaPDF::Error) do
262
+ @handler.set_up_decryption({V: 5})
263
+ end
264
+ assert_match(/Validation error for encryption dictionary.*Required field Filter/i, exp.message)
265
+ end
266
+
266
267
  it "fails for unsupported /V values in the dict" do
267
268
  exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
268
- @handler.set_up_decryption({V: 3})
269
+ @handler.set_up_decryption({Filter: :Text, V: 3})
269
270
  end
270
271
  assert_match(/Unsupported encryption version/i, exp.message)
271
272
  end
272
273
 
273
274
  it "fails for unsupported crypt filter encryption methods" do
274
275
  exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
275
- @handler.set_up_decryption({V: 4, StrF: :Mine, CF: {Mine: {CFM: :Unknown}}})
276
+ @handler.set_up_decryption({Filter: :Test, V: 4, StrF: :Mine, CF: {Mine: {CFM: :Unknown}}})
276
277
  end
277
278
  assert_match(/Unsupported encryption method/i, exp.message)
278
279
  end
@@ -280,7 +281,7 @@ describe HexaPDF::Encryption::SecurityHandler do
280
281
 
281
282
  describe "decrypt" do
282
283
  before do
283
- @handler.set_up_decryption({V: 1})
284
+ @handler.set_up_decryption({Filter: :Test, V: 1})
284
285
  @encrypted = @handler.encrypt_string('string', @obj)
285
286
  @obj.value = {Key: @encrypted.dup, Array: [@encrypted.dup], Hash: {Another: @encrypted.dup}}
286
287
  end
@@ -21,13 +21,6 @@ describe HexaPDF::Encryption::StandardEncryptionDictionary do
21
21
  @dict[:Perms] = 'test' * 8
22
22
  end
23
23
 
24
- it "validates the /R value" do
25
- @dict[:R] = 2
26
- assert(@dict.validate)
27
- @dict[:R] = 5
28
- refute(@dict.validate)
29
- end
30
-
31
24
  [:U, :O].each do |field|
32
25
  it "validates the length of /#{field} field for R <= 4" do
33
26
  @dict[field] = 'test'
@@ -35,19 +28,36 @@ describe HexaPDF::Encryption::StandardEncryptionDictionary do
35
28
  end
36
29
  end
37
30
 
38
- [:U, :O, :UE, :OE, :Perms].each do |field|
39
- it "validates the length of /#{field} field for R=6" do
31
+ describe "validation for R=6" do
32
+ before do
40
33
  @dict[:R] = 6
41
- @dict[field] = 'test'
42
- refute(@dict.validate)
34
+ @dict[:U] = 't' * 48
35
+ @dict[:O] = 't' * 48
36
+ @dict[:UE] = 't' * 32
37
+ @dict[:OE] = 't' * 32
38
+ @dict[:Perms] = 't' * 16
43
39
  end
44
- end
45
40
 
46
- [:UE, :OE, :Perms].each do |field|
47
- it "validates the existence of the /#{field} field for R=6" do
48
- @dict[:R] = 6
49
- @dict.delete(field)
50
- refute(@dict.validate)
41
+ [:U, :O, :UE, :OE, :Perms].each do |field|
42
+ it "validates the length of /#{field}" do
43
+ @dict[field] = 'test'
44
+ refute(@dict.validate)
45
+ end
46
+ end
47
+
48
+ [:U, :O].each do |field|
49
+ it "auto-corrects /#{field} if it is larger and only padded with 0 bytes" do
50
+ @dict[field] = 't' * 48 + "\x00" * 20
51
+ assert(@dict.validate)
52
+ assert_equal('t' * 48, @dict[field])
53
+ end
54
+ end
55
+
56
+ [:UE, :OE, :Perms].each do |field|
57
+ it "validates the existence of the /#{field} field" do
58
+ @dict.delete(field)
59
+ refute(@dict.validate)
60
+ end
51
61
  end
52
62
  end
53
63
  end
@@ -211,22 +221,24 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
211
221
  describe "prepare_decryption" do
212
222
  it "fails if the /Filter value is incorrect" do
213
223
  exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
214
- @handler.set_up_decryption({Filter: :NonStandard, V: 2})
224
+ @handler.set_up_decryption({Filter: :NonStandard, V: 2, R: 4, O: 't' * 32, U: 't' * 32, P: 0,
225
+ Length: 128})
215
226
  end
216
227
  assert_match(/Invalid \/Filter value NonStandard/i, exp.message)
217
228
  end
218
229
 
219
230
  it "fails if the /R value is incorrect" do
220
231
  exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
221
- @handler.set_up_decryption({Filter: :Standard, V: 2, R: 5})
232
+ @handler.set_up_decryption({Filter: :Standard, V: 2, R: 5, O: 't' * 32, U: 't' * 32, P: 0,
233
+ Length: 128})
222
234
  end
223
235
  assert_match(/Invalid \/R value 5/i, exp.message)
224
236
  end
225
237
 
226
238
  it "fails if the supplied password is invalid" do
227
239
  exp = assert_raises(HexaPDF::EncryptionError) do
228
- @handler.set_up_decryption({Filter: :Standard, V: 2, R: 6, U: 'a' * 48, O: 'a' * 48,
229
- UE: 'a' * 32, OE: 'a' * 32})
240
+ @handler.set_up_decryption({Filter: :Standard, V: 5, R: 6, U: 'a' * 48, O: 'a' * 48,
241
+ UE: 'a' * 32, OE: 'a' * 32, P: 0, Perms: 'a' * 16})
230
242
  end
231
243
  assert_match(/Invalid password/i, exp.message)
232
244
  end
@@ -234,7 +246,7 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
234
246
  it "assigns empty strings to the trailer's ID field if it is missing" do
235
247
  refute(@document.trailer.key?(:ID))
236
248
  exp = assert_raises(HexaPDF::EncryptionError) do
237
- @handler.set_up_decryption({Filter: :Standard, V: 1, R: 2, U: 'a' * 48, O: 'a' * 48, P: 15})
249
+ @handler.set_up_decryption({Filter: :Standard, V: 1, R: 2, U: 'a' * 32, O: 'a' * 32, P: 15})
238
250
  end
239
251
  assert_match(/Invalid password/i, exp.message)
240
252
  assert_equal(['', ''], @document.trailer[:ID].value)
@@ -286,7 +298,7 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
286
298
  it "returns an array of permission symbols" do
287
299
  perms = @handler.class::Permissions::MODIFY_CONTENT | @handler.class::Permissions::COPY_CONTENT
288
300
  @handler.set_up_encryption(permissions: perms)
289
- assert_equal([:copy_content, :modify_content], @handler.permissions.sort)
301
+ assert_equal([:copy_content, :extract_content, :modify_content], @handler.permissions.sort)
290
302
  end
291
303
 
292
304
  describe "handling of metadata streams" do
@@ -66,6 +66,20 @@ describe HexaPDF::Font::TrueTypeWrapper do
66
66
  end
67
67
  end
68
68
 
69
+ describe "custom_glyph" do
70
+ it "returns the specified glyph object" do
71
+ glyph = @font_wrapper.custom_glyph(0, "str")
72
+ assert_equal(0, glyph.id)
73
+ assert_equal("str", glyph.str)
74
+ end
75
+
76
+ it "fails if an invalid glyph id is specified" do
77
+ exp = assert_raises(HexaPDF::Error) { @font_wrapper.custom_glyph(-5, 'c') }
78
+ assert_match(/Glyph ID -5 is invalid for font 'Ubuntu-Title'/, exp.message)
79
+ assert_raises(HexaPDF::Error) { @font_wrapper.custom_glyph(9999, 'c') }
80
+ end
81
+ end
82
+
69
83
  describe "encode" do
70
84
  it "returns the encoded glyph ID for fonts that are subset" do
71
85
  code = @font_wrapper.encode(@font_wrapper.glyph(3))
@@ -83,7 +97,10 @@ describe HexaPDF::Font::TrueTypeWrapper do
83
97
  end
84
98
 
85
99
  it "raises an error if an InvalidGlyph is encoded" do
86
- assert_raises(HexaPDF::Error) { @font_wrapper.encode(@font_wrapper.glyph(9999)) }
100
+ exp = assert_raises(HexaPDF::MissingGlyphError) do
101
+ @font_wrapper.encode(@font_wrapper.decode_utf8("ö").first)
102
+ end
103
+ assert_match(/No glyph for "ö" in font 'Ubuntu-Title'/, exp.message)
87
104
  end
88
105
  end
89
106
 
@@ -65,6 +65,19 @@ describe HexaPDF::Font::Type1Wrapper do
65
65
  end
66
66
  end
67
67
 
68
+ describe "custom_glyph" do
69
+ it "returns the specified glyph object" do
70
+ glyph = @times_wrapper.custom_glyph(:question, "str")
71
+ assert_equal(:question, glyph.name)
72
+ assert_equal("str", glyph.str)
73
+ end
74
+
75
+ it "fails if the provided glyph name is not available for the font" do
76
+ exp = assert_raises(HexaPDF::Error) { @times_wrapper.custom_glyph(:handicap, 'c') }
77
+ assert_match(/Glyph named :handicap not found in font 'Times Roman'/, exp.message)
78
+ end
79
+ end
80
+
68
81
  describe "encode" do
69
82
  describe "uses WinAnsiEncoding as initial encoding for non-symbolic fonts" do
70
83
  it "returns the PDF font dictionary using WinAnsiEncoding and encoded glyph" do
@@ -75,7 +88,8 @@ describe HexaPDF::Font::Type1Wrapper do
75
88
  end
76
89
 
77
90
  it "fails if an InvalidGlyph is encoded" do
78
- assert_raises(HexaPDF::Error) { @times_wrapper.encode(@times_wrapper.glyph(:ffi)) }
91
+ exp = assert_raises(HexaPDF::MissingGlyphError) { @times_wrapper.encode(@times_wrapper.glyph(:ffi)) }
92
+ assert_match(/No glyph for "ffi" in font 'Times Roman'/, exp.message)
79
93
  end
80
94
 
81
95
  it "fails if the encoding does not support the given glyph" do
@@ -56,8 +56,8 @@ describe HexaPDF::Layout::Box do
56
56
  end
57
57
 
58
58
  it "allows setting custom properties" do
59
- box = create_box(properties: {'key' => :value})
60
- assert_equal({'key' => :value}, box.properties)
59
+ assert_equal({}, create_box(properties: nil).properties)
60
+ assert_equal({'key' => :value}, create_box(properties: {'key' => :value}).properties)
61
61
  end
62
62
  end
63
63
 
@@ -150,6 +150,10 @@ describe HexaPDF::Layout::Box do
150
150
  end
151
151
 
152
152
  describe "draw" do
153
+ before do
154
+ @canvas = HexaPDF::Document.new.pages.add.canvas
155
+ end
156
+
153
157
  it "draws the box onto the canvas" do
154
158
  box = create_box(width: 150, height: 130) do |canvas, _|
155
159
  canvas.line_width(15)
@@ -161,7 +165,6 @@ describe HexaPDF::Layout::Box do
161
165
  box.style.underlays.add {|canvas, _| canvas.line_width(10) }
162
166
  box.style.overlays.add {|canvas, _| canvas.line_width(20) }
163
167
 
164
- @canvas = HexaPDF::Document.new.pages.add.canvas
165
168
  box.draw(@canvas, 5, 5)
166
169
  assert_operators(@canvas.contents, [[:save_graphics_state],
167
170
  [:set_graphics_state_parameters, [:GS1]],
@@ -195,7 +198,6 @@ describe HexaPDF::Layout::Box do
195
198
  end
196
199
 
197
200
  it "draws nothing onto the canvas if the box is empty" do
198
- @canvas = HexaPDF::Document.new.pages.add.canvas
199
201
  box = create_box
200
202
  box.draw(@canvas, 5, 5)
201
203
  assert_operators(@canvas.contents, [])
@@ -204,10 +206,17 @@ describe HexaPDF::Layout::Box do
204
206
  refute(box.style.border?)
205
207
  refute(box.style.overlays?)
206
208
  end
209
+
210
+ it "wraps the box in optional content markers if the optional_content property is set" do
211
+ box = create_box(properties: {'optional_content' => 'Text'})
212
+ box.draw(@canvas, 0, 0)
213
+ assert_operators(@canvas.contents, [[:begin_marked_content_with_property_list, [:OC, :P1]],
214
+ [:end_marked_content]])
215
+ end
207
216
  end
208
217
 
209
218
  describe "empty?" do
210
- it "is only empty when no drawing operation is specified" do
219
+ it "is empty when no drawing operation is specified" do
211
220
  assert(create_box.empty?)
212
221
  refute(create_box {}.empty?)
213
222
  refute(create_box(style: {background_color: [5]}).empty?)
@@ -22,7 +22,7 @@ describe HexaPDF::Layout::ColumnBox do
22
22
  end
23
23
 
24
24
  def check_box(box, width, height, fit_pos = nil)
25
- assert(box.fit(@frame.available_width, @frame.available_height, @frame), "box fit?")
25
+ assert(box.fit(@frame.available_width, @frame.available_height, @frame), "box didn't fit")
26
26
  assert_equal(width, box.width, "box width")
27
27
  assert_equal(height, box.height, "box height")
28
28
  if fit_pos
@@ -46,6 +46,24 @@ describe HexaPDF::Layout::ColumnBox do
46
46
  end
47
47
  end
48
48
 
49
+ describe "empty?" do
50
+ it "is empty if nothing is fit yet" do
51
+ assert(create_box.empty?)
52
+ end
53
+
54
+ it "is empty if no box fits" do
55
+ box = create_box(children: [@fixed_size_boxes[0]], columns: [10])
56
+ box.fit(@frame.available_width, @frame.available_height, @frame)
57
+ assert(box.empty?)
58
+ end
59
+
60
+ it "is not empty if at least one box fits" do
61
+ box = create_box(children: [@fixed_size_boxes[0]], columns: [30])
62
+ check_box(box, 30, 10)
63
+ refute(box.empty?)
64
+ end
65
+ end
66
+
49
67
  describe "fit" do
50
68
  [:default, :flow].each do |position|
51
69
  it "respects the set initial width, position #{position}" do
@@ -132,27 +150,53 @@ describe HexaPDF::Layout::ColumnBox do
132
150
  assert_equal(5, box_b.children.size)
133
151
  end
134
152
 
135
- it "draws the result onto the canvas" do
136
- box = create_box(children: @fixed_size_boxes)
137
- box.fit(100, 100, @frame)
153
+ describe "draw_content" do
154
+ before do
155
+ @canvas = HexaPDF::Document.new.pages.add.canvas
156
+ end
157
+
158
+ it "draws the result onto the canvas" do
159
+ box = create_box(children: @fixed_size_boxes)
160
+ box.fit(100, 100, @frame)
161
+ box.draw(@canvas, 0, 100 - box.height)
162
+ operators = 90.step(to: 20, by: -10).map do |y|
163
+ [[:save_graphics_state],
164
+ [:concatenate_matrix, [1, 0, 0, 1, 0, y]],
165
+ [:move_to, [0, 0]],
166
+ [:end_path],
167
+ [:restore_graphics_state]]
168
+ end
169
+ operators.concat(90.step(to: 30, by: -10).map do |y|
170
+ [[:save_graphics_state],
171
+ [:concatenate_matrix, [1, 0, 0, 1, 55, y]],
172
+ [:move_to, [0, 0]],
173
+ [:end_path],
174
+ [:restore_graphics_state]]
175
+ end)
176
+ operators.flatten!(1)
177
+ assert_operators(@canvas.contents, operators)
178
+ end
138
179
 
139
- @canvas = HexaPDF::Document.new.pages.add.canvas
140
- box.draw(@canvas, 0, 0)
141
- operators = 90.step(to: 20, by: -10).map do |y|
142
- [[:save_graphics_state],
143
- [:concatenate_matrix, [1, 0, 0, 1, 0, y]],
144
- [:move_to, [0, 0]],
145
- [:end_path],
146
- [:restore_graphics_state]]
180
+ it "takes a different final location into account" do
181
+ box = create_box(children: @fixed_size_boxes[0, 2])
182
+ box.fit(100, 100, @frame)
183
+ box.draw(@canvas, 20, 10)
184
+ operators = [
185
+ [:save_graphics_state],
186
+ [:concatenate_matrix, [1, 0, 0, 1, 20, -80]],
187
+ [:save_graphics_state],
188
+ [:concatenate_matrix, [1, 0, 0, 1, 0, 90]],
189
+ [:move_to, [0, 0]],
190
+ [:end_path],
191
+ [:restore_graphics_state],
192
+ [:save_graphics_state],
193
+ [:concatenate_matrix, [1, 0, 0, 1, 55, 90]],
194
+ [:move_to, [0, 0]],
195
+ [:end_path],
196
+ [:restore_graphics_state],
197
+ [:restore_graphics_state],
198
+ ]
199
+ assert_operators(@canvas.contents, operators)
147
200
  end
148
- operators.concat(90.step(to: 30, by: -10).map do |y|
149
- [[:save_graphics_state],
150
- [:concatenate_matrix, [1, 0, 0, 1, 55, y]],
151
- [:move_to, [0, 0]],
152
- [:end_path],
153
- [:restore_graphics_state]]
154
- end)
155
- operators.flatten!(1)
156
- assert_operators(@canvas.contents, operators)
157
201
  end
158
202
  end
@@ -5,31 +5,31 @@ require 'hexapdf/layout/frame'
5
5
  require 'hexapdf/layout/box'
6
6
  require 'hexapdf/document'
7
7
 
8
- describe HexaPDF::Layout::Frame do
8
+ describe HexaPDF::Layout::Frame::FitResult do
9
9
  it "shows the box's mask area on #draw when using debug output" do
10
10
  doc = HexaPDF::Document.new(config: {'debug' => true})
11
11
  canvas = doc.pages.add.canvas
12
12
  box = HexaPDF::Layout::Box.create(width: 20, height: 20) {}
13
13
  result = HexaPDF::Layout::Frame::FitResult.new(box)
14
- result.mask = Geom2D::Polygon([0, 0], [0, 20], [20, 20], [20, 0])
14
+ result.mask = Geom2D::Rectangle(0, 0, 20, 20)
15
15
  result.x = result.y = 0
16
16
  result.draw(canvas)
17
17
  assert_equal(<<~CONTENTS, canvas.contents)
18
+ /OC /P1 BDC
18
19
  q
19
20
  0.0 0.501961 0.0 rg
20
21
  0.0 0.392157 0.0 RG
21
22
  /GS1 gs
22
- 0 0 m
23
- 0 20 l
24
- 20 20 l
25
- 20 0 l
26
- h
23
+ 0 0 20 20 re
27
24
  B
28
25
  Q
26
+ EMC
29
27
  q
30
28
  1 0 0 1 0 0 cm
31
29
  Q
32
30
  CONTENTS
31
+ ocg = doc.optional_content.ocgs.first
32
+ assert_equal([['Debug', ocg]], doc.optional_content.default_configuration[:Order])
33
33
  end
34
34
  end
35
35
 
@@ -38,6 +38,14 @@ describe HexaPDF::Layout::Frame do
38
38
  @frame = HexaPDF::Layout::Frame.new(5, 10, 100, 150)
39
39
  end
40
40
 
41
+ it "allows accessing the context's document" do
42
+ assert_nil(@frame.document)
43
+ context = Minitest::Mock.new
44
+ context.expect(:document, :document)
45
+ assert_equal(:document, HexaPDF::Layout::Frame.new(0, 0, 10, 10, context: context).document)
46
+ context.verify
47
+ end
48
+
41
49
  it "allows access to the bounding box attributes" do
42
50
  assert_equal(5, @frame.left)
43
51
  assert_equal(10, @frame.bottom)
@@ -90,7 +98,11 @@ describe HexaPDF::Layout::Frame do
90
98
  refute_nil(fit_result)
91
99
  @frame.draw(@canvas, fit_result)
92
100
  assert_equal(mask, fit_result.mask.bbox.to_a)
93
- assert_equal(points, @frame.shape.polygons.map(&:to_a))
101
+ if @frame.shape.respond_to?(:polygons)
102
+ assert_equal(points, @frame.shape.polygons.map(&:to_a))
103
+ else
104
+ assert_equal(points, [@frame.shape.to_a])
105
+ end
94
106
  @canvas.verify
95
107
  end
96
108
 
@@ -99,9 +111,9 @@ describe HexaPDF::Layout::Frame do
99
111
  areas.each do |area|
100
112
  @frame.remove_area(
101
113
  case area
102
- when :left then Geom2D::Polygon([10, 10], [10, 110], [20, 110], [20, 10])
103
- when :right then Geom2D::Polygon([100, 10], [100, 110], [110, 110], [110, 10])
104
- when :top then Geom2D::Polygon([10, 110], [110, 110], [110, 100], [10, 100])
114
+ when :left then Geom2D::Rectangle(10, 10, 10, 100)
115
+ when :right then Geom2D::Rectangle(100, 10, 10, 100)
116
+ when :top then Geom2D::Rectangle(10, 100, 100, 10)
105
117
  end
106
118
  )
107
119
  end
@@ -323,7 +335,7 @@ describe HexaPDF::Layout::Frame do
323
335
  end
324
336
 
325
337
  it "can't fit the box if there is no available space" do
326
- @frame.remove_area(Geom2D::Polygon([0, 0], [110, 0], [110, 110], [0, 110]))
338
+ @frame.remove_area(Geom2D::Rectangle(0, 0, 110, 110))
327
339
  box = HexaPDF::Layout::Box.create
328
340
  refute(@frame.fit(box).success?)
329
341
  end
@@ -388,7 +400,7 @@ describe HexaPDF::Layout::Frame do
388
400
  # +--------+
389
401
  it "works for a region with a hole" do
390
402
  frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100)
391
- frame.remove_area(Geom2D::Polygon([20, 20], [80, 20], [80, 80], [20, 80]))
403
+ frame.remove_area(Geom2D::Rectangle(20, 20, 60, 60))
392
404
  check_regions(frame, [[0, 100, 100, 20], [0, 100, 20, 100],
393
405
  [0, 80, 20, 80], [0, 20, 100, 20]])
394
406
  end
@@ -400,7 +412,7 @@ describe HexaPDF::Layout::Frame do
400
412
  # +--------+
401
413
  it "works for a u-shaped frame" do
402
414
  frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100)
403
- frame.remove_area(Geom2D::Polygon([30, 100], [70, 100], [70, 60], [30, 60]))
415
+ frame.remove_area(Geom2D::Rectangle(30, 60, 40, 40))
404
416
  check_regions(frame, [[0, 100, 30, 100], [0, 60, 100, 60]])
405
417
  end
406
418
 
@@ -415,7 +427,7 @@ describe HexaPDF::Layout::Frame do
415
427
  it "works for a complicated frame" do
416
428
  frame = HexaPDF::Layout::Frame.new(0, 0, 100, 100)
417
429
  top_cut = Geom2D::Polygon([20, 100], [20, 80], [40, 80], [40, 90], [60, 90], [60, 100])
418
- left_cut = Geom2D::Polygon([0, 20], [30, 20], [30, 40], [0, 40])
430
+ left_cut = Geom2D::Rectangle(0, 20, 30, 20)
419
431
  frame.remove_area(Geom2D::PolygonSet(top_cut, left_cut))
420
432
 
421
433
  check_regions(frame, [[0, 100, 20, 60], [0, 90, 20, 50], [0, 80, 100, 40],
@@ -56,6 +56,10 @@ describe HexaPDF::Layout::ImageBox do
56
56
  end
57
57
  end
58
58
 
59
+ it "always returns false for empty?" do
60
+ refute(create_box.empty?)
61
+ end
62
+
59
63
  describe "draw" do
60
64
  it "draws the image" do
61
65
  box = create_box(height: 40, style: {padding: [10, 4, 6, 2]})
@@ -24,20 +24,28 @@ describe HexaPDF::Layout::InlineBox do
24
24
  assert_equal(:top, ibox.valign)
25
25
  end
26
26
 
27
+ it "fails if the wrapped box has not width set" do
28
+ box = HexaPDF::Document.new.layout.text("test is not going good")
29
+ assert_raises(HexaPDF::Error) { inline_box(box) }
30
+ end
31
+ end
32
+
33
+ describe "fit_wrapped_box" do
27
34
  it "automatically fits the provided box into a frame" do
28
35
  ibox = inline_box(HexaPDF::Document.new.layout.text("test is going good", width: 20))
36
+ ibox.fit_wrapped_box(nil)
29
37
  assert_equal(20, ibox.width)
30
38
  assert_equal(45, ibox.height)
31
39
  end
32
40
 
33
41
  it "fails if the wrapped box could not be fit" do
34
42
  box = HexaPDF::Document.new.layout.text("test is not going good", width: 1)
35
- assert_raises(HexaPDF::Error) { inline_box(box) }
43
+ assert_raises(HexaPDF::Error) { inline_box(box).fit_wrapped_box(nil) }
36
44
  end
37
45
 
38
46
  it "fails if the height is not set explicitly and during fitting" do
39
47
  assert_raises(HexaPDF::Error) do
40
- inline_box(HexaPDF::Layout::Box.create(width: 10))
48
+ inline_box(HexaPDF::Layout::Box.create(width: 10)).fit_wrapped_box(nil)
41
49
  end
42
50
  end
43
51
  end
@@ -45,7 +53,9 @@ describe HexaPDF::Layout::InlineBox do
45
53
  it "draws the wrapped box at the correct position" do
46
54
  doc = HexaPDF::Document.new
47
55
  canvas = doc.pages.add.canvas
48
- inline_box(doc.layout.text("", width: 20, margin: [15, 10])).draw(canvas, 100, 200)
56
+ box = inline_box(doc.layout.text("", width: 20, margin: [15, 10]))
57
+ box.fit_wrapped_box(nil)
58
+ box.draw(canvas, 100, 200)
49
59
  assert_equal("q\n1 0 0 1 110 -99785 cm\nQ\n", canvas.contents)
50
60
  end
51
61
 
@@ -54,6 +64,10 @@ describe HexaPDF::Layout::InlineBox do
54
64
  refute(HexaPDF::Layout::InlineBox.create(width: 10, height: 15) {}.empty?)
55
65
  end
56
66
 
67
+ it "returns the style of the box" do
68
+ assert_same(@box.box.style, @box.style)
69
+ end
70
+
57
71
  describe "valign" do
58
72
  it "has a default value of :baseline" do
59
73
  assert_equal(:baseline, @box.valign)