pdfrb 0.3.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (404) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/Gemfile +1 -0
  4. data/PROPOSAL.canvas-clip.md +92 -0
  5. data/PROPOSAL.canvas-draw-image-matrix.md +87 -0
  6. data/PROPOSAL.canvas-rich-text.md +103 -0
  7. data/PROPOSAL.canvas-text-lines.md +77 -0
  8. data/PROPOSAL.canvas-transparency.md +106 -0
  9. data/PROPOSAL.external-font-metrics.md +129 -0
  10. data/PROPOSAL.font-embedding-fontfile2.md +88 -0
  11. data/PROPOSAL.font-resolver-ttc.md +105 -0
  12. data/PROPOSAL.font-resolver.md +96 -0
  13. data/PROPOSAL.font-subsetting.md +98 -0
  14. data/PROPOSAL.gradient-shadings.md +110 -0
  15. data/PROPOSAL.page-mediabox-setter.md +66 -0
  16. data/PROPOSAL.per-glyph-width.md +91 -0
  17. data/PROPOSAL.stringio-require.md +53 -0
  18. data/PROPOSAL.struct-tree-root.md +102 -0
  19. data/PROPOSAL.text-measurement.md +93 -0
  20. data/PROPOSAL.ttf-table-parsing-fix.md +112 -0
  21. data/RELEASE.md +48 -0
  22. data/lib/pdfrb/action/base.rb +21 -0
  23. data/lib/pdfrb/action/types.rb +178 -0
  24. data/lib/pdfrb/action.rb +55 -0
  25. data/lib/pdfrb/annotation/base.rb +79 -0
  26. data/lib/pdfrb/annotation/types.rb +222 -0
  27. data/lib/pdfrb/annotation.rb +78 -0
  28. data/lib/pdfrb/appearance/generator.rb +216 -0
  29. data/lib/pdfrb/appearance.rb +11 -0
  30. data/lib/pdfrb/arlington/object_definition.rb +2 -2
  31. data/lib/pdfrb/arlington/predicate/evaluator.rb +1 -1
  32. data/lib/pdfrb/arlington/predicate/functions.rb +25 -9
  33. data/lib/pdfrb/arlington/predicate.rb +1 -5
  34. data/lib/pdfrb/cli/batch.rb +18 -0
  35. data/lib/pdfrb/cli/debug_info.rb +18 -0
  36. data/lib/pdfrb/cli/files.rb +16 -0
  37. data/lib/pdfrb/cli/fonts.rb +16 -0
  38. data/lib/pdfrb/cli/form.rb +18 -0
  39. data/lib/pdfrb/cli/image2pdf.rb +18 -0
  40. data/lib/pdfrb/cli/images.rb +34 -0
  41. data/lib/pdfrb/cli/info.rb +20 -0
  42. data/lib/pdfrb/cli/inspect.rb +18 -0
  43. data/lib/pdfrb/cli/merge.rb +18 -0
  44. data/lib/pdfrb/cli/modify.rb +18 -0
  45. data/lib/pdfrb/cli/optimize.rb +18 -0
  46. data/lib/pdfrb/cli/split.rb +21 -0
  47. data/lib/pdfrb/cli/watermark.rb +18 -0
  48. data/lib/pdfrb/cli.rb +291 -7
  49. data/lib/pdfrb/color/color_space.rb +259 -0
  50. data/lib/pdfrb/color/icc_profile.rb +90 -0
  51. data/lib/pdfrb/color.rb +12 -0
  52. data/lib/pdfrb/compare/comparator.rb +244 -0
  53. data/lib/pdfrb/compare/report.rb +78 -0
  54. data/lib/pdfrb/compare.rb +42 -0
  55. data/lib/pdfrb/composer.rb +161 -0
  56. data/lib/pdfrb/conformance/pdf_a.rb +317 -56
  57. data/lib/pdfrb/conformance/pdf_ua.rb +186 -29
  58. data/lib/pdfrb/conformance/pdf_x.rb +247 -0
  59. data/lib/pdfrb/conformance/rule.rb +78 -0
  60. data/lib/pdfrb/conformance/structure_elements.rb +174 -0
  61. data/lib/pdfrb/conformance/verapdf_bridge.rb +92 -0
  62. data/lib/pdfrb/conformance.rb +7 -3
  63. data/lib/pdfrb/content/canvas.rb +217 -72
  64. data/lib/pdfrb/content/color_space.rb +157 -0
  65. data/lib/pdfrb/content/hidden_text_detector.rb +167 -0
  66. data/lib/pdfrb/content/operator.rb +21 -1
  67. data/lib/pdfrb/content/operators/clipping.rb +36 -0
  68. data/lib/pdfrb/content/operators/graphics_state_params.rb +15 -0
  69. data/lib/pdfrb/content/operators.rb +21 -0
  70. data/lib/pdfrb/content/shading/base.rb +139 -0
  71. data/lib/pdfrb/content/shading.rb +17 -0
  72. data/lib/pdfrb/content/smart_text_extractor.rb +85 -0
  73. data/lib/pdfrb/content/tiling_pattern.rb +78 -0
  74. data/lib/pdfrb/content/transformation_matrix.rb +61 -0
  75. data/lib/pdfrb/content.rb +7 -26
  76. data/lib/pdfrb/destination/fit.rb +185 -0
  77. data/lib/pdfrb/destination.rb +13 -0
  78. data/lib/pdfrb/digital_signature/cms_handler.rb +22 -0
  79. data/lib/pdfrb/digital_signature/signing.rb +107 -11
  80. data/lib/pdfrb/digital_signature/timestamp_handler.rb +45 -0
  81. data/lib/pdfrb/digital_signature/verification.rb +88 -9
  82. data/lib/pdfrb/digital_signature/verification_result.rb +14 -0
  83. data/lib/pdfrb/digital_signature.rb +26 -2
  84. data/lib/pdfrb/document/annotations.rb +120 -3
  85. data/lib/pdfrb/document/associated_files.rb +104 -0
  86. data/lib/pdfrb/document/colors.rb +95 -0
  87. data/lib/pdfrb/document/destinations.rb +127 -5
  88. data/lib/pdfrb/document/display.rb +84 -0
  89. data/lib/pdfrb/document/fonts.rb +417 -45
  90. data/lib/pdfrb/document/form.rb +338 -0
  91. data/lib/pdfrb/document/graphics_state.rb +91 -0
  92. data/lib/pdfrb/document/images.rb +4 -4
  93. data/lib/pdfrb/document/info.rb +102 -0
  94. data/lib/pdfrb/document/layers.rb +62 -0
  95. data/lib/pdfrb/document/metadata.rb +12 -5
  96. data/lib/pdfrb/document/output_intents.rb +91 -0
  97. data/lib/pdfrb/document/page_labels.rb +80 -0
  98. data/lib/pdfrb/document/pages.rb +68 -19
  99. data/lib/pdfrb/document/portfolio.rb +119 -0
  100. data/lib/pdfrb/document/shadings.rb +159 -0
  101. data/lib/pdfrb/document/stamps.rb +68 -0
  102. data/lib/pdfrb/document/structure.rb +242 -0
  103. data/lib/pdfrb/document.rb +73 -4
  104. data/lib/pdfrb/encryption/identity.rb +13 -0
  105. data/lib/pdfrb/encryption/password_verification.rb +53 -8
  106. data/lib/pdfrb/encryption/security_handler.rb +8 -5
  107. data/lib/pdfrb/encryption/standard_security_handler.rb +154 -81
  108. data/lib/pdfrb/encryption.rb +17 -0
  109. data/lib/pdfrb/error.rb +2 -2
  110. data/lib/pdfrb/filter/brotli_decode.rb +32 -0
  111. data/lib/pdfrb/filter/ccitt_fax_decode.rb +12 -0
  112. data/lib/pdfrb/filter/crypt.rb +2 -2
  113. data/lib/pdfrb/filter/dct_decode.rb +18 -0
  114. data/lib/pdfrb/filter/flate_decode.rb +4 -18
  115. data/lib/pdfrb/filter/jbig2_decode.rb +12 -0
  116. data/lib/pdfrb/filter/jpx_decode.rb +18 -0
  117. data/lib/pdfrb/filter/lzw_decode.rb +3 -2
  118. data/lib/pdfrb/filter/png_predictor.rb +94 -0
  119. data/lib/pdfrb/filter.rb +26 -13
  120. data/lib/pdfrb/font/afm_parser.rb +50 -26
  121. data/lib/pdfrb/font/cmap/writer.rb +9 -0
  122. data/lib/pdfrb/font/encoding/win_ansi_encoding.rb +17 -0
  123. data/lib/pdfrb/font/encoding.rb +32 -3
  124. data/lib/pdfrb/font/metrics_helper.rb +121 -0
  125. data/lib/pdfrb/font/true_type/file.rb +10 -1
  126. data/lib/pdfrb/font/true_type/glyf.rb +82 -0
  127. data/lib/pdfrb/font/true_type/kern.rb +58 -0
  128. data/lib/pdfrb/font/true_type/loca.rb +42 -0
  129. data/lib/pdfrb/font/true_type/maxp.rb +44 -0
  130. data/lib/pdfrb/font/true_type/name.rb +53 -0
  131. data/lib/pdfrb/font/true_type/post.rb +93 -0
  132. data/lib/pdfrb/font/true_type/subsetter.rb +3 -5
  133. data/lib/pdfrb/font/true_type/wrapper.rb +102 -0
  134. data/lib/pdfrb/font/true_type.rb +7 -0
  135. data/lib/pdfrb/font.rb +2 -0
  136. data/lib/pdfrb/font_loader/from_configuration.rb +19 -0
  137. data/lib/pdfrb/font_loader/from_file.rb +20 -0
  138. data/lib/pdfrb/font_loader/standard14.rb +20 -0
  139. data/lib/pdfrb/font_loader/variant_from_name.rb +36 -0
  140. data/lib/pdfrb/font_loader.rb +4 -0
  141. data/lib/pdfrb/font_resolver.rb +138 -0
  142. data/lib/pdfrb/image/audit.rb +94 -0
  143. data/lib/pdfrb/image/downsampler.rb +124 -0
  144. data/lib/pdfrb/image.rb +13 -0
  145. data/lib/pdfrb/image_loader/jpeg.rb +39 -58
  146. data/lib/pdfrb/image_loader/png.rb +31 -257
  147. data/lib/pdfrb/layout/bidi.rb +198 -0
  148. data/lib/pdfrb/layout/box.rb +49 -0
  149. data/lib/pdfrb/layout/box_fitter.rb +54 -0
  150. data/lib/pdfrb/layout/column_box.rb +71 -0
  151. data/lib/pdfrb/layout/container_box.rb +45 -0
  152. data/lib/pdfrb/layout/frame.rb +67 -0
  153. data/lib/pdfrb/layout/image_box.rb +44 -0
  154. data/lib/pdfrb/layout/inline_box.rb +25 -0
  155. data/lib/pdfrb/layout/line.rb +55 -0
  156. data/lib/pdfrb/layout/list_box.rb +87 -0
  157. data/lib/pdfrb/layout/numeric_refinements.rb +43 -0
  158. data/lib/pdfrb/layout/page_style.rb +65 -0
  159. data/lib/pdfrb/layout/style.rb +62 -0
  160. data/lib/pdfrb/layout/table_box.rb +66 -0
  161. data/lib/pdfrb/layout/text_box.rb +38 -0
  162. data/lib/pdfrb/layout/text_fragment.rb +45 -0
  163. data/lib/pdfrb/layout/text_layouter.rb +123 -0
  164. data/lib/pdfrb/layout.rb +28 -0
  165. data/lib/pdfrb/linearization/hint_stream.rb +93 -0
  166. data/lib/pdfrb/linearization/writer.rb +259 -0
  167. data/lib/pdfrb/linearization.rb +21 -0
  168. data/lib/pdfrb/model/cos/dictionary.rb +12 -0
  169. data/lib/pdfrb/model/pdf_array.rb +2 -2
  170. data/lib/pdfrb/model/type/action.rb +42 -0
  171. data/lib/pdfrb/model/type/action_go_to_3d_view.rb +14 -0
  172. data/lib/pdfrb/model/type/action_go_to_dp.rb +16 -0
  173. data/lib/pdfrb/model/type/action_go_to_r.rb +26 -0
  174. data/lib/pdfrb/model/type/action_goto.rb +34 -0
  175. data/lib/pdfrb/model/type/action_hide.rb +26 -0
  176. data/lib/pdfrb/model/type/action_import_data.rb +18 -0
  177. data/lib/pdfrb/model/type/action_java_script.rb +18 -0
  178. data/lib/pdfrb/model/type/action_launch.rb +22 -0
  179. data/lib/pdfrb/model/type/action_movie.rb +22 -0
  180. data/lib/pdfrb/model/type/action_named.rb +23 -0
  181. data/lib/pdfrb/model/type/action_nop.rb +12 -0
  182. data/lib/pdfrb/model/type/action_rendition.rb +28 -0
  183. data/lib/pdfrb/model/type/action_reset_form.rb +22 -0
  184. data/lib/pdfrb/model/type/action_rich_media_execute.rb +17 -0
  185. data/lib/pdfrb/model/type/action_set_ocg_state.rb +32 -0
  186. data/lib/pdfrb/model/type/action_set_state.rb +14 -0
  187. data/lib/pdfrb/model/type/action_sound_action.rb +23 -0
  188. data/lib/pdfrb/model/type/action_submit_form.rb +64 -0
  189. data/lib/pdfrb/model/type/action_thread.rb +23 -0
  190. data/lib/pdfrb/model/type/action_trans.rb +30 -0
  191. data/lib/pdfrb/model/type/action_uri.rb +30 -0
  192. data/lib/pdfrb/model/type/af_embedded_file.rb +11 -0
  193. data/lib/pdfrb/model/type/af_file_specification.rb +34 -0
  194. data/lib/pdfrb/model/type/annotation.rb +49 -10
  195. data/lib/pdfrb/model/type/appearance.rb +41 -0
  196. data/lib/pdfrb/model/type/appearance_characteristics.rb +36 -0
  197. data/lib/pdfrb/model/type/appearance_generator.rb +22 -0
  198. data/lib/pdfrb/model/type/appearance_trap_net.rb +84 -0
  199. data/lib/pdfrb/model/type/border_effect.rb +25 -0
  200. data/lib/pdfrb/model/type/border_styling.rb +38 -0
  201. data/lib/pdfrb/model/type/box_color_info.rb +21 -0
  202. data/lib/pdfrb/model/type/box_style.rb +19 -0
  203. data/lib/pdfrb/model/type/button.rb +17 -0
  204. data/lib/pdfrb/model/type/cal_gray.rb +17 -0
  205. data/lib/pdfrb/model/type/cal_rgb.rb +22 -0
  206. data/lib/pdfrb/model/type/caret_annotation.rb +21 -0
  207. data/lib/pdfrb/model/type/catalog.rb +99 -16
  208. data/lib/pdfrb/model/type/choice.rb +21 -0
  209. data/lib/pdfrb/model/type/cid_font.rb +52 -1
  210. data/lib/pdfrb/model/type/cid_font_descriptor_metrics.rb +28 -0
  211. data/lib/pdfrb/model/type/cid_system_info.rb +28 -0
  212. data/lib/pdfrb/model/type/circle_annotation.rb +20 -0
  213. data/lib/pdfrb/model/type/collection.rb +27 -0
  214. data/lib/pdfrb/model/type/collection_field.rb +24 -0
  215. data/lib/pdfrb/model/type/collection_item.rb +19 -0
  216. data/lib/pdfrb/model/type/collection_schema.rb +25 -0
  217. data/lib/pdfrb/model/type/collection_sort.rb +13 -0
  218. data/lib/pdfrb/model/type/crypt_filter.rb +58 -0
  219. data/lib/pdfrb/model/type/device_n.rb +37 -0
  220. data/lib/pdfrb/model/type/device_n_mixing_hints.rb +17 -0
  221. data/lib/pdfrb/model/type/device_n_process.rb +13 -0
  222. data/lib/pdfrb/model/type/doc_mdp_transform_parameters.rb +21 -0
  223. data/lib/pdfrb/model/type/document_security_store.rb +55 -0
  224. data/lib/pdfrb/model/type/embedded_file.rb +19 -0
  225. data/lib/pdfrb/model/type/embedded_file_parameter.rb +48 -0
  226. data/lib/pdfrb/model/type/encryption_public_key.rb +38 -0
  227. data/lib/pdfrb/model/type/encryption_standard.rb +68 -0
  228. data/lib/pdfrb/model/type/field_mdp_transform_parameters.rb +26 -0
  229. data/lib/pdfrb/model/type/file_attachment_annotation.rb +29 -0
  230. data/lib/pdfrb/model/type/file_spec_ef.rb +22 -0
  231. data/lib/pdfrb/model/type/file_spec_rf.rb +21 -0
  232. data/lib/pdfrb/model/type/file_specification.rb +43 -0
  233. data/lib/pdfrb/model/type/file_trailer.rb +54 -0
  234. data/lib/pdfrb/model/type/font.rb +73 -2
  235. data/lib/pdfrb/model/type/font_cid_type0.rb +17 -0
  236. data/lib/pdfrb/model/type/font_cid_type2.rb +17 -0
  237. data/lib/pdfrb/model/type/font_descriptor.rb +47 -0
  238. data/lib/pdfrb/model/type/font_descriptor_cid_type0.rb +15 -0
  239. data/lib/pdfrb/model/type/font_descriptor_cid_type2.rb +15 -0
  240. data/lib/pdfrb/model/type/font_encoding.rb +47 -0
  241. data/lib/pdfrb/model/type/font_file.rb +28 -0
  242. data/lib/pdfrb/model/type/font_file2.rb +32 -0
  243. data/lib/pdfrb/model/type/font_file3.rb +23 -0
  244. data/lib/pdfrb/model/type/font_multiple_master.rb +60 -0
  245. data/lib/pdfrb/model/type/font_true_type.rb +1 -1
  246. data/lib/pdfrb/model/type/font_type0.rb +1 -1
  247. data/lib/pdfrb/model/type/font_type1.rb +1 -1
  248. data/lib/pdfrb/model/type/font_type3.rb +1 -1
  249. data/lib/pdfrb/model/type/form_field.rb +73 -0
  250. data/lib/pdfrb/model/type/free_text_annotation.rb +37 -0
  251. data/lib/pdfrb/model/type/function.rb +42 -0
  252. data/lib/pdfrb/model/type/function_exponential.rb +31 -0
  253. data/lib/pdfrb/model/type/function_postscript.rb +25 -0
  254. data/lib/pdfrb/model/type/function_sampled.rb +55 -0
  255. data/lib/pdfrb/model/type/function_stitching.rb +33 -0
  256. data/lib/pdfrb/model/type/generic_appearance.rb +40 -0
  257. data/lib/pdfrb/model/type/graphics_state_parameter.rb +69 -0
  258. data/lib/pdfrb/model/type/halftone.rb +21 -0
  259. data/lib/pdfrb/model/type/halftone_type1.rb +17 -0
  260. data/lib/pdfrb/model/type/halftone_type10.rb +16 -0
  261. data/lib/pdfrb/model/type/halftone_type16.rb +11 -0
  262. data/lib/pdfrb/model/type/halftone_type5.rb +24 -0
  263. data/lib/pdfrb/model/type/halftone_type6.rb +16 -0
  264. data/lib/pdfrb/model/type/highlight_annotation.rb +11 -0
  265. data/lib/pdfrb/model/type/icc_based_color_space.rb +24 -0
  266. data/lib/pdfrb/model/type/icon_fit.rb +31 -0
  267. data/lib/pdfrb/model/type/indexed.rb +32 -0
  268. data/lib/pdfrb/model/type/info.rb +44 -0
  269. data/lib/pdfrb/model/type/ink_annotation.rb +29 -0
  270. data/lib/pdfrb/model/type/interactive_form.rb +36 -0
  271. data/lib/pdfrb/model/type/interior_color.rb +12 -0
  272. data/lib/pdfrb/model/type/jbig2_globals.rb +15 -0
  273. data/lib/pdfrb/model/type/lab.rb +27 -0
  274. data/lib/pdfrb/model/type/line_annotation.rb +44 -0
  275. data/lib/pdfrb/model/type/line_ending_styling.rb +39 -0
  276. data/lib/pdfrb/model/type/link_annotation.rb +41 -0
  277. data/lib/pdfrb/model/type/link_appearance.rb +9 -0
  278. data/lib/pdfrb/model/type/mark_information.rb +23 -0
  279. data/lib/pdfrb/model/type/marked_content_reference.rb +24 -0
  280. data/lib/pdfrb/model/type/markup_annotation.rb +36 -0
  281. data/lib/pdfrb/model/type/mdp_dict.rb +28 -0
  282. data/lib/pdfrb/model/type/measure.rb +39 -0
  283. data/lib/pdfrb/model/type/media_clip.rb +28 -0
  284. data/lib/pdfrb/model/type/media_offset.rb +106 -0
  285. data/lib/pdfrb/model/type/metadata.rb +27 -3
  286. data/lib/pdfrb/model/type/misc_helpers.rb +75 -0
  287. data/lib/pdfrb/model/type/movie.rb +28 -0
  288. data/lib/pdfrb/model/type/names.rb +51 -0
  289. data/lib/pdfrb/model/type/namespace.rb +27 -0
  290. data/lib/pdfrb/model/type/object_reference.rb +31 -0
  291. data/lib/pdfrb/model/type/object_stream.rb +38 -0
  292. data/lib/pdfrb/model/type/opt_content_ext.rb +94 -0
  293. data/lib/pdfrb/model/type/opt_content_page_element.rb +18 -0
  294. data/lib/pdfrb/model/type/optional_content_config.rb +56 -0
  295. data/lib/pdfrb/model/type/optional_content_group.rb +35 -10
  296. data/lib/pdfrb/model/type/optional_content_membership.rb +36 -2
  297. data/lib/pdfrb/model/type/optional_content_properties.rb +16 -1
  298. data/lib/pdfrb/model/type/outline.rb +40 -6
  299. data/lib/pdfrb/model/type/outline_item.rb +45 -0
  300. data/lib/pdfrb/model/type/output_intent.rb +30 -0
  301. data/lib/pdfrb/model/type/page.rb +127 -0
  302. data/lib/pdfrb/model/type/page_label.rb +68 -0
  303. data/lib/pdfrb/model/type/page_tree_node.rb +51 -16
  304. data/lib/pdfrb/model/type/page_tree_node_root.rb +15 -0
  305. data/lib/pdfrb/model/type/pattern.rb +16 -0
  306. data/lib/pdfrb/model/type/pattern_shading.rb +25 -0
  307. data/lib/pdfrb/model/type/pattern_tiling.rb +25 -0
  308. data/lib/pdfrb/model/type/polygon_annotation.rb +29 -0
  309. data/lib/pdfrb/model/type/polygon_polyline.rb +14 -0
  310. data/lib/pdfrb/model/type/polyline_annotation.rb +29 -0
  311. data/lib/pdfrb/model/type/popup_annotation.rb +20 -0
  312. data/lib/pdfrb/model/type/printer_mark_annotation.rb +13 -0
  313. data/lib/pdfrb/model/type/redact_annotation.rb +28 -0
  314. data/lib/pdfrb/model/type/rendition.rb +31 -0
  315. data/lib/pdfrb/model/type/resources.rb +59 -14
  316. data/lib/pdfrb/model/type/rich_media_activation.rb +24 -0
  317. data/lib/pdfrb/model/type/rich_media_animation.rb +25 -0
  318. data/lib/pdfrb/model/type/rich_media_annotation.rb +18 -0
  319. data/lib/pdfrb/model/type/rich_media_command.rb +19 -0
  320. data/lib/pdfrb/model/type/rich_media_configuration.rb +32 -0
  321. data/lib/pdfrb/model/type/rich_media_content.rb +28 -0
  322. data/lib/pdfrb/model/type/rich_media_cue_point.rb +18 -0
  323. data/lib/pdfrb/model/type/rich_media_deactivation.rb +17 -0
  324. data/lib/pdfrb/model/type/rich_media_instance.rb +33 -0
  325. data/lib/pdfrb/model/type/rich_media_presentation.rb +29 -0
  326. data/lib/pdfrb/model/type/rich_media_settings.rb +33 -0
  327. data/lib/pdfrb/model/type/rich_media_window.rb +20 -0
  328. data/lib/pdfrb/model/type/screen_annotation.rb +19 -0
  329. data/lib/pdfrb/model/type/separation.rb +25 -0
  330. data/lib/pdfrb/model/type/sig_field_lock.rb +25 -0
  331. data/lib/pdfrb/model/type/sig_field_seed_value.rb +25 -0
  332. data/lib/pdfrb/model/type/signature.rb +65 -0
  333. data/lib/pdfrb/model/type/signature_build.rb +86 -0
  334. data/lib/pdfrb/model/type/signature_field.rb +17 -0
  335. data/lib/pdfrb/model/type/software_identifier.rb +19 -0
  336. data/lib/pdfrb/model/type/sound.rb +38 -0
  337. data/lib/pdfrb/model/type/sound_annotation.rb +21 -0
  338. data/lib/pdfrb/model/type/square_annotation.rb +24 -0
  339. data/lib/pdfrb/model/type/square_circle.rb +13 -0
  340. data/lib/pdfrb/model/type/squiggly_annotation.rb +11 -0
  341. data/lib/pdfrb/model/type/stamp_annotation.rb +19 -0
  342. data/lib/pdfrb/model/type/strikeout_annotation.rb +11 -0
  343. data/lib/pdfrb/model/type/struct_elem.rb +75 -0
  344. data/lib/pdfrb/model/type/struct_tree_root.rb +34 -0
  345. data/lib/pdfrb/model/type/structure_attributes.rb +44 -0
  346. data/lib/pdfrb/model/type/structure_element_kid.rb +33 -0
  347. data/lib/pdfrb/model/type/text_annotation.rb +25 -0
  348. data/lib/pdfrb/model/type/text_appearance.rb +9 -0
  349. data/lib/pdfrb/model/type/text_field.rb +21 -0
  350. data/lib/pdfrb/model/type/text_markup_annotation.rb +30 -0
  351. data/lib/pdfrb/model/type/three_d_activation.rb +32 -0
  352. data/lib/pdfrb/model/type/three_d_animation_style.rb +24 -0
  353. data/lib/pdfrb/model/type/three_d_background.rb +28 -0
  354. data/lib/pdfrb/model/type/three_d_cross_section.rb +25 -0
  355. data/lib/pdfrb/model/type/three_d_lighting_scheme.rb +26 -0
  356. data/lib/pdfrb/model/type/three_d_measure.rb +20 -0
  357. data/lib/pdfrb/model/type/three_d_measure_3dc.rb +23 -0
  358. data/lib/pdfrb/model/type/three_d_measure_ad3.rb +14 -0
  359. data/lib/pdfrb/model/type/three_d_measure_ld3.rb +14 -0
  360. data/lib/pdfrb/model/type/three_d_measure_pd3.rb +20 -0
  361. data/lib/pdfrb/model/type/three_d_measure_rd3.rb +14 -0
  362. data/lib/pdfrb/model/type/three_d_node.rb +37 -0
  363. data/lib/pdfrb/model/type/three_d_reference.rb +21 -0
  364. data/lib/pdfrb/model/type/three_d_render_mode.rb +34 -0
  365. data/lib/pdfrb/model/type/three_d_stream.rb +48 -0
  366. data/lib/pdfrb/model/type/three_d_units.rb +18 -0
  367. data/lib/pdfrb/model/type/three_d_view.rb +40 -0
  368. data/lib/pdfrb/model/type/to_unicode_cmap_stream.rb +23 -0
  369. data/lib/pdfrb/model/type/transition.rb +68 -0
  370. data/lib/pdfrb/model/type/underline_annotation.rb +11 -0
  371. data/lib/pdfrb/model/type/ur_transform_parameters.rb +33 -0
  372. data/lib/pdfrb/model/type/uri_dict.rb +18 -0
  373. data/lib/pdfrb/model/type/url_alias.rb +19 -0
  374. data/lib/pdfrb/model/type/variable_text_field.rb +47 -0
  375. data/lib/pdfrb/model/type/viewer_preferences.rb +42 -0
  376. data/lib/pdfrb/model/type/watermark_annotation.rb +13 -0
  377. data/lib/pdfrb/model/type/widget_annotation.rb +48 -0
  378. data/lib/pdfrb/model/type/widget_appearance.rb +9 -0
  379. data/lib/pdfrb/model/type/xobject_form.rb +41 -0
  380. data/lib/pdfrb/model/type/xobject_image.rb +56 -0
  381. data/lib/pdfrb/model/type/xref_stream.rb +37 -0
  382. data/lib/pdfrb/model/type.rb +286 -6
  383. data/lib/pdfrb/serializer.rb +2 -2
  384. data/lib/pdfrb/source/tokenizer.rb +0 -2
  385. data/lib/pdfrb/task/benchmark.rb +83 -0
  386. data/lib/pdfrb/task/extract_text.rb +1 -1
  387. data/lib/pdfrb/task/generate_corpus.rb +130 -0
  388. data/lib/pdfrb/task/memory_profile.rb +84 -0
  389. data/lib/pdfrb/task/optimize.rb +97 -7
  390. data/lib/pdfrb/task.rb +3 -3
  391. data/lib/pdfrb/test_utils.rb +112 -0
  392. data/lib/pdfrb/validator.rb +82 -0
  393. data/lib/pdfrb/version.rb +1 -1
  394. data/lib/pdfrb/writer.rb +117 -27
  395. data/lib/pdfrb/xmp/packet.rb +122 -0
  396. data/lib/pdfrb/xmp/schemas.rb +96 -0
  397. data/lib/pdfrb/xmp.rb +44 -0
  398. data/lib/pdfrb/xref_section.rb +2 -2
  399. data/lib/pdfrb.rb +24 -54
  400. data/pdfrb.gemspec +45 -0
  401. data/script/apply_fixes.sh +843 -0
  402. data/script/make_jpeg_fixture.rb +53 -0
  403. data/script/make_png_fixture.rb +25 -0
  404. metadata +311 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 04b26536220e55c3be57627ba747561baeee1d58f37e57a2f68f5f121ec1381c
