@nasser-sw/fabric 7.0.1-beta16 → 7.0.1-beta17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +7 -0
- package/dist/index.js +1982 -649
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.min.mjs +1 -1
- package/dist/index.min.mjs.map +1 -1
- package/dist/index.mjs +1982 -649
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +1982 -649
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +1982 -649
- package/dist/index.node.mjs.map +1 -1
- package/dist/package.json.min.mjs +1 -1
- package/dist/package.json.mjs +1 -1
- package/dist/src/shapes/IText/IText.d.ts +31 -6
- package/dist/src/shapes/IText/IText.d.ts.map +1 -1
- package/dist/src/shapes/IText/IText.min.mjs +1 -1
- package/dist/src/shapes/IText/IText.min.mjs.map +1 -1
- package/dist/src/shapes/IText/IText.mjs +495 -126
- package/dist/src/shapes/IText/IText.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextBehavior.d.ts +12 -0
- package/dist/src/shapes/IText/ITextBehavior.d.ts.map +1 -1
- package/dist/src/shapes/IText/ITextBehavior.min.mjs +1 -1
- package/dist/src/shapes/IText/ITextBehavior.min.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextBehavior.mjs +127 -36
- package/dist/src/shapes/IText/ITextBehavior.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.d.ts.map +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.min.mjs +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.min.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.mjs +21 -4
- package/dist/src/shapes/IText/ITextClickBehavior.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextKeyBehavior.min.mjs +1 -1
- package/dist/src/shapes/IText/ITextKeyBehavior.min.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextKeyBehavior.mjs +17 -21
- package/dist/src/shapes/IText/ITextKeyBehavior.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.d.ts +69 -1
- package/dist/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist/src/shapes/Text/Text.min.mjs +1 -1
- package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.mjs +374 -60
- package/dist/src/shapes/Text/Text.mjs.map +1 -1
- package/dist/src/shapes/Text/constants.d.ts.map +1 -1
- package/dist/src/shapes/Text/constants.min.mjs +1 -1
- package/dist/src/shapes/Text/constants.min.mjs.map +1 -1
- package/dist/src/shapes/Text/constants.mjs +2 -1
- package/dist/src/shapes/Text/constants.mjs.map +1 -1
- package/dist/src/shapes/Textbox.d.ts +8 -1
- package/dist/src/shapes/Textbox.d.ts.map +1 -1
- package/dist/src/shapes/Textbox.min.mjs +1 -1
- package/dist/src/shapes/Textbox.min.mjs.map +1 -1
- package/dist/src/shapes/Textbox.mjs +406 -63
- package/dist/src/shapes/Textbox.mjs.map +1 -1
- package/dist/src/text/hitTest.min.mjs +1 -1
- package/dist/src/text/hitTest.min.mjs.map +1 -1
- package/dist/src/text/hitTest.mjs +1 -198
- package/dist/src/text/hitTest.mjs.map +1 -1
- package/dist/src/text/layout.min.mjs +1 -1
- package/dist/src/text/layout.min.mjs.map +1 -1
- package/dist/src/text/layout.mjs +122 -5
- package/dist/src/text/layout.mjs.map +1 -1
- package/dist/src/text/overlayEditor.min.mjs +1 -1
- package/dist/src/text/overlayEditor.min.mjs.map +1 -1
- package/dist/src/text/overlayEditor.mjs +132 -142
- package/dist/src/text/overlayEditor.mjs.map +1 -1
- package/dist/src/text/unicode.d.ts +28 -0
- package/dist/src/text/unicode.d.ts.map +1 -1
- package/dist/src/text/unicode.min.mjs +1 -1
- package/dist/src/text/unicode.min.mjs.map +1 -1
- package/dist/src/text/unicode.mjs +294 -1
- package/dist/src/text/unicode.mjs.map +1 -1
- package/dist-extensions/src/shapes/IText/IText.d.ts +31 -6
- package/dist-extensions/src/shapes/IText/IText.d.ts.map +1 -1
- package/dist-extensions/src/shapes/IText/ITextBehavior.d.ts +12 -0
- package/dist-extensions/src/shapes/IText/ITextBehavior.d.ts.map +1 -1
- package/dist-extensions/src/shapes/IText/ITextClickBehavior.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/Text.d.ts +69 -1
- package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/constants.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Textbox.d.ts +8 -1
- package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
- package/dist-extensions/src/text/unicode.d.ts +28 -0
- package/dist-extensions/src/text/unicode.d.ts.map +1 -1
- package/package.json +164 -164
- package/rtl-debug.html +358 -200
- package/src/shapes/IText/IText.ts +524 -110
- package/src/shapes/IText/ITextBehavior.ts +174 -80
- package/src/shapes/IText/ITextClickBehavior.ts +20 -6
- package/src/shapes/IText/ITextKeyBehavior.ts +15 -15
- package/src/shapes/Text/Text.ts +488 -107
- package/src/shapes/Text/constants.ts +4 -2
- package/src/shapes/Textbox.ts +414 -65
- package/src/text/layout.ts +150 -23
- package/src/text/overlayEditor.ts +148 -148
- package/src/text/unicode.ts +177 -2
package/dist/src/text/layout.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { measureGraphemeWithKerning } from './measure.mjs';
|
|
2
2
|
import { applyEllipsis } from './ellipsis.mjs';
|
|
3
|
-
import { segmentGraphemes } from './unicode.mjs';
|
|
3
|
+
import { segmentGraphemes, analyzeBiDi } from './unicode.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Core Text Layout Engine
|
|
@@ -182,7 +182,7 @@ function layoutSingleLine(text, options) {
|
|
|
182
182
|
let lineHeight = 0;
|
|
183
183
|
let charIndex = textOffset; // Track character position in original text
|
|
184
184
|
|
|
185
|
-
// Measure each grapheme
|
|
185
|
+
// Measure each grapheme in logical order
|
|
186
186
|
for (let i = 0; i < graphemes.length; i++) {
|
|
187
187
|
const grapheme = graphemes[i];
|
|
188
188
|
const prevGrapheme = i > 0 ? graphemes[i - 1] : undefined;
|
|
@@ -198,12 +198,14 @@ function layoutSingleLine(text, options) {
|
|
|
198
198
|
bounds.push({
|
|
199
199
|
grapheme,
|
|
200
200
|
x,
|
|
201
|
+
// Will be updated by BiDi reordering
|
|
201
202
|
y: 0,
|
|
202
203
|
// Will be adjusted later
|
|
203
204
|
width: measurement.width,
|
|
204
205
|
height: measurement.height,
|
|
205
206
|
kernedWidth: measurement.kernedWidth,
|
|
206
207
|
left: x,
|
|
208
|
+
// Logical position (cumulative)
|
|
207
209
|
baseline: measurement.baseline,
|
|
208
210
|
charIndex: charIndex,
|
|
209
211
|
// Character position in original text
|
|
@@ -217,6 +219,9 @@ function layoutSingleLine(text, options) {
|
|
|
217
219
|
lineHeight = Math.max(lineHeight, measurement.height);
|
|
218
220
|
}
|
|
219
221
|
|
|
222
|
+
// Note: BiDi visual reordering is handled by the browser's canvas fillText
|
|
223
|
+
// The layout stores positions in logical order; hit testing handles the visual mapping
|
|
224
|
+
|
|
220
225
|
// Remove trailing spacing from total width (but keep in bounds for rendering)
|
|
221
226
|
if (bounds.length > 0) {
|
|
222
227
|
options.letterSpacing || 0;
|
|
@@ -227,7 +232,9 @@ function layoutSingleLine(text, options) {
|
|
|
227
232
|
}
|
|
228
233
|
|
|
229
234
|
// Apply line height
|
|
230
|
-
|
|
235
|
+
// Note: Fabric.js uses _fontSizeMult = 1.13 for line height calculation
|
|
236
|
+
const fontSizeMult = 1.13;
|
|
237
|
+
const finalHeight = lineHeight * options.lineHeight * fontSizeMult;
|
|
231
238
|
return {
|
|
232
239
|
text,
|
|
233
240
|
graphemes,
|
|
@@ -297,11 +304,119 @@ function wrapByCharacters(text, maxWidth, options) {
|
|
|
297
304
|
return lines.length > 0 ? lines : [''];
|
|
298
305
|
}
|
|
299
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Apply BiDi visual reordering to calculate correct visual X positions
|
|
309
|
+
* This implements the Unicode Bidirectional Algorithm for character placement
|
|
310
|
+
*/
|
|
311
|
+
function applyBiDiVisualReordering(line, options) {
|
|
312
|
+
const baseDirection = options.direction === 'inherit' ? 'ltr' : options.direction;
|
|
313
|
+
|
|
314
|
+
// Quick check: if all characters are same direction as base, no reordering needed
|
|
315
|
+
const runs = analyzeBiDi(line.text, baseDirection);
|
|
316
|
+
const hasMixedBiDi = runs.length > 1 || runs.length === 1 && runs[0].direction !== baseDirection;
|
|
317
|
+
if (!hasMixedBiDi) {
|
|
318
|
+
// For pure LTR or pure RTL, just set visual x = logical left
|
|
319
|
+
// For RTL base direction, we need to flip positions
|
|
320
|
+
if (baseDirection === 'rtl') {
|
|
321
|
+
// RTL: rightmost character should be at x=0, leftmost at x=lineWidth
|
|
322
|
+
line.bounds.forEach(bound => {
|
|
323
|
+
bound.x = line.width - bound.left - bound.kernedWidth;
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
// For LTR, x is already correct (same as left)
|
|
327
|
+
return line;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Mixed BiDi text - need to reorder runs visually
|
|
331
|
+
// 1. Build mapping from grapheme index to run
|
|
332
|
+
const graphemeToRun = [];
|
|
333
|
+
let runGraphemeStart = 0;
|
|
334
|
+
for (let runIdx = 0; runIdx < runs.length; runIdx++) {
|
|
335
|
+
const run = runs[runIdx];
|
|
336
|
+
const runGraphemes = segmentGraphemes(run.text);
|
|
337
|
+
for (let i = 0; i < runGraphemes.length; i++) {
|
|
338
|
+
graphemeToRun.push(runIdx);
|
|
339
|
+
}
|
|
340
|
+
runGraphemeStart += runGraphemes.length;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// 2. Calculate run widths and positions
|
|
344
|
+
const runWidths = [];
|
|
345
|
+
const runStartIndices = [];
|
|
346
|
+
let currentIdx = 0;
|
|
347
|
+
for (const run of runs) {
|
|
348
|
+
runStartIndices.push(currentIdx);
|
|
349
|
+
const runGraphemes = segmentGraphemes(run.text);
|
|
350
|
+
let runWidth = 0;
|
|
351
|
+
for (let i = 0; i < runGraphemes.length; i++) {
|
|
352
|
+
if (currentIdx + i < line.bounds.length) {
|
|
353
|
+
const letterSpacing = options.letterSpacing || 0;
|
|
354
|
+
const charSpacing = options.charSpacing ? options.fontSize * options.charSpacing / 1000 : 0;
|
|
355
|
+
runWidth += line.bounds[currentIdx + i].kernedWidth + letterSpacing + charSpacing;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
runWidths.push(runWidth);
|
|
359
|
+
currentIdx += runGraphemes.length;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// 3. Determine visual order of runs based on base direction
|
|
363
|
+
// RTL base: runs display right-to-left (first run on right)
|
|
364
|
+
// LTR base: runs display left-to-right (first run on left)
|
|
365
|
+
const visualRunOrder = runs.map((_, i) => i);
|
|
366
|
+
if (baseDirection === 'rtl') {
|
|
367
|
+
visualRunOrder.reverse();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// 4. Calculate visual X position for each run
|
|
371
|
+
const runVisualX = new Array(runs.length);
|
|
372
|
+
let currentX = 0;
|
|
373
|
+
for (const runIdx of visualRunOrder) {
|
|
374
|
+
runVisualX[runIdx] = currentX;
|
|
375
|
+
currentX += runWidths[runIdx];
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// 5. Assign visual X positions to each grapheme
|
|
379
|
+
for (let i = 0; i < line.bounds.length; i++) {
|
|
380
|
+
const runIdx = graphemeToRun[i];
|
|
381
|
+
if (runIdx === undefined) continue;
|
|
382
|
+
const run = runs[runIdx];
|
|
383
|
+
const runStart = runStartIndices[runIdx];
|
|
384
|
+
|
|
385
|
+
// Calculate spacing once
|
|
386
|
+
const letterSpacing = options.letterSpacing || 0;
|
|
387
|
+
const charSpacing = options.charSpacing ? options.fontSize * options.charSpacing / 1000 : 0;
|
|
388
|
+
const totalSpacing = letterSpacing + charSpacing;
|
|
389
|
+
|
|
390
|
+
// Calculate offset within run (sum of widths of chars before this one)
|
|
391
|
+
let offsetInRun = 0;
|
|
392
|
+
for (let j = runStart; j < i; j++) {
|
|
393
|
+
offsetInRun += line.bounds[j].kernedWidth + totalSpacing;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Character width including spacing
|
|
397
|
+
const charWidth = line.bounds[i].kernedWidth + totalSpacing;
|
|
398
|
+
|
|
399
|
+
// For RTL runs, characters within the run are reversed visually
|
|
400
|
+
// First logical char appears on the right, last on the left
|
|
401
|
+
if (run.direction === 'rtl') {
|
|
402
|
+
// Visual X = run right edge - cumulative width including this char
|
|
403
|
+
// This places first char at right side of run, last char at left side
|
|
404
|
+
line.bounds[i].x = runVisualX[runIdx] + runWidths[runIdx] - offsetInRun - charWidth;
|
|
405
|
+
} else {
|
|
406
|
+
// LTR run: visual position is run start + offset within run
|
|
407
|
+
line.bounds[i].x = runVisualX[runIdx] + offsetInRun;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return line;
|
|
411
|
+
}
|
|
412
|
+
|
|
300
413
|
/**
|
|
301
414
|
* Apply text alignment to lines
|
|
302
415
|
*/
|
|
303
416
|
function applyAlignment(lines, align, containerWidth, options) {
|
|
304
417
|
return lines.map(line => {
|
|
418
|
+
// First apply BiDi reordering to get correct visual X positions
|
|
419
|
+
applyBiDiVisualReordering(line, options);
|
|
305
420
|
let offsetX = 0;
|
|
306
421
|
switch (align) {
|
|
307
422
|
case 'center':
|
|
@@ -321,7 +436,7 @@ function applyAlignment(lines, align, containerWidth, options) {
|
|
|
321
436
|
break;
|
|
322
437
|
}
|
|
323
438
|
|
|
324
|
-
// Apply offset to all bounds
|
|
439
|
+
// Apply offset to all bounds (both visual x and logical left for alignment)
|
|
325
440
|
if (offsetX !== 0) {
|
|
326
441
|
line.bounds.forEach(bound => {
|
|
327
442
|
bound.x += offsetX;
|
|
@@ -405,7 +520,9 @@ function handleHeightOverflow(existingLines, overflowLine, remainingHeight, opti
|
|
|
405
520
|
* Create empty line for empty paragraphs
|
|
406
521
|
*/
|
|
407
522
|
function createEmptyLine(options) {
|
|
408
|
-
|
|
523
|
+
// Fabric.js uses _fontSizeMult = 1.13 for line height calculation
|
|
524
|
+
const fontSizeMult = 1.13;
|
|
525
|
+
const height = options.fontSize * options.lineHeight * fontSizeMult;
|
|
409
526
|
return {
|
|
410
527
|
text: '',
|
|
411
528
|
graphemes: [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.mjs","sources":["../../../src/text/layout.ts"],"sourcesContent":["/**\r\n * Core Text Layout Engine\r\n * \r\n * Implements Konva-compatible text layout with support for:\r\n * - Multiple wrap modes (word/char/none)\r\n * - Ellipsis truncation\r\n * - Justify alignment with proper space distribution\r\n * - RTL/LTR text direction\r\n * - Advanced grapheme handling\r\n */\r\n\r\nimport { graphemeSplit } from '../util/lang_string';\r\nimport type { MeasurementOptions, GraphemeMeasurement, KerningMeasurement } from './measure';\r\nimport { measureGrapheme, measureGraphemeWithKerning, getFontMetrics } from './measure';\r\nimport type { EllipsisResult } from './ellipsis';\r\nimport { applyEllipsis } from './ellipsis';\r\nimport { segmentGraphemes, analyzeBiDi, type BiDiRun } from './unicode';\r\n\r\nexport interface TextLayoutOptions {\r\n text: string;\r\n width?: number;\r\n height?: number;\r\n wrap: 'word' | 'char' | 'none';\r\n align: 'left' | 'center' | 'right' | 'justify';\r\n ellipsis?: boolean | string;\r\n fontSize: number;\r\n lineHeight: number;\r\n letterSpacing?: number; // px-based (Konva style)\r\n charSpacing?: number; // em-based (Fabric style) \r\n direction: 'ltr' | 'rtl' | 'inherit';\r\n fontFamily: string;\r\n fontStyle: string;\r\n fontWeight: string | number;\r\n padding?: number;\r\n verticalAlign?: 'top' | 'middle' | 'bottom';\r\n}\r\n\r\nexport interface LayoutResult {\r\n lines: LayoutLine[];\r\n totalWidth: number;\r\n totalHeight: number;\r\n isTruncated: boolean;\r\n graphemeCount: number;\r\n ellipsisApplied?: EllipsisResult;\r\n}\r\n\r\nexport interface LayoutLine {\r\n text: string;\r\n graphemes: string[];\r\n width: number;\r\n height: number;\r\n bounds: GraphemeBounds[];\r\n isWrapped: boolean;\r\n isLastInParagraph: boolean;\r\n justifyRatio?: number; // For justify alignment - space expansion factor\r\n baseline: number;\r\n}\r\n\r\nexport interface GraphemeBounds {\r\n grapheme: string;\r\n x: number;\r\n y: number;\r\n width: number;\r\n height: number;\r\n kernedWidth: number;\r\n left: number;\r\n baseline: number;\r\n deltaY?: number;\r\n charIndex: number; // Logical character index in original text\r\n graphemeIndex: number; // Logical grapheme index in original text\r\n}\r\n\r\n/**\r\n * Main text layout function - converts text and options into positioned layout\r\n */\r\nexport function layoutText(options: TextLayoutOptions): LayoutResult {\r\n const {\r\n text,\r\n width: containerWidth,\r\n height: containerHeight,\r\n wrap,\r\n align,\r\n ellipsis,\r\n direction,\r\n padding = 0,\r\n verticalAlign = 'top'\r\n } = options;\r\n\r\n // Handle empty text\r\n if (!text) {\r\n return {\r\n lines: [],\r\n totalWidth: 0,\r\n totalHeight: 0,\r\n isTruncated: false,\r\n graphemeCount: 0,\r\n };\r\n }\r\n\r\n // Calculate available space\r\n const maxWidth = containerWidth ? containerWidth - (padding * 2) : Infinity;\r\n const maxHeight = containerHeight ? containerHeight - (padding * 2) : Infinity;\r\n\r\n // Split text into paragraphs (by \\n)\r\n const paragraphs = text.split('\\n');\r\n \r\n // Process each paragraph\r\n const allLines: LayoutLine[] = [];\r\n let totalHeight = 0;\r\n let maxLineWidth = 0;\r\n let totalGraphemes = 0;\r\n\r\n for (let i = 0; i < paragraphs.length; i++) {\r\n const paragraph = paragraphs[i];\r\n const isLastParagraph = i === paragraphs.length - 1;\r\n \r\n // Layout this paragraph\r\n const paragraphLines = layoutParagraph(paragraph, {\r\n ...options,\r\n width: maxWidth,\r\n isLastParagraph,\r\n });\r\n \r\n // Check height constraints\r\n for (const line of paragraphLines) {\r\n if (containerHeight && totalHeight + line.height > maxHeight) {\r\n // Height exceeded - truncate here\r\n const truncatedResult = handleHeightOverflow(\r\n allLines,\r\n line,\r\n maxHeight - totalHeight,\r\n options\r\n );\r\n \r\n return {\r\n lines: truncatedResult.lines,\r\n totalWidth: Math.max(maxLineWidth, ...truncatedResult.lines.map(l => l.width)),\r\n totalHeight: maxHeight,\r\n isTruncated: true,\r\n graphemeCount: totalGraphemes + truncatedResult.addedGraphemes,\r\n ellipsisApplied: truncatedResult.ellipsisResult,\r\n };\r\n }\r\n\r\n // Mark last line in paragraph\r\n if (line === paragraphLines[paragraphLines.length - 1]) {\r\n line.isLastInParagraph = true;\r\n }\r\n\r\n allLines.push(line);\r\n totalHeight += line.height;\r\n maxLineWidth = Math.max(maxLineWidth, line.width);\r\n totalGraphemes += line.graphemes.length;\r\n }\r\n }\r\n\r\n // Apply ellipsis if width exceeded and ellipsis enabled\r\n let ellipsisResult: EllipsisResult | undefined;\r\n if (ellipsis && containerWidth) {\r\n for (const line of allLines) {\r\n if (line.width > maxWidth) {\r\n ellipsisResult = applyEllipsis(line.text, {\r\n maxWidth,\r\n maxHeight: Infinity,\r\n ellipsisChar: typeof ellipsis === 'string' ? ellipsis : '…',\r\n measureFn: (text: string) => measureLineWidth(text, options),\r\n });\r\n \r\n if (ellipsisResult.isTruncated) {\r\n // Rebuild truncated line\r\n const truncatedLine = layoutSingleLine(ellipsisResult.truncatedText, options);\r\n Object.assign(line, truncatedLine);\r\n break;\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Apply alignment\r\n const alignedLines = applyAlignment(allLines, align, maxLineWidth, options);\r\n\r\n // Apply vertical alignment\r\n const verticalOffset = calculateVerticalOffset(\r\n totalHeight,\r\n containerHeight || totalHeight,\r\n verticalAlign\r\n );\r\n\r\n // Adjust line positions for vertical alignment\r\n alignedLines.forEach(line => {\r\n line.bounds.forEach(bound => {\r\n bound.y += verticalOffset;\r\n });\r\n });\r\n\r\n return {\r\n lines: alignedLines,\r\n totalWidth: maxLineWidth,\r\n totalHeight: totalHeight,\r\n isTruncated: !!ellipsisResult?.isTruncated,\r\n graphemeCount: totalGraphemes,\r\n ellipsisApplied: ellipsisResult,\r\n };\r\n}\r\n\r\n/**\r\n * Layout a single paragraph with wrapping\r\n */\r\nfunction layoutParagraph(\r\n text: string, \r\n options: TextLayoutOptions & { width: number; isLastParagraph: boolean }\r\n): LayoutLine[] {\r\n const { wrap, width: maxWidth } = options;\r\n\r\n if (!text) {\r\n // Empty paragraph - create empty line\r\n return [createEmptyLine(options)];\r\n }\r\n\r\n // Handle no wrapping\r\n if (wrap === 'none' || maxWidth === Infinity) {\r\n return [layoutSingleLine(text, options, 0)];\r\n }\r\n\r\n // Apply wrapping\r\n const lines: string[] = [];\r\n \r\n if (wrap === 'word') {\r\n lines.push(...wrapByWords(text, maxWidth, options));\r\n } else if (wrap === 'char') {\r\n lines.push(...wrapByCharacters(text, maxWidth, options));\r\n }\r\n\r\n // Convert wrapped lines to layout lines, tracking text offset\r\n let textOffset = 0;\r\n const layoutLines = lines.map(lineText => {\r\n const line = layoutSingleLine(lineText, options, textOffset);\r\n textOffset += lineText.length + 1; // +1 for newline character\r\n return line;\r\n });\r\n \r\n return layoutLines;\r\n}\r\n\r\n/**\r\n * Layout a single line of text (no wrapping)\r\n */\r\nfunction layoutSingleLine(text: string, options: TextLayoutOptions, textOffset: number = 0): LayoutLine {\r\n const graphemes = segmentGraphemes(text);\r\n const bounds: GraphemeBounds[] = [];\r\n const measurementOptions = createMeasurementOptions(options);\r\n \r\n let x = 0;\r\n let lineWidth = 0;\r\n let lineHeight = 0;\r\n let charIndex = textOffset; // Track character position in original text\r\n \r\n // Measure each grapheme\r\n for (let i = 0; i < graphemes.length; i++) {\r\n const grapheme = graphemes[i];\r\n const prevGrapheme = i > 0 ? graphemes[i - 1] : undefined;\r\n \r\n // Measure with kerning\r\n const measurement = measureGraphemeWithKerning(\r\n grapheme,\r\n prevGrapheme,\r\n measurementOptions\r\n );\r\n \r\n // Apply letter spacing (Konva style - applied to ALL characters including last)\r\n const letterSpacing = options.letterSpacing || 0;\r\n const charSpacing = options.charSpacing ? \r\n (options.fontSize * options.charSpacing) / 1000 : 0;\r\n \r\n const totalSpacing = letterSpacing + charSpacing;\r\n const effectiveWidth = measurement.kernedWidth + totalSpacing;\r\n \r\n bounds.push({\r\n grapheme,\r\n x,\r\n y: 0, // Will be adjusted later\r\n width: measurement.width,\r\n height: measurement.height,\r\n kernedWidth: measurement.kernedWidth,\r\n left: x,\r\n baseline: measurement.baseline,\r\n charIndex: charIndex, // Character position in original text\r\n graphemeIndex: textOffset + i, // Grapheme index in original text\r\n });\r\n \r\n // Update character index for next iteration\r\n charIndex += grapheme.length;\r\n \r\n x += effectiveWidth;\r\n lineWidth += effectiveWidth;\r\n lineHeight = Math.max(lineHeight, measurement.height);\r\n }\r\n\r\n // Remove trailing spacing from total width (but keep in bounds for rendering)\r\n if (bounds.length > 0) {\r\n const letterSpacing = options.letterSpacing || 0;\r\n const charSpacing = options.charSpacing ? \r\n (options.fontSize * options.charSpacing) / 1000 : 0;\r\n const totalSpacing = letterSpacing + charSpacing;\r\n \r\n // Konva applies letterSpacing to all chars, so we don't remove it\r\n // lineWidth -= totalSpacing;\r\n }\r\n\r\n // Apply line height\r\n const finalHeight = lineHeight * options.lineHeight;\r\n\r\n return {\r\n text,\r\n graphemes,\r\n width: lineWidth,\r\n height: finalHeight,\r\n bounds,\r\n isWrapped: false,\r\n isLastInParagraph: false,\r\n baseline: finalHeight * 0.8, // Approximate baseline position\r\n };\r\n}\r\n\r\n/**\r\n * Word-based wrapping algorithm\r\n */\r\nfunction wrapByWords(text: string, maxWidth: number, options: TextLayoutOptions): string[] {\r\n const lines: string[] = [];\r\n const words = text.split(/(\\s+)/); // Preserve whitespace\r\n let currentLine = '';\r\n let currentWidth = 0;\r\n\r\n for (let i = 0; i < words.length; i++) {\r\n const word = words[i];\r\n const wordWidth = measureLineWidth(word, options);\r\n const testLine = currentLine ? currentLine + word : word;\r\n const testWidth = measureLineWidth(testLine, options);\r\n\r\n // If adding this word exceeds max width and we have content\r\n if (testWidth > maxWidth && currentLine) {\r\n lines.push(currentLine.trim());\r\n currentLine = word;\r\n currentWidth = wordWidth;\r\n }\r\n // If single word is too long, break it by characters\r\n else if (wordWidth > maxWidth && !currentLine) {\r\n const brokenWord = wrapByCharacters(word, maxWidth, options);\r\n lines.push(...brokenWord.slice(0, -1)); // All but last part\r\n currentLine = brokenWord[brokenWord.length - 1]; // Last part\r\n currentWidth = measureLineWidth(currentLine, options);\r\n }\r\n else {\r\n currentLine = testLine;\r\n currentWidth = testWidth;\r\n }\r\n }\r\n\r\n if (currentLine) {\r\n lines.push(currentLine.trim());\r\n }\r\n\r\n return lines.length > 0 ? lines : [''];\r\n}\r\n\r\n/**\r\n * Character-based wrapping algorithm \r\n */\r\nfunction wrapByCharacters(text: string, maxWidth: number, options: TextLayoutOptions): string[] {\r\n const lines: string[] = [];\r\n const graphemes = segmentGraphemes(text);\r\n let currentLine = '';\r\n \r\n for (const grapheme of graphemes) {\r\n const testLine = currentLine + grapheme;\r\n const testWidth = measureLineWidth(testLine, options);\r\n \r\n if (testWidth > maxWidth && currentLine) {\r\n lines.push(currentLine);\r\n currentLine = grapheme;\r\n } else {\r\n currentLine = testLine;\r\n }\r\n }\r\n \r\n if (currentLine) {\r\n lines.push(currentLine);\r\n }\r\n \r\n return lines.length > 0 ? lines : [''];\r\n}\r\n\r\n/**\r\n * Apply text alignment to lines\r\n */\r\nfunction applyAlignment(\r\n lines: LayoutLine[], \r\n align: string, \r\n containerWidth: number,\r\n options: TextLayoutOptions\r\n): LayoutLine[] {\r\n return lines.map(line => {\r\n let offsetX = 0;\r\n \r\n switch (align) {\r\n case 'center':\r\n offsetX = (containerWidth - line.width) / 2;\r\n break;\r\n case 'right':\r\n offsetX = containerWidth - line.width;\r\n break;\r\n case 'justify':\r\n if (!line.isLastInParagraph && line.graphemes.length > 1) {\r\n return applyJustification(line, containerWidth, options);\r\n }\r\n break;\r\n case 'left':\r\n default:\r\n offsetX = 0;\r\n break;\r\n }\r\n \r\n // Apply offset to all bounds\r\n if (offsetX !== 0) {\r\n line.bounds.forEach(bound => {\r\n bound.x += offsetX;\r\n bound.left += offsetX;\r\n });\r\n }\r\n \r\n return line;\r\n });\r\n}\r\n\r\n/**\r\n * Apply justify alignment by expanding spaces\r\n */\r\nfunction applyJustification(\r\n line: LayoutLine, \r\n containerWidth: number, \r\n options: TextLayoutOptions\r\n): LayoutLine {\r\n const spaces = line.graphemes.filter(g => /\\s/.test(g)).length;\r\n if (spaces === 0) return line;\r\n \r\n const extraSpace = containerWidth - line.width;\r\n const spaceExpansion = extraSpace / spaces;\r\n \r\n let offsetX = 0;\r\n line.bounds.forEach(bound => {\r\n bound.x += offsetX;\r\n bound.left += offsetX;\r\n \r\n if (/\\s/.test(bound.grapheme)) {\r\n bound.kernedWidth += spaceExpansion;\r\n bound.width += spaceExpansion;\r\n offsetX += spaceExpansion;\r\n }\r\n });\r\n \r\n line.width = containerWidth;\r\n line.justifyRatio = 1 + (spaceExpansion / (options.fontSize * 0.25)); // Approximate space width\r\n \r\n return line;\r\n}\r\n\r\n/**\r\n * Calculate vertical alignment offset\r\n */\r\nfunction calculateVerticalOffset(\r\n contentHeight: number,\r\n containerHeight: number, \r\n align: 'top' | 'middle' | 'bottom'\r\n): number {\r\n switch (align) {\r\n case 'middle':\r\n return (containerHeight - contentHeight) / 2;\r\n case 'bottom':\r\n return containerHeight - contentHeight;\r\n case 'top':\r\n default:\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Handle height overflow with ellipsis\r\n */\r\nfunction handleHeightOverflow(\r\n existingLines: LayoutLine[],\r\n overflowLine: LayoutLine,\r\n remainingHeight: number,\r\n options: TextLayoutOptions\r\n): { \r\n lines: LayoutLine[]; \r\n addedGraphemes: number; \r\n ellipsisResult?: EllipsisResult;\r\n} {\r\n // If ellipsis is enabled, try to fit part of the overflow line\r\n if (options.ellipsis && remainingHeight > 0) {\r\n const ellipsisChar = typeof options.ellipsis === 'string' ? options.ellipsis : '…';\r\n const maxWidth = options.width || Infinity;\r\n \r\n const ellipsisResult = applyEllipsis(overflowLine.text, {\r\n maxWidth,\r\n maxHeight: remainingHeight,\r\n ellipsisChar,\r\n measureFn: (text: string) => measureLineWidth(text, options),\r\n });\r\n \r\n if (ellipsisResult.isTruncated) {\r\n const truncatedLine = layoutSingleLine(ellipsisResult.truncatedText, options);\r\n truncatedLine.isLastInParagraph = true;\r\n \r\n return {\r\n lines: [...existingLines, truncatedLine],\r\n addedGraphemes: truncatedLine.graphemes.length,\r\n ellipsisResult,\r\n };\r\n }\r\n }\r\n \r\n return {\r\n lines: existingLines,\r\n addedGraphemes: 0,\r\n };\r\n}\r\n\r\n/**\r\n * Create empty line for empty paragraphs\r\n */\r\nfunction createEmptyLine(options: TextLayoutOptions): LayoutLine {\r\n const height = options.fontSize * options.lineHeight;\r\n \r\n return {\r\n text: '',\r\n graphemes: [],\r\n width: 0,\r\n height,\r\n bounds: [],\r\n isWrapped: false,\r\n isLastInParagraph: true,\r\n baseline: height * 0.8,\r\n };\r\n}\r\n\r\n/**\r\n * Measure width of a line of text\r\n */\r\nfunction measureLineWidth(text: string, options: TextLayoutOptions): number {\r\n const graphemes = segmentGraphemes(text);\r\n const measurementOptions = createMeasurementOptions(options);\r\n \r\n let width = 0;\r\n for (let i = 0; i < graphemes.length; i++) {\r\n const grapheme = graphemes[i];\r\n const prevGrapheme = i > 0 ? graphemes[i - 1] : undefined;\r\n \r\n const measurement = measureGraphemeWithKerning(\r\n grapheme,\r\n prevGrapheme, \r\n measurementOptions\r\n );\r\n \r\n const letterSpacing = options.letterSpacing || 0;\r\n const charSpacing = options.charSpacing ? \r\n (options.fontSize * options.charSpacing) / 1000 : 0;\r\n \r\n width += measurement.kernedWidth + letterSpacing + charSpacing;\r\n }\r\n \r\n return width;\r\n}\r\n\r\n/**\r\n * Convert layout options to measurement options\r\n */\r\nfunction createMeasurementOptions(options: TextLayoutOptions): MeasurementOptions {\r\n return {\r\n fontFamily: options.fontFamily,\r\n fontSize: options.fontSize,\r\n fontStyle: options.fontStyle,\r\n fontWeight: options.fontWeight,\r\n letterSpacing: options.letterSpacing,\r\n direction: options.direction === 'inherit' ? 'ltr' : options.direction,\r\n };\r\n}"],"names":["layoutText","options","_ellipsisResult","text","width","containerWidth","height","containerHeight","wrap","align","ellipsis","direction","padding","verticalAlign","lines","totalWidth","totalHeight","isTruncated","graphemeCount","maxWidth","Infinity","maxHeight","paragraphs","split","allLines","maxLineWidth","totalGraphemes","i","length","paragraph","isLastParagraph","paragraphLines","layoutParagraph","line","truncatedResult","handleHeightOverflow","Math","max","map","l","addedGraphemes","ellipsisApplied","ellipsisResult","isLastInParagraph","push","graphemes","applyEllipsis","ellipsisChar","measureFn","measureLineWidth","truncatedLine","layoutSingleLine","truncatedText","Object","assign","alignedLines","applyAlignment","verticalOffset","calculateVerticalOffset","forEach","bounds","bound","y","createEmptyLine","wrapByWords","wrapByCharacters","textOffset","layoutLines","lineText","arguments","undefined","segmentGraphemes","measurementOptions","createMeasurementOptions","x","lineWidth","lineHeight","charIndex","grapheme","prevGrapheme","measurement","measureGraphemeWithKerning","letterSpacing","charSpacing","fontSize","totalSpacing","effectiveWidth","kernedWidth","left","baseline","graphemeIndex","finalHeight","isWrapped","words","currentLine","word","wordWidth","testLine","testWidth","trim","brokenWord","slice","currentWidth","offsetX","applyJustification","spaces","filter","g","test","extraSpace","spaceExpansion","justifyRatio","contentHeight","existingLines","overflowLine","remainingHeight","fontFamily","fontStyle","fontWeight"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"layout.mjs","sources":["../../../src/text/layout.ts"],"sourcesContent":["/**\r\n * Core Text Layout Engine\r\n * \r\n * Implements Konva-compatible text layout with support for:\r\n * - Multiple wrap modes (word/char/none)\r\n * - Ellipsis truncation\r\n * - Justify alignment with proper space distribution\r\n * - RTL/LTR text direction\r\n * - Advanced grapheme handling\r\n */\r\n\r\nimport { graphemeSplit } from '../util/lang_string';\r\nimport type { MeasurementOptions, GraphemeMeasurement, KerningMeasurement } from './measure';\r\nimport { measureGrapheme, measureGraphemeWithKerning, getFontMetrics } from './measure';\r\nimport type { EllipsisResult } from './ellipsis';\r\nimport { applyEllipsis } from './ellipsis';\r\nimport { segmentGraphemes, analyzeBiDi, type BiDiRun } from './unicode';\r\n\r\nexport interface TextLayoutOptions {\r\n text: string;\r\n width?: number;\r\n height?: number;\r\n wrap: 'word' | 'char' | 'none';\r\n align: 'left' | 'center' | 'right' | 'justify';\r\n ellipsis?: boolean | string;\r\n fontSize: number;\r\n lineHeight: number;\r\n letterSpacing?: number; // px-based (Konva style)\r\n charSpacing?: number; // em-based (Fabric style) \r\n direction: 'ltr' | 'rtl' | 'inherit';\r\n fontFamily: string;\r\n fontStyle: string;\r\n fontWeight: string | number;\r\n padding?: number;\r\n verticalAlign?: 'top' | 'middle' | 'bottom';\r\n}\r\n\r\nexport interface LayoutResult {\r\n lines: LayoutLine[];\r\n totalWidth: number;\r\n totalHeight: number;\r\n isTruncated: boolean;\r\n graphemeCount: number;\r\n ellipsisApplied?: EllipsisResult;\r\n}\r\n\r\nexport interface LayoutLine {\r\n text: string;\r\n graphemes: string[];\r\n width: number;\r\n height: number;\r\n bounds: GraphemeBounds[];\r\n isWrapped: boolean;\r\n isLastInParagraph: boolean;\r\n justifyRatio?: number; // For justify alignment - space expansion factor\r\n baseline: number;\r\n}\r\n\r\nexport interface GraphemeBounds {\r\n grapheme: string;\r\n x: number;\r\n y: number;\r\n width: number;\r\n height: number;\r\n kernedWidth: number;\r\n left: number;\r\n baseline: number;\r\n deltaY?: number;\r\n charIndex: number; // Logical character index in original text\r\n graphemeIndex: number; // Logical grapheme index in original text\r\n}\r\n\r\n/**\r\n * Main text layout function - converts text and options into positioned layout\r\n */\r\nexport function layoutText(options: TextLayoutOptions): LayoutResult {\r\n const {\r\n text,\r\n width: containerWidth,\r\n height: containerHeight,\r\n wrap,\r\n align,\r\n ellipsis,\r\n direction,\r\n padding = 0,\r\n verticalAlign = 'top'\r\n } = options;\r\n\r\n // Handle empty text\r\n if (!text) {\r\n return {\r\n lines: [],\r\n totalWidth: 0,\r\n totalHeight: 0,\r\n isTruncated: false,\r\n graphemeCount: 0,\r\n };\r\n }\r\n\r\n // Calculate available space\r\n const maxWidth = containerWidth ? containerWidth - (padding * 2) : Infinity;\r\n const maxHeight = containerHeight ? containerHeight - (padding * 2) : Infinity;\r\n\r\n // Split text into paragraphs (by \\n)\r\n const paragraphs = text.split('\\n');\r\n \r\n // Process each paragraph\r\n const allLines: LayoutLine[] = [];\r\n let totalHeight = 0;\r\n let maxLineWidth = 0;\r\n let totalGraphemes = 0;\r\n\r\n for (let i = 0; i < paragraphs.length; i++) {\r\n const paragraph = paragraphs[i];\r\n const isLastParagraph = i === paragraphs.length - 1;\r\n \r\n // Layout this paragraph\r\n const paragraphLines = layoutParagraph(paragraph, {\r\n ...options,\r\n width: maxWidth,\r\n isLastParagraph,\r\n });\r\n \r\n // Check height constraints\r\n for (const line of paragraphLines) {\r\n if (containerHeight && totalHeight + line.height > maxHeight) {\r\n // Height exceeded - truncate here\r\n const truncatedResult = handleHeightOverflow(\r\n allLines,\r\n line,\r\n maxHeight - totalHeight,\r\n options\r\n );\r\n \r\n return {\r\n lines: truncatedResult.lines,\r\n totalWidth: Math.max(maxLineWidth, ...truncatedResult.lines.map(l => l.width)),\r\n totalHeight: maxHeight,\r\n isTruncated: true,\r\n graphemeCount: totalGraphemes + truncatedResult.addedGraphemes,\r\n ellipsisApplied: truncatedResult.ellipsisResult,\r\n };\r\n }\r\n\r\n // Mark last line in paragraph\r\n if (line === paragraphLines[paragraphLines.length - 1]) {\r\n line.isLastInParagraph = true;\r\n }\r\n\r\n allLines.push(line);\r\n totalHeight += line.height;\r\n maxLineWidth = Math.max(maxLineWidth, line.width);\r\n totalGraphemes += line.graphemes.length;\r\n }\r\n }\r\n\r\n // Apply ellipsis if width exceeded and ellipsis enabled\r\n let ellipsisResult: EllipsisResult | undefined;\r\n if (ellipsis && containerWidth) {\r\n for (const line of allLines) {\r\n if (line.width > maxWidth) {\r\n ellipsisResult = applyEllipsis(line.text, {\r\n maxWidth,\r\n maxHeight: Infinity,\r\n ellipsisChar: typeof ellipsis === 'string' ? ellipsis : '…',\r\n measureFn: (text: string) => measureLineWidth(text, options),\r\n });\r\n \r\n if (ellipsisResult.isTruncated) {\r\n // Rebuild truncated line\r\n const truncatedLine = layoutSingleLine(ellipsisResult.truncatedText, options);\r\n Object.assign(line, truncatedLine);\r\n break;\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Apply alignment\r\n const alignedLines = applyAlignment(allLines, align, maxLineWidth, options);\r\n\r\n // Apply vertical alignment\r\n const verticalOffset = calculateVerticalOffset(\r\n totalHeight,\r\n containerHeight || totalHeight,\r\n verticalAlign\r\n );\r\n\r\n // Adjust line positions for vertical alignment\r\n alignedLines.forEach(line => {\r\n line.bounds.forEach(bound => {\r\n bound.y += verticalOffset;\r\n });\r\n });\r\n\r\n return {\r\n lines: alignedLines,\r\n totalWidth: maxLineWidth,\r\n totalHeight: totalHeight,\r\n isTruncated: !!ellipsisResult?.isTruncated,\r\n graphemeCount: totalGraphemes,\r\n ellipsisApplied: ellipsisResult,\r\n };\r\n}\r\n\r\n/**\r\n * Layout a single paragraph with wrapping\r\n */\r\nfunction layoutParagraph(\r\n text: string, \r\n options: TextLayoutOptions & { width: number; isLastParagraph: boolean }\r\n): LayoutLine[] {\r\n const { wrap, width: maxWidth } = options;\r\n\r\n if (!text) {\r\n // Empty paragraph - create empty line\r\n return [createEmptyLine(options)];\r\n }\r\n\r\n // Handle no wrapping\r\n if (wrap === 'none' || maxWidth === Infinity) {\r\n return [layoutSingleLine(text, options, 0)];\r\n }\r\n\r\n // Apply wrapping\r\n const lines: string[] = [];\r\n \r\n if (wrap === 'word') {\r\n lines.push(...wrapByWords(text, maxWidth, options));\r\n } else if (wrap === 'char') {\r\n lines.push(...wrapByCharacters(text, maxWidth, options));\r\n }\r\n\r\n // Convert wrapped lines to layout lines, tracking text offset\r\n let textOffset = 0;\r\n const layoutLines = lines.map(lineText => {\r\n const line = layoutSingleLine(lineText, options, textOffset);\r\n textOffset += lineText.length + 1; // +1 for newline character\r\n return line;\r\n });\r\n \r\n return layoutLines;\r\n}\r\n\r\n/**\r\n * Layout a single line of text (no wrapping)\r\n */\r\nfunction layoutSingleLine(text: string, options: TextLayoutOptions, textOffset: number = 0): LayoutLine {\r\n const graphemes = segmentGraphemes(text);\r\n const bounds: GraphemeBounds[] = [];\r\n const measurementOptions = createMeasurementOptions(options);\r\n\r\n let x = 0;\r\n let lineWidth = 0;\r\n let lineHeight = 0;\r\n let charIndex = textOffset; // Track character position in original text\r\n\r\n // Measure each grapheme in logical order\r\n for (let i = 0; i < graphemes.length; i++) {\r\n const grapheme = graphemes[i];\r\n const prevGrapheme = i > 0 ? graphemes[i - 1] : undefined;\r\n\r\n // Measure with kerning\r\n const measurement = measureGraphemeWithKerning(\r\n grapheme,\r\n prevGrapheme,\r\n measurementOptions\r\n );\r\n\r\n // Apply letter spacing (Konva style - applied to ALL characters including last)\r\n const letterSpacing = options.letterSpacing || 0;\r\n const charSpacing = options.charSpacing ?\r\n (options.fontSize * options.charSpacing) / 1000 : 0;\r\n\r\n const totalSpacing = letterSpacing + charSpacing;\r\n const effectiveWidth = measurement.kernedWidth + totalSpacing;\r\n\r\n bounds.push({\r\n grapheme,\r\n x, // Will be updated by BiDi reordering\r\n y: 0, // Will be adjusted later\r\n width: measurement.width,\r\n height: measurement.height,\r\n kernedWidth: measurement.kernedWidth,\r\n left: x, // Logical position (cumulative)\r\n baseline: measurement.baseline,\r\n charIndex: charIndex, // Character position in original text\r\n graphemeIndex: textOffset + i, // Grapheme index in original text\r\n });\r\n\r\n // Update character index for next iteration\r\n charIndex += grapheme.length;\r\n\r\n x += effectiveWidth;\r\n lineWidth += effectiveWidth;\r\n lineHeight = Math.max(lineHeight, measurement.height);\r\n }\r\n\r\n // Note: BiDi visual reordering is handled by the browser's canvas fillText\r\n // The layout stores positions in logical order; hit testing handles the visual mapping\r\n\r\n // Remove trailing spacing from total width (but keep in bounds for rendering)\r\n if (bounds.length > 0) {\r\n const letterSpacing = options.letterSpacing || 0;\r\n const charSpacing = options.charSpacing ?\r\n (options.fontSize * options.charSpacing) / 1000 : 0;\r\n const totalSpacing = letterSpacing + charSpacing;\r\n\r\n // Konva applies letterSpacing to all chars, so we don't remove it\r\n // lineWidth -= totalSpacing;\r\n }\r\n\r\n // Apply line height\r\n // Note: Fabric.js uses _fontSizeMult = 1.13 for line height calculation\r\n const fontSizeMult = 1.13;\r\n const finalHeight = lineHeight * options.lineHeight * fontSizeMult;\r\n\r\n return {\r\n text,\r\n graphemes,\r\n width: lineWidth,\r\n height: finalHeight,\r\n bounds,\r\n isWrapped: false,\r\n isLastInParagraph: false,\r\n baseline: finalHeight * 0.8, // Approximate baseline position\r\n };\r\n}\r\n\r\n/**\r\n * Word-based wrapping algorithm\r\n */\r\nfunction wrapByWords(text: string, maxWidth: number, options: TextLayoutOptions): string[] {\r\n const lines: string[] = [];\r\n const words = text.split(/(\\s+)/); // Preserve whitespace\r\n let currentLine = '';\r\n let currentWidth = 0;\r\n\r\n for (let i = 0; i < words.length; i++) {\r\n const word = words[i];\r\n const wordWidth = measureLineWidth(word, options);\r\n const testLine = currentLine ? currentLine + word : word;\r\n const testWidth = measureLineWidth(testLine, options);\r\n\r\n // If adding this word exceeds max width and we have content\r\n if (testWidth > maxWidth && currentLine) {\r\n lines.push(currentLine.trim());\r\n currentLine = word;\r\n currentWidth = wordWidth;\r\n }\r\n // If single word is too long, break it by characters\r\n else if (wordWidth > maxWidth && !currentLine) {\r\n const brokenWord = wrapByCharacters(word, maxWidth, options);\r\n lines.push(...brokenWord.slice(0, -1)); // All but last part\r\n currentLine = brokenWord[brokenWord.length - 1]; // Last part\r\n currentWidth = measureLineWidth(currentLine, options);\r\n }\r\n else {\r\n currentLine = testLine;\r\n currentWidth = testWidth;\r\n }\r\n }\r\n\r\n if (currentLine) {\r\n lines.push(currentLine.trim());\r\n }\r\n\r\n return lines.length > 0 ? lines : [''];\r\n}\r\n\r\n/**\r\n * Character-based wrapping algorithm \r\n */\r\nfunction wrapByCharacters(text: string, maxWidth: number, options: TextLayoutOptions): string[] {\r\n const lines: string[] = [];\r\n const graphemes = segmentGraphemes(text);\r\n let currentLine = '';\r\n \r\n for (const grapheme of graphemes) {\r\n const testLine = currentLine + grapheme;\r\n const testWidth = measureLineWidth(testLine, options);\r\n \r\n if (testWidth > maxWidth && currentLine) {\r\n lines.push(currentLine);\r\n currentLine = grapheme;\r\n } else {\r\n currentLine = testLine;\r\n }\r\n }\r\n \r\n if (currentLine) {\r\n lines.push(currentLine);\r\n }\r\n \r\n return lines.length > 0 ? lines : [''];\r\n}\r\n\r\n/**\r\n * Apply BiDi visual reordering to calculate correct visual X positions\r\n * This implements the Unicode Bidirectional Algorithm for character placement\r\n */\r\nfunction applyBiDiVisualReordering(\r\n line: LayoutLine,\r\n options: TextLayoutOptions\r\n): LayoutLine {\r\n const baseDirection = options.direction === 'inherit' ? 'ltr' : options.direction;\r\n\r\n // Quick check: if all characters are same direction as base, no reordering needed\r\n const runs = analyzeBiDi(line.text, baseDirection);\r\n const hasMixedBiDi = runs.length > 1 || (runs.length === 1 && runs[0].direction !== baseDirection);\r\n\r\n if (!hasMixedBiDi) {\r\n // For pure LTR or pure RTL, just set visual x = logical left\r\n // For RTL base direction, we need to flip positions\r\n if (baseDirection === 'rtl') {\r\n // RTL: rightmost character should be at x=0, leftmost at x=lineWidth\r\n line.bounds.forEach(bound => {\r\n bound.x = line.width - bound.left - bound.kernedWidth;\r\n });\r\n }\r\n // For LTR, x is already correct (same as left)\r\n return line;\r\n }\r\n\r\n // Mixed BiDi text - need to reorder runs visually\r\n // 1. Build mapping from grapheme index to run\r\n const graphemeToRun: number[] = [];\r\n let runGraphemeStart = 0;\r\n\r\n for (let runIdx = 0; runIdx < runs.length; runIdx++) {\r\n const run = runs[runIdx];\r\n const runGraphemes = segmentGraphemes(run.text);\r\n for (let i = 0; i < runGraphemes.length; i++) {\r\n graphemeToRun.push(runIdx);\r\n }\r\n runGraphemeStart += runGraphemes.length;\r\n }\r\n\r\n // 2. Calculate run widths and positions\r\n const runWidths: number[] = [];\r\n const runStartIndices: number[] = [];\r\n let currentIdx = 0;\r\n\r\n for (const run of runs) {\r\n runStartIndices.push(currentIdx);\r\n const runGraphemes = segmentGraphemes(run.text);\r\n let runWidth = 0;\r\n for (let i = 0; i < runGraphemes.length; i++) {\r\n if (currentIdx + i < line.bounds.length) {\r\n const letterSpacing = options.letterSpacing || 0;\r\n const charSpacing = options.charSpacing ?\r\n (options.fontSize * options.charSpacing) / 1000 : 0;\r\n runWidth += line.bounds[currentIdx + i].kernedWidth + letterSpacing + charSpacing;\r\n }\r\n }\r\n runWidths.push(runWidth);\r\n currentIdx += runGraphemes.length;\r\n }\r\n\r\n // 3. Determine visual order of runs based on base direction\r\n // RTL base: runs display right-to-left (first run on right)\r\n // LTR base: runs display left-to-right (first run on left)\r\n const visualRunOrder = runs.map((_, i) => i);\r\n if (baseDirection === 'rtl') {\r\n visualRunOrder.reverse();\r\n }\r\n\r\n // 4. Calculate visual X position for each run\r\n const runVisualX: number[] = new Array(runs.length);\r\n let currentX = 0;\r\n\r\n for (const runIdx of visualRunOrder) {\r\n runVisualX[runIdx] = currentX;\r\n currentX += runWidths[runIdx];\r\n }\r\n\r\n // 5. Assign visual X positions to each grapheme\r\n for (let i = 0; i < line.bounds.length; i++) {\r\n const runIdx = graphemeToRun[i];\r\n if (runIdx === undefined) continue;\r\n\r\n const run = runs[runIdx];\r\n const runStart = runStartIndices[runIdx];\r\n\r\n // Calculate spacing once\r\n const letterSpacing = options.letterSpacing || 0;\r\n const charSpacing = options.charSpacing ?\r\n (options.fontSize * options.charSpacing) / 1000 : 0;\r\n const totalSpacing = letterSpacing + charSpacing;\r\n\r\n // Calculate offset within run (sum of widths of chars before this one)\r\n let offsetInRun = 0;\r\n for (let j = runStart; j < i; j++) {\r\n offsetInRun += line.bounds[j].kernedWidth + totalSpacing;\r\n }\r\n\r\n // Character width including spacing\r\n const charWidth = line.bounds[i].kernedWidth + totalSpacing;\r\n\r\n // For RTL runs, characters within the run are reversed visually\r\n // First logical char appears on the right, last on the left\r\n if (run.direction === 'rtl') {\r\n // Visual X = run right edge - cumulative width including this char\r\n // This places first char at right side of run, last char at left side\r\n line.bounds[i].x = runVisualX[runIdx] + runWidths[runIdx] - offsetInRun - charWidth;\r\n } else {\r\n // LTR run: visual position is run start + offset within run\r\n line.bounds[i].x = runVisualX[runIdx] + offsetInRun;\r\n }\r\n }\r\n\r\n return line;\r\n}\r\n\r\n/**\r\n * Apply text alignment to lines\r\n */\r\nfunction applyAlignment(\r\n lines: LayoutLine[],\r\n align: string,\r\n containerWidth: number,\r\n options: TextLayoutOptions\r\n): LayoutLine[] {\r\n return lines.map(line => {\r\n // First apply BiDi reordering to get correct visual X positions\r\n applyBiDiVisualReordering(line, options);\r\n\r\n let offsetX = 0;\r\n\r\n switch (align) {\r\n case 'center':\r\n offsetX = (containerWidth - line.width) / 2;\r\n break;\r\n case 'right':\r\n offsetX = containerWidth - line.width;\r\n break;\r\n case 'justify':\r\n if (!line.isLastInParagraph && line.graphemes.length > 1) {\r\n return applyJustification(line, containerWidth, options);\r\n }\r\n break;\r\n case 'left':\r\n default:\r\n offsetX = 0;\r\n break;\r\n }\r\n\r\n // Apply offset to all bounds (both visual x and logical left for alignment)\r\n if (offsetX !== 0) {\r\n line.bounds.forEach(bound => {\r\n bound.x += offsetX;\r\n bound.left += offsetX;\r\n });\r\n }\r\n\r\n return line;\r\n });\r\n}\r\n\r\n/**\r\n * Apply justify alignment by expanding spaces\r\n */\r\nfunction applyJustification(\r\n line: LayoutLine, \r\n containerWidth: number, \r\n options: TextLayoutOptions\r\n): LayoutLine {\r\n const spaces = line.graphemes.filter(g => /\\s/.test(g)).length;\r\n if (spaces === 0) return line;\r\n \r\n const extraSpace = containerWidth - line.width;\r\n const spaceExpansion = extraSpace / spaces;\r\n \r\n let offsetX = 0;\r\n line.bounds.forEach(bound => {\r\n bound.x += offsetX;\r\n bound.left += offsetX;\r\n \r\n if (/\\s/.test(bound.grapheme)) {\r\n bound.kernedWidth += spaceExpansion;\r\n bound.width += spaceExpansion;\r\n offsetX += spaceExpansion;\r\n }\r\n });\r\n \r\n line.width = containerWidth;\r\n line.justifyRatio = 1 + (spaceExpansion / (options.fontSize * 0.25)); // Approximate space width\r\n \r\n return line;\r\n}\r\n\r\n/**\r\n * Calculate vertical alignment offset\r\n */\r\nfunction calculateVerticalOffset(\r\n contentHeight: number,\r\n containerHeight: number, \r\n align: 'top' | 'middle' | 'bottom'\r\n): number {\r\n switch (align) {\r\n case 'middle':\r\n return (containerHeight - contentHeight) / 2;\r\n case 'bottom':\r\n return containerHeight - contentHeight;\r\n case 'top':\r\n default:\r\n return 0;\r\n }\r\n}\r\n\r\n/**\r\n * Handle height overflow with ellipsis\r\n */\r\nfunction handleHeightOverflow(\r\n existingLines: LayoutLine[],\r\n overflowLine: LayoutLine,\r\n remainingHeight: number,\r\n options: TextLayoutOptions\r\n): { \r\n lines: LayoutLine[]; \r\n addedGraphemes: number; \r\n ellipsisResult?: EllipsisResult;\r\n} {\r\n // If ellipsis is enabled, try to fit part of the overflow line\r\n if (options.ellipsis && remainingHeight > 0) {\r\n const ellipsisChar = typeof options.ellipsis === 'string' ? options.ellipsis : '…';\r\n const maxWidth = options.width || Infinity;\r\n \r\n const ellipsisResult = applyEllipsis(overflowLine.text, {\r\n maxWidth,\r\n maxHeight: remainingHeight,\r\n ellipsisChar,\r\n measureFn: (text: string) => measureLineWidth(text, options),\r\n });\r\n \r\n if (ellipsisResult.isTruncated) {\r\n const truncatedLine = layoutSingleLine(ellipsisResult.truncatedText, options);\r\n truncatedLine.isLastInParagraph = true;\r\n \r\n return {\r\n lines: [...existingLines, truncatedLine],\r\n addedGraphemes: truncatedLine.graphemes.length,\r\n ellipsisResult,\r\n };\r\n }\r\n }\r\n \r\n return {\r\n lines: existingLines,\r\n addedGraphemes: 0,\r\n };\r\n}\r\n\r\n/**\r\n * Create empty line for empty paragraphs\r\n */\r\nfunction createEmptyLine(options: TextLayoutOptions): LayoutLine {\r\n // Fabric.js uses _fontSizeMult = 1.13 for line height calculation\r\n const fontSizeMult = 1.13;\r\n const height = options.fontSize * options.lineHeight * fontSizeMult;\r\n\r\n return {\r\n text: '',\r\n graphemes: [],\r\n width: 0,\r\n height,\r\n bounds: [],\r\n isWrapped: false,\r\n isLastInParagraph: true,\r\n baseline: height * 0.8,\r\n };\r\n}\r\n\r\n/**\r\n * Measure width of a line of text\r\n */\r\nfunction measureLineWidth(text: string, options: TextLayoutOptions): number {\r\n const graphemes = segmentGraphemes(text);\r\n const measurementOptions = createMeasurementOptions(options);\r\n \r\n let width = 0;\r\n for (let i = 0; i < graphemes.length; i++) {\r\n const grapheme = graphemes[i];\r\n const prevGrapheme = i > 0 ? graphemes[i - 1] : undefined;\r\n \r\n const measurement = measureGraphemeWithKerning(\r\n grapheme,\r\n prevGrapheme, \r\n measurementOptions\r\n );\r\n \r\n const letterSpacing = options.letterSpacing || 0;\r\n const charSpacing = options.charSpacing ? \r\n (options.fontSize * options.charSpacing) / 1000 : 0;\r\n \r\n width += measurement.kernedWidth + letterSpacing + charSpacing;\r\n }\r\n \r\n return width;\r\n}\r\n\r\n/**\r\n * Convert layout options to measurement options\r\n */\r\nfunction createMeasurementOptions(options: TextLayoutOptions): MeasurementOptions {\r\n return {\r\n fontFamily: options.fontFamily,\r\n fontSize: options.fontSize,\r\n fontStyle: options.fontStyle,\r\n fontWeight: options.fontWeight,\r\n letterSpacing: options.letterSpacing,\r\n direction: options.direction === 'inherit' ? 'ltr' : options.direction,\r\n };\r\n}"],"names":["layoutText","options","_ellipsisResult","text","width","containerWidth","height","containerHeight","wrap","align","ellipsis","direction","padding","verticalAlign","lines","totalWidth","totalHeight","isTruncated","graphemeCount","maxWidth","Infinity","maxHeight","paragraphs","split","allLines","maxLineWidth","totalGraphemes","i","length","paragraph","isLastParagraph","paragraphLines","layoutParagraph","line","truncatedResult","handleHeightOverflow","Math","max","map","l","addedGraphemes","ellipsisApplied","ellipsisResult","isLastInParagraph","push","graphemes","applyEllipsis","ellipsisChar","measureFn","measureLineWidth","truncatedLine","layoutSingleLine","truncatedText","Object","assign","alignedLines","applyAlignment","verticalOffset","calculateVerticalOffset","forEach","bounds","bound","y","createEmptyLine","wrapByWords","wrapByCharacters","textOffset","layoutLines","lineText","arguments","undefined","segmentGraphemes","measurementOptions","createMeasurementOptions","x","lineWidth","lineHeight","charIndex","grapheme","prevGrapheme","measurement","measureGraphemeWithKerning","letterSpacing","charSpacing","fontSize","totalSpacing","effectiveWidth","kernedWidth","left","baseline","graphemeIndex","fontSizeMult","finalHeight","isWrapped","words","currentLine","word","wordWidth","testLine","testWidth","trim","brokenWord","slice","currentWidth","applyBiDiVisualReordering","baseDirection","runs","analyzeBiDi","hasMixedBiDi","graphemeToRun","runGraphemeStart","runIdx","run","runGraphemes","runWidths","runStartIndices","currentIdx","runWidth","visualRunOrder","_","reverse","runVisualX","Array","currentX","runStart","offsetInRun","j","charWidth","offsetX","applyJustification","spaces","filter","g","test","extraSpace","spaceExpansion","justifyRatio","contentHeight","existingLines","overflowLine","remainingHeight","fontFamily","fontStyle","fontWeight"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{defineProperty as t}from"../../_virtual/_rollupPluginBabelHelpers.min.mjs";import{transformPoint as e}from"../util/misc/matrix.min.mjs";class o{constructor(e){t(this,"canvas",void 0),t(this,"target",void 0),t(this,"container",void 0),t(this,"textarea",void 0),t(this,"hostDiv",void 0),t(this,"isDestroyed",!1),t(this,"isComposing",!1),t(this,"lastText",void 0),t(this,"onCommit",void 0),t(this,"onCancel",void 0),t(this,"boundHandlers",{onInput:this.handleInput.bind(this),onKeyDown:this.handleKeyDown.bind(this),onBlur:this.handleBlur.bind(this),onCompositionStart:this.handleCompositionStart.bind(this),onCompositionEnd:this.handleCompositionEnd.bind(this),onAfterRender:this.handleAfterRender.bind(this),onMouseWheel:this.handleMouseWheel.bind(this),onFocus:this.handleFocus.bind(this),onMouseDown:this.handleMouseDown.bind(this)}),this.canvas=e.canvas,this.target=e.target,this.onCommit=e.onCommit,this.onCancel=e.onCancel,this.lastText=this.target.text||"",this.container=this.getCanvasContainer(),this.createOverlayDOM(),this.attachEventListeners(),this.refresh(),this.focusTextarea()}getCanvasContainer(){const t=this.canvas.upperCanvasEl.parentElement;if(!t)throw new Error("Canvas must be mounted in DOM to use overlay editing");return t.style.position="relative",t}createOverlayDOM(){this.hostDiv=document.createElement("div"),this.hostDiv.style.position="absolute",this.hostDiv.style.pointerEvents="none",this.hostDiv.style.zIndex="1000",this.hostDiv.style.transformOrigin="left top",this.textarea=document.createElement("textarea"),this.textarea.style.position="absolute",this.textarea.style.left="0",this.textarea.style.top="0",this.textarea.style.margin="0",this.textarea.style.resize="none",this.textarea.style.pointerEvents="auto";const t=/[\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/.test(this.target.text||""),e=/[a-zA-Z]/.test(this.target.text||""),o="ltr"===this.target.direction;this.textarea.style.unicodeBidi=t&&e&&o||t&&o?"embed":"plaintext",this.textarea.style.caretColor="auto",this.textarea.style.border="none",this.textarea.style.padding="0",this.textarea.style.background="transparent",this.textarea.style.outline="none",this.textarea.style.overflow="hidden",this.textarea.style.whiteSpace="pre-wrap",this.textarea.style.wordBreak="normal",this.textarea.style.overflowWrap="break-word",this.textarea.style.userSelect="text",this.textarea.style.textTransform="none",this.textarea.style.opacity="1",this.textarea.value=this.target.text||"",this.hostDiv.appendChild(this.textarea),document.body.appendChild(this.hostDiv)}attachEventListeners(){this.textarea.addEventListener("input",this.boundHandlers.onInput),this.textarea.addEventListener("keydown",this.boundHandlers.onKeyDown),this.textarea.addEventListener("blur",this.boundHandlers.onBlur),this.textarea.addEventListener("compositionstart",this.boundHandlers.onCompositionStart),this.textarea.addEventListener("compositionend",this.boundHandlers.onCompositionEnd),this.textarea.addEventListener("focus",this.boundHandlers.onFocus),this.canvas.on("after:render",this.boundHandlers.onAfterRender),this.canvas.on("mouse:wheel",this.boundHandlers.onMouseWheel),this.canvas.on("mouse:down",this.boundHandlers.onMouseDown),this.setupViewportChangeDetection()}removeEventListeners(){this.textarea.removeEventListener("input",this.boundHandlers.onInput),this.textarea.removeEventListener("keydown",this.boundHandlers.onKeyDown),this.textarea.removeEventListener("blur",this.boundHandlers.onBlur),this.textarea.removeEventListener("compositionstart",this.boundHandlers.onCompositionStart),this.textarea.removeEventListener("compositionend",this.boundHandlers.onCompositionEnd),this.textarea.removeEventListener("focus",this.boundHandlers.onFocus),this.canvas.off("after:render",this.boundHandlers.onAfterRender),this.canvas.off("mouse:wheel",this.boundHandlers.onMouseWheel),this.canvas.off("mouse:down",this.boundHandlers.onMouseDown),this.restoreViewportChangeDetection()}updatePosition(){this.applyOverlayStyle()}updateObjectBounds(){if(this.isDestroyed)return;const t=this.target,e=this.canvas.getZoom();parseFloat(this.hostDiv.style.width);const o=parseFloat(this.hostDiv.style.height)/e;Math.abs(o-t.height)>.5&&(t.height,t.height=o,t.setCoords(),t.dirty=!0,this.canvas.requestRenderAll(),requestAnimationFrame(()=>{this.isDestroyed||(this.applyOverlayStyle(),console.log("📐 Height changed - rechecking alignment after repositioning:"))}))}letterSpacingPx(t,e){return t/1e3*e}firstStrongDir(t){return/[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/.test(t)?"rtl":"ltr"}applyOverlayStyle(){var t,o;const s=this.target,i=this.canvas;s.setCoords();const a=s.aCoords,n=i.upperCanvasEl.getBoundingClientRect(),r=window.scrollX||window.pageXOffset,l=window.scrollY||window.pageYOffset,h=i.getZoom(),c=i.viewportTransform,d=s.padding||0,g=d*(s.scaleX||1)*h,u=d*(s.scaleY||1)*h,x=e({x:a.tl.x,y:a.tl.y},c),y=n.left+r+x.x,f=n.top+l+x.y,p=s.getBoundingRect(),v=Math.round(p.width*h),m=Math.round(p.height*h);this.hostDiv.style.position="absolute",this.hostDiv.style.left=`${y}px`,this.hostDiv.style.top=`${f}px`,this.hostDiv.style.width=`${v}px`,this.hostDiv.style.height=`${m}px`,this.hostDiv.style.overflow="hidden",s.angle?(this.hostDiv.style.transform=`rotate(${s.angle}deg)`,this.hostDiv.style.transformOrigin="top left"):(this.hostDiv.style.transform="",this.hostDiv.style.transformOrigin="");const w=(null!==(t=s.fontSize)&&void 0!==t?t:16)*(s.scaleX||1)*h,b=s.lineHeight||1.16;this.textarea.style.boxSizing="border-box",this.textarea.style.width=`${v}px`,this.textarea.style.height="100%",this.textarea.style.padding=`${u}px ${g}px`;const D=(s.charSpacing||0)/1e3*w;!1!==s.dirty&&s.initDimensions&&(console.log("🔧 Ensuring text object is properly initialized before overlay editing"),s.initDimensions()),this.textarea.style.fontSize=`${w}px`,this.textarea.style.lineHeight=String(b),this.textarea.style.fontFamily=s.fontFamily||"Arial",this.textarea.style.fontWeight=String(s.fontWeight||"normal"),this.textarea.style.fontStyle=s.fontStyle||"normal";const F=s.textAlign||"left";let S=F;const C=this.firstStrongDir(this.textarea.value||"");if(console.log("🔍 ALIGNMENT DEBUG:"),console.log(" Fabric textAlign:",F),console.log(" Fabric direction:",s.direction),console.log(" Text content:",JSON.stringify(s.text)),console.log(" Detected direction:",C),F.includes("justify"))try{S="justify","justify"===F?this.textarea.style.textAlignLast="rtl"===C?"right":"left":"justify-left"===F?"rtl"===C?(this.textarea.style.textAlignLast="right",console.log(" → Overrode justify-left to justify-right for RTL text")):this.textarea.style.textAlignLast="left":"justify-right"===F?"ltr"===C?(this.textarea.style.textAlignLast="left",console.log(" → Overrode justify-right to justify-left for LTR text")):this.textarea.style.textAlignLast="right":"justify-center"===F&&(this.textarea.style.textAlignLast="center"),this.textarea.style.textJustify="inter-word",this.textarea.style.wordSpacing="normal",this.textarea.style.textAlign="justify",this.textarea.style.textAlignLast=this.textarea.style.textAlignLast,this.textarea.style.textJustifyTrim="none",this.textarea.style.textAutospace="none",console.log(" → Applied justify alignment:",F,"with last-line:",this.textarea.style.textAlignLast)}catch(t){console.warn(" → Justify setup failed, falling back to standard alignment:",t),S=F.replace("justify-","").replace("justify","left")}else this.textarea.style.textAlignLast="auto",this.textarea.style.textJustify="auto",this.textarea.style.wordSpacing="normal",console.log(" → Applied standard alignment:",S);this.textarea.style.textAlign=S,this.textarea.style.color=(null===(o=s.fill)||void 0===o?void 0:o.toString())||"#000",this.textarea.style.letterSpacing=`${D}px`;const E=s.direction;var A,T;(this.textarea.style.direction=C||E||"ltr",this.textarea.style.fontVariant="normal",this.textarea.style.fontStretch="normal",this.textarea.style.textRendering="auto",this.textarea.style.fontKerning="normal",this.textarea.style.fontFeatureSettings="normal",this.textarea.style.fontVariationSettings="normal",this.textarea.style.margin="0",this.textarea.style.border="none",this.textarea.style.outline="none",this.textarea.style.background="transparent",this.textarea.style.overflowWrap="break-word",this.textarea.style.whiteSpace="pre-wrap",this.textarea.style.hyphens="none",console.log("🎨 FINAL TEXTAREA CSS:"),console.log(" textAlign:",this.textarea.style.textAlign),console.log(" textAlignLast:",this.textarea.style.textAlignLast),console.log(" direction:",this.textarea.style.direction),console.log(" unicodeBidi:",this.textarea.style.unicodeBidi),console.log(" width:",this.textarea.style.width),console.log(" textJustify:",this.textarea.style.textJustify),console.log(" wordSpacing:",this.textarea.style.wordSpacing),console.log(" whiteSpace:",this.textarea.style.whiteSpace),F.includes("justify"))&&(console.log("🔧 FABRIC OBJECT JUSTIFY INFO:"),console.log(" Fabric width:",s.width),console.log(" Fabric calcTextWidth:",null===(A=(T=s).calcTextWidth)||void 0===A?void 0:A.call(T)),console.log(" Fabric textAlign:",s.textAlign),console.log(" Text lines:",s.textLines));console.log("🔤 FONT PROPERTIES COMPARISON:"),console.log(" Fabric fontFamily:",s.fontFamily),console.log(" Fabric fontWeight:",s.fontWeight),console.log(" Fabric fontStyle:",s.fontStyle),console.log(" Fabric fontSize:",s.fontSize),console.log(" → Textarea fontFamily:",this.textarea.style.fontFamily),console.log(" → Textarea fontWeight:",this.textarea.style.fontWeight),console.log(" → Textarea fontStyle:",this.textarea.style.fontStyle),console.log(" → Textarea fontSize:",this.textarea.style.fontSize),console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"),this.textarea.style.webkitFontSmoothing="auto",this.textarea.style.mozOsxFontSmoothing="auto",this.textarea.style.fontSmooth="auto",this.textarea.style.textSizeAdjust="none";const M=String(s.fontWeight||"normal");("bold"===M||"700"===M||parseInt(M)>=600)&&(this.textarea.style.webkitFontSmoothing="subpixel-antialiased",this.textarea.style.mozOsxFontSmoothing="unset",console.log("🔤 Applied enhanced bold rendering for better thickness matching")),console.log("🎨 FONT SMOOTHING APPLIED:"),console.log(" webkitFontSmoothing:",this.textarea.style.webkitFontSmoothing),console.log(" mozOsxFontSmoothing:",this.textarea.style.mozOsxFontSmoothing)}debugBoundingBoxComparison(){const t=this.target,e=this.canvas,o=e.getZoom(),s=this.textarea.getBoundingClientRect(),i=this.hostDiv.getBoundingClientRect(),a=t.getBoundingRect(),n=e.upperCanvasEl.getBoundingClientRect(),r=e.viewportTransform,l=n.left+a.left*o+r[4],h=n.top+a.top*o+r[5],c=a.width*o,d=a.height*o;console.log("🔍 BOUNDING BOX COMPARISON:"),console.log("📦 Textarea Rect:",{left:Math.round(100*s.left)/100,top:Math.round(100*s.top)/100,width:Math.round(100*s.width)/100,height:Math.round(100*s.height)/100}),console.log("📦 Host Div Rect:",{left:Math.round(100*i.left)/100,top:Math.round(100*i.top)/100,width:Math.round(100*i.width)/100,height:Math.round(100*i.height)/100}),console.log("📦 Canvas Object Bounds (screen):",{left:Math.round(100*l)/100,top:Math.round(100*h)/100,width:Math.round(100*c)/100,height:Math.round(100*d)/100}),console.log("📦 Canvas Object Bounds (canvas):",a);const g={leftDiff:Math.round(100*(i.left-l))/100,topDiff:Math.round(100*(i.top-h))/100,widthDiff:Math.round(100*(i.width-c))/100,heightDiff:Math.round(100*(i.height-d))/100},u={leftDiff:Math.round(100*(s.left-l))/100,topDiff:Math.round(100*(s.top-h))/100,widthDiff:Math.round(100*(s.width-c))/100,heightDiff:Math.round(100*(s.height-d))/100};console.log("📏 Host Div vs Canvas Object Diff:",g),console.log("📏 Textarea vs Canvas Object Diff:",u);const x=Math.abs(g.leftDiff)<2&&Math.abs(g.topDiff)<2&&Math.abs(g.widthDiff)<2&&Math.abs(g.heightDiff)<2,y=Math.abs(u.leftDiff)<2&&Math.abs(u.topDiff)<2&&Math.abs(u.widthDiff)<2&&Math.abs(u.heightDiff)<2;console.log(x?"✅ Host Div ALIGNED with canvas object":"❌ Host Div MISALIGNED with canvas object"),console.log(y?"✅ Textarea ALIGNED with canvas object":"❌ Textarea MISALIGNED with canvas object"),console.log("🔍 Zoom:",o,"Viewport Transform:",r),console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")}debugTextWrapping(){const t=this.target,e=this.textarea.value;console.log("📝 TEXT WRAPPING COMPARISON:"),console.log("📄 Text Content:",`"${e}"`),console.log("📄 Text Length:",e.length);const o=e.split("\n");console.log("📄 Explicit Lines (\\n):",o.length),o.forEach((t,e)=>{console.log(` Line ${e+1}: "${t}" (${t.length} chars)`)});const s=window.getComputedStyle(this.textarea);console.log("📐 Textarea Wrapping Styles:"),console.log(" width:",s.width),console.log(" fontSize:",s.fontSize),console.log(" fontFamily:",s.fontFamily),console.log(" fontWeight:",s.fontWeight),console.log(" letterSpacing:",s.letterSpacing),console.log(" lineHeight:",s.lineHeight),console.log(" whiteSpace:",s.whiteSpace),console.log(" wordWrap:",s.wordWrap),console.log(" overflowWrap:",s.overflowWrap),console.log(" direction:",s.direction),console.log(" textAlign:",s.textAlign),console.log("📐 Fabric Text Object Properties:"),console.log(" width:",t.width),console.log(" fontSize:",t.fontSize),console.log(" fontFamily:",t.fontFamily),console.log(" fontWeight:",t.fontWeight),console.log(" charSpacing:",t.charSpacing),console.log(" lineHeight:",t.lineHeight),console.log(" direction:",t.direction),console.log(" textAlign:",t.textAlign),console.log(" scaleX:",t.scaleX),console.log(" scaleY:",t.scaleY);const i=this.target.getBoundingRect().width,a=parseFloat(window.getComputedStyle(this.textarea).width)/this.canvas.getZoom(),n=Math.abs(a-i);console.log("📏 Effective Width Comparison:"),console.log(" Textarea Effective Width:",a),console.log(" Fabric Effective Width:",i),console.log(" Width Difference:",n.toFixed(2)+"px"),console.log(n<1?"✅ Widths MATCH for wrapping":"❌ Width MISMATCH may cause different wrapping");const r=/[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/.test(e),l=/[\u0590-\u06FF]/.test(e)&&/[a-zA-Z]/.test(e);console.log("🌍 Text Direction Analysis:"),console.log(" Has RTL characters:",r),console.log(" Has mixed Bidi text:",l),console.log(" Textarea direction:",s.direction),console.log(" Fabric direction:",t.direction||"auto"),console.log(" Textarea unicodeBidi:",s.unicodeBidi);const h=this.textarea.scrollHeight,c=parseFloat(s.lineHeight)||1.2*parseFloat(s.fontSize),d=Math.round(h/c);console.log("📊 Line Count Analysis:"),console.log(" Textarea scrollHeight:",h),console.log(" Textarea lineHeight:",c),console.log(" Estimated rendered lines:",d),console.log(" Explicit line breaks:",o.length),d>o.length?(console.log("🔄 Text wrapping detected in textarea"),console.log(" Wrapped lines:",d-o.length)):console.log("📏 No text wrapping in textarea"),console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")}focusTextarea(){this.target.opacity=.01,this.target.selected=!0,this.target.isEditing=!1,this.target.set({hasControls:!0,hasBorders:!0,selectable:!0,lockMovementX:!1,lockMovementY:!1}),this.canvas.setActiveObject(this.target),this.canvas.requestRenderAll(),this.target.setCoords(),this.applyOverlayStyle(),this.target._fixCharacterMappingAfterJsonLoad&&this.target._fixCharacterMappingAfterJsonLoad(),this.textarea.focus(),this.textarea.setSelectionRange(this.textarea.value.length,this.textarea.value.length),this.canvas.setActiveObject(this.target),this.canvas.requestRenderAll()}refresh(){this.isDestroyed||this.updatePosition()}destroy(){let t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];if(!this.isDestroyed){if(this.isDestroyed=!0,this.removeEventListeners(),this.target.__overlayEditor===this&&(this.target.__overlayEditor=void 0,void 0!==this.target.__overlayOriginalOpacity&&(this.target.opacity=this.target.__overlayOriginalOpacity,delete this.target.__overlayOriginalOpacity)),this.hostDiv.parentNode&&this.hostDiv.parentNode.removeChild(this.hostDiv),t&&!this.isComposing){const t=this.textarea.value,e=this.firstStrongDir(t),o=this.target.direction||"ltr";!("inherit"!==o)&&e&&e!==o?(console.log(`🔄 Overlay Exit: Auto-detected direction change from "${o}" to "${e}"`),console.log(` Text content: "${t.substring(0,50)}..."`),this.target.set("direction",e),this.canvas.requestRenderAll(),console.log(`✅ Fabric object direction updated to: ${e}`)):console.log(`📝 Overlay Exit: Direction unchanged (${o}), text: "${t.substring(0,30)}..."`),this.onCommit&&this.onCommit(t)}else!t&&this.onCancel&&this.onCancel();setTimeout(()=>{this.canvas.upperCanvasEl.style.cursor="",this.canvas.setCursor(this.canvas.defaultCursor)},0),this.canvas.requestRenderAll()}}handleInput(){this.isComposing||this.target.text===this.textarea.value||(this.target.text=this.textarea.value,this.autoResizeTextarea(),this.target.selected=!0,this.target.isEditing=!1,this.canvas.setActiveObject(this.target),this.canvas.requestRenderAll())}autoResizeTextarea(){const t=this.textarea.scrollTop,e=parseFloat(this.hostDiv.style.height||"0");this.textarea.style.height="1px";const o=this.textarea.scrollHeight+2;Math.abs(o-e)>1?(this.textarea.style.height=`${o}px`,this.hostDiv.style.height=`${o}px`,this.updateObjectBounds()):this.textarea.style.height=this.hostDiv.style.height,this.textarea.scrollTop=t}handleKeyDown(t){"Escape"===t.key?(t.preventDefault(),this.destroy(!1)):(t.ctrlKey||t.metaKey)&&"Enter"===t.key?(t.preventDefault(),this.destroy(!0)):"Enter"!==t.key&&"Backspace"!==t.key&&"Delete"!==t.key||(requestAnimationFrame(()=>{this.isDestroyed||this.autoResizeTextarea()}),setTimeout(()=>{this.isDestroyed||this.autoResizeTextarea()},10))}handleFocus(){}handleBlur(){this.isComposing||this.destroy(!0)}handleCompositionStart(){this.isComposing=!0}handleCompositionEnd(){this.isComposing=!1,this.handleInput()}handleAfterRender(){this.refresh()}handleMouseWheel(){this.refresh()}handleMouseDown(t){t.target!==this.target&&this.destroy(!0)}setupViewportChangeDetection(){this.canvas.__originalSetZoom=this.canvas.setZoom,this.canvas.__originalSetViewportTransform=this.canvas.setViewportTransform,this.canvas.__overlayEditor=this;const t=this.canvas.setZoom.bind(this.canvas);this.canvas.setZoom=e=>{const o=t(e);return this.canvas.__overlayEditor&&!this.isDestroyed&&this.refresh(),o};const e=this.canvas.setViewportTransform.bind(this.canvas);this.canvas.setViewportTransform=t=>{const o=e(t);return this.canvas.__overlayEditor&&!this.isDestroyed&&this.refresh(),o}}restoreViewportChangeDetection(){this.canvas.__originalSetZoom&&(this.canvas.setZoom=this.canvas.__originalSetZoom,delete this.canvas.__originalSetZoom),this.canvas.__originalSetViewportTransform&&(this.canvas.setViewportTransform=this.canvas.__originalSetViewportTransform,delete this.canvas.__originalSetViewportTransform),delete this.canvas.__overlayEditor}}function s(t,e,s){e.__overlayEditor&&e.__overlayEditor.destroy(!1),e.__overlayOriginalOpacity=e.opacity;const i=new o({canvas:t,target:e,onCommit:null==s?void 0:s.onCommit,onCancel:null==s?void 0:s.onCancel});return e.__overlayEditor=i,i}export{o as OverlayEditor,s as enterTextOverlayEdit};
|
|
1
|
+
import{defineProperty as t}from"../../_virtual/_rollupPluginBabelHelpers.min.mjs";import{transformPoint as e}from"../util/misc/matrix.min.mjs";class s{constructor(e){t(this,"canvas",void 0),t(this,"target",void 0),t(this,"container",void 0),t(this,"textarea",void 0),t(this,"hostDiv",void 0),t(this,"isDestroyed",!1),t(this,"isComposing",!1),t(this,"lastText",void 0),t(this,"onCommit",void 0),t(this,"onCancel",void 0),t(this,"boundHandlers",{onInput:this.handleInput.bind(this),onKeyDown:this.handleKeyDown.bind(this),onBlur:this.handleBlur.bind(this),onCompositionStart:this.handleCompositionStart.bind(this),onCompositionEnd:this.handleCompositionEnd.bind(this),onAfterRender:this.handleAfterRender.bind(this),onMouseWheel:this.handleMouseWheel.bind(this),onFocus:this.handleFocus.bind(this),onMouseDown:this.handleMouseDown.bind(this)}),this.canvas=e.canvas,this.target=e.target,this.onCommit=e.onCommit,this.onCancel=e.onCancel,this.lastText=this.target.text||"",this.container=this.getCanvasContainer(),this.createOverlayDOM(),this.attachEventListeners(),this.refresh(),this.focusTextarea()}getCanvasContainer(){const t=this.canvas.upperCanvasEl.parentElement;if(!t)throw new Error("Canvas must be mounted in DOM to use overlay editing");return t.style.position="relative",t}createOverlayDOM(){this.hostDiv=document.createElement("div"),this.hostDiv.style.position="absolute",this.hostDiv.style.pointerEvents="none",this.hostDiv.style.zIndex="1000",this.hostDiv.style.transformOrigin="left top",this.textarea=document.createElement("textarea"),this.textarea.style.position="absolute",this.textarea.style.left="0",this.textarea.style.top="0",this.textarea.style.margin="0",this.textarea.style.resize="none",this.textarea.style.pointerEvents="auto";const t=/[\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/.test(this.target.text||""),e=/[a-zA-Z]/.test(this.target.text||""),s="ltr"===this.target.direction;this.textarea.style.unicodeBidi=t&&e&&s||t&&s?"embed":"plaintext",this.textarea.style.caretColor="auto",this.textarea.style.border="none",this.textarea.style.padding="0",this.textarea.style.background="transparent",this.textarea.style.outline="none",this.textarea.style.overflow="hidden",this.textarea.style.whiteSpace="pre-wrap",this.textarea.style.wordBreak="normal",this.textarea.style.overflowWrap="break-word",this.textarea.style.userSelect="text",this.textarea.style.textTransform="none",this.textarea.style.opacity="1",this.textarea.value=this.target.text||"",this.hostDiv.appendChild(this.textarea),document.body.appendChild(this.hostDiv)}attachEventListeners(){this.textarea.addEventListener("input",this.boundHandlers.onInput),this.textarea.addEventListener("keydown",this.boundHandlers.onKeyDown),this.textarea.addEventListener("blur",this.boundHandlers.onBlur),this.textarea.addEventListener("compositionstart",this.boundHandlers.onCompositionStart),this.textarea.addEventListener("compositionend",this.boundHandlers.onCompositionEnd),this.textarea.addEventListener("focus",this.boundHandlers.onFocus),this.canvas.on("after:render",this.boundHandlers.onAfterRender),this.canvas.on("mouse:wheel",this.boundHandlers.onMouseWheel),this.canvas.on("mouse:down",this.boundHandlers.onMouseDown),this.setupViewportChangeDetection()}removeEventListeners(){this.textarea.removeEventListener("input",this.boundHandlers.onInput),this.textarea.removeEventListener("keydown",this.boundHandlers.onKeyDown),this.textarea.removeEventListener("blur",this.boundHandlers.onBlur),this.textarea.removeEventListener("compositionstart",this.boundHandlers.onCompositionStart),this.textarea.removeEventListener("compositionend",this.boundHandlers.onCompositionEnd),this.textarea.removeEventListener("focus",this.boundHandlers.onFocus),this.canvas.off("after:render",this.boundHandlers.onAfterRender),this.canvas.off("mouse:wheel",this.boundHandlers.onMouseWheel),this.canvas.off("mouse:down",this.boundHandlers.onMouseDown),this.restoreViewportChangeDetection()}updatePosition(){this.applyOverlayStyle()}updateObjectBounds(){if(this.isDestroyed)return;const t=this.target,e=this.canvas.getZoom();parseFloat(this.hostDiv.style.width);const s=parseFloat(this.hostDiv.style.height)/e;Math.abs(s-t.height)>.5&&(t.height,t.height=s,t.setCoords(),t.dirty=!0,this.canvas.requestRenderAll(),requestAnimationFrame(()=>{this.isDestroyed||this.applyOverlayStyle()}))}letterSpacingPx(t,e){return t/1e3*e}firstStrongDir(t){return/[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/.test(t)?"rtl":"ltr"}applyOverlayStyle(){var t,s;const i=this.target,a=this.canvas;i.setCoords();const o=i.aCoords,n=a.upperCanvasEl.getBoundingClientRect(),r=window.scrollX||window.pageXOffset,h=window.scrollY||window.pageYOffset,l=a.getZoom(),d=a.viewportTransform,u=i.padding||0,c=u*(i.scaleX||1)*l,y=u*(i.scaleY||1)*l,v=e({x:o.tl.x,y:o.tl.y},d),g=n.left+r+v.x,p=n.top+h+v.y,x=i.getBoundingRect(),m=Math.round(x.width*l),f=Math.round(x.height*l);this.hostDiv.style.position="absolute",this.hostDiv.style.left=`${g}px`,this.hostDiv.style.top=`${p}px`,this.hostDiv.style.width=`${m}px`,this.hostDiv.style.height=`${f}px`,this.hostDiv.style.overflow="hidden",i.angle?(this.hostDiv.style.transform=`rotate(${i.angle}deg)`,this.hostDiv.style.transformOrigin="top left"):(this.hostDiv.style.transform="",this.hostDiv.style.transformOrigin="");const w=(null!==(t=i.fontSize)&&void 0!==t?t:16)*(i.scaleX||1)*l,D=i.lineHeight||1.16;this.textarea.style.boxSizing="border-box",this.textarea.style.width=`${m}px`,this.textarea.style.height="100%",this.textarea.style.padding=`${y}px ${c}px`;const C=(i.charSpacing||0)/1e3*w;!1!==i.dirty&&i.initDimensions&&i.initDimensions(),this.textarea.style.fontSize=`${w}px`,this.textarea.style.lineHeight=String(D),this.textarea.style.fontFamily=i.fontFamily||"Arial",this.textarea.style.fontWeight=String(i.fontWeight||"normal"),this.textarea.style.fontStyle=i.fontStyle||"normal";const b=i.textAlign||"left";let E=b;const S=this.firstStrongDir(this.textarea.value||"");if(b.includes("justify"))try{E="justify","justify"===b||"justify-left"===b?this.textarea.style.textAlignLast="rtl"===S?"right":"left":"justify-right"===b?this.textarea.style.textAlignLast="ltr"===S?"left":"right":"justify-center"===b&&(this.textarea.style.textAlignLast="center"),this.textarea.style.textJustify="inter-word",this.textarea.style.wordSpacing="normal",this.textarea.style.textAlign="justify",this.textarea.style.textAlignLast=this.textarea.style.textAlignLast,this.textarea.style.textJustifyTrim="none",this.textarea.style.textAutospace="none"}catch(t){E=b.replace("justify-","").replace("justify","left")}else this.textarea.style.textAlignLast="auto",this.textarea.style.textJustify="auto",this.textarea.style.wordSpacing="normal";this.textarea.style.textAlign=E,this.textarea.style.color=(null===(s=i.fill)||void 0===s?void 0:s.toString())||"#000",this.textarea.style.letterSpacing=`${C}px`;const _=i.direction;this.textarea.style.direction=S||_||"ltr",this.textarea.style.fontVariant="normal",this.textarea.style.fontStretch="normal",this.textarea.style.textRendering="auto",this.textarea.style.fontKerning="normal",this.textarea.style.fontFeatureSettings="normal",this.textarea.style.fontVariationSettings="normal",this.textarea.style.margin="0",this.textarea.style.border="none",this.textarea.style.outline="none",this.textarea.style.background="transparent",this.textarea.style.overflowWrap="break-word",this.textarea.style.whiteSpace="pre-wrap",this.textarea.style.hyphens="none",b.includes("justify"),this.textarea.style.webkitFontSmoothing="auto",this.textarea.style.mozOsxFontSmoothing="auto",this.textarea.style.fontSmooth="auto",this.textarea.style.textSizeAdjust="none";const F=String(i.fontWeight||"normal");("bold"===F||"700"===F||parseInt(F)>=600)&&(this.textarea.style.webkitFontSmoothing="subpixel-antialiased",this.textarea.style.mozOsxFontSmoothing="unset")}debugBoundingBoxComparison(){const t=this.target,e=this.canvas,s=e.getZoom(),i=this.textarea.getBoundingClientRect(),a=this.hostDiv.getBoundingClientRect(),o=t.getBoundingRect(),n=e.upperCanvasEl.getBoundingClientRect(),r=e.viewportTransform,h=n.left+o.left*s+r[4],l=n.top+o.top*s+r[5],d=o.width*s,u=o.height*s;Math.round(100*(a.left-h)),Math.round(100*(a.top-l)),Math.round(100*(a.width-d)),Math.round(100*(a.height-u)),Math.round(100*(i.left-h)),Math.round(100*(i.top-l)),Math.round(100*(i.width-d)),Math.round(100*(i.height-u))}debugTextWrapping(){this.target;const t=this.textarea.value.split("\n");t.forEach((t,e)=>{});const e=window.getComputedStyle(this.textarea);this.target.getBoundingRect().width;parseFloat(window.getComputedStyle(this.textarea).width);this.canvas.getZoom();const s=this.textarea.scrollHeight,i=parseFloat(e.lineHeight)||1.2*parseFloat(e.fontSize);Math.round(s/i);t.length}focusTextarea(){this.target.opacity=.01,this.target.selected=!0,this.target.isEditing=!1,this.target.set({hasControls:!0,hasBorders:!0,selectable:!0,lockMovementX:!1,lockMovementY:!1}),this.canvas.setActiveObject(this.target),this.canvas.requestRenderAll(),this.target.setCoords(),this.applyOverlayStyle(),this.target._fixCharacterMappingAfterJsonLoad&&this.target._fixCharacterMappingAfterJsonLoad(),this.textarea.focus(),this.textarea.setSelectionRange(this.textarea.value.length,this.textarea.value.length),this.canvas.setActiveObject(this.target),this.canvas.requestRenderAll()}refresh(){this.isDestroyed||this.updatePosition()}destroy(){let t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];if(!this.isDestroyed){if(this.isDestroyed=!0,this.removeEventListeners(),this.target.__overlayEditor===this&&(this.target.__overlayEditor=void 0,void 0!==this.target.__overlayOriginalOpacity&&(this.target.opacity=this.target.__overlayOriginalOpacity,delete this.target.__overlayOriginalOpacity)),this.hostDiv.parentNode&&this.hostDiv.parentNode.removeChild(this.hostDiv),t&&!this.isComposing){const t=this.textarea.value,e=this.firstStrongDir(t),s=this.target.direction||"ltr";!("inherit"!==s)&&e&&e!==s&&(this.target.set("direction",e),this.canvas.requestRenderAll()),this.onCommit&&this.onCommit(t)}else!t&&this.onCancel&&this.onCancel();setTimeout(()=>{this.canvas.upperCanvasEl.style.cursor="",this.canvas.setCursor(this.canvas.defaultCursor)},0),this.canvas.requestRenderAll()}}handleInput(){this.isComposing||this.target.text===this.textarea.value||(this.target.text=this.textarea.value,this.autoResizeTextarea(),this.target.selected=!0,this.target.isEditing=!1,this.canvas.setActiveObject(this.target),this.canvas.requestRenderAll())}autoResizeTextarea(){const t=this.textarea.scrollTop,e=parseFloat(this.hostDiv.style.height||"0");this.textarea.style.height="1px";const s=this.textarea.scrollHeight+2;Math.abs(s-e)>1?(this.textarea.style.height=`${s}px`,this.hostDiv.style.height=`${s}px`,this.updateObjectBounds()):this.textarea.style.height=this.hostDiv.style.height,this.textarea.scrollTop=t}handleKeyDown(t){"Escape"===t.key?(t.preventDefault(),this.destroy(!1)):(t.ctrlKey||t.metaKey)&&"Enter"===t.key?(t.preventDefault(),this.destroy(!0)):"Enter"!==t.key&&"Backspace"!==t.key&&"Delete"!==t.key||(requestAnimationFrame(()=>{this.isDestroyed||this.autoResizeTextarea()}),setTimeout(()=>{this.isDestroyed||this.autoResizeTextarea()},10))}handleFocus(){}handleBlur(){this.isComposing||this.destroy(!0)}handleCompositionStart(){this.isComposing=!0}handleCompositionEnd(){this.isComposing=!1,this.handleInput()}handleAfterRender(){this.refresh()}handleMouseWheel(){this.refresh()}handleMouseDown(t){t.target!==this.target&&this.destroy(!0)}setupViewportChangeDetection(){this.canvas.__originalSetZoom=this.canvas.setZoom,this.canvas.__originalSetViewportTransform=this.canvas.setViewportTransform,this.canvas.__overlayEditor=this;const t=this.canvas.setZoom.bind(this.canvas);this.canvas.setZoom=e=>{const s=t(e);return this.canvas.__overlayEditor&&!this.isDestroyed&&this.refresh(),s};const e=this.canvas.setViewportTransform.bind(this.canvas);this.canvas.setViewportTransform=t=>{const s=e(t);return this.canvas.__overlayEditor&&!this.isDestroyed&&this.refresh(),s}}restoreViewportChangeDetection(){this.canvas.__originalSetZoom&&(this.canvas.setZoom=this.canvas.__originalSetZoom,delete this.canvas.__originalSetZoom),this.canvas.__originalSetViewportTransform&&(this.canvas.setViewportTransform=this.canvas.__originalSetViewportTransform,delete this.canvas.__originalSetViewportTransform),delete this.canvas.__overlayEditor}}function i(t,e,i){e.__overlayEditor&&e.__overlayEditor.destroy(!1),e.__overlayOriginalOpacity=e.opacity;const a=new s({canvas:t,target:e,onCommit:null==i?void 0:i.onCommit,onCancel:null==i?void 0:i.onCancel});return e.__overlayEditor=a,a}export{s as OverlayEditor,i as enterTextOverlayEdit};
|
|
2
2
|
//# sourceMappingURL=overlayEditor.min.mjs.map
|