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
@@ -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
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class Document
5
+ # Output Intents facade. An output intent declares the intended
6
+ # output device color profile (ICC) so that downstream processors
7
+ # can render colors consistently. Required for PDF/X.
8
+ #
9
+ # /OutputIntents is an array on the Catalog. Each entry has:
10
+ # /Type /OutputIntent
11
+ # /S /GTS_PDFA1 (for PDF/A) or /GTS_PDFX (for PDF/X)
12
+ # /OutputConditionIdentifier — registry name (e.g. "FOGRA39")
13
+ # /OutputCondition — human-readable description
14
+ # /RegistryName — e.g. "http://www.color.org"
15
+ # /DestOutputProfile — ICC profile stream reference
16
+ class OutputIntents
17
+ attr_reader :document
18
+
19
+ def initialize(document)
20
+ @document = document
21
+ end
22
+
23
+ # Add an output intent referencing an ICC profile stream.
24
+ # @param icc_stream_ref [Pdfrb::Model::Reference] ICC profile stream.
25
+ # @param identifier [String] e.g. "FOGRA39", "CGATS TR 001".
26
+ # @param condition [String] human-readable description.
27
+ # @param registry [String] URL of the color registry.
28
+ # @param subtype [Symbol] :GTS_PDFA1 or :GTS_PDFX.
29
+ # @return [Pdfrb::Model::Cos::Dictionary] the output intent dict.
30
+ def add(icc_stream_ref, identifier:, condition: nil,
31
+ registry: nil, subtype: :GTS_PDFX)
32
+ intent = document.add(
33
+ {
34
+ Type: :OutputIntent,
35
+ S: subtype,
36
+ OutputConditionIdentifier: identifier,
37
+ OutputCondition: condition,
38
+ RegistryName: registry,
39
+ DestOutputProfile: icc_stream_ref,
40
+ }.compact,
41
+ type: Pdfrb::Model::Cos::Dictionary
42
+ )
43
+ catalog = document.catalog
44
+ intents = catalog.value[:OutputIntents]
45
+ ref = Pdfrb::Model::Reference.new(intent.oid, intent.gen)
46
+ if intents.nil?
47
+ catalog.value[:OutputIntents] = [ref]
48
+ elsif intents.is_a?(::Array)
49
+ intents << ref
50
+ else
51
+ catalog.value[:OutputIntents] = [intents, ref]
52
+ end
53
+ intent
54
+ end
55
+
56
+ # Convenience: embed an ICC profile and register as output intent.
57
+ def embed_icc(icc_bytes, identifier:, condition: nil,
58
+ registry: nil, subtype: :GTS_PDFX)
59
+ profile = Pdfrb::Color::ICCProfile.new(icc_bytes)
60
+ stream = document.add(
61
+ { **profile.stream_dictionary_fields,
62
+ Length: profile.raw_data.bytesize },
63
+ type: Pdfrb::Model::Cos::Stream
64
+ )
65
+ stream.stream = profile.raw_data
66
+ ref = Pdfrb::Model::Reference.new(stream.oid, stream.gen)
67
+ add(ref, identifier: identifier, condition: condition, registry: registry, subtype: subtype)
68
+ end
69
+
70
+ # Enumerate output intents.
71
+ def each(&block)
72
+ intents = document.catalog.value[:OutputIntents]
73
+ return enum_for(:each) unless block
74
+ return self unless intents
75
+
76
+ intents.each do |ref|
77
+ obj = ref.is_a?(Pdfrb::Model::Reference) ? document.object(ref) : ref
78
+ yield obj if obj
79
+ end
80
+ self
81
+ end
82
+
83
+ def count
84
+ intents = document.catalog.value[:OutputIntents]
85
+ return 0 unless intents
86
+
87
+ intents.is_a?(::Array) ? intents.length : 1
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class Document
5
+ # Page Labels facade. Generates the /PageLabels number tree on the
6
+ # Catalog for custom page numbering (roman numerals, prefixed
7
+ # arabic, etc.).
8
+ #
9
+ # Usage:
10
+ # doc.page_labels.set do
11
+ # style :roman, count: 5 # i, ii, iii, iv, v
12
+ # style :decimal, prefix: "A-" # A-1, A-2, ...
13
+ # end
14
+ class PageLabels
15
+ STYLES = {
16
+ decimal: :D,
17
+ roman_lower: :r,
18
+ roman_upper: :R,
19
+ alpha_lower: :a,
20
+ alpha_upper: :A,
21
+ }.freeze
22
+
23
+ LabelRule = Struct.new(:style, :prefix, :start, :page_count, keyword_init: true)
24
+
25
+ attr_reader :document, :rules
26
+
27
+ def initialize(document)
28
+ @document = document
29
+ @rules = []
30
+ @current_index = 0
31
+ end
32
+
33
+ # Add a page labeling rule for the next batch of pages.
34
+ # @param style [Symbol] :decimal, :roman_lower, :roman_upper,
35
+ # :alpha_lower, :alpha_upper.
36
+ # @param prefix [String] prefix prepended to each label.
37
+ # @param start [Integer] starting number (default 1).
38
+ # @param count [Integer, nil] number of pages this rule covers
39
+ # (nil = until next rule or end).
40
+ def style(style, prefix: nil, start: 1, count: nil)
41
+ @rules << LabelRule.new(
42
+ style: STYLES.fetch(style),
43
+ prefix: prefix,
44
+ start: start,
45
+ page_count: count
46
+ )
47
+ @current_index += count if count
48
+ self
49
+ end
50
+
51
+ # Build the /PageLabels number tree and set it on the Catalog.
52
+ def commit!
53
+ return if @rules.empty?
54
+
55
+ nums = build_nums_array
56
+ document.catalog.value[:PageLabels] = { Nums: nums }
57
+ end
58
+
59
+ private
60
+
61
+ def build_nums_array
62
+ nums = []
63
+ index = 0
64
+ @rules.each do |rule|
65
+ nums << index
66
+ nums << rule_dict(rule)
67
+ index += rule.page_count if rule.page_count
68
+ end
69
+ nums
70
+ end
71
+
72
+ def rule_dict(rule)
73
+ dict = { S: rule.style }
74
+ dict[:P] = rule.prefix if rule.prefix
75
+ dict[:St] = rule.start unless rule.start == 1
76
+ dict
77
+ end
78
+ end
79
+ end
80
+ end
@@ -20,19 +20,24 @@ module Pdfrb
20
20
  # rotate: 0 # degrees
