pdfrb 0.3.0 → 0.7.1

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 (422) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +51 -0
  4. data/Gemfile +1 -0
  5. data/PROPOSAL.canvas-clip.md +92 -0
  6. data/PROPOSAL.canvas-draw-image-matrix.md +87 -0
  7. data/PROPOSAL.canvas-rich-text.md +103 -0
  8. data/PROPOSAL.canvas-text-lines.md +77 -0
  9. data/PROPOSAL.canvas-transparency.md +106 -0
  10. data/PROPOSAL.external-font-metrics.md +129 -0
  11. data/PROPOSAL.font-embedding-fontfile2.md +88 -0
  12. data/PROPOSAL.font-resolver-ttc.md +105 -0
  13. data/PROPOSAL.font-resolver.md +96 -0
  14. data/PROPOSAL.font-subsetting.md +98 -0
  15. data/PROPOSAL.gradient-shadings.md +110 -0
  16. data/PROPOSAL.page-mediabox-setter.md +66 -0
  17. data/PROPOSAL.per-glyph-width.md +91 -0
  18. data/PROPOSAL.stringio-require.md +53 -0
  19. data/PROPOSAL.struct-tree-root.md +102 -0
  20. data/PROPOSAL.text-measurement.md +93 -0
  21. data/PROPOSAL.ttf-table-parsing-fix.md +112 -0
  22. data/RELEASE.md +61 -0
  23. data/data/pdfrb/afm/Courier-Bold.afm +342 -342
  24. data/data/pdfrb/afm/Courier-BoldOblique.afm +342 -342
  25. data/data/pdfrb/afm/Courier-Oblique.afm +342 -342
  26. data/data/pdfrb/afm/Courier.afm +342 -342
  27. data/data/pdfrb/afm/Helvetica-Bold.afm +2827 -2827
  28. data/data/pdfrb/afm/Helvetica-BoldOblique.afm +2827 -2827
  29. data/data/pdfrb/afm/Helvetica-Oblique.afm +3051 -3051
  30. data/data/pdfrb/afm/Helvetica.afm +3051 -3051
  31. data/data/pdfrb/afm/Symbol.afm +213 -213
  32. data/data/pdfrb/afm/Times-Bold.afm +2588 -2588
  33. data/data/pdfrb/afm/Times-BoldItalic.afm +2384 -2384
  34. data/data/pdfrb/afm/Times-Italic.afm +2667 -2667
  35. data/data/pdfrb/afm/Times-Roman.afm +2419 -2419
  36. data/data/pdfrb/afm/ZapfDingbats.afm +225 -225
  37. data/lib/pdfrb/action/base.rb +21 -0
  38. data/lib/pdfrb/action/types.rb +178 -0
  39. data/lib/pdfrb/action.rb +55 -0
  40. data/lib/pdfrb/annotation/base.rb +79 -0
  41. data/lib/pdfrb/annotation/types.rb +222 -0
  42. data/lib/pdfrb/annotation.rb +78 -0
  43. data/lib/pdfrb/appearance/generator.rb +216 -0
  44. data/lib/pdfrb/appearance.rb +11 -0
  45. data/lib/pdfrb/arlington/object_definition.rb +2 -2
  46. data/lib/pdfrb/arlington/predicate/evaluator.rb +1 -1
  47. data/lib/pdfrb/arlington/predicate/functions.rb +25 -9
  48. data/lib/pdfrb/arlington/predicate.rb +1 -5
  49. data/lib/pdfrb/cli/batch.rb +18 -0
  50. data/lib/pdfrb/cli/debug_info.rb +18 -0
  51. data/lib/pdfrb/cli/files.rb +16 -0
  52. data/lib/pdfrb/cli/fonts.rb +16 -0
  53. data/lib/pdfrb/cli/form.rb +18 -0
  54. data/lib/pdfrb/cli/image2pdf.rb +18 -0
  55. data/lib/pdfrb/cli/images.rb +34 -0
  56. data/lib/pdfrb/cli/info.rb +20 -0
  57. data/lib/pdfrb/cli/inspect.rb +18 -0
  58. data/lib/pdfrb/cli/merge.rb +18 -0
  59. data/lib/pdfrb/cli/modify.rb +18 -0
  60. data/lib/pdfrb/cli/optimize.rb +18 -0
  61. data/lib/pdfrb/cli/split.rb +21 -0
  62. data/lib/pdfrb/cli/watermark.rb +18 -0
  63. data/lib/pdfrb/cli.rb +291 -7
  64. data/lib/pdfrb/color/color_space.rb +259 -0
  65. data/lib/pdfrb/color/icc_profile.rb +90 -0
  66. data/lib/pdfrb/color.rb +12 -0
  67. data/lib/pdfrb/compare/comparator.rb +244 -0
  68. data/lib/pdfrb/compare/report.rb +78 -0
  69. data/lib/pdfrb/compare.rb +42 -0
  70. data/lib/pdfrb/composer.rb +161 -0
  71. data/lib/pdfrb/conformance/pdf_a.rb +317 -56
  72. data/lib/pdfrb/conformance/pdf_ua.rb +186 -29
  73. data/lib/pdfrb/conformance/pdf_x.rb +247 -0
  74. data/lib/pdfrb/conformance/rule.rb +78 -0
  75. data/lib/pdfrb/conformance/structure_elements.rb +174 -0
  76. data/lib/pdfrb/conformance/verapdf_bridge.rb +92 -0
  77. data/lib/pdfrb/conformance.rb +7 -3
  78. data/lib/pdfrb/content/canvas.rb +354 -72
  79. data/lib/pdfrb/content/color_space.rb +157 -0
  80. data/lib/pdfrb/content/hidden_text_detector.rb +167 -0
  81. data/lib/pdfrb/content/operator.rb +21 -1
  82. data/lib/pdfrb/content/operators/clipping.rb +36 -0
  83. data/lib/pdfrb/content/operators/graphics_state_params.rb +15 -0
  84. data/lib/pdfrb/content/operators/inline_image.rb +53 -0
  85. data/lib/pdfrb/content/operators.rb +22 -0
  86. data/lib/pdfrb/content/shading/base.rb +139 -0
  87. data/lib/pdfrb/content/shading.rb +17 -0
  88. data/lib/pdfrb/content/smart_text_extractor.rb +85 -0
  89. data/lib/pdfrb/content/tiling_pattern.rb +78 -0
  90. data/lib/pdfrb/content/transformation_matrix.rb +61 -0
  91. data/lib/pdfrb/content.rb +7 -26
  92. data/lib/pdfrb/destination/fit.rb +185 -0
  93. data/lib/pdfrb/destination.rb +13 -0
  94. data/lib/pdfrb/digital_signature/cms_handler.rb +22 -0
  95. data/lib/pdfrb/digital_signature/signing.rb +107 -11
  96. data/lib/pdfrb/digital_signature/timestamp_handler.rb +45 -0
  97. data/lib/pdfrb/digital_signature/verification.rb +88 -9
  98. data/lib/pdfrb/digital_signature/verification_result.rb +14 -0
  99. data/lib/pdfrb/digital_signature.rb +26 -2
  100. data/lib/pdfrb/document/annotations.rb +120 -3
  101. data/lib/pdfrb/document/associated_files.rb +104 -0
  102. data/lib/pdfrb/document/colors.rb +95 -0
  103. data/lib/pdfrb/document/destinations.rb +127 -5
  104. data/lib/pdfrb/document/display.rb +84 -0
  105. data/lib/pdfrb/document/fonts.rb +417 -45
  106. data/lib/pdfrb/document/form.rb +338 -0
  107. data/lib/pdfrb/document/graphics_state.rb +91 -0
  108. data/lib/pdfrb/document/images.rb +4 -4
  109. data/lib/pdfrb/document/info.rb +102 -0
  110. data/lib/pdfrb/document/layers.rb +62 -0
  111. data/lib/pdfrb/document/metadata.rb +12 -5
  112. data/lib/pdfrb/document/output_intents.rb +91 -0
  113. data/lib/pdfrb/document/page_labels.rb +80 -0
  114. data/lib/pdfrb/document/pages.rb +68 -19
  115. data/lib/pdfrb/document/portfolio.rb +119 -0
  116. data/lib/pdfrb/document/shadings.rb +159 -0
  117. data/lib/pdfrb/document/stamps.rb +68 -0
  118. data/lib/pdfrb/document/structure.rb +242 -0
  119. data/lib/pdfrb/document.rb +73 -4
  120. data/lib/pdfrb/encryption/identity.rb +13 -0
  121. data/lib/pdfrb/encryption/password_verification.rb +53 -8
  122. data/lib/pdfrb/encryption/security_handler.rb +8 -5
  123. data/lib/pdfrb/encryption/standard_security_handler.rb +154 -81
  124. data/lib/pdfrb/encryption.rb +17 -0
  125. data/lib/pdfrb/error.rb +2 -2
  126. data/lib/pdfrb/filter/brotli_decode.rb +32 -0
  127. data/lib/pdfrb/filter/ccitt_fax_decode.rb +12 -0
  128. data/lib/pdfrb/filter/crypt.rb +2 -2
  129. data/lib/pdfrb/filter/dct_decode.rb +18 -0
  130. data/lib/pdfrb/filter/flate_decode.rb +4 -18
  131. data/lib/pdfrb/filter/jbig2_decode.rb +12 -0
  132. data/lib/pdfrb/filter/jpx_decode.rb +18 -0
  133. data/lib/pdfrb/filter/lzw_decode.rb +3 -2
  134. data/lib/pdfrb/filter/png_predictor.rb +94 -0
  135. data/lib/pdfrb/filter.rb +26 -13
  136. data/lib/pdfrb/font/afm_parser.rb +50 -26
  137. data/lib/pdfrb/font/cmap/writer.rb +9 -0
  138. data/lib/pdfrb/font/encoding/win_ansi_encoding.rb +17 -0
  139. data/lib/pdfrb/font/encoding.rb +32 -3
  140. data/lib/pdfrb/font/metrics_helper.rb +121 -0
  141. data/lib/pdfrb/font/true_type/file.rb +10 -1
  142. data/lib/pdfrb/font/true_type/glyf.rb +82 -0
  143. data/lib/pdfrb/font/true_type/kern.rb +58 -0
  144. data/lib/pdfrb/font/true_type/loca.rb +42 -0
  145. data/lib/pdfrb/font/true_type/maxp.rb +44 -0
  146. data/lib/pdfrb/font/true_type/name.rb +53 -0
  147. data/lib/pdfrb/font/true_type/post.rb +93 -0
  148. data/lib/pdfrb/font/true_type/subsetter.rb +3 -5
  149. data/lib/pdfrb/font/true_type/wrapper.rb +102 -0
  150. data/lib/pdfrb/font/true_type.rb +7 -0
  151. data/lib/pdfrb/font.rb +2 -0
  152. data/lib/pdfrb/font_loader/from_configuration.rb +19 -0
  153. data/lib/pdfrb/font_loader/from_file.rb +20 -0
  154. data/lib/pdfrb/font_loader/standard14.rb +20 -0
  155. data/lib/pdfrb/font_loader/variant_from_name.rb +36 -0
  156. data/lib/pdfrb/font_loader.rb +4 -0
  157. data/lib/pdfrb/font_resolver.rb +138 -0
  158. data/lib/pdfrb/image/audit.rb +94 -0
  159. data/lib/pdfrb/image/downsampler.rb +124 -0
  160. data/lib/pdfrb/image.rb +13 -0
  161. data/lib/pdfrb/image_loader/jpeg.rb +39 -58
  162. data/lib/pdfrb/image_loader/png.rb +82 -251
  163. data/lib/pdfrb/layout/bidi.rb +198 -0
  164. data/lib/pdfrb/layout/box.rb +49 -0
  165. data/lib/pdfrb/layout/box_fitter.rb +54 -0
  166. data/lib/pdfrb/layout/column_box.rb +71 -0
  167. data/lib/pdfrb/layout/container_box.rb +45 -0
  168. data/lib/pdfrb/layout/frame.rb +67 -0
  169. data/lib/pdfrb/layout/image_box.rb +44 -0
  170. data/lib/pdfrb/layout/inline_box.rb +25 -0
  171. data/lib/pdfrb/layout/line.rb +55 -0
  172. data/lib/pdfrb/layout/list_box.rb +87 -0
  173. data/lib/pdfrb/layout/numeric_refinements.rb +43 -0
  174. data/lib/pdfrb/layout/page_style.rb +65 -0
  175. data/lib/pdfrb/layout/style.rb +62 -0
  176. data/lib/pdfrb/layout/table_box.rb +66 -0
  177. data/lib/pdfrb/layout/text_box.rb +38 -0
  178. data/lib/pdfrb/layout/text_fragment.rb +45 -0
  179. data/lib/pdfrb/layout/text_layouter.rb +123 -0
  180. data/lib/pdfrb/layout.rb +28 -0
  181. data/lib/pdfrb/linearization/hint_stream.rb +93 -0
  182. data/lib/pdfrb/linearization/writer.rb +259 -0
  183. data/lib/pdfrb/linearization.rb +21 -0
  184. data/lib/pdfrb/model/cos/dictionary.rb +12 -0
  185. data/lib/pdfrb/model/pdf_array.rb +2 -2
  186. data/lib/pdfrb/model/type/action.rb +42 -0
  187. data/lib/pdfrb/model/type/action_go_to_3d_view.rb +14 -0
  188. data/lib/pdfrb/model/type/action_go_to_dp.rb +16 -0
  189. data/lib/pdfrb/model/type/action_go_to_r.rb +26 -0
  190. data/lib/pdfrb/model/type/action_goto.rb +34 -0
  191. data/lib/pdfrb/model/type/action_hide.rb +26 -0
  192. data/lib/pdfrb/model/type/action_import_data.rb +18 -0
  193. data/lib/pdfrb/model/type/action_java_script.rb +18 -0
  194. data/lib/pdfrb/model/type/action_launch.rb +22 -0
  195. data/lib/pdfrb/model/type/action_movie.rb +22 -0
  196. data/lib/pdfrb/model/type/action_named.rb +23 -0
  197. data/lib/pdfrb/model/type/action_nop.rb +12 -0
  198. data/lib/pdfrb/model/type/action_rendition.rb +28 -0
  199. data/lib/pdfrb/model/type/action_reset_form.rb +22 -0
  200. data/lib/pdfrb/model/type/action_rich_media_execute.rb +17 -0
  201. data/lib/pdfrb/model/type/action_set_ocg_state.rb +32 -0
  202. data/lib/pdfrb/model/type/action_set_state.rb +14 -0
  203. data/lib/pdfrb/model/type/action_sound_action.rb +23 -0
  204. data/lib/pdfrb/model/type/action_submit_form.rb +64 -0
  205. data/lib/pdfrb/model/type/action_thread.rb +23 -0
  206. data/lib/pdfrb/model/type/action_trans.rb +30 -0
  207. data/lib/pdfrb/model/type/action_uri.rb +30 -0
  208. data/lib/pdfrb/model/type/af_embedded_file.rb +11 -0
  209. data/lib/pdfrb/model/type/af_file_specification.rb +34 -0
  210. data/lib/pdfrb/model/type/annotation.rb +49 -10
  211. data/lib/pdfrb/model/type/appearance.rb +41 -0
  212. data/lib/pdfrb/model/type/appearance_characteristics.rb +36 -0
  213. data/lib/pdfrb/model/type/appearance_generator.rb +22 -0
  214. data/lib/pdfrb/model/type/appearance_trap_net.rb +84 -0
  215. data/lib/pdfrb/model/type/border_effect.rb +25 -0
  216. data/lib/pdfrb/model/type/border_styling.rb +38 -0
  217. data/lib/pdfrb/model/type/box_color_info.rb +21 -0
  218. data/lib/pdfrb/model/type/box_style.rb +19 -0
  219. data/lib/pdfrb/model/type/button.rb +17 -0
  220. data/lib/pdfrb/model/type/cal_gray.rb +17 -0
  221. data/lib/pdfrb/model/type/cal_rgb.rb +22 -0
  222. data/lib/pdfrb/model/type/caret_annotation.rb +21 -0
  223. data/lib/pdfrb/model/type/catalog.rb +99 -16
  224. data/lib/pdfrb/model/type/choice.rb +21 -0
  225. data/lib/pdfrb/model/type/cid_font.rb +52 -1
  226. data/lib/pdfrb/model/type/cid_font_descriptor_metrics.rb +28 -0
  227. data/lib/pdfrb/model/type/cid_system_info.rb +28 -0
  228. data/lib/pdfrb/model/type/circle_annotation.rb +20 -0
  229. data/lib/pdfrb/model/type/collection.rb +27 -0
  230. data/lib/pdfrb/model/type/collection_field.rb +24 -0
  231. data/lib/pdfrb/model/type/collection_item.rb +19 -0
  232. data/lib/pdfrb/model/type/collection_schema.rb +25 -0
  233. data/lib/pdfrb/model/type/collection_sort.rb +13 -0
  234. data/lib/pdfrb/model/type/crypt_filter.rb +58 -0
  235. data/lib/pdfrb/model/type/device_n.rb +37 -0
  236. data/lib/pdfrb/model/type/device_n_mixing_hints.rb +17 -0
  237. data/lib/pdfrb/model/type/device_n_process.rb +13 -0
  238. data/lib/pdfrb/model/type/doc_mdp_transform_parameters.rb +21 -0
  239. data/lib/pdfrb/model/type/document_security_store.rb +55 -0
  240. data/lib/pdfrb/model/type/embedded_file.rb +19 -0
  241. data/lib/pdfrb/model/type/embedded_file_parameter.rb +48 -0
  242. data/lib/pdfrb/model/type/encryption_public_key.rb +38 -0
  243. data/lib/pdfrb/model/type/encryption_standard.rb +68 -0
  244. data/lib/pdfrb/model/type/field_mdp_transform_parameters.rb +26 -0
  245. data/lib/pdfrb/model/type/file_attachment_annotation.rb +29 -0
  246. data/lib/pdfrb/model/type/file_spec_ef.rb +22 -0
  247. data/lib/pdfrb/model/type/file_spec_rf.rb +21 -0
  248. data/lib/pdfrb/model/type/file_specification.rb +43 -0
  249. data/lib/pdfrb/model/type/file_trailer.rb +54 -0
  250. data/lib/pdfrb/model/type/font.rb +73 -2
  251. data/lib/pdfrb/model/type/font_cid_type0.rb +17 -0
  252. data/lib/pdfrb/model/type/font_cid_type2.rb +17 -0
  253. data/lib/pdfrb/model/type/font_descriptor.rb +47 -0
  254. data/lib/pdfrb/model/type/font_descriptor_cid_type0.rb +15 -0
  255. data/lib/pdfrb/model/type/font_descriptor_cid_type2.rb +15 -0
  256. data/lib/pdfrb/model/type/font_encoding.rb +47 -0
  257. data/lib/pdfrb/model/type/font_file.rb +28 -0
  258. data/lib/pdfrb/model/type/font_file2.rb +32 -0
  259. data/lib/pdfrb/model/type/font_file3.rb +23 -0
  260. data/lib/pdfrb/model/type/font_multiple_master.rb +60 -0
  261. data/lib/pdfrb/model/type/font_true_type.rb +1 -1
  262. data/lib/pdfrb/model/type/font_type0.rb +1 -1
  263. data/lib/pdfrb/model/type/font_type1.rb +1 -1
  264. data/lib/pdfrb/model/type/font_type3.rb +1 -1
  265. data/lib/pdfrb/model/type/form_field.rb +73 -0
  266. data/lib/pdfrb/model/type/free_text_annotation.rb +37 -0
  267. data/lib/pdfrb/model/type/function.rb +42 -0
  268. data/lib/pdfrb/model/type/function_exponential.rb +31 -0
  269. data/lib/pdfrb/model/type/function_postscript.rb +25 -0
  270. data/lib/pdfrb/model/type/function_sampled.rb +55 -0
  271. data/lib/pdfrb/model/type/function_stitching.rb +33 -0
  272. data/lib/pdfrb/model/type/generic_appearance.rb +40 -0
  273. data/lib/pdfrb/model/type/graphics_state_parameter.rb +69 -0
  274. data/lib/pdfrb/model/type/halftone.rb +21 -0
  275. data/lib/pdfrb/model/type/halftone_type1.rb +17 -0
  276. data/lib/pdfrb/model/type/halftone_type10.rb +16 -0
  277. data/lib/pdfrb/model/type/halftone_type16.rb +11 -0
  278. data/lib/pdfrb/model/type/halftone_type5.rb +24 -0
  279. data/lib/pdfrb/model/type/halftone_type6.rb +16 -0
  280. data/lib/pdfrb/model/type/highlight_annotation.rb +11 -0
  281. data/lib/pdfrb/model/type/icc_based_color_space.rb +24 -0
  282. data/lib/pdfrb/model/type/icon_fit.rb +31 -0
  283. data/lib/pdfrb/model/type/indexed.rb +32 -0
  284. data/lib/pdfrb/model/type/info.rb +44 -0
  285. data/lib/pdfrb/model/type/ink_annotation.rb +29 -0
  286. data/lib/pdfrb/model/type/interactive_form.rb +36 -0
  287. data/lib/pdfrb/model/type/interior_color.rb +12 -0
  288. data/lib/pdfrb/model/type/jbig2_globals.rb +15 -0
  289. data/lib/pdfrb/model/type/lab.rb +27 -0
  290. data/lib/pdfrb/model/type/line_annotation.rb +44 -0
  291. data/lib/pdfrb/model/type/line_ending_styling.rb +39 -0
  292. data/lib/pdfrb/model/type/link_annotation.rb +41 -0
  293. data/lib/pdfrb/model/type/link_appearance.rb +9 -0
  294. data/lib/pdfrb/model/type/mark_information.rb +23 -0
  295. data/lib/pdfrb/model/type/marked_content_reference.rb +24 -0
  296. data/lib/pdfrb/model/type/markup_annotation.rb +36 -0
  297. data/lib/pdfrb/model/type/mdp_dict.rb +28 -0
  298. data/lib/pdfrb/model/type/measure.rb +39 -0
  299. data/lib/pdfrb/model/type/media_clip.rb +28 -0
  300. data/lib/pdfrb/model/type/media_offset.rb +106 -0
  301. data/lib/pdfrb/model/type/metadata.rb +27 -3
  302. data/lib/pdfrb/model/type/misc_helpers.rb +75 -0
  303. data/lib/pdfrb/model/type/movie.rb +28 -0
  304. data/lib/pdfrb/model/type/names.rb +51 -0
  305. data/lib/pdfrb/model/type/namespace.rb +27 -0
  306. data/lib/pdfrb/model/type/object_reference.rb +31 -0
  307. data/lib/pdfrb/model/type/object_stream.rb +38 -0
  308. data/lib/pdfrb/model/type/opt_content_ext.rb +94 -0
  309. data/lib/pdfrb/model/type/opt_content_page_element.rb +18 -0
  310. data/lib/pdfrb/model/type/optional_content_config.rb +56 -0
  311. data/lib/pdfrb/model/type/optional_content_group.rb +35 -10
  312. data/lib/pdfrb/model/type/optional_content_membership.rb +36 -2
  313. data/lib/pdfrb/model/type/optional_content_properties.rb +16 -1
  314. data/lib/pdfrb/model/type/outline.rb +40 -6
  315. data/lib/pdfrb/model/type/outline_item.rb +45 -0
  316. data/lib/pdfrb/model/type/output_intent.rb +30 -0
  317. data/lib/pdfrb/model/type/page.rb +127 -0
  318. data/lib/pdfrb/model/type/page_label.rb +68 -0
  319. data/lib/pdfrb/model/type/page_piece_info.rb +58 -0
  320. data/lib/pdfrb/model/type/page_tree_node.rb +51 -16
  321. data/lib/pdfrb/model/type/page_tree_node_root.rb +15 -0
  322. data/lib/pdfrb/model/type/pattern.rb +16 -0
  323. data/lib/pdfrb/model/type/pattern_shading.rb +25 -0
  324. data/lib/pdfrb/model/type/pattern_tiling.rb +25 -0
  325. data/lib/pdfrb/model/type/polygon_annotation.rb +29 -0
  326. data/lib/pdfrb/model/type/polygon_polyline.rb +14 -0
  327. data/lib/pdfrb/model/type/polyline_annotation.rb +29 -0
  328. data/lib/pdfrb/model/type/popup_annotation.rb +20 -0
  329. data/lib/pdfrb/model/type/printer_mark_annotation.rb +13 -0
  330. data/lib/pdfrb/model/type/redact_annotation.rb +28 -0
  331. data/lib/pdfrb/model/type/rendition.rb +31 -0
  332. data/lib/pdfrb/model/type/resources.rb +59 -14
  333. data/lib/pdfrb/model/type/rich_media_activation.rb +24 -0
  334. data/lib/pdfrb/model/type/rich_media_animation.rb +25 -0
  335. data/lib/pdfrb/model/type/rich_media_annotation.rb +18 -0
  336. data/lib/pdfrb/model/type/rich_media_command.rb +19 -0
  337. data/lib/pdfrb/model/type/rich_media_configuration.rb +32 -0
  338. data/lib/pdfrb/model/type/rich_media_content.rb +28 -0
  339. data/lib/pdfrb/model/type/rich_media_cue_point.rb +18 -0
  340. data/lib/pdfrb/model/type/rich_media_deactivation.rb +17 -0
  341. data/lib/pdfrb/model/type/rich_media_instance.rb +33 -0
  342. data/lib/pdfrb/model/type/rich_media_presentation.rb +29 -0
  343. data/lib/pdfrb/model/type/rich_media_settings.rb +33 -0
  344. data/lib/pdfrb/model/type/rich_media_window.rb +20 -0
  345. data/lib/pdfrb/model/type/screen_annotation.rb +19 -0
  346. data/lib/pdfrb/model/type/separation.rb +25 -0
  347. data/lib/pdfrb/model/type/sig_field_lock.rb +25 -0
  348. data/lib/pdfrb/model/type/sig_field_seed_value.rb +25 -0
  349. data/lib/pdfrb/model/type/signature.rb +65 -0
  350. data/lib/pdfrb/model/type/signature_build.rb +86 -0
  351. data/lib/pdfrb/model/type/signature_field.rb +17 -0
  352. data/lib/pdfrb/model/type/software_identifier.rb +19 -0
  353. data/lib/pdfrb/model/type/sound.rb +38 -0
  354. data/lib/pdfrb/model/type/sound_annotation.rb +21 -0
  355. data/lib/pdfrb/model/type/square_annotation.rb +24 -0
  356. data/lib/pdfrb/model/type/square_circle.rb +13 -0
  357. data/lib/pdfrb/model/type/squiggly_annotation.rb +11 -0
  358. data/lib/pdfrb/model/type/stamp_annotation.rb +19 -0
  359. data/lib/pdfrb/model/type/strikeout_annotation.rb +11 -0
  360. data/lib/pdfrb/model/type/struct_elem.rb +75 -0
  361. data/lib/pdfrb/model/type/struct_tree_root.rb +34 -0
  362. data/lib/pdfrb/model/type/structure_attributes.rb +44 -0
  363. data/lib/pdfrb/model/type/structure_element_kid.rb +33 -0
  364. data/lib/pdfrb/model/type/text_annotation.rb +25 -0
  365. data/lib/pdfrb/model/type/text_appearance.rb +9 -0
  366. data/lib/pdfrb/model/type/text_field.rb +21 -0
  367. data/lib/pdfrb/model/type/text_markup_annotation.rb +30 -0
  368. data/lib/pdfrb/model/type/three_d_activation.rb +32 -0
  369. data/lib/pdfrb/model/type/three_d_animation_style.rb +24 -0
  370. data/lib/pdfrb/model/type/three_d_background.rb +28 -0
  371. data/lib/pdfrb/model/type/three_d_cross_section.rb +25 -0
  372. data/lib/pdfrb/model/type/three_d_lighting_scheme.rb +26 -0
  373. data/lib/pdfrb/model/type/three_d_measure.rb +20 -0
  374. data/lib/pdfrb/model/type/three_d_measure_3dc.rb +23 -0
  375. data/lib/pdfrb/model/type/three_d_measure_ad3.rb +14 -0
  376. data/lib/pdfrb/model/type/three_d_measure_ld3.rb +14 -0
  377. data/lib/pdfrb/model/type/three_d_measure_pd3.rb +20 -0
  378. data/lib/pdfrb/model/type/three_d_measure_rd3.rb +14 -0
  379. data/lib/pdfrb/model/type/three_d_node.rb +37 -0
  380. data/lib/pdfrb/model/type/three_d_reference.rb +21 -0
  381. data/lib/pdfrb/model/type/three_d_render_mode.rb +34 -0
  382. data/lib/pdfrb/model/type/three_d_stream.rb +48 -0
  383. data/lib/pdfrb/model/type/three_d_units.rb +18 -0
  384. data/lib/pdfrb/model/type/three_d_view.rb +40 -0
  385. data/lib/pdfrb/model/type/to_unicode_cmap_stream.rb +23 -0
  386. data/lib/pdfrb/model/type/transition.rb +68 -0
  387. data/lib/pdfrb/model/type/underline_annotation.rb +11 -0
  388. data/lib/pdfrb/model/type/ur_transform_parameters.rb +33 -0
  389. data/lib/pdfrb/model/type/uri_dict.rb +18 -0
  390. data/lib/pdfrb/model/type/url_alias.rb +19 -0
  391. data/lib/pdfrb/model/type/variable_text_field.rb +47 -0
  392. data/lib/pdfrb/model/type/viewer_preferences.rb +42 -0
  393. data/lib/pdfrb/model/type/watermark_annotation.rb +13 -0
  394. data/lib/pdfrb/model/type/widget_annotation.rb +48 -0
  395. data/lib/pdfrb/model/type/widget_appearance.rb +9 -0
  396. data/lib/pdfrb/model/type/xobject_form.rb +41 -0
  397. data/lib/pdfrb/model/type/xobject_image.rb +56 -0
  398. data/lib/pdfrb/model/type/xref_stream.rb +37 -0
  399. data/lib/pdfrb/model/type.rb +289 -6
  400. data/lib/pdfrb/serializer.rb +2 -2
  401. data/lib/pdfrb/source/tokenizer.rb +0 -2
  402. data/lib/pdfrb/task/benchmark.rb +83 -0
  403. data/lib/pdfrb/task/extract_text.rb +1 -1
  404. data/lib/pdfrb/task/generate_corpus.rb +130 -0
  405. data/lib/pdfrb/task/memory_profile.rb +84 -0
  406. data/lib/pdfrb/task/optimize.rb +97 -7
  407. data/lib/pdfrb/task/regenerate_appearances.rb +98 -0
  408. data/lib/pdfrb/task.rb +4 -3
  409. data/lib/pdfrb/test_utils.rb +112 -0
  410. data/lib/pdfrb/validator.rb +82 -0
  411. data/lib/pdfrb/version.rb +1 -1
  412. data/lib/pdfrb/writer.rb +117 -27
  413. data/lib/pdfrb/xmp/packet.rb +122 -0
  414. data/lib/pdfrb/xmp/schemas.rb +96 -0
  415. data/lib/pdfrb/xmp.rb +44 -0
  416. data/lib/pdfrb/xref_section.rb +2 -2
  417. data/lib/pdfrb.rb +24 -54
  418. data/pdfrb.gemspec +45 -0
  419. data/script/apply_fixes.sh +843 -0
  420. data/script/make_jpeg_fixture.rb +53 -0
  421. data/script/make_png_fixture.rb +25 -0
  422. metadata +319 -4
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class Document
5
+ # Facade for authoring tagged PDF structure trees (PDF/UA §7.9, ISO 32000-2 §14.8).
6
+ #
7
+ # A tagged PDF has:
8
+ # * /StructTreeRoot on the Catalog — root of the structure hierarchy.
9
+ # * /MarkInfo /Marked true on the Catalog — declares the PDF as tagged.
10
+ # * /ParentTree on StructTreeRoot — maps marked-content BDC/EMC
11
+ # references (via /MCID on BDC) back to their parent StructElem.
12
+ #
13
+ # Structure elements form a tree: a document has sections, sections
14
+ # have headings/paragraphs/figures, etc. Each element has a /S
15
+ # (structure type — :H1, :P, :Figure, :Table, ...) and optional
16
+ # /T (title), /Alt (alternate text), /Lang (language).
17
+ class Structure
18
+ attr_reader :document, :root
19
+
20
+ def initialize(document)
21
+ @document = document
22
+ @elements = []
23
+ @current_index = 0
24
+ end
25
+
26
+ # Enable tagged PDF: create /StructTreeRoot, set /MarkInfo.
27
+ # Idempotent — returns the existing root if already enabled.
28
+ # @return [Pdfrb::Model::Cos::Dictionary] the StructTreeRoot.
29
+ def enable!
30
+ return @root if @root
31
+
32
+ root_elem = document.add(
33
+ { Type: :StructTreeRoot },
34
+ type: Pdfrb::Model::Type::StructTreeRoot
35
+ )
36
+ catalog = document.catalog
37
+ catalog.value[:StructTreeRoot] =
38
+ Pdfrb::Model::Reference.new(root_elem.oid, root_elem.gen)
39
+ mark_info = catalog.value[:MarkInfo]
40
+ if mark_info.nil?
41
+ mark_info = document.add({ Marked: true },
42
+ type: Pdfrb::Model::Cos::Dictionary)
43
+ catalog.value[:MarkInfo] =
44
+ Pdfrb::Model::Reference.new(mark_info.oid, mark_info.gen)
45
+ end
46
+ @root = root_elem
47
+ end
48
+
49
+ # Add a top-level child element to the structure tree.
50
+ # @param type [Symbol] structure type (:Document, :Part, :H1, :P, ...).
51
+ # @param attrs [Hash] additional attributes (:T, :Alt, :Lang, ...).
52
+ # @return [Pdfrb::Model::Cos::Dictionary] the new element.
53
+ def add_element(type, page: nil, mcid: nil, **attrs)
54
+ ensure_root
55
+ elem = create_element(type, **attrs)
56
+ append_child(@root, elem)
57
+
58
+ if page
59
+ page_ref = if page.is_a?(Pdfrb::Model::Reference)
60
+ page
61
+ else
62
+ Pdfrb::Model::Reference.new(page.oid, page.gen)
63
+ end
64
+ elem.value[:Pg] = page_ref
65
+ end
66
+ elem.value[:K] = { MCID: mcid } if mcid
67
+
68
+ @elements << elem
69
+ elem
70
+ end
71
+
72
+ # Add a child element to a parent element.
73
+ # @param parent [Pdfrb::Model::Cos::Dictionary] the parent StructElem.
74
+ # @param type [Symbol] structure type.
75
+ # @param attrs [Hash] additional attributes.
76
+ # @return [Pdfrb::Model::Cos::Dictionary] the new element.
77
+ def add_child(parent, type, **attrs)
78
+ elem = create_element(type, **attrs)
79
+ append_child(parent, elem)
80
+ @elements << elem
81
+ elem
82
+ end
83
+
84
+ # Set alternate text on a structure element for screen readers.
85
+ # Required for /Figure elements per PDF/UA-1 Tech Note 001.
86
+ # @param element [Pdfrb::Model::Cos::Dictionary] the StructElem.
87
+ # @param text [String] the alt text.
88
+ def set_alt_text(element, text)
89
+ element.value[:Alt] = text.to_s
90
+ end
91
+
92
+ # Set actual text on a structure element. Overrides the visual
93
+ # text for screen readers (e.g., expanding abbreviations).
94
+ # @param element [Pdfrb::Model::Cos::Dictionary] the StructElem.
95
+ # @param text [String] the actual text.
96
+ def set_actual_text(element, text)
97
+ element.value[:ActualText] = text.to_s
98
+ end
99
+
100
+ # Set language on a structure element (overrides document /Lang).
101
+ # @param element [Pdfrb::Model::Cos::Dictionary] the StructElem.
102
+ # @param lang [String] BCP 47 language tag (e.g., "en-US").
103
+ def set_language(element, lang)
104
+ element.value[:Lang] = lang.to_s
105
+ end
106
+
107
+ # Check if a structure element has alt text (required for /Figure).
108
+ # @param element [Pdfrb::Model::Cos::Dictionary] the StructElem.
109
+ # @return [Boolean]
110
+ def has_alt_text?(element)
111
+ alt = element.value[:Alt]
112
+ alt && !alt.to_s.empty?
113
+ end
114
+
115
+ # Validate that all /Figure elements have /Alt text per PDF/UA-1.
116
+ # @return [Array<Hash>] list of violations (element + reason).
117
+ def validate_alt_text!
118
+ violations = []
119
+ each_element do |elem|
120
+ next unless elem.value[:S]&.to_sym == :Figure
121
+
122
+ unless has_alt_text?(elem)
123
+ violations << { element: elem, reason: "Figure missing /Alt text" }
124
+ end
125
+ end
126
+ violations
127
+ end
128
+
129
+ # Walk all structure elements depth-first.
130
+ def each_element(&block)
131
+ return enum_for(:each_element) unless block
132
+
133
+ @elements.each(&block)
134
+ end
135
+
136
+ # Define a role mapping: maps a custom structure type name to
137
+ # a standard one. Required for PDF/UA when custom types are used.
138
+ # @param custom [Symbol] the custom type name.
139
+ # @param standard [Symbol] the standard type (:H1, :P, :Figure, ...).
140
+ def map_role(custom, standard)
141
+ ensure_root
142
+ role_map = @root.value[:RoleMap]
143
+ if role_map.nil?
144
+ role_map = {}
145
+ @root.value[:RoleMap] = role_map
146
+ end
147
+ role_map[custom] = standard
148
+ role_map
149
+ end
150
+
151
+ private
152
+
153
+ def build_parent_tree
154
+ return if @root.nil?
155
+
156
+ page_map = Hash.new { |h, k| h[k] = [] }
157
+ max_page = -1
158
+ each_element_with_page do |elem, page_index, mcid|
159
+ page_map[page_index] << [mcid, elem]
160
+ max_page = page_index if page_index > max_page
161
+ end
162
+
163
+ nums = []
164
+ page_map.keys.sort.each do |page_idx|
165
+ entries = page_map[page_idx]
166
+ nums << page_idx
167
+ nums << entries.length
168
+ entries.each do |_mcid, elem| # rubocop:disable Style/HashEachMethods
169
+ nums << Pdfrb::Model::Reference.new(elem.oid, elem.gen)
170
+ end
171
+ end
172
+
173
+ @root.value[:ParentTree] = { Nums: nums }
174
+ @root.value[:ParentTreeNextKey] = max_page + 1 if max_page >= 0
175
+ end
176
+
177
+ def each_element_with_page
178
+ return enum_for(:each_element_with_page) unless block_given?
179
+
180
+ @elements.each do |elem|
181
+ page_ref = elem.value[:Pg]
182
+ page_index = page_index_for(page_ref)
183
+ mcid = mcid_for(elem)
184
+ yield elem, page_index, mcid
185
+ end
186
+ end
187
+
188
+ def page_index_for(page_ref)
189
+ return 0 unless page_ref
190
+
191
+ document.pages.each_with_index do |page, idx|
192
+ page_obj = page.is_a?(Pdfrb::Model::Reference) ? document.object(page) : page
193
+ return idx if page_obj == page_ref || page == page_ref
194
+ end
195
+ 0
196
+ end
197
+
198
+ def mcid_for(elem)
199
+ k = elem.value[:K]
200
+ k.is_a?(::Hash) ? k[:MCID] || 0 : 0
201
+ end
202
+
203
+ def ensure_root
204
+ @root || enable!
205
+ end
206
+
207
+ def create_element(type, **attrs)
208
+ dict = { Type: :StructElem, S: type }
209
+ dict[:T] = attrs[:title] if attrs[:title]
210
+ dict[:Alt] = attrs[:alt] if attrs[:alt]
211
+ dict[:Lang] = attrs[:lang] if attrs[:lang]
212
+ document.add(dict, type: Pdfrb::Model::Type::StructElem)
213
+ end
214
+
215
+ def append_child(parent, child)
216
+ children = parent.value[:K]
217
+ if children.nil?
218
+ parent.value[:K] = [Pdfrb::Model::Reference.new(child.oid, child.gen)]
219
+ elsif children.is_a?(::Array)
220
+ children << Pdfrb::Model::Reference.new(child.oid, child.gen)
221
+ else
222
+ parent.value[:K] = [
223
+ children,
224
+ Pdfrb::Model::Reference.new(child.oid, child.gen),
225
+ ]
226
+ end
227
+ child.value[:P] = Pdfrb::Model::Reference.new(parent.oid, parent.gen)
228
+ end
229
+
230
+ public
231
+
232
+ # Build the structure tree, ParentTree, and set up /MarkInfo.
233
+ # Idempotent — safe to call multiple times.
234
+ def build!
235
+ enable! if @root.nil?
236
+ return if @elements.empty?
237
+
238
+ build_parent_tree
239
+ end
240
+ end
241
+ end
242
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
-
5
3
  module Pdfrb
