pdfrb 0.2.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (406) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +24 -0
  4. data/Gemfile +1 -0
  5. data/PROPOSAL.canvas-clip.md +92 -0
  6. data/PROPOSAL.canvas-draw-image-matrix.md +87 -0
  7. data/PROPOSAL.canvas-rich-text.md +103 -0
  8. data/PROPOSAL.canvas-text-lines.md +77 -0
  9. data/PROPOSAL.canvas-transparency.md +106 -0
  10. data/PROPOSAL.external-font-metrics.md +129 -0
  11. data/PROPOSAL.font-embedding-fontfile2.md +88 -0
  12. data/PROPOSAL.font-resolver-ttc.md +105 -0
  13. data/PROPOSAL.font-resolver.md +96 -0
  14. data/PROPOSAL.font-subsetting.md +98 -0
  15. data/PROPOSAL.gradient-shadings.md +110 -0
  16. data/PROPOSAL.page-mediabox-setter.md +66 -0
  17. data/PROPOSAL.per-glyph-width.md +91 -0
  18. data/PROPOSAL.stringio-require.md +53 -0
  19. data/PROPOSAL.struct-tree-root.md +102 -0
  20. data/PROPOSAL.text-measurement.md +93 -0
  21. data/PROPOSAL.ttf-table-parsing-fix.md +112 -0
  22. data/RELEASE.md +48 -0
  23. data/lib/pdfrb/action/base.rb +21 -0
  24. data/lib/pdfrb/action/types.rb +178 -0
  25. data/lib/pdfrb/action.rb +55 -0
  26. data/lib/pdfrb/annotation/base.rb +79 -0
  27. data/lib/pdfrb/annotation/types.rb +222 -0
  28. data/lib/pdfrb/annotation.rb +78 -0
  29. data/lib/pdfrb/appearance/generator.rb +216 -0
  30. data/lib/pdfrb/appearance.rb +11 -0
  31. data/lib/pdfrb/arlington/object_definition.rb +2 -2
  32. data/lib/pdfrb/arlington/predicate/evaluator.rb +1 -1
  33. data/lib/pdfrb/arlington/predicate/functions.rb +25 -9
  34. data/lib/pdfrb/arlington/predicate.rb +1 -5
  35. data/lib/pdfrb/cli/batch.rb +18 -0
  36. data/lib/pdfrb/cli/debug_info.rb +18 -0
  37. data/lib/pdfrb/cli/files.rb +16 -0
  38. data/lib/pdfrb/cli/fonts.rb +16 -0
  39. data/lib/pdfrb/cli/form.rb +18 -0
  40. data/lib/pdfrb/cli/image2pdf.rb +18 -0
  41. data/lib/pdfrb/cli/images.rb +34 -0
  42. data/lib/pdfrb/cli/info.rb +20 -0
  43. data/lib/pdfrb/cli/inspect.rb +18 -0
  44. data/lib/pdfrb/cli/merge.rb +18 -0
  45. data/lib/pdfrb/cli/modify.rb +18 -0
  46. data/lib/pdfrb/cli/optimize.rb +18 -0
  47. data/lib/pdfrb/cli/split.rb +21 -0
  48. data/lib/pdfrb/cli/watermark.rb +18 -0
  49. data/lib/pdfrb/cli.rb +312 -7
  50. data/lib/pdfrb/color/color_space.rb +259 -0
  51. data/lib/pdfrb/color/icc_profile.rb +90 -0
  52. data/lib/pdfrb/color.rb +12 -0
  53. data/lib/pdfrb/compare/comparator.rb +244 -0
  54. data/lib/pdfrb/compare/report.rb +78 -0
  55. data/lib/pdfrb/compare.rb +42 -0
  56. data/lib/pdfrb/composer.rb +161 -0
  57. data/lib/pdfrb/conformance/pdf_a.rb +317 -56
  58. data/lib/pdfrb/conformance/pdf_ua.rb +186 -29
  59. data/lib/pdfrb/conformance/pdf_x.rb +247 -0
  60. data/lib/pdfrb/conformance/rule.rb +78 -0
  61. data/lib/pdfrb/conformance/structure_elements.rb +174 -0
  62. data/lib/pdfrb/conformance/verapdf_bridge.rb +92 -0
  63. data/lib/pdfrb/conformance.rb +7 -3
  64. data/lib/pdfrb/content/canvas.rb +217 -72
  65. data/lib/pdfrb/content/color_space.rb +157 -0
  66. data/lib/pdfrb/content/hidden_text_detector.rb +167 -0
  67. data/lib/pdfrb/content/operator.rb +21 -1
  68. data/lib/pdfrb/content/operators/clipping.rb +36 -0
  69. data/lib/pdfrb/content/operators/graphics_state_params.rb +15 -0
  70. data/lib/pdfrb/content/operators.rb +21 -0
  71. data/lib/pdfrb/content/shading/base.rb +139 -0
  72. data/lib/pdfrb/content/shading.rb +17 -0
  73. data/lib/pdfrb/content/smart_text_extractor.rb +85 -0
  74. data/lib/pdfrb/content/tiling_pattern.rb +78 -0
  75. data/lib/pdfrb/content/transformation_matrix.rb +61 -0
  76. data/lib/pdfrb/content.rb +7 -26
  77. data/lib/pdfrb/destination/fit.rb +185 -0
  78. data/lib/pdfrb/destination.rb +13 -0
  79. data/lib/pdfrb/digital_signature/cms_handler.rb +22 -0
  80. data/lib/pdfrb/digital_signature/signing.rb +107 -11
  81. data/lib/pdfrb/digital_signature/timestamp_handler.rb +45 -0
  82. data/lib/pdfrb/digital_signature/verification.rb +88 -9
  83. data/lib/pdfrb/digital_signature/verification_result.rb +14 -0
  84. data/lib/pdfrb/digital_signature.rb +26 -2
  85. data/lib/pdfrb/document/annotations.rb +120 -3
  86. data/lib/pdfrb/document/associated_files.rb +104 -0
  87. data/lib/pdfrb/document/colors.rb +95 -0
  88. data/lib/pdfrb/document/destinations.rb +127 -5
  89. data/lib/pdfrb/document/display.rb +84 -0
  90. data/lib/pdfrb/document/fonts.rb +417 -45
  91. data/lib/pdfrb/document/form.rb +338 -0
  92. data/lib/pdfrb/document/form_xobject.rb +68 -0
  93. data/lib/pdfrb/document/graphics_state.rb +91 -0
  94. data/lib/pdfrb/document/images.rb +4 -4
  95. data/lib/pdfrb/document/info.rb +102 -0
  96. data/lib/pdfrb/document/layers.rb +62 -0
  97. data/lib/pdfrb/document/metadata.rb +12 -5
  98. data/lib/pdfrb/document/output_intents.rb +91 -0
  99. data/lib/pdfrb/document/page_labels.rb +80 -0
  100. data/lib/pdfrb/document/pages.rb +68 -19
  101. data/lib/pdfrb/document/portfolio.rb +119 -0
  102. data/lib/pdfrb/document/shadings.rb +159 -0
  103. data/lib/pdfrb/document/stamps.rb +68 -0
  104. data/lib/pdfrb/document/structure.rb +242 -0
  105. data/lib/pdfrb/document.rb +73 -4
  106. data/lib/pdfrb/encryption/identity.rb +13 -0
  107. data/lib/pdfrb/encryption/password_verification.rb +53 -8
  108. data/lib/pdfrb/encryption/security_handler.rb +8 -5
  109. data/lib/pdfrb/encryption/standard_security_handler.rb +154 -81
  110. data/lib/pdfrb/encryption.rb +17 -0
  111. data/lib/pdfrb/error.rb +2 -2
  112. data/lib/pdfrb/filter/brotli_decode.rb +32 -0
  113. data/lib/pdfrb/filter/ccitt_fax_decode.rb +12 -0
  114. data/lib/pdfrb/filter/crypt.rb +2 -2
  115. data/lib/pdfrb/filter/dct_decode.rb +18 -0
  116. data/lib/pdfrb/filter/flate_decode.rb +4 -18
  117. data/lib/pdfrb/filter/jbig2_decode.rb +12 -0
  118. data/lib/pdfrb/filter/jpx_decode.rb +18 -0
  119. data/lib/pdfrb/filter/lzw_decode.rb +3 -2
  120. data/lib/pdfrb/filter/png_predictor.rb +94 -0
  121. data/lib/pdfrb/filter.rb +26 -13
  122. data/lib/pdfrb/font/afm_parser.rb +50 -26
  123. data/lib/pdfrb/font/cmap/writer.rb +9 -0
  124. data/lib/pdfrb/font/encoding/win_ansi_encoding.rb +17 -0
  125. data/lib/pdfrb/font/encoding.rb +32 -3
  126. data/lib/pdfrb/font/metrics_helper.rb +121 -0
  127. data/lib/pdfrb/font/true_type/file.rb +10 -1
  128. data/lib/pdfrb/font/true_type/glyf.rb +82 -0
  129. data/lib/pdfrb/font/true_type/kern.rb +58 -0
  130. data/lib/pdfrb/font/true_type/loca.rb +42 -0
  131. data/lib/pdfrb/font/true_type/maxp.rb +44 -0
  132. data/lib/pdfrb/font/true_type/name.rb +53 -0
  133. data/lib/pdfrb/font/true_type/post.rb +93 -0
  134. data/lib/pdfrb/font/true_type/subsetter.rb +3 -5
  135. data/lib/pdfrb/font/true_type/wrapper.rb +102 -0
  136. data/lib/pdfrb/font/true_type.rb +7 -0
  137. data/lib/pdfrb/font.rb +2 -0
  138. data/lib/pdfrb/font_loader/from_configuration.rb +19 -0
  139. data/lib/pdfrb/font_loader/from_file.rb +20 -0
  140. data/lib/pdfrb/font_loader/standard14.rb +20 -0
  141. data/lib/pdfrb/font_loader/variant_from_name.rb +36 -0
  142. data/lib/pdfrb/font_loader.rb +4 -0
  143. data/lib/pdfrb/font_resolver.rb +138 -0
  144. data/lib/pdfrb/image/audit.rb +94 -0
  145. data/lib/pdfrb/image/downsampler.rb +124 -0
  146. data/lib/pdfrb/image.rb +13 -0
  147. data/lib/pdfrb/image_loader/jpeg.rb +39 -58
  148. data/lib/pdfrb/image_loader/png.rb +31 -257
  149. data/lib/pdfrb/layout/bidi.rb +198 -0
  150. data/lib/pdfrb/layout/box.rb +49 -0
  151. data/lib/pdfrb/layout/box_fitter.rb +54 -0
  152. data/lib/pdfrb/layout/column_box.rb +71 -0
  153. data/lib/pdfrb/layout/container_box.rb +45 -0
  154. data/lib/pdfrb/layout/frame.rb +67 -0
  155. data/lib/pdfrb/layout/image_box.rb +44 -0
  156. data/lib/pdfrb/layout/inline_box.rb +25 -0
  157. data/lib/pdfrb/layout/line.rb +55 -0
  158. data/lib/pdfrb/layout/list_box.rb +87 -0
  159. data/lib/pdfrb/layout/numeric_refinements.rb +43 -0
  160. data/lib/pdfrb/layout/page_style.rb +65 -0
  161. data/lib/pdfrb/layout/style.rb +62 -0
  162. data/lib/pdfrb/layout/table_box.rb +66 -0
  163. data/lib/pdfrb/layout/text_box.rb +38 -0
  164. data/lib/pdfrb/layout/text_fragment.rb +45 -0
  165. data/lib/pdfrb/layout/text_layouter.rb +123 -0
  166. data/lib/pdfrb/layout.rb +28 -0
  167. data/lib/pdfrb/linearization/hint_stream.rb +93 -0
  168. data/lib/pdfrb/linearization/writer.rb +259 -0
  169. data/lib/pdfrb/linearization.rb +21 -0
  170. data/lib/pdfrb/model/cos/dictionary.rb +12 -0
  171. data/lib/pdfrb/model/pdf_array.rb +2 -2
  172. data/lib/pdfrb/model/type/action.rb +42 -0
  173. data/lib/pdfrb/model/type/action_go_to_3d_view.rb +14 -0
  174. data/lib/pdfrb/model/type/action_go_to_dp.rb +16 -0
  175. data/lib/pdfrb/model/type/action_go_to_r.rb +26 -0
  176. data/lib/pdfrb/model/type/action_goto.rb +34 -0
  177. data/lib/pdfrb/model/type/action_hide.rb +26 -0
  178. data/lib/pdfrb/model/type/action_import_data.rb +18 -0
  179. data/lib/pdfrb/model/type/action_java_script.rb +18 -0
  180. data/lib/pdfrb/model/type/action_launch.rb +22 -0
  181. data/lib/pdfrb/model/type/action_movie.rb +22 -0
  182. data/lib/pdfrb/model/type/action_named.rb +23 -0
  183. data/lib/pdfrb/model/type/action_nop.rb +12 -0
  184. data/lib/pdfrb/model/type/action_rendition.rb +28 -0
  185. data/lib/pdfrb/model/type/action_reset_form.rb +22 -0
  186. data/lib/pdfrb/model/type/action_rich_media_execute.rb +17 -0
  187. data/lib/pdfrb/model/type/action_set_ocg_state.rb +32 -0
  188. data/lib/pdfrb/model/type/action_set_state.rb +14 -0
  189. data/lib/pdfrb/model/type/action_sound_action.rb +23 -0
  190. data/lib/pdfrb/model/type/action_submit_form.rb +64 -0
  191. data/lib/pdfrb/model/type/action_thread.rb +23 -0
  192. data/lib/pdfrb/model/type/action_trans.rb +30 -0
  193. data/lib/pdfrb/model/type/action_uri.rb +30 -0
  194. data/lib/pdfrb/model/type/af_embedded_file.rb +11 -0
  195. data/lib/pdfrb/model/type/af_file_specification.rb +34 -0
  196. data/lib/pdfrb/model/type/annotation.rb +49 -10
  197. data/lib/pdfrb/model/type/appearance.rb +41 -0
  198. data/lib/pdfrb/model/type/appearance_characteristics.rb +36 -0
  199. data/lib/pdfrb/model/type/appearance_generator.rb +22 -0
  200. data/lib/pdfrb/model/type/appearance_trap_net.rb +84 -0
  201. data/lib/pdfrb/model/type/border_effect.rb +25 -0
  202. data/lib/pdfrb/model/type/border_styling.rb +38 -0
  203. data/lib/pdfrb/model/type/box_color_info.rb +21 -0
  204. data/lib/pdfrb/model/type/box_style.rb +19 -0
  205. data/lib/pdfrb/model/type/button.rb +17 -0
  206. data/lib/pdfrb/model/type/cal_gray.rb +17 -0
  207. data/lib/pdfrb/model/type/cal_rgb.rb +22 -0
  208. data/lib/pdfrb/model/type/caret_annotation.rb +21 -0
  209. data/lib/pdfrb/model/type/catalog.rb +99 -16
  210. data/lib/pdfrb/model/type/choice.rb +21 -0
  211. data/lib/pdfrb/model/type/cid_font.rb +52 -1
  212. data/lib/pdfrb/model/type/cid_font_descriptor_metrics.rb +28 -0
  213. data/lib/pdfrb/model/type/cid_system_info.rb +28 -0
  214. data/lib/pdfrb/model/type/circle_annotation.rb +20 -0
  215. data/lib/pdfrb/model/type/collection.rb +27 -0
  216. data/lib/pdfrb/model/type/collection_field.rb +24 -0
  217. data/lib/pdfrb/model/type/collection_item.rb +19 -0
  218. data/lib/pdfrb/model/type/collection_schema.rb +25 -0
  219. data/lib/pdfrb/model/type/collection_sort.rb +13 -0
  220. data/lib/pdfrb/model/type/crypt_filter.rb +58 -0
  221. data/lib/pdfrb/model/type/device_n.rb +37 -0
  222. data/lib/pdfrb/model/type/device_n_mixing_hints.rb +17 -0
  223. data/lib/pdfrb/model/type/device_n_process.rb +13 -0
  224. data/lib/pdfrb/model/type/doc_mdp_transform_parameters.rb +21 -0
  225. data/lib/pdfrb/model/type/document_security_store.rb +55 -0
  226. data/lib/pdfrb/model/type/embedded_file.rb +19 -0
  227. data/lib/pdfrb/model/type/embedded_file_parameter.rb +48 -0
  228. data/lib/pdfrb/model/type/encryption_public_key.rb +38 -0
  229. data/lib/pdfrb/model/type/encryption_standard.rb +68 -0
  230. data/lib/pdfrb/model/type/field_mdp_transform_parameters.rb +26 -0
  231. data/lib/pdfrb/model/type/file_attachment_annotation.rb +29 -0
  232. data/lib/pdfrb/model/type/file_spec_ef.rb +22 -0
  233. data/lib/pdfrb/model/type/file_spec_rf.rb +21 -0
  234. data/lib/pdfrb/model/type/file_specification.rb +43 -0
  235. data/lib/pdfrb/model/type/file_trailer.rb +54 -0
  236. data/lib/pdfrb/model/type/font.rb +73 -2
  237. data/lib/pdfrb/model/type/font_cid_type0.rb +17 -0
  238. data/lib/pdfrb/model/type/font_cid_type2.rb +17 -0
  239. data/lib/pdfrb/model/type/font_descriptor.rb +47 -0
  240. data/lib/pdfrb/model/type/font_descriptor_cid_type0.rb +15 -0
  241. data/lib/pdfrb/model/type/font_descriptor_cid_type2.rb +15 -0
  242. data/lib/pdfrb/model/type/font_encoding.rb +47 -0
  243. data/lib/pdfrb/model/type/font_file.rb +28 -0
  244. data/lib/pdfrb/model/type/font_file2.rb +32 -0
  245. data/lib/pdfrb/model/type/font_file3.rb +23 -0
  246. data/lib/pdfrb/model/type/font_multiple_master.rb +60 -0
  247. data/lib/pdfrb/model/type/font_true_type.rb +1 -1
  248. data/lib/pdfrb/model/type/font_type0.rb +1 -1
  249. data/lib/pdfrb/model/type/font_type1.rb +1 -1
  250. data/lib/pdfrb/model/type/font_type3.rb +1 -1
  251. data/lib/pdfrb/model/type/form_field.rb +73 -0
  252. data/lib/pdfrb/model/type/free_text_annotation.rb +37 -0
  253. data/lib/pdfrb/model/type/function.rb +42 -0
  254. data/lib/pdfrb/model/type/function_exponential.rb +31 -0
  255. data/lib/pdfrb/model/type/function_postscript.rb +25 -0
  256. data/lib/pdfrb/model/type/function_sampled.rb +55 -0
  257. data/lib/pdfrb/model/type/function_stitching.rb +33 -0
  258. data/lib/pdfrb/model/type/generic_appearance.rb +40 -0
  259. data/lib/pdfrb/model/type/graphics_state_parameter.rb +69 -0
  260. data/lib/pdfrb/model/type/halftone.rb +21 -0
  261. data/lib/pdfrb/model/type/halftone_type1.rb +17 -0
  262. data/lib/pdfrb/model/type/halftone_type10.rb +16 -0
  263. data/lib/pdfrb/model/type/halftone_type16.rb +11 -0
  264. data/lib/pdfrb/model/type/halftone_type5.rb +24 -0
  265. data/lib/pdfrb/model/type/halftone_type6.rb +16 -0
  266. data/lib/pdfrb/model/type/highlight_annotation.rb +11 -0
  267. data/lib/pdfrb/model/type/icc_based_color_space.rb +24 -0
  268. data/lib/pdfrb/model/type/icon_fit.rb +31 -0
  269. data/lib/pdfrb/model/type/indexed.rb +32 -0
  270. data/lib/pdfrb/model/type/info.rb +44 -0
  271. data/lib/pdfrb/model/type/ink_annotation.rb +29 -0
  272. data/lib/pdfrb/model/type/interactive_form.rb +36 -0
  273. data/lib/pdfrb/model/type/interior_color.rb +12 -0
  274. data/lib/pdfrb/model/type/jbig2_globals.rb +15 -0
  275. data/lib/pdfrb/model/type/lab.rb +27 -0
  276. data/lib/pdfrb/model/type/line_annotation.rb +44 -0
  277. data/lib/pdfrb/model/type/line_ending_styling.rb +39 -0
  278. data/lib/pdfrb/model/type/link_annotation.rb +41 -0
  279. data/lib/pdfrb/model/type/link_appearance.rb +9 -0
  280. data/lib/pdfrb/model/type/mark_information.rb +23 -0
  281. data/lib/pdfrb/model/type/marked_content_reference.rb +24 -0
  282. data/lib/pdfrb/model/type/markup_annotation.rb +36 -0
  283. data/lib/pdfrb/model/type/mdp_dict.rb +28 -0
  284. data/lib/pdfrb/model/type/measure.rb +39 -0
  285. data/lib/pdfrb/model/type/media_clip.rb +28 -0
  286. data/lib/pdfrb/model/type/media_offset.rb +106 -0
  287. data/lib/pdfrb/model/type/metadata.rb +27 -3
  288. data/lib/pdfrb/model/type/misc_helpers.rb +75 -0
  289. data/lib/pdfrb/model/type/movie.rb +28 -0
  290. data/lib/pdfrb/model/type/names.rb +51 -0
  291. data/lib/pdfrb/model/type/namespace.rb +27 -0
  292. data/lib/pdfrb/model/type/object_reference.rb +31 -0
  293. data/lib/pdfrb/model/type/object_stream.rb +38 -0
  294. data/lib/pdfrb/model/type/opt_content_ext.rb +94 -0
  295. data/lib/pdfrb/model/type/opt_content_page_element.rb +18 -0
  296. data/lib/pdfrb/model/type/optional_content_config.rb +56 -0
  297. data/lib/pdfrb/model/type/optional_content_group.rb +35 -10
  298. data/lib/pdfrb/model/type/optional_content_membership.rb +36 -2
  299. data/lib/pdfrb/model/type/optional_content_properties.rb +16 -1
  300. data/lib/pdfrb/model/type/outline.rb +40 -6
  301. data/lib/pdfrb/model/type/outline_item.rb +45 -0
  302. data/lib/pdfrb/model/type/output_intent.rb +30 -0
  303. data/lib/pdfrb/model/type/page.rb +127 -0
  304. data/lib/pdfrb/model/type/page_label.rb +68 -0
  305. data/lib/pdfrb/model/type/page_tree_node.rb +51 -16
  306. data/lib/pdfrb/model/type/page_tree_node_root.rb +15 -0
  307. data/lib/pdfrb/model/type/pattern.rb +16 -0
  308. data/lib/pdfrb/model/type/pattern_shading.rb +25 -0
  309. data/lib/pdfrb/model/type/pattern_tiling.rb +25 -0
  310. data/lib/pdfrb/model/type/polygon_annotation.rb +29 -0
  311. data/lib/pdfrb/model/type/polygon_polyline.rb +14 -0
  312. data/lib/pdfrb/model/type/polyline_annotation.rb +29 -0
  313. data/lib/pdfrb/model/type/popup_annotation.rb +20 -0
  314. data/lib/pdfrb/model/type/printer_mark_annotation.rb +13 -0
  315. data/lib/pdfrb/model/type/redact_annotation.rb +28 -0
  316. data/lib/pdfrb/model/type/rendition.rb +31 -0
  317. data/lib/pdfrb/model/type/resources.rb +59 -14
  318. data/lib/pdfrb/model/type/rich_media_activation.rb +24 -0
  319. data/lib/pdfrb/model/type/rich_media_animation.rb +25 -0
  320. data/lib/pdfrb/model/type/rich_media_annotation.rb +18 -0
  321. data/lib/pdfrb/model/type/rich_media_command.rb +19 -0
  322. data/lib/pdfrb/model/type/rich_media_configuration.rb +32 -0
  323. data/lib/pdfrb/model/type/rich_media_content.rb +28 -0
  324. data/lib/pdfrb/model/type/rich_media_cue_point.rb +18 -0
  325. data/lib/pdfrb/model/type/rich_media_deactivation.rb +17 -0
  326. data/lib/pdfrb/model/type/rich_media_instance.rb +33 -0
  327. data/lib/pdfrb/model/type/rich_media_presentation.rb +29 -0
  328. data/lib/pdfrb/model/type/rich_media_settings.rb +33 -0
  329. data/lib/pdfrb/model/type/rich_media_window.rb +20 -0
  330. data/lib/pdfrb/model/type/screen_annotation.rb +19 -0
  331. data/lib/pdfrb/model/type/separation.rb +25 -0
  332. data/lib/pdfrb/model/type/sig_field_lock.rb +25 -0
  333. data/lib/pdfrb/model/type/sig_field_seed_value.rb +25 -0
  334. data/lib/pdfrb/model/type/signature.rb +65 -0
  335. data/lib/pdfrb/model/type/signature_build.rb +86 -0
  336. data/lib/pdfrb/model/type/signature_field.rb +17 -0
  337. data/lib/pdfrb/model/type/software_identifier.rb +19 -0
  338. data/lib/pdfrb/model/type/sound.rb +38 -0
  339. data/lib/pdfrb/model/type/sound_annotation.rb +21 -0
  340. data/lib/pdfrb/model/type/square_annotation.rb +24 -0
  341. data/lib/pdfrb/model/type/square_circle.rb +13 -0
  342. data/lib/pdfrb/model/type/squiggly_annotation.rb +11 -0
  343. data/lib/pdfrb/model/type/stamp_annotation.rb +19 -0
  344. data/lib/pdfrb/model/type/strikeout_annotation.rb +11 -0
  345. data/lib/pdfrb/model/type/struct_elem.rb +75 -0
  346. data/lib/pdfrb/model/type/struct_tree_root.rb +34 -0
  347. data/lib/pdfrb/model/type/structure_attributes.rb +44 -0
  348. data/lib/pdfrb/model/type/structure_element_kid.rb +33 -0
  349. data/lib/pdfrb/model/type/text_annotation.rb +25 -0
  350. data/lib/pdfrb/model/type/text_appearance.rb +9 -0
  351. data/lib/pdfrb/model/type/text_field.rb +21 -0
  352. data/lib/pdfrb/model/type/text_markup_annotation.rb +30 -0
  353. data/lib/pdfrb/model/type/three_d_activation.rb +32 -0
  354. data/lib/pdfrb/model/type/three_d_animation_style.rb +24 -0
  355. data/lib/pdfrb/model/type/three_d_background.rb +28 -0
  356. data/lib/pdfrb/model/type/three_d_cross_section.rb +25 -0
  357. data/lib/pdfrb/model/type/three_d_lighting_scheme.rb +26 -0
  358. data/lib/pdfrb/model/type/three_d_measure.rb +20 -0
  359. data/lib/pdfrb/model/type/three_d_measure_3dc.rb +23 -0
  360. data/lib/pdfrb/model/type/three_d_measure_ad3.rb +14 -0
  361. data/lib/pdfrb/model/type/three_d_measure_ld3.rb +14 -0
  362. data/lib/pdfrb/model/type/three_d_measure_pd3.rb +20 -0
  363. data/lib/pdfrb/model/type/three_d_measure_rd3.rb +14 -0
  364. data/lib/pdfrb/model/type/three_d_node.rb +37 -0
  365. data/lib/pdfrb/model/type/three_d_reference.rb +21 -0
  366. data/lib/pdfrb/model/type/three_d_render_mode.rb +34 -0
  367. data/lib/pdfrb/model/type/three_d_stream.rb +48 -0
  368. data/lib/pdfrb/model/type/three_d_units.rb +18 -0
  369. data/lib/pdfrb/model/type/three_d_view.rb +40 -0
  370. data/lib/pdfrb/model/type/to_unicode_cmap_stream.rb +23 -0
  371. data/lib/pdfrb/model/type/transition.rb +68 -0
  372. data/lib/pdfrb/model/type/underline_annotation.rb +11 -0
  373. data/lib/pdfrb/model/type/ur_transform_parameters.rb +33 -0
  374. data/lib/pdfrb/model/type/uri_dict.rb +18 -0
  375. data/lib/pdfrb/model/type/url_alias.rb +19 -0
  376. data/lib/pdfrb/model/type/variable_text_field.rb +47 -0
  377. data/lib/pdfrb/model/type/viewer_preferences.rb +42 -0
  378. data/lib/pdfrb/model/type/watermark_annotation.rb +13 -0
  379. data/lib/pdfrb/model/type/widget_annotation.rb +48 -0
  380. data/lib/pdfrb/model/type/widget_appearance.rb +9 -0
  381. data/lib/pdfrb/model/type/xobject_form.rb +41 -0
  382. data/lib/pdfrb/model/type/xobject_image.rb +56 -0
  383. data/lib/pdfrb/model/type/xref_stream.rb +37 -0
  384. data/lib/pdfrb/model/type.rb +286 -6
  385. data/lib/pdfrb/serializer.rb +2 -2
  386. data/lib/pdfrb/source/tokenizer.rb +0 -2
  387. data/lib/pdfrb/task/benchmark.rb +83 -0
  388. data/lib/pdfrb/task/extract_text.rb +1 -1
  389. data/lib/pdfrb/task/generate_corpus.rb +130 -0
  390. data/lib/pdfrb/task/memory_profile.rb +84 -0
  391. data/lib/pdfrb/task/optimize.rb +97 -7
  392. data/lib/pdfrb/task.rb +3 -3
  393. data/lib/pdfrb/test_utils.rb +112 -0
  394. data/lib/pdfrb/validator.rb +82 -0
  395. data/lib/pdfrb/version.rb +1 -1
  396. data/lib/pdfrb/writer.rb +117 -27
  397. data/lib/pdfrb/xmp/packet.rb +122 -0
  398. data/lib/pdfrb/xmp/schemas.rb +96 -0
  399. data/lib/pdfrb/xmp.rb +44 -0
  400. data/lib/pdfrb/xref_section.rb +2 -2
  401. data/lib/pdfrb.rb +25 -53
  402. data/pdfrb.gemspec +45 -0
  403. data/script/apply_fixes.sh +843 -0
  404. data/script/make_jpeg_fixture.rb +53 -0
  405. data/script/make_png_fixture.rb +25 -0
  406. metadata +312 -2
