usps_intelligent_barcode 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +8 -0
  3. data/CHANGELOG.markdown +67 -0
  4. data/Gemfile +13 -0
  5. data/Gemfile.lock +90 -0
  6. data/LICENSE.md +11 -0
  7. data/README.markdown +64 -0
  8. data/Rakefile +40 -0
  9. data/VERSION +1 -0
  10. data/examples/example.rb +17 -0
  11. data/lib/USPS-intelligent-barcode.rb +17 -0
  12. data/lib/USPS-intelligent-barcode/bar_map.rb +52 -0
  13. data/lib/USPS-intelligent-barcode/bar_position.rb +40 -0
  14. data/lib/USPS-intelligent-barcode/bar_symbol.rb +49 -0
  15. data/lib/USPS-intelligent-barcode/bar_to_character_mapping.yml +66 -0
  16. data/lib/USPS-intelligent-barcode/barcode.rb +152 -0
  17. data/lib/USPS-intelligent-barcode/barcode_id.rb +98 -0
  18. data/lib/USPS-intelligent-barcode/character_position.rb +28 -0
  19. data/lib/USPS-intelligent-barcode/codeword_map.rb +38 -0
  20. data/lib/USPS-intelligent-barcode/codeword_to_character_mapping.yml +1366 -0
  21. data/lib/USPS-intelligent-barcode/crc.rb +52 -0
  22. data/lib/USPS-intelligent-barcode/mailer_id.rb +105 -0
  23. data/lib/USPS-intelligent-barcode/numeric_conversions.rb +22 -0
  24. data/lib/USPS-intelligent-barcode/routing_code.rb +134 -0
  25. data/lib/USPS-intelligent-barcode/serial_number.rb +88 -0
  26. data/lib/USPS-intelligent-barcode/service_type.rb +79 -0
  27. data/lib/usps_intelligent_barcode.rb +17 -0
  28. data/lib/usps_intelligent_barcode/bar_map.rb +52 -0
  29. data/lib/usps_intelligent_barcode/bar_position.rb +40 -0
  30. data/lib/usps_intelligent_barcode/bar_symbol.rb +49 -0
  31. data/lib/usps_intelligent_barcode/bar_to_character_mapping.yml +66 -0
  32. data/lib/usps_intelligent_barcode/barcode.rb +152 -0
  33. data/lib/usps_intelligent_barcode/barcode_id.rb +98 -0
  34. data/lib/usps_intelligent_barcode/character_position.rb +28 -0
  35. data/lib/usps_intelligent_barcode/codeword_map.rb +38 -0
  36. data/lib/usps_intelligent_barcode/codeword_to_character_mapping.yml +1366 -0
  37. data/lib/usps_intelligent_barcode/crc.rb +52 -0
  38. data/lib/usps_intelligent_barcode/mailer_id.rb +105 -0
  39. data/lib/usps_intelligent_barcode/numeric_conversions.rb +22 -0
  40. data/lib/usps_intelligent_barcode/routing_code.rb +134 -0
  41. data/lib/usps_intelligent_barcode/serial_number.rb +88 -0
  42. data/lib/usps_intelligent_barcode/service_type.rb +79 -0
  43. data/spec/bar_map_spec.rb +30 -0
  44. data/spec/bar_position_spec.rb +40 -0
  45. data/spec/bar_symbol_spec.rb +39 -0
  46. data/spec/barcode_id_spec.rb +106 -0
  47. data/spec/barcode_spec.rb +213 -0
  48. data/spec/character_position_spec.rb +25 -0
  49. data/spec/codeword_map_spec.rb +22 -0
  50. data/spec/crc_spec.rb +21 -0
  51. data/spec/mailer_id_spec.rb +124 -0
  52. data/spec/numeric_conversions_spec.rb +23 -0
  53. data/spec/routing_code_spec.rb +180 -0
  54. data/spec/serial_number_spec.rb +117 -0
  55. data/spec/service_type_spec.rb +93 -0
  56. data/spec/spec_helper.rb +8 -0
  57. data/usps_intelligent_barcode.gemspec +117 -0
  58. metadata +216 -0
