hexapdf 0.32.2 → 0.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (202) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +63 -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/lib/hexapdf/cli/command.rb +5 -2
  19. data/lib/hexapdf/cli/form.rb +5 -5
  20. data/lib/hexapdf/cli/inspect.rb +3 -3
  21. data/lib/hexapdf/composer.rb +104 -52
  22. data/lib/hexapdf/configuration.rb +44 -39
  23. data/lib/hexapdf/content/canvas.rb +393 -267
  24. data/lib/hexapdf/content/color_space.rb +72 -25
  25. data/lib/hexapdf/content/graphic_object/arc.rb +57 -24
  26. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -23
  27. data/lib/hexapdf/content/graphic_object/geom2d.rb +47 -6
  28. data/lib/hexapdf/content/graphic_object/solid_arc.rb +58 -36
  29. data/lib/hexapdf/content/graphic_object.rb +6 -7
  30. data/lib/hexapdf/content/graphics_state.rb +54 -45
  31. data/lib/hexapdf/content/operator.rb +52 -54
  32. data/lib/hexapdf/content/parser.rb +2 -2
  33. data/lib/hexapdf/content/processor.rb +15 -15
  34. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  35. data/lib/hexapdf/content.rb +5 -0
  36. data/lib/hexapdf/dictionary.rb +6 -5
  37. data/lib/hexapdf/dictionary_fields.rb +42 -14
  38. data/lib/hexapdf/digital_signature/cms_handler.rb +2 -2
  39. data/lib/hexapdf/digital_signature/handler.rb +1 -1
  40. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +2 -3
  41. data/lib/hexapdf/digital_signature/signature.rb +6 -6
  42. data/lib/hexapdf/digital_signature/signatures.rb +13 -12
  43. data/lib/hexapdf/digital_signature/signing/default_handler.rb +14 -5
  44. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +2 -4
  45. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +4 -4
  46. data/lib/hexapdf/digital_signature/signing.rb +4 -0
  47. data/lib/hexapdf/digital_signature/verification_result.rb +2 -2
  48. data/lib/hexapdf/digital_signature.rb +7 -2
  49. data/lib/hexapdf/document/destinations.rb +12 -11
  50. data/lib/hexapdf/document/files.rb +1 -1
  51. data/lib/hexapdf/document/fonts.rb +1 -1
  52. data/lib/hexapdf/document/layout.rb +167 -39
  53. data/lib/hexapdf/document/pages.rb +3 -2
  54. data/lib/hexapdf/document.rb +89 -55
  55. data/lib/hexapdf/encryption/aes.rb +5 -5
  56. data/lib/hexapdf/encryption/arc4.rb +1 -1
  57. data/lib/hexapdf/encryption/fast_aes.rb +2 -2
  58. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  59. data/lib/hexapdf/encryption/identity.rb +1 -1
  60. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  61. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  62. data/lib/hexapdf/encryption/security_handler.rb +31 -24
  63. data/lib/hexapdf/encryption/standard_security_handler.rb +45 -36
  64. data/lib/hexapdf/encryption.rb +7 -2
  65. data/lib/hexapdf/error.rb +18 -0
  66. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  67. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  68. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  69. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  70. data/lib/hexapdf/filter/pass_through.rb +1 -1
  71. data/lib/hexapdf/filter/predictor.rb +1 -1
  72. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  73. data/lib/hexapdf/filter.rb +55 -6
  74. data/lib/hexapdf/font/cmap/parser.rb +2 -2
  75. data/lib/hexapdf/font/cmap.rb +1 -1
  76. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  77. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  78. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +2 -2
  79. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  80. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  81. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +3 -3
  82. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  83. data/lib/hexapdf/font/invalid_glyph.rb +3 -0
  84. data/lib/hexapdf/font/true_type_wrapper.rb +17 -4
  85. data/lib/hexapdf/font/type1_wrapper.rb +19 -4
  86. data/lib/hexapdf/font_loader/from_configuration.rb +5 -2
  87. data/lib/hexapdf/font_loader/from_file.rb +5 -5
  88. data/lib/hexapdf/font_loader/standard14.rb +3 -3
  89. data/lib/hexapdf/font_loader.rb +3 -0
  90. data/lib/hexapdf/image_loader/jpeg.rb +2 -2
  91. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  92. data/lib/hexapdf/image_loader/png.rb +2 -2
  93. data/lib/hexapdf/image_loader.rb +1 -1
  94. data/lib/hexapdf/importer.rb +13 -0
  95. data/lib/hexapdf/layout/box.rb +9 -2
  96. data/lib/hexapdf/layout/box_fitter.rb +2 -2
  97. data/lib/hexapdf/layout/column_box.rb +18 -4
  98. data/lib/hexapdf/layout/frame.rb +30 -12
  99. data/lib/hexapdf/layout/image_box.rb +5 -0
  100. data/lib/hexapdf/layout/inline_box.rb +1 -0
  101. data/lib/hexapdf/layout/list_box.rb +17 -1
  102. data/lib/hexapdf/layout/page_style.rb +4 -4
  103. data/lib/hexapdf/layout/style.rb +18 -3
  104. data/lib/hexapdf/layout/table_box.rb +682 -0
  105. data/lib/hexapdf/layout/text_box.rb +5 -3
  106. data/lib/hexapdf/layout/text_fragment.rb +1 -1
  107. data/lib/hexapdf/layout/text_layouter.rb +12 -4
  108. data/lib/hexapdf/layout.rb +1 -0
  109. data/lib/hexapdf/name_tree_node.rb +1 -1
  110. data/lib/hexapdf/number_tree_node.rb +1 -1
  111. data/lib/hexapdf/object.rb +18 -7
  112. data/lib/hexapdf/parser.rb +7 -7
  113. data/lib/hexapdf/pdf_array.rb +1 -1
  114. data/lib/hexapdf/rectangle.rb +1 -1
  115. data/lib/hexapdf/reference.rb +1 -1
  116. data/lib/hexapdf/revision.rb +1 -1
  117. data/lib/hexapdf/revisions.rb +3 -3
  118. data/lib/hexapdf/serializer.rb +15 -15
  119. data/lib/hexapdf/stream.rb +4 -2
  120. data/lib/hexapdf/tokenizer.rb +14 -14
  121. data/lib/hexapdf/type/acro_form/appearance_generator.rb +22 -22
  122. data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
  123. data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
  124. data/lib/hexapdf/type/acro_form/field.rb +2 -2
  125. data/lib/hexapdf/type/acro_form/form.rb +1 -1
  126. data/lib/hexapdf/type/acro_form/signature_field.rb +4 -4
  127. data/lib/hexapdf/type/acro_form/text_field.rb +1 -1
  128. data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
  129. data/lib/hexapdf/type/acro_form.rb +1 -1
  130. data/lib/hexapdf/type/action.rb +1 -1
  131. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  132. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  133. data/lib/hexapdf/type/actions/launch.rb +1 -1
  134. data/lib/hexapdf/type/actions/uri.rb +1 -1
  135. data/lib/hexapdf/type/actions.rb +1 -1
  136. data/lib/hexapdf/type/annotation.rb +3 -3
  137. data/lib/hexapdf/type/annotations/link.rb +1 -1
  138. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  139. data/lib/hexapdf/type/annotations/text.rb +1 -1
  140. data/lib/hexapdf/type/annotations/widget.rb +2 -2
  141. data/lib/hexapdf/type/annotations.rb +1 -1
  142. data/lib/hexapdf/type/catalog.rb +1 -1
  143. data/lib/hexapdf/type/cid_font.rb +3 -3
  144. data/lib/hexapdf/type/embedded_file.rb +1 -1
  145. data/lib/hexapdf/type/file_specification.rb +2 -2
  146. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  147. data/lib/hexapdf/type/font_simple.rb +2 -2
  148. data/lib/hexapdf/type/font_type0.rb +3 -3
  149. data/lib/hexapdf/type/font_type3.rb +1 -1
  150. data/lib/hexapdf/type/form.rb +1 -1
  151. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  152. data/lib/hexapdf/type/icon_fit.rb +1 -1
  153. data/lib/hexapdf/type/image.rb +1 -1
  154. data/lib/hexapdf/type/info.rb +1 -1
  155. data/lib/hexapdf/type/mark_information.rb +1 -1
  156. data/lib/hexapdf/type/names.rb +2 -2
  157. data/lib/hexapdf/type/object_stream.rb +2 -1
  158. data/lib/hexapdf/type/outline.rb +1 -1
  159. data/lib/hexapdf/type/outline_item.rb +1 -1
  160. data/lib/hexapdf/type/page.rb +19 -10
  161. data/lib/hexapdf/type/page_label.rb +1 -1
  162. data/lib/hexapdf/type/page_tree_node.rb +1 -1
  163. data/lib/hexapdf/type/resources.rb +1 -1
  164. data/lib/hexapdf/type/trailer.rb +2 -2
  165. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  166. data/lib/hexapdf/type/xref_stream.rb +2 -2
  167. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  168. data/lib/hexapdf/version.rb +1 -1
  169. data/lib/hexapdf/writer.rb +4 -4
  170. data/lib/hexapdf/xref_section.rb +2 -2
  171. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +11 -1
  172. data/test/hexapdf/content/graphic_object/test_geom2d.rb +7 -0
  173. data/test/hexapdf/content/test_canvas.rb +0 -1
  174. data/test/hexapdf/digital_signature/test_signatures.rb +22 -0
  175. data/test/hexapdf/document/test_files.rb +2 -2
  176. data/test/hexapdf/document/test_layout.rb +98 -0
  177. data/test/hexapdf/encryption/test_security_handler.rb +12 -11
  178. data/test/hexapdf/encryption/test_standard_security_handler.rb +35 -23
  179. data/test/hexapdf/font/test_true_type_wrapper.rb +18 -1
  180. data/test/hexapdf/font/test_type1_wrapper.rb +15 -1
  181. data/test/hexapdf/layout/test_box.rb +1 -1
  182. data/test/hexapdf/layout/test_column_box.rb +65 -21
  183. data/test/hexapdf/layout/test_frame.rb +14 -14
  184. data/test/hexapdf/layout/test_image_box.rb +4 -0
  185. data/test/hexapdf/layout/test_inline_box.rb +5 -0
  186. data/test/hexapdf/layout/test_list_box.rb +40 -6
  187. data/test/hexapdf/layout/test_page_style.rb +3 -2
  188. data/test/hexapdf/layout/test_style.rb +50 -0
  189. data/test/hexapdf/layout/test_table_box.rb +722 -0
  190. data/test/hexapdf/layout/test_text_box.rb +18 -0
  191. data/test/hexapdf/layout/test_text_layouter.rb +4 -0
  192. data/test/hexapdf/test_dictionary_fields.rb +4 -1
  193. data/test/hexapdf/test_document.rb +1 -0
  194. data/test/hexapdf/test_filter.rb +8 -0
  195. data/test/hexapdf/test_importer.rb +9 -0
  196. data/test/hexapdf/test_object.rb +16 -5
  197. data/test/hexapdf/test_stream.rb +7 -0
  198. data/test/hexapdf/test_writer.rb +3 -3
  199. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +13 -5
  200. data/test/hexapdf/type/acro_form/test_form.rb +4 -3
  201. data/test/hexapdf/type/test_page.rb +18 -4
  202. metadata +17 -8
