pdfrb 0.3.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (404) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/Gemfile +1 -0
  4. data/PROPOSAL.canvas-clip.md +92 -0
  5. data/PROPOSAL.canvas-draw-image-matrix.md +87 -0
  6. data/PROPOSAL.canvas-rich-text.md +103 -0
  7. data/PROPOSAL.canvas-text-lines.md +77 -0
  8. data/PROPOSAL.canvas-transparency.md +106 -0
  9. data/PROPOSAL.external-font-metrics.md +129 -0
  10. data/PROPOSAL.font-embedding-fontfile2.md +88 -0
  11. data/PROPOSAL.font-resolver-ttc.md +105 -0
  12. data/PROPOSAL.font-resolver.md +96 -0
  13. data/PROPOSAL.font-subsetting.md +98 -0
  14. data/PROPOSAL.gradient-shadings.md +110 -0
  15. data/PROPOSAL.page-mediabox-setter.md +66 -0
  16. data/PROPOSAL.per-glyph-width.md +91 -0
  17. data/PROPOSAL.stringio-require.md +53 -0
  18. data/PROPOSAL.struct-tree-root.md +102 -0
  19. data/PROPOSAL.text-measurement.md +93 -0
  20. data/PROPOSAL.ttf-table-parsing-fix.md +112 -0
  21. data/RELEASE.md +48 -0
  22. data/lib/pdfrb/action/base.rb +21 -0
  23. data/lib/pdfrb/action/types.rb +178 -0
  24. data/lib/pdfrb/action.rb +55 -0
  25. data/lib/pdfrb/annotation/base.rb +79 -0
  26. data/lib/pdfrb/annotation/types.rb +222 -0
  27. data/lib/pdfrb/annotation.rb +78 -0
  28. data/lib/pdfrb/appearance/generator.rb +216 -0
  29. data/lib/pdfrb/appearance.rb +11 -0
  30. data/lib/pdfrb/arlington/object_definition.rb +2 -2
  31. data/lib/pdfrb/arlington/predicate/evaluator.rb +1 -1
  32. data/lib/pdfrb/arlington/predicate/functions.rb +25 -9
  33. data/lib/pdfrb/arlington/predicate.rb +1 -5
  34. data/lib/pdfrb/cli/batch.rb +18 -0
  35. data/lib/pdfrb/cli/debug_info.rb +18 -0
  36. data/lib/pdfrb/cli/files.rb +16 -0
  37. data/lib/pdfrb/cli/fonts.rb +16 -0
  38. data/lib/pdfrb/cli/form.rb +18 -0
  39. data/lib/pdfrb/cli/image2pdf.rb +18 -0
  40. data/lib/pdfrb/cli/images.rb +34 -0
  41. data/lib/pdfrb/cli/info.rb +20 -0
  42. data/lib/pdfrb/cli/inspect.rb +18 -0
  43. data/lib/pdfrb/cli/merge.rb +18 -0
  44. data/lib/pdfrb/cli/modify.rb +18 -0
  45. data/lib/pdfrb/cli/optimize.rb +18 -0
  46. data/lib/pdfrb/cli/split.rb +21 -0
  47. data/lib/pdfrb/cli/watermark.rb +18 -0
  48. data/lib/pdfrb/cli.rb +291 -7
  49. data/lib/pdfrb/color/color_space.rb +259 -0
  50. data/lib/pdfrb/color/icc_profile.rb +90 -0
  51. data/lib/pdfrb/color.rb +12 -0
  52. data/lib/pdfrb/compare/comparator.rb +244 -0
  53. data/lib/pdfrb/compare/report.rb +78 -0
  54. data/lib/pdfrb/compare.rb +42 -0
  55. data/lib/pdfrb/composer.rb +161 -0
  56. data/lib/pdfrb/conformance/pdf_a.rb +317 -56
  57. data/lib/pdfrb/conformance/pdf_ua.rb +186 -29
  58. data/lib/pdfrb/conformance/pdf_x.rb +247 -0
  59. data/lib/pdfrb/conformance/rule.rb +78 -0
  60. data/lib/pdfrb/conformance/structure_elements.rb +174 -0
  61. data/lib/pdfrb/conformance/verapdf_bridge.rb +92 -0
  62. data/lib/pdfrb/conformance.rb +7 -3
  63. data/lib/pdfrb/content/canvas.rb +217 -72
  64. data/lib/pdfrb/content/color_space.rb +157 -0
  65. data/lib/pdfrb/content/hidden_text_detector.rb +167 -0
  66. data/lib/pdfrb/content/operator.rb +21 -1
  67. data/lib/pdfrb/content/operators/clipping.rb +36 -0
  68. data/lib/pdfrb/content/operators/graphics_state_params.rb +15 -0
  69. data/lib/pdfrb/content/operators.rb +21 -0
  70. data/lib/pdfrb/content/shading/base.rb +139 -0
  71. data/lib/pdfrb/content/shading.rb +17 -0
  72. data/lib/pdfrb/content/smart_text_extractor.rb +85 -0
  73. data/lib/pdfrb/content/tiling_pattern.rb +78 -0
  74. data/lib/pdfrb/content/transformation_matrix.rb +61 -0
  75. data/lib/pdfrb/content.rb +7 -26
  76. data/lib/pdfrb/destination/fit.rb +185 -0
  77. data/lib/pdfrb/destination.rb +13 -0
  78. data/lib/pdfrb/digital_signature/cms_handler.rb +22 -0
  79. data/lib/pdfrb/digital_signature/signing.rb +107 -11
  80. data/lib/pdfrb/digital_signature/timestamp_handler.rb +45 -0
  81. data/lib/pdfrb/digital_signature/verification.rb +88 -9
  82. data/lib/pdfrb/digital_signature/verification_result.rb +14 -0
  83. data/lib/pdfrb/digital_signature.rb +26 -2
  84. data/lib/pdfrb/document/annotations.rb +120 -3
  85. data/lib/pdfrb/document/associated_files.rb +104 -0
  86. data/lib/pdfrb/document/colors.rb +95 -0
  87. data/lib/pdfrb/document/destinations.rb +127 -5
  88. data/lib/pdfrb/document/display.rb +84 -0
  89. data/lib/pdfrb/document/fonts.rb +417 -45
  90. data/lib/pdfrb/document/form.rb +338 -0
  91. data/lib/pdfrb/document/graphics_state.rb +91 -0
  92. data/lib/pdfrb/document/images.rb +4 -4
  93. data/lib/pdfrb/document/info.rb +102 -0
  94. data/lib/pdfrb/document/layers.rb +62 -0
  95. data/lib/pdfrb/document/metadata.rb +12 -5
  96. data/lib/pdfrb/document/output_intents.rb +91 -0
  97. data/lib/pdfrb/document/page_labels.rb +80 -0
  98. data/lib/pdfrb/document/pages.rb +68 -19
  99. data/lib/pdfrb/document/portfolio.rb +119 -0
  100. data/lib/pdfrb/document/shadings.rb +159 -0
  101. data/lib/pdfrb/document/stamps.rb +68 -0
  102. data/lib/pdfrb/document/structure.rb +242 -0
  103. data/lib/pdfrb/document.rb +73 -4
  104. data/lib/pdfrb/encryption/identity.rb +13 -0
  105. data/lib/pdfrb/encryption/password_verification.rb +53 -8
  106. data/lib/pdfrb/encryption/security_handler.rb +8 -5
  107. data/lib/pdfrb/encryption/standard_security_handler.rb +154 -81
  108. data/lib/pdfrb/encryption.rb +17 -0
  109. data/lib/pdfrb/error.rb +2 -2
  110. data/lib/pdfrb/filter/brotli_decode.rb +32 -0
  111. data/lib/pdfrb/filter/ccitt_fax_decode.rb +12 -0
  112. data/lib/pdfrb/filter/crypt.rb +2 -2
  113. data/lib/pdfrb/filter/dct_decode.rb +18 -0
  114. data/lib/pdfrb/filter/flate_decode.rb +4 -18
  115. data/lib/pdfrb/filter/jbig2_decode.rb +12 -0
  116. data/lib/pdfrb/filter/jpx_decode.rb +18 -0
  117. data/lib/pdfrb/filter/lzw_decode.rb +3 -2
  118. data/lib/pdfrb/filter/png_predictor.rb +94 -0
  119. data/lib/pdfrb/filter.rb +26 -13
  120. data/lib/pdfrb/font/afm_parser.rb +50 -26
  121. data/lib/pdfrb/font/cmap/writer.rb +9 -0
  122. data/lib/pdfrb/font/encoding/win_ansi_encoding.rb +17 -0
  123. data/lib/pdfrb/font/encoding.rb +32 -3
  124. data/lib/pdfrb/font/metrics_helper.rb +121 -0
  125. data/lib/pdfrb/font/true_type/file.rb +10 -1
  126. data/lib/pdfrb/font/true_type/glyf.rb +82 -0
  127. data/lib/pdfrb/font/true_type/kern.rb +58 -0
  128. data/lib/pdfrb/font/true_type/loca.rb +42 -0
  129. data/lib/pdfrb/font/true_type/maxp.rb +44 -0
  130. data/lib/pdfrb/font/true_type/name.rb +53 -0
  131. data/lib/pdfrb/font/true_type/post.rb +93 -0
  132. data/lib/pdfrb/font/true_type/subsetter.rb +3 -5
  133. data/lib/pdfrb/font/true_type/wrapper.rb +102 -0
  134. data/lib/pdfrb/font/true_type.rb +7 -0
  135. data/lib/pdfrb/font.rb +2 -0
  136. data/lib/pdfrb/font_loader/from_configuration.rb +19 -0
  137. data/lib/pdfrb/font_loader/from_file.rb +20 -0
  138. data/lib/pdfrb/font_loader/standard14.rb +20 -0
  139. data/lib/pdfrb/font_loader/variant_from_name.rb +36 -0
  140. data/lib/pdfrb/font_loader.rb +4 -0
  141. data/lib/pdfrb/font_resolver.rb +138 -0
  142. data/lib/pdfrb/image/audit.rb +94 -0
  143. data/lib/pdfrb/image/downsampler.rb +124 -0
  144. data/lib/pdfrb/image.rb +13 -0
  145. data/lib/pdfrb/image_loader/jpeg.rb +39 -58
  146. data/lib/pdfrb/image_loader/png.rb +31 -257
  147. data/lib/pdfrb/layout/bidi.rb +198 -0
  148. data/lib/pdfrb/layout/box.rb +49 -0
  149. data/lib/pdfrb/layout/box_fitter.rb +54 -0
  150. data/lib/pdfrb/layout/column_box.rb +71 -0
  151. data/lib/pdfrb/layout/container_box.rb +45 -0
  152. data/lib/pdfrb/layout/frame.rb +67 -0
  153. data/lib/pdfrb/layout/image_box.rb +44 -0
  154. data/lib/pdfrb/layout/inline_box.rb +25 -0
  155. data/lib/pdfrb/layout/line.rb +55 -0
  156. data/lib/pdfrb/layout/list_box.rb +87 -0
  157. data/lib/pdfrb/layout/numeric_refinements.rb +43 -0
  158. data/lib/pdfrb/layout/page_style.rb +65 -0
  159. data/lib/pdfrb/layout/style.rb +62 -0
  160. data/lib/pdfrb/layout/table_box.rb +66 -0
  161. data/lib/pdfrb/layout/text_box.rb +38 -0
  162. data/lib/pdfrb/layout/text_fragment.rb +45 -0
  163. data/lib/pdfrb/layout/text_layouter.rb +123 -0
  164. data/lib/pdfrb/layout.rb +28 -0
  165. data/lib/pdfrb/linearization/hint_stream.rb +93 -0
  166. data/lib/pdfrb/linearization/writer.rb +259 -0
  167. data/lib/pdfrb/linearization.rb +21 -0
  168. data/lib/pdfrb/model/cos/dictionary.rb +12 -0
  169. data/lib/pdfrb/model/pdf_array.rb +2 -2
  170. data/lib/pdfrb/model/type/action.rb +42 -0
  171. data/lib/pdfrb/model/type/action_go_to_3d_view.rb +14 -0
  172. data/lib/pdfrb/model/type/action_go_to_dp.rb +16 -0
  173. data/lib/pdfrb/model/type/action_go_to_r.rb +26 -0
  174. data/lib/pdfrb/model/type/action_goto.rb +34 -0
  175. data/lib/pdfrb/model/type/action_hide.rb +26 -0
  176. data/lib/pdfrb/model/type/action_import_data.rb +18 -0
  177. data/lib/pdfrb/model/type/action_java_script.rb +18 -0
  178. data/lib/pdfrb/model/type/action_launch.rb +22 -0
  179. data/lib/pdfrb/model/type/action_movie.rb +22 -0
  180. data/lib/pdfrb/model/type/action_named.rb +23 -0
  181. data/lib/pdfrb/model/type/action_nop.rb +12 -0
  182. data/lib/pdfrb/model/type/action_rendition.rb +28 -0
  183. data/lib/pdfrb/model/type/action_reset_form.rb +22 -0
  184. data/lib/pdfrb/model/type/action_rich_media_execute.rb +17 -0
  185. data/lib/pdfrb/model/type/action_set_ocg_state.rb +32 -0
  186. data/lib/pdfrb/model/type/action_set_state.rb +14 -0
  187. data/lib/pdfrb/model/type/action_sound_action.rb +23 -0
  188. data/lib/pdfrb/model/type/action_submit_form.rb +64 -0
  189. data/lib/pdfrb/model/type/action_thread.rb +23 -0
  190. data/lib/pdfrb/model/type/action_trans.rb +30 -0
  191. data/lib/pdfrb/model/type/action_uri.rb +30 -0
  192. data/lib/pdfrb/model/type/af_embedded_file.rb +11 -0
  193. data/lib/pdfrb/model/type/af_file_specification.rb +34 -0
  194. data/lib/pdfrb/model/type/annotation.rb +49 -10
  195. data/lib/pdfrb/model/type/appearance.rb +41 -0
  196. data/lib/pdfrb/model/type/appearance_characteristics.rb +36 -0
  197. data/lib/pdfrb/model/type/appearance_generator.rb +22 -0
  198. data/lib/pdfrb/model/type/appearance_trap_net.rb +84 -0
  199. data/lib/pdfrb/model/type/border_effect.rb +25 -0
  200. data/lib/pdfrb/model/type/border_styling.rb +38 -0
  201. data/lib/pdfrb/model/type/box_color_info.rb +21 -0
  202. data/lib/pdfrb/model/type/box_style.rb +19 -0
  203. data/lib/pdfrb/model/type/button.rb +17 -0
  204. data/lib/pdfrb/model/type/cal_gray.rb +17 -0
  205. data/lib/pdfrb/model/type/cal_rgb.rb +22 -0
  206. data/lib/pdfrb/model/type/caret_annotation.rb +21 -0
  207. data/lib/pdfrb/model/type/catalog.rb +99 -16
  208. data/lib/pdfrb/model/type/choice.rb +21 -0
  209. data/lib/pdfrb/model/type/cid_font.rb +52 -1
  210. data/lib/pdfrb/model/type/cid_font_descriptor_metrics.rb +28 -0
  211. data/lib/pdfrb/model/type/cid_system_info.rb +28 -0
  212. data/lib/pdfrb/model/type/circle_annotation.rb +20 -0
  213. data/lib/pdfrb/model/type/collection.rb +27 -0
  214. data/lib/pdfrb/model/type/collection_field.rb +24 -0
  215. data/lib/pdfrb/model/type/collection_item.rb +19 -0
  216. data/lib/pdfrb/model/type/collection_schema.rb +25 -0
  217. data/lib/pdfrb/model/type/collection_sort.rb +13 -0
  218. data/lib/pdfrb/model/type/crypt_filter.rb +58 -0
  219. data/lib/pdfrb/model/type/device_n.rb +37 -0
  220. data/lib/pdfrb/model/type/device_n_mixing_hints.rb +17 -0
  221. data/lib/pdfrb/model/type/device_n_process.rb +13 -0
  222. data/lib/pdfrb/model/type/doc_mdp_transform_parameters.rb +21 -0
  223. data/lib/pdfrb/model/type/document_security_store.rb +55 -0
  224. data/lib/pdfrb/model/type/embedded_file.rb +19 -0
  225. data/lib/pdfrb/model/type/embedded_file_parameter.rb +48 -0
  226. data/lib/pdfrb/model/type/encryption_public_key.rb +38 -0
  227. data/lib/pdfrb/model/type/encryption_standard.rb +68 -0
  228. data/lib/pdfrb/model/type/field_mdp_transform_parameters.rb +26 -0
  229. data/lib/pdfrb/model/type/file_attachment_annotation.rb +29 -0
  230. data/lib/pdfrb/model/type/file_spec_ef.rb +22 -0
  231. data/lib/pdfrb/model/type/file_spec_rf.rb +21 -0
  232. data/lib/pdfrb/model/type/file_specification.rb +43 -0
  233. data/lib/pdfrb/model/type/file_trailer.rb +54 -0
  234. data/lib/pdfrb/model/type/font.rb +73 -2
  235. data/lib/pdfrb/model/type/font_cid_type0.rb +17 -0
  236. data/lib/pdfrb/model/type/font_cid_type2.rb +17 -0
  237. data/lib/pdfrb/model/type/font_descriptor.rb +47 -0
  238. data/lib/pdfrb/model/type/font_descriptor_cid_type0.rb +15 -0
  239. data/lib/pdfrb/model/type/font_descriptor_cid_type2.rb +15 -0
  240. data/lib/pdfrb/model/type/font_encoding.rb +47 -0
  241. data/lib/pdfrb/model/type/font_file.rb +28 -0
  242. data/lib/pdfrb/model/type/font_file2.rb +32 -0
  243. data/lib/pdfrb/model/type/font_file3.rb +23 -0
  244. data/lib/pdfrb/model/type/font_multiple_master.rb +60 -0
  245. data/lib/pdfrb/model/type/font_true_type.rb +1 -1
  246. data/lib/pdfrb/model/type/font_type0.rb +1 -1
  247. data/lib/pdfrb/model/type/font_type1.rb +1 -1
  248. data/lib/pdfrb/model/type/font_type3.rb +1 -1
  249. data/lib/pdfrb/model/type/form_field.rb +73 -0
  250. data/lib/pdfrb/model/type/free_text_annotation.rb +37 -0
  251. data/lib/pdfrb/model/type/function.rb +42 -0
  252. data/lib/pdfrb/model/type/function_exponential.rb +31 -0
  253. data/lib/pdfrb/model/type/function_postscript.rb +25 -0
  254. data/lib/pdfrb/model/type/function_sampled.rb +55 -0
  255. data/lib/pdfrb/model/type/function_stitching.rb +33 -0
  256. data/lib/pdfrb/model/type/generic_appearance.rb +40 -0
  257. data/lib/pdfrb/model/type/graphics_state_parameter.rb +69 -0
  258. data/lib/pdfrb/model/type/halftone.rb +21 -0
  259. data/lib/pdfrb/model/type/halftone_type1.rb +17 -0
  260. data/lib/pdfrb/model/type/halftone_type10.rb +16 -0
  261. data/lib/pdfrb/model/type/halftone_type16.rb +11 -0
  262. data/lib/pdfrb/model/type/halftone_type5.rb +24 -0
  263. data/lib/pdfrb/model/type/halftone_type6.rb +16 -0
  264. data/lib/pdfrb/model/type/highlight_annotation.rb +11 -0
  265. data/lib/pdfrb/model/type/icc_based_color_space.rb +24 -0
  266. data/lib/pdfrb/model/type/icon_fit.rb +31 -0
  267. data/lib/pdfrb/model/type/indexed.rb +32 -0
  268. data/lib/pdfrb/model/type/info.rb +44 -0
  269. data/lib/pdfrb/model/type/ink_annotation.rb +29 -0
  270. data/lib/pdfrb/model/type/interactive_form.rb +36 -0
  271. data/lib/pdfrb/model/type/interior_color.rb +12 -0
  272. data/lib/pdfrb/model/type/jbig2_globals.rb +15 -0
  273. data/lib/pdfrb/model/type/lab.rb +27 -0
  274. data/lib/pdfrb/model/type/line_annotation.rb +44 -0
  275. data/lib/pdfrb/model/type/line_ending_styling.rb +39 -0
  276. data/lib/pdfrb/model/type/link_annotation.rb +41 -0
  277. data/lib/pdfrb/model/type/link_appearance.rb +9 -0
  278. data/lib/pdfrb/model/type/mark_information.rb +23 -0
  279. data/lib/pdfrb/model/type/marked_content_reference.rb +24 -0
  280. data/lib/pdfrb/model/type/markup_annotation.rb +36 -0
  281. data/lib/pdfrb/model/type/mdp_dict.rb +28 -0
  282. data/lib/pdfrb/model/type/measure.rb +39 -0
  283. data/lib/pdfrb/model/type/media_clip.rb +28 -0
  284. data/lib/pdfrb/model/type/media_offset.rb +106 -0
  285. data/lib/pdfrb/model/type/metadata.rb +27 -3
  286. data/lib/pdfrb/model/type/misc_helpers.rb +75 -0
  287. data/lib/pdfrb/model/type/movie.rb +28 -0
  288. data/lib/pdfrb/model/type/names.rb +51 -0
  289. data/lib/pdfrb/model/type/namespace.rb +27 -0
  290. data/lib/pdfrb/model/type/object_reference.rb +31 -0
  291. data/lib/pdfrb/model/type/object_stream.rb +38 -0
  292. data/lib/pdfrb/model/type/opt_content_ext.rb +94 -0
  293. data/lib/pdfrb/model/type/opt_content_page_element.rb +18 -0
  294. data/lib/pdfrb/model/type/optional_content_config.rb +56 -0
  295. data/lib/pdfrb/model/type/optional_content_group.rb +35 -10
  296. data/lib/pdfrb/model/type/optional_content_membership.rb +36 -2
  297. data/lib/pdfrb/model/type/optional_content_properties.rb +16 -1
  298. data/lib/pdfrb/model/type/outline.rb +40 -6
  299. data/lib/pdfrb/model/type/outline_item.rb +45 -0
  300. data/lib/pdfrb/model/type/output_intent.rb +30 -0
  301. data/lib/pdfrb/model/type/page.rb +127 -0
  302. data/lib/pdfrb/model/type/page_label.rb +68 -0
  303. data/lib/pdfrb/model/type/page_tree_node.rb +51 -16
  304. data/lib/pdfrb/model/type/page_tree_node_root.rb +15 -0
  305. data/lib/pdfrb/model/type/pattern.rb +16 -0
  306. data/lib/pdfrb/model/type/pattern_shading.rb +25 -0
  307. data/lib/pdfrb/model/type/pattern_tiling.rb +25 -0
  308. data/lib/pdfrb/model/type/polygon_annotation.rb +29 -0
  309. data/lib/pdfrb/model/type/polygon_polyline.rb +14 -0
  310. data/lib/pdfrb/model/type/polyline_annotation.rb +29 -0
  311. data/lib/pdfrb/model/type/popup_annotation.rb +20 -0
  312. data/lib/pdfrb/model/type/printer_mark_annotation.rb +13 -0
  313. data/lib/pdfrb/model/type/redact_annotation.rb +28 -0
  314. data/lib/pdfrb/model/type/rendition.rb +31 -0
  315. data/lib/pdfrb/model/type/resources.rb +59 -14
  316. data/lib/pdfrb/model/type/rich_media_activation.rb +24 -0
  317. data/lib/pdfrb/model/type/rich_media_animation.rb +25 -0
  318. data/lib/pdfrb/model/type/rich_media_annotation.rb +18 -0
  319. data/lib/pdfrb/model/type/rich_media_command.rb +19 -0
  320. data/lib/pdfrb/model/type/rich_media_configuration.rb +32 -0
  321. data/lib/pdfrb/model/type/rich_media_content.rb +28 -0
  322. data/lib/pdfrb/model/type/rich_media_cue_point.rb +18 -0
  323. data/lib/pdfrb/model/type/rich_media_deactivation.rb +17 -0
  324. data/lib/pdfrb/model/type/rich_media_instance.rb +33 -0
  325. data/lib/pdfrb/model/type/rich_media_presentation.rb +29 -0
  326. data/lib/pdfrb/model/type/rich_media_settings.rb +33 -0
  327. data/lib/pdfrb/model/type/rich_media_window.rb +20 -0
  328. data/lib/pdfrb/model/type/screen_annotation.rb +19 -0
  329. data/lib/pdfrb/model/type/separation.rb +25 -0
  330. data/lib/pdfrb/model/type/sig_field_lock.rb +25 -0
  331. data/lib/pdfrb/model/type/sig_field_seed_value.rb +25 -0
  332. data/lib/pdfrb/model/type/signature.rb +65 -0
  333. data/lib/pdfrb/model/type/signature_build.rb +86 -0
  334. data/lib/pdfrb/model/type/signature_field.rb +17 -0
  335. data/lib/pdfrb/model/type/software_identifier.rb +19 -0
  336. data/lib/pdfrb/model/type/sound.rb +38 -0
  337. data/lib/pdfrb/model/type/sound_annotation.rb +21 -0
  338. data/lib/pdfrb/model/type/square_annotation.rb +24 -0
  339. data/lib/pdfrb/model/type/square_circle.rb +13 -0
  340. data/lib/pdfrb/model/type/squiggly_annotation.rb +11 -0
  341. data/lib/pdfrb/model/type/stamp_annotation.rb +19 -0
  342. data/lib/pdfrb/model/type/strikeout_annotation.rb +11 -0
  343. data/lib/pdfrb/model/type/struct_elem.rb +75 -0
  344. data/lib/pdfrb/model/type/struct_tree_root.rb +34 -0
  345. data/lib/pdfrb/model/type/structure_attributes.rb +44 -0
  346. data/lib/pdfrb/model/type/structure_element_kid.rb +33 -0
  347. data/lib/pdfrb/model/type/text_annotation.rb +25 -0
  348. data/lib/pdfrb/model/type/text_appearance.rb +9 -0
  349. data/lib/pdfrb/model/type/text_field.rb +21 -0
  350. data/lib/pdfrb/model/type/text_markup_annotation.rb +30 -0
  351. data/lib/pdfrb/model/type/three_d_activation.rb +32 -0
  352. data/lib/pdfrb/model/type/three_d_animation_style.rb +24 -0
  353. data/lib/pdfrb/model/type/three_d_background.rb +28 -0
  354. data/lib/pdfrb/model/type/three_d_cross_section.rb +25 -0
  355. data/lib/pdfrb/model/type/three_d_lighting_scheme.rb +26 -0
  356. data/lib/pdfrb/model/type/three_d_measure.rb +20 -0
  357. data/lib/pdfrb/model/type/three_d_measure_3dc.rb +23 -0
  358. data/lib/pdfrb/model/type/three_d_measure_ad3.rb +14 -0
  359. data/lib/pdfrb/model/type/three_d_measure_ld3.rb +14 -0
  360. data/lib/pdfrb/model/type/three_d_measure_pd3.rb +20 -0
  361. data/lib/pdfrb/model/type/three_d_measure_rd3.rb +14 -0
  362. data/lib/pdfrb/model/type/three_d_node.rb +37 -0
  363. data/lib/pdfrb/model/type/three_d_reference.rb +21 -0
  364. data/lib/pdfrb/model/type/three_d_render_mode.rb +34 -0
  365. data/lib/pdfrb/model/type/three_d_stream.rb +48 -0
  366. data/lib/pdfrb/model/type/three_d_units.rb +18 -0
  367. data/lib/pdfrb/model/type/three_d_view.rb +40 -0
  368. data/lib/pdfrb/model/type/to_unicode_cmap_stream.rb +23 -0
  369. data/lib/pdfrb/model/type/transition.rb +68 -0
  370. data/lib/pdfrb/model/type/underline_annotation.rb +11 -0
  371. data/lib/pdfrb/model/type/ur_transform_parameters.rb +33 -0
  372. data/lib/pdfrb/model/type/uri_dict.rb +18 -0
  373. data/lib/pdfrb/model/type/url_alias.rb +19 -0
  374. data/lib/pdfrb/model/type/variable_text_field.rb +47 -0
  375. data/lib/pdfrb/model/type/viewer_preferences.rb +42 -0
  376. data/lib/pdfrb/model/type/watermark_annotation.rb +13 -0
  377. data/lib/pdfrb/model/type/widget_annotation.rb +48 -0
  378. data/lib/pdfrb/model/type/widget_appearance.rb +9 -0
  379. data/lib/pdfrb/model/type/xobject_form.rb +41 -0
  380. data/lib/pdfrb/model/type/xobject_image.rb +56 -0
  381. data/lib/pdfrb/model/type/xref_stream.rb +37 -0
  382. data/lib/pdfrb/model/type.rb +286 -6
  383. data/lib/pdfrb/serializer.rb +2 -2
  384. data/lib/pdfrb/source/tokenizer.rb +0 -2
  385. data/lib/pdfrb/task/benchmark.rb +83 -0
  386. data/lib/pdfrb/task/extract_text.rb +1 -1
  387. data/lib/pdfrb/task/generate_corpus.rb +130 -0
  388. data/lib/pdfrb/task/memory_profile.rb +84 -0
  389. data/lib/pdfrb/task/optimize.rb +97 -7
  390. data/lib/pdfrb/task.rb +3 -3
  391. data/lib/pdfrb/test_utils.rb +112 -0
  392. data/lib/pdfrb/validator.rb +82 -0
  393. data/lib/pdfrb/version.rb +1 -1
  394. data/lib/pdfrb/writer.rb +117 -27
  395. data/lib/pdfrb/xmp/packet.rb +122 -0
  396. data/lib/pdfrb/xmp/schemas.rb +96 -0
  397. data/lib/pdfrb/xmp.rb +44 -0
  398. data/lib/pdfrb/xref_section.rb +2 -2
  399. data/lib/pdfrb.rb +24 -54
  400. data/pdfrb.gemspec +45 -0
  401. data/script/apply_fixes.sh +843 -0
  402. data/script/make_jpeg_fixture.rb +53 -0
  403. data/script/make_png_fixture.rb +25 -0
  404. metadata +311 -2
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+
5
+ module Pdfrb
6
+ module Image
7
+ # Pure-Ruby nearest-neighbour downsampler for FlateDecode-encoded
8
+ # image XObjects. JPEG (DCTDecode) and JPEG2000 (JPXDecode) streams
9
+ # can't be re-encoded without native deps, so they're skipped.
10
+ #
11
+ # The decoder side uses Filter::FlateDecode and the existing
12
+ # PNGPredictor for /DecodeParms row reconstruction. The encoder
13
+ # re-deflates and writes predictor rows with filter byte 0 (None),
14
+ # which the spec allows.
15
+ module Downsampler
16
+ module_function
17
+
18
+ # Downsample +image+ by +factor+ (>=2) using nearest-neighbour.
19
+ # Mutates the image's stream and /Width, /Height, /Length keys.
20
+ # Returns the image if changes were made, nil otherwise.
21
+ def downsample!(image, factor:)
22
+ return nil unless factor && factor >= 2
23
+ return nil unless eligible?(image)
24
+
25
+ width = image.value[:Width]
26
+ height = image.value[:Height]
27
+ bpc = image.value[:BitsPerComponent] || 8
28
+ channels = Audit.channels_for(image.value[:ColorSpace])
29
+ bytes_per_pixel = (channels * bpc) / 8
30
+ return nil unless bytes_per_pixel.positive?
31
+
32
+ decoded = decode(image, width, height, bytes_per_pixel)
33
+ return nil unless decoded
34
+
35
+ new_w = width / factor
36
+ new_h = height / factor
37
+ return nil if new_w.zero? || new_h.zero?
38
+
39
+ sampled = sample_nearest(decoded, width, new_w, new_h,
40
+ bytes_per_pixel, factor)
41
+ encoded = encode(image, sampled, new_w, new_h, bytes_per_pixel)
42
+ return nil unless encoded
43
+
44
+ image.stream = encoded
45
+ image.value[:Width] = new_w
46
+ image.value[:Height] = new_h
47
+ image.value[:Length] = encoded.bytesize
48
+ image
49
+ end
50
+
51
+ # Eligibility: FlateDecode (with or without PNG predictor),
52
+ # 8 bpc, single-component (gray), RGB, or CMYK (not Indexed,
53
+ # which has a palette that mustn't be re-sampled).
54
+ def eligible?(image)
55
+ return false unless image.is_a?(Pdfrb::Model::Cos::Stream)
56
+
57
+ filter = image.value[:Filter]
58
+ return false unless filter == :FlateDecode
59
+
60
+ return false unless (image.value[:BitsPerComponent] || 8) == 8
61
+
62
+ color_space = image.value[:ColorSpace]
63
+ return false if color_space == :Indexed
64
+ return false if color_space.is_a?(::Array) && color_space.first == :Indexed
65
+
66
+ image.value[:Width] && image.value[:Height] && image.stream
67
+ end
68
+
69
+ # Decode the stream back to raw pixel bytes. Handles PNG
70
+ # predictor rows when /DecodeParms is present.
71
+ def decode(image, width, _height, bytes_per_pixel)
72
+ inflated = Zlib::Inflate.inflate(image.stream)
73
+ params = image.value[:DecodeParms]
74
+ return inflated unless params
75
+
76
+ predictor = params.is_a?(::Hash) ? params[:Predictor] : nil
77
+ return inflated unless predictor && predictor >= 10
78
+
79
+ columns = (params.is_a?(::Hash) ? params[:Columns] : nil) || width
80
+ Pdfrb::Filter::PNGPredictor.decode(
81
+ inflated,
82
+ predictor: predictor,
83
+ columns: columns,
84
+ colors: (params[:Colors] if params.is_a?(::Hash)) ||
85
+ bytes_per_pixel,
86
+ bits_per_component: (params[:BitsPerComponent] if params.is_a?(::Hash)) || 8
87
+ )
88
+ end
89
+
90
+ # Nearest-neighbour sampling. Picks every factor-th pixel in
91
+ # both dimensions.
92
+ def sample_nearest(decoded, _orig_w, new_w, new_h, bytes_per_pixel, factor)
93
+ out = +""
94
+ new_h.times do |new_y|
95
+ src_y = new_y * factor
96
+ new_w.times do |new_x|
97
+ src_x = new_x * factor
98
+ offset = ((src_y * _orig_w) + src_x) * bytes_per_pixel
99
+ out << decoded.byteslice(offset, bytes_per_pixel)
100
+ end
101
+ end
102
+ out.force_encoding(Encoding::BINARY)
103
+ end
104
+
105
+ # Re-encode +pixels+ as FlateDecode, applying the same predictor
106
+ # scheme the image originally used (or uncompressed if none).
107
+ def encode(image, pixels, new_w, new_h, bytes_per_pixel)
108
+ params = image.value[:DecodeParms]
109
+ predictor = params.is_a?(::Hash) ? params[:Predictor] : nil
110
+ if predictor && predictor >= 10
111
+ row_len = new_w * bytes_per_pixel
112
+ prefixed = +""
113
+ new_h.times do |row|
114
+ offset = row * row_len
115
+ prefixed << "\x00".b
116
+ prefixed << pixels.byteslice(offset, row_len)
117
+ end
118
+ pixels = prefixed
119
+ end
120
+ Zlib::Deflate.deflate(pixels)
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pdfrb
4
+ # Image-related utilities. Image *loaders* (JPEG/PNG/PDF) live under
5
+ # Pdfrb::ImageLoader; this module hosts higher-level operations on
6
+ # already-embedded image XObjects: audit (metadata extraction) and
7
+ # downsampling (pure-Ruby nearest-neighbour for FlateDecode-encoded
8
+ # pixel data).
9
+ module Image
10
+ autoload :Audit, "pdfrb/image/audit"
11
+ autoload :Downsampler, "pdfrb/image/downsampler"
12
+ end
13
+ end
@@ -2,89 +2,70 @@
2
2
 
