OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
181 ilm 1
/*
2
 * Copyright 2014 Robin Stuart
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5
 * in compliance with the License. You may obtain a copy of the License at
6
 *
7
 * http://www.apache.org/licenses/LICENSE-2.0
8
 *
9
 * Unless required by applicable law or agreed to in writing, software distributed under the License
10
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11
 * or implied. See the License for the specific language governing permissions and limitations under
12
 * the License.
13
 */
14
package uk.org.okapibarcode.backend;
15
 
16
import static uk.org.okapibarcode.backend.Ean.calcDigit;
17
import static uk.org.okapibarcode.backend.Ean.validateAndPad;
18
import static uk.org.okapibarcode.backend.HumanReadableLocation.BOTTOM;
19
import static uk.org.okapibarcode.backend.HumanReadableLocation.NONE;
20
import static uk.org.okapibarcode.backend.HumanReadableLocation.TOP;
21
 
22
import java.awt.geom.Rectangle2D;
23
import java.util.Arrays;
24
 
25
/**
26
 * <p>
27
 * Implements UPC bar code symbology according to BS EN 797:1996.
28
 *
29
 * <p>
30
 * UPC-A requires an 11 digit article number. The check digit is calculated. UPC-E is a
31
 * zero-compressed version of UPC-A developed for smaller packages. The code requires a 6 digit
32
 * article number (digits 0-9). The check digit is calculated. Also supports Number System 1
33
 * encoding by entering a 7-digit article number stating with the digit 1.
34
 *
35
 * <p>
36
 * EAN-2 and EAN-5 add-on symbols can be added using the '+' character followed by the add-on data.
37
 *
38
 * @author <a href="mailto:jakel2006@me.com">Robert Elliott</a>
39
 */
