/*
* Copyright (c) 2009-2013, i Data Connect!
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.orsonpdf.filter;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* <p>
* An ascii85 encoder, implemented as an {@link OutputStream}.
* </p>
* <p>
* Call <code>flush()</code> or <code>close()</code> to properly close the
* ascii85 block. The block must be closed for the encoded data to be valid.
* Do not call <code>flush()</code> before you intend to end the ascii85 block.
* Multiple ascii85 blocks may be encoded by calling flush() and then writing
* more bytes to the stream.
* </p>
* <p>
* Note that if you use the constructor with the
* <code>useSpaceCompression</code> option, the encoded text will be shorter
* when there are many consecutive space characters in the encoded data, but
* it will not be compatible with Adobe's ascii85 implementation. It makes sense
* to use this option if interoperability with other ascii85 implementations
* is not a requirement.
* </p>
* @author Ben Upsavs
*/
public class Ascii85OutputStream extends FilterOutputStream {
private int width = 72;
private int pos;
private int tuple;
private int count;
private boolean encoding;
private boolean useSpaceCompression;
/**
* Creates an output stream to encode ascii85 data, using a default line
* with of 72 characters and not using the space character compression
* option. Call <code>flush()</code> to add the padding and end the ascii85
* block.
* @param out the output stream.
*/
public Ascii85OutputStream(OutputStream out) {
super(out);
}
/**
* Creates an output stream to encode ascii85 data, using a default line
* width of 72 characters. Call <code>flush()</code> to end the ascii85
* block.
* @param out the output stream.
* @param useSpaceCompression Whether to use space character compression in
* the output.
*/
public Ascii85OutputStream(OutputStream out, boolean useSpaceCompression) {
this(out);
this.useSpaceCompression = useSpaceCompression;
}
/**
* Creates an output stream to encode ascii85 data. Call
* <code>flush()</code> to end the ascii85 block.
* @param out the output stream.
* @param width The maximum line width of the encoded output text.
* Whitespace characters are ignored when decoding.
* @param useSpaceCompression Whether to use space character compression in
* the output.
*/
public Ascii85OutputStream(OutputStream out, int width, boolean useSpaceCompression) {
this(out);
this.width = width;
this.useSpaceCompression = useSpaceCompression;
}
private void startEncoding() throws IOException {
//out.write('<');
//out.write('~');
//pos = 2;
encoding = true;
}
/**
* Writes a single byte to the stream. See
* {@link OutputStream#write(int b)} for details.
* @param b The byte to encode.
* @throws java.io.IOException If an I/O error occurs in the underlying
* output stream.
*/
@Override
public void write(int b) throws IOException {
if (!encoding)
startEncoding();
switch (count++) {
case 0:
tuple |= ((b & 0xff) << 24);
break;
case 1:
tuple |= ((b & 0xff) << 16);
break;
case 2:
tuple |= ((b & 0xff) << 8);
break;
case 3:
tuple |= (b & 0xff);
if (tuple == 0) {
// Use null compression
out.write('z');
if (pos++ >= width) {
pos = 0;
out.write('\r');
out.write('\n');
}
} else if (useSpaceCompression && (tuple == 0x20202020)) {
// Use space compression
out.write('y');
if (pos++ >= width) {
pos = 0;
out.write('\r');
out.write('\n');
}
} else
encode(tuple, count);
tuple = 0;
count = 0;
break;
}
}
/**
* Writes a single byte to the underlying output stream, unencoded. If
* done improperly, this may corrupt the ascii85 data stream. Writing
* a byte using this method may cause the line length to increase since
* the line length counter will not be updated by this method.
* @param b The byte to write.
* @throws java.io.IOException If the underlying output stream has an I/O
* error.
*/
public void writeUnencoded(int b) throws IOException {
super.write(b);
}
/**
* Writes bytes to the underlying output stream, unencoded. If done
* improperly, this may corrupt the ascii85 data stream. Writing bytes
* using this method may cause the line length to increase since the line
* length counter will not be updated by this method.
* @param b The bytes to write.
* @throws java.io.IOException If the underlying output stream has an I/O
* error.
*/
public void writeUnencoded(byte[] b) throws IOException {
writeUnencoded(b, 0, b.length);
}
/**
* Writes bytes to the underlying output stream, unencoded. If done
* improperly, this may corrupt the ascii85 data stream. Writing bytes
* using this method may cause the line length to increase since the line
* length counter will not be updated by this method.
* @param b The bytes to write.
* @param off The offset of <code>b</code> to start reading from.
* @param len The amount of bytes to read from <code>b</code>.
* @throws java.io.IOException If the underlying output stream has an I/O
* error.
*/
public void writeUnencoded(byte[] b, int off, int len) throws IOException {
for (int i = 0; i < len; i++)
writeUnencoded(b[off + i]);
}
/**
* Encodes <code>tuple</code> and writes it to the output stream. The number
* of bytes in the tuple, and thus the value of <code>count</code> is
* normally 4, however less bytes may also be encoded, particularly if the
* input stream has ended before the current tuple is full.
* @param tuple The tuple to encode.
* @param count The number of bytes stuffed into the tuple.
* @throws IOException If an I/O error occurs.
*/
private void encode(int tuple, int count) throws IOException {
int i = 5;
byte[] buf = new byte[5];
short bufPos = 0;
long longTuple = (tuple & 0xffffffffL);
do {
buf[bufPos++] = (byte)(longTuple % 85);
longTuple /= 85;
} while (--i > 0);
i = count;
do {
out.write(buf[--bufPos] + '!');
if (pos++ >= width) {
pos = 0;
out.write('\r');
out.write('\n');
}
} while (i-- > 0);
}
/**
* Adds the closing block and flushes the underlying output stream. This
* method should only be called if it is intended that the ascii85 block
* should be closed.
*/
@Override
public void flush() throws IOException {
// Add padding if required.
if (encoding) {
if (count > 0)
encode(tuple, count);
if (pos + 2 > width) {
out.write('\r');
out.write('\n');
}
out.write('~');
out.write('>');
out.write('\r');
out.write('\n');
encoding = false;
tuple = count = 0;
}
super.flush();
}
}