hexapdf 0.17.1 → 0.17.2

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 (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