pdfrb 0.3.0 → 0.6.0

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 (404) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/Gemfile +1 -0
  4. data/PROPOSAL.canvas-clip.md +92 -0
  5. data/PROPOSAL.canvas-draw-image-matrix.md +87 -0
  6. data/PROPOSAL.canvas-rich-text.md +103 -0
  7. data/PROPOSAL.canvas-text-lines.md +77 -0
  8. data/PROPOSAL.canvas-transparency.md +106 -0
  9. data/PROPOSAL.external-font-metrics.md +129 -0
  10. data/PROPOSAL.font-embedding-fontfile2.md +88 -0
  11. data/PROPOSAL.font-resolver-ttc.md +105 -0
  12. data/PROPOSAL.font-resolver.md +96 -0
  13. data/PROPOSAL.font-subsetting.md +98 -0
  14. data/PROPOSAL.gradient-shadings.md +110 -0
  15. data/PROPOSAL.page-mediabox-setter.md +66 -0
  16. data/PROPOSAL.per-glyph-width.md +91 -0
  17. data/PROPOSAL.stringio-require.md +53 -0
  18. data/PROPOSAL.struct-tree-root.md +102 -0
  19. data/PROPOSAL.text-measurement.md +93 -0
  20. data/PROPOSAL.ttf-table-parsing-fix.md +112 -0
  21. data/RELEASE.md +48 -0
  22. data/lib/pdfrb/action/base.rb +21 -0
  23. data/lib/pdfrb/action/types.rb +178 -0
  24. data/lib/pdfrb/action.rb +55 -0
  25. data/lib/pdfrb/annotation/base.rb +79 -0
  26. data/lib/pdfrb/annotation/types.rb +222 -0
  27. data/lib/pdfrb/annotation.rb +78 -0
  28. data/lib/pdfrb/appearance/generator.rb +216 -0
  29. data/lib/pdfrb/appearance.rb +11 -0
  30. data/lib/pdfrb/arlington/object_definition.rb +2 -2
  31. data/lib/pdfrb/arlington/predicate/evaluator.rb +1 -1
  32. data/lib/pdfrb/arlington/predicate/functions.rb +25 -9
  33. data/lib/pdfrb/arlington/predicate.rb +1 -5
  34. data/lib/pdfrb/cli/batch.rb +18 -0
  35. data/lib/pdfrb/cli/debug_info.rb +18 -0
  36. data/lib/pdfrb/cli/files.rb +16 -0
  37. data/lib/pdfrb/cli/fonts.rb +16 -0
  38. data/lib/pdfrb/cli/form.rb +18 -0
  39. data/lib/pdfrb/cli/image2pdf.rb +18 -0
  40. data/lib/pdfrb/cli/images.rb +34 -0
  41. data/lib/pdfrb/cli/info.rb +20 -0
  42. data/lib/pdfrb/cli/inspect.rb +18 -0
  43. data/lib/pdfrb/cli/merge.rb +18 -0
  44. data/lib/pdfrb/cli/modify.rb +18 -0
  45. data/lib/pdfrb/cli/optimize.rb +18 -0
  46. data/lib/pdfrb/cli/split.rb +21 -0
  47. data/lib/pdfrb/cli/watermark.rb +18 -0
  48. data/lib/pdfrb/cli.rb +291 -7
  49. data/lib/pdfrb/color/color_space.rb +259 -0
  50. data/lib/pdfrb/color/icc_profile.rb +90 -0
  51. data/lib/pdfrb/color.rb +12 -0
  52. data/lib/pdfrb/compare/comparator.rb +244 -0
  53. data/lib/pdfrb/compare/report.rb +78 -0
  54. data/lib/pdfrb/compare.rb +42 -0
  55. data/lib/pdfrb/composer.rb +161 -0
  56. data/lib/pdfrb/conformance/pdf_a.rb +317 -56
  57. data/lib/pdfrb/conformance/pdf_ua.rb +186 -29
  58. data/lib/pdfrb/conformance/pdf_x.rb +247 -0
  59. data/lib/pdfrb/conformance/rule.rb +78 -0
  60. data/lib/pdfrb/conformance/structure_elements.rb +174 -0
  61. data/lib/pdfrb/conformance/verapdf_bridge.rb +92 -0
  62. data/lib/pdfrb/conformance.rb +7 -3
  63. data/lib/pdfrb/content/canvas.rb +217 -72
  64. data/lib/pdfrb/content/color_space.rb +157 -0
  65. data/lib/pdfrb/content/hidden_text_detector.rb +167 -0
  66. data/lib/pdfrb/content/operator.rb +21 -1
  67. data/lib/pdfrb/content/operators/clipping.rb +36 -0
  68. data/lib/pdfrb/content/operators/graphics_state_params.rb +15 -0
  69. data/lib/pdfrb/content/operators.rb +21 -0
  70. data/lib/pdfrb/content/shading/base.rb +139 -0
  71. data/lib/pdfrb/content/shading.rb +17 -0
  72. data/lib/pdfrb/content/smart_text_extractor.rb +85 -0
  73. data/lib/pdfrb/content/tiling_pattern.rb +78 -0
  74. data/lib/pdfrb/content/transformation_matrix.rb +61 -0
  75. data/lib/pdfrb/content.rb +7 -26
  76. data/lib/pdfrb/destination/fit.rb +185 -0
  77. data/lib/pdfrb/destination.rb +13 -0
  78. data/lib/pdfrb/digital_signature/cms_handler.rb +22 -0
  79. data/lib/pdfrb/digital_signature/signing.rb +107 -11
  80. data/lib/pdfrb/digital_signature/timestamp_handler.rb +45 -0
  81. data/lib/pdfrb/digital_signature/verification.rb +88 -9
  82. data/lib/pdfrb/digital_signature/verification_result.rb +14 -0
  83. data/lib/pdfrb/digital_signature.rb +26 -2
  84. data/lib/pdfrb/document/annotations.rb +120 -3
  85. data/lib/pdfrb/document/associated_files.rb +104 -0
  86. data/lib/pdfrb/document/colors.rb +95 -0
  87. data/lib/pdfrb/document/destinations.rb +127 -5
  88. data/lib/pdfrb/document/display.rb +84 -0
  89. data/lib/pdfrb/document/fonts.rb +417 -45
  90. data/lib/pdfrb/document/form.rb +338 -0
  91. data/lib/pdfrb/document/graphics_state.rb +91 -0
  92. data/lib/pdfrb/document/images.rb +4 -4
  93. data/lib/pdfrb/document/info.rb +102 -0
  94. data/lib/pdfrb/document/layers.rb +62 -0
  95. data/lib/pdfrb/document/metadata.rb +12 -5
  96. data/lib/pdfrb/document/output_intents.rb +91 -0
  97. data/lib/pdfrb/document/page_labels.rb +80 -0
  98. data/lib/pdfrb/document/pages.rb +68 -19
  99. data/lib/pdfrb/document/portfolio.rb +119 -0
  100. data/lib/pdfrb/document/shadings.rb +159 -0
  101. data/lib/pdfrb/document/stamps.rb +68 -0
  102. data/lib/pdfrb/document/structure.rb +242 -0
  103. data/lib/pdfrb/document.rb +73 -4
  104. data/lib/pdfrb/encryption/identity.rb +13 -0
  105. data/lib/pdfrb/encryption/password_verification.rb +53 -8
  106. data/lib/pdfrb/encryption/security_handler.rb +8 -5
  107. data/lib/pdfrb/encryption/standard_security_handler.rb +154 -81
  108. data/lib/pdfrb/encryption.rb +17 -0
  109. data/lib/pdfrb/error.rb +2 -2
  110. data/lib/pdfrb/filter/brotli_decode.rb +32 -0
  111. data/lib/pdfrb/filter/ccitt_fax_decode.rb +12 -0
  112. data/lib/pdfrb/filter/crypt.rb +2 -2
  113. data/lib/pdfrb/filter/dct_decode.rb +18 -0
  114. data/lib/pdfrb/filter/flate_decode.rb +4 -18
  115. data/lib/pdfrb/filter/jbig2_decode.rb +12 -0
  116. data/lib/pdfrb/filter/jpx_decode.rb +18 -0
  117. data/lib/pdfrb/filter/lzw_decode.rb +3 -2
  118. data/lib/pdfrb/filter/png_predictor.rb +94 -0
  119. data/lib/pdfrb/filter.rb +26 -13
  120. data/lib/pdfrb/font/afm_parser.rb +50 -26
  121. data/lib/pdfrb/font/cmap/writer.rb +9 -0
  122. data/lib/pdfrb/font/encoding/win_ansi_encoding.rb +17 -0
  123. data/lib/pdfrb/font/encoding.rb +32 -3
  124. data/lib/pdfrb/font/metrics_helper.rb +121 -0
  125. data/lib/pdfrb/font/true_type/file.rb +10 -1
  126. data/lib/pdfrb/font/true_type/glyf.rb +82 -0
  127. data/lib/pdfrb/font/true_type/kern.rb +58 -0
  128. data/lib/pdfrb/font/true_type/loca.rb +42 -0
  129. data/lib/pdfrb/font/true_type/maxp.rb +44 -0
  130. data/lib/pdfrb/font/true_type/name.rb +53 -0
  131. data/lib/pdfrb/font/true_type/post.rb +93 -0
  132. data/lib/pdfrb/font/true_type/subsetter.rb +3 -5
  133. data/lib/pdfrb/font/true_type/wrapper.rb +102 -0
  134. data/lib/pdfrb/font/true_type.rb +7 -0
  135. data/lib/pdfrb/font.rb +2 -0
  136. data/lib/pdfrb/font_loader/from_configuration.rb +19 -0
  137. data/lib/pdfrb/font_loader/from_file.rb +20 -0
  138. data/lib/pdfrb/font_loader/standard14.rb +20 -0
  139. data/lib/pdfrb/font_loader/variant_from_name.rb +36 -0
  140. data/lib/pdfrb/font_loader.rb +4 -0
  141. data/lib/pdfrb/font_resolver.rb +138 -0
  142. data/lib/pdfrb/image/audit.rb +94 -0
  143. data/lib/pdfrb/image/downsampler.rb +124 -0
  144. data/lib/pdfrb/image.rb +13 -0
  145. data/lib/pdfrb/image_loader/jpeg.rb +39 -58
  146. data/lib/pdfrb/image_loader/png.rb +31 -257
  147. data/lib/pdfrb/layout/bidi.rb +198 -0
  148. data/lib/pdfrb/layout/box.rb +49 -0
  149. data/lib/pdfrb/layout/box_fitter.rb +54 -0
  150. data/lib/pdfrb/layout/column_box.rb +71 -0
  151. data/lib/pdfrb/layout/container_box.rb +45 -0
  152. data/lib/pdfrb/layout/frame.rb +67 -0
  153. data/lib/pdfrb/layout/image_box.rb +44 -0
  154. data/lib/pdfrb/layout/inline_box.rb +25 -0
  155. data/lib/pdfrb/layout/line.rb +55 -0
  156. data/lib/pdfrb/layout/list_box.rb +87 -0
  157. data/lib/pdfrb/layout/numeric_refinements.rb +43 -0
  158. data/lib/pdfrb/layout/page_style.rb +65 -0
  159. data/lib/pdfrb/layout/style.rb +62 -0
  160. data/lib/pdfrb/layout/table_box.rb +66 -0
  161. data/lib/pdfrb/layout/text_box.rb +38 -0
  162. data/lib/pdfrb/layout/text_fragment.rb +45 -0
  163. data/lib/pdfrb/layout/text_layouter.rb +123 -0
  164. data/lib/pdfrb/layout.rb +28 -0
  165. data/lib/pdfrb/linearization/hint_stream.rb +93 -0
  166. data/lib/pdfrb/linearization/writer.rb +259 -0
  167. data/lib/pdfrb/linearization.rb +21 -0
  168. data/lib/pdfrb/model/cos/dictionary.rb +12 -0
  169. data/lib/pdfrb/model/pdf_array.rb +2 -2
  170. data/lib/pdfrb/model/type/action.rb +42 -0
  171. data/lib/pdfrb/model/type/action_go_to_3d_view.rb +14 -0
  172. data/lib/pdfrb/model/type/action_go_to_dp.rb +16 -0
  173. data/lib/pdfrb/model/type/action_go_to_r.rb +26 -0
  174. data/lib/pdfrb/model/type/action_goto.rb +34 -0
  175. data/lib/pdfrb/model/type/action_hide.rb +26 -0
  176. data/lib/pdfrb/model/type/action_import_data.rb +18 -0
  177. data/lib/pdfrb/model/type/action_java_script.rb +18 -0
  178. data/lib/pdfrb/model/type/action_launch.rb +22 -0
  179. data/lib/pdfrb/model/type/action_movie.rb +22 -0
  180. data/lib/pdfrb/model/type/action_named.rb +23 -0
  181. data/lib/pdfrb/model/type/action_nop.rb +12 -0
  182. data/lib/pdfrb/model/type/action_rendition.rb +28 -0
  183. data/lib/pdfrb/model/type/action_reset_form.rb +22 -0
  184. data/lib/pdfrb/model/type/action_rich_media_execute.rb +17 -0
  185. data/lib/pdfrb/model/type/action_set_ocg_state.rb +32 -0
  186. data/lib/pdfrb/model/type/action_set_state.rb +14 -0
  187. data/lib/pdfrb/model/type/action_sound_action.rb +23 -0
  188. data/lib/pdfrb/model/type/action_submit_form.rb +64 -0
  189. data/lib/pdfrb/model/type/action_thread.rb +23 -0
  190. data/lib/pdfrb/model/type/action_trans.rb +30 -0
  191. data/lib/pdfrb/model/type/action_uri.rb +30 -0
  192. data/lib/pdfrb/model/type/af_embedded_file.rb +11 -0
  193. data/lib/pdfrb/model/type/af_file_specification.rb +34 -0
  194. data/lib/pdfrb/model/type/annotation.rb +49 -10
  195. data/lib/pdfrb/model/type/appearance.rb +41 -0
  196. data/lib/pdfrb/model/type/appearance_characteristics.rb +36 -0
  197. data/lib/pdfrb/model/type/appearance_generator.rb +22 -0
  198. data/lib/pdfrb/model/type/appearance_trap_net.rb +84 -0
  199. data/lib/pdfrb/model/type/border_effect.rb +25 -0
  200. data/lib/pdfrb/model/type/border_styling.rb +38 -0
  201. data/lib/pdfrb/model/type/box_color_info.rb +21 -0
  202. data/lib/pdfrb/model/type/box_style.rb +19 -0
  203. data/lib/pdfrb/model/type/button.rb +17 -0
  204. data/lib/pdfrb/model/type/cal_gray.rb +17 -0
  205. data/lib/pdfrb/model/type/cal_rgb.rb +22 -0
  206. data/lib/pdfrb/model/type/caret_annotation.rb +21 -0
  207. data/lib/pdfrb/model/type/catalog.rb +99 -16
  208. data/lib/pdfrb/model/type/choice.rb +21 -0
  209. data/lib/pdfrb/model/type/cid_font.rb +52 -1
  210. data/lib/pdfrb/model/type/cid_font_descriptor_metrics.rb +28 -0
  211. data/lib/pdfrb/model/type/cid_system_info.rb +28 -0
  212. data/lib/pdfrb/model/type/circle_annotation.rb +20 -0
  213. data/lib/pdfrb/model/type/collection.rb +27 -0
  214. data/lib/pdfrb/model/type/collection_field.rb +24 -0
  215. data/lib/pdfrb/model/type/collection_item.rb +19 -0
  216. data/lib/pdfrb/model/type/collection_schema.rb +25 -0
  217. data/lib/pdfrb/model/type/collection_sort.rb +13 -0
  218. data/lib/pdfrb/model/type/crypt_filter.rb +58 -0
  219. data/lib/pdfrb/model/type/device_n.rb +37 -0
  220. data/lib/pdfrb/model/type/device_n_mixing_hints.rb +17 -0
  221. data/lib/pdfrb/model/type/device_n_process.rb +13 -0
  222. data/lib/pdfrb/model/type/doc_mdp_transform_parameters.rb +21 -0
  223. data/lib/pdfrb/model/type/document_security_store.rb +55 -0
  224. data/lib/pdfrb/model/type/embedded_file.rb +19 -0
  225. data/lib/pdfrb/model/type/embedded_file_parameter.rb +48 -0
  226. data/lib/pdfrb/model/type/encryption_public_key.rb +38 -0
  227. data/lib/pdfrb/model/type/encryption_standard.rb +68 -0
  228. data/lib/pdfrb/model/type/field_mdp_transform_parameters.rb +26 -0
  229. data/lib/pdfrb/model/type/file_attachment_annotation.rb +29 -0
  230. data/lib/pdfrb/model/type/file_spec_ef.rb +22 -0
  231. data/lib/pdfrb/model/type/file_spec_rf.rb +21 -0
  232. data/lib/pdfrb/model/type/file_specification.rb +43 -0
  233. data/lib/pdfrb/model/type/file_trailer.rb +54 -0
  234. data/lib/pdfrb/model/type/font.rb +73 -2
  235. data/lib/pdfrb/model/type/font_cid_type0.rb +17 -0
  236. data/lib/pdfrb/model/type/font_cid_type2.rb +17 -0
  237. data/lib/pdfrb/model/type/font_descriptor.rb +47 -0
  238. data/lib/pdfrb/model/type/font_descriptor_cid_type0.rb +15 -0
  239. data/lib/pdfrb/model/type/font_descriptor_cid_type2.rb +15 -0
  240. data/lib/pdfrb/model/type/font_encoding.rb +47 -0
  241. data/lib/pdfrb/model/type/font_file.rb +28 -0
  242. data/lib/pdfrb/model/type/font_file2.rb +32 -0
  243. data/lib/pdfrb/model/type/font_file3.rb +23 -0
  244. data/lib/pdfrb/model/type/font_multiple_master.rb +60 -0
  245. data/lib/pdfrb/model/type/font_true_type.rb +1 -1
  246. data/lib/pdfrb/model/type/font_type0.rb +1 -1
  247. data/lib/pdfrb/model/type/font_type1.rb +1 -1
  248. data/lib/pdfrb/model/type/font_type3.rb +1 -1
  249. data/lib/pdfrb/model/type/form_field.rb +73 -0
  250. data/lib/pdfrb/model/type/free_text_annotation.rb +37 -0
  251. data/lib/pdfrb/model/type/function.rb +42 -0
  252. data/lib/pdfrb/model/type/function_exponential.rb +31 -0
  253. data/lib/pdfrb/model/type/function_postscript.rb +25 -0
  254. data/lib/pdfrb/model/type/function_sampled.rb +55 -0
  255. data/lib/pdfrb/model/type/function_stitching.rb +33 -0
  256. data/lib/pdfrb/model/type/generic_appearance.rb +40 -0
  257. data/lib/pdfrb/model/type/graphics_state_parameter.rb +69 -0
  258. data/lib/pdfrb/model/type/halftone.rb +21 -0
  259. data/lib/pdfrb/model/type/halftone_type1.rb +17 -0
  260. data/lib/pdfrb/model/type/halftone_type10.rb +16 -0
  261. data/lib/pdfrb/model/type/halftone_type16.rb +11 -0
  262. data/lib/pdfrb/model/type/halftone_type5.rb +24 -0
  263. data/lib/pdfrb/model/type/halftone_type6.rb +16 -0
  264. data/lib/pdfrb/model/type/highlight_annotation.rb +11 -0
  265. data/lib/pdfrb/model/type/icc_based_color_space.rb +24 -0
  266. data/lib/pdfrb/model/type/icon_fit.rb +31 -0
  267. data/lib/pdfrb/model/type/indexed.rb +32 -0
  268. data/lib/pdfrb/model/type/info.rb +44 -0
  269. data/lib/pdfrb/model/type/ink_annotation.rb +29 -0
  270. data/lib/pdfrb/model/type/interactive_form.rb +36 -0
  271. data/lib/pdfrb/model/type/interior_color.rb +12 -0
  272. data/lib/pdfrb/model/type/jbig2_globals.rb +15 -0
  273. data/lib/pdfrb/model/type/lab.rb +27 -0
  274. data/lib/pdfrb/model/type/line_annotation.rb +44 -0
  275. data/lib/pdfrb/model/type/line_ending_styling.rb +39 -0
  276. data/lib/pdfrb/model/type/link_annotation.rb +41 -0
  277. data/lib/pdfrb/model/type/link_appearance.rb +9 -0
  278. data/lib/pdfrb/model/type/mark_information.rb +23 -0
  279. data/lib/pdfrb/model/type/marked_content_reference.rb +24 -0
  280. data/lib/pdfrb/model/type/markup_annotation.rb +36 -0
  281. data/lib/pdfrb/model/type/mdp_dict.rb +28 -0
  282. data/lib/pdfrb/model/type/measure.rb +39 -0
  283. data/lib/pdfrb/model/type/media_clip.rb +28 -0
  284. data/lib/pdfrb/model/type/media_offset.rb +106 -0
  285. data/lib/pdfrb/model/type/metadata.rb +27 -3
  286. data/lib/pdfrb/model/type/misc_helpers.rb +75 -0
  287. data/lib/pdfrb/model/type/movie.rb +28 -0
  288. data/lib/pdfrb/model/type/names.rb +51 -0
  289. data/lib/pdfrb/model/type/namespace.rb +27 -0
  290. data/lib/pdfrb/model/type/object_reference.rb +31 -0
  291. data/lib/pdfrb/model/type/object_stream.rb +38 -0
  292. data/lib/pdfrb/model/type/opt_content_ext.rb +94 -0
  293. data/lib/pdfrb/model/type/opt_content_page_element.rb +18 -0
  294. data/lib/pdfrb/model/type/optional_content_config.rb +56 -0
  295. data/lib/pdfrb/model/type/optional_content_group.rb +35 -10
  296. data/lib/pdfrb/model/type/optional_content_membership.rb +36 -2
  297. data/lib/pdfrb/model/type/optional_content_properties.rb +16 -1
  298. data/lib/pdfrb/model/type/outline.rb +40 -6
  299. data/lib/pdfrb/model/type/outline_item.rb +45 -0
  300. data/lib/pdfrb/model/type/output_intent.rb +30 -0
  301. data/lib/pdfrb/model/type/page.rb +127 -0
  302. data/lib/pdfrb/model/type/page_label.rb +68 -0
  303. data/lib/pdfrb/model/type/page_tree_node.rb +51 -16
  304. data/lib/pdfrb/model/type/page_tree_node_root.rb +15 -0
  305. data/lib/pdfrb/model/type/pattern.rb +16 -0
  306. data/lib/pdfrb/model/type/pattern_shading.rb +25 -0
  307. data/lib/pdfrb/model/type/pattern_tiling.rb +25 -0
  308. data/lib/pdfrb/model/type/polygon_annotation.rb +29 -0
  309. data/lib/pdfrb/model/type/polygon_polyline.rb +14 -0
  310. data/lib/pdfrb/model/type/polyline_annotation.rb +29 -0
  311. data/lib/pdfrb/model/type/popup_annotation.rb +20 -0
  312. data/lib/pdfrb/model/type/printer_mark_annotation.rb +13 -0
  313. data/lib/pdfrb/model/type/redact_annotation.rb +28 -0
  314. data/lib/pdfrb/model/type/rendition.rb +31 -0
  315. data/lib/pdfrb/model/type/resources.rb +59 -14
  316. data/lib/pdfrb/model/type/rich_media_activation.rb +24 -0
  317. data/lib/pdfrb/model/type/rich_media_animation.rb +25 -0
  318. data/lib/pdfrb/model/type/rich_media_annotation.rb +18 -0
  319. data/lib/pdfrb/model/type/rich_media_command.rb +19 -0
  320. data/lib/pdfrb/model/type/rich_media_configuration.rb +32 -0
  321. data/lib/pdfrb/model/type/rich_media_content.rb +28 -0
  322. data/lib/pdfrb/model/type/rich_media_cue_point.rb +18 -0
  323. data/lib/pdfrb/model/type/rich_media_deactivation.rb +17 -0
  324. data/lib/pdfrb/model/type/rich_media_instance.rb +33 -0
  325. data/lib/pdfrb/model/type/rich_media_presentation.rb +29 -0
  326. data/lib/pdfrb/model/type/rich_media_settings.rb +33 -0
  327. data/lib/pdfrb/model/type/rich_media_window.rb +20 -0
  328. data/lib/pdfrb/model/type/screen_annotation.rb +19 -0
  329. data/lib/pdfrb/model/type/separation.rb +25 -0
  330. data/lib/pdfrb/model/type/sig_field_lock.rb +25 -0
  331. data/lib/pdfrb/model/type/sig_field_seed_value.rb +25 -0
  332. data/lib/pdfrb/model/type/signature.rb +65 -0
  333. data/lib/pdfrb/model/type/signature_build.rb +86 -0
  334. data/lib/pdfrb/model/type/signature_field.rb +17 -0
  335. data/lib/pdfrb/model/type/software_identifier.rb +19 -0
  336. data/lib/pdfrb/model/type/sound.rb +38 -0
  337. data/lib/pdfrb/model/type/sound_annotation.rb +21 -0
  338. data/lib/pdfrb/model/type/square_annotation.rb +24 -0
  339. data/lib/pdfrb/model/type/square_circle.rb +13 -0
  340. data/lib/pdfrb/model/type/squiggly_annotation.rb +11 -0
  341. data/lib/pdfrb/model/type/stamp_annotation.rb +19 -0
  342. data/lib/pdfrb/model/type/strikeout_annotation.rb +11 -0
  343. data/lib/pdfrb/model/type/struct_elem.rb +75 -0
  344. data/lib/pdfrb/model/type/struct_tree_root.rb +34 -0
  345. data/lib/pdfrb/model/type/structure_attributes.rb +44 -0
  346. data/lib/pdfrb/model/type/structure_element_kid.rb +33 -0
  347. data/lib/pdfrb/model/type/text_annotation.rb +25 -0
  348. data/lib/pdfrb/model/type/text_appearance.rb +9 -0
  349. data/lib/pdfrb/model/type/text_field.rb +21 -0
  350. data/lib/pdfrb/model/type/text_markup_annotation.rb +30 -0
  351. data/lib/pdfrb/model/type/three_d_activation.rb +32 -0
  352. data/lib/pdfrb/model/type/three_d_animation_style.rb +24 -0
  353. data/lib/pdfrb/model/type/three_d_background.rb +28 -0
  354. data/lib/pdfrb/model/type/three_d_cross_section.rb +25 -0
  355. data/lib/pdfrb/model/type/three_d_lighting_scheme.rb +26 -0
  356. data/lib/pdfrb/model/type/three_d_measure.rb +20 -0
  357. data/lib/pdfrb/model/type/three_d_measure_3dc.rb +23 -0
  358. data/lib/pdfrb/model/type/three_d_measure_ad3.rb +14 -0
  359. data/lib/pdfrb/model/type/three_d_measure_ld3.rb +14 -0
  360. data/lib/pdfrb/model/type/three_d_measure_pd3.rb +20 -0
  361. data/lib/pdfrb/model/type/three_d_measure_rd3.rb +14 -0
  362. data/lib/pdfrb/model/type/three_d_node.rb +37 -0
  363. data/lib/pdfrb/model/type/three_d_reference.rb +21 -0
  364. data/lib/pdfrb/model/type/three_d_render_mode.rb +34 -0
  365. data/lib/pdfrb/model/type/three_d_stream.rb +48 -0
  366. data/lib/pdfrb/model/type/three_d_units.rb +18 -0
  367. data/lib/pdfrb/model/type/three_d_view.rb +40 -0
  368. data/lib/pdfrb/model/type/to_unicode_cmap_stream.rb +23 -0
  369. data/lib/pdfrb/model/type/transition.rb +68 -0
  370. data/lib/pdfrb/model/type/underline_annotation.rb +11 -0
  371. data/lib/pdfrb/model/type/ur_transform_parameters.rb +33 -0
  372. data/lib/pdfrb/model/type/uri_dict.rb +18 -0
  373. data/lib/pdfrb/model/type/url_alias.rb +19 -0
  374. data/lib/pdfrb/model/type/variable_text_field.rb +47 -0
  375. data/lib/pdfrb/model/type/viewer_preferences.rb +42 -0
  376. data/lib/pdfrb/model/type/watermark_annotation.rb +13 -0
  377. data/lib/pdfrb/model/type/widget_annotation.rb +48 -0
  378. data/lib/pdfrb/model/type/widget_appearance.rb +9 -0
  379. data/lib/pdfrb/model/type/xobject_form.rb +41 -0
  380. data/lib/pdfrb/model/type/xobject_image.rb +56 -0
  381. data/lib/pdfrb/model/type/xref_stream.rb +37 -0
  382. data/lib/pdfrb/model/type.rb +286 -6
  383. data/lib/pdfrb/serializer.rb +2 -2
  384. data/lib/pdfrb/source/tokenizer.rb +0 -2
  385. data/lib/pdfrb/task/benchmark.rb +83 -0
  386. data/lib/pdfrb/task/extract_text.rb +1 -1
  387. data/lib/pdfrb/task/generate_corpus.rb +130 -0
  388. data/lib/pdfrb/task/memory_profile.rb +84 -0
  389. data/lib/pdfrb/task/optimize.rb +97 -7
  390. data/lib/pdfrb/task.rb +3 -3
  391. data/lib/pdfrb/test_utils.rb +112 -0
  392. data/lib/pdfrb/validator.rb +82 -0
  393. data/lib/pdfrb/version.rb +1 -1
  394. data/lib/pdfrb/writer.rb +117 -27
  395. data/lib/pdfrb/xmp/packet.rb +122 -0
  396. data/lib/pdfrb/xmp/schemas.rb +96 -0
  397. data/lib/pdfrb/xmp.rb +44 -0
  398. data/lib/pdfrb/xref_section.rb +2 -2
  399. data/lib/pdfrb.rb +24 -54
  400. data/pdfrb.gemspec +45 -0
  401. data/script/apply_fixes.sh +843 -0
  402. data/script/make_jpeg_fixture.rb +53 -0
  403. data/script/make_png_fixture.rb +25 -0
  404. metadata +311 -2
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class CLI
5
+ module Ubatch
6
+ module_function
7
+
8
+ def call(path, **opts)
9
+ doc = Pdfrb::Document.open(path)
10
+ yield doc if block_given?
11
+ output = opts[:output] || path
12
+ doc.write(output)
13
+ puts "Processed: #{output}"
14
+ true
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class CLI
5
+ module UdebugUinfo
6
+ module_function
7
+
8
+ def call(path, **opts)
9
+ doc = Pdfrb::Document.open(path)
10
+ yield doc if block_given?
11
+ output = opts[:output] || path
12
+ doc.write(output)
13
+ puts "Processed: #{output}"
14
+ true
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class CLI
5
+ module Files
6
+ module_function
7
+
8
+ def call(path)
9
+ doc = Pdfrb::Document.open(path)
10
+ puts "Embedded files:"
11
+ doc.files.each { |f| puts " #{f}" }
12
+ true
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class CLI
5
+ module Fonts
6
+ module_function
7
+
8
+ def call(path)
9
+ doc = Pdfrb::Document.open(path)
10
+ puts "Fonts:"
11
+ doc.fonts.each { |name, res| puts " #{res}: #{name}" }
12
+ true
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class CLI
5
+ module Uform
6
+ module_function
7
+
8
+ def call(path, **opts)
9
+ doc = Pdfrb::Document.open(path)
10
+ yield doc if block_given?
11
+ output = opts[:output] || path
12
+ doc.write(output)
13
+ puts "Processed: #{output}"
14
+ true
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class CLI
5
+ module Uimage2pdf
6
+ module_function
7
+
8
+ def call(path, **opts)
9
+ doc = Pdfrb::Document.open(path)
10
+ yield doc if block_given?
11
+ output = opts[:output] || path
12
+ doc.write(output)
13
+ puts "Processed: #{output}"
14
+ true
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class CLI
5
+ module Images
6
+ module_function
7
+
8
+ def call(path, output_dir: nil)
9
+ doc = Pdfrb::Document.open(path)
10
+ output_dir ||= File.dirname(path)
11
+ count = 0
12
+ doc.pages.each_with_index do |page, pn|
13
+ res = page.value[:Resources]
14
+ next unless res
15
+
16
+ xo = res[:XObject] || res.value[:XObject]
17
+ next unless xo
18
+
19
+ xo = xo.value if xo.is_a?(Pdfrb::Model::Cos::Dictionary)
20
+ xo&.each do |name, ref|
21
+ obj = ref.is_a?(Pdfrb::Model::Reference) ? doc.object(ref) : ref
22
+ next unless obj&.value&.[](:Subtype) == :Image
23
+
24
+ count += 1
25
+ data = obj.stream || ""
26
+ File.binwrite(File.join(output_dir, "page#{pn + 1}_#{name}.raw"), data)
27
+ end
28
+ end
29
+ puts "Extracted #{count} images"
30
+ true
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class CLI
5
+ module Info
6
+ module_function
7
+
8
+ def call(path)
9
+ doc = Pdfrb::Document.open(path)
10
+ puts "File: #{path}"
11
+ puts "Version: #{doc.version}"
12
+ puts "Pages: #{doc.pages.count}"
13
+ puts "Encrypted: #{!doc.trailer&.[](:Encrypt).nil?}"
14
+ puts "Title: #{doc.metadata[:Title]}" if doc.metadata[:Title]
15
+ puts "Author: #{doc.metadata[:Author]}" if doc.metadata[:Author]
16
+ true
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class CLI
5
+ module Uinspect
6
+ module_function
7
+
8
+ def call(path, **opts)
9
+ doc = Pdfrb::Document.open(path)
10
+ yield doc if block_given?
11
+ output = opts[:output] || path
12
+ doc.write(output)
13
+ puts "Processed: #{output}"
14
+ true
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class CLI
5
+ module Umerge
6
+ module_function
7
+
8
+ def call(path, **opts)
9
+ doc = Pdfrb::Document.open(path)
10
+ yield doc if block_given?
11
+ output = opts[:output] || path
12
+ doc.write(output)
13
+ puts "Processed: #{output}"
14
+ true
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class CLI
5
+ module Umodify
6
+ module_function
7
+
8
+ def call(path, **opts)
9
+ doc = Pdfrb::Document.open(path)
10
+ yield doc if block_given?
11
+ output = opts[:output] || path
12
+ doc.write(output)
13
+ puts "Processed: #{output}"
14
+ true
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class CLI
5
+ module Uoptimize
6
+ module_function
7
+
8
+ def call(path, **opts)
9
+ doc = Pdfrb::Document.open(path)
10
+ yield doc if block_given?
11
+ output = opts[:output] || path
12
+ doc.write(output)
13
+ puts "Processed: #{output}"
14
+ true
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class CLI
5
+ module Split
6
+ module_function
7
+
8
+ def call(path, output_dir: nil)
9
+ doc = Pdfrb::Document.open(path)
10
+ output_dir ||= File.dirname(path)
11
+ basename = File.basename(path, ".pdf")
12
+ doc.pages.each_with_index do |page, i|
13
+ single = Pdfrb::Document.new
14
+ single.pages.add(media_box: page.media_box)
15
+ single.write(File.join(output_dir, "#{basename}_#{i + 1}.pdf"))
16
+ end
17
+ true
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class CLI
5
+ module Uwatermark
6
+ module_function
7
+
8
+ def call(path, **opts)
9
+ doc = Pdfrb::Document.open(path)
10
+ yield doc if block_given?
11
+ output = opts[:output] || path
12
+ doc.write(output)
13
+ puts "Processed: #{output}"
14
+ true
15
+ end
16
+ end
17
+ end
18
+ end
data/lib/pdfrb/cli.rb CHANGED
@@ -59,7 +59,7 @@ module Pdfrb
59
59
  end