@@ -65,7 +65,7 @@ module HexaPDF
65
65
  # cross-reference section and trailer information and then written. Otherwise a normal
66
66
  # cross-reference section plus trailer are written.
67
67
  #
68
- # See: PDF1.7 s7.5.8
68
+ # See: PDF2.0 s7.5.8
69
69
  class XRefStream < HexaPDF::Stream
70
70
 
71
71
  define_type :XRef
@@ -161,7 +161,7 @@ module HexaPDF
161
161
  when TYPE_COMPRESSED
162
162
  xref.add_compressed_entry(oid, field2, field3)
163
163
  else
164
- nil # Ignore entry as per PDF1.7 s7.5.8.3
164
+ nil # Ignore entry as per PDF2.0 s7.5.8.3
165
165
  end
166
166
  start_pos = end_pos
167
167
  end
@@ -49,7 +49,7 @@ module HexaPDF
49
49
  # When a PDF file is written, text strings are automatically encoded in either PDFDocEncoding
50
50
  # or UTF-16BE depending on the characters in the text string.
51
51
  #
52
- # See: PDF1.7 s7.9.2, D.1, D.3
52
+ # See: PDF2.0 s7.9.2, D.1, D.3
53
53
  module PDFDocEncoding
54
54
 
55
55
  # :nodoc:
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '0.32.2'
40
+ VERSION = '0.33.0'
41
41
 