6
4
  # Top-level PDF document facade. Owns:
7
5
  # * The IO it was read from (or nil for in-memory).
@@ -20,8 +18,22 @@ module Pdfrb
20
18
  autoload :Annotations, "pdfrb/document/annotations"
21
19
  autoload :Outline, "pdfrb/document/outline"
22
20
  autoload :FormXObject, "pdfrb/document/form_xobject"
21
+ autoload :AssociatedFiles, "pdfrb/document/associated_files"
22
+ autoload :Colors, "pdfrb/document/colors"
23
+ autoload :Display, "pdfrb/document/display"
24
+ autoload :Form, "pdfrb/document/form"
25
+ autoload :GraphicsState, "pdfrb/document/graphics_state"
26
+ autoload :Layers, "pdfrb/document/layers"
27
+ autoload :Portfolio, "pdfrb/document/portfolio"
28
+ autoload :Structure, "pdfrb/document/structure"
29
+ autoload :Shadings, "pdfrb/document/shadings"
30
+ autoload :OutputIntents, "pdfrb/document/output_intents"
31
+ autoload :Info, "pdfrb/document/info"
32
+ autoload :PageLabels, "pdfrb/document/page_labels"
33
+ autoload :Stamps, "pdfrb/document/stamps"
23
34
 
24
35
  def initialize(io: nil, config: {})
