split font into fontSize and fontFamily (#1635)
This commit is contained in:
parent
46b574283f
commit
63c10743fb
@ -3,6 +3,7 @@ import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawTextElement,
|
||||
TextAlign,
|
||||
FontFamily,
|
||||
} from "../element/types";
|
||||
import {
|
||||
getCommonAttributeOfSelectedElements,
|
||||
@ -17,9 +18,9 @@ import {
|
||||
import { ColorPicker } from "../components/ColorPicker";
|
||||
import { AppState } from "../../src/types";
|
||||
import { t } from "../i18n";
|
||||
import { DEFAULT_FONT } from "../appState";
|
||||
import { register } from "./register";
|
||||
import { newElementWith } from "../element/mutateElement";
|
||||
import { DEFAULT_FONT_SIZE, DEFAULT_FONT_FAMILY } from "../appState";
|
||||
|
||||
const changeProperty = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
@ -318,7 +319,7 @@ export const actionChangeFontSize = register({
|
||||
elements: changeProperty(elements, appState, (el) => {
|
||||
if (isTextElement(el)) {
|
||||
const element: ExcalidrawTextElement = newElementWith(el, {
|
||||
font: `${value}px ${el.font.split("px ")[1]}`,
|
||||
fontSize: value,
|
||||
});
|
||||
redrawTextBoundingBox(element);
|
||||
return element;
|
||||
@ -328,9 +329,7 @@ export const actionChangeFontSize = register({
|
||||
}),
|
||||
appState: {
|
||||
...appState,
|
||||
currentItemFont: `${value}px ${
|
||||
appState.currentItemFont.split("px ")[1]
|
||||
}`,
|
||||
currentItemFontSize: value,
|
||||
},
|
||||
commitToHistory: true,
|
||||
};
|
||||
@ -349,8 +348,8 @@ export const actionChangeFontSize = register({
|
||||
value={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
(element) => isTextElement(element) && +element.font.split("px ")[0],
|
||||
+(appState.currentItemFont || DEFAULT_FONT).split("px ")[0],
|
||||
(element) => isTextElement(element) && element.fontSize,
|
||||
appState.currentItemFontSize || DEFAULT_FONT_SIZE,
|
||||
)}
|
||||
onChange={(value) => updateData(value)}
|
||||
/>
|
||||
@ -365,7 +364,7 @@ export const actionChangeFontFamily = register({
|
||||
elements: changeProperty(elements, appState, (el) => {
|
||||
if (isTextElement(el)) {
|
||||
const element: ExcalidrawTextElement = newElementWith(el, {
|
||||
font: `${el.font.split("px ")[0]}px ${value}`,
|
||||
fontFamily: value,
|
||||
});
|
||||
redrawTextBoundingBox(element);
|
||||
return element;
|
||||
@ -375,33 +374,35 @@ export const actionChangeFontFamily = register({
|
||||
}),
|
||||
appState: {
|
||||
...appState,
|
||||
currentItemFont: `${
|
||||
appState.currentItemFont.split("px ")[0]
|
||||
}px ${value}`,
|
||||
currentItemFontFamily: appState.currentItemFontFamily,
|
||||
},
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<fieldset>
|
||||
<legend>{t("labels.fontFamily")}</legend>
|
||||
<ButtonSelect
|
||||
group="font-family"
|
||||
options={[
|
||||
{ value: "Virgil", text: t("labels.handDrawn") },
|
||||
{ value: "Helvetica", text: t("labels.normal") },
|
||||
{ value: "Cascadia", text: t("labels.code") },
|
||||
]}
|
||||
value={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
(element) => isTextElement(element) && element.font.split("px ")[1],
|
||||
(appState.currentItemFont || DEFAULT_FONT).split("px ")[1],
|
||||
)}
|
||||
onChange={(value) => updateData(value)}
|
||||
/>
|
||||
</fieldset>
|
||||
),
|
||||
PanelComponent: ({ elements, appState, updateData }) => {
|
||||
const options: { value: FontFamily; text: string }[] = [
|
||||
{ value: 1, text: t("labels.handDrawn") },
|
||||
{ value: 2, text: t("labels.normal") },
|
||||
{ value: 3, text: t("labels.code") },
|
||||
];
|
||||
|
||||
return (
|
||||
<fieldset>
|
||||
<legend>{t("labels.fontFamily")}</legend>
|
||||
<ButtonSelect<FontFamily | false>
|
||||
group="font-family"
|
||||
options={options}
|
||||
value={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
(element) => isTextElement(element) && element.fontFamily,
|
||||
appState.currentItemFontFamily || DEFAULT_FONT_FAMILY,
|
||||
)}
|
||||
onChange={(value) => updateData(value)}
|
||||
/>
|
||||
</fieldset>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const actionChangeTextAlign = register({
|
||||
|
@ -4,7 +4,11 @@ import {
|
||||
redrawTextBoundingBox,
|
||||
} from "../element";
|
||||
import { KEYS } from "../keys";
|
||||
import { DEFAULT_FONT, DEFAULT_TEXT_ALIGN } from "../appState";
|
||||
import {
|
||||
DEFAULT_FONT_SIZE,
|
||||
DEFAULT_FONT_FAMILY,
|
||||
DEFAULT_TEXT_ALIGN,
|
||||
} from "../appState";
|
||||
import { register } from "./register";
|
||||
import { mutateElement, newElementWith } from "../element/mutateElement";
|
||||
|
||||
@ -47,7 +51,8 @@ export const actionPasteStyles = register({
|
||||
});
|
||||
if (isTextElement(newElement)) {
|
||||
mutateElement(newElement, {
|
||||
font: pastedElement?.font || DEFAULT_FONT,
|
||||
fontSize: pastedElement?.fontSize || DEFAULT_FONT_SIZE,
|
||||
fontFamily: pastedElement?.fontFamily || DEFAULT_FONT_FAMILY,
|
||||
textAlign: pastedElement?.textAlign || DEFAULT_TEXT_ALIGN,
|
||||
});
|
||||
redrawTextBoundingBox(newElement);
|
||||
|
@ -2,8 +2,10 @@ import oc from "open-color";
|
||||
import { AppState, FlooredNumber } from "./types";
|
||||
import { getDateTime } from "./utils";
|
||||
import { t } from "./i18n";
|
||||
import { FontFamily } from "./element/types";
|
||||
|
||||
export const DEFAULT_FONT = "20px Virgil";
|
||||
export const DEFAULT_FONT_SIZE = 20;
|
||||
export const DEFAULT_FONT_FAMILY: FontFamily = 1;
|
||||
export const DEFAULT_TEXT_ALIGN = "left";
|
||||
|
||||
export const getDefaultAppState = (): AppState => {
|
||||
@ -25,7 +27,8 @@ export const getDefaultAppState = (): AppState => {
|
||||
currentItemStrokeStyle: "solid",
|
||||
currentItemRoughness: 1,
|
||||
currentItemOpacity: 100,
|
||||
currentItemFont: DEFAULT_FONT,
|
||||
currentItemFontSize: DEFAULT_FONT_SIZE,
|
||||
currentItemFontFamily: DEFAULT_FONT_FAMILY,
|
||||
currentItemTextAlign: DEFAULT_TEXT_ALIGN,
|
||||
viewBackgroundColor: oc.white,
|
||||
scrollX: 0 as FlooredNumber,
|
||||
|
@ -751,7 +751,8 @@ class App extends React.Component<any, AppState> {
|
||||
roughness: this.state.currentItemRoughness,
|
||||
opacity: this.state.currentItemOpacity,
|
||||
text: text,
|
||||
font: this.state.currentItemFont,
|
||||
fontSize: this.state.currentItemFontSize,
|
||||
fontFamily: this.state.currentItemFontFamily,
|
||||
textAlign: this.state.currentItemTextAlign,
|
||||
});
|
||||
|
||||
@ -1319,7 +1320,8 @@ class App extends React.Component<any, AppState> {
|
||||
initText: element.text,
|
||||
strokeColor: element.strokeColor,
|
||||
opacity: element.opacity,
|
||||
font: element.font,
|
||||
fontSize: element.fontSize,
|
||||
fontFamily: element.fontFamily,
|
||||
angle: element.angle,
|
||||
textAlign: element.textAlign,
|
||||
zoom: this.state.zoom,
|
||||
@ -1399,7 +1401,8 @@ class App extends React.Component<any, AppState> {
|
||||
roughness: this.state.currentItemRoughness,
|
||||
opacity: this.state.currentItemOpacity,
|
||||
text: "",
|
||||
font: this.state.currentItemFont,
|
||||
fontSize: this.state.currentItemFontSize,
|
||||
fontFamily: this.state.currentItemFontFamily,
|
||||
textAlign: this.state.currentItemTextAlign,
|
||||
});
|
||||
|
||||
|
@ -58,3 +58,10 @@ export const BROADCAST = {
|
||||
export const CLASSES = {
|
||||
SHAPE_ACTIONS_MENU: "App-menu__left",
|
||||
};
|
||||
|
||||
// 1-based in case we ever do `if(element.fontFamily)`
|
||||
export const FONT_FAMILY = {
|
||||
1: "Virgil",
|
||||
2: "Helvetica",
|
||||
3: "Cascadia",
|
||||
} as const;
|
||||
|
@ -12,7 +12,7 @@ export const serializeAsJSON = (
|
||||
JSON.stringify(
|
||||
{
|
||||
type: "excalidraw",
|
||||
version: 1,
|
||||
version: 2,
|
||||
source: window.location.origin,
|
||||
elements: elements.filter((element) => !element.isDeleted),
|
||||
appState: cleanAppStateForExport(appState),
|
||||
|
@ -1,6 +1,10 @@
|
||||
import { Point } from "../types";
|
||||
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawTextElement,
|
||||
FontFamily,
|
||||
} from "../element/types";
|
||||
import { AppState } from "../types";
|
||||
import { DataState } from "./types";
|
||||
import {
|
||||
@ -10,7 +14,17 @@ import {
|
||||
} from "../element";
|
||||
import { calculateScrollCenter } from "../scene";
|
||||
import { randomId } from "../random";
|
||||
import { DEFAULT_TEXT_ALIGN } from "../appState";
|
||||
import { DEFAULT_TEXT_ALIGN, DEFAULT_FONT_FAMILY } from "../appState";
|
||||
import { FONT_FAMILY } from "../constants";
|
||||
|
||||
const getFontFamilyByName = (fontFamilyName: string): FontFamily => {
|
||||
for (const [id, fontFamilyString] of Object.entries(FONT_FAMILY)) {
|
||||
if (fontFamilyString.includes(fontFamilyName)) {
|
||||
return parseInt(id) as FontFamily;
|
||||
}
|
||||
}
|
||||
return DEFAULT_FONT_FAMILY;
|
||||
};
|
||||
|
||||
export const restore = (
|
||||
// we're making the elements mutable for this API because we want to
|
||||
@ -57,6 +71,20 @@ export const restore = (
|
||||
element.points = points;
|
||||
} else {
|
||||
if (isTextElement(element)) {
|
||||
if ("font" in element) {
|
||||
const [fontPx, fontFamily]: [
|
||||
string,
|
||||
string,
|
||||
] = (element as any).font.split(" ");
|
||||
(element as Mutable<ExcalidrawTextElement>).fontSize = parseInt(
|
||||
fontPx,
|
||||
10,
|
||||
);
|
||||
(element as Mutable<
|
||||
ExcalidrawTextElement
|
||||
>).fontFamily = getFontFamilyByName(fontFamily);
|
||||
delete (element as any).font;
|
||||
}
|
||||
if (!element.textAlign) {
|
||||
element.textAlign = DEFAULT_TEXT_ALIGN;
|
||||
}
|
||||
|
@ -78,7 +78,8 @@ it("clones text element", () => {
|
||||
roughness: 1,
|
||||
opacity: 100,
|
||||
text: "hello",
|
||||
font: "Arial 20px",
|
||||
fontSize: 20,
|
||||
fontFamily: 1,
|
||||
textAlign: "left",
|
||||
});
|
||||
|
||||
|
@ -5,9 +5,10 @@ import {
|
||||
ExcalidrawGenericElement,
|
||||
NonDeleted,
|
||||
TextAlign,
|
||||
FontFamily,
|
||||
GroupId,
|
||||
} from "../element/types";
|
||||
import { measureText } from "../utils";
|
||||
import { measureText, getFontString } from "../utils";
|
||||
import { randomInteger, randomId } from "../random";
|
||||
import { newElementWith } from "./mutateElement";
|
||||
import nanoid from "nanoid";
|
||||
@ -77,16 +78,18 @@ export const newElement = (
|
||||
export const newTextElement = (
|
||||
opts: {
|
||||
text: string;
|
||||
font: string;
|
||||
fontSize: number;
|
||||
fontFamily: FontFamily;
|
||||
textAlign: TextAlign;
|
||||
} & ElementConstructorOpts,
|
||||
): NonDeleted<ExcalidrawTextElement> => {
|
||||
const metrics = measureText(opts.text, opts.font);
|
||||
const metrics = measureText(opts.text, getFontString(opts));
|
||||
const textElement = newElementWith(
|
||||
{
|
||||
..._newElementBase<ExcalidrawTextElement>("text", opts),
|
||||
text: opts.text,
|
||||
font: opts.font,
|
||||
fontSize: opts.fontSize,
|
||||
fontFamily: opts.fontFamily,
|
||||
textAlign: opts.textAlign,
|
||||
// Center the text
|
||||
x: opts.x - metrics.width / 2,
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { measureText } from "../utils";
|
||||
import { measureText, getFontString } from "../utils";
|
||||
import { ExcalidrawTextElement } from "./types";
|
||||
import { mutateElement } from "./mutateElement";
|
||||
|
||||
export const redrawTextBoundingBox = (element: ExcalidrawTextElement) => {
|
||||
const metrics = measureText(element.text, element.font);
|
||||
const metrics = measureText(element.text, getFontString(element));
|
||||
mutateElement(element, {
|
||||
width: metrics.width,
|
||||
height: metrics.height,
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { KEYS } from "../keys";
|
||||
import { selectNode, isWritableElement } from "../utils";
|
||||
import { selectNode, isWritableElement, getFontString } from "../utils";
|
||||
import { globalSceneState } from "../scene";
|
||||
import { isTextElement } from "./typeChecks";
|
||||
import { CLASSES } from "../constants";
|
||||
import { FontFamily } from "./types";
|
||||
|
||||
const trimText = (text: string) => {
|
||||
// whitespace only → trim all because we'd end up inserting invisible element
|
||||
@ -21,7 +22,8 @@ type TextWysiwygParams = {
|
||||
x: number;
|
||||
y: number;
|
||||
strokeColor: string;
|
||||
font: string;
|
||||
fontSize: number;
|
||||
fontFamily: FontFamily;
|
||||
opacity: number;
|
||||
zoom: number;
|
||||
angle: number;
|
||||
@ -37,7 +39,8 @@ export const textWysiwyg = ({
|
||||
x,
|
||||
y,
|
||||
strokeColor,
|
||||
font,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
opacity,
|
||||
zoom,
|
||||
angle,
|
||||
@ -68,7 +71,7 @@ export const textWysiwyg = ({
|
||||
transform: `translate(-50%, -50%) scale(${zoom}) rotate(${degree}deg)`,
|
||||
textAlign: textAlign,
|
||||
display: "inline-block",
|
||||
font: font,
|
||||
font: getFontString({ fontSize, fontFamily }),
|
||||
padding: "4px",
|
||||
// This needs to have "1px solid" otherwise the carret doesn't show up
|
||||
// the first time on Safari and Chrome!
|
||||
@ -193,7 +196,7 @@ export const textWysiwyg = ({
|
||||
.find((element) => element.id === id);
|
||||
if (editingElement && isTextElement(editingElement)) {
|
||||
Object.assign(editable.style, {
|
||||
font: editingElement.font,
|
||||
font: getFontString(editingElement),
|
||||
textAlign: editingElement.textAlign,
|
||||
color: editingElement.strokeColor,
|
||||
opacity: editingElement.opacity / 100,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Point } from "../types";
|
||||
import { FONT_FAMILY } from "../constants";
|
||||
|
||||
export type GroupId = string;
|
||||
|
||||
@ -49,7 +50,8 @@ export type NonDeletedExcalidrawElement = NonDeleted<ExcalidrawElement>;
|
||||
export type ExcalidrawTextElement = _ExcalidrawElementBase &
|
||||
Readonly<{
|
||||
type: "text";
|
||||
font: string;
|
||||
fontSize: number;
|
||||
fontFamily: FontFamily;
|
||||
text: string;
|
||||
baseline: number;
|
||||
textAlign: TextAlign;
|
||||
@ -65,3 +67,6 @@ export type ExcalidrawLinearElement = _ExcalidrawElementBase &
|
||||
export type PointerType = "mouse" | "pen" | "touch";
|
||||
|
||||
export type TextAlign = "left" | "center" | "right";
|
||||
|
||||
export type FontFamily = keyof typeof FONT_FAMILY;
|
||||
export type FontString = string & { _brand: "fontString" };
|
||||
|
@ -14,7 +14,7 @@ import { Drawable, Options } from "roughjs/bin/core";
|
||||
import { RoughSVG } from "roughjs/bin/svg";
|
||||
import { RoughGenerator } from "roughjs/bin/generator";
|
||||
import { SceneState } from "../scene/types";
|
||||
import { SVG_NS, distance } from "../utils";
|
||||
import { SVG_NS, distance, getFontString, getFontFamilyString } from "../utils";
|
||||
import { isPathALoop } from "../math";
|
||||
import rough from "roughjs/bin/rough";
|
||||
|
||||
@ -101,7 +101,7 @@ const drawElementOnCanvas = (
|
||||
default: {
|
||||
if (isTextElement(element)) {
|
||||
const font = context.font;
|
||||
context.font = element.font;
|
||||
context.font = getFontString(element);
|
||||
const fillStyle = context.fillStyle;
|
||||
context.fillStyle = element.strokeColor;
|
||||
const textAlign = context.textAlign;
|
||||
@ -492,13 +492,6 @@ export const renderElementToSvg = (
|
||||
: element.textAlign === "right"
|
||||
? element.width
|
||||
: 0;
|
||||
const fontSplit = element.font.split(" ").filter((d) => !!d.trim());
|
||||
let fontFamily = fontSplit[0];
|
||||
let fontSize = "20px";
|
||||
if (fontSplit.length > 1) {
|
||||
fontFamily = fontSplit[1];
|
||||
fontSize = fontSplit[0];
|
||||
}
|
||||
const textAnchor =
|
||||
element.textAlign === "center"
|
||||
? "middle"
|
||||
@ -510,8 +503,8 @@ export const renderElementToSvg = (
|
||||
text.textContent = lines[i];
|
||||
text.setAttribute("x", `${horizontalOffset}`);
|
||||
text.setAttribute("y", `${(i + 1) * lineHeight - verticalOffset}`);
|
||||
text.setAttribute("font-family", fontFamily);
|
||||
text.setAttribute("font-size", fontSize);
|
||||
text.setAttribute("font-family", getFontFamilyString(element));
|
||||
text.setAttribute("font-size", `${element.fontSize}px`);
|
||||
text.setAttribute("fill", element.strokeColor);
|
||||
text.setAttribute("text-anchor", textAnchor);
|
||||
text.setAttribute("style", "white-space: pre;");
|
||||
|
@ -4,10 +4,11 @@ import { newTextElement } from "../element";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { getCommonBounds } from "../element/bounds";
|
||||
import { renderScene, renderSceneToSvg } from "../renderer/renderScene";
|
||||
import { distance, SVG_NS, measureText } from "../utils";
|
||||
import { distance, SVG_NS, measureText, getFontString } from "../utils";
|
||||
import { normalizeScroll } from "./scroll";
|
||||
import { AppState } from "../types";
|
||||
import { t } from "../i18n";
|
||||
import { DEFAULT_FONT_FAMILY } from "../appState";
|
||||
|
||||
export const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`;
|
||||
|
||||
@ -149,12 +150,17 @@ export const exportToSvg = (
|
||||
|
||||
const getWatermarkElement = (maxX: number, maxY: number) => {
|
||||
const text = t("labels.madeWithExcalidraw");
|
||||
const font = "16px Virgil";
|
||||
const { width: textWidth } = measureText(text, font);
|
||||
const fontSize = 16;
|
||||
const fontFamily = DEFAULT_FONT_FAMILY;
|
||||
const { width: textWidth } = measureText(
|
||||
text,
|
||||
getFontString({ fontSize, fontFamily }),
|
||||
);
|
||||
|
||||
return newTextElement({
|
||||
text,
|
||||
font,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
textAlign: "center",
|
||||
x: maxX - textWidth / 2,
|
||||
y: maxY + 16,
|
||||
|
@ -5,7 +5,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -202,7 +203,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -320,7 +322,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "#fa5252",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#5f3dc4",
|
||||
@ -566,7 +569,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -718,7 +722,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -905,7 +910,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -1101,7 +1107,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -1396,7 +1403,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -2216,7 +2224,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -2334,7 +2343,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -2452,7 +2462,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -2570,7 +2581,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -2710,7 +2722,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -2850,7 +2863,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -2990,7 +3004,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -3130,7 +3145,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -3248,7 +3264,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -3366,7 +3383,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -3506,7 +3524,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -3624,7 +3643,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -3764,7 +3784,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -3960,7 +3981,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -4021,7 +4043,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -4803,7 +4826,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -5180,7 +5204,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -5478,7 +5503,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -5701,7 +5727,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -5853,7 +5880,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -6586,7 +6614,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -7224,7 +7253,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -7771,7 +7801,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -8231,7 +8262,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -8649,7 +8681,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -8986,7 +9019,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -9246,7 +9280,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -9433,7 +9468,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -10215,7 +10251,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -10900,7 +10937,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -11492,7 +11530,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -11995,7 +12034,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -12251,7 +12291,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -12310,7 +12351,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -12371,7 +12413,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
@ -12785,7 +12828,8 @@ Object {
|
||||
"collaborators": Map {},
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFont": "20px Virgil",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
NonDeleted,
|
||||
TextAlign,
|
||||
ExcalidrawElement,
|
||||
FontFamily,
|
||||
GroupId,
|
||||
} from "./element/types";
|
||||
import { SHAPES } from "./shapes";
|
||||
@ -35,7 +36,8 @@ export type AppState = {
|
||||
currentItemStrokeStyle: ExcalidrawElement["strokeStyle"];
|
||||
currentItemRoughness: number;
|
||||
currentItemOpacity: number;
|
||||
currentItemFont: string;
|
||||
currentItemFontFamily: FontFamily;
|
||||
currentItemFontSize: number;
|
||||
currentItemTextAlign: TextAlign;
|
||||
viewBackgroundColor: string;
|
||||
scrollX: FlooredNumber;
|
||||
|
24
src/utils.ts
24
src/utils.ts
@ -1,6 +1,7 @@
|
||||
import { FlooredNumber } from "./types";
|
||||
import { getZoomOrigin } from "./scene";
|
||||
import { CURSOR_TYPE } from "./constants";
|
||||
import { CURSOR_TYPE, FONT_FAMILY } from "./constants";
|
||||
import { FontFamily, FontString } from "./element/types";
|
||||
|
||||
export const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
|
||||
@ -60,8 +61,27 @@ export const isWritableElement = (
|
||||
(target instanceof HTMLInputElement &&
|
||||
(target.type === "text" || target.type === "number"));
|
||||
|
||||
export const getFontFamilyString = ({
|
||||
fontFamily,
|
||||
}: {
|
||||
fontFamily: FontFamily;
|
||||
}) => {
|
||||
return FONT_FAMILY[fontFamily];
|
||||
};
|
||||
|
||||
/** returns fontSize+fontFamily string for assignment to DOM elements */
|
||||
export const getFontString = ({
|
||||
fontSize,
|
||||
fontFamily,
|
||||
}: {
|
||||
fontSize: number;
|
||||
fontFamily: FontFamily;
|
||||
}) => {
|
||||
return `${fontSize}px ${getFontFamilyString({ fontFamily })}` as FontString;
|
||||
};
|
||||
|
||||
// https://github.com/grassator/canvas-text-editor/blob/master/lib/FontMetrics.js
|
||||
export const measureText = (text: string, font: string) => {
|
||||
export const measureText = (text: string, font: FontString) => {
|
||||
const line = document.createElement("div");
|
||||
const body = document.body;
|
||||
line.style.position = "absolute";
|
||||
|
Loading…
x
Reference in New Issue
Block a user