hexapdf 0.32.2 → 0.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (221) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +104 -1
  3. data/README.md +9 -0
  4. data/examples/002-graphics.rb +15 -17
  5. data/examples/003-arcs.rb +9 -9
  6. data/examples/009-text_layouter_alignment.rb +1 -1
  7. data/examples/010-text_layouter_inline_boxes.rb +2 -2
  8. data/examples/011-text_layouter_line_wrapping.rb +1 -1
  9. data/examples/012-text_layouter_styling.rb +7 -7
  10. data/examples/013-text_layouter_shapes.rb +1 -1
  11. data/examples/014-text_in_polygon.rb +1 -1
  12. data/examples/015-boxes.rb +8 -7
  13. data/examples/016-frame_automatic_box_placement.rb +2 -2
  14. data/examples/017-frame_text_flow.rb +2 -1
  15. data/examples/018-composer.rb +1 -1
  16. data/examples/020-column_box.rb +2 -1
  17. data/examples/025-table_box.rb +46 -0
  18. data/examples/026-optional_content.rb +55 -0
  19. data/examples/027-composer_optional_content.rb +83 -0
  20. data/lib/hexapdf/cli/command.rb +12 -3
  21. data/lib/hexapdf/cli/fonts.rb +1 -1
  22. data/lib/hexapdf/cli/form.rb +5 -5
  23. data/lib/hexapdf/cli/inspect.rb +5 -7
  24. data/lib/hexapdf/composer.rb +106 -53
  25. data/lib/hexapdf/configuration.rb +65 -40
  26. data/lib/hexapdf/content/canvas.rb +445 -267
  27. data/lib/hexapdf/content/color_space.rb +72 -25
  28. data/lib/hexapdf/content/graphic_object/arc.rb +57 -24
  29. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -23
  30. data/lib/hexapdf/content/graphic_object/geom2d.rb +47 -6
  31. data/lib/hexapdf/content/graphic_object/solid_arc.rb +58 -36
  32. data/lib/hexapdf/content/graphic_object.rb +6 -7
  33. data/lib/hexapdf/content/graphics_state.rb +54 -45
  34. data/lib/hexapdf/content/operator.rb +54 -54
  35. data/lib/hexapdf/content/parser.rb +2 -2
  36. data/lib/hexapdf/content/processor.rb +15 -15
  37. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  38. data/lib/hexapdf/content.rb +5 -0
  39. data/lib/hexapdf/dictionary.rb +7 -5
  40. data/lib/hexapdf/dictionary_fields.rb +43 -16
  41. data/lib/hexapdf/digital_signature/cms_handler.rb +2 -2
  42. data/lib/hexapdf/digital_signature/handler.rb +1 -1
  43. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +2 -3
  44. data/lib/hexapdf/digital_signature/signature.rb +6 -6
  45. data/lib/hexapdf/digital_signature/signatures.rb +13 -12
  46. data/lib/hexapdf/digital_signature/signing/default_handler.rb +14 -5
  47. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +2 -4
  48. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +4 -4
  49. data/lib/hexapdf/digital_signature/signing.rb +4 -0
  50. data/lib/hexapdf/digital_signature/verification_result.rb +3 -4
  51. data/lib/hexapdf/digital_signature.rb +7 -2
  52. data/lib/hexapdf/document/destinations.rb +12 -11
  53. data/lib/hexapdf/document/files.rb +1 -1
  54. data/lib/hexapdf/document/fonts.rb +1 -1
  55. data/lib/hexapdf/document/layout.rb +170 -39
  56. data/lib/hexapdf/document/pages.rb +4 -3
  57. data/lib/hexapdf/document.rb +96 -55
  58. data/lib/hexapdf/encryption/aes.rb +5 -5
  59. data/lib/hexapdf/encryption/arc4.rb +1 -1
  60. data/lib/hexapdf/encryption/fast_aes.rb +2 -2
  61. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  62. data/lib/hexapdf/encryption/identity.rb +1 -1
  63. data/lib/hexapdf/encryption/ruby_aes.rb +11 -21
  64. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  65. data/lib/hexapdf/encryption/security_handler.rb +31 -24
  66. data/lib/hexapdf/encryption/standard_security_handler.rb +45 -36
  67. data/lib/hexapdf/encryption.rb +7 -2
  68. data/lib/hexapdf/error.rb +18 -0
  69. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  70. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  71. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  72. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  73. data/lib/hexapdf/filter/pass_through.rb +1 -1
  74. data/lib/hexapdf/filter/predictor.rb +1 -1
  75. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  76. data/lib/hexapdf/filter.rb +55 -6
  77. data/lib/hexapdf/font/cmap/parser.rb +2 -2
  78. data/lib/hexapdf/font/cmap.rb +1 -1
  79. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  80. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  81. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +2 -2
  82. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  83. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  84. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +3 -3
  85. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  86. data/lib/hexapdf/font/invalid_glyph.rb +3 -0
  87. data/lib/hexapdf/font/true_type_wrapper.rb +17 -4
  88. data/lib/hexapdf/font/type1_wrapper.rb +19 -4
  89. data/lib/hexapdf/font_loader/from_configuration.rb +5 -2
  90. data/lib/hexapdf/font_loader/from_file.rb +5 -5
  91. data/lib/hexapdf/font_loader/standard14.rb +3 -3
  92. data/lib/hexapdf/font_loader.rb +3 -0
  93. data/lib/hexapdf/image_loader/jpeg.rb +2 -2
  94. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  95. data/lib/hexapdf/image_loader/png.rb +2 -2
  96. data/lib/hexapdf/image_loader.rb +1 -1
  97. data/lib/hexapdf/importer.rb +13 -0
  98. data/lib/hexapdf/layout/box.rb +32 -5
  99. data/lib/hexapdf/layout/box_fitter.rb +2 -2
  100. data/lib/hexapdf/layout/column_box.rb +20 -5
  101. data/lib/hexapdf/layout/frame.rb +53 -18
  102. data/lib/hexapdf/layout/image_box.rb +5 -0
  103. data/lib/hexapdf/layout/inline_box.rb +21 -9
  104. data/lib/hexapdf/layout/list_box.rb +50 -20
  105. data/lib/hexapdf/layout/page_style.rb +6 -5
  106. data/lib/hexapdf/layout/style.rb +64 -9
  107. data/lib/hexapdf/layout/table_box.rb +684 -0
  108. data/lib/hexapdf/layout/text_box.rb +12 -3
  109. data/lib/hexapdf/layout/text_fragment.rb +29 -3
  110. data/lib/hexapdf/layout/text_layouter.rb +32 -8
  111. data/lib/hexapdf/layout.rb +1 -0
  112. data/lib/hexapdf/name_tree_node.rb +1 -1
  113. data/lib/hexapdf/number_tree_node.rb +1 -1
  114. data/lib/hexapdf/object.rb +18 -7
  115. data/lib/hexapdf/parser.rb +7 -7
  116. data/lib/hexapdf/pdf_array.rb +1 -1
  117. data/lib/hexapdf/rectangle.rb +1 -1
  118. data/lib/hexapdf/reference.rb +1 -1
  119. data/lib/hexapdf/revision.rb +1 -1
  120. data/lib/hexapdf/revisions.rb +3 -3
  121. data/lib/hexapdf/serializer.rb +15 -15
  122. data/lib/hexapdf/stream.rb +5 -4
  123. data/lib/hexapdf/tokenizer.rb +14 -14
  124. data/lib/hexapdf/type/acro_form/appearance_generator.rb +22 -22
  125. data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
  126. data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
  127. data/lib/hexapdf/type/acro_form/field.rb +2 -2
  128. data/lib/hexapdf/type/acro_form/form.rb +1 -1
  129. data/lib/hexapdf/type/acro_form/signature_field.rb +4 -4
  130. data/lib/hexapdf/type/acro_form/text_field.rb +1 -1
  131. data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
  132. data/lib/hexapdf/type/acro_form.rb +1 -1
  133. data/lib/hexapdf/type/action.rb +1 -1
  134. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  135. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  136. data/lib/hexapdf/type/actions/launch.rb +1 -1
  137. data/lib/hexapdf/type/actions/set_ocg_state.rb +86 -0
  138. data/lib/hexapdf/type/actions/uri.rb +1 -1
  139. data/lib/hexapdf/type/actions.rb +2 -1
  140. data/lib/hexapdf/type/annotation.rb +3 -3
  141. data/lib/hexapdf/type/annotations/link.rb +1 -1
  142. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  143. data/lib/hexapdf/type/annotations/text.rb +2 -3
  144. data/lib/hexapdf/type/annotations/widget.rb +2 -2
  145. data/lib/hexapdf/type/annotations.rb +1 -1
  146. data/lib/hexapdf/type/catalog.rb +11 -2
  147. data/lib/hexapdf/type/cid_font.rb +18 -4
  148. data/lib/hexapdf/type/embedded_file.rb +1 -1
  149. data/lib/hexapdf/type/file_specification.rb +2 -2
  150. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  151. data/lib/hexapdf/type/font_simple.rb +2 -2
  152. data/lib/hexapdf/type/font_type0.rb +3 -3
  153. data/lib/hexapdf/type/font_type3.rb +1 -1
  154. data/lib/hexapdf/type/form.rb +76 -6
  155. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  156. data/lib/hexapdf/type/icon_fit.rb +1 -1
  157. data/lib/hexapdf/type/image.rb +1 -1
  158. data/lib/hexapdf/type/info.rb +1 -1
  159. data/lib/hexapdf/type/mark_information.rb +1 -1
  160. data/lib/hexapdf/type/names.rb +2 -2
  161. data/lib/hexapdf/type/object_stream.rb +2 -1
  162. data/lib/hexapdf/type/optional_content_configuration.rb +170 -0
  163. data/lib/hexapdf/type/optional_content_group.rb +370 -0
  164. data/lib/hexapdf/type/optional_content_membership.rb +63 -0
  165. data/lib/hexapdf/type/optional_content_properties.rb +158 -0
  166. data/lib/hexapdf/type/outline.rb +1 -1
  167. data/lib/hexapdf/type/outline_item.rb +1 -1
  168. data/lib/hexapdf/type/page.rb +46 -21
  169. data/lib/hexapdf/type/page_label.rb +5 -9
  170. data/lib/hexapdf/type/page_tree_node.rb +1 -1
  171. data/lib/hexapdf/type/resources.rb +1 -1
  172. data/lib/hexapdf/type/trailer.rb +2 -2
  173. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  174. data/lib/hexapdf/type/xref_stream.rb +2 -2
  175. data/lib/hexapdf/type.rb +4 -0
  176. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -2
  177. data/lib/hexapdf/version.rb +1 -1
  178. data/lib/hexapdf/writer.rb +4 -4
  179. data/lib/hexapdf/xref_section.rb +2 -2
  180. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +11 -1
  181. data/test/hexapdf/content/graphic_object/test_geom2d.rb +7 -0
  182. data/test/hexapdf/content/test_canvas.rb +49 -1
  183. data/test/hexapdf/digital_signature/test_signatures.rb +22 -0
  184. data/test/hexapdf/document/test_files.rb +2 -2
  185. data/test/hexapdf/document/test_layout.rb +105 -2
  186. data/test/hexapdf/document/test_pages.rb +6 -6
  187. data/test/hexapdf/encryption/test_security_handler.rb +12 -11
  188. data/test/hexapdf/encryption/test_standard_security_handler.rb +35 -23
  189. data/test/hexapdf/font/test_true_type_wrapper.rb +18 -1
  190. data/test/hexapdf/font/test_type1_wrapper.rb +15 -1
  191. data/test/hexapdf/layout/test_box.rb +14 -5
  192. data/test/hexapdf/layout/test_column_box.rb +65 -21
  193. data/test/hexapdf/layout/test_frame.rb +27 -15
  194. data/test/hexapdf/layout/test_image_box.rb +4 -0
  195. data/test/hexapdf/layout/test_inline_box.rb +17 -3
  196. data/test/hexapdf/layout/test_list_box.rb +84 -33
  197. data/test/hexapdf/layout/test_page_style.rb +3 -2
  198. data/test/hexapdf/layout/test_style.rb +60 -0
  199. data/test/hexapdf/layout/test_table_box.rb +728 -0
  200. data/test/hexapdf/layout/test_text_box.rb +26 -0
  201. data/test/hexapdf/layout/test_text_fragment.rb +33 -0
  202. data/test/hexapdf/layout/test_text_layouter.rb +36 -5
  203. data/test/hexapdf/test_composer.rb +10 -0
  204. data/test/hexapdf/test_dictionary.rb +10 -0
  205. data/test/hexapdf/test_dictionary_fields.rb +4 -1
  206. data/test/hexapdf/test_document.rb +5 -0
  207. data/test/hexapdf/test_filter.rb +8 -0
  208. data/test/hexapdf/test_importer.rb +9 -0
  209. data/test/hexapdf/test_object.rb +16 -5
  210. data/test/hexapdf/test_stream.rb +7 -0
  211. data/test/hexapdf/test_writer.rb +3 -3
  212. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +13 -5
  213. data/test/hexapdf/type/acro_form/test_form.rb +4 -3
  214. data/test/hexapdf/type/actions/test_set_ocg_state.rb +40 -0
  215. data/test/hexapdf/type/test_catalog.rb +11 -0
  216. data/test/hexapdf/type/test_form.rb +119 -0
  217. data/test/hexapdf/type/test_optional_content_configuration.rb +112 -0
  218. data/test/hexapdf/type/test_optional_content_group.rb +158 -0
  219. data/test/hexapdf/type/test_optional_content_properties.rb +109 -0
  220. data/test/hexapdf/type/test_page.rb +20 -6
  221. metadata +28 -8