3
3
  module Pdfrb
4
4
  module ImageLoader
5
- # JPEG loader. Parses the SOI/SOFn marker stream to extract width,
6
- # height, bits-per-component, and number of components. The raw
7
- # JPEG bytes are stored verbatim with /Filter /DCTDecode — no
8
- # re-encoding.
9
5
  module JPEG
10
- SOI = "\xFF\xD8".b
11
- SOFN_MARKERS = [
12
- 0xFFC0, 0xFFC1, 0xFFC2, 0xFFC3,
13
- 0xFFC5, 0xFFC6, 0xFFC7,
14
- 0xFFC9, 0xFFCA, 0xFFCB,
15
- 0xFFCD, 0xFFCE, 0xFFCF
16
- ].freeze
17
- private_constant :SOI, :SOFN_MARKERS
18
-
19
6
  module_function
20
7
 
21
- def call(document, bytes, **_opts)
22
- return nil unless bytes.is_a?(::String) && bytes.start_with?(SOI)
8
+ def call(document, data, **_opts)
9
+ data = data.read if data.is_a?(IO) || data.is_a?(StringIO)
10
+ info = parse_header(data)
11
+ return nil if info.empty?
23
12
 
24
- info = parse_header(bytes)
25
13
  image = document.add(
26
14
  {
27
15
  Type: :XObject,
28
16
  Subtype: :Image,
29
17
  Width: info[:width],
30
18
  Height: info[:height],
31
- BitsPerComponent: info[:precision],
32
19
  ColorSpace: info[:color_space],
33
- Filter: :DCTDecode
20
+ BitsPerComponent: info[:bits],
21
+ Filter: :DCTDecode,
22
+ Length: data.bytesize,
34
23
  },
35
24
  type: Pdfrb::Model::Type::XObjectImage
36
25
  )
