hexapdf 1.7.0 → 1.9.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 (294) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -1
  3. data/LICENSE +1 -1
  4. data/README.md +3 -0
  5. data/Rakefile +1 -1
  6. data/data/hexapdf/fonts/Inter-Bold.ttf +0 -0
  7. data/data/hexapdf/fonts/Inter-BoldItalic.ttf +0 -0
  8. data/data/hexapdf/fonts/Inter-Italic.ttf +0 -0
  9. data/data/hexapdf/fonts/Inter-Regular.ttf +0 -0
  10. data/data/hexapdf/fonts/OFL.txt +92 -0
  11. data/examples/005-merging.rb +2 -1
  12. data/examples/019-acro_form.rb +3 -1
  13. data/examples/030-pdfa.rb +9 -16
  14. data/examples/034-text_shaping.rb +37 -0
  15. data/lib/hexapdf/cli/batch.rb +1 -1
  16. data/lib/hexapdf/cli/command.rb +1 -1
  17. data/lib/hexapdf/cli/debug_info.rb +1 -1
  18. data/lib/hexapdf/cli/files.rb +1 -1
  19. data/lib/hexapdf/cli/fonts.rb +6 -4
  20. data/lib/hexapdf/cli/form.rb +1 -1
  21. data/lib/hexapdf/cli/image2pdf.rb +1 -1
  22. data/lib/hexapdf/cli/images.rb +17 -17
  23. data/lib/hexapdf/cli/info.rb +1 -1
  24. data/lib/hexapdf/cli/inspect.rb +1 -1
  25. data/lib/hexapdf/cli/merge.rb +14 -2
  26. data/lib/hexapdf/cli/modify.rb +1 -1
  27. data/lib/hexapdf/cli/optimize.rb +1 -1
  28. data/lib/hexapdf/cli/split.rb +1 -1
  29. data/lib/hexapdf/cli/usage.rb +1 -1
  30. data/lib/hexapdf/cli/watermark.rb +1 -1
  31. data/lib/hexapdf/cli.rb +1 -1
  32. data/lib/hexapdf/composer.rb +1 -1
  33. data/lib/hexapdf/configuration.rb +15 -2
  34. data/lib/hexapdf/content/canvas.rb +1 -1
  35. data/lib/hexapdf/content/canvas_composer.rb +1 -1
  36. data/lib/hexapdf/content/color_space.rb +1 -1
  37. data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
  38. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
  39. data/lib/hexapdf/content/graphic_object/geom2d.rb +1 -1
  40. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  41. data/lib/hexapdf/content/graphic_object.rb +1 -1
  42. data/lib/hexapdf/content/graphics_state.rb +1 -1
  43. data/lib/hexapdf/content/operator.rb +1 -1
  44. data/lib/hexapdf/content/parser.rb +1 -1
  45. data/lib/hexapdf/content/processor.rb +1 -1
  46. data/lib/hexapdf/content/smart_text_extractor.rb +10 -4
  47. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  48. data/lib/hexapdf/content.rb +1 -1
  49. data/lib/hexapdf/data_dir.rb +1 -1
  50. data/lib/hexapdf/dictionary.rb +1 -1
  51. data/lib/hexapdf/dictionary_fields.rb +1 -1
  52. data/lib/hexapdf/digital_signature/cms_handler.rb +1 -1
  53. data/lib/hexapdf/digital_signature/handler.rb +1 -1
  54. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +1 -1
  55. data/lib/hexapdf/digital_signature/signature.rb +1 -1
  56. data/lib/hexapdf/digital_signature/signatures.rb +1 -1
  57. data/lib/hexapdf/digital_signature/signing/default_handler.rb +1 -1
  58. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +1 -1
  59. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +1 -1
  60. data/lib/hexapdf/digital_signature/signing.rb +1 -1
  61. data/lib/hexapdf/digital_signature/verification_result.rb +1 -1
  62. data/lib/hexapdf/digital_signature.rb +1 -1
  63. data/lib/hexapdf/document/annotations.rb +26 -1
  64. data/lib/hexapdf/document/destinations.rb +1 -1
  65. data/lib/hexapdf/document/files.rb +1 -1
  66. data/lib/hexapdf/document/fonts.rb +1 -1
  67. data/lib/hexapdf/document/images.rb +1 -1
  68. data/lib/hexapdf/document/layout.rb +1 -1
  69. data/lib/hexapdf/document/metadata.rb +1 -1
  70. data/lib/hexapdf/document/pages.rb +1 -1
  71. data/lib/hexapdf/document.rb +1 -1
  72. data/lib/hexapdf/encryption/aes.rb +1 -1
  73. data/lib/hexapdf/encryption/arc4.rb +1 -1
  74. data/lib/hexapdf/encryption/fast_aes.rb +1 -1
  75. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  76. data/lib/hexapdf/encryption/identity.rb +1 -1
  77. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  78. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  79. data/lib/hexapdf/encryption/security_handler.rb +1 -1
  80. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  81. data/lib/hexapdf/encryption.rb +1 -1
  82. data/lib/hexapdf/error.rb +1 -1
  83. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  84. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  85. data/lib/hexapdf/filter/brotli_decode.rb +1 -1
  86. data/lib/hexapdf/filter/crypt.rb +1 -1
  87. data/lib/hexapdf/filter/encryption.rb +1 -1
  88. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  89. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  90. data/lib/hexapdf/filter/pass_through.rb +1 -1
  91. data/lib/hexapdf/filter/predictor.rb +1 -1
  92. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  93. data/lib/hexapdf/filter.rb +1 -1
  94. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  95. data/lib/hexapdf/font/cmap/writer.rb +16 -10
  96. data/lib/hexapdf/font/cmap.rb +1 -1
  97. data/lib/hexapdf/font/encoding/base.rb +1 -1
  98. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  99. data/lib/hexapdf/font/encoding/glyph_list.rb +1 -1
  100. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  101. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
  102. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  103. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  104. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
  105. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  106. data/lib/hexapdf/font/encoding.rb +1 -1
  107. data/lib/hexapdf/font/invalid_glyph.rb +1 -1
  108. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  109. data/lib/hexapdf/font/true_type/font.rb +1 -1
  110. data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
  111. data/lib/hexapdf/font/true_type/subsetter.rb +4 -4
  112. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  113. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
  114. data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
  115. data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
  116. data/lib/hexapdf/font/true_type/table/head.rb +1 -1
  117. data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
  118. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
  119. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  120. data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
  121. data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
  122. data/lib/hexapdf/font/true_type/table/name.rb +1 -1
  123. data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
  124. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  125. data/lib/hexapdf/font/true_type/table.rb +1 -1
  126. data/lib/hexapdf/font/true_type.rb +1 -1
  127. data/lib/hexapdf/font/true_type_wrapper.rb +9 -6
  128. data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
  129. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  130. data/lib/hexapdf/font/type1/font.rb +1 -1
  131. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  132. data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
  133. data/lib/hexapdf/font/type1.rb +1 -1
  134. data/lib/hexapdf/font/type1_wrapper.rb +1 -1
  135. data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
  136. data/lib/hexapdf/font_loader/from_file.rb +5 -1
  137. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  138. data/lib/hexapdf/font_loader/variant_from_name.rb +1 -1
  139. data/lib/hexapdf/font_loader.rb +48 -1
  140. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  141. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  142. data/lib/hexapdf/image_loader/png.rb +1 -1
  143. data/lib/hexapdf/image_loader.rb +1 -1
  144. data/lib/hexapdf/importer.rb +1 -1
  145. data/lib/hexapdf/layout/box.rb +1 -1
  146. data/lib/hexapdf/layout/box_fitter.rb +1 -1
  147. data/lib/hexapdf/layout/column_box.rb +1 -1
  148. data/lib/hexapdf/layout/container_box.rb +3 -5
  149. data/lib/hexapdf/layout/frame.rb +1 -1
  150. data/lib/hexapdf/layout/image_box.rb +1 -1
  151. data/lib/hexapdf/layout/inline_box.rb +1 -1
  152. data/lib/hexapdf/layout/line.rb +1 -1
  153. data/lib/hexapdf/layout/list_box.rb +1 -1
  154. data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
  155. data/lib/hexapdf/layout/page_style.rb +1 -1
  156. data/lib/hexapdf/layout/style.rb +67 -5
  157. data/lib/hexapdf/layout/table_box.rb +97 -14
  158. data/lib/hexapdf/layout/text_box.rb +1 -1
  159. data/lib/hexapdf/layout/text_fragment.rb +14 -8
  160. data/lib/hexapdf/layout/text_layouter.rb +1 -1
  161. data/lib/hexapdf/layout/text_shaper.rb +163 -11
  162. data/lib/hexapdf/layout/width_from_polygon.rb +1 -1
  163. data/lib/hexapdf/layout.rb +1 -1
  164. data/lib/hexapdf/name_tree_node.rb +1 -1
  165. data/lib/hexapdf/number_tree_node.rb +1 -1
  166. data/lib/hexapdf/object.rb +1 -1
  167. data/lib/hexapdf/parser.rb +1 -1
  168. data/lib/hexapdf/pdf_array.rb +1 -1
  169. data/lib/hexapdf/rectangle.rb +1 -1
  170. data/lib/hexapdf/reference.rb +1 -1
  171. data/lib/hexapdf/revision.rb +1 -1
  172. data/lib/hexapdf/revisions.rb +1 -1
  173. data/lib/hexapdf/serializer.rb +3 -3
  174. data/lib/hexapdf/stream.rb +1 -1
  175. data/lib/hexapdf/task/dereference.rb +1 -1
  176. data/lib/hexapdf/task/import_pages.rb +185 -0
  177. data/lib/hexapdf/task/merge_acro_form.rb +1 -1
  178. data/lib/hexapdf/task/optimize.rb +1 -1
  179. data/lib/hexapdf/task/pdfa.rb +1 -1
  180. data/lib/hexapdf/task.rb +2 -1
  181. data/lib/hexapdf/test_utils.rb +1 -1
  182. data/lib/hexapdf/tokenizer.rb +1 -1
  183. data/lib/hexapdf/type/acro_form/appearance_generator.rb +1 -1
  184. data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
  185. data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
  186. data/lib/hexapdf/type/acro_form/field.rb +1 -1
  187. data/lib/hexapdf/type/acro_form/form.rb +1 -1
  188. data/lib/hexapdf/type/acro_form/java_script_actions.rb +1 -1
  189. data/lib/hexapdf/type/acro_form/signature_field.rb +1 -1
  190. data/lib/hexapdf/type/acro_form/text_field.rb +1 -1
  191. data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
  192. data/lib/hexapdf/type/acro_form.rb +1 -1
  193. data/lib/hexapdf/type/action.rb +1 -1
  194. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  195. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  196. data/lib/hexapdf/type/actions/launch.rb +1 -1
  197. data/lib/hexapdf/type/actions/set_ocg_state.rb +1 -1
  198. data/lib/hexapdf/type/actions/uri.rb +1 -1
  199. data/lib/hexapdf/type/actions.rb +1 -1
  200. data/lib/hexapdf/type/annotation.rb +1 -1
  201. data/lib/hexapdf/type/annotations/appearance_generator.rb +43 -1
  202. data/lib/hexapdf/type/annotations/border_effect.rb +1 -1
  203. data/lib/hexapdf/type/annotations/border_styling.rb +1 -1
  204. data/lib/hexapdf/type/annotations/circle.rb +1 -1
  205. data/lib/hexapdf/type/annotations/ink.rb +107 -0
  206. data/lib/hexapdf/type/annotations/interior_color.rb +1 -1
  207. data/lib/hexapdf/type/annotations/line.rb +1 -1
  208. data/lib/hexapdf/type/annotations/line_ending_styling.rb +1 -1
  209. data/lib/hexapdf/type/annotations/link.rb +1 -1
  210. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  211. data/lib/hexapdf/type/annotations/polygon.rb +1 -1
  212. data/lib/hexapdf/type/annotations/polygon_polyline.rb +1 -1
  213. data/lib/hexapdf/type/annotations/polyline.rb +1 -1
  214. data/lib/hexapdf/type/annotations/square.rb +1 -1
  215. data/lib/hexapdf/type/annotations/square_circle.rb +1 -1
  216. data/lib/hexapdf/type/annotations/text.rb +1 -1
  217. data/lib/hexapdf/type/annotations/widget.rb +1 -1
  218. data/lib/hexapdf/type/annotations.rb +2 -1
  219. data/lib/hexapdf/type/catalog.rb +1 -1
  220. data/lib/hexapdf/type/cid_font.rb +1 -1
  221. data/lib/hexapdf/type/cmap.rb +1 -1
  222. data/lib/hexapdf/type/document_security_store.rb +1 -1
  223. data/lib/hexapdf/type/embedded_file.rb +1 -1
  224. data/lib/hexapdf/type/file_specification.rb +1 -1
  225. data/lib/hexapdf/type/font.rb +4 -4
  226. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  227. data/lib/hexapdf/type/font_simple.rb +1 -1
  228. data/lib/hexapdf/type/font_true_type.rb +1 -1
  229. data/lib/hexapdf/type/font_type0.rb +1 -1
  230. data/lib/hexapdf/type/font_type1.rb +1 -1
  231. data/lib/hexapdf/type/font_type3.rb +6 -1
  232. data/lib/hexapdf/type/form.rb +1 -1
  233. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  234. data/lib/hexapdf/type/icon_fit.rb +1 -1
  235. data/lib/hexapdf/type/image.rb +1 -1
  236. data/lib/hexapdf/type/info.rb +1 -1
  237. data/lib/hexapdf/type/mark_information.rb +1 -1
  238. data/lib/hexapdf/type/marked_content_reference.rb +1 -1
  239. data/lib/hexapdf/type/measure.rb +1 -1
  240. data/lib/hexapdf/type/metadata.rb +1 -1
  241. data/lib/hexapdf/type/names.rb +1 -1
  242. data/lib/hexapdf/type/namespace.rb +1 -1
  243. data/lib/hexapdf/type/object_reference.rb +1 -1
  244. data/lib/hexapdf/type/object_stream.rb +1 -1
  245. data/lib/hexapdf/type/optional_content_configuration.rb +1 -1
  246. data/lib/hexapdf/type/optional_content_group.rb +1 -1
  247. data/lib/hexapdf/type/optional_content_membership.rb +1 -1
  248. data/lib/hexapdf/type/optional_content_properties.rb +1 -1
  249. data/lib/hexapdf/type/outline.rb +1 -1
  250. data/lib/hexapdf/type/outline_item.rb +1 -1
  251. data/lib/hexapdf/type/output_intent.rb +1 -1
  252. data/lib/hexapdf/type/page.rb +1 -1
  253. data/lib/hexapdf/type/page_label.rb +1 -1
  254. data/lib/hexapdf/type/page_tree_node.rb +1 -1
  255. data/lib/hexapdf/type/resources.rb +1 -1
  256. data/lib/hexapdf/type/struct_elem.rb +1 -1
  257. data/lib/hexapdf/type/struct_tree_root.rb +1 -1
  258. data/lib/hexapdf/type/trailer.rb +1 -1
  259. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  260. data/lib/hexapdf/type/xref_stream.rb +1 -1
  261. data/lib/hexapdf/type.rb +1 -1
  262. data/lib/hexapdf/utils/bit_field.rb +1 -1
  263. data/lib/hexapdf/utils/bit_stream.rb +1 -1
  264. data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
  265. data/lib/hexapdf/utils/lru_cache.rb +1 -1
  266. data/lib/hexapdf/utils/math_helpers.rb +1 -1
  267. data/lib/hexapdf/utils/object_hash.rb +1 -1
  268. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  269. data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
  270. data/lib/hexapdf/utils.rb +1 -1
  271. data/lib/hexapdf/version.rb +2 -2
  272. data/lib/hexapdf/writer.rb +1 -1
  273. data/lib/hexapdf/xref_section.rb +1 -1
  274. data/lib/hexapdf.rb +1 -1
  275. data/test/hexapdf/digital_signature/common.rb +5 -5
  276. data/test/hexapdf/digital_signature/test_cms_handler.rb +1 -1
  277. data/test/hexapdf/document/test_annotations.rb +10 -0
  278. data/test/hexapdf/document/test_layout.rb +6 -3
  279. data/test/hexapdf/filter/test_brotli_decode.rb +1 -1
  280. data/test/hexapdf/font/cmap/test_writer.rb +8 -6
  281. data/test/hexapdf/font/test_true_type_wrapper.rb +6 -2
  282. data/test/hexapdf/font/true_type/test_subsetter.rb +7 -6
  283. data/test/hexapdf/font_loader/test_from_file.rb +7 -0
  284. data/test/hexapdf/layout/test_container_box.rb +3 -1
  285. data/test/hexapdf/layout/test_style.rb +4 -0
  286. data/test/hexapdf/layout/test_table_box.rb +117 -1
  287. data/test/hexapdf/layout/test_text_fragment.rb +18 -8
  288. data/test/hexapdf/layout/test_text_shaper.rb +55 -5
  289. data/test/hexapdf/task/test_import_pages.rb +126 -0
  290. data/test/hexapdf/test_serializer.rb +1 -1
  291. data/test/hexapdf/type/annotations/test_appearance_generator.rb +63 -0
  292. data/test/hexapdf/type/annotations/test_ink.rb +31 -0
  293. data/test/hexapdf/type/test_font_type3.rb +4 -0
  294. metadata +26 -2