@@ -25,6 +25,14 @@ describe HexaPDF::Layout::TextBox do
25
25
  end
26
26
  end
27
27
 
28
+ it "returns the text contents as string" do
29
+ doc = HexaPDF::Document.new
30
+ font = doc.fonts.add("Times")
31
+ box = create_box([HexaPDF::Layout::TextFragment.create('Test ', font: font), @inline_box,
32
+ HexaPDF::Layout::TextFragment.create('here', font: font)])
33
+ assert_equal('Test here', box.text)
34
+ end
35
+
28
36
  describe "fit" do
29
37
  it "fits into a rectangular area" do
30
38
  box = create_box([@inline_box] * 5, style: {padding: 10})
@@ -107,6 +115,14 @@ describe HexaPDF::Layout::TextBox do
107
115
  assert_equal([nil, box], box.split(100, 100, @frame))
108
116
  end
109
117
 
118
+ it "works if the box fits exactly (+/- float divergence)" do
119
+ box = create_box([@inline_box] * 5)
120
+ box.fit(50, 10, @frame)
121
+ box.instance_variable_set(:@width, 50.00000000006)
122
+ box.instance_variable_set(:@height, 10.00000000003)
123
+ assert_equal([box], box.split(50, 10, @frame))
124
+ end
125
+
110
126
  it "splits the box if necessary when using non-flowing text" do
