pdfrb 0.2.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (406) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +24 -0
  4. data/Gemfile +1 -0
  5. data/PROPOSAL.canvas-clip.md +92 -0
  6. data/PROPOSAL.canvas-draw-image-matrix.md +87 -0
  7. data/PROPOSAL.canvas-rich-text.md +103 -0
  8. data/PROPOSAL.canvas-text-lines.md +77 -0
  9. data/PROPOSAL.canvas-transparency.md +106 -0
  10. data/PROPOSAL.external-font-metrics.md +129 -0
  11. data/PROPOSAL.font-embedding-fontfile2.md +88 -0
  12. data/PROPOSAL.font-resolver-ttc.md +105 -0
  13. data/PROPOSAL.font-resolver.md +96 -0
  14. data/PROPOSAL.font-subsetting.md +98 -0
  15. data/PROPOSAL.gradient-shadings.md +110 -0
  16. data/PROPOSAL.page-mediabox-setter.md +66 -0
  17. data/PROPOSAL.per-glyph-width.md +91 -0
  18. data/PROPOSAL.stringio-require.md +53 -0
  19. data/PROPOSAL.struct-tree-root.md +102 -0
  20. data/PROPOSAL.text-measurement.md +93 -0
  21. data/PROPOSAL.ttf-table-parsing-fix.md +112 -0
  22. data/RELEASE.md +48 -0
  23. data/lib/pdfrb/action/base.rb +21 -0
  24. data/lib/pdfrb/action/types.rb +178 -0
  25. data/lib/pdfrb/action.rb +55 -0
  26. data/lib/pdfrb/annotation/base.rb +79 -0
  27. data/lib/pdfrb/annotation/types.rb +222 -0
  28. data/lib/pdfrb/annotation.rb +78 -0
  29. data/lib/pdfrb/appearance/generator.rb +216 -0
  30. data/lib/pdfrb/appearance.rb +11 -0
  31. data/lib/pdfrb/arlington/object_definition.rb +2 -2
  32. data/lib/pdfrb/arlington/predicate/evaluator.rb +1 -1
  33. data/lib/pdfrb/arlington/predicate/functions.rb +25 -9
  34. data/lib/pdfrb/arlington/predicate.rb +1 -5
  35. data/lib/pdfrb/cli/batch.rb +18 -0
  36. data/lib/pdfrb/cli/debug_info.rb +18 -0
  37. data/lib/pdfrb/cli/files.rb +16 -0
  38. data/lib/pdfrb/cli/fonts.rb +16 -0
  39. data/lib/pdfrb/cli/form.rb +18 -0
  40. data/lib/pdfrb/cli/image2pdf.rb +18 -0
  41. data/lib/pdfrb/cli/images.rb +34 -0
  42. data/lib/pdfrb/cli/info.rb +20 -0
  43. data/lib/pdfrb/cli/inspect.rb +18 -0
  44. data/lib/pdfrb/cli/merge.rb +18 -0
  45. data/lib/pdfrb/cli/modify.rb +18 -0
  46. data/lib/pdfrb/cli/optimize.rb +18 -0
  47. data/lib/pdfrb/cli/split.rb +21 -0
  48. data/lib/pdfrb/cli/watermark.rb +18 -0
  49. data/lib/pdfrb/cli.rb +312 -7
  50. data/lib/pdfrb/color/color_space.rb +259 -0
  51. data/lib/pdfrb/color/icc_profile.rb +90 -0
  52. data/lib/pdfrb/color.rb +12 -0
  53. data/lib/pdfrb/compare/comparator.rb +244 -0
  54. data/lib/pdfrb/compare/report.rb +78 -0
  55. data/lib/pdfrb/compare.rb +42 -0
  56. data/lib/pdfrb/composer.rb +161 -0
  57. data/lib/pdfrb/conformance/pdf_a.rb +317 -56
  58. data/lib/pdfrb/conformance/pdf_ua.rb +186 -29
  59. data/lib/pdfrb/conformance/pdf_x.rb +247 -0
  60. data/lib/pdfrb/conformance/rule.rb +78 -0
  61. data/lib/pdfrb/conformance/structure_elements.rb +174 -0
  62. data/lib/pdfrb/conformance/verapdf_bridge.rb +92 -0
  63. data/lib/pdfrb/conformance.rb +7 -3
  64. data/lib/pdfrb/content/canvas.rb +217 -72
  65. data/lib/pdfrb/content/color_space.rb +157 -0
  66. data/lib/pdfrb/content/hidden_text_detector.rb +167 -0
  67. data/lib/pdfrb/content/operator.rb +21 -1
  68. data/lib/pdfrb/content/operators/clipping.rb +36 -0
  69. data/lib/pdfrb/content/operators/graphics_state_params.rb +15 -0
  70. data/lib/pdfrb/content/operators.rb +21 -0
  71. data/lib/pdfrb/content/shading/base.rb +139 -0
  72. data/lib/pdfrb/content/shading.rb +17 -0
  73. data/lib/pdfrb/content/smart_text_extractor.rb +85 -0
  74. data/lib/pdfrb/content/tiling_pattern.rb +78 -0
  75. data/lib/pdfrb/content/transformation_matrix.rb +61 -0
  76. data/lib/pdfrb/content.rb +7 -26
  77. data/lib/pdfrb/destination/fit.rb +185 -0
  78. data/lib/pdfrb/destination.rb +13 -0
  79. data/lib/pdfrb/digital_signature/cms_handler.rb +22 -0
  80. data/lib/pdfrb/digital_signature/signing.rb +107 -11
  81. data/lib/pdfrb/digital_signature/timestamp_handler.rb +45 -0
  82. data/lib/pdfrb/digital_signature/verification.rb +88 -9
  83. data/lib/pdfrb/digital_signature/verification_result.rb +14 -0
  84. data/lib/pdfrb/digital_signature.rb +26 -2
  85. data/lib/pdfrb/document/annotations.rb +120 -3
  86. data/lib/pdfrb/document/associated_files.rb +104 -0
  87. data/lib/pdfrb/document/colors.rb +95 -0
  88. data/lib/pdfrb/document/destinations.rb +127 -5
  89. data/lib/pdfrb/document/display.rb +84 -0
  90. data/lib/pdfrb/document/fonts.rb +417 -45
  91. data/lib/pdfrb/document/form.rb +338 -0
  92. data/lib/pdfrb/document/form_xobject.rb +68 -0
  93. data/lib/pdfrb/document/graphics_state.rb +91 -0
  94. data/lib/pdfrb/document/images.rb +4 -4
  95. data/lib/pdfrb/document/info.rb +102 -0
  96. data/lib/pdfrb/document/layers.rb +62 -0
  97. data/lib/pdfrb/document/metadata.rb +12 -5
  98. data/lib/pdfrb/document/output_intents.rb +91 -0
  99. data/lib/pdfrb/document/page_labels.rb +80 -0
  100. data/lib/pdfrb/document/pages.rb +68 -19
  101. data/lib/pdfrb/document/portfolio.rb +119 -0
  102. data/lib/pdfrb/document/shadings.rb +159 -0
  103. data/lib/pdfrb/document/stamps.rb +68 -0
  104. data/lib/pdfrb/document/structure.rb +242 -0
  105. data/lib/pdfrb/document.rb +73 -4
  106. data/lib/pdfrb/encryption/identity.rb +13 -0
  107. data/lib/pdfrb/encryption/password_verification.rb +53 -8
  108. data/lib/pdfrb/encryption/security_handler.rb +8 -5
  109. data/lib/pdfrb/encryption/standard_security_handler.rb +154 -81
  110. data/lib/pdfrb/encryption.rb +17 -0
  111. data/lib/pdfrb/error.rb +2 -2
  112. data/lib/pdfrb/filter/brotli_decode.rb +32 -0
  113. data/lib/pdfrb/filter/ccitt_fax_decode.rb +12 -0
  114. data/lib/pdfrb/filter/crypt.rb +2 -2
  115. data/lib/pdfrb/filter/dct_decode.rb +18 -0
  116. data/lib/pdfrb/filter/flate_decode.rb +4 -18
  117. data/lib/pdfrb/filter/jbig2_decode.rb +12 -0
  118. data/lib/pdfrb/filter/jpx_decode.rb +18 -0
  119. data/lib/pdfrb/filter/lzw_decode.rb +3 -2
  120. data/lib/pdfrb/filter/png_predictor.rb +94 -0
  121. data/lib/pdfrb/filter.rb +26 -13
  122. data/lib/pdfrb/font/afm_parser.rb +50 -26
  123. data/lib/pdfrb/font/cmap/writer.rb +9 -0
  124. data/lib/pdfrb/font/encoding/win_ansi_encoding.rb +17 -0
  125. data/lib/pdfrb/font/encoding.rb +32 -3
  126. data/lib/pdfrb/font/metrics_helper.rb +121 -0
  127. data/lib/pdfrb/font/true_type/file.rb +10 -1
  128. data/lib/pdfrb/font/true_type/glyf.rb +82 -0
  129. data/lib/pdfrb/font/true_type/kern.rb +58 -0
  130. data/lib/pdfrb/font/true_type/loca.rb +42 -0
  131. data/lib/pdfrb/font/true_type/maxp.rb +44 -0
  132. data/lib/pdfrb/font/true_type/name.rb +53 -0
  133. data/lib/pdfrb/font/true_type/post.rb +93 -0
  134. data/lib/pdfrb/font/true_type/subsetter.rb +3 -5
  135. data/lib/pdfrb/font/true_type/wrapper.rb +102 -0
  136. data/lib/pdfrb/font/true_type.rb +7 -0
  137. data/lib/pdfrb/font.rb +2 -0
  138. data/lib/pdfrb/font_loader/from_configuration.rb +19 -0
  139. data/lib/pdfrb/font_loader/from_file.rb +20 -0
  140. data/lib/pdfrb/font_loader/standard14.rb +20 -0
  141. data/lib/pdfrb/font_loader/variant_from_name.rb +36 -0
  142. data/lib/pdfrb/font_loader.rb +4 -0
  143. data/lib/pdfrb/font_resolver.rb +138 -0
  144. data/lib/pdfrb/image/audit.rb +94 -0
  145. data/lib/pdfrb/image/downsampler.rb +124 -0
  146. data/lib/pdfrb/image.rb +13 -0
  147. data/lib/pdfrb/image_loader/jpeg.rb +39 -58
  148. data/lib/pdfrb/image_loader/png.rb +31 -257
  149. data/lib/pdfrb/layout/bidi.rb +198 -0
  150. data/lib/pdfrb/layout/box.rb +49 -0
  151. data/lib/pdfrb/layout/box_fitter.rb +54 -0
  152. data/lib/pdfrb/layout/column_box.rb +71 -0
  153. data/lib/pdfrb/layout/container_box.rb +45 -0
  154. data/lib/pdfrb/layout/frame.rb +67 -0
  155. data/lib/pdfrb/layout/image_box.rb +44 -0
  156. data/lib/pdfrb/layout/inline_box.rb +25 -0
  157. data/lib/pdfrb/layout/line.rb +55 -0
  158. data/lib/pdfrb/layout/list_box.rb +87 -0
  159. data/lib/pdfrb/layout/numeric_refinements.rb +43 -0
  160. data/lib/pdfrb/layout/page_style.rb +65 -0
  161. data/lib/pdfrb/layout/style.rb +62 -0
  162. data/lib/pdfrb/layout/table_box.rb +66 -0
  163. data/lib/pdfrb/layout/text_box.rb +38 -0
  164. data/lib/pdfrb/layout/text_fragment.rb +45 -0
  165. data/lib/pdfrb/layout/text_layouter.rb +123 -0
  166. data/lib/pdfrb/layout.rb +28 -0
  167. data/lib/pdfrb/linearization/hint_stream.rb +93 -0
  168. data/lib/pdfrb/linearization/writer.rb +259 -0
  169. data/lib/pdfrb/linearization.rb +21 -0
  170. data/lib/pdfrb/model/cos/dictionary.rb +12 -0
  171. data/lib/pdfrb/model/pdf_array.rb +2 -2
  172. data/lib/pdfrb/model/type/action.rb +42 -0
  173. data/lib/pdfrb/model/type/action_go_to_3d_view.rb +14 -0
  174. data/lib/pdfrb/model/type/action_go_to_dp.rb +16 -0
  175. data/lib/pdfrb/model/type/action_go_to_r.rb +26 -0
  176. data/lib/pdfrb/model/type/action_goto.rb +34 -0
  177. data/lib/pdfrb/model/type/action_hide.rb +26 -0
  178. data/lib/pdfrb/model/type/action_import_data.rb +18 -0
  179. data/lib/pdfrb/model/type/action_java_script.rb +18 -0
  180. data/lib/pdfrb/model/type/action_launch.rb +22 -0
  181. data/lib/pdfrb/model/type/action_movie.rb +22 -0
  182. data/lib/pdfrb/model/type/action_named.rb +23 -0
  183. data/lib/pdfrb/model/type/action_nop.rb +12 -0
  184. data/lib/pdfrb/model/type/action_rendition.rb +28 -0
  185. data/lib/pdfrb/model/type/action_reset_form.rb +22 -0
  186. data/lib/pdfrb/model/type/action_rich_media_execute.rb +17 -0
  187. data/lib/pdfrb/model/type/action_set_ocg_state.rb +32 -0
  188. data/lib/pdfrb/model/type/action_set_state.rb +14 -0
  189. data/lib/pdfrb/model/type/action_sound_action.rb +23 -0
  190. data/lib/pdfrb/model/type/action_submit_form.rb +64 -0
  191. data/lib/pdfrb/model/type/action_thread.rb +23 -0
  192. data/lib/pdfrb/model/type/action_trans.rb +30 -0
  193. data/lib/pdfrb/model/type/action_uri.rb +30 -0
  194. data/lib/pdfrb/model/type/af_embedded_file.rb +11 -0
  195. data/lib/pdfrb/model/type/af_file_specification.rb +34 -0
  196. data/lib/pdfrb/model/type/annotation.rb +49 -10
  197. data/lib/pdfrb/model/type/appearance.rb +41 -0
  198. data/lib/pdfrb/model/type/appearance_characteristics.rb +36 -0
  199. data/lib/pdfrb/model/type/appearance_generator.rb +22 -0
  200. data/lib/pdfrb/model/type/appearance_trap_net.rb +84 -0
  201. data/lib/pdfrb/model/type/border_effect.rb +25 -0
  202. data/lib/pdfrb/model/type/border_styling.rb +38 -0
  203. data/lib/pdfrb/model/type/box_color_info.rb +21 -0
  204. data/lib/pdfrb/model/type/box_style.rb +19 -0
  205. data/lib/pdfrb/model/type/button.rb +17 -0
  206. data/lib/pdfrb/model/type/cal_gray.rb +17 -0
  207. data/lib/pdfrb/model/type/cal_rgb.rb +22 -0
  208. data/lib/pdfrb/model/type/caret_annotation.rb +21 -0
  209. data/lib/pdfrb/model/type/catalog.rb +99 -16
  210. data/lib/pdfrb/model/type/choice.rb +21 -0
  211. data/lib/pdfrb/model/type/cid_font.rb +52 -1
  212. data/lib/pdfrb/model/type/cid_font_descriptor_metrics.rb +28 -0
  213. data/lib/pdfrb/model/type/cid_system_info.rb +28 -0
  214. data/lib/pdfrb/model/type/circle_annotation.rb +20 -0
  215. data/lib/pdfrb/model/type/collection.rb +27 -0
  216. data/lib/pdfrb/model/type/collection_field.rb +24 -0
  217. data/lib/pdfrb/model/type/collection_item.rb +19 -0
  218. data/lib/pdfrb/model/type/collection_schema.rb +25 -0
  219. data/lib/pdfrb/model/type/collection_sort.rb +13 -0
  220. data/lib/pdfrb/model/type/crypt_filter.rb +58 -0
  221. data/lib/pdfrb/model/type/device_n.rb +37 -0
  222. data/lib/pdfrb/model/type/device_n_mixing_hints.rb +17 -0
  223. data/lib/pdfrb/model/type/device_n_process.rb +13 -0
  224. data/lib/pdfrb/model/type/doc_mdp_transform_parameters.rb +21 -0
  225. data/lib/pdfrb/model/type/document_security_store.rb +55 -0
  226. data/lib/pdfrb/model/type/embedded_file.rb +19 -0
  227. data/lib/pdfrb/model/type/embedded_file_parameter.rb +48 -0
  228. data/lib/pdfrb/model/type/encryption_public_key.rb +38 -0
  229. data/lib/pdfrb/model/type/encryption_standard.rb +68 -0
  230. data/lib/pdfrb/model/type/field_mdp_transform_parameters.rb +26 -0
  231. data/lib/pdfrb/model/type/file_attachment_annotation.rb +29 -0
  232. data/lib/pdfrb/model/type/file_spec_ef.rb +22 -0
  233. data/lib/pdfrb/model/type/file_spec_rf.rb +21 -0
  234. data/lib/pdfrb/model/type/file_specification.rb +43 -0
  235. data/lib/pdfrb/model/type/file_trailer.rb +54 -0
  236. data/lib/pdfrb/model/type/font.rb +73 -2
  237. data/lib/pdfrb/model/type/font_cid_type0.rb +17 -0
  238. data/lib/pdfrb/model/type/font_cid_type2.rb +17 -0
  239. data/lib/pdfrb/model/type/font_descriptor.rb +47 -0
  240. data/lib/pdfrb/model/type/font_descriptor_cid_type0.rb +15 -0
  241. data/lib/pdfrb/model/type/font_descriptor_cid_type2.rb +15 -0
  242. data/lib/pdfrb/model/type/font_encoding.rb +47 -0
  243. data/lib/pdfrb/model/type/font_file.rb +28 -0
  244. data/lib/pdfrb/model/type/font_file2.rb +32 -0
  245. data/lib/pdfrb/model/type/font_file3.rb +23 -0
  246. data/lib/pdfrb/model/type/font_multiple_master.rb +60 -0
  247. data/lib/pdfrb/model/type/font_true_type.rb +1 -1
  248. data/lib/pdfrb/model/type/font_type0.rb +1 -1
  249. data/lib/pdfrb/model/type/font_type1.rb +1 -1
  250. data/lib/pdfrb/model/type/font_type3.rb +1 -1
  251. data/lib/pdfrb/model/type/form_field.rb +73 -0
  252. data/lib/pdfrb/model/type/free_text_annotation.rb +37 -0
  253. data/lib/pdfrb/model/type/function.rb +42 -0
  254. data/lib/pdfrb/model/type/function_exponential.rb +31 -0
  255. data/lib/pdfrb/model/type/function_postscript.rb +25 -0
  256. data/lib/pdfrb/model/type/function_sampled.rb +55 -0
  257. data/lib/pdfrb/model/type/function_stitching.rb +33 -0
  258. data/lib/pdfrb/model/type/generic_appearance.rb +40 -0
  259. data/lib/pdfrb/model/type/graphics_state_parameter.rb +69 -0
  260. data/lib/pdfrb/model/type/halftone.rb +21 -0
  261. data/lib/pdfrb/model/type/halftone_type1.rb +17 -0
  262. data/lib/pdfrb/model/type/halftone_type10.rb +16 -0
  263. data/lib/pdfrb/model/type/halftone_type16.rb +11 -0
  264. data/lib/pdfrb/model/type/halftone_type5.rb +24 -0
  265. data/lib/pdfrb/model/type/halftone_type6.rb +16 -0
  266. data/lib/pdfrb/model/type/highlight_annotation.rb +11 -0
  267. data/lib/pdfrb/model/type/icc_based_color_space.rb +24 -0
  268. data/lib/pdfrb/model/type/icon_fit.rb +31 -0
  269. data/lib/pdfrb/model/type/indexed.rb +32 -0
  270. data/lib/pdfrb/model/type/info.rb +44 -0
  271. data/lib/pdfrb/model/type/ink_annotation.rb +29 -0
  272. data/lib/pdfrb/model/type/interactive_form.rb +36 -0
  273. data/lib/pdfrb/model/type/interior_color.rb +12 -0
  274. data/lib/pdfrb/model/type/jbig2_globals.rb +15 -0
  275. data/lib/pdfrb/model/type/lab.rb +27 -0
  276. data/lib/pdfrb/model/type/line_annotation.rb +44 -0
  277. data/lib/pdfrb/model/type/line_ending_styling.rb +39 -0
  278. data/lib/pdfrb/model/type/link_annotation.rb +41 -0
  279. data/lib/pdfrb/model/type/link_appearance.rb +9 -0
  280. data/lib/pdfrb/model/type/mark_information.rb +23 -0
  281. data/lib/pdfrb/model/type/marked_content_reference.rb +24 -0
  282. data/lib/pdfrb/model/type/markup_annotation.rb +36 -0
  283. data/lib/pdfrb/model/type/mdp_dict.rb +28 -0
  284. data/lib/pdfrb/model/type/measure.rb +39 -0
  285. data/lib/pdfrb/model/type/media_clip.rb +28 -0
  286. data/lib/pdfrb/model/type/media_offset.rb +106 -0
  287. data/lib/pdfrb/model/type/metadata.rb +27 -3
  288. data/lib/pdfrb/model/type/misc_helpers.rb +75 -0
  289. data/lib/pdfrb/model/type/movie.rb +28 -0
  290. data/lib/pdfrb/model/type/names.rb +51 -0
  291. data/lib/pdfrb/model/type/namespace.rb +27 -0
  292. data/lib/pdfrb/model/type/object_reference.rb +31 -0
  293. data/lib/pdfrb/model/type/object_stream.rb +38 -0
  294. data/lib/pdfrb/model/type/opt_content_ext.rb +94 -0
  295. data/lib/pdfrb/model/type/opt_content_page_element.rb +18 -0
  296. data/lib/pdfrb/model/type/optional_content_config.rb +56 -0
  297. data/lib/pdfrb/model/type/optional_content_group.rb +35 -10
  298. data/lib/pdfrb/model/type/optional_content_membership.rb +36 -2
  299. data/lib/pdfrb/model/type/optional_content_properties.rb +16 -1
  300. data/lib/pdfrb/model/type/outline.rb +40 -6
  301. data/lib/pdfrb/model/type/outline_item.rb +45 -0
  302. data/lib/pdfrb/model/type/output_intent.rb +30 -0
  303. data/lib/pdfrb/model/type/page.rb +127 -0
  304. data/lib/pdfrb/model/type/page_label.rb +68 -0
  305. data/lib/pdfrb/model/type/page_tree_node.rb +51 -16
  306. data/lib/pdfrb/model/type/page_tree_node_root.rb +15 -0
  307. data/lib/pdfrb/model/type/pattern.rb +16 -0
  308. data/lib/pdfrb/model/type/pattern_shading.rb +25 -0
  309. data/lib/pdfrb/model/type/pattern_tiling.rb +25 -0
  310. data/lib/pdfrb/model/type/polygon_annotation.rb +29 -0
  311. data/lib/pdfrb/model/type/polygon_polyline.rb +14 -0
  312. data/lib/pdfrb/model/type/polyline_annotation.rb +29 -0
  313. data/lib/pdfrb/model/type/popup_annotation.rb +20 -0
  314. data/lib/pdfrb/model/type/printer_mark_annotation.rb +13 -0
  315. data/lib/pdfrb/model/type/redact_annotation.rb +28 -0
  316. data/lib/pdfrb/model/type/rendition.rb +31 -0
  317. data/lib/pdfrb/model/type/resources.rb +59 -14
  318. data/lib/pdfrb/model/type/rich_media_activation.rb +24 -0
  319. data/lib/pdfrb/model/type/rich_media_animation.rb +25 -0
  320. data/lib/pdfrb/model/type/rich_media_annotation.rb +18 -0
  321. data/lib/pdfrb/model/type/rich_media_command.rb +19 -0
  322. data/lib/pdfrb/model/type/rich_media_configuration.rb +32 -0
  323. data/lib/pdfrb/model/type/rich_media_content.rb +28 -0
  324. data/lib/pdfrb/model/type/rich_media_cue_point.rb +18 -0
  325. data/lib/pdfrb/model/type/rich_media_deactivation.rb +17 -0
  326. data/lib/pdfrb/model/type/rich_media_instance.rb +33 -0
  327. data/lib/pdfrb/model/type/rich_media_presentation.rb +29 -0
  328. data/lib/pdfrb/model/type/rich_media_settings.rb +33 -0
  329. data/lib/pdfrb/model/type/rich_media_window.rb +20 -0
  330. data/lib/pdfrb/model/type/screen_annotation.rb +19 -0
  331. data/lib/pdfrb/model/type/separation.rb +25 -0
  332. data/lib/pdfrb/model/type/sig_field_lock.rb +25 -0
  333. data/lib/pdfrb/model/type/sig_field_seed_value.rb +25 -0
  334. data/lib/pdfrb/model/type/signature.rb +65 -0
  335. data/lib/pdfrb/model/type/signature_build.rb +86 -0
  336. data/lib/pdfrb/model/type/signature_field.rb +17 -0
  337. data/lib/pdfrb/model/type/software_identifier.rb +19 -0
  338. data/lib/pdfrb/model/type/sound.rb +38 -0
  339. data/lib/pdfrb/model/type/sound_annotation.rb +21 -0
  340. data/lib/pdfrb/model/type/square_annotation.rb +24 -0
  341. data/lib/pdfrb/model/type/square_circle.rb +13 -0
  342. data/lib/pdfrb/model/type/squiggly_annotation.rb +11 -0
  343. data/lib/pdfrb/model/type/stamp_annotation.rb +19 -0
  344. data/lib/pdfrb/model/type/strikeout_annotation.rb +11 -0
  345. data/lib/pdfrb/model/type/struct_elem.rb +75 -0
  346. data/lib/pdfrb/model/type/struct_tree_root.rb +34 -0
  347. data/lib/pdfrb/model/type/structure_attributes.rb +44 -0
  348. data/lib/pdfrb/model/type/structure_element_kid.rb +33 -0
  349. data/lib/pdfrb/model/type/text_annotation.rb +25 -0
  350. data/lib/pdfrb/model/type/text_appearance.rb +9 -0
  351. data/lib/pdfrb/model/type/text_field.rb +21 -0
  352. data/lib/pdfrb/model/type/text_markup_annotation.rb +30 -0
  353. data/lib/pdfrb/model/type/three_d_activation.rb +32 -0
  354. data/lib/pdfrb/model/type/three_d_animation_style.rb +24 -0
  355. data/lib/pdfrb/model/type/three_d_background.rb +28 -0
  356. data/lib/pdfrb/model/type/three_d_cross_section.rb +25 -0
  357. data/lib/pdfrb/model/type/three_d_lighting_scheme.rb +26 -0
  358. data/lib/pdfrb/model/type/three_d_measure.rb +20 -0
  359. data/lib/pdfrb/model/type/three_d_measure_3dc.rb +23 -0
  360. data/lib/pdfrb/model/type/three_d_measure_ad3.rb +14 -0
  361. data/lib/pdfrb/model/type/three_d_measure_ld3.rb +14 -0
  362. data/lib/pdfrb/model/type/three_d_measure_pd3.rb +20 -0
  363. data/lib/pdfrb/model/type/three_d_measure_rd3.rb +14 -0
  364. data/lib/pdfrb/model/type/three_d_node.rb +37 -0
  365. data/lib/pdfrb/model/type/three_d_reference.rb +21 -0
  366. data/lib/pdfrb/model/type/three_d_render_mode.rb +34 -0
  367. data/lib/pdfrb/model/type/three_d_stream.rb +48 -0
  368. data/lib/pdfrb/model/type/three_d_units.rb +18 -0
  369. data/lib/pdfrb/model/type/three_d_view.rb +40 -0
  370. data/lib/pdfrb/model/type/to_unicode_cmap_stream.rb +23 -0
  371. data/lib/pdfrb/model/type/transition.rb +68 -0
  372. data/lib/pdfrb/model/type/underline_annotation.rb +11 -0
  373. data/lib/pdfrb/model/type/ur_transform_parameters.rb +33 -0
  374. data/lib/pdfrb/model/type/uri_dict.rb +18 -0
  375. data/lib/pdfrb/model/type/url_alias.rb +19 -0
  376. data/lib/pdfrb/model/type/variable_text_field.rb +47 -0
  377. data/lib/pdfrb/model/type/viewer_preferences.rb +42 -0
  378. data/lib/pdfrb/model/type/watermark_annotation.rb +13 -0
  379. data/lib/pdfrb/model/type/widget_annotation.rb +48 -0
  380. data/lib/pdfrb/model/type/widget_appearance.rb +9 -0
  381. data/lib/pdfrb/model/type/xobject_form.rb +41 -0
  382. data/lib/pdfrb/model/type/xobject_image.rb +56 -0
  383. data/lib/pdfrb/model/type/xref_stream.rb +37 -0
  384. data/lib/pdfrb/model/type.rb +286 -6
  385. data/lib/pdfrb/serializer.rb +2 -2
  386. data/lib/pdfrb/source/tokenizer.rb +0 -2
  387. data/lib/pdfrb/task/benchmark.rb +83 -0
  388. data/lib/pdfrb/task/extract_text.rb +1 -1
  389. data/lib/pdfrb/task/generate_corpus.rb +130 -0
  390. data/lib/pdfrb/task/memory_profile.rb +84 -0
  391. data/lib/pdfrb/task/optimize.rb +97 -7
  392. data/lib/pdfrb/task.rb +3 -3
  393. data/lib/pdfrb/test_utils.rb +112 -0
  394. data/lib/pdfrb/validator.rb +82 -0
  395. data/lib/pdfrb/version.rb +1 -1
  396. data/lib/pdfrb/writer.rb +117 -27
  397. data/lib/pdfrb/xmp/packet.rb +122 -0
  398. data/lib/pdfrb/xmp/schemas.rb +96 -0
  399. data/lib/pdfrb/xmp.rb +44 -0
  400. data/lib/pdfrb/xref_section.rb +2 -2
  401. data/lib/pdfrb.rb +25 -53
  402. data/pdfrb.gemspec +45 -0
  403. data/script/apply_fixes.sh +843 -0
  404. data/script/make_jpeg_fixture.rb +53 -0
  405. data/script/make_png_fixture.rb +25 -0
  406. metadata +312 -2
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ # Semantic PDF comparison. Compares two PDFs at the structural level:
5
+ # page count, per-page extracted text, font inventory, image count,
6
+ # outline structure, and metadata. Returns a structured report.
7
+ #
8
+ # Designed for regression testing: "did our render change from
9
+ # yesterday's reference?" Not a pixel diff — that's a separate
10
+ # concern (rasterize + image compare).
11
+ module Compare
12
+ autoload :Report, "pdfrb/compare/report"
13
+ autoload :Comparator, "pdfrb/compare/comparator"
14
+
15
+ module_function
16
+
17
+ # Compare two PDFs. Accepts bytes, IO, or Document objects.
18
+ #
19
+ # @param left [String, IO, Pdfrb::Document]
20
+ # @param right [String, IO, Pdfrb::Document]
21
+ # @return [Pdfrb::Compare::Report]
22
+ def compare(left, right)
23
+ left_doc = to_document(left)
24
+ right_doc = to_document(right)
25
+ Comparator.new.compare(left_doc, right_doc)
26
+ end
27
+
28
+ # Convenience: returns true if the two PDFs are equivalent.
29
+ def equivalent?(left, right)
30
+ compare(left, right).equivalent?
31
+ end
32
+
33
+ def to_document(input)
34
+ case input
35
+ when Pdfrb::Document then input
36
+ when ::String then Pdfrb::Document.new(io: StringIO.new(input))
37
+ when IO, StringIO then Pdfrb::Document.new(io: input)
38
+ else raise ArgumentError, "expected String, IO, or Document"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ # High-level document composition API. Wraps a +Document+ and a
5
+ # +Layout::BoxFitter+, exposing a DSL for adding text, images, and
6
+ # boxes with automatic page flow.
7
+ #
8
+ # Usage:
9
+ # Pdfrb::Composer.new("out.pdf") do |c|
10
+ # c.style(:heading, font_size: 24)
11
+ # c.text("Hello world", style: :heading)
12
+ # c.image("/path/to/logo.png", width: 200)
13
+ # c.text("Body text flows naturally.")
14
+ # end
15
+ class Composer
16
+ DEFAULT_PAGE_STYLE = :default
17
+
18
+ attr_reader :document, :page_styles, :current_page, :current_style_name
19
+
20
+ def initialize(output = nil, page_size: :A4, page_orientation: :portrait,
21
+ margin: 36, skip_page_creation: false)
22
+ @document = Pdfrb::Document.new
23
+ @page_styles = {}
24
+ @output_target = output
25
+ @pending_boxes = []
26
+
27
+ page_style(DEFAULT_PAGE_STYLE, page_size: page_size,
28
+ orientation: page_orientation,
29
+ margin: margin)
30
+ @current_style_name = DEFAULT_PAGE_STYLE
31
+ new_page unless skip_page_creation
32
+ end
33
+
34
+ # Class-level shortcut: create a document, yield to block, write.
35
+ def self.create(output, **)
36
+ composer = new(output, **)
37
+ yield composer
38
+ composer.write
39
+ end
40
+
41
+ # Define a named text style.
42
+ def style(name, base: :base, **properties)
43
+ @styles ||= {}
44
+ @styles[name.to_sym] = Layout::Style.new(base, **properties)
45
+ end
46
+
47
+ def style?(name)
48
+ @styles&.key?(name.to_sym)
49
+ end
50
+
51
+ # Define a named page style.
52
+ def page_style(name, **attrs, &)
53
+ @page_styles[name.to_sym] = Layout::PageStyle.new(name: name, **attrs, &)
54
+ end
55
+
56
+ # Create a new page using the named (or current) page style.
57
+ def new_page(style_name = nil)
58
+ @current_style_name = style_name || next_style_name
59
+ style_obj = @page_styles[@current_style_name]
60
+ page = @document.pages.add
61
+ page.value[:MediaBox] = [0, 0, style_obj.width, style_obj.height]
62
+ @current_page = page
63
+ @pending_boxes = []
64
+ page
65
+ end
66
+
67
+ def x
68
+ @cursor_x || 50
69
+ end
70
+
71
+ def y
72
+ @cursor_y || (@current_page ? 750 : 0)
73
+ end
74
+
75
+ # Add text using a style.
76
+ def text(str, width: 0, height: 0, style: nil, **style_properties)
77
+ resolved_style = resolve_style(style, style_properties)
78
+ box = Layout::TextBox.new(text: str, width: width, height: height,
79
+ style: resolved_style)
80
+ draw_box_on_current_page(box)
81
+ end
82
+
83
+ # Add formatted text (array of {text:, style:} runs).
84
+ def formatted_text(data, **opts)
85
+ data.each { |run| text(run[:text], **opts, **run.fetch(:style, {})) }
86
+ end
87
+
88
+ # Add an image from a file path.
89
+ def image(path, width: 0, height: 0, **opts)
90
+ box = Layout::ImageBox.new(path: path, document: @document,
91
+ width: width, height: height)
92
+ draw_box_on_current_page(box)
93
+ end
94
+
95
+ # Add an arbitrary box.
96
+ def box(box_class, **, &)
97
+ box = box_class.new(**, &)
98
+ draw_box_on_current_page(box)
99
+ end
100
+
101
+ # Write the document.
102
+ def write(output = @output_target, optimize: false, **opts)
103
+ flush_pending_boxes
104
+ target = output
105
+ if target.is_a?(String)
106
+ @document.write(target)
107
+ else
108
+ @document.write(io: target)
109
+ end
110
+ end
111
+
112
+ def write_to_string(optimize: false, **)
113
+ require "stringio"
114
+ io = StringIO.new
115
+ write(io, optimize: optimize, **)
116
+ io.string
117
+ end
118
+
119
+ private
120
+
121
+ def next_style_name
122
+ current = @page_styles[@current_style_name]
123
+ current&.next_style || @current_style_name
124
+ end
125
+
126
+ def resolve_style(name, properties)
127
+ if name.is_a?(Layout::Style)
128
+ properties.empty? ? name : name.update(**properties)
129
+ elsif @styles && @styles[name.to_sym]
130
+ base = @styles[name.to_sym]
131
+ properties.empty? ? base : base.update(**properties)
132
+ else
133
+ Layout::Style.new(properties.empty? ? :base : properties)
134
+ end
135
+ end
136
+
137
+ def draw_box_on_current_page(box)
138
+ @pending_boxes << box
139
+ end
140
+
141
+ def flush_pending_boxes
142
+ return if @pending_boxes.empty?
143
+
144
+ style_obj = @page_styles[@current_style_name]
145
+ frame = style_obj.frame
146
+ cursor_y = frame.top
147
+
148
+ @pending_boxes.each do |box|
149
+ unless box.fit?(frame.width, cursor_y - frame.bottom)
150
+ new_page
151
+ cursor_y = @page_styles[@current_style_name].frame.top
152
+ box.fit?(frame.width, cursor_y - frame.bottom)
153
+ end
154
+ canvas = @current_page.canvas
155
+ box.draw(canvas, frame.left, cursor_y)
156
+ cursor_y -= box.height
157
+ end
158
+ @pending_boxes = []
159
+ end
160
+ end
161
+ end
@@ -1,74 +1,335 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/BlockLength
3
4
  module Pdfrb