21
21
  #
22
22
  # Returns the new +Model::Type::Page+.
23
- def add(media_box: [0, 0, 612, 792], rotate: 0)
23
+ def add(media_box: nil, rotate: 0, width: nil, height: nil,
24
+ bleed_box: nil, trim_box: nil, art_box: nil, crop_box: nil)
25
+ media_box ||= (width && height ? [0, 0, width, height] : [0, 0, 612, 792])
26
+ media_box ||= (width && height ? [0, 0, width, height] : [0, 0, 612, 792])
24
27
  root = pages_root
25
28
  contents = document.add({}, type: Pdfrb::Model::Cos::Stream)
26
- page = document.add(
27
- {
28
- Type: :Page,
29
- Parent: Pdfrb::Model::Reference.new(root.oid, root.gen),
30
- MediaBox: media_box,
31
- Resources: {},
32
- Contents: Pdfrb::Model::Reference.new(contents.oid, 0)
33
- },
34
- type: Pdfrb::Model::Type::Page
35
- )
29
+ page_hash = {
30
+ Type: :Page,
31
+ Parent: Pdfrb::Model::Reference.new(root.oid, root.gen),
32
+ MediaBox: media_box,
33
+ Resources: {},
34
+ Contents: Pdfrb::Model::Reference.new(contents.oid, 0)
35
+ }
36
+ page_hash[:BleedBox] = bleed_box if bleed_box
37
+ page_hash[:TrimBox] = trim_box if trim_box
38
+ page_hash[:ArtBox] = art_box if art_box
39
+ page_hash[:CropBox] = crop_box if crop_box
40
+ page = document.add(page_hash, type: Pdfrb::Model::Type::Page)
36
41
  page.value[:Rotate] = rotate if rotate.nonzero?
