1
0
mirror of https://github.com/excalidraw/excalidraw.git synced 2025-02-18 13:29:36 +01:00

fix: load fonts for exportToCanvas (#8298)

This commit is contained in:
Marcel Mraz 2024-07-30 17:23:35 +02:00 committed by GitHub
parent adcdbe2907
commit 5a0771ad9c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 73 additions and 20 deletions

@ -2345,7 +2345,7 @@ class App extends React.Component<AppProps, AppState> {
// fire (looking at you Safari), so on init we manually load all
// fonts and rerender scene text elements once done. This also
// seems faster even in browsers that do fire the loadingdone event.
this.fonts.load();
this.fonts.loadSceneFonts();
};
private isMobileBreakpoint = (width: number, height: number) => {

@ -89,7 +89,7 @@ export const FontPickerList = React.memo(
);
const sceneFamilies = useMemo(
() => new Set(fonts.sceneFamilies),
() => new Set(fonts.getSceneFontFamilies()),
// cache per selected font family, so hover re-render won't mess it up
// eslint-disable-next-line react-hooks/exhaustive-deps
[selectedFontFamily],

@ -1,6 +1,10 @@
import type Scene from "../scene/Scene";
import type { ValueOf } from "../utility-types";
import type { ExcalidrawTextElement, FontFamilyValues } from "../element/types";
import type {
ExcalidrawElement,
ExcalidrawTextElement,
FontFamilyValues,
} from "../element/types";
import { ShapeCache } from "../scene/ShapeCache";
import { isTextElement } from "../element";
import { getFontString } from "../utils";
@ -44,10 +48,19 @@ export class Fonts {
>
| undefined;
private static _initialized: boolean = false;
public static get registered() {
// lazy load the font registration
if (!Fonts._registered) {
// lazy load the fonts
Fonts._registered = Fonts.init();
} else if (!Fonts._initialized) {
// case when host app register fonts before they are lazy loaded
// don't override whatever has been previously registered
Fonts._registered = new Map([
...Fonts.init().entries(),
...Fonts._registered.entries(),
]);
}
return Fonts._registered;
@ -59,17 +72,6 @@ export class Fonts {
private readonly scene: Scene;
public get sceneFamilies() {
return Array.from(
this.scene.getNonDeletedElements().reduce((families, element) => {
if (isTextElement(element)) {
families.add(element.fontFamily);
}
return families;
}, new Set<number>()),
);
}
constructor({ scene }: { scene: Scene }) {
this.scene = scene;
}
@ -119,7 +121,36 @@ export class Fonts {
}
};
public load = async () => {
/**
* Load font faces for a given scene and trigger scene update.
*/
public loadSceneFonts = async (): Promise<FontFace[]> => {
const sceneFamilies = this.getSceneFontFamilies();
const loaded = await Fonts.loadFontFaces(sceneFamilies);
this.onLoaded(loaded);
return loaded;
};
/**
* Gets all the font families for the given scene.
*/
public getSceneFontFamilies = () => {
return Fonts.getFontFamilies(this.scene.getNonDeletedElements());
};
/**
* Load font faces for passed elements - use when the scene is unavailable (i.e. export).
*/
public static loadFontsForElements = async (
elements: readonly ExcalidrawElement[],
): Promise<FontFace[]> => {
const fontFamilies = Fonts.getFontFamilies(elements);
return await Fonts.loadFontFaces(fontFamilies);
};
private static async loadFontFaces(
fontFamilies: Array<ExcalidrawTextElement["fontFamily"]>,
) {
// Add all registered font faces into the `document.fonts` (if not added already)
for (const { fonts } of Fonts.registered.values()) {
for (const { fontFace } of fonts) {
@ -129,8 +160,8 @@ export class Fonts {
}
}
const loaded = await Promise.all(
this.sceneFamilies.map(async (fontFamily) => {
const loadedFontFaces = await Promise.all(
fontFamilies.map(async (fontFamily) => {
const fontString = getFontString({
fontFamily,
fontSize: 16,
@ -157,8 +188,8 @@ export class Fonts {
}),
);
this.onLoaded(loaded.flat().filter(Boolean) as FontFace[]);
};
return loadedFontFaces.flat().filter(Boolean) as FontFace[];
}
/**
* WARN: should be called just once on init, even across multiple instances.
@ -171,6 +202,7 @@ export class Fonts {
>(),
};
// TODO: let's tweak this once we know how `register` will be exposed as part of the custom fonts API
const _register = register.bind(fonts);
_register("Virgil", FONT_METADATA[FONT_FAMILY.Virgil], {
@ -235,8 +267,23 @@ export class Fonts {
},
);
Fonts._initialized = true;
return fonts.registered;
}
private static getFontFamilies(
elements: ReadonlyArray<ExcalidrawElement>,
): Array<ExcalidrawTextElement["fontFamily"]> {
return Array.from(
elements.reduce((families, element) => {
if (isTextElement(element)) {
families.add(element.fontFamily);
}
return families;
}, new Set<number>()),
);
}
}
/**

@ -187,7 +187,13 @@ export const exportToCanvas = async (
canvas.height = height * appState.exportScale;
return { canvas, scale: appState.exportScale };
},
loadFonts: () => Promise<void> = async () => {
await Fonts.loadFontsForElements(elements);
},
) => {
// load font faces before continuing, by default leverages browsers' [FontFace API](https://developer.mozilla.org/en-US/docs/Web/API/FontFace)
await loadFonts();
const frameRendering = getFrameRenderingConfig(
exportingFrame ?? null,
appState.frameRendering ?? null,