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.HumanReadableLocation.BOTTOM;
import static uk.org.okapibarcode.backend.HumanReadableLocation.NONE;
import static uk.org.okapibarcode.backend.HumanReadableLocation.TOP;

import java.awt.geom.Rectangle2D;

/**
 * <p>
 * Implements EAN bar code symbology according to BS EN 797:1996.
 *
 * <p>
 * European Article Number data can be encoded in EAN-8 or EAN-13 format requiring a 7-digit or
 * 12-digit input respectively. EAN-13 numbers map to Global Trade Identification Numbers (GTIN)
 * whereas EAN-8 symbols are generally for internal use only. Check digit is calculated and should
 * not be in input data. Leading zeroes are added as required.
 *
 * <p>
 * Add-on content can be appended to the main symbol content by adding a <tt>'+'</tt> character,
 * followed by the add-on content (up to 5 digits).
 *
 * @author <a href="mailto:jakel2006@me.com">Robert Elliott</a>
 */
public class Ean extends Symbol {

    public enum Mode {
        EAN8, EAN13
    };

    private static final String[] EAN13_PARITY = { "AAAAAA", "AABABB", "AABBAB", "AABBBA", "ABAABB", "ABBAAB", "ABBBAA", "ABABAB", "ABABBA", "ABBABA" };

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

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

    private Mode mode = Mode.EAN13;
    private int guardPatternExtraHeight = 5;
    private boolean linkageFlag;
    private EanUpcAddOn addOn;

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

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

    /**
     * Returns the EAN mode (EAN-8 or EAN-13).
     *
     * @return the EAN mode (EAN-8 or EAN-13)
     */
    public Mode getMode() {
        return this.mode;
    }

    /**
     * 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 EAN data");
        }

        if (this.mode == Mode.EAN8) {
            ean8();
        } else {
            ean13();
        }
    }

    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 ean13() {

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

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

        final String hrt = this.content + check;
        final char parityChar = hrt.charAt(0);
        final String parity = EAN13_PARITY[parityChar - '0'];
        infoLine("Parity Digit: " + parityChar);

        final StringBuilder dest = new StringBuilder("111");
        for (int i = 1; i < 13; i++) {
            if (i == 7) {
                dest.append("11111");
            }
            if (i <= 6) {
                if (parity.charAt(i - 1) == 'B') {
                    dest.append(EAN_SET_B[hrt.charAt(i) - '0']);
                } else {
                    dest.append(EAN_SET_A[hrt.charAt(i) - '0']);
                }
            } else {
                dest.append(EAN_SET_A[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 ean8() {

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

        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 < 8; i++) {
            if (i == 4) {
                dest.append("11111");
            }
            dest.append(EAN_SET_A[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 };
    }

    protected static String validateAndPad(String s, final int targetLength) {

        if (!s.matches("[0-9]+")) {
            throw new OkapiException("Invalid characters in input");
        }

        if (s.length() > targetLength) {
            throw new OkapiException("Input data too long");
        }

        if (s.length() < targetLength) {
            for (int i = s.length(); i < targetLength; i++) {
                s = '0' + s;
            }
        }

        return s;
    }

    public static char calcDigit(final String s) {

        int count = 0;
        int p = 0;

        for (int i = s.length() - 1; i >= 0; i--) {
            int c = Character.getNumericValue(s.charAt(i));
            if (p % 2 == 0) {
                c = c * 3;
            }
            count += c;
            p++;
        }

        int cdigit = 10 - count % 10;
        if (cdigit == 10) {
            cdigit = 0;
        }

        return (char) (cdigit + '0');
    }

    @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.EAN13) {
                    if (x < 3 || x > 91 || x > 45 && x < 49) {
                        h += this.guardPatternExtraHeight;
                    }
                    if (this.linkageFlag && (x == 0 || x == 94)) {
                        h += 2;
                        y -= 2;
                    }
                } else {
                    if (x < 3 || x > 62 || x > 30 && x < 35) {
                        h += this.guardPatternExtraHeight;
                    }
                    if (this.linkageFlag && (x == 0 || x == 66)) {
                        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.EAN13) {
                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 { // EAN8
                this.rectangles.add(new Rectangle2D.Double(scale(0), 0, scale(1), 2));
                this.rectangles.add(new Rectangle2D.Double(scale(66), 0, scale(1), 2));
                this.rectangles.add(new Rectangle2D.Double(scale(-1), 2, scale(1), 2));
                this.rectangles.add(new Rectangle2D.Double(scale(67), 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.EAN13) {
                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));
                this.texts.add(new TextBox(scale(51), baseline, scale(39), this.readable.substring(7, 13), this.humanReadableAlignment));
            } else { // EAN8
                this.texts.add(new TextBox(scale(5), baseline, scale(25), this.readable.substring(0, 4), this.humanReadableAlignment));
                this.texts.add(new TextBox(scale(37), baseline, scale(25), this.readable.substring(4, 8), this.humanReadableAlignment));
            }
        } else if (this.humanReadableLocation == TOP) {
            final double baseline = this.fontSize;
            final int width = this.mode == Mode.EAN13 ? 94 : 66;
            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;
    }
}