Skip to content

Commit 13a00f7

Browse files
committed
Dynamic loading of fonts with FontFace API, embedding fonts inside svg exports
1 parent 0c12e2a commit 13a00f7

20 files changed

+402
-350
lines changed

excalidraw-app/index.html

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -124,38 +124,35 @@
124124
<!-- Excalidraw version -->
125125
<meta name="version" content="{version}" />
126126

127-
<!-- Preload Virgil (canvas fonts) and Assistant (ui font) with the high priority and in the quickest way possible -->
128-
<!-- Otherwise, the browser would have to first parse CSS (@font-face) or JS (FontFace API), which would postpone the download -->
129-
<!-- * with CSS (@font-face) the browser could skip loading some fonts if it does not find a usage; also there are no options for execution priorities ~ these fonts tend to be loaded towards the end -->
130-
<!-- * with JS (FontFace API) we have more options, but still there is a necessary parsing step, so we could use it for less critical fonts (i.e. dynamic / custom fonts) -->
127+
<!-- Preload Excalifont, Virgil (canvas fonts) and Assistant (ui font) with the high priority and in the quickest way possible -->
128+
<!-- Preload from the package, as fonts inside /public are deprecated and left just for backwards compatibility in old svgs -->
131129
<link
132130
rel="preload"
133-
href="/Virgil.woff2"
131+
href="../packages/excalidraw/fonts/Excalifont.woff2"
134132
as="font"
135133
vittype="font/woff2"
136134
crossorigin="anonymous"
137135
/>
138136
<link
139-
rel="preload"
140-
href="/Virgil2.woff2"
137+
rel="preload"
138+
href="../packages/excalidraw/fonts/Virgil.woff2"
141139
as="font"
142140
vittype="font/woff2"
143141
crossorigin="anonymous"
144142
/>
145-
<!-- All the other Assistant weight variations should fallback to this one, rather than to a completely different font, while swapped back once fully loaded -->
146143
<link
147144
rel="preload"
148-
href="/Assistant-Regular.woff2"
145+
href="../packages/excalidraw/fonts/Assistant-Regular.woff2"
149146
as="font"
150147
vittype="font/woff2"
151148
crossorigin="anonymous"
152149
/>
153150

151+
154152
<!-- Warmup the connection for Google fonts -->
155153
<link rel="preconnect" href="https://fonts.googleapis.com">
156154
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
157155

