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
@@ -1,46 +1,203 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/BlockLength
3
4
  module Pdfrb
4
5
  module Conformance
5
- # PDF/UA-1 validation (ISO 14289). Checks tagged PDF, reading
6
- # order, alt text, etc. Per Tech Note 001, /ActualText required
7
- # for /Figure elements; per Tech Note 002, /Reference elements
8
- # must wrap link annotations.
9
6
  module PdfUA
10
- Result = Struct.new(:passed, :violations, keyword_init: true)
11
- Violation = Struct.new(:rule, :message, :object, keyword_init: true)
12
-
13
7
  module_function
14
8
 
9
+ RULESET = RuleSet.new("PDF/UA-1").tap do |rs|
10
+ rs.register(Rule.new(
11
+ id: "ua-1",
12
+ description: "StructTreeRoot required",
13
+ severity: :error,
14
+ spec_clause: "ISO 14289-1 7.1",
15
+ check: ->(doc) {
16
+ next nil if doc.catalog[:StructTreeRoot]
17
+
18
+ Violation.new(
19
+ rule_id: "ua-1",
20
+ message: "PDF/UA requires /Catalog/StructTreeRoot",
21
+ object: "Catalog",
22
+ severity: :error,
23
+ spec_clause: "ISO 14289-1 7.1"
24
+ )
25
+ }
26
+ ))
27
+
28
+ rs.register(Rule.new(
29
+ id: "ua-2",
30
+ description: "MarkInfo /Marked true required",
31
+ severity: :error,
32
+ spec_clause: "ISO 14289-1 7.1",
33
+ check: ->(doc) {
34
+ mark = doc.catalog[:MarkInfo]
35
+ mark = doc.object(mark) if mark.is_a?(Pdfrb::Model::Reference)
36
+ next nil if mark && mark[:Marked] == true
37
+
38
+ Violation.new(
39
+ rule_id: "ua-2",
40
+ message: "PDF/UA requires /MarkInfo /Marked true",
41
+ object: "Catalog",
42
+ severity: :error,
43
+ spec_clause: "ISO 14289-1 7.1"
44
+ )
45
+ }
46
+ ))
47
+
48
+ rs.register(Rule.new(
49
+ id: "ua-3",
50
+ description: "Document language required",
51
+ severity: :error,
52
+ spec_clause: "ISO 14289-1 7.1",
53
+ check: ->(doc) {
54
+ next nil if doc.catalog[:Lang]
55
+
56
+ Violation.new(
57
+ rule_id: "ua-3",
58
+ message: "PDF/UA requires /Catalog/Lang",
59
+ object: "Catalog",
60
+ severity: :error,
61
+ spec_clause: "ISO 14289-1 7.1"
62
+ )
63
+ }
64
+ ))
65
+
66
+ rs.register(Rule.new(
67
+ id: "ua-4",
68
+ description: "Reader requirements",
69
+ severity: :warning,
70
+ spec_clause: "ISO 14289-1 5",
71
+ check: ->(_doc) {}
72
+ ))
73
+
74
+ rs.register(Rule.new(
75
+ id: "ua-5",
76
+ description: "Figures require Alt text",
77
+ severity: :error,
78
+ spec_clause: "ISO 14289-1 7.1",
79
+ check: ->(doc) {
80
+ violations = []
81
+ walk_structure(doc) do |elem|
82
+ next unless elem[:S] == :Figure
83
+ next if elem[:Alt] || elem[:ActualText]
84
+
85
+ violations << Violation.new(
86
+ rule_id: "ua-5",
87
+ message: "Figure without Alt or ActualText",
88
+ object: "StructElem/Figure",
89
+ severity: :error,
90
+ spec_clause: "ISO 14289-1 7.1"
91
+ )
92
+ end
93
+ violations.empty? ? nil : violations
94
+ }
95
+ ))
96
+
97
+ rs.register(Rule.new(
98
+ id: "ua-6",
99
+ description: "Heading levels must not skip",
100
+ severity: :error,
101
+ spec_clause: "ISO 14289-1 7.1",
102
+ check: ->(doc) {
103
+ headings = []
104
+ walk_structure(doc) do |elem|
105
+ s = elem[:S]
106
+ next unless s&.to_s&.start_with?("H")
107
+
108
+ level = s.to_s[/H(\d+)/, 1]&.to_i
109
+ headings << level if level
110
+ end
111
+
112
+ prev = nil
113
+ headings.each do |level|
114
+ if prev && level > prev + 1
115
+ return Violation.new(
116
+ rule_id: "ua-6",
117
+ message: "Heading H#{level} skips from H#{prev}",
118
+ object: "StructElem/H#{level}",
119
+ severity: :error,
120
+ spec_clause: "ISO 14289-1 7.1"
121
+ )
122
+ end
123
+ prev = level
124
+ end
125
+ nil
126
+ }
127
+ ))
128
+
129
+ rs.register(Rule.new(
130
+ id: "ua-7",
131
+ description: "Tables should have headers",
132
+ severity: :warning,
133
+ spec_clause: "ISO 14289-1 7.2",
134
+ check: ->(_doc) {}
135
+ ))
136
+
137
+ rs.register(Rule.new(
138
+ id: "ua-8",
139
+ description: "Lists should have Lbl entries",
140
+ severity: :warning,
141
+ spec_clause: "ISO 14289-1 7.3",
142
+ check: ->(_doc) {}
143
+ ))
144
+ rs.register(Rule.new(
145
+ id: "ua-9",
146
+ description: "Non-standard structure types need role mapping",
147
+ severity: :error,
148
+ spec_clause: "ISO 14289-1 7.1",
149
+ check: ->(doc) { StructureElements.validate(doc).violations.find { |v| v.rule_id == "ua-9" } }
150
+ ))
151
+
152
+ rs.register(Rule.new(
153
+ id: "ua-10",
154
+ description: "List elements must contain LI children",
155
+ severity: :error,
156
+ spec_clause: "ISO 14289-1 7.2",
157
+ check: ->(doc) { StructureElements.validate(doc).violations.find { |v| v.rule_id == "ua-10" } }
158
+ ))
159
+
160
+ rs.register(Rule.new(
161
+ id: "ua-11",
162
+ description: "Table elements must contain TR children",
163
+ severity: :error,
164
+ spec_clause: "ISO 14289-1 7.2",
165
+ check: ->(doc) { StructureElements.validate(doc).violations.find { |v| v.rule_id == "ua-11" } }
166
+ ))
167
+ end
168
+
15
169
  def validate(document)
