OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Blame | Last modification | View Log | RSS feed

/*
 * Copyright 2014 Robin Stuart
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package uk.org.okapibarcode.backend;

import static uk.org.okapibarcode.backend.Ean.calcDigit;
import static uk.org.okapibarcode.backend.Ean.validateAndPad;
import static uk.org.okapibarcode.backend.HumanReadableLocation.BOTTOM;
import static uk.org.okapibarcode.backend.HumanReadableLocation.NONE;
import static uk.org.okapibarcode.backend.HumanReadableLocation.TOP;

import java.awt.geom.Rectangle2D;
import java.util.Arrays;

/**
 * <p>
 * Implements UPC bar code symbology according to BS EN 797:1996.
 *
 * <p>
 * UPC-A requires an 11 digit article number. The check digit is calculated. UPC-E is a
 * zero-compressed version of UPC-A developed for smaller packages. The code requires a 6 digit
 * article number (digits 0-9). The check digit is calculated. Also supports Number System 1
 * encoding by entering a 7-digit article number stating with the digit 1.
 *
 * <p>
 * EAN-2 and EAN-5 add-on symbols can be added using the '+' character followed by the add-on data.
 *
 * @author <a href="mailto:jakel2006@me.com">Robert Elliott</a>
 */
public class Upc extends Symbol {

    public static enum Mode {
        UPCA, UPCE
    };

    private static final String[] SET_AC = { "3211", "2221", "2122", "1411", "1132", "1231", "1114", "1312", "1213", "3112" };

    private static final String[] SET_B = { "1123", "1222", "2212", "1141", "2311", "1321", "4111", "2131", "3121", "2113" };

    /* Number set for UPC-E symbol (EN Table 4) */
    private static final String[] UPC_PARITY_0 = { "BBBAAA", "BBABAA", "BBAABA", "BBAAAB", "BABBAA", "BAABBA", "BAAABB", "BABABA", "BABAAB", "BAABAB" };

    /* Not covered by BS EN 797 */
    private static final String[] UPC_PARITY_1 = { "AAABBB", "AABABB", "AABBAB", "AABBBA", "ABAABB", "ABBAAB", "ABBBAA", "ABABAB", "ABABBA", "ABBABA" };

    private Mode mode = Mode.UPCA;
    private boolean showCheckDigit = true;
    private int guardPatternExtraHeight = 5;
    private boolean linkageFlag;
    private EanUpcAddOn addOn;

    /** Creates a new instance. */
    public Upc() {
        this.humanReadableAlignment = HumanReadableAlignment.JUSTIFY;
    }

    /**
     * Sets the UPC mode (UPC-A or UPC-E). The default is UPC-A.
     *
     * @param mode the UPC mode (UPC-A or UPC-E)
     */
    public void setMode(final Mode mode) {
        this.mode = mode;
    }

    /**
     * Returns the UPC mode (UPC-A or UPC-E).
     *
     * @return the UPC mode (UPC-A or UPC-E)
     */
    public Mode getMode() {
        return this.mode;
    }

    /**
     * Sets whether or not to show the check digit in the human-readable text.
     *
     * @param showCheckDigit whether or not to show the check digit in the human-readable text
     */
    public void setShowCheckDigit(final boolean showCheckDigit) {
        this.showCheckDigit = showCheckDigit;
    }

    /**
     * Returns whether or not to show the check digit in the human-readable text.
     *
     * @return whether or not to show the check digit in the human-readable text
     */
    public boolean getShowCheckDigit() {
        return this.showCheckDigit;
    }

    /**
     * Sets the extra height used for the guard patterns. The default value is <code>5</code>.
     *
     * @param guardPatternExtraHeight the extra height used for the guard patterns
     */
    public void setGuardPatternExtraHeight(final int guardPatternExtraHeight) {
        this.guardPatternExtraHeight = guardPatternExtraHeight;
    }

    /**
     * Returns the extra height used for the guard patterns.
     *
     * @return the extra height used for the guard patterns
     */
    public int getGuardPatternExtraHeight() {
        return this.guardPatternExtraHeight;
    }

    /**
     * Sets the linkage flag. If set to <code>true</code>, this symbol is part of a composite
     * symbol.
     *
     * @param linkageFlag the linkage flag
     */
    protected void setLinkageFlag(final boolean linkageFlag) {
        this.linkageFlag = linkageFlag;
    }

    @Override
    protected void encode() {

        separateContent();

        if (this.content.isEmpty()) {
            throw new OkapiException("Missing UPC data");
        }

        if (this.mode == Mode.UPCA) {
            upca();
        } else {
            upce();
        }
    }

