Subversion Repositories mkgmap

Rev

Rev 3408 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2007,2014 Steve Ratcliffe
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *
 * Author: Steve Ratcliffe
 * Create date: 14-Jan-2007
 */

package uk.me.parabola.imgfmt.app.labelenc;

import java.util.Locale;

/**
 * Format according to the '6 bit' .img format.  The text is first upper
 * cased.  Any letter with a diacritic or accent is replaced with its base
 * letter.
 *
 * For example Körnerstraße would become KORNERSTRASSE,
 * Řípovská would become RIPOVSKA etc.
 *
 * I believe that some Garmin units are only capable of showing uppercase
 * ascii characters, so this will be the default.
 *
 * @author Steve Ratcliffe
 * @see <a href="http://garmin-img.sf.net">Garmin IMG File Format</a>
 */

public class Format6Encoder extends BaseEncoder implements CharacterEncoder {

        // This is 0x1b is the source document, but the accompanying code uses
        // the value 0x1c, which seems to work.
        private static final int SYMBOL_SHIFT = 0x1c;

        public static final String LETTERS =
                " ABCDEFGHIJKLMNO" +    // 0x00-0x0F
                "PQRSTUVWXYZxx   " +    // 0x10-0x1F
                "0123456789\u0001\u0002\u0003\u0004\u0005\u0006";       // 0x20-0x2F

        public static final String SYMBOLS =
                "@!\"#$%&'()*+,-./" +   // 0x00-0x0F
                "xxxxxxxxxx:;<=>?" +    // 0x10-0x1F
                "xxxxxxxxxxx[\\]^_";    // 0x20-0x2F

        private final Transliterator transliterator = new TableTransliterator("ascii");

        /**
         * Encode the text into the 6 bit format.  See the class level notes.
         *
         * @param text The original text, which can contain non-ascii characters.
         * @return Encoded form of the text.  Only uppercase ascii characters and
         * some escape sequences will be present.
         */

        public EncodedText encodeText(String text) {
                if (text == null || text.isEmpty())
                        return NO_TEXT;

                String s = transliterator.transliterate(text).toUpperCase(Locale.ENGLISH);

                // Allocate more than enough space on average for the label.
                // if you overdo it then it will waste a lot of space , but
                // not enough and there will be an error
                byte[] buf = new byte[2 * s.length() + 4];
                int off = 0;

                for (char c : s.toCharArray()) {

                        if (c == ' ') {
                                put6(buf, off++, 0);
                        } else if (c >= 'A' && c <= 'Z') {
                                put6(buf, off++, c - 'A' + 1);
                        } else if (c >= '0' && c <= '9') {
                                put6(buf, off++, c - '0' + 0x20);
                        } else if (c == 0x1b || c == 0x1c) {
                                put6(buf, off++, 0x1b);
                                put6(buf, off++, c + 0x10);
                        } else if (c >= 0x1d && c <= 0x1f) {
                                put6(buf, off++, c);
                        } else if (c >= 1 && c <= 6) {
                                // Highway shields
                                put6(buf, off++, 0x29 + c);
                        } else {
                                off = shiftedSymbol(buf, off, c);
                        }
                }

                buf = put6(buf, off++, 0xff);

                int len = ((off - 1) * 6) / 8 + 1;

                char[] chars = s.toCharArray();
                return new EncodedText(buf, len, chars);
        }

        /**
         * Certain characters have to be represented by two 6byte quantities.  This
         * routine sorts these out.
         *
         * @param buf The buffer to write into.
         * @param startOffset The offset to start writing to in the output buffer.
         * @param c The character that we are decoding.
         * @return The final offset.  This will be unchanged if there was nothing
         * written because the character does not have any representation.
         */

        private int shiftedSymbol(byte[] buf, int startOffset, char c) {
                int off = startOffset;
                int ind = SYMBOLS.indexOf(c);
                if (ind >= 0) {
                        put6(buf, off++, SYMBOL_SHIFT);
                        put6(buf, off++, ind);
                }
                return off;
        }

        /**
         * Each character is packed into 6 bits.  This keeps track of everything so
         * that the character can be put into the right place in the byte array.
         *
         * @param buf The buffer to populate.
         * @param off The character offset, that is the number of the six bit
         * character.
         * @param c The character to place.
         */

        private byte[] put6(byte[] buf, int off, int c) {
                int bitOff = off * 6;

                // The byte offset
                int byteOff = bitOff/8;

                // The offset within the byte
                int shift = bitOff - 8*byteOff;

                int mask = 0xfc >> shift;
                buf[byteOff] |= ((c << 2) >> shift) & mask;

                // IF the shift is greater than two we have to put the rest in the
                // next byte.
                if (shift > 2) {
                        mask = 0xfc << (8 - shift);
                        buf[byteOff + 1] = (byte) (((c << 2) << (8 - shift)) & mask);
                }

                return buf;
        }
}