Skip to content

Commit 73fdc67

Browse files
committed
[ADD] TTFDataStream.createSubView() to create a subview without copying arrays
[ADD] RandomAccessReadUncachedDataStream that doesn't read input stream to byte[]
1 parent 51d91bd commit 73fdc67

File tree

6 files changed

+294
-1
lines changed

6 files changed

+294
-1
lines changed

fontbox/src/main/java/org/apache/fontbox/ttf/NameRecord.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ void initData( TrueTypeFont ttf, TTFDataStream data ) throws IOException
180180
*
181181
* @return A string for this class.
182182
*/
183+
@Override
183184
public String toString()
184185
{
185186
return

fontbox/src/main/java/org/apache/fontbox/ttf/RandomAccessReadDataStream.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
import java.io.ByteArrayInputStream;
2020
import java.io.IOException;
2121
import java.io.InputStream;
22+
import java.util.logging.Level;
23+
import java.util.logging.Logger;
2224

2325
import org.apache.pdfbox.io.IOUtils;
2426
import org.apache.pdfbox.io.RandomAccessRead;
27+
import org.apache.pdfbox.io.RandomAccessReadBuffer;
2528

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

180+
@Override
181+
public RandomAccessRead createSubView(long length)
182+
{
183+
try
184+
{
185+
return new RandomAccessReadBuffer(data).createView(currentPosition, length);
186+
}
187+
catch (IOException ex)
188+
{
189+
Logger.getLogger(RandomAccessReadDataStream.class.getName()).log(Level.SEVERE, null, ex);
190+
return null;
191+
}
192+
}
193+
177194
/**
178195
* {@inheritDoc}
179196
*/
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.fontbox.ttf;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import org.apache.pdfbox.io.RandomAccessRead;
22+
import org.apache.pdfbox.io.RandomAccessReadView;
23+
24+
/**
25+
* In contrast to {@link RandomAccessReadDataStream},
26+
* this class doesn't pre-load {@code RandomAccessRead} into a {@code byte[]},
27+
* it works with {@link RandomAccessRead} directly.
28+
*
29+
* Performance: it is much faster if most of the buffer is skipped, and slower if whole buffer is read()
30+
*/
31+
class RandomAccessReadUnbufferedDataStream extends TTFDataStream
32+
{
33+
private final long length;
34+
private final RandomAccessRead randomAccessRead;
35+
36+
/**
37+
* @throws IOException If there is a problem reading the source length.
38+
*/
39+
RandomAccessReadUnbufferedDataStream(RandomAccessRead randomAccessRead) throws IOException
40+
{
41+
this.length = randomAccessRead.length();
42+
this.randomAccessRead = randomAccessRead;
43+
}
44+
45+
/**
46+
* {@inheritDoc}
47+
*/
48+
@Override
49+
public long getCurrentPosition() throws IOException
50+
{
51+
return randomAccessRead.getPosition();
52+
}
53+
54+
/**
55+
* Close the underlying resources.
56+
*
57+
* @throws IOException If there is an error closing the resources.
58+
*/
59+
@Override
60+
public void close() throws IOException
61+
{
62+
randomAccessRead.close();
63+
}
64+
65+
/**
66+
* {@inheritDoc}
67+
*/
68+
@Override
69+
public int read() throws IOException
70+
{
71+
return randomAccessRead.read();
72+
}
73+
74+
/**
75+
* {@inheritDoc}
76+
*/
77+
@Override
78+
public final long readLong() throws IOException
79+
{
80+
return ((long) readInt() << 32) | (readInt() & 0xFFFFFFFFL);
81+
}
82+
83+
/**
84+
* {@inheritDoc}
85+
*/
86+
private int readInt() throws IOException
87+
{
88+
int b1 = read();
89+
int b2 = read();
90+
int b3 = read();
91+
int b4 = read();
92+
return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4;
93+
}
94+
95+
/**
96+
* {@inheritDoc}
97+
*/
98+
@Override
99+
public void seek(long pos) throws IOException
100+
{
101+
randomAccessRead.seek(pos);
102+
}
103+
104+
/**
105+
* {@inheritDoc}
106+
*/
107+
@Override
108+
public int read(byte[] b, int off, int len) throws IOException
109+
{
110+
randomAccessRead.readExact(b, off, len);
111+
return len;
112+
}
113+
114+
/**
115+
* Lifetime of returned InputStream is bound by {@code this} lifetime, it won't close underlying {@code RandomAccessRead}.
116+
*
117+
* {@inheritDoc}
118+
*/
119+
@Override
120+
public InputStream getOriginalData() throws IOException
121+
{
122+
return new RandomAccessReadNonClosingInputStream(randomAccessRead.createView(0, length));
123+
}
124+
125+
/**
126+
* {@inheritDoc}
127+
*/
128+
@Override
129+
public long getOriginalDataSize()
130+
{
131+
return length;
132+
}
133+
134+
@Override
135+
public RandomAccessRead createSubView(long length)
136+
{
137+
try
138+
{
139+
return randomAccessRead.createView(randomAccessRead.getPosition(), length);
140+
}
141+
catch (IOException ex)
142+
{
143+
assert false : "Please implement " + randomAccessRead.getClass() + ".createView()";
144+
return null;
145+
}
146+
}
147+
148+
private static final class RandomAccessReadNonClosingInputStream extends InputStream {
149+
150+
private final RandomAccessReadView randomAccessRead;
151+
152+
public RandomAccessReadNonClosingInputStream(RandomAccessReadView randomAccessRead)
153+
{
154+
this.randomAccessRead = randomAccessRead;
155+
}
156+
157+
@Override
158+
public int read() throws IOException
159+
{
160+
return randomAccessRead.read();
161+
}
162+
163+
@Override
164+
public int read(byte[] b) throws IOException
165+
{
166+
return randomAccessRead.read(b);
167+
}
168+
169+
@Override
170+
public int read(byte[] b, int off, int len) throws IOException
171+
{
172+
return randomAccessRead.read(b, off, len);
173+
}
174+
175+
@Override
176+
public long skip(long n) throws IOException
177+
{
178+
randomAccessRead.seek(randomAccessRead.getPosition() + n);
179+
return n;
180+
}
181+
182+
@Override
183+
public void close() throws IOException {
184+
// WARNING: .close() will close RandomAccessReadMemoryMappedFile if this View was based on it
185+
// randomAccessRead.close();
186+
}
187+
}
188+
}

fontbox/src/main/java/org/apache/fontbox/ttf/TTCDataStream.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.io.IOException;
2121
import java.io.InputStream;
22+
import org.apache.pdfbox.io.RandomAccessRead;
2223

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

87+
@Override
88+
public RandomAccessRead createSubView(long length)
89+
{
90+
return stream.createSubView(length);
91+
}
8692
}

