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
data/lib/pdfrb/content.rb CHANGED
@@ -1,38 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pdfrb
4
- # Content-stream layer. Pages and Form XObjects carry a content
5
- # stream: a sequence of `operands operator` instructions that draw
6
- # graphics and text. This layer parses, walks, and emits those
7
- # streams.
8
- #
9
- # Architecture mirrors HexaPDF::Content:
10
- # * +Tokenizer+ from Source layer produces tokens (content
11
- # streams use the same PDF object syntax as COS values).
12
- # * +Content::Parser+ groups operands + operator into invocations.
13
- # * +Content::Operator::Base+ subclasses implement each PDF
14
- # operator (one file per category — open/closed via `register`).
15
- # * +Content::Processor+ walks a stream, invoking operators
16
- # against a +GraphicsState+ (with a stack for `q`/`Q`).
17
- # * +Content::Canvas+ is the high-level drawing API on a Stream.
18
4
  module Content
19
5
  autoload :GraphicsState, "pdfrb/content/graphics_state"
20
6
  autoload :Operator, "pdfrb/content/operator"
21
7
  autoload :Parser, "pdfrb/content/parser"
22
8
  autoload :Processor, "pdfrb/content/processor"
9
+ autoload :ColorSpace, "pdfrb/content/color_space"
10
+ autoload :TransformationMatrix, "pdfrb/content/transformation_matrix"
23
11
  autoload :Canvas, "pdfrb/content/canvas"
24
12
  autoload :GraphicObject, "pdfrb/content/graphic_object"
13
+ autoload :Operators, "pdfrb/content/operators"
14
+ autoload :TilingPattern, "pdfrb/content/tiling_pattern"
15
+ autoload :HiddenTextDetector, "pdfrb/content/hidden_text_detector"
16
+ autoload :SmartTextExtractor, "pdfrb/content/smart_text_extractor"
17
+ autoload :Shading, "pdfrb/content/shading"
25
18
  end
26
19
  end
