pdfrb 0.3.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (422) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +51 -0
  4. data/Gemfile +1 -0
  5. data/PROPOSAL.canvas-clip.md +92 -0
  6. data/PROPOSAL.canvas-draw-image-matrix.md +87 -0
  7. data/PROPOSAL.canvas-rich-text.md +103 -0
  8. data/PROPOSAL.canvas-text-lines.md +77 -0
  9. data/PROPOSAL.canvas-transparency.md +106 -0
  10. data/PROPOSAL.external-font-metrics.md +129 -0
  11. data/PROPOSAL.font-embedding-fontfile2.md +88 -0
  12. data/PROPOSAL.font-resolver-ttc.md +105 -0
  13. data/PROPOSAL.font-resolver.md +96 -0
  14. data/PROPOSAL.font-subsetting.md +98 -0
  15. data/PROPOSAL.gradient-shadings.md +110 -0
  16. data/PROPOSAL.page-mediabox-setter.md +66 -0
  17. data/PROPOSAL.per-glyph-width.md +91 -0
  18. data/PROPOSAL.stringio-require.md +53 -0
  19. data/PROPOSAL.struct-tree-root.md +102 -0
  20. data/PROPOSAL.text-measurement.md +93 -0
  21. data/PROPOSAL.ttf-table-parsing-fix.md +112 -0
  22. data/RELEASE.md +61 -0
  23. data/data/pdfrb/afm/Courier-Bold.afm +342 -342
  24. data/data/pdfrb/afm/Courier-BoldOblique.afm +342 -342
  25. data/data/pdfrb/afm/Courier-Oblique.afm +342 -342
  26. data/data/pdfrb/afm/Courier.afm +342 -342
  27. data/data/pdfrb/afm/Helvetica-Bold.afm +2827 -2827
  28. data/data/pdfrb/afm/Helvetica-BoldOblique.afm +2827 -2827
  29. data/data/pdfrb/afm/Helvetica-Oblique.afm +3051 -3051
  30. data/data/pdfrb/afm/Helvetica.afm +3051 -3051
  31. data/data/pdfrb/afm/Symbol.afm +213 -213
  32. data/data/pdfrb/afm/Times-Bold.afm +2588 -2588
  33. data/data/pdfrb/afm/Times-BoldItalic.afm +2384 -2384
  34. data/data/pdfrb/afm/Times-Italic.afm +2667 -2667
  35. data/data/pdfrb/afm/Times-Roman.afm +2419 -2419
  36. data/data/pdfrb/afm/ZapfDingbats.afm +225 -225
  37. data/lib/pdfrb/action/base.rb +21 -0
  38. data/lib/pdfrb/action/types.rb +178 -0
  39. data/lib/pdfrb/action.rb +55 -0
  40. data/lib/pdfrb/annotation/base.rb +79 -0
  41. data/lib/pdfrb/annotation/types.rb +222 -0
  42. data/lib/pdfrb/annotation.rb +78 -0
  43. data/lib/pdfrb/appearance/generator.rb +216 -0
  44. data/lib/pdfrb/appearance.rb +11 -0
  45. data/lib/pdfrb/arlington/object_definition.rb +2 -2
  46. data/lib/pdfrb/arlington/predicate/evaluator.rb +1 -1
  47. data/lib/pdfrb/arlington/predicate/functions.rb +25 -9
  48. data/lib/pdfrb/arlington/predicate.rb +1 -5
  49. data/lib/pdfrb/cli/batch.rb +18 -0
  50. data/lib/pdfrb/cli/debug_info.rb +18 -0
  51. data/lib/pdfrb/cli/files.rb +16 -0
  52. data/lib/pdfrb/cli/fonts.rb +16 -0
  53. data/lib/pdfrb/cli/form.rb +18 -0
  54. data/lib/pdfrb/cli/image2pdf.rb +18 -0
  55. data/lib/pdfrb/cli/images.rb +34 -0
  56. data/lib/pdfrb/cli/info.rb +20 -0
  57. data/lib/pdfrb/cli/inspect.rb +18 -0
  58. data/lib/pdfrb/cli/merge.rb +18 -0
  59. data/lib/pdfrb/cli/modify.rb +18 -0
  60. data/lib/pdfrb/cli/optimize.rb +18 -0
  61. data/lib/pdfrb/cli/split.rb +21 -0
  62. data/lib/pdfrb/cli/watermark.rb +18 -0
  63. data/lib/pdfrb/cli.rb +291 -7
  64. data/lib/pdfrb/color/color_space.rb +259 -0
  65. data/lib/pdfrb/color/icc_profile.rb +90 -0
  66. data/lib/pdfrb/color.rb +12 -0
  67. data/lib/pdfrb/compare/comparator.rb +244 -0
  68. data/lib/pdfrb/compare/report.rb +78 -0
  69. data/lib/pdfrb/compare.rb +42 -0
  70. data/lib/pdfrb/composer.rb +161 -0
  71. data/lib/pdfrb/conformance/pdf_a.rb +317 -56
  72. data/lib/pdfrb/conformance/pdf_ua.rb +186 -29
  73. data/lib/pdfrb/conformance/pdf_x.rb +247 -0
  74. data/lib/pdfrb/conformance/rule.rb +78 -0
  75. data/lib/pdfrb/conformance/structure_elements.rb +174 -0
  76. data/lib/pdfrb/conformance/verapdf_bridge.rb +92 -0
  77. data/lib/pdfrb/conformance.rb +7 -3
  78. data/lib/pdfrb/content/canvas.rb +354 -72
  79. data/lib/pdfrb/content/color_space.rb +157 -0
  80. data/lib/pdfrb/content/hidden_text_detector.rb +167 -0
  81. data/lib/pdfrb/content/operator.rb +21 -1
  82. data/lib/pdfrb/content/operators/clipping.rb +36 -0
  83. data/lib/pdfrb/content/operators/graphics_state_params.rb +15 -0
  84. data/lib/pdfrb/content/operators/inline_image.rb +53 -0
  85. data/lib/pdfrb/content/operators.rb +22 -0
  86. data/lib/pdfrb/content/shading/base.rb +139 -0
  87. data/lib/pdfrb/content/shading.rb +17 -0
  88. data/lib/pdfrb/content/smart_text_extractor.rb +85 -0
  89. data/lib/pdfrb/content/tiling_pattern.rb +78 -0
  90. data/lib/pdfrb/content/transformation_matrix.rb +61 -0
  91. data/lib/pdfrb/content.rb +7 -26
  92. data/lib/pdfrb/destination/fit.rb +185 -0
  93. data/lib/pdfrb/destination.rb +13 -0
  94. data/lib/pdfrb/digital_signature/cms_handler.rb +22 -0
  95. data/lib/pdfrb/digital_signature/signing.rb +107 -11
  96. data/lib/pdfrb/digital_signature/timestamp_handler.rb +45 -0
  97. data/lib/pdfrb/digital_signature/verification.rb +88 -9
  98. data/lib/pdfrb/digital_signature/verification_result.rb +14 -0
  99. data/lib/pdfrb/digital_signature.rb +26 -2
  100. data/lib/pdfrb/document/annotations.rb +120 -3
  101. data/lib/pdfrb/document/associated_files.rb +104 -0
  102. data/lib/pdfrb/document/colors.rb +95 -0
  103. data/lib/pdfrb/document/destinations.rb +127 -5
  104. data/lib/pdfrb/document/display.rb +84 -0
  105. data/lib/pdfrb/document/fonts.rb +417 -45
  106. data/lib/pdfrb/document/form.rb +338 -0
  107. data/lib/pdfrb/document/graphics_state.rb +91 -0
  108. data/lib/pdfrb/document/images.rb +4 -4
  109. data/lib/pdfrb/document/info.rb +102 -0
  110. data/lib/pdfrb/document/layers.rb +62 -0
  111. data/lib/pdfrb/document/metadata.rb +12 -5
  112. data/lib/pdfrb/document/output_intents.rb +91 -0
  113. data/lib/pdfrb/document/page_labels.rb +80 -0
  114. data/lib/pdfrb/document/pages.rb +68 -19
  115. data/lib/pdfrb/document/portfolio.rb +119 -0
  116. data/lib/pdfrb/document/shadings.rb +159 -0
  117. data/lib/pdfrb/document/stamps.rb +68 -0
  118. data/lib/pdfrb/document/structure.rb +242 -0
  119. data/lib/pdfrb/document.rb +73 -4
  120. data/lib/pdfrb/encryption/identity.rb +13 -0
  121. data/lib/pdfrb/encryption/password_verification.rb +53 -8
  122. data/lib/pdfrb/encryption/security_handler.rb +8 -5
  123. data/lib/pdfrb/encryption/standard_security_handler.rb +154 -81
  124. data/lib/pdfrb/encryption.rb +17 -0
  125. data/lib/pdfrb/error.rb +2 -2
  126. data/lib/pdfrb/filter/brotli_decode.rb +32 -0
  127. data/lib/pdfrb/filter/ccitt_fax_decode.rb +12 -0
  128. data/lib/pdfrb/filter/crypt.rb +2 -2
  129. data/lib/pdfrb/filter/dct_decode.rb +18 -0
  130. data/lib/pdfrb/filter/flate_decode.rb +4 -18
  131. data/lib/pdfrb/filter/jbig2_decode.rb +12 -0
  132. data/lib/pdfrb/filter/jpx_decode.rb +18 -0
  133. data/lib/pdfrb/filter/lzw_decode.rb +3 -2
  134. data/lib/pdfrb/filter/png_predictor.rb +94 -0
  135. data/lib/pdfrb/filter.rb +26 -13
  136. data/lib/pdfrb/font/afm_parser.rb +50 -26
  137. data/lib/pdfrb/font/cmap/writer.rb +9 -0
  138. data/lib/pdfrb/font/encoding/win_ansi_encoding.rb +17 -0
  139. data/lib/pdfrb/font/encoding.rb +32 -3
  140. data/lib/pdfrb/font/metrics_helper.rb +121 -0
  141. data/lib/pdfrb/font/true_type/file.rb +10 -1
  142. data/lib/pdfrb/font/true_type/glyf.rb +82 -0
  143. data/lib/pdfrb/font/true_type/kern.rb +58 -0
  144. data/lib/pdfrb/font/true_type/loca.rb +42 -0
  145. data/lib/pdfrb/font/true_type/maxp.rb +44 -0
  146. data/lib/pdfrb/font/true_type/name.rb +53 -0
  147. data/lib/pdfrb/font/true_type/post.rb +93 -0
  148. data/lib/pdfrb/font/true_type/subsetter.rb +3 -5
  149. data/lib/pdfrb/font/true_type/wrapper.rb +102 -0
  150. data/lib/pdfrb/font/true_type.rb +7 -0
  151. data/lib/pdfrb/font.rb +2 -0
  152. data/lib/pdfrb/font_loader/from_configuration.rb +19 -0
  153. data/lib/pdfrb/font_loader/from_file.rb +20 -0
  154. data/lib/pdfrb/font_loader/standard14.rb +20 -0
  155. data/lib/pdfrb/font_loader/variant_from_name.rb +36 -0
  156. data/lib/pdfrb/font_loader.rb +4 -0
  157. data/lib/pdfrb/font_resolver.rb +138 -0
  158. data/lib/pdfrb/image/audit.rb +94 -0
  159. data/lib/pdfrb/image/downsampler.rb +124 -0
  160. data/lib/pdfrb/image.rb +13 -0
  161. data/lib/pdfrb/image_loader/jpeg.rb +39 -58
  162. data/lib/pdfrb/image_loader/png.rb +82 -251
  163. data/lib/pdfrb/layout/bidi.rb +198 -0
  164. data/lib/pdfrb/layout/box.rb +49 -0
  165. data/lib/pdfrb/layout/box_fitter.rb +54 -0
  166. data/lib/pdfrb/layout/column_box.rb +71 -0
  167. data/lib/pdfrb/layout/container_box.rb +45 -0
  168. data/lib/pdfrb/layout/frame.rb +67 -0
  169. data/lib/pdfrb/layout/image_box.rb +44 -0
  170. data/lib/pdfrb/layout/inline_box.rb +25 -0
  171. data/lib/pdfrb/layout/line.rb +55 -0
  172. data/lib/pdfrb/layout/list_box.rb +87 -0
  173. data/lib/pdfrb/layout/numeric_refinements.rb +43 -0
  174. data/lib/pdfrb/layout/page_style.rb +65 -0
  175. data/lib/pdfrb/layout/style.rb +62 -0
  176. data/lib/pdfrb/layout/table_box.rb +66 -0
  177. data/lib/pdfrb/layout/text_box.rb +38 -0
  178. data/lib/pdfrb/layout/text_fragment.rb +45 -0
  179. data/lib/pdfrb/layout/text_layouter.rb +123 -0
  180. data/lib/pdfrb/layout.rb +28 -0
  181. data/lib/pdfrb/linearization/hint_stream.rb +93 -0
  182. data/lib/pdfrb/linearization/writer.rb +259 -0
  183. data/lib/pdfrb/linearization.rb +21 -0
  184. data/lib/pdfrb/model/cos/dictionary.rb +12 -0
  185. data/lib/pdfrb/model/pdf_array.rb +2 -2
  186. data/lib/pdfrb/model/type/action.rb +42 -0
  187. data/lib/pdfrb/model/type/action_go_to_3d_view.rb +14 -0
  188. data/lib/pdfrb/model/type/action_go_to_dp.rb +16 -0
  189. data/lib/pdfrb/model/type/action_go_to_r.rb +26 -0
  190. data/lib/pdfrb/model/type/action_goto.rb +34 -0
  191. data/lib/pdfrb/model/type/action_hide.rb +26 -0
  192. data/lib/pdfrb/model/type/action_import_data.rb +18 -0
  193. data/lib/pdfrb/model/type/action_java_script.rb +18 -0
  194. data/lib/pdfrb/model/type/action_launch.rb +22 -0
  195. data/lib/pdfrb/model/type/action_movie.rb +22 -0
  196. data/lib/pdfrb/model/type/action_named.rb +23 -0
  197. data/lib/pdfrb/model/type/action_nop.rb +12 -0
  198. data/lib/pdfrb/model/type/action_rendition.rb +28 -0
  199. data/lib/pdfrb/model/type/action_reset_form.rb +22 -0
  200. data/lib/pdfrb/model/type/action_rich_media_execute.rb +17 -0
  201. data/lib/pdfrb/model/type/action_set_ocg_state.rb +32 -0
  202. data/lib/pdfrb/model/type/action_set_state.rb +14 -0
  203. data/lib/pdfrb/model/type/action_sound_action.rb +23 -0
  204. data/lib/pdfrb/model/type/action_submit_form.rb +64 -0
  205. data/lib/pdfrb/model/type/action_thread.rb +23 -0
  206. data/lib/pdfrb/model/type/action_trans.rb +30 -0
  207. data/lib/pdfrb/model/type/action_uri.rb +30 -0
  208. data/lib/pdfrb/model/type/af_embedded_file.rb +11 -0
  209. data/lib/pdfrb/model/type/af_file_specification.rb +34 -0
  210. data/lib/pdfrb/model/type/annotation.rb +49 -10
  211. data/lib/pdfrb/model/type/appearance.rb +41 -0
  212. data/lib/pdfrb/model/type/appearance_characteristics.rb +36 -0
  213. data/lib/pdfrb/model/type/appearance_generator.rb +22 -0
  214. data/lib/pdfrb/model/type/appearance_trap_net.rb +84 -0
  215. data/lib/pdfrb/model/type/border_effect.rb +25 -0
  216. data/lib/pdfrb/model/type/border_styling.rb +38 -0
  217. data/lib/pdfrb/model/type/box_color_info.rb +21 -0
  218. data/lib/pdfrb/model/type/box_style.rb +19 -0
  219. data/lib/pdfrb/model/type/button.rb +17 -0
  220. data/lib/pdfrb/model/type/cal_gray.rb +17 -0
  221. data/lib/pdfrb/model/type/cal_rgb.rb +22 -0
  222. data/lib/pdfrb/model/type/caret_annotation.rb +21 -0
  223. data/lib/pdfrb/model/type/catalog.rb +99 -16
  224. data/lib/pdfrb/model/type/choice.rb +21 -0
  225. data/lib/pdfrb/model/type/cid_font.rb +52 -1
  226. data/lib/pdfrb/model/type/cid_font_descriptor_metrics.rb +28 -0
  227. data/lib/pdfrb/model/type/cid_system_info.rb +28 -0
  228. data/lib/pdfrb/model/type/circle_annotation.rb +20 -0
  229. data/lib/pdfrb/model/type/collection.rb +27 -0
  230. data/lib/pdfrb/model/type/collection_field.rb +24 -0
  231. data/lib/pdfrb/model/type/collection_item.rb +19 -0
  232. data/lib/pdfrb/model/type/collection_schema.rb +25 -0
  233. data/lib/pdfrb/model/type/collection_sort.rb +13 -0
  234. data/lib/pdfrb/model/type/crypt_filter.rb +58 -0
  235. data/lib/pdfrb/model/type/device_n.rb +37 -0
  236. data/lib/pdfrb/model/type/device_n_mixing_hints.rb +17 -0
  237. data/lib/pdfrb/model/type/device_n_process.rb +13 -0
  238. data/lib/pdfrb/model/type/doc_mdp_transform_parameters.rb +21 -0
  239. data/lib/pdfrb/model/type/document_security_store.rb +55 -0
  240. data/lib/pdfrb/model/type/embedded_file.rb +19 -0
  241. data/lib/pdfrb/model/type/embedded_file_parameter.rb +48 -0
  242. data/lib/pdfrb/model/type/encryption_public_key.rb +38 -0
  243. data/lib/pdfrb/model/type/encryption_standard.rb +68 -0
  244. data/lib/pdfrb/model/type/field_mdp_transform_parameters.rb +26 -0
  245. data/lib/pdfrb/model/type/file_attachment_annotation.rb +29 -0
  246. data/lib/pdfrb/model/type/file_spec_ef.rb +22 -0
  247. data/lib/pdfrb/model/type/file_spec_rf.rb +21 -0
  248. data/lib/pdfrb/model/type/file_specification.rb +43 -0
  249. data/lib/pdfrb/model/type/file_trailer.rb +54 -0
  250. data/lib/pdfrb/model/type/font.rb +73 -2
  251. data/lib/pdfrb/model/type/font_cid_type0.rb +17 -0
  252. data/lib/pdfrb/model/type/font_cid_type2.rb +17 -0
  253. data/lib/pdfrb/model/type/font_descriptor.rb +47 -0
  254. data/lib/pdfrb/model/type/font_descriptor_cid_type0.rb +15 -0
  255. data/lib/pdfrb/model/type/font_descriptor_cid_type2.rb +15 -0
  256. data/lib/pdfrb/model/type/font_encoding.rb +47 -0
  257. data/lib/pdfrb/model/type/font_file.rb +28 -0
  258. data/lib/pdfrb/model/type/font_file2.rb +32 -0
  259. data/lib/pdfrb/model/type/font_file3.rb +23 -0
  260. data/lib/pdfrb/model/type/font_multiple_master.rb +60 -0
  261. data/lib/pdfrb/model/type/font_true_type.rb +1 -1
  262. data/lib/pdfrb/model/type/font_type0.rb +1 -1
  263. data/lib/pdfrb/model/type/font_type1.rb +1 -1
  264. data/lib/pdfrb/model/type/font_type3.rb +1 -1
  265. data/lib/pdfrb/model/type/form_field.rb +73 -0
  266. data/lib/pdfrb/model/type/free_text_annotation.rb +37 -0
  267. data/lib/pdfrb/model/type/function.rb +42 -0
  268. data/lib/pdfrb/model/type/function_exponential.rb +31 -0
  269. data/lib/pdfrb/model/type/function_postscript.rb +25 -0
  270. data/lib/pdfrb/model/type/function_sampled.rb +55 -0
  271. data/lib/pdfrb/model/type/function_stitching.rb +33 -0
  272. data/lib/pdfrb/model/type/generic_appearance.rb +40 -0
  273. data/lib/pdfrb/model/type/graphics_state_parameter.rb +69 -0
  274. data/lib/pdfrb/model/type/halftone.rb +21 -0
  275. data/lib/pdfrb/model/type/halftone_type1.rb +17 -0
  276. data/lib/pdfrb/model/type/halftone_type10.rb +16 -0
  277. data/lib/pdfrb/model/type/halftone_type16.rb +11 -0
  278. data/lib/pdfrb/model/type/halftone_type5.rb +24 -0
  279. data/lib/pdfrb/model/type/halftone_type6.rb +16 -0
  280. data/lib/pdfrb/model/type/highlight_annotation.rb +11 -0
  281. data/lib/pdfrb/model/type/icc_based_color_space.rb +24 -0
  282. data/lib/pdfrb/model/type/icon_fit.rb +31 -0
  283. data/lib/pdfrb/model/type/indexed.rb +32 -0
  284. data/lib/pdfrb/model/type/info.rb +44 -0
  285. data/lib/pdfrb/model/type/ink_annotation.rb +29 -0
  286. data/lib/pdfrb/model/type/interactive_form.rb +36 -0
  287. data/lib/pdfrb/model/type/interior_color.rb +12 -0
  288. data/lib/pdfrb/model/type/jbig2_globals.rb +15 -0
  289. data/lib/pdfrb/model/type/lab.rb +27 -0
  290. data/lib/pdfrb/model/type/line_annotation.rb +44 -0
  291. data/lib/pdfrb/model/type/line_ending_styling.rb +39 -0
  292. data/lib/pdfrb/model/type/link_annotation.rb +41 -0
  293. data/lib/pdfrb/model/type/link_appearance.rb +9 -0
  294. data/lib/pdfrb/model/type/mark_information.rb +23 -0
  295. data/lib/pdfrb/model/type/marked_content_reference.rb +24 -0
  296. data/lib/pdfrb/model/type/markup_annotation.rb +36 -0
  297. data/lib/pdfrb/model/type/mdp_dict.rb +28 -0
  298. data/lib/pdfrb/model/type/measure.rb +39 -0
  299. data/lib/pdfrb/model/type/media_clip.rb +28 -0
  300. data/lib/pdfrb/model/type/media_offset.rb +106 -0
  301. data/lib/pdfrb/model/type/metadata.rb +27 -3
  302. data/lib/pdfrb/model/type/misc_helpers.rb +75 -0
  303. data/lib/pdfrb/model/type/movie.rb +28 -0
  304. data/lib/pdfrb/model/type/names.rb +51 -0
  305. data/lib/pdfrb/model/type/namespace.rb +27 -0
  306. data/lib/pdfrb/model/type/object_reference.rb +31 -0
  307. data/lib/pdfrb/model/type/object_stream.rb +38 -0
  308. data/lib/pdfrb/model/type/opt_content_ext.rb +94 -0
  309. data/lib/pdfrb/model/type/opt_content_page_element.rb +18 -0
  310. data/lib/pdfrb/model/type/optional_content_config.rb +56 -0
  311. data/lib/pdfrb/model/type/optional_content_group.rb +35 -10
  312. data/lib/pdfrb/model/type/optional_content_membership.rb +36 -2
  313. data/lib/pdfrb/model/type/optional_content_properties.rb +16 -1
  314. data/lib/pdfrb/model/type/outline.rb +40 -6
  315. data/lib/pdfrb/model/type/outline_item.rb +45 -0
  316. data/lib/pdfrb/model/type/output_intent.rb +30 -0
  317. data/lib/pdfrb/model/type/page.rb +127 -0
  318. data/lib/pdfrb/model/type/page_label.rb +68 -0
  319. data/lib/pdfrb/model/type/page_piece_info.rb +58 -0
  320. data/lib/pdfrb/model/type/page_tree_node.rb +51 -16
  321. data/lib/pdfrb/model/type/page_tree_node_root.rb +15 -0
  322. data/lib/pdfrb/model/type/pattern.rb +16 -0
  323. data/lib/pdfrb/model/type/pattern_shading.rb +25 -0
  324. data/lib/pdfrb/model/type/pattern_tiling.rb +25 -0
  325. data/lib/pdfrb/model/type/polygon_annotation.rb +29 -0
  326. data/lib/pdfrb/model/type/polygon_polyline.rb +14 -0
  327. data/lib/pdfrb/model/type/polyline_annotation.rb +29 -0
  328. data/lib/pdfrb/model/type/popup_annotation.rb +20 -0
  329. data/lib/pdfrb/model/type/printer_mark_annotation.rb +13 -0
  330. data/lib/pdfrb/model/type/redact_annotation.rb +28 -0
  331. data/lib/pdfrb/model/type/rendition.rb +31 -0
  332. data/lib/pdfrb/model/type/resources.rb +59 -14
  333. data/lib/pdfrb/model/type/rich_media_activation.rb +24 -0
  334. data/lib/pdfrb/model/type/rich_media_animation.rb +25 -0
  335. data/lib/pdfrb/model/type/rich_media_annotation.rb +18 -0
  336. data/lib/pdfrb/model/type/rich_media_command.rb +19 -0
  337. data/lib/pdfrb/model/type/rich_media_configuration.rb +32 -0
  338. data/lib/pdfrb/model/type/rich_media_content.rb +28 -0
  339. data/lib/pdfrb/model/type/rich_media_cue_point.rb +18 -0
  340. data/lib/pdfrb/model/type/rich_media_deactivation.rb +17 -0
  341. data/lib/pdfrb/model/type/rich_media_instance.rb +33 -0
  342. data/lib/pdfrb/model/type/rich_media_presentation.rb +29 -0
  343. data/lib/pdfrb/model/type/rich_media_settings.rb +33 -0
  344. data/lib/pdfrb/model/type/rich_media_window.rb +20 -0
  345. data/lib/pdfrb/model/type/screen_annotation.rb +19 -0
  346. data/lib/pdfrb/model/type/separation.rb +25 -0
  347. data/lib/pdfrb/model/type/sig_field_lock.rb +25 -0
  348. data/lib/pdfrb/model/type/sig_field_seed_value.rb +25 -0
  349. data/lib/pdfrb/model/type/signature.rb +65 -0
  350. data/lib/pdfrb/model/type/signature_build.rb +86 -0
  351. data/lib/pdfrb/model/type/signature_field.rb +17 -0
  352. data/lib/pdfrb/model/type/software_identifier.rb +19 -0
  353. data/lib/pdfrb/model/type/sound.rb +38 -0
  354. data/lib/pdfrb/model/type/sound_annotation.rb +21 -0
  355. data/lib/pdfrb/model/type/square_annotation.rb +24 -0
  356. data/lib/pdfrb/model/type/square_circle.rb +13 -0
  357. data/lib/pdfrb/model/type/squiggly_annotation.rb +11 -0
  358. data/lib/pdfrb/model/type/stamp_annotation.rb +19 -0
  359. data/lib/pdfrb/model/type/strikeout_annotation.rb +11 -0
  360. data/lib/pdfrb/model/type/struct_elem.rb +75 -0
  361. data/lib/pdfrb/model/type/struct_tree_root.rb +34 -0
  362. data/lib/pdfrb/model/type/structure_attributes.rb +44 -0
  363. data/lib/pdfrb/model/type/structure_element_kid.rb +33 -0
  364. data/lib/pdfrb/model/type/text_annotation.rb +25 -0
  365. data/lib/pdfrb/model/type/text_appearance.rb +9 -0
  366. data/lib/pdfrb/model/type/text_field.rb +21 -0
  367. data/lib/pdfrb/model/type/text_markup_annotation.rb +30 -0
  368. data/lib/pdfrb/model/type/three_d_activation.rb +32 -0
  369. data/lib/pdfrb/model/type/three_d_animation_style.rb +24 -0
  370. data/lib/pdfrb/model/type/three_d_background.rb +28 -0
  371. data/lib/pdfrb/model/type/three_d_cross_section.rb +25 -0
  372. data/lib/pdfrb/model/type/three_d_lighting_scheme.rb +26 -0
  373. data/lib/pdfrb/model/type/three_d_measure.rb +20 -0
  374. data/lib/pdfrb/model/type/three_d_measure_3dc.rb +23 -0
  375. data/lib/pdfrb/model/type/three_d_measure_ad3.rb +14 -0
  376. data/lib/pdfrb/model/type/three_d_measure_ld3.rb +14 -0
  377. data/lib/pdfrb/model/type/three_d_measure_pd3.rb +20 -0
  378. data/lib/pdfrb/model/type/three_d_measure_rd3.rb +14 -0
  379. data/lib/pdfrb/model/type/three_d_node.rb +37 -0
  380. data/lib/pdfrb/model/type/three_d_reference.rb +21 -0
  381. data/lib/pdfrb/model/type/three_d_render_mode.rb +34 -0
  382. data/lib/pdfrb/model/type/three_d_stream.rb +48 -0
  383. data/lib/pdfrb/model/type/three_d_units.rb +18 -0
  384. data/lib/pdfrb/model/type/three_d_view.rb +40 -0
  385. data/lib/pdfrb/model/type/to_unicode_cmap_stream.rb +23 -0
  386. data/lib/pdfrb/model/type/transition.rb +68 -0
  387. data/lib/pdfrb/model/type/underline_annotation.rb +11 -0
  388. data/lib/pdfrb/model/type/ur_transform_parameters.rb +33 -0
  389. data/lib/pdfrb/model/type/uri_dict.rb +18 -0
  390. data/lib/pdfrb/model/type/url_alias.rb +19 -0
  391. data/lib/pdfrb/model/type/variable_text_field.rb +47 -0
  392. data/lib/pdfrb/model/type/viewer_preferences.rb +42 -0
  393. data/lib/pdfrb/model/type/watermark_annotation.rb +13 -0
  394. data/lib/pdfrb/model/type/widget_annotation.rb +48 -0
  395. data/lib/pdfrb/model/type/widget_appearance.rb +9 -0
  396. data/lib/pdfrb/model/type/xobject_form.rb +41 -0
  397. data/lib/pdfrb/model/type/xobject_image.rb +56 -0
  398. data/lib/pdfrb/model/type/xref_stream.rb +37 -0
  399. data/lib/pdfrb/model/type.rb +289 -6
  400. data/lib/pdfrb/serializer.rb +2 -2
  401. data/lib/pdfrb/source/tokenizer.rb +0 -2
  402. data/lib/pdfrb/task/benchmark.rb +83 -0
  403. data/lib/pdfrb/task/extract_text.rb +1 -1
  404. data/lib/pdfrb/task/generate_corpus.rb +130 -0
  405. data/lib/pdfrb/task/memory_profile.rb +84 -0
  406. data/lib/pdfrb/task/optimize.rb +97 -7
  407. data/lib/pdfrb/task/regenerate_appearances.rb +98 -0
  408. data/lib/pdfrb/task.rb +4 -3
  409. data/lib/pdfrb/test_utils.rb +112 -0
  410. data/lib/pdfrb/validator.rb +82 -0
  411. data/lib/pdfrb/version.rb +1 -1
  412. data/lib/pdfrb/writer.rb +117 -27
  413. data/lib/pdfrb/xmp/packet.rb +122 -0
  414. data/lib/pdfrb/xmp/schemas.rb +96 -0
  415. data/lib/pdfrb/xmp.rb +44 -0
  416. data/lib/pdfrb/xref_section.rb +2 -2
  417. data/lib/pdfrb.rb +24 -54
  418. data/pdfrb.gemspec +45 -0
  419. data/script/apply_fixes.sh +843 -0
  420. data/script/make_jpeg_fixture.rb +53 -0
  421. data/script/make_png_fixture.rb +25 -0
  422. metadata +319 -4