4
5
  module Conformance
5
- # PDF/A-1/2/3 validation (ISO 19005). Checks a document against
6
- # the subset constraints. Returns a +Result+ with per-rule verdicts.
7
6
  module PdfA
8
- Result = Struct.new(:level, :passed, :violations, keyword_init: true)
9
- Violation = Struct.new(:rule, :message, :object, keyword_init: true)
10
-
11
7
  module_function
12
8
 
13
- def validate(document, level: :a2b)
14
- violations = []
15
- check_no_encryption(document, violations)
16
- check_has_metadata(document, violations)
17
- check_embedded_fonts(document, violations)
18
- Result.new(
19
- level: level,
20
- passed: violations.empty?,
21
- violations: violations
22
- )
9
+ SHARED = RuleSet.new("PDF/A-shared").tap do |rs|
10
+ rs.register(Rule.new(
11
+ id: "6.1-1", description: "Encryption prohibited",
12
+ severity: :error, spec_clause: "ISO 19005-1 6.1",
13
+ check: ->(doc) {
14
+ next nil unless doc.trailer && doc.trailer[:Encrypt]
15
+
16
+ Violation.new(rule_id: "6.1-1", message: "PDF/A prohibits encryption",
17
+ object: "trailer", severity: :error, spec_clause: "ISO 19005-1 6.1")
18
+ }
19
+ ))
20
+ rs.register(Rule.new(
21
+ id: "6.1-2", description: "XMP Metadata required",
22
+ severity: :error, spec_clause: "ISO 19005-1 6.1",
23
+ check: ->(doc) {
24
+ next nil if doc.catalog[:Metadata]
25
+
26
+ Violation.new(rule_id: "6.1-2", message: "PDF/A requires /Catalog/Metadata XMP stream",
27
+ object: "Catalog", severity: :error, spec_clause: "ISO 19005-1 6.1")
28
+ }
29
+ ))
30
+ rs.register(Rule.new(
31
+ id: "6.1-4", description: "JavaScript prohibited",
32
+ severity: :error, spec_clause: "ISO 19005-1 6.1",
33
+ check: ->(doc) {
34
+ doc.each_indirect_object do |obj|
35
+ next unless obj.is_a?(Pdfrb::Model::Cos::Dictionary)
36
+ if obj.value[:S] == :JavaScript
37
+ return Violation.new(rule_id: "6.1-4", message: "PDF/A prohibits JavaScript actions",
38
+ object: "JavaScript action", severity: :error,
39
+ spec_clause: "ISO 19005-1 6.1")
40
+ end
41
+ end
42
+ nil
43
+ }
44
+ ))
45
+ rs.register(Rule.new(
46
+ id: "6.1-7", description: "Language recommended",
47
+ severity: :warning, spec_clause: "ISO 19005-1 6.1",
48
+ check: ->(doc) {
49
+ next nil if doc.catalog[:Lang]
50
+
51
+ Violation.new(rule_id: "6.1-7", message: "PDF/A recommends /Catalog/Lang",
52
+ object: "Catalog", severity: :warning, spec_clause: "ISO 19005-1 6.1")
53
+ }
54
+ ))
55
+ rs.register(Rule.new(
56
+ id: "embedded-fonts", description: "All fonts must be embedded",
57
+ severity: :error, spec_clause: "ISO 19005-1 6.2",
58
+ check: ->(doc) {
59
+ vs = []
60
+ doc.pages.each do |page|
61
+ res = page.value[:Resources]
62
+ next unless res
63
+
64
+ fonts = res.is_a?(Pdfrb::Model::Cos::Dictionary) ? res.value[:Font] : res[:Font]
65
+ next unless fonts
66
+
67
+ fonts.each_value do |ref|
68
+ f = ref.is_a?(Pdfrb::Model::Reference) ? doc.object(ref) : ref
69
+ next unless f
70
+ next if f[:FontDescriptor]
71
+
72
+ vs << Violation.new(rule_id: "embedded-fonts",
73
+ message: "Font /#{f[:BaseFont]} missing FontDescriptor",
74
+ object: f[:BaseFont]&.to_s, severity: :error,
75
+ spec_clause: "ISO 19005-1 6.2")
76
+ end
77
+ end
78
+ vs.empty? ? nil : vs
79
+ }
80
+ ))
81
+ end
82
+
83
+ A1_SPECIFIC = RuleSet.new("PDF/A-1-specific").tap do |rs|
84
+ rs.register(Rule.new(
85
+ id: "a1-1", description: "JPEG2000 not allowed in A-1",
86
+ severity: :error, spec_clause: "ISO 19005-1 6.2.4",
87
+ check: ->(doc) {
88
+ doc.each_indirect_object do |obj|
89
+ next unless obj.is_a?(Pdfrb::Model::Cos::Stream)
90
+ next unless obj[:Filter] == :JPXDecode
91
+
92
+ return Violation.new(rule_id: "a1-1",
93
+ message: "JPEG2000 not allowed in PDF/A-1",
94
+ object: "XObject", severity: :error,
95
+ spec_clause: "ISO 19005-1 6.2.4")
96
+ end
97
+ nil
98
+ }
99
+ ))
100
+ rs.register(Rule.new(
101
+ id: "a1-2", description: "Object streams not allowed in A-1",
102
+ severity: :error, spec_clause: "ISO 19005-1 6.2.4",
103
+ check: ->(doc) {
104
+ doc.each_indirect_object do |obj|
105
+ next unless obj.is_a?(Pdfrb::Model::Cos::Dictionary)
106
+ next unless obj.value[:Type] == :ObjStm
107
+
108
+ return Violation.new(rule_id: "a1-2",
109
+ message: "Object streams not allowed in PDF/A-1",
110
+ object: "ObjStm", severity: :error,
111
+ spec_clause: "ISO 19005-1 6.2.4")
112
+ end
113
+ nil
114
+ }
115
+ ))
116
+ rs.register(Rule.new(
117
+ id: "a1-3", description: "PDF version must not exceed 1.4",
118
+ severity: :error, spec_clause: "ISO 19005-1 6.1",
119
+ check: ->(doc) {
120
+ parts = doc.version.to_s.split(".")
121
+ major = parts[0].to_i
122
+ minor = (parts[1] || "0").to_i
123
+ next nil if major < 1 || (major == 1 && minor <= 4)
124
+
125
+ Violation.new(rule_id: "a1-3",
126
+ message: "PDF/A-1 requires version <= 1.4, got #{doc.version}",
127
+ object: "header", severity: :error, spec_clause: "ISO 19005-1 6.1")
128
+ }
129
+ ))
130
+ rs.register(Rule.new(
131
+ id: "a1-4", description: "LZWDecode not allowed in A-1",
132
+ severity: :error, spec_clause: "ISO 19005-1 6.2.4",
133
+ check: ->(doc) {
134
+ doc.each_indirect_object do |obj|
135
+ next unless obj.is_a?(Pdfrb::Model::Cos::Stream)
136
+
137
+ f = obj[:Filter]
138
+ fs = f.is_a?(::Array) ? f : [f]
139
+ next unless fs.include?(:LZWDecode)
140
+
141
+ return Violation.new(rule_id: "a1-4",
142
+ message: "LZWDecode not allowed in PDF/A-1",
143
+ object: "Stream", severity: :error,
144
+ spec_clause: "ISO 19005-1 6.2.4")
145
+ end
146
+ nil
147
+ }
148
+ ))
149
+ end
150
+
151
+ A2_SPECIFIC = RuleSet.new("PDF/A-2-specific").tap do |rs|
152
+ rs.register(Rule.new(
153
+ id: "a2-1", description: "PostScript XObjects prohibited",
154
+ severity: :warning, spec_clause: "ISO 19005-2 6.2.4",
155
+ check: ->(doc) {
156
+ doc.each_indirect_object do |obj|
157
+ next unless obj.is_a?(Pdfrb::Model::Cos::Stream)
158
+ next unless [:PS, :PSXObject].include?(obj[:Subtype])
159
+
160
+ return Violation.new(rule_id: "a2-1",
161
+ message: "PostScript XObjects not allowed in PDF/A-2",
162
+ object: "XObject", severity: :warning,
163
+ spec_clause: "ISO 19005-2 6.2.4")
164
+ end
165
+ nil
166
+ }
167
+ ))
168
+ rs.register(Rule.new(
169
+ id: "a2-2", description: "Embedded files not allowed in A-2",
170
+ severity: :error, spec_clause: "ISO 19005-2 6.2",
171
+ check: ->(doc) {
172
+ doc.each_indirect_object do |obj|
173
+ next unless obj.is_a?(Pdfrb::Model::Cos::Dictionary)
174
+ next unless obj.value[:Type] == :EmbeddedFile
175
+
176
+ return Violation.new(rule_id: "a2-2",
177
+ message: "Embedded files not allowed in PDF/A-2 (use PDF/A-3)",
178
+ object: "EmbeddedFile", severity: :error,
179
+ spec_clause: "ISO 19005-2 6.2")
180
+ end
181
+ nil
182
+ }
183
+ ))
184
+ rs.register(Rule.new(
185
+ id: "a2-3", description: "PDF 2.0 not allowed",
186
+ severity: :error, spec_clause: "ISO 19005-2 6.1",
187
+ check: ->(doc) {
188
+ next nil unless doc.version.to_s.start_with?("2.")
189
+
190
+ Violation.new(rule_id: "a2-3",
191
+ message: "PDF/A-2 does not allow PDF 2.0, got #{doc.version}",
192
+ object: "header", severity: :error, spec_clause: "ISO 19005-2 6.1")
193
+ }
194
+ ))
23
195
  end