@@ -0,0 +1,88 @@
1
+ # PROPOSAL: Font embedding verification and FontFile2 output
2
+
3
+ ## Summary
4
+
5
+ Ensure `Fonts#add` produces a `/FontFile2` (or `/FontFile3`) stream
6
+ in the output PDF when a TrueType/OpenType font file is passed. Add
7
+ a `Fonts#embedded?` query and log warnings when embedding fails.
8
+
9
+ ## Motivation
10
+
11
+ The `idml` gem registers document fonts via `document.fonts.add(path)`.
12
+ The returned resource name works for `canvas.text`, but the output PDF
13
+ does not contain `/FontFile2`. This means:
14
+ - Text renders correctly only on systems with the font installed.
15
+ - The PDF is not self-contained.
16
+ - PDF/A compliance (which requires all fonts embedded) is impossible.
17
+
18
+ Testing with `/System/Library/Fonts/Supplemental/Arial.ttf`:
19
+ ```ruby
20
+ doc = Pdfrb::Document.new
21
+ doc.fonts.add("/System/Library/Fonts/Supplemental/Arial.ttf")
22
+ # Output PDF has /BaseFont but no /FontFile2
23
+ ```
24
+
25
+ ## Root cause analysis
26
+
27
+ Looking at `Fonts#add`, the code path for TrueType fonts calls
28
+ `register_font(resource, name, name_or_io, **opts)`. This method
29
+ may not be creating the FontDescriptor + FontFile2 objects for
30
+ all font types. The standard 14 Type1 fonts don't need embedding,
31
+ but TrueType (.ttf) and OpenType (.otf) files should be embedded
32
+ as FontFile2 / FontFile3 respectively.
33
+
34
+ ## Proposed changes
35
+
36
+ ### 1. Ensure FontFile2 for TrueType
37
+
38
+ When `name_or_io` is a `.ttf` file path or IO:
39
+ 1. Read the font data.
40
+ 2. Create a stream object with the raw TTF bytes.
41
+ 3. Create a FontDescriptor with `/FontFile2` pointing to the stream.
42
+ 4. Set the Font dictionary's `/FontDescriptor` to the FontDescriptor.
43
+
44
+ ### 2. Add `Fonts#embedded?(resource)` query
45
+
46
+ ```ruby
47
+ def embedded?(resource)
48
+ entry = @font_entries[resource]
49
+ return false unless entry
50
+
51
+ entry[:font_descriptor]&.value&.key?(:FontFile2) ||
52
+ entry[:font_descriptor]&.value&.key?(:FontFile3)
53
+ end
54
+ ```
55
+
56
+ ### 3. Log warning on embedding failure
57
+
58
+ When embedding is requested but fails (corrupt font, unsupported
59
+ format), log a warning:
60
+
61
+ ```ruby
62
+ Pdfrb.logger.warn("Failed to embed font #{name}: #{e.message}")
63
+ ```
64
+
65
+ ### 4. Font subsetting (stretch goal)
66
+
67
+ Embed only the glyphs actually used:
68
+ 1. Collect used codepoints from `encode_text` calls.
69
+ 2. Subset the TTF using a glyph table parser.
70
+ 3. Embed the subsetted font as FontFile2.
71
+
72
+ This reduces a 500KB font to ~5-20KB in the PDF.
73
+
74
+ ## Usage example
75
+
76
+ ```ruby
77
+ font = doc.fonts.add("/path/to/MinionPro-Regular.otf")
78
+ doc.fonts.embedded?(font) # => true (after this proposal)
79
+
80
+ page = doc.pages.add
81
+ page.canvas.text("Hello", at: [72, 720], font: font, size: 12)
82
+ # PDF now contains /FontFile2 with the font data
83
+ ```
84
+
85
+ ## Priority
86
+
87
+ High — without font embedding, the PDF is not portable. This is the
88
+ #1 gap for the idml gem's rendering pipeline.
@@ -0,0 +1,105 @@
1
+ # PROPOSAL: FontResolver .ttc (TrueType Collection) support
2
+
3
+ ## Summary
4
+
5
+ `Pdfrb::FontResolver` doesn't search for `.ttc` (TrueType Collection)
6
+ files. On macOS, many common fonts (Helvetica, Times New Roman, Courier,
7
+ Arial Unicode) are stored exclusively as `.ttc`. Without `.ttc` support,
8
+ the resolver returns `nil` for these fonts.
9
+
10
+ ## Bug description
11
+
12
+ In `build_cache` (font_resolver.rb):
13
+
14
+ ```ruby
15
+ Dir.glob("**/*.{ttf,otf}", base: dir).each do |rel_path|
16
+ ```
17
+
18
+ The glob only matches `.ttf` and `.otf` files — `.ttc` is excluded.
19
+
20
+ ## Reproduction
21
+
22
+ ```ruby
23
+ resolver = Pdfrb::FontResolver.new
24
+ resolver.find(family: "Helvetica", style: "Regular")
25
+ # => nil (should find /System/Library/Fonts/Helvetica.ttc)
26
+ ```
27
+
28
+ On macOS:
29
+ - Helvetica is at `/System/Library/Fonts/Helvetica.ttc`
30
+ - Times New Roman is at `/System/Library/Fonts/Times New Roman.ttf` (works)
31
+ - Arial Unicode MS is at `/System/Library/Fonts/Supplemental/Arial Unicode.ttf` (works)
32
+ - Many system fonts are `.ttc` only
33
+
34
+ ## Impact
35
+
36
+ The `idml` gem cannot use `Pdfrb::FontResolver` on macOS because many
37
+ fonts are `.ttc` only. It keeps the `Fontisan`-based resolver as a
38
+ fallback.
39
+
40
+ ## Proposed fix
41
+
42
+ ### 1. Add `.ttc` to glob
43
+
44
+ ```ruby
45
+ Dir.glob("**/*.{ttf,otf,ttc}", base: dir).each do |rel_path|
46
+ ```
47
+
48
+ ### 2. Parse .ttc files
49
+
50
+ A `.ttc` file contains multiple fonts. The TTC header lists the offset
51
+ of each font's data within the file:
52
+
53
+ ```
54
+ TTC Header:
55
+ Tag: "ttcf"
56
+ MajorVersion, MinorVersion
57
+ numFonts: N
58
+ offsetTable[N]: offsets to each font's data
59
+
60
+ Each font at its offset has a standard TTF/OTF structure.
61
+ ```
62
+
63
+ For each `.ttc` file, parse the TTC header and iterate each font:
64
+
65
+ ```ruby
66
+ def parse_font_info(path)
67
+ data = File.binread(path)
68
+ if data[0, 4] == "ttcf"
69
+ parse_ttc(path, data)
70
+ else
71
+ parse_ttf(path, data) # existing single-font parsing
72
+ end
73
+ end
74
+
75
+ def parse_ttc(path, data)
76
+ results = []
77
+ num_fonts = data[8, 4].unpack1("N")
78
+ num_fonts.times do |i|
79
+ offset = data[12 + i * 4, 4].unpack1("N")
80
+ info = parse_ttf_at_offset(path, data, offset)
81
+ results << info if info
82
+ end
83
+ results
84
+ end
85
+ ```
86
+
87
+ ### 3. Register fonts from .ttc with Fonts#add
88
+
89
+ When registering a font from a `.ttc` file, the caller should specify
90
+ which font within the collection:
91
+
92
+ ```ruby
93
+ # Option A: FontResolver returns the .ttc path + index
94
+ resolver.find(family: "Helvetica", style: "Regular")
95
+ # => { path: "/System/Library/Fonts/Helvetica.ttc", index: 0 }
96
+
97
+ # Option B: Fonts#add accepts a collection index
98
+ doc.fonts.add("/path/to/Helvetica.ttc", collection_index: 0)
99
+ ```
100
+
101
+ ## Priority
102
+
103
+ High — macOS is a primary development platform for the idml gem.
104
+ Without `.ttc` support, pdfrb's FontResolver is unusable on macOS for
105
+ common fonts.
@@ -0,0 +1,96 @@
1
+ # PROPOSAL: Font file resolver
2
+
3
+ ## Summary
4
+
5
+ Add `Pdfrb::FontResolver` that searches system font directories for
6
+ font files by family name + style. Eliminates the need for external
7
+ font-finding libraries (like `fontisan` in the `idml` gem).
8
+
9
+ ## Motivation
10
+
11
+ The `idml` gem uses `Fontisan` to:
12
+ 1. Search `/System/Library/Fonts`, `/Library/Fonts`, `~/Library/Fonts`,
13
+ `/usr/share/fonts`, `~/.local/share/fonts` for `.ttf`/`.otf` files.
14
+ 2. Parse each file's `name` table to get PostScript name, family name,
15
+ style name.
16
+ 3. Match by family + style (e.g., "Minion Pro" + "Regular" →
17
+ "MinionPro-Regular").
18
+ 4. Return the file path.
19
+
20
+ This is ~100 lines of `fontisan`-dependent code. If pdfrb provided font
21
+ resolution, the `idml` gem could drop the `fontisan` dependency entirely.
22
+
23
+ ## Proposed API
24
+
25
+ ```ruby
26
+ module Pdfrb
27
+ class FontResolver
28
+ DEFAULT_SEARCH_PATHS = [
29
+ "/System/Library/Fonts",
30
+ "/Library/Fonts",
31
+ File.expand_path("~/Library/Fonts"),
32
+ "/usr/share/fonts",
33
+ File.expand_path("~/.local/share/fonts"),
34
+ ].freeze
35
+
36
+ # @param search_paths [Array<String>] directories to search.
37
+ def initialize(search_paths: DEFAULT_SEARCH_PATHS)
38
+ end
39
+
40
+ # Find a font file by family name and optional style.
41
+ # @param family [String] e.g., "Arial", "Minion Pro"
42
+ # @param style [String] e.g., "Regular", "Bold", "Italic"
43
+ # @return [String, nil] file path, or nil if not found.
44
+ def find(family:, style: "Regular")
45
+ end
46
+
47
+ # Find by PostScript name (exact match).
48
+ # @param ps_name [String] e.g., "ArialMT", "MinionPro-Regular"
49
+ # @return [String, nil] file path.
50
+ def find_by_ps_name(ps_name)
51
+ end
52
+
53
+ # List all available font files in search paths.
54
+ # @return [Array<Hash>] [{ path:, family:, style:, ps_name: }, ...]
55
+ def available_fonts
56
+ end
57
+ end
58
+ end
59
+ ```
60
+
61
+ ## Implementation notes
62
+
63
+ pdfrb already parses font `name` tables during `Fonts#add`. The resolver
64
+ would:
65
+
66
+ 1. Glob `**/*.{ttf,otf,ttc}` in each search path.
67
+ 2. For each file, parse the `name` table to get family/style/PS name.
68
+ 3. Cache results (font directory scanning is expensive).
69
+ 4. Match by family + style with fuzzy matching (e.g., "Bold Italic"
70
+ matches "BoldItalic").
71
+
72
+ For `.ttc` (TrueType Collection) files, each file contains multiple
73
+ fonts. The resolver should iterate all fonts within the collection.
74
+
75
+ Platform-specific paths:
76
+ - macOS: `/System/Library/Fonts`, `/Library/Fonts`, `~/Library/Fonts`
77
+ - Linux: `/usr/share/fonts`, `/usr/local/share/fonts`, `~/.local/share/fonts`, `~/.fonts`
78
+ - Windows: `C:\Windows\Fonts`
79
+
80
+ ## Usage example
81
+
82
+ ```ruby
83
+ resolver = Pdfrb::FontResolver.new
84
+ path = resolver.find(family: "Minion Pro", style: "Regular")
85
+ # => "/Library/Fonts/MinionPro-Regular.otf"
86
+
87
+ font = doc.fonts.add(path)
88
+ page = doc.pages.add
89
+ page.canvas.text("Hello", at: [72, 720], font: font, size: 12)
90
+ ```
91
+
92
+ ## Priority
93
+
94
+ Medium — eliminates the `fontisan` dependency for pdfrb consumers.
95
+ The `idml` gem's entire `FontResolver` + `FontMetrics` classes
96
+ (~300 lines) could be replaced by pdfrb's resolver + measurement.
@@ -0,0 +1,98 @@
1
+ # PROPOSAL: Font subsetting via used_codepoints
2
+
3
+ ## Summary
4
+
5
+ Automatically subset embedded TrueType/OpenType fonts to include only
6
+ the glyphs actually used in the document. pdfrb 0.18.0 already tracks
7
+ `Fonts#used_codepoints` — extend this to drive the font subsetting
8
+ during serialization.
9
+
10
+ ## Motivation
11
+
12
+ The `idml` gem embeds document fonts via `Fonts#add`. Without subsetting,
13
+ a 500KB Minion Pro font is embedded in full, even if only 50 glyphs are
14
+ used. Subsetting reduces this to ~5-15KB — a 30-100× reduction.
15
+
16
+ This is critical for:
17
+ - Web PDFs (smaller download)
18
+ - Email attachments (size limits)
19
+ - PDF/A compliance (font embedding is mandatory, but subsetting is best practice)
20
+ - Batch processing (many small PDFs)
21
+
22
+ ## Current state in pdfrb 0.18.0
23
+
24
+ - `Fonts#used_codepoints` — tracks which Unicode codepoints have been
25
+ encoded via `encode_text`. ✓
26
+ - `Fonts#embedded?` — checks if a font has FontFile2. ✓
27
+ - `Fonts#add` — registers fonts but may not subset by default.
28
+
29
+ ## Proposed changes
30
+
31
+ ### 1. Automatic subsetting on write
32
+
33
+ When `Document#write` serializes the PDF, for each embedded TrueType
34
+ font:
35
+
36
+ 1. Get `used_codepoints` for the font resource.
37
+ 2. Parse the TTF/OTF file's `glyf` (TrueType) or `CFF` (OpenType) table.
38
+ 3. Build the glyph closure: used glyphs + their component glyphs
39
+ (for composite glyphs) + `.notdef`.
40
+ 4. Rebuild the font tables with only the needed glyphs.
41
+ 5. Write the subsetted font as the FontFile2 stream.
42
+
43
+ ### 2. Opt-in flag
44
+
45
+ ```ruby
46
+ font = doc.fonts.add("/path/to/font.ttf", subset: true)
47
+ # ... render text ...
48
+ doc.write("output.pdf") # FontFile2 contains only used glyphs
49
+ ```
50
+
51
+ Default: `subset: true` (most callers want subsetting).
52
+
53
+ ### 3. Subset prefix
54
+
55
+ Subsetting conventions use a 6-character uppercase prefix + `+` before
56
+ the font name (e.g., `ABCDEF+MinionPro-Regular`). This signals to PDF
57
+ processors that the font is a subset.
58
+
59
+ ```ruby
60
+ # In the /BaseFont entry:
61
+ /BaseFont /ABCDEF+MinionPro-Regular
62
+ ```
63
+
64
+ ### 4. Fallback for invalid fonts
65
+
66
+ If subsetting fails (corrupt font, unsupported table structure), fall
67
+ back to full embedding and log a warning.
68
+
69
+ ## Implementation notes
70
+
71
+ TrueType subsetting algorithm:
72
+ 1. Collect used codepoint → glyph ID mappings from `cmap`.
73
+ 2. Expand glyph closure: for each glyph, check if it's a composite
74
+ (`.ttf` glyf table with `GF_COMPONENT` flag). Add component glyphs.
75
+ 3. Rebuild `glyf`, `loca`, `maxp`, `hmtx`, `hhea`, `cmap`, `name`
76
+ tables with only the needed glyphs.
77
+ 4. Recompute checksums.
78
+
79
+ OpenType/CFF subsetting is more complex (CharStrings, Private DICT,
80
+ FDArray). Consider using a library like `ttf-rb` or porting the
81
+ algorithm from `fontTools.subset` (Python).
82
+
83
+ ## Usage example
84
+
85
+ ```ruby
86
+ doc = Pdfrb::Document.new
87
+ font = doc.fonts.add("/path/to/MinionPro-Regular.otf")
88
+ page = doc.pages.add
89
+ page.canvas.text("Hello, World!", at: [72, 720], font: font, size: 12)
90
+ doc.write("output.pdf")
91
+ # Only H, e, l, o, comma, space, W, r, d, ! glyphs are embedded
92
+ ```
93
+
94
+ ## Priority
95
+
96
+ High — font subsetting is the #1 remaining gap for production-quality
97
+ PDF output. Without it, every idml-generated PDF is 300-500KB larger
98
+ than necessary.
@@ -0,0 +1,110 @@
1
+ # PROPOSAL: Gradient shading patterns
2
+
3
+ ## Summary
4
+
5
+ Add `Document#shadings` facade and `Canvas#fill_shading(name)` /
6
+ `Canvas#stroke_shading(name)` for PDF Type 2 (axial) and Type 3 (radial)
7
+ gradient shading patterns. Replaces the discrete-rectangle approximation
8
+ used by the `idml` gem.
9
+
10
+ ## Motivation
11
+
12
+ The `idml` gem renders IDML gradient fills as 32 discrete colored
13
+ rectangles (a "staircase" approximation). This produces visible banding
14
+ and a larger file size than a proper PDF shading pattern.
15
+
16
+ PDF natively supports smooth gradients via shading dictionaries:
17
+ - **Type 2 (Axial)**: linear gradient between two points.
18
+ - **Type 3 (Radial)**: circular gradient between two circles.
19
+ - **Type 4–7 (Free-form, Coons, Tensor-product)**: complex meshes.
20
+
21
+ A proper shading pattern produces a mathematically smooth gradient with
22
+ zero banding.
23
+
24
+ ## Proposed API
25
+
26
+ ```ruby
27
+ module Pdfrb
28
+ class Document
29
+ class Shadings
30
+ # Add an axial (linear) gradient.
31
+ # @param from [Array(Numeric, Numeric)] start point [x, y].
32
+ # @param to [Array(Numeric, Numeric)] end point [x, y].
33
+ # @param stops [Array<Array>] gradient stops:
34
+ # [[offset, [color_family, *values]], ...]
35
+ # e.g., [[0.0, [:rgb, 1, 0, 0]], [1.0, [:rgb, 0, 0, 1]]]
36
+ # @return [Symbol] shading resource name (e.g., :Sh1).
37
+ def add_axial(from:, to:, stops:)
38
+ end
39
+
40
+ # Add a radial gradient.
41
+ # @param from [Array(Numeric, Numeric, Numeric)] [cx, cy, radius].
42
+ # @param to [Array(Numeric, Numeric, Numeric)] [cx, cy, radius].
43
+ # @param stops [Array<Array>] same format as axial.
44
+ # @return [Symbol] shading resource name.
45
+ def add_radial(from:, to:, stops:)
46
+ end
47
+
48
+ def [](name); end
49
+ def each(&block); end
50
+ end
51
+ end
52
+ end
53
+ ```
54
+
55
+ ```ruby
56
+ class Pdfrb::Content::Canvas
57
+ # Fill the current path using a shading pattern.
58
+ # @param name [Symbol] shading resource name from Shadings#add_axial.
59
+ def fill_shading(name)
60
+ emit_op(Operator::ShadingFill, name)
61
+ end
62
+ end
63
+ ```
64
+
65
+ ## PDF structure
66
+
67
+ ```
68
+ /Shading << /Sh1 << /Type /Shading /ShadingType 2
69
+ /ColorSpace /DeviceRGB
70
+ /Coords [x0 y0 x1 y1]
71
+ /Function << /FunctionType 4 /Domain [0 1]
72
+ /Range [0 1 0 1 0 1]
73
+ /Length N >> stream { ... PostScript calculator ... } endstream
74
+ >> >>
75
+ ```
76
+
77
+ For simple 2-stop gradients, a Type 2 ExponentialInterpolation
78
+ function is simpler:
79
+
80
+ ```
81
+ /Function << /FunctionType 2 /Domain [0 1]
82
+ /C0 [1 0 0] /C1 [0 0 1] /N 1 >>
83
+ ```
84
+
85
+ ## Usage example
86
+
87
+ ```ruby
88
+ shading = doc.shadings.add_axial(
89
+ from: [0, 0], to: [200, 0],
90
+ stops: [[0.0, [:rgb, 1, 0, 0]], [1.0, [:rgb, 0, 0, 1]]]
91
+ )
92
+ page.canvas.rectangle(0, 0, 200, 100)
93
+ page.canvas.fill_shading(shading)
94
+ ```
95
+
96
+ ## Implementation notes
97
+
98
+ - Shadings need to be in `/Resources/Shading` on each page (or in the
99
+ catalog for document-wide).
100
+ - Multi-stop gradients require a StitchingFunction (Type 3) that
101
+ chains multiple Type 2 interpolation functions.
102
+ - For color spaces, support DeviceRGB, DeviceCMYK, and DeviceGray.
103
+ - The `sh` operator (shading fill) ignores the current path — it fills
104
+ the entire shading clip area. To clip to a shape, use the path as a
105
+ clip before `sh`.
106
+
107
+ ## Priority
108
+
109
+ Medium — gradients are a visual quality issue, not a correctness issue.
110
+ The discrete approximation works but produces visible banding.
@@ -0,0 +1,66 @@
1
+ # PROPOSAL: Page MediaBox setter
2
+
3
+ ## Summary
4
+
5
+ Add `Page#media_box=` setter method so pages can have custom dimensions
6
+ without accessing the COS dictionary directly.
7
+
8
+ ## Motivation
9
+
10
+ The `idml` gem renders IDML documents with arbitrary page sizes (A4,
11
+ business card, poster, etc.). pdfrb defaults to US Letter (612×792).
12
+ Currently, setting a custom MediaBox requires:
13
+
14
+ ```ruby
15
+ page = document.pages.add
16
+ page[:"MediaBox"] = [0, 0, width, height] # COS dict access
17
+ ```
18
+
19
+ This is fragile — it bypasses pdfrb's model layer and couples the caller
20
+ to COS internals. A proper setter provides a clean, model-driven API.
21
+
22
+ ## Proposed API
23
+
24
+ ```ruby
25
+ class Pdfrb::Model::Type::Page
26
+ # Set the page's MediaBox.
27
+ # @param box [Array<Numeric>] [llx, lly, urx, ury] in PDF points.
28
+ def media_box=(box)
29
+ self[:"MediaBox"] = box
30
+ end
31
+ end
32
+ ```
33
+
34
+ Alternatively, support it at creation time:
35
+
36
+ ```ruby
37
+ # In Pages#add
38
+ def add(width: 612, height: 792, **opts)
39
+ page = super(**opts)
40
+ page.media_box = [0, 0, width, height] unless width == 612 && height == 792
41
+ page
42
+ end
43
+ ```
44
+
45
+ ## Usage example
46
+
47
+ ```ruby
48
+ # A4 portrait
49
+ page = document.pages.add
50
+ page.media_box = [0, 0, 595, 842]
51
+
52
+ # Custom size via creation
53
+ page = document.pages.add(width: 595, height: 842)
54
+ ```
55
+
56
+ ## Implementation notes
57
+
58
+ - The reader method `media_box` already exists.
59
+ - The setter is a one-liner delegating to `self[:"MediaBox"]=`.
60
+ - The COS dict `[]=` is public (inherited from Cos::Dictionary).
61
+ - No validation needed at this level — callers pass valid rectangles.
62
+
63
+ ## Priority
64
+
65
+ Low — workaround exists via `page[:"MediaBox"] = [...]`. But a proper
66
+ setter improves API cleanliness and discoverability.
@@ -0,0 +1,91 @@
1
+ # PROPOSAL: Per-glyph width measurement
2
+
3
+ ## Summary
4
+
5
+ Add `Fonts#glyph_width(font, codepoint)` and `Fonts#glyph_widths(font,
6
+ codepoints)` for measuring individual glyph advance widths. Enables
7
+ text layout engines to do per-character shaping without parsing font
8
+ binary tables externally.
9
+
10
+ ## Motivation
11
+
12
+ The `idml` gem has its own text layout engine (`Idml::TextEngine`) that
13
+ shapes text character-by-character via a `FontMetrics#glyph_width(cp)`
14
+ method. This requires parsing the TTF `hmtx` table via the `fontisan`
15
+ gem (~200 lines of binary parsing code).
16
+
17
+ pdfrb 0.18.0 has `Fonts#measure_text(text, font:, size:)` which measures
18
+ a full string. But for line breaking, the layout engine needs per-glyph
19
+ widths to know where word boundaries fall and how to distribute space
20
+ across justified lines.
21
+
22
+ ## Proposed API
23
+
24
+ ```ruby
25
+ # Measure the advance width of a single glyph at size 1000 (font units).
26
+ # @param font [Symbol] font resource name.
27
+ # @param codepoint [Integer] Unicode codepoint.
28
+ # @return [Integer] advance width in font units (not scaled by size).
29
+ def glyph_width(font, codepoint)
30
+ # Look up glyph ID from cmap, then width from hmtx.
31
+ end
32
+
33
+ # Measure advance widths for multiple codepoints in one call.
34
+ # @param font [Symbol] font resource name.
35
+ # @param codepoints [Array<Integer>] Unicode codepoints.
36
+ # @return [Array<Integer>] widths in font units, one per codepoint.
37
+ def glyph_widths(font, codepoints)
38
+ codepoints.map { |cp| glyph_width(font, cp) }
39
+ end
40
+ ```
41
+
42
+ ## Why not just measure_text per character?
43
+
44
+ `measure_text(char, font:, size:)` works but:
45
+ 1. It returns a scaled `Float` (width in points at the given size).
46
+ The layout engine needs unscaled `Integer` widths (font units) to
47
+ avoid floating-point accumulation errors across many glyphs.
48
+ 2. It's slower — each call goes through the full encoding + metrics
49
+ pipeline. A batch `glyph_widths` call is more efficient.
50
+ 3. It doesn't distinguish between "glyph not found" (width 0) and
51
+ "glyph has width 0" (like space in some fonts).
52
+
53
+ ## Implementation notes
54
+
55
+ pdfrb already parses the `cmap` and `hmtx` tables during font loading
56
+ (for `encode_text` and `measure_text`). The glyph width data is in
57
+ memory — `glyph_width` just needs to expose it.
58
+
59
+ For the 14 standard Type1 fonts, AFM data already has per-glyph widths.
60
+ For embedded TrueType/OpenType, the `hmtx` table has advance widths
61
+ indexed by glyph ID.
62
+
63
+ ```ruby
64
+ def glyph_width(font, codepoint)
65
+ font_data = font_data_for(font)
66
+ return 0 unless font_data
67
+
68
+ glyph_id = font_data.cmap_lookup(codepoint)
69
+ font_data.advance_width(glyph_id)
70
+ end
71
+ ```
72
+
73
+ ## Usage example
74
+
75
+ ```ruby
76
+ font = doc.fonts.add("/path/to/Arial.ttf")
77
+
78
+ # Per-glyph widths for line breaking
79
+ widths = doc.fonts.glyph_widths(font, "Hello World".each_codepoint.to_a)
80
+ # => [722, 556, 556, 556, 277, 0, 667, 556, 722, 556, 500]
81
+
82
+ # Text layout engine uses these for word-wrap
83
+ total = widths.sum # => 5661 font units
84
+ width_at_12pt = total * 12.0 / 1000 # => 67.9 points
85
+ ```
86
+
87
+ ## Priority
88
+
89
+ Medium — `measure_text` is a workaround, but per-glyph widths eliminate
90
+ the need for `fontisan` in the `idml` gem and enable faster, more
91
+ accurate text layout.