hexapdf 1.6.0 → 1.8.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 (287) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/LICENSE +1 -1
  4. data/examples/005-merging.rb +2 -1
  5. data/examples/032-acro_form_list_and_fill.rb +47 -0
  6. data/examples/033-text_extraction.rb +34 -0
  7. data/lib/hexapdf/cli/batch.rb +1 -1
  8. data/lib/hexapdf/cli/command.rb +1 -1
  9. data/lib/hexapdf/cli/debug_info.rb +1 -1
  10. data/lib/hexapdf/cli/files.rb +1 -1
  11. data/lib/hexapdf/cli/fonts.rb +6 -4
  12. data/lib/hexapdf/cli/form.rb +1 -1
  13. data/lib/hexapdf/cli/image2pdf.rb +1 -1
  14. data/lib/hexapdf/cli/images.rb +17 -17
  15. data/lib/hexapdf/cli/info.rb +3 -1
  16. data/lib/hexapdf/cli/inspect.rb +1 -1
  17. data/lib/hexapdf/cli/merge.rb +14 -2
  18. data/lib/hexapdf/cli/modify.rb +1 -1
  19. data/lib/hexapdf/cli/optimize.rb +1 -1
  20. data/lib/hexapdf/cli/split.rb +1 -1
  21. data/lib/hexapdf/cli/usage.rb +1 -1
  22. data/lib/hexapdf/cli/watermark.rb +1 -1
  23. data/lib/hexapdf/cli.rb +1 -1
  24. data/lib/hexapdf/composer.rb +1 -1
  25. data/lib/hexapdf/configuration.rb +10 -1
  26. data/lib/hexapdf/content/canvas.rb +2 -2
  27. data/lib/hexapdf/content/canvas_composer.rb +1 -1
  28. data/lib/hexapdf/content/color_space.rb +1 -1
  29. data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
  30. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
  31. data/lib/hexapdf/content/graphic_object/geom2d.rb +1 -1
  32. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  33. data/lib/hexapdf/content/graphic_object.rb +1 -1
  34. data/lib/hexapdf/content/graphics_state.rb +1 -1
  35. data/lib/hexapdf/content/operator.rb +1 -1
  36. data/lib/hexapdf/content/parser.rb +1 -1
  37. data/lib/hexapdf/content/processor.rb +1 -1
  38. data/lib/hexapdf/content/smart_text_extractor.rb +311 -0
  39. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  40. data/lib/hexapdf/content.rb +3 -1
  41. data/lib/hexapdf/data_dir.rb +1 -1
  42. data/lib/hexapdf/dictionary.rb +1 -1
  43. data/lib/hexapdf/dictionary_fields.rb +1 -1
  44. data/lib/hexapdf/digital_signature/cms_handler.rb +1 -1
  45. data/lib/hexapdf/digital_signature/handler.rb +1 -1
  46. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +1 -1
  47. data/lib/hexapdf/digital_signature/signature.rb +1 -1
  48. data/lib/hexapdf/digital_signature/signatures.rb +1 -1
  49. data/lib/hexapdf/digital_signature/signing/default_handler.rb +2 -16
  50. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +22 -9
  51. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +1 -1
  52. data/lib/hexapdf/digital_signature/signing.rb +1 -1
  53. data/lib/hexapdf/digital_signature/verification_result.rb +1 -1
  54. data/lib/hexapdf/digital_signature.rb +1 -1
  55. data/lib/hexapdf/document/annotations.rb +1 -1
  56. data/lib/hexapdf/document/destinations.rb +1 -1
  57. data/lib/hexapdf/document/files.rb +1 -1
  58. data/lib/hexapdf/document/fonts.rb +1 -1
  59. data/lib/hexapdf/document/images.rb +1 -1
  60. data/lib/hexapdf/document/layout.rb +1 -1
  61. data/lib/hexapdf/document/metadata.rb +1 -1
  62. data/lib/hexapdf/document/pages.rb +1 -1
  63. data/lib/hexapdf/document.rb +8 -4
  64. data/lib/hexapdf/encryption/aes.rb +1 -1
  65. data/lib/hexapdf/encryption/arc4.rb +1 -1
  66. data/lib/hexapdf/encryption/fast_aes.rb +1 -1
  67. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  68. data/lib/hexapdf/encryption/identity.rb +1 -1
  69. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  70. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  71. data/lib/hexapdf/encryption/security_handler.rb +1 -1
  72. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  73. data/lib/hexapdf/encryption.rb +1 -1
  74. data/lib/hexapdf/error.rb +1 -1
  75. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  76. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  77. data/lib/hexapdf/filter/brotli_decode.rb +88 -0
  78. data/lib/hexapdf/filter/crypt.rb +1 -1
  79. data/lib/hexapdf/filter/encryption.rb +1 -1
  80. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  81. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  82. data/lib/hexapdf/filter/pass_through.rb +1 -1
  83. data/lib/hexapdf/filter/predictor.rb +1 -1
  84. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  85. data/lib/hexapdf/filter.rb +2 -1
  86. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  87. data/lib/hexapdf/font/cmap/writer.rb +1 -1
  88. data/lib/hexapdf/font/cmap.rb +1 -1
  89. data/lib/hexapdf/font/encoding/base.rb +1 -1
  90. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  91. data/lib/hexapdf/font/encoding/glyph_list.rb +1 -1
  92. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  93. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
  94. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  95. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  96. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
  97. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  98. data/lib/hexapdf/font/encoding.rb +1 -1
  99. data/lib/hexapdf/font/invalid_glyph.rb +1 -1
  100. data/lib/hexapdf/font/true_type/builder.rb +2 -2
  101. data/lib/hexapdf/font/true_type/font.rb +14 -1
  102. data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
  103. data/lib/hexapdf/font/true_type/subsetter.rb +11 -6
  104. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  105. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
  106. data/lib/hexapdf/font/true_type/table/directory.rb +6 -1
  107. data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
  108. data/lib/hexapdf/font/true_type/table/head.rb +1 -1
  109. data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
  110. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
  111. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  112. data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
  113. data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
  114. data/lib/hexapdf/font/true_type/table/name.rb +1 -1
  115. data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
  116. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  117. data/lib/hexapdf/font/true_type/table.rb +1 -1
  118. data/lib/hexapdf/font/true_type.rb +2 -1
  119. data/lib/hexapdf/font/true_type_wrapper.rb +3 -3
  120. data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
  121. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  122. data/lib/hexapdf/font/type1/font.rb +1 -1
  123. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  124. data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
  125. data/lib/hexapdf/font/type1.rb +1 -1
  126. data/lib/hexapdf/font/type1_wrapper.rb +1 -1
  127. data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
  128. data/lib/hexapdf/font_loader/from_file.rb +5 -1
  129. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  130. data/lib/hexapdf/font_loader/variant_from_name.rb +1 -1
  131. data/lib/hexapdf/font_loader.rb +1 -1
  132. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  133. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  134. data/lib/hexapdf/image_loader/png.rb +1 -1
  135. data/lib/hexapdf/image_loader.rb +1 -1
  136. data/lib/hexapdf/importer.rb +1 -1
  137. data/lib/hexapdf/layout/box.rb +1 -1
  138. data/lib/hexapdf/layout/box_fitter.rb +1 -1
  139. data/lib/hexapdf/layout/column_box.rb +1 -1
  140. data/lib/hexapdf/layout/container_box.rb +1 -1
  141. data/lib/hexapdf/layout/frame.rb +1 -1
  142. data/lib/hexapdf/layout/image_box.rb +1 -1
  143. data/lib/hexapdf/layout/inline_box.rb +1 -1
  144. data/lib/hexapdf/layout/line.rb +1 -1
  145. data/lib/hexapdf/layout/list_box.rb +1 -1
  146. data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
  147. data/lib/hexapdf/layout/page_style.rb +1 -1
  148. data/lib/hexapdf/layout/style.rb +7 -3
  149. data/lib/hexapdf/layout/table_box.rb +1 -1
  150. data/lib/hexapdf/layout/text_box.rb +1 -1
  151. data/lib/hexapdf/layout/text_fragment.rb +1 -1
  152. data/lib/hexapdf/layout/text_layouter.rb +1 -1
  153. data/lib/hexapdf/layout/text_shaper.rb +1 -1
  154. data/lib/hexapdf/layout/width_from_polygon.rb +1 -1
  155. data/lib/hexapdf/layout.rb +1 -1
  156. data/lib/hexapdf/name_tree_node.rb +1 -1
  157. data/lib/hexapdf/number_tree_node.rb +1 -1
  158. data/lib/hexapdf/object.rb +1 -1
  159. data/lib/hexapdf/parser.rb +1 -1
  160. data/lib/hexapdf/pdf_array.rb +1 -1
  161. data/lib/hexapdf/rectangle.rb +1 -1
  162. data/lib/hexapdf/reference.rb +1 -1
  163. data/lib/hexapdf/revision.rb +1 -1
  164. data/lib/hexapdf/revisions.rb +1 -1
  165. data/lib/hexapdf/serializer.rb +3 -3
  166. data/lib/hexapdf/stream.rb +1 -1
  167. data/lib/hexapdf/task/dereference.rb +1 -1
  168. data/lib/hexapdf/task/import_pages.rb +185 -0
  169. data/lib/hexapdf/task/merge_acro_form.rb +1 -1
  170. data/lib/hexapdf/task/optimize.rb +1 -1
  171. data/lib/hexapdf/task/pdfa.rb +109 -2
  172. data/lib/hexapdf/task.rb +2 -1
  173. data/lib/hexapdf/test_utils.rb +1 -1
  174. data/lib/hexapdf/tokenizer.rb +1 -1
  175. data/lib/hexapdf/type/acro_form/appearance_generator.rb +1 -1
  176. data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
  177. data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
  178. data/lib/hexapdf/type/acro_form/field.rb +1 -1
  179. data/lib/hexapdf/type/acro_form/form.rb +5 -1
  180. data/lib/hexapdf/type/acro_form/java_script_actions.rb +1 -1
  181. data/lib/hexapdf/type/acro_form/signature_field.rb +1 -1
  182. data/lib/hexapdf/type/acro_form/text_field.rb +5 -3
  183. data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
  184. data/lib/hexapdf/type/acro_form.rb +1 -1
  185. data/lib/hexapdf/type/action.rb +1 -1
  186. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  187. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  188. data/lib/hexapdf/type/actions/launch.rb +1 -1
  189. data/lib/hexapdf/type/actions/set_ocg_state.rb +1 -1
  190. data/lib/hexapdf/type/actions/uri.rb +1 -1
  191. data/lib/hexapdf/type/actions.rb +1 -1
  192. data/lib/hexapdf/type/annotation.rb +1 -1
  193. data/lib/hexapdf/type/annotations/appearance_generator.rb +1 -1
  194. data/lib/hexapdf/type/annotations/border_effect.rb +1 -1
  195. data/lib/hexapdf/type/annotations/border_styling.rb +1 -1
  196. data/lib/hexapdf/type/annotations/circle.rb +1 -1
  197. data/lib/hexapdf/type/annotations/interior_color.rb +1 -1
  198. data/lib/hexapdf/type/annotations/line.rb +1 -1
  199. data/lib/hexapdf/type/annotations/line_ending_styling.rb +1 -1
  200. data/lib/hexapdf/type/annotations/link.rb +1 -1
  201. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  202. data/lib/hexapdf/type/annotations/polygon.rb +1 -1
  203. data/lib/hexapdf/type/annotations/polygon_polyline.rb +1 -1
  204. data/lib/hexapdf/type/annotations/polyline.rb +1 -1
  205. data/lib/hexapdf/type/annotations/square.rb +1 -1
  206. data/lib/hexapdf/type/annotations/square_circle.rb +1 -1
  207. data/lib/hexapdf/type/annotations/text.rb +1 -1
  208. data/lib/hexapdf/type/annotations/widget.rb +10 -1
  209. data/lib/hexapdf/type/annotations.rb +1 -1
  210. data/lib/hexapdf/type/catalog.rb +1 -1
  211. data/lib/hexapdf/type/cid_font.rb +1 -1
  212. data/lib/hexapdf/type/cmap.rb +1 -1
  213. data/lib/hexapdf/type/document_security_store.rb +80 -0
  214. data/lib/hexapdf/type/embedded_file.rb +1 -1
  215. data/lib/hexapdf/type/file_specification.rb +1 -1
  216. data/lib/hexapdf/type/font.rb +4 -4
  217. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  218. data/lib/hexapdf/type/font_simple.rb +1 -1
  219. data/lib/hexapdf/type/font_true_type.rb +1 -1
  220. data/lib/hexapdf/type/font_type0.rb +1 -1
  221. data/lib/hexapdf/type/font_type1.rb +1 -1
  222. data/lib/hexapdf/type/font_type3.rb +6 -1
  223. data/lib/hexapdf/type/form.rb +1 -1
  224. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  225. data/lib/hexapdf/type/icon_fit.rb +1 -1
  226. data/lib/hexapdf/type/image.rb +1 -1
  227. data/lib/hexapdf/type/info.rb +1 -1
  228. data/lib/hexapdf/type/mark_information.rb +1 -1
  229. data/lib/hexapdf/type/marked_content_reference.rb +1 -1
  230. data/lib/hexapdf/type/measure.rb +1 -1
  231. data/lib/hexapdf/type/metadata.rb +1 -1
  232. data/lib/hexapdf/type/names.rb +1 -1
  233. data/lib/hexapdf/type/namespace.rb +1 -1
  234. data/lib/hexapdf/type/object_reference.rb +1 -1
  235. data/lib/hexapdf/type/object_stream.rb +1 -1
  236. data/lib/hexapdf/type/optional_content_configuration.rb +1 -1
  237. data/lib/hexapdf/type/optional_content_group.rb +1 -1
  238. data/lib/hexapdf/type/optional_content_membership.rb +1 -1
  239. data/lib/hexapdf/type/optional_content_properties.rb +1 -1
  240. data/lib/hexapdf/type/outline.rb +1 -1
  241. data/lib/hexapdf/type/outline_item.rb +1 -1
  242. data/lib/hexapdf/type/output_intent.rb +1 -1
  243. data/lib/hexapdf/type/page.rb +12 -1
  244. data/lib/hexapdf/type/page_label.rb +1 -1
  245. data/lib/hexapdf/type/page_tree_node.rb +1 -1
  246. data/lib/hexapdf/type/resources.rb +1 -1
  247. data/lib/hexapdf/type/struct_elem.rb +1 -1
  248. data/lib/hexapdf/type/struct_tree_root.rb +1 -1
  249. data/lib/hexapdf/type/trailer.rb +1 -1
  250. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  251. data/lib/hexapdf/type/xref_stream.rb +1 -1
  252. data/lib/hexapdf/type.rb +2 -1
  253. data/lib/hexapdf/utils/bit_field.rb +1 -1
  254. data/lib/hexapdf/utils/bit_stream.rb +1 -1
  255. data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
  256. data/lib/hexapdf/utils/lru_cache.rb +1 -1
  257. data/lib/hexapdf/utils/math_helpers.rb +1 -1
  258. data/lib/hexapdf/utils/object_hash.rb +1 -1
  259. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  260. data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
  261. data/lib/hexapdf/utils.rb +1 -1
  262. data/lib/hexapdf/version.rb +2 -2
  263. data/lib/hexapdf/writer.rb +1 -1
  264. data/lib/hexapdf/xref_section.rb +1 -1
  265. data/lib/hexapdf.rb +1 -1
  266. data/test/data/pdfa/mismatching_glyph_widths_cidfont_type2.pdf +0 -0
  267. data/test/hexapdf/content/test_smart_text_extractor.rb +129 -0
  268. data/test/hexapdf/digital_signature/common.rb +19 -5
  269. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +29 -4
  270. data/test/hexapdf/digital_signature/test_signatures.rb +3 -3
  271. data/test/hexapdf/filter/test_brotli_decode.rb +34 -0
  272. data/test/hexapdf/font/test_true_type_wrapper.rb +2 -2
  273. data/test/hexapdf/font/true_type/table/test_directory.rb +5 -3
  274. data/test/hexapdf/font/true_type/test_builder.rb +9 -0
  275. data/test/hexapdf/font/true_type/test_font.rb +17 -3
  276. data/test/hexapdf/font/true_type/test_subsetter.rb +11 -9
  277. data/test/hexapdf/font_loader/test_from_file.rb +7 -0
  278. data/test/hexapdf/task/test_import_pages.rb +126 -0
  279. data/test/hexapdf/task/test_pdfa.rb +72 -0
  280. data/test/hexapdf/test_document.rb +13 -0
  281. data/test/hexapdf/test_serializer.rb +1 -1
  282. data/test/hexapdf/type/acro_form/test_form.rb +6 -0
  283. data/test/hexapdf/type/acro_form/test_text_field.rb +7 -1
  284. data/test/hexapdf/type/annotations/test_widget.rb +11 -0
  285. data/test/hexapdf/type/test_font_type3.rb +4 -0
  286. data/test/hexapdf/type/test_page.rb +8 -0
  287. metadata +25 -1
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -395,6 +395,17 @@ module HexaPDF
395
395
  Content::Parser.parse(contents, processor)
396
396
  end
397
397
 
398
+ # Extracts the layouted text from the page.
399
+ #
400
+ # See HexaPDF::Content::SmartTextExtractor.layout_text_runs for the available +options+.
401
+ def extract_text(**options)
402
+ processor = Content::SmartTextExtractor::TextRunProcessor.new
403
+ process_contents(processor)
404
+ box = box(:media)
405
+ Content::SmartTextExtractor.layout_text_runs(processor.text_runs, box.width, box.height,
406
+ **options)
407
+ end
408
+
398
409
  # Returns the index of the page in the page tree.
399
410
  def index
400
411
  idx = 0
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
data/lib/hexapdf/type.rb CHANGED
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -89,6 +89,7 @@ module HexaPDF
89
89
  autoload(:MarkedContentReference, 'hexapdf/type/marked_content_reference')
90
90
  autoload(:ObjectReference, 'hexapdf/type/object_reference')
91
91
  autoload(:Measure, 'hexapdf/type/measure')
92
+ autoload(:DocumentSecurityStore, 'hexapdf/type/document_security_store')
92
93
 
93
94
  end
94
95
 
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
data/lib/hexapdf/utils.rb CHANGED
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '1.6.0'
40
+ VERSION = '1.8.0'
41
41
 
42
42
  end
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
data/lib/hexapdf.rb CHANGED
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2025 Thomas Leitner
7
+ # Copyright (C) 2014-2026 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -0,0 +1,129 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/content/smart_text_extractor'
5
+ require 'hexapdf/document'
6
+
7
+ describe HexaPDF::Content::SmartTextExtractor::TextRunCollector::TextRun do
8
+ it "has various accessors" do
9
+ text_run = HexaPDF::Content::SmartTextExtractor::TextRunCollector::TextRun.new('s', 1, 2, 3, 5)
10
+ assert_equal('s', text_run.string)
11
+ assert_equal(2, text_run.width)
12
+ assert_equal(3, text_run.height)
13
+ end
14
+ end
15
+
16
+ describe HexaPDF::Content::SmartTextExtractor::TextRunProcessor do
17
+ it "turns glyphs into TextRun objects" do
18
+ processor = HexaPDF::Content::SmartTextExtractor::TextRunProcessor.new
19
+ doc = HexaPDF::Document.new
20
+ page = doc.pages.add
21
+ page.canvas.font('Helvetica', size: 10).
22
+ text('Te', at: [10, 500]).
23
+ text_matrix(0.866, -0.5, 0.5, 0.866, 0, 0).
24
+ text('Te')
25
+ page.process_contents(processor)
26
+ assert_equal([['T', 10, 497.75, 16.11, 509.31], ['e', 16.11, 497.75, 21.67, 509.31],
27
+ ["T", -1.125, -5.0035, 9.94626, 8.06246],
28
+ ["e", 4.16626, -7.7835, 14.761220000000002, 5.00746]],
29
+ processor.text_runs.map(&:to_a))
30
+ end
31
+ end
32
+
33
+ describe HexaPDF::Content::SmartTextExtractor do
34
+ def text_run(str, left, bottom, right, top)
35
+ HexaPDF::Content::SmartTextExtractor::TextRunCollector::TextRun.new(str, left, bottom, right, top)
36
+ end
37
+
38
+ def layout_runs(runs, width = 595, height = 842, **options)
39
+ runs = runs.map {|args| text_run(*args) }
40
+ HexaPDF::Content::SmartTextExtractor.layout_text_runs(runs, width, height, **options)
41
+ end
42
+
43
+ it "works for a page with no text" do
44
+ assert_equal('', layout_runs([]))
45
+ end
46
+
47
+ it "works for a single run on the left side of the page" do
48
+ assert_equal('test', layout_runs([['test', 0, 100, 20, 110]]))
49
+ end
50
+
51
+ it "works for a single run not on the left side of the page" do
52
+ assert_equal('test', layout_runs([['test', 50, 100, 70, 110]]))
53
+ end
54
+
55
+ it "preserves the relative indent" do
56
+ assert_equal("Hello\n World", layout_runs([['Hello', 50, 100, 70, 110],
57
+ ['World', 70, 80, 90, 100]]))
58
+ end
59
+
60
+ it "combines text runs if they have the same top/bottom and there is less than 1pt between them" do
61
+ x = +'Hello'
62
+ assert_equal('HelloWorld', layout_runs([[x, 50, 100, 60, 110],
63
+ ['World', 60, 100, 70, 110]]))
64
+ assert_equal('HelloWorld', x)
65
+ end
66
+
67
+ it "preserves the space between two runs" do
68
+ assert_equal('Hello World', layout_runs([['Hello', 50, 100, 70, 110],
69
+ ['World', 72, 100, 92, 110]]))
70
+ assert_equal('Hello World', layout_runs([['Hello', 50, 100, 70, 110],
71
+ ['World', 80, 100, 100, 110]]))
72
+ end
73
+
74
+ it "inserts a space after very narrow text parts if necessary" do
75
+ assert_equal('Hello World!', layout_runs([['Hello', 50, 100, 60, 110],
76
+ ['World!', 63, 100, 87, 110]]))
77
+ end
78
+
79
+ it "preserves the visual horizontal ordering of two runs" do
80
+ assert_equal('Hello World', layout_runs([['World', 72, 100, 92, 110],
81
+ ['Hello', 50, 100, 70, 110]]))
82
+ end
83
+
84
+ it "preserves the visual vertical ordering of two runs" do
85
+ assert_equal("Hello\nWorld", layout_runs([['World', 50, 80, 70, 100],
86
+ ['Hello', 50, 100, 70, 110]]))
87
+ end
88
+
89
+ it "inserts a single blank line between paragraphs" do
90
+ assert_equal("Hello\nWorld\n\nHere",
91
+ layout_runs([['Hello', 50, 100, 70, 110],
92
+ ['World', 50, 90, 70, 100],
93
+ ['Here', 50, 65, 66, 75]]))
94
+ end
95
+
96
+ it "inserts multiply lines for large gaps between paragraphs" do
97
+ assert_equal("Hello\nWorld\nHere\n\n\n\n\n\n\nFoot",
98
+ layout_runs([['Hello', 50, 100, 70, 110],
99
+ ['World', 50, 90, 70, 100],
100
+ ['Here', 50, 80, 70, 90],
101
+ ['Foot', 50, 10, 66, 20]]))
102
+ end
103
+
104
+ it "ignores outliers when calculating the normal line spacing" do
105
+ assert_equal("Hello\nWorld\n\n\n\nHere",
106
+ layout_runs([['Hello', 50, 100, 70, 110],
107
+ ['World', 50, 90, 70, 100],
108
+ ['Here', 50, 50, 70, 60]]))
109
+ end
110
+
111
+ it "can use a different line_tolerance_factor" do
112
+ assert_equal("HelloWorld",
113
+ layout_runs([['Hello', 50, 100, 70, 110],
114
+ ['World', 50, 90, 70, 100]], line_tolerance_factor: 1))
115
+ end
116
+
117
+ it "can use a different paragraph_distance_threshold" do
118
+ assert_equal("Hello\n\nWorld",
119
+ layout_runs([['Hello', 50, 100, 70, 110],
120
+ ['World', 50, 90, 70, 100]], paragraph_distance_threshold: 1))
121
+ end
122
+
123
+ it "can use a different large_distance_threshold" do
124
+ assert_equal("Hello\nWorld\n\nHere",
125
+ layout_runs([['Hello', 50, 100, 70, 110],
126
+ ['World', 50, 90, 70, 100],
127
+ ['Here', 50, 50, 66, 60]], large_distance_threshold: 8))
128
+ end
129
+ end
@@ -13,7 +13,7 @@ module HexaPDF
13
13
  @ca_certificate ||=