24
196
 
25
- def check_no_encryption(document, violations)
26
- return unless document.trailer && document.trailer[:Encrypt]
197
+ A3_SPECIFIC = RuleSet.new("PDF/A-3-specific").tap do |rs|
198
+ rs.register(Rule.new(
199
+ id: "a3-1", description: "Filespecs must have AFRelationship",
200
+ severity: :error, spec_clause: "ISO 19005-3 6.2",
201
+ check: ->(doc) {
202
+ vs = []
203
+ doc.each_indirect_object do |obj|
204
+ next unless obj.is_a?(Pdfrb::Model::Cos::Dictionary)
205
+ next unless obj.value[:Type] == :Filespec
206
+ next if obj.value[:AFRelationship]
27
207
 
28
- violations << Violation.new(
29
- rule: "no-encryption",
30
- message: "PDF/A prohibits encryption",
31
- object: "trailer"
32
- )
208
+ vs << Violation.new(rule_id: "a3-1",
209
+ message: "Filespec missing /AFRelationship",
210
+ object: "Filespec", severity: :error,
211
+ spec_clause: "ISO 19005-3 6.2")
212
+ end
213
+ vs.empty? ? nil : vs
214
+ }
215
+ ))
33
216
  end
34
- private_class_method :check_no_encryption
35
217
 
