mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
fix: load fonts for exportToCanvas
(#8298)
This commit is contained in:
parent
adcdbe2907
commit
5a0771ad9c
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
|
// fire (looking at you Safari), so on init we manually load all
|
||||||
// fonts and rerender scene text elements once done. This also
|
// fonts and rerender scene text elements once done. This also
|
||||||
// seems faster even in browsers that do fire the loadingdone event.
|
// seems faster even in browsers that do fire the loadingdone event.
|
||||||
this.fonts.load();
|
this.fonts.loadSceneFonts();
|
||||||
};
|
};
|
||||||
|
|
||||||
private isMobileBreakpoint = (width: number, height: number) => {
|
private isMobileBreakpoint = (width: number, height: number) => {
|
||||||
|
|
|
@ -89,7 +89,7 @@ export const FontPickerList = React.memo(
|
||||||
);
|
);
|
||||||
|
|
||||||
const sceneFamilies = useMemo(
|
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
|
// cache per selected font family, so hover re-render won't mess it up
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[selectedFontFamily],
|
[selectedFontFamily],
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import type Scene from "../scene/Scene";
|
import type Scene from "../scene/Scene";
|
||||||
import type { ValueOf } from "../utility-types";
|
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 { ShapeCache } from "../scene/ShapeCache";
|
||||||
import { isTextElement } from "../element";
|
import { isTextElement } from "../element";
|
||||||
import { getFontString } from "../utils";
|
import { getFontString } from "../utils";
|
||||||
|
@ -44,10 +48,19 @@ export class Fonts {
|
||||||
>
|
>
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
|
private static _initialized: boolean = false;
|
||||||
|
|
||||||
public static get registered() {
|
public static get registered() {
|
||||||
|
// lazy load the font registration
|
||||||
if (!Fonts._registered) {
|
if (!Fonts._registered) {
|
||||||
// lazy load the fonts
|
|
||||||
Fonts._registered = Fonts.init();
|
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;
|
return Fonts._registered;
|
||||||
|
@ -59,17 +72,6 @@ export class Fonts {
|
||||||
|
|
||||||
private readonly scene: Scene;
|
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 }) {
|
constructor({ scene }: { scene: Scene }) {
|
||||||
this.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)
|
// Add all registered font faces into the `document.fonts` (if not added already)
|
||||||
for (const { fonts } of Fonts.registered.values()) {
|
for (const { fonts } of Fonts.registered.values()) {
|
||||||
for (const { fontFace } of fonts) {
|
for (const { fontFace } of fonts) {
|
||||||
|
@ -129,8 +160,8 @@ export class Fonts {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const loaded = await Promise.all(
|
const loadedFontFaces = await Promise.all(
|
||||||
this.sceneFamilies.map(async (fontFamily) => {
|
fontFamilies.map(async (fontFamily) => {
|
||||||
const fontString = getFontString({
|
const fontString = getFontString({
|
||||||
fontFamily,
|
fontFamily,
|
||||||
fontSize: 16,
|
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.
|
* 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);
|
const _register = register.bind(fonts);
|
||||||
|
|
||||||
_register("Virgil", FONT_METADATA[FONT_FAMILY.Virgil], {
|
_register("Virgil", FONT_METADATA[FONT_FAMILY.Virgil], {
|
||||||
|
@ -235,8 +267,23 @@ export class Fonts {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Fonts._initialized = true;
|
||||||
|
|
||||||
return fonts.registered;
|
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;
|
canvas.height = height * appState.exportScale;
|
||||||
return { canvas, scale: 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(
|
const frameRendering = getFrameRenderingConfig(
|
||||||
exportingFrame ?? null,
|
exportingFrame ?? null,
|
||||||
appState.frameRendering ?? null,
|
appState.frameRendering ?? null,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue