split font into fontSize and fontFamily (#1635)

This commit is contained in:
David Luzar 2020-05-27 15:14:50 +02:00 committed by GitHub
parent 46b574283f
commit 63c10743fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 240 additions and 116 deletions

View File

@ -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({

View File

@ -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);

View File

@ -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,

View File

@ -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,
});

View File

@ -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;

View File

@ -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),

View File

@ -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;
}

View File

@ -78,7 +78,8 @@ it("clones text element", () => {
roughness: 1,
opacity: 100,
text: "hello",
font: "Arial 20px",
fontSize: 20,
fontFamily: 1,
textAlign: "left",
});

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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" };

View File

@ -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;");

View File

@ -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,

View File

@ -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",

View File

@ -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;

View File

@ -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";