|
@@ -18,7 +18,6 @@ import {
|
|
DEFAULT_FONT_FAMILY,
|
|
DEFAULT_FONT_FAMILY,
|
|
DEFAULT_FONT_SIZE,
|
|
DEFAULT_FONT_SIZE,
|
|
FONT_FAMILY,
|
|
FONT_FAMILY,
|
|
- isSafari,
|
|
|
|
TEXT_ALIGN,
|
|
TEXT_ALIGN,
|
|
VERTICAL_ALIGN,
|
|
VERTICAL_ALIGN,
|
|
} from "../constants";
|
|
} from "../constants";
|
|
@@ -62,7 +61,6 @@ export const redrawTextBoundingBox = (
|
|
text: textElement.text,
|
|
text: textElement.text,
|
|
width: textElement.width,
|
|
width: textElement.width,
|
|
height: textElement.height,
|
|
height: textElement.height,
|
|
- baseline: textElement.baseline,
|
|
|
|
};
|
|
};
|
|
|
|
|
|
boundTextUpdates.text = textElement.text;
|
|
boundTextUpdates.text = textElement.text;
|
|
@@ -83,7 +81,6 @@ export const redrawTextBoundingBox = (
|
|
|
|
|
|
boundTextUpdates.width = metrics.width;
|
|
boundTextUpdates.width = metrics.width;
|
|
boundTextUpdates.height = metrics.height;
|
|
boundTextUpdates.height = metrics.height;
|
|
- boundTextUpdates.baseline = metrics.baseline;
|
|
|
|
|
|
|
|
if (container) {
|
|
if (container) {
|
|
const maxContainerHeight = getBoundTextMaxHeight(
|
|
const maxContainerHeight = getBoundTextMaxHeight(
|
|
@@ -188,7 +185,6 @@ export const handleBindTextResize = (
|
|
const maxWidth = getBoundTextMaxWidth(container, textElement);
|
|
const maxWidth = getBoundTextMaxWidth(container, textElement);
|
|
const maxHeight = getBoundTextMaxHeight(container, textElement);
|
|
const maxHeight = getBoundTextMaxHeight(container, textElement);
|
|
let containerHeight = container.height;
|
|
let containerHeight = container.height;
|
|
- let nextBaseLine = textElement.baseline;
|
|
|
|
if (
|
|
if (
|
|
shouldMaintainAspectRatio ||
|
|
shouldMaintainAspectRatio ||
|
|
(transformHandleType !== "n" && transformHandleType !== "s")
|
|
(transformHandleType !== "n" && transformHandleType !== "s")
|
|
@@ -207,7 +203,6 @@ export const handleBindTextResize = (
|
|
);
|
|
);
|
|
nextHeight = metrics.height;
|
|
nextHeight = metrics.height;
|
|
nextWidth = metrics.width;
|
|
nextWidth = metrics.width;
|
|
- nextBaseLine = metrics.baseline;
|
|
|
|
}
|
|
}
|
|
// increase height in case text element height exceeds
|
|
// increase height in case text element height exceeds
|
|
if (nextHeight > maxHeight) {
|
|
if (nextHeight > maxHeight) {
|
|
@@ -235,7 +230,6 @@ export const handleBindTextResize = (
|
|
text,
|
|
text,
|
|
width: nextWidth,
|
|
width: nextWidth,
|
|
height: nextHeight,
|
|
height: nextHeight,
|
|
- baseline: nextBaseLine,
|
|
|
|
});
|
|
});
|
|
|
|
|
|
if (!isArrowElement(container)) {
|
|
if (!isArrowElement(container)) {
|
|
@@ -285,8 +279,6 @@ export const computeBoundTextPosition = (
|
|
return { x, y };
|
|
return { x, y };
|
|
};
|
|
};
|
|
|
|
|
|
-// https://github.com/grassator/canvas-text-editor/blob/master/lib/FontMetrics.js
|
|
|
|
-
|
|
|
|
export const measureText = (
|
|
export const measureText = (
|
|
text: string,
|
|
text: string,
|
|
font: FontString,
|
|
font: FontString,
|
|
@@ -301,59 +293,7 @@ export const measureText = (
|
|
const fontSize = parseFloat(font);
|
|
const fontSize = parseFloat(font);
|
|
const height = getTextHeight(text, fontSize, lineHeight);
|
|
const height = getTextHeight(text, fontSize, lineHeight);
|
|
const width = getTextWidth(text, font);
|
|
const width = getTextWidth(text, font);
|
|
- const baseline = measureBaseline(text, font, lineHeight);
|
|
|
|
- return { width, height, baseline };
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-export const measureBaseline = (
|
|
|
|
- text: string,
|
|
|
|
- font: FontString,
|
|
|
|
- lineHeight: ExcalidrawTextElement["lineHeight"],
|
|
|
|
- wrapInContainer?: boolean,
|
|
|
|
-) => {
|
|
|
|
- const container = document.createElement("div");
|
|
|
|
- container.style.position = "absolute";
|
|
|
|
- container.style.whiteSpace = "pre";
|
|
|
|
- container.style.font = font;
|
|
|
|
- container.style.minHeight = "1em";
|
|
|
|
- if (wrapInContainer) {
|
|
|
|
- container.style.overflow = "hidden";
|
|
|
|
- container.style.wordBreak = "break-word";
|
|
|
|
- container.style.whiteSpace = "pre-wrap";
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- container.style.lineHeight = String(lineHeight);
|
|
|
|
-
|
|
|
|
- container.innerText = text;
|
|
|
|
-
|
|
|
|
- // Baseline is important for positioning text on canvas
|
|
|
|
- document.body.appendChild(container);
|
|
|
|
-
|
|
|
|
- const span = document.createElement("span");
|
|
|
|
- span.style.display = "inline-block";
|
|
|
|
- span.style.overflow = "hidden";
|
|
|
|
- span.style.width = "1px";
|
|
|
|
- span.style.height = "1px";
|
|
|
|
- container.appendChild(span);
|
|
|
|
- let baseline = span.offsetTop + span.offsetHeight;
|
|
|
|
- const height = container.offsetHeight;
|
|
|
|
-
|
|
|
|
- if (isSafari) {
|
|
|
|
- const canvasHeight = getTextHeight(text, parseFloat(font), lineHeight);
|
|
|
|
- const fontSize = parseFloat(font);
|
|
|
|
- // In Safari the font size gets rounded off when rendering hence calculating the safari height and shifting the baseline if it differs
|
|
|
|
- // from the actual canvas height
|
|
|
|
- const domHeight = getTextHeight(text, Math.round(fontSize), lineHeight);
|
|
|
|
- if (canvasHeight > height) {
|
|
|
|
- baseline += canvasHeight - domHeight;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (height > canvasHeight) {
|
|
|
|
- baseline -= domHeight - canvasHeight;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- document.body.removeChild(container);
|
|
|
|
- return baseline;
|
|
|
|
|
|
+ return { width, height };
|
|
};
|
|
};
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -378,6 +318,23 @@ export const getLineHeightInPx = (
|
|
return fontSize * lineHeight;
|
|
return fontSize * lineHeight;
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+/**
|
|
|
|
+ * Calculates vertical offset for a text with alphabetic baseline.
|
|
|
|
+ */
|
|
|
|
+export const getVerticalOffset = (
|
|
|
|
+ fontFamily: ExcalidrawTextElement["fontFamily"],
|
|
|
|
+ fontSize: ExcalidrawTextElement["fontSize"],
|
|
|
|
+ lineHeightPx: number,
|
|
|
|
+) => {
|
|
|
|
+ const { unitsPerEm, ascender, descender } = FONT_METRICS[fontFamily];
|
|
|
|
+
|
|
|
|
+ const fontSizeEm = fontSize / unitsPerEm;
|
|
|
|
+ const lineGap = lineHeightPx - fontSizeEm * ascender + fontSizeEm * descender;
|
|
|
|
+
|
|
|
|
+ const verticalOffset = fontSizeEm * ascender + lineGap;
|
|
|
|
+ return verticalOffset;
|
|
|
|
+};
|
|
|
|
+
|
|
// FIXME rename to getApproxMinContainerHeight
|
|
// FIXME rename to getApproxMinContainerHeight
|
|
export const getApproxMinLineHeight = (
|
|
export const getApproxMinLineHeight = (
|
|
fontSize: ExcalidrawTextElement["fontSize"],
|
|
fontSize: ExcalidrawTextElement["fontSize"],
|
|
@@ -964,13 +921,40 @@ const DEFAULT_LINE_HEIGHT = {
|
|
// ~1.25 is the average for Virgil in WebKit and Blink.
|
|
// ~1.25 is the average for Virgil in WebKit and Blink.
|
|
// Gecko (FF) uses ~1.28.
|
|
// Gecko (FF) uses ~1.28.
|
|
[FONT_FAMILY.Virgil]: 1.25 as ExcalidrawTextElement["lineHeight"],
|
|
[FONT_FAMILY.Virgil]: 1.25 as ExcalidrawTextElement["lineHeight"],
|
|
- // ~1.15 is the average for Virgil in WebKit and Blink.
|
|
|
|
- // Gecko if all over the place.
|
|
|
|
|
|
+ // ~1.15 is the average for Helvetica in WebKit and Blink.
|
|
[FONT_FAMILY.Helvetica]: 1.15 as ExcalidrawTextElement["lineHeight"],
|
|
[FONT_FAMILY.Helvetica]: 1.15 as ExcalidrawTextElement["lineHeight"],
|
|
- // ~1.2 is the average for Virgil in WebKit and Blink, and kinda Gecko too
|
|
|
|
|
|
+ // ~1.2 is the average for Cascadia in WebKit and Blink, and kinda Gecko too
|
|
[FONT_FAMILY.Cascadia]: 1.2 as ExcalidrawTextElement["lineHeight"],
|
|
[FONT_FAMILY.Cascadia]: 1.2 as ExcalidrawTextElement["lineHeight"],
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+type FontMetrics = {
|
|
|
|
+ unitsPerEm: number; // head.unitsPerEm
|
|
|
|
+ ascender: number; // sTypoAscender
|
|
|
|
+ descender: number; // sTypoDescender
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * Hardcoded metrics for default fonts, read by https://opentype.js.org/font-inspector.html.
|
|
|
|
+ * For custom fonts, read these metrics on load and extend this object.
|
|
|
|
+ */
|
|
|
|
+const FONT_METRICS = {
|
|
|
|
+ [FONT_FAMILY.Virgil]: {
|
|
|
|
+ unitsPerEm: 1000,
|
|
|
|
+ ascender: 886,
|
|
|
|
+ descender: -374,
|
|
|
|
+ },
|
|
|
|
+ [FONT_FAMILY.Helvetica]: {
|
|
|
|
+ unitsPerEm: 2048,
|
|
|
|
+ ascender: 1577,
|
|
|
|
+ descender: -471,
|
|
|
|
+ },
|
|
|
|
+ [FONT_FAMILY.Cascadia]: {
|
|
|
|
+ unitsPerEm: 2048,
|
|
|
|
+ ascender: 1977,
|
|
|
|
+ descender: -480,
|
|
|
|
+ },
|
|
|
|
+} as Record<number, FontMetrics>;
|
|
|
|
+
|
|
export const getDefaultLineHeight = (fontFamily: FontFamilyValues) => {
|
|
export const getDefaultLineHeight = (fontFamily: FontFamilyValues) => {
|
|
if (fontFamily in DEFAULT_LINE_HEIGHT) {
|
|
if (fontFamily in DEFAULT_LINE_HEIGHT) {
|
|
return DEFAULT_LINE_HEIGHT[fontFamily];
|
|
return DEFAULT_LINE_HEIGHT[fontFamily];
|