40
public class Upc extends Symbol {
41
 
42
    public static enum Mode {
43
        UPCA, UPCE
44
    };
45
 
46
    private static final String[] SET_AC = { "3211", "2221", "2122", "1411", "1132", "1231", "1114", "1312", "1213", "3112" };
47
 
48
    private static final String[] SET_B = { "1123", "1222", "2212", "1141", "2311", "1321", "4111", "2131", "3121", "2113" };
49
 
50
    /* Number set for UPC-E symbol (EN Table 4) */
51
    private static final String[] UPC_PARITY_0 = { "BBBAAA", "BBABAA", "BBAABA", "BBAAAB", "BABBAA", "BAABBA", "BAAABB", "BABABA", "BABAAB", "BAABAB" };
52
 
53
    /* Not covered by BS EN 797 */
54
    private static final String[] UPC_PARITY_1 = { "AAABBB", "AABABB", "AABBAB", "AABBBA", "ABAABB", "ABBAAB", "ABBBAA", "ABABAB", "ABABBA", "ABBABA" };
55
 
56
    private Mode mode = Mode.UPCA;
57
    private boolean showCheckDigit = true;
58
    private int guardPatternExtraHeight = 5;
59
    private boolean linkageFlag;
60
    private EanUpcAddOn addOn;
61
 
62
    /** Creates a new instance. */
63
    public Upc() {
64
        this.humanReadableAlignment = HumanReadableAlignment.JUSTIFY;
65
    }
66
 
67
    /**
68
     * Sets the UPC mode (UPC-A or UPC-E). The default is UPC-A.
69
     *
70
     * @param mode the UPC mode (UPC-A or UPC-E)
71
     */
72
    public void setMode(final Mode mode) {
73
        this.mode = mode;
74
    }
75
 
76
    /**
77
     * Returns the UPC mode (UPC-A or UPC-E).
78
     *
79
     * @return the UPC mode (UPC-A or UPC-E)
80
     */
81
    public Mode getMode() {
82
        return this.mode;
83
    }
84
 
85
    /**
86
     * Sets whether or not to show the check digit in the human-readable text.
87
     *
88
     * @param showCheckDigit whether or not to show the check digit in the human-readable text
89
     */
90
    public void setShowCheckDigit(final boolean showCheckDigit) {
91
        this.showCheckDigit = showCheckDigit;
92
    }
93
 
94
    /**
95
     * Returns whether or not to show the check digit in the human-readable text.
96
     *
97
     * @return whether or not to show the check digit in the human-readable text
98
     */
99
    public boolean getShowCheckDigit() {
100
        return this.showCheckDigit;
101
    }
102
 
103
    /**
104
     * Sets the extra height used for the guard patterns. The default value is <code>5</code>.
105
     *
106
     * @param guardPatternExtraHeight the extra height used for the guard patterns
107
     */
108
    public void setGuardPatternExtraHeight(final int guardPatternExtraHeight) {
109
        this.guardPatternExtraHeight = guardPatternExtraHeight;
110
    }
111
 
112
    /**
113
     * Returns the extra height used for the guard patterns.
114
     *
115
     * @return the extra height used for the guard patterns
116
     */
117
    public int getGuardPatternExtraHeight() {
118
        return this.guardPatternExtraHeight;
119
    }
120
 
121
    /**
122
     * Sets the linkage flag. If set to <code>true</code>, this symbol is part of a composite
123
     * symbol.
124
     *
125
     * @param linkageFlag the linkage flag
126
     */
127
    protected void setLinkageFlag(final boolean linkageFlag) {
128
        this.linkageFlag = linkageFlag;
129
    }
130
 
131
    @Override
132
    protected void encode() {
133
 
134
        separateContent();
135
 
136
        if (this.content.isEmpty()) {
137
            throw new OkapiException("Missing UPC data");
138
        }
139
 
140
        if (this.mode == Mode.UPCA) {
141
            upca();
142
        } else {
143
            upce();
144
        }
145
    }
146
 
147
    private void separateContent() {
148
        final int splitPoint = this.content.indexOf('+');
149
        if (splitPoint == -1) {
150
            // there is no add-on data
151
            this.addOn = null;
152
        } else if (splitPoint == this.content.length() - 1) {
153
            // we found the add-on separator, but no add-on data
154
            throw new OkapiException("Invalid add-on data");
155
        } else {
156
            // there is a '+' in the input data, use an add-on EAN2 or EAN5
157
            this.addOn = new EanUpcAddOn();
158
            this.addOn.font = this.font;
159
            this.addOn.fontName = this.fontName;
160
            this.addOn.fontSize = this.fontSize;
161
            this.addOn.humanReadableLocation = this.humanReadableLocation == NONE ? NONE : TOP;
162
            this.addOn.moduleWidth = this.moduleWidth;
163
            this.addOn.default_height = this.default_height + this.guardPatternExtraHeight - 8;
164
            this.addOn.setContent(this.content.substring(splitPoint + 1));
165
            this.content = this.content.substring(0, splitPoint);
166
        }
167
    }
168
 
169
    private void upca() {
170
 
171
        this.content = validateAndPad(this.content, 11);
172
 
173
        final char check = calcDigit(this.content);
174
        infoLine("Check Digit: " + check);
175
 
176
        final String hrt = this.content + check;
177
 
178
        final StringBuilder dest = new StringBuilder("111");
179
        for (int i = 0; i < 12; i++) {
180
            if (i == 6) {
181
                dest.append("11111");
182
            }
183
            dest.append(SET_AC[hrt.charAt(i) - '0']);
184
        }
185
        dest.append("111");
186
 
187
        this.readable = hrt;
188
        this.pattern = new String[] { dest.toString() };
189
        this.row_count = 1;
190
        this.row_height = new int[] { -1 };
191
    }
192
 
193
    private void upce() {
194
 
195
        this.content = validateAndPad(this.content, 7);
196
 
197
        final String expanded = expandToEquivalentUpcA(this.content, true);
198
        infoLine("UPC-A Equivalent: " + expanded);
199
 
200
        final char check = calcDigit(expanded);
201
        infoLine("Check Digit: " + check);
202
 
203
        final String hrt = this.content + check;
204
 
205
        final int numberSystem = getNumberSystem(this.content);
206
        final String[] parityArray = numberSystem == 1 ? UPC_PARITY_1 : UPC_PARITY_0;
207
        final String parity = parityArray[check - '0'];
208
 
209
        final StringBuilder dest = new StringBuilder("111");
210
        for (int i = 0; i < 6; i++) {
211
            if (parity.charAt(i) == 'A') {
212
                dest.append(SET_AC[this.content.charAt(i + 1) - '0']);
213
            } else { // B
214
                dest.append(SET_B[this.content.charAt(i + 1) - '0']);
215
            }
216
        }
217
        dest.append("111111");
218
 
219
        this.readable = hrt;
220
        this.pattern = new String[] { dest.toString() };
221
        this.row_count = 1;
222
        this.row_height = new int[] { -1 };
223
    }
224
 
225
    /**
226
     * Expands the zero-compressed UPC-E code to make a UPC-A equivalent (EN Table 5).
227
     *
228
     * @param content the UPC-E code to expand
229
     * @param validate whether or not to validate the input
230
     * @return the UPC-A equivalent of the specified UPC-E code
231
     */
232
    protected String expandToEquivalentUpcA(final String content, final boolean validate) {
233
 
234
        final char[] upce = content.toCharArray();
235
        final char[] upca = new char[11];
236
        Arrays.fill(upca, '0');
237
        upca[0] = upce[0];
238
        upca[1] = upce[1];
239
        upca[2] = upce[2];
240
 
241
        final char emode = upce[6];
242
 
243
        switch (emode) {
244
        case '0':
245
        case '1':
246
        case '2':
247
            upca[3] = emode;
248
            upca[8] = upce[3];
249
            upca[9] = upce[4];
250
            upca[10] = upce[5];
251
            break;
252
        case '3':
253
            upca[3] = upce[3];
254
            upca[9] = upce[4];
255
            upca[10] = upce[5];
256
            if (validate && (upce[3] == '0' || upce[3] == '1' || upce[3] == '2')) {
257
                /* Note 1 - "X3 shall not be equal to 0, 1 or 2" */
258
                throw new OkapiException("Invalid UPC-E data");
259
            }
260
            break;
261
        case '4':
262
            upca[3] = upce[3];
263
            upca[4] = upce[4];
264
            upca[10] = upce[5];
265
            if (validate && upce[4] == '0') {
266
                /* Note 2 - "X4 shall not be equal to 0" */
267
                throw new OkapiException("Invalid UPC-E data");
268
            }
269
            break;
270
        default:
271
            upca[3] = upce[3];
272
            upca[4] = upce[4];
273
            upca[5] = upce[5];
274
            upca[10] = emode;
275
            if (validate && upce[5] == '0') {
276
                /* Note 3 - "X5 shall not be equal to 0" */
277
                throw new OkapiException("Invalid UPC-E data");
278
            }
279
            break;
280
        }
281
 
282
        return new String(upca);
283
    }
284
 
285
    /** Two number systems can be used: system 0 and system 1. */
286
    private static int getNumberSystem(final String content) {
287
        switch (content.charAt(0)) {
288
        case '0':
289
            return 0;
290
        case '1':
291
            return 1;
292
        default:
293
            throw new OkapiException("Invalid input data");
294
        }
295
    }
296
 
297
    @Override
298
    protected void plotSymbol() {
299
 
300
        int xBlock;
301
        int x, y, w, h;
302
        boolean black = true;
303
        final int compositeOffset = this.linkageFlag ? 6 : 0; // space for composite separator above
304
        final int hrtOffset = this.humanReadableLocation == TOP ? getTheoreticalHumanReadableHeight() : 0; // space
305
                                                                                                           // for
306
                                                                                                           // HRT
307
                                                                                                           // above
308
 
309
        this.rectangles.clear();
310
        this.texts.clear();
311
        x = 0;
312
 
313
        /* Draw the bars in the symbology */
314
        for (xBlock = 0; xBlock < this.pattern[0].length(); xBlock++) {
315
 
316
            w = this.pattern[0].charAt(xBlock) - '0';
317
 
318
            if (black) {
319
                y = 0;
320
                h = this.default_height;
321
                /* Add extension to guide bars */
322
                if (this.mode == Mode.UPCA) {
323
                    if (x < 10 || x > 84 || x > 45 && x < 49) {
324
                        h += this.guardPatternExtraHeight;
325
                    }
326
                    if (this.linkageFlag && (x == 0 || x == 94)) {
327
                        h += 2;
328
                        y -= 2;
329
                    }
330
                } else {
331
                    if (x < 4 || x > 45) {
332
                        h += this.guardPatternExtraHeight;
333
                    }
334
                    if (this.linkageFlag && (x == 0 || x == 50)) {
335
                        h += 2;
336
                        y -= 2;
337
                    }
338
                }
339
                final Rectangle2D.Double rect = new Rectangle2D.Double(scale(x), y + compositeOffset + hrtOffset, scale(w), h);
340
                this.rectangles.add(rect);
341
                this.symbol_width = Math.max(this.symbol_width, (int) rect.getMaxX());
342
                this.symbol_height = Math.max(this.symbol_height, (int) rect.getHeight());
343
            }
344
 
345
            black = !black;
346
            x += w;
347
        }
348
 
349
        /* Add separator for composite symbology, if necessary */
350
        if (this.linkageFlag) {
351
            if (this.mode == Mode.UPCA) {
352
                this.rectangles.add(new Rectangle2D.Double(scale(0), 0, scale(1), 2));
353
                this.rectangles.add(new Rectangle2D.Double(scale(94), 0, scale(1), 2));
354
                this.rectangles.add(new Rectangle2D.Double(scale(-1), 2, scale(1), 2));
355
                this.rectangles.add(new Rectangle2D.Double(scale(95), 2, scale(1), 2));
356
            } else { // UPCE
357
                this.rectangles.add(new Rectangle2D.Double(scale(0), 0, scale(1), 2));
358
                this.rectangles.add(new Rectangle2D.Double(scale(50), 0, scale(1), 2));
359
                this.rectangles.add(new Rectangle2D.Double(scale(-1), 2, scale(1), 2));
360
                this.rectangles.add(new Rectangle2D.Double(scale(51), 2, scale(1), 2));
361
            }
362
            this.symbol_height += 4;
363
        }
364
 
365
        /* Now add the text */
366
        if (this.humanReadableLocation == BOTTOM) {
367
            this.symbol_height -= this.guardPatternExtraHeight;
368
            final double baseline = this.symbol_height + this.fontSize;
369
            if (this.mode == Mode.UPCA) {
370
                this.texts.add(new TextBox(scale(-9), baseline, scale(4), this.readable.substring(0, 1), HumanReadableAlignment.RIGHT));
371
                this.texts.add(new TextBox(scale(12), baseline, scale(32), this.readable.substring(1, 6), this.humanReadableAlignment));
372
                this.texts.add(new TextBox(scale(51), baseline, scale(32), this.readable.substring(6, 11), this.humanReadableAlignment));
373
                if (this.showCheckDigit) {
374
                    this.texts.add(new TextBox(scale(97), baseline, scale(4), this.readable.substring(11, 12), HumanReadableAlignment.LEFT));
375
                }
376
            } else { // UPCE
377
                this.texts.add(new TextBox(scale(-9), baseline, scale(4), this.readable.substring(0, 1), HumanReadableAlignment.RIGHT));
378
                this.texts.add(new TextBox(scale(5), baseline, scale(39), this.readable.substring(1, 7), this.humanReadableAlignment));
379
                if (this.showCheckDigit) {
380
                    this.texts.add(new TextBox(scale(53), baseline, scale(4), this.readable.substring(7, 8), HumanReadableAlignment.LEFT));
381
                }
382
            }
383
        } else if (this.humanReadableLocation == TOP) {
384
            final double baseline = this.fontSize;
385
            final int width = this.mode == Mode.UPCA ? 94 : 50;
386
            this.texts.add(new TextBox(scale(0), baseline, scale(width), this.readable, this.humanReadableAlignment));
387
        }
388
 
389
        /* Now add the add-on symbol, if necessary */
390
        if (this.addOn != null) {
391
            final int gap = 9;
392
            final int baseX = this.symbol_width + scale(gap);
393
            final Rectangle2D.Double r1 = this.rectangles.get(0);
394
            final Rectangle2D.Double ar1 = this.addOn.rectangles.get(0);
395
            final int baseY = (int) (r1.y + r1.getHeight() - ar1.y - ar1.getHeight());
396
            for (final TextBox t : this.addOn.getTexts()) {
397
                this.texts.add(new TextBox(baseX + t.x, baseY + t.y, t.width, t.text, t.alignment));
398
            }
399
            for (final Rectangle2D.Double r : this.addOn.getRectangles()) {
400
                this.rectangles.add(new Rectangle2D.Double(baseX + r.x, baseY + r.y, r.width, r.height));
401
            }
402
            this.symbol_width += scale(gap) + this.addOn.symbol_width;
403
            this.pattern[0] = this.pattern[0] + gap + this.addOn.pattern[0];
404
        }
405
    }
406
 
407
    /** Scales the specified width or x-dimension according to the current module width. */
408
    private int scale(final int w) {
409
        return this.moduleWidth * w;
410
    }
411
}