42
42
  end
@@ -124,7 +124,7 @@ module HexaPDF
124
124
 
125
125
  # Writes the PDF file header.
126
126
  #
127
- # See: PDF1.7 s7.5.2
127
+ # See: PDF2.0 s7.5.2
128
128
  def write_file_header
129
129
  @io << "%PDF-#{@document.version}\n%\xCF\xEC\xFF\xE8\xD7\xCB\xCD\n"
130
130
  end
@@ -225,7 +225,7 @@ module HexaPDF
225
225
 
226
226
  # Writes the cross-reference section.
227
227
  #
228
- # See: PDF1.7 s7.5.4
228
+ # See: PDF2.0 s7.5.4
229
229
  def write_xref_section(xref_section)
230
230
  @io << "xref\n"
231
231
  xref_section.each_subsection do |entries|
@@ -245,14 +245,14 @@ module HexaPDF
245
245
 
246
246
  # Writes the trailer dictionary.
247
247
  #
248
- # See: PDF1.7 s7.5.5
248
+ # See: PDF2.0 s7.5.5
249
249
  def write_trailer(trailer)
250
250
  @io << "trailer\n#{@serializer.serialize(trailer)}\n"
251
251
  end
252
252
 
253
253
  # Writes the startxref line needed for cross-reference sections and cross-reference streams.
254
254
  #
255
- # See: PDF1.7 s7.5.5, s7.5.8
255
+ # See: PDF2.0 s7.5.5, s7.5.8
256
256
  def write_startxref(startxref)
257
257
  @io << "startxref\n#{startxref}\n%%EOF\n"
258
258
  end
@@ -46,7 +46,7 @@ module HexaPDF
46
46
  #
47
47
  # Note that a cross-reference section may contain a single object number only once.
48
48
  #
49
- # See: HexaPDF::Revision, PDF1.7 s7.5.4, s7.5.8
49
+ # See: HexaPDF::Revision, PDF2.0 s7.5.4, s7.5.8
50
50
  class XRefSection < Utils::ObjectHash
51
51
 
52
52
  # One entry of a cross-reference section or stream.
@@ -69,7 +69,7 @@ module HexaPDF
69
69
  #
70
70
  # Objects in an object stream always have a generation number of 0!
71
71
  #
72
- # See: PDF1.7 s7.5.4, s7.5.8
72
+ # See: PDF2.0 s7.5.4, s7.5.8
73
73
  Entry = Struct.new(:type, :oid, :gen, :pos, :objstm) do
74
74
  def free?
75
75
  type == :free
@@ -16,13 +16,15 @@ describe HexaPDF::Content::GraphicObject::EndpointArc do
16
16
  assert_equal(0, arc.inclination)
17
17
  assert(arc.large_arc)
18
18
  refute(arc.clockwise)
19
+ assert_nil(arc.max_curves)
19
20
  end
20
21
  end
21
22
 
22
23
  describe "configure" do
23
24
  it "changes the values" do
24
25
  arc = HexaPDF::Content::GraphicObject::EndpointArc.new
25
- arc.configure(x: 1, y: 2, a: 3, b: 4, inclination: 5, large_arc: false, clockwise: true)
26
+ arc.configure(x: 1, y: 2, a: 3, b: 4, inclination: 5, large_arc: false, clockwise: true,
27
+ max_curves: 8)
26
28
  assert_equal(1, arc.x)
27
29
  assert_equal(2, arc.y)
28
30
  assert_equal(3, arc.a)
@@ -30,6 +32,7 @@ describe HexaPDF::Content::GraphicObject::EndpointArc do
30
32
  assert_equal(5, arc.inclination)
31
33
  refute(arc.large_arc)
32
34
  assert(arc.clockwise)
