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 26, 2024
1 parent 3cfd7a4 commit bfddf39
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 5 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,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.read(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 @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class TrueTypeCollection implements Closeable
*/
public TrueTypeCollection(File file) throws IOException
{
this(new RandomAccessReadBufferedFile(file), true);
this(new RandomAccessReadBufferedFile(file), true, true);
}

/**
Expand All @@ -58,21 +58,24 @@ public TrueTypeCollection(File file) throws IOException
*/
public TrueTypeCollection(InputStream stream) throws IOException
{
this(new RandomAccessReadBuffer(stream), false);
this(new RandomAccessReadBuffer(stream), false, true);
}

/**
* Creates a new TrueTypeCollection from a RandomAccessRead.
*
* @param randomAccessRead
* @param closeAfterReading {@code true} to close randomAccessRead
* @param buffered {@code true} to use {@link RandomAccessReadDataStream}, {@code false} to use {@link RandomAccessReadUnbufferedDataStream}
* @throws IOException If the font could not be parsed.
*/
private TrueTypeCollection(RandomAccessRead randomAccessRead, boolean closeAfterReading) throws IOException
private TrueTypeCollection(RandomAccessRead randomAccessRead, boolean closeAfterReading, boolean buffered) throws IOException
{
try
{
this.stream = new RandomAccessReadDataStream(randomAccessRead);
this.stream = buffered
? new RandomAccessReadDataStream(randomAccessRead)
: new RandomAccessReadUnbufferedDataStream(randomAccessRead);
}
finally
{
Expand Down Expand Up @@ -107,7 +110,19 @@ private TrueTypeCollection(RandomAccessRead randomAccessRead, boolean closeAfter
int ulDsigOffset = stream.readUnsignedShort();
}
}


/**
* Creates a new TrueTypeCollection without reading the whole .ttc file.
* It is important to {@link #close()} the resulting {@link TrueTypeCollection} to dispose the FileChannel.
*
* @param file The TTC file.
* @throws IOException If the font could not be parsed.
*/
public static TrueTypeCollection createUnbuffered(File file) throws IOException
{
return new TrueTypeCollection(new RandomAccessReadBufferedFile(file), false, false);
}

/**
* Run the callback for each TT font in the collection.
*
Expand Down

0 comments on commit bfddf39

Please sign in to comment.