@@ -19,8 +19,9 @@ describe HexaPDF::Font::CMap::Writer do
19
19
  1 begincodespacerange
20
20
  <0000> <FFFF>
21
21
  endcodespacerange
22
- 2 beginbfchar
22
+ 3 beginbfchar
23
23
  <0060><0090>
24
+ <00A0><00410042>
24
25
  <3A51><d840dc3e>
25
26
  endbfchar
26
27
  2 beginbfrange
@@ -79,6 +80,7 @@ describe HexaPDF::Font::CMap::Writer do
79
80
  0x1379.upto(0x137B) do |i|
80
81
  @to_unicode_mapping << [i, 0x90FE + i - 0x1379]
81
82
  end
83
+ @to_unicode_mapping << [0x00A0, "AB"]
82
84
  @to_unicode_mapping << [0x3A51, 0x2003E]
83
85
  end
84
86
 
@@ -89,17 +91,17 @@ describe HexaPDF::Font::CMap::Writer do
89
91
  end
90
92
 
91
93
  it "works if the last item is a range" do
92
- @to_unicode_mapping.pop
93
- @to_unicode_cmap_data.sub!(/2 beginbfchar/, '1 beginbfchar')
94
- @to_unicode_cmap_data.sub!(/<3A51><d840dc3e>\n/, '')
94
+ @to_unicode_mapping[-2, 2] = []
95
+ @to_unicode_cmap_data.sub!(/3 beginbfchar/, '1 beginbfchar')
96
+ @to_unicode_cmap_data.sub!(/<00A0>.*<d840dc3e>\n/m, '')
95
97
  assert_equal(@to_unicode_cmap_data,
96
98
  HexaPDF::Font::CMap.create_to_unicode_cmap(@to_unicode_mapping))