37
42
  kids = (root.value[:Kids] ||= [])
38
43
  kids << Pdfrb::Model::Reference.new(page.oid, page.gen)
@@ -41,10 +46,10 @@ module Pdfrb
41
46
  end
42
47
  alias << add
43
48
 
44
- def each(&block)
49
+ def each(&)
45
50
  return enum_for(:each) unless block_given?
46
51
 
47
- walk(pages_root, &block)
52
+ walk(pages_root, &)
48
53
  self
49
54
  end
50
55
 
@@ -83,6 +88,52 @@ module Pdfrb
83
88
  page
84
89
  end
85
90
 
91
+ # Delete the page at +index+ (0-based).
92
+ def delete_at(index)
93
+ page = self[index]
94
+ return nil unless page
95
+
96
+ delete(page)
97
+ end
98
+
99
+ # Set /Rotate on every page. +angle+ must be 0, 90, 180, or 270.
100
+ def rotate(angle)
101
+ each { |page| page.value[:Rotate] = angle }
102
+ self
103
+ end
104
+
105
+ # Move a page from one position to another.
106
+ # @param from [Integer] source index (0-based).
107
+ # @param to [Integer] destination index (0-based).
108
+ def move(from, to)
109
+ return self if from == to
110
+
111
+ root = pages_root
112
+ kids = root.value[:Kids]
113
+ return self unless kids.is_a?(::Array)
114
+
115
+ page = self[from]
116
+ return self unless page
117
+
118
+ ref = ref_to(page)
119
+ kids.delete(ref)
120
+ # Clamp to valid range after deletion.
121
+ insert_at = to > from ? to - 1 : to
122
+ kids.insert(insert_at, ref)
123
+ self
124
+ end
125
+
126
+ # Iterate pages with index.
127
+ def each_with_index(&)
128
+ return enum_for(:each_with_index) unless block_given?
129
+
130
+ i = 0
131
+ each do |page|
132
+ yield page, i
133
+ i += 1
134
+ end
135
+ end
136
+
86
137
  private
87
138
 
88
139
  def pages_root
@@ -100,26 +151,24 @@ module Pdfrb
100
151
  end
101
152
 
102
153
  def walk(node, &block)
103
- kids = node.respond_to?(:value) ? node.value[:Kids] : node[:Kids]
154
+ kids = node.is_a?(Pdfrb::Model::Cos::Dictionary) ? node.value[:Kids] : node[:Kids]
104
155
  return unless kids
105
156
 
106
- # Kids may itself be an indirect reference, especially in
107
- # compressed-object PDFs (FOP output).
108
157
  kids = document.object(kids).value if kids.is_a?(Pdfrb::Model::Reference)
109
- kids = kids.value if kids.respond_to?(:value) && !kids.is_a?(::Hash)
158
+ kids = kids.value if kids.is_a?(Pdfrb::Model::Cos::Dictionary) && !kids.is_a?(::Hash)
110
159
 
111
160
  Array(kids).each do |kid_ref|
112
161
  kid = kid_ref.is_a?(Pdfrb::Model::Reference) ?
113
162
  document.object(kid_ref) : kid_ref
114
163
  next unless kid
115
164
 
116
- type = kid.respond_to?(:value) ? kid.value[:Type] : nil
165
+ type = kid.is_a?(Pdfrb::Model::Cos::Dictionary) ? kid.value[:Type] : nil
117
166
  case type
118
167
  when :Pages then walk(kid, &block)
119
168
  when :Page then yield kid
120
169
  else
121
170
  # Unknown type — best effort, treat as a Page.
122
- yield kid if kid.respond_to?(:value)
171
+ yield kid if kid.is_a?(Pdfrb::Model::Cos::Dictionary)
123
172
  end
124
173
  end
125
174
  end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class Document
