@pixldocs/canvas-renderer 0.5.117 → 0.5.119
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-81pAjQOk.cjs +18489 -0
- package/dist/index-81pAjQOk.cjs.map +1 -0
- package/dist/index-CuTWdeXZ.js +18474 -0
- package/dist/index-CuTWdeXZ.js.map +1 -0
- package/dist/index.cjs +36 -18109
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +36 -18092
- package/dist/index.js.map +1 -1
- package/dist/pdfFonts-CB5z77qO.js +1014 -0
- package/dist/pdfFonts-CB5z77qO.js.map +1 -0
- package/dist/{svgTextToPath-CQ2Tp03U.js → pdfFonts-CHHpRsRK.cjs} +304 -645
- package/dist/pdfFonts-CHHpRsRK.cjs.map +1 -0
- package/dist/pdfWatermark-CewmS9qQ.js +36 -0
- package/dist/pdfWatermark-CewmS9qQ.js.map +1 -0
- package/dist/pdfWatermark-DdBUt6s5.cjs +36 -0
- package/dist/pdfWatermark-DdBUt6s5.cjs.map +1 -0
- package/dist/{svgColorUtils-BkKZ8cyd.js → svgColorUtils-CIehRL4U.js} +262 -1
- package/dist/{svgColorUtils-BkKZ8cyd.js.map → svgColorUtils-CIehRL4U.js.map} +1 -1
- package/dist/{svgColorUtils-DQN6fbIM.cjs → svgColorUtils-DKcCq1sO.cjs} +262 -1
- package/dist/{svgColorUtils-DQN6fbIM.cjs.map → svgColorUtils-DKcCq1sO.cjs.map} +1 -1
- package/dist/svgTextToPath-B6FmPvcr.js +626 -0
- package/dist/svgTextToPath-B6FmPvcr.js.map +1 -0
- package/dist/svgTextToPath-B77kYzHp.cjs +665 -0
- package/dist/svgTextToPath-B77kYzHp.cjs.map +1 -0
- package/dist/vectorPdfExport-BrHHt_7L.js +4749 -0
- package/dist/vectorPdfExport-BrHHt_7L.js.map +1 -0
- package/dist/vectorPdfExport-CyJXH2cR.cjs +4766 -0
- package/dist/vectorPdfExport-CyJXH2cR.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/svgTextToPath-4Y_THSBg.cjs +0 -1393
- package/dist/svgTextToPath-4Y_THSBg.cjs.map +0 -1
- package/dist/svgTextToPath-CQ2Tp03U.js.map +0 -1
|
@@ -0,0 +1,4749 @@
|
|
|
1
|
+
import { jsPDF, ShadingPattern } from "jspdf";
|
|
2
|
+
import { svg2pdf } from "svg2pdf.js";
|
|
3
|
+
import * as fabric from "fabric";
|
|
4
|
+
import { g as getCanvasForPage, f as findNodeById, a as getAbsoluteBounds, n as normalizeShapeType, r as renderSmartElementToSvg, h as hasEdgeFade, b as getProxiedImageUrl, c as bakeEdgeFade, i as isElement, d as isGroup, e as buildRoundedTrianglePath, A as API_URL, j as getImageProxyFetchOptions, k as getRoundedRectRadii, T as TRIANGLE_STROKE_MITER_LIMIT, l as getTrianglePoints } from "./index-CuTWdeXZ.js";
|
|
5
|
+
import { F as FONT_FALLBACK_SYMBOLS, a as FONT_FALLBACK_MATH, b as FONT_FALLBACK_DEVANAGARI, e as embedFontWithGoogleFallback, i as isFontAvailable, c as isFamilyEmbedded, r as resolveBestRegisteredVariant, g as getEmbeddedJsPDFFontName, d as resolveFontWeight, f as doesVariantSupportChar } from "./pdfFonts-CB5z77qO.js";
|
|
6
|
+
async function bakeEdgeFadeIntoDataUrl(dataUrl, element) {
|
|
7
|
+
const fade = element;
|
|
8
|
+
if (!hasEdgeFade(fade)) return null;
|
|
9
|
+
if (!dataUrl || typeof dataUrl !== "string") return null;
|
|
10
|
+
return await new Promise((resolve) => {
|
|
11
|
+
try {
|
|
12
|
+
const img = new Image();
|
|
13
|
+
img.crossOrigin = "anonymous";
|
|
14
|
+
img.onload = () => {
|
|
15
|
+
try {
|
|
16
|
+
const c = bakeEdgeFade(img, fade);
|
|
17
|
+
resolve(c.toDataURL("image/png"));
|
|
18
|
+
} catch {
|
|
19
|
+
resolve(null);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
img.onerror = () => resolve(null);
|
|
23
|
+
img.src = dataUrl;
|
|
24
|
+
} catch {
|
|
25
|
+
resolve(null);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function elementHasFade(element) {
|
|
30
|
+
return hasEdgeFade(element);
|
|
31
|
+
}
|
|
32
|
+
const yieldToUI = () => new Promise((resolve) => {
|
|
33
|
+
requestAnimationFrame(() => setTimeout(resolve, 0));
|
|
34
|
+
});
|
|
35
|
+
const SHADOW_RASTER_ALPHA_COMPENSATION = 0.84;
|
|
36
|
+
let debugSvgDrawSequence = 0;
|
|
37
|
+
function debugLog(...args) {
|
|
38
|
+
}
|
|
39
|
+
function pdfWithColorLogging(pdf, seq, elementId) {
|
|
40
|
+
return pdf;
|
|
41
|
+
}
|
|
42
|
+
const FONT_KEY_SEP = "";
|
|
43
|
+
function isBasicLatinOrLatin1(char) {
|
|
44
|
+
const c = char.codePointAt(0) ?? 0;
|
|
45
|
+
return c >= 32 && c <= 126 || c >= 160 && c <= 255;
|
|
46
|
+
}
|
|
47
|
+
function isCommonLatinPunctuation(char) {
|
|
48
|
+
const c = char.codePointAt(0) ?? 0;
|
|
49
|
+
if (c >= 8208 && c <= 8265) return true;
|
|
50
|
+
if (c === 8467 || c === 8482) return true;
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
function isMathOperator(char) {
|
|
54
|
+
const c = char.codePointAt(0) ?? 0;
|
|
55
|
+
if (c >= 8592 && c <= 8703) return true;
|
|
56
|
+
if (c >= 8704 && c <= 8959) return true;
|
|
57
|
+
if (c >= 8960 && c <= 9215) return true;
|
|
58
|
+
if (c >= 10176 && c <= 10223) return true;
|
|
59
|
+
if (c >= 10624 && c <= 10751) return true;
|
|
60
|
+
if (c >= 10752 && c <= 11007) return true;
|
|
61
|
+
if (c >= 11008 && c <= 11097) return true;
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
function isDevanagari(char) {
|
|
65
|
+
const c = char.codePointAt(0) ?? 0;
|
|
66
|
+
return c >= 2304 && c <= 2431 || c >= 43232 && c <= 43263 || c >= 7376 && c <= 7423;
|
|
67
|
+
}
|
|
68
|
+
function classifyChar(char, mainSupportsChar) {
|
|
69
|
+
if (isBasicLatinOrLatin1(char)) return "main";
|
|
70
|
+
if (isCommonLatinPunctuation(char)) return "main";
|
|
71
|
+
if (!isDevanagari(char) && (mainSupportsChar == null ? void 0 : mainSupportsChar(char))) return "main";
|
|
72
|
+
if (isDevanagari(char)) return "devanagari";
|
|
73
|
+
if (isMathOperator(char)) return "math";
|
|
74
|
+
return "symbol";
|
|
75
|
+
}
|
|
76
|
+
function isPureLatin(line) {
|
|
77
|
+
for (let i = 0; i < line.length; i++) {
|
|
78
|
+
const c = line.charCodeAt(i);
|
|
79
|
+
if (!(c >= 32 && c <= 126 || c >= 160 && c <= 255 || c >= 8208 && c <= 8265 || c === 8467 || c === 8482)) return false;
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
function splitLineIntoRuns(line, mainSupportsChar) {
|
|
84
|
+
if (!line) return [];
|
|
85
|
+
const runs = [];
|
|
86
|
+
const chars = [...line];
|
|
87
|
+
let current = "";
|
|
88
|
+
let currentType = "main";
|
|
89
|
+
for (const char of chars) {
|
|
90
|
+
const charType = classifyChar(char, mainSupportsChar);
|
|
91
|
+
if (current && charType !== currentType) {
|
|
92
|
+
runs.push({ text: current, runType: currentType });
|
|
93
|
+
current = "";
|
|
94
|
+
}
|
|
95
|
+
currentType = charType;
|
|
96
|
+
current += char;
|
|
97
|
+
}
|
|
98
|
+
if (current) runs.push({ text: current, runType: currentType });
|
|
99
|
+
return runs;
|
|
100
|
+
}
|
|
101
|
+
function loadNaturalDimensionsFromUrl(imageUrl) {
|
|
102
|
+
return new Promise((resolve) => {
|
|
103
|
+
if (!imageUrl) {
|
|
104
|
+
resolve(null);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const img = new Image();
|
|
108
|
+
img.crossOrigin = imageUrl.startsWith("data:") || imageUrl.startsWith("blob:") ? void 0 : "anonymous";
|
|
109
|
+
img.onload = () => {
|
|
110
|
+
const w = img.naturalWidth || 0;
|
|
111
|
+
const h = img.naturalHeight || 0;
|
|
112
|
+
resolve(w > 0 && h > 0 ? { width: w, height: h } : null);
|
|
113
|
+
};
|
|
114
|
+
img.onerror = () => resolve(null);
|
|
115
|
+
try {
|
|
116
|
+
img.src = imageUrl.startsWith("data:") || imageUrl.startsWith("blob:") ? imageUrl : getProxiedImageUrl(imageUrl);
|
|
117
|
+
} catch {
|
|
118
|
+
resolve(null);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async function rasterizeStoredCropImageToPng(options) {
|
|
123
|
+
const {
|
|
124
|
+
imageUrl,
|
|
125
|
+
frameW,
|
|
126
|
+
frameH,
|
|
127
|
+
clipShape,
|
|
128
|
+
borderRadius = 0,
|
|
129
|
+
panX = 0.5,
|
|
130
|
+
panY = 0.5,
|
|
131
|
+
zoom = 1,
|
|
132
|
+
naturalWidth,
|
|
133
|
+
naturalHeight,
|
|
134
|
+
maintainResolution = true,
|
|
135
|
+
backgroundColor
|
|
136
|
+
} = options;
|
|
137
|
+
if (!imageUrl || frameW <= 0 || frameH <= 0) return null;
|
|
138
|
+
const img = await new Promise((resolve) => {
|
|
139
|
+
const el = new Image();
|
|
140
|
+
el.crossOrigin = imageUrl.startsWith("data:") || imageUrl.startsWith("blob:") ? void 0 : "anonymous";
|
|
141
|
+
el.onload = () => resolve(el);
|
|
142
|
+
el.onerror = () => resolve(null);
|
|
143
|
+
try {
|
|
144
|
+
el.src = imageUrl.startsWith("data:") || imageUrl.startsWith("blob:") ? imageUrl : getProxiedImageUrl(imageUrl);
|
|
145
|
+
} catch {
|
|
146
|
+
resolve(null);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
if (!img) return null;
|
|
150
|
+
const natW = Math.max(1, naturalWidth || img.naturalWidth || frameW);
|
|
151
|
+
const natH = Math.max(1, naturalHeight || img.naturalHeight || frameH);
|
|
152
|
+
const baseScale = Math.max(frameW / natW, frameH / natH);
|
|
153
|
+
const finalScale = Math.max(1, zoom) * baseScale;
|
|
154
|
+
const drawW = natW * finalScale;
|
|
155
|
+
const drawH = natH * finalScale;
|
|
156
|
+
const clampedPanX = Math.max(0, Math.min(1, panX));
|
|
157
|
+
const clampedPanY = Math.max(0, Math.min(1, panY));
|
|
158
|
+
const overflowX = Math.max(0, drawW - frameW);
|
|
159
|
+
const overflowY = Math.max(0, drawH - frameH);
|
|
160
|
+
const offsetX = overflowX > 0 ? -overflowX * (clampedPanX - 0.5) : 0;
|
|
161
|
+
const offsetY = overflowY > 0 ? -overflowY * (clampedPanY - 0.5) : 0;
|
|
162
|
+
const imgX = frameW / 2 + offsetX - drawW / 2;
|
|
163
|
+
const imgY = frameH / 2 + offsetY - drawH / 2;
|
|
164
|
+
const outW = maintainResolution && natW > frameW ? Math.max(1, Math.round(natW)) : Math.max(1, Math.round(frameW * 2));
|
|
165
|
+
const outH = maintainResolution && natH > frameH ? Math.max(1, Math.round(natH)) : Math.max(1, Math.round(frameH * 2));
|
|
166
|
+
const canvas = document.createElement("canvas");
|
|
167
|
+
canvas.width = outW;
|
|
168
|
+
canvas.height = outH;
|
|
169
|
+
const ctx = canvas.getContext("2d");
|
|
170
|
+
if (!ctx) return null;
|
|
171
|
+
const bgFill = backgroundColor && backgroundColor !== "transparent" && backgroundColor !== "none" ? normalizeBgColor(backgroundColor) : void 0;
|
|
172
|
+
if (bgFill) {
|
|
173
|
+
ctx.fillStyle = bgFill;
|
|
174
|
+
ctx.fillRect(0, 0, outW, outH);
|
|
175
|
+
} else {
|
|
176
|
+
ctx.clearRect(0, 0, outW, outH);
|
|
177
|
+
}
|
|
178
|
+
const sx = outW / frameW;
|
|
179
|
+
const sy = outH / frameH;
|
|
180
|
+
ctx.save();
|
|
181
|
+
ctx.scale(sx, sy);
|
|
182
|
+
ctx.beginPath();
|
|
183
|
+
if (clipShape === "circle") {
|
|
184
|
+
ctx.ellipse(frameW / 2, frameH / 2, frameW / 2, frameH / 2, 0, 0, 2 * Math.PI);
|
|
185
|
+
} else {
|
|
186
|
+
const isRounded = clipShape === "rounded" || clipShape === "roundRect" || (borderRadius ?? 0) > 0;
|
|
187
|
+
const r = isRounded ? Math.max(0, Math.min(borderRadius, frameW / 2, frameH / 2)) : 0;
|
|
188
|
+
if (r > 0) {
|
|
189
|
+
ctx.moveTo(r, 0);
|
|
190
|
+
ctx.lineTo(frameW - r, 0);
|
|
191
|
+
ctx.quadraticCurveTo(frameW, 0, frameW, r);
|
|
192
|
+
ctx.lineTo(frameW, frameH - r);
|
|
193
|
+
ctx.quadraticCurveTo(frameW, frameH, frameW - r, frameH);
|
|
194
|
+
ctx.lineTo(r, frameH);
|
|
195
|
+
ctx.quadraticCurveTo(0, frameH, 0, frameH - r);
|
|
196
|
+
ctx.lineTo(0, r);
|
|
197
|
+
ctx.quadraticCurveTo(0, 0, r, 0);
|
|
198
|
+
ctx.closePath();
|
|
199
|
+
} else {
|
|
200
|
+
ctx.rect(0, 0, frameW, frameH);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
ctx.clip();
|
|
204
|
+
ctx.drawImage(img, imgX, imgY, drawW, drawH);
|
|
205
|
+
ctx.restore();
|
|
206
|
+
return canvas.toDataURL("image/png");
|
|
207
|
+
}
|
|
208
|
+
function svg2pdfOpts(x, y, width, height) {
|
|
209
|
+
const sanitize = (value, fallback) => {
|
|
210
|
+
if (!Number.isFinite(value)) return fallback;
|
|
211
|
+
return Number(value.toFixed(3));
|
|
212
|
+
};
|
|
213
|
+
const w = Math.max(1e-3, sanitize(width, 1));
|
|
214
|
+
const h = Math.max(1e-3, sanitize(height, 1));
|
|
215
|
+
return {
|
|
216
|
+
x: sanitize(x, 0),
|
|
217
|
+
y: sanitize(y, 0),
|
|
218
|
+
width: w,
|
|
219
|
+
height: h
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function getAxisAlignedSvgFrameFromMatrix(matrix, localX, localY, width, height) {
|
|
223
|
+
const [a, b, c, d, e, f] = matrix;
|
|
224
|
+
const EPS = 1e-5;
|
|
225
|
+
if (Math.abs(b) > EPS || Math.abs(c) > EPS) return null;
|
|
226
|
+
if (a < -EPS || d < -EPS) return null;
|
|
227
|
+
const x1 = a * localX + e;
|
|
228
|
+
const x2 = a * (localX + width) + e;
|
|
229
|
+
const y1 = d * localY + f;
|
|
230
|
+
const y2 = d * (localY + height) + f;
|
|
231
|
+
const left = Math.min(x1, x2);
|
|
232
|
+
const top = Math.min(y1, y2);
|
|
233
|
+
const frameW = Math.max(0, Math.abs(x2 - x1));
|
|
234
|
+
const frameH = Math.max(0, Math.abs(y2 - y1));
|
|
235
|
+
if (!Number.isFinite(left) || !Number.isFinite(top) || !Number.isFinite(frameW) || !Number.isFinite(frameH)) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
if (frameW < EPS || frameH < EPS) return null;
|
|
239
|
+
return { x: left, y: top, width: frameW, height: frameH };
|
|
240
|
+
}
|
|
241
|
+
function transformPointWithMatrix(matrix, x, y) {
|
|
242
|
+
const [a, b, c, d, e, f] = matrix;
|
|
243
|
+
return {
|
|
244
|
+
x: a * x + c * y + e,
|
|
245
|
+
y: b * x + d * y + f
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function buildMatrixTransformedSvgForPage(svg, matrix, localX, localY, width, height) {
|
|
249
|
+
const EPS = 1e-5;
|
|
250
|
+
const corners = [
|
|
251
|
+
transformPointWithMatrix(matrix, localX, localY),
|
|
252
|
+
transformPointWithMatrix(matrix, localX + width, localY),
|
|
253
|
+
transformPointWithMatrix(matrix, localX, localY + height),
|
|
254
|
+
transformPointWithMatrix(matrix, localX + width, localY + height)
|
|
255
|
+
];
|
|
256
|
+
const minX = Math.min(...corners.map((p) => p.x));
|
|
257
|
+
const minY = Math.min(...corners.map((p) => p.y));
|
|
258
|
+
const maxX = Math.max(...corners.map((p) => p.x));
|
|
259
|
+
const maxY = Math.max(...corners.map((p) => p.y));
|
|
260
|
+
const frameW = maxX - minX;
|
|
261
|
+
const frameH = maxY - minY;
|
|
262
|
+
if (!Number.isFinite(minX) || !Number.isFinite(minY) || !Number.isFinite(frameW) || !Number.isFinite(frameH)) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
if (frameW < EPS || frameH < EPS) return null;
|
|
266
|
+
const ns = "http://www.w3.org/2000/svg";
|
|
267
|
+
const wrapper = document.createElementNS(ns, "svg");
|
|
268
|
+
wrapper.setAttribute("xmlns", ns);
|
|
269
|
+
wrapper.setAttribute("width", String(frameW));
|
|
270
|
+
wrapper.setAttribute("height", String(frameH));
|
|
271
|
+
wrapper.setAttribute("viewBox", `0 0 ${frameW} ${frameH}`);
|
|
272
|
+
const g = document.createElementNS(ns, "g");
|
|
273
|
+
const [a, b, c, d, e, f] = matrix;
|
|
274
|
+
g.setAttribute("transform", `matrix(${a} ${b} ${c} ${d} ${e - minX} ${f - minY})`);
|
|
275
|
+
const nested = svg.cloneNode(true);
|
|
276
|
+
nested.setAttribute("x", String(localX));
|
|
277
|
+
nested.setAttribute("y", String(localY));
|
|
278
|
+
nested.setAttribute("width", String(width));
|
|
279
|
+
nested.setAttribute("height", String(height));
|
|
280
|
+
g.appendChild(nested);
|
|
281
|
+
wrapper.appendChild(g);
|
|
282
|
+
return {
|
|
283
|
+
svg: wrapper,
|
|
284
|
+
frame: { x: minX, y: minY, width: frameW, height: frameH }
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
async function svg2pdfWithDomMount(svg, pdf, opts) {
|
|
288
|
+
const wrap = document.createElement("div");
|
|
289
|
+
wrap.style.cssText = "position:fixed;left:-9999px;top:0;width:0;height:0;overflow:hidden;pointer-events:none;opacity:0";
|
|
290
|
+
wrap.appendChild(svg);
|
|
291
|
+
document.body.appendChild(wrap);
|
|
292
|
+
try {
|
|
293
|
+
await svg2pdf(svg, pdf, opts);
|
|
294
|
+
} finally {
|
|
295
|
+
svg.remove();
|
|
296
|
+
if (wrap.parentNode) document.body.removeChild(wrap);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function resetPdfColorState(pdf, label) {
|
|
300
|
+
pdf.setFillColor(0, 0, 0);
|
|
301
|
+
pdf.setDrawColor(0, 0, 0);
|
|
302
|
+
if (typeof pdf.setTextColor === "function") pdf.setTextColor(0, 0, 0);
|
|
303
|
+
}
|
|
304
|
+
function getFirstExplicitColorFromSvg(svg) {
|
|
305
|
+
let fill = null;
|
|
306
|
+
let stroke = null;
|
|
307
|
+
const isRealColor = (v) => {
|
|
308
|
+
if (!v || v === "none") return false;
|
|
309
|
+
const s = v.trim().toLowerCase();
|
|
310
|
+
return s !== "currentcolor" && s !== "inherit" && (s.startsWith("#") || s.startsWith("rgb"));
|
|
311
|
+
};
|
|
312
|
+
const resolveUrl = (v) => {
|
|
313
|
+
if (!v || v === "none") return null;
|
|
314
|
+
const gradientId = extractGradientIdFromPaint(v);
|
|
315
|
+
if (!gradientId) return null;
|
|
316
|
+
return getGradientStopColorAsHex(svg, gradientId);
|
|
317
|
+
};
|
|
318
|
+
function walk(el) {
|
|
319
|
+
var _a, _b;
|
|
320
|
+
if (fill && stroke) return;
|
|
321
|
+
const f = el.getAttribute("fill") ?? ((_a = el.style) == null ? void 0 : _a.getPropertyValue("fill"));
|
|
322
|
+
const s = el.getAttribute("stroke") ?? ((_b = el.style) == null ? void 0 : _b.getPropertyValue("stroke"));
|
|
323
|
+
if (!fill) {
|
|
324
|
+
if (isRealColor(f)) fill = f;
|
|
325
|
+
else if (f) {
|
|
326
|
+
const resolved = resolveUrl(f);
|
|
327
|
+
if (resolved) fill = resolved;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (!stroke) {
|
|
331
|
+
if (isRealColor(s)) stroke = s;
|
|
332
|
+
else if (s) {
|
|
333
|
+
const resolved = resolveUrl(s);
|
|
334
|
+
if (resolved) stroke = resolved;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
for (let i = 0; i < el.children.length; i++) walk(el.children[i]);
|
|
338
|
+
}
|
|
339
|
+
walk(svg);
|
|
340
|
+
return { fill, stroke };
|
|
341
|
+
}
|
|
342
|
+
function setPdfColorFromSvg(pdf, svg, elementId) {
|
|
343
|
+
const { fill, stroke } = getFirstExplicitColorFromSvg(svg);
|
|
344
|
+
const rgbFill = fill ? parseColor(fill) : null;
|
|
345
|
+
const rgbStroke = stroke ? parseColor(stroke) : null;
|
|
346
|
+
if (rgbFill) pdf.setFillColor(rgbFill.r, rgbFill.g, rgbFill.b);
|
|
347
|
+
else pdf.setFillColor(0, 0, 0);
|
|
348
|
+
if (rgbStroke) pdf.setDrawColor(rgbStroke.r, rgbStroke.g, rgbStroke.b);
|
|
349
|
+
else pdf.setDrawColor(0, 0, 0);
|
|
350
|
+
}
|
|
351
|
+
const SVG_DRAWABLE_TAGS = /* @__PURE__ */ new Set(["path", "rect", "circle", "ellipse", "polygon", "polyline", "line", "text", "tspan"]);
|
|
352
|
+
const SVG_DEFINITION_CONTAINER_TAGS = /* @__PURE__ */ new Set([
|
|
353
|
+
"defs",
|
|
354
|
+
"clippath",
|
|
355
|
+
"mask",
|
|
356
|
+
"pattern",
|
|
357
|
+
"marker",
|
|
358
|
+
"symbol",
|
|
359
|
+
"filter",
|
|
360
|
+
"lineargradient",
|
|
361
|
+
"radialgradient"
|
|
362
|
+
]);
|
|
363
|
+
function isInSvgDefinitionSubtree(el) {
|
|
364
|
+
var _a;
|
|
365
|
+
let current = el;
|
|
366
|
+
while (current) {
|
|
367
|
+
const tag = ((_a = current.tagName) == null ? void 0 : _a.toLowerCase()) ?? "";
|
|
368
|
+
if (SVG_DEFINITION_CONTAINER_TAGS.has(tag)) return true;
|
|
369
|
+
current = current.parentElement;
|
|
370
|
+
}
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
function parseInlineSvgStyleDeclarations(styleText) {
|
|
374
|
+
return styleText.split(";").map((part) => part.trim()).filter(Boolean).map((part) => {
|
|
375
|
+
const idx = part.indexOf(":");
|
|
376
|
+
if (idx <= 0) return null;
|
|
377
|
+
return {
|
|
378
|
+
key: part.slice(0, idx).trim().toLowerCase(),
|
|
379
|
+
value: part.slice(idx + 1).trim()
|
|
380
|
+
};
|
|
381
|
+
}).filter((x) => !!x);
|
|
382
|
+
}
|
|
383
|
+
function logGradientBindingDiagnostics(svg) {
|
|
384
|
+
const gradientCount = svg.querySelectorAll("linearGradient, radialGradient").length;
|
|
385
|
+
if (gradientCount === 0) return;
|
|
386
|
+
let gradientRefCount = 0;
|
|
387
|
+
svg.querySelectorAll("*").forEach((el) => {
|
|
388
|
+
const fill = el.getAttribute("fill");
|
|
389
|
+
const stroke = el.getAttribute("stroke");
|
|
390
|
+
const style = el.getAttribute("style");
|
|
391
|
+
if (extractGradientIdFromPaint(fill) || extractGradientIdFromPaint(stroke)) {
|
|
392
|
+
gradientRefCount++;
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (style) {
|
|
396
|
+
const declarations = parseInlineSvgStyleDeclarations(style);
|
|
397
|
+
if (declarations.some(({ value }) => !!extractGradientIdFromPaint(value))) {
|
|
398
|
+
gradientRefCount++;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
if (gradientRefCount === 0) {
|
|
403
|
+
console.warn("[PDF export] SVG gradient diagnostics: defs exist but no gradient refs bound after style inlining. Export may render black.", {
|
|
404
|
+
gradientCount,
|
|
405
|
+
drawableCount: svg.querySelectorAll("path, rect, circle, ellipse, polygon, polyline, line").length
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const SVG_STYLE_PROPS = /* @__PURE__ */ new Set([
|
|
410
|
+
"fill",
|
|
411
|
+
"stroke",
|
|
412
|
+
"color",
|
|
413
|
+
"opacity",
|
|
414
|
+
"fill-opacity",
|
|
415
|
+
"stroke-opacity",
|
|
416
|
+
"fill-rule",
|
|
417
|
+
"stroke-width",
|
|
418
|
+
"stroke-linecap",
|
|
419
|
+
"stroke-linejoin",
|
|
420
|
+
"stroke-miterlimit",
|
|
421
|
+
"stroke-dasharray",
|
|
422
|
+
"stroke-dashoffset",
|
|
423
|
+
"display",
|
|
424
|
+
"visibility",
|
|
425
|
+
"stop-color",
|
|
426
|
+
"stop-opacity",
|
|
427
|
+
"clip-rule",
|
|
428
|
+
"clip-path",
|
|
429
|
+
"mask",
|
|
430
|
+
"filter"
|
|
431
|
+
]);
|
|
432
|
+
function inlineSvgStyleBlockDeclarations(svg) {
|
|
433
|
+
const styleNodes = Array.from(svg.querySelectorAll("style"));
|
|
434
|
+
if (styleNodes.length === 0) return;
|
|
435
|
+
const cssText = styleNodes.map((node) => node.textContent || "").join("\n").replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
436
|
+
if (!cssText.trim()) return;
|
|
437
|
+
const allElements = [svg, ...Array.from(svg.querySelectorAll("*"))];
|
|
438
|
+
const classIndex = /* @__PURE__ */ new Map();
|
|
439
|
+
allElements.forEach((el) => {
|
|
440
|
+
const raw = el.getAttribute("class") || "";
|
|
441
|
+
raw.split(/\s+/).map((token) => token.trim()).filter(Boolean).forEach((token) => {
|
|
442
|
+
const list = classIndex.get(token) ?? [];
|
|
443
|
+
list.push(el);
|
|
444
|
+
classIndex.set(token, list);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
const normalizePresentationValue = (key, value) => {
|
|
448
|
+
const cleaned = value.replace(/\s*!important\s*$/i, "").trim();
|
|
449
|
+
if (["fill", "stroke", "clip-path", "mask", "filter"].includes(key)) {
|
|
450
|
+
return normalizeGradientPaintRef(cleaned);
|
|
451
|
+
}
|
|
452
|
+
return cleaned;
|
|
453
|
+
};
|
|
454
|
+
const applyDeclarations = (elements, declarations) => {
|
|
455
|
+
for (const el of elements) {
|
|
456
|
+
const inlineStyle = el.getAttribute("style") || "";
|
|
457
|
+
const inlineDeclMap = new Map(
|
|
458
|
+
parseInlineSvgStyleDeclarations(inlineStyle).map(({ key, value }) => [key, value])
|
|
459
|
+
);
|
|
460
|
+
for (const { key, value } of declarations) {
|
|
461
|
+
if (!value || inlineDeclMap.has(key)) continue;
|
|
462
|
+
el.setAttribute(key, normalizePresentationValue(key, value));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
const ruleRegex = /([^{}]+)\{([^}]*)\}/g;
|
|
467
|
+
let ruleMatch;
|
|
468
|
+
while (ruleMatch = ruleRegex.exec(cssText)) {
|
|
469
|
+
const selectors = ruleMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
470
|
+
const declarations = parseInlineSvgStyleDeclarations(ruleMatch[2]).filter((decl) => SVG_STYLE_PROPS.has(decl.key));
|
|
471
|
+
if (selectors.length === 0 || declarations.length === 0) continue;
|
|
472
|
+
for (const selector of selectors) {
|
|
473
|
+
if (!selector || selector.startsWith("@")) continue;
|
|
474
|
+
const classOnlyMatch = selector.match(/^\.([A-Za-z_][\w-]*)$/);
|
|
475
|
+
if (classOnlyMatch) {
|
|
476
|
+
applyDeclarations(classIndex.get(classOnlyMatch[1]) ?? [], declarations);
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
try {
|
|
480
|
+
applyDeclarations(svg.querySelectorAll(selector), declarations);
|
|
481
|
+
} catch {
|
|
482
|
+
const classMatch = selector.match(/\.([A-Za-z0-9_-]+)/);
|
|
483
|
+
if (classMatch) {
|
|
484
|
+
applyDeclarations(classIndex.get(classMatch[1]) ?? [], declarations);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
for (const el of allElements) {
|
|
490
|
+
const style = el.getAttribute("style");
|
|
491
|
+
if (!style) continue;
|
|
492
|
+
const keptDecls = [];
|
|
493
|
+
let changed = false;
|
|
494
|
+
for (const { key, value } of parseInlineSvgStyleDeclarations(style)) {
|
|
495
|
+
if (SVG_STYLE_PROPS.has(key)) {
|
|
496
|
+
el.setAttribute(key, normalizePresentationValue(key, value));
|
|
497
|
+
changed = true;
|
|
498
|
+
} else {
|
|
499
|
+
keptDecls.push(`${key}: ${value}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (changed) {
|
|
503
|
+
if (keptDecls.length > 0) el.setAttribute("style", keptDecls.join("; "));
|
|
504
|
+
else el.removeAttribute("style");
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
for (const styleNode of styleNodes) {
|
|
508
|
+
const css = styleNode.textContent || "";
|
|
509
|
+
let cleaned = css;
|
|
510
|
+
for (const prop of SVG_STYLE_PROPS) {
|
|
511
|
+
const regex = new RegExp(`${prop.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")}\\s*:\\s*[^;}{]+;?`, "gi");
|
|
512
|
+
cleaned = cleaned.replace(regex, "");
|
|
513
|
+
}
|
|
514
|
+
styleNode.textContent = cleaned.replace(/;\s*;/g, ";").replace(/\{\s*;/g, "{").replace(/;\s*}/g, "}").replace(/\{\s*}/g, "{}");
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function normalizeSvgExplicitColors(svg) {
|
|
518
|
+
const clone = svg.cloneNode(true);
|
|
519
|
+
inlineSvgStyleBlockDeclarations(clone);
|
|
520
|
+
logGradientBindingDiagnostics(clone);
|
|
521
|
+
const parseColorAttr = (v) => {
|
|
522
|
+
if (!v) return null;
|
|
523
|
+
const s = v.trim().toLowerCase();
|
|
524
|
+
if (!s || s === "none" || s === "transparent" || s === "currentcolor") return null;
|
|
525
|
+
return normalizeGradientPaintRef(v);
|
|
526
|
+
};
|
|
527
|
+
const getRawAttr = (el, attr) => {
|
|
528
|
+
var _a;
|
|
529
|
+
return (el.getAttribute(attr) ?? ((_a = el.style) == null ? void 0 : _a.getPropertyValue(attr)) ?? "").trim().toLowerCase();
|
|
530
|
+
};
|
|
531
|
+
const getRawOpacityAttr = (el, attr) => {
|
|
532
|
+
var _a;
|
|
533
|
+
if (attr === "color") return "";
|
|
534
|
+
return (el.getAttribute(`${attr}-opacity`) ?? ((_a = el.style) == null ? void 0 : _a.getPropertyValue(`${attr}-opacity`)) ?? "").trim().toLowerCase();
|
|
535
|
+
};
|
|
536
|
+
const hasExplicitNonePaint = (el, attr) => {
|
|
537
|
+
const raw = getRawAttr(el, attr);
|
|
538
|
+
if (raw === "none" || raw === "transparent") return true;
|
|
539
|
+
const rawOpacity = getRawOpacityAttr(el, attr);
|
|
540
|
+
return rawOpacity === "0" || rawOpacity === "0%" || rawOpacity === "0.0";
|
|
541
|
+
};
|
|
542
|
+
const getAttr = (el, attr) => {
|
|
543
|
+
var _a;
|
|
544
|
+
const v = el.getAttribute(attr) ?? ((_a = el.style) == null ? void 0 : _a.getPropertyValue(attr));
|
|
545
|
+
return parseColorAttr(v);
|
|
546
|
+
};
|
|
547
|
+
function walk(el, parentFill, parentStroke, parentColor) {
|
|
548
|
+
var _a;
|
|
549
|
+
if (isInSvgDefinitionSubtree(el)) {
|
|
550
|
+
for (let i = 0; i < el.children.length; i++) {
|
|
551
|
+
walk(el.children[i], null, null, null);
|
|
552
|
+
}
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const tag = ((_a = el.tagName) == null ? void 0 : _a.toLowerCase()) ?? "";
|
|
556
|
+
const isDrawable = SVG_DRAWABLE_TAGS.has(tag);
|
|
557
|
+
let fill = getAttr(el, "fill");
|
|
558
|
+
let stroke = getAttr(el, "stroke");
|
|
559
|
+
const color = getAttr(el, "color") ?? parentColor;
|
|
560
|
+
const fillNone = hasExplicitNonePaint(el, "fill");
|
|
561
|
+
const strokeNone = hasExplicitNonePaint(el, "stroke");
|
|
562
|
+
const inheritedFill = parentFill === "none" ? null : parentFill;
|
|
563
|
+
const inheritedStroke = parentStroke === "none" ? null : parentStroke;
|
|
564
|
+
if (isDrawable) {
|
|
565
|
+
if (fillNone) {
|
|
566
|
+
el.setAttribute("fill", "none");
|
|
567
|
+
fill = null;
|
|
568
|
+
} else {
|
|
569
|
+
const resolved = fill ?? color ?? inheritedFill;
|
|
570
|
+
if (resolved) {
|
|
571
|
+
el.setAttribute("fill", resolved);
|
|
572
|
+
fill = resolved;
|
|
573
|
+
} else if (tag !== "text" && tag !== "tspan") {
|
|
574
|
+
el.setAttribute("fill", "none");
|
|
575
|
+
fill = null;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (isDrawable) {
|
|
580
|
+
if (strokeNone) {
|
|
581
|
+
el.setAttribute("stroke", "none");
|
|
582
|
+
stroke = null;
|
|
583
|
+
} else if (stroke != null || color != null || inheritedStroke != null) {
|
|
584
|
+
const resolved = stroke ?? color ?? inheritedStroke;
|
|
585
|
+
if (resolved) {
|
|
586
|
+
el.setAttribute("stroke", resolved);
|
|
587
|
+
stroke = resolved;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
const nextColor = color ?? parentColor;
|
|
592
|
+
const nextFill = fillNone ? "none" : fill ?? inheritedFill;
|
|
593
|
+
const nextStroke = strokeNone ? "none" : stroke ?? inheritedStroke;
|
|
594
|
+
for (let i = 0; i < el.children.length; i++) {
|
|
595
|
+
walk(el.children[i], nextFill, nextStroke, nextColor);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const rootColor = getAttr(clone, "color") ?? (clone.getAttribute("color") || null);
|
|
599
|
+
const rootFill = getAttr(clone, "fill") ?? rootColor;
|
|
600
|
+
const rootStroke = getAttr(clone, "stroke") ?? rootColor;
|
|
601
|
+
const rootFillNone = hasExplicitNonePaint(clone, "fill");
|
|
602
|
+
const rootStrokeNone = hasExplicitNonePaint(clone, "stroke");
|
|
603
|
+
if (rootFillNone) {
|
|
604
|
+
clone.setAttribute("fill", "none");
|
|
605
|
+
} else if (rootFill) {
|
|
606
|
+
clone.setAttribute("fill", rootFill);
|
|
607
|
+
}
|
|
608
|
+
if (rootStrokeNone) {
|
|
609
|
+
clone.setAttribute("stroke", "none");
|
|
610
|
+
} else if (rootStroke) {
|
|
611
|
+
clone.setAttribute("stroke", rootStroke);
|
|
612
|
+
}
|
|
613
|
+
for (let i = 0; i < clone.children.length; i++) {
|
|
614
|
+
walk(
|
|
615
|
+
clone.children[i],
|
|
616
|
+
rootFillNone ? "none" : rootFill ?? null,
|
|
617
|
+
rootStrokeNone ? "none" : rootStroke ?? null,
|
|
618
|
+
rootColor
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
return clone;
|
|
622
|
+
}
|
|
623
|
+
function parseSvgOpacity(value) {
|
|
624
|
+
if (!value) return null;
|
|
625
|
+
const raw = value.trim();
|
|
626
|
+
if (!raw) return null;
|
|
627
|
+
if (raw.endsWith("%")) {
|
|
628
|
+
const pct = Number.parseFloat(raw.slice(0, -1));
|
|
629
|
+
if (!Number.isFinite(pct)) return null;
|
|
630
|
+
return Math.max(0, Math.min(1, pct / 100));
|
|
631
|
+
}
|
|
632
|
+
const n = Number.parseFloat(raw);
|
|
633
|
+
if (!Number.isFinite(n)) return null;
|
|
634
|
+
return Math.max(0, Math.min(1, n));
|
|
635
|
+
}
|
|
636
|
+
function formatSvgOpacity(value) {
|
|
637
|
+
const clamped = Math.max(0, Math.min(1, value));
|
|
638
|
+
return Number(clamped.toFixed(4)).toString();
|
|
639
|
+
}
|
|
640
|
+
function multiplySvgOpacityAttr(el, attr, factor) {
|
|
641
|
+
const existing = parseSvgOpacity(el.getAttribute(attr));
|
|
642
|
+
const next = (existing ?? 1) * factor;
|
|
643
|
+
el.setAttribute(attr, formatSvgOpacity(next));
|
|
644
|
+
}
|
|
645
|
+
function injectOpacityIntoSvg(svg, opacity) {
|
|
646
|
+
if (opacity >= 0.999) return;
|
|
647
|
+
const factor = Math.max(0, Math.min(1, opacity));
|
|
648
|
+
const drawable = svg.querySelectorAll("path, rect, circle, ellipse, polygon, polyline, line, text, tspan");
|
|
649
|
+
drawable.forEach((el) => {
|
|
650
|
+
multiplySvgOpacityAttr(el, "fill-opacity", factor);
|
|
651
|
+
multiplySvgOpacityAttr(el, "stroke-opacity", factor);
|
|
652
|
+
});
|
|
653
|
+
svg.querySelectorAll("stop").forEach((stop) => {
|
|
654
|
+
multiplySvgOpacityAttr(stop, "stop-opacity", factor);
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
function bakeGroupOpacityIntoChildren(svg) {
|
|
658
|
+
const DRAWABLE_TAGS = /* @__PURE__ */ new Set(["path", "rect", "circle", "ellipse", "polygon", "polyline", "line", "text", "tspan"]);
|
|
659
|
+
function walkAndBake(el, inheritedOpacity) {
|
|
660
|
+
var _a, _b, _c;
|
|
661
|
+
if (isInSvgDefinitionSubtree(el)) {
|
|
662
|
+
for (let i = 0; i < el.children.length; i++) {
|
|
663
|
+
walkAndBake(el.children[i], 1);
|
|
664
|
+
}
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const tag = ((_a = el.tagName) == null ? void 0 : _a.toLowerCase()) ?? "";
|
|
668
|
+
const opacityAttr = parseSvgOpacity(el.getAttribute("opacity"));
|
|
669
|
+
const styleOpacity = parseSvgOpacity(((_b = el.style) == null ? void 0 : _b.getPropertyValue("opacity")) || null);
|
|
670
|
+
const ownOpacity = opacityAttr ?? styleOpacity ?? 1;
|
|
671
|
+
const combinedOpacity = inheritedOpacity * ownOpacity;
|
|
672
|
+
if (ownOpacity < 0.999) {
|
|
673
|
+
el.removeAttribute("opacity");
|
|
674
|
+
if ((_c = el.style) == null ? void 0 : _c.opacity) {
|
|
675
|
+
el.style.removeProperty("opacity");
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
if (DRAWABLE_TAGS.has(tag) && combinedOpacity < 0.999) {
|
|
679
|
+
multiplySvgOpacityAttr(el, "fill-opacity", combinedOpacity);
|
|
680
|
+
multiplySvgOpacityAttr(el, "stroke-opacity", combinedOpacity);
|
|
681
|
+
}
|
|
682
|
+
if (tag === "stop" && ownOpacity < 0.999) {
|
|
683
|
+
multiplySvgOpacityAttr(el, "stop-opacity", ownOpacity);
|
|
684
|
+
}
|
|
685
|
+
for (let i = 0; i < el.children.length; i++) {
|
|
686
|
+
walkAndBake(el.children[i], combinedOpacity);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
walkAndBake(svg, 1);
|
|
690
|
+
}
|
|
691
|
+
const URL_GRADIENT_RE = /^\s*url\s*\(\s*(['"]?)([^)]+?)\1\s*\)/i;
|
|
692
|
+
function extractGradientIdFromPaint(value) {
|
|
693
|
+
if (!value) return null;
|
|
694
|
+
const match = value.trim().match(URL_GRADIENT_RE);
|
|
695
|
+
if (!match) return null;
|
|
696
|
+
const rawRef = (match[2] || "").trim();
|
|
697
|
+
if (!rawRef) return null;
|
|
698
|
+
const hashIdx = rawRef.lastIndexOf("#");
|
|
699
|
+
if (hashIdx < 0 || hashIdx === rawRef.length - 1) return null;
|
|
700
|
+
const id = rawRef.slice(hashIdx + 1).trim();
|
|
701
|
+
if (!id) return null;
|
|
702
|
+
return id.replace(/[\s"')].*$/, "");
|
|
703
|
+
}
|
|
704
|
+
function normalizeGradientPaintRef(value) {
|
|
705
|
+
const id = extractGradientIdFromPaint(value);
|
|
706
|
+
return id ? `url(#${id})` : value;
|
|
707
|
+
}
|
|
708
|
+
function normalizeSvgGradientStopOffsets(svg) {
|
|
709
|
+
for (const stop of svg.querySelectorAll("linearGradient stop, radialGradient stop")) {
|
|
710
|
+
const offset = stop.getAttribute("offset");
|
|
711
|
+
if (!offset) continue;
|
|
712
|
+
const trimmed = offset.trim();
|
|
713
|
+
if (trimmed.endsWith("%")) {
|
|
714
|
+
const num = parseFloat(trimmed.slice(0, -1)) / 100;
|
|
715
|
+
if (Number.isFinite(num)) stop.setAttribute("offset", String(Math.max(0, Math.min(1, num))));
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
function decodeSvgDataUri(href) {
|
|
720
|
+
if (!href.startsWith("data:image/svg+xml")) return null;
|
|
721
|
+
const commaIdx = href.indexOf(",");
|
|
722
|
+
if (commaIdx < 0) return null;
|
|
723
|
+
const meta = href.slice(0, commaIdx).toLowerCase();
|
|
724
|
+
const payload = href.slice(commaIdx + 1);
|
|
725
|
+
try {
|
|
726
|
+
if (meta.includes(";base64")) {
|
|
727
|
+
const binary = atob(payload);
|
|
728
|
+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
729
|
+
return new TextDecoder("utf-8").decode(bytes);
|
|
730
|
+
}
|
|
731
|
+
return decodeURIComponent(payload);
|
|
732
|
+
} catch {
|
|
733
|
+
try {
|
|
734
|
+
return atob(payload);
|
|
735
|
+
} catch {
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
function inlineNestedSvgImageDataUris(svgString, domParser = new DOMParser()) {
|
|
741
|
+
var _a;
|
|
742
|
+
try {
|
|
743
|
+
const doc = domParser.parseFromString(svgString, "image/svg+xml");
|
|
744
|
+
if (doc.querySelector("parsererror")) return svgString;
|
|
745
|
+
const root = doc.documentElement;
|
|
746
|
+
if (!root || root.tagName.toLowerCase() !== "svg") return svgString;
|
|
747
|
+
let changed = false;
|
|
748
|
+
const images = Array.from(doc.querySelectorAll("image"));
|
|
749
|
+
for (const img of images) {
|
|
750
|
+
const href = img.getAttribute("href") || img.getAttributeNS("http://www.w3.org/1999/xlink", "href");
|
|
751
|
+
if (!href || !href.startsWith("data:image/svg+xml")) continue;
|
|
752
|
+
try {
|
|
753
|
+
const svgContent = decodeSvgDataUri(href);
|
|
754
|
+
if (!svgContent || !/<svg[\s>]/i.test(svgContent)) continue;
|
|
755
|
+
const innerDoc = domParser.parseFromString(svgContent, "image/svg+xml");
|
|
756
|
+
if (innerDoc.querySelector("parsererror")) continue;
|
|
757
|
+
const innerSvg = innerDoc.documentElement;
|
|
758
|
+
if (!innerSvg || innerSvg.tagName.toLowerCase() !== "svg") continue;
|
|
759
|
+
const ix = parseFloat(img.getAttribute("x") || "0") || 0;
|
|
760
|
+
const iy = parseFloat(img.getAttribute("y") || "0") || 0;
|
|
761
|
+
const iw = parseFloat(img.getAttribute("width") || "0");
|
|
762
|
+
const ih = parseFloat(img.getAttribute("height") || "0");
|
|
763
|
+
if (!(iw > 0 && ih > 0)) continue;
|
|
764
|
+
const nestedSvg = doc.importNode(innerSvg, true);
|
|
765
|
+
const vb = nestedSvg.getAttribute("viewBox");
|
|
766
|
+
if (!vb) {
|
|
767
|
+
nestedSvg.setAttribute("viewBox", `0 0 ${iw} ${ih}`);
|
|
768
|
+
}
|
|
769
|
+
nestedSvg.setAttribute("x", "0");
|
|
770
|
+
nestedSvg.setAttribute("y", "0");
|
|
771
|
+
nestedSvg.setAttribute("width", String(iw));
|
|
772
|
+
nestedSvg.setAttribute("height", String(ih));
|
|
773
|
+
nestedSvg.setAttribute(
|
|
774
|
+
"preserveAspectRatio",
|
|
775
|
+
img.getAttribute("preserveAspectRatio") || nestedSvg.getAttribute("preserveAspectRatio") || "xMidYMid meet"
|
|
776
|
+
);
|
|
777
|
+
const g = doc.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
778
|
+
const existingTransform = img.getAttribute("transform") || "";
|
|
779
|
+
let transform = existingTransform;
|
|
780
|
+
transform += `${transform ? " " : ""}translate(${ix},${iy})`;
|
|
781
|
+
if (transform) {
|
|
782
|
+
g.setAttribute("transform", transform);
|
|
783
|
+
}
|
|
784
|
+
const passthroughAttrs = /* @__PURE__ */ new Set([
|
|
785
|
+
"id",
|
|
786
|
+
"class",
|
|
787
|
+
"style",
|
|
788
|
+
"opacity",
|
|
789
|
+
"display",
|
|
790
|
+
"visibility",
|
|
791
|
+
"clip-path",
|
|
792
|
+
"mask",
|
|
793
|
+
"filter",
|
|
794
|
+
"pointer-events"
|
|
795
|
+
]);
|
|
796
|
+
Array.from(img.attributes).forEach((attr) => {
|
|
797
|
+
if (passthroughAttrs.has(attr.name)) {
|
|
798
|
+
g.setAttribute(attr.name, attr.value);
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
g.appendChild(nestedSvg);
|
|
802
|
+
(_a = img.parentNode) == null ? void 0 : _a.replaceChild(g, img);
|
|
803
|
+
changed = true;
|
|
804
|
+
} catch {
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return changed ? new XMLSerializer().serializeToString(doc.documentElement) : svgString;
|
|
808
|
+
} catch {
|
|
809
|
+
return svgString;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
function hasInvalidSvgNumericToken(value) {
|
|
813
|
+
return typeof value === "string" && /\b(?:NaN|-?Infinity)\b/.test(value);
|
|
814
|
+
}
|
|
815
|
+
function sanitizeSvgNumericTokens(value) {
|
|
816
|
+
return value.replace(/\bNaN\b/g, "0").replace(/\b-?Infinity\b/g, "0");
|
|
817
|
+
}
|
|
818
|
+
function sanitizeSvgTreeForPdf(svg) {
|
|
819
|
+
const attrsToSanitize = [
|
|
820
|
+
"d",
|
|
821
|
+
"points",
|
|
822
|
+
"transform",
|
|
823
|
+
"gradientTransform",
|
|
824
|
+
"patternTransform",
|
|
825
|
+
"viewBox",
|
|
826
|
+
"x",
|
|
827
|
+
"y",
|
|
828
|
+
"x1",
|
|
829
|
+
"y1",
|
|
830
|
+
"x2",
|
|
831
|
+
"y2",
|
|
832
|
+
"cx",
|
|
833
|
+
"cy",
|
|
834
|
+
"r",
|
|
835
|
+
"rx",
|
|
836
|
+
"ry",
|
|
837
|
+
"width",
|
|
838
|
+
"height",
|
|
839
|
+
"dx",
|
|
840
|
+
"dy",
|
|
841
|
+
"opacity",
|
|
842
|
+
"fill-opacity",
|
|
843
|
+
"stroke-opacity",
|
|
844
|
+
"stroke-width",
|
|
845
|
+
"stroke-dashoffset",
|
|
846
|
+
"font-size",
|
|
847
|
+
"letter-spacing",
|
|
848
|
+
"word-spacing"
|
|
849
|
+
];
|
|
850
|
+
const nodes = [svg, ...Array.from(svg.querySelectorAll("*"))];
|
|
851
|
+
for (const node of nodes) {
|
|
852
|
+
for (const attr of attrsToSanitize) {
|
|
853
|
+
const value = node.getAttribute(attr);
|
|
854
|
+
if (!hasInvalidSvgNumericToken(value)) continue;
|
|
855
|
+
node.setAttribute(attr, sanitizeSvgNumericTokens(value));
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
function stripRootPageBackgroundFromSvg(svg) {
|
|
860
|
+
const rootChildren = Array.from(svg.children);
|
|
861
|
+
for (const child of rootChildren) {
|
|
862
|
+
const tag = child.tagName.toLowerCase();
|
|
863
|
+
if (tag !== "rect" && tag !== "path") continue;
|
|
864
|
+
const x = Number.parseFloat(child.getAttribute("x") || "0");
|
|
865
|
+
const y = Number.parseFloat(child.getAttribute("y") || "0");
|
|
866
|
+
const width = Number.parseFloat(child.getAttribute("width") || "0");
|
|
867
|
+
const height = Number.parseFloat(child.getAttribute("height") || "0");
|
|
868
|
+
const fill = child.getAttribute("fill") || getInlineStyleValue(child, "fill");
|
|
869
|
+
const stroke = child.getAttribute("stroke") || getInlineStyleValue(child, "stroke");
|
|
870
|
+
const opacity = parseSvgOpacity(child.getAttribute("opacity")) ?? 1;
|
|
871
|
+
const isFullPageRect = tag === "rect" && Math.abs(x) < 0.01 && Math.abs(y) < 0.01 && Math.abs(width - Number.parseFloat(svg.getAttribute("width") || "0")) < 0.5 && Math.abs(height - Number.parseFloat(svg.getAttribute("height") || "0")) < 0.5;
|
|
872
|
+
const isVisiblePaint = opacity > 1e-4 && !isTransparentColorToken(fill) && (!stroke || isTransparentColorToken(stroke));
|
|
873
|
+
if (isFullPageRect && isVisiblePaint) {
|
|
874
|
+
child.remove();
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
function stripSuspiciousFullPageOverlayNodes(svg) {
|
|
880
|
+
const rootWidth = Number.parseFloat(svg.getAttribute("width") || "0");
|
|
881
|
+
const rootHeight = Number.parseFloat(svg.getAttribute("height") || "0");
|
|
882
|
+
const viewBox = (svg.getAttribute("viewBox") || "").trim().split(/[\s,]+/).map((n) => Number.parseFloat(n));
|
|
883
|
+
const pageWidth = Number.isFinite(rootWidth) && rootWidth > 0 ? rootWidth : viewBox[2] || 0;
|
|
884
|
+
const pageHeight = Number.isFinite(rootHeight) && rootHeight > 0 ? rootHeight : viewBox[3] || 0;
|
|
885
|
+
if (!(pageWidth > 0 && pageHeight > 0)) return;
|
|
886
|
+
const isNear = (a, b, tolerance = 0.75) => Math.abs(a - b) <= tolerance;
|
|
887
|
+
const hasVisibleStyleToken = (value, token) => {
|
|
888
|
+
if (!value) return false;
|
|
889
|
+
return new RegExp(`(?:^|;)\\s*${token}\\s*:\\s*([^;]+)`, "i").test(value);
|
|
890
|
+
};
|
|
891
|
+
const hasHiddenDisplay = (el) => {
|
|
892
|
+
const display = (el.getAttribute("display") || getInlineStyleValue(el, "display") || "").trim().toLowerCase();
|
|
893
|
+
const visibility = (el.getAttribute("visibility") || getInlineStyleValue(el, "visibility") || "").trim().toLowerCase();
|
|
894
|
+
return display === "none" || visibility === "hidden";
|
|
895
|
+
};
|
|
896
|
+
const getNodeOpacity = (el) => {
|
|
897
|
+
return parseSvgOpacity(el.getAttribute("opacity")) ?? parseSvgOpacity(getInlineStyleValue(el, "opacity")) ?? 1;
|
|
898
|
+
};
|
|
899
|
+
const isDarkPaint = (value) => {
|
|
900
|
+
const rgb = value ? parseColor(value) : null;
|
|
901
|
+
if (!rgb) return false;
|
|
902
|
+
return rgb.r <= 32 && rgb.g <= 32 && rgb.b <= 32;
|
|
903
|
+
};
|
|
904
|
+
const removeIfSuspicious = (el) => {
|
|
905
|
+
if (hasHiddenDisplay(el)) return;
|
|
906
|
+
const opacity = getNodeOpacity(el);
|
|
907
|
+
if (opacity <= 1e-3) return;
|
|
908
|
+
const fill = el.getAttribute("fill") || getInlineStyleValue(el, "fill");
|
|
909
|
+
const stroke = el.getAttribute("stroke") || getInlineStyleValue(el, "stroke");
|
|
910
|
+
if (!isDarkPaint(fill) && !isDarkPaint(stroke)) return;
|
|
911
|
+
const width = Number.parseFloat(el.getAttribute("width") || "0");
|
|
912
|
+
const height = Number.parseFloat(el.getAttribute("height") || "0");
|
|
913
|
+
const x = Number.parseFloat(el.getAttribute("x") || "0");
|
|
914
|
+
const y = Number.parseFloat(el.getAttribute("y") || "0");
|
|
915
|
+
const isRectLike = el.tagName.toLowerCase() === "rect" && isNear(x, 0) && isNear(y, 0) && isNear(width, pageWidth, 1.5) && isNear(height, pageHeight, 1.5);
|
|
916
|
+
let isPathLike = false;
|
|
917
|
+
if (el.tagName.toLowerCase() === "path") {
|
|
918
|
+
const d = (el.getAttribute("d") || "").replace(/\s+/g, " ").trim();
|
|
919
|
+
isPathLike = !!d && (new RegExp(`^Ms*0(?:.0+)?s+0(?:.0+)?s*Hs*${Math.round(pageWidth)}(?:.0+)?s*Vs*${Math.round(pageHeight)}(?:.0+)?s*Hs*0(?:.0+)?s*Z?$`, "i").test(d) || new RegExp(`^Ms*0(?:.0+)?s+0(?:.0+)?s*Ls*${Math.round(pageWidth)}(?:.0+)?s+0(?:.0+)?s*Ls*${Math.round(pageWidth)}(?:.0+)?s+${Math.round(pageHeight)}(?:.0+)?s*Ls*0(?:.0+)?s+${Math.round(pageHeight)}(?:.0+)?s*Z?$`, "i").test(d));
|
|
920
|
+
}
|
|
921
|
+
const inlineStyle = el.getAttribute("style") || "";
|
|
922
|
+
const likelyOverlay = isRectLike || isPathLike || hasVisibleStyleToken(inlineStyle, "mix-blend-mode");
|
|
923
|
+
if (likelyOverlay) {
|
|
924
|
+
el.remove();
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
const candidates = [];
|
|
928
|
+
for (const child of Array.from(svg.children)) {
|
|
929
|
+
const tag = child.tagName.toLowerCase();
|
|
930
|
+
if (tag === "defs" || tag === "style" || tag === "title" || tag === "desc" || tag === "metadata") continue;
|
|
931
|
+
if (tag === "g") {
|
|
932
|
+
for (const grandchild of Array.from(child.children)) {
|
|
933
|
+
candidates.push(grandchild);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
candidates.push(child);
|
|
937
|
+
}
|
|
938
|
+
for (const el of candidates) {
|
|
939
|
+
removeIfSuspicious(el);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
async function convertTextDecorationsToLines(svg) {
|
|
943
|
+
const doc = svg.ownerDocument;
|
|
944
|
+
if (!doc) return;
|
|
945
|
+
const resolveInheritedSvgValue = (el, attr, styleProp = attr) => {
|
|
946
|
+
var _a, _b;
|
|
947
|
+
let current = el;
|
|
948
|
+
while (current) {
|
|
949
|
+
const attrValue = (_a = current.getAttribute(attr)) == null ? void 0 : _a.trim();
|
|
950
|
+
if (attrValue) return attrValue;
|
|
951
|
+
const styleValue = (_b = getInlineStyleValue(current, styleProp)) == null ? void 0 : _b.trim();
|
|
952
|
+
if (styleValue) return styleValue;
|
|
953
|
+
current = current.parentElement;
|
|
954
|
+
}
|
|
955
|
+
return null;
|
|
956
|
+
};
|
|
957
|
+
if (typeof document !== "undefined" && document.fonts) {
|
|
958
|
+
const fontFamilies = /* @__PURE__ */ new Set();
|
|
959
|
+
for (const textEl of svg.querySelectorAll("text")) {
|
|
960
|
+
const ff = textEl.getAttribute("data-source-font-family") || textEl.getAttribute("font-family");
|
|
961
|
+
if (ff) fontFamilies.add(ff.replace(/'/g, ""));
|
|
962
|
+
for (const tspan of textEl.querySelectorAll("tspan")) {
|
|
963
|
+
const tff = tspan.getAttribute("data-source-font-family") || tspan.getAttribute("font-family");
|
|
964
|
+
if (tff) fontFamilies.add(tff.replace(/'/g, ""));
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
const loadPromises = [];
|
|
968
|
+
for (const ff of fontFamilies) {
|
|
969
|
+
if (!document.fonts.check(`16px "${ff}"`)) {
|
|
970
|
+
loadPromises.push(
|
|
971
|
+
document.fonts.load(`16px "${ff}"`).then(() => {
|
|
972
|
+
}).catch(() => {
|
|
973
|
+
})
|
|
974
|
+
);
|
|
975
|
+
loadPromises.push(
|
|
976
|
+
document.fonts.load(`bold 16px "${ff}"`).then(() => {
|
|
977
|
+
}).catch(() => {
|
|
978
|
+
})
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (loadPromises.length > 0) {
|
|
983
|
+
await Promise.all(loadPromises);
|
|
984
|
+
}
|
|
985
|
+
await document.fonts.ready;
|
|
986
|
+
}
|
|
987
|
+
let tempContainer = null;
|
|
988
|
+
let liveSvg = null;
|
|
989
|
+
try {
|
|
990
|
+
if (typeof document !== "undefined") {
|
|
991
|
+
tempContainer = document.createElement("div");
|
|
992
|
+
tempContainer.style.cssText = "position:fixed;left:-9999px;top:-9999px;visibility:hidden;pointer-events:none;";
|
|
993
|
+
document.body.appendChild(tempContainer);
|
|
994
|
+
const clone = svg.cloneNode(true);
|
|
995
|
+
for (const textNode of clone.querySelectorAll("text, tspan, textPath")) {
|
|
996
|
+
const sourceFamily = textNode.getAttribute("data-source-font-family");
|
|
997
|
+
const sourceWeight = textNode.getAttribute("data-source-font-weight");
|
|
998
|
+
const sourceStyle = textNode.getAttribute("data-source-font-style");
|
|
999
|
+
if (sourceFamily) textNode.setAttribute("font-family", sourceFamily);
|
|
1000
|
+
if (sourceWeight) textNode.setAttribute("font-weight", sourceWeight);
|
|
1001
|
+
if (sourceStyle) textNode.setAttribute("font-style", sourceStyle);
|
|
1002
|
+
const inlineStyle = textNode.getAttribute("style") || "";
|
|
1003
|
+
const stylePairs = inlineStyle.split(";").map((part) => part.trim()).filter(Boolean).filter((part) => !/^font-family\s*:/i.test(part) && !/^font-weight\s*:/i.test(part) && !/^font-style\s*:/i.test(part));
|
|
1004
|
+
if (sourceFamily) stylePairs.push(`font-family: ${sourceFamily}`);
|
|
1005
|
+
if (sourceWeight) stylePairs.push(`font-weight: ${sourceWeight}`);
|
|
1006
|
+
if (sourceStyle) stylePairs.push(`font-style: ${sourceStyle}`);
|
|
1007
|
+
if (stylePairs.length > 0) textNode.setAttribute("style", stylePairs.join("; "));
|
|
1008
|
+
}
|
|
1009
|
+
tempContainer.appendChild(clone);
|
|
1010
|
+
liveSvg = clone;
|
|
1011
|
+
}
|
|
1012
|
+
} catch {
|
|
1013
|
+
}
|
|
1014
|
+
let ctx = null;
|
|
1015
|
+
try {
|
|
1016
|
+
const realDoc = typeof document !== "undefined" ? document : doc;
|
|
1017
|
+
const measureCanvas = realDoc.createElement("canvas");
|
|
1018
|
+
ctx = measureCanvas.getContext("2d");
|
|
1019
|
+
} catch {
|
|
1020
|
+
}
|
|
1021
|
+
const textEls = Array.from(svg.querySelectorAll("text"));
|
|
1022
|
+
const liveTextEls = liveSvg ? Array.from(liveSvg.querySelectorAll("text")) : null;
|
|
1023
|
+
for (let ti = 0; ti < textEls.length; ti++) {
|
|
1024
|
+
const textEl = textEls[ti];
|
|
1025
|
+
const liveTextEl = liveTextEls == null ? void 0 : liveTextEls[ti];
|
|
1026
|
+
const tspans = Array.from(textEl.querySelectorAll("tspan"));
|
|
1027
|
+
if (tspans.length === 0) continue;
|
|
1028
|
+
const liveTspans = liveTextEl ? Array.from(liveTextEl.querySelectorAll("tspan")) : null;
|
|
1029
|
+
const textDecOnText = (textEl.getAttribute("text-decoration") || "").toLowerCase();
|
|
1030
|
+
const textStyleDec = (getInlineStyleValue(textEl, "text-decoration") || "").toLowerCase();
|
|
1031
|
+
const textHasUnderline = textDecOnText.includes("underline") || textStyleDec.includes("underline");
|
|
1032
|
+
const textHasLinethrough = textDecOnText.includes("line-through") || textStyleDec.includes("line-through");
|
|
1033
|
+
for (let si = 0; si < tspans.length; si++) {
|
|
1034
|
+
const tspan = tspans[si];
|
|
1035
|
+
const liveTspan = liveTspans == null ? void 0 : liveTspans[si];
|
|
1036
|
+
const tspanDec = (tspan.getAttribute("text-decoration") || "").toLowerCase();
|
|
1037
|
+
const tspanStyleDec = (getInlineStyleValue(tspan, "text-decoration") || "").toLowerCase();
|
|
1038
|
+
const hasUnderline = tspanDec.includes("underline") || tspanStyleDec.includes("underline") || textHasUnderline;
|
|
1039
|
+
const hasLinethrough = tspanDec.includes("line-through") || tspanStyleDec.includes("line-through") || textHasLinethrough;
|
|
1040
|
+
if (!hasUnderline && !hasLinethrough) continue;
|
|
1041
|
+
const content = tspan.textContent || "";
|
|
1042
|
+
if (!content.trim()) continue;
|
|
1043
|
+
const xAttr = tspan.getAttribute("x") ?? textEl.getAttribute("x") ?? "0";
|
|
1044
|
+
const yAttr = tspan.getAttribute("y") ?? textEl.getAttribute("y") ?? "0";
|
|
1045
|
+
const x = parseFloat(xAttr) || 0;
|
|
1046
|
+
const y = parseFloat(yAttr) || 0;
|
|
1047
|
+
const fontSize = parseFloat(
|
|
1048
|
+
tspan.getAttribute("font-size") || textEl.getAttribute("font-size") || "16"
|
|
1049
|
+
);
|
|
1050
|
+
const fontFamily = tspan.getAttribute("data-source-font-family") || textEl.getAttribute("data-source-font-family") || resolveInheritedSvgValue(tspan, "font-family") || "sans-serif";
|
|
1051
|
+
const fontWeight = tspan.getAttribute("data-source-font-weight") || textEl.getAttribute("data-source-font-weight") || resolveInheritedSvgValue(tspan, "font-weight") || "normal";
|
|
1052
|
+
const fontStyle = tspan.getAttribute("data-source-font-style") || textEl.getAttribute("data-source-font-style") || resolveInheritedSvgValue(tspan, "font-style") || "normal";
|
|
1053
|
+
const fill = tspan.getAttribute("fill") || getInlineStyleValue(tspan, "fill") || resolveInheritedSvgValue(tspan, "fill") || textEl.getAttribute("fill") || getInlineStyleValue(textEl, "fill") || "#000000";
|
|
1054
|
+
const fillOpacity = tspan.getAttribute("fill-opacity") || getInlineStyleValue(tspan, "fill-opacity") || resolveInheritedSvgValue(tspan, "fill-opacity") || textEl.getAttribute("fill-opacity") || getInlineStyleValue(textEl, "fill-opacity") || null;
|
|
1055
|
+
let textWidth = content.length * fontSize * 0.6;
|
|
1056
|
+
if (ctx) {
|
|
1057
|
+
ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily.replace(/'/g, "")}`;
|
|
1058
|
+
textWidth = ctx.measureText(content).width;
|
|
1059
|
+
}
|
|
1060
|
+
const textAnchorRaw = (resolveInheritedSvgValue(tspan, "text-anchor") || "start").toLowerCase();
|
|
1061
|
+
const textAnchor = textAnchorRaw === "middle" || textAnchorRaw === "end" ? textAnchorRaw : "start";
|
|
1062
|
+
let lineStartX = x - (textAnchor === "middle" ? textWidth / 2 : textAnchor === "end" ? textWidth : 0);
|
|
1063
|
+
let lineEndX = lineStartX + textWidth;
|
|
1064
|
+
let baselineY = y;
|
|
1065
|
+
if (liveTspan) {
|
|
1066
|
+
try {
|
|
1067
|
+
const liveCharCount = liveTspan.getNumberOfChars();
|
|
1068
|
+
if (liveCharCount > 0) {
|
|
1069
|
+
const start = liveTspan.getStartPositionOfChar(0);
|
|
1070
|
+
const end = liveTspan.getEndPositionOfChar(liveCharCount - 1);
|
|
1071
|
+
if (Number.isFinite(start.x)) lineStartX = start.x;
|
|
1072
|
+
if (Number.isFinite(start.y)) baselineY = start.y;
|
|
1073
|
+
if (Number.isFinite(end.x)) lineEndX = end.x;
|
|
1074
|
+
if (Number.isFinite(end.y) && !Number.isFinite(baselineY)) baselineY = end.y;
|
|
1075
|
+
} else {
|
|
1076
|
+
const svgWidth = liveTspan.getComputedTextLength();
|
|
1077
|
+
if (Number.isFinite(svgWidth) && svgWidth > 0) {
|
|
1078
|
+
textWidth = svgWidth;
|
|
1079
|
+
lineEndX = lineStartX + textWidth;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
} catch {
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
if (!(lineEndX > lineStartX)) {
|
|
1086
|
+
lineStartX = x - (textAnchor === "middle" ? textWidth / 2 : textAnchor === "end" ? textWidth : 0);
|
|
1087
|
+
lineEndX = lineStartX + textWidth;
|
|
1088
|
+
}
|
|
1089
|
+
const thickness = Math.max(0.5, fontSize * 0.066667);
|
|
1090
|
+
const strokeColor = fill.startsWith("url(") ? "#000000" : fill;
|
|
1091
|
+
const decorations = [];
|
|
1092
|
+
if (hasUnderline) decorations.push(baselineY + fontSize * 0.15);
|
|
1093
|
+
if (hasLinethrough) decorations.push(baselineY - fontSize * 0.3);
|
|
1094
|
+
for (const decoY of decorations) {
|
|
1095
|
+
const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
1096
|
+
line.setAttribute("x1", String(lineStartX));
|
|
1097
|
+
line.setAttribute("y1", String(decoY));
|
|
1098
|
+
line.setAttribute("x2", String(lineEndX));
|
|
1099
|
+
line.setAttribute("y2", String(decoY));
|
|
1100
|
+
line.setAttribute("stroke", strokeColor);
|
|
1101
|
+
line.setAttribute("stroke-width", String(thickness));
|
|
1102
|
+
line.setAttribute("stroke-linecap", "butt");
|
|
1103
|
+
line.setAttribute("fill", "none");
|
|
1104
|
+
if (fillOpacity) line.setAttribute("stroke-opacity", fillOpacity);
|
|
1105
|
+
if (textEl.parentElement) {
|
|
1106
|
+
textEl.parentElement.insertBefore(line, textEl.nextSibling);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
if (tempContainer) {
|
|
1112
|
+
try {
|
|
1113
|
+
document.body.removeChild(tempContainer);
|
|
1114
|
+
} catch {
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
async function convertSvgTextDecorationsToLinesString(svgStr) {
|
|
1119
|
+
if (typeof DOMParser === "undefined" || typeof XMLSerializer === "undefined") {
|
|
1120
|
+
return svgStr;
|
|
1121
|
+
}
|
|
1122
|
+
if (!/text-decoration/i.test(svgStr) && !/underline/i.test(svgStr)) {
|
|
1123
|
+
return svgStr;
|
|
1124
|
+
}
|
|
1125
|
+
try {
|
|
1126
|
+
const parser = new DOMParser();
|
|
1127
|
+
const docEl = parser.parseFromString(svgStr, "image/svg+xml");
|
|
1128
|
+
const rootSvg = docEl.documentElement;
|
|
1129
|
+
if (!rootSvg) return svgStr;
|
|
1130
|
+
await convertTextDecorationsToLines(rootSvg);
|
|
1131
|
+
const serializer = new XMLSerializer();
|
|
1132
|
+
return serializer.serializeToString(rootSvg);
|
|
1133
|
+
} catch {
|
|
1134
|
+
return svgStr;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
async function rasterizeShadowMarkers(svg) {
|
|
1138
|
+
var _a, _b, _c, _d, _e;
|
|
1139
|
+
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
1140
|
+
const markers = Array.from(svg.querySelectorAll("g.__pdShadowRaster"));
|
|
1141
|
+
if (markers.length === 0) return;
|
|
1142
|
+
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
1143
|
+
const XLINK_NS = "http://www.w3.org/1999/xlink";
|
|
1144
|
+
try {
|
|
1145
|
+
if ((_a = document.fonts) == null ? void 0 : _a.ready) await document.fonts.ready;
|
|
1146
|
+
} catch {
|
|
1147
|
+
}
|
|
1148
|
+
const fontFaceCss = await collectInlinedFontFaceCss();
|
|
1149
|
+
for (const marker of markers) {
|
|
1150
|
+
try {
|
|
1151
|
+
const blur = parseFloat(marker.getAttribute("data-blur") || "0");
|
|
1152
|
+
const ox = parseFloat(marker.getAttribute("data-ox") || "0");
|
|
1153
|
+
const oy = parseFloat(marker.getAttribute("data-oy") || "0");
|
|
1154
|
+
const bx = parseFloat(marker.getAttribute("data-bx") || "0");
|
|
1155
|
+
const by = parseFloat(marker.getAttribute("data-by") || "0");
|
|
1156
|
+
const bw = parseFloat(marker.getAttribute("data-bw") || "0");
|
|
1157
|
+
const bh = parseFloat(marker.getAttribute("data-bh") || "0");
|
|
1158
|
+
if (!Number.isFinite(bw) || !Number.isFinite(bh) || bw <= 0 || bh <= 0) {
|
|
1159
|
+
(_b = marker.parentNode) == null ? void 0 : _b.removeChild(marker);
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
const innerXml = restoreSourceFontsForShadowRaster(
|
|
1163
|
+
Array.from(marker.childNodes).map((n) => n instanceof Element ? new XMLSerializer().serializeToString(n) : "").join("")
|
|
1164
|
+
);
|
|
1165
|
+
const scale = 2;
|
|
1166
|
+
const pxW = Math.min(4096, Math.max(8, Math.ceil(bw * scale)));
|
|
1167
|
+
const pxH = Math.min(4096, Math.max(8, Math.ceil(bh * scale)));
|
|
1168
|
+
const stdDev = Math.max(0, blur / 2);
|
|
1169
|
+
const filterId = `pdShadowBlur_${Math.random().toString(36).slice(2, 9)}`;
|
|
1170
|
+
const styleBlock = fontFaceCss ? `<style>${fontFaceCss}</style>` : "";
|
|
1171
|
+
const miniSvg = `<svg xmlns="${SVG_NS}" xmlns:xlink="${XLINK_NS}" width="${pxW}" height="${pxH}" viewBox="${bx} ${by} ${bw} ${bh}">${styleBlock}<defs><filter id="${filterId}" filterUnits="userSpaceOnUse" x="${bx}" y="${by}" width="${bw}" height="${bh}" color-interpolation-filters="sRGB"><feOffset dx="${ox}" dy="${oy}" result="offsetShadow" /><feGaussianBlur in="offsetShadow" stdDeviation="${stdDev}" /></filter></defs><g filter="url(#${filterId})">${innerXml}</g></svg>`;
|
|
1172
|
+
const dataUrl = await rasterSvgToPngDataUrl(miniSvg, pxW, pxH);
|
|
1173
|
+
if (!dataUrl) {
|
|
1174
|
+
(_c = marker.parentNode) == null ? void 0 : _c.removeChild(marker);
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
const img = svg.ownerDocument.createElementNS(SVG_NS, "image");
|
|
1178
|
+
img.setAttribute("x", String(bx));
|
|
1179
|
+
img.setAttribute("y", String(by));
|
|
1180
|
+
img.setAttribute("width", String(bw));
|
|
1181
|
+
img.setAttribute("height", String(bh));
|
|
1182
|
+
img.setAttribute("opacity", String(SHADOW_RASTER_ALPHA_COMPENSATION));
|
|
1183
|
+
img.setAttribute("preserveAspectRatio", "none");
|
|
1184
|
+
img.setAttributeNS(XLINK_NS, "xlink:href", dataUrl);
|
|
1185
|
+
img.setAttribute("href", dataUrl);
|
|
1186
|
+
(_d = marker.parentNode) == null ? void 0 : _d.replaceChild(img, marker);
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
console.warn("[Vector PDF] text shadow rasterization failed for one marker:", error);
|
|
1189
|
+
try {
|
|
1190
|
+
(_e = marker.parentNode) == null ? void 0 : _e.removeChild(marker);
|
|
1191
|
+
} catch {
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
function restoreSourceFontsForShadowRaster(markup) {
|
|
1197
|
+
try {
|
|
1198
|
+
const parser = new DOMParser();
|
|
1199
|
+
const doc = parser.parseFromString(`<svg xmlns="http://www.w3.org/2000/svg">${markup}</svg>`, "image/svg+xml");
|
|
1200
|
+
if (doc.querySelector("parsererror")) return markup;
|
|
1201
|
+
for (const node of Array.from(doc.querySelectorAll("text, tspan, textPath"))) {
|
|
1202
|
+
const family = node.getAttribute("data-source-font-family");
|
|
1203
|
+
const weight = node.getAttribute("data-source-font-weight");
|
|
1204
|
+
const style = node.getAttribute("data-source-font-style");
|
|
1205
|
+
if (!family && !weight && !style) continue;
|
|
1206
|
+
const stylePairs = (node.getAttribute("style") || "").split(";").map((part) => part.trim()).filter(Boolean).filter((part) => !/^font-family\s*:/i.test(part) && !/^font-weight\s*:/i.test(part) && !/^font-style\s*:/i.test(part));
|
|
1207
|
+
if (family) {
|
|
1208
|
+
node.setAttribute("font-family", family);
|
|
1209
|
+
stylePairs.push(`font-family: ${family}`);
|
|
1210
|
+
}
|
|
1211
|
+
if (weight) {
|
|
1212
|
+
node.setAttribute("font-weight", weight);
|
|
1213
|
+
stylePairs.push(`font-weight: ${weight}`);
|
|
1214
|
+
}
|
|
1215
|
+
if (style) {
|
|
1216
|
+
node.setAttribute("font-style", style);
|
|
1217
|
+
stylePairs.push(`font-style: ${style}`);
|
|
1218
|
+
}
|
|
1219
|
+
if (stylePairs.length > 0) node.setAttribute("style", stylePairs.join("; "));
|
|
1220
|
+
}
|
|
1221
|
+
const root = doc.documentElement;
|
|
1222
|
+
return Array.from(root.childNodes).map((n) => n instanceof Element ? new XMLSerializer().serializeToString(n) : n.textContent || "").join("");
|
|
1223
|
+
} catch {
|
|
1224
|
+
return markup;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
function rasterSvgToPngDataUrl(svgMarkup, pxW, pxH) {
|
|
1228
|
+
return new Promise((resolve) => {
|
|
1229
|
+
try {
|
|
1230
|
+
const blob = new Blob([svgMarkup], { type: "image/svg+xml;charset=utf-8" });
|
|
1231
|
+
const url = URL.createObjectURL(blob);
|
|
1232
|
+
const img = new Image();
|
|
1233
|
+
const cleanup = () => {
|
|
1234
|
+
try {
|
|
1235
|
+
URL.revokeObjectURL(url);
|
|
1236
|
+
} catch {
|
|
1237
|
+
}
|
|
1238
|
+
};
|
|
1239
|
+
img.onload = () => {
|
|
1240
|
+
try {
|
|
1241
|
+
const canvas = document.createElement("canvas");
|
|
1242
|
+
canvas.width = pxW;
|
|
1243
|
+
canvas.height = pxH;
|
|
1244
|
+
const ctx = canvas.getContext("2d");
|
|
1245
|
+
if (!ctx) {
|
|
1246
|
+
cleanup();
|
|
1247
|
+
resolve(null);
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
ctx.clearRect(0, 0, pxW, pxH);
|
|
1251
|
+
ctx.drawImage(img, 0, 0, pxW, pxH);
|
|
1252
|
+
const dataUrl = canvas.toDataURL("image/png");
|
|
1253
|
+
cleanup();
|
|
1254
|
+
resolve(dataUrl);
|
|
1255
|
+
} catch {
|
|
1256
|
+
cleanup();
|
|
1257
|
+
resolve(null);
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
img.onerror = () => {
|
|
1261
|
+
cleanup();
|
|
1262
|
+
resolve(null);
|
|
1263
|
+
};
|
|
1264
|
+
img.src = url;
|
|
1265
|
+
} catch {
|
|
1266
|
+
resolve(null);
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
let cachedShadowFontFaceCss = null;
|
|
1271
|
+
function collectDocumentFontFaceCss() {
|
|
1272
|
+
if (cachedShadowFontFaceCss !== null) return cachedShadowFontFaceCss;
|
|
1273
|
+
const out = [];
|
|
1274
|
+
try {
|
|
1275
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
1276
|
+
let rules = null;
|
|
1277
|
+
try {
|
|
1278
|
+
rules = sheet.cssRules;
|
|
1279
|
+
} catch {
|
|
1280
|
+
rules = null;
|
|
1281
|
+
}
|
|
1282
|
+
if (!rules) continue;
|
|
1283
|
+
for (const rule of Array.from(rules)) {
|
|
1284
|
+
const r = rule;
|
|
1285
|
+
if (r && (r.type === 5 || /@font-face/i.test(r.cssText || "")) && r.cssText) out.push(r.cssText);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
} catch {
|
|
1289
|
+
}
|
|
1290
|
+
cachedShadowFontFaceCss = out.join("\n");
|
|
1291
|
+
return cachedShadowFontFaceCss;
|
|
1292
|
+
}
|
|
1293
|
+
let cachedInlinedFontFaceCss = null;
|
|
1294
|
+
const fontUrlDataCache = /* @__PURE__ */ new Map();
|
|
1295
|
+
async function fetchFontAsDataUri(url) {
|
|
1296
|
+
if (fontUrlDataCache.has(url)) return fontUrlDataCache.get(url) ?? null;
|
|
1297
|
+
try {
|
|
1298
|
+
const resp = await fetch(url, { mode: "cors", credentials: "omit" });
|
|
1299
|
+
if (!resp.ok) {
|
|
1300
|
+
fontUrlDataCache.set(url, null);
|
|
1301
|
+
return null;
|
|
1302
|
+
}
|
|
1303
|
+
const blob = await resp.blob();
|
|
1304
|
+
const dataUri = await new Promise((resolve, reject) => {
|
|
1305
|
+
const fr = new FileReader();
|
|
1306
|
+
fr.onload = () => resolve(String(fr.result));
|
|
1307
|
+
fr.onerror = () => reject(fr.error);
|
|
1308
|
+
fr.readAsDataURL(blob);
|
|
1309
|
+
});
|
|
1310
|
+
fontUrlDataCache.set(url, dataUri);
|
|
1311
|
+
return dataUri;
|
|
1312
|
+
} catch {
|
|
1313
|
+
fontUrlDataCache.set(url, null);
|
|
1314
|
+
return null;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
async function inlineUrlsInCss(css) {
|
|
1318
|
+
const urlRe = /url\((['"]?)([^'")]+)\1\)/gi;
|
|
1319
|
+
const matches = [];
|
|
1320
|
+
let m;
|
|
1321
|
+
while ((m = urlRe.exec(css)) !== null) {
|
|
1322
|
+
const raw = m[2].trim();
|
|
1323
|
+
if (raw.startsWith("data:")) continue;
|
|
1324
|
+
let abs = raw;
|
|
1325
|
+
try {
|
|
1326
|
+
abs = new URL(raw, document.baseURI).toString();
|
|
1327
|
+
} catch {
|
|
1328
|
+
}
|
|
1329
|
+
matches.push({ full: m[0], url: abs });
|
|
1330
|
+
}
|
|
1331
|
+
if (matches.length === 0) return css;
|
|
1332
|
+
const unique = Array.from(new Set(matches.map((m2) => m2.url)));
|
|
1333
|
+
const results = await Promise.all(unique.map((u) => fetchFontAsDataUri(u)));
|
|
1334
|
+
const map = /* @__PURE__ */ new Map();
|
|
1335
|
+
unique.forEach((u, i) => map.set(u, results[i]));
|
|
1336
|
+
let out = css;
|
|
1337
|
+
for (const { full, url } of matches) {
|
|
1338
|
+
const data = map.get(url);
|
|
1339
|
+
if (!data) continue;
|
|
1340
|
+
const safeFull = full.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1341
|
+
out = out.replace(new RegExp(safeFull, "g"), `url("${data}")`);
|
|
1342
|
+
}
|
|
1343
|
+
return out;
|
|
1344
|
+
}
|
|
1345
|
+
async function collectInlinedFontFaceCss() {
|
|
1346
|
+
if (cachedInlinedFontFaceCss !== null) return cachedInlinedFontFaceCss;
|
|
1347
|
+
const raw = collectDocumentFontFaceCss();
|
|
1348
|
+
if (!raw) {
|
|
1349
|
+
cachedInlinedFontFaceCss = "";
|
|
1350
|
+
return "";
|
|
1351
|
+
}
|
|
1352
|
+
try {
|
|
1353
|
+
cachedInlinedFontFaceCss = await inlineUrlsInCss(raw);
|
|
1354
|
+
} catch {
|
|
1355
|
+
cachedInlinedFontFaceCss = raw;
|
|
1356
|
+
}
|
|
1357
|
+
return cachedInlinedFontFaceCss;
|
|
1358
|
+
}
|
|
1359
|
+
async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, options) {
|
|
1360
|
+
var _a;
|
|
1361
|
+
try {
|
|
1362
|
+
const parser = new DOMParser();
|
|
1363
|
+
const processedSvg = inlineNestedSvgImageDataUris(rawSvg, parser);
|
|
1364
|
+
const doc = parser.parseFromString(processedSvg, "image/svg+xml");
|
|
1365
|
+
if (doc.querySelector("parsererror")) return null;
|
|
1366
|
+
const svg = doc.documentElement;
|
|
1367
|
+
if (!svg || svg.tagName.toLowerCase() !== "svg") return null;
|
|
1368
|
+
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
|
1369
|
+
if (/xlink:href/i.test(processedSvg) && !svg.getAttribute("xmlns:xlink")) {
|
|
1370
|
+
svg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
|
|
1371
|
+
}
|
|
1372
|
+
svg.setAttribute("width", String(pageWidth));
|
|
1373
|
+
svg.setAttribute("height", String(pageHeight));
|
|
1374
|
+
svg.setAttribute("viewBox", `0 0 ${pageWidth} ${pageHeight}`);
|
|
1375
|
+
sanitizeSvgTreeForPdf(svg);
|
|
1376
|
+
normalizeSvgViewBoxOrigin(svg);
|
|
1377
|
+
disambiguateNestedSvgIds(svg);
|
|
1378
|
+
expandSvgUseElements(svg, pageKey);
|
|
1379
|
+
const svgToDraw = normalizeSvgExplicitColors(svg);
|
|
1380
|
+
inlineComputedStyles(svgToDraw);
|
|
1381
|
+
sanitizeSvgTreeForPdf(svgToDraw);
|
|
1382
|
+
normalizeSvgGradientStopOffsets(svgToDraw);
|
|
1383
|
+
expandSvgGradientHrefs(svgToDraw);
|
|
1384
|
+
prefixSvgIds(svgToDraw, pageKey);
|
|
1385
|
+
bakeGroupOpacityIntoChildren(svgToDraw);
|
|
1386
|
+
stripSuspiciousFullPageOverlayNodes(svgToDraw);
|
|
1387
|
+
if (options == null ? void 0 : options.stripPageBackground) {
|
|
1388
|
+
stripRootPageBackgroundFromSvg(svgToDraw);
|
|
1389
|
+
}
|
|
1390
|
+
sanitizeSvgTreeForPdf(svgToDraw);
|
|
1391
|
+
await rasterizeShadowMarkers(svgToDraw);
|
|
1392
|
+
await convertTextDecorationsToLines(svgToDraw);
|
|
1393
|
+
const rewritten = rewriteSvgFontsForJsPDFWithSourceMeta(new XMLSerializer().serializeToString(svgToDraw));
|
|
1394
|
+
const rewrittenDoc = parser.parseFromString(rewritten, "image/svg+xml");
|
|
1395
|
+
if (!rewrittenDoc.querySelector("parsererror") && ((_a = rewrittenDoc.documentElement) == null ? void 0 : _a.tagName.toLowerCase()) === "svg") {
|
|
1396
|
+
return rewrittenDoc.documentElement;
|
|
1397
|
+
}
|
|
1398
|
+
return svgToDraw;
|
|
1399
|
+
} catch {
|
|
1400
|
+
return null;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
async function drawPreparedLiveCanvasSvgPageToPdf(options) {
|
|
1404
|
+
const { rawSvg, pdf, pageWidth, pageHeight, pageKey, stripPageBackground } = options;
|
|
1405
|
+
const svgToDraw = await prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, {
|
|
1406
|
+
stripPageBackground
|
|
1407
|
+
});
|
|
1408
|
+
if (!svgToDraw) return false;
|
|
1409
|
+
++debugSvgDrawSequence;
|
|
1410
|
+
pdf.setFillColor(0, 0, 0);
|
|
1411
|
+
pdf.setDrawColor(0, 0, 0);
|
|
1412
|
+
pdf.saveGraphicsState();
|
|
1413
|
+
setPdfColorFromSvg(pdf, svgToDraw);
|
|
1414
|
+
const pdfToUse = pdfWithColorLogging(pdf);
|
|
1415
|
+
await svg2pdfWithDomMount(svgToDraw, pdfToUse, svg2pdfOpts(0, 0, pageWidth, pageHeight));
|
|
1416
|
+
pdf.restoreGraphicsState();
|
|
1417
|
+
pdf.setFillColor(0, 0, 0);
|
|
1418
|
+
pdf.setDrawColor(0, 0, 0);
|
|
1419
|
+
return true;
|
|
1420
|
+
}
|
|
1421
|
+
function rewriteSvgFontsForJsPDFWithSourceMeta(svgStr) {
|
|
1422
|
+
var _a, _b;
|
|
1423
|
+
const parser = new DOMParser();
|
|
1424
|
+
const doc = parser.parseFromString(svgStr, "image/svg+xml");
|
|
1425
|
+
const allTextEls = Array.from(doc.querySelectorAll("text, tspan, textPath"));
|
|
1426
|
+
const readStyleToken = (style, prop) => {
|
|
1427
|
+
var _a2;
|
|
1428
|
+
const match = style.match(new RegExp(`${prop}\\s*:\\s*([^;]+)`, "i"));
|
|
1429
|
+
return ((_a2 = match == null ? void 0 : match[1]) == null ? void 0 : _a2.trim()) || null;
|
|
1430
|
+
};
|
|
1431
|
+
const resolveInheritedValue = (el, attr, styleProp = attr) => {
|
|
1432
|
+
var _a2;
|
|
1433
|
+
let current = el;
|
|
1434
|
+
while (current) {
|
|
1435
|
+
const attrVal = (_a2 = current.getAttribute(attr)) == null ? void 0 : _a2.trim();
|
|
1436
|
+
if (attrVal) return attrVal;
|
|
1437
|
+
const styleVal = readStyleToken(current.getAttribute("style") || "", styleProp);
|
|
1438
|
+
if (styleVal) return styleVal;
|
|
1439
|
+
current = current.parentElement;
|
|
1440
|
+
}
|
|
1441
|
+
return null;
|
|
1442
|
+
};
|
|
1443
|
+
const getDepth = (el) => {
|
|
1444
|
+
let depth = 0;
|
|
1445
|
+
let current = el.parentElement;
|
|
1446
|
+
while (current) {
|
|
1447
|
+
depth++;
|
|
1448
|
+
current = current.parentElement;
|
|
1449
|
+
}
|
|
1450
|
+
return depth;
|
|
1451
|
+
};
|
|
1452
|
+
const setRunFontAttrs = (target, fontName, baseStyle = target.getAttribute("style") || "") => {
|
|
1453
|
+
target.setAttribute("font-family", fontName);
|
|
1454
|
+
target.setAttribute("font-weight", "normal");
|
|
1455
|
+
target.setAttribute("font-style", "normal");
|
|
1456
|
+
const stylePairs = baseStyle.split(";").map((part) => part.trim()).filter(Boolean).filter((part) => !/^font-family\s*:/i.test(part) && !/^font-weight\s*:/i.test(part) && !/^font-style\s*:/i.test(part));
|
|
1457
|
+
stylePairs.push(`font-family: ${fontName}`);
|
|
1458
|
+
stylePairs.push("font-weight: normal");
|
|
1459
|
+
stylePairs.push("font-style: normal");
|
|
1460
|
+
target.setAttribute("style", stylePairs.join("; "));
|
|
1461
|
+
};
|
|
1462
|
+
const needsSyntheticBold = (requestedWeight, actualWeight) => requestedWeight >= 600 && actualWeight < 600;
|
|
1463
|
+
const needsSyntheticItalic = (requestedItalic, actualItalic) => requestedItalic && !actualItalic;
|
|
1464
|
+
const SYNTHETIC_ITALIC_SKEW_DEG = 12;
|
|
1465
|
+
const applySyntheticItalicTransform = (el) => {
|
|
1466
|
+
const xRaw = resolveInheritedValue(el, "x") ?? "0";
|
|
1467
|
+
const yRaw = resolveInheritedValue(el, "y") ?? "0";
|
|
1468
|
+
const x = Number.parseFloat(xRaw) || 0;
|
|
1469
|
+
const y = Number.parseFloat(yRaw) || 0;
|
|
1470
|
+
const existing = el.getAttribute("transform") || "";
|
|
1471
|
+
const synth = `translate(${x} ${y}) skewX(-${SYNTHETIC_ITALIC_SKEW_DEG}) translate(${-x} ${-y})`;
|
|
1472
|
+
el.setAttribute("transform", existing ? `${existing} ${synth}` : synth);
|
|
1473
|
+
};
|
|
1474
|
+
const sourceMeta = /* @__PURE__ */ new Map();
|
|
1475
|
+
for (const el of allTextEls) {
|
|
1476
|
+
const inlineStyle = el.getAttribute("style") || "";
|
|
1477
|
+
const rawFf = resolveInheritedValue(el, "font-family");
|
|
1478
|
+
if (!rawFf) continue;
|
|
1479
|
+
const clean = (_a = rawFf.split(",")[0]) == null ? void 0 : _a.replace(/['"]/g, "").trim();
|
|
1480
|
+
if (!clean) continue;
|
|
1481
|
+
if (!isFontAvailable(clean) && !isFamilyEmbedded(clean)) continue;
|
|
1482
|
+
const weightRaw = resolveInheritedValue(el, "font-weight") || "400";
|
|
1483
|
+
const styleRaw = resolveInheritedValue(el, "font-style") || "normal";
|
|
1484
|
+
const parsedWeight = Number.parseInt(weightRaw, 10);
|
|
1485
|
+
const weight = Number.isFinite(parsedWeight) ? parsedWeight : /bold/i.test(weightRaw) ? 700 : /medium/i.test(weightRaw) ? 500 : /semi/i.test(weightRaw) ? 600 : /light/i.test(weightRaw) ? 300 : 400;
|
|
1486
|
+
const resolvedWeight = resolveFontWeight(weight);
|
|
1487
|
+
const italic = /italic|oblique/i.test(styleRaw);
|
|
1488
|
+
const best = resolveBestRegisteredVariant(clean, resolvedWeight, italic);
|
|
1489
|
+
if (!best) {
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
const jsPdfName = getEmbeddedJsPDFFontName(clean, best.weight, best.italic);
|
|
1493
|
+
sourceMeta.set(el, {
|
|
1494
|
+
clean,
|
|
1495
|
+
requestedWeight: resolvedWeight,
|
|
1496
|
+
resolvedWeight: best.weight,
|
|
1497
|
+
requestedItalic: italic,
|
|
1498
|
+
italic: best.italic,
|
|
1499
|
+
jsPdfName,
|
|
1500
|
+
inlineStyle
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
const textEls = allTextEls.sort((a, b) => getDepth(b) - getDepth(a));
|
|
1504
|
+
for (const el of textEls) {
|
|
1505
|
+
if (!el.isConnected) continue;
|
|
1506
|
+
const meta = sourceMeta.get(el);
|
|
1507
|
+
if (!meta) continue;
|
|
1508
|
+
const { clean, requestedWeight, resolvedWeight, requestedItalic, italic, jsPdfName, inlineStyle } = meta;
|
|
1509
|
+
el.setAttribute("data-source-font-family", clean);
|
|
1510
|
+
el.setAttribute("data-source-font-weight", String(requestedWeight));
|
|
1511
|
+
el.setAttribute("data-source-font-style", requestedItalic ? "italic" : "normal");
|
|
1512
|
+
if (needsSyntheticBold(requestedWeight, resolvedWeight)) {
|
|
1513
|
+
const fill = resolveInheritedValue(el, "fill") || readStyleToken(inlineStyle, "fill") || "#000000";
|
|
1514
|
+
el.setAttribute("stroke", fill);
|
|
1515
|
+
el.setAttribute("stroke-width", String(Math.max(0.35, Math.min(1.2, requestedWeight === 700 ? 0.7 : 0.5))));
|
|
1516
|
+
el.setAttribute("stroke-linejoin", "round");
|
|
1517
|
+
}
|
|
1518
|
+
const hasOwnItalicStyle = /italic|oblique/i.test(
|
|
1519
|
+
`${el.getAttribute("font-style") || ""};${readStyleToken(inlineStyle, "font-style") || ""}`
|
|
1520
|
+
);
|
|
1521
|
+
if (needsSyntheticItalic(requestedItalic, italic) && hasOwnItalicStyle) {
|
|
1522
|
+
applySyntheticItalicTransform(el);
|
|
1523
|
+
}
|
|
1524
|
+
const directTextChildren = Array.from(el.childNodes).filter((n) => n.nodeType === 3);
|
|
1525
|
+
if (directTextChildren.length === 0) {
|
|
1526
|
+
setRunFontAttrs(el, jsPdfName, inlineStyle);
|
|
1527
|
+
continue;
|
|
1528
|
+
}
|
|
1529
|
+
const symbolJsPdfName = isFontAvailable(FONT_FALLBACK_SYMBOLS) ? getEmbeddedJsPDFFontName(FONT_FALLBACK_SYMBOLS, 400) : jsPdfName;
|
|
1530
|
+
const devanagariJsPdfName = isFontAvailable(FONT_FALLBACK_DEVANAGARI) ? getEmbeddedJsPDFFontName(FONT_FALLBACK_DEVANAGARI, resolvedWeight) : jsPdfName;
|
|
1531
|
+
const mathJsPdfName = isFontAvailable(FONT_FALLBACK_MATH) ? getEmbeddedJsPDFFontName(FONT_FALLBACK_MATH, 400) : symbolJsPdfName;
|
|
1532
|
+
const mainSupportsChar = (char) => doesVariantSupportChar(clean, resolvedWeight, italic, char);
|
|
1533
|
+
for (const node of directTextChildren) {
|
|
1534
|
+
const txt = node.textContent || "";
|
|
1535
|
+
if (!txt) continue;
|
|
1536
|
+
const runs = splitLineIntoRuns(txt, mainSupportsChar);
|
|
1537
|
+
if (runs.length === 1 && runs[0].runType === "main") continue;
|
|
1538
|
+
const fragment = doc.createDocumentFragment();
|
|
1539
|
+
for (const run of runs) {
|
|
1540
|
+
const tspan = doc.createElementNS("http://www.w3.org/2000/svg", "tspan");
|
|
1541
|
+
let runFont = jsPdfName;
|
|
1542
|
+
if (run.runType === "symbol") runFont = symbolJsPdfName;
|
|
1543
|
+
else if (run.runType === "devanagari") runFont = devanagariJsPdfName;
|
|
1544
|
+
else if (run.runType === "math") runFont = mathJsPdfName;
|
|
1545
|
+
tspan.setAttribute("font-family", runFont);
|
|
1546
|
+
tspan.setAttribute("font-weight", "normal");
|
|
1547
|
+
tspan.setAttribute("font-style", "normal");
|
|
1548
|
+
tspan.textContent = run.text;
|
|
1549
|
+
fragment.appendChild(tspan);
|
|
1550
|
+
}
|
|
1551
|
+
el.replaceChild(fragment, node);
|
|
1552
|
+
}
|
|
1553
|
+
setRunFontAttrs(el, jsPdfName, inlineStyle);
|
|
1554
|
+
}
|
|
1555
|
+
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
1556
|
+
const transformedTspans = Array.from(doc.querySelectorAll("tspan[transform]"));
|
|
1557
|
+
for (const tspan of transformedTspans) {
|
|
1558
|
+
const parentText = tspan.parentElement;
|
|
1559
|
+
if (!parentText || parentText.tagName.toLowerCase() !== "text") continue;
|
|
1560
|
+
const transformVal = tspan.getAttribute("transform") || "";
|
|
1561
|
+
if (!/skewX/i.test(transformVal)) continue;
|
|
1562
|
+
const wrapper = doc.createElementNS(SVG_NS, "text");
|
|
1563
|
+
let parentTransform = "";
|
|
1564
|
+
for (const attr of Array.from(parentText.attributes)) {
|
|
1565
|
+
if (attr.name === "transform") {
|
|
1566
|
+
parentTransform = attr.value;
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
wrapper.setAttribute(attr.name, attr.value);
|
|
1570
|
+
}
|
|
1571
|
+
const combined = parentTransform ? `${parentTransform} ${transformVal}` : transformVal;
|
|
1572
|
+
wrapper.setAttribute("transform", combined);
|
|
1573
|
+
tspan.removeAttribute("transform");
|
|
1574
|
+
wrapper.appendChild(tspan);
|
|
1575
|
+
(_b = parentText.parentNode) == null ? void 0 : _b.insertBefore(wrapper, parentText.nextSibling);
|
|
1576
|
+
}
|
|
1577
|
+
return new XMLSerializer().serializeToString(doc.documentElement);
|
|
1578
|
+
}
|
|
1579
|
+
function findGradientInTree(svgRoot, gradientId) {
|
|
1580
|
+
for (const el of svgRoot.querySelectorAll("[id]")) {
|
|
1581
|
+
if (el.getAttribute("id") === gradientId) return el;
|
|
1582
|
+
}
|
|
1583
|
+
return null;
|
|
1584
|
+
}
|
|
1585
|
+
function isNearWhite(hex) {
|
|
1586
|
+
if (!/^#[0-9a-f]{6}$/i.test(hex)) return false;
|
|
1587
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
1588
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
1589
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
1590
|
+
return r >= 250 && g >= 250 && b >= 250;
|
|
1591
|
+
}
|
|
1592
|
+
function getInlineStyleValue(el, key) {
|
|
1593
|
+
const style = el.getAttribute("style") || "";
|
|
1594
|
+
if (!style) return null;
|
|
1595
|
+
const hit = parseInlineSvgStyleDeclarations(style).find((decl) => decl.key === key);
|
|
1596
|
+
return (hit == null ? void 0 : hit.value) ?? null;
|
|
1597
|
+
}
|
|
1598
|
+
function isTransparentColorToken(value) {
|
|
1599
|
+
if (!value) return false;
|
|
1600
|
+
const s = value.trim().toLowerCase();
|
|
1601
|
+
if (!s || s === "none" || s === "transparent") return true;
|
|
1602
|
+
if (/^#[0-9a-f]{8}$/i.test(s)) {
|
|
1603
|
+
const alphaHex = s.slice(7, 9);
|
|
1604
|
+
return Number.parseInt(alphaHex, 16) === 0;
|
|
1605
|
+
}
|
|
1606
|
+
const rgba = s.match(/^rgba\s*\(([^)]+)\)$/i);
|
|
1607
|
+
if (rgba) {
|
|
1608
|
+
const parts = rgba[1].split(",").map((part) => part.trim());
|
|
1609
|
+
if (parts.length >= 4) {
|
|
1610
|
+
const alpha = parseSvgOpacity(parts[3]);
|
|
1611
|
+
return alpha != null && alpha <= 1e-4;
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
return false;
|
|
1615
|
+
}
|
|
1616
|
+
function getStopOpacity(stop) {
|
|
1617
|
+
var _a;
|
|
1618
|
+
const fromAttr = parseSvgOpacity(stop.getAttribute("stop-opacity"));
|
|
1619
|
+
if (fromAttr != null) return fromAttr;
|
|
1620
|
+
const fromStyleObj = parseSvgOpacity(((_a = stop.style) == null ? void 0 : _a.getPropertyValue("stop-opacity")) || null);
|
|
1621
|
+
if (fromStyleObj != null) return fromStyleObj;
|
|
1622
|
+
const fromStyleAttr = parseSvgOpacity(getInlineStyleValue(stop, "stop-opacity"));
|
|
1623
|
+
return fromStyleAttr;
|
|
1624
|
+
}
|
|
1625
|
+
function getStopColorRaw(stop) {
|
|
1626
|
+
var _a, _b;
|
|
1627
|
+
return stop.getAttribute("stop-color") ?? ((_b = (_a = stop.style) == null ? void 0 : _a.getPropertyValue("stop-color")) == null ? void 0 : _b.trim()) ?? getInlineStyleValue(stop, "stop-color");
|
|
1628
|
+
}
|
|
1629
|
+
function isGradientStopFullyTransparent(stop) {
|
|
1630
|
+
const stopOpacity = getStopOpacity(stop);
|
|
1631
|
+
if (stopOpacity != null && stopOpacity <= 1e-4) return true;
|
|
1632
|
+
const stopColor = getStopColorRaw(stop);
|
|
1633
|
+
return isTransparentColorToken(stopColor);
|
|
1634
|
+
}
|
|
1635
|
+
function isGradientFullyTransparent(svgRoot, gradientId, visited = /* @__PURE__ */ new Set()) {
|
|
1636
|
+
var _a;
|
|
1637
|
+
if (visited.has(gradientId)) return false;
|
|
1638
|
+
visited.add(gradientId);
|
|
1639
|
+
const gradient = findGradientInTree(svgRoot, gradientId);
|
|
1640
|
+
if (!gradient) return false;
|
|
1641
|
+
const tag = (_a = gradient.tagName) == null ? void 0 : _a.toLowerCase();
|
|
1642
|
+
if (tag !== "lineargradient" && tag !== "radialgradient") return false;
|
|
1643
|
+
const stops = Array.from(gradient.querySelectorAll("stop"));
|
|
1644
|
+
if (stops.length > 0) {
|
|
1645
|
+
return stops.every((stop) => isGradientStopFullyTransparent(stop));
|
|
1646
|
+
}
|
|
1647
|
+
const href = (gradient.getAttribute("href") || gradient.getAttribute("xlink:href") || "").trim();
|
|
1648
|
+
if (href.startsWith("#")) {
|
|
1649
|
+
return isGradientFullyTransparent(svgRoot, href.slice(1), visited);
|
|
1650
|
+
}
|
|
1651
|
+
return false;
|
|
1652
|
+
}
|
|
1653
|
+
function getGradientStopColorAsHex(svgRoot, gradientId, visited = /* @__PURE__ */ new Set()) {
|
|
1654
|
+
var _a;
|
|
1655
|
+
if (visited.has(gradientId)) return null;
|
|
1656
|
+
visited.add(gradientId);
|
|
1657
|
+
const gradient = findGradientInTree(svgRoot, gradientId);
|
|
1658
|
+
if (!gradient) return null;
|
|
1659
|
+
const tag = (_a = gradient.tagName) == null ? void 0 : _a.toLowerCase();
|
|
1660
|
+
if (tag !== "lineargradient" && tag !== "radialgradient") return null;
|
|
1661
|
+
const stops = Array.from(gradient.querySelectorAll("stop"));
|
|
1662
|
+
if (stops.length === 0) {
|
|
1663
|
+
const href = (gradient.getAttribute("href") || gradient.getAttribute("xlink:href") || "").trim();
|
|
1664
|
+
if (href.startsWith("#")) {
|
|
1665
|
+
const refId = href.slice(1);
|
|
1666
|
+
return getGradientStopColorAsHex(svgRoot, refId, visited);
|
|
1667
|
+
}
|
|
1668
|
+
return null;
|
|
1669
|
+
}
|
|
1670
|
+
const getStopColor = (stop) => {
|
|
1671
|
+
if (isGradientStopFullyTransparent(stop)) return null;
|
|
1672
|
+
const stopColor = getStopColorRaw(stop);
|
|
1673
|
+
return stopColor ? cssColorToHex(stopColor) : null;
|
|
1674
|
+
};
|
|
1675
|
+
const firstHex = getStopColor(stops[0]);
|
|
1676
|
+
if (firstHex && !isNearWhite(firstHex)) return firstHex;
|
|
1677
|
+
const lastStop = stops[stops.length - 1];
|
|
1678
|
+
if (lastStop !== stops[0]) {
|
|
1679
|
+
const lastHex = getStopColor(lastStop);
|
|
1680
|
+
if (lastHex) return lastHex;
|
|
1681
|
+
}
|
|
1682
|
+
return firstHex;
|
|
1683
|
+
}
|
|
1684
|
+
const GRADIENT_ATTRS_LINEAR = ["x1", "y1", "x2", "y2", "gradientUnits", "gradientTransform", "spreadMethod"];
|
|
1685
|
+
const GRADIENT_ATTRS_RADIAL = ["cx", "cy", "r", "fx", "fy", "gradientUnits", "gradientTransform", "spreadMethod"];
|
|
1686
|
+
function copyGradientAttrsFromRef(gradient, ref) {
|
|
1687
|
+
var _a;
|
|
1688
|
+
const tag = (_a = gradient.tagName) == null ? void 0 : _a.toLowerCase();
|
|
1689
|
+
const attrs = tag === "radialgradient" ? GRADIENT_ATTRS_RADIAL : GRADIENT_ATTRS_LINEAR;
|
|
1690
|
+
for (const name of attrs) {
|
|
1691
|
+
if (!gradient.hasAttribute(name) && ref.hasAttribute(name)) {
|
|
1692
|
+
gradient.setAttribute(name, ref.getAttribute(name));
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
function expandSvgGradientHrefs(svg) {
|
|
1697
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1698
|
+
function expand(gradient) {
|
|
1699
|
+
const id = gradient.getAttribute("id");
|
|
1700
|
+
if (!id || visited.has(id)) return;
|
|
1701
|
+
visited.add(id);
|
|
1702
|
+
const stops = gradient.querySelectorAll("stop");
|
|
1703
|
+
if (stops.length > 0) return;
|
|
1704
|
+
const href = (gradient.getAttribute("href") || gradient.getAttribute("xlink:href") || "").trim();
|
|
1705
|
+
if (!href.startsWith("#")) return;
|
|
1706
|
+
const refId = href.slice(1);
|
|
1707
|
+
const ref = findGradientInTree(svg, refId);
|
|
1708
|
+
if (!ref) return;
|
|
1709
|
+
expand(ref);
|
|
1710
|
+
copyGradientAttrsFromRef(gradient, ref);
|
|
1711
|
+
const refStops = ref.querySelectorAll("stop");
|
|
1712
|
+
for (const stop of refStops) gradient.appendChild(stop.cloneNode(true));
|
|
1713
|
+
gradient.removeAttribute("href");
|
|
1714
|
+
gradient.removeAttribute("xlink:href");
|
|
1715
|
+
}
|
|
1716
|
+
for (const g of svg.querySelectorAll("linearGradient, radialGradient")) {
|
|
1717
|
+
expand(g);
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
function cssColorToHex(css) {
|
|
1721
|
+
var _a;
|
|
1722
|
+
const s = css.trim();
|
|
1723
|
+
if (/^#[0-9a-f]{3}$/i.test(s)) {
|
|
1724
|
+
const r2 = s[1] + s[1], g2 = s[2] + s[2], b2 = s[3] + s[3];
|
|
1725
|
+
return "#" + r2 + g2 + b2;
|
|
1726
|
+
}
|
|
1727
|
+
if (/^#[0-9a-f]{6}$/i.test(s)) return s;
|
|
1728
|
+
if (/^#[0-9a-f]{8}$/i.test(s)) return "#" + s.slice(1, 7);
|
|
1729
|
+
const rgb = s.match(/^rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
|
|
1730
|
+
if (rgb) {
|
|
1731
|
+
const r2 = Number(rgb[1]).toString(16).padStart(2, "0");
|
|
1732
|
+
const g2 = Number(rgb[2]).toString(16).padStart(2, "0");
|
|
1733
|
+
const b2 = Number(rgb[3]).toString(16).padStart(2, "0");
|
|
1734
|
+
return "#" + r2 + g2 + b2;
|
|
1735
|
+
}
|
|
1736
|
+
if (typeof document === "undefined") return null;
|
|
1737
|
+
const div = document.createElement("div");
|
|
1738
|
+
div.style.color = s;
|
|
1739
|
+
const computed = (_a = document.defaultView) == null ? void 0 : _a.getComputedStyle(div).color;
|
|
1740
|
+
if (!computed || !computed.startsWith("rgb")) return null;
|
|
1741
|
+
const m = computed.match(/\d+/g);
|
|
1742
|
+
if (!m || m.length < 3) return null;
|
|
1743
|
+
const r = Number(m[0]).toString(16).padStart(2, "0");
|
|
1744
|
+
const g = Number(m[1]).toString(16).padStart(2, "0");
|
|
1745
|
+
const b = Number(m[2]).toString(16).padStart(2, "0");
|
|
1746
|
+
return "#" + r + g + b;
|
|
1747
|
+
}
|
|
1748
|
+
function gradientHasStops(svgRoot, gradientId) {
|
|
1749
|
+
const gradient = findGradientInTree(svgRoot, gradientId);
|
|
1750
|
+
if (!gradient) return false;
|
|
1751
|
+
return gradient.querySelectorAll("stop").length > 0;
|
|
1752
|
+
}
|
|
1753
|
+
function resolveSvgGradientRefsToSolid(svg, elementId) {
|
|
1754
|
+
svg.querySelectorAll("path, rect, circle, ellipse, polygon, polyline, line");
|
|
1755
|
+
const all = svg.querySelectorAll("*");
|
|
1756
|
+
let fallbackHex = null;
|
|
1757
|
+
for (const el of all) {
|
|
1758
|
+
for (const attr of ["fill", "stroke"]) {
|
|
1759
|
+
const val = el.getAttribute(attr);
|
|
1760
|
+
if (!val) continue;
|
|
1761
|
+
const id = extractGradientIdFromPaint(val);
|
|
1762
|
+
if (!id) continue;
|
|
1763
|
+
const normalizedRef = `url(#${id})`;
|
|
1764
|
+
if (val !== normalizedRef) el.setAttribute(attr, normalizedRef);
|
|
1765
|
+
if (isGradientFullyTransparent(svg, id)) {
|
|
1766
|
+
el.setAttribute(attr, "none");
|
|
1767
|
+
continue;
|
|
1768
|
+
}
|
|
1769
|
+
if (gradientHasStops(svg, id)) continue;
|
|
1770
|
+
let hex = getGradientStopColorAsHex(svg, id);
|
|
1771
|
+
if (!hex && fallbackHex) hex = fallbackHex;
|
|
1772
|
+
if (!hex) {
|
|
1773
|
+
const firstGradient = svg.querySelector("linearGradient[id], radialGradient[id]");
|
|
1774
|
+
if (firstGradient) {
|
|
1775
|
+
const firstId = firstGradient.getAttribute("id");
|
|
1776
|
+
if (firstId) hex = getGradientStopColorAsHex(svg, firstId);
|
|
1777
|
+
}
|
|
1778
|
+
if (!hex) hex = "#000000";
|
|
1779
|
+
}
|
|
1780
|
+
if (hex) {
|
|
1781
|
+
el.setAttribute(attr, hex);
|
|
1782
|
+
if (!fallbackHex) fallbackHex = hex;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
function disambiguateNestedSvgIds(rootSvg) {
|
|
1788
|
+
const nestedSvgs = Array.from(rootSvg.querySelectorAll("svg"));
|
|
1789
|
+
const toProcess = nestedSvgs.filter((s) => s !== rootSvg);
|
|
1790
|
+
if (toProcess.length < 2) return;
|
|
1791
|
+
toProcess.forEach((nested, idx) => {
|
|
1792
|
+
prefixSvgIds(nested, `n${idx}`);
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
function prefixSvgIds(svg, prefix) {
|
|
1796
|
+
const safePrefix = String(prefix).replace(/[^a-zA-Z0-9-]/g, "_");
|
|
1797
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
1798
|
+
svg.querySelectorAll("[id]").forEach((el) => {
|
|
1799
|
+
const id = el.getAttribute("id");
|
|
1800
|
+
if (id) idMap.set(id, `${safePrefix}_${id}`);
|
|
1801
|
+
});
|
|
1802
|
+
if (idMap.size === 0) return;
|
|
1803
|
+
idMap.forEach((newId, oldId) => {
|
|
1804
|
+
svg.querySelectorAll(`[id="${oldId}"]`).forEach((el) => el.setAttribute("id", newId));
|
|
1805
|
+
});
|
|
1806
|
+
const replaceUrlRefs = (value) => {
|
|
1807
|
+
return value.replace(/url\(\s*(['"]?)([^)]+?)\1\s*\)/gi, (full, quoteRaw, refRaw) => {
|
|
1808
|
+
const ref = String(refRaw || "").trim();
|
|
1809
|
+
const hashIdx = ref.lastIndexOf("#");
|
|
1810
|
+
if (hashIdx < 0 || hashIdx === ref.length - 1) return full;
|
|
1811
|
+
const base = ref.slice(0, hashIdx + 1);
|
|
1812
|
+
const oldId = ref.slice(hashIdx + 1).trim().replace(/[\s"')].*$/, "");
|
|
1813
|
+
const mapped = idMap.get(oldId);
|
|
1814
|
+
if (!mapped) return full;
|
|
1815
|
+
const quote = String(quoteRaw || "");
|
|
1816
|
+
return `url(${quote}${base}${mapped}${quote})`;
|
|
1817
|
+
});
|
|
1818
|
+
};
|
|
1819
|
+
const replaceHrefRef = (value) => {
|
|
1820
|
+
const trimmed = value.trim();
|
|
1821
|
+
if (trimmed.startsWith("#")) {
|
|
1822
|
+
const oldId = trimmed.slice(1);
|
|
1823
|
+
const mapped = idMap.get(oldId);
|
|
1824
|
+
return mapped ? `#${mapped}` : value;
|
|
1825
|
+
}
|
|
1826
|
+
return replaceUrlRefs(value);
|
|
1827
|
+
};
|
|
1828
|
+
const refAttrs = [
|
|
1829
|
+
"fill",
|
|
1830
|
+
"stroke",
|
|
1831
|
+
"clip-path",
|
|
1832
|
+
"mask",
|
|
1833
|
+
"filter",
|
|
1834
|
+
"href",
|
|
1835
|
+
"xlink:href",
|
|
1836
|
+
"marker-start",
|
|
1837
|
+
"marker-mid",
|
|
1838
|
+
"marker-end"
|
|
1839
|
+
];
|
|
1840
|
+
svg.querySelectorAll("*").forEach((el) => {
|
|
1841
|
+
refAttrs.forEach((attr) => {
|
|
1842
|
+
const val = el.getAttribute(attr);
|
|
1843
|
+
if (!val) return;
|
|
1844
|
+
const next = attr === "href" || attr === "xlink:href" ? replaceHrefRef(val) : replaceUrlRefs(val);
|
|
1845
|
+
if (next !== val) el.setAttribute(attr, next);
|
|
1846
|
+
});
|
|
1847
|
+
const style = el.getAttribute("style");
|
|
1848
|
+
if (style) {
|
|
1849
|
+
const nextStyle = replaceUrlRefs(style);
|
|
1850
|
+
if (nextStyle !== style) el.setAttribute("style", nextStyle);
|
|
1851
|
+
}
|
|
1852
|
+
});
|
|
1853
|
+
svg.querySelectorAll("style").forEach((styleNode) => {
|
|
1854
|
+
const text = styleNode.textContent || "";
|
|
1855
|
+
const next = replaceUrlRefs(text);
|
|
1856
|
+
if (next !== text) styleNode.textContent = next;
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
function expandSvgUseElements(svg, elementId) {
|
|
1860
|
+
var _a;
|
|
1861
|
+
const doc = svg.ownerDocument;
|
|
1862
|
+
const uses = Array.from(svg.querySelectorAll("use"));
|
|
1863
|
+
for (const use of uses) {
|
|
1864
|
+
const ref = (use.getAttribute("href") || use.getAttribute("xlink:href") || "").trim();
|
|
1865
|
+
if (!ref.startsWith("#")) {
|
|
1866
|
+
continue;
|
|
1867
|
+
}
|
|
1868
|
+
const id = ref.slice(1);
|
|
1869
|
+
const target = doc.getElementById(id);
|
|
1870
|
+
if (!target) continue;
|
|
1871
|
+
const x = parseFloat(use.getAttribute("x") || "0") || 0;
|
|
1872
|
+
const y = parseFloat(use.getAttribute("y") || "0") || 0;
|
|
1873
|
+
const w = parseFloat(use.getAttribute("width") || "0") || 0;
|
|
1874
|
+
const h = parseFloat(use.getAttribute("height") || "0") || 0;
|
|
1875
|
+
const g = doc.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
1876
|
+
if (((_a = target.tagName) == null ? void 0 : _a.toLowerCase()) === "symbol") {
|
|
1877
|
+
const viewBox = target.getAttribute("viewBox");
|
|
1878
|
+
for (let i = 0; i < target.children.length; i++) {
|
|
1879
|
+
g.appendChild(target.children[i].cloneNode(true));
|
|
1880
|
+
}
|
|
1881
|
+
if (viewBox && w && h) {
|
|
1882
|
+
const parts = viewBox.split(/\s+/).map(Number);
|
|
1883
|
+
const vbW = parts[2];
|
|
1884
|
+
const vbH = parts[3];
|
|
1885
|
+
if (vbW && vbH) g.setAttribute("transform", `translate(${x},${y}) scale(${w / vbW},${h / vbH})`);
|
|
1886
|
+
else g.setAttribute("transform", `translate(${x},${y})`);
|
|
1887
|
+
} else if (x || y) {
|
|
1888
|
+
g.setAttribute("transform", `translate(${x},${y})`);
|
|
1889
|
+
}
|
|
1890
|
+
} else {
|
|
1891
|
+
const clone = target.cloneNode(true);
|
|
1892
|
+
if (x || y) g.setAttribute("transform", `translate(${x},${y})`);
|
|
1893
|
+
g.appendChild(clone);
|
|
1894
|
+
}
|
|
1895
|
+
if (use.parentNode) use.parentNode.replaceChild(g, use);
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
function normalizeSvgViewBoxOrigin(svg) {
|
|
1899
|
+
const viewBox = svg.getAttribute("viewBox");
|
|
1900
|
+
if (!viewBox) return;
|
|
1901
|
+
const parts = viewBox.split(/[\s,]+/).map((n) => Number.parseFloat(n)).filter((n) => Number.isFinite(n));
|
|
1902
|
+
if (parts.length !== 4) return;
|
|
1903
|
+
const [vx, vy, vw, vh] = parts;
|
|
1904
|
+
if (vw <= 0 || vh <= 0) return;
|
|
1905
|
+
if (Math.abs(vx) < 1e-3 && Math.abs(vy) < 1e-3) return;
|
|
1906
|
+
const doc = svg.ownerDocument;
|
|
1907
|
+
if (!doc) return;
|
|
1908
|
+
const keepAtRoot = /* @__PURE__ */ new Set(["defs", "style", "title", "desc", "metadata"]);
|
|
1909
|
+
const translatableChildren = Array.from(svg.children).filter(
|
|
1910
|
+
(child) => !keepAtRoot.has(child.tagName.toLowerCase())
|
|
1911
|
+
);
|
|
1912
|
+
if (translatableChildren.length === 0) {
|
|
1913
|
+
svg.setAttribute("viewBox", `0 0 ${vw} ${vh}`);
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
const wrapper = doc.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
1917
|
+
wrapper.setAttribute("transform", `translate(${-vx} ${-vy})`);
|
|
1918
|
+
for (const child of translatableChildren) {
|
|
1919
|
+
wrapper.appendChild(child);
|
|
1920
|
+
}
|
|
1921
|
+
svg.appendChild(wrapper);
|
|
1922
|
+
svg.setAttribute("viewBox", `0 0 ${vw} ${vh}`);
|
|
1923
|
+
}
|
|
1924
|
+
async function fetchSvgAsElement(imageUrl, colorMap) {
|
|
1925
|
+
if (!imageUrl) return null;
|
|
1926
|
+
const isSvgUrl = imageUrl.toLowerCase().includes(".svg") || imageUrl.startsWith("data:image/svg+xml");
|
|
1927
|
+
if (!isSvgUrl) return null;
|
|
1928
|
+
try {
|
|
1929
|
+
let text;
|
|
1930
|
+
if (imageUrl.startsWith("data:image/svg+xml;base64,")) {
|
|
1931
|
+
const b64 = imageUrl.slice("data:image/svg+xml;base64,".length);
|
|
1932
|
+
const binary = atob(b64);
|
|
1933
|
+
try {
|
|
1934
|
+
text = decodeURIComponent(
|
|
1935
|
+
Array.from(binary).map((ch) => `%${ch.charCodeAt(0).toString(16).padStart(2, "0")}`).join("")
|
|
1936
|
+
);
|
|
1937
|
+
} catch {
|
|
1938
|
+
text = binary;
|
|
1939
|
+
}
|
|
1940
|
+
} else if (imageUrl.startsWith("data:image/svg+xml,")) {
|
|
1941
|
+
text = decodeURIComponent(imageUrl.slice("data:image/svg+xml,".length).replace(/\+/g, " "));
|
|
1942
|
+
} else if (imageUrl.startsWith("data:") || imageUrl.startsWith("blob:")) {
|
|
1943
|
+
const res = await fetch(imageUrl);
|
|
1944
|
+
if (!res.ok) return null;
|
|
1945
|
+
text = await res.text();
|
|
1946
|
+
} else {
|
|
1947
|
+
const url = getProxiedImageUrl(imageUrl);
|
|
1948
|
+
const res = await fetch(url);
|
|
1949
|
+
if (!res.ok) return null;
|
|
1950
|
+
text = await res.text();
|
|
1951
|
+
}
|
|
1952
|
+
text = text.trim().replace(/^\uFEFF/, "").replace(/<(\/?)\s*svg:svg\b/gi, "<$1svg").replace(/<(\/?)\s*svg:/gi, "<$1").replace(/(\s)svg:([a-zA-Z_][\w:.-]*)(\s*=)/gi, "$1$2$3").replace(/\s+xmlns:svg\s*=\s*"[^"]*"/gi, "").replace(/\s+xmlns:svg\s*=\s*'[^']*'/gi, "");
|
|
1953
|
+
if (!/\sxmlns\s*=\s*["'][^"']+["']/i.test(text)) {
|
|
1954
|
+
text = text.replace(/<svg\b/i, '<svg xmlns="http://www.w3.org/2000/svg"');
|
|
1955
|
+
}
|
|
1956
|
+
if (colorMap && Object.keys(colorMap).length > 0) {
|
|
1957
|
+
const { applySvgColorMap } = await import("./svgColorUtils-CIehRL4U.js");
|
|
1958
|
+
text = applySvgColorMap(text, colorMap);
|
|
1959
|
+
}
|
|
1960
|
+
const parser = new DOMParser();
|
|
1961
|
+
const doc = parser.parseFromString(text, "image/svg+xml");
|
|
1962
|
+
if (doc.querySelector("parsererror")) return null;
|
|
1963
|
+
const svg = doc.documentElement;
|
|
1964
|
+
if (!svg || svg.tagName.toUpperCase() !== "SVG") return null;
|
|
1965
|
+
if (/xlink:href/i.test(text) && !svg.getAttribute("xmlns:xlink")) {
|
|
1966
|
+
svg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
|
|
1967
|
+
}
|
|
1968
|
+
normalizeSvgViewBoxOrigin(svg);
|
|
1969
|
+
return svg;
|
|
1970
|
+
} catch {
|
|
1971
|
+
return null;
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
async function getRecoloredSvgDataUrl(imageUrl, colorMap) {
|
|
1975
|
+
if (!colorMap || Object.keys(colorMap).length === 0) return null;
|
|
1976
|
+
try {
|
|
1977
|
+
const { getNormalizedSvgUrl } = await import("./index-CuTWdeXZ.js").then((n) => n.Y);
|
|
1978
|
+
return await getNormalizedSvgUrl(imageUrl, colorMap);
|
|
1979
|
+
} catch {
|
|
1980
|
+
return null;
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
function normalizeElement(el) {
|
|
1984
|
+
var _a, _b, _c, _d, _e;
|
|
1985
|
+
const baseW = el.width ?? ((_a = el.size) == null ? void 0 : _a.width) ?? 100;
|
|
1986
|
+
const baseH = el.height ?? ((_b = el.size) == null ? void 0 : _b.height) ?? 100;
|
|
1987
|
+
const scaleX = el.scaleX ?? 1;
|
|
1988
|
+
const scaleY = el.scaleY ?? 1;
|
|
1989
|
+
const w = baseW * scaleX;
|
|
1990
|
+
const h = baseH * scaleY;
|
|
1991
|
+
const originX = el.originX ?? "left";
|
|
1992
|
+
const originY = el.originY ?? "top";
|
|
1993
|
+
const rawX = el.left ?? ((_c = el.position) == null ? void 0 : _c.x) ?? 0;
|
|
1994
|
+
const rawY = el.top ?? ((_d = el.position) == null ? void 0 : _d.y) ?? 0;
|
|
1995
|
+
let x = rawX;
|
|
1996
|
+
let y = rawY;
|
|
1997
|
+
if (originX === "center") x = rawX - w / 2;
|
|
1998
|
+
else if (originX === "right") x = rawX - w;
|
|
1999
|
+
if (originY === "center") y = rawY - h / 2;
|
|
2000
|
+
else if (originY === "bottom") y = rawY - h;
|
|
2001
|
+
const angle = el.angle ?? el.rotation ?? 0;
|
|
2002
|
+
const skewX = el.skewX ?? 0;
|
|
2003
|
+
const skewY = el.skewY ?? 0;
|
|
2004
|
+
const style = el.style || {};
|
|
2005
|
+
const opacity = el.opacity ?? style.opacity ?? 1;
|
|
2006
|
+
const fill = el.fill || style.fill || "";
|
|
2007
|
+
const stroke = el.stroke || style.stroke || "";
|
|
2008
|
+
const strokeWidth = el.strokeWidth ?? style.strokeWidth ?? 0;
|
|
2009
|
+
const strokeDashArray = ((_e = el.strokeDashArray) == null ? void 0 : _e.join(",")) || style.strokeDasharray;
|
|
2010
|
+
const text = el.text || el.content || "";
|
|
2011
|
+
const fontSize = el.fontSize ?? style.fontSize ?? 16;
|
|
2012
|
+
const fontFamily = el.fontFamily || style.fontFamily || "Open Sans";
|
|
2013
|
+
const fontWeightRaw = el.fontWeight ?? style.fontWeight ?? 400;
|
|
2014
|
+
const fontWeight = typeof fontWeightRaw === "number" ? fontWeightRaw : parseInt(String(fontWeightRaw), 10) || 400;
|
|
2015
|
+
const fontStyleRaw = el.fontStyle || style.fontStyle || "normal";
|
|
2016
|
+
const fontStyle = fontStyleRaw === "italic" ? "italic" : "normal";
|
|
2017
|
+
const underline = el.underline ?? false;
|
|
2018
|
+
const letterSpacing = el.charSpacing ?? el.letterSpacing ?? style.letterSpacing ?? 0;
|
|
2019
|
+
const rawTextAlign = el.textAlign || style.textAlign || "left";
|
|
2020
|
+
const textAlign = rawTextAlign === "left" || rawTextAlign === "center" || rawTextAlign === "right" ? rawTextAlign : "left";
|
|
2021
|
+
const lineHeight = el.lineHeight ?? style.lineHeight ?? 1.2;
|
|
2022
|
+
const splitByGrapheme = el.splitByGrapheme ?? el.wordWrap === "break-word";
|
|
2023
|
+
const normalizedShapeType = normalizeShapeType(el.shapeType);
|
|
2024
|
+
const shapeType = normalizedShapeType === "unsupported" ? el.shapeType || "rounded-rect" : normalizedShapeType;
|
|
2025
|
+
const borderRadius = el.rx ?? style.borderRadius ?? 0;
|
|
2026
|
+
const rxTL = el.rxTL;
|
|
2027
|
+
const rxTR = el.rxTR;
|
|
2028
|
+
const rxBR = el.rxBR;
|
|
2029
|
+
const rxBL = el.rxBL;
|
|
2030
|
+
const triRTop = el.triRTop;
|
|
2031
|
+
const triRBR = el.triRBR;
|
|
2032
|
+
const triRBL = el.triRBL;
|
|
2033
|
+
const imageUrl = el.src || el.imageUrl;
|
|
2034
|
+
return {
|
|
2035
|
+
x,
|
|
2036
|
+
y,
|
|
2037
|
+
w,
|
|
2038
|
+
h,
|
|
2039
|
+
angle,
|
|
2040
|
+
skewX,
|
|
2041
|
+
skewY,
|
|
2042
|
+
opacity,
|
|
2043
|
+
fill,
|
|
2044
|
+
stroke,
|
|
2045
|
+
strokeWidth,
|
|
2046
|
+
strokeDashArray,
|
|
2047
|
+
text,
|
|
2048
|
+
fontSize,
|
|
2049
|
+
fontFamily,
|
|
2050
|
+
fontWeight,
|
|
2051
|
+
fontStyle,
|
|
2052
|
+
underline,
|
|
2053
|
+
letterSpacing,
|
|
2054
|
+
textAlign,
|
|
2055
|
+
lineHeight,
|
|
2056
|
+
splitByGrapheme,
|
|
2057
|
+
shapeType,
|
|
2058
|
+
borderRadius,
|
|
2059
|
+
rxTL,
|
|
2060
|
+
rxTR,
|
|
2061
|
+
rxBR,
|
|
2062
|
+
rxBL,
|
|
2063
|
+
triRTop,
|
|
2064
|
+
triRBR,
|
|
2065
|
+
triRBL,
|
|
2066
|
+
imageUrl,
|
|
2067
|
+
originX,
|
|
2068
|
+
originY
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
function parseColor(color) {
|
|
2072
|
+
if (!color) return null;
|
|
2073
|
+
const raw = color.trim().toLowerCase();
|
|
2074
|
+
if (!raw || raw === "transparent" || raw === "none") return null;
|
|
2075
|
+
const clamp = (value) => Math.max(0, Math.min(255, Math.round(value)));
|
|
2076
|
+
const parseNumeric = (token) => {
|
|
2077
|
+
const value = parseFloat(token);
|
|
2078
|
+
return Number.isFinite(value) ? value : NaN;
|
|
2079
|
+
};
|
|
2080
|
+
const parseRgbComponent = (token) => {
|
|
2081
|
+
const value = parseNumeric(token);
|
|
2082
|
+
if (!Number.isFinite(value)) return NaN;
|
|
2083
|
+
return token.endsWith("%") ? value / 100 * 255 : value;
|
|
2084
|
+
};
|
|
2085
|
+
const parseAlpha = (token) => {
|
|
2086
|
+
if (!token) return 1;
|
|
2087
|
+
const value = parseNumeric(token);
|
|
2088
|
+
if (!Number.isFinite(value)) return 1;
|
|
2089
|
+
return token.endsWith("%") ? value / 100 : value;
|
|
2090
|
+
};
|
|
2091
|
+
const toRgbFromHsl = (h, s, l) => {
|
|
2092
|
+
const hue = (h % 360 + 360) % 360;
|
|
2093
|
+
const sat = Math.max(0, Math.min(1, s));
|
|
2094
|
+
const light = Math.max(0, Math.min(1, l));
|
|
2095
|
+
if (sat === 0) {
|
|
2096
|
+
const gray = clamp(light * 255);
|
|
2097
|
+
return { r: gray, g: gray, b: gray };
|
|
2098
|
+
}
|
|
2099
|
+
const q = light < 0.5 ? light * (1 + sat) : light + sat - light * sat;
|
|
2100
|
+
const p = 2 * light - q;
|
|
2101
|
+
const hueToRgb = (t) => {
|
|
2102
|
+
let tt = t;
|
|
2103
|
+
if (tt < 0) tt += 1;
|
|
2104
|
+
if (tt > 1) tt -= 1;
|
|
2105
|
+
if (tt < 1 / 6) return p + (q - p) * 6 * tt;
|
|
2106
|
+
if (tt < 1 / 2) return q;
|
|
2107
|
+
if (tt < 2 / 3) return p + (q - p) * (2 / 3 - tt) * 6;
|
|
2108
|
+
return p;
|
|
2109
|
+
};
|
|
2110
|
+
return {
|
|
2111
|
+
r: clamp(hueToRgb(hue / 360 + 1 / 3) * 255),
|
|
2112
|
+
g: clamp(hueToRgb(hue / 360) * 255),
|
|
2113
|
+
b: clamp(hueToRgb(hue / 360 - 1 / 3) * 255)
|
|
2114
|
+
};
|
|
2115
|
+
};
|
|
2116
|
+
if (raw.startsWith("#")) {
|
|
2117
|
+
const hex = raw.slice(1);
|
|
2118
|
+
const expanded = hex.length === 3 || hex.length === 4 ? hex.split("").map((char) => char + char).join("") : hex;
|
|
2119
|
+
if (expanded.length !== 6 && expanded.length !== 8) return null;
|
|
2120
|
+
const intValue = parseInt(expanded.slice(0, 6), 16);
|
|
2121
|
+
if (!Number.isFinite(intValue)) return null;
|
|
2122
|
+
if (expanded.length === 8) {
|
|
2123
|
+
const alphaHex = parseInt(expanded.slice(6, 8), 16);
|
|
2124
|
+
if (Number.isFinite(alphaHex) && alphaHex <= 0) return null;
|
|
2125
|
+
}
|
|
2126
|
+
return {
|
|
2127
|
+
r: intValue >> 16 & 255,
|
|
2128
|
+
g: intValue >> 8 & 255,
|
|
2129
|
+
b: intValue & 255
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
const rgbMatch = raw.match(/^rgba?\((.+)\)$/);
|
|
2133
|
+
if (rgbMatch) {
|
|
2134
|
+
const normalized = rgbMatch[1].replace(/\//g, " ").replace(/,/g, " ");
|
|
2135
|
+
const parts = normalized.split(/\s+/).filter(Boolean);
|
|
2136
|
+
if (parts.length >= 3) {
|
|
2137
|
+
const alpha = parseAlpha(parts[3]);
|
|
2138
|
+
if (alpha <= 0) return null;
|
|
2139
|
+
const r = parseRgbComponent(parts[0]);
|
|
2140
|
+
const g = parseRgbComponent(parts[1]);
|
|
2141
|
+
const b = parseRgbComponent(parts[2]);
|
|
2142
|
+
if (Number.isFinite(r) && Number.isFinite(g) && Number.isFinite(b)) {
|
|
2143
|
+
return { r: clamp(r), g: clamp(g), b: clamp(b) };
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
const hslMatch = raw.match(/^hsla?\((.+)\)$/);
|
|
2148
|
+
if (hslMatch) {
|
|
2149
|
+
const normalized = hslMatch[1].replace(/\//g, " ").replace(/,/g, " ");
|
|
2150
|
+
const parts = normalized.split(/\s+/).filter(Boolean);
|
|
2151
|
+
if (parts.length >= 3) {
|
|
2152
|
+
const alpha = parseAlpha(parts[3]);
|
|
2153
|
+
if (alpha <= 0) return null;
|
|
2154
|
+
const h = parseNumeric(parts[0].replace(/deg$/, ""));
|
|
2155
|
+
const sRaw = parseNumeric(parts[1].replace(/%$/, ""));
|
|
2156
|
+
const lRaw = parseNumeric(parts[2].replace(/%$/, ""));
|
|
2157
|
+
if (Number.isFinite(h) && Number.isFinite(sRaw) && Number.isFinite(lRaw)) {
|
|
2158
|
+
return toRgbFromHsl(h, sRaw / 100, lRaw / 100);
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
return null;
|
|
2163
|
+
}
|
|
2164
|
+
function rgbToHex(r, g, b) {
|
|
2165
|
+
return "#" + [r, g, b].map((x) => Math.max(0, Math.min(255, Math.round(x))).toString(16).padStart(2, "0")).join("");
|
|
2166
|
+
}
|
|
2167
|
+
function inlineComputedStyles(svg) {
|
|
2168
|
+
if (typeof document === "undefined") return;
|
|
2169
|
+
const wrap = document.createElement("div");
|
|
2170
|
+
wrap.style.cssText = "position:fixed;left:-9999px;top:0;width:400px;height:400px;overflow:hidden;pointer-events:none";
|
|
2171
|
+
const root = svg;
|
|
2172
|
+
if (!root.hasAttribute("width")) root.setAttribute("width", "400");
|
|
2173
|
+
if (!root.hasAttribute("height")) root.setAttribute("height", "400");
|
|
2174
|
+
wrap.appendChild(root);
|
|
2175
|
+
document.body.appendChild(wrap);
|
|
2176
|
+
try {
|
|
2177
|
+
let walk = function(el) {
|
|
2178
|
+
var _a;
|
|
2179
|
+
const tag = (_a = el.tagName) == null ? void 0 : _a.toLowerCase();
|
|
2180
|
+
if (drawable.includes(tag)) {
|
|
2181
|
+
const cs = window.getComputedStyle(el);
|
|
2182
|
+
const fill = cs.fill;
|
|
2183
|
+
const stroke = cs.stroke;
|
|
2184
|
+
if (fill && fill !== "none" && fill !== "rgba(0, 0, 0, 0)") {
|
|
2185
|
+
const parsed = parseColor(fill);
|
|
2186
|
+
if (parsed) el.setAttribute("fill", rgbToHex(parsed.r, parsed.g, parsed.b));
|
|
2187
|
+
} else if (fill === "rgba(0, 0, 0, 0)" || fill === "transparent") {
|
|
2188
|
+
el.setAttribute("fill", "none");
|
|
2189
|
+
}
|
|
2190
|
+
if (stroke && stroke !== "none" && stroke !== "rgba(0, 0, 0, 0)") {
|
|
2191
|
+
const parsed = parseColor(stroke);
|
|
2192
|
+
if (parsed) el.setAttribute("stroke", rgbToHex(parsed.r, parsed.g, parsed.b));
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
for (let i = 0; i < el.children.length; i++) walk(el.children[i]);
|
|
2196
|
+
};
|
|
2197
|
+
const drawable = ["path", "rect", "circle", "ellipse", "polygon", "polyline", "line", "text", "tspan"];
|
|
2198
|
+
walk(root);
|
|
2199
|
+
root.remove();
|
|
2200
|
+
} finally {
|
|
2201
|
+
if (wrap.parentNode) document.body.removeChild(wrap);
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
function applyPaintStyle(pdf, style) {
|
|
2205
|
+
if (style === "FD") {
|
|
2206
|
+
pdf.fillStroke();
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2209
|
+
if (style === "F") {
|
|
2210
|
+
pdf.fill();
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
pdf.stroke();
|
|
2214
|
+
}
|
|
2215
|
+
function drawPathWithStyle(pdf, pathOps, style) {
|
|
2216
|
+
const anyPdf = pdf;
|
|
2217
|
+
if (typeof anyPdf.path !== "function") return false;
|
|
2218
|
+
try {
|
|
2219
|
+
anyPdf.path(pathOps);
|
|
2220
|
+
applyPaintStyle(pdf, style);
|
|
2221
|
+
return true;
|
|
2222
|
+
} catch {
|
|
2223
|
+
}
|
|
2224
|
+
try {
|
|
2225
|
+
anyPdf.path(pathOps, style);
|
|
2226
|
+
return true;
|
|
2227
|
+
} catch {
|
|
2228
|
+
return false;
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
function drawRect(pdf, x, y, w, h, fill, stroke, strokeWidth, borderRadius, rxTL, rxTR, rxBR, rxBL) {
|
|
2232
|
+
const fillColor = parseColor(fill);
|
|
2233
|
+
const strokeColor = parseColor(stroke);
|
|
2234
|
+
if (fillColor) {
|
|
2235
|
+
pdf.setFillColor(fillColor.r, fillColor.g, fillColor.b);
|
|
2236
|
+
}
|
|
2237
|
+
if (strokeColor && strokeWidth > 0) {
|
|
2238
|
+
pdf.setDrawColor(strokeColor.r, strokeColor.g, strokeColor.b);
|
|
2239
|
+
pdf.setLineWidth(strokeWidth);
|
|
2240
|
+
const hasIndividualVals = rxTL != null && rxTL > 0 || rxTR != null && rxTR > 0 || rxBR != null && rxBR > 0 || rxBL != null && rxBL > 0;
|
|
2241
|
+
const hasUniformRounding = !hasIndividualVals && borderRadius != null && borderRadius > 0;
|
|
2242
|
+
if (typeof pdf.setLineJoin === "function") pdf.setLineJoin(hasUniformRounding ? "round" : "miter");
|
|
2243
|
+
if (typeof pdf.setLineCap === "function") pdf.setLineCap(hasUniformRounding ? "round" : "butt");
|
|
2244
|
+
}
|
|
2245
|
+
const style = fillColor && strokeColor && strokeWidth > 0 ? "FD" : fillColor ? "F" : strokeColor && strokeWidth > 0 ? "S" : void 0;
|
|
2246
|
+
if (!style) return;
|
|
2247
|
+
const hasIndividualCorners = rxTL != null && rxTL > 0 || rxTR != null && rxTR > 0 || rxBR != null && rxBR > 0 || rxBL != null && rxBL > 0;
|
|
2248
|
+
if (hasIndividualCorners) {
|
|
2249
|
+
const { tl, tr, br, bl } = getRoundedRectRadii(w, h, {
|
|
2250
|
+
rx: borderRadius ?? 0,
|
|
2251
|
+
rxTL,
|
|
2252
|
+
rxTR,
|
|
2253
|
+
rxBR,
|
|
2254
|
+
rxBL
|
|
2255
|
+
});
|
|
2256
|
+
const k = 0.5522847498307936;
|
|
2257
|
+
const pathOps = [
|
|
2258
|
+
{ op: "m", c: [x + tl, y] },
|
|
2259
|
+
{ op: "l", c: [x + w - tr, y] },
|
|
2260
|
+
...tr > 0 ? [{ op: "c", c: [x + w - tr + tr * k, y, x + w, y + tr - tr * k, x + w, y + tr] }] : [{ op: "l", c: [x + w, y] }],
|
|
2261
|
+
{ op: "l", c: [x + w, y + h - br] },
|
|
2262
|
+
...br > 0 ? [{ op: "c", c: [x + w, y + h - br + br * k, x + w - br + br * k, y + h, x + w - br, y + h] }] : [{ op: "l", c: [x + w, y + h] }],
|
|
2263
|
+
{ op: "l", c: [x + bl, y + h] },
|
|
2264
|
+
...bl > 0 ? [{ op: "c", c: [x + bl - bl * k, y + h, x, y + h - bl + bl * k, x, y + h - bl] }] : [{ op: "l", c: [x, y + h] }],
|
|
2265
|
+
{ op: "l", c: [x, y + tl] },
|
|
2266
|
+
...tl > 0 ? [{ op: "c", c: [x, y + tl - tl * k, x + tl - tl * k, y, x + tl, y] }] : [{ op: "l", c: [x, y] }],
|
|
2267
|
+
{ op: "h", c: [] }
|
|
2268
|
+
];
|
|
2269
|
+
if (drawPathWithStyle(pdf, pathOps, style)) return;
|
|
2270
|
+
}
|
|
2271
|
+
if (borderRadius && borderRadius > 0) {
|
|
2272
|
+
const r = Math.min(borderRadius, w / 2, h / 2);
|
|
2273
|
+
pdf.roundedRect(x, y, w, h, r, r, style);
|
|
2274
|
+
} else {
|
|
2275
|
+
pdf.rect(x, y, w, h, style);
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
function drawEllipse(pdf, cx, cy, rx, ry, fill, stroke, strokeWidth) {
|
|
2279
|
+
const fillColor = parseColor(fill);
|
|
2280
|
+
const strokeColor = parseColor(stroke);
|
|
2281
|
+
if (fillColor) {
|
|
2282
|
+
pdf.setFillColor(fillColor.r, fillColor.g, fillColor.b);
|
|
2283
|
+
}
|
|
2284
|
+
if (strokeColor && strokeWidth > 0) {
|
|
2285
|
+
pdf.setDrawColor(strokeColor.r, strokeColor.g, strokeColor.b);
|
|
2286
|
+
pdf.setLineWidth(strokeWidth);
|
|
2287
|
+
if (typeof pdf.setLineJoin === "function") pdf.setLineJoin("round");
|
|
2288
|
+
if (typeof pdf.setLineCap === "function") pdf.setLineCap("round");
|
|
2289
|
+
}
|
|
2290
|
+
const style = fillColor && strokeColor && strokeWidth > 0 ? "FD" : fillColor ? "F" : strokeColor && strokeWidth > 0 ? "S" : void 0;
|
|
2291
|
+
if (!style) return;
|
|
2292
|
+
if (!Number.isFinite(rx) || !Number.isFinite(ry) || rx <= 0 || ry <= 0) return;
|
|
2293
|
+
const k = 0.5522847498307936;
|
|
2294
|
+
const ox = rx * k;
|
|
2295
|
+
const oy = ry * k;
|
|
2296
|
+
const pathOps = [
|
|
2297
|
+
{ op: "m", c: [cx - rx, cy] },
|
|
2298
|
+
{ op: "c", c: [cx - rx, cy - oy, cx - ox, cy - ry, cx, cy - ry] },
|
|
2299
|
+
{ op: "c", c: [cx + ox, cy - ry, cx + rx, cy - oy, cx + rx, cy] },
|
|
2300
|
+
{ op: "c", c: [cx + rx, cy + oy, cx + ox, cy + ry, cx, cy + ry] },
|
|
2301
|
+
{ op: "c", c: [cx - ox, cy + ry, cx - rx, cy + oy, cx - rx, cy] },
|
|
2302
|
+
{ op: "h", c: [] }
|
|
2303
|
+
];
|
|
2304
|
+
if (drawPathWithStyle(pdf, pathOps, style)) return;
|
|
2305
|
+
pdf.ellipse(cx, cy, rx, ry, style);
|
|
2306
|
+
}
|
|
2307
|
+
function drawLine(pdf, x1, y1, x2, y2, stroke, strokeWidth, dashArray) {
|
|
2308
|
+
const strokeColor = parseColor(stroke);
|
|
2309
|
+
if (!strokeColor || strokeWidth <= 0) return;
|
|
2310
|
+
pdf.setDrawColor(strokeColor.r, strokeColor.g, strokeColor.b);
|
|
2311
|
+
pdf.setLineWidth(strokeWidth);
|
|
2312
|
+
if (typeof pdf.setLineCap === "function") pdf.setLineCap("round");
|
|
2313
|
+
if (typeof pdf.setLineJoin === "function") pdf.setLineJoin("round");
|
|
2314
|
+
if (dashArray) {
|
|
2315
|
+
const dashes = dashArray.split(/[\s,]+/).map(Number).filter((n) => !isNaN(n));
|
|
2316
|
+
if (dashes.length > 0) {
|
|
2317
|
+
pdf.setLineDashPattern(dashes, 0);
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
pdf.line(x1, y1, x2, y2);
|
|
2321
|
+
if (dashArray) {
|
|
2322
|
+
pdf.setLineDashPattern([], 0);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
function drawRoundedTrianglePdf(pdf, x, y, w, h, svgPath, fill, stroke, strokeWidth) {
|
|
2326
|
+
const fillColor = parseColor(fill);
|
|
2327
|
+
const strokeColor = parseColor(stroke);
|
|
2328
|
+
if (fillColor) pdf.setFillColor(fillColor.r, fillColor.g, fillColor.b);
|
|
2329
|
+
if (strokeColor && strokeWidth > 0) {
|
|
2330
|
+
pdf.setDrawColor(strokeColor.r, strokeColor.g, strokeColor.b);
|
|
2331
|
+
pdf.setLineWidth(strokeWidth);
|
|
2332
|
+
if (typeof pdf.setLineJoin === "function") pdf.setLineJoin("miter");
|
|
2333
|
+
if (typeof pdf.setLineCap === "function") pdf.setLineCap("butt");
|
|
2334
|
+
if (typeof pdf.setMiterLimit === "function") pdf.setMiterLimit(TRIANGLE_STROKE_MITER_LIMIT);
|
|
2335
|
+
}
|
|
2336
|
+
const style = fillColor && strokeColor && strokeWidth > 0 ? "FD" : fillColor ? "F" : strokeColor && strokeWidth > 0 ? "S" : void 0;
|
|
2337
|
+
if (!style) return;
|
|
2338
|
+
const pathOps = [];
|
|
2339
|
+
const tokens = svgPath.match(/[MLQZmlqz]|[-+]?[0-9]*\.?[0-9]+/g) || [];
|
|
2340
|
+
let i = 0;
|
|
2341
|
+
while (i < tokens.length) {
|
|
2342
|
+
const cmd = tokens[i];
|
|
2343
|
+
if (cmd === "M" || cmd === "m") {
|
|
2344
|
+
i++;
|
|
2345
|
+
pathOps.push({ op: "m", c: [x + parseFloat(tokens[i]), y + parseFloat(tokens[i + 1])] });
|
|
2346
|
+
i += 2;
|
|
2347
|
+
} else if (cmd === "L" || cmd === "l") {
|
|
2348
|
+
i++;
|
|
2349
|
+
pathOps.push({ op: "l", c: [x + parseFloat(tokens[i]), y + parseFloat(tokens[i + 1])] });
|
|
2350
|
+
i += 2;
|
|
2351
|
+
} else if (cmd === "Q" || cmd === "q") {
|
|
2352
|
+
i++;
|
|
2353
|
+
const qcx = x + parseFloat(tokens[i]);
|
|
2354
|
+
const qcy = y + parseFloat(tokens[i + 1]);
|
|
2355
|
+
const qex = x + parseFloat(tokens[i + 2]);
|
|
2356
|
+
const qey = y + parseFloat(tokens[i + 3]);
|
|
2357
|
+
const lastOp = pathOps[pathOps.length - 1];
|
|
2358
|
+
const px = lastOp ? lastOp.c[lastOp.c.length - 2] : x;
|
|
2359
|
+
const py = lastOp ? lastOp.c[lastOp.c.length - 1] : y;
|
|
2360
|
+
const cp1x = px + 2 / 3 * (qcx - px);
|
|
2361
|
+
const cp1y = py + 2 / 3 * (qcy - py);
|
|
2362
|
+
const cp2x = qex + 2 / 3 * (qcx - qex);
|
|
2363
|
+
const cp2y = qey + 2 / 3 * (qcy - qey);
|
|
2364
|
+
pathOps.push({ op: "c", c: [cp1x, cp1y, cp2x, cp2y, qex, qey] });
|
|
2365
|
+
i += 4;
|
|
2366
|
+
} else if (cmd === "Z" || cmd === "z") {
|
|
2367
|
+
pathOps.push({ op: "h", c: [] });
|
|
2368
|
+
i++;
|
|
2369
|
+
} else {
|
|
2370
|
+
i++;
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
if (drawPathWithStyle(pdf, pathOps, style)) return;
|
|
2374
|
+
}
|
|
2375
|
+
function drawPolygon(pdf, points, fill, stroke, strokeWidth) {
|
|
2376
|
+
if (points.length < 3) return;
|
|
2377
|
+
const fillColor = parseColor(fill);
|
|
2378
|
+
const strokeColor = parseColor(stroke);
|
|
2379
|
+
if (fillColor) {
|
|
2380
|
+
pdf.setFillColor(fillColor.r, fillColor.g, fillColor.b);
|
|
2381
|
+
}
|
|
2382
|
+
if (strokeColor && strokeWidth > 0) {
|
|
2383
|
+
pdf.setDrawColor(strokeColor.r, strokeColor.g, strokeColor.b);
|
|
2384
|
+
pdf.setLineWidth(strokeWidth);
|
|
2385
|
+
if (typeof pdf.setLineJoin === "function") pdf.setLineJoin("miter");
|
|
2386
|
+
if (typeof pdf.setLineCap === "function") pdf.setLineCap("butt");
|
|
2387
|
+
if (typeof pdf.setMiterLimit === "function") pdf.setMiterLimit(TRIANGLE_STROKE_MITER_LIMIT);
|
|
2388
|
+
}
|
|
2389
|
+
const style = fillColor && strokeColor && strokeWidth > 0 ? "FD" : fillColor ? "F" : strokeColor && strokeWidth > 0 ? "S" : void 0;
|
|
2390
|
+
if (!style) return;
|
|
2391
|
+
const pathOps = [
|
|
2392
|
+
{ op: "m", c: [points[0].x, points[0].y] },
|
|
2393
|
+
...points.slice(1).map((point) => ({ op: "l", c: [point.x, point.y] })),
|
|
2394
|
+
{ op: "h", c: [] }
|
|
2395
|
+
];
|
|
2396
|
+
if (drawPathWithStyle(pdf, pathOps, style)) return;
|
|
2397
|
+
pdf.moveTo(points[0].x, points[0].y);
|
|
2398
|
+
for (let i = 1; i < points.length; i++) {
|
|
2399
|
+
pdf.lineTo(points[i].x, points[i].y);
|
|
2400
|
+
}
|
|
2401
|
+
pdf.lineTo(points[0].x, points[0].y);
|
|
2402
|
+
applyPaintStyle(pdf, style);
|
|
2403
|
+
}
|
|
2404
|
+
function addLinkAnnotation(pdf, url, x, y, w, h, canvasHeight) {
|
|
2405
|
+
if (!url || !url.trim()) return;
|
|
2406
|
+
try {
|
|
2407
|
+
pdf.link(x, y, w, h, { url: url.trim() });
|
|
2408
|
+
} catch {
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
const PX_TO_PT = 72 / 96;
|
|
2412
|
+
const FIRST_LINE_BASELINE_MULT = 1.18;
|
|
2413
|
+
const TEXT_UP_NUDGE_PX = 2;
|
|
2414
|
+
const SYMBOL_FONT_SCALE = 0.78;
|
|
2415
|
+
function drawText(pdf, text, x, y, width, fontSizePx, fontFamily, fontWeight, fontStyle, underline, letterSpacing, fill, textAlign, lineHeight, embeddedFonts, splitByGrapheme = true, liveFabricText) {
|
|
2416
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2417
|
+
const fillColor = parseColor(fill);
|
|
2418
|
+
if (!fillColor) return;
|
|
2419
|
+
const fontSizePt = fontSizePx * PX_TO_PT;
|
|
2420
|
+
pdf.setTextColor(fillColor.r, fillColor.g, fillColor.b);
|
|
2421
|
+
pdf.setFontSize(fontSizePt);
|
|
2422
|
+
const isItalic = fontStyle === "italic";
|
|
2423
|
+
const resolvedWeight = resolveFontWeight(fontWeight);
|
|
2424
|
+
const fontKey = `${fontFamily}${FONT_KEY_SEP}${resolvedWeight}`;
|
|
2425
|
+
const italicFontKey = `${fontFamily}${FONT_KEY_SEP}${resolvedWeight}${FONT_KEY_SEP}italic`;
|
|
2426
|
+
const hasEmbeddedItalic = isItalic && embeddedFonts.has(italicFontKey);
|
|
2427
|
+
const mainJsPdfFontName = hasEmbeddedItalic ? embeddedFonts.has(italicFontKey) ? getEmbeddedJsPDFFontName(fontFamily, fontWeight, true) : null : embeddedFonts.has(fontKey) ? getEmbeddedJsPDFFontName(fontFamily, fontWeight) : null;
|
|
2428
|
+
const mainStyle = fontWeight >= 600 ? "bold" : "normal";
|
|
2429
|
+
if (mainJsPdfFontName) {
|
|
2430
|
+
try {
|
|
2431
|
+
pdf.setFont(mainJsPdfFontName, "normal");
|
|
2432
|
+
} catch {
|
|
2433
|
+
pdf.setFont("helvetica", mainStyle);
|
|
2434
|
+
}
|
|
2435
|
+
} else {
|
|
2436
|
+
pdf.setFont("helvetica", mainStyle);
|
|
2437
|
+
}
|
|
2438
|
+
const useSkewForItalic = isItalic && !hasEmbeddedItalic;
|
|
2439
|
+
const fallbackFontKey = `${FONT_FALLBACK_SYMBOLS}${FONT_KEY_SEP}400`;
|
|
2440
|
+
const fallbackJsPdfName = embeddedFonts.has(fallbackFontKey) ? getEmbeddedJsPDFFontName(FONT_FALLBACK_SYMBOLS, 400) : null;
|
|
2441
|
+
const devanagariWeight = resolveFontWeight(fontWeight);
|
|
2442
|
+
const devanagariFontKey = `${FONT_FALLBACK_DEVANAGARI}${FONT_KEY_SEP}${devanagariWeight}`;
|
|
2443
|
+
const devanagariJsPdfName = embeddedFonts.has(devanagariFontKey) ? getEmbeddedJsPDFFontName(FONT_FALLBACK_DEVANAGARI, devanagariWeight) : null;
|
|
2444
|
+
function setRunFont(runType) {
|
|
2445
|
+
if (runType === "devanagari" && devanagariJsPdfName) {
|
|
2446
|
+
try {
|
|
2447
|
+
pdf.setFont(devanagariJsPdfName, "normal");
|
|
2448
|
+
} catch {
|
|
2449
|
+
if (mainJsPdfFontName) pdf.setFont(mainJsPdfFontName, "normal");
|
|
2450
|
+
else pdf.setFont("helvetica", mainStyle);
|
|
2451
|
+
}
|
|
2452
|
+
} else if (runType === "symbol" && fallbackJsPdfName) {
|
|
2453
|
+
try {
|
|
2454
|
+
pdf.setFont(fallbackJsPdfName, "normal");
|
|
2455
|
+
} catch {
|
|
2456
|
+
if (mainJsPdfFontName) pdf.setFont(mainJsPdfFontName, "normal");
|
|
2457
|
+
else pdf.setFont("helvetica", mainStyle);
|
|
2458
|
+
}
|
|
2459
|
+
} else if (mainJsPdfFontName) {
|
|
2460
|
+
try {
|
|
2461
|
+
pdf.setFont(mainJsPdfFontName, "normal");
|
|
2462
|
+
} catch {
|
|
2463
|
+
pdf.setFont("helvetica", mainStyle);
|
|
2464
|
+
}
|
|
2465
|
+
} else {
|
|
2466
|
+
pdf.setFont("helvetica", mainStyle);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
const charSpacePt = letterSpacing !== 0 ? letterSpacing / 1e3 * fontSizePt : 0;
|
|
2470
|
+
if (letterSpacing !== 0) {
|
|
2471
|
+
try {
|
|
2472
|
+
(_a = pdf.setCharSpace) == null ? void 0 : _a.call(pdf, charSpacePt);
|
|
2473
|
+
} catch {
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
let textLines = [];
|
|
2477
|
+
let getBaselineFromTopPx;
|
|
2478
|
+
let getLineLeftOffset;
|
|
2479
|
+
let baselineInPoints = false;
|
|
2480
|
+
if (liveFabricText && typeof liveFabricText.getHeightOfLine === "function") {
|
|
2481
|
+
const live = liveFabricText;
|
|
2482
|
+
textLines = (live.textLines && Array.isArray(live.textLines) ? live.textLines : []).map((line) => Array.isArray(line) ? line.join("") : line);
|
|
2483
|
+
const lh = (live.lineHeight ?? lineHeight) || 1.2;
|
|
2484
|
+
baselineInPoints = true;
|
|
2485
|
+
getBaselineFromTopPx = (lineIndex) => {
|
|
2486
|
+
let sum = 0;
|
|
2487
|
+
for (let j = 0; j < lineIndex; j++) {
|
|
2488
|
+
sum += live.getHeightOfLine(j);
|
|
2489
|
+
}
|
|
2490
|
+
const lineH = live.getHeightOfLine(lineIndex);
|
|
2491
|
+
return sum + lineH / lh;
|
|
2492
|
+
};
|
|
2493
|
+
getLineLeftOffset = (i) => (typeof live._getLineLeftOffset === "function" ? live._getLineLeftOffset(i) : 0) ?? 0;
|
|
2494
|
+
} else {
|
|
2495
|
+
const textbox = new fabric.Textbox(text, {
|
|
2496
|
+
width,
|
|
2497
|
+
fontSize: fontSizePx,
|
|
2498
|
+
fontFamily,
|
|
2499
|
+
fontWeight: String(fontWeight),
|
|
2500
|
+
charSpacing: letterSpacing,
|
|
2501
|
+
textAlign,
|
|
2502
|
+
lineHeight: lineHeight || 1.2,
|
|
2503
|
+
splitByGrapheme,
|
|
2504
|
+
left: 0,
|
|
2505
|
+
top: 0,
|
|
2506
|
+
originX: "left",
|
|
2507
|
+
originY: "top"
|
|
2508
|
+
});
|
|
2509
|
+
textbox.initDimensions();
|
|
2510
|
+
textLines = textbox.textLines || [];
|
|
2511
|
+
const lh = lineHeight || 1.2;
|
|
2512
|
+
getBaselineFromTopPx = (lineIndex) => {
|
|
2513
|
+
var _a2, _b2;
|
|
2514
|
+
let sum = 0;
|
|
2515
|
+
for (let j = 0; j < lineIndex; j++) {
|
|
2516
|
+
sum += ((_a2 = textbox.getHeightOfLine) == null ? void 0 : _a2.call(textbox, j)) ?? fontSizePx * lh;
|
|
2517
|
+
}
|
|
2518
|
+
const lineH = ((_b2 = textbox.getHeightOfLine) == null ? void 0 : _b2.call(textbox, lineIndex)) ?? fontSizePx * lh;
|
|
2519
|
+
return sum + lineH / lh;
|
|
2520
|
+
};
|
|
2521
|
+
getLineLeftOffset = (i) => {
|
|
2522
|
+
var _a2;
|
|
2523
|
+
return ((_a2 = textbox._getLineLeftOffset) == null ? void 0 : _a2.call(textbox, i)) ?? 0;
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
if (textLines.length === 0) return;
|
|
2527
|
+
const italicSkewAngle = isItalic ? -0.21 : 0;
|
|
2528
|
+
for (let i = 0; i < textLines.length; i++) {
|
|
2529
|
+
const lineText = textLines[i];
|
|
2530
|
+
if (!lineText) continue;
|
|
2531
|
+
const pureLatin = isPureLatin(lineText);
|
|
2532
|
+
let totalLineWidth;
|
|
2533
|
+
if (pureLatin) {
|
|
2534
|
+
setRunFont("main");
|
|
2535
|
+
totalLineWidth = pdf.getTextWidth(lineText);
|
|
2536
|
+
} else {
|
|
2537
|
+
const runs = splitLineIntoRuns(lineText);
|
|
2538
|
+
totalLineWidth = 0;
|
|
2539
|
+
for (let r = 0; r < runs.length; r++) {
|
|
2540
|
+
const rt = runs[r].runType;
|
|
2541
|
+
if (rt === "symbol") pdf.setFontSize(fontSizePt * SYMBOL_FONT_SCALE);
|
|
2542
|
+
setRunFont(rt);
|
|
2543
|
+
totalLineWidth += pdf.getTextWidth(runs[r].text);
|
|
2544
|
+
if (rt === "symbol") pdf.setFontSize(fontSizePt);
|
|
2545
|
+
if (letterSpacing !== 0 && r < runs.length - 1) totalLineWidth += charSpacePt;
|
|
2546
|
+
}
|
|
2547
|
+
lineText.__runs = runs;
|
|
2548
|
+
}
|
|
2549
|
+
let lineLeftOffset = getLineLeftOffset(i);
|
|
2550
|
+
if (lineLeftOffset === 0 && (textAlign === "center" || textAlign === "right")) {
|
|
2551
|
+
if (textAlign === "center") {
|
|
2552
|
+
lineLeftOffset = (width - totalLineWidth) / 2;
|
|
2553
|
+
} else if (textAlign === "right") {
|
|
2554
|
+
lineLeftOffset = width - totalLineWidth;
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
const rawBaseline = getBaselineFromTopPx(i);
|
|
2558
|
+
const firstLineRaw = getBaselineFromTopPx(0);
|
|
2559
|
+
const baselineFromTop = i === 0 ? rawBaseline / FIRST_LINE_BASELINE_MULT : firstLineRaw / FIRST_LINE_BASELINE_MULT + (rawBaseline - firstLineRaw);
|
|
2560
|
+
const lineY = y + (baselineInPoints ? baselineFromTop : baselineFromTop * PX_TO_PT) - TEXT_UP_NUDGE_PX;
|
|
2561
|
+
const lineX = x + lineLeftOffset;
|
|
2562
|
+
if (pureLatin) {
|
|
2563
|
+
setRunFont("main");
|
|
2564
|
+
if (useSkewForItalic) {
|
|
2565
|
+
(_b = pdf.saveGraphicsState) == null ? void 0 : _b.call(pdf);
|
|
2566
|
+
try {
|
|
2567
|
+
const pdfInternal = pdf.internal;
|
|
2568
|
+
if (pdfInternal == null ? void 0 : pdfInternal.write) {
|
|
2569
|
+
pdfInternal.write("q");
|
|
2570
|
+
pdfInternal.write(`1 0 ${italicSkewAngle.toFixed(4)} 1 ${lineX.toFixed(2)} ${lineY.toFixed(2)} cm`);
|
|
2571
|
+
pdf.text(lineText, 0, 0);
|
|
2572
|
+
pdfInternal.write("Q");
|
|
2573
|
+
} else {
|
|
2574
|
+
pdf.text(lineText, lineX, lineY);
|
|
2575
|
+
}
|
|
2576
|
+
} catch {
|
|
2577
|
+
pdf.text(lineText, lineX, lineY);
|
|
2578
|
+
}
|
|
2579
|
+
(_c = pdf.restoreGraphicsState) == null ? void 0 : _c.call(pdf);
|
|
2580
|
+
} else {
|
|
2581
|
+
pdf.text(lineText, lineX, lineY);
|
|
2582
|
+
}
|
|
2583
|
+
} else {
|
|
2584
|
+
const runs = lineText.__runs || splitLineIntoRuns(lineText);
|
|
2585
|
+
let runX = lineX;
|
|
2586
|
+
if (useSkewForItalic) {
|
|
2587
|
+
(_d = pdf.saveGraphicsState) == null ? void 0 : _d.call(pdf);
|
|
2588
|
+
try {
|
|
2589
|
+
const pdfInternal = pdf.internal;
|
|
2590
|
+
if (pdfInternal == null ? void 0 : pdfInternal.write) {
|
|
2591
|
+
pdfInternal.write("q");
|
|
2592
|
+
pdfInternal.write(`1 0 ${italicSkewAngle.toFixed(4)} 1 ${lineX.toFixed(2)} ${lineY.toFixed(2)} cm`);
|
|
2593
|
+
runX = 0;
|
|
2594
|
+
for (let r = 0; r < runs.length; r++) {
|
|
2595
|
+
const rt = runs[r].runType;
|
|
2596
|
+
if (rt === "symbol") pdf.setFontSize(fontSizePt * SYMBOL_FONT_SCALE);
|
|
2597
|
+
setRunFont(rt);
|
|
2598
|
+
pdf.text(runs[r].text, runX, 0);
|
|
2599
|
+
runX += pdf.getTextWidth(runs[r].text);
|
|
2600
|
+
if (rt === "symbol") pdf.setFontSize(fontSizePt);
|
|
2601
|
+
if (letterSpacing !== 0 && r < runs.length - 1) runX += charSpacePt;
|
|
2602
|
+
}
|
|
2603
|
+
pdfInternal.write("Q");
|
|
2604
|
+
} else {
|
|
2605
|
+
for (let r = 0; r < runs.length; r++) {
|
|
2606
|
+
const rt = runs[r].runType;
|
|
2607
|
+
if (rt === "symbol") pdf.setFontSize(fontSizePt * SYMBOL_FONT_SCALE);
|
|
2608
|
+
setRunFont(rt);
|
|
2609
|
+
pdf.text(runs[r].text, runX, lineY);
|
|
2610
|
+
runX += pdf.getTextWidth(runs[r].text);
|
|
2611
|
+
if (rt === "symbol") pdf.setFontSize(fontSizePt);
|
|
2612
|
+
if (letterSpacing !== 0 && r < runs.length - 1) runX += charSpacePt;
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
} catch {
|
|
2616
|
+
for (let r = 0; r < runs.length; r++) {
|
|
2617
|
+
const rt = runs[r].runType;
|
|
2618
|
+
if (rt === "symbol") pdf.setFontSize(fontSizePt * SYMBOL_FONT_SCALE);
|
|
2619
|
+
setRunFont(rt);
|
|
2620
|
+
pdf.text(runs[r].text, runX, lineY);
|
|
2621
|
+
runX += pdf.getTextWidth(runs[r].text);
|
|
2622
|
+
if (rt === "symbol") pdf.setFontSize(fontSizePt);
|
|
2623
|
+
if (letterSpacing !== 0 && r < runs.length - 1) runX += charSpacePt;
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
(_e = pdf.restoreGraphicsState) == null ? void 0 : _e.call(pdf);
|
|
2627
|
+
} else {
|
|
2628
|
+
for (let r = 0; r < runs.length; r++) {
|
|
2629
|
+
const rt = runs[r].runType;
|
|
2630
|
+
if (rt === "symbol") pdf.setFontSize(fontSizePt * SYMBOL_FONT_SCALE);
|
|
2631
|
+
setRunFont(rt);
|
|
2632
|
+
pdf.text(runs[r].text, runX, lineY);
|
|
2633
|
+
runX += pdf.getTextWidth(runs[r].text);
|
|
2634
|
+
if (rt === "symbol") pdf.setFontSize(fontSizePt);
|
|
2635
|
+
if (letterSpacing !== 0 && r < runs.length - 1) runX += charSpacePt;
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
if (underline && lineText.trim()) {
|
|
2640
|
+
const underlineY = lineY + fontSizePt * 0.15;
|
|
2641
|
+
const underlineThickness = Math.max(0.5, fontSizePt * 0.066667);
|
|
2642
|
+
pdf.setDrawColor(fillColor.r, fillColor.g, fillColor.b);
|
|
2643
|
+
pdf.setLineWidth(underlineThickness);
|
|
2644
|
+
pdf.line(lineX, underlineY, lineX + totalLineWidth, underlineY);
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
if (letterSpacing !== 0) {
|
|
2648
|
+
try {
|
|
2649
|
+
(_f = pdf.setCharSpace) == null ? void 0 : _f.call(pdf, 0);
|
|
2650
|
+
} catch {
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
function normalizeBgColor(bg) {
|
|
2655
|
+
if (!bg || bg === "transparent" || bg === "none") return "#ffffff";
|
|
2656
|
+
return bg;
|
|
2657
|
+
}
|
|
2658
|
+
function blobToDataUrl(blob) {
|
|
2659
|
+
return new Promise((resolve, reject) => {
|
|
2660
|
+
const reader = new FileReader();
|
|
2661
|
+
reader.onloadend = () => resolve(reader.result);
|
|
2662
|
+
reader.onerror = () => reject(new Error("Failed to read blob"));
|
|
2663
|
+
reader.readAsDataURL(blob);
|
|
2664
|
+
});
|
|
2665
|
+
}
|
|
2666
|
+
function readPngHeader(buf) {
|
|
2667
|
+
try {
|
|
2668
|
+
const bytes = new Uint8Array(buf);
|
|
2669
|
+
if (bytes[0] !== 137 || bytes[1] !== 80 || bytes[2] !== 78 || bytes[3] !== 71) {
|
|
2670
|
+
return null;
|
|
2671
|
+
}
|
|
2672
|
+
const type = String.fromCharCode(bytes[12], bytes[13], bytes[14], bytes[15]);
|
|
2673
|
+
if (type !== "IHDR") return null;
|
|
2674
|
+
const bitDepth = bytes[24];
|
|
2675
|
+
const colorType = bytes[25];
|
|
2676
|
+
return { bitDepth, colorType };
|
|
2677
|
+
} catch {
|
|
2678
|
+
return null;
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
async function detectAlpha(blob) {
|
|
2682
|
+
const bitmap = await createImageBitmap(blob);
|
|
2683
|
+
const w = 32;
|
|
2684
|
+
const h = 32;
|
|
2685
|
+
const c = document.createElement("canvas");
|
|
2686
|
+
c.width = w;
|
|
2687
|
+
c.height = h;
|
|
2688
|
+
const ctx = c.getContext("2d");
|
|
2689
|
+
if (!ctx) return false;
|
|
2690
|
+
ctx.clearRect(0, 0, w, h);
|
|
2691
|
+
ctx.drawImage(bitmap, 0, 0, w, h);
|
|
2692
|
+
const data = ctx.getImageData(0, 0, w, h).data;
|
|
2693
|
+
for (let i = 3; i < data.length; i += 4) {
|
|
2694
|
+
if (data[i] !== 255) return true;
|
|
2695
|
+
}
|
|
2696
|
+
return false;
|
|
2697
|
+
}
|
|
2698
|
+
function getTargetPixelSize(bitmap, opts) {
|
|
2699
|
+
const dpiScale = 1.5 * (opts.resolutionMultiplier ?? 1);
|
|
2700
|
+
const maxPx = opts.resolutionMultiplier && opts.resolutionMultiplier > 1 ? 4400 : 2200;
|
|
2701
|
+
const capByBitmap = !(opts.resolutionMultiplier && opts.resolutionMultiplier > 1);
|
|
2702
|
+
const desiredW = Math.max(1, Math.round((opts.targetWidthPt ?? bitmap.width) * dpiScale));
|
|
2703
|
+
const desiredH = Math.max(1, Math.round((opts.targetHeightPt ?? bitmap.height) * dpiScale));
|
|
2704
|
+
return {
|
|
2705
|
+
w: Math.min(maxPx, desiredW, capByBitmap ? bitmap.width : Infinity),
|
|
2706
|
+
h: Math.min(maxPx, desiredH, capByBitmap ? bitmap.height : Infinity)
|
|
2707
|
+
};
|
|
2708
|
+
}
|
|
2709
|
+
async function rasterizeToDataUrl(blob, opts, out, flattenBackground) {
|
|
2710
|
+
const bitmap = await createImageBitmap(blob);
|
|
2711
|
+
const { w, h } = getTargetPixelSize(bitmap, opts);
|
|
2712
|
+
const canvas = document.createElement("canvas");
|
|
2713
|
+
canvas.width = w;
|
|
2714
|
+
canvas.height = h;
|
|
2715
|
+
const ctx = canvas.getContext("2d");
|
|
2716
|
+
if (!ctx) throw new Error("Canvas context not available");
|
|
2717
|
+
if (flattenBackground) {
|
|
2718
|
+
ctx.fillStyle = normalizeBgColor(opts.backgroundColor);
|
|
2719
|
+
ctx.fillRect(0, 0, w, h);
|
|
2720
|
+
} else {
|
|
2721
|
+
ctx.clearRect(0, 0, w, h);
|
|
2722
|
+
}
|
|
2723
|
+
ctx.imageSmoothingEnabled = true;
|
|
2724
|
+
try {
|
|
2725
|
+
ctx.imageSmoothingQuality = "high";
|
|
2726
|
+
} catch {
|
|
2727
|
+
}
|
|
2728
|
+
ctx.drawImage(bitmap, 0, 0, w, h);
|
|
2729
|
+
if (out === "JPEG") {
|
|
2730
|
+
return canvas.toDataURL("image/jpeg", 0.82);
|
|
2731
|
+
}
|
|
2732
|
+
return canvas.toDataURL("image/png");
|
|
2733
|
+
}
|
|
2734
|
+
async function fetchImageAsBase64(imageUrl, opts = {}) {
|
|
2735
|
+
try {
|
|
2736
|
+
const forcePreserveAlpha = opts.preserveAlpha === true;
|
|
2737
|
+
if (imageUrl.startsWith("data:")) {
|
|
2738
|
+
const res = await fetch(imageUrl);
|
|
2739
|
+
const blob2 = await res.blob();
|
|
2740
|
+
const type2 = (blob2.type || "").toLowerCase();
|
|
2741
|
+
if (type2 === "image/jpeg" || type2 === "image/jpg") {
|
|
2742
|
+
return { data: imageUrl, format: "JPEG" };
|
|
2743
|
+
}
|
|
2744
|
+
if (forcePreserveAlpha) {
|
|
2745
|
+
const pngUrl = await rasterizeToDataUrl(blob2, opts, "PNG", false);
|
|
2746
|
+
return { data: pngUrl, format: "PNG" };
|
|
2747
|
+
}
|
|
2748
|
+
const hasAlpha2 = await detectAlpha(blob2);
|
|
2749
|
+
if (hasAlpha2) {
|
|
2750
|
+
const pngUrl = await rasterizeToDataUrl(blob2, opts, "PNG", false);
|
|
2751
|
+
return { data: pngUrl, format: "PNG" };
|
|
2752
|
+
}
|
|
2753
|
+
const jpegUrl2 = await rasterizeToDataUrl(blob2, opts, "JPEG", true);
|
|
2754
|
+
return { data: jpegUrl2, format: "JPEG" };
|
|
2755
|
+
}
|
|
2756
|
+
let fetchUrl = imageUrl;
|
|
2757
|
+
if (imageUrl.startsWith("http://") || imageUrl.startsWith("https://")) {
|
|
2758
|
+
const { isPrivateUrl } = await import("./index-CuTWdeXZ.js").then((n) => n.Y);
|
|
2759
|
+
if (isPrivateUrl(imageUrl)) return null;
|
|
2760
|
+
const proxyUrl = new URL(`${API_URL}/image-proxy`);
|
|
2761
|
+
proxyUrl.searchParams.set("url", imageUrl);
|
|
2762
|
+
fetchUrl = proxyUrl.toString();
|
|
2763
|
+
}
|
|
2764
|
+
const response = await fetch(fetchUrl, { cache: "no-store", ...getImageProxyFetchOptions() });
|
|
2765
|
+
if (!response.ok) return null;
|
|
2766
|
+
const blob = await response.blob();
|
|
2767
|
+
const type = (blob.type || "").toLowerCase();
|
|
2768
|
+
if (type === "image/jpeg" || type === "image/jpg") {
|
|
2769
|
+
const dataUrl = await blobToDataUrl(blob);
|
|
2770
|
+
return { data: dataUrl, format: "JPEG" };
|
|
2771
|
+
}
|
|
2772
|
+
if (type === "image/png") {
|
|
2773
|
+
const buf = await blob.arrayBuffer();
|
|
2774
|
+
const hdr = readPngHeader(buf);
|
|
2775
|
+
if ((hdr == null ? void 0 : hdr.bitDepth) === 16) {
|
|
2776
|
+
const flattenWithBg2 = !forcePreserveAlpha && !!(opts.backgroundColor && opts.backgroundColor !== "transparent" && opts.backgroundColor !== "none");
|
|
2777
|
+
const pngUrl = await rasterizeToDataUrl(new Blob([buf], { type: "image/png" }), opts, "PNG", flattenWithBg2);
|
|
2778
|
+
return { data: pngUrl, format: "PNG" };
|
|
2779
|
+
}
|
|
2780
|
+
if (forcePreserveAlpha) {
|
|
2781
|
+
const dataUrl2 = await blobToDataUrl(blob);
|
|
2782
|
+
return { data: dataUrl2, format: "PNG" };
|
|
2783
|
+
}
|
|
2784
|
+
const flattenWithBg = !!(opts.backgroundColor && opts.backgroundColor !== "transparent" && opts.backgroundColor !== "none");
|
|
2785
|
+
if (flattenWithBg) {
|
|
2786
|
+
const hasAlpha2 = (hdr == null ? void 0 : hdr.colorType) === 4 || (hdr == null ? void 0 : hdr.colorType) === 6;
|
|
2787
|
+
if (hasAlpha2) {
|
|
2788
|
+
const pngUrl = await rasterizeToDataUrl(blob, opts, "PNG", true);
|
|
2789
|
+
return { data: pngUrl, format: "PNG" };
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
const dataUrl = await blobToDataUrl(blob);
|
|
2793
|
+
return { data: dataUrl, format: "PNG" };
|
|
2794
|
+
}
|
|
2795
|
+
if (forcePreserveAlpha) {
|
|
2796
|
+
const pngUrl = await rasterizeToDataUrl(blob, opts, "PNG", false);
|
|
2797
|
+
return { data: pngUrl, format: "PNG" };
|
|
2798
|
+
}
|
|
2799
|
+
const hasAlpha = await detectAlpha(blob);
|
|
2800
|
+
if (hasAlpha) {
|
|
2801
|
+
const flattenWithBg = !!(opts.backgroundColor && opts.backgroundColor !== "transparent" && opts.backgroundColor !== "none");
|
|
2802
|
+
const pngUrl = await rasterizeToDataUrl(blob, opts, "PNG", flattenWithBg);
|
|
2803
|
+
return { data: pngUrl, format: "PNG" };
|
|
2804
|
+
}
|
|
2805
|
+
const jpegUrl = await rasterizeToDataUrl(blob, opts, "JPEG", true);
|
|
2806
|
+
return { data: jpegUrl, format: "JPEG" };
|
|
2807
|
+
} catch {
|
|
2808
|
+
return null;
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
function getShapePoints(shapeType, x, y, w, h) {
|
|
2812
|
+
const cx = x + w / 2;
|
|
2813
|
+
const cy = y + h / 2;
|
|
2814
|
+
switch (shapeType) {
|
|
2815
|
+
case "triangle":
|
|
2816
|
+
return getTrianglePoints(x, y, w, h);
|
|
2817
|
+
case "diamond":
|
|
2818
|
+
return [
|
|
2819
|
+
{ x: cx, y },
|
|
2820
|
+
{ x: x + w, y: cy },
|
|
2821
|
+
{ x: cx, y: y + h },
|
|
2822
|
+
{ x, y: cy }
|
|
2823
|
+
];
|
|
2824
|
+
case "pentagon":
|
|
2825
|
+
return generateRegularPolygon(5, cx, cy, Math.min(w, h) / 2);
|
|
2826
|
+
case "hexagon":
|
|
2827
|
+
return generateRegularPolygon(6, cx, cy, Math.min(w, h) / 2);
|
|
2828
|
+
case "octagon":
|
|
2829
|
+
return generateRegularPolygon(8, cx, cy, Math.min(w, h) / 2);
|
|
2830
|
+
case "star":
|
|
2831
|
+
return generateStar(5, cx, cy, Math.min(w, h) / 2);
|
|
2832
|
+
case "arrow":
|
|
2833
|
+
return [
|
|
2834
|
+
{ x, y: y + h * 0.3 },
|
|
2835
|
+
{ x: x + w * 0.6, y: y + h * 0.3 },
|
|
2836
|
+
{ x: x + w * 0.6, y },
|
|
2837
|
+
{ x: x + w, y: cy },
|
|
2838
|
+
{ x: x + w * 0.6, y: y + h },
|
|
2839
|
+
{ x: x + w * 0.6, y: y + h * 0.7 },
|
|
2840
|
+
{ x, y: y + h * 0.7 }
|
|
2841
|
+
];
|
|
2842
|
+
case "parallelogram":
|
|
2843
|
+
return [
|
|
2844
|
+
{ x: x + w * 0.2, y },
|
|
2845
|
+
{ x: x + w, y },
|
|
2846
|
+
{ x: x + w * 0.8, y: y + h },
|
|
2847
|
+
{ x, y: y + h }
|
|
2848
|
+
];
|
|
2849
|
+
case "trapezoid":
|
|
2850
|
+
return [
|
|
2851
|
+
{ x: x + w * 0.2, y },
|
|
2852
|
+
{ x: x + w * 0.8, y },
|
|
2853
|
+
{ x: x + w, y: y + h },
|
|
2854
|
+
{ x, y: y + h }
|
|
2855
|
+
];
|
|
2856
|
+
case "cross":
|
|
2857
|
+
const t = 0.35;
|
|
2858
|
+
return [
|
|
2859
|
+
{ x: x + w * t, y },
|
|
2860
|
+
{ x: x + w * (1 - t), y },
|
|
2861
|
+
{ x: x + w * (1 - t), y: y + h * t },
|
|
2862
|
+
{ x: x + w, y: y + h * t },
|
|
2863
|
+
{ x: x + w, y: y + h * (1 - t) },
|
|
2864
|
+
{ x: x + w * (1 - t), y: y + h * (1 - t) },
|
|
2865
|
+
{ x: x + w * (1 - t), y: y + h },
|
|
2866
|
+
{ x: x + w * t, y: y + h },
|
|
2867
|
+
{ x: x + w * t, y: y + h * (1 - t) },
|
|
2868
|
+
{ x, y: y + h * (1 - t) },
|
|
2869
|
+
{ x, y: y + h * t },
|
|
2870
|
+
{ x: x + w * t, y: y + h * t }
|
|
2871
|
+
];
|
|
2872
|
+
default:
|
|
2873
|
+
return null;
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
function generateRegularPolygon(sides, cx, cy, radius) {
|
|
2877
|
+
const points = [];
|
|
2878
|
+
const angleOffset = -Math.PI / 2;
|
|
2879
|
+
for (let i = 0; i < sides; i++) {
|
|
2880
|
+
const angle = angleOffset + 2 * Math.PI * i / sides;
|
|
2881
|
+
points.push({
|
|
2882
|
+
x: cx + radius * Math.cos(angle),
|
|
2883
|
+
y: cy + radius * Math.sin(angle)
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2886
|
+
return points;
|
|
2887
|
+
}
|
|
2888
|
+
function generateStar(points, cx, cy, outerRadius) {
|
|
2889
|
+
const result = [];
|
|
2890
|
+
const innerRadius = outerRadius * 0.4;
|
|
2891
|
+
const angleOffset = -Math.PI / 2;
|
|
2892
|
+
for (let i = 0; i < points * 2; i++) {
|
|
2893
|
+
const radius = i % 2 === 0 ? outerRadius : innerRadius;
|
|
2894
|
+
const angle = angleOffset + Math.PI * i / points;
|
|
2895
|
+
result.push({
|
|
2896
|
+
x: cx + radius * Math.cos(angle),
|
|
2897
|
+
y: cy + radius * Math.sin(angle)
|
|
2898
|
+
});
|
|
2899
|
+
}
|
|
2900
|
+
return result;
|
|
2901
|
+
}
|
|
2902
|
+
function getMatrixScaleFactors(matrix) {
|
|
2903
|
+
if (!matrix || matrix.length !== 6) return { sx: 1, sy: 1 };
|
|
2904
|
+
const [a, b, c, d] = matrix;
|
|
2905
|
+
const sx = Math.hypot(a, b);
|
|
2906
|
+
const sy = Math.hypot(c, d);
|
|
2907
|
+
return {
|
|
2908
|
+
sx: Number.isFinite(sx) && sx > 0 ? sx : 1,
|
|
2909
|
+
sy: Number.isFinite(sy) && sy > 0 ? sy : 1
|
|
2910
|
+
};
|
|
2911
|
+
}
|
|
2912
|
+
function getStrokeScaleCompensationForMatrix(element, matrix) {
|
|
2913
|
+
if (element.type !== "shape" && element.type !== "line") return 1;
|
|
2914
|
+
const { sx, sy } = getMatrixScaleFactors(matrix);
|
|
2915
|
+
if (sx <= 0 || sy <= 0) return 1;
|
|
2916
|
+
const isUniformScale = Math.abs(sx - sy) < 1e-4;
|
|
2917
|
+
const referenceScale = isUniformScale ? sx : Math.max(sx, sy);
|
|
2918
|
+
if (!Number.isFinite(referenceScale) || referenceScale <= 0) return 1;
|
|
2919
|
+
const compensation = 1 / referenceScale;
|
|
2920
|
+
return Math.max(1e-4, Math.min(1e3, compensation));
|
|
2921
|
+
}
|
|
2922
|
+
function withCompensatedStrokeWidth(norm, element, matrix) {
|
|
2923
|
+
if (norm.strokeWidth <= 0) return norm;
|
|
2924
|
+
const compensation = getStrokeScaleCompensationForMatrix(element, matrix);
|
|
2925
|
+
if (Math.abs(compensation - 1) < 1e-4) return norm;
|
|
2926
|
+
return {
|
|
2927
|
+
...norm,
|
|
2928
|
+
strokeWidth: Number((norm.strokeWidth * compensation).toFixed(4))
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
function fabricMatrixToJsPdfAdvancedMatrix(fabricM, _pageHeight) {
|
|
2932
|
+
return fabricM.length === 6 ? fabricM : [1, 0, 0, 1, 0, 0];
|
|
2933
|
+
}
|
|
2934
|
+
async function rasterizeCropGroupToPng(liveGroup, frameW, frameH, multiplier = 2, maintainResolution = false, imageUrl, forceBlobReplace = false, stored, backgroundColor) {
|
|
2935
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
2936
|
+
const bgFill = backgroundColor && backgroundColor !== "transparent" && backgroundColor !== "none" ? normalizeBgColor(backgroundColor) : void 0;
|
|
2937
|
+
const cropData = liveGroup.__cropData;
|
|
2938
|
+
const rxRatio = (cropData == null ? void 0 : cropData.rx) || 0;
|
|
2939
|
+
const minFrameDim = Math.min(frameW, frameH);
|
|
2940
|
+
const clipRx = Math.min(rxRatio > 0.5 ? rxRatio : rxRatio * minFrameDim, frameW / 2, frameH / 2);
|
|
2941
|
+
const shape = (cropData == null ? void 0 : cropData.shape) || "rect";
|
|
2942
|
+
const panX = Math.max(0, Math.min(1, (stored == null ? void 0 : stored.panX) ?? ((_b = (_a = cropData == null ? void 0 : cropData._img) == null ? void 0 : _a._ct) == null ? void 0 : _b.panX) ?? 0.5));
|
|
2943
|
+
const panY = Math.max(0, Math.min(1, (stored == null ? void 0 : stored.panY) ?? ((_d = (_c = cropData == null ? void 0 : cropData._img) == null ? void 0 : _c._ct) == null ? void 0 : _d.panY) ?? 0.5));
|
|
2944
|
+
const zoom = Math.max(1, (stored == null ? void 0 : stored.zoom) ?? ((_f = (_e = cropData == null ? void 0 : cropData._img) == null ? void 0 : _e._ct) == null ? void 0 : _f.zoom) ?? 1);
|
|
2945
|
+
if (maintainResolution && imageUrl) {
|
|
2946
|
+
const dims = await loadNaturalDimensionsFromUrl(imageUrl);
|
|
2947
|
+
const natW = (dims == null ? void 0 : dims.width) ?? (stored == null ? void 0 : stored.naturalWidth) ?? frameW;
|
|
2948
|
+
const natH = (dims == null ? void 0 : dims.height) ?? (stored == null ? void 0 : stored.naturalHeight) ?? frameH;
|
|
2949
|
+
const useNatW = natW > 0 && natH > 0 ? natW : frameW;
|
|
2950
|
+
const useNatH = natW > 0 && natH > 0 ? natH : frameH;
|
|
2951
|
+
if (useNatW > frameW || useNatH > frameH) {
|
|
2952
|
+
let htmlImg = null;
|
|
2953
|
+
if (imageUrl.startsWith("data:") || imageUrl.startsWith("blob:")) {
|
|
2954
|
+
try {
|
|
2955
|
+
htmlImg = await new Promise((resolve, reject) => {
|
|
2956
|
+
const im = new Image();
|
|
2957
|
+
im.onload = () => resolve(im);
|
|
2958
|
+
im.onerror = () => reject(new Error("Image load failed"));
|
|
2959
|
+
im.src = imageUrl;
|
|
2960
|
+
});
|
|
2961
|
+
} catch {
|
|
2962
|
+
htmlImg = null;
|
|
2963
|
+
}
|
|
2964
|
+
} else {
|
|
2965
|
+
try {
|
|
2966
|
+
const proxied = getProxiedImageUrl(imageUrl);
|
|
2967
|
+
const res = await fetch(proxied);
|
|
2968
|
+
if (!res.ok) throw new Error("Fetch failed");
|
|
2969
|
+
const blob = await res.blob();
|
|
2970
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
2971
|
+
try {
|
|
2972
|
+
htmlImg = await new Promise((resolve, reject) => {
|
|
2973
|
+
const im = new Image();
|
|
2974
|
+
im.crossOrigin = "anonymous";
|
|
2975
|
+
im.onload = () => resolve(im);
|
|
2976
|
+
im.onerror = () => reject(new Error("Image load failed"));
|
|
2977
|
+
im.src = blobUrl;
|
|
2978
|
+
});
|
|
2979
|
+
} finally {
|
|
2980
|
+
URL.revokeObjectURL(blobUrl);
|
|
2981
|
+
}
|
|
2982
|
+
} catch {
|
|
2983
|
+
htmlImg = null;
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
if (htmlImg) {
|
|
2987
|
+
const iw = htmlImg.naturalWidth || useNatW;
|
|
2988
|
+
const ih = htmlImg.naturalHeight || useNatH;
|
|
2989
|
+
const baseScale = Math.max(frameW / iw, frameH / ih);
|
|
2990
|
+
const finalScale = baseScale * zoom;
|
|
2991
|
+
const drawW = iw * finalScale;
|
|
2992
|
+
const drawH = ih * finalScale;
|
|
2993
|
+
const overflowX = Math.max(0, drawW - frameW);
|
|
2994
|
+
const overflowY = Math.max(0, drawH - frameH);
|
|
2995
|
+
const offsetX = overflowX > 0 ? -overflowX * (panX - 0.5) : 0;
|
|
2996
|
+
const offsetY = overflowY > 0 ? -overflowY * (panY - 0.5) : 0;
|
|
2997
|
+
const imageLeft = frameW / 2 + offsetX - iw * finalScale / 2;
|
|
2998
|
+
const imageTop = frameH / 2 + offsetY - ih * finalScale / 2;
|
|
2999
|
+
const srcX = Math.max(0, Math.floor(-imageLeft / finalScale));
|
|
3000
|
+
const srcY = Math.max(0, Math.floor(-imageTop / finalScale));
|
|
3001
|
+
const srcW = Math.min(Math.ceil(frameW / finalScale), iw - srcX);
|
|
3002
|
+
const srcH = Math.min(Math.ceil(frameH / finalScale), ih - srcY);
|
|
3003
|
+
if (srcW >= 1 && srcH >= 1) {
|
|
3004
|
+
const cropCanvas = document.createElement("canvas");
|
|
3005
|
+
cropCanvas.width = srcW;
|
|
3006
|
+
cropCanvas.height = srcH;
|
|
3007
|
+
const ctx = cropCanvas.getContext("2d");
|
|
3008
|
+
if (ctx) {
|
|
3009
|
+
if (bgFill) {
|
|
3010
|
+
ctx.fillStyle = bgFill;
|
|
3011
|
+
ctx.fillRect(0, 0, srcW, srcH);
|
|
3012
|
+
} else {
|
|
3013
|
+
ctx.clearRect(0, 0, srcW, srcH);
|
|
3014
|
+
}
|
|
3015
|
+
ctx.drawImage(htmlImg, srcX, srcY, srcW, srcH, 0, 0, srcW, srcH);
|
|
3016
|
+
if (shape === "circle" || shape === "roundRect" || shape === "rect") {
|
|
3017
|
+
ctx.globalCompositeOperation = "destination-in";
|
|
3018
|
+
if (shape === "circle") {
|
|
3019
|
+
ctx.beginPath();
|
|
3020
|
+
ctx.ellipse(srcW / 2, srcH / 2, srcW / 2, srcH / 2, 0, 0, 2 * Math.PI);
|
|
3021
|
+
ctx.fill();
|
|
3022
|
+
} else {
|
|
3023
|
+
const rx2 = Math.min(
|
|
3024
|
+
(rxRatio > 0.5 ? rxRatio : rxRatio * minFrameDim) * (srcW / frameW),
|
|
3025
|
+
srcW / 2,
|
|
3026
|
+
srcH / 2
|
|
3027
|
+
);
|
|
3028
|
+
const r = Math.max(0, rx2);
|
|
3029
|
+
ctx.beginPath();
|
|
3030
|
+
ctx.moveTo(r, 0);
|
|
3031
|
+
ctx.lineTo(srcW - r, 0);
|
|
3032
|
+
if (r > 0) ctx.quadraticCurveTo(srcW, 0, srcW, r);
|
|
3033
|
+
ctx.lineTo(srcW, srcH - r);
|
|
3034
|
+
if (r > 0) ctx.quadraticCurveTo(srcW, srcH, srcW - r, srcH);
|
|
3035
|
+
ctx.lineTo(r, srcH);
|
|
3036
|
+
if (r > 0) ctx.quadraticCurveTo(0, srcH, 0, srcH - r);
|
|
3037
|
+
ctx.lineTo(0, r);
|
|
3038
|
+
if (r > 0) ctx.quadraticCurveTo(0, 0, r, 0);
|
|
3039
|
+
ctx.closePath();
|
|
3040
|
+
ctx.fill();
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
return cropCanvas.toDataURL("image/png");
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
if (forceBlobReplace && imageUrl && !imageUrl.startsWith("data:") && !imageUrl.startsWith("blob:")) {
|
|
3050
|
+
const dims = await loadNaturalDimensionsFromUrl(imageUrl);
|
|
3051
|
+
const natW = (dims == null ? void 0 : dims.width) ?? (stored == null ? void 0 : stored.naturalWidth) ?? frameW;
|
|
3052
|
+
const natH = (dims == null ? void 0 : dims.height) ?? (stored == null ? void 0 : stored.naturalHeight) ?? frameH;
|
|
3053
|
+
const useNatW = natW > 0 && natH > 0 ? natW : frameW;
|
|
3054
|
+
const useNatH = natW > 0 && natH > 0 ? natH : frameH;
|
|
3055
|
+
const outW2 = maintainResolution && useNatW > frameW ? Math.max(1, Math.round(useNatW)) : Math.max(1, Math.round(frameW * multiplier));
|
|
3056
|
+
const outH2 = maintainResolution && useNatH > frameH ? Math.max(1, Math.round(useNatH)) : Math.max(1, Math.round(frameH * multiplier));
|
|
3057
|
+
const proxied = getProxiedImageUrl(imageUrl);
|
|
3058
|
+
const res = await fetch(proxied);
|
|
3059
|
+
if (!res.ok) throw new Error(`Export blob fetch failed: ${res.status}`);
|
|
3060
|
+
const blob = await res.blob();
|
|
3061
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
3062
|
+
try {
|
|
3063
|
+
const img = await fabric.FabricImage.fromURL(blobUrl, { crossOrigin: "anonymous" });
|
|
3064
|
+
img.set({ width: useNatW, height: useNatH, originX: "center", originY: "center", objectCaching: false, dirty: true });
|
|
3065
|
+
const baseScale = Math.max(frameW / useNatW, frameH / useNatH);
|
|
3066
|
+
const finalScale = baseScale * zoom;
|
|
3067
|
+
img.set({ scaleX: finalScale, scaleY: finalScale });
|
|
3068
|
+
const dispW = useNatW * finalScale;
|
|
3069
|
+
const dispH = useNatH * finalScale;
|
|
3070
|
+
const overflowX = Math.max(0, dispW - frameW);
|
|
3071
|
+
const overflowY = Math.max(0, dispH - frameH);
|
|
3072
|
+
img.set({ left: overflowX > 0 ? -overflowX * (panX - 0.5) : 0, top: overflowY > 0 ? -overflowY * (panY - 0.5) : 0 });
|
|
3073
|
+
img.setCoords();
|
|
3074
|
+
const clip = shape === "circle" ? new fabric.Ellipse({ rx: frameW / 2, ry: frameH / 2, left: 0, top: 0, originX: "center", originY: "center", selectable: false, evented: false }) : new fabric.Rect({ width: frameW, height: frameH, rx: clipRx, ry: clipRx, left: 0, top: 0, originX: "center", originY: "center", selectable: false, evented: false });
|
|
3075
|
+
clip.absolutePositioned = false;
|
|
3076
|
+
clip.excludeFromExport = false;
|
|
3077
|
+
const group = new fabric.Group([img], { left: outW2 / 2, top: outH2 / 2, originX: "center", originY: "center", width: frameW, height: frameH, scaleX: outW2 / frameW, scaleY: outH2 / frameH, angle: 0, opacity: liveGroup.opacity ?? 1, objectCaching: false });
|
|
3078
|
+
group.clipPath = clip;
|
|
3079
|
+
group.clipPath.absolutePositioned = false;
|
|
3080
|
+
group.clipPath.excludeFromExport = false;
|
|
3081
|
+
group.set({ width: frameW, height: frameH });
|
|
3082
|
+
group.setCoords();
|
|
3083
|
+
if (group.clipPath) group.clipPath.setCoords();
|
|
3084
|
+
const el2 = document.createElement("canvas");
|
|
3085
|
+
el2.width = outW2;
|
|
3086
|
+
el2.height = outH2;
|
|
3087
|
+
const exportCanvas2 = new fabric.StaticCanvas(el2, {
|
|
3088
|
+
width: outW2,
|
|
3089
|
+
height: outH2,
|
|
3090
|
+
backgroundColor: bgFill ?? "transparent",
|
|
3091
|
+
renderOnAddRemove: false
|
|
3092
|
+
});
|
|
3093
|
+
exportCanvas2.add(group);
|
|
3094
|
+
exportCanvas2.renderAll();
|
|
3095
|
+
const png2 = exportCanvas2.toDataURL({ format: "png", multiplier: 1, enableRetinaScaling: false });
|
|
3096
|
+
exportCanvas2.dispose();
|
|
3097
|
+
return png2;
|
|
3098
|
+
} finally {
|
|
3099
|
+
URL.revokeObjectURL(blobUrl);
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
let outW;
|
|
3103
|
+
let outH;
|
|
3104
|
+
let exportNaturalW = 0;
|
|
3105
|
+
let exportNaturalH = 0;
|
|
3106
|
+
let urlDims = null;
|
|
3107
|
+
if (imageUrl && (forceBlobReplace || maintainResolution)) {
|
|
3108
|
+
urlDims = await loadNaturalDimensionsFromUrl(imageUrl);
|
|
3109
|
+
}
|
|
3110
|
+
if (maintainResolution) {
|
|
3111
|
+
let naturalWidth = (urlDims == null ? void 0 : urlDims.width) ?? (((stored == null ? void 0 : stored.naturalWidth) ?? 0) > 0 && ((stored == null ? void 0 : stored.naturalHeight) ?? 0) > 0 ? stored.naturalWidth : 0);
|
|
3112
|
+
let naturalHeight = (urlDims == null ? void 0 : urlDims.height) ?? (naturalWidth > 0 ? stored.naturalHeight : 0);
|
|
3113
|
+
if (naturalWidth <= 0 || naturalHeight <= 0) {
|
|
3114
|
+
let img = cropData == null ? void 0 : cropData._img;
|
|
3115
|
+
if (!img || typeof img.getElement !== "function" && !img._element) {
|
|
3116
|
+
const objects = liveGroup.getObjects();
|
|
3117
|
+
img = objects.find((o) => o instanceof fabric.FabricImage || o.getElement);
|
|
3118
|
+
}
|
|
3119
|
+
if (img && (typeof img.getElement === "function" || img._element)) {
|
|
3120
|
+
const orig = typeof img.getOriginalSize === "function" ? img.getOriginalSize() : null;
|
|
3121
|
+
const htmlImg = orig ? null : typeof img.getElement === "function" ? img.getElement() : img._element;
|
|
3122
|
+
naturalWidth = ((orig == null ? void 0 : orig.width) ?? (htmlImg == null ? void 0 : htmlImg.naturalWidth) ?? img.width) || 0;
|
|
3123
|
+
naturalHeight = ((orig == null ? void 0 : orig.height) ?? (htmlImg == null ? void 0 : htmlImg.naturalHeight) ?? img.height) || 0;
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
const fromFabricLooksBaked = naturalWidth > 0 && naturalHeight > 0 && naturalWidth <= frameW + 2 && naturalHeight <= frameH + 2;
|
|
3127
|
+
if (imageUrl && (naturalWidth <= 0 || naturalHeight <= 0 || fromFabricLooksBaked)) {
|
|
3128
|
+
const dims = await loadNaturalDimensionsFromUrl(imageUrl);
|
|
3129
|
+
if (dims && dims.width > 0 && dims.height > 0) {
|
|
3130
|
+
naturalWidth = dims.width;
|
|
3131
|
+
naturalHeight = dims.height;
|
|
3132
|
+
} else if (fromFabricLooksBaked) {
|
|
3133
|
+
naturalWidth = Math.max(naturalWidth, Math.round(frameW * 4));
|
|
3134
|
+
naturalHeight = Math.max(naturalHeight, Math.round(frameH * 4));
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
if (naturalWidth > 0 && naturalHeight > 0) {
|
|
3138
|
+
exportNaturalW = naturalWidth;
|
|
3139
|
+
exportNaturalH = naturalHeight;
|
|
3140
|
+
let scaleUp = Math.max(naturalWidth / frameW, naturalHeight / frameH, multiplier);
|
|
3141
|
+
if (imageUrl && imageUrl.toLowerCase().includes(".svg")) {
|
|
3142
|
+
scaleUp = Math.max(scaleUp, 4);
|
|
3143
|
+
}
|
|
3144
|
+
outW = Math.max(1, Math.round(frameW * scaleUp));
|
|
3145
|
+
outH = Math.max(1, Math.round(frameH * scaleUp));
|
|
3146
|
+
} else {
|
|
3147
|
+
outW = Math.max(1, Math.round(frameW * multiplier));
|
|
3148
|
+
outH = Math.max(1, Math.round(frameH * multiplier));
|
|
3149
|
+
}
|
|
3150
|
+
} else {
|
|
3151
|
+
outW = Math.max(1, Math.round(frameW * multiplier));
|
|
3152
|
+
outH = Math.max(1, Math.round(frameH * multiplier));
|
|
3153
|
+
}
|
|
3154
|
+
const el = document.createElement("canvas");
|
|
3155
|
+
el.width = outW;
|
|
3156
|
+
el.height = outH;
|
|
3157
|
+
const exportCanvas = new fabric.StaticCanvas(el, {
|
|
3158
|
+
width: outW,
|
|
3159
|
+
height: outH,
|
|
3160
|
+
backgroundColor: bgFill ?? "transparent",
|
|
3161
|
+
renderOnAddRemove: false
|
|
3162
|
+
});
|
|
3163
|
+
const clonedGroup = await liveGroup.clone();
|
|
3164
|
+
clonedGroup.set({
|
|
3165
|
+
objectCaching: false,
|
|
3166
|
+
dirty: true,
|
|
3167
|
+
opacity: liveGroup.opacity ?? 1
|
|
3168
|
+
// Preserve opacity
|
|
3169
|
+
});
|
|
3170
|
+
(_g = clonedGroup._objects) == null ? void 0 : _g.forEach((o) => o.set({ objectCaching: false, dirty: true }));
|
|
3171
|
+
const groupScaleX = maintainResolution ? 1 : multiplier;
|
|
3172
|
+
const groupScaleY = maintainResolution ? 1 : multiplier;
|
|
3173
|
+
const clipW = maintainResolution ? outW : frameW;
|
|
3174
|
+
const clipH = maintainResolution ? outH : frameH;
|
|
3175
|
+
const scaledClipRx = maintainResolution ? Math.min(clipRx * (outW / frameW), outW / 2, outH / 2) : Math.min(clipRx, clipW / 2, clipH / 2);
|
|
3176
|
+
if (!clonedGroup.clipPath && liveGroup.clipPath) {
|
|
3177
|
+
if (shape === "circle") {
|
|
3178
|
+
clonedGroup.clipPath = new fabric.Ellipse({
|
|
3179
|
+
rx: clipW / 2,
|
|
3180
|
+
ry: clipH / 2,
|
|
3181
|
+
left: 0,
|
|
3182
|
+
top: 0,
|
|
3183
|
+
originX: "center",
|
|
3184
|
+
originY: "center",
|
|
3185
|
+
selectable: false,
|
|
3186
|
+
evented: false,
|
|
3187
|
+
hasControls: false,
|
|
3188
|
+
hasBorders: false
|
|
3189
|
+
});
|
|
3190
|
+
} else {
|
|
3191
|
+
clonedGroup.clipPath = new fabric.Rect({
|
|
3192
|
+
width: clipW,
|
|
3193
|
+
height: clipH,
|
|
3194
|
+
rx: scaledClipRx,
|
|
3195
|
+
ry: scaledClipRx,
|
|
3196
|
+
left: 0,
|
|
3197
|
+
top: 0,
|
|
3198
|
+
originX: "center",
|
|
3199
|
+
originY: "center",
|
|
3200
|
+
selectable: false,
|
|
3201
|
+
evented: false,
|
|
3202
|
+
hasControls: false,
|
|
3203
|
+
hasBorders: false
|
|
3204
|
+
});
|
|
3205
|
+
}
|
|
3206
|
+
clonedGroup.clipPath.absolutePositioned = false;
|
|
3207
|
+
clonedGroup.clipPath.excludeFromExport = false;
|
|
3208
|
+
} else if (clonedGroup.clipPath) {
|
|
3209
|
+
if (shape === "circle" && clonedGroup.clipPath instanceof fabric.Ellipse) {
|
|
3210
|
+
clonedGroup.clipPath.set({
|
|
3211
|
+
rx: clipW / 2,
|
|
3212
|
+
ry: clipH / 2
|
|
3213
|
+
});
|
|
3214
|
+
} else if (clonedGroup.clipPath instanceof fabric.Rect) {
|
|
3215
|
+
clonedGroup.clipPath.set({
|
|
3216
|
+
width: clipW,
|
|
3217
|
+
height: clipH,
|
|
3218
|
+
rx: scaledClipRx,
|
|
3219
|
+
ry: scaledClipRx
|
|
3220
|
+
});
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
if (clonedGroup.clipPath) {
|
|
3224
|
+
clonedGroup.clipPath.set({
|
|
3225
|
+
excludeFromExport: false,
|
|
3226
|
+
objectCaching: false,
|
|
3227
|
+
dirty: true,
|
|
3228
|
+
absolutePositioned: false
|
|
3229
|
+
});
|
|
3230
|
+
clonedGroup.clipPath.setCoords();
|
|
3231
|
+
}
|
|
3232
|
+
if (clonedGroup.clipPath) {
|
|
3233
|
+
clonedGroup.clipPath.set({
|
|
3234
|
+
excludeFromExport: false,
|
|
3235
|
+
absolutePositioned: false,
|
|
3236
|
+
objectCaching: false,
|
|
3237
|
+
dirty: true
|
|
3238
|
+
});
|
|
3239
|
+
clonedGroup.clipPath.setCoords();
|
|
3240
|
+
}
|
|
3241
|
+
clonedGroup.set({ width: clipW, height: clipH });
|
|
3242
|
+
clonedGroup.setCoords();
|
|
3243
|
+
clonedGroup.set({
|
|
3244
|
+
left: outW / 2,
|
|
3245
|
+
top: outH / 2,
|
|
3246
|
+
originX: "center",
|
|
3247
|
+
originY: "center",
|
|
3248
|
+
scaleX: groupScaleX,
|
|
3249
|
+
scaleY: groupScaleY,
|
|
3250
|
+
angle: 0,
|
|
3251
|
+
skewX: 0,
|
|
3252
|
+
skewY: 0,
|
|
3253
|
+
flipX: false,
|
|
3254
|
+
flipY: false
|
|
3255
|
+
});
|
|
3256
|
+
clonedGroup.setCoords();
|
|
3257
|
+
if (clonedGroup.clipPath) {
|
|
3258
|
+
clonedGroup.clipPath.setCoords();
|
|
3259
|
+
}
|
|
3260
|
+
if (forceBlobReplace && imageUrl && !imageUrl.startsWith("data:") && !imageUrl.startsWith("blob:")) {
|
|
3261
|
+
const objects = clonedGroup.getObjects();
|
|
3262
|
+
const imgIndex = objects.findIndex((o) => o instanceof fabric.FabricImage);
|
|
3263
|
+
if (imgIndex >= 0) {
|
|
3264
|
+
const oldImg = objects[imgIndex];
|
|
3265
|
+
try {
|
|
3266
|
+
const dims = urlDims ?? await loadNaturalDimensionsFromUrl(imageUrl);
|
|
3267
|
+
const natW = (dims == null ? void 0 : dims.width) ?? (stored == null ? void 0 : stored.naturalWidth) ?? 0;
|
|
3268
|
+
const natH = (dims == null ? void 0 : dims.height) ?? (stored == null ? void 0 : stored.naturalHeight) ?? 0;
|
|
3269
|
+
const useNatW = natW > 0 && natH > 0 ? natW : frameW;
|
|
3270
|
+
const useNatH = natW > 0 && natH > 0 ? natH : frameH;
|
|
3271
|
+
const proxied = getProxiedImageUrl(imageUrl);
|
|
3272
|
+
const res = await fetch(proxied);
|
|
3273
|
+
if (!res.ok) {
|
|
3274
|
+
} else {
|
|
3275
|
+
const blob = await res.blob();
|
|
3276
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
3277
|
+
try {
|
|
3278
|
+
const newImg = await fabric.FabricImage.fromURL(blobUrl, { crossOrigin: "anonymous" });
|
|
3279
|
+
exportNaturalW = useNatW;
|
|
3280
|
+
exportNaturalH = useNatH;
|
|
3281
|
+
newImg.set({
|
|
3282
|
+
width: useNatW,
|
|
3283
|
+
height: useNatH,
|
|
3284
|
+
left: 0,
|
|
3285
|
+
top: 0,
|
|
3286
|
+
scaleX: 1,
|
|
3287
|
+
scaleY: 1,
|
|
3288
|
+
angle: oldImg.angle ?? 0,
|
|
3289
|
+
originX: "center",
|
|
3290
|
+
originY: "center",
|
|
3291
|
+
objectCaching: false,
|
|
3292
|
+
dirty: true
|
|
3293
|
+
});
|
|
3294
|
+
newImg.setCoords();
|
|
3295
|
+
clonedGroup.remove(oldImg);
|
|
3296
|
+
clonedGroup.insertAt(imgIndex, newImg);
|
|
3297
|
+
} finally {
|
|
3298
|
+
URL.revokeObjectURL(blobUrl);
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
} catch (_e2) {
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
const cloneImg = clonedGroup.getObjects().find((o) => o instanceof fabric.FabricImage);
|
|
3306
|
+
if (cloneImg) {
|
|
3307
|
+
let natW = exportNaturalW;
|
|
3308
|
+
let natH = exportNaturalH;
|
|
3309
|
+
if (natW <= 0 || natH <= 0) {
|
|
3310
|
+
const el2 = ((_h = cloneImg.getElement) == null ? void 0 : _h.call(cloneImg)) ?? cloneImg._element;
|
|
3311
|
+
natW = (el2 == null ? void 0 : el2.naturalWidth) ?? (stored == null ? void 0 : stored.naturalWidth) ?? 0;
|
|
3312
|
+
natH = (el2 == null ? void 0 : el2.naturalHeight) ?? (stored == null ? void 0 : stored.naturalHeight) ?? 0;
|
|
3313
|
+
}
|
|
3314
|
+
if (natW > 0 && natH > 0) {
|
|
3315
|
+
const _ct = (_i = cropData == null ? void 0 : cropData._img) == null ? void 0 : _i._ct;
|
|
3316
|
+
const panX2 = Math.max(0, Math.min(1, (stored == null ? void 0 : stored.panX) ?? (_ct == null ? void 0 : _ct.panX) ?? 0.5));
|
|
3317
|
+
const panY2 = Math.max(0, Math.min(1, (stored == null ? void 0 : stored.panY) ?? (_ct == null ? void 0 : _ct.panY) ?? 0.5));
|
|
3318
|
+
const zoom2 = Math.max(1, (stored == null ? void 0 : stored.zoom) ?? (_ct == null ? void 0 : _ct.zoom) ?? 1);
|
|
3319
|
+
const layoutW = maintainResolution ? outW : frameW;
|
|
3320
|
+
const layoutH = maintainResolution ? outH : frameH;
|
|
3321
|
+
const baseScale = Math.max(layoutW / natW, layoutH / natH);
|
|
3322
|
+
const finalScale = baseScale * zoom2;
|
|
3323
|
+
cloneImg.set({
|
|
3324
|
+
width: natW,
|
|
3325
|
+
height: natH,
|
|
3326
|
+
scaleX: finalScale,
|
|
3327
|
+
scaleY: finalScale,
|
|
3328
|
+
originX: "center",
|
|
3329
|
+
originY: "center",
|
|
3330
|
+
objectCaching: false,
|
|
3331
|
+
dirty: true
|
|
3332
|
+
});
|
|
3333
|
+
const dispW = natW * finalScale;
|
|
3334
|
+
const dispH = natH * finalScale;
|
|
3335
|
+
const overflowX = Math.max(0, dispW - layoutW);
|
|
3336
|
+
const overflowY = Math.max(0, dispH - layoutH);
|
|
3337
|
+
const offsetX = overflowX > 0 ? -overflowX * (panX2 - 0.5) : 0;
|
|
3338
|
+
const offsetY = overflowY > 0 ? -overflowY * (panY2 - 0.5) : 0;
|
|
3339
|
+
cloneImg.set({ left: offsetX, top: offsetY });
|
|
3340
|
+
cloneImg.setCoords();
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
clonedGroup.set({ width: clipW, height: clipH });
|
|
3344
|
+
clonedGroup.setCoords();
|
|
3345
|
+
exportCanvas.add(clonedGroup);
|
|
3346
|
+
exportCanvas.renderAll();
|
|
3347
|
+
const png = exportCanvas.toDataURL({
|
|
3348
|
+
format: "png",
|
|
3349
|
+
multiplier: 1,
|
|
3350
|
+
enableRetinaScaling: false
|
|
3351
|
+
});
|
|
3352
|
+
exportCanvas.dispose();
|
|
3353
|
+
return png;
|
|
3354
|
+
}
|
|
3355
|
+
async function drawElement(pdf, element, embeddedFonts, canvasWidth, canvasHeight, backgroundColor, fabricMatrix, fabricIntrinsic, pageId, liveFabricText, pageChildren) {
|
|
3356
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
3357
|
+
if (!element.visible) return;
|
|
3358
|
+
debugLog(`drawElement START id=${element.id} type=${element.type}`);
|
|
3359
|
+
const norm = normalizeElement(element);
|
|
3360
|
+
if (!fabricMatrix && (pageChildren == null ? void 0 : pageChildren.length)) {
|
|
3361
|
+
const node = findNodeById(pageChildren, element.id);
|
|
3362
|
+
if (node) {
|
|
3363
|
+
const abs = getAbsoluteBounds(node, pageChildren);
|
|
3364
|
+
norm.x = abs.left;
|
|
3365
|
+
norm.y = abs.top;
|
|
3366
|
+
norm.w = abs.width;
|
|
3367
|
+
norm.h = abs.height;
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
if (element.smartElementType && element.smartProps && norm.w > 0 && norm.h > 0) {
|
|
3371
|
+
const propsWithFont = {
|
|
3372
|
+
...element.smartProps,
|
|
3373
|
+
fontFamily: element.smartProps.fontFamily || element.fontFamily || "sans-serif"
|
|
3374
|
+
};
|
|
3375
|
+
const freshSvg = renderSmartElementToSvg(element.smartElementType, propsWithFont, norm.w, norm.h);
|
|
3376
|
+
if (freshSvg) {
|
|
3377
|
+
element = {
|
|
3378
|
+
...element,
|
|
3379
|
+
sourceFormat: "svg",
|
|
3380
|
+
src: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(freshSvg)}`,
|
|
3381
|
+
imageUrl: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(freshSvg)}`
|
|
3382
|
+
};
|
|
3383
|
+
Object.assign(norm, { imageUrl: element.src });
|
|
3384
|
+
debugLog(`Smart element regenerated: type=${element.smartElementType} id=${element.id}`);
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
if (element.type === "image" && pageId) {
|
|
3388
|
+
const liveCanvas = getCanvasForPage(pageId);
|
|
3389
|
+
if (liveCanvas) {
|
|
3390
|
+
const liveGroup = liveCanvas.getObjects().find(
|
|
3391
|
+
(obj) => obj.__docuforgeId === element.id && obj.__cropGroup
|
|
3392
|
+
);
|
|
3393
|
+
if (liveGroup) {
|
|
3394
|
+
const cropData = liveGroup.__cropData;
|
|
3395
|
+
const frameW = (cropData == null ? void 0 : cropData.frameW) || liveGroup.width || norm.w;
|
|
3396
|
+
const frameH = (cropData == null ? void 0 : cropData.frameH) || liveGroup.height || norm.h;
|
|
3397
|
+
(cropData == null ? void 0 : cropData.shape) || "rect";
|
|
3398
|
+
(cropData == null ? void 0 : cropData.rx) || 0;
|
|
3399
|
+
const maintainResolution = element.maintainResolution !== false;
|
|
3400
|
+
let exportMultiplier = 2;
|
|
3401
|
+
if (maintainResolution) {
|
|
3402
|
+
let img = cropData == null ? void 0 : cropData._img;
|
|
3403
|
+
if (!img || typeof img.getElement !== "function" && !img._element) {
|
|
3404
|
+
const objects = liveGroup.getObjects();
|
|
3405
|
+
img = objects.find((o) => o instanceof fabric.FabricImage || o.getElement);
|
|
3406
|
+
}
|
|
3407
|
+
if (img && (typeof img.getElement === "function" || img._element)) {
|
|
3408
|
+
const orig = typeof img.getOriginalSize === "function" ? img.getOriginalSize() : null;
|
|
3409
|
+
const htmlImg = orig ? null : typeof img.getElement === "function" ? img.getElement() : img._element;
|
|
3410
|
+
const naturalWidth = ((orig == null ? void 0 : orig.width) ?? (htmlImg == null ? void 0 : htmlImg.naturalWidth) ?? img.width) || frameW;
|
|
3411
|
+
const naturalHeight = ((orig == null ? void 0 : orig.height) ?? (htmlImg == null ? void 0 : htmlImg.naturalHeight) ?? img.height) || frameH;
|
|
3412
|
+
const scaleX2 = naturalWidth / frameW;
|
|
3413
|
+
const scaleY2 = naturalHeight / frameH;
|
|
3414
|
+
exportMultiplier = Math.max(scaleX2, scaleY2, 2);
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
const imageUrlForRaster = element.src || element.imageUrl || liveGroup.__imageSrc;
|
|
3418
|
+
const isSvgSource = element.sourceFormat === "svg" || imageUrlForRaster && imageUrlForRaster.toLowerCase().includes(".svg");
|
|
3419
|
+
if (isSvgSource) {
|
|
3420
|
+
exportMultiplier = Math.max(exportMultiplier, 4);
|
|
3421
|
+
}
|
|
3422
|
+
const canVectorizeSvg = isSvgSource && !!imageUrlForRaster && !elementHasFade(element);
|
|
3423
|
+
if (canVectorizeSvg) {
|
|
3424
|
+
const svgEl = await fetchSvgAsElement(imageUrlForRaster, element.svgColorMap);
|
|
3425
|
+
if (svgEl) {
|
|
3426
|
+
expandSvgUseElements(svgEl, element.id);
|
|
3427
|
+
const svgToDraw = normalizeSvgExplicitColors(svgEl);
|
|
3428
|
+
inlineComputedStyles(svgToDraw);
|
|
3429
|
+
normalizeSvgGradientStopOffsets(svgToDraw);
|
|
3430
|
+
expandSvgGradientHrefs(svgToDraw);
|
|
3431
|
+
resolveSvgGradientRefsToSolid(svgToDraw, element.id);
|
|
3432
|
+
prefixSvgIds(svgToDraw, element.id);
|
|
3433
|
+
bakeGroupOpacityIntoChildren(svgToDraw);
|
|
3434
|
+
injectOpacityIntoSvg(svgToDraw, norm.opacity);
|
|
3435
|
+
try {
|
|
3436
|
+
const anyPdf3 = pdf;
|
|
3437
|
+
const canUseAdvancedTransforms3 = typeof anyPdf3.advancedAPI === "function" && typeof anyPdf3.setCurrentTransformationMatrix === "function" && typeof anyPdf3.Matrix === "function";
|
|
3438
|
+
pdf.setGState(pdf.GState({ opacity: 1, "stroke-opacity": 1 }));
|
|
3439
|
+
const cropZoomVal = cropData ? Math.max(1, ((_b = (_a = cropData._img) == null ? void 0 : _a._ct) == null ? void 0 : _b.zoom) ?? element.cropZoom ?? 1) : 1;
|
|
3440
|
+
const cropPanXVal = cropData ? Math.max(0, Math.min(1, ((_d = (_c = cropData._img) == null ? void 0 : _c._ct) == null ? void 0 : _d.panX) ?? element.cropPanX ?? 0.5)) : 0.5;
|
|
3441
|
+
const cropPanYVal = cropData ? Math.max(0, Math.min(1, ((_f = (_e = cropData._img) == null ? void 0 : _e._ct) == null ? void 0 : _f.panY) ?? element.cropPanY ?? 0.5)) : 0.5;
|
|
3442
|
+
const naturalW = Math.max(1, Number(element.imageNaturalWidth ?? 0) || Number(((_g = cropData == null ? void 0 : cropData._img) == null ? void 0 : _g.width) ?? 0) || frameW);
|
|
3443
|
+
const naturalH = Math.max(1, Number(element.imageNaturalHeight ?? 0) || Number(((_h = cropData == null ? void 0 : cropData._img) == null ? void 0 : _h.height) ?? 0) || frameH);
|
|
3444
|
+
const fitContain = ((cropData == null ? void 0 : cropData.fit) ?? element.imageFit ?? "cover") === "contain";
|
|
3445
|
+
const cropSvgRect = (clipX, clipY, clipW, clipH) => {
|
|
3446
|
+
const baseScale = fitContain ? Math.min(clipW / naturalW, clipH / naturalH) : Math.max(clipW / naturalW, clipH / naturalH);
|
|
3447
|
+
const finalScale = baseScale * (fitContain ? 1 : cropZoomVal);
|
|
3448
|
+
const drawW = naturalW * finalScale;
|
|
3449
|
+
const drawH = naturalH * finalScale;
|
|
3450
|
+
const overflowX = Math.max(0, drawW - clipW);
|
|
3451
|
+
const overflowY = Math.max(0, drawH - clipH);
|
|
3452
|
+
const offX = overflowX > 0 ? -overflowX * (cropPanXVal - 0.5) : 0;
|
|
3453
|
+
const offY = overflowY > 0 ? -overflowY * (cropPanYVal - 0.5) : 0;
|
|
3454
|
+
return {
|
|
3455
|
+
x: clipX + clipW / 2 + offX - drawW / 2,
|
|
3456
|
+
y: clipY + clipH / 2 + offY - drawH / 2,
|
|
3457
|
+
width: drawW,
|
|
3458
|
+
height: drawH
|
|
3459
|
+
};
|
|
3460
|
+
};
|
|
3461
|
+
const localX = -frameW / 2;
|
|
3462
|
+
const localY = -frameH / 2;
|
|
3463
|
+
if (canUseAdvancedTransforms3 && fabricMatrix && fabricMatrix.length === 6) {
|
|
3464
|
+
const mPdf = fabricMatrixToJsPdfAdvancedMatrix(fabricMatrix, canvasHeight);
|
|
3465
|
+
const axisFrame = getAxisAlignedSvgFrameFromMatrix(mPdf, localX, localY, frameW, frameH);
|
|
3466
|
+
if (axisFrame) {
|
|
3467
|
+
const svgDrawRect = cropSvgRect(axisFrame.x, axisFrame.y, axisFrame.width, axisFrame.height);
|
|
3468
|
+
const seq = ++debugSvgDrawSequence;
|
|
3469
|
+
debugLog(`svg2pdf START #${seq} elementId=${element.id} path=cropGroup-axis`);
|
|
3470
|
+
resetPdfColorState(pdf, `before save #${seq} ${element.id}`);
|
|
3471
|
+
pdf.saveGraphicsState();
|
|
3472
|
+
resetPdfColorState(pdf, `after save #${seq} ${element.id}`);
|
|
3473
|
+
pdf.setLineWidth(0);
|
|
3474
|
+
pdf.rect(axisFrame.x, axisFrame.y, axisFrame.width, axisFrame.height, null);
|
|
3475
|
+
pdf.clip();
|
|
3476
|
+
if (typeof pdf.discardPath === "function") pdf.discardPath();
|
|
3477
|
+
resetPdfColorState(pdf, `immediately before svg2pdf #${seq} ${element.id}`);
|
|
3478
|
+
setPdfColorFromSvg(pdf, svgToDraw, element.id);
|
|
3479
|
+
const pdfToUse = pdfWithColorLogging(pdf, seq, element.id);
|
|
3480
|
+
await svg2pdfWithDomMount(svgToDraw, pdfToUse, svg2pdfOpts(svgDrawRect.x, svgDrawRect.y, svgDrawRect.width, svgDrawRect.height));
|
|
3481
|
+
debugLog(`svg2pdf DONE #${seq} elementId=${element.id}`);
|
|
3482
|
+
resetPdfColorState(pdf, `after svg2pdf #${seq} ${element.id}`);
|
|
3483
|
+
pdf.restoreGraphicsState();
|
|
3484
|
+
resetPdfColorState(pdf, `after restore #${seq} ${element.id}`);
|
|
3485
|
+
} else {
|
|
3486
|
+
const transformed = buildMatrixTransformedSvgForPage(svgToDraw, mPdf, localX, localY, frameW, frameH);
|
|
3487
|
+
if (transformed) {
|
|
3488
|
+
const seq = ++debugSvgDrawSequence;
|
|
3489
|
+
debugLog(`svg2pdf START #${seq} elementId=${element.id} path=cropGroup-matrix-wrapper`);
|
|
3490
|
+
resetPdfColorState(pdf, `before save #${seq} ${element.id} matrix-wrapper`);
|
|
3491
|
+
pdf.saveGraphicsState();
|
|
3492
|
+
resetPdfColorState(pdf, `after save #${seq} ${element.id}`);
|
|
3493
|
+
pdf.setLineWidth(0);
|
|
3494
|
+
pdf.rect(transformed.frame.x, transformed.frame.y, transformed.frame.width, transformed.frame.height, null);
|
|
3495
|
+
pdf.clip();
|
|
3496
|
+
if (typeof pdf.discardPath === "function") pdf.discardPath();
|
|
3497
|
+
resetPdfColorState(pdf, `immediately before svg2pdf #${seq} ${element.id}`);
|
|
3498
|
+
setPdfColorFromSvg(pdf, transformed.svg, element.id);
|
|
3499
|
+
const tfCropRect = cropSvgRect(transformed.frame.x, transformed.frame.y, transformed.frame.width, transformed.frame.height);
|
|
3500
|
+
await svg2pdfWithDomMount(
|
|
3501
|
+
transformed.svg,
|
|
3502
|
+
pdfWithColorLogging(pdf, seq, element.id),
|
|
3503
|
+
svg2pdfOpts(tfCropRect.x, tfCropRect.y, tfCropRect.width, tfCropRect.height)
|
|
3504
|
+
);
|
|
3505
|
+
debugLog(`svg2pdf DONE #${seq} elementId=${element.id}`);
|
|
3506
|
+
resetPdfColorState(pdf, `after svg2pdf #${seq} ${element.id}`);
|
|
3507
|
+
pdf.restoreGraphicsState();
|
|
3508
|
+
resetPdfColorState(pdf, `after restore #${seq} ${element.id}`);
|
|
3509
|
+
} else {
|
|
3510
|
+
const pdfForSvg = new Proxy(pdf, {
|
|
3511
|
+
get(target, prop) {
|
|
3512
|
+
if (prop === "advancedAPI") return () => {
|
|
3513
|
+
};
|
|
3514
|
+
return target[prop];
|
|
3515
|
+
}
|
|
3516
|
+
});
|
|
3517
|
+
await new Promise((resolve, reject) => {
|
|
3518
|
+
anyPdf3.advancedAPI(async () => {
|
|
3519
|
+
try {
|
|
3520
|
+
const seq = ++debugSvgDrawSequence;
|
|
3521
|
+
debugLog(`svg2pdf START #${seq} elementId=${element.id} path=cropGroup-matrix-fallback`);
|
|
3522
|
+
resetPdfColorState(pdf, `before save #${seq} ${element.id} matrix`);
|
|
3523
|
+
pdf.saveGraphicsState();
|
|
3524
|
+
resetPdfColorState(pdf, `after save #${seq} ${element.id}`);
|
|
3525
|
+
anyPdf3.setCurrentTransformationMatrix(anyPdf3.Matrix(...mPdf));
|
|
3526
|
+
pdf.setLineWidth(0);
|
|
3527
|
+
pdf.rect(localX, localY, frameW, frameH, null);
|
|
3528
|
+
pdf.clip();
|
|
3529
|
+
if (typeof pdf.discardPath === "function") pdf.discardPath();
|
|
3530
|
+
resetPdfColorState(pdf, `immediately before svg2pdf #${seq} ${element.id}`);
|
|
3531
|
+
setPdfColorFromSvg(pdf, svgToDraw, element.id);
|
|
3532
|
+
const mfCropRect = cropSvgRect(localX, localY, frameW, frameH);
|
|
3533
|
+
await svg2pdfWithDomMount(svgToDraw, pdfWithColorLogging(pdf, seq, element.id), svg2pdfOpts(mfCropRect.x, mfCropRect.y, mfCropRect.width, mfCropRect.height));
|
|
3534
|
+
debugLog(`svg2pdf DONE #${seq} elementId=${element.id}`);
|
|
3535
|
+
resetPdfColorState(pdf, `after svg2pdf #${seq} ${element.id}`);
|
|
3536
|
+
pdf.restoreGraphicsState();
|
|
3537
|
+
resetPdfColorState(pdf, `after restore #${seq} ${element.id}`);
|
|
3538
|
+
resolve();
|
|
3539
|
+
} catch (err) {
|
|
3540
|
+
reject(err);
|
|
3541
|
+
}
|
|
3542
|
+
});
|
|
3543
|
+
});
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
} else {
|
|
3547
|
+
const angle2 = norm.angle ?? 0;
|
|
3548
|
+
const hasRotation2 = Math.abs(angle2) > 0.01;
|
|
3549
|
+
const cx = norm.x + frameW / 2;
|
|
3550
|
+
const cy = norm.y + frameH / 2;
|
|
3551
|
+
if (hasRotation2 && canUseAdvancedTransforms3) {
|
|
3552
|
+
const rad = angle2 * Math.PI / 180;
|
|
3553
|
+
const cos = Math.cos(rad);
|
|
3554
|
+
const sin = Math.sin(rad);
|
|
3555
|
+
const computedM = [cos, sin, -sin, cos, cx, cy];
|
|
3556
|
+
const localX2 = -frameW / 2;
|
|
3557
|
+
const localY2 = -frameH / 2;
|
|
3558
|
+
const pdfForSvg = new Proxy(pdf, {
|
|
3559
|
+
get(target, prop) {
|
|
3560
|
+
if (prop === "advancedAPI") return () => {
|
|
3561
|
+
};
|
|
3562
|
+
return target[prop];
|
|
3563
|
+
}
|
|
3564
|
+
});
|
|
3565
|
+
await new Promise((resolve, reject) => {
|
|
3566
|
+
anyPdf3.advancedAPI(async () => {
|
|
3567
|
+
try {
|
|
3568
|
+
const seq2 = ++debugSvgDrawSequence;
|
|
3569
|
+
debugLog(`svg2pdf START #${seq2} elementId=${element.id} path=cropGroup-rotation`);
|
|
3570
|
+
resetPdfColorState(pdf, `before save #${seq2} ${element.id} rotation`);
|
|
3571
|
+
pdf.saveGraphicsState();
|
|
3572
|
+
resetPdfColorState(pdf, `after save #${seq2} ${element.id}`);
|
|
3573
|
+
anyPdf3.setCurrentTransformationMatrix(anyPdf3.Matrix(...computedM));
|
|
3574
|
+
pdf.setLineWidth(0);
|
|
3575
|
+
pdf.rect(localX2, localY2, frameW, frameH, null);
|
|
3576
|
+
pdf.clip();
|
|
3577
|
+
if (typeof pdf.discardPath === "function") pdf.discardPath();
|
|
3578
|
+
resetPdfColorState(pdf, `immediately before svg2pdf #${seq2} ${element.id}`);
|
|
3579
|
+
setPdfColorFromSvg(pdf, svgToDraw, element.id);
|
|
3580
|
+
const rotCropRect = cropSvgRect(localX2, localY2, frameW, frameH);
|
|
3581
|
+
await svg2pdfWithDomMount(svgToDraw, pdfWithColorLogging(pdf, seq2, element.id), svg2pdfOpts(rotCropRect.x, rotCropRect.y, rotCropRect.width, rotCropRect.height));
|
|
3582
|
+
debugLog(`svg2pdf DONE #${seq2} elementId=${element.id}`);
|
|
3583
|
+
resetPdfColorState(pdf, `after svg2pdf #${seq2} ${element.id}`);
|
|
3584
|
+
pdf.restoreGraphicsState();
|
|
3585
|
+
resetPdfColorState(pdf, `after restore #${seq2} ${element.id}`);
|
|
3586
|
+
resolve();
|
|
3587
|
+
} catch (e) {
|
|
3588
|
+
reject(e);
|
|
3589
|
+
}
|
|
3590
|
+
});
|
|
3591
|
+
});
|
|
3592
|
+
} else {
|
|
3593
|
+
const seq3 = ++debugSvgDrawSequence;
|
|
3594
|
+
debugLog(`svg2pdf START #${seq3} elementId=${element.id} path=cropGroup-norm`);
|
|
3595
|
+
resetPdfColorState(pdf, `before save #${seq3} ${element.id} norm`);
|
|
3596
|
+
pdf.saveGraphicsState();
|
|
3597
|
+
resetPdfColorState(pdf, `after save #${seq3} ${element.id}`);
|
|
3598
|
+
pdf.setLineWidth(0);
|
|
3599
|
+
pdf.rect(norm.x, norm.y, frameW, frameH, null);
|
|
3600
|
+
pdf.clip();
|
|
3601
|
+
if (typeof pdf.discardPath === "function") pdf.discardPath();
|
|
3602
|
+
resetPdfColorState(pdf, `immediately before svg2pdf #${seq3} ${element.id}`);
|
|
3603
|
+
setPdfColorFromSvg(pdf, svgToDraw, element.id);
|
|
3604
|
+
const normCropRect = cropSvgRect(norm.x, norm.y, frameW, frameH);
|
|
3605
|
+
await svg2pdfWithDomMount(svgToDraw, pdfWithColorLogging(pdf, seq3, element.id), svg2pdfOpts(normCropRect.x, normCropRect.y, normCropRect.width, normCropRect.height));
|
|
3606
|
+
debugLog(`svg2pdf DONE #${seq3} elementId=${element.id}`);
|
|
3607
|
+
resetPdfColorState(pdf, `after svg2pdf #${seq3} ${element.id}`);
|
|
3608
|
+
pdf.restoreGraphicsState();
|
|
3609
|
+
resetPdfColorState(pdf, `after restore #${seq3} ${element.id}`);
|
|
3610
|
+
}
|
|
3611
|
+
}
|
|
3612
|
+
pdf.setGState(pdf.GState({ opacity: 1, "stroke-opacity": 1 }));
|
|
3613
|
+
return;
|
|
3614
|
+
} catch {
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
const storedCrop = element.imageNaturalWidth != null && element.imageNaturalHeight != null ? {
|
|
3619
|
+
naturalWidth: element.imageNaturalWidth,
|
|
3620
|
+
naturalHeight: element.imageNaturalHeight,
|
|
3621
|
+
panX: element.cropPanX,
|
|
3622
|
+
panY: element.cropPanY,
|
|
3623
|
+
zoom: element.cropZoom
|
|
3624
|
+
} : void 0;
|
|
3625
|
+
let png;
|
|
3626
|
+
try {
|
|
3627
|
+
png = await rasterizeCropGroupToPng(liveGroup, frameW, frameH, exportMultiplier, maintainResolution, imageUrlForRaster, false, storedCrop, backgroundColor);
|
|
3628
|
+
} catch (err) {
|
|
3629
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3630
|
+
const isTainted = msg.includes("Tainted canvases") || (err == null ? void 0 : err.name) === "SecurityError";
|
|
3631
|
+
if (isTainted && imageUrlForRaster && !imageUrlForRaster.startsWith("data:") && !imageUrlForRaster.startsWith("blob:")) {
|
|
3632
|
+
png = await rasterizeCropGroupToPng(liveGroup, frameW, frameH, exportMultiplier, maintainResolution, imageUrlForRaster, true, storedCrop, backgroundColor);
|
|
3633
|
+
} else {
|
|
3634
|
+
throw err;
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
const anyPdf2 = pdf;
|
|
3638
|
+
const canUseAdvancedTransforms2 = typeof anyPdf2.advancedAPI === "function" && typeof anyPdf2.setCurrentTransformationMatrix === "function" && typeof anyPdf2.Matrix === "function";
|
|
3639
|
+
const clampedOpacity2 = Math.max(0, Math.min(1, norm.opacity));
|
|
3640
|
+
pdf.setGState(pdf.GState({ opacity: clampedOpacity2 }));
|
|
3641
|
+
if (!png || typeof png !== "string" || !png.startsWith("data:image/") || png.length < 200) {
|
|
3642
|
+
return;
|
|
3643
|
+
}
|
|
3644
|
+
const fadedMasked = await bakeEdgeFadeIntoDataUrl(png, element);
|
|
3645
|
+
if (fadedMasked) {
|
|
3646
|
+
png = fadedMasked;
|
|
3647
|
+
}
|
|
3648
|
+
if (canUseAdvancedTransforms2 && fabricMatrix && fabricMatrix.length === 6) {
|
|
3649
|
+
const mPdf = fabricMatrixToJsPdfAdvancedMatrix(fabricMatrix);
|
|
3650
|
+
const localX = -frameW / 2;
|
|
3651
|
+
const localY = -frameH / 2;
|
|
3652
|
+
anyPdf2.advancedAPI(() => {
|
|
3653
|
+
pdf.saveGraphicsState();
|
|
3654
|
+
anyPdf2.setCurrentTransformationMatrix(anyPdf2.Matrix(...mPdf));
|
|
3655
|
+
pdf.addImage(png, "PNG", localX, localY, frameW, frameH, void 0, "SLOW");
|
|
3656
|
+
pdf.restoreGraphicsState();
|
|
3657
|
+
});
|
|
3658
|
+
pdf.setGState(pdf.GState({ opacity: 1, "stroke-opacity": 1 }));
|
|
3659
|
+
return;
|
|
3660
|
+
}
|
|
3661
|
+
pdf.addImage(png, "PNG", norm.x, norm.y, frameW, frameH, void 0, "SLOW");
|
|
3662
|
+
pdf.setGState(pdf.GState({ opacity: 1, "stroke-opacity": 1 }));
|
|
3663
|
+
return;
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3667
|
+
const clampedOpacity = Math.max(0, Math.min(1, norm.opacity));
|
|
3668
|
+
const isSvgImageElement = element.type === "image" && (element.sourceFormat === "svg" || (norm.imageUrl || "").toLowerCase().includes(".svg") || (norm.imageUrl || "").startsWith("data:image/svg+xml"));
|
|
3669
|
+
pdf.setGState(pdf.GState({ opacity: isSvgImageElement ? 1 : clampedOpacity, "stroke-opacity": isSvgImageElement ? 1 : clampedOpacity }));
|
|
3670
|
+
const { angle } = norm;
|
|
3671
|
+
const hasRotation = Math.abs(angle) > 0.01;
|
|
3672
|
+
const intrinsicW = ((fabricIntrinsic == null ? void 0 : fabricIntrinsic.w) ?? 0) > 0 ? fabricIntrinsic.w : element.width ?? norm.w;
|
|
3673
|
+
const intrinsicH = ((fabricIntrinsic == null ? void 0 : fabricIntrinsic.h) ?? 0) > 0 ? fabricIntrinsic.h : element.height ?? norm.h;
|
|
3674
|
+
const scaleX = element.scaleX ?? 1;
|
|
3675
|
+
const scaleY = element.scaleY ?? 1;
|
|
3676
|
+
const visualW = intrinsicW * scaleX;
|
|
3677
|
+
const visualH = intrinsicH * scaleY;
|
|
3678
|
+
const anyPdf = pdf;
|
|
3679
|
+
const canUseAdvancedTransforms = typeof anyPdf.advancedAPI === "function" && typeof anyPdf.setCurrentTransformationMatrix === "function" && typeof anyPdf.Matrix === "function";
|
|
3680
|
+
const drawWithMatrixAdvanced = async (fabricM) => {
|
|
3681
|
+
const mPdf = fabricMatrixToJsPdfAdvancedMatrix(fabricM);
|
|
3682
|
+
if (element.type === "image") {
|
|
3683
|
+
const imageUrl = norm.imageUrl;
|
|
3684
|
+
if (!imageUrl) return;
|
|
3685
|
+
const isSvg = element.sourceFormat === "svg" || imageUrl && imageUrl.toLowerCase().includes(".svg");
|
|
3686
|
+
const canVectorizeSvg = isSvg && !elementHasFade(element);
|
|
3687
|
+
if (canVectorizeSvg) {
|
|
3688
|
+
const svgElement = await fetchSvgAsElement(imageUrl, element.svgColorMap);
|
|
3689
|
+
if (svgElement) {
|
|
3690
|
+
expandSvgUseElements(svgElement, element.id);
|
|
3691
|
+
const svgToDraw = normalizeSvgExplicitColors(svgElement);
|
|
3692
|
+
inlineComputedStyles(svgToDraw);
|
|
3693
|
+
normalizeSvgGradientStopOffsets(svgToDraw);
|
|
3694
|
+
expandSvgGradientHrefs(svgToDraw);
|
|
3695
|
+
resolveSvgGradientRefsToSolid(svgToDraw, element.id);
|
|
3696
|
+
prefixSvgIds(svgToDraw, element.id);
|
|
3697
|
+
bakeGroupOpacityIntoChildren(svgToDraw);
|
|
3698
|
+
injectOpacityIntoSvg(svgToDraw, norm.opacity);
|
|
3699
|
+
try {
|
|
3700
|
+
const localX = -intrinsicW / 2;
|
|
3701
|
+
const localY = -intrinsicH / 2;
|
|
3702
|
+
const axisFrame = getAxisAlignedSvgFrameFromMatrix(mPdf, localX, localY, intrinsicW, intrinsicH);
|
|
3703
|
+
const seq4 = ++debugSvgDrawSequence;
|
|
3704
|
+
debugLog(`svg2pdf START #${seq4} elementId=${element.id} path=${axisFrame ? "drawContentCore-image-axis" : "drawContentCore-image-matrix"}`);
|
|
3705
|
+
resetPdfColorState(pdf, `before setPdfColor #${seq4} ${element.id}`);
|
|
3706
|
+
if (axisFrame) {
|
|
3707
|
+
pdf.saveGraphicsState();
|
|
3708
|
+
pdf.setLineWidth(0);
|
|
3709
|
+
pdf.rect(axisFrame.x, axisFrame.y, axisFrame.width, axisFrame.height, null);
|
|
3710
|
+
pdf.clip();
|
|
3711
|
+
if (typeof pdf.discardPath === "function") pdf.discardPath();
|
|
3712
|
+
resetPdfColorState(pdf, `immediately before svg2pdf #${seq4} ${element.id}`);
|
|
3713
|
+
setPdfColorFromSvg(pdf, svgToDraw, element.id);
|
|
3714
|
+
await svg2pdfWithDomMount(
|
|
3715
|
+
svgToDraw,
|
|
3716
|
+
pdfWithColorLogging(pdf, seq4, element.id),
|
|
3717
|
+
svg2pdfOpts(axisFrame.x, axisFrame.y, axisFrame.width, axisFrame.height)
|
|
3718
|
+
);
|
|
3719
|
+
debugLog(`svg2pdf DONE #${seq4} elementId=${element.id}`);
|
|
3720
|
+
resetPdfColorState(pdf, `after svg2pdf #${seq4} ${element.id}`);
|
|
3721
|
+
pdf.restoreGraphicsState();
|
|
3722
|
+
resetPdfColorState(pdf, `after restore #${seq4} ${element.id}`);
|
|
3723
|
+
} else {
|
|
3724
|
+
const transformed = buildMatrixTransformedSvgForPage(svgToDraw, mPdf, localX, localY, intrinsicW, intrinsicH);
|
|
3725
|
+
if (transformed) {
|
|
3726
|
+
pdf.saveGraphicsState();
|
|
3727
|
+
pdf.setLineWidth(0);
|
|
3728
|
+
pdf.rect(transformed.frame.x, transformed.frame.y, transformed.frame.width, transformed.frame.height, null);
|
|
3729
|
+
pdf.clip();
|
|
3730
|
+
if (typeof pdf.discardPath === "function") pdf.discardPath();
|
|
3731
|
+
resetPdfColorState(pdf, `immediately before svg2pdf #${seq4} ${element.id}`);
|
|
3732
|
+
setPdfColorFromSvg(pdf, transformed.svg, element.id);
|
|
3733
|
+
await svg2pdfWithDomMount(
|
|
3734
|
+
transformed.svg,
|
|
3735
|
+
pdfWithColorLogging(pdf, seq4, element.id),
|
|
3736
|
+
svg2pdfOpts(transformed.frame.x, transformed.frame.y, transformed.frame.width, transformed.frame.height)
|
|
3737
|
+
);
|
|
3738
|
+
debugLog(`svg2pdf DONE #${seq4} elementId=${element.id}`);
|
|
3739
|
+
resetPdfColorState(pdf, `after svg2pdf #${seq4} ${element.id}`);
|
|
3740
|
+
pdf.restoreGraphicsState();
|
|
3741
|
+
resetPdfColorState(pdf, `after restore #${seq4} ${element.id}`);
|
|
3742
|
+
} else {
|
|
3743
|
+
await new Promise((resolve, reject) => {
|
|
3744
|
+
anyPdf.advancedAPI(async () => {
|
|
3745
|
+
try {
|
|
3746
|
+
pdf.saveGraphicsState();
|
|
3747
|
+
anyPdf.setCurrentTransformationMatrix(anyPdf.Matrix(...mPdf));
|
|
3748
|
+
resetPdfColorState(pdf, `immediately before svg2pdf #${seq4} ${element.id}`);
|
|
3749
|
+
setPdfColorFromSvg(pdf, svgToDraw, element.id);
|
|
3750
|
+
await svg2pdfWithDomMount(svgToDraw, pdfWithColorLogging(pdf, seq4, element.id), svg2pdfOpts(localX, localY, intrinsicW, intrinsicH));
|
|
3751
|
+
debugLog(`svg2pdf DONE #${seq4} elementId=${element.id}`);
|
|
3752
|
+
resetPdfColorState(pdf, `after svg2pdf #${seq4} ${element.id}`);
|
|
3753
|
+
pdf.restoreGraphicsState();
|
|
3754
|
+
resetPdfColorState(pdf, `after restore #${seq4} ${element.id}`);
|
|
3755
|
+
resolve();
|
|
3756
|
+
} catch (err) {
|
|
3757
|
+
reject(err);
|
|
3758
|
+
}
|
|
3759
|
+
});
|
|
3760
|
+
});
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3763
|
+
} catch {
|
|
3764
|
+
const imageData2 = await fetchImageAsBase64(imageUrl, {
|
|
3765
|
+
backgroundColor,
|
|
3766
|
+
targetWidthPt: visualW,
|
|
3767
|
+
targetHeightPt: visualH,
|
|
3768
|
+
preserveAlpha: isSvg || visualW > canvasWidth * 0.7 && visualH > canvasHeight * 0.7,
|
|
3769
|
+
resolutionMultiplier: 4
|
|
3770
|
+
});
|
|
3771
|
+
if (imageData2) {
|
|
3772
|
+
const fadedAdv = await bakeEdgeFadeIntoDataUrl(imageData2.data, element);
|
|
3773
|
+
if (fadedAdv) {
|
|
3774
|
+
imageData2.data = fadedAdv;
|
|
3775
|
+
imageData2.format = "PNG";
|
|
3776
|
+
}
|
|
3777
|
+
anyPdf.advancedAPI(() => {
|
|
3778
|
+
pdf.saveGraphicsState();
|
|
3779
|
+
anyPdf.setCurrentTransformationMatrix(anyPdf.Matrix(...mPdf));
|
|
3780
|
+
const localX = -intrinsicW / 2;
|
|
3781
|
+
const localY = -intrinsicH / 2;
|
|
3782
|
+
pdf.addImage(imageData2.data, imageData2.format, localX, localY, intrinsicW, intrinsicH, void 0, "SLOW");
|
|
3783
|
+
pdf.restoreGraphicsState();
|
|
3784
|
+
});
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
return;
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
const preserveAlpha = isSvg || visualW > canvasWidth * 0.7 && visualH > canvasHeight * 0.7;
|
|
3791
|
+
const imageData = await fetchImageAsBase64(imageUrl, {
|
|
3792
|
+
backgroundColor,
|
|
3793
|
+
targetWidthPt: visualW,
|
|
3794
|
+
targetHeightPt: visualH,
|
|
3795
|
+
preserveAlpha: preserveAlpha || elementHasFade(element),
|
|
3796
|
+
resolutionMultiplier: isSvg ? 4 : void 0
|
|
3797
|
+
});
|
|
3798
|
+
if (!imageData) return;
|
|
3799
|
+
const fadedMain = await bakeEdgeFadeIntoDataUrl(imageData.data, element);
|
|
3800
|
+
if (fadedMain) {
|
|
3801
|
+
imageData.data = fadedMain;
|
|
3802
|
+
imageData.format = "PNG";
|
|
3803
|
+
}
|
|
3804
|
+
anyPdf.advancedAPI(() => {
|
|
3805
|
+
pdf.saveGraphicsState();
|
|
3806
|
+
anyPdf.setCurrentTransformationMatrix(anyPdf.Matrix(...mPdf));
|
|
3807
|
+
const localX = -intrinsicW / 2;
|
|
3808
|
+
const localY = -intrinsicH / 2;
|
|
3809
|
+
pdf.addImage(imageData.data, imageData.format, localX, localY, intrinsicW, intrinsicH, void 0, "SLOW");
|
|
3810
|
+
pdf.restoreGraphicsState();
|
|
3811
|
+
});
|
|
3812
|
+
return;
|
|
3813
|
+
}
|
|
3814
|
+
anyPdf.advancedAPI(() => {
|
|
3815
|
+
pdf.saveGraphicsState();
|
|
3816
|
+
anyPdf.setCurrentTransformationMatrix(anyPdf.Matrix(...mPdf));
|
|
3817
|
+
const localX = -intrinsicW / 2;
|
|
3818
|
+
const localY = -intrinsicH / 2;
|
|
3819
|
+
const normForMatrix = withCompensatedStrokeWidth(norm, element, fabricM);
|
|
3820
|
+
void drawElementContentCore(
|
|
3821
|
+
pdf,
|
|
3822
|
+
element,
|
|
3823
|
+
normForMatrix,
|
|
3824
|
+
localX,
|
|
3825
|
+
localY,
|
|
3826
|
+
intrinsicW,
|
|
3827
|
+
intrinsicH,
|
|
3828
|
+
embeddedFonts,
|
|
3829
|
+
canvasWidth,
|
|
3830
|
+
canvasHeight,
|
|
3831
|
+
backgroundColor,
|
|
3832
|
+
1,
|
|
3833
|
+
liveFabricText ?? void 0
|
|
3834
|
+
);
|
|
3835
|
+
pdf.restoreGraphicsState();
|
|
3836
|
+
});
|
|
3837
|
+
};
|
|
3838
|
+
const hasIndividualRoundedCorners = element.type === "shape" && norm.shapeType === "rounded-rect" && (norm.rxTL != null || norm.rxTR != null || norm.rxBR != null || norm.rxBL != null);
|
|
3839
|
+
const hasNonIdentityScale = Math.abs(scaleX - 1) > 1e-4 || Math.abs(scaleY - 1) > 1e-4;
|
|
3840
|
+
const hasSkew = Math.abs(norm.skewX) > 0.01 || Math.abs(norm.skewY) > 0.01;
|
|
3841
|
+
const hasLiveFabricMatrix = !!(fabricMatrix && fabricMatrix.length === 6);
|
|
3842
|
+
const canDrawRoundedRectInPageSpace = hasIndividualRoundedCorners && !hasLiveFabricMatrix && !hasNonIdentityScale && !hasRotation && !hasSkew && !(element.flipX ?? false) && !(element.flipY ?? false);
|
|
3843
|
+
if (canDrawRoundedRectInPageSpace) {
|
|
3844
|
+
await drawElementContent(
|
|
3845
|
+
pdf,
|
|
3846
|
+
element,
|
|
3847
|
+
norm,
|
|
3848
|
+
norm.x,
|
|
3849
|
+
norm.y,
|
|
3850
|
+
visualW,
|
|
3851
|
+
visualH,
|
|
3852
|
+
embeddedFonts,
|
|
3853
|
+
canvasWidth,
|
|
3854
|
+
canvasHeight,
|
|
3855
|
+
backgroundColor,
|
|
3856
|
+
scaleY,
|
|
3857
|
+
liveFabricText ?? void 0
|
|
3858
|
+
);
|
|
3859
|
+
pdf.setGState(pdf.GState({ opacity: 1, "stroke-opacity": 1 }));
|
|
3860
|
+
const linkUrl2 = (_i = element.linkConfig) == null ? void 0 : _i.url;
|
|
3861
|
+
if (linkUrl2) {
|
|
3862
|
+
addLinkAnnotation(pdf, linkUrl2, norm.x, norm.y, visualW, visualH);
|
|
3863
|
+
}
|
|
3864
|
+
debugLog(`drawElement END id=${element.id}`);
|
|
3865
|
+
return;
|
|
3866
|
+
}
|
|
3867
|
+
if (canUseAdvancedTransforms && fabricMatrix && fabricMatrix.length === 6) {
|
|
3868
|
+
await drawWithMatrixAdvanced(fabricMatrix);
|
|
3869
|
+
} else if (canUseAdvancedTransforms && hasIndividualRoundedCorners && hasNonIdentityScale) {
|
|
3870
|
+
const cx = norm.x + visualW / 2;
|
|
3871
|
+
const cy = norm.y + visualH / 2;
|
|
3872
|
+
const computedFabricM = [scaleX, 0, 0, scaleY, cx, cy];
|
|
3873
|
+
await drawWithMatrixAdvanced(computedFabricM);
|
|
3874
|
+
} else if (canUseAdvancedTransforms && hasRotation) {
|
|
3875
|
+
const cx = norm.x + visualW / 2;
|
|
3876
|
+
const cy = norm.y + visualH / 2;
|
|
3877
|
+
const radians = angle * Math.PI / 180;
|
|
3878
|
+
const cos = Math.cos(radians);
|
|
3879
|
+
const sin = Math.sin(radians);
|
|
3880
|
+
const computedFabricM = [
|
|
3881
|
+
scaleX * cos,
|
|
3882
|
+
scaleX * sin,
|
|
3883
|
+
-scaleY * sin,
|
|
3884
|
+
scaleY * cos,
|
|
3885
|
+
cx,
|
|
3886
|
+
cy
|
|
3887
|
+
];
|
|
3888
|
+
await drawWithMatrixAdvanced(computedFabricM);
|
|
3889
|
+
} else if (fabricMatrix && fabricMatrix.length === 6) {
|
|
3890
|
+
pdf.saveGraphicsState();
|
|
3891
|
+
if (typeof anyPdf.setCurrentTransformationMatrix === "function" && typeof anyPdf.Matrix === "function") {
|
|
3892
|
+
const [a, b, c, d, e, f] = fabricMatrix;
|
|
3893
|
+
anyPdf.setCurrentTransformationMatrix(anyPdf.Matrix(a, b, c, d, e, f));
|
|
3894
|
+
}
|
|
3895
|
+
const localX = -intrinsicW / 2;
|
|
3896
|
+
const localY = -intrinsicH / 2;
|
|
3897
|
+
const normForMatrix = withCompensatedStrokeWidth(norm, element, fabricMatrix);
|
|
3898
|
+
await drawElementContentWithIntrinsic(
|
|
3899
|
+
pdf,
|
|
3900
|
+
element,
|
|
3901
|
+
normForMatrix,
|
|
3902
|
+
localX,
|
|
3903
|
+
localY,
|
|
3904
|
+
intrinsicW,
|
|
3905
|
+
intrinsicH,
|
|
3906
|
+
embeddedFonts,
|
|
3907
|
+
canvasWidth,
|
|
3908
|
+
canvasHeight,
|
|
3909
|
+
backgroundColor,
|
|
3910
|
+
liveFabricText ?? void 0
|
|
3911
|
+
);
|
|
3912
|
+
pdf.restoreGraphicsState();
|
|
3913
|
+
} else if (hasRotation) {
|
|
3914
|
+
const cx = norm.x + visualW / 2;
|
|
3915
|
+
const cy = norm.y + visualH / 2;
|
|
3916
|
+
const radians = angle * Math.PI / 180;
|
|
3917
|
+
const cos = Math.cos(radians);
|
|
3918
|
+
const sin = Math.sin(radians);
|
|
3919
|
+
const computedMatrix = [scaleX * cos, scaleX * sin, -scaleY * sin, scaleY * cos, cx, cy];
|
|
3920
|
+
pdf.saveGraphicsState();
|
|
3921
|
+
if (typeof anyPdf.setCurrentTransformationMatrix === "function" && typeof anyPdf.Matrix === "function") {
|
|
3922
|
+
anyPdf.setCurrentTransformationMatrix(anyPdf.Matrix(...computedMatrix));
|
|
3923
|
+
}
|
|
3924
|
+
const localX = -intrinsicW / 2;
|
|
3925
|
+
const localY = -intrinsicH / 2;
|
|
3926
|
+
const normForMatrix = withCompensatedStrokeWidth(norm, element, computedMatrix);
|
|
3927
|
+
await drawElementContentWithIntrinsic(
|
|
3928
|
+
pdf,
|
|
3929
|
+
element,
|
|
3930
|
+
normForMatrix,
|
|
3931
|
+
localX,
|
|
3932
|
+
localY,
|
|
3933
|
+
intrinsicW,
|
|
3934
|
+
intrinsicH,
|
|
3935
|
+
embeddedFonts,
|
|
3936
|
+
canvasWidth,
|
|
3937
|
+
canvasHeight,
|
|
3938
|
+
backgroundColor,
|
|
3939
|
+
liveFabricText ?? void 0
|
|
3940
|
+
);
|
|
3941
|
+
pdf.restoreGraphicsState();
|
|
3942
|
+
} else {
|
|
3943
|
+
await drawElementContent(
|
|
3944
|
+
pdf,
|
|
3945
|
+
element,
|
|
3946
|
+
norm,
|
|
3947
|
+
norm.x,
|
|
3948
|
+
norm.y,
|
|
3949
|
+
visualW,
|
|
3950
|
+
visualH,
|
|
3951
|
+
embeddedFonts,
|
|
3952
|
+
canvasWidth,
|
|
3953
|
+
canvasHeight,
|
|
3954
|
+
backgroundColor,
|
|
3955
|
+
scaleY,
|
|
3956
|
+
liveFabricText ?? void 0
|
|
3957
|
+
);
|
|
3958
|
+
}
|
|
3959
|
+
pdf.setGState(pdf.GState({ opacity: 1, "stroke-opacity": 1 }));
|
|
3960
|
+
const linkUrl = (_j = element.linkConfig) == null ? void 0 : _j.url;
|
|
3961
|
+
if (linkUrl) {
|
|
3962
|
+
addLinkAnnotation(pdf, linkUrl, norm.x, norm.y, visualW, visualH);
|
|
3963
|
+
}
|
|
3964
|
+
debugLog(`drawElement END id=${element.id}`);
|
|
3965
|
+
}
|
|
3966
|
+
async function drawElementContent(pdf, element, norm, x, y, w, h, embeddedFonts, canvasWidth, canvasHeight, backgroundColor, fontScale = 1, liveFabricText) {
|
|
3967
|
+
await drawElementContentCore(pdf, element, norm, x, y, w, h, embeddedFonts, canvasWidth, canvasHeight, backgroundColor, fontScale, liveFabricText);
|
|
3968
|
+
}
|
|
3969
|
+
async function drawElementContentWithIntrinsic(pdf, element, norm, x, y, intrinsicW, intrinsicH, embeddedFonts, canvasWidth, canvasHeight, backgroundColor, liveFabricText) {
|
|
3970
|
+
await drawElementContentCore(pdf, element, norm, x, y, intrinsicW, intrinsicH, embeddedFonts, canvasWidth, canvasHeight, backgroundColor, 1, liveFabricText);
|
|
3971
|
+
}
|
|
3972
|
+
async function drawElementContentCore(pdf, element, norm, x, y, w, h, embeddedFonts, canvasWidth, canvasHeight, backgroundColor, fontScale = 1, liveFabricText) {
|
|
3973
|
+
var _a;
|
|
3974
|
+
switch (element.type) {
|
|
3975
|
+
case "shape": {
|
|
3976
|
+
const { shapeType, fill, stroke, strokeWidth, borderRadius, rxTL, rxTR, rxBR, rxBL } = norm;
|
|
3977
|
+
const canonicalShape = normalizeShapeType(shapeType);
|
|
3978
|
+
if (canonicalShape === "rounded-rect") {
|
|
3979
|
+
drawRect(pdf, x, y, w, h, fill, stroke, strokeWidth, borderRadius, rxTL, rxTR, rxBR, rxBL);
|
|
3980
|
+
} else if (canonicalShape === "circle") {
|
|
3981
|
+
const diameter = Math.max(1, Math.min(w, h));
|
|
3982
|
+
drawEllipse(pdf, x + diameter / 2, y + diameter / 2, diameter / 2, diameter / 2, fill, stroke, strokeWidth);
|
|
3983
|
+
} else if (canonicalShape === "triangle") {
|
|
3984
|
+
const rTop = Math.max(0, Number(norm.triRTop ?? 0));
|
|
3985
|
+
const rBR = Math.max(0, Number(norm.triRBR ?? 0));
|
|
3986
|
+
const rBL = Math.max(0, Number(norm.triRBL ?? 0));
|
|
3987
|
+
const hasRounding = rTop > 0 || rBR > 0 || rBL > 0;
|
|
3988
|
+
if (hasRounding) {
|
|
3989
|
+
const pathStr = buildRoundedTrianglePath(w, h, rTop, rBR, rBL);
|
|
3990
|
+
drawRoundedTrianglePdf(pdf, x, y, w, h, pathStr, fill, stroke, strokeWidth);
|
|
3991
|
+
} else {
|
|
3992
|
+
const points = getTrianglePoints(x, y, w, h);
|
|
3993
|
+
drawPolygon(pdf, points, fill, stroke, strokeWidth);
|
|
3994
|
+
}
|
|
3995
|
+
} else {
|
|
3996
|
+
const points = getShapePoints(shapeType, x, y, w, h);
|
|
3997
|
+
if (points) {
|
|
3998
|
+
drawPolygon(pdf, points, fill, stroke, strokeWidth);
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
break;
|
|
4002
|
+
}
|
|
4003
|
+
case "line": {
|
|
4004
|
+
const { stroke, strokeWidth, strokeDashArray } = norm;
|
|
4005
|
+
drawLine(pdf, x, y, x + w, y, stroke, strokeWidth || 2, strokeDashArray);
|
|
4006
|
+
break;
|
|
4007
|
+
}
|
|
4008
|
+
case "text": {
|
|
4009
|
+
const { text: originalText, fontSize, fontFamily, fontWeight, fontStyle, underline, letterSpacing, fill, textAlign, lineHeight, splitByGrapheme } = norm;
|
|
4010
|
+
const overflowPolicy = element.overflowPolicy || "grow-and-push";
|
|
4011
|
+
(element.minFontSize || 8) * fontScale;
|
|
4012
|
+
const maxLines = element.maxLines || 3;
|
|
4013
|
+
const effectiveSplitByGrapheme = overflowPolicy === "auto-shrink" ? false : splitByGrapheme;
|
|
4014
|
+
let finalText = originalText;
|
|
4015
|
+
let finalFontSize = fontSize * fontScale;
|
|
4016
|
+
if (overflowPolicy === "auto-shrink") {
|
|
4017
|
+
const explicitLineCount = Math.max(1, finalText.split("\n").length);
|
|
4018
|
+
const measureWidth = Math.max(1, w / PX_TO_PT);
|
|
4019
|
+
while (finalFontSize > 1) {
|
|
4020
|
+
const testTextbox = new fabric.Textbox(finalText, {
|
|
4021
|
+
width: measureWidth,
|
|
4022
|
+
fontSize: finalFontSize / PX_TO_PT,
|
|
4023
|
+
fontFamily,
|
|
4024
|
+
fontWeight,
|
|
4025
|
+
fontStyle,
|
|
4026
|
+
lineHeight: lineHeight || 1.2,
|
|
4027
|
+
charSpacing: letterSpacing || 0,
|
|
4028
|
+
splitByGrapheme: false
|
|
4029
|
+
});
|
|
4030
|
+
testTextbox.initDimensions();
|
|
4031
|
+
const textHeight = (testTextbox.height || 0) * PX_TO_PT;
|
|
4032
|
+
const renderedLineCount = ((_a = testTextbox.textLines) == null ? void 0 : _a.length) || 1;
|
|
4033
|
+
const hasNoImplicitWrap = renderedLineCount <= explicitLineCount;
|
|
4034
|
+
const fitsHeight = h <= 0 || textHeight <= h;
|
|
4035
|
+
const actualTextboxWidth = testTextbox.width ?? measureWidth;
|
|
4036
|
+
const widthDidGrow = actualTextboxWidth > measureWidth + 0.5;
|
|
4037
|
+
const lineWidths = testTextbox.__lineWidths;
|
|
4038
|
+
const maxLineWidth = lineWidths && lineWidths.length > 0 ? Math.max(...lineWidths) : 0;
|
|
4039
|
+
const fitsWidth = !widthDidGrow && maxLineWidth <= measureWidth + 1;
|
|
4040
|
+
if (hasNoImplicitWrap && fitsHeight && fitsWidth) {
|
|
4041
|
+
break;
|
|
4042
|
+
}
|
|
4043
|
+
finalFontSize--;
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
if (overflowPolicy === "max-lines-ellipsis") {
|
|
4047
|
+
const testTextbox = new fabric.Textbox(originalText, {
|
|
4048
|
+
width: w / PX_TO_PT,
|
|
4049
|
+
fontSize: finalFontSize / PX_TO_PT,
|
|
4050
|
+
fontFamily,
|
|
4051
|
+
lineHeight: lineHeight || 1.2,
|
|
4052
|
+
splitByGrapheme: effectiveSplitByGrapheme
|
|
4053
|
+
});
|
|
4054
|
+
testTextbox.initDimensions();
|
|
4055
|
+
const lines = testTextbox.textLines || [];
|
|
4056
|
+
if (lines.length > maxLines) {
|
|
4057
|
+
const countLines = (testText) => {
|
|
4058
|
+
var _a2;
|
|
4059
|
+
const tb = new fabric.Textbox(testText, {
|
|
4060
|
+
width: w / PX_TO_PT,
|
|
4061
|
+
fontSize: finalFontSize / PX_TO_PT,
|
|
4062
|
+
fontFamily,
|
|
4063
|
+
lineHeight: lineHeight || 1.2,
|
|
4064
|
+
splitByGrapheme: effectiveSplitByGrapheme
|
|
4065
|
+
});
|
|
4066
|
+
tb.initDimensions();
|
|
4067
|
+
return ((_a2 = tb.textLines) == null ? void 0 : _a2.length) || 1;
|
|
4068
|
+
};
|
|
4069
|
+
let low = 0;
|
|
4070
|
+
let high = originalText.length;
|
|
4071
|
+
let result = originalText;
|
|
4072
|
+
while (low < high) {
|
|
4073
|
+
const mid = Math.floor((low + high + 1) / 2);
|
|
4074
|
+
const testText = originalText.slice(0, mid) + "...";
|
|
4075
|
+
const lineCount = countLines(testText);
|
|
4076
|
+
if (lineCount <= maxLines) {
|
|
4077
|
+
low = mid;
|
|
4078
|
+
result = testText;
|
|
4079
|
+
} else {
|
|
4080
|
+
high = mid - 1;
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
if (result.endsWith("...") && result.length > 3) {
|
|
4084
|
+
const beforeEllipsis = result.slice(0, -3).trimEnd();
|
|
4085
|
+
result = beforeEllipsis + "...";
|
|
4086
|
+
}
|
|
4087
|
+
finalText = result;
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
drawText(pdf, finalText, x, y, w, finalFontSize, fontFamily, fontWeight, fontStyle, underline, letterSpacing, fill, textAlign, lineHeight, embeddedFonts, effectiveSplitByGrapheme, liveFabricText);
|
|
4091
|
+
break;
|
|
4092
|
+
}
|
|
4093
|
+
case "image": {
|
|
4094
|
+
const { imageUrl } = norm;
|
|
4095
|
+
if (imageUrl) {
|
|
4096
|
+
const isSvg = element.sourceFormat === "svg" || imageUrl.toLowerCase().includes(".svg");
|
|
4097
|
+
const hasStoredCrop = Boolean(
|
|
4098
|
+
element.useCropHandles || element.cropZoom != null || element.cropPanX != null || element.cropPanY != null
|
|
4099
|
+
);
|
|
4100
|
+
if (hasStoredCrop && isSvg && !elementHasFade(element)) {
|
|
4101
|
+
const svgElement = await fetchSvgAsElement(imageUrl, element.svgColorMap);
|
|
4102
|
+
if (svgElement) {
|
|
4103
|
+
expandSvgUseElements(svgElement, element.id);
|
|
4104
|
+
const svgToDraw = normalizeSvgExplicitColors(svgElement);
|
|
4105
|
+
inlineComputedStyles(svgToDraw);
|
|
4106
|
+
normalizeSvgGradientStopOffsets(svgToDraw);
|
|
4107
|
+
expandSvgGradientHrefs(svgToDraw);
|
|
4108
|
+
resolveSvgGradientRefsToSolid(svgToDraw, element.id);
|
|
4109
|
+
prefixSvgIds(svgToDraw, element.id);
|
|
4110
|
+
bakeGroupOpacityIntoChildren(svgToDraw);
|
|
4111
|
+
injectOpacityIntoSvg(svgToDraw, norm.opacity);
|
|
4112
|
+
const cropZoom = Math.max(1, Number(element.cropZoom ?? 1));
|
|
4113
|
+
const cropPanX = Math.max(0, Math.min(1, Number(element.cropPanX ?? 0.5)));
|
|
4114
|
+
const cropPanY = Math.max(0, Math.min(1, Number(element.cropPanY ?? 0.5)));
|
|
4115
|
+
const naturalW = Math.max(1, Number(element.imageNaturalWidth ?? 0) || w);
|
|
4116
|
+
const naturalH = Math.max(1, Number(element.imageNaturalHeight ?? 0) || h);
|
|
4117
|
+
const fitContain = (element.imageFit ?? "cover") === "contain";
|
|
4118
|
+
const baseScale = fitContain ? Math.min(w / naturalW, h / naturalH) : Math.max(w / naturalW, h / naturalH);
|
|
4119
|
+
const finalScale = baseScale * (fitContain ? 1 : cropZoom);
|
|
4120
|
+
const drawW = naturalW * finalScale;
|
|
4121
|
+
const drawH = naturalH * finalScale;
|
|
4122
|
+
const overflowX = Math.max(0, drawW - w);
|
|
4123
|
+
const overflowY = Math.max(0, drawH - h);
|
|
4124
|
+
const offsetX = overflowX > 0 ? -overflowX * (cropPanX - 0.5) : 0;
|
|
4125
|
+
const offsetY = overflowY > 0 ? -overflowY * (cropPanY - 0.5) : 0;
|
|
4126
|
+
const svgX = x + w / 2 + offsetX - drawW / 2;
|
|
4127
|
+
const svgY = y + h / 2 + offsetY - drawH / 2;
|
|
4128
|
+
try {
|
|
4129
|
+
const seq6 = ++debugSvgDrawSequence;
|
|
4130
|
+
debugLog(`svg2pdf START #${seq6} elementId=${element.id} path=drawElement-svg-crop`);
|
|
4131
|
+
pdf.saveGraphicsState();
|
|
4132
|
+
pdf.setLineWidth(0);
|
|
4133
|
+
pdf.rect(x, y, w, h, null);
|
|
4134
|
+
pdf.clip();
|
|
4135
|
+
if (typeof pdf.discardPath === "function") pdf.discardPath();
|
|
4136
|
+
resetPdfColorState(pdf, `before svg2pdf #${seq6} ${element.id}`);
|
|
4137
|
+
setPdfColorFromSvg(pdf, svgToDraw, element.id);
|
|
4138
|
+
pdf.setGState(pdf.GState({ opacity: 1, "stroke-opacity": 1 }));
|
|
4139
|
+
await svg2pdfWithDomMount(svgToDraw, pdfWithColorLogging(pdf, seq6, element.id), svg2pdfOpts(svgX, svgY, drawW, drawH));
|
|
4140
|
+
debugLog(`svg2pdf DONE #${seq6} elementId=${element.id}`);
|
|
4141
|
+
resetPdfColorState(pdf, `after svg2pdf #${seq6} ${element.id}`);
|
|
4142
|
+
pdf.restoreGraphicsState();
|
|
4143
|
+
resetPdfColorState(pdf, `after restore #${seq6} ${element.id}`);
|
|
4144
|
+
break;
|
|
4145
|
+
} catch {
|
|
4146
|
+
pdf.restoreGraphicsState();
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
if (hasStoredCrop && !isSvg) {
|
|
4151
|
+
const croppedPng = await rasterizeStoredCropImageToPng({
|
|
4152
|
+
imageUrl,
|
|
4153
|
+
frameW: w,
|
|
4154
|
+
frameH: h,
|
|
4155
|
+
clipShape: element.clipShape,
|
|
4156
|
+
borderRadius: Number(element.rx ?? element.borderRadius ?? norm.borderRadius ?? 0) || 0,
|
|
4157
|
+
panX: Number(element.cropPanX ?? 0.5),
|
|
4158
|
+
panY: Number(element.cropPanY ?? 0.5),
|
|
4159
|
+
zoom: Number(element.cropZoom ?? 1),
|
|
4160
|
+
naturalWidth: Number(element.imageNaturalWidth ?? 0),
|
|
4161
|
+
naturalHeight: Number(element.imageNaturalHeight ?? 0),
|
|
4162
|
+
maintainResolution: element.maintainResolution !== false,
|
|
4163
|
+
backgroundColor
|
|
4164
|
+
});
|
|
4165
|
+
if (croppedPng) {
|
|
4166
|
+
try {
|
|
4167
|
+
const fadedCrop = await bakeEdgeFadeIntoDataUrl(croppedPng, element);
|
|
4168
|
+
const finalCropPng = fadedCrop || croppedPng;
|
|
4169
|
+
pdf.addImage(finalCropPng, "PNG", x, y, w, h, void 0, "SLOW");
|
|
4170
|
+
break;
|
|
4171
|
+
} catch {
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
}
|
|
4175
|
+
const canVectorizeSvg = isSvg && !elementHasFade(element);
|
|
4176
|
+
if (canVectorizeSvg) {
|
|
4177
|
+
const svgElement = await fetchSvgAsElement(imageUrl, element.svgColorMap);
|
|
4178
|
+
if (svgElement) {
|
|
4179
|
+
expandSvgUseElements(svgElement, element.id);
|
|
4180
|
+
const svgToDraw = normalizeSvgExplicitColors(svgElement);
|
|
4181
|
+
inlineComputedStyles(svgToDraw);
|
|
4182
|
+
normalizeSvgGradientStopOffsets(svgToDraw);
|
|
4183
|
+
expandSvgGradientHrefs(svgToDraw);
|
|
4184
|
+
resolveSvgGradientRefsToSolid(svgToDraw, element.id);
|
|
4185
|
+
prefixSvgIds(svgToDraw, element.id);
|
|
4186
|
+
bakeGroupOpacityIntoChildren(svgToDraw);
|
|
4187
|
+
injectOpacityIntoSvg(svgToDraw, norm.opacity);
|
|
4188
|
+
try {
|
|
4189
|
+
const seq5 = ++debugSvgDrawSequence;
|
|
4190
|
+
debugLog(`svg2pdf START #${seq5} elementId=${element.id} path=drawElement-image`);
|
|
4191
|
+
pdf.saveGraphicsState();
|
|
4192
|
+
resetPdfColorState(pdf, `before svg2pdf #${seq5} ${element.id}`);
|
|
4193
|
+
resetPdfColorState(pdf, `immediately before svg2pdf #${seq5} ${element.id}`);
|
|
4194
|
+
setPdfColorFromSvg(pdf, svgToDraw, element.id);
|
|
4195
|
+
await svg2pdfWithDomMount(svgToDraw, pdfWithColorLogging(pdf, seq5, element.id), svg2pdfOpts(x, y, w, h));
|
|
4196
|
+
debugLog(`svg2pdf DONE #${seq5} elementId=${element.id}`);
|
|
4197
|
+
resetPdfColorState(pdf, `after svg2pdf #${seq5} ${element.id}`);
|
|
4198
|
+
pdf.restoreGraphicsState();
|
|
4199
|
+
resetPdfColorState(pdf, `after restore #${seq5} ${element.id}`);
|
|
4200
|
+
} catch {
|
|
4201
|
+
const rasterUrl = await getRecoloredSvgDataUrl(imageUrl, element.svgColorMap) || imageUrl;
|
|
4202
|
+
const imageData2 = await fetchImageAsBase64(rasterUrl, {
|
|
4203
|
+
backgroundColor,
|
|
4204
|
+
targetWidthPt: w,
|
|
4205
|
+
targetHeightPt: h,
|
|
4206
|
+
preserveAlpha: true,
|
|
4207
|
+
resolutionMultiplier: 4
|
|
4208
|
+
});
|
|
4209
|
+
if (imageData2) {
|
|
4210
|
+
const fadedFallback = await bakeEdgeFadeIntoDataUrl(imageData2.data, element);
|
|
4211
|
+
if (fadedFallback) {
|
|
4212
|
+
imageData2.data = fadedFallback;
|
|
4213
|
+
imageData2.format = "PNG";
|
|
4214
|
+
}
|
|
4215
|
+
try {
|
|
4216
|
+
pdf.addImage(imageData2.data, imageData2.format, x, y, w, h, void 0, "SLOW");
|
|
4217
|
+
} catch {
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
break;
|
|
4222
|
+
}
|
|
4223
|
+
}
|
|
4224
|
+
const rasterUrlFinal = isSvg ? await getRecoloredSvgDataUrl(imageUrl, element.svgColorMap) || imageUrl : imageUrl;
|
|
4225
|
+
const preserveAlpha = isSvg || w > canvasWidth * 0.7 && h > canvasHeight * 0.7;
|
|
4226
|
+
const imageData = await fetchImageAsBase64(rasterUrlFinal, {
|
|
4227
|
+
backgroundColor,
|
|
4228
|
+
targetWidthPt: w,
|
|
4229
|
+
targetHeightPt: h,
|
|
4230
|
+
preserveAlpha: preserveAlpha || elementHasFade(element),
|
|
4231
|
+
resolutionMultiplier: isSvg ? 4 : void 0
|
|
4232
|
+
});
|
|
4233
|
+
if (imageData) {
|
|
4234
|
+
const faded = await bakeEdgeFadeIntoDataUrl(imageData.data, element);
|
|
4235
|
+
if (faded) {
|
|
4236
|
+
imageData.data = faded;
|
|
4237
|
+
imageData.format = "PNG";
|
|
4238
|
+
}
|
|
4239
|
+
try {
|
|
4240
|
+
pdf.addImage(imageData.data, imageData.format, x, y, w, h, void 0, "SLOW");
|
|
4241
|
+
} catch {
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
}
|
|
4245
|
+
break;
|
|
4246
|
+
}
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
async function exportFabricCanvasToVectorPdf(_fabricCanvas, options) {
|
|
4250
|
+
const { filename = "document.pdf", width, height, title, elements, backgroundColor } = options;
|
|
4251
|
+
const orientation = width > height ? "landscape" : "portrait";
|
|
4252
|
+
const pdf = new jsPDF({
|
|
4253
|
+
orientation,
|
|
4254
|
+
unit: "px",
|
|
4255
|
+
format: [width, height],
|
|
4256
|
+
hotfixes: ["px_scaling"],
|
|
4257
|
+
compress: true
|
|
4258
|
+
});
|
|
4259
|
+
if (title) {
|
|
4260
|
+
pdf.setProperties({ title, creator: "DocuForge" });
|
|
4261
|
+
}
|
|
4262
|
+
if (backgroundColor && backgroundColor !== "transparent") {
|
|
4263
|
+
const bgColor = parseColor(backgroundColor);
|
|
4264
|
+
if (bgColor) {
|
|
4265
|
+
pdf.setFillColor(bgColor.r, bgColor.g, bgColor.b);
|
|
4266
|
+
pdf.rect(0, 0, width, height, "F");
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
const fontKeysNeeded = /* @__PURE__ */ new Set();
|
|
4270
|
+
const italicKeysNeeded = /* @__PURE__ */ new Set();
|
|
4271
|
+
elements.forEach((el) => {
|
|
4272
|
+
var _a;
|
|
4273
|
+
if (el.type === "text") {
|
|
4274
|
+
const norm = normalizeElement(el);
|
|
4275
|
+
const { fontFamily, fontWeight } = norm;
|
|
4276
|
+
if (fontFamily) {
|
|
4277
|
+
const resolved = resolveFontWeight(fontWeight ?? 400);
|
|
4278
|
+
fontKeysNeeded.add(`${fontFamily}${FONT_KEY_SEP}${resolved}`);
|
|
4279
|
+
const fontStyleRaw = el.fontStyle || ((_a = el.style) == null ? void 0 : _a.fontStyle) || "normal";
|
|
4280
|
+
if (fontStyleRaw === "italic") {
|
|
4281
|
+
italicKeysNeeded.add(`${fontFamily}${FONT_KEY_SEP}${resolved}${FONT_KEY_SEP}italic`);
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
});
|
|
4286
|
+
fontKeysNeeded.add(`${FONT_FALLBACK_SYMBOLS}${FONT_KEY_SEP}400`);
|
|
4287
|
+
fontKeysNeeded.add(`${FONT_FALLBACK_MATH}${FONT_KEY_SEP}400`);
|
|
4288
|
+
for (const w of [300, 400, 500, 600, 700]) {
|
|
4289
|
+
fontKeysNeeded.add(`${FONT_FALLBACK_DEVANAGARI}${FONT_KEY_SEP}${w}`);
|
|
4290
|
+
}
|
|
4291
|
+
const embeddedFonts = /* @__PURE__ */ new Set();
|
|
4292
|
+
for (const key of fontKeysNeeded) {
|
|
4293
|
+
const sep = key.indexOf(FONT_KEY_SEP);
|
|
4294
|
+
const fontName = key.slice(0, sep);
|
|
4295
|
+
const weight = parseInt(key.slice(sep + 1), 10);
|
|
4296
|
+
const success = await embedFontWithGoogleFallback(pdf, fontName, weight);
|
|
4297
|
+
if (success) embeddedFonts.add(key);
|
|
4298
|
+
}
|
|
4299
|
+
for (const key of italicKeysNeeded) {
|
|
4300
|
+
const parts = key.split(FONT_KEY_SEP);
|
|
4301
|
+
const fontName = parts[0];
|
|
4302
|
+
const weight = parseInt(parts[1], 10);
|
|
4303
|
+
const success = await embedFontWithGoogleFallback(pdf, fontName, weight, true);
|
|
4304
|
+
if (success) embeddedFonts.add(key);
|
|
4305
|
+
}
|
|
4306
|
+
debugLog(`single-page export: drawing ${elements.length} elements (order = first=back, last=front)`);
|
|
4307
|
+
elements.forEach((el, i) => debugLog(` [${i}] id=${el.id} type=${el.type}`));
|
|
4308
|
+
for (const element of elements) {
|
|
4309
|
+
await drawElement(pdf, element, embeddedFonts, width, height, backgroundColor);
|
|
4310
|
+
}
|
|
4311
|
+
pdf.save(filename);
|
|
4312
|
+
}
|
|
4313
|
+
function isGroupBackgroundDrawable(item) {
|
|
4314
|
+
return item.type === "groupBackground";
|
|
4315
|
+
}
|
|
4316
|
+
function buildPageDrawList(children, pageTree, visibilityFilter) {
|
|
4317
|
+
const out = [];
|
|
4318
|
+
function visit(nodes, reverseGroupChildren) {
|
|
4319
|
+
const list = reverseGroupChildren ? [...nodes].reverse() : nodes;
|
|
4320
|
+
for (const n of list) {
|
|
4321
|
+
if (isElement(n)) {
|
|
4322
|
+
if (visibilityFilter === "strict" ? n.visible !== false : n.visible) out.push(n);
|
|
4323
|
+
} else if (isGroup(n)) {
|
|
4324
|
+
const g = n;
|
|
4325
|
+
if (g.backgroundColor && g.backgroundColor !== "transparent") {
|
|
4326
|
+
const abs = getAbsoluteBounds(g, pageTree);
|
|
4327
|
+
out.push({
|
|
4328
|
+
type: "groupBackground",
|
|
4329
|
+
id: g.id,
|
|
4330
|
+
left: abs.left,
|
|
4331
|
+
top: abs.top,
|
|
4332
|
+
width: Math.max(1, abs.width),
|
|
4333
|
+
height: Math.max(1, abs.height),
|
|
4334
|
+
fill: g.backgroundColor
|
|
4335
|
+
});
|
|
4336
|
+
}
|
|
4337
|
+
visit(g.children ?? [], true);
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
}
|
|
4341
|
+
visit(children, false);
|
|
4342
|
+
return out;
|
|
4343
|
+
}
|
|
4344
|
+
function preparePagesForExport(pages, canvasWidth, canvasHeight, options) {
|
|
4345
|
+
const { sortByTop = false, visibilityFilter = "strict" } = options || {};
|
|
4346
|
+
return pages.map((page) => {
|
|
4347
|
+
var _a, _b;
|
|
4348
|
+
const pageChildren = page.children || [];
|
|
4349
|
+
const drawList = buildPageDrawList(pageChildren, pageChildren, visibilityFilter);
|
|
4350
|
+
const elements = sortByTop && drawList.every((item) => !isGroupBackgroundDrawable(item)) ? [...drawList].sort((a, b) => {
|
|
4351
|
+
var _a2, _b2;
|
|
4352
|
+
const aTop = "top" in a ? a.top ?? ((_a2 = a.position) == null ? void 0 : _a2.y) ?? 0 : 0;
|
|
4353
|
+
const bTop = "top" in b ? b.top ?? ((_b2 = b.position) == null ? void 0 : _b2.y) ?? 0 : 0;
|
|
4354
|
+
return aTop - bTop;
|
|
4355
|
+
}) : drawList;
|
|
4356
|
+
return {
|
|
4357
|
+
pageId: page.id,
|
|
4358
|
+
elements,
|
|
4359
|
+
pageChildren,
|
|
4360
|
+
width: canvasWidth,
|
|
4361
|
+
height: canvasHeight,
|
|
4362
|
+
backgroundColor: ((_a = page.settings) == null ? void 0 : _a.backgroundColor) || "#ffffff",
|
|
4363
|
+
backgroundGradient: (_b = page.settings) == null ? void 0 : _b.backgroundGradient
|
|
4364
|
+
};
|
|
4365
|
+
});
|
|
4366
|
+
}
|
|
4367
|
+
async function exportMultiPagePdf(pages, options) {
|
|
4368
|
+
var _a, _b, _c, _d, _e;
|
|
4369
|
+
const { filename = "document.pdf", title, watermark = false, returnBlob = false } = options;
|
|
4370
|
+
if (pages.length === 0) {
|
|
4371
|
+
throw new Error("No pages to export");
|
|
4372
|
+
}
|
|
4373
|
+
const firstPage = pages[0];
|
|
4374
|
+
const orientation = firstPage.width > firstPage.height ? "landscape" : "portrait";
|
|
4375
|
+
const pdf = new jsPDF({
|
|
4376
|
+
orientation,
|
|
4377
|
+
unit: "px",
|
|
4378
|
+
format: [firstPage.width, firstPage.height],
|
|
4379
|
+
hotfixes: ["px_scaling"],
|
|
4380
|
+
compress: true
|
|
4381
|
+
});
|
|
4382
|
+
if (title) {
|
|
4383
|
+
pdf.setProperties({ title, creator: "DocuForge" });
|
|
4384
|
+
}
|
|
4385
|
+
const fontKeysNeeded = /* @__PURE__ */ new Set();
|
|
4386
|
+
const italicKeysNeeded = /* @__PURE__ */ new Set();
|
|
4387
|
+
pages.forEach((page) => {
|
|
4388
|
+
page.elements.forEach((item) => {
|
|
4389
|
+
var _a2;
|
|
4390
|
+
if (!isGroupBackgroundDrawable(item) && item.type === "text") {
|
|
4391
|
+
const norm = normalizeElement(item);
|
|
4392
|
+
const { fontFamily, fontWeight } = norm;
|
|
4393
|
+
if (fontFamily) {
|
|
4394
|
+
const resolved = resolveFontWeight(fontWeight ?? 400);
|
|
4395
|
+
fontKeysNeeded.add(`${fontFamily}${FONT_KEY_SEP}${resolved}`);
|
|
4396
|
+
const fontStyleRaw = item.fontStyle || ((_a2 = item.style) == null ? void 0 : _a2.fontStyle) || "normal";
|
|
4397
|
+
if (fontStyleRaw === "italic") {
|
|
4398
|
+
italicKeysNeeded.add(`${fontFamily}${FONT_KEY_SEP}${resolved}${FONT_KEY_SEP}italic`);
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
4402
|
+
});
|
|
4403
|
+
});
|
|
4404
|
+
fontKeysNeeded.add(`${FONT_FALLBACK_SYMBOLS}${FONT_KEY_SEP}400`);
|
|
4405
|
+
fontKeysNeeded.add(`${FONT_FALLBACK_MATH}${FONT_KEY_SEP}400`);
|
|
4406
|
+
for (const w of [300, 400, 500, 600, 700]) {
|
|
4407
|
+
fontKeysNeeded.add(`${FONT_FALLBACK_DEVANAGARI}${FONT_KEY_SEP}${w}`);
|
|
4408
|
+
}
|
|
4409
|
+
const embeddedFonts = /* @__PURE__ */ new Set();
|
|
4410
|
+
for (const key of fontKeysNeeded) {
|
|
4411
|
+
const sep = key.indexOf(FONT_KEY_SEP);
|
|
4412
|
+
const fontName = key.slice(0, sep);
|
|
4413
|
+
const weight = parseInt(key.slice(sep + 1), 10);
|
|
4414
|
+
const success = await embedFontWithGoogleFallback(pdf, fontName, weight);
|
|
4415
|
+
if (success) embeddedFonts.add(key);
|
|
4416
|
+
await yieldToUI();
|
|
4417
|
+
}
|
|
4418
|
+
for (const key of italicKeysNeeded) {
|
|
4419
|
+
const parts = key.split(FONT_KEY_SEP);
|
|
4420
|
+
const fontName = parts[0];
|
|
4421
|
+
const weight = parseInt(parts[1], 10);
|
|
4422
|
+
const success = await embedFontWithGoogleFallback(pdf, fontName, weight, true);
|
|
4423
|
+
if (success) embeddedFonts.add(key);
|
|
4424
|
+
await yieldToUI();
|
|
4425
|
+
}
|
|
4426
|
+
await yieldToUI();
|
|
4427
|
+
for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) {
|
|
4428
|
+
const page = pages[pageIndex];
|
|
4429
|
+
if (pageIndex > 0) {
|
|
4430
|
+
const pageOrientation = page.width > page.height ? "landscape" : "portrait";
|
|
4431
|
+
pdf.addPage([page.width, page.height], pageOrientation);
|
|
4432
|
+
}
|
|
4433
|
+
if (page.backgroundGradient && ((_a = page.backgroundGradient.stops) == null ? void 0 : _a.length) >= 2) {
|
|
4434
|
+
const grad = page.backgroundGradient;
|
|
4435
|
+
const colorStops = grad.stops.map((s) => {
|
|
4436
|
+
const c = parseColor(s.color);
|
|
4437
|
+
return {
|
|
4438
|
+
offset: Math.max(0, Math.min(1, Number(s.offset))),
|
|
4439
|
+
color: c ? [c.r, c.g, c.b] : [0, 0, 0]
|
|
4440
|
+
};
|
|
4441
|
+
}).filter((s) => Number.isFinite(s.offset)).sort((a, b) => a.offset - b.offset);
|
|
4442
|
+
const normalizedStops = [...colorStops];
|
|
4443
|
+
if (normalizedStops.length > 0) {
|
|
4444
|
+
if (normalizedStops[0].offset > 0) {
|
|
4445
|
+
normalizedStops.unshift({ offset: 0, color: normalizedStops[0].color });
|
|
4446
|
+
}
|
|
4447
|
+
if (normalizedStops[normalizedStops.length - 1].offset < 1) {
|
|
4448
|
+
const last = normalizedStops[normalizedStops.length - 1];
|
|
4449
|
+
normalizedStops.push({ offset: 1, color: last.color });
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4452
|
+
const shadingColors = normalizedStops.map((s) => ({ offset: s.offset, color: s.color }));
|
|
4453
|
+
const patternKey = `bg-grad-${pageIndex}`;
|
|
4454
|
+
try {
|
|
4455
|
+
pdf.advancedAPI((doc) => {
|
|
4456
|
+
const isLinear = grad.type === "linear";
|
|
4457
|
+
const isConic = grad.type === "conic";
|
|
4458
|
+
if (isLinear || isConic) {
|
|
4459
|
+
const angleDeg = grad.angle ?? (isConic ? 0 : 90);
|
|
4460
|
+
const angleRad = angleDeg * Math.PI / 180;
|
|
4461
|
+
const sinA = Math.sin(angleRad);
|
|
4462
|
+
const cosA = Math.cos(angleRad);
|
|
4463
|
+
const corners = [[0, 0], [page.width, 0], [page.width, page.height], [0, page.height]];
|
|
4464
|
+
const projs = corners.map(([x, y]) => x * sinA - y * cosA);
|
|
4465
|
+
const minP = Math.min(...projs);
|
|
4466
|
+
const maxP = Math.max(...projs);
|
|
4467
|
+
const gradLen = maxP - minP;
|
|
4468
|
+
const midX = page.width / 2;
|
|
4469
|
+
const midY = page.height / 2;
|
|
4470
|
+
const halfLen = gradLen / 2;
|
|
4471
|
+
const x1 = midX - sinA * halfLen;
|
|
4472
|
+
const y1 = midY + cosA * halfLen;
|
|
4473
|
+
const x2 = midX + sinA * halfLen;
|
|
4474
|
+
const y2 = midY - cosA * halfLen;
|
|
4475
|
+
let finalStops = shadingColors;
|
|
4476
|
+
if (isConic && shadingColors.length >= 2) {
|
|
4477
|
+
const reversed = [...shadingColors].reverse().map((s, idx, arr) => ({
|
|
4478
|
+
offset: 0.5 + (1 - s.offset) * 0.5,
|
|
4479
|
+
color: s.color
|
|
4480
|
+
}));
|
|
4481
|
+
const firstHalf = shadingColors.map((s) => ({
|
|
4482
|
+
offset: s.offset * 0.5,
|
|
4483
|
+
color: s.color
|
|
4484
|
+
}));
|
|
4485
|
+
finalStops = [...firstHalf, ...reversed.slice(1)];
|
|
4486
|
+
}
|
|
4487
|
+
doc.addShadingPattern(
|
|
4488
|
+
patternKey,
|
|
4489
|
+
new ShadingPattern("axial", [x1, y1, x2, y2], finalStops)
|
|
4490
|
+
);
|
|
4491
|
+
} else {
|
|
4492
|
+
const cx = (grad.cx ?? 0.5) * page.width;
|
|
4493
|
+
const cy = (grad.cy ?? 0.5) * page.height;
|
|
4494
|
+
const maxR = (grad.r ?? 0.5) * Math.max(page.width, page.height);
|
|
4495
|
+
doc.addShadingPattern(
|
|
4496
|
+
patternKey,
|
|
4497
|
+
new ShadingPattern("radial", [cx, cy, 0, cx, cy, maxR], shadingColors)
|
|
4498
|
+
);
|
|
4499
|
+
}
|
|
4500
|
+
doc.rect(0, 0, page.width, page.height);
|
|
4501
|
+
doc.fill({ key: patternKey, matrix: doc.Matrix(1, 0, 0, 1, 0, 0) });
|
|
4502
|
+
});
|
|
4503
|
+
} catch (e) {
|
|
4504
|
+
const fallback = ((_b = colorStops[0]) == null ? void 0 : _b.color) || [255, 255, 255];
|
|
4505
|
+
pdf.setFillColor(fallback[0], fallback[1], fallback[2]);
|
|
4506
|
+
pdf.rect(0, 0, page.width, page.height, "F");
|
|
4507
|
+
}
|
|
4508
|
+
} else {
|
|
4509
|
+
const bgColor = parseColor(page.backgroundColor && page.backgroundColor !== "transparent" ? page.backgroundColor : "#ffffff");
|
|
4510
|
+
debugLog(`page ${pageIndex + 1} background: ${page.backgroundColor ?? "default"} → rgb(${(bgColor == null ? void 0 : bgColor.r) ?? 0},${(bgColor == null ? void 0 : bgColor.g) ?? 0},${(bgColor == null ? void 0 : bgColor.b) ?? 0})`);
|
|
4511
|
+
if (bgColor) {
|
|
4512
|
+
pdf.setFillColor(bgColor.r, bgColor.g, bgColor.b);
|
|
4513
|
+
pdf.rect(0, 0, page.width, page.height, "F");
|
|
4514
|
+
}
|
|
4515
|
+
}
|
|
4516
|
+
const liveCanvasForPage = page.pageId ? getCanvasForPage(page.pageId) : null;
|
|
4517
|
+
let allowLiveCanvasFallback = !!liveCanvasForPage;
|
|
4518
|
+
if (liveCanvasForPage) {
|
|
4519
|
+
let previousSvgViewportTransformation;
|
|
4520
|
+
let previousViewportTransform;
|
|
4521
|
+
try {
|
|
4522
|
+
previousSvgViewportTransformation = liveCanvasForPage.svgViewportTransformation;
|
|
4523
|
+
previousViewportTransform = liveCanvasForPage.viewportTransform ? [...liveCanvasForPage.viewportTransform] : void 0;
|
|
4524
|
+
liveCanvasForPage.viewportTransform = [1, 0, 0, 1, 0, 0];
|
|
4525
|
+
liveCanvasForPage.svgViewportTransformation = true;
|
|
4526
|
+
const liveSvgString = liveCanvasForPage.toSVG();
|
|
4527
|
+
const liveSvgWidth = page.width;
|
|
4528
|
+
const liveSvgHeight = page.height;
|
|
4529
|
+
const liveSvg = await prepareLiveCanvasSvgForPdf(
|
|
4530
|
+
liveSvgString,
|
|
4531
|
+
liveSvgWidth,
|
|
4532
|
+
liveSvgHeight,
|
|
4533
|
+
`page-${pageIndex + 1}`,
|
|
4534
|
+
{ stripPageBackground: !!page.backgroundGradient }
|
|
4535
|
+
);
|
|
4536
|
+
if (liveSvg) {
|
|
4537
|
+
const seq = ++debugSvgDrawSequence;
|
|
4538
|
+
console.log(`[client-pdf-export] live SVG path active for page ${pageIndex + 1}`);
|
|
4539
|
+
debugLog(`live canvas svg2pdf START #${seq} page=${pageIndex + 1}`);
|
|
4540
|
+
resetPdfColorState(pdf, `before live canvas svg2pdf page ${pageIndex + 1}`);
|
|
4541
|
+
setPdfColorFromSvg(pdf, liveSvg, `page-${pageIndex + 1}`);
|
|
4542
|
+
await svg2pdfWithDomMount(
|
|
4543
|
+
liveSvg,
|
|
4544
|
+
pdfWithColorLogging(pdf, seq, `page-${pageIndex + 1}`),
|
|
4545
|
+
svg2pdfOpts(0, 0, page.width, page.height)
|
|
4546
|
+
);
|
|
4547
|
+
debugLog(`live canvas svg2pdf DONE #${seq} page=${pageIndex + 1}`);
|
|
4548
|
+
resetPdfColorState(pdf, `after live canvas svg2pdf page ${pageIndex + 1}`);
|
|
4549
|
+
await yieldToUI();
|
|
4550
|
+
continue;
|
|
4551
|
+
}
|
|
4552
|
+
allowLiveCanvasFallback = false;
|
|
4553
|
+
console.error(`[client-pdf-export] live SVG prep returned null for page ${pageIndex + 1}`, {
|
|
4554
|
+
pageId: page.pageId,
|
|
4555
|
+
hasLiveCanvas: !!liveCanvasForPage,
|
|
4556
|
+
svgLength: (liveSvgString == null ? void 0 : liveSvgString.length) ?? 0
|
|
4557
|
+
});
|
|
4558
|
+
} catch (error) {
|
|
4559
|
+
allowLiveCanvasFallback = false;
|
|
4560
|
+
console.error(`[client-pdf-export] live SVG export failed for page ${pageIndex + 1}`, error);
|
|
4561
|
+
} finally {
|
|
4562
|
+
liveCanvasForPage.svgViewportTransformation = previousSvgViewportTransformation;
|
|
4563
|
+
if (previousViewportTransform) {
|
|
4564
|
+
liveCanvasForPage.viewportTransform = previousViewportTransform;
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4568
|
+
const matrixByElementId = /* @__PURE__ */ new Map();
|
|
4569
|
+
const intrinsicByElementId = /* @__PURE__ */ new Map();
|
|
4570
|
+
const getIntrinsic = (o) => {
|
|
4571
|
+
if (o instanceof fabric.Circle) {
|
|
4572
|
+
const r = o.radius ?? 0;
|
|
4573
|
+
return { w: r * 2, h: r * 2 };
|
|
4574
|
+
}
|
|
4575
|
+
if (o instanceof fabric.Ellipse) {
|
|
4576
|
+
return { w: (o.rx ?? 0) * 2, h: (o.ry ?? 0) * 2 };
|
|
4577
|
+
}
|
|
4578
|
+
return { w: o.width ?? 0, h: o.height ?? 0 };
|
|
4579
|
+
};
|
|
4580
|
+
const liveTextByElementId = /* @__PURE__ */ new Map();
|
|
4581
|
+
if (page.pageId && allowLiveCanvasFallback) {
|
|
4582
|
+
const liveCanvas = getCanvasForPage(page.pageId);
|
|
4583
|
+
if (liveCanvas) {
|
|
4584
|
+
const objects = liveCanvas.getObjects();
|
|
4585
|
+
const isTextLike = (o) => {
|
|
4586
|
+
var _a2, _b2;
|
|
4587
|
+
return o instanceof fabric.Textbox || o instanceof fabric.IText || ((_a2 = o.constructor) == null ? void 0 : _a2.name) === "Textbox" || ((_b2 = o.constructor) == null ? void 0 : _b2.name) === "Text";
|
|
4588
|
+
};
|
|
4589
|
+
for (const obj of objects) {
|
|
4590
|
+
const isCropGroup = obj instanceof fabric.Group && obj.__cropGroup;
|
|
4591
|
+
if (isCropGroup) {
|
|
4592
|
+
const id2 = obj.__docuforgeId;
|
|
4593
|
+
if (!id2 || id2 === "__background__") continue;
|
|
4594
|
+
obj.__exportAsRenderedImage = true;
|
|
4595
|
+
intrinsicByElementId.set(id2, getIntrinsic(obj));
|
|
4596
|
+
try {
|
|
4597
|
+
matrixByElementId.set(id2, obj.calcTransformMatrix());
|
|
4598
|
+
} catch {
|
|
4599
|
+
}
|
|
4600
|
+
continue;
|
|
4601
|
+
}
|
|
4602
|
+
const isSectionGroup = obj instanceof fabric.Group && obj.__docuforgeSectionGroup;
|
|
4603
|
+
const groupId = obj.__docuforgeGroupId;
|
|
4604
|
+
if ((groupId || isSectionGroup) && obj instanceof fabric.Group) {
|
|
4605
|
+
const groupMatrix = obj.calcTransformMatrix();
|
|
4606
|
+
const memberObjects = obj.getObjects();
|
|
4607
|
+
for (const memberObj of memberObjects) {
|
|
4608
|
+
const memberId = memberObj.__docuforgeId;
|
|
4609
|
+
if (!memberId) continue;
|
|
4610
|
+
intrinsicByElementId.set(memberId, getIntrinsic(memberObj));
|
|
4611
|
+
if (isTextLike(memberObj)) {
|
|
4612
|
+
liveTextByElementId.set(memberId, memberObj);
|
|
4613
|
+
}
|
|
4614
|
+
try {
|
|
4615
|
+
const memberLocalMatrix = ((_c = memberObj.calcOwnMatrix) == null ? void 0 : _c.call(memberObj)) ?? ((_d = memberObj.calcTransformMatrix) == null ? void 0 : _d.call(memberObj));
|
|
4616
|
+
if (memberLocalMatrix && memberLocalMatrix.length === 6) {
|
|
4617
|
+
const [a1, b1, c1, d1, e1, f1] = groupMatrix;
|
|
4618
|
+
const [a2, b2, c2, d2, e2, f2] = memberLocalMatrix;
|
|
4619
|
+
const absoluteMatrix = [
|
|
4620
|
+
a1 * a2 + c1 * b2,
|
|
4621
|
+
b1 * a2 + d1 * b2,
|
|
4622
|
+
a1 * c2 + c1 * d2,
|
|
4623
|
+
b1 * c2 + d1 * d2,
|
|
4624
|
+
a1 * e2 + c1 * f2 + e1,
|
|
4625
|
+
b1 * e2 + d1 * f2 + f1
|
|
4626
|
+
];
|
|
4627
|
+
matrixByElementId.set(memberId, absoluteMatrix);
|
|
4628
|
+
}
|
|
4629
|
+
} catch {
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4632
|
+
continue;
|
|
4633
|
+
}
|
|
4634
|
+
const id = obj.__docuforgeId;
|
|
4635
|
+
if (!id || id === "__background__") continue;
|
|
4636
|
+
intrinsicByElementId.set(id, getIntrinsic(obj));
|
|
4637
|
+
if (isTextLike(obj)) {
|
|
4638
|
+
liveTextByElementId.set(id, obj);
|
|
4639
|
+
}
|
|
4640
|
+
try {
|
|
4641
|
+
matrixByElementId.set(id, obj.calcTransformMatrix());
|
|
4642
|
+
} catch {
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
}
|
|
4646
|
+
}
|
|
4647
|
+
let drawList = page.elements;
|
|
4648
|
+
if (page.pageId && allowLiveCanvasFallback) {
|
|
4649
|
+
const liveCanvas = getCanvasForPage(page.pageId);
|
|
4650
|
+
const hasGroupBgs = page.elements.some((item) => isGroupBackgroundDrawable(item));
|
|
4651
|
+
if (liveCanvas && !hasGroupBgs) {
|
|
4652
|
+
const canvasOrderIds = liveCanvas.getObjects().map((o) => o.__docuforgeId).filter(Boolean);
|
|
4653
|
+
const byId = new Map(page.elements.map((el) => [el.id, el]));
|
|
4654
|
+
const reordered = [];
|
|
4655
|
+
for (const id of canvasOrderIds) {
|
|
4656
|
+
const el = byId.get(id);
|
|
4657
|
+
if (el) reordered.push(el);
|
|
4658
|
+
}
|
|
4659
|
+
for (const item of page.elements) {
|
|
4660
|
+
if (!reordered.includes(item)) reordered.push(item);
|
|
4661
|
+
}
|
|
4662
|
+
drawList = reordered.length ? reordered : page.elements;
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
const clientTextPositions = [];
|
|
4666
|
+
for (const item of drawList) {
|
|
4667
|
+
if (isGroupBackgroundDrawable(item)) continue;
|
|
4668
|
+
const el = item;
|
|
4669
|
+
if (el.type !== "text") continue;
|
|
4670
|
+
const n = normalizeElement(el);
|
|
4671
|
+
let absX = n.x, absY = n.y, absW = n.w, absH = n.h;
|
|
4672
|
+
if ((_e = page.pageChildren) == null ? void 0 : _e.length) {
|
|
4673
|
+
const node = findNodeById(page.pageChildren, el.id);
|
|
4674
|
+
if (node) {
|
|
4675
|
+
const abs = getAbsoluteBounds(node, page.pageChildren);
|
|
4676
|
+
absX = abs.left;
|
|
4677
|
+
absY = abs.top;
|
|
4678
|
+
absW = abs.width;
|
|
4679
|
+
absH = abs.height;
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
clientTextPositions.push({
|
|
4683
|
+
p: pageIndex + 1,
|
|
4684
|
+
id: el.id.slice(0, 50),
|
|
4685
|
+
top: Math.round(absY * 100) / 100,
|
|
4686
|
+
left: Math.round(absX * 100) / 100,
|
|
4687
|
+
w: Math.round(absW * 100) / 100,
|
|
4688
|
+
h: Math.round(absH * 100) / 100,
|
|
4689
|
+
fs: n.fontSize,
|
|
4690
|
+
lh: n.lineHeight,
|
|
4691
|
+
angle: n.angle,
|
|
4692
|
+
text: (n.text || "").replace(/\s+/g, " ").trim().slice(0, 60)
|
|
4693
|
+
});
|
|
4694
|
+
}
|
|
4695
|
+
if (clientTextPositions.length > 0) {
|
|
4696
|
+
console.log(`[client-pdf-export] page ${pageIndex + 1} textPositions (${clientTextPositions.length}):`, JSON.stringify(clientTextPositions));
|
|
4697
|
+
}
|
|
4698
|
+
debugSvgDrawSequence = 0;
|
|
4699
|
+
debugLog(`page ${pageIndex + 1}: drawing ${drawList.length} items (order = first=back, last=front)`);
|
|
4700
|
+
resetPdfColorState(pdf);
|
|
4701
|
+
for (let drawIndex = 0; drawIndex < drawList.length; drawIndex++) {
|
|
4702
|
+
const item = drawList[drawIndex];
|
|
4703
|
+
if (isGroupBackgroundDrawable(item)) {
|
|
4704
|
+
debugLog(` [${drawIndex}] groupBg id=${item.id}`);
|
|
4705
|
+
const c = parseColor(item.fill);
|
|
4706
|
+
if (c) {
|
|
4707
|
+
pdf.setFillColor(c.r, c.g, c.b);
|
|
4708
|
+
pdf.rect(item.left, item.top, item.width, item.height, "F");
|
|
4709
|
+
}
|
|
4710
|
+
continue;
|
|
4711
|
+
}
|
|
4712
|
+
const element = item;
|
|
4713
|
+
const imgUrl = element.type === "image" ? (element.imageUrl || element.src || "").slice(-40) : "";
|
|
4714
|
+
debugLog(` [${drawIndex}] element id=${element.id} type=${element.type} ${imgUrl ? `imageUrl=...${imgUrl}` : ""}`);
|
|
4715
|
+
const liveMatrix = matrixByElementId.get(element.id);
|
|
4716
|
+
const storedMatrix = element.transformMatrix;
|
|
4717
|
+
const m = liveMatrix ?? storedMatrix ?? null;
|
|
4718
|
+
let intrinsic = intrinsicByElementId.get(element.id) ?? null;
|
|
4719
|
+
const liveText = element.type === "text" ? liveTextByElementId.get(element.id) : void 0;
|
|
4720
|
+
await drawElement(pdf, element, embeddedFonts, page.width, page.height, page.backgroundColor, m, intrinsic, page.pageId, liveText, page.pageChildren);
|
|
4721
|
+
if (drawIndex % 3 === 2) await yieldToUI();
|
|
4722
|
+
}
|
|
4723
|
+
await yieldToUI();
|
|
4724
|
+
}
|
|
4725
|
+
if (watermark) {
|
|
4726
|
+
const { addWatermarkToAllPages } = await import("./pdfWatermark-CewmS9qQ.js");
|
|
4727
|
+
addWatermarkToAllPages(pdf);
|
|
4728
|
+
}
|
|
4729
|
+
if (returnBlob) {
|
|
4730
|
+
const arrayBuffer = pdf.output("arraybuffer");
|
|
4731
|
+
const blob = new Blob([arrayBuffer], { type: "application/pdf" });
|
|
4732
|
+
return {
|
|
4733
|
+
blob,
|
|
4734
|
+
arrayBuffer,
|
|
4735
|
+
totalPages: pages.length,
|
|
4736
|
+
pages: pages.map((p) => ({ width: p.width, height: p.height }))
|
|
4737
|
+
};
|
|
4738
|
+
}
|
|
4739
|
+
pdf.save(filename);
|
|
4740
|
+
}
|
|
4741
|
+
export {
|
|
4742
|
+
convertSvgTextDecorationsToLinesString,
|
|
4743
|
+
drawPreparedLiveCanvasSvgPageToPdf,
|
|
4744
|
+
exportFabricCanvasToVectorPdf,
|
|
4745
|
+
exportMultiPagePdf,
|
|
4746
|
+
preparePagesForExport,
|
|
4747
|
+
rewriteSvgFontsForJsPDFWithSourceMeta
|
|
4748
|
+
};
|
|
4749
|
+
//# sourceMappingURL=vectorPdfExport-BrHHt_7L.js.map
|