27
-
28
- # Eager-load operator subclasses so they self-register in the
29
- # Operator::REGISTRY. (Each file calls Operator.register at the bottom.)
30
- require "pdfrb/content/operators/general"
31
- require "pdfrb/content/operators/path"
32
- require "pdfrb/content/operators/painting"
33
- require "pdfrb/content/operators/text_state"
34
- require "pdfrb/content/operators/text_positioning"
35
- require "pdfrb/content/operators/text_showing"
36
- require "pdfrb/content/operators/color"
37
- require "pdfrb/content/operators/graphics_state_params"
38
- require "pdfrb/content/operators/marked_content"
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Destination
5
+ # Destination fit types (ISO 32000-2 §12.3.2.2). Each type specifies
6
+ # how the page should be displayed when navigated to.
7
+ #
8
+ # OCP: new fit type = subclass + register_as.
9
+ class Fit
10
+ class << self
11
+ def registry
12
+ @registry ||= {}
13
+ end
14
+
15
+ def register(name, klass)
16
+ registry[name.to_sym] = klass
17
+ end
18
+
19
+ def [](name)
20
+ registry[name.to_sym]
21
+ end
22
+
23
+ def types
24
+ registry.keys
25
+ end
26
+
27
+ def fit_keyword
28
+ raise NotImplementedError
29
+ end
30
+
31
+ def register_as(name = fit_keyword)
32
+ Fit.register(name, self)
33
+ self
34
+ end
35
+ end
36
+
37
+ attr_reader :page_ref
38
+
39
+ def initialize(page_ref)
40
+ @page_ref = page_ref
41
+ end
42
+
43
+ # Serialize to the PDF destination array form.
44
+ # @return [Pdfrb::Model::PdfArray]
45
+ def to_pdf
46
+ Pdfrb::Model::PdfArray.new([@page_ref, self.class.fit_keyword])
47
+ end
48
+ end
49
+
50
+ # /Fit — fit the entire page in the window.
51
+ class FullPage < Fit
52
+ class << self
53
+ def fit_keyword; :Fit; end
54
+ end
55
+ register_as
56
+ end
57
+
58
+ # /FitH top — fit width, top at +top+.
59
+ class FitHorizontal < Fit
60
+ attr_reader :top
61
+
62
+ def initialize(page_ref, top: nil)
63
+ super(page_ref)
64
+ @top = top
65
+ end
66
+
67
+ class << self
68
+ def fit_keyword; :FitH; end
69
+ end
70
+
71
+ def to_pdf
72
+ Pdfrb::Model::PdfArray.new([@page_ref, :FitH, @top].compact)
73
+ end
74
+ register_as
75
+ end
76
+
77
+ # /FitV left — fit height, left at +left+.
78
+ class FitVertical < Fit
79
+ attr_reader :left
80
+
81
+ def initialize(page_ref, left: nil)
82
+ super(page_ref)
83
+ @left = left
84
+ end
85
+
86
+ class << self
87
+ def fit_keyword; :FitV; end
88
+ end
89
+
90
+ def to_pdf
91
+ Pdfrb::Model::PdfArray.new([@page_ref, :FitV, @left].compact)
92
+ end
93
+ register_as
94
+ end
95
+
96
+ # /FitR left bottom right top — fit the specified rectangle.
97
+ class FitRectangle < Fit
98
+ attr_reader :left, :bottom, :right, :top
99
+
100
+ def initialize(page_ref, left:, bottom:, right:, top:)
101
+ super(page_ref)
102
+ @left = left
103
+ @bottom = bottom
104
+ @right = right
105
+ @top = top
106
+ end
107
+
108
+ class << self
109
+ def fit_keyword; :FitR; end
110
+ end
111
+
112
+ def to_pdf
113
+ Pdfrb::Model::PdfArray.new([@page_ref, :FitR, @left, @bottom, @right, @top])
114
+ end
115
+ register_as
116
+ end
117
+
118
+ # /XYZ left top zoom — explicit position and zoom.
119
+ class XYZ < Fit
120
+ attr_reader :left, :top, :zoom
121
+
122
+ def initialize(page_ref, left: nil, top: nil, zoom: nil)
123
+ super(page_ref)
124
+ @left = left
125
+ @top = top
126
+ @zoom = zoom
127
+ end
128
+
129
+ class << self
130
+ def fit_keyword; :XYZ; end
131
+ end
132
+
133
+ def to_pdf
134
+ Pdfrb::Model::PdfArray.new([@page_ref, :XYZ, @left, @top, @zoom].compact)
135
+ end
136
+ register_as
137
+ end
138
+
139
+ # /FitB — fit the bounding box of the page contents.
140
+ class FitBoundingBox < Fit
141
+ class << self
142
+ def fit_keyword; :FitB; end
143
+ end
144
+ register_as
145
+ end
146
+
147
+ # /FitBH top — fit bounding box width, top at +top+.
148
+ class FitBoundingBoxHorizontal < Fit
149
+ attr_reader :top
150
+
151
+ def initialize(page_ref, top: nil)
152
+ super(page_ref)
153
+ @top = top
154
+ end
155
+
156
+ class << self
157
+ def fit_keyword; :FitBH; end
158
+ end
159
+
160
+ def to_pdf
161
+ Pdfrb::Model::PdfArray.new([@page_ref, :FitBH, @top].compact)
162
+ end
163
+ register_as
164
+ end
165
+
166
+ # /FitBV left — fit bounding box height, left at +left+.
167
+ class FitBoundingBoxVertical < Fit
168
+ attr_reader :left
169
+
170
+ def initialize(page_ref, left: nil)
171
+ super(page_ref)
172
+ @left = left
173
+ end
174
+
175
+ class << self
176
+ def fit_keyword; :FitBV; end
177
+ end
178
+
179
+ def to_pdf
180
+ Pdfrb::Model::PdfArray.new([@page_ref, :FitBV, @left].compact)
181
+ end
182
+ register_as
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ # Destinations (ISO 32000-2 §12.3.2). A destination specifies a view
5
+ # of a page: which page, how to fit it in the viewport, and optional
6
+ # position/zoom parameters.
7
+ #
8
+ # Each fit type is a value object that serializes to a PDF destination
9
+ # array: [page_ref /Fit], [page_ref /XYZ x y zoom], etc.
10
+ module Destination
11
+ autoload :Fit, "pdfrb/destination/fit"
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module DigitalSignature
5
+ module CmsHandler
6
+ module_function
7
+
8
+ def type; :"adbe.pkcs7.detached"; end
9
+
10
+ def sign(document, cert:, key:, **)
11
+ Pdfrb::DigitalSignature::Signing.sign(document, cert: cert, key: key, **)
12
+ end
13
+
14
+ def verify(signature_data, signed_data, trusted_certs: [])
15
+ pkcs7 = OpenSSL::PKCS7.new(signature_data)
16
+ store = OpenSSL::X509::Store.new
17
+ trusted_certs.each { |c| store.add_cert(c) }
18
+ pkcs7.verify(nil, store, signed_data, OpenSSL::PKCS7::DETACHED)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,28 +1,124 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "openssl"
4
+ require "stringio"
4
5
 
5
6
  module Pdfrb
6
7
  module DigitalSignature