111
127
  box = create_box([@inline_box] * 10)
112
128
  boxes = box.split(50, 10, @frame)
@@ -125,6 +141,16 @@ describe HexaPDF::Layout::TextBox do
125
141
  assert_equal(box, boxes[0])
126
142
  assert_equal(5, boxes[1].instance_variable_get(:@items).length)
127
143
  end
144
+
145
+ it "correctly handles text indentation for split boxes" do
146
+ [{}, {position: :flow}].each do |styles|
147
+ box = create_box([@inline_box] * 202, style: {text_indent: 50, **styles})
148
+ boxes = box.split(100, 100, @frame)
149
+ assert_equal(107, boxes[1].instance_variable_get(:@items).length)
150
+ boxes = boxes[1].split(100, 100, @frame)
151
+ assert_equal(7, boxes[1].instance_variable_get(:@items).length)
152
+ end
153
+ end
128
154
  end
129
155
 
130
156
  describe "draw" do
@@ -51,6 +51,10 @@ describe HexaPDF::Layout::TextFragment do
51
51
  end
52
52
  end
53
53
 
54
+ it "returns the text value of the items as string" do
55
+ assert_equal("Hal lo\u{00a0}d\n", setup_fragment(@font.decode_utf8("Hal lo\u{00a0}d\n")).text)
56
+ end
57
+
54
58
  it "allows duplicating with only its attributes while also setting new items" do
55
59
  setup_fragment([20])
56
60
  @fragment.properties['key'] = :value
@@ -392,6 +396,35 @@ describe HexaPDF::Layout::TextFragment do
392
396
  end