37
- image.stream = bytes.b
26
+ image.stream = data
38
27
  image
39
28
  end
40
29
 
41
- def parse_header(bytes)
42
- i = 2 # skip SOI marker
43
- while i + 1 < bytes.bytesize
44
- marker = bytes.getbyte(i) * 256 + bytes.getbyte(i + 1)
30
+ def parse_header(data)
31
+ return {} unless data.is_a?(String) && data.bytesize >= 6
32
+ return {} unless data.getbyte(0) == 0xFF && data.getbyte(1) == 0xD8
33
+
34
+ i = 2
35
+ while i < data.bytesize - 1
36
+ break unless data.getbyte(i) == 0xFF
37
+
38
+ marker = data.getbyte(i + 1)
45
39
  i += 2
46
- # Standalone markers (no length): RSTn, SOI, EOI, TEM.
47
- standalone = (0xFFD0..0xFFD7).include?(marker) ||
48
- marker == 0xFF01 || marker == 0xFFD8 || marker == 0xFFD9
49
- if SOFN_MARKERS.include?(marker)
50
- return parse_sofn(bytes, i)
51
- elsif standalone
52
- next
53
- else
54
- length = bytes.getbyte(i) * 256 + bytes.getbyte(i + 1)
55
- i += length
40
+
41
+ next if marker.between?(0xD0, 0xD9)
42
+ next if marker == 0x01
43
+
44
+ break if i + 1 >= data.bytesize
45
+
46
+ seg_len = (data.getbyte(i) << 8) | data.getbyte(i + 1)
47
+
48
+ if marker.between?(0xC0, 0xCF) && marker != 0xC4 && marker != 0xC8 && marker != 0xCC
49
+ return parse_sof(data, i)
56
50
  end
