pdf-labels 1.0.1 → 2.0.1
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.
- data/History.txt +9 -1
- data/LICENCE +0 -0
- data/Manifest.txt +32 -31
- data/Rakefile +2 -2
- data/fonts/Code128.afm +275 -0
- data/fonts/Code128.pfb +0 -0
- data/fonts/Code128.ttf +0 -0
- data/fonts/Code2of5interleaved.afm +275 -0
- data/fonts/Code2of5interleaved.pfb +0 -0
- data/fonts/Code2of5interleaved.ttf +0 -0
- data/fonts/Code3de9.afm +275 -0
- data/fonts/Code3de9.pfb +0 -0
- data/fonts/Code3de9.ttf +0 -0
- data/fonts/CodeDatamatrix.afm +275 -0
- data/fonts/CodeDatamatrix.pfb +0 -0
- data/fonts/CodeDatamatrix.ttf +0 -0
- data/fonts/CodeEAN13.afm +275 -0
- data/fonts/CodeEAN13.pfb +0 -0
- data/fonts/CodeEAN13.ttf +0 -0
- data/fonts/CodePDF417.afm +275 -0
- data/fonts/CodePDF417.pfb +0 -0
- data/fonts/CodePDF417.ttf +0 -0
- data/init.rb +2 -0
- data/lib/pdf/label.rb +12 -0
- data/lib/pdf/label/alias.rb +11 -0
- data/lib/pdf/label/batch.rb +276 -0
- data/lib/pdf/label/glabel_template.rb +38 -0
- data/lib/pdf/label/label.rb +55 -0
- data/lib/pdf/label/layout.rb +15 -0
- data/lib/pdf/label/length_node.rb +52 -0
- data/lib/pdf/label/markup.rb +26 -0
- data/lib/pdf/label/template.rb +39 -0
- data/pdf-labels-patch-to-r62.txt +98 -0
- data/templates/avery-us-templates.xml +0 -0
- data/templates/glabels-2.0.dtd +0 -0
- data/test/test_pdf_label_page.rb +76 -18
- data/vendor/pdf/writer.rb +4 -1
- data/vendor/pdf/writer/object/font.rb +4 -4
- data/vendor/pdf/writer/object/viewerpreferences.rb +2 -2
- data/vendor/pdf_writer_font_patch.diff +35 -0
- metadata +84 -66
- data/lib/alias.rb +0 -8
- data/lib/glabel_template.rb +0 -36
- data/lib/label.rb +0 -52
- data/lib/layout.rb +0 -13
- data/lib/length_node.rb +0 -47
- data/lib/markup.rb +0 -25
- data/lib/pdf_label_page.rb +0 -171
- data/lib/pdf_labels.rb +0 -3
- data/lib/template.rb +0 -37
Binary file
|
Binary file
|
data/init.rb
ADDED
data/lib/pdf/label.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/label/batch')
|
2
|
+
|
3
|
+
module Pdf
|
4
|
+
module Label
|
5
|
+
VERSION = '2.0.1'
|
6
|
+
|
7
|
+
#We want the barcode fonts to be loaded as availible fonts in the font path
|
8
|
+
root = File.expand_path(File.dirname(__FILE__) + "/../../")
|
9
|
+
PDF::Writer::FONT_PATH << (root + "/fonts")
|
10
|
+
PDF::Writer::FontMetrics::METRICS_PATH << (root + "/fonts")
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,276 @@
|
|
1
|
+
$: << File.expand_path(File.dirname(__FILE__) + "/../../../vendor")
|
2
|
+
require 'xml/mapping'
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/glabel_template')
|
4
|
+
require 'pdf/writer'
|
5
|
+
#--- require 'breakpoint'
|
6
|
+
|
7
|
+
module Pdf
|
8
|
+
module Label
|
9
|
+
class Batch
|
10
|
+
attr_accessor :gt, :template, :label, :pdf, :barcode_font
|
11
|
+
@@gt = nil
|
12
|
+
def initialize(template_name, pdf_opts = {})
|
13
|
+
@@gt || self.class.load_template_set
|
14
|
+
unless @template = @@gt.find_template(template_name)
|
15
|
+
raise "Template not found!"
|
16
|
+
end
|
17
|
+
#if the template specifies the paper type, and the user didn't use it.
|
18
|
+
if @template.size && !pdf_opts.has_key?(:paper)
|
19
|
+
pdf_opts[:paper] = @template.size.gsub(/^.*-/,'')
|
20
|
+
end
|
21
|
+
#TODO figure out how to cope with multiple label types on a page
|
22
|
+
@label = @template.labels["0"]
|
23
|
+
#TODO figure out how to handle multiple layouts
|
24
|
+
@layout = @label.layouts[0]
|
25
|
+
@labels_per_page = @layout.nx * @layout.ny
|
26
|
+
@zero_based_labels_per_page = @labels_per_page - 1
|
27
|
+
|
28
|
+
@pdf = PDF::Writer.new(pdf_opts)
|
29
|
+
@pdf.margins_pt(0, 0, 0, 0)
|
30
|
+
|
31
|
+
# Turn off print scaling in the generated PDF to ensure
|
32
|
+
# that the labels are positioned correctly when printing
|
33
|
+
# TODO This goes boom! @pdf.viewer_preferences('PrintScaling', '/None')
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.load_template_set(template_set_file=nil)
|
37
|
+
template_set_file ||= File.expand_path(File.dirname(__FILE__) + "/../../../templates/avery-us-templates.xml")
|
38
|
+
@@gt = GlabelsTemplate.load_from_file(template_set_file)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.all_template_names
|
42
|
+
@@gt || self.load_template_set
|
43
|
+
@@gt.find_all_templates
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.all_barcode_fonts
|
47
|
+
{"Code128.afm" => :translation_needed,
|
48
|
+
"Code2of5interleaved.afm" => :translation_needed,
|
49
|
+
"Code3de9.afm" => :code39,
|
50
|
+
"CodeDatamatrix.afm" => :translation_needed,
|
51
|
+
"CodeEAN13.afm" => :translation_needed,
|
52
|
+
"CodePDF417.afm" => :translation_needed}
|
53
|
+
end
|
54
|
+
|
55
|
+
def code39(text)
|
56
|
+
out = text.upcase
|
57
|
+
raise "Characters Not Encodable in Code3of9" unless out.match(/^[0-9A-Z\-\. \/\$\+%\*]+$/)
|
58
|
+
out = "*" + out unless out.match(/^\*/)
|
59
|
+
out = out + "*" unless out.match(/\*$/)
|
60
|
+
return out
|
61
|
+
end
|
62
|
+
|
63
|
+
def translation_needed(text)
|
64
|
+
$stderr.puts("This barcode format does not automatically get formatted yet")
|
65
|
+
#TODO - Rob need to add barcode formatting
|
66
|
+
return text
|
67
|
+
end
|
68
|
+
|
69
|
+
=begin rdoc
|
70
|
+
add_label takes an argument hash.
|
71
|
+
[:position] Which label slot to print. Positions are top to bottom, left to right so position 1 is the label in the top lefthand corner. Defaults to 0
|
72
|
+
[:x & :y] The (x,y) coordinates on the page to print the text. Ignored if position is specified.
|
73
|
+
[:text] What you want to print in the label. Defaults to the (x,y) of the top left corner of the label.
|
74
|
+
[:use_margin] If the label has a markupMargin, setting this argument to true will respect that margin when writing text. Defaults to true.
|
75
|
+
[:justification] Values can be :left, :right, :center, :full. Defaults to :left
|
76
|
+
[:offset_x, offset_y] If your printer doesn't want to print with out margins you can define these values to fine tune printout.
|
77
|
+
=end
|
78
|
+
def add_label(options = {})
|
79
|
+
label_x, label_y, label_width = setup_add_label_options(options)
|
80
|
+
|
81
|
+
text = options[:text] || "[#{label_x / 72}, #{label_y / 72}]"
|
82
|
+
|
83
|
+
arg_hash = setup_arg_hash(options, label_x, label_y, label_width)
|
84
|
+
|
85
|
+
@pdf.y = label_y
|
86
|
+
@pdf.text(text,arg_hash)
|
87
|
+
end
|
88
|
+
|
89
|
+
=begin rdoc
|
90
|
+
You can add the same text to many labels this way, takes all the arguments of add_label, but must have position instead of x,y. Requires count.
|
91
|
+
[:count] - Number of labels to print
|
92
|
+
=end
|
93
|
+
def add_many_labels(options = {})
|
94
|
+
if (options[:x] || options[:y]) && !options[:position]
|
95
|
+
raise "Can't use X,Y with add_many_labels, you must use position"
|
96
|
+
end
|
97
|
+
if !options[:position]
|
98
|
+
options[:position] = 0
|
99
|
+
end
|
100
|
+
raise "Count required" unless options[:count]
|
101
|
+
count = options[:count]
|
102
|
+
count.times do
|
103
|
+
add_label(options)
|
104
|
+
options[:position] = options[:position] + 1
|
105
|
+
end
|
106
|
+
end
|
107
|
+
=begin rdoc
|
108
|
+
To facilitate aligning a printer we give a method that prints the outlines of the labels
|
109
|
+
=end
|
110
|
+
def draw_boxes(write_coord = true, draw_markups = true)
|
111
|
+
@layout.nx.times do |x|
|
112
|
+
@layout.ny.times do |y|
|
113
|
+
box_x, box_y = get_x_y(x, y)
|
114
|
+
@pdf.rounded_rectangle(box_x,
|
115
|
+
box_y,
|
116
|
+
@label.width.as_pts,
|
117
|
+
@label.height.as_pts,
|
118
|
+
@label.round.as_pts).stroke
|
119
|
+
if write_coord
|
120
|
+
text = "#{box_x / 72}, #{box_y / 72}, #{@label.width.number}, #{label.height.number}"
|
121
|
+
add_label(:x => box_x, :y => box_y, :text => text)
|
122
|
+
end
|
123
|
+
|
124
|
+
if draw_markups
|
125
|
+
@label.markupMargins.each {|margin|
|
126
|
+
size = margin.size.as_pts
|
127
|
+
@pdf.rounded_rectangle(box_x + size,
|
128
|
+
box_y - margin.size.as_pts,
|
129
|
+
@label.width.as_pts - 2*size,
|
130
|
+
@label.height.as_pts - 2*size,
|
131
|
+
@label.round.as_pts).stroke
|
132
|
+
}
|
133
|
+
@label.markupLines.each {|line|
|
134
|
+
@pdf.line(box_x + line.x1.as_pts,
|
135
|
+
box_y + line.y1.as_pts,
|
136
|
+
box_x + line.x2.as_pts,
|
137
|
+
box_y + line.y2.as_pts).stroke
|
138
|
+
}
|
139
|
+
=begin TODO Draw cirles
|
140
|
+
@label.markupCircles.each {|cicle|
|
141
|
+
@pdf.
|
142
|
+
=end
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
=begin rdoc
|
150
|
+
add_label takes an argument hash.
|
151
|
+
[:position] Which label slot to print. Positions are top to bottom, left to right so position 1 is the label in the top lefthand corner. Defaults to 0
|
152
|
+
[:x & :y] The (x,y) coordinates on the page to print the text. Ignored if position is specified.
|
153
|
+
[:text] What you want to print in the label. Defaults to the (x,y) of the top left corner of the label.
|
154
|
+
[:use_margin] If the label has a markupMargin, setting this argument to true will respect that margin when writing text. Defaults to true.
|
155
|
+
[:justification] Values can be :left, :right, :center, :full. Defaults to :left
|
156
|
+
[:offset_x, offset_y] If your printer doesn't want to print with out margins you can define these values to fine tune printout.
|
157
|
+
=end
|
158
|
+
def add_barcode_label(options = {})
|
159
|
+
label_x, label_y, label_width = setup_add_label_options(options)
|
160
|
+
|
161
|
+
text = options[:text] || "[#{label_x / 72}, #{label_y / 72}]"
|
162
|
+
|
163
|
+
bar_text = setup_bar_text(options, text)
|
164
|
+
|
165
|
+
arg_hash = setup_arg_hash(options, label_x, label_y, label_width)
|
166
|
+
|
167
|
+
bar_hash = arg_hash.clone
|
168
|
+
bar_hash[:font_size] = options[:bar_size] || 12
|
169
|
+
|
170
|
+
old_font = @pdf.current_font!
|
171
|
+
@pdf.select_font(self.barcode_font)
|
172
|
+
|
173
|
+
@pdf.y = label_y
|
174
|
+
@pdf.text(bar_text,bar_hash)
|
175
|
+
|
176
|
+
@pdf.select_font(old_font)
|
177
|
+
@pdf.text(text,arg_hash)
|
178
|
+
end
|
179
|
+
|
180
|
+
def save_as(file_name)
|
181
|
+
@pdf.save_as(file_name)
|
182
|
+
end
|
183
|
+
|
184
|
+
def barcode_font
|
185
|
+
@barcode_font ||= "Code3de9.afm"
|
186
|
+
end
|
187
|
+
|
188
|
+
def barcode_font=(value)
|
189
|
+
if Pdf::Label::Batch.all_barcode_fonts.keys.include?(value)
|
190
|
+
@barcode_font = value
|
191
|
+
return @barcode_font
|
192
|
+
else
|
193
|
+
raise "Barcode Font Not Found for #{value}"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
protected
|
198
|
+
|
199
|
+
=begin rdoc
|
200
|
+
Position is top to bottom, left to right, starting at 1 and ending at the end of the page
|
201
|
+
=end
|
202
|
+
def position_to_x_y(position)
|
203
|
+
x = (position * 1.0 / @layout.ny).floor
|
204
|
+
y = position % @layout.ny
|
205
|
+
return get_x_y(x, y)
|
206
|
+
end
|
207
|
+
|
208
|
+
def get_x_y(x, y)
|
209
|
+
label_y = @pdf.absolute_top_margin
|
210
|
+
label_y = label_y + @pdf.top_margin
|
211
|
+
label_y = label_y - @layout.y0.as_pts
|
212
|
+
label_y = label_y - y * @layout.dy.as_pts
|
213
|
+
|
214
|
+
label_x = @pdf.absolute_left_margin
|
215
|
+
label_x = label_x - @pdf.left_margin
|
216
|
+
label_x = label_x + @layout.x0.as_pts
|
217
|
+
label_x = label_x + x * @layout.dx.as_pts
|
218
|
+
|
219
|
+
return label_x, label_y
|
220
|
+
end
|
221
|
+
|
222
|
+
def setup_add_label_options(options)
|
223
|
+
if position = options[:position]
|
224
|
+
# condition to handle multi-page PDF generation. If true, we're past the first page
|
225
|
+
if position > @zero_based_labels_per_page
|
226
|
+
# if remainder is zero, we're dealing with the first label of a new page
|
227
|
+
@pdf.new_page if ((position) % @labels_per_page) == 0
|
228
|
+
# Translate the position to a value between 1 and the number of labels for a given page
|
229
|
+
position = position - (position/@labels_per_page)*@labels_per_page
|
230
|
+
end
|
231
|
+
label_x, label_y = position_to_x_y(position)
|
232
|
+
elsif((label_x = options[:x]) && (label_y = options[:y]))
|
233
|
+
else
|
234
|
+
label_x, label_y = position_to_x_y(0)
|
235
|
+
end
|
236
|
+
#line wrap margin
|
237
|
+
label_width = label_x + @label.width.as_pts
|
238
|
+
|
239
|
+
if (use_margin = options[:use_margin]).nil?
|
240
|
+
use_margin = true
|
241
|
+
end
|
242
|
+
if use_margin
|
243
|
+
@label.markupMargins.each {|margin|
|
244
|
+
label_x = label_x + margin.size.as_pts
|
245
|
+
label_y = label_y - margin.size.as_pts
|
246
|
+
label_width = label_width - margin.size.as_pts
|
247
|
+
}
|
248
|
+
end
|
249
|
+
|
250
|
+
if offset = options[:offset_x]
|
251
|
+
label_x = label_x + offset
|
252
|
+
label_width = label_width + offset
|
253
|
+
end
|
254
|
+
if offset = options[:offset_y]
|
255
|
+
label_y = label_y + offset
|
256
|
+
end
|
257
|
+
return label_x, label_y, label_width
|
258
|
+
end
|
259
|
+
|
260
|
+
def setup_arg_hash(options, label_x, label_y, label_width)
|
261
|
+
arg_hash = {:justification => (options[:justification] || :left),
|
262
|
+
:font_size => (options[:font_size] || 12)}
|
263
|
+
|
264
|
+
arg_hash = arg_hash.merge :absolute_left => label_x,
|
265
|
+
:absolute_right => label_width
|
266
|
+
|
267
|
+
end
|
268
|
+
|
269
|
+
def setup_bar_text(options, text)
|
270
|
+
bar_text = options[:bar_text] || text
|
271
|
+
bar_text = send(Pdf::Label::Batch.all_barcode_fonts[self.barcode_font], bar_text)
|
272
|
+
end
|
273
|
+
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/template')
|
2
|
+
module Pdf
|
3
|
+
module Label
|
4
|
+
class GlabelsTemplate
|
5
|
+
include XML::Mapping
|
6
|
+
|
7
|
+
hash_node :templates, "Template", "@name", :class => Template, :default_value => Hash.new
|
8
|
+
|
9
|
+
def find_all_templates
|
10
|
+
return @t unless @t.nil?
|
11
|
+
@t = []
|
12
|
+
templates.each {|t|
|
13
|
+
@t << "#{t[1].name}"
|
14
|
+
t[1].alias.each {|a|
|
15
|
+
@t << "#{a[1].name}"
|
16
|
+
}
|
17
|
+
}
|
18
|
+
return @t
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def find_template(t_name)
|
23
|
+
return find_all_with_templates if t_name == :all
|
24
|
+
if t = templates[t_name]
|
25
|
+
return t
|
26
|
+
else
|
27
|
+
templates.each { |t|
|
28
|
+
if t[1].alias[t_name]
|
29
|
+
return t[1]
|
30
|
+
end
|
31
|
+
}
|
32
|
+
end
|
33
|
+
return nil
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/length_node')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/layout')
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/markup')
|
4
|
+
|
5
|
+
module Pdf
|
6
|
+
module Label
|
7
|
+
|
8
|
+
class Label
|
9
|
+
include XML::Mapping
|
10
|
+
attr_accessor :shape
|
11
|
+
numeric_node :id, "@id"
|
12
|
+
array_node :markupMargins, "Markup-margin", :class => MarkupMargin, :default_value => nil
|
13
|
+
array_node :markupLines, "Markup-line", :class => MarkupLine, :default_value => nil
|
14
|
+
array_node :markupCircles, "Markup-circle", :class => MarkupCircle, :default_value => nil
|
15
|
+
|
16
|
+
array_node :layouts, "Layout", :class => Layout
|
17
|
+
|
18
|
+
def markups
|
19
|
+
@markups = Hash.new
|
20
|
+
@markups = @markups.merge @markupMargins
|
21
|
+
@markups = @markups.merge @markupLines
|
22
|
+
@markups = @markups.merge @markupCircles
|
23
|
+
@markups
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
class LabelRectangle < Label
|
31
|
+
length_node :width, "@width"
|
32
|
+
length_node :height, "@height"
|
33
|
+
length_node :round, "@round", :default_value => "0 pt"
|
34
|
+
length_node :waste, "@waste", :default_value => "0 pt"
|
35
|
+
length_node :x_waste, "@x_waste", :default_value => "0 pt"
|
36
|
+
length_node :y_waste, "@y_waste", :default_value => "0 pt"
|
37
|
+
@kind = "Rectangle"
|
38
|
+
end
|
39
|
+
|
40
|
+
class LabelRound < Label
|
41
|
+
length_node :radius, "@radius"
|
42
|
+
length_node :waste, "@radius", :default_value => "0 pt"
|
43
|
+
@kind = "Round"
|
44
|
+
end
|
45
|
+
|
46
|
+
class LabelCD < Label
|
47
|
+
length_node :radius, "@radius"
|
48
|
+
length_node :hole, "@hole"
|
49
|
+
length_node :width, "@width", :default_value => ""
|
50
|
+
length_node :height, "@height", :default_value => ""
|
51
|
+
length_node :waste, "@waste", :default_value => ""
|
52
|
+
@kind = "CD"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/length_node')
|
2
|
+
|
3
|
+
module Pdf
|
4
|
+
module Label
|
5
|
+
class Layout
|
6
|
+
include XML::Mapping
|
7
|
+
numeric_node :nx, "@nx"
|
8
|
+
numeric_node :ny, "@ny"
|
9
|
+
length_node :x0, "@x0", :default_value => "0 pt"
|
10
|
+
length_node :y0, "@y0", :default_value => "0 pt"
|
11
|
+
length_node :dx, "@dx", :default_value => "0 pt"
|
12
|
+
length_node :dy, "@dy", :default_value => "0 pt"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'xml/mapping/base'
|
2
|
+
|
3
|
+
module Pdf
|
4
|
+
module Label
|
5
|
+
class Length
|
6
|
+
attr_accessor :value, :unit, :number
|
7
|
+
|
8
|
+
def initialize(value)
|
9
|
+
@value = value
|
10
|
+
@number = value.match(/[\d\.]*/)[0].to_f
|
11
|
+
@unit = value.delete("#{number}").strip
|
12
|
+
end
|
13
|
+
|
14
|
+
#Return the numeric portion as a Points
|
15
|
+
def as_pts
|
16
|
+
if @unit =~ /pt/
|
17
|
+
return @number
|
18
|
+
elsif @unit =~ /in/
|
19
|
+
return @number * 72 #72.270
|
20
|
+
elsif @unit =~ /mm/
|
21
|
+
return @number * 2.83464566929134
|
22
|
+
elsif @unit =~ /cm/
|
23
|
+
return @number * 28.3464566929134
|
24
|
+
elsif @unit =~ /pc/
|
25
|
+
return 1.0 * @number / 12
|
26
|
+
elsif @unit == ''
|
27
|
+
return @number
|
28
|
+
else
|
29
|
+
raise "Unit #{unit} unknown"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class LengthNode < XML::Mapping::SingleAttributeNode
|
35
|
+
def initialize_impl(path)
|
36
|
+
@path = XML::XXPath.new(path)
|
37
|
+
end
|
38
|
+
|
39
|
+
def extract_attr_value(xml)
|
40
|
+
@value = default_when_xpath_err{@path.first(xml).text}
|
41
|
+
Length.new(@value)
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_attr_value(xml, value)
|
45
|
+
raise "Not a Length: #{value}" unless Length===value
|
46
|
+
@path.first(xml,:ensure_created=>true).text = value.value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
XML::Mapping.add_node_class LengthNode
|
51
|
+
end
|
52
|
+
end
|