1
0
mirror of https://github.com/excalidraw/excalidraw.git synced 2024-11-10 11:35:52 +01:00

fix: Comic Shanns issues, new fonts structure (#8641)

This commit is contained in:
Marcel Mraz 2024-10-21 01:11:53 +03:00 committed by GitHub
parent 15ca182333
commit 61623bbeba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
279 changed files with 13267 additions and 488 deletions

@ -36,4 +36,4 @@ yarn-error.log*
next-env.d.ts
# copied assets
public/*.woff2
public/**/*.woff2

@ -4,7 +4,7 @@
"private": true,
"scripts": {
"build:workspace": "yarn workspace @excalidraw/excalidraw run build:esm && yarn copy:assets",
"copy:assets": "cp ../../../packages/excalidraw/dist/browser/prod/excalidraw-assets/*.woff2 ./public",
"copy:assets": "cp -r ../../../packages/excalidraw/dist/prod/fonts ./public",
"dev": "yarn build:workspace && next dev -p 3005",
"build": "yarn build:workspace && next build",
"start": "next start -p 3006",

@ -1,2 +1,2 @@
# copied assets
public/*.woff2
public/**/*.woff2

@ -13,7 +13,7 @@
},
"scripts": {
"build:workspace": "yarn workspace @excalidraw/excalidraw run build:esm && yarn copy:assets",
"copy:assets": "cp ../../../packages/excalidraw/dist/browser/prod/excalidraw-assets/*.woff2 ./public",
"copy:assets": "cp -r ../../../packages/excalidraw/dist/prod/fonts ./public",
"start": "yarn build:workspace && vite",
"build": "yarn build:workspace && vite build",
"build:preview": "yarn build && vite preview --port 5002"

@ -133,7 +133,7 @@
<!-- Register Assistant as the UI font, before the scene inits -->
<link
rel="stylesheet"
href="../packages/excalidraw/fonts/css/fonts.css"
href="../packages/excalidraw/fonts/fonts.css"
type="text/css"
/>

@ -25,14 +25,8 @@ export default defineConfig({
output: {
assetFileNames(chunkInfo) {
if (chunkInfo?.name?.endsWith(".woff2")) {
// TODO: consider splitting all fonts similar to Xiaolai
// fonts don't change often, so hash is not necessary
// put on root so we are flexible about the CDN path
if (chunkInfo.name.includes("Xiaolai")) {
return "[name][extname]";
} else {
return "[name]-[hash][extname]";
}
const family = chunkInfo.name.split("-")[0];
return `fonts/${family}/[name][extname]`;
}
return "assets/[name]-[hash][extname]";

@ -736,7 +736,7 @@ class App extends React.Component<AppProps, AppState> {
id: this.id,
};
this.fonts = new Fonts({ scene: this.scene });
this.fonts = new Fonts(this.scene);
this.history = new History();
this.actionManager.registerAll(actions);
@ -2471,7 +2471,7 @@ class App extends React.Component<AppProps, AppState> {
this.renderer.destroy();
this.scene.destroy();
this.scene = new Scene();
this.fonts = new Fonts({ scene: this.scene });
this.fonts = new Fonts(this.scene);
this.renderer = new Renderer(this.scene);
this.files = {};
this.imageCache.clear();

@ -1,5 +1,6 @@
import CascadiaCodeRegular from "./CascadiaCode-Regular.woff2";
import { type ExcalidrawFontFaceDescriptor } from "../..";
import { type ExcalidrawFontFaceDescriptor } from "../Fonts";
export const CascadiaFontFaces: ExcalidrawFontFaceDescriptor[] = [
{

File diff suppressed because it is too large Load Diff

@ -0,0 +1,96 @@
// The following file content was generated with https://chinese-font.netlify.app/online-split,
// but has been manully rewritten from `@font-face` rules into TS while leveraging FontFace API.
import _0 from "./ComicShanns-Regular-279a7b317d12eb88de06167bd672b4b4.woff2";
import _1 from "./ComicShanns-Regular-fcb0fc02dcbee4c9846b3e2508668039.woff2";
import _2 from "./ComicShanns-Regular-dc6a8806fa96795d7b3be5026f989a17.woff2";
import _3 from "./ComicShanns-Regular-6e066e8de2ac57ea9283adb9c24d7f0c.woff2";
import { type ExcalidrawFontFaceDescriptor } from "../Fonts";
/* Generated By cn-font-split@5.2.2 https://www.npmjs.com/package/cn-font-split
CreateTime: Thu, 17 Oct 2024 09:57:51 GMT;
Origin File Name Table:
copyright: MIT License
Copyright (c) 2018 Shannon Miwa
Copyright (c) 2023 Jesus Gonzalez
Copyright (c) 2023 Rodrigo Batista de Moraes
Copyright (c) 2024 Fini Jastrow
Copyright (c) 2024 Kyle Beechly
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
fontFamily: Comic Shanns Mono-Regular
fontSubfamily: Regular
uniqueID: FontForge 2.0 : Comic Shanns Mono Regular : 17-10-2024
fullName: Comic Shanns Mono Regular
version: 1.3.0
postScriptName: ComicShannsMono-Regular
license: MIT License
Copyright (c) 2018 Shannon Miwa
Copyright (c) 2023 Jesus Gonzalez
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
preferredFamily: Comic Shanns Mono
*/
export const ComicShannsFontFaces: ExcalidrawFontFaceDescriptor[] = [
{
uri: _0,
descriptors: {
unicodeRange:
"U+20-7e,U+a1-a6,U+a8,U+ab-ac,U+af-b1,U+b4,U+b8,U+bb-bc,U+bf-cf,U+d1-d7,U+d9-de,U+e0-ef,U+f1-f7,U+f9-ff,U+131,U+152-153,U+2c6,U+2da,U+2dc,U+2013-2014,U+2018-201a,U+201c-201d,U+2020-2022,U+2026,U+2039-203a,U+2044,U+20ac,U+2191,U+2193,U+2212",
},
},
{
uri: _1,
descriptors: {
unicodeRange:
"U+100-10f,U+112-125,U+128-130,U+134-137,U+139-13c,U+141-148,U+14c-151,U+154-161,U+164-165,U+168-17f,U+1bf,U+1f7,U+218-21b,U+237,U+1e80-1e85,U+1ef2-1ef3,U+a75b",
},
},
{
uri: _2,
descriptors: {
unicodeRange:
"U+2c7,U+2d8-2d9,U+2db,U+2dd,U+315,U+2190,U+2192,U+2200,U+2203-2204,U+2264-2265,U+f6c3",
},
},
{
uri: _3,
descriptors: { unicodeRange: "U+3bb" },
},
];

@ -0,0 +1,9 @@
import { LOCAL_FONT_PROTOCOL } from "../FontMetadata";
import { type ExcalidrawFontFaceDescriptor } from "../Fonts";
export const EmojiFontFaces: ExcalidrawFontFaceDescriptor[] = [
{
uri: LOCAL_FONT_PROTOCOL,
},
];

@ -1,19 +1,10 @@
import { promiseTry } from "../utils";
import { LOCAL_FONT_PROTOCOL } from "./metadata";
import { subsetWoff2GlyphsByCodepoints } from "./subset/subset-main";
import { LOCAL_FONT_PROTOCOL } from "./FontMetadata";
import { subsetWoff2GlyphsByCodepoints } from "../subset/subset-main";
type DataURL = string;
export interface IExcalidrawFontFace {
urls: URL[] | DataURL[];
fontFace: FontFace;
toCSS(
characters: string,
codePoints: Array<number>,
): Promise<string> | undefined;
}
export class ExcalidrawFontFace implements IExcalidrawFontFace {
export class ExcalidrawFontFace {
public readonly urls: URL[] | DataURL[];
public readonly fontFace: FontFace;
@ -43,16 +34,17 @@ export class ExcalidrawFontFace implements IExcalidrawFontFace {
*
* Retrieves `undefined` otherwise.
*/
public toCSS(
characters: string,
codePoints: Array<number>,
): Promise<string> | undefined {
public toCSS(characters: string): Promise<string> | undefined {
// quick exit in case the characters are not within this font face's unicode range
if (!this.getUnicodeRangeRegex().test(characters)) {
return;
}
return this.getContent(codePoints).then(
const codepoints = Array.from(characters).map(
(char) => char.codePointAt(0)!,
);
return this.getContent(codepoints).then(
(content) =>
`@font-face { font-family: ${this.fontFace.family}; src: url(${content}); }`,
);
@ -98,6 +90,10 @@ export class ExcalidrawFontFace implements IExcalidrawFontFace {
public fetchFont(url: URL | DataURL): Promise<ArrayBuffer> {
return promiseTry(async () => {
const response = await fetch(url, {
// always prefer cache (even stale), otherwise it always triggers an unnecessary validation request
// which we don't need as we are controlling freshness of the fonts with the stable hash suffix in the url
// https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
cache: "force-cache",
headers: {
Accept: "font/woff2",
},

@ -0,0 +1,160 @@
import _0 from "./Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.woff2";
import _1 from "./Excalifont-Regular-be310b9bcd4f1a43f571c46df7809174.woff2";
import _2 from "./Excalifont-Regular-b9dcf9d2e50a1eaf42fc664b50a3fd0d.woff2";
import _3 from "./Excalifont-Regular-41b173a47b57366892116a575a43e2b6.woff2";
import _4 from "./Excalifont-Regular-3f2c5db56cc93c5a6873b1361d730c16.woff2";
import _5 from "./Excalifont-Regular-349fac6ca4700ffec595a7150a0d1e1d.woff2";
import _6 from "./Excalifont-Regular-623ccf21b21ef6b3a0d87738f77eb071.woff2";
import { type ExcalidrawFontFaceDescriptor } from "../Fonts";
/* Generated By cn-font-split@5.2.2 https://www.npmjs.com/package/cn-font-split
CreateTime: Mon, 14 Oct 2024 18:59:19 GMT;
Origin File Name Table:
copyright: Copyright (c) 2024 by Excalidraw. All rights reserved.
fontFamily: Excalifont
fontSubfamily: Regular
uniqueID: 1.000;DSGN;Excalifont
fullName: Excalifont Regular
version: Version 1.000;Glyphs 3.2 (3227)
postScriptName: Excalifont-Regular
trademark: Excalifont is a trademark of Excalidraw.
manufacturer: Your Own Font Foundry (Virgil); Ján Filípek / DizajnDesign (Excalifont, modifications)
designer: Your Own Font Foundry (Virgil); Ján Filípek / DizajnDesign (Excalifont, modifications)
manufacturerURL: https://dizajndesign.sk
designerURL: https://dizajndesign.sk
license: This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
licenseURL: http://scripts.sil.org/OFL
preferredFamily: Excalifont
preferredSubfamily: Regular
*/
export const ExcalifontFontFaces: ExcalidrawFontFaceDescriptor[] = [
{
uri: _0,
descriptors: {
unicodeRange:
"U+20-7e,U+a0-a3,U+a5-a6,U+a8-ab,U+ad-b1,U+b4,U+b6-b8,U+ba-ff,U+131,U+152-153,U+2bc,U+2c6,U+2da,U+2dc,U+304,U+308,U+2013-2014,U+2018-201a,U+201c-201e,U+2020,U+2022,U+2024-2026,U+2030,U+2039-203a,U+20ac,U+2122,U+2212",
},
},
{
uri: _1,
descriptors: {
unicodeRange:
"U+100-130,U+132-137,U+139-149,U+14c-151,U+154-17e,U+192,U+1fc-1ff,U+218-21b,U+237,U+1e80-1e85,U+1ef2-1ef3,U+2113",
},
},
{ uri: _2, descriptors: { unicodeRange: "U+400-45f,U+490-491,U+2116" } },
{
uri: _3,
descriptors: {
unicodeRange:
"U+37e,U+384-38a,U+38c,U+38e-393,U+395-3a1,U+3a3-3a8,U+3aa-3cf,U+3d7",
},
},
{
uri: _4,
descriptors: {
unicodeRange:
"U+2c7,U+2d8-2d9,U+2db,U+2dd,U+302,U+306-307,U+30a-30c,U+326-328,U+212e,U+2211,U+fb01-fb02",
},
},
{
uri: _5,
descriptors: {
unicodeRange:
"U+462-463,U+472-475,U+4d8-4d9,U+4e2-4e3,U+4e6-4e9,U+4ee-4ef",
},
},
{ uri: _6, descriptors: { unicodeRange: "U+300-301,U+303" } },
];

@ -1,8 +1,8 @@
import {
FontFamilyCodeIcon,
FontFamilyHeadingIcon,
FontFamilyNormalIcon,
FreedrawIcon,
FontFamilyNormalIcon,
FontFamilyHeadingIcon,
FontFamilyCodeIcon,
} from "../components/icons";
import { FONT_FAMILY, FONT_FAMILY_FALLBACKS } from "../constants";

@ -0,0 +1,349 @@
import {
FONT_FAMILY,
FONT_FAMILY_FALLBACKS,
CJK_HAND_DRAWN_FALLBACK_FONT,
WINDOWS_EMOJI_FALLBACK_FONT,
} from "../constants";
import { isTextElement } from "../element";
import { charWidth, getContainerElement } from "../element/textElement";
import { ShapeCache } from "../scene/ShapeCache";
import { getFontString } from "../utils";
import { ExcalidrawFontFace } from "./ExcalidrawFontFace";
import { CascadiaFontFaces } from "./Cascadia";
import { ComicShannsFontFaces } from "./ComicShanns";
import { EmojiFontFaces } from "./Emoji";
import { ExcalifontFontFaces } from "./Excalifont";
import { HelveticaFontFaces } from "./Helvetica";
import { LiberationFontFaces } from "./Liberation";
import { LilitaFontFaces } from "./Lilita";
import { NunitoFontFaces } from "./Nunito";
import { VirgilFontFaces } from "./Virgil";
import { XiaolaiFontFaces } from "./Xiaolai";
import { FONT_METADATA, type FontMetadata } from "./FontMetadata";
import type {
ExcalidrawElement,
ExcalidrawTextElement,
FontFamilyValues,
} from "../element/types";
import type Scene from "../scene/Scene";
import type { ValueOf } from "../utility-types";
export class Fonts {
// it's ok to track fonts across multiple instances only once, so let's use
// a static member to reduce memory footprint
public static readonly loadedFontsCache = new Set<string>();
private static _registered:
| Map<
number,
{
metadata: FontMetadata;
fontFaces: ExcalidrawFontFace[];
}
>
| undefined;
private static _initialized: boolean = false;
public static get registered() {
// lazy load the font registration
if (!Fonts._registered) {
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;
}
public get registered() {
return Fonts.registered;
}
private readonly scene: Scene;
constructor(scene: Scene) {
this.scene = scene;
}
/**
* 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 onLoaded = (fontFaces: readonly FontFace[]) => {
// 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.
let shouldBail = true;
for (const fontFace of fontFaces) {
const sig = `${fontFace.family}-${fontFace.style}-${fontFace.weight}-${fontFace.unicodeRange}`;
// make sure to update our cache with all the loaded font faces
if (!Fonts.loadedFontsCache.has(sig)) {
Fonts.loadedFontsCache.add(sig);
shouldBail = false;
}
}
if (shouldBail) {
return;
}
let didUpdate = false;
const elementsMap = this.scene.getNonDeletedElementsMap();
for (const element of this.scene.getNonDeletedElements()) {
if (isTextElement(element)) {
didUpdate = true;
ShapeCache.delete(element);
// clear the width cache, so that we don't perform subsequent wrapping based on the stale fallback font metrics
charWidth.clearCache(getFontString(element));
const container = getContainerElement(element, elementsMap);
if (container) {
ShapeCache.delete(container);
}
}
}
if (didUpdate) {
this.scene.triggerUpdate();
}
};
/**
* Load font faces for a given scene and trigger scene update.
*/
public loadSceneFonts = async (): Promise<FontFace[]> => {
const sceneFamilies = this.getSceneFamilies();
const loaded = await Fonts.loadFontFaces(sceneFamilies);
this.onLoaded(loaded);
return loaded;
};
/**
* Load all registered font faces.
*/
public static loadAllFonts = async (): Promise<FontFace[]> => {
const allFamilies = Fonts.getAllFamilies();
return Fonts.loadFontFaces(allFamilies);
};
/**
* Load font faces for passed elements - use when the scene is unavailable (i.e. export).
*/
public static loadElementsFonts = async (
elements: readonly ExcalidrawElement[],
): Promise<FontFace[]> => {
const fontFamilies = Fonts.getElementsFamilies(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 { fontFaces, metadata } of Fonts.registered.values()) {
// skip registering font faces for local fonts (i.e. Helvetica)
if (metadata.local) {
continue;
}
for (const { fontFace } of fontFaces) {
if (!window.document.fonts.has(fontFace)) {
window.document.fonts.add(fontFace);
}
}
}
const loadedFontFaces = await Promise.all(
fontFamilies.map(async (fontFamily) => {
const fontString = getFontString({
fontFamily,
fontSize: 16,
});
// WARN: without "text" param it does not have to mean that all font faces are loaded, instead it could be just one!
if (!window.document.fonts.check(fontString)) {
try {
// WARN: browser prioritizes loading only font faces with unicode ranges for characters which are present in the document (html & canvas), other font faces could stay unloaded
// we might want to retry here, i.e. in case CDN is down, but so far I didn't experience any issues - maybe it handles retry-like logic under the hood
return await window.document.fonts.load(fontString);
} catch (e) {
// don't let it all fail if just one font fails to load
console.error(
`Failed to load font "${fontString}" from urls "${Fonts.registered
.get(fontFamily)
?.fontFaces.map((x) => x.urls)}"`,
e,
);
}
}
return Promise.resolve();
}),
);
return loadedFontFaces.flat().filter(Boolean) as FontFace[];
}
/**
* WARN: should be called just once on init, even across multiple instances.
*/
private static init() {
const fonts = {
registered: new Map<
ValueOf<typeof FONT_FAMILY | typeof FONT_FAMILY_FALLBACKS>,
{ metadata: FontMetadata; fontFaces: ExcalidrawFontFace[] }
>(),
};
const init = (
family: keyof typeof FONT_FAMILY | keyof typeof FONT_FAMILY_FALLBACKS,
...fontFacesDescriptors: ExcalidrawFontFaceDescriptor[]
) => {
const fontFamily =
FONT_FAMILY[family as keyof typeof FONT_FAMILY] ??
FONT_FAMILY_FALLBACKS[family as keyof typeof FONT_FAMILY_FALLBACKS];
// default to Excalifont metrics
const metadata =
FONT_METADATA[fontFamily] ?? FONT_METADATA[FONT_FAMILY.Excalifont];
Fonts.register.call(fonts, family, metadata, ...fontFacesDescriptors);
};
init("Cascadia", ...CascadiaFontFaces);
init("Comic Shanns", ...ComicShannsFontFaces);
init("Excalifont", ...ExcalifontFontFaces);
// keeping for backwards compatibility reasons, uses system font (Helvetica on MacOS, Arial on Win)
init("Helvetica", ...HelveticaFontFaces);
// used for server-side pdf & png export instead of helvetica (technically does not need metrics, but kept in for consistency)
init("Liberation Sans", ...LiberationFontFaces);
init("Lilita One", ...LilitaFontFaces);
init("Nunito", ...NunitoFontFaces);
init("Virgil", ...VirgilFontFaces);
// fallback font faces
init(CJK_HAND_DRAWN_FALLBACK_FONT, ...XiaolaiFontFaces);
init(WINDOWS_EMOJI_FALLBACK_FONT, ...EmojiFontFaces);
Fonts._initialized = true;
return fonts.registered;
}
/**
* Register a new font.
*
* @param family font family
* @param metadata font metadata
* @param fontFacesDecriptors font faces descriptors
*/
private static register(
this:
| Fonts
| {
registered: Map<
number,
{ metadata: FontMetadata; fontFaces: ExcalidrawFontFace[] }
>;
},
family: string,
metadata: FontMetadata,
...fontFacesDecriptors: ExcalidrawFontFaceDescriptor[]
) {
// TODO: likely we will need to abandon number value in order to support custom fonts
const fontFamily =
FONT_FAMILY[family as keyof typeof FONT_FAMILY] ??
FONT_FAMILY_FALLBACKS[family as keyof typeof FONT_FAMILY_FALLBACKS];
const registeredFamily = this.registered.get(fontFamily);
if (!registeredFamily) {
this.registered.set(fontFamily, {
metadata,
fontFaces: fontFacesDecriptors.map(
({ uri, descriptors }) =>
new ExcalidrawFontFace(family, uri, descriptors),
),
});
}
return this.registered;
}
/**
* Gets all the font families for the given scene.
*/
public getSceneFamilies = () => {
return Fonts.getElementsFamilies(this.scene.getNonDeletedElements());
};
private static getAllFamilies() {
return Array.from(Fonts.registered.keys());
}
private static getElementsFamilies(
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>()),
);
}
}
/**
* Calculates vertical offset for a text with alphabetic baseline.
*/
export const getVerticalOffset = (
fontFamily: ExcalidrawTextElement["fontFamily"],
fontSize: ExcalidrawTextElement["fontSize"],
lineHeightPx: number,
) => {
const { unitsPerEm, ascender, descender } =
Fonts.registered.get(fontFamily)?.metadata.metrics ||
FONT_METADATA[FONT_FAMILY.Virgil].metrics;
const fontSizeEm = fontSize / unitsPerEm;
const lineGap =
(lineHeightPx - fontSizeEm * ascender + fontSizeEm * descender) / 2;
const verticalOffset = fontSizeEm * ascender + lineGap;
return verticalOffset;
};
/**
* Gets line height forr a selected family.
*/
export const getLineHeight = (fontFamily: FontFamilyValues) => {
const { lineHeight } =
Fonts.registered.get(fontFamily)?.metadata.metrics ||
FONT_METADATA[FONT_FAMILY.Excalifont].metrics;
return lineHeight as ExcalidrawTextElement["lineHeight"];
};
export interface ExcalidrawFontFaceDescriptor {
uri: string;
descriptors?: FontFaceDescriptors;
}

@ -0,0 +1,9 @@
import { LOCAL_FONT_PROTOCOL } from "../FontMetadata";
import { type ExcalidrawFontFaceDescriptor } from "../Fonts";
export const HelveticaFontFaces: ExcalidrawFontFaceDescriptor[] = [
{
uri: LOCAL_FONT_PROTOCOL,
},
];

@ -1,5 +1,6 @@
import LiberationSansRegular from "./LiberationSans-Regular.woff2";
import { type ExcalidrawFontFaceDescriptor } from "../..";
import { type ExcalidrawFontFaceDescriptor } from "../Fonts";
export const LiberationFontFaces: ExcalidrawFontFaceDescriptor[] = [
{

@ -1,8 +1,9 @@
import LilitaLatin from "./Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2";
import LilitaLatinExt from "./Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2";
import { GOOGLE_FONTS_RANGES } from "../../metadata";
import { type ExcalidrawFontFaceDescriptor } from "../..";
import { GOOGLE_FONTS_RANGES } from "../FontMetadata";
import { type ExcalidrawFontFaceDescriptor } from "../Fonts";
export const LilitaFontFaces: ExcalidrawFontFaceDescriptor[] = [
{

@ -4,8 +4,9 @@ import Cyrilic from "./Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXW
import CyrilicExt from "./Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5.woff2";
import Vietnamese from "./Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5.woff2";
import { GOOGLE_FONTS_RANGES } from "../../metadata";
import { type ExcalidrawFontFaceDescriptor } from "../..";
import { GOOGLE_FONTS_RANGES } from "../FontMetadata";
import { type ExcalidrawFontFaceDescriptor } from "../Fonts";
export const NunitoFontFaces: ExcalidrawFontFaceDescriptor[] = [
{

@ -1,5 +1,6 @@
import Virgil from "./Virgil-Regular.woff2";
import { type ExcalidrawFontFaceDescriptor } from "../..";
import { type ExcalidrawFontFaceDescriptor } from "../Fonts";
export const VirgilFontFaces: ExcalidrawFontFaceDescriptor[] = [
{

Some files were not shown because too many files have changed in this diff Show More