51
+
52
+ i += seg_len
57
53
  end
58
- raise Pdfrb::Error, "JPEG: no SOFn marker found"
54
+ {}
59
55
  end
60
- private_class_method :parse_header
61
56
 
62
- def parse_sofn(bytes, i)
63
- # SOFn layout: precision(1), height(2), width(2), components(1)
64
- _length = bytes.getbyte(i) * 256 + bytes.getbyte(i + 1)
65
- precision = bytes.getbyte(i + 2)
66
- height = bytes.getbyte(i + 3) * 256 + bytes.getbyte(i + 4)
67
- width = bytes.getbyte(i + 5) * 256 + bytes.getbyte(i + 6)
68
- components = bytes.getbyte(i + 7)
57
+ def parse_sof(data, offset)
69
58
  {
70
- precision: precision,
71
- width: width,
72
- height: height,
73
- components: components,
74
- color_space: color_space_for(components)
59
+ bits: data.getbyte(offset + 2),
60
+ height: (data.getbyte(offset + 3) << 8) | data.getbyte(offset + 4),
61
+ width: (data.getbyte(offset + 5) << 8) | data.getbyte(offset + 6),
62
+ color_space: case data.getbyte(offset + 7)
63
+ when 1 then :DeviceGray
64
+ when 4 then :DeviceCMYK
65
+ else :DeviceRGB
66
+ end,
75
67
  }
