feat: Allow non-WYSIWYG measurements and wrapping for text elements.
This commit is contained in:
parent
ca3be2c678
commit
7098013671
@ -5,7 +5,7 @@ import {
|
|||||||
computeBoundTextPosition,
|
computeBoundTextPosition,
|
||||||
computeContainerDimensionForBoundText,
|
computeContainerDimensionForBoundText,
|
||||||
getBoundTextElement,
|
getBoundTextElement,
|
||||||
measureText,
|
measureTextElement,
|
||||||
redrawTextBoundingBox,
|
redrawTextBoundingBox,
|
||||||
} from "../element/textElement";
|
} from "../element/textElement";
|
||||||
import {
|
import {
|
||||||
@ -25,7 +25,6 @@ import {
|
|||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import { getSelectedElements } from "../scene";
|
import { getSelectedElements } from "../scene";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { getFontString } from "../utils";
|
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
|
|
||||||
export const actionUnbindText = register({
|
export const actionUnbindText = register({
|
||||||
@ -45,10 +44,11 @@ export const actionUnbindText = register({
|
|||||||
selectedElements.forEach((element) => {
|
selectedElements.forEach((element) => {
|
||||||
const boundTextElement = getBoundTextElement(element);
|
const boundTextElement = getBoundTextElement(element);
|
||||||
if (boundTextElement) {
|
if (boundTextElement) {
|
||||||
const { width, height, baseline } = measureText(
|
const { width, height, baseline } = measureTextElement(
|
||||||
boundTextElement.originalText,
|
boundTextElement,
|
||||||
getFontString(boundTextElement),
|
{
|
||||||
boundTextElement.lineHeight,
|
text: boundTextElement.originalText,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
const originalContainerHeight = getOriginalContainerHeightFromCache(
|
const originalContainerHeight = getOriginalContainerHeightFromCache(
|
||||||
element.id,
|
element.id,
|
||||||
|
@ -31,14 +31,14 @@ import {
|
|||||||
import { getDefaultAppState } from "../appState";
|
import { getDefaultAppState } from "../appState";
|
||||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||||
import { bumpVersion } from "../element/mutateElement";
|
import { bumpVersion } from "../element/mutateElement";
|
||||||
import { getFontString, getUpdatedTimestamp, updateActiveTool } from "../utils";
|
import { getUpdatedTimestamp, updateActiveTool } from "../utils";
|
||||||
import { arrayToMap } from "../utils";
|
import { arrayToMap } from "../utils";
|
||||||
import oc from "open-color";
|
import oc from "open-color";
|
||||||
import { MarkOptional, Mutable } from "../utility-types";
|
import { MarkOptional, Mutable } from "../utility-types";
|
||||||
import {
|
import {
|
||||||
detectLineHeight,
|
detectLineHeight,
|
||||||
getDefaultLineHeight,
|
getDefaultLineHeight,
|
||||||
measureBaseline,
|
measureTextElement,
|
||||||
} from "../element/textElement";
|
} from "../element/textElement";
|
||||||
|
|
||||||
type RestoredAppState = Omit<
|
type RestoredAppState = Omit<
|
||||||
@ -80,7 +80,8 @@ const getFontFamilyByName = (fontFamilyName: string): FontFamilyValues => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const restoreElementWithProperties = <
|
const restoreElementWithProperties = <
|
||||||
T extends Required<Omit<ExcalidrawElement, "customData">> & {
|
T extends Required<Omit<ExcalidrawElement, "subtype" | "customData">> & {
|
||||||
|
subtype?: ExcalidrawElement["subtype"];
|
||||||
customData?: ExcalidrawElement["customData"];
|
customData?: ExcalidrawElement["customData"];
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
boundElementIds?: readonly ExcalidrawElement["id"][];
|
boundElementIds?: readonly ExcalidrawElement["id"][];
|
||||||
@ -143,6 +144,9 @@ const restoreElementWithProperties = <
|
|||||||
locked: element.locked ?? false,
|
locked: element.locked ?? false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if ("subtype" in element) {
|
||||||
|
base.subtype = element.subtype;
|
||||||
|
}
|
||||||
if ("customData" in element) {
|
if ("customData" in element) {
|
||||||
base.customData = element.customData;
|
base.customData = element.customData;
|
||||||
}
|
}
|
||||||
@ -188,11 +192,7 @@ const restoreElement = (
|
|||||||
: // no element height likely means programmatic use, so default
|
: // no element height likely means programmatic use, so default
|
||||||
// to a fixed line height
|
// to a fixed line height
|
||||||
getDefaultLineHeight(element.fontFamily));
|
getDefaultLineHeight(element.fontFamily));
|
||||||
const baseline = measureBaseline(
|
const baseline = measureTextElement(element, { text }).baseline;
|
||||||
element.text,
|
|
||||||
getFontString(element),
|
|
||||||
lineHeight,
|
|
||||||
);
|
|
||||||
element = restoreElementWithProperties(element, {
|
element = restoreElementWithProperties(element, {
|
||||||
fontSize,
|
fontSize,
|
||||||
fontFamily,
|
fontFamily,
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
FontFamilyValues,
|
FontFamilyValues,
|
||||||
ExcalidrawTextContainer,
|
ExcalidrawTextContainer,
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import { getFontString, getUpdatedTimestamp, isTestEnv } from "../utils";
|
import { getUpdatedTimestamp, isTestEnv } from "../utils";
|
||||||
import { randomInteger, randomId } from "../random";
|
import { randomInteger, randomId } from "../random";
|
||||||
import { mutateElement, newElementWith } from "./mutateElement";
|
import { mutateElement, newElementWith } from "./mutateElement";
|
||||||
import { getNewGroupIdsForDuplication } from "../groups";
|
import { getNewGroupIdsForDuplication } from "../groups";
|
||||||
@ -25,9 +25,9 @@ import {
|
|||||||
getBoundTextElementOffset,
|
getBoundTextElementOffset,
|
||||||
getContainerDims,
|
getContainerDims,
|
||||||
getContainerElement,
|
getContainerElement,
|
||||||
measureText,
|
measureTextElement,
|
||||||
normalizeText,
|
normalizeText,
|
||||||
wrapText,
|
wrapTextElement,
|
||||||
getMaxContainerWidth,
|
getMaxContainerWidth,
|
||||||
getDefaultLineHeight,
|
getDefaultLineHeight,
|
||||||
} from "./textElement";
|
} from "./textElement";
|
||||||
@ -46,6 +46,8 @@ type ElementConstructorOpts = MarkOptional<
|
|||||||
| "version"
|
| "version"
|
||||||
| "versionNonce"
|
| "versionNonce"
|
||||||
| "link"
|
| "link"
|
||||||
|
| "subtype"
|
||||||
|
| "customData"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const _newElementBase = <T extends ExcalidrawElement>(
|
const _newElementBase = <T extends ExcalidrawElement>(
|
||||||
@ -143,7 +145,13 @@ export const newTextElement = (
|
|||||||
): NonDeleted<ExcalidrawTextElement> => {
|
): NonDeleted<ExcalidrawTextElement> => {
|
||||||
const lineHeight = opts.lineHeight || getDefaultLineHeight(opts.fontFamily);
|
const lineHeight = opts.lineHeight || getDefaultLineHeight(opts.fontFamily);
|
||||||
const text = normalizeText(opts.text);
|
const text = normalizeText(opts.text);
|
||||||
const metrics = measureText(text, getFontString(opts), lineHeight);
|
const metrics = measureTextElement(
|
||||||
|
{ ...opts, lineHeight },
|
||||||
|
{
|
||||||
|
text,
|
||||||
|
customData: opts.customData,
|
||||||
|
},
|
||||||
|
);
|
||||||
const offsets = getTextElementPositionOffsets(opts, metrics);
|
const offsets = getTextElementPositionOffsets(opts, metrics);
|
||||||
|
|
||||||
const textElement = newElementWith(
|
const textElement = newElementWith(
|
||||||
@ -184,7 +192,9 @@ const getAdjustedDimensions = (
|
|||||||
width: nextWidth,
|
width: nextWidth,
|
||||||
height: nextHeight,
|
height: nextHeight,
|
||||||
baseline: nextBaseline,
|
baseline: nextBaseline,
|
||||||
} = measureText(nextText, getFontString(element), element.lineHeight);
|
} = measureTextElement(element, {
|
||||||
|
text: nextText,
|
||||||
|
});
|
||||||
const { textAlign, verticalAlign } = element;
|
const { textAlign, verticalAlign } = element;
|
||||||
let x: number;
|
let x: number;
|
||||||
let y: number;
|
let y: number;
|
||||||
@ -193,11 +203,7 @@ const getAdjustedDimensions = (
|
|||||||
verticalAlign === VERTICAL_ALIGN.MIDDLE &&
|
verticalAlign === VERTICAL_ALIGN.MIDDLE &&
|
||||||
!element.containerId
|
!element.containerId
|
||||||
) {
|
) {
|
||||||
const prevMetrics = measureText(
|
const prevMetrics = measureTextElement(element);
|
||||||
element.text,
|
|
||||||
getFontString(element),
|
|
||||||
element.lineHeight,
|
|
||||||
);
|
|
||||||
const offsets = getTextElementPositionOffsets(element, {
|
const offsets = getTextElementPositionOffsets(element, {
|
||||||
width: nextWidth - prevMetrics.width,
|
width: nextWidth - prevMetrics.width,
|
||||||
height: nextHeight - prevMetrics.height,
|
height: nextHeight - prevMetrics.height,
|
||||||
@ -274,11 +280,9 @@ export const refreshTextDimensions = (
|
|||||||
}
|
}
|
||||||
const container = getContainerElement(textElement);
|
const container = getContainerElement(textElement);
|
||||||
if (container) {
|
if (container) {
|
||||||
text = wrapText(
|
text = wrapTextElement(textElement, getMaxContainerWidth(container), {
|
||||||
text,
|
text,
|
||||||
getFontString(textElement),
|
});
|
||||||
getMaxContainerWidth(container),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const dimensions = getAdjustedDimensions(textElement, text);
|
const dimensions = getAdjustedDimensions(textElement, text);
|
||||||
return { text, ...dimensions };
|
return { text, ...dimensions };
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { getSubtypeMethods, SubtypeMethods } from "../subtypes";
|
||||||
import { getFontString, arrayToMap, isTestEnv } from "../utils";
|
import { getFontString, arrayToMap, isTestEnv } from "../utils";
|
||||||
import {
|
import {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
@ -34,6 +35,30 @@ import {
|
|||||||
} from "./textWysiwyg";
|
} from "./textWysiwyg";
|
||||||
import { ExtractSetType } from "../utility-types";
|
import { ExtractSetType } from "../utility-types";
|
||||||
|
|
||||||
|
export const measureTextElement = function (element, next) {
|
||||||
|
const map = getSubtypeMethods(element.subtype);
|
||||||
|
if (map?.measureText) {
|
||||||
|
return map.measureText(element, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fontSize = next?.fontSize ?? element.fontSize;
|
||||||
|
const font = getFontString({ fontSize, fontFamily: element.fontFamily });
|
||||||
|
const text = next?.text ?? element.text;
|
||||||
|
return measureText(text, font, element.lineHeight);
|
||||||
|
} as SubtypeMethods["measureText"];
|
||||||
|
|
||||||
|
export const wrapTextElement = function (element, containerWidth, next) {
|
||||||
|
const map = getSubtypeMethods(element.subtype);
|
||||||
|
if (map?.wrapText) {
|
||||||
|
return map.wrapText(element, containerWidth, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fontSize = next?.fontSize ?? element.fontSize;
|
||||||
|
const font = getFontString({ fontSize, fontFamily: element.fontFamily });
|
||||||
|
const text = next?.text ?? element.originalText;
|
||||||
|
return wrapText(text, font, containerWidth);
|
||||||
|
} as SubtypeMethods["wrapText"];
|
||||||
|
|
||||||
export const normalizeText = (text: string) => {
|
export const normalizeText = (text: string) => {
|
||||||
return (
|
return (
|
||||||
text
|
text
|
||||||
@ -66,22 +91,24 @@ export const redrawTextBoundingBox = (
|
|||||||
|
|
||||||
if (container) {
|
if (container) {
|
||||||
maxWidth = getMaxContainerWidth(container);
|
maxWidth = getMaxContainerWidth(container);
|
||||||
boundTextUpdates.text = wrapText(
|
boundTextUpdates.text = wrapTextElement(textElement, maxWidth);
|
||||||
textElement.originalText,
|
|
||||||
getFontString(textElement),
|
|
||||||
maxWidth,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const metrics = measureText(
|
const metrics = measureTextElement(textElement, {
|
||||||
boundTextUpdates.text,
|
text: boundTextUpdates.text,
|
||||||
getFontString(textElement),
|
});
|
||||||
textElement.lineHeight,
|
|
||||||
);
|
|
||||||
|
|
||||||
boundTextUpdates.width = metrics.width;
|
boundTextUpdates.width = metrics.width;
|
||||||
boundTextUpdates.height = metrics.height;
|
boundTextUpdates.height = metrics.height;
|
||||||
boundTextUpdates.baseline = metrics.baseline;
|
boundTextUpdates.baseline = metrics.baseline;
|
||||||
|
|
||||||
|
// Maintain coordX for non left-aligned text in case the width has changed
|
||||||
|
if (!container) {
|
||||||
|
if (textElement.textAlign === TEXT_ALIGN.RIGHT) {
|
||||||
|
boundTextUpdates.x += textElement.width - metrics.width;
|
||||||
|
} else if (textElement.textAlign === TEXT_ALIGN.CENTER) {
|
||||||
|
boundTextUpdates.x += textElement.width / 2 - metrics.width / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (container) {
|
if (container) {
|
||||||
if (isArrowElement(container)) {
|
if (isArrowElement(container)) {
|
||||||
const centerX = textElement.x + textElement.width / 2;
|
const centerX = textElement.x + textElement.width / 2;
|
||||||
@ -189,17 +216,9 @@ export const handleBindTextResize = (
|
|||||||
let nextBaseLine = textElement.baseline;
|
let nextBaseLine = textElement.baseline;
|
||||||
if (transformHandleType !== "n" && transformHandleType !== "s") {
|
if (transformHandleType !== "n" && transformHandleType !== "s") {
|
||||||
if (text) {
|
if (text) {
|
||||||
text = wrapText(
|
text = wrapTextElement(textElement, maxWidth);
|
||||||
textElement.originalText,
|
|
||||||
getFontString(textElement),
|
|
||||||
maxWidth,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const metrics = measureText(
|
const metrics = measureTextElement(textElement, { text });
|
||||||
text,
|
|
||||||
getFontString(textElement),
|
|
||||||
textElement.lineHeight,
|
|
||||||
);
|
|
||||||
nextHeight = metrics.height;
|
nextHeight = metrics.height;
|
||||||
nextWidth = metrics.width;
|
nextWidth = metrics.width;
|
||||||
nextBaseLine = metrics.baseline;
|
nextBaseLine = metrics.baseline;
|
||||||
|
@ -47,6 +47,7 @@ import { LinearElementEditor } from "./linearElementEditor";
|
|||||||
import { parseClipboard } from "../clipboard";
|
import { parseClipboard } from "../clipboard";
|
||||||
|
|
||||||
const getTransform = (
|
const getTransform = (
|
||||||
|
offsetX: number,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
angle: number,
|
angle: number,
|
||||||
@ -64,7 +65,7 @@ const getTransform = (
|
|||||||
if (height > maxHeight && zoom.value !== 1) {
|
if (height > maxHeight && zoom.value !== 1) {
|
||||||
translateY = (maxHeight * (zoom.value - 1)) / 2;
|
translateY = (maxHeight * (zoom.value - 1)) / 2;
|
||||||
}
|
}
|
||||||
return `translate(${translateX}px, ${translateY}px) scale(${zoom.value}) rotate(${degree}deg)`;
|
return `translate(${translateX}px, ${translateY}px) scale(${zoom.value}) rotate(${degree}deg) translate(${offsetX}px, 0px)`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const originalContainerCache: {
|
const originalContainerCache: {
|
||||||
@ -158,13 +159,30 @@ export const textWysiwyg = ({
|
|||||||
const container = getContainerElement(updatedTextElement);
|
const container = getContainerElement(updatedTextElement);
|
||||||
let maxWidth = updatedTextElement.width;
|
let maxWidth = updatedTextElement.width;
|
||||||
|
|
||||||
let maxHeight = updatedTextElement.height;
|
// Editing metrics
|
||||||
let textElementWidth = updatedTextElement.width;
|
const eMetrics = measureText(
|
||||||
|
container && updatedTextElement.containerId
|
||||||
|
? wrapText(
|
||||||
|
updatedTextElement.originalText,
|
||||||
|
getFontString(updatedTextElement),
|
||||||
|
getMaxContainerWidth(container),
|
||||||
|
)
|
||||||
|
: updatedTextElement.originalText,
|
||||||
|
getFontString(updatedTextElement),
|
||||||
|
updatedTextElement.lineHeight,
|
||||||
|
);
|
||||||
|
|
||||||
|
let maxHeight = eMetrics.height;
|
||||||
|
let textElementWidth = Math.max(updatedTextElement.width, eMetrics.width);
|
||||||
// Set to element height by default since that's
|
// Set to element height by default since that's
|
||||||
// what is going to be used for unbounded text
|
// what is going to be used for unbounded text
|
||||||
let textElementHeight = updatedTextElement.height;
|
let textElementHeight = Math.max(updatedTextElement.height, maxHeight);
|
||||||
|
|
||||||
if (container && updatedTextElement.containerId) {
|
if (container && updatedTextElement.containerId) {
|
||||||
|
textElementHeight = Math.min(
|
||||||
|
getMaxContainerHeight(container),
|
||||||
|
textElementHeight,
|
||||||
|
);
|
||||||
if (isArrowElement(container)) {
|
if (isArrowElement(container)) {
|
||||||
const boundTextCoords =
|
const boundTextCoords =
|
||||||
LinearElementEditor.getBoundTextElementPosition(
|
LinearElementEditor.getBoundTextElementPosition(
|
||||||
@ -173,6 +191,8 @@ export const textWysiwyg = ({
|
|||||||
);
|
);
|
||||||
coordX = boundTextCoords.x;
|
coordX = boundTextCoords.x;
|
||||||
coordY = boundTextCoords.y;
|
coordY = boundTextCoords.y;
|
||||||
|
} else {
|
||||||
|
coordX = Math.max(coordX, getContainerCoords(container).x);
|
||||||
}
|
}
|
||||||
const propertiesUpdated = textPropertiesUpdated(
|
const propertiesUpdated = textPropertiesUpdated(
|
||||||
updatedTextElement,
|
updatedTextElement,
|
||||||
@ -186,7 +206,18 @@ export const textWysiwyg = ({
|
|||||||
}
|
}
|
||||||
if (propertiesUpdated) {
|
if (propertiesUpdated) {
|
||||||
// update height of the editor after properties updated
|
// update height of the editor after properties updated
|
||||||
textElementHeight = updatedTextElement.height;
|
const font = getFontString(updatedTextElement);
|
||||||
|
textElementHeight =
|
||||||
|
updatedTextElement.lineHeight *
|
||||||
|
wrapText(
|
||||||
|
updatedTextElement.originalText,
|
||||||
|
font,
|
||||||
|
getMaxContainerWidth(container),
|
||||||
|
).split("\n").length;
|
||||||
|
textElementHeight = Math.max(
|
||||||
|
textElementHeight,
|
||||||
|
updatedTextElement.height,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let originalContainerData;
|
let originalContainerData;
|
||||||
@ -266,12 +297,29 @@ export const textWysiwyg = ({
|
|||||||
editable.selectionEnd = editable.value.length - diff;
|
editable.selectionEnd = editable.value.length - diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let transformWidth = updatedTextElement.width;
|
||||||
if (!container) {
|
if (!container) {
|
||||||
maxWidth = (appState.width - 8 - viewportX) / appState.zoom.value;
|
maxWidth = (appState.width - 8 - viewportX) / appState.zoom.value;
|
||||||
textElementWidth = Math.min(textElementWidth, maxWidth);
|
textElementWidth = Math.min(textElementWidth, maxWidth);
|
||||||
} else {
|
} else {
|
||||||
textElementWidth += 0.5;
|
textElementWidth += 0.5;
|
||||||
|
transformWidth += 0.5;
|
||||||
}
|
}
|
||||||
|
// Horizontal offset in case updatedTextElement has a non-WYSIWYG subtype
|
||||||
|
const offWidth = container
|
||||||
|
? Math.min(
|
||||||
|
0,
|
||||||
|
updatedTextElement.width - Math.min(maxWidth, eMetrics.width),
|
||||||
|
)
|
||||||
|
: Math.min(maxWidth, updatedTextElement.width) -
|
||||||
|
Math.min(maxWidth, eMetrics.width);
|
||||||
|
const offsetX =
|
||||||
|
textAlign === "right"
|
||||||
|
? offWidth
|
||||||
|
: textAlign === "center"
|
||||||
|
? offWidth / 2
|
||||||
|
: 0;
|
||||||
|
const { width: w, height: h } = updatedTextElement;
|
||||||
|
|
||||||
let lineHeight = updatedTextElement.lineHeight;
|
let lineHeight = updatedTextElement.lineHeight;
|
||||||
|
|
||||||
@ -290,13 +338,15 @@ export const textWysiwyg = ({
|
|||||||
font: getFontString(updatedTextElement),
|
font: getFontString(updatedTextElement),
|
||||||
// must be defined *after* font ¯\_(ツ)_/¯
|
// must be defined *after* font ¯\_(ツ)_/¯
|
||||||
lineHeight,
|
lineHeight,
|
||||||
width: `${textElementWidth}px`,
|
width: `${Math.min(textElementWidth, maxWidth)}px`,
|
||||||
height: `${textElementHeight}px`,
|
height: `${textElementHeight}px`,
|
||||||
left: `${viewportX}px`,
|
left: `${viewportX}px`,
|
||||||
top: `${viewportY}px`,
|
top: `${viewportY}px`,
|
||||||
|
transformOrigin: `${w / 2}px ${h / 2}px`,
|
||||||
transform: getTransform(
|
transform: getTransform(
|
||||||
textElementWidth,
|
offsetX,
|
||||||
textElementHeight,
|
transformWidth,
|
||||||
|
updatedTextElement.height,
|
||||||
getTextElementAngle(updatedTextElement),
|
getTextElementAngle(updatedTextElement),
|
||||||
appState,
|
appState,
|
||||||
maxWidth,
|
maxWidth,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Subtype } from "../subtypes";
|
||||||
import { Point } from "../types";
|
import { Point } from "../types";
|
||||||
import {
|
import {
|
||||||
FONT_FAMILY,
|
FONT_FAMILY,
|
||||||
@ -64,6 +65,7 @@ type _ExcalidrawElementBase = Readonly<{
|
|||||||
updated: number;
|
updated: number;
|
||||||
link: string | null;
|
link: string | null;
|
||||||
locked: boolean;
|
locked: boolean;
|
||||||
|
subtype?: Subtype;
|
||||||
customData?: Record<string, any>;
|
customData?: Record<string, any>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
52
src/subtypes.ts
Normal file
52
src/subtypes.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { ExcalidrawElement, ExcalidrawTextElement } from "./element/types";
|
||||||
|
|
||||||
|
// Subtype Names
|
||||||
|
export type Subtype = string;
|
||||||
|
|
||||||
|
// Subtype Methods
|
||||||
|
export type SubtypeMethods = {
|
||||||
|
measureText: (
|
||||||
|
element: Pick<
|
||||||
|
ExcalidrawTextElement,
|
||||||
|
| "subtype"
|
||||||
|
| "customData"
|
||||||
|
| "fontSize"
|
||||||
|
| "fontFamily"
|
||||||
|
| "text"
|
||||||
|
| "lineHeight"
|
||||||
|
>,
|
||||||
|
next?: {
|
||||||
|
fontSize?: number;
|
||||||
|
text?: string;
|
||||||
|
customData?: ExcalidrawElement["customData"];
|
||||||
|
},
|
||||||
|
) => { width: number; height: number; baseline: number };
|
||||||
|
wrapText: (
|
||||||
|
element: Pick<
|
||||||
|
ExcalidrawTextElement,
|
||||||
|
| "subtype"
|
||||||
|
| "customData"
|
||||||
|
| "fontSize"
|
||||||
|
| "fontFamily"
|
||||||
|
| "originalText"
|
||||||
|
| "lineHeight"
|
||||||
|
>,
|
||||||
|
containerWidth: number,
|
||||||
|
next?: {
|
||||||
|
fontSize?: number;
|
||||||
|
text?: string;
|
||||||
|
customData?: ExcalidrawElement["customData"];
|
||||||
|
},
|
||||||
|
) => string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MethodMap = { subtype: Subtype; methods: Partial<SubtypeMethods> };
|
||||||
|
const methodMaps = [] as Array<MethodMap>;
|
||||||
|
|
||||||
|
// Use `getSubtypeMethods` to call subtype-specialized methods, like `render`.
|
||||||
|
export const getSubtypeMethods = (
|
||||||
|
subtype: Subtype | undefined,
|
||||||
|
): Partial<SubtypeMethods> | undefined => {
|
||||||
|
const map = methodMaps.find((method) => method.subtype === subtype);
|
||||||
|
return map?.methods;
|
||||||
|
};
|
@ -5,7 +5,7 @@ exports[`Test Linear Elements Test bound text element should match styles for te
|
|||||||
class="excalidraw-wysiwyg"
|
class="excalidraw-wysiwyg"
|
||||||
data-type="wysiwyg"
|
data-type="wysiwyg"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
style="position: absolute; display: inline-block; min-height: 1em; margin: 0px; padding: 0px; border: 0px; outline: 0; resize: none; background: transparent; overflow: hidden; z-index: var(--zIndex-wysiwyg); word-break: break-word; white-space: pre-wrap; overflow-wrap: break-word; box-sizing: content-box; width: 10.5px; height: 25px; left: 35px; top: 7.5px; transform: translate(0px, 0px) scale(1) rotate(0deg); text-align: center; vertical-align: middle; color: rgb(0, 0, 0); opacity: 1; filter: var(--theme-filter); max-height: -7.5px; font: Emoji 20px 20px; line-height: 1.25; font-family: Virgil, Segoe UI Emoji;"
|
style="position: absolute; display: inline-block; min-height: 1em; margin: 0px; padding: 0px; border: 0px; outline: 0; resize: none; background: transparent; overflow: hidden; z-index: var(--zIndex-wysiwyg); word-break: break-word; white-space: pre-wrap; overflow-wrap: break-word; box-sizing: content-box; width: 10.5px; height: 25px; left: 35px; top: 7.5px; transform-origin: 5px 12.5px; transform: translate(0px, 0px) scale(1) rotate(0deg) translate(0px, 0px); text-align: center; vertical-align: middle; color: rgb(0, 0, 0); opacity: 1; filter: var(--theme-filter); max-height: -7.5px; font: Emoji 20px 20px; line-height: 1.25; font-family: Virgil, Segoe UI Emoji;"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
wrap="off"
|
wrap="off"
|
||||||
/>
|
/>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user