pdfrb 0.3.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (404) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/Gemfile +1 -0
  4. data/PROPOSAL.canvas-clip.md +92 -0
  5. data/PROPOSAL.canvas-draw-image-matrix.md +87 -0
  6. data/PROPOSAL.canvas-rich-text.md +103 -0
  7. data/PROPOSAL.canvas-text-lines.md +77 -0
  8. data/PROPOSAL.canvas-transparency.md +106 -0
  9. data/PROPOSAL.external-font-metrics.md +129 -0
  10. data/PROPOSAL.font-embedding-fontfile2.md +88 -0
  11. data/PROPOSAL.font-resolver-ttc.md +105 -0
  12. data/PROPOSAL.font-resolver.md +96 -0
  13. data/PROPOSAL.font-subsetting.md +98 -0
  14. data/PROPOSAL.gradient-shadings.md +110 -0
  15. data/PROPOSAL.page-mediabox-setter.md +66 -0
  16. data/PROPOSAL.per-glyph-width.md +91 -0
  17. data/PROPOSAL.stringio-require.md +53 -0
  18. data/PROPOSAL.struct-tree-root.md +102 -0
  19. data/PROPOSAL.text-measurement.md +93 -0
  20. data/PROPOSAL.ttf-table-parsing-fix.md +112 -0
  21. data/RELEASE.md +48 -0
  22. data/lib/pdfrb/action/base.rb +21 -0
  23. data/lib/pdfrb/action/types.rb +178 -0
  24. data/lib/pdfrb/action.rb +55 -0
  25. data/lib/pdfrb/annotation/base.rb +79 -0
  26. data/lib/pdfrb/annotation/types.rb +222 -0
  27. data/lib/pdfrb/annotation.rb +78 -0
  28. data/lib/pdfrb/appearance/generator.rb +216 -0
  29. data/lib/pdfrb/appearance.rb +11 -0
  30. data/lib/pdfrb/arlington/object_definition.rb +2 -2
  31. data/lib/pdfrb/arlington/predicate/evaluator.rb +1 -1
  32. data/lib/pdfrb/arlington/predicate/functions.rb +25 -9
  33. data/lib/pdfrb/arlington/predicate.rb +1 -5
  34. data/lib/pdfrb/cli/batch.rb +18 -0
  35. data/lib/pdfrb/cli/debug_info.rb +18 -0
  36. data/lib/pdfrb/cli/files.rb +16 -0
  37. data/lib/pdfrb/cli/fonts.rb +16 -0
  38. data/lib/pdfrb/cli/form.rb +18 -0
  39. data/lib/pdfrb/cli/image2pdf.rb +18 -0
  40. data/lib/pdfrb/cli/images.rb +34 -0
  41. data/lib/pdfrb/cli/info.rb +20 -0
  42. data/lib/pdfrb/cli/inspect.rb +18 -0
  43. data/lib/pdfrb/cli/merge.rb +18 -0
  44. data/lib/pdfrb/cli/modify.rb +18 -0
  45. data/lib/pdfrb/cli/optimize.rb +18 -0
  46. data/lib/pdfrb/cli/split.rb +21 -0
  47. data/lib/pdfrb/cli/watermark.rb +18 -0
  48. data/lib/pdfrb/cli.rb +291 -7
  49. data/lib/pdfrb/color/color_space.rb +259 -0
  50. data/lib/pdfrb/color/icc_profile.rb +90 -0
  51. data/lib/pdfrb/color.rb +12 -0
  52. data/lib/pdfrb/compare/comparator.rb +244 -0
  53. data/lib/pdfrb/compare/report.rb +78 -0
  54. data/lib/pdfrb/compare.rb +42 -0
  55. data/lib/pdfrb/composer.rb +161 -0
  56. data/lib/pdfrb/conformance/pdf_a.rb +317 -56
  57. data/lib/pdfrb/conformance/pdf_ua.rb +186 -29
  58. data/lib/pdfrb/conformance/pdf_x.rb +247 -0
  59. data/lib/pdfrb/conformance/rule.rb +78 -0
  60. data/lib/pdfrb/conformance/structure_elements.rb +174 -0
  61. data/lib/pdfrb/conformance/verapdf_bridge.rb +92 -0
  62. data/lib/pdfrb/conformance.rb +7 -3
  63. data/lib/pdfrb/content/canvas.rb +217 -72
  64. data/lib/pdfrb/content/color_space.rb +157 -0
  65. data/lib/pdfrb/content/hidden_text_detector.rb +167 -0
  66. data/lib/pdfrb/content/operator.rb +21 -1
  67. data/lib/pdfrb/content/operators/clipping.rb +36 -0
  68. data/lib/pdfrb/content/operators/graphics_state_params.rb +15 -0
  69. data/lib/pdfrb/content/operators.rb +21 -0
  70. data/lib/pdfrb/content/shading/base.rb +139 -0
  71. data/lib/pdfrb/content/shading.rb +17 -0
  72. data/lib/pdfrb/content/smart_text_extractor.rb +85 -0
  73. data/lib/pdfrb/content/tiling_pattern.rb +78 -0
  74. data/lib/pdfrb/content/transformation_matrix.rb +61 -0
  75. data/lib/pdfrb/content.rb +7 -26
  76. data/lib/pdfrb/destination/fit.rb +185 -0
  77. data/lib/pdfrb/destination.rb +13 -0
  78. data/lib/pdfrb/digital_signature/cms_handler.rb +22 -0
  79. data/lib/pdfrb/digital_signature/signing.rb +107 -11
  80. data/lib/pdfrb/digital_signature/timestamp_handler.rb +45 -0
  81. data/lib/pdfrb/digital_signature/verification.rb +88 -9
  82. data/lib/pdfrb/digital_signature/verification_result.rb +14 -0
  83. data/lib/pdfrb/digital_signature.rb +26 -2
  84. data/lib/pdfrb/document/annotations.rb +120 -3
  85. data/lib/pdfrb/document/associated_files.rb +104 -0
  86. data/lib/pdfrb/document/colors.rb +95 -0
  87. data/lib/pdfrb/document/destinations.rb +127 -5
  88. data/lib/pdfrb/document/display.rb +84 -0
  89. data/lib/pdfrb/document/fonts.rb +417 -45
  90. data/lib/pdfrb/document/form.rb +338 -0
  91. data/lib/pdfrb/document/graphics_state.rb +91 -0
  92. data/lib/pdfrb/document/images.rb +4 -4
  93. data/lib/pdfrb/document/info.rb +102 -0
  94. data/lib/pdfrb/document/layers.rb +62 -0
  95. data/lib/pdfrb/document/metadata.rb +12 -5
  96. data/lib/pdfrb/document/output_intents.rb +91 -0
  97. data/lib/pdfrb/document/page_labels.rb +80 -0
  98. data/lib/pdfrb/document/pages.rb +68 -19
  99. data/lib/pdfrb/document/portfolio.rb +119 -0
  100. data/lib/pdfrb/document/shadings.rb +159 -0
  101. data/lib/pdfrb/document/stamps.rb +68 -0
  102. data/lib/pdfrb/document/structure.rb +242 -0
  103. data/lib/pdfrb/document.rb +73 -4
  104. data/lib/pdfrb/encryption/identity.rb +13 -0
  105. data/lib/pdfrb/encryption/password_verification.rb +53 -8
  106. data/lib/pdfrb/encryption/security_handler.rb +8 -5
  107. data/lib/pdfrb/encryption/standard_security_handler.rb +154 -81
  108. data/lib/pdfrb/encryption.rb +17 -0
  109. data/lib/pdfrb/error.rb +2 -2
  110. data/lib/pdfrb/filter/brotli_decode.rb +32 -0
  111. data/lib/pdfrb/filter/ccitt_fax_decode.rb +12 -0
  112. data/lib/pdfrb/filter/crypt.rb +2 -2
  113. data/lib/pdfrb/filter/dct_decode.rb +18 -0
  114. data/lib/pdfrb/filter/flate_decode.rb +4 -18
  115. data/lib/pdfrb/filter/jbig2_decode.rb +12 -0
  116. data/lib/pdfrb/filter/jpx_decode.rb +18 -0
  117. data/lib/pdfrb/filter/lzw_decode.rb +3 -2
  118. data/lib/pdfrb/filter/png_predictor.rb +94 -0
  119. data/lib/pdfrb/filter.rb +26 -13
  120. data/lib/pdfrb/font/afm_parser.rb +50 -26
  121. data/lib/pdfrb/font/cmap/writer.rb +9 -0
  122. data/lib/pdfrb/font/encoding/win_ansi_encoding.rb +17 -0
  123. data/lib/pdfrb/font/encoding.rb +32 -3
  124. data/lib/pdfrb/font/metrics_helper.rb +121 -0
  125. data/lib/pdfrb/font/true_type/file.rb +10 -1
  126. data/lib/pdfrb/font/true_type/glyf.rb +82 -0
  127. data/lib/pdfrb/font/true_type/kern.rb +58 -0
  128. data/lib/pdfrb/font/true_type/loca.rb +42 -0
  129. data/lib/pdfrb/font/true_type/maxp.rb +44 -0
  130. data/lib/pdfrb/font/true_type/name.rb +53 -0
  131. data/lib/pdfrb/font/true_type/post.rb +93 -0
  132. data/lib/pdfrb/font/true_type/subsetter.rb +3 -5
  133. data/lib/pdfrb/font/true_type/wrapper.rb +102 -0
  134. data/lib/pdfrb/font/true_type.rb +7 -0
  135. data/lib/pdfrb/font.rb +2 -0
  136. data/lib/pdfrb/font_loader/from_configuration.rb +19 -0
  137. data/lib/pdfrb/font_loader/from_file.rb +20 -0
  138. data/lib/pdfrb/font_loader/standard14.rb +20 -0
  139. data/lib/pdfrb/font_loader/variant_from_name.rb +36 -0
  140. data/lib/pdfrb/font_loader.rb +4 -0
  141. data/lib/pdfrb/font_resolver.rb +138 -0
  142. data/lib/pdfrb/image/audit.rb +94 -0
  143. data/lib/pdfrb/image/downsampler.rb +124 -0
  144. data/lib/pdfrb/image.rb +13 -0
  145. data/lib/pdfrb/image_loader/jpeg.rb +39 -58
  146. data/lib/pdfrb/image_loader/png.rb +31 -257
  147. data/lib/pdfrb/layout/bidi.rb +198 -0
  148. data/lib/pdfrb/layout/box.rb +49 -0
  149. data/lib/pdfrb/layout/box_fitter.rb +54 -0
  150. data/lib/pdfrb/layout/column_box.rb +71 -0
  151. data/lib/pdfrb/layout/container_box.rb +45 -0
  152. data/lib/pdfrb/layout/frame.rb +67 -0
  153. data/lib/pdfrb/layout/image_box.rb +44 -0
  154. data/lib/pdfrb/layout/inline_box.rb +25 -0
  155. data/lib/pdfrb/layout/line.rb +55 -0
  156. data/lib/pdfrb/layout/list_box.rb +87 -0
  157. data/lib/pdfrb/layout/numeric_refinements.rb +43 -0
  158. data/lib/pdfrb/layout/page_style.rb +65 -0
  159. data/lib/pdfrb/layout/style.rb +62 -0
  160. data/lib/pdfrb/layout/table_box.rb +66 -0
  161. data/lib/pdfrb/layout/text_box.rb +38 -0
  162. data/lib/pdfrb/layout/text_fragment.rb +45 -0
  163. data/lib/pdfrb/layout/text_layouter.rb +123 -0
  164. data/lib/pdfrb/layout.rb +28 -0
  165. data/lib/pdfrb/linearization/hint_stream.rb +93 -0
  166. data/lib/pdfrb/linearization/writer.rb +259 -0
  167. data/lib/pdfrb/linearization.rb +21 -0
  168. data/lib/pdfrb/model/cos/dictionary.rb +12 -0
  169. data/lib/pdfrb/model/pdf_array.rb +2 -2
  170. data/lib/pdfrb/model/type/action.rb +42 -0
  171. data/lib/pdfrb/model/type/action_go_to_3d_view.rb +14 -0
  172. data/lib/pdfrb/model/type/action_go_to_dp.rb +16 -0
  173. data/lib/pdfrb/model/type/action_go_to_r.rb +26 -0
  174. data/lib/pdfrb/model/type/action_goto.rb +34 -0
  175. data/lib/pdfrb/model/type/action_hide.rb +26 -0
  176. data/lib/pdfrb/model/type/action_import_data.rb +18 -0
  177. data/lib/pdfrb/model/type/action_java_script.rb +18 -0
  178. data/lib/pdfrb/model/type/action_launch.rb +22 -0
  179. data/lib/pdfrb/model/type/action_movie.rb +22 -0
  180. data/lib/pdfrb/model/type/action_named.rb +23 -0
  181. data/lib/pdfrb/model/type/action_nop.rb +12 -0
  182. data/lib/pdfrb/model/type/action_rendition.rb +28 -0
  183. data/lib/pdfrb/model/type/action_reset_form.rb +22 -0
  184. data/lib/pdfrb/model/type/action_rich_media_execute.rb +17 -0
  185. data/lib/pdfrb/model/type/action_set_ocg_state.rb +32 -0
  186. data/lib/pdfrb/model/type/action_set_state.rb +14 -0
  187. data/lib/pdfrb/model/type/action_sound_action.rb +23 -0
  188. data/lib/pdfrb/model/type/action_submit_form.rb +64 -0
  189. data/lib/pdfrb/model/type/action_thread.rb +23 -0
  190. data/lib/pdfrb/model/type/action_trans.rb +30 -0
  191. data/lib/pdfrb/model/type/action_uri.rb +30 -0
  192. data/lib/pdfrb/model/type/af_embedded_file.rb +11 -0
  193. data/lib/pdfrb/model/type/af_file_specification.rb +34 -0
  194. data/lib/pdfrb/model/type/annotation.rb +49 -10
  195. data/lib/pdfrb/model/type/appearance.rb +41 -0
  196. data/lib/pdfrb/model/type/appearance_characteristics.rb +36 -0
  197. data/lib/pdfrb/model/type/appearance_generator.rb +22 -0
  198. data/lib/pdfrb/model/type/appearance_trap_net.rb +84 -0
  199. data/lib/pdfrb/model/type/border_effect.rb +25 -0
  200. data/lib/pdfrb/model/type/border_styling.rb +38 -0
  201. data/lib/pdfrb/model/type/box_color_info.rb +21 -0
  202. data/lib/pdfrb/model/type/box_style.rb +19 -0
  203. data/lib/pdfrb/model/type/button.rb +17 -0
  204. data/lib/pdfrb/model/type/cal_gray.rb +17 -0
  205. data/lib/pdfrb/model/type/cal_rgb.rb +22 -0
  206. data/lib/pdfrb/model/type/caret_annotation.rb +21 -0
  207. data/lib/pdfrb/model/type/catalog.rb +99 -16
  208. data/lib/pdfrb/model/type/choice.rb +21 -0
  209. data/lib/pdfrb/model/type/cid_font.rb +52 -1
  210. data/lib/pdfrb/model/type/cid_font_descriptor_metrics.rb +28 -0
  211. data/lib/pdfrb/model/type/cid_system_info.rb +28 -0
  212. data/lib/pdfrb/model/type/circle_annotation.rb +20 -0
  213. data/lib/pdfrb/model/type/collection.rb +27 -0
  214. data/lib/pdfrb/model/type/collection_field.rb +24 -0
  215. data/lib/pdfrb/model/type/collection_item.rb +19 -0
  216. data/lib/pdfrb/model/type/collection_schema.rb +25 -0
  217. data/lib/pdfrb/model/type/collection_sort.rb +13 -0
  218. data/lib/pdfrb/model/type/crypt_filter.rb +58 -0
  219. data/lib/pdfrb/model/type/device_n.rb +37 -0
  220. data/lib/pdfrb/model/type/device_n_mixing_hints.rb +17 -0
  221. data/lib/pdfrb/model/type/device_n_process.rb +13 -0
  222. data/lib/pdfrb/model/type/doc_mdp_transform_parameters.rb +21 -0
  223. data/lib/pdfrb/model/type/document_security_store.rb +55 -0
  224. data/lib/pdfrb/model/type/embedded_file.rb +19 -0
  225. data/lib/pdfrb/model/type/embedded_file_parameter.rb +48 -0
  226. data/lib/pdfrb/model/type/encryption_public_key.rb +38 -0
  227. data/lib/pdfrb/model/type/encryption_standard.rb +68 -0
  228. data/lib/pdfrb/model/type/field_mdp_transform_parameters.rb +26 -0
  229. data/lib/pdfrb/model/type/file_attachment_annotation.rb +29 -0
  230. data/lib/pdfrb/model/type/file_spec_ef.rb +22 -0
  231. data/lib/pdfrb/model/type/file_spec_rf.rb +21 -0
  232. data/lib/pdfrb/model/type/file_specification.rb +43 -0
  233. data/lib/pdfrb/model/type/file_trailer.rb +54 -0
  234. data/lib/pdfrb/model/type/font.rb +73 -2
  235. data/lib/pdfrb/model/type/font_cid_type0.rb +17 -0
  236. data/lib/pdfrb/model/type/font_cid_type2.rb +17 -0
  237. data/lib/pdfrb/model/type/font_descriptor.rb +47 -0
  238. data/lib/pdfrb/model/type/font_descriptor_cid_type0.rb +15 -0
  239. data/lib/pdfrb/model/type/font_descriptor_cid_type2.rb +15 -0
  240. data/lib/pdfrb/model/type/font_encoding.rb +47 -0
  241. data/lib/pdfrb/model/type/font_file.rb +28 -0
  242. data/lib/pdfrb/model/type/font_file2.rb +32 -0
  243. data/lib/pdfrb/model/type/font_file3.rb +23 -0
  244. data/lib/pdfrb/model/type/font_multiple_master.rb +60 -0
  245. data/lib/pdfrb/model/type/font_true_type.rb +1 -1
  246. data/lib/pdfrb/model/type/font_type0.rb +1 -1
  247. data/lib/pdfrb/model/type/font_type1.rb +1 -1
  248. data/lib/pdfrb/model/type/font_type3.rb +1 -1
  249. data/lib/pdfrb/model/type/form_field.rb +73 -0
  250. data/lib/pdfrb/model/type/free_text_annotation.rb +37 -0
  251. data/lib/pdfrb/model/type/function.rb +42 -0
  252. data/lib/pdfrb/model/type/function_exponential.rb +31 -0
  253. data/lib/pdfrb/model/type/function_postscript.rb +25 -0
  254. data/lib/pdfrb/model/type/function_sampled.rb +55 -0
  255. data/lib/pdfrb/model/type/function_stitching.rb +33 -0
  256. data/lib/pdfrb/model/type/generic_appearance.rb +40 -0
  257. data/lib/pdfrb/model/type/graphics_state_parameter.rb +69 -0
  258. data/lib/pdfrb/model/type/halftone.rb +21 -0
  259. data/lib/pdfrb/model/type/halftone_type1.rb +17 -0
  260. data/lib/pdfrb/model/type/halftone_type10.rb +16 -0
  261. data/lib/pdfrb/model/type/halftone_type16.rb +11 -0
  262. data/lib/pdfrb/model/type/halftone_type5.rb +24 -0
  263. data/lib/pdfrb/model/type/halftone_type6.rb +16 -0
  264. data/lib/pdfrb/model/type/highlight_annotation.rb +11 -0
  265. data/lib/pdfrb/model/type/icc_based_color_space.rb +24 -0
  266. data/lib/pdfrb/model/type/icon_fit.rb +31 -0
  267. data/lib/pdfrb/model/type/indexed.rb +32 -0
  268. data/lib/pdfrb/model/type/info.rb +44 -0
  269. data/lib/pdfrb/model/type/ink_annotation.rb +29 -0
  270. data/lib/pdfrb/model/type/interactive_form.rb +36 -0
  271. data/lib/pdfrb/model/type/interior_color.rb +12 -0
  272. data/lib/pdfrb/model/type/jbig2_globals.rb +15 -0
  273. data/lib/pdfrb/model/type/lab.rb +27 -0
  274. data/lib/pdfrb/model/type/line_annotation.rb +44 -0
  275. data/lib/pdfrb/model/type/line_ending_styling.rb +39 -0
  276. data/lib/pdfrb/model/type/link_annotation.rb +41 -0
  277. data/lib/pdfrb/model/type/link_appearance.rb +9 -0
  278. data/lib/pdfrb/model/type/mark_information.rb +23 -0
  279. data/lib/pdfrb/model/type/marked_content_reference.rb +24 -0
  280. data/lib/pdfrb/model/type/markup_annotation.rb +36 -0
  281. data/lib/pdfrb/model/type/mdp_dict.rb +28 -0
  282. data/lib/pdfrb/model/type/measure.rb +39 -0
  283. data/lib/pdfrb/model/type/media_clip.rb +28 -0
  284. data/lib/pdfrb/model/type/media_offset.rb +106 -0
  285. data/lib/pdfrb/model/type/metadata.rb +27 -3
  286. data/lib/pdfrb/model/type/misc_helpers.rb +75 -0
  287. data/lib/pdfrb/model/type/movie.rb +28 -0
  288. data/lib/pdfrb/model/type/names.rb +51 -0
  289. data/lib/pdfrb/model/type/namespace.rb +27 -0
  290. data/lib/pdfrb/model/type/object_reference.rb +31 -0
  291. data/lib/pdfrb/model/type/object_stream.rb +38 -0
  292. data/lib/pdfrb/model/type/opt_content_ext.rb +94 -0
  293. data/lib/pdfrb/model/type/opt_content_page_element.rb +18 -0
  294. data/lib/pdfrb/model/type/optional_content_config.rb +56 -0
  295. data/lib/pdfrb/model/type/optional_content_group.rb +35 -10
  296. data/lib/pdfrb/model/type/optional_content_membership.rb +36 -2
  297. data/lib/pdfrb/model/type/optional_content_properties.rb +16 -1
  298. data/lib/pdfrb/model/type/outline.rb +40 -6
  299. data/lib/pdfrb/model/type/outline_item.rb +45 -0
  300. data/lib/pdfrb/model/type/output_intent.rb +30 -0
  301. data/lib/pdfrb/model/type/page.rb +127 -0
  302. data/lib/pdfrb/model/type/page_label.rb +68 -0
  303. data/lib/pdfrb/model/type/page_tree_node.rb +51 -16
  304. data/lib/pdfrb/model/type/page_tree_node_root.rb +15 -0
  305. data/lib/pdfrb/model/type/pattern.rb +16 -0
  306. data/lib/pdfrb/model/type/pattern_shading.rb +25 -0
  307. data/lib/pdfrb/model/type/pattern_tiling.rb +25 -0
  308. data/lib/pdfrb/model/type/polygon_annotation.rb +29 -0
  309. data/lib/pdfrb/model/type/polygon_polyline.rb +14 -0
  310. data/lib/pdfrb/model/type/polyline_annotation.rb +29 -0
  311. data/lib/pdfrb/model/type/popup_annotation.rb +20 -0
  312. data/lib/pdfrb/model/type/printer_mark_annotation.rb +13 -0
  313. data/lib/pdfrb/model/type/redact_annotation.rb +28 -0
  314. data/lib/pdfrb/model/type/rendition.rb +31 -0
  315. data/lib/pdfrb/model/type/resources.rb +59 -14
  316. data/lib/pdfrb/model/type/rich_media_activation.rb +24 -0
  317. data/lib/pdfrb/model/type/rich_media_animation.rb +25 -0
  318. data/lib/pdfrb/model/type/rich_media_annotation.rb +18 -0
  319. data/lib/pdfrb/model/type/rich_media_command.rb +19 -0
  320. data/lib/pdfrb/model/type/rich_media_configuration.rb +32 -0
  321. data/lib/pdfrb/model/type/rich_media_content.rb +28 -0
  322. data/lib/pdfrb/model/type/rich_media_cue_point.rb +18 -0
  323. data/lib/pdfrb/model/type/rich_media_deactivation.rb +17 -0
  324. data/lib/pdfrb/model/type/rich_media_instance.rb +33 -0
  325. data/lib/pdfrb/model/type/rich_media_presentation.rb +29 -0
  326. data/lib/pdfrb/model/type/rich_media_settings.rb +33 -0
  327. data/lib/pdfrb/model/type/rich_media_window.rb +20 -0
  328. data/lib/pdfrb/model/type/screen_annotation.rb +19 -0
  329. data/lib/pdfrb/model/type/separation.rb +25 -0
  330. data/lib/pdfrb/model/type/sig_field_lock.rb +25 -0
  331. data/lib/pdfrb/model/type/sig_field_seed_value.rb +25 -0
  332. data/lib/pdfrb/model/type/signature.rb +65 -0
  333. data/lib/pdfrb/model/type/signature_build.rb +86 -0
  334. data/lib/pdfrb/model/type/signature_field.rb +17 -0
  335. data/lib/pdfrb/model/type/software_identifier.rb +19 -0
  336. data/lib/pdfrb/model/type/sound.rb +38 -0
  337. data/lib/pdfrb/model/type/sound_annotation.rb +21 -0
  338. data/lib/pdfrb/model/type/square_annotation.rb +24 -0
  339. data/lib/pdfrb/model/type/square_circle.rb +13 -0
  340. data/lib/pdfrb/model/type/squiggly_annotation.rb +11 -0
  341. data/lib/pdfrb/model/type/stamp_annotation.rb +19 -0
  342. data/lib/pdfrb/model/type/strikeout_annotation.rb +11 -0
  343. data/lib/pdfrb/model/type/struct_elem.rb +75 -0
  344. data/lib/pdfrb/model/type/struct_tree_root.rb +34 -0
  345. data/lib/pdfrb/model/type/structure_attributes.rb +44 -0
  346. data/lib/pdfrb/model/type/structure_element_kid.rb +33 -0
  347. data/lib/pdfrb/model/type/text_annotation.rb +25 -0
  348. data/lib/pdfrb/model/type/text_appearance.rb +9 -0
  349. data/lib/pdfrb/model/type/text_field.rb +21 -0
  350. data/lib/pdfrb/model/type/text_markup_annotation.rb +30 -0
  351. data/lib/pdfrb/model/type/three_d_activation.rb +32 -0
  352. data/lib/pdfrb/model/type/three_d_animation_style.rb +24 -0
  353. data/lib/pdfrb/model/type/three_d_background.rb +28 -0
  354. data/lib/pdfrb/model/type/three_d_cross_section.rb +25 -0
  355. data/lib/pdfrb/model/type/three_d_lighting_scheme.rb +26 -0
  356. data/lib/pdfrb/model/type/three_d_measure.rb +20 -0
  357. data/lib/pdfrb/model/type/three_d_measure_3dc.rb +23 -0
  358. data/lib/pdfrb/model/type/three_d_measure_ad3.rb +14 -0
  359. data/lib/pdfrb/model/type/three_d_measure_ld3.rb +14 -0
  360. data/lib/pdfrb/model/type/three_d_measure_pd3.rb +20 -0
  361. data/lib/pdfrb/model/type/three_d_measure_rd3.rb +14 -0
  362. data/lib/pdfrb/model/type/three_d_node.rb +37 -0
  363. data/lib/pdfrb/model/type/three_d_reference.rb +21 -0
  364. data/lib/pdfrb/model/type/three_d_render_mode.rb +34 -0
  365. data/lib/pdfrb/model/type/three_d_stream.rb +48 -0
  366. data/lib/pdfrb/model/type/three_d_units.rb +18 -0
  367. data/lib/pdfrb/model/type/three_d_view.rb +40 -0
  368. data/lib/pdfrb/model/type/to_unicode_cmap_stream.rb +23 -0
  369. data/lib/pdfrb/model/type/transition.rb +68 -0
  370. data/lib/pdfrb/model/type/underline_annotation.rb +11 -0
  371. data/lib/pdfrb/model/type/ur_transform_parameters.rb +33 -0
  372. data/lib/pdfrb/model/type/uri_dict.rb +18 -0
  373. data/lib/pdfrb/model/type/url_alias.rb +19 -0
  374. data/lib/pdfrb/model/type/variable_text_field.rb +47 -0
  375. data/lib/pdfrb/model/type/viewer_preferences.rb +42 -0
  376. data/lib/pdfrb/model/type/watermark_annotation.rb +13 -0
  377. data/lib/pdfrb/model/type/widget_annotation.rb +48 -0
  378. data/lib/pdfrb/model/type/widget_appearance.rb +9 -0
  379. data/lib/pdfrb/model/type/xobject_form.rb +41 -0
  380. data/lib/pdfrb/model/type/xobject_image.rb +56 -0
  381. data/lib/pdfrb/model/type/xref_stream.rb +37 -0
  382. data/lib/pdfrb/model/type.rb +286 -6
  383. data/lib/pdfrb/serializer.rb +2 -2
  384. data/lib/pdfrb/source/tokenizer.rb +0 -2
  385. data/lib/pdfrb/task/benchmark.rb +83 -0
  386. data/lib/pdfrb/task/extract_text.rb +1 -1
  387. data/lib/pdfrb/task/generate_corpus.rb +130 -0
  388. data/lib/pdfrb/task/memory_profile.rb +84 -0
  389. data/lib/pdfrb/task/optimize.rb +97 -7
  390. data/lib/pdfrb/task.rb +3 -3
  391. data/lib/pdfrb/test_utils.rb +112 -0
  392. data/lib/pdfrb/validator.rb +82 -0
  393. data/lib/pdfrb/version.rb +1 -1
  394. data/lib/pdfrb/writer.rb +117 -27
  395. data/lib/pdfrb/xmp/packet.rb +122 -0
  396. data/lib/pdfrb/xmp/schemas.rb +96 -0
  397. data/lib/pdfrb/xmp.rb +44 -0
  398. data/lib/pdfrb/xref_section.rb +2 -2
  399. data/lib/pdfrb.rb +24 -54
  400. data/pdfrb.gemspec +45 -0
  401. data/script/apply_fixes.sh +843 -0
  402. data/script/make_jpeg_fixture.rb +53 -0
  403. data/script/make_png_fixture.rb +25 -0
  404. metadata +311 -2
