@nasser-sw/fabric 7.0.0-beta1 → 7.0.1-beta1

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.
@@ -222,6 +222,19 @@ export declare class Textbox<Props extends TOptions<TextboxProps> = Partial<Text
222
222
  _splitTextIntoLines(text: string): TextLinesInfo;
223
223
  getMinWidth(): number;
224
224
  _removeExtraneousStyles(): void;
225
+ /**
226
+ * Initialize event listeners for safety snap functionality
227
+ * @private
228
+ */
229
+ private initializeEventListeners;
230
+ /**
231
+ * Safety snap to prevent glyph clipping after manual resize.
232
+ * Similar to Polotno - checks if any glyphs are too close to edges
233
+ * and automatically expands width if needed.
234
+ * @private
235
+ * @param resizeOrigin - Which side was used for resizing ('left' or 'right')
236
+ */
237
+ private safetySnapWidth;
225
238
  /**
226
239
  * Returns object representation of an instance
227
240
  * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
@@ -1 +1 @@
1
- {"version":3,"file":"Textbox.d.ts","sourceRoot":"","sources":["../../../src/shapes/Textbox.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Textbox.d.ts","sourceRoot":"","sources":["../../../src/shapes/Textbox.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{defineProperty as t}from"../../_virtual/_rollupPluginBabelHelpers.min.mjs";import{IText as e}from"./IText/IText.min.mjs";import{classRegistry as i}from"../ClassRegistry.min.mjs";import{createTextboxDefaultControls as s}from"../controls/commonControls.min.mjs";import{JUSTIFY as n}from"./Text/constants.min.mjs";import{layoutText as h}from"../text/layout.min.mjs";const r={minWidth:20,dynamicMinWidth:2,lockScalingFlip:!0,noScaleCache:!1,_wordJoiners:/[ \t\r]/,splitByGrapheme:!1};class a extends e{static getDefaults(){return{...super.getDefaults(),...a.ownDefaults}}constructor(t,e){super(t,{...a.ownDefaults,...e})}static createControls(){return{controls:s()}}initDimensions(){if(this.initialized){if(this.enableAdvancedLayout)return this.initDimensionsAdvanced();this.isEditing&&this.initDelayedCursor(),this._clearCache(),this.dynamicMinWidth=0,this._styleMap=this._generateStyleMap(this._splitText()),this.dynamicMinWidth>this.width&&this._set("width",this.dynamicMinWidth),this.textAlign.includes(n)&&this.enlargeSpaces(),this.height=this.calcTextHeight()}}initDimensionsAdvanced(){if(!this.initialized)return;this.isEditing&&this.initDelayedCursor(),this._clearCache(),this.dynamicMinWidth=0;const t=h({text:this.text,width:this.width,height:this.height,wrap:this.wrap||"word",align:this._mapTextAlignToAlign(this.textAlign),ellipsis:this.ellipsis||!1,fontSize:this.fontSize,lineHeight:this.lineHeight,letterSpacing:this.letterSpacing||0,charSpacing:this.charSpacing,direction:"inherit"===this.direction?"ltr":this.direction,fontFamily:this.fontFamily,fontStyle:this.fontStyle,fontWeight:this.fontWeight,verticalAlign:this.verticalAlign||"top"});if(t.lines.length>0){const e=Math.max(...t.lines.map(t=>t.width));this.dynamicMinWidth=Math.max(this.minWidth,e)}if(this.dynamicMinWidth>this.width){this._set("width",this.dynamicMinWidth);const t=h({...this._getAdvancedLayoutOptions(),width:this.width});this.height=t.totalHeight,this._convertLayoutToLegacyFormat(t)}else this.height=t.totalHeight,this._convertLayoutToLegacyFormat(t);this._styleMap=this._generateStyleMapFromLayout(t),this.dirty=!0}_generateStyleMapFromLayout(t){const e={};let i=0,s=0;return t.lines.forEach((n,h)=>{n.text.includes("\n")&&h>0&&i++,e[h]={line:i,offset:0},s+=n.graphemes.length,h<t.lines.length-1&&(s+=1)}),e}_generateStyleMap(t){let e=0,i=0,s=0;const n={};for(let h=0;h<t.graphemeLines.length;h++)"\n"===t.graphemeText[s]&&h>0?(i=0,s++,e++):!this.splitByGrapheme&&this._reSpaceAndTab.test(t.graphemeText[s])&&h>0&&(i++,s++),n[h]={line:e,offset:i},s+=t.graphemeLines[h].length,i+=t.graphemeLines[h].length;return n}styleHas(t,e){if(this._styleMap&&!this.isWrapping){const t=this._styleMap[e];t&&(e=t.line)}return super.styleHas(t,e)}isEmptyStyles(t){if(!this.styles)return!0;let e,i=0,s=t+1,n=!1;const h=this._styleMap[t],r=this._styleMap[t+1];h&&(t=h.line,i=h.offset),r&&(s=r.line,n=s===t,e=r.offset);const a=void 0===t?this.styles:{line:this.styles[t]};for(const t in a)for(const s in a[t]){const h=parseInt(s,10);if(h>=i&&(!n||h<e))for(const e in a[t][s])return!1}return!0}_getStyleDeclaration(t,e){if(this._styleMap&&!this.isWrapping){const i=this._styleMap[t];if(!i)return{};t=i.line,e=i.offset+e}return super._getStyleDeclaration(t,e)}_setStyleDeclaration(t,e,i){const s=this._styleMap[t];super._setStyleDeclaration(s.line,s.offset+e,i)}_deleteStyleDeclaration(t,e){const i=this._styleMap[t];super._deleteStyleDeclaration(i.line,i.offset+e)}_getLineStyle(t){const e=this._styleMap[t];return!!this.styles[e.line]}_setLineStyle(t){const e=this._styleMap[t];super._setLineStyle(e.line)}_wrapText(t,e){this.isWrapping=!0;const i=this.getGraphemeDataForRender(t),s=[];for(let t=0;t<i.wordsData.length;t++)s.push(...this._wrapLine(t,e,i));return this.isWrapping=!1,s}getGraphemeDataForRender(t){const e=this.splitByGrapheme,i=e?"":" ";let s=0;return{wordsData:t.map((t,n)=>{let h=0;const r=e?this.graphemeSplit(t):this.wordSplit(t);return 0===r.length?[{word:[],width:0}]:r.map(t=>{const r=e?[t]:this.graphemeSplit(t),a=this._measureWord(r,n,h);return s=Math.max(a,s),h+=r.length+i.length,{word:r,width:a}})}),largestWordWidth:s}}_measureWord(t,e){let i,s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,n=0;for(let h=0,r=t.length;h<r;h++){n+=this._getGraphemeBox(t[h],e,h+s,i,true).kernedWidth,i=t[h]}return n}wordSplit(t){return t.split(this._wordJoiners)}_wrapLine(t,e,i){let{largestWordWidth:s,wordsData:n}=i,h=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0;const r=this._getWidthOfCharSpacing(),a=this.splitByGrapheme,l=[],o=a?"":" ";let p=0,c=[],d=0,m=0,g=!0;e-=h;const y=Math.max(e,s,this.dynamicMinWidth),u=n[t];let f;for(d=0,f=0;f<u.length;f++){const{word:e,width:i}=u[f];d+=e.length,p+=m+i-r,p>y&&!g?(l.push(c),c=[],p=i,g=!0):p+=r,g||a||c.push(o),c=c.concat(e),m=a?0:this._measureWord([o],t,d),d++,g=!1}return f&&l.push(c),s+h>this.dynamicMinWidth&&(this.dynamicMinWidth=s-r+h),l}isEndOfWrapping(t){return!this._styleMap[t+1]||this._styleMap[t+1].line!==this._styleMap[t].line}missingNewlineOffset(t,e){return this.splitByGrapheme&&!e?this.isEndOfWrapping(t)?1:0:1}_splitTextIntoLines(t){const e=super._splitTextIntoLines(t),i=this._wrapText(e.lines,this.width),s=new Array(i.length);for(let t=0;t<i.length;t++)s[t]=i[t].join("");return e.lines=s,e.graphemeLines=i,e}getMinWidth(){return Math.max(this.minWidth,this.dynamicMinWidth)}_removeExtraneousStyles(){const t=new Map;for(const e in this._styleMap){const i=parseInt(e,10);if(this._textLines[i]){const i=this._styleMap[e].line;t.set(`${i}`,!0)}}for(const e in this.styles)t.has(e)||delete this.styles[e]}toObject(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return super.toObject(["minWidth","splitByGrapheme",...t])}}t(a,"type","Textbox"),t(a,"textLayoutProperties",[...e.textLayoutProperties,"width"]),t(a,"ownDefaults",r),i.setClass(a);export{a as Textbox,r as textboxDefaultValues};
1
+ import{defineProperty as t}from"../../_virtual/_rollupPluginBabelHelpers.min.mjs";import{IText as i}from"./IText/IText.min.mjs";import{classRegistry as e}from"../ClassRegistry.min.mjs";import{createTextboxDefaultControls as s}from"../controls/commonControls.min.mjs";import{JUSTIFY as n}from"./Text/constants.min.mjs";import{layoutText as h}from"../text/layout.min.mjs";const o={minWidth:20,dynamicMinWidth:2,lockScalingFlip:!0,noScaleCache:!1,_wordJoiners:/[ \t\r]/,splitByGrapheme:!1};class r extends i{static getDefaults(){return{...super.getDefaults(),...r.ownDefaults}}constructor(t,i){super(t,{...r.ownDefaults,...i}),this.initializeEventListeners()}static createControls(){return{controls:s()}}initDimensions(){if(this.initialized){if(this.enableAdvancedLayout)return this.initDimensionsAdvanced();this.isEditing&&this.initDelayedCursor(),this._clearCache(),this.dynamicMinWidth=0,this._styleMap=this._generateStyleMap(this._splitText()),this.dynamicMinWidth>this.width&&this._set("width",this.dynamicMinWidth),this.textAlign.includes(n)&&this.enlargeSpaces(),this.height=this.calcTextHeight()}}initDimensionsAdvanced(){if(!this.initialized)return;this.isEditing&&this.initDelayedCursor(),this._clearCache(),this.dynamicMinWidth=0;const t=h({text:this.text,width:this.width,height:this.height,wrap:this.wrap||"word",align:this._mapTextAlignToAlign(this.textAlign),ellipsis:this.ellipsis||!1,fontSize:this.fontSize,lineHeight:this.lineHeight,letterSpacing:this.letterSpacing||0,charSpacing:this.charSpacing,direction:"inherit"===this.direction?"ltr":this.direction,fontFamily:this.fontFamily,fontStyle:this.fontStyle,fontWeight:this.fontWeight,verticalAlign:this.verticalAlign||"top"});if(t.lines.length>0){const i=Math.max(...t.lines.map(t=>t.width));this.dynamicMinWidth=Math.max(this.minWidth,i)}if(this.dynamicMinWidth>this.width){this._set("width",this.dynamicMinWidth);const t=h({...this._getAdvancedLayoutOptions(),width:this.width});this.height=t.totalHeight,this._convertLayoutToLegacyFormat(t)}else this.height=t.totalHeight,this._convertLayoutToLegacyFormat(t);this._styleMap=this._generateStyleMapFromLayout(t),this.dirty=!0}_generateStyleMapFromLayout(t){const i={};let e=0,s=0;return t.lines.forEach((n,h)=>{n.text.includes("\n")&&h>0&&e++,i[h]={line:e,offset:0},s+=n.graphemes.length,h<t.lines.length-1&&(s+=1)}),i}_generateStyleMap(t){let i=0,e=0,s=0;const n={};for(let h=0;h<t.graphemeLines.length;h++)"\n"===t.graphemeText[s]&&h>0?(e=0,s++,i++):!this.splitByGrapheme&&this._reSpaceAndTab.test(t.graphemeText[s])&&h>0&&(e++,s++),n[h]={line:i,offset:e},s+=t.graphemeLines[h].length,e+=t.graphemeLines[h].length;return n}styleHas(t,i){if(this._styleMap&&!this.isWrapping){const t=this._styleMap[i];t&&(i=t.line)}return super.styleHas(t,i)}isEmptyStyles(t){if(!this.styles)return!0;let i,e=0,s=t+1,n=!1;const h=this._styleMap[t],o=this._styleMap[t+1];h&&(t=h.line,e=h.offset),o&&(s=o.line,n=s===t,i=o.offset);const r=void 0===t?this.styles:{line:this.styles[t]};for(const t in r)for(const s in r[t]){const h=parseInt(s,10);if(h>=e&&(!n||h<i))for(const i in r[t][s])return!1}return!0}_getStyleDeclaration(t,i){if(this._styleMap&&!this.isWrapping){const e=this._styleMap[t];if(!e)return{};t=e.line,i=e.offset+i}return super._getStyleDeclaration(t,i)}_setStyleDeclaration(t,i,e){const s=this._styleMap[t];super._setStyleDeclaration(s.line,s.offset+i,e)}_deleteStyleDeclaration(t,i){const e=this._styleMap[t];super._deleteStyleDeclaration(e.line,e.offset+i)}_getLineStyle(t){const i=this._styleMap[t];return!!this.styles[i.line]}_setLineStyle(t){const i=this._styleMap[t];super._setLineStyle(i.line)}_wrapText(t,i){this.isWrapping=!0;const e=this.getGraphemeDataForRender(t),s=[];for(let t=0;t<e.wordsData.length;t++)s.push(...this._wrapLine(t,i,e));return this.isWrapping=!1,s}getGraphemeDataForRender(t){const i=this.splitByGrapheme,e=i?"":" ";let s=0;return{wordsData:t.map((t,n)=>{let h=0;const o=i?this.graphemeSplit(t):this.wordSplit(t);return 0===o.length?[{word:[],width:0}]:o.map(t=>{const o=i?[t]:this.graphemeSplit(t),r=this._measureWord(o,n,h);return s=Math.max(r,s),h+=o.length+e.length,{word:o,width:r}})}),largestWordWidth:s}}_measureWord(t,i){let e,s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,n=0;for(let h=0,o=t.length;h<o;h++){n+=this._getGraphemeBox(t[h],i,h+s,e,true).kernedWidth,e=t[h]}return n}wordSplit(t){return t.split(this._wordJoiners)}_wrapLine(t,i,e){let{largestWordWidth:s,wordsData:n}=e,h=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0;const o=this._getWidthOfCharSpacing(),r=this.splitByGrapheme,l=[],a=r?"":" ";let d=0,p=[],c=0,g=0,m=!0;i-=h;const f=Math.max(i,s,this.dynamicMinWidth),u=n[t];let y;for(c=0,y=0;y<u.length;y++){const{word:i,width:e}=u[y];c+=i.length,d+=g+e-o,d>f&&!m?(l.push(p),p=[],d=e,m=!0):d+=o,m||r||p.push(a),p=p.concat(i),g=r?0:this._measureWord([a],t,c),c++,m=!1}return y&&l.push(p),s+h>this.dynamicMinWidth&&(this.dynamicMinWidth=s-o+h),l}isEndOfWrapping(t){return!this._styleMap[t+1]||this._styleMap[t+1].line!==this._styleMap[t].line}missingNewlineOffset(t,i){return this.splitByGrapheme&&!i?this.isEndOfWrapping(t)?1:0:1}_splitTextIntoLines(t){const i=super._splitTextIntoLines(t),e=this._wrapText(i.lines,this.width),s=new Array(e.length);for(let t=0;t<e.length;t++)s[t]=e[t].join("");return i.lines=s,i.graphemeLines=e,i}getMinWidth(){return Math.max(this.minWidth,this.dynamicMinWidth)}_removeExtraneousStyles(){const t=new Map;for(const i in this._styleMap){const e=parseInt(i,10);if(this._textLines[e]){const e=this._styleMap[i].line;t.set(`${e}`,!0)}}for(const i in this.styles)t.has(i)||delete this.styles[i]}initializeEventListeners(){var t;let i=null;this.on("resizing",t=>{if(console.log("🔍 Resize event data:",t),t.transform){const{originX:e,originY:s}=t.transform;console.log("🔍 Transform origins:",{originX:e,originY:s}),i="right"===e?"left":"left"===e?"right":null,console.log("🎯 Setting resizeOrigin to:",i)}else if(t.originX){const{originX:e,originY:s}=t;console.log("🔍 Event origins:",{originX:e,originY:s}),i="right"===e?"left":"left"===e?"right":null,console.log("🎯 Setting resizeOrigin to:",i)}}),this.on("modified",()=>{const t=i;console.log("✅ Modified event fired - resize complete, triggering safety snap",{resizeOrigin:t}),setTimeout(()=>this.safetySnapWidth(t),10),i=null}),null===(t=this.canvas)||void 0===t||t.on("object:modified",t=>{if(t.target===this){const t=i;console.log("✅ Canvas object:modified fired for this textbox"),setTimeout(()=>this.safetySnapWidth(t),10),i=null}})}safetySnapWidth(t){var i,e;if(console.log("🔍 safetySnapWidth called",{isWrapping:this.isWrapping,hasTextLines:!!this._textLines,lineCount:(null===(i=this._textLines)||void 0===i?void 0:i.length)||0,currentWidth:this.width,type:this.type,text:this.text}),!this._textLines||"textbox"!==this.type.toLowerCase()||0===this._textLines.length)return void console.log("❌ Early return - missing requirements",{hasTextLines:!!this._textLines,typeMatch:"textbox"===this.type.toLowerCase(),actualType:this.type,hasLines:(null===(e=this._textLines)||void 0===e?void 0:e.length)>0});const s=this._textLines.length;if(0===s)return;let n=0,h=0;for(let t=0;t<s;t++){const i=this._textLines[t].join(""),e=this.getLineWidth(t);n=Math.max(n,e);if(/[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/.test(i)){const t=.15*(this.fontSize||16);h=Math.max(h,e+t)}else h=Math.max(h,e)}if(h>this.width-2){var o;const i=h+1;console.log(`Safety snap: ${this.width.toFixed(0)}px -> ${i.toFixed(0)}px`,{maxActualLineWidth:n.toFixed(1),maxRequiredWidth:h.toFixed(1),difference:(i-this.width).toFixed(1)});const e=this.left,s=this.top,r=i-this.width;this.set("width",i),this.initDimensions(),"left"===t?(console.log("🔧 Compensating for left-side resize",{originalLeft:e,widthIncrease:r,newLeft:e-r}),this.set({left:e-r,top:s})):console.log("✅ Right-side resize, no compensation needed"),this.setCoords(),this.__overlayEditor&&setTimeout(()=>{this.__overlayEditor.refresh()},0),null===(o=this.canvas)||void 0===o||o.requestRenderAll()}}toObject(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return super.toObject(["minWidth","splitByGrapheme",...t])}}t(r,"type","Textbox"),t(r,"textLayoutProperties",[...i.textLayoutProperties,"width"]),t(r,"ownDefaults",o),e.setClass(r);export{r as Textbox,o as textboxDefaultValues};
2
2
  //# sourceMappingURL=Textbox.min.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Textbox.min.mjs","sources":["../../../src/shapes/Textbox.ts"],"sourcesContent":["import type { TClassProperties, TOptions } from '../typedefs';\r\nimport { IText } from './IText/IText';\r\nimport { classRegistry } from '../ClassRegistry';\r\nimport { createTextboxDefaultControls } from '../controls/commonControls';\r\nimport { JUSTIFY } from './Text/constants';\r\nimport type { TextStyleDeclaration } from './Text/StyledText';\r\nimport type { SerializedITextProps, ITextProps } from './IText/IText';\r\nimport type { ITextEvents } from './IText/ITextBehavior';\r\nimport type { TextLinesInfo } from './Text/Text';\r\nimport type { Control } from '../controls/Control';\r\nimport { layoutText } from '../text/layout';\r\n\r\n// @TODO: Many things here are configuration related and shouldn't be on the class nor prototype\r\n// regexes, list of properties that are not suppose to change by instances, magic consts.\r\n// this will be a separated effort\r\nexport const textboxDefaultValues: Partial<TClassProperties<Textbox>> = {\r\n minWidth: 20,\r\n dynamicMinWidth: 2,\r\n lockScalingFlip: true,\r\n noScaleCache: false,\r\n _wordJoiners: /[ \\t\\r]/,\r\n splitByGrapheme: false,\r\n};\r\n\r\nexport type GraphemeData = {\r\n wordsData: {\r\n word: string[];\r\n width: number;\r\n }[][];\r\n largestWordWidth: number;\r\n};\r\n\r\nexport type StyleMap = Record<string, { line: number; offset: number }>;\r\n\r\n// @TODO this is not complete\r\ninterface UniqueTextboxProps {\r\n minWidth: number;\r\n splitByGrapheme: boolean;\r\n dynamicMinWidth: number;\r\n _wordJoiners: RegExp;\r\n}\r\n\r\nexport interface SerializedTextboxProps\r\n extends SerializedITextProps,\r\n Pick<UniqueTextboxProps, 'minWidth' | 'splitByGrapheme'> {}\r\n\r\nexport interface TextboxProps extends ITextProps, UniqueTextboxProps {}\r\n\r\n/**\r\n * Textbox class, based on IText, allows the user to resize the text rectangle\r\n * and wraps lines automatically. Textboxes have their Y scaling locked, the\r\n * user can only change width. Height is adjusted automatically based on the\r\n * wrapping of lines.\r\n */\r\nexport class Textbox<\r\n Props extends TOptions<TextboxProps> = Partial<TextboxProps>,\r\n SProps extends SerializedTextboxProps = SerializedTextboxProps,\r\n EventSpec extends ITextEvents = ITextEvents,\r\n >\r\n extends IText<Props, SProps, EventSpec>\r\n implements UniqueTextboxProps\r\n{\r\n /**\r\n * Minimum width of textbox, in pixels.\r\n * @type Number\r\n */\r\n declare minWidth: number;\r\n\r\n /**\r\n * Minimum calculated width of a textbox, in pixels.\r\n * fixed to 2 so that an empty textbox cannot go to 0\r\n * and is still selectable without text.\r\n * @type Number\r\n */\r\n declare dynamicMinWidth: number;\r\n\r\n /**\r\n * Use this boolean property in order to split strings that have no white space concept.\r\n * this is a cheap way to help with chinese/japanese\r\n * @type Boolean\r\n * @since 2.6.0\r\n */\r\n declare splitByGrapheme: boolean;\r\n\r\n declare _wordJoiners: RegExp;\r\n\r\n declare _styleMap: StyleMap;\r\n\r\n declare isWrapping: boolean;\r\n\r\n static type = 'Textbox';\r\n\r\n static textLayoutProperties = [...IText.textLayoutProperties, 'width'];\r\n\r\n static ownDefaults = textboxDefaultValues;\r\n\r\n static getDefaults(): Record<string, any> {\r\n return {\r\n ...super.getDefaults(),\r\n ...Textbox.ownDefaults,\r\n };\r\n }\r\n\r\n /**\r\n * Constructor\r\n * @param {String} text Text string\r\n * @param {Object} [options] Options object\r\n */\r\n constructor(text: string, options?: Props) {\r\n super(text, { ...Textbox.ownDefaults, ...options } as Props);\r\n }\r\n\r\n /**\r\n * Creates the default control object.\r\n * If you prefer to have on instance of controls shared among all objects\r\n * make this function return an empty object and add controls to the ownDefaults object\r\n */\r\n static createControls(): { controls: Record<string, Control> } {\r\n return { controls: createTextboxDefaultControls() };\r\n }\r\n\r\n /**\r\n * Unlike superclass's version of this function, Textbox does not update\r\n * its width.\r\n * @private\r\n * @override\r\n */\r\n initDimensions() {\r\n if (!this.initialized) {\r\n return;\r\n }\r\n \r\n // Use advanced layout if enabled\r\n if (this.enableAdvancedLayout) {\r\n return this.initDimensionsAdvanced();\r\n }\r\n \r\n this.isEditing && this.initDelayedCursor();\r\n this._clearCache();\r\n // clear dynamicMinWidth as it will be different after we re-wrap line\r\n this.dynamicMinWidth = 0;\r\n // wrap lines\r\n this._styleMap = this._generateStyleMap(this._splitText());\r\n // if after wrapping, the width is smaller than dynamicMinWidth, change the width and re-wrap\r\n if (this.dynamicMinWidth > this.width) {\r\n this._set('width', this.dynamicMinWidth);\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 this.enlargeSpaces();\r\n }\r\n // clear cache and re-calculate height\r\n this.height = this.calcTextHeight();\r\n }\r\n\r\n /**\r\n * Advanced dimensions calculation using new layout engine\r\n * @private\r\n */\r\n initDimensionsAdvanced() {\r\n if (!this.initialized) {\r\n return;\r\n }\r\n \r\n this.isEditing && this.initDelayedCursor();\r\n this._clearCache();\r\n this.dynamicMinWidth = 0;\r\n \r\n // Use new layout engine\r\n const layout = layoutText({\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 as any)._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 // Update dynamic minimum width based on layout\r\n if (layout.lines.length > 0) {\r\n const maxLineWidth = Math.max(...layout.lines.map(line => line.width));\r\n this.dynamicMinWidth = Math.max(this.minWidth, maxLineWidth);\r\n }\r\n \r\n // Adjust width if needed (preserving Textbox behavior)\r\n if (this.dynamicMinWidth > this.width) {\r\n this._set('width', this.dynamicMinWidth);\r\n // Re-layout with new width\r\n const newLayout = layoutText({\r\n ...(this as any)._getAdvancedLayoutOptions(),\r\n width: this.width,\r\n });\r\n this.height = newLayout.totalHeight;\r\n (this as any)._convertLayoutToLegacyFormat(newLayout);\r\n } else {\r\n this.height = layout.totalHeight;\r\n (this as any)._convertLayoutToLegacyFormat(layout);\r\n }\r\n \r\n // Generate style map for compatibility\r\n this._styleMap = this._generateStyleMapFromLayout(layout);\r\n this.dirty = true;\r\n }\r\n\r\n /**\r\n * Generate style map from new layout format\r\n * @private\r\n */\r\n _generateStyleMapFromLayout(layout: any): StyleMap {\r\n const map: StyleMap = {};\r\n let realLineCount = 0;\r\n let charCount = 0;\r\n\r\n layout.lines.forEach((line: any, i: number) => {\r\n if (line.text.includes('\\n') && i > 0) {\r\n realLineCount++;\r\n }\r\n \r\n map[i] = { line: realLineCount, offset: 0 };\r\n charCount += line.graphemes.length;\r\n \r\n if (i < layout.lines.length - 1) {\r\n charCount += 1; // newline character\r\n }\r\n });\r\n\r\n return map;\r\n }\r\n\r\n /**\r\n * Generate an object that translates the style object so that it is\r\n * broken up by visual lines (new lines and automatic wrapping).\r\n * The original text styles object is broken up by actual lines (new lines only),\r\n * which is only sufficient for Text / IText\r\n * @private\r\n */\r\n _generateStyleMap(textInfo: TextLinesInfo): StyleMap {\r\n let realLineCount = 0,\r\n realLineCharCount = 0,\r\n charCount = 0;\r\n const map: StyleMap = {};\r\n\r\n for (let i = 0; i < textInfo.graphemeLines.length; i++) {\r\n if (textInfo.graphemeText[charCount] === '\\n' && i > 0) {\r\n realLineCharCount = 0;\r\n charCount++;\r\n realLineCount++;\r\n } else if (\r\n !this.splitByGrapheme &&\r\n this._reSpaceAndTab.test(textInfo.graphemeText[charCount]) &&\r\n i > 0\r\n ) {\r\n // this case deals with space's that are removed from end of lines when wrapping\r\n realLineCharCount++;\r\n charCount++;\r\n }\r\n\r\n map[i] = { line: realLineCount, offset: realLineCharCount };\r\n\r\n charCount += textInfo.graphemeLines[i].length;\r\n realLineCharCount += textInfo.graphemeLines[i].length;\r\n }\r\n\r\n return map;\r\n }\r\n\r\n /**\r\n * Returns true if object has a style property or has it on a specified line\r\n * @param {Number} lineIndex\r\n * @return {Boolean}\r\n */\r\n styleHas(property: keyof TextStyleDeclaration, lineIndex: number): boolean {\r\n if (this._styleMap && !this.isWrapping) {\r\n const map = this._styleMap[lineIndex];\r\n if (map) {\r\n lineIndex = map.line;\r\n }\r\n }\r\n return super.styleHas(property, lineIndex);\r\n }\r\n\r\n /**\r\n * Returns true if object has no styling or no styling in a line\r\n * @param {Number} lineIndex , lineIndex is on wrapped lines.\r\n * @return {Boolean}\r\n */\r\n isEmptyStyles(lineIndex: number): boolean {\r\n if (!this.styles) {\r\n return true;\r\n }\r\n let offset = 0,\r\n nextLineIndex = lineIndex + 1,\r\n nextOffset: number,\r\n shouldLimit = false;\r\n const map = this._styleMap[lineIndex],\r\n mapNextLine = this._styleMap[lineIndex + 1];\r\n if (map) {\r\n lineIndex = map.line;\r\n offset = map.offset;\r\n }\r\n if (mapNextLine) {\r\n nextLineIndex = mapNextLine.line;\r\n shouldLimit = nextLineIndex === lineIndex;\r\n nextOffset = mapNextLine.offset;\r\n }\r\n const obj =\r\n typeof lineIndex === 'undefined'\r\n ? this.styles\r\n : { line: this.styles[lineIndex] };\r\n for (const p1 in obj) {\r\n for (const p2 in obj[p1]) {\r\n const p2Number = parseInt(p2, 10);\r\n if (p2Number >= offset && (!shouldLimit || p2Number < nextOffset!)) {\r\n for (const p3 in obj[p1][p2]) {\r\n return false;\r\n }\r\n }\r\n }\r\n }\r\n return true;\r\n }\r\n\r\n /**\r\n * @protected\r\n * @param {Number} lineIndex\r\n * @param {Number} charIndex\r\n * @return {TextStyleDeclaration} a style object reference to the existing one or a new empty object when undefined\r\n */\r\n _getStyleDeclaration(\r\n lineIndex: number,\r\n charIndex: number,\r\n ): TextStyleDeclaration {\r\n if (this._styleMap && !this.isWrapping) {\r\n const map = this._styleMap[lineIndex];\r\n if (!map) {\r\n return {};\r\n }\r\n lineIndex = map.line;\r\n charIndex = map.offset + charIndex;\r\n }\r\n return super._getStyleDeclaration(lineIndex, charIndex);\r\n }\r\n\r\n /**\r\n * @param {Number} lineIndex\r\n * @param {Number} charIndex\r\n * @param {Object} style\r\n * @private\r\n */\r\n protected _setStyleDeclaration(\r\n lineIndex: number,\r\n charIndex: number,\r\n style: object,\r\n ) {\r\n const map = this._styleMap[lineIndex];\r\n super._setStyleDeclaration(map.line, map.offset + charIndex, style);\r\n }\r\n\r\n /**\r\n * @param {Number} lineIndex\r\n * @param {Number} charIndex\r\n * @private\r\n */\r\n protected _deleteStyleDeclaration(lineIndex: number, charIndex: number) {\r\n const map = this._styleMap[lineIndex];\r\n super._deleteStyleDeclaration(map.line, map.offset + charIndex);\r\n }\r\n\r\n /**\r\n * probably broken need a fix\r\n * Returns the real style line that correspond to the wrapped lineIndex line\r\n * Used just to verify if the line does exist or not.\r\n * @param {Number} lineIndex\r\n * @returns {Boolean} if the line exists or not\r\n * @private\r\n */\r\n protected _getLineStyle(lineIndex: number): boolean {\r\n const map = this._styleMap[lineIndex];\r\n return !!this.styles[map.line];\r\n }\r\n\r\n /**\r\n * Set the line style to an empty object so that is initialized\r\n * @param {Number} lineIndex\r\n * @param {Object} style\r\n * @private\r\n */\r\n protected _setLineStyle(lineIndex: number) {\r\n const map = this._styleMap[lineIndex];\r\n super._setLineStyle(map.line);\r\n }\r\n\r\n /**\r\n * Wraps text using the 'width' property of Textbox. First this function\r\n * splits text on newlines, so we preserve newlines entered by the user.\r\n * Then it wraps each line using the width of the Textbox by calling\r\n * _wrapLine().\r\n * @param {Array} lines The string array of text that is split into lines\r\n * @param {Number} desiredWidth width you want to wrap to\r\n * @returns {Array} Array of lines\r\n */\r\n _wrapText(lines: string[], desiredWidth: number): string[][] {\r\n this.isWrapping = true;\r\n // extract all thewords and the widths to optimally wrap lines.\r\n const data = this.getGraphemeDataForRender(lines);\r\n const wrapped: string[][] = [];\r\n for (let i = 0; i < data.wordsData.length; i++) {\r\n wrapped.push(...this._wrapLine(i, desiredWidth, data));\r\n }\r\n this.isWrapping = false;\r\n return wrapped;\r\n }\r\n\r\n /**\r\n * For each line of text terminated by an hard line stop,\r\n * measure each word width and extract the largest word from all.\r\n * The returned words here are the one that at the end will be rendered.\r\n * @param {string[]} lines the lines we need to measure\r\n *\r\n */\r\n getGraphemeDataForRender(lines: string[]): GraphemeData {\r\n const splitByGrapheme = this.splitByGrapheme,\r\n infix = splitByGrapheme ? '' : ' ';\r\n\r\n let largestWordWidth = 0;\r\n\r\n const data = lines.map((line, lineIndex) => {\r\n let offset = 0;\r\n const wordsOrGraphemes = splitByGrapheme\r\n ? this.graphemeSplit(line)\r\n : this.wordSplit(line);\r\n\r\n if (wordsOrGraphemes.length === 0) {\r\n return [{ word: [], width: 0 }];\r\n }\r\n\r\n return wordsOrGraphemes.map((word: string) => {\r\n // if using splitByGrapheme words are already in graphemes.\r\n const graphemeArray = splitByGrapheme\r\n ? [word]\r\n : this.graphemeSplit(word);\r\n const width = this._measureWord(graphemeArray, lineIndex, offset);\r\n largestWordWidth = Math.max(width, largestWordWidth);\r\n offset += graphemeArray.length + infix.length;\r\n return { word: graphemeArray, width };\r\n });\r\n });\r\n\r\n return {\r\n wordsData: data,\r\n largestWordWidth,\r\n };\r\n }\r\n\r\n /**\r\n * Helper function to measure a string of text, given its lineIndex and charIndex offset\r\n * It gets called when charBounds are not available yet.\r\n * Override if necessary\r\n * Use with {@link Textbox#wordSplit}\r\n *\r\n * @param {CanvasRenderingContext2D} ctx\r\n * @param {String} text\r\n * @param {number} lineIndex\r\n * @param {number} charOffset\r\n * @returns {number}\r\n */\r\n _measureWord(word: string[], lineIndex: number, charOffset = 0): number {\r\n let width = 0,\r\n prevGrapheme;\r\n const skipLeft = true;\r\n for (let i = 0, len = word.length; i < len; i++) {\r\n const box = this._getGraphemeBox(\r\n word[i],\r\n lineIndex,\r\n i + charOffset,\r\n prevGrapheme,\r\n skipLeft,\r\n );\r\n width += box.kernedWidth;\r\n prevGrapheme = word[i];\r\n }\r\n return width;\r\n }\r\n\r\n /**\r\n * Override this method to customize word splitting\r\n * Use with {@link Textbox#_measureWord}\r\n * @param {string} value\r\n * @returns {string[]} array of words\r\n */\r\n wordSplit(value: string): string[] {\r\n return value.split(this._wordJoiners);\r\n }\r\n\r\n /**\r\n * Wraps a line of text using the width of the Textbox as desiredWidth\r\n * and leveraging the known width o words from GraphemeData\r\n * @private\r\n * @param {Number} lineIndex\r\n * @param {Number} desiredWidth width you want to wrap the line to\r\n * @param {GraphemeData} graphemeData an object containing all the lines' words width.\r\n * @param {Number} reservedSpace space to remove from wrapping for custom functionalities\r\n * @returns {Array} Array of line(s) into which the given text is wrapped\r\n * to.\r\n */\r\n _wrapLine(\r\n lineIndex: number,\r\n desiredWidth: number,\r\n { largestWordWidth, wordsData }: GraphemeData,\r\n reservedSpace = 0,\r\n ): string[][] {\r\n const additionalSpace = this._getWidthOfCharSpacing(),\r\n splitByGrapheme = this.splitByGrapheme,\r\n graphemeLines = [],\r\n infix = splitByGrapheme ? '' : ' ';\r\n\r\n let lineWidth = 0,\r\n line: string[] = [],\r\n // spaces in different languages?\r\n offset = 0,\r\n infixWidth = 0,\r\n lineJustStarted = true;\r\n\r\n desiredWidth -= reservedSpace;\r\n\r\n const maxWidth = Math.max(\r\n desiredWidth,\r\n largestWordWidth,\r\n this.dynamicMinWidth,\r\n );\r\n // layout words\r\n const data = wordsData[lineIndex];\r\n offset = 0;\r\n let i;\r\n for (i = 0; i < data.length; i++) {\r\n const { word, width: wordWidth } = data[i];\r\n offset += word.length;\r\n\r\n lineWidth += infixWidth + wordWidth - additionalSpace;\r\n if (lineWidth > maxWidth && !lineJustStarted) {\r\n graphemeLines.push(line);\r\n line = [];\r\n lineWidth = wordWidth;\r\n lineJustStarted = true;\r\n } else {\r\n lineWidth += additionalSpace;\r\n }\r\n\r\n if (!lineJustStarted && !splitByGrapheme) {\r\n line.push(infix);\r\n }\r\n line = line.concat(word);\r\n\r\n infixWidth = splitByGrapheme\r\n ? 0\r\n : this._measureWord([infix], lineIndex, offset);\r\n offset++;\r\n lineJustStarted = false;\r\n }\r\n\r\n i && graphemeLines.push(line);\r\n\r\n // TODO: this code is probably not necessary anymore.\r\n // it can be moved out of this function since largestWordWidth is now\r\n // known in advance\r\n if (largestWordWidth + reservedSpace > this.dynamicMinWidth) {\r\n this.dynamicMinWidth = largestWordWidth - additionalSpace + reservedSpace;\r\n }\r\n return graphemeLines;\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 * @param {Number} lineIndex text to split\r\n * @return {Boolean}\r\n */\r\n isEndOfWrapping(lineIndex: number): boolean {\r\n if (!this._styleMap[lineIndex + 1]) {\r\n // is last line, return true;\r\n return true;\r\n }\r\n if (this._styleMap[lineIndex + 1].line !== this._styleMap[lineIndex].line) {\r\n // this is last line before a line break, return true;\r\n return true;\r\n }\r\n return false;\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 * This is important only for splitByGrapheme at the end of wrapping.\r\n * If we are not wrapping the offset is always 1\r\n * @return Number\r\n */\r\n missingNewlineOffset(lineIndex: number, skipWrapping?: boolean): 0 | 1 {\r\n if (this.splitByGrapheme && !skipWrapping) {\r\n return this.isEndOfWrapping(lineIndex) ? 1 : 0;\r\n }\r\n return 1;\r\n }\r\n\r\n /**\r\n * Gets lines of text to render in the Textbox. This function calculates\r\n * text wrapping on the fly every time it is called.\r\n * @param {String} text text to split\r\n * @returns {Array} Array of lines in the Textbox.\r\n * @override\r\n */\r\n _splitTextIntoLines(text: string) {\r\n const newText = super._splitTextIntoLines(text),\r\n graphemeLines = this._wrapText(newText.lines, this.width),\r\n lines = new Array(graphemeLines.length);\r\n for (let i = 0; i < graphemeLines.length; i++) {\r\n lines[i] = graphemeLines[i].join('');\r\n }\r\n newText.lines = lines;\r\n newText.graphemeLines = graphemeLines;\r\n return newText;\r\n }\r\n\r\n getMinWidth() {\r\n return Math.max(this.minWidth, this.dynamicMinWidth);\r\n }\r\n\r\n _removeExtraneousStyles() {\r\n const linesToKeep = new Map();\r\n for (const prop in this._styleMap) {\r\n const propNumber = parseInt(prop, 10);\r\n if (this._textLines[propNumber]) {\r\n const lineIndex = this._styleMap[prop].line;\r\n linesToKeep.set(`${lineIndex}`, true);\r\n }\r\n }\r\n for (const prop in this.styles) {\r\n if (!linesToKeep.has(prop)) {\r\n delete this.styles[prop];\r\n }\r\n }\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 super.toObject<T, K>([\r\n 'minWidth',\r\n 'splitByGrapheme',\r\n ...propertiesToInclude,\r\n ] as K[]);\r\n }\r\n}\r\n\r\nclassRegistry.setClass(Textbox);\r\n"],"names":["textboxDefaultValues","minWidth","dynamicMinWidth","lockScalingFlip","noScaleCache","_wordJoiners","splitByGrapheme","Textbox","IText","getDefaults","super","ownDefaults","constructor","text","options","createControls","controls","createTextboxDefaultControls","initDimensions","this","initialized","enableAdvancedLayout","initDimensionsAdvanced","isEditing","initDelayedCursor","_clearCache","_styleMap","_generateStyleMap","_splitText","width","_set","textAlign","includes","JUSTIFY","enlargeSpaces","height","calcTextHeight","layout","layoutText","wrap","align","_mapTextAlignToAlign","ellipsis","fontSize","lineHeight","letterSpacing","charSpacing","direction","fontFamily","fontStyle","fontWeight","verticalAlign","lines","length","maxLineWidth","Math","max","map","line","newLayout","_getAdvancedLayoutOptions","totalHeight","_convertLayoutToLegacyFormat","_generateStyleMapFromLayout","dirty","realLineCount","charCount","forEach","i","offset","graphemes","textInfo","realLineCharCount","graphemeLines","graphemeText","_reSpaceAndTab","test","styleHas","property","lineIndex","isWrapping","isEmptyStyles","styles","nextOffset","nextLineIndex","shouldLimit","mapNextLine","obj","p1","p2","p2Number","parseInt","p3","_getStyleDeclaration","charIndex","_setStyleDeclaration","style","_deleteStyleDeclaration","_getLineStyle","_setLineStyle","_wrapText","desiredWidth","data","getGraphemeDataForRender","wrapped","wordsData","push","_wrapLine","infix","largestWordWidth","wordsOrGraphemes","graphemeSplit","wordSplit","word","graphemeArray","_measureWord","prevGrapheme","charOffset","arguments","undefined","len","_getGraphemeBox","kernedWidth","value","split","_ref","reservedSpace","additionalSpace","_getWidthOfCharSpacing","lineWidth","infixWidth","lineJustStarted","maxWidth","wordWidth","concat","isEndOfWrapping","missingNewlineOffset","skipWrapping","_splitTextIntoLines","newText","Array","join","getMinWidth","_removeExtraneousStyles","linesToKeep","Map","prop","propNumber","_textLines","set","has","toObject","propertiesToInclude","_defineProperty","textLayoutProperties","classRegistry","setClass"],"mappings":"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"}
1
+ {"version":3,"file":"Textbox.min.mjs","sources":["../../../src/shapes/Textbox.ts"],"sourcesContent":["import type { TClassProperties, TOptions } from '../typedefs';\r\nimport { IText } from './IText/IText';\r\nimport { classRegistry } from '../ClassRegistry';\r\nimport { createTextboxDefaultControls } from '../controls/commonControls';\r\nimport { JUSTIFY } from './Text/constants';\r\nimport type { TextStyleDeclaration } from './Text/StyledText';\r\nimport type { SerializedITextProps, ITextProps } from './IText/IText';\r\nimport type { ITextEvents } from './IText/ITextBehavior';\r\nimport type { TextLinesInfo } from './Text/Text';\r\nimport type { Control } from '../controls/Control';\r\nimport { layoutText } from '../text/layout';\r\n\r\n// @TODO: Many things here are configuration related and shouldn't be on the class nor prototype\r\n// regexes, list of properties that are not suppose to change by instances, magic consts.\r\n// this will be a separated effort\r\nexport const textboxDefaultValues: Partial<TClassProperties<Textbox>> = {\r\n minWidth: 20,\r\n dynamicMinWidth: 2,\r\n lockScalingFlip: true,\r\n noScaleCache: false,\r\n _wordJoiners: /[ \\t\\r]/,\r\n splitByGrapheme: false,\r\n};\r\n\r\nexport type GraphemeData = {\r\n wordsData: {\r\n word: string[];\r\n width: number;\r\n }[][];\r\n largestWordWidth: number;\r\n};\r\n\r\nexport type StyleMap = Record<string, { line: number; offset: number }>;\r\n\r\n// @TODO this is not complete\r\ninterface UniqueTextboxProps {\r\n minWidth: number;\r\n splitByGrapheme: boolean;\r\n dynamicMinWidth: number;\r\n _wordJoiners: RegExp;\r\n}\r\n\r\nexport interface SerializedTextboxProps\r\n extends SerializedITextProps,\r\n Pick<UniqueTextboxProps, 'minWidth' | 'splitByGrapheme'> {}\r\n\r\nexport interface TextboxProps extends ITextProps, UniqueTextboxProps {}\r\n\r\n/**\r\n * Textbox class, based on IText, allows the user to resize the text rectangle\r\n * and wraps lines automatically. Textboxes have their Y scaling locked, the\r\n * user can only change width. Height is adjusted automatically based on the\r\n * wrapping of lines.\r\n */\r\nexport class Textbox<\r\n Props extends TOptions<TextboxProps> = Partial<TextboxProps>,\r\n SProps extends SerializedTextboxProps = SerializedTextboxProps,\r\n EventSpec extends ITextEvents = ITextEvents,\r\n >\r\n extends IText<Props, SProps, EventSpec>\r\n implements UniqueTextboxProps\r\n{\r\n /**\r\n * Minimum width of textbox, in pixels.\r\n * @type Number\r\n */\r\n declare minWidth: number;\r\n\r\n /**\r\n * Minimum calculated width of a textbox, in pixels.\r\n * fixed to 2 so that an empty textbox cannot go to 0\r\n * and is still selectable without text.\r\n * @type Number\r\n */\r\n declare dynamicMinWidth: number;\r\n\r\n /**\r\n * Use this boolean property in order to split strings that have no white space concept.\r\n * this is a cheap way to help with chinese/japanese\r\n * @type Boolean\r\n * @since 2.6.0\r\n */\r\n declare splitByGrapheme: boolean;\r\n\r\n declare _wordJoiners: RegExp;\r\n\r\n declare _styleMap: StyleMap;\r\n\r\n declare isWrapping: boolean;\r\n\r\n static type = 'Textbox';\r\n\r\n static textLayoutProperties = [...IText.textLayoutProperties, 'width'];\r\n\r\n static ownDefaults = textboxDefaultValues;\r\n\r\n static getDefaults(): Record<string, any> {\r\n return {\r\n ...super.getDefaults(),\r\n ...Textbox.ownDefaults,\r\n };\r\n }\r\n\r\n /**\r\n * Constructor\r\n * @param {String} text Text string\r\n * @param {Object} [options] Options object\r\n */\r\n constructor(text: string, options?: Props) {\r\n super(text, { ...Textbox.ownDefaults, ...options } as Props);\r\n this.initializeEventListeners();\r\n }\r\n\r\n /**\r\n * Creates the default control object.\r\n * If you prefer to have on instance of controls shared among all objects\r\n * make this function return an empty object and add controls to the ownDefaults object\r\n */\r\n static createControls(): { controls: Record<string, Control> } {\r\n return { controls: createTextboxDefaultControls() };\r\n }\r\n\r\n /**\r\n * Unlike superclass's version of this function, Textbox does not update\r\n * its width.\r\n * @private\r\n * @override\r\n */\r\n initDimensions() {\r\n if (!this.initialized) {\r\n return;\r\n }\r\n \r\n // Use advanced layout if enabled\r\n if (this.enableAdvancedLayout) {\r\n return this.initDimensionsAdvanced();\r\n }\r\n \r\n this.isEditing && this.initDelayedCursor();\r\n this._clearCache();\r\n // clear dynamicMinWidth as it will be different after we re-wrap line\r\n this.dynamicMinWidth = 0;\r\n // wrap lines\r\n this._styleMap = this._generateStyleMap(this._splitText());\r\n // if after wrapping, the width is smaller than dynamicMinWidth, change the width and re-wrap\r\n if (this.dynamicMinWidth > this.width) {\r\n this._set('width', this.dynamicMinWidth);\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 this.enlargeSpaces();\r\n }\r\n // clear cache and re-calculate height\r\n this.height = this.calcTextHeight();\r\n }\r\n\r\n /**\r\n * Advanced dimensions calculation using new layout engine\r\n * @private\r\n */\r\n initDimensionsAdvanced() {\r\n if (!this.initialized) {\r\n return;\r\n }\r\n \r\n this.isEditing && this.initDelayedCursor();\r\n this._clearCache();\r\n this.dynamicMinWidth = 0;\r\n \r\n // Use new layout engine\r\n const layout = layoutText({\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 as any)._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 // Update dynamic minimum width based on layout\r\n if (layout.lines.length > 0) {\r\n const maxLineWidth = Math.max(...layout.lines.map(line => line.width));\r\n this.dynamicMinWidth = Math.max(this.minWidth, maxLineWidth);\r\n }\r\n \r\n // Adjust width if needed (preserving Textbox behavior)\r\n if (this.dynamicMinWidth > this.width) {\r\n this._set('width', this.dynamicMinWidth);\r\n // Re-layout with new width\r\n const newLayout = layoutText({\r\n ...(this as any)._getAdvancedLayoutOptions(),\r\n width: this.width,\r\n });\r\n this.height = newLayout.totalHeight;\r\n (this as any)._convertLayoutToLegacyFormat(newLayout);\r\n } else {\r\n this.height = layout.totalHeight;\r\n (this as any)._convertLayoutToLegacyFormat(layout);\r\n }\r\n \r\n // Generate style map for compatibility\r\n this._styleMap = this._generateStyleMapFromLayout(layout);\r\n this.dirty = true;\r\n }\r\n\r\n /**\r\n * Generate style map from new layout format\r\n * @private\r\n */\r\n _generateStyleMapFromLayout(layout: any): StyleMap {\r\n const map: StyleMap = {};\r\n let realLineCount = 0;\r\n let charCount = 0;\r\n\r\n layout.lines.forEach((line: any, i: number) => {\r\n if (line.text.includes('\\n') && i > 0) {\r\n realLineCount++;\r\n }\r\n \r\n map[i] = { line: realLineCount, offset: 0 };\r\n charCount += line.graphemes.length;\r\n \r\n if (i < layout.lines.length - 1) {\r\n charCount += 1; // newline character\r\n }\r\n });\r\n\r\n return map;\r\n }\r\n\r\n /**\r\n * Generate an object that translates the style object so that it is\r\n * broken up by visual lines (new lines and automatic wrapping).\r\n * The original text styles object is broken up by actual lines (new lines only),\r\n * which is only sufficient for Text / IText\r\n * @private\r\n */\r\n _generateStyleMap(textInfo: TextLinesInfo): StyleMap {\r\n let realLineCount = 0,\r\n realLineCharCount = 0,\r\n charCount = 0;\r\n const map: StyleMap = {};\r\n\r\n for (let i = 0; i < textInfo.graphemeLines.length; i++) {\r\n if (textInfo.graphemeText[charCount] === '\\n' && i > 0) {\r\n realLineCharCount = 0;\r\n charCount++;\r\n realLineCount++;\r\n } else if (\r\n !this.splitByGrapheme &&\r\n this._reSpaceAndTab.test(textInfo.graphemeText[charCount]) &&\r\n i > 0\r\n ) {\r\n // this case deals with space's that are removed from end of lines when wrapping\r\n realLineCharCount++;\r\n charCount++;\r\n }\r\n\r\n map[i] = { line: realLineCount, offset: realLineCharCount };\r\n\r\n charCount += textInfo.graphemeLines[i].length;\r\n realLineCharCount += textInfo.graphemeLines[i].length;\r\n }\r\n\r\n return map;\r\n }\r\n\r\n /**\r\n * Returns true if object has a style property or has it on a specified line\r\n * @param {Number} lineIndex\r\n * @return {Boolean}\r\n */\r\n styleHas(property: keyof TextStyleDeclaration, lineIndex: number): boolean {\r\n if (this._styleMap && !this.isWrapping) {\r\n const map = this._styleMap[lineIndex];\r\n if (map) {\r\n lineIndex = map.line;\r\n }\r\n }\r\n return super.styleHas(property, lineIndex);\r\n }\r\n\r\n /**\r\n * Returns true if object has no styling or no styling in a line\r\n * @param {Number} lineIndex , lineIndex is on wrapped lines.\r\n * @return {Boolean}\r\n */\r\n isEmptyStyles(lineIndex: number): boolean {\r\n if (!this.styles) {\r\n return true;\r\n }\r\n let offset = 0,\r\n nextLineIndex = lineIndex + 1,\r\n nextOffset: number,\r\n shouldLimit = false;\r\n const map = this._styleMap[lineIndex],\r\n mapNextLine = this._styleMap[lineIndex + 1];\r\n if (map) {\r\n lineIndex = map.line;\r\n offset = map.offset;\r\n }\r\n if (mapNextLine) {\r\n nextLineIndex = mapNextLine.line;\r\n shouldLimit = nextLineIndex === lineIndex;\r\n nextOffset = mapNextLine.offset;\r\n }\r\n const obj =\r\n typeof lineIndex === 'undefined'\r\n ? this.styles\r\n : { line: this.styles[lineIndex] };\r\n for (const p1 in obj) {\r\n for (const p2 in obj[p1]) {\r\n const p2Number = parseInt(p2, 10);\r\n if (p2Number >= offset && (!shouldLimit || p2Number < nextOffset!)) {\r\n for (const p3 in obj[p1][p2]) {\r\n return false;\r\n }\r\n }\r\n }\r\n }\r\n return true;\r\n }\r\n\r\n /**\r\n * @protected\r\n * @param {Number} lineIndex\r\n * @param {Number} charIndex\r\n * @return {TextStyleDeclaration} a style object reference to the existing one or a new empty object when undefined\r\n */\r\n _getStyleDeclaration(\r\n lineIndex: number,\r\n charIndex: number,\r\n ): TextStyleDeclaration {\r\n if (this._styleMap && !this.isWrapping) {\r\n const map = this._styleMap[lineIndex];\r\n if (!map) {\r\n return {};\r\n }\r\n lineIndex = map.line;\r\n charIndex = map.offset + charIndex;\r\n }\r\n return super._getStyleDeclaration(lineIndex, charIndex);\r\n }\r\n\r\n /**\r\n * @param {Number} lineIndex\r\n * @param {Number} charIndex\r\n * @param {Object} style\r\n * @private\r\n */\r\n protected _setStyleDeclaration(\r\n lineIndex: number,\r\n charIndex: number,\r\n style: object,\r\n ) {\r\n const map = this._styleMap[lineIndex];\r\n super._setStyleDeclaration(map.line, map.offset + charIndex, style);\r\n }\r\n\r\n /**\r\n * @param {Number} lineIndex\r\n * @param {Number} charIndex\r\n * @private\r\n */\r\n protected _deleteStyleDeclaration(lineIndex: number, charIndex: number) {\r\n const map = this._styleMap[lineIndex];\r\n super._deleteStyleDeclaration(map.line, map.offset + charIndex);\r\n }\r\n\r\n /**\r\n * probably broken need a fix\r\n * Returns the real style line that correspond to the wrapped lineIndex line\r\n * Used just to verify if the line does exist or not.\r\n * @param {Number} lineIndex\r\n * @returns {Boolean} if the line exists or not\r\n * @private\r\n */\r\n protected _getLineStyle(lineIndex: number): boolean {\r\n const map = this._styleMap[lineIndex];\r\n return !!this.styles[map.line];\r\n }\r\n\r\n /**\r\n * Set the line style to an empty object so that is initialized\r\n * @param {Number} lineIndex\r\n * @param {Object} style\r\n * @private\r\n */\r\n protected _setLineStyle(lineIndex: number) {\r\n const map = this._styleMap[lineIndex];\r\n super._setLineStyle(map.line);\r\n }\r\n\r\n /**\r\n * Wraps text using the 'width' property of Textbox. First this function\r\n * splits text on newlines, so we preserve newlines entered by the user.\r\n * Then it wraps each line using the width of the Textbox by calling\r\n * _wrapLine().\r\n * @param {Array} lines The string array of text that is split into lines\r\n * @param {Number} desiredWidth width you want to wrap to\r\n * @returns {Array} Array of lines\r\n */\r\n _wrapText(lines: string[], desiredWidth: number): string[][] {\r\n this.isWrapping = true;\r\n // extract all thewords and the widths to optimally wrap lines.\r\n const data = this.getGraphemeDataForRender(lines);\r\n const wrapped: string[][] = [];\r\n for (let i = 0; i < data.wordsData.length; i++) {\r\n wrapped.push(...this._wrapLine(i, desiredWidth, data));\r\n }\r\n this.isWrapping = false;\r\n return wrapped;\r\n }\r\n\r\n /**\r\n * For each line of text terminated by an hard line stop,\r\n * measure each word width and extract the largest word from all.\r\n * The returned words here are the one that at the end will be rendered.\r\n * @param {string[]} lines the lines we need to measure\r\n *\r\n */\r\n getGraphemeDataForRender(lines: string[]): GraphemeData {\r\n const splitByGrapheme = this.splitByGrapheme,\r\n infix = splitByGrapheme ? '' : ' ';\r\n\r\n let largestWordWidth = 0;\r\n\r\n const data = lines.map((line, lineIndex) => {\r\n let offset = 0;\r\n const wordsOrGraphemes = splitByGrapheme\r\n ? this.graphemeSplit(line)\r\n : this.wordSplit(line);\r\n\r\n if (wordsOrGraphemes.length === 0) {\r\n return [{ word: [], width: 0 }];\r\n }\r\n\r\n return wordsOrGraphemes.map((word: string) => {\r\n // if using splitByGrapheme words are already in graphemes.\r\n const graphemeArray = splitByGrapheme\r\n ? [word]\r\n : this.graphemeSplit(word);\r\n const width = this._measureWord(graphemeArray, lineIndex, offset);\r\n largestWordWidth = Math.max(width, largestWordWidth);\r\n offset += graphemeArray.length + infix.length;\r\n return { word: graphemeArray, width };\r\n });\r\n });\r\n\r\n return {\r\n wordsData: data,\r\n largestWordWidth,\r\n };\r\n }\r\n\r\n /**\r\n * Helper function to measure a string of text, given its lineIndex and charIndex offset\r\n * It gets called when charBounds are not available yet.\r\n * Override if necessary\r\n * Use with {@link Textbox#wordSplit}\r\n *\r\n * @param {CanvasRenderingContext2D} ctx\r\n * @param {String} text\r\n * @param {number} lineIndex\r\n * @param {number} charOffset\r\n * @returns {number}\r\n */\r\n _measureWord(word: string[], lineIndex: number, charOffset = 0): number {\r\n let width = 0,\r\n prevGrapheme;\r\n const skipLeft = true;\r\n for (let i = 0, len = word.length; i < len; i++) {\r\n const box = this._getGraphemeBox(\r\n word[i],\r\n lineIndex,\r\n i + charOffset,\r\n prevGrapheme,\r\n skipLeft,\r\n );\r\n width += box.kernedWidth;\r\n prevGrapheme = word[i];\r\n }\r\n return width;\r\n }\r\n\r\n /**\r\n * Override this method to customize word splitting\r\n * Use with {@link Textbox#_measureWord}\r\n * @param {string} value\r\n * @returns {string[]} array of words\r\n */\r\n wordSplit(value: string): string[] {\r\n return value.split(this._wordJoiners);\r\n }\r\n\r\n /**\r\n * Wraps a line of text using the width of the Textbox as desiredWidth\r\n * and leveraging the known width o words from GraphemeData\r\n * @private\r\n * @param {Number} lineIndex\r\n * @param {Number} desiredWidth width you want to wrap the line to\r\n * @param {GraphemeData} graphemeData an object containing all the lines' words width.\r\n * @param {Number} reservedSpace space to remove from wrapping for custom functionalities\r\n * @returns {Array} Array of line(s) into which the given text is wrapped\r\n * to.\r\n */\r\n _wrapLine(\r\n lineIndex: number,\r\n desiredWidth: number,\r\n { largestWordWidth, wordsData }: GraphemeData,\r\n reservedSpace = 0,\r\n ): string[][] {\r\n const additionalSpace = this._getWidthOfCharSpacing(),\r\n splitByGrapheme = this.splitByGrapheme,\r\n graphemeLines = [],\r\n infix = splitByGrapheme ? '' : ' ';\r\n\r\n let lineWidth = 0,\r\n line: string[] = [],\r\n // spaces in different languages?\r\n offset = 0,\r\n infixWidth = 0,\r\n lineJustStarted = true;\r\n\r\n desiredWidth -= reservedSpace;\r\n\r\n const maxWidth = Math.max(\r\n desiredWidth,\r\n largestWordWidth,\r\n this.dynamicMinWidth,\r\n );\r\n // layout words\r\n const data = wordsData[lineIndex];\r\n offset = 0;\r\n let i;\r\n for (i = 0; i < data.length; i++) {\r\n const { word, width: wordWidth } = data[i];\r\n offset += word.length;\r\n\r\n lineWidth += infixWidth + wordWidth - additionalSpace;\r\n if (lineWidth > maxWidth && !lineJustStarted) {\r\n graphemeLines.push(line);\r\n line = [];\r\n lineWidth = wordWidth;\r\n lineJustStarted = true;\r\n } else {\r\n lineWidth += additionalSpace;\r\n }\r\n\r\n if (!lineJustStarted && !splitByGrapheme) {\r\n line.push(infix);\r\n }\r\n line = line.concat(word);\r\n\r\n infixWidth = splitByGrapheme\r\n ? 0\r\n : this._measureWord([infix], lineIndex, offset);\r\n offset++;\r\n lineJustStarted = false;\r\n }\r\n\r\n i && graphemeLines.push(line);\r\n\r\n // TODO: this code is probably not necessary anymore.\r\n // it can be moved out of this function since largestWordWidth is now\r\n // known in advance\r\n if (largestWordWidth + reservedSpace > this.dynamicMinWidth) {\r\n this.dynamicMinWidth = largestWordWidth - additionalSpace + reservedSpace;\r\n }\r\n return graphemeLines;\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 * @param {Number} lineIndex text to split\r\n * @return {Boolean}\r\n */\r\n isEndOfWrapping(lineIndex: number): boolean {\r\n if (!this._styleMap[lineIndex + 1]) {\r\n // is last line, return true;\r\n return true;\r\n }\r\n if (this._styleMap[lineIndex + 1].line !== this._styleMap[lineIndex].line) {\r\n // this is last line before a line break, return true;\r\n return true;\r\n }\r\n return false;\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 * This is important only for splitByGrapheme at the end of wrapping.\r\n * If we are not wrapping the offset is always 1\r\n * @return Number\r\n */\r\n missingNewlineOffset(lineIndex: number, skipWrapping?: boolean): 0 | 1 {\r\n if (this.splitByGrapheme && !skipWrapping) {\r\n return this.isEndOfWrapping(lineIndex) ? 1 : 0;\r\n }\r\n return 1;\r\n }\r\n\r\n /**\r\n * Gets lines of text to render in the Textbox. This function calculates\r\n * text wrapping on the fly every time it is called.\r\n * @param {String} text text to split\r\n * @returns {Array} Array of lines in the Textbox.\r\n * @override\r\n */\r\n _splitTextIntoLines(text: string) {\r\n const newText = super._splitTextIntoLines(text),\r\n graphemeLines = this._wrapText(newText.lines, this.width),\r\n lines = new Array(graphemeLines.length);\r\n for (let i = 0; i < graphemeLines.length; i++) {\r\n lines[i] = graphemeLines[i].join('');\r\n }\r\n newText.lines = lines;\r\n newText.graphemeLines = graphemeLines;\r\n return newText;\r\n }\r\n\r\n getMinWidth() {\r\n return Math.max(this.minWidth, this.dynamicMinWidth);\r\n }\r\n\r\n _removeExtraneousStyles() {\r\n const linesToKeep = new Map();\r\n for (const prop in this._styleMap) {\r\n const propNumber = parseInt(prop, 10);\r\n if (this._textLines[propNumber]) {\r\n const lineIndex = this._styleMap[prop].line;\r\n linesToKeep.set(`${lineIndex}`, true);\r\n }\r\n }\r\n for (const prop in this.styles) {\r\n if (!linesToKeep.has(prop)) {\r\n delete this.styles[prop];\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Initialize event listeners for safety snap functionality\r\n * @private\r\n */\r\n private initializeEventListeners(): void {\r\n // Track which side is being used for resize to handle position compensation\r\n let resizeOrigin: 'left' | 'right' | null = null;\r\n \r\n // Detect resize origin during resizing\r\n this.on('resizing', (e: any) => {\r\n // Check transform origin to determine which side is being resized\r\n console.log('🔍 Resize event data:', e);\r\n if (e.transform) {\r\n const { originX, originY } = e.transform;\r\n console.log('🔍 Transform origins:', { originX, originY });\r\n // originX tells us which side is the anchor - opposite side is being dragged\r\n resizeOrigin = originX === 'right' ? 'left' : originX === 'left' ? 'right' : null;\r\n console.log('🎯 Setting resizeOrigin to:', resizeOrigin);\r\n } else if (e.originX) {\r\n const { originX, originY } = e;\r\n console.log('🔍 Event origins:', { originX, originY });\r\n resizeOrigin = originX === 'right' ? 'left' : originX === 'left' ? 'right' : null;\r\n console.log('🎯 Setting resizeOrigin to:', resizeOrigin);\r\n }\r\n });\r\n \r\n // Only trigger safety snap after resize is complete (not during)\r\n // Use 'modified' event which fires after user releases the mouse\r\n this.on('modified', () => {\r\n const currentResizeOrigin = resizeOrigin; // Capture the value before reset\r\n console.log('✅ Modified event fired - resize complete, triggering safety snap', { resizeOrigin: currentResizeOrigin });\r\n // Small delay to ensure text layout is updated\r\n setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);\r\n resizeOrigin = null; // Reset after capturing\r\n });\r\n \r\n // Also listen to canvas-level modified event as backup\r\n this.canvas?.on('object:modified', (e) => {\r\n if (e.target === this) {\r\n const currentResizeOrigin = resizeOrigin; // Capture the value before reset\r\n console.log('✅ Canvas object:modified fired for this textbox');\r\n setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);\r\n resizeOrigin = null; // Reset after capturing\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Safety snap to prevent glyph clipping after manual resize.\r\n * Similar to Polotno - checks if any glyphs are too close to edges\r\n * and automatically expands width if needed.\r\n * @private\r\n * @param resizeOrigin - Which side was used for resizing ('left' or 'right')\r\n */\r\n private safetySnapWidth(resizeOrigin?: 'left' | 'right' | null): void {\r\n console.log('🔍 safetySnapWidth called', { \r\n isWrapping: this.isWrapping, \r\n hasTextLines: !!this._textLines,\r\n lineCount: this._textLines?.length || 0,\r\n currentWidth: this.width,\r\n type: this.type,\r\n text: this.text\r\n });\r\n \r\n // For Textbox objects, we always want to check for clipping regardless of isWrapping flag\r\n if (!this._textLines || this.type.toLowerCase() !== 'textbox' || this._textLines.length === 0) {\r\n console.log('❌ Early return - missing requirements', {\r\n hasTextLines: !!this._textLines,\r\n typeMatch: this.type.toLowerCase() === 'textbox',\r\n actualType: this.type,\r\n hasLines: this._textLines?.length > 0\r\n });\r\n return;\r\n }\r\n \r\n const lineCount = this._textLines.length;\r\n if (lineCount === 0) return;\r\n\r\n // Check all lines, not just the last one\r\n let maxActualLineWidth = 0; // Actual measured width without buffers\r\n let maxRequiredWidth = 0; // Width including RTL buffer\r\n \r\n for (let i = 0; i < lineCount; i++) {\r\n const lineText = this._textLines[i].join(''); // Convert grapheme array to string\r\n const lineWidth = this.getLineWidth(i);\r\n maxActualLineWidth = Math.max(maxActualLineWidth, lineWidth);\r\n \r\n // RTL detection - regex for Arabic, Hebrew, and other RTL characters\r\n const rtlRegex = /[\\u0590-\\u05FF\\u0600-\\u06FF\\u0750-\\u077F\\uFB50-\\uFDFF\\uFE70-\\uFEFF]/;\r\n if (rtlRegex.test(lineText)) {\r\n // Add minimal RTL compensation buffer - just enough to prevent clipping\r\n const rtlBuffer = (this.fontSize || 16) * 0.15; // 15% of font size (much smaller)\r\n maxRequiredWidth = Math.max(maxRequiredWidth, lineWidth + rtlBuffer);\r\n } else {\r\n maxRequiredWidth = Math.max(maxRequiredWidth, lineWidth);\r\n }\r\n }\r\n\r\n // Safety margin - how close glyphs can get before we snap\r\n const safetyThreshold = 2; // px - very subtle trigger\r\n \r\n if (maxRequiredWidth > this.width - safetyThreshold) {\r\n // Set width to exactly what's needed + minimal safety margin\r\n const newWidth = maxRequiredWidth + 1; // Add just 1px safety margin\r\n console.log(`Safety snap: ${this.width.toFixed(0)}px -> ${newWidth.toFixed(0)}px`, {\r\n maxActualLineWidth: maxActualLineWidth.toFixed(1),\r\n maxRequiredWidth: maxRequiredWidth.toFixed(1),\r\n difference: (newWidth - this.width).toFixed(1)\r\n });\r\n \r\n // Store original position before width change\r\n const originalLeft = this.left;\r\n const originalTop = this.top;\r\n const widthIncrease = newWidth - this.width;\r\n \r\n // Change width \r\n this.set('width', newWidth);\r\n \r\n // Force text layout recalculation\r\n this.initDimensions();\r\n \r\n // Only compensate position when resizing from left handle\r\n // Right handle resize doesn't shift the text position\r\n if (resizeOrigin === 'left') {\r\n console.log('🔧 Compensating for left-side resize', {\r\n originalLeft,\r\n widthIncrease,\r\n newLeft: originalLeft - widthIncrease\r\n });\r\n // When resizing from left, the expansion pushes text right\r\n // Compensate by moving the textbox left by the width increase\r\n this.set({\r\n 'left': originalLeft - widthIncrease,\r\n 'top': originalTop\r\n });\r\n } else {\r\n console.log('✅ Right-side resize, no compensation needed');\r\n }\r\n \r\n this.setCoords();\r\n \r\n // Also refresh the overlay editor if it exists\r\n if ((this as any).__overlayEditor) {\r\n setTimeout(() => {\r\n (this as any).__overlayEditor.refresh();\r\n }, 0);\r\n }\r\n \r\n this.canvas?.requestRenderAll();\r\n }\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 super.toObject<T, K>([\r\n 'minWidth',\r\n 'splitByGrapheme',\r\n ...propertiesToInclude,\r\n ] as K[]);\r\n }\r\n}\r\n\r\nclassRegistry.setClass(Textbox);\r\n"],"names":["textboxDefaultValues","minWidth","dynamicMinWidth","lockScalingFlip","noScaleCache","_wordJoiners","splitByGrapheme","Textbox","IText","getDefaults","super","ownDefaults","constructor","text","options","this","initializeEventListeners","createControls","controls","createTextboxDefaultControls","initDimensions","initialized","enableAdvancedLayout","initDimensionsAdvanced","isEditing","initDelayedCursor","_clearCache","_styleMap","_generateStyleMap","_splitText","width","_set","textAlign","includes","JUSTIFY","enlargeSpaces","height","calcTextHeight","layout","layoutText","wrap","align","_mapTextAlignToAlign","ellipsis","fontSize","lineHeight","letterSpacing","charSpacing","direction","fontFamily","fontStyle","fontWeight","verticalAlign","lines","length","maxLineWidth","Math","max","map","line","newLayout","_getAdvancedLayoutOptions","totalHeight","_convertLayoutToLegacyFormat","_generateStyleMapFromLayout","dirty","realLineCount","charCount","forEach","i","offset","graphemes","textInfo","realLineCharCount","graphemeLines","graphemeText","_reSpaceAndTab","test","styleHas","property","lineIndex","isWrapping","isEmptyStyles","styles","nextOffset","nextLineIndex","shouldLimit","mapNextLine","obj","p1","p2","p2Number","parseInt","p3","_getStyleDeclaration","charIndex","_setStyleDeclaration","style","_deleteStyleDeclaration","_getLineStyle","_setLineStyle","_wrapText","desiredWidth","data","getGraphemeDataForRender","wrapped","wordsData","push","_wrapLine","infix","largestWordWidth","wordsOrGraphemes","graphemeSplit","wordSplit","word","graphemeArray","_measureWord","prevGrapheme","charOffset","arguments","undefined","len","_getGraphemeBox","kernedWidth","value","split","_ref","reservedSpace","additionalSpace","_getWidthOfCharSpacing","lineWidth","infixWidth","lineJustStarted","maxWidth","wordWidth","concat","isEndOfWrapping","missingNewlineOffset","skipWrapping","_splitTextIntoLines","newText","Array","join","getMinWidth","_removeExtraneousStyles","linesToKeep","Map","prop","propNumber","_textLines","set","has","_this$canvas","resizeOrigin","on","e","console","log","transform","originX","originY","currentResizeOrigin","setTimeout","safetySnapWidth","canvas","target","_this$_textLines","_this$_textLines2","hasTextLines","lineCount","currentWidth","type","toLowerCase","typeMatch","actualType","hasLines","maxActualLineWidth","maxRequiredWidth","lineText","getLineWidth","rtlBuffer","_this$canvas2","newWidth","toFixed","difference","originalLeft","left","originalTop","top","widthIncrease","newLeft","setCoords","__overlayEditor","refresh","requestRenderAll","toObject","propertiesToInclude","_defineProperty","textLayoutProperties","classRegistry","setClass"],"mappings":"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"}
@@ -43,6 +43,7 @@ class Textbox extends IText {
43
43
  ...Textbox.ownDefaults,
44
44
  ...options
45
45
  });
46
+ this.initializeEventListeners();
46
47
  }