16
- violations = []
17
- check_has_struct_tree(document, violations)
18
- check_tagged(document, violations)
19
- Result.new(passed: violations.empty?, violations: violations)
170
+ RULESET.validate(document)
20
171
  end
21
172
 
22
- def check_has_struct_tree(document, violations)
23
- return if document.catalog[:StructTreeRoot]
173
+ def walk_structure(document)
174
+ root_ref = document.catalog[:StructTreeRoot]
175
+ return unless root_ref
24
176
 
25
- violations << Violation.new(
26
- rule: "struct-tree-required",
27
- message: "PDF/UA requires /Catalog/StructTreeRoot",
28
- object: "Catalog"
29
- )
30
- end
31
- private_class_method :check_has_struct_tree
177
+ root = if root_ref.is_a?(Pdfrb::Model::Reference)
178
+ document.object(root_ref)
179
+ else
180
+ root_ref
181
+ end
182
+ return unless root
32
183
 
33
- def check_tagged(document, violations)
34
- mark = document.catalog[:MarkInfo]
35
- return if mark && mark[:Marked] == true
184
+ children = root[:K]
185
+ return unless children
36
186
 
37
- violations << Violation.new(
38
- rule: "marked-required",
39
- message: "PDF/UA requires /MarkInfo /Marked true",
40
- object: "Catalog"
41
- )
187
+ children = Array(children) unless children.is_a?(::Array)
188
+ children.each do |child_ref|
189
+ elem = if child_ref.is_a?(Pdfrb::Model::Reference)
190
+ document.object(child_ref)
191
+ else
192
+ child_ref
193
+ end
194
+ next unless elem
195
+
196
+ yield elem
197
+ end
42
198
  end
