mirror of
				https://github.com/excalidraw/excalidraw.git
				synced 2025-10-31 02:44:50 +01:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			v0.18.0
			...
			feature/do
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | f16967fc52 | ||
|   | 8a06b70588 | ||
|   | c31cbe646d | 
| @@ -49,7 +49,7 @@ export const actionUnbindText = register({ | ||||
|     selectedElements.forEach((element) => { | ||||
|       const boundTextElement = getBoundTextElement(element, elementsMap); | ||||
|       if (boundTextElement) { | ||||
|         const { width, height, baseline } = measureText( | ||||
|         const { width, height } = measureText( | ||||
|           boundTextElement.originalText, | ||||
|           getFontString(boundTextElement), | ||||
|           boundTextElement.lineHeight, | ||||
| @@ -63,7 +63,6 @@ export const actionUnbindText = register({ | ||||
|           containerId: null, | ||||
|           width, | ||||
|           height, | ||||
|           baseline, | ||||
|           text: boundTextElement.originalText, | ||||
|           x, | ||||
|           y, | ||||
|   | ||||
| @@ -219,7 +219,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": [ | ||||
|     { | ||||
|       "id": "id48", | ||||
| @@ -263,7 +262,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": [ | ||||
|     { | ||||
|       "id": "id48", | ||||
| @@ -365,7 +363,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": null, | ||||
|   "containerId": "id48", | ||||
|   "fillStyle": "solid", | ||||
| @@ -462,7 +459,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": null, | ||||
|   "containerId": "id37", | ||||
|   "fillStyle": "solid", | ||||
| @@ -629,7 +625,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": null, | ||||
|   "containerId": "id41", | ||||
|   "fillStyle": "solid", | ||||
| @@ -668,7 +663,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": [ | ||||
|     { | ||||
|       "id": "id41", | ||||
| @@ -712,7 +706,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": [ | ||||
|     { | ||||
|       "id": "id41", | ||||
| @@ -1146,7 +1139,6 @@ exports[`Test Transform > should transform text element 1`] = ` | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": null, | ||||
|   "containerId": null, | ||||
|   "fillStyle": "solid", | ||||
| @@ -1185,7 +1177,6 @@ exports[`Test Transform > should transform text element 2`] = ` | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": null, | ||||
|   "containerId": null, | ||||
|   "fillStyle": "solid", | ||||
| @@ -1424,7 +1415,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": null, | ||||
|   "containerId": "id25", | ||||
|   "fillStyle": "solid", | ||||
| @@ -1463,7 +1453,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": null, | ||||
|   "containerId": "id26", | ||||
|   "fillStyle": "solid", | ||||
| @@ -1502,7 +1491,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": null, | ||||
|   "containerId": "id27", | ||||
|   "fillStyle": "solid", | ||||
| @@ -1542,7 +1530,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": null, | ||||
|   "containerId": "id28", | ||||
|   "fillStyle": "solid", | ||||
| @@ -1792,7 +1779,6 @@ exports[`Test Transform > should transform to text containers when label provide | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": null, | ||||
|   "containerId": "id13", | ||||
|   "fillStyle": "solid", | ||||
| @@ -1831,7 +1817,6 @@ exports[`Test Transform > should transform to text containers when label provide | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": null, | ||||
|   "containerId": "id14", | ||||
|   "fillStyle": "solid", | ||||
| @@ -1871,7 +1856,6 @@ exports[`Test Transform > should transform to text containers when label provide | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": null, | ||||
|   "containerId": "id15", | ||||
|   "fillStyle": "solid", | ||||
| @@ -1913,7 +1897,6 @@ exports[`Test Transform > should transform to text containers when label provide | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": null, | ||||
|   "containerId": "id16", | ||||
|   "fillStyle": "solid", | ||||
| @@ -1953,7 +1936,6 @@ exports[`Test Transform > should transform to text containers when label provide | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": null, | ||||
|   "containerId": "id17", | ||||
|   "fillStyle": "solid", | ||||
| @@ -1994,7 +1976,6 @@ exports[`Test Transform > should transform to text containers when label provide | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": null, | ||||
|   "containerId": "id18", | ||||
|   "fillStyle": "solid", | ||||
|   | ||||
| @@ -35,14 +35,13 @@ import { | ||||
| import { getDefaultAppState } from "../appState"; | ||||
| import { LinearElementEditor } from "../element/linearElementEditor"; | ||||
| import { bumpVersion } from "../element/mutateElement"; | ||||
| import { getFontString, getUpdatedTimestamp, updateActiveTool } from "../utils"; | ||||
| import { getUpdatedTimestamp, updateActiveTool } from "../utils"; | ||||
| import { arrayToMap } from "../utils"; | ||||
| import { MarkOptional, Mutable } from "../utility-types"; | ||||
| import { | ||||
|   detectLineHeight, | ||||
|   getContainerElement, | ||||
|   getDefaultLineHeight, | ||||
|   measureBaseline, | ||||
| } from "../element/textElement"; | ||||
| import { normalizeLink } from "./url"; | ||||
|  | ||||
| @@ -207,11 +206,6 @@ const restoreElement = ( | ||||
|           : // no element height likely means programmatic use, so default | ||||
|             // to a fixed line height | ||||
|             getDefaultLineHeight(element.fontFamily)); | ||||
|       const baseline = measureBaseline( | ||||
|         element.text, | ||||
|         getFontString(element), | ||||
|         lineHeight, | ||||
|       ); | ||||
|       element = restoreElementWithProperties(element, { | ||||
|         fontSize, | ||||
|         fontFamily, | ||||
| @@ -222,7 +216,6 @@ const restoreElement = ( | ||||
|         originalText: element.originalText || text, | ||||
|  | ||||
|         lineHeight, | ||||
|         baseline, | ||||
|       }); | ||||
|  | ||||
|       // if empty text, mark as deleted. We keep in array | ||||
|   | ||||
| @@ -246,7 +246,6 @@ export const newTextElement = ( | ||||
|       y: opts.y - offsets.y, | ||||
|       width: metrics.width, | ||||
|       height: metrics.height, | ||||
|       baseline: metrics.baseline, | ||||
|       containerId: opts.containerId || null, | ||||
|       originalText: text, | ||||
|       lineHeight, | ||||
| @@ -264,13 +263,12 @@ const getAdjustedDimensions = ( | ||||
|   y: number; | ||||
|   width: number; | ||||
|   height: number; | ||||
|   baseline: number; | ||||
| } => { | ||||
|   const { | ||||
|     width: nextWidth, | ||||
|     height: nextHeight, | ||||
|     baseline: nextBaseline, | ||||
|   } = measureText(nextText, getFontString(element), element.lineHeight); | ||||
|   const { width: nextWidth, height: nextHeight } = measureText( | ||||
|     nextText, | ||||
|     getFontString(element), | ||||
|     element.lineHeight, | ||||
|   ); | ||||
|   const { textAlign, verticalAlign } = element; | ||||
|   let x: number; | ||||
|   let y: number; | ||||
| @@ -324,7 +322,6 @@ const getAdjustedDimensions = ( | ||||
|   return { | ||||
|     width: nextWidth, | ||||
|     height: nextHeight, | ||||
|     baseline: nextBaseline, | ||||
|     x: Number.isFinite(x) ? x : element.x, | ||||
|     y: Number.isFinite(y) ? y : element.y, | ||||
|   }; | ||||
|   | ||||
| @@ -52,8 +52,6 @@ import { | ||||
|   handleBindTextResize, | ||||
|   getBoundTextMaxWidth, | ||||
|   getApproxMinLineHeight, | ||||
|   measureText, | ||||
|   getBoundTextMaxHeight, | ||||
| } from "./textElement"; | ||||
| import { LinearElementEditor } from "./linearElementEditor"; | ||||
|  | ||||
| @@ -211,8 +209,7 @@ const measureFontSizeFromWidth = ( | ||||
|   element: NonDeleted<ExcalidrawTextElement>, | ||||
|   elementsMap: ElementsMap, | ||||
|   nextWidth: number, | ||||
|   nextHeight: number, | ||||
| ): { size: number; baseline: number } | null => { | ||||
| ): { size: number } | null => { | ||||
|   // We only use width to scale font on resize | ||||
|   let width = element.width; | ||||
|  | ||||
| @@ -227,14 +224,9 @@ const measureFontSizeFromWidth = ( | ||||
|   if (nextFontSize < MIN_FONT_SIZE) { | ||||
|     return null; | ||||
|   } | ||||
|   const metrics = measureText( | ||||
|     element.text, | ||||
|     getFontString({ fontSize: nextFontSize, fontFamily: element.fontFamily }), | ||||
|     element.lineHeight, | ||||
|   ); | ||||
|  | ||||
|   return { | ||||
|     size: nextFontSize, | ||||
|     baseline: metrics.baseline + (nextHeight - metrics.height), | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @@ -307,12 +299,7 @@ const resizeSingleTextElement = ( | ||||
|   if (scale > 0) { | ||||
|     const nextWidth = element.width * scale; | ||||
|     const nextHeight = element.height * scale; | ||||
|     const metrics = measureFontSizeFromWidth( | ||||
|       element, | ||||
|       elementsMap, | ||||
|       nextWidth, | ||||
|       nextHeight, | ||||
|     ); | ||||
|     const metrics = measureFontSizeFromWidth(element, elementsMap, nextWidth); | ||||
|     if (metrics === null) { | ||||
|       return; | ||||
|     } | ||||
| @@ -340,7 +327,6 @@ const resizeSingleTextElement = ( | ||||
|       fontSize: metrics.size, | ||||
|       width: nextWidth, | ||||
|       height: nextHeight, | ||||
|       baseline: metrics.baseline, | ||||
|       x: nextElementX, | ||||
|       y: nextElementY, | ||||
|     }); | ||||
| @@ -394,7 +380,7 @@ export const resizeSingleElement = ( | ||||
|   let scaleX = atStartBoundsWidth / boundsCurrentWidth; | ||||
|   let scaleY = atStartBoundsHeight / boundsCurrentHeight; | ||||
|  | ||||
|   let boundTextFont: { fontSize?: number; baseline?: number } = {}; | ||||
|   let boundTextFont: { fontSize?: number } = {}; | ||||
|   const boundTextElement = getBoundTextElement(element, elementsMap); | ||||
|  | ||||
|   if (transformHandleDirection.includes("e")) { | ||||
| @@ -446,7 +432,6 @@ export const resizeSingleElement = ( | ||||
|     if (stateOfBoundTextElementAtResize) { | ||||
|       boundTextFont = { | ||||
|         fontSize: stateOfBoundTextElementAtResize.fontSize, | ||||
|         baseline: stateOfBoundTextElementAtResize.baseline, | ||||
|       }; | ||||
|     } | ||||
|     if (shouldMaintainAspectRatio) { | ||||
| @@ -460,14 +445,12 @@ export const resizeSingleElement = ( | ||||
|         boundTextElement, | ||||
|         elementsMap, | ||||
|         getBoundTextMaxWidth(updatedElement, boundTextElement), | ||||
|         getBoundTextMaxHeight(updatedElement, boundTextElement), | ||||
|       ); | ||||
|       if (nextFont === null) { | ||||
|         return; | ||||
|       } | ||||
|       boundTextFont = { | ||||
|         fontSize: nextFont.size, | ||||
|         baseline: nextFont.baseline, | ||||
|       }; | ||||
|     } else { | ||||
|       const minWidth = getApproxMinLineWidth( | ||||
| @@ -636,7 +619,6 @@ export const resizeSingleElement = ( | ||||
|     if (boundTextElement && boundTextFont != null) { | ||||
|       mutateElement(boundTextElement, { | ||||
|         fontSize: boundTextFont.fontSize, | ||||
|         baseline: boundTextFont.baseline, | ||||
|       }); | ||||
|     } | ||||
|     handleBindTextResize( | ||||
| @@ -763,7 +745,6 @@ export const resizeMultipleElements = ( | ||||
|     > & { | ||||
|       points?: ExcalidrawLinearElement["points"]; | ||||
|       fontSize?: ExcalidrawTextElement["fontSize"]; | ||||
|       baseline?: ExcalidrawTextElement["baseline"]; | ||||
|       scale?: ExcalidrawImageElement["scale"]; | ||||
|       boundTextFontSize?: ExcalidrawTextElement["fontSize"]; | ||||
|     }; | ||||
| @@ -838,17 +819,11 @@ export const resizeMultipleElements = ( | ||||
|     } | ||||
|  | ||||
|     if (isTextElement(orig)) { | ||||
|       const metrics = measureFontSizeFromWidth( | ||||
|         orig, | ||||
|         elementsMap, | ||||
|         width, | ||||
|         height, | ||||
|       ); | ||||
|       const metrics = measureFontSizeFromWidth(orig, elementsMap, width); | ||||
|       if (!metrics) { | ||||
|         return; | ||||
|       } | ||||
|       update.fontSize = metrics.size; | ||||
|       update.baseline = metrics.baseline; | ||||
|     } | ||||
|  | ||||
|     const boundTextElement = originalElements.get( | ||||
|   | ||||
| @@ -18,7 +18,6 @@ import { | ||||
|   DEFAULT_FONT_FAMILY, | ||||
|   DEFAULT_FONT_SIZE, | ||||
|   FONT_FAMILY, | ||||
|   isSafari, | ||||
|   TEXT_ALIGN, | ||||
|   VERTICAL_ALIGN, | ||||
| } from "../constants"; | ||||
| @@ -61,7 +60,6 @@ export const redrawTextBoundingBox = ( | ||||
|     text: textElement.text, | ||||
|     width: textElement.width, | ||||
|     height: textElement.height, | ||||
|     baseline: textElement.baseline, | ||||
|   }; | ||||
|  | ||||
|   boundTextUpdates.text = textElement.text; | ||||
| @@ -82,7 +80,6 @@ export const redrawTextBoundingBox = ( | ||||
|  | ||||
|   boundTextUpdates.width = metrics.width; | ||||
|   boundTextUpdates.height = metrics.height; | ||||
|   boundTextUpdates.baseline = metrics.baseline; | ||||
|  | ||||
|   if (container) { | ||||
|     const maxContainerHeight = getBoundTextMaxHeight( | ||||
| @@ -183,7 +180,6 @@ export const handleBindTextResize = ( | ||||
|     const maxWidth = getBoundTextMaxWidth(container, textElement); | ||||
|     const maxHeight = getBoundTextMaxHeight(container, textElement); | ||||
|     let containerHeight = container.height; | ||||
|     let nextBaseLine = textElement.baseline; | ||||
|     if ( | ||||
|       shouldMaintainAspectRatio || | ||||
|       (transformHandleType !== "n" && transformHandleType !== "s") | ||||
| @@ -202,7 +198,6 @@ export const handleBindTextResize = ( | ||||
|       ); | ||||
|       nextHeight = metrics.height; | ||||
|       nextWidth = metrics.width; | ||||
|       nextBaseLine = metrics.baseline; | ||||
|     } | ||||
|     // increase height in case text element height exceeds | ||||
|     if (nextHeight > maxHeight) { | ||||
| @@ -230,7 +225,6 @@ export const handleBindTextResize = ( | ||||
|       text, | ||||
|       width: nextWidth, | ||||
|       height: nextHeight, | ||||
|       baseline: nextBaseLine, | ||||
|     }); | ||||
|  | ||||
|     if (!isArrowElement(container)) { | ||||
| @@ -294,59 +288,7 @@ export const measureText = ( | ||||
|   const fontSize = parseFloat(font); | ||||
|   const height = getTextHeight(text, fontSize, lineHeight); | ||||
|   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 }; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -176,7 +176,6 @@ export type ExcalidrawTextElement = _ExcalidrawElementBase & | ||||
|     fontSize: number; | ||||
|     fontFamily: FontFamilyValues; | ||||
|     text: string; | ||||
|     baseline: number; | ||||
|     textAlign: TextAlign; | ||||
|     verticalAlign: VerticalAlign; | ||||
|     containerId: ExcalidrawGenericElement["id"] | null; | ||||
|   | ||||
| @@ -395,12 +395,24 @@ const drawElementOnCanvas = ( | ||||
|           element.fontSize, | ||||
|           element.lineHeight, | ||||
|         ); | ||||
|         const verticalOffset = element.height - element.baseline; | ||||
|  | ||||
|         const metrics = context.measureText(element.text); | ||||
|         const lineGap = | ||||
|           lineHeightPx - | ||||
|           (metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent); | ||||
|         /** | ||||
|          * Set a vertical offset to be aligned with <textarea>. | ||||
|          * - `fontBoundingBoxAscent` is here the font bouding box with its default line-height used in textareas | ||||
|          * - half of the line gap is the additional padding above and below the bounding box when line-height isn't equal to the default value | ||||
|          * - for details check - https://codesandbox.io/p/devbox/v4nsqz?file=%2Fsrc%2Findex.js%3A1%2C1-166%2C1 | ||||
|          */ | ||||
|         context.translate(0, metrics.fontBoundingBoxAscent + lineGap / 2); | ||||
|  | ||||
|         for (let index = 0; index < lines.length; index++) { | ||||
|           context.fillText( | ||||
|             lines[index], | ||||
|             horizontalOffset, | ||||
|             (index + 1) * lineHeightPx - verticalOffset, | ||||
|             index * lineHeightPx, | ||||
|           ); | ||||
|         } | ||||
|         context.restore(); | ||||
|   | ||||
| @@ -289,7 +289,6 @@ exports[`restoreElements > should restore text element correctly passing value f | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": [], | ||||
|   "containerId": null, | ||||
|   "fillStyle": "solid", | ||||
| @@ -330,7 +329,6 @@ exports[`restoreElements > should restore text element correctly with unknown fo | ||||
| { | ||||
|   "angle": 0, | ||||
|   "backgroundColor": "transparent", | ||||
|   "baseline": 0, | ||||
|   "boundElements": [], | ||||
|   "containerId": null, | ||||
|   "fillStyle": "solid", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user