Compare commits
9 Commits
master
...
aakansha-n
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4ed3a2e7be | ||
![]() |
faec098e30 | ||
![]() |
65e849804d | ||
![]() |
7c0f783cbc | ||
![]() |
fd379c2897 | ||
![]() |
97929c07d6 | ||
![]() |
ba22646c22 | ||
![]() |
c21fecde40 | ||
![]() |
caf0a904db |
@ -49,18 +49,7 @@ describe("Test wrapText", () => {
|
||||
{
|
||||
desc: "break all characters when width of each character is less than container width",
|
||||
width: 25,
|
||||
res: `H
|
||||
e
|
||||
l
|
||||
l
|
||||
o
|
||||
w
|
||||
h
|
||||
a
|
||||
t
|
||||
s
|
||||
u
|
||||
p`,
|
||||
res: `H\ne\nl\nl\no \nw\nh\na\nt\ns \nu\np`,
|
||||
},
|
||||
{
|
||||
desc: "break words as per the width",
|
||||
@ -90,8 +79,7 @@ up`,
|
||||
});
|
||||
|
||||
describe("When text contain new lines", () => {
|
||||
const text = `Hello
|
||||
whats up`;
|
||||
const text = "Hello\nwhats up";
|
||||
[
|
||||
{
|
||||
desc: "break all words when width of each word is less than container width",
|
||||
@ -101,18 +89,7 @@ whats up`;
|
||||
{
|
||||
desc: "break all characters when width of each character is less than container width",
|
||||
width: 25,
|
||||
res: `H
|
||||
e
|
||||
l
|
||||
l
|
||||
o
|
||||
w
|
||||
h
|
||||
a
|
||||
t
|
||||
s
|
||||
u
|
||||
p`,
|
||||
res: `H\ne\nl\nl\no\nw\nh\na\nt\ns \nu\np`,
|
||||
},
|
||||
{
|
||||
desc: "break words as per the width",
|
||||
@ -149,13 +126,7 @@ whats up`,
|
||||
desc: "fit characters of long string as per container width and break words as per the width",
|
||||
|
||||
width: 130,
|
||||
res: `hellolongte
|
||||
xtthisiswha
|
||||
tsupwithyou
|
||||
Iamtypinggg
|
||||
ggandtyping
|
||||
gg break it
|
||||
now`,
|
||||
res: `hellolongte\nxtthisiswha\ntsupwithyou\nIamtypinggg\nggandtyping\ngg break it \nnow`,
|
||||
},
|
||||
{
|
||||
desc: "fit the long text when container width is greater than text length and move the rest to next line",
|
||||
@ -190,7 +161,7 @@ now`,
|
||||
"Wikipedia is hosted by Wikimedia- Foundation, a non-profit organization that also hosts a range-of other projects";
|
||||
const res = wrapText(text, font, 110);
|
||||
expect(res).toBe(
|
||||
`Wikipedia \nis hosted \nby \nWikimedia-\nFoundation,\na non-\nprofit \norganizati\non that \nalso hosts\na range-of\nother \nprojects`,
|
||||
`Wikipedia \nis hosted \nby \nWikimedia- \nFoundation,\na non-\nprofit \norganizati\non that \nalso hosts \na range-of \nother \nprojects`,
|
||||
);
|
||||
|
||||
text = "Hello thereusing-now";
|
||||
|
@ -76,6 +76,7 @@ export const redrawTextBoundingBox = (
|
||||
boundTextUpdates.text,
|
||||
getFontString(textElement),
|
||||
textElement.lineHeight,
|
||||
maxWidth,
|
||||
);
|
||||
|
||||
boundTextUpdates.width = metrics.width;
|
||||
@ -195,6 +196,7 @@ export const handleBindTextResize = (
|
||||
text,
|
||||
getFontString(textElement),
|
||||
textElement.lineHeight,
|
||||
maxWidth,
|
||||
);
|
||||
nextHeight = metrics.height;
|
||||
nextWidth = metrics.width;
|
||||
@ -283,6 +285,7 @@ export const measureText = (
|
||||
text: string,
|
||||
font: FontString,
|
||||
lineHeight: ExcalidrawTextElement["lineHeight"],
|
||||
maxWidth?: number | null,
|
||||
) => {
|
||||
text = text
|
||||
.split("\n")
|
||||
@ -292,7 +295,14 @@ export const measureText = (
|
||||
.join("\n");
|
||||
const fontSize = parseFloat(font);
|
||||
const height = getTextHeight(text, fontSize, lineHeight);
|
||||
const width = getTextWidth(text, font);
|
||||
let width = getTextWidth(text, font);
|
||||
// Since we now preserve trailing whitespaces so if the text has
|
||||
// trailing whitespaces, it will be considered in the width and thus width
|
||||
// computed might be much higher than the allowed max width
|
||||
// by the container hence making sure the width never goes beyond the max width.
|
||||
if (maxWidth) {
|
||||
width = Math.min(width, maxWidth);
|
||||
}
|
||||
const baseline = measureBaseline(text, font, lineHeight);
|
||||
return { width, height, baseline };
|
||||
};
|
||||
@ -380,7 +390,7 @@ export const getApproxMinLineHeight = (
|
||||
|
||||
let canvas: HTMLCanvasElement | undefined;
|
||||
|
||||
const getLineWidth = (text: string, font: FontString) => {
|
||||
export const getLineWidth = (text: string, font: FontString) => {
|
||||
if (!canvas) {
|
||||
canvas = document.createElement("canvas");
|
||||
}
|
||||
@ -440,10 +450,8 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
|
||||
if (!Number.isFinite(maxWidth) || maxWidth < 0) {
|
||||
return text;
|
||||
}
|
||||
|
||||
const lines: Array<string> = [];
|
||||
const originalLines = text.split("\n");
|
||||
const spaceWidth = getLineWidth(" ", font);
|
||||
|
||||
let currentLine = "";
|
||||
let currentLineWidthTillNow = 0;
|
||||
@ -459,7 +467,7 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
|
||||
currentLineWidthTillNow = 0;
|
||||
};
|
||||
originalLines.forEach((originalLine) => {
|
||||
const currentLineWidth = getTextWidth(originalLine, font);
|
||||
const currentLineWidth = getLineWidth(originalLine, font);
|
||||
|
||||
// Push the line if its <= maxWidth
|
||||
if (currentLineWidth <= maxWidth) {
|
||||
@ -507,23 +515,25 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
|
||||
}
|
||||
}
|
||||
// push current line if appending space exceeds max width
|
||||
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
|
||||
if (currentLineWidthTillNow >= maxWidth) {
|
||||
push(currentLine);
|
||||
resetParams();
|
||||
// space needs to be appended before next word
|
||||
// as currentLine contains chars which couldn't be appended
|
||||
// to previous line unless the line ends with hyphen to sync
|
||||
// with css word-wrap
|
||||
} else if (!currentLine.endsWith("-")) {
|
||||
} else if (!currentLine.endsWith("-") && index < words.length) {
|
||||
currentLine += " ";
|
||||
currentLineWidthTillNow += spaceWidth;
|
||||
}
|
||||
index++;
|
||||
} else {
|
||||
// Start appending words in a line till max width reached
|
||||
while (currentLineWidthTillNow < maxWidth && index < words.length) {
|
||||
const word = words[index];
|
||||
currentLineWidthTillNow = getLineWidth(currentLine + word, font);
|
||||
currentLineWidthTillNow = getLineWidth(
|
||||
`${currentLine + word}`.trimEnd(),
|
||||
font,
|
||||
);
|
||||
|
||||
if (currentLineWidthTillNow > maxWidth) {
|
||||
push(currentLine);
|
||||
@ -531,24 +541,20 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
|
||||
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
|
||||
// if word ends with "-" then we don't need to add space
|
||||
// to sync with css word-wrap
|
||||
const shouldAppendSpace = !word.endsWith("-");
|
||||
currentLine += word;
|
||||
|
||||
if (shouldAppendSpace) {
|
||||
if (shouldAppendSpace && index < words.length) {
|
||||
currentLine += " ";
|
||||
}
|
||||
index++;
|
||||
|
||||
// Push the word if appending space exceeds max width
|
||||
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
|
||||
if (shouldAppendSpace) {
|
||||
lines.push(currentLine.slice(0, -1));
|
||||
} else {
|
||||
lines.push(currentLine);
|
||||
}
|
||||
if (currentLineWidthTillNow >= maxWidth) {
|
||||
lines.push(currentLine);
|
||||
resetParams();
|
||||
break;
|
||||
}
|
||||
@ -971,3 +977,22 @@ export const getDefaultLineHeight = (fontFamily: FontFamilyValues) => {
|
||||
}
|
||||
return DEFAULT_LINE_HEIGHT[DEFAULT_FONT_FAMILY];
|
||||
};
|
||||
|
||||
export const getSpacesOffsetForLine = (
|
||||
element: ExcalidrawTextElement,
|
||||
line: string,
|
||||
font: FontString,
|
||||
) => {
|
||||
const container = getContainerElement(element);
|
||||
const trailingSpacesWidth =
|
||||
getLineWidth(line, font) - getLineWidth(line.trimEnd(), font);
|
||||
const maxWidth = container ? getBoundTextMaxWidth(container) : element.width;
|
||||
const availableWidth = maxWidth - getLineWidth(line.trimEnd(), font);
|
||||
let spacesOffset = 0;
|
||||
if (element.textAlign === TEXT_ALIGN.CENTER) {
|
||||
spacesOffset = -Math.min(trailingSpacesWidth / 2, availableWidth / 2);
|
||||
} else if (element.textAlign === TEXT_ALIGN.RIGHT) {
|
||||
spacesOffset = -Math.min(availableWidth, trailingSpacesWidth);
|
||||
}
|
||||
return spacesOffset;
|
||||
};
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
isBoundToContainer,
|
||||
isTextElement,
|
||||
} from "./typeChecks";
|
||||
import { CLASSES, isSafari } from "../constants";
|
||||
import { CLASSES, TEXT_ALIGN, isSafari } from "../constants";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawLinearElement,
|
||||
@ -26,7 +26,7 @@ import {
|
||||
getContainerDims,
|
||||
getContainerElement,
|
||||
getTextElementAngle,
|
||||
getTextWidth,
|
||||
measureText,
|
||||
normalizeText,
|
||||
redrawTextBoundingBox,
|
||||
wrapText,
|
||||
@ -196,6 +196,8 @@ export const textWysiwyg = ({
|
||||
}
|
||||
|
||||
maxWidth = getBoundTextMaxWidth(container);
|
||||
textElementWidth = Math.min(textElementWidth, maxWidth);
|
||||
|
||||
maxHeight = getBoundTextMaxHeight(
|
||||
container,
|
||||
updatedTextElement as ExcalidrawTextElementWithContainer,
|
||||
@ -230,7 +232,16 @@ export const textWysiwyg = ({
|
||||
coordY = y;
|
||||
}
|
||||
}
|
||||
const [viewportX, viewportY] = getViewportCoords(coordX, coordY);
|
||||
let spacesOffset = 0;
|
||||
if (updatedTextElement.textAlign === TEXT_ALIGN.CENTER) {
|
||||
spacesOffset = Math.max(0, updatedTextElement.width / 2 - maxWidth / 2);
|
||||
} else if (updatedTextElement.textAlign === TEXT_ALIGN.RIGHT) {
|
||||
spacesOffset = Math.max(0, updatedTextElement.width - maxWidth);
|
||||
}
|
||||
const [viewportX, viewportY] = getViewportCoords(
|
||||
coordX + spacesOffset,
|
||||
coordY,
|
||||
);
|
||||
const initialSelectionStart = editable.selectionStart;
|
||||
const initialSelectionEnd = editable.selectionEnd;
|
||||
const initialLength = editable.value.length;
|
||||
@ -362,7 +373,12 @@ export const textWysiwyg = ({
|
||||
font,
|
||||
getBoundTextMaxWidth(container),
|
||||
);
|
||||
const width = getTextWidth(wrappedText, font);
|
||||
const { width } = measureText(
|
||||
wrappedText,
|
||||
font,
|
||||
element.lineHeight,
|
||||
getBoundTextMaxWidth(container),
|
||||
);
|
||||
editable.style.width = `${width}px`;
|
||||
}
|
||||
};
|
||||
|
@ -46,6 +46,7 @@ import {
|
||||
getLineHeightInPx,
|
||||
getBoundTextMaxHeight,
|
||||
getBoundTextMaxWidth,
|
||||
getSpacesOffsetForLine,
|
||||
} from "../element/textElement";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
|
||||
@ -319,13 +320,13 @@ const drawElementOnCanvas = (
|
||||
}
|
||||
context.canvas.setAttribute("dir", rtl ? "rtl" : "ltr");
|
||||
context.save();
|
||||
context.font = getFontString(element);
|
||||
const font = getFontString(element);
|
||||
context.font = font;
|
||||
context.fillStyle = element.strokeColor;
|
||||
context.textAlign = element.textAlign as CanvasTextAlign;
|
||||
|
||||
// Canvas does not support multiline text by default
|
||||
const lines = element.text.replace(/\r\n?/g, "\n").split("\n");
|
||||
|
||||
const horizontalOffset =
|
||||
element.textAlign === "center"
|
||||
? element.width / 2
|
||||
@ -336,11 +337,17 @@ const drawElementOnCanvas = (
|
||||
element.fontSize,
|
||||
element.lineHeight,
|
||||
);
|
||||
|
||||
const verticalOffset = element.height - element.baseline;
|
||||
for (let index = 0; index < lines.length; index++) {
|
||||
context.fillText(
|
||||
const spacesOffset = getSpacesOffsetForLine(
|
||||
element,
|
||||
lines[index],
|
||||
horizontalOffset,
|
||||
font,
|
||||
);
|
||||
context.fillText(
|
||||
lines[index].trimEnd(),
|
||||
horizontalOffset + spacesOffset,
|
||||
(index + 1) * lineHeightPx - verticalOffset,
|
||||
);
|
||||
}
|
||||
|
@ -1151,7 +1151,7 @@ describe("Test Linear Elements", () => {
|
||||
expect(
|
||||
wrapText(textElement.originalText, font, getBoundTextMaxWidth(arrow)),
|
||||
).toMatchInlineSnapshot(`
|
||||
"Online whiteboard collaboration
|
||||
"Online whiteboard collaboration
|
||||
made easy"
|
||||
`);
|
||||
const handleBindTextResizeSpy = jest.spyOn(
|
||||
|
Loading…
x
Reference in New Issue
Block a user