14
14
  begin
15
15
  cert = create_cert(name: '/C=AT/O=HexaPDF/CN=HexaPDF Test Root CA', serial: 0,
16
- public_key: ca_key.public_key)
16
+ public_key: ca_key)
17
17
  add_extensions(cert, cert, ca_key, is_ca: true, key_usage: 'cRLSign,keyCertSign')
18
18
  cert
19
19
  end
@@ -27,7 +27,7 @@ module HexaPDF
27
27
  @signer_certificate ||=
28
28
  begin
29
29
  cert = create_cert(name: '/CN=RSA signer/DC=gettalong', serial: 2,
30
- public_key: signer_key.public_key, issuer: ca_certificate)
30
+ public_key: signer_key, issuer: ca_certificate)
31
31
  add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature')
32
32
  cert
33
33
  end
@@ -37,7 +37,7 @@ module HexaPDF
37
37
  @non_repudiation_signer_certificate ||=
38
38
  begin
39
39
  cert = create_cert(name: '/CN=Non repudiation signer/DC=gettalong', serial: 2,
40
- public_key: signer_key.public_key, issuer: ca_certificate)
40
+ public_key: signer_key, issuer: ca_certificate)
41
41
  add_extensions(cert, ca_certificate, ca_key, key_usage: 'nonRepudiation')
