@@ -58,6 +58,14 @@ public class CustomSetPropertyOnRenderable
5858 public static IRuntimeFontService ? FontService { get ; set ; }
5959#endif
6060
61+ /// <summary>
62+ /// Optional in-memory font creator. When set, font generation bypasses disk entirely —
63+ /// the creator produces a <see cref="BitmapFont"/> directly from raw pixel data and
64+ /// .fnt metadata. If null or if creation fails, falls back to the disk-based
65+ /// <see cref="FontService"/> path.
66+ /// </summary>
67+ public static IInMemoryFontCreator ? InMemoryFontCreator { get ; set ; }
68+
6169 public static event Action < string > ? PropertyAssignmentError ;
6270
6371 /// <summary>
@@ -1086,23 +1094,9 @@ BitmapFont GetAndCreateFontIfNecessary()
10861094 // no cache, does it need to be created?
10871095 if ( font == null )
10881096 {
1089- // this could be a custom font, so let's see if it exists:
1090-
1091- string fileName = String . Empty ;
1092- if ( ToolsUtilities . FileManager . FileExists ( fontFileName ) )
1093- {
1094- fileName = fontFileName ;
1095- }
1096- #if ! FRB
1097- else if ( FontService ! = null )
1098- {
1099- fileName = FontService . AbsoluteFontCacheFolder +
1100- ToolsUtilities . FileManager . RemovePath ( fontFileName ) ;
1101- }
1102-
1103- if ( FontService != null && ! ToolsUtilities . FileManager . FileExists ( fileName ) )
1097+ // Try in-memory font creation first (no disk I/O)
1098+ if ( InMemoryFontCreator != null )
11041099 {
1105- // user could have typed anything in there, so who knows if this will succeed. Therefore, try/catch:
11061100 try
11071101 {
11081102 BmfcSave bmfcSave = new BmfcSave ( ) ;
@@ -1112,35 +1106,83 @@ BitmapFont GetAndCreateFontIfNecessary()
11121106 bmfcSave . UseSmoothing = useFontSmoothingStack . Peek ( ) ;
11131107 bmfcSave . IsItalic = isItalicStack . Peek ( ) ;
11141108 bmfcSave . IsBold = isBoldStack . Peek ( ) ;
1115- #if ! FRB
1116- // BBCode inline font creation: when BBCode tags like [FontSize=24] reference a font
1117- // that doesn't exist, create it on demand. This parallels the font creation in
1118- // UpdateToFontValues — both use FontService.CreateFontIfNecessary with the same pattern.
1109+
11191110 var gumProject = ObjectFinder . Self . GumProjectSave ;
11201111 bmfcSave . Ranges = gumProject ? . FontRanges ?? BmfcSave . DefaultRanges ;
11211112 bmfcSave . SpacingHorizontal = gumProject ? . FontSpacingHorizontal ?? 1 ;
11221113 bmfcSave . SpacingVertical = gumProject ? . FontSpacingVertical ?? 1 ;
1123- #endif
11241114
1125- FontService . CreateFontIfNecessary ( bmfcSave ) ;
1115+ font = InMemoryFontCreator . TryCreateFont ( bmfcSave ) ;
1116+ if ( font != null )
1117+ {
1118+ global ::RenderingLibrary . Content . LoaderManager . Self . AddDisposable ( fontFileName , font ) ;
1119+ }
11261120 }
11271121 catch
11281122 {
1129- // do nothing?
1123+ // Fall through to disk-based path
11301124 }
11311125 }
1132- #endif
11331126
1134- if ( ToolsUtilities . FileManager . FileExists ( fileName ) )
1135- {
1136- font = new BitmapFont ( fileName ) ;
1137- }
1138- else
1127+ // Fall back to disk-based font creation
1128+ if ( font == null )
11391129 {
1140- // This can happen when closing tags are encountered at the end of a font. If no font exists, we can just go to the default
1141- font = Text . DefaultBitmapFont ;
1130+ // this could be a custom font, so let's see if it exists:
1131+
1132+ string fileName = String . Empty ;
1133+ if ( ToolsUtilities . FileManager . FileExists ( fontFileName ) )
1134+ {
1135+ fileName = fontFileName ;
1136+ }
1137+ #if ! FRB
1138+ else if ( FontService ! = null )
1139+ {
1140+ fileName = FontService . AbsoluteFontCacheFolder +
1141+ ToolsUtilities . FileManager . RemovePath ( fontFileName ) ;
1142+ }
1143+
1144+ if ( FontService != null && ! ToolsUtilities . FileManager . FileExists ( fileName ) )
1145+ {
1146+ // user could have typed anything in there, so who knows if this will succeed. Therefore, try/catch:
1147+ try
1148+ {
1149+ BmfcSave bmfcSave = new BmfcSave ( ) ;
1150+ bmfcSave . FontSize = fontSizeStack . Peek ( ) ;
1151+ bmfcSave . FontName = fontNameStack . Peek ( ) ;
1152+ bmfcSave . OutlineThickness = outlineThicknessStack . Peek ( ) ;
1153+ bmfcSave . UseSmoothing = useFontSmoothingStack . Peek ( ) ;
1154+ bmfcSave . IsItalic = isItalicStack . Peek ( ) ;
1155+ bmfcSave . IsBold = isBoldStack . Peek ( ) ;
1156+ #if ! FRB
1157+ // BBCode inline font creation: when BBCode tags like [FontSize=24] reference a font
1158+ // that doesn't exist, create it on demand. This parallels the font creation in
1159+ // UpdateToFontValues — both use FontService.CreateFontIfNecessary with the same pattern.
1160+ var gumProject = ObjectFinder . Self . GumProjectSave ;
1161+ bmfcSave . Ranges = gumProject ? . FontRanges ?? BmfcSave . DefaultRanges ;
1162+ bmfcSave . SpacingHorizontal = gumProject ? . FontSpacingHorizontal ?? 1 ;
1163+ bmfcSave . SpacingVertical = gumProject ? . FontSpacingVertical ?? 1 ;
1164+ #endif
1165+
1166+ FontService . CreateFontIfNecessary ( bmfcSave ) ;
1167+ }
1168+ catch
1169+ {
1170+ // do nothing?
1171+ }
1172+ }
1173+ #endif
1174+
1175+ if ( ToolsUtilities . FileManager . FileExists ( fileName ) )
1176+ {
1177+ font = new BitmapFont ( fileName ) ;
1178+ }
1179+ else
1180+ {
1181+ // This can happen when closing tags are encountered at the end of a font. If no font exists, we can just go to the default
1182+ font = Text . DefaultBitmapFont ;
1183+ }
1184+ global ::RenderingLibrary . Content . LoaderManager . Self . AddDisposable ( fontFileName , font ) ;
11421185 }
1143- global ::RenderingLibrary . Content . LoaderManager . Self . AddDisposable ( fontFileName , font ) ;
11441186 }
11451187
11461188 return font ;
@@ -1303,15 +1345,40 @@ public static void UpdateToFontValues(IText text, GraphicalUiElement graphicalUi
13031345 font = GetFontDisposable ( fontName ) ;
13041346 }
13051347
1348+ // Try in-memory font creation first (no disk I/O)
1349+ if ( font == null && InMemoryFontCreator != null )
1350+ {
1351+ try
1352+ {
1353+ BmfcSave bmfcSave = new BmfcSave ( ) ;
1354+ bmfcSave . FontSize = textRuntime . FontSize ;
1355+ bmfcSave . FontName = textRuntime . Font ;
1356+ bmfcSave . OutlineThickness = textRuntime . OutlineThickness ;
1357+ bmfcSave . UseSmoothing = textRuntime . UseFontSmoothing ;
1358+ bmfcSave . IsItalic = textRuntime . IsItalic ;
1359+ bmfcSave . IsBold = textRuntime . IsBold ;
1360+
1361+ var gumProject = ObjectFinder . Self . GumProjectSave ;
1362+ bmfcSave . Ranges = gumProject ? . FontRanges ?? BmfcSave . DefaultRanges ;
1363+ bmfcSave . SpacingHorizontal = gumProject ? . FontSpacingHorizontal ?? 1 ;
1364+ bmfcSave . SpacingVertical = gumProject ? . FontSpacingVertical ?? 1 ;
1365+
1366+ font = InMemoryFontCreator . TryCreateFont ( bmfcSave ) ;
1367+ if ( font != null )
1368+ {
1369+ loaderManager . AddDisposable ( fullFileName , font ) ;
1370+ }
1371+ }
1372+ catch
1373+ {
1374+ // Fall through to disk-based path
1375+ }
1376+ }
1377+
13061378#if ! FRB
1307- // On-demand font creation: if no cached or embedded font was found, ask the FontService
1308- // to generate the .fnt/.png files. This is the primary font creation path for both the
1309- // Gum tool and game runtimes. The Gum tool wires FontService to its FontManager (which
1310- // delegates to HeadlessFontGenerationService / bmfont.exe). Game runtimes can provide
1311- // their own IRuntimeFontService implementation.
1312- //
1313- // Bulk font generation (e.g., recreating the entire font cache on project load) is handled
1314- // separately by IFontManager.CreateAllMissingFontFiles, which is tool-only.
1379+ // Disk-based font creation: ask FontService to generate .fnt/.png files,
1380+ // then load from disk. This is the fallback when no InMemoryFontCreator
1381+ // is available or when in-memory creation fails.
13151382 if ( font == null && FontService != null )
13161383 {
13171384 try
0 commit comments