36
- def check_has_metadata(document, violations)
37
- return if document.catalog[:Metadata]
218
+ A4_SPECIFIC = RuleSet.new("PDF/A-4-specific").tap do |rs|
219
+ rs.register(Rule.new(
220
+ id: "a4-1", description: "PDF 2.0 required",
221
+ severity: :error, spec_clause: "ISO 19005-4 6.1",
222
+ check: ->(doc) {
223
+ next nil if doc.version.to_s.start_with?("2.0")
38
224
 
39
- violations << Violation.new(
40
- rule: "metadata-required",
41
- message: "PDF/A requires /Catalog/Metadata XMP stream",
42
- object: "Catalog"
43
- )
225
+ Violation.new(rule_id: "a4-1",
226
+ message: "PDF/A-4 requires PDF 2.0, got #{doc.version}",
227
+ object: "header", severity: :error, spec_clause: "ISO 19005-4 6.1")
228
+ }
229
+ ))
230
+ rs.register(Rule.new(
231
+ id: "a4-2", description: "Filespecs must have AFRelationship",
232
+ severity: :error, spec_clause: "ISO 19005-4 6.2",
233
+ check: ->(doc) {
234
+ vs = []
235
+ doc.each_indirect_object do |obj|
236
+ next unless obj.is_a?(Pdfrb::Model::Cos::Dictionary)
237
+ next unless obj.value[:Type] == :Filespec
238
+ next if obj.value[:AFRelationship]
239
+
240
+ vs << Violation.new(rule_id: "a4-2",
241
+ message: "Filespec missing /AFRelationship",
242
+ object: "Filespec", severity: :error,
243
+ spec_clause: "ISO 19005-4 6.2")
244
+ end
245
+ vs.empty? ? nil : vs
246
+ }
247
+ ))
248
+ rs.register(Rule.new(
249
+ id: "a4-3", description: "Annotations need appearance streams",
250
+ severity: :error, spec_clause: "ISO 19005-4 6.2.3",
251
+ check: ->(doc) {
252
+ vs = []
253
+ doc.each_indirect_object do |annot|
254
+ next unless annot.is_a?(Pdfrb::Model::Cos::Dictionary)
255
+ next unless annot.value[:Type] == :Annot
256
+
257
+ st = annot.value[:Subtype]
258
+ next if st == :Link
259
+
260
+ r = annot.value[:Rect]
261
+ next if r.is_a?(::Array) && r.length == 4 && r[0] == r[2] && r[1] == r[3]
262
+ next if annot.value[:AP]
263
+
264
+ vs << Violation.new(rule_id: "a4-3",
265
+ message: "Annotation /#{st} missing /AP",
266
+ object: "Annot", severity: :error,
267
+ spec_clause: "ISO 19005-4 6.2.3")
268
+ end
269
+ vs.empty? ? nil : vs
270
+ }
271
+ ))
272
+ rs.register(Rule.new(
273
+ id: "a4-4", description: "Trailer /ID recommended",
274
+ severity: :warning, spec_clause: "ISO 19005-4 6.1",
275
+ check: ->(doc) {
276
+ next nil if doc.trailer && doc.trailer[:ID]
277
+
278
+ Violation.new(rule_id: "a4-4", message: "PDF/A-4 recommends /ID in trailer",
279
+ object: "trailer", severity: :warning, spec_clause: "ISO 19005-4 6.1")
280
+ }
281
+ ))
282
+ rs.register(Rule.new(
283
+ id: "a4-5", description: "Metadata XMP recommended",
284
+ severity: :warning, spec_clause: "ISO 19005-4 6.1",
285
+ check: ->(doc) {
286
+ next nil if doc.catalog[:Metadata]
287
+
288
+ Violation.new(rule_id: "a4-5",
289
+ message: "PDF/A-4 recommends /Catalog/Metadata XMP stream",
290
+ object: "Catalog", severity: :warning, spec_clause: "ISO 19005-4 6.1")
291
+ }
292
+ ))
293
+ end
294
+
295
+ A1 = RuleSet.new("PDF/A-1").tap do |rs|
296
+ SHARED.rules.each { |r| rs.register(r) }
297
+ A1_SPECIFIC.rules.each { |r| rs.register(r) }
298
+ end
299
+
300
+ A2 = RuleSet.new("PDF/A-2").tap do |rs|
301
+ SHARED.rules.each { |r| rs.register(r) }
302
+ A2_SPECIFIC.rules.each { |r| rs.register(r) }
44
303
  end
