fix: Comic Shanns issues, new fonts structure (#8641)
This commit is contained in:
@@ -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();
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
import CascadiaCodeRegular from "./CascadiaCode-Regular.woff2";
|
||||
import { type ExcalidrawFontFaceDescriptor } from "../..";
|
||||
|
||||
import { type ExcalidrawFontFaceDescriptor } from "../Fonts";
|
||||
|
||||
export const CascadiaFontFaces: ExcalidrawFontFaceDescriptor[] = [
|
||||
{
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
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",
|
||||
},
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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,
|
||||
},
|
||||
];
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
import LiberationSansRegular from "./LiberationSans-Regular.woff2";
|
||||
import { type ExcalidrawFontFaceDescriptor } from "../..";
|
||||
|
||||
import { type ExcalidrawFontFaceDescriptor } from "../Fonts";
|
||||
|
||||
export const LiberationFontFaces: ExcalidrawFontFaceDescriptor[] = [
|
||||
{
|
||||
+3
-2
@@ -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[] = [
|
||||
{
|
||||
+3
-2
@@ -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[] = [
|
||||
{
|
||||
+2
-1
@@ -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
Reference in New Issue
Block a user