pdfrb 0.2.1 → 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 (406) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +24 -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 +48 -0
  23. data/lib/pdfrb/action/base.rb +21 -0
  24. data/lib/pdfrb/action/types.rb +178 -0
  25. data/lib/pdfrb/action.rb +55 -0
  26. data/lib/pdfrb/annotation/base.rb +79 -0
  27. data/lib/pdfrb/annotation/types.rb +222 -0
  28. data/lib/pdfrb/annotation.rb +78 -0
  29. data/lib/pdfrb/appearance/generator.rb +216 -0
  30. data/lib/pdfrb/appearance.rb +11 -0
  31. data/lib/pdfrb/arlington/object_definition.rb +2 -2
  32. data/lib/pdfrb/arlington/predicate/evaluator.rb +1 -1
  33. data/lib/pdfrb/arlington/predicate/functions.rb +25 -9
  34. data/lib/pdfrb/arlington/predicate.rb +1 -5
  35. data/lib/pdfrb/cli/batch.rb +18 -0
  36. data/lib/pdfrb/cli/debug_info.rb +18 -0
  37. data/lib/pdfrb/cli/files.rb +16 -0
  38. data/lib/pdfrb/cli/fonts.rb +16 -0
  39. data/lib/pdfrb/cli/form.rb +18 -0
  40. data/lib/pdfrb/cli/image2pdf.rb +18 -0
  41. data/lib/pdfrb/cli/images.rb +34 -0
  42. data/lib/pdfrb/cli/info.rb +20 -0
  43. data/lib/pdfrb/cli/inspect.rb +18 -0
  44. data/lib/pdfrb/cli/merge.rb +18 -0
  45. data/lib/pdfrb/cli/modify.rb +18 -0
  46. data/lib/pdfrb/cli/optimize.rb +18 -0
  47. data/lib/pdfrb/cli/split.rb +21 -0
  48. data/lib/pdfrb/cli/watermark.rb +18 -0
  49. data/lib/pdfrb/cli.rb +312 -7
  50. data/lib/pdfrb/color/color_space.rb +259 -0
  51. data/lib/pdfrb/color/icc_profile.rb +90 -0
  52. data/lib/pdfrb/color.rb +12 -0
  53. data/lib/pdfrb/compare/comparator.rb +244 -0
  54. data/lib/pdfrb/compare/report.rb +78 -0
  55. data/lib/pdfrb/compare.rb +42 -0
  56. data/lib/pdfrb/composer.rb +161 -0
  57. data/lib/pdfrb/conformance/pdf_a.rb +317 -56
  58. data/lib/pdfrb/conformance/pdf_ua.rb +186 -29
  59. data/lib/pdfrb/conformance/pdf_x.rb +247 -0
  60. data/lib/pdfrb/conformance/rule.rb +78 -0
  61. data/lib/pdfrb/conformance/structure_elements.rb +174 -0
  62. data/lib/pdfrb/conformance/verapdf_bridge.rb +92 -0
  63. data/lib/pdfrb/conformance.rb +7 -3
  64. data/lib/pdfrb/content/canvas.rb +217 -72
  65. data/lib/pdfrb/content/color_space.rb +157 -0
  66. data/lib/pdfrb/content/hidden_text_detector.rb +167 -0
  67. data/lib/pdfrb/content/operator.rb +21 -1
  68. data/lib/pdfrb/content/operators/clipping.rb +36 -0
  69. data/lib/pdfrb/content/operators/graphics_state_params.rb +15 -0
  70. data/lib/pdfrb/content/operators.rb +21 -0
  71. data/lib/pdfrb/content/shading/base.rb +139 -0
  72. data/lib/pdfrb/content/shading.rb +17 -0
  73. data/lib/pdfrb/content/smart_text_extractor.rb +85 -0
  74. data/lib/pdfrb/content/tiling_pattern.rb +78 -0
  75. data/lib/pdfrb/content/transformation_matrix.rb +61 -0
  76. data/lib/pdfrb/content.rb +7 -26
  77. data/lib/pdfrb/destination/fit.rb +185 -0
  78. data/lib/pdfrb/destination.rb +13 -0
  79. data/lib/pdfrb/digital_signature/cms_handler.rb +22 -0
  80. data/lib/pdfrb/digital_signature/signing.rb +107 -11
  81. data/lib/pdfrb/digital_signature/timestamp_handler.rb +45 -0
  82. data/lib/pdfrb/digital_signature/verification.rb +88 -9
  83. data/lib/pdfrb/digital_signature/verification_result.rb +14 -0
  84. data/lib/pdfrb/digital_signature.rb +26 -2
  85. data/lib/pdfrb/document/annotations.rb +120 -3
  86. data/lib/pdfrb/document/associated_files.rb +104 -0
  87. data/lib/pdfrb/document/colors.rb +95 -0
  88. data/lib/pdfrb/document/destinations.rb +127 -5
  89. data/lib/pdfrb/document/display.rb +84 -0
  90. data/lib/pdfrb/document/fonts.rb +417 -45
  91. data/lib/pdfrb/document/form.rb +338 -0
  92. data/lib/pdfrb/document/form_xobject.rb +68 -0
  93. data/lib/pdfrb/document/graphics_state.rb +91 -0
  94. data/lib/pdfrb/document/images.rb +4 -4
  95. data/lib/pdfrb/document/info.rb +102 -0
  96. data/lib/pdfrb/document/layers.rb +62 -0
  97. data/lib/pdfrb/document/metadata.rb +12 -5
  98. data/lib/pdfrb/document/output_intents.rb +91 -0
  99. data/lib/pdfrb/document/page_labels.rb +80 -0
  100. data/lib/pdfrb/document/pages.rb +68 -19
  101. data/lib/pdfrb/document/portfolio.rb +119 -0
  102. data/lib/pdfrb/document/shadings.rb +159 -0
  103. data/lib/pdfrb/document/stamps.rb +68 -0
  104. data/lib/pdfrb/document/structure.rb +242 -0
  105. data/lib/pdfrb/document.rb +73 -4
  106. data/lib/pdfrb/encryption/identity.rb +13 -0
  107. data/lib/pdfrb/encryption/password_verification.rb +53 -8
  108. data/lib/pdfrb/encryption/security_handler.rb +8 -5
  109. data/lib/pdfrb/encryption/standard_security_handler.rb +154 -81
  110. data/lib/pdfrb/encryption.rb +17 -0
  111. data/lib/pdfrb/error.rb +2 -2
  112. data/lib/pdfrb/filter/brotli_decode.rb +32 -0
  113. data/lib/pdfrb/filter/ccitt_fax_decode.rb +12 -0
  114. data/lib/pdfrb/filter/crypt.rb +2 -2
  115. data/lib/pdfrb/filter/dct_decode.rb +18 -0
  116. data/lib/pdfrb/filter/flate_decode.rb +4 -18
  117. data/lib/pdfrb/filter/jbig2_decode.rb +12 -0
  118. data/lib/pdfrb/filter/jpx_decode.rb +18 -0
  119. data/lib/pdfrb/filter/lzw_decode.rb +3 -2
  120. data/lib/pdfrb/filter/png_predictor.rb +94 -0
  121. data/lib/pdfrb/filter.rb +26 -13
  122. data/lib/pdfrb/font/afm_parser.rb +50 -26
  123. data/lib/pdfrb/font/cmap/writer.rb +9 -0
  124. data/lib/pdfrb/font/encoding/win_ansi_encoding.rb +17 -0
  125. data/lib/pdfrb/font/encoding.rb +32 -3
  126. data/lib/pdfrb/font/metrics_helper.rb +121 -0
  127. data/lib/pdfrb/font/true_type/file.rb +10 -1
  128. data/lib/pdfrb/font/true_type/glyf.rb +82 -0
  129. data/lib/pdfrb/font/true_type/kern.rb +58 -0
  130. data/lib/pdfrb/font/true_type/loca.rb +42 -0
  131. data/lib/pdfrb/font/true_type/maxp.rb +44 -0
  132. data/lib/pdfrb/font/true_type/name.rb +53 -0
  133. data/lib/pdfrb/font/true_type/post.rb +93 -0
  134. data/lib/pdfrb/font/true_type/subsetter.rb +3 -5
  135. data/lib/pdfrb/font/true_type/wrapper.rb +102 -0
  136. data/lib/pdfrb/font/true_type.rb +7 -0
  137. data/lib/pdfrb/font.rb +2 -0
  138. data/lib/pdfrb/font_loader/from_configuration.rb +19 -0
  139. data/lib/pdfrb/font_loader/from_file.rb +20 -0
  140. data/lib/pdfrb/font_loader/standard14.rb +20 -0
  141. data/lib/pdfrb/font_loader/variant_from_name.rb +36 -0
  142. data/lib/pdfrb/font_loader.rb +4 -0
  143. data/lib/pdfrb/font_resolver.rb +138 -0
  144. data/lib/pdfrb/image/audit.rb +94 -0
  145. data/lib/pdfrb/image/downsampler.rb +124 -0
  146. data/lib/pdfrb/image.rb +13 -0
  147. data/lib/pdfrb/image_loader/jpeg.rb +39 -58
  148. data/lib/pdfrb/image_loader/png.rb +31 -257
  149. data/lib/pdfrb/layout/bidi.rb +198 -0
  150. data/lib/pdfrb/layout/box.rb +49 -0
  151. data/lib/pdfrb/layout/box_fitter.rb +54 -0
  152. data/lib/pdfrb/layout/column_box.rb +71 -0
  153. data/lib/pdfrb/layout/container_box.rb +45 -0
  154. data/lib/pdfrb/layout/frame.rb +67 -0
  155. data/lib/pdfrb/layout/image_box.rb +44 -0
  156. data/lib/pdfrb/layout/inline_box.rb +25 -0
  157. data/lib/pdfrb/layout/line.rb +55 -0
  158. data/lib/pdfrb/layout/list_box.rb +87 -0
  159. data/lib/pdfrb/layout/numeric_refinements.rb +43 -0
  160. data/lib/pdfrb/layout/page_style.rb +65 -0
  161. data/lib/pdfrb/layout/style.rb +62 -0
  162. data/lib/pdfrb/layout/table_box.rb +66 -0
  163. data/lib/pdfrb/layout/text_box.rb +38 -0
  164. data/lib/pdfrb/layout/text_fragment.rb +45 -0
  165. data/lib/pdfrb/layout/text_layouter.rb +123 -0
  166. data/lib/pdfrb/layout.rb +28 -0
  167. data/lib/pdfrb/linearization/hint_stream.rb +93 -0
  168. data/lib/pdfrb/linearization/writer.rb +259 -0
  169. data/lib/pdfrb/linearization.rb +21 -0
  170. data/lib/pdfrb/model/cos/dictionary.rb +12 -0
  171. data/lib/pdfrb/model/pdf_array.rb +2 -2
  172. data/lib/pdfrb/model/type/action.rb +42 -0
  173. data/lib/pdfrb/model/type/action_go_to_3d_view.rb +14 -0
  174. data/lib/pdfrb/model/type/action_go_to_dp.rb +16 -0
  175. data/lib/pdfrb/model/type/action_go_to_r.rb +26 -0
  176. data/lib/pdfrb/model/type/action_goto.rb +34 -0
  177. data/lib/pdfrb/model/type/action_hide.rb +26 -0
  178. data/lib/pdfrb/model/type/action_import_data.rb +18 -0
  179. data/lib/pdfrb/model/type/action_java_script.rb +18 -0
  180. data/lib/pdfrb/model/type/action_launch.rb +22 -0
  181. data/lib/pdfrb/model/type/action_movie.rb +22 -0
  182. data/lib/pdfrb/model/type/action_named.rb +23 -0
  183. data/lib/pdfrb/model/type/action_nop.rb +12 -0
  184. data/lib/pdfrb/model/type/action_rendition.rb +28 -0
  185. data/lib/pdfrb/model/type/action_reset_form.rb +22 -0
  186. data/lib/pdfrb/model/type/action_rich_media_execute.rb +17 -0
  187. data/lib/pdfrb/model/type/action_set_ocg_state.rb +32 -0
  188. data/lib/pdfrb/model/type/action_set_state.rb +14 -0
  189. data/lib/pdfrb/model/type/action_sound_action.rb +23 -0
  190. data/lib/pdfrb/model/type/action_submit_form.rb +64 -0
  191. data/lib/pdfrb/model/type/action_thread.rb +23 -0
  192. data/lib/pdfrb/model/type/action_trans.rb +30 -0
  193. data/lib/pdfrb/model/type/action_uri.rb +30 -0
  194. data/lib/pdfrb/model/type/af_embedded_file.rb +11 -0
  195. data/lib/pdfrb/model/type/af_file_specification.rb +34 -0
  196. data/lib/pdfrb/model/type/annotation.rb +49 -10
  197. data/lib/pdfrb/model/type/appearance.rb +41 -0
  198. data/lib/pdfrb/model/type/appearance_characteristics.rb +36 -0
  199. data/lib/pdfrb/model/type/appearance_generator.rb +22 -0
  200. data/lib/pdfrb/model/type/appearance_trap_net.rb +84 -0
  201. data/lib/pdfrb/model/type/border_effect.rb +25 -0
  202. data/lib/pdfrb/model/type/border_styling.rb +38 -0
  203. data/lib/pdfrb/model/type/box_color_info.rb +21 -0
  204. data/lib/pdfrb/model/type/box_style.rb +19 -0
  205. data/lib/pdfrb/model/type/button.rb +17 -0
  206. data/lib/pdfrb/model/type/cal_gray.rb +17 -0
  207. data/lib/pdfrb/model/type/cal_rgb.rb +22 -0
  208. data/lib/pdfrb/model/type/caret_annotation.rb +21 -0
  209. data/lib/pdfrb/model/type/catalog.rb +99 -16
  210. data/lib/pdfrb/model/type/choice.rb +21 -0
  211. data/lib/pdfrb/model/type/cid_font.rb +52 -1
  212. data/lib/pdfrb/model/type/cid_font_descriptor_metrics.rb +28 -0
  213. data/lib/pdfrb/model/type/cid_system_info.rb +28 -0
  214. data/lib/pdfrb/model/type/circle_annotation.rb +20 -0
  215. data/lib/pdfrb/model/type/collection.rb +27 -0
  216. data/lib/pdfrb/model/type/collection_field.rb +24 -0
  217. data/lib/pdfrb/model/type/collection_item.rb +19 -0
  218. data/lib/pdfrb/model/type/collection_schema.rb +25 -0
  219. data/lib/pdfrb/model/type/collection_sort.rb +13 -0
  220. data/lib/pdfrb/model/type/crypt_filter.rb +58 -0
  221. data/lib/pdfrb/model/type/device_n.rb +37 -0
  222. data/lib/pdfrb/model/type/device_n_mixing_hints.rb +17 -0
  223. data/lib/pdfrb/model/type/device_n_process.rb +13 -0
  224. data/lib/pdfrb/model/type/doc_mdp_transform_parameters.rb +21 -0
  225. data/lib/pdfrb/model/type/document_security_store.rb +55 -0
  226. data/lib/pdfrb/model/type/embedded_file.rb +19 -0
  227. data/lib/pdfrb/model/type/embedded_file_parameter.rb +48 -0
  228. data/lib/pdfrb/model/type/encryption_public_key.rb +38 -0
  229. data/lib/pdfrb/model/type/encryption_standard.rb +68 -0
  230. data/lib/pdfrb/model/type/field_mdp_transform_parameters.rb +26 -0
  231. data/lib/pdfrb/model/type/file_attachment_annotation.rb +29 -0
  232. data/lib/pdfrb/model/type/file_spec_ef.rb +22 -0
  233. data/lib/pdfrb/model/type/file_spec_rf.rb +21 -0
  234. data/lib/pdfrb/model/type/file_specification.rb +43 -0
  235. data/lib/pdfrb/model/type/file_trailer.rb +54 -0
  236. data/lib/pdfrb/model/type/font.rb +73 -2
  237. data/lib/pdfrb/model/type/font_cid_type0.rb +17 -0
  238. data/lib/pdfrb/model/type/font_cid_type2.rb +17 -0
  239. data/lib/pdfrb/model/type/font_descriptor.rb +47 -0
  240. data/lib/pdfrb/model/type/font_descriptor_cid_type0.rb +15 -0
  241. data/lib/pdfrb/model/type/font_descriptor_cid_type2.rb +15 -0
  242. data/lib/pdfrb/model/type/font_encoding.rb +47 -0
  243. data/lib/pdfrb/model/type/font_file.rb +28 -0
  244. data/lib/pdfrb/model/type/font_file2.rb +32 -0
  245. data/lib/pdfrb/model/type/font_file3.rb +23 -0
  246. data/lib/pdfrb/model/type/font_multiple_master.rb +60 -0
  247. data/lib/pdfrb/model/type/font_true_type.rb +1 -1
  248. data/lib/pdfrb/model/type/font_type0.rb +1 -1
  249. data/lib/pdfrb/model/type/font_type1.rb +1 -1
  250. data/lib/pdfrb/model/type/font_type3.rb +1 -1
  251. data/lib/pdfrb/model/type/form_field.rb +73 -0
  252. data/lib/pdfrb/model/type/free_text_annotation.rb +37 -0
  253. data/lib/pdfrb/model/type/function.rb +42 -0
  254. data/lib/pdfrb/model/type/function_exponential.rb +31 -0
  255. data/lib/pdfrb/model/type/function_postscript.rb +25 -0
  256. data/lib/pdfrb/model/type/function_sampled.rb +55 -0
  257. data/lib/pdfrb/model/type/function_stitching.rb +33 -0
  258. data/lib/pdfrb/model/type/generic_appearance.rb +40 -0
  259. data/lib/pdfrb/model/type/graphics_state_parameter.rb +69 -0
  260. data/lib/pdfrb/model/type/halftone.rb +21 -0
  261. data/lib/pdfrb/model/type/halftone_type1.rb +17 -0
  262. data/lib/pdfrb/model/type/halftone_type10.rb +16 -0
  263. data/lib/pdfrb/model/type/halftone_type16.rb +11 -0
  264. data/lib/pdfrb/model/type/halftone_type5.rb +24 -0
  265. data/lib/pdfrb/model/type/halftone_type6.rb +16 -0
  266. data/lib/pdfrb/model/type/highlight_annotation.rb +11 -0
  267. data/lib/pdfrb/model/type/icc_based_color_space.rb +24 -0
  268. data/lib/pdfrb/model/type/icon_fit.rb +31 -0
  269. data/lib/pdfrb/model/type/indexed.rb +32 -0
  270. data/lib/pdfrb/model/type/info.rb +44 -0
  271. data/lib/pdfrb/model/type/ink_annotation.rb +29 -0
  272. data/lib/pdfrb/model/type/interactive_form.rb +36 -0
  273. data/lib/pdfrb/model/type/interior_color.rb +12 -0
  274. data/lib/pdfrb/model/type/jbig2_globals.rb +15 -0
  275. data/lib/pdfrb/model/type/lab.rb +27 -0
  276. data/lib/pdfrb/model/type/line_annotation.rb +44 -0
  277. data/lib/pdfrb/model/type/line_ending_styling.rb +39 -0
  278. data/lib/pdfrb/model/type/link_annotation.rb +41 -0
  279. data/lib/pdfrb/model/type/link_appearance.rb +9 -0
  280. data/lib/pdfrb/model/type/mark_information.rb +23 -0
  281. data/lib/pdfrb/model/type/marked_content_reference.rb +24 -0
  282. data/lib/pdfrb/model/type/markup_annotation.rb +36 -0
  283. data/lib/pdfrb/model/type/mdp_dict.rb +28 -0
  284. data/lib/pdfrb/model/type/measure.rb +39 -0
  285. data/lib/pdfrb/model/type/media_clip.rb +28 -0
  286. data/lib/pdfrb/model/type/media_offset.rb +106 -0
  287. data/lib/pdfrb/model/type/metadata.rb +27 -3
  288. data/lib/pdfrb/model/type/misc_helpers.rb +75 -0
  289. data/lib/pdfrb/model/type/movie.rb +28 -0
  290. data/lib/pdfrb/model/type/names.rb +51 -0
  291. data/lib/pdfrb/model/type/namespace.rb +27 -0
  292. data/lib/pdfrb/model/type/object_reference.rb +31 -0
  293. data/lib/pdfrb/model/type/object_stream.rb +38 -0
  294. data/lib/pdfrb/model/type/opt_content_ext.rb +94 -0
  295. data/lib/pdfrb/model/type/opt_content_page_element.rb +18 -0
  296. data/lib/pdfrb/model/type/optional_content_config.rb +56 -0
  297. data/lib/pdfrb/model/type/optional_content_group.rb +35 -10
  298. data/lib/pdfrb/model/type/optional_content_membership.rb +36 -2
  299. data/lib/pdfrb/model/type/optional_content_properties.rb +16 -1
  300. data/lib/pdfrb/model/type/outline.rb +40 -6
  301. data/lib/pdfrb/model/type/outline_item.rb +45 -0
  302. data/lib/pdfrb/model/type/output_intent.rb +30 -0
  303. data/lib/pdfrb/model/type/page.rb +127 -0
  304. data/lib/pdfrb/model/type/page_label.rb +68 -0
  305. data/lib/pdfrb/model/type/page_tree_node.rb +51 -16
  306. data/lib/pdfrb/model/type/page_tree_node_root.rb +15 -0
  307. data/lib/pdfrb/model/type/pattern.rb +16 -0
  308. data/lib/pdfrb/model/type/pattern_shading.rb +25 -0
  309. data/lib/pdfrb/model/type/pattern_tiling.rb +25 -0
  310. data/lib/pdfrb/model/type/polygon_annotation.rb +29 -0
  311. data/lib/pdfrb/model/type/polygon_polyline.rb +14 -0
  312. data/lib/pdfrb/model/type/polyline_annotation.rb +29 -0
  313. data/lib/pdfrb/model/type/popup_annotation.rb +20 -0
  314. data/lib/pdfrb/model/type/printer_mark_annotation.rb +13 -0
  315. data/lib/pdfrb/model/type/redact_annotation.rb +28 -0
  316. data/lib/pdfrb/model/type/rendition.rb +31 -0
  317. data/lib/pdfrb/model/type/resources.rb +59 -14
  318. data/lib/pdfrb/model/type/rich_media_activation.rb +24 -0
  319. data/lib/pdfrb/model/type/rich_media_animation.rb +25 -0
  320. data/lib/pdfrb/model/type/rich_media_annotation.rb +18 -0
  321. data/lib/pdfrb/model/type/rich_media_command.rb +19 -0
  322. data/lib/pdfrb/model/type/rich_media_configuration.rb +32 -0
  323. data/lib/pdfrb/model/type/rich_media_content.rb +28 -0
  324. data/lib/pdfrb/model/type/rich_media_cue_point.rb +18 -0
  325. data/lib/pdfrb/model/type/rich_media_deactivation.rb +17 -0
  326. data/lib/pdfrb/model/type/rich_media_instance.rb +33 -0
  327. data/lib/pdfrb/model/type/rich_media_presentation.rb +29 -0
  328. data/lib/pdfrb/model/type/rich_media_settings.rb +33 -0
  329. data/lib/pdfrb/model/type/rich_media_window.rb +20 -0
  330. data/lib/pdfrb/model/type/screen_annotation.rb +19 -0
  331. data/lib/pdfrb/model/type/separation.rb +25 -0
  332. data/lib/pdfrb/model/type/sig_field_lock.rb +25 -0
  333. data/lib/pdfrb/model/type/sig_field_seed_value.rb +25 -0
  334. data/lib/pdfrb/model/type/signature.rb +65 -0
  335. data/lib/pdfrb/model/type/signature_build.rb +86 -0
  336. data/lib/pdfrb/model/type/signature_field.rb +17 -0
  337. data/lib/pdfrb/model/type/software_identifier.rb +19 -0
  338. data/lib/pdfrb/model/type/sound.rb +38 -0
  339. data/lib/pdfrb/model/type/sound_annotation.rb +21 -0
  340. data/lib/pdfrb/model/type/square_annotation.rb +24 -0
  341. data/lib/pdfrb/model/type/square_circle.rb +13 -0
  342. data/lib/pdfrb/model/type/squiggly_annotation.rb +11 -0
  343. data/lib/pdfrb/model/type/stamp_annotation.rb +19 -0
  344. data/lib/pdfrb/model/type/strikeout_annotation.rb +11 -0
  345. data/lib/pdfrb/model/type/struct_elem.rb +75 -0
  346. data/lib/pdfrb/model/type/struct_tree_root.rb +34 -0
  347. data/lib/pdfrb/model/type/structure_attributes.rb +44 -0
  348. data/lib/pdfrb/model/type/structure_element_kid.rb +33 -0
  349. data/lib/pdfrb/model/type/text_annotation.rb +25 -0
  350. data/lib/pdfrb/model/type/text_appearance.rb +9 -0
  351. data/lib/pdfrb/model/type/text_field.rb +21 -0
  352. data/lib/pdfrb/model/type/text_markup_annotation.rb +30 -0
  353. data/lib/pdfrb/model/type/three_d_activation.rb +32 -0
  354. data/lib/pdfrb/model/type/three_d_animation_style.rb +24 -0
  355. data/lib/pdfrb/model/type/three_d_background.rb +28 -0
  356. data/lib/pdfrb/model/type/three_d_cross_section.rb +25 -0
  357. data/lib/pdfrb/model/type/three_d_lighting_scheme.rb +26 -0
  358. data/lib/pdfrb/model/type/three_d_measure.rb +20 -0
  359. data/lib/pdfrb/model/type/three_d_measure_3dc.rb +23 -0
  360. data/lib/pdfrb/model/type/three_d_measure_ad3.rb +14 -0
  361. data/lib/pdfrb/model/type/three_d_measure_ld3.rb +14 -0
  362. data/lib/pdfrb/model/type/three_d_measure_pd3.rb +20 -0
  363. data/lib/pdfrb/model/type/three_d_measure_rd3.rb +14 -0
  364. data/lib/pdfrb/model/type/three_d_node.rb +37 -0
  365. data/lib/pdfrb/model/type/three_d_reference.rb +21 -0
  366. data/lib/pdfrb/model/type/three_d_render_mode.rb +34 -0
  367. data/lib/pdfrb/model/type/three_d_stream.rb +48 -0
  368. data/lib/pdfrb/model/type/three_d_units.rb +18 -0
  369. data/lib/pdfrb/model/type/three_d_view.rb +40 -0
  370. data/lib/pdfrb/model/type/to_unicode_cmap_stream.rb +23 -0
  371. data/lib/pdfrb/model/type/transition.rb +68 -0
  372. data/lib/pdfrb/model/type/underline_annotation.rb +11 -0
  373. data/lib/pdfrb/model/type/ur_transform_parameters.rb +33 -0
  374. data/lib/pdfrb/model/type/uri_dict.rb +18 -0
  375. data/lib/pdfrb/model/type/url_alias.rb +19 -0
  376. data/lib/pdfrb/model/type/variable_text_field.rb +47 -0
  377. data/lib/pdfrb/model/type/viewer_preferences.rb +42 -0
  378. data/lib/pdfrb/model/type/watermark_annotation.rb +13 -0
  379. data/lib/pdfrb/model/type/widget_annotation.rb +48 -0
  380. data/lib/pdfrb/model/type/widget_appearance.rb +9 -0
  381. data/lib/pdfrb/model/type/xobject_form.rb +41 -0
  382. data/lib/pdfrb/model/type/xobject_image.rb +56 -0
  383. data/lib/pdfrb/model/type/xref_stream.rb +37 -0
  384. data/lib/pdfrb/model/type.rb +286 -6
  385. data/lib/pdfrb/serializer.rb +2 -2
  386. data/lib/pdfrb/source/tokenizer.rb +0 -2
  387. data/lib/pdfrb/task/benchmark.rb +83 -0
  388. data/lib/pdfrb/task/extract_text.rb +1 -1
  389. data/lib/pdfrb/task/generate_corpus.rb +130 -0
  390. data/lib/pdfrb/task/memory_profile.rb +84 -0
  391. data/lib/pdfrb/task/optimize.rb +97 -7
  392. data/lib/pdfrb/task.rb +3 -3
  393. data/lib/pdfrb/test_utils.rb +112 -0
  394. data/lib/pdfrb/validator.rb +82 -0
  395. data/lib/pdfrb/version.rb +1 -1
  396. data/lib/pdfrb/writer.rb +117 -27
  397. data/lib/pdfrb/xmp/packet.rb +122 -0
  398. data/lib/pdfrb/xmp/schemas.rb +96 -0
  399. data/lib/pdfrb/xmp.rb +44 -0
  400. data/lib/pdfrb/xref_section.rb +2 -2
  401. data/lib/pdfrb.rb +25 -53
  402. data/pdfrb.gemspec +45 -0
  403. data/script/apply_fixes.sh +843 -0
  404. data/script/make_jpeg_fixture.rb +53 -0
  405. data/script/make_png_fixture.rb +25 -0
  406. metadata +312 -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)