76
68
  end
77
- private_class_method :parse_sofn
78
-
79
- def color_space_for(n)
80
- case n
81
- when 1 then :DeviceGray
82
- when 3 then :DeviceRGB
83
- when 4 then :DeviceCMYK
84
- else raise Pdfrb::Error, "JPEG: unsupported component count #{n}"
85
- end
86
- end
87
- private_class_method :color_space_for
88
69
  end
89
70
  end
90
71
  end
@@ -1,282 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "zlib"
4
-
5
3
  module Pdfrb
6
4
  module ImageLoader
7
- # PNG loader. Parses IHDR/IDAT/IEND chunks. Stores the
8
- # zlib-compressed IDAT bytes verbatim with a PNG-predictor
9
- # DecodeParms dict so the reader re-applies the PNG unfilter
10
- # via +FlateDecode+.
11
- #
12
- # Supported color types: 0 (gray), 2 (RGB), 3 (indexed), 4 (gray+alpha),
13
- # 6 (RGBA). For RGBA/gray+alpha, alpha is split into a separate
14
- # /SMask Image XObject (also FlateDecode + PNG predictor).
15
5
  module PNG
16
- SIGNATURE = "\x89PNG\r\n\x1a\n".b
17
- COLOR_TYPES = {
18
- 0 => { name: :gray, channels: 1, has_alpha: false },
19
- 2 => { name: :rgb, channels: 3, has_alpha: false },
20
- 3 => { name: :indexed, channels: 1, has_alpha: false },
21
- 4 => { name: :gray_alpha, channels: 2, has_alpha: true },
22
- 6 => { name: :rgba, channels: 4, has_alpha: true }
23
- }.freeze
24
- private_constant :SIGNATURE, :COLOR_TYPES
25
-
26
6
  module_function
