hexapdf 0.17.1 → 0.17.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (255) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -0
  3. data/LICENSE +29 -0
  4. data/README.md +129 -0
  5. data/Rakefile +109 -0
  6. data/agpl-3.0.txt +661 -0
  7. data/examples/001-hello_world.rb +16 -0
  8. data/examples/002-graphics.rb +275 -0
  9. data/examples/003-arcs.rb +50 -0
  10. data/examples/004-optimizing.rb +23 -0
  11. data/examples/005-merging.rb +27 -0
  12. data/examples/006-standard_pdf_fonts.rb +73 -0
  13. data/examples/007-truetype.rb +42 -0
  14. data/examples/008-show_char_bboxes.rb +55 -0
  15. data/examples/009-text_layouter_alignment.rb +47 -0
  16. data/examples/010-text_layouter_inline_boxes.rb +64 -0
  17. data/examples/011-text_layouter_line_wrapping.rb +57 -0
  18. data/examples/012-text_layouter_styling.rb +122 -0
  19. data/examples/013-text_layouter_shapes.rb +176 -0
  20. data/examples/014-text_in_polygon.rb +60 -0
  21. data/examples/015-boxes.rb +76 -0
  22. data/examples/016-frame_automatic_box_placement.rb +90 -0
  23. data/examples/017-frame_text_flow.rb +60 -0
  24. data/examples/018-composer.rb +44 -0
  25. data/examples/019-acro_form.rb +88 -0
  26. data/examples/emoji-smile.png +0 -0
  27. data/examples/emoji-wink.png +0 -0
  28. data/examples/machupicchu.jpg +0 -0
  29. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -0
  30. data/lib/hexapdf/content/graphic_object/geom2d.rb +13 -0
  31. data/lib/hexapdf/version.rb +1 -1
  32. data/test/data/aes-test-vectors/CBCGFSbox-128-decrypt.data.gz +0 -0
  33. data/test/data/aes-test-vectors/CBCGFSbox-128-encrypt.data.gz +0 -0
  34. data/test/data/aes-test-vectors/CBCGFSbox-192-decrypt.data.gz +0 -0
  35. data/test/data/aes-test-vectors/CBCGFSbox-192-encrypt.data.gz +0 -0
  36. data/test/data/aes-test-vectors/CBCGFSbox-256-decrypt.data.gz +0 -0
  37. data/test/data/aes-test-vectors/CBCGFSbox-256-encrypt.data.gz +0 -0
  38. data/test/data/aes-test-vectors/CBCKeySbox-128-decrypt.data.gz +0 -0
  39. data/test/data/aes-test-vectors/CBCKeySbox-128-encrypt.data.gz +0 -0
  40. data/test/data/aes-test-vectors/CBCKeySbox-192-decrypt.data.gz +0 -0
  41. data/test/data/aes-test-vectors/CBCKeySbox-192-encrypt.data.gz +0 -0
  42. data/test/data/aes-test-vectors/CBCKeySbox-256-decrypt.data.gz +0 -0
  43. data/test/data/aes-test-vectors/CBCKeySbox-256-encrypt.data.gz +0 -0
  44. data/test/data/aes-test-vectors/CBCVarKey-128-decrypt.data.gz +0 -0
  45. data/test/data/aes-test-vectors/CBCVarKey-128-encrypt.data.gz +0 -0
  46. data/test/data/aes-test-vectors/CBCVarKey-192-decrypt.data.gz +0 -0
  47. data/test/data/aes-test-vectors/CBCVarKey-192-encrypt.data.gz +0 -0
  48. data/test/data/aes-test-vectors/CBCVarKey-256-decrypt.data.gz +0 -0
  49. data/test/data/aes-test-vectors/CBCVarKey-256-encrypt.data.gz +0 -0
  50. data/test/data/aes-test-vectors/CBCVarTxt-128-decrypt.data.gz +0 -0
  51. data/test/data/aes-test-vectors/CBCVarTxt-128-encrypt.data.gz +0 -0
  52. data/test/data/aes-test-vectors/CBCVarTxt-192-decrypt.data.gz +0 -0
  53. data/test/data/aes-test-vectors/CBCVarTxt-192-encrypt.data.gz +0 -0
  54. data/test/data/aes-test-vectors/CBCVarTxt-256-decrypt.data.gz +0 -0
  55. data/test/data/aes-test-vectors/CBCVarTxt-256-encrypt.data.gz +0 -0
  56. data/test/data/fonts/Ubuntu-Title.ttf +0 -0
  57. data/test/data/images/cmyk.jpg +0 -0
  58. data/test/data/images/fillbytes.jpg +0 -0
  59. data/test/data/images/gray.jpg +0 -0
  60. data/test/data/images/greyscale-1bit.png +0 -0
  61. data/test/data/images/greyscale-2bit.png +0 -0
  62. data/test/data/images/greyscale-4bit.png +0 -0
  63. data/test/data/images/greyscale-8bit.png +0 -0
  64. data/test/data/images/greyscale-alpha-8bit.png +0 -0
  65. data/test/data/images/greyscale-trns-8bit.png +0 -0
  66. data/test/data/images/greyscale-with-gamma1.0.png +0 -0
  67. data/test/data/images/greyscale-with-gamma1.5.png +0 -0
  68. data/test/data/images/indexed-1bit.png +0 -0
  69. data/test/data/images/indexed-2bit.png +0 -0
  70. data/test/data/images/indexed-4bit.png +0 -0
  71. data/test/data/images/indexed-8bit.png +0 -0
  72. data/test/data/images/indexed-alpha-4bit.png +0 -0
  73. data/test/data/images/indexed-alpha-8bit.png +0 -0
  74. data/test/data/images/rgb.jpg +0 -0
  75. data/test/data/images/truecolour-8bit.png +0 -0
  76. data/test/data/images/truecolour-alpha-8bit.png +0 -0
  77. data/test/data/images/truecolour-gama-chrm-8bit.png +0 -0
  78. data/test/data/images/truecolour-srgb-8bit.png +0 -0
  79. data/test/data/images/ycck.jpg +0 -0
  80. data/test/data/minimal.pdf +44 -0
  81. data/test/data/standard-security-handler/README +9 -0
  82. data/test/data/standard-security-handler/bothpwd-aes-128bit-V4.pdf +44 -0
  83. data/test/data/standard-security-handler/bothpwd-aes-256bit-V5.pdf +0 -0
  84. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V2.pdf +43 -0
  85. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V4.pdf +43 -0
  86. data/test/data/standard-security-handler/bothpwd-arc4-40bit-V1.pdf +0 -0
  87. data/test/data/standard-security-handler/nopwd-aes-128bit-V4.pdf +43 -0
  88. data/test/data/standard-security-handler/nopwd-aes-256bit-V5.pdf +0 -0
  89. data/test/data/standard-security-handler/nopwd-arc4-128bit-V2.pdf +43 -0
  90. data/test/data/standard-security-handler/nopwd-arc4-128bit-V4.pdf +43 -0
  91. data/test/data/standard-security-handler/nopwd-arc4-40bit-V1.pdf +43 -0
  92. data/test/data/standard-security-handler/ownerpwd-aes-128bit-V4.pdf +0 -0
  93. data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5.pdf +43 -0
  94. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V2.pdf +43 -0
  95. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V4.pdf +43 -0
  96. data/test/data/standard-security-handler/ownerpwd-arc4-40bit-V1.pdf +43 -0
  97. data/test/data/standard-security-handler/userpwd-aes-128bit-V4.pdf +43 -0
  98. data/test/data/standard-security-handler/userpwd-aes-256bit-V5.pdf +43 -0
  99. data/test/data/standard-security-handler/userpwd-arc4-128bit-V2.pdf +0 -0
  100. data/test/data/standard-security-handler/userpwd-arc4-128bit-V4.pdf +0 -0
  101. data/test/data/standard-security-handler/userpwd-arc4-40bit-V1.pdf +43 -0
  102. data/test/hexapdf/common_tokenizer_tests.rb +236 -0
  103. data/test/hexapdf/content/common.rb +39 -0
  104. data/test/hexapdf/content/graphic_object/test_arc.rb +102 -0
  105. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +90 -0
  106. data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
  107. data/test/hexapdf/content/graphic_object/test_solid_arc.rb +86 -0
  108. data/test/hexapdf/content/test_canvas.rb +1279 -0
  109. data/test/hexapdf/content/test_color_space.rb +176 -0
  110. data/test/hexapdf/content/test_graphics_state.rb +151 -0
  111. data/test/hexapdf/content/test_operator.rb +619 -0
  112. data/test/hexapdf/content/test_parser.rb +99 -0
  113. data/test/hexapdf/content/test_processor.rb +163 -0
  114. data/test/hexapdf/content/test_transformation_matrix.rb +64 -0
  115. data/test/hexapdf/document/test_files.rb +72 -0
  116. data/test/hexapdf/document/test_fonts.rb +60 -0
  117. data/test/hexapdf/document/test_images.rb +72 -0
  118. data/test/hexapdf/document/test_pages.rb +130 -0
  119. data/test/hexapdf/encryption/common.rb +87 -0
  120. data/test/hexapdf/encryption/test_aes.rb +129 -0
  121. data/test/hexapdf/encryption/test_arc4.rb +39 -0
  122. data/test/hexapdf/encryption/test_fast_aes.rb +17 -0
  123. data/test/hexapdf/encryption/test_fast_arc4.rb +12 -0
  124. data/test/hexapdf/encryption/test_identity.rb +21 -0
  125. data/test/hexapdf/encryption/test_ruby_aes.rb +23 -0
  126. data/test/hexapdf/encryption/test_ruby_arc4.rb +20 -0
  127. data/test/hexapdf/encryption/test_security_handler.rb +380 -0
  128. data/test/hexapdf/encryption/test_standard_security_handler.rb +322 -0
  129. data/test/hexapdf/filter/common.rb +53 -0
  130. data/test/hexapdf/filter/test_ascii85_decode.rb +59 -0
  131. data/test/hexapdf/filter/test_ascii_hex_decode.rb +38 -0
  132. data/test/hexapdf/filter/test_crypt.rb +21 -0
  133. data/test/hexapdf/filter/test_encryption.rb +24 -0
  134. data/test/hexapdf/filter/test_flate_decode.rb +44 -0
  135. data/test/hexapdf/filter/test_lzw_decode.rb +52 -0
  136. data/test/hexapdf/filter/test_predictor.rb +219 -0
  137. data/test/hexapdf/filter/test_run_length_decode.rb +32 -0
  138. data/test/hexapdf/font/cmap/test_parser.rb +102 -0
  139. data/test/hexapdf/font/cmap/test_writer.rb +66 -0
  140. data/test/hexapdf/font/encoding/test_base.rb +45 -0
  141. data/test/hexapdf/font/encoding/test_difference_encoding.rb +29 -0
  142. data/test/hexapdf/font/encoding/test_glyph_list.rb +59 -0
  143. data/test/hexapdf/font/encoding/test_zapf_dingbats_encoding.rb +16 -0
  144. data/test/hexapdf/font/test_cmap.rb +104 -0
  145. data/test/hexapdf/font/test_encoding.rb +27 -0
  146. data/test/hexapdf/font/test_invalid_glyph.rb +34 -0
  147. data/test/hexapdf/font/test_true_type_wrapper.rb +186 -0
  148. data/test/hexapdf/font/test_type1_wrapper.rb +107 -0
  149. data/test/hexapdf/font/true_type/common.rb +17 -0
  150. data/test/hexapdf/font/true_type/table/common.rb +27 -0
  151. data/test/hexapdf/font/true_type/table/test_cmap.rb +47 -0
  152. data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +141 -0
  153. data/test/hexapdf/font/true_type/table/test_directory.rb +30 -0
  154. data/test/hexapdf/font/true_type/table/test_glyf.rb +58 -0
  155. data/test/hexapdf/font/true_type/table/test_head.rb +56 -0
  156. data/test/hexapdf/font/true_type/table/test_hhea.rb +26 -0
  157. data/test/hexapdf/font/true_type/table/test_hmtx.rb +30 -0
  158. data/test/hexapdf/font/true_type/table/test_kern.rb +61 -0
  159. data/test/hexapdf/font/true_type/table/test_loca.rb +33 -0
  160. data/test/hexapdf/font/true_type/table/test_maxp.rb +50 -0
  161. data/test/hexapdf/font/true_type/table/test_name.rb +76 -0
  162. data/test/hexapdf/font/true_type/table/test_os2.rb +55 -0
  163. data/test/hexapdf/font/true_type/table/test_post.rb +78 -0
  164. data/test/hexapdf/font/true_type/test_builder.rb +42 -0
  165. data/test/hexapdf/font/true_type/test_font.rb +116 -0
  166. data/test/hexapdf/font/true_type/test_optimizer.rb +26 -0
  167. data/test/hexapdf/font/true_type/test_subsetter.rb +73 -0
  168. data/test/hexapdf/font/true_type/test_table.rb +48 -0
  169. data/test/hexapdf/font/type1/common.rb +6 -0
  170. data/test/hexapdf/font/type1/test_afm_parser.rb +65 -0
  171. data/test/hexapdf/font/type1/test_font.rb +104 -0
  172. data/test/hexapdf/font/type1/test_font_metrics.rb +22 -0
  173. data/test/hexapdf/font/type1/test_pfb_parser.rb +37 -0
  174. data/test/hexapdf/font_loader/test_from_configuration.rb +43 -0
  175. data/test/hexapdf/font_loader/test_from_file.rb +36 -0
  176. data/test/hexapdf/font_loader/test_standard14.rb +33 -0
  177. data/test/hexapdf/image_loader/test_jpeg.rb +93 -0
  178. data/test/hexapdf/image_loader/test_pdf.rb +47 -0
  179. data/test/hexapdf/image_loader/test_png.rb +259 -0
  180. data/test/hexapdf/layout/test_box.rb +154 -0
  181. data/test/hexapdf/layout/test_frame.rb +350 -0
  182. data/test/hexapdf/layout/test_image_box.rb +73 -0
  183. data/test/hexapdf/layout/test_inline_box.rb +71 -0
  184. data/test/hexapdf/layout/test_line.rb +206 -0
  185. data/test/hexapdf/layout/test_style.rb +790 -0
  186. data/test/hexapdf/layout/test_text_box.rb +140 -0
  187. data/test/hexapdf/layout/test_text_fragment.rb +375 -0
  188. data/test/hexapdf/layout/test_text_layouter.rb +758 -0
  189. data/test/hexapdf/layout/test_text_shaper.rb +62 -0
  190. data/test/hexapdf/layout/test_width_from_polygon.rb +109 -0
  191. data/test/hexapdf/task/test_dereference.rb +51 -0
  192. data/test/hexapdf/task/test_optimize.rb +162 -0
  193. data/test/hexapdf/test_composer.rb +258 -0
  194. data/test/hexapdf/test_configuration.rb +93 -0
  195. data/test/hexapdf/test_data_dir.rb +32 -0
  196. data/test/hexapdf/test_dictionary.rb +340 -0
  197. data/test/hexapdf/test_dictionary_fields.rb +269 -0
  198. data/test/hexapdf/test_document.rb +641 -0
  199. data/test/hexapdf/test_filter.rb +100 -0
  200. data/test/hexapdf/test_importer.rb +106 -0
  201. data/test/hexapdf/test_object.rb +258 -0
  202. data/test/hexapdf/test_parser.rb +645 -0
  203. data/test/hexapdf/test_pdf_array.rb +169 -0
  204. data/test/hexapdf/test_rectangle.rb +73 -0
  205. data/test/hexapdf/test_reference.rb +50 -0
  206. data/test/hexapdf/test_revision.rb +188 -0
  207. data/test/hexapdf/test_revisions.rb +196 -0
  208. data/test/hexapdf/test_serializer.rb +195 -0
  209. data/test/hexapdf/test_stream.rb +274 -0
  210. data/test/hexapdf/test_tokenizer.rb +80 -0
  211. data/test/hexapdf/test_type.rb +18 -0
  212. data/test/hexapdf/test_writer.rb +140 -0
  213. data/test/hexapdf/test_xref_section.rb +61 -0
  214. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +795 -0
  215. data/test/hexapdf/type/acro_form/test_button_field.rb +308 -0
  216. data/test/hexapdf/type/acro_form/test_choice_field.rb +220 -0
  217. data/test/hexapdf/type/acro_form/test_field.rb +259 -0
  218. data/test/hexapdf/type/acro_form/test_form.rb +357 -0
  219. data/test/hexapdf/type/acro_form/test_signature_field.rb +38 -0
  220. data/test/hexapdf/type/acro_form/test_text_field.rb +201 -0
  221. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +88 -0
  222. data/test/hexapdf/type/actions/test_launch.rb +24 -0
  223. data/test/hexapdf/type/actions/test_uri.rb +23 -0
  224. data/test/hexapdf/type/annotations/test_markup_annotation.rb +22 -0
  225. data/test/hexapdf/type/annotations/test_text.rb +34 -0
  226. data/test/hexapdf/type/annotations/test_widget.rb +225 -0
  227. data/test/hexapdf/type/test_annotation.rb +97 -0
  228. data/test/hexapdf/type/test_catalog.rb +48 -0
  229. data/test/hexapdf/type/test_cid_font.rb +61 -0
  230. data/test/hexapdf/type/test_file_specification.rb +141 -0
  231. data/test/hexapdf/type/test_font.rb +67 -0
  232. data/test/hexapdf/type/test_font_descriptor.rb +61 -0
  233. data/test/hexapdf/type/test_font_simple.rb +176 -0
  234. data/test/hexapdf/type/test_font_true_type.rb +31 -0
  235. data/test/hexapdf/type/test_font_type0.rb +120 -0
  236. data/test/hexapdf/type/test_font_type1.rb +142 -0
  237. data/test/hexapdf/type/test_font_type3.rb +26 -0
  238. data/test/hexapdf/type/test_form.rb +120 -0
  239. data/test/hexapdf/type/test_image.rb +261 -0
  240. data/test/hexapdf/type/test_info.rb +9 -0
  241. data/test/hexapdf/type/test_object_stream.rb +117 -0
  242. data/test/hexapdf/type/test_page.rb +598 -0
  243. data/test/hexapdf/type/test_page_tree_node.rb +315 -0
  244. data/test/hexapdf/type/test_resources.rb +209 -0
  245. data/test/hexapdf/type/test_trailer.rb +116 -0
  246. data/test/hexapdf/type/test_xref_stream.rb +143 -0
  247. data/test/hexapdf/utils/test_bit_field.rb +63 -0
  248. data/test/hexapdf/utils/test_bit_stream.rb +69 -0
  249. data/test/hexapdf/utils/test_graphics_helpers.rb +37 -0
  250. data/test/hexapdf/utils/test_lru_cache.rb +22 -0
  251. data/test/hexapdf/utils/test_object_hash.rb +120 -0
  252. data/test/hexapdf/utils/test_pdf_doc_encoding.rb +18 -0
  253. data/test/hexapdf/utils/test_sorted_tree_node.rb +239 -0
  254. data/test/test_helper.rb +58 -0
  255. metadata +263 -3