@@ -0,0 +1,259 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Lint/MissingSuper
4
+ module Pdfrb
5
+ module Color
6
+ # Color space value objects (ISO 32000-2 §8.6). Each color space
7
+ # knows how to serialize to its PDF representation (a name for
8
+ # device spaces, an array for CIE-based and special spaces).
9
+ #
10
+ # Hierarchy:
11
+ # Base
12
+ # Device (DeviceGray, DeviceRGB, DeviceCMYK)
13
+ # CIEBased (CalGray, CalRGB, Lab, ICCBased)
14
+ # Special (Indexed, Separation, DeviceN, Pattern)
15
+ #
16
+ # OCP: new color space = subclass + register_as.
17
+ class ColorSpace
18
+ class << self
19
+ def registry
20
+ @registry ||= {}
21
+ end
22
+
23
+ def register(name, klass)
24
+ registry[name.to_sym] = klass
25
+ end
26
+
27
+ def [](name)
28
+ registry[name.to_sym]
29
+ end
30
+
31
+ def names
32
+ registry.keys
33
+ end
34
+
35
+ # Subclass hook: the PDF name or array-head symbol.
36
+ def pdf_name
37
+ raise NotImplementedError
38
+ end
39
+
40
+ # Register this class under its pdf_name.
41
+ def register_as(name = pdf_name)
42
+ ColorSpace.register(name, self)
43
+ self
44
+ end
45
+ end
46
+ end
47
+
48
+ # Device color spaces — simple names, no parameters.
49
+ class DeviceColorSpace < ColorSpace
50
+ def to_pdf
51
+ self.class.pdf_name
52
+ end
53
+ end
54
+
55
+ class DeviceGray < DeviceColorSpace
56
+ class << self
57
+ def pdf_name; :DeviceGray; end
58
+ end
59
+ register_as
60
+ end
61
+
62
+ class DeviceRGB < DeviceColorSpace
63
+ class << self
64
+ def pdf_name; :DeviceRGB; end
65
+ end
66
+ register_as
67
+ end
68
+
69
+ class DeviceCMYK < DeviceColorSpace
70
+ class << self
71
+ def pdf_name; :DeviceCMYK; end
72
+ end
73
+ register_as
74
+ end
75
+
76
+ # CIE-based color spaces — arrays with calibration parameters.
77
+ class CIEBasedColorSpace < ColorSpace
78
+ def to_pdf
79
+ Pdfrb::Model::PdfArray.new([self.class.pdf_name, parameters_array].compact.flatten)
80
+ end
81
+
82
+ def parameters_array
83
+ []
84
+ end
85
+ end
86
+
87
+ class CalGray < CIEBasedColorSpace
88
+ attr_reader :white_point, :black_point, :gamma
89
+
90
+ def initialize(white_point:, black_point: nil, gamma: nil)
91
+ @white_point = white_point
92
+ @black_point = black_point
93
+ @gamma = gamma
94
+ end
95
+
96
+ class << self
97
+ def pdf_name; :CalGray; end
98
+ end
99
+
100
+ def parameters_array
101
+ dict = { WhitePoint: Pdfrb::Model::PdfArray.new(@white_point) }
102
+ dict[:BlackPoint] = Pdfrb::Model::PdfArray.new(@black_point) if @black_point
103
+ dict[:Gamma] = @gamma if @gamma
104
+ dict
105
+ end
106
+ register_as
107
+ end
108
+
109
+ class CalRGB < CIEBasedColorSpace
110
+ attr_reader :white_point, :gamma, :matrix, :black_point
111
+
112
+ def initialize(white_point:, gamma: nil, matrix: nil, black_point: nil)
113
+ @white_point = white_point
114
+ @gamma = gamma
115
+ @matrix = matrix
116
+ @black_point = black_point
117
+ end
118
+
119
+ class << self
120
+ def pdf_name; :CalRGB; end
121
+ end
122
+
123
+ def parameters_array
124
+ dict = { WhitePoint: Pdfrb::Model::PdfArray.new(@white_point) }
125
+ dict[:Gamma] = Pdfrb::Model::PdfArray.new(@gamma) if @gamma
126
+ dict[:Matrix] = Pdfrb::Model::PdfArray.new(@matrix) if @matrix
127
+ dict[:BlackPoint] = Pdfrb::Model::PdfArray.new(@black_point) if @black_point
128
+ dict
129
+ end
130
+ register_as
131
+ end
132
+
133
+ class Lab < CIEBasedColorSpace
134
+ attr_reader :white_point, :black_point, :range
135
+
136
+ def initialize(white_point:, black_point: nil, range: nil)
137
+ @white_point = white_point
138
+ @black_point = black_point
139
+ @range = range
140
+ end
141
+
142
+ class << self
143
+ def pdf_name; :Lab; end
144
+ end
145
+
146
+ def parameters_array
147
+ dict = { WhitePoint: Pdfrb::Model::PdfArray.new(@white_point) }
148
+ dict[:BlackPoint] = Pdfrb::Model::PdfArray.new(@black_point) if @black_point
149
+ dict[:Range] = Pdfrb::Model::PdfArray.new(@range) if @range
150
+ dict
151
+ end
152
+ register_as
153
+ end
154
+
155
+ # ICCBased is special — references an ICC profile stream.
156
+ # Delegates to Pdfrb::Color::ICCProfile for stream creation.
157
+ class ICCBased < CIEBasedColorSpace
158
+ attr_reader :icc_stream_ref
159
+
160
+ def initialize(icc_stream_ref)
161
+ @icc_stream_ref = icc_stream_ref
162
+ end
163
+
164
+ class << self
165
+ def pdf_name; :ICCBased; end
166
+ end
167
+
168
+ def to_pdf
169
+ Pdfrb::Model::PdfArray.new([:ICCBased, @icc_stream_ref])
170
+ end
171
+ register_as
172
+ end
173
+
174
+ # Indexed color space — maps small integer codes to colors in a base space.
175
+ class Indexed < ColorSpace
176
+ attr_reader :base, :hival, :lookup
177
+
178
+ def initialize(base:, hival:, lookup:)
179
+ @base = base
180
+ @hival = hival
181
+ @lookup = lookup
182
+ end
183
+
184
+ class << self
185
+ def pdf_name; :Indexed; end
186
+ end
187
+
188
+ def to_pdf
189
+ Pdfrb::Model::PdfArray.new([:Indexed, @base, @hival, @lookup])
190
+ end
191
+ register_as
192
+ end
193
+
194
+ # Separation color space — a single named ink (e.g. "PANTONE 185 C").
195
+ class Separation < ColorSpace
196
+ attr_reader :name, :alternate, :tint_transform
197
+
198
+ def initialize(name:, alternate:, tint_transform:)
199
+ @name = name
200
+ @alternate = alternate
201
+ @tint_transform = tint_transform
202
+ end
203
+
204
+ class << self
205
+ def pdf_name; :Separation; end
206
+ end
207
+
208
+ def to_pdf
209
+ Pdfrb::Model::PdfArray.new([:Separation, @name, @alternate, @tint_transform])
210
+ end
211
+ register_as
212
+ end
213
+
214
+ # DeviceN color space — multiple named inks.
215
+ class DeviceN < ColorSpace
216
+ attr_reader :names, :alternate, :tint_transform
217
+
218
+ def initialize(names:, alternate:, tint_transform:)
219
+ @names = names
220
+ @alternate = alternate
221
+ @tint_transform = tint_transform
222
+ end
223
+
224
+ class << self
225
+ def pdf_name; :DeviceN; end
226
+ end
227
+
228
+ def to_pdf
229
+ Pdfrb::Model::PdfArray.new([
230
+ :DeviceN,
231
+ Pdfrb::Model::PdfArray.new(@names),
232
+ @alternate,
233
+ @tint_transform,
234
+ ])
235
+ end
236
+ register_as
237
+ end
238
+
239
+ # Pattern color space — for tiling and shading patterns.
240
+ class Pattern < ColorSpace
241
+ attr_reader :base
242
+
243
+ def initialize(base: nil)
244
+ @base = base
245
+ end
246
+
247
+ class << self
248
+ def pdf_name; :Pattern; end
249
+ end
250
+
251
+ def to_pdf
252
+ @base ? Pdfrb::Model::PdfArray.new([:Pattern, @base]) : :Pattern
253
+ end
254
+ register_as
255
+ end
256
+ end
257
+ end
258
+
259
+ # rubocop:enable Lint/MissingSuper
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Color
5
+ # ICC profile stream model. An ICCBased color space is a
6
+ # [/ICCBased <stream-ref>] array where the stream carries the
7
+ # raw ICC profile bytes and the dictionary declares /N (number
8
+ # of components), /Alternate (fallback space), and /Range.
9
+ #
10
+ # This class parses the ICC header to extract metadata (version,
11
+ # device class, color space, component count) and builds the
12
+ # PDF stream dictionary.
13
+ #
14
+ # ICC spec: ISO 15076-1 / ICC.1:2010. PDF integration: ISO 32000-2 §8.6.5.5.
15
+ class ICCProfile
16
+ HEADER_SIZE = 128
17
+ SIGNATURE_OFFSET = 36
18
+
19
+ attr_reader :raw_data, :version, :device_class, :color_space,
20
+ :component_count, :alternate
21
+
22
+ def initialize(raw_data, alternate: nil)
23
+ @raw_data = raw_data.b
24
+ parse_header
25
+ @alternate = alternate || derive_alternate
26
+ end
27
+
28
+ def self.read(path)
29
+ new(File.binread(path))
30
+ end
31
+
32
+ def pdf_color_space_array
33
+ [:ICCBased, :__icc_stream_ref__]
34
+ end
35
+
36
+ def stream_dictionary_fields
37
+ {
38
+ N: @component_count,
39
+ Alternate: @alternate,
40
+ }.freeze
41
+ end
42
+
43
+ def valid?
44
+ @raw_data.bytesize >= HEADER_SIZE && signature_valid?
45
+ end
46
+
47
+ private
48
+
49
+ def parse_header
50
+ return unless @raw_data.bytesize >= HEADER_SIZE
51
+
52
+ major = @raw_data.getbyte(8)
53
+ minor_bugfix = @raw_data.getbyte(9)
54
+ @version = format("%d.%d.%d", major, (minor_bugfix >> 4) & 0x0F, minor_bugfix & 0x0F)
55
+ @device_class = read_str(12, 4)
56
+ @color_space = read_str(16, 4)
57
+ @component_count = component_count_for(@color_space)
58
+ end
59
+
60
+ def signature_valid?
61
+ read_str(SIGNATURE_OFFSET, 4) == "acsp"
62
+ end
63
+
64
+ def derive_alternate
65
+ case @color_space
66
+ when "CMYK" then :DeviceCMYK
67
+ when "GRAY" then :DeviceGray
68
+ when "LAB " then :Lab
69
+ else :DeviceRGB # RGB and unknown fall back to DeviceRGB
70
+ end
71
+ end
72
+
73
+ def component_count_for(cs)
74
+ case cs
75
+ when "CMYK" then 4
76
+ when "GRAY" then 1
77
+ else 3 # RGB, LAB, and unknown all use 3 components
78
+ end
79
+ end
80
+
81
+ def read_u32(offset)
82
+ @raw_data.byteslice(offset, 4)&.unpack1("N")
83
+ end
84
+
85
+ def read_str(offset, len)
86
+ @raw_data.byteslice(offset, len)&.force_encoding(Encoding::US_ASCII) || ""
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ # Color spaces and color management. Provides domain types for
5
+ # device color spaces (DeviceGray/RGB/CMYK), CIE-based spaces
6
+ # (CalGray, CalRGB, Lab, ICCBased), and special spaces (Indexed,
7
+ # Separation, DeviceN, Pattern).
8
+ module Color
9
+ autoload :ICCProfile, "pdfrb/color/icc_profile"
10
+ autoload :ColorSpace, "pdfrb/color/color_space"
11
+ end
12
+ end
@@ -0,0 +1,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Compare
5
+ # Compares two Pdfrb::Documents at the structural level.
6
+ # Walks pages, extracts text, inventories fonts/images,
7
+ # inspects outlines and metadata. Produces a Report.
8
+ class Comparator
9
+ def compare(left, right)
10
+ Report.new(
11
+ page_count_delta: page_count_delta(left, right),
12
+ per_page_text_diffs: per_page_text_diffs(left, right),
13
+ font_diff: font_diff(left, right),
14
+ image_count_delta: image_count_delta(left, right),
15
+ outline_diff: outline_diff(left, right),
16
+ metadata_diff: metadata_diff(left, right),
17
+ structural_diffs: structural_diffs(left, right)
18
+ )
19
+ end
20
+
21
+ private
22
+
23
+ def page_count_delta(left, right)
24
+ right.pages.count - left.pages.count
25
+ end
26
+
27
+ def per_page_text_diffs(left, right)
28
+ left_text = extract_text_per_page(left)
29
+ right_text = extract_text_per_page(right)
30
+
31
+ max = [left_text.length, right_text.length].max
32
+ (0...max).filter_map do |i|
33
+ lt = left_text[i] || ""
34
+ rt = right_text[i] || ""
35
+ sim = text_similarity(lt, rt)
36
+ next nil if sim >= 1.0 && lt == rt
37
+
38
+ {
39
+ page: i,
40
+ similarity: sim,
41
+ left_length: lt.length,
42
+ right_length: rt.length,
43
+ left_missing: chars_only_in(rt, lt),
44
+ right_missing: chars_only_in(lt, rt),
45
+ }
46
+ end
47
+ end
48
+
49
+ def extract_text_per_page(doc)
50
+ Pdfrb::Task::ExtractText.call(doc)
51
+ rescue StandardError
52
+ []
53
+ end
54
+
55
+ def text_similarity(a, b)
56
+ return 1.0 if a == b
57
+ return 0.0 if a.empty? || b.empty?
58
+
59
+ max_len = [a.length, b.length].max
60
+ lev = levenshtein(a, b)
61
+ 1.0 - (lev.to_f / max_len)
62
+ end
63
+
64
+ def levenshtein(a, b)
65
+ m = a.length
66
+ n = b.length
67
+ return n if m.zero?
68
+ return m if n.zero?
69
+
70
+ d = Array.new(m + 1) { |i| [i] + Array.new(n, 0) }
71
+ d[0] = (0..n).to_a
72
+
73
+ (1..m).each do |i|
74
+ (1..n).each do |j|
75
+ cost = a[i - 1] == b[j - 1] ? 0 : 1
76
+ d[i][j] = [d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost].min
77
+ end
78
+ end
79
+ d[m][n]
80
+ end
81
+
82
+ def chars_only_in(a, b)
83
+ (a.chars.to_a - b.chars.to_a).uniq.first(20).join
84
+ end
85
+
86
+ def font_diff(left, right)
87
+ left_fonts = inventory_fonts(left)
88
+ right_fonts = inventory_fonts(right)
89
+
90
+ {
91
+ added: (right_fonts - left_fonts).sort,
92
+ removed: (left_fonts - right_fonts).sort,
93
+ common: (left_fonts & right_fonts).sort,
94
+ }
95
+ end
96
+
97
+ def inventory_fonts(doc)
98
+ fonts = Set.new
99
+ doc.each_indirect_object do |obj|
100
+ next unless obj.is_a?(Pdfrb::Model::Cos::Dictionary)
101
+ next unless obj[:Type] == :Font
102
+
103
+ name = obj[:BaseFont] || obj[:Name] || "Unknown"
104
+ subtype = obj[:Subtype] || "Unknown"
105
+ fonts << "#{subtype}:#{name}"
106
+ end
107
+ fonts.to_a
108
+ rescue StandardError
109
+ []
110
+ end
111
+
112
+ def image_count_delta(left, right)
113
+ right_count = count_images(right)
114
+ left_count = count_images(left)
115
+ right_count - left_count
116
+ end
117
+
118
+ def count_images(doc)
119
+ count = 0
120
+ doc.each_indirect_object do |obj|
121
+ next unless obj.is_a?(Pdfrb::Model::Cos::Stream)
122
+ next unless obj[:Subtype] == :Image
123
+
124
+ count += 1
125
+ end
126
+ count
127
+ rescue StandardError
128
+ 0
129
+ end
130
+
131
+ def outline_diff(left, right)
132
+ left_titles = outline_titles(left)
133
+ right_titles = outline_titles(right)
134
+
135
+ {
136
+ added: (right_titles - left_titles),
137
+ removed: (left_titles - right_titles),
138
+ common: (left_titles & right_titles),
139
+ }
140
+ end
141
+
142
+ def outline_titles(doc)
143
+ titles = Set.new
144
+ outlines_ref = doc.catalog&.value&.dig(:Outlines)
145
+ return titles.to_a unless outlines_ref
146
+
147
+ outlines = doc.object(outlines_ref)
148
+ return titles.to_a unless outlines
149
+
150
+ walk_outline(outlines, doc) { |title| titles << title }
151
+ titles.to_a
152
+ rescue StandardError
153
+ []
154
+ end
155
+
156
+ def walk_outline(outlines, doc, &)
157
+ current = outlines[:First]
158
+ while current
159
+ entry = doc.object(current)
160
+ break unless entry
161
+
162
+ title = entry[:Title]
163
+ yield(title.to_s) if title
164
+
165
+ if entry[:First]
166
+ child = doc.object(entry[:First])
167
+ walk_outline(child, doc, &) if child
168
+ end
169
+
170
+ current = entry[:Next]
171
+ end
172
+ end
173
+
174
+ def metadata_diff(left, right)
175
+ left_info = extract_info(left)
176
+ right_info = extract_info(right)
177
+
178
+ diff = {}
179
+ (left_info.keys | right_info.keys).each do |key|
180
+ lv = left_info[key]
181
+ rv = right_info[key]
182
+ next if lv == rv
183
+
184
+ diff[key] = { left: lv, right: rv }
185
+ end
186
+ diff
187
+ end
188
+
189
+ def extract_info(doc)
190
+ trailer = doc.trailer || {}
191
+ info_ref = trailer[:Info]
192
+ return {} unless info_ref
193
+
194
+ info = doc.object(info_ref)
195
+ return {} unless info
196
+
197
+ result = {}
198
+ info.value.each do |k, v|
199
+ next if k == :Type
200
+
201
+ result[k] = v.to_s
202
+ end
203
+ result
204
+ rescue StandardError
205
+ {}
206
+ end
207
+
208
+ def structural_diffs(left, right)
209
+ diffs = []
210
+
211
+ lc = left.catalog
212
+ rc = right.catalog
213
+ if lc && rc
214
+ left_keys = lc.value.keys.to_set
215
+ right_keys = rc.value.keys.to_set
216
+
217
+ added = right_keys - left_keys
218
+ removed = left_keys - right_keys
219
+ unless added.empty? && removed.empty?
220
+ diffs << {
221
+ location: "catalog",
222
+ keys_added: added.to_a.sort,
223
+ keys_removed: removed.to_a.sort,
224
+ }
225
+ end
226
+ end
227
+
228
+ lt = left.trailer || {}
229
+ rt = right.trailer || {}
230
+ left_tkeys = lt.keys.to_set - [:Size, :Root, :Prev]
231
+ right_tkeys = rt.keys.to_set - [:Size, :Root, :Prev]
232
+ unless (left_tkeys - right_tkeys).empty? && (right_tkeys - left_tkeys).empty?
233
+ diffs << {
234
+ location: "trailer",
235
+ keys_added: (right_tkeys - left_tkeys).to_a.sort,
236
+ keys_removed: (left_tkeys - right_tkeys).to_a.sort,
237
+ }
238
+ end
239
+
240
+ diffs
241
+ end
242
+ end
243
+ end
244
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Compare
5
+ # Result of comparing two PDFs. Immutable value object.
6
+ class Report
7
+ attr_reader :page_count_delta, :per_page_text_diffs,
8
+ :font_diff, :image_count_delta,
9
+ :outline_diff, :metadata_diff, :structural_diffs
10
+
11
+ def initialize(page_count_delta:, per_page_text_diffs:, font_diff:,
12
+ image_count_delta:, outline_diff:, metadata_diff:,
13
+ structural_diffs:)
14
+ @page_count_delta = page_count_delta
15
+ @per_page_text_diffs = per_page_text_diffs.freeze
16
+ @font_diff = font_diff.freeze
17
+ @image_count_delta = image_count_delta
18
+ @outline_diff = outline_diff.freeze
19
+ @metadata_diff = metadata_diff.freeze
20
+ @structural_diffs = structural_diffs.freeze
21
+ freeze
22
+ end
23
+
24
+ def equivalent?
25
+ return false unless @page_count_delta.zero?
26
+ return false unless @per_page_text_diffs.empty?
27
+ return false unless @font_diff[:added].empty? && @font_diff[:removed].empty?
28
+ return false unless @image_count_delta.zero?
29
+ return false unless @structural_diffs.empty?
30
+
31
+ true
32
+ end
33
+
34
+ def similarity
35
+ total_checks = 0
36
+ passing = 0
37
+
38
+ total_checks += 1
39
+ passing += 1 if @page_count_delta.zero?
40
+
41
+ total_checks += @per_page_text_diffs.length + 1
42
+ passing += @per_page_text_diffs.count { |d| d[:similarity] > 0.95 } + 1
43
+
44
+ total_checks += 1
45
+ passing += 1 if @font_diff[:added].empty? && @font_diff[:removed].empty?
46
+
47
+ total_checks += 1
48
+ passing += 1 if @image_count_delta.zero?
49
+
50
+ total_checks + 1
51
+ passing + (@structural_diffs.empty? ? 1 : 0)
52
+ (passing.to_f / (total_checks + 1)).round(4)
53
+ end
54
+
55
+ def to_h
56
+ {
57
+ equivalent: equivalent?,
58
+ similarity: similarity,
59
+ page_count_delta: @page_count_delta,
60
+ per_page_text_diffs: @per_page_text_diffs,
61
+ font_diff: @font_diff,
62
+ image_count_delta: @image_count_delta,
63
+ outline_diff: @outline_diff,
64
+ metadata_diff: @metadata_diff,
65
+ structural_diffs: @structural_diffs,
66
+ }
67
+ end
68
+
69
+ def summary
70
+ status = equivalent? ? "EQUIVALENT" : "DIFFERENT"
71
+ sim = (similarity * 100).round(1)
72
+ "#{status} (#{sim}% similarity, page_delta=#{@page_count_delta}, " \
73
+ "text_diffs=#{@per_page_text_diffs.length}, " \
74
+ "font_changes=#{@font_diff[:added].length + @font_diff[:removed].length})"
75
+ end
76
+ end
77
+ end
78
+ end