@nasser-sw/fabric 7.0.1-beta16 → 7.0.1-beta17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +7 -0
- package/dist/index.js +1982 -649
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.min.mjs +1 -1
- package/dist/index.min.mjs.map +1 -1
- package/dist/index.mjs +1982 -649
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +1982 -649
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +1982 -649
- package/dist/index.node.mjs.map +1 -1
- package/dist/package.json.min.mjs +1 -1
- package/dist/package.json.mjs +1 -1
- package/dist/src/shapes/IText/IText.d.ts +31 -6
- package/dist/src/shapes/IText/IText.d.ts.map +1 -1
- package/dist/src/shapes/IText/IText.min.mjs +1 -1
- package/dist/src/shapes/IText/IText.min.mjs.map +1 -1
- package/dist/src/shapes/IText/IText.mjs +495 -126
- package/dist/src/shapes/IText/IText.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextBehavior.d.ts +12 -0
- package/dist/src/shapes/IText/ITextBehavior.d.ts.map +1 -1
- package/dist/src/shapes/IText/ITextBehavior.min.mjs +1 -1
- package/dist/src/shapes/IText/ITextBehavior.min.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextBehavior.mjs +127 -36
- package/dist/src/shapes/IText/ITextBehavior.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.d.ts.map +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.min.mjs +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.min.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.mjs +21 -4
- package/dist/src/shapes/IText/ITextClickBehavior.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextKeyBehavior.min.mjs +1 -1
- package/dist/src/shapes/IText/ITextKeyBehavior.min.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextKeyBehavior.mjs +17 -21
- package/dist/src/shapes/IText/ITextKeyBehavior.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.d.ts +69 -1
- package/dist/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist/src/shapes/Text/Text.min.mjs +1 -1
- package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.mjs +374 -60
- package/dist/src/shapes/Text/Text.mjs.map +1 -1
- package/dist/src/shapes/Text/constants.d.ts.map +1 -1
- package/dist/src/shapes/Text/constants.min.mjs +1 -1
- package/dist/src/shapes/Text/constants.min.mjs.map +1 -1
- package/dist/src/shapes/Text/constants.mjs +2 -1
- package/dist/src/shapes/Text/constants.mjs.map +1 -1
- package/dist/src/shapes/Textbox.d.ts +8 -1
- package/dist/src/shapes/Textbox.d.ts.map +1 -1
- package/dist/src/shapes/Textbox.min.mjs +1 -1
- package/dist/src/shapes/Textbox.min.mjs.map +1 -1
- package/dist/src/shapes/Textbox.mjs +406 -63
- package/dist/src/shapes/Textbox.mjs.map +1 -1
- package/dist/src/text/hitTest.min.mjs +1 -1
- package/dist/src/text/hitTest.min.mjs.map +1 -1
- package/dist/src/text/hitTest.mjs +1 -198
- package/dist/src/text/hitTest.mjs.map +1 -1
- package/dist/src/text/layout.min.mjs +1 -1
- package/dist/src/text/layout.min.mjs.map +1 -1
- package/dist/src/text/layout.mjs +122 -5
- package/dist/src/text/layout.mjs.map +1 -1
- package/dist/src/text/overlayEditor.min.mjs +1 -1
- package/dist/src/text/overlayEditor.min.mjs.map +1 -1
- package/dist/src/text/overlayEditor.mjs +132 -142
- package/dist/src/text/overlayEditor.mjs.map +1 -1
- package/dist/src/text/unicode.d.ts +28 -0
- package/dist/src/text/unicode.d.ts.map +1 -1
- package/dist/src/text/unicode.min.mjs +1 -1
- package/dist/src/text/unicode.min.mjs.map +1 -1
- package/dist/src/text/unicode.mjs +294 -1
- package/dist/src/text/unicode.mjs.map +1 -1
- package/dist-extensions/src/shapes/IText/IText.d.ts +31 -6
- package/dist-extensions/src/shapes/IText/IText.d.ts.map +1 -1
- package/dist-extensions/src/shapes/IText/ITextBehavior.d.ts +12 -0
- package/dist-extensions/src/shapes/IText/ITextBehavior.d.ts.map +1 -1
- package/dist-extensions/src/shapes/IText/ITextClickBehavior.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/Text.d.ts +69 -1
- package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/constants.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Textbox.d.ts +8 -1
- package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
- package/dist-extensions/src/text/unicode.d.ts +28 -0
- package/dist-extensions/src/text/unicode.d.ts.map +1 -1
- package/package.json +164 -164
- package/rtl-debug.html +358 -200
- package/src/shapes/IText/IText.ts +524 -110
- package/src/shapes/IText/ITextBehavior.ts +174 -80
- package/src/shapes/IText/ITextClickBehavior.ts +20 -6
- package/src/shapes/IText/ITextKeyBehavior.ts +15 -15
- package/src/shapes/Text/Text.ts +488 -107
- package/src/shapes/Text/constants.ts +4 -2
- package/src/shapes/Textbox.ts +414 -65
- package/src/text/layout.ts +150 -23
- package/src/text/overlayEditor.ts +148 -148
- package/src/text/unicode.ts +177 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Text.min.mjs","sources":["../../../../src/shapes/Text/Text.ts"],"sourcesContent":["import { cache } from '../../cache';\r\nimport { DEFAULT_SVG_FONT_SIZE, FILL, STROKE } from '../../constants';\r\nimport type { ObjectEvents } from '../../EventTypeDefs';\r\nimport type {\r\n CompleteTextStyleDeclaration,\r\n TextStyle,\r\n TextStyleDeclaration,\r\n} from './StyledText';\r\nimport { StyledText } from './StyledText';\r\nimport { SHARED_ATTRIBUTES } from '../../parser/attributes';\r\nimport { parseAttributes } from '../../parser/parseAttributes';\r\nimport type {\r\n Abortable,\r\n TCacheCanvasDimensions,\r\n TClassProperties,\r\n TFiller,\r\n TOptions,\r\n} from '../../typedefs';\r\nimport { classRegistry } from '../../ClassRegistry';\r\nimport { graphemeSplit } from '../../util/lang_string';\r\nimport { createCanvasElementFor } from '../../util/misc/dom';\r\nimport { layoutText, type LayoutResult, type TextLayoutOptions } from '../../text/layout';\r\nimport { measureGrapheme, measureGraphemeWithKerning } from '../../text/measure';\r\nimport { applyEllipsis } from '../../text/ellipsis';\r\nimport { segmentGraphemes } from '../../text/unicode';\r\nimport type { TextStyleArray } from '../../util/misc/textStyles';\r\nimport {\r\n hasStyleChanged,\r\n stylesFromArray,\r\n stylesToArray,\r\n} from '../../util/misc/textStyles';\r\nimport { getPathSegmentsInfo, getPointOnPath } from '../../util/path';\r\nimport { cacheProperties } from '../Object/FabricObject';\r\nimport type { Path } from '../Path';\r\nimport { TextSVGExportMixin } from './TextSVGExportMixin';\r\nimport { applyMixins } from '../../util/applyMixins';\r\nimport type { FabricObjectProps, SerializedObjectProps } from '../Object/types';\r\nimport type { StylePropertiesType } from './constants';\r\nimport {\r\n additionalProps,\r\n textDefaultValues,\r\n textLayoutProperties,\r\n JUSTIFY,\r\n JUSTIFY_CENTER,\r\n JUSTIFY_LEFT,\r\n JUSTIFY_RIGHT,\r\n TEXT_DECORATION_THICKNESS,\r\n} from './constants';\r\nimport { CENTER, LEFT, RIGHT, TOP, BOTTOM } from '../../constants';\r\nimport { isFiller } from '../../util/typeAssertions';\r\nimport type { Gradient } from '../../gradient/Gradient';\r\nimport type { Pattern } from '../../Pattern';\r\nimport type { CSSRules } from '../../parser/typedefs';\r\nimport { getBrowserLines, clearBrowserLines } from '../../text/browserLines';\r\nimport type { BrowserLine } from '../../text/browserLines';\r\n\r\nlet measuringContext: CanvasRenderingContext2D | null;\r\n\r\n/**\r\n * Return a context for measurement of text string.\r\n * if created it gets stored for reuse\r\n */\r\nfunction getMeasuringContext() {\r\n if (!measuringContext) {\r\n const canvas = createCanvasElementFor({\r\n width: 0,\r\n height: 0,\r\n });\r\n measuringContext = canvas.getContext('2d');\r\n }\r\n return measuringContext;\r\n}\r\n\r\nexport type TPathSide = 'left' | 'right';\r\n\r\nexport type TPathAlign = 'baseline' | 'center' | 'ascender' | 'descender';\r\n\r\nexport type TextLinesInfo = {\r\n lines: string[];\r\n graphemeLines: string[][];\r\n graphemeText: string[];\r\n _unwrappedLines: string[][];\r\n};\r\n\r\n/**\r\n * Measure and return the info of a single grapheme.\r\n * needs the the info of previous graphemes already filled\r\n * Override to customize measuring\r\n */\r\nexport type GraphemeBBox = {\r\n width: number;\r\n height: number;\r\n kernedWidth: number;\r\n left: number;\r\n deltaY: number;\r\n renderLeft?: number;\r\n renderTop?: number;\r\n angle?: number;\r\n};\r\n\r\n// @TODO this is not complete\r\ninterface UniqueTextProps {\r\n charSpacing: number;\r\n lineHeight: number;\r\n fontSize: number;\r\n fontWeight: string | number;\r\n fontFamily: string;\r\n fontStyle: string;\r\n pathSide: TPathSide;\r\n pathAlign: TPathAlign;\r\n underline: boolean;\r\n overline: boolean;\r\n linethrough: boolean;\r\n textAlign: string;\r\n direction: CanvasDirection;\r\n path?: Path;\r\n textDecorationThickness: number;\r\n wrap: 'word' | 'char' | 'none';\r\n ellipsis: boolean | string;\r\n letterSpacing: number;\r\n enableAdvancedLayout: boolean;\r\n verticalAlign: 'top' | 'middle' | 'bottom';\r\n useOverlayEditing: boolean;\r\n}\r\n\r\nexport interface SerializedTextProps\r\n extends SerializedObjectProps,\r\n UniqueTextProps {\r\n styles: TextStyleArray | TextStyle;\r\n}\r\n\r\nexport interface TextProps extends FabricObjectProps, UniqueTextProps {\r\n styles: TextStyle;\r\n}\r\n\r\n/**\r\n * Text class\r\n * @see {@link http://fabricjs.com/fabric-intro-part-2#text}\r\n */\r\nexport class FabricText<\r\n Props extends TOptions<TextProps> = Partial<TextProps>,\r\n SProps extends SerializedTextProps = SerializedTextProps,\r\n EventSpec extends ObjectEvents = ObjectEvents,\r\n >\r\n extends StyledText<Props, SProps, EventSpec>\r\n implements UniqueTextProps\r\n{\r\n /**\r\n * Properties that requires a text layout recalculation when changed\r\n * @type string[]\r\n * @protected\r\n */\r\n static textLayoutProperties: string[] = textLayoutProperties;\r\n\r\n /**\r\n * @private\r\n */\r\n declare _reNewline: RegExp;\r\n\r\n /**\r\n * Use this regular expression to filter for whitespaces that is not a new line.\r\n * Mostly used when text is 'justify' aligned.\r\n * @private\r\n */\r\n declare _reSpacesAndTabs: RegExp;\r\n\r\n /**\r\n * Use this regular expression to filter for whitespace that is not a new line.\r\n * Mostly used when text is 'justify' aligned.\r\n * @private\r\n */\r\n declare _reSpaceAndTab: RegExp;\r\n\r\n /**\r\n * Use this regular expression to filter consecutive groups of non spaces.\r\n * Mostly used when text is 'justify' aligned.\r\n * @private\r\n */\r\n declare _reWords: RegExp;\r\n\r\n declare text: string;\r\n\r\n /**\r\n * Font size (in pixels)\r\n * @type Number\r\n */\r\n declare fontSize: number;\r\n\r\n /**\r\n * Font weight (e.g. bold, normal, 400, 600, 800)\r\n * @type {(Number|String)}\r\n */\r\n declare fontWeight: string | number;\r\n\r\n /**\r\n * Font family\r\n * @type String\r\n */\r\n declare fontFamily: string;\r\n\r\n /**\r\n * Text decoration underline.\r\n * @type Boolean\r\n */\r\n declare underline: boolean;\r\n\r\n /**\r\n * Text decoration overline.\r\n * @type Boolean\r\n */\r\n declare overline: boolean;\r\n\r\n /**\r\n * Text decoration linethrough.\r\n * @type Boolean\r\n */\r\n declare linethrough: boolean;\r\n\r\n /**\r\n * Text alignment. Possible values: \"left\", \"center\", \"right\", \"justify\",\r\n * \"justify-left\", \"justify-center\" or \"justify-right\".\r\n * @type String\r\n */\r\n declare textAlign: string;\r\n\r\n /**\r\n * Font style . Possible values: \"\", \"normal\", \"italic\" or \"oblique\".\r\n * @type String\r\n */\r\n declare fontStyle: string;\r\n\r\n /**\r\n * Line height\r\n * @type Number\r\n */\r\n declare lineHeight: number;\r\n\r\n /**\r\n * Superscript schema object (minimum overlap)\r\n */\r\n declare superscript: {\r\n /**\r\n * fontSize factor\r\n * @default 0.6\r\n */\r\n size: number;\r\n /**\r\n * baseline-shift factor (upwards)\r\n * @default -0.35\r\n */\r\n baseline: number;\r\n };\r\n\r\n /**\r\n * Subscript schema object (minimum overlap)\r\n */\r\n declare subscript: {\r\n /**\r\n * fontSize factor\r\n * @default 0.6\r\n */\r\n size: number;\r\n /**\r\n * baseline-shift factor (downwards)\r\n * @default 0.11\r\n */\r\n baseline: number;\r\n };\r\n\r\n /**\r\n * Background color of text lines\r\n * @type String\r\n */\r\n declare textBackgroundColor: string;\r\n\r\n declare styles: TextStyle;\r\n\r\n /**\r\n * Path that the text should follow.\r\n * since 4.6.0 the path will be drawn automatically.\r\n * if you want to make the path visible, give it a stroke and strokeWidth or fill value\r\n * if you want it to be hidden, assign visible = false to the path.\r\n * This feature is in BETA, and SVG import/export is not yet supported.\r\n * @type Path\r\n * @example\r\n * const textPath = new Text('Text on a path', {\r\n * top: 150,\r\n * left: 150,\r\n * textAlign: 'center',\r\n * charSpacing: -50,\r\n * path: new Path('M 0 0 C 50 -100 150 -100 200 0', {\r\n * strokeWidth: 1,\r\n * visible: false\r\n * }),\r\n * pathSide: 'left',\r\n * pathStartOffset: 0\r\n * });\r\n */\r\n declare path?: Path;\r\n\r\n /**\r\n * Text wrapping mode\r\n * @type string\r\n * @default 'word'\r\n */\r\n declare wrap: 'word' | 'char' | 'none';\r\n\r\n /**\r\n * Ellipsis truncation\r\n * @type boolean | string\r\n * @default false\r\n */\r\n declare ellipsis: boolean | string;\r\n\r\n /**\r\n * Letter spacing in pixels (Konva-style)\r\n * @type number\r\n * @default 0\r\n */\r\n declare letterSpacing: number;\r\n\r\n /**\r\n * Enable advanced text layout engine\r\n * @type boolean\r\n * @default false\r\n */\r\n declare enableAdvancedLayout: boolean;\r\n\r\n /**\r\n * Vertical text alignment\r\n * @type string\r\n * @default 'top'\r\n */\r\n declare verticalAlign: 'top' | 'middle' | 'bottom';\r\n\r\n /**\r\n * Use overlay editor for inline text editing instead of hidden textarea.\r\n * @default false\r\n */\r\n declare useOverlayEditing: boolean;\r\n\r\n /**\r\n * The text decoration tickness for underline, overline and strikethrough\r\n * The tickness is expressed in thousandths of fontSize ( em ).\r\n * The original value was 1/15 that translates to 66.6667 thousandths.\r\n * The choice of unit of measure is to align with charSpacing.\r\n * You can slim the tickness without issues, while large underline or overline may end up\r\n * outside the bounding box of the text. In order to fix that a bigger refactor of the code\r\n * is needed and is out of scope for now. If you need such large overline on the first line\r\n * of text or large underline on the last line of text, consider disabling caching as a\r\n * workaround\r\n * @default 66.667\r\n */\r\n declare textDecorationThickness: number;\r\n\r\n /**\r\n * Offset amount for text path starting position\r\n * Only used when text has a path\r\n */\r\n declare pathStartOffset: number;\r\n\r\n /**\r\n * Which side of the path the text should be drawn on.\r\n * Only used when text has a path\r\n * @type {TPathSide} 'left|right'\r\n */\r\n declare pathSide: TPathSide;\r\n\r\n /**\r\n * How text is aligned to the path. This property determines\r\n * the perpendicular position of each character relative to the path.\r\n * (one of \"baseline\", \"center\", \"ascender\", \"descender\")\r\n * This feature is in BETA, and its behavior may change\r\n * @type TPathAlign\r\n */\r\n declare pathAlign: TPathAlign;\r\n\r\n /**\r\n * @private\r\n */\r\n declare _fontSizeFraction: number;\r\n\r\n /**\r\n * @private\r\n */\r\n declare offsets: { underline: number; linethrough: number; overline: number };\r\n\r\n /**\r\n * Text Line proportion to font Size (in pixels)\r\n * @type Number\r\n */\r\n declare _fontSizeMult: number;\r\n\r\n /**\r\n * additional space between characters\r\n * expressed in thousands of em unit\r\n * @type Number\r\n */\r\n declare charSpacing: number;\r\n\r\n /**\r\n * Baseline shift, styles only, keep at 0 for the main text object\r\n * @type {Number}\r\n */\r\n declare deltaY: number;\r\n\r\n /**\r\n * WARNING: EXPERIMENTAL. NOT SUPPORTED YET\r\n * determine the direction of the text.\r\n * This has to be set manually together with textAlign and originX for proper\r\n * experience.\r\n * some interesting link for the future\r\n * https://www.w3.org/International/questions/qa-bidi-unicode-controls\r\n * @since 4.5.0\r\n * @type {CanvasDirection} 'ltr|rtl'\r\n */\r\n declare direction: CanvasDirection;\r\n\r\n /**\r\n * contains characters bounding boxes\r\n * This variable is considered to be protected.\r\n * But for how mixins are implemented right now, we can't leave it private\r\n * @protected\r\n */\r\n __charBounds: GraphemeBBox[][] = [];\r\n\r\n /**\r\n * use this size when measuring text. To avoid IE11 rounding errors\r\n * @type {Number}\r\n * @readonly\r\n * @private\r\n */\r\n declare CACHE_FONT_SIZE: number;\r\n\r\n /**\r\n * contains the min text width to avoid getting 0\r\n * @type {Number}\r\n */\r\n declare MIN_TEXT_WIDTH: number;\r\n\r\n /**\r\n * contains the the text of the object, divided in lines as they are displayed\r\n * on screen. Wrapping will divide the text independently of line breaks\r\n * @type {string[]}\r\n */\r\n declare textLines: string[];\r\n\r\n /**\r\n * same as textlines, but each line is an array of graphemes as split by splitByGrapheme\r\n * @type {string[]}\r\n */\r\n declare _textLines: string[][];\r\n\r\n declare _unwrappedTextLines: string[][];\r\n declare _text: string[];\r\n declare cursorWidth: number;\r\n declare __lineHeights: number[];\r\n declare __lineWidths: number[];\r\n declare initialized?: true;\r\n\r\n static cacheProperties = [...cacheProperties, ...additionalProps];\r\n\r\n static ownDefaults = textDefaultValues;\r\n\r\n static type = 'Text';\r\n\r\n static getDefaults(): Record<string, any> {\r\n return { ...super.getDefaults(), ...FabricText.ownDefaults };\r\n }\r\n\r\n constructor(text: string, options?: Props) {\r\n super();\r\n Object.assign(this, FabricText.ownDefaults);\r\n this.setOptions(options);\r\n if (!this.styles) {\r\n this.styles = {};\r\n }\r\n this.text = text;\r\n this.initialized = true;\r\n if (this.path) {\r\n this.setPathInfo();\r\n }\r\n this.initDimensions();\r\n this.setCoords();\r\n }\r\n\r\n /**\r\n * If text has a path, it will add the extra information needed\r\n * for path and text calculations\r\n */\r\n setPathInfo() {\r\n const path = this.path;\r\n if (path) {\r\n path.segmentsInfo = getPathSegmentsInfo(path.path);\r\n }\r\n }\r\n\r\n /**\r\n * @private\r\n * Divides text into lines of text and lines of graphemes.\r\n * Uses browser lines when available for pixel-perfect consistency.\r\n */\r\n _splitText(): TextLinesInfo {\r\n // Check if we have valid browser lines and should use them\r\n const browserLines = getBrowserLines(this);\r\n if (browserLines && this.useOverlayEditing) {\r\n return this._splitTextFromBrowserLines(browserLines);\r\n }\r\n \r\n const newLines = this._splitTextIntoLines(this.text);\r\n this.textLines = newLines.lines;\r\n this._textLines = newLines.graphemeLines;\r\n this._unwrappedTextLines = newLines._unwrappedLines;\r\n this._text = newLines.graphemeText;\r\n return newLines;\r\n }\r\n\r\n /**\r\n * Create TextLinesInfo from browser-extracted lines\r\n * @private\r\n */\r\n _splitTextFromBrowserLines(browserLines: BrowserLine[]): TextLinesInfo {\r\n const lines: string[] = [];\r\n const graphemeLines: string[][] = [];\r\n const unwrappedLines: string[][] = [];\r\n let graphemeText: string[] = [];\r\n\r\n for (const browserLine of browserLines) {\r\n lines.push(browserLine.text);\r\n const lineGraphemes = this.graphemeSplit(browserLine.text);\r\n graphemeLines.push(lineGraphemes);\r\n unwrappedLines.push(lineGraphemes);\r\n graphemeText = graphemeText.concat(lineGraphemes);\r\n \r\n // Add newline separator between lines (except for the last line)\r\n if (browserLine !== browserLines[browserLines.length - 1]) {\r\n graphemeText.push('\\n');\r\n }\r\n }\r\n\r\n const result: TextLinesInfo = {\r\n lines,\r\n graphemeLines,\r\n graphemeText,\r\n _unwrappedLines: unwrappedLines,\r\n };\r\n\r\n // Update instance properties\r\n this.textLines = result.lines;\r\n this._textLines = result.graphemeLines;\r\n this._unwrappedTextLines = result._unwrappedLines;\r\n this._text = result.graphemeText;\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Initialize or update text dimensions.\r\n * Updates this.width and this.height with the proper values.\r\n * Does not return dimensions.\r\n */\r\n initDimensions(): void {\r\n // Check if font is ready for accurate measurements\r\n // Only block initialization if it's a critical font loading situation\r\n const fontReady = this._isFontReady();\r\n if (!fontReady && !this.initialized) {\r\n // Only schedule font loading on first initialization\r\n this._scheduleInitAfterFontLoad();\r\n // Continue with fallback measurements for now\r\n }\r\n \r\n // Use advanced layout if enabled\r\n if (this.enableAdvancedLayout && !this.path) {\r\n return this.initDimensionsAdvanced();\r\n }\r\n \r\n this._splitText();\r\n this._clearCache();\r\n this.dirty = true;\r\n if (this.path) {\r\n this.width = this.path.width;\r\n this.height = this.path.height;\r\n } else {\r\n this.width =\r\n this.calcTextWidth() || this.cursorWidth || this.MIN_TEXT_WIDTH;\r\n this.height = this.calcTextHeight();\r\n }\r\n if (this.textAlign.includes(JUSTIFY)) {\r\n // once text is measured we need to make space fatter to make justified text.\r\n if (this.__charBounds && this.__charBounds.length > 0) {\r\n this.enlargeSpaces();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Enlarge space boxes and shift the others for justify alignment\r\n */\r\n enlargeSpaces() {\r\n let diffSpace,\r\n currentLineWidth,\r\n numberOfSpaces,\r\n accumulatedSpace,\r\n line,\r\n charBound,\r\n spaces;\r\n\r\n for (let i = 0, len = this._textLines.length; i < len; i++) {\r\n // Check if this line should be justified\r\n const hasTextAfter = this._textLines\r\n .slice(i + 1)\r\n .some((line) => {\r\n const lineText = Array.isArray(line) ? line.join('') : line;\r\n return /\\S/.test(lineText);\r\n });\r\n const isVisualLastLine = !hasTextAfter;\r\n const isLastLine =\r\n i === len - 1 || this.isEndOfWrapping(i) || isVisualLastLine;\r\n const shouldJustifyLine =\r\n this.textAlign.includes('justify') && !isLastLine;\r\n\r\n if (!shouldJustifyLine) {\r\n continue;\r\n }\r\n\r\n accumulatedSpace = 0;\r\n line = this._textLines[i];\r\n currentLineWidth = this.getLineWidth(i);\r\n\r\n if (\r\n currentLineWidth < this.width &&\r\n (spaces = this.textLines[i].match(this._reSpacesAndTabs))\r\n ) {\r\n numberOfSpaces = spaces.length;\r\n diffSpace = (this.width - currentLineWidth) / numberOfSpaces;\r\n\r\n // Same logic for both LTR and RTL:\r\n // Expand space widths and shift subsequent characters\r\n // The rendering handles direction via ctx.direction\r\n for (let j = 0; j <= line.length; j++) {\r\n charBound = this.__charBounds[i][j];\r\n if (charBound) {\r\n if (this._reSpaceAndTab.test(line[j])) {\r\n charBound.width += diffSpace;\r\n charBound.kernedWidth += diffSpace;\r\n charBound.left += accumulatedSpace;\r\n accumulatedSpace += diffSpace;\r\n } else {\r\n charBound.left += accumulatedSpace;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Advanced layout using new text engine (Konva-compatible)\r\n * @private\r\n */\r\n _layoutTextAdvanced(): LayoutResult {\r\n const options = this._getAdvancedLayoutOptions();\r\n return layoutText(options);\r\n }\r\n\r\n /**\r\n * Get advanced layout options from current text properties\r\n * @private\r\n */\r\n _getAdvancedLayoutOptions(): TextLayoutOptions {\r\n return {\r\n text: this.text,\r\n width: this.width,\r\n height: this.height,\r\n wrap: this.wrap || 'word',\r\n align: this._mapTextAlignToAlign(this.textAlign),\r\n ellipsis: this.ellipsis || false,\r\n fontSize: this.fontSize,\r\n lineHeight: this.lineHeight,\r\n letterSpacing: this.letterSpacing || 0,\r\n charSpacing: this.charSpacing,\r\n direction: this.direction === 'inherit' ? 'ltr' : this.direction,\r\n fontFamily: this.fontFamily,\r\n fontStyle: this.fontStyle,\r\n fontWeight: this.fontWeight,\r\n verticalAlign: this.verticalAlign || 'top',\r\n };\r\n }\r\n\r\n /**\r\n * Map Fabric textAlign to Konva align format\r\n * @private\r\n */\r\n _mapTextAlignToAlign(textAlign: string): 'left' | 'center' | 'right' | 'justify' {\r\n switch (textAlign) {\r\n case 'center':\r\n case CENTER:\r\n return 'center';\r\n case 'right':\r\n case RIGHT:\r\n return 'right';\r\n case 'justify':\r\n case JUSTIFY:\r\n case JUSTIFY_LEFT:\r\n case JUSTIFY_RIGHT:\r\n case JUSTIFY_CENTER:\r\n return 'justify';\r\n default:\r\n return 'left';\r\n }\r\n }\r\n\r\n /**\r\n * Enhanced initDimensions that uses advanced layout when enabled\r\n */\r\n initDimensionsAdvanced(): void {\r\n if (!this.enableAdvancedLayout) {\r\n return this.initDimensions();\r\n }\r\n\r\n const layout = this._layoutTextAdvanced();\r\n \r\n // Update dimensions from layout\r\n this.width = layout.totalWidth || this.MIN_TEXT_WIDTH;\r\n this.height = layout.totalHeight;\r\n \r\n // Convert layout to legacy format for compatibility\r\n this._convertLayoutToLegacyFormat(layout);\r\n \r\n // Ensure justify alignment is properly applied for compatibility with legacy rendering\r\n // Skip legacy enlargeSpaces when using advanced layout; Konva layout already distributes spaces.\r\n \r\n this.dirty = true;\r\n }\r\n\r\n /**\r\n * Convert new layout format to legacy _textLines and __charBounds format\r\n * @private\r\n */\r\n _convertLayoutToLegacyFormat(layout: LayoutResult): void {\r\n this._textLines = layout.lines.map(line => line.graphemes);\r\n (this as any).textLines = layout.lines.map(line => line.text);\r\n \r\n // Convert bounds to legacy format\r\n this.__charBounds = layout.lines.map(line => \r\n line.bounds.map(bound => ({\r\n left: bound.left,\r\n top: bound.y,\r\n width: bound.width,\r\n height: bound.height,\r\n kernedWidth: bound.kernedWidth,\r\n deltaY: bound.deltaY || 0,\r\n }))\r\n );\r\n \r\n // Update grapheme info for compatibility\r\n if (layout.lines.length > 0) {\r\n (this as any)._unwrappedTextLines = layout.lines.map(line => line.graphemes);\r\n }\r\n }\r\n\r\n /**\r\n * Detect if the text line is ended with an hard break\r\n * text and itext do not have wrapping, return false\r\n * @return {Boolean}\r\n */\r\n isEndOfWrapping(lineIndex: number): boolean {\r\n return lineIndex === this._textLines.length - 1;\r\n }\r\n\r\n /**\r\n * Detect if a line has a linebreak and so we need to account for it when moving\r\n * and counting style.\r\n * It return always 1 for text and Itext. Textbox has its own implementation\r\n * @return Number\r\n */\r\n missingNewlineOffset(lineIndex: number, skipWrapping?: boolean): 0 | 1;\r\n missingNewlineOffset(_lineIndex: number): 1 {\r\n return 1;\r\n }\r\n\r\n /**\r\n * Returns 2d representation (lineIndex and charIndex) of cursor\r\n * @param {Number} selectionStart\r\n * @param {Boolean} [skipWrapping] consider the location for unwrapped lines. useful to manage styles.\r\n */\r\n get2DCursorLocation(selectionStart: number, skipWrapping?: boolean) {\r\n const lines = skipWrapping ? this._unwrappedTextLines : this._textLines;\r\n let i: number;\r\n for (i = 0; i < lines.length; i++) {\r\n if (selectionStart <= lines[i].length) {\r\n return {\r\n lineIndex: i,\r\n charIndex: selectionStart,\r\n };\r\n }\r\n selectionStart -=\r\n lines[i].length + this.missingNewlineOffset(i, skipWrapping);\r\n }\r\n return {\r\n lineIndex: i - 1,\r\n charIndex:\r\n lines[i - 1].length < selectionStart\r\n ? lines[i - 1].length\r\n : selectionStart,\r\n };\r\n }\r\n\r\n /**\r\n * Returns string representation of an instance\r\n * @return {String} String representation of text object\r\n */\r\n toString(): string {\r\n return `#<Text (${this.complexity()}): { \"text\": \"${\r\n this.text\r\n }\", \"fontFamily\": \"${this.fontFamily}\" }>`;\r\n }\r\n\r\n /**\r\n * Return the dimension and the zoom level needed to create a cache canvas\r\n * big enough to host the object to be cached.\r\n * @private\r\n * @param {Object} dim.x width of object to be cached\r\n * @param {Object} dim.y height of object to be cached\r\n * @return {Object}.width width of canvas\r\n * @return {Object}.height height of canvas\r\n * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache\r\n * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache\r\n */\r\n _getCacheCanvasDimensions(): TCacheCanvasDimensions {\r\n const dims = super._getCacheCanvasDimensions();\r\n const fontSize = this.fontSize;\r\n dims.width += fontSize * dims.zoomX;\r\n dims.height += fontSize * dims.zoomY;\r\n return dims;\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n */\r\n _render(ctx: CanvasRenderingContext2D) {\r\n const path = this.path;\r\n path && !path.isNotVisible() && path._render(ctx);\r\n this._setTextStyles(ctx);\r\n this._renderTextLinesBackground(ctx);\r\n this._renderTextDecoration(ctx, 'underline');\r\n this._renderText(ctx);\r\n this._renderTextDecoration(ctx, 'overline');\r\n this._renderTextDecoration(ctx, 'linethrough');\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n */\r\n _renderText(ctx: CanvasRenderingContext2D) {\r\n // Skip text rendering if in overlay editing mode\r\n if ((this as any).__overlayEditor) {\r\n return;\r\n }\r\n if (this.paintFirst === STROKE) {\r\n this._renderTextStroke(ctx);\r\n this._renderTextFill(ctx);\r\n } else {\r\n this._renderTextFill(ctx);\r\n this._renderTextStroke(ctx);\r\n }\r\n }\r\n\r\n /**\r\n * Set the font parameter of the context with the object properties or with charStyle\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n * @param {Object} [charStyle] object with font style properties\r\n * @param {String} [charStyle.fontFamily] Font Family\r\n * @param {Number} [charStyle.fontSize] Font size in pixels. ( without px suffix )\r\n * @param {String} [charStyle.fontWeight] Font weight\r\n * @param {String} [charStyle.fontStyle] Font style (italic|normal)\r\n */\r\n _setTextStyles(\r\n ctx: CanvasRenderingContext2D,\r\n charStyle?: any,\r\n forMeasuring?: boolean,\r\n ) {\r\n ctx.textBaseline = 'alphabetic';\r\n if (this.path) {\r\n switch (this.pathAlign) {\r\n case CENTER:\r\n ctx.textBaseline = 'middle';\r\n break;\r\n case 'ascender':\r\n ctx.textBaseline = TOP;\r\n break;\r\n case 'descender':\r\n ctx.textBaseline = BOTTOM;\r\n break;\r\n }\r\n }\r\n ctx.font = this._getFontDeclaration(charStyle, forMeasuring);\r\n }\r\n\r\n /**\r\n * calculate and return the text Width measuring each line.\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n * @return {Number} Maximum width of Text object\r\n */\r\n calcTextWidth(): number {\r\n let maxWidth = this.getLineWidth(0);\r\n\r\n for (let i = 1, len = this._textLines.length; i < len; i++) {\r\n const currentLineWidth = this.getLineWidth(i);\r\n if (currentLineWidth > maxWidth) {\r\n maxWidth = currentLineWidth;\r\n }\r\n }\r\n return maxWidth;\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {String} method Method name (\"fillText\" or \"strokeText\")\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n * @param {String} line Text to render\r\n * @param {Number} left Left position of text\r\n * @param {Number} top Top position of text\r\n * @param {Number} lineIndex Index of a line in a text\r\n */\r\n _renderTextLine(\r\n method: 'fillText' | 'strokeText',\r\n ctx: CanvasRenderingContext2D,\r\n line: string[],\r\n left: number,\r\n top: number,\r\n lineIndex: number,\r\n ) {\r\n this._renderChars(method, ctx, line, left, top, lineIndex);\r\n }\r\n\r\n /**\r\n * Renders the text background for lines, taking care of style\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n */\r\n _renderTextLinesBackground(ctx: CanvasRenderingContext2D) {\r\n if (!this.textBackgroundColor && !this.styleHas('textBackgroundColor')) {\r\n return;\r\n }\r\n const originalFill = ctx.fillStyle,\r\n leftOffset = this._getLeftOffset();\r\n let lineTopOffset = this._getTopOffset();\r\n\r\n for (let i = 0, len = this._textLines.length; i < len; i++) {\r\n const heightOfLine = this.getHeightOfLine(i);\r\n if (\r\n !this.textBackgroundColor &&\r\n !this.styleHas('textBackgroundColor', i)\r\n ) {\r\n lineTopOffset += heightOfLine;\r\n continue;\r\n }\r\n const jlen = this._textLines[i].length;\r\n const lineLeftOffset = this._getLineLeftOffset(i);\r\n let boxWidth = 0;\r\n let boxStart = 0;\r\n let drawStart;\r\n let currentColor;\r\n let lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor');\r\n for (let j = 0; j < jlen; j++) {\r\n // at this point charbox are either standard or full with pathInfo if there is a path.\r\n const charBox = this.__charBounds[i][j] as Required<GraphemeBBox>;\r\n currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor');\r\n if (this.path) {\r\n ctx.save();\r\n ctx.translate(charBox.renderLeft, charBox.renderTop);\r\n ctx.rotate(charBox.angle);\r\n ctx.fillStyle = currentColor;\r\n currentColor &&\r\n ctx.fillRect(\r\n -charBox.width / 2,\r\n (-heightOfLine / this.lineHeight) * (1 - this._fontSizeFraction),\r\n charBox.width,\r\n heightOfLine / this.lineHeight,\r\n );\r\n ctx.restore();\r\n } else if (currentColor !== lastColor) {\r\n drawStart = leftOffset + lineLeftOffset + boxStart;\r\n if (this.direction === 'rtl') {\r\n drawStart = this.width - drawStart - boxWidth;\r\n }\r\n ctx.fillStyle = lastColor;\r\n lastColor &&\r\n ctx.fillRect(\r\n drawStart,\r\n lineTopOffset,\r\n boxWidth,\r\n heightOfLine / this.lineHeight,\r\n );\r\n boxStart = charBox.left;\r\n boxWidth = charBox.width;\r\n lastColor = currentColor;\r\n } else {\r\n boxWidth += charBox.kernedWidth;\r\n }\r\n }\r\n if (currentColor && !this.path) {\r\n drawStart = leftOffset + lineLeftOffset + boxStart;\r\n if (this.direction === 'rtl') {\r\n drawStart = this.width - drawStart - boxWidth;\r\n }\r\n ctx.fillStyle = currentColor;\r\n ctx.fillRect(\r\n drawStart,\r\n lineTopOffset,\r\n boxWidth,\r\n heightOfLine / this.lineHeight,\r\n );\r\n }\r\n lineTopOffset += heightOfLine;\r\n }\r\n ctx.fillStyle = originalFill;\r\n // if there is text background color no\r\n // other shadows should be casted\r\n this._removeShadow(ctx);\r\n }\r\n\r\n /**\r\n * measure and return the width of a single character.\r\n * possibly overridden to accommodate different measure logic or\r\n * to hook some external lib for character measurement\r\n * @private\r\n * @param {String} _char, char to be measured\r\n * @param {Object} charStyle style of char to be measured\r\n * @param {String} [previousChar] previous char\r\n * @param {Object} [prevCharStyle] style of previous char\r\n */\r\n _measureChar(\r\n _char: string,\r\n charStyle: CompleteTextStyleDeclaration,\r\n previousChar: string | undefined,\r\n prevCharStyle: CompleteTextStyleDeclaration | Record<string, never>,\r\n ) {\r\n const fontCache = cache.getFontCache(charStyle),\r\n fontDeclaration = this._getFontDeclaration(charStyle),\r\n couple = previousChar + _char,\r\n stylesAreEqual =\r\n previousChar &&\r\n fontDeclaration === this._getFontDeclaration(prevCharStyle),\r\n fontMultiplier = charStyle.fontSize / this.CACHE_FONT_SIZE;\r\n let width: number | undefined,\r\n coupleWidth: number | undefined,\r\n previousWidth: number | undefined,\r\n kernedWidth: number | undefined;\r\n\r\n if (previousChar && fontCache[previousChar] !== undefined) {\r\n previousWidth = fontCache[previousChar];\r\n }\r\n if (fontCache[_char] !== undefined) {\r\n kernedWidth = width = fontCache[_char];\r\n }\r\n if (stylesAreEqual && fontCache[couple] !== undefined) {\r\n coupleWidth = fontCache[couple];\r\n kernedWidth = coupleWidth - previousWidth!;\r\n }\r\n if (\r\n width === undefined ||\r\n previousWidth === undefined ||\r\n coupleWidth === undefined\r\n ) {\r\n const ctx = getMeasuringContext()!;\r\n // send a TRUE to specify measuring font size CACHE_FONT_SIZE\r\n this._setTextStyles(ctx, charStyle, true);\r\n if (width === undefined) {\r\n kernedWidth = width = ctx.measureText(_char).width;\r\n fontCache[_char] = width;\r\n }\r\n if (previousWidth === undefined && stylesAreEqual && previousChar) {\r\n previousWidth = ctx.measureText(previousChar).width;\r\n fontCache[previousChar] = previousWidth;\r\n }\r\n if (stylesAreEqual && coupleWidth === undefined) {\r\n // we can measure the kerning couple and subtract the width of the previous character\r\n coupleWidth = ctx.measureText(couple).width;\r\n fontCache[couple] = coupleWidth;\r\n // safe to use the non-null since if undefined we defined it before.\r\n kernedWidth = coupleWidth - previousWidth!;\r\n }\r\n }\r\n return {\r\n width: width * fontMultiplier,\r\n kernedWidth: kernedWidth! * fontMultiplier,\r\n };\r\n }\r\n\r\n /**\r\n * Computes height of character at given position\r\n * @param {Number} line the line index number\r\n * @param {Number} _char the character index number\r\n * @return {Number} fontSize of the character\r\n */\r\n getHeightOfChar(line: number, _char: number): number {\r\n return this.getValueOfPropertyAt(line, _char, 'fontSize');\r\n }\r\n\r\n /**\r\n * measure a text line measuring all characters.\r\n * @param {Number} lineIndex line number\r\n */\r\n measureLine(lineIndex: number) {\r\n const lineInfo = this._measureLine(lineIndex);\r\n if (this.charSpacing !== 0) {\r\n lineInfo.width -= this._getWidthOfCharSpacing();\r\n }\r\n if (lineInfo.width < 0) {\r\n lineInfo.width = 0;\r\n }\r\n return lineInfo;\r\n }\r\n\r\n /**\r\n * measure every grapheme of a line, populating __charBounds\r\n * @param {Number} lineIndex\r\n * @return {Object} object.width total width of characters\r\n * @return {Object} object.numOfSpaces length of chars that match this._reSpacesAndTabs\r\n */\r\n _measureLine(lineIndex: number) {\r\n // Debug: detect if measureLine is called after justify was applied\r\n if ((this as any)._justifyApplied) {\r\n console.warn(`WARNING: _measureLine called for line ${lineIndex} AFTER justify was applied! This will overwrite justified charBounds.`);\r\n console.trace('Stack trace:');\r\n }\r\n\r\n let width = 0,\r\n prevGrapheme: string | undefined,\r\n graphemeInfo: GraphemeBBox | undefined;\r\n\r\n const reverse = this.pathSide === RIGHT,\r\n path = this.path,\r\n line = this._textLines[lineIndex],\r\n llength = line.length,\r\n lineBounds = new Array<GraphemeBBox>(llength);\r\n\r\n this.__charBounds[lineIndex] = lineBounds;\r\n for (let i = 0; i < llength; i++) {\r\n const grapheme = line[i];\r\n graphemeInfo = this._getGraphemeBox(grapheme, lineIndex, i, prevGrapheme);\r\n lineBounds[i] = graphemeInfo;\r\n width += graphemeInfo.kernedWidth;\r\n prevGrapheme = grapheme;\r\n }\r\n // this latest bound box represent the last character of the line\r\n // to simplify cursor handling in interactive mode.\r\n lineBounds[llength] = {\r\n left: graphemeInfo ? graphemeInfo.left + graphemeInfo.width : 0,\r\n width: 0,\r\n kernedWidth: 0,\r\n height: this.fontSize,\r\n deltaY: 0,\r\n } as GraphemeBBox;\r\n if (path && path.segmentsInfo) {\r\n let positionInPath = 0;\r\n const totalPathLength =\r\n path.segmentsInfo[path.segmentsInfo.length - 1].length;\r\n switch (this.textAlign) {\r\n case LEFT:\r\n positionInPath = reverse ? totalPathLength - width : 0;\r\n break;\r\n case CENTER:\r\n positionInPath = (totalPathLength - width) / 2;\r\n break;\r\n case RIGHT:\r\n positionInPath = reverse ? 0 : totalPathLength - width;\r\n break;\r\n //todo - add support for justify\r\n }\r\n positionInPath += this.pathStartOffset * (reverse ? -1 : 1);\r\n for (\r\n let i = reverse ? llength - 1 : 0;\r\n reverse ? i >= 0 : i < llength;\r\n reverse ? i-- : i++\r\n ) {\r\n graphemeInfo = lineBounds[i];\r\n if (positionInPath > totalPathLength) {\r\n positionInPath %= totalPathLength;\r\n } else if (positionInPath < 0) {\r\n positionInPath += totalPathLength;\r\n }\r\n // it would probably much faster to send all the grapheme position for a line\r\n // and calculate path position/angle at once.\r\n this._setGraphemeOnPath(positionInPath, graphemeInfo);\r\n positionInPath += graphemeInfo.kernedWidth;\r\n }\r\n }\r\n return { width: width, numOfSpaces: 0 };\r\n }\r\n\r\n /**\r\n * Calculate the angle and the left,top position of the char that follow a path.\r\n * It appends it to graphemeInfo to be reused later at rendering\r\n * @private\r\n * @param {Number} positionInPath to be measured\r\n * @param {GraphemeBBox} graphemeInfo current grapheme box information\r\n * @param {Object} startingPoint position of the point\r\n */\r\n _setGraphemeOnPath(positionInPath: number, graphemeInfo: GraphemeBBox) {\r\n const centerPosition = positionInPath + graphemeInfo.kernedWidth / 2,\r\n path = this.path!;\r\n\r\n // we are at currentPositionOnPath. we want to know what point on the path is.\r\n const info = getPointOnPath(path.path, centerPosition, path.segmentsInfo)!;\r\n graphemeInfo.renderLeft = info.x - path.pathOffset.x;\r\n graphemeInfo.renderTop = info.y - path.pathOffset.y;\r\n graphemeInfo.angle = info.angle + (this.pathSide === RIGHT ? Math.PI : 0);\r\n }\r\n\r\n /**\r\n *\r\n * @param {String} grapheme to be measured\r\n * @param {Number} lineIndex index of the line where the char is\r\n * @param {Number} charIndex position in the line\r\n * @param {String} [prevGrapheme] character preceding the one to be measured\r\n * @returns {GraphemeBBox} grapheme bbox\r\n */\r\n _getGraphemeBox(\r\n grapheme: string,\r\n lineIndex: number,\r\n charIndex: number,\r\n prevGrapheme?: string,\r\n skipLeft?: boolean,\r\n ): GraphemeBBox {\r\n const style = this.getCompleteStyleDeclaration(lineIndex, charIndex),\r\n prevStyle = prevGrapheme\r\n ? this.getCompleteStyleDeclaration(lineIndex, charIndex - 1)\r\n : {},\r\n info = this._measureChar(grapheme, style, prevGrapheme, prevStyle);\r\n let kernedWidth = info.kernedWidth,\r\n width = info.width,\r\n charSpacing;\r\n\r\n if (this.charSpacing !== 0) {\r\n charSpacing = this._getWidthOfCharSpacing();\r\n width += charSpacing;\r\n kernedWidth += charSpacing;\r\n }\r\n\r\n const box: GraphemeBBox = {\r\n width,\r\n left: 0,\r\n height: style.fontSize,\r\n kernedWidth,\r\n deltaY: style.deltaY,\r\n };\r\n if (charIndex > 0 && !skipLeft) {\r\n const previousBox = this.__charBounds[lineIndex][charIndex - 1];\r\n box.left =\r\n previousBox.left + previousBox.width + info.kernedWidth - info.width;\r\n }\r\n return box;\r\n }\r\n\r\n /**\r\n * Calculate height of line at 'lineIndex'\r\n * @param {Number} lineIndex index of line to calculate\r\n * @return {Number}\r\n */\r\n getHeightOfLine(lineIndex: number): number {\r\n if (this.__lineHeights[lineIndex]) {\r\n return this.__lineHeights[lineIndex];\r\n }\r\n\r\n // char 0 is measured before the line cycle because it needs to char\r\n // emptylines\r\n let maxHeight = this.getHeightOfChar(lineIndex, 0);\r\n for (let i = 1, len = this._textLines[lineIndex].length; i < len; i++) {\r\n maxHeight = Math.max(this.getHeightOfChar(lineIndex, i), maxHeight);\r\n }\r\n\r\n return (this.__lineHeights[lineIndex] =\r\n maxHeight * this.lineHeight * this._fontSizeMult);\r\n }\r\n\r\n /**\r\n * Calculate text box height\r\n */\r\n calcTextHeight() {\r\n let lineHeight,\r\n height = 0;\r\n for (let i = 0, len = this._textLines.length; i < len; i++) {\r\n lineHeight = this.getHeightOfLine(i);\r\n height += i === len - 1 ? lineHeight / this.lineHeight : lineHeight;\r\n }\r\n return height;\r\n }\r\n\r\n /**\r\n * @private\r\n * @return {Number} Left offset\r\n */\r\n _getLeftOffset(): number {\r\n return this.direction === 'ltr' ? -this.width / 2 : this.width / 2;\r\n }\r\n\r\n /**\r\n * @private\r\n * @return {Number} Top offset\r\n */\r\n _getTopOffset(): number {\r\n return -this.height / 2;\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n * @param {String} method Method name (\"fillText\" or \"strokeText\")\r\n */\r\n _renderTextCommon(\r\n ctx: CanvasRenderingContext2D,\r\n method: 'fillText' | 'strokeText',\r\n ) {\r\n ctx.save();\r\n let lineHeights = 0;\r\n const left = this._getLeftOffset(),\r\n top = this._getTopOffset();\r\n\r\n // Debug: log once per render\r\n if (method === 'fillText' && this.textAlign?.includes('justify')) {\r\n console.log('=== RENDER DEBUG ===');\r\n console.log('direction:', this.direction);\r\n console.log('textAlign:', this.textAlign);\r\n console.log('width:', this.width);\r\n console.log('_getLeftOffset:', left);\r\n }\r\n\r\n for (let i = 0, len = this._textLines.length; i < len; i++) {\r\n const heightOfLine = this.getHeightOfLine(i),\r\n maxHeight = heightOfLine / this.lineHeight,\r\n leftOffset = this._getLineLeftOffset(i);\r\n\r\n // Debug: log line offsets for justify\r\n if (method === 'fillText' && this.textAlign?.includes('justify')) {\r\n const lineWidth = this.getLineWidth(i);\r\n console.log(`Line ${i}: leftOffset=${leftOffset.toFixed(2)}, lineWidth=${lineWidth.toFixed(2)}, renderAt=${(left + leftOffset).toFixed(2)}`);\r\n }\r\n\r\n this._renderTextLine(\r\n method,\r\n ctx,\r\n this._textLines[i],\r\n left + leftOffset,\r\n top + lineHeights + maxHeight,\r\n i,\r\n );\r\n lineHeights += heightOfLine;\r\n }\r\n ctx.restore();\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n */\r\n _renderTextFill(ctx: CanvasRenderingContext2D) {\r\n if (!this.fill && !this.styleHas(FILL)) {\r\n return;\r\n }\r\n\r\n this._renderTextCommon(ctx, 'fillText');\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n */\r\n _renderTextStroke(ctx: CanvasRenderingContext2D) {\r\n if ((!this.stroke || this.strokeWidth === 0) && this.isEmptyStyles()) {\r\n return;\r\n }\r\n\r\n if (this.shadow && !this.shadow.affectStroke) {\r\n this._removeShadow(ctx);\r\n }\r\n\r\n ctx.save();\r\n this._setLineDash(ctx, this.strokeDashArray);\r\n ctx.beginPath();\r\n this._renderTextCommon(ctx, 'strokeText');\r\n ctx.closePath();\r\n ctx.restore();\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {String} method fillText or strokeText.\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n * @param {Array} line Content of the line, splitted in an array by grapheme\r\n * @param {Number} left\r\n * @param {Number} top\r\n * @param {Number} lineIndex\r\n */\r\n _renderChars(\r\n method: 'fillText' | 'strokeText',\r\n ctx: CanvasRenderingContext2D,\r\n line: Array<any>,\r\n left: number,\r\n top: number,\r\n lineIndex: number,\r\n ) {\r\n const lineHeight = this.getHeightOfLine(lineIndex),\r\n isJustify = this.textAlign.includes(JUSTIFY),\r\n path = this.path,\r\n shortCut =\r\n !isJustify &&\r\n this.charSpacing === 0 &&\r\n this.isEmptyStyles(lineIndex) &&\r\n !path,\r\n isLtr = this.direction === 'ltr',\r\n sign = this.direction === 'ltr' ? 1 : -1,\r\n // this was changed in the PR #7674\r\n // currentDirection = ctx.canvas.getAttribute('dir');\r\n currentDirection = ctx.direction;\r\n\r\n let actualStyle,\r\n nextStyle,\r\n charsToRender = '',\r\n charBox,\r\n boxWidth = 0,\r\n timeToRender,\r\n drawingLeft;\r\n\r\n ctx.save();\r\n if (currentDirection !== this.direction) {\r\n ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');\r\n ctx.direction = isLtr ? 'ltr' : 'rtl';\r\n \r\n // Set text alignment based on direction\r\n // For RTL, use RIGHT alignment so x-coordinate specifies the right edge (where RTL text starts)\r\n // For LTR, use LEFT alignment so x-coordinate specifies the left edge (where LTR text starts)\r\n ctx.textAlign = isLtr ? LEFT : RIGHT;\r\n }\r\n top -= (lineHeight * this._fontSizeFraction) / this.lineHeight;\r\n if (shortCut) {\r\n // render all the line in one pass without checking\r\n // drawingLeft = isLtr ? left : left - this.getLineWidth(lineIndex);\r\n this._renderChar(method, ctx, lineIndex, 0, line.join(''), left, top);\r\n ctx.restore();\r\n return;\r\n }\r\n // Debug: Log charBounds being used for first line only during justify\r\n if (isJustify && lineIndex === 0 && method === 'fillText') {\r\n console.log(`\\n=== RENDER _renderChars line ${lineIndex} ===`);\r\n console.log('Initial left:', left.toFixed(2), 'sign:', sign);\r\n console.log('_justifyApplied flag:', (this as any)._justifyApplied);\r\n const lineBounds = this.__charBounds[lineIndex];\r\n const totalKW = lineBounds?.reduce((s, b) => s + (b?.kernedWidth || 0), 0) || 0;\r\n console.log('Total kernedWidth in charBounds:', totalKW.toFixed(2), '(should be ~300 if justify was applied)');\r\n // Log first few space widths to verify expansion\r\n const spaceIndices = [3, 9, 15, 23, 31];\r\n spaceIndices.forEach(idx => {\r\n const b = lineBounds?.[idx];\r\n if (b) console.log(` Space at idx ${idx}: kernedWidth=${b.kernedWidth?.toFixed(2)}`);\r\n });\r\n }\r\n\r\n for (let i = 0, len = line.length - 1; i <= len; i++) {\r\n timeToRender = i === len || this.charSpacing || path;\r\n charsToRender += line[i];\r\n charBox = this.__charBounds[lineIndex][i] as Required<GraphemeBBox>;\r\n if (boxWidth === 0) {\r\n if (isLtr) {\r\n // For LTR, adjust for kerning difference of first character\r\n left += sign * (charBox.kernedWidth - charBox.width);\r\n boxWidth += charBox.width;\r\n } else {\r\n // For RTL with drawingLeft = left - boxWidth positioning,\r\n // use kernedWidth consistently to ensure the right edge is exactly at 'left'\r\n boxWidth += charBox.kernedWidth;\r\n }\r\n } else {\r\n boxWidth += charBox.kernedWidth;\r\n }\r\n if (isJustify && !timeToRender) {\r\n if (this._reSpaceAndTab.test(line[i])) {\r\n timeToRender = true;\r\n }\r\n }\r\n if (!timeToRender) {\r\n // if we have charSpacing, we render char by char\r\n actualStyle =\r\n actualStyle || this.getCompleteStyleDeclaration(lineIndex, i);\r\n nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1);\r\n timeToRender = hasStyleChanged(actualStyle, nextStyle, false);\r\n }\r\n if (timeToRender) {\r\n if (path) {\r\n ctx.save();\r\n ctx.translate(charBox.renderLeft, charBox.renderTop);\r\n ctx.rotate(charBox.angle);\r\n this._renderChar(\r\n method,\r\n ctx,\r\n lineIndex,\r\n i,\r\n charsToRender,\r\n -boxWidth / 2,\r\n 0,\r\n );\r\n ctx.restore();\r\n } else {\r\n // For LTR with textAlign='left': x is the left edge, so drawingLeft = left\r\n // For RTL with textAlign='right': x is the right edge, so drawingLeft = left\r\n // Both cases: drawingLeft = left (the text alignment handles the edge correctly)\r\n drawingLeft = left;\r\n // Debug: log first chunk positioning for justify\r\n if (isJustify && lineIndex === 0 && method === 'fillText' && i < 5) {\r\n console.log(` Chunk ending at char ${i}: left=${left.toFixed(2)}, boxWidth=${boxWidth.toFixed(2)}, drawingLeft=${drawingLeft.toFixed(2)}, textAlign=${isLtr ? 'left' : 'right'}`);\r\n }\r\n this._renderChar(\r\n method,\r\n ctx,\r\n lineIndex,\r\n i,\r\n charsToRender,\r\n drawingLeft,\r\n top,\r\n );\r\n }\r\n charsToRender = '';\r\n actualStyle = nextStyle;\r\n left += sign * boxWidth;\r\n boxWidth = 0;\r\n }\r\n }\r\n // Debug: log final position for justify\r\n if (isJustify && lineIndex === 0 && method === 'fillText') {\r\n console.log('Final left position after rendering:', left.toFixed(2));\r\n console.log('Expected final position:', (sign > 0 ? this.width / 2 : -this.width / 2).toFixed(2));\r\n }\r\n ctx.restore();\r\n }\r\n\r\n /**\r\n * This function try to patch the missing gradientTransform on canvas gradients.\r\n * transforming a context to transform the gradient, is going to transform the stroke too.\r\n * we want to transform the gradient but not the stroke operation, so we create\r\n * a transformed gradient on a pattern and then we use the pattern instead of the gradient.\r\n * this method has drawbacks: is slow, is in low resolution, needs a patch for when the size\r\n * is limited.\r\n * @private\r\n * @param {TFiller} filler a fabric gradient instance\r\n * @return {CanvasPattern} a pattern to use as fill/stroke style\r\n */\r\n _applyPatternGradientTransformText(filler: TFiller) {\r\n // TODO: verify compatibility with strokeUniform\r\n const width = this.width + this.strokeWidth,\r\n height = this.height + this.strokeWidth,\r\n pCanvas = createCanvasElementFor({\r\n width,\r\n height,\r\n }),\r\n pCtx = pCanvas.getContext('2d')!;\r\n pCanvas.width = width;\r\n pCanvas.height = height;\r\n pCtx.beginPath();\r\n pCtx.moveTo(0, 0);\r\n pCtx.lineTo(width, 0);\r\n pCtx.lineTo(width, height);\r\n pCtx.lineTo(0, height);\r\n pCtx.closePath();\r\n pCtx.translate(width / 2, height / 2);\r\n pCtx.fillStyle = filler.toLive(pCtx)!;\r\n this._applyPatternGradientTransform(pCtx, filler);\r\n pCtx.fill();\r\n return pCtx.createPattern(pCanvas, 'no-repeat')!;\r\n }\r\n\r\n handleFiller<T extends 'fill' | 'stroke'>(\r\n ctx: CanvasRenderingContext2D,\r\n property: `${T}Style`,\r\n filler: TFiller | string,\r\n ): { offsetX: number; offsetY: number } {\r\n let offsetX: number, offsetY: number;\r\n if (isFiller(filler)) {\r\n if (\r\n (filler as Gradient<'linear'>).gradientUnits === 'percentage' ||\r\n (filler as Gradient<'linear'>).gradientTransform ||\r\n (filler as Pattern).patternTransform\r\n ) {\r\n // need to transform gradient in a pattern.\r\n // this is a slow process. If you are hitting this codepath, and the object\r\n // is not using caching, you should consider switching it on.\r\n // we need a canvas as big as the current object caching canvas.\r\n offsetX = -this.width / 2;\r\n offsetY = -this.height / 2;\r\n ctx.translate(offsetX, offsetY);\r\n ctx[property] = this._applyPatternGradientTransformText(filler);\r\n return { offsetX, offsetY };\r\n } else {\r\n // is a simple gradient or pattern\r\n ctx[property] = filler.toLive(ctx)!;\r\n return this._applyPatternGradientTransform(ctx, filler);\r\n }\r\n } else {\r\n // is a color\r\n ctx[property] = filler;\r\n }\r\n return { offsetX: 0, offsetY: 0 };\r\n }\r\n\r\n /**\r\n * This function prepare the canvas for a stroke style, and stroke and strokeWidth\r\n * need to be sent in as defined\r\n * @param {CanvasRenderingContext2D} ctx\r\n * @param {CompleteTextStyleDeclaration} style with stroke and strokeWidth defined\r\n * @returns\r\n */\r\n _setStrokeStyles(\r\n ctx: CanvasRenderingContext2D,\r\n {\r\n stroke,\r\n strokeWidth,\r\n }: Pick<CompleteTextStyleDeclaration, 'stroke' | 'strokeWidth'>,\r\n ) {\r\n ctx.lineWidth = strokeWidth;\r\n ctx.lineCap = this.strokeLineCap;\r\n ctx.lineDashOffset = this.strokeDashOffset;\r\n ctx.lineJoin = this.strokeLineJoin;\r\n ctx.miterLimit = this.strokeMiterLimit;\r\n return this.handleFiller(ctx, 'strokeStyle', stroke!);\r\n }\r\n\r\n /**\r\n * This function prepare the canvas for a ill style, and fill\r\n * need to be sent in as defined\r\n * @param {CanvasRenderingContext2D} ctx\r\n * @param {CompleteTextStyleDeclaration} style with ill defined\r\n * @returns\r\n */\r\n _setFillStyles(ctx: CanvasRenderingContext2D, { fill }: Pick<this, 'fill'>) {\r\n return this.handleFiller(ctx, 'fillStyle', fill!);\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {String} method\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n * @param {Number} lineIndex\r\n * @param {Number} charIndex\r\n * @param {String} _char\r\n * @param {Number} left Left coordinate\r\n * @param {Number} top Top coordinate\r\n * @param {Number} lineHeight Height of the line\r\n */\r\n _renderChar(\r\n method: 'fillText' | 'strokeText',\r\n ctx: CanvasRenderingContext2D,\r\n lineIndex: number,\r\n charIndex: number,\r\n _char: string,\r\n left: number,\r\n top: number,\r\n ) {\r\n const decl = this._getStyleDeclaration(lineIndex, charIndex),\r\n fullDecl = this.getCompleteStyleDeclaration(lineIndex, charIndex),\r\n shouldFill = method === 'fillText' && fullDecl.fill,\r\n shouldStroke =\r\n method === 'strokeText' && fullDecl.stroke && fullDecl.strokeWidth;\r\n\r\n if (!shouldStroke && !shouldFill) {\r\n return;\r\n }\r\n ctx.save();\r\n\r\n ctx.font = this._getFontDeclaration(fullDecl);\r\n\r\n if (decl.textBackgroundColor) {\r\n this._removeShadow(ctx);\r\n }\r\n if (decl.deltaY) {\r\n top += decl.deltaY;\r\n }\r\n\r\n if (shouldFill) {\r\n const fillOffsets = this._setFillStyles(ctx, fullDecl);\r\n ctx.fillText(\r\n _char,\r\n left - fillOffsets.offsetX,\r\n top - fillOffsets.offsetY,\r\n );\r\n }\r\n\r\n if (shouldStroke) {\r\n const strokeOffsets = this._setStrokeStyles(ctx, fullDecl);\r\n ctx.strokeText(\r\n _char,\r\n left - strokeOffsets.offsetX,\r\n top - strokeOffsets.offsetY,\r\n );\r\n }\r\n\r\n ctx.restore();\r\n }\r\n\r\n /**\r\n * Turns the character into a 'superior figure' (i.e. 'superscript')\r\n * @param {Number} start selection start\r\n * @param {Number} end selection end\r\n */\r\n setSuperscript(start: number, end: number) {\r\n this._setScript(start, end, this.superscript);\r\n }\r\n\r\n /**\r\n * Turns the character into an 'inferior figure' (i.e. 'subscript')\r\n * @param {Number} start selection start\r\n * @param {Number} end selection end\r\n */\r\n setSubscript(start: number, end: number) {\r\n this._setScript(start, end, this.subscript);\r\n }\r\n\r\n /**\r\n * Applies 'schema' at given position\r\n * @private\r\n * @param {Number} start selection start\r\n * @param {Number} end selection end\r\n * @param {Number} schema\r\n */\r\n protected _setScript(\r\n start: number,\r\n end: number,\r\n schema: {\r\n size: number;\r\n baseline: number;\r\n },\r\n ) {\r\n const loc = this.get2DCursorLocation(start, true),\r\n fontSize = this.getValueOfPropertyAt(\r\n loc.lineIndex,\r\n loc.charIndex,\r\n 'fontSize',\r\n ),\r\n dy = this.getValueOfPropertyAt(loc.lineIndex, loc.charIndex, 'deltaY'),\r\n style = {\r\n fontSize: fontSize * schema.size,\r\n deltaY: dy + fontSize * schema.baseline,\r\n };\r\n this.setSelectionStyles(style, start, end);\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {Number} lineIndex index text line\r\n * @return {Number} Line left offset\r\n */\r\n _getLineLeftOffset(lineIndex: number): number {\r\n const lineWidth = this.getLineWidth(lineIndex),\r\n lineDiff = this.width - lineWidth,\r\n textAlign = this.textAlign,\r\n direction = this.direction,\r\n isEndOfWrapping = this.isEndOfWrapping(lineIndex);\r\n const hasTextAfter = this._textLines\r\n .slice(lineIndex + 1)\r\n .some((line) => {\r\n const lineText = Array.isArray(line) ? line.join('') : line;\r\n return /\\S/.test(lineText);\r\n });\r\n const isVisualLastLine = !hasTextAfter;\r\n let leftOffset = 0;\r\n\r\n // Determine if this line should be justified (not last line)\r\n const isJustifyLine =\r\n textAlign === JUSTIFY ||\r\n (textAlign === JUSTIFY_CENTER && !isEndOfWrapping && !isVisualLastLine) ||\r\n (textAlign === JUSTIFY_RIGHT && !isEndOfWrapping && !isVisualLastLine) ||\r\n (textAlign === JUSTIFY_LEFT && !isEndOfWrapping && !isVisualLastLine);\r\n\r\n if (isJustifyLine) {\r\n // Justified lines start at the left edge (offset 0) for both LTR and RTL\r\n // The space expansion from enlargeSpaces() fills the line to the full width\r\n return 0;\r\n }\r\n\r\n // Handle last line alignment (or non-justify alignments)\r\n if (textAlign === CENTER || textAlign === JUSTIFY_CENTER) {\r\n leftOffset = lineDiff / 2;\r\n } else if (textAlign === RIGHT || textAlign === JUSTIFY_RIGHT) {\r\n leftOffset = lineDiff;\r\n }\r\n // LEFT and JUSTIFY_LEFT have leftOffset = 0, which is the default\r\n\r\n // Apply RTL adjustments for non-justified lines\r\n if (direction === 'rtl') {\r\n if (textAlign === RIGHT || textAlign === JUSTIFY || textAlign === JUSTIFY_RIGHT) {\r\n leftOffset = 0;\r\n } else if (textAlign === LEFT || textAlign === JUSTIFY_LEFT) {\r\n leftOffset = -lineDiff;\r\n } else if (textAlign === CENTER || textAlign === JUSTIFY_CENTER) {\r\n leftOffset = -lineDiff / 2;\r\n }\r\n // Clamp to 0 for overflow cases\r\n if (lineDiff <= 0) {\r\n leftOffset = 0;\r\n }\r\n }\r\n\r\n return leftOffset;\r\n }\r\n\r\n /**\r\n * @private\r\n */\r\n _clearCache() {\r\n this._forceClearCache = false;\r\n this.__lineWidths = [];\r\n this.__lineHeights = [];\r\n this.__charBounds = [];\r\n // Reset justify applied flag\r\n (this as any)._justifyApplied = false;\r\n }\r\n\r\n /**\r\n * Measure a single line given its index. Used to calculate the initial\r\n * text bounding box. The values are calculated and stored in __lineWidths cache.\r\n * @private\r\n * @param {Number} lineIndex line number\r\n * @return {Number} Line width\r\n */\r\n getLineWidth(lineIndex: number): number {\r\n if (this.__lineWidths[lineIndex] !== undefined) {\r\n return this.__lineWidths[lineIndex];\r\n }\r\n\r\n const { width } = this.measureLine(lineIndex);\r\n this.__lineWidths[lineIndex] = width;\r\n return width;\r\n }\r\n\r\n _getWidthOfCharSpacing() {\r\n if (this.charSpacing !== 0) {\r\n return (this.fontSize * this.charSpacing) / 1000;\r\n }\r\n return 0;\r\n }\r\n\r\n /**\r\n * Retrieves the value of property at given character position\r\n * @param {Number} lineIndex the line number\r\n * @param {Number} charIndex the character number\r\n * @param {String} property the property name\r\n * @returns the value of 'property'\r\n */\r\n getValueOfPropertyAt<T extends StylePropertiesType>(\r\n lineIndex: number,\r\n charIndex: number,\r\n property: T,\r\n ): this[T] {\r\n const charStyle = this._getStyleDeclaration(lineIndex, charIndex);\r\n return (charStyle[property] ?? this[property]) as this[T];\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n */\r\n _renderTextDecoration(\r\n ctx: CanvasRenderingContext2D,\r\n type: 'underline' | 'linethrough' | 'overline',\r\n ) {\r\n if (!this[type] && !this.styleHas(type)) {\r\n return;\r\n }\r\n let topOffset = this._getTopOffset();\r\n const leftOffset = this._getLeftOffset(),\r\n path = this.path,\r\n charSpacing = this._getWidthOfCharSpacing(),\r\n offsetAligner =\r\n type === 'linethrough' ? 0.5 : type === 'overline' ? 1 : 0,\r\n offsetY = this.offsets[type];\r\n for (let i = 0, len = this._textLines.length; i < len; i++) {\r\n const heightOfLine = this.getHeightOfLine(i);\r\n if (!this[type] && !this.styleHas(type, i)) {\r\n topOffset += heightOfLine;\r\n continue;\r\n }\r\n const line = this._textLines[i];\r\n const maxHeight = heightOfLine / this.lineHeight;\r\n const lineLeftOffset = this._getLineLeftOffset(i);\r\n let boxStart = 0;\r\n let boxWidth = 0;\r\n let lastDecoration = this.getValueOfPropertyAt(i, 0, type);\r\n let lastFill = this.getValueOfPropertyAt(i, 0, FILL);\r\n let lastTickness = this.getValueOfPropertyAt(\r\n i,\r\n 0,\r\n TEXT_DECORATION_THICKNESS,\r\n );\r\n let currentDecoration = lastDecoration;\r\n let currentFill = lastFill;\r\n let currentTickness = lastTickness;\r\n const top = topOffset + maxHeight * (1 - this._fontSizeFraction);\r\n let size = this.getHeightOfChar(i, 0);\r\n let dy = this.getValueOfPropertyAt(i, 0, 'deltaY');\r\n for (let j = 0, jlen = line.length; j < jlen; j++) {\r\n const charBox = this.__charBounds[i][j] as Required<GraphemeBBox>;\r\n currentDecoration = this.getValueOfPropertyAt(i, j, type);\r\n currentFill = this.getValueOfPropertyAt(i, j, FILL);\r\n currentTickness = this.getValueOfPropertyAt(\r\n i,\r\n j,\r\n TEXT_DECORATION_THICKNESS,\r\n );\r\n const currentSize = this.getHeightOfChar(i, j);\r\n const currentDy = this.getValueOfPropertyAt(i, j, 'deltaY');\r\n if (path && currentDecoration && currentFill) {\r\n const finalTickness = (this.fontSize * currentTickness) / 1000;\r\n ctx.save();\r\n // bug? verify lastFill is a valid fill here.\r\n ctx.fillStyle = lastFill as string;\r\n ctx.translate(charBox.renderLeft, charBox.renderTop);\r\n ctx.rotate(charBox.angle);\r\n ctx.fillRect(\r\n -charBox.kernedWidth / 2,\r\n offsetY * currentSize + currentDy - offsetAligner * finalTickness,\r\n charBox.kernedWidth,\r\n finalTickness,\r\n );\r\n ctx.restore();\r\n } else if (\r\n (currentDecoration !== lastDecoration ||\r\n currentFill !== lastFill ||\r\n currentSize !== size ||\r\n currentTickness !== lastTickness ||\r\n currentDy !== dy) &&\r\n boxWidth > 0\r\n ) {\r\n const finalTickness = (this.fontSize * lastTickness) / 1000;\r\n let drawStart = leftOffset + lineLeftOffset + boxStart;\r\n if (this.direction === 'rtl') {\r\n drawStart = this.width - drawStart - boxWidth;\r\n }\r\n if (lastDecoration && lastFill && lastTickness) {\r\n // bug? verify lastFill is a valid fill here.\r\n ctx.fillStyle = lastFill as string;\r\n ctx.fillRect(\r\n drawStart,\r\n top + offsetY * size + dy - offsetAligner * finalTickness,\r\n boxWidth,\r\n finalTickness,\r\n );\r\n }\r\n boxStart = charBox.left;\r\n boxWidth = charBox.width;\r\n lastDecoration = currentDecoration;\r\n lastTickness = currentTickness;\r\n lastFill = currentFill;\r\n size = currentSize;\r\n dy = currentDy;\r\n } else {\r\n boxWidth += charBox.kernedWidth;\r\n }\r\n }\r\n let drawStart = leftOffset + lineLeftOffset + boxStart;\r\n if (this.direction === 'rtl') {\r\n drawStart = this.width - drawStart - boxWidth;\r\n }\r\n ctx.fillStyle = currentFill as string;\r\n const finalTickness = (this.fontSize * currentTickness) / 1000;\r\n currentDecoration &&\r\n currentFill &&\r\n currentTickness &&\r\n ctx.fillRect(\r\n drawStart,\r\n top + offsetY * size + dy - offsetAligner * finalTickness,\r\n boxWidth - charSpacing,\r\n finalTickness,\r\n );\r\n topOffset += heightOfLine;\r\n }\r\n // if there is text background color no\r\n // other shadows should be casted\r\n this._removeShadow(ctx);\r\n }\r\n\r\n /**\r\n * return font declaration string for canvas context\r\n * @param {Object} [styleObject] object\r\n * @returns {String} font declaration formatted for canvas context.\r\n */\r\n _getFontDeclaration(\r\n {\r\n fontFamily = this.fontFamily,\r\n fontStyle = this.fontStyle,\r\n fontWeight = this.fontWeight,\r\n fontSize = this.fontSize,\r\n }: Partial<\r\n Pick<\r\n TextStyleDeclaration,\r\n 'fontFamily' | 'fontStyle' | 'fontWeight' | 'fontSize'\r\n >\r\n > = {},\r\n forMeasuring?: boolean,\r\n ): string {\r\n let parsedFontFamily =\r\n fontFamily.includes(\"'\") ||\r\n fontFamily.includes('\"') ||\r\n fontFamily.includes(',') ||\r\n FabricText.genericFonts.includes(fontFamily.toLowerCase())\r\n ? fontFamily\r\n : `\"${fontFamily}\"`;\r\n \r\n // For fonts like STV that don't support English/Latin characters,\r\n // add fallback fonts for consistent rendering of unsupported characters\r\n // Only add fallbacks during actual rendering, not for measurements\r\n if (!forMeasuring && // Only during rendering, not measuring\r\n !fontFamily.includes(',') && // Don't add fallbacks if already has them\r\n (fontFamily.toLowerCase().includes('stv') || \r\n fontFamily.toLowerCase().includes('arabic') ||\r\n fontFamily.toLowerCase().includes('naskh') ||\r\n fontFamily.toLowerCase().includes('kufi'))) {\r\n // Add fallback fonts for unsupported characters (spaces, punctuation, etc.)\r\n parsedFontFamily = `${parsedFontFamily}, \"Arial Unicode MS\", Arial, sans-serif`;\r\n }\r\n \r\n return [\r\n fontStyle,\r\n fontWeight,\r\n `${forMeasuring ? this.CACHE_FONT_SIZE : fontSize}px`,\r\n parsedFontFamily,\r\n ].join(' ');\r\n }\r\n\r\n /**\r\n * Renders text instance on a specified context\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n */\r\n render(ctx: CanvasRenderingContext2D) {\r\n if (!this.visible) {\r\n return;\r\n }\r\n if (\r\n this.canvas &&\r\n this.canvas.skipOffscreen &&\r\n !this.group &&\r\n !this.isOnScreen()\r\n ) {\r\n return;\r\n }\r\n if (this._forceClearCache) {\r\n this.initDimensions();\r\n }\r\n super.render(ctx);\r\n }\r\n\r\n /**\r\n * Override this method to customize grapheme splitting\r\n * @todo the util `graphemeSplit` needs to be injectable in some way.\r\n * is more comfortable to inject the correct util rather than having to override text\r\n * in the middle of the prototype chain\r\n * @param {string} value\r\n * @returns {string[]} array of graphemes\r\n */\r\n graphemeSplit(value: string): string[] {\r\n return graphemeSplit(value);\r\n }\r\n\r\n /**\r\n * Returns the text as an array of lines.\r\n * @param {String} text text to split\r\n * @returns Lines in the text\r\n */\r\n _splitTextIntoLines(text: string): TextLinesInfo {\r\n const lines = text.split(this._reNewline),\r\n newLines = new Array<string[]>(lines.length),\r\n newLine = ['\\n'];\r\n let newText: string[] = [];\r\n for (let i = 0; i < lines.length; i++) {\r\n // Use BiDi-aware grapheme splitting for RTL text\r\n if (this.direction === 'rtl' || this._containsArabicText(lines[i])) {\r\n newLines[i] = segmentGraphemes(lines[i]);\r\n } else {\r\n newLines[i] = this.graphemeSplit(lines[i]);\r\n }\r\n newText = newText.concat(newLines[i], newLine);\r\n }\r\n newText.pop();\r\n return {\r\n _unwrappedLines: newLines,\r\n lines: lines,\r\n graphemeText: newText,\r\n graphemeLines: newLines,\r\n };\r\n }\r\n \r\n /**\r\n * Check if text contains Arabic characters\r\n * @private\r\n */\r\n _containsArabicText(text: string): boolean {\r\n return /[\\u0600-\\u06FF\\u0750-\\u077F\\uFB50-\\uFDFF\\uFE70-\\uFEFF]/.test(text);\r\n }\r\n\r\n /**\r\n * Returns object representation of an instance\r\n * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output\r\n * @return {Object} Object representation of an instance\r\n */\r\n toObject<\r\n T extends Omit<Props & TClassProperties<this>, keyof SProps>,\r\n K extends keyof T = never,\r\n >(propertiesToInclude: K[] = []): Pick<T, K> & SProps {\r\n return {\r\n ...super.toObject([...additionalProps, ...propertiesToInclude] as K[]),\r\n styles: stylesToArray(this.styles, this.text),\r\n ...(this.path ? { path: this.path.toObject() } : {}),\r\n };\r\n }\r\n\r\n set(key: string | any, value?: any) {\r\n const { textLayoutProperties } = this.constructor as typeof FabricText;\r\n super.set(key, value);\r\n let needsDims = false;\r\n let isAddingPath = false;\r\n if (typeof key === 'object') {\r\n for (const _key in key) {\r\n if (_key === 'path') {\r\n this.setPathInfo();\r\n }\r\n needsDims = needsDims || textLayoutProperties.includes(_key);\r\n isAddingPath = isAddingPath || _key === 'path';\r\n }\r\n } else {\r\n needsDims = textLayoutProperties.includes(key);\r\n isAddingPath = key === 'path';\r\n }\r\n if (isAddingPath) {\r\n this.setPathInfo();\r\n }\r\n if (needsDims && this.initialized) {\r\n // Clear browser lines when layout-affecting properties change\r\n clearBrowserLines(this);\r\n this.initDimensions();\r\n this.setCoords();\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * Returns complexity of an instance\r\n * @return {Number} complexity\r\n */\r\n complexity(): number {\r\n return 1;\r\n }\r\n\r\n /**\r\n * List of generic font families\r\n * @see https://developer.mozilla.org/en-US/docs/Web/CSS/font-family#generic-name\r\n */\r\n static genericFonts = [\r\n 'serif',\r\n 'sans-serif',\r\n 'monospace',\r\n 'cursive',\r\n 'fantasy',\r\n 'system-ui',\r\n 'ui-serif',\r\n 'ui-sans-serif',\r\n 'ui-monospace',\r\n 'ui-rounded',\r\n 'math',\r\n 'emoji',\r\n 'fangsong',\r\n ];\r\n\r\n /* _FROM_SVG_START_ */\r\n\r\n /**\r\n * List of attribute names to account for when parsing SVG element (used by {@link FabricText.fromElement})\r\n * @see: http://www.w3.org/TR/SVG/text.html#TextElement\r\n */\r\n static ATTRIBUTE_NAMES = SHARED_ATTRIBUTES.concat(\r\n 'x',\r\n 'y',\r\n 'dx',\r\n 'dy',\r\n 'font-family',\r\n 'font-style',\r\n 'font-weight',\r\n 'font-size',\r\n 'letter-spacing',\r\n 'text-decoration',\r\n 'text-anchor',\r\n );\r\n\r\n /**\r\n * Returns FabricText instance from an SVG element (<b>not yet implemented</b>)\r\n * @param {HTMLElement} element Element to parse\r\n * @param {Object} [options] Options object\r\n */\r\n static async fromElement(\r\n element: HTMLElement | SVGElement,\r\n options?: Abortable,\r\n cssRules?: CSSRules,\r\n ) {\r\n const parsedAttributes = parseAttributes(\r\n element,\r\n FabricText.ATTRIBUTE_NAMES,\r\n cssRules,\r\n );\r\n\r\n const {\r\n textAnchor = LEFT as typeof LEFT | typeof CENTER | typeof RIGHT,\r\n textDecoration = '',\r\n dx = 0,\r\n dy = 0,\r\n top = 0,\r\n left = 0,\r\n fontSize = DEFAULT_SVG_FONT_SIZE,\r\n strokeWidth = 1,\r\n ...restOfOptions\r\n } = { ...options, ...parsedAttributes };\r\n\r\n const textContent = (element.textContent || '')\r\n .replace(/^\\s+|\\s+$|\\n+/g, '')\r\n .replace(/\\s+/g, ' ');\r\n\r\n // this code here is probably the usual issue for SVG center find\r\n // this can later looked at again and probably removed.\r\n\r\n const text = new this(textContent, {\r\n left: left + dx,\r\n top: top + dy,\r\n underline: textDecoration.includes('underline'),\r\n overline: textDecoration.includes('overline'),\r\n linethrough: textDecoration.includes('line-through'),\r\n // we initialize this as 0\r\n strokeWidth: 0,\r\n fontSize,\r\n ...restOfOptions,\r\n }),\r\n textHeightScaleFactor = text.getScaledHeight() / text.height,\r\n lineHeightDiff =\r\n (text.height + text.strokeWidth) * text.lineHeight - text.height,\r\n scaledDiff = lineHeightDiff * textHeightScaleFactor,\r\n textHeight = text.getScaledHeight() + scaledDiff;\r\n\r\n let offX = 0;\r\n /*\r\n Adjust positioning:\r\n x/y attributes in SVG correspond to the bottom-left corner of text bounding box\r\n fabric output by default at top, left.\r\n */\r\n if (textAnchor === CENTER) {\r\n offX = text.getScaledWidth() / 2;\r\n }\r\n if (textAnchor === RIGHT) {\r\n offX = text.getScaledWidth();\r\n }\r\n text.set({\r\n left: text.left - offX,\r\n top:\r\n text.top -\r\n (textHeight - text.fontSize * (0.07 + text._fontSizeFraction)) /\r\n text.lineHeight,\r\n strokeWidth,\r\n });\r\n return text;\r\n }\r\n\r\n /* _FROM_SVG_END_ */\r\n\r\n /**\r\n * Check if the font is ready for accurate measurements\r\n * @private\r\n */\r\n _isFontReady(): boolean {\r\n if (typeof document === 'undefined' || !('fonts' in document)) {\r\n return true; // Assume ready in non-browser environments\r\n }\r\n \r\n try {\r\n return document.fonts.check(`${this.fontSize}px ${this.fontFamily}`);\r\n } catch (e) {\r\n return true; // Fallback to assuming ready if check fails\r\n }\r\n }\r\n\r\n /**\r\n * Schedule re-initialization after font loads\r\n * @private\r\n */\r\n _scheduleInitAfterFontLoad(): void {\r\n if (typeof document === 'undefined' || !('fonts' in document)) {\r\n return;\r\n }\r\n \r\n // Only schedule if not already waiting\r\n if ((this as any)._fontLoadScheduled) {\r\n return;\r\n }\r\n (this as any)._fontLoadScheduled = true;\r\n \r\n const fontSpec = `${this.fontSize}px ${this.fontFamily}`;\r\n document.fonts.load(fontSpec).then(() => {\r\n (this as any)._fontLoadScheduled = false;\r\n // Re-initialize dimensions with proper font metrics\r\n this.initDimensions();\r\n \r\n // Extra step for justify alignment after font loading\r\n if (this.textAlign && this.textAlign.includes(JUSTIFY)) {\r\n setTimeout(() => {\r\n if (this.enlargeSpaces) {\r\n this.enlargeSpaces();\r\n }\r\n this.canvas?.requestRenderAll();\r\n }, 10);\r\n } else {\r\n this.canvas?.requestRenderAll();\r\n }\r\n }).catch(() => {\r\n (this as any)._fontLoadScheduled = false;\r\n });\r\n }\r\n\r\n /**\r\n * Force complete text re-initialization (useful after JSON loading)\r\n */\r\n forceTextReinitialization(): void {\r\n // Clear all caches\r\n this._clearCache();\r\n this.dirty = true;\r\n\r\n // Force text splitting to rebuild internal structures\r\n this._splitText();\r\n\r\n // Re-initialize dimensions\r\n this.initDimensions();\r\n\r\n // Special handling for justify alignment\r\n if (this.textAlign && this.textAlign.includes(JUSTIFY)) {\r\n setTimeout(() => {\r\n if (this.__charBounds && this.__charBounds.length > 0 && this.enlargeSpaces) {\r\n this.enlargeSpaces();\r\n this.canvas?.requestRenderAll();\r\n }\r\n }, 10);\r\n }\r\n }\r\n\r\n /**\r\n * Returns FabricText instance from an object representation\r\n * @param {Object} object plain js Object to create an instance from\r\n * @returns {Promise<FabricText>}\r\n */\r\n static fromObject<\r\n T extends TOptions<SerializedTextProps>,\r\n S extends FabricText,\r\n >(object: T) {\r\n return this._fromObject<S>(\r\n {\r\n ...object,\r\n styles: stylesFromArray(object.styles || {}, object.text),\r\n },\r\n {\r\n extraParam: 'text',\r\n },\r\n ).then((textObject: S) => {\r\n // Ensure text object is properly initialized after JSON deserialization\r\n textObject.initialized = true;\r\n\r\n // Force reinitialization to ensure proper layout\r\n if (textObject._clearCache) {\r\n textObject._clearCache();\r\n }\r\n textObject.dirty = true;\r\n\r\n const fontSpec = `${textObject.fontSize}px ${textObject.fontFamily}`;\r\n\r\n // For custom fonts, ensure they're loaded before initializing dimensions\r\n if (\r\n typeof document !== 'undefined' &&\r\n 'fonts' in document &&\r\n textObject.fontFamily !== 'Arial' &&\r\n textObject.fontFamily !== 'Times New Roman'\r\n ) {\r\n return document.fonts\r\n .load(fontSpec)\r\n .then(() => {\r\n textObject.initialized = true;\r\n\r\n // Special handling for STV fonts which have measurement issues\r\n const isStvFont = textObject.fontFamily\r\n ?.toLowerCase()\r\n .includes('stv');\r\n if (isStvFont) {\r\n (textObject as any)._browserWrapCache = null;\r\n (textObject as any)._lastDimensionState = null;\r\n (textObject as any)._browserWrapInitialized = false;\r\n (textObject as any)._usingBrowserWrapping = true;\r\n (textObject as any).useOverlayEditing = true;\r\n\r\n const reinitWithDelay = (attempt: number) => {\r\n if ((textObject as any).forceTextReinitialization) {\r\n (textObject as any).forceTextReinitialization();\r\n } else {\r\n textObject.initDimensions();\r\n }\r\n if (textObject.width < 50 && attempt < 3) {\r\n setTimeout(() => reinitWithDelay(attempt + 1), 100 * attempt);\r\n }\r\n };\r\n reinitWithDelay(0);\r\n } else {\r\n if ((textObject as any).forceTextReinitialization) {\r\n (textObject as any).forceTextReinitialization();\r\n } else {\r\n textObject.initDimensions();\r\n }\r\n }\r\n return textObject;\r\n })\r\n .catch(() => {\r\n textObject.initialized = true;\r\n if ((textObject as any).forceTextReinitialization) {\r\n (textObject as any).forceTextReinitialization();\r\n } else {\r\n textObject.initDimensions();\r\n }\r\n return textObject;\r\n });\r\n } else {\r\n textObject.initialized = true;\r\n if ((textObject as any).forceTextReinitialization) {\r\n (textObject as any).forceTextReinitialization();\r\n } else {\r\n textObject.initDimensions();\r\n }\r\n return textObject;\r\n }\r\n });\r\n }\r\n}\r\n\r\napplyMixins(FabricText, [TextSVGExportMixin]);\r\nclassRegistry.setClass(FabricText);\r\nclassRegistry.setSVGClass(FabricText);\r\n"],"names":["measuringContext","FabricText","StyledText","getDefaults","super","ownDefaults","constructor","text","options","_defineProperty","Object","assign","this","setOptions","styles","initialized","path","setPathInfo","initDimensions","setCoords","segmentsInfo","getPathSegmentsInfo","_splitText","browserLines","getBrowserLines","useOverlayEditing","_splitTextFromBrowserLines","newLines","_splitTextIntoLines","textLines","lines","_textLines","graphemeLines","_unwrappedTextLines","_unwrappedLines","_text","graphemeText","unwrappedLines","browserLine","push","lineGraphemes","graphemeSplit","concat","length","result","_isFontReady","_scheduleInitAfterFontLoad","enableAdvancedLayout","initDimensionsAdvanced","_clearCache","dirty","width","height","calcTextWidth","cursorWidth","MIN_TEXT_WIDTH","calcTextHeight","textAlign","includes","JUSTIFY","__charBounds","enlargeSpaces","diffSpace","currentLineWidth","numberOfSpaces","accumulatedSpace","line","charBound","spaces","i","len","hasTextAfter","slice","some","lineText","Array","isArray","join","test","isVisualLastLine","isLastLine","isEndOfWrapping","getLineWidth","match","_reSpacesAndTabs","j","_reSpaceAndTab","kernedWidth","left","_layoutTextAdvanced","_getAdvancedLayoutOptions","layoutText","wrap","align","_mapTextAlignToAlign","ellipsis","fontSize","lineHeight","letterSpacing","charSpacing","direction","fontFamily","fontStyle","fontWeight","verticalAlign","CENTER","RIGHT","JUSTIFY_LEFT","JUSTIFY_RIGHT","JUSTIFY_CENTER","layout","totalWidth","totalHeight","_convertLayoutToLegacyFormat","map","graphemes","bounds","bound","top","y","deltaY","lineIndex","missingNewlineOffset","_lineIndex","get2DCursorLocation","selectionStart","skipWrapping","charIndex","toString","complexity","_getCacheCanvasDimensions","dims","zoomX","zoomY","_render","ctx","isNotVisible","_setTextStyles","_renderTextLinesBackground","_renderTextDecoration","_renderText","__overlayEditor","paintFirst","STROKE","_renderTextStroke","_renderTextFill","charStyle","forMeasuring","textBaseline","pathAlign","TOP","BOTTOM","font","_getFontDeclaration","maxWidth","_renderTextLine","method","_renderChars","textBackgroundColor","styleHas","originalFill","fillStyle","leftOffset","_getLeftOffset","lineTopOffset","_getTopOffset","heightOfLine","getHeightOfLine","jlen","lineLeftOffset","_getLineLeftOffset","drawStart","currentColor","boxWidth","boxStart","lastColor","getValueOfPropertyAt","charBox","save","translate","renderLeft","renderTop","rotate","angle","fillRect","_fontSizeFraction","restore","_removeShadow","_measureChar","_char","previousChar","prevCharStyle","fontCache","cache","getFontCache","fontDeclaration","couple","stylesAreEqual","fontMultiplier","CACHE_FONT_SIZE","coupleWidth","previousWidth","undefined","canvas","createCanvasElementFor","getContext","getMeasuringContext","measureText","getHeightOfChar","measureLine","lineInfo","_measureLine","_getWidthOfCharSpacing","_justifyApplied","console","warn","trace","prevGrapheme","graphemeInfo","reverse","pathSide","llength","lineBounds","grapheme","_getGraphemeBox","positionInPath","totalPathLength","LEFT","pathStartOffset","_setGraphemeOnPath","numOfSpaces","centerPosition","info","getPointOnPath","x","pathOffset","Math","PI","skipLeft","style","getCompleteStyleDeclaration","prevStyle","box","previousBox","__lineHeights","maxHeight","max","_fontSizeMult","_renderTextCommon","_this$textAlign","lineHeights","log","_this$textAlign2","lineWidth","toFixed","fill","FILL","stroke","strokeWidth","isEmptyStyles","shadow","affectStroke","_setLineDash","strokeDashArray","beginPath","closePath","isJustify","shortCut","isLtr","sign","currentDirection","actualStyle","nextStyle","timeToRender","drawingLeft","charsToRender","setAttribute","_renderChar","totalKW","reduce","s","b","forEach","idx","_b$kernedWidth","hasStyleChanged","_applyPatternGradientTransformText","filler","pCanvas","pCtx","moveTo","lineTo","toLive","_applyPatternGradientTransform","createPattern","handleFiller","property","offsetX","offsetY","isFiller","gradientUnits","gradientTransform","patternTransform","_setStrokeStyles","_ref","lineCap","strokeLineCap","lineDashOffset","strokeDashOffset","lineJoin","strokeLineJoin","miterLimit","strokeMiterLimit","_setFillStyles","_ref2","decl","_getStyleDeclaration","fullDecl","shouldFill","shouldStroke","fillOffsets","fillText","strokeOffsets","strokeText","setSuperscript","start","end","_setScript","superscript","setSubscript","subscript","schema","loc","dy","size","baseline","setSelectionStyles","lineDiff","_forceClearCache","__lineWidths","_charStyle$property","type","topOffset","offsetAligner","offsets","lastDecoration","lastFill","lastTickness","TEXT_DECORATION_THICKNESS","currentDecoration","currentFill","currentTickness","currentSize","currentDy","finalTickness","arguments","parsedFontFamily","genericFonts","toLowerCase","render","visible","skipOffscreen","group","isOnScreen","value","split","_reNewline","newLine","newText","_containsArabicText","segmentGraphemes","pop","toObject","propertiesToInclude","additionalProps","stylesToArray","set","key","textLayoutProperties","needsDims","isAddingPath","_key","clearBrowserLines","fromElement","element","cssRules","parsedAttributes","parseAttributes","ATTRIBUTE_NAMES","textAnchor","textDecoration","dx","DEFAULT_SVG_FONT_SIZE","restOfOptions","textContent","replace","underline","overline","linethrough","textHeightScaleFactor","getScaledHeight","scaledDiff","textHeight","offX","getScaledWidth","document","fonts","check","e","_fontLoadScheduled","fontSpec","load","then","_this$canvas2","setTimeout","_this$canvas","requestRenderAll","catch","forceTextReinitialization","_this$canvas3","fromObject","object","_fromObject","stylesFromArray","extraParam","textObject","_textObject$fontFamil","_browserWrapCache","_lastDimensionState","_browserWrapInitialized","_usingBrowserWrapping","reinitWithDelay","attempt","cacheProperties","textDefaultValues","SHARED_ATTRIBUTES","applyMixins","TextSVGExportMixin","classRegistry","setClass","setSVGClass"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Text.min.mjs","sources":["../../../../src/shapes/Text/Text.ts"],"sourcesContent":["import { cache } from '../../cache';\r\nimport { DEFAULT_SVG_FONT_SIZE, FILL, STROKE } from '../../constants';\r\nimport type { ObjectEvents } from '../../EventTypeDefs';\r\nimport type {\r\n CompleteTextStyleDeclaration,\r\n TextStyle,\r\n TextStyleDeclaration,\r\n} from './StyledText';\r\nimport { StyledText } from './StyledText';\r\nimport { SHARED_ATTRIBUTES } from '../../parser/attributes';\r\nimport { parseAttributes } from '../../parser/parseAttributes';\r\nimport type {\r\n Abortable,\r\n TCacheCanvasDimensions,\r\n TClassProperties,\r\n TFiller,\r\n TOptions,\r\n} from '../../typedefs';\r\nimport { classRegistry } from '../../ClassRegistry';\r\nimport { graphemeSplit } from '../../util/lang_string';\r\nimport { createCanvasElementFor } from '../../util/misc/dom';\r\nimport { layoutText, type LayoutResult, type TextLayoutOptions } from '../../text/layout';\r\nimport { measureGrapheme, measureGraphemeWithKerning } from '../../text/measure';\r\nimport { applyEllipsis } from '../../text/ellipsis';\r\nimport { segmentGraphemes, findKashidaPoints, ARABIC_TATWEEL, type KashidaPoint } from '../../text/unicode';\r\nimport type { TextStyleArray } from '../../util/misc/textStyles';\r\nimport {\r\n hasStyleChanged,\r\n stylesFromArray,\r\n stylesToArray,\r\n} from '../../util/misc/textStyles';\r\nimport { getPathSegmentsInfo, getPointOnPath } from '../../util/path';\r\nimport { cacheProperties } from '../Object/FabricObject';\r\nimport type { Path } from '../Path';\r\nimport { TextSVGExportMixin } from './TextSVGExportMixin';\r\nimport { applyMixins } from '../../util/applyMixins';\r\nimport type { FabricObjectProps, SerializedObjectProps } from '../Object/types';\r\nimport type { StylePropertiesType } from './constants';\r\nimport {\r\n additionalProps,\r\n textDefaultValues,\r\n textLayoutProperties,\r\n JUSTIFY,\r\n JUSTIFY_CENTER,\r\n JUSTIFY_LEFT,\r\n JUSTIFY_RIGHT,\r\n TEXT_DECORATION_THICKNESS,\r\n} from './constants';\r\nimport { CENTER, LEFT, RIGHT, TOP, BOTTOM } from '../../constants';\r\nimport { isFiller } from '../../util/typeAssertions';\r\nimport type { Gradient } from '../../gradient/Gradient';\r\nimport type { Pattern } from '../../Pattern';\r\nimport type { CSSRules } from '../../parser/typedefs';\r\nimport { getBrowserLines, clearBrowserLines } from '../../text/browserLines';\r\nimport type { BrowserLine } from '../../text/browserLines';\r\n\r\nlet measuringContext: CanvasRenderingContext2D | null;\r\n\r\n/**\r\n * Return a context for measurement of text string.\r\n * if created it gets stored for reuse\r\n */\r\nfunction getMeasuringContext() {\r\n if (!measuringContext) {\r\n const canvas = createCanvasElementFor({\r\n width: 0,\r\n height: 0,\r\n });\r\n measuringContext = canvas.getContext('2d');\r\n }\r\n return measuringContext;\r\n}\r\n\r\nexport type TPathSide = 'left' | 'right';\r\n\r\nexport type TPathAlign = 'baseline' | 'center' | 'ascender' | 'descender';\r\n\r\nexport type TextLinesInfo = {\r\n lines: string[];\r\n graphemeLines: string[][];\r\n graphemeText: string[];\r\n _unwrappedLines: string[][];\r\n};\r\n\r\n/**\r\n * Measure and return the info of a single grapheme.\r\n * needs the the info of previous graphemes already filled\r\n * Override to customize measuring\r\n */\r\nexport type GraphemeBBox = {\r\n width: number;\r\n height: number;\r\n kernedWidth: number;\r\n left: number;\r\n deltaY: number;\r\n renderLeft?: number;\r\n renderTop?: number;\r\n angle?: number;\r\n};\r\n\r\n// @TODO this is not complete\r\ninterface UniqueTextProps {\r\n charSpacing: number;\r\n lineHeight: number;\r\n fontSize: number;\r\n fontWeight: string | number;\r\n fontFamily: string;\r\n fontStyle: string;\r\n pathSide: TPathSide;\r\n pathAlign: TPathAlign;\r\n underline: boolean;\r\n overline: boolean;\r\n linethrough: boolean;\r\n textAlign: string;\r\n direction: CanvasDirection;\r\n path?: Path;\r\n textDecorationThickness: number;\r\n wrap: 'word' | 'char' | 'none';\r\n ellipsis: boolean | string;\r\n letterSpacing: number;\r\n enableAdvancedLayout: boolean;\r\n verticalAlign: 'top' | 'middle' | 'bottom';\r\n useOverlayEditing: boolean;\r\n kashida: 'none' | 'short' | 'medium' | 'long' | 'stylistic';\r\n}\r\n\r\nexport interface SerializedTextProps\r\n extends SerializedObjectProps,\r\n UniqueTextProps {\r\n styles: TextStyleArray | TextStyle;\r\n}\r\n\r\nexport interface TextProps extends FabricObjectProps, UniqueTextProps {\r\n styles: TextStyle;\r\n}\r\n\r\n/**\r\n * Text class\r\n * @see {@link http://fabricjs.com/fabric-intro-part-2#text}\r\n */\r\nexport class FabricText<\r\n Props extends TOptions<TextProps> = Partial<TextProps>,\r\n SProps extends SerializedTextProps = SerializedTextProps,\r\n EventSpec extends ObjectEvents = ObjectEvents,\r\n>\r\n extends StyledText<Props, SProps, EventSpec>\r\n implements UniqueTextProps {\r\n /**\r\n * Properties that requires a text layout recalculation when changed\r\n * @type string[]\r\n * @protected\r\n */\r\n static textLayoutProperties: string[] = textLayoutProperties;\r\n\r\n /**\r\n * @private\r\n */\r\n declare _reNewline: RegExp;\r\n\r\n /**\r\n * Use this regular expression to filter for whitespaces that is not a new line.\r\n * Mostly used when text is 'justify' aligned.\r\n * @private\r\n */\r\n declare _reSpacesAndTabs: RegExp;\r\n\r\n /**\r\n * Use this regular expression to filter for whitespace that is not a new line.\r\n * Mostly used when text is 'justify' aligned.\r\n * @private\r\n */\r\n declare _reSpaceAndTab: RegExp;\r\n\r\n /**\r\n * Use this regular expression to filter consecutive groups of non spaces.\r\n * Mostly used when text is 'justify' aligned.\r\n * @private\r\n */\r\n declare _reWords: RegExp;\r\n\r\n declare text: string;\r\n\r\n /**\r\n * Font size (in pixels)\r\n * @type Number\r\n */\r\n declare fontSize: number;\r\n\r\n /**\r\n * Font weight (e.g. bold, normal, 400, 600, 800)\r\n * @type {(Number|String)}\r\n */\r\n declare fontWeight: string | number;\r\n\r\n /**\r\n * Font family\r\n * @type String\r\n */\r\n declare fontFamily: string;\r\n\r\n /**\r\n * Text decoration underline.\r\n * @type Boolean\r\n */\r\n declare underline: boolean;\r\n\r\n /**\r\n * Text decoration overline.\r\n * @type Boolean\r\n */\r\n declare overline: boolean;\r\n\r\n /**\r\n * Text decoration linethrough.\r\n * @type Boolean\r\n */\r\n declare linethrough: boolean;\r\n\r\n /**\r\n * Text alignment. Possible values: \"left\", \"center\", \"right\", \"justify\",\r\n * \"justify-left\", \"justify-center\" or \"justify-right\".\r\n * @type String\r\n */\r\n declare textAlign: string;\r\n\r\n /**\r\n * Font style . Possible values: \"\", \"normal\", \"italic\" or \"oblique\".\r\n * @type String\r\n */\r\n declare fontStyle: string;\r\n\r\n /**\r\n * Line height\r\n * @type Number\r\n */\r\n declare lineHeight: number;\r\n\r\n /**\r\n * Superscript schema object (minimum overlap)\r\n */\r\n declare superscript: {\r\n /**\r\n * fontSize factor\r\n * @default 0.6\r\n */\r\n size: number;\r\n /**\r\n * baseline-shift factor (upwards)\r\n * @default -0.35\r\n */\r\n baseline: number;\r\n };\r\n\r\n /**\r\n * Subscript schema object (minimum overlap)\r\n */\r\n declare subscript: {\r\n /**\r\n * fontSize factor\r\n * @default 0.6\r\n */\r\n size: number;\r\n /**\r\n * baseline-shift factor (downwards)\r\n * @default 0.11\r\n */\r\n baseline: number;\r\n };\r\n\r\n /**\r\n * Background color of text lines\r\n * @type String\r\n */\r\n declare textBackgroundColor: string;\r\n\r\n declare styles: TextStyle;\r\n\r\n /**\r\n * Path that the text should follow.\r\n * since 4.6.0 the path will be drawn automatically.\r\n * if you want to make the path visible, give it a stroke and strokeWidth or fill value\r\n * if you want it to be hidden, assign visible = false to the path.\r\n * This feature is in BETA, and SVG import/export is not yet supported.\r\n * @type Path\r\n * @example\r\n * const textPath = new Text('Text on a path', {\r\n * top: 150,\r\n * left: 150,\r\n * textAlign: 'center',\r\n * charSpacing: -50,\r\n * path: new Path('M 0 0 C 50 -100 150 -100 200 0', {\r\n * strokeWidth: 1,\r\n * visible: false\r\n * }),\r\n * pathSide: 'left',\r\n * pathStartOffset: 0\r\n * });\r\n */\r\n declare path?: Path;\r\n\r\n /**\r\n * Text wrapping mode\r\n * @type string\r\n * @default 'word'\r\n */\r\n declare wrap: 'word' | 'char' | 'none';\r\n\r\n /**\r\n * Ellipsis truncation\r\n * @type boolean | string\r\n * @default false\r\n */\r\n declare ellipsis: boolean | string;\r\n\r\n /**\r\n * Letter spacing in pixels (Konva-style)\r\n * @type number\r\n * @default 0\r\n */\r\n declare letterSpacing: number;\r\n\r\n /**\r\n * Enable advanced text layout engine\r\n * @type boolean\r\n * @default false\r\n */\r\n declare enableAdvancedLayout: boolean;\r\n\r\n /**\r\n * Vertical text alignment\r\n * @type string\r\n * @default 'top'\r\n */\r\n declare verticalAlign: 'top' | 'middle' | 'bottom';\r\n\r\n /**\r\n * Use overlay editor for inline text editing instead of hidden textarea.\r\n * @default false\r\n */\r\n declare useOverlayEditing: boolean;\r\n\r\n /**\r\n * Arabic kashida (tatweel) justification level.\r\n * Distributes extra space using kashida extensions for justified Arabic text.\r\n * - 'none': No kashida (default), uses only space expansion\r\n * - 'short': 25% kashida, 75% space expansion\r\n * - 'medium': 50% kashida, 50% space expansion\r\n * - 'long': 75% kashida, 25% space expansion\r\n * - 'stylistic': 100% kashida, no space expansion\r\n * @type string\r\n * @default 'none'\r\n */\r\n declare kashida: 'none' | 'short' | 'medium' | 'long' | 'stylistic';\r\n\r\n /**\r\n * The text decoration tickness for underline, overline and strikethrough\r\n * The tickness is expressed in thousandths of fontSize ( em ).\r\n * The original value was 1/15 that translates to 66.6667 thousandths.\r\n * The choice of unit of measure is to align with charSpacing.\r\n * You can slim the tickness without issues, while large underline or overline may end up\r\n * outside the bounding box of the text. In order to fix that a bigger refactor of the code\r\n * is needed and is out of scope for now. If you need such large overline on the first line\r\n * of text or large underline on the last line of text, consider disabling caching as a\r\n * workaround\r\n * @default 66.667\r\n */\r\n declare textDecorationThickness: number;\r\n\r\n /**\r\n * Offset amount for text path starting position\r\n * Only used when text has a path\r\n */\r\n declare pathStartOffset: number;\r\n\r\n /**\r\n * Which side of the path the text should be drawn on.\r\n * Only used when text has a path\r\n * @type {TPathSide} 'left|right'\r\n */\r\n declare pathSide: TPathSide;\r\n\r\n /**\r\n * How text is aligned to the path. This property determines\r\n * the perpendicular position of each character relative to the path.\r\n * (one of \"baseline\", \"center\", \"ascender\", \"descender\")\r\n * This feature is in BETA, and its behavior may change\r\n * @type TPathAlign\r\n */\r\n declare pathAlign: TPathAlign;\r\n\r\n /**\r\n * @private\r\n */\r\n declare _fontSizeFraction: number;\r\n\r\n /**\r\n * @private\r\n */\r\n declare offsets: { underline: number; linethrough: number; overline: number };\r\n\r\n /**\r\n * Text Line proportion to font Size (in pixels)\r\n * @type Number\r\n */\r\n declare _fontSizeMult: number;\r\n\r\n /**\r\n * additional space between characters\r\n * expressed in thousands of em unit\r\n * @type Number\r\n */\r\n declare charSpacing: number;\r\n\r\n /**\r\n * Baseline shift, styles only, keep at 0 for the main text object\r\n * @type {Number}\r\n */\r\n declare deltaY: number;\r\n\r\n /**\r\n * WARNING: EXPERIMENTAL. NOT SUPPORTED YET\r\n * determine the direction of the text.\r\n * This has to be set manually together with textAlign and originX for proper\r\n * experience.\r\n * some interesting link for the future\r\n * https://www.w3.org/International/questions/qa-bidi-unicode-controls\r\n * @since 4.5.0\r\n * @type {CanvasDirection} 'ltr|rtl'\r\n */\r\n declare direction: CanvasDirection;\r\n\r\n /**\r\n * contains characters bounding boxes\r\n * This variable is considered to be protected.\r\n * But for how mixins are implemented right now, we can't leave it private\r\n * @protected\r\n */\r\n __charBounds: GraphemeBBox[][] = [];\r\n\r\n /**\r\n * contains kashida extension info for each line.\r\n * Each entry contains { charIndex, width } for characters that have kashida extensions.\r\n * @protected\r\n */\r\n __kashidaInfo: Array<Array<{ charIndex: number; width: number; tatweelCount?: number }>> = [];\r\n\r\n /**\r\n * use this size when measuring text. To avoid IE11 rounding errors\r\n * @type {Number}\r\n * @readonly\r\n * @private\r\n */\r\n declare CACHE_FONT_SIZE: number;\r\n\r\n /**\r\n * contains the min text width to avoid getting 0\r\n * @type {Number}\r\n */\r\n declare MIN_TEXT_WIDTH: number;\r\n\r\n /**\r\n * contains the the text of the object, divided in lines as they are displayed\r\n * on screen. Wrapping will divide the text independently of line breaks\r\n * @type {string[]}\r\n */\r\n declare textLines: string[];\r\n\r\n /**\r\n * same as textlines, but each line is an array of graphemes as split by splitByGrapheme\r\n * @type {string[]}\r\n */\r\n declare _textLines: string[][];\r\n\r\n declare _unwrappedTextLines: string[][];\r\n declare _text: string[];\r\n declare cursorWidth: number;\r\n declare __lineHeights: number[];\r\n declare __lineWidths: number[];\r\n declare initialized?: true;\r\n\r\n static cacheProperties = [...cacheProperties, ...additionalProps];\r\n\r\n static ownDefaults = textDefaultValues;\r\n\r\n static type = 'Text';\r\n\r\n static getDefaults(): Record<string, any> {\r\n return { ...super.getDefaults(), ...FabricText.ownDefaults };\r\n }\r\n\r\n constructor(text: string, options?: Props) {\r\n super();\r\n Object.assign(this, FabricText.ownDefaults);\r\n this.setOptions(options);\r\n if (!this.styles) {\r\n this.styles = {};\r\n }\r\n this.text = text;\r\n this.initialized = true;\r\n if (this.path) {\r\n this.setPathInfo();\r\n }\r\n this.initDimensions();\r\n this.setCoords();\r\n }\r\n\r\n /**\r\n * If text has a path, it will add the extra information needed\r\n * for path and text calculations\r\n */\r\n setPathInfo() {\r\n const path = this.path;\r\n if (path) {\r\n path.segmentsInfo = getPathSegmentsInfo(path.path);\r\n }\r\n }\r\n\r\n /**\r\n * @private\r\n * Divides text into lines of text and lines of graphemes.\r\n * Uses browser lines when available for pixel-perfect consistency.\r\n */\r\n _splitText(): TextLinesInfo {\r\n // Check if we have valid browser lines and should use them\r\n const browserLines = getBrowserLines(this);\r\n if (browserLines && this.useOverlayEditing) {\r\n return this._splitTextFromBrowserLines(browserLines);\r\n }\r\n\r\n const newLines = this._splitTextIntoLines(this.text);\r\n this.textLines = newLines.lines;\r\n this._textLines = newLines.graphemeLines;\r\n this._unwrappedTextLines = newLines._unwrappedLines;\r\n this._text = newLines.graphemeText;\r\n return newLines;\r\n }\r\n\r\n /**\r\n * Create TextLinesInfo from browser-extracted lines\r\n * @private\r\n */\r\n _splitTextFromBrowserLines(browserLines: BrowserLine[]): TextLinesInfo {\r\n const lines: string[] = [];\r\n const graphemeLines: string[][] = [];\r\n const unwrappedLines: string[][] = [];\r\n let graphemeText: string[] = [];\r\n\r\n for (const browserLine of browserLines) {\r\n lines.push(browserLine.text);\r\n const lineGraphemes = this.graphemeSplit(browserLine.text);\r\n graphemeLines.push(lineGraphemes);\r\n unwrappedLines.push(lineGraphemes);\r\n graphemeText = graphemeText.concat(lineGraphemes);\r\n\r\n // Add newline separator between lines (except for the last line)\r\n if (browserLine !== browserLines[browserLines.length - 1]) {\r\n graphemeText.push('\\n');\r\n }\r\n }\r\n\r\n const result: TextLinesInfo = {\r\n lines,\r\n graphemeLines,\r\n graphemeText,\r\n _unwrappedLines: unwrappedLines,\r\n };\r\n\r\n // Update instance properties\r\n this.textLines = result.lines;\r\n this._textLines = result.graphemeLines;\r\n this._unwrappedTextLines = result._unwrappedLines;\r\n this._text = result.graphemeText;\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Initialize or update text dimensions.\r\n * Updates this.width and this.height with the proper values.\r\n * Does not return dimensions.\r\n */\r\n initDimensions(): void {\r\n // Check if font is ready for accurate measurements\r\n // Only block initialization if it's a critical font loading situation\r\n const fontReady = this._isFontReady();\r\n if (!fontReady && !this.initialized) {\r\n // Only schedule font loading on first initialization\r\n this._scheduleInitAfterFontLoad();\r\n // Continue with fallback measurements for now\r\n }\r\n\r\n // Use advanced layout if enabled\r\n if (this.enableAdvancedLayout && !this.path) {\r\n return this.initDimensionsAdvanced();\r\n }\r\n\r\n this._splitText();\r\n this._clearCache();\r\n this.dirty = true;\r\n if (this.path) {\r\n this.width = this.path.width;\r\n this.height = this.path.height;\r\n } else {\r\n this.width =\r\n this.calcTextWidth() || this.cursorWidth || this.MIN_TEXT_WIDTH;\r\n this.height = this.calcTextHeight();\r\n }\r\n if (this.textAlign.includes(JUSTIFY)) {\r\n // once text is measured we need to make space fatter to make justified text.\r\n if (this.__charBounds && this.__charBounds.length > 0) {\r\n this.enlargeSpaces();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Enlarge space boxes and shift the others for justify alignment.\r\n * Supports Arabic kashida (tatweel) justification when kashida property is set.\r\n * When kashida is enabled, actual tatweel characters are inserted into the text.\r\n */\r\n enlargeSpaces() {\r\n // console.log('=== enlargeSpaces START ===');\r\n // console.log('this.kashida:', this.kashida);\r\n\r\n // Kashida ratios: proportion of extra space distributed via kashida vs space expansion\r\n const kashidaRatios: Record<string, number> = {\r\n none: 0,\r\n short: 0.25,\r\n medium: 0.5,\r\n long: 0.75,\r\n stylistic: 1.0,\r\n };\r\n const kashidaRatio = kashidaRatios[this.kashida] || 0;\r\n // console.log('kashidaRatio:', kashidaRatio);\r\n\r\n // Reset kashida info\r\n this.__kashidaInfo = [];\r\n\r\n for (let i = 0, len = this._textLines.length; i < len; i++) {\r\n // Initialize kashida info for this line\r\n this.__kashidaInfo[i] = [];\r\n\r\n // Check if this line should be justified\r\n const hasTextAfter = this._textLines\r\n .slice(i + 1)\r\n .some((line) => {\r\n const lineText = Array.isArray(line) ? line.join('') : line;\r\n return /\\S/.test(lineText);\r\n });\r\n const isVisualLastLine = !hasTextAfter;\r\n const isLastLine =\r\n i === len - 1 || this.isEndOfWrapping(i) || isVisualLastLine;\r\n const shouldJustifyLine =\r\n this.textAlign.includes('justify') && !isLastLine;\r\n\r\n if (!shouldJustifyLine) {\r\n // console.log(` Line ${i}: skipped (not justified)`);\r\n continue;\r\n }\r\n\r\n const line = this._textLines[i];\r\n const currentLineWidth = this.getLineWidth(i);\r\n const totalExtraSpace = this.width - currentLineWidth;\r\n // console.log(` Line ${i}: width=${this.width}, lineWidth=${currentLineWidth}, extraSpace=${totalExtraSpace}`);\r\n\r\n if (totalExtraSpace <= 0) {\r\n // console.log(` Line ${i}: skipped (no extra space)`);\r\n continue;\r\n }\r\n\r\n // Find spaces for space expansion\r\n const spaces = this.textLines[i].match(this._reSpacesAndTabs);\r\n const numberOfSpaces = spaces ? spaces.length : 0;\r\n\r\n // Find kashida points if enabled\r\n const kashidaPoints = kashidaRatio > 0 ? findKashidaPoints(line) : [];\r\n const hasKashidaPoints = kashidaPoints.length > 0;\r\n\r\n // Calculate space distribution\r\n let kashidaSpace = 0;\r\n let spaceExpansion = totalExtraSpace;\r\n\r\n if (hasKashidaPoints && kashidaRatio > 0) {\r\n // Distribute between kashida and spaces\r\n kashidaSpace = totalExtraSpace * kashidaRatio;\r\n spaceExpansion = totalExtraSpace * (1 - kashidaRatio);\r\n }\r\n\r\n // Calculate per-kashida and per-space widths\r\n const perKashidaWidth = hasKashidaPoints ? kashidaSpace / kashidaPoints.length : 0;\r\n const perSpaceWidth = numberOfSpaces > 0 ? spaceExpansion / numberOfSpaces : 0;\r\n\r\n // If kashida is enabled, insert tatweel characters into the text\r\n if (hasKashidaPoints && perKashidaWidth > 0) {\r\n // console.log(`=== Inserting kashida for line ${i} ===`);\r\n // console.log(` kashidaPoints: ${kashidaPoints.length}, perKashidaWidth: ${perKashidaWidth}`);\r\n\r\n // Sort by charIndex descending to insert from end (so indices stay valid)\r\n const sortedPoints = [...kashidaPoints].sort((a, b) => b.charIndex - a.charIndex);\r\n\r\n // Calculate how many tatweels to insert per point\r\n // Measure tatweel width to determine count\r\n const ctx = getMeasuringContext();\r\n // console.log(` getMeasuringContext: ${ctx ? 'OK' : 'NULL'}`);\r\n\r\n if (ctx) {\r\n ctx.font = this._getFontDeclaration();\r\n const tatweelWidth = ctx.measureText(ARABIC_TATWEEL).width;\r\n // console.log(` tatweelWidth: ${tatweelWidth}`);\r\n\r\n if (tatweelWidth > 0) {\r\n const newLine = [...line];\r\n let insertedCount = 0;\r\n\r\n for (const point of sortedPoints) {\r\n const tatweelCount = Math.max(1, Math.round(perKashidaWidth / tatweelWidth));\r\n // console.log(` Point ${point.charIndex}: inserting ${tatweelCount} tatweels`);\r\n\r\n // Insert tatweels after the character\r\n for (let t = 0; t < tatweelCount; t++) {\r\n newLine.splice(point.charIndex + 1, 0, ARABIC_TATWEEL);\r\n insertedCount++;\r\n }\r\n\r\n // Store kashida info with updated indices and tatweel count\r\n this.__kashidaInfo[i].push({\r\n charIndex: point.charIndex,\r\n width: perKashidaWidth,\r\n tatweelCount: tatweelCount,\r\n });\r\n }\r\n\r\n // console.log(` Total inserted: ${insertedCount} tatweels`);\r\n // console.log(` Original line length: ${line.length}, new line length: ${newLine.length}`);\r\n // console.log(` New line: ${newLine.join('')}`);\r\n\r\n // Update _textLines with the new line containing tatweels\r\n this._textLines[i] = newLine;\r\n\r\n // Update textLines string version\r\n if (this.textLines && this.textLines[i] !== undefined) {\r\n (this as any).textLines[i] = newLine.join('');\r\n }\r\n\r\n // Recalculate charBounds for this line since text changed\r\n this.__charBounds[i] = [];\r\n this.__lineWidths[i] = undefined as any;\r\n this._measureLine(i);\r\n\r\n // console.log(` After remeasure, lineWidth: ${this.__lineWidths[i]}`);\r\n }\r\n }\r\n }\r\n\r\n // Now apply space expansion to remaining extra space\r\n const newLineWidth = this.getLineWidth(i);\r\n const remainingSpace = this.width - newLineWidth;\r\n\r\n if (remainingSpace > 0 && numberOfSpaces > 0) {\r\n const extraPerSpace = remainingSpace / numberOfSpaces;\r\n let accumulatedOffset = 0;\r\n\r\n for (let j = 0; j < this._textLines[i].length; j++) {\r\n const charBound = this.__charBounds[i][j];\r\n if (!charBound) continue;\r\n\r\n charBound.left += accumulatedOffset;\r\n\r\n if (this._reSpaceAndTab.test(this._textLines[i][j])) {\r\n charBound.width += extraPerSpace;\r\n charBound.kernedWidth += extraPerSpace;\r\n accumulatedOffset += extraPerSpace;\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Final debug log showing kashida state\r\n // console.log('=== enlargeSpaces END ===');\r\n // console.log('Final __kashidaInfo:', JSON.stringify(this.__kashidaInfo.map((lineInfo, i) => ({\r\n // line: i,\r\n // entries: lineInfo.map(k => ({ charIndex: k.charIndex, tatweelCount: k.tatweelCount }))\r\n // }))));\r\n }\r\n\r\n /**\r\n * Advanced layout using new text engine (Konva-compatible)\r\n * @private\r\n */\r\n _layoutTextAdvanced(): LayoutResult {\r\n const options = this._getAdvancedLayoutOptions();\r\n return layoutText(options);\r\n }\r\n\r\n /**\r\n * Get advanced layout options from current text properties\r\n * @private\r\n */\r\n _getAdvancedLayoutOptions(): TextLayoutOptions {\r\n return {\r\n text: this.text,\r\n width: this.width,\r\n // Don't pass height constraint to allow vertical auto-expansion\r\n // Only pass height if ellipsis is enabled (need to truncate)\r\n height: this.ellipsis ? this.height : undefined,\r\n wrap: this.wrap || 'word',\r\n align: this._mapTextAlignToAlign(this.textAlign),\r\n ellipsis: this.ellipsis || false,\r\n fontSize: this.fontSize,\r\n lineHeight: this.lineHeight,\r\n letterSpacing: this.letterSpacing || 0,\r\n charSpacing: this.charSpacing,\r\n direction: this.direction === 'inherit' ? 'ltr' : this.direction,\r\n fontFamily: this.fontFamily,\r\n fontStyle: this.fontStyle,\r\n fontWeight: this.fontWeight,\r\n verticalAlign: this.verticalAlign || 'top',\r\n };\r\n }\r\n\r\n /**\r\n * Map Fabric textAlign to Konva align format\r\n * @private\r\n */\r\n _mapTextAlignToAlign(textAlign: string): 'left' | 'center' | 'right' | 'justify' {\r\n switch (textAlign) {\r\n case 'center':\r\n case CENTER:\r\n return 'center';\r\n case 'right':\r\n case RIGHT:\r\n return 'right';\r\n case 'justify':\r\n case JUSTIFY:\r\n case JUSTIFY_LEFT:\r\n case JUSTIFY_RIGHT:\r\n case JUSTIFY_CENTER:\r\n return 'justify';\r\n default:\r\n return 'left';\r\n }\r\n }\r\n\r\n /**\r\n * Enhanced initDimensions that uses advanced layout when enabled\r\n */\r\n initDimensionsAdvanced(): void {\r\n if (!this.enableAdvancedLayout) {\r\n return this.initDimensions();\r\n }\r\n\r\n const layout = this._layoutTextAdvanced();\r\n\r\n // Update dimensions from layout\r\n this.width = layout.totalWidth || this.MIN_TEXT_WIDTH;\r\n this.height = layout.totalHeight;\r\n\r\n // Convert layout to legacy format for compatibility\r\n this._convertLayoutToLegacyFormat(layout);\r\n\r\n // Apply kashida if enabled for justify alignment\r\n // This must be called after _convertLayoutToLegacyFormat to ensure __charBounds exists\r\n if (this.textAlign.includes(JUSTIFY) && this.kashida && this.kashida !== 'none') {\r\n if (this.__charBounds && this.__charBounds.length > 0) {\r\n this.enlargeSpaces();\r\n }\r\n }\r\n\r\n this.dirty = true;\r\n }\r\n\r\n /**\r\n * Convert new layout format to legacy _textLines and __charBounds format\r\n * @private\r\n */\r\n _convertLayoutToLegacyFormat(layout: LayoutResult): void {\r\n this._textLines = layout.lines.map(line => line.graphemes);\r\n (this as any).textLines = layout.lines.map(line => line.text);\r\n\r\n // Set _text as flat array of all graphemes (required for editing)\r\n this._text = layout.lines.flatMap(line => line.graphemes);\r\n\r\n // Convert bounds to legacy format\r\n // IMPORTANT: Preserve both logical (left) and visual (renderLeft) positions\r\n // - left: cumulative logical offset (for text editing operations)\r\n // - renderLeft: actual visual X position after BiDi reordering and alignment\r\n // The renderLeft is critical for correct cursor/selection hit testing in mixed RTL/LTR text\r\n this.__charBounds = layout.lines.map(line =>\r\n line.bounds.map(bound => ({\r\n left: bound.left,\r\n top: bound.y,\r\n width: bound.width,\r\n height: bound.height,\r\n kernedWidth: bound.kernedWidth,\r\n deltaY: bound.deltaY || 0,\r\n renderLeft: bound.x, // Visual X position for hit testing\r\n }))\r\n );\r\n\r\n // Populate line widths cache to prevent getLineWidth from triggering legacy measurement\r\n this.__lineWidths = layout.lines.map(line => line.width);\r\n\r\n // Update grapheme info for compatibility\r\n if (layout.lines.length > 0) {\r\n (this as any)._unwrappedTextLines = layout.lines.map(line => line.graphemes);\r\n }\r\n }\r\n\r\n /**\r\n * Detect if the text line is ended with an hard break\r\n * text and itext do not have wrapping, return false\r\n * @return {Boolean}\r\n */\r\n isEndOfWrapping(lineIndex: number): boolean {\r\n return lineIndex === this._textLines.length - 1;\r\n }\r\n\r\n /**\r\n * Detect if a line has a linebreak and so we need to account for it when moving\r\n * and counting style.\r\n * It return always 1 for text and Itext. Textbox has its own implementation\r\n * @return Number\r\n */\r\n missingNewlineOffset(lineIndex: number, skipWrapping?: boolean): 0 | 1;\r\n missingNewlineOffset(_lineIndex: number): 1 {\r\n return 1;\r\n }\r\n\r\n /**\r\n * Returns 2d representation (lineIndex and charIndex) of cursor\r\n * @param {Number} selectionStart\r\n * @param {Boolean} [skipWrapping] consider the location for unwrapped lines. useful to manage styles.\r\n */\r\n get2DCursorLocation(selectionStart: number, skipWrapping?: boolean) {\r\n const lines = skipWrapping ? this._unwrappedTextLines : this._textLines;\r\n let i: number;\r\n for (i = 0; i < lines.length; i++) {\r\n if (selectionStart <= lines[i].length) {\r\n return {\r\n lineIndex: i,\r\n charIndex: selectionStart,\r\n };\r\n }\r\n selectionStart -=\r\n lines[i].length + this.missingNewlineOffset(i, skipWrapping);\r\n }\r\n return {\r\n lineIndex: i - 1,\r\n charIndex:\r\n lines[i - 1].length < selectionStart\r\n ? lines[i - 1].length\r\n : selectionStart,\r\n };\r\n }\r\n\r\n /**\r\n * Returns string representation of an instance\r\n * @return {String} String representation of text object\r\n */\r\n toString(): string {\r\n return `#<Text (${this.complexity()}): { \"text\": \"${this.text\r\n }\", \"fontFamily\": \"${this.fontFamily}\" }>`;\r\n }\r\n\r\n /**\r\n * Return the dimension and the zoom level needed to create a cache canvas\r\n * big enough to host the object to be cached.\r\n * @private\r\n * @param {Object} dim.x width of object to be cached\r\n * @param {Object} dim.y height of object to be cached\r\n * @return {Object}.width width of canvas\r\n * @return {Object}.height height of canvas\r\n * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache\r\n * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache\r\n */\r\n _getCacheCanvasDimensions(): TCacheCanvasDimensions {\r\n const dims = super._getCacheCanvasDimensions();\r\n const fontSize = this.fontSize;\r\n dims.width += fontSize * dims.zoomX;\r\n dims.height += fontSize * dims.zoomY;\r\n return dims;\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n */\r\n _render(ctx: CanvasRenderingContext2D) {\r\n const path = this.path;\r\n path && !path.isNotVisible() && path._render(ctx);\r\n this._setTextStyles(ctx);\r\n this._renderTextLinesBackground(ctx);\r\n this._renderTextDecoration(ctx, 'underline');\r\n this._renderText(ctx);\r\n this._renderTextDecoration(ctx, 'overline');\r\n this._renderTextDecoration(ctx, 'linethrough');\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n */\r\n _renderText(ctx: CanvasRenderingContext2D) {\r\n // Skip text rendering if in overlay editing mode\r\n if ((this as any).__overlayEditor) {\r\n return;\r\n }\r\n if (this.paintFirst === STROKE) {\r\n this._renderTextStroke(ctx);\r\n this._renderTextFill(ctx);\r\n } else {\r\n this._renderTextFill(ctx);\r\n this._renderTextStroke(ctx);\r\n }\r\n }\r\n\r\n /**\r\n * Set the font parameter of the context with the object properties or with charStyle\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n * @param {Object} [charStyle] object with font style properties\r\n * @param {String} [charStyle.fontFamily] Font Family\r\n * @param {Number} [charStyle.fontSize] Font size in pixels. ( without px suffix )\r\n * @param {String} [charStyle.fontWeight] Font weight\r\n * @param {String} [charStyle.fontStyle] Font style (italic|normal)\r\n */\r\n _setTextStyles(\r\n ctx: CanvasRenderingContext2D,\r\n charStyle?: any,\r\n forMeasuring?: boolean,\r\n ) {\r\n ctx.textBaseline = 'alphabetic';\r\n if (this.path) {\r\n switch (this.pathAlign) {\r\n case CENTER:\r\n ctx.textBaseline = 'middle';\r\n break;\r\n case 'ascender':\r\n ctx.textBaseline = TOP;\r\n break;\r\n case 'descender':\r\n ctx.textBaseline = BOTTOM;\r\n break;\r\n }\r\n }\r\n ctx.font = this._getFontDeclaration(charStyle, forMeasuring);\r\n }\r\n\r\n /**\r\n * calculate and return the text Width measuring each line.\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n * @return {Number} Maximum width of Text object\r\n */\r\n calcTextWidth(): number {\r\n let maxWidth = this.getLineWidth(0);\r\n\r\n for (let i = 1, len = this._textLines.length; i < len; i++) {\r\n const currentLineWidth = this.getLineWidth(i);\r\n if (currentLineWidth > maxWidth) {\r\n maxWidth = currentLineWidth;\r\n }\r\n }\r\n return maxWidth;\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {String} method Method name (\"fillText\" or \"strokeText\")\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n * @param {String} line Text to render\r\n * @param {Number} left Left position of text\r\n * @param {Number} top Top position of text\r\n * @param {Number} lineIndex Index of a line in a text\r\n */\r\n _renderTextLine(\r\n method: 'fillText' | 'strokeText',\r\n ctx: CanvasRenderingContext2D,\r\n line: string[],\r\n left: number,\r\n top: number,\r\n lineIndex: number,\r\n ) {\r\n this._renderChars(method, ctx, line, left, top, lineIndex);\r\n }\r\n\r\n /**\r\n * Build display text lines with kashida characters inserted.\r\n * This creates a version of _textLines with tatweel characters added at kashida points.\r\n * @private\r\n */\r\n _buildKashidaDisplayLines(): string[][] {\r\n if (this.kashida === 'none' || !this.__kashidaInfo) {\r\n return this._textLines;\r\n }\r\n\r\n const displayLines: string[][] = [];\r\n\r\n for (let lineIndex = 0; lineIndex < this._textLines.length; lineIndex++) {\r\n const line = this._textLines[lineIndex];\r\n const kashidaInfo = this.__kashidaInfo[lineIndex];\r\n\r\n if (!kashidaInfo || kashidaInfo.length === 0) {\r\n displayLines.push([...line]);\r\n continue;\r\n }\r\n\r\n // Sort kashida points by charIndex descending so we can insert from the end\r\n const sortedKashida = [...kashidaInfo].sort((a, b) => b.charIndex - a.charIndex);\r\n\r\n // Calculate how many tatweels to insert based on width\r\n const newLine = [...line];\r\n for (const { charIndex, width } of sortedKashida) {\r\n if (width <= 0 || charIndex >= newLine.length) continue;\r\n\r\n // Calculate number of tatweel characters based on width\r\n // Each tatweel is approximately 5px at font size 24\r\n const tatweelCount = Math.max(1, Math.round(width / 3));\r\n const tatweels = ARABIC_TATWEEL.repeat(tatweelCount);\r\n\r\n // Insert tatweels after the character at charIndex\r\n newLine.splice(charIndex + 1, 0, tatweels);\r\n }\r\n\r\n displayLines.push(newLine);\r\n }\r\n\r\n return displayLines;\r\n }\r\n\r\n /**\r\n * Renders the text background for lines, taking care of style\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n */\r\n _renderTextLinesBackground(ctx: CanvasRenderingContext2D) {\r\n if (!this.textBackgroundColor && !this.styleHas('textBackgroundColor')) {\r\n return;\r\n }\r\n const originalFill = ctx.fillStyle,\r\n leftOffset = this._getLeftOffset();\r\n let lineTopOffset = this._getTopOffset();\r\n\r\n for (let i = 0, len = this._textLines.length; i < len; i++) {\r\n const heightOfLine = this.getHeightOfLine(i);\r\n if (\r\n !this.textBackgroundColor &&\r\n !this.styleHas('textBackgroundColor', i)\r\n ) {\r\n lineTopOffset += heightOfLine;\r\n continue;\r\n }\r\n const jlen = this._textLines[i].length;\r\n const lineLeftOffset = this._getLineLeftOffset(i);\r\n let boxWidth = 0;\r\n let boxStart = 0;\r\n let drawStart;\r\n let currentColor;\r\n let lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor');\r\n for (let j = 0; j < jlen; j++) {\r\n // at this point charbox are either standard or full with pathInfo if there is a path.\r\n const charBox = this.__charBounds[i][j] as Required<GraphemeBBox>;\r\n currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor');\r\n if (this.path) {\r\n ctx.save();\r\n ctx.translate(charBox.renderLeft, charBox.renderTop);\r\n ctx.rotate(charBox.angle);\r\n ctx.fillStyle = currentColor;\r\n currentColor &&\r\n ctx.fillRect(\r\n -charBox.width / 2,\r\n (-heightOfLine / this.lineHeight) * (1 - this._fontSizeFraction),\r\n charBox.width,\r\n heightOfLine / this.lineHeight,\r\n );\r\n ctx.restore();\r\n } else if (currentColor !== lastColor) {\r\n drawStart = leftOffset + lineLeftOffset + boxStart;\r\n if (this.direction === 'rtl') {\r\n drawStart = this.width - drawStart - boxWidth;\r\n }\r\n ctx.fillStyle = lastColor;\r\n lastColor &&\r\n ctx.fillRect(\r\n drawStart,\r\n lineTopOffset,\r\n boxWidth,\r\n heightOfLine / this.lineHeight,\r\n );\r\n boxStart = charBox.left;\r\n boxWidth = charBox.width;\r\n lastColor = currentColor;\r\n } else {\r\n boxWidth += charBox.kernedWidth;\r\n }\r\n }\r\n if (currentColor && !this.path) {\r\n drawStart = leftOffset + lineLeftOffset + boxStart;\r\n if (this.direction === 'rtl') {\r\n drawStart = this.width - drawStart - boxWidth;\r\n }\r\n ctx.fillStyle = currentColor;\r\n ctx.fillRect(\r\n drawStart,\r\n lineTopOffset,\r\n boxWidth,\r\n heightOfLine / this.lineHeight,\r\n );\r\n }\r\n lineTopOffset += heightOfLine;\r\n }\r\n ctx.fillStyle = originalFill;\r\n // if there is text background color no\r\n // other shadows should be casted\r\n this._removeShadow(ctx);\r\n }\r\n\r\n /**\r\n * measure and return the width of a single character.\r\n * possibly overridden to accommodate different measure logic or\r\n * to hook some external lib for character measurement\r\n * @private\r\n * @param {String} _char, char to be measured\r\n * @param {Object} charStyle style of char to be measured\r\n * @param {String} [previousChar] previous char\r\n * @param {Object} [prevCharStyle] style of previous char\r\n */\r\n _measureChar(\r\n _char: string,\r\n charStyle: CompleteTextStyleDeclaration,\r\n previousChar: string | undefined,\r\n prevCharStyle: CompleteTextStyleDeclaration | Record<string, never>,\r\n ) {\r\n const fontCache = cache.getFontCache(charStyle),\r\n fontDeclaration = this._getFontDeclaration(charStyle),\r\n couple = previousChar + _char,\r\n // Skip kerning for tatweel (kashida) characters - they extend connections\r\n // and kerning would make the following character appear too narrow\r\n isTatweel = previousChar === '\\u0640',\r\n stylesAreEqual =\r\n previousChar &&\r\n !isTatweel &&\r\n fontDeclaration === this._getFontDeclaration(prevCharStyle),\r\n fontMultiplier = charStyle.fontSize / this.CACHE_FONT_SIZE;\r\n let width: number | undefined,\r\n coupleWidth: number | undefined,\r\n previousWidth: number | undefined,\r\n kernedWidth: number | undefined;\r\n\r\n if (previousChar && !isTatweel && fontCache[previousChar] !== undefined) {\r\n previousWidth = fontCache[previousChar];\r\n }\r\n if (fontCache[_char] !== undefined) {\r\n kernedWidth = width = fontCache[_char];\r\n }\r\n if (stylesAreEqual && fontCache[couple] !== undefined) {\r\n coupleWidth = fontCache[couple];\r\n kernedWidth = coupleWidth - previousWidth!;\r\n }\r\n if (\r\n width === undefined ||\r\n previousWidth === undefined ||\r\n coupleWidth === undefined\r\n ) {\r\n const ctx = getMeasuringContext()!;\r\n // send a TRUE to specify measuring font size CACHE_FONT_SIZE\r\n this._setTextStyles(ctx, charStyle, true);\r\n if (width === undefined) {\r\n kernedWidth = width = ctx.measureText(_char).width;\r\n fontCache[_char] = width;\r\n }\r\n if (previousWidth === undefined && stylesAreEqual && previousChar && !isTatweel) {\r\n previousWidth = ctx.measureText(previousChar).width;\r\n fontCache[previousChar] = previousWidth;\r\n }\r\n if (stylesAreEqual && coupleWidth === undefined && !isTatweel) {\r\n // we can measure the kerning couple and subtract the width of the previous character\r\n coupleWidth = ctx.measureText(couple).width;\r\n fontCache[couple] = coupleWidth;\r\n // safe to use the non-null since if undefined we defined it before.\r\n kernedWidth = coupleWidth - previousWidth!;\r\n }\r\n }\r\n return {\r\n width: width * fontMultiplier,\r\n kernedWidth: kernedWidth! * fontMultiplier,\r\n };\r\n }\r\n\r\n /**\r\n * Computes height of character at given position\r\n * @param {Number} line the line index number\r\n * @param {Number} _char the character index number\r\n * @return {Number} fontSize of the character\r\n */\r\n getHeightOfChar(line: number, _char: number): number {\r\n return this.getValueOfPropertyAt(line, _char, 'fontSize');\r\n }\r\n\r\n /**\r\n * measure a text line measuring all characters.\r\n * @param {Number} lineIndex line number\r\n */\r\n measureLine(lineIndex: number) {\r\n const lineInfo = this._measureLine(lineIndex);\r\n if (this.charSpacing !== 0) {\r\n lineInfo.width -= this._getWidthOfCharSpacing();\r\n }\r\n if (lineInfo.width < 0) {\r\n lineInfo.width = 0;\r\n }\r\n return lineInfo;\r\n }\r\n\r\n /**\r\n * measure every grapheme of a line, populating __charBounds\r\n * @param {Number} lineIndex\r\n * @return {Object} object.width total width of characters\r\n * @return {Object} object.numOfSpaces length of chars that match this._reSpacesAndTabs\r\n */\r\n _measureLine(lineIndex: number) {\r\n // Debug: detect if measureLine is called after justify was applied\r\n if ((this as any)._justifyApplied) {\r\n // console.warn(`WARNING: _measureLine called for line ${lineIndex} AFTER justify was applied! This will overwrite justified charBounds.`);\r\n // console.trace('Stack trace:');\r\n }\r\n\r\n let width = 0,\r\n prevGrapheme: string | undefined,\r\n graphemeInfo: GraphemeBBox | undefined;\r\n\r\n const reverse = this.pathSide === RIGHT,\r\n path = this.path,\r\n line = this._textLines[lineIndex],\r\n llength = line.length,\r\n lineBounds = new Array<GraphemeBBox>(llength);\r\n\r\n this.__charBounds[lineIndex] = lineBounds;\r\n for (let i = 0; i < llength; i++) {\r\n const grapheme = line[i];\r\n graphemeInfo = this._getGraphemeBox(grapheme, lineIndex, i, prevGrapheme);\r\n lineBounds[i] = graphemeInfo;\r\n width += graphemeInfo.kernedWidth;\r\n prevGrapheme = grapheme;\r\n }\r\n // this latest bound box represent the last character of the line\r\n // to simplify cursor handling in interactive mode.\r\n lineBounds[llength] = {\r\n left: graphemeInfo ? graphemeInfo.left + graphemeInfo.width : 0,\r\n width: 0,\r\n kernedWidth: 0,\r\n height: this.fontSize,\r\n deltaY: 0,\r\n } as GraphemeBBox;\r\n if (path && path.segmentsInfo) {\r\n let positionInPath = 0;\r\n const totalPathLength =\r\n path.segmentsInfo[path.segmentsInfo.length - 1].length;\r\n switch (this.textAlign) {\r\n case LEFT:\r\n positionInPath = reverse ? totalPathLength - width : 0;\r\n break;\r\n case CENTER:\r\n positionInPath = (totalPathLength - width) / 2;\r\n break;\r\n case RIGHT:\r\n positionInPath = reverse ? 0 : totalPathLength - width;\r\n break;\r\n //todo - add support for justify\r\n }\r\n positionInPath += this.pathStartOffset * (reverse ? -1 : 1);\r\n for (\r\n let i = reverse ? llength - 1 : 0;\r\n reverse ? i >= 0 : i < llength;\r\n reverse ? i-- : i++\r\n ) {\r\n graphemeInfo = lineBounds[i];\r\n if (positionInPath > totalPathLength) {\r\n positionInPath %= totalPathLength;\r\n } else if (positionInPath < 0) {\r\n positionInPath += totalPathLength;\r\n }\r\n // it would probably much faster to send all the grapheme position for a line\r\n // and calculate path position/angle at once.\r\n this._setGraphemeOnPath(positionInPath, graphemeInfo);\r\n positionInPath += graphemeInfo.kernedWidth;\r\n }\r\n }\r\n return { width: width, numOfSpaces: 0 };\r\n }\r\n\r\n /**\r\n * Calculate the angle and the left,top position of the char that follow a path.\r\n * It appends it to graphemeInfo to be reused later at rendering\r\n * @private\r\n * @param {Number} positionInPath to be measured\r\n * @param {GraphemeBBox} graphemeInfo current grapheme box information\r\n * @param {Object} startingPoint position of the point\r\n */\r\n _setGraphemeOnPath(positionInPath: number, graphemeInfo: GraphemeBBox) {\r\n const centerPosition = positionInPath + graphemeInfo.kernedWidth / 2,\r\n path = this.path!;\r\n\r\n // we are at currentPositionOnPath. we want to know what point on the path is.\r\n const info = getPointOnPath(path.path, centerPosition, path.segmentsInfo)!;\r\n graphemeInfo.renderLeft = info.x - path.pathOffset.x;\r\n graphemeInfo.renderTop = info.y - path.pathOffset.y;\r\n graphemeInfo.angle = info.angle + (this.pathSide === RIGHT ? Math.PI : 0);\r\n }\r\n\r\n /**\r\n *\r\n * @param {String} grapheme to be measured\r\n * @param {Number} lineIndex index of the line where the char is\r\n * @param {Number} charIndex position in the line\r\n * @param {String} [prevGrapheme] character preceding the one to be measured\r\n * @returns {GraphemeBBox} grapheme bbox\r\n */\r\n _getGraphemeBox(\r\n grapheme: string,\r\n lineIndex: number,\r\n charIndex: number,\r\n prevGrapheme?: string,\r\n skipLeft?: boolean,\r\n ): GraphemeBBox {\r\n const style = this.getCompleteStyleDeclaration(lineIndex, charIndex),\r\n prevStyle = prevGrapheme\r\n ? this.getCompleteStyleDeclaration(lineIndex, charIndex - 1)\r\n : {},\r\n info = this._measureChar(grapheme, style, prevGrapheme, prevStyle);\r\n let kernedWidth = info.kernedWidth,\r\n width = info.width,\r\n charSpacing;\r\n\r\n if (this.charSpacing !== 0) {\r\n charSpacing = this._getWidthOfCharSpacing();\r\n width += charSpacing;\r\n kernedWidth += charSpacing;\r\n }\r\n\r\n const box: GraphemeBBox = {\r\n width,\r\n left: 0,\r\n height: style.fontSize,\r\n kernedWidth,\r\n deltaY: style.deltaY,\r\n };\r\n if (charIndex > 0 && !skipLeft) {\r\n const previousBox = this.__charBounds[lineIndex][charIndex - 1];\r\n box.left =\r\n previousBox.left + previousBox.width + info.kernedWidth - info.width;\r\n }\r\n return box;\r\n }\r\n\r\n /**\r\n * Calculate height of line at 'lineIndex'\r\n * @param {Number} lineIndex index of line to calculate\r\n * @return {Number}\r\n */\r\n getHeightOfLine(lineIndex: number): number {\r\n if (this.__lineHeights[lineIndex]) {\r\n return this.__lineHeights[lineIndex];\r\n }\r\n\r\n // char 0 is measured before the line cycle because it needs to char\r\n // emptylines\r\n let maxHeight = this.getHeightOfChar(lineIndex, 0);\r\n for (let i = 1, len = this._textLines[lineIndex].length; i < len; i++) {\r\n maxHeight = Math.max(this.getHeightOfChar(lineIndex, i), maxHeight);\r\n }\r\n\r\n return (this.__lineHeights[lineIndex] =\r\n maxHeight * this.lineHeight * this._fontSizeMult);\r\n }\r\n\r\n /**\r\n * Calculate text box height\r\n */\r\n calcTextHeight() {\r\n let lineHeight,\r\n height = 0;\r\n for (let i = 0, len = this._textLines.length; i < len; i++) {\r\n lineHeight = this.getHeightOfLine(i);\r\n height += i === len - 1 ? lineHeight / this.lineHeight : lineHeight;\r\n }\r\n return height;\r\n }\r\n\r\n /**\r\n * @private\r\n * @return {Number} Left offset\r\n */\r\n _getLeftOffset(): number {\r\n return this.direction === 'ltr' ? -this.width / 2 : this.width / 2;\r\n }\r\n\r\n /**\r\n * @private\r\n * @return {Number} Top offset\r\n */\r\n _getTopOffset(): number {\r\n return -this.height / 2;\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n * @param {String} method Method name (\"fillText\" or \"strokeText\")\r\n */\r\n _renderTextCommon(\r\n ctx: CanvasRenderingContext2D,\r\n method: 'fillText' | 'strokeText',\r\n ) {\r\n ctx.save();\r\n let lineHeights = 0;\r\n const left = this._getLeftOffset(),\r\n top = this._getTopOffset();\r\n\r\n // Debug: log once per render\r\n if (method === 'fillText' && this.textAlign?.includes('justify')) {\r\n // console.log('=== RENDER DEBUG ===');\r\n // console.log('direction:', this.direction);\r\n // console.log('textAlign:', this.textAlign);\r\n // console.log('width:', this.width);\r\n // console.log('_getLeftOffset:', left);\r\n }\r\n\r\n for (let i = 0, len = this._textLines.length; i < len; i++) {\r\n const heightOfLine = this.getHeightOfLine(i),\r\n maxHeight = heightOfLine / this.lineHeight,\r\n leftOffset = this._getLineLeftOffset(i);\r\n\r\n // Debug: log line offsets for justify\r\n if (method === 'fillText' && this.textAlign?.includes('justify')) {\r\n const lineWidth = this.getLineWidth(i);\r\n // console.log(`Line ${i}: leftOffset=${leftOffset.toFixed(2)}, lineWidth=${lineWidth.toFixed(2)}, renderAt=${(left + leftOffset).toFixed(2)}`);\r\n }\r\n\r\n this._renderTextLine(\r\n method,\r\n ctx,\r\n this._textLines[i],\r\n left + leftOffset,\r\n top + lineHeights + maxHeight,\r\n i,\r\n );\r\n lineHeights += heightOfLine;\r\n }\r\n ctx.restore();\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n */\r\n _renderTextFill(ctx: CanvasRenderingContext2D) {\r\n if (!this.fill && !this.styleHas(FILL)) {\r\n return;\r\n }\r\n\r\n this._renderTextCommon(ctx, 'fillText');\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n */\r\n _renderTextStroke(ctx: CanvasRenderingContext2D) {\r\n if ((!this.stroke || this.strokeWidth === 0) && this.isEmptyStyles()) {\r\n return;\r\n }\r\n\r\n if (this.shadow && !this.shadow.affectStroke) {\r\n this._removeShadow(ctx);\r\n }\r\n\r\n ctx.save();\r\n this._setLineDash(ctx, this.strokeDashArray);\r\n ctx.beginPath();\r\n this._renderTextCommon(ctx, 'strokeText');\r\n ctx.closePath();\r\n ctx.restore();\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {String} method fillText or strokeText.\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n * @param {Array} line Content of the line, splitted in an array by grapheme\r\n * @param {Number} left\r\n * @param {Number} top\r\n * @param {Number} lineIndex\r\n */\r\n _renderChars(\r\n method: 'fillText' | 'strokeText',\r\n ctx: CanvasRenderingContext2D,\r\n line: Array<any>,\r\n left: number,\r\n top: number,\r\n lineIndex: number,\r\n ) {\r\n const lineHeight = this.getHeightOfLine(lineIndex),\r\n isJustify = this.textAlign.includes(JUSTIFY),\r\n path = this.path,\r\n isLtr = this.direction === 'ltr',\r\n sign = this.direction === 'ltr' ? 1 : -1,\r\n // this was changed in the PR #7674\r\n // currentDirection = ctx.canvas.getAttribute('dir');\r\n currentDirection = ctx.direction;\r\n\r\n // Check if we should use BiDi-aware rendering with pre-calculated positions\r\n // This is needed for advanced layout with RTL or mixed BiDi text\r\n const chars = this.__charBounds[lineIndex];\r\n const hasRenderLeft = this.enableAdvancedLayout && chars?.length > 0 && chars[0].renderLeft !== undefined;\r\n // Disable individual char rendering for now as it breaks Arabic shaping (ligatures)\r\n // We still need hasRenderLeft to remain true for hit-testing logic in IText\r\n const useBiDiRendering = false; // hasRenderLeft && !isLtr;\r\n\r\n const shortCut =\r\n !useBiDiRendering &&\r\n !isJustify &&\r\n this.charSpacing === 0 &&\r\n this.isEmptyStyles(lineIndex) &&\r\n !path;\r\n\r\n let actualStyle,\r\n nextStyle,\r\n charsToRender = '',\r\n charBox,\r\n boxWidth = 0,\r\n timeToRender,\r\n drawingLeft;\r\n\r\n ctx.save();\r\n\r\n // For BiDi rendering with pre-calculated positions, disable browser BiDi\r\n // and render each character at its calculated visual position\r\n if (useBiDiRendering) {\r\n ctx.canvas.setAttribute('dir', 'ltr');\r\n ctx.direction = 'ltr';\r\n ctx.textAlign = LEFT;\r\n } else if (currentDirection !== this.direction) {\r\n ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');\r\n ctx.direction = isLtr ? 'ltr' : 'rtl';\r\n\r\n // Set text alignment based on direction\r\n // For RTL, use RIGHT alignment so x-coordinate specifies the right edge (where RTL text starts)\r\n // For LTR, use LEFT alignment so x-coordinate specifies the left edge (where LTR text starts)\r\n ctx.textAlign = isLtr ? LEFT : RIGHT;\r\n }\r\n top -= (lineHeight * this._fontSizeFraction) / this.lineHeight;\r\n\r\n // BiDi rendering: render each character at its pre-calculated visual position\r\n // renderLeft is relative to line start (0 to lineWidth), and 'left' already includes\r\n // the line left offset from _renderTextLine, so just add renderLeft to left\r\n // Note: This renders chars individually which may affect Arabic contextual shaping\r\n // A more sophisticated approach would group consecutive chars into runs\r\n if (useBiDiRendering) {\r\n for (let i = 0; i < line.length; i++) {\r\n charBox = chars[i] as Required<GraphemeBBox>;\r\n const renderX = charBox.renderLeft !== undefined ? charBox.renderLeft : charBox.left;\r\n // Render each character at its visual position\r\n // layoutText provides renderLeft relative to the visual left edge of the bounding box\r\n // So we start from -this.width / 2 (visual left) and add renderLeft\r\n this._renderChar(method, ctx, lineIndex, i, line[i], -this.width / 2 + renderX, top);\r\n }\r\n ctx.restore();\r\n return;\r\n }\r\n\r\n if (shortCut) {\r\n // render all the line in one pass without checking\r\n // drawingLeft = isLtr ? left : left - this.getLineWidth(lineIndex);\r\n this._renderChar(method, ctx, lineIndex, 0, line.join(''), left, top);\r\n ctx.restore();\r\n return;\r\n }\r\n // Debug: Log charBounds being used for first line only during justify\r\n if (isJustify && lineIndex === 0 && method === 'fillText') {\r\n // console.log(`\\n=== RENDER _renderChars line ${lineIndex} ===`);\r\n // console.log('Initial left:', left.toFixed(2), 'sign:', sign);\r\n // console.log('_justifyApplied flag:', (this as any)._justifyApplied);\r\n const lineBounds = this.__charBounds[lineIndex];\r\n const totalKW = lineBounds?.reduce((s, b) => s + (b?.kernedWidth || 0), 0) || 0;\r\n // console.log('Total kernedWidth in charBounds:', totalKW.toFixed(2), '(should be ~300 if justify was applied)');\r\n // Log first few space widths to verify expansion\r\n const spaceIndices = [3, 9, 15, 23, 31];\r\n spaceIndices.forEach(idx => {\r\n const b = lineBounds?.[idx];\r\n if (b) console.log(` Space at idx ${idx}: kernedWidth=${b.kernedWidth?.toFixed(2)}`);\r\n });\r\n }\r\n\r\n for (let i = 0, len = line.length - 1; i <= len; i++) {\r\n timeToRender = i === len || this.charSpacing || path;\r\n charsToRender += line[i];\r\n charBox = this.__charBounds[lineIndex][i] as Required<GraphemeBBox>;\r\n if (boxWidth === 0) {\r\n if (isLtr) {\r\n // For LTR, adjust for kerning difference of first character\r\n left += sign * (charBox.kernedWidth - charBox.width);\r\n boxWidth += charBox.width;\r\n } else {\r\n // For RTL with drawingLeft = left - boxWidth positioning,\r\n // use kernedWidth consistently to ensure the right edge is exactly at 'left'\r\n boxWidth += charBox.kernedWidth;\r\n }\r\n } else {\r\n boxWidth += charBox.kernedWidth;\r\n }\r\n if (isJustify && !timeToRender) {\r\n if (this._reSpaceAndTab.test(line[i])) {\r\n timeToRender = true;\r\n }\r\n }\r\n if (!timeToRender) {\r\n // if we have charSpacing, we render char by char\r\n actualStyle =\r\n actualStyle || this.getCompleteStyleDeclaration(lineIndex, i);\r\n nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1);\r\n timeToRender = hasStyleChanged(actualStyle, nextStyle, false);\r\n }\r\n if (timeToRender) {\r\n if (path) {\r\n ctx.save();\r\n ctx.translate(charBox.renderLeft, charBox.renderTop);\r\n ctx.rotate(charBox.angle);\r\n this._renderChar(\r\n method,\r\n ctx,\r\n lineIndex,\r\n i,\r\n charsToRender,\r\n -boxWidth / 2,\r\n 0,\r\n );\r\n ctx.restore();\r\n } else {\r\n // For LTR with textAlign='left': x is the left edge, so drawingLeft = left\r\n // For RTL with textAlign='right': x is the right edge, so drawingLeft = left\r\n // Both cases: drawingLeft = left (the text alignment handles the edge correctly)\r\n drawingLeft = left;\r\n // Debug: log first chunk positioning for justify\r\n if (isJustify && lineIndex === 0 && method === 'fillText' && i < 5) {\r\n // console.log(` Chunk ending at char ${i}: left=${left.toFixed(2)}, boxWidth=${boxWidth.toFixed(2)}, drawingLeft=${drawingLeft.toFixed(2)}, textAlign=${isLtr ? 'left' : 'right'}`);\r\n }\r\n this._renderChar(\r\n method,\r\n ctx,\r\n lineIndex,\r\n i,\r\n charsToRender,\r\n drawingLeft,\r\n top,\r\n );\r\n }\r\n charsToRender = '';\r\n actualStyle = nextStyle;\r\n left += sign * boxWidth;\r\n boxWidth = 0;\r\n }\r\n }\r\n // Debug: log final position for justify\r\n if (isJustify && lineIndex === 0 && method === 'fillText') {\r\n // console.log('Final left position after rendering:', left.toFixed(2));\r\n // console.log('Expected final position:', (sign > 0 ? this.width / 2 : -this.width / 2).toFixed(2));\r\n }\r\n ctx.restore();\r\n }\r\n\r\n /**\r\n * This function try to patch the missing gradientTransform on canvas gradients.\r\n * transforming a context to transform the gradient, is going to transform the stroke too.\r\n * we want to transform the gradient but not the stroke operation, so we create\r\n * a transformed gradient on a pattern and then we use the pattern instead of the gradient.\r\n * this method has drawbacks: is slow, is in low resolution, needs a patch for when the size\r\n * is limited.\r\n * @private\r\n * @param {TFiller} filler a fabric gradient instance\r\n * @return {CanvasPattern} a pattern to use as fill/stroke style\r\n */\r\n _applyPatternGradientTransformText(filler: TFiller) {\r\n // TODO: verify compatibility with strokeUniform\r\n const width = this.width + this.strokeWidth,\r\n height = this.height + this.strokeWidth,\r\n pCanvas = createCanvasElementFor({\r\n width,\r\n height,\r\n }),\r\n pCtx = pCanvas.getContext('2d')!;\r\n pCanvas.width = width;\r\n pCanvas.height = height;\r\n pCtx.beginPath();\r\n pCtx.moveTo(0, 0);\r\n pCtx.lineTo(width, 0);\r\n pCtx.lineTo(width, height);\r\n pCtx.lineTo(0, height);\r\n pCtx.closePath();\r\n pCtx.translate(width / 2, height / 2);\r\n pCtx.fillStyle = filler.toLive(pCtx)!;\r\n this._applyPatternGradientTransform(pCtx, filler);\r\n pCtx.fill();\r\n return pCtx.createPattern(pCanvas, 'no-repeat')!;\r\n }\r\n\r\n handleFiller<T extends 'fill' | 'stroke'>(\r\n ctx: CanvasRenderingContext2D,\r\n property: `${T}Style`,\r\n filler: TFiller | string,\r\n ): { offsetX: number; offsetY: number } {\r\n let offsetX: number, offsetY: number;\r\n if (isFiller(filler)) {\r\n if (\r\n (filler as Gradient<'linear'>).gradientUnits === 'percentage' ||\r\n (filler as Gradient<'linear'>).gradientTransform ||\r\n (filler as Pattern).patternTransform\r\n ) {\r\n // need to transform gradient in a pattern.\r\n // this is a slow process. If you are hitting this codepath, and the object\r\n // is not using caching, you should consider switching it on.\r\n // we need a canvas as big as the current object caching canvas.\r\n offsetX = -this.width / 2;\r\n offsetY = -this.height / 2;\r\n ctx.translate(offsetX, offsetY);\r\n ctx[property] = this._applyPatternGradientTransformText(filler);\r\n return { offsetX, offsetY };\r\n } else {\r\n // is a simple gradient or pattern\r\n ctx[property] = filler.toLive(ctx)!;\r\n return this._applyPatternGradientTransform(ctx, filler);\r\n }\r\n } else {\r\n // is a color\r\n ctx[property] = filler;\r\n }\r\n return { offsetX: 0, offsetY: 0 };\r\n }\r\n\r\n /**\r\n * This function prepare the canvas for a stroke style, and stroke and strokeWidth\r\n * need to be sent in as defined\r\n * @param {CanvasRenderingContext2D} ctx\r\n * @param {CompleteTextStyleDeclaration} style with stroke and strokeWidth defined\r\n * @returns\r\n */\r\n _setStrokeStyles(\r\n ctx: CanvasRenderingContext2D,\r\n {\r\n stroke,\r\n strokeWidth,\r\n }: Pick<CompleteTextStyleDeclaration, 'stroke' | 'strokeWidth'>,\r\n ) {\r\n ctx.lineWidth = strokeWidth;\r\n ctx.lineCap = this.strokeLineCap;\r\n ctx.lineDashOffset = this.strokeDashOffset;\r\n ctx.lineJoin = this.strokeLineJoin;\r\n ctx.miterLimit = this.strokeMiterLimit;\r\n return this.handleFiller(ctx, 'strokeStyle', stroke!);\r\n }\r\n\r\n /**\r\n * This function prepare the canvas for a ill style, and fill\r\n * need to be sent in as defined\r\n * @param {CanvasRenderingContext2D} ctx\r\n * @param {CompleteTextStyleDeclaration} style with ill defined\r\n * @returns\r\n */\r\n _setFillStyles(ctx: CanvasRenderingContext2D, { fill }: Pick<this, 'fill'>) {\r\n return this.handleFiller(ctx, 'fillStyle', fill!);\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {String} method\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n * @param {Number} lineIndex\r\n * @param {Number} charIndex\r\n * @param {String} _char\r\n * @param {Number} left Left coordinate\r\n * @param {Number} top Top coordinate\r\n * @param {Number} lineHeight Height of the line\r\n */\r\n _renderChar(\r\n method: 'fillText' | 'strokeText',\r\n ctx: CanvasRenderingContext2D,\r\n lineIndex: number,\r\n charIndex: number,\r\n _char: string,\r\n left: number,\r\n top: number,\r\n ) {\r\n const decl = this._getStyleDeclaration(lineIndex, charIndex),\r\n fullDecl = this.getCompleteStyleDeclaration(lineIndex, charIndex),\r\n shouldFill = method === 'fillText' && fullDecl.fill,\r\n shouldStroke =\r\n method === 'strokeText' && fullDecl.stroke && fullDecl.strokeWidth;\r\n\r\n if (!shouldStroke && !shouldFill) {\r\n return;\r\n }\r\n ctx.save();\r\n\r\n ctx.font = this._getFontDeclaration(fullDecl);\r\n\r\n if (decl.textBackgroundColor) {\r\n this._removeShadow(ctx);\r\n }\r\n if (decl.deltaY) {\r\n top += decl.deltaY;\r\n }\r\n\r\n if (shouldFill) {\r\n const fillOffsets = this._setFillStyles(ctx, fullDecl);\r\n ctx.fillText(\r\n _char,\r\n left - fillOffsets.offsetX,\r\n top - fillOffsets.offsetY,\r\n );\r\n }\r\n\r\n if (shouldStroke) {\r\n const strokeOffsets = this._setStrokeStyles(ctx, fullDecl);\r\n ctx.strokeText(\r\n _char,\r\n left - strokeOffsets.offsetX,\r\n top - strokeOffsets.offsetY,\r\n );\r\n }\r\n\r\n ctx.restore();\r\n }\r\n\r\n /**\r\n * Turns the character into a 'superior figure' (i.e. 'superscript')\r\n * @param {Number} start selection start\r\n * @param {Number} end selection end\r\n */\r\n setSuperscript(start: number, end: number) {\r\n this._setScript(start, end, this.superscript);\r\n }\r\n\r\n /**\r\n * Turns the character into an 'inferior figure' (i.e. 'subscript')\r\n * @param {Number} start selection start\r\n * @param {Number} end selection end\r\n */\r\n setSubscript(start: number, end: number) {\r\n this._setScript(start, end, this.subscript);\r\n }\r\n\r\n /**\r\n * Applies 'schema' at given position\r\n * @private\r\n * @param {Number} start selection start\r\n * @param {Number} end selection end\r\n * @param {Number} schema\r\n */\r\n protected _setScript(\r\n start: number,\r\n end: number,\r\n schema: {\r\n size: number;\r\n baseline: number;\r\n },\r\n ) {\r\n const loc = this.get2DCursorLocation(start, true),\r\n fontSize = this.getValueOfPropertyAt(\r\n loc.lineIndex,\r\n loc.charIndex,\r\n 'fontSize',\r\n ),\r\n dy = this.getValueOfPropertyAt(loc.lineIndex, loc.charIndex, 'deltaY'),\r\n style = {\r\n fontSize: fontSize * schema.size,\r\n deltaY: dy + fontSize * schema.baseline,\r\n };\r\n this.setSelectionStyles(style, start, end);\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {Number} lineIndex index text line\r\n * @return {Number} Line left offset\r\n */\r\n _getLineLeftOffset(lineIndex: number): number {\r\n const lineWidth = this.getLineWidth(lineIndex),\r\n lineDiff = this.width - lineWidth,\r\n textAlign = this.textAlign,\r\n direction = this.direction,\r\n isEndOfWrapping = this.isEndOfWrapping(lineIndex);\r\n const hasTextAfter = this._textLines\r\n .slice(lineIndex + 1)\r\n .some((line) => {\r\n const lineText = Array.isArray(line) ? line.join('') : line;\r\n return /\\S/.test(lineText);\r\n });\r\n const isVisualLastLine = !hasTextAfter;\r\n let leftOffset = 0;\r\n\r\n // Determine if this line should be justified (not last line)\r\n const isJustifyLine =\r\n textAlign === JUSTIFY ||\r\n (textAlign === JUSTIFY_CENTER && !isEndOfWrapping && !isVisualLastLine) ||\r\n (textAlign === JUSTIFY_RIGHT && !isEndOfWrapping && !isVisualLastLine) ||\r\n (textAlign === JUSTIFY_LEFT && !isEndOfWrapping && !isVisualLastLine);\r\n\r\n if (isJustifyLine) {\r\n // Justified lines start at the left edge (offset 0) for both LTR and RTL\r\n // The space expansion from enlargeSpaces() fills the line to the full width\r\n return 0;\r\n }\r\n\r\n // Handle last line alignment (or non-justify alignments)\r\n if (textAlign === CENTER || textAlign === JUSTIFY_CENTER) {\r\n leftOffset = lineDiff / 2;\r\n } else if (textAlign === RIGHT || textAlign === JUSTIFY_RIGHT) {\r\n leftOffset = lineDiff;\r\n }\r\n // LEFT and JUSTIFY_LEFT have leftOffset = 0, which is the default\r\n\r\n // Apply RTL adjustments for non-justified lines\r\n if (direction === 'rtl') {\r\n if (textAlign === RIGHT || textAlign === JUSTIFY || textAlign === JUSTIFY_RIGHT) {\r\n leftOffset = 0;\r\n } else if (textAlign === LEFT || textAlign === JUSTIFY_LEFT) {\r\n leftOffset = -lineDiff;\r\n } else if (textAlign === CENTER || textAlign === JUSTIFY_CENTER) {\r\n leftOffset = -lineDiff / 2;\r\n }\r\n // Clamp to 0 for overflow cases\r\n if (lineDiff <= 0) {\r\n leftOffset = 0;\r\n }\r\n }\r\n\r\n return leftOffset;\r\n }\r\n\r\n /**\r\n * @private\r\n */\r\n _clearCache() {\r\n // console.log('🗑️ _clearCache called');\r\n // console.trace('🗑️ _clearCache stack trace');\r\n this._forceClearCache = false;\r\n this.__lineWidths = [];\r\n this.__lineHeights = [];\r\n this.__charBounds = [];\r\n this.__kashidaInfo = [];\r\n // Reset justify applied flag\r\n (this as any)._justifyApplied = false;\r\n // Reset dimension state to force recalculation\r\n (this as any)._lastDimensionState = null;\r\n }\r\n\r\n /**\r\n * Convert a display character index (in _textLines with tatweels) to original text index.\r\n * When kashida is applied, _textLines contains extra tatweel characters that don't exist\r\n * in the original text. This method maps back to the original index.\r\n * @param lineIndex - The line index\r\n * @param displayCharIndex - Character index in the display text (with tatweels)\r\n * @returns Original character index (without tatweels)\r\n */\r\n _displayToOriginalIndex(lineIndex: number, displayCharIndex: number): number {\r\n // console.log(`🔄 _displayToOriginalIndex called: line=${lineIndex}, displayIdx=${displayCharIndex}`);\r\n // console.log(`🔄 __kashidaInfo exists: ${!!this.__kashidaInfo}, length: ${this.__kashidaInfo?.length}`);\r\n // console.log(`🔄 __kashidaInfo raw:`, JSON.stringify(this.__kashidaInfo));\r\n\r\n const kashidaInfo = this.__kashidaInfo?.[lineIndex];\r\n if (!kashidaInfo || kashidaInfo.length === 0) {\r\n // No kashida on this line, indices are the same\r\n // console.log(`🔄 No kashida info for line ${lineIndex}, returning same index`);\r\n return displayCharIndex;\r\n }\r\n\r\n // Sort kashida info by charIndex ascending for proper traversal\r\n const sortedKashida = [...kashidaInfo].sort((a, b) => a.charIndex - b.charIndex);\r\n\r\n // console.log(`🔄 _displayToOriginalIndex: line=${lineIndex}, displayIdx=${displayCharIndex}`);\r\n // console.log(`🔄 kashidaInfo:`, sortedKashida.map(k => `{charIdx:${k.charIndex}, cnt:${k.tatweelCount}}`).join(', '));\r\n\r\n let tatweelsBeforeIndex = 0;\r\n\r\n for (const k of sortedKashida) {\r\n const tatweelCount = k.tatweelCount || 0;\r\n // Position where tatweels start (after the original character)\r\n const tatweelStartPos = k.charIndex + 1 + tatweelsBeforeIndex;\r\n const tatweelEndPos = tatweelStartPos + tatweelCount;\r\n\r\n // console.log(`🔄 k.charIndex=${k.charIndex}, tatweelStartPos=${tatweelStartPos}, tatweelEndPos=${tatweelEndPos}, tatweelsBeforeIndex=${tatweelsBeforeIndex}`);\r\n\r\n if (displayCharIndex < tatweelStartPos) {\r\n // Before this kashida point\r\n // console.log(`🔄 displayIdx < tatweelStartPos, break`);\r\n break;\r\n } else if (displayCharIndex < tatweelEndPos) {\r\n // Within tatweel characters - map to the character before tatweels\r\n // console.log(`🔄 Within tatweel, return ${k.charIndex + 1}`);\r\n return k.charIndex + 1;\r\n } else {\r\n // After this kashida point\r\n tatweelsBeforeIndex += tatweelCount;\r\n // console.log(`🔄 After this kashida, tatweelsBeforeIndex now=${tatweelsBeforeIndex}`);\r\n }\r\n }\r\n\r\n // Subtract all tatweels that come before this position\r\n const result = displayCharIndex - tatweelsBeforeIndex;\r\n // console.log(`🔄 Final result: ${displayCharIndex} - ${tatweelsBeforeIndex} = ${result}`);\r\n return result;\r\n }\r\n\r\n /**\r\n * Convert an original text character index to display index (in _textLines with tatweels).\r\n * @param lineIndex - The line index\r\n * @param originalCharIndex - Character index in the original text (without tatweels)\r\n * @returns Display character index (with tatweels)\r\n */\r\n _originalToDisplayIndex(lineIndex: number, originalCharIndex: number): number {\r\n const kashidaInfo = this.__kashidaInfo?.[lineIndex];\r\n if (!kashidaInfo || kashidaInfo.length === 0) {\r\n // No kashida on this line, indices are the same\r\n return originalCharIndex;\r\n }\r\n\r\n // Sort kashida info by charIndex ascending\r\n const sortedKashida = [...kashidaInfo].sort((a, b) => a.charIndex - b.charIndex);\r\n\r\n let tatweelsBeforeIndex = 0;\r\n\r\n for (const k of sortedKashida) {\r\n const tatweelCount = k.tatweelCount || 0;\r\n // If the original char index is after this kashida insertion point,\r\n // add the tatweels to the offset\r\n if (originalCharIndex > k.charIndex) {\r\n tatweelsBeforeIndex += tatweelCount;\r\n } else {\r\n break;\r\n }\r\n }\r\n\r\n return originalCharIndex + tatweelsBeforeIndex;\r\n }\r\n\r\n /**\r\n * Check if a display character index points to a tatweel character.\r\n * @param lineIndex - The line index\r\n * @param displayCharIndex - Character index in the display text\r\n * @returns True if the character at this index is a tatweel\r\n */\r\n _isTatweelAtDisplayIndex(lineIndex: number, displayCharIndex: number): boolean {\r\n const kashidaInfo = this.__kashidaInfo?.[lineIndex];\r\n if (!kashidaInfo || kashidaInfo.length === 0) {\r\n return false;\r\n }\r\n\r\n // Sort kashida info by charIndex ascending\r\n const sortedKashida = [...kashidaInfo].sort((a, b) => a.charIndex - b.charIndex);\r\n\r\n let tatweelsBeforeIndex = 0;\r\n\r\n for (const k of sortedKashida) {\r\n const tatweelCount = k.tatweelCount || 0;\r\n const tatweelStartPos = k.charIndex + 1 + tatweelsBeforeIndex;\r\n const tatweelEndPos = tatweelStartPos + tatweelCount;\r\n\r\n if (displayCharIndex >= tatweelStartPos && displayCharIndex < tatweelEndPos) {\r\n return true;\r\n }\r\n\r\n tatweelsBeforeIndex += tatweelCount;\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * Get the total number of tatweel characters inserted in a line.\r\n * @param lineIndex - The line index\r\n * @returns Total number of tatweels in this line\r\n */\r\n _getTatweelCountForLine(lineIndex: number): number {\r\n const kashidaInfo = this.__kashidaInfo?.[lineIndex];\r\n if (!kashidaInfo || kashidaInfo.length === 0) {\r\n return 0;\r\n }\r\n return kashidaInfo.reduce((sum, k) => sum + (k.tatweelCount || 0), 0);\r\n }\r\n\r\n /**\r\n * Get the original line length (without tatweels).\r\n * When kashida is applied, _textLines contains extra tatweel characters.\r\n * This returns the length as it would be in the original text.\r\n * @param lineIndex - The line index\r\n * @returns Original line length without tatweels\r\n */\r\n _getOriginalLineLength(lineIndex: number): number {\r\n const displayLength = this._textLines[lineIndex]?.length || 0;\r\n return displayLength - this._getTatweelCountForLine(lineIndex);\r\n }\r\n\r\n /**\r\n * Measure a single line given its index. Used to calculate the initial\r\n * text bounding box. The values are calculated and stored in __lineWidths cache.\r\n * @private\r\n * @param {Number} lineIndex line number\r\n * @return {Number} Line width\r\n */\r\n getLineWidth(lineIndex: number): number {\r\n if (this.__lineWidths[lineIndex] !== undefined) {\r\n return this.__lineWidths[lineIndex];\r\n }\r\n\r\n const { width } = this.measureLine(lineIndex);\r\n this.__lineWidths[lineIndex] = width;\r\n return width;\r\n }\r\n\r\n _getWidthOfCharSpacing() {\r\n if (this.charSpacing !== 0) {\r\n return (this.fontSize * this.charSpacing) / 1000;\r\n }\r\n return 0;\r\n }\r\n\r\n /**\r\n * Retrieves the value of property at given character position\r\n * @param {Number} lineIndex the line number\r\n * @param {Number} charIndex the character number\r\n * @param {String} property the property name\r\n * @returns the value of 'property'\r\n */\r\n getValueOfPropertyAt<T extends StylePropertiesType>(\r\n lineIndex: number,\r\n charIndex: number,\r\n property: T,\r\n ): this[T] {\r\n const charStyle = this._getStyleDeclaration(lineIndex, charIndex);\r\n return (charStyle[property] ?? this[property]) as this[T];\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n */\r\n _renderTextDecoration(\r\n ctx: CanvasRenderingContext2D,\r\n type: 'underline' | 'linethrough' | 'overline',\r\n ) {\r\n if (!this[type] && !this.styleHas(type)) {\r\n return;\r\n }\r\n let topOffset = this._getTopOffset();\r\n const leftOffset = this._getLeftOffset(),\r\n path = this.path,\r\n charSpacing = this._getWidthOfCharSpacing(),\r\n offsetAligner =\r\n type === 'linethrough' ? 0.5 : type === 'overline' ? 1 : 0,\r\n offsetY = this.offsets[type];\r\n for (let i = 0, len = this._textLines.length; i < len; i++) {\r\n const heightOfLine = this.getHeightOfLine(i);\r\n if (!this[type] && !this.styleHas(type, i)) {\r\n topOffset += heightOfLine;\r\n continue;\r\n }\r\n const line = this._textLines[i];\r\n const maxHeight = heightOfLine / this.lineHeight;\r\n const lineLeftOffset = this._getLineLeftOffset(i);\r\n let boxStart = 0;\r\n let boxWidth = 0;\r\n let lastDecoration = this.getValueOfPropertyAt(i, 0, type);\r\n let lastFill = this.getValueOfPropertyAt(i, 0, FILL);\r\n let lastTickness = this.getValueOfPropertyAt(\r\n i,\r\n 0,\r\n TEXT_DECORATION_THICKNESS,\r\n );\r\n let currentDecoration = lastDecoration;\r\n let currentFill = lastFill;\r\n let currentTickness = lastTickness;\r\n const top = topOffset + maxHeight * (1 - this._fontSizeFraction);\r\n let size = this.getHeightOfChar(i, 0);\r\n let dy = this.getValueOfPropertyAt(i, 0, 'deltaY');\r\n for (let j = 0, jlen = line.length; j < jlen; j++) {\r\n const charBox = this.__charBounds[i][j] as Required<GraphemeBBox>;\r\n currentDecoration = this.getValueOfPropertyAt(i, j, type);\r\n currentFill = this.getValueOfPropertyAt(i, j, FILL);\r\n currentTickness = this.getValueOfPropertyAt(\r\n i,\r\n j,\r\n TEXT_DECORATION_THICKNESS,\r\n );\r\n const currentSize = this.getHeightOfChar(i, j);\r\n const currentDy = this.getValueOfPropertyAt(i, j, 'deltaY');\r\n if (path && currentDecoration && currentFill) {\r\n const finalTickness = (this.fontSize * currentTickness) / 1000;\r\n ctx.save();\r\n // bug? verify lastFill is a valid fill here.\r\n ctx.fillStyle = lastFill as string;\r\n ctx.translate(charBox.renderLeft, charBox.renderTop);\r\n ctx.rotate(charBox.angle);\r\n ctx.fillRect(\r\n -charBox.kernedWidth / 2,\r\n offsetY * currentSize + currentDy - offsetAligner * finalTickness,\r\n charBox.kernedWidth,\r\n finalTickness,\r\n );\r\n ctx.restore();\r\n } else if (\r\n (currentDecoration !== lastDecoration ||\r\n currentFill !== lastFill ||\r\n currentSize !== size ||\r\n currentTickness !== lastTickness ||\r\n currentDy !== dy) &&\r\n boxWidth > 0\r\n ) {\r\n const finalTickness = (this.fontSize * lastTickness) / 1000;\r\n let drawStart = leftOffset + lineLeftOffset + boxStart;\r\n if (this.direction === 'rtl') {\r\n drawStart = this.width - drawStart - boxWidth;\r\n }\r\n if (lastDecoration && lastFill && lastTickness) {\r\n // bug? verify lastFill is a valid fill here.\r\n ctx.fillStyle = lastFill as string;\r\n ctx.fillRect(\r\n drawStart,\r\n top + offsetY * size + dy - offsetAligner * finalTickness,\r\n boxWidth,\r\n finalTickness,\r\n );\r\n }\r\n boxStart = charBox.left;\r\n boxWidth = charBox.width;\r\n lastDecoration = currentDecoration;\r\n lastTickness = currentTickness;\r\n lastFill = currentFill;\r\n size = currentSize;\r\n dy = currentDy;\r\n } else {\r\n boxWidth += charBox.kernedWidth;\r\n }\r\n }\r\n let drawStart = leftOffset + lineLeftOffset + boxStart;\r\n if (this.direction === 'rtl') {\r\n drawStart = this.width - drawStart - boxWidth;\r\n }\r\n ctx.fillStyle = currentFill as string;\r\n const finalTickness = (this.fontSize * currentTickness) / 1000;\r\n currentDecoration &&\r\n currentFill &&\r\n currentTickness &&\r\n ctx.fillRect(\r\n drawStart,\r\n top + offsetY * size + dy - offsetAligner * finalTickness,\r\n boxWidth - charSpacing,\r\n finalTickness,\r\n );\r\n topOffset += heightOfLine;\r\n }\r\n // if there is text background color no\r\n // other shadows should be casted\r\n this._removeShadow(ctx);\r\n }\r\n\r\n /**\r\n * return font declaration string for canvas context\r\n * @param {Object} [styleObject] object\r\n * @returns {String} font declaration formatted for canvas context.\r\n */\r\n _getFontDeclaration(\r\n {\r\n fontFamily = this.fontFamily,\r\n fontStyle = this.fontStyle,\r\n fontWeight = this.fontWeight,\r\n fontSize = this.fontSize,\r\n }: Partial<\r\n Pick<\r\n TextStyleDeclaration,\r\n 'fontFamily' | 'fontStyle' | 'fontWeight' | 'fontSize'\r\n >\r\n > = {},\r\n forMeasuring?: boolean,\r\n ): string {\r\n let parsedFontFamily =\r\n fontFamily.includes(\"'\") ||\r\n fontFamily.includes('\"') ||\r\n fontFamily.includes(',') ||\r\n FabricText.genericFonts.includes(fontFamily.toLowerCase())\r\n ? fontFamily\r\n : `\"${fontFamily}\"`;\r\n\r\n // For fonts like STV that don't support English/Latin characters,\r\n // add fallback fonts for consistent rendering of unsupported characters\r\n // Only add fallbacks during actual rendering, not for measurements\r\n if (!forMeasuring && // Only during rendering, not measuring\r\n !fontFamily.includes(',') && // Don't add fallbacks if already has them\r\n (fontFamily.toLowerCase().includes('stv') ||\r\n fontFamily.toLowerCase().includes('arabic') ||\r\n fontFamily.toLowerCase().includes('naskh') ||\r\n fontFamily.toLowerCase().includes('kufi'))) {\r\n // Add fallback fonts for unsupported characters (spaces, punctuation, etc.)\r\n parsedFontFamily = `${parsedFontFamily}, \"Arial Unicode MS\", Arial, sans-serif`;\r\n }\r\n\r\n return [\r\n fontStyle,\r\n fontWeight,\r\n `${forMeasuring ? this.CACHE_FONT_SIZE : fontSize}px`,\r\n parsedFontFamily,\r\n ].join(' ');\r\n }\r\n\r\n /**\r\n * Renders text instance on a specified context\r\n * @param {CanvasRenderingContext2D} ctx Context to render on\r\n */\r\n render(ctx: CanvasRenderingContext2D) {\r\n if (!this.visible) {\r\n return;\r\n }\r\n if (\r\n this.canvas &&\r\n this.canvas.skipOffscreen &&\r\n !this.group &&\r\n !this.isOnScreen()\r\n ) {\r\n return;\r\n }\r\n if (this._forceClearCache) {\r\n this.initDimensions();\r\n }\r\n super.render(ctx);\r\n }\r\n\r\n /**\r\n * Override this method to customize grapheme splitting\r\n * @todo the util `graphemeSplit` needs to be injectable in some way.\r\n * is more comfortable to inject the correct util rather than having to override text\r\n * in the middle of the prototype chain\r\n * @param {string} value\r\n * @returns {string[]} array of graphemes\r\n */\r\n graphemeSplit(value: string): string[] {\r\n return graphemeSplit(value);\r\n }\r\n\r\n /**\r\n * Returns the text as an array of lines.\r\n * @param {String} text text to split\r\n * @returns Lines in the text\r\n */\r\n _splitTextIntoLines(text: string): TextLinesInfo {\r\n const lines = text.split(this._reNewline),\r\n newLines = new Array<string[]>(lines.length),\r\n newLine = ['\\n'];\r\n let newText: string[] = [];\r\n for (let i = 0; i < lines.length; i++) {\r\n // Use BiDi-aware grapheme splitting for RTL text\r\n if (this.direction === 'rtl' || this._containsArabicText(lines[i])) {\r\n newLines[i] = segmentGraphemes(lines[i]);\r\n } else {\r\n newLines[i] = this.graphemeSplit(lines[i]);\r\n }\r\n newText = newText.concat(newLines[i], newLine);\r\n }\r\n newText.pop();\r\n return {\r\n _unwrappedLines: newLines,\r\n lines: lines,\r\n graphemeText: newText,\r\n graphemeLines: newLines,\r\n };\r\n }\r\n\r\n /**\r\n * Check if text contains Arabic characters\r\n * @private\r\n */\r\n _containsArabicText(text: string): boolean {\r\n return /[\\u0600-\\u06FF\\u0750-\\u077F\\uFB50-\\uFDFF\\uFE70-\\uFEFF]/.test(text);\r\n }\r\n\r\n /**\r\n * Returns object representation of an instance\r\n * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output\r\n * @return {Object} Object representation of an instance\r\n */\r\n toObject<\r\n T extends Omit<Props & TClassProperties<this>, keyof SProps>,\r\n K extends keyof T = never,\r\n >(propertiesToInclude: K[] = []): Pick<T, K> & SProps {\r\n return {\r\n ...super.toObject([...additionalProps, ...propertiesToInclude] as K[]),\r\n styles: stylesToArray(this.styles, this.text),\r\n ...(this.path ? { path: this.path.toObject() } : {}),\r\n };\r\n }\r\n\r\n set(key: string | any, value?: any) {\r\n const { textLayoutProperties } = this.constructor as typeof FabricText;\r\n super.set(key, value);\r\n let needsDims = false;\r\n let isAddingPath = false;\r\n if (typeof key === 'object') {\r\n for (const _key in key) {\r\n if (_key === 'path') {\r\n this.setPathInfo();\r\n }\r\n needsDims = needsDims || textLayoutProperties.includes(_key);\r\n isAddingPath = isAddingPath || _key === 'path';\r\n }\r\n } else {\r\n needsDims = textLayoutProperties.includes(key);\r\n isAddingPath = key === 'path';\r\n }\r\n if (isAddingPath) {\r\n this.setPathInfo();\r\n }\r\n if (needsDims && this.initialized) {\r\n // Clear browser lines when layout-affecting properties change\r\n clearBrowserLines(this);\r\n this.initDimensions();\r\n this.setCoords();\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * Returns complexity of an instance\r\n * @return {Number} complexity\r\n */\r\n complexity(): number {\r\n return 1;\r\n }\r\n\r\n /**\r\n * List of generic font families\r\n * @see https://developer.mozilla.org/en-US/docs/Web/CSS/font-family#generic-name\r\n */\r\n static genericFonts = [\r\n 'serif',\r\n 'sans-serif',\r\n 'monospace',\r\n 'cursive',\r\n 'fantasy',\r\n 'system-ui',\r\n 'ui-serif',\r\n 'ui-sans-serif',\r\n 'ui-monospace',\r\n 'ui-rounded',\r\n 'math',\r\n 'emoji',\r\n 'fangsong',\r\n ];\r\n\r\n /* _FROM_SVG_START_ */\r\n\r\n /**\r\n * List of attribute names to account for when parsing SVG element (used by {@link FabricText.fromElement})\r\n * @see: http://www.w3.org/TR/SVG/text.html#TextElement\r\n */\r\n static ATTRIBUTE_NAMES = SHARED_ATTRIBUTES.concat(\r\n 'x',\r\n 'y',\r\n 'dx',\r\n 'dy',\r\n 'font-family',\r\n 'font-style',\r\n 'font-weight',\r\n 'font-size',\r\n 'letter-spacing',\r\n 'text-decoration',\r\n 'text-anchor',\r\n );\r\n\r\n /**\r\n * Returns FabricText instance from an SVG element (<b>not yet implemented</b>)\r\n * @param {HTMLElement} element Element to parse\r\n * @param {Object} [options] Options object\r\n */\r\n static async fromElement(\r\n element: HTMLElement | SVGElement,\r\n options?: Abortable,\r\n cssRules?: CSSRules,\r\n ) {\r\n const parsedAttributes = parseAttributes(\r\n element,\r\n FabricText.ATTRIBUTE_NAMES,\r\n cssRules,\r\n );\r\n\r\n const {\r\n textAnchor = LEFT as typeof LEFT | typeof CENTER | typeof RIGHT,\r\n textDecoration = '',\r\n dx = 0,\r\n dy = 0,\r\n top = 0,\r\n left = 0,\r\n fontSize = DEFAULT_SVG_FONT_SIZE,\r\n strokeWidth = 1,\r\n ...restOfOptions\r\n } = { ...options, ...parsedAttributes };\r\n\r\n const textContent = (element.textContent || '')\r\n .replace(/^\\s+|\\s+$|\\n+/g, '')\r\n .replace(/\\s+/g, ' ');\r\n\r\n // this code here is probably the usual issue for SVG center find\r\n // this can later looked at again and probably removed.\r\n\r\n const text = new this(textContent, {\r\n left: left + dx,\r\n top: top + dy,\r\n underline: textDecoration.includes('underline'),\r\n overline: textDecoration.includes('overline'),\r\n linethrough: textDecoration.includes('line-through'),\r\n // we initialize this as 0\r\n strokeWidth: 0,\r\n fontSize,\r\n ...restOfOptions,\r\n }),\r\n textHeightScaleFactor = text.getScaledHeight() / text.height,\r\n lineHeightDiff =\r\n (text.height + text.strokeWidth) * text.lineHeight - text.height,\r\n scaledDiff = lineHeightDiff * textHeightScaleFactor,\r\n textHeight = text.getScaledHeight() + scaledDiff;\r\n\r\n let offX = 0;\r\n /*\r\n Adjust positioning:\r\n x/y attributes in SVG correspond to the bottom-left corner of text bounding box\r\n fabric output by default at top, left.\r\n */\r\n if (textAnchor === CENTER) {\r\n offX = text.getScaledWidth() / 2;\r\n }\r\n if (textAnchor === RIGHT) {\r\n offX = text.getScaledWidth();\r\n }\r\n text.set({\r\n left: text.left - offX,\r\n top:\r\n text.top -\r\n (textHeight - text.fontSize * (0.07 + text._fontSizeFraction)) /\r\n text.lineHeight,\r\n strokeWidth,\r\n });\r\n return text;\r\n }\r\n\r\n /* _FROM_SVG_END_ */\r\n\r\n /**\r\n * Check if the font is ready for accurate measurements\r\n * @private\r\n */\r\n _isFontReady(): boolean {\r\n if (typeof document === 'undefined' || !('fonts' in document)) {\r\n return true; // Assume ready in non-browser environments\r\n }\r\n\r\n try {\r\n return document.fonts.check(`${this.fontSize}px ${this.fontFamily}`);\r\n } catch (e) {\r\n return true; // Fallback to assuming ready if check fails\r\n }\r\n }\r\n\r\n /**\r\n * Schedule re-initialization after font loads\r\n * @private\r\n */\r\n _scheduleInitAfterFontLoad(): void {\r\n if (typeof document === 'undefined' || !('fonts' in document)) {\r\n return;\r\n }\r\n\r\n // Only schedule if not already waiting\r\n if ((this as any)._fontLoadScheduled) {\r\n return;\r\n }\r\n (this as any)._fontLoadScheduled = true;\r\n\r\n const fontSpec = `${this.fontSize}px ${this.fontFamily}`;\r\n document.fonts.load(fontSpec).then(() => {\r\n (this as any)._fontLoadScheduled = false;\r\n // Re-initialize dimensions with proper font metrics\r\n this.initDimensions();\r\n\r\n // Extra step for justify alignment after font loading\r\n if (this.textAlign && this.textAlign.includes(JUSTIFY)) {\r\n setTimeout(() => {\r\n if (this.enlargeSpaces) {\r\n this.enlargeSpaces();\r\n }\r\n this.canvas?.requestRenderAll();\r\n }, 10);\r\n } else {\r\n this.canvas?.requestRenderAll();\r\n }\r\n }).catch(() => {\r\n (this as any)._fontLoadScheduled = false;\r\n });\r\n }\r\n\r\n /**\r\n * Force complete text re-initialization (useful after JSON loading)\r\n */\r\n forceTextReinitialization(): void {\r\n // Clear all caches\r\n this._clearCache();\r\n this.dirty = true;\r\n\r\n // Force text splitting to rebuild internal structures\r\n this._splitText();\r\n\r\n // Re-initialize dimensions\r\n this.initDimensions();\r\n\r\n // Special handling for justify alignment\r\n if (this.textAlign && this.textAlign.includes(JUSTIFY)) {\r\n setTimeout(() => {\r\n if (this.__charBounds && this.__charBounds.length > 0 && this.enlargeSpaces) {\r\n this.enlargeSpaces();\r\n this.canvas?.requestRenderAll();\r\n }\r\n }, 10);\r\n }\r\n }\r\n\r\n /**\r\n * Returns FabricText instance from an object representation\r\n * @param {Object} object plain js Object to create an instance from\r\n * @returns {Promise<FabricText>}\r\n */\r\n static fromObject<\r\n T extends TOptions<SerializedTextProps>,\r\n S extends FabricText,\r\n >(object: T) {\r\n return this._fromObject<S>(\r\n {\r\n ...object,\r\n styles: stylesFromArray(object.styles || {}, object.text),\r\n },\r\n {\r\n extraParam: 'text',\r\n },\r\n ).then((textObject: S) => {\r\n // Ensure text object is properly initialized after JSON deserialization\r\n textObject.initialized = true;\r\n\r\n // Force reinitialization to ensure proper layout\r\n if (textObject._clearCache) {\r\n textObject._clearCache();\r\n }\r\n textObject.dirty = true;\r\n\r\n const fontSpec = `${textObject.fontSize}px ${textObject.fontFamily}`;\r\n\r\n // For custom fonts, ensure they're loaded before initializing dimensions\r\n if (\r\n typeof document !== 'undefined' &&\r\n 'fonts' in document &&\r\n textObject.fontFamily !== 'Arial' &&\r\n textObject.fontFamily !== 'Times New Roman'\r\n ) {\r\n return document.fonts\r\n .load(fontSpec)\r\n .then(() => {\r\n textObject.initialized = true;\r\n\r\n // Special handling for STV fonts which have measurement issues\r\n const isStvFont = textObject.fontFamily\r\n ?.toLowerCase()\r\n .includes('stv');\r\n if (isStvFont) {\r\n (textObject as any)._browserWrapCache = null;\r\n (textObject as any)._lastDimensionState = null;\r\n (textObject as any)._browserWrapInitialized = false;\r\n (textObject as any)._usingBrowserWrapping = true;\r\n (textObject as any).useOverlayEditing = true;\r\n\r\n const reinitWithDelay = (attempt: number) => {\r\n if ((textObject as any).forceTextReinitialization) {\r\n (textObject as any).forceTextReinitialization();\r\n } else {\r\n textObject.initDimensions();\r\n }\r\n if (textObject.width < 50 && attempt < 3) {\r\n setTimeout(() => reinitWithDelay(attempt + 1), 100 * attempt);\r\n }\r\n };\r\n reinitWithDelay(0);\r\n } else {\r\n if ((textObject as any).forceTextReinitialization) {\r\n (textObject as any).forceTextReinitialization();\r\n } else {\r\n textObject.initDimensions();\r\n }\r\n }\r\n return textObject;\r\n })\r\n .catch(() => {\r\n textObject.initialized = true;\r\n if ((textObject as any).forceTextReinitialization) {\r\n (textObject as any).forceTextReinitialization();\r\n } else {\r\n textObject.initDimensions();\r\n }\r\n return textObject;\r\n });\r\n } else {\r\n textObject.initialized = true;\r\n if ((textObject as any).forceTextReinitialization) {\r\n (textObject as any).forceTextReinitialization();\r\n } else {\r\n textObject.initDimensions();\r\n }\r\n return textObject;\r\n }\r\n });\r\n }\r\n}\r\n\r\napplyMixins(FabricText, [TextSVGExportMixin]);\r\nclassRegistry.setClass(FabricText);\r\nclassRegistry.setSVGClass(FabricText);\r\n"],"names":["measuringContext","getMeasuringContext","canvas","createCanvasElementFor","width","height","getContext","FabricText","StyledText","getDefaults","super","ownDefaults","constructor","text","options","_defineProperty","Object","assign","this","setOptions","styles","initialized","path","setPathInfo","initDimensions","setCoords","segmentsInfo","getPathSegmentsInfo","_splitText","browserLines","getBrowserLines","useOverlayEditing","_splitTextFromBrowserLines","newLines","_splitTextIntoLines","textLines","lines","_textLines","graphemeLines","_unwrappedTextLines","_unwrappedLines","_text","graphemeText","unwrappedLines","browserLine","push","lineGraphemes","graphemeSplit","concat","length","result","_isFontReady","_scheduleInitAfterFontLoad","enableAdvancedLayout","initDimensionsAdvanced","_clearCache","dirty","calcTextWidth","cursorWidth","MIN_TEXT_WIDTH","calcTextHeight","textAlign","includes","JUSTIFY","__charBounds","enlargeSpaces","kashidaRatio","none","short","medium","long","stylistic","kashida","__kashidaInfo","i","len","hasTextAfter","slice","some","line","lineText","Array","isArray","join","test","isVisualLastLine","isLastLine","isEndOfWrapping","currentLineWidth","getLineWidth","totalExtraSpace","spaces","match","_reSpacesAndTabs","numberOfSpaces","kashidaPoints","findKashidaPoints","hasKashidaPoints","kashidaSpace","perKashidaWidth","sortedPoints","sort","a","b","charIndex","ctx","font","_getFontDeclaration","tatweelWidth","measureText","ARABIC_TATWEEL","newLine","point","tatweelCount","Math","max","round","t","splice","undefined","__lineWidths","_measureLine","newLineWidth","remainingSpace","extraPerSpace","accumulatedOffset","j","charBound","left","_reSpaceAndTab","kernedWidth","_layoutTextAdvanced","_getAdvancedLayoutOptions","layoutText","ellipsis","wrap","align","_mapTextAlignToAlign","fontSize","lineHeight","letterSpacing","charSpacing","direction","fontFamily","fontStyle","fontWeight","verticalAlign","CENTER","RIGHT","JUSTIFY_LEFT","JUSTIFY_RIGHT","JUSTIFY_CENTER","layout","totalWidth","totalHeight","_convertLayoutToLegacyFormat","map","graphemes","flatMap","bounds","bound","top","y","deltaY","renderLeft","x","lineIndex","missingNewlineOffset","_lineIndex","get2DCursorLocation","selectionStart","skipWrapping","toString","complexity","_getCacheCanvasDimensions","dims","zoomX","zoomY","_render","isNotVisible","_setTextStyles","_renderTextLinesBackground","_renderTextDecoration","_renderText","__overlayEditor","paintFirst","STROKE","_renderTextStroke","_renderTextFill","charStyle","forMeasuring","textBaseline","pathAlign","TOP","BOTTOM","maxWidth","_renderTextLine","method","_renderChars","_buildKashidaDisplayLines","displayLines","kashidaInfo","sortedKashida","tatweels","repeat","textBackgroundColor","styleHas","originalFill","fillStyle","leftOffset","_getLeftOffset","lineTopOffset","_getTopOffset","heightOfLine","getHeightOfLine","jlen","lineLeftOffset","_getLineLeftOffset","drawStart","currentColor","boxWidth","boxStart","lastColor","getValueOfPropertyAt","charBox","save","translate","renderTop","rotate","angle","fillRect","_fontSizeFraction","restore","_removeShadow","_measureChar","_char","previousChar","prevCharStyle","fontCache","cache","getFontCache","fontDeclaration","couple","isTatweel","stylesAreEqual","fontMultiplier","CACHE_FONT_SIZE","coupleWidth","previousWidth","getHeightOfChar","measureLine","lineInfo","_getWidthOfCharSpacing","_justifyApplied","prevGrapheme","graphemeInfo","reverse","pathSide","llength","lineBounds","grapheme","_getGraphemeBox","positionInPath","totalPathLength","LEFT","pathStartOffset","_setGraphemeOnPath","numOfSpaces","centerPosition","info","getPointOnPath","pathOffset","PI","skipLeft","style","getCompleteStyleDeclaration","prevStyle","box","previousBox","__lineHeights","maxHeight","_fontSizeMult","_renderTextCommon","_this$textAlign","lineHeights","_this$textAlign2","fill","FILL","stroke","strokeWidth","isEmptyStyles","shadow","affectStroke","_setLineDash","strokeDashArray","beginPath","closePath","isJustify","isLtr","sign","currentDirection","chars","shortCut","actualStyle","nextStyle","timeToRender","drawingLeft","charsToRender","setAttribute","_renderChar","reduce","s","forEach","idx","_b$kernedWidth","console","log","toFixed","hasStyleChanged","_applyPatternGradientTransformText","filler","pCanvas","pCtx","moveTo","lineTo","toLive","_applyPatternGradientTransform","createPattern","handleFiller","property","offsetX","offsetY","isFiller","gradientUnits","gradientTransform","patternTransform","_setStrokeStyles","_ref","lineWidth","lineCap","strokeLineCap","lineDashOffset","strokeDashOffset","lineJoin","strokeLineJoin","miterLimit","strokeMiterLimit","_setFillStyles","_ref2","decl","_getStyleDeclaration","fullDecl","shouldFill","shouldStroke","fillOffsets","fillText","strokeOffsets","strokeText","setSuperscript","start","end","_setScript","superscript","setSubscript","subscript","schema","loc","dy","size","baseline","setSelectionStyles","lineDiff","_forceClearCache","_lastDimensionState","_displayToOriginalIndex","displayCharIndex","_this$__kashidaInfo","tatweelsBeforeIndex","k","tatweelStartPos","tatweelEndPos","_originalToDisplayIndex","originalCharIndex","_this$__kashidaInfo2","_isTatweelAtDisplayIndex","_this$__kashidaInfo3","_getTatweelCountForLine","_this$__kashidaInfo4","sum","_getOriginalLineLength","_this$_textLines$line","_charStyle$property","type","topOffset","offsetAligner","offsets","lastDecoration","lastFill","lastTickness","TEXT_DECORATION_THICKNESS","currentDecoration","currentFill","currentTickness","currentSize","currentDy","finalTickness","arguments","parsedFontFamily","genericFonts","toLowerCase","render","visible","skipOffscreen","group","isOnScreen","value","split","_reNewline","newText","_containsArabicText","segmentGraphemes","pop","toObject","propertiesToInclude","additionalProps","stylesToArray","set","key","textLayoutProperties","needsDims","isAddingPath","_key","clearBrowserLines","fromElement","element","cssRules","parsedAttributes","parseAttributes","ATTRIBUTE_NAMES","textAnchor","textDecoration","dx","DEFAULT_SVG_FONT_SIZE","restOfOptions","textContent","replace","underline","overline","linethrough","textHeightScaleFactor","getScaledHeight","scaledDiff","textHeight","offX","getScaledWidth","document","fonts","check","e","_fontLoadScheduled","fontSpec","load","then","_this$canvas2","setTimeout","_this$canvas","requestRenderAll","catch","forceTextReinitialization","_this$canvas3","fromObject","object","_fromObject","stylesFromArray","extraParam","textObject","_textObject$fontFamil","_browserWrapCache","_browserWrapInitialized","_usingBrowserWrapping","reinitWithDelay","attempt","cacheProperties","textDefaultValues","SHARED_ATTRIBUTES","applyMixins","TextSVGExportMixin","classRegistry","setClass","setSVGClass"],"mappings":"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"}
|