47
48
 
48
49
  /**
@@ -550,6 +551,173 @@ class Textbox extends IText {
550
551
  }
551
552
  }
552
553
 
554
+ /**
555
+ * Initialize event listeners for safety snap functionality
556
+ * @private
557
+ */
558
+ initializeEventListeners() {
559
+ var _this$canvas;
560
+ // Track which side is being used for resize to handle position compensation
561
+ let resizeOrigin = null;
562
+
563
+ // Detect resize origin during resizing
564
+ this.on('resizing', e => {
565
+ // Check transform origin to determine which side is being resized
566
+ console.log('🔍 Resize event data:', e);
567
+ if (e.transform) {
568
+ const {
569
+ originX,
570
+ originY
571
+ } = e.transform;
572
+ console.log('🔍 Transform origins:', {
573
+ originX,
574
+ originY
575
+ });
576
+ // originX tells us which side is the anchor - opposite side is being dragged
577
+ resizeOrigin = originX === 'right' ? 'left' : originX === 'left' ? 'right' : null;
578
+ console.log('🎯 Setting resizeOrigin to:', resizeOrigin);
579
+ } else if (e.originX) {
580
+ const {
581
+ originX,
582
+ originY
583
+ } = e;
584
+ console.log('🔍 Event origins:', {
585
+ originX,
586
+ originY
587
+ });
588
+ resizeOrigin = originX === 'right' ? 'left' : originX === 'left' ? 'right' : null;
589
+ console.log('🎯 Setting resizeOrigin to:', resizeOrigin);
590
+ }
591
+ });
592
+
593
+ // Only trigger safety snap after resize is complete (not during)
594
+ // Use 'modified' event which fires after user releases the mouse
595
+ this.on('modified', () => {
596
+ const currentResizeOrigin = resizeOrigin; // Capture the value before reset
597
+ console.log('✅ Modified event fired - resize complete, triggering safety snap', {
598
+ resizeOrigin: currentResizeOrigin
599
+ });
600
+ // Small delay to ensure text layout is updated
601
+ setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);
602
+ resizeOrigin = null; // Reset after capturing
603
+ });
604
+
605
+ // Also listen to canvas-level modified event as backup
606
+ (_this$canvas = this.canvas) === null || _this$canvas === void 0 || _this$canvas.on('object:modified', e => {
607
+ if (e.target === this) {
608
+ const currentResizeOrigin = resizeOrigin; // Capture the value before reset
609
+ console.log('✅ Canvas object:modified fired for this textbox');
610
+ setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);
611
+ resizeOrigin = null; // Reset after capturing
612
+ }
613
+ });
614
+ }
615
+
616
+ /**
617
+ * Safety snap to prevent glyph clipping after manual resize.
618
+ * Similar to Polotno - checks if any glyphs are too close to edges
619
+ * and automatically expands width if needed.
620
+ * @private
621
+ * @param resizeOrigin - Which side was used for resizing ('left' or 'right')
622
+ */
623
+ safetySnapWidth(resizeOrigin) {
624
+ var _this$_textLines;
625
+ console.log('🔍 safetySnapWidth called', {
626
+ isWrapping: this.isWrapping,
627
+ hasTextLines: !!this._textLines,
628
+ lineCount: ((_this$_textLines = this._textLines) === null || _this$_textLines === void 0 ? void 0 : _this$_textLines.length) || 0,
629
+ currentWidth: this.width,
630
+ type: this.type,
631
+ text: this.text
632
+ });
633
+
634
+ // For Textbox objects, we always want to check for clipping regardless of isWrapping flag
635
+ if (!this._textLines || this.type.toLowerCase() !== 'textbox' || this._textLines.length === 0) {
636
+ var _this$_textLines2;
637
+ console.log('❌ Early return - missing requirements', {
638
+ hasTextLines: !!this._textLines,
639
+ typeMatch: this.type.toLowerCase() === 'textbox',
640
+ actualType: this.type,
641
+ hasLines: ((_this$_textLines2 = this._textLines) === null || _this$_textLines2 === void 0 ? void 0 : _this$_textLines2.length) > 0
642
+ });
643
+ return;
644
+ }
645
+ const lineCount = this._textLines.length;
646
+ if (lineCount === 0) return;
647
+
648
+ // Check all lines, not just the last one
649
+ let maxActualLineWidth = 0; // Actual measured width without buffers
650
+ let maxRequiredWidth = 0; // Width including RTL buffer
651
+
652
+ for (let i = 0; i < lineCount; i++) {
653
+ const lineText = this._textLines[i].join(''); // Convert grapheme array to string
654
+ const lineWidth = this.getLineWidth(i);
655
+ maxActualLineWidth = Math.max(maxActualLineWidth, lineWidth);
656
+
657
+ // RTL detection - regex for Arabic, Hebrew, and other RTL characters
658
+ const rtlRegex = /[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/;
659
+ if (rtlRegex.test(lineText)) {
660
+ // Add minimal RTL compensation buffer - just enough to prevent clipping
661
+ const rtlBuffer = (this.fontSize || 16) * 0.15; // 15% of font size (much smaller)
662
+ maxRequiredWidth = Math.max(maxRequiredWidth, lineWidth + rtlBuffer);
663
+ } else {
664
+ maxRequiredWidth = Math.max(maxRequiredWidth, lineWidth);
665
+ }
666
+ }
667
+
668
+ // Safety margin - how close glyphs can get before we snap
669
+ const safetyThreshold = 2; // px - very subtle trigger
670
+
671
+ if (maxRequiredWidth > this.width - safetyThreshold) {
672
+ var _this$canvas2;
673
+ // Set width to exactly what's needed + minimal safety margin
674
+ const newWidth = maxRequiredWidth + 1; // Add just 1px safety margin
675
+ console.log(`Safety snap: ${this.width.toFixed(0)}px -> ${newWidth.toFixed(0)}px`, {
676
+ maxActualLineWidth: maxActualLineWidth.toFixed(1),
677
+ maxRequiredWidth: maxRequiredWidth.toFixed(1),
678
+ difference: (newWidth - this.width).toFixed(1)
679
+ });
680
+
681
+ // Store original position before width change
682
+ const originalLeft = this.left;
683
+ const originalTop = this.top;
684
+ const widthIncrease = newWidth - this.width;
685
+
686
+ // Change width
687
+ this.set('width', newWidth);
688
+
689
+ // Force text layout recalculation
690
+ this.initDimensions();
691
+
692
+ // Only compensate position when resizing from left handle
693
+ // Right handle resize doesn't shift the text position
694
+ if (resizeOrigin === 'left') {
695
+ console.log('🔧 Compensating for left-side resize', {
696
+ originalLeft,
697
+ widthIncrease,
698
+ newLeft: originalLeft - widthIncrease
699
+ });
700
+ // When resizing from left, the expansion pushes text right
701
+ // Compensate by moving the textbox left by the width increase
702
+ this.set({
703
+ 'left': originalLeft - widthIncrease,
704
+ 'top': originalTop
705
+ });
706
+ } else {
707
+ console.log('✅ Right-side resize, no compensation needed');
708
+ }
709
+ this.setCoords();
710
+
711
+ // Also refresh the overlay editor if it exists
712
+ if (this.__overlayEditor) {
713
+ setTimeout(() => {
714
+ this.__overlayEditor.refresh();
715
+ }, 0);
716
+ }
717
+ (_this$canvas2 = this.canvas) === null || _this$canvas2 === void 0 || _this$canvas2.requestRenderAll();
718
+ }
719
+ }
720
+
553
721
  /**
554
722
  * Returns object representation of an instance
555
723
  * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output