4
- data.tar.gz: 7a3f7a5a10b43e253e34bb5551042db89e3d94694a568beee186c2995fefe867
3
+ metadata.gz: 6f24b6c90eb4e01436139908a7aaa0472ed8a22704e40e934f651c0265763d62
4
+ data.tar.gz: da4e7fc8a4f9c4cecbbf22ca2ab2288ca8f07c4bc974dc6eac8b2166fb784d94
5
5
  SHA512:
6
- metadata.gz: 70ec6abd4b94413e5cfcf1c18b4437332fc84c38262bc58347c3b55b354416cbf4f1ecfe24995dfeea12776306e8c07b1acdddd0d96b64e572bc82d60ed629df
7
- data.tar.gz: 35daa8aa6439430f7d6dbc043777bc969f0844a14dd503650076fe5ffc9edd03859f22f64a3b3d290cc84eb10774589dca52c7138fc4b102bfab660692fb4461
6
+ metadata.gz: 550b8e52600d4ebe1cffbce341d0725d750a41ba8585c1b85c827f9c7a4b6fda7e24d6dd6f3c2ed6f5f83e442729e792f3fef05a9d34459118a9720d039bf3bb
7
+ data.tar.gz: c950a24d0cf3af6734edbe2819d0390c07442fa941e026be4744fb6db0e02beadc4e3d2ac220fbb5d4f52eee67bc3e9d19e03602dabaaefdf65929ecbe1a61d3
data/.rubocop.yml CHANGED
@@ -12,7 +12,7 @@ plugins:
12
12
  - rubocop-rake