42
42
  cert
43
43
  end
@@ -51,7 +51,21 @@ module HexaPDF
51
51
  @dsa_signer_certificate ||=
52
52
  begin
53
53
  cert = create_cert(name: '/CN=DSA signer/DC=gettalong', serial: 3,
54
- public_key: dsa_signer_key.public_key, issuer: ca_certificate)
54
+ public_key: dsa_signer_key, issuer: ca_certificate)
55
+ add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature')
56
+ cert
57
+ end
58
+ end
59
+
60
+ def ecdsa_signer_key
61
+ @ecdsa_signer_key ||= OpenSSL::PKey::EC.generate('sect163k1')
62
+ end
63
+
64
+ def ecdsa_signer_certificate
65
+ @ecdsa_signer_certificate ||=
66
+ begin
67
+ cert = create_cert(name: '/CN=ECDSA signer/DC=gettalong', serial: 4,
68
+ public_key: ecdsa_signer_key, issuer: ca_certificate)
55
69
  add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature')
56
70
  cert
57
71
  end
@@ -61,7 +75,7 @@ module HexaPDF
61
75
  @timestamp_certificate ||=
62
76
  begin
63
77
  cert = create_cert(name: '/CN=timestamp/DC=gettalong', serial: 3,
64
- public_key: signer_key.public_key, issuer: ca_certificate)
78
+ public_key: signer_key, issuer: ca_certificate)
65
79
  add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature',
