com.itextpdf.text.pdf.ByteBuffer Maven / Gradle / Ivy
/*
* $Id: 0e30bceaec800dd928fff8e7de5a4a96fa91c334 $
*
* This file is part of the iText (R) project.
* Copyright (c) 1998-2016 iText Group NV
* Authors: Bruno Lowagie, Paulo Soares, et al.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation with the addition of the
* following permission added to Section 15 as permitted in Section 7(a):
* FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
* ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
* OF THIRD PARTY RIGHTS
*
* 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 Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, see http://www.gnu.org/licenses or write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA, 02110-1301 USA, or download the license from the following URL:
* http://itextpdf.com/terms-of-use/
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License.
*
* In accordance with Section 7(b) of the GNU Affero General Public License,
* a covered work must retain the producer line in every PDF that is created
* or manipulated using iText.
*
* You can be released from the requirements of the license by purchasing
* a commercial license. Buying such a license is mandatory as soon as you
* develop commercial activities involving the iText software without
* disclosing the source code of your own applications.
* These activities include: offering paid services to customers as an ASP,
* serving PDFs on the fly in a web application, shipping iText with a closed
* source product.
*
* For more information, please contact iText Software Corp. at this
* address: [email protected]
*/
package com.itextpdf.text.pdf;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import com.itextpdf.text.error_messages.MessageLocalization;
import com.itextpdf.text.DocWriter;
/**
* Acts like a StringBuffer
but works with byte
arrays.
* Floating point is converted to a format suitable to the PDF.
* @author Paulo Soares
*/
public class ByteBuffer extends OutputStream {
/** The count of bytes in the buffer. */
protected int count;
/** The buffer where the bytes are stored. */
protected byte buf[];
private static int byteCacheSize = 0;
private static byte[][] byteCache = new byte[byteCacheSize][];
public static final byte ZERO = (byte)'0';
private static final char[] chars = new char[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
private static final byte[] bytes = new byte[] {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102};
/**
* If true
always output floating point numbers with 6 decimal digits.
* If false
uses the faster, although less precise, representation.
*/
public static boolean HIGH_PRECISION = false;
private static final DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.US);
/** Creates new ByteBuffer with capacity 128 */
public ByteBuffer() {
this(128);
}
/**
* Creates a byte buffer with a certain capacity.
* @param size the initial capacity
*/
public ByteBuffer(int size) {
if (size < 1)
size = 128;
buf = new byte[size];
}
/**
* Sets the cache size.
*
* This can only be used to increment the size.
* If the size that is passed through is smaller than the current size, nothing happens.
*
* @param size the size of the cache
*/
public static void setCacheSize(int size) {
if (size > 3276700) size = 3276700;
if (size <= byteCacheSize) return;
byte[][] tmpCache = new byte[size][];
System.arraycopy(byteCache, 0, tmpCache, 0, byteCacheSize);
byteCache = tmpCache;
byteCacheSize = size;
}
/**
* You can fill the cache in advance if you want to.
*
* @param decimals
*/
public static void fillCache(int decimals) {
int step = 1;
switch(decimals) {
case 0:
step = 100;
break;
case 1:
step = 10;
break;
}
for (int i = 1; i < byteCacheSize; i += step) {
if (byteCache[i] != null) continue;
byteCache[i] = convertToBytes(i);
}
}
/**
* Converts an double (multiplied by 100 and cast to an int) into an array of bytes.
*
* @param i the int
* @return a byte array
*/
private static byte[] convertToBytes(int i) {
int size = (int)Math.floor(Math.log(i) / Math.log(10));
if (i % 100 != 0) {
size += 2;
}
if (i % 10 != 0) {
size++;
}
if (i < 100) {
size++;
if (i < 10) {
size++;
}
}
size--;
byte[] cache = new byte[size];
size --;
if (i < 100) {
cache[0] = (byte)'0';
}
if (i % 10 != 0) {
cache[size--] = bytes[i % 10];
}
if (i % 100 != 0) {
cache[size--] = bytes[(i / 10) % 10];
cache[size--] = (byte)'.';
}
size = (int)Math.floor(Math.log(i) / Math.log(10)) - 1;
int add = 0;
while (add < size) {
cache[add] = bytes[(i / (int)Math.pow(10, size - add + 1)) % 10];
add++;
}
return cache;
}
/**
* Appends an int
. The size of the array will grow by one.
* @param b the int to be appended
* @return a reference to this ByteBuffer
object
*/
public ByteBuffer append_i(int b) {
int newcount = count + 1;
if (newcount > buf.length) {
byte newbuf[] = new byte[Math.max(buf.length << 1, newcount)];
System.arraycopy(buf, 0, newbuf, 0, count);
buf = newbuf;
}
buf[count] = (byte)b;
count = newcount;
return this;
}
/**
* Appends the subarray of the byte
array. The buffer will grow by
* len
bytes.
* @param b the array to be appended
* @param off the offset to the start of the array
* @param len the length of bytes to append
* @return a reference to this ByteBuffer
object
*/
public ByteBuffer append(byte b[], int off, int len) {
if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0) || len == 0)
return this;
int newcount = count + len;
if (newcount > buf.length) {
byte newbuf[] = new byte[Math.max(buf.length << 1, newcount)];
System.arraycopy(buf, 0, newbuf, 0, count);
buf = newbuf;
}
System.arraycopy(b, off, buf, count, len);
count = newcount;
return this;
}
/**
* Appends an array of bytes.
* @param b the array to be appended
* @return a reference to this ByteBuffer
object
*/
public ByteBuffer append(byte b[]) {
return append(b, 0, b.length);
}
/**
* Appends a String
to the buffer. The String
is
* converted according to the encoding ISO-8859-1.
* @param str the String
to be appended
* @return a reference to this ByteBuffer
object
*/
public ByteBuffer append(String str) {
if (str != null)
return append(DocWriter.getISOBytes(str));
return this;
}
/**
* Appends a char
to the buffer. The char
is
* converted according to the encoding ISO-8859-1.
* @param c the char
to be appended
* @return a reference to this ByteBuffer
object
*/
public ByteBuffer append(char c) {
return append_i(c);
}
/**
* Appends another ByteBuffer
to this buffer.
* @param buf the ByteBuffer
to be appended
* @return a reference to this ByteBuffer
object
*/
public ByteBuffer append(ByteBuffer buf) {
return append(buf.buf, 0, buf.count);
}
/**
* Appends the string representation of an int
.
* @param i the int
to be appended
* @return a reference to this ByteBuffer
object
*/
public ByteBuffer append(int i) {
return append((double)i);
}
/**
* Appends the string representation of a long
.
* @param i the long
to be appended
* @return a reference to this ByteBuffer
object
*/
public ByteBuffer append(long i) {
return append(Long.toString(i));
}
public ByteBuffer append(byte b) {
return append_i(b);
}
public ByteBuffer appendHex(byte b) {
append(bytes[(b >> 4) & 0x0f]);
return append(bytes[b & 0x0f]);
}
/**
* Appends a string representation of a float
according
* to the Pdf conventions.
* @param i the float
to be appended
* @return a reference to this ByteBuffer
object
*/
public ByteBuffer append(float i) {
return append((double)i);
}
/**
* Appends a string representation of a double
according
* to the Pdf conventions.
* @param d the double
to be appended
* @return a reference to this ByteBuffer
object
*/
public ByteBuffer append(double d) {
append(formatDouble(d, this));
return this;
}
/**
* Outputs a double
into a format suitable for the PDF.
* @param d a double
* @return the String
representation of the double
*/
public static String formatDouble(double d) {
return formatDouble(d, null);
}
/**
* Outputs a double
into a format suitable for the PDF.
* @param d a double
* @param buf a ByteBuffer
* @return the String
representation of the double
if
* buf
is null
. If buf
is not null
,
* then the double is appended directly to the buffer and this methods returns null
.
*/
public static String formatDouble(double d, ByteBuffer buf) {
if (HIGH_PRECISION) {
DecimalFormat dn = new DecimalFormat("0.######", dfs);
String sform = dn.format(d);
if (buf == null)
return sform;
else {
buf.append(sform);
return null;
}
}
boolean negative = false;
if (Math.abs(d) < 0.000015) {
if (buf != null) {
buf.append(ZERO);
return null;
} else {
return "0";
}
}
if (d < 0) {
negative = true;
d = -d;
}
if (d < 1.0) {
d += 0.000005;
if (d >= 1) {
if (negative) {
if (buf != null) {
buf.append((byte)'-');
buf.append((byte)'1');
return null;
} else {
return "-1";
}
} else {
if (buf != null) {
buf.append((byte)'1');
return null;
} else {
return "1";
}
}
}
if (buf != null) {
int v = (int) (d * 100000);
if (negative) buf.append((byte)'-');
buf.append((byte)'0');
buf.append((byte)'.');
buf.append( (byte)(v / 10000 + ZERO) );
if (v % 10000 != 0) {
buf.append( (byte)((v / 1000) % 10 + ZERO) );
if (v % 1000 != 0) {
buf.append( (byte)((v / 100) % 10 + ZERO) );
if (v % 100 != 0) {
buf.append((byte)((v / 10) % 10 + ZERO) );
if (v % 10 != 0) {
buf.append((byte)((v) % 10 + ZERO) );
}
}
}
}
return null;
} else {
int x = 100000;
int v = (int) (d * x);
StringBuilder res = new StringBuilder();
if (negative) res.append('-');
res.append("0.");
while( v < x/10 ) {
res.append('0');
x /= 10;
}
res.append(v);
int cut = res.length() - 1;
while (res.charAt(cut) == '0') {
--cut;
}
res.setLength(cut + 1);
return res.toString();
}
} else if (d <= 32767) {
d += 0.005;
int v = (int) (d * 100);
if (v < byteCacheSize && byteCache[v] != null) {
if (buf != null) {
if (negative) buf.append((byte)'-');
buf.append(byteCache[v]);
return null;
} else {
String tmp = PdfEncodings.convertToString(byteCache[v], null);
if (negative) tmp = "-" + tmp;
return tmp;
}
}
if (buf != null) {
if (v < byteCacheSize) {
//create the cachebyte[]
byte[] cache;
int size = 0;
if (v >= 1000000) {
//the original number is >=10000, we need 5 more bytes
size += 5;
} else if (v >= 100000) {
//the original number is >=1000, we need 4 more bytes
size += 4;
} else if (v >= 10000) {
//the original number is >=100, we need 3 more bytes
size += 3;
} else if (v >= 1000) {
//the original number is >=10, we need 2 more bytes
size += 2;
} else if (v >= 100) {
//the original number is >=1, we need 1 more bytes
size += 1;
}
//now we must check if we have a decimal number
if (v % 100 != 0) {
//yes, do not forget the "."
size += 2;
}
if (v % 10 != 0) {
size++;
}
cache = new byte[size];
int add = 0;
if (v >= 1000000) {
cache[add++] = bytes[(v / 1000000)];
}
if (v >= 100000) {
cache[add++] = bytes[(v / 100000) % 10];
}
if (v >= 10000) {
cache[add++] = bytes[(v / 10000) % 10];
}
if (v >= 1000) {
cache[add++] = bytes[(v / 1000) % 10];
}
if (v >= 100) {
cache[add++] = bytes[(v / 100) % 10];
}
if (v % 100 != 0) {
cache[add++] = (byte)'.';
cache[add++] = bytes[(v / 10) % 10];
if (v % 10 != 0) {
cache[add++] = bytes[v % 10];
}
}
byteCache[v] = cache;
}
if (negative) buf.append((byte)'-');
if (v >= 1000000) {
buf.append( bytes[(v / 1000000)] );
}
if (v >= 100000) {
buf.append( bytes[(v / 100000) % 10] );
}
if (v >= 10000) {
buf.append( bytes[(v / 10000) % 10] );
}
if (v >= 1000) {
buf.append( bytes[(v / 1000) % 10] );
}
if (v >= 100) {
buf.append( bytes[(v / 100) % 10] );
}
if (v % 100 != 0) {
buf.append((byte)'.');
buf.append( bytes[(v / 10) % 10] );
if (v % 10 != 0) {
buf.append( bytes[v % 10] );
}
}
return null;
} else {
StringBuilder res = new StringBuilder();
if (negative) res.append('-');
if (v >= 1000000) {
res.append( chars[(v / 1000000)] );
}
if (v >= 100000) {
res.append( chars[(v / 100000) % 10] );
}
if (v >= 10000) {
res.append( chars[(v / 10000) % 10] );
}
if (v >= 1000) {
res.append( chars[(v / 1000) % 10] );
}
if (v >= 100) {
res.append( chars[(v / 100) % 10] );
}
if (v % 100 != 0) {
res.append('.');
res.append( chars[(v / 10) % 10] );
if (v % 10 != 0) {
res.append( chars[v % 10] );
}
}
return res.toString();
}
} else {
d += 0.5;
long v = (long) d;
if (negative)
return "-" + Long.toString(v);
else
return Long.toString(v);
}
}
/**
* Sets the size to zero.
*/
public void reset() {
count = 0;
}
/**
* Creates a newly allocated byte array. Its size is the current
* size of this output stream and the valid contents of the buffer
* have been copied into it.
*
* @return the current contents of this output stream, as a byte array.
*/
public byte[] toByteArray() {
byte newbuf[] = new byte[count];
System.arraycopy(buf, 0, newbuf, 0, count);
return newbuf;
}
/**
* Returns the current size of the buffer.
*
* @return the value of the count
field, which is the number of valid bytes in this byte buffer.
*/
public int size() {
return count;
}
public void setSize(int size) {
if (size > count || size < 0)
throw new IndexOutOfBoundsException(MessageLocalization.getComposedMessage("the.new.size.must.be.positive.and.lt.eq.of.the.current.size"));
count = size;
}
/**
* Converts the buffer's contents into a string, translating bytes into
* characters according to the platform's default character encoding.
*
* @return String translated from the buffer's contents.
*/
@Override
public String toString() {
return new String(buf, 0, count);
}
/**
* Converts the buffer's contents into a string, translating bytes into
* characters according to the specified character encoding.
*
* @param enc a character-encoding name.
* @return String translated from the buffer's contents.
* @throws UnsupportedEncodingException
* If the named encoding is not supported.
*/
public String toString(String enc) throws UnsupportedEncodingException {
return new String(buf, 0, count, enc);
}
/**
* Writes the complete contents of this byte buffer output to
* the specified output stream argument, as if by calling the output
* stream's write method using out.write(buf, 0, count)
.
*
* @param out the output stream to which to write the data.
* @exception IOException if an I/O error occurs.
*/
public void writeTo(OutputStream out) throws IOException {
out.write(buf, 0, count);
}
public void write(int b) throws IOException {
append((byte)b);
}
@Override
public void write(byte[] b, int off, int len) {
append(b, off, len);
}
public byte[] getBuffer() {
return buf;
}
}