60
60
  end
61
61
 
62
- desc "optimize INPUT OUTPUT", "Optimize (stub TODO 31)"
62
+ desc "optimize INPUT OUTPUT", "Optimize: dedup objects, pack into ObjStm, rewrite xref stream"
63
63
  def optimize(input, output)
64
64
  doc = open_doc(input)
65
65
  Pdfrb::Task::Optimize.call(doc)
@@ -88,22 +88,53 @@ module Pdfrb
88
88
  end
89
89
  end
90
90
 
91
- desc "encrypt INPUT OUTPUT --password PASSWORD", "Encrypt a PDF"
91
+ desc "encrypt INPUT OUTPUT --password PASSWORD", "Encrypt a PDF with RC4 or AES"
92
92
  method_option :password, type: :string, required: true
93
+ method_option :owner_password, type: :string, default: nil
94
+ method_option :bits, type: :numeric, default: 128, banner: "40|128|256"
95
+ method_option :permissions, type: :string, default: "",
96
+ banner: "print,copy,modify,annotate,fill,extract,assemble,print-hq"
93
97
  def encrypt(input, output)
94
- warn "encrypt: not yet fully implemented (Phase 11 integration pending)"
95
- # Copy through for now.
96
98
  doc = open_doc(input)
99
+ bits = options[:bits]
100
+ password = options[:password]
101
+ owner_pw = options[:owner_password] || password
102
+
103
+ perms = parse_permissions(options[:permissions])
104
+
105
+ # Build the /Encrypt dictionary.
106
+ case bits
107
+ when 256
108
+ build_aes256_encrypt(doc, password, owner_pw, perms)
109
+ when 128
110
+ build_encrypt(doc, password, owner_pw, perms, v: 4, r: 4, length: 128)
111
+ when 40
112
+ build_encrypt(doc, password, owner_pw, perms, v: 2, r: 3, length: 40)
113
+ else
114
+ raise "unsupported key length: #{bits} (use 40, 128, or 256)"
115
+ end
116
+
97
117
  doc.write(output)