    private void separateContent() {
        final int splitPoint = this.content.indexOf('+');
        if (splitPoint == -1) {
            // there is no add-on data
            this.addOn = null;
        } else if (splitPoint == this.content.length() - 1) {
            // we found the add-on separator, but no add-on data
            throw new OkapiException("Invalid add-on data");
        } else {
            // there is a '+' in the input data, use an add-on EAN2 or EAN5
            this.addOn = new EanUpcAddOn();
            this.addOn.font = this.font;
            this.addOn.fontName = this.fontName;
            this.addOn.fontSize = this.fontSize;
            this.addOn.humanReadableLocation = this.humanReadableLocation == NONE ? NONE : TOP;
            this.addOn.moduleWidth = this.moduleWidth;
            this.addOn.default_height = this.default_height + this.guardPatternExtraHeight - 8;
            this.addOn.setContent(this.content.substring(splitPoint + 1));
            this.content = this.content.substring(0, splitPoint);
        }
    }

    private void upca() {

        this.content = validateAndPad(this.content, 11);

        final char check = calcDigit(this.content);
        infoLine("Check Digit: " + check);

        final String hrt = this.content + check;

        final StringBuilder dest = new StringBuilder("111");
        for (int i = 0; i < 12; i++) {
            if (i == 6) {
                dest.append("11111");
            }
            dest.append(SET_AC[hrt.charAt(i) - '0']);
        }
        dest.append("111");

        this.readable = hrt;
        this.pattern = new String[] { dest.toString() };
        this.row_count = 1;
        this.row_height = new int[] { -1 };
    }

    private void upce() {

        this.content = validateAndPad(this.content, 7);

        final String expanded = expandToEquivalentUpcA(this.content, true);
        infoLine("UPC-A Equivalent: " + expanded);

        final char check = calcDigit(expanded);
        infoLine("Check Digit: " + check);

        final String hrt = this.content + check;

        final int numberSystem = getNumberSystem(this.content);
        final String[] parityArray = numberSystem == 1 ? UPC_PARITY_1 : UPC_PARITY_0;
        final String parity = parityArray[check - '0'];

        final StringBuilder dest = new StringBuilder("111");
        for (int i = 0; i < 6; i++) {
            if (parity.charAt(i) == 'A') {
                dest.append(SET_AC[this.content.charAt(i + 1) - '0']);
            } else { // B
                dest.append(SET_B[this.content.charAt(i + 1) - '0']);
            }
        }
        dest.append("111111");

        this.readable = hrt;
        this.pattern = new String[] { dest.toString() };
        this.row_count = 1;
        this.row_height = new int[] { -1 };
    }

    /**
     * Expands the zero-compressed UPC-E code to make a UPC-A equivalent (EN Table 5).
     *
     * @param content the UPC-E code to expand
     * @param validate whether or not to validate the input
     * @return the UPC-A equivalent of the specified UPC-E code
     */
    protected String expandToEquivalentUpcA(final String content, final boolean validate) {

        final char[] upce = content.toCharArray();
        final char[] upca = new char[11];
        Arrays.fill(upca, '0');
        upca[0] = upce[0];
        upca[1] = upce[1];
        upca[2] = upce[2];

        final char emode = upce[6];

        switch (emode) {
        case '0':
        case '1':
        case '2':
            upca[3] = emode;
            upca[8] = upce[3];
            upca[9] = upce[4];
            upca[10] = upce[5];
            break;
        case '3':
            upca[3] = upce[3];
            upca[9] = upce[4];
            upca[10] = upce[5];
            if (validate && (upce[3] == '0' || upce[3] == '1' || upce[3] == '2')) {
                /* Note 1 - "X3 shall not be equal to 0, 1 or 2" */
                throw new OkapiException("Invalid UPC-E data");
            }
            break;
        case '4':
            upca[3] = upce[3];
            upca[4] = upce[4];
            upca[10] = upce[5];
            if (validate && upce[4] == '0') {
                /* Note 2 - "X4 shall not be equal to 0" */
                throw new OkapiException("Invalid UPC-E data");
            }
            break;
        default:
            upca[3] = upce[3];
            upca[4] = upce[4];
            upca[5] = upce[5];
            upca[10] = emode;
            if (validate && upce[5] == '0') {
                /* Note 3 - "X5 shall not be equal to 0" */
                throw new OkapiException("Invalid UPC-E data");
            }
            break;
        }

        return new String(upca);
    }

    /** Two number systems can be used: system 0 and system 1. */
    private static int getNumberSystem(final String content) {
        switch (content.charAt(0)) {
        case '0':
            return 0;
        case '1':
            return 1;
        default:
            throw new OkapiException("Invalid input data");
        }
    }

