package com.justwayward.reader.view.chmview; import java.io.IOException; public class LZXTree { public static final int LZX_LENTABLE_SAFETY = 64; // allow length table decoding overruns public static final int LZX_PRETREE_NUM_ELEMENTS = 20; int bits; int max_symbol; int[] symbols; byte[] lens; LZXTree(int bits, int max) { this.bits = bits; this.max_symbol = max; symbols = new int[(1 << bits) + (max << 1)]; lens = new byte[max + LZX_LENTABLE_SAFETY]; } /** * This function was coded by David Tritscher. It builds a fast huffman * decoding table out of just a canonical huffman code lengths table. */ void makeSymbolTable() throws DataFormatException { int bit_num = 1; int pos = 0; // the current position in the decode table int table_mask = 1 << bits; int bit_mask = table_mask >> 1; // don't do 0 length codes int next_symbol = bit_mask; // base of allocation for long codes // fill entries for codes short enough for a direct mapping while (bit_num <= bits) { for (int symbol = 0; symbol < max_symbol; symbol++) { if (lens[symbol] == bit_num) { int leaf = pos; if ((pos += bit_mask) > table_mask) // ensure capacity throw new DataFormatException("symbol table overruns"); // fill all possible lookups of this symbol with the symbol itself while (leaf < pos) symbols[leaf++] = symbol; } } bit_mask >>= 1; bit_num++; } // if there are any codes longer than table.bits if (pos != table_mask) { // clear the remainder of the table for (int i = pos; i < table_mask; i++) symbols[i] = 0; // give ourselves room for codes to grow by up to 16 more bits pos <<= 16; table_mask <<= 16; bit_mask = 1 << 15; while (bit_num <= 16) { for (int symbol = 0; symbol < max_symbol; symbol++) { if (lens[symbol] == bit_num) { int leaf = pos >> 16; for (int fill = 0; fill < bit_num - bits; fill++) { // if this path hasn't been taken yet, 'allocate' two entries if (symbols[leaf] == 0) { symbols[next_symbol << 1] = 0; symbols[(next_symbol << 1) + 1] = 0; symbols[leaf] = (next_symbol++); } // follow the path and select either left or right for next bit leaf = symbols[leaf] << 1; if (((pos >> (15 - fill)) & 1) > 0) // odd leaf++; } symbols[leaf] = symbol; if ((pos += bit_mask) > table_mask) throw new DataFormatException("symbol table overflow"); } } bit_mask >>= 1; bit_num++; } } // full table? if (pos == table_mask) return; // either erroneous table, or all elements are 0 - let's find out. for (short sym = 0; sym < max_symbol; sym++) if (lens[sym] != 0) throw new DataFormatException("erroneous symbol table"); } /** * reads in code lengths for symbols * first to last in the given table. The code lengths are stored in their * own special LZX way. */ void readLengthTable(BitsInputStream bin, int first, int last) throws DataFormatException, IOException { LZXTree preTree = new LZXTree(6, LZX_PRETREE_NUM_ELEMENTS); for (int i = 0; i < preTree.max_symbol; i++) preTree.lens[i] = (byte) bin.readLE(4); preTree.makeSymbolTable(); for (int pos = first; pos < last; ) { int symbol = preTree.readHuffmanSymbol(bin); if (symbol == 0x11) { int pos2 = pos + bin.readLE(4) + 4; while (pos < pos2) lens[pos++] = (byte) 0; } else if (symbol == 0x12) { int pos2 = pos + bin.readLE(5) + 20; while (pos < pos2) lens[pos++] = (byte) 0; } else if (symbol == 0x13) { int pos2 = pos + bin.readLE(1) + 4; symbol = lens[pos] - preTree.readHuffmanSymbol(bin); if (symbol < 0) symbol += 0x11; while (pos < pos2) lens[pos++] = (byte) symbol; } else { symbol = lens[pos] - symbol; if (symbol < 0) symbol += 0x11; lens[pos++] = (byte) symbol; } } } /** * decodes one huffman symbol from the bitstream using the * stated table and return it. * @throws IOException */ int readHuffmanSymbol(BitsInputStream bin) throws IOException { int next = bin.peekUnder(16); /* TODO: it's very strange that bin.peek(bits) will raise EOFException, * we have to use peekUnder(bits) here, but how should it happen like this? */ int symbol = symbols[bin.peekUnder(bits)]; if (symbol >= max_symbol) { int j = 1 << (16 - bits); do { j >>= 1; symbol <<= 1; symbol |= (next & j) > 0 ? 1 : 0; symbol = symbols[symbol]; } while (symbol >= max_symbol); } bin.readLE(lens[symbol]); return symbol; } public void clear() { for (int i = 0; i < lens.length; i++) lens[i] = 0; } }