@testbank-inc/excalidraw
Version:
Excalidraw as a React component
1,344 lines (1,264 loc) • 302 kB
JavaScript
import {
define_import_meta_env_default
} from "./chunk-LDSNT77A.js";
import {
__publicField
} from "./chunk-XDFCUUT6.js";
// data/image.ts
import tEXt from "png-chunk-text";
import encodePng from "png-chunks-encode";
import decodePng from "png-chunks-extract";
import { EXPORT_DATA_TYPES as EXPORT_DATA_TYPES3, MIME_TYPES as MIME_TYPES7 } from "@testbank-inc/common";
// data/blob.ts
import { nanoid } from "nanoid";
import {
IMAGE_MIME_TYPES,
MIME_TYPES as MIME_TYPES6,
bytesToHexString,
isPromiseLike
} from "@testbank-inc/common";
import { clearElementsForExport as clearElementsForExport2 } from "@testbank-inc/element";
// appState.ts
import {
COLOR_PALETTE,
ARROW_TYPE,
DEFAULT_ELEMENT_PROPS,
DEFAULT_FONT_FAMILY,
DEFAULT_FONT_SIZE,
DEFAULT_TEXT_ALIGN,
DEFAULT_GRID_SIZE,
EXPORT_SCALES,
STATS_PANELS,
THEME,
DEFAULT_GRID_STEP,
isTestEnv
} from "@testbank-inc/common";
var defaultExportScale = EXPORT_SCALES.includes(devicePixelRatio) ? devicePixelRatio : 1;
var getDefaultAppState = () => {
return {
showWelcomeScreen: false,
theme: THEME.LIGHT,
collaborators: /* @__PURE__ */ new Map(),
currentChartType: "bar",
currentItemBackgroundColor: DEFAULT_ELEMENT_PROPS.backgroundColor,
currentItemEndArrowhead: "arrow",
currentItemFillStyle: DEFAULT_ELEMENT_PROPS.fillStyle,
currentItemFontFamily: DEFAULT_FONT_FAMILY,
currentItemFontSize: DEFAULT_FONT_SIZE,
currentItemOpacity: DEFAULT_ELEMENT_PROPS.opacity,
currentItemRoughness: DEFAULT_ELEMENT_PROPS.roughness,
currentItemStartArrowhead: null,
currentItemStrokeColor: DEFAULT_ELEMENT_PROPS.strokeColor,
currentItemRoundness: isTestEnv() ? "sharp" : "round",
currentItemArrowType: ARROW_TYPE.round,
currentItemStrokeStyle: DEFAULT_ELEMENT_PROPS.strokeStyle,
currentItemStrokeWidth: DEFAULT_ELEMENT_PROPS.strokeWidth,
currentItemTextAlign: DEFAULT_TEXT_ALIGN,
currentHoveredFontFamily: null,
cursorButton: "up",
activeEmbeddable: null,
newElement: null,
editingTextElement: null,
editingGroupId: null,
editingLinearElement: null,
activeTool: {
type: "selection",
customType: null,
locked: DEFAULT_ELEMENT_PROPS.locked,
fromSelection: false,
lastActiveTool: null
},
penMode: false,
penDetected: false,
errorMessage: null,
exportBackground: true,
exportScale: defaultExportScale,
exportEmbedScene: false,
exportWithDarkMode: false,
fileHandle: null,
gridSize: DEFAULT_GRID_SIZE,
gridStep: DEFAULT_GRID_STEP,
gridModeEnabled: false,
isBindingEnabled: true,
defaultSidebarDockedPreference: false,
isLoading: false,
isResizing: false,
isRotating: false,
lastPointerDownWith: "mouse",
multiElement: null,
name: null,
contextMenu: null,
openMenu: null,
openPopup: null,
openSidebar: null,
openDialog: null,
pasteDialog: { shown: false, data: null },
previousSelectedElementIds: {},
resizingElement: null,
scrolledOutside: false,
scrollX: 0,
scrollY: 0,
selectedElementIds: {},
hoveredElementIds: {},
selectedGroupIds: {},
selectedElementsAreBeingDragged: false,
selectionElement: null,
shouldCacheIgnoreZoom: false,
stats: {
open: false,
panels: STATS_PANELS.generalStats | STATS_PANELS.elementProperties
},
startBoundElement: null,
suggestedBindings: [],
frameRendering: { enabled: true, clip: true, name: true, outline: true },
frameToHighlight: null,
editingFrame: null,
elementsToHighlight: null,
toast: null,
viewBackgroundColor: COLOR_PALETTE.white,
zenModeEnabled: false,
zoom: {
value: 1
},
viewModeEnabled: false,
showHyperlinkPopup: false,
selectedLinearElement: null,
snapLines: [],
originSnapOffset: {
x: 0,
y: 0
},
objectsSnapModeEnabled: false,
userToFollow: null,
followedBy: /* @__PURE__ */ new Set(),
isCropping: false,
croppingElementId: null,
searchMatches: null,
lockedMultiSelections: {},
activeLockedId: null,
canvasPageSettings: {
enabled: true,
width: 794,
// A4 width at 96 DPI
height: 1123,
// A4 height at 96 DPI
backgroundColor: "#ffffff",
showBorder: true
}
};
};
var APP_STATE_STORAGE_CONF = /* @__PURE__ */ ((config) => config)({
showWelcomeScreen: { browser: true, export: false, server: false },
theme: { browser: true, export: false, server: false },
collaborators: { browser: false, export: false, server: false },
currentChartType: { browser: true, export: false, server: false },
currentItemBackgroundColor: { browser: true, export: false, server: false },
currentItemEndArrowhead: { browser: true, export: false, server: false },
currentItemFillStyle: { browser: true, export: false, server: false },
currentItemFontFamily: { browser: true, export: false, server: false },
currentItemFontSize: { browser: true, export: false, server: false },
currentItemRoundness: {
browser: true,
export: false,
server: false
},
currentItemArrowType: {
browser: true,
export: false,
server: false
},
currentItemOpacity: { browser: true, export: false, server: false },
currentItemRoughness: { browser: true, export: false, server: false },
currentItemStartArrowhead: { browser: true, export: false, server: false },
currentItemStrokeColor: { browser: true, export: false, server: false },
currentItemStrokeStyle: { browser: true, export: false, server: false },
currentItemStrokeWidth: { browser: true, export: false, server: false },
currentItemTextAlign: { browser: true, export: false, server: false },
currentHoveredFontFamily: { browser: false, export: false, server: false },
cursorButton: { browser: true, export: false, server: false },
activeEmbeddable: { browser: false, export: false, server: false },
newElement: { browser: false, export: false, server: false },
editingTextElement: { browser: false, export: false, server: false },
editingGroupId: { browser: true, export: false, server: false },
editingLinearElement: { browser: false, export: false, server: false },
activeTool: { browser: true, export: false, server: false },
penMode: { browser: true, export: false, server: false },
penDetected: { browser: true, export: false, server: false },
errorMessage: { browser: false, export: false, server: false },
exportBackground: { browser: true, export: false, server: false },
exportEmbedScene: { browser: true, export: false, server: false },
exportScale: { browser: true, export: false, server: false },
exportWithDarkMode: { browser: true, export: false, server: false },
fileHandle: { browser: false, export: false, server: false },
gridSize: { browser: true, export: true, server: true },
gridStep: { browser: true, export: true, server: true },
gridModeEnabled: { browser: true, export: true, server: true },
height: { browser: false, export: false, server: false },
isBindingEnabled: { browser: false, export: false, server: false },
defaultSidebarDockedPreference: {
browser: true,
export: false,
server: false
},
isLoading: { browser: false, export: false, server: false },
isResizing: { browser: false, export: false, server: false },
isRotating: { browser: false, export: false, server: false },
lastPointerDownWith: { browser: true, export: false, server: false },
multiElement: { browser: false, export: false, server: false },
name: { browser: true, export: false, server: false },
offsetLeft: { browser: false, export: false, server: false },
offsetTop: { browser: false, export: false, server: false },
contextMenu: { browser: false, export: false, server: false },
openMenu: { browser: true, export: false, server: false },
openPopup: { browser: false, export: false, server: false },
openSidebar: { browser: true, export: false, server: false },
openDialog: { browser: false, export: false, server: false },
pasteDialog: { browser: false, export: false, server: false },
previousSelectedElementIds: { browser: true, export: false, server: false },
resizingElement: { browser: false, export: false, server: false },
scrolledOutside: { browser: true, export: false, server: false },
scrollX: { browser: true, export: false, server: false },
scrollY: { browser: true, export: false, server: false },
selectedElementIds: { browser: true, export: false, server: false },
hoveredElementIds: { browser: false, export: false, server: false },
selectedGroupIds: { browser: true, export: false, server: false },
selectedElementsAreBeingDragged: {
browser: false,
export: false,
server: false
},
selectionElement: { browser: false, export: false, server: false },
shouldCacheIgnoreZoom: { browser: true, export: false, server: false },
stats: { browser: true, export: false, server: false },
startBoundElement: { browser: false, export: false, server: false },
suggestedBindings: { browser: false, export: false, server: false },
frameRendering: { browser: false, export: false, server: false },
frameToHighlight: { browser: false, export: false, server: false },
editingFrame: { browser: false, export: false, server: false },
elementsToHighlight: { browser: false, export: false, server: false },
toast: { browser: false, export: false, server: false },
viewBackgroundColor: { browser: true, export: true, server: true },
width: { browser: false, export: false, server: false },
zenModeEnabled: { browser: true, export: false, server: false },
zoom: { browser: true, export: false, server: false },
viewModeEnabled: { browser: false, export: false, server: false },
showHyperlinkPopup: { browser: false, export: false, server: false },
selectedLinearElement: { browser: true, export: false, server: false },
snapLines: { browser: false, export: false, server: false },
originSnapOffset: { browser: false, export: false, server: false },
objectsSnapModeEnabled: { browser: true, export: false, server: false },
userToFollow: { browser: false, export: false, server: false },
followedBy: { browser: false, export: false, server: false },
isCropping: { browser: false, export: false, server: false },
croppingElementId: { browser: false, export: false, server: false },
searchMatches: { browser: false, export: false, server: false },
lockedMultiSelections: { browser: true, export: true, server: true },
activeLockedId: { browser: false, export: false, server: false },
canvasPageSettings: { browser: true, export: true, server: true }
});
var _clearAppStateForStorage = (appState, exportType) => {
const stateForExport = {};
for (const key of Object.keys(appState)) {
const propConfig = APP_STATE_STORAGE_CONF[key];
if (propConfig?.[exportType]) {
const nextValue = appState[key];
stateForExport[key] = nextValue;
}
}
return stateForExport;
};
var cleanAppStateForExport = (appState) => {
return _clearAppStateForStorage(appState, "export");
};
var clearAppStateForDatabase = (appState) => {
return _clearAppStateForStorage(appState, "server");
};
var isEraserActive = ({
activeTool
}) => activeTool.type === "eraser";
var isHandToolActive = ({
activeTool
}) => {
return activeTool.type === "hand";
};
// errors.ts
var CanvasError = class extends Error {
constructor(message = "Couldn't export canvas.", name = "CANVAS_ERROR") {
super();
this.name = name;
this.message = message;
}
};
var AbortError = class extends DOMException {
constructor(message = "Request Aborted") {
super(message, "AbortError");
}
};
var ImageSceneDataError = class extends Error {
constructor(message = "Image Scene Data Error", code = "IMAGE_SCENE_DATA_ERROR") {
super(message);
__publicField(this, "code");
this.name = "EncodingError";
this.code = code;
}
};
var WorkerUrlNotDefinedError = class extends Error {
constructor(message = "Worker URL is not defined!", code = "WORKER_URL_NOT_DEFINED") {
super(message);
__publicField(this, "code");
this.name = "WorkerUrlNotDefinedError";
this.code = code;
}
};
var WorkerInTheMainChunkError = class extends Error {
constructor(message = "Worker has to be in a separate chunk!", code = "WORKER_IN_THE_MAIN_CHUNK") {
super(message);
__publicField(this, "code");
this.name = "WorkerInTheMainChunkError";
this.code = code;
}
};
var ExcalidrawError = class extends Error {
constructor(message) {
super(message);
this.name = "ExcalidrawError";
}
};
// scene/index.ts
import {
isSomeElementSelected,
getElementsWithinSelection,
getSelectedElements,
getTargetElements
} from "@testbank-inc/element";
// scene/scroll.ts
import { getVisibleElements } from "@testbank-inc/element";
import {
sceneCoordsToViewportCoords,
viewportCoordsToSceneCoords
} from "@testbank-inc/common";
import { getClosestElementBounds } from "@testbank-inc/element";
import { getCommonBounds } from "@testbank-inc/element";
var isOutsideViewPort = (appState, cords) => {
const [x1, y1, x2, y2] = cords;
const { x: viewportX1, y: viewportY1 } = sceneCoordsToViewportCoords(
{ sceneX: x1, sceneY: y1 },
appState
);
const { x: viewportX2, y: viewportY2 } = sceneCoordsToViewportCoords(
{ sceneX: x2, sceneY: y2 },
appState
);
return viewportX2 - viewportX1 > appState.width || viewportY2 - viewportY1 > appState.height;
};
var centerScrollOn = ({
scenePoint,
viewportDimensions,
zoom,
offsets
}) => {
let scrollX = (viewportDimensions.width - (offsets?.right ?? 0)) / 2 / zoom.value - scenePoint.x;
scrollX += (offsets?.left ?? 0) / 2 / zoom.value;
let scrollY = (viewportDimensions.height - (offsets?.bottom ?? 0)) / 2 / zoom.value - scenePoint.y;
scrollY += (offsets?.top ?? 0) / 2 / zoom.value;
return {
scrollX,
scrollY
};
};
var calculateScrollCenter = (elements, appState) => {
elements = getVisibleElements(elements);
if (!elements.length) {
return {
scrollX: 0,
scrollY: 0
};
}
let [x1, y1, x2, y2] = getCommonBounds(elements);
if (isOutsideViewPort(appState, [x1, y1, x2, y2])) {
[x1, y1, x2, y2] = getClosestElementBounds(
elements,
viewportCoordsToSceneCoords(
{ clientX: appState.scrollX, clientY: appState.scrollY },
appState
)
);
}
const centerX = (x1 + x2) / 2;
const centerY = (y1 + y2) / 2;
return centerScrollOn({
scenePoint: { x: centerX, y: centerY },
viewportDimensions: { width: appState.width, height: appState.height },
zoom: appState.zoom
});
};
var constrainScrollToPageBounds = (scrollX, scrollY, appState) => {
const { canvasPageSettings, zoom, width, height } = appState;
if (!canvasPageSettings?.enabled) {
return { scrollX, scrollY };
}
const pageWidth = canvasPageSettings.width;
const pageHeight = canvasPageSettings.height;
const viewportWidth = width / zoom.value;
const viewportHeight = height / zoom.value;
const maxScrollX = 0;
const maxScrollY = 0;
const minScrollX = Math.min(0, -(pageWidth - viewportWidth));
const minScrollY = Math.min(0, -(pageHeight - viewportHeight));
return {
scrollX: Math.max(minScrollX, Math.min(maxScrollX, scrollX)),
scrollY: Math.max(minScrollY, Math.min(maxScrollY, scrollY))
};
};
var constrainZoomForPageBounds = (zoom, appState) => {
const { canvasPageSettings, width, height } = appState;
if (!canvasPageSettings?.enabled) {
return zoom;
}
const pageWidth = canvasPageSettings.width;
const pageHeight = canvasPageSettings.height;
const scaleX = width / pageWidth;
const scaleY = height / pageHeight;
const minZoom = Math.max(scaleX, scaleY);
const maxZoom = 5;
return Math.max(minZoom, Math.min(maxZoom, zoom));
};
// scene/index.ts
import {
hasBackground,
hasStrokeWidth,
hasStrokeStyle,
canHaveArrowheads,
canChangeRoundness
} from "@testbank-inc/element";
// scene/normalize.ts
import { MAX_ZOOM, MIN_ZOOM } from "@testbank-inc/common";
import { clamp, round } from "@testbank-inc/math";
var getNormalizedZoom = (zoom) => {
return clamp(round(zoom, 6), MIN_ZOOM, MAX_ZOOM);
};
var getNormalizedGridSize = (gridStep) => {
return clamp(Math.round(gridStep), 1, 100);
};
var getNormalizedGridStep = (gridStep) => {
return clamp(Math.round(gridStep), 1, 100);
};
// scene/export.ts
import rough from "roughjs/bin/rough";
import {
DEFAULT_EXPORT_PADDING,
FRAME_STYLE as FRAME_STYLE3,
FONT_FAMILY as FONT_FAMILY2,
SVG_NS as SVG_NS2,
THEME as THEME3,
THEME_FILTER as THEME_FILTER2,
MIME_TYPES as MIME_TYPES5,
EXPORT_DATA_TYPES as EXPORT_DATA_TYPES2,
arrayToMap,
distance,
getFontString as getFontString2,
toBrandedType
} from "@testbank-inc/common";
import { getCommonBounds as getCommonBounds2, getElementAbsoluteCoords as getElementAbsoluteCoords4 } from "@testbank-inc/element";
import {
getInitializedImageElements,
updateImageCache
} from "@testbank-inc/element";
import { newElementWith } from "@testbank-inc/element";
import { isFrameLikeElement } from "@testbank-inc/element";
import {
getElementsOverlappingFrame,
getFrameLikeElements,
getFrameLikeTitle,
getRootElements
} from "@testbank-inc/element";
import { syncInvalidIndices } from "@testbank-inc/element";
import { newTextElement } from "@testbank-inc/element";
// data/encode.ts
import { deflate, inflate } from "pako";
// data/encryption.ts
import { ENCRYPTION_KEY_BITS } from "@testbank-inc/common";
// data/encode.ts
var toByteString = (data) => {
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data instanceof Uint8Array ? data : new Uint8Array(data);
let bstring = "";
for (const byte of bytes) {
bstring += String.fromCharCode(byte);
}
return bstring;
};
var byteStringToArrayBuffer = (byteString) => {
const buffer = new ArrayBuffer(byteString.length);
const bufferView = new Uint8Array(buffer);
for (let i = 0, len = byteString.length; i < len; i++) {
bufferView[i] = byteString.charCodeAt(i);
}
return buffer;
};
var byteStringToString = (byteString) => {
return new TextDecoder("utf-8").decode(byteStringToArrayBuffer(byteString));
};
var stringToBase64 = (str, isByteString = false) => {
return isByteString ? window.btoa(str) : window.btoa(toByteString(str));
};
var base64ToString = (base64, isByteString = false) => {
return isByteString ? window.atob(base64) : byteStringToString(window.atob(base64));
};
var encode = ({
text,
compress
}) => {
let deflated;
if (compress !== false) {
try {
deflated = toByteString(deflate(text));
} catch (error) {
console.error("encode: cannot deflate", error);
}
}
return {
version: "1",
encoding: "bstring",
compressed: !!deflated,
encoded: deflated || toByteString(text)
};
};
var decode = (data) => {
let decoded;
switch (data.encoding) {
case "bstring":
decoded = data.compressed ? data.encoded : byteStringToString(data.encoded);
break;
default:
throw new Error(`decode: unknown encoding "${data.encoding}"`);
}
if (data.compressed) {
return inflate(new Uint8Array(byteStringToArrayBuffer(decoded)), {
to: "string"
});
}
return decoded;
};
// data/json.ts
import {
DEFAULT_FILENAME,
EXPORT_DATA_TYPES,
getExportSource,
MIME_TYPES as MIME_TYPES2,
VERSIONS
} from "@testbank-inc/common";
import {
clearElementsForDatabase,
clearElementsForExport
} from "@testbank-inc/element";
// data/filesystem.ts
import {
fileOpen as _fileOpen,
fileSave as _fileSave,
supported as nativeFileSystemSupported
} from "browser-fs-access";
import { EVENT, MIME_TYPES, debounce } from "@testbank-inc/common";
var INPUT_CHANGE_INTERVAL_MS = 500;
var fileOpen = (opts) => {
const mimeTypes = opts.extensions?.reduce((mimeTypes2, type) => {
mimeTypes2.push(MIME_TYPES[type]);
return mimeTypes2;
}, []);
const extensions = opts.extensions?.reduce((acc, ext) => {
if (ext === "jpg") {
return acc.concat(".jpg", ".jpeg");
}
return acc.concat(`.${ext}`);
}, []);
return _fileOpen({
description: opts.description,
extensions,
mimeTypes,
multiple: opts.multiple ?? false,
legacySetup: (resolve, reject, input) => {
const scheduleRejection = debounce(reject, INPUT_CHANGE_INTERVAL_MS);
const focusHandler = () => {
checkForFile();
document.addEventListener(EVENT.KEYUP, scheduleRejection);
document.addEventListener(EVENT.POINTER_UP, scheduleRejection);
scheduleRejection();
};
const checkForFile = () => {
if (input.files?.length) {
const ret = opts.multiple ? [...input.files] : input.files[0];
resolve(ret);
}
};
requestAnimationFrame(() => {
window.addEventListener(EVENT.FOCUS, focusHandler);
});
const interval = window.setInterval(() => {
checkForFile();
}, INPUT_CHANGE_INTERVAL_MS);
return (rejectPromise) => {
clearInterval(interval);
scheduleRejection.cancel();
window.removeEventListener(EVENT.FOCUS, focusHandler);
document.removeEventListener(EVENT.KEYUP, scheduleRejection);
document.removeEventListener(EVENT.POINTER_UP, scheduleRejection);
if (rejectPromise) {
console.warn("Opening the file was canceled (legacy-fs).");
rejectPromise(new AbortError());
}
};
}
});
};
var fileSave = (blob, opts) => {
return _fileSave(
blob,
{
fileName: `${opts.name}.${opts.extension}`,
description: opts.description,
extensions: [`.${opts.extension}`],
mimeTypes: opts.mimeTypes
},
opts.fileHandle
);
};
// data/json.ts
var filterOutDeletedFiles = (elements, files) => {
const nextFiles = {};
for (const element of elements) {
if (!element.isDeleted && "fileId" in element && element.fileId && files[element.fileId]) {
nextFiles[element.fileId] = files[element.fileId];
}
}
return nextFiles;
};
var serializeAsJSON = (elements, appState, files, type) => {
const data = {
type: EXPORT_DATA_TYPES.excalidraw,
version: VERSIONS.excalidraw,
source: getExportSource(),
elements: type === "local" ? clearElementsForExport(elements) : clearElementsForDatabase(elements),
appState: type === "local" ? cleanAppStateForExport(appState) : clearAppStateForDatabase(appState),
files: type === "local" ? filterOutDeletedFiles(elements, files) : (
// will be stripped from JSON
void 0
)
};
return JSON.stringify(data, null, 2);
};
var saveAsJSON = async (elements, appState, files, name = appState.name || DEFAULT_FILENAME) => {
const serialized = serializeAsJSON(elements, appState, files, "local");
const blob = new Blob([serialized], {
type: MIME_TYPES2.excalidraw
});
const fileHandle = await fileSave(blob, {
name,
extension: "excalidraw",
description: "Excalidraw file",
fileHandle: isImageFileHandle(appState.fileHandle) ? null : appState.fileHandle
});
return { fileHandle };
};
var loadFromJSON = async (localAppState, localElements) => {
const file = await fileOpen({
description: "Excalidraw files"
// ToDo: Be over-permissive until https://bugs.webkit.org/show_bug.cgi?id=34442
// gets resolved. Else, iOS users cannot open `.excalidraw` files.
// extensions: ["json", "excalidraw", "png", "svg"],
});
return loadFromBlob(
await normalizeFile(file),
localAppState,
localElements,
file.handle
);
};
var isValidExcalidrawData = (data) => {
return data?.type === EXPORT_DATA_TYPES.excalidraw && (!data.elements || Array.isArray(data.elements) && (!data.appState || typeof data.appState === "object"));
};
var isValidLibrary = (json) => {
return typeof json === "object" && json && json.type === EXPORT_DATA_TYPES.excalidrawLibrary && (json.version === 1 || json.version === 2);
};
var serializeLibraryAsJSON = (libraryItems) => {
const data = {
type: EXPORT_DATA_TYPES.excalidrawLibrary,
version: VERSIONS.excalidrawLibrary,
source: getExportSource(),
libraryItems
};
return JSON.stringify(data, null, 2);
};
var saveLibraryAsJSON = async (libraryItems) => {
const serialized = serializeLibraryAsJSON(libraryItems);
await fileSave(
new Blob([serialized], {
type: MIME_TYPES2.excalidrawlib
}),
{
name: "library",
extension: "excalidrawlib",
description: "Excalidraw library file"
}
);
};
// fonts/Fonts.ts
import {
FONT_FAMILY,
FONT_FAMILY_FALLBACKS,
CJK_HAND_DRAWN_FALLBACK_FONT,
WINDOWS_EMOJI_FALLBACK_FONT,
getFontFamilyFallbacks
} from "@testbank-inc/common";
import { getContainerElement } from "@testbank-inc/element";
import { charWidth } from "@testbank-inc/element";
import { containsCJK } from "@testbank-inc/element";
import {
FONT_METADATA,
getFontString,
PromisePool,
promiseTry as promiseTry3
} from "@testbank-inc/common";
import { ShapeCache } from "@testbank-inc/element";
import { isTextElement } from "@testbank-inc/element";
// fonts/Cascadia/CascadiaCode-Regular.woff2
var CascadiaCode_Regular_default = "./fonts/Cascadia/CascadiaCode-Regular.woff2";
// fonts/Cascadia/index.ts
var CascadiaFontFaces = [
{
uri: CascadiaCode_Regular_default
}
];
// fonts/ComicShanns/ComicShanns-Regular-279a7b317d12eb88de06167bd672b4b4.woff2
var ComicShanns_Regular_279a7b317d12eb88de06167bd672b4b4_default = "./fonts/ComicShanns/ComicShanns-Regular-279a7b317d12eb88de06167bd672b4b4.woff2";
// fonts/ComicShanns/ComicShanns-Regular-6e066e8de2ac57ea9283adb9c24d7f0c.woff2
var ComicShanns_Regular_6e066e8de2ac57ea9283adb9c24d7f0c_default = "./fonts/ComicShanns/ComicShanns-Regular-6e066e8de2ac57ea9283adb9c24d7f0c.woff2";
// fonts/ComicShanns/ComicShanns-Regular-dc6a8806fa96795d7b3be5026f989a17.woff2
var ComicShanns_Regular_dc6a8806fa96795d7b3be5026f989a17_default = "./fonts/ComicShanns/ComicShanns-Regular-dc6a8806fa96795d7b3be5026f989a17.woff2";
// fonts/ComicShanns/ComicShanns-Regular-fcb0fc02dcbee4c9846b3e2508668039.woff2
var ComicShanns_Regular_fcb0fc02dcbee4c9846b3e2508668039_default = "./fonts/ComicShanns/ComicShanns-Regular-fcb0fc02dcbee4c9846b3e2508668039.woff2";
// fonts/ComicShanns/index.ts
var ComicShannsFontFaces = [
{
uri: ComicShanns_Regular_279a7b317d12eb88de06167bd672b4b4_default,
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: ComicShanns_Regular_fcb0fc02dcbee4c9846b3e2508668039_default,
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: ComicShanns_Regular_dc6a8806fa96795d7b3be5026f989a17_default,
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: ComicShanns_Regular_6e066e8de2ac57ea9283adb9c24d7f0c_default,
descriptors: { unicodeRange: "U+3bb" }
}
];
// fonts/Emoji/index.ts
import { LOCAL_FONT_PROTOCOL } from "@testbank-inc/common";
var EmojiFontFaces = [
{
uri: LOCAL_FONT_PROTOCOL
}
];
// fonts/ExcalidrawFontFace.ts
import { promiseTry as promiseTry2, LOCAL_FONT_PROTOCOL as LOCAL_FONT_PROTOCOL2 } from "@testbank-inc/common";
// subset/subset-main.ts
import { isServerEnv, promiseTry } from "@testbank-inc/common";
// workers.ts
import { debounce as debounce2 } from "@testbank-inc/common";
var IdleWorker = class {
constructor(workerUrl) {
__publicField(this, "instance");
/**
* Use to prolong the worker's life by `workerTTL` or terminate it with a flush immediately.
*/
__publicField(this, "debounceTerminate");
this.instance = new Worker(workerUrl, { type: "module" });
}
};
var WorkerPool = class _WorkerPool {
constructor(workerUrl, options) {
__publicField(this, "idleWorkers", /* @__PURE__ */ new Set());
__publicField(this, "workerUrl");
__publicField(this, "workerTTL");
this.workerUrl = workerUrl;
this.workerTTL = options.ttl || 1e3;
}
/**
* Create a new worker pool.
*
* @param workerUrl - The URL of the worker file.
* @param options - The options for the worker pool.
* @throws If the worker is bundled into the main chunk.
* @returns A new worker pool instance.
*/
static create(workerUrl, options = {}) {
if (!workerUrl) {
throw new WorkerUrlNotDefinedError();
}
if (!import.meta.url || workerUrl.toString() === import.meta.url) {
throw new WorkerInTheMainChunkError();
}
return new _WorkerPool(workerUrl, options);
}
/**
* Take idle worker from the pool or create a new one and post a message to it.
*/
async postMessage(data, options) {
let worker;
const idleWorker = Array.from(this.idleWorkers).shift();
if (idleWorker) {
this.idleWorkers.delete(idleWorker);
worker = idleWorker;
} else {
worker = await this.createWorker();
}
return new Promise((resolve, reject) => {
worker.instance.onmessage = this.onMessageHandler(worker, resolve);
worker.instance.onerror = this.onErrorHandler(worker, reject);
worker.instance.postMessage(data, options);
worker.debounceTerminate(
() => reject(
new Error(`Active worker did not respond for ${this.workerTTL}ms!`)
)
);
});
}
/**
* Terminate the idle workers in the pool.
*/
async clear() {
for (const worker of this.idleWorkers) {
worker.debounceTerminate.cancel();
worker.instance.terminate();
}
this.idleWorkers.clear();
}
/**
* Used to get a worker from the pool or create a new one if there is no idle available.
*/
async createWorker() {
const worker = new IdleWorker(this.workerUrl);
worker.debounceTerminate = debounce2((reject) => {
worker.instance.terminate();
if (this.idleWorkers.has(worker)) {
this.idleWorkers.delete(worker);
console.debug(
"Job finished! Idle worker has been released from the pool."
);
} else if (reject) {
reject();
} else {
console.error("Worker has been terminated!");
}
}, this.workerTTL);
return worker;
}
onMessageHandler(worker, resolve) {
return (e) => {
worker.debounceTerminate();
this.idleWorkers.add(worker);
resolve(e.data);
};
}
onErrorHandler(worker, reject) {
return (e) => {
worker.debounceTerminate(() => reject(e));
worker.debounceTerminate.flush();
this.clear();
};
}
};
// subset/subset-main.ts
var shouldUseWorkers = typeof Worker !== "undefined";
var subsetWoff2GlyphsByCodepoints = async (arrayBuffer, codePoints) => {
const { Commands, subsetToBase64, toBase64 } = await lazyLoadSharedSubsetChunk();
if (!shouldUseWorkers) {
return subsetToBase64(arrayBuffer, codePoints);
}
return promiseTry(async () => {
try {
const workerPool2 = await getOrCreateWorkerPool();
const arrayBufferCopy = arrayBuffer.slice(0);
const result = await workerPool2.postMessage(
{
command: Commands.Subset,
arrayBuffer: arrayBufferCopy,
codePoints
},
{ transfer: [arrayBufferCopy] }
);
return toBase64(result);
} catch (e) {
shouldUseWorkers = false;
if (
// don't log the expected errors server-side
!(isServerEnv() && (e instanceof WorkerUrlNotDefinedError || e instanceof WorkerInTheMainChunkError))
) {
console.error(
"Failed to use workers for subsetting, falling back to the main thread.",
e
);
}
return subsetToBase64(arrayBuffer, codePoints);
}
});
};
var subsetWorker = null;
var subsetShared = null;
var lazyLoadWorkerSubsetChunk = async () => {
if (!subsetWorker) {
subsetWorker = import("./subset-worker.chunk.js");
}
return subsetWorker;
};
var lazyLoadSharedSubsetChunk = async () => {
if (!subsetShared) {
subsetShared = import("./subset-shared.chunk.js");
}
return subsetShared;
};
var workerPool = null;
var getOrCreateWorkerPool = () => {
if (!workerPool) {
workerPool = promiseTry(async () => {
const { WorkerUrl } = await lazyLoadWorkerSubsetChunk();
const pool = WorkerPool.create(WorkerUrl);
return pool;
});
}
return workerPool;
};
// fonts/ExcalidrawFontFace.ts
var _ExcalidrawFontFace = class _ExcalidrawFontFace {
constructor(family, uri, descriptors) {
__publicField(this, "urls");
__publicField(this, "fontFace");
this.urls = _ExcalidrawFontFace.createUrls(uri);
const sources = this.urls.map((url) => `url(${url}) ${_ExcalidrawFontFace.getFormat(url)}`).join(", ");
this.fontFace = new FontFace(family, sources, {
display: "swap",
style: "normal",
weight: "400",
...descriptors
});
}
/**
* Generates CSS `@font-face` definition with the (subsetted) font source as a data url for the characters within the unicode range.
*
* Retrieves `undefined` otherwise.
*/
toCSS(characters) {
if (!this.getUnicodeRangeRegex().test(characters)) {
return;
}
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}); }`
);
}
/**
* Tries to fetch woff2 content, based on the registered urls (from first to last, treated as fallbacks).
*
* @returns base64 with subsetted glyphs based on the passed codepoint, last defined url otherwise
*/
async getContent(codePoints) {
let i = 0;
const errorMessages = [];
while (i < this.urls.length) {
const url = this.urls[i];
try {
const arrayBuffer = await this.fetchFont(url);
const base64 = await subsetWoff2GlyphsByCodepoints(
arrayBuffer,
codePoints
);
return base64;
} catch (e) {
errorMessages.push(`"${url.toString()}" returned error "${e}"`);
}
i++;
}
console.error(
`Failed to fetch font family "${this.fontFace.family}"`,
JSON.stringify(errorMessages, void 0, 2)
);
return this.urls.length ? this.urls[this.urls.length - 1].toString() : "";
}
fetchFont(url) {
return promiseTry2(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"
}
});
if (!response.ok) {
const urlString = url instanceof URL ? url.toString() : "dataurl";
throw new Error(
`Failed to fetch "${urlString}": ${response.statusText}`
);
}
const arrayBuffer = await response.arrayBuffer();
return arrayBuffer;
});
}
getUnicodeRangeRegex() {
const unicodeRangeRegex = this.fontFace.unicodeRange.split(/,\s*/).map((range) => {
const [start, end] = range.replace("U+", "").split("-");
if (end) {
return `\\u{${start}}-\\u{${end}}`;
}
return `\\u{${start}}`;
}).join("");
return new RegExp(`[${unicodeRangeRegex}]`, "u");
}
static createUrls(uri) {
if (uri.startsWith("data")) {
return [uri];
}
if (uri.startsWith(LOCAL_FONT_PROTOCOL2)) {
return [];
}
if (uri.startsWith("http")) {
return [new URL(uri)];
}
const assetUrl = uri.replace(/^\/+/, "");
const urls = [];
if (typeof window.EXCALIDRAW_ASSET_PATH === "string") {
const normalizedBaseUrl = this.normalizeBaseUrl(
window.EXCALIDRAW_ASSET_PATH
);
urls.push(new URL(assetUrl, normalizedBaseUrl));
} else if (Array.isArray(window.EXCALIDRAW_ASSET_PATH)) {
window.EXCALIDRAW_ASSET_PATH.forEach((path) => {
const normalizedBaseUrl = this.normalizeBaseUrl(path);
urls.push(new URL(assetUrl, normalizedBaseUrl));
});
}
urls.push(new URL(assetUrl, _ExcalidrawFontFace.ASSETS_FALLBACK_URL));
return urls;
}
static getFormat(url) {
if (!(url instanceof URL)) {
return "";
}
try {
const parts = new URL(url).pathname.split(".");
if (parts.length === 1) {
return "";
}
return `format('${parts.pop()}')`;
} catch (error) {
return "";
}
}
static normalizeBaseUrl(baseUrl) {
let result = baseUrl;
if (/^\.?\//.test(result)) {
result = new URL(
result.replace(/^\.?\/+/, ""),
window?.location?.origin
).toString();
}
result = `${result.replace(/\/+$/, "")}/`;
return result;
}
};
__publicField(_ExcalidrawFontFace, "ASSETS_FALLBACK_URL", `https://esm.sh/${define_import_meta_env_default.PKG_NAME ? `${define_import_meta_env_default.PKG_NAME}@${define_import_meta_env_default.PKG_VERSION}` : "@excalidraw/excalidraw"}/dist/prod/`);
var ExcalidrawFontFace = _ExcalidrawFontFace;
// fonts/Excalifont/Excalifont-Regular-349fac6ca4700ffec595a7150a0d1e1d.woff2
var Excalifont_Regular_349fac6ca4700ffec595a7150a0d1e1d_default = "./fonts/Excalifont/Excalifont-Regular-349fac6ca4700ffec595a7150a0d1e1d.woff2";
// fonts/Excalifont/Excalifont-Regular-3f2c5db56cc93c5a6873b1361d730c16.woff2
var Excalifont_Regular_3f2c5db56cc93c5a6873b1361d730c16_default = "./fonts/Excalifont/Excalifont-Regular-3f2c5db56cc93c5a6873b1361d730c16.woff2";
// fonts/Excalifont/Excalifont-Regular-41b173a47b57366892116a575a43e2b6.woff2
var Excalifont_Regular_41b173a47b57366892116a575a43e2b6_default = "./fonts/Excalifont/Excalifont-Regular-41b173a47b57366892116a575a43e2b6.woff2";
// fonts/Excalifont/Excalifont-Regular-623ccf21b21ef6b3a0d87738f77eb071.woff2
var Excalifont_Regular_623ccf21b21ef6b3a0d87738f77eb071_default = "./fonts/Excalifont/Excalifont-Regular-623ccf21b21ef6b3a0d87738f77eb071.woff2";
// fonts/Excalifont/Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.woff2
var Excalifont_Regular_a88b72a24fb54c9f94e3b5fdaa7481c9_default = "./fonts/Excalifont/Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.woff2";
// fonts/Excalifont/Excalifont-Regular-b9dcf9d2e50a1eaf42fc664b50a3fd0d.woff2
var Excalifont_Regular_b9dcf9d2e50a1eaf42fc664b50a3fd0d_default = "./fonts/Excalifont/Excalifont-Regular-b9dcf9d2e50a1eaf42fc664b50a3fd0d.woff2";
// fonts/Excalifont/Excalifont-Regular-be310b9bcd4f1a43f571c46df7809174.woff2
var Excalifont_Regular_be310b9bcd4f1a43f571c46df7809174_default = "./fonts/Excalifont/Excalifont-Regular-be310b9bcd4f1a43f571c46df7809174.woff2";
// fonts/Excalifont/index.ts
var ExcalifontFontFaces = [
{
uri: Excalifont_Regular_a88b72a24fb54c9f94e3b5fdaa7481c9_default,
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: Excalifont_Regular_be310b9bcd4f1a43f571c46df7809174_default,
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: Excalifont_Regular_b9dcf9d2e50a1eaf42fc664b50a3fd0d_default, descriptors: { unicodeRange: "U+400-45f,U+490-491,U+2116" } },
{
uri: Excalifont_Regular_41b173a47b57366892116a575a43e2b6_default,
descriptors: {
unicodeRange: "U+37e,U+384-38a,U+38c,U+38e-393,U+395-3a1,U+3a3-3a8,U+3aa-3cf,U+3d7"
}
},
{
uri: Excalifont_Regular_3f2c5db56cc93c5a6873b1361d730c16_default,
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: Excalifont_Regular_349fac6ca4700ffec595a7150a0d1e1d_default,
descriptors: {
unicodeRange: "U+462-463,U+472-475,U+4d8-4d9,U+4e2-4e3,U+4e6-4e9,U+4ee-4ef"
}
},
{ uri: Excalifont_Regular_623ccf21b21ef6b3a0d87738f77eb071_default, descriptors: { unicodeRange: "U+300-301,U+303" } }
];
// fonts/Helvetica/index.ts
import { LOCAL_FONT_PROTOCOL as LOCAL_FONT_PROTOCOL3 } from "@testbank-inc/common";
var HelveticaFontFaces = [
{
uri: LOCAL_FONT_PROTOCOL3
}
];
// fonts/Liberation/LiberationSans-Regular.woff2
var LiberationSans_Regular_default = "./fonts/Liberation/LiberationSans-Regular.woff2";
// fonts/Liberation/index.ts
var LiberationFontFaces = [
{
uri: LiberationSans_Regular_default
}
];
// fonts/Lilita/index.ts
import { GOOGLE_FONTS_RANGES } from "@testbank-inc/common";
// fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2
var Lilita_Regular_i7dPIFZ9Zz_WBtRtedDbYE98RXi4EwSsbg_default = "./fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2";
// fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2
var Lilita_Regular_i7dPIFZ9Zz_WBtRtedDbYEF8RXi4EwQ_default = "./fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2";
// fonts/Lilita/index.ts
var LilitaFontFaces = [
{
uri: Lilita_Regular_i7dPIFZ9Zz_WBtRtedDbYE98RXi4EwSsbg_default,
descriptors: { unicodeRange: GOOGLE_FONTS_RANGES.LATIN_EXT }
},
{
uri: Lilita_Regular_i7dPIFZ9Zz_WBtRtedDbYEF8RXi4EwQ_default,
descriptors: { unicodeRange: GOOGLE_FONTS_RANGES.LATIN }
}
];
// fonts/Nunito/index.ts
import { GOOGLE_FONTS_RANGES as GOOGLE_FONTS_RANGES2 } from "@testbank-inc/common";
// fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXWjgevT5.woff2
var Nunito_Regular_XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXWjgevT5_default = "./fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXWjgevT5.woff2";
// fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg.woff2
var Nunito_Regular_XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg_default = "./fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg.woff2";
// fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5.woff2
var Nunito_Regular_XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5_default = "./fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5.woff2";
// fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTo3j6zbXWjgevT5.woff2
var Nunito_Regular_XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTo3j6zbXWjgevT5_default = "./fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTo3j6zbXWjgevT5.woff2";
// fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5.woff2
var Nunito_Regular_XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5_default = "./fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5.woff2";
// fonts/Nunito/index.ts
var NunitoFontFaces = [
{
uri: Nunito_Regular_XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5_default,
descriptors: {
unicodeRange: GOOGLE_FONTS_RANGES2.CYRILIC_EXT,
weight: "500"
}
},
{
uri: Nunito_Regular_XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXWjgevT5_default,
descriptors: { unicodeRange: GOOGLE_FONTS_RANGES2.CYRILIC, weight: "500" }
},
{
uri: Nunito_Regular_XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5_default,
descriptors: {
unicodeRange: GOOGLE_FONTS_RANGES2.VIETNAMESE,
weight: "500"
}
},
{
uri: Nunito_Regular_XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTo3j6zbXWjgevT5_default,
descriptors: { unicodeRange: GOOGLE_FONTS_RANGES2.LATIN_EXT, weight: "500" }
},
{
uri: Nunito_Regular_XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg_default,
descriptors: { unicodeRange: GOOGLE_FONTS_RANGES2.LATIN, weight: "500" }
}
];
// fonts/Virgil/Virgil-Regular.woff2
var Virgil_Regular_default = "./fonts/Virgil/Virgil-Regular.woff2";
// fonts/Virgil/index.ts
var VirgilFontFaces = [
{
uri: Virgil_Regular_default
}
];
// fonts/Xiaolai/Xiaolai-Regular-019d66dcad46dc156b162d267f981c20.woff2
var Xiaolai_Regular_019d66dcad46dc156b162d267f981c20_default = "./fonts/Xiaolai/Xiaolai-Regular-019d66dcad46dc156b162d267f981c20.woff2";
// fonts/Xiaolai/Xiaolai-Regular-04b718e5623574919c8b0dea5f301444.woff2
var Xiaolai_Regular_04b718e5623574919c8b0dea5f301444_default = "./fonts/Xiaolai/Xiaolai-Regular-04b718e5623574919c8b0dea5f301444.woff2";
// fonts/Xiaolai/Xiaolai-Regular-069e77aac84590e2e991d0a0176d34f2.woff2
var Xiaolai_Regular_069e77aac84590e2e991d0a0176d34f2_default = "./fonts/Xiaolai/Xiaolai-Regular-069e77aac84590e2e991d0a0176d34f2.woff2";
// fonts/Xiaolai/Xiaolai-Regular-06c77b8c66e51ed6c63ccb502dd8b8af.woff2
var Xiaolai_Regular_06c77b8c66e51ed6c63ccb502dd8b8af_default = "./fonts/Xiaolai/Xiaolai-Regular-06c77b8c66e51ed6c63ccb502dd8b8af.woff2";
// fonts/Xiaolai/Xiaolai-Regular-08e0dc436ad0ad61ba5558db0674d762.woff2
var Xiaolai_Regular_08e0dc436ad0ad61ba5558db0674d762_default = "./fonts/Xiaolai/Xiaolai-Regular-08e0dc436ad0ad61ba5558db0674d762.woff2";
// fonts/Xiaolai/Xiaolai-Regular-093b9ef39a46ceae95a1df18a0a3a326.woff2
var Xiaolai_Regular_093b9ef39a46ceae95a1df18a0a3a326_default = "./fonts/Xiaolai/Xiaolai-Regular-093b9ef39a46ceae95a1df18a0a3a326.woff2";
// fonts/Xiaolai/Xiaolai-Regular-095c169f3314805276f603a362766abd.woff2
var Xiaolai_Regular_095c169f3314805276f603a362766abd_default = "./fonts/Xiaolai/Xiaolai-Regular-095c169f3314805276f603a362766abd.woff2";
// fonts/Xiaolai/Xiaolai-Regular-09850c4077f3fffe707905872e0e2460.woff2
var Xiaolai_Regular_09850c4077f3fffe707905872e0e2460_default = "./fonts/Xiaolai/Xiaolai-Regular-09850c4077f3fffe707905872e0e2460.woff2";
// fonts/Xiaolai/Xiaolai-Regular-41521fade99856108931b4768b1b2648.woff2
var Xiaolai_Regular_41521fade99856108931b4768b1b2648_default = "./fonts/Xiaolai/Xiaolai-Regular-41521fade99856108931b4768b1b2648.woff2";
// fonts/Xiaolai/Xiaolai-Regular-544fc28abe2c5c30e62383fd4dac255f.woff2
var Xiaolai_Regular_544fc28abe2c5c30e62383fd4dac255f_default = "./fonts/Xiaolai/Xiaolai-Regular-544fc28abe2c5c30e62383fd4dac255f.woff2";
// fonts/Xiaolai/Xiaolai-Regular-60a3089806700d379f11827ee9843b6b.woff2
var Xiaolai_Regular_60a3089806700d379f11827ee9843b6b_default = "./fonts/Xiaolai/Xiaolai-Regular-60a3089806700d379f11827ee9843b6b.woff2";
// fonts/Xiaolai/Xiaolai-Regular-7eb9fffd1aa890d07d0f88cc82e6cfe4.woff2
var Xiaolai_Regular_7eb9fffd1aa890d07d0f88cc82e6cfe4_default = "./fonts/Xiaolai/Xiaolai-Regular-7eb9fffd1aa890d07d0f88cc82e6cfe4.woff2";
// fonts/Xiaolai/Xiaolai-Regular-6fe5c5973cc06f74b2387a631ea36b88.woff2
var Xiaolai_Regular_6fe5c5973cc06f74b2387a631ea36b88_default = "./fonts/Xiaolai/Xiaolai-Regular-6fe5c5973cc06f74b2387a631ea36b88.woff2";
// fonts/Xiaolai/Xiaolai-Regular-a4c34be6d42152e64b0df90bc4607f64.woff2
var Xiaolai_Regular_a4c34be6d42152e64b0df90bc4607f64_default = "./fonts/Xiaolai/Xiaolai-Regular-a4c34be6d42152e64b0df90bc4607f64.woff2";
// fonts/Xiaolai/Xiaolai-Regular-b96d9226ce77ec94ceca043d712182e6.woff2
var Xiaolai_Regular_b96d9226ce77ec94ceca043d712182e6_default = "./fonts/Xiaolai/Xiaolai-Regular-b96d9226ce77ec94ceca043d712182e6.woff2";
// fonts/Xiaolai/Xiaolai-Regular-6ae5b42180ad70b971c91e7eefb8eba2.woff2
var Xiaolai_Regular_6ae5b42180ad70b971c91e7eefb8eba2_default = "./fonts/Xiaolai/Xiaolai-Regular-6ae5b42180ad70b971c91e7eefb8eba2.woff2";
// fonts/Xiaolai/Xiaolai-Regular-c69f61a4ab18d0488c8d1fc12e7028e8.woff2
var Xiaolai_Regular_c69f61a4ab18d0488c8d1fc12e7028e8_default = "./fonts/Xiaolai/Xiaolai-Regular-c69f61a4ab18d0488c8d1fc12e7028e8.woff2";
// fonts/Xiaolai/Xiaolai-Regular-cb17fc3db95f6d139afc9d31a8e93293.woff2
var Xiaolai_Regular_cb17fc3db95f6d139afc9d31a8e93293_default = "./fonts/Xiaolai/Xiaolai-Regular-cb17fc3db95f6d139afc9d31a8e93293.woff2";
// fonts/Xiaolai/Xiaolai-Regular-e3fcf5180fd466c8915c4e8069491054.woff2
var Xiaolai_Regular_e3fcf5180fd466c8915c4e8069491054_default = "./fonts/Xiaolai/Xiaolai-Regular-e3fcf5180fd466c8915c4e8069491054.woff2";
// fonts/Xiaolai/Xiaolai-Regular-c1f94158256bb1f3bf665b053d895af9.woff2
var Xiaolai_Regular_c1f94158256bb1f3bf665b053d895af9_default = "./fonts/Xiaolai/Xiaolai-Regular-c1f94158256bb1f3bf665b053d895af9.woff2";
// fonts/Xiaolai/Xiaolai-Regular-7197d6fda6cba7c3874c53d6381ca239.woff2
var Xiaolai_Regular_7197d6fda6cba7c3874c53d6381ca239_default = "./fonts/Xiaolai/Xiaolai-Regular-7197d6fda6cba7c3874c53d6381ca239.woff2";
// fonts/Xiaolai/Xiaolai-Regular-70c2eb8d64e71a42a834eb857ea9df51.woff2
var Xiaolai_Regular_70c2eb8d64e71a42a834eb857ea9df51_default = "./fonts/Xiaolai/Xiaolai-Regular-70c2eb8d64e71a42a834eb857ea9df51.woff2";
// fonts/Xiaolai/Xiaolai-Regular-a004ddfcb26e67bd6e678c8ed19e25ce.woff2
var Xiaolai_Regular_a004ddfcb26e67bd6e678c8ed19e25ce_default = "./fonts/Xiaolai/Xiaolai-Regular-a004ddfcb26e67bd6e678c8ed19e25ce.woff2";
// fonts/Xiaolai/Xiaolai-Regular-7e4bde7e9c7f84cd34d8a845e384c746.woff2
var Xiaolai_Regular_7e4bde7e9c7f84cd34d8a845e384c746_default = "./fonts/Xiaolai/Xiaolai-Regular-7e4bde7e9c7f84cd34d8a845e384c746.woff2";
// fonts/Xiaolai/Xiaolai-Regular-23686f7f29da6e8008c36dd3a80c83d6.woff2
var Xiaolai_Regular_23686f7f29da6e8008c36dd3a80c83d6_default = "./fonts/Xiaolai/Xiaolai-Regular-23686f7f29da6e8008c36dd3a80c83d6.woff2";
// fonts/Xiaolai/Xiaolai-Regular-69c09cc5fa3e55c74fc4821f76909cc3.woff2
var Xiaolai_Regular_69c09cc5fa3e55c74fc4821f76909cc3_default = "./fonts/Xiaolai/Xiaolai-Regular-69c09cc5fa3e55c74fc4821f76909cc3.woff2";
// fonts/Xiaolai/Xiaolai-Regular-25b7f38e18f035f96cb5e