@@ -1,116 +1,189 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "digest/md5"
3
+ require "openssl"
4
4
 
5
5
  module Pdfrb
6
6
  module Encryption
7
- # Standard security handler (s7.6.3). V1..V6 / R2..R6.
8
- # Owns per-document key derivation + per-object (de)cryption.
9
- #
10
- # Supports:
11
- # V1, V2 + R2 : RC4 40-bit and 128-bit (PDF 1.4-1.5).
12
- # V4 + R4 : RC4 or AES-128 with crypt filters (PDF 1.5+).
13
- # V5 + R5 : AES-256 (PDF 1.7 Ext. 3).
14
- # V6 + R6 : AES-256 (PDF 2.0).
15
- #
16
- # AES uses the openssl stdlib. RC4 is pure Ruby (TODO 119).
17
- class StandardSecurityHandler < SecurityHandler
18
- register "Standard", self
19
-
20
- attr_reader :file_key, :version, :revision
21
-
22
- def set_up_decryption(password: "")
23
- @encrypt_dict = read_encrypt_dict
24
- return unless @encrypt_dict
25
-
26
- @version = @encrypt_dict[:V].to_i
27
- @revision = @encrypt_dict[:R].to_i
28
- id0 = document.trailer[:ID].to_a.first.b
29
- unless verify_password(password)
30
- raise Pdfrb::EncryptionError, "incorrect password"
7
+ class StandardSecurityHandler
8
+ PAD = String.new("\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08" \
9
+ "\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A",
10
+ encoding: Encoding::BINARY).freeze
11
+
12
+ attr_reader :key, :key_length, :version, :revision, :permissions
13
+
14
+ def initialize(trailer_dict)
15
+ @encrypt = trailer_dict[:Encrypt]
16
+ @id = trailer_dict[:ID]
17
+ @version = (@encrypt[:V] || 0).to_i
18
+ @revision = (@encrypt[:R] || 3).to_i
19
+ @key_length = (@encrypt[:Length] || 40).to_i / 8
20
+ @permissions = (@encrypt[:P] || -1).to_i
21
+ @key = nil
22
+ end
23
+
24
+ def verify_user_password?(password)
25
+ computed = compute_encryption_key(password)
26
+ @key = computed
27
+ verify_user_password_hash?(computed)
28
+ end
29
+
30
+ def compute_encryption_key(password)
31
+ padded = pad_password(password)
32
+ owner_hash = @encrypt[:O] || String.new("", encoding: Encoding::BINARY)
33
+ perms = [@permissions].pack("V")
34
+ id_first = id_bytes
35
+
36
+ md5 = Digest::MD5.new
37
+ md5.update(padded)
38
+ md5.update(owner_hash)
39
+ md5.update(perms)
40
+ md5.update(id_first)
41
+
42
+ if @revision >= 4
43
+ md5.update(@encrypt[:UE] || "")
44
+ md5.update(@encrypt[:Perms] ? [@encrypt[:Perms]].pack("N") : "")
45
+ end
46
+
47
+ hash = md5.digest
48
+
49
+ if @revision >= 3
50
+ 50.times { hash = Digest::MD5.digest(hash[0, @key_length]) }
31
51
  end
32
- derive_file_key(password, id0)
52
+
53
+ hash[0, @key_length]
33
54
  end
34
55
 
35
- # Verify the user (or owner) password. Returns true on match.
36
- def verify_password(password)
37
- PasswordVerification.verify_user_password(
38
- password: password,
39
- encrypt_dict: @encrypt_dict,
40
- id0: document.trailer[:ID].to_a.first.b
41
- )
56
+ def compute_object_key(oid, gen)
57
+ md5 = Digest::MD5.new
58
+ md5.update(@key)
59
+ md5.update([oid].pack("V"))
60
+ md5.update([gen].pack("v"))
61
+
62
+ if @version >= 4
63
+ md5.update("\xAA")
64
+ end
65
+
66
+ md5.digest[0, [@key_length + 5, 16].min]
42
67
  end
43
68
 
44
- def decrypt(bytes, oid, gen)
45
- return bytes if @file_key.nil?
69
+ def encrypt_data(data, oid, gen)
70
+ return data unless @key
46
71
 
47
- case cipher_type
48
- when :rc4 then RC4.new(object_key(oid, gen, 16)).process(bytes)
49
- when :aes then AES.new(key_size: aes_key_bits, mode: :CBC).decrypt(bytes, object_key(oid, gen, aes_key_bits / 8 + 5).byteslice(0, aes_key_bits / 8))
50
- else bytes
72
+ obj_key = compute_object_key(oid, gen)
73
+
74
+ if @version >= 4
75
+ encrypt_aes_cbc(data, obj_key)
76
+ else
77
+ encrypt_rc4(data, obj_key)
51
78
  end
