stride3d/stride · error · InvalidOperationException

Failed to load font from

Error message

Failed to load font from '{filePath}' (FreeType error {err})

What it means

LoadFontFromFileSystem reads the font file into memory and asks FreeType to parse it via FT_New_Memory_Face. A nonzero error means FreeType could not parse the data as a supported font format, so an InvalidOperationException is thrown with the file path and FreeType error code.

Solutions

  1. Validate the file is a real TrueType/OpenType font (check magic bytes 0x00010000 / 'OTTO') before loading
  2. Re-copy the font file from a known-good source; check file size > 0
  3. Decode the FreeType error code in the message (e.g. 2 = cannot open resource, 3 = unknown file format) to target the fix
  4. Ensure the deployment pipeline copies font files verbatim (no encoding/line-ending transforms)

Example fix

// before
fontManager.LoadFontFromFileSystem(name, suspiciousPath, style);
// after
var bytes = File.ReadAllBytes(suspiciousPath);
if (bytes.Length < 12)
    throw new InvalidDataException($"Font file too small or corrupt: {suspiciousPath}");
fontManager.LoadFontFromFileSystem(name, suspiciousPath, style);
Defensive patterns

Strategy: validation

Validate before calling

var data = File.ReadAllBytes(filePath);
if (data.Length < 12) throw new InvalidDataException($"Not a font file: {filePath}");
bool validMagic = data[0]==0 && data[1]==1 && data[2]==0 && data[3]==0
               || data[0]==(byte)'O' && data[1]==(byte)'T' && data[2]==(byte)'T' && data[3]==(byte)'O';

Try / catch

try { fontManager.LoadFontFromFileSystem(name, path, style); }
catch (InvalidOperationException ex) when (ex.Message.Contains("FreeType error"))
{
    log.Error($"Font load failed for {path}; falling back to default font", ex);
    LoadDefaultFont();
}

Prevention

When it happens

Trigger: Calling LoadFontFromFileSystem with a file that exists but is not a valid font (corrupted download, truncated file, wrong extension, unsupported format like a bitmap-only font).

Common situations: Deploying a placeholder/zero-byte .ttf; fonts corrupted by source-control line-ending mangling or incomplete copy; pointing at an HTML error page saved as .ttf; font format not compiled into the shipped FreeType build.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/25e4fc1cb11ca87d. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.Graphics/Font/FontManager.cs:162

        public void LoadFontFromFileSystem(string fontName, string filePath, FontStyle style)
        {
            var cacheKey = FontHelper.GetFontPath(fontName, style);

            lock (freetypeLock)
            {
                if (cachedFontFaces.ContainsKey(cacheKey))
                    return;

                var fontData = File.ReadAllBytes(filePath);
                var handle = GCHandle.Alloc(fontData, GCHandleType.Pinned);

                fixed (byte* ptr = fontData)
                {
                    int err = FreeTypeNative.FT_New_Memory_Face(freetypeLibrary, ptr, new CLong(fontData.Length), new CLong(0), out FT_FaceRec* face);
                    if (err != 0)
                    {
                        handle.Free();
                        throw new InvalidOperationException($"Failed to load font from '{filePath}' (FreeType error {err})");
                    }
                    cachedFontFaces[cacheKey] = ((nint)face, handle);
                }
            }
        }

        private void GenerateCharacterGlyph(CharacterSpecification character, bool renderBitmap, bool computeMetrics)
        {
            // Reset and (re)compute Glyph.XAdvance only on the metric pass. The async builder thread
            // re-renders the bitmap of an already-measured glyph and must leave XAdvance untouched:
            // text measurement reads it lock-free while the bitmap is still pending, so zeroing it
            // here — even transiently — makes the last glyph on a line measure as zero-width and the
            // UI background fall short of the text.
            if (computeMetrics)
                ResetGlyph(character);

            // let the glyph info null if the size is not valid
            if (character.Size.X < 1 || character.Size.Y < 1)

View on GitHub (pinned to 96fad776d2)