|
@@ -44,68 +44,69 @@ export const redrawTextBoundingBox = (
|
|
|
container: ExcalidrawElement | null,
|
|
|
) => {
|
|
|
let maxWidth = undefined;
|
|
|
- let text = textElement.text;
|
|
|
+
|
|
|
+ const boundTextUpdates = {
|
|
|
+ x: textElement.x,
|
|
|
+ y: textElement.y,
|
|
|
+ text: textElement.text,
|
|
|
+ width: textElement.width,
|
|
|
+ height: textElement.height,
|
|
|
+ baseline: textElement.baseline,
|
|
|
+ };
|
|
|
+
|
|
|
+ boundTextUpdates.text = textElement.text;
|
|
|
+
|
|
|
if (container) {
|
|
|
maxWidth = getMaxContainerWidth(container);
|
|
|
- text = wrapText(
|
|
|
+ boundTextUpdates.text = wrapText(
|
|
|
textElement.originalText,
|
|
|
getFontString(textElement),
|
|
|
maxWidth,
|
|
|
);
|
|
|
}
|
|
|
- const metrics = measureText(text, getFontString(textElement), maxWidth);
|
|
|
- let coordY = textElement.y;
|
|
|
- let coordX = textElement.x;
|
|
|
- // Resize container and vertically center align the text
|
|
|
+ const metrics = measureText(
|
|
|
+ boundTextUpdates.text,
|
|
|
+ getFontString(textElement),
|
|
|
+ maxWidth,
|
|
|
+ );
|
|
|
+
|
|
|
+ boundTextUpdates.width = metrics.width;
|
|
|
+ boundTextUpdates.height = metrics.height;
|
|
|
+ boundTextUpdates.baseline = metrics.baseline;
|
|
|
+
|
|
|
if (container) {
|
|
|
- if (!isArrowElement(container)) {
|
|
|
- const containerDims = getContainerDims(container);
|
|
|
- let nextHeight = containerDims.height;
|
|
|
- if (textElement.verticalAlign === VERTICAL_ALIGN.TOP) {
|
|
|
- coordY = container.y;
|
|
|
- } else if (textElement.verticalAlign === VERTICAL_ALIGN.BOTTOM) {
|
|
|
- coordY =
|
|
|
- container.y +
|
|
|
- containerDims.height -
|
|
|
- metrics.height -
|
|
|
- BOUND_TEXT_PADDING;
|
|
|
- } else {
|
|
|
- coordY = container.y + containerDims.height / 2 - metrics.height / 2;
|
|
|
- if (metrics.height > getMaxContainerHeight(container)) {
|
|
|
- nextHeight = metrics.height + BOUND_TEXT_PADDING * 2;
|
|
|
- coordY = container.y + nextHeight / 2 - metrics.height / 2;
|
|
|
- }
|
|
|
- }
|
|
|
- if (textElement.textAlign === TEXT_ALIGN.LEFT) {
|
|
|
- coordX = container.x + BOUND_TEXT_PADDING;
|
|
|
- } else if (textElement.textAlign === TEXT_ALIGN.RIGHT) {
|
|
|
- coordX =
|
|
|
- container.x +
|
|
|
- containerDims.width -
|
|
|
- metrics.width -
|
|
|
- BOUND_TEXT_PADDING;
|
|
|
- } else {
|
|
|
- coordX = container.x + containerDims.width / 2 - metrics.width / 2;
|
|
|
- }
|
|
|
- updateOriginalContainerCache(container.id, nextHeight);
|
|
|
- mutateElement(container, { height: nextHeight });
|
|
|
- } else {
|
|
|
+ if (isArrowElement(container)) {
|
|
|
const centerX = textElement.x + textElement.width / 2;
|
|
|
const centerY = textElement.y + textElement.height / 2;
|
|
|
const diffWidth = metrics.width - textElement.width;
|
|
|
const diffHeight = metrics.height - textElement.height;
|
|
|
- coordY = centerY - (textElement.height + diffHeight) / 2;
|
|
|
- coordX = centerX - (textElement.width + diffWidth) / 2;
|
|
|
+ boundTextUpdates.x = centerY - (textElement.height + diffHeight) / 2;
|
|
|
+ boundTextUpdates.y = centerX - (textElement.width + diffWidth) / 2;
|
|
|
+ } else {
|
|
|
+ const containerDims = getContainerDims(container);
|
|
|
+ let maxContainerHeight = getMaxContainerHeight(container);
|
|
|
+
|
|
|
+ let nextHeight = containerDims.height;
|
|
|
+ if (metrics.height > maxContainerHeight) {
|
|
|
+ nextHeight = computeContainerHeightForBoundText(
|
|
|
+ container,
|
|
|
+ metrics.height,
|
|
|
+ );
|
|
|
+ mutateElement(container, { height: nextHeight });
|
|
|
+ maxContainerHeight = getMaxContainerHeight(container);
|
|
|
+ updateOriginalContainerCache(container.id, nextHeight);
|
|
|
+ }
|
|
|
+ const updatedTextElement = {
|
|
|
+ ...textElement,
|
|
|
+ ...boundTextUpdates,
|
|
|
+ } as ExcalidrawTextElementWithContainer;
|
|
|
+ const { x, y } = computeBoundTextPosition(container, updatedTextElement);
|
|
|
+ boundTextUpdates.x = x;
|
|
|
+ boundTextUpdates.y = y;
|
|
|
}
|
|
|
}
|
|
|
- mutateElement(textElement, {
|
|
|
- width: metrics.width,
|
|
|
- height: metrics.height,
|
|
|
- baseline: metrics.baseline,
|
|
|
- y: coordY,
|
|
|
- x: coordX,
|
|
|
- text,
|
|
|
- });
|
|
|
+
|
|
|
+ mutateElement(textElement, boundTextUpdates);
|
|
|
};
|
|
|
|
|
|
export const bindTextToShapeAfterDuplication = (
|
|
@@ -197,7 +198,11 @@ export const handleBindTextResize = (
|
|
|
}
|
|
|
// increase height in case text element height exceeds
|
|
|
if (nextHeight > maxHeight) {
|
|
|
- containerHeight = nextHeight + getBoundTextElementOffset(textElement) * 2;
|
|
|
+ containerHeight = computeContainerHeightForBoundText(
|
|
|
+ container,
|
|
|
+ nextHeight,
|
|
|
+ );
|
|
|
+
|
|
|
const diff = containerHeight - containerDims.height;
|
|
|
// fix the y coord when resizing from ne/nw/n
|
|
|
const updatedY =
|
|
@@ -217,48 +222,57 @@ export const handleBindTextResize = (
|
|
|
text,
|
|
|
width: nextWidth,
|
|
|
height: nextHeight,
|
|
|
-
|
|
|
baseline: nextBaseLine,
|
|
|
});
|
|
|
+
|
|
|
if (!isArrowElement(container)) {
|
|
|
- updateBoundTextPosition(
|
|
|
- container,
|
|
|
- textElement as ExcalidrawTextElementWithContainer,
|
|
|
+ mutateElement(
|
|
|
+ textElement,
|
|
|
+ computeBoundTextPosition(
|
|
|
+ container,
|
|
|
+ textElement as ExcalidrawTextElementWithContainer,
|
|
|
+ ),
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-const updateBoundTextPosition = (
|
|
|
+const computeBoundTextPosition = (
|
|
|
container: ExcalidrawElement,
|
|
|
boundTextElement: ExcalidrawTextElementWithContainer,
|
|
|
) => {
|
|
|
- const containerDims = getContainerDims(container);
|
|
|
- const boundTextElementPadding = getBoundTextElementOffset(boundTextElement);
|
|
|
+ const containerCoords = getContainerCoords(container);
|
|
|
+ const maxContainerHeight = getMaxContainerHeight(container);
|
|
|
+ const maxContainerWidth = getMaxContainerWidth(container);
|
|
|
+ const padding = container.type === "ellipse" ? 0 : BOUND_TEXT_PADDING;
|
|
|
+
|
|
|
+ let x;
|
|
|
let y;
|
|
|
if (boundTextElement.verticalAlign === VERTICAL_ALIGN.TOP) {
|
|
|
- y = container.y + boundTextElementPadding;
|
|
|
+ y = containerCoords.y + padding;
|
|
|
} else if (boundTextElement.verticalAlign === VERTICAL_ALIGN.BOTTOM) {
|
|
|
y =
|
|
|
- container.y +
|
|
|
- containerDims.height -
|
|
|
- boundTextElement.height -
|
|
|
- boundTextElementPadding;
|
|
|
+ containerCoords.y +
|
|
|
+ (maxContainerHeight - boundTextElement.height + padding);
|
|
|
} else {
|
|
|
- y = container.y + containerDims.height / 2 - boundTextElement.height / 2;
|
|
|
- }
|
|
|
- const x =
|
|
|
- boundTextElement.textAlign === TEXT_ALIGN.LEFT
|
|
|
- ? container.x + boundTextElementPadding
|
|
|
- : boundTextElement.textAlign === TEXT_ALIGN.RIGHT
|
|
|
- ? container.x +
|
|
|
- containerDims.width -
|
|
|
- boundTextElement.width -
|
|
|
- boundTextElementPadding
|
|
|
- : container.x + containerDims.width / 2 - boundTextElement.width / 2;
|
|
|
-
|
|
|
- mutateElement(boundTextElement, { x, y });
|
|
|
+ y =
|
|
|
+ containerCoords.y +
|
|
|
+ (maxContainerHeight / 2 - boundTextElement.height / 2 + padding);
|
|
|
+ }
|
|
|
+ if (boundTextElement.textAlign === TEXT_ALIGN.LEFT) {
|
|
|
+ x = containerCoords.x + padding;
|
|
|
+ } else if (boundTextElement.textAlign === TEXT_ALIGN.RIGHT) {
|
|
|
+ x =
|
|
|
+ containerCoords.x +
|
|
|
+ (maxContainerWidth - boundTextElement.width + padding);
|
|
|
+ } else {
|
|
|
+ x =
|
|
|
+ containerCoords.x +
|
|
|
+ (maxContainerWidth / 2 - boundTextElement.width / 2 + padding);
|
|
|
+ }
|
|
|
+ return { x, y };
|
|
|
};
|
|
|
+
|
|
|
// https://github.com/grassator/canvas-text-editor/blob/master/lib/FontMetrics.js
|
|
|
export const measureText = (
|
|
|
text: string,
|
|
@@ -621,6 +635,24 @@ export const getContainerCenter = (
|
|
|
return { x: midSegmentMidpoint[0], y: midSegmentMidpoint[1] };
|
|
|
};
|
|
|
|
|
|
+export const getContainerCoords = (container: NonDeletedExcalidrawElement) => {
|
|
|
+ if (container.type === "ellipse") {
|
|
|
+ // The derivation of coordinates is explained in https://github.com/excalidraw/excalidraw/pull/6172
|
|
|
+ const offsetX =
|
|
|
+ (container.width / 2) * (1 - Math.sqrt(2) / 2) + BOUND_TEXT_PADDING;
|
|
|
+ const offsetY =
|
|
|
+ (container.height / 2) * (1 - Math.sqrt(2) / 2) + BOUND_TEXT_PADDING;
|
|
|
+ return {
|
|
|
+ x: container.x + offsetX,
|
|
|
+ y: container.y + offsetY,
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ x: container.x,
|
|
|
+ y: container.y,
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
export const getTextElementAngle = (textElement: ExcalidrawTextElement) => {
|
|
|
const container = getContainerElement(textElement);
|
|
|
if (!container || isArrowElement(container)) {
|
|
@@ -633,12 +665,13 @@ export const getBoundTextElementOffset = (
|
|
|
boundTextElement: ExcalidrawTextElement | null,
|
|
|
) => {
|
|
|
const container = getContainerElement(boundTextElement);
|
|
|
- if (!container) {
|
|
|
+ if (!container || !boundTextElement) {
|
|
|
return 0;
|
|
|
}
|
|
|
if (isArrowElement(container)) {
|
|
|
return BOUND_TEXT_PADDING * 8;
|
|
|
}
|
|
|
+
|
|
|
return BOUND_TEXT_PADDING;
|
|
|
};
|
|
|
|
|
@@ -723,3 +756,16 @@ export const isValidTextContainer = (element: ExcalidrawElement) => {
|
|
|
isArrowElement(element)
|
|
|
);
|
|
|
};
|
|
|
+
|
|
|
+export const computeContainerHeightForBoundText = (
|
|
|
+ container: NonDeletedExcalidrawElement,
|
|
|
+ boundTextElementHeight: number,
|
|
|
+) => {
|
|
|
+ if (container.type === "ellipse") {
|
|
|
+ return Math.round((boundTextElementHeight / Math.sqrt(2)) * 2);
|
|
|
+ }
|
|
|
+ if (isArrowElement(container)) {
|
|
|
+ return boundTextElementHeight + BOUND_TEXT_PADDING * 8 * 2;
|
|
|
+ }
|
|
|
+ return boundTextElementHeight + BOUND_TEXT_PADDING * 2;
|
|
|
+};
|