97
99
  end
98
100
 
99
101
  it "works with only ranges" do
100
- @to_unicode_mapping.delete_at(-1)
102
+ @to_unicode_mapping[-2, 2] = []
101
103
  @to_unicode_mapping.delete_at(0x5f)
102
- @to_unicode_cmap_data.sub!(/\n2 beginbfchar.*endbfchar/m, '')
104
+ @to_unicode_cmap_data.sub!(/\n3 beginbfchar.*endbfchar/m, '')
103
105
  assert_equal(@to_unicode_cmap_data,
104
106
  HexaPDF::Font::CMap.create_to_unicode_cmap(@to_unicode_mapping))
105
107
  end
@@ -37,6 +37,10 @@ describe HexaPDF::Font::TrueTypeWrapper do
37
37
  refute(HexaPDF::Font::TrueTypeWrapper.new(@doc, @font, subset: false).subset?)
38
38
  end
39
39
 
40
+ it "can be asked for the filename from which the wrapped font was created" do
41
+ assert_equal(@font_file, @font_wrapper.filename)
42
+ end
43
+
40
44
  describe "decode_*" do
41
45
  it "decode_utf8 returns an array of glyph objects" do
42
46
  assert_equal("Test",
@@ -119,9 +123,9 @@ describe HexaPDF::Font::TrueTypeWrapper do
119
123
  assert_equal([3].pack('n'), code)
120
124
  end
121
125
 
122
- it "doesn't use char codes 13, 40, 41 and 92 because they would need to be escaped" do
126
+ it "doesn't use char codes 10, 13, 40, 41 and 92 because they would need to be escaped" do
123
127
  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)
128
+ assert_equal([1..9, 11..12, 14..39, 42..91, 93..98].flat_map(&:to_a).pack('n*'), codes)
125
129
  end
126
130
 
127
131
  it "raises an error if an InvalidGlyph is encoded" do
@@ -29,12 +29,13 @@ describe HexaPDF::Font::TrueType::Subsetter do
29
29
 
30
30
  it "doesn't use certain subset glyph IDs for performance reasons" do
31
31
  1.upto(93) {|i| @subsetter.use_glyph(i) }
32
- # glyph 0, 93 used glyph, 4 special glyphs
33
- assert_equal(1 + 93 + 4, @subsetter.instance_variable_get(:@glyph_map).size)
34
- 1.upto(12) {|i| assert_equal(i, @subsetter.subset_glyph_id(i), "id=#{i}") }
35
- 13.upto(38) {|i| assert_equal(i + 1, @subsetter.subset_glyph_id(i), "id=#{i}") }
36
- 39.upto(88) {|i| assert_equal(i + 3, @subsetter.subset_glyph_id(i), "id=#{i}") }
37
- 89.upto(93) {|i| assert_equal(i + 4, @subsetter.subset_glyph_id(i), "id=#{i}") }
32
+ # glyph 0 and 93 are used glyph, 5 special glyphs
33
+ assert_equal(1 + 93 + 5, @subsetter.instance_variable_get(:@glyph_map).size)
34
+ 1.upto(9) {|i| assert_equal(i, @subsetter.subset_glyph_id(i), "id=#{i}") }
35
+ 10.upto(11) {|i| assert_equal(i + 1, @subsetter.subset_glyph_id(i), "id=#{i}") }
36
+ 12.upto(37) {|i| assert_equal(i + 2, @subsetter.subset_glyph_id(i), "id=#{i}") }
37
+ 38.upto(87) {|i| assert_equal(i + 4, @subsetter.subset_glyph_id(i), "id=#{i}") }
38
+ 88.upto(93) {|i| assert_equal(i + 5, @subsetter.subset_glyph_id(i), "id=#{i}") }
38
39
  end
39
40
 
40
41
  it "creates the subset font file" do
@@ -30,6 +30,13 @@ describe HexaPDF::FontLoader::FromFile do
30
30
  refute(wrapper.subset?)
31
31
  end
32
32
 
33
+ it "raises an error if the provided font does not contain TrueType outlines" do
34
+ font = HexaPDF::Font::TrueType::Font.new(File.open(@font_file, 'rb'))
35
+ font.directory.instance_variable_get(:@tables).delete('glyf')
36
+ exception = assert_raises(HexaPDF::Error) { @klass.call(@doc, font) }
37
+ assert_match(/does not contain TrueType but CFF/, exception.message)
38
+ end
39
+
33
40
  it "returns nil if the given name doesn't represent a file" do
34
41
  assert_nil(@klass.call(@doc, "Unknown"))
35
42
  end
@@ -71,9 +71,11 @@ describe HexaPDF::Layout::ContainerBox do
71
71
  end
72
72
 
73
73
  it "splits the box if splitting is allowed and the content is too big" do
74
- box = create_box([child_box(height: 80), child_box(height: 30)], splitable: true)
74
+ box = create_box([child_box(width: 30, height: 80), child_box(height: 30)], splitable: true)
75
75
  box.fit(@frame.available_width, @frame.available_height, @frame)
76
76
  assert(box.fit_result.overflow?)
77
+ assert_equal(100, box.width)
78
+ assert_equal(80, box.height)
77
79
  end
78
80
  end
79
81
 
@@ -785,6 +785,10 @@ describe HexaPDF::Layout::Style do
785
785
  assert_equal(100, @style.horizontal_scaling)
786
786
  assert_equal(0, @style.text_rise)
787
787
  assert_equal({}, @style.font_features)
788
+ assert_nil(@style.font_script)
789
+ assert_equal(:ltr, @style.direction)
790
+ assert_nil(@style.language)
791
+ assert_equal(:internal, @style.shaping_engine)
788
792
  assert_equal(HexaPDF::Content::TextRenderingMode::FILL, @style.text_rendering_mode)
789
793
  assert_equal([0], @style.fill_color.components)
790
794
  assert_equal(1, @style.fill_alpha)
@@ -128,6 +128,20 @@ describe HexaPDF::Layout::TableBox::Cell do
128
128
  assert_equal(32, cell.height)
129
129
  end
130
130
 
131
+ it "can split a cell if necessary" do
132
+ children = [HexaPDF::Layout::Box.create(width: 70, height: 60),
133
+ HexaPDF::Layout::Box.create(width: 70, height: 60)]
134
+ container = HexaPDF::Layout::ContainerBox.new(children: children, splitable: true)
135
+ [container, container.children].each do |used_children|
136
+ cell = create_cell(children: used_children)
137
+ assert(cell.fit(100, 100, @frame).overflow?)
138
+ assert_equal(100, cell.width)
139
+ assert_equal(72, cell.height)
140
+ assert_equal(used_children == container ? 100 : 82, cell.preferred_width)
141
+ assert_equal(72, cell.preferred_height)
142
+ end
143
+ end
144
+
131
145
  it "doesn't fit children that are too big" do
132
146
  cell = create_cell(children: HexaPDF::Layout::Box.create(width: 300, height: 20))
133
147
  assert(cell.fit(100, 100, @frame).failure?)
@@ -142,6 +156,18 @@ describe HexaPDF::Layout::TableBox::Cell do
142
156
  end
143
157
  end
144
158
 
159
+ describe "split" do
160
+ it "assigns the overflown boxes to the split box" do
161
+ children = [HexaPDF::Layout::Box.create(width: 70, height: 60),
162
+ HexaPDF::Layout::Box.create(width: 70, height: 60)]
163
+ cell = create_cell(children: children)
164
+ assert(cell.fit(100, 100, @frame).overflow?)
165
+ box, overflow_box = cell.split
166
+ assert_same(cell, box)
167
+ assert_equal([children[1]], overflow_box.children)
168
+ end
169
+ end
170
+
145
171
  describe "draw" do
146
172
  before do
147
173
  @canvas = HexaPDF::Document.new.pages.add.canvas
@@ -349,6 +375,13 @@ describe HexaPDF::Layout::TableBox::Cells do
349
375
  cells = create_cells([[:a, :b], [:c], [:d, :e]])
350
376
  assert_equal([[:a, :b], [:c], [:d, :e]], cells.each_row.map {|cols| cols.map(&:children) })
351
377
  end
378
+
379
+ it "allows iterating over rows containing an overridden one" do
380
+ cells = create_cells([[:a, :b], [:c], [:d, :e]])
381
+ cells.instance_variable_set(:@overridden_row_index, 1)
382
+ cells.instance_variable_set(:@overridden_row_cells, [cells[2, 1], cells[2, 0]])
383
+ assert_equal([[:a, :b], [:e, :d], [:d, :e]], cells.each_row.map {|cols| cols.map(&:children) })
384
+ end
352
385
  end
353
386
 
354
387
  describe "style" do
@@ -612,6 +645,29 @@ describe HexaPDF::Layout::TableBox do
612
645
  check_box(box, :overflow, 160, 10,
613
646
  [[0, 0, 80, 10], [80, 0, 80, 10], [nil, nil, 80, 0], [nil, nil, 0, 0]])
614
647
  end
648
+
649
+ it "fails if not even enough height for the first row is available" do
650
+ check_box(create_box(height: 10), :failure, 160, 10)
651
+ end
652
+
653
+ describe "last row splitting" do
654
+ before do
655
+ @boxes = [[80, 50], [80, 40], [80, 70]].map do |w, h|
656
+ HexaPDF::Layout::Box.new(width: w, height: h, &@draw_block)
657
+ end
658
+ end
659
+
660
+ it "splits the last row if necessary" do
661
+ box = create_box(cells: [[@boxes[0], @boxes[1, 2]]], cell_style: {padding: 0, border: {width: 0}})
662
+ check_box(box, :overflow, 160, 50, [[0, 0, 80, 50], [80, 0, 80, 50]])
663
+ end
664
+
665
+ it "doesn't split the last row if it is part of a row span" do
666
+ cells = [[@boxes[0], {content: @boxes[1, 2], row_span: 2}]]
667
+ box = create_box(cells: cells, cell_style: {padding: 0, border: {width: 0}})
668
+ check_box(box, :failure, 160, 0)
669
+ end
670
+ end
615
671
  end
616
672
 
617
673
  describe "split" do
@@ -672,6 +728,23 @@ describe HexaPDF::Layout::TableBox do
672
728
  end
673
729
  end
674
730
  end
731
+
732
+ it "splits the last row of a table" do
733
+ box = create_box(cells: [[@fixed_size_boxes[0], @fixed_size_boxes[1, 3]]],
734
+ cell_style: {padding: 0, border: {width: 0}})
735
+ assert(box.fit(100, 15, @frame).overflow?)
736
+ box_a, box_b = box.split
737
+ assert_same(box_a, box)
738
+
739
+ assert_equal(0, box_a.start_row_index)
740
+ assert_equal(0, box_a.last_fitted_row_index)
741
+ assert_equal(0, box_b.start_row_index)
742
+ assert_equal(-1, box_b.last_fitted_row_index)
743
+
744
+ assert_nil(box_b.cells[0, 0].children)
745
+ assert_same(box_a.cells[0, 0].style, box_b.cells[0, 0].style)
746
+ assert_equal(@fixed_size_boxes[2, 2], box_b.cells[0, 1].children)
747
+ end
675
748
  end
676
749
 
677
750
  describe "draw_content" do
@@ -722,7 +795,7 @@ describe HexaPDF::Layout::TableBox do
722
795
  assert_operators(@canvas.contents, operators)
723
796
  end
724
797
 
725
- it "correctly works for split boxes" do
798
+ it "correctly works for split tables" do
726
799
  box = create_box(cell_style: {padding: 0, border: {width: 0}})
727
800
  assert(box.fit(100, 10, @frame).overflow?)
728
801
  _, split_box = box.split
@@ -753,6 +826,49 @@ describe HexaPDF::Layout::TableBox do
753
826
  assert_operators(@canvas.contents, operators)
754
827
  end
755
828
 
829
+ it "correctly works for split cells" do
830
+ box = create_box(cells: [[@fixed_size_boxes[0], @fixed_size_boxes[1, 3]]],
831
+ cell_style: {padding: 0, border: {width: 0}})
832
+ box.cells[0, 0].style.background_color = 'red'
833
+ assert(box.fit(100, 10, @frame).overflow?)
834
+ _, split_box = box.split
835
+ assert(split_box.fit(100, 100, @frame).success?)
836
+
837
+ box.draw(@canvas, 20, 10)
838
+ split_box.draw(@canvas, 0, 50)
839
+ operators = [[:save_graphics_state],
840
+ [:set_device_rgb_non_stroking_color, [1, 0, 0]],
841
+ [:append_rectangle, [20, 10, 50, 10]],
842
+ [:fill_path_non_zero],
843
+ [:restore_graphics_state],
844
+ [:save_graphics_state],
845
+ [:concatenate_matrix, [1, 0, 0, 1, 20, 10]],
846
+ [:move_to, [0, 0]],
847
+ [:end_path],
848
+ [:restore_graphics_state],
849
+ [:save_graphics_state],
850
+ [:concatenate_matrix, [1, 0, 0, 1, 70, 10]],
851
+ [:move_to, [0, 0]],
852
+ [:end_path],
853
+ [:restore_graphics_state],
854
+ [:save_graphics_state],
855
+ [:set_device_rgb_non_stroking_color, [1, 0, 0]],
856
+ [:append_rectangle, [0, 50, 50, 20]],
857
+ [:fill_path_non_zero],
858
+ [:restore_graphics_state],
859
+ [:save_graphics_state],
860
+ [:concatenate_matrix, [1, 0, 0, 1, 50, 60]],
861
+ [:move_to, [0, 0]],
862
+ [:end_path],
863
+ [:restore_graphics_state],
864
+ [:save_graphics_state],
865
+ [:concatenate_matrix, [1, 0, 0, 1, 50, 50]],
866
+ [:move_to, [0, 0]],
867
+ [:end_path],
868
+ [:restore_graphics_state]]
869
+ assert_operators(@canvas.contents, operators)
870
+ end
871
+
756
872
  it "correctly works for tables with headers and footers" do
757
873
  box = create_box(header: lambda {|_| [@fixed_size_boxes[10, 1]] },
758
874
  footer: lambda {|_| [@fixed_size_boxes[12, 1]] },
@@ -50,24 +50,29 @@ describe HexaPDF::Layout::TextFragment do
50
50
  it "replaces invalid glyphs with the result of the block" do
51
51
  zapf_dingbats = @doc.fonts.add('ZapfDingbats')
52
52
  i = 0
53
- fallback = lambda do |codepoint, _invalid_glyph|
54
- case (i += 1) % 3
53
+ fallback = lambda do |codepoint, invalid_glyph|
54
+ case (i += 1) % 4
55
55
  when 0 then []
56
56
  when 1 then [zapf_dingbats.decode_codepoint(codepoint)]
57
57
  when 2 then @font.decode_utf8("Tom")
58
+ when 3 then [invalid_glyph]
58
59
  end
59
60
  end
60
61
  style = HexaPDF::Layout::Style.new(font: @font, font_size: 20, font_features: {kern: true})
61
62
 
62
- frags = HexaPDF::Layout::TextFragment.create_with_fallback_glyphs("✂Tom✂✂Tom✂", style, &fallback)
63
- assert_equal(5, frags.size)
63
+ frags = HexaPDF::Layout::TextFragment.create_with_fallback_glyphs("✂TomTom✂Tom✂Tom\u{ad}",
64
+ style, &fallback)
65
+ assert_equal(8, frags.size)
64
66
  assert_equal(zapf_dingbats, frags[0].style.font)
65
67
  assert_equal(:a2, frags[0].items[0].name)
68
+ assert_equal("Tom", frags[1].text)
69
+ assert_equal("Tom", frags[2].text)
66
70
  assert_equal(@font, frags[2].style.font)
67
-
68
- frags = HexaPDF::Layout::TextFragment.create_with_fallback_glyphs("Tom✂Tom", style, &fallback)
69
- assert_equal(3, frags.size)
70
- assert_equal(frags[0].width, frags[1].width)
71
+ assert_equal("Tom", frags[3].text)
72
+ assert_equal(:'.notdef', frags[4].items[0].name)
73
+ assert_equal("Tom", frags[5].text)
74
+ assert_equal("Tom", frags[6].text)
75
+ assert_equal("\u{ad}", frags[7].text)
71
76
  end
72
77
  end
73
78
 
@@ -173,6 +178,11 @@ describe HexaPDF::Layout::TextFragment do
173
178
  [:move_text_next_line]])
174
179
  end
175
180
 
181
+ it "without any horizontal or vertical movement" do
182
+ @fragment.draw(@canvas, 0, 0, ignore_text_properties: true)
183
+ assert_operators(@canvas.contents, [[:begin_text]])
184
+ end
185
+
176
186
  it "only horizontal movement" do
177
187
  @fragment.draw(@canvas, 20, 0, ignore_text_properties: true)