98
- puts "Wrote #{output} (encryption not yet applied)"
118
+ puts "Encrypted #{output} (#{bits}-bit)"
99
119
  end
100
120
 
101
- desc "decrypt INPUT OUTPUT --password PASSWORD", "Decrypt a PDF"
121
+ desc "decrypt INPUT OUTPUT --password PASSWORD", "Decrypt a PDF (remove /Encrypt)"
102
122
  method_option :password, type: :string, default: ""
103
123
  def decrypt(input, output)
104
124
  doc = open_doc(input)
125
+
126
+ # Strip /Encrypt from trailer and clear handler config so the
127
+ # Writer doesn't re-encrypt on output.
128
+ trailer = doc.trailer
129
+ if trailer && trailer[:Encrypt]
130
+ trailer.value.delete(:Encrypt)
131
+ doc.config.delete("encryption.handler")
132
+ doc.config.delete("encryption.password")
133
+ puts "Stripped encryption"
134
+ end
135
+
105
136
  doc.write(output)
106
- puts "Wrote #{output}"
137
+ puts "Decrypted #{output}"
107
138
  end
108
139
 
109
140
  desc "images-add INPUT IMAGE OUTPUT", "Add an image to a new page"
@@ -137,8 +168,261 @@ module Pdfrb
137
168
  end