393
397
  end
394
398
 
399
+ describe "fill_horizontal!" do
400
+ before do
401
+ @fragment = HexaPDF::Layout::TextFragment.create('ab', fill_horizontal: 1, font: @font)
402
+ end
403
+
404
+ it "returns the fragment if the given width is too small" do
405
+ assert_same(@fragment, @fragment.fill_horizontal!(0.1))
406
+ end
407
+
408
+ it "repeats all items of the fragment" do
409
+ fragment = @fragment.fill_horizontal!(@fragment.width * 2)
410
+ assert_equal([*(@fragment.items * 2), 0], fragment.items)
411
+ assert_in_delta(@fragment.width * 2, fragment.width)
412
+ end
413
+
414
+ it "adds, after repeating, items from the start of the fragment to fill the available space" do
415
+ fragment = @fragment.fill_horizontal!(90)
416
+ assert_equal([*(@fragment.items * 9), @fragment.items[0], 3.3333333333332673], fragment.items)
417
+ assert_in_delta(90, fragment.width)
418
+ end
419
+
420
+ it "sets the character spacing correctly to account for the remaining space after filling with items" do
421
+ fragment = @fragment.fill_horizontal!(90)
422
+ refute_same(@fragment.style, fragment.style)
423
+ assert_equal(0.033333333333332674, fragment.style.character_spacing)
424
+ assert_in_delta(90, fragment.width)
425
+ end
426
+ end
427
+
395
428
  it "can be inspected" do
396
429
  frag = setup_fragment(@font.decode_utf8("H") << 5)
397
430
  assert_match(/:H/, frag.inspect)
@@ -262,7 +262,7 @@ module CommonLineWrappingTests
262
262
  end
263
263
 
264
264
  it "handles prohibited breakpoint penalties with non-zero width" do
265
- item = boxes(20).first
265
+ item = boxes(20).first.item
266
266
  result = call(boxes(70) + [glue(10)] + boxes(10) + [penalty(5000, item)] + boxes(30))
267
267
  assert_line_wrapping(result, [70, 60])
268
268
  end
@@ -295,11 +295,34 @@ module CommonLineWrappingTests
295
295
  assert_equal(2, lines.count)
296
296
  end
297
297
 
298
+ it "handles items with fill_horizontal correctly" do
299
+ doc = HexaPDF::Document.new
300
+ font = doc.fonts.add("Times")
301
+ box1 = HexaPDF::Layout::TextLayouter::Box.new(
302
+ HexaPDF::Layout::TextFragment.create('.', font: font, fill_horizontal: 1)
303
+ )
304
+ box2 = HexaPDF::Layout::TextLayouter::Box.new(
305
+ HexaPDF::Layout::TextFragment.create('.', font: font, fill_horizontal: 2)
306
+ )
307
+ items = [box1, *boxes(10), box2]
308
+ rest, lines = call(items, 40)
309
+ assert_equal(0, rest.size)
310
+ assert_equal(1, lines.size)
311
+
312
+ line = lines.first
313
+ refute_same(items[0].item, line.items[0])
314
+ assert_same(items[1].item, line.items[1])
315
+ refute_same(items[2].item, line.items[2])
316
+ assert_equal(10, line.items[0].width)
317
+ assert_equal(10, line.items[1].width)
318
+ assert_equal(20, line.items[2].width)
319
+ end
298
320
  end
299
321
 
300
322
  describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
301
323
  before do
302
324
  @obj = HexaPDF::Layout::TextLayouter::SimpleLineWrapping
325
+ @mock_frame = nil
303
326
  end
304
327
 
305
328
  describe "fixed width wrapping" do
@@ -308,7 +331,9 @@ describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
308
331
  def call(items, width = 100, &block)
309
332
  lines = []
310
333
  block ||= proc { true }
311
- rest = @obj.call(items, proc { width }) {|line, item| lines << line; block.call(line, item) }
334
+ rest = @obj.call(items, proc { width }, @mock_frame) do |line, item|
335
+ lines << line; block.call(line, item)
336
+ end
312
337
  [rest, lines]
313
338
  end
314
339
  end
@@ -319,7 +344,9 @@ describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
319
344
  def call(items, width = 100, &block)
320
345
  lines = []
321
346
  block ||= proc { true }
322
- rest = @obj.call(items, proc {|_| width }) {|line, i| lines << line; block.call(line, i) }
347
+ rest = @obj.call(items, proc {|_| width }, @mock_frame) do |line, i|
348
+ lines << line; block.call(line, i)
349
+ end
323
350
  [rest, lines]
324
351
  end
325
352
 
@@ -334,7 +361,7 @@ describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
334
361
  end
335
362
  end
336
363
  lines = []
337
- rest = @obj.call(boxes([20, 10], [10, 10], [20, 15], [40, 10]), width_block) do |line|
364
+ rest = @obj.call(boxes([20, 10], [10, 10], [20, 15], [40, 10]), width_block, @mock_frame) do |line|
338
365
  height += line.height
339
366
  lines << line
340
367
  true
@@ -357,7 +384,7 @@ describe HexaPDF::Layout::TextLayouter::SimpleLineWrapping do
357
384
  lines = []
358
385
  item = HexaPDF::Layout::InlineBox.create(width: 20, height: 10) {}
359
386
  items = boxes([20, 10]) + [penalty(0, item)] + boxes([40, 15])
360
- rest = @obj.call(items, width_block) do |line|
387
+ rest = @obj.call(items, width_block, @mock_frame) do |line|
361
388
  height += line.height
362
389
  lines << line
363
390
  true
