pdfrb 0.3.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (422) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +51 -0
  4. data/Gemfile +1 -0
  5. data/PROPOSAL.canvas-clip.md +92 -0
  6. data/PROPOSAL.canvas-draw-image-matrix.md +87 -0
  7. data/PROPOSAL.canvas-rich-text.md +103 -0
  8. data/PROPOSAL.canvas-text-lines.md +77 -0
  9. data/PROPOSAL.canvas-transparency.md +106 -0
  10. data/PROPOSAL.external-font-metrics.md +129 -0
  11. data/PROPOSAL.font-embedding-fontfile2.md +88 -0
  12. data/PROPOSAL.font-resolver-ttc.md +105 -0
  13. data/PROPOSAL.font-resolver.md +96 -0
  14. data/PROPOSAL.font-subsetting.md +98 -0
  15. data/PROPOSAL.gradient-shadings.md +110 -0
  16. data/PROPOSAL.page-mediabox-setter.md +66 -0
  17. data/PROPOSAL.per-glyph-width.md +91 -0
  18. data/PROPOSAL.stringio-require.md +53 -0
  19. data/PROPOSAL.struct-tree-root.md +102 -0
  20. data/PROPOSAL.text-measurement.md +93 -0
  21. data/PROPOSAL.ttf-table-parsing-fix.md +112 -0
  22. data/RELEASE.md +61 -0
  23. data/data/pdfrb/afm/Courier-Bold.afm +342 -342
  24. data/data/pdfrb/afm/Courier-BoldOblique.afm +342 -342
  25. data/data/pdfrb/afm/Courier-Oblique.afm +342 -342
  26. data/data/pdfrb/afm/Courier.afm +342 -342
  27. data/data/pdfrb/afm/Helvetica-Bold.afm +2827 -2827
  28. data/data/pdfrb/afm/Helvetica-BoldOblique.afm +2827 -2827
  29. data/data/pdfrb/afm/Helvetica-Oblique.afm +3051 -3051
  30. data/data/pdfrb/afm/Helvetica.afm +3051 -3051
  31. data/data/pdfrb/afm/Symbol.afm +213 -213
  32. data/data/pdfrb/afm/Times-Bold.afm +2588 -2588
  33. data/data/pdfrb/afm/Times-BoldItalic.afm +2384 -2384
  34. data/data/pdfrb/afm/Times-Italic.afm +2667 -2667
  35. data/data/pdfrb/afm/Times-Roman.afm +2419 -2419
  36. data/data/pdfrb/afm/ZapfDingbats.afm +225 -225
  37. data/lib/pdfrb/action/base.rb +21 -0
  38. data/lib/pdfrb/action/types.rb +178 -0
  39. data/lib/pdfrb/action.rb +55 -0
  40. data/lib/pdfrb/annotation/base.rb +79 -0
  41. data/lib/pdfrb/annotation/types.rb +222 -0
  42. data/lib/pdfrb/annotation.rb +78 -0
  43. data/lib/pdfrb/appearance/generator.rb +216 -0
  44. data/lib/pdfrb/appearance.rb +11 -0
  45. data/lib/pdfrb/arlington/object_definition.rb +2 -2
  46. data/lib/pdfrb/arlington/predicate/evaluator.rb +1 -1
  47. data/lib/pdfrb/arlington/predicate/functions.rb +25 -9
  48. data/lib/pdfrb/arlington/predicate.rb +1 -5
  49. data/lib/pdfrb/cli/batch.rb +18 -0
  50. data/lib/pdfrb/cli/debug_info.rb +18 -0
  51. data/lib/pdfrb/cli/files.rb +16 -0
  52. data/lib/pdfrb/cli/fonts.rb +16 -0
  53. data/lib/pdfrb/cli/form.rb +18 -0
  54. data/lib/pdfrb/cli/image2pdf.rb +18 -0
  55. data/lib/pdfrb/cli/images.rb +34 -0
  56. data/lib/pdfrb/cli/info.rb +20 -0
  57. data/lib/pdfrb/cli/inspect.rb +18 -0
  58. data/lib/pdfrb/cli/merge.rb +18 -0
  59. data/lib/pdfrb/cli/modify.rb +18 -0
  60. data/lib/pdfrb/cli/optimize.rb +18 -0
  61. data/lib/pdfrb/cli/split.rb +21 -0
  62. data/lib/pdfrb/cli/watermark.rb +18 -0
  63. data/lib/pdfrb/cli.rb +291 -7
  64. data/lib/pdfrb/color/color_space.rb +259 -0
  65. data/lib/pdfrb/color/icc_profile.rb +90 -0
  66. data/lib/pdfrb/color.rb +12 -0
  67. data/lib/pdfrb/compare/comparator.rb +244 -0
  68. data/lib/pdfrb/compare/report.rb +78 -0
  69. data/lib/pdfrb/compare.rb +42 -0
  70. data/lib/pdfrb/composer.rb +161 -0
  71. data/lib/pdfrb/conformance/pdf_a.rb +317 -56
  72. data/lib/pdfrb/conformance/pdf_ua.rb +186 -29
  73. data/lib/pdfrb/conformance/pdf_x.rb +247 -0
  74. data/lib/pdfrb/conformance/rule.rb +78 -0
  75. data/lib/pdfrb/conformance/structure_elements.rb +174 -0
  76. data/lib/pdfrb/conformance/verapdf_bridge.rb +92 -0
  77. data/lib/pdfrb/conformance.rb +7 -3
  78. data/lib/pdfrb/content/canvas.rb +354 -72
  79. data/lib/pdfrb/content/color_space.rb +157 -0
  80. data/lib/pdfrb/content/hidden_text_detector.rb +167 -0
  81. data/lib/pdfrb/content/operator.rb +21 -1
  82. data/lib/pdfrb/content/operators/clipping.rb +36 -0
  83. data/lib/pdfrb/content/operators/graphics_state_params.rb +15 -0
  84. data/lib/pdfrb/content/operators/inline_image.rb +53 -0
  85. data/lib/pdfrb/content/operators.rb +22 -0
  86. data/lib/pdfrb/content/shading/base.rb +139 -0
  87. data/lib/pdfrb/content/shading.rb +17 -0
  88. data/lib/pdfrb/content/smart_text_extractor.rb +85 -0
  89. data/lib/pdfrb/content/tiling_pattern.rb +78 -0
  90. data/lib/pdfrb/content/transformation_matrix.rb +61 -0
  91. data/lib/pdfrb/content.rb +7 -26
  92. data/lib/pdfrb/destination/fit.rb +185 -0
  93. data/lib/pdfrb/destination.rb +13 -0
  94. data/lib/pdfrb/digital_signature/cms_handler.rb +22 -0
  95. data/lib/pdfrb/digital_signature/signing.rb +107 -11
  96. data/lib/pdfrb/digital_signature/timestamp_handler.rb +45 -0
  97. data/lib/pdfrb/digital_signature/verification.rb +88 -9
  98. data/lib/pdfrb/digital_signature/verification_result.rb +14 -0
  99. data/lib/pdfrb/digital_signature.rb +26 -2
  100. data/lib/pdfrb/document/annotations.rb +120 -3
  101. data/lib/pdfrb/document/associated_files.rb +104 -0
  102. data/lib/pdfrb/document/colors.rb +95 -0
  103. data/lib/pdfrb/document/destinations.rb +127 -5
  104. data/lib/pdfrb/document/display.rb +84 -0
  105. data/lib/pdfrb/document/fonts.rb +417 -45
  106. data/lib/pdfrb/document/form.rb +338 -0
  107. data/lib/pdfrb/document/graphics_state.rb +91 -0
  108. data/lib/pdfrb/document/images.rb +4 -4
  109. data/lib/pdfrb/document/info.rb +102 -0
  110. data/lib/pdfrb/document/layers.rb +62 -0
  111. data/lib/pdfrb/document/metadata.rb +12 -5
  112. data/lib/pdfrb/document/output_intents.rb +91 -0
  113. data/lib/pdfrb/document/page_labels.rb +80 -0
  114. data/lib/pdfrb/document/pages.rb +68 -19
  115. data/lib/pdfrb/document/portfolio.rb +119 -0
  116. data/lib/pdfrb/document/shadings.rb +159 -0
  117. data/lib/pdfrb/document/stamps.rb +68 -0
  118. data/lib/pdfrb/document/structure.rb +242 -0
  119. data/lib/pdfrb/document.rb +73 -4
  120. data/lib/pdfrb/encryption/identity.rb +13 -0
  121. data/lib/pdfrb/encryption/password_verification.rb +53 -8
  122. data/lib/pdfrb/encryption/security_handler.rb +8 -5
  123. data/lib/pdfrb/encryption/standard_security_handler.rb +154 -81
  124. data/lib/pdfrb/encryption.rb +17 -0
  125. data/lib/pdfrb/error.rb +2 -2
  126. data/lib/pdfrb/filter/brotli_decode.rb +32 -0
  127. data/lib/pdfrb/filter/ccitt_fax_decode.rb +12 -0
  128. data/lib/pdfrb/filter/crypt.rb +2 -2
  129. data/lib/pdfrb/filter/dct_decode.rb +18 -0
  130. data/lib/pdfrb/filter/flate_decode.rb +4 -18
  131. data/lib/pdfrb/filter/jbig2_decode.rb +12 -0
  132. data/lib/pdfrb/filter/jpx_decode.rb +18 -0
  133. data/lib/pdfrb/filter/lzw_decode.rb +3 -2
  134. data/lib/pdfrb/filter/png_predictor.rb +94 -0
  135. data/lib/pdfrb/filter.rb +26 -13
  136. data/lib/pdfrb/font/afm_parser.rb +50 -26
  137. data/lib/pdfrb/font/cmap/writer.rb +9 -0
  138. data/lib/pdfrb/font/encoding/win_ansi_encoding.rb +17 -0
  139. data/lib/pdfrb/font/encoding.rb +32 -3
  140. data/lib/pdfrb/font/metrics_helper.rb +121 -0
  141. data/lib/pdfrb/font/true_type/file.rb +10 -1
  142. data/lib/pdfrb/font/true_type/glyf.rb +82 -0
  143. data/lib/pdfrb/font/true_type/kern.rb +58 -0
  144. data/lib/pdfrb/font/true_type/loca.rb +42 -0
  145. data/lib/pdfrb/font/true_type/maxp.rb +44 -0
  146. data/lib/pdfrb/font/true_type/name.rb +53 -0
  147. data/lib/pdfrb/font/true_type/post.rb +93 -0
  148. data/lib/pdfrb/font/true_type/subsetter.rb +3 -5
  149. data/lib/pdfrb/font/true_type/wrapper.rb +102 -0
  150. data/lib/pdfrb/font/true_type.rb +7 -0
  151. data/lib/pdfrb/font.rb +2 -0
  152. data/lib/pdfrb/font_loader/from_configuration.rb +19 -0
  153. data/lib/pdfrb/font_loader/from_file.rb +20 -0
  154. data/lib/pdfrb/font_loader/standard14.rb +20 -0
  155. data/lib/pdfrb/font_loader/variant_from_name.rb +36 -0
  156. data/lib/pdfrb/font_loader.rb +4 -0
  157. data/lib/pdfrb/font_resolver.rb +138 -0
  158. data/lib/pdfrb/image/audit.rb +94 -0
  159. data/lib/pdfrb/image/downsampler.rb +124 -0
  160. data/lib/pdfrb/image.rb +13 -0
  161. data/lib/pdfrb/image_loader/jpeg.rb +39 -58
  162. data/lib/pdfrb/image_loader/png.rb +82 -251
  163. data/lib/pdfrb/layout/bidi.rb +198 -0
  164. data/lib/pdfrb/layout/box.rb +49 -0
  165. data/lib/pdfrb/layout/box_fitter.rb +54 -0
  166. data/lib/pdfrb/layout/column_box.rb +71 -0
  167. data/lib/pdfrb/layout/container_box.rb +45 -0
  168. data/lib/pdfrb/layout/frame.rb +67 -0
  169. data/lib/pdfrb/layout/image_box.rb +44 -0
  170. data/lib/pdfrb/layout/inline_box.rb +25 -0
  171. data/lib/pdfrb/layout/line.rb +55 -0
  172. data/lib/pdfrb/layout/list_box.rb +87 -0
  173. data/lib/pdfrb/layout/numeric_refinements.rb +43 -0
  174. data/lib/pdfrb/layout/page_style.rb +65 -0
  175. data/lib/pdfrb/layout/style.rb +62 -0
  176. data/lib/pdfrb/layout/table_box.rb +66 -0
  177. data/lib/pdfrb/layout/text_box.rb +38 -0
  178. data/lib/pdfrb/layout/text_fragment.rb +45 -0
  179. data/lib/pdfrb/layout/text_layouter.rb +123 -0
  180. data/lib/pdfrb/layout.rb +28 -0
  181. data/lib/pdfrb/linearization/hint_stream.rb +93 -0
  182. data/lib/pdfrb/linearization/writer.rb +259 -0
  183. data/lib/pdfrb/linearization.rb +21 -0
  184. data/lib/pdfrb/model/cos/dictionary.rb +12 -0
  185. data/lib/pdfrb/model/pdf_array.rb +2 -2
  186. data/lib/pdfrb/model/type/action.rb +42 -0
  187. data/lib/pdfrb/model/type/action_go_to_3d_view.rb +14 -0
  188. data/lib/pdfrb/model/type/action_go_to_dp.rb +16 -0
  189. data/lib/pdfrb/model/type/action_go_to_r.rb +26 -0
  190. data/lib/pdfrb/model/type/action_goto.rb +34 -0
  191. data/lib/pdfrb/model/type/action_hide.rb +26 -0
  192. data/lib/pdfrb/model/type/action_import_data.rb +18 -0
  193. data/lib/pdfrb/model/type/action_java_script.rb +18 -0
  194. data/lib/pdfrb/model/type/action_launch.rb +22 -0
  195. data/lib/pdfrb/model/type/action_movie.rb +22 -0
  196. data/lib/pdfrb/model/type/action_named.rb +23 -0
  197. data/lib/pdfrb/model/type/action_nop.rb +12 -0
  198. data/lib/pdfrb/model/type/action_rendition.rb +28 -0
  199. data/lib/pdfrb/model/type/action_reset_form.rb +22 -0
  200. data/lib/pdfrb/model/type/action_rich_media_execute.rb +17 -0
  201. data/lib/pdfrb/model/type/action_set_ocg_state.rb +32 -0
  202. data/lib/pdfrb/model/type/action_set_state.rb +14 -0
  203. data/lib/pdfrb/model/type/action_sound_action.rb +23 -0
  204. data/lib/pdfrb/model/type/action_submit_form.rb +64 -0
  205. data/lib/pdfrb/model/type/action_thread.rb +23 -0
  206. data/lib/pdfrb/model/type/action_trans.rb +30 -0
  207. data/lib/pdfrb/model/type/action_uri.rb +30 -0
  208. data/lib/pdfrb/model/type/af_embedded_file.rb +11 -0
  209. data/lib/pdfrb/model/type/af_file_specification.rb +34 -0
  210. data/lib/pdfrb/model/type/annotation.rb +49 -10
  211. data/lib/pdfrb/model/type/appearance.rb +41 -0
  212. data/lib/pdfrb/model/type/appearance_characteristics.rb +36 -0
  213. data/lib/pdfrb/model/type/appearance_generator.rb +22 -0
  214. data/lib/pdfrb/model/type/appearance_trap_net.rb +84 -0
  215. data/lib/pdfrb/model/type/border_effect.rb +25 -0
  216. data/lib/pdfrb/model/type/border_styling.rb +38 -0
  217. data/lib/pdfrb/model/type/box_color_info.rb +21 -0
  218. data/lib/pdfrb/model/type/box_style.rb +19 -0
  219. data/lib/pdfrb/model/type/button.rb +17 -0
  220. data/lib/pdfrb/model/type/cal_gray.rb +17 -0
  221. data/lib/pdfrb/model/type/cal_rgb.rb +22 -0
  222. data/lib/pdfrb/model/type/caret_annotation.rb +21 -0
  223. data/lib/pdfrb/model/type/catalog.rb +99 -16
  224. data/lib/pdfrb/model/type/choice.rb +21 -0
  225. data/lib/pdfrb/model/type/cid_font.rb +52 -1
  226. data/lib/pdfrb/model/type/cid_font_descriptor_metrics.rb +28 -0
  227. data/lib/pdfrb/model/type/cid_system_info.rb +28 -0
  228. data/lib/pdfrb/model/type/circle_annotation.rb +20 -0
  229. data/lib/pdfrb/model/type/collection.rb +27 -0
  230. data/lib/pdfrb/model/type/collection_field.rb +24 -0
  231. data/lib/pdfrb/model/type/collection_item.rb +19 -0
  232. data/lib/pdfrb/model/type/collection_schema.rb +25 -0
  233. data/lib/pdfrb/model/type/collection_sort.rb +13 -0
  234. data/lib/pdfrb/model/type/crypt_filter.rb +58 -0
  235. data/lib/pdfrb/model/type/device_n.rb +37 -0
  236. data/lib/pdfrb/model/type/device_n_mixing_hints.rb +17 -0
  237. data/lib/pdfrb/model/type/device_n_process.rb +13 -0
  238. data/lib/pdfrb/model/type/doc_mdp_transform_parameters.rb +21 -0
  239. data/lib/pdfrb/model/type/document_security_store.rb +55 -0
  240. data/lib/pdfrb/model/type/embedded_file.rb +19 -0
  241. data/lib/pdfrb/model/type/embedded_file_parameter.rb +48 -0
  242. data/lib/pdfrb/model/type/encryption_public_key.rb +38 -0
  243. data/lib/pdfrb/model/type/encryption_standard.rb +68 -0
  244. data/lib/pdfrb/model/type/field_mdp_transform_parameters.rb +26 -0
  245. data/lib/pdfrb/model/type/file_attachment_annotation.rb +29 -0
  246. data/lib/pdfrb/model/type/file_spec_ef.rb +22 -0
  247. data/lib/pdfrb/model/type/file_spec_rf.rb +21 -0
  248. data/lib/pdfrb/model/type/file_specification.rb +43 -0
  249. data/lib/pdfrb/model/type/file_trailer.rb +54 -0
  250. data/lib/pdfrb/model/type/font.rb +73 -2
  251. data/lib/pdfrb/model/type/font_cid_type0.rb +17 -0
  252. data/lib/pdfrb/model/type/font_cid_type2.rb +17 -0
  253. data/lib/pdfrb/model/type/font_descriptor.rb +47 -0
  254. data/lib/pdfrb/model/type/font_descriptor_cid_type0.rb +15 -0
  255. data/lib/pdfrb/model/type/font_descriptor_cid_type2.rb +15 -0
  256. data/lib/pdfrb/model/type/font_encoding.rb +47 -0
  257. data/lib/pdfrb/model/type/font_file.rb +28 -0
  258. data/lib/pdfrb/model/type/font_file2.rb +32 -0
  259. data/lib/pdfrb/model/type/font_file3.rb +23 -0
  260. data/lib/pdfrb/model/type/font_multiple_master.rb +60 -0
  261. data/lib/pdfrb/model/type/font_true_type.rb +1 -1
  262. data/lib/pdfrb/model/type/font_type0.rb +1 -1
  263. data/lib/pdfrb/model/type/font_type1.rb +1 -1
  264. data/lib/pdfrb/model/type/font_type3.rb +1 -1
  265. data/lib/pdfrb/model/type/form_field.rb +73 -0
  266. data/lib/pdfrb/model/type/free_text_annotation.rb +37 -0
  267. data/lib/pdfrb/model/type/function.rb +42 -0
  268. data/lib/pdfrb/model/type/function_exponential.rb +31 -0
  269. data/lib/pdfrb/model/type/function_postscript.rb +25 -0
  270. data/lib/pdfrb/model/type/function_sampled.rb +55 -0
  271. data/lib/pdfrb/model/type/function_stitching.rb +33 -0
  272. data/lib/pdfrb/model/type/generic_appearance.rb +40 -0
  273. data/lib/pdfrb/model/type/graphics_state_parameter.rb +69 -0
  274. data/lib/pdfrb/model/type/halftone.rb +21 -0
  275. data/lib/pdfrb/model/type/halftone_type1.rb +17 -0
  276. data/lib/pdfrb/model/type/halftone_type10.rb +16 -0
  277. data/lib/pdfrb/model/type/halftone_type16.rb +11 -0
  278. data/lib/pdfrb/model/type/halftone_type5.rb +24 -0
  279. data/lib/pdfrb/model/type/halftone_type6.rb +16 -0
  280. data/lib/pdfrb/model/type/highlight_annotation.rb +11 -0
  281. data/lib/pdfrb/model/type/icc_based_color_space.rb +24 -0
  282. data/lib/pdfrb/model/type/icon_fit.rb +31 -0
  283. data/lib/pdfrb/model/type/indexed.rb +32 -0
  284. data/lib/pdfrb/model/type/info.rb +44 -0
  285. data/lib/pdfrb/model/type/ink_annotation.rb +29 -0
  286. data/lib/pdfrb/model/type/interactive_form.rb +36 -0
  287. data/lib/pdfrb/model/type/interior_color.rb +12 -0
  288. data/lib/pdfrb/model/type/jbig2_globals.rb +15 -0
  289. data/lib/pdfrb/model/type/lab.rb +27 -0
  290. data/lib/pdfrb/model/type/line_annotation.rb +44 -0
  291. data/lib/pdfrb/model/type/line_ending_styling.rb +39 -0
  292. data/lib/pdfrb/model/type/link_annotation.rb +41 -0
  293. data/lib/pdfrb/model/type/link_appearance.rb +9 -0
  294. data/lib/pdfrb/model/type/mark_information.rb +23 -0
  295. data/lib/pdfrb/model/type/marked_content_reference.rb +24 -0
  296. data/lib/pdfrb/model/type/markup_annotation.rb +36 -0
  297. data/lib/pdfrb/model/type/mdp_dict.rb +28 -0
  298. data/lib/pdfrb/model/type/measure.rb +39 -0
  299. data/lib/pdfrb/model/type/media_clip.rb +28 -0
  300. data/lib/pdfrb/model/type/media_offset.rb +106 -0
  301. data/lib/pdfrb/model/type/metadata.rb +27 -3
  302. data/lib/pdfrb/model/type/misc_helpers.rb +75 -0
  303. data/lib/pdfrb/model/type/movie.rb +28 -0
  304. data/lib/pdfrb/model/type/names.rb +51 -0
  305. data/lib/pdfrb/model/type/namespace.rb +27 -0
  306. data/lib/pdfrb/model/type/object_reference.rb +31 -0
  307. data/lib/pdfrb/model/type/object_stream.rb +38 -0
  308. data/lib/pdfrb/model/type/opt_content_ext.rb +94 -0
  309. data/lib/pdfrb/model/type/opt_content_page_element.rb +18 -0
  310. data/lib/pdfrb/model/type/optional_content_config.rb +56 -0
  311. data/lib/pdfrb/model/type/optional_content_group.rb +35 -10
  312. data/lib/pdfrb/model/type/optional_content_membership.rb +36 -2
  313. data/lib/pdfrb/model/type/optional_content_properties.rb +16 -1
  314. data/lib/pdfrb/model/type/outline.rb +40 -6
  315. data/lib/pdfrb/model/type/outline_item.rb +45 -0
  316. data/lib/pdfrb/model/type/output_intent.rb +30 -0
  317. data/lib/pdfrb/model/type/page.rb +127 -0
  318. data/lib/pdfrb/model/type/page_label.rb +68 -0
  319. data/lib/pdfrb/model/type/page_piece_info.rb +58 -0
  320. data/lib/pdfrb/model/type/page_tree_node.rb +51 -16
  321. data/lib/pdfrb/model/type/page_tree_node_root.rb +15 -0
  322. data/lib/pdfrb/model/type/pattern.rb +16 -0
  323. data/lib/pdfrb/model/type/pattern_shading.rb +25 -0
  324. data/lib/pdfrb/model/type/pattern_tiling.rb +25 -0
  325. data/lib/pdfrb/model/type/polygon_annotation.rb +29 -0
  326. data/lib/pdfrb/model/type/polygon_polyline.rb +14 -0
  327. data/lib/pdfrb/model/type/polyline_annotation.rb +29 -0
  328. data/lib/pdfrb/model/type/popup_annotation.rb +20 -0
  329. data/lib/pdfrb/model/type/printer_mark_annotation.rb +13 -0
  330. data/lib/pdfrb/model/type/redact_annotation.rb +28 -0
  331. data/lib/pdfrb/model/type/rendition.rb +31 -0
  332. data/lib/pdfrb/model/type/resources.rb +59 -14
  333. data/lib/pdfrb/model/type/rich_media_activation.rb +24 -0
  334. data/lib/pdfrb/model/type/rich_media_animation.rb +25 -0
  335. data/lib/pdfrb/model/type/rich_media_annotation.rb +18 -0
  336. data/lib/pdfrb/model/type/rich_media_command.rb +19 -0
  337. data/lib/pdfrb/model/type/rich_media_configuration.rb +32 -0
  338. data/lib/pdfrb/model/type/rich_media_content.rb +28 -0
  339. data/lib/pdfrb/model/type/rich_media_cue_point.rb +18 -0
  340. data/lib/pdfrb/model/type/rich_media_deactivation.rb +17 -0
  341. data/lib/pdfrb/model/type/rich_media_instance.rb +33 -0
  342. data/lib/pdfrb/model/type/rich_media_presentation.rb +29 -0
  343. data/lib/pdfrb/model/type/rich_media_settings.rb +33 -0
  344. data/lib/pdfrb/model/type/rich_media_window.rb +20 -0
  345. data/lib/pdfrb/model/type/screen_annotation.rb +19 -0
  346. data/lib/pdfrb/model/type/separation.rb +25 -0
  347. data/lib/pdfrb/model/type/sig_field_lock.rb +25 -0
  348. data/lib/pdfrb/model/type/sig_field_seed_value.rb +25 -0
  349. data/lib/pdfrb/model/type/signature.rb +65 -0
  350. data/lib/pdfrb/model/type/signature_build.rb +86 -0
  351. data/lib/pdfrb/model/type/signature_field.rb +17 -0
  352. data/lib/pdfrb/model/type/software_identifier.rb +19 -0
  353. data/lib/pdfrb/model/type/sound.rb +38 -0
  354. data/lib/pdfrb/model/type/sound_annotation.rb +21 -0
  355. data/lib/pdfrb/model/type/square_annotation.rb +24 -0
  356. data/lib/pdfrb/model/type/square_circle.rb +13 -0
  357. data/lib/pdfrb/model/type/squiggly_annotation.rb +11 -0
  358. data/lib/pdfrb/model/type/stamp_annotation.rb +19 -0
  359. data/lib/pdfrb/model/type/strikeout_annotation.rb +11 -0
  360. data/lib/pdfrb/model/type/struct_elem.rb +75 -0
  361. data/lib/pdfrb/model/type/struct_tree_root.rb +34 -0
  362. data/lib/pdfrb/model/type/structure_attributes.rb +44 -0
  363. data/lib/pdfrb/model/type/structure_element_kid.rb +33 -0
  364. data/lib/pdfrb/model/type/text_annotation.rb +25 -0
  365. data/lib/pdfrb/model/type/text_appearance.rb +9 -0
  366. data/lib/pdfrb/model/type/text_field.rb +21 -0
  367. data/lib/pdfrb/model/type/text_markup_annotation.rb +30 -0
  368. data/lib/pdfrb/model/type/three_d_activation.rb +32 -0
  369. data/lib/pdfrb/model/type/three_d_animation_style.rb +24 -0
  370. data/lib/pdfrb/model/type/three_d_background.rb +28 -0
  371. data/lib/pdfrb/model/type/three_d_cross_section.rb +25 -0
  372. data/lib/pdfrb/model/type/three_d_lighting_scheme.rb +26 -0
  373. data/lib/pdfrb/model/type/three_d_measure.rb +20 -0
  374. data/lib/pdfrb/model/type/three_d_measure_3dc.rb +23 -0
  375. data/lib/pdfrb/model/type/three_d_measure_ad3.rb +14 -0
  376. data/lib/pdfrb/model/type/three_d_measure_ld3.rb +14 -0
  377. data/lib/pdfrb/model/type/three_d_measure_pd3.rb +20 -0
  378. data/lib/pdfrb/model/type/three_d_measure_rd3.rb +14 -0
  379. data/lib/pdfrb/model/type/three_d_node.rb +37 -0
  380. data/lib/pdfrb/model/type/three_d_reference.rb +21 -0
  381. data/lib/pdfrb/model/type/three_d_render_mode.rb +34 -0
  382. data/lib/pdfrb/model/type/three_d_stream.rb +48 -0
  383. data/lib/pdfrb/model/type/three_d_units.rb +18 -0
  384. data/lib/pdfrb/model/type/three_d_view.rb +40 -0
  385. data/lib/pdfrb/model/type/to_unicode_cmap_stream.rb +23 -0
  386. data/lib/pdfrb/model/type/transition.rb +68 -0
  387. data/lib/pdfrb/model/type/underline_annotation.rb +11 -0
  388. data/lib/pdfrb/model/type/ur_transform_parameters.rb +33 -0
  389. data/lib/pdfrb/model/type/uri_dict.rb +18 -0
  390. data/lib/pdfrb/model/type/url_alias.rb +19 -0
  391. data/lib/pdfrb/model/type/variable_text_field.rb +47 -0
  392. data/lib/pdfrb/model/type/viewer_preferences.rb +42 -0
  393. data/lib/pdfrb/model/type/watermark_annotation.rb +13 -0
  394. data/lib/pdfrb/model/type/widget_annotation.rb +48 -0
  395. data/lib/pdfrb/model/type/widget_appearance.rb +9 -0
  396. data/lib/pdfrb/model/type/xobject_form.rb +41 -0
  397. data/lib/pdfrb/model/type/xobject_image.rb +56 -0
  398. data/lib/pdfrb/model/type/xref_stream.rb +37 -0
  399. data/lib/pdfrb/model/type.rb +289 -6
  400. data/lib/pdfrb/serializer.rb +2 -2
  401. data/lib/pdfrb/source/tokenizer.rb +0 -2
  402. data/lib/pdfrb/task/benchmark.rb +83 -0
  403. data/lib/pdfrb/task/extract_text.rb +1 -1
  404. data/lib/pdfrb/task/generate_corpus.rb +130 -0
  405. data/lib/pdfrb/task/memory_profile.rb +84 -0
  406. data/lib/pdfrb/task/optimize.rb +97 -7
  407. data/lib/pdfrb/task/regenerate_appearances.rb +98 -0
  408. data/lib/pdfrb/task.rb +4 -3
  409. data/lib/pdfrb/test_utils.rb +112 -0
  410. data/lib/pdfrb/validator.rb +82 -0
  411. data/lib/pdfrb/version.rb +1 -1
  412. data/lib/pdfrb/writer.rb +117 -27
  413. data/lib/pdfrb/xmp/packet.rb +122 -0
  414. data/lib/pdfrb/xmp/schemas.rb +96 -0
  415. data/lib/pdfrb/xmp.rb +44 -0
  416. data/lib/pdfrb/xref_section.rb +2 -2
  417. data/lib/pdfrb.rb +24 -54
  418. data/pdfrb.gemspec +45 -0
  419. data/script/apply_fixes.sh +843 -0
  420. data/script/make_jpeg_fixture.rb +53 -0
  421. data/script/make_png_fixture.rb +25 -0
  422. metadata +319 -4
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Font
5
+ module TrueType
6
+ class Post
7
+ attr_reader :version, :italic_angle, :underline_position,
8
+ :underline_thickness, :is_fixed_pitch, :glyph_names
9
+
10
+ def initialize(data)
11
+ return unless data && data.bytesize >= 32
12
+
13
+ @version = data.bytes[0, 4].pack("C*").unpack1("N")
14
+ @italic_angle = s32(data, 4) / 65536.0
15
+ @underline_position = s16(data, 8)
16
+ @underline_thickness = s16(data, 10)
17
+ @is_fixed_pitch = data.bytes[12, 4].pack("C*").unpack1("N")
18
+ @glyph_names = parse_names(data) if @version == 0x00020000
19
+ end
20
+
21
+ def glyph_name(index)
22
+ return nil unless @glyph_names
23
+ return nil if index >= @glyph_names.length
24
+
25
+ @glyph_names[index]
26
+ end
27
+
28
+ def s32(data, off); (data.getbyte(off) << 24) | (data.getbyte(off + 1) << 16) | (data.getbyte(off + 2) << 8) | data.getbyte(off + 3); end
29
+
30
+ STANDARD_GLYPH_NAMES = %w[
31
+ .notdef .null nonmarkingreturn space exclam quotedbl numbersign
32
+ dollar percent ampersand quotesingle parenleft parenright asterisk
33
+ plus comma hyphen period slash zero one two three four five six
34
+ seven eight nine colon semicolon less equal greater question at
35
+ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
36
+ bracketleft backslash bracketright asciicircum underscore grave
37
+ a b c d e f g h i j k l m n o p q r s t u v w x y z
38
+ braceleft bar braceright asciitilde Adieresis Aring Ccedilla Eacute
39
+ Ntilde Odieresis Udieresis aacute agrave acircumflex adieresis atilde
40
+ aring ccedilla eacute egrave ecircumflex edieresis iacute igrave
41
+ icircumflex idieresis ntilde oacute ograve ocircumflex odieresis
42
+ otilde uacute ugrave ucircumflex udieresis dagger degree cent
43
+ sterling section bullet paragraph germandbls registered copyright
44
+ trademark acute dieresis notequal AE Oslash infinity plusminus
45
+ lessequal greaterequal yen mu partialdiff summation product pi
46
+ integral ordfeminine ordmasculine Omega ae oslash questiondown
47
+ exclamdown logicalnot radical florin approxequal Delta guillemotleft
48
+ guillemotright ellipsis nonbreakingspace Agrave Atilde Otilde OE oe
49
+ endash emdash quotedblleft quotedblright quoteleft quoteright divide
50
+ lozenge ydieresis Ydieresis fraction currency guilsinglleft
51
+ guilsinglright fi fl daggerdbl periodcentered quotesinglbase
52
+ quotedblbase perthousand Acircumflex Ecircumflex Aacute Edieresis
53
+ Egrave Iacute Icircumflex Idieresis Eth eth Yacute yacute Thorne thorn
54
+ multiply minus onesuperior twosuperior threesuperior onehalf
55
+ onequarter threequarters franc Gbreve gbreve Idotaccent Scedilla
56
+ scedilla Cacute cacute Ccaron ccaron dcroat
57
+ ].freeze
58
+
59
+ private
60
+
61
+ def parse_names(data)
62
+ count = u16(data, 32)
63
+ indices = Array.new(count) { |i| u16(data, 34 + (i * 2)) }
64
+ offset = 34 + (count * 2)
65
+ custom_names = {}
66
+
67
+ indices.each do |idx|
68
+ next if idx < 258
69
+ next if custom_names.key?(idx)
70
+
71
+ len = data.getbyte(offset) || 0
72
+ offset += 1
73
+ custom_names[idx] = data.bytes[offset, len]&.pack("C*")&.force_encoding("UTF-8")
74
+ offset += len
75
+ end
76
+
77
+ STANDARD_GLYPH_NAMES.map.with_index do |_name, i|
78
+ indices[i] && indices[i] < 258 ? STANDARD_GLYPH_NAMES[indices[i]] : custom_names[indices[i]]
79
+ end
80
+ rescue StandardError
81
+ nil
82
+ end
83
+
84
+ def u16(data, off); (data.getbyte(off) << 8) | data.getbyte(off + 1); end
85
+
86
+ def s16(data, off)
87
+ v = u16(data, off)
88
+ v >= 32768 ? v - 65536 : v
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -1,5 +1,3 @@
1
- require "set"
2
-
3
1
  # frozen_string_literal: true