7
- # PKCS#7 signing (s12.8.3). Creates a signature field, computes
8
- # the byte-range hash, and embeds the detached PKCS#7 signature.
9
8
  module Signing
9
+ PLACEHOLDER_HEX_LEN = 8192
10
+
10
11
  module_function
11
12
 
12
- def sign(document, cert:, key:, **_opts)
13
+ def sign(document, cert:, key:, reason: nil, name: nil)
13
14
  raise Pdfrb::Error, "OpenSSL cert required" unless cert.is_a?(OpenSSL::X509::Certificate)
14
15
  raise Pdfrb::Error, "OpenSSL PKey required" unless key.is_a?(OpenSSL::PKey::PKey)
15
16
 
16
- # Create a signature field (widget annotation).
17
- sig_dict = document.add(
18
- {
19
- Type: :Sig,
20
- Filter: :Adobe.PPKLite,
21
- SubFilter: :adbe.pkcs7.detached
22
- },
17
+ sig_dict = build_sig_dict(document, reason: reason, name: name)
18
+ attach_to_acroform(document, sig_dict)
19
+
20
+ pdf = serialize(document)
21
+ pdf = insert_contents_placeholder(pdf, sig_dict.oid)
22
+ finalize_signature(pdf, cert, key)
23
+ end
24
+
25
+ def build_sig_dict(document, reason:, name:)
26
+ hash = {
27
+ Type: :Sig,
28
+ Filter: :"Adobe.PPKLite",
29
+ SubFilter: :"adbe.pkcs7.detached",
30
+ ByteRange: [0, 0, 0, 0],
31
+ }
32
+ hash[:Reason] = reason if reason
33
+ hash[:Name] = name if name
34
+ hash[:M] = "D:#{Time.now.utc.strftime("%Y%m%d%H%M%S+00'00'")}"
35
+ document.add(hash, type: Pdfrb::Model::Cos::Dictionary)
36
+ end
37
+
38
+ def attach_to_acroform(document, sig_dict)
39
+ catalog = document.catalog
40
+ catalog.value[:AcroForm] ||= {}
41
+ catalog.value[:AcroForm][:SigFlags] = 3
42
+ catalog.value[:AcroForm][:Fields] ||= []
43
+
44
+ field = document.add(
45
+ { Type: :Annot, Subtype: :Widget, FT: :Sig, T: "Signature1",
46
+ V: Pdfrb::Model::Reference.new(sig_dict.oid, sig_dict.gen),
47
+ Rect: [0, 0, 0, 0] },
23
48
  type: Pdfrb::Model::Cos::Dictionary
24
49
  )
25
- sig_dict
50
+ catalog.value[:AcroForm][:Fields] <<
51
+ Pdfrb::Model::Reference.new(field.oid, field.gen)
52
+ end
53
+
54
+ def serialize(document)
55
+ io = StringIO.new
56
+ Pdfrb::Writer.write(document, io)
57
+ io.string
58
+ end
59
+
60
+ def insert_contents_placeholder(pdf, sig_oid)
61
+ sig_pattern = "#{sig_oid} 0 obj"
62
+ sig_start = pdf.index(sig_pattern)
63
+ return pdf unless sig_start
64
+
65
+ dict_end = pdf.index(">>", sig_start)
66
+ return pdf unless dict_end
67
+
68
+ placeholder = "0" * PLACEHOLDER_HEX_LEN
69
+ contents_str = "/Contents <#{placeholder}>"
70
+
71
+ "#{pdf[0, dict_end + 2]}\n#{contents_str}#{pdf[(dict_end + 2)..]}"
72
+ end
73
+
74
+ def finalize_signature(pdf, cert, key)
75
+ marker = "/Contents <"
76
+ contents_marker = pdf.index(marker)
77
+ return pdf unless contents_marker
78
+
79
+ hex_start = contents_marker + marker.bytesize
80
+ hex_end = pdf.index(">", hex_start)
81
+ return pdf unless hex_end
82
+
83
+ part1_end = hex_start
84
+ part2_start = hex_end + 1
85
+ part2_len = pdf.bytesize - part2_start
86
+
87
+ br_old = "/ByteRange [0 0 0 0]"
88
+ br_new = "/ByteRange [0 #{part1_end} #{part2_start} #{part2_len}]"
89
+ size_diff = br_new.bytesize - br_old.bytesize
90
+
91
+ if size_diff.positive?
92
+ part1_end += size_diff
93
+ part2_start += size_diff
94
+ br_new = "/ByteRange [0 #{part1_end} #{part2_start} #{part2_len}]"
95
+ new_size_diff = br_new.bytesize - br_old.bytesize
96
+ if new_size_diff != size_diff
97
+ adjust = new_size_diff - size_diff
98
+ part1_end += adjust
99
+ part2_start += adjust
100
+ br_new = "/ByteRange [0 #{part1_end} #{part2_start} #{part2_len}]"
101
+ end
102
+ elsif size_diff.negative?
103
+ br_new = br_new.ljust(br_old.bytesize)
104
+ end
105
+
106
+ pdf = pdf.sub(br_old, br_new)
107
+
108
+ hex_start = part1_end
109
+ hex_end = pdf.index(">", hex_start)
110
+ hex_len = hex_end - hex_start
111
+
112
+ signed_data = pdf.bytes[0, part1_end].pack("C*") +
113
+ pdf.bytes[part2_start, part2_len].pack("C*")
114
+
115
+ der = OpenSSL::PKCS7.sign(cert, key, signed_data, [],
116
+ OpenSSL::PKCS7::DETACHED |
117
+ OpenSSL::PKCS7::BINARY).to_der
118
+
119
+ hex_sig = der.unpack1("H*").ljust(hex_len, "0")
120
+
121
+ pdf[0, hex_start] + hex_sig + pdf[hex_end..]
26
122
  end
27
123
  end
28
124
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module DigitalSignature
5
+ module TimestampHandler
6
+ module_function
7
+
8
+ def type; :"adbe.revision"; end
9
+
10
+ def create_timestamp(data, tsa_url: nil)
11
+ return nil unless tsa_url
12
+
13
+ require "net/http"
14
+ require "uri"
15
+
16
+ uri = URI(tsa_url)
17
+ response = Net::HTTP.start(uri.host, uri.port) do |http|
18
+ req = Net::HTTP::Post.new(uri.request_uri)
19
+ req["Content-Type"] = "application/timestamp-query"
20
+ req.body = build_tsr_request(data)
21
+ http.request(req)
22
+ end
23
+
24
+ response.body if response.is_a?(Net::HTTPSuccess)
25
+ end
26
+
27
+ def build_tsr_request(data)
28
+ require "openssl"
29
+ hash = OpenSSL::Digest.new("SHA256")
30
+ req = OpenSSL::ASN1::Sequence.new([
31
+ OpenSSL::ASN1::Integer.new(1),
32
+ OpenSSL::ASN1::Sequence.new([
33
+ OpenSSL::ASN1::Sequence.new([
34
+ OpenSSL::ASN1::ObjectId.new("2.16.840.1.101.3.4.2.1"),
35
+ OpenSSL::ASN1::OctetString.new(hash.digest(data).force_encoding("BINARY")),
36
+ ]),
37
+ ]),
38
+ OpenSSL::ASN1::Integer.new(0),
39
+ OpenSSL::ASN1::Boolean.new(false),
40
+ ])
41
+ req.to_der
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,21 +1,100 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "openssl"
4
+ require "stringio"
4
5
 
5
6
  module Pdfrb
6
7
  module DigitalSignature
7
- # PKCS#7 signature verification (s12.8.3). Extracts signature
8
- # from the document, checks byte-range integrity, and verifies
9
- # the cert chain against the system trust store.
10
8
  module Verification
11
- Result = Struct.new(:signer, :valid?, :byte_range_ok?,
12
- :cert_chain, :error, keyword_init: true)
13
-
14
9
  module_function
15
10
 
16
- def verify(document)
17
- Result.new(signer: nil, valid?: false, byte_range_ok?: nil,
18
- cert_chain: [], error: "verification stub — not yet implemented")
11
+ def verify(pdf_bytes, trusted_certs: [])
12
+ results = []
13
+ offset = 0
14
+
15
+ loop do
16
+ sig_info = find_signature(pdf_bytes, offset)
17
+ break unless sig_info
18
+
19
+ results << verify_signature(pdf_bytes, sig_info, trusted_certs)
20
+ offset = sig_info[:contents_end] + 1
21
+ end
22
+
23
+ results
24
+ end
25
+
26
+ def find_signature(pdf_bytes, start_offset)
27
+ contents_marker = "/Contents <"
28
+ ck_start = pdf_bytes.index(contents_marker, start_offset)
29
+ return nil unless ck_start
30
+
31
+ hex_start = ck_start + contents_marker.bytesize
32
+ hex_end = pdf_bytes.index(">", hex_start)
33
+ return nil unless hex_end
34
+
35
+ br_start = pdf_bytes.rindex("/ByteRange [", ck_start)
36
+ return nil unless br_start
37
+
38
+ br_str_start = br_start + "/ByteRange [".bytesize
39
+ br_end = pdf_bytes.index("]", br_str_start)
40
+ return nil unless br_end
41
+
42
+ br_str = pdf_bytes[br_str_start...br_end]
43
+ byte_range = br_str.split.map(&:to_i)
44
+
45
+ {
46
+ byte_range: byte_range,
47
+ contents_hex_start: hex_start,
48
+ contents_hex_end: hex_end,
49
+ contents_end: hex_end,
50
+ }
51
+ end
52
+
53
+ def verify_signature(pdf_bytes, sig_info, trusted_certs)
54
+ byte_range = sig_info[:byte_range]
55
+
56
+ if byte_range.nil? || byte_range.length != 4
57
+ return VerificationResult.new(valid?: false,
58
+ byte_range_ok?: false,
59
+ cert_chain: [],
60
+ error: "invalid ByteRange")
61
+ end
62
+
63
+ _start1, len1, start2, len2 = byte_range
64
+ part1 = pdf_bytes.bytes[0, len1] || []
65
+ part2 = pdf_bytes.bytes[start2, len2] || []
66
+ signed_data = part1.pack("C*") + part2.pack("C*")
67
+
68
+ hex_start = sig_info[:contents_hex_start]
69
+ hex_end = sig_info[:contents_hex_end]
70
+ contents_hex = pdf_bytes[hex_start...hex_end]
71
+ contents_hex = contents_hex.sub(/0+$/, "")
72
+
73
+ begin
74
+ der = [contents_hex].pack("H*")
75
+ pkcs7 = OpenSSL::PKCS7.new(der)
76
+
77
+ store = OpenSSL::X509::Store.new
78
+ trusted_certs.each { |c| store.add_cert(c) }
79
+
80
+ valid = pkcs7.verify(nil, store, signed_data,
81
+ OpenSSL::PKCS7::DETACHED |
82
+ OpenSSL::PKCS7::BINARY)
83
+
84
+ VerificationResult.new(
85
+ signer: pkcs7.signers.first&.issuer&.to_s,
86
+ valid?: valid,
87
+ byte_range_ok?: true,
88
+ cert_chain: pkcs7.certificates || [],
89
+ trusted?: !trusted_certs.empty? && valid,
90
+ error: valid ? nil : "signature verification failed",
91
+ )
92
+ rescue OpenSSL::PKCS7::PKCS7Error => e
93
+ VerificationResult.new(valid?: false,
94
+ byte_range_ok?: true,
95
+ cert_chain: [],
96
+ error: e.message)
97
+ end
19
98
  end
20
99
  end
21
100
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module DigitalSignature
5
+ VerificationResult = Struct.new(
6
+ :signer, :valid?, :byte_range_ok?, :cert_chain, :error,
7
+ :signed_at, :trusted?, keyword_init: true
8
+ ) do
9
+ def trusted?; self[:trusted?]; end
10
+ def has_error?; !error.nil?; end
11
+ def cert_count; cert_chain&.length || 0; end
12
+ end
13
+ end
14
+ end
@@ -1,11 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pdfrb
4
- # Digital signatures (s12.8). Stub implementation full PKCS#7
5
- # signing + verification needs the OpenSSL PKCS7 module.
4
+ # Digital signatures (s12.8). PKCS#7 detached signing + verification
5
+ # against a cert + private key pair. CMS handler is the default
6
+ # +adbe.pkcs7.detached+ subfilter.
6
7
  module DigitalSignature
7
8
  autoload :Handler, "pdfrb/digital_signature/handler"
8
9
  autoload :Signing, "pdfrb/digital_signature/signing"
9
10
  autoload :Verification, "pdfrb/digital_signature/verification"
11
+ autoload :VerificationResult, "pdfrb/digital_signature/verification_result"
12
+ autoload :CmsHandler, "pdfrb/digital_signature/cms_handler"
13
+ autoload :TimestampHandler, "pdfrb/digital_signature/timestamp_handler"
14
+
15
+ HANDLERS = {
16
+ "adbe.pkcs7.detached": CmsHandler,
17
+ "adbe.pkcs7.sha1": CmsHandler,
18
+ "adbe.revision": TimestampHandler,
19
+ }.freeze
20
+
21
+ module_function
22
+
23
+ def handler_for(sub_filter)
24
+ HANDLERS[sub_filter.to_sym]
25
+ end
26
+
27
+ def sign(document, cert:, key:, **)
28
+ Handler.sign(document, cert: cert, key: key, **)
29
+ end
30
+
31
+ def verify(document, trusted_certs: [])
32
+ Handler.verify(document, trusted_certs: trusted_certs)
33
+ end
10
34
  end
11
35
  end