52
79
  end
53
80
 
54
- def encrypt(bytes, oid, gen)
55
- return bytes if @file_key.nil?
81
+ # Serializer-compatible alias for encrypt_data. The Serializer
82
+ # calls encrypter.encrypt(payload, oid, gen) on string/stream
83
+ # payloads during serialization.
84
+ alias encrypt encrypt_data
85
+
86
+ def decrypt_data(data, oid, gen)
87
+ return data unless @key
56
88
 
57
- case cipher_type
58
- when :rc4 then RC4.new(object_key(oid, gen, 16)).process(bytes)
59
- when :aes then AES.new(key_size: aes_key_bits, mode: :CBC).encrypt(bytes, object_key(oid, gen, aes_key_bits / 8 + 5).byteslice(0, aes_key_bits / 8), random_iv)
60
- else bytes
89
+ obj_key = compute_object_key(oid, gen)
90
+
91
+ if @version >= 4
92
+ decrypt_aes_cbc(data, obj_key)
93
+ else
94
+ decrypt_rc4(data, obj_key)
61
95
  end
62
96
  end
63
97
 
64
98
  private
65
99
 
66
- def read_encrypt_dict
67
- ref = document.trailer[:Encrypt]
68
- return nil unless ref
100
+ def pad_password(password)
101
+ pw = password.to_s.encode(Encoding::BINARY)[0, 32]
102
+ pw + PAD[pw.bytesize, 32 - pw.bytesize]
103
+ end
104
+
105
+ def id_bytes
106
+ return String.new("", encoding: Encoding::BINARY) unless @id
69
107
 