5
+ # PDF Portfolio facade. A portfolio is a PDF with a /Collection
6
+ # dictionary on the Catalog that provides a collection view of
7
+ # embedded files (ISO 32000-2 §12.3.5).
8
+ #
9
+ # Inspired by mn2pdf's PDFPortfolio (which uses PDFBox). Provides
10
+ # field schema, default view configuration, and per-item metadata.
11
+ class Portfolio
12
+ Field = Struct.new(:name, :type, :subtype, :order, keyword_init: true) do
13
+ def pdf_hash
14
+ h = { N: name, T: type }
15
+ h[:FT] = type unless type == :text
16
+ h[:Subtype] = subtype if subtype
17
+ h[:O] = order if order
18
+ h
19
+ end
20
+ end
21
+
22
+ Item = Struct.new(:name, :file_bytes, :mime_type,
23
+ :description, :fields, keyword_init: true)
24
+
25
+ DEFAULT_FIELDS = [
26
+ { name: "Name", type: :text }.freeze,
27
+ { name: "Description", type: :text, subtype: :Description }.freeze,
28
+ { name: "Size", type: :number }.freeze,
29
+ { name: "Modified", type: :date, subtype: :CreationDate }.freeze,
30
+ ].freeze
31
+
32
+ attr_reader :document, :items, :schema_fields
33
+
34
+ def initialize(document)
35
+ @document = document
36
+ @items = []
37
+ @schema_fields = DEFAULT_FIELDS.map { |f| Field.new(**f) }
38
+ end
39
+
40
+ # Add a schema field. Order matters for display.
41
+ def add_field(name, type: :text, subtype: nil, order: nil)
42
+ @schema_fields << Field.new(
43
+ name: name,
44
+ type: type,
45
+ subtype: subtype,
46
+ order: order || @schema_fields.length
47
+ )
48
+ end
49
+
50
+ # Add an item (embedded file) to the portfolio.
51
+ def add_item(name, file_bytes, mime_type: nil,
52
+ description: nil, fields: {})
53
+ @items << Item.new(
54
+ name: name,
55
+ file_bytes: file_bytes,
56
+ mime_type: mime_type,
57
+ description: description,
58
+ fields: fields
59
+ )
60
+ end
61
+
62
+ # Commit the portfolio configuration to the document.
63
+ # Sets /Collection on the Catalog, creates /Filespec for each item,
64
+ # and embeds the file data via /EF.
65
+ def commit!
66
+ catalog = document.catalog
67
+ catalog.value[:Collection] = collection_dict
68
+ end
69
+
70
+ private
71
+
72
+ def collection_dict
73
+ {
74
+ Type: :Collection,
75
+ Schema: schema_fields.map(&:pdf_hash),
76
+ D: {
77
+ View: :D,
78
+ Hide: false,
79
+ Order: @schema_fields.map(&:name),
80
+ },
81
+ Items: items_references,
82
+ }
83
+ end
84
+
85
+ def items_references
86
+ @items.map do |item|
87
+ filespec = document.add(
88
+ {
89
+ Type: :Filespec,
90
+ F: item.name,
91
+ UF: item.name,
92
+ Desc: item.description,
93
+ }.compact,
94
+ type: Pdfrb::Model::Cos::Dictionary
95
+ )
96
+ ef = document.add(
97
+ {
98
+ F: embedded_file_ref(item),
99
+ UF: embedded_file_ref(item),
100
+ },
101
+ type: Pdfrb::Model::Cos::Dictionary
102
+ )
103
+ filespec.value[:EF] = Pdfrb::Model::Reference.new(ef.oid, ef.gen)
104
+ filespec.value[:AFRelationship] = :Unspecified
105
+ Pdfrb::Model::Reference.new(filespec.oid, filespec.gen)
106
+ end
107
+ end
108
+
109
+ def embedded_file_ref(item)
110
+ embedded = document.add(
111
+ { Type: :EmbeddedFile, Length: item.file_bytes.bytesize },
112
+ type: Pdfrb::Model::Cos::Stream
113
+ )
114
+ embedded.stream = item.file_bytes
115
+ Pdfrb::Model::Reference.new(embedded.oid, embedded.gen)
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class Document
5
+ # Gradient shading patterns facade. Creates Type 2 (axial) and
6
+ # Type 3 (radial) shading dictionaries and registers them in page
7
+ # /Resources /Shading.
8
+ #
9
+ # For 2-stop gradients, uses a Type 2 ExponentialInterpolation
10
+ # function (/FunctionType 2). For multi-stop, chains Type 3
11
+ # StitchingFunction.
12
+ class Shadings
13
+ attr_reader :document, :registry
14
+
15
+ def initialize(document)
16
+ @document = document
17
+ @registry = {}
18
+ @next_id = 1
19
+ end
20
+
21
+ # Add an axial (linear) gradient.
22
+ # @param from [Array(Numeric, Numeric)] start point [x, y].
23
+ # @param to [Array(Numeric, Numeric)] end point [x, y].
24
+ # @param stops [Array<Array>] gradient stops:
25
+ # [[offset, [color_family, *values]], ...]
26
+ # @param extend [Array<Boolean, Boolean>] extend before/after.
27
+ # @return [Symbol] shading resource name (e.g., :Sh1).
28
+ def add_axial(from:, to:, stops:, extend: [true, true])
29
+ color_space = stops_to_color_values(stops.first || [0, [:gray, 0]])[0]
30
+ name = next_name
31
+
32
+ function = build_gradient_function(stops, color_space)
33
+
34
+ shading = document.add(
35
+ {
36
+ ShadingType: 2,
37
+ ColorSpace: color_space,
38
+ Coords: Pdfrb::Model::PdfArray.new([*from, *to]),
39
+ Function: function,
40
+ Extend: Pdfrb::Model::PdfArray.new(extend),
41
+ },
42
+ type: Pdfrb::Model::Cos::Dictionary
43
+ )
44
+ register(name, shading)
45
+ name
46
+ end
47
+
48
+ # Add a radial gradient.
49
+ # @param from [Array(Numeric, Numeric, Numeric)] [cx, cy, radius].
50
+ # @param to [Array(Numeric, Numeric, Numeric)] [cx, cy, radius].
51
+ # @param stops [Array<Array>] same format as axial.
52
+ # @param extend [Array<Boolean, Boolean>] extend before/after.
53
+ # @return [Symbol] shading resource name.
54
+ def add_radial(from:, to:, stops:, extend: [true, true])
55
+ color_space = stops_to_color_values(stops.first || [0, [:gray, 0]])[0]
56
+ name = next_name
57
+
58
+ function = build_gradient_function(stops, color_space)
59
+
60
+ shading = document.add(
61
+ {
62
+ ShadingType: 3,
63
+ ColorSpace: color_space,
64
+ Coords: Pdfrb::Model::PdfArray.new([*from, *to]),
65
+ Function: function,
66
+ Extend: Pdfrb::Model::PdfArray.new(extend),
67
+ },
68
+ type: Pdfrb::Model::Cos::Dictionary
69
+ )
70
+ register(name, shading)
71
+ name
72
+ end
73
+
74
+ def [](name)
75
+ @registry[name]
76
+ end
77
+
78
+ def each(&block)
79
+ return enum_for(:each) unless block
80
+
81
+ @registry.each(&block)
82
+ self
83
+ end
84
+
85
+ private
86
+
87
+ def build_gradient_function(stops, color_space)
88
+ return build_exponential_function(nil, nil, color_space) if stops.empty?
89
+
90
+ if stops.length == 2
91
+ _, c0 = stops_to_color_values(stops[0])
92
+ _, c1 = stops_to_color_values(stops[1])
93
+ build_exponential_function(c0, c1, color_space)
94
+ else
95
+ build_stitching_function(stops, color_space)
96
+ end
97
+ end
98
+
99
+ def build_stitching_function(stops, color_space)
100
+ functions = stops.each_cons(2).map do |s0, s1|
101
+ _, c0 = stops_to_color_values(s0)
102
+ _, c1 = stops_to_color_values(s1)
103
+ build_exponential_function(c0, c1, color_space)
104
+ end
105
+
106
+ bounds = stops[1..-2].map { |s| s[0] }
107
+
108
+ document.add(
109
+ {
110
+ FunctionType: 3,
111
+ Domain: Pdfrb::Model::PdfArray.new([0, 1]),
112
+ Functions: functions.map { |f| Pdfrb::Model::Reference.new(f.oid, f.gen) },
113
+ Bounds: Pdfrb::Model::PdfArray.new(bounds),
114
+ Encode: Pdfrb::Model::PdfArray.new(stops.each_cons(2).map { [0, 1] }.flatten),
115
+ },
116
+ type: Pdfrb::Model::Cos::Dictionary
117
+ )
118
+ end
119
+
120
+ def stops_to_color_values(stop)
121
+ family, *values = stop[1]
122
+ cs = case family.to_sym
123
+ when :rgb then :DeviceRGB
124
+ when :cmyk then :DeviceCMYK
125
+ else :DeviceGray
126
+ end
127
+ [cs, values]
128
+ end
129
+
130
+ def build_exponential_function(c0, c1, _color_space)
131
+ document.add(
132
+ {
133
+ FunctionType: 2,
134
+ Domain: Pdfrb::Model::PdfArray.new([0, 1]),
135
+ C0: Pdfrb::Model::PdfArray.new(c0),
136
+ C1: Pdfrb::Model::PdfArray.new(c1),
137
+ N: 1,
138
+ },
139
+ type: Pdfrb::Model::Cos::Dictionary
140
+ )
141
+ end
142
+
143
+ def register(name, shading_obj)
144
+ @registry[name] = shading_obj
145
+ catalog = document.catalog
146
+ catalog.value[:Resources] ||= {}
147
+ catalog.value[:Resources][:Shading] ||= {}
148
+ catalog.value[:Resources][:Shading][name] =
149
+ Pdfrb::Model::Reference.new(shading_obj.oid, shading_obj.gen)
150
+ end
151
+
152
+ def next_name
153
+ name = :"Sh#{@next_id}"
154
+ @next_id += 1
155
+ name
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ class Document
5
+ # Page stamping facade. Stamps a Form XObject (logo, watermark,
6
+ # page number) onto every page of the document by emitting the
7
+ # Do operator in each page's content stream.
8
+ class Stamps
9
+ attr_reader :document
10
+
11
+ def initialize(document)
12
+ @document = document
13
+ end
14
+
15
+ # Stamp a Form XObject on every page.
16
+ #
17
+ # @param form_xobject [Pdfrb::Model::Cos::Stream] the Form XObject.
18
+ # @param at [Array<Numeric>] x, y position in points.
19
+ # @return [Symbol] the resource name (e.g., :St1).
20
+ def stamp_everywhere(form_xobject, at: [0, 0])
21
+ name = next_stamp_name
22
+ ref = Pdfrb::Model::Reference.new(form_xobject.oid, form_xobject.gen)
23
+
24
+ document.pages.each do |page|
25
+ register_xobject_in_resources(page, name, ref)
26
+ emit_do_operator(page, name, at)
27
+ end
28
+ name
29
+ end
30
+
31
+ # Stamp a Form XObject on a single page.
32
+ def stamp_page(page, form_xobject, at: [0, 0])
33
+ name = next_stamp_name
34
+ ref = Pdfrb::Model::Reference.new(form_xobject.oid, form_xobject.gen)
35
+ register_xobject_in_resources(page, name, ref)
36
+ emit_do_operator(page, name, at)
37
+ name
38
+ end
39
+
40
+ private
41
+
42
+ def next_stamp_name
43
+ @stamp_counter ||= 0
44
+ @stamp_counter += 1
45
+ :"St#{@stamp_counter}"
46
+ end
47
+
48
+ def register_xobject_in_resources(page, name, ref)
49
+ resources = page.value[:Resources]
50
+ resources = page.value[:Resources] = {} unless resources.is_a?(Pdfrb::Model::Cos::Dictionary)
51
+ xobjects = resources.value[:XObject] || {}
52
+ xobjects[name] = ref
53
+ resources.value[:XObject] = xobjects
54
+ end
55
+
56
+ def emit_do_operator(page, name, at)
57
+ canvas = page.canvas
58
+ x, y = at
59
+ canvas.save_graphics_state
60
+ canvas.translate(x, y) if x != 0 || y != 0
61
+ canvas.emit_op(Pdfrb::Content::Operator::InvokeXObject, name)
62
+ canvas.restore_graphics_state
63
+ rescue StandardError
64
+ # Canvas may not be available on pages with no content stream yet.
65
+ end
66
+ end
67
+ end
68
+ end