@clypra/engine 1.1.2 → 1.2.1

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/dist/index.cjs CHANGED
@@ -263,35 +263,26 @@ function wrapTextToWidth(ctx, text, maxWidth, letterSpacing) {
263
263
  const paragraphs = text.split("\n");
264
264
  const lines = [];
265
265
  for (const para of paragraphs) {
266
- if (!para.trim()) {
266
+ if (para === "") {
267
267
  lines.push("");
268
268
  continue;
269
269
  }
270
- const words = para.split(/\s+/).filter(Boolean);
271
270
  let current = "";
272
- for (const word of words) {
273
- const candidate = current ? `${current} ${word}` : word;
274
- if (measureLine(ctx, candidate, letterSpacing) <= maxWidth) {
275
- current = candidate;
271
+ for (let i = 0; i < para.length; i++) {
272
+ const char = para[i];
273
+ const tryLine = current + char;
274
+ if (measureLine(ctx, tryLine, letterSpacing) <= maxWidth) {
275
+ current = tryLine;
276
276
  } else {
277
- if (current) lines.push(current);
278
- if (measureLine(ctx, word, letterSpacing) > maxWidth) {
279
- let chunk = "";
280
- for (const ch of word) {
281
- const tryChunk = chunk + ch;
282
- if (measureLine(ctx, tryChunk, letterSpacing) <= maxWidth) chunk = tryChunk;
283
- else {
284
- if (chunk) lines.push(chunk);
285
- chunk = ch;
286
- }
287
- }
288
- current = chunk;
289
- } else {
290
- current = word;
277
+ if (current) {
278
+ lines.push(current);
291
279
  }
280
+ current = char;
292
281
  }
293
282
  }
294
- if (current) lines.push(current);
283
+ if (current) {
284
+ lines.push(current);
285
+ }
295
286
  }
296
287
  return lines.length > 0 ? lines : [""];
297
288
  }
@@ -317,17 +308,17 @@ function layoutWithFontSize(ctx, cfg, fontSize, lines) {
317
308
  } else {
318
309
  startX = safe.x + safe.width / 2;
319
310
  }
320
- let startY = safe.y + (safe.height - textBlockHeight) / 2 + fontSize * 0.82;
311
+ let startY = safe.y + (safe.height - textBlockHeight) / 2 + fontSize * 0.85;
321
312
  if (cfg.textPosY === "top") {
322
- startY = safe.y + fontSize * 0.82;
313
+ startY = safe.y + fontSize * 0.85;
323
314
  } else if (cfg.textPosY === "bottom") {
324
- startY = safe.y + safe.height - textBlockHeight + fontSize * 0.82;
315
+ startY = safe.y + safe.height - textBlockHeight + fontSize * 0.85;
325
316
  }
326
317
  let xMin = startX;
327
318
  if (align === "center") xMin = startX - maxLineWidth / 2;
328
319
  else if (align === "right") xMin = startX - maxLineWidth;
329
320
  const yMin = startY - fontSize * 0.85;
330
- const yMax = startY + (lines.length - 1) * fontSize * lineHeight + fontSize * 0.25;
321
+ const yMax = startY + (lines.length - 1) * fontSize * lineHeight + fontSize * 0.15;
331
322
  return {
332
323
  lines,
333
324
  fontSize,
@@ -766,11 +757,11 @@ var InkBrushEngine = class {
766
757
  if (letterSpacing !== 0) {
767
758
  ctx.letterSpacing = `${letterSpacing}px`;
768
759
  }
769
- let startY = (height - textBlockHeight) / 2 + fontSize * 0.8;
760
+ let startY = (height - textBlockHeight) / 2 + fontSize * 0.85;
770
761
  if (textPosY === "top") {
771
- startY = 40 + fontSize * 0.8;
762
+ startY = 40 + fontSize * 0.85;
772
763
  } else if (textPosY === "bottom") {
773
- startY = height - 40 - textBlockHeight + fontSize * 0.8;
764
+ startY = height - 40 - textBlockHeight + fontSize * 0.85;
774
765
  }
775
766
  ctx.save();
776
767
  if (skewX !== 0) {
@@ -918,11 +909,11 @@ var InkBrushEngine = class {
918
909
  if (letterSpacing !== 0) {
919
910
  ctx.letterSpacing = `${letterSpacing}px`;
920
911
  }
921
- let startY = (height - textBlockHeight) / 2 + fontSize * 0.8;
912
+ let startY = (height - textBlockHeight) / 2 + fontSize * 0.85;
922
913
  if (textPosY === "top") {
923
- startY = 40 + fontSize * 0.8;
914
+ startY = 40 + fontSize * 0.85;
924
915
  } else if (textPosY === "bottom") {
925
- startY = height - 40 - textBlockHeight + fontSize * 0.8;
916
+ startY = height - 40 - textBlockHeight + fontSize * 0.85;
926
917
  }
927
918
  ctx.save();
928
919
  if (skewX !== 0) {
@@ -1007,6 +998,17 @@ function restoreLetterSpacing(ctx, saved) {
1007
998
  function getCanvas2DContext2(canvas) {
1008
999
  return canvas.getContext("2d");
1009
1000
  }
1001
+ function ctxSupportsFilter(ctx) {
1002
+ try {
1003
+ const prev = ctx.filter;
1004
+ ctx.filter = "blur(4px)";
1005
+ const ok = typeof ctx.filter === "string" && ctx.filter.includes("blur");
1006
+ ctx.filter = prev;
1007
+ return ok;
1008
+ } catch {
1009
+ return false;
1010
+ }
1011
+ }
1010
1012
  function renderTextEffectCore(ctx, cfg) {
1011
1013
  if (cfg.customRenderer === "InkBrushEngine") {
1012
1014
  const engine = new InkBrushEngine(cfg);
@@ -1316,19 +1318,31 @@ function renderTextEffectCore(ctx, cfg) {
1316
1318
  const vpy = cHeight / 2 + (bevelVanishingPointY !== void 0 ? bevelVanishingPointY : 80) / 100 * (cHeight / 2);
1317
1319
  const fl = Math.max(100, bevelFocalLength !== void 0 ? bevelFocalLength : 400);
1318
1320
  if (bevelBlur && bevelBlur > 0) {
1319
- ctx.save();
1320
- ctx.filter = `blur(${bevelBlur}px)`;
1321
1321
  const blurColor = bevelBlurColor || bevelShadow || "#000000";
1322
- for (let i = bevelDepth; i > 0; i -= Math.max(1, Math.floor(bevelDepth / 4))) {
1323
- const scale = fl / (fl + i);
1322
+ if (ctxSupportsFilter(ctx)) {
1324
1323
  ctx.save();
1325
- ctx.translate(vpx, vpy);
1326
- ctx.scale(scale, scale);
1327
- ctx.translate(-vpx, -vpy);
1328
- renderLines("fill", blurColor);
1324
+ ctx.filter = `blur(${bevelBlur}px)`;
1325
+ for (let i = bevelDepth; i > 0; i -= Math.max(1, Math.floor(bevelDepth / 4))) {
1326
+ const scale = fl / (fl + i);
1327
+ ctx.save();
1328
+ ctx.translate(vpx, vpy);
1329
+ ctx.scale(scale, scale);
1330
+ ctx.translate(-vpx, -vpy);
1331
+ renderLines("fill", blurColor);
1332
+ ctx.restore();
1333
+ }
1329
1334
  ctx.restore();
1335
+ } else {
1336
+ for (let i = bevelDepth; i > 0; i -= Math.max(1, Math.floor(bevelDepth / 4))) {
1337
+ const scale = fl / (fl + i);
1338
+ ctx.save();
1339
+ ctx.translate(vpx, vpy);
1340
+ ctx.scale(scale, scale);
1341
+ ctx.translate(-vpx, -vpy);
1342
+ renderWithShadowTrick("fill", blurColor, bevelBlur, 0, 0, 100);
1343
+ ctx.restore();
1344
+ }
1330
1345
  }
1331
- ctx.restore();
1332
1346
  }
1333
1347
  ctx.save();
1334
1348
  for (let i = bevelDepth; i > 0; i--) {
@@ -1375,14 +1389,21 @@ function renderTextEffectCore(ctx, cfg) {
1375
1389
  return { dx: i, dy: i };
1376
1390
  };
1377
1391
  if (bevelBlur && bevelBlur > 0) {
1378
- ctx.save();
1379
- ctx.filter = `blur(${bevelBlur}px)`;
1380
1392
  const blurColor = bevelBlurColor || bevelShadow || "#000000";
1381
- for (let i = bevelDepth; i > 0; i -= Math.max(1, Math.floor(bevelDepth / 4))) {
1382
- const { dx, dy } = getDirOffset(i);
1383
- renderLines("fill", blurColor, dx, dy);
1393
+ if (ctxSupportsFilter(ctx)) {
1394
+ ctx.save();
1395
+ ctx.filter = `blur(${bevelBlur}px)`;
1396
+ for (let i = bevelDepth; i > 0; i -= Math.max(1, Math.floor(bevelDepth / 4))) {
1397
+ const { dx, dy } = getDirOffset(i);
1398
+ renderLines("fill", blurColor, dx, dy);
1399
+ }
1400
+ ctx.restore();
1401
+ } else {
1402
+ for (let i = bevelDepth; i > 0; i -= Math.max(1, Math.floor(bevelDepth / 4))) {
1403
+ const { dx, dy } = getDirOffset(i);
1404
+ renderWithShadowTrick("fill", blurColor, bevelBlur, dx, dy, 100);
1405
+ }
1384
1406
  }
1385
- ctx.restore();
1386
1407
  }
1387
1408
  ctx.save();
1388
1409
  for (let i = bevelDepth; i > 0; i--) {
@@ -1436,24 +1457,51 @@ function renderTextEffectCore(ctx, cfg) {
1436
1457
  customStrokeStyle = grad;
1437
1458
  }
1438
1459
  const drawStrokeLayer = (color, width, blurAmount, opacity, position) => {
1439
- ctx.save();
1440
- ctx.globalAlpha = opacity / 100;
1441
- ctx.strokeStyle = color;
1442
- if (blurAmount > 0) {
1460
+ if (blurAmount > 0 && ctxSupportsFilter(ctx)) {
1461
+ ctx.save();
1462
+ ctx.globalAlpha = opacity / 100;
1463
+ ctx.strokeStyle = color;
1443
1464
  ctx.filter = `blur(${blurAmount}px)`;
1465
+ if (position === "outside") {
1466
+ ctx.lineWidth = width * 2;
1467
+ renderLines("stroke");
1468
+ } else if (position === "center") {
1469
+ ctx.lineWidth = width;
1470
+ renderLines("stroke");
1471
+ } else if (position === "inside") {
1472
+ ctx.globalCompositeOperation = "source-atop";
1473
+ ctx.lineWidth = width * 2;
1474
+ renderLines("stroke");
1475
+ }
1476
+ ctx.restore();
1477
+ } else if (blurAmount > 0) {
1478
+ const colorStr = typeof color === "string" ? color : strokeColor;
1479
+ const spread = position === "center" ? width / 2 : width;
1480
+ if (position === "inside") {
1481
+ ctx.save();
1482
+ ctx.globalCompositeOperation = "source-atop";
1483
+ renderWithShadowTrick("stroke", colorStr, blurAmount, 0, 0, opacity, void 0, spread);
1484
+ ctx.restore();
1485
+ } else {
1486
+ renderWithShadowTrick("stroke", colorStr, blurAmount, 0, 0, opacity, void 0, spread);
1487
+ }
1488
+ } else {
1489
+ ctx.save();
1490
+ ctx.globalAlpha = opacity / 100;
1491
+ ctx.strokeStyle = color;
1492
+ if (position === "outside") {
1493
+ ctx.lineWidth = width * 2;
1494
+ renderLines("stroke");
1495
+ } else if (position === "center") {
1496
+ ctx.lineWidth = width;
1497
+ renderLines("stroke");
1498
+ } else if (position === "inside") {
1499
+ ctx.globalCompositeOperation = "source-atop";
1500
+ ctx.lineWidth = width * 2;
1501
+ renderLines("stroke");
1502
+ }
1503
+ ctx.restore();
1444
1504
  }
1445
- if (position === "outside") {
1446
- ctx.lineWidth = width * 2;
1447
- renderLines("stroke");
1448
- } else if (position === "center") {
1449
- ctx.lineWidth = width;
1450
- renderLines("stroke");
1451
- } else if (position === "inside") {
1452
- ctx.globalCompositeOperation = "source-atop";
1453
- ctx.lineWidth = width * 2;
1454
- renderLines("stroke");
1455
- }
1456
- ctx.restore();
1457
1505
  };
1458
1506
  if (sType === "double") {
1459
1507
  const outerWidth = strokeWidth + sWidthSecondary;