Skip to content

Commit 2bd40b9

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 37c6266 commit 2bd40b9

File tree

5 files changed

+246
-6
lines changed

5 files changed

+246
-6
lines changed

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

Lines changed: 19 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 org.apache.commons.logging.Log;
23+
import org.apache.commons.logging.LogFactory;
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.
@@ -30,6 +33,8 @@
3033
*/
3134
class RandomAccessReadDataStream extends TTFDataStream
3235
{
36+
private static final Log LOG = LogFactory.getLog(RandomAccessReadDataStream.class);
37+
3338
private final long length;
3439
private final byte[] data;
3540
private int currentPosition = 0;
@@ -174,6 +179,20 @@ public int read(byte[] b, int off, int len) throws IOException
174179
return bytesToRead;
175180
}
176181

182+
@Override
183+
public RandomAccessRead createSubView(long length)
184+
{
185+
try
186+
{
187+
return new RandomAccessReadBuffer(data).createView(currentPosition, length);
188+
}
189+
catch (IOException e)
190+
{
191+
LOG.warn("Could not create a SubView", e);
192+
return null;
193+
}
194+
}
195+
177196
/**
178197
* {@inheritDoc}
179198
*/
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.read(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.
@@ -279,6 +280,17 @@ public byte[] read(int numberOfBytes) throws IOException
279280
*/
280281
public abstract int read(byte[] b, int off, int len) throws IOException;
281282

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

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public class TrueTypeCollection implements Closeable
4747
*/
4848
public TrueTypeCollection(File file) throws IOException
4949
{
50-
this(new RandomAccessReadBufferedFile(file), true);
50+
this(new RandomAccessReadBufferedFile(file), true, true);
5151
}
5252

5353
/**
@@ -58,7 +58,7 @@ public TrueTypeCollection(File file) throws IOException
5858
*/
5959
public TrueTypeCollection(InputStream stream) throws IOException
6060
{
61-
this(new RandomAccessReadBuffer(stream), false);
61+
this(new RandomAccessReadBuffer(stream), false, true);
6262
}
6363

6464
/**
@@ -69,21 +69,24 @@ public TrueTypeCollection(InputStream stream) throws IOException
6969
*/
7070
TrueTypeCollection(RandomAccessRead randomAccessRead) throws IOException
7171
{
72-
this(randomAccessRead, false);
72+
this(randomAccessRead, false, true);
7373
}
7474

7575
/**
7676
* Creates a new TrueTypeCollection from a RandomAccessRead.
7777
*
7878
* @param randomAccessRead
7979
* @param closeAfterReading {@code true} to close randomAccessRead
80+
* @param buffered {@code true} to use {@link RandomAccessReadDataStream}, {@code false} to use {@link RandomAccessReadUnbufferedDataStream}
8081
* @throws IOException If the font could not be parsed.
8182
*/
82-
private TrueTypeCollection(RandomAccessRead randomAccessRead, boolean closeAfterReading) throws IOException
83+
private TrueTypeCollection(RandomAccessRead randomAccessRead, boolean closeAfterReading, boolean buffered) throws IOException
8384
{
8485
try
8586
{
86-
this.stream = new RandomAccessReadDataStream(randomAccessRead);
87+
this.stream = buffered
88+
? new RandomAccessReadDataStream(randomAccessRead)
89+
: new RandomAccessReadUnbufferedDataStream(randomAccessRead);
8790
}
8891
finally
8992
{
@@ -118,7 +121,19 @@ private TrueTypeCollection(RandomAccessRead randomAccessRead, boolean closeAfter
118121
int ulDsigOffset = stream.readUnsignedShort();
119122
}
120123
}
121-
124+
125+
/**
126+
* Creates a new TrueTypeCollection without reading the whole .ttc file.
127+
* It is important to {@link #close()} the resulting {@link TrueTypeCollection} to dispose the FileChannel.
128+
*
129+
* @param file The TTC file.
130+
* @throws IOException If the font could not be parsed.
131+
*/
132+
public static TrueTypeCollection createUnbuffered(File file) throws IOException
133+
{
134+
return new TrueTypeCollection(new RandomAccessReadBufferedFile(file), false, false);
135+
}
136+
122137
/**
123138
* Run the callback for each TT font in the collection.
124139
*

0 commit comments

Comments
 (0)