66
80
  extended_key_usage: 'timeStamping')
67
81
  cert
@@ -154,10 +154,35 @@ describe HexaPDF::DigitalSignature::Signing::SignedDataCreator do
154
154
  assert_equal(CERTIFICATES.signer_key.sign('SHA256', to_sign), @structure.value[5].value)
155
155
  end
156
156
 
157
- it "fails if the signature algorithm is not supported" do
158
- @signed_data.certificate = CERTIFICATES.dsa_signer_certificate
159
- @signed_data.key = CERTIFICATES.dsa_signer_key
160
- assert_raises(HexaPDF::Error) { @signed_data.create("data") }
157
+ describe "DSA key pair" do
158
+ before do
159
+ @signed_data.certificate = CERTIFICATES.dsa_signer_certificate
160
+ @signed_data.key = CERTIFICATES.dsa_signer_key
161
+ end
162
+
163
+ it "works with a DSA key pair" do
164
+ @structure = @signed_data.create("data").value[1].value[4].value[0]
165
+ assert_equal('2.16.840.1.101.3.4.3.2', @structure.value[4].value[0].value)
166
+ assert_nil(@structure.value[4].value[1].value)
167
+ end
168
+
169
+ it "fails if the digest algorithm is not SHA256" do
170
+ @signed_data.digest_algorithm = 'sha512'
171
+ assert_raises { @signed_data.create("data") }
172
+ end
173
+ end
174
+
175
+ describe "ECDSA key pair" do
176
+ before do
177
+ @signed_data.certificate = CERTIFICATES.ecdsa_signer_certificate
178
+ @signed_data.key = CERTIFICATES.ecdsa_signer_key
179
+ end
180
+
181
+ it "works with an ECDSA key pair" do
182
+ structure = @signed_data.create("data").value[1].value[4].value[0]
183
+ assert_equal('1.2.840.10045.4.3.2', structure.value[4].value[0].value)
184
+ assert_nil(structure.value[4].value[1].value)
185
+ end
161
186
  end
