prawn 2.0.2 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/data/images/blend_modes_bottom_layer.jpg +0 -0
- data/data/images/blend_modes_top_layer.jpg +0 -0
- data/data/images/indexed_transparency.png +0 -0
- data/data/images/indexed_transparency_alpha.dat +0 -0
- data/data/images/indexed_transparency_color.dat +0 -0
- data/lib/prawn.rb +2 -1
- data/lib/prawn/document.rb +1 -0
- data/lib/prawn/document/internals.rb +10 -2
- data/lib/prawn/font.rb +14 -1
- data/lib/prawn/graphics.rb +2 -0
- data/lib/prawn/graphics/blend_mode.rb +64 -0
- data/lib/prawn/graphics/patterns.rb +52 -16
- data/lib/prawn/graphics/transformation.rb +3 -0
- data/lib/prawn/images/png.rb +43 -5
- data/lib/prawn/text/formatted/arranger.rb +25 -17
- data/lib/prawn/text/formatted/line_wrap.rb +2 -3
- data/lib/prawn/transformation_stack.rb +42 -0
- data/lib/prawn/version.rb +1 -1
- data/manual/graphics/blend_mode.rb +49 -0
- data/manual/graphics/graphics.rb +1 -0
- data/manual/graphics/soft_masks.rb +1 -1
- data/prawn.gemspec +4 -5
- data/spec/acceptance/png_spec.rb +35 -0
- data/spec/blend_mode_spec.rb +71 -0
- data/spec/document_spec.rb +72 -76
- data/spec/font_spec.rb +11 -11
- data/spec/formatted_text_arranger_spec.rb +178 -149
- data/spec/formatted_text_box_spec.rb +23 -23
- data/spec/graphics_spec.rb +67 -28
- data/spec/image_handler_spec.rb +7 -7
- data/spec/images_spec.rb +1 -1
- data/spec/png_spec.rb +26 -4
- data/spec/repeater_spec.rb +9 -9
- data/spec/spec_helper.rb +1 -4
- data/spec/text_at_spec.rb +2 -2
- data/spec/text_box_spec.rb +20 -16
- data/spec/text_spec.rb +8 -14
- data/spec/transformation_stack_spec.rb +63 -0
- data/spec/view_spec.rb +10 -10
- metadata +27 -31
- data/data/images/pal_bk.png +0 -0
- data/spec/acceptance/png.rb +0 -24
- data/spec/extensions/mocha.rb +0 -45
data/spec/font_spec.rb
CHANGED
@@ -265,11 +265,11 @@ describe "Document#page_fonts" do
|
|
265
265
|
end
|
266
266
|
|
267
267
|
def page_should_include_font(font)
|
268
|
-
expect(page_includes_font?(font)).to
|
268
|
+
expect(page_includes_font?(font)).to eq true
|
269
269
|
end
|
270
270
|
|
271
271
|
def page_should_not_include_font(font)
|
272
|
-
expect(page_includes_font?(font)).to
|
272
|
+
expect(page_includes_font?(font)).to eq false
|
273
273
|
end
|
274
274
|
end
|
275
275
|
|
@@ -313,13 +313,13 @@ describe "AFM fonts" do
|
|
313
313
|
it "should not modify the original string when normalize_encoding() is used" do
|
314
314
|
original = "Foo"
|
315
315
|
normalized = @times.normalize_encoding(original)
|
316
|
-
expect(original.equal?(normalized)).to
|
316
|
+
expect(original.equal?(normalized)).to eq false
|
317
317
|
end
|
318
318
|
|
319
319
|
it "should modify the original string when normalize_encoding!() is used" do
|
320
320
|
original = "Foo"
|
321
321
|
normalized = @times.normalize_encoding!(original)
|
322
|
-
expect(original.equal?(normalized)).to
|
322
|
+
expect(original.equal?(normalized)).to eq true
|
323
323
|
end
|
324
324
|
end
|
325
325
|
|
@@ -335,25 +335,25 @@ describe "#glyph_present" do
|
|
335
335
|
|
336
336
|
it "should return true when present in an AFM font" do
|
337
337
|
font = @pdf.find_font("Helvetica")
|
338
|
-
expect(font.glyph_present?("H")).to
|
338
|
+
expect(font.glyph_present?("H")).to eq true
|
339
339
|
end
|
340
340
|
|
341
341
|
it "should return false when absent in an AFM font" do
|
342
342
|
font = @pdf.find_font("Helvetica")
|
343
|
-
expect(font.glyph_present?("再")).to
|
343
|
+
expect(font.glyph_present?("再")).to eq false
|
344
344
|
end
|
345
345
|
|
346
346
|
it "should return true when present in a TTF font" do
|
347
347
|
font = @pdf.find_font("#{Prawn::DATADIR}/fonts/DejaVuSans.ttf")
|
348
|
-
expect(font.glyph_present?("H")).to
|
348
|
+
expect(font.glyph_present?("H")).to eq true
|
349
349
|
end
|
350
350
|
|
351
351
|
it "should return false when absent in a TTF font" do
|
352
352
|
font = @pdf.find_font("#{Prawn::DATADIR}/fonts/DejaVuSans.ttf")
|
353
|
-
expect(font.glyph_present?("再")).to
|
353
|
+
expect(font.glyph_present?("再")).to eq false
|
354
354
|
|
355
355
|
font = @pdf.find_font("#{Prawn::DATADIR}/fonts/gkai00mp.ttf")
|
356
|
-
expect(font.glyph_present?("€")).to
|
356
|
+
expect(font.glyph_present?("€")).to eq false
|
357
357
|
end
|
358
358
|
end
|
359
359
|
|
@@ -410,13 +410,13 @@ describe "TTF fonts" do
|
|
410
410
|
it "should not modify the original string when normalize_encoding() is used" do
|
411
411
|
original = "Foo"
|
412
412
|
normalized = @font.normalize_encoding(original)
|
413
|
-
expect(original.equal?(normalized)).to
|
413
|
+
expect(original.equal?(normalized)).to eq false
|
414
414
|
end
|
415
415
|
|
416
416
|
it "should modify the original string when normalize_encoding!() is used" do
|
417
417
|
original = "Foo"
|
418
418
|
normalized = @font.normalize_encoding!(original)
|
419
|
-
expect(original.equal?(normalized)).to
|
419
|
+
expect(original.equal?(normalized)).to eq true
|
420
420
|
end
|
421
421
|
end
|
422
422
|
end
|
@@ -2,167 +2,196 @@
|
|
2
2
|
|
3
3
|
require File.join(File.expand_path(File.dirname(__FILE__)), "spec_helper")
|
4
4
|
|
5
|
-
describe
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
arranger.format_array = array
|
38
|
-
arranger.preview_next_string
|
39
|
-
expect(arranger.consumed).to eq([])
|
40
|
-
end
|
41
|
-
it "should not consumed array" do
|
42
|
-
create_pdf
|
43
|
-
arranger = Prawn::Text::Formatted::Arranger.new(@pdf)
|
44
|
-
array = [{ :text => "hello" }]
|
45
|
-
arranger.format_array = array
|
46
|
-
expect(arranger.preview_next_string).to eq("hello")
|
47
|
-
end
|
48
|
-
end
|
49
|
-
describe "Core::Text::Formatted::Arranger#next_string" do
|
50
|
-
before(:each) do
|
51
|
-
create_pdf
|
52
|
-
@arranger = Prawn::Text::Formatted::Arranger.new(@pdf)
|
53
|
-
array = [{ :text => "hello " },
|
54
|
-
{ :text => "world how ", :styles => [:bold] },
|
55
|
-
{ :text => "are", :styles => [:bold, :italic] },
|
56
|
-
{ :text => " you?" }]
|
57
|
-
@arranger.format_array = array
|
58
|
-
end
|
59
|
-
it "should raise_error an error if called after a line was finalized and" \
|
60
|
-
" before a new line was initialized" do
|
61
|
-
@arranger.finalize_line
|
62
|
-
expect do
|
63
|
-
@arranger.next_string
|
64
|
-
end.to raise_error(RuntimeError)
|
65
|
-
end
|
66
|
-
it "should populate consumed array" do
|
67
|
-
while string = @arranger.next_string
|
5
|
+
describe Prawn::Text::Formatted::Arranger do
|
6
|
+
let(:document) { create_pdf }
|
7
|
+
subject { Prawn::Text::Formatted::Arranger.new document }
|
8
|
+
|
9
|
+
describe '#format_array' do
|
10
|
+
it 'populates the unconsumed array' do
|
11
|
+
array = [
|
12
|
+
{ text: 'hello ' },
|
13
|
+
{ text: 'world how ', styles: [:bold] },
|
14
|
+
{ text: 'are', styles: [:bold, :italic] },
|
15
|
+
{ text: ' you?' }
|
16
|
+
]
|
17
|
+
|
18
|
+
subject.format_array = array
|
19
|
+
|
20
|
+
expect(subject.unconsumed[0]).to eq(text: 'hello ')
|
21
|
+
expect(subject.unconsumed[1]).to eq(text: 'world how ', styles: [:bold])
|
22
|
+
expect(subject.unconsumed[2]).to eq(text: 'are', styles: [:bold, :italic])
|
23
|
+
expect(subject.unconsumed[3]).to eq(text: ' you?')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'splits newlins into their own elements' do
|
27
|
+
array = [
|
28
|
+
{ text: "\nhello\nworld" }
|
29
|
+
]
|
30
|
+
|
31
|
+
subject.format_array = array
|
32
|
+
|
33
|
+
expect(subject.unconsumed[0]).to eq(text: "\n")
|
34
|
+
expect(subject.unconsumed[1]).to eq(text: "hello")
|
35
|
+
expect(subject.unconsumed[2]).to eq(text: "\n")
|
36
|
+
expect(subject.unconsumed[3]).to eq(text: "world")
|
68
37
|
end
|
69
|
-
expect(@arranger.consumed[0]).to eq(:text => "hello ")
|
70
|
-
expect(@arranger.consumed[1]).to eq(:text => "world how ",
|
71
|
-
:styles => [:bold])
|
72
|
-
expect(@arranger.consumed[2]).to eq(:text => "are",
|
73
|
-
:styles => [:bold, :italic])
|
74
|
-
expect(@arranger.consumed[3]).to eq(:text => " you?")
|
75
38
|
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
39
|
+
|
40
|
+
describe '#preview_next_string' do
|
41
|
+
context 'with a formatted array' do
|
42
|
+
let(:array) { [{ text: 'hello' }] }
|
43
|
+
|
44
|
+
before do
|
45
|
+
subject.format_array = array
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'does not populate the consumed array' do
|
49
|
+
subject.preview_next_string
|
50
|
+
expect(subject.consumed).to eq([])
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'returns the text of the next unconsumed hash' do
|
54
|
+
expect(subject.preview_next_string).to eq("hello")
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'returns nil if there is no more unconsumed text' do
|
58
|
+
subject.next_string
|
59
|
+
expect(subject.preview_next_string).to be_nil
|
95
60
|
end
|
96
|
-
counter += 1
|
97
61
|
end
|
98
62
|
end
|
99
|
-
end
|
100
63
|
|
101
|
-
describe
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
64
|
+
describe '#next_string' do
|
65
|
+
let(:array) {
|
66
|
+
[
|
67
|
+
{ text: 'hello ' },
|
68
|
+
{ text: 'world how ', styles: [:bold] },
|
69
|
+
{ text: 'are', styles: [:bold, :italic] },
|
70
|
+
{ text: ' you?' }
|
71
|
+
]
|
72
|
+
}
|
73
|
+
|
74
|
+
before do
|
75
|
+
subject.format_array = array
|
111
76
|
end
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
it "should return the consumed fragments in order of consumption" \
|
117
|
-
" and update" do
|
118
|
-
create_pdf
|
119
|
-
arranger = Prawn::Text::Formatted::Arranger.new(@pdf)
|
120
|
-
array = [{ :text => "hello " },
|
121
|
-
{ :text => "world how ", :styles => [:bold] },
|
122
|
-
{ :text => "are", :styles => [:bold, :italic] },
|
123
|
-
{ :text => " you?" }]
|
124
|
-
arranger.format_array = array
|
125
|
-
while string = arranger.next_string
|
77
|
+
|
78
|
+
it 'raises RuntimeError if called after a line was finalized' do
|
79
|
+
subject.finalize_line
|
80
|
+
expect { subject.next_string }.to raise_error(RuntimeError)
|
126
81
|
end
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
array = [{ :text => "hello\nworld\n\n\nhow are you?" },
|
137
|
-
{ :text => "\n" },
|
138
|
-
{ :text => "\n" },
|
139
|
-
{ :text => "\n" },
|
140
|
-
{ :text => "" },
|
141
|
-
{ :text => "fine, thanks." },
|
142
|
-
{ :text => "" },
|
143
|
-
{ :text => "\n" },
|
144
|
-
{ :text => "" }]
|
145
|
-
arranger.format_array = array
|
146
|
-
while string = arranger.next_string
|
82
|
+
|
83
|
+
it 'populates the conumed array' do
|
84
|
+
while string = subject.next_string
|
85
|
+
end
|
86
|
+
|
87
|
+
expect(subject.consumed[0]).to eq(text: 'hello ')
|
88
|
+
expect(subject.consumed[1]).to eq(text: 'world how ', styles: [:bold])
|
89
|
+
expect(subject.consumed[2]).to eq(text: 'are', styles: [:bold, :italic])
|
90
|
+
expect(subject.consumed[3]).to eq(text: ' you?')
|
147
91
|
end
|
148
|
-
|
149
|
-
|
150
|
-
|
92
|
+
|
93
|
+
it 'populates the current_format_state array' do
|
94
|
+
string = subject.next_string
|
95
|
+
expect(subject.current_format_state).to eq({})
|
96
|
+
|
97
|
+
string = subject.next_string
|
98
|
+
expect(subject.current_format_state).to eq(:styles => [:bold])
|
99
|
+
|
100
|
+
string = subject.next_string
|
101
|
+
expect(subject.current_format_state).to eq(:styles => [:bold, :italic])
|
102
|
+
|
103
|
+
string = subject.next_string
|
104
|
+
expect(subject.current_format_state).to eq({})
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'returns the text of the newly consumed hash' do
|
108
|
+
expect(subject.next_string).to eq('hello ')
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'returns nil when there are no more unconsumed hashes' do
|
112
|
+
4.times do
|
113
|
+
subject.next_string
|
114
|
+
end
|
115
|
+
|
116
|
+
expect(subject.next_string).to be_nil
|
151
117
|
end
|
152
118
|
end
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
119
|
+
|
120
|
+
describe '#retrieve_fragment' do
|
121
|
+
context 'with a formatted array whos text is an empty string' do
|
122
|
+
let(:array) {
|
123
|
+
[
|
124
|
+
{ text: "hello\nworld\n\n\nhow are you?" },
|
125
|
+
{ text: "\n" },
|
126
|
+
{ text: "\n" },
|
127
|
+
{ text: "\n" },
|
128
|
+
{ text: "" },
|
129
|
+
{ text: "fine, thanks." },
|
130
|
+
{ text: "" },
|
131
|
+
{ text: "\n" },
|
132
|
+
{ text: "" }
|
133
|
+
]
|
134
|
+
}
|
135
|
+
|
136
|
+
before do
|
137
|
+
subject.format_array = array
|
138
|
+
|
139
|
+
while string = subject.next_string
|
140
|
+
end
|
141
|
+
|
142
|
+
subject.finalize_line
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'never returns a fragment whose text is an empty string' do
|
146
|
+
while fragment = subject.retrieve_fragment
|
147
|
+
expect(fragment.text).not_to be_empty
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'with formatted array' do
|
153
|
+
let(:array) {
|
154
|
+
[
|
155
|
+
{ text: 'hello ' },
|
156
|
+
{ text: 'world how ', styles: [:bold] },
|
157
|
+
{ text: 'are', styles: [:bold, :italic] },
|
158
|
+
{ text: ' you?' }
|
159
|
+
]
|
160
|
+
}
|
161
|
+
|
162
|
+
before do
|
163
|
+
subject.format_array = array
|
164
|
+
end
|
165
|
+
|
166
|
+
context 'after all strings have been consumed' do
|
167
|
+
before do
|
168
|
+
while string = subject.next_string
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'should raise RuntimeError an error if not finalized' do
|
173
|
+
expect { subject.retrieve_fragment }.to raise_error(RuntimeError)
|
174
|
+
end
|
175
|
+
|
176
|
+
context 'and finalized' do
|
177
|
+
before do
|
178
|
+
subject.finalize_line
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'returns the consumed fragments in order of consumption' do
|
182
|
+
expect(subject.retrieve_fragment.text).to eq("hello ")
|
183
|
+
expect(subject.retrieve_fragment.text).to eq("world how ")
|
184
|
+
expect(subject.retrieve_fragment.text).to eq("are")
|
185
|
+
expect(subject.retrieve_fragment.text).to eq(" you?")
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'does not alter the current font style' do
|
189
|
+
subject.retrieve_fragment
|
190
|
+
expect(subject.current_format_state[:styles]).to be_nil
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
162
194
|
end
|
163
|
-
arranger.finalize_line
|
164
|
-
arranger.retrieve_fragment
|
165
|
-
expect(arranger.current_format_state[:styles]).to be_nil
|
166
195
|
end
|
167
196
|
end
|
168
197
|
|
@@ -225,7 +225,7 @@ describe "Text::Formatted::Box" do
|
|
225
225
|
:document => @pdf,
|
226
226
|
:fallback_fonts => [])
|
227
227
|
|
228
|
-
box.
|
228
|
+
expect(box).to_not receive(:process_fallback_fonts)
|
229
229
|
box.render
|
230
230
|
end
|
231
231
|
|
@@ -236,7 +236,7 @@ describe "Text::Formatted::Box" do
|
|
236
236
|
|
237
237
|
@pdf.font("Kai")
|
238
238
|
|
239
|
-
box.
|
239
|
+
expect(box).to_not receive(:process_fallback_fonts)
|
240
240
|
box.render
|
241
241
|
end
|
242
242
|
end
|
@@ -339,10 +339,10 @@ describe "Text::Formatted::Box#render" do
|
|
339
339
|
it "should be able to perform fragment callbacks" do
|
340
340
|
create_pdf
|
341
341
|
callback_object = TestFragmentCallback.new("something", 7, :document => @pdf)
|
342
|
-
callback_object.
|
342
|
+
expect(callback_object).to receive(:render_behind).with(
|
343
343
|
kind_of(Prawn::Text::Formatted::Fragment)
|
344
344
|
)
|
345
|
-
callback_object.
|
345
|
+
expect(callback_object).to receive(:render_in_front).with(
|
346
346
|
kind_of(Prawn::Text::Formatted::Fragment)
|
347
347
|
)
|
348
348
|
array = [{ :text => "hello world " },
|
@@ -355,18 +355,18 @@ describe "Text::Formatted::Box#render" do
|
|
355
355
|
create_pdf
|
356
356
|
|
357
357
|
callback_object = TestFragmentCallback.new("something", 7, :document => @pdf)
|
358
|
-
callback_object.
|
358
|
+
expect(callback_object).to receive(:render_behind).with(
|
359
359
|
kind_of(Prawn::Text::Formatted::Fragment)
|
360
360
|
)
|
361
|
-
callback_object.
|
361
|
+
expect(callback_object).to receive(:render_in_front).with(
|
362
362
|
kind_of(Prawn::Text::Formatted::Fragment)
|
363
363
|
)
|
364
364
|
|
365
365
|
callback_object2 = TestFragmentCallback.new("something else", 14, :document => @pdf)
|
366
|
-
callback_object2.
|
366
|
+
expect(callback_object2).to receive(:render_behind).with(
|
367
367
|
kind_of(Prawn::Text::Formatted::Fragment)
|
368
368
|
)
|
369
|
-
callback_object2.
|
369
|
+
expect(callback_object2).to receive(:render_in_front).with(
|
370
370
|
kind_of(Prawn::Text::Formatted::Fragment)
|
371
371
|
)
|
372
372
|
|
@@ -485,12 +485,12 @@ describe "Text::Formatted::Box#render" do
|
|
485
485
|
end
|
486
486
|
it "should be able to add URL links" do
|
487
487
|
create_pdf
|
488
|
-
@pdf.
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
488
|
+
expect(@pdf).to receive(:link_annotation).with(kind_of(Array), :Border => [0, 0, 0],
|
489
|
+
:A => {
|
490
|
+
:Type => :Action,
|
491
|
+
:S => :URI,
|
492
|
+
:URI => "http://example.com"
|
493
|
+
})
|
494
494
|
array = [{ :text => "click " },
|
495
495
|
{ :text => "here", :link => "http://example.com" },
|
496
496
|
{ :text => " to visit" }]
|
@@ -499,8 +499,8 @@ describe "Text::Formatted::Box#render" do
|
|
499
499
|
end
|
500
500
|
it "should be able to add destination links" do
|
501
501
|
create_pdf
|
502
|
-
@pdf.
|
503
|
-
|
502
|
+
expect(@pdf).to receive(:link_annotation).with(kind_of(Array), :Border => [0, 0, 0],
|
503
|
+
:Dest => "ToC")
|
504
504
|
array = [{ :text => "Go to the " },
|
505
505
|
{ :text => "Table of Contents", :anchor => "ToC" }]
|
506
506
|
text_box = Prawn::Text::Formatted::Box.new(array, :document => @pdf)
|
@@ -508,13 +508,13 @@ describe "Text::Formatted::Box#render" do
|
|
508
508
|
end
|
509
509
|
it "should be able to add local actions" do
|
510
510
|
create_pdf
|
511
|
-
@pdf.
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
511
|
+
expect(@pdf).to receive(:link_annotation).with(kind_of(Array), :Border => [0, 0, 0],
|
512
|
+
:A => {
|
513
|
+
:Type => :Action,
|
514
|
+
:S => :Launch,
|
515
|
+
:F => "../example.pdf",
|
516
|
+
:NewWindow => true
|
517
|
+
})
|
518
518
|
array = [{ :text => "click " },
|
519
519
|
{ :text => "here", :local => "../example.pdf" },
|
520
520
|
{ :text => " to open a local file" }]
|