36
+ Pdfrb::Model::Type.eager_load!
25
37
  @config = Configuration.new(config)
26
38
  @io = io
27
39
  @objects = {} # oid -> Pdfrb::Model::Object (modified or new)
@@ -39,7 +51,7 @@ module Pdfrb
39
51
  end
40
52
  end
41
53
 
42
- attr_reader :config, :io, :xref, :version
54
+ attr_reader :config, :io, :xref, :version, :next_oid
43
55
 
44
56
  def self.open(path, **opts)
45
57
  if block_given?
@@ -128,6 +140,53 @@ module Pdfrb
128
140
  @outline ||= Document::Outline.new(self)
129
141
  end
130
142
 
143
+ def structure; @structure ||= Document::Structure.new(self); end
144
+
145
+ def form; @form ||= Document::Form.new(self); end
146
+
147
+ def shadings; @shadings ||= Document::Shadings.new(self); end
148
+
149
+ def colors; @colors ||= Document::Colors.new(self); end
150
+
151
+ def graphics_state; @graphics_state ||= Document::GraphicsState.new(self); end
152
+
153
+ def layers; @layers ||= Document::Layers.new(self); end
154
+
155
+ def associated_files; @associated_files ||= Document::AssociatedFiles.new(self); end
156
+
157
+ def portfolio; @portfolio ||= Document::Portfolio.new(self); end
158
+
159
+ def output_intents; @output_intents ||= Document::OutputIntents.new(self); end
160
+
161
+ def page_labels; @page_labels ||= Document::PageLabels.new(self); end
162
+
163
+ def stamps; @stamps ||= Document::Stamps.new(self); end
164
+
165
+ def info; @info ||= Document::Info.new(self); end
166
+
167
+ def display; @display ||= Document::Display.new(self); end
168
+
169
+ def xmp
170
+ return @xmp if @xmp
171
+
172
+ packet = load_xmp_packet
173
+ return nil unless packet
174
+
175
+ title = metadata[:Title]
176
+ packet.title = title if title
177
+ author = metadata[:Author]
178
+ packet.author = author if author
179
+ @xmp = packet
180
+ end
181
+
182
+ def load_xmp_packet
183
+ Pdfrb::XMP::Packet.new
184
+ rescue LoadError
185
+ nil
186
+ end
187
+
188
+ def version=(v); @version = v.to_s; end
189
+
131
190
  def create_form_xobject(name: nil, bbox: nil, matrix: nil)
