@nasser-sw/fabric 7.0.1-beta16 → 7.0.1-beta17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +7 -0
- package/dist/index.js +1982 -649
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.min.mjs +1 -1
- package/dist/index.min.mjs.map +1 -1
- package/dist/index.mjs +1982 -649
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +1982 -649
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +1982 -649
- package/dist/index.node.mjs.map +1 -1
- package/dist/package.json.min.mjs +1 -1
- package/dist/package.json.mjs +1 -1
- package/dist/src/shapes/IText/IText.d.ts +31 -6
- package/dist/src/shapes/IText/IText.d.ts.map +1 -1
- package/dist/src/shapes/IText/IText.min.mjs +1 -1
- package/dist/src/shapes/IText/IText.min.mjs.map +1 -1
- package/dist/src/shapes/IText/IText.mjs +495 -126
- package/dist/src/shapes/IText/IText.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextBehavior.d.ts +12 -0
- package/dist/src/shapes/IText/ITextBehavior.d.ts.map +1 -1
- package/dist/src/shapes/IText/ITextBehavior.min.mjs +1 -1
- package/dist/src/shapes/IText/ITextBehavior.min.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextBehavior.mjs +127 -36
- package/dist/src/shapes/IText/ITextBehavior.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.d.ts.map +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.min.mjs +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.min.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.mjs +21 -4
- package/dist/src/shapes/IText/ITextClickBehavior.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextKeyBehavior.min.mjs +1 -1
- package/dist/src/shapes/IText/ITextKeyBehavior.min.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextKeyBehavior.mjs +17 -21
- package/dist/src/shapes/IText/ITextKeyBehavior.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.d.ts +69 -1
- package/dist/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist/src/shapes/Text/Text.min.mjs +1 -1
- package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.mjs +374 -60
- package/dist/src/shapes/Text/Text.mjs.map +1 -1
- package/dist/src/shapes/Text/constants.d.ts.map +1 -1
- package/dist/src/shapes/Text/constants.min.mjs +1 -1
- package/dist/src/shapes/Text/constants.min.mjs.map +1 -1
- package/dist/src/shapes/Text/constants.mjs +2 -1
- package/dist/src/shapes/Text/constants.mjs.map +1 -1
- package/dist/src/shapes/Textbox.d.ts +8 -1
- package/dist/src/shapes/Textbox.d.ts.map +1 -1
- package/dist/src/shapes/Textbox.min.mjs +1 -1
- package/dist/src/shapes/Textbox.min.mjs.map +1 -1
- package/dist/src/shapes/Textbox.mjs +406 -63
- package/dist/src/shapes/Textbox.mjs.map +1 -1
- package/dist/src/text/hitTest.min.mjs +1 -1
- package/dist/src/text/hitTest.min.mjs.map +1 -1
- package/dist/src/text/hitTest.mjs +1 -198
- package/dist/src/text/hitTest.mjs.map +1 -1
- package/dist/src/text/layout.min.mjs +1 -1
- package/dist/src/text/layout.min.mjs.map +1 -1
- package/dist/src/text/layout.mjs +122 -5
- package/dist/src/text/layout.mjs.map +1 -1
- package/dist/src/text/overlayEditor.min.mjs +1 -1
- package/dist/src/text/overlayEditor.min.mjs.map +1 -1
- package/dist/src/text/overlayEditor.mjs +132 -142
- package/dist/src/text/overlayEditor.mjs.map +1 -1
- package/dist/src/text/unicode.d.ts +28 -0
- package/dist/src/text/unicode.d.ts.map +1 -1
- package/dist/src/text/unicode.min.mjs +1 -1
- package/dist/src/text/unicode.min.mjs.map +1 -1
- package/dist/src/text/unicode.mjs +294 -1
- package/dist/src/text/unicode.mjs.map +1 -1
- package/dist-extensions/src/shapes/IText/IText.d.ts +31 -6
- package/dist-extensions/src/shapes/IText/IText.d.ts.map +1 -1
- package/dist-extensions/src/shapes/IText/ITextBehavior.d.ts +12 -0
- package/dist-extensions/src/shapes/IText/ITextBehavior.d.ts.map +1 -1
- package/dist-extensions/src/shapes/IText/ITextClickBehavior.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/Text.d.ts +69 -1
- package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/constants.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Textbox.d.ts +8 -1
- package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
- package/dist-extensions/src/text/unicode.d.ts +28 -0
- package/dist-extensions/src/text/unicode.d.ts.map +1 -1
- package/package.json +164 -164
- package/rtl-debug.html +358 -200
- package/src/shapes/IText/IText.ts +524 -110
- package/src/shapes/IText/ITextBehavior.ts +174 -80
- package/src/shapes/IText/ITextClickBehavior.ts +20 -6
- package/src/shapes/IText/ITextKeyBehavior.ts +15 -15
- package/src/shapes/Text/Text.ts +488 -107
- package/src/shapes/Text/constants.ts +4 -2
- package/src/shapes/Textbox.ts +414 -65
- package/src/text/layout.ts +150 -23
- package/src/text/overlayEditor.ts +148 -148
- package/src/text/unicode.ts +177 -2
package/src/text/layout.ts
CHANGED
|
@@ -249,66 +249,71 @@ function layoutSingleLine(text: string, options: TextLayoutOptions, textOffset:
|
|
|
249
249
|
const graphemes = segmentGraphemes(text);
|
|
250
250
|
const bounds: GraphemeBounds[] = [];
|
|
251
251
|
const measurementOptions = createMeasurementOptions(options);
|
|
252
|
-
|
|
252
|
+
|
|
253
253
|
let x = 0;
|
|
254
254
|
let lineWidth = 0;
|
|
255
255
|
let lineHeight = 0;
|
|
256
256
|
let charIndex = textOffset; // Track character position in original text
|
|
257
|
-
|
|
258
|
-
// Measure each grapheme
|
|
257
|
+
|
|
258
|
+
// Measure each grapheme in logical order
|
|
259
259
|
for (let i = 0; i < graphemes.length; i++) {
|
|
260
260
|
const grapheme = graphemes[i];
|
|
261
261
|
const prevGrapheme = i > 0 ? graphemes[i - 1] : undefined;
|
|
262
|
-
|
|
262
|
+
|
|
263
263
|
// Measure with kerning
|
|
264
264
|
const measurement = measureGraphemeWithKerning(
|
|
265
265
|
grapheme,
|
|
266
266
|
prevGrapheme,
|
|
267
267
|
measurementOptions
|
|
268
268
|
);
|
|
269
|
-
|
|
269
|
+
|
|
270
270
|
// Apply letter spacing (Konva style - applied to ALL characters including last)
|
|
271
271
|
const letterSpacing = options.letterSpacing || 0;
|
|
272
|
-
const charSpacing = options.charSpacing ?
|
|
272
|
+
const charSpacing = options.charSpacing ?
|
|
273
273
|
(options.fontSize * options.charSpacing) / 1000 : 0;
|
|
274
|
-
|
|
274
|
+
|
|
275
275
|
const totalSpacing = letterSpacing + charSpacing;
|
|
276
276
|
const effectiveWidth = measurement.kernedWidth + totalSpacing;
|
|
277
|
-
|
|
277
|
+
|
|
278
278
|
bounds.push({
|
|
279
279
|
grapheme,
|
|
280
|
-
x,
|
|
280
|
+
x, // Will be updated by BiDi reordering
|
|
281
281
|
y: 0, // Will be adjusted later
|
|
282
282
|
width: measurement.width,
|
|
283
283
|
height: measurement.height,
|
|
284
284
|
kernedWidth: measurement.kernedWidth,
|
|
285
|
-
left: x,
|
|
285
|
+
left: x, // Logical position (cumulative)
|
|
286
286
|
baseline: measurement.baseline,
|
|
287
287
|
charIndex: charIndex, // Character position in original text
|
|
288
288
|
graphemeIndex: textOffset + i, // Grapheme index in original text
|
|
289
289
|
});
|
|
290
|
-
|
|
290
|
+
|
|
291
291
|
// Update character index for next iteration
|
|
292
292
|
charIndex += grapheme.length;
|
|
293
|
-
|
|
293
|
+
|
|
294
294
|
x += effectiveWidth;
|
|
295
295
|
lineWidth += effectiveWidth;
|
|
296
296
|
lineHeight = Math.max(lineHeight, measurement.height);
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
+
// Note: BiDi visual reordering is handled by the browser's canvas fillText
|
|
300
|
+
// The layout stores positions in logical order; hit testing handles the visual mapping
|
|
301
|
+
|
|
299
302
|
// Remove trailing spacing from total width (but keep in bounds for rendering)
|
|
300
303
|
if (bounds.length > 0) {
|
|
301
304
|
const letterSpacing = options.letterSpacing || 0;
|
|
302
|
-
const charSpacing = options.charSpacing ?
|
|
305
|
+
const charSpacing = options.charSpacing ?
|
|
303
306
|
(options.fontSize * options.charSpacing) / 1000 : 0;
|
|
304
307
|
const totalSpacing = letterSpacing + charSpacing;
|
|
305
|
-
|
|
308
|
+
|
|
306
309
|
// Konva applies letterSpacing to all chars, so we don't remove it
|
|
307
310
|
// lineWidth -= totalSpacing;
|
|
308
311
|
}
|
|
309
312
|
|
|
310
313
|
// Apply line height
|
|
311
|
-
|
|
314
|
+
// Note: Fabric.js uses _fontSizeMult = 1.13 for line height calculation
|
|
315
|
+
const fontSizeMult = 1.13;
|
|
316
|
+
const finalHeight = lineHeight * options.lineHeight * fontSizeMult;
|
|
312
317
|
|
|
313
318
|
return {
|
|
314
319
|
text,
|
|
@@ -390,18 +395,138 @@ function wrapByCharacters(text: string, maxWidth: number, options: TextLayoutOpt
|
|
|
390
395
|
return lines.length > 0 ? lines : [''];
|
|
391
396
|
}
|
|
392
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Apply BiDi visual reordering to calculate correct visual X positions
|
|
400
|
+
* This implements the Unicode Bidirectional Algorithm for character placement
|
|
401
|
+
*/
|
|
402
|
+
function applyBiDiVisualReordering(
|
|
403
|
+
line: LayoutLine,
|
|
404
|
+
options: TextLayoutOptions
|
|
405
|
+
): LayoutLine {
|
|
406
|
+
const baseDirection = options.direction === 'inherit' ? 'ltr' : options.direction;
|
|
407
|
+
|
|
408
|
+
// Quick check: if all characters are same direction as base, no reordering needed
|
|
409
|
+
const runs = analyzeBiDi(line.text, baseDirection);
|
|
410
|
+
const hasMixedBiDi = runs.length > 1 || (runs.length === 1 && runs[0].direction !== baseDirection);
|
|
411
|
+
|
|
412
|
+
if (!hasMixedBiDi) {
|
|
413
|
+
// For pure LTR or pure RTL, just set visual x = logical left
|
|
414
|
+
// For RTL base direction, we need to flip positions
|
|
415
|
+
if (baseDirection === 'rtl') {
|
|
416
|
+
// RTL: rightmost character should be at x=0, leftmost at x=lineWidth
|
|
417
|
+
line.bounds.forEach(bound => {
|
|
418
|
+
bound.x = line.width - bound.left - bound.kernedWidth;
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
// For LTR, x is already correct (same as left)
|
|
422
|
+
return line;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Mixed BiDi text - need to reorder runs visually
|
|
426
|
+
// 1. Build mapping from grapheme index to run
|
|
427
|
+
const graphemeToRun: number[] = [];
|
|
428
|
+
let runGraphemeStart = 0;
|
|
429
|
+
|
|
430
|
+
for (let runIdx = 0; runIdx < runs.length; runIdx++) {
|
|
431
|
+
const run = runs[runIdx];
|
|
432
|
+
const runGraphemes = segmentGraphemes(run.text);
|
|
433
|
+
for (let i = 0; i < runGraphemes.length; i++) {
|
|
434
|
+
graphemeToRun.push(runIdx);
|
|
435
|
+
}
|
|
436
|
+
runGraphemeStart += runGraphemes.length;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// 2. Calculate run widths and positions
|
|
440
|
+
const runWidths: number[] = [];
|
|
441
|
+
const runStartIndices: number[] = [];
|
|
442
|
+
let currentIdx = 0;
|
|
443
|
+
|
|
444
|
+
for (const run of runs) {
|
|
445
|
+
runStartIndices.push(currentIdx);
|
|
446
|
+
const runGraphemes = segmentGraphemes(run.text);
|
|
447
|
+
let runWidth = 0;
|
|
448
|
+
for (let i = 0; i < runGraphemes.length; i++) {
|
|
449
|
+
if (currentIdx + i < line.bounds.length) {
|
|
450
|
+
const letterSpacing = options.letterSpacing || 0;
|
|
451
|
+
const charSpacing = options.charSpacing ?
|
|
452
|
+
(options.fontSize * options.charSpacing) / 1000 : 0;
|
|
453
|
+
runWidth += line.bounds[currentIdx + i].kernedWidth + letterSpacing + charSpacing;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
runWidths.push(runWidth);
|
|
457
|
+
currentIdx += runGraphemes.length;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// 3. Determine visual order of runs based on base direction
|
|
461
|
+
// RTL base: runs display right-to-left (first run on right)
|
|
462
|
+
// LTR base: runs display left-to-right (first run on left)
|
|
463
|
+
const visualRunOrder = runs.map((_, i) => i);
|
|
464
|
+
if (baseDirection === 'rtl') {
|
|
465
|
+
visualRunOrder.reverse();
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// 4. Calculate visual X position for each run
|
|
469
|
+
const runVisualX: number[] = new Array(runs.length);
|
|
470
|
+
let currentX = 0;
|
|
471
|
+
|
|
472
|
+
for (const runIdx of visualRunOrder) {
|
|
473
|
+
runVisualX[runIdx] = currentX;
|
|
474
|
+
currentX += runWidths[runIdx];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// 5. Assign visual X positions to each grapheme
|
|
478
|
+
for (let i = 0; i < line.bounds.length; i++) {
|
|
479
|
+
const runIdx = graphemeToRun[i];
|
|
480
|
+
if (runIdx === undefined) continue;
|
|
481
|
+
|
|
482
|
+
const run = runs[runIdx];
|
|
483
|
+
const runStart = runStartIndices[runIdx];
|
|
484
|
+
|
|
485
|
+
// Calculate spacing once
|
|
486
|
+
const letterSpacing = options.letterSpacing || 0;
|
|
487
|
+
const charSpacing = options.charSpacing ?
|
|
488
|
+
(options.fontSize * options.charSpacing) / 1000 : 0;
|
|
489
|
+
const totalSpacing = letterSpacing + charSpacing;
|
|
490
|
+
|
|
491
|
+
// Calculate offset within run (sum of widths of chars before this one)
|
|
492
|
+
let offsetInRun = 0;
|
|
493
|
+
for (let j = runStart; j < i; j++) {
|
|
494
|
+
offsetInRun += line.bounds[j].kernedWidth + totalSpacing;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Character width including spacing
|
|
498
|
+
const charWidth = line.bounds[i].kernedWidth + totalSpacing;
|
|
499
|
+
|
|
500
|
+
// For RTL runs, characters within the run are reversed visually
|
|
501
|
+
// First logical char appears on the right, last on the left
|
|
502
|
+
if (run.direction === 'rtl') {
|
|
503
|
+
// Visual X = run right edge - cumulative width including this char
|
|
504
|
+
// This places first char at right side of run, last char at left side
|
|
505
|
+
line.bounds[i].x = runVisualX[runIdx] + runWidths[runIdx] - offsetInRun - charWidth;
|
|
506
|
+
} else {
|
|
507
|
+
// LTR run: visual position is run start + offset within run
|
|
508
|
+
line.bounds[i].x = runVisualX[runIdx] + offsetInRun;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return line;
|
|
513
|
+
}
|
|
514
|
+
|
|
393
515
|
/**
|
|
394
516
|
* Apply text alignment to lines
|
|
395
517
|
*/
|
|
396
518
|
function applyAlignment(
|
|
397
|
-
lines: LayoutLine[],
|
|
398
|
-
align: string,
|
|
519
|
+
lines: LayoutLine[],
|
|
520
|
+
align: string,
|
|
399
521
|
containerWidth: number,
|
|
400
522
|
options: TextLayoutOptions
|
|
401
523
|
): LayoutLine[] {
|
|
402
524
|
return lines.map(line => {
|
|
525
|
+
// First apply BiDi reordering to get correct visual X positions
|
|
526
|
+
applyBiDiVisualReordering(line, options);
|
|
527
|
+
|
|
403
528
|
let offsetX = 0;
|
|
404
|
-
|
|
529
|
+
|
|
405
530
|
switch (align) {
|
|
406
531
|
case 'center':
|
|
407
532
|
offsetX = (containerWidth - line.width) / 2;
|
|
@@ -419,15 +544,15 @@ function applyAlignment(
|
|
|
419
544
|
offsetX = 0;
|
|
420
545
|
break;
|
|
421
546
|
}
|
|
422
|
-
|
|
423
|
-
// Apply offset to all bounds
|
|
547
|
+
|
|
548
|
+
// Apply offset to all bounds (both visual x and logical left for alignment)
|
|
424
549
|
if (offsetX !== 0) {
|
|
425
550
|
line.bounds.forEach(bound => {
|
|
426
551
|
bound.x += offsetX;
|
|
427
552
|
bound.left += offsetX;
|
|
428
553
|
});
|
|
429
554
|
}
|
|
430
|
-
|
|
555
|
+
|
|
431
556
|
return line;
|
|
432
557
|
});
|
|
433
558
|
}
|
|
@@ -530,8 +655,10 @@ function handleHeightOverflow(
|
|
|
530
655
|
* Create empty line for empty paragraphs
|
|
531
656
|
*/
|
|
532
657
|
function createEmptyLine(options: TextLayoutOptions): LayoutLine {
|
|
533
|
-
|
|
534
|
-
|
|
658
|
+
// Fabric.js uses _fontSizeMult = 1.13 for line height calculation
|
|
659
|
+
const fontSizeMult = 1.13;
|
|
660
|
+
const height = options.fontSize * options.lineHeight * fontSizeMult;
|
|
661
|
+
|
|
535
662
|
return {
|
|
536
663
|
text: '',
|
|
537
664
|
graphemes: [],
|