@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
package/src/shapes/Textbox.ts
CHANGED
|
@@ -2,12 +2,13 @@ import type { TClassProperties, TOptions } from '../typedefs';
|
|
|
2
2
|
import { IText } from './IText/IText';
|
|
3
3
|
import { classRegistry } from '../ClassRegistry';
|
|
4
4
|
import { createTextboxDefaultControls } from '../controls/commonControls';
|
|
5
|
-
import { JUSTIFY } from './Text/constants';
|
|
5
|
+
import { JUSTIFY, JUSTIFY_CENTER } from './Text/constants';
|
|
6
6
|
import type { TextStyleDeclaration } from './Text/StyledText';
|
|
7
7
|
import type { SerializedITextProps, ITextProps } from './IText/IText';
|
|
8
8
|
import type { ITextEvents } from './IText/ITextBehavior';
|
|
9
9
|
import type { TextLinesInfo } from './Text/Text';
|
|
10
10
|
import type { Control } from '../controls/Control';
|
|
11
|
+
import { fontLacksEnglishGlyphsCached } from '../text/measure';
|
|
11
12
|
import { layoutText } from '../text/layout';
|
|
12
13
|
|
|
13
14
|
// @TODO: Many things here are configuration related and shouldn't be on the class nor prototype
|
|
@@ -128,8 +129,28 @@ export class Textbox<
|
|
|
128
129
|
*/
|
|
129
130
|
initDimensions() {
|
|
130
131
|
if (!this.initialized) {
|
|
132
|
+
this.initialized = true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Prevent rapid recalculations during moves
|
|
136
|
+
if ((this as any)._usingBrowserWrapping) {
|
|
137
|
+
const now = Date.now();
|
|
138
|
+
const lastCall = (this as any)._lastInitDimensionsTime || 0;
|
|
139
|
+
const isRapidCall = now - lastCall < 100;
|
|
140
|
+
const isDuringLoading = (this as any)._jsonLoading || !(this as any)._browserWrapInitialized;
|
|
141
|
+
|
|
142
|
+
if (isRapidCall && !isDuringLoading) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
(this as any)._lastInitDimensionsTime = now;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Skip if nothing changed
|
|
149
|
+
const currentState = `${this.text}|${this.width}|${this.fontSize}|${this.fontFamily}|${this.textAlign}`;
|
|
150
|
+
if ((this as any)._lastDimensionState === currentState && this._textLines && this._textLines.length > 0) {
|
|
131
151
|
return;
|
|
132
152
|
}
|
|
153
|
+
(this as any)._lastDimensionState = currentState;
|
|
133
154
|
|
|
134
155
|
// Use advanced layout if enabled
|
|
135
156
|
if (this.enableAdvancedLayout) {
|
|
@@ -141,17 +162,142 @@ export class Textbox<
|
|
|
141
162
|
// clear dynamicMinWidth as it will be different after we re-wrap line
|
|
142
163
|
this.dynamicMinWidth = 0;
|
|
143
164
|
// wrap lines
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
165
|
+
const splitTextResult = this._splitText();
|
|
166
|
+
this._styleMap = this._generateStyleMap(splitTextResult);
|
|
167
|
+
|
|
168
|
+
// For browser wrapping, ensure _textLines is set from browser results
|
|
169
|
+
if ((this as any)._usingBrowserWrapping && splitTextResult && splitTextResult.lines) {
|
|
170
|
+
this._textLines = splitTextResult.lines.map(line => line.split(''));
|
|
171
|
+
|
|
172
|
+
// Store justify measurements and browser height
|
|
173
|
+
const justifyMeasurements = (splitTextResult as any).justifySpaceMeasurements;
|
|
174
|
+
if (justifyMeasurements) {
|
|
175
|
+
(this._styleMap as any).justifySpaceMeasurements = justifyMeasurements;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const actualHeight = (splitTextResult as any).actualBrowserHeight;
|
|
179
|
+
if (actualHeight) {
|
|
180
|
+
(this as any)._actualBrowserHeight = actualHeight;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Don't auto-resize width when using browser wrapping to prevent width increases during moves
|
|
184
|
+
if (!((this as any)._usingBrowserWrapping) && this.dynamicMinWidth > this.width) {
|
|
147
185
|
this._set('width', this.dynamicMinWidth);
|
|
148
186
|
}
|
|
187
|
+
|
|
188
|
+
// For browser wrapping fonts (like STV), ensure minimum width for new textboxes
|
|
189
|
+
// since these fonts can't measure English characters properly
|
|
190
|
+
if ((this as any)._usingBrowserWrapping && this.width < 50) {
|
|
191
|
+
console.log(`🔤 BROWSER WRAP: Font ${this.fontFamily} has width ${this.width}px, setting to 300px for usability`);
|
|
192
|
+
this.width = 300;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Mark browser wrapping as initialized when complete
|
|
196
|
+
if ((this as any)._usingBrowserWrapping) {
|
|
197
|
+
(this as any)._browserWrapInitialized = true;
|
|
198
|
+
}
|
|
199
|
+
|
|
149
200
|
if (this.textAlign.includes(JUSTIFY)) {
|
|
201
|
+
// For browser wrapping fonts, apply browser-calculated justify spaces
|
|
202
|
+
if ((this as any)._usingBrowserWrapping) {
|
|
203
|
+
console.log('🔤 BROWSER WRAP: Applying browser-calculated justify spaces');
|
|
204
|
+
this._applyBrowserJustifySpaces();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Don't apply justify alignment during drag operations to prevent snapping
|
|
209
|
+
const now = Date.now();
|
|
210
|
+
const lastDragTime = (this as any)._lastInitDimensionsTime || 0;
|
|
211
|
+
const isDuringDrag = now - lastDragTime < 200; // 200ms window for drag detection
|
|
212
|
+
|
|
213
|
+
if (isDuringDrag) {
|
|
214
|
+
console.log('🔤 Skipping justify during drag operation to prevent snapping');
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// For non-browser-wrapping fonts, use Fabric's justify system
|
|
150
219
|
// once text is measured we need to make space fatter to make justified text.
|
|
151
|
-
|
|
220
|
+
// Ensure __charBounds exists and fonts are ready before applying justify
|
|
221
|
+
if (this.__charBounds && this.__charBounds.length > 0) {
|
|
222
|
+
// Check if font is ready for accurate justify calculations
|
|
223
|
+
const fontReady = this._isFontReady ? this._isFontReady() : true;
|
|
224
|
+
if (fontReady) {
|
|
225
|
+
this.enlargeSpaces();
|
|
226
|
+
} else {
|
|
227
|
+
console.warn('⚠️ Textbox: Font not ready for justify, deferring enlargeSpaces');
|
|
228
|
+
// Defer justify calculation until font is ready
|
|
229
|
+
this._scheduleJustifyAfterFontLoad();
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
console.warn('⚠️ Textbox: __charBounds not ready for justify alignment, deferring enlargeSpaces');
|
|
233
|
+
// Defer the justify calculation until the next frame
|
|
234
|
+
setTimeout(() => {
|
|
235
|
+
if (this.__charBounds && this.__charBounds.length > 0 && this.enlargeSpaces) {
|
|
236
|
+
console.log('🔧 Applying deferred Textbox justify alignment');
|
|
237
|
+
this.enlargeSpaces();
|
|
238
|
+
this.canvas?.requestRenderAll();
|
|
239
|
+
}
|
|
240
|
+
}, 0);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Calculate height - use Fabric's calculation for proper text rendering space
|
|
244
|
+
if ((this as any)._usingBrowserWrapping && this._textLines && this._textLines.length > 0) {
|
|
245
|
+
const actualBrowserHeight = (this as any)._actualBrowserHeight;
|
|
246
|
+
const oldHeight = this.height;
|
|
247
|
+
// Use Fabric's height calculation since it knows how much space text rendering needs
|
|
248
|
+
this.height = this.calcTextHeight();
|
|
249
|
+
|
|
250
|
+
// Force canvas refresh and control update if height changed significantly
|
|
251
|
+
if (Math.abs(this.height - oldHeight) > 1) {
|
|
252
|
+
this.setCoords();
|
|
253
|
+
this.canvas?.requestRenderAll();
|
|
254
|
+
|
|
255
|
+
// DEBUG: Log exact positioning details
|
|
256
|
+
console.log(`🎯 POSITIONING DEBUG:`);
|
|
257
|
+
console.log(` Textbox height: ${this.height}px`);
|
|
258
|
+
console.log(` Textbox top: ${this.top}px`);
|
|
259
|
+
console.log(` Textbox left: ${this.left}px`);
|
|
260
|
+
console.log(` Text lines: ${this._textLines?.length || 0}`);
|
|
261
|
+
console.log(` Font size: ${this.fontSize}px`);
|
|
262
|
+
console.log(` Line height: ${this.lineHeight || 1.16}`);
|
|
263
|
+
console.log(` Calculated line height: ${this.fontSize * (this.lineHeight || 1.16)}px`);
|
|
264
|
+
console.log(` _getTopOffset(): ${this._getTopOffset()}px`);
|
|
265
|
+
console.log(` calcTextHeight(): ${this.calcTextHeight()}px`);
|
|
266
|
+
console.log(` Browser height: ${actualBrowserHeight}px`);
|
|
267
|
+
console.log(` Height difference: ${this.height - this.calcTextHeight()}px`);
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
this.height = this.calcTextHeight();
|
|
152
271
|
}
|
|
153
|
-
|
|
154
|
-
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Schedule justify calculation after font loads (Textbox-specific)
|
|
276
|
+
* @private
|
|
277
|
+
*/
|
|
278
|
+
_scheduleJustifyAfterFontLoad(): void {
|
|
279
|
+
if (typeof document === 'undefined' || !('fonts' in document)) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Only schedule if not already waiting
|
|
284
|
+
if ((this as any)._fontJustifyScheduled) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
(this as any)._fontJustifyScheduled = true;
|
|
288
|
+
|
|
289
|
+
const fontSpec = `${this.fontSize}px ${this.fontFamily}`;
|
|
290
|
+
document.fonts.load(fontSpec).then(() => {
|
|
291
|
+
(this as any)._fontJustifyScheduled = false;
|
|
292
|
+
console.log('🔧 Textbox: Font loaded, applying justify alignment');
|
|
293
|
+
|
|
294
|
+
// Re-run initDimensions to ensure proper justify calculation
|
|
295
|
+
this.initDimensions();
|
|
296
|
+
this.canvas?.requestRenderAll();
|
|
297
|
+
}).catch(() => {
|
|
298
|
+
(this as any)._fontJustifyScheduled = false;
|
|
299
|
+
console.warn('⚠️ Textbox: Font loading failed, justify may be incorrect');
|
|
300
|
+
});
|
|
155
301
|
}
|
|
156
302
|
|
|
157
303
|
/**
|
|
@@ -546,20 +692,35 @@ export class Textbox<
|
|
|
546
692
|
const { word, width: wordWidth } = data[i];
|
|
547
693
|
offset += word.length;
|
|
548
694
|
|
|
549
|
-
|
|
550
|
-
|
|
695
|
+
// Predictive wrapping: check if adding this word would exceed the width
|
|
696
|
+
const potentialLineWidth = lineWidth + infixWidth + wordWidth - additionalSpace;
|
|
697
|
+
// Use exact width to match overlay editor behavior
|
|
698
|
+
const conservativeMaxWidth = maxWidth; // No artificial buffer
|
|
699
|
+
|
|
700
|
+
// Debug logging for wrapping decisions
|
|
701
|
+
const currentLineText = line.join('');
|
|
702
|
+
console.log(`🔧 FABRIC WRAP CHECK: "${data[i].word}" -> potential: ${potentialLineWidth.toFixed(1)}px vs limit: ${conservativeMaxWidth.toFixed(1)}px`);
|
|
703
|
+
|
|
704
|
+
if (potentialLineWidth > conservativeMaxWidth && !lineJustStarted) {
|
|
705
|
+
// This word would exceed the width, wrap before adding it
|
|
706
|
+
console.log(`🔧 FABRIC WRAP! Line: "${currentLineText}" (${lineWidth.toFixed(1)}px)`);
|
|
551
707
|
graphemeLines.push(line);
|
|
552
708
|
line = [];
|
|
553
|
-
lineWidth = wordWidth;
|
|
709
|
+
lineWidth = wordWidth; // Start new line with just this word
|
|
554
710
|
lineJustStarted = true;
|
|
555
711
|
} else {
|
|
556
|
-
|
|
712
|
+
// Word fits, add it to current line
|
|
713
|
+
lineWidth = potentialLineWidth + additionalSpace;
|
|
557
714
|
}
|
|
558
715
|
|
|
559
716
|
if (!lineJustStarted && !splitByGrapheme) {
|
|
560
717
|
line.push(infix);
|
|
561
718
|
}
|
|
562
719
|
line = line.concat(word);
|
|
720
|
+
|
|
721
|
+
// Debug: show current line after adding word
|
|
722
|
+
console.log(`🔧 FABRIC AFTER ADD: Line now: "${line.join('')}" (${line.length} chars)`);
|
|
723
|
+
|
|
563
724
|
|
|
564
725
|
infixWidth = splitByGrapheme
|
|
565
726
|
? 0
|
|
@@ -573,9 +734,20 @@ export class Textbox<
|
|
|
573
734
|
// TODO: this code is probably not necessary anymore.
|
|
574
735
|
// it can be moved out of this function since largestWordWidth is now
|
|
575
736
|
// known in advance
|
|
576
|
-
|
|
737
|
+
// Don't modify dynamicMinWidth when using browser wrapping to prevent width increases
|
|
738
|
+
if (!((this as any)._usingBrowserWrapping) && largestWordWidth + reservedSpace > this.dynamicMinWidth) {
|
|
739
|
+
console.log(`🔧 FABRIC updating dynamicMinWidth: ${this.dynamicMinWidth} -> ${largestWordWidth - additionalSpace + reservedSpace}`);
|
|
577
740
|
this.dynamicMinWidth = largestWordWidth - additionalSpace + reservedSpace;
|
|
741
|
+
} else if ((this as any)._usingBrowserWrapping) {
|
|
742
|
+
console.log(`🔤 BROWSER WRAP: Skipping dynamicMinWidth update to prevent width increase`);
|
|
578
743
|
}
|
|
744
|
+
|
|
745
|
+
// Debug: show final wrapped lines
|
|
746
|
+
console.log(`🔧 FABRIC FINAL LINES: ${graphemeLines.length} lines`);
|
|
747
|
+
graphemeLines.forEach((line, i) => {
|
|
748
|
+
console.log(` Line ${i + 1}: "${line.join('')}" (${line.length} chars)`);
|
|
749
|
+
});
|
|
750
|
+
|
|
579
751
|
return graphemeLines;
|
|
580
752
|
}
|
|
581
753
|
|
|
@@ -619,6 +791,284 @@ export class Textbox<
|
|
|
619
791
|
* @override
|
|
620
792
|
*/
|
|
621
793
|
_splitTextIntoLines(text: string) {
|
|
794
|
+
// Check if we need browser wrapping using smart font detection
|
|
795
|
+
const needsBrowserWrapping = this.fontFamily && fontLacksEnglishGlyphsCached(this.fontFamily);
|
|
796
|
+
|
|
797
|
+
if (needsBrowserWrapping) {
|
|
798
|
+
// Cache key based on text content, width, font properties, AND text alignment
|
|
799
|
+
const textHash = text.length + text.slice(0, 50); // Include text content in cache key
|
|
800
|
+
const cacheKey = `${textHash}|${this.width}|${this.fontSize}|${this.fontFamily}|${this.textAlign}`;
|
|
801
|
+
|
|
802
|
+
// Check if we have a cached result and nothing has changed
|
|
803
|
+
if ((this as any)._browserWrapCache && (this as any)._browserWrapCache.key === cacheKey) {
|
|
804
|
+
const cachedResult = (this as any)._browserWrapCache.result;
|
|
805
|
+
|
|
806
|
+
// For justify alignment, ensure we have the measurements
|
|
807
|
+
if (this.textAlign.includes('justify') && !(cachedResult as any).justifySpaceMeasurements) {
|
|
808
|
+
// Fall through to recalculate
|
|
809
|
+
} else {
|
|
810
|
+
return cachedResult;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const result = this._splitTextIntoLinesWithBrowser(text);
|
|
815
|
+
|
|
816
|
+
// Cache the result
|
|
817
|
+
(this as any)._browserWrapCache = { key: cacheKey, result };
|
|
818
|
+
|
|
819
|
+
// Mark that we used browser wrapping to prevent dynamicMinWidth modifications
|
|
820
|
+
(this as any)._usingBrowserWrapping = true;
|
|
821
|
+
|
|
822
|
+
return result;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Clear the browser wrapping flag when using regular wrapping
|
|
826
|
+
(this as any)._usingBrowserWrapping = false;
|
|
827
|
+
|
|
828
|
+
// Default Fabric wrapping for other fonts
|
|
829
|
+
const newText = super._splitTextIntoLines(text),
|
|
830
|
+
graphemeLines = this._wrapText(newText.lines, this.width),
|
|
831
|
+
lines = new Array(graphemeLines.length);
|
|
832
|
+
for (let i = 0; i < graphemeLines.length; i++) {
|
|
833
|
+
lines[i] = graphemeLines[i].join('');
|
|
834
|
+
}
|
|
835
|
+
newText.lines = lines;
|
|
836
|
+
newText.graphemeLines = graphemeLines;
|
|
837
|
+
return newText;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Use browser's native text wrapping for accurate handling of fonts without English glyphs
|
|
842
|
+
* @private
|
|
843
|
+
*/
|
|
844
|
+
_splitTextIntoLinesWithBrowser(text: string) {
|
|
845
|
+
if (typeof document === 'undefined') {
|
|
846
|
+
// Fallback to regular wrapping in Node.js
|
|
847
|
+
return this._splitTextIntoLinesDefault(text);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Create a hidden element that mimics the overlay editor
|
|
851
|
+
const testElement = document.createElement('div');
|
|
852
|
+
testElement.style.position = 'absolute';
|
|
853
|
+
testElement.style.left = '-9999px';
|
|
854
|
+
testElement.style.visibility = 'hidden';
|
|
855
|
+
testElement.style.fontSize = `${this.fontSize}px`;
|
|
856
|
+
testElement.style.fontFamily = `"${this.fontFamily}"`;
|
|
857
|
+
testElement.style.fontWeight = String(this.fontWeight || 'normal');
|
|
858
|
+
testElement.style.fontStyle = String(this.fontStyle || 'normal');
|
|
859
|
+
testElement.style.lineHeight = String(this.lineHeight || 1.16);
|
|
860
|
+
|
|
861
|
+
testElement.style.width = `${this.width}px`;
|
|
862
|
+
|
|
863
|
+
testElement.style.direction = this.direction || 'ltr';
|
|
864
|
+
testElement.style.whiteSpace = 'pre-wrap';
|
|
865
|
+
testElement.style.wordBreak = 'normal';
|
|
866
|
+
testElement.style.overflowWrap = 'break-word';
|
|
867
|
+
|
|
868
|
+
// Set browser-native text alignment (including justify)
|
|
869
|
+
if (this.textAlign.includes('justify')) {
|
|
870
|
+
testElement.style.textAlign = 'justify';
|
|
871
|
+
testElement.style.textAlignLast = 'auto'; // Let browser decide last line alignment
|
|
872
|
+
} else {
|
|
873
|
+
testElement.style.textAlign = this.textAlign;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
testElement.textContent = text;
|
|
877
|
+
|
|
878
|
+
document.body.appendChild(testElement);
|
|
879
|
+
|
|
880
|
+
// Get the browser's natural line breaks
|
|
881
|
+
const range = document.createRange();
|
|
882
|
+
const lines: string[] = [];
|
|
883
|
+
const graphemeLines: string[][] = [];
|
|
884
|
+
|
|
885
|
+
try {
|
|
886
|
+
// Simple approach: split by measuring character positions
|
|
887
|
+
const textNode = testElement.firstChild;
|
|
888
|
+
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
|
889
|
+
let currentLineStart = 0;
|
|
890
|
+
const textLength = text.length;
|
|
891
|
+
let previousBottom = 0;
|
|
892
|
+
|
|
893
|
+
for (let i = 0; i <= textLength; i++) {
|
|
894
|
+
range.setStart(textNode, currentLineStart);
|
|
895
|
+
range.setEnd(textNode, i);
|
|
896
|
+
const rect = range.getBoundingClientRect();
|
|
897
|
+
|
|
898
|
+
if (i > currentLineStart && (rect.bottom > previousBottom + 5 || i === textLength)) {
|
|
899
|
+
// New line detected or end of text
|
|
900
|
+
const lineEnd = i === textLength ? i : i - 1;
|
|
901
|
+
const lineText = text.substring(currentLineStart, lineEnd).trim();
|
|
902
|
+
if (lineText) {
|
|
903
|
+
lines.push(lineText);
|
|
904
|
+
// Convert to graphemes for compatibility
|
|
905
|
+
const graphemeLine = lineText.split('');
|
|
906
|
+
graphemeLines.push(graphemeLine);
|
|
907
|
+
}
|
|
908
|
+
currentLineStart = lineEnd;
|
|
909
|
+
previousBottom = rect.bottom;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
} catch (error) {
|
|
914
|
+
console.warn('Browser wrapping failed, using fallback:', error);
|
|
915
|
+
document.body.removeChild(testElement);
|
|
916
|
+
return this._splitTextIntoLinesDefault(text);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// Extract actual browser height BEFORE removing element
|
|
920
|
+
const actualBrowserHeight = testElement.scrollHeight;
|
|
921
|
+
const offsetHeight = testElement.offsetHeight;
|
|
922
|
+
const clientHeight = testElement.clientHeight;
|
|
923
|
+
const boundingRect = testElement.getBoundingClientRect();
|
|
924
|
+
|
|
925
|
+
console.log(`🔤 Browser element measurements:`);
|
|
926
|
+
console.log(` scrollHeight: ${actualBrowserHeight}px (content + padding + hidden overflow)`);
|
|
927
|
+
console.log(` offsetHeight: ${offsetHeight}px (content + padding + border)`);
|
|
928
|
+
console.log(` clientHeight: ${clientHeight}px (content + padding, no border/scrollbar)`);
|
|
929
|
+
console.log(` boundingRect.height: ${boundingRect.height}px (actual rendered height)`);
|
|
930
|
+
console.log(` Font size: ${this.fontSize}px, Line height: ${this.lineHeight || 1.16}, Lines: ${lines.length}`);
|
|
931
|
+
|
|
932
|
+
// For justify alignment, extract space measurements from browser BEFORE removing element
|
|
933
|
+
let justifySpaceMeasurements = null;
|
|
934
|
+
if (this.textAlign.includes('justify')) {
|
|
935
|
+
justifySpaceMeasurements = this._extractJustifySpaceMeasurements(testElement, lines);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
document.body.removeChild(testElement);
|
|
939
|
+
|
|
940
|
+
console.log(`🔤 Browser wrapping result: ${lines.length} lines`);
|
|
941
|
+
|
|
942
|
+
// Try different height measurements to find the most accurate
|
|
943
|
+
let bestHeight = actualBrowserHeight;
|
|
944
|
+
|
|
945
|
+
// If scrollHeight and offsetHeight differ significantly, investigate
|
|
946
|
+
if (Math.abs(actualBrowserHeight - offsetHeight) > 2) {
|
|
947
|
+
console.log(`🔤 Height discrepancy detected: scrollHeight=${actualBrowserHeight}px vs offsetHeight=${offsetHeight}px`);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Consider using boundingRect height if it's larger (sometimes more accurate for visible content)
|
|
951
|
+
if (boundingRect.height > bestHeight) {
|
|
952
|
+
console.log(`🔤 Using boundingRect height (${boundingRect.height}px) instead of scrollHeight (${bestHeight}px)`);
|
|
953
|
+
bestHeight = boundingRect.height;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// Font-specific height adjustments for accurate bounding box
|
|
957
|
+
let adjustedHeight = bestHeight;
|
|
958
|
+
|
|
959
|
+
// Fonts without English glyphs need additional height buffer due to different font metrics
|
|
960
|
+
const lacksEnglishGlyphs = fontLacksEnglishGlyphsCached(this.fontFamily);
|
|
961
|
+
if (lacksEnglishGlyphs) {
|
|
962
|
+
const glyphBuffer = this.fontSize * 0.25; // 25% of font size for non-English fonts
|
|
963
|
+
adjustedHeight = bestHeight + glyphBuffer;
|
|
964
|
+
console.log(`🔤 Non-English font detected (${this.fontFamily}): Adding ${glyphBuffer}px buffer (${bestHeight}px + ${glyphBuffer}px = ${adjustedHeight}px)`);
|
|
965
|
+
} else {
|
|
966
|
+
console.log(`🔤 Standard font (${this.fontFamily}): Using browser height directly (${bestHeight}px)`);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
return {
|
|
970
|
+
_unwrappedLines: [text.split('')],
|
|
971
|
+
lines: lines,
|
|
972
|
+
graphemeText: text.split(''),
|
|
973
|
+
graphemeLines: graphemeLines,
|
|
974
|
+
justifySpaceMeasurements: justifySpaceMeasurements,
|
|
975
|
+
actualBrowserHeight: adjustedHeight,
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Extract justify space measurements from browser
|
|
984
|
+
* @private
|
|
985
|
+
*/
|
|
986
|
+
_extractJustifySpaceMeasurements(element: HTMLElement, lines: string[]) {
|
|
987
|
+
console.log(`🔤 Extracting browser justify space measurements for ${lines.length} lines`);
|
|
988
|
+
|
|
989
|
+
// For now, we'll use a simplified approach:
|
|
990
|
+
// Apply uniform space expansion to match the line width
|
|
991
|
+
const spaceWidths: number[][] = [];
|
|
992
|
+
|
|
993
|
+
lines.forEach((line, lineIndex) => {
|
|
994
|
+
const lineSpaces: number[] = [];
|
|
995
|
+
const spaceCount = (line.match(/\s/g) || []).length;
|
|
996
|
+
|
|
997
|
+
if (spaceCount > 0 && lineIndex < lines.length - 1) { // Don't justify last line
|
|
998
|
+
// Calculate how much space expansion is needed
|
|
999
|
+
const normalSpaceWidth = 6.4; // Default space width for STV font
|
|
1000
|
+
const lineWidth = this.width;
|
|
1001
|
+
|
|
1002
|
+
// Estimate natural line width
|
|
1003
|
+
const charCount = line.length - spaceCount;
|
|
1004
|
+
const avgCharWidth = 12; // Approximate for STV font
|
|
1005
|
+
const naturalWidth = charCount * avgCharWidth + spaceCount * normalSpaceWidth;
|
|
1006
|
+
|
|
1007
|
+
// Calculate expanded space width
|
|
1008
|
+
const remainingSpace = lineWidth - (charCount * avgCharWidth);
|
|
1009
|
+
const expandedSpaceWidth = remainingSpace / spaceCount;
|
|
1010
|
+
|
|
1011
|
+
console.log(`🔤 Line ${lineIndex}: ${spaceCount} spaces, natural: ${normalSpaceWidth}px -> justified: ${expandedSpaceWidth.toFixed(1)}px`);
|
|
1012
|
+
|
|
1013
|
+
// Fill array with expanded space widths for this line
|
|
1014
|
+
for (let i = 0; i < spaceCount; i++) {
|
|
1015
|
+
lineSpaces.push(expandedSpaceWidth);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
spaceWidths.push(lineSpaces);
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
return spaceWidths;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* Apply browser-calculated justify space measurements
|
|
1027
|
+
* @private
|
|
1028
|
+
*/
|
|
1029
|
+
_applyBrowserJustifySpaces() {
|
|
1030
|
+
if (!this._textLines || !this.__charBounds) {
|
|
1031
|
+
console.warn('🔤 BROWSER JUSTIFY: _textLines or __charBounds not ready');
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// Get space measurements from browser wrapping result
|
|
1036
|
+
const styleMap = this._styleMap as any;
|
|
1037
|
+
if (!styleMap || !styleMap.justifySpaceMeasurements) {
|
|
1038
|
+
console.warn('🔤 BROWSER JUSTIFY: No justify space measurements available');
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const spaceWidths = styleMap.justifySpaceMeasurements as number[][];
|
|
1043
|
+
console.log('🔤 BROWSER JUSTIFY: Applying space measurements to __charBounds');
|
|
1044
|
+
|
|
1045
|
+
// Apply space widths to character bounds
|
|
1046
|
+
this._textLines.forEach((line, lineIndex) => {
|
|
1047
|
+
if (!this.__charBounds || !this.__charBounds[lineIndex] || !spaceWidths[lineIndex]) return;
|
|
1048
|
+
|
|
1049
|
+
const lineBounds = this.__charBounds[lineIndex];
|
|
1050
|
+
const lineSpaceWidths = spaceWidths[lineIndex];
|
|
1051
|
+
let spaceIndex = 0;
|
|
1052
|
+
|
|
1053
|
+
for (let charIndex = 0; charIndex < line.length; charIndex++) {
|
|
1054
|
+
if (/\s/.test(line[charIndex]) && spaceIndex < lineSpaceWidths.length) {
|
|
1055
|
+
const expandedWidth = lineSpaceWidths[spaceIndex];
|
|
1056
|
+
if (lineBounds[charIndex]) {
|
|
1057
|
+
const oldWidth = lineBounds[charIndex].width;
|
|
1058
|
+
lineBounds[charIndex].width = expandedWidth;
|
|
1059
|
+
console.log(`🔤 Line ${lineIndex} space ${spaceIndex}: ${oldWidth.toFixed(1)}px -> ${expandedWidth.toFixed(1)}px`);
|
|
1060
|
+
}
|
|
1061
|
+
spaceIndex++;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* Fallback to default Fabric wrapping
|
|
1069
|
+
* @private
|
|
1070
|
+
*/
|
|
1071
|
+
_splitTextIntoLinesDefault(text: string) {
|
|
622
1072
|
const newText = super._splitTextIntoLines(text),
|
|
623
1073
|
graphemeLines = this._wrapText(newText.lines, this.width),
|
|
624
1074
|
lines = new Array(graphemeLines.length);
|
|
@@ -768,6 +1218,88 @@ export class Textbox<
|
|
|
768
1218
|
}
|
|
769
1219
|
}
|
|
770
1220
|
|
|
1221
|
+
/**
|
|
1222
|
+
* Fix character selection mismatch after JSON loading for browser-wrapped fonts
|
|
1223
|
+
* @private
|
|
1224
|
+
*/
|
|
1225
|
+
_fixCharacterMappingAfterJsonLoad(): void {
|
|
1226
|
+
if ((this as any)._usingBrowserWrapping) {
|
|
1227
|
+
// Clear all cached states to force fresh text layout calculation
|
|
1228
|
+
(this as any)._browserWrapCache = null;
|
|
1229
|
+
(this as any)._lastDimensionState = null;
|
|
1230
|
+
|
|
1231
|
+
// Force complete re-initialization
|
|
1232
|
+
this.initDimensions();
|
|
1233
|
+
this._forceClearCache = true;
|
|
1234
|
+
|
|
1235
|
+
// Ensure canvas refresh
|
|
1236
|
+
this.setCoords();
|
|
1237
|
+
if (this.canvas) {
|
|
1238
|
+
this.canvas.requestRenderAll();
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
/**
|
|
1244
|
+
* Force complete textbox re-initialization (useful after JSON loading)
|
|
1245
|
+
* Overrides Text version with Textbox-specific logic
|
|
1246
|
+
*/
|
|
1247
|
+
forceTextReinitialization(): void {
|
|
1248
|
+
console.log('🔄 Force reinitializing Textbox object');
|
|
1249
|
+
|
|
1250
|
+
// CRITICAL: Ensure textbox is marked as initialized
|
|
1251
|
+
this.initialized = true;
|
|
1252
|
+
|
|
1253
|
+
// Clear all caches and force dirty state
|
|
1254
|
+
this._clearCache();
|
|
1255
|
+
this.dirty = true;
|
|
1256
|
+
this.dynamicMinWidth = 0;
|
|
1257
|
+
|
|
1258
|
+
// Force isEditing false to ensure clean state
|
|
1259
|
+
this.isEditing = false;
|
|
1260
|
+
|
|
1261
|
+
console.log(' → Set initialized=true, dirty=true, cleared caches');
|
|
1262
|
+
|
|
1263
|
+
// Re-initialize dimensions (this will handle justify properly)
|
|
1264
|
+
this.initDimensions();
|
|
1265
|
+
|
|
1266
|
+
// Double-check that justify was applied by checking space widths
|
|
1267
|
+
if (this.textAlign.includes('justify') && this.__charBounds) {
|
|
1268
|
+
setTimeout(() => {
|
|
1269
|
+
// Verify justify was applied by checking if space widths vary
|
|
1270
|
+
let hasVariableSpaces = false;
|
|
1271
|
+
this.__charBounds.forEach((lineBounds, i) => {
|
|
1272
|
+
if (lineBounds && this._textLines && this._textLines[i]) {
|
|
1273
|
+
const spaces = lineBounds.filter((bound, j) => /\s/.test(this._textLines[i][j]));
|
|
1274
|
+
if (spaces.length > 1) {
|
|
1275
|
+
const firstSpaceWidth = spaces[0].width;
|
|
1276
|
+
hasVariableSpaces = spaces.some(space => Math.abs(space.width - firstSpaceWidth) > 0.1);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
if (!hasVariableSpaces && this.__charBounds.length > 0) {
|
|
1282
|
+
console.warn(' ⚠️ Justify spaces still uniform - forcing enlargeSpaces again');
|
|
1283
|
+
if (this.enlargeSpaces) {
|
|
1284
|
+
this.enlargeSpaces();
|
|
1285
|
+
}
|
|
1286
|
+
} else {
|
|
1287
|
+
console.log(' ✅ Justify spaces properly expanded');
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// Ensure height is recalculated - use browser height if available
|
|
1291
|
+
if ((this as any)._usingBrowserWrapping && (this as any)._actualBrowserHeight) {
|
|
1292
|
+
this.height = (this as any)._actualBrowserHeight;
|
|
1293
|
+
console.log(`🔤 JUSTIFY: Preserved browser height: ${this.height}px`);
|
|
1294
|
+
} else {
|
|
1295
|
+
this.height = this.calcTextHeight();
|
|
1296
|
+
console.log(`🔧 JUSTIFY: Used calcTextHeight: ${this.height}px`);
|
|
1297
|
+
}
|
|
1298
|
+
this.canvas?.requestRenderAll();
|
|
1299
|
+
}, 10);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
|
|
771
1303
|
/**
|
|
772
1304
|
* Returns object representation of an instance
|
|
773
1305
|
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
|