132
191
  FormXObject.new(self, name: name, bbox: bbox, matrix: matrix)
133
192
  end
@@ -139,13 +198,21 @@ module Pdfrb
139
198
  @objects[obj.oid] = obj
140
199
  end
141
200
 
201
+ # Drop a modified/new object from the in-memory table so it's no
202
+ # longer emitted by the writer. Used by Task::Optimize when a
203
+ # stream has been deduplicated against a canonical sibling.
204
+ # Returns the dropped object (or nil if not present).
205
+ def forget(oid)
206
+ @objects.delete(oid)
207
+ end
208
+
142
209
  # Write this document to +path+ (or any IO via +io:).
143
210
  def write(path = nil, io: nil)
144
211
  target = io || (path && File.open(path, "wb"))
145
212
  raise ArgumentError, "write needs a path or io:" unless target
146
213
 
147
214
  Pdfrb::Writer.write(self, target)
148
- target.close if path && io.nil? && target.respond_to?(:close)
215
+ target.close if path && io.nil? && target.is_a?(IO)
149
216
  self
150
217
  end
151
218
 
@@ -170,6 +237,8 @@ module Pdfrb
170
237
  # first (they shadow loaded ones), then every loaded entry the
171
238
  # xref knows about.
172
239
  def each_indirect_object
