• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2010 ZXing authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.zxing.oned;
18 
19 import com.google.zxing.BarcodeFormat;
20 import com.google.zxing.EncodeHintType;
21 import com.google.zxing.common.BitMatrix;
22 
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.Map;
27 
28 /**
29  * This object renders a CODE128 code as a {@link BitMatrix}.
30  *
31  * @author erik.barbara@gmail.com (Erik Barbara)
32  */
33 public final class Code128Writer extends OneDimensionalCodeWriter {
34 
35   private static final int CODE_START_A = 103;
36   private static final int CODE_START_B = 104;
37   private static final int CODE_START_C = 105;
38   private static final int CODE_CODE_A = 101;
39   private static final int CODE_CODE_B = 100;
40   private static final int CODE_CODE_C = 99;
41   private static final int CODE_STOP = 106;
42 
43   // Dummy characters used to specify control characters in input
44   private static final char ESCAPE_FNC_1 = '\u00f1';
45   private static final char ESCAPE_FNC_2 = '\u00f2';
46   private static final char ESCAPE_FNC_3 = '\u00f3';
47   private static final char ESCAPE_FNC_4 = '\u00f4';
48 
49   private static final int CODE_FNC_1 = 102;   // Code A, Code B, Code C
50   private static final int CODE_FNC_2 = 97;    // Code A, Code B
51   private static final int CODE_FNC_3 = 96;    // Code A, Code B
52   private static final int CODE_FNC_4_A = 101; // Code A
53   private static final int CODE_FNC_4_B = 100; // Code B
54 
55   // Results of minimal lookahead for code C
56   private enum CType {
57     UNCODABLE,
58     ONE_DIGIT,
59     TWO_DIGITS,
60     FNC_1
61   }
62 
63   @Override
getSupportedWriteFormats()64   protected Collection<BarcodeFormat> getSupportedWriteFormats() {
65     return Collections.singleton(BarcodeFormat.CODE_128);
66   }
67 
68   @Override
encode(String contents)69   public boolean[] encode(String contents) {
70     return encode(contents, null);
71   }
72 
73   @Override
encode(String contents, Map<EncodeHintType,?> hints)74   public boolean[] encode(String contents, Map<EncodeHintType,?> hints) {
75 
76     int forcedCodeSet = check(contents, hints);
77 
78     boolean hasCompactionHint = hints != null && hints.containsKey(EncodeHintType.CODE128_COMPACT) &&
79         Boolean.parseBoolean(hints.get(EncodeHintType.CODE128_COMPACT).toString());
80 
81     return hasCompactionHint ? new MinimalEncoder().encode(contents) : encodeFast(contents, forcedCodeSet);
82   }
83 
check(String contents, Map<EncodeHintType,?> hints)84   private static int check(String contents, Map<EncodeHintType,?> hints) {
85     int length = contents.length();
86     // Check length
87     if (length < 1 || length > 80) {
88       throw new IllegalArgumentException(
89           "Contents length should be between 1 and 80 characters, but got " + length);
90     }
91 
92     // Check for forced code set hint.
93     int forcedCodeSet = -1;
94     if (hints != null && hints.containsKey(EncodeHintType.FORCE_CODE_SET)) {
95       String codeSetHint = hints.get(EncodeHintType.FORCE_CODE_SET).toString();
96       switch (codeSetHint) {
97         case "A":
98           forcedCodeSet = CODE_CODE_A;
99           break;
100         case "B":
101           forcedCodeSet = CODE_CODE_B;
102           break;
103         case "C":
104           forcedCodeSet = CODE_CODE_C;
105           break;
106         default:
107           throw new IllegalArgumentException("Unsupported code set hint: " + codeSetHint);
108       }
109     }
110 
111     // Check content
112     for (int i = 0; i < length; i++) {
113       char c = contents.charAt(i);
114       // check for non ascii characters that are not special GS1 characters
115       switch (c) {
116         // special function characters
117         case ESCAPE_FNC_1:
118         case ESCAPE_FNC_2:
119         case ESCAPE_FNC_3:
120         case ESCAPE_FNC_4:
121           break;
122         // non ascii characters
123         default:
124           if (c > 127) {
125             // no full Latin-1 character set available at the moment
126             // shift and manual code change are not supported
127             throw new IllegalArgumentException("Bad character in input: ASCII value=" + (int) c);
128           }
129       }
130       // check characters for compatibility with forced code set
131       switch (forcedCodeSet) {
132         case CODE_CODE_A:
133           // allows no ascii above 95 (no lower caps, no special symbols)
134           if (c > 95 && c <= 127) {
135             throw new IllegalArgumentException("Bad character in input for forced code set A: ASCII value=" + (int) c);
136           }
137           break;
138         case CODE_CODE_B:
139           // allows no ascii below 32 (terminal symbols)
140           if (c < 32) {
141             throw new IllegalArgumentException("Bad character in input for forced code set B: ASCII value=" + (int) c);
142           }
143           break;
144         case CODE_CODE_C:
145           // allows only numbers and no FNC 2/3/4
146           if (c < 48 || (c > 57 && c <= 127) || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4) {
147             throw new IllegalArgumentException("Bad character in input for forced code set C: ASCII value=" + (int) c);
148           }
149           break;
150       }
151     }
152     return forcedCodeSet;
153   }
154 
encodeFast(String contents, int forcedCodeSet)155   private static boolean[] encodeFast(String contents, int forcedCodeSet) {
156     int length = contents.length();
157 
158     Collection<int[]> patterns = new ArrayList<>(); // temporary storage for patterns
159     int checkSum = 0;
160     int checkWeight = 1;
161     int codeSet = 0; // selected code (CODE_CODE_B or CODE_CODE_C)
162     int position = 0; // position in contents
163 
164     while (position < length) {
165       //Select code to use
166       int newCodeSet;
167       if (forcedCodeSet == -1) {
168         newCodeSet = chooseCode(contents, position, codeSet);
169       } else {
170         newCodeSet = forcedCodeSet;
171       }
172 
173       //Get the pattern index
174       int patternIndex;
175       if (newCodeSet == codeSet) {
176         // Encode the current character
177         // First handle escapes
178         switch (contents.charAt(position)) {
179           case ESCAPE_FNC_1:
180             patternIndex = CODE_FNC_1;
181             break;
182           case ESCAPE_FNC_2:
183             patternIndex = CODE_FNC_2;
184             break;
185           case ESCAPE_FNC_3:
186             patternIndex = CODE_FNC_3;
187             break;
188           case ESCAPE_FNC_4:
189             if (codeSet == CODE_CODE_A) {
190               patternIndex = CODE_FNC_4_A;
191             } else {
192               patternIndex = CODE_FNC_4_B;
193             }
194             break;
195           default:
196             // Then handle normal characters otherwise
197             switch (codeSet) {
198               case CODE_CODE_A:
199                 patternIndex = contents.charAt(position) - ' ';
200                 if (patternIndex < 0) {
201                   // everything below a space character comes behind the underscore in the code patterns table
202                   patternIndex += '`';
203                 }
204                 break;
205               case CODE_CODE_B:
206                 patternIndex = contents.charAt(position) - ' ';
207                 break;
208               default:
209                 // CODE_CODE_C
210                 if (position + 1 == length) {
211                   // this is the last character, but the encoding is C, which always encodes two characers
212                   throw new IllegalArgumentException("Bad number of characters for digit only encoding.");
213                 }
214                 patternIndex = Integer.parseInt(contents.substring(position, position + 2));
215                 position++; // Also incremented below
216                 break;
217             }
218         }
219         position++;
220       } else {
221         // Should we change the current code?
222         // Do we have a code set?
223         if (codeSet == 0) {
224           // No, we don't have a code set
225           switch (newCodeSet) {
226             case CODE_CODE_A:
227               patternIndex = CODE_START_A;
228               break;
229             case CODE_CODE_B:
230               patternIndex = CODE_START_B;
231               break;
232             default:
233               patternIndex = CODE_START_C;
234               break;
235           }
236         } else {
237           // Yes, we have a code set
238           patternIndex = newCodeSet;
239         }
240         codeSet = newCodeSet;
241       }
242 
243       // Get the pattern
244       patterns.add(Code128Reader.CODE_PATTERNS[patternIndex]);
245 
246       // Compute checksum
247       checkSum += patternIndex * checkWeight;
248       if (position != 0) {
249         checkWeight++;
250       }
251     }
252     return produceResult(patterns, checkSum);
253   }
254 
produceResult(Collection<int[]> patterns, int checkSum)255   static boolean[] produceResult(Collection<int[]> patterns, int checkSum) {
256     // Compute and append checksum
257     checkSum %= 103;
258     patterns.add(Code128Reader.CODE_PATTERNS[checkSum]);
259 
260     // Append stop code
261     patterns.add(Code128Reader.CODE_PATTERNS[CODE_STOP]);
262 
263     // Compute code width
264     int codeWidth = 0;
265     for (int[] pattern : patterns) {
266       for (int width : pattern) {
267         codeWidth += width;
268       }
269     }
270 
271     // Compute result
272     boolean[] result = new boolean[codeWidth];
273     int pos = 0;
274     for (int[] pattern : patterns) {
275       pos += appendPattern(result, pos, pattern, true);
276     }
277 
278     return result;
279   }
280 
findCType(CharSequence value, int start)281   private static CType findCType(CharSequence value, int start) {
282     int last = value.length();
283     if (start >= last) {
284       return CType.UNCODABLE;
285     }
286     char c = value.charAt(start);
287     if (c == ESCAPE_FNC_1) {
288       return CType.FNC_1;
289     }
290     if (c < '0' || c > '9') {
291       return CType.UNCODABLE;
292     }
293     if (start + 1 >= last) {
294       return CType.ONE_DIGIT;
295     }
296     c = value.charAt(start + 1);
297     if (c < '0' || c > '9') {
298       return CType.ONE_DIGIT;
299     }
300     return CType.TWO_DIGITS;
301   }
302 
chooseCode(CharSequence value, int start, int oldCode)303   private static int chooseCode(CharSequence value, int start, int oldCode) {
304     CType lookahead = findCType(value, start);
305     if (lookahead == CType.ONE_DIGIT) {
306       if (oldCode == CODE_CODE_A) {
307         return CODE_CODE_A;
308       }
309       return CODE_CODE_B;
310     }
311     if (lookahead == CType.UNCODABLE) {
312       if (start < value.length()) {
313         char c = value.charAt(start);
314         if (c < ' ' || (oldCode == CODE_CODE_A && (c < '`' || (c >= ESCAPE_FNC_1 && c <= ESCAPE_FNC_4)))) {
315           // can continue in code A, encodes ASCII 0 to 95 or FNC1 to FNC4
316           return CODE_CODE_A;
317         }
318       }
319       return CODE_CODE_B; // no choice
320     }
321     if (oldCode == CODE_CODE_A && lookahead == CType.FNC_1) {
322       return CODE_CODE_A;
323     }
324     if (oldCode == CODE_CODE_C) { // can continue in code C
325       return CODE_CODE_C;
326     }
327     if (oldCode == CODE_CODE_B) {
328       if (lookahead == CType.FNC_1) {
329         return CODE_CODE_B; // can continue in code B
330       }
331       // Seen two consecutive digits, see what follows
332       lookahead = findCType(value, start + 2);
333       if (lookahead == CType.UNCODABLE || lookahead == CType.ONE_DIGIT) {
334         return CODE_CODE_B; // not worth switching now
335       }
336       if (lookahead == CType.FNC_1) { // two digits, then FNC_1...
337         lookahead = findCType(value, start + 3);
338         if (lookahead == CType.TWO_DIGITS) { // then two more digits, switch
339           return CODE_CODE_C;
340         } else {
341           return CODE_CODE_B; // otherwise not worth switching
342         }
343       }
344       // At this point, there are at least 4 consecutive digits.
345       // Look ahead to choose whether to switch now or on the next round.
346       int index = start + 4;
347       while ((lookahead = findCType(value, index)) == CType.TWO_DIGITS) {
348         index += 2;
349       }
350       if (lookahead == CType.ONE_DIGIT) { // odd number of digits, switch later
351         return CODE_CODE_B;
352       }
353       return CODE_CODE_C; // even number of digits, switch now
354     }
355     // Here oldCode == 0, which means we are choosing the initial code
356     if (lookahead == CType.FNC_1) { // ignore FNC_1
357       lookahead = findCType(value, start + 1);
358     }
359     if (lookahead == CType.TWO_DIGITS) { // at least two digits, start in code C
360       return CODE_CODE_C;
361     }
362     return CODE_CODE_B;
363   }
364 
365   /**
366    * Encodes minimally using Divide-And-Conquer with Memoization
367    **/
368   private static final class MinimalEncoder {
369 
370     private enum Charset { A, B, C, NONE }
371     private enum Latch { A, B, C, SHIFT, NONE }
372 
373     static final String A = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\u0000\u0001\u0002" +
374                             "\u0003\u0004\u0005\u0006\u0007\u0008\u0009\n\u000B\u000C\r\u000E\u000F\u0010\u0011" +
375                             "\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F" +
376                             "\u00FF";
377     static final String B = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqr" +
378                             "stuvwxyz{|}~\u007F\u00FF";
379 
380     private static final int CODE_SHIFT = 98;
381 
382     private int[][] memoizedCost;
383     private Latch[][] minPath;
384 
encode(String contents)385     private boolean[] encode(String contents) {
386       memoizedCost = new int[4][contents.length()];
387       minPath = new Latch[4][contents.length()];
388 
389       encode(contents, Charset.NONE, 0);
390 
391       Collection<int[]> patterns = new ArrayList<>();
392       int[] checkSum = new int[] {0};
393       int[] checkWeight = new int[] {1};
394       int length = contents.length();
395       Charset charset = Charset.NONE;
396       for (int i = 0; i < length; i++) {
397         Latch latch = minPath[charset.ordinal()][i];
398         switch (latch) {
399           case A:
400             charset = Charset.A;
401             addPattern(patterns, i == 0 ? CODE_START_A : CODE_CODE_A, checkSum, checkWeight, i);
402             break;
403           case B:
404             charset = Charset.B;
405             addPattern(patterns, i == 0 ? CODE_START_B : CODE_CODE_B, checkSum, checkWeight, i);
406             break;
407           case C:
408             charset = Charset.C;
409             addPattern(patterns, i == 0 ? CODE_START_C : CODE_CODE_C, checkSum, checkWeight, i);
410             break;
411           case SHIFT:
412             addPattern(patterns, CODE_SHIFT, checkSum, checkWeight, i);
413             break;
414         }
415         if (charset == Charset.C) {
416           if (contents.charAt(i) == ESCAPE_FNC_1) {
417             addPattern(patterns, CODE_FNC_1, checkSum, checkWeight, i);
418           } else {
419             addPattern(patterns, Integer.parseInt(contents.substring(i, i + 2)), checkSum, checkWeight, i);
420             assert i + 1 < length; //the algorithm never leads to a single trailing digit in character set C
421             if (i + 1 < length) {
422               i++;
423             }
424           }
425         } else { // charset A or B
426           int patternIndex;
427           switch (contents.charAt(i)) {
428             case ESCAPE_FNC_1:
429               patternIndex = CODE_FNC_1;
430               break;
431             case ESCAPE_FNC_2:
432               patternIndex = CODE_FNC_2;
433               break;
434             case ESCAPE_FNC_3:
435               patternIndex = CODE_FNC_3;
436               break;
437             case ESCAPE_FNC_4:
438               if (charset == Charset.A && latch != Latch.SHIFT ||
439                   charset == Charset.B && latch == Latch.SHIFT) {
440                 patternIndex = CODE_FNC_4_A;
441               } else {
442                 patternIndex = CODE_FNC_4_B;
443               }
444               break;
445             default:
446               patternIndex = contents.charAt(i) - ' ';
447           }
448           if ((charset == Charset.A && latch != Latch.SHIFT ||
449                charset == Charset.B && latch == Latch.SHIFT) &&
450                patternIndex < 0) {
451             patternIndex += '`';
452           }
453           addPattern(patterns, patternIndex, checkSum, checkWeight, i);
454         }
455       }
456       memoizedCost = null;
457       minPath = null;
458       return produceResult(patterns, checkSum[0]);
459     }
460 
461     private static void addPattern(Collection<int[]> patterns,
462                                   int patternIndex,
463                                   int[] checkSum,
464                                   int[] checkWeight,
465                                   int position) {
466       patterns.add(Code128Reader.CODE_PATTERNS[patternIndex]);
467       if (position != 0) {
468         checkWeight[0]++;
469       }
470       checkSum[0] += patternIndex * checkWeight[0];
471     }
472 
473     private static boolean isDigit(char c) {
474       return c >= '0' && c <= '9';
475     }
476 
477     private boolean canEncode(CharSequence contents, Charset charset,int position) {
478       char c = contents.charAt(position);
479       switch (charset) {
480         case A: return c == ESCAPE_FNC_1 ||
481                        c == ESCAPE_FNC_2 ||
482                        c == ESCAPE_FNC_3 ||
483                        c == ESCAPE_FNC_4 ||
484                        A.indexOf(c) >= 0;
485         case B: return c == ESCAPE_FNC_1 ||
486                        c == ESCAPE_FNC_2 ||
487                        c == ESCAPE_FNC_3 ||
488                        c == ESCAPE_FNC_4 ||
489                        B.indexOf(c) >= 0;
490         case C: return c == ESCAPE_FNC_1 ||
491                        (position + 1 < contents.length() &&
492                         isDigit(c) &&
493                         isDigit(contents.charAt(position + 1)));
494         default: return false;
495       }
496     }
497 
498     /**
499      * Encode the string starting at position position starting with the character set charset
500      **/
501     private int encode(CharSequence contents, Charset charset, int position) {
502       assert position < contents.length();
503       int mCost = memoizedCost[charset.ordinal()][position];
504       if (mCost > 0) {
505         return mCost;
506       }
507 
508       int minCost = Integer.MAX_VALUE;
509       Latch minLatch = Latch.NONE;
510       boolean atEnd = position + 1 >= contents.length();
511 
512       Charset[] sets = new Charset[] { Charset.A, Charset.B };
513       for (int i = 0; i <= 1; i++) {
514         if (canEncode(contents, sets[i], position)) {
515           int cost =  1;
516           Latch latch = Latch.NONE;
517           if (charset != sets[i]) {
518             cost++;
519             latch = Latch.valueOf(sets[i].toString());
520           }
521           if (!atEnd) {
522             cost += encode(contents, sets[i], position + 1);
523           }
524           if (cost < minCost) {
525             minCost = cost;
526             minLatch = latch;
527           }
528           cost = 1;
529           if (charset == sets[(i + 1) % 2]) {
530             cost++;
531             latch = Latch.SHIFT;
532             if (!atEnd) {
533               cost += encode(contents, charset, position + 1);
534             }
535             if (cost < minCost) {
536               minCost = cost;
537               minLatch = latch;
538             }
539           }
540         }
541       }
542       if (canEncode(contents, Charset.C, position)) {
543         int cost = 1;
544         Latch latch = Latch.NONE;
545         if (charset != Charset.C) {
546           cost++;
547           latch = Latch.C;
548         }
549         int advance = contents.charAt(position) == ESCAPE_FNC_1 ? 1 : 2;
550         if (position + advance < contents.length()) {
551           cost += encode(contents, Charset.C, position + advance);
552         }
553         if (cost < minCost) {
554           minCost = cost;
555           minLatch = latch;
556         }
557       }
558       if (minCost == Integer.MAX_VALUE) {
559         throw new IllegalArgumentException("Bad character in input: ASCII value=" + (int) contents.charAt(position));
560       }
561       memoizedCost[charset.ordinal()][position] = minCost;
562       minPath[charset.ordinal()][position] = minLatch;
563       return minCost;
564     }
565   }
566 }
567