@@ -419,6 +446,10 @@ describe HexaPDF::Layout::TextLayouter do
419
446
  assert(result.remaining_items.empty?)
420
447
  assert_equal(:success, result.status)
421
448
  end
449
+
450
+ result = @layouter.fit(boxes([20, 20], [20, 20], [20, 20], [20, 20]), 60, 200,
451
+ apply_first_text_indent: false)
452
+ assert_equal([60, 20], result.lines.map(&:width))
422
453
  end
423
454
 
424
455
  it "fits using a limited height" do
@@ -243,6 +243,16 @@ describe HexaPDF::Composer do
243
243
  [:restore_graphics_state]])
244
244
  end
245
245
 
246
+ it "returns the last drawn box" do
247
+ box = create_box(height: 400)
248
+ assert_same(box, @composer.draw_box(box))
249
+
250
+ box = create_box(height: 400)
251
+ split_box = create_box(height: 100)
252
+ box.define_singleton_method(:split) {|*| [box, split_box] }
253
+ assert_same(split_box, @composer.draw_box(box))
254
+ end
255
+
246
256
  it "raises an error if a box doesn't fit onto an empty page" do
247
257
  assert_raises(HexaPDF::Error) do
248
258
  @composer.draw_box(create_box(height: 800))
@@ -94,6 +94,16 @@ describe HexaPDF::Dictionary do
94
94
  obj = @test_class.new(nil)
95
95
  assert_equal(:MyType, obj.value[:Type])
96
96
  end
97
+
98
+ it "doesn't set the default values for required fields if the type class might be wrong" do
99
+ @test_class.define_type(:MyType)
100
+ obj = @test_class.new({})
101
+ assert_equal([], obj.value[:Array])
102
+ obj = @test_class.new({Type: :MyType})
103
+ assert_equal([], obj.value[:Array])
104
+ obj = @test_class.new({Type: :OtherType})
105
+ refute(obj.key?(:Array))
106
+ end
97
107
  end
98
108
 
99
109
  describe "[]" do
@@ -188,8 +188,11 @@ describe HexaPDF::DictionaryFields do
188
188
  ["D:19981223-08'00'", [1998, 12, 23, 00, 00, 00, "-08:00"]],
189
189
  ["D:199812-08'00'", [1998, 12, 01, 00, 00, 00, "-08:00"]],
190
190
  ["D:1998-08'00'", [1998, 01, 01, 00, 00, 00, "-08:00"]],
191
+ ["D:19981223195210Z", [1998, 12, 23, 19, 52, 10, "+00:00"]],
192
+ ["D:19981223195210Z00", [1998, 12, 23, 19, 52, 10, "+00:00"]],
193
+ ["D:19981223195210Z00'00", [1998, 12, 23, 19, 52, 10, "+00:00"]],
191
194
  ["D:19981223195210-08", [1998, 12, 23, 19, 52, 10, "-08:00"]], # missing '
192
- ["D:19981223195210-08'00", [1998, 12, 23, 19, 52, 10, "-08:00"]], # missing '
195
+ ["D:19981223195210-08'00", [1998, 12, 23, 19, 52, 10, "-08:00"]], # no trailing ', as per PDF 2.0
193
196
  ["D:19981223195210-54'00", [1998, 12, 23, 19, 52, 10, "-23:59:59"]], # TZ hour too large
194
197
  ["D:19981223195210+10'65", [1998, 12, 23, 19, 52, 10, "+11:05"]], # TZ min too large
195
198
  ["D:19982423195210-08'00'", [1998, 12, 23, 19, 52, 10, "-08:00"]], # months too large
@@ -240,6 +240,7 @@ describe HexaPDF::Document do
240
240
  assert_kind_of(@myclass2, @doc.wrap({Subtype: :Global, Test: "true"}))
241
241
  assert_kind_of(@myclass2, @doc.wrap({Type: :MyClass, S: :TheSecond}))
242
242
  assert_kind_of(@myclass, @doc.wrap({Type: :MyClass, Subtype: :TheThird}))
243
+ assert_kind_of(HexaPDF::Type::FontTrueType, @doc.wrap({Subtype: :TrueType, BaseFont: :Help}))
243
244
  end
244
245
 
245
246
  it "respects the given type/subtype arguments" do
@@ -560,6 +561,10 @@ describe HexaPDF::Document do
560
561
  assert_kind_of(HexaPDF::Type::Outline, @doc.outline)
561
562
  end
562
563
 
564
+ it "returns the optional content properties" do
565
+ assert_kind_of(HexaPDF::Type::OptionalContentProperties, @doc.optional_content)
566
+ end
567
+
563
568
  it "can be inspected and the output is not too large" do
564
569
  assert_match(/HexaPDF::Document:\d+/, @doc.inspect)
565
570
  end
@@ -12,6 +12,14 @@ describe HexaPDF::Filter do
12
12
  40.times { @str << [rand(2**32)].pack('N') }
13
13
  end
14
14
 
15
+ describe "source_from_proc" do
16
+ it "returns the whole string, once" do
17
+ fib = @obj.source_from_proc { @str }
18
+ assert_equal(@str, collector(fib))
19
+ assert_equal('', collector(fib))
20
+ end
21
+ end
22
+
15
23
  describe "source_from_string" do
16
24
  it "doesn't modify the given string" do
17
25
  str = @str.dup
@@ -40,6 +40,15 @@ describe HexaPDF::Importer do
40
40
  end
41
41
  end
42
42
 