70
- encrypt = ref.is_a?(Pdfrb::Model::Reference) ?
71
- document.object(ref) : ref
72
- encrypt.is_a?(Pdfrb::Model::Cos::Dictionary) ? encrypt : nil
108
+ first = @id.is_a?(Array) ? @id[0] : @id
109
+ first.to_s.encode(Encoding::BINARY)
73
110
  end
74
111
 
75
- def derive_file_key(password, id0)
76
- @file_key = PasswordVerification.derive_key_rc4(
77
- password: password,
78
- o_entry: @encrypt_dict[:O].to_s.b,
79
- p_flags: @encrypt_dict[:P].to_i,
80
- id0: id0,
81
- revision: @revision,
82
- key_length_bits: (@encrypt_dict[:Length] || 40).to_i,
83
- encrypt_metadata: @encrypt_dict.fetch(:EncryptMetadata, true)
84
- )
112
+ def verify_user_password_hash?(key)
113
+ return true if @revision < 3
114
+
115
+ hash = compute_user_password_hash(key)
116
+ stored = @encrypt[:U] || ""
117
+ hash[0, 16] == stored[0, 16]
85
118
  end
86
119
 
87
- def cipher_type
88
- return :rc4 if @version <= 2
89
- return :aes if @version >= 4 && @version <= 5
120
+ def compute_user_password_hash(key)
121
+ md5 = Digest::MD5.new
122
+ md5.update(PAD)
123
+ md5.update(id_bytes)
124
+ hash = md5.digest
90
125
 