13
13
 
14
14
  AllCops:
15
- TargetRubyVersion: 3.0
15
+ TargetRubyVersion: 3.2
16
16
  NewCops: enable
17
17
  Exclude:
18
18
  - "data/**/*"
data/Gemfile CHANGED
@@ -12,4 +12,5 @@ gem "rubocop-performance", "~> 1.23"
12
12
  gem "rubocop-rake", "~> 0.7"
13
13
  gem "rubocop-rspec", "~> 3.5"
14
14
 
15
+ gem "lutaml-model", "~> 0.8"
15
16
  gem "simplecov", require: false, group: :test
@@ -0,0 +1,92 @@
1
+ # PROPOSAL: Canvas clip operators
2
+
3
+ ## Summary
4
+
5
+ Add `Canvas#clip` and `Canvas#clip_path` methods for PDF clipping path
6
+ operators (`W`, `W*`, `n`), enabling content to be clipped to arbitrary
7
+ paths.
8
+
9
+ ## Motivation
10
+
11
+ The IDML→PDF rendering pipeline (the `idml` gem) needs to clip placed
12
+ images to their parent Rectangle's bounds. Currently, pdfrb 0.3.0 has
13
+ no clip operator on Canvas. The `idml` gem implements a workaround via
14
+ `PdfrbExt::Clip` (custom `Operator::Base` subclass), but this belongs
15
+ in pdfrb itself.
16
+
17
+ Other use cases:
18
+ - Clipping text frames to their bounds
19
+ - Masking layers
20
+ - Cropping form XObjects
21
+ - Print marks (crop/bleed/trim)
22
+
23
+ ## Proposed API
24
+
25
+ ```ruby
26
+ # Set clipping path using nonzero winding rule.
27
+ # Must be called after defining a path (rectangle, move_to/line_to, etc.)
28
+ # and before drawing content that should be clipped.
29
+ # The clip persists until the graphics state is restored (Q).
30
+ def clip
31
+ emit_op(Operator::ClipNonZero)
32
+ emit_op(Operator::EndPath)
33
+ self
34
+ end
35
+
36
+ # Set clipping path using even-odd rule.
37
+ def clip_even_odd
38
+ emit_op(Operator::ClipEvenOdd)
39
+ emit_op(Operator::EndPath)
40
+ self
41
+ end
42
+ ```
43
+
44
+ ## Operator classes to add
45
+
46
+ ```ruby
47
+ module Pdfrb::Content::Operator
48
+ # PDF `W` — modify clipping path (nonzero winding)
49
+ class ClipNonZero < NoArg
50
+ def self.name; "W"; end
51
+ register
52
+ end
53
+
54
+ # PDF `W*` — modify clipping path (even-odd)
55
+ class ClipEvenOdd < NoArg
56
+ def self.name; "W*"; end
57
+ register
58
+ end
59
+
60
+ # PDF `n` — end path without filling or stroking
61
+ class EndPath < NoArg
62
+ def self.name; "n"; end
63
+ register
64
+ end
65
+ end
66
+ ```
67
+
68
+ ## Usage example
69
+
70
+ ```ruby
71
+ canvas.save_graphics_state do
72
+ canvas.rectangle(100, 100, 200, 200)
73
+ canvas.clip # clip to the rectangle
74
+ canvas.draw_image(:Im1, at: [50, 50], width: 300, height: 300)
75
+ end
76
+ ```
77
+
78
+ ## Implementation notes
79
+
80
+ - `W` and `W*` are technically not no-arg operators in the strict PDF
81
+ sense — they are modifiers applied to the current path. But since they
82
+ take no operands, `NoArg` is the correct base class.
83
+ - `n` is already listed as `EndPath` in the operator module but may not
84
+ be registered. If it is, `Canvas#end_path` already exists and can be
85
+ reused.
86
+ - The `clip` method should call `end_path` after `W`/`W*` to consume
87
+ the path, matching the common `W n` idiom.
88
+
89
+ ## Priority
90
+
91
+ Medium — commonly needed for any PDF rendering pipeline that places
92
+ images or text within geometric frames.
@@ -0,0 +1,87 @@
1
+ # PROPOSAL: Canvas draw_image with transform matrix
2
+
3
+ ## Summary
4
+
5
+ Add `Canvas#draw_image_matrix(name, a:, b:, c:, d:, e:, f:)` for drawing
6
+ image XObjects with an arbitrary affine transform matrix, complementing
7
+ the existing `Canvas#draw_image(name, at:, width:, height:)` which only
8
+ supports position + uniform scale.
9
+
10
+ ## Motivation
11
+
12
+ The `idml` gem places images using IDML's `ItemTransform` — a full 2D
13
+ affine matrix `a b c d e f` that includes non-uniform scaling, rotation,
14
+ and translation. The current `draw_image` only accepts `at: [x, y]` and
15
+ `width:, height:`, which can't express rotation or skew.
16
+
17
+ Currently the `idml` gem works around this by emitting raw operators:
18
+
19
+ ```ruby
20
+ canvas.concat(scale_x, 0, 0, scale_y, x, y)
21
+ canvas.emit_op(PdfrbExt::InvokeXObject, name)
22
+ ```
23
+
24
+ This works but bypasses pdfrb's Canvas abstraction. A proper API method
25
+ would be cleaner and more discoverable.
26
+
27
+ ## Proposed API
28
+
29
+ ```ruby
30
+ # Draw an image XObject with a full affine transform matrix.
31
+ # The matrix maps the image's unit square (0,0)-(1,1) to the
32
+ # destination rectangle on the page.
33
+ #
34
+ # @param name [Symbol] image resource name from Document#images#add.
35
+ # @param a, b, c, d, e, f [Numeric] affine matrix components.
36
+ #
37
+ # Example: scale 200×150 and place at (72, 300):
38
+ # canvas.draw_image_matrix(:Im1, a: 200, b: 0, c: 0, d: 150, e: 72, f: 300)
39
+ #
40
+ # Example: rotate 45° and scale:
41
+ # rad = 45 * Math::PI / 180
42
+ # cos = Math.cos(rad)
43
+ # sin = Math.sin(rad)
44
+ # canvas.draw_image_matrix(:Im1, a: cos*200, b: sin*200, c: -sin*150, d: cos*150, e: 100, f: 400)
45
+ def draw_image_matrix(name, a:, b:, c:, d:, e:, f:)
46
+ save_graphics_state do
47
+ concat(a, b, c, d, e, f)
48
+ emit_op(Operator::InvokeXObject, name)
49
+ end
50
+ self
51
+ end
52
+ ```
53
+
54
+ ## Alternative: extend draw_image
55
+
56
+ ```ruby
57
+ def draw_image(name, at: nil, width: nil, height: nil, matrix: nil)
58
+ if matrix
59
+ a, b, c, d, e, f = matrix
60
+ save_graphics_state { concat(a, b, c, d, e, f); emit_op(Operator::InvokeXObject, name) }
61
+ else
62
+ # existing at:/width:/height: behavior
63
+ end
64
+ end
65
+ ```
66
+
67
+ ## Register InvokeXObject operator
68
+
69
+ This proposal also requires registering the `Do` (Invoke XObject)
70
+ operator in pdfrb's operator catalogue:
71
+
72
+ ```ruby
73
+ module Pdfrb::Content::Operator
74
+ class InvokeXObject < Base
75
+ class << self
76
+ def name; "Do"; end
77
+ def serialize(serializer, name); "/#{name} Do\n"; end
78
+ end
79
+ register
80
+ end
81
+ end
82
+ ```
83
+
84
+ ## Priority
85
+
86
+ Medium — the `idml` gem has a workaround via PdfrbExt, but pdfrb should
87
+ have the `Do` operator and a matrix-aware image drawing method built-in.
@@ -0,0 +1,103 @@
1
+ # PROPOSAL: Canvas rich text — multi-run text in one BT/ET block
2
+
3
+ ## Summary
4
+
5
+ Add `Canvas#text_rich(runs, at:)` for drawing text with per-run font,
6
+ size, color, and positioning. Eliminates the need for N separate
7
+ `text` calls when a paragraph contains mixed styling (e.g., bold word
8
+ within regular text, different font sizes, color changes).
9
+
10
+ ## Motivation
11
+
12
+ The `idml` gem's `TextFrameRenderer` renders styled text from IDML
13
+ documents where a single paragraph can contain multiple
14
+ `CharacterStyleRange` runs — each with its own font, size, and color.
15
+ Currently, each run requires a separate `canvas.text` call (separate
16
+ BT/ET block), which:
17
+
18
+ 1. Produces a larger content stream (more BT/ET pairs).
19
+ 2. Loses cursor continuity — the caller must compute the x-offset
20
+ after each run manually.
21
+ 3. Can't use `TJ` (array show) for kerning between runs.
22
+
23
+ ## Proposed API
24
+
25
+ ```ruby
26
+ # Draw multiple text runs in a single BT/ET block with cursor
27
+ # tracking. Each run advances the text cursor by its width.
28
+ #
29
+ # @param runs [Array<Hash>] each hash has:
30
+ # :text [String] the text to show.
31
+ # :font [Symbol] font resource name.
32
+ # :size [Float] font size in points.
33
+ # :color [Array, nil] optional fill color (e.g., [:rgb, 1, 0, 0]).
34
+ # :char_spacing [Float, nil] optional character spacing.
35
+ # @param at [Array(Numeric, Numeric)] [x, y] of the first run's baseline.
36
+ #
37
+ # Example:
38
+ # canvas.text_rich([
39
+ # { text: "Hello ", font: :F1, size: 12 },
40
+ # { text: "World", font: :F1, size: 12, color: [:rgb, 1, 0, 0] },
41
+ # { text: "!", font: :F1, size: 14 },
42
+ # ], at: [72, 720])
43
+ def text_rich(runs, at:)
44
+ emit_op Operator::BeginText
45
+ cursor_x, cursor_y = at
46
+
47
+ runs.each do |run|
48
+ emit_op Operator::Font, run[:font], run[:size]
49
+ emit_op Operator::SetTextMatrix, 1, 0, 0, 1, cursor_x, cursor_y
50
+ if run[:color]
51
+ fill_color(run[:color])
52
+ end
53
+ encoded = document.fonts.encode_text(run[:text], run[:font])
54
+ width = document.fonts.measure_text(run[:text],
55
+ font: run[:font],
56
+ size: run[:size])
57
+ emit_op Operator::ShowText, escape_pdf_string(encoded)
58
+ cursor_x += width
59
+ end
60
+
61
+ emit_op Operator::EndText
62
+ self
63
+ end
64
+ ```
65
+
66
+ ## Alternative: TJ (array show) optimization
67
+
68
+ For runs with the same font and size, use `TJ` to show text with
69
+ positioning adjustments in a single operator:
70
+
71
+ ```
72
+ [(Hel) -50 (lo W) 20 (orld!)] TJ
73
+ ```
74
+
75
+ This enables kerning between runs and reduces operator count. But it
76
+ requires all runs to use the same font/size, which limits its use.
77
+
78
+ ## Implementation notes
79
+
80
+ - `text_rich` uses `SetFont` (Tf) per run to switch fonts within the
81
+ same BT/ET block. This is valid PDF — Tf can be called multiple times.
82
+ - Color changes use `SetFillColor` (rg/k) between runs.
83
+ - `SetTextMatrix` (Tm) positions each run at the absolute cursor.
84
+ - The cursor advances by `measure_text(run[:text], ...)` after each run.
85
+
86
+ ## Usage example
87
+
88
+ ```ruby
89
+ # Render a paragraph with mixed styling
90
+ canvas.text_rich([
91
+ { text: "This is ", font: :F1, size: 12 },
92
+ { text: "bold", font: :F2, size: 12 }, # F2 = bold variant
93
+ { text: " text in ", font: :F1, size: 12 },
94
+ { text: "red", font: :F1, size: 12, color: [:rgb, 1, 0, 0] },
95
+ { text: ".", font: :F1, size: 12 },
96
+ ], at: [72, 720])
97
+ ```
98
+
99
+ ## Priority
100
+
101
+ Medium — the `idml` gem works around this with N `text` calls, but
102
+ `text_rich` would produce cleaner, smaller content streams and enable
103
+ proper kerning between styled runs.
@@ -0,0 +1,77 @@
1
+ # PROPOSAL: Canvas multi-line text helper
2
+
3
+ ## Summary
4
+
5
+ Add `Canvas#text_lines(lines, font:, size:, leading:, at:)` for drawing
6
+ multiple lines of text with consistent leading. Simplifies multi-line
7
+ text rendering that currently requires N separate `text` calls.
8
+
9
+ ## Motivation
10
+
11
+ The `idml` gem's `TextFrameRenderer` breaks text into lines via a line
12
+ breaker, then calls `canvas.text` once per line at a manually computed
13
+ y position:
14
+
15
+ ```ruby
16
+ lines.each do |line|
17
+ canvas.text(line.text, at: [x, baseline_y], font: font, size: size)
18
+ baseline_y -= size * 1.2
19
+ end
20
+ ```
21
+
22
+ This is verbose and error-prone (leading calculation, y overflow). A
23
+ multi-line helper centralizes the logic.
24
+
25
+ ## Proposed API
26
+
27
+ ```ruby
28
+ # Draw multiple lines of text with consistent leading.
29
+ # @param lines [Array<String>] text strings, one per line.
30
+ # @param font [Symbol] font resource name.
31
+ # @param size [Float] font size in points.
32
+ # @param leading [Float] line spacing in points (default: size * 1.2).
33
+ # @param at [Array(Numeric, Numeric)] [x, y] of the first line's baseline.
34
+ # @param char_spacing [Float, nil] optional character spacing.
35
+ # @param word_spacing [Float, nil] optional word spacing.
36
+ def text_lines(lines, font:, size:, at:, leading: nil,
37
+ char_spacing: nil, word_spacing: nil)
38
+ lead = leading || size * 1.2
39
+ x, y = at
40
+ lines.each do |line|
41
+ text(line, at: [x, y], font: font, size: size,
42
+ char_spacing: char_spacing, word_spacing: word_spacing)
43
+ y -= lead
44
+ end
45
+ self
46
+ end
47
+ ```
48
+
49
+ ## Usage example
50
+
51
+ ```ruby
52
+ # Simple multi-line text
53
+ canvas.text_lines(
54
+ ["Line 1", "Line 2", "Line 3"],
55
+ font: :F1, size: 12, at: [72, 720]
56
+ )
57
+
58
+ # Custom leading
59
+ canvas.text_lines(
60
+ wrapped_text_array,
61
+ font: :F1, size: 10, at: [72, 700], leading: 14
62
+ )
63
+ ```
64
+
65
+ ## Implementation notes
66
+
67
+ - Each line is a separate `text` call (no TJ array optimization).
68
+ This is intentional: different lines may have different widths
69
+ (justified vs left-aligned).
70
+ - The caller controls wrapping (via their own line breaker). This
71
+ method only handles vertical positioning.
72
+ - `leading` follows PDF convention: distance between baselines.
73
+
74
+ ## Priority
75
+
76
+ Low — the current approach (N `text` calls) works. But the helper
77
+ reduces boilerplate in every text-heavy rendering pipeline.
@@ -0,0 +1,106 @@
1
+ # PROPOSAL: Canvas transparency and blend modes
2
+
3
+ ## Summary
4
+
5
+ Add `Canvas#opacity=`, `Canvas#blend_mode=`, and
6
+ `Canvas#with_transparency(opacity:, blend_mode:, &block)` for
7
+ rendering semi-transparent page items with blending modes
8
+ (Multiply, Screen, Overlay, etc.).
9
+
10
+ ## Motivation
11
+
12
+ The `idml` gem's element models include transparency attributes
13
+ (`BlendingSetting`, `OpacityGradientStop`, `FillTransparencySetting`)
14
+ that carry Opacity, BlendMode, and IsolatedKnockout settings. Without
15
+ transparency support in pdfrb's Canvas, these attributes are silently
16
+ ignored — semi-transparent shapes render at full opacity.
17
+
18
+ IDML documents commonly use:
19
+ - Semi-transparent overlays (Opacity = 50%)
20
+ - Multiply blend mode for shadow effects
21
+ - Screen blend mode for highlights
22
+ - Isolated blending groups
23
+
24
+ ## Proposed API
25
+
26
+ ```ruby
27
+ class Pdfrb::Content::Canvas
28
+ # Set fill/stroke opacity (0.0–1.0).
29
+ # Uses ExtGState with /ca (fill alpha) and /CA (stroke alpha).
30
+ # @param alpha [Float] opacity value (0.0 = transparent, 1.0 = opaque).
31
+ def opacity=(alpha)
32
+ gs = document.ext_g_states.add(ca: alpha, CA: alpha)
33
+ emit_op Operator::ApplyExtGState, gs
34
+ end
35
+
36
+ # Set blend mode for subsequent drawing operations.
37
+ # @param mode [Symbol] one of: :Normal, :Multiply, :Screen,
38
+ # :Overlay, :Darken, :Lighten, :ColorDodge, :ColorBurn,
39
+ # :HardLight, :SoftLight, :Difference, :Exclusion,
40
+ # :Hue, :Saturation, :Color, :Luminosity.
41
+ def blend_mode=(mode)
42
+ gs = document.ext_g_states.add(BM: mode.to_s)
43
+ emit_op Operator::ApplyExtGState, gs
44
+ end
45
+
46
+ # Execute a block with transparency settings, then restore.
47
+ def with_transparency(opacity: 1.0, blend_mode: nil)
48
+ save_graphics_state
49
+ self.opacity = opacity if opacity < 1.0
50
+ self.blend_mode = blend_mode if blend_mode
51
+ yield self
52
+ restore_graphics_state
53
+ end
54
+ end
55
+ ```
56
+
57
+ ## PDF structure
58
+
59
+ Transparency requires an `/ExtGState` (Extended Graphics State)
60
+ dictionary with alpha and blend mode entries:
61
+
62
+ ```
63
+ /ExtGState <<
64
+ /Type /ExtGState
65
+ /ca 0.5 % fill alpha
66
+ /CA 0.5 % stroke alpha
67
+ /BM /Multiply
68
+ /SMask << % soft mask (optional)
69
+ /Type /Mask /S /Alpha /G <form xobject>
70
+ >>
71
+ >>
72
+ ```
73
+
74
+ The Canvas applies it via `gs` operator:
75
+ ```
76
+ /GS1 gs
77
+ ```
78
+
79
+ ## Document-level support
80
+
81
+ ```ruby
82
+ class Pdfrb::Document
83
+ class ExtGStates
84
+ def add(ca: nil, CA: nil, BM: nil, SMask: nil)
85
+ # Create ExtGState dict, register in /Resources/ExtGState
86
+ # Return resource name (e.g., :GS1)
87
+ end
88
+ end
89
+ end
90
+ ```
91
+
92
+ ## Usage example
93
+
94
+ ```ruby
95
+ # Semi-transparent red rectangle with multiply blend
96
+ canvas.with_transparency(opacity: 0.5, blend_mode: :Multiply) do
97
+ canvas.fill_color([:rgb, 1, 0, 0])
98
+ canvas.rectangle(100, 100, 200, 200)
99
+ canvas.fill
100
+ end
101
+ ```
102
+
103
+ ## Priority
104
+
105
+ Low — transparency is an advanced feature. Most IDML documents don't
106
+ use it. But for documents that do, it's a visible rendering gap.
@@ -0,0 +1,129 @@
1
+ # PROPOSAL: Accept externally-provided font metrics in Fonts#add
2
+
3
+ ## Summary
4
+
5
+ Allow `Fonts#add` to accept pre-parsed font metrics (glyph widths,
6
+ ascent/descent, encoding) from the caller, instead of requiring pdfrb
7
+ to parse TTF/OTF binary tables internally. This lets consumers use
8
+ well-tested font parsers (like Fontisan) and pass the results to pdfrb.
9
+
10
+ ## Motivation
11
+
12
+ pdfrb's internal TTF table parser (`TrueType::File#table`) is broken
13
+ — it returns raw `String` bytes instead of parsed table objects, causing
14
+ `glyph_id_for` to fail for all TrueType/OpenType fonts. This breaks
15
+ `glyph_width`, `measure_text`, `metrics_for`, and font width arrays.
16
+
17
+ Fontisan (used by the `idml` gem) correctly parses all TTF tables
18
+ (cmap, hmtx, head, hhea, name) and provides per-glyph widths, metrics,
19
+ and encoding. It is battle-tested across hundreds of font files.
20
+
21
+ Instead of pdfrb reimplementing TTF parsing (and getting it wrong),
22
+ pdfrb should accept externally-provided metrics. This follows the
23
+ Unix philosophy: do one thing well. pdfrb assembles PDFs; Fontisan
24
+ parses fonts.
25
+
26
+ ## Proposed API
27
+
28
+ ```ruby
29
+ # Register a font with externally-provided metrics.
30
+ # @param name_or_io [String, IO] font file path or IO (for FontFile2
31
+ # embedding — pdfrb reads the raw bytes, does NOT parse tables).
32
+ # @param widths [Hash{Integer => Integer}, nil] codepoint → advance
33
+ # width in font units. Required for correct text width measurement.
34
+ # @param metrics [Hash, nil] font-level metrics:
35
+ # { units_per_em:, ascent:, descent:, cap_height:, x_height: }
36
+ # @param encoding [Hash{Integer => Integer}, nil] codepoint → glyph ID
37
+ # mapping (from cmap table). Used for encode_text.
38
+ # @param subset [Boolean] if true, subset during write using
39
+ # used_codepoints.
40
+ # @return [Symbol] font resource name.
41
+ def add(name_or_io, widths: nil, metrics: nil, encoding: nil,
42
+ subset: nil, **opts)
43
+ ```
44
+
45
+ When `widths:` is provided, pdfrb uses it instead of calling the broken
46
+ `extract_ttf_widths`. When `metrics:` is provided, `metrics_for` returns
47
+ it instead of an empty hash. When `encoding:` is provided, `encode_text`
48
+ uses it instead of the broken cmap parser.
49
+
50
+ ## Usage with Fontisan
51
+
52
+ ```ruby
53
+ require "fontisan"
54
+ require "pdfrb"
55
+
56
+ # Parse font with Fontisan (correct, tested)
57
+ font_path = "/path/to/Arial.ttf"
58
+ fontisan = Fontisan::FontLoader.load(font_path)
59
+
60
+ # Extract metrics via Fontisan
61
+ metrics = {
62
+ units_per_em: fontisan.head.units_per_em,
63
+ ascent: fontisan.hhea.ascent,
64
+ descent: fontisan.hhea.descent,
65
+ }
66
+ widths = fontisan.hmtx.advance_widths # {glyph_id => width}
67
+ encoding = fontisan.cmap.code_map # {codepoint => glyph_id}
68
+
69
+ # Register with pdfrb using externally-provided data
70
+ doc = Pdfrb::Document.new
71
+ font = doc.fonts.add(font_path,
72
+ widths: widths,
73
+ metrics: metrics,
74
+ encoding: encoding)
75
+
76
+ # Now glyph_width, measure_text, metrics_for all work correctly
77
+ doc.fonts.glyph_width(font, 65) # => 667 (for 'A')
78
+ doc.fonts.measure_text("Hello", font: font, size: 12) # => ~28.0
79
+ ```
80
+
81
+ ## Usage from the idml gem
82
+
83
+ The `idml` gem already has `FontMetrics` (Fontisan-based) that provides
84
+ `glyph_width(codepoint)`, `units_per_em`, `ascent`, `descent`. It would
85
+ build the `widths:` and `metrics:` hashes from FontMetrics and pass them
86
+ to pdfrb:
87
+
88
+ ```ruby
89
+ # In Idml::Render::Pipeline
90
+ metrics = Idml::TextEngine::FontMetrics.open(font_path)
91
+ widths = {}
92
+ (32..255).each { |cp| widths[cp] = metrics.glyph_width(cp) }
93
+
94
+ font_name = writer.register_font_with_metrics(
95
+ font_path,
96
+ widths: widths,
97
+ metrics: { units_per_em: metrics.units_per_em,
98
+ ascent: metrics.ascent,
99
+ descent: metrics.descent },
100
+ )
101
+ ```
102
+
103
+ ## Implementation notes
104
+
105
+ 1. Store `widths`, `metrics`, `encoding` alongside the font registration.
106
+ 2. In `glyph_width(font, codepoint)`: if external widths provided,
107
+ return `widths[codepoint] || 0`. Otherwise fall back to AFM (standard
108
+ 14) or the internal TTF parser.
109
+ 3. In `metrics_for(font)`: if external metrics provided, return them.
110
+ Otherwise fall back to AFM or internal parser.
111
+ 4. In `encode_text(text, font)`: if external encoding provided, use it
112
+ for codepoint → glyph ID mapping. Otherwise fall back to existing
113
+ encoding.
114
+ 5. FontFile2 embedding reads the raw file bytes (pdfrb already does
115
+ this correctly — the bug is only in table *parsing*, not file reading).
116
+
117
+ ## What pdfrb should NOT do
118
+
119
+ - Parse TTF/OTF tables internally — this is Fontisan's job.
120
+ - Depend on Fontisan — pdfrb should remain dependency-free for font
121
+ parsing. The caller decides which parser to use.
122
+ - Reimplement cmap/hmtx/head/hhea/name parsing — these are complex
123
+ binary formats with many edge cases that Fontisan handles correctly.
124
+
125
+ ## Priority
126
+
127
+ **Critical** — this unblocks TODOs 52 and 63 in the idml gem. Without
128
+ external metrics support, the idml gem cannot use pdfrb's measurement
129
+ API and must keep its entire text engine separate from pdfrb.