You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
success/packages/excalidraw/scene/Fonts.ts

95 lines
2.8 KiB
TypeScript

import { isTextElement, refreshTextDimensions } from "../element";
import { newElementWith } from "../element/mutateElement";
import { isBoundToContainer } from "../element/typeChecks";
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
import { getFontString } from "../utils";
import type Scene from "./Scene";
import { ShapeCache } from "./ShapeCache";
export class Fonts {
private scene: Scene;
private onSceneUpdated: () => void;
constructor({
scene,
onSceneUpdated,
}: {
scene: Scene;
onSceneUpdated: () => void;
}) {
this.scene = scene;
this.onSceneUpdated = onSceneUpdated;
}
// it's ok to track fonts across multiple instances only once, so let's use
// a static member to reduce memory footprint
private static loadedFontFaces = new Set<string>();
/**
* if we load a (new) font, it's likely that text elements using it have
* already been rendered using a fallback font. Thus, we want invalidate
* their shapes and rerender. See #637.
*
* Invalidates text elements and rerenders scene, provided that at least one
* of the supplied fontFaces has not already been processed.
*/
public onFontsLoaded = (fontFaces: readonly FontFace[]) => {
if (
// bail if all fonts with have been processed. We're checking just a
// subset of the font properties (though it should be enough), so it
// can technically bail on a false positive.
fontFaces.every((fontFace) => {
const sig = `${fontFace.family}-${fontFace.style}-${fontFace.weight}`;
if (Fonts.loadedFontFaces.has(sig)) {
return true;
}
Fonts.loadedFontFaces.add(sig);
return false;
})
) {
return false;
}
let didUpdate = false;
this.scene.mapElements((element) => {
if (isTextElement(element) && !isBoundToContainer(element)) {
ShapeCache.delete(element);
didUpdate = true;
return newElementWith(element, {
...refreshTextDimensions(element),
});
}
return element;
});
if (didUpdate) {
this.onSceneUpdated();
}
};
public loadFontsForElements = async (
elements: readonly ExcalidrawElement[],
) => {
const fontFaces = await Promise.all(
[
...new Set(
elements
.filter((element) => isTextElement(element))
.map((element) => (element as ExcalidrawTextElement).fontFamily),
),
].map((fontFamily) => {
const fontString = getFontString({
fontFamily,
fontSize: 16,
});
if (!document.fonts?.check?.(fontString)) {
return document.fonts?.load?.(fontString);
}
return undefined;
}),
);
this.onFontsLoaded(fontFaces.flat().filter(Boolean) as FontFace[]);
};
}