91
- # V6 (PDF 2.0) — AES-256 always.
92
- :aes
126
+ rc4 = RC4Impl.new(key)
127
+ 20.times { |i| hash = rc4.process(hash, key ^ i) }
128
+
129
+ hash[0, 16]
93
130
  end
94
131
 
95
- def aes_key_bits
96
- @version >= 5 ? 256 : 128
132
+ def encrypt_rc4(data, key)
133
+ RC4Impl.new(key).process(data)
97
134
  end
98
135
 
99
- # Per-object key per Algorithm 1: MD5(file_key + oid(LE) + gen(LE) + (sAlT for AES)).
100
- def object_key(oid, gen, output_len)
101
- md5 = Digest::MD5.new
102
- md5.update(@file_key)
103
- md5.update([oid].pack("V"))
104
- md5.update([gen].pack("V"))
105
- md5.update("sAlT".b) if cipher_type == :aes
106
- md5.digest.byteslice(0, output_len)
136
+ def decrypt_rc4(data, key)
137
+ RC4Impl.new(key).process(data)
107
138
  end
108
139
 
109
- def random_iv
110
- OpenSSL::Random.random_bytes(16)
111
- rescue StandardError
112
- require "securerandom"
113
- SecureRandom.random_bytes(16)
140
+ def encrypt_aes_cbc(data, key)
141
+ iv = OpenSSL::Random.random_bytes(16)
142
+ cipher = OpenSSL::Cipher.new("aes-128-cbc")
143
+ cipher.encrypt
144
+ cipher.key = key[0, 16]
145
+ cipher.iv = iv
146
+ encrypted = cipher.update(data) + cipher.final
147
+ iv + encrypted
148
+ end
149
+
150
+ def decrypt_aes_cbc(data, key)
151
+ return data if data.bytesize < 16
152
+
153
+ iv = data[0, 16]
154
+ ciphertext = data[16..]
155
+ decipher = OpenSSL::Cipher.new("aes-128-cbc")
156
+ decipher.decrypt
157
+ decipher.key = key[0, 16]
158
+ decipher.iv = iv
159
+ decipher.update(ciphertext) + decipher.final
160
+ rescue OpenSSL::Cipher::CipherError
161
+ data
162
+ end
163
+ end
164
+
165
+ # Minimal RC4 implementation
166
+ class RC4Impl
167
+ def initialize(key)
168
+ @s = (0..255).to_a
169
+ j = 0
170
+ key.bytes.each_with_index do |_b, i|
171
+ j = (j + @s[i] + key.getbyte(i % key.bytesize)) & 0xFF
172
+ @s[i], @s[j] = @s[j], @s[i]
173
+ end
174
+ @i = @j = 0
175
+ end
176
+
177
+ def process(data, _xor = nil)
178
+ result = data.dup.force_encoding(Encoding::BINARY)
179
+ result.bytes.each_with_index do |_b, idx|
180
+ @i = (@i + 1) & 0xFF
181
+ @j = (@j + @s[@i]) & 0xFF
182
+ @s[@i], @s[@j] = @s[@j], @s[@i]
183
+ k = @s[(@s[@i] + @s[@j]) & 0xFF]
184
+ result.setbyte(idx, result.getbyte(idx) ^ k)
185
+ end
186
+ result
114
187
  end
