/* * 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; } }