158-
<link rel="stylesheet" href="/fonts.css" type="text/css" />
159156
<% if (typeof VITE_APP_DEV_DISABLE_LIVE_RELOAD != 'undefined' &&
160157
VITE_APP_DEV_DISABLE_LIVE_RELOAD == true) { %>
161158
<script>

excalidraw-app/vite.config.mts

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,6 @@ export default defineConfig({
3535
// Taking the substring after "locales/"
3636
return `locales/${id.substring(index + 8)}`;
3737
}
38-
},
39-
assetFileNames: (assetsInfo) => {
40-
if (assetsInfo.name?.match(/.+.(ttf|woff2|otf)/)) {
41-
// keep fonts in the root due to backwards compatibility (i.e. embedded inside SVGs)
42-
return "[name][extname]";
43-
}
44-
45-
return "assets/[name]-[hash][extname]"
4638
}
4739
},
4840
},
@@ -72,7 +64,7 @@ export default defineConfig({
7264

7365
workbox: {
7466
// Don't push fonts and locales to app precache
75-
globIgnores: ["fonts.css", "**/locales/**", "service-worker.js"],
67+
globIgnores: ["**/locales/**", "service-worker.js"],
7668
runtimeCaching: [
7769
{
7870
urlPattern: new RegExp("/.+.(ttf|woff2|otf)"),
@@ -85,16 +77,6 @@ export default defineConfig({
8577
},
8678
},
8779
},
88-
{
89-
urlPattern: new RegExp("fonts.css"),
90-
handler: "StaleWhileRevalidate",
91-
options: {
92-
cacheName: "fonts",
93-
expiration: {
94-
maxEntries: 50,
95-
},
96-
},
97-
},
9880
{
9981
urlPattern: new RegExp("locales/[^/]+.js"),
10082
handler: "CacheFirst",

packages/excalidraw/components/App.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -533,8 +533,8 @@ class App extends React.Component<AppProps, AppState> {
533533
private excalidrawContainerRef = React.createRef<HTMLDivElement>();
534534

535535
public scene: Scene;
536+
public fonts: Fonts;
536537
public renderer: Renderer;
537-
private fonts: Fonts;
538538
private resizeObserver: ResizeObserver | undefined;
539539
private nearestScrollableContainer: HTMLElement | Document | undefined;
540540
public library: AppClassProperties["library"];
@@ -2261,7 +2261,7 @@ class App extends React.Component<AppProps, AppState> {
22612261
// fire (looking at you Safari), so on init we manually load all
22622262
// fonts and rerender scene text elements once done. This also
22632263
// seems faster even in browsers that do fire the loadingdone event.
2264-
this.fonts.loadRegularFonts();
2264+
this.fonts.load();
22652265

22662266
if ("launchQueue" in window && "LaunchParams" in window) {
22672267
(window as any).launchQueue.setConsumer(
@@ -2434,6 +2434,10 @@ class App extends React.Component<AppProps, AppState> {
24342434
configurable: true,
24352435
value: this.store,
24362436
},
2437+
fonts: {
2438+
configurable: true,
2439+
value: this.fonts,
2440+
},
24372441
});
24382442
}
24392443

@@ -2570,7 +2574,7 @@ class App extends React.Component<AppProps, AppState> {
25702574
// rerender text elements on font load to fix #637 && #1553
25712575
addEventListener(document.fonts, "loadingdone", (event) => {
25722576
const loadedFontFaces = (event as FontFaceSetLoadEvent).fontfaces;
2573-
this.fonts.onFontsLoaded(loadedFontFaces);
2577+
this.fonts.onLoaded(loadedFontFaces);
25742578
}),
25752579
// Safari-only desktop pinch zoom
25762580
addEventListener(

packages/excalidraw/components/FontPicker/FontPicker.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import "./FontPicker.scss";
1818

1919
export const DEFAULT_FONTS = [
2020
{
21-
value: FONT_FAMILY.Virgil2,
21+
value: FONT_FAMILY.Excalifont,
2222
icon: FreedrawIcon,
2323
text: t("labels.handDrawn"),
2424
testId: "font-family-virgil-new",

packages/excalidraw/components/FontPicker/FontPickerList.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ export const ALL_FONTS = [
5858
placeholder: t("fontList.badge.new"),
5959
},
6060
},
61+
{
62+
value: FONT_FAMILY.Excalifont,
63+
text: "Excalifont",
64+
badge: {
65+
type: DropDownMenuItemBadgeType.GREEN,
66+
placeholder: t("fontList.badge.new"),
67+
},
68+
},
6169
{
6270
value: FONT_FAMILY.Helvetica,
6371
text: "Helvetica",
@@ -94,17 +102,9 @@ export const ALL_FONTS = [
94102
placeholder: t("fontList.badge.new"),
95103
},
96104
},
97-
{
98-
value: FONT_FAMILY.Virgil2,
99-
text: "Virgil",
100-
badge: {
101-
type: DropDownMenuItemBadgeType.GREEN,
102-
placeholder: t("fontList.badge.new"),
103-
},
104-
},
105105
{
106106
value: FONT_FAMILY.Virgil,
107-
text: "Virgil Classic",
107+
text: "Virgil",
108108
},
109109
];
110110

packages/excalidraw/constants.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export const CLASSES = {
112112
/**
113113
* // TODO: shouldn't be really `const`, likely neither have integers as values; let's think through possible classifications and consider:
114114
* - encoding font-family in the scene (not necessarily the whole content), to dynamicly load custom fonts (especially when in multiplayer)
115-
* - having fallbacks per family, first to other family names (i.e. Virgil to Virgil2 and vice versa), then to generic family names (i.e. Cascadia to monospace)
115+
* - having fallbacks per family, first to other family names (i.e. Virgil to Excalifont and vice versa), then to generic family names (i.e. Cascadia to monospace)
116116
* - having only few decent options per generic family name
117117
*
118118
* resources:
@@ -125,7 +125,7 @@ export const FONT_FAMILY = {
125125
Helvetica: 2,
126126
Cascadia: 3,
127127
Assistant: 4,
128-
Virgil2: 5,
128+
Excalifont: 5,
129129
Nunito: 6,
130130
ComicShanns: 7,
131131
Bangers: 8,
@@ -159,7 +159,7 @@ export const WINDOWS_EMOJI_FALLBACK_FONT = "Segoe UI Emoji";
159159

160160
export const MIN_FONT_SIZE = 1;
161161
export const DEFAULT_FONT_SIZE = 20;
162-
export const DEFAULT_FONT_FAMILY: FontFamilyValues = FONT_FAMILY.Virgil2;
162+
export const DEFAULT_FONT_FAMILY: FontFamilyValues = FONT_FAMILY.Excalifont;
163163
export const DEFAULT_TEXT_ALIGN = "left";
164164
export const DEFAULT_VERTICAL_ALIGN = "top";
165165
export const DEFAULT_VERSION = "{version}";

packages/excalidraw/element/textElement.ts

Lines changed: 61 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -861,116 +861,93 @@ export const isMeasureTextSupported = () => {
861861
return width > 0;
862862
};
863863

864-
/**
865-
* Unitless line height
866-
*
867-
* In previous versions we used `normal` line height, which browsers interpret
868-
* differently, and based on font-family and font-size.
869-
*
870-
* To make line heights consistent across browsers we hardcode the values for
871-
* each of our fonts based on most common average line-heights.
872-
* See https://github.com/excalidraw/excalidraw/pull/6360#issuecomment-1477635971
873-
* where the values come from.
874-
*/
875-
const DEFAULT_LINE_HEIGHT = {
876-
// ~1.25 is the average for Virgil in WebKit and Blink.
877-
// Gecko (FF) uses ~1.28.
878-
[FONT_FAMILY.Virgil]: 1.25 as ExcalidrawTextElement["lineHeight"],
879-
[FONT_FAMILY.Virgil2]: 1.25 as ExcalidrawTextElement["lineHeight"],
880-
// ~1.15 is the average for Helvetica in WebKit and Blink.
881-
[FONT_FAMILY.Helvetica]: 1.15 as ExcalidrawTextElement["lineHeight"],
882-
[FONT_FAMILY.TeXGyreHeros]: 1.15 as ExcalidrawTextElement["lineHeight"],
883-
// ~1.2 is the average for Cascadia in WebKit and Blink, and kinda Gecko too
884-
[FONT_FAMILY.Cascadia]: 1.2 as ExcalidrawTextElement["lineHeight"],
885-
[FONT_FAMILY.ComicShanns]: 1.2 as ExcalidrawTextElement["lineHeight"],
886-
// Pacifico glyphs often reach over the font bb, bumping to a usable value
887-
[FONT_FAMILY.Pacifico]: 1.75 as ExcalidrawTextElement["lineHeight"],
888-
};
889-
890-
/** hhea.ascender */
891-
type ascender = number & MakeBrand<"ascender">;
892-
893-
/** hhea.descender */
894-
type descender = number & MakeBrand<"descender">;
895-
896-
/** head.unitsPerEm */
897-
type unitsPerEm = 1000 | 1024 | 2048;
898-
899-
/**
900-
* Hardcoded metrics for default fonts, read by https://opentype.js.org/font-inspector.html.
901-
* For custom fonts, read these metrics from head & hhea tables and extend this object.
902-
*
903-
* WARN: opentype does NOT open WOFF2 correctly, make sure to convert WOFF2 to TTF first.
904-
*/
905-
export const FONT_METRICS: Record<
906-
number,
907-
{
908-
unitsPerEm: unitsPerEm;
909-
ascender: ascender;
910-
descender: descender;
911-
}
912-
> = {
864+
/** For head & hhea metrics read the woff2 with https://fontdrop.info/ */
865+
interface FontMetrics {
866+
/** head.unitsPerEm */
867+
unitsPerEm: 1000 | 1024 | 2048;
868+
/** hhea.ascender */
869+
ascender: number;
870+
/** hhea.descender */
871+
descender: number;
872+
/** harcoded unitless line-height, https://github.com/excalidraw/excalidraw/pull/6360#issuecomment-1477635971 */
873+
lineHeight: number;
874+
}
875+
876+
export const FONT_METRICS: Record<number, FontMetrics> = {
913877
[FONT_FAMILY.Virgil]: {
914878
unitsPerEm: 1000,
915-
ascender: 886 as ascender,
916-
descender: -374 as descender,
879+
ascender: 886,
880+
descender: -374,
881+
lineHeight: 1.25,
917882
},
918-
[FONT_FAMILY.Virgil2]: {
883+
[FONT_FAMILY.Excalifont]: {
919884
unitsPerEm: 1000,
920-
ascender: 886 as ascender,
921-
descender: -374 as descender,
885+
ascender: 886,
886+
descender: -374,
887+
lineHeight: 1.25,
922888
},
923889
[FONT_FAMILY.Helvetica]: {
924890
unitsPerEm: 2048,
925-
ascender: 1577 as ascender,
926-
descender: -471 as descender,
927-
},
928-
[FONT_FAMILY.Cascadia]: {
929-
unitsPerEm: 2048,
930-
ascender: 1977 as ascender,
931-
descender: -480 as descender,
891+
ascender: 1577,
892+
descender: -471,
893+
lineHeight: 1.15,
932894
},
933-
[FONT_FAMILY.Assistant]: {
895+
[FONT_FAMILY.TeXGyreHeros]: {
934896
unitsPerEm: 1000,
935-
ascender: 1021 as ascender,
936-
descender: -287 as descender,
897+
ascender: 1148,
898+
descender: -284,
899+
lineHeight: 1.15,
937900
},
938-
[FONT_FAMILY.Nunito]: {
939-
unitsPerEm: 1000,
940-
ascender: 1011 as ascender,
941-
descender: -353 as descender,
901+
[FONT_FAMILY.Cascadia]: {
902+
unitsPerEm: 2048,
903+
ascender: 1977,
904+
descender: -480,
905+
lineHeight: 1.2,
942906
},
943907
[FONT_FAMILY.ComicShanns]: {
944908
unitsPerEm: 1000,
945-
ascender: 750 as ascender,
946-
descender: -250 as descender,
909+
ascender: 750,
910+
descender: -250,
911+
lineHeight: 1.2,
947912
},
948-
[FONT_FAMILY.TeXGyreHeros]: {
913+
[FONT_FAMILY.Assistant]: {
949914
unitsPerEm: 1000,
950-
ascender: 1148 as ascender,
951-
descender: -284 as descender,
915+
ascender: 1021,
916+
descender: -287,
917+
lineHeight: 1.25,
952918
},
953-
[FONT_FAMILY.Bangers]: {
919+
[FONT_FAMILY.Nunito]: {
954920
unitsPerEm: 1000,
955-
ascender: 883 as ascender,
956-
descender: -181 as descender,
921+
ascender: 1011,
922+
descender: -353,
923+
lineHeight: 1.25,
957924
},
958-
[FONT_FAMILY.Pacifico]: {
925+
[FONT_FAMILY.Bangers]: {
959926
unitsPerEm: 1000,
960-
ascender: 1303 as ascender,
961-
descender: -453 as descender,
927+
ascender: 883,
928+
descender: -181,
929+
lineHeight: 1.25,
962930
},
963931
[FONT_FAMILY.PermanentMarker]: {
964932
unitsPerEm: 1024,
965-
ascender: 1136 as ascender,
966-
descender: -325 as descender,
933+
ascender: 1136,
934+
descender: -325,
935+
lineHeight: 1.25,
936+
},
937+
[FONT_FAMILY.Pacifico]: {
938+
unitsPerEm: 1000,
939+
ascender: 1303,
940+
descender: -453,
941+
lineHeight: 1.75,
967942
},
968943
};
969944

970945
export const getDefaultLineHeight = (fontFamily: FontFamilyValues) => {
971-
if (fontFamily in DEFAULT_LINE_HEIGHT) {
972-
return DEFAULT_LINE_HEIGHT[fontFamily];
946+
if (fontFamily in FONT_METRICS) {
947+
return FONT_METRICS[fontFamily]
948+
.lineHeight as ExcalidrawTextElement["lineHeight"];
973949
}
974950

975-
return DEFAULT_LINE_HEIGHT[DEFAULT_FONT_FAMILY];
951+
return FONT_METRICS[DEFAULT_FONT_FAMILY]
952+
.lineHeight as ExcalidrawTextElement["lineHeight"];
976953
};
File renamed without changes.
File renamed without changes.
19.8 KB
Binary file not shown.

0 commit comments

Comments
 (0)