@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
|
@@ -337,21 +337,101 @@ export class OverlayEditor {
|
|
|
337
337
|
|
|
338
338
|
// Apply all other font and text styles to match Fabric
|
|
339
339
|
const letterSpacingPx = ((target.charSpacing || 0) / 1000) * finalFontSize;
|
|
340
|
+
|
|
341
|
+
// Special handling for text objects loaded from JSON - ensure they're properly initialized
|
|
342
|
+
if (target.dirty !== false && target.initDimensions) {
|
|
343
|
+
console.log('🔧 Ensuring text object is properly initialized before overlay editing');
|
|
344
|
+
// Force re-initialization if the text object seems to be in a dirty state
|
|
345
|
+
target.initDimensions();
|
|
346
|
+
}
|
|
340
347
|
|
|
341
348
|
this.textarea.style.fontSize = `${finalFontSize}px`;
|
|
342
349
|
this.textarea.style.lineHeight = String(fabricLineHeight);
|
|
343
350
|
this.textarea.style.fontFamily = target.fontFamily || 'Arial';
|
|
344
351
|
this.textarea.style.fontWeight = String(target.fontWeight || 'normal');
|
|
345
352
|
this.textarea.style.fontStyle = target.fontStyle || 'normal';
|
|
346
|
-
|
|
353
|
+
// Handle text alignment and justification
|
|
354
|
+
const textAlign = (target as any).textAlign || 'left';
|
|
355
|
+
let cssTextAlign = textAlign;
|
|
356
|
+
|
|
357
|
+
// Detect text direction from content for proper justify handling
|
|
358
|
+
const autoDetectedDirection = this.firstStrongDir(this.textarea.value || '');
|
|
359
|
+
|
|
360
|
+
// DEBUG: Log alignment details
|
|
361
|
+
console.log('🔍 ALIGNMENT DEBUG:');
|
|
362
|
+
console.log(' Fabric textAlign:', textAlign);
|
|
363
|
+
console.log(' Fabric direction:', (target as any).direction);
|
|
364
|
+
console.log(' Text content:', JSON.stringify(target.text));
|
|
365
|
+
console.log(' Detected direction:', autoDetectedDirection);
|
|
366
|
+
|
|
367
|
+
// Map fabric.js justify to CSS
|
|
368
|
+
if (textAlign.includes('justify')) {
|
|
369
|
+
// Try to match fabric.js justify behavior more precisely
|
|
370
|
+
try {
|
|
371
|
+
// For justify, we need to replicate fabric.js space expansion
|
|
372
|
+
// Use CSS justify but with specific settings to match fabric.js better
|
|
373
|
+
cssTextAlign = 'justify';
|
|
374
|
+
|
|
375
|
+
// Set text-align-last based on justify type and detected direction
|
|
376
|
+
// Smart justify: respect detected direction even when fabric alignment doesn't match
|
|
377
|
+
if (textAlign === 'justify') {
|
|
378
|
+
this.textarea.style.textAlignLast = autoDetectedDirection === 'rtl' ? 'right' : 'left';
|
|
379
|
+
} else if (textAlign === 'justify-left') {
|
|
380
|
+
// If text is RTL but fabric says justify-left, override to justify-right for better UX
|
|
381
|
+
if (autoDetectedDirection === 'rtl') {
|
|
382
|
+
this.textarea.style.textAlignLast = 'right';
|
|
383
|
+
console.log(' → Overrode justify-left to justify-right for RTL text');
|
|
384
|
+
} else {
|
|
385
|
+
this.textarea.style.textAlignLast = 'left';
|
|
386
|
+
}
|
|
387
|
+
} else if (textAlign === 'justify-right') {
|
|
388
|
+
// If text is LTR but fabric says justify-right, override to justify-left for better UX
|
|
389
|
+
if (autoDetectedDirection === 'ltr') {
|
|
390
|
+
this.textarea.style.textAlignLast = 'left';
|
|
391
|
+
console.log(' → Overrode justify-right to justify-left for LTR text');
|
|
392
|
+
} else {
|
|
393
|
+
this.textarea.style.textAlignLast = 'right';
|
|
394
|
+
}
|
|
395
|
+
} else if (textAlign === 'justify-center') {
|
|
396
|
+
this.textarea.style.textAlignLast = 'center';
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Enhanced justify settings for better fabric.js matching
|
|
400
|
+
(this.textarea.style as any).textJustify = 'inter-word';
|
|
401
|
+
(this.textarea.style as any).wordSpacing = 'normal';
|
|
402
|
+
|
|
403
|
+
// Additional CSS properties for better justify matching
|
|
404
|
+
this.textarea.style.textAlign = 'justify';
|
|
405
|
+
this.textarea.style.textAlignLast = this.textarea.style.textAlignLast;
|
|
406
|
+
|
|
407
|
+
// Try to force better justify behavior
|
|
408
|
+
(this.textarea.style as any).textJustifyTrim = 'none';
|
|
409
|
+
(this.textarea.style as any).textAutospace = 'none';
|
|
410
|
+
|
|
411
|
+
console.log(' → Applied justify alignment:', textAlign, 'with last-line:', this.textarea.style.textAlignLast);
|
|
412
|
+
} catch (error) {
|
|
413
|
+
console.warn(' → Justify setup failed, falling back to standard alignment:', error);
|
|
414
|
+
cssTextAlign = textAlign.replace('justify-', '').replace('justify', 'left');
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
this.textarea.style.textAlignLast = 'auto';
|
|
418
|
+
(this.textarea.style as any).textJustify = 'auto';
|
|
419
|
+
(this.textarea.style as any).wordSpacing = 'normal';
|
|
420
|
+
console.log(' → Applied standard alignment:', cssTextAlign);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
this.textarea.style.textAlign = cssTextAlign;
|
|
347
424
|
this.textarea.style.color = target.fill?.toString() || '#000';
|
|
348
425
|
this.textarea.style.letterSpacing = `${letterSpacingPx}px`;
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
426
|
+
|
|
427
|
+
// Use the already detected direction from above
|
|
428
|
+
const fabricDirection = (target as any).direction;
|
|
429
|
+
|
|
430
|
+
// Use auto-detected direction for better BiDi support, but respect fabric direction if it makes sense
|
|
431
|
+
this.textarea.style.direction = autoDetectedDirection || fabricDirection || 'ltr';
|
|
352
432
|
this.textarea.style.fontVariant = 'normal';
|
|
353
433
|
this.textarea.style.fontStretch = 'normal';
|
|
354
|
-
this.textarea.style.textRendering = 'optimizeLegibility'
|
|
434
|
+
this.textarea.style.textRendering = 'auto'; // Changed from 'optimizeLegibility' to match canvas
|
|
355
435
|
this.textarea.style.fontKerning = 'normal';
|
|
356
436
|
this.textarea.style.fontFeatureSettings = 'normal';
|
|
357
437
|
this.textarea.style.fontVariationSettings = 'normal';
|
|
@@ -363,14 +443,61 @@ export class OverlayEditor {
|
|
|
363
443
|
this.textarea.style.whiteSpace = 'pre-wrap';
|
|
364
444
|
this.textarea.style.hyphens = 'none';
|
|
365
445
|
|
|
366
|
-
|
|
367
|
-
(
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
this.
|
|
446
|
+
// DEBUG: Log final CSS properties
|
|
447
|
+
console.log('🎨 FINAL TEXTAREA CSS:');
|
|
448
|
+
console.log(' textAlign:', this.textarea.style.textAlign);
|
|
449
|
+
console.log(' textAlignLast:', this.textarea.style.textAlignLast);
|
|
450
|
+
console.log(' direction:', this.textarea.style.direction);
|
|
451
|
+
console.log(' unicodeBidi:', this.textarea.style.unicodeBidi);
|
|
452
|
+
console.log(' width:', this.textarea.style.width);
|
|
453
|
+
console.log(' textJustify:', (this.textarea.style as any).textJustify);
|
|
454
|
+
console.log(' wordSpacing:', (this.textarea.style as any).wordSpacing);
|
|
455
|
+
console.log(' whiteSpace:', this.textarea.style.whiteSpace);
|
|
456
|
+
|
|
457
|
+
// If justify, log Fabric object dimensions for comparison
|
|
458
|
+
if (textAlign.includes('justify')) {
|
|
459
|
+
console.log('🔧 FABRIC OBJECT JUSTIFY INFO:');
|
|
460
|
+
console.log(' Fabric width:', (target as any).width);
|
|
461
|
+
console.log(' Fabric calcTextWidth:', (target as any).calcTextWidth?.());
|
|
462
|
+
console.log(' Fabric textAlign:', (target as any).textAlign);
|
|
463
|
+
console.log(' Text lines:', (target as any).textLines);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Debug font properties matching
|
|
467
|
+
console.log('🔤 FONT PROPERTIES COMPARISON:');
|
|
468
|
+
console.log(' Fabric fontFamily:', target.fontFamily);
|
|
469
|
+
console.log(' Fabric fontWeight:', target.fontWeight);
|
|
470
|
+
console.log(' Fabric fontStyle:', target.fontStyle);
|
|
471
|
+
console.log(' Fabric fontSize:', target.fontSize);
|
|
472
|
+
console.log(' → Textarea fontFamily:', this.textarea.style.fontFamily);
|
|
473
|
+
console.log(' → Textarea fontWeight:', this.textarea.style.fontWeight);
|
|
474
|
+
console.log(' → Textarea fontStyle:', this.textarea.style.fontStyle);
|
|
475
|
+
console.log(' → Textarea fontSize:', this.textarea.style.fontSize);
|
|
476
|
+
|
|
477
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
478
|
+
|
|
479
|
+
// Enhanced font rendering to better match fabric.js canvas rendering
|
|
480
|
+
// Default to auto for more natural rendering
|
|
481
|
+
(this.textarea.style as any).webkitFontSmoothing = 'auto';
|
|
482
|
+
(this.textarea.style as any).mozOsxFontSmoothing = 'auto';
|
|
483
|
+
(this.textarea.style as any).fontSmooth = 'auto';
|
|
484
|
+
(this.textarea.style as any).textSizeAdjust = 'none';
|
|
485
|
+
|
|
486
|
+
// For bold fonts, use subpixel rendering to match canvas thickness better
|
|
487
|
+
const fontWeight = String(target.fontWeight || 'normal');
|
|
488
|
+
const isBold = fontWeight === 'bold' || fontWeight === '700' ||
|
|
489
|
+
(parseInt(fontWeight) >= 600);
|
|
490
|
+
|
|
491
|
+
if (isBold) {
|
|
492
|
+
(this.textarea.style as any).webkitFontSmoothing = 'subpixel-antialiased';
|
|
493
|
+
(this.textarea.style as any).mozOsxFontSmoothing = 'unset';
|
|
494
|
+
console.log('🔤 Applied enhanced bold rendering for better thickness matching');
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
console.log('🎨 FONT SMOOTHING APPLIED:');
|
|
498
|
+
console.log(' webkitFontSmoothing:', (this.textarea.style as any).webkitFontSmoothing);
|
|
499
|
+
console.log(' mozOsxFontSmoothing:', (this.textarea.style as any).mozOsxFontSmoothing);
|
|
371
500
|
|
|
372
|
-
// Debug: Compare text wrapping behavior
|
|
373
|
-
this.debugTextWrapping();
|
|
374
501
|
|
|
375
502
|
// Initial bounds are set correctly by Fabric.js - don't force update here
|
|
376
503
|
}
|
|
@@ -615,6 +742,11 @@ export class OverlayEditor {
|
|
|
615
742
|
this.target.setCoords();
|
|
616
743
|
this.applyOverlayStyle();
|
|
617
744
|
|
|
745
|
+
// Fix character mapping issues after JSON loading for browser-wrapped fonts
|
|
746
|
+
if ((this.target as any)._fixCharacterMappingAfterJsonLoad) {
|
|
747
|
+
(this.target as any)._fixCharacterMappingAfterJsonLoad();
|
|
748
|
+
}
|
|
749
|
+
|
|
618
750
|
this.textarea.focus();
|
|
619
751
|
|
|
620
752
|
this.textarea.setSelectionRange(
|
|
@@ -664,6 +796,26 @@ export class OverlayEditor {
|
|
|
664
796
|
// Handle commit/cancel after restoring visibility
|
|
665
797
|
if (commit && !this.isComposing) {
|
|
666
798
|
const finalText = this.textarea.value;
|
|
799
|
+
|
|
800
|
+
// Auto-detect text direction and update fabric object if needed
|
|
801
|
+
const detectedDirection = this.firstStrongDir(finalText);
|
|
802
|
+
const currentDirection = (this.target as any).direction || 'ltr';
|
|
803
|
+
|
|
804
|
+
if (detectedDirection && detectedDirection !== currentDirection) {
|
|
805
|
+
console.log(`🔄 Overlay Exit: Auto-detected direction change from "${currentDirection}" to "${detectedDirection}"`);
|
|
806
|
+
console.log(` Text content: "${finalText.substring(0, 50)}..."`);
|
|
807
|
+
|
|
808
|
+
// Update the fabric object's direction
|
|
809
|
+
(this.target as any).set('direction', detectedDirection);
|
|
810
|
+
|
|
811
|
+
// Force a re-render to apply the direction change
|
|
812
|
+
this.canvas.requestRenderAll();
|
|
813
|
+
|
|
814
|
+
console.log(`✅ Fabric object direction updated to: ${detectedDirection}`);
|
|
815
|
+
} else {
|
|
816
|
+
console.log(`📝 Overlay Exit: Direction unchanged (${currentDirection}), text: "${finalText.substring(0, 30)}..."`);
|
|
817
|
+
}
|
|
818
|
+
|
|
667
819
|
if (this.onCommit) {
|
|
668
820
|
this.onCommit(finalText);
|
|
669
821
|
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {
|
|
2
|
+
pointDistance,
|
|
3
|
+
normalizeVector,
|
|
4
|
+
angleBetweenVectors,
|
|
5
|
+
getMaxRadius,
|
|
6
|
+
calculateRoundedCorner,
|
|
7
|
+
applyCornerRadiusToPolygon,
|
|
8
|
+
generateRoundedPolygonPath,
|
|
9
|
+
} from './cornerRadius';
|
|
10
|
+
|
|
11
|
+
describe('cornerRadius utilities', () => {
|
|
12
|
+
describe('pointDistance', () => {
|
|
13
|
+
it('should calculate distance between two points correctly', () => {
|
|
14
|
+
expect(pointDistance({ x: 0, y: 0 }, { x: 3, y: 4 })).toBe(5);
|
|
15
|
+
expect(pointDistance({ x: 1, y: 1 }, { x: 1, y: 1 })).toBe(0);
|
|
16
|
+
expect(pointDistance({ x: 0, y: 0 }, { x: 1, y: 0 })).toBe(1);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('normalizeVector', () => {
|
|
21
|
+
it('should normalize vectors correctly', () => {
|
|
22
|
+
const result = normalizeVector({ x: 3, y: 4 });
|
|
23
|
+
expect(result.x).toBeCloseTo(0.6);
|
|
24
|
+
expect(result.y).toBeCloseTo(0.8);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should handle zero vector', () => {
|
|
28
|
+
const result = normalizeVector({ x: 0, y: 0 });
|
|
29
|
+
expect(result).toEqual({ x: 0, y: 0 });
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('getMaxRadius', () => {
|
|
34
|
+
it('should return half of the shortest adjacent edge', () => {
|
|
35
|
+
const prevPoint = { x: 0, y: 0 };
|
|
36
|
+
const currentPoint = { x: 10, y: 0 };
|
|
37
|
+
const nextPoint = { x: 10, y: 5 };
|
|
38
|
+
|
|
39
|
+
expect(getMaxRadius(prevPoint, currentPoint, nextPoint)).toBe(2.5);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('calculateRoundedCorner', () => {
|
|
44
|
+
it('should calculate rounded corner data correctly', () => {
|
|
45
|
+
const prevPoint = { x: 0, y: 0 };
|
|
46
|
+
const currentPoint = { x: 10, y: 0 };
|
|
47
|
+
const nextPoint = { x: 10, y: 10 };
|
|
48
|
+
const radius = 2;
|
|
49
|
+
|
|
50
|
+
const result = calculateRoundedCorner(prevPoint, currentPoint, nextPoint, radius);
|
|
51
|
+
|
|
52
|
+
expect(result.corner).toEqual(currentPoint);
|
|
53
|
+
expect(result.actualRadius).toBe(radius);
|
|
54
|
+
expect(result.start.x).toBeCloseTo(8);
|
|
55
|
+
expect(result.start.y).toBeCloseTo(0);
|
|
56
|
+
expect(result.end.x).toBeCloseTo(10);
|
|
57
|
+
expect(result.end.y).toBeCloseTo(2);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should constrain radius to maximum allowed', () => {
|
|
61
|
+
const prevPoint = { x: 0, y: 0 };
|
|
62
|
+
const currentPoint = { x: 2, y: 0 };
|
|
63
|
+
const nextPoint = { x: 2, y: 2 };
|
|
64
|
+
const radius = 5; // Request larger radius than possible
|
|
65
|
+
|
|
66
|
+
const result = calculateRoundedCorner(prevPoint, currentPoint, nextPoint, radius);
|
|
67
|
+
|
|
68
|
+
expect(result.actualRadius).toBe(1); // Should be constrained to 1
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('applyCornerRadiusToPolygon', () => {
|
|
73
|
+
it('should apply corner radius to a simple triangle', () => {
|
|
74
|
+
const points = [
|
|
75
|
+
{ x: 0, y: 0 },
|
|
76
|
+
{ x: 10, y: 0 },
|
|
77
|
+
{ x: 5, y: 10 }
|
|
78
|
+
];
|
|
79
|
+
const radius = 2;
|
|
80
|
+
|
|
81
|
+
const result = applyCornerRadiusToPolygon(points, radius);
|
|
82
|
+
|
|
83
|
+
expect(result).toHaveLength(3);
|
|
84
|
+
expect(result[0].actualRadius).toBeCloseTo(2);
|
|
85
|
+
expect(result[1].actualRadius).toBeCloseTo(2);
|
|
86
|
+
expect(result[2].actualRadius).toBeCloseTo(2);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should handle percentage-based radius', () => {
|
|
90
|
+
const points = [
|
|
91
|
+
{ x: 0, y: 0 },
|
|
92
|
+
{ x: 100, y: 0 },
|
|
93
|
+
{ x: 100, y: 100 },
|
|
94
|
+
{ x: 0, y: 100 }
|
|
95
|
+
];
|
|
96
|
+
const radius = 10; // 10%
|
|
97
|
+
|
|
98
|
+
const result = applyCornerRadiusToPolygon(points, radius, true);
|
|
99
|
+
|
|
100
|
+
expect(result).toHaveLength(4);
|
|
101
|
+
expect(result[0].actualRadius).toBeCloseTo(10); // 10% of 100px = 10px
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should throw error for polygons with less than 3 points', () => {
|
|
105
|
+
expect(() => {
|
|
106
|
+
applyCornerRadiusToPolygon([{ x: 0, y: 0 }, { x: 1, y: 1 }], 5);
|
|
107
|
+
}).toThrow('Polygon must have at least 3 points');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('generateRoundedPolygonPath', () => {
|
|
112
|
+
it('should generate valid SVG path data', () => {
|
|
113
|
+
const points = [
|
|
114
|
+
{ x: 0, y: 0 },
|
|
115
|
+
{ x: 10, y: 0 },
|
|
116
|
+
{ x: 5, y: 10 }
|
|
117
|
+
];
|
|
118
|
+
const roundedCorners = applyCornerRadiusToPolygon(points, 2);
|
|
119
|
+
|
|
120
|
+
const pathData = generateRoundedPolygonPath(roundedCorners, true);
|
|
121
|
+
|
|
122
|
+
expect(pathData).toContain('M '); // Should start with move command
|
|
123
|
+
expect(pathData).toContain('C '); // Should contain bezier curve commands
|
|
124
|
+
expect(pathData).toContain('L '); // Should contain line commands
|
|
125
|
+
expect(pathData).toContain('Z'); // Should end with close command for closed path
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should generate open path when closed=false', () => {
|
|
129
|
+
const points = [
|
|
130
|
+
{ x: 0, y: 0 },
|
|
131
|
+
{ x: 10, y: 0 },
|
|
132
|
+
{ x: 5, y: 10 }
|
|
133
|
+
];
|
|
134
|
+
const roundedCorners = applyCornerRadiusToPolygon(points, 2);
|
|
135
|
+
|
|
136
|
+
const pathData = generateRoundedPolygonPath(roundedCorners, false);
|
|
137
|
+
|
|
138
|
+
expect(pathData).not.toContain('Z'); // Should not end with close command
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import type { XY } from '../../Point';
|
|
2
|
+
import { Point } from '../../Point';
|
|
3
|
+
import { kRect } from '../../constants';
|
|
4
|
+
|
|
5
|
+
export interface CornerRadiusOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Corner radius value
|
|
8
|
+
*/
|
|
9
|
+
radius: number;
|
|
10
|
+
/**
|
|
11
|
+
* Whether to apply radius as percentage of the smallest dimension
|
|
12
|
+
*/
|
|
13
|
+
radiusAsPercentage?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface RoundedCornerPoint {
|
|
17
|
+
/**
|
|
18
|
+
* Original corner point
|
|
19
|
+
*/
|
|
20
|
+
corner: XY;
|
|
21
|
+
/**
|
|
22
|
+
* Start point of the rounded corner arc
|
|
23
|
+
*/
|
|
24
|
+
start: XY;
|
|
25
|
+
/**
|
|
26
|
+
* End point of the rounded corner arc
|
|
27
|
+
*/
|
|
28
|
+
end: XY;
|
|
29
|
+
/**
|
|
30
|
+
* First control point for bezier curve
|
|
31
|
+
*/
|
|
32
|
+
cp1: XY;
|
|
33
|
+
/**
|
|
34
|
+
* Second control point for bezier curve
|
|
35
|
+
*/
|
|
36
|
+
cp2: XY;
|
|
37
|
+
/**
|
|
38
|
+
* Actual radius used (may be different from requested if constrained)
|
|
39
|
+
*/
|
|
40
|
+
actualRadius: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Calculate the distance between two points
|
|
45
|
+
*/
|
|
46
|
+
export function pointDistance(p1: XY, p2: XY): number {
|
|
47
|
+
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Normalize a vector
|
|
52
|
+
*/
|
|
53
|
+
export function normalizeVector(vector: XY): XY {
|
|
54
|
+
const length = Math.sqrt(vector.x * vector.x + vector.y * vector.y);
|
|
55
|
+
if (length === 0) return { x: 0, y: 0 };
|
|
56
|
+
return { x: vector.x / length, y: vector.y / length };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Calculate the angle between two vectors
|
|
61
|
+
*/
|
|
62
|
+
export function angleBetweenVectors(v1: XY, v2: XY): number {
|
|
63
|
+
const dot = v1.x * v2.x + v1.y * v2.y;
|
|
64
|
+
const det = v1.x * v2.y - v1.y * v2.x;
|
|
65
|
+
return Math.atan2(det, dot);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get the maximum allowed radius for a corner based on adjacent edge lengths
|
|
70
|
+
*/
|
|
71
|
+
export function getMaxRadius(
|
|
72
|
+
prevPoint: XY,
|
|
73
|
+
currentPoint: XY,
|
|
74
|
+
nextPoint: XY,
|
|
75
|
+
): number {
|
|
76
|
+
const dist1 = pointDistance(prevPoint, currentPoint);
|
|
77
|
+
const dist2 = pointDistance(currentPoint, nextPoint);
|
|
78
|
+
return Math.min(dist1, dist2) / 2;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Calculate rounded corner data for a single corner
|
|
83
|
+
*/
|
|
84
|
+
export function calculateRoundedCorner(
|
|
85
|
+
prevPoint: XY,
|
|
86
|
+
currentPoint: XY,
|
|
87
|
+
nextPoint: XY,
|
|
88
|
+
radius: number,
|
|
89
|
+
): RoundedCornerPoint {
|
|
90
|
+
// Calculate edge vectors
|
|
91
|
+
const edge1 = {
|
|
92
|
+
x: currentPoint.x - prevPoint.x,
|
|
93
|
+
y: currentPoint.y - prevPoint.y,
|
|
94
|
+
};
|
|
95
|
+
const edge2 = {
|
|
96
|
+
x: nextPoint.x - currentPoint.x,
|
|
97
|
+
y: nextPoint.y - currentPoint.y,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Normalize edge vectors
|
|
101
|
+
const norm1 = normalizeVector(edge1);
|
|
102
|
+
const norm2 = normalizeVector(edge2);
|
|
103
|
+
|
|
104
|
+
// Calculate the maximum allowed radius
|
|
105
|
+
const maxRadius = getMaxRadius(prevPoint, currentPoint, nextPoint);
|
|
106
|
+
const actualRadius = Math.min(radius, maxRadius);
|
|
107
|
+
|
|
108
|
+
// Calculate start and end points of the rounded corner
|
|
109
|
+
const startPoint = {
|
|
110
|
+
x: currentPoint.x - norm1.x * actualRadius,
|
|
111
|
+
y: currentPoint.y - norm1.y * actualRadius,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const endPoint = {
|
|
115
|
+
x: currentPoint.x + norm2.x * actualRadius,
|
|
116
|
+
y: currentPoint.y + norm2.y * actualRadius,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Calculate control points for bezier curve
|
|
120
|
+
// Using the magic number kRect for optimal circular approximation
|
|
121
|
+
const controlOffset = actualRadius * kRect;
|
|
122
|
+
|
|
123
|
+
const cp1 = {
|
|
124
|
+
x: startPoint.x + norm1.x * controlOffset,
|
|
125
|
+
y: startPoint.y + norm1.y * controlOffset,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const cp2 = {
|
|
129
|
+
x: endPoint.x - norm2.x * controlOffset,
|
|
130
|
+
y: endPoint.y - norm2.y * controlOffset,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
corner: currentPoint,
|
|
135
|
+
start: startPoint,
|
|
136
|
+
end: endPoint,
|
|
137
|
+
cp1,
|
|
138
|
+
cp2,
|
|
139
|
+
actualRadius,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Apply corner radius to a polygon defined by points
|
|
145
|
+
*/
|
|
146
|
+
export function applyCornerRadiusToPolygon(
|
|
147
|
+
points: XY[],
|
|
148
|
+
radius: number,
|
|
149
|
+
radiusAsPercentage = false,
|
|
150
|
+
): RoundedCornerPoint[] {
|
|
151
|
+
if (points.length < 3) {
|
|
152
|
+
throw new Error('Polygon must have at least 3 points');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Calculate bounding box if radius is percentage-based
|
|
156
|
+
let actualRadius = radius;
|
|
157
|
+
if (radiusAsPercentage) {
|
|
158
|
+
const minX = Math.min(...points.map((p) => p.x));
|
|
159
|
+
const maxX = Math.max(...points.map((p) => p.x));
|
|
160
|
+
const minY = Math.min(...points.map((p) => p.y));
|
|
161
|
+
const maxY = Math.max(...points.map((p) => p.y));
|
|
162
|
+
const width = maxX - minX;
|
|
163
|
+
const height = maxY - minY;
|
|
164
|
+
const minDimension = Math.min(width, height);
|
|
165
|
+
actualRadius = (radius / 100) * minDimension;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const roundedCorners: RoundedCornerPoint[] = [];
|
|
169
|
+
|
|
170
|
+
for (let i = 0; i < points.length; i++) {
|
|
171
|
+
const prevIndex = (i - 1 + points.length) % points.length;
|
|
172
|
+
const nextIndex = (i + 1) % points.length;
|
|
173
|
+
|
|
174
|
+
const prevPoint = points[prevIndex];
|
|
175
|
+
const currentPoint = points[i];
|
|
176
|
+
const nextPoint = points[nextIndex];
|
|
177
|
+
|
|
178
|
+
const roundedCorner = calculateRoundedCorner(
|
|
179
|
+
prevPoint,
|
|
180
|
+
currentPoint,
|
|
181
|
+
nextPoint,
|
|
182
|
+
actualRadius,
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
roundedCorners.push(roundedCorner);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return roundedCorners;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Render a rounded polygon to a canvas context
|
|
193
|
+
*/
|
|
194
|
+
export function renderRoundedPolygon(
|
|
195
|
+
ctx: CanvasRenderingContext2D,
|
|
196
|
+
roundedCorners: RoundedCornerPoint[],
|
|
197
|
+
closed = true,
|
|
198
|
+
) {
|
|
199
|
+
if (roundedCorners.length === 0) return;
|
|
200
|
+
|
|
201
|
+
ctx.beginPath();
|
|
202
|
+
|
|
203
|
+
// Start at the first corner's start point
|
|
204
|
+
const firstCorner = roundedCorners[0];
|
|
205
|
+
ctx.moveTo(firstCorner.start.x, firstCorner.start.y);
|
|
206
|
+
|
|
207
|
+
for (let i = 0; i < roundedCorners.length; i++) {
|
|
208
|
+
const corner = roundedCorners[i];
|
|
209
|
+
const nextIndex = (i + 1) % roundedCorners.length;
|
|
210
|
+
const nextCorner = roundedCorners[nextIndex];
|
|
211
|
+
|
|
212
|
+
// Draw the rounded corner using bezier curve
|
|
213
|
+
ctx.bezierCurveTo(
|
|
214
|
+
corner.cp1.x,
|
|
215
|
+
corner.cp1.y,
|
|
216
|
+
corner.cp2.x,
|
|
217
|
+
corner.cp2.y,
|
|
218
|
+
corner.end.x,
|
|
219
|
+
corner.end.y,
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// Draw line to next corner's start point (if not the last segment in open path)
|
|
223
|
+
if (i < roundedCorners.length - 1 || closed) {
|
|
224
|
+
ctx.lineTo(nextCorner.start.x, nextCorner.start.y);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (closed) {
|
|
229
|
+
ctx.closePath();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Generate SVG path data for a rounded polygon
|
|
235
|
+
*/
|
|
236
|
+
export function generateRoundedPolygonPath(
|
|
237
|
+
roundedCorners: RoundedCornerPoint[],
|
|
238
|
+
closed = true,
|
|
239
|
+
): string {
|
|
240
|
+
if (roundedCorners.length === 0) return '';
|
|
241
|
+
|
|
242
|
+
const pathData: string[] = [];
|
|
243
|
+
const firstCorner = roundedCorners[0];
|
|
244
|
+
|
|
245
|
+
// Move to first corner's start point
|
|
246
|
+
pathData.push(`M ${firstCorner.start.x} ${firstCorner.start.y}`);
|
|
247
|
+
|
|
248
|
+
for (let i = 0; i < roundedCorners.length; i++) {
|
|
249
|
+
const corner = roundedCorners[i];
|
|
250
|
+
const nextIndex = (i + 1) % roundedCorners.length;
|
|
251
|
+
const nextCorner = roundedCorners[nextIndex];
|
|
252
|
+
|
|
253
|
+
// Add bezier curve for the rounded corner
|
|
254
|
+
pathData.push(
|
|
255
|
+
`C ${corner.cp1.x} ${corner.cp1.y} ${corner.cp2.x} ${corner.cp2.y} ${corner.end.x} ${corner.end.y}`,
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
// Add line to next corner's start point (if not the last segment in open path)
|
|
259
|
+
if (i < roundedCorners.length - 1 || closed) {
|
|
260
|
+
pathData.push(`L ${nextCorner.start.x} ${nextCorner.start.y}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (closed) {
|
|
265
|
+
pathData.push('Z');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return pathData.join(' ');
|
|
269
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|