pdfrb 0.3.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (422) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +51 -0
  4. data/Gemfile +1 -0
  5. data/PROPOSAL.canvas-clip.md +92 -0
  6. data/PROPOSAL.canvas-draw-image-matrix.md +87 -0
  7. data/PROPOSAL.canvas-rich-text.md +103 -0
  8. data/PROPOSAL.canvas-text-lines.md +77 -0
  9. data/PROPOSAL.canvas-transparency.md +106 -0
  10. data/PROPOSAL.external-font-metrics.md +129 -0
  11. data/PROPOSAL.font-embedding-fontfile2.md +88 -0
  12. data/PROPOSAL.font-resolver-ttc.md +105 -0
  13. data/PROPOSAL.font-resolver.md +96 -0
  14. data/PROPOSAL.font-subsetting.md +98 -0
  15. data/PROPOSAL.gradient-shadings.md +110 -0
  16. data/PROPOSAL.page-mediabox-setter.md +66 -0
  17. data/PROPOSAL.per-glyph-width.md +91 -0
  18. data/PROPOSAL.stringio-require.md +53 -0
  19. data/PROPOSAL.struct-tree-root.md +102 -0
  20. data/PROPOSAL.text-measurement.md +93 -0
  21. data/PROPOSAL.ttf-table-parsing-fix.md +112 -0
  22. data/RELEASE.md +61 -0
  23. data/data/pdfrb/afm/Courier-Bold.afm +342 -342
  24. data/data/pdfrb/afm/Courier-BoldOblique.afm +342 -342
  25. data/data/pdfrb/afm/Courier-Oblique.afm +342 -342
  26. data/data/pdfrb/afm/Courier.afm +342 -342
  27. data/data/pdfrb/afm/Helvetica-Bold.afm +2827 -2827
  28. data/data/pdfrb/afm/Helvetica-BoldOblique.afm +2827 -2827
  29. data/data/pdfrb/afm/Helvetica-Oblique.afm +3051 -3051
  30. data/data/pdfrb/afm/Helvetica.afm +3051 -3051
  31. data/data/pdfrb/afm/Symbol.afm +213 -213
  32. data/data/pdfrb/afm/Times-Bold.afm +2588 -2588
  33. data/data/pdfrb/afm/Times-BoldItalic.afm +2384 -2384
  34. data/data/pdfrb/afm/Times-Italic.afm +2667 -2667
  35. data/data/pdfrb/afm/Times-Roman.afm +2419 -2419
  36. data/data/pdfrb/afm/ZapfDingbats.afm +225 -225
  37. data/lib/pdfrb/action/base.rb +21 -0
  38. data/lib/pdfrb/action/types.rb +178 -0
  39. data/lib/pdfrb/action.rb +55 -0
  40. data/lib/pdfrb/annotation/base.rb +79 -0
  41. data/lib/pdfrb/annotation/types.rb +222 -0
  42. data/lib/pdfrb/annotation.rb +78 -0
  43. data/lib/pdfrb/appearance/generator.rb +216 -0
  44. data/lib/pdfrb/appearance.rb +11 -0
  45. data/lib/pdfrb/arlington/object_definition.rb +2 -2
  46. data/lib/pdfrb/arlington/predicate/evaluator.rb +1 -1
  47. data/lib/pdfrb/arlington/predicate/functions.rb +25 -9
  48. data/lib/pdfrb/arlington/predicate.rb +1 -5
  49. data/lib/pdfrb/cli/batch.rb +18 -0
  50. data/lib/pdfrb/cli/debug_info.rb +18 -0
  51. data/lib/pdfrb/cli/files.rb +16 -0
  52. data/lib/pdfrb/cli/fonts.rb +16 -0
  53. data/lib/pdfrb/cli/form.rb +18 -0
  54. data/lib/pdfrb/cli/image2pdf.rb +18 -0
  55. data/lib/pdfrb/cli/images.rb +34 -0
  56. data/lib/pdfrb/cli/info.rb +20 -0
  57. data/lib/pdfrb/cli/inspect.rb +18 -0
  58. data/lib/pdfrb/cli/merge.rb +18 -0
  59. data/lib/pdfrb/cli/modify.rb +18 -0
  60. data/lib/pdfrb/cli/optimize.rb +18 -0
  61. data/lib/pdfrb/cli/split.rb +21 -0
  62. data/lib/pdfrb/cli/watermark.rb +18 -0
  63. data/lib/pdfrb/cli.rb +291 -7
  64. data/lib/pdfrb/color/color_space.rb +259 -0
  65. data/lib/pdfrb/color/icc_profile.rb +90 -0
  66. data/lib/pdfrb/color.rb +12 -0
  67. data/lib/pdfrb/compare/comparator.rb +244 -0
  68. data/lib/pdfrb/compare/report.rb +78 -0
  69. data/lib/pdfrb/compare.rb +42 -0
  70. data/lib/pdfrb/composer.rb +161 -0
  71. data/lib/pdfrb/conformance/pdf_a.rb +317 -56
  72. data/lib/pdfrb/conformance/pdf_ua.rb +186 -29
  73. data/lib/pdfrb/conformance/pdf_x.rb +247 -0
  74. data/lib/pdfrb/conformance/rule.rb +78 -0
  75. data/lib/pdfrb/conformance/structure_elements.rb +174 -0
  76. data/lib/pdfrb/conformance/verapdf_bridge.rb +92 -0
  77. data/lib/pdfrb/conformance.rb +7 -3
  78. data/lib/pdfrb/content/canvas.rb +354 -72
  79. data/lib/pdfrb/content/color_space.rb +157 -0
  80. data/lib/pdfrb/content/hidden_text_detector.rb +167 -0
  81. data/lib/pdfrb/content/operator.rb +21 -1
  82. data/lib/pdfrb/content/operators/clipping.rb +36 -0
  83. data/lib/pdfrb/content/operators/graphics_state_params.rb +15 -0
  84. data/lib/pdfrb/content/operators/inline_image.rb +53 -0
  85. data/lib/pdfrb/content/operators.rb +22 -0
  86. data/lib/pdfrb/content/shading/base.rb +139 -0
  87. data/lib/pdfrb/content/shading.rb +17 -0
  88. data/lib/pdfrb/content/smart_text_extractor.rb +85 -0
  89. data/lib/pdfrb/content/tiling_pattern.rb +78 -0
  90. data/lib/pdfrb/content/transformation_matrix.rb +61 -0
  91. data/lib/pdfrb/content.rb +7 -26
  92. data/lib/pdfrb/destination/fit.rb +185 -0
  93. data/lib/pdfrb/destination.rb +13 -0
  94. data/lib/pdfrb/digital_signature/cms_handler.rb +22 -0
  95. data/lib/pdfrb/digital_signature/signing.rb +107 -11
  96. data/lib/pdfrb/digital_signature/timestamp_handler.rb +45 -0
  97. data/lib/pdfrb/digital_signature/verification.rb +88 -9
  98. data/lib/pdfrb/digital_signature/verification_result.rb +14 -0
  99. data/lib/pdfrb/digital_signature.rb +26 -2
  100. data/lib/pdfrb/document/annotations.rb +120 -3
  101. data/lib/pdfrb/document/associated_files.rb +104 -0
  102. data/lib/pdfrb/document/colors.rb +95 -0
  103. data/lib/pdfrb/document/destinations.rb +127 -5
  104. data/lib/pdfrb/document/display.rb +84 -0
  105. data/lib/pdfrb/document/fonts.rb +417 -45
  106. data/lib/pdfrb/document/form.rb +338 -0
  107. data/lib/pdfrb/document/graphics_state.rb +91 -0
  108. data/lib/pdfrb/document/images.rb +4 -4
  109. data/lib/pdfrb/document/info.rb +102 -0
  110. data/lib/pdfrb/document/layers.rb +62 -0
  111. data/lib/pdfrb/document/metadata.rb +12 -5
  112. data/lib/pdfrb/document/output_intents.rb +91 -0
  113. data/lib/pdfrb/document/page_labels.rb +80 -0
  114. data/lib/pdfrb/document/pages.rb +68 -19
  115. data/lib/pdfrb/document/portfolio.rb +119 -0
  116. data/lib/pdfrb/document/shadings.rb +159 -0
  117. data/lib/pdfrb/document/stamps.rb +68 -0
  118. data/lib/pdfrb/document/structure.rb +242 -0
  119. data/lib/pdfrb/document.rb +73 -4
  120. data/lib/pdfrb/encryption/identity.rb +13 -0
  121. data/lib/pdfrb/encryption/password_verification.rb +53 -8
  122. data/lib/pdfrb/encryption/security_handler.rb +8 -5
  123. data/lib/pdfrb/encryption/standard_security_handler.rb +154 -81
  124. data/lib/pdfrb/encryption.rb +17 -0
  125. data/lib/pdfrb/error.rb +2 -2
  126. data/lib/pdfrb/filter/brotli_decode.rb +32 -0
  127. data/lib/pdfrb/filter/ccitt_fax_decode.rb +12 -0
  128. data/lib/pdfrb/filter/crypt.rb +2 -2
  129. data/lib/pdfrb/filter/dct_decode.rb +18 -0
  130. data/lib/pdfrb/filter/flate_decode.rb +4 -18
  131. data/lib/pdfrb/filter/jbig2_decode.rb +12 -0
  132. data/lib/pdfrb/filter/jpx_decode.rb +18 -0
  133. data/lib/pdfrb/filter/lzw_decode.rb +3 -2
  134. data/lib/pdfrb/filter/png_predictor.rb +94 -0
  135. data/lib/pdfrb/filter.rb +26 -13
  136. data/lib/pdfrb/font/afm_parser.rb +50 -26
  137. data/lib/pdfrb/font/cmap/writer.rb +9 -0
  138. data/lib/pdfrb/font/encoding/win_ansi_encoding.rb +17 -0
  139. data/lib/pdfrb/font/encoding.rb +32 -3
  140. data/lib/pdfrb/font/metrics_helper.rb +121 -0
  141. data/lib/pdfrb/font/true_type/file.rb +10 -1
  142. data/lib/pdfrb/font/true_type/glyf.rb +82 -0
  143. data/lib/pdfrb/font/true_type/kern.rb +58 -0
  144. data/lib/pdfrb/font/true_type/loca.rb +42 -0
  145. data/lib/pdfrb/font/true_type/maxp.rb +44 -0
  146. data/lib/pdfrb/font/true_type/name.rb +53 -0
  147. data/lib/pdfrb/font/true_type/post.rb +93 -0
  148. data/lib/pdfrb/font/true_type/subsetter.rb +3 -5
  149. data/lib/pdfrb/font/true_type/wrapper.rb +102 -0
  150. data/lib/pdfrb/font/true_type.rb +7 -0
  151. data/lib/pdfrb/font.rb +2 -0
  152. data/lib/pdfrb/font_loader/from_configuration.rb +19 -0
  153. data/lib/pdfrb/font_loader/from_file.rb +20 -0
  154. data/lib/pdfrb/font_loader/standard14.rb +20 -0
  155. data/lib/pdfrb/font_loader/variant_from_name.rb +36 -0
  156. data/lib/pdfrb/font_loader.rb +4 -0
  157. data/lib/pdfrb/font_resolver.rb +138 -0
  158. data/lib/pdfrb/image/audit.rb +94 -0
  159. data/lib/pdfrb/image/downsampler.rb +124 -0
  160. data/lib/pdfrb/image.rb +13 -0
  161. data/lib/pdfrb/image_loader/jpeg.rb +39 -58
  162. data/lib/pdfrb/image_loader/png.rb +82 -251
  163. data/lib/pdfrb/layout/bidi.rb +198 -0
  164. data/lib/pdfrb/layout/box.rb +49 -0
  165. data/lib/pdfrb/layout/box_fitter.rb +54 -0
  166. data/lib/pdfrb/layout/column_box.rb +71 -0
  167. data/lib/pdfrb/layout/container_box.rb +45 -0
  168. data/lib/pdfrb/layout/frame.rb +67 -0
  169. data/lib/pdfrb/layout/image_box.rb +44 -0
  170. data/lib/pdfrb/layout/inline_box.rb +25 -0
  171. data/lib/pdfrb/layout/line.rb +55 -0
  172. data/lib/pdfrb/layout/list_box.rb +87 -0
  173. data/lib/pdfrb/layout/numeric_refinements.rb +43 -0
  174. data/lib/pdfrb/layout/page_style.rb +65 -0
  175. data/lib/pdfrb/layout/style.rb +62 -0
  176. data/lib/pdfrb/layout/table_box.rb +66 -0
  177. data/lib/pdfrb/layout/text_box.rb +38 -0
  178. data/lib/pdfrb/layout/text_fragment.rb +45 -0
  179. data/lib/pdfrb/layout/text_layouter.rb +123 -0
  180. data/lib/pdfrb/layout.rb +28 -0
  181. data/lib/pdfrb/linearization/hint_stream.rb +93 -0
  182. data/lib/pdfrb/linearization/writer.rb +259 -0
  183. data/lib/pdfrb/linearization.rb +21 -0
  184. data/lib/pdfrb/model/cos/dictionary.rb +12 -0
  185. data/lib/pdfrb/model/pdf_array.rb +2 -2
  186. data/lib/pdfrb/model/type/action.rb +42 -0
  187. data/lib/pdfrb/model/type/action_go_to_3d_view.rb +14 -0
  188. data/lib/pdfrb/model/type/action_go_to_dp.rb +16 -0
  189. data/lib/pdfrb/model/type/action_go_to_r.rb +26 -0
  190. data/lib/pdfrb/model/type/action_goto.rb +34 -0
  191. data/lib/pdfrb/model/type/action_hide.rb +26 -0
  192. data/lib/pdfrb/model/type/action_import_data.rb +18 -0
  193. data/lib/pdfrb/model/type/action_java_script.rb +18 -0
  194. data/lib/pdfrb/model/type/action_launch.rb +22 -0
  195. data/lib/pdfrb/model/type/action_movie.rb +22 -0
  196. data/lib/pdfrb/model/type/action_named.rb +23 -0
  197. data/lib/pdfrb/model/type/action_nop.rb +12 -0
  198. data/lib/pdfrb/model/type/action_rendition.rb +28 -0
  199. data/lib/pdfrb/model/type/action_reset_form.rb +22 -0
  200. data/lib/pdfrb/model/type/action_rich_media_execute.rb +17 -0
  201. data/lib/pdfrb/model/type/action_set_ocg_state.rb +32 -0
  202. data/lib/pdfrb/model/type/action_set_state.rb +14 -0
  203. data/lib/pdfrb/model/type/action_sound_action.rb +23 -0
  204. data/lib/pdfrb/model/type/action_submit_form.rb +64 -0
  205. data/lib/pdfrb/model/type/action_thread.rb +23 -0
  206. data/lib/pdfrb/model/type/action_trans.rb +30 -0
  207. data/lib/pdfrb/model/type/action_uri.rb +30 -0
  208. data/lib/pdfrb/model/type/af_embedded_file.rb +11 -0
  209. data/lib/pdfrb/model/type/af_file_specification.rb +34 -0
  210. data/lib/pdfrb/model/type/annotation.rb +49 -10
  211. data/lib/pdfrb/model/type/appearance.rb +41 -0
  212. data/lib/pdfrb/model/type/appearance_characteristics.rb +36 -0
  213. data/lib/pdfrb/model/type/appearance_generator.rb +22 -0
  214. data/lib/pdfrb/model/type/appearance_trap_net.rb +84 -0
  215. data/lib/pdfrb/model/type/border_effect.rb +25 -0
  216. data/lib/pdfrb/model/type/border_styling.rb +38 -0
  217. data/lib/pdfrb/model/type/box_color_info.rb +21 -0
  218. data/lib/pdfrb/model/type/box_style.rb +19 -0
  219. data/lib/pdfrb/model/type/button.rb +17 -0
  220. data/lib/pdfrb/model/type/cal_gray.rb +17 -0
  221. data/lib/pdfrb/model/type/cal_rgb.rb +22 -0
  222. data/lib/pdfrb/model/type/caret_annotation.rb +21 -0
  223. data/lib/pdfrb/model/type/catalog.rb +99 -16
  224. data/lib/pdfrb/model/type/choice.rb +21 -0
  225. data/lib/pdfrb/model/type/cid_font.rb +52 -1
  226. data/lib/pdfrb/model/type/cid_font_descriptor_metrics.rb +28 -0
  227. data/lib/pdfrb/model/type/cid_system_info.rb +28 -0
  228. data/lib/pdfrb/model/type/circle_annotation.rb +20 -0
  229. data/lib/pdfrb/model/type/collection.rb +27 -0
  230. data/lib/pdfrb/model/type/collection_field.rb +24 -0
  231. data/lib/pdfrb/model/type/collection_item.rb +19 -0
  232. data/lib/pdfrb/model/type/collection_schema.rb +25 -0
  233. data/lib/pdfrb/model/type/collection_sort.rb +13 -0
  234. data/lib/pdfrb/model/type/crypt_filter.rb +58 -0
  235. data/lib/pdfrb/model/type/device_n.rb +37 -0
  236. data/lib/pdfrb/model/type/device_n_mixing_hints.rb +17 -0
  237. data/lib/pdfrb/model/type/device_n_process.rb +13 -0
  238. data/lib/pdfrb/model/type/doc_mdp_transform_parameters.rb +21 -0
  239. data/lib/pdfrb/model/type/document_security_store.rb +55 -0
  240. data/lib/pdfrb/model/type/embedded_file.rb +19 -0
  241. data/lib/pdfrb/model/type/embedded_file_parameter.rb +48 -0
  242. data/lib/pdfrb/model/type/encryption_public_key.rb +38 -0
  243. data/lib/pdfrb/model/type/encryption_standard.rb +68 -0
  244. data/lib/pdfrb/model/type/field_mdp_transform_parameters.rb +26 -0
  245. data/lib/pdfrb/model/type/file_attachment_annotation.rb +29 -0
  246. data/lib/pdfrb/model/type/file_spec_ef.rb +22 -0
  247. data/lib/pdfrb/model/type/file_spec_rf.rb +21 -0
  248. data/lib/pdfrb/model/type/file_specification.rb +43 -0
  249. data/lib/pdfrb/model/type/file_trailer.rb +54 -0
  250. data/lib/pdfrb/model/type/font.rb +73 -2
  251. data/lib/pdfrb/model/type/font_cid_type0.rb +17 -0
  252. data/lib/pdfrb/model/type/font_cid_type2.rb +17 -0
  253. data/lib/pdfrb/model/type/font_descriptor.rb +47 -0
  254. data/lib/pdfrb/model/type/font_descriptor_cid_type0.rb +15 -0
  255. data/lib/pdfrb/model/type/font_descriptor_cid_type2.rb +15 -0
  256. data/lib/pdfrb/model/type/font_encoding.rb +47 -0
  257. data/lib/pdfrb/model/type/font_file.rb +28 -0
  258. data/lib/pdfrb/model/type/font_file2.rb +32 -0
  259. data/lib/pdfrb/model/type/font_file3.rb +23 -0
  260. data/lib/pdfrb/model/type/font_multiple_master.rb +60 -0
  261. data/lib/pdfrb/model/type/font_true_type.rb +1 -1
  262. data/lib/pdfrb/model/type/font_type0.rb +1 -1
  263. data/lib/pdfrb/model/type/font_type1.rb +1 -1
  264. data/lib/pdfrb/model/type/font_type3.rb +1 -1
  265. data/lib/pdfrb/model/type/form_field.rb +73 -0
  266. data/lib/pdfrb/model/type/free_text_annotation.rb +37 -0
  267. data/lib/pdfrb/model/type/function.rb +42 -0
  268. data/lib/pdfrb/model/type/function_exponential.rb +31 -0
  269. data/lib/pdfrb/model/type/function_postscript.rb +25 -0
  270. data/lib/pdfrb/model/type/function_sampled.rb +55 -0
  271. data/lib/pdfrb/model/type/function_stitching.rb +33 -0
  272. data/lib/pdfrb/model/type/generic_appearance.rb +40 -0
  273. data/lib/pdfrb/model/type/graphics_state_parameter.rb +69 -0
  274. data/lib/pdfrb/model/type/halftone.rb +21 -0
  275. data/lib/pdfrb/model/type/halftone_type1.rb +17 -0
  276. data/lib/pdfrb/model/type/halftone_type10.rb +16 -0
  277. data/lib/pdfrb/model/type/halftone_type16.rb +11 -0
  278. data/lib/pdfrb/model/type/halftone_type5.rb +24 -0
  279. data/lib/pdfrb/model/type/halftone_type6.rb +16 -0
  280. data/lib/pdfrb/model/type/highlight_annotation.rb +11 -0
  281. data/lib/pdfrb/model/type/icc_based_color_space.rb +24 -0
  282. data/lib/pdfrb/model/type/icon_fit.rb +31 -0
  283. data/lib/pdfrb/model/type/indexed.rb +32 -0
  284. data/lib/pdfrb/model/type/info.rb +44 -0
  285. data/lib/pdfrb/model/type/ink_annotation.rb +29 -0
  286. data/lib/pdfrb/model/type/interactive_form.rb +36 -0
  287. data/lib/pdfrb/model/type/interior_color.rb +12 -0
  288. data/lib/pdfrb/model/type/jbig2_globals.rb +15 -0
  289. data/lib/pdfrb/model/type/lab.rb +27 -0
  290. data/lib/pdfrb/model/type/line_annotation.rb +44 -0
  291. data/lib/pdfrb/model/type/line_ending_styling.rb +39 -0
  292. data/lib/pdfrb/model/type/link_annotation.rb +41 -0
  293. data/lib/pdfrb/model/type/link_appearance.rb +9 -0
  294. data/lib/pdfrb/model/type/mark_information.rb +23 -0
  295. data/lib/pdfrb/model/type/marked_content_reference.rb +24 -0
  296. data/lib/pdfrb/model/type/markup_annotation.rb +36 -0
  297. data/lib/pdfrb/model/type/mdp_dict.rb +28 -0
  298. data/lib/pdfrb/model/type/measure.rb +39 -0
  299. data/lib/pdfrb/model/type/media_clip.rb +28 -0
  300. data/lib/pdfrb/model/type/media_offset.rb +106 -0
  301. data/lib/pdfrb/model/type/metadata.rb +27 -3
  302. data/lib/pdfrb/model/type/misc_helpers.rb +75 -0
  303. data/lib/pdfrb/model/type/movie.rb +28 -0
  304. data/lib/pdfrb/model/type/names.rb +51 -0
  305. data/lib/pdfrb/model/type/namespace.rb +27 -0
  306. data/lib/pdfrb/model/type/object_reference.rb +31 -0
  307. data/lib/pdfrb/model/type/object_stream.rb +38 -0
  308. data/lib/pdfrb/model/type/opt_content_ext.rb +94 -0
  309. data/lib/pdfrb/model/type/opt_content_page_element.rb +18 -0
  310. data/lib/pdfrb/model/type/optional_content_config.rb +56 -0
  311. data/lib/pdfrb/model/type/optional_content_group.rb +35 -10
  312. data/lib/pdfrb/model/type/optional_content_membership.rb +36 -2
  313. data/lib/pdfrb/model/type/optional_content_properties.rb +16 -1
  314. data/lib/pdfrb/model/type/outline.rb +40 -6
  315. data/lib/pdfrb/model/type/outline_item.rb +45 -0
  316. data/lib/pdfrb/model/type/output_intent.rb +30 -0
  317. data/lib/pdfrb/model/type/page.rb +127 -0
  318. data/lib/pdfrb/model/type/page_label.rb +68 -0
  319. data/lib/pdfrb/model/type/page_piece_info.rb +58 -0
  320. data/lib/pdfrb/model/type/page_tree_node.rb +51 -16
  321. data/lib/pdfrb/model/type/page_tree_node_root.rb +15 -0
  322. data/lib/pdfrb/model/type/pattern.rb +16 -0
  323. data/lib/pdfrb/model/type/pattern_shading.rb +25 -0
  324. data/lib/pdfrb/model/type/pattern_tiling.rb +25 -0
  325. data/lib/pdfrb/model/type/polygon_annotation.rb +29 -0
  326. data/lib/pdfrb/model/type/polygon_polyline.rb +14 -0
  327. data/lib/pdfrb/model/type/polyline_annotation.rb +29 -0
  328. data/lib/pdfrb/model/type/popup_annotation.rb +20 -0
  329. data/lib/pdfrb/model/type/printer_mark_annotation.rb +13 -0
  330. data/lib/pdfrb/model/type/redact_annotation.rb +28 -0
  331. data/lib/pdfrb/model/type/rendition.rb +31 -0
  332. data/lib/pdfrb/model/type/resources.rb +59 -14
  333. data/lib/pdfrb/model/type/rich_media_activation.rb +24 -0
  334. data/lib/pdfrb/model/type/rich_media_animation.rb +25 -0
  335. data/lib/pdfrb/model/type/rich_media_annotation.rb +18 -0
  336. data/lib/pdfrb/model/type/rich_media_command.rb +19 -0
  337. data/lib/pdfrb/model/type/rich_media_configuration.rb +32 -0
  338. data/lib/pdfrb/model/type/rich_media_content.rb +28 -0
  339. data/lib/pdfrb/model/type/rich_media_cue_point.rb +18 -0
  340. data/lib/pdfrb/model/type/rich_media_deactivation.rb +17 -0
  341. data/lib/pdfrb/model/type/rich_media_instance.rb +33 -0
  342. data/lib/pdfrb/model/type/rich_media_presentation.rb +29 -0
  343. data/lib/pdfrb/model/type/rich_media_settings.rb +33 -0
  344. data/lib/pdfrb/model/type/rich_media_window.rb +20 -0
  345. data/lib/pdfrb/model/type/screen_annotation.rb +19 -0
  346. data/lib/pdfrb/model/type/separation.rb +25 -0
  347. data/lib/pdfrb/model/type/sig_field_lock.rb +25 -0
  348. data/lib/pdfrb/model/type/sig_field_seed_value.rb +25 -0
  349. data/lib/pdfrb/model/type/signature.rb +65 -0
  350. data/lib/pdfrb/model/type/signature_build.rb +86 -0
  351. data/lib/pdfrb/model/type/signature_field.rb +17 -0
  352. data/lib/pdfrb/model/type/software_identifier.rb +19 -0
  353. data/lib/pdfrb/model/type/sound.rb +38 -0
  354. data/lib/pdfrb/model/type/sound_annotation.rb +21 -0
  355. data/lib/pdfrb/model/type/square_annotation.rb +24 -0
  356. data/lib/pdfrb/model/type/square_circle.rb +13 -0
  357. data/lib/pdfrb/model/type/squiggly_annotation.rb +11 -0
  358. data/lib/pdfrb/model/type/stamp_annotation.rb +19 -0
  359. data/lib/pdfrb/model/type/strikeout_annotation.rb +11 -0
  360. data/lib/pdfrb/model/type/struct_elem.rb +75 -0
  361. data/lib/pdfrb/model/type/struct_tree_root.rb +34 -0
  362. data/lib/pdfrb/model/type/structure_attributes.rb +44 -0
  363. data/lib/pdfrb/model/type/structure_element_kid.rb +33 -0
  364. data/lib/pdfrb/model/type/text_annotation.rb +25 -0
  365. data/lib/pdfrb/model/type/text_appearance.rb +9 -0
  366. data/lib/pdfrb/model/type/text_field.rb +21 -0
  367. data/lib/pdfrb/model/type/text_markup_annotation.rb +30 -0
  368. data/lib/pdfrb/model/type/three_d_activation.rb +32 -0
  369. data/lib/pdfrb/model/type/three_d_animation_style.rb +24 -0
  370. data/lib/pdfrb/model/type/three_d_background.rb +28 -0
  371. data/lib/pdfrb/model/type/three_d_cross_section.rb +25 -0
  372. data/lib/pdfrb/model/type/three_d_lighting_scheme.rb +26 -0
  373. data/lib/pdfrb/model/type/three_d_measure.rb +20 -0
  374. data/lib/pdfrb/model/type/three_d_measure_3dc.rb +23 -0
  375. data/lib/pdfrb/model/type/three_d_measure_ad3.rb +14 -0
  376. data/lib/pdfrb/model/type/three_d_measure_ld3.rb +14 -0
  377. data/lib/pdfrb/model/type/three_d_measure_pd3.rb +20 -0
  378. data/lib/pdfrb/model/type/three_d_measure_rd3.rb +14 -0
  379. data/lib/pdfrb/model/type/three_d_node.rb +37 -0
  380. data/lib/pdfrb/model/type/three_d_reference.rb +21 -0
  381. data/lib/pdfrb/model/type/three_d_render_mode.rb +34 -0
  382. data/lib/pdfrb/model/type/three_d_stream.rb +48 -0
  383. data/lib/pdfrb/model/type/three_d_units.rb +18 -0
  384. data/lib/pdfrb/model/type/three_d_view.rb +40 -0
  385. data/lib/pdfrb/model/type/to_unicode_cmap_stream.rb +23 -0
  386. data/lib/pdfrb/model/type/transition.rb +68 -0
  387. data/lib/pdfrb/model/type/underline_annotation.rb +11 -0
  388. data/lib/pdfrb/model/type/ur_transform_parameters.rb +33 -0
  389. data/lib/pdfrb/model/type/uri_dict.rb +18 -0
  390. data/lib/pdfrb/model/type/url_alias.rb +19 -0
  391. data/lib/pdfrb/model/type/variable_text_field.rb +47 -0
  392. data/lib/pdfrb/model/type/viewer_preferences.rb +42 -0
  393. data/lib/pdfrb/model/type/watermark_annotation.rb +13 -0
  394. data/lib/pdfrb/model/type/widget_annotation.rb +48 -0
  395. data/lib/pdfrb/model/type/widget_appearance.rb +9 -0
  396. data/lib/pdfrb/model/type/xobject_form.rb +41 -0
  397. data/lib/pdfrb/model/type/xobject_image.rb +56 -0
  398. data/lib/pdfrb/model/type/xref_stream.rb +37 -0
  399. data/lib/pdfrb/model/type.rb +289 -6
  400. data/lib/pdfrb/serializer.rb +2 -2
  401. data/lib/pdfrb/source/tokenizer.rb +0 -2
  402. data/lib/pdfrb/task/benchmark.rb +83 -0
  403. data/lib/pdfrb/task/extract_text.rb +1 -1
  404. data/lib/pdfrb/task/generate_corpus.rb +130 -0
  405. data/lib/pdfrb/task/memory_profile.rb +84 -0
  406. data/lib/pdfrb/task/optimize.rb +97 -7
  407. data/lib/pdfrb/task/regenerate_appearances.rb +98 -0
  408. data/lib/pdfrb/task.rb +4 -3
  409. data/lib/pdfrb/test_utils.rb +112 -0
  410. data/lib/pdfrb/validator.rb +82 -0
  411. data/lib/pdfrb/version.rb +1 -1
  412. data/lib/pdfrb/writer.rb +117 -27
  413. data/lib/pdfrb/xmp/packet.rb +122 -0
  414. data/lib/pdfrb/xmp/schemas.rb +96 -0
  415. data/lib/pdfrb/xmp.rb +44 -0
  416. data/lib/pdfrb/xref_section.rb +2 -2
  417. data/lib/pdfrb.rb +24 -54
  418. data/pdfrb.gemspec +45 -0
  419. data/script/apply_fixes.sh +843 -0
  420. data/script/make_jpeg_fixture.rb +53 -0
  421. data/script/make_png_fixture.rb +25 -0
  422. metadata +319 -4
