@shotstack/shotstack-canvas 2.0.14 → 2.0.15

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.
@@ -1377,17 +1377,45 @@ function mirrorAnimationDirection(direction, isRTL) {
1377
1377
  }
1378
1378
 
1379
1379
  // src/core/layout.ts
1380
+ var import_bidi_js2 = __toESM(require("bidi-js"), 1);
1380
1381
  function isEmoji(char) {
1381
1382
  const code = char.codePointAt(0);
1382
1383
  if (!code) return false;
1383
- return code >= 127744 && code <= 129535 || // Emoticons, symbols, pictographs
1384
- code >= 9728 && code <= 9983 || // Miscellaneous symbols
1385
- code >= 9984 && code <= 10175 || // Dingbats
1386
- code >= 65024 && code <= 65039 || // Variation selectors
1387
- code >= 128512 && code <= 128591 || // Emoticons
1388
- code >= 128640 && code <= 128767 || // Transport and map symbols
1389
- code >= 129280 && code <= 129535 || // Supplemental symbols and pictographs
1390
- code >= 129648 && code <= 129791;
1384
+ return code >= 127744 && code <= 129535 || code >= 9728 && code <= 9983 || code >= 9984 && code <= 10175 || code >= 65024 && code <= 65039 || code >= 128512 && code <= 128591 || code >= 128640 && code <= 128767 || code >= 129280 && code <= 129535 || code >= 129648 && code <= 129791;
1385
+ }
1386
+ function reorderRunsVisually(runs) {
1387
+ if (runs.length <= 1) return runs;
1388
+ const result = runs.slice();
1389
+ const runLevels = result.map((r) => r.level);
1390
+ const maxLevel = Math.max(...runLevels);
1391
+ const minLevel = Math.min(...runLevels);
1392
+ const minOddLevel = minLevel % 2 === 1 ? minLevel : minLevel + 1;
1393
+ for (let level = maxLevel; level >= minOddLevel; level--) {
1394
+ let start = 0;
1395
+ while (start < result.length) {
1396
+ if (runLevels[result.indexOf(result[start])] >= level) {
1397
+ let end = start;
1398
+ while (end + 1 < result.length && result[end + 1].level >= level) {
1399
+ end++;
1400
+ }
1401
+ if (start < end) {
1402
+ let left = start;
1403
+ let right = end;
1404
+ while (left < right) {
1405
+ const tmp = result[left];
1406
+ result[left] = result[right];
1407
+ result[right] = tmp;
1408
+ left++;
1409
+ right--;
1410
+ }
1411
+ }
1412
+ start = end + 1;
1413
+ } else {
1414
+ start++;
1415
+ }
1416
+ }
1417
+ }
1418
+ return result;
1391
1419
  }
