Repository navigation
Conversation
RawTable copies every table out of the Resource into a new buffer, and a font.Font keeps the tables it parses. For a color emoji font this is most of the file: the 'sbix' table of macOS Apple Color Emoji.ttc is 182MB, so NewFont on it keeps 193MB on the Go heap. NewLoadersFromBytes builds the loaders from a []byte and returns each uncompressed table as a sub-slice of it, with its capacity limited to the table end. Compressed WOFF tables are still decoded into a new buffer. The input can be a read-only memory mapping, so the OS pages the tables in as they are used and can drop them again under memory pressure. The dst argument of RawTableTo is not used by such a loader: it may be a table returned before, which is then a view of the input. Measured on Apple Color Emoji.ttc, face 0, heap after GC with the Font alive: os.File + NewLoaders +193MB; mmap + NewLoadersFromBytes +11MB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestParseCrashers already feeds random input to NewLoaders, which NewLoadersFromBytes calls. The loop also used math/rand.Read, which staticcheck flags (SA1019). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…table at EOF - Doc: the whole input stays reachable while any loader, font or data derived from them is in use; it must not be modified or unmapped; a returned table must never be passed as dst to a copying loader. - An empty table at offset == len(data) is now rejected, as ReadAt does on the copying path. - Tests: a compressed WOFF table with a view of the input as dst (fails without the dst guard); truncated input must accept and reject the same tables as NewLoaders; empty table at EOF. The old dst test is removed: it passed without the guard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Hello, While I sympathize with the issue, I'm not sure I like the proposed solution that much. It seems the issue is that Could we instead introduce an interface extending Then, the new interface is easy to implement if you already have the whole resource as a byte slice. |
|
Thanks, that is cleaner. I'll rework it as an optional interface checked in
On the slice path, |
Yes, returning an error should be fine. I'm not sure about exporting a concrete type wrapping a slice. Maybe that would benefit other users who already have the full slice "in memory" ? |
|
Thanks. I'll add |
As suggested in review, the zero-copy path is now an optional interface checked in findTableBuffer, and not a separate constructor: - ResourceSlicer is a Resource with Slice(start, end int64) ([]byte, error). When the Resource given to NewLoader(s) implements it, uncompressed tables are returned as the slice from Slice, capped at the table end. dst is not used, since a previous result can be a view of read-only memory. - NewSliceResource([]byte) wraps a bytes.Reader for callers that already have the whole file in memory, or as a memory mapping. - A Slice error, or a slice of the wrong length, is returned as an error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@benoitkugler The rework is pushed (b157558):
The heap result is the same as before: +11 MB with an mmap of Apple Color Emoji, against +193 MB with an |
|
The It stops before it checks any code. Other branches fail the same way, for example Changing the pin in |
Loader.RawTablecopies every table out of theResourceinto a new buffer, and afont.Fontkeeps the tables it parses. For a color emoji font that is most of the file. Thesbixtable of macOSApple Color Emoji.ttcis 182 MB, soNewFonton it keeps 193 MB on the Go heap. A terminal that falls back to this font for one emoji grows by that much.As suggested in review, this PR adds an optional interface, checked in
findTableBuffer:When the
Resourcegiven toNewLoaderorNewLoadersis aResourceSlicer, each uncompressed table is the slice thatSlicereturns. It is not copied.NewSliceResourcewraps abytes.Readerfor callers that have the whole file in memory. The data can also be a read-only memory mapping of the file. The OS then pages the tables in as they are used, and can drop the pages again under memory pressure.Slicereturns an error, or a slice of the wrong length,RawTablereturns an error.dstinRawTableTois not used.dstcan be a table that an earlier call returned (NewFontandfontscanpass the previous buffer back), and that table is now a view of read-only memory.NewSliceResourcerejects the same table ranges asbytes.Reader.ReadAt, so both paths accept the same fonts.Resourcethat does not implementSliceworks as before.I checked that nothing in
fontortableswrites into a table buffer. The only write found, inKernData1.parseValues, goes to a[4]bytearray field.Measured
Apple Color Emoji.ttc, face 0, heap after
runtime.GC()with theFontalive:os.Filesyscall.Mmap+NewSliceResourceU+1F600still decodes as a PNGGlyphBitmap.Tests
commonandcollectionsfont (this includes a compressed WOFF) reads back the same as from abytes.Reader.cap == len.RawTableTowith the previous table asdstleaves the input unchanged.Sliceerror, or a short slice, from an outside implementation is returned as an error.font,sbixglyph data from the Sbix toy fonts is the same as from abytes.Reader, and it points into the input.go test ./...,go vetandstaticcheckv0.8.2 are clean.🤖 Generated with Claude Code