@@ -0,0 +1,338 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class Document
5
+ # Facade for AcroForm interactive forms (ISO 32000-2 §12.7).
6
+ #
7
+ # Creates the /AcroForm dictionary on the Catalog and manages
8
+ # form fields. Each field is a /Type /Annot /Subtype /Widget
9
+ # indirect object linked to a page via /P and positioned via /Rect.
10
+ #
11
+ # Supported field types (FT):
12
+ # :Btn — button (checkbox, radio, pushbutton)
13
+ # :Tx — text input
14
+ # :Ch — choice (list, combo)
15
+ # :Sig — digital signature
16
+ #
17
+ # Appearance streams are generated by the viewer when /NeedAppearances
18
+ # is true on the /AcroForm dict.
19
+ class Form
20
+ attr_reader :document
21
+
22
+ def initialize(document)
23
+ @document = document
24
+ end
25
+
26
+ # Enable AcroForm by creating the /AcroForm dict on the Catalog.
27
+ # Idempotent. @return [Hash] the AcroForm dict value.
28
+ def enable!
29
+ catalog = document.catalog
30
+ return catalog.value[:AcroForm] if catalog.value[:AcroForm]
31
+
32
+ acroform = {
33
+ Fields: [],
34
+ NeedAppearances: true,
35
+ DA: "/Helv 0 Tf 0 g",
36
+ }
37
+ catalog.value[:AcroForm] = acroform
38
+ acroform
39
+ end
40
+
41
+ # Add a text field. @return [Pdfrb::Model::Cos::Dictionary] the field.
42
+ def add_text_field(name, page:, rect:, value: nil, multiline: false)
43
+ enable!
44
+ flags = 0
45
+ flags |= 0x1000 if multiline # bit 13 = multiline
46
+
47
+ field = build_widget(name, page: page, rect: rect)
48
+ field.value[:FT] = :Tx
49
+ field.value[:Ff] = flags if flags.positive?
50
+ field.value[:V] = value if value
51
+ register_field(field)
52
+ field
53
+ end
54
+
55
+ # Add a checkbox field. @return [Pdfrb::Model::Cos::Dictionary] the field.
56
+ def add_checkbox(name, page:, rect:, checked: false)
57
+ enable!
58
+ field = build_widget(name, page: page, rect: rect)
59
+ field.value[:FT] = :Btn
60
+ field.value[:V] = checked ? :Yes : :Off
61
+ register_field(field)
62
+ field
63
+ end
64
+
65
+ # Add a combo box (dropdown) field.
66
+ # @return [Pdfrb::Model::Cos::Dictionary] the field.
67
+ def add_combo(name, page:, rect:, options:, value: nil)
68
+ enable!
69
+ field = build_widget(name, page: page, rect: rect)
70
+ field.value[:FT] = :Ch
71
+ field.value[:Ff] = 0x20000 # bit 18 = Combo
72
+ field.value[:Opt] = options
73
+ field.value[:V] = value if value
74
+ register_field(field)
75
+ field
76
+ end
77
+
78
+ # Total field count.
79
+ def count
80
+ acroform = document.catalog.value[:AcroForm]
81
+ return 0 unless acroform
82
+
83
+ fields = acroform[:Fields]
84
+ fields ? fields.length : 0
85
+ end
86
+
87
+ # Find a field by fully qualified name.
88
+ def find(name)
89
+ each_field.find { |f| f.value[:T] == name }
90
+ end
91
+
92
+ # Enumerate all top-level fields.
93
+ def each_field(&block)
94
+ acroform = document.catalog.value[:AcroForm]
95
+ return enum_for(:each_field) unless block
96
+
97
+ return unless acroform
98
+
99
+ fields = acroform[:Fields] || []
100
+ fields.each do |ref|
101
+ obj = ref.is_a?(Pdfrb::Model::Reference) ? document.object(ref) : ref
102
+ yield obj if obj
103
+ end
104
+ end
105
+
106
+ # List of fully qualified field names (T values).
107
+ def field_names
108
+ each_field.filter_map { |f| f.value[:T] }
109
+ end
110
+
111
+ # Set a field value by name. Auto-regenerates the appearance stream
112
+ # for text/checkbox fields. For unsupported types, sets /V only.
113
+ # @param name [String] the field's /T value.
114
+ # @param value [Object] the new /V value.
115
+ # @return [Pdfrb::Model::Cos::Dictionary, nil] the field if found.
116
+ def set_value(name, value)
117
+ field = find(name)
118
+ return nil unless field
119
+
120
+ field.value[:V] = normalize_value(field, value)
121
+ regenerate_appearance(field)
122
+ field
123
+ end
124
+
125
+ # Get a field value by name.
126
+ # @param name [String] the field's /T value.
127
+ # @return [Object, nil] the /V value, or nil if not found.
128
+ def get_value(name)
129
+ field = find(name)
130
+ return nil unless field
131
+
132
+ field.value[:V]
133
+ end
134
+
135
+ # Flatten all form fields: stamp their appearance streams into page
136
+ # content, then remove the field from /AcroForm /Fields. After
137
+ # flattening, the form is read-only.
138
+ def flatten!
139
+ each_field.to_a.each do |field|
140
+ stamp_field_appearance(field)
141
+ remove_field(field)
142
+ end
143
+ acroform = document.catalog.value[:AcroForm]
144
+ delete_acroform_key(acroform, :Fields)
145
+ delete_acroform_key(acroform, :NeedAppearances)
146
+ document
147
+ end
148
+
149
+ def delete_acroform_key(acroform, key)
150
+ return unless acroform
151
+
152
+ if acroform.is_a?(Pdfrb::Model::Cos::Dictionary)
153
+ acroform.value.delete(key)
154
+ else
155
+ acroform.delete(key)
156
+ end
157
+ end
158
+
159
+ # Remove a single field from /AcroForm /Fields and its widget from
160
+ # the page's /Annots array.
161
+ def remove_field(field)
162
+ acroform = document.catalog.value[:AcroForm]
163
+ return nil unless acroform
164
+
165
+ fields = acroform[:Fields]
166
+ return nil unless fields
167
+
168
+ field_ref = Pdfrb::Model::Reference.new(field.oid, field.gen)
169
+ fields.delete_if { |r| r == field_ref || (r.is_a?(Pdfrb::Model::Reference) && r.oid == field.oid) }
170
+
171
+ # Remove widget annotation from page /Annots
172
+ page_ref = field.value[:P]
173
+ if page_ref
174
+ page = page_ref.is_a?(Pdfrb::Model::Reference) ? document.object(page_ref) : page_ref
175
+ annots = page&.value&.[](:Annots)
176
+ annots&.delete_if { |r| r == field_ref || (r.is_a?(Pdfrb::Model::Reference) && r.oid == field.oid) }
177
+ end
178
+ field
179
+ end
180
+
181
+ private
182
+
183
+ def normalize_value(field, value)
184
+ case field.value[:FT]&.to_sym
185
+ when :Btn
186
+ # Buttons accept :Yes/:Off or boolean. Normalize to symbol.
187
+ if [true, :Yes].include?(value)
188
+ :Yes
189
+ else
190
+ ([false, :Off].include?(value) ? :Off : value)
191
+ end
192
+ when :Ch
193
+ # Choices: validate against /Opt if present.
194
+ value
195
+ else
196
+ # Text and signature: accept strings.
197
+ value.to_s
198
+ end
199
+ end
200
+
201
+ def regenerate_appearance(field)
202
+ ft = field.value[:FT]&.to_sym
203
+ generator = Pdfrb::Appearance::Generator.new(document)
204
+ case ft
205
+ when :Tx
206
+ generator.text_field(field, value: field.value[:V].to_s)
207
+ when :Btn
208
+ checked = field.value[:V] == :Yes
209
+ generator.checkbox(field, checked: checked)
210
+ end
211
+ rescue StandardError
212
+ # Appearance generation is best-effort; viewer can regenerate.
213
+ end
214
+
215
+ def stamp_field_appearance(field)
216
+ form_stream = appearance_form_stream(field)
217
+ return unless form_stream
218
+
219
+ page_ref = field.value[:P]
220
+ return unless page_ref
221
+
222
+ page = page_ref.is_a?(Pdfrb::Model::Reference) ? document.object(page_ref) : page_ref
223
+ return unless page
224
+
225
+ name = register_form_on_page(page, form_stream)
226
+ rect = field.value[:Rect]
227
+ origin = rect ? [rect[0], rect[1]] : [0, 0]
228
+ emit_do_on_page(page, name, origin)
229
+ end
230
+
231
+ # Resolve the Form XObject stream that backs the field's current
232
+ # appearance. Handles single-state (/AP /N -> ref) and multi-state
233
+ # (/AP /N -> {state -> ref}, picked from /AS).
234
+ def appearance_form_stream(field)
235
+ ap = field.value[:AP]
236
+ return nil unless ap
237
+
238
+ n = dictionary_get(ap, :N)
239
+ return nil unless n
240
+
241
+ target = if dictionary?(n)
242
+ state = field.value[:AS]
243
+ dictionary_get(n, state) ||
244
+ dictionary_get(n, :Normal) ||
245
+ dictionary_values(n).first
246
+ else
247
+ n
248
+ end
249
+ return nil unless target
250
+
251
+ target.is_a?(Pdfrb::Model::Reference) ? document.object(target) : target
252
+ end
253
+
254
+ def dictionary?(value)
255
+ value.is_a?(::Hash) || value.is_a?(Pdfrb::Model::Cos::Dictionary)
256
+ end
257
+
258
+ def dictionary_get(dict, key)
259
+ return nil unless key
260
+
261
+ if dict.is_a?(Pdfrb::Model::Cos::Dictionary)
262
+ dict.value[key]
263
+ else
264
+ dict[key]
265
+ end
266
+ end
267
+
268
+ def dictionary_values(dict)
269
+ if dict.is_a?(Pdfrb::Model::Cos::Dictionary)
270
+ dict.value.values
271
+ else
272
+ dict.values
273
+ end
274
+ end
275
+
276
+ def register_form_on_page(page, form_stream)
277
+ resources = page.value[:Resources]
278
+ resources = page.value[:Resources] = Pdfrb::Model::Cos::Dictionary.new({}) unless resources.is_a?(Pdfrb::Model::Cos::Dictionary)
279
+ xobjects = resources.value[:XObject] || {}
280
+ name = unique_xobject_name(xobjects)
281
+ xobjects[name] = Pdfrb::Model::Reference.new(form_stream.oid, form_stream.gen)
282
+ resources.value[:XObject] = xobjects
283
+ name
284
+ end
285
+
286
+ def unique_xobject_name(existing)
287
+ counter = (existing.keys.map { |k| k.to_s[/\d+\z/].to_i }.max || 0) + 1
288
+ :"Fm#{counter}"
289
+ end
290
+
291
+ def emit_do_on_page(page, name, at)
292
+ canvas = page.canvas
293
+ canvas.save_graphics_state
294
+ canvas.translate(at[0], at[1]) if at[0] != 0 || at[1] != 0
295
+ canvas.emit_op(Pdfrb::Content::Operator::InvokeXObject, name)
296
+ canvas.restore_graphics_state
297
+ rescue StandardError
298
+ # Pages without a content stream or with malformed resources
299
+ # cannot be stamped; flattening still removes the widget.
300
+ end
301
+
302
+ def build_widget(name, page:, rect:)
303
+ widget = document.add(
304
+ {
305
+ Type: :Annot,
306
+ Subtype: :Widget,
307
+ T: name,
308
+ Rect: rect,
309
+ P: Pdfrb::Model::Reference.new(page.oid, page.gen),
310
+ FT: :Tx,
311
+ },
312
+ type: Pdfrb::Model::Type::Annotation
313
+ )
314
+ add_annot_to_page(page, widget)
315
+ widget
316
+ end
317
+
318
+ def add_annot_to_page(page, widget)
319
+ annots = page.value[:Annots]
320
+ ref = Pdfrb::Model::Reference.new(widget.oid, widget.gen)
321
+ if annots.nil?
322
+ page.value[:Annots] = [ref]
323
+ elsif annots.is_a?(::Array)
324
+ annots << ref
325
+ else
326
+ page.value[:Annots] = [annots, ref]
327
+ end
328
+ end
329
+
330
+ def register_field(field)
331
+ acroform = document.catalog.value[:AcroForm]
332
+ fields = acroform[:Fields]
333
+ ref = Pdfrb::Model::Reference.new(field.oid, field.gen)
334
+ fields << ref
335
+ end
336
+ end
337
+ end
338
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class Document
5
+ # Facade for managing ExtGState (graphics state) entries on pages.
6
+ # Each page can reference a /ExtGState sub-dict in /Resources that
7
+ # holds named graphics-state parameter sets (transparency, line
8
+ # width, overprint, etc.).
9
+ class GraphicsState
10
+ attr_reader :document
11
+
12
+ def initialize(document)
13
+ @document = document
14
+ @next_gs_number = 1
15
+ end
16
+
17
+ # Register an ExtGState parameter dict on a page and return the
18
+ # resource name (e.g., :GS1).
19
+ #
20
+ # @param page [Pdfrb::Model::Type::Page] target page.
21
+ # @param params [Hash] graphics state parameters (e.g.,
22
+ # { CA: 0.5, ca: 0.5 } for 50% stroke + fill alpha).
23
+ # @return [Symbol] resource name.
24
+ def register(page, **params)
25
+ gs = document.add({ Type: :ExtGState, **params },
26
+ type: Pdfrb::Model::Type::GraphicsStateParameter)
27
+ ref = Pdfrb::Model::Reference.new(gs.oid, gs.gen)
28
+
29
+ resources = ensure_resources(page)
30
+ ext_gstate = resources.value[:ExtGState] || {}
31
+ name = gs_name
32
+ ext_gstate[name] = ref
33
+ resources.value[:ExtGState] = ext_gstate
34
+ name
35
+ end
36
+
37
+ # Convenience: register a transparency state.
38
+ def register_transparency(page, opacity:, blend_mode: nil)
39
+ params = { ca: opacity, CA: opacity }
40
+ params[:BM] = blend_mode if blend_mode
41
+ register(page, **params)
42
+ end
43
+
44
+ # Convenience: register a line-width state.
45
+ def register_line_width(page, width:, line_cap: nil, line_join: nil)
46
+ params = { LW: width }
47
+ params[:LC] = line_cap if line_cap
48
+ params[:LJ] = line_join if line_join
49
+ register(page, **params)
50
+ end
51
+
52
+ # Enumerate ExtGState entries on a page.
53
+ def each(page, &block)
54
+ return enum_for(:each, page) unless block
55
+
56
+ resources = page_resources(page)
57
+ return unless resources
58
+
59
+ ext = resources.value[:ExtGState]
60
+ return unless ext
61
+
62
+ ext.each(&block)
63
+ end
64
+
65
+ def count(page)
66
+ each(page).count
67
+ end
68
+
69
+ private
70
+
71
+ def ensure_resources(page)
72
+ resources = page.value[:Resources]
73
+ return resources if resources.is_a?(Pdfrb::Model::Cos::Dictionary)
74
+
75
+ wrapped = Pdfrb::Model::Cos::Dictionary.new(resources || {})
76
+ page.value[:Resources] = wrapped
77
+ wrapped
78
+ end
79
+
80
+ def page_resources(page)
81
+ page.value[:Resources]
82
+ end
83
+
84
+ def gs_name
85
+ name = :"GS#{@next_gs_number}"
86
+ @next_gs_number += 1
87
+ name
88
+ end
89
+ end
90
+ end
91
+ end
@@ -18,8 +18,8 @@ module Pdfrb
18
18
  # Build an Image XObject from +io+ (path, IO, or bytes) and