4
2
 
5
3
  module Pdfrb
@@ -43,7 +41,7 @@ module Pdfrb
43
41
  private
44
42
 
45
43
  def build_subset
46
- return @ttf.instance_variable_get(:@data) unless can_subset?
44
+ return @ttf.raw_data unless can_subset?
47
45
 
48
46
  @resolved = resolve_composites(@glyph_ids)
49
47
 
@@ -65,7 +63,7 @@ module Pdfrb
65
63
  copy_remaining_tables(tables)
66
64
  assemble_ttf(tables)
67
65
  rescue StandardError
68
- @ttf.instance_variable_get(:@data)
66
+ @ttf.raw_data
69
67
  end
70
68
 
71
69
  def can_subset?
@@ -299,7 +297,7 @@ module Pdfrb
299
297
  end
300
298
 
301
299
  def copy_remaining_tables(tables)
302
- raw = @ttf.instance_variable_get(:@data)
300
+ raw = @ttf.raw_data
303
301
  num_tables = (raw.getbyte(4) * 256) + raw.getbyte(5)
304
302
  num_tables.times do |i|
305
303
  base = 12 + (i * 16)
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Font
5
+ module TrueType
6
+ module Wrapper
7
+ module_function
8
+
9
+ def embed(document, ttf_data, **)
10
+ ttf = File.new(ttf_data)
11
+ name = ttf.name_table_parsed
12
+ ps_name = name&.ps_name || "EmbeddedFont"
13
+
14
+ fd = create_font_descriptor(document, ttf, ps_name)
15
+ widths = build_widths(ttf)
16
+ tu_stream = build_tounicode(document, ttf)
17
+ font_file = embed_font_file(document, ttf_data)
18
+
19
+ fd.value[:FontFile2] = Pdfrb::Model::Reference.new(font_file.oid, font_file.gen)
20
+
21
+ document.add(
22
+ {
23
+ Type: :Font,
24
+ Subtype: :TrueType,
25
+ BaseFont: ps_name.to_sym,
26
+ FirstChar: 0,
27
+ LastChar: 255,
28
+ Widths: widths,
29
+ FontDescriptor: Pdfrb::Model::Reference.new(fd.oid, fd.gen),
30
+ ToUnicode: Pdfrb::Model::Reference.new(tu_stream.oid, tu_stream.gen),
31
+ Encoding: :WinAnsiEncoding,
32
+ },
33
+ type: Pdfrb::Model::Type::FontTrueType
34
+ )
35
+ end
36
+
37
+ def create_font_descriptor(document, ttf, ps_name)
38
+ head = ttf.head
39
+ os2 = ttf.os2
40
+ document.add(
41
+ {
42
+ Type: :FontDescriptor,
43
+ FontName: ps_name.to_sym,
44
+ Flags: 32,
45
+ FontBBox: head&.bbox || [0, 0, 1000, 1000],
46
+ ItalicAngle: 0,
47
+ Ascent: os2&.s_typo_ascender || ttf.hhea&.ascender || 800,
48
+ Descent: os2&.s_typo_descender || ttf.hhea&.descender || -200,
49
+ CapHeight: os2&.s_cap_height || 700,
50
+ StemV: 80,
51
+ },
52
+ type: Pdfrb::Model::Cos::Dictionary
53
+ )
54
+ end
55
+
56
+ def build_widths(ttf)
57
+ widths = Array.new(256, 500)
58
+ table = Pdfrb::Font::Encoding::WinAnsiEncoding::TABLE
59
+ Maxp.new(ttf.maxp_table)
60
+
61
+ table.each_with_index do |cp, byte|
62
+ next unless cp
63
+
64
+ begin
65
+ gid = ttf.cmap.glyph_id_for(cp)
66
+ next unless gid&.positive?
67
+
68
+ w = ttf.hmtx.advance_width(gid)
69
+ widths[byte] = w if w&.positive?
70
+ rescue StandardError
71
+ next
72
+ end
73
+ end
74
+ widths
75
+ end
76
+
77
+ def build_tounicode(document, _ttf)
78
+ table = Pdfrb::Font::Encoding::WinAnsiEncoding::TABLE
79
+ mapping = {}
80
+ table.each_with_index { |cp, byte| mapping[byte] = cp if cp }
81
+
82
+ cmap_data = Pdfrb::Font::CMap::Writer.write(mapping)
83
+ stream = document.add(
84
+ { Length: cmap_data.bytesize },
85
+ type: Pdfrb::Model::Cos::Stream
86
+ )
87
+ stream.stream = cmap_data
88
+ stream
89
+ end
90
+
91
+ def embed_font_file(document, ttf_data)
92
+ stream = document.add(
93
+ { Length: ttf_data.bytesize },
94
+ type: Pdfrb::Model::Cos::Stream
95
+ )
96
+ stream.stream = ttf_data
97
+ stream
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -9,6 +9,13 @@ module Pdfrb
9
9
  autoload :Hmtx, "pdfrb/font/true_type/hmtx"