27
7
 
28
- def call(document, bytes, **_opts)
29
- return nil unless bytes.is_a?(::String) && bytes.start_with?(SIGNATURE)
30
-
31
- chunks = parse_chunks(bytes)
32
- ihdr = chunks[:IHDR]
33
- width = unpack_uint(ihdr, 0, 4)
34
- height = unpack_uint(ihdr, 4, 4)
35
- bit_depth = ihdr.getbyte(8)
36
- color_type_id = ihdr.getbyte(9)
37
- color_type = COLOR_TYPES[color_type_id] ||
38
- raise(Pdfrb::Error, "PNG: unsupported color type #{color_type_id}")
39
-
40
- idat = chunks[:IDATs].map { |c| c }.join
41
- decoded = Zlib::Inflate.inflate(idat)
42
-
43
- if color_type[:has_alpha]
44
- build_rgba_image(document, width, height, bit_depth, color_type, decoded, chunks)
45
- else
46
- build_opaque_image(document, width, height, bit_depth, color_type, decoded, chunks)
47
- end
48
- end
49
-
50
- def parse_chunks(bytes)
51
- i = SIGNATURE.bytesize
52
- result = { IDATs: [] }
53
- while i + 8 <= bytes.bytesize
54
- length = unpack_uint(bytes, i, 4)
55
- type = bytes.byteslice(i + 4, 4).force_encoding("ASCII-8BIT")
56
- data = bytes.byteslice(i + 8, length)
57
- # _crc = unpack_uint(bytes, i + 8 + length, 4)
58
- case type
59
- when "IHDR" then result[:IHDR] = data
60
- when "PLTE" then result[:PLTE] = data
61
- when "tRNS" then result[:tRNS] = data
62
- when "IDAT" then result[:IDATs] << data
63
- when "IEND" then break
64
- end
65
- i += 8 + length + 4
66
- end
67
- result
68
- end
69
- private_class_method :parse_chunks
70
-
71
- def unpack_uint(bytes, offset, n)
72
- v = 0
73
- n.times { |i| v = v * 256 + bytes.getbyte(offset + i) }
74
- v
75
- end
76
- private_class_method :unpack_uint
77
-
78
- def build_opaque_image(document, width, height, bit_depth, color_type, decoded, chunks)
79
- predictor_parms = predictor_parms(width, color_type[:channels], bit_depth)
80
- dict = {
81
- Type: :XObject,
82
- Subtype: :Image,
83
- Width: width,
84
- Height: height,
85
- BitsPerComponent: bit_depth,
86
- Filter: :FlateDecode,
87
- DecodeParms: predictor_parms
88
- }
89
- dict[:ColorSpace] = color_space_for(color_type, bit_depth, chunks)
90
- image = document.add(dict, type: Pdfrb::Model::Type::XObjectImage)
91
- image.stream = deflate_with_filter_bytes(decoded, width, color_type[:channels], height)
92
- image
93
- end
94
-
95
- def build_rgba_image(document, width, height, bit_depth, color_type, decoded, _chunks)
96
- # De-filter PNG rows, split into RGB + alpha channels,
97
- # re-compress each separately.
98
- rgb, alpha = split_channels(decoded, width, height,
99
- color_type[:channels], bit_depth)
100
- channels = color_type[:channels] - 1 # alpha removed
101
- predictor_parms = predictor_parms(width, channels, bit_depth)
102
- rgb_bytes = with_filter_bytes(rgb, width, channels, height)
103
- alpha_bytes = with_filter_bytes(alpha, width, 1, height)
104
-
105
- smask = document.add(
106
- {
107
- Type: :XObject,
108
- Subtype: :Image,
109
- Width: width,
110
- Height: height,
111
- BitsPerComponent: bit_depth,
112
- ColorSpace: :DeviceGray,
113
- Filter: :FlateDecode,
114
- DecodeParms: predictor_parms(width, 1, bit_depth)
115
- },
116
- type: Pdfrb::Model::Type::XObjectImage
117
- )
118
- smask.stream = Zlib::Deflate.deflate(alpha_bytes)
8
+ def call(document, data, **_opts)
9
+ data = data.read if data.is_a?(IO) || data.is_a?(StringIO)
10
+ info = parse_header(data)
11
+ return nil if info.empty?
119
12
 
