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