@nasser-sw/fabric 7.0.1-beta1 → 7.0.1-beta10
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/0 +0 -0
- package/debug/{konva → konva-master}/CHANGELOG.md +2 -1
- package/debug/{konva → konva-master}/README.md +7 -3
- package/debug/{konva → konva-master}/package.json +1 -1
- package/debug/{konva → konva-master}/release.sh +1 -4
- package/debug/{konva → konva-master}/src/Canvas.ts +37 -0
- package/debug/{konva → konva-master}/src/shapes/Text.ts +2 -2
- package/dist/index.js +1853 -288
- 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 +1853 -288
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +1853 -288
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +1853 -288
- 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/Line.d.ts +33 -86
- package/dist/src/shapes/Line.d.ts.map +1 -1
- package/dist/src/shapes/Line.min.mjs +1 -1
- package/dist/src/shapes/Line.min.mjs.map +1 -1
- package/dist/src/shapes/Line.mjs +405 -159
- package/dist/src/shapes/Line.mjs.map +1 -1
- package/dist/src/shapes/Polyline.d.ts +7 -0
- package/dist/src/shapes/Polyline.d.ts.map +1 -1
- package/dist/src/shapes/Polyline.min.mjs +1 -1
- package/dist/src/shapes/Polyline.min.mjs.map +1 -1
- package/dist/src/shapes/Polyline.mjs +48 -16
- package/dist/src/shapes/Polyline.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.d.ts +19 -0
- 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 +302 -16
- package/dist/src/shapes/Text/Text.mjs.map +1 -1
- package/dist/src/shapes/Textbox.d.ts +43 -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 +521 -67
- package/dist/src/shapes/Textbox.mjs.map +1 -1
- package/dist/src/shapes/Triangle.d.ts +27 -2
- package/dist/src/shapes/Triangle.d.ts.map +1 -1
- package/dist/src/shapes/Triangle.min.mjs +1 -1
- package/dist/src/shapes/Triangle.min.mjs.map +1 -1
- package/dist/src/shapes/Triangle.mjs +72 -12
- package/dist/src/shapes/Triangle.mjs.map +1 -1
- package/dist/src/text/examples/arabicTextExample.d.ts +60 -0
- package/dist/src/text/examples/arabicTextExample.d.ts.map +1 -0
- package/dist/src/text/measure.d.ts +9 -0
- package/dist/src/text/measure.d.ts.map +1 -1
- package/dist/src/text/measure.min.mjs +1 -1
- package/dist/src/text/measure.min.mjs.map +1 -1
- package/dist/src/text/measure.mjs +175 -4
- package/dist/src/text/measure.mjs.map +1 -1
- package/dist/src/text/overlayEditor.d.ts.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 +155 -9
- package/dist/src/text/overlayEditor.mjs.map +1 -1
- package/dist/src/text/scriptUtils.d.ts +142 -0
- package/dist/src/text/scriptUtils.d.ts.map +1 -0
- package/dist/src/text/scriptUtils.min.mjs +2 -0
- package/dist/src/text/scriptUtils.min.mjs.map +1 -0
- package/dist/src/text/scriptUtils.mjs +212 -0
- package/dist/src/text/scriptUtils.mjs.map +1 -0
- package/dist/src/util/misc/cornerRadius.d.ts +70 -0
- package/dist/src/util/misc/cornerRadius.d.ts.map +1 -0
- package/dist/src/util/misc/cornerRadius.min.mjs +2 -0
- package/dist/src/util/misc/cornerRadius.min.mjs.map +1 -0
- package/dist/src/util/misc/cornerRadius.mjs +181 -0
- package/dist/src/util/misc/cornerRadius.mjs.map +1 -0
- package/dist-extensions/src/shapes/CustomLine.d.ts +10 -0
- package/dist-extensions/src/shapes/CustomLine.d.ts.map +1 -0
- package/dist-extensions/src/shapes/Line.d.ts +33 -86
- package/dist-extensions/src/shapes/Line.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Polyline.d.ts +7 -0
- package/dist-extensions/src/shapes/Polyline.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/Text.d.ts +19 -0
- package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Textbox.d.ts +43 -1
- package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Triangle.d.ts +27 -2
- package/dist-extensions/src/shapes/Triangle.d.ts.map +1 -1
- package/dist-extensions/src/text/measure.d.ts +9 -0
- package/dist-extensions/src/text/measure.d.ts.map +1 -1
- package/dist-extensions/src/text/overlayEditor.d.ts.map +1 -1
- package/dist-extensions/src/text/scriptUtils.d.ts +142 -0
- package/dist-extensions/src/text/scriptUtils.d.ts.map +1 -0
- package/dist-extensions/src/util/misc/cornerRadius.d.ts +70 -0
- package/dist-extensions/src/util/misc/cornerRadius.d.ts.map +1 -0
- package/fabric-test-editor.html +3552 -0
- package/fabric-test2.html +647 -0
- package/fabric.ts +182 -182
- package/fonts/STV Bold.ttf +0 -0
- package/fonts/STV Light.ttf +0 -0
- package/fonts/STV Regular.ttf +0 -0
- package/package.json +164 -164
- package/src/shapes/Line.ts +484 -157
- package/src/shapes/Polyline.ts +70 -29
- package/src/shapes/Text/Text.ts +317 -19
- package/src/shapes/Textbox.ts +544 -12
- package/src/shapes/Triangle.spec.ts +76 -0
- package/src/shapes/Triangle.ts +85 -15
- package/src/text/measure.ts +200 -50
- package/src/text/overlayEditor.ts +164 -12
- package/src/util/misc/cornerRadius.spec.ts +141 -0
- package/src/util/misc/cornerRadius.ts +269 -0
- /package/debug/{konva → konva-master}/LICENSE +0 -0
- /package/debug/{konva → konva-master}/gulpfile.mjs +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/ContainerParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/NodeParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/ShapeParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/jsdoc.conf.json +0 -0
- /package/debug/{konva → konva-master}/rollup.config.mjs +0 -0
- /package/debug/{konva → konva-master}/src/Animation.ts +0 -0
- /package/debug/{konva → konva-master}/src/BezierFunctions.ts +0 -0
- /package/debug/{konva → konva-master}/src/Container.ts +0 -0
- /package/debug/{konva → konva-master}/src/Context.ts +0 -0
- /package/debug/{konva → konva-master}/src/Core.ts +0 -0
- /package/debug/{konva → konva-master}/src/DragAndDrop.ts +0 -0
- /package/debug/{konva → konva-master}/src/Factory.ts +0 -0
- /package/debug/{konva → konva-master}/src/FastLayer.ts +0 -0
- /package/debug/{konva → konva-master}/src/Global.ts +0 -0
- /package/debug/{konva → konva-master}/src/Group.ts +0 -0
- /package/debug/{konva → konva-master}/src/Layer.ts +0 -0
- /package/debug/{konva → konva-master}/src/Node.ts +0 -0
- /package/debug/{konva → konva-master}/src/PointerEvents.ts +0 -0
- /package/debug/{konva → konva-master}/src/Shape.ts +0 -0
- /package/debug/{konva → konva-master}/src/Stage.ts +0 -0
- /package/debug/{konva → konva-master}/src/Tween.ts +0 -0
- /package/debug/{konva → konva-master}/src/Util.ts +0 -0
- /package/debug/{konva → konva-master}/src/Validators.ts +0 -0
- /package/debug/{konva → konva-master}/src/_CoreInternals.ts +0 -0
- /package/debug/{konva → konva-master}/src/_FullInternals.ts +0 -0
- /package/debug/{konva → konva-master}/src/canvas-backend.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Blur.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Brighten.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Brightness.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Contrast.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Emboss.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Enhance.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Grayscale.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/HSL.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/HSV.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Invert.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Kaleidoscope.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Mask.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Noise.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Pixelate.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Posterize.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/RGB.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/RGBA.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Sepia.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Solarize.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Threshold.ts +0 -0
- /package/debug/{konva → konva-master}/src/index.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Arc.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Arrow.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Circle.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Ellipse.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Image.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Label.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Line.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Path.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Rect.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/RegularPolygon.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Ring.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Sprite.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Star.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/TextPath.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Transformer.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Wedge.ts +0 -0
- /package/debug/{konva → konva-master}/src/skia-backend.ts +0 -0
- /package/debug/{konva → konva-master}/src/types.ts +0 -0
- /package/debug/{konva → konva-master}/tsconfig.json +0 -0
- /package/debug/{konva → konva-master}/tsconfig.test.json +0 -0
|
@@ -3,6 +3,7 @@ import { IText } from './IText/IText.mjs';
|
|
|
3
3
|
import { classRegistry } from '../ClassRegistry.mjs';
|
|
4
4
|
import { createTextboxDefaultControls } from '../controls/commonControls.mjs';
|
|
5
5
|
import { JUSTIFY } from './Text/constants.mjs';
|
|
6
|
+
import { fontLacksEnglishGlyphsCached } from '../text/measure.mjs';
|
|
6
7
|
import { layoutText } from '../text/layout.mjs';
|
|
7
8
|
|
|
8
9
|
// @TODO: Many things here are configuration related and shouldn't be on the class nor prototype
|
|
@@ -65,8 +66,27 @@ class Textbox extends IText {
|
|
|
65
66
|
*/
|
|
66
67
|
initDimensions() {
|
|
67
68
|
if (!this.initialized) {
|
|
69
|
+
this.initialized = true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Prevent rapid recalculations during moves
|
|
73
|
+
if (this._usingBrowserWrapping) {
|
|
74
|
+
const now = Date.now();
|
|
75
|
+
const lastCall = this._lastInitDimensionsTime || 0;
|
|
76
|
+
const isRapidCall = now - lastCall < 100;
|
|
77
|
+
const isDuringLoading = this._jsonLoading || !this._browserWrapInitialized;
|
|
78
|
+
if (isRapidCall && !isDuringLoading) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
this._lastInitDimensionsTime = now;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Skip if nothing changed
|
|
85
|
+
const currentState = `${this.text}|${this.width}|${this.fontSize}|${this.fontFamily}|${this.textAlign}`;
|
|
86
|
+
if (this._lastDimensionState === currentState && this._textLines && this._textLines.length > 0) {
|
|
68
87
|
return;
|
|
69
88
|
}
|
|
89
|
+
this._lastDimensionState = currentState;
|
|
70
90
|
|
|
71
91
|
// Use advanced layout if enabled
|
|
72
92
|
if (this.enableAdvancedLayout) {
|
|
@@ -77,17 +97,142 @@ class Textbox extends IText {
|
|
|
77
97
|
// clear dynamicMinWidth as it will be different after we re-wrap line
|
|
78
98
|
this.dynamicMinWidth = 0;
|
|
79
99
|
// wrap lines
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
100
|
+
const splitTextResult = this._splitText();
|
|
101
|
+
this._styleMap = this._generateStyleMap(splitTextResult);
|
|
102
|
+
|
|
103
|
+
// For browser wrapping, ensure _textLines is set from browser results
|
|
104
|
+
if (this._usingBrowserWrapping && splitTextResult && splitTextResult.lines) {
|
|
105
|
+
this._textLines = splitTextResult.lines.map(line => line.split(''));
|
|
106
|
+
|
|
107
|
+
// Store justify measurements and browser height
|
|
108
|
+
const justifyMeasurements = splitTextResult.justifySpaceMeasurements;
|
|
109
|
+
if (justifyMeasurements) {
|
|
110
|
+
this._styleMap.justifySpaceMeasurements = justifyMeasurements;
|
|
111
|
+
}
|
|
112
|
+
const actualHeight = splitTextResult.actualBrowserHeight;
|
|
113
|
+
if (actualHeight) {
|
|
114
|
+
this._actualBrowserHeight = actualHeight;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Don't auto-resize width when using browser wrapping to prevent width increases during moves
|
|
118
|
+
if (!this._usingBrowserWrapping && this.dynamicMinWidth > this.width) {
|
|
83
119
|
this._set('width', this.dynamicMinWidth);
|
|
84
120
|
}
|
|
121
|
+
|
|
122
|
+
// For browser wrapping fonts (like STV), ensure minimum width for new textboxes
|
|
123
|
+
// since these fonts can't measure English characters properly
|
|
124
|
+
if (this._usingBrowserWrapping && this.width < 50) {
|
|
125
|
+
console.log(`🔤 BROWSER WRAP: Font ${this.fontFamily} has width ${this.width}px, setting to 300px for usability`);
|
|
126
|
+
this.width = 300;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Mark browser wrapping as initialized when complete
|
|
130
|
+
if (this._usingBrowserWrapping) {
|
|
131
|
+
this._browserWrapInitialized = true;
|
|
132
|
+
}
|
|
85
133
|
if (this.textAlign.includes(JUSTIFY)) {
|
|
134
|
+
// For browser wrapping fonts, apply browser-calculated justify spaces
|
|
135
|
+
if (this._usingBrowserWrapping) {
|
|
136
|
+
console.log('🔤 BROWSER WRAP: Applying browser-calculated justify spaces');
|
|
137
|
+
this._applyBrowserJustifySpaces();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Don't apply justify alignment during drag operations to prevent snapping
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
const lastDragTime = this._lastInitDimensionsTime || 0;
|
|
144
|
+
const isDuringDrag = now - lastDragTime < 200; // 200ms window for drag detection
|
|
145
|
+
|
|
146
|
+
if (isDuringDrag) {
|
|
147
|
+
console.log('🔤 Skipping justify during drag operation to prevent snapping');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// For non-browser-wrapping fonts, use Fabric's justify system
|
|
86
152
|
// once text is measured we need to make space fatter to make justified text.
|
|
87
|
-
|
|
153
|
+
// Ensure __charBounds exists and fonts are ready before applying justify
|
|
154
|
+
if (this.__charBounds && this.__charBounds.length > 0) {
|
|
155
|
+
// Check if font is ready for accurate justify calculations
|
|
156
|
+
const fontReady = this._isFontReady ? this._isFontReady() : true;
|
|
157
|
+
if (fontReady) {
|
|
158
|
+
this.enlargeSpaces();
|
|
159
|
+
} else {
|
|
160
|
+
console.warn('⚠️ Textbox: Font not ready for justify, deferring enlargeSpaces');
|
|
161
|
+
// Defer justify calculation until font is ready
|
|
162
|
+
this._scheduleJustifyAfterFontLoad();
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
console.warn('⚠️ Textbox: __charBounds not ready for justify alignment, deferring enlargeSpaces');
|
|
166
|
+
// Defer the justify calculation until the next frame
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
if (this.__charBounds && this.__charBounds.length > 0 && this.enlargeSpaces) {
|
|
169
|
+
var _this$canvas;
|
|
170
|
+
console.log('🔧 Applying deferred Textbox justify alignment');
|
|
171
|
+
this.enlargeSpaces();
|
|
172
|
+
(_this$canvas = this.canvas) === null || _this$canvas === void 0 || _this$canvas.requestRenderAll();
|
|
173
|
+
}
|
|
174
|
+
}, 0);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Calculate height - use Fabric's calculation for proper text rendering space
|
|
178
|
+
if (this._usingBrowserWrapping && this._textLines && this._textLines.length > 0) {
|
|
179
|
+
const actualBrowserHeight = this._actualBrowserHeight;
|
|
180
|
+
const oldHeight = this.height;
|
|
181
|
+
// Use Fabric's height calculation since it knows how much space text rendering needs
|
|
182
|
+
this.height = this.calcTextHeight();
|
|
183
|
+
|
|
184
|
+
// Force canvas refresh and control update if height changed significantly
|
|
185
|
+
if (Math.abs(this.height - oldHeight) > 1) {
|
|
186
|
+
var _this$canvas2, _this$_textLines;
|
|
187
|
+
this.setCoords();
|
|
188
|
+
(_this$canvas2 = this.canvas) === null || _this$canvas2 === void 0 || _this$canvas2.requestRenderAll();
|
|
189
|
+
|
|
190
|
+
// DEBUG: Log exact positioning details
|
|
191
|
+
console.log(`🎯 POSITIONING DEBUG:`);
|
|
192
|
+
console.log(` Textbox height: ${this.height}px`);
|
|
193
|
+
console.log(` Textbox top: ${this.top}px`);
|
|
194
|
+
console.log(` Textbox left: ${this.left}px`);
|
|
195
|
+
console.log(` Text lines: ${((_this$_textLines = this._textLines) === null || _this$_textLines === void 0 ? void 0 : _this$_textLines.length) || 0}`);
|
|
196
|
+
console.log(` Font size: ${this.fontSize}px`);
|
|
197
|
+
console.log(` Line height: ${this.lineHeight || 1.16}`);
|
|
198
|
+
console.log(` Calculated line height: ${this.fontSize * (this.lineHeight || 1.16)}px`);
|
|
199
|
+
console.log(` _getTopOffset(): ${this._getTopOffset()}px`);
|
|
200
|
+
console.log(` calcTextHeight(): ${this.calcTextHeight()}px`);
|
|
201
|
+
console.log(` Browser height: ${actualBrowserHeight}px`);
|
|
202
|
+
console.log(` Height difference: ${this.height - this.calcTextHeight()}px`);
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
this.height = this.calcTextHeight();
|
|
88
206
|
}
|
|
89
|
-
|
|
90
|
-
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Schedule justify calculation after font loads (Textbox-specific)
|
|
211
|
+
* @private
|
|
212
|
+
*/
|
|
213
|
+
_scheduleJustifyAfterFontLoad() {
|
|
214
|
+
if (typeof document === 'undefined' || !('fonts' in document)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Only schedule if not already waiting
|
|
219
|
+
if (this._fontJustifyScheduled) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
this._fontJustifyScheduled = true;
|
|
223
|
+
const fontSpec = `${this.fontSize}px ${this.fontFamily}`;
|
|
224
|
+
document.fonts.load(fontSpec).then(() => {
|
|
225
|
+
var _this$canvas3;
|
|
226
|
+
this._fontJustifyScheduled = false;
|
|
227
|
+
console.log('🔧 Textbox: Font loaded, applying justify alignment');
|
|
228
|
+
|
|
229
|
+
// Re-run initDimensions to ensure proper justify calculation
|
|
230
|
+
this.initDimensions();
|
|
231
|
+
(_this$canvas3 = this.canvas) === null || _this$canvas3 === void 0 || _this$canvas3.requestRenderAll();
|
|
232
|
+
}).catch(() => {
|
|
233
|
+
this._fontJustifyScheduled = false;
|
|
234
|
+
console.warn('⚠️ Textbox: Font loading failed, justify may be incorrect');
|
|
235
|
+
});
|
|
91
236
|
}
|
|
92
237
|
|
|
93
238
|
/**
|
|
@@ -454,19 +599,33 @@ class Textbox extends IText {
|
|
|
454
599
|
width: wordWidth
|
|
455
600
|
} = data[i];
|
|
456
601
|
offset += word.length;
|
|
457
|
-
|
|
458
|
-
if
|
|
602
|
+
|
|
603
|
+
// Predictive wrapping: check if adding this word would exceed the width
|
|
604
|
+
const potentialLineWidth = lineWidth + infixWidth + wordWidth - additionalSpace;
|
|
605
|
+
// Use exact width to match overlay editor behavior
|
|
606
|
+
const conservativeMaxWidth = maxWidth; // No artificial buffer
|
|
607
|
+
|
|
608
|
+
// Debug logging for wrapping decisions
|
|
609
|
+
const currentLineText = line.join('');
|
|
610
|
+
console.log(`🔧 FABRIC WRAP CHECK: "${data[i].word}" -> potential: ${potentialLineWidth.toFixed(1)}px vs limit: ${conservativeMaxWidth.toFixed(1)}px`);
|
|
611
|
+
if (potentialLineWidth > conservativeMaxWidth && !lineJustStarted) {
|
|
612
|
+
// This word would exceed the width, wrap before adding it
|
|
613
|
+
console.log(`🔧 FABRIC WRAP! Line: "${currentLineText}" (${lineWidth.toFixed(1)}px)`);
|
|
459
614
|
graphemeLines.push(line);
|
|
460
615
|
line = [];
|
|
461
|
-
lineWidth = wordWidth;
|
|
616
|
+
lineWidth = wordWidth; // Start new line with just this word
|
|
462
617
|
lineJustStarted = true;
|
|
463
618
|
} else {
|
|
464
|
-
|
|
619
|
+
// Word fits, add it to current line
|
|
620
|
+
lineWidth = potentialLineWidth + additionalSpace;
|
|
465
621
|
}
|
|
466
622
|
if (!lineJustStarted && !splitByGrapheme) {
|
|
467
623
|
line.push(infix);
|
|
468
624
|
}
|
|
469
625
|
line = line.concat(word);
|
|
626
|
+
|
|
627
|
+
// Debug: show current line after adding word
|
|
628
|
+
console.log(`🔧 FABRIC AFTER ADD: Line now: "${line.join('')}" (${line.length} chars)`);
|
|
470
629
|
infixWidth = splitByGrapheme ? 0 : this._measureWord([infix], lineIndex, offset);
|
|
471
630
|
offset++;
|
|
472
631
|
lineJustStarted = false;
|
|
@@ -476,9 +635,19 @@ class Textbox extends IText {
|
|
|
476
635
|
// TODO: this code is probably not necessary anymore.
|
|
477
636
|
// it can be moved out of this function since largestWordWidth is now
|
|
478
637
|
// known in advance
|
|
479
|
-
|
|
638
|
+
// Don't modify dynamicMinWidth when using browser wrapping to prevent width increases
|
|
639
|
+
if (!this._usingBrowserWrapping && largestWordWidth + reservedSpace > this.dynamicMinWidth) {
|
|
640
|
+
console.log(`🔧 FABRIC updating dynamicMinWidth: ${this.dynamicMinWidth} -> ${largestWordWidth - additionalSpace + reservedSpace}`);
|
|
480
641
|
this.dynamicMinWidth = largestWordWidth - additionalSpace + reservedSpace;
|
|
642
|
+
} else if (this._usingBrowserWrapping) {
|
|
643
|
+
console.log(`🔤 BROWSER WRAP: Skipping dynamicMinWidth update to prevent width increase`);
|
|
481
644
|
}
|
|
645
|
+
|
|
646
|
+
// Debug: show final wrapped lines
|
|
647
|
+
console.log(`🔧 FABRIC FINAL LINES: ${graphemeLines.length} lines`);
|
|
648
|
+
graphemeLines.forEach((line, i) => {
|
|
649
|
+
console.log(` Line ${i + 1}: "${line.join('')}" (${line.length} chars)`);
|
|
650
|
+
});
|
|
482
651
|
return graphemeLines;
|
|
483
652
|
}
|
|
484
653
|
|
|
@@ -522,6 +691,260 @@ class Textbox extends IText {
|
|
|
522
691
|
* @override
|
|
523
692
|
*/
|
|
524
693
|
_splitTextIntoLines(text) {
|
|
694
|
+
// Check if we need browser wrapping using smart font detection
|
|
695
|
+
const needsBrowserWrapping = this.fontFamily && fontLacksEnglishGlyphsCached(this.fontFamily);
|
|
696
|
+
if (needsBrowserWrapping) {
|
|
697
|
+
// Cache key based on text content, width, font properties, AND text alignment
|
|
698
|
+
const textHash = text.length + text.slice(0, 50); // Include text content in cache key
|
|
699
|
+
const cacheKey = `${textHash}|${this.width}|${this.fontSize}|${this.fontFamily}|${this.textAlign}`;
|
|
700
|
+
|
|
701
|
+
// Check if we have a cached result and nothing has changed
|
|
702
|
+
if (this._browserWrapCache && this._browserWrapCache.key === cacheKey) {
|
|
703
|
+
const cachedResult = this._browserWrapCache.result;
|
|
704
|
+
|
|
705
|
+
// For justify alignment, ensure we have the measurements
|
|
706
|
+
if (this.textAlign.includes('justify') && !cachedResult.justifySpaceMeasurements) ; else {
|
|
707
|
+
return cachedResult;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const result = this._splitTextIntoLinesWithBrowser(text);
|
|
711
|
+
|
|
712
|
+
// Cache the result
|
|
713
|
+
this._browserWrapCache = {
|
|
714
|
+
key: cacheKey,
|
|
715
|
+
result
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
// Mark that we used browser wrapping to prevent dynamicMinWidth modifications
|
|
719
|
+
this._usingBrowserWrapping = true;
|
|
720
|
+
return result;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Clear the browser wrapping flag when using regular wrapping
|
|
724
|
+
this._usingBrowserWrapping = false;
|
|
725
|
+
|
|
726
|
+
// Default Fabric wrapping for other fonts
|
|
727
|
+
const newText = super._splitTextIntoLines(text),
|
|
728
|
+
graphemeLines = this._wrapText(newText.lines, this.width),
|
|
729
|
+
lines = new Array(graphemeLines.length);
|
|
730
|
+
for (let i = 0; i < graphemeLines.length; i++) {
|
|
731
|
+
lines[i] = graphemeLines[i].join('');
|
|
732
|
+
}
|
|
733
|
+
newText.lines = lines;
|
|
734
|
+
newText.graphemeLines = graphemeLines;
|
|
735
|
+
return newText;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Use browser's native text wrapping for accurate handling of fonts without English glyphs
|
|
740
|
+
* @private
|
|
741
|
+
*/
|
|
742
|
+
_splitTextIntoLinesWithBrowser(text) {
|
|
743
|
+
if (typeof document === 'undefined') {
|
|
744
|
+
// Fallback to regular wrapping in Node.js
|
|
745
|
+
return this._splitTextIntoLinesDefault(text);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Create a hidden element that mimics the overlay editor
|
|
749
|
+
const testElement = document.createElement('div');
|
|
750
|
+
testElement.style.position = 'absolute';
|
|
751
|
+
testElement.style.left = '-9999px';
|
|
752
|
+
testElement.style.visibility = 'hidden';
|
|
753
|
+
testElement.style.fontSize = `${this.fontSize}px`;
|
|
754
|
+
testElement.style.fontFamily = `"${this.fontFamily}"`;
|
|
755
|
+
testElement.style.fontWeight = String(this.fontWeight || 'normal');
|
|
756
|
+
testElement.style.fontStyle = String(this.fontStyle || 'normal');
|
|
757
|
+
testElement.style.lineHeight = String(this.lineHeight || 1.16);
|
|
758
|
+
testElement.style.width = `${this.width}px`;
|
|
759
|
+
testElement.style.direction = this.direction || 'ltr';
|
|
760
|
+
testElement.style.whiteSpace = 'pre-wrap';
|
|
761
|
+
testElement.style.wordBreak = 'normal';
|
|
762
|
+
testElement.style.overflowWrap = 'break-word';
|
|
763
|
+
|
|
764
|
+
// Set browser-native text alignment (including justify)
|
|
765
|
+
if (this.textAlign.includes('justify')) {
|
|
766
|
+
testElement.style.textAlign = 'justify';
|
|
767
|
+
testElement.style.textAlignLast = 'auto'; // Let browser decide last line alignment
|
|
768
|
+
} else {
|
|
769
|
+
testElement.style.textAlign = this.textAlign;
|
|
770
|
+
}
|
|
771
|
+
testElement.textContent = text;
|
|
772
|
+
document.body.appendChild(testElement);
|
|
773
|
+
|
|
774
|
+
// Get the browser's natural line breaks
|
|
775
|
+
const range = document.createRange();
|
|
776
|
+
const lines = [];
|
|
777
|
+
const graphemeLines = [];
|
|
778
|
+
try {
|
|
779
|
+
// Simple approach: split by measuring character positions
|
|
780
|
+
const textNode = testElement.firstChild;
|
|
781
|
+
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
|
782
|
+
let currentLineStart = 0;
|
|
783
|
+
const textLength = text.length;
|
|
784
|
+
let previousBottom = 0;
|
|
785
|
+
for (let i = 0; i <= textLength; i++) {
|
|
786
|
+
range.setStart(textNode, currentLineStart);
|
|
787
|
+
range.setEnd(textNode, i);
|
|
788
|
+
const rect = range.getBoundingClientRect();
|
|
789
|
+
if (i > currentLineStart && (rect.bottom > previousBottom + 5 || i === textLength)) {
|
|
790
|
+
// New line detected or end of text
|
|
791
|
+
const lineEnd = i === textLength ? i : i - 1;
|
|
792
|
+
const lineText = text.substring(currentLineStart, lineEnd).trim();
|
|
793
|
+
if (lineText) {
|
|
794
|
+
lines.push(lineText);
|
|
795
|
+
// Convert to graphemes for compatibility
|
|
796
|
+
const graphemeLine = lineText.split('');
|
|
797
|
+
graphemeLines.push(graphemeLine);
|
|
798
|
+
}
|
|
799
|
+
currentLineStart = lineEnd;
|
|
800
|
+
previousBottom = rect.bottom;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
} catch (error) {
|
|
805
|
+
console.warn('Browser wrapping failed, using fallback:', error);
|
|
806
|
+
document.body.removeChild(testElement);
|
|
807
|
+
return this._splitTextIntoLinesDefault(text);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Extract actual browser height BEFORE removing element
|
|
811
|
+
const actualBrowserHeight = testElement.scrollHeight;
|
|
812
|
+
const offsetHeight = testElement.offsetHeight;
|
|
813
|
+
const clientHeight = testElement.clientHeight;
|
|
814
|
+
const boundingRect = testElement.getBoundingClientRect();
|
|
815
|
+
console.log(`🔤 Browser element measurements:`);
|
|
816
|
+
console.log(` scrollHeight: ${actualBrowserHeight}px (content + padding + hidden overflow)`);
|
|
817
|
+
console.log(` offsetHeight: ${offsetHeight}px (content + padding + border)`);
|
|
818
|
+
console.log(` clientHeight: ${clientHeight}px (content + padding, no border/scrollbar)`);
|
|
819
|
+
console.log(` boundingRect.height: ${boundingRect.height}px (actual rendered height)`);
|
|
820
|
+
console.log(` Font size: ${this.fontSize}px, Line height: ${this.lineHeight || 1.16}, Lines: ${lines.length}`);
|
|
821
|
+
|
|
822
|
+
// For justify alignment, extract space measurements from browser BEFORE removing element
|
|
823
|
+
let justifySpaceMeasurements = null;
|
|
824
|
+
if (this.textAlign.includes('justify')) {
|
|
825
|
+
justifySpaceMeasurements = this._extractJustifySpaceMeasurements(testElement, lines);
|
|
826
|
+
}
|
|
827
|
+
document.body.removeChild(testElement);
|
|
828
|
+
console.log(`🔤 Browser wrapping result: ${lines.length} lines`);
|
|
829
|
+
|
|
830
|
+
// Try different height measurements to find the most accurate
|
|
831
|
+
let bestHeight = actualBrowserHeight;
|
|
832
|
+
|
|
833
|
+
// If scrollHeight and offsetHeight differ significantly, investigate
|
|
834
|
+
if (Math.abs(actualBrowserHeight - offsetHeight) > 2) {
|
|
835
|
+
console.log(`🔤 Height discrepancy detected: scrollHeight=${actualBrowserHeight}px vs offsetHeight=${offsetHeight}px`);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Consider using boundingRect height if it's larger (sometimes more accurate for visible content)
|
|
839
|
+
if (boundingRect.height > bestHeight) {
|
|
840
|
+
console.log(`🔤 Using boundingRect height (${boundingRect.height}px) instead of scrollHeight (${bestHeight}px)`);
|
|
841
|
+
bestHeight = boundingRect.height;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Font-specific height adjustments for accurate bounding box
|
|
845
|
+
let adjustedHeight = bestHeight;
|
|
846
|
+
|
|
847
|
+
// Fonts without English glyphs need additional height buffer due to different font metrics
|
|
848
|
+
const lacksEnglishGlyphs = fontLacksEnglishGlyphsCached(this.fontFamily);
|
|
849
|
+
if (lacksEnglishGlyphs) {
|
|
850
|
+
const glyphBuffer = this.fontSize * 0.25; // 25% of font size for non-English fonts
|
|
851
|
+
adjustedHeight = bestHeight + glyphBuffer;
|
|
852
|
+
console.log(`🔤 Non-English font detected (${this.fontFamily}): Adding ${glyphBuffer}px buffer (${bestHeight}px + ${glyphBuffer}px = ${adjustedHeight}px)`);
|
|
853
|
+
} else {
|
|
854
|
+
console.log(`🔤 Standard font (${this.fontFamily}): Using browser height directly (${bestHeight}px)`);
|
|
855
|
+
}
|
|
856
|
+
return {
|
|
857
|
+
_unwrappedLines: [text.split('')],
|
|
858
|
+
lines: lines,
|
|
859
|
+
graphemeText: text.split(''),
|
|
860
|
+
graphemeLines: graphemeLines,
|
|
861
|
+
justifySpaceMeasurements: justifySpaceMeasurements,
|
|
862
|
+
actualBrowserHeight: adjustedHeight
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/**
|
|
867
|
+
* Extract justify space measurements from browser
|
|
868
|
+
* @private
|
|
869
|
+
*/
|
|
870
|
+
_extractJustifySpaceMeasurements(element, lines) {
|
|
871
|
+
console.log(`🔤 Extracting browser justify space measurements for ${lines.length} lines`);
|
|
872
|
+
|
|
873
|
+
// For now, we'll use a simplified approach:
|
|
874
|
+
// Apply uniform space expansion to match the line width
|
|
875
|
+
const spaceWidths = [];
|
|
876
|
+
lines.forEach((line, lineIndex) => {
|
|
877
|
+
const lineSpaces = [];
|
|
878
|
+
const spaceCount = (line.match(/\s/g) || []).length;
|
|
879
|
+
if (spaceCount > 0 && lineIndex < lines.length - 1) {
|
|
880
|
+
// Don't justify last line
|
|
881
|
+
// Calculate how much space expansion is needed
|
|
882
|
+
const normalSpaceWidth = 6.4; // Default space width for STV font
|
|
883
|
+
const lineWidth = this.width;
|
|
884
|
+
|
|
885
|
+
// Estimate natural line width
|
|
886
|
+
const charCount = line.length - spaceCount;
|
|
887
|
+
const avgCharWidth = 12; // Approximate for STV font
|
|
888
|
+
|
|
889
|
+
// Calculate expanded space width
|
|
890
|
+
const remainingSpace = lineWidth - charCount * avgCharWidth;
|
|
891
|
+
const expandedSpaceWidth = remainingSpace / spaceCount;
|
|
892
|
+
console.log(`🔤 Line ${lineIndex}: ${spaceCount} spaces, natural: ${normalSpaceWidth}px -> justified: ${expandedSpaceWidth.toFixed(1)}px`);
|
|
893
|
+
|
|
894
|
+
// Fill array with expanded space widths for this line
|
|
895
|
+
for (let i = 0; i < spaceCount; i++) {
|
|
896
|
+
lineSpaces.push(expandedSpaceWidth);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
spaceWidths.push(lineSpaces);
|
|
900
|
+
});
|
|
901
|
+
return spaceWidths;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Apply browser-calculated justify space measurements
|
|
906
|
+
* @private
|
|
907
|
+
*/
|
|
908
|
+
_applyBrowserJustifySpaces() {
|
|
909
|
+
if (!this._textLines || !this.__charBounds) {
|
|
910
|
+
console.warn('🔤 BROWSER JUSTIFY: _textLines or __charBounds not ready');
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Get space measurements from browser wrapping result
|
|
915
|
+
const styleMap = this._styleMap;
|
|
916
|
+
if (!styleMap || !styleMap.justifySpaceMeasurements) {
|
|
917
|
+
console.warn('🔤 BROWSER JUSTIFY: No justify space measurements available');
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
const spaceWidths = styleMap.justifySpaceMeasurements;
|
|
921
|
+
console.log('🔤 BROWSER JUSTIFY: Applying space measurements to __charBounds');
|
|
922
|
+
|
|
923
|
+
// Apply space widths to character bounds
|
|
924
|
+
this._textLines.forEach((line, lineIndex) => {
|
|
925
|
+
if (!this.__charBounds || !this.__charBounds[lineIndex] || !spaceWidths[lineIndex]) return;
|
|
926
|
+
const lineBounds = this.__charBounds[lineIndex];
|
|
927
|
+
const lineSpaceWidths = spaceWidths[lineIndex];
|
|
928
|
+
let spaceIndex = 0;
|
|
929
|
+
for (let charIndex = 0; charIndex < line.length; charIndex++) {
|
|
930
|
+
if (/\s/.test(line[charIndex]) && spaceIndex < lineSpaceWidths.length) {
|
|
931
|
+
const expandedWidth = lineSpaceWidths[spaceIndex];
|
|
932
|
+
if (lineBounds[charIndex]) {
|
|
933
|
+
const oldWidth = lineBounds[charIndex].width;
|
|
934
|
+
lineBounds[charIndex].width = expandedWidth;
|
|
935
|
+
console.log(`🔤 Line ${lineIndex} space ${spaceIndex}: ${oldWidth.toFixed(1)}px -> ${expandedWidth.toFixed(1)}px`);
|
|
936
|
+
}
|
|
937
|
+
spaceIndex++;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* Fallback to default Fabric wrapping
|
|
945
|
+
* @private
|
|
946
|
+
*/
|
|
947
|
+
_splitTextIntoLinesDefault(text) {
|
|
525
948
|
const newText = super._splitTextIntoLines(text),
|
|
526
949
|
graphemeLines = this._wrapText(newText.lines, this.width),
|
|
527
950
|
lines = new Array(graphemeLines.length);
|
|
@@ -556,37 +979,24 @@ class Textbox extends IText {
|
|
|
556
979
|
* @private
|
|
557
980
|
*/
|
|
558
981
|
initializeEventListeners() {
|
|
559
|
-
var _this$
|
|
982
|
+
var _this$canvas4;
|
|
560
983
|
// Track which side is being used for resize to handle position compensation
|
|
561
984
|
let resizeOrigin = null;
|
|
562
985
|
|
|
563
986
|
// Detect resize origin during resizing
|
|
564
987
|
this.on('resizing', e => {
|
|
565
988
|
// Check transform origin to determine which side is being resized
|
|
566
|
-
console.log('🔍 Resize event data:', e);
|
|
567
989
|
if (e.transform) {
|
|
568
990
|
const {
|
|
569
|
-
originX
|
|
570
|
-
originY
|
|
991
|
+
originX
|
|
571
992
|
} = e.transform;
|
|
572
|
-
console.log('🔍 Transform origins:', {
|
|
573
|
-
originX,
|
|
574
|
-
originY
|
|
575
|
-
});
|
|
576
993
|
// originX tells us which side is the anchor - opposite side is being dragged
|
|
577
994
|
resizeOrigin = originX === 'right' ? 'left' : originX === 'left' ? 'right' : null;
|
|
578
|
-
console.log('🎯 Setting resizeOrigin to:', resizeOrigin);
|
|
579
995
|
} else if (e.originX) {
|
|
580
996
|
const {
|
|
581
|
-
originX
|
|
582
|
-
originY
|
|
997
|
+
originX
|
|
583
998
|
} = e;
|
|
584
|
-
console.log('🔍 Event origins:', {
|
|
585
|
-
originX,
|
|
586
|
-
originY
|
|
587
|
-
});
|
|
588
999
|
resizeOrigin = originX === 'right' ? 'left' : originX === 'left' ? 'right' : null;
|
|
589
|
-
console.log('🎯 Setting resizeOrigin to:', resizeOrigin);
|
|
590
1000
|
}
|
|
591
1001
|
});
|
|
592
1002
|
|
|
@@ -594,19 +1004,15 @@ class Textbox extends IText {
|
|
|
594
1004
|
// Use 'modified' event which fires after user releases the mouse
|
|
595
1005
|
this.on('modified', () => {
|
|
596
1006
|
const currentResizeOrigin = resizeOrigin; // Capture the value before reset
|
|
597
|
-
console.log('✅ Modified event fired - resize complete, triggering safety snap', {
|
|
598
|
-
resizeOrigin: currentResizeOrigin
|
|
599
|
-
});
|
|
600
1007
|
// Small delay to ensure text layout is updated
|
|
601
1008
|
setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);
|
|
602
1009
|
resizeOrigin = null; // Reset after capturing
|
|
603
1010
|
});
|
|
604
1011
|
|
|
605
1012
|
// Also listen to canvas-level modified event as backup
|
|
606
|
-
(_this$
|
|
1013
|
+
(_this$canvas4 = this.canvas) === null || _this$canvas4 === void 0 || _this$canvas4.on('object:modified', e => {
|
|
607
1014
|
if (e.target === this) {
|
|
608
1015
|
const currentResizeOrigin = resizeOrigin; // Capture the value before reset
|
|
609
|
-
console.log('✅ Canvas object:modified fired for this textbox');
|
|
610
1016
|
setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);
|
|
611
1017
|
resizeOrigin = null; // Reset after capturing
|
|
612
1018
|
}
|
|
@@ -621,38 +1027,17 @@ class Textbox extends IText {
|
|
|
621
1027
|
* @param resizeOrigin - Which side was used for resizing ('left' or 'right')
|
|
622
1028
|
*/
|
|
623
1029
|
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
1030
|
// For Textbox objects, we always want to check for clipping regardless of isWrapping flag
|
|
635
1031
|
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
1032
|
return;
|
|
644
1033
|
}
|
|
645
1034
|
const lineCount = this._textLines.length;
|
|
646
1035
|
if (lineCount === 0) return;
|
|
647
|
-
|
|
648
|
-
// Check all lines, not just the last one
|
|
649
|
-
let maxActualLineWidth = 0; // Actual measured width without buffers
|
|
650
1036
|
let maxRequiredWidth = 0; // Width including RTL buffer
|
|
651
1037
|
|
|
652
1038
|
for (let i = 0; i < lineCount; i++) {
|
|
653
1039
|
const lineText = this._textLines[i].join(''); // Convert grapheme array to string
|
|
654
1040
|
const lineWidth = this.getLineWidth(i);
|
|
655
|
-
maxActualLineWidth = Math.max(maxActualLineWidth, lineWidth);
|
|
656
1041
|
|
|
657
1042
|
// RTL detection - regex for Arabic, Hebrew, and other RTL characters
|
|
658
1043
|
const rtlRegex = /[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/;
|
|
@@ -669,14 +1054,9 @@ class Textbox extends IText {
|
|
|
669
1054
|
const safetyThreshold = 2; // px - very subtle trigger
|
|
670
1055
|
|
|
671
1056
|
if (maxRequiredWidth > this.width - safetyThreshold) {
|
|
672
|
-
var _this$
|
|
1057
|
+
var _this$canvas5;
|
|
673
1058
|
// Set width to exactly what's needed + minimal safety margin
|
|
674
1059
|
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
1060
|
|
|
681
1061
|
// Store original position before width change
|
|
682
1062
|
const originalLeft = this.left;
|
|
@@ -692,19 +1072,12 @@ class Textbox extends IText {
|
|
|
692
1072
|
// Only compensate position when resizing from left handle
|
|
693
1073
|
// Right handle resize doesn't shift the text position
|
|
694
1074
|
if (resizeOrigin === 'left') {
|
|
695
|
-
console.log('🔧 Compensating for left-side resize', {
|
|
696
|
-
originalLeft,
|
|
697
|
-
widthIncrease,
|
|
698
|
-
newLeft: originalLeft - widthIncrease
|
|
699
|
-
});
|
|
700
1075
|
// When resizing from left, the expansion pushes text right
|
|
701
1076
|
// Compensate by moving the textbox left by the width increase
|
|
702
1077
|
this.set({
|
|
703
1078
|
'left': originalLeft - widthIncrease,
|
|
704
1079
|
'top': originalTop
|
|
705
1080
|
});
|
|
706
|
-
} else {
|
|
707
|
-
console.log('✅ Right-side resize, no compensation needed');
|
|
708
1081
|
}
|
|
709
1082
|
this.setCoords();
|
|
710
1083
|
|
|
@@ -714,7 +1087,88 @@ class Textbox extends IText {
|
|
|
714
1087
|
this.__overlayEditor.refresh();
|
|
715
1088
|
}, 0);
|
|
716
1089
|
}
|
|
717
|
-
(_this$
|
|
1090
|
+
(_this$canvas5 = this.canvas) === null || _this$canvas5 === void 0 || _this$canvas5.requestRenderAll();
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Fix character selection mismatch after JSON loading for browser-wrapped fonts
|
|
1096
|
+
* @private
|
|
1097
|
+
*/
|
|
1098
|
+
_fixCharacterMappingAfterJsonLoad() {
|
|
1099
|
+
if (this._usingBrowserWrapping) {
|
|
1100
|
+
// Clear all cached states to force fresh text layout calculation
|
|
1101
|
+
this._browserWrapCache = null;
|
|
1102
|
+
this._lastDimensionState = null;
|
|
1103
|
+
|
|
1104
|
+
// Force complete re-initialization
|
|
1105
|
+
this.initDimensions();
|
|
1106
|
+
this._forceClearCache = true;
|
|
1107
|
+
|
|
1108
|
+
// Ensure canvas refresh
|
|
1109
|
+
this.setCoords();
|
|
1110
|
+
if (this.canvas) {
|
|
1111
|
+
this.canvas.requestRenderAll();
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* Force complete textbox re-initialization (useful after JSON loading)
|
|
1118
|
+
* Overrides Text version with Textbox-specific logic
|
|
1119
|
+
*/
|
|
1120
|
+
forceTextReinitialization() {
|
|
1121
|
+
console.log('🔄 Force reinitializing Textbox object');
|
|
1122
|
+
|
|
1123
|
+
// CRITICAL: Ensure textbox is marked as initialized
|
|
1124
|
+
this.initialized = true;
|
|
1125
|
+
|
|
1126
|
+
// Clear all caches and force dirty state
|
|
1127
|
+
this._clearCache();
|
|
1128
|
+
this.dirty = true;
|
|
1129
|
+
this.dynamicMinWidth = 0;
|
|
1130
|
+
|
|
1131
|
+
// Force isEditing false to ensure clean state
|
|
1132
|
+
this.isEditing = false;
|
|
1133
|
+
console.log(' → Set initialized=true, dirty=true, cleared caches');
|
|
1134
|
+
|
|
1135
|
+
// Re-initialize dimensions (this will handle justify properly)
|
|
1136
|
+
this.initDimensions();
|
|
1137
|
+
|
|
1138
|
+
// Double-check that justify was applied by checking space widths
|
|
1139
|
+
if (this.textAlign.includes('justify') && this.__charBounds) {
|
|
1140
|
+
setTimeout(() => {
|
|
1141
|
+
var _this$canvas6;
|
|
1142
|
+
// Verify justify was applied by checking if space widths vary
|
|
1143
|
+
let hasVariableSpaces = false;
|
|
1144
|
+
this.__charBounds.forEach((lineBounds, i) => {
|
|
1145
|
+
if (lineBounds && this._textLines && this._textLines[i]) {
|
|
1146
|
+
const spaces = lineBounds.filter((bound, j) => /\s/.test(this._textLines[i][j]));
|
|
1147
|
+
if (spaces.length > 1) {
|
|
1148
|
+
const firstSpaceWidth = spaces[0].width;
|
|
1149
|
+
hasVariableSpaces = spaces.some(space => Math.abs(space.width - firstSpaceWidth) > 0.1);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
if (!hasVariableSpaces && this.__charBounds.length > 0) {
|
|
1154
|
+
console.warn(' ⚠️ Justify spaces still uniform - forcing enlargeSpaces again');
|
|
1155
|
+
if (this.enlargeSpaces) {
|
|
1156
|
+
this.enlargeSpaces();
|
|
1157
|
+
}
|
|
1158
|
+
} else {
|
|
1159
|
+
console.log(' ✅ Justify spaces properly expanded');
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Ensure height is recalculated - use browser height if available
|
|
1163
|
+
if (this._usingBrowserWrapping && this._actualBrowserHeight) {
|
|
1164
|
+
this.height = this._actualBrowserHeight;
|
|
1165
|
+
console.log(`🔤 JUSTIFY: Preserved browser height: ${this.height}px`);
|
|
1166
|
+
} else {
|
|
1167
|
+
this.height = this.calcTextHeight();
|
|
1168
|
+
console.log(`🔧 JUSTIFY: Used calcTextHeight: ${this.height}px`);
|
|
1169
|
+
}
|
|
1170
|
+
(_this$canvas6 = this.canvas) === null || _this$canvas6 === void 0 || _this$canvas6.requestRenderAll();
|
|
1171
|
+
}, 10);
|
|
718
1172
|
}
|
|
719
1173
|
}
|
|
720
1174
|
|