10
10
  autoload :Cmap, "pdfrb/font/true_type/cmap"
11
11
  autoload :OS2, "pdfrb/font/true_type/os2"
12
+ autoload :Maxp, "pdfrb/font/true_type/maxp"
13
+ autoload :Post, "pdfrb/font/true_type/post"
14
+ autoload :Name, "pdfrb/font/true_type/name"
15
+ autoload :Loca, "pdfrb/font/true_type/loca"
16
+ autoload :Glyf, "pdfrb/font/true_type/glyf"
17
+ autoload :Kern, "pdfrb/font/true_type/kern"
18
+ autoload :Wrapper, "pdfrb/font/true_type/wrapper"
12
19
  end
13
20
  end
14
21
  end
data/lib/pdfrb/font.rb CHANGED
@@ -10,6 +10,8 @@ module Pdfrb
10
10
  autoload :CMap, "pdfrb/font/cmap"
11
11
  autoload :TrueType, "pdfrb/font/true_type"
12
12
  autoload :Type1, "pdfrb/font/type1"
13
+ autoload :Metrics, "pdfrb/font/metrics_helper"
14
+ autoload :MetricsHelper, "pdfrb/font/metrics_helper"
13
15
  end
14
16
 
15
17
  # Top-level font loader registry. Extends what +Document::Fonts+
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module FontLoader
5
+ module FromConfiguration
6
+ module_function
7
+
8
+ def call(document, name, **_opts)
9
+ config = document.config["font.#{name}"]
10
+ return nil unless config
11
+
12
+ case config["type"]
13
+ when "standard14" then Standard14.call(document, config["name"] || name)
14
+ when "file" then FromFile.call(document, config["path"])
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module FontLoader
5
+ module FromFile
6
+ module_function
7
+
8
+ def call(document, path, **_opts)
9
+ return nil unless path.is_a?(String) && File.file?(path)
10
+
11
+ data = File.binread(path)
12
+ magic = data&.byteslice(0, 4)
13
+ return nil unless magic
14
+ return nil unless ["\x00\x01\x00\x00".b, "true".b, "OTTO".b].include?(magic)
15
+
16
+ Pdfrb::Font::TrueType::Wrapper.embed(document, data, resource_name: :F1)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module FontLoader
5
+ module Standard14
6
+ STANDARDS = %w[Helvetica Helvetica-Bold Helvetica-Oblique Helvetica-BoldOblique
7
+ Times-Roman Times-Bold Times-Italic Times-BoldItalic
8
+ Courier Courier-Bold Courier-Oblique Courier-BoldOblique Symbol ZapfDingbats].freeze
9
+
10
+ module_function
11
+
12
+ def call(document, name, **_opts)
13
+ return nil unless STANDARDS.include?(name.to_s)
14
+
15
+ document.add({ Type: :Font, Subtype: :Type1, BaseFont: name.to_sym },
16
+ type: Pdfrb::Model::Type::FontType1)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module FontLoader
5
+ module VariantFromName
6
+ BASE_MAP = {
7
+ "Helvetica" => %w[Helvetica Helvetica-Bold Helvetica-Oblique Helvetica-BoldOblique],
8
+ "Times" => %w[Times-Roman Times-Bold Times-Italic Times-BoldItalic],
9
+ "Courier" => %w[Courier Courier-Bold Courier-Oblique Courier-BoldOblique],
10
+ }.freeze
11
+
12
+ module_function
13
+
14
+ def call(document, name, **_opts)
15
+ return nil unless name.is_a?(String)
16
+
17
+ base = name.sub(/[- ](Bold|Italic|Oblique|BoldItalic|BoldOblique).*/i, "")
18
+ return nil unless BASE_MAP.key?(base)
19
+
20
+ idx = case name
21
+ when /bold.*italic|italic.*bold/i
22
+ 3
23
+ when /bold/i
24
+ 1
25
+ else
26
+ /oblique|italic/i.match?(name) ? 2 : 0
27
+ end
28
+ full_name = BASE_MAP[base][idx]
29
+ return nil unless full_name
30
+
31
+ document.add({ Type: :Font, Subtype: :Type1, BaseFont: full_name.to_sym },
32
+ type: Pdfrb::Model::Type::FontType1)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -6,5 +6,9 @@ module Pdfrb
6
6
  module FontLoader