@@ -0,0 +1,641 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'tempfile'
4
+ require 'test_helper'
5
+ require 'hexapdf/document'
6
+ require 'stringio'
7
+
8
+ describe HexaPDF::Document do
9
+ before do
10
+ @io = StringIO.new(<<~EOF)
11
+ %PDF-1.7
12
+ 1 0 obj
13
+ 10
14
+ endobj
15
+
16
+ 2 0 obj
17
+ 20
18
+ endobj
19
+
20
+ 3 0 obj
21
+ 30
22
+ endobj
23
+
24
+ xref
25
+ 0 4
26
+ 0000000000 65535 f
27
+ 0000000009 00000 n
28
+ 0000000028 00000 n
29
+ 0000000047 00000 n
30
+ trailer
31
+ << /Size 4 >>
32
+ startxref
33
+ 66
34
+ %%EOF
35
+
36
+ 2 0 obj
37
+ 200
38
+ endobj
39
+
40
+ xref
41
+ 2 2
42
+ 0000000197 00000 n
43
+ 0000000000 00001 f
44
+ trailer
45
+ << /Size 4 /Prev 66 >>
46
+ startxref
47
+ 217
48
+ %%EOF
49
+ EOF
50
+ @io_doc = HexaPDF::Document.new(io: @io)
51
+ @doc = HexaPDF::Document.new
52
+ end
53
+
54
+ describe "::open" do
55
+ before do
56
+ @file = Tempfile.new('hexapdf-document')
57
+ @io_doc.write(@file)
58
+ @file.close
59
+ end
60
+
61
+ after do
62
+ @file.unlink
63
+ end
64
+
65
+ it "works without block" do
66
+ doc = HexaPDF::Document.open(@file.path)
67
+ assert_equal(200, doc.object(2).value)
68
+ end
69
+
70
+ it "works with a block" do
71
+ HexaPDF::Document.open(@file.path) do |doc|
72
+ assert_equal(200, doc.object(2).value)
73
+ end
74
+ end
75
+ end
76
+
77
+ describe "initialize" do
78
+ it "doesn't need any arguments" do
79
+ doc = HexaPDF::Document.new
80
+ assert_equal(:A4, doc.config['page.default_media_box'])
81
+ end
82
+
83
+ it "takes a configuration hash as option" do
84
+ doc = HexaPDF::Document.new(config: {'page.default_media_box' => :A5})
85
+ assert_equal(:A5, doc.config['page.default_media_box'])
86
+ end
87
+
88
+ it "takes an IO object as option" do
89
+ doc = HexaPDF::Document.new(io: @io)
90
+ assert_equal(10, doc.object(1).value)
91
+ end
92
+ end
93
+
94
+ describe "object" do
95
+ it "accepts a Reference object as argument" do
96
+ assert_equal(10, @io_doc.object(HexaPDF::Reference.new(1, 0)).value)
97
+ end
98
+
99
+ it "accepts an object number as arguments" do
100
+ assert_equal(10, @io_doc.object(1).value)
101
+ end
102
+
103
+ it "returns added objects" do
104
+ obj = @io_doc.add(@io_doc.wrap({Type: :Test}, oid: 100))
105
+ assert_equal(obj, @io_doc.object(100))
106
+ end
107
+
108
+ it "returns nil for unknown object references" do
109
+ assert_nil(@io_doc.object(100))
110
+ end
111
+
112
+ it "returns only the newest version of an object" do
113
+ assert_equal(200, @io_doc.object(2).value)
114
+ assert_equal(200, @io_doc.object(HexaPDF::Reference.new(2, 0)).value)
115
+ assert_nil(@io_doc.object(3).value)
116
+ assert_nil(@io_doc.object(HexaPDF::Reference.new(3, 1)).value)
117
+ assert_equal(30, @io_doc.object(HexaPDF::Reference.new(3, 0)).value)
118
+ end
119
+ end
120
+
121
+ describe "object?" do
122
+ it "works with a Reference object as argument" do
123
+ assert(@io_doc.object?(HexaPDF::Reference.new(1, 0)))
124
+ end
125
+
126
+ it "works with an object number as arguments" do
127
+ assert(@io_doc.object?(1))
128
+ end
129
+ end
130
+
131
+ describe "deref" do
132
+ it "returns a dereferenced object when given a Reference object" do
133
+ assert_equal(@io_doc.object(1), @io_doc.deref(HexaPDF::Reference.new(1, 0)))
134
+ end
135
+
136
+ it "returns the given object when it is not a Reference object" do
137
+ assert_equal(5, @io_doc.deref(5))
138
+ end
139
+ end
140
+
141
+ describe "add" do
142
+ it "automatically assigns free object numbers" do
143
+ assert_equal(1, @doc.add(5).oid)
144
+ assert_equal(2, @doc.add(5).oid)
145
+ @doc.revisions.add
146
+ assert_equal(3, @doc.add(5).oid)
147
+ end
148
+
149
+ it "assigns the object's document" do
150
+ obj = @doc.add(5)
151
+ assert_equal(@doc, obj.document)
152
+ end
153
+
154
+ it "allows adding a native ruby object" do
155
+ obj = @doc.add(5)
156
+ assert_equal(5, obj.value)
157
+ end
158
+
159
+ it "allows passing arguments to the wrap call" do
160
+ obj = @doc.add({}, type: HexaPDF::Dictionary)
161
+ assert_equal(HexaPDF::Dictionary, obj.class)
162
+ end
163
+
164
+ it "allows adding a HexaPDF::Object" do
165
+ obj = @doc.add(HexaPDF::Object.new(5))
166
+ assert_equal(5, obj.value)
167
+ end
168
+
169
+ it "returns the given object if it is already stored in the document" do
170
+ obj = @doc.add(5)
171
+ assert_same(obj, @doc.add(obj))
172
+ end
173
+
174
+ it "allows specifying a revision to which the object should be added" do
175
+ @doc.revisions.add
176
+ @doc.revisions.add
177
+
178
+ @doc.add(@doc.wrap(5, oid: 1), revision: 0)
179
+ assert_equal(5, @doc.object(1).value)
180
+
181
+ @doc.add(@doc.wrap(10, oid: 1), revision: 2)
182
+ assert_equal(10, @doc.object(1).value)
183
+
184
+ @doc.add(@doc.wrap(7.5, oid: 1), revision: 1)
185
+ assert_equal(10, @doc.object(1).value)
186
+ end
187
+
188
+ it "fails if the specified revision index is invalid" do
189
+ assert_raises(ArgumentError) { @doc.add(5, revision: 5) }
190
+ end
191
+
192
+ it "fails if the object to be added is associated with another document" do
193
+ doc = HexaPDF::Document.new
194
+ obj = doc.add(5)
195
+ assert_raises(HexaPDF::Error) { @doc.add(obj) }
196
+ end
197
+
198
+ it "fails if the object number is already associated with another object" do
199
+ obj = @doc.add(5)
200
+ assert_raises(HexaPDF::Error) { @doc.add(@doc.wrap(5, oid: obj.oid, gen: 1)) }
201
+ end
202
+ end
203
+
204
+ describe "delete" do
205
+ it "works with a Reference object as argument" do
206
+ obj = @doc.add(5)
207
+ @doc.delete(obj, mark_as_free: false)
208
+ refute(@doc.object?(obj))
209
+ end
210
+
211
+ it "works with an object number as arguments" do
212
+ @doc.add(5)
213
+ @doc.delete(1, mark_as_free: false)
214
+ refute(@doc.object?(1))
215
+ end
216
+
217
+ describe "with an object in multiple revisions" do
218
+ before do
219
+ @ref = HexaPDF::Reference.new(2, 3)
220
+ obj = @doc.wrap(5, oid: @ref.oid, gen: @ref.gen)
221
+ @doc.revisions.add
222
+ @doc.add(obj, revision: 0)
223
+ @doc.add(obj, revision: 1)
224
+ end
225
+
226
+ it "deletes an object for all revisions when revision = :all" do
227
+ @doc.delete(@ref, revision: :all, mark_as_free: false)
228
+ refute(@doc.object?(@ref))
229
+ end
230
+
231
+ it "deletes an object only in the current revision when revision = :current" do
232
+ @doc.delete(@ref, revision: :current, mark_as_free: false)
233
+ assert(@doc.object?(@ref))
234
+ end
235
+
236
+ it "marks the object as PDF null object when using mark_as_free=true" do
237
+ @doc.delete(@ref, revision: :current)
238
+ assert(@doc.object(@ref).null?)
239
+ end
240
+ end
241
+
242
+ it "fails if the revision argument is invalid" do
243
+ assert_raises(ArgumentError) { @doc.delete(1, revision: :invalid) }
244
+ end
245
+ end
246
+
247
+ describe "import" do
248
+ it "allows importing objects from another document" do
249
+ obj = @doc.import(@io_doc.object(2))
250
+ assert_equal(200, obj.value)
251
+ refute_equal(0, obj.oid)
252
+ end
253
+
254
+ it "fails if the given object is not a PDF object" do
255
+ assert_raises(ArgumentError) { @doc.import(5) }
256
+ end
257
+
258
+ it "fails if the given object is associated with no or the destination document" do
259
+ assert_raises(ArgumentError) { @doc.import(HexaPDF::Object.new(5)) }
260
+ obj = @doc.add(5)
261
+ assert_raises(ArgumentError) { @doc.import(obj) }
262
+ end
263
+ end
264
+
265
+ describe "wrap" do
266
+ before do
267
+ @myclass = Class.new(HexaPDF::Dictionary)
268
+ @myclass.define_type(:MyClass)
269
+ @myclass2 = Class.new(HexaPDF::Dictionary)
270
+ @myclass2.define_field(:Test, type: String, required: true)
271
+ HexaPDF::GlobalConfiguration['object.type_map'][:MyClass] = @myclass
272
+ HexaPDF::GlobalConfiguration['object.subtype_map'][nil][:Global] = @myclass2
273
+ HexaPDF::GlobalConfiguration['object.subtype_map'][:MyClass] = {TheSecond: @myclass2}
274
+ end
275
+
276
+ after do
277
+ HexaPDF::GlobalConfiguration['object.type_map'].delete(:MyClass)
278
+ HexaPDF::GlobalConfiguration['object.subtype_map'][nil].delete(:Global)
279
+ HexaPDF::GlobalConfiguration['object.subtype_map'][:MyClass].delete(:TheSecond)
280
+ end
281
+
282
+ it "uses a suitable default type if no special type is specified" do
283
+ assert_instance_of(HexaPDF::Object, @doc.wrap(5))
284
+ assert_instance_of(HexaPDF::Stream, @doc.wrap({a: 5}, stream: ''))
285
+ assert_instance_of(HexaPDF::Dictionary, @doc.wrap({a: 5}))
286
+ assert_instance_of(HexaPDF::PDFArray, @doc.wrap([1, 2]))
287
+ end
288
+
289
+ it "returns an object of type HexaPDF::Object" do
290
+ assert_kind_of(HexaPDF::Object, @doc.wrap(5))
291
+ assert_kind_of(HexaPDF::Object, @doc.wrap({}, stream: ''))
292
+ end
293
+
294
+ it "associates the returned object with the document" do
295
+ assert_equal(@doc, @doc.wrap(5).document)
296
+ end
297
+
298
+ it "sets the given object (not === HexaPDF::Object) as value for the PDF object" do
299
+ assert_equal(5, @doc.wrap(5).value)
300
+ end
301
+
302
+ it "uses the data of the given PDF object for re-wrapping" do
303
+ obj = @doc.wrap({a: :b}, oid: 10, gen: 20, stream: 'hallo')
304
+ new_obj = @doc.wrap(obj)
305
+ assert_equal({a: :b}, new_obj.value)
306
+ assert_equal('hallo', new_obj.raw_stream)
307
+ assert_equal(10, new_obj.oid)
308
+ assert_equal(20, new_obj.gen)
309
+ refute_same(obj, new_obj)
310
+
311
+ obj = @doc.wrap({a: :b}, oid: 10, gen: 20)
312
+ new_obj = @doc.wrap(obj)
313
+ refute_same(obj, new_obj)
314
+ end
315
+
316
+ it "allows overrding the data of the given PDF object" do
317
+ obj = @doc.wrap({a: :b}, oid: 10, gen: 20, stream: 'hallo')
318
+ new_obj = @doc.wrap(obj, oid: 15, gen: 25, stream: 'not')
319
+ assert_equal('not', new_obj.raw_stream)
320
+ assert_equal(15, new_obj.oid)
321
+ assert_equal(25, new_obj.gen)
322
+ end
323
+
324
+ it "sets the given oid/gen values on the returned object" do
325
+ obj = @doc.wrap(5, oid: 10, gen: 20)
326
+ assert_equal(10, obj.oid)
327
+ assert_equal(20, obj.gen)
328
+ end
329
+
330
+ it "uses the type/subtype information in the hash that should be wrapped" do
331
+ assert_kind_of(@myclass, @doc.wrap({Type: :MyClass}))
332
+ refute_kind_of(@myclass2, @doc.wrap({Subtype: :TheSecond}))
333
+ refute_kind_of(@myclass2, @doc.wrap({Subtype: :Global}))
334
+ assert_kind_of(@myclass2, @doc.wrap({Subtype: :Global, Test: "true"}))
335
+ assert_kind_of(@myclass2, @doc.wrap({Type: :MyClass, S: :TheSecond}))
336
+ assert_kind_of(@myclass, @doc.wrap({Type: :MyClass, Subtype: :TheThird}))
337
+ end
338
+
339
+ it "respects the given type/subtype arguments" do
340
+ assert_kind_of(@myclass, @doc.wrap({Type: :Other}, type: :MyClass))
341
+ refute_kind_of(@myclass2, @doc.wrap({Subtype: :Other}, subtype: :Global))
342
+ assert_kind_of(@myclass2, @doc.wrap({Subtype: :Other, Test: "true"}, subtype: :Global))
343
+ assert_kind_of(@myclass2, @doc.wrap({Type: :Other, Subtype: :Other},
344
+ type: :MyClass, subtype: :TheSecond))
345
+ assert_kind_of(@myclass2, @doc.wrap({Subtype: :TheSecond}, type: @myclass))
346
+ end
347
+
348
+ it "directly uses a class given via the type argument" do
349
+ obj = @doc.wrap({a: :b}, type: @myclass, oid: 5)
350
+ assert_kind_of(@myclass, obj)
351
+ obj = @doc.wrap(obj, type: @myclass2)
352
+ assert_kind_of(@myclass2, obj)
353
+ assert_equal(:b, obj.value[:a])
354
+ assert_equal(5, obj.oid)
355
+ end
356
+ end
357
+
358
+ describe "unwrap" do
359
+ it "returns a simple native ruby type" do
360
+ assert_equal(5, @doc.unwrap(5))
361
+ end
362
+
363
+ it "recursively unwraps arrays" do
364
+ assert_equal([5, 10, [200], [200]],
365
+ @io_doc.unwrap([5, HexaPDF::Reference.new(1, 0), [HexaPDF::Reference.new(2, 0)],
366
+ [HexaPDF::Reference.new(2, 0)]]))
367
+ end
368
+
369
+ it "recursively unwraps hashes" do
370
+ assert_equal({a: 5, b: 10, c: [200], d: [200]},
371
+ @io_doc.unwrap({a: 5, b: HexaPDF::Reference.new(1, 0),
372
+ c: [HexaPDF::Reference.new(2, 0)],
373
+ d: [HexaPDF::Reference.new(2, 0)]}))
374
+ end
375
+
376
+ it "recursively unwraps PDF objects" do
377
+ assert_equal({a: 10}, @io_doc.unwrap(@io_doc.wrap({a: HexaPDF::Reference.new(1, 0)})))
378
+ value = {a: HexaPDF::Object.new({b: HexaPDF::Object.new(10)})}
379
+ assert_equal({a: {b: 10}}, @doc.unwrap(value))
380
+ end
381
+
382
+ it "fails to unwrap recursive structures" do
383
+ obj1 = @doc.add({})
384
+ obj2 = @doc.add({})
385
+ obj1.value[2] = obj2
386
+ obj2.value[1] = obj1
387
+ assert_raises(HexaPDF::Error) { @doc.unwrap({a: obj1}) }
388
+ end
389
+ end
390
+
391
+ describe "each" do
392
+ it "iterates over the current objects" do
393
+ assert_equal([10, 200, nil], @io_doc.each(only_current: true).sort.map(&:value))
394
+ end
395
+
396
+ it "iterates over all objects" do
397
+ assert_equal([10, 200, 20, 30, nil], @io_doc.each(only_current: false).sort.map(&:value))
398
+ end
399
+
400
+ it "iterates over all loaded objects" do
401
+ assert_equal(200, @io_doc.object(2).value)
402
+ assert_equal([200], @io_doc.each(only_loaded: true).sort.map(&:value))
403
+ end
404
+
405
+ it "yields the revision as second argument if the block accepts exactly two arguments" do
406
+ objs = [[10, 20, 30], [200, nil]]
407
+ data = @io_doc.revisions.map.with_index {|rev, i| objs[i].map {|o| [o, rev] } }.reverse.flatten
408
+ @io_doc.each(only_current: false) do |obj, rev|
409
+ assert(data.shift == obj.value)
410
+ assert_equal(data.shift, rev)
411
+ end
412
+ end
413
+ end
414
+
415
+ describe "encryption" do
416
+ it "checks for encryption based on the existence of the trailer's /Encrypt dictionary" do
417
+ refute(@doc.encrypted?)
418
+ @doc.trailer[:Encrypt] = {Filter: :Standard}
419
+ assert(@doc.encrypted?)
420
+ end
421
+
422
+ it "can set or delete a security handler via #encrypt" do
423
+ @doc.encrypt
424
+ refute_nil(@doc.security_handler)
425
+ assert(@doc.encrypted?)
426
+
427
+ @doc.encrypt(name: nil)
428
+ assert_nil(@doc.security_handler)
429
+ refute(@doc.encrypted?)
430
+ end
431
+
432
+ it "doesn't decrypt the document if document.auto_encrypt=false" do
433
+ test_file = File.join(TEST_DATA_DIR, 'standard-security-handler', 'nopwd-arc4-40bit-V1.pdf')
434
+ doc = HexaPDF::Document.new(io: StringIO.new(File.read(test_file)),
435
+ config: {'document.auto_decrypt' => false})
436
+ assert_kind_of(String, doc.trailer[:Info][:ModDate])
437
+ handler = HexaPDF::Encryption::SecurityHandler.set_up_decryption(doc)
438
+ assert_kind_of(Time, handler.decrypt(doc.trailer[:Info])[:ModDate])
439
+ end
440
+ end
441
+
442
+ describe "validate" do
443
+ before do
444
+ @doc.validate # to create a valid document
445
+ end
446
+
447
+ it "validates indirect objects" do
448
+ obj = @doc.add({Type: :Page, MediaBox: [1, 1, 1, 1], Parent: @doc.pages.root})
449
+ refute(@doc.validate(auto_correct: false))
450
+
451
+ called = false
452
+ assert(@doc.validate {|_, _, o| assert_same(obj, o); called = true })
453
+ assert(called)
454
+ end
455
+
456
+ it "validates the trailer object" do
457
+ @doc.trailer[:ID] = :Symbol
458
+ refute(@doc.validate {|_, _, obj| assert_same(@doc.trailer, obj) })
459
+ end
460
+
461
+ it "validates only loaded objects" do
462
+ io = StringIO.new
463
+ doc = HexaPDF::Document.new
464
+ doc.pages.add.delete(:Resources)
465
+ page = doc.pages.add
466
+ page[:Annots] = [doc.add({Type: :Annot, Subtype: :Link, Rect: [0, 0, 1, 1], H: :Z})]
467
+ doc.write(io, validate: false)
468
+ doc = HexaPDF::Document.new(io: io)
469
+ doc.pages[0] # force loading of the first page
470
+
471
+ refute(doc.validate(auto_correct: false, only_loaded: true)) # bc of Resources
472
+ assert(doc.validate(only_loaded: true))
473
+ refute(doc.validate(auto_correct: false)) # bc of annot key H
474
+ end
475
+ end
476
+
477
+ describe "write" do
478
+ it "writes the document to a file" do
479
+ begin
480
+ file = Tempfile.new('hexapdf-write')
481
+ file.close
482
+ @io_doc.write(file.path)
483
+ HexaPDF::Document.open(file.path) do |doc|
484
+ assert_equal(200, doc.object(2).value)
485
+ end
486
+ ensure
487
+ file.unlink
488
+ end
489
+ end
490
+
491
+ it "writes the document to an IO object" do
492
+ io = StringIO.new(''.b)
493
+ @doc.write(io)
494
+ refute(io.string.empty?)
495
+ end
496
+
497
+ it "writes the document incrementally" do
498
+ io = StringIO.new
499
+ @io_doc.write(io, incremental: true)
500
+ assert_equal(@io.string, io.string[0, @io.string.length])
501
+ end
502
+
503
+ it "fails if the document is not valid" do
504
+ @doc.trailer[:Size] = :Symbol
505
+ assert_raises(HexaPDF::Error) { @doc.write(StringIO.new(''.b)) }
506
+ end
507
+
508
+ it "update the ID and the Info's ModDate field" do
509
+ _, id1 = @doc.trailer.set_random_id
510
+
511
+ @doc.write(StringIO.new(''.b), update_fields: false)
512
+ assert_same(id1, @doc.trailer[:ID][1])
513
+ refute(@doc.trailer.info.key?(:ModDate))
514
+
515
+ @doc.write(StringIO.new(''.b))
516
+ refute_same(id1, (id2 = @doc.trailer[:ID][1]))
517
+ assert(@doc.trailer.info.key?(:ModDate))
518
+
519
+ @doc.trailer.info[:Author] = 'Me'
520
+ @doc.write(StringIO.new(''.b))
521
+ refute_same(id2, @doc.trailer[:ID][1])
522
+ assert(@doc.trailer.info.key?(:ModDate))
523
+ assert(@doc.trailer.info.key?(:Author))
524
+ end
525
+
526
+ it "it doesn't optimize the file by default" do
527
+ io = StringIO.new(''.b)
528
+ @io_doc.write(io)
529
+ doc = HexaPDF::Document.new(io: io)
530
+ assert_equal(0, doc.each.count {|o| o.type == :ObjStm })
531
+ end
532
+
533
+ it "allows optimizing the file by using object streams" do
534
+ io = StringIO.new(''.b)
535
+ @io_doc.write(io, optimize: true)
536
+ doc = HexaPDF::Document.new(io: io)
537
+ assert_equal(2, doc.each.count {|o| o.type == :ObjStm })
538
+ end
539
+ end
540
+
541
+ describe "version" do
542
+ it "uses the file header version of a loaded document" do
543
+ assert_equal('1.7', @io_doc.version)
544
+ end
545
+
546
+ it "uses the default version for a new document" do
547
+ assert_equal('1.2', @doc.version)
548
+ end
549
+
550
+ it "uses the catalog's /Version entry if it points to a later version" do
551
+ (@doc.trailer[:Root] ||= {})[:Version] = '1.4'
552
+ assert_equal('1.4', @doc.version)
553
+ end
554
+
555
+ it "allows setting the version" do
556
+ @doc.version = '1.4'
557
+ assert_equal('1.4', @doc.version)
558
+ end
559
+
560
+ it "fails setting a version with an invalid format" do
561
+ assert_raises(ArgumentError) { @doc.version = 'bla' }
562
+ end
563
+ end
564
+
565
+ describe "task" do
566
+ it "executes the given task with options" do
567
+ @doc.config['task.map'][:test] = lambda do |doc, arg1:|
568
+ assert_equal(doc, @doc)
569
+ assert_equal(:arg1, arg1)
570
+ end
571
+ @doc.task(:test, arg1: :arg1)
572
+ end
573
+
574
+ it "executes the given task with a block" do
575
+ @doc.config['task.map'][:test] = lambda do |doc, **, &block|
576
+ assert_equal(doc, @doc)
577
+ block.call('inside')
578
+ end
579
+ assert_equal(:done, @doc.task(:test) {|msg| assert_equal('inside', msg); :done })
580
+ end
581
+
582
+ it "fails if the given task is not available" do
583
+ assert_raises(HexaPDF::Error) { @doc.task(:unknown) }
584
+ end
585
+ end
586
+
587
+ describe "acro_form" do
588
+ it "returns the main AcroForm object" do
589
+ assert_nil(@doc.acro_form)
590
+ @doc.catalog[:AcroForm] = 5
591
+ assert_equal(5, @doc.acro_form)
592
+ end
593
+
594
+ it "create the AcroForm object if instructed" do
595
+ assert_equal(:XXAcroForm, @doc.acro_form(create: true).type)
596
+ end
597
+ end
598
+
599
+ describe "listener interface" do
600
+ it "allows registering and dispatching messages" do
601
+ args = []
602
+ callable = lambda {|*a| args << [:callable, a] }
603
+ @doc.register_listener(:something, callable)
604
+ @doc.register_listener(:something) {|*a| args << [:block, a] }
605
+ @doc.dispatch_message(:something, :arg)
606
+ assert_equal([[:callable, [:arg]], [:block, [:arg]]], args)
607
+ end
608
+ end
609
+
610
+ describe "caching interface" do
611
+ it "allows setting and retrieving values" do
612
+ assert_equal(:test, @doc.cache(:a, :b, :test) { :notused })
613
+ assert_equal(:test, @doc.cache(:a, :b) { :other })
614
+ assert_equal(:test, @doc.cache(:a, :b))
615
+ assert_nil(@doc.cache(:a, :c, nil))
616
+ assert_nil(@doc.cache(:a, :c) { :other })
617
+ assert_nil(@doc.cache(:a, :c))
618
+ assert(@doc.cached?(:a, :b))
619
+ assert(@doc.cached?(:a, :c))
620
+ end
621
+
622
+ it "allows updating a value" do
623
+ @doc.cache(:a, :b) { :test }
624
+ assert_equal(:new, @doc.cache(:a, :b, update: true) { :new })
625
+ end
626
+
627
+ it "allows clearing cached values" do
628
+ @doc.cache(:a, :b) { :c }
629
+ @doc.cache(:b, :c) { :d }
630
+ @doc.clear_cache(:a)
631
+ refute(@doc.cached?(:a, :b))
632
+ assert(@doc.cached?(:b, :c))
633
+ @doc.clear_cache
634
+ refute(@doc.cached?(:a, :c))
635
+ end
636
+
637
+ it "fails if no cached value exists and no block is given" do
638
+ assert_raises(LocalJumpError) { @doc.cache(:a, :b) }
639
+ end
640
+ end
641
+ end