43
+ describe "::copy" do
44
+ it "copies a complete object including references" do
45
+ obj1 = HexaPDF::Importer.copy(@dest, @obj)
46
+ obj2 = HexaPDF::Importer.copy(@dest, @obj)
47
+ refute_same(obj1, obj2)
48
+ refute_same(obj1[:ref], obj2[:ref])
49
+ end
50
+ end
51
+
43
52
  describe "import" do
44
53
  it "updates the associated document" do
45
54
  obj = @importer.import(@obj)
@@ -50,26 +50,37 @@ describe HexaPDF::Object do
50
50
  @doc = HexaPDF::Document.new
51
51
  end
52
52
 
53
- it "doesn't touch wrapped direct objects" do
53
+ it "makes values of wrapped direct objects also direct" do
54
54
  obj = HexaPDF::Object.new(5)
55
- assert_same(obj, HexaPDF::Object.make_direct(obj))
55
+ assert_same(obj, HexaPDF::Object.make_direct(obj, @doc))
56
+ obj = HexaPDF::Dictionary.new({a: 5, b: HexaPDF::Object.new(:a, oid: 3, document: @doc)})
57
+ assert_same(obj, HexaPDF::Object.make_direct(obj, @doc))
58
+ assert_equal(:a, obj[:b])
56
59
  end
57
60
 
58
61
  it "works for simple values" do
59
62
  obj = HexaPDF::Object.new(5, oid: 1, document: @doc)
60
- assert_same(5, HexaPDF::Object.make_direct(obj))
63
+ assert_same(5, HexaPDF::Object.make_direct(obj, @doc))
61
64
  end
62
65
 
63
66
  it "works for hashes" do
64
67
  obj = HexaPDF::Dictionary.new({a: 5, b: HexaPDF::Object.new(:a, oid: 3, document: @doc)},
65
68
  oid: 1, document: @doc)
66
- assert_equal({a: 5, b: :a}, HexaPDF::Object.make_direct(obj))
69
+ assert_equal({a: 5, b: :a}, HexaPDF::Object.make_direct(obj, @doc))
67
70
  end
68
71
 
69
72
  it "works for arrays" do
70
73
  obj = HexaPDF::PDFArray.new([:b, HexaPDF::Object.new(:a, oid: 3, document: @doc)],
71
74
  oid: 1, document: @doc)
72
- assert_equal([:b, :a], HexaPDF::Object.make_direct(obj))
75
+ assert_equal([:b, :a], HexaPDF::Object.make_direct(obj, @doc))
76
+ end
77
+
78
+ it "resolves references" do
79
+ @doc.add(:Test, oid: 1)
80
+ obj = HexaPDF::PDFArray.new([HexaPDF::Reference.new(1, 0)],
81
+ oid: 2, document: @doc)
82
+ assert_equal([:Test], HexaPDF::Object.make_direct(obj, @doc))
83
+ assert(@doc.object(1).null?)
73
84
  end
74
85
  end
75
86
 
@@ -31,6 +31,13 @@ describe HexaPDF::StreamData do
31
31
  end
32
32
 
33
33
  describe "fiber" do
34
+ it "returns a duplicate for a FiberDoubleForString source" do
35
+ source = HexaPDF::Filter.source_from_string("str")
36
+ fiber = HexaPDF::StreamData.new(source).fiber
37
+ assert_equal("str", fiber.resume)
38
+ refute_same(source, fiber)
39
+ end
40
+
34
41
  it "returns a fiber for a Proc source" do
35
42
  s = HexaPDF::StreamData.new(proc { :source })
36
43
  assert_equal(:source, s.fiber.resume)
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.32.2)>>
43
+ <</Producer(HexaPDF version 0.34.0)>>
44
44
  endobj
45
45
  xref
46
46
  3 1
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
72
72
  141
73
73
  %%EOF
74
74
  6 0 obj
75
- <</Producer(HexaPDF version 0.32.2)>>
75
+ <</Producer(HexaPDF version 0.34.0)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -214,7 +214,7 @@ describe HexaPDF::Writer do
214
214
  <</Type/Page/MediaBox[0 0 595 842]/Parent 2 0 R/Resources<<>>>>
215
215
  endobj
216
216
  5 0 obj
217
- <</Producer(HexaPDF version 0.32.2)>>
217
+ <</Producer(HexaPDF version 0.34.0)>>
218
218
  endobj
219
219
  4 0 obj
220
220
  <</Root 1 0 R/Info 5 0 R/Size 6/Type/XRef/W[1 1 2]/Index[0 6]/Filter/FlateDecode/DecodeParms<</Columns 4/Predictor 12>>/Length 33>>stream
@@ -467,12 +467,20 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
467
467
  it "calculates the font size based on the rectangle height and border width" do
468
468
  @generator.create_appearances
469
469
  assert_operators(@widget[:AP][:N].stream,
470
- [:set_font_and_size, [:F1, 12.923875]],
470
+ [:set_font_and_size, [:F1, 13.235294]],
471
471
  range: 5)
472
472
  @widget.border_style(width: 2, color: :transparent)
473
473
  @generator.create_appearances
474
474
  assert_operators(@widget[:AP][:N].stream,
475
- [:set_font_and_size, [:F1, 11.487889]],
475
+ [:set_font_and_size, [:F1, 11.764706]],
476
+ range: 5)
477
+ end
478
+
479
+ it "shrinks the font size if necessary to fit the rectangle width" do
480
+ @field.field_value = "This is some arbitrary, long text"
481
+ @generator.create_appearances
482
+ assert_operators(@widget[:AP][:N].stream,
483
+ [:set_font_and_size, [:F1, 6.909955]],
476
484
  range: 5)
477
485
  end
478
486
 