35
+ assert_equal(8, arc.max_curves)
33
36
  end
34
37
  end
35
38
 
@@ -94,5 +97,12 @@ describe HexaPDF::Content::GraphicObject::EndpointArc do
94
97
  clockwise: false)
95
98
  assert_equal(arc_data, @page.contents)
96
99
  end
100
+
101
+ it "assigns the max curves to the generated arc" do
102
+ arc = HexaPDF::Content::GraphicObject::EndpointArc.new
103
+ arc.configure(a: 1, b: 1, x: -1, y: 0, max_curves: 10)
104
+ hash = arc.send(:compute_arc_values, 1, 0)
105
+ assert_equal(10, hash[:max_curves])
106
+ end
97
107
  end
98
108
  end
@@ -51,6 +51,13 @@ describe HexaPDF::Content::GraphicObject::Geom2D do
51
51
  [[:move_to, [5, 6]], [:line_to, [10, 11]], [:stroke_path]])
52
52
  end
53
53
 
54
+ it "draws a Geom2D::Rectangle onto the canvas" do
55
+ @obj.object = Geom2D::Rectangle(5, 6, 20, 50)
56
+ @obj.draw(@canvas)
57
+ assert_operators(@canvas.contents,
58
+ [[:append_rectangle, [5, 6, 20, 50]], [:stroke_path]])
59
+ end
60
+
54
61
  it "draws a Geom2D::Polygon onto the canvas" do
55
62
  @obj.object = Geom2D::Polygon([5, 6], [10, 11], [7, 9])
56
63
  @obj.draw(@canvas)
@@ -1193,7 +1193,6 @@ describe HexaPDF::Content::Canvas do
1193
1193
  assert_operators(@canvas.contents, [[:set_font_and_size, [:F1, 10]],
1194
1194
  [:begin_text],
1195
1195
  [:set_text_matrix, [1, 0, 0, 1, 100, 100]],
1196
- [:set_leading, [10]],
1197
1196
  [:show_text_with_positioning, [["Hallo"]]]])
1198
1197
  end
1199
1198
 
@@ -107,6 +107,28 @@ describe HexaPDF::DigitalSignature::Signatures do
107
107
  assert_equal(1, field.each_widget.count)
108
108
  end
109
109
 
110
+ it "creates an empty widget on the first page for the signature field if necessary" do
111
+ @doc.pages.add
112
+ field = @doc.acro_form(create: true).create_signature_field('Signature2')
113
+ field.field_value = sig = @doc.add({Type: :Sig, key: :value})
114
+ @doc.signatures.add(@io, @handler, signature: sig)
115
+ widgets = field.each_widget.to_a
116
+ assert_equal(1, widgets.size)
117
+ assert_equal(@doc.pages[0], widgets[0][:P])
118
+ assert_equal([0, 0, 0, 0], widgets[0][:Rect])
119
+ end
120
+
121
+ it "handles a bug in Adobe Acrobat related to images not showing without a /Resources entry" do
122
+ field = @doc.acro_form(create: true).create_signature_field('Signature')
123
+ image = @doc.add({Type: :XObject, Subtype: :Image, Width: 1, Height: 1, ColorSpace: :DeviceGray,
124
+ BitsPerComponent: 8}, stream: 'A')
125
+ field.create_widget(@doc.pages[0], Rect: [0, 0, 100, 100]).create_appearance.
126
+ canvas.xobject(image, at: [0, 0])
127
+ @doc.signatures.add(@io, @handler, signature: field)
128
+ assert(image.key?(:Resources))
129
+ assert_equal({}, image[:Resources])
130
+ end
131
+
110
132
  it "handles different xref section types correctly when determing the offsets" do
111
133
  @doc.delete(7)
112
134
  sig = @doc.signatures.add(@io, @handler, write_options: {update_fields: false})
@@ -55,8 +55,8 @@ describe HexaPDF::Document::Files do
55
55
  spec2 = @doc.add({Type: :Filespec})
56
56
  @doc.pages.add # page without annot
57
57
  @doc.pages.add[:Annots] = [
58
- {Subtype: :FileAttachment, FS: HexaPDF::Reference.new(spec1.oid, spec1.gen)},
59
- {Subtype: :FileAttachment, FS: spec2},
58
+ {Subtype: :FileAttachment, Rect: [0, 0, 0, 0], FS: HexaPDF::Reference.new(spec1.oid, spec1.gen)},
59
+ {Subtype: :FileAttachment, Rect: [0, 0, 0, 0], FS: spec2},
60
60
  {},
61
61
  ]
62
62
  assert_equal([spec1, spec2], @doc.files.to_a)
@@ -59,6 +59,54 @@ describe HexaPDF::Document::Layout::ChildrenCollector do
59
59
  end
60
60
  end
61
61
 
