mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
feat: introduce font picker (#8012)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
parent
4c5408263c
commit
62228e0bbb
120 changed files with 3390 additions and 1106 deletions
78
packages/excalidraw/fonts/ExcalidrawFont.ts
Normal file
78
packages/excalidraw/fonts/ExcalidrawFont.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { stringToBase64, toByteString } from "../data/encode";
|
||||
|
||||
export interface Font {
|
||||
url: URL;
|
||||
fontFace: FontFace;
|
||||
getContent(): Promise<string>;
|
||||
}
|
||||
export const UNPKG_PROD_URL = `https://unpkg.com/${
|
||||
import.meta.env.VITE_PKG_NAME
|
||||
}@${import.meta.env.PKG_VERSION}/dist/prod/`;
|
||||
|
||||
export class ExcalidrawFont implements Font {
|
||||
public readonly url: URL;
|
||||
public readonly fontFace: FontFace;
|
||||
|
||||
constructor(family: string, uri: string, descriptors?: FontFaceDescriptors) {
|
||||
// absolute assets paths, which are found in tests and excalidraw-app build, won't work with base url, so we are stripping initial slash away
|
||||
const assetUrl: string = uri.replace(/^\/+/, "");
|
||||
let baseUrl: string | undefined = undefined;
|
||||
|
||||
// fallback to unpkg to form a valid URL in case of a passed relative assetUrl
|
||||
let baseUrlBuilder = window.EXCALIDRAW_ASSET_PATH || UNPKG_PROD_URL;
|
||||
|
||||
// in case user passed a root-relative url (~absolute path),
|
||||
// like "/" or "/some/path", or relative (starts with "./"),
|
||||
// prepend it with `location.origin`
|
||||
if (/^\.?\//.test(baseUrlBuilder)) {
|
||||
baseUrlBuilder = new URL(
|
||||
baseUrlBuilder.replace(/^\.?\/+/, ""),
|
||||
window?.location?.origin,
|
||||
).toString();
|
||||
}
|
||||
|
||||
// ensure there is a trailing slash, otherwise url won't be correctly concatenated
|
||||
baseUrl = `${baseUrlBuilder.replace(/\/+$/, "")}/`;
|
||||
|
||||
this.url = new URL(assetUrl, baseUrl);
|
||||
this.fontFace = new FontFace(family, `url(${this.url})`, {
|
||||
display: "swap",
|
||||
style: "normal",
|
||||
weight: "400",
|
||||
...descriptors,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches woff2 content based on the registered url (browser).
|
||||
*
|
||||
* Use dataurl outside the browser environment.
|
||||
*/
|
||||
public async getContent(): Promise<string> {
|
||||
if (this.url.protocol === "data:") {
|
||||
// it's dataurl, the font is inlined as base64, no need to fetch
|
||||
return this.url.toString();
|
||||
}
|
||||
|
||||
const response = await fetch(this.url, {
|
||||
headers: {
|
||||
Accept: "font/woff2",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(
|
||||
`Couldn't fetch font-family "${this.fontFace.family}" from url "${this.url}"`,
|
||||
response,
|
||||
);
|
||||
}
|
||||
|
||||
const mimeType = await response.headers.get("Content-Type");
|
||||
const buffer = await response.arrayBuffer();
|
||||
|
||||
return `data:${mimeType};base64,${await stringToBase64(
|
||||
await toByteString(buffer),
|
||||
true,
|
||||
)}`;
|
||||
}
|
||||
}
|
BIN
packages/excalidraw/fonts/assets/Assistant-Bold.woff2
Normal file
BIN
packages/excalidraw/fonts/assets/Assistant-Bold.woff2
Normal file
Binary file not shown.
BIN
packages/excalidraw/fonts/assets/Assistant-Medium.woff2
Normal file
BIN
packages/excalidraw/fonts/assets/Assistant-Medium.woff2
Normal file
Binary file not shown.
BIN
packages/excalidraw/fonts/assets/Assistant-Regular.woff2
Normal file
BIN
packages/excalidraw/fonts/assets/Assistant-Regular.woff2
Normal file
Binary file not shown.
BIN
packages/excalidraw/fonts/assets/Assistant-SemiBold.woff2
Normal file
BIN
packages/excalidraw/fonts/assets/Assistant-SemiBold.woff2
Normal file
Binary file not shown.
BIN
packages/excalidraw/fonts/assets/CascadiaMono-Regular.woff2
Normal file
BIN
packages/excalidraw/fonts/assets/CascadiaMono-Regular.woff2
Normal file
Binary file not shown.
BIN
packages/excalidraw/fonts/assets/ComicShanns-Regular.woff2
Normal file
BIN
packages/excalidraw/fonts/assets/ComicShanns-Regular.woff2
Normal file
Binary file not shown.
BIN
packages/excalidraw/fonts/assets/Excalifont-Regular.woff2
Normal file
BIN
packages/excalidraw/fonts/assets/Excalifont-Regular.woff2
Normal file
Binary file not shown.
BIN
packages/excalidraw/fonts/assets/LiberationSans-Regular.woff2
Normal file
BIN
packages/excalidraw/fonts/assets/LiberationSans-Regular.woff2
Normal file
Binary file not shown.
BIN
packages/excalidraw/fonts/assets/Virgil-Regular.woff2
Normal file
BIN
packages/excalidraw/fonts/assets/Virgil-Regular.woff2
Normal file
Binary file not shown.
34
packages/excalidraw/fonts/assets/fonts.css
Normal file
34
packages/excalidraw/fonts/assets/fonts.css
Normal file
|
@ -0,0 +1,34 @@
|
|||
/* Only UI fonts here, which are needed before the editor initializes. */
|
||||
/* These also cannot be preprended with `EXCALIDRAW_ASSET_PATH`. */
|
||||
|
||||
@font-face {
|
||||
font-family: "Assistant";
|
||||
src: url(./Assistant-Regular.woff2) format("woff2");
|
||||
font-weight: 400;
|
||||
style: normal;
|
||||
display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Assistant";
|
||||
src: url(./Assistant-Medium.woff2) format("woff2");
|
||||
font-weight: 500;
|
||||
style: normal;
|
||||
display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Assistant";
|
||||
src: url(./Assistant-SemiBold.woff2) format("woff2");
|
||||
font-weight: 600;
|
||||
style: normal;
|
||||
display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Assistant";
|
||||
src: url(./Assistant-Bold.woff2) format("woff2");
|
||||
font-weight: 700;
|
||||
style: normal;
|
||||
display: swap;
|
||||
}
|
308
packages/excalidraw/fonts/index.ts
Normal file
308
packages/excalidraw/fonts/index.ts
Normal file
|
@ -0,0 +1,308 @@
|
|||
import type Scene from "../scene/Scene";
|
||||
import type { ValueOf } from "../utility-types";
|
||||
import type { ExcalidrawTextElement, FontFamilyValues } from "../element/types";
|
||||
import { ShapeCache } from "../scene/ShapeCache";
|
||||
import { isTextElement } from "../element";
|
||||
import { getFontString } from "../utils";
|
||||
import { FONT_FAMILY } from "../constants";
|
||||
import {
|
||||
LOCAL_FONT_PROTOCOL,
|
||||
FONT_METADATA,
|
||||
RANGES,
|
||||
type FontMetadata,
|
||||
} from "./metadata";
|
||||
import { ExcalidrawFont, type Font } from "./ExcalidrawFont";
|
||||
import { getContainerElement } from "../element/textElement";
|
||||
|
||||
import Virgil from "./assets/Virgil-Regular.woff2";
|
||||
import Excalifont from "./assets/Excalifont-Regular.woff2";
|
||||
import Cascadia from "./assets/CascadiaMono-Regular.woff2";
|
||||
import ComicShanns from "./assets/ComicShanns-Regular.woff2";
|
||||
import LiberationSans from "./assets/LiberationSans-Regular.woff2";
|
||||
|
||||
import LilitaLatin from "https://fonts.gstatic.com/s/lilitaone/v15/i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2";
|
||||
import LilitaLatinExt from "https://fonts.gstatic.com/s/lilitaone/v15/i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2";
|
||||
|
||||
import NunitoLatin from "https://fonts.gstatic.com/s/nunito/v26/XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg.woff2";
|
||||
import NunitoLatinExt from "https://fonts.gstatic.com/s/nunito/v26/XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTo3j6zbXWjgevT5.woff2";
|
||||
import NunitoCyrilic from "https://fonts.gstatic.com/s/nunito/v26/XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXWjgevT5.woff2";
|
||||
import NunitoCyrilicExt from "https://fonts.gstatic.com/s/nunito/v26/XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5.woff2";
|
||||
import NunitoVietnamese from "https://fonts.gstatic.com/s/nunito/v26/XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5.woff2";
|
||||
|
||||
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: Font[];
|
||||
}
|
||||
>
|
||||
| undefined;
|
||||
|
||||
public static get registered() {
|
||||
if (!Fonts._registered) {
|
||||
// lazy load the fonts
|
||||
Fonts._registered = Fonts.init();
|
||||
}
|
||||
|
||||
return Fonts._registered;
|
||||
}
|
||||
|
||||
public get registered() {
|
||||
return Fonts.registered;
|
||||
}
|
||||
|
||||
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 }) {
|
||||
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[]) => {
|
||||
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}-${fontFace.unicodeRange}`;
|
||||
if (Fonts.loadedFontsCache.has(sig)) {
|
||||
return true;
|
||||
}
|
||||
Fonts.loadedFontsCache.add(sig);
|
||||
return false;
|
||||
})
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let didUpdate = false;
|
||||
|
||||
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||
|
||||
for (const element of this.scene.getNonDeletedElements()) {
|
||||
if (isTextElement(element)) {
|
||||
didUpdate = true;
|
||||
ShapeCache.delete(element);
|
||||
const container = getContainerElement(element, elementsMap);
|
||||
if (container) {
|
||||
ShapeCache.delete(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (didUpdate) {
|
||||
this.scene.triggerUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
public load = async () => {
|
||||
// Add all registered font faces into the `document.fonts` (if not added already)
|
||||
for (const { fontFaces } of Fonts.registered.values()) {
|
||||
for (const { fontFace, url } of fontFaces) {
|
||||
if (
|
||||
url.protocol !== LOCAL_FONT_PROTOCOL &&
|
||||
!window.document.fonts.has(fontFace)
|
||||
) {
|
||||
window.document.fonts.add(fontFace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const loaded = await Promise.all(
|
||||
this.sceneFamilies.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}" with error "${e}", given the following registered font:`,
|
||||
JSON.stringify(Fonts.registered.get(fontFamily), undefined, 2),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}),
|
||||
);
|
||||
|
||||
this.onLoaded(loaded.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>,
|
||||
{ metadata: FontMetadata; fontFaces: Font[] }
|
||||
>(),
|
||||
};
|
||||
|
||||
const _register = register.bind(fonts);
|
||||
|
||||
_register("Virgil", FONT_METADATA[FONT_FAMILY.Virgil], {
|
||||
uri: Virgil,
|
||||
});
|
||||
|
||||
_register("Excalifont", FONT_METADATA[FONT_FAMILY.Excalifont], {
|
||||
uri: Excalifont,
|
||||
});
|
||||
|
||||
// keeping for backwards compatibility reasons, uses system font (Helvetica on MacOS, Arial on Win)
|
||||
_register("Helvetica", FONT_METADATA[FONT_FAMILY.Helvetica], {
|
||||
uri: LOCAL_FONT_PROTOCOL,
|
||||
});
|
||||
|
||||
// used for server-side pdf & png export instead of helvetica (technically does not need metrics, but kept in for consistency)
|
||||
_register(
|
||||
"Liberation Sans",
|
||||
FONT_METADATA[FONT_FAMILY["Liberation Sans"]],
|
||||
{
|
||||
uri: LiberationSans,
|
||||
},
|
||||
);
|
||||
|
||||
_register("Cascadia", FONT_METADATA[FONT_FAMILY.Cascadia], {
|
||||
uri: Cascadia,
|
||||
});
|
||||
|
||||
_register("Comic Shanns", FONT_METADATA[FONT_FAMILY["Comic Shanns"]], {
|
||||
uri: ComicShanns,
|
||||
});
|
||||
|
||||
_register(
|
||||
"Lilita One",
|
||||
FONT_METADATA[FONT_FAMILY["Lilita One"]],
|
||||
{ uri: LilitaLatinExt, descriptors: { unicodeRange: RANGES.LATIN_EXT } },
|
||||
{ uri: LilitaLatin, descriptors: { unicodeRange: RANGES.LATIN } },
|
||||
);
|
||||
|
||||
_register(
|
||||
"Nunito",
|
||||
FONT_METADATA[FONT_FAMILY.Nunito],
|
||||
{
|
||||
uri: NunitoCyrilicExt,
|
||||
descriptors: { unicodeRange: RANGES.CYRILIC_EXT, weight: "500" },
|
||||
},
|
||||
{
|
||||
uri: NunitoCyrilic,
|
||||
descriptors: { unicodeRange: RANGES.CYRILIC, weight: "500" },
|
||||
},
|
||||
{
|
||||
uri: NunitoVietnamese,
|
||||
descriptors: { unicodeRange: RANGES.VIETNAMESE, weight: "500" },
|
||||
},
|
||||
{
|
||||
uri: NunitoLatinExt,
|
||||
descriptors: { unicodeRange: RANGES.LATIN_EXT, weight: "500" },
|
||||
},
|
||||
{
|
||||
uri: NunitoLatin,
|
||||
descriptors: { unicodeRange: RANGES.LATIN, weight: "500" },
|
||||
},
|
||||
);
|
||||
|
||||
return fonts.registered;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new font.
|
||||
*
|
||||
* @param family font family
|
||||
* @param metadata font metadata
|
||||
* @param params array of the rest of the FontFace parameters [uri: string, descriptors: FontFaceDescriptors?] ,
|
||||
*/
|
||||
function register(
|
||||
this:
|
||||
| Fonts
|
||||
| {
|
||||
registered: Map<
|
||||
ValueOf<typeof FONT_FAMILY>,
|
||||
{ metadata: FontMetadata; fontFaces: Font[] }
|
||||
>;
|
||||
},
|
||||
family: string,
|
||||
metadata: FontMetadata,
|
||||
...params: Array<{ uri: string; descriptors?: FontFaceDescriptors }>
|
||||
) {
|
||||
// TODO: likely we will need to abandon number "id" in order to support custom fonts
|
||||
const familyId = FONT_FAMILY[family as keyof typeof FONT_FAMILY];
|
||||
const registeredFamily = this.registered.get(familyId);
|
||||
|
||||
if (!registeredFamily) {
|
||||
this.registered.set(familyId, {
|
||||
metadata,
|
||||
fontFaces: params.map(
|
||||
({ uri, descriptors }) => new ExcalidrawFont(family, uri, descriptors),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return this.registered;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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"];
|
||||
};
|
125
packages/excalidraw/fonts/metadata.ts
Normal file
125
packages/excalidraw/fonts/metadata.ts
Normal file
|
@ -0,0 +1,125 @@
|
|||
import {
|
||||
FontFamilyCodeIcon,
|
||||
FontFamilyHeadingIcon,
|
||||
FontFamilyNormalIcon,
|
||||
FreedrawIcon,
|
||||
} from "../components/icons";
|
||||
import { FONT_FAMILY } from "../constants";
|
||||
|
||||
/**
|
||||
* Encapsulates font metrics with additional font metadata.
|
||||
* */
|
||||
export interface FontMetadata {
|
||||
/** for head & hhea metrics read the woff2 with https://fontdrop.info/ */
|
||||
metrics: {
|
||||
/** head.unitsPerEm metric */
|
||||
unitsPerEm: 1000 | 1024 | 2048;
|
||||
/** hhea.ascender metric */
|
||||
ascender: number;
|
||||
/** hhea.descender metric */
|
||||
descender: number;
|
||||
/** harcoded unitless line-height, https://github.com/excalidraw/excalidraw/pull/6360#issuecomment-1477635971 */
|
||||
lineHeight: number;
|
||||
};
|
||||
/** element to be displayed as an icon */
|
||||
icon: JSX.Element;
|
||||
/** flag to indicate a deprecated font */
|
||||
deprecated?: true;
|
||||
/** flag to indicate a server-side only font */
|
||||
serverSide?: true;
|
||||
}
|
||||
|
||||
export const FONT_METADATA: Record<number, FontMetadata> = {
|
||||
[FONT_FAMILY.Excalifont]: {
|
||||
metrics: {
|
||||
unitsPerEm: 1000,
|
||||
ascender: 886,
|
||||
descender: -374,
|
||||
lineHeight: 1.25,
|
||||
},
|
||||
icon: FreedrawIcon,
|
||||
},
|
||||
[FONT_FAMILY.Nunito]: {
|
||||
metrics: {
|
||||
unitsPerEm: 1000,
|
||||
ascender: 1011,
|
||||
descender: -353,
|
||||
lineHeight: 1.35,
|
||||
},
|
||||
icon: FontFamilyNormalIcon,
|
||||
},
|
||||
[FONT_FAMILY["Lilita One"]]: {
|
||||
metrics: {
|
||||
unitsPerEm: 1000,
|
||||
ascender: 923,
|
||||
descender: -220,
|
||||
lineHeight: 1.15,
|
||||
},
|
||||
icon: FontFamilyHeadingIcon,
|
||||
},
|
||||
[FONT_FAMILY["Comic Shanns"]]: {
|
||||
metrics: {
|
||||
unitsPerEm: 1000,
|
||||
ascender: 750,
|
||||
descender: -250,
|
||||
lineHeight: 1.25,
|
||||
},
|
||||
icon: FontFamilyCodeIcon,
|
||||
},
|
||||
[FONT_FAMILY.Virgil]: {
|
||||
metrics: {
|
||||
unitsPerEm: 1000,
|
||||
ascender: 886,
|
||||
descender: -374,
|
||||
lineHeight: 1.25,
|
||||
},
|
||||
icon: FreedrawIcon,
|
||||
deprecated: true,
|
||||
},
|
||||
[FONT_FAMILY.Helvetica]: {
|
||||
metrics: {
|
||||
unitsPerEm: 2048,
|
||||
ascender: 1577,
|
||||
descender: -471,
|
||||
lineHeight: 1.15,
|
||||
},
|
||||
icon: FontFamilyNormalIcon,
|
||||
deprecated: true,
|
||||
},
|
||||
[FONT_FAMILY.Cascadia]: {
|
||||
metrics: {
|
||||
unitsPerEm: 2048,
|
||||
ascender: 1900,
|
||||
descender: -480,
|
||||
lineHeight: 1.2,
|
||||
},
|
||||
icon: FontFamilyCodeIcon,
|
||||
deprecated: true,
|
||||
},
|
||||
[FONT_FAMILY["Liberation Sans"]]: {
|
||||
metrics: {
|
||||
unitsPerEm: 2048,
|
||||
ascender: 1854,
|
||||
descender: -434,
|
||||
lineHeight: 1.15,
|
||||
},
|
||||
icon: FontFamilyNormalIcon,
|
||||
serverSide: true,
|
||||
},
|
||||
};
|
||||
|
||||
/** Unicode ranges */
|
||||
export const RANGES = {
|
||||
LATIN:
|
||||
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD",
|
||||
LATIN_EXT:
|
||||
"U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF",
|
||||
CYRILIC_EXT:
|
||||
"U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F",
|
||||
CYRILIC: "U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116",
|
||||
VIETNAMESE:
|
||||
"U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB",
|
||||
};
|
||||
|
||||
/** local protocol to skip the local font from registering or inlining */
|
||||
export const LOCAL_FONT_PROTOCOL = "local:";
|
Loading…
Add table
Add a link
Reference in a new issue