162
187
 
163
188
  it "can use a different digest algorithm" do
@@ -70,7 +70,7 @@ describe HexaPDF::DigitalSignature::Signatures do
70
70
  end
71
71
  @doc.signatures.add(@io, @handler, write_options: {update_fields: false})
72
72
  sig = @doc.signatures.first
73
- assert_equal([0, 925, 925 + sig[:Contents].size * 2 + 2, 2455 + HexaPDF::VERSION.length],
73
+ assert_equal([0, 925, 925 + (sig[:Contents].size + 5) * 2 + 2, 2455 + HexaPDF::VERSION.length],
74
74
  sig[:ByteRange].value)
75
75
  assert_equal(:sig, sig[:key])
76
76
  assert_equal(:sig_field, @doc.acro_form.each_field.first[:key])
@@ -134,7 +134,7 @@ describe HexaPDF::DigitalSignature::Signatures do
134
134
  @doc.delete(7)
135
135
  sig = @doc.signatures.add(@io, @handler, write_options: {update_fields: false})
136
136
  l1 = 1030 + HexaPDF::VERSION.length
137
- assert_equal([0, l1, l1 + sig[:Contents].size * 2 + 2, 2437 + HexaPDF::VERSION.length],
137
+ assert_equal([0, l1, l1 + (sig[:Contents].size + 5) * 2 + 2, 2437 + HexaPDF::VERSION.length],
138
138
  sig[:ByteRange].value)
139
139
  end
140
140
 
@@ -143,7 +143,7 @@ describe HexaPDF::DigitalSignature::Signatures do
143
143
  field.create_widget(@doc.pages[0], Rect: [0, 0, 0, 0])
144
144
  sig = @doc.signatures.add(@io, @handler, signature: field, write_options: {update_fields: false})
145
145
  l1 = 3097 + HexaPDF::VERSION.length
146
- assert_equal([0, l1, l1 + sig[:Contents].size * 2 + 2, 374 + HexaPDF::VERSION.length],
146
+ assert_equal([0, l1, l1 + (sig[:Contents].size + 5) * 2 + 2, 374 + HexaPDF::VERSION.length],
147
147
  sig[:ByteRange].value)
148
148
  end
149
149
 
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require_relative 'common'
4
+ require 'hexapdf/filter/brotli_decode'
5
+
6
+ describe HexaPDF::Filter::BrotliDecode do
7
+ include CommonFilterTests
8
+
9
+ before do
10
+ @obj = HexaPDF::Filter::BrotliDecode
11
+ @all_test_cases = [["abcdefg".b, Brotli.deflate("abcdefg".b)]]
12
+ @decoded = @all_test_cases[0][0]
13
+ @encoded = @all_test_cases[0][1]
14
+ @encoded_predictor = "\e\r\x00\xF8%\x05\x02\xC2\xC2\x86\x00\x80%".b
15
+ @predictor_opts = {Predictor: 12}
16
+ end
17
+
18
+ describe "decoder" do
19
+ it "works for empty input" do
20
+ assert_equal('', collector(@obj.decoder(Fiber.new { "" })))
21
+ assert_equal('', collector(@obj.decoder(Fiber.new {})))
22
+ end
23
+
24
+ it "applies the Predictor after decoding" do
25
+ assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded_predictor), @predictor_opts)))
26
+ end
27
+ end
28
+
29
+ describe "encoder" do
30
+ it "applies the Predictor before encoding" do
31
+ assert_equal(@encoded_predictor, collector(@obj.encoder(feeder(@decoded), @predictor_opts)))
32
+ end
33
+ end
34
+ end
@@ -119,9 +119,9 @@ describe HexaPDF::Font::TrueTypeWrapper do
119
119
  assert_equal([3].pack('n'), code)