178
188
  assert_operators(@canvas.contents, [[:begin_text],
@@ -15,7 +15,7 @@ describe HexaPDF::Layout::TextShaper do
15
15
  end
16
16
 
17
17
  def setup_fragment(items, **options)
18
- style = HexaPDF::Layout::Style.new(font: @font, font_size: 20, font_features: options)
18
+ style = HexaPDF::Layout::Style.new(font: @font, font_size: 20, **options)
19
19
  HexaPDF::Layout::TextFragment.new(items, style)
20
20
  end
21
21
 
@@ -26,14 +26,15 @@ describe HexaPDF::Layout::TextShaper do
26
26
 
27
27
  it "handles ligatures" do
28
28
  fragment = setup_fragment(@font.decode_utf8('fish fish fi').insert(1, 100).
29
- insert(0, 100), liga: true)
29
+ insert(0, 100), font_features: {liga: true})
30
30
  @shaper.shape_text(fragment)
31
31
  assert_equal([100, :fi, :s, :h, :space, :fi, :s, :h, :space, :fi],
32
32
  fragment.items.map {|item| item.kind_of?(Numeric) ? item : item.id })
33
33
  end
34
34
 
35
35
  it "handles kerning" do
36
- fragment = setup_fragment(@font.decode_utf8('fish fish wow').insert(1, 100), kern: true)
36
+ fragment = setup_fragment(@font.decode_utf8('fish fish wow').insert(1, 100),
37
+ font_features: {kern: true})
37
38
  @shaper.shape_text(fragment)
38
39
  assert_equal([:f, 100, :i, :s, :h, :space, :f, 20, :i, :s, :h, :space, :w, 10, :o, 25, :w],
39
40
  fragment.items.map {|item| item.kind_of?(Numeric) ? item : item.id })
@@ -47,16 +48,65 @@ describe HexaPDF::Layout::TextShaper do
47
48
  @font = HexaPDF::Font::TrueTypeWrapper.new(@doc, @wrapped_font)
48
49
  end
49
50
 
50
- it "handles kerning" do
51
+ it "handles kerning via the kern table" do
51
52
  data = [0, 1].pack('n2') <<
52
53
  [0, 6 + 8 + 12, 0x1].pack('n3') <<
53
54
  [2, 0, 0, 0, 53, 80, -20, 80, 81, -10].pack('n4n2s>n2s>')
54
55
  table = create_table(:Kern, data, standalone: true)
55
56
  @wrapped_font.instance_eval { @tables[:kern] = table }
56
- fragment = setup_fragment(@font.decode_utf8('Top Top').insert(1, 100), kern: true)
57
+ fragment = setup_fragment(@font.decode_utf8('Top Top').insert(1, 100),
58
+ shaping_engine: :internal, font_features: {kern: true})
57
59
  @shaper.shape_text(fragment)
58
60
  assert_equal([53, [100], 80, [10], 81, 3, 53, [20], 80, [10], 81],
59
61
  fragment.items.map {|item| item.kind_of?(Numeric) ? [item] : item.id })
60
62
  end
63
+
64
+ describe "HarfBuzz OpenType shaper" do
65
+ before do
66
+ @font = @doc.fonts.add('Inter')
67
+ skip if Gem.win_platform?
68
+ end
69
+
70
+ it "performs the shaping" do
71
+ # Test composition of o+diaresis, invalid char \n, kerning WA, x/y offsets with marks
72
+ fragment = setup_fragment(@font.decode_utf8("ö\nWAď̄"), shaping_engine: :harfbuzz,
73
+ font_features: {kern: true})
74
+ assert_equal(8, fragment.items.size)
75
+ result = @shaper.shape_text(fragment)
76
+ assert_equal(2, result.size)
77
+ [[791, 0, 459, [56.640625], 2, 603],
78
+ [[664.55078125], 1773, [-664.55078125]]].each_with_index do |expected, index|
79
+ assert_equal(expected,
80
+ result[index].items.map {|item| item.kind_of?(Numeric) ? [item] : item.id })
81
+ end
82
+ assert_equal("\n", result[0].items[1].str)
83
+ end
84
+
85
+ it "handles glyphs with the same cluster number" do
86
+ # Force Harfbuzz into cluster level 0 to force the same cluster numbers
87
+ cluster_level_method = HarfBuzz::Buffer.instance_method(:cluster_level=)
88
+ HarfBuzz::Buffer.remove_method(:cluster_level=)
89
+ HarfBuzz::Buffer.define_method(:cluster_level=) {|val| }
90
+
91
+ fragment = setup_fragment(@font.decode_utf8("ď̄aď̄"), shaping_engine: :harfbuzz)
92
+ result = @shaper.shape_text(fragment)
93
+ assert_equal("ď̄", result[0].items[0].str)
94
+ assert_equal("", result[1].items[1].str)
95
+ ensure
96
+ HarfBuzz::Buffer.remove_method(:cluster_level=)
97
+ HarfBuzz::Buffer.define_method(:cluster_level=, cluster_level_method)
98
+ end
99
+
100
+ it "raises an error if the harfbuzz-ruby gem is not available" do
101
+ Object.send(:remove_const, :HARFBUZZ_AVAILABLE)
102
+ Object.const_set(:HARFBUZZ_AVAILABLE, false)
103
+ fragment = setup_fragment(@font.decode_utf8('test'), shaping_engine: :harfbuzz)
104
+ error = assert_raises(HexaPDF::Error) { @shaper.shape_text(fragment) }
105
+ assert_match(/harfbuzz-ruby/, error.message)
106
+ ensure
107
+ Object.send(:remove_const, :HARFBUZZ_AVAILABLE)
108
+ Object.const_set(:HARFBUZZ_AVAILABLE, true)
109
+ end
110
+ end
61
111
  end
62
112
  end
@@ -0,0 +1,126 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/task/import_pages'
6
+
7
+ describe HexaPDF::Task::ImportPages do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @pages = [@doc.pages.add, @doc.pages.add]
11
+ @pages[0][:Page1] = true
12
+ @pages[1][:Page2] = true
13
+
14
+ @target = HexaPDF::Document.new
15
+ end
16
+
17
+ describe "pages argument" do
18
+ it "imports all pages by default" do
19
+ @target.task(:import_pages, source: @doc)
20
+ assert_equal(2, @target.pages.count)
21
+ assert(@target.pages[0][:Page1])
22
+ assert(@target.pages[1][:Page2])
23
+ end
24
+
25
+ it "imports the provided page objects" do
26
+ @target.task(:import_pages, source: @doc, pages: @pages.reverse)
27
+ assert_equal(2, @target.pages.count)
28
+ assert(@target.pages[0][:Page2])
29
+ assert(@target.pages[1][:Page1])
30
+ end
31
+
32
+ it "imports a single page" do
33
+ @target.task(:import_pages, source: @doc, pages: 1)
34
+ assert_equal(1, @target.pages.count)
35
+ assert(@target.pages[0][:Page2])
36
+ end
37
+
38
+ it "imports a page range" do
39
+ @target.task(:import_pages, source: @doc, pages: 0..-1)
40
+ assert_equal(2, @target.pages.count)
41
+ assert(@target.pages[0][:Page1])
42
+ assert(@target.pages[1][:Page2])
43
+ end
44
+
45
+ it "imports multiple pages" do
46
+ @target.task(:import_pages, source: @doc, pages: [1, 0..-1],
47
+ ocgs: :ignore, acro_form: :ignore)
48
+ assert_equal(2, @target.pages.count)
49
+ assert(@target.pages[0][:Page2])
50
+ assert(@target.pages[1][:Page1])
51
+ end
52
+ end
53
+
54
+ it "doesn't append the pages if specified so" do
55
+ result = @target.task(:import_pages, source: @doc, append: false)
56
+ assert_equal(0, @target.pages.count)
57
+ assert_equal(2, result.size)
58
+ assert(result[0][:Page1])
59
+ assert(result[1][:Page2])
60
+ end
61
+
62
+ it "merges the AcroForm fields" do
63
+ form = @doc.acro_form(create: true)
64
+ field = form.create_text_field("Text")
65
+ field.create_widget(@doc.pages[0], Rect: [0, 0, 0, 0])
66
+ @doc.dispatch_message(:complete_objects)
67
+ @doc.validate
68
+
69
+ @target.task(:import_pages, source: @doc)
70
+ assert_equal(1, @target.acro_form.root_fields.size)
71
+ end
72
+
73
+ describe "ocgs argument" do
74
+ before do
75
+ @ocg1 = @doc.optional_content.ocg('OCG')
76
+ @ocg1.add_to_ui(path: @ocg1)
77
+ @ocg2 = @doc.optional_content.ocg('OCMD')
78
+ @ocg2.add_to_ui(path: @ocg2)
79
+ @ocg2.off!
80
+ @ocmd = @doc.optional_content.create_ocmd(@ocg2)
81
+ end
82
+
83
+ it "doesn't preserve unused ocgs" do
84
+ @target.task(:import_pages, source: @doc)
85
+ assert(@target.optional_content.ocgs.empty?)
86
+ end
87
+
88
+ it "preserves OCGs and OCMDs in content streams" do
89
+ canvas = @doc.pages[0].canvas
90
+ canvas.optional_content(@ocg1)
91
+ canvas.optional_content(@ocmd)
92
+ @target.task(:import_pages, source: @doc)
93
+ assert_equal(['OCG', 'OCMD'], @target.optional_content.ocgs.map(&:name))
94
+ assert(@target.optional_content.ocg('OCG').on?)
95
+ refute(@target.optional_content.ocg('OCMD').on?)
96
+ end
97
+
98
+ it "preserves OCGs/OCMDs associated with XObjects" do
99
+ canvas = @doc.pages[0].canvas
100
+ form = canvas.form
101
+ form[:OC] = @ocg1
102
+ canvas.xobject(form, at: [0, 0])
103
+ @target.task(:import_pages, source: @doc)
104
+ assert_equal(['OCG'], @target.optional_content.ocgs.map(&:name))
105
+ end
106
+
107
+ it "preserves OCGs/OCMDs associated with annotations" do
108
+ annot = @doc.annotations.create_line(@doc.pages[0], start_point: [0, 0], end_point: [50, 50])
109
+ annot[:OC] = @ocmd
110
+ annot.regenerate_appearance
111
+ @target.task(:import_pages, source: @doc)
112
+ assert_equal(['OCMD'], @target.optional_content.ocgs.map(&:name))
113
+ refute(@target.optional_content.ocg('OCMD').on?)
114
+ end
115
+
116
+ it "preserves the radio button group state of imported OCGs" do
117
+ @doc.pages[0].canvas.optional_content(@ocg1)
118
+ @doc.optional_content.default_configuration[:RBGroups] = [[@ocg1, @ocg2]]
119
+ @target.task(:import_pages, source: @doc)
120
+ assert_equal(['OCG'], @target.optional_content.ocgs.map {|ocg| ocg.name })
121
+ rb_groups = @target.optional_content.default_configuration[:RBGroups]
122
+ assert_equal(1, rb_groups.size)
123
+ assert_equal(['OCG'], rb_groups[0].map(&:name))
124
+ end
125
+ end
126
+ end
@@ -104,7 +104,7 @@ describe HexaPDF::Serializer do
104
104
 
105
105
  it "serializes strings" do
106
106
  assert_serialized("(Hallo)", "Hallo")
107
- assert_serialized("(Hallo\\r\n\t\\(\\)\\\\)", "Hallo\r\n\t()\\")
107
+ assert_serialized("(Hallo\\r\\n\t\\(\\)\\\\)", "Hallo\r\n\t()\\")
108
108
  assert_serialized("(\xFE\xFF\x00H\x00a\x00l\x00\f\x00\b\x00\\()".b, "Hal\f\b(")
109
109
  end
110
110
 
@@ -605,4 +605,67 @@ describe HexaPDF::Type::Annotations::AppearanceGenerator do
605
605
  [:fill_and_stroke_path_non_zero]], range: 6..-1)