fontbox/src/main/java/org/apache/fontbox/ttf/TTFDataStream.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.nio.charset.StandardCharsets;
2525
import java.util.Calendar;
2626
import java.util.TimeZone;
27+
import org.apache.pdfbox.io.RandomAccessRead;
2728

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

282+
/**
283+
* Creates a view from current position to {@code pos + length}.
284+
* It can be faster than {@code read(length)} if you only need a few bytes.
285+
* {@code SubView.close()} should never close {@code TTFDataStream.this}, only itself.
286+
*
287+
* @return A view or null (caller can use {@link #read} instead). Please close() the result
288+
*/
289+
public /*@Nullable*/ RandomAccessRead createSubView(long length) {
290+
return null;
291+
}
292+
281293
/**
282294
* Get the current position in the stream.
283295
*

io/src/main/java/org/apache/pdfbox/io/RandomAccessRead.java

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,76 @@ default int read(byte[] b) throws IOException
5555
* @throws IOException If there was an error while reading the data.
5656
*/
5757
int read(byte[] b, int offset, int length) throws IOException;
58-
58+
59+
/**
60+
* Read a buffer of data of exactly {@code length} bytes.
61+
*
62+
* @throws IOException if less than {@code length} bytes are available
63+
*/
64+
default byte[] readExact(byte[] b, int offset, int length) throws IOException
65+
{
66+
if (length() - getPosition() >= length)
67+
{
68+
int read = readUpTo(b, offset, length);
69+
if (read == length)
70+
{
71+
return b;
72+
}
73+
rewind(read);
74+
}
75+
throw new IOException("End-of-data");
76+
}
77+
78+
/**
79+
* Read a buffer of data of exactly {@code length} bytes.
80+
*
81+
* @throws IOException if less than {@code length} bytes are available
82+
*/
83+
default byte[] readExact(int length) throws IOException
84+
{
85+
return readExact(new byte[length], 0, length);
86+
}
87+
88+
/**
89+
* Finishes when {@code length} bytes are read, or EOF. Always returns {@code result}, never trims.
90+
* @see InputStream#readNBytes(byte[], int, int)
91+
* @return when {@code result.length} bytes are read, or EOF
92+
*/
93+
default int readUpTo(byte[] result) throws IOException
94+
{
95+
return readUpTo(result, 0, result.length);
96+
}
97+
98+
/**
99+
* Finishes when {@code length} bytes are read, or EOF. Just like {@link org.apache.pdfbox.io.IOUtils#populateBuffer(java.io.InputStream, byte[])}
100+
* @see InputStream#readNBytes(byte[], int, int)
101+
* @return amount of read bytes
102+
*/
103+
default int readUpTo(byte[] result, int offset, int length) throws IOException
104+
{
105+
if (Integer.MAX_VALUE - length < offset)
106+
{
107+
throw new IOException("Integer overflow");
108+
}
109+
int cursor = offset;
110+
int end = offset + length;
111+
while (cursor < end)
112+
{
113+
int read = read(result, cursor, end - cursor);
114+
if (read < 0)
115+
{
116+
break;
117+
}
118+
else if (read == 0)
119+
{
120+
// in order to not get stuck in a loop we check readBytes (this should never happen)
121+
throw new IOException("Read 0 bytes, risk of an infinite loop");
122+
}
123+
cursor += read;
124+
}
125+
return cursor - offset;
126+
}
127+
59128
/**
60129
* Returns offset of next byte to be returned by a read method.
61130
*

0 commit comments

Comments
 (0)