@nasser-sw/fabric 7.0.0-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.
Files changed (183) hide show
  1. package/0 +0 -0
  2. package/debug/{konva → konva-master}/CHANGELOG.md +2 -1
  3. package/debug/{konva → konva-master}/README.md +7 -3
  4. package/debug/{konva → konva-master}/package.json +1 -1
  5. package/debug/{konva → konva-master}/release.sh +1 -4
  6. package/debug/{konva → konva-master}/src/Canvas.ts +37 -0
  7. package/debug/{konva → konva-master}/src/shapes/Text.ts +2 -2
  8. package/dist/index.js +2198 -272
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.min.js +1 -1
  11. package/dist/index.min.js.map +1 -1
  12. package/dist/index.min.mjs +1 -1
  13. package/dist/index.min.mjs.map +1 -1
  14. package/dist/index.mjs +2198 -272
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/index.node.cjs +2198 -272
  17. package/dist/index.node.cjs.map +1 -1
  18. package/dist/index.node.mjs +2198 -272
  19. package/dist/index.node.mjs.map +1 -1
  20. package/dist/package.json.min.mjs +1 -1
  21. package/dist/package.json.mjs +1 -1
  22. package/dist/src/shapes/Line.d.ts +33 -86
  23. package/dist/src/shapes/Line.d.ts.map +1 -1
  24. package/dist/src/shapes/Line.min.mjs +1 -1
  25. package/dist/src/shapes/Line.min.mjs.map +1 -1
  26. package/dist/src/shapes/Line.mjs +405 -159
  27. package/dist/src/shapes/Line.mjs.map +1 -1
  28. package/dist/src/shapes/Polyline.d.ts +7 -0
  29. package/dist/src/shapes/Polyline.d.ts.map +1 -1
  30. package/dist/src/shapes/Polyline.min.mjs +1 -1
  31. package/dist/src/shapes/Polyline.min.mjs.map +1 -1
  32. package/dist/src/shapes/Polyline.mjs +48 -16
  33. package/dist/src/shapes/Polyline.mjs.map +1 -1
  34. package/dist/src/shapes/Text/Text.d.ts +19 -0
  35. package/dist/src/shapes/Text/Text.d.ts.map +1 -1
  36. package/dist/src/shapes/Text/Text.min.mjs +1 -1
  37. package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
  38. package/dist/src/shapes/Text/Text.mjs +302 -16
  39. package/dist/src/shapes/Text/Text.mjs.map +1 -1
  40. package/dist/src/shapes/Textbox.d.ts +56 -1
  41. package/dist/src/shapes/Textbox.d.ts.map +1 -1
  42. package/dist/src/shapes/Textbox.min.mjs +1 -1
  43. package/dist/src/shapes/Textbox.min.mjs.map +1 -1
  44. package/dist/src/shapes/Textbox.mjs +633 -11
  45. package/dist/src/shapes/Textbox.mjs.map +1 -1
  46. package/dist/src/shapes/Triangle.d.ts +27 -2
  47. package/dist/src/shapes/Triangle.d.ts.map +1 -1
  48. package/dist/src/shapes/Triangle.min.mjs +1 -1
  49. package/dist/src/shapes/Triangle.min.mjs.map +1 -1
  50. package/dist/src/shapes/Triangle.mjs +72 -12
  51. package/dist/src/shapes/Triangle.mjs.map +1 -1
  52. package/dist/src/text/examples/arabicTextExample.d.ts +60 -0
  53. package/dist/src/text/examples/arabicTextExample.d.ts.map +1 -0
  54. package/dist/src/text/measure.d.ts +9 -0
  55. package/dist/src/text/measure.d.ts.map +1 -1
  56. package/dist/src/text/measure.min.mjs +1 -1
  57. package/dist/src/text/measure.min.mjs.map +1 -1
  58. package/dist/src/text/measure.mjs +175 -4
  59. package/dist/src/text/measure.mjs.map +1 -1
  60. package/dist/src/text/overlayEditor.d.ts +8 -0
  61. package/dist/src/text/overlayEditor.d.ts.map +1 -1
  62. package/dist/src/text/overlayEditor.min.mjs +1 -1
  63. package/dist/src/text/overlayEditor.min.mjs.map +1 -1
  64. package/dist/src/text/overlayEditor.mjs +395 -56
  65. package/dist/src/text/overlayEditor.mjs.map +1 -1
  66. package/dist/src/text/scriptUtils.d.ts +142 -0
  67. package/dist/src/text/scriptUtils.d.ts.map +1 -0
  68. package/dist/src/text/scriptUtils.min.mjs +2 -0
  69. package/dist/src/text/scriptUtils.min.mjs.map +1 -0
  70. package/dist/src/text/scriptUtils.mjs +212 -0
  71. package/dist/src/text/scriptUtils.mjs.map +1 -0
  72. package/dist/src/util/misc/cornerRadius.d.ts +70 -0
  73. package/dist/src/util/misc/cornerRadius.d.ts.map +1 -0
  74. package/dist/src/util/misc/cornerRadius.min.mjs +2 -0
  75. package/dist/src/util/misc/cornerRadius.min.mjs.map +1 -0
  76. package/dist/src/util/misc/cornerRadius.mjs +181 -0
  77. package/dist/src/util/misc/cornerRadius.mjs.map +1 -0
  78. package/dist-extensions/src/shapes/CustomLine.d.ts +10 -0
  79. package/dist-extensions/src/shapes/CustomLine.d.ts.map +1 -0
  80. package/dist-extensions/src/shapes/Line.d.ts +33 -86
  81. package/dist-extensions/src/shapes/Line.d.ts.map +1 -1
  82. package/dist-extensions/src/shapes/Polyline.d.ts +7 -0
  83. package/dist-extensions/src/shapes/Polyline.d.ts.map +1 -1
  84. package/dist-extensions/src/shapes/Text/Text.d.ts +19 -0
  85. package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
  86. package/dist-extensions/src/shapes/Textbox.d.ts +56 -1
  87. package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
  88. package/dist-extensions/src/shapes/Triangle.d.ts +27 -2
  89. package/dist-extensions/src/shapes/Triangle.d.ts.map +1 -1
  90. package/dist-extensions/src/text/measure.d.ts +9 -0
  91. package/dist-extensions/src/text/measure.d.ts.map +1 -1
  92. package/dist-extensions/src/text/overlayEditor.d.ts +8 -0
  93. package/dist-extensions/src/text/overlayEditor.d.ts.map +1 -1
  94. package/dist-extensions/src/text/scriptUtils.d.ts +142 -0
  95. package/dist-extensions/src/text/scriptUtils.d.ts.map +1 -0
  96. package/dist-extensions/src/util/misc/cornerRadius.d.ts +70 -0
  97. package/dist-extensions/src/util/misc/cornerRadius.d.ts.map +1 -0
  98. package/fabric-test-editor.html +3552 -0
  99. package/fabric-test2.html +647 -0
  100. package/fabric.ts +182 -182
  101. package/fonts/STV Bold.ttf +0 -0
  102. package/fonts/STV Light.ttf +0 -0
  103. package/fonts/STV Regular.ttf +0 -0
  104. package/package.json +164 -164
  105. package/src/shapes/Line.ts +484 -157
  106. package/src/shapes/Polyline.ts +70 -29
  107. package/src/shapes/Text/Text.ts +317 -19
  108. package/src/shapes/Textbox.ts +663 -12
  109. package/src/shapes/Triangle.spec.ts +76 -0
  110. package/src/shapes/Triangle.ts +85 -15
  111. package/src/text/measure.ts +200 -50
  112. package/src/text/overlayEditor.ts +504 -94
  113. package/src/util/misc/cornerRadius.spec.ts +141 -0
  114. package/src/util/misc/cornerRadius.ts +269 -0
  115. /package/debug/{konva → konva-master}/LICENSE +0 -0
  116. /package/debug/{konva → konva-master}/gulpfile.mjs +0 -0
  117. /package/debug/{konva → konva-master}/resources/doc-includes/ContainerParams.txt +0 -0
  118. /package/debug/{konva → konva-master}/resources/doc-includes/NodeParams.txt +0 -0
  119. /package/debug/{konva → konva-master}/resources/doc-includes/ShapeParams.txt +0 -0
  120. /package/debug/{konva → konva-master}/resources/jsdoc.conf.json +0 -0
  121. /package/debug/{konva → konva-master}/rollup.config.mjs +0 -0
  122. /package/debug/{konva → konva-master}/src/Animation.ts +0 -0
  123. /package/debug/{konva → konva-master}/src/BezierFunctions.ts +0 -0
  124. /package/debug/{konva → konva-master}/src/Container.ts +0 -0
  125. /package/debug/{konva → konva-master}/src/Context.ts +0 -0
  126. /package/debug/{konva → konva-master}/src/Core.ts +0 -0
  127. /package/debug/{konva → konva-master}/src/DragAndDrop.ts +0 -0
  128. /package/debug/{konva → konva-master}/src/Factory.ts +0 -0
  129. /package/debug/{konva → konva-master}/src/FastLayer.ts +0 -0
  130. /package/debug/{konva → konva-master}/src/Global.ts +0 -0
  131. /package/debug/{konva → konva-master}/src/Group.ts +0 -0
  132. /package/debug/{konva → konva-master}/src/Layer.ts +0 -0
  133. /package/debug/{konva → konva-master}/src/Node.ts +0 -0
  134. /package/debug/{konva → konva-master}/src/PointerEvents.ts +0 -0
  135. /package/debug/{konva → konva-master}/src/Shape.ts +0 -0
  136. /package/debug/{konva → konva-master}/src/Stage.ts +0 -0
  137. /package/debug/{konva → konva-master}/src/Tween.ts +0 -0
  138. /package/debug/{konva → konva-master}/src/Util.ts +0 -0
  139. /package/debug/{konva → konva-master}/src/Validators.ts +0 -0
  140. /package/debug/{konva → konva-master}/src/_CoreInternals.ts +0 -0
  141. /package/debug/{konva → konva-master}/src/_FullInternals.ts +0 -0
  142. /package/debug/{konva → konva-master}/src/canvas-backend.ts +0 -0
  143. /package/debug/{konva → konva-master}/src/filters/Blur.ts +0 -0
  144. /package/debug/{konva → konva-master}/src/filters/Brighten.ts +0 -0
  145. /package/debug/{konva → konva-master}/src/filters/Brightness.ts +0 -0
  146. /package/debug/{konva → konva-master}/src/filters/Contrast.ts +0 -0
  147. /package/debug/{konva → konva-master}/src/filters/Emboss.ts +0 -0
  148. /package/debug/{konva → konva-master}/src/filters/Enhance.ts +0 -0
  149. /package/debug/{konva → konva-master}/src/filters/Grayscale.ts +0 -0
  150. /package/debug/{konva → konva-master}/src/filters/HSL.ts +0 -0
  151. /package/debug/{konva → konva-master}/src/filters/HSV.ts +0 -0
  152. /package/debug/{konva → konva-master}/src/filters/Invert.ts +0 -0
  153. /package/debug/{konva → konva-master}/src/filters/Kaleidoscope.ts +0 -0
  154. /package/debug/{konva → konva-master}/src/filters/Mask.ts +0 -0
  155. /package/debug/{konva → konva-master}/src/filters/Noise.ts +0 -0
  156. /package/debug/{konva → konva-master}/src/filters/Pixelate.ts +0 -0
  157. /package/debug/{konva → konva-master}/src/filters/Posterize.ts +0 -0
  158. /package/debug/{konva → konva-master}/src/filters/RGB.ts +0 -0
  159. /package/debug/{konva → konva-master}/src/filters/RGBA.ts +0 -0
  160. /package/debug/{konva → konva-master}/src/filters/Sepia.ts +0 -0
  161. /package/debug/{konva → konva-master}/src/filters/Solarize.ts +0 -0
  162. /package/debug/{konva → konva-master}/src/filters/Threshold.ts +0 -0
  163. /package/debug/{konva → konva-master}/src/index.ts +0 -0
  164. /package/debug/{konva → konva-master}/src/shapes/Arc.ts +0 -0
  165. /package/debug/{konva → konva-master}/src/shapes/Arrow.ts +0 -0
  166. /package/debug/{konva → konva-master}/src/shapes/Circle.ts +0 -0
  167. /package/debug/{konva → konva-master}/src/shapes/Ellipse.ts +0 -0
  168. /package/debug/{konva → konva-master}/src/shapes/Image.ts +0 -0
  169. /package/debug/{konva → konva-master}/src/shapes/Label.ts +0 -0
  170. /package/debug/{konva → konva-master}/src/shapes/Line.ts +0 -0
  171. /package/debug/{konva → konva-master}/src/shapes/Path.ts +0 -0
  172. /package/debug/{konva → konva-master}/src/shapes/Rect.ts +0 -0
  173. /package/debug/{konva → konva-master}/src/shapes/RegularPolygon.ts +0 -0
  174. /package/debug/{konva → konva-master}/src/shapes/Ring.ts +0 -0
  175. /package/debug/{konva → konva-master}/src/shapes/Sprite.ts +0 -0
  176. /package/debug/{konva → konva-master}/src/shapes/Star.ts +0 -0
  177. /package/debug/{konva → konva-master}/src/shapes/TextPath.ts +0 -0
  178. /package/debug/{konva → konva-master}/src/shapes/Transformer.ts +0 -0
  179. /package/debug/{konva → konva-master}/src/shapes/Wedge.ts +0 -0
  180. /package/debug/{konva → konva-master}/src/skia-backend.ts +0 -0
  181. /package/debug/{konva → konva-master}/src/types.ts +0 -0
  182. /package/debug/{konva → konva-master}/tsconfig.json +0 -0
  183. /package/debug/{konva → konva-master}/tsconfig.test.json +0 -0