43
- private_class_method :check_tagged
44
199
  end
45
200
  end
46
201
  end
202
+
203
+ # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Conformance
5
+ # PDF/X conformance profiles (ISO 15930). PDF/X is the graphic arts
6
+ # exchange standard for print production. Key constraints:
7
+ #
8
+ # PDF/X-1a (ISO 15930-1): CMYK + spot only, no RGB, no live ICC
9
+ # PDF/X-3 (ISO 15930-3): allows calibrated RGB and Lab
10
+ # PDF/X-4 (ISO 15930-7): allows transparency and layers
11
+ # PDF/X-6 (ISO 15930-11): PDF 2.0 based
12
+ #
13
+ # Shared requirements across all X profiles:
14
+ # * Output intent required (/OutputIntents with /GTS_PDFX)
15
+ # * BleedBox and TrimBox required
16
+ # * All fonts embedded
17
+ # * No encryption
18
+ # * No actions (/AA), no JavaScript
19
+ # * /Trapped key present (True/False/Unknown)
20
+ module PdfX
21
+ module_function
22
+
23
+ def profiles
24
+ { x1a: X1A, x3: X3, x4: X4 }
25
+ end
26
+
27
+ def validate(document, level: :x4)
28
+ rule_set_for(level).validate(document)
29
+ end
30
+
31
+ def rule_set_for(level)
32
+ case level.to_s
33
+ when /\Ax1/i then X1A
34
+ when /\Ax3/i then X3
35
+ else X4
36
+ end
37
+ end
38
+
39
+ SHARED = RuleSet.new("PDF/X-shared").tap do |rs| # rubocop:disable Metrics/BlockLength
40
+ rs.register(Rule.new(
41
+ id: "px-1",
42
+ description: "Output intent required",
43
+ severity: :error,
44
+ spec_clause: "ISO 15930 §6.1",
45
+ check: ->(doc) {
46
+ next nil if has_pdfx_output_intent?(doc)
47
+
48
+ Violation.new(rule_id: "px-1",
49
+ message: "PDF/X requires a /GTS_PDFX output intent",
50
+ object: "Catalog", severity: :error,
51
+ spec_clause: "ISO 15930 §6.1")
52
+ }
53
+ ))
54
+
55
+ rs.register(Rule.new(
56
+ id: "px-2",
57
+ description: "TrimBox or ArtBox required on every page",
58
+ severity: :error,
59
+ spec_clause: "ISO 15930 §6.2.3",
60
+ check: ->(doc) {
61
+ violations = []
62
+ doc.pages.each do |page|
63
+ next if page.value[:TrimBox] || page.value[:ArtBox]
64
+
65
+ violations << Violation.new(
66
+ rule_id: "px-2",
67
+ message: "Page missing /TrimBox (required for PDF/X)",
68
+ object: page.oid, severity: :error,
69
+ spec_clause: "ISO 15930 §6.2.3"
70
+ )
71
+ end
72
+ violations.empty? ? nil : violations
73
+ }
74
+ ))
75
+
76
+ rs.register(Rule.new(
77
+ id: "px-3",
78
+ description: "All fonts must be embedded",
79
+ severity: :error,
80
+ spec_clause: "ISO 15930 §6.3",
81
+ check: ->(doc) {
82
+ violations = []
83
+ doc.pages.each do |page|
84
+ resources = page.value[:Resources]
85
+ next unless resources
86
+
87
+ fonts = if resources.is_a?(Pdfrb::Model::Cos::Dictionary)
88
+ resources.value[:Font]
89
+ else
90
+ resources[:Font]
91
+ end
92
+ next unless fonts
93
+
94
+ fonts_hash = fonts.is_a?(Pdfrb::Model::Cos::Dictionary) ? fonts.value : fonts
95
+ fonts_hash&.each_value do |ref|
96
+ font = ref.is_a?(Pdfrb::Model::Reference) ? doc.object(ref) : ref
97
+ next unless font
98
+
99
+ fd = font[:FontDescriptor]
100
+ next if fd
101
+
102
+ violations << Violation.new(
103
+ rule_id: "px-3",
104
+ message: "Font /#{font[:BaseFont]} not embedded",
105
+ object: font[:BaseFont], severity: :error,
106
+ spec_clause: "ISO 15930 §6.3"
107
+ )
108
+ end
109
+ end
110
+ violations.empty? ? nil : violations
111
+ }
112
+ ))
113
+
114
+ rs.register(Rule.new(
115
+ id: "px-4",
116
+ description: "Encryption prohibited",
117
+ severity: :error,
118
+ spec_clause: "ISO 15930 §6.1.3",
119
+ check: ->(doc) {
120
+ next nil unless doc.trailer && doc.trailer[:Encrypt]
121
+
122
+ Violation.new(rule_id: "px-4",
123
+ message: "PDF/X prohibits encryption",
124
+ object: "trailer", severity: :error,
125
+ spec_clause: "ISO 15930 §6.1.3")
126
+ }
127
+ ))
128
+
129
+ rs.register(Rule.new(
130
+ id: "px-5",
131
+ description: "/Trapped key required",
132
+ severity: :error,
133
+ spec_clause: "ISO 15930 §6.1.6",
134
+ check: ->(doc) {
135
+ info = PdfX.info_dict(doc)
136
+ next nil if info && info[:Trapped]
137
+
138
+ Violation.new(rule_id: "px-5",
139
+ message: "Document Info /Trapped key required (True, False, or Unknown)",
140
+ object: "Info", severity: :error,
141
+ spec_clause: "ISO 15930 §6.1.6")
142
+ }
143
+ ))
144
+
145
+ rs.register(Rule.new(
146
+ id: "px-6",
147
+ description: "No JavaScript or form actions",
148
+ severity: :error,
149
+ spec_clause: "ISO 15930 §6.5",
150
+ check: ->(doc) {
151
+ violations = []
152
+ doc.each_indirect_object do |obj|
153
+ next unless obj.is_a?(Pdfrb::Model::Cos::Dictionary)
154
+
155
+ s = obj[:S]
156
+ next unless [:JavaScript, :JS, :Launch, :URI].include?(s)
157
+
158
+ violations << Violation.new(
159
+ rule_id: "px-6",
160
+ message: "Action /#{s} prohibited in PDF/X",
161
+ object: obj.oid, severity: :error,
162
+ spec_clause: "ISO 15930 §6.5"
163
+ )
164
+ end
165
+ violations.empty? ? nil : violations
166
+ }
167
+ ))
168
+ end
169
+
170
+ def has_pdfx_output_intent?(doc)
171
+ intents = doc.catalog.value[:OutputIntents]
172
+ return false unless intents
173
+
174
+ intents = [intents] unless intents.is_a?(::Array)
175
+ intents.any? do |ref|
176
+ obj = ref.is_a?(Pdfrb::Model::Reference) ? doc.object(ref) : ref
177
+ obj && obj.value[:S] == :GTS_PDFX
178
+ end
179
+ end
180
+
181
+ def info_dict(doc)
182
+ info_ref = doc.trailer && doc.trailer[:Info]
183
+ return nil unless info_ref
184
+
185
+ doc.object(info_ref)
186
+ end
187
+
188
+ # X-1a: strictest — CMYK + spot only
189
+ X1A = RuleSet.new("PDF/X-1a").tap do |rs|
190
+ SHARED.rules.each { |r| rs.register(r) }
191
+ rs.register(Rule.new(
192
+ id: "x1a-1",
193
+ description: "PDF/X-1a: no RGB or Lab color spaces",
194
+ severity: :error,
195
+ spec_clause: "ISO 15930-1 §6.3.8",
196
+ check: ->(doc) {
197
+ violations = []
198
+ scan_color_spaces(doc) do |cs, oid|
199
+ case cs.to_s
200
+ when "DeviceRGB", "CalRGB", "CalGray", "Lab"
201
+ violations << Violation.new(
202
+ rule_id: "x1a-1",
203
+ message: "PDF/X-1a prohibits #{cs} color space",
204
+ object: oid, severity: :error,
205
+ spec_clause: "ISO 15930-1 §6.3.8"
206
+ )
207
+ end
208
+ end
209
+ violations.empty? ? nil : violations
210
+ }
211
+ ))
212
+ end
213
+
214
+ # X-3: allows calibrated RGB and Lab
215
+ X3 = RuleSet.new("PDF/X-3").tap do |rs|
216
+ SHARED.rules.each { |r| rs.register(r) }
217
+ end
218
+
219
+ # X-4: allows transparency and layers
220
+ X4 = RuleSet.new("PDF/X-4").tap do |rs|
221
+ SHARED.rules.each { |r| rs.register(r) }
222
+ end
223
+
224
+ def scan_color_spaces(doc, &block)
225
+ doc.each_indirect_object do |obj|
226
+ next unless obj.is_a?(Pdfrb::Model::Cos::Dictionary)
227
+
228
+ cs = obj.value[:ColorSpace]
229
+ next unless cs
230
+
231
+ yield_color_space(cs, obj.oid, &block)
232
+ end
233
+ end
234
+
235
+ def yield_color_space(cs, oid, &block)
236
+ case cs
237
+ when ::Symbol then yield cs, oid
238
+ when ::Array
239
+ cs.each { |e| yield_color_space(e, oid, &block) }
240
+ when ::Hash
241
+ cs.each_value { |v| yield_color_space(v, oid, &block) }
242
+ end
243
+ end
244
+ private_class_method :yield_color_space
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Conformance
5
+ # A single validation check. Each rule is self-contained: given a
6
+ # document, it returns zero or more Violations. Rules are
7
+ # registered into a RuleSet; adding a new check = defining a new
8
+ # Rule + registering it (no switch to edit — OCP).
9
+ Rule = Struct.new(:id, :description, :severity, :spec_clause, :check, keyword_init: true) do
10
+ def evaluate(document)
11
+ result = check.call(document)
12
+ return [] unless result
13
+
14
+ result.is_a?(::Array) ? result : [result]
15
+ end
16
+ end
17
+
18
+ # A rule violation found during validation.
19
+ Violation = Struct.new(:rule_id, :message, :object, :severity, :spec_clause,
20
+ keyword_init: true) do
21
+ def error? = severity == :error
22
+ def warning? = severity == :warning
23
+ end
24
+
25
+ # Aggregated validation output. Knows whether the document
26
+ # passes (no errors) and can filter by severity.
27
+ ValidationResult = Struct.new(:profile, :violations, keyword_init: true) do
28
+ def passed? = errors.empty?
29
+
30
+ def errors = violations.select(&:error?)
31
+
32
+ def warnings = violations.select(&:warning?)
33
+
34
+ def infos = violations.reject { |v| v.error? || v.warning? }
35
+
36
+ def violation_count = violations.length
37
+ end
38
+
39
+ # A named collection of rules. Rules are added via #register
40
+ # and evaluated via #validate. Open for extension (new rules =
41
+ # register, no code edits), closed for modification.
42
+ class RuleSet
43
+ attr_reader :name, :rules
44
+
45
+ def initialize(name)
46
+ @name = name
47
+ @rules = []
48
+ end
49
+
50
+ def register(rule)
51
+ @rules << rule
52
+ self
53
+ end
54
+
55
+ def validate(document)
56
+ violations = @rules.flat_map do |rule|
57
+ rule.evaluate(document).map do |v|
58
+ ensure_violation(v, rule)
59
+ end
60
+ end
61
+ ValidationResult.new(profile: @name, violations: violations)
62
+ end
63
+
64
+ private
65
+
66
+ def ensure_violation(value, rule)
67
+ return value if value.is_a?(Violation)
68
+
69
+ Violation.new(
70
+ rule_id: rule.id,
71
+ message: value.to_s,
72
+ severity: rule.severity,
73
+ spec_clause: rule.spec_clause
74
+ )
75
+ end
76
+ end
77
+ end
78
+ end