138
169
  end
139
170
 
171
+ desc "split INPUT OUTPUT_PATTERN [FROM [TO]]", "Split a PDF into individual pages"
172
+ def split(input, output_pattern, from = nil, to = nil)
173
+ doc = open_doc(input)
174
+ total = doc.pages.count
175
+ from_idx = from ? from.to_i : 1
176
+ to_idx = to ? to.to_i : total
177
+ (from_idx..to_idx).each do |i|
178
+ target = Pdfrb::Document.new
179
+ Pdfrb::Importer.new(target).import(doc.pages[i - 1].value, doc)
180
+ out_path = format(output_pattern, i)
181
+ target.write(out_path)
182
+ end
183
+ puts "Split #{input} into #{to_idx - from_idx + 1} files"
184
+ end
185
+
186
+ desc "watermark INPUT OUTPUT --text TEXT", "Stamp text on every page"
187
+ method_option :text, type: :string, required: true
188
+ method_option :font, type: :string, default: "Helvetica"
189
+ method_option :size, type: :numeric, default: 50
190
+ method_option :color, type: :string, default: "0.5,0.5,0.5"
191
+ method_option :opacity, type: :numeric, default: 0.3
192
+ method_option :angle, type: :numeric, default: 45
193
+ def watermark(input, output)
194
+ doc = open_doc(input)
195
+ color = options[:color].split(",").map(&:to_f)
196
+ doc.pages.each do |page|
197
+ canvas = page.canvas
198
+ doc.graphics_state.register_transparency(page, opacity: options[:opacity])
199
+ canvas.save_graphics_state
200
+ canvas.opacity = options[:opacity]
201
+ canvas.fill_color(color)
202
+ canvas.text(options[:text], at: [100, 400],
203
+ font: options[:font].to_sym,
204
+ size: options[:size])
205
+ canvas.restore_graphics_state
206
+ end
207
+ doc.write(output)
208
+ puts "Watermarked #{doc.pages.count} pages → #{output}"
209
+ end
210
+
211
+ desc "modify INPUT OUTPUT", "Apply modifications to a PDF"
212
+ method_option :delete, type: :string, banner: "PAGE_RANGE e.g. 1-3,5"
213
+ method_option :rotate, type: :numeric, banner: "ANGLE 0/90/180/270"
214
+ def modify(input, output)
215
+ doc = open_doc(input)
216
+ apply_delete(doc, options[:delete]) if options[:delete]
217
+ apply_rotate(doc, options[:rotate]) if options[:rotate]
218
+ doc.write(output)
219
+ puts "Modified → #{output}"
220
+ end
221
+
222
+ desc "inspect INPUT", "Walk the PDF structure"
223
+ method_option :object, type: :numeric, banner: "OID to dump"
224
+ def inspect(input)
225
+ doc = open_doc(input)
226
+ if options[:object]
227
+ ref = Pdfrb::Model::Reference.new(options[:object], 0)
228
+ obj = doc.object(ref)
229
+ puts obj&.value
230
+ return
231
+ end
232
+
233
+ catalog = doc.catalog
234
+ puts "Catalog: /Type=#{catalog[:Type]}"
235
+ puts " Pages: #{doc.pages.count}"
236
+ puts " Title: #{doc.metadata.title || '-'}"
237
+ af = catalog[:AcroForm]
238
+ puts " AcroForm: #{af ? 'present' : 'absent'}"
239
+ outlines = catalog[:Outlines]
240
+ puts " Outlines: #{outlines ? 'present' : 'absent'}"
241
+ end
242
+
243
+ desc "files INPUT", "List embedded files in the document"
244
+ def files(input)
245
+ doc = open_doc(input)
246
+ if doc.files.empty?
247
+ puts "No embedded files."
248
+ return
249
+ end
250
+ doc.files.each do |name, spec|
251
+ size = spec[:EF] ? "embedded" : "external"
252
+ puts "#{name} (#{size})"
253
+ end
254
+ end
255
+
256
+ desc "files-add INPUT OUTPUT FILE", "Embed a file into the document"
257
+ method_option :relationship, type: :string, default: nil
258
+ def files_add(input, output, file)
259
+ doc = open_doc(input)
260
+ doc.files.add(file, name: File.basename(file),
261
+ relationship: options[:relationship]&.to_sym)
262
+ doc.write(output)
263
+ puts "Embedded #{file} → #{output}"
264
+ end
265
+
266
+ desc "batch INPUT OUTPUT COMMAND1 ARGS... -- COMMAND2 ARGS...",
267
+ "Run multiple operations on a single document"
268
+ def batch(input, output, *args)
269
+ doc = open_doc(input)
270
+ commands = args.split("--").reject(&:empty?)
271
+ commands.each do |cmd_args|
272
+ cmd, *cmd_args = cmd_args
273
+ apply_batch_command(doc, cmd, cmd_args)
274
+ end
275
+ doc.write(output)
276
+ puts "Batch processed → #{output}"
277
+ end
278
+
279
+ PERMISSION_BITS = {
280
+ "print" => 4,
281
+ "modify" => 8,
282
+ "copy" => 16,
283
+ "annotate" => 32,
284
+ "fill" => 256,
285
+ "extract" => 512,
286
+ "assemble" => 1024,
287
+ "print-hq" => 2048,
288
+ }.freeze
289
+
140
290
  private