@@ -67,22 +67,74 @@ module Pdfrb
67
67
  puts "Wrote #{output}"
68
68
  end
69
69
 
70
- desc "encrypt INPUT OUTPUT --password PASSWORD", "Encrypt a PDF (stub — needs Phase 11 integration)"
70
+ desc "diff LEFT RIGHT", "Compare two PDFs semantically"
71
+ def diff(left, right)
72
+ report = Pdfrb::Compare.compare(
73
+ File.binread(left),
74
+ File.binread(right)
75
+ )
76
+ puts report.summary
77
+ unless report.equivalent?
78
+ puts
79
+ report.per_page_text_diffs.first(5).each do |d|
80
+ puts " Page #{d[:page] + 1}: #{(d[:similarity] * 100).round(1)}% similar"
81
+ end
82
+ unless report.font_diff[:added].empty? && report.font_diff[:removed].empty?
83
+ puts " Fonts added: #{report.font_diff[:added].join(', ')}" unless report.font_diff[:added].empty?
84
+ puts " Fonts removed: #{report.font_diff[:removed].join(', ')}" unless report.font_diff[:removed].empty?
85
+ end
86
+ puts " Page count delta: #{report.page_count_delta}" unless report.page_count_delta.zero?
87
+ exit 1
88
+ end
89
+ end
90
+
91
+ desc "encrypt INPUT OUTPUT --password PASSWORD", "Encrypt a PDF with RC4 or AES"
71
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"
72
97
  def encrypt(input, output)
73
- warn "encrypt: not yet fully implemented (Phase 11 integration pending)"
74
- # Copy through for now.
75
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
+
76
117
  doc.write(output)
77
- puts "Wrote #{output} (encryption not yet applied)"
118
+ puts "Encrypted #{output} (#{bits}-bit)"
78
119
  end
79
120
 
80
- desc "decrypt INPUT OUTPUT --password PASSWORD", "Decrypt a PDF"
121
+ desc "decrypt INPUT OUTPUT --password PASSWORD", "Decrypt a PDF (remove /Encrypt)"
81
122
  method_option :password, type: :string, default: ""
82
123
  def decrypt(input, output)
83
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
+
84
136
  doc.write(output)
85
- puts "Wrote #{output}"
137
+ puts "Decrypted #{output}"
86
138
  end
87
139
 
88
140
  desc "images-add INPUT IMAGE OUTPUT", "Add an image to a new page"
@@ -116,8 +168,261 @@ module Pdfrb
116
168
  end
117
169
  end
118
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
+
119
290
  private
120
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
+
121
426
  def open_doc(path)
122
427
  raise "no such file: #{path}" unless File.exist?(path)
123
428