115
188
  end
116
189
  end
@@ -16,5 +16,22 @@ module Pdfrb
16
16
  autoload :RC4, "pdfrb/encryption/rc4"
17
17
  autoload :AES, "pdfrb/encryption/aes"
18
18
  autoload :PasswordVerification, "pdfrb/encryption/password_verification"
19
+ autoload :Identity, "pdfrb/encryption/identity"
20
+
21
+ module_function
22
+
23
+ # Look up a registered security-handler subclass by /Filter name.
24
+ def handler_for(filter_name)
25
+ SecurityHandler.lookup(filter_name)
26
+ end
27
+
28
+ # Build a security handler for +document+ by inspecting its /Encrypt.
29
+ # Returns nil if the document is not encrypted.
30
+ def handler_for_document(document, **opts)
31
+ SecurityHandler.for(document, decryption_opts: opts)
32
+ end
19
33
  end
20
34
  end
35
+
36
+ # Register built-in handlers after the module is fully defined.
37
+ Pdfrb::Encryption::SecurityHandler.register("Standard", Pdfrb::Encryption::StandardSecurityHandler)
data/lib/pdfrb/error.rb CHANGED
@@ -29,9 +29,9 @@ module Pdfrb
29
29
  class MalformedPdfError < ParseError
30
30
  attr_reader :recovered
31
31
 
32
- def initialize(message, recovered: false, **opts)
32
+ def initialize(message, recovered: false, **)
33
33
  @recovered = recovered
34
- super(message, **opts)
34
+ super(message, **)
35
35
  end
36
36
 
37
37
  def recovered? = @recovered
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Filter
5
+ module BrotliDecode
6
+ module_function
7
+
8
+ def decoder(data, **_opts)
9
+ return data unless data
10
+
11
+ begin
12
+ require "brotli"
13
+ Brotli.inflate(data)
14
+ rescue LoadError
15
+ raise FilterError, "BrotliDecode requires the 'brotli' gem"
16
+ end
17
+ end
18
+
19
+ def encoder(data, **_opts)
20
+ return data unless data
21
+
22
+ begin
23
+ require "brotli"
24
+ Brotli.deflate(data)
25
+ rescue LoadError
26
+ data
27
+ end
28
+ end
29
+ end
30
+ REGISTRY[:BrotliDecode] = BrotliDecode if defined?(REGISTRY)
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Filter
5
+ module CCITTFaxDecode
6
+ module_function
7
+
8
+ def decoder(data, **_opts); data; end
9
+ def encoder(data, **_opts); data; end
10
+ end
11
+ end
12
+ end
@@ -15,7 +15,7 @@ module Pdfrb
15
15
  return bytes unless document
16
16
  return bytes if parms == :Identity || parms.nil?
17
17
 
18
- handler = document.security_handler if document.respond_to?(:security_handler)
18
+ handler = document.security_handler if !document.config["security.handler"].nil?
19
19
  return bytes unless handler
20
20
 
21
21
  handler.decrypt(bytes, document.current_object_oid)
@@ -25,7 +25,7 @@ module Pdfrb
25
25
  return bytes unless document
26
26
  return bytes if parms == :Identity || parms.nil?
27
27
 
28
- handler = document.security_handler if document.respond_to?(:security_handler)
28
+ handler = document.security_handler if !document.config["security.handler"].nil?
29
29
  return bytes unless handler
30
30
 
31
31
  handler.encrypt(bytes, document.current_object_oid)
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Filter
5
+ module DCTDecode
6
+ module_function
7
+
8
+ def decoder(data, **_opts)
9
+ data
10
+ end
11
+
12
+ def encoder(data, **_opts)
13
+ data
14
+ end
15
+ end
16
+ REGISTRY[:DCTDecode] = DCTDecode if defined?(REGISTRY)
17
+ end
18
+ end
@@ -51,24 +51,10 @@ module Pdfrb
51
51
  # predictor: 10..15 = PNG predictors; we implement the None/Sub/Up/
52
52
  # Average/Paeth (filter type byte at start of each row).
53
53
  def apply_png_predictor_decode(bytes, predictor:, columns:, colors:, bits_per_component:)
54
- return bytes unless (10..15).cover?(predictor)
55
-
56
- row_len = columns * colors * (bits_per_component / 8)
57
- row_len += 1 # leading filter-type byte per row
58
- rows = bytes.each_byte.each_slice(row_len).to_a
59
- prev_row = nil
60
- bpp = (colors * (bits_per_component / 8)) || 1
61
- bpp = 1 if bpp.zero?
62
-
63
- out = +""
64
- rows.each do |row|
65
- filter_type = row[0]
66
- data = row[1..]
67
- unfiltered = unfilter_row(filter_type, data, prev_row, bpp)
68
- out << unfiltered.pack("C*")
69
- prev_row = unfiltered
70
- end
71
- out.force_encoding(Encoding::BINARY)
54
+ Pdfrb::Filter::PNGPredictor.decode(
55
+ bytes, predictor: predictor, columns: columns,
56
+ colors: colors, bits_per_component: bits_per_component
57
+ )
72
58
  end
73
59
 
74
60
  def unfilter_row(filter_type, data, prev_row, bpp)
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Filter
5
+ module JBIG2Decode
6
+ module_function
7
+
8
+ def decoder(data, **_opts); data; end
9
+ def encoder(data, **_opts); data; end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Filter
5
+ module JPXDecode
6
+ module_function
7
+
8
+ def decoder(data, **_opts)
9
+ data
10
+ end
11
+
12
+ def encoder(data, **_opts)
13
+ data
14
+ end
15
+ end
16
+ REGISTRY[:JPXDecode] = JPXDecode if defined?(REGISTRY)
17
+ end
18
+ end
@@ -86,12 +86,13 @@ module Pdfrb
86
86
  predictor = parms[:Predictor] || 1
