@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.
Files changed (95) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/dist/index.js +1982 -649
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.min.js +1 -1
  5. package/dist/index.min.js.map +1 -1
  6. package/dist/index.min.mjs +1 -1
  7. package/dist/index.min.mjs.map +1 -1
  8. package/dist/index.mjs +1982 -649
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/index.node.cjs +1982 -649
  11. package/dist/index.node.cjs.map +1 -1
  12. package/dist/index.node.mjs +1982 -649
  13. package/dist/index.node.mjs.map +1 -1
  14. package/dist/package.json.min.mjs +1 -1
  15. package/dist/package.json.mjs +1 -1
  16. package/dist/src/shapes/IText/IText.d.ts +31 -6
  17. package/dist/src/shapes/IText/IText.d.ts.map +1 -1
  18. package/dist/src/shapes/IText/IText.min.mjs +1 -1
  19. package/dist/src/shapes/IText/IText.min.mjs.map +1 -1
  20. package/dist/src/shapes/IText/IText.mjs +495 -126
  21. package/dist/src/shapes/IText/IText.mjs.map +1 -1
  22. package/dist/src/shapes/IText/ITextBehavior.d.ts +12 -0
  23. package/dist/src/shapes/IText/ITextBehavior.d.ts.map +1 -1
  24. package/dist/src/shapes/IText/ITextBehavior.min.mjs +1 -1
  25. package/dist/src/shapes/IText/ITextBehavior.min.mjs.map +1 -1
  26. package/dist/src/shapes/IText/ITextBehavior.mjs +127 -36
  27. package/dist/src/shapes/IText/ITextBehavior.mjs.map +1 -1
  28. package/dist/src/shapes/IText/ITextClickBehavior.d.ts.map +1 -1
  29. package/dist/src/shapes/IText/ITextClickBehavior.min.mjs +1 -1
  30. package/dist/src/shapes/IText/ITextClickBehavior.min.mjs.map +1 -1
  31. package/dist/src/shapes/IText/ITextClickBehavior.mjs +21 -4
  32. package/dist/src/shapes/IText/ITextClickBehavior.mjs.map +1 -1
  33. package/dist/src/shapes/IText/ITextKeyBehavior.min.mjs +1 -1
  34. package/dist/src/shapes/IText/ITextKeyBehavior.min.mjs.map +1 -1
  35. package/dist/src/shapes/IText/ITextKeyBehavior.mjs +17 -21
  36. package/dist/src/shapes/IText/ITextKeyBehavior.mjs.map +1 -1
  37. package/dist/src/shapes/Text/Text.d.ts +69 -1
  38. package/dist/src/shapes/Text/Text.d.ts.map +1 -1
  39. package/dist/src/shapes/Text/Text.min.mjs +1 -1
  40. package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
  41. package/dist/src/shapes/Text/Text.mjs +374 -60
  42. package/dist/src/shapes/Text/Text.mjs.map +1 -1
  43. package/dist/src/shapes/Text/constants.d.ts.map +1 -1
  44. package/dist/src/shapes/Text/constants.min.mjs +1 -1
  45. package/dist/src/shapes/Text/constants.min.mjs.map +1 -1
  46. package/dist/src/shapes/Text/constants.mjs +2 -1
  47. package/dist/src/shapes/Text/constants.mjs.map +1 -1
  48. package/dist/src/shapes/Textbox.d.ts +8 -1
  49. package/dist/src/shapes/Textbox.d.ts.map +1 -1
  50. package/dist/src/shapes/Textbox.min.mjs +1 -1
  51. package/dist/src/shapes/Textbox.min.mjs.map +1 -1
  52. package/dist/src/shapes/Textbox.mjs +406 -63
  53. package/dist/src/shapes/Textbox.mjs.map +1 -1
  54. package/dist/src/text/hitTest.min.mjs +1 -1
  55. package/dist/src/text/hitTest.min.mjs.map +1 -1
  56. package/dist/src/text/hitTest.mjs +1 -198
  57. package/dist/src/text/hitTest.mjs.map +1 -1
  58. package/dist/src/text/layout.min.mjs +1 -1
  59. package/dist/src/text/layout.min.mjs.map +1 -1
  60. package/dist/src/text/layout.mjs +122 -5
  61. package/dist/src/text/layout.mjs.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 +132 -142
  65. package/dist/src/text/overlayEditor.mjs.map +1 -1
  66. package/dist/src/text/unicode.d.ts +28 -0
  67. package/dist/src/text/unicode.d.ts.map +1 -1
  68. package/dist/src/text/unicode.min.mjs +1 -1
  69. package/dist/src/text/unicode.min.mjs.map +1 -1
  70. package/dist/src/text/unicode.mjs +294 -1
  71. package/dist/src/text/unicode.mjs.map +1 -1
  72. package/dist-extensions/src/shapes/IText/IText.d.ts +31 -6
  73. package/dist-extensions/src/shapes/IText/IText.d.ts.map +1 -1
  74. package/dist-extensions/src/shapes/IText/ITextBehavior.d.ts +12 -0
  75. package/dist-extensions/src/shapes/IText/ITextBehavior.d.ts.map +1 -1
  76. package/dist-extensions/src/shapes/IText/ITextClickBehavior.d.ts.map +1 -1
  77. package/dist-extensions/src/shapes/Text/Text.d.ts +69 -1
  78. package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
  79. package/dist-extensions/src/shapes/Text/constants.d.ts.map +1 -1
  80. package/dist-extensions/src/shapes/Textbox.d.ts +8 -1
  81. package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
  82. package/dist-extensions/src/text/unicode.d.ts +28 -0
  83. package/dist-extensions/src/text/unicode.d.ts.map +1 -1
  84. package/package.json +164 -164
  85. package/rtl-debug.html +358 -200
  86. package/src/shapes/IText/IText.ts +524 -110
  87. package/src/shapes/IText/ITextBehavior.ts +174 -80
  88. package/src/shapes/IText/ITextClickBehavior.ts +20 -6
  89. package/src/shapes/IText/ITextKeyBehavior.ts +15 -15
  90. package/src/shapes/Text/Text.ts +488 -107
  91. package/src/shapes/Text/constants.ts +4 -2
  92. package/src/shapes/Textbox.ts +414 -65
  93. package/src/text/layout.ts +150 -23
  94. package/src/text/overlayEditor.ts +148 -148
  95. package/src/text/unicode.ts +177 -2