62
+ describe HexaPDF::Document::Layout::CellArgumentCollector do
63
+ before do
64
+ @args = HexaPDF::Document::Layout::CellArgumentCollector.new(20, 10)
65
+ end
66
+
67
+ describe "[]" do
68
+ def check_argument_info(info, rows, cols, args)
69
+ assert_equal(rows, info.rows)
70
+ assert_equal(cols, info.cols)
71
+ assert_equal(args, info.args)
72
+ end
73
+
74
+ it "allows assigning to all cells" do
75
+ @args[] = {key: :value}
76
+ check_argument_info(@args.argument_infos.first, 0..19, 0..9, {key: :value})
77
+ end
78
+
79
+ it "allows assigning to all columns of a range of rows" do
80
+ @args[1..4] = {key: :value}
81
+ check_argument_info(@args.argument_infos.first, 1..4, 0..9, {key: :value})
82
+ end
83
+
84
+ it "allows assigning to the intersection of a range of rows with a range of columns" do
85
+ @args[1..4, 3..5] = {key: :value}
86
+ check_argument_info(@args.argument_infos.first, 1..4, 3..5, {key: :value})
87
+ end
88
+
89
+ it "allows selecting a single row or column" do
90
+ @args[1, 3] = {key: :value}
91
+ check_argument_info(@args.argument_infos.first, 1..1, 3..3, {key: :value})
92
+ end
93
+
94
+ it "allows using negative indices" do
95
+ @args[-3..-1, -5..-2] = {key: :value}
96
+ check_argument_info(@args.argument_infos.first, 17..19, 5..8, {key: :value})
97
+ end
98
+ end
99
+
100
+ describe "retrieve_arguments_for" do
101
+ it "merges all argument hashes, with later defined ones overridding prior ones" do
102
+ @args[] = {key: :value, a: :b}
103
+ @args[3..7] = {a: :c}
104
+ @args[5, 6] = {e: :f}
105
+ assert_equal({key: :value, a: :c, e: :f}, @args.retrieve_arguments_for(5, 6))
106
+ end
107
+ end
108
+ end
109
+
62
110
  describe HexaPDF::Document::Layout do
63
111
  before do
64
112
  @doc = HexaPDF::Document.new
@@ -290,6 +338,56 @@ describe HexaPDF::Document::Layout do
290
338
  end
291
339
  end
292
340
 
341
+ describe "table_box" do
342
+ it "creates a table box" do
343
+ box = @layout.table_box([['m']], column_widths: [100], header: proc { [['a']] },
344
+ footer: proc { [['b']] }, cell_style: {background_color: "red"},
345
+ width: 100, height: 300, style: {background_color: "blue"},
346
+ properties: {key: :value}, border: {width: 1})
347
+ assert_equal(100, box.width)
348
+ assert_equal(300, box.height)
349
+ assert_equal("blue", box.style.background_color)
350
+ assert_equal(1, box.style.border.width.left)
351
+ assert_equal({key: :value}, box.properties)
352
+ assert_equal(HexaPDF::Layout::TextBox, box.cells[0, 0].children.class)
353
+ assert_equal([100], box.column_widths)
354
+ assert_equal('a', box.header_cells[0, 0].children)
355
+ assert_equal('b', box.footer_cells[0, 0].children)
356
+ end
357
+
358
+ it "doesn't modify the children of cells if they are already in the correct form" do
359
+ image_path = File.join(TEST_DATA_DIR, 'images', 'gray.jpg')
360
+ cell0 = @layout.text('a')
361
+ cell1 = [@layout.text('b'), @layout.image(image_path)]
362
+ box = @layout.table_box([[cell0, cell1]])
363
+ assert_same(cell0, box.cells[0, 0].children)
364
+ assert_same(cell1, box.cells[0, 1].children)
365
+ end
366
+
367
+ it "converts cells containing other than Box and Array instances" do
368
+ box = @layout.table_box([['a', 5]])
369
+ assert_kind_of(HexaPDF::Layout::TextBox, box.cells[0, 0].children)
370
+ assert_kind_of(HexaPDF::Layout::TextBox, box.cells[0, 1].children)
371
+ end
372
+
373
+ it "allows customizing the creation arguments" do
374
+ box = @layout.table_box([['a']]) do |args|
375
+ args[] = {font_size: 20}
376
+ end
377
+ assert_equal(20, box.cells[0, 0].children.style.font_size)
378
+ refute_equal(20, box.cells[0, 0].style.font_size)
379
+ end
380
+
381
+ it "allows styling table cells themselves" do
382
+ box = @layout.table_box([['a', @layout.text('b')]]) do |args|
383
+ args[] = {cell: {background_color: "green"}}
384
+ end
385
+ assert_equal('green', box.cells[0, 0].style.background_color)
386
+ assert_nil(box.cells[0, 0].children.style.background_color)
387
+ assert_equal('green', box.cells[0, 1].style.background_color)
388
+ end
389
+ end
390
+
293
391
  describe "lorem_ipsum_box" do
294
392
  it "creates a standard lorem ipsum box" do
295
393
  box = @layout.lorem_ipsum_box(width: 10, height: 15, font_size: 15)
@@ -17,13 +17,6 @@ describe HexaPDF::Encryption::EncryptionDictionary do
17
17
  assert(@dict.must_be_indirect?)
18
18
  end
19
19
 
20
- it "validates the /V value" do
21
- @dict[:V] = 1
22
- assert(@dict.validate)
23
- @dict[:V] = 3
24
- refute(@dict.validate)
25
- end
26
-
27
20
  it "validates the /Length field when /V=2" do
28
21
  @dict[:V] = 2
29
22
  refute(@dict.validate)
@@ -240,6 +233,7 @@ describe HexaPDF::Encryption::SecurityHandler do
240
233
  [:identity, 128, {V: 4, StrF: :Mine, CF: {Mine: {CFM: :None}}}],
241
234
  [:identity, 128, {V: 4, CF: {Mine: {CFM: :AESV2}}}],
242
235
  ].each do |alg, length, dict|
236
+ dict[:Filter] = :Test
243
237
  @enc.strf = alg
244
238
  @enc.set_up_encryption(key_length: length, algorithm: (alg == :identity ? :aes : alg))
245
239
  @obj[:X] = @enc.encrypt_string('data', @obj)
