usps_intelligent_barcode 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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