1392
1420
  var LayoutEngine = class {
1393
1421
  constructor(fonts) {
@@ -1412,6 +1440,9 @@ var LayoutEngine = class {
1412
1440
  try {
1413
1441
  buffer.addText(text);
1414
1442
  buffer.guessSegmentProperties();
1443
+ if (direction) {
1444
+ buffer.setDirection(direction);
1445
+ }
1415
1446
  const font = await this.fonts.getFont(desc);
1416
1447
  const face = await this.fonts.getFace(desc);
1417
1448
  const upem = face?.upem || 1e3;
@@ -1434,6 +1465,64 @@ var LayoutEngine = class {
1434
1465
  );
1435
1466
  }
1436
1467
  }
1468
+ splitIntoBidiRuns(input, levels, desc, emojiFallback) {
1469
+ const runs = [];
1470
+ if (input.length === 0) return runs;
1471
+ let runStart = 0;
1472
+ let runLevel = levels[0];
1473
+ let runIsEmoji = emojiFallback ? isEmoji(input[0]) : false;
1474
+ for (let i = 1; i <= input.length; i++) {
1475
+ const atEnd = i === input.length;
1476
+ const charLevel = atEnd ? -1 : levels[i];
1477
+ const charIsEmoji = !atEnd && emojiFallback ? isEmoji(String.fromCodePoint(input.codePointAt(i) ?? 0)) : false;
1478
+ if (atEnd || charLevel !== runLevel || emojiFallback && charIsEmoji !== runIsEmoji) {
1479
+ const text = input.slice(runStart, i);
1480
+ const font = runIsEmoji && emojiFallback ? emojiFallback : desc;
1481
+ runs.push({ text, startIndex: runStart, endIndex: i, level: runLevel, font });
1482
+ if (!atEnd) {
1483
+ runStart = i;
1484
+ runLevel = charLevel;
1485
+ runIsEmoji = charIsEmoji;
1486
+ }
1487
+ }
1488
+ }
1489
+ return runs;
1490
+ }
1491
+ async shapeWithBidi(input, desc, emojiFallback) {
1492
+ const hasRTL = containsRTLCharacters(input);
1493
+ const hasLTR = /[a-zA-Z0-9]/.test(input);
1494
+ const hasMixedDirection = hasRTL && hasLTR;
1495
+ if (!hasMixedDirection && !emojiFallback) {
1496
+ const textDirection = hasRTL ? "rtl" : void 0;
1497
+ return this.shapeFull(input, desc, textDirection);
1498
+ }
1499
+ const bidi = (0, import_bidi_js2.default)();
1500
+ const { levels } = bidi.getEmbeddingLevels(input);
1501
+ const bidiRuns = this.splitIntoBidiRuns(input, levels, desc, emojiFallback);
1502
+ const shapedRuns = [];
1503
+ for (const run of bidiRuns) {
1504
+ const runDirection = run.level % 2 === 1 ? "rtl" : "ltr";
1505
+ const runShaped = await this.shapeFull(run.text, run.font, runDirection);
1506
+ for (const glyph of runShaped) {
1507
+ glyph.cl += run.startIndex;
1508
+ if (run.font !== desc) {
1509
+ glyph.fontDesc = run.font;
1510
+ }
1511
+ }
1512
+ shapedRuns.push({
1513
+ glyphs: runShaped,
1514
+ startIndex: run.startIndex,
1515
+ endIndex: run.endIndex,
1516
+ level: run.level
1517
+ });
1518
+ }
1519
+ const visualRuns = reorderRunsVisually(shapedRuns);
1520
+ const visualGlyphs = [];
1521
+ for (const run of visualRuns) {
1522
+ visualGlyphs.push(...run.glyphs);
1523
+ }
1524
+ return visualGlyphs;
1525
+ }
1437
1526
  async layout(params) {
1438
1527
  try {
1439
1528
  const { textTransform, desc, fontSize, letterSpacing, width, emojiFallback } = params;
@@ -1443,38 +1532,7 @@ var LayoutEngine = class {
1443
1532
  }
1444
1533
  let shaped;
1445
1534
  try {
1446
- if (!emojiFallback) {
1447
- const textDirection = containsRTLCharacters(input) ? "rtl" : void 0;
1448
- shaped = await this.shapeFull(input, desc, textDirection);
1449
- } else {
1450
- const chars = Array.from(input);
1451
- const runs = [];
1452
- let currentRun = { text: "", startIndex: 0, isEmoji: false };
1453
- for (let i = 0; i < chars.length; i++) {
1454
- const char = chars[i];
1455
- const charIsEmoji = isEmoji(char);
1456
- if (i === 0) {
1457
- currentRun = { text: char, startIndex: 0, isEmoji: charIsEmoji };
1458
- } else if (currentRun.isEmoji === charIsEmoji) {
1459
- currentRun.text += char;
1460
- } else {
1461
- runs.push(currentRun);
1462
- currentRun = { text: char, startIndex: currentRun.startIndex + currentRun.text.length, isEmoji: charIsEmoji };
1463
- }
1464
- }
1465
- if (currentRun.text) runs.push(currentRun);
1466
- shaped = [];
1467
- for (const run of runs) {
1468
- const runFont = run.isEmoji ? emojiFallback : desc;
1469
- const runDirection = containsRTLCharacters(run.text) ? "rtl" : void 0;
1470
- const runShaped = await this.shapeFull(run.text, runFont, runDirection);
1471
- for (const glyph of runShaped) {
1472
- glyph.cl += run.startIndex;
1473
- glyph.fontDesc = runFont;
1474
- }
1475
- shaped.push(...runShaped);
1476
- }
1477
- }
1535
+ shaped = await this.shapeWithBidi(input, desc, emojiFallback);
1478
1536
  } catch (err) {
1479
1537
  throw new Error(`Text shaping failed: ${err instanceof Error ? err.message : String(err)}`);
1480
1538
  }
@@ -1505,7 +1563,6 @@ var LayoutEngine = class {
1505
1563
  cluster: g.cl,
1506
1564
  char,
1507
1565
  fontDesc: g.fontDesc
1508
- // Preserve font descriptor
1509
1566
  };
1510
1567
  });
1511
1568
  const lines = [];
@@ -534,6 +534,7 @@ interface HBGlyphInfo {
534
534
  interface HBBuffer {
535
535
  addText(text: string): void;
536
536
  guessSegmentProperties(): void;
537
+ setDirection(direction: "ltr" | "rtl" | "ttb" | "btt"): void;
537
538
  json(): HBGlyphInfo[];
538
539
  destroy(): void;
539
540
  }
@@ -534,6 +534,7 @@ interface HBGlyphInfo {
534
534
  interface HBBuffer {
535
535
  addText(text: string): void;
536
536
  guessSegmentProperties(): void;
537
+ setDirection(direction: "ltr" | "rtl" | "ttb" | "btt"): void;
537
538
  json(): HBGlyphInfo[];
538
539
  destroy(): void;
539
540
  }
@@ -974,17 +974,45 @@ function mirrorAnimationDirection(direction, isRTL) {
974
974
  }
975
975
 
976
976
  // src/core/layout.ts
977
+ import bidiFactory2 from "bidi-js";
977
978
  function isEmoji(char) {
978
979
  const code = char.codePointAt(0);
979
980
  if (!code) return false;
980
- return code >= 127744 && code <= 129535 || // Emoticons, symbols, pictographs
981
- code >= 9728 && code <= 9983 || // Miscellaneous symbols
982
- code >= 9984 && code <= 10175 || // Dingbats
983
- code >= 65024 && code <= 65039 || // Variation selectors
984
- code >= 128512 && code <= 128591 || // Emoticons
985
- code >= 128640 && code <= 128767 || // Transport and map symbols
986
- code >= 129280 && code <= 129535 || // Supplemental symbols and pictographs
987
- code >= 129648 && code <= 129791;
981
+ return code >= 127744 && code <= 129535 || code >= 9728 && code <= 9983 || code >= 9984 && code <= 10175 || code >= 65024 && code <= 65039 || code >= 128512 && code <= 128591 || code >= 128640 && code <= 128767 || code >= 129280 && code <= 129535 || code >= 129648 && code <= 129791;
982
+ }
983
+ function reorderRunsVisually(runs) {
984
+ if (runs.length <= 1) return runs;
985
+ const result = runs.slice();
986
+ const runLevels = result.map((r) => r.level);
987
+ const maxLevel = Math.max(...runLevels);
988
+ const minLevel = Math.min(...runLevels);
989
+ const minOddLevel = minLevel % 2 === 1 ? minLevel : minLevel + 1;
990
+ for (let level = maxLevel; level >= minOddLevel; level--) {
991
+ let start = 0;
992
+ while (start < result.length) {
993
+ if (runLevels[result.indexOf(result[start])] >= level) {
994
+ let end = start;
995
+ while (end + 1 < result.length && result[end + 1].level >= level) {
996
+ end++;
997
+ }
998
+ if (start < end) {
999
+ let left = start;
1000
+ let right = end;
1001
+ while (left < right) {
1002
+ const tmp = result[left];
1003
+ result[left] = result[right];
1004
+ result[right] = tmp;
1005
+ left++;
1006
+ right--;
1007
+ }
1008
+ }
1009
+ start = end + 1;
1010
+ } else {
1011
+ start++;
1012
+ }
1013
+ }
1014
+ }
1015
+ return result;
988
1016
  }
989
1017
  var LayoutEngine = class {
990
1018
  constructor(fonts) {
@@ -1009,6 +1037,9 @@ var LayoutEngine = class {
1009
1037
  try {
1010
1038
  buffer.addText(text);
1011
1039
  buffer.guessSegmentProperties();
1040
+ if (direction) {
1041
+ buffer.setDirection(direction);
1042
+ }
1012
1043
  const font = await this.fonts.getFont(desc);
1013
1044
  const face = await this.fonts.getFace(desc);
1014
1045
  const upem = face?.upem || 1e3;
@@ -1031,6 +1062,64 @@ var LayoutEngine = class {
1031
1062
  );
1032
1063
  }
1033
1064
  }
1065
+ splitIntoBidiRuns(input, levels, desc, emojiFallback) {
1066
+ const runs = [];
1067
+ if (input.length === 0) return runs;
1068
+ let runStart = 0;
1069
+ let runLevel = levels[0];
1070
+ let runIsEmoji = emojiFallback ? isEmoji(input[0]) : false;
1071
+ for (let i = 1; i <= input.length; i++) {
1072
+ const atEnd = i === input.length;
1073
+ const charLevel = atEnd ? -1 : levels[i];
1074
+ const charIsEmoji = !atEnd && emojiFallback ? isEmoji(String.fromCodePoint(input.codePointAt(i) ?? 0)) : false;
1075
+ if (atEnd || charLevel !== runLevel || emojiFallback && charIsEmoji !== runIsEmoji) {
1076
+ const text = input.slice(runStart, i);
1077
+ const font = runIsEmoji && emojiFallback ? emojiFallback : desc;
1078
+ runs.push({ text, startIndex: runStart, endIndex: i, level: runLevel, font });
1079
+ if (!atEnd) {
1080
+ runStart = i;
1081
+ runLevel = charLevel;
1082
+ runIsEmoji = charIsEmoji;
1083
+ }
1084
+ }
1085
+ }
1086
+ return runs;
1087
+ }
1088
+ async shapeWithBidi(input, desc, emojiFallback) {
1089
+ const hasRTL = containsRTLCharacters(input);
1090
+ const hasLTR = /[a-zA-Z0-9]/.test(input);
1091
+ const hasMixedDirection = hasRTL && hasLTR;
1092
+ if (!hasMixedDirection && !emojiFallback) {
1093
+ const textDirection = hasRTL ? "rtl" : void 0;
1094
+ return this.shapeFull(input, desc, textDirection);
1095
+ }
1096
+ const bidi = bidiFactory2();
1097
+ const { levels } = bidi.getEmbeddingLevels(input);
1098
+ const bidiRuns = this.splitIntoBidiRuns(input, levels, desc, emojiFallback);
1099
+ const shapedRuns = [];
1100
+ for (const run of bidiRuns) {
1101
+ const runDirection = run.level % 2 === 1 ? "rtl" : "ltr";
1102
+ const runShaped = await this.shapeFull(run.text, run.font, runDirection);
1103
+ for (const glyph of runShaped) {
1104
+ glyph.cl += run.startIndex;
1105
+ if (run.font !== desc) {
1106
+ glyph.fontDesc = run.font;
1107
+ }
1108
+ }
1109
+ shapedRuns.push({
1110
+ glyphs: runShaped,
1111
+ startIndex: run.startIndex,
1112
+ endIndex: run.endIndex,
1113
+ level: run.level
1114
+ });
1115
+ }
1116
+ const visualRuns = reorderRunsVisually(shapedRuns);
1117
+ const visualGlyphs = [];
1118
+ for (const run of visualRuns) {
1119
+ visualGlyphs.push(...run.glyphs);
1120
+ }
1121
+ return visualGlyphs;
1122
+ }
1034
1123
  async layout(params) {
1035
1124
  try {
1036
1125
  const { textTransform, desc, fontSize, letterSpacing, width, emojiFallback } = params;
@@ -1040,38 +1129,7 @@ var LayoutEngine = class {
1040
1129
  }
1041
1130
  let shaped;
1042
1131
  try {
1043
- if (!emojiFallback) {
1044
- const textDirection = containsRTLCharacters(input) ? "rtl" : void 0;
1045
- shaped = await this.shapeFull(input, desc, textDirection);
1046
- } else {
1047
- const chars = Array.from(input);
1048
- const runs = [];
1049
- let currentRun = { text: "", startIndex: 0, isEmoji: false };
1050
- for (let i = 0; i < chars.length; i++) {
1051
- const char = chars[i];
1052
- const charIsEmoji = isEmoji(char);
1053
- if (i === 0) {
1054
- currentRun = { text: char, startIndex: 0, isEmoji: charIsEmoji };
1055
- } else if (currentRun.isEmoji === charIsEmoji) {
1056
- currentRun.text += char;
1057
- } else {
1058
- runs.push(currentRun);
1059
- currentRun = { text: char, startIndex: currentRun.startIndex + currentRun.text.length, isEmoji: charIsEmoji };
1060
- }
1061
- }
1062
- if (currentRun.text) runs.push(currentRun);
1063
- shaped = [];
1064
- for (const run of runs) {
1065
- const runFont = run.isEmoji ? emojiFallback : desc;
1066
- const runDirection = containsRTLCharacters(run.text) ? "rtl" : void 0;
1067
- const runShaped = await this.shapeFull(run.text, runFont, runDirection);
1068
- for (const glyph of runShaped) {
1069
- glyph.cl += run.startIndex;
1070
- glyph.fontDesc = runFont;
1071
- }
1072
- shaped.push(...runShaped);
1073
- }
1074
- }
1132
+ shaped = await this.shapeWithBidi(input, desc, emojiFallback);
1075
1133
  } catch (err) {
1076
1134
  throw new Error(`Text shaping failed: ${err instanceof Error ? err.message : String(err)}`);
1077
1135
  }
@@ -1102,7 +1160,6 @@ var LayoutEngine = class {
1102
1160
  cluster: g.cl,
1103
1161
  char,
1104
1162
  fontDesc: g.fontDesc
1105
- // Preserve font descriptor
1106
1163
  };
1107
1164
  });
1108
1165
  const lines = [];
@@ -534,6 +534,7 @@ interface HBGlyphInfo {
534
534
  interface HBBuffer {
535
535
  addText(text: string): void;
536
536
  guessSegmentProperties(): void;
537
+ setDirection(direction: "ltr" | "rtl" | "ttb" | "btt"): void;
537
538
  json(): HBGlyphInfo[];
538
539
  destroy(): void;
539
540
  }
package/dist/entry.web.js CHANGED
@@ -33016,14 +33016,41 @@ function mirrorAnimationDirection(direction, isRTL) {
33016
33016
  function isEmoji(char) {
33017
33017
  const code = char.codePointAt(0);
33018
33018
  if (!code) return false;
33019
- return code >= 127744 && code <= 129535 || // Emoticons, symbols, pictographs
33020
- code >= 9728 && code <= 9983 || // Miscellaneous symbols
33021
- code >= 9984 && code <= 10175 || // Dingbats
33022
- code >= 65024 && code <= 65039 || // Variation selectors
33023
- code >= 128512 && code <= 128591 || // Emoticons
33024
- code >= 128640 && code <= 128767 || // Transport and map symbols
33025
- code >= 129280 && code <= 129535 || // Supplemental symbols and pictographs
33026
- code >= 129648 && code <= 129791;
33019
+ return code >= 127744 && code <= 129535 || code >= 9728 && code <= 9983 || code >= 9984 && code <= 10175 || code >= 65024 && code <= 65039 || code >= 128512 && code <= 128591 || code >= 128640 && code <= 128767 || code >= 129280 && code <= 129535 || code >= 129648 && code <= 129791;
33020
+ }
33021
+ function reorderRunsVisually(runs) {
33022
+ if (runs.length <= 1) return runs;
33023
+ const result = runs.slice();
33024
+ const runLevels = result.map((r) => r.level);
33025
+ const maxLevel = Math.max(...runLevels);
33026
+ const minLevel = Math.min(...runLevels);
33027
+ const minOddLevel = minLevel % 2 === 1 ? minLevel : minLevel + 1;
33028
+ for (let level = maxLevel; level >= minOddLevel; level--) {
33029
+ let start = 0;
33030
+ while (start < result.length) {
33031
+ if (runLevels[result.indexOf(result[start])] >= level) {
33032
+ let end = start;
33033
+ while (end + 1 < result.length && result[end + 1].level >= level) {
33034
+ end++;
33035
+ }
33036
+ if (start < end) {
33037
+ let left = start;
33038
+ let right = end;
33039
+ while (left < right) {
33040
+ const tmp = result[left];
33041
+ result[left] = result[right];
33042
+ result[right] = tmp;
33043
+ left++;
33044
+ right--;
33045
+ }
33046
+ }
33047
+ start = end + 1;
33048
+ } else {
33049
+ start++;
33050
+ }
33051
+ }
33052
+ }
33053
+ return result;
33027
33054
  }
33028
33055
  var LayoutEngine = class {
33029
33056
  constructor(fonts) {
@@ -33048,6 +33075,9 @@ var LayoutEngine = class {
33048
33075
  try {
33049
33076
  buffer.addText(text);
33050
33077
  buffer.guessSegmentProperties();
33078
+ if (direction) {
33079
+ buffer.setDirection(direction);
33080
+ }
33051
33081
  const font = await this.fonts.getFont(desc);
33052
33082
  const face = await this.fonts.getFace(desc);
33053
33083
  const upem = face?.upem || 1e3;
@@ -33070,6 +33100,64 @@ var LayoutEngine = class {
33070
33100
  );
33071
33101
  }
33072
33102
  }
33103
+ splitIntoBidiRuns(input, levels, desc, emojiFallback) {
33104
+ const runs = [];
33105
+ if (input.length === 0) return runs;
33106
+ let runStart = 0;
33107
+ let runLevel = levels[0];
33108
+ let runIsEmoji = emojiFallback ? isEmoji(input[0]) : false;
33109
+ for (let i = 1; i <= input.length; i++) {
33110
+ const atEnd = i === input.length;
33111
+ const charLevel = atEnd ? -1 : levels[i];
33112
+ const charIsEmoji = !atEnd && emojiFallback ? isEmoji(String.fromCodePoint(input.codePointAt(i) ?? 0)) : false;
33113
+ if (atEnd || charLevel !== runLevel || emojiFallback && charIsEmoji !== runIsEmoji) {
33114
+ const text = input.slice(runStart, i);
33115
+ const font = runIsEmoji && emojiFallback ? emojiFallback : desc;
33116
+ runs.push({ text, startIndex: runStart, endIndex: i, level: runLevel, font });
33117
+ if (!atEnd) {
33118
+ runStart = i;
33119
+ runLevel = charLevel;
33120
+ runIsEmoji = charIsEmoji;
33121
+ }
33122
+ }
33123
+ }
33124
+ return runs;
33125
+ }
33126
+ async shapeWithBidi(input, desc, emojiFallback) {
33127
+ const hasRTL = containsRTLCharacters(input);
33128
+ const hasLTR = /[a-zA-Z0-9]/.test(input);
33129
+ const hasMixedDirection = hasRTL && hasLTR;
33130
+ if (!hasMixedDirection && !emojiFallback) {
33131
+ const textDirection = hasRTL ? "rtl" : void 0;
33132
+ return this.shapeFull(input, desc, textDirection);
33133
+ }
33134
+ const bidi = bidi_default();
33135
+ const { levels } = bidi.getEmbeddingLevels(input);
33136
+ const bidiRuns = this.splitIntoBidiRuns(input, levels, desc, emojiFallback);
33137
+ const shapedRuns = [];
33138
+ for (const run of bidiRuns) {
33139
+ const runDirection = run.level % 2 === 1 ? "rtl" : "ltr";
33140
+ const runShaped = await this.shapeFull(run.text, run.font, runDirection);
33141
+ for (const glyph of runShaped) {
33142
+ glyph.cl += run.startIndex;
33143
+ if (run.font !== desc) {
33144
+ glyph.fontDesc = run.font;
33145
+ }
33146
+ }
33147
+ shapedRuns.push({
33148
+ glyphs: runShaped,
33149
+ startIndex: run.startIndex,
33150
+ endIndex: run.endIndex,
33151
+ level: run.level
33152
+ });
33153
+ }
33154
+ const visualRuns = reorderRunsVisually(shapedRuns);
33155
+ const visualGlyphs = [];
33156
+ for (const run of visualRuns) {
33157
+ visualGlyphs.push(...run.glyphs);
33158
+ }
33159
+ return visualGlyphs;
33160
+ }
33073
33161
  async layout(params) {
33074
33162
  try {
33075
33163
  const { textTransform, desc, fontSize, letterSpacing, width, emojiFallback } = params;
@@ -33079,38 +33167,7 @@ var LayoutEngine = class {
33079
33167
  }
33080
33168
  let shaped;
33081
33169
  try {
33082
- if (!emojiFallback) {
33083
- const textDirection = containsRTLCharacters(input) ? "rtl" : void 0;
33084
- shaped = await this.shapeFull(input, desc, textDirection);
33085
- } else {
33086
- const chars = Array.from(input);
33087
- const runs = [];
33088
- let currentRun = { text: "", startIndex: 0, isEmoji: false };
33089
- for (let i = 0; i < chars.length; i++) {
33090
- const char = chars[i];
33091
- const charIsEmoji = isEmoji(char);
33092
- if (i === 0) {
33093
- currentRun = { text: char, startIndex: 0, isEmoji: charIsEmoji };
33094
- } else if (currentRun.isEmoji === charIsEmoji) {
33095
- currentRun.text += char;
33096
- } else {
33097
- runs.push(currentRun);
33098
- currentRun = { text: char, startIndex: currentRun.startIndex + currentRun.text.length, isEmoji: charIsEmoji };
33099
- }
33100
- }
33101
- if (currentRun.text) runs.push(currentRun);
33102
- shaped = [];
33103
- for (const run of runs) {
33104
- const runFont = run.isEmoji ? emojiFallback : desc;
33105
- const runDirection = containsRTLCharacters(run.text) ? "rtl" : void 0;
33106
- const runShaped = await this.shapeFull(run.text, runFont, runDirection);
33107
- for (const glyph of runShaped) {
33108
- glyph.cl += run.startIndex;
33109
- glyph.fontDesc = runFont;
33110
- }
33111
- shaped.push(...runShaped);
33112
- }
33113
- }
33170
+ shaped = await this.shapeWithBidi(input, desc, emojiFallback);
33114
33171
  } catch (err) {
33115
33172
  throw new Error(`Text shaping failed: ${err instanceof Error ? err.message : String(err)}`);
33116
33173
  }
@@ -33141,7 +33198,6 @@ var LayoutEngine = class {
33141
33198
  cluster: g.cl,
33142
33199
  char,
33143
33200
  fontDesc: g.fontDesc
33144
- // Preserve font descriptor
33145
33201
  };
33146
33202
  });
33147
33203
  const lines = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shotstack/shotstack-canvas",
3
- "version": "2.0.14",
3
+ "version": "2.0.15",
4
4
  "description": "Text layout & animation engine (HarfBuzz) for Node & Web - fully self-contained.",
5
5
  "type": "module",
6
6
  "main": "./dist/entry.node.cjs",