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,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Layout
5
+ # Paragraph-level Unicode bidi reordering (UAX #9, simplified).
6
+ #
7
+ # Implements:
8
+ # * P2-P3: paragraph embedding level from the first strong
9
+ # character (L => 0, R/AL => 1).
10
+ # * X9: skip BN/RLO/RLE/LRO/LRE/PDF/RLI/LRI/FSI (treated as
11
+ # neutral for this paragraph-level pass).
12
+ # * I1-I2: implicit level resolution for strong characters.
13
+ # * L1-L4: whitespace resets, neutrals on level boundaries,
14
+ # paired-bracket mirroring.
15
+ # * L2: reverse character order on level runs from highest to
16
+ # lowest, producing a visual order string.
17
+ #
18
+ # Scope: paragraph-level reordering only (no full isolating-run
19
+ # sequence handling). Sufficient for Hebrew/Arabic paragraphs and
20
+ # embedded LTR runs inside RTL paragraphs (and vice versa). Not
21
+ # sufficient for complex mixed-direction runs at character
22
+ # granularity inside a single visual line.
23
+ module Bidi
24
+ LEFT_TO_RIGHT = 0
25
+ RIGHT_TO_LEFT = 1
26
+
27
+ # Mirroring map for paired brackets (UAX #9 Bidi_Mirroring_Glyph
28
+ # property, subset covering common punctuation).
29
+ MIRROR = {
30
+ "(" => ")",
31
+ ")" => "(",
32
+ "[" => "]",
33
+ "]" => "[",
34
+ "{" => "}",
35
+ "}" => "{",
36
+ "<" => ">",
37
+ ">" => "<",
38
+ "‘" => "’",
39
+ "’" => "‘",
40
+ "“" => "”",
41
+ "”" => "“",
42
+ "«" => "»",
43
+ "»" => "«",
44
+ "‹" => "›",
45
+ "›" => "‹",
46
+ }.freeze
47
+
48
+ module_function
49
+
50
+ # Returns the resolved paragraph embedding level: 0 for LTR
51
+ # (or no strong direction), 1 for RTL.
52
+ def paragraph_level(text)
53
+ text.to_s.each_codepoint do |cp|
54
+ case strong_direction(cp)
55
+ when :ltr then return LEFT_TO_RIGHT
56
+ when :rtl then return RIGHT_TO_LEFT
57
+ end
58
+ end
59
+ LEFT_TO_RIGHT
60
+ end
61
+
62
+ # Reorder +text+ into visual order. Returns a new String of the
63
+ # same grapheme count.
64
+ def reorder(text)
65
+ clusters = text.to_s.grapheme_clusters.to_a
66
+ return text.to_s if clusters.size <= 1
67
+
68
+ base = paragraph_level(text)
69
+ levels = levels_for(clusters, base)
70
+ visual = reverse_by_levels(clusters, levels)
71
+ mirror_runs(visual, levels)
72
+ end
73
+
74
+ # Whether +text+ contains any RTL strong character.
75
+ def rtl?(text)
76
+ text.to_s.each_codepoint.any? { |cp| strong_direction(cp) == :rtl }
77
+ end
78
+
79
+ # ---- Character classification (UAX #9 BD1-BD2, trimmed) ----
80
+
81
+ # Strong direction (:ltr, :rtl, or nil for non-strong). Used by
82
+ # paragraph_level to find the first strong character per P2-P3.
83
+ def strong_direction(codepoint)
84
+ case bidi_type(codepoint)
85
+ when :L then :ltr
86
+ when :R, :AL then :rtl
87
+ end
88
+ end
89
+
90
+ # UAX #9 bidi type for a codepoint. Subset implementation:
91
+ # covers Latin, Hebrew, Arabic, digits, common punctuation.
92
+ def bidi_type(codepoint)
93
+ case codepoint
94
+ when 0x0660..0x0669, 0x06F0..0x06F9 then :AN
95
+ when 0x0300..0x036F, 0x0483..0x0489,
96
+ 0x0591..0x05BD, 0x05BF, 0x05C1..0x05C2,
97
+ 0x05C4..0x05C5, 0x05C7, 0x0610..0x061A,
98
+ 0x064B..0x065F, 0x0670, 0x06D6..0x06DC,
99
+ 0x06DF..0x06E4, 0x06E7..0x06E8,
100
+ 0x06EA..0x06ED, 0x0711, 0x0730..0x074A then :NSM
101
+ when 0x0041..0x005A, 0x0061..0x007A,
102
+ 0x00AA, 0x00B5, 0x00BA,
103
+ 0x00C0..0x00D6, 0x00D8..0x00F6,
104
+ 0x00F8..0x02B8, 0x02BB..0x02C1,
105
+ 0x1D00..0x1D25, 0x1D2C..0x1D5C,
106
+ 0x1D62..0x1D65, 0x1D6B..0x1D77,
107
+ 0x1D79..0x1DBE, 0x1E00..0x1EFF,
108
+ 0x2071, 0x207F, 0x2090..0x209C,
109
+ 0x212A..0x212B, 0x2132, 0x214E,
110
+ 0x2160..0x2188, 0x2C60..0x2C7F,
111
+ 0xA722..0xA787, 0xA78B..0xA78E,
112
+ 0xA790..0xA793, 0xA7A0..0xA7AA,
113
+ 0xA7F8..0xA7FF, 0xFB00..0xFB06,
114
+ 0xFB13..0xFB17 then :L
115
+ when 0x0590..0x05FF, 0x07C0..0x089F,
116
+ 0xFB1D..0xFB4F then :R
117
+ when 0x0600..0x07BF, 0x08A0..0x08FF,
118
+ 0xFB50..0xFDFF, 0xFE70..0xFEFF then :AL
119
+ when 0x0030..0x0039, 0x06DD then :EN
120
+ when 0x002B, 0x002D then :ES
121
+ when 0x0023, 0x0024, 0x0025, 0x00A2..0x00A5 then :ET
122
+ when 0x002C, 0x002E, 0x003A, 0x003B,
123
+ 0x0028, 0x0029,
124
+ 0x0000..0x0008, 0x000E..0x001B,
125
+ 0x007F..0x0084, 0x0086..0x009F,
126
+ 0x200B..0x200D, 0x2060, 0xFEFF then :ON
127
+ when 0x0020, 0x00A0, 0x1680, 0x2000..0x200A,
128
+ 0x2028..0x2029, 0x202F, 0x205F, 0x3000 then :WS
129
+ else :BN
130
+ end
131
+ end
132
+
133
+ # ---- Level resolution ----
134
+
135
+ # Compute embedding level per grapheme cluster using the implicit
136
+ # level resolution rules I1/I2 from UAX #9. NSM inherits the
137
+ # level of the preceding character (simplified: assumes the
138
+ # paragraph base level for the first character).
139
+ def levels_for(clusters, base_level)
140
+ prev = base_level
141
+ clusters.map do |cluster|
142
+ cp = cluster.codepoints.first || 0
143
+ level = implicit_level(bidi_type(cp), base_level, prev)
144
+ prev = level
145
+ level
146
+ end
147
+ end
148
+
149
+ # I1/I2 from UAX #9. Even base levels add 1 for R/AL/AN, 2 for
150
+ # EN. Odd base levels add 1 for L/EN/AN.
151
+ def implicit_level(type, base_level, prev_level)
152
+ case type
153
+ when :NSM then prev_level
154
+ when :L then base_level.even? ? base_level : base_level + 1
155
+ when :R, :AL then base_level.even? ? base_level + 1 : base_level
156
+ when :AN then base_level + 1
157
+ when :EN then base_level + (base_level.even? ? 2 : 1)
158
+ else base_level
159
+ end
160
+ end
161
+
162
+ # UAX #9 L2: reverse contiguous runs at each level, from highest
163
+ # to lowest. Returns a new array of clusters in visual order.
164
+ def reverse_by_levels(clusters, levels)
165
+ result = clusters.dup
166
+ max_level = levels.max || 0
167
+ min_odd = levels.min.to_i
168
+ min_odd += 1 if min_odd.even?
169
+ (min_odd..max_level).to_a.reverse.each do |level|
170
+ start = nil
171
+ levels.each_with_index do |lvl, i|
172
+ if lvl >= level
173
+ start ||= i
174
+ elsif start
175
+ result[start, i - start] = result[start, i - start].reverse
176
+ start = nil
177
+ end
178
+ end
179
+ next unless start
180
+
181
+ result[start, levels.size - start] = result[start, levels.size - start].reverse
182
+ end
183
+ result
184
+ end
185
+
186
+ # UAX #9 L4: mirror paired brackets on RTL runs. Returns a
187
+ # string built from the visual-order cluster list with mirror
188
+ # glyphs substituted where applicable.
189
+ def mirror_runs(clusters, levels)
190
+ clusters.each_with_index.map do |cluster, i|
191
+ next cluster if levels[i].nil? || levels[i].even?
192
+
193
+ MIRROR[cluster] || cluster
194
+ end.join
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Layout
5
+ # Base class for all layout boxes. A Box has a width, height, and
6
+ # content; it knows how to fit itself into a Frame region and how
7
+ # to draw onto a Canvas at a given (x, y) location.
8
+ #
9
+ # Subclasses override +fit+ (compute height for given width) and
10
+ # +draw_content+ (render to canvas).
11
+ class Box
12
+ attr_reader :style, :width, :height
13
+
14
+ def initialize(width: 0, height: 0, style: Style.new(:base), **)
15
+ @width = width.to_f
16
+ @height = height.to_f
17
+ @style = style
18
+ end
19
+
20
+ # Fit this box into the given available (width, height) region.
21
+ # Returns true if it fits (sets @height); false otherwise.
22
+ # Subclasses override.
23
+ def fit?(_available_width, _available_height)
24
+ true
25
+ end
26
+
27
+ # Draw this box at (x, y) on +canvas+. Subclasses override
28
+ # +draw_content+.
29
+ def draw(canvas, x, y)
30
+ draw_background(canvas, x, y)
31
+ draw_content(canvas, x, y)
32
+ end
33
+
34
+ def supports_position_flow?
35
+ false
36
+ end
37
+
38
+ def empty?
39
+ false
40
+ end
41
+
42
+ protected
43
+
44
+ def draw_background(canvas, x, y); end
45
+
46
+ def draw_content(canvas, x, y); end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Layout
5
+ # Fits an ordered list of boxes into a Frame, flowing to a new
6
+ # Frame (provided by a block) when one fills up.
7
+ class BoxFitter
8
+ attr_reader :boxes, :frame, :result, :overflow
9
+
10
+ def initialize(boxes:, frame:)
11
+ @boxes = boxes
12
+ @frame = frame
13
+ @result = []
14
+ @overflow = []
15
+ end
16
+
17
+ def fit
18
+ current_frame = @frame
19
+ cursor_y = current_frame.top
20
+ @boxes.each do |box|
21
+ loop do
22
+ position = current_frame.find_available_area(box.width || current_frame.width, box.height || (cursor_y - current_frame.bottom))
23
+ if position && box.fit?(position[2] || current_frame.width, position[3] || (cursor_y - current_frame.bottom))
24
+ @result << [box, position]
25
+ current_frame.remove_area(position[0], position[1] - box.height, box.width, box.height)
26
+ cursor_y = position[1] - box.height
27
+ break
28
+ else
29
+ next_frame = yield if block_given?
30
+ if next_frame
31
+ current_frame = next_frame
32
+ cursor_y = current_frame.top
33
+ else
34
+ @overflow << box
35
+ break
36
+ end
37
+ end
38
+ end
39
+ end
40
+ self
41
+ end
42
+
43
+ def draw(canvas)
44
+ @result.each do |box, pos|
45
+ box.draw(canvas, pos[0], pos[1])
46
+ end
47
+ end
48
+
49
+ def fit_complete?
50
+ @overflow.empty?
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Layout
5
+ # Lays out children into N columns of equal width separated by
6
+ # gutters. Children flow top-to-bottom in column 1, then wrap to
7
+ # column 2 when column 1 fills, etc.
8
+ class ColumnBox < Box
9
+ attr_reader :children, :columns, :gutter
10
+
11
+ def initialize(children:, columns: 2, gutter: 20, **)
12
+ super(**)
13
+ @children = children
14
+ @columns = columns.to_i
15
+ @gutter = gutter.to_f
16
+ end
17
+
18
+ def fit?(available_width, available_height)
19
+ column_w = (available_width - (@gutter * (@columns - 1))) / @columns
20
+ @child_heights = []
21
+ remaining_children = @children.dup
22
+ col_heights = Array.new(@columns, 0.0)
23
+
24
+ @columns.times do |col_idx|
25
+ col_remaining = available_height
26
+ while remaining_children.any?
27
+ child = remaining_children.first
28
+ if child.fit?(column_w, col_remaining)
29
+ col_heights[col_idx] += child.height
30
+ col_remaining -= child.height
31
+ remaining_children.shift
32
+ else
33
+ break
34
+ end
35
+ end
36
+ end
37
+ @column_width = column_w
38
+ @column_heights = col_heights
39
+ @width = available_width
40
+ @height = available_height
41
+ remaining_children.empty?
42
+ end
43
+
44
+ def draw_content(canvas, x, y)
45
+ idx = 0
46
+ @column_heights.each_with_index do |_h, col_idx|
47
+ col_x = x + (col_idx * (@column_width + @gutter))
48
+ col_y = y
49
+ while idx < @children.size
50
+ child = @children[idx]
51
+ break if child_height_in_column(idx, col_idx).nil?
52
+
53
+ child.draw(canvas, col_x, col_y)
54
+ col_y -= child.height
55
+ idx += 1
56
+ end
57
+ end
58
+ end
59
+
60
+ def empty?
61
+ @children.empty?
62
+ end
63
+
64
+ private
65
+
66
+ def child_height_in_column(child_idx, col_idx)
67
+ @column_heights[col_idx] if child_idx < @column_heights.size
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Layout
5
+ # A box that holds an ordered list of child boxes laid out
6
+ # vertically. Each child is fit in turn; if a child doesn't fit
7
+ # in the remaining height, +fit+ returns false and the fitter
8
+ # moves to a new Frame.
9
+ class ContainerBox < Box
10
+ attr_reader :children
11
+
12
+ def initialize(children: [], **)
13
+ super(**)
14
+ @children = children
15
+ end
16
+
17
+ def fit?(available_width, available_height)
18
+ remaining_height = available_height
19
+ @children.each do |child|
20
+ if child.fit?(available_width, remaining_height)
21
+ remaining_height -= child.height
22
+ else
23
+ return false
24
+ end
25
+ end
26
+ @width = available_width
27
+ @height = available_height - remaining_height
28
+ true
29
+ end
30
+
31
+ def draw(canvas, x, y)
32
+ super
33
+ offset_y = y
34
+ @children.each do |child|
35
+ child.draw(canvas, x, offset_y)
36
+ offset_y -= child.height
37
+ end
38
+ end
39
+
40
+ def empty?
41
+ @children.empty?
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Layout
5
+ # A rectangular region of a page where boxes are placed. The Frame
6
+ # tracks which areas are filled and exposes +find_available_area+
7
+ # to locate the next free region of a given width and height.
8
+ #
9
+ # Simple case: the Frame is a plain rectangle. Advanced: the
10
+ # +shape+ polygon allows non-rectangular regions (L-shaped, with
11
+ # cutouts around images).
12
+ class Frame
13
+ attr_reader :left, :bottom, :width, :height, :shape
14
+
15
+ def initialize(left:, bottom:, width:, height:, shape: nil)
16
+ @left = left.to_f
17
+ @bottom = bottom.to_f
18
+ @width = width.to_f
19
+ @height = height.to_f
20
+ @shape = shape || default_shape
21
+ end
22
+
23
+ def right
24
+ @left + @width
25
+ end
26
+
27
+ def top
28
+ @bottom + @height
29
+ end
30
+
31
+ # Locate the next free area of (width × height) within this Frame.
32
+ # Returns nil if none fits. After a successful find, callers
33
+ # typically call +remove_area+ with the returned rectangle to
34
+ # mark it as used.
35
+ def find_available_area(width, height)
36
+ w = width.to_f
37
+ h = height.to_f
38
+ return nil if w > @width || h > @height
39
+
40
+ [@left, @bottom + @height - h]
41
+ end
42
+
43
+ # Mark the rectangle at (x, y) of (w × h) as filled. Subsequent
44
+ # +find_available_area+ calls avoid this region.
45
+ def remove_area(x, y, w, h)
46
+ return if x.nil? || y.nil?
47
+
48
+ @removed ||= []
49
+ @removed << [x.to_f, y.to_f, w.to_f, h.to_f]
50
+ end
51
+
52
+ def full?
53
+ @removed&.any?
54
+ end
55
+
56
+ def empty?
57
+ !full?
58
+ end
59
+
60
+ private
61
+
62
+ def default_shape
63
+ [[@left, @bottom], [right, @bottom], [right, top], [@left, top]]
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Layout
5
+ # Renders an image (PNG or JPEG) into the layout flow. The image
6
+ # is loaded via Pdfrb::ImageLoader and registered with the
7
+ # document's Resources on first draw.
8
+ class ImageBox < Box
9
+ attr_reader :path, :image_data
10
+
11
+ def initialize(path:, document:, width: 0, height: 0, **)
12
+ super(width: width, height: height, **)
13
+ @path = path
14
+ @document = document
15
+ @image_data = File.binread(path)
16
+ end
17
+
18
+ def fit?(available_width, available_height)
19
+ loader = Pdfrb::ImageLoader.load(@document, @image_data)
20
+ native_w = loader[:Width].to_f
21
+ native_h = loader[:Height].to_f
22
+ target_w = @width.positive? ? @width : native_w
23
+ target_h = @height.positive? ? @height : native_h
24
+
25
+ if @width.zero? && @height.zero?
26
+ scale = [available_width / native_w, available_height / native_h].min
27
+ scale = 1.0 if scale > 1
28
+ target_w = native_w * scale
29
+ target_h = native_h * scale
30
+ end
31
+
32
+ @width = target_w
33
+ @height = target_h
34
+ @loaded = loader
35
+ @width <= available_width && @height <= available_height
36
+ end
37
+
38
+ def draw_content(canvas, x, y)
39
+ name = canvas.document.images.add(@path)
40
+ canvas.draw_image(name, at: [x, y], width: @width, height: @height)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Layout
5
+ # A box that sits inline within a text line (e.g., a styled span
6
+ # or an inline icon). Width + height are fixed; Line arranges it
7
+ # alongside TextFragments.
8
+ class InlineBox < Box
9
+ attr_reader :block
10
+
11
+ def initialize(width:, height:, &block)
12
+ super(width: width, height: height)
13
+ @block = block
14
+ end
15
+
16
+ def supports_position_flow?
17
+ true
18
+ end
19
+
20
+ def draw_content(canvas, x, y)
21
+ @block&.call(canvas, x, y)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Layout
5
+ # A single line of text — an ordered list of TextFragment instances
6
+ # sharing a baseline. Knows its total width/height/ascent/descent.
7
+ class Line
8
+ attr_reader :fragments
9
+
10
+ def initialize(fragments: [])
11
+ @fragments = fragments
12
+ end
13
+
14
+ def width
15
+ @fragments.sum(&:width)
16
+ end
17
+
18
+ def height
19
+ return 0 if @fragments.empty?
20
+
21
+ max_top = @fragments.map(&:y_max).max
22
+ min_bottom = @fragments.map(&:y_min).min
23
+ max_top - min_bottom
24
+ end
25
+
26
+ def ascender
27
+ @fragments.map(&:y_max).max || 0
28
+ end
29
+
30
+ def descender
31
+ @fragments.map(&:y_min).min || 0
32
+ end
33
+
34
+ def empty?
35
+ @fragments.empty?
36
+ end
37
+
38
+ # Draw the line at baseline (x, y) applying the requested alignment
39
+ # within the available width.
40
+ def draw(canvas, x, y, available_width:, alignment: :left)
41
+ offset = if alignment == :center
42
+ x + ((available_width - width) / 2)
43
+ elsif alignment == :right
44
+ x + available_width - width
45
+ else
46
+ x
47
+ end
48
+ @fragments.each do |frag|
49
+ frag.draw(canvas, offset, y)
50
+ offset += frag.width
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Layout
5
+ # Roman numeral converter. Extracted as a standalone helper so
6
+ # +ListBox+ and +PageLabel+ (model layer) can share it.
7
+ class RomanNumeral
8
+ MAPPINGS = [[1000, "M"], [900, "CM"], [500, "D"], [400, "CD"],
9
+ [100, "C"], [90, "XC"], [50, "L"], [40, "XL"],
10
+ [10, "X"], [9, "IX"], [5, "V"], [4, "IV"], [1, "I"]].freeze
11
+
12
+ def self.convert(n)
13
+ return "0" if n <= 0
14
+
15
+ result = +""
16
+ MAPPINGS.each do |value, symbol|
17
+ while n >= value
18
+ result << symbol
19
+ n -= value
20
+ end
21
+ end
22
+ result
23
+ end
24
+ end
25
+
26
+ # A list of items, each rendered with a marker (bullet, decimal,
27
+ # alpha, roman, etc.). Children are boxes (typically TextBox).
28
+ class ListBox < Box
29
+ attr_reader :items, :marker_type
30
+
31
+ MARKERS = {
32
+ bullet: "•",
33
+ disc: "•",
34
+ circle: "○",
35
+ square: "▪",
36
+ decimal: ->(i) { "#{i + 1}." },
37
+ lower_alpha: ->(i) { "#{('a'.ord + i).chr}." },
38
+ upper_alpha: ->(i) { "#{('A'.ord + i).chr}." },
39
+ lower_roman: ->(i) { "#{RomanNumeral.convert(i + 1).downcase}." },
40
+ upper_roman: ->(i) { "#{RomanNumeral.convert(i + 1)}." },
41
+ }.freeze
42
+
43
+ def initialize(items:, marker_type: :bullet, **)
44
+ super(**)
45
+ @items = items
46
+ @marker_type = marker_type
47
+ end
48
+
49
+ def fit?(available_width, available_height)
50
+ marker_w = 20.0
51
+ content_w = available_width - marker_w
52
+ remaining = available_height
53
+ @items.each do |item|
54
+ return false unless item.fit?(content_w, remaining)
55
+
56
+ remaining -= item.height
57
+ end
58
+ @width = available_width
59
+ @height = available_height - remaining
60
+ true
61
+ end
62
+
63
+ def draw_content(canvas, x, y)
64
+ marker_w = 20.0
65
+ offset_y = y
66
+ @items.each_with_index do |item, i|
67
+ marker_text(canvas, x, offset_y, i)
68
+ item.draw(canvas, x + marker_w, offset_y)
69
+ offset_y -= item.height
70
+ end
71
+ end
72
+
73
+ def empty?
74
+ @items.empty?
75
+ end
76
+
77
+ private
78
+
79
+ def marker_text(canvas, x, y, index)
80
+ marker = MARKERS[@marker_type] || MARKERS[:bullet]
81
+ text = marker.is_a?(Proc) ? marker.call(index) : marker
82
+ canvas.text(text, at: [x, y], font: :Helvetica,
83
+ size: @style.font_size || 10)
84
+ end
85
+ end
86
+ end
87
+ end