19
19
  # register it in the document's /Resources/XObject. Returns the
20
20
  # resource name (e.g. :Im1).
21
- def add(io, **opts)
22
- image = Pdfrb::ImageLoader.load(document, io, **opts)
21
+ def add(io, **)
22
+ image = Pdfrb::ImageLoader.load(document, io, **)
23
23
  name = next_resource_name
24
24
  attach_to_resources(name, image)
25
25
  registry[name] = image
@@ -30,10 +30,10 @@ module Pdfrb
30
30
  registry[name]
31
31
  end
32
32
 
33
- def each(&block)
33
+ def each(&)
34
34
  return enum_for(:each) unless block_given?
35
35
 
36
- registry.each(&block)
36
+ registry.each(&)
37
37
  self
38
38
  end
39
39
 
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class Document
5
+ # Document Info dictionary facade (PDF 1.x metadata on /Info).
6
+ # Also bridges to XMP packet so both metadata representations stay
7
+ # in sync. PDF 2.0 deprecates /Info in favor of XMP-only, but
8
+ # /Info is still required for backward compatibility.
9
+ class Info
10
+ attr_reader :document
11
+
12
+ def initialize(document)
13
+ @document = document
14
+ end
15
+
16
+ FIELD_MAP = {
17
+ title: :Title,
18
+ author: :Author,
19
+ subject: :Subject,
20
+ keywords: :Keywords,
21
+ creator: :Creator,
22
+ producer: :Producer,
23
+ }.freeze
24
+
25
+ FIELD_MAP.each do |method_name, pdf_key|
26
+ define_method(method_name) do
27
+ info_dict[pdf_key]
28
+ end
29
+
30
+ define_method("#{method_name}=") do |value|
31
+ set_info_field(pdf_key, value)
32
+ end
33
+ end
34
+
35
+ def creation_date
36
+ info_dict[:CreationDate]
37
+ end
38
+
39
+ def creation_date=(value)
40
+ set_info_field(:CreationDate, pdf_date(value))
41
+ end
42
+
43
+ def mod_date
44
+ info_dict[:ModDate]
45
+ end
46
+
47
+ def mod_date=(value)
48
+ set_info_field(:ModDate, pdf_date(value))
49
+ end
50
+
51
+ def trapped
52
+ info_dict[:Trapped]
53
+ end
54
+
55
+ def trapped=(value)
56
+ set_info_field(:Trapped, value)
57
+ end
58
+
59
+ # Returns the /Info dictionary object (creating one if needed).
60
+ def info_dict
61
+ info_ref = document.trailer && document.trailer[:Info]
62
+ return document.object(info_ref) if info_ref
63
+
64
+ info = document.add({}, type: Pdfrb::Model::Cos::Dictionary)
65
+ if document.trailer
66
+ document.trailer[:Info] = Pdfrb::Model::Reference.new(info.oid, info.gen)
67
+ end
68
+ info
69
+ end
70
+
71
+ private
72
+
73
+ def set_info_field(field, value)
74
+ return if value.nil?
75
+
76
+ dict = info_dict
77
+ stored = field == :Trapped ? value : value.to_s
78
+ dict.value[field] = stored
79
+ sync_xmp(field, value)
80
+ end
81
+
82
+ def sync_xmp(field, value)
83
+ case field
84
+ when :Title then document.xmp.title = value
85
+ when :Author then document.xmp.creator = value
86
+ when :Subject then document.xmp.description = value
87
+ when :Keywords then document.xmp.keywords = value
88
+ when :Creator then document.xmp.creator_tool = value
89
+ when :Producer then document.xmp.producer = value
90
+ end
91
+ end
92
+
93
+ def pdf_date(time)
94
+ return time.to_s if time.is_a?(::String)
95
+
96
+ t = time.is_a?(::Time) ? time : Time.now
97
+ utc = t.utc
98
+ "D:#{utc.strftime('%Y%m%d%H%M%S')}+00'00'"
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class Document
5
+ # Facade for Optional Content Groups (layers). OCGs let authors
6
+ # mark content as belonging to a named layer that can be toggled
7
+ # on/off by the viewer (ISO 32000-2 §8.11).
8
+ #
9
+ # Each layer is a /Type /OCG dictionary with a /Name. The
10
+ # Catalog gets an /OCProperties entry:
11
+ # /OCGs — array of all OCG indirect refs.
12
+ # /D — default viewing config:
13
+ # /BaseState /ON, /OFF [list of off-by-default OCGs], etc.
14
+ class Layers
15
+ attr_reader :document, :groups
16
+
17
+ def initialize(document)
18
+ @document = document
19
+ @groups = []
20
+ end
21
+
22
+ # Add a named layer. @return [Pdfrb::Model::Cos::Dictionary] the OCG.
23
+ def add(name, default_on: true)
24
+ ensure_oc_properties
25
+ ocg = document.add(
26
+ { Type: :OCG, Name: name },
27
+ type: Pdfrb::Model::Cos::Dictionary
28
+ )
29
+ ref = Pdfrb::Model::Reference.new(ocg.oid, ocg.gen)
30
+ @groups << { ref: ref, ocg: ocg, default_on: default_on }
31
+ ocg
32
+ end
33
+
34
+ # Write /OCProperties to the Catalog. Called automatically by #add
35
+ # but can be called again to refresh after external modifications.
36
+ def sync!
37
+ return if @groups.empty?
38
+
39
+ ocgs_array = @groups.map { |g| g[:ref] }
40
+ off_array = @groups.reject { |g| g[:default_on] }.map { |g| g[:ref] }
41
+
42
+ default_config = { BaseState: :ON }
43
+ default_config[:OFF] = off_array unless off_array.empty?
44
+
45
+ catalog = document.catalog
46
+ catalog.value[:OCProperties] = {
47
+ OCGs: ocgs_array,
48
+ D: default_config,
49
+ }
50
+ end
51
+
52
+ private
53
+
54
+ def ensure_oc_properties
55
+ catalog = document.catalog
56
+ return if catalog.value[:OCProperties]
57
+
58
+ catalog.value[:OCProperties] = { OCGs: [], D: { BaseState: :ON } }
59
+ end
60
+ end
61
+ end
62
+ end
@@ -2,10 +2,6 @@
2
2
 