120
120
  end
121
121
 
122
- it "doesn't use char codes 13, 40, 41 and 92 because they would need to be escaped" do
122
+ it "doesn't use char codes 10, 13, 40, 41 and 92 because they would need to be escaped" do
123
123
  codes = 1.upto(93).map {|i| @font_wrapper.encode(@font_wrapper.glyph(i)) }.join
124
- assert_equal([1..12, 14..39, 42..91, 93..97].flat_map(&:to_a).pack('n*'), codes)
124
+ assert_equal([1..9, 11..12, 14..39, 42..91, 93..98].flat_map(&:to_a).pack('n*'), codes)
125
125
  end
126
126
 
127
127
  it "raises an error if an InvalidGlyph is encoded" do
@@ -5,8 +5,9 @@ require 'hexapdf/font/true_type/table/directory'
5
5
 
6
6
  describe HexaPDF::Font::TrueType::Table::Directory do
7
7
  before do
8
- io = StringIO.new("TEST\x00\x01\x00\x00\x00\x00\x00\x00" \
9
- "CUST----\x00\x00\x00\x1C\x00\x00\x00\x05ENTRY".b)
8
+ io = StringIO.new("TEST\x00\x02\x00\x00\x00\x00\x00\x00" \
9
+ "CUST----\x00\x00\x00\x2C\x00\x00\x00\x05" \
10
+ " TWO----\x00\x00\x00\x31\x00\x00\x00\x05ENTRYENTRY".b)
10
11
  @file = Object.new