87
87
  return bytes if predictor == 1
88
88
 
89
- Pdfrb::Filter::FlateDecode.send(:apply_png_predictor_decode,
89
+ Pdfrb::Filter::PNGPredictor.decode(
90
90
  bytes,
91
91
  predictor: predictor,
92
92
  columns: parms[:Columns] || 1,
93
93
  colors: parms[:Colors] || 1,
94
- bits_per_component: parms[:BitsPerComponent] || 8)
94
+ bits_per_component: parms[:BitsPerComponent] || 8
95
+ )
95
96
  end
96
97
  end
97
98
 
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Filter
5
+ # PNG predictor (de)coding shared between FlateDecode and LZWDecode
6
+ # (s7.4.8.4 / RFC 2083 6.6). Extracted as a standalone module so
7
+ # both filters can use it without one calling the other's private
8
+ # methods.
9
+ module PNGPredictor
10
+ module_function
11
+
12
+ def decode(bytes, predictor:, columns:, colors:, bits_per_component:)
13
+ return bytes unless (10..15).cover?(predictor)
14
+
15
+ row_len = columns * colors * (bits_per_component / 8)
16
+ row_len += 1 # leading filter-type byte per row
17
+ rows = bytes.each_byte.each_slice(row_len).to_a
18
+ prev_row = nil
19
+ bpp = (colors * (bits_per_component / 8)) || 1
20
+ bpp = 1 if bpp.zero?
21
+
22
+ out = +""
23
+ rows.each do |row|
24
+ filter_type = row[0]
25
+ data = row[1..]
26
+ unfiltered = unfilter_row(filter_type, data, prev_row, bpp)
27
+ out << unfiltered.pack("C*")
28
+ prev_row = unfiltered
29
+ end
30
+ out.force_encoding(Encoding::BINARY)
31
+ end
32
+
33
+ def unfilter_row(filter_type, data, prev_row, bpp)
34
+ case filter_type
35
+ when 1 then png_sub(data, bpp)
36
+ when 2 then png_up(data, prev_row)
37
+ when 3 then png_average(data, prev_row, bpp)
38
+ when 4 then png_paeth(data, prev_row, bpp)
39
+ else data
40
+ end
41
+ end
42
+
43
+ def png_sub(data, bpp)
44
+ bytes = data.dup
45
+ bpp.upto(bytes.length - 1).each do |i|
46
+ bytes[i] = (bytes[i] + bytes[i - bpp]) & 0xFF
47
+ end
48
+ bytes
49
+ end
50
+
51
+ def png_up(data, prev_row)
52
+ return data unless prev_row
53
+
54
+ bytes = data.dup
55
+ (0...bytes.length).each do |i|
56
+ bytes[i] = (bytes[i] + (prev_row[i] || 0)) & 0xFF
57
+ end
58
+ bytes
59
+ end
60
+
61
+ def png_average(data, prev_row, bpp)
62
+ bytes = data.dup
63
+ (0...bytes.length).each do |i|
64
+ left = bytes[i - bpp] || 0
65
+ up = prev_row ? (prev_row[i] || 0) : 0
66
+ bytes[i] = (bytes[i] + ((left + up) / 2)) & 0xFF
67
+ end
68
+ bytes
69
+ end
70
+
71
+ def png_paeth(data, prev_row, bpp)
72
+ bytes = data.dup
73
+ (0...bytes.length).each do |i|
74
+ left = bytes[i - bpp] || 0
75
+ up = prev_row ? (prev_row[i] || 0) : 0
76
+ up_left = prev_row ? (prev_row[i - bpp] || 0) : 0
77
+ bytes[i] = (bytes[i] + paeth(left, up, up_left)) & 0xFF
78
+ end
79
+ bytes
80
+ end
81
+
82
+ def paeth(a, b, c)
83
+ p = a + b - c
84
+ pa = (p - a).abs
85
+ pb = (p - b).abs
86
+ pc = (p - c).abs
87
+ return a if pa <= pb && pa <= pc
88
+ return b if pb <= pc
89
+
90
+ c
91
+ end
92
+ end
93
+ end
94
+ end
data/lib/pdfrb/filter.rb CHANGED
@@ -6,7 +6,18 @@ module Pdfrb
6
6
  # document)` class methods, registered via `register_as`.
7
7
  module Filter
8
8
  autoload :Base, "pdfrb/filter/base"
9
-
9
+ autoload :FlateDecode, "pdfrb/filter/flate_decode"
10
+ autoload :ASCIIHexDecode, "pdfrb/filter/ascii_hex_decode"
11
+ autoload :ASCII85Decode, "pdfrb/filter/ascii_85_decode"
12
+ autoload :LZWDecode, "pdfrb/filter/lzw_decode"
13
+ autoload :RunLengthDecode, "pdfrb/filter/run_length_decode"
14
+ autoload :Crypt, "pdfrb/filter/crypt"
15
+ autoload :DCTDecode, "pdfrb/filter/dct_decode"
16
+ autoload :JPXDecode, "pdfrb/filter/jpx_decode"
17
+ autoload :CCITTFaxDecode, "pdfrb/filter/ccitt_fax_decode"
18
+ autoload :JBIG2Decode, "pdfrb/filter/jbig2_decode"
19
+ autoload :BrotliDecode, "pdfrb/filter/brotli_decode"
20
+ autoload :PNGPredictor, "pdfrb/filter/png_predictor"
10
21
  class << self
11
22
  def registry; @registry ||= {}; end
12
23
 
@@ -18,7 +29,21 @@ module Pdfrb
18
29
  registry[name.to_s]
19
30
  end
20
31
 
32
+ # Force-load all filter implementations so register_as calls
33
+ # fire and populate the registry. Idempotent.
34
+ def eager_load!
35
+ return if @eager_loaded
36
+
37
+ constants.each do |c|
38
+ const_get(c)
39
+ rescue NameError
40
+ # Skip autoload targets that don't define a class.
41
+ end
42
+ @eager_loaded = true
43
+ end
44
+
21
45
  def apply(bytes, filters:, parms:, direction:, document: nil)
46
+ eager_load!
22
47
  return bytes if filters.empty?
23
48
 
24
49
  case direction
@@ -50,15 +75,3 @@ module Pdfrb
50
75
  end
51
76
  end
52
77
  end
53
-
54
- # Eager-load filters so register_as calls populate the registry
55
- # before any stream decode happens. Must be after the Filter module
56
- # and its singleton methods are fully defined.
57
- require "pdfrb/filter/base"
58
- require "pdfrb/filter/flate_decode"
59
- require "pdfrb/filter/ascii_hex_decode"
60
- require "pdfrb/filter/ascii_85_decode"
61
- require "pdfrb/filter/lzw_decode"
62
- require "pdfrb/filter/run_length_decode"
63
- require "pdfrb/filter/crypt"
64
- require "pdfrb/filter/passthrough_filters"