@nasser-sw/fabric 7.0.1-beta12 → 7.0.1-beta13
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/.playwright-mcp/after-justify-applied.png +0 -0
- package/.playwright-mcp/after-justify-click.png +0 -0
- package/.playwright-mcp/initial-state.png +0 -0
- package/.playwright-mcp/justify-fixed-final.png +0 -0
- package/45.6875Textbox.ts +0 -0
- package/dist/index.js +541 -477
- 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 +541 -477
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +541 -477
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +541 -477
- 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/Text/Text.min.mjs +1 -1
- package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.mjs +295 -295
- package/dist/src/shapes/Text/Text.mjs.map +1 -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 +245 -181
- package/dist/src/shapes/Textbox.mjs.map +1 -1
- package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/shapes/Text/Text.ts +2463 -2463
- package/src/shapes/Textbox.ts +1389 -1320
|
@@ -20,11 +20,11 @@ const textboxDefaultValues = {
|
|
|
20
20
|
|
|
21
21
|
// @TODO this is not complete
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
* Textbox class, based on IText, allows the user to resize the text rectangle
|
|
25
|
-
* and wraps lines automatically. Textboxes have their Y scaling locked, the
|
|
26
|
-
* user can only change width. Height is adjusted automatically based on the
|
|
27
|
-
* wrapping of lines.
|
|
23
|
+
/**
|
|
24
|
+
* Textbox class, based on IText, allows the user to resize the text rectangle
|
|
25
|
+
* and wraps lines automatically. Textboxes have their Y scaling locked, the
|
|
26
|
+
* user can only change width. Height is adjusted automatically based on the
|
|
27
|
+
* wrapping of lines.
|
|
28
28
|
*/
|
|
29
29
|
class Textbox extends IText {
|
|
30
30
|
static getDefaults() {
|
|
@@ -34,10 +34,10 @@ class Textbox extends IText {
|
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
* Constructor
|
|
39
|
-
* @param {String} text Text string
|
|
40
|
-
* @param {Object} [options] Options object
|
|
37
|
+
/**
|
|
38
|
+
* Constructor
|
|
39
|
+
* @param {String} text Text string
|
|
40
|
+
* @param {Object} [options] Options object
|
|
41
41
|
*/
|
|
42
42
|
constructor(text, options) {
|
|
43
43
|
super(text, {
|
|
@@ -47,10 +47,10 @@ class Textbox extends IText {
|
|
|
47
47
|
this.initializeEventListeners();
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
/**
|
|
51
|
-
* Creates the default control object.
|
|
52
|
-
* If you prefer to have on instance of controls shared among all objects
|
|
53
|
-
* make this function return an empty object and add controls to the ownDefaults object
|
|
50
|
+
/**
|
|
51
|
+
* Creates the default control object.
|
|
52
|
+
* If you prefer to have on instance of controls shared among all objects
|
|
53
|
+
* make this function return an empty object and add controls to the ownDefaults object
|
|
54
54
|
*/
|
|
55
55
|
static createControls() {
|
|
56
56
|
return {
|
|
@@ -58,11 +58,11 @@ class Textbox extends IText {
|
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
/**
|
|
62
|
-
* Unlike superclass's version of this function, Textbox does not update
|
|
63
|
-
* its width.
|
|
64
|
-
* @private
|
|
65
|
-
* @override
|
|
61
|
+
/**
|
|
62
|
+
* Unlike superclass's version of this function, Textbox does not update
|
|
63
|
+
* its width.
|
|
64
|
+
* @private
|
|
65
|
+
* @override
|
|
66
66
|
*/
|
|
67
67
|
initDimensions() {
|
|
68
68
|
if (!this.initialized) {
|
|
@@ -130,6 +130,7 @@ class Textbox extends IText {
|
|
|
130
130
|
if (this._usingBrowserWrapping) {
|
|
131
131
|
this._browserWrapInitialized = true;
|
|
132
132
|
}
|
|
133
|
+
this.calcTextWidth();
|
|
133
134
|
if (this.textAlign.includes(JUSTIFY)) {
|
|
134
135
|
// For browser wrapping fonts, apply browser-calculated justify spaces
|
|
135
136
|
if (this._usingBrowserWrapping) {
|
|
@@ -204,11 +205,38 @@ class Textbox extends IText {
|
|
|
204
205
|
} else {
|
|
205
206
|
this.height = this.calcTextHeight();
|
|
206
207
|
}
|
|
208
|
+
|
|
209
|
+
// Double-check that justify was applied by checking space widths
|
|
210
|
+
if (this.textAlign.includes('justify') && this.__charBounds) {
|
|
211
|
+
setTimeout(() => {
|
|
212
|
+
// Verify justify was applied by checking if space widths vary
|
|
213
|
+
let hasVariableSpaces = false;
|
|
214
|
+
this.__charBounds.forEach((lineBounds, i) => {
|
|
215
|
+
if (lineBounds && this._textLines && this._textLines[i]) {
|
|
216
|
+
const spaces = lineBounds.filter((bound, j) => /\s/.test(this._textLines[i][j]));
|
|
217
|
+
if (spaces.length > 1) {
|
|
218
|
+
const firstSpaceWidth = spaces[0].width;
|
|
219
|
+
hasVariableSpaces = spaces.some(space => Math.abs(space.width - firstSpaceWidth) > 0.1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
if (!hasVariableSpaces && this.__charBounds.length > 0) {
|
|
224
|
+
console.warn(' ⚠️ Justify spaces still uniform - forcing enlargeSpaces again');
|
|
225
|
+
if (this.enlargeSpaces) {
|
|
226
|
+
var _this$canvas3;
|
|
227
|
+
this.enlargeSpaces();
|
|
228
|
+
(_this$canvas3 = this.canvas) === null || _this$canvas3 === void 0 || _this$canvas3.requestRenderAll();
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
console.log(' ✅ Justify spaces properly expanded');
|
|
232
|
+
}
|
|
233
|
+
}, 10);
|
|
234
|
+
}
|
|
207
235
|
}
|
|
208
236
|
|
|
209
|
-
/**
|
|
210
|
-
* Schedule justify calculation after font loads (Textbox-specific)
|
|
211
|
-
* @private
|
|
237
|
+
/**
|
|
238
|
+
* Schedule justify calculation after font loads (Textbox-specific)
|
|
239
|
+
* @private
|
|
212
240
|
*/
|
|
213
241
|
_scheduleJustifyAfterFontLoad() {
|
|
214
242
|
if (typeof document === 'undefined' || !('fonts' in document)) {
|
|
@@ -222,22 +250,22 @@ class Textbox extends IText {
|
|
|
222
250
|
this._fontJustifyScheduled = true;
|
|
223
251
|
const fontSpec = `${this.fontSize}px ${this.fontFamily}`;
|
|
224
252
|
document.fonts.load(fontSpec).then(() => {
|
|
225
|
-
var _this$
|
|
253
|
+
var _this$canvas4;
|
|
226
254
|
this._fontJustifyScheduled = false;
|
|
227
255
|
console.log('🔧 Textbox: Font loaded, applying justify alignment');
|
|
228
256
|
|
|
229
257
|
// Re-run initDimensions to ensure proper justify calculation
|
|
230
258
|
this.initDimensions();
|
|
231
|
-
(_this$
|
|
259
|
+
(_this$canvas4 = this.canvas) === null || _this$canvas4 === void 0 || _this$canvas4.requestRenderAll();
|
|
232
260
|
}).catch(() => {
|
|
233
261
|
this._fontJustifyScheduled = false;
|
|
234
262
|
console.warn('⚠️ Textbox: Font loading failed, justify may be incorrect');
|
|
235
263
|
});
|
|
236
264
|
}
|
|
237
265
|
|
|
238
|
-
/**
|
|
239
|
-
* Advanced dimensions calculation using new layout engine
|
|
240
|
-
* @private
|
|
266
|
+
/**
|
|
267
|
+
* Advanced dimensions calculation using new layout engine
|
|
268
|
+
* @private
|
|
241
269
|
*/
|
|
242
270
|
initDimensionsAdvanced() {
|
|
243
271
|
if (!this.initialized) {
|
|
@@ -292,9 +320,9 @@ class Textbox extends IText {
|
|
|
292
320
|
this.dirty = true;
|
|
293
321
|
}
|
|
294
322
|
|
|
295
|
-
/**
|
|
296
|
-
* Generate style map from new layout format
|
|
297
|
-
* @private
|
|
323
|
+
/**
|
|
324
|
+
* Generate style map from new layout format
|
|
325
|
+
* @private
|
|
298
326
|
*/
|
|
299
327
|
_generateStyleMapFromLayout(layout) {
|
|
300
328
|
const map = {};
|
|
@@ -316,12 +344,12 @@ class Textbox extends IText {
|
|
|
316
344
|
return map;
|
|
317
345
|
}
|
|
318
346
|
|
|
319
|
-
/**
|
|
320
|
-
* Generate an object that translates the style object so that it is
|
|
321
|
-
* broken up by visual lines (new lines and automatic wrapping).
|
|
322
|
-
* The original text styles object is broken up by actual lines (new lines only),
|
|
323
|
-
* which is only sufficient for Text / IText
|
|
324
|
-
* @private
|
|
347
|
+
/**
|
|
348
|
+
* Generate an object that translates the style object so that it is
|
|
349
|
+
* broken up by visual lines (new lines and automatic wrapping).
|
|
350
|
+
* The original text styles object is broken up by actual lines (new lines only),
|
|
351
|
+
* which is only sufficient for Text / IText
|
|
352
|
+
* @private
|
|
325
353
|
*/
|
|
326
354
|
_generateStyleMap(textInfo) {
|
|
327
355
|
let realLineCount = 0,
|
|
@@ -348,10 +376,10 @@ class Textbox extends IText {
|
|
|
348
376
|
return map;
|
|
349
377
|
}
|
|
350
378
|
|
|
351
|
-
/**
|
|
352
|
-
* Returns true if object has a style property or has it on a specified line
|
|
353
|
-
* @param {Number} lineIndex
|
|
354
|
-
* @return {Boolean}
|
|
379
|
+
/**
|
|
380
|
+
* Returns true if object has a style property or has it on a specified line
|
|
381
|
+
* @param {Number} lineIndex
|
|
382
|
+
* @return {Boolean}
|
|
355
383
|
*/
|
|
356
384
|
styleHas(property, lineIndex) {
|
|
357
385
|
if (this._styleMap && !this.isWrapping) {
|
|
@@ -363,10 +391,10 @@ class Textbox extends IText {
|
|
|
363
391
|
return super.styleHas(property, lineIndex);
|
|
364
392
|
}
|
|
365
393
|
|
|
366
|
-
/**
|
|
367
|
-
* Returns true if object has no styling or no styling in a line
|
|
368
|
-
* @param {Number} lineIndex , lineIndex is on wrapped lines.
|
|
369
|
-
* @return {Boolean}
|
|
394
|
+
/**
|
|
395
|
+
* Returns true if object has no styling or no styling in a line
|
|
396
|
+
* @param {Number} lineIndex , lineIndex is on wrapped lines.
|
|
397
|
+
* @return {Boolean}
|
|
370
398
|
*/
|
|
371
399
|
isEmptyStyles(lineIndex) {
|
|
372
400
|
if (!this.styles) {
|
|
@@ -403,11 +431,11 @@ class Textbox extends IText {
|
|
|
403
431
|
return true;
|
|
404
432
|
}
|
|
405
433
|
|
|
406
|
-
/**
|
|
407
|
-
* @protected
|
|
408
|
-
* @param {Number} lineIndex
|
|
409
|
-
* @param {Number} charIndex
|
|
410
|
-
* @return {TextStyleDeclaration} a style object reference to the existing one or a new empty object when undefined
|
|
434
|
+
/**
|
|
435
|
+
* @protected
|
|
436
|
+
* @param {Number} lineIndex
|
|
437
|
+
* @param {Number} charIndex
|
|
438
|
+
* @return {TextStyleDeclaration} a style object reference to the existing one or a new empty object when undefined
|
|
411
439
|
*/
|
|
412
440
|
_getStyleDeclaration(lineIndex, charIndex) {
|
|
413
441
|
if (this._styleMap && !this.isWrapping) {
|
|
@@ -421,59 +449,59 @@ class Textbox extends IText {
|
|
|
421
449
|
return super._getStyleDeclaration(lineIndex, charIndex);
|
|
422
450
|
}
|
|
423
451
|
|
|
424
|
-
/**
|
|
425
|
-
* @param {Number} lineIndex
|
|
426
|
-
* @param {Number} charIndex
|
|
427
|
-
* @param {Object} style
|
|
428
|
-
* @private
|
|
452
|
+
/**
|
|
453
|
+
* @param {Number} lineIndex
|
|
454
|
+
* @param {Number} charIndex
|
|
455
|
+
* @param {Object} style
|
|
456
|
+
* @private
|
|
429
457
|
*/
|
|
430
458
|
_setStyleDeclaration(lineIndex, charIndex, style) {
|
|
431
459
|
const map = this._styleMap[lineIndex];
|
|
432
460
|
super._setStyleDeclaration(map.line, map.offset + charIndex, style);
|
|
433
461
|
}
|
|
434
462
|
|
|
435
|
-
/**
|
|
436
|
-
* @param {Number} lineIndex
|
|
437
|
-
* @param {Number} charIndex
|
|
438
|
-
* @private
|
|
463
|
+
/**
|
|
464
|
+
* @param {Number} lineIndex
|
|
465
|
+
* @param {Number} charIndex
|
|
466
|
+
* @private
|
|
439
467
|
*/
|
|
440
468
|
_deleteStyleDeclaration(lineIndex, charIndex) {
|
|
441
469
|
const map = this._styleMap[lineIndex];
|
|
442
470
|
super._deleteStyleDeclaration(map.line, map.offset + charIndex);
|
|
443
471
|
}
|
|
444
472
|
|
|
445
|
-
/**
|
|
446
|
-
* probably broken need a fix
|
|
447
|
-
* Returns the real style line that correspond to the wrapped lineIndex line
|
|
448
|
-
* Used just to verify if the line does exist or not.
|
|
449
|
-
* @param {Number} lineIndex
|
|
450
|
-
* @returns {Boolean} if the line exists or not
|
|
451
|
-
* @private
|
|
473
|
+
/**
|
|
474
|
+
* probably broken need a fix
|
|
475
|
+
* Returns the real style line that correspond to the wrapped lineIndex line
|
|
476
|
+
* Used just to verify if the line does exist or not.
|
|
477
|
+
* @param {Number} lineIndex
|
|
478
|
+
* @returns {Boolean} if the line exists or not
|
|
479
|
+
* @private
|
|
452
480
|
*/
|
|
453
481
|
_getLineStyle(lineIndex) {
|
|
454
482
|
const map = this._styleMap[lineIndex];
|
|
455
483
|
return !!this.styles[map.line];
|
|
456
484
|
}
|
|
457
485
|
|
|
458
|
-
/**
|
|
459
|
-
* Set the line style to an empty object so that is initialized
|
|
460
|
-
* @param {Number} lineIndex
|
|
461
|
-
* @param {Object} style
|
|
462
|
-
* @private
|
|
486
|
+
/**
|
|
487
|
+
* Set the line style to an empty object so that is initialized
|
|
488
|
+
* @param {Number} lineIndex
|
|
489
|
+
* @param {Object} style
|
|
490
|
+
* @private
|
|
463
491
|
*/
|
|
464
492
|
_setLineStyle(lineIndex) {
|
|
465
493
|
const map = this._styleMap[lineIndex];
|
|
466
494
|
super._setLineStyle(map.line);
|
|
467
495
|
}
|
|
468
496
|
|
|
469
|
-
/**
|
|
470
|
-
* Wraps text using the 'width' property of Textbox. First this function
|
|
471
|
-
* splits text on newlines, so we preserve newlines entered by the user.
|
|
472
|
-
* Then it wraps each line using the width of the Textbox by calling
|
|
473
|
-
* _wrapLine().
|
|
474
|
-
* @param {Array} lines The string array of text that is split into lines
|
|
475
|
-
* @param {Number} desiredWidth width you want to wrap to
|
|
476
|
-
* @returns {Array} Array of lines
|
|
497
|
+
/**
|
|
498
|
+
* Wraps text using the 'width' property of Textbox. First this function
|
|
499
|
+
* splits text on newlines, so we preserve newlines entered by the user.
|
|
500
|
+
* Then it wraps each line using the width of the Textbox by calling
|
|
501
|
+
* _wrapLine().
|
|
502
|
+
* @param {Array} lines The string array of text that is split into lines
|
|
503
|
+
* @param {Number} desiredWidth width you want to wrap to
|
|
504
|
+
* @returns {Array} Array of lines
|
|
477
505
|
*/
|
|
478
506
|
_wrapText(lines, desiredWidth) {
|
|
479
507
|
this.isWrapping = true;
|
|
@@ -487,12 +515,12 @@ class Textbox extends IText {
|
|
|
487
515
|
return wrapped;
|
|
488
516
|
}
|
|
489
517
|
|
|
490
|
-
/**
|
|
491
|
-
* For each line of text terminated by an hard line stop,
|
|
492
|
-
* measure each word width and extract the largest word from all.
|
|
493
|
-
* The returned words here are the one that at the end will be rendered.
|
|
494
|
-
* @param {string[]} lines the lines we need to measure
|
|
495
|
-
*
|
|
518
|
+
/**
|
|
519
|
+
* For each line of text terminated by an hard line stop,
|
|
520
|
+
* measure each word width and extract the largest word from all.
|
|
521
|
+
* The returned words here are the one that at the end will be rendered.
|
|
522
|
+
* @param {string[]} lines the lines we need to measure
|
|
523
|
+
*
|
|
496
524
|
*/
|
|
497
525
|
getGraphemeDataForRender(lines) {
|
|
498
526
|
const splitByGrapheme = this.splitByGrapheme,
|
|
@@ -525,17 +553,17 @@ class Textbox extends IText {
|
|
|
525
553
|
};
|
|
526
554
|
}
|
|
527
555
|
|
|
528
|
-
/**
|
|
529
|
-
* Helper function to measure a string of text, given its lineIndex and charIndex offset
|
|
530
|
-
* It gets called when charBounds are not available yet.
|
|
531
|
-
* Override if necessary
|
|
532
|
-
* Use with {@link Textbox#wordSplit}
|
|
533
|
-
*
|
|
534
|
-
* @param {CanvasRenderingContext2D} ctx
|
|
535
|
-
* @param {String} text
|
|
536
|
-
* @param {number} lineIndex
|
|
537
|
-
* @param {number} charOffset
|
|
538
|
-
* @returns {number}
|
|
556
|
+
/**
|
|
557
|
+
* Helper function to measure a string of text, given its lineIndex and charIndex offset
|
|
558
|
+
* It gets called when charBounds are not available yet.
|
|
559
|
+
* Override if necessary
|
|
560
|
+
* Use with {@link Textbox#wordSplit}
|
|
561
|
+
*
|
|
562
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
563
|
+
* @param {String} text
|
|
564
|
+
* @param {number} lineIndex
|
|
565
|
+
* @param {number} charOffset
|
|
566
|
+
* @returns {number}
|
|
539
567
|
*/
|
|
540
568
|
_measureWord(word, lineIndex) {
|
|
541
569
|
let charOffset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
|
|
@@ -550,26 +578,26 @@ class Textbox extends IText {
|
|
|
550
578
|
return width;
|
|
551
579
|
}
|
|
552
580
|
|
|
553
|
-
/**
|
|
554
|
-
* Override this method to customize word splitting
|
|
555
|
-
* Use with {@link Textbox#_measureWord}
|
|
556
|
-
* @param {string} value
|
|
557
|
-
* @returns {string[]} array of words
|
|
581
|
+
/**
|
|
582
|
+
* Override this method to customize word splitting
|
|
583
|
+
* Use with {@link Textbox#_measureWord}
|
|
584
|
+
* @param {string} value
|
|
585
|
+
* @returns {string[]} array of words
|
|
558
586
|
*/
|
|
559
587
|
wordSplit(value) {
|
|
560
588
|
return value.split(this._wordJoiners);
|
|
561
589
|
}
|
|
562
590
|
|
|
563
|
-
/**
|
|
564
|
-
* Wraps a line of text using the width of the Textbox as desiredWidth
|
|
565
|
-
* and leveraging the known width o words from GraphemeData
|
|
566
|
-
* @private
|
|
567
|
-
* @param {Number} lineIndex
|
|
568
|
-
* @param {Number} desiredWidth width you want to wrap the line to
|
|
569
|
-
* @param {GraphemeData} graphemeData an object containing all the lines' words width.
|
|
570
|
-
* @param {Number} reservedSpace space to remove from wrapping for custom functionalities
|
|
571
|
-
* @returns {Array} Array of line(s) into which the given text is wrapped
|
|
572
|
-
* to.
|
|
591
|
+
/**
|
|
592
|
+
* Wraps a line of text using the width of the Textbox as desiredWidth
|
|
593
|
+
* and leveraging the known width o words from GraphemeData
|
|
594
|
+
* @private
|
|
595
|
+
* @param {Number} lineIndex
|
|
596
|
+
* @param {Number} desiredWidth width you want to wrap the line to
|
|
597
|
+
* @param {GraphemeData} graphemeData an object containing all the lines' words width.
|
|
598
|
+
* @param {Number} reservedSpace space to remove from wrapping for custom functionalities
|
|
599
|
+
* @returns {Array} Array of line(s) into which the given text is wrapped
|
|
600
|
+
* to.
|
|
573
601
|
*/
|
|
574
602
|
_wrapLine(lineIndex, desiredWidth, _ref) {
|
|
575
603
|
let {
|
|
@@ -651,11 +679,11 @@ class Textbox extends IText {
|
|
|
651
679
|
return graphemeLines;
|
|
652
680
|
}
|
|
653
681
|
|
|
654
|
-
/**
|
|
655
|
-
* Detect if the text line is ended with an hard break
|
|
656
|
-
* text and itext do not have wrapping, return false
|
|
657
|
-
* @param {Number} lineIndex text to split
|
|
658
|
-
* @return {Boolean}
|
|
682
|
+
/**
|
|
683
|
+
* Detect if the text line is ended with an hard break
|
|
684
|
+
* text and itext do not have wrapping, return false
|
|
685
|
+
* @param {Number} lineIndex text to split
|
|
686
|
+
* @return {Boolean}
|
|
659
687
|
*/
|
|
660
688
|
isEndOfWrapping(lineIndex) {
|
|
661
689
|
if (!this._styleMap[lineIndex + 1]) {
|
|
@@ -669,12 +697,12 @@ class Textbox extends IText {
|
|
|
669
697
|
return false;
|
|
670
698
|
}
|
|
671
699
|
|
|
672
|
-
/**
|
|
673
|
-
* Detect if a line has a linebreak and so we need to account for it when moving
|
|
674
|
-
* and counting style.
|
|
675
|
-
* This is important only for splitByGrapheme at the end of wrapping.
|
|
676
|
-
* If we are not wrapping the offset is always 1
|
|
677
|
-
* @return Number
|
|
700
|
+
/**
|
|
701
|
+
* Detect if a line has a linebreak and so we need to account for it when moving
|
|
702
|
+
* and counting style.
|
|
703
|
+
* This is important only for splitByGrapheme at the end of wrapping.
|
|
704
|
+
* If we are not wrapping the offset is always 1
|
|
705
|
+
* @return Number
|
|
678
706
|
*/
|
|
679
707
|
missingNewlineOffset(lineIndex, skipWrapping) {
|
|
680
708
|
if (this.splitByGrapheme && !skipWrapping) {
|
|
@@ -683,12 +711,12 @@ class Textbox extends IText {
|
|
|
683
711
|
return 1;
|
|
684
712
|
}
|
|
685
713
|
|
|
686
|
-
/**
|
|
687
|
-
* Gets lines of text to render in the Textbox. This function calculates
|
|
688
|
-
* text wrapping on the fly every time it is called.
|
|
689
|
-
* @param {String} text text to split
|
|
690
|
-
* @returns {Array} Array of lines in the Textbox.
|
|
691
|
-
* @override
|
|
714
|
+
/**
|
|
715
|
+
* Gets lines of text to render in the Textbox. This function calculates
|
|
716
|
+
* text wrapping on the fly every time it is called.
|
|
717
|
+
* @param {String} text text to split
|
|
718
|
+
* @returns {Array} Array of lines in the Textbox.
|
|
719
|
+
* @override
|
|
692
720
|
*/
|
|
693
721
|
_splitTextIntoLines(text) {
|
|
694
722
|
// Check if we need browser wrapping using smart font detection
|
|
@@ -735,9 +763,9 @@ class Textbox extends IText {
|
|
|
735
763
|
return newText;
|
|
736
764
|
}
|
|
737
765
|
|
|
738
|
-
/**
|
|
739
|
-
* Use browser's native text wrapping for accurate handling of fonts without English glyphs
|
|
740
|
-
* @private
|
|
766
|
+
/**
|
|
767
|
+
* Use browser's native text wrapping for accurate handling of fonts without English glyphs
|
|
768
|
+
* @private
|
|
741
769
|
*/
|
|
742
770
|
_splitTextIntoLinesWithBrowser(text) {
|
|
743
771
|
if (typeof document === 'undefined') {
|
|
@@ -863,9 +891,9 @@ class Textbox extends IText {
|
|
|
863
891
|
};
|
|
864
892
|
}
|
|
865
893
|
|
|
866
|
-
/**
|
|
867
|
-
* Extract justify space measurements from browser
|
|
868
|
-
* @private
|
|
894
|
+
/**
|
|
895
|
+
* Extract justify space measurements from browser
|
|
896
|
+
* @private
|
|
869
897
|
*/
|
|
870
898
|
_extractJustifySpaceMeasurements(element, lines) {
|
|
871
899
|
console.log(`🔤 Extracting browser justify space measurements for ${lines.length} lines`);
|
|
@@ -901,9 +929,9 @@ class Textbox extends IText {
|
|
|
901
929
|
return spaceWidths;
|
|
902
930
|
}
|
|
903
931
|
|
|
904
|
-
/**
|
|
905
|
-
* Apply browser-calculated justify space measurements
|
|
906
|
-
* @private
|
|
932
|
+
/**
|
|
933
|
+
* Apply browser-calculated justify space measurements
|
|
934
|
+
* @private
|
|
907
935
|
*/
|
|
908
936
|
_applyBrowserJustifySpaces() {
|
|
909
937
|
if (!this._textLines || !this.__charBounds) {
|
|
@@ -926,23 +954,59 @@ class Textbox extends IText {
|
|
|
926
954
|
const lineBounds = this.__charBounds[lineIndex];
|
|
927
955
|
const lineSpaceWidths = spaceWidths[lineIndex];
|
|
928
956
|
let spaceIndex = 0;
|
|
957
|
+
let accumulatedSpace = 0;
|
|
958
|
+
const isRtl = this.direction === 'rtl';
|
|
959
|
+
const spaceDiffs = [];
|
|
960
|
+
|
|
961
|
+
// First, calculate the difference for each space
|
|
929
962
|
for (let charIndex = 0; charIndex < line.length; charIndex++) {
|
|
930
963
|
if (/\s/.test(line[charIndex]) && spaceIndex < lineSpaceWidths.length) {
|
|
931
|
-
const
|
|
932
|
-
if (
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
964
|
+
const charBound = lineBounds[charIndex];
|
|
965
|
+
if (charBound) {
|
|
966
|
+
spaceDiffs.push(lineSpaceWidths[spaceIndex] - charBound.width);
|
|
967
|
+
}
|
|
968
|
+
spaceIndex++;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
spaceIndex = 0;
|
|
972
|
+
let remainingDiff = spaceDiffs.reduce((a, b) => a + b, 0);
|
|
973
|
+
for (let charIndex = 0; charIndex < line.length; charIndex++) {
|
|
974
|
+
const charBound = lineBounds[charIndex];
|
|
975
|
+
if (!charBound) continue;
|
|
976
|
+
if (isRtl) {
|
|
977
|
+
charBound.left += remainingDiff;
|
|
978
|
+
} else {
|
|
979
|
+
charBound.left += accumulatedSpace;
|
|
980
|
+
}
|
|
981
|
+
if (/\s/.test(line[charIndex]) && spaceIndex < spaceDiffs.length) {
|
|
982
|
+
const diff = spaceDiffs[spaceIndex];
|
|
983
|
+
const oldWidth = charBound.width;
|
|
984
|
+
charBound.width += diff;
|
|
985
|
+
charBound.kernedWidth += diff;
|
|
986
|
+
console.log(`🔤 Line ${lineIndex} space ${spaceIndex}: ${oldWidth.toFixed(1)}px -> ${charBound.width.toFixed(1)}px`);
|
|
987
|
+
if (isRtl) {
|
|
988
|
+
remainingDiff -= diff;
|
|
989
|
+
} else {
|
|
990
|
+
accumulatedSpace += diff;
|
|
936
991
|
}
|
|
937
992
|
spaceIndex++;
|
|
938
993
|
}
|
|
939
994
|
}
|
|
995
|
+
// also need to update the last charBound
|
|
996
|
+
const lastBound = lineBounds[line.length];
|
|
997
|
+
if (lastBound) {
|
|
998
|
+
if (isRtl) {
|
|
999
|
+
lastBound.left += remainingDiff;
|
|
1000
|
+
} else {
|
|
1001
|
+
lastBound.left += accumulatedSpace;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
940
1004
|
});
|
|
941
1005
|
}
|
|
942
1006
|
|
|
943
|
-
/**
|
|
944
|
-
* Fallback to default Fabric wrapping
|
|
945
|
-
* @private
|
|
1007
|
+
/**
|
|
1008
|
+
* Fallback to default Fabric wrapping
|
|
1009
|
+
* @private
|
|
946
1010
|
*/
|
|
947
1011
|
_splitTextIntoLinesDefault(text) {
|
|
948
1012
|
const newText = super._splitTextIntoLines(text),
|
|
@@ -974,12 +1038,12 @@ class Textbox extends IText {
|
|
|
974
1038
|
}
|
|
975
1039
|
}
|
|
976
1040
|
|
|
977
|
-
/**
|
|
978
|
-
* Initialize event listeners for safety snap functionality
|
|
979
|
-
* @private
|
|
1041
|
+
/**
|
|
1042
|
+
* Initialize event listeners for safety snap functionality
|
|
1043
|
+
* @private
|
|
980
1044
|
*/
|
|
981
1045
|
initializeEventListeners() {
|
|
982
|
-
var _this$
|
|
1046
|
+
var _this$canvas5;
|
|
983
1047
|
// Track which side is being used for resize to handle position compensation
|
|
984
1048
|
let resizeOrigin = null;
|
|
985
1049
|
|
|
@@ -1010,7 +1074,7 @@ class Textbox extends IText {
|
|
|
1010
1074
|
});
|
|
1011
1075
|
|
|
1012
1076
|
// Also listen to canvas-level modified event as backup
|
|
1013
|
-
(_this$
|
|
1077
|
+
(_this$canvas5 = this.canvas) === null || _this$canvas5 === void 0 || _this$canvas5.on('object:modified', e => {
|
|
1014
1078
|
if (e.target === this) {
|
|
1015
1079
|
const currentResizeOrigin = resizeOrigin; // Capture the value before reset
|
|
1016
1080
|
setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);
|
|
@@ -1019,12 +1083,12 @@ class Textbox extends IText {
|
|
|
1019
1083
|
});
|
|
1020
1084
|
}
|
|
1021
1085
|
|
|
1022
|
-
/**
|
|
1023
|
-
* Safety snap to prevent glyph clipping after manual resize.
|
|
1024
|
-
* Similar to Polotno - checks if any glyphs are too close to edges
|
|
1025
|
-
* and automatically expands width if needed.
|
|
1026
|
-
* @private
|
|
1027
|
-
* @param resizeOrigin - Which side was used for resizing ('left' or 'right')
|
|
1086
|
+
/**
|
|
1087
|
+
* Safety snap to prevent glyph clipping after manual resize.
|
|
1088
|
+
* Similar to Polotno - checks if any glyphs are too close to edges
|
|
1089
|
+
* and automatically expands width if needed.
|
|
1090
|
+
* @private
|
|
1091
|
+
* @param resizeOrigin - Which side was used for resizing ('left' or 'right')
|
|
1028
1092
|
*/
|
|
1029
1093
|
safetySnapWidth(resizeOrigin) {
|
|
1030
1094
|
// For Textbox objects, we always want to check for clipping regardless of isWrapping flag
|
|
@@ -1054,7 +1118,7 @@ class Textbox extends IText {
|
|
|
1054
1118
|
const safetyThreshold = 2; // px - very subtle trigger
|
|
1055
1119
|
|
|
1056
1120
|
if (maxRequiredWidth > this.width - safetyThreshold) {
|
|
1057
|
-
var _this$
|
|
1121
|
+
var _this$canvas6;
|
|
1058
1122
|
// Set width to exactly what's needed + minimal safety margin
|
|
1059
1123
|
const newWidth = maxRequiredWidth + 1; // Add just 1px safety margin
|
|
1060
1124
|
|
|
@@ -1087,13 +1151,13 @@ class Textbox extends IText {
|
|
|
1087
1151
|
this.__overlayEditor.refresh();
|
|
1088
1152
|
}, 0);
|
|
1089
1153
|
}
|
|
1090
|
-
(_this$
|
|
1154
|
+
(_this$canvas6 = this.canvas) === null || _this$canvas6 === void 0 || _this$canvas6.requestRenderAll();
|
|
1091
1155
|
}
|
|
1092
1156
|
}
|
|
1093
1157
|
|
|
1094
|
-
/**
|
|
1095
|
-
* Fix character selection mismatch after JSON loading for browser-wrapped fonts
|
|
1096
|
-
* @private
|
|
1158
|
+
/**
|
|
1159
|
+
* Fix character selection mismatch after JSON loading for browser-wrapped fonts
|
|
1160
|
+
* @private
|
|
1097
1161
|
*/
|
|
1098
1162
|
_fixCharacterMappingAfterJsonLoad() {
|
|
1099
1163
|
if (this._usingBrowserWrapping) {
|
|
@@ -1113,9 +1177,9 @@ class Textbox extends IText {
|
|
|
1113
1177
|
}
|
|
1114
1178
|
}
|
|
1115
1179
|
|
|
1116
|
-
/**
|
|
1117
|
-
* Force complete textbox re-initialization (useful after JSON loading)
|
|
1118
|
-
* Overrides Text version with Textbox-specific logic
|
|
1180
|
+
/**
|
|
1181
|
+
* Force complete textbox re-initialization (useful after JSON loading)
|
|
1182
|
+
* Overrides Text version with Textbox-specific logic
|
|
1119
1183
|
*/
|
|
1120
1184
|
forceTextReinitialization() {
|
|
1121
1185
|
console.log('🔄 Force reinitializing Textbox object');
|
|
@@ -1138,7 +1202,7 @@ class Textbox extends IText {
|
|
|
1138
1202
|
// Double-check that justify was applied by checking space widths
|
|
1139
1203
|
if (this.textAlign.includes('justify') && this.__charBounds) {
|
|
1140
1204
|
setTimeout(() => {
|
|
1141
|
-
var _this$
|
|
1205
|
+
var _this$canvas7;
|
|
1142
1206
|
// Verify justify was applied by checking if space widths vary
|
|
1143
1207
|
let hasVariableSpaces = false;
|
|
1144
1208
|
this.__charBounds.forEach((lineBounds, i) => {
|
|
@@ -1167,36 +1231,36 @@ class Textbox extends IText {
|
|
|
1167
1231
|
this.height = this.calcTextHeight();
|
|
1168
1232
|
console.log(`🔧 JUSTIFY: Used calcTextHeight: ${this.height}px`);
|
|
1169
1233
|
}
|
|
1170
|
-
(_this$
|
|
1234
|
+
(_this$canvas7 = this.canvas) === null || _this$canvas7 === void 0 || _this$canvas7.requestRenderAll();
|
|
1171
1235
|
}, 10);
|
|
1172
1236
|
}
|
|
1173
1237
|
}
|
|
1174
1238
|
|
|
1175
|
-
/**
|
|
1176
|
-
* Returns object representation of an instance
|
|
1177
|
-
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
|
|
1178
|
-
* @return {Object} object representation of an instance
|
|
1239
|
+
/**
|
|
1240
|
+
* Returns object representation of an instance
|
|
1241
|
+
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
|
|
1242
|
+
* @return {Object} object representation of an instance
|
|
1179
1243
|
*/
|
|
1180
1244
|
toObject() {
|
|
1181
1245
|
let propertiesToInclude = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
1182
1246
|
return super.toObject(['minWidth', 'splitByGrapheme', ...propertiesToInclude]);
|
|
1183
1247
|
}
|
|
1184
1248
|
}
|
|
1185
|
-
/**
|
|
1186
|
-
* Minimum width of textbox, in pixels.
|
|
1187
|
-
* @type Number
|
|
1249
|
+
/**
|
|
1250
|
+
* Minimum width of textbox, in pixels.
|
|
1251
|
+
* @type Number
|
|
1188
1252
|
*/
|
|
1189
|
-
/**
|
|
1190
|
-
* Minimum calculated width of a textbox, in pixels.
|
|
1191
|
-
* fixed to 2 so that an empty textbox cannot go to 0
|
|
1192
|
-
* and is still selectable without text.
|
|
1193
|
-
* @type Number
|
|
1253
|
+
/**
|
|
1254
|
+
* Minimum calculated width of a textbox, in pixels.
|
|
1255
|
+
* fixed to 2 so that an empty textbox cannot go to 0
|
|
1256
|
+
* and is still selectable without text.
|
|
1257
|
+
* @type Number
|
|
1194
1258
|
*/
|
|
1195
|
-
/**
|
|
1196
|
-
* Use this boolean property in order to split strings that have no white space concept.
|
|
1197
|
-
* this is a cheap way to help with chinese/japanese
|
|
1198
|
-
* @type Boolean
|
|
1199
|
-
* @since 2.6.0
|
|
1259
|
+
/**
|
|
1260
|
+
* Use this boolean property in order to split strings that have no white space concept.
|
|
1261
|
+
* this is a cheap way to help with chinese/japanese
|
|
1262
|
+
* @type Boolean
|
|
1263
|
+
* @since 2.6.0
|
|
1200
1264
|
*/
|
|
1201
1265
|
_defineProperty(Textbox, "type", 'Textbox');
|
|
1202
1266
|
_defineProperty(Textbox, "textLayoutProperties", [...IText.textLayoutProperties, 'width']);
|