606
606
  end
607
607
  end
608
+
609
+ describe "ink" do
610
+ before do
611
+ @ink = @doc.add({Type: :Annot, Subtype: :Ink, C: [0],
612
+ InkList: [[100, 100, 200, 150], [210, 80, 110, 160]]})
613
+ @generator = HexaPDF::Type::Annotations::AppearanceGenerator.new(@ink)
614
+ end
615
+
616
+ it "sets the print flag and unsets the hidden flag" do
617
+ @ink.flag(:hidden)
618
+ @generator.create_appearance
619
+ assert(@ink.flagged?(:print))
620
+ refute(@ink.flagged?(:hidden))
621
+ end
622
+
623
+ it "creates the appearance" do
624
+ @generator.create_appearance
625
+ assert_equal([96, 76, 214, 164], @ink[:Rect])
626
+ assert_equal([96, 76, 214, 164], @ink.appearance[:BBox])
627
+ assert_operators(@ink.appearance.stream,
628
+ [[:move_to, [100, 100]],
629
+ [:line_to, [200, 150]],
630
+ [:move_to, [210, 80]],
631
+ [:line_to, [110, 160]],
632
+ [:stroke_path]])
633
+ end
634
+
635
+ describe "stroke color" do
636
+ it "uses the specified border color for stroking operations" do
637
+ @ink.border_style(color: "red")
638
+ @generator.create_appearance
639
+ assert_operators(@ink.appearance.stream,
640
+ [:set_device_rgb_stroking_color, [1, 0, 0]], range: 0)
641
+ end
642
+
643
+ it "works with a transparent border" do
644
+ @ink.border_style(color: :transparent)
645
+ @generator.create_appearance
646
+ assert_operators(@ink.appearance.stream, [:end_path], range: 4)
647
+ end
648
+ end
649
+
650
+ it "sets the specified border line width" do
651
+ @ink.border_style(width: 4)
652
+ @generator.create_appearance
653
+ assert_operators(@ink.appearance.stream,
654
+ [:set_line_width, [4]], range: 0)
655
+ end
656
+
657
+ it "sets the specified line dash pattern if it is an array" do
658
+ @ink.border_style(style: [5, 2])
659
+ @generator.create_appearance
660
+ assert_operators(@ink.appearance.stream,
661
+ [:set_line_dash_pattern, [[5, 2], 0]], range: 0)
662
+ end
663
+
664
+ it "sets the specified opacity" do
665
+ @ink.opacity(fill_alpha: 0.5, stroke_alpha: 0.5)
666
+ @generator.create_appearance
667
+ assert_operators(@ink.appearance.stream,
668
+ [:set_graphics_state_parameters, [:GS1]], range: 0)
669
+ end
670
+ end
608
671
  end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/annotations/ink'
6
+
7
+ describe HexaPDF::Type::Annotations::Ink do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @annot = @doc.add({Type: :Annot, Subtype: :Ink, Rect: [0, 0, 0, 0]})
11
+ end
12
+
13
+ it "returns the paths" do
14
+ assert_equal([], @annot.paths)
15
+ @annot[:InkList] = [[10, 20, 30, 40], [50, 60, 70, 80]]
16
+ assert_equal([[10, 20, 30, 40], [50, 60, 70, 80]], @annot.paths)
17
+ end
18
+
19
+ describe "add_paths" do
20
+ it "adds the given path" do
21
+ assert_same(@annot, @annot.add_path(1, 2, 3, 4, 5, 6))
22
+ assert_equal([[1, 2, 3, 4, 5, 6]], @annot[:InkList])
23
+ @annot.add_path(7, 8, 9, 10)
24
+ assert_equal([[1, 2, 3, 4, 5, 6], [7, 8, 9, 10]], @annot[:InkList])
25
+ end
26
+
27
+ it "raises an ArgumentError if an uneven number of arguments is provided" do
28
+ assert_raises(ArgumentError) { @annot.add_path(1, 2, 3) }
29
+ end
30
+ end
31
+ end