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,315 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/page_tree_node'
6
+
7
+ describe HexaPDF::Type::PageTreeNode do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @root = @doc.add({Type: :Pages})
11
+ end
12
+
13
+ # Defines the following page tree:
14
+ #
15
+ # @root
16
+ # @kid1
17
+ # @kid11
18
+ # @pages[0]
19
+ # @pages[1]
20
+ # @kid12
21
+ # @pages[2]
22
+ # @pages[3]
23
+ # @pages[4]
24
+ # @pages[5]
25
+ # @kid2
26
+ # @pages[6]
27
+ # @pages[7]
28
+ def define_multilevel_page_tree
29
+ @pages = Array.new(8) { @doc.add({Type: :Page}) }
30
+ @kid1 = @doc.add({Type: :Pages, Parent: @root, Count: 5})
31
+ @kid11 = @doc.add({Type: :Pages, Parent: @kid1})
32
+ @kid11.add_page(@pages[0])
33
+ @kid11.add_page(@pages[1])
34
+ @kid12 = @doc.add({Type: :Pages, Parent: @kid1})
35
+ @kid12.add_page(@pages[2])
36
+ @kid12.add_page(@pages[3])
37
+ @kid12.add_page(@pages[4])
38
+ @kid1[:Kids] << @kid11 << @kid12
39
+ @root[:Kids] << @kid1
40
+
41
+ @pages[5][:Parent] = @root
42
+ @root[:Kids] << @pages[5]
43
+
44
+ @kid2 = @doc.add({Type: :Pages, Parent: @root})
45
+ @kid2.add_page(@pages[6])
46
+ @kid2.add_page(@pages[7])
47
+ @root[:Kids] << @kid2
48
+ @root[:Count] = 8
49
+ end
50
+
51
+ it "must always be indirect" do
52
+ pages = @doc.add({Type: :Pages})
53
+ pages.must_be_indirect = false
54
+ assert(pages.must_be_indirect?)
55
+ end
56
+
57
+ describe "page" do
58
+ before do
59
+ define_multilevel_page_tree
60
+ end
61
+
62
+ it "returns the page for a given index" do
63
+ assert_equal(@pages[0], @root.page(0))
64
+ assert_equal(@pages[3], @root.page(3))
65
+ assert_equal(@pages[5], @root.page(5))
66
+ assert_equal(@pages[7], @root.page(7))
67
+ end
68
+
69
+ it "works with negative indicies counting backwards from the end" do
70
+ assert_equal(@pages[0], @root.page(-8))
71
+ assert_equal(@pages[3], @root.page(-5))
72
+ assert_equal(@pages[5], @root.page(-3))
73
+ assert_equal(@pages[7], @root.page(-1))
74
+ end
75
+
76
+ it "returns nil for bad indices" do
77
+ assert_nil(@root.page(20))
78
+ assert_nil(@root.page(-20))
79
+ end
80
+ end
81
+
82
+ describe "insert_page" do
83
+ it "uses an empty new page when none is provided, respecting the set configuration options" do
84
+ @doc.config['page.default_media_box'] = :A4
85
+ @doc.config['page.default_media_orientation'] = :landscape
86
+ page = @root.insert_page(3)
87
+ assert_equal([page], @root[:Kids].value)
88
+ assert_equal(1, @root.page_count)
89
+ assert_equal(:Page, page[:Type])
90
+ assert_equal(@root, page[:Parent])
91
+ assert_kind_of(HexaPDF::Rectangle, page[:MediaBox])
92
+ assert_equal([0, 0, 842, 595], page[:MediaBox].value)
93
+ assert_equal({}, page[:Resources].value)
94
+ refute(@root.value.key?(:Parent))
95
+ end
96
+
97
+ it "doesn't create a /Resources entry if an inherited one exists" do
98
+ @root[:Resources] = {Font: {F1: nil}}
99
+ page = @root.insert_page(3)
100
+ assert_equal(@root[:Resources], page[:Resources])
101
+ end
102
+
103
+ it "inserts the provided page at the given index" do
104
+ page = @doc.wrap({Type: :Page})
105
+ assert_equal(page, @root.insert_page(3, page))
106
+ assert_equal([page], @root[:Kids].value)
107
+ assert_equal(@root, page[:Parent])
108
+ refute(@root.value.key?(:Parent))
109
+ end
110
+
111
+ it "inserts multiple pages correctly in an empty root node" do
112
+ page3 = @root.insert_page(5)
113
+ page1 = @root.insert_page(0)
114
+ page2 = @root.insert_page(1)
115
+ assert_equal([page1, page2, page3], @root[:Kids].value)
116
+ assert_equal(3, @root.page_count)
117
+ end
118
+
119
+ it "inserts multiple pages correctly in a multilevel page tree" do
120
+ define_multilevel_page_tree
121
+ page = @root.insert_page(2)
122
+ assert_equal([@pages[0], @pages[1], page], @kid11[:Kids].value)
123
+ assert_equal(3, @kid11.page_count)
124
+ assert_equal(6, @kid1.page_count)
125
+ assert_equal(9, @root.page_count)
126
+
127
+ page = @root.insert_page(4)
128
+ assert_equal([@pages[2], page, @pages[3], @pages[4]], @kid12[:Kids].value)
129
+ assert_equal(4, @kid12.page_count)
130
+ assert_equal(7, @kid1.page_count)
131
+ assert_equal(10, @root.page_count)
132
+
133
+ page = @root.insert_page(8)
134
+ assert_equal([@kid1, @pages[5], page, @kid2], @root[:Kids].value)
135
+ assert_equal(11, @root.page_count)
136
+
137
+ page = @root.insert_page(100)
138
+ assert_equal([@kid1, @pages[5], @root[:Kids][2], @kid2, page], @root[:Kids].value)
139
+ assert_equal(12, @root.page_count)
140
+ end
141
+
142
+ it "allows negative indices to be specified" do
143
+ define_multilevel_page_tree
144
+ page = @root.insert_page(-1)
145
+ assert_equal(page, @root[:Kids][-1])
146
+
147
+ page = @root.insert_page(-4)
148
+ assert_equal(page, @root[:Kids][2])
149
+ end
150
+ end
151
+
152
+ describe "delete_page" do
153
+ before do
154
+ define_multilevel_page_tree
155
+ end
156
+
157
+ it "deletes the correct page by index" do
158
+ @root.delete_page(2)
159
+ assert_equal(2, @kid12.page_count)
160
+ assert_equal(4, @kid1.page_count)
161
+ assert_equal(7, @root.page_count)
162
+ assert(@pages[2].null?)
163
+
164
+ @root.delete_page(4)
165
+ assert_equal(6, @root.page_count)
166
+ assert(@pages[5].null?)
167
+ end
168
+
169
+ it "deletes the given page" do
170
+ @root.delete_page(@pages[2])
171
+ assert(@pages[2].null?)
172
+ @root.delete_page(@pages[5])
173
+ assert(@pages[5].null?)
174
+ end
175
+
176
+ it "allows deleting a page from an intermediary node" do
177
+ @kid1.delete_page(@pages[2])
178
+ assert_equal(7, @root.page_count)
179
+ end
180
+
181
+ it "does nothing if the page index is not valid" do
182
+ @root.delete_page(20)
183
+ @root.delete_page(-20)
184
+ assert_equal(8, @root.page_count)
185
+ end
186
+
187
+ it "does nothing if the page has already been deleted" do
188
+ @root.delete_page(@pages[2])
189
+ @root.delete_page(@pages[2])
190
+ assert_equal(7, @root.page_count)
191
+ end
192
+
193
+ it "fails if the page is not in its parent's /Kids array" do
194
+ @kid12[:Kids].delete_at(0)
195
+ assert_raises(HexaPDF::Error) { @root.delete_page(@pages[2]) }
196
+ assert_equal(8, @root.page_count)
197
+ end
198
+
199
+ it "does nothing if the page is not part of the page tree" do
200
+ pages = @doc.add({Type: :Pages, Count: 1})
201
+ page = @doc.add({Type: :Page, Parent: pages})
202
+ pages[:Kids] << page
203
+
204
+ @root.delete_page(page)
205
+ assert_equal(8, @root.page_count)
206
+ end
207
+ end
208
+
209
+ describe "move_page" do
210
+ before do
211
+ define_multilevel_page_tree
212
+ end
213
+
214
+ it "moves the page to the first place" do
215
+ @root.move_page(@pages[1], 0)
216
+ assert_equal([@pages[1], @pages[0], *@pages[2..-1]], @root.each_page.to_a)
217
+ assert(@root.validate)
218
+ end
219
+
220
+ it "moves the page to the correct location with a positive index" do
221
+ @root.move_page(1, 3)
222
+ assert_equal([@pages[0], @pages[2], @pages[1], *@pages[3..-1]], @root.each_page.to_a)
223
+ assert(@root.validate)
224
+ end
225
+
226
+ it "moves the page to the last place" do
227
+ @root.move_page(1, -1)
228
+ assert_equal([@pages[0], *@pages[2..-1], @pages[1]], @root.each_page.to_a)
229
+ assert(@root.validate)
230
+ end
231
+
232
+ it "fails if the index to the moving page is invalid" do
233
+ assert_raises(HexaPDF::Error) { @root.move_page(10, 0) }
234
+ end
235
+
236
+ it "fails if the moving page was deleted/is null" do
237
+ @doc.delete(@pages[0])
238
+ assert_raises(HexaPDF::Error) { @root.move_page(@pages[0], 3) }
239
+ end
240
+
241
+ it "fails if the page was not yet added to a page tree" do
242
+ page = @doc.add({Type: :Page})
243
+ assert_raises(HexaPDF::Error) { @root.move_page(page, 3) }
244
+ end
245
+
246
+ it "fails if the page is not part of the page tree" do
247
+ assert_raises(HexaPDF::Error) { @kid1.move_page(@pages[6], 3) }
248
+ end
249
+ end
250
+
251
+ describe "each_page" do
252
+ before do
253
+ define_multilevel_page_tree
254
+ end
255
+
256
+ it "iterates over a simple, one-level page tree" do
257
+ assert_equal([@pages[2], @pages[3], @pages[4]], @kid12.each_page.to_a)
258
+ end
259
+
260
+ it "iterates over a multilevel page tree" do
261
+ assert_equal(@pages, @root.each_page.to_a)
262
+ end
263
+ end
264
+
265
+ describe "validation" do
266
+ it "corrects faulty /Count entries" do
267
+ define_multilevel_page_tree
268
+ root_count = @root.page_count
269
+ @root[:Count] = -5
270
+ kid_count = @kid12.page_count
271
+ @kid12[:Count] = 100
272
+
273
+ called_msg = ''
274
+ refute(@root.validate(auto_correct: false) {|msg, _| called_msg = msg })
275
+ assert_match(/Count.*invalid/, called_msg)
276
+
277
+ assert(@root.validate)
278
+ assert_equal(root_count, @root.page_count)
279
+ assert_equal(kid_count, @kid12.page_count)
280
+ end
281
+
282
+ it "corrects faulty /Parent entries" do
283
+ define_multilevel_page_tree
284
+ @kid12.delete(:Parent)
285
+ @kid2.delete(:Parent)
286
+
287
+ called_msg = ''
288
+ refute(@root.validate(auto_correct: false) {|msg, _| called_msg = msg })
289
+ assert_match(/Parent.*invalid/, called_msg)
290
+
291
+ assert(@root.validate)
292
+ assert_equal(@kid1, @kid12[:Parent])
293
+ assert_equal(@root, @kid2[:Parent])
294
+ end
295
+
296
+ it "removes invalid objects from the page tree (like null objects)" do
297
+ define_multilevel_page_tree
298
+ assert(@root.validate(auto_correct: false) {|m, _| p m })
299
+
300
+ @doc.delete(@pages[3])
301
+ refute(@root.validate(auto_correct: false)) do |msg, _|
302
+ assert_match(/invalid object/i, msg)
303
+ end
304
+ assert(@root.validate)
305
+ assert_equal(2, @kid12[:Count])
306
+ assert_equal([@pages[2], @pages[4]], @kid12[:Kids].value)
307
+ end
308
+
309
+ it "needs at least one page node" do
310
+ refute(@root.validate(auto_correct: false))
311
+ assert(@root.validate)
312
+ assert_equal(1, @root.page_count)
313
+ end
314
+ end
315
+ end
@@ -0,0 +1,209 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/type/resources'
5
+ require 'hexapdf/document'
6
+
7
+ describe HexaPDF::Type::Resources do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @res = HexaPDF::Type::Resources.new({}, document: @doc)
11
+ end
12
+
13
+ describe "color_space" do
14
+ it "works for device color spaces" do
15
+ assert_equal(HexaPDF::Content::ColorSpace::DeviceRGB::DEFAULT,
16
+ @res.color_space(:DeviceRGB))
17
+ assert_equal(HexaPDF::Content::ColorSpace::DeviceCMYK::DEFAULT,
18
+ @res.color_space(:DeviceCMYK))
19
+ assert_equal(HexaPDF::Content::ColorSpace::DeviceGray::DEFAULT,
20
+ @res.color_space(:DeviceGray))
21
+ end
22
+
23
+ it "works for color spaces defined only with a name" do
24
+ @res[:ColorSpace] = {CSName: :Pattern}
25
+ assert_kind_of(HexaPDF::Content::ColorSpace::Universal, @res.color_space(:CSName))
26
+ end
27
+
28
+ it "works for the Pattern color space" do
29
+ assert_kind_of(HexaPDF::Content::ColorSpace::Universal, @res.color_space(:Pattern))
30
+ end
31
+
32
+ it "returns the universal color space for unknown color spaces, with resolved references" do
33
+ data = @doc.add({Some: :data})
34
+ @res[:ColorSpace] = {CSName: [:SomeUnknownColorSpace,
35
+ HexaPDF::Reference.new(data.oid, data.gen)]}
36
+ color_space = @res.color_space(:CSName)
37
+ assert_kind_of(HexaPDF::Content::ColorSpace::Universal, color_space)
38
+ assert_equal([:SomeUnknownColorSpace, data], color_space.definition)
39
+ end
40
+
41
+ it "fails if the specified name is neither a device color space nor in the dictionary" do
42
+ assert_raises(HexaPDF::Error) { @res.color_space(:UnknownColorSpace) }
43
+ end
44
+ end
45
+
46
+ describe "add_color_space" do
47
+ it "returns device color spaces without adding an entry" do
48
+ [HexaPDF::Content::ColorSpace::DeviceRGB::DEFAULT,
49
+ HexaPDF::Content::ColorSpace::DeviceCMYK::DEFAULT,
50
+ HexaPDF::Content::ColorSpace::DeviceGray::DEFAULT].each do |space|
51
+ name = @res.add_color_space(space)
52
+ assert_equal(space.family, name)
53
+ refute(@res.key?(:ColorSpace))
54
+ end
55
+ end
56
+
57
+ it "adds a color space that is not a device color space" do
58
+ space = HexaPDF::Content::ColorSpace::Universal.new([:DeviceN, :data])
59
+ name = @res.add_color_space(space)
60
+ assert(@res[:ColorSpace].key?(name))
61
+ assert_equal(space.definition, @res[:ColorSpace][name])
62
+ assert_equal(space, @res.color_space(name))
63
+ end
64
+
65
+ it "doesn't add the same color space twice" do
66
+ object = @doc.add({some: :data})
67
+ @res[:ColorSpace] = {space: [:DeviceN, HexaPDF::Reference.new(object.oid, object.gen)]}
68
+ space = HexaPDF::Content::ColorSpace::Universal.new([:DeviceN, object])
69
+ name = @res.add_color_space(space)
70
+ assert_equal(:space, name)
71
+ assert_equal(1, @res[:ColorSpace].value.size)
72
+ assert_equal(space, @res.color_space(name))
73
+ end
74
+
75
+ it "uses a unique color space name" do
76
+ @res[:ColorSpace] = {CS2: [:DeviceN, :test]}
77
+ space = HexaPDF::Content::ColorSpace::Universal.new([:DeviceN, :data])
78
+ name = @res.add_color_space(space)
79
+ refute_equal(:CS2, name)
80
+ assert_equal(2, @res[:ColorSpace].value.size)
81
+ end
82
+ end
83
+
84
+ describe "private object_getter" do
85
+ it "returns the named entry under the dictionary" do
86
+ @res[:XObject] = {name: :value}
87
+ assert_equal(:value, @res.xobject(:name))
88
+ end
89
+
90
+ it "fails if the specified name is not in the dictionary" do
91
+ assert_raises(HexaPDF::Error) { @res.xobject(:UnknownXObject) }
92
+ end
93
+ end
94
+
95
+ describe "private object_setter" do
96
+ it "adds the object to the specified subdictionary" do
97
+ obj = @doc.add({some: :xobject})
98
+ name = @res.add_xobject(obj)
99
+ assert(@res[:XObject].key?(name))
100
+ assert_equal(obj, @res[:XObject][name])
101
+ end
102
+
103
+ it "doesn't add the same object twice" do
104
+ obj = @doc.add({some: :xobject})
105
+ name = @res.add_xobject(obj)
106
+ name2 = @res.add_xobject(obj)
107
+ assert_equal(name, name2)
108
+ end
109
+ end
110
+
111
+ describe "xobject" do
112
+ it "invokes the object_getter method" do
113
+ assert_method_invoked(@res, :object_getter, [:XObject, :test]) do
114
+ @res.xobject(:test)
115
+ end
116
+ end
117
+ end
118
+
119
+ describe "add_xobject" do
120
+ it "invokes the object_setter method" do
121
+ assert_method_invoked(@res, :object_setter, [:XObject, 'XO', :test]) do
122
+ @res.add_xobject(:test)
123
+ end
124
+ end
125
+ end
126
+
127
+ describe "ext_gstate" do
128
+ it "invokes the object_getter method" do
129
+ assert_method_invoked(@res, :object_getter, [:ExtGState, :test]) do
130
+ @res.ext_gstate(:test)
131
+ end
132
+ end
133
+ end
134
+
135
+ describe "add_ext_gstate" do
136
+ it "invokes the object_setter method" do
137
+ assert_method_invoked(@res, :object_setter, [:ExtGState, 'GS', :test]) do
138
+ @res.add_ext_gstate(:test)
139
+ end
140
+ end
141
+ end
142
+
143
+ describe "font" do
144
+ it "invokes the object_getter method" do
145
+ assert_method_invoked(@res, :object_getter, [:Font, :test]) do
146
+ @res.font(:test)
147
+ end
148
+ end
149
+ end
150
+
151
+ describe "add_font" do
152
+ it "invokes the object_setter method" do
153
+ assert_method_invoked(@res, :object_setter, [:Font, 'F', :test]) do
154
+ @res.add_font(:test)
155
+ end
156
+ end
157
+ end
158
+
159
+ describe "property_list" do
160
+ it "invokes the object_getter method" do
161
+ assert_method_invoked(@res, :object_getter, [:Properties, :test]) do
162
+ @res.property_list(:test)
163
+ end
164
+ end
165
+ end
166
+
167
+ describe "add_property_list" do
168
+ it "invokes the object_setter method" do
169
+ assert_method_invoked(@res, :object_setter, [:Properties, 'P', :test]) do
170
+ @res.add_property_list(:test)
171
+ end
172
+ end
173
+ end
174
+
175
+ describe "pattern" do
176
+ it "invokes the object_getter method" do
177
+ assert_method_invoked(@res, :object_getter, [:Pattern, :test]) do
178
+ @res.pattern(:test)
179
+ end
180
+ end
181
+ end
182
+
183
+ describe "add_pattern" do
184
+ it "invokes the object_setter method" do
185
+ assert_method_invoked(@res, :object_setter, [:Pattern, 'P', :test]) do
186
+ @res.add_pattern(:test)
187
+ end
188
+ end
189
+ end
190
+
191
+ describe "validation" do
192
+ it "assigns the default value if ProcSet is not set" do
193
+ @res.validate
194
+ assert_equal([:PDF, :Text, :ImageB, :ImageC, :ImageI], @res[:ProcSet].value)
195
+ end
196
+
197
+ it "handles an invalid ProcSet containing a single value instead of an array" do
198
+ @res[:ProcSet] = :PDF
199
+ @res.validate
200
+ assert_equal([:PDF], @res[:ProcSet].value)
201
+ end
202
+
203
+ it "removes invalid procedure set names from ProcSet" do
204
+ @res[:ProcSet] = [:PDF, :Unknown]
205
+ @res.validate
206
+ assert_equal([:PDF], @res[:ProcSet].value)
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,116 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/type/trailer'
5
+ require 'hexapdf/object'
6
+ require 'hexapdf/type'
7
+
8
+ describe HexaPDF::Type::Trailer do
9
+ before do
10
+ @doc = Object.new
11
+ @doc.instance_variable_set(:@version, '1.2')
12
+
13
+ def (@doc).version=(v); @version = v; end
14
+
15
+ def (@doc).deref(obj); obj; end
16
+
17
+ def (@doc).wrap(obj, *)
18
+ (obj.kind_of?(Array) ? HexaPDF::PDFArray : HexaPDF::Dictionary).
19
+ new(obj, oid: (obj.oid rescue 0))
20
+ end
21
+
22
+ root = HexaPDF::Dictionary.new({}, oid: 3)
23
+ @obj = HexaPDF::Type::Trailer.new({Size: 10, Root: root}, document: @doc)
24
+ end
25
+
26
+ it "returns the catalog object, creating it if needed" do
27
+ doc = Minitest::Mock.new
28
+ doc.expect(:add, :val, [{Type: :Catalog}])
29
+ trailer = HexaPDF::Type::Trailer.new({}, document: doc)
30
+ assert_equal(:val, trailer.catalog)
31
+ doc.verify
32
+ assert_equal(:val, trailer.value[:Root])
33
+ end
34
+
35
+ it "returns the info object, creating it if needed" do
36
+ doc = Minitest::Mock.new
37
+ doc.expect(:add, :val, [{}, type: :XXInfo])
38
+ trailer = HexaPDF::Type::Trailer.new({}, document: doc)
39
+ assert_equal(:val, trailer.info)
40
+ doc.verify
41
+ assert_equal(:val, trailer.value[:Info])
42
+ end
43
+
44
+ describe "ID field" do
45
+ it "sets a random ID" do
46
+ @obj.set_random_id
47
+ assert_kind_of(HexaPDF::PDFArray, @obj[:ID])
48
+ assert_equal(2, @obj[:ID].length)
49
+ assert_same(@obj[:ID][0], @obj[:ID][1])
50
+ assert_kind_of(String, @obj[:ID][0])
51
+ end
52
+
53
+ it "updates the ID field" do
54
+ @obj.update_id
55
+ assert_same(@obj[:ID][0], @obj[:ID][1])
56
+
57
+ @obj[:ID] = 5
58
+ @obj.update_id
59
+ assert_same(@obj[:ID][0], @obj[:ID][1])
60
+
61
+ @obj.update_id
62
+ refute_same(@obj[:ID][0], @obj[:ID][1])
63
+ end
64
+ end
65
+
66
+ describe "validation" do
67
+ it "validates and corrects a missing ID entry" do
68
+ @obj.validate do |msg, correctable|
69
+ assert(correctable)
70
+ assert_match(/ID.*be set/, msg)
71
+ end
72
+ refute_nil(@obj[:ID])
73
+ end
74
+
75
+ it "validates and corrects a missing ID entry when an Encrypt dictionary is set" do
76
+ def (@doc).security_handler
77
+ obj = Object.new
78
+ def obj.encryption_key_valid?; true; end
79
+ obj
80
+ end
81
+ @obj[:Encrypt] = {}
82
+ @obj.validate do |msg, correctable|
83
+ assert(correctable)
84
+ assert_match(/ID.*Encrypt/, msg)
85
+ end
86
+ refute_nil(@obj[:ID])
87
+ end
88
+
89
+ it "corrects a missing Catalog entry" do
90
+ @obj.delete(:Root)
91
+ @obj.set_random_id
92
+ def (@doc).add(val) HexaPDF::Object.new(val, oid: 3) end
93
+
94
+ message = ''
95
+ refute(@obj.validate(auto_correct: false) {|m, _| message = m })
96
+ assert_match(/Catalog/, message)
97
+ assert(@obj.validate)
98
+ end
99
+
100
+ it "fails if the Encrypt dictionary is set but no security handler is available" do
101
+ def (@doc).security_handler; nil; end
102
+ @obj[:Encrypt] = {}
103
+ refute(@obj.validate)
104
+ end
105
+
106
+ it "fails if the Encrypt dictionary is set but the security handler's key is wrong" do
107
+ def (@doc).security_handler
108
+ obj = Object.new
109
+ def obj.encryption_key_valid?; false; end
110
+ obj
111
+ end
112
+ @obj[:Encrypt] = {}
113
+ refute(@obj.validate)
114
+ end
115
+ end
116
+ end