hexapdf 0.32.2 → 0.33.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +63 -1
- data/README.md +9 -0
- data/examples/002-graphics.rb +15 -17
- data/examples/003-arcs.rb +9 -9
- data/examples/009-text_layouter_alignment.rb +1 -1
- data/examples/010-text_layouter_inline_boxes.rb +2 -2
- data/examples/011-text_layouter_line_wrapping.rb +1 -1
- data/examples/012-text_layouter_styling.rb +7 -7
- data/examples/013-text_layouter_shapes.rb +1 -1
- data/examples/014-text_in_polygon.rb +1 -1
- data/examples/015-boxes.rb +8 -7
- data/examples/016-frame_automatic_box_placement.rb +2 -2
- data/examples/017-frame_text_flow.rb +2 -1
- data/examples/018-composer.rb +1 -1
- data/examples/020-column_box.rb +2 -1
- data/examples/025-table_box.rb +46 -0
- data/lib/hexapdf/cli/command.rb +5 -2
- data/lib/hexapdf/cli/form.rb +5 -5
- data/lib/hexapdf/cli/inspect.rb +3 -3
- data/lib/hexapdf/composer.rb +104 -52
- data/lib/hexapdf/configuration.rb +44 -39
- data/lib/hexapdf/content/canvas.rb +393 -267
- data/lib/hexapdf/content/color_space.rb +72 -25
- data/lib/hexapdf/content/graphic_object/arc.rb +57 -24
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -23
- data/lib/hexapdf/content/graphic_object/geom2d.rb +47 -6
- data/lib/hexapdf/content/graphic_object/solid_arc.rb +58 -36
- data/lib/hexapdf/content/graphic_object.rb +6 -7
- data/lib/hexapdf/content/graphics_state.rb +54 -45
- data/lib/hexapdf/content/operator.rb +52 -54
- data/lib/hexapdf/content/parser.rb +2 -2
- data/lib/hexapdf/content/processor.rb +15 -15
- data/lib/hexapdf/content/transformation_matrix.rb +1 -1
- data/lib/hexapdf/content.rb +5 -0
- data/lib/hexapdf/dictionary.rb +6 -5
- data/lib/hexapdf/dictionary_fields.rb +42 -14
- data/lib/hexapdf/digital_signature/cms_handler.rb +2 -2
- data/lib/hexapdf/digital_signature/handler.rb +1 -1
- data/lib/hexapdf/digital_signature/pkcs1_handler.rb +2 -3
- data/lib/hexapdf/digital_signature/signature.rb +6 -6
- data/lib/hexapdf/digital_signature/signatures.rb +13 -12
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +14 -5
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +2 -4
- data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +4 -4
- data/lib/hexapdf/digital_signature/signing.rb +4 -0
- data/lib/hexapdf/digital_signature/verification_result.rb +2 -2
- data/lib/hexapdf/digital_signature.rb +7 -2
- data/lib/hexapdf/document/destinations.rb +12 -11
- data/lib/hexapdf/document/files.rb +1 -1
- data/lib/hexapdf/document/fonts.rb +1 -1
- data/lib/hexapdf/document/layout.rb +167 -39
- data/lib/hexapdf/document/pages.rb +3 -2
- data/lib/hexapdf/document.rb +89 -55
- data/lib/hexapdf/encryption/aes.rb +5 -5
- data/lib/hexapdf/encryption/arc4.rb +1 -1
- data/lib/hexapdf/encryption/fast_aes.rb +2 -2
- data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
- data/lib/hexapdf/encryption/identity.rb +1 -1
- data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
- data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
- data/lib/hexapdf/encryption/security_handler.rb +31 -24
- data/lib/hexapdf/encryption/standard_security_handler.rb +45 -36
- data/lib/hexapdf/encryption.rb +7 -2
- data/lib/hexapdf/error.rb +18 -0
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
- data/lib/hexapdf/filter/flate_decode.rb +1 -1
- data/lib/hexapdf/filter/lzw_decode.rb +1 -1
- data/lib/hexapdf/filter/pass_through.rb +1 -1
- data/lib/hexapdf/filter/predictor.rb +1 -1
- data/lib/hexapdf/filter/run_length_decode.rb +1 -1
- data/lib/hexapdf/filter.rb +55 -6
- data/lib/hexapdf/font/cmap/parser.rb +2 -2
- data/lib/hexapdf/font/cmap.rb +1 -1
- data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +2 -2
- data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +3 -3
- data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
- data/lib/hexapdf/font/invalid_glyph.rb +3 -0
- data/lib/hexapdf/font/true_type_wrapper.rb +17 -4
- data/lib/hexapdf/font/type1_wrapper.rb +19 -4
- data/lib/hexapdf/font_loader/from_configuration.rb +5 -2
- data/lib/hexapdf/font_loader/from_file.rb +5 -5
- data/lib/hexapdf/font_loader/standard14.rb +3 -3
- data/lib/hexapdf/font_loader.rb +3 -0
- data/lib/hexapdf/image_loader/jpeg.rb +2 -2
- data/lib/hexapdf/image_loader/pdf.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +2 -2
- data/lib/hexapdf/image_loader.rb +1 -1
- data/lib/hexapdf/importer.rb +13 -0
- data/lib/hexapdf/layout/box.rb +9 -2
- data/lib/hexapdf/layout/box_fitter.rb +2 -2
- data/lib/hexapdf/layout/column_box.rb +18 -4
- data/lib/hexapdf/layout/frame.rb +30 -12
- data/lib/hexapdf/layout/image_box.rb +5 -0
- data/lib/hexapdf/layout/inline_box.rb +1 -0
- data/lib/hexapdf/layout/list_box.rb +17 -1
- data/lib/hexapdf/layout/page_style.rb +4 -4
- data/lib/hexapdf/layout/style.rb +18 -3
- data/lib/hexapdf/layout/table_box.rb +682 -0
- data/lib/hexapdf/layout/text_box.rb +5 -3
- data/lib/hexapdf/layout/text_fragment.rb +1 -1
- data/lib/hexapdf/layout/text_layouter.rb +12 -4
- data/lib/hexapdf/layout.rb +1 -0
- data/lib/hexapdf/name_tree_node.rb +1 -1
- data/lib/hexapdf/number_tree_node.rb +1 -1
- data/lib/hexapdf/object.rb +18 -7
- data/lib/hexapdf/parser.rb +7 -7
- data/lib/hexapdf/pdf_array.rb +1 -1
- data/lib/hexapdf/rectangle.rb +1 -1
- data/lib/hexapdf/reference.rb +1 -1
- data/lib/hexapdf/revision.rb +1 -1
- data/lib/hexapdf/revisions.rb +3 -3
- data/lib/hexapdf/serializer.rb +15 -15
- data/lib/hexapdf/stream.rb +4 -2
- data/lib/hexapdf/tokenizer.rb +14 -14
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +22 -22
- data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/field.rb +2 -2
- data/lib/hexapdf/type/acro_form/form.rb +1 -1
- data/lib/hexapdf/type/acro_form/signature_field.rb +4 -4
- data/lib/hexapdf/type/acro_form/text_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
- data/lib/hexapdf/type/acro_form.rb +1 -1
- data/lib/hexapdf/type/action.rb +1 -1
- data/lib/hexapdf/type/actions/go_to.rb +1 -1
- data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
- data/lib/hexapdf/type/actions/launch.rb +1 -1
- data/lib/hexapdf/type/actions/uri.rb +1 -1
- data/lib/hexapdf/type/actions.rb +1 -1
- data/lib/hexapdf/type/annotation.rb +3 -3
- data/lib/hexapdf/type/annotations/link.rb +1 -1
- data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
- data/lib/hexapdf/type/annotations/text.rb +1 -1
- data/lib/hexapdf/type/annotations/widget.rb +2 -2
- data/lib/hexapdf/type/annotations.rb +1 -1
- data/lib/hexapdf/type/catalog.rb +1 -1
- data/lib/hexapdf/type/cid_font.rb +3 -3
- data/lib/hexapdf/type/embedded_file.rb +1 -1
- data/lib/hexapdf/type/file_specification.rb +2 -2
- data/lib/hexapdf/type/font_descriptor.rb +1 -1
- data/lib/hexapdf/type/font_simple.rb +2 -2
- data/lib/hexapdf/type/font_type0.rb +3 -3
- data/lib/hexapdf/type/font_type3.rb +1 -1
- data/lib/hexapdf/type/form.rb +1 -1
- data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
- data/lib/hexapdf/type/icon_fit.rb +1 -1
- data/lib/hexapdf/type/image.rb +1 -1
- data/lib/hexapdf/type/info.rb +1 -1
- data/lib/hexapdf/type/mark_information.rb +1 -1
- data/lib/hexapdf/type/names.rb +2 -2
- data/lib/hexapdf/type/object_stream.rb +2 -1
- data/lib/hexapdf/type/outline.rb +1 -1
- data/lib/hexapdf/type/outline_item.rb +1 -1
- data/lib/hexapdf/type/page.rb +19 -10
- data/lib/hexapdf/type/page_label.rb +1 -1
- data/lib/hexapdf/type/page_tree_node.rb +1 -1
- data/lib/hexapdf/type/resources.rb +1 -1
- data/lib/hexapdf/type/trailer.rb +2 -2
- data/lib/hexapdf/type/viewer_preferences.rb +1 -1
- data/lib/hexapdf/type/xref_stream.rb +2 -2
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +4 -4
- data/lib/hexapdf/xref_section.rb +2 -2
- data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +11 -1
- data/test/hexapdf/content/graphic_object/test_geom2d.rb +7 -0
- data/test/hexapdf/content/test_canvas.rb +0 -1
- data/test/hexapdf/digital_signature/test_signatures.rb +22 -0
- data/test/hexapdf/document/test_files.rb +2 -2
- data/test/hexapdf/document/test_layout.rb +98 -0
- data/test/hexapdf/encryption/test_security_handler.rb +12 -11
- data/test/hexapdf/encryption/test_standard_security_handler.rb +35 -23
- data/test/hexapdf/font/test_true_type_wrapper.rb +18 -1
- data/test/hexapdf/font/test_type1_wrapper.rb +15 -1
- data/test/hexapdf/layout/test_box.rb +1 -1
- data/test/hexapdf/layout/test_column_box.rb +65 -21
- data/test/hexapdf/layout/test_frame.rb +14 -14
- data/test/hexapdf/layout/test_image_box.rb +4 -0
- data/test/hexapdf/layout/test_inline_box.rb +5 -0
- data/test/hexapdf/layout/test_list_box.rb +40 -6
- data/test/hexapdf/layout/test_page_style.rb +3 -2
- data/test/hexapdf/layout/test_style.rb +50 -0
- data/test/hexapdf/layout/test_table_box.rb +722 -0
- data/test/hexapdf/layout/test_text_box.rb +18 -0
- data/test/hexapdf/layout/test_text_layouter.rb +4 -0
- data/test/hexapdf/test_dictionary_fields.rb +4 -1
- data/test/hexapdf/test_document.rb +1 -0
- data/test/hexapdf/test_filter.rb +8 -0
- data/test/hexapdf/test_importer.rb +9 -0
- data/test/hexapdf/test_object.rb +16 -5
- data/test/hexapdf/test_stream.rb +7 -0
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +13 -5
- data/test/hexapdf/type/acro_form/test_form.rb +4 -3
- data/test/hexapdf/type/test_page.rb +18 -4
- 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:
|
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
|
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:
|
52
|
+
# See: PDF2.0 s7.9.2, D.1, D.3
|
53
53
|
module PDFDocEncoding
|
54
54
|
|
55
55
|
# :nodoc:
|
data/lib/hexapdf/version.rb
CHANGED
data/lib/hexapdf/writer.rb
CHANGED
@@ -124,7 +124,7 @@ module HexaPDF
|
|
124
124
|
|
125
125
|
# Writes the PDF file header.
|
126
126
|
#
|
127
|
-
# See:
|
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:
|
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:
|
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:
|
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
|
data/lib/hexapdf/xref_section.rb
CHANGED
@@ -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,
|
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:
|
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
|
-
|
39
|
-
|
31
|
+
describe "validation for R=6" do
|
32
|
+
before do
|
40
33
|
@dict[:R] = 6
|
41
|
-
@dict[
|
42
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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:
|
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' *
|
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::
|
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::
|
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
|
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
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|