240
+ return enum_for(:each_indirect_object) unless block_given?
241
+
173
242
  seen = Set.new
174
243
  @objects.each_value do |obj|
175
244
  next unless obj.indirect?
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Encryption
5
+ module Identity
6
+ module_function
7
+
8
+ def encrypt(data, **_opts); data; end
9
+ def decrypt(data, **_opts); data; end
10
+ def key_length; 0; end
11
+ end
12
+ end
13
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "digest/md5"
4
4
  require "digest/sha2"
5
+ require "openssl"
5
6
 
6
7
  module Pdfrb
7
8
  module Encryption
@@ -20,8 +21,8 @@ module Pdfrb
20
21
 
21
22
  module_function
22
23
 
23
- # Algorithm 2: derive the encryption key from a user password
24
- # for revisions 2..4 (RC4 and AES-128).
24
+ # Algorithm 2 (revision 2..4, RC4 and AES-128 V4): derive the
25
+ # encryption key from a user password.
25
26
  def derive_key_rc4(password:, o_entry:, p_flags:, id0:,
26
27
  revision:, key_length_bits:, encrypt_metadata: true)
27
28
  padded = pad_password(password)
@@ -40,12 +41,56 @@ module Pdfrb
40
41
  hash.byteslice(0, key_length_bits / 8)
