Summary
Stream Font Face .ufont files are cooked as serialized FFontFaceData, but the runtime Stream path passes the file directly to FreeType as raw font data.
What Type of Bug are you experiencing?
Foundation (C++ Tools, Profiling, & Pipeline)
Steps to Reproduce
- Create or use a Font Face asset with Loading Policy set to Stream.
- Use that Font Face from a composite font / sub-font configuration.
- Package the project.
- Run the packaged executable and display a glyph that must be loaded from the Stream Font Face.
Expected Result
The glyph is loaded from the Stream Font Face and rendered correctly while the font remains streamed rather than fully resident.
Observed Result
The glyph cannot be loaded correctly from the cooked Stream Font Face.
In UE 5.7.2, the cooked .ufont file is produced by:
Engine/Source/Runtime/Engine/Private/FontFace.cpp
UFontFace::CookAdditionalFilesOverride()
The current implementation serializes FFontFaceData for non-Inline Font Faces:
TArray<uint8> Data;
FMemoryWriter Ar(Data, true);
FontFaceData->Serialize(Ar);
const int32 NumBytes = Data.Num() * Data.GetTypeSize();
WriteAdditionalFile(*CookedFontFilename, (void*)Data.GetData(), NumBytes);
This means a Stream .ufont is written as serialized FFontFaceData.
However, the runtime Stream path in:
Engine/Source/Runtime/SlateCore/Private/Fonts/FontCacheCompositeFont.cpp
FCompositeFontCache::GetFontFace()
passes the .ufont filename directly to FFreeTypeFace:
case EFontLoadingPolicy::Stream:
{
FaceAndMemory = MakeShared<FFreeTypeFace>(
FTLibrary,
InFontData.GetFontFilename(),
InFontData.GetSubFaceIndex(),
InFontData.GetLayoutMethod());
}
break;
FFreeTypeFace then opens the file through FreeType streaming using FT_StreamRec / FT_Open_Face. The Stream path does not appear to deserialize the FFontFaceData wrapper before FreeType reads the file.
Affects Versions
5.7
Platform(s)
Windows
Additional Notes
The issue was isolated by replacing the cooked Stream .ufont contents with the raw font payload equivalent to FontFaceData->GetData(), while keeping the same asset and LoadingPolicy=Stream.
With the raw font payload:
CanLoadCodepoint succeeds.
The glyph renders correctly in the packaged executable.
Resident font bytes remain 0, so the font still behaves as a streamed font rather than becoming fully resident.
This suggests a producer / consumer format mismatch:
Cook:
Stream .ufont → serialized FFontFaceData
Runtime Stream path:
.ufont → passed to FreeType as raw font data
Two possible intended fixes seem plausible:
make the runtime Stream path understand the serialized FFontFaceData wrapper, or
keep serialized FFontFaceData for LazyLoad, while writing raw FontFaceData->GetData() bytes for Stream.