45
- private_class_method :check_has_metadata
46
-
47
- def check_embedded_fonts(document, violations)
48
- document.pages.each do |page|
49
- resources = page.value[:Resources]
50
- next unless resources
51
-
52
- fonts = resources.is_a?(Pdfrb::Model::Cos::Dictionary) ?
53
- resources.value[:Font] : resources[:Font]
54
- next unless fonts
55
-
56
- fonts.each_value do |ref|
57
- font = ref.is_a?(Pdfrb::Model::Reference) ?
58
- document.object(ref) : ref
59
- next unless font
60
-
61
- unless font[:FontDescriptor]
62
- violations << Violation.new(
63
- rule: "embedded-fonts",
64
- message: "Font /#{font[:BaseFont]} missing FontDescriptor",
65
- object: font[:BaseFont]
66
- )
67
- end
68
- end
69
- end
304
+
305
+ A3 = RuleSet.new("PDF/A-3").tap do |rs|
306
+ SHARED.rules.each { |r| rs.register(r) }
307
+ A3_SPECIFIC.rules.each { |r| rs.register(r) }
308
+ end
309
+
310
+ A4 = RuleSet.new("PDF/A-4").tap do |rs|
311
+ SHARED.rules.each { |r| rs.register(r) }
312
+ A4_SPECIFIC.rules.each { |r| rs.register(r) }
313
+ end
314
+
315
+ LEVEL_RULESETS = {
316
+ a1b: A1, a1a: A1,
317
+ a2b: A2, a2a: A2,
318
+ a3b: A3, a3a: A3,
319
+ a4: A4
320
+ }.freeze
321
+
322
+ def profiles
323
+ { a1b: A1, a1a: A1, a2b: A2, a2a: A2,
324
+ a3b: A3, a3a: A3, a4: A4 }
325
+ end
326
+
327
+ def validate(document, level: :a2b)
328
+ rs = LEVEL_RULESETS[level] || SHARED
329
+ rs.validate(document)
70
330
  end
71
- private_class_method :check_embedded_fonts
72
331
  end
73
332
  end
74
333
  end
334
+
335
+ # rubocop:enable Metrics/BlockLength