@@ -79,8 +79,12 @@ class OverlayEditor {
79
79
  this.textarea.style.pointerEvents = 'auto';
80
80
  // Set appropriate unicodeBidi based on content and direction
81
81
  const hasArabicText = /[\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/.test(this.target.text || '');
82
+ const hasLatinText = /[a-zA-Z]/.test(this.target.text || '');
82
83
  const isLTRDirection = this.target.direction === 'ltr';
83
- if (hasArabicText && isLTRDirection) {
84
+ if (hasArabicText && hasLatinText && isLTRDirection) {
85
+ // For mixed Arabic/Latin text in LTR mode, use embed for consistent line wrapping
86
+ this.textarea.style.unicodeBidi = 'embed';
87
+ } else if (hasArabicText && isLTRDirection) {
84
88
  // For Arabic text in LTR mode, use embed to preserve shaping while respecting direction
85
89
  this.textarea.style.unicodeBidi = 'embed';
86
90
  } else {
@@ -169,14 +173,26 @@ class OverlayEditor {
169
173
  parseFloat(this.hostDiv.style.width) / zoom;
170
174
  const currentHeight = parseFloat(this.hostDiv.style.height) / zoom;
171
175
 
172
- // Only update if there's a meaningful change (avoid float precision issues)
176
+ // Always update height for responsive controls (especially important for line deletion)
173
177
  const heightDiff = Math.abs(currentHeight - target.height);
174
- const threshold = 1; // 1px threshold to avoid micro-changes
178
+ const threshold = 0.5; // Lower threshold for better responsiveness to line changes
175
179
 
176
180
  if (heightDiff > threshold) {
181
+ target.height;
177
182
  target.height = currentHeight;
178
183
  target.setCoords(); // Update control positions
184
+
185
+ // Force dirty to ensure proper re-rendering
186
+ target.dirty = true;
179
187
  this.canvas.requestRenderAll(); // Re-render to show updated selection
188
+
189
+ // IMPORTANT: Reposition overlay after height change
190
+ requestAnimationFrame(() => {
191
+ if (!this.isDestroyed) {
192
+ this.applyOverlayStyle();
193
+ console.log('📐 Height changed - rechecking alignment after repositioning:');
194
+ }
195
+ });
180
196
  }
181
197
  }
182
198
 
@@ -204,14 +220,6 @@ class OverlayEditor {
204
220
  target.setCoords();
205
221
  const aCoords = target.aCoords;
206
222
 
207
- // DEBUG: Log dimensions before edit
208
- console.log('BEFORE EDIT:');
209
- console.log(' target.width =', target.width);
210
- console.log(' target.height =', target.height);
211
- console.log(' target.getScaledWidth() =', target.getScaledWidth());
212
- console.log(' target.getScaledHeight() =', target.getScaledHeight());
213
- console.log(' target.padding =', target.padding);
214
-
215
223
  // 2. Get canvas position and scroll offsets (like rtl-test.html)
216
224
  const canvasEl = canvas.upperCanvasEl;
217
225
  const canvasRect = canvasEl.getBoundingClientRect();
@@ -234,14 +242,12 @@ class OverlayEditor {
234
242
  const left = canvasRect.left + scrollX + screenPoint.x;
235
243
  const top = canvasRect.top + scrollY + screenPoint.y;
236
244
 
237
- // 4. Get dimensions with zoom scaling - use target.width for text wrapping, scaled height for container
238
- const width = target.width * (target.scaleX || 1) * zoom; // Account for object scale and viewport zoom
239
- const height = target.height * (target.scaleY || 1) * zoom;
240
- console.log('WIDTH CALCULATION:');
241
- console.log(' target.width =', target.width);
242
- console.log(' scaledWidth =', target.getScaledWidth());
243
- console.log(' zoom =', zoom);
244
- console.log(' final width =', width);
245
+ // 4. Calculate the precise width and height for the container
246
+ // **THE FIX:** Use getBoundingRect() for BOTH width and height.
247
+ // This is the most reliable measure of the object's final rendered dimensions.
248
+ const objectBounds = target.getBoundingRect();
249
+ const width = Math.round(objectBounds.width * zoom);
250
+ const height = Math.round(objectBounds.height * zoom);
245
251
 
246
252
  // 5. Apply styles to host DIV - absolute positioning like rtl-test.html
247
253
  this.hostDiv.style.position = 'absolute';
@@ -265,50 +271,333 @@ class OverlayEditor {
265
271
  const scaleX = target.scaleX || 1;
266
272
  const finalFontSize = baseFontSize * scaleX * zoom;
267
273
  const fabricLineHeight = target.lineHeight || 1.16;
268
- // Apply padding and dimensions to textarea
269
- const textareaWidth = paddingX > 0 ? `calc(100% - ${2 * paddingX}px)` : '100%';
270
- const textareaHeight = paddingY > 0 ? `calc(100% - ${2 * paddingY}px)` : '100%';
271
- this.textarea.style.width = textareaWidth;
272
- this.textarea.style.height = textareaHeight;
274
+ // **THE FIX:** Use 'border-box' so the width property includes padding.
275
+ // This makes alignment much easier and more reliable.
276
+ this.textarea.style.boxSizing = 'border-box';
277
+
278
+ // **THE FIX:** Set the textarea width to be IDENTICAL to the host div's width.
279
+ // The padding will now be correctly contained *inside* this width.
280
+ this.textarea.style.width = `${width}px`;
281
+ this.textarea.style.height = '100%'; // Let hostDiv control height
273
282
  this.textarea.style.padding = `${paddingY}px ${paddingX}px`;
283
+
284
+ // Apply all other font and text styles to match Fabric
285
+ const letterSpacingPx = (target.charSpacing || 0) / 1000 * finalFontSize;
286
+
287
+ // Special handling for text objects loaded from JSON - ensure they're properly initialized
288
+ if (target.dirty !== false && target.initDimensions) {
289
+ console.log('🔧 Ensuring text object is properly initialized before overlay editing');
290
+ // Force re-initialization if the text object seems to be in a dirty state
291
+ target.initDimensions();
292
+ }
274
293
  this.textarea.style.fontSize = `${finalFontSize}px`;
275
- this.textarea.style.lineHeight = String(fabricLineHeight); // Use unit-less multiplier
294
+ this.textarea.style.lineHeight = String(fabricLineHeight);
276
295
  this.textarea.style.fontFamily = target.fontFamily || 'Arial';
277
296
  this.textarea.style.fontWeight = String(target.fontWeight || 'normal');
278
297
  this.textarea.style.fontStyle = target.fontStyle || 'normal';
279
- this.textarea.style.textAlign = target.textAlign || 'left';
298
+ // Handle text alignment and justification
299
+ const textAlign = target.textAlign || 'left';
300
+ let cssTextAlign = textAlign;
301
+
302
+ // Detect text direction from content for proper justify handling
303
+ const autoDetectedDirection = this.firstStrongDir(this.textarea.value || '');
304
+
305
+ // DEBUG: Log alignment details
306
+ console.log('🔍 ALIGNMENT DEBUG:');
307
+ console.log(' Fabric textAlign:', textAlign);
308
+ console.log(' Fabric direction:', target.direction);
309
+ console.log(' Text content:', JSON.stringify(target.text));
310
+ console.log(' Detected direction:', autoDetectedDirection);
311
+
312
+ // Map fabric.js justify to CSS
313
+ if (textAlign.includes('justify')) {
314
+ // Try to match fabric.js justify behavior more precisely
315
+ try {
316
+ // For justify, we need to replicate fabric.js space expansion
317
+ // Use CSS justify but with specific settings to match fabric.js better
318
+ cssTextAlign = 'justify';
319
+
320
+ // Set text-align-last based on justify type and detected direction
321
+ // Smart justify: respect detected direction even when fabric alignment doesn't match
322
+ if (textAlign === 'justify') {
323
+ this.textarea.style.textAlignLast = autoDetectedDirection === 'rtl' ? 'right' : 'left';
324
+ } else if (textAlign === 'justify-left') {
325
+ // If text is RTL but fabric says justify-left, override to justify-right for better UX
326
+ if (autoDetectedDirection === 'rtl') {
327
+ this.textarea.style.textAlignLast = 'right';
328
+ console.log(' → Overrode justify-left to justify-right for RTL text');
329
+ } else {
330
+ this.textarea.style.textAlignLast = 'left';
331
+ }
332
+ } else if (textAlign === 'justify-right') {
333
+ // If text is LTR but fabric says justify-right, override to justify-left for better UX
334
+ if (autoDetectedDirection === 'ltr') {
335
+ this.textarea.style.textAlignLast = 'left';
336
+ console.log(' → Overrode justify-right to justify-left for LTR text');
337
+ } else {
338
+ this.textarea.style.textAlignLast = 'right';
339
+ }
340
+ } else if (textAlign === 'justify-center') {
341
+ this.textarea.style.textAlignLast = 'center';
342
+ }
343
+
344
+ // Enhanced justify settings for better fabric.js matching
345
+ this.textarea.style.textJustify = 'inter-word';
346
+ this.textarea.style.wordSpacing = 'normal';
347
+
348
+ // Additional CSS properties for better justify matching
349
+ this.textarea.style.textAlign = 'justify';
350
+ this.textarea.style.textAlignLast = this.textarea.style.textAlignLast;
351
+
352
+ // Try to force better justify behavior
353
+ this.textarea.style.textJustifyTrim = 'none';
354
+ this.textarea.style.textAutospace = 'none';
355
+ console.log(' → Applied justify alignment:', textAlign, 'with last-line:', this.textarea.style.textAlignLast);
356
+ } catch (error) {
357
+ console.warn(' → Justify setup failed, falling back to standard alignment:', error);
358
+ cssTextAlign = textAlign.replace('justify-', '').replace('justify', 'left');
359
+ }
360
+ } else {
361
+ this.textarea.style.textAlignLast = 'auto';
362
+ this.textarea.style.textJustify = 'auto';
363
+ this.textarea.style.wordSpacing = 'normal';
364
+ console.log(' → Applied standard alignment:', cssTextAlign);
365
+ }
366
+ this.textarea.style.textAlign = cssTextAlign;
280
367
  this.textarea.style.color = ((_target$fill = target.fill) === null || _target$fill === void 0 ? void 0 : _target$fill.toString()) || '#000';
281
- this.textarea.style.letterSpacing = `${(target.charSpacing || 0) / 1000}em`;
282
- this.textarea.style.direction = target.direction || this.firstStrongDir(this.textarea.value || '');
368
+ this.textarea.style.letterSpacing = `${letterSpacingPx}px`;
369
+
370
+ // Use the already detected direction from above
371
+ const fabricDirection = target.direction;
283
372
 
284
- // Ensure consistent font rendering between Fabric and CSS
373
+ // Use auto-detected direction for better BiDi support, but respect fabric direction if it makes sense
374
+ this.textarea.style.direction = autoDetectedDirection || fabricDirection || 'ltr';
285
375
  this.textarea.style.fontVariant = 'normal';
286
376
  this.textarea.style.fontStretch = 'normal';
287
- this.textarea.style.textRendering = 'auto';
288
- this.textarea.style.fontKerning = 'auto';
289
- this.textarea.style.boxSizing = 'content-box'; // Padding is added outside width/height
377
+ this.textarea.style.textRendering = 'auto'; // Changed from 'optimizeLegibility' to match canvas
378
+ this.textarea.style.fontKerning = 'normal';
379
+ this.textarea.style.fontFeatureSettings = 'normal';
380
+ this.textarea.style.fontVariationSettings = 'normal';
290
381
  this.textarea.style.margin = '0';
291
382
  this.textarea.style.border = 'none';
292
383
  this.textarea.style.outline = 'none';
293
384
  this.textarea.style.background = 'transparent';
294
- this.textarea.style.wordWrap = 'break-word';
385
+ this.textarea.style.overflowWrap = 'break-word';
295
386
  this.textarea.style.whiteSpace = 'pre-wrap';
387
+ this.textarea.style.hyphens = 'none';
388
+
389
+ // DEBUG: Log final CSS properties
390
+ console.log('🎨 FINAL TEXTAREA CSS:');
391
+ console.log(' textAlign:', this.textarea.style.textAlign);
392
+ console.log(' textAlignLast:', this.textarea.style.textAlignLast);
393
+ console.log(' direction:', this.textarea.style.direction);
394
+ console.log(' unicodeBidi:', this.textarea.style.unicodeBidi);
395
+ console.log(' width:', this.textarea.style.width);
396
+ console.log(' textJustify:', this.textarea.style.textJustify);
397
+ console.log(' wordSpacing:', this.textarea.style.wordSpacing);
398
+ console.log(' whiteSpace:', this.textarea.style.whiteSpace);
399
+
400
+ // If justify, log Fabric object dimensions for comparison
401
+ if (textAlign.includes('justify')) {
402
+ var _calcTextWidth, _ref;
403
+ console.log('🔧 FABRIC OBJECT JUSTIFY INFO:');
404
+ console.log(' Fabric width:', target.width);
405
+ console.log(' Fabric calcTextWidth:', (_calcTextWidth = (_ref = target).calcTextWidth) === null || _calcTextWidth === void 0 ? void 0 : _calcTextWidth.call(_ref));
406
+ console.log(' Fabric textAlign:', target.textAlign);
407
+ console.log(' Text lines:', target.textLines);
408
+ }
296
409
 
297
- // DEBUG: Log final textarea dimensions
298
- console.log('TEXTAREA AFTER SETUP:');
299
- console.log(' textarea width =', this.textarea.style.width);
300
- console.log(' textarea height =', this.textarea.style.height);
301
- console.log(' textarea padding =', this.textarea.style.padding);
302
- console.log(' paddingX =', paddingX, 'paddingY =', paddingY);
303
- console.log(' baseFontSize =', baseFontSize);
304
- console.log(' scaleX =', scaleX);
305
- console.log(' zoom =', zoom);
306
- console.log(' finalFontSize =', finalFontSize);
307
- console.log(' fabricLineHeight =', fabricLineHeight);
410
+ // Debug font properties matching
411
+ console.log('🔤 FONT PROPERTIES COMPARISON:');
412
+ console.log(' Fabric fontFamily:', target.fontFamily);
413
+ console.log(' Fabric fontWeight:', target.fontWeight);
414
+ console.log(' Fabric fontStyle:', target.fontStyle);
415
+ console.log(' Fabric fontSize:', target.fontSize);
416
+ console.log(' Textarea fontFamily:', this.textarea.style.fontFamily);
417
+ console.log(' Textarea fontWeight:', this.textarea.style.fontWeight);
418
+ console.log(' Textarea fontStyle:', this.textarea.style.fontStyle);
419
+ console.log(' Textarea fontSize:', this.textarea.style.fontSize);
420
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
421
+
422
+ // Enhanced font rendering to better match fabric.js canvas rendering
423
+ // Default to auto for more natural rendering
424
+ this.textarea.style.webkitFontSmoothing = 'auto';
425
+ this.textarea.style.mozOsxFontSmoothing = 'auto';
426
+ this.textarea.style.fontSmooth = 'auto';
427
+ this.textarea.style.textSizeAdjust = 'none';
428
+
429
+ // For bold fonts, use subpixel rendering to match canvas thickness better
430
+ const fontWeight = String(target.fontWeight || 'normal');
431
+ const isBold = fontWeight === 'bold' || fontWeight === '700' || parseInt(fontWeight) >= 600;
432
+ if (isBold) {
433
+ this.textarea.style.webkitFontSmoothing = 'subpixel-antialiased';
434
+ this.textarea.style.mozOsxFontSmoothing = 'unset';
435
+ console.log('🔤 Applied enhanced bold rendering for better thickness matching');
436
+ }
437
+ console.log('🎨 FONT SMOOTHING APPLIED:');
438
+ console.log(' webkitFontSmoothing:', this.textarea.style.webkitFontSmoothing);
439
+ console.log(' mozOsxFontSmoothing:', this.textarea.style.mozOsxFontSmoothing);
308
440
 
309
441
  // Initial bounds are set correctly by Fabric.js - don't force update here
310
442
  }
311
443
 
444
+ /**
445
+ * Debug method to compare textarea and canvas object bounding boxes
446
+ */
447
+ debugBoundingBoxComparison() {
448
+ const target = this.target;
449
+ const canvas = this.canvas;
450
+ const zoom = canvas.getZoom();
451
+
452
+ // Get textarea bounding box (in screen coordinates)
453
+ const textareaRect = this.textarea.getBoundingClientRect();
454
+ const hostRect = this.hostDiv.getBoundingClientRect();
455
+
456
+ // Get canvas object bounding box (in screen coordinates)
457
+ const canvasBounds = target.getBoundingRect();
458
+ const canvasRect = canvas.upperCanvasEl.getBoundingClientRect();
459
+
460
+ // Convert canvas object bounds to screen coordinates
461
+ const vpt = canvas.viewportTransform;
462
+ const screenObjectBounds = {
463
+ left: canvasRect.left + canvasBounds.left * zoom + vpt[4],
464
+ top: canvasRect.top + canvasBounds.top * zoom + vpt[5],
465
+ width: canvasBounds.width * zoom,
466
+ height: canvasBounds.height * zoom
467
+ };
468
+ console.log('🔍 BOUNDING BOX COMPARISON:');
469
+ console.log('📦 Textarea Rect:', {
470
+ left: Math.round(textareaRect.left * 100) / 100,
471
+ top: Math.round(textareaRect.top * 100) / 100,
472
+ width: Math.round(textareaRect.width * 100) / 100,
473
+ height: Math.round(textareaRect.height * 100) / 100
474
+ });
475
+ console.log('📦 Host Div Rect:', {
476
+ left: Math.round(hostRect.left * 100) / 100,
477
+ top: Math.round(hostRect.top * 100) / 100,
478
+ width: Math.round(hostRect.width * 100) / 100,
479
+ height: Math.round(hostRect.height * 100) / 100
480
+ });
481
+ console.log('📦 Canvas Object Bounds (screen):', {
482
+ left: Math.round(screenObjectBounds.left * 100) / 100,
483
+ top: Math.round(screenObjectBounds.top * 100) / 100,
484
+ width: Math.round(screenObjectBounds.width * 100) / 100,
485
+ height: Math.round(screenObjectBounds.height * 100) / 100
486
+ });
487
+ console.log('📦 Canvas Object Bounds (canvas):', canvasBounds);
488
+
489
+ // Calculate differences
490
+ const hostVsObject = {
491
+ leftDiff: Math.round((hostRect.left - screenObjectBounds.left) * 100) / 100,
492
+ topDiff: Math.round((hostRect.top - screenObjectBounds.top) * 100) / 100,
493
+ widthDiff: Math.round((hostRect.width - screenObjectBounds.width) * 100) / 100,
494
+ heightDiff: Math.round((hostRect.height - screenObjectBounds.height) * 100) / 100
495
+ };
496
+ const textareaVsObject = {
497
+ leftDiff: Math.round((textareaRect.left - screenObjectBounds.left) * 100) / 100,
498
+ topDiff: Math.round((textareaRect.top - screenObjectBounds.top) * 100) / 100,
499
+ widthDiff: Math.round((textareaRect.width - screenObjectBounds.width) * 100) / 100,
500
+ heightDiff: Math.round((textareaRect.height - screenObjectBounds.height) * 100) / 100
501
+ };
502
+ console.log('📏 Host Div vs Canvas Object Diff:', hostVsObject);
503
+ console.log('📏 Textarea vs Canvas Object Diff:', textareaVsObject);
504
+
505
+ // Check if they're aligned (within 2px tolerance)
506
+ const tolerance = 2;
507
+ const hostAligned = Math.abs(hostVsObject.leftDiff) < tolerance && Math.abs(hostVsObject.topDiff) < tolerance && Math.abs(hostVsObject.widthDiff) < tolerance && Math.abs(hostVsObject.heightDiff) < tolerance;
508
+ const textareaAligned = Math.abs(textareaVsObject.leftDiff) < tolerance && Math.abs(textareaVsObject.topDiff) < tolerance && Math.abs(textareaVsObject.widthDiff) < tolerance && Math.abs(textareaVsObject.heightDiff) < tolerance;
509
+ console.log(hostAligned ? '✅ Host Div ALIGNED with canvas object' : '❌ Host Div MISALIGNED with canvas object');
510
+ console.log(textareaAligned ? '✅ Textarea ALIGNED with canvas object' : '❌ Textarea MISALIGNED with canvas object');
511
+ console.log('🔍 Zoom:', zoom, 'Viewport Transform:', vpt);
512
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
513
+ }
514
+
515
+ /**
516
+ * Debug method to compare text wrapping between textarea and Fabric text object
517
+ */
518
+ debugTextWrapping() {
519
+ const target = this.target;
520
+ const text = this.textarea.value;
521
+ console.log('📝 TEXT WRAPPING COMPARISON:');
522
+ console.log('📄 Text Content:', `"${text}"`);
523
+ console.log('📄 Text Length:', text.length);
524
+
525
+ // Analyze line breaks
526
+ const explicitLines = text.split('\n');
527
+ console.log('📄 Explicit Lines (\\n):', explicitLines.length);
528
+ explicitLines.forEach((line, i) => {
529
+ console.log(` Line ${i + 1}: "${line}" (${line.length} chars)`);
530
+ });
531
+
532
+ // Get textarea computed styles for wrapping analysis
533
+ const textareaStyles = window.getComputedStyle(this.textarea);
534
+ console.log('📐 Textarea Wrapping Styles:');
535
+ console.log(' width:', textareaStyles.width);
536
+ console.log(' fontSize:', textareaStyles.fontSize);
537
+ console.log(' fontFamily:', textareaStyles.fontFamily);
538
+ console.log(' fontWeight:', textareaStyles.fontWeight);
539
+ console.log(' letterSpacing:', textareaStyles.letterSpacing);
540
+ console.log(' lineHeight:', textareaStyles.lineHeight);
541
+ console.log(' whiteSpace:', textareaStyles.whiteSpace);
542
+ console.log(' wordWrap:', textareaStyles.wordWrap);
543
+ console.log(' overflowWrap:', textareaStyles.overflowWrap);
544
+ console.log(' direction:', textareaStyles.direction);
545
+ console.log(' textAlign:', textareaStyles.textAlign);
546
+
547
+ // Get Fabric text object properties for comparison
548
+ console.log('📐 Fabric Text Object Properties:');
549
+ console.log(' width:', target.width);
550
+ console.log(' fontSize:', target.fontSize);
551
+ console.log(' fontFamily:', target.fontFamily);
552
+ console.log(' fontWeight:', target.fontWeight);
553
+ console.log(' charSpacing:', target.charSpacing);
554
+ console.log(' lineHeight:', target.lineHeight);
555
+ console.log(' direction:', target.direction);
556
+ console.log(' textAlign:', target.textAlign);
557
+ console.log(' scaleX:', target.scaleX);
558
+ console.log(' scaleY:', target.scaleY);
559
+
560
+ // Calculate effective dimensions for comparison - use actual rendered width
561
+ // **THE FIX:** Use getBoundingRect to get the *actual rendered width* of the Fabric object.
562
+ const fabricEffectiveWidth = this.target.getBoundingRect().width;
563
+ // Use the exact width set on textarea for comparison
564
+ const textareaComputedWidth = parseFloat(window.getComputedStyle(this.textarea).width);
565
+ const textareaEffectiveWidth = textareaComputedWidth / this.canvas.getZoom();
566
+ const widthDiff = Math.abs(textareaEffectiveWidth - fabricEffectiveWidth);
567
+ console.log('📏 Effective Width Comparison:');
568
+ console.log(' Textarea Effective Width:', textareaEffectiveWidth);
569
+ console.log(' Fabric Effective Width:', fabricEffectiveWidth);
570
+ console.log(' Width Difference:', widthDiff.toFixed(2) + 'px');
571
+ console.log(widthDiff < 1 ? '✅ Widths MATCH for wrapping' : '❌ Width MISMATCH may cause different wrapping');
572
+
573
+ // Check text direction and bidi handling
574
+ const hasRTLText = /[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/.test(text);
575
+ const hasBidiText = /[\u0590-\u06FF]/.test(text) && /[a-zA-Z]/.test(text);
576
+ console.log('🌍 Text Direction Analysis:');
577
+ console.log(' Has RTL characters:', hasRTLText);
578
+ console.log(' Has mixed Bidi text:', hasBidiText);
579
+ console.log(' Textarea direction:', textareaStyles.direction);
580
+ console.log(' Fabric direction:', target.direction || 'auto');
581
+ console.log(' Textarea unicodeBidi:', textareaStyles.unicodeBidi);
582
+
583
+ // Measure actual rendered line count
584
+ const textareaScrollHeight = this.textarea.scrollHeight;
585
+ const textareaLineHeight = parseFloat(textareaStyles.lineHeight) || parseFloat(textareaStyles.fontSize) * 1.2;
586
+ const estimatedTextareaLines = Math.round(textareaScrollHeight / textareaLineHeight);
587
+ console.log('📊 Line Count Analysis:');
588
+ console.log(' Textarea scrollHeight:', textareaScrollHeight);
589
+ console.log(' Textarea lineHeight:', textareaLineHeight);
590
+ console.log(' Estimated rendered lines:', estimatedTextareaLines);
591
+ console.log(' Explicit line breaks:', explicitLines.length);
592
+ if (estimatedTextareaLines > explicitLines.length) {
593
+ console.log('🔄 Text wrapping detected in textarea');
594
+ console.log(' Wrapped lines:', estimatedTextareaLines - explicitLines.length);
595
+ } else {
596
+ console.log('📏 No text wrapping in textarea');
597
+ }
598
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
599
+ }
600
+
312
601
  /**
313
602
  * Focus the textarea and position cursor at end
314
603
  */
@@ -337,6 +626,11 @@ class OverlayEditor {
337
626
  this.canvas.requestRenderAll();
338
627
  this.target.setCoords();
339
628
  this.applyOverlayStyle();
629
+
630
+ // Fix character mapping issues after JSON loading for browser-wrapped fonts
631
+ if (this.target._fixCharacterMappingAfterJsonLoad) {
632
+ this.target._fixCharacterMappingAfterJsonLoad();
633
+ }
340
634
  this.textarea.focus();
341
635
  this.textarea.setSelectionRange(this.textarea.value.length, this.textarea.value.length);
342
636
 
@@ -382,6 +676,23 @@ class OverlayEditor {
382
676
  // Handle commit/cancel after restoring visibility
383
677
  if (commit && !this.isComposing) {
384
678
  const finalText = this.textarea.value;
679
+
680
+ // Auto-detect text direction and update fabric object if needed
681
+ const detectedDirection = this.firstStrongDir(finalText);
682
+ const currentDirection = this.target.direction || 'ltr';
683
+ if (detectedDirection && detectedDirection !== currentDirection) {
684
+ console.log(`🔄 Overlay Exit: Auto-detected direction change from "${currentDirection}" to "${detectedDirection}"`);
685
+ console.log(` Text content: "${finalText.substring(0, 50)}..."`);
686
+
687
+ // Update the fabric object's direction
688
+ this.target.set('direction', detectedDirection);
689
+
690
+ // Force a re-render to apply the direction change
691
+ this.canvas.requestRenderAll();
692
+ console.log(`✅ Fabric object direction updated to: ${detectedDirection}`);
693
+ } else {
694
+ console.log(`📝 Overlay Exit: Direction unchanged (${currentDirection}), text: "${finalText.substring(0, 30)}..."`);
695
+ }
385
696
  if (this.onCommit) {
386
697
  this.onCommit(finalText);
387
698
  }
@@ -421,25 +732,40 @@ class OverlayEditor {
421
732
  }
422
733
  }
423
734
  autoResizeTextarea() {
424
- // Allow both vertical growth and shrinking; host width stays fixed
425
- const oldHeight = parseFloat(window.getComputedStyle(this.textarea).height);
426
-
427
- // Reset height to measure actual needed height
428
- this.textarea.style.height = 'auto';
735
+ // Store the scroll position and the container's old height for comparison.
736
+ const scrollTop = this.textarea.scrollTop;
737
+ const oldHeight = parseFloat(this.hostDiv.style.height || '0');
738
+
739
+ // 1. **Force a reliable reflow.**
740
+ // First, reset the textarea's height to a minimal value. This is the crucial step
741
+ // that forces the browser to recalculate the content's height from scratch,
742
+ // ignoring the hostDiv's larger, stale height.
743
+ this.textarea.style.height = '1px';
744
+
745
+ // 2. Read the now-accurate scrollHeight. This value reflects the minimum
746
+ // height required for the content, whether it's single or multi-line.
429
747
  const scrollHeight = this.textarea.scrollHeight;
430
748
 
431
- // Add extra padding to prevent text clipping (especially for line height)
432
- const lineHeightBuffer = 8; // Extra space to prevent clipping
433
- const newHeight = Math.max(scrollHeight + lineHeightBuffer, 25); // Minimum height with buffer
434
- const heightChanged = Math.abs(newHeight - oldHeight) > 2; // Only if meaningful change
749
+ // A small buffer for rendering consistency across browsers.
750
+ const buffer = 2;
751
+ const newHeight = scrollHeight + buffer;
435
752
 
436
- this.textarea.style.height = `${newHeight}px`;
437
- this.hostDiv.style.height = `${newHeight}px`; // Match exactly
753
+ // Check if the height has changed significantly.
754
+ const heightChanged = Math.abs(newHeight - oldHeight) > 1;
438
755
 
439
- // Only update object bounds if height actually changed
756
+ // 4. Only update heights and object bounds if there was a change.
440
757
  if (heightChanged) {
758
+ this.textarea.style.height = `${newHeight}px`;
759
+ this.hostDiv.style.height = `${newHeight}px`;
441
760
  this.updateObjectBounds();
761
+ } else {
762
+ // If no significant change, ensure the textarea's height matches the container
763
+ // to prevent any minor visual misalignment.
764
+ this.textarea.style.height = this.hostDiv.style.height;
442
765
  }
766
+
767
+ // 5. Restore the original scroll position.
768
+ this.textarea.scrollTop = scrollTop;
443
769
  }
444
770
  handleKeyDown(e) {
445
771
  if (e.key === 'Escape') {
@@ -448,6 +774,19 @@ class OverlayEditor {
448
774
  } else if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
449
775
  e.preventDefault();
450
776
  this.destroy(true); // Commit
777
+ } else if (e.key === 'Enter' || e.key === 'Backspace' || e.key === 'Delete') {
778
+ // For keys that might change the height, schedule a resize check
779
+ // Use both immediate and delayed checks to catch all scenarios
780
+ requestAnimationFrame(() => {
781
+ if (!this.isDestroyed) {
782
+ this.autoResizeTextarea();
783
+ }
784
+ });
785
+ setTimeout(() => {
786
+ if (!this.isDestroyed) {
787
+ this.autoResizeTextarea();
788
+ }
789
+ }, 10); // Small delay to ensure DOM is updated
451
790
  }
452
791
  }
453
792
  handleFocus() {