11
12
  @file.define_singleton_method(:io) { io }
12
13
  @self_entry = HexaPDF::Font::TrueType::Table::Directory::SELF_ENTRY
@@ -23,8 +24,9 @@ describe HexaPDF::Font::TrueType::Table::Directory do
23
24
  entry = dir.entry('CUST')
24
25
  assert_equal('CUST', entry.tag)
25
26
  assert_equal('----'.unpack1('N'), entry.checksum)
26
- assert_equal(28, entry.offset)
27
+ assert_equal(44, entry.offset)
27
28
  assert_equal(5, entry.length)
29
+ assert_equal(['CUST', ' TWO'], dir.table_names)
28
30
  end
29
31
  end
30
32
  end
@@ -39,4 +39,13 @@ describe HexaPDF::Font::TrueType::Builder do
39
39
  tables = built_font.directory.instance_variable_get(:@tables)
40
40
  assert_equal(tables.keys.sort, tables.keys)
41
41
  end
42
+
43
+ it "allows setting the font version to OTTO for OpenType CFF fonts" do
44
+ tables = {
45
+ "head" => @font[:head].raw_data,
46
+ "maxp" => @font[:maxp].raw_data,
47
+ }
48
+ font_data = HexaPDF::Font::TrueType::Builder.build(tables)
49
+ assert_equal('OTTO', font_data[0, 4])
50
+ end
42
51
  end
@@ -7,16 +7,30 @@ require_relative 'common'
7
7
 
8
8
  describe HexaPDF::Font::TrueType::Font do
9
9
  before do
10
- @io = StringIO.new("TEST\x00\x01\x00\x00\x00\x00\x00\x00" \
11
- "TEST----\x00\x00\x00\x1C\x00\x00\x00\x05ENTRY".b)
10
+ @io = StringIO.new("OTTO\x00\x02\x00 \x00\x01\x00\x00" \
11
+ "TESTDATA\x00\x00\x00\x2C\x00\x00\x00\x04" \
12
+ "head`\x11?\xFA\x00\x00\x00\x30\x00\x00\x00\x36" \
13
+ "DATA" \
14
+ "\x00\x00\x00\x01\x01\x02\x03\x04]\t}\x85_\x0F<\xF5#{"\x00" * 38}\x00\x00".b)
12
15
  @font = HexaPDF::Font::TrueType::Font.new(@io)
13
16
  @font.config['font.true_type.table_mapping'][:TEST] = TrueTypeTestTable.name
14
17
  end
15
18
 
19
+ describe "build" do
20
+ it "creates a font file from the tables" do
21
+ assert_equal(@io.string, @font.build)
22
+
23
+ result = @io.string.dup
24
+ result[16, 4] = result[44, 4] = 'OTHR'
25
+ result[56, 4] = "F\xE3\x95c".b
26
+ assert_equal(result, @font.build('TEST' => 'OTHR'))
27
+ end
28
+ end
29
+
16
30
  describe "[]" do
17
31
  it "returns a named table" do
18
32
  table = @font[:TEST]
19
- assert_equal('ENTRY', table.data)
33
+ assert_equal('DATA', table.data)
20
34
  end
21
35
 
22
36
  it "always returns the same table instance" do