@@ -27,7 +27,11 @@ import { extractLinesFromDOM, storeBrowserLines } from '../../text/browserLines'
27
27
  * - `\-` Matches a "-" character (char code 45).
28
28
  */
29
29
  // eslint-disable-next-line no-useless-escape
30
- const reNonWord = /[ \n\.,;!\?\-]/;
30
+ // Word boundary characters for Latin, Arabic, and Hebrew
31
+ // Latin: space, newline, punctuation
32
+ // Arabic: ، (comma U+060C), ؛ (semicolon U+061B), ؟ (question U+061F), ۔ (full stop U+06D4), ـ (tatweel U+0640)
33
+ // Hebrew: ׃ (sof pasuq U+05C3), ״ (gershayim U+05F4)
34
+ const reNonWord = /[ \n\.,;!\?\-\u060C\u061B\u061F\u06D4\u0640\u05C3\u05F4\u2000-\u206F]/;
31
35
 
32
36
  export type ITextEvents = ObjectEvents & {
33
37
  'selection:changed': never;
@@ -325,12 +329,102 @@ export abstract class ITextBehavior<
325
329
 
326
330
  /**
327
331
  * Finds index corresponding to beginning or end of a word
332
+ * Uses Intl.Segmenter for proper Unicode word segmentation when available,
333
+ * falls back to regex-based detection for older browsers.
328
334
  * @param {Number} selectionStart Index of a character
329
335
  * @param {Number} direction 1 or -1
330
336
  * @return {Number} Index of the beginning or end of a word
331
337
  */
332
338
  searchWordBoundary(selectionStart: number, direction: 1 | -1): number {
333
- const text = this._text;
339
+ // Try to use Intl.Segmenter for proper Unicode word segmentation
340
+ if (typeof Intl !== 'undefined' && (Intl as any).Segmenter) {
341
+ return this._searchWordBoundaryWithSegmenter(selectionStart, direction);
342
+ }
343
+ // Fallback to regex-based detection
344
+ return this._searchWordBoundaryWithRegex(selectionStart, direction);
345
+ }
346
+
347
+ /**
348
+ * Word boundary search using Intl.Segmenter (proper Unicode support)
349
+ * Works on original text (this.text) since selectionStart is in original text space
350
+ */
351
+ private _searchWordBoundaryWithSegmenter(selectionStart: number, direction: 1 | -1): number {
352
+ // Use original text (without kashida) since indices are in original text space
353
+ const originalText = this.text;
354
+ const SegmenterClass = (Intl as any).Segmenter;
355
+ const segmenter = new SegmenterClass(undefined, { granularity: 'word' });
356
+ const segments = Array.from(segmenter.segment(originalText)) as Array<{
357
+ segment: string;
358
+ index: number;
359
+ isWordLike: boolean;
360
+ }>;
361
+
362
+ if (segments.length === 0) {
363
+ return direction === -1 ? 0 : originalText.length;
364
+ }
365
+
366
+ // Find the segment containing the cursor position
367
+ let currentSegmentIdx = 0;
368
+ for (let i = 0; i < segments.length; i++) {
369
+ const seg = segments[i];
370
+ if (selectionStart >= seg.index && selectionStart < seg.index + seg.segment.length) {
371
+ currentSegmentIdx = i;
372
+ break;
373
+ }
374
+ if (selectionStart >= seg.index + seg.segment.length) {
375
+ currentSegmentIdx = i;
376
+ }
377
+ }
378
+
379
+ // Find word boundaries
380
+ if (direction === -1) {
381
+ // Search backwards for word start
382
+ let targetIdx = currentSegmentIdx;
383
+
384
+ // If cursor is at the start of a segment, look at previous segment
385
+ if (selectionStart === segments[targetIdx].index && targetIdx > 0) {
386
+ targetIdx--;
387
+ }
388
+
389
+ // Skip non-word segments
390
+ while (targetIdx > 0 && !segments[targetIdx].isWordLike) {
391
+ targetIdx--;
392
+ }
393
+
394
+ // Return the start of the word segment
395
+ if (segments[targetIdx].isWordLike) {
396
+ return segments[targetIdx].index;
397
+ }
398
+ return 0;
399
+ } else {
400
+ // Search forwards for word end
401
+ let targetIdx = currentSegmentIdx;
402
+
403
+ // If we're in a word, find its end
404
+ if (segments[targetIdx].isWordLike) {
405
+ return segments[targetIdx].index + segments[targetIdx].segment.length;
406
+ }
407
+
408
+ // Skip non-word segments to find next word
409
+ while (targetIdx < segments.length && !segments[targetIdx].isWordLike) {
410
+ targetIdx++;
411
+ }
412
+
413
+ // Return the end of the next word segment
414
+ if (targetIdx < segments.length && segments[targetIdx].isWordLike) {
415
+ return segments[targetIdx].index + segments[targetIdx].segment.length;
416
+ }
417
+ return originalText.length;
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Word boundary search using regex (fallback for older browsers)
423
+ * Works on original text (this.text) since selectionStart is in original text space
424
+ */
425
+ private _searchWordBoundaryWithRegex(selectionStart: number, direction: 1 | -1): number {
426
+ // Use original text as an array of characters
427
+ const text = Array.from(this.text);
334
428
  // if we land on a space we move the cursor backwards
335
429
  // if we are searching boundary end we move the cursor backwards ONLY if we don't land on a line break
336
430
  let index =
@@ -455,84 +549,84 @@ export abstract class ITextBehavior<
455
549
  });
456
550
  }
457
551
 
458
- /**
459
- * Commit overlay editing changes
460
- */
461
- private commitOverlayEdit(text: string) {
462
-
463
- // Preserve geometry to avoid nudge when layout recalculates
464
- const prevLeft = this.left;
465
- const prevTop = this.top;
466
- const prevWidth = this.get('width');
467
- const prevMinWidth = (this as any).dynamicMinWidth;
468
- const prevUsingBrowserWrap = (this as any)._usingBrowserWrapping;
469
- const hadLock = (this as any).lockDynamicMinWidth;
470
- (this as any).lockDynamicMinWidth = true;
471
- const countKashida = (val?: string) => (val ? (val.match(/\u0640/g) || []).length : 0);
472
- console.log('[OverlayCommit] pre-layout', {
473
- textLength: text?.length,
474
- kashidas: countKashida(text),
475
- prevWidth,
476
- prevMinWidth,
477
- prevUsingBrowserWrap,
478
- hadLock,
479
- dir: (this as any).direction,
480
- align: (this as any).textAlign,
481
- });
482
-
483
- const overlayEditor = (this as any).__overlayEditor;
484
-
485
- if (overlayEditor) {
486
- // Extract browser lines for pixel-perfect rendering
487
- try {
552
+ /**
553
+ * Commit overlay editing changes
554
+ */
555
+ private commitOverlayEdit(text: string) {
556
+
557
+ // Preserve geometry to avoid nudge when layout recalculates
558
+ const prevLeft = this.left;
559
+ const prevTop = this.top;
560
+ const prevWidth = this.get('width');
561
+ const prevMinWidth = (this as any).dynamicMinWidth;
562
+ const prevUsingBrowserWrap = (this as any)._usingBrowserWrapping;
563
+ const hadLock = (this as any).lockDynamicMinWidth;
564
+ (this as any).lockDynamicMinWidth = true;
565
+ const countKashida = (val?: string) => (val ? (val.match(/\u0640/g) || []).length : 0);
566
+ // console.log('[OverlayCommit] pre-layout', {
567
+ // textLength: text?.length,
568
+ // kashidas: countKashida(text),
569
+ // prevWidth,
570
+ // prevMinWidth,
571
+ // prevUsingBrowserWrap,
572
+ // hadLock,
573
+ // dir: (this as any).direction,
574
+ // align: (this as any).textAlign,
575
+ // });
576
+
577
+ const overlayEditor = (this as any).__overlayEditor;
578
+
579
+ if (overlayEditor) {
580
+ // Extract browser lines for pixel-perfect rendering
581
+ try {
488
582
  const result = extractLinesFromDOM(overlayEditor.textareaElement);
489
583
  storeBrowserLines(this, result.lines);
490
584
  } catch (error) {
491
- console.warn('Failed to extract browser lines:', error);
585
+ // console.warn('Failed to extract browser lines:', error);
492
586
  }
493
587
  }
494
-
495
- // Update text content and trigger layout recalculation
496
- this.text = text;
497
- // Freeze dynamic min width during this layout pass so width doesn't shrink/expand on commit
498
- if (prevMinWidth !== undefined) {
499
- (this as any).dynamicMinWidth = Math.max(prevMinWidth || 0, prevWidth || 0);
500
- }
501
- // Keep browser wrapping flag stable
502
- if (prevUsingBrowserWrap !== undefined) {
503
- (this as any)._usingBrowserWrapping = prevUsingBrowserWrap;
504
- }
505
-
506
- this.dirty = true;
507
- this.initDimensions();
508
- console.log('[OverlayCommit] post-layout', {
509
- width: this.get('width'),
510
- dynMinWidth: (this as any).dynamicMinWidth,
511
- usingBrowserWrap: (this as any)._usingBrowserWrapping,
512
- lockDynamicMinWidth: (this as any).lockDynamicMinWidth,
513
- kashidas: countKashida(this.text),
514
- left: this.left,
515
- top: this.top,
516
- });
517
- // Restore geometry after layout so the object doesn't drift
518
- this.set({
519
- left: prevLeft,
520
- top: prevTop,
521
- width: prevWidth,
522
- });
523
- this.setCoords();
524
- this.exitEditing();
525
- (this as any).lockDynamicMinWidth = hadLock;
526
- console.log('[OverlayCommit] final', {
527
- width: this.get('width'),
528
- dynMinWidth: (this as any).dynamicMinWidth,
529
- lockRestored: hadLock,
530
- left: this.left,
531
- top: this.top,
532
- });
533
- this.fire('changed');
534
- this.canvas && this.canvas.requestRenderAll();
535
- }
588
+
589
+ // Update text content and trigger layout recalculation
590
+ this.text = text;
591
+ // Freeze dynamic min width during this layout pass so width doesn't shrink/expand on commit
592
+ if (prevMinWidth !== undefined) {
593
+ (this as any).dynamicMinWidth = Math.max(prevMinWidth || 0, prevWidth || 0);
594
+ }
595
+ // Keep browser wrapping flag stable
596
+ if (prevUsingBrowserWrap !== undefined) {
597
+ (this as any)._usingBrowserWrapping = prevUsingBrowserWrap;
598
+ }
599
+
600
+ this.dirty = true;
601
+ this.initDimensions();
602
+ // console.log('[OverlayCommit] post-layout', {
603
+ // width: this.get('width'),
604
+ // dynMinWidth: (this as any).dynamicMinWidth,
605
+ // usingBrowserWrap: (this as any)._usingBrowserWrapping,
606
+ // lockDynamicMinWidth: (this as any).lockDynamicMinWidth,
607
+ // kashidas: countKashida(this.text),
608
+ // left: this.left,
609
+ // top: this.top,
610
+ // });
611
+ // Restore geometry after layout so the object doesn't drift
612
+ this.set({
613
+ left: prevLeft,
614
+ top: prevTop,
615
+ width: prevWidth,
616
+ });
617
+ this.setCoords();
618
+ this.exitEditing();
619
+ (this as any).lockDynamicMinWidth = hadLock;
620
+ // console.log('[OverlayCommit] final', {
621
+ // width: this.get('width'),
622
+ // dynMinWidth: (this as any).dynamicMinWidth,
623
+ // lockRestored: hadLock,
624
+ // left: this.left,
625
+ // top: this.top,
626
+ // });
627
+ this.fire('changed');
628
+ this.canvas && this.canvas.requestRenderAll();
629
+ }
536
630
 
537
631
  /**
538
632
  * Cancel overlay editing without changes
@@ -646,7 +740,7 @@ export abstract class ITextBehavior<
646
740
  * @private
647
741
  */
648
742
  _updateTextarea() {
649
- console.log('🔤 _updateTextarea called with fabric text:', this.text);
743
+ // console.log('🔤 _updateTextarea called with fabric text:', this.text);
650
744
  this.cursorOffsetCache = {};
651
745
  if (!this.hiddenTextarea) {
652
746
  return;
@@ -655,9 +749,9 @@ export abstract class ITextBehavior<
655
749
  // Sync textarea content with fabric text to prevent double-keypress issues
656
750
  const currentFabricText = this.text;
657
751
  if (this.hiddenTextarea.value !== currentFabricText) {
658
- console.log('🔤 _updateTextarea: syncing textarea to fabric text');
659
- console.log('🔤 _updateTextarea: textarea was:', this.hiddenTextarea.value);
660
- console.log('🔤 _updateTextarea: fabric is:', currentFabricText);
752
+ // console.log('🔤 _updateTextarea: syncing textarea to fabric text');
753
+ // console.log('🔤 _updateTextarea: textarea was:', this.hiddenTextarea.value);
754
+ // console.log('🔤 _updateTextarea: fabric is:', currentFabricText);
661
755
  this.hiddenTextarea.value = currentFabricText;
662
756
  }
663
757
 
@@ -1237,4 +1331,4 @@ export abstract class ITextBehavior<
1237
1331
  this.selectionEnd = newSelection;
1238
1332
  }
1239
1333
  }
1240
- }
1334
+ }
@@ -232,7 +232,8 @@ export abstract class ITextClickBehavior<
232
232
 
233
233
  for (let j = 0; j < charLength; j++) {
234
234
  const charStart = lineLeftOffset + chars[j].left;
235
- const charEnd = lineLeftOffset + chars[j + 1].left;
235
+ // For last character, use its width to calculate end position
236
+ const charEnd = lineLeftOffset + (chars[j + 1]?.left ?? (chars[j].left + chars[j].kernedWidth));
236
237
  const charMiddle = (charStart + charEnd) / 2;
237
238
  if (mouseOffset.x <= charMiddle) {
238
239
  charIndex = lineStartIndex + j;
@@ -244,11 +245,24 @@ export abstract class ITextClickBehavior<
244
245
  charIndex = lineStartIndex + charLength;
245
246
  }
246
247
 
247
- const lineCharIndex = charIndex - lineStartIndex;
248
- const result = this.flipX
249
- ? lineStartIndex + (charLength - lineCharIndex)
250
- : charIndex;
248
+ let lineCharIndex = charIndex - lineStartIndex;
251
249
 
252
- return Math.min(result, this._text.length);
250
+ // Handle flipX
251
+ if (this.flipX) {
252
+ lineCharIndex = charLength - lineCharIndex;
253
+ }
254
+
255
+ // Convert display index to original index (handles kashida)
256
+ const originalLineCharIndex = (this as any)._displayToOriginalIndex(lineIndex, lineCharIndex);
257
+
258
+ // Calculate original line start (sum of original line lengths before this line)
259
+ let originalLineStart = 0;
260
+ for (let i = 0; i < lineIndex; i++) {
261
+ const originalLineLength = (this as any)._getOriginalLineLength(i);
262
+ originalLineStart += originalLineLength + this.missingNewlineOffset(i);
263
+ }
264
+
265
+ const originalIndex = originalLineStart + originalLineCharIndex;
266
+ return Math.min(originalIndex, this.text.length);
253
267
  }
254
268
  }
@@ -180,21 +180,21 @@ export abstract class ITextKeyBehavior<
180
180
  }
181
181
 
182
182
  // Debug log to track the double keypress issue
183
- console.log('🔤 onInput debug:', {
184
- fabricText: this.text,
185
- textareaValue: value,
186
- fabricSelection: { start: this.selectionStart, end: this.selectionEnd },
187
- textareaSelection: { start: selectionStart, end: selectionEnd },
188
- fromPaste,
189
- inComposition: this.inCompositionMode
190
- });
183
+ // console.log('🔤 onInput debug:', {
184
+ // fabricText: this.text,
185
+ // textareaValue: value,
186
+ // fabricSelection: { start: this.selectionStart, end: this.selectionEnd },
187
+ // textareaSelection: { start: selectionStart, end: selectionEnd },
188
+ // fromPaste,
189
+ // inComposition: this.inCompositionMode
190
+ // });
191
191
 
192
192
  // Immediate sync for simple character replacement - fix for double keypress issue
193
193
  if (this.text !== value && !this.inCompositionMode) {
194
- console.log('🔤 Immediate sync: fabric text differs from textarea, syncing immediately');
195
- console.log('🔤 Before sync - fabric text:', this.text);
196
- console.log('🔤 Before sync - textarea value:', value);
197
- console.log('🔤 fromPaste:', fromPaste);
194
+ // console.log('🔤 Immediate sync: fabric text differs from textarea, syncing immediately');
195
+ // console.log('🔤 Before sync - fabric text:', this.text);
196
+ // console.log('🔤 Before sync - textarea value:', value);
197
+ // console.log('🔤 fromPaste:', fromPaste);
198
198
 
199
199
  // Clear all relevant caches that might prevent visual updates
200
200
  this.cursorOffsetCache = {};
@@ -202,7 +202,7 @@ export abstract class ITextKeyBehavior<
202
202
  (this as any)._lastDimensionState = null;
203
203
  this._forceClearCache = true;
204
204
 
205
- console.log('🔤 Cleared all caches');
205
+ // console.log('🔤 Cleared all caches');
206
206
 
207
207
  // Use the same logic as updateAndFire but immediately
208
208
  this.updateFromTextArea();
@@ -214,8 +214,8 @@ export abstract class ITextKeyBehavior<
214
214
  this.canvas.renderAll();
215
215
  }
216
216
 
217
- console.log('🔤 After updateFromTextArea - fabric text:', this.text);
218
- console.log('🔤 Sync complete, caches cleared, synchronous render only');
217
+ // console.log('🔤 After updateFromTextArea - fabric text:', this.text);
218
+ // console.log('🔤 Sync complete, caches cleared, synchronous render only');
219
219
  return;
220
220
  }
221
221