41
42
  end
42
43
 
43
- # Algorithm 5: derive the encryption key for revision 6
44
- # (AES-256, PDF 2.0).
45
- def derive_key_v6(password:, u_entry:, o_entry:,
46
- p_flags:, oe_entry:)
47
- # PDF 2.0 uses SHA-256 with password + validation salt.
48
- raise NotImplementedError, "V6 key derivation TBD"
44
+ # Algorithm 2.B / 8 (revision >= 5, AES-256, PDF 2.0): verify a
45
+ # user password by hashing password + 8-byte validation salt from
46
+ # /U[32..40]. Returns the 32-byte key on success, nil on mismatch.
47
+ def verify_user_password_v5(password:, u_entry:, validation_salt:)
48
+ hash = sha256_password_rounds(password: password.b, salt: validation_salt.b,
49
+ u_entry: u_entry.b)
50
+ hash.byteslice(0, 32) == u_entry.byteslice(0, 32) ? hash : nil
51
+ end
52
+
53
+ # Algorithm 2.B / 9: same for owner password. Owner password
54
+ # validation uses /O[0..32] + /U[0..48] in the hash chain.
55
+ def verify_owner_password_v5(password:, o_entry:, u_entry:,
56
+ validation_salt:)
57
+ hash = sha256_password_rounds(password: password.b, salt: validation_salt.b,
58
+ u_entry: u_entry.b, o_entry: o_entry.b)
59
+ hash.byteslice(0, 32) == o_entry.byteslice(0, 32) ? hash : nil
60
+ end
61
+
62
+ # Algorithm 2.B: the iterative SHA-256 hash loop (sha256 + 64 bytes
63
+ # of last output, XORed with input, repeated until the MSB of byte
64
+ # 0 is 0). Used for V5 password verification and key extraction.
65
+ def sha256_password_rounds(password:, salt:, u_entry: "", o_entry: nil)
66
+ input = password + salt + (o_entry ? o_entry.byteslice(0, 48) : u_entry.byteslice(0, 48))
67
+ hash = Digest::SHA256.digest(input)
68
+ round = 0
69
+ while hash.getbyte(0).nobits?(0x80)
70
+ round += 1
71
+ key = hash.byteslice(0, 16)
72
+ iv = hash.byteslice(16, 16)
73
+ cipher = OpenSSL::Cipher.new("aes-128-cbc")
74
+ cipher.encrypt
75
+ cipher.key = key
76
+ cipher.iv = iv
77
+ cipher.padding = 0
78
+ encrypted = cipher.update(input) + cipher.final
79
+ input = encrypted.bytes.zip(input.bytes).map { |a, b| a ^ b }.pack("C*")
80
+ hash = Digest::SHA256.digest(input)
81
+ end
82
+ hash
83
+ end
84
+ private_class_method :sha256_password_rounds
85
+
86
+ # Extract the validation salt (bytes 32..40) and key salt (40..48)
87
+ # from a /U or /O entry for V5.
88
+ def extract_v5_salts(entry)
89
+ bytes = entry.b
90
+ {
91
+ validation_salt: bytes.byteslice(32, 8) || "".b,
92
+ key_salt: bytes.byteslice(40, 8) || "".b,
93
+ }
49
94
  end
50
95
 
51
96
  # Algorithm 4 (R=2): build the /U entry.
@@ -26,7 +26,7 @@ module Pdfrb
26
26
  return nil unless encrypt
27
27
 
28
28
  filter = encrypt[:Filter] || :Standard
29
- klass = HANDLERS[filter.to_s] ||
29
+ klass = registry[filter.to_s] ||
30
30
  raise(Pdfrb::EncryptionError, "unsupported /Filter #{filter.inspect}")
31
31
  handler = klass.new(document)
32
32
  handler.set_up_decryption(**decryption_opts)
@@ -50,15 +50,18 @@ module Pdfrb
50
50
  !@encrypt_dict.nil?
51
51
  end
52
52
 
53
- HANDLERS = {}.freeze
54
- private_constant :HANDLERS
55
-
56
53
  class << self
57
- def registry; @registry ||= {}; end
54
+ def registry
55
+ @registry ||= {}
56
+ end
58
57
 
59
58
  def register(filter_name, klass)
60
59
  registry[filter_name.to_s] = klass
61
60
  end
61
+
62
+ def lookup(filter_name)
63
+ registry[filter_name.to_s]
64
+ end
62
65
  end
63
66
  end
64
67
  end