@@ -0,0 +1,17 @@
1
+ require 'andand'
2
+ require 'memoizer'
3
+ require 'yaml'
4
+
5
+ require 'usps_intelligent_barcode/bar_map'
6
+ require 'usps_intelligent_barcode/bar_position'
7
+ require 'usps_intelligent_barcode/bar_symbol'
8
+ require 'usps_intelligent_barcode/barcode'
9
+ require 'usps_intelligent_barcode/barcode_id'
10
+ require 'usps_intelligent_barcode/character_position'
11
+ require 'usps_intelligent_barcode/codeword_map'
12
+ require 'usps_intelligent_barcode/crc'
13
+ require 'usps_intelligent_barcode/mailer_id'
14
+ require 'usps_intelligent_barcode/numeric_conversions'
15
+ require 'usps_intelligent_barcode/routing_code'
16
+ require 'usps_intelligent_barcode/serial_number'
17
+ require 'usps_intelligent_barcode/service_type'
@@ -0,0 +1,52 @@
1
+ require 'usps_intelligent_barcode/character_position'
2
+
3
+ # @!group Internal
4
+
5
+ module Imb
6
+
7
+ # Maps intelligent barcode "characters" to codes that indicate what
8
+ # type of bar to print at each given position.
9
+
10
+ class BarMap
11
+
12
+ def initialize
13
+ @mapping = load_mapping
14
+ end
15
+
16
+ # Given an array of intelligent barcode "characters", return an
17
+ # the symbols for each position.
18
+ # @param [[Integer]] characters array of characters
19
+ # @return [[BarSymbol]] array of symbols
20
+
21
+ def symbols(characters)
22
+ @mapping.map do |bar_position|
23
+ bar_position.map(characters)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def load_mapping
30
+ convert_mapping_data(load_mapping_data)
31
+ end
32
+
33
+ def convert_mapping_data(mapping_data)
34
+ mapping_data.map do |descender, ascender|
35
+ descender_character_position = CharacterPosition.new(*descender)
36
+ ascender_character_position = CharacterPosition.new(*ascender)
37
+ BarPosition.new(descender_character_position,
38
+ ascender_character_position)
39
+ end
40
+ end
41
+
42
+ def load_mapping_data
43
+ YAML.load_file(mapping_path)
44
+ end
45
+
46
+ def mapping_path
47
+ File.expand_path('bar_to_character_mapping.yml', File.dirname(__FILE__))
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,40 @@
1
+ module Imb
2
+
3
+ # @!group Internal
4
+
5
+ # Represents a position (one line) in the barcode. This class is
6
+ # internal and may change.
7
+
8
+ class BarPosition
9
+
10
+ # @param [CharacterPosition] descender_character_position
11
+ # @param [CharacterPosition] ascender_character_position
12
+
13
+ def initialize(descender_character_position, ascender_character_position)
14
+ @descender_character_position = descender_character_position
15
+ @ascender_character_position = ascender_character_position
16
+ end
17
+
18
+ # Given an array of characters, return a symbol for this
19
+ # barcode position.
20
+ # @param [[Integer]] characters character codes
21
+ # @return [BarSymbol] symbol code
22
+
23
+ def map(characters)
24
+ BarSymbol.make(ascender_bit(characters),
25
+ descender_bit(characters))
26
+ end
27
+
28
+ private
29
+
30
+ def descender_bit(characters)
31
+ @descender_character_position.extract_bit_from_characters(characters)
32
+ end
33
+
34
+ def ascender_bit(characters)
35
+ @ascender_character_position.extract_bit_from_characters(characters)
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,49 @@
1
+ module Imb
2
+
3
+ # @!group Internal
4
+
5
+ # Represents a symbol in the barcode.
6
+
7
+ class BarSymbol
8
+
9
+ # @param [Integer] ascender_bit (0..1)
10
+ # @param [Integer] descender_bit (0..1)
11
+ # @return [BarSymbol]
12
+
13
+ def self.make(ascender_bit, descender_bit)
14
+ case [ascender_bit, descender_bit]
15
+ when [0, 0]
16
+ TRACKER
17
+ when [0, 1]
18
+ DESCENDER
19
+ when [1, 0]
20
+ ASCENDER
21
+ when [1, 1]
22
+ FULL
23
+ end
24
+ end
25
+
26
+ # @return [Integer] the code for this symbol
27
+ attr_reader :code
28
+
29
+ # @return [String] the letter for this symbol
30
+ attr_reader :letter
31
+
32
+ # @param [Integer] code (0..3)
33
+ # @param [String] letter
34
+
35
+ def initialize(code, letter)
36
+ @code = code
37
+ @letter = letter
38
+ end
39
+
40
+ private
41
+
42
+ TRACKER = BarSymbol.new(0, 'T')
43
+ DESCENDER = BarSymbol.new(1, 'D')
44
+ ASCENDER = BarSymbol.new(2, 'A')
45
+ FULL = BarSymbol.new(3, 'F')
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,66 @@
1
+ ---
2
+ - [[7, 2], [4, 3]]
3
+ - [[1, 10], [0, 0]]
4
+ - [[9, 12], [2, 8]]
5
+ - [[5, 5], [6, 11]]
6
+ - [[8, 9], [3, 1]]
7
+ - [[0, 1], [5, 12]]
8
+ - [[2, 5], [1, 8]]
9
+ - [[4, 4], [9, 11]]
10
+ - [[6, 3], [8, 10]]
11
+ - [[3, 9], [7, 6]]
12
+ - [[5, 11], [1, 4]]
13
+ - [[8, 5], [2, 12]]
14
+ - [[9, 10], [0, 2]]
15
+ - [[7, 1], [6, 7]]
16
+ - [[3, 6], [4, 9]]
17
+ - [[0, 3], [8, 6]]
18
+ - [[6, 4], [2, 7]]
19
+ - [[1, 1], [9, 9]]
20
+ - [[7, 10], [5, 2]]
21
+ - [[4, 0], [3, 8]]
22
+ - [[6, 2], [0, 4]]
23
+ - [[8, 11], [1, 0]]
24
+ - [[9, 8], [3, 12]]
25
+ - [[2, 6], [7, 7]]
26
+ - [[5, 1], [4, 10]]
27
+ - [[1, 12], [6, 9]]
28
+ - [[7, 3], [8, 0]]
29
+ - [[5, 8], [9, 7]]
30
+ - [[4, 6], [2, 10]]
31
+ - [[3, 4], [0, 5]]
32
+ - [[8, 4], [5, 7]]
33
+ - [[7, 11], [1, 9]]
34
+ - [[6, 0], [9, 6]]
35
+ - [[0, 6], [4, 8]]
36
+ - [[2, 1], [3, 2]]
37
+ - [[5, 9], [8, 12]]
38
+ - [[4, 11], [6, 1]]
39
+ - [[9, 5], [7, 4]]
40
+ - [[3, 3], [1, 2]]
41
+ - [[0, 7], [2, 0]]
42
+ - [[1, 3], [4, 1]]
43
+ - [[6, 10], [3, 5]]
44
+ - [[8, 7], [9, 4]]
45
+ - [[2, 11], [5, 6]]
46
+ - [[0, 8], [7, 12]]
47
+ - [[4, 2], [8, 1]]
48
+ - [[5, 10], [3, 0]]
49
+ - [[9, 3], [0, 9]]
50
+ - [[6, 5], [2, 4]]
51
+ - [[7, 8], [1, 7]]
52
+ - [[5, 0], [4, 5]]
53
+ - [[2, 3], [0, 10]]
54
+ - [[6, 12], [9, 2]]
55
+ - [[3, 11], [1, 6]]
56
+ - [[8, 8], [7, 9]]
57
+ - [[5, 4], [0, 11]]
58
+ - [[1, 5], [2, 2]]
59
+ - [[9, 1], [4, 12]]
60
+ - [[8, 3], [6, 6]]
61
+ - [[7, 0], [3, 7]]
62
+ - [[4, 7], [7, 5]]
63
+ - [[0, 12], [1, 11]]
64
+ - [[2, 9], [9, 0]]
65
+ - [[6, 8], [5, 3]]
66
+ - [[3, 10], [8, 2]]
@@ -0,0 +1,152 @@
1
+ require 'usps_intelligent_barcode/codeword_map'
2
+ require 'usps_intelligent_barcode/crc'
3
+
4
+ # The namespace for everything in this library.
5
+
6
+ module Imb
7
+
8
+ # This class represents a barcode.
9
+
10
+ class Barcode
11
+
12
+ include Memoizer
13
+
14
+ # @return [BarcodeId]
15
+ attr_reader :barcode_id
16
+
17
+ # @return [ServiceType]
18
+ attr_reader :service_type
19
+
20
+ # @return [MailerId]
21
+ attr_reader :mailer_id
22
+
23
+ # @return [SerialNumber]
24
+ attr_reader :serial_number
25
+
26
+ # @return [RoutingCode]
27
+ attr_reader :routing_code
28
+
29
+ # @param
30
+
31
+ # Create a new barcode
32
+ #
33
+ # @param barcode_id [String] Nominally a String, but can be
34
+ # anything that {BarcodeId.coerce} will accept.
35
+ # @param service_type [String] Nominally a String, but can be
36
+ # anything that {ServiceType.coerce} will accept.
37
+ # @param mailer_id [String] Nominally a String, but can be
38
+ # anything that {MailerId.coerce} will accept.
39
+ # @param serial_number [String] Nominally a String, but can be
40
+ # anything that {SerialNumber.coerce} will accept.
41
+ # @param routing_code [String] Nominally a String, but can be
42
+ # anything that {RoutingCode.coerce} will accept.
43
+
44
+ def initialize(barcode_id,
45
+ service_type,
46
+ mailer_id,
47
+ serial_number,
48
+ routing_code)
49
+ @barcode_id = BarcodeId.coerce(barcode_id)
50
+ @service_type = ServiceType.coerce(service_type)
51
+ @mailer_id = MailerId.coerce(mailer_id)
52
+ @serial_number = SerialNumber.coerce(serial_number)
53
+ @routing_code = RoutingCode.coerce(routing_code)
54
+ validate_components
55
+ end
56
+
57
+ # Return a string to print using one of the USPS Intelligent Mail
58
+ # Barcode fonts. Each character of the string will be one of:
59
+ # * 'T' for a tracking mark (neither ascender nor descender)
60
+ # * 'A' for an ascender mark
61
+ # * 'D' for a descender mark
62
+ # * 'F' for a full mark (both ascender and descender)
63
+ # @return [String] A string that represents the barcode.
64
+
65
+ def barcode_letters
66
+ symbols.map(&:letter).join
67
+ end
68
+
69
+ private
70
+
71
+ # :stopdoc:
72
+ BAR_MAP = BarMap.new
73
+ CODEWORD_MAP = CodewordMap.new
74
+ CRC = Crc.new
75
+ # :startdoc:
76
+
77
+ def validate_components
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
96
+
97
+ def binary_data
98
+ components.inject(0) do |data, component|
99
+ component.shift_and_add_to(data, long_mailer_id?)
100
+ end
101
+ end
102
+ memoize :binary_data
103
+
104
+ def frame_check_sequence
105
+ CRC.crc(binary_data)
106
+ end
107
+ memoize :frame_check_sequence
108
+
109
+ def codewords
110
+ codewords = []
111
+ data = binary_data
112
+ data, codewords[9] = data.divmod 636
113
+ 8.downto(0) do |i|
114
+ data, codewords[i] = data.divmod 1365
115
+ end
116
+ codewords
117
+ end
118
+ memoize :codewords
119
+
120
+ def codewords_with_orientation_in_character_j
121
+ result = codewords.dup
122
+ result[9] *= 2
123
+ result
124
+ end
125
+
126
+ def codewords_with_fcs_bit_in_character_a
127
+ result = codewords_with_orientation_in_character_j.dup
128
+ result[0] += 659 if frame_check_sequence[10] == 1
129
+ result
130
+ end
131
+
132
+ def characters
133
+ CODEWORD_MAP.characters(codewords_with_fcs_bit_in_character_a)
134
+ end
135
+
136
+ def characters_with_fcs_bits_0_through_9
137
+ characters.each_with_index.map do |character, i|
138
+ if frame_check_sequence[i] == 1
139
+ character ^ 0b1111111111111
140
+ else
141
+ character
142
+ end
143
+ end
144
+ end
145
+
146
+ def symbols
147
+ BAR_MAP.symbols(characters_with_fcs_bits_0_through_9)
148
+ end
149
+
150
+ end
151
+
152
+ end
@@ -0,0 +1,98 @@
1
+ module Imb
2
+
3
+ # This class represents a Barcode ID
4
+
5
+ class BarcodeId
6
+
7
+ # The allowable range of a barcode ID
8
+ RANGE = 0..94
9
+
10
+ # The allowable range of a barcode ID's least significant digit
11
+ LSD_RANGE = 0..4
12
+
13
+ # Turn the argument into a BarcodeID if possible. Accepts any of:
14
+ # * {BarcodeId}
15
+ # * String
16
+ # * Integer
17
+ # @return [BarcodeId]
18
+ # @raise [ArgumentError] If the argument cannot be coerced
19
+
20
+ def self.coerce(o)
21
+ case o
22
+ when BarcodeId
23
+ o
24
+ when String
25
+ new(o.to_i)
26
+ when Integer
27
+ new(o)
28
+ else
29
+ raise ArgumentError, 'Cannot coerce to BarcodeId'
30
+ end
31
+ end
32
+
33
+ # Create a new BarcodeId
34
+ # @param [Integer] value The barcode ID
35
+
36
+ def initialize(value)
37
+ @value = value
38
+ end
39
+
40
+ # Return true if this object is equal to o
41
+ # @param [Object] o Any object acceptable to {.coerce}
42
+
43
+ def ==(o)
44
+ BarcodeId.coerce(o).to_i == to_i
45
+ rescue ArgumentError
46
+ false
47
+ end
48
+
49
+ # @return [Integer] The integer value of the barcode ID
50
+
51
+ def to_i
52
+ @value
53
+ end
54
+
55
+ # @!group Internal
56
+
57
+ # Validate the value.
58
+ # @param long_mailer_id truthy if the mailer ID is long (9 digits).
59
+ # @raise ArgumentError if invalid
60
+
61
+ def validate(long_mailer_id)
62
+ unless RANGE === @value
63
+ raise ArgumentError, "Must be #{RANGE}"
64
+ end
65
+ unless LSD_RANGE === least_significant_digit
66
+ raise ArgumentError, "Least significant digit must be #{LSD_RANGE}"
67
+ end
68
+ end
69
+
70
+ # Add this object's value to target, shifting it left as many
71
+ # digts as are needed to make room.
72
+ # @param [Integer] target The target to be shifted and added to
73
+ # @param long_mailer_id truthy if the mailer ID is long (9 digits).
74
+ # @return [Integer] The new value of the target
75
+
76
+ def shift_and_add_to(target, long_mailer_id)
77
+ target *= 10
78
+ target += most_significant_digit
79
+ target *= 5
80
+ target += least_significant_digit
81
+ target
82
+ end
83
+
84
+ # @!endgroup
85
+
86
+ private
87
+
88
+ def most_significant_digit
89
+ @value / 10
90
+ end
91
+
92
+ def least_significant_digit
93
+ @value % 10
94
+ end
95
+
96
+ end
97
+
98
+ end