/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tom_roush.pdfbox.filter; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; /** * This class represents an ASCII85 output stream. * * @author Ben Litchfield */ final class ASCII85OutputStream extends FilterOutputStream { private int lineBreak; private int count; private byte[] indata; private byte[] outdata; /** * Function produces five ASCII printing characters from * four bytes of binary data. */ private int maxline; private boolean flushed; private char terminator; private static final char OFFSET = '!'; private static final char NEWLINE = '\n'; private static final char Z = 'z'; /** * Constructor. * * @param out The output stream to write to. */ ASCII85OutputStream(OutputStream out) { super(out); lineBreak = 36 * 2; maxline = 36 * 2; count = 0; indata = new byte[4]; outdata = new byte[5]; flushed = true; terminator = '~'; } /** * This will set the terminating character. * * @param term The terminating character. */ public void setTerminator(char term) { if (term < 118 || term > 126 || term == Z) { throw new IllegalArgumentException("Terminator must be 118-126 excluding z"); } terminator = term; } /** * This will get the terminating character. * * @return The terminating character. */ public char getTerminator() { return terminator; } /** * This will set the line length that will be used. * * @param l The length of the line to use. */ public void setLineLength(int l) { if (lineBreak > l) { lineBreak = l; } maxline = l; } /** * This will get the length of the line. * * @return The line length attribute. */ public int getLineLength() { return maxline; } /** * This will transform the next four ascii bytes. */ private void transformASCII85() { long word = ((((indata[0] << 8) | (indata[1] & 0xFF)) << 16) | ((indata[2] & 0xFF) << 8) | (indata[3] & 0xFF)) & 0xFFFFFFFFL; if (word == 0) { outdata[0] = (byte) Z; outdata[1] = 0; return; } long x; x = word / (85L * 85L * 85L * 85L); outdata[0] = (byte) (x + OFFSET); word -= x * 85L * 85L * 85L * 85L; x = word / (85L * 85L * 85L); outdata[1] = (byte) (x + OFFSET); word -= x * 85L * 85L * 85L; x = word / (85L * 85L); outdata[2] = (byte) (x + OFFSET); word -= x * 85L * 85L; x = word / 85L; outdata[3] = (byte) (x + OFFSET); outdata[4] = (byte) ((word % 85L) + OFFSET); } /** * This will write a single byte. * * @param b The byte to write. * * @throws IOException If there is an error writing to the stream. */ @Override public void write(int b) throws IOException { flushed = false; indata[count++] = (byte) b; if (count < 4) { return; } transformASCII85(); for (int i = 0; i < 5; i++) { if (outdata[i] == 0) { break; } out.write(outdata[i]); if (--lineBreak == 0) { out.write(NEWLINE); lineBreak = maxline; } } count = 0; } /** * This will flush the data to the stream. * * @throws IOException If there is an error writing the data to the stream. */ @Override public void flush() throws IOException { if (flushed) { return; } if (count > 0) { for (int i = count; i < 4; i++) { indata[i] = 0; } transformASCII85(); if (outdata[0] == Z) { for (int i = 0; i < 5; i++) // expand 'z', { outdata[i] = (byte) OFFSET; } } for (int i = 0; i < count + 1; i++) { out.write(outdata[i]); if (--lineBreak == 0) { out.write(NEWLINE); lineBreak = maxline; } } } if (--lineBreak == 0) { out.write(NEWLINE); } out.write(terminator); out.write(NEWLINE); count = 0; lineBreak = maxline; flushed = true; super.flush(); } /** * This will close the stream. * * @throws IOException If there is an error closing the wrapped stream. */ @Override public void close() throws IOException { try { flush(); super.close(); } finally { indata = outdata = null; } } }