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 30, 2024
1 parent 2685fe9 commit cb29a4a
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 0 deletions.
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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

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 All @@ -30,6 +33,8 @@
*/
class RandomAccessReadDataStream extends TTFDataStream
{
private static final Log LOG = LogFactory.getLog(RandomAccessReadDataStream.class);

private final long length;
private final byte[] data;
private int currentPosition = 0;
Expand Down Expand Up @@ -174,6 +179,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 e)
{
LOG.warn("Could not create a SubView", e);
return null;
}
}

/**
* {@inheritDoc}
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/*
* 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
{
return randomAccessRead.read(b, off, 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 @@ -279,6 +280,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 RandomAccessRead createSubView(long length) {
return null;
}

/**
* Get the current position in the stream.
*
Expand Down

0 comments on commit cb29a4a

Please sign in to comment.