Skip to content

Commit

Permalink
[ADD] TTFDataStream.createSubView() to create a subview without copyi…
Browse files Browse the repository at this point in the history
…ng arrays

[ADD] RandomAccessReadUncachedDataStream that doesn't read input stream to byte[]
  • Loading branch information
bogdiuk committed Jun 18, 2024
1 parent 51d91bd commit 73fdc67
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ void initData( TrueTypeFont ttf, TTFDataStream data ) throws IOException
*
* @return A string for this class.
*/
@Override
public String toString()
{
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.io.RandomAccessRead;
import org.apache.pdfbox.io.RandomAccessReadBuffer;

/**
* An implementation of the TTFDataStream using RandomAccessRead as source.
Expand Down Expand Up @@ -174,6 +177,20 @@ public int read(byte[] b, int off, int len) throws IOException
return bytesToRead;
}

@Override
public RandomAccessRead createSubView(long length)
{
try
{
return new RandomAccessReadBuffer(data).createView(currentPosition, length);
}
catch (IOException ex)
{
Logger.getLogger(RandomAccessReadDataStream.class.getName()).log(Level.SEVERE, null, ex);
return null;
}
}

/**
* {@inheritDoc}
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* 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 org.apache.fontbox.ttf;

import java.io.IOException;
import java.io.InputStream;
import org.apache.pdfbox.io.RandomAccessRead;
import org.apache.pdfbox.io.RandomAccessReadView;

/**
* In contrast to {@link RandomAccessReadDataStream},
* this class doesn't pre-load {@code RandomAccessRead} into a {@code byte[]},
* it works with {@link RandomAccessRead} directly.
*
* Performance: it is much faster if most of the buffer is skipped, and slower if whole buffer is read()
*/
class RandomAccessReadUnbufferedDataStream extends TTFDataStream
{
private final long length;
private final RandomAccessRead randomAccessRead;

/**
* @throws IOException If there is a problem reading the source length.
*/
RandomAccessReadUnbufferedDataStream(RandomAccessRead randomAccessRead) throws IOException
{
this.length = randomAccessRead.length();
this.randomAccessRead = randomAccessRead;
}

/**
* {@inheritDoc}
*/
@Override
public long getCurrentPosition() throws IOException
{
return randomAccessRead.getPosition();
}

/**
* Close the underlying resources.
*
* @throws IOException If there is an error closing the resources.
*/
@Override
public void close() throws IOException
{
randomAccessRead.close();
}

/**
* {@inheritDoc}
*/
@Override
public int read() throws IOException
{
return randomAccessRead.read();
}

/**
* {@inheritDoc}
*/
@Override
public final long readLong() throws IOException
{
return ((long) readInt() << 32) | (readInt() & 0xFFFFFFFFL);
}

/**
* {@inheritDoc}
*/
private int readInt() throws IOException
{
int b1 = read();
int b2 = read();
int b3 = read();
int b4 = read();
return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4;
}

/**
* {@inheritDoc}
*/
@Override
public void seek(long pos) throws IOException
{
randomAccessRead.seek(pos);
}

/**
* {@inheritDoc}
*/
@Override
public int read(byte[] b, int off, int len) throws IOException
{
randomAccessRead.readExact(b, off, len);
return len;
}

/**
* Lifetime of returned InputStream is bound by {@code this} lifetime, it won't close underlying {@code RandomAccessRead}.
*
* {@inheritDoc}
*/
@Override
public InputStream getOriginalData() throws IOException
{
return new RandomAccessReadNonClosingInputStream(randomAccessRead.createView(0, length));
}

/**
* {@inheritDoc}
*/
@Override
public long getOriginalDataSize()
{
return length;
}

@Override
public RandomAccessRead createSubView(long length)
{
try
{
return randomAccessRead.createView(randomAccessRead.getPosition(), length);
}
catch (IOException ex)
{
assert false : "Please implement " + randomAccessRead.getClass() + ".createView()";
return null;
}
}

private static final class RandomAccessReadNonClosingInputStream extends InputStream {

private final RandomAccessReadView randomAccessRead;

public RandomAccessReadNonClosingInputStream(RandomAccessReadView randomAccessRead)
{
this.randomAccessRead = randomAccessRead;
}

@Override
public int read() throws IOException
{
return randomAccessRead.read();
}

@Override
public int read(byte[] b) throws IOException
{
return randomAccessRead.read(b);
}

@Override
public int read(byte[] b, int off, int len) throws IOException
{
return randomAccessRead.read(b, off, len);
}

@Override
public long skip(long n) throws IOException
{
randomAccessRead.seek(randomAccessRead.getPosition() + n);
return n;
}

@Override
public void close() throws IOException {
// WARNING: .close() will close RandomAccessReadMemoryMappedFile if this View was based on it
// randomAccessRead.close();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import java.io.IOException;
import java.io.InputStream;
import org.apache.pdfbox.io.RandomAccessRead;

/**
* A wrapper for a TTF stream inside a TTC file, does not close the underlying shared stream.
Expand Down Expand Up @@ -83,4 +84,9 @@ public long getOriginalDataSize()
return stream.getOriginalDataSize();
}

@Override
public RandomAccessRead createSubView(long length)
{
return stream.createSubView(length);
}
}
12 changes: 12 additions & 0 deletions fontbox/src/main/java/org/apache/fontbox/ttf/TTFDataStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
import java.util.TimeZone;
import org.apache.pdfbox.io.RandomAccessRead;

/**
* An abstract class to read a data stream.
Expand Down Expand Up @@ -278,6 +279,17 @@ public byte[] read(int numberOfBytes) throws IOException
*/
public abstract int read(byte[] b, int off, int len) throws IOException;

/**
* Creates a view from current position to {@code pos + length}.
* It can be faster than {@code read(length)} if you only need a few bytes.
* {@code SubView.close()} should never close {@code TTFDataStream.this}, only itself.
*
* @return A view or null (caller can use {@link #read} instead). Please close() the result
*/
public /*@Nullable*/ RandomAccessRead createSubView(long length) {
return null;
}

/**
* Get the current position in the stream.
*
Expand Down
71 changes: 70 additions & 1 deletion io/src/main/java/org/apache/pdfbox/io/RandomAccessRead.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,76 @@ default int read(byte[] b) throws IOException
* @throws IOException If there was an error while reading the data.
*/
int read(byte[] b, int offset, int length) throws IOException;


/**
* Read a buffer of data of exactly {@code length} bytes.
*
* @throws IOException if less than {@code length} bytes are available
*/
default byte[] readExact(byte[] b, int offset, int length) throws IOException
{
if (length() - getPosition() >= length)
{
int read = readUpTo(b, offset, length);
if (read == length)
{
return b;
}
rewind(read);
}
throw new IOException("End-of-data");
}

/**
* Read a buffer of data of exactly {@code length} bytes.
*
* @throws IOException if less than {@code length} bytes are available
*/
default byte[] readExact(int length) throws IOException
{
return readExact(new byte[length], 0, length);
}

/**
* Finishes when {@code length} bytes are read, or EOF. Always returns {@code result}, never trims.
* @see InputStream#readNBytes(byte[], int, int)
* @return when {@code result.length} bytes are read, or EOF
*/
default int readUpTo(byte[] result) throws IOException
{
return readUpTo(result, 0, result.length);
}

/**
* Finishes when {@code length} bytes are read, or EOF. Just like {@link org.apache.pdfbox.io.IOUtils#populateBuffer(java.io.InputStream, byte[])}
* @see InputStream#readNBytes(byte[], int, int)
* @return amount of read bytes
*/
default int readUpTo(byte[] result, int offset, int length) throws IOException
{
if (Integer.MAX_VALUE - length < offset)
{
throw new IOException("Integer overflow");
}
int cursor = offset;
int end = offset + length;
while (cursor < end)
{
int read = read(result, cursor, end - cursor);
if (read < 0)
{
break;
}
else if (read == 0)
{
// in order to not get stuck in a loop we check readBytes (this should never happen)
throw new IOException("Read 0 bytes, risk of an infinite loop");
}
cursor += read;
}
return cursor - offset;
}

/**
* Returns offset of next byte to be returned by a read method.
*
Expand Down

0 comments on commit 73fdc67

Please sign in to comment.