html2canvas-pro 2.4.2 → 2.4.4
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/html2canvas-pro.cjs +463 -72
- package/dist/html2canvas-pro.cjs.map +1 -1
- package/dist/html2canvas-pro.esm.js +463 -72
- package/dist/html2canvas-pro.esm.js.map +1 -1
- package/dist/html2canvas-pro.js +463 -72
- package/dist/html2canvas-pro.js.map +1 -1
- package/dist/html2canvas-pro.min.js +5 -5
- package/dist/lib/core/render-element.js +14 -15
- package/dist/lib/css/property-descriptors/filter.js +14 -4
- package/dist/lib/css/property-descriptors/font-family.js +16 -1
- package/dist/lib/dom/node-parser.js +1 -0
- package/dist/lib/render/canvas/canvas-renderer.js +190 -19
- package/dist/lib/render/canvas/filter-surface.js +270 -0
- package/dist/lib/render/canvas/surface-bounds.js +64 -0
- package/dist/lib/render/canvas/text-renderer.js +13 -19
- package/dist/lib/render/stacking-context.js +6 -5
- package/dist/types/render/canvas/canvas-renderer.d.ts +8 -1
- package/dist/types/render/canvas/filter-surface.d.ts +17 -0
- package/dist/types/render/canvas/surface-bounds.d.ts +10 -0
- package/dist/types/render/canvas/text-renderer.d.ts +10 -8
- package/dist/types/render/stacking-context.d.ts +1 -1
- package/package.json +4 -2
package/dist/html2canvas-pro.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* html2canvas-pro 2.4.
|
|
2
|
+
* html2canvas-pro 2.4.4 <https://yorickshan.github.io/html2canvas-pro/>
|
|
3
3
|
* Copyright (c) 2024-present yorickshan and html2canvas-pro contributors
|
|
4
4
|
* Released under MIT License
|
|
5
5
|
*/
|
|
@@ -5328,10 +5328,15 @@ const fontFamily = {
|
|
|
5328
5328
|
parse: (_context, tokens) => {
|
|
5329
5329
|
const accumulator = [];
|
|
5330
5330
|
const results = [];
|
|
5331
|
+
const quoted = [];
|
|
5332
|
+
let isQuoted = false;
|
|
5331
5333
|
tokens.forEach((token) => {
|
|
5332
5334
|
switch (token.type) {
|
|
5333
5335
|
case 20:
|
|
5336
|
+
accumulator.push(token.value);
|
|
5337
|
+
break;
|
|
5334
5338
|
case 0:
|
|
5339
|
+
isQuoted = true;
|
|
5335
5340
|
accumulator.push(token.value);
|
|
5336
5341
|
break;
|
|
5337
5342
|
case 17:
|
|
@@ -5339,12 +5344,17 @@ const fontFamily = {
|
|
|
5339
5344
|
break;
|
|
5340
5345
|
case 4:
|
|
5341
5346
|
results.push(accumulator.join(" "));
|
|
5347
|
+
quoted.push(isQuoted);
|
|
5348
|
+
isQuoted = false;
|
|
5342
5349
|
accumulator.length = 0;
|
|
5343
5350
|
break;
|
|
5344
5351
|
}
|
|
5345
5352
|
});
|
|
5346
|
-
if (accumulator.length)
|
|
5347
|
-
|
|
5353
|
+
if (accumulator.length) {
|
|
5354
|
+
results.push(accumulator.join(" "));
|
|
5355
|
+
quoted.push(isQuoted);
|
|
5356
|
+
}
|
|
5357
|
+
return results.map((result, index) => quoted[index] || result.indexOf(" ") !== -1 ? `'${result.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\a ").replace(/\r/g, "\\d ").replace(/\f/g, "\\c ")}'` : result);
|
|
5348
5358
|
}
|
|
5349
5359
|
};
|
|
5350
5360
|
//#endregion
|
|
@@ -5707,7 +5717,7 @@ const filter = {
|
|
|
5707
5717
|
const renderedArgs = renderFilterArgs(fn.values);
|
|
5708
5718
|
switch (fn.name) {
|
|
5709
5719
|
case "blur":
|
|
5710
|
-
parts.push(`blur(${renderedArgs}
|
|
5720
|
+
parts.push(`blur(${renderedArgs})`);
|
|
5711
5721
|
break;
|
|
5712
5722
|
case "brightness":
|
|
5713
5723
|
case "contrast":
|
|
@@ -5721,7 +5731,7 @@ const filter = {
|
|
|
5721
5731
|
parts.push(`grayscale(${renderedArgs})`);
|
|
5722
5732
|
break;
|
|
5723
5733
|
case "hue-rotate":
|
|
5724
|
-
parts.push(`hue-rotate(${renderedArgs}
|
|
5734
|
+
parts.push(`hue-rotate(${renderedArgs})`);
|
|
5725
5735
|
break;
|
|
5726
5736
|
case "drop-shadow":
|
|
5727
5737
|
parts.push(`drop-shadow(${renderedArgs})`);
|
|
@@ -5739,14 +5749,20 @@ const filter = {
|
|
|
5739
5749
|
const renderFilterArgs = (values) => {
|
|
5740
5750
|
const parts = [];
|
|
5741
5751
|
for (const v of values) {
|
|
5742
|
-
if (v.type === 31)
|
|
5743
|
-
|
|
5752
|
+
if (v.type === 31) {
|
|
5753
|
+
parts.push(" ");
|
|
5754
|
+
continue;
|
|
5755
|
+
}
|
|
5756
|
+
if (v.type === 18) parts.push(`${v.name}(${renderFilterArgs(v.values)})`);
|
|
5757
|
+
else if (v.type === 4) parts.push(",");
|
|
5758
|
+
else if (v.type === 6) parts.push(v.value);
|
|
5759
|
+
else if (v.type === 15) parts.push(`${v.number}${v.unit}`);
|
|
5744
5760
|
else if (v.type === 17) parts.push(`${v.number}`);
|
|
5745
5761
|
else if (v.type === 16) parts.push(`${v.number}%`);
|
|
5746
5762
|
else if (v.type === 20) parts.push(v.value);
|
|
5747
5763
|
else if (v.type === 5) parts.push(`#${v.value}`);
|
|
5748
5764
|
}
|
|
5749
|
-
return parts.join("
|
|
5765
|
+
return parts.join("").trim();
|
|
5750
5766
|
};
|
|
5751
5767
|
//#endregion
|
|
5752
5768
|
//#region src/css/property-descriptors/font-variant-ligatures.ts
|
|
@@ -8296,7 +8312,7 @@ const parseTree = (context, element) => {
|
|
|
8296
8312
|
return container;
|
|
8297
8313
|
};
|
|
8298
8314
|
const createsRealStackingContext = (node, container, root) => {
|
|
8299
|
-
return container.styles.isPositionedWithZIndex() || container.styles.opacity < 1 || container.styles.isTransformed() || isBodyElement(node) && root.styles.isTransparent();
|
|
8315
|
+
return container.styles.isPositionedWithZIndex() || container.styles.opacity < 1 || Boolean(container.styles.filter) || container.styles.isTransformed() || isBodyElement(node) && root.styles.isTransparent();
|
|
8300
8316
|
};
|
|
8301
8317
|
const createsStackingContext = (styles) => {
|
|
8302
8318
|
if (styles.isPositioned() || styles.isFloating()) return true;
|
|
@@ -8733,12 +8749,13 @@ var ElementPaint = class {
|
|
|
8733
8749
|
this.effects.push(new TransformEffect(offsetX, offsetY, zoomMatrix));
|
|
8734
8750
|
}
|
|
8735
8751
|
}
|
|
8736
|
-
getEffects(target) {
|
|
8752
|
+
getEffects(target, surfaceRoot) {
|
|
8737
8753
|
let inFlow = [2, 3].indexOf(this.container.styles.position) === -1;
|
|
8738
|
-
let parent = this.parent;
|
|
8739
|
-
const
|
|
8754
|
+
let parent = this === surfaceRoot ? null : this.parent;
|
|
8755
|
+
const sourceEffects = (paint) => paint.effects.filter((effect) => paint !== surfaceRoot || !isFilterEffect(effect) && !isOpacityEffect(effect));
|
|
8756
|
+
const effects = sourceEffects(this);
|
|
8740
8757
|
while (parent) {
|
|
8741
|
-
const croplessEffects = parent.
|
|
8758
|
+
const croplessEffects = sourceEffects(parent).filter((effect) => !isClipEffect(effect));
|
|
8742
8759
|
if (inFlow || parent.container.styles.position !== 0 || !parent.parent) {
|
|
8743
8760
|
inFlow = [2, 3].indexOf(parent.container.styles.position) === -1;
|
|
8744
8761
|
if (hasOverflowClip(parent.container.styles)) {
|
|
@@ -8748,7 +8765,7 @@ var ElementPaint = class {
|
|
|
8748
8765
|
}
|
|
8749
8766
|
effects.unshift(...croplessEffects);
|
|
8750
8767
|
} else effects.unshift(...croplessEffects);
|
|
8751
|
-
parent = parent.parent;
|
|
8768
|
+
parent = parent === surfaceRoot ? null : parent.parent;
|
|
8752
8769
|
}
|
|
8753
8770
|
return effects.filter((effect) => contains(effect.target, target));
|
|
8754
8771
|
}
|
|
@@ -9029,6 +9046,268 @@ const calculateBackgroundRepeatPath = (repeat, [x, y], [width, height], backgrou
|
|
|
9029
9046
|
}
|
|
9030
9047
|
};
|
|
9031
9048
|
//#endregion
|
|
9049
|
+
//#region src/core/abort-helper.ts
|
|
9050
|
+
/**
|
|
9051
|
+
* AbortSignal helper utilities.
|
|
9052
|
+
*/
|
|
9053
|
+
/**
|
|
9054
|
+
* Throw a DOMException if the given AbortSignal is already aborted.
|
|
9055
|
+
* Used at checkpoints throughout the rendering pipeline to support
|
|
9056
|
+
* cancellable renders.
|
|
9057
|
+
*
|
|
9058
|
+
* @param signal - The AbortSignal to check. If undefined or not aborted, this is a no-op.
|
|
9059
|
+
* @throws {DOMException} With name 'AbortError' if the signal has been aborted.
|
|
9060
|
+
*/
|
|
9061
|
+
const throwIfAborted = (signal) => {
|
|
9062
|
+
if (signal?.aborted) throw new DOMException("The operation was aborted.", "AbortError");
|
|
9063
|
+
};
|
|
9064
|
+
//#endregion
|
|
9065
|
+
//#region src/render/canvas/filter-surface.ts
|
|
9066
|
+
var FilterSurfaceError = class extends Error {};
|
|
9067
|
+
const releaseSurface = (canvas) => {
|
|
9068
|
+
canvas.width = canvas.height = 0;
|
|
9069
|
+
};
|
|
9070
|
+
function parseSimpleFilter(value) {
|
|
9071
|
+
if (!value || value === "none") return { blur: 0 };
|
|
9072
|
+
const effect = new FilterEffect(value);
|
|
9073
|
+
const shadows = value.match(/drop-shadow\(/g) || [];
|
|
9074
|
+
if (shadows.length > 1 || shadows.length === 1 && !effect.shadow) return null;
|
|
9075
|
+
const blur = effect.safeFilterString.match(/^blur\((\d+(?:\.\d+)?)px\)$/);
|
|
9076
|
+
if (effect.safeFilterString && !blur) return null;
|
|
9077
|
+
if (blur && shadows.length && value.indexOf("blur(") > value.indexOf("drop-shadow(")) return null;
|
|
9078
|
+
return {
|
|
9079
|
+
blur: blur ? Number(blur[1]) : 0,
|
|
9080
|
+
shadow: effect.shadow ? {
|
|
9081
|
+
x: effect.shadow.offsetX,
|
|
9082
|
+
y: effect.shadow.offsetY,
|
|
9083
|
+
blur: effect.shadow.blur,
|
|
9084
|
+
color: effect.shadow.color
|
|
9085
|
+
} : void 0
|
|
9086
|
+
};
|
|
9087
|
+
}
|
|
9088
|
+
function filterOutset(filter) {
|
|
9089
|
+
return Math.ceil(3 * (filter.blur + (filter.shadow?.blur ?? 0)) + Math.max(Math.abs(filter.shadow?.x ?? 0), Math.abs(filter.shadow?.y ?? 0)));
|
|
9090
|
+
}
|
|
9091
|
+
function canvasLike(source) {
|
|
9092
|
+
const canvas = document.createElement("canvas");
|
|
9093
|
+
canvas.width = source.width;
|
|
9094
|
+
canvas.height = source.height;
|
|
9095
|
+
return canvas;
|
|
9096
|
+
}
|
|
9097
|
+
const decodeSurface = (image, signal) => new Promise((resolve, reject) => {
|
|
9098
|
+
const finish = (error) => {
|
|
9099
|
+
clearTimeout(timer);
|
|
9100
|
+
signal?.removeEventListener("abort", abort);
|
|
9101
|
+
if (error) reject(error);
|
|
9102
|
+
else resolve();
|
|
9103
|
+
};
|
|
9104
|
+
const abort = () => finish(new DOMException("The operation was aborted.", "AbortError"));
|
|
9105
|
+
const timer = setTimeout(() => finish(new FilterSurfaceError("Filter surface image decode timed out")), 1e4);
|
|
9106
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
9107
|
+
if (signal?.aborted) abort();
|
|
9108
|
+
else image.decode().then(() => finish(), () => finish(new FilterSurfaceError("Filter surface image decode failed")));
|
|
9109
|
+
});
|
|
9110
|
+
const canvasFilterSupport = /* @__PURE__ */ new WeakMap();
|
|
9111
|
+
function supportsNativeFilters(owner = document) {
|
|
9112
|
+
const cached = canvasFilterSupport.get(owner);
|
|
9113
|
+
if (cached !== void 0) return cached;
|
|
9114
|
+
const source = owner.createElement("canvas");
|
|
9115
|
+
const target = owner.createElement("canvas");
|
|
9116
|
+
let supported = false;
|
|
9117
|
+
try {
|
|
9118
|
+
source.width = source.height = target.width = target.height = 32;
|
|
9119
|
+
const input = source.getContext("2d");
|
|
9120
|
+
const context = target.getContext("2d");
|
|
9121
|
+
if (input && context && "filter" in context) {
|
|
9122
|
+
input.fillStyle = "#fff";
|
|
9123
|
+
input.fillRect(8, 8, 8, 8);
|
|
9124
|
+
context.filter = "blur(2px)";
|
|
9125
|
+
context.drawImage(source, 0, 0);
|
|
9126
|
+
const blurred = context.getImageData(6, 12, 1, 1).data[3] > 0;
|
|
9127
|
+
context.clearRect(0, 0, 32, 32);
|
|
9128
|
+
context.filter = "drop-shadow(12px 0px 0px rgba(255, 0, 0, 0.5))";
|
|
9129
|
+
context.drawImage(source, 0, 0);
|
|
9130
|
+
const shadow = context.getImageData(24, 12, 1, 1).data;
|
|
9131
|
+
supported = blurred && shadow[0] > 240 && shadow[1] < 10 && shadow[3] >= 126 && shadow[3] <= 129;
|
|
9132
|
+
}
|
|
9133
|
+
} catch {} finally {
|
|
9134
|
+
releaseSurface(source);
|
|
9135
|
+
releaseSurface(target);
|
|
9136
|
+
}
|
|
9137
|
+
canvasFilterSupport.set(owner, supported);
|
|
9138
|
+
return supported;
|
|
9139
|
+
}
|
|
9140
|
+
function tryNativeFilterSurface(source, { blur, shadow }, opacity, scale, signal) {
|
|
9141
|
+
let filtered;
|
|
9142
|
+
let output;
|
|
9143
|
+
let complete = false;
|
|
9144
|
+
try {
|
|
9145
|
+
throwIfAborted(signal);
|
|
9146
|
+
filtered = canvasLike(source);
|
|
9147
|
+
const context = filtered.getContext("2d");
|
|
9148
|
+
if (!context || !("filter" in context)) return null;
|
|
9149
|
+
const functions = [];
|
|
9150
|
+
if (blur) functions.push(`blur(${blur * scale}px)`);
|
|
9151
|
+
if (shadow) functions.push(`drop-shadow(${shadow.x * scale}px ${shadow.y * scale}px ${shadow.blur * scale}px ${shadow.color})`);
|
|
9152
|
+
context.filter = functions.join(" ");
|
|
9153
|
+
if (context.filter === "none") return null;
|
|
9154
|
+
context.drawImage(source, 0, 0);
|
|
9155
|
+
throwIfAborted(signal);
|
|
9156
|
+
output = canvasLike(source);
|
|
9157
|
+
const composite = output.getContext("2d");
|
|
9158
|
+
if (!composite) return null;
|
|
9159
|
+
composite.globalAlpha = opacity;
|
|
9160
|
+
composite.drawImage(filtered, 0, 0);
|
|
9161
|
+
throwIfAborted(signal);
|
|
9162
|
+
complete = true;
|
|
9163
|
+
return output;
|
|
9164
|
+
} catch {
|
|
9165
|
+
throwIfAborted(signal);
|
|
9166
|
+
return null;
|
|
9167
|
+
} finally {
|
|
9168
|
+
if (filtered) releaseSurface(filtered);
|
|
9169
|
+
if (output && !complete) releaseSurface(output);
|
|
9170
|
+
}
|
|
9171
|
+
}
|
|
9172
|
+
async function renderFilterSurface(source, filter, opacity, scale, signal) {
|
|
9173
|
+
throwIfAborted(signal);
|
|
9174
|
+
if ((filter.blur || filter.shadow) && supportsNativeFilters()) {
|
|
9175
|
+
const output = tryNativeFilterSurface(source, filter, opacity, scale, signal);
|
|
9176
|
+
if (output) return output;
|
|
9177
|
+
}
|
|
9178
|
+
return renderSvgFilterSurface(source, filter, opacity, scale, signal);
|
|
9179
|
+
}
|
|
9180
|
+
async function renderSvgFilterSurface(source, { blur, shadow }, opacity, scale, signal) {
|
|
9181
|
+
throwIfAborted(signal);
|
|
9182
|
+
if (!blur && !shadow) {
|
|
9183
|
+
const output = canvasLike(source);
|
|
9184
|
+
const context = output.getContext("2d");
|
|
9185
|
+
if (!context) {
|
|
9186
|
+
releaseSurface(output);
|
|
9187
|
+
throw new FilterSurfaceError("Filter surface canvas is unavailable");
|
|
9188
|
+
}
|
|
9189
|
+
context.globalAlpha = opacity;
|
|
9190
|
+
try {
|
|
9191
|
+
context.drawImage(source, 0, 0);
|
|
9192
|
+
return output;
|
|
9193
|
+
} catch {
|
|
9194
|
+
releaseSurface(output);
|
|
9195
|
+
throw new FilterSurfaceError("Filter surface image cannot be drawn");
|
|
9196
|
+
}
|
|
9197
|
+
}
|
|
9198
|
+
const namespace = "http://www.w3.org/2000/svg";
|
|
9199
|
+
const element = (tag, attributes = {}) => {
|
|
9200
|
+
const node = document.createElementNS(namespace, tag);
|
|
9201
|
+
Object.entries(attributes).forEach(([key, value]) => node.setAttribute(key, String(value)));
|
|
9202
|
+
return node;
|
|
9203
|
+
};
|
|
9204
|
+
const svg = element("svg", {
|
|
9205
|
+
width: source.width,
|
|
9206
|
+
height: source.height,
|
|
9207
|
+
viewBox: `0 0 ${source.width} ${source.height}`
|
|
9208
|
+
});
|
|
9209
|
+
const defs = element("defs");
|
|
9210
|
+
const filter = element("filter", {
|
|
9211
|
+
id: "composited-filter",
|
|
9212
|
+
filterUnits: "userSpaceOnUse",
|
|
9213
|
+
x: 0,
|
|
9214
|
+
y: 0,
|
|
9215
|
+
width: source.width,
|
|
9216
|
+
height: source.height,
|
|
9217
|
+
"color-interpolation-filters": "sRGB"
|
|
9218
|
+
});
|
|
9219
|
+
if (blur) filter.appendChild(element("feGaussianBlur", {
|
|
9220
|
+
stdDeviation: blur * scale,
|
|
9221
|
+
result: "blurred-source"
|
|
9222
|
+
}));
|
|
9223
|
+
if (shadow) {
|
|
9224
|
+
filter.appendChild(element("feGaussianBlur", {
|
|
9225
|
+
in: blur ? "blurred-source" : "SourceAlpha",
|
|
9226
|
+
stdDeviation: shadow.blur * scale
|
|
9227
|
+
}));
|
|
9228
|
+
filter.appendChild(element("feOffset", {
|
|
9229
|
+
dx: shadow.x * scale,
|
|
9230
|
+
dy: shadow.y * scale,
|
|
9231
|
+
result: "offset-shadow"
|
|
9232
|
+
}));
|
|
9233
|
+
filter.appendChild(element("feFlood", { "flood-color": shadow.color }));
|
|
9234
|
+
filter.appendChild(element("feComposite", {
|
|
9235
|
+
in2: "offset-shadow",
|
|
9236
|
+
operator: "in"
|
|
9237
|
+
}));
|
|
9238
|
+
const merge = element("feMerge");
|
|
9239
|
+
merge.appendChild(element("feMergeNode"));
|
|
9240
|
+
merge.appendChild(element("feMergeNode", { in: blur ? "blurred-source" : "SourceGraphic" }));
|
|
9241
|
+
filter.appendChild(merge);
|
|
9242
|
+
}
|
|
9243
|
+
defs.appendChild(filter);
|
|
9244
|
+
svg.appendChild(defs);
|
|
9245
|
+
let sourceUrl;
|
|
9246
|
+
try {
|
|
9247
|
+
sourceUrl = source.toDataURL("image/png");
|
|
9248
|
+
if (sourceUrl === "data:,") throw new Error("Canvas size is unsupported");
|
|
9249
|
+
} catch {
|
|
9250
|
+
throw new FilterSurfaceError("Filter surface pixels cannot be serialized");
|
|
9251
|
+
}
|
|
9252
|
+
svg.appendChild(element("image", {
|
|
9253
|
+
width: source.width,
|
|
9254
|
+
height: source.height,
|
|
9255
|
+
href: sourceUrl,
|
|
9256
|
+
filter: "url(#composited-filter)"
|
|
9257
|
+
}));
|
|
9258
|
+
const image = new Image();
|
|
9259
|
+
let output;
|
|
9260
|
+
try {
|
|
9261
|
+
image.src = `data:image/svg+xml,${encodeURIComponent(new XMLSerializer().serializeToString(svg))}`;
|
|
9262
|
+
await decodeSurface(image, signal);
|
|
9263
|
+
throwIfAborted(signal);
|
|
9264
|
+
output = canvasLike(source);
|
|
9265
|
+
const context = output.getContext("2d");
|
|
9266
|
+
if (!context) throw new FilterSurfaceError("Filter surface canvas is unavailable");
|
|
9267
|
+
context.globalAlpha = opacity;
|
|
9268
|
+
context.drawImage(image, 0, 0);
|
|
9269
|
+
return output;
|
|
9270
|
+
} catch (error) {
|
|
9271
|
+
if (output) releaseSurface(output);
|
|
9272
|
+
if (signal?.aborted) throwIfAborted(signal);
|
|
9273
|
+
throw error instanceof FilterSurfaceError ? error : new FilterSurfaceError("Filter surface image cannot be drawn");
|
|
9274
|
+
} finally {
|
|
9275
|
+
image.removeAttribute("src");
|
|
9276
|
+
}
|
|
9277
|
+
}
|
|
9278
|
+
function reserveSurface(budget, width, height) {
|
|
9279
|
+
const pixels = width * height * 4;
|
|
9280
|
+
if (!Number.isFinite(pixels) || width <= 0 || height <= 0 || width > 8192 || height > 8192 || budget.pixels + pixels > 16777216) return 0;
|
|
9281
|
+
budget.pixels += pixels;
|
|
9282
|
+
return pixels;
|
|
9283
|
+
}
|
|
9284
|
+
const expand = (b, margin) => b.add(-margin, -margin, margin * 2, margin * 2);
|
|
9285
|
+
const union = (a, b) => new Bounds(Math.min(a.left, b.left), Math.min(a.top, b.top), Math.max(a.left + a.width, b.left + b.width) - Math.min(a.left, b.left), Math.max(a.top + a.height, b.top + b.height) - Math.min(a.top, b.top));
|
|
9286
|
+
function surfaceBounds(container) {
|
|
9287
|
+
const styles = container.styles;
|
|
9288
|
+
let bounds = container.bounds;
|
|
9289
|
+
for (const shadow of styles.boxShadow) if (!shadow.inset) {
|
|
9290
|
+
const spread = Math.max(0, shadow.spread.number) + 3 * shadow.blur.number;
|
|
9291
|
+
bounds = union(bounds, expand(container.bounds.add(shadow.offsetX.number, shadow.offsetY.number, 0, 0), spread));
|
|
9292
|
+
}
|
|
9293
|
+
for (const text of container.textNodes) for (const part of text.textBounds) {
|
|
9294
|
+
const ink = expand(part.bounds, 2 * getAbsoluteValue(styles.fontSize, container.bounds.height) + styles.webkitTextStrokeWidth);
|
|
9295
|
+
bounds = union(bounds, ink);
|
|
9296
|
+
for (const shadow of styles.textShadow) bounds = union(bounds, expand(ink.add(shadow.offsetX.number, shadow.offsetY.number, 0, 0), 3 * shadow.blur.number));
|
|
9297
|
+
}
|
|
9298
|
+
for (const child of container.elements) if (child.styles.isVisible()) bounds = union(bounds, surfaceBounds(child));
|
|
9299
|
+
const filter = parseSimpleFilter(styles.filter);
|
|
9300
|
+
return expand(bounds, filter ? filterOutset(filter) : 0);
|
|
9301
|
+
}
|
|
9302
|
+
function cropSurface(bounds, viewport, margin, scale) {
|
|
9303
|
+
const area = expand(viewport, margin);
|
|
9304
|
+
const left = Math.floor(Math.max(bounds.left, area.left) * scale) / scale;
|
|
9305
|
+
const top = Math.floor(Math.max(bounds.top, area.top) * scale) / scale;
|
|
9306
|
+
const right = Math.ceil(Math.min(bounds.left + bounds.width, area.left + area.width) * scale) / scale;
|
|
9307
|
+
const bottom = Math.ceil(Math.min(bounds.top + bounds.height, area.top + area.height) * scale) / scale;
|
|
9308
|
+
return new Bounds(left, top, Math.max(0, right - left), Math.max(0, bottom - top));
|
|
9309
|
+
}
|
|
9310
|
+
//#endregion
|
|
9032
9311
|
//#region src/render/canvas/text/text-decoration-renderer.ts
|
|
9033
9312
|
var TextDecorationRenderer = class {
|
|
9034
9313
|
constructor(ctx) {
|
|
@@ -9158,8 +9437,8 @@ const iOSBrokenFonts = ["-apple-system", "system-ui"];
|
|
|
9158
9437
|
const TEXT_OVERFLOW_EPSILON = 1;
|
|
9159
9438
|
/**
|
|
9160
9439
|
* Detect CJK (Chinese, Japanese, Korean) characters in a string.
|
|
9161
|
-
*
|
|
9162
|
-
*
|
|
9440
|
+
* Vertical writing modes use it to keep CJK glyphs upright and on the
|
|
9441
|
+
* ideographic baseline while other scripts are rotated.
|
|
9163
9442
|
*
|
|
9164
9443
|
* Covers:
|
|
9165
9444
|
* U+2E80–U+2FFF CJK Radicals Supplement, Kangxi Radicals
|
|
@@ -9229,15 +9508,17 @@ var TextRenderer = class {
|
|
|
9229
9508
|
return w;
|
|
9230
9509
|
}
|
|
9231
9510
|
/**
|
|
9232
|
-
* Iterate grapheme clusters one-by-one, applying correct letter-spacing
|
|
9233
|
-
* per-script baseline for each character.
|
|
9511
|
+
* Iterate grapheme clusters one-by-one, applying correct letter-spacing.
|
|
9234
9512
|
*
|
|
9235
9513
|
* Issue #73: When letter-spacing is non-zero, text must be rendered character by
|
|
9236
|
-
* character
|
|
9237
|
-
*
|
|
9238
|
-
*
|
|
9239
|
-
*
|
|
9240
|
-
*
|
|
9514
|
+
* character, adding `letterSpacing` to each character's advance width.
|
|
9515
|
+
*
|
|
9516
|
+
* In horizontal text every glyph keeps the alphabetic baseline, CJK included:
|
|
9517
|
+
* inline layout aligns a whole line on its alphabetic baseline regardless of
|
|
9518
|
+
* script, and `baseline` was measured for that. Drawing CJK graphemes at the
|
|
9519
|
+
* same y with `textBaseline = 'ideographic'` lifts them above the Latin text
|
|
9520
|
+
* next to them, and away from where the `letterSpacing === 0` whole-string
|
|
9521
|
+
* path draws them.
|
|
9241
9522
|
*
|
|
9242
9523
|
* The `renderFn` callback receives (letter, x, y) and performs the actual draw
|
|
9243
9524
|
* call (fillText or strokeText), allowing fill and stroke paths to share one
|
|
@@ -9253,12 +9534,7 @@ var TextRenderer = class {
|
|
|
9253
9534
|
const fontString = this.ctx.font;
|
|
9254
9535
|
let left = text.bounds.left;
|
|
9255
9536
|
for (const letter of letters) {
|
|
9256
|
-
|
|
9257
|
-
const savedBaseline = this.ctx.textBaseline;
|
|
9258
|
-
this.ctx.textBaseline = "ideographic";
|
|
9259
|
-
renderFn(letter, left, y);
|
|
9260
|
-
this.ctx.textBaseline = savedBaseline;
|
|
9261
|
-
} else renderFn(letter, left, y);
|
|
9537
|
+
renderFn(letter, left, y);
|
|
9262
9538
|
left += this.cachedMeasureText(letter, fontString) + letterSpacing;
|
|
9263
9539
|
}
|
|
9264
9540
|
}
|
|
@@ -9300,7 +9576,7 @@ var TextRenderer = class {
|
|
|
9300
9576
|
/**
|
|
9301
9577
|
* Render text with letter-spacing applied (fill pass).
|
|
9302
9578
|
* When letterSpacing is 0 the whole string is drawn in one call; otherwise each
|
|
9303
|
-
* grapheme is drawn individually so spacing
|
|
9579
|
+
* grapheme is drawn individually so spacing is applied correctly.
|
|
9304
9580
|
*/
|
|
9305
9581
|
renderTextWithLetterSpacing(text, letterSpacing, baseline, writingMode = 0) {
|
|
9306
9582
|
this.renderFillText(text, letterSpacing, baseline, writingMode);
|
|
@@ -10535,12 +10811,19 @@ const canvasTextAlign = (textAlign) => {
|
|
|
10535
10811
|
//#endregion
|
|
10536
10812
|
//#region src/render/canvas/canvas-renderer.ts
|
|
10537
10813
|
var CanvasRenderer = class CanvasRenderer {
|
|
10538
|
-
constructor(context, options) {
|
|
10814
|
+
constructor(context, options, surfaceRoot, surfaceBudget = { pixels: 0 }) {
|
|
10815
|
+
this.surfaceRoot = surfaceRoot;
|
|
10816
|
+
this.surfaceBudget = surfaceBudget;
|
|
10817
|
+
this.legacySubtree = false;
|
|
10539
10818
|
this.context = context;
|
|
10540
10819
|
this.options = options;
|
|
10541
10820
|
this.canvas = options.canvas ? options.canvas : document.createElement("canvas");
|
|
10542
10821
|
const ctx = this.canvas.getContext("2d");
|
|
10543
|
-
if (!ctx)
|
|
10822
|
+
if (!ctx) {
|
|
10823
|
+
if (!options.canvas) releaseSurface(this.canvas);
|
|
10824
|
+
if (surfaceRoot) throw new FilterSurfaceError("Filter surface canvas is unavailable");
|
|
10825
|
+
throw new Error("Failed to get 2D rendering context from canvas");
|
|
10826
|
+
}
|
|
10544
10827
|
this.ctx = ctx;
|
|
10545
10828
|
if (!options.canvas) {
|
|
10546
10829
|
this.canvas.width = Math.floor(options.width * options.scale);
|
|
@@ -10576,7 +10859,77 @@ var CanvasRenderer = class CanvasRenderer {
|
|
|
10576
10859
|
this.context.logger.debug(`Canvas renderer initialized (${options.width}x${options.height}) with scale ${options.scale}`);
|
|
10577
10860
|
}
|
|
10578
10861
|
async renderStack(stack) {
|
|
10579
|
-
|
|
10862
|
+
const styles = stack.element.container.styles;
|
|
10863
|
+
if (styles.isVisible()) {
|
|
10864
|
+
const filter = parseSimpleFilter(styles.filter);
|
|
10865
|
+
if (stack.element !== this.surfaceRoot && !this.legacySubtree && filter && (styles.filter || styles.opacity < 1) && this.canComposite(stack.element)) {
|
|
10866
|
+
if (!await this.renderCompositedStack(stack, filter)) {
|
|
10867
|
+
this.legacySubtree = true;
|
|
10868
|
+
try {
|
|
10869
|
+
await this.renderStackContent(stack);
|
|
10870
|
+
} finally {
|
|
10871
|
+
this.legacySubtree = false;
|
|
10872
|
+
}
|
|
10873
|
+
}
|
|
10874
|
+
return;
|
|
10875
|
+
}
|
|
10876
|
+
await this.renderStackContent(stack);
|
|
10877
|
+
}
|
|
10878
|
+
}
|
|
10879
|
+
canComposite(paint) {
|
|
10880
|
+
const supported = (container) => {
|
|
10881
|
+
const styles = container.styles;
|
|
10882
|
+
return !styles.isTransformed() && styles.zoom === 1 && styles.mixBlendMode === MIX_BLEND_MODE$1.NORMAL && styles.clipPath.type === 0 && !contains(styles.display, 2048) && parseSimpleFilter(styles.filter) !== null;
|
|
10883
|
+
};
|
|
10884
|
+
const subtree = (container) => supported(container) && container.elements.every(subtree);
|
|
10885
|
+
let ancestor = paint.parent;
|
|
10886
|
+
while (ancestor && ancestor !== this.surfaceRoot) {
|
|
10887
|
+
if (!supported(ancestor.container)) return false;
|
|
10888
|
+
ancestor = ancestor.parent;
|
|
10889
|
+
}
|
|
10890
|
+
return subtree(paint.container);
|
|
10891
|
+
}
|
|
10892
|
+
async renderCompositedStack(stack, filter) {
|
|
10893
|
+
const signal = this.options.signal;
|
|
10894
|
+
if (signal?.aborted) throw new DOMException("The operation was aborted.", "AbortError");
|
|
10895
|
+
const bounds = cropSurface(surfaceBounds(stack.element.container), new Bounds(this.options.x, this.options.y, this.options.width, this.options.height), filterOutset(filter), this.options.scale);
|
|
10896
|
+
if (!bounds.width || !bounds.height) return true;
|
|
10897
|
+
const reserved = reserveSurface(this.surfaceBudget, Math.ceil(bounds.width * this.options.scale), Math.ceil(bounds.height * this.options.scale));
|
|
10898
|
+
if (!reserved) {
|
|
10899
|
+
this.context.logger.info("Filter surface budget exceeded; using the existing renderer");
|
|
10900
|
+
return false;
|
|
10901
|
+
}
|
|
10902
|
+
const options = {
|
|
10903
|
+
...this.options,
|
|
10904
|
+
canvas: void 0,
|
|
10905
|
+
backgroundColor: null,
|
|
10906
|
+
x: bounds.left,
|
|
10907
|
+
y: bounds.top,
|
|
10908
|
+
width: bounds.width,
|
|
10909
|
+
height: bounds.height
|
|
10910
|
+
};
|
|
10911
|
+
let source;
|
|
10912
|
+
let filtered;
|
|
10913
|
+
try {
|
|
10914
|
+
source = new CanvasRenderer(this.context, options, stack.element, this.surfaceBudget);
|
|
10915
|
+
await source.renderStackContent(stack);
|
|
10916
|
+
source.effectsRenderer.applyEffects([]);
|
|
10917
|
+
filtered = await renderFilterSurface(source.canvas, filter, stack.element.container.styles.opacity, options.scale, signal);
|
|
10918
|
+
if (signal?.aborted) throw new DOMException("The operation was aborted.", "AbortError");
|
|
10919
|
+
const own = stack.element.effects;
|
|
10920
|
+
const effects = stack.element.getEffects(4, this.surfaceRoot).filter((effect) => !own.includes(effect) || !isFilterEffect(effect) && !isOpacityEffect(effect) && !isClipEffect(effect));
|
|
10921
|
+
this.effectsRenderer.applyEffects(effects);
|
|
10922
|
+
this.ctx.drawImage(filtered, options.x, options.y, filtered.width / options.scale, filtered.height / options.scale);
|
|
10923
|
+
return true;
|
|
10924
|
+
} catch (error) {
|
|
10925
|
+
if (!(error instanceof FilterSurfaceError)) throw error;
|
|
10926
|
+
this.context.logger.info(`${error.message}; using the existing renderer`);
|
|
10927
|
+
return false;
|
|
10928
|
+
} finally {
|
|
10929
|
+
if (source) releaseSurface(source.canvas);
|
|
10930
|
+
if (filtered) releaseSurface(filtered);
|
|
10931
|
+
this.surfaceBudget.pixels -= reserved;
|
|
10932
|
+
}
|
|
10580
10933
|
}
|
|
10581
10934
|
async renderNode(paint) {
|
|
10582
10935
|
if (paint.container.debugRender) debugger;
|
|
@@ -10604,7 +10957,7 @@ var CanvasRenderer = class CanvasRenderer {
|
|
|
10604
10957
|
}
|
|
10605
10958
|
}
|
|
10606
10959
|
async renderNodeContent(paint) {
|
|
10607
|
-
this.effectsRenderer.applyEffects(paint.getEffects(4));
|
|
10960
|
+
this.effectsRenderer.applyEffects(paint.getEffects(4, this.surfaceRoot));
|
|
10608
10961
|
const container = paint.container;
|
|
10609
10962
|
const curves = paint.curves;
|
|
10610
10963
|
const styles = container.styles;
|
|
@@ -10668,8 +11021,51 @@ var CanvasRenderer = class CanvasRenderer {
|
|
|
10668
11021
|
formatPath(paths) {
|
|
10669
11022
|
formatCanvasPath(this.ctx, paths);
|
|
10670
11023
|
}
|
|
11024
|
+
async renderSurfaceBoxShadow(paint, shadow) {
|
|
11025
|
+
const spread = shadow.spread.number;
|
|
11026
|
+
const blur = shadow.blur.number / 2;
|
|
11027
|
+
const margin = Math.ceil(3 * blur) + 1;
|
|
11028
|
+
const box = paint.container.bounds.add(-spread, -spread, 2 * spread, 2 * spread);
|
|
11029
|
+
if (box.width <= 0 || box.height <= 0) return;
|
|
11030
|
+
const scale = this.options.scale;
|
|
11031
|
+
const bounds = cropSurface(box.add(-margin, -margin, 2 * margin, 2 * margin), new Bounds(this.options.x - shadow.offsetX.number, this.options.y - shadow.offsetY.number, this.options.width, this.options.height), margin, scale);
|
|
11032
|
+
if (!bounds.width || !bounds.height) return;
|
|
11033
|
+
const width = Math.ceil(bounds.width * scale), height = Math.ceil(bounds.height * scale);
|
|
11034
|
+
const reserved = reserveSurface(this.surfaceBudget, width, height);
|
|
11035
|
+
if (!reserved) throw new FilterSurfaceError("Box shadow surface budget exceeded");
|
|
11036
|
+
let source;
|
|
11037
|
+
let filtered;
|
|
11038
|
+
try {
|
|
11039
|
+
source = document.createElement("canvas");
|
|
11040
|
+
source.width = width;
|
|
11041
|
+
source.height = height;
|
|
11042
|
+
const ctx = source.getContext("2d");
|
|
11043
|
+
if (!ctx) throw new FilterSurfaceError("Box shadow surface canvas is unavailable");
|
|
11044
|
+
ctx.scale(scale, scale);
|
|
11045
|
+
ctx.translate(-bounds.left, -bounds.top);
|
|
11046
|
+
createCanvasPath(ctx, transformPath(calculateBorderBoxPath(paint.curves), -spread, -spread, 2 * spread, 2 * spread));
|
|
11047
|
+
ctx.fillStyle = asString(shadow.color);
|
|
11048
|
+
ctx.fill();
|
|
11049
|
+
filtered = await renderFilterSurface(source, { blur }, 1, scale, this.options.signal);
|
|
11050
|
+
this.ctx.save();
|
|
11051
|
+
try {
|
|
11052
|
+
this.ctx.beginPath();
|
|
11053
|
+
this.ctx.rect(this.options.x, this.options.y, this.options.width, this.options.height);
|
|
11054
|
+
formatCanvasPath(this.ctx, calculateBorderBoxPath(paint.curves));
|
|
11055
|
+
this.ctx.closePath();
|
|
11056
|
+
this.ctx.clip("evenodd");
|
|
11057
|
+
this.ctx.drawImage(filtered, bounds.left + shadow.offsetX.number, bounds.top + shadow.offsetY.number, filtered.width / scale, filtered.height / scale);
|
|
11058
|
+
} finally {
|
|
11059
|
+
this.ctx.restore();
|
|
11060
|
+
}
|
|
11061
|
+
} finally {
|
|
11062
|
+
if (source) releaseSurface(source);
|
|
11063
|
+
if (filtered) releaseSurface(filtered);
|
|
11064
|
+
this.surfaceBudget.pixels -= reserved;
|
|
11065
|
+
}
|
|
11066
|
+
}
|
|
10671
11067
|
async renderNodeBackgroundAndBorders(paint) {
|
|
10672
|
-
this.effectsRenderer.applyEffects(paint.getEffects(2));
|
|
11068
|
+
this.effectsRenderer.applyEffects(paint.getEffects(2, this.surfaceRoot));
|
|
10673
11069
|
const styles = paint.container.styles;
|
|
10674
11070
|
const hasBackground = !isTransparent(styles.backgroundColor) || styles.backgroundImage.length;
|
|
10675
11071
|
const hasTextClip = hasTextBackgroundClip(styles);
|
|
@@ -10710,7 +11106,11 @@ var CanvasRenderer = class CanvasRenderer {
|
|
|
10710
11106
|
await this.backgroundRenderer.renderBackgroundImage(paint.container);
|
|
10711
11107
|
this.ctx.restore();
|
|
10712
11108
|
}
|
|
10713
|
-
styles.boxShadow.slice(0).reverse()
|
|
11109
|
+
for (const shadow of styles.boxShadow.slice(0).reverse()) {
|
|
11110
|
+
if (this.surfaceRoot && !shadow.inset) {
|
|
11111
|
+
await this.renderSurfaceBoxShadow(paint, shadow);
|
|
11112
|
+
continue;
|
|
11113
|
+
}
|
|
10714
11114
|
this.ctx.save();
|
|
10715
11115
|
const borderBoxArea = calculateBorderBoxPath(paint.curves);
|
|
10716
11116
|
const maskOffset = shadow.inset ? 0 : SHADOW_MASK_OFFSET;
|
|
@@ -10724,14 +11124,15 @@ var CanvasRenderer = class CanvasRenderer {
|
|
|
10724
11124
|
this.ctx.clip();
|
|
10725
11125
|
this.path(shadowPaintingArea);
|
|
10726
11126
|
}
|
|
10727
|
-
this.
|
|
10728
|
-
this.ctx.
|
|
11127
|
+
const shadowScale = this.surfaceRoot ? this.options.scale : 1;
|
|
11128
|
+
this.ctx.shadowOffsetX = (shadow.offsetX.number + maskOffset) * shadowScale;
|
|
11129
|
+
this.ctx.shadowOffsetY = shadow.offsetY.number * shadowScale;
|
|
10729
11130
|
this.ctx.shadowColor = asString(shadow.color);
|
|
10730
|
-
this.ctx.shadowBlur = shadow.blur.number;
|
|
11131
|
+
this.ctx.shadowBlur = shadow.blur.number * shadowScale;
|
|
10731
11132
|
this.ctx.fillStyle = shadow.inset ? asString(shadow.color) : "rgba(0,0,0,1)";
|
|
10732
11133
|
this.ctx.fill();
|
|
10733
11134
|
this.ctx.restore();
|
|
10734
|
-
}
|
|
11135
|
+
}
|
|
10735
11136
|
}
|
|
10736
11137
|
if (styles.borderImageSource) {
|
|
10737
11138
|
const source = styles.borderImageSource;
|
|
@@ -10787,14 +11188,19 @@ var CanvasRenderer = class CanvasRenderer {
|
|
|
10787
11188
|
}
|
|
10788
11189
|
}
|
|
10789
11190
|
async render(element) {
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
|
|
11191
|
+
try {
|
|
11192
|
+
if (this.options.backgroundColor) {
|
|
11193
|
+
this.ctx.fillStyle = asString(this.options.backgroundColor);
|
|
11194
|
+
this.ctx.fillRect(this.options.x, this.options.y, this.options.width, this.options.height);
|
|
11195
|
+
}
|
|
11196
|
+
const stack = parseStackingContexts(element);
|
|
11197
|
+
await this.renderStack(stack);
|
|
11198
|
+
this.effectsRenderer.applyEffects([]);
|
|
11199
|
+
return this.canvas;
|
|
11200
|
+
} catch (error) {
|
|
11201
|
+
if (!this.options.canvas) releaseSurface(this.canvas);
|
|
11202
|
+
throw error;
|
|
10793
11203
|
}
|
|
10794
|
-
const stack = parseStackingContexts(element);
|
|
10795
|
-
await this.renderStack(stack);
|
|
10796
|
-
this.effectsRenderer.applyEffects([]);
|
|
10797
|
-
return this.canvas;
|
|
10798
11204
|
}
|
|
10799
11205
|
/**
|
|
10800
11206
|
* Render background clipped to text shape using offscreen canvas with destination-in compositing.
|
|
@@ -11210,22 +11616,6 @@ var Context = class Context {
|
|
|
11210
11616
|
}
|
|
11211
11617
|
};
|
|
11212
11618
|
//#endregion
|
|
11213
|
-
//#region src/core/abort-helper.ts
|
|
11214
|
-
/**
|
|
11215
|
-
* AbortSignal helper utilities.
|
|
11216
|
-
*/
|
|
11217
|
-
/**
|
|
11218
|
-
* Throw a DOMException if the given AbortSignal is already aborted.
|
|
11219
|
-
* Used at checkpoints throughout the rendering pipeline to support
|
|
11220
|
-
* cancellable renders.
|
|
11221
|
-
*
|
|
11222
|
-
* @param signal - The AbortSignal to check. If undefined or not aborted, this is a no-op.
|
|
11223
|
-
* @throws {DOMException} With name 'AbortError' if the signal has been aborted.
|
|
11224
|
-
*/
|
|
11225
|
-
const throwIfAborted = (signal) => {
|
|
11226
|
-
if (signal?.aborted) throw new DOMException("The operation was aborted.", "AbortError");
|
|
11227
|
-
};
|
|
11228
|
-
//#endregion
|
|
11229
11619
|
//#region src/core/config-assembler.ts
|
|
11230
11620
|
/**
|
|
11231
11621
|
* Coerce known numeric options from string (or other) values to actual numbers.
|
|
@@ -11394,18 +11784,19 @@ const renderElement = async (element, opts, config) => {
|
|
|
11394
11784
|
canvas = await new CanvasRenderer(context, renderOptions).render(root);
|
|
11395
11785
|
perfMonitor.end("render");
|
|
11396
11786
|
}
|
|
11787
|
+
} finally {
|
|
11397
11788
|
perfMonitor.start("cleanup");
|
|
11398
|
-
|
|
11399
|
-
if (
|
|
11789
|
+
try {
|
|
11790
|
+
if (root) root.restoreTree();
|
|
11791
|
+
} finally {
|
|
11792
|
+
if (opts.removeContainer ?? true) DocumentCloner.destroy(container);
|
|
11793
|
+
perfMonitor.end("cleanup");
|
|
11400
11794
|
}
|
|
11401
|
-
perfMonitor.end("cleanup");
|
|
11402
|
-
perfMonitor.end("total");
|
|
11403
|
-
context.logger.debug(`Finished rendering`);
|
|
11404
|
-
if (performanceMonitoring) perfMonitor.logSummary();
|
|
11405
|
-
return canvas;
|
|
11406
|
-
} finally {
|
|
11407
|
-
if (root) root.restoreTree();
|
|
11408
11795
|
}
|
|
11796
|
+
perfMonitor.end("total");
|
|
11797
|
+
context.logger.debug(`Finished rendering`);
|
|
11798
|
+
if (performanceMonitoring) perfMonitor.logSummary();
|
|
11799
|
+
return canvas;
|
|
11409
11800
|
};
|
|
11410
11801
|
//#endregion
|
|
11411
11802
|
//#region src/index.ts
|