7
7
  autoload :TrueType, "pdfrb/font_loader/true_type"
8
8
  autoload :Type1, "pdfrb/font_loader/type1"
9
+ autoload :Standard14, "pdfrb/font_loader/standard14"
10
+ autoload :FromFile, "pdfrb/font_loader/from_file"
11
+ autoload :VariantFromName, "pdfrb/font_loader/variant_from_name"
12
+ autoload :FromConfiguration, "pdfrb/font_loader/from_configuration"
9
13
  end
10
14
  end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ # Font file resolver. Searches system font directories for font files
5
+ # by family name + style. Eliminates the need for external font-finding
6
+ # libraries like fontisan.
7
+ class FontResolver
8
+ DEFAULT_SEARCH_PATHS = [
9
+ "/System/Library/Fonts",
10
+ "/Library/Fonts",
11
+ File.expand_path("~/Library/Fonts"),
12
+ "/usr/share/fonts",
13
+ File.expand_path("~/.local/share/fonts"),
14
+ "/usr/local/share/fonts",
15
+ ].freeze
16
+
17
+ FontInfo = Struct.new(:path, :family, :style, :ps_name, keyword_init: true)
18
+
19
+ attr_reader :search_paths, :cache
20
+
21
+ def initialize(search_paths: DEFAULT_SEARCH_PATHS)
22
+ @search_paths = search_paths.dup
23
+ @cache = nil
24
+ end
25
+
26
+ def find(family:, style: "Regular")
27
+ build_cache unless @cache
28
+ normalized_family = family.downcase.gsub(/\s+/, "")
29
+ normalized_style = style.downcase.gsub(/\s+/, "")
30
+
31
+ @cache.each do |info|
32
+ next unless info.family&.downcase&.gsub(/\s+/, "") == normalized_family
33
+ next unless match_style?(info.style, normalized_style)
34
+
35
+ return info.path
36
+ end
37
+ nil
38
+ end
39
+
40
+ def find_by_ps_name(ps_name)
41
+ build_cache unless @cache
42
+ normalized = ps_name.downcase
43
+
44
+ @cache.each do |info|
45
+ return info.path if info.ps_name&.downcase == normalized
46
+ end
47
+ nil
48
+ end
49
+
50
+ def available_fonts
51
+ build_cache unless @cache
52
+ @cache.dup
53
+ end
54
+
55
+ private
56
+
57
+ def match_style?(actual, requested)
58
+ return true if requested == "regular" && (actual.nil? || actual == "" || actual.downcase == "regular")
59
+ return true if actual&.downcase&.gsub(/\s+/, "") == requested
60
+
61
+ false
62
+ end
63
+
64
+ def build_cache
65
+ @cache = []
66
+ @search_paths.each do |dir|
67
+ next unless Dir.exist?(dir)
68
+
69
+ Dir.glob("**/*.{ttf,otf,ttc}", base: dir).each do |rel_path|
70
+ full_path = File.join(dir, rel_path)
71
+ info = parse_font_info(full_path)
72
+ @cache << info if info
73
+ end
74
+ end
75
+ @cache.freeze
76
+ end
77
+
78
+ def parse_font_info(path)
79
+ data = File.binread(path)
80
+ return nil unless valid_font_magic?(data)
81
+
82
+ ttf = Pdfrb::Font::TrueType::File.new(data)
83
+ name_table = ttf.name_table
84
+ return FontInfo.new(path: path) unless name_table
85
+
86
+ records = parse_name_table(name_table)
87
+ family = records[1] || records[16]
88
+ subfamily = records[2] || records[17]
89
+ ps_name = records[6]
90
+
91
+ FontInfo.new(path: path, family: family, style: subfamily, ps_name: ps_name)
92
+ rescue StandardError
93
+ FontInfo.new(path: path)
94
+ end
95
+
96
+ def valid_font_magic?(data)
97
+ return false if data.bytesize < 4
98
+
99
+ magic = data.byteslice(0, 4)
100
+ return true if magic == "ttcf".b
101
+ return true if magic == "\x00\x01\x00\x00".b
102
+ return true if magic == "OTTO".b
103
+ return true if magic == "true".b
104
+ return true if magic == "typ1".b
105
+
106
+ false
107
+ end
108
+
109
+ def parse_name_table(name_data)
110
+ results = {}
111
+ return results unless name_data && name_data.bytesize >= 6
112
+
113
+ name_data.unpack1("n")
114
+ count = name_data.unpack1("n", offset: 2)
115
+ string_offset = name_data.unpack1("n", offset: 4)
116
+
117
+ count.times do |i|
118
+ offset = 6 + (i * 12)
119
+ break if offset + 12 > name_data.bytesize
120
+
121
+ platform_id, _encoding_id, _lang_id, name_id, length, str_offset =
122
+ name_data.unpack("nnnnnn", offset: offset)
123
+ next unless [1, 3].include?(platform_id)
124
+
125
+ raw = name_data.byteslice(string_offset + str_offset, length)
126
+ next unless raw
127
+
128
+ str = if platform_id == 3
129
+ raw.unpack("n*").pack("U*")
130
+ else
131
+ raw.force_encoding("UTF-8")
132
+ end
133
+ results[name_id] = str
134
+ end
135
+ results
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ module Image
5
+ # Read-only metadata extractor for embedded image XObjects. Used
6
+ # by Task::Optimize to find downsampling candidates and by callers
7
+ # that need to know what's in a document without re-parsing every
8
+ # stream.
9
+ module Audit
10
+ ImageInfo = Struct.new(
11
+ :oid, :width, :height, :bits_per_component, :color_space,
12
+ :filter_name, :decoded_bytesize, :stream_bytesize, :smask_oid,
13
+ keyword_init: true
14
+ )
15
+
16
+ module_function
17
+
18
+ # Enumerate every image XObject in +document+. Yields an
19
+ # ImageInfo per image; returns an Enumerator if no block given.
20
+ def each_image(document)
21
+ return enum_for(:each_image, document) unless block_given?
22
+
23
+ document.each_indirect_object do |obj|
24
+ next unless image_xobject?(obj)
25
+
26
+ yield info_for(obj)
27
+ end
28
+ end
29
+
30
+ # Return an Array of all image infos (mirror of #each_image).
31
+ def all(document)
32
+ each_image(document).to_a
33
+ end
34
+
35
+ # Find images whose pixel count exceeds +target_pixels+. Useful
36
+ # for spotting oversized images worth downsampling. Returns an
37
+ # Array of ImageInfo.
38
+ def oversized(document, target_pixels:)
39
+ all(document).select do |info|
40
+ (info.width || 0) * (info.height || 0) > target_pixels
41
+ end
42
+ end
43
+
44
+ def image_xobject?(obj)
45
+ return false unless obj.is_a?(Pdfrb::Model::Cos::Stream)
46
+
47
+ obj.value[:Type] == :XObject && obj.value[:Subtype] == :Image
48
+ end
49
+
50
+ def info_for(image)
51
+ ImageInfo.new(
52
+ oid: image.oid,
53
+ width: image.value[:Width],
54
+ height: image.value[:Height],
55
+ bits_per_component: image.value[:BitsPerComponent],
56
+ color_space: image.value[:ColorSpace],
57
+ filter_name: image.value[:Filter],
58
+ decoded_bytesize: decoded_size(image),
59
+ stream_bytesize: image.stream&.bytesize || 0,
60
+ smask_oid: smask_oid(image)
61
+ )
62
+ end
63
+
64
+ def decoded_size(image)
65
+ w = image.value[:Width]
66
+ h = image.value[:Height]
67
+ bpc = image.value[:BitsPerComponent] || 8
68
+ return nil unless w && h
69
+
70
+ channels = channels_for(image.value[:ColorSpace])
71
+ (w * h * channels * bpc) / 8
72
+ end
73
+
74
+ def channels_for(color_space)
75
+ case color_space
76
+ when :DeviceGray, :CalGray, :Indexed then 1
77
+ when :DeviceCMYK then 4
78
+ else
79
+ # :DeviceRGB, :CalRGB, :Lab, nil, and ICCBased (default to
80
+ # 3 channels when we can't introspect the profile) all share
81
+ # this branch.
82
+ 3
83
+ end
84
+ end
85
+
86
+ def smask_oid(image)
87
+ smask = image.value[:SMask]
88
+ return nil unless smask.is_a?(Pdfrb::Model::Reference)
89
+
90
+ smask.oid
91
+ end
92
+ end
93
+ end
94
+ end