fix: fonts not loading on export (again) (#9064)

This commit is contained in:
Marcel Mraz 2025-01-29 22:24:26 +01:00 committed by GitHub
parent a58822c1c1
commit d29c3db7f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -3,7 +3,6 @@ import {
FONT_FAMILY_FALLBACKS, FONT_FAMILY_FALLBACKS,
CJK_HAND_DRAWN_FALLBACK_FONT, CJK_HAND_DRAWN_FALLBACK_FONT,
WINDOWS_EMOJI_FALLBACK_FONT, WINDOWS_EMOJI_FALLBACK_FONT,
isSafari,
getFontFamilyFallbacks, getFontFamilyFallbacks,
} from "../constants"; } from "../constants";
import { isTextElement } from "../element"; import { isTextElement } from "../element";
@ -137,50 +136,28 @@ export class Fonts {
/** /**
* Load font faces for a given scene and trigger scene update. * Load font faces for a given scene and trigger scene update.
*
* FontFaceSet loadingdone event we listen on may not always
* fire (looking at you Safari), so on init we manually load all
* fonts and rerender scene text elements once done.
*
* For Safari we make sure to check against each loaded font face
* with the unique characters per family in the scene,
* otherwise fonts might remain unloaded.
*/ */
public loadSceneFonts = async (): Promise<FontFace[]> => { public loadSceneFonts = async (): Promise<FontFace[]> => {
const sceneFamilies = this.getSceneFamilies(); const sceneFamilies = this.getSceneFamilies();
const charsPerFamily = isSafari const charsPerFamily = Fonts.getCharsPerFamily(
? Fonts.getCharsPerFamily(this.scene.getNonDeletedElements()) this.scene.getNonDeletedElements(),
: undefined; );
return Fonts.loadFontFaces(sceneFamilies, charsPerFamily); return Fonts.loadFontFaces(sceneFamilies, charsPerFamily);
}; };
/** /**
* Load font faces for passed elements - use when the scene is unavailable (i.e. export). * Load font faces for passed elements - use when the scene is unavailable (i.e. export).
*
* For Safari we make sure to check against each loaded font face,
* with the unique characters per family in the elements
* otherwise fonts might remain unloaded.
*/ */
public static loadElementsFonts = async ( public static loadElementsFonts = async (
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
): Promise<FontFace[]> => { ): Promise<FontFace[]> => {
const fontFamilies = Fonts.getUniqueFamilies(elements); const fontFamilies = Fonts.getUniqueFamilies(elements);
const charsPerFamily = isSafari const charsPerFamily = Fonts.getCharsPerFamily(elements);
? Fonts.getCharsPerFamily(elements)
: undefined;
return Fonts.loadFontFaces(fontFamilies, charsPerFamily); return Fonts.loadFontFaces(fontFamilies, charsPerFamily);
}; };
/**
* Load all registered font faces.
*/
public static loadAllFonts = async (): Promise<FontFace[]> => {
const allFamilies = Fonts.getAllFamilies();
return Fonts.loadFontFaces(allFamilies);
};
/** /**
* Generate CSS @font-face declarations for the given elements. * Generate CSS @font-face declarations for the given elements.
*/ */
@ -223,7 +200,7 @@ export class Fonts {
private static async loadFontFaces( private static async loadFontFaces(
fontFamilies: Array<ExcalidrawTextElement["fontFamily"]>, fontFamilies: Array<ExcalidrawTextElement["fontFamily"]>,
charsPerFamily?: Record<number, Set<string>>, charsPerFamily: Record<number, Set<string>>,
) { ) {
// add all registered font faces into the `document.fonts` (if not added already) // add all registered font faces into the `document.fonts` (if not added already)
for (const { fontFaces, metadata } of Fonts.registered.values()) { for (const { fontFaces, metadata } of Fonts.registered.values()) {
@ -248,7 +225,7 @@ export class Fonts {
private static *fontFacesLoader( private static *fontFacesLoader(
fontFamilies: Array<ExcalidrawTextElement["fontFamily"]>, fontFamilies: Array<ExcalidrawTextElement["fontFamily"]>,
charsPerFamily?: Record<number, Set<string>>, charsPerFamily: Record<number, Set<string>>,
): Generator<Promise<void | readonly [number, FontFace[]]>> { ): Generator<Promise<void | readonly [number, FontFace[]]>> {
for (const [index, fontFamily] of fontFamilies.entries()) { for (const [index, fontFamily] of fontFamilies.entries()) {
const font = getFontString({ const font = getFontString({
@ -256,12 +233,9 @@ export class Fonts {
fontSize: 16, fontSize: 16,
}); });
// WARN: without "text" param it does not have to mean that all font faces are loaded, instead it could be just one! // WARN: without "text" param it does not have to mean that all font faces are loaded as it could be just one irrelevant font face!
// for Safari on init, we rather check with the "text" param, even though it's less efficient, as otherwise fonts might remain unloaded // instead, we are always checking chars used in the family, so that no required font faces remain unloaded
const text = const text = Fonts.getCharacters(charsPerFamily, fontFamily);
isSafari && charsPerFamily
? Fonts.getCharacters(charsPerFamily, fontFamily)
: "";
if (!window.document.fonts.check(font, text)) { if (!window.document.fonts.check(font, text)) {
yield promiseTry(async () => { yield promiseTry(async () => {