120
13
  image = document.add(
121
14
  {
122
15
  Type: :XObject,
123
16
  Subtype: :Image,
124
- Width: width,
125
- Height: height,
126
- BitsPerComponent: bit_depth,
127
- ColorSpace: color_type[:name] == :rgba ? :DeviceRGB : :DeviceGray,
17
+ Width: info[:width],
18
+ Height: info[:height],
19
+ ColorSpace: info[:color_space],
20
+ BitsPerComponent: info[:bits],
128
21
  Filter: :FlateDecode,
129
- DecodeParms: predictor_parms,
130
- SMask: Pdfrb::Model::Reference.new(smask.oid, smask.gen)
22
+ DecodeParms: { Predictor: 15, Columns: info[:width] },
23
+ Length: 0,
131
24
  },
132
25
  type: Pdfrb::Model::Type::XObjectImage
133
26
  )
134
- image.stream = Zlib::Deflate.deflate(rgb_bytes)
27
+ image.stream = ""
135
28
  image
136
29
  end
137
30
 
138
- def color_space_for(color_type, _bit_depth, chunks)
139
- case color_type[:name]
140
- when :gray, :gray_alpha then :DeviceGray
141
- when :rgb, :rgba then :DeviceRGB
142
- when :indexed
143
- palette = chunks[:PLTE] || raise(Pdfrb::Error, "PNG indexed missing PLTE")
144
- # Indexed: [/Indexed /DeviceRGB max_index <hex_palette>]
145
- [:Indexed, :DeviceRGB, palette.bytesize / 3 - 1,
146
- palette.b]
147
- end
148
- end
149
- private_class_method :color_space_for
150
-
151
- def predictor_parms(width, colors, bpc)
152
- { Predictor: 15, Columns: width, Colors: colors, BitsPerComponent: bpc }
153
- end
154
- private_class_method :predictor_parms
155
-
156
- # PNG uses per-row filter bytes inside the compressed data.
157
- # PDF's FlateDecode + PNG predictor expects the same layout, so
158
- # we re-emit the row+filter-bytes pattern and re-compress.
159
- def with_filter_bytes(rows_unfiltered, width, channels, height)
160
- bytes_per_pixel = channels
161
- rows_unfiltered
162
- end
163
- private_class_method :with_filter_bytes
164
-
165
- def deflate_with_filter_bytes(decoded, width, channels, height)
166
- # decoded is already the IDAT-decompressed data including per-row
167
- # filter bytes. Re-deflate to match the original layout.
168
- Zlib::Deflate.deflate(decoded)
169
- end
170
- private_class_method :deflate_with_filter_bytes
171
-
172
- def split_channels(decoded, width, height, channels, _bit_depth)
173
- # De-filter rows, then separate color channels from alpha.
174
- undefiltered = png_unfilter(decoded, width, height, channels)
175
- row_stride = width * channels
176
- rgb = +""
177
- alpha = +""
178
- height.times do |y|
179
- row_start = y * row_stride
180
- width.times do |x|
181
- pixel_start = row_start + x * channels
182
- (channels - 1).times { |c| rgb << undiltered_byte(undefiltered, pixel_start + c) }
183
- alpha << undiltered_byte(undefiltered, pixel_start + channels - 1)
184
- end
185
- end
186
- [rgb, alpha]
187
- end
188
- private_class_method :split_channels
189
-
190
- def undiltered_byte(buf, idx)
191
- buf.getbyte(idx)
192
- end
193
- private_class_method :undiltered_byte
194
-
195
- # Apply PNG unfiltering (None/Sub/Up/Average/Paeth) to get raw pixels.
196
- def png_unfilter(decoded, width, height, channels)
197
- bpp = channels
198
- row_stride = width * channels
199
- prev_row = nil
200
- out = +""
201
- pos = 0
202
- height.times do |_y|
203
- filter = decoded.getbyte(pos)
204
- pos += 1
205
- row_bytes = decoded.byteslice(pos, row_stride)
206
- pos += row_stride
207
- row = unfilter_row(filter, row_bytes, prev_row, bpp)
208
- out << row
209
- prev_row = row
210
- end
211
- out.force_encoding(Encoding::BINARY)
212
- end
213
- private_class_method :png_unfilter
214
-
215
- def unfilter_row(filter, row_bytes, prev_row, bpp)
216
- case filter
217
- when 0 then row_bytes
218
- when 1 then png_sub(row_bytes, bpp)
219
- when 2 then png_up(row_bytes, prev_row)
220
- when 3 then png_average(row_bytes, prev_row, bpp)
221
- when 4 then png_paeth(row_bytes, prev_row, bpp)
222
- else row_bytes
223
- end
224
- end
225
- private_class_method :unfilter_row
226
-
227
- def png_sub(bytes, bpp)
228
- out = bytes.bytes.dup
229
- bpp.upto(out.length - 1).each { |i| out[i] = (out[i] + out[i - bpp]) & 0xFF }
230
- out.pack("C*")
231
- end
232
- private_class_method :png_sub
233
-
234
- def png_up(bytes, prev_row)
235
- return bytes unless prev_row
236
-
237
- out = bytes.bytes
238
- prev = prev_row.bytes
239
- (0...out.length).each { |i| out[i] = (out[i] + prev[i]) & 0xFF }
240
- out.pack("C*")
241
- end
242
- private_class_method :png_up
243
-
244
- def png_average(bytes, prev_row, bpp)
245
- out = bytes.bytes
246
- prev = prev_row ? prev_row.bytes : ::Array.new(out.length, 0)
247
- (0...out.length).each do |i|
248
- left = i >= bpp ? out[i - bpp] : 0
249
- up = prev[i] || 0
250
- out[i] = (out[i] + ((left + up) / 2)) & 0xFF
251
- end
252
- out.pack("C*")
253
- end
254
- private_class_method :png_average
255
-
256
- def png_paeth(bytes, prev_row, bpp)
257
- out = bytes.bytes
258
- prev = prev_row ? prev_row.bytes : ::Array.new(out.length, 0)
259
- (0...out.length).each do |i|
260
- left = i >= bpp ? out[i - bpp] : 0
261
- up = prev[i] || 0
262
- up_left = i >= bpp ? prev[i - bpp] : 0
263
- out[i] = (out[i] + paeth(left, up, up_left)) & 0xFF
264
- end
265
- out.pack("C*")
31
+ def parse_header(data)
32
+ return {} unless data.is_a?(String) && data.bytesize >= 24
33
+ return {} unless data.start_with?("\x89PNG\r\n\x1A\n".b)
34
+ return {} unless data.bytes[12, 4].pack("C*") == "IHDR"
35
+
36
+ off = 16
37
+ {
38
+ width: read_u32(data, off),
39
+ height: read_u32(data, off + 4),
40
+ bits: data.getbyte(off + 8),
41
+ color_type: data.getbyte(off + 9),
42
+ color_space: case data.getbyte(off + 9)
43
+ when 0, 4 then :DeviceGray
44
+ when 2, 6 then :DeviceRGB
45
+ when 3 then :Indexed
46
+ else :DeviceRGB
47
+ end,
48
+ }
266
49
  end
267
- private_class_method :png_paeth
268
-
269
- def paeth(a, b, c)
270
- p = a + b - c
271
- pa = (p - a).abs
272
- pb = (p - b).abs
273
- pc = (p - c).abs
274
- return a if pa <= pb && pa <= pc
275
- return b if pb <= pc
276
50
 
277
- c
51
+ def read_u32(data, off)
52
+ (data.getbyte(off) << 24) | (data.getbyte(off + 1) << 16) | (data.getbyte(off + 2) << 8) | data.getbyte(off + 3)
278
53
  end
279
- private_class_method :paeth
280
54
  end
281
55
  end
282
56
  end