hexapdf 0.32.2 → 0.33.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 (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