@@ -249,7 +243,7 @@ describe HexaPDF::Encryption::SecurityHandler do
249
243
  end
250
244
 
251
245
  it "selects the correct algorithm for string, stream and embedded file decryption" do
252
- @handler.set_up_decryption({V: 4, StrF: :Mine, StmF: :Mine, EFF: :Mine,
246
+ @handler.set_up_decryption({Filter: :Test, V: 4, StrF: :Mine, StmF: :Mine, EFF: :Mine,
253
247
  CF: {Mine: {CFM: :V2}}})
254
248
  assert_equal(HexaPDF::Encryption::FastARC4, @handler.send(:embedded_file_algorithm))
255
249
  assert_equal(HexaPDF::Encryption::FastARC4, @handler.send(:string_algorithm))
@@ -263,16 +257,23 @@ describe HexaPDF::Encryption::SecurityHandler do
263
257
  @handler.encryption_details)
264
258
  end
265
259
 
260
+ it "fails if the encryption dictionary is not valid" do
261
+ exp = assert_raises(HexaPDF::Error) do
262
+ @handler.set_up_decryption({V: 5})
263
+ end
264
+ assert_match(/Validation error for encryption dictionary.*Required field Filter/i, exp.message)
265
+ end
266
+
266
267
  it "fails for unsupported /V values in the dict" do
267
268
  exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
268
- @handler.set_up_decryption({V: 3})
269
+ @handler.set_up_decryption({Filter: :Text, V: 3})
269
270
  end
270
271
  assert_match(/Unsupported encryption version/i, exp.message)
271
272
  end
272
273
 
273
274
  it "fails for unsupported crypt filter encryption methods" do
274
275
  exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
275
- @handler.set_up_decryption({V: 4, StrF: :Mine, CF: {Mine: {CFM: :Unknown}}})
276
+ @handler.set_up_decryption({Filter: :Test, V: 4, StrF: :Mine, CF: {Mine: {CFM: :Unknown}}})
276
277
  end
277
278
  assert_match(/Unsupported encryption method/i, exp.message)
278
279
  end
@@ -280,7 +281,7 @@ describe HexaPDF::Encryption::SecurityHandler do
280
281
 
281
282
  describe "decrypt" do
282
283
  before do
283
- @handler.set_up_decryption({V: 1})
284
+ @handler.set_up_decryption({Filter: :Test, V: 1})
284
285
  @encrypted = @handler.encrypt_string('string', @obj)
285
286
  @obj.value = {Key: @encrypted.dup, Array: [@encrypted.dup], Hash: {Another: @encrypted.dup}}
286
287
  end
@@ -21,13 +21,6 @@ describe HexaPDF::Encryption::StandardEncryptionDictionary do
21
21
  @dict[:Perms] = 'test' * 8
22
22
  end
23
23
 
24
- it "validates the /R value" do
25
- @dict[:R] = 2
26
- assert(@dict.validate)
27
- @dict[:R] = 5
28
- refute(@dict.validate)
29
- end
30
-
31
24
  [:U, :O].each do |field|
32
25
  it "validates the length of /#{field} field for R <= 4" do
33
26
  @dict[field] = 'test'
@@ -35,19 +28,36 @@ describe HexaPDF::Encryption::StandardEncryptionDictionary do
35
28
  end
36
29
  end
37
30
 
38
- [:U, :O, :UE, :OE, :Perms].each do |field|
39
- it "validates the length of /#{field} field for R=6" do
31
+ describe "validation for R=6" do
32
+ before do
40
33
  @dict[:R] = 6
41
- @dict[field] = 'test'
42
- refute(@dict.validate)
34
+ @dict[:U] = 't' * 48
35
+ @dict[:O] = 't' * 48
36
+ @dict[:UE] = 't' * 32
37
+ @dict[:OE] = 't' * 32
38
+ @dict[:Perms] = 't' * 16
43
39
  end
44
- end
45
40
 
46
- [:UE, :OE, :Perms].each do |field|
47
- it "validates the existence of the /#{field} field for R=6" do
48
- @dict[:R] = 6
49
- @dict.delete(field)
50
- refute(@dict.validate)
41
+ [:U, :O, :UE, :OE, :Perms].each do |field|
42
+ it "validates the length of /#{field}" do
43
+ @dict[field] = 'test'
44
+ refute(@dict.validate)
45
+ end
46
+ end
47
+
48
+ [:U, :O].each do |field|
49
+ it "auto-corrects /#{field} if it is larger and only padded with 0 bytes" do
50
+ @dict[field] = 't' * 48 + "\x00" * 20
51
+ assert(@dict.validate)
52
+ assert_equal('t' * 48, @dict[field])
53
+ end
54
+ end
55
+
56
+ [:UE, :OE, :Perms].each do |field|
57
+ it "validates the existence of the /#{field} field" do
58
+ @dict.delete(field)
59
+ refute(@dict.validate)
60
+ end
51
61
  end
52
62
  end
53
63
  end
@@ -211,22 +221,24 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
211
221
  describe "prepare_decryption" do
212
222
  it "fails if the /Filter value is incorrect" do
213
223
  exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
214
- @handler.set_up_decryption({Filter: :NonStandard, V: 2})
224
+ @handler.set_up_decryption({Filter: :NonStandard, V: 2, R: 4, O: 't' * 32, U: 't' * 32, P: 0,
225
+ Length: 128})
215
226
  end
216
227
  assert_match(/Invalid \/Filter value NonStandard/i, exp.message)
217
228
  end
218
229
 
219
230
  it "fails if the /R value is incorrect" do
220
231
  exp = assert_raises(HexaPDF::UnsupportedEncryptionError) do
221
- @handler.set_up_decryption({Filter: :Standard, V: 2, R: 5})
232
+ @handler.set_up_decryption({Filter: :Standard, V: 2, R: 5, O: 't' * 32, U: 't' * 32, P: 0,
233
+ Length: 128})
222
234
  end
223
235
  assert_match(/Invalid \/R value 5/i, exp.message)
224
236
  end
225
237
 
226
238
  it "fails if the supplied password is invalid" do
227
239
  exp = assert_raises(HexaPDF::EncryptionError) do
228
- @handler.set_up_decryption({Filter: :Standard, V: 2, R: 6, U: 'a' * 48, O: 'a' * 48,
229
- UE: 'a' * 32, OE: 'a' * 32})
240
+ @handler.set_up_decryption({Filter: :Standard, V: 5, R: 6, U: 'a' * 48, O: 'a' * 48,
241
+ UE: 'a' * 32, OE: 'a' * 32, P: 0, Perms: 'a' * 16})
230
242
  end
231
243
  assert_match(/Invalid password/i, exp.message)
232
244
  end
@@ -234,7 +246,7 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
234
246
  it "assigns empty strings to the trailer's ID field if it is missing" do
235
247
  refute(@document.trailer.key?(:ID))
236
248
  exp = assert_raises(HexaPDF::EncryptionError) do
237
- @handler.set_up_decryption({Filter: :Standard, V: 1, R: 2, U: 'a' * 48, O: 'a' * 48, P: 15})
249
+ @handler.set_up_decryption({Filter: :Standard, V: 1, R: 2, U: 'a' * 32, O: 'a' * 32, P: 15})
238
250
  end
239
251
  assert_match(/Invalid password/i, exp.message)
240
252
  assert_equal(['', ''], @document.trailer[:ID].value)
@@ -286,7 +298,7 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
286
298
  it "returns an array of permission symbols" do
287
299
  perms = @handler.class::Permissions::MODIFY_CONTENT | @handler.class::Permissions::COPY_CONTENT
288
300
  @handler.set_up_encryption(permissions: perms)
289
- assert_equal([:copy_content, :modify_content], @handler.permissions.sort)
301
+ assert_equal([:copy_content, :extract_content, :modify_content], @handler.permissions.sort)
290
302
  end
291
303
 
292
304
  describe "handling of metadata streams" do
@@ -66,6 +66,20 @@ describe HexaPDF::Font::TrueTypeWrapper do
66
66
  end
67
67
  end
68
68
 
69
+ describe "custom_glyph" do
70
+ it "returns the specified glyph object" do
71
+ glyph = @font_wrapper.custom_glyph(0, "str")
72
+ assert_equal(0, glyph.id)
73
+ assert_equal("str", glyph.str)
74
+ end
75
+
76
+ it "fails if an invalid glyph id is specified" do
77
+ exp = assert_raises(HexaPDF::Error) { @font_wrapper.custom_glyph(-5, 'c') }
78
+ assert_match(/Glyph ID -5 is invalid for font 'Ubuntu-Title'/, exp.message)
79
+ assert_raises(HexaPDF::Error) { @font_wrapper.custom_glyph(9999, 'c') }
80
+ end
81
+ end
82
+
69
83
  describe "encode" do
70
84
  it "returns the encoded glyph ID for fonts that are subset" do
71
85
  code = @font_wrapper.encode(@font_wrapper.glyph(3))
@@ -83,7 +97,10 @@ describe HexaPDF::Font::TrueTypeWrapper do
83
97
  end
84
98
 
85
99
  it "raises an error if an InvalidGlyph is encoded" do
86
- assert_raises(HexaPDF::Error) { @font_wrapper.encode(@font_wrapper.glyph(9999)) }
100
+ exp = assert_raises(HexaPDF::MissingGlyphError) do
101
+ @font_wrapper.encode(@font_wrapper.decode_utf8("ö").first)
102
+ end
103
+ assert_match(/No glyph for "ö" in font 'Ubuntu-Title'/, exp.message)
87
104
  end
88
105
  end
89
106
 
@@ -65,6 +65,19 @@ describe HexaPDF::Font::Type1Wrapper do
65
65
  end
66
66
  end
67
67
 
68
+ describe "custom_glyph" do
69
+ it "returns the specified glyph object" do
70
+ glyph = @times_wrapper.custom_glyph(:question, "str")
71
+ assert_equal(:question, glyph.name)
72
+ assert_equal("str", glyph.str)
73
+ end
74
+
75
+ it "fails if the provided glyph name is not available for the font" do
76
+ exp = assert_raises(HexaPDF::Error) { @times_wrapper.custom_glyph(:handicap, 'c') }
77
+ assert_match(/Glyph named :handicap not found in font 'Times Roman'/, exp.message)
78
+ end
79
+ end
80
+
68
81
  describe "encode" do
69
82
  describe "uses WinAnsiEncoding as initial encoding for non-symbolic fonts" do
70
83
  it "returns the PDF font dictionary using WinAnsiEncoding and encoded glyph" do
@@ -75,7 +88,8 @@ describe HexaPDF::Font::Type1Wrapper do
75
88
  end
76
89
 
77
90
  it "fails if an InvalidGlyph is encoded" do
78
- assert_raises(HexaPDF::Error) { @times_wrapper.encode(@times_wrapper.glyph(:ffi)) }
91
+ exp = assert_raises(HexaPDF::MissingGlyphError) { @times_wrapper.encode(@times_wrapper.glyph(:ffi)) }
92
+ assert_match(/No glyph for "ffi" in font 'Times Roman'/, exp.message)
79
93
  end
80
94
 
81
95
  it "fails if the encoding does not support the given glyph" do
@@ -207,7 +207,7 @@ describe HexaPDF::Layout::Box do
207
207
  end
208
208
 
209
209
  describe "empty?" do
210
- it "is only empty when no drawing operation is specified" do
210
+ it "is empty when no drawing operation is specified" do
211
211
  assert(create_box.empty?)
212
212
  refute(create_box {}.empty?)
213
213
  refute(create_box(style: {background_color: [5]}).empty?)
@@ -22,7 +22,7 @@ describe HexaPDF::Layout::ColumnBox do
22
22
  end
23
23
 
24
24
  def check_box(box, width, height, fit_pos = nil)
25
- assert(box.fit(@frame.available_width, @frame.available_height, @frame), "box fit?")
25
+ assert(box.fit(@frame.available_width, @frame.available_height, @frame), "box didn't fit")
26
26
  assert_equal(width, box.width, "box width")
27
27
  assert_equal(height, box.height, "box height")
28
28
  if fit_pos
@@ -46,6 +46,24 @@ describe HexaPDF::Layout::ColumnBox do
46
46
  end
47
47
  end
48
48
 
49
+ describe "empty?" do
50
+ it "is empty if nothing is fit yet" do
51
+ assert(create_box.empty?)
52
+ end
53
+
54
+ it "is empty if no box fits" do
55
+ box = create_box(children: [@fixed_size_boxes[0]], columns: [10])
56
+ box.fit(@frame.available_width, @frame.available_height, @frame)
57
+ assert(box.empty?)
58
+ end
59
+
60
+ it "is not empty if at least one box fits" do
61
+ box = create_box(children: [@fixed_size_boxes[0]], columns: [30])
62
+ check_box(box, 30, 10)
63
+ refute(box.empty?)
64
+ end
65
+ end
66
+
49
67
  describe "fit" do
50
68
  [:default, :flow].each do |position|
51
69
  it "respects the set initial width, position #{position}" do
@@ -132,27 +150,53 @@ describe HexaPDF::Layout::ColumnBox do
132
150
  assert_equal(5, box_b.children.size)
133
151
  end
134
152
 
135
- it "draws the result onto the canvas" do
136
- box = create_box(children: @fixed_size_boxes)
137
- box.fit(100, 100, @frame)
153
+ describe "draw_content" do
154
+ before do
155
+ @canvas = HexaPDF::Document.new.pages.add.canvas
156
+ end
157
+
158
+ it "draws the result onto the canvas" do
159
+ box = create_box(children: @fixed_size_boxes)
160
+ box.fit(100, 100, @frame)
161
+ box.draw(@canvas, 0, 100 - box.height)
162
+ operators = 90.step(to: 20, by: -10).map do |y|
163
+ [[:save_graphics_state],
164
+ [:concatenate_matrix, [1, 0, 0, 1, 0, y]],
165
+ [:move_to, [0, 0]],
166
+ [:end_path],
167
+ [:restore_graphics_state]]
168
+ end
169
+ operators.concat(90.step(to: 30, by: -10).map do |y|
170
+ [[:save_graphics_state],
171
+ [:concatenate_matrix, [1, 0, 0, 1, 55, y]],
172
+ [:move_to, [0, 0]],
173
+ [:end_path],
174
+ [:restore_graphics_state]]
175
+ end)
176
+ operators.flatten!(1)
177
+ assert_operators(@canvas.contents, operators)
178
+ end
138
179
 
139
- @canvas = HexaPDF::Document.new.pages.add.canvas
140
- box.draw(@canvas, 0, 0)
141
- operators = 90.step(to: 20, by: -10).map do |y|
142
- [[:save_graphics_state],
143
- [:concatenate_matrix, [1, 0, 0, 1, 0, y]],
144
- [:move_to, [0, 0]],
145
- [:end_path],
146
- [:restore_graphics_state]]
180
+ it "takes a different final location into account" do
181
+ box = create_box(children: @fixed_size_boxes[0, 2])
182
+ box.fit(100, 100, @frame)
183
+ box.draw(@canvas, 20, 10)
184
+ operators = [
185
+ [:save_graphics_state],
186
+ [:concatenate_matrix, [1, 0, 0, 1, 20, -80]],
187
+ [:save_graphics_state],
188
+ [:concatenate_matrix, [1, 0, 0, 1, 0, 90]],
189
+ [:move_to, [0, 0]],
190
+ [:end_path],
191
+ [:restore_graphics_state],
192
+ [:save_graphics_state],
193
+ [:concatenate_matrix, [1, 0, 0, 1, 55, 90]],
194
+ [:move_to, [0, 0]],
195
+ [:end_path],
196
+ [:restore_graphics_state],
197
+ [:restore_graphics_state],
198
+ ]
199
+ assert_operators(@canvas.contents, operators)
147
200
  end
148
- operators.concat(90.step(to: 30, by: -10).map do |y|
149
- [[:save_graphics_state],
150
- [:concatenate_matrix, [1, 0, 0, 1, 55, y]],
151
- [:move_to, [0, 0]],
152
- [:end_path],
153
- [:restore_graphics_state]]
154
- end)
155
- operators.flatten!(1)
156
- assert_operators(@canvas.contents, operators)
157
201
  end
158
202
  end