3
3
  module Pdfrb
4
4
  class Document
5
- # Document metadata facade. Reads/writes Title, Author, Subject,
6
- # Keywords, Creator, Producer, CreationDate, ModDate via the
7
- # trailer's /Info dict (PDF 1.x) and prefers /Catalog/Metadata
8
- # XMP for PDF 2.0 per App Note 003.
9
5
  class Metadata
10
6
  STANDARD_FIELDS = %i[Title Author Subject Keywords Creator
11
7
  Producer CreationDate ModDate Trapped].freeze
@@ -29,6 +25,17 @@ module Pdfrb
29
25
  write_field(field, value)
30
26
  end
31
27
 
28
+ def to_xmp
29
+ begin
30
+ packet = Pdfrb::XMP::Packet.new
31
+ rescue StandardError
32
+ return ""
33
+ end
34
+ title = read_field(:Title)
35
+ packet.title = title if title
36
+ packet.to_xmp
37
+ end
38
+
32
39
  private
33
40
 
34
41
  def read_field(field)
@@ -54,7 +61,7 @@ module Pdfrb
54
61
  obj = ref ? document.object(ref) : nil
55
62
 
56
63
  if obj.nil? && create
57
- obj = document.add({ Type: :Metadata }, type: Pdfrb::Model::Type::Info)
64
+ obj = document.add({}, type: Pdfrb::Model::Type::Info)
58
65
  document.trailer[:Info] = Pdfrb::Model::Reference.new(obj.oid, obj.gen)
59
66
  end
60
67
  @cached_info = obj