Skip to content

parse a TrueType font from a ByteBuffer without copying - #538

Open
peteraisher wants to merge 1 commit into
apache:trunkfrom
peteraisher:tech/ttf-shared-buffer-trunk
Open

peteraisher wants to merge 1 commit into
apache:trunkfrom
peteraisher:tech/ttf-shared-buffer-trunk

Conversation

@peteraisher

Copy link
Copy Markdown

parse a TrueType font from a ByteBuffer without copying it

What

Adds TTFParser.parse(ByteBuffer), which reads the font in place. Each font receives its own slice of the buffer, so multiple fonts can be parsed from one buffer and used in different threads.

GlyphTable now has a view of its part of the font data. Before, it copied glyf twice. Streams that aren't in memory (e.g. a TTC over a file) still copy, but only once.

RandomAccessReadDataStream holds a ByteBuffer instead of a byte[] and an int position. Behaviour is otherwise unchanged.

Arial Unicode MS (23.3 MB, glyf 22.4 MB), memory allocated per call:

before after
parse(RandomAccessRead) 72.2 MB 27.4 MB
parse(ByteBuffer) n/a 4.1 MB
PDType0Font.load + one page saved 79.3 MB 11.3 MB

Why

Reduce memory usage while enabling safe resource sharing across threads.

With a cheap parse, each document can have its own font instead of sharing one (PDFBOX-4220, PDFBOX-5779).

Safety

Tests: TestTTFParser.testParseByteBuffer and RandomAccessReadBufferDataStreamTest.testCreateSubStream. The fontbox and pdfbox module tests pass on 3.0 and trunk.

LLM usage disclaimer

Claude Opus 5.5 was used during development. The code submitted represents standard usage of the ByteBuffer interface. I would expect (and hope) that any code making use of this interface, proprietary or otherwise, would look similar, and am confident that this doesn't present a copyright risk.

… don't copy glyf twice

TTFParser.parse(ByteBuffer) reads the font in place, so many fonts can be
parsed from one buffer, each with its own position. GlyphTable now keeps a
view of its part of the font data instead of copying it twice.

Generated-by: Claude Code (Claude Opus 5.5)
@peteraisher

Copy link
Copy Markdown
Author

I'm happy to open a separate PR against 3.0 if that's helpful. The patch was tested against trunk and 3.0.

@THausherr

Copy link
Copy Markdown
Contributor

How does this affect / improve the rest of pdfbox / is there a change that we should do in the main pdfbox? Or is this something that you need for your own project?

@peteraisher

Copy link
Copy Markdown
Author

In my opinion it's both. We will use this patch anyway — sharing here since it seems likely to benefit others.

TL;DR:

Previous options:

  • share font resource, no access serialisation -> fast and low memory usage, potentially unsafe under load
  • share font resource, serialise access -> safe and low memory usage, potentially slow under load
  • one font resource per thread -> fast and safe, high memory usage

New option:

  • one font resource per thread, share underlying memory -> fast, safe, low memory usage

Right now, people using pdfbox across multiple threads (us included) have to choose between resource duplication or access serialisation if they want a strong guarantee against data corruption when multiple threads use a single font.
Reads are mutating in a potentially non-safe way, since they involve seeking, and the seek state is shared across threads and happens behind different locks.

To get an independent seek state currently requires copying the underlying byte[] too, but since all the accesses are reads, there's nothing to stop the underlying storage being shared. Only the position needs to be separate.

Putting this change on main makes that choice between safety guarantee and resource sharing go away.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants