From 4d6d6cf1294465fae8e5093b638dac06f8261ae0 Mon Sep 17 00:00:00 2001 From: "Daniel J. Geiger" <1852529+DanielJGeiger@users.noreply.github.com> Date: Fri, 22 Sep 2023 10:17:51 -0500 Subject: [PATCH] fix: Text-only measurements off by a pixel --- .../subtypes/mathjax/implementation.tsx | 20 ++++++++++++------ .../mathjax/tests/implementation.test.tsx | 21 +++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 src/excalidraw-app/subtypes/mathjax/tests/implementation.test.tsx diff --git a/src/excalidraw-app/subtypes/mathjax/implementation.tsx b/src/excalidraw-app/subtypes/mathjax/implementation.tsx index 8c2d4e37e..45818c345 100644 --- a/src/excalidraw-app/subtypes/mathjax/implementation.tsx +++ b/src/excalidraw-app/subtypes/mathjax/implementation.tsx @@ -621,8 +621,6 @@ const measureMarkup = ( container.appendChild(span); // Baseline is important for positioning text on canvas const baseline = span.offsetTop + span.offsetHeight; - const width = container.offsetWidth + 1; - const height = container.offsetHeight; const containerRect = container.getBoundingClientRect(); // Compute for each SVG or Text child node of line (the last @@ -673,6 +671,12 @@ const measureMarkup = ( childMetrics.push({ x: 0, y: 0, width: 0, height: 0 }); } document.body.removeChild(container); + let width = 0; + let height = 0; + childMetrics.forEach((metrics) => (width += metrics.width)); + childMetrics.forEach( + (metrics) => (height = Math.max(height, metrics.height)), + ); return { width, height, baseline, childMetrics }; }; @@ -735,10 +739,14 @@ const getMetrics = ( imageHeight += height; } const lastLineMetrics = lineMetrics[lineMetrics.length - 1]; + const imageBaseline = Math.max( + 0, + imageHeight - lastLineMetrics.height + lastLineMetrics.baseline - 1, + ); const imageMetrics = { width: imageWidth, height: imageHeight, - baseline: imageHeight - lastLineMetrics.height + lastLineMetrics.baseline, + baseline: imageBaseline, }; const metrics = { markupMetrics, lineMetrics, imageMetrics }; if (isMathJaxLoaded) { @@ -778,17 +786,17 @@ const renderMath = ( ); const width = parentWidth ?? metrics.imageMetrics.width; - let y = 0; + let y = -1; for (let index = 0; index < markup.length; index++) { const lineMetrics = metrics.lineMetrics[index]; const lineMarkupMetrics = metrics.markupMetrics[index]; const rtl = isRTL(mathLines[index]); const x = textAlign === "right" - ? width - lineMetrics.width + ? width - lineMetrics.width + 1 : textAlign === "left" ? 0 - : (width - lineMetrics.width) / 2; + : (width - lineMetrics.width + 1) / 2; // Drop any empty strings from this line to match childMetrics const content = markup[index].filter((value) => value !== ""); for (let i = 0; i < content.length; i += 1) { diff --git a/src/excalidraw-app/subtypes/mathjax/tests/implementation.test.tsx b/src/excalidraw-app/subtypes/mathjax/tests/implementation.test.tsx new file mode 100644 index 000000000..940c69323 --- /dev/null +++ b/src/excalidraw-app/subtypes/mathjax/tests/implementation.test.tsx @@ -0,0 +1,21 @@ +import { render } from "../../../../tests/test-utils"; +import { API } from "../../../../tests/helpers/api"; +import ExcalidrawApp from "../../../"; + +import { measureTextElement } from "../../../../element/textElement"; +import { ensureSubtypesLoaded } from "../../../../subtypes"; + +describe("mathjax", () => { + it("text-only measurements match", async () => { + await render(); + await ensureSubtypesLoaded(["math"]); + const text = "A quick brown fox jumps over the lazy dog."; + const elements = [ + API.createElement({ type: "text", id: "A", text, subtype: "math" }), + API.createElement({ type: "text", id: "B", text }), + ]; + const metrics1 = measureTextElement(elements[0]); + const metrics2 = measureTextElement(elements[1]); + expect(metrics1).toStrictEqual(metrics2); + }); +});