usps_intelligent_barcode 0.3.1 → 1.1.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.
- checksums.yaml +5 -13
- data/.gitignore +6 -0
- data/.ruby-version +1 -0
- data/.travis.yml +2 -1
- data/{CHANGELOG.markdown → CHANGELOG.md} +27 -12
- data/Gemfile +2 -12
- data/Gemfile.lock +74 -81
- data/LICENSE.md +7 -0
- data/README.md +92 -0
- data/Rakefile +4 -28
- data/VERSION +1 -1
- data/doc/font_installation.md +55 -0
- data/doc/migration.md +47 -0
- data/doc/publishing.md +75 -0
- data/examples/example.rb +4 -4
- data/examples/generate_pdf.rb +70 -0
- data/fonts/LICENSE +47 -0
- data/fonts/USPSIMBCompact.ttf +0 -0
- data/fonts/USPSIMBStandard.ttf +0 -0
- data/lib/usps_intelligent_barcode/bar_map.rb +10 -8
- data/lib/usps_intelligent_barcode/bar_position.rb +14 -10
- data/lib/usps_intelligent_barcode/bar_symbol.rb +42 -12
- data/lib/usps_intelligent_barcode/bar_to_character_mapping.yml +24 -0
- data/lib/usps_intelligent_barcode/barcode.rb +83 -39
- data/lib/usps_intelligent_barcode/barcode_id.rb +19 -12
- data/lib/usps_intelligent_barcode/character_position.rb +10 -8
- data/lib/usps_intelligent_barcode/codeword_map.rb +0 -1
- data/lib/usps_intelligent_barcode/codeword_to_character_mapping.yml +7 -0
- data/lib/usps_intelligent_barcode/crc.rb +3 -3
- data/lib/usps_intelligent_barcode/mailer_id.rb +18 -14
- data/lib/usps_intelligent_barcode/numeric_conversions.rb +7 -6
- data/lib/usps_intelligent_barcode/project_dirs.rb +19 -0
- data/lib/usps_intelligent_barcode/routing_code.rb +38 -27
- data/lib/usps_intelligent_barcode/serial_number.rb +16 -11
- data/lib/usps_intelligent_barcode/service_type.rb +14 -12
- data/lib/usps_intelligent_barcode/usps_fonts.rb +48 -0
- data/lib/usps_intelligent_barcode.rb +2 -0
- data/rake/bundler.rb +1 -0
- data/rake/default.rb +1 -0
- data/rake/rspec.rb +2 -0
- data/rake/version.rb +33 -0
- data/rake/yard.rb +9 -0
- data/usps_intelligent_barcode.gemspec +24 -99
- metadata +68 -71
- data/README.markdown +0 -69
- data/USPS-intelligent-barcode.gemspec +0 -95
- data/spec/bar_map_spec.rb +0 -30
- data/spec/bar_position_spec.rb +0 -40
- data/spec/bar_symbol_spec.rb +0 -39
- data/spec/barcode_id_spec.rb +0 -106
- data/spec/barcode_spec.rb +0 -213
- data/spec/character_position_spec.rb +0 -25
- data/spec/codeword_map_spec.rb +0 -22
- data/spec/crc_spec.rb +0 -21
- data/spec/mailer_id_spec.rb +0 -124
- data/spec/numeric_conversions_spec.rb +0 -23
- data/spec/routing_code_spec.rb +0 -180
- data/spec/serial_number_spec.rb +0 -117
- data/spec/service_type_spec.rb +0 -93
- data/spec/spec_helper.rb +0 -8
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
lib_dir = File.expand_path('../lib', __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
|
|
5
|
+
require 'usps_intelligent_barcode'
|
|
6
|
+
|
|
7
|
+
begin
|
|
8
|
+
require 'prawn'
|
|
9
|
+
rescue LoadError
|
|
10
|
+
puts "This example requires the 'prawn' gem."
|
|
11
|
+
puts "Install it with: gem install prawn"
|
|
12
|
+
exit 1
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Generate a PDF using a USPS Intelligent Mail Barcode font
|
|
16
|
+
#
|
|
17
|
+
# Uses bundled USPS IMB fonts - no installation required.
|
|
18
|
+
|
|
19
|
+
class BarcodeToPDFFont
|
|
20
|
+
OUTPUT_FILENAME = 'barcode_font.pdf'
|
|
21
|
+
FONT_NAME = 'USPSIMBStandard'
|
|
22
|
+
FONT_FILE = Imb::UspsFonts.standard_font_path
|
|
23
|
+
FONT_SIZE = Imb::UspsFonts.font_size
|
|
24
|
+
|
|
25
|
+
def initialize(barcode)
|
|
26
|
+
@barcode = barcode
|
|
27
|
+
@letters = barcode.barcode_letters
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def generate(path)
|
|
31
|
+
Prawn::Document.generate(path, page_size: 'LETTER') do |pdf|
|
|
32
|
+
pdf.text "USPS Intelligent Mail Barcode (Font-Based)", size: 16, style: :bold
|
|
33
|
+
pdf.move_down 10
|
|
34
|
+
pdf.text "Barcode ID: #{@barcode.barcode_id.to_s}"
|
|
35
|
+
pdf.text "Service Type: #{@barcode.service_type.to_s}"
|
|
36
|
+
pdf.text "Mailer ID: #{@barcode.mailer_id.to_s}"
|
|
37
|
+
pdf.text "Serial Number: #{@barcode.serial_number.to_s}"
|
|
38
|
+
pdf.text "Routing Code: #{@barcode.routing_code.to_s}"
|
|
39
|
+
pdf.text "\n"
|
|
40
|
+
pdf.text "Barcode string: #{@letters}", size: 8
|
|
41
|
+
pdf.text "\n"
|
|
42
|
+
pdf.text "Barcode string printed with font #{FONT_NAME} #{FONT_SIZE}"
|
|
43
|
+
render_barcode(pdf)
|
|
44
|
+
pdf.move_down 10
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def render_barcode(pdf)
|
|
51
|
+
pdf.font_families.update(FONT_NAME => { normal: FONT_FILE })
|
|
52
|
+
pdf.font(FONT_NAME) do
|
|
53
|
+
pdf.text @letters, size: FONT_SIZE
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Example usage
|
|
59
|
+
barcode = Imb::Barcode.new(
|
|
60
|
+
'01', # barcode_id
|
|
61
|
+
'234', # service_type
|
|
62
|
+
'567094', # mailer_id
|
|
63
|
+
'987654321', # serial_number
|
|
64
|
+
'01234567891' # routing_code
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
pdf_generator = BarcodeToPDFFont.new(barcode)
|
|
68
|
+
path = "/tmp/barcode_to_pdf.pdf"
|
|
69
|
+
pdf_generator.generate(path)
|
|
70
|
+
puts "Generated #{path}"
|
data/fonts/LICENSE
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
USPS Intelligent Mail Barcode Fonts License
|
|
2
|
+
============================================
|
|
3
|
+
|
|
4
|
+
Copyright © US Postal Service 2009
|
|
5
|
+
|
|
6
|
+
LICENSE GRANT
|
|
7
|
+
|
|
8
|
+
The Postal Service grants you a world-wide, royalty-free, non-exclusive license
|
|
9
|
+
to use the Software, Documentation, and Fonts in the mailing industry without
|
|
10
|
+
paying a royalty or other fee, including rights to reproduce, display, merge,
|
|
11
|
+
sell, distribute, sublicense, and translate them; and the right to create
|
|
12
|
+
derivative works.
|
|
13
|
+
|
|
14
|
+
CONDITIONS
|
|
15
|
+
|
|
16
|
+
Redistribution of the Software, Documentation, and/or Fonts must contain:
|
|
17
|
+
(a) the copyright notice set forth above
|
|
18
|
+
(b) this list of conditions
|
|
19
|
+
(c) the disclaimer noted below
|
|
20
|
+
|
|
21
|
+
You may redistribute your versions of the Software and/or Fonts if your versions
|
|
22
|
+
were created by making modifications or improvements for a legitimate purpose in
|
|
23
|
+
furtherance of your or another's business.
|
|
24
|
+
|
|
25
|
+
The US Postal Service reserves the right to change this License in any manner
|
|
26
|
+
within reason at its discretion.
|
|
27
|
+
|
|
28
|
+
DISCLAIMER
|
|
29
|
+
|
|
30
|
+
THE POSTAL SERVICE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
|
31
|
+
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
32
|
+
PURPOSE. IN NO EVENT SHALL THE POSTAL SERVICE BE LIABLE FOR ANY SPECIAL,
|
|
33
|
+
INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
34
|
+
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
35
|
+
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
|
36
|
+
THIS SOFTWARE.
|
|
37
|
+
|
|
38
|
+
FONTS INCLUDED
|
|
39
|
+
|
|
40
|
+
- USPSIMBStandard.ttf - Primary USPS Intelligent Mail Barcode font
|
|
41
|
+
- USPSIMBCompact.ttf - Compact variant of the USPS Intelligent Mail Barcode font
|
|
42
|
+
|
|
43
|
+
OFFICIAL SOURCE
|
|
44
|
+
|
|
45
|
+
Official USPS font downloads and documentation:
|
|
46
|
+
https://postalpro.usps.com/mailing/encoder-software-and-fonts
|
|
47
|
+
https://postalpro.usps.com/onecodesolution
|
|
Binary file
|
|
Binary file
|
|
@@ -4,9 +4,7 @@ require 'usps_intelligent_barcode/character_position'
|
|
|
4
4
|
|
|
5
5
|
module Imb
|
|
6
6
|
|
|
7
|
-
# Maps intelligent barcode "characters" to
|
|
8
|
-
# type of bar to print at each given position.
|
|
9
|
-
|
|
7
|
+
# Maps intelligent barcode "characters" to the actual barcode.
|
|
10
8
|
class BarMap
|
|
11
9
|
|
|
12
10
|
def initialize
|
|
@@ -15,9 +13,11 @@ module Imb
|
|
|
15
13
|
|
|
16
14
|
# Given an array of intelligent barcode "characters", return an
|
|
17
15
|
# the symbols for each position.
|
|
18
|
-
#
|
|
19
|
-
# @
|
|
20
|
-
|
|
16
|
+
#
|
|
17
|
+
# @param characters [Array<Integer>] array of 13-bit "characters"
|
|
18
|
+
# between 0 and 1364
|
|
19
|
+
# @return [Array<BarSymbol>] array of symbols,
|
|
20
|
+
# e.g. [BarSymbol::TRACKER, BarSymbol::ASCENDER, ...]
|
|
21
21
|
def symbols(characters)
|
|
22
22
|
@mapping.map do |bar_position|
|
|
23
23
|
bar_position.map(characters)
|
|
@@ -34,8 +34,10 @@ module Imb
|
|
|
34
34
|
mapping_data.map do |descender, ascender|
|
|
35
35
|
descender_character_position = CharacterPosition.new(*descender)
|
|
36
36
|
ascender_character_position = CharacterPosition.new(*ascender)
|
|
37
|
-
BarPosition.new(
|
|
38
|
-
|
|
37
|
+
BarPosition.new(
|
|
38
|
+
descender_character_position,
|
|
39
|
+
ascender_character_position,
|
|
40
|
+
)
|
|
39
41
|
end
|
|
40
42
|
end
|
|
41
43
|
|
|
@@ -2,14 +2,17 @@ module Imb
|
|
|
2
2
|
|
|
3
3
|
# @!group Internal
|
|
4
4
|
|
|
5
|
-
# Represents a position (
|
|
6
|
-
# internal and may change.
|
|
7
|
-
|
|
5
|
+
# Represents a position (a vertical bar) in the barcode. This class
|
|
6
|
+
# is internal and may change.
|
|
7
|
+
#
|
|
8
|
+
# Each bar represents two bits, but which two bits it represents is
|
|
9
|
+
# determined by the "Bar to Character Mapping" table in the
|
|
10
|
+
# specification (see Table 22, "Bar to Character Mapping", appendix
|
|
11
|
+
# E) in the specification linked to in the README.
|
|
8
12
|
class BarPosition
|
|
9
13
|
|
|
10
|
-
# @param [CharacterPosition]
|
|
11
|
-
# @param [CharacterPosition]
|
|
12
|
-
|
|
14
|
+
# @param descender_character_position [CharacterPosition]
|
|
15
|
+
# @param ascender_character_position [CharacterPosition]
|
|
13
16
|
def initialize(descender_character_position, ascender_character_position)
|
|
14
17
|
@descender_character_position = descender_character_position
|
|
15
18
|
@ascender_character_position = ascender_character_position
|
|
@@ -17,12 +20,13 @@ module Imb
|
|
|
17
20
|
|
|
18
21
|
# Given an array of characters, return a symbol for this
|
|
19
22
|
# barcode position.
|
|
20
|
-
# @param [
|
|
23
|
+
# @param characters [Array<Integer>] character codes
|
|
21
24
|
# @return [BarSymbol] symbol code
|
|
22
|
-
|
|
23
25
|
def map(characters)
|
|
24
|
-
BarSymbol.make(
|
|
25
|
-
|
|
26
|
+
BarSymbol.make(
|
|
27
|
+
ascender_bit(characters),
|
|
28
|
+
descender_bit(characters),
|
|
29
|
+
)
|
|
26
30
|
end
|
|
27
31
|
|
|
28
32
|
private
|
|
@@ -2,14 +2,34 @@ module Imb
|
|
|
2
2
|
|
|
3
3
|
# @!group Internal
|
|
4
4
|
|
|
5
|
-
# Represents a symbol in the barcode.
|
|
6
|
-
|
|
5
|
+
# Represents a symbol in the barcode. A symbol encodes two bits,
|
|
6
|
+
# and is represented as one of four characters to be printed using
|
|
7
|
+
# one of the USPS Intelligent Barcode fonts. Each character, when
|
|
8
|
+
# printed using that font, results in a vertical bar having three
|
|
9
|
+
# part: An ascender, which may be present or missing; a descender,
|
|
10
|
+
# which may be present or missing; and between the ascender and the
|
|
11
|
+
# descender, a tracker which is always present.
|
|
12
|
+
#
|
|
13
|
+
# This chart shows the bits being encoded (ascender bit, then
|
|
14
|
+
# descender bit), the code (ASCII character) used for the barcode
|
|
15
|
+
# font, and an ASCII art repsentation of the bar that is printed by
|
|
16
|
+
# that code:
|
|
17
|
+
#
|
|
18
|
+
# bits: 00 01 10 11
|
|
19
|
+
#
|
|
20
|
+
# ascender: | |
|
|
21
|
+
# tracker: | | | |
|
|
22
|
+
# descender: | |
|
|
23
|
+
#
|
|
24
|
+
# code: T D A F
|
|
25
|
+
# mnemonic: tracker descender ascender full
|
|
7
26
|
class BarSymbol
|
|
8
27
|
|
|
9
|
-
#
|
|
10
|
-
#
|
|
28
|
+
# Return the symbol for a given ascender bit and descender bit.
|
|
29
|
+
#
|
|
30
|
+
# @param ascender_bit [Integer] 0 or 1
|
|
31
|
+
# @param descender_bit [Integer] 0 or 1
|
|
11
32
|
# @return [BarSymbol]
|
|
12
|
-
|
|
13
33
|
def self.make(ascender_bit, descender_bit)
|
|
14
34
|
case [ascender_bit, descender_bit]
|
|
15
35
|
when [0, 0]
|
|
@@ -29,9 +49,12 @@ module Imb
|
|
|
29
49
|
# @return [String] the letter for this symbol
|
|
30
50
|
attr_reader :letter
|
|
31
51
|
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
|
|
52
|
+
# Make an instance.
|
|
53
|
+
#
|
|
54
|
+
# @param code [Integer] Binary number from 0b00 to 0b11. Bit 1
|
|
55
|
+
# controls the ascender; bit 0 controls the descender.
|
|
56
|
+
# @param letter [String] The character to print in the barcode
|
|
57
|
+
# font.
|
|
35
58
|
def initialize(code, letter)
|
|
36
59
|
@code = code
|
|
37
60
|
@letter = letter
|
|
@@ -39,10 +62,17 @@ module Imb
|
|
|
39
62
|
|
|
40
63
|
private
|
|
41
64
|
|
|
42
|
-
TRACKER = BarSymbol.new(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
65
|
+
TRACKER = BarSymbol.new(0b00, 'T')
|
|
66
|
+
private_constant :TRACKER
|
|
67
|
+
|
|
68
|
+
DESCENDER = BarSymbol.new(0b01, 'D')
|
|
69
|
+
private_constant :DESCENDER
|
|
70
|
+
|
|
71
|
+
ASCENDER = BarSymbol.new(0b10, 'A')
|
|
72
|
+
private_constant :ASCENDER
|
|
73
|
+
|
|
74
|
+
FULL = BarSymbol.new(0b11, 'F')
|
|
75
|
+
private_constant :FULL
|
|
46
76
|
|
|
47
77
|
end
|
|
48
78
|
|
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
# Represents the table from the spec. that controls how the bits of
|
|
2
|
+
# the "characters" are mapped to barcode positions. Each line of this
|
|
3
|
+
# file represents a single barcode position (or vertical line), with
|
|
4
|
+
# the leftmost position first.
|
|
5
|
+
#
|
|
6
|
+
# Let's decode the first line:
|
|
7
|
+
#
|
|
8
|
+
# [
|
|
9
|
+
# [
|
|
10
|
+
# 7, # descender character index (0..)
|
|
11
|
+
# 2, # descender bit number (0..)
|
|
12
|
+
# ],
|
|
13
|
+
# [
|
|
14
|
+
# 4, # ascender character index (0..)
|
|
15
|
+
# 3, #ascender bit number (0..)
|
|
16
|
+
# ]
|
|
17
|
+
# ]
|
|
18
|
+
#
|
|
19
|
+
# This means that the first barcode position's ascender is controlled
|
|
20
|
+
# by bit 2 of character 7; the descender is controlled by bit 3 of
|
|
21
|
+
# character 4.
|
|
22
|
+
#
|
|
23
|
+
# See spec. section 10.0 ("Appendix E -- Tables for Converting
|
|
24
|
+
# Characters"), Table 22 ("Bar to Character Mapping")
|
|
1
25
|
---
|
|
2
26
|
- [[7, 2], [4, 3]]
|
|
3
27
|
- [[1, 10], [0, 0]]
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
1
3
|
require 'usps_intelligent_barcode/codeword_map'
|
|
2
4
|
require 'usps_intelligent_barcode/crc'
|
|
3
5
|
|
|
4
6
|
# The namespace for everything in this library.
|
|
5
|
-
|
|
6
7
|
module Imb
|
|
7
8
|
|
|
8
|
-
# This class
|
|
9
|
-
|
|
9
|
+
# This class turns a collection of fields into a string of symbols
|
|
10
|
+
# for printing using a barcode font.
|
|
10
11
|
class Barcode
|
|
11
12
|
|
|
12
13
|
include Memoizer
|
|
@@ -26,8 +27,6 @@ module Imb
|
|
|
26
27
|
# @return [RoutingCode]
|
|
27
28
|
attr_reader :routing_code
|
|
28
29
|
|
|
29
|
-
# @param
|
|
30
|
-
|
|
31
30
|
# Create a new barcode
|
|
32
31
|
#
|
|
33
32
|
# @param barcode_id [String] Nominally a String, but can be
|
|
@@ -40,12 +39,13 @@ module Imb
|
|
|
40
39
|
# anything that {SerialNumber.coerce} will accept.
|
|
41
40
|
# @param routing_code [String] Nominally a String, but can be
|
|
42
41
|
# anything that {RoutingCode.coerce} will accept.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
def initialize(
|
|
43
|
+
barcode_id,
|
|
44
|
+
service_type,
|
|
45
|
+
mailer_id,
|
|
46
|
+
serial_number,
|
|
47
|
+
routing_code
|
|
48
|
+
)
|
|
49
49
|
@barcode_id = BarcodeId.coerce(barcode_id)
|
|
50
50
|
@service_type = ServiceType.coerce(service_type)
|
|
51
51
|
@mailer_id = MailerId.coerce(mailer_id)
|
|
@@ -61,39 +61,18 @@ module Imb
|
|
|
61
61
|
# * 'D' for a descender mark
|
|
62
62
|
# * 'F' for a full mark (both ascender and descender)
|
|
63
63
|
# @return [String] A string that represents the barcode.
|
|
64
|
-
|
|
65
64
|
def barcode_letters
|
|
66
65
|
symbols.map(&:letter).join
|
|
67
66
|
end
|
|
68
|
-
|
|
69
|
-
private
|
|
70
|
-
|
|
71
|
-
# :stopdoc:
|
|
72
|
-
BAR_MAP = BarMap.new
|
|
73
|
-
CODEWORD_MAP = CodewordMap.new
|
|
74
|
-
CRC = Crc.new
|
|
75
|
-
# :startdoc:
|
|
76
67
|
|
|
77
|
-
|
|
78
|
-
components.each do |component|
|
|
79
|
-
component.validate(long_mailer_id?)
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def components
|
|
84
|
-
[
|
|
85
|
-
@routing_code,
|
|
86
|
-
@barcode_id,
|
|
87
|
-
@service_type,
|
|
88
|
-
@mailer_id,
|
|
89
|
-
@serial_number,
|
|
90
|
-
]
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def long_mailer_id?
|
|
94
|
-
@mailer_id.long?
|
|
95
|
-
end
|
|
68
|
+
# @!group Algorithm Steps - Public for testing
|
|
96
69
|
|
|
70
|
+
# The components ("fields" in the spec) are turned into a single
|
|
71
|
+
# number that the spec calls "binary_data"). This is done through
|
|
72
|
+
# a series of multiplications and additions. See spec. section
|
|
73
|
+
# 2.2.1 ("Step 1--Conversion of Data Fields into Binary Data").
|
|
74
|
+
#
|
|
75
|
+
# @return [Integer]
|
|
97
76
|
def binary_data
|
|
98
77
|
components.inject(0) do |data, component|
|
|
99
78
|
component.shift_and_add_to(data, long_mailer_id?)
|
|
@@ -101,11 +80,20 @@ module Imb
|
|
|
101
80
|
end
|
|
102
81
|
memoize :binary_data
|
|
103
82
|
|
|
83
|
+
# Compute the "frame check sequence." See spec. section 2.2.2
|
|
84
|
+
# ("Step 2--Generation of 11-Bit CRC on Binary Data").
|
|
85
|
+
#
|
|
86
|
+
# @return [Integer]
|
|
104
87
|
def frame_check_sequence
|
|
105
88
|
CRC.crc(binary_data)
|
|
106
89
|
end
|
|
107
90
|
memoize :frame_check_sequence
|
|
108
91
|
|
|
92
|
+
# Compute the "code words." This is an array of 10 integers
|
|
93
|
+
# computed from the binary data. See spec. section 2.2.3 ("Step
|
|
94
|
+
# 3--Conversion from Binary Data to Codewords").
|
|
95
|
+
#
|
|
96
|
+
# @return [Array<Integer>] 10 "characters."
|
|
109
97
|
def codewords
|
|
110
98
|
codewords = []
|
|
111
99
|
data = binary_data
|
|
@@ -117,22 +105,45 @@ module Imb
|
|
|
117
105
|
end
|
|
118
106
|
memoize :codewords
|
|
119
107
|
|
|
108
|
+
# Insert the orientation into the codewords. The spec. doesn't
|
|
109
|
+
# say much about this, other than to multiply codeword "J" by two.
|
|
110
|
+
# This will cause the LSB to be zero, which is presumably the
|
|
111
|
+
# orientation. See spec. section 2.4.4 ("Step 4--Inserting
|
|
112
|
+
# Additional Information into Codewords").
|
|
113
|
+
#
|
|
114
|
+
# @return [Array<Integer>] 10 "characters."
|
|
120
115
|
def codewords_with_orientation_in_character_j
|
|
121
116
|
result = codewords.dup
|
|
122
117
|
result[9] *= 2
|
|
123
118
|
result
|
|
124
119
|
end
|
|
125
120
|
|
|
121
|
+
# Insert the most significant bit of the FCS into codeword A. See
|
|
122
|
+
# spec. section 2.4.4 ("Step 4--Inserting Additional Information
|
|
123
|
+
# into Codewords").
|
|
124
|
+
#
|
|
125
|
+
# @return [Array<Integer>] 10 "characters."
|
|
126
126
|
def codewords_with_fcs_bit_in_character_a
|
|
127
127
|
result = codewords_with_orientation_in_character_j.dup
|
|
128
128
|
result[0] += 659 if frame_check_sequence[10] == 1
|
|
129
129
|
result
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
+
# Convert the codewords to "characters". Each character is a
|
|
133
|
+
# 13-bit integer; there are 10 of them, labeled "A" through "J" by
|
|
134
|
+
# the spec. See spec. section 2.2.5 ("Step 5--Conversion from
|
|
135
|
+
# Codewords to Characters"), para. "A".
|
|
136
|
+
#
|
|
137
|
+
# @return [Array<Integer>] 10 "characters."
|
|
132
138
|
def characters
|
|
133
139
|
CODEWORD_MAP.characters(codewords_with_fcs_bit_in_character_a)
|
|
134
140
|
end
|
|
135
141
|
|
|
142
|
+
# Fold the least-significant 10 bits of the FCS into the
|
|
143
|
+
# "characters". See spec. section 2.2.5 ("Step 5--Conversion from
|
|
144
|
+
# Codewords to Characters"), para. "B".
|
|
145
|
+
#
|
|
146
|
+
# @return [Array<Integer>] 10 "characters".
|
|
136
147
|
def characters_with_fcs_bits_0_through_9
|
|
137
148
|
characters.each_with_index.map do |character, i|
|
|
138
149
|
if frame_check_sequence[i] == 1
|
|
@@ -143,6 +154,39 @@ module Imb
|
|
|
143
154
|
end
|
|
144
155
|
end
|
|
145
156
|
|
|
157
|
+
# @!endgroup
|
|
158
|
+
|
|
159
|
+
private
|
|
160
|
+
|
|
161
|
+
# :stopdoc:
|
|
162
|
+
BAR_MAP = BarMap.new
|
|
163
|
+
CODEWORD_MAP = CodewordMap.new
|
|
164
|
+
CRC = Crc.new
|
|
165
|
+
# :startdoc:
|
|
166
|
+
|
|
167
|
+
def validate_components
|
|
168
|
+
components.each do |component|
|
|
169
|
+
component.validate(long_mailer_id?)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def components
|
|
174
|
+
[
|
|
175
|
+
@routing_code,
|
|
176
|
+
@barcode_id,
|
|
177
|
+
@service_type,
|
|
178
|
+
@mailer_id,
|
|
179
|
+
@serial_number,
|
|
180
|
+
]
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def long_mailer_id?
|
|
184
|
+
@mailer_id.long?
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Map the "characters" to symbols. Here is where the barcode is made.
|
|
188
|
+
#
|
|
189
|
+
# @return [Array<BarSymbol>]
|
|
146
190
|
def symbols
|
|
147
191
|
BAR_MAP.symbols(characters_with_fcs_bits_0_through_9)
|
|
148
192
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Imb
|
|
2
2
|
|
|
3
|
-
# This class represents a Barcode ID
|
|
4
|
-
|
|
3
|
+
# This class represents a Barcode ID, one of the fields that is used
|
|
4
|
+
# to generate a barcode.
|
|
5
5
|
class BarcodeId
|
|
6
6
|
|
|
7
7
|
# The allowable range of a barcode ID
|
|
@@ -14,6 +14,7 @@ module Imb
|
|
|
14
14
|
# * {BarcodeId}
|
|
15
15
|
# * String
|
|
16
16
|
# * Integer
|
|
17
|
+
#
|
|
17
18
|
# @return [BarcodeId]
|
|
18
19
|
# @raise [ArgumentError] If the argument cannot be coerced
|
|
19
20
|
|
|
@@ -31,15 +32,15 @@ module Imb
|
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
# Create a new BarcodeId
|
|
34
|
-
#
|
|
35
|
-
|
|
35
|
+
#
|
|
36
|
+
# @param value [Integer] The barcode ID
|
|
36
37
|
def initialize(value)
|
|
37
38
|
@value = value
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
# Return true if this object is equal to o
|
|
41
|
-
#
|
|
42
|
-
|
|
42
|
+
#
|
|
43
|
+
# @param o [Object] Any object acceptable to {.coerce}
|
|
43
44
|
def ==(o)
|
|
44
45
|
BarcodeId.coerce(o).to_i == to_i
|
|
45
46
|
rescue ArgumentError
|
|
@@ -47,17 +48,22 @@ module Imb
|
|
|
47
48
|
end
|
|
48
49
|
|
|
49
50
|
# @return [Integer] The integer value of the barcode ID
|
|
50
|
-
|
|
51
51
|
def to_i
|
|
52
52
|
@value
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# @return [String] The string value of the barcode ID
|
|
56
|
+
def to_s
|
|
57
|
+
"%02d" % @value
|
|
58
|
+
end
|
|
59
|
+
|
|
55
60
|
# @!group Internal
|
|
56
61
|
|
|
57
62
|
# Validate the value.
|
|
58
|
-
#
|
|
63
|
+
#
|
|
64
|
+
# @param long_mailer_id [boolean] truthy if the mailer ID is long
|
|
65
|
+
# (9 digits).
|
|
59
66
|
# @raise ArgumentError if invalid
|
|
60
|
-
|
|
61
67
|
def validate(long_mailer_id)
|
|
62
68
|
unless RANGE === @value
|
|
63
69
|
raise ArgumentError, "Must be #{RANGE}"
|
|
@@ -69,10 +75,11 @@ module Imb
|
|
|
69
75
|
|
|
70
76
|
# Add this object's value to target, shifting it left as many
|
|
71
77
|
# digts as are needed to make room.
|
|
72
|
-
#
|
|
73
|
-
# @param
|
|
78
|
+
#
|
|
79
|
+
# @param target [Integer] The target to be shifted and added to
|
|
80
|
+
# @param long_mailer_id [boolean] truthy if the mailer ID is long
|
|
81
|
+
# (9 digits).
|
|
74
82
|
# @return [Integer] The new value of the target
|
|
75
|
-
|
|
76
83
|
def shift_and_add_to(target, long_mailer_id)
|
|
77
84
|
target *= 10
|
|
78
85
|
target += most_significant_digit
|
|
@@ -2,23 +2,25 @@ module Imb
|
|
|
2
2
|
|
|
3
3
|
# @!group Internal
|
|
4
4
|
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
|
|
5
|
+
# For a given barcode position, maps to the "character" index and
|
|
6
|
+
# bit number used to drive either the ascender or descender of that
|
|
7
|
+
# position.
|
|
8
8
|
class CharacterPosition
|
|
9
9
|
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
|
|
10
|
+
# Construct an instance.
|
|
11
|
+
#
|
|
12
|
+
# @param character_index [Integer] The character's index within an
|
|
13
|
+
# array of characters.
|
|
14
|
+
# @param bit_number [Integer] The character's bit number
|
|
13
15
|
def initialize(character_index, bit_number)
|
|
14
16
|
@character_index = character_index
|
|
15
17
|
@bit_number = bit_number
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
# Given an array of characters, return the bit for this position.
|
|
19
|
-
#
|
|
21
|
+
#
|
|
22
|
+
# @param characters [Array<Integer>]
|
|
20
23
|
# @return [Integer] bit (0 or 1)
|
|
21
|
-
|
|
22
24
|
def extract_bit_from_characters(characters)
|
|
23
25
|
characters[@character_index][@bit_number]
|
|
24
26
|
end
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# Represents the table from the spec. that maps from codewords to
|
|
2
|
+
# "characters." A codeword is an integer between 0 and 1364, so this
|
|
3
|
+
# table is an array of 1365 entries.
|
|
4
|
+
#
|
|
5
|
+
# See spec. section 10.0 ("Appendix E -- Tables for Converting
|
|
6
|
+
# Characters"), Tables 19 ("5 of 13 Characters") and 20 ("2 of 13
|
|
7
|
+
# Characters")
|
|
1
8
|
---
|
|
2
9
|
- 31
|
|
3
10
|
- 7936
|
|
@@ -5,15 +5,15 @@ module Imb
|
|
|
5
5
|
# @!group Internal
|
|
6
6
|
|
|
7
7
|
# Calculates the Intelligent Mail Barcode CRC.
|
|
8
|
-
|
|
8
|
+
#
|
|
9
|
+
# See spec. section 9.1 ("CRC Generating Code")
|
|
9
10
|
class Crc
|
|
10
11
|
|
|
11
12
|
include NumericConversions
|
|
12
13
|
|
|
13
14
|
# Calculate a CRC.
|
|
14
|
-
# @param [Integer]
|
|
15
|
+
# @param binary_data [Integer] A 102-bit integer
|
|
15
16
|
# @return [Integer] An 11-bit CRC
|
|
16
|
-
|
|
17
17
|
def crc(binary_data)
|
|
18
18
|
crc = MASK
|
|
19
19
|
bytes = numeric_to_bytes(binary_data, NUM_INPUT_BYTES)
|