    @Override
    protected void plotSymbol() {

        int xBlock;
        int x, y, w, h;
        boolean black = true;
        final int compositeOffset = this.linkageFlag ? 6 : 0; // space for composite separator above
        final int hrtOffset = this.humanReadableLocation == TOP ? getTheoreticalHumanReadableHeight() : 0; // space
                                                                                                           // for
                                                                                                           // HRT
                                                                                                           // above

        this.rectangles.clear();
        this.texts.clear();
        x = 0;

        /* Draw the bars in the symbology */
        for (xBlock = 0; xBlock < this.pattern[0].length(); xBlock++) {

            w = this.pattern[0].charAt(xBlock) - '0';

            if (black) {
                y = 0;
                h = this.default_height;
                /* Add extension to guide bars */
                if (this.mode == Mode.UPCA) {
                    if (x < 10 || x > 84 || x > 45 && x < 49) {
                        h += this.guardPatternExtraHeight;
                    }
                    if (this.linkageFlag && (x == 0 || x == 94)) {
                        h += 2;
                        y -= 2;
                    }
                } else {
                    if (x < 4 || x > 45) {
                        h += this.guardPatternExtraHeight;
                    }
                    if (this.linkageFlag && (x == 0 || x == 50)) {
                        h += 2;
                        y -= 2;
                    }
                }
                final Rectangle2D.Double rect = new Rectangle2D.Double(scale(x), y + compositeOffset + hrtOffset, scale(w), h);
                this.rectangles.add(rect);
                this.symbol_width = Math.max(this.symbol_width, (int) rect.getMaxX());
                this.symbol_height = Math.max(this.symbol_height, (int) rect.getHeight());
            }

            black = !black;
            x += w;
        }

        /* Add separator for composite symbology, if necessary */
        if (this.linkageFlag) {
            if (this.mode == Mode.UPCA) {
                this.rectangles.add(new Rectangle2D.Double(scale(0), 0, scale(1), 2));
                this.rectangles.add(new Rectangle2D.Double(scale(94), 0, scale(1), 2));
                this.rectangles.add(new Rectangle2D.Double(scale(-1), 2, scale(1), 2));
                this.rectangles.add(new Rectangle2D.Double(scale(95), 2, scale(1), 2));
            } else { // UPCE
                this.rectangles.add(new Rectangle2D.Double(scale(0), 0, scale(1), 2));
                this.rectangles.add(new Rectangle2D.Double(scale(50), 0, scale(1), 2));
                this.rectangles.add(new Rectangle2D.Double(scale(-1), 2, scale(1), 2));
                this.rectangles.add(new Rectangle2D.Double(scale(51), 2, scale(1), 2));
            }
            this.symbol_height += 4;
        }

        /* Now add the text */
        if (this.humanReadableLocation == BOTTOM) {
            this.symbol_height -= this.guardPatternExtraHeight;
            final double baseline = this.symbol_height + this.fontSize;
            if (this.mode == Mode.UPCA) {
                this.texts.add(new TextBox(scale(-9), baseline, scale(4), this.readable.substring(0, 1), HumanReadableAlignment.RIGHT));
                this.texts.add(new TextBox(scale(12), baseline, scale(32), this.readable.substring(1, 6), this.humanReadableAlignment));
                this.texts.add(new TextBox(scale(51), baseline, scale(32), this.readable.substring(6, 11), this.humanReadableAlignment));
                if (this.showCheckDigit) {
                    this.texts.add(new TextBox(scale(97), baseline, scale(4), this.readable.substring(11, 12), HumanReadableAlignment.LEFT));
                }
            } else { // UPCE
                this.texts.add(new TextBox(scale(-9), baseline, scale(4), this.readable.substring(0, 1), HumanReadableAlignment.RIGHT));
                this.texts.add(new TextBox(scale(5), baseline, scale(39), this.readable.substring(1, 7), this.humanReadableAlignment));
                if (this.showCheckDigit) {
                    this.texts.add(new TextBox(scale(53), baseline, scale(4), this.readable.substring(7, 8), HumanReadableAlignment.LEFT));
                }
            }
        } else if (this.humanReadableLocation == TOP) {
            final double baseline = this.fontSize;
            final int width = this.mode == Mode.UPCA ? 94 : 50;
            this.texts.add(new TextBox(scale(0), baseline, scale(width), this.readable, this.humanReadableAlignment));
        }

        /* Now add the add-on symbol, if necessary */
        if (this.addOn != null) {
            final int gap = 9;
            final int baseX = this.symbol_width + scale(gap);
            final Rectangle2D.Double r1 = this.rectangles.get(0);
            final Rectangle2D.Double ar1 = this.addOn.rectangles.get(0);
            final int baseY = (int) (r1.y + r1.getHeight() - ar1.y - ar1.getHeight());
            for (final TextBox t : this.addOn.getTexts()) {
                this.texts.add(new TextBox(baseX + t.x, baseY + t.y, t.width, t.text, t.alignment));
            }
            for (final Rectangle2D.Double r : this.addOn.getRectangles()) {
                this.rectangles.add(new Rectangle2D.Double(baseX + r.x, baseY + r.y, r.width, r.height));
            }
            this.symbol_width += scale(gap) + this.addOn.symbol_width;
            this.pattern[0] = this.pattern[0] + gap + this.addOn.pattern[0];
        }
    }

    /** Scales the specified width or x-dimension according to the current module width. */
    private int scale(final int w) {
        return this.moduleWidth * w;
    }
}