@@ -573,7 +581,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
573
581
  assert_format("2, 3, #{style}, 0, \"E\", true",
574
582
  [[:set_device_rgb_non_stroking_color, [style % 2, 0.0, 0.0]],
575
583
  [:begin_text],
576
- [:set_text_matrix, [1, 0, 0, 1, 2, 3.240724]],
584
+ [:set_text_matrix, [1, 0, 0, 1, 2, 3.183272]],
577
585
  [:show_text, [result]]], 6..9)
578
586
  end
579
587
  end
@@ -598,10 +606,10 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
598
606
  [:append_rectangle, [1, 1, 98, 9.25]],
599
607
  [:clip_path_non_zero],
600
608
  [:end_path],
601
- [:set_font_and_size, [:F1, 6.641436]],
609
+ [:set_font_and_size, [:F1, 6.801471]],
602
610
  [:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
603
611
  [:begin_text],
604
- [:set_text_matrix, [1, 0, 0, 1, 2, 3.240724]],
612
+ [:set_text_matrix, [1, 0, 0, 1, 2, 3.183272]],
605
613
  [:show_text, ["Text"]],
606
614
  [:end_text],
607
615
  [:restore_graphics_state],
@@ -42,11 +42,12 @@ describe HexaPDF::Type::AcroForm::Form do
42
42
  end
43
43
 
44
44
  it "finds the root fields" do
45
- @doc.pages.add[:Annots] = [{FT: :Tx}, {FT: :Tx2, Parent: {FT: :Tx3}}]
46
- @doc.pages.add[:Annots] = [{Subtype: :Widget}]
45
+ @doc.pages.add[:Annots] = [{Subtype: :Widget, Rect: [0, 0, 0, 0], FT: :Tx},
46
+ {Subtype: :Widget, Rect: [0, 0, 0, 0], FT: :Tx, Parent: {FT: :Tx}}]
47
+ @doc.pages.add[:Annots] = [{Subtype: :Widget, Rect: [0, 0, 0, 0]}]
47
48
  @doc.pages.add
48
49
 
49
- result = [{FT: :Tx}, {FT: :Tx3}]
50
+ result = [{Subtype: :Widget, Rect: [0, 0, 0, 0], FT: :Tx}, {FT: :Tx}]
50
51
  root_fields = @acro_form.find_root_fields
51
52
  assert_equal(result, root_fields.map(&:value))
52
53
  assert_kind_of(HexaPDF::Type::AcroForm::TextField, root_fields[0])
@@ -0,0 +1,40 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require 'hexapdf/type/actions/set_ocg_state'
6
+
7
+ describe HexaPDF::Type::Actions::SetOCGState do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @action = HexaPDF::Type::Actions::SetOCGState.new({}, document: @doc)
11
+ @ocg = @doc.optional_content.add_ocg('Test')
12
+ end
13
+
14
+ describe "add_state_change" do
15
+ it "allows using Ruby-esque and PDF type names for the state change type" do
16
+ @action.add_state_change(:on, @ocg)
17
+ @action.add_state_change(:ON, @ocg)
18
+ @action.add_state_change(:off, @ocg)
19
+ @action.add_state_change(:OFF, @ocg)
20
+ @action.add_state_change(:toggle, @ocg)
21
+ @action.add_state_change(:Toggle, @ocg)
22
+ assert_equal([:ON, @ocg, :ON, @ocg, :OFF, @ocg, :OFF, @ocg, :Toggle, @ocg, :Toggle, @ocg],
23
+ @action[:State].value)
24
+ end
25
+
26
+ it "allows specifying more than one OCG" do
27
+ @action.add_state_change(:on, [@ocg, @doc.optional_content.add_ocg('Test2')])
28
+ assert_equal([:ON, @ocg, @doc.optional_content.ocg('Test2')], @action[:State].value)
29
+ end
30
+
31
+ it "raises an error if the provide state change type is invalid" do
32
+ assert_raises(ArgumentError) { @action.add_state_change(:unknown, nil) }
33
+ end
34
+
35
+ it "raises an error if an OCG specified via a string does not exist" do
36
+ error = assert_raises(HexaPDF::Error) { @action.add_state_change(:on, "Unknown") }
37
+ assert_match(/Invalid OCG.*Unknown.*specified/, error.message)
38
+ end
39
+ end
40
+ end
@@ -39,6 +39,17 @@ describe HexaPDF::Type::Catalog do
39
39
  assert_same(outline, @catalog.outline)
40
40
  end
41
41
 
42
+ it "uses or creates the optional content properties dictionary on access" do
43
+ @catalog[:OCProperties] = hash = {}
44
+ assert_equal(:XXOCProperties, @catalog.optional_content.type)
45
+ assert_same(hash, @catalog.optional_content.value)
46
+
47
+ @catalog.delete(:OCProperties)
48
+ oc = @catalog.optional_content
49
+ assert_equal([], oc[:OCGs])
50
+ assert_equal(:XXOCConfiguration, oc[:D].type)
51
+ end
52
+
42
53
  describe "acro_form" do
43
54
  it "returns an existing form object" do
44
55
  @catalog[:AcroForm] = :test
@@ -1,6 +1,8 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  require 'test_helper'
4
+ require 'stringio'
5
+ require 'tempfile'
4
6
  require 'hexapdf/document'
5
7
  require 'hexapdf/type/form'
6
8
 
@@ -85,6 +87,18 @@ describe HexaPDF::Type::Form do
85
87
  @form.process_contents(processor, original_resources: resources)
86
88
  assert_same(resources, processor.resources)
87
89
  end
90
+
91
+ it "uses the referenced content in case of a Reference XObject" do
92
+ @form[:Ref] = @doc.add({F: {}})
93
+ io = StringIO.new
94
+ HexaPDF::Document.new.tap {|d| d.pages.add.canvas.line_width(5) }.write(io)
95
+ @form[:Ref][:F].embed(io, name: 'test')
96
+ @form[:Ref][:Page] = 0
97
+
98
+ processor = HexaPDF::TestUtils::OperatorRecorder.new
99
+ @form.process_contents(processor)
100
+ assert_equal([[:set_line_width, [5]]], processor.recorded_ops)
101
+ end
88
102
  end
89
103
 
90
104
  describe "canvas" do
@@ -116,4 +130,109 @@ describe HexaPDF::Type::Form do
116
130
  assert_raises(HexaPDF::Error) { @form.canvas }
117
131
  end
118
132
  end
133
+
134
+ describe "reference_xobject?" do
135
+ it "returns true if the form is a reference XObject" do
136
+ refute(@form.reference_xobject?)
137
+ @form[:Ref] = {}
138
+ assert(@form.reference_xobject?)
139
+ end
140
+ end
141
+
142
+ describe "referenced_content" do
143
+ before do
144
+ @form[:BBox] = [10, 10, 110, 60]
145
+ @form[:Matrix] = [1, 0, 0, 1, 10, 20]
146
+ @form[:Ref] = @doc.add({F: {}})
147
+ @ref = @form[:Ref]
148
+ end
149
+
150
+ it "returns a Form XObject with the imported page from an embedded file" do
151
+ io = StringIO.new
152
+ HexaPDF::Document.new.tap {|d| d.pages.add.canvas.line_width(5) }.write(io)
153
+ @ref[:F].embed(io, name: 'test.pdf')
154
+ @ref[:Page] = 0
155
+
156
+ ref_form = @form.referenced_content
157
+ refute_nil(ref_form)
158
+ assert_equal([10, 10, 110, 60], ref_form[:BBox].value)
159
+ assert_equal([1, 0, 0, 1, 10, 20], ref_form[:Matrix].value)
160
+ assert_equal("5 w\n", ref_form.contents)
161
+ end
162
+
163
+ it "returns a Form XObject with the imported page from an external file" do
164
+ file = Tempfile.new('hexapdf')
165
+ HexaPDF::Document.new.tap {|d| d.pages.add.canvas.line_width(5) }.write(file.path)
166
+ @ref[:F].path = file.path
167
+ @ref[:Page] = 0
168
+ assert_equal("5 w\n", @form.referenced_content.contents)
169
+ end
170
+
171
+ it "also works with a page label" do
172
+ file = Tempfile.new('hexapdf')
173
+ HexaPDF::Document.new.tap do |d|
174
+ d.pages.add
175
+ d.pages.add
176
+ d.pages.add.canvas.line_width(5)
177
+ d.pages.add
178
+ d.pages.add_labelling_range(1, numbering_style: :decimal, prefix: 'Test', start_number: 4)
179
+ end.write(file.path)
180
+ @ref[:F].path = file.path
181
+ @ref[:Page] = 'Test5'
182
+ assert_equal("5 w\n", @form.referenced_content.contents)
183
+ end
184
+
185
+ it "flattens printable annotations into the page's content stream" do
186
+ io = StringIO.new
187
+ HexaPDF::Document.new.tap do |d|
188
+ d.pages.add.canvas.line_width(5)
189
+ tf = d.acro_form(create: true).create_text_field('text')
190
+ widget = tf.create_widget(d.pages[0], Rect: [10, 10, 30, 30])
191
+ widget.border_style(color: "black")
192
+ widget = tf.create_widget(d.pages[0], Rect: [40, 10, 70, 30])
193
+ widget.border_style(color: "red")
194
+ tf.field_value = 't'
195
+ widget.unflag(:print)
196
+ end.write(io)
197
+ @ref[:F].embed(io, name: 'Test')
198
+ @ref[:Page] = 0
199
+ assert_equal(" q Q q 5 w\n Q q q\n1.0 0 0 1.0 10.0 10.0 cm\n/XO1 Do\nQ\n Q ",
200
+ @form.referenced_content.contents)
201
+ end
202
+
203
+ it "returns nil if the form is not a reference XObject" do
204
+ @form.delete(:Ref)
205
+ assert_nil(@form.referenced_content)
206
+ end
207
+
208
+ it "returns nil if the file is not embedded and not found" do
209
+ @ref[:F].path = '/tmp/non_existing_path'
210
+ assert_nil(@form.referenced_content)
211
+ end
212
+
213
+ it "returns nil if the page referenced by page number is not found" do
214
+ io = StringIO.new
215
+ HexaPDF::Document.new.tap {|d| d.pages.add; d.pages.add }.write(io)
216
+ @ref[:F].embed(io, name: 'test.pdf')
217
+ @ref[:Page] = 5
218
+ assert_nil(@form.referenced_content)
219
+ end
220
+
221
+ it "returns nil if the page referenced by page label is not found" do
222
+ io = StringIO.new
223
+ HexaPDF::Document.new.tap do |d|
224
+ d.pages.add
225
+ d.pages.add
226
+ d.pages.add_labelling_range(1, numbering_style: :decimal, prefix: 'Test')
227
+ end.write(io)
228
+ @ref[:F].embed(io, name: 'test.pdf')
229
+ @ref[:Page] = 'Test5'
230
+ assert_nil(@form.referenced_content)
231
+ end
232
+
233
+ it "returns nil if an error happens during processing" do
234
+ @ref[:F].embed(StringIO.new('temp'), name: 'test.pdf')
235
+ assert_nil(@form.referenced_content)
236
+ end
237
+ end
119
238
  end