141
291
 
292
+ def parse_permissions(spec)
293
+ return -1 if spec.empty? || spec == "all"
294
+
295
+ granted = PERMISSION_BITS.values.sum
296
+ spec.split(",").map(&:strip).each do |perm|
297
+ bit = PERMISSION_BITS[perm]
298
+ granted &= ~bit if bit
299
+ end
300
+ granted
301
+ end
302
+
303
+ def build_encrypt(doc, user_pw, owner_pw, perms, v:, r:, length:)
304
+ require "digest/md5"
305
+
306
+ id0 = Digest::MD5.hexdigest(Time.now.to_s + rand.to_s)[0, 16]
307
+ doc.trailer[:ID] = [id0.b, id0.b]
308
+
309
+ # Compute /O entry (owner password hash).
310
+ o_entry = compute_o_entry(user_pw, owner_pw, r, length)
311
+
312
+ # Derive encryption key from user password (Algorithm 2).
313
+ key = Pdfrb::Encryption::PasswordVerification.derive_key_rc4(
314
+ password: user_pw, o_entry: o_entry.b, p_flags: perms,
315
+ id0: id0.b, revision: r, key_length_bits: length,
316
+ encrypt_metadata: true
317
+ )
318
+
319
+ # Build /U entry (Algorithm 4/5).
320
+ u_entry = if r == 2
321
+ Pdfrb::Encryption::PasswordVerification.build_u_r2(key)
322
+ else
323
+ Pdfrb::Encryption::PasswordVerification.build_u_r3plus(
324
+ file_key: key, id0: id0.b, revision: r
325
+ )
326
+ end
327
+
328
+ encrypt_dict = doc.add(
329
+ { Filter: :Standard, V: v, R: r, Length: length, P: perms,
330
+ O: o_entry, U: u_entry },
331
+ type: Pdfrb::Model::Cos::Dictionary
332
+ )
333
+ doc.trailer[:Encrypt] = Pdfrb::Model::Reference.new(encrypt_dict.oid, encrypt_dict.gen)
334
+
335
+ # Set up the handler so Writer can encrypt per-object.
336
+ handler = Pdfrb::Encryption::StandardSecurityHandler.new(
337
+ Encrypt: { V: v, R: r, Length: length, P: perms, O: o_entry, U: u_entry },
338
+ ID: [id0.b, id0.b]
339
+ )
340
+ handler.verify_user_password(user_pw)
341
+ doc.config["encryption.handler"] = handler
342
+ doc.config["encryption.password"] = user_pw
343
+ end
344
+
345
+ def build_aes256_encrypt(doc, user_pw, owner_pw, perms)
346
+ require "digest/sha2"
347
+ require "securerandom"
348
+
349
+ val_salt = SecureRandom.random_bytes(8)
350
+ key_salt = SecureRandom.random_bytes(8)
351
+ u_hash = Digest::SHA256.digest(user_pw + val_salt)
352
+ u_entry = u_hash + val_salt + key_salt
353
+
354
+ o_val_salt = SecureRandom.random_bytes(8)
355
+ o_key_salt = SecureRandom.random_bytes(8)
356
+ o_hash = Digest::SHA256.digest(owner_pw + o_val_salt + u_hash)
357
+ o_entry = o_hash + o_val_salt + o_key_salt
358
+
359
+ id0 = Digest::MD5.hexdigest(Time.now.to_s + rand.to_s)[0, 16]
360
+ doc.trailer[:ID] = [id0.b, id0.b]
361
+
362
+ encrypt_dict = doc.add(
363
+ { Filter: :Standard, V: 5, R: 6, Length: 256, P: perms,
364
+ O: o_entry, U: u_entry, OE: "".b, UE: "".b },
365
+ type: Pdfrb::Model::Cos::Dictionary
366
+ )
367
+ doc.trailer[:Encrypt] = Pdfrb::Model::Reference.new(encrypt_dict.oid, encrypt_dict.gen)
368
+ doc.config["encryption.password"] = user_pw
369
+ end
370
+
371
+ def compute_o_entry(user_pw, owner_pw, revision, length_bits)
372
+ Pdfrb::Encryption::PasswordVerification.pad_password(owner_pw)
373
+ require "digest/md5"
374
+ padded = Pdfrb::Encryption::PasswordVerification.pad_password(owner_pw)
375
+ hash = Digest::MD5.digest(padded)
376
+
377
+ if revision >= 3
378
+ 50.times { hash = Digest::MD5.digest(hash[0, length_bits / 8]) }
379
+ end
380
+
381
+ rc4 = Pdfrb::Encryption::RC4.new(hash[0, length_bits / 8])
382
+ user_padded = Pdfrb::Encryption::PasswordVerification.pad_password(user_pw)
383
+ result = rc4.process(user_padded)
384
+
385
+ if revision >= 3
386
+ 19.times do |i|
387
+ key = hash[0, length_bits / 8].bytes.map { |b| (b ^ i).chr }.join
388
+ result = Pdfrb::Encryption::RC4.new(key).process(result)
389
+ end
390
+ end
391
+
392
+ result + ("\x00".b * [0, 32 - result.bytesize].max)
393
+ end
394
+
395
+ def apply_delete(doc, range_spec)
396
+ ranges = range_spec.split(",").map { |r| parse_range(r, doc.pages.count) }
397
+ oids_to_delete = []
398
+ ranges.each do |(from, to)|
399
+ (from..to).each { |i| oids_to_delete << doc.pages[i - 1].oid }
400
+ end
401
+ oids_to_delete.uniq.each { |oid| doc.pages.delete_at(oid) }
402
+ end
403
+
404
+ def apply_rotate(doc, angle)
405
+ doc.pages.each { |page| page.value[:Rotate] = angle }
406
+ end
407
+
408
+ def parse_range(spec, _total)
409
+ if spec.include?("-")
410
+ from, to = spec.split("-").map(&:to_i)
411
+ [from, to]
412
+ else
413
+ n = spec.to_i
414
+ [n, n]
415
+ end
416
+ end
417
+
418
+ def apply_batch_command(doc, cmd, args)
419
+ case cmd
420
+ when "rotate" then apply_rotate(doc, args.first.to_i)
421
+ when "delete" then apply_delete(doc, args.first)
422
+ else warn "unknown batch command: #{cmd}"
423
+ end
424
+ end
425
+
142
426
  def open_doc(path)
143
427
  raise "no such file: #{path}" unless File.exist?(path)
144
428