hexapdf 0.32.2 → 0.34.0

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