stride3d/stride · error · InvalidOperationException

Failed to load font ' ' (FreeType error )

Error message

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

What it means

After msdfgen loads the font, the importer reads the font bytes and asks FreeType (FT_New_Memory_Face) to create a face from memory; a non-zero FreeType error code means FreeType rejected the font data. The error message embeds the raw FreeType error code. This is a font-file validity problem detected by FreeType.

Solutions

  1. Decode the FreeType code (e.g. 2 = cannot open resource, 3 = unknown file format) to identify the issue.
  2. Validate the font with `ftdump`/`fonttools ttx` or a font viewer and replace it with a valid TTF/OTF.
  3. Check file size on disk vs. expected; ensure Git LFS files are checked out fully.
  4. Convert WOFF2 fonts to TTF before using them as FontSource.

Example fix

// before
FontSource = "fonts/MyFont.woff2", // FreeType error 3 (unknown format)
// after
FontSource = "fonts/MyFont.ttf", // converted with woff2_decompress / fonttools
Defensive patterns

Strategy: validation

Validate before calling

byte[] data = File.ReadAllBytes(fontPath.ToOSPath());
// TTF: 0x00010000 or 'true'; OTF/CFF: 'OTTO'
bool looksLikeFont = data.Length > 12 &&
    (BitConverter.ToUInt32(data, 0) == 0x00010000 ||
     data[0] == 'O' && data[1] == 'T' && data[2] == 'T' && data[3] == 'O');
if (!looksLikeFont) throw new InvalidDataException($"{fontPath} is not a TTF/OTF file");

Type guard

static bool IsTrueTypeOrOpenType(byte[] d) => d.Length >= 4 &&
    (d[0] == 0x00 && d[1] == 0x01) || (d[0] == 'O' && d[1] == 'T' && d[2] == 'T' && d[3] == 'O');

Try / catch

try
{
    CompileSdfFont(asset);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("FreeType error"))
{
    logger.Error($"FreeType rejected '{asset.FontSource}': {ex.Message}. Re-export as TTF/OTF.");
}

Prevention

When it happens

Trigger: Compiling an SDF spritefont where FT_New_Memory_Face returns an error such as 2 (Cannot Open Resource), 3 (Unknown File Format), or 85 (invalid table) for the bytes at fontSource.

Common situations: Font file truncated during version control or copy (LFS not pulled); actually a WOFF/WOFF2 or bitmap-only font renamed to .ttf; corrupted download; empty file committed accidentally.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at sources/engine/Stride.Assets/SpriteFont/Compiler/SignedDistanceFieldFontImporter.cs:79

                MsdfgenNative.msdfgenContextDestroy(msdfgenContext);
                msdfgenContext = IntPtr.Zero;
                FreeTypeNative.FT_Done_FreeType(library);
                throw new AssetException($"Failed to load font '{fontSource}' into msdfgen.");
            }

            try
            {
                var fontData = File.ReadAllBytes(fontSource);
                var handle = GCHandle.Alloc(fontData, GCHandleType.Pinned);

                try
                {
                    FT_FaceRec* face;
                    fixed (byte* ptr = fontData)
                    {
                        err = FreeTypeNative.FT_New_Memory_Face(library, ptr, new CLong(fontData.Length), new CLong(0), out face);
                        if (err != 0)
                            throw new InvalidOperationException($"Failed to load font '{fontSource}' (FreeType error {err})");
                    }

                    try
                    {
                        var fontSize = options.FontType.Size;

                        // FreeType metrics are in font units; convert to pixels
                        float unitsToPixels = fontSize / face->units_per_EM;

                        var lineGap = (face->height - face->ascender + face->descender) * options.LineGapFactor;
                        LineSpacing = (lineGap + face->ascender - face->descender) * unitsToPixels;
                        BaseLine = (lineGap * options.LineGapBaseLineFactor + face->ascender) * unitsToPixels;

                        var glyphList = new List<Glyph>();
                        foreach (var character in characters)
                            glyphList.Add(ImportGlyph(character, fontSize));

                        Glyphs = glyphList;

View on GitHub (pinned to 96fad776d2)