@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 +110 -62
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +110 -62
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -462,7 +462,7 @@ interface CompositionPreset {
|
|
|
462
462
|
description?: string;
|
|
463
463
|
}
|
|
464
464
|
declare const COMPOSITION_PRESETS: CompositionPreset[];
|
|
465
|
-
/** Soft-wrap paragraphs to fit safe width */
|
|
465
|
+
/** Soft-wrap paragraphs to fit safe width character-by-character */
|
|
466
466
|
declare function wrapTextToWidth(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, text: string, maxWidth: number, letterSpacing: number): string[];
|
|
467
467
|
declare function measureTextFits(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, cfg: TextEffectConfig, fontSize: number, lines: string[]): boolean;
|
|
468
468
|
declare function computeAutoFitFontSize(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, cfg: TextEffectConfig, wrappedLines: string[]): number;
|
package/dist/index.d.ts
CHANGED
|
@@ -462,7 +462,7 @@ interface CompositionPreset {
|
|
|
462
462
|
description?: string;
|
|
463
463
|
}
|
|
464
464
|
declare const COMPOSITION_PRESETS: CompositionPreset[];
|
|
465
|
-
/** Soft-wrap paragraphs to fit safe width */
|
|
465
|
+
/** Soft-wrap paragraphs to fit safe width character-by-character */
|
|
466
466
|
declare function wrapTextToWidth(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, text: string, maxWidth: number, letterSpacing: number): string[];
|
|
467
467
|
declare function measureTextFits(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, cfg: TextEffectConfig, fontSize: number, lines: string[]): boolean;
|
|
468
468
|
declare function computeAutoFitFontSize(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, cfg: TextEffectConfig, wrappedLines: string[]): number;
|
package/dist/index.js
CHANGED
|
@@ -73,35 +73,26 @@ function wrapTextToWidth(ctx, text, maxWidth, letterSpacing) {
|
|
|
73
73
|
const paragraphs = text.split("\n");
|
|
74
74
|
const lines = [];
|
|
75
75
|
for (const para of paragraphs) {
|
|
76
|
-
if (
|
|
76
|
+
if (para === "") {
|
|
77
77
|
lines.push("");
|
|
78
78
|
continue;
|
|
79
79
|
}
|
|
80
|
-
const words = para.split(/\s+/).filter(Boolean);
|
|
81
80
|
let current = "";
|
|
82
|
-
for (
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
for (let i = 0; i < para.length; i++) {
|
|
82
|
+
const char = para[i];
|
|
83
|
+
const tryLine = current + char;
|
|
84
|
+
if (measureLine(ctx, tryLine, letterSpacing) <= maxWidth) {
|
|
85
|
+
current = tryLine;
|
|
86
86
|
} else {
|
|
87
|
-
if (current)
|
|
88
|
-
|
|
89
|
-
let chunk = "";
|
|
90
|
-
for (const ch of word) {
|
|
91
|
-
const tryChunk = chunk + ch;
|
|
92
|
-
if (measureLine(ctx, tryChunk, letterSpacing) <= maxWidth) chunk = tryChunk;
|
|
93
|
-
else {
|
|
94
|
-
if (chunk) lines.push(chunk);
|
|
95
|
-
chunk = ch;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
current = chunk;
|
|
99
|
-
} else {
|
|
100
|
-
current = word;
|
|
87
|
+
if (current) {
|
|
88
|
+
lines.push(current);
|
|
101
89
|
}
|
|
90
|
+
current = char;
|
|
102
91
|
}
|
|
103
92
|
}
|
|
104
|
-
if (current)
|
|
93
|
+
if (current) {
|
|
94
|
+
lines.push(current);
|
|
95
|
+
}
|
|
105
96
|
}
|
|
106
97
|
return lines.length > 0 ? lines : [""];
|
|
107
98
|
}
|
|
@@ -127,17 +118,17 @@ function layoutWithFontSize(ctx, cfg, fontSize, lines) {
|
|
|
127
118
|
} else {
|
|
128
119
|
startX = safe.x + safe.width / 2;
|
|
129
120
|
}
|
|
130
|
-
let startY = safe.y + (safe.height - textBlockHeight) / 2 + fontSize * 0.
|
|
121
|
+
let startY = safe.y + (safe.height - textBlockHeight) / 2 + fontSize * 0.85;
|
|
131
122
|
if (cfg.textPosY === "top") {
|
|
132
|
-
startY = safe.y + fontSize * 0.
|
|
123
|
+
startY = safe.y + fontSize * 0.85;
|
|
133
124
|
} else if (cfg.textPosY === "bottom") {
|
|
134
|
-
startY = safe.y + safe.height - textBlockHeight + fontSize * 0.
|
|
125
|
+
startY = safe.y + safe.height - textBlockHeight + fontSize * 0.85;
|
|
135
126
|
}
|
|
136
127
|
let xMin = startX;
|
|
137
128
|
if (align === "center") xMin = startX - maxLineWidth / 2;
|
|
138
129
|
else if (align === "right") xMin = startX - maxLineWidth;
|
|
139
130
|
const yMin = startY - fontSize * 0.85;
|
|
140
|
-
const yMax = startY + (lines.length - 1) * fontSize * lineHeight + fontSize * 0.
|
|
131
|
+
const yMax = startY + (lines.length - 1) * fontSize * lineHeight + fontSize * 0.15;
|
|
141
132
|
return {
|
|
142
133
|
lines,
|
|
143
134
|
fontSize,
|
|
@@ -576,11 +567,11 @@ var InkBrushEngine = class {
|
|
|
576
567
|
if (letterSpacing !== 0) {
|
|
577
568
|
ctx.letterSpacing = `${letterSpacing}px`;
|
|
578
569
|
}
|
|
579
|
-
let startY = (height - textBlockHeight) / 2 + fontSize * 0.
|
|
570
|
+
let startY = (height - textBlockHeight) / 2 + fontSize * 0.85;
|
|
580
571
|
if (textPosY === "top") {
|
|
581
|
-
startY = 40 + fontSize * 0.
|
|
572
|
+
startY = 40 + fontSize * 0.85;
|
|
582
573
|
} else if (textPosY === "bottom") {
|
|
583
|
-
startY = height - 40 - textBlockHeight + fontSize * 0.
|
|
574
|
+
startY = height - 40 - textBlockHeight + fontSize * 0.85;
|
|
584
575
|
}
|
|
585
576
|
ctx.save();
|
|
586
577
|
if (skewX !== 0) {
|
|
@@ -728,11 +719,11 @@ var InkBrushEngine = class {
|
|
|
728
719
|
if (letterSpacing !== 0) {
|
|
729
720
|
ctx.letterSpacing = `${letterSpacing}px`;
|
|
730
721
|
}
|
|
731
|
-
let startY = (height - textBlockHeight) / 2 + fontSize * 0.
|
|
722
|
+
let startY = (height - textBlockHeight) / 2 + fontSize * 0.85;
|
|
732
723
|
if (textPosY === "top") {
|
|
733
|
-
startY = 40 + fontSize * 0.
|
|
724
|
+
startY = 40 + fontSize * 0.85;
|
|
734
725
|
} else if (textPosY === "bottom") {
|
|
735
|
-
startY = height - 40 - textBlockHeight + fontSize * 0.
|
|
726
|
+
startY = height - 40 - textBlockHeight + fontSize * 0.85;
|
|
736
727
|
}
|
|
737
728
|
ctx.save();
|
|
738
729
|
if (skewX !== 0) {
|
|
@@ -817,6 +808,17 @@ function restoreLetterSpacing(ctx, saved) {
|
|
|
817
808
|
function getCanvas2DContext2(canvas) {
|
|
818
809
|
return canvas.getContext("2d");
|
|
819
810
|
}
|
|
811
|
+
function ctxSupportsFilter(ctx) {
|
|
812
|
+
try {
|
|
813
|
+
const prev = ctx.filter;
|
|
814
|
+
ctx.filter = "blur(4px)";
|
|
815
|
+
const ok = typeof ctx.filter === "string" && ctx.filter.includes("blur");
|
|
816
|
+
ctx.filter = prev;
|
|
817
|
+
return ok;
|
|
818
|
+
} catch {
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
820
822
|
function renderTextEffectCore(ctx, cfg) {
|
|
821
823
|
if (cfg.customRenderer === "InkBrushEngine") {
|
|
822
824
|
const engine = new InkBrushEngine(cfg);
|
|
@@ -1126,19 +1128,31 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
1126
1128
|
const vpy = cHeight / 2 + (bevelVanishingPointY !== void 0 ? bevelVanishingPointY : 80) / 100 * (cHeight / 2);
|
|
1127
1129
|
const fl = Math.max(100, bevelFocalLength !== void 0 ? bevelFocalLength : 400);
|
|
1128
1130
|
if (bevelBlur && bevelBlur > 0) {
|
|
1129
|
-
ctx.save();
|
|
1130
|
-
ctx.filter = `blur(${bevelBlur}px)`;
|
|
1131
1131
|
const blurColor = bevelBlurColor || bevelShadow || "#000000";
|
|
1132
|
-
|
|
1133
|
-
const scale = fl / (fl + i);
|
|
1132
|
+
if (ctxSupportsFilter(ctx)) {
|
|
1134
1133
|
ctx.save();
|
|
1135
|
-
ctx.
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1134
|
+
ctx.filter = `blur(${bevelBlur}px)`;
|
|
1135
|
+
for (let i = bevelDepth; i > 0; i -= Math.max(1, Math.floor(bevelDepth / 4))) {
|
|
1136
|
+
const scale = fl / (fl + i);
|
|
1137
|
+
ctx.save();
|
|
1138
|
+
ctx.translate(vpx, vpy);
|
|
1139
|
+
ctx.scale(scale, scale);
|
|
1140
|
+
ctx.translate(-vpx, -vpy);
|
|
1141
|
+
renderLines("fill", blurColor);
|
|
1142
|
+
ctx.restore();
|
|
1143
|
+
}
|
|
1139
1144
|
ctx.restore();
|
|
1145
|
+
} else {
|
|
1146
|
+
for (let i = bevelDepth; i > 0; i -= Math.max(1, Math.floor(bevelDepth / 4))) {
|
|
1147
|
+
const scale = fl / (fl + i);
|
|
1148
|
+
ctx.save();
|
|
1149
|
+
ctx.translate(vpx, vpy);
|
|
1150
|
+
ctx.scale(scale, scale);
|
|
1151
|
+
ctx.translate(-vpx, -vpy);
|
|
1152
|
+
renderWithShadowTrick("fill", blurColor, bevelBlur, 0, 0, 100);
|
|
1153
|
+
ctx.restore();
|
|
1154
|
+
}
|
|
1140
1155
|
}
|
|
1141
|
-
ctx.restore();
|
|
1142
1156
|
}
|
|
1143
1157
|
ctx.save();
|
|
1144
1158
|
for (let i = bevelDepth; i > 0; i--) {
|
|
@@ -1185,14 +1199,21 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
1185
1199
|
return { dx: i, dy: i };
|
|
1186
1200
|
};
|
|
1187
1201
|
if (bevelBlur && bevelBlur > 0) {
|
|
1188
|
-
ctx.save();
|
|
1189
|
-
ctx.filter = `blur(${bevelBlur}px)`;
|
|
1190
1202
|
const blurColor = bevelBlurColor || bevelShadow || "#000000";
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1203
|
+
if (ctxSupportsFilter(ctx)) {
|
|
1204
|
+
ctx.save();
|
|
1205
|
+
ctx.filter = `blur(${bevelBlur}px)`;
|
|
1206
|
+
for (let i = bevelDepth; i > 0; i -= Math.max(1, Math.floor(bevelDepth / 4))) {
|
|
1207
|
+
const { dx, dy } = getDirOffset(i);
|
|
1208
|
+
renderLines("fill", blurColor, dx, dy);
|
|
1209
|
+
}
|
|
1210
|
+
ctx.restore();
|
|
1211
|
+
} else {
|
|
1212
|
+
for (let i = bevelDepth; i > 0; i -= Math.max(1, Math.floor(bevelDepth / 4))) {
|
|
1213
|
+
const { dx, dy } = getDirOffset(i);
|
|
1214
|
+
renderWithShadowTrick("fill", blurColor, bevelBlur, dx, dy, 100);
|
|
1215
|
+
}
|
|
1194
1216
|
}
|
|
1195
|
-
ctx.restore();
|
|
1196
1217
|
}
|
|
1197
1218
|
ctx.save();
|
|
1198
1219
|
for (let i = bevelDepth; i > 0; i--) {
|
|
@@ -1246,24 +1267,51 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
1246
1267
|
customStrokeStyle = grad;
|
|
1247
1268
|
}
|
|
1248
1269
|
const drawStrokeLayer = (color, width, blurAmount, opacity, position) => {
|
|
1249
|
-
ctx
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1270
|
+
if (blurAmount > 0 && ctxSupportsFilter(ctx)) {
|
|
1271
|
+
ctx.save();
|
|
1272
|
+
ctx.globalAlpha = opacity / 100;
|
|
1273
|
+
ctx.strokeStyle = color;
|
|
1253
1274
|
ctx.filter = `blur(${blurAmount}px)`;
|
|
1275
|
+
if (position === "outside") {
|
|
1276
|
+
ctx.lineWidth = width * 2;
|
|
1277
|
+
renderLines("stroke");
|
|
1278
|
+
} else if (position === "center") {
|
|
1279
|
+
ctx.lineWidth = width;
|
|
1280
|
+
renderLines("stroke");
|
|
1281
|
+
} else if (position === "inside") {
|
|
1282
|
+
ctx.globalCompositeOperation = "source-atop";
|
|
1283
|
+
ctx.lineWidth = width * 2;
|
|
1284
|
+
renderLines("stroke");
|
|
1285
|
+
}
|
|
1286
|
+
ctx.restore();
|
|
1287
|
+
} else if (blurAmount > 0) {
|
|
1288
|
+
const colorStr = typeof color === "string" ? color : strokeColor;
|
|
1289
|
+
const spread = position === "center" ? width / 2 : width;
|
|
1290
|
+
if (position === "inside") {
|
|
1291
|
+
ctx.save();
|
|
1292
|
+
ctx.globalCompositeOperation = "source-atop";
|
|
1293
|
+
renderWithShadowTrick("stroke", colorStr, blurAmount, 0, 0, opacity, void 0, spread);
|
|
1294
|
+
ctx.restore();
|
|
1295
|
+
} else {
|
|
1296
|
+
renderWithShadowTrick("stroke", colorStr, blurAmount, 0, 0, opacity, void 0, spread);
|
|
1297
|
+
}
|
|
1298
|
+
} else {
|
|
1299
|
+
ctx.save();
|
|
1300
|
+
ctx.globalAlpha = opacity / 100;
|
|
1301
|
+
ctx.strokeStyle = color;
|
|
1302
|
+
if (position === "outside") {
|
|
1303
|
+
ctx.lineWidth = width * 2;
|
|
1304
|
+
renderLines("stroke");
|
|
1305
|
+
} else if (position === "center") {
|
|
1306
|
+
ctx.lineWidth = width;
|
|
1307
|
+
renderLines("stroke");
|
|
1308
|
+
} else if (position === "inside") {
|
|
1309
|
+
ctx.globalCompositeOperation = "source-atop";
|
|
1310
|
+
ctx.lineWidth = width * 2;
|
|
1311
|
+
renderLines("stroke");
|
|
1312
|
+
}
|
|
1313
|
+
ctx.restore();
|
|
1254
1314
|
}
|
|
1255
|
-
if (position === "outside") {
|
|
1256
|
-
ctx.lineWidth = width * 2;
|
|
1257
|
-
renderLines("stroke");
|
|
1258
|
-
} else if (position === "center") {
|
|
1259
|
-
ctx.lineWidth = width;
|
|
1260
|
-
renderLines("stroke");
|
|
1261
|
-
} else if (position === "inside") {
|
|
1262
|
-
ctx.globalCompositeOperation = "source-atop";
|
|
1263
|
-
ctx.lineWidth = width * 2;
|
|
1264
|
-
renderLines("stroke");
|
|
1265
|
-
}
|
|
1266
|
-
ctx.restore();
|
|
1267
1315
|
};
|
|
1268
1316
|
if (sType === "double") {
|
|
1269
1317
|
const outerWidth = strokeWidth + sWidthSecondary;
|