/* * JSwiff is an open source Java API for Macromedia Flash file generation * and manipulation * * Copyright (C) 2004-2006 Ralf Terdic (contact@jswiff.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.jswiff.io; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.zip.InflaterInputStream; /** * Implements a bit stream used for reading SWF files. */ public final class InputBitStream { private InputStream stream; private int bitBuffer; private int bitCursor = 8; // 8 if bitBuffer empty, else 0-7 private boolean compressed = false; private long offset; private boolean ansi; private boolean shiftJIS; /** * Creates a new bit stream instance from a given input stream. * * @param stream the internal input stream the data is read from. */ public InputBitStream(InputStream stream) { this.stream = stream; offset = 0; } /** * Creates a new bit stream instance. Data is read from a given byte buffer. * * @param buffer data buffer the bit stream reads from */ public InputBitStream(byte[] buffer) { this(new ByteArrayInputStream(buffer)); } /** * Specifies whether ANSI encoding is to be used when decoding strings. * * @param ansi <code>true</code> for ANSI encoding */ public void setANSI(boolean ansi) { this.ansi = ansi; } /** * Checks whether ANSI encoding is to be used when decoding strings. * * @return <code>true</code> for ANSI encoding */ public boolean isANSI() { return ansi; } /** * Returns the stream offset, i.e. the number of bytes (fully) read. * * @return the stream offset */ public long getOffset() { return offset; } /** * Specifies whether Shift-JIS encoding is to be used when decoding strings. * * @param shiftJIS <code>true</code> for Shift-JIS encoding */ public void setShiftJIS(boolean shiftJIS) { this.shiftJIS = shiftJIS; } /** * Checks whether Shift-JIS encoding is to be used when decoding strings. * * @return <code>true</code> for Shift-JIS encoding */ public boolean isShiftJIS() { return shiftJIS; } /** * Byte align, i.e. after invocation, data is read beginning at the following * byte boundary. Has no effect if cursor is already at a byte boundary. */ public void align() { bitCursor = 8; } /** * Returns the number of bytes that can be read from the internal input * stream without blocking. This value is obtained by invoking available() * on the internal stream. Don't use this method on compressed streams. * * @return the number of bytes that can be read without blocking * * @throws IOException if an I/O error has occurred * @throws IllegalStateException if stream is compressed */ public int available() throws IOException { if (compressed) { throw new IllegalStateException( "Don't use available() on compressed streams!"); } return stream.available(); } /** * Closes the internal stream. After this, data cannot be read anymore. * * @throws IOException if an I/O error has occurred */ public void close() throws IOException { stream.close(); } /** * Enables stream compression (ZLIB). */ public void enableCompression() { if (!compressed) { stream = new BufferedInputStream(new InflaterInputStream(stream)); } compressed = true; } /** * Move within the stream, relative to the current position. * * @param delta positive to move forward, negative for backward move * * @throws IOException if an I/O error has occured */ public void move(long delta) throws IOException { offset = offset + delta; stream.reset(); stream.skip(offset); } /** * Reads a bit and interprets it as boolean. * * @return true for 1, false for 0 * * @throws IOException if an I/O error has occurred */ public boolean readBooleanBit() throws IOException { return ((readUnsignedBits(1)) == 1); } /** * Reads a specific number of bytes. * * @param length number of bytes to be read * * @return the read data, as byte array * * @throws IOException if an I/O error has occurred */ public byte[] readBytes(int length) throws IOException { byte[] result; if (length > 0) { result = new byte[length]; int totalRead = 0; while (totalRead < length) { int read = stream.read(result, totalRead, length - totalRead); if (read < 0) { endReached(); return null; } totalRead += read; } } else { return new byte[0]; } offset += length; align(); return result; } /** * Reads a double value. * * @return double value * * @throws IOException if an I/O error has occurred */ public double readDouble() throws IOException { byte[] buffer = readBytes(8); long longBits = (((long) buffer[3] << 56) + ((long) (buffer[2] & 255) << 48) + ((long) (buffer[1] & 255) << 40) + ((long) (buffer[0] & 255) << 32) + ((long) (buffer[7] & 255) << 24) + ((buffer[6] & 255) << 16) + ((buffer[5] & 255) << 8) + ((buffer[4] & 255) << 0)); return Double.longBitsToDouble(longBits); } /** * Reads a 16 bit (8.8) fixed point number. * * @return number as double value * * @throws IOException if an I/O error has occured */ public double readFP16() throws IOException { short value = readSI16(); return value / 256.0; } /** * Reads a 32 bit (16.16) fixed point number. * * @return number as double value * * @throws IOException if an I/O error has occured */ public double readFP32() throws IOException { int value = readSI32(); return value / 65536.0; } /** * Reads a specific number of bits and interprets them as a fixed point * (x.16) value. * * @param nBits number of bits to be read * * @return fixed point number as a double value * * @throws IOException if an I/O error has occurred */ public double readFPBits(int nBits) throws IOException { long longNumber = readSignedBits(nBits); return longNumber / 65536.0; } /** * Reads a floating point value. * * @return floating point number as a float value * * @throws IOException if an I/O error has occurred */ public float readFloat() throws IOException { return Float.intBitsToFloat(readSI32()); } /** * Reads a 16 bit floating point number (half precision, or s10e5, i.e. 1 * sign bit, 5 exponent bits and 10 mantissa bits). * * @return float value * * @throws IOException if an I/O error occured */ public float readFloat16() throws IOException { int bits16 = readUI16(); int sign = (bits16 & 0x8000) >> 15; int exponent16 = (bits16 & 0x7c00) >> 10; int mantissa16 = bits16 & 0x3ff; int exponent32 = 0; if (exponent16 != 0) { if (exponent16 == 0x1f) { exponent32 = 0xff; } else { exponent32 = exponent16 - 15 + 127; } } int mantissa32 = mantissa16 << 13; int bits32 = sign << 31; bits32 |= exponent32 << 23; bits32 |= mantissa32; return Float.intBitsToFloat(bits32); } /** * Reads a signed word value * * @return signed word as a short value * * @throws IOException if an I/O error has occurred */ public short readSI16() throws IOException { return (short) readUI16(); } /** * Reads a signed double word * * @return signed double word as an int value * * @throws IOException if an I/O error has occurred */ public int readSI32() throws IOException { return (int) readUI32(); } /** * Reads a signed byte value * * @return signed byte as a byte value * * @throws IOException if an I/O error has occurred */ public byte readSI8() throws IOException { return (byte) readUI8(); } /** * Reads a specific number of bits and interprets them as a signed integer * * @param nBits number of bits to be read * * @return signed integer as a long value * * @throws IOException if an I/O error has occurred */ public long readSignedBits(int nBits) throws IOException { long result = readUnsignedBits(nBits); if ((result & (1L << (nBits - 1))) != 0) { result |= (-1L << nBits); } return result; } /** * Reads an UTF-8 encoded, null-terminated string * * @return a string * * @throws IOException if an I/O error has occurred */ public String readString() throws IOException { // read the string byte for byte until we get a null-byte ByteArrayOutputStream baos = new ByteArrayOutputStream(); fillBitBuffer(); while (bitBuffer != 0) { baos.write(bitBuffer); fillBitBuffer(); } byte[] buffer = baos.toByteArray(); String encoding; if (shiftJIS) { encoding = "SJIS"; } else if (ansi) { encoding = "cp1252"; } else { encoding = "UTF-8"; } return new String(buffer, encoding); } /** * Reads an unsigned word value * * @return unsigned word as an int value * * @throws IOException if an I/O error has occurred */ public int readUI16() throws IOException { fillBitBuffer(); int result = bitBuffer; fillBitBuffer(); result |= (bitBuffer << 8); align(); return result; } /** * Reads an unsigned double word value * * @return unsigned double word as a long value * * @throws IOException if an I/O error has occurred */ public long readUI32() throws IOException { fillBitBuffer(); long result = bitBuffer; fillBitBuffer(); result |= (bitBuffer << 8); fillBitBuffer(); result |= (bitBuffer << 16); fillBitBuffer(); result |= (bitBuffer << 24); align(); return result; } /** * Reads an unsigned byte value * * @return unsigned byte as a byte value * * @throws IOException if an I/O error has occurred */ public short readUI8() throws IOException { fillBitBuffer(); short result = (short) bitBuffer; align(); return result; } /** * Read an unsigned integer from the given number of bits. * * @param nBits number of bits to be read * * @return unsigned integer as a long value * * @throws IOException if an I/O error has occurred */ public long readUnsignedBits(int nBits) throws IOException { if (nBits == 0) { return 0; } int bitsLeft = nBits; long result = 0; while (bitsLeft > 0) { if (bitCursor == 8) { // buffer is empty fillBitBuffer(); } // check if bit is set if ((bitBuffer & (1 << (7 - bitCursor))) != 0) { // set corresponding result bit result |= (1L << (bitsLeft - 1)); } bitCursor++; bitsLeft--; } return result; } private void endReached() throws IOException { throw new IOException("Input data stream ended unexpectedly!"); } private void fillBitBuffer() throws IOException { bitBuffer = stream.read(); offset++; if (bitBuffer < 0) { endReached(); } bitCursor = 0; } public void reset() { try { stream.reset(); offset = 0; } catch (IOException e) { e.printStackTrace(); } } }