Fix RTL text direction rendering (#1687)
Co-authored-by: Lipis <lipiridis@gmail.com>
This commit is contained in:
parent
fd75b88bd3
commit
a118bed82f
@ -330,7 +330,6 @@ export const exportCanvas = async (
|
|||||||
shouldAddWatermark,
|
shouldAddWatermark,
|
||||||
});
|
});
|
||||||
tempCanvas.style.display = "none";
|
tempCanvas.style.display = "none";
|
||||||
document.body.appendChild(tempCanvas);
|
|
||||||
|
|
||||||
if (type === "png") {
|
if (type === "png") {
|
||||||
const fileName = `${name}.png`;
|
const fileName = `${name}.png`;
|
||||||
|
@ -14,7 +14,13 @@ 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, getFontString, getFontFamilyString } from "../utils";
|
import {
|
||||||
|
SVG_NS,
|
||||||
|
distance,
|
||||||
|
getFontString,
|
||||||
|
getFontFamilyString,
|
||||||
|
isRTL,
|
||||||
|
} from "../utils";
|
||||||
import { isPathALoop } from "../math";
|
import { isPathALoop } from "../math";
|
||||||
import rough from "roughjs/bin/rough";
|
import rough from "roughjs/bin/rough";
|
||||||
|
|
||||||
@ -38,6 +44,11 @@ const generateElementCanvas = (
|
|||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
const context = canvas.getContext("2d")!;
|
const context = canvas.getContext("2d")!;
|
||||||
|
|
||||||
|
// To be able to draw a nested element with RTL, we have to append it to the DOM
|
||||||
|
canvas.style.display = "none";
|
||||||
|
canvas.id = "nested-canvas-element";
|
||||||
|
document.body.appendChild(canvas);
|
||||||
|
|
||||||
let canvasOffsetX = 0;
|
let canvasOffsetX = 0;
|
||||||
let canvasOffsetY = 0;
|
let canvasOffsetY = 0;
|
||||||
|
|
||||||
@ -100,12 +111,14 @@ const drawElementOnCanvas = (
|
|||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
if (isTextElement(element)) {
|
if (isTextElement(element)) {
|
||||||
|
context.canvas.setAttribute("dir", isRTL(element.text) ? "rtl" : "ltr");
|
||||||
const font = context.font;
|
const font = context.font;
|
||||||
context.font = getFontString(element);
|
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;
|
||||||
context.textAlign = element.textAlign as CanvasTextAlign;
|
context.textAlign = element.textAlign as CanvasTextAlign;
|
||||||
|
|
||||||
// Canvas does not support multiline text by default
|
// Canvas does not support multiline text by default
|
||||||
const lines = element.text.replace(/\r\n?/g, "\n").split("\n");
|
const lines = element.text.replace(/\r\n?/g, "\n").split("\n");
|
||||||
const lineHeight = element.height / lines.length;
|
const lineHeight = element.height / lines.length;
|
||||||
@ -119,7 +132,7 @@ const drawElementOnCanvas = (
|
|||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
context.fillText(
|
context.fillText(
|
||||||
lines[i],
|
lines[i],
|
||||||
0 + horizontalOffset,
|
horizontalOffset,
|
||||||
(i + 1) * lineHeight - verticalOffset,
|
(i + 1) * lineHeight - verticalOffset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -342,6 +355,12 @@ const drawElementFromCanvas = (
|
|||||||
context.rotate(-element.angle);
|
context.rotate(-element.angle);
|
||||||
context.translate(-cx, -cy);
|
context.translate(-cx, -cy);
|
||||||
context.scale(window.devicePixelRatio, window.devicePixelRatio);
|
context.scale(window.devicePixelRatio, window.devicePixelRatio);
|
||||||
|
|
||||||
|
// Clear the nested element we appended to the DOM
|
||||||
|
const node = document.querySelector("#nested-canvas-element");
|
||||||
|
if (node && node.parentNode) {
|
||||||
|
node.parentNode.removeChild(node);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const renderElement = (
|
export const renderElement = (
|
||||||
@ -375,9 +394,12 @@ export const renderElement = (
|
|||||||
case "draw":
|
case "draw":
|
||||||
case "arrow":
|
case "arrow":
|
||||||
case "text": {
|
case "text": {
|
||||||
const elementWithCanvas = generateElement(element, generator, sceneState);
|
|
||||||
|
|
||||||
if (renderOptimizations) {
|
if (renderOptimizations) {
|
||||||
|
const elementWithCanvas = generateElement(
|
||||||
|
element,
|
||||||
|
generator,
|
||||||
|
sceneState,
|
||||||
|
);
|
||||||
drawElementFromCanvas(elementWithCanvas, rc, context, sceneState);
|
drawElementFromCanvas(elementWithCanvas, rc, context, sceneState);
|
||||||
} else {
|
} else {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||||
@ -487,17 +509,14 @@ export const renderElementToSvg = (
|
|||||||
const lineHeight = element.height / lines.length;
|
const lineHeight = element.height / lines.length;
|
||||||
const verticalOffset = element.height - element.baseline;
|
const verticalOffset = element.height - element.baseline;
|
||||||
const horizontalOffset =
|
const horizontalOffset =
|
||||||
element.textAlign === "center"
|
element.textAlign === "center" ? element.width / 2 : element.width;
|
||||||
? element.width / 2
|
|
||||||
: element.textAlign === "right"
|
|
||||||
? element.width
|
|
||||||
: 0;
|
|
||||||
const textAnchor =
|
const textAnchor =
|
||||||
element.textAlign === "center"
|
element.textAlign === "center"
|
||||||
? "middle"
|
? "middle"
|
||||||
: element.textAlign === "right"
|
: element.textAlign === "right"
|
||||||
? "end"
|
? "end"
|
||||||
: "start";
|
: "start";
|
||||||
|
const direction = isRTL(element.text) ? "rtl" : "ltr";
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const text = svgRoot.ownerDocument!.createElementNS(SVG_NS, "text");
|
const text = svgRoot.ownerDocument!.createElementNS(SVG_NS, "text");
|
||||||
text.textContent = lines[i];
|
text.textContent = lines[i];
|
||||||
@ -508,6 +527,7 @@ export const renderElementToSvg = (
|
|||||||
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;");
|
||||||
|
text.setAttribute("direction", direction);
|
||||||
node.appendChild(text);
|
node.appendChild(text);
|
||||||
}
|
}
|
||||||
svgRoot.appendChild(node);
|
svgRoot.appendChild(node);
|
||||||
|
@ -32,6 +32,8 @@ export const exportToCanvas = (
|
|||||||
const tempCanvas = document.createElement("canvas");
|
const tempCanvas = document.createElement("canvas");
|
||||||
tempCanvas.width = width * scale;
|
tempCanvas.width = width * scale;
|
||||||
tempCanvas.height = height * scale;
|
tempCanvas.height = height * scale;
|
||||||
|
// We append the canvas before drawing it to it for RTL to work
|
||||||
|
document.body.appendChild(tempCanvas);
|
||||||
return tempCanvas;
|
return tempCanvas;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
|
10
src/utils.ts
10
src/utils.ts
@ -227,3 +227,13 @@ export const sceneCoordsToViewportCoords = (
|
|||||||
|
|
||||||
export const getGlobalCSSVariable = (name: string) =>
|
export const getGlobalCSSVariable = (name: string) =>
|
||||||
getComputedStyle(document.documentElement).getPropertyValue(`--${name}`);
|
getComputedStyle(document.documentElement).getPropertyValue(`--${name}`);
|
||||||
|
|
||||||
|
export const isRTL = (text: string) => {
|
||||||
|
const ltrChars =
|
||||||
|
"A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF" +
|
||||||
|
"\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF";
|
||||||
|
const rtlChars = "\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC";
|
||||||
|
const rtlDirCheck = new RegExp(`^[^${ltrChars}]*[${rtlChars}]`);
|
||||||
|
|
||||||
|
return rtlDirCheck.test(text);
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user