@reportwright/viewer 0.0.0-stage → 0.11.0
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/CHANGELOG.md +206 -0
- package/LICENSE +23 -0
- package/README.md +146 -2
- package/dist/chunk-2NAW3MAP.js +1284 -0
- package/dist/chunk-2NAW3MAP.js.map +6 -0
- package/dist/chunk-3D43XGIN.js +1167 -0
- package/dist/chunk-3D43XGIN.js.map +6 -0
- package/dist/chunk-66QZ3RDV.js +1661 -0
- package/dist/chunk-66QZ3RDV.js.map +6 -0
- package/dist/chunk-72S6DETS.js +40 -0
- package/dist/chunk-72S6DETS.js.map +6 -0
- package/dist/chunk-AH47FS6Y.js +13 -0
- package/dist/chunk-AH47FS6Y.js.map +6 -0
- package/dist/chunk-APRPSQOD.js +301 -0
- package/dist/chunk-APRPSQOD.js.map +6 -0
- package/dist/chunk-BXKUE45K.js +1284 -0
- package/dist/chunk-BXKUE45K.js.map +6 -0
- package/dist/chunk-D2NTLAKL.js +14 -0
- package/dist/chunk-D2NTLAKL.js.map +6 -0
- package/dist/chunk-E7INJHTL.js +1284 -0
- package/dist/chunk-E7INJHTL.js.map +6 -0
- package/dist/chunk-FGEFSGJT.js +1284 -0
- package/dist/chunk-FGEFSGJT.js.map +6 -0
- package/dist/chunk-GJS242RR.js +1652 -0
- package/dist/chunk-GJS242RR.js.map +6 -0
- package/dist/chunk-HAMBWF6C.js +20682 -0
- package/dist/chunk-HAMBWF6C.js.map +6 -0
- package/dist/chunk-J56FRCFU.js +1284 -0
- package/dist/chunk-J56FRCFU.js.map +6 -0
- package/dist/chunk-KJTRZJHN.js +599 -0
- package/dist/chunk-KJTRZJHN.js.map +6 -0
- package/dist/chunk-N5WHQ4DM.js +153 -0
- package/dist/chunk-N5WHQ4DM.js.map +6 -0
- package/dist/chunk-NJWQT6VB.js +1284 -0
- package/dist/chunk-NJWQT6VB.js.map +6 -0
- package/dist/chunk-ODREGGKA.js +219 -0
- package/dist/chunk-ODREGGKA.js.map +6 -0
- package/dist/chunk-OLLMACWA.js +35 -0
- package/dist/chunk-OLLMACWA.js.map +6 -0
- package/dist/chunk-PXXMOP4N.js +1284 -0
- package/dist/chunk-PXXMOP4N.js.map +6 -0
- package/dist/chunk-RKGIVPK5.js +1284 -0
- package/dist/chunk-RKGIVPK5.js.map +6 -0
- package/dist/chunk-RYHJUQSV.js +98 -0
- package/dist/chunk-RYHJUQSV.js.map +6 -0
- package/dist/chunk-SBD55ZES.js +316 -0
- package/dist/chunk-SBD55ZES.js.map +6 -0
- package/dist/chunk-SR2TQ2ZK.js +70 -0
- package/dist/chunk-SR2TQ2ZK.js.map +6 -0
- package/dist/chunk-WQOBRZAL.js +14777 -0
- package/dist/chunk-WQOBRZAL.js.map +6 -0
- package/dist/designer.d.ts +48 -0
- package/dist/designer.js +35865 -0
- package/dist/designer.js.map +6 -0
- package/dist/peer/chunk-2NJMXASD.js +1282 -0
- package/dist/peer/chunk-2NJMXASD.js.map +6 -0
- package/dist/peer/chunk-5X4ZSSJT.js +1660 -0
- package/dist/peer/chunk-5X4ZSSJT.js.map +6 -0
- package/dist/peer/chunk-6YO42ALN.js +14777 -0
- package/dist/peer/chunk-6YO42ALN.js.map +6 -0
- package/dist/peer/chunk-76ZABVTI.js +598 -0
- package/dist/peer/chunk-76ZABVTI.js.map +6 -0
- package/dist/peer/chunk-776OR56A.js +6040 -0
- package/dist/peer/chunk-776OR56A.js.map +6 -0
- package/dist/peer/chunk-BPF3SLL4.js +1282 -0
- package/dist/peer/chunk-BPF3SLL4.js.map +6 -0
- package/dist/peer/chunk-BWVDLOVL.js +1282 -0
- package/dist/peer/chunk-BWVDLOVL.js.map +6 -0
- package/dist/peer/chunk-CXM3YSWF.js +1282 -0
- package/dist/peer/chunk-CXM3YSWF.js.map +6 -0
- package/dist/peer/chunk-DQFZ6LVB.js +1282 -0
- package/dist/peer/chunk-DQFZ6LVB.js.map +6 -0
- package/dist/peer/chunk-EKWTFHVE.js +152 -0
- package/dist/peer/chunk-EKWTFHVE.js.map +6 -0
- package/dist/peer/chunk-IMQRHDR4.js +12 -0
- package/dist/peer/chunk-IMQRHDR4.js.map +6 -0
- package/dist/peer/chunk-IPU2YRL2.js +300 -0
- package/dist/peer/chunk-IPU2YRL2.js.map +6 -0
- package/dist/peer/chunk-M4AGFUWE.js +97 -0
- package/dist/peer/chunk-M4AGFUWE.js.map +6 -0
- package/dist/peer/chunk-PG2PNYDX.js +1282 -0
- package/dist/peer/chunk-PG2PNYDX.js.map +6 -0
- package/dist/peer/chunk-QRAJXW2D.js +1282 -0
- package/dist/peer/chunk-QRAJXW2D.js.map +6 -0
- package/dist/peer/chunk-SHVCNAUG.js +1282 -0
- package/dist/peer/chunk-SHVCNAUG.js.map +6 -0
- package/dist/peer/chunk-Y5CAHLXS.js +218 -0
- package/dist/peer/chunk-Y5CAHLXS.js.map +6 -0
- package/dist/peer/designer.js +35841 -0
- package/dist/peer/designer.js.map +6 -0
- package/dist/peer/viewer.js +1061 -0
- package/dist/peer/viewer.js.map +6 -0
- package/dist/viewer.d.ts +77 -0
- package/dist/viewer.js +1066 -0
- package/dist/viewer.js.map +6 -0
- package/package.json +76 -4
|
@@ -0,0 +1,1167 @@
|
|
|
1
|
+
// src/engine/text/fonts.js
|
|
2
|
+
import fontkitModule from "@pdf-lib/fontkit";
|
|
3
|
+
|
|
4
|
+
// src/engine/text/shaper.js
|
|
5
|
+
var LTR = 4;
|
|
6
|
+
var RTL = 5;
|
|
7
|
+
var PLAIN = ["-kern", "-liga", "-clig", "-calt", "-dlig"];
|
|
8
|
+
async function createShaper(wasm) {
|
|
9
|
+
let memory;
|
|
10
|
+
const { instance } = await WebAssembly.instantiate(wasm, {
|
|
11
|
+
env: {
|
|
12
|
+
_emscripten_runtime_keepalive_clear() {
|
|
13
|
+
},
|
|
14
|
+
_abort_js() {
|
|
15
|
+
throw new Error("HarfBuzz stopped");
|
|
16
|
+
},
|
|
17
|
+
_setitimer_js() {
|
|
18
|
+
return 0;
|
|
19
|
+
},
|
|
20
|
+
emscripten_resize_heap(size) {
|
|
21
|
+
const need = (size >>> 0) - memory.buffer.byteLength;
|
|
22
|
+
try {
|
|
23
|
+
memory.grow(Math.ceil(need / 65536));
|
|
24
|
+
return 1;
|
|
25
|
+
} catch {
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
wasi_snapshot_preview1: { proc_exit(code) {
|
|
31
|
+
throw new Error(`HarfBuzz exited (${code})`);
|
|
32
|
+
} }
|
|
33
|
+
});
|
|
34
|
+
const x = (
|
|
35
|
+
/** @type {any} */
|
|
36
|
+
instance.exports
|
|
37
|
+
);
|
|
38
|
+
memory = x.memory;
|
|
39
|
+
x.__wasm_call_ctors();
|
|
40
|
+
const featPtr = x.malloc(16 * PLAIN.length);
|
|
41
|
+
PLAIN.forEach((f, i) => {
|
|
42
|
+
const s = x.malloc(f.length + 1);
|
|
43
|
+
new Uint8Array(memory.buffer).set([...f].map((c) => c.charCodeAt(0)).concat(0), s);
|
|
44
|
+
x.hb_feature_from_string(s, -1, featPtr + 16 * i);
|
|
45
|
+
x.free(s);
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
/**
|
|
49
|
+
* A HarfBuzz font for these bytes. It lives as long as the shaper: fonts are few and reused.
|
|
50
|
+
* @param {Uint8Array} bytes @returns {{ ptr: number, upem: number }}
|
|
51
|
+
*/
|
|
52
|
+
font(bytes) {
|
|
53
|
+
const buf = x.malloc(bytes.byteLength);
|
|
54
|
+
new Uint8Array(memory.buffer).set(bytes, buf);
|
|
55
|
+
const blob = x.hb_blob_create(buf, bytes.byteLength, 1, 0, 0);
|
|
56
|
+
const face = x.hb_face_create(blob, 0);
|
|
57
|
+
const ptr = x.hb_font_create(face);
|
|
58
|
+
const upem = x.hb_face_get_upem(face);
|
|
59
|
+
x.hb_face_destroy(face);
|
|
60
|
+
x.hb_blob_destroy(blob);
|
|
61
|
+
return { ptr, upem };
|
|
62
|
+
},
|
|
63
|
+
/**
|
|
64
|
+
* Shape one run (one font, one script, one direction).
|
|
65
|
+
* @param {{ ptr: number }} font @param {string} text @param {boolean} rtl
|
|
66
|
+
* @param {boolean} [plain] kerning and ligatures off (a simple-script run in a right-to-left context)
|
|
67
|
+
* @returns {number[]} [glyph id, x advance, x offset, y offset, cluster (UTF-16 index)] per glyph, in visual order
|
|
68
|
+
*/
|
|
69
|
+
shape(font, text, rtl, plain = false) {
|
|
70
|
+
const n2 = text.length;
|
|
71
|
+
const p = x.malloc(n2 * 2 + 2);
|
|
72
|
+
const u16 = new Uint16Array(memory.buffer, p, n2);
|
|
73
|
+
for (let i = 0; i < n2; i++) u16[i] = text.charCodeAt(i);
|
|
74
|
+
const b = x.hb_buffer_create();
|
|
75
|
+
x.hb_buffer_add_utf16(b, p, n2, 0, n2);
|
|
76
|
+
x.hb_buffer_set_direction(b, rtl ? RTL : LTR);
|
|
77
|
+
x.hb_buffer_guess_segment_properties(b);
|
|
78
|
+
x.hb_shape(font.ptr, b, plain ? featPtr : 0, plain ? PLAIN.length : 0);
|
|
79
|
+
const len = x.hb_buffer_get_length(b);
|
|
80
|
+
const info = new Uint32Array(memory.buffer, x.hb_buffer_get_glyph_infos(b, 0), len * 5);
|
|
81
|
+
const pos = new Int32Array(memory.buffer, x.hb_buffer_get_glyph_positions(b, 0), len * 5);
|
|
82
|
+
const out = new Array(len * 5);
|
|
83
|
+
for (let i = 0; i < len; i++) {
|
|
84
|
+
out[i * 5] = info[i * 5];
|
|
85
|
+
out[i * 5 + 1] = pos[i * 5];
|
|
86
|
+
out[i * 5 + 2] = pos[i * 5 + 2];
|
|
87
|
+
out[i * 5 + 3] = pos[i * 5 + 3];
|
|
88
|
+
out[i * 5 + 4] = info[i * 5 + 2];
|
|
89
|
+
}
|
|
90
|
+
x.hb_buffer_destroy(b);
|
|
91
|
+
x.free(p);
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/engine/text/fonts.js
|
|
98
|
+
var fontkit = (
|
|
99
|
+
/** @type {any} */
|
|
100
|
+
fontkitModule.default || fontkitModule
|
|
101
|
+
);
|
|
102
|
+
var BUNDLED = [
|
|
103
|
+
["Inter", "Inter-Regular", 400],
|
|
104
|
+
["Inter", "Inter-Italic", 400, "italic"],
|
|
105
|
+
["Inter", "Inter-SemiBold", 600],
|
|
106
|
+
["Inter", "Inter-Bold", 700],
|
|
107
|
+
["Inter", "Inter-BoldItalic", 700, "italic"],
|
|
108
|
+
["JetBrains Mono", "JetBrainsMono-Regular", 400],
|
|
109
|
+
["JetBrains Mono", "JetBrainsMono-Bold", 700],
|
|
110
|
+
["Noto Sans Devanagari", "NotoSansDevanagari-Regular", 400],
|
|
111
|
+
["Noto Sans Devanagari", "NotoSansDevanagari-Bold", 700],
|
|
112
|
+
["Noto Sans Arabic", "NotoSansArabic-Regular", 400],
|
|
113
|
+
["Noto Sans Arabic", "NotoSansArabic-Bold", 700],
|
|
114
|
+
["Noto Sans Hebrew", "NotoSansHebrew-Regular", 400],
|
|
115
|
+
["Noto Sans Hebrew", "NotoSansHebrew-Bold", 700],
|
|
116
|
+
// fallbacks loaded only when text uses the script (docs/TEXT.md has the sizes)
|
|
117
|
+
...["Thai", "Lao", "Khmer", "Myanmar", "Tamil", "Bengali", "Telugu", "Kannada", "Gujarati"].flatMap((s) => [
|
|
118
|
+
[`Noto Sans ${s}`, `NotoSans${s}-Regular`, 400],
|
|
119
|
+
[`Noto Sans ${s}`, `NotoSans${s}-Bold`, 700]
|
|
120
|
+
]),
|
|
121
|
+
// optional, about 40 MB (npm run fonts:cjk fetches them into /fonts/cjk): variable fonts drawn at their default
|
|
122
|
+
// (regular) weight. Without them the text shows as boxes, and the warning says how to add them.
|
|
123
|
+
["Noto Sans JP", "NotoSansJP", 400, "normal", "cjk/NotoSansJP.ttf"],
|
|
124
|
+
["Noto Sans KR", "NotoSansKR", 400, "normal", "cjk/NotoSansKR.ttf"],
|
|
125
|
+
["Noto Sans SC", "NotoSansSC", 400, "normal", "cjk/NotoSansSC.ttf"],
|
|
126
|
+
["Noto Emoji", "NotoEmoji", 400, "normal", "cjk/NotoEmoji.ttf"]
|
|
127
|
+
];
|
|
128
|
+
var OPTIONAL_KEYS = ["NotoSansJP", "NotoSansKR", "NotoSansSC", "NotoEmoji"];
|
|
129
|
+
var FONT_FAMILIES = {};
|
|
130
|
+
var FACES = /* @__PURE__ */ new Map();
|
|
131
|
+
var addFace = (f) => {
|
|
132
|
+
var _a;
|
|
133
|
+
(FONT_FAMILIES[_a = f.family] || (FONT_FAMILIES[_a] = [])).push(f);
|
|
134
|
+
FACES.set(f.key, f);
|
|
135
|
+
};
|
|
136
|
+
for (const [family, key, weight, style = "normal", file] of BUNDLED) {
|
|
137
|
+
addFace({ family: String(family), key: String(key), weight: Number(weight), style: style === "italic" ? "italic" : "normal", file: String(file || `${key}.ttf`) });
|
|
138
|
+
}
|
|
139
|
+
var BUNDLED_FAMILIES = Object.keys(FONT_FAMILIES);
|
|
140
|
+
var DEFAULT_FAMILY = "Inter";
|
|
141
|
+
var ALL_FONT_KEYS = [...FONT_FAMILIES.Inter, ...FONT_FAMILIES["JetBrains Mono"]].map((f) => f.key);
|
|
142
|
+
var CORE_FONT_KEY = "Inter-Regular";
|
|
143
|
+
var PRELOAD_KEYS = [...ALL_FONT_KEYS];
|
|
144
|
+
var SHAPER = "harfbuzz";
|
|
145
|
+
function fontFile(key) {
|
|
146
|
+
if (key === SHAPER) return "harfbuzz.wasm";
|
|
147
|
+
return FACES.get(key)?.file || `${key}.ttf`;
|
|
148
|
+
}
|
|
149
|
+
function setCustomFonts(list) {
|
|
150
|
+
for (const [k, f] of FACES) if (k.startsWith("u-")) {
|
|
151
|
+
FACES.delete(k);
|
|
152
|
+
const left = FONT_FAMILIES[f.family].filter((x) => x.key !== k);
|
|
153
|
+
if (left.length) FONT_FAMILIES[f.family] = left;
|
|
154
|
+
else delete FONT_FAMILIES[f.family];
|
|
155
|
+
}
|
|
156
|
+
for (const f of list || []) {
|
|
157
|
+
if (BUNDLED_FAMILIES.includes(f.family) || !/^[a-f0-9]{8,64}$/.test(String(f.id))) continue;
|
|
158
|
+
addFace({ key: `u-${f.id}`, family: String(f.family), weight: Number(f.weight) || 400, style: f.style === "italic" ? "italic" : "normal", file: `custom/${f.id}` });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function resolveFontKey(family, weight, style) {
|
|
162
|
+
const faces = FONT_FAMILIES[family || DEFAULT_FAMILY] || FONT_FAMILIES[DEFAULT_FAMILY];
|
|
163
|
+
const w = weight === "bold" ? 700 : weight === "normal" || weight == null ? 400 : Number(weight) || 400;
|
|
164
|
+
const italic = faces.filter((f) => f.style === "italic");
|
|
165
|
+
const list = style === "italic" && italic.length ? italic : faces.filter((f) => f.style !== "italic").length ? faces.filter((f) => f.style !== "italic") : faces;
|
|
166
|
+
const rank = (f) => {
|
|
167
|
+
const fw = f.weight;
|
|
168
|
+
if (w >= 400 && w <= 500) return fw >= w && fw <= 500 ? fw - w : fw < w ? 1e3 + w - fw : 2e3 + fw - w;
|
|
169
|
+
if (w < 400) return fw <= w ? w - fw : 1e3 + fw - w;
|
|
170
|
+
return fw >= w ? fw - w : 1e3 + w - fw;
|
|
171
|
+
};
|
|
172
|
+
return list.reduce((best, f) => rank(f) < rank(best) ? f : best).key;
|
|
173
|
+
}
|
|
174
|
+
function fontCss(key) {
|
|
175
|
+
const f = FACES.get(key);
|
|
176
|
+
return f ? { family: f.family, weight: f.weight, style: f.style } : { family: DEFAULT_FAMILY, weight: 400, style: "normal" };
|
|
177
|
+
}
|
|
178
|
+
var FALLBACK = [
|
|
179
|
+
[1424, 1535, "Noto Sans Hebrew"],
|
|
180
|
+
[64285, 64335, "Noto Sans Hebrew"],
|
|
181
|
+
[1536, 2303, "Noto Sans Arabic"],
|
|
182
|
+
[64336, 65023, "Noto Sans Arabic"],
|
|
183
|
+
[65136, 65278, "Noto Sans Arabic"],
|
|
184
|
+
[2304, 2431, "Noto Sans Devanagari"],
|
|
185
|
+
[43232, 43263, "Noto Sans Devanagari"],
|
|
186
|
+
[7376, 7423, "Noto Sans Devanagari"],
|
|
187
|
+
[2432, 2559, "Noto Sans Bengali"],
|
|
188
|
+
[2688, 2815, "Noto Sans Gujarati"],
|
|
189
|
+
[2944, 3071, "Noto Sans Tamil"],
|
|
190
|
+
[73664, 73727, "Noto Sans Tamil"],
|
|
191
|
+
[3072, 3199, "Noto Sans Telugu"],
|
|
192
|
+
[3200, 3327, "Noto Sans Kannada"],
|
|
193
|
+
[3584, 3711, "Noto Sans Thai"],
|
|
194
|
+
[3712, 3839, "Noto Sans Lao"],
|
|
195
|
+
[4096, 4255, "Noto Sans Myanmar"],
|
|
196
|
+
[43488, 43519, "Noto Sans Myanmar"],
|
|
197
|
+
[43616, 43647, "Noto Sans Myanmar"],
|
|
198
|
+
[6016, 6143, "Noto Sans Khmer"],
|
|
199
|
+
[6624, 6655, "Noto Sans Khmer"],
|
|
200
|
+
// CJK (optional fonts): kana and Han in the Japanese face (Han shapes differ a little from Chinese; a report can
|
|
201
|
+
// set fontFamily "Noto Sans SC"), Hangul in the Korean one; emoji in Noto Emoji (monochrome)
|
|
202
|
+
[12352, 12543, "Noto Sans JP"],
|
|
203
|
+
[12784, 12799, "Noto Sans JP"],
|
|
204
|
+
[12288, 12351, "Noto Sans JP"],
|
|
205
|
+
[13312, 19903, "Noto Sans JP"],
|
|
206
|
+
[19968, 40959, "Noto Sans JP"],
|
|
207
|
+
[63744, 64255, "Noto Sans JP"],
|
|
208
|
+
[65280, 65519, "Noto Sans JP"],
|
|
209
|
+
[4352, 4607, "Noto Sans KR"],
|
|
210
|
+
[12592, 12687, "Noto Sans KR"],
|
|
211
|
+
[44032, 55215, "Noto Sans KR"],
|
|
212
|
+
[126976, 129791, "Noto Emoji"],
|
|
213
|
+
[9728, 10175, "Noto Emoji"],
|
|
214
|
+
[11008, 11263, "Noto Emoji"]
|
|
215
|
+
];
|
|
216
|
+
var allFontKeys = () => [...FACES.keys()];
|
|
217
|
+
function fallbackKeys(key) {
|
|
218
|
+
const c = fontCss(key);
|
|
219
|
+
return [...new Set(FALLBACK.map((f) => String(f[2])))].map((family) => resolveFontKey(family, c.weight, c.style));
|
|
220
|
+
}
|
|
221
|
+
function unicodeRange(key) {
|
|
222
|
+
const family = FACES.get(key)?.family;
|
|
223
|
+
const r = FALLBACK.filter((f) => f[2] === family).map(([a, b]) => `U+${Number(a).toString(16)}-${Number(b).toString(16)}`);
|
|
224
|
+
return r.length ? [...r, "U+300-36F", "U+2000-206F", "U+25CC", "U+FE00-FE0F"].join(",") : null;
|
|
225
|
+
}
|
|
226
|
+
function fallbackKey(cp, key) {
|
|
227
|
+
for (const [a, b, family] of FALLBACK) {
|
|
228
|
+
if (cp >= /** @type {number} */
|
|
229
|
+
a && cp <= /** @type {number} */
|
|
230
|
+
b) {
|
|
231
|
+
const c = fontCss(key);
|
|
232
|
+
return resolveFontKey(String(family), c.weight, c.style);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
var SIMPLE_FALLBACK = /* @__PURE__ */ new Set(["Noto Sans JP", "Noto Sans KR", "Noto Emoji"]);
|
|
238
|
+
var BEYOND_LATIN = /[^\u0000-\u024f\u1e00-\u1eff\u2000-\u20cf\u2100-\u218f]/;
|
|
239
|
+
function fontsForDef(def) {
|
|
240
|
+
const keys = /* @__PURE__ */ new Set();
|
|
241
|
+
const seen = /* @__PURE__ */ new Set();
|
|
242
|
+
const walk = (o) => {
|
|
243
|
+
if (!o || typeof o !== "object" || seen.has(o)) return;
|
|
244
|
+
seen.add(o);
|
|
245
|
+
if (Array.isArray(o)) {
|
|
246
|
+
for (const x of o) walk(x);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (o.fontFamily != null || o.fontWeight != null || o.fontStyle != null) keys.add(resolveFontKey(o.fontFamily, o.fontWeight, o.fontStyle));
|
|
250
|
+
if (o.type === "chart") keys.add(resolveFontKey(o.fontFamily, "bold", "normal"));
|
|
251
|
+
for (const [k, v] of Object.entries(o)) if (v && typeof v === "object" && k !== "dataSources" && k !== "data") walk(v);
|
|
252
|
+
};
|
|
253
|
+
walk(def);
|
|
254
|
+
return [...keys];
|
|
255
|
+
}
|
|
256
|
+
function fontsFor(texts) {
|
|
257
|
+
const keys = /* @__PURE__ */ new Set(), seen = /* @__PURE__ */ new Set();
|
|
258
|
+
let shaper = false;
|
|
259
|
+
for (const t of texts) {
|
|
260
|
+
if (typeof t !== "string" || !BEYOND_LATIN.test(t)) continue;
|
|
261
|
+
for (const ch of t) {
|
|
262
|
+
const cp = (
|
|
263
|
+
/** @type {number} */
|
|
264
|
+
ch.codePointAt(0)
|
|
265
|
+
);
|
|
266
|
+
if (cp < 592 || seen.has(cp)) continue;
|
|
267
|
+
seen.add(cp);
|
|
268
|
+
for (const [a, b, family] of FALLBACK) {
|
|
269
|
+
if (cp < /** @type {number} */
|
|
270
|
+
a || cp > /** @type {number} */
|
|
271
|
+
b) continue;
|
|
272
|
+
keys.add(resolveFontKey(String(family), 400, "normal")).add(resolveFontKey(String(family), 700, "normal"));
|
|
273
|
+
if (!SIMPLE_FALLBACK.has(String(family))) shaper = true;
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (shaper) keys.add(SHAPER);
|
|
279
|
+
return [...keys];
|
|
280
|
+
}
|
|
281
|
+
var FontStore = class {
|
|
282
|
+
/** @param {(key: string) => Promise<ArrayBuffer|Uint8Array>} loader */
|
|
283
|
+
constructor(loader) {
|
|
284
|
+
this.loader = loader;
|
|
285
|
+
this.fonts = /* @__PURE__ */ new Map();
|
|
286
|
+
this.bytes = /* @__PURE__ */ new Map();
|
|
287
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
288
|
+
this.failed = /* @__PURE__ */ new Set();
|
|
289
|
+
this.shaper = null;
|
|
290
|
+
this.hb = /* @__PURE__ */ new Map();
|
|
291
|
+
}
|
|
292
|
+
/** @param {string[]} keys */
|
|
293
|
+
async load(keys = ALL_FONT_KEYS) {
|
|
294
|
+
await Promise.all(keys.map((k) => {
|
|
295
|
+
if (this.fonts.has(k) || this.failed.has(k) || k === SHAPER && this.shaper) return null;
|
|
296
|
+
if (!this.pending.has(k)) {
|
|
297
|
+
this.pending.set(k, (async () => {
|
|
298
|
+
try {
|
|
299
|
+
const b = await this.loader(k);
|
|
300
|
+
const u8 = b instanceof Uint8Array ? b : new Uint8Array(b);
|
|
301
|
+
if (k === SHAPER) {
|
|
302
|
+
this.shaper = await createShaper(u8);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
this.fonts.set(k, fontkit.create(u8));
|
|
306
|
+
this.bytes.set(k, u8);
|
|
307
|
+
} catch (e) {
|
|
308
|
+
this.pending.delete(k);
|
|
309
|
+
if (k === CORE_FONT_KEY) throw e;
|
|
310
|
+
this.failed.add(k);
|
|
311
|
+
}
|
|
312
|
+
})());
|
|
313
|
+
}
|
|
314
|
+
return this.pending.get(k);
|
|
315
|
+
}));
|
|
316
|
+
}
|
|
317
|
+
/** @param {string} key */
|
|
318
|
+
has(key) {
|
|
319
|
+
return this.fonts.has(key);
|
|
320
|
+
}
|
|
321
|
+
/** The HarfBuzz font for a loaded key (the shaper must be loaded). @param {string} key */
|
|
322
|
+
hbFont(key) {
|
|
323
|
+
let f = this.hb.get(key);
|
|
324
|
+
if (!f) {
|
|
325
|
+
f = this.shaper.font(this.bytes.get(key));
|
|
326
|
+
this.hb.set(key, f);
|
|
327
|
+
}
|
|
328
|
+
return f;
|
|
329
|
+
}
|
|
330
|
+
/** @param {string} key */
|
|
331
|
+
get(key) {
|
|
332
|
+
const f = this.fonts.get(key);
|
|
333
|
+
if (!f) throw new Error(`Font "${key}" is not loaded`);
|
|
334
|
+
return f;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// src/engine/image.js
|
|
339
|
+
var IMAGE_LIMITS = {
|
|
340
|
+
bytes: 10 * 1024 * 1024,
|
|
341
|
+
// one image
|
|
342
|
+
total: 50 * 1024 * 1024,
|
|
343
|
+
// all images of one render (each distinct image counted once)
|
|
344
|
+
pixels: 4e7,
|
|
345
|
+
// a PNG larger than this is not decoded (a small file can inflate to gigabytes)
|
|
346
|
+
svgBytes: 2 * 1024 * 1024,
|
|
347
|
+
// an SVG drawn as vector paths in the PDF
|
|
348
|
+
svgShapes: 1e4
|
|
349
|
+
};
|
|
350
|
+
var PX = 0.75;
|
|
351
|
+
var MIME = { png: "image/png", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp", svg: "image/svg+xml" };
|
|
352
|
+
function sniff(b) {
|
|
353
|
+
if (b.length >= 8 && b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) return "png";
|
|
354
|
+
if (b.length >= 3 && b[0] === 255 && b[1] === 216 && b[2] === 255) return "jpeg";
|
|
355
|
+
if (b.length >= 6 && b[0] === 71 && b[1] === 73 && b[2] === 70 && b[3] === 56) return "gif";
|
|
356
|
+
if (b.length >= 12 && b[0] === 82 && b[1] === 73 && b[2] === 70 && b[3] === 70 && b[8] === 87 && b[9] === 69 && b[10] === 66 && b[11] === 80) return "webp";
|
|
357
|
+
const head = String.fromCharCode(...b.subarray(0, 512)).replace(/^|^\xEF\xBB\xBF/, "").trimStart();
|
|
358
|
+
if (/^<(\?xml|svg|!--|!DOCTYPE svg)/i.test(head) && /<svg[\s>]/i.test(head + (b.length > 512 ? "<svg " : ""))) return "svg";
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
function toBase64(b) {
|
|
362
|
+
let s = "";
|
|
363
|
+
for (let i = 0; i < b.length; i += 32768) s += String.fromCharCode(...b.subarray(i, i + 32768));
|
|
364
|
+
return btoa(s);
|
|
365
|
+
}
|
|
366
|
+
function fromBase64(s) {
|
|
367
|
+
const bin = atob(s);
|
|
368
|
+
const out = new Uint8Array(bin.length);
|
|
369
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
370
|
+
return out;
|
|
371
|
+
}
|
|
372
|
+
var B64 = /^[A-Za-z0-9+/]+={0,2}$/;
|
|
373
|
+
function toDataUri(v) {
|
|
374
|
+
if (v instanceof ArrayBuffer) v = new Uint8Array(v);
|
|
375
|
+
if (v instanceof Uint8Array) {
|
|
376
|
+
if (v.length > IMAGE_LIMITS.bytes) return null;
|
|
377
|
+
const type2 = sniff(v);
|
|
378
|
+
return type2 ? `data:${MIME[type2]};base64,${toBase64(v)}` : null;
|
|
379
|
+
}
|
|
380
|
+
if (typeof v !== "string" || v.length < 16 || v.startsWith("data:")) return v;
|
|
381
|
+
if (/[^A-Za-z0-9+/=\r\n]/.test(v)) return v;
|
|
382
|
+
const s = v.replace(/[\r\n]/g, "");
|
|
383
|
+
if (!B64.test(s) || s.length % 4) return v;
|
|
384
|
+
let type;
|
|
385
|
+
try {
|
|
386
|
+
type = sniff(fromBase64(s.slice(0, 700)));
|
|
387
|
+
} catch {
|
|
388
|
+
return v;
|
|
389
|
+
}
|
|
390
|
+
return type ? `data:${MIME[type]};base64,${s}` : v;
|
|
391
|
+
}
|
|
392
|
+
function splitDataUri(uri) {
|
|
393
|
+
if (typeof uri !== "string" || !/^data:/i.test(uri)) return null;
|
|
394
|
+
const c = uri.indexOf(",");
|
|
395
|
+
if (c < 0) return null;
|
|
396
|
+
const meta = uri.slice(5, c);
|
|
397
|
+
return { meta, b64: meta.slice(meta.lastIndexOf(";") + 1).trim().toLowerCase() === "base64" && meta.includes(";"), body: uri.slice(c + 1) };
|
|
398
|
+
}
|
|
399
|
+
function dataUriBytes(uri) {
|
|
400
|
+
const d = splitDataUri(uri);
|
|
401
|
+
if (!d) return 0;
|
|
402
|
+
return d.b64 ? Math.floor(d.body.length * 3 / 4) : d.body.length;
|
|
403
|
+
}
|
|
404
|
+
function dataUriHead(uri, max = 262144) {
|
|
405
|
+
const d = splitDataUri(uri);
|
|
406
|
+
if (!d) return null;
|
|
407
|
+
try {
|
|
408
|
+
if (d.b64) return fromBase64(d.body.slice(0, Number.isFinite(max) ? Math.ceil(max / 3) * 4 : void 0).replace(/\s/g, ""));
|
|
409
|
+
return new TextEncoder().encode(decodeURIComponent(d.body.slice(0, max)));
|
|
410
|
+
} catch {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function dataUriBytesOf(uri) {
|
|
415
|
+
return dataUriHead(uri, Infinity);
|
|
416
|
+
}
|
|
417
|
+
var UNIT = { px: 1, pt: 4 / 3, pc: 16, mm: 96 / 25.4, cm: 96 / 2.54, in: 96, "": 1 };
|
|
418
|
+
function svgLength(v) {
|
|
419
|
+
const t = String(v ?? "").trim();
|
|
420
|
+
if (t.length > 64) return null;
|
|
421
|
+
const m = /^([\d.]+(?:e[-+]?\d+)?)(px|pt|pc|mm|cm|in)?$/i.exec(t);
|
|
422
|
+
if (!m) return null;
|
|
423
|
+
const n2 = Number(m[1]) * UNIT[(m[2] || "").toLowerCase()];
|
|
424
|
+
return Number.isFinite(n2) && n2 > 0 ? n2 : null;
|
|
425
|
+
}
|
|
426
|
+
var isName = (c) => c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c >= "0" && c <= "9" || c === "_" || c === ":" || c === "-" || c === ".";
|
|
427
|
+
var isSpace = (c) => c === " " || c === " " || c === "\n" || c === "\r";
|
|
428
|
+
function* xmlTags(text) {
|
|
429
|
+
const n2 = text.length;
|
|
430
|
+
let i = 0;
|
|
431
|
+
while (i < n2) {
|
|
432
|
+
const lt = text.indexOf("<", i);
|
|
433
|
+
if (lt < 0) return;
|
|
434
|
+
if (text.startsWith("<!--", lt)) {
|
|
435
|
+
const e = text.indexOf("-->", lt + 4);
|
|
436
|
+
if (e < 0) return;
|
|
437
|
+
i = e + 3;
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (text.startsWith("<![CDATA[", lt)) {
|
|
441
|
+
const e = text.indexOf("]]>", lt + 9);
|
|
442
|
+
if (e < 0) return;
|
|
443
|
+
i = e + 3;
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (text[lt + 1] === "?" || text[lt + 1] === "!") {
|
|
447
|
+
const e = text.indexOf(">", lt + 2);
|
|
448
|
+
if (e < 0) return;
|
|
449
|
+
i = e + 1;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
let j = lt + 1, q = "";
|
|
453
|
+
for (; j < n2; j++) {
|
|
454
|
+
const c = text[j];
|
|
455
|
+
if (q) {
|
|
456
|
+
if (c === q) q = "";
|
|
457
|
+
} else if (c === '"' || c === "'") q = c;
|
|
458
|
+
else if (c === ">") break;
|
|
459
|
+
}
|
|
460
|
+
if (j >= n2) return;
|
|
461
|
+
i = j + 1;
|
|
462
|
+
let body = text.slice(lt + 1, j);
|
|
463
|
+
const close = body[0] === "/";
|
|
464
|
+
if (close) body = body.slice(1);
|
|
465
|
+
const self = body.endsWith("/");
|
|
466
|
+
if (self) body = body.slice(0, -1);
|
|
467
|
+
let k = 0;
|
|
468
|
+
while (k < body.length && isName(body[k])) k++;
|
|
469
|
+
const name = body.slice(0, k);
|
|
470
|
+
if (!/^[a-zA-Z]/.test(name)) continue;
|
|
471
|
+
yield { close, name, attrs: body.slice(k), self };
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function xmlAttrs(s) {
|
|
475
|
+
const a = /* @__PURE__ */ Object.create(null);
|
|
476
|
+
const n2 = s.length;
|
|
477
|
+
let i = 0;
|
|
478
|
+
while (i < n2) {
|
|
479
|
+
while (i < n2 && !isName(s[i])) i++;
|
|
480
|
+
const st = i;
|
|
481
|
+
while (i < n2 && isName(s[i])) i++;
|
|
482
|
+
const name = s.slice(st, i);
|
|
483
|
+
while (i < n2 && isSpace(s[i])) i++;
|
|
484
|
+
if (s[i] !== "=") continue;
|
|
485
|
+
i++;
|
|
486
|
+
while (i < n2 && isSpace(s[i])) i++;
|
|
487
|
+
const q = s[i];
|
|
488
|
+
if (q !== '"' && q !== "'") continue;
|
|
489
|
+
const e = s.indexOf(q, i + 1);
|
|
490
|
+
if (e < 0) break;
|
|
491
|
+
if (name) a[name] = s.slice(i + 1, e);
|
|
492
|
+
i = e + 1;
|
|
493
|
+
}
|
|
494
|
+
return a;
|
|
495
|
+
}
|
|
496
|
+
function svgRootAttrs(text) {
|
|
497
|
+
for (const t of xmlTags(String(text))) if (!t.close && t.name.toLowerCase() === "svg") return xmlAttrs(t.attrs);
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
function svgSize(text) {
|
|
501
|
+
const a = svgRootAttrs(text);
|
|
502
|
+
if (!a) return null;
|
|
503
|
+
const vb = String(a.viewBox || "").trim().split(/[\s,]+/).map(Number);
|
|
504
|
+
const hasVb = vb.length === 4 && vb.every(Number.isFinite) && vb[2] > 0 && vb[3] > 0;
|
|
505
|
+
let w = svgLength(a.width), h = svgLength(a.height);
|
|
506
|
+
if (w && !h) h = hasVb ? w * vb[3] / vb[2] : null;
|
|
507
|
+
if (h && !w) w = hasVb ? h * vb[2] / vb[3] : null;
|
|
508
|
+
if (!w && hasVb) {
|
|
509
|
+
w = vb[2];
|
|
510
|
+
h = vb[3];
|
|
511
|
+
}
|
|
512
|
+
return w && h ? { w, h } : null;
|
|
513
|
+
}
|
|
514
|
+
function imageInfoOf(b) {
|
|
515
|
+
const type = sniff(b);
|
|
516
|
+
if (!type) return null;
|
|
517
|
+
const u32 = (i) => (b[i] << 24 | b[i + 1] << 16 | b[i + 2] << 8 | b[i + 3]) >>> 0;
|
|
518
|
+
if (type === "png") return pngInfo(b);
|
|
519
|
+
if (type === "gif" && b.length >= 10) return { type, w: b[6] | b[7] << 8, h: b[8] | b[9] << 8 };
|
|
520
|
+
if (type === "jpeg") {
|
|
521
|
+
for (let i = 2; i + 9 < b.length; ) {
|
|
522
|
+
if (b[i] !== 255) {
|
|
523
|
+
i++;
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
const mk = b[i + 1];
|
|
527
|
+
if (mk === 255) {
|
|
528
|
+
i++;
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
if (mk === 216 || mk === 1 || mk >= 208 && mk <= 215) {
|
|
532
|
+
i += 2;
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
if (mk >= 192 && mk <= 207 && mk !== 196 && mk !== 200 && mk !== 204) return { type, w: b[i + 7] << 8 | b[i + 8], h: b[i + 5] << 8 | b[i + 6] };
|
|
536
|
+
i += 2 + (b[i + 2] << 8 | b[i + 3]);
|
|
537
|
+
}
|
|
538
|
+
return { type };
|
|
539
|
+
}
|
|
540
|
+
if (type === "svg") {
|
|
541
|
+
const s = svgSize(new TextDecoder().decode(b));
|
|
542
|
+
return s ? { type, ...s } : { type };
|
|
543
|
+
}
|
|
544
|
+
return { type };
|
|
545
|
+
}
|
|
546
|
+
function pngInfo(b) {
|
|
547
|
+
const u32 = (i) => (b[i] << 24 | b[i + 1] << 16 | b[i + 2] << 8 | b[i + 3]) >>> 0;
|
|
548
|
+
let w = 0, h = 0, ihdrs = 0, animated = false;
|
|
549
|
+
for (let off = 8; off + 8 <= b.length; ) {
|
|
550
|
+
const len = u32(off);
|
|
551
|
+
const t = String.fromCharCode(b[off + 4], b[off + 5], b[off + 6], b[off + 7]);
|
|
552
|
+
const d = off + 8;
|
|
553
|
+
if (t === "IHDR" && d + 8 <= b.length) {
|
|
554
|
+
w = u32(d);
|
|
555
|
+
h = u32(d + 4);
|
|
556
|
+
ihdrs++;
|
|
557
|
+
} else if (t === "acTL" || t === "fcTL" || t === "fdAT") animated = true;
|
|
558
|
+
else if (t === "IEND") break;
|
|
559
|
+
off = d + len + 4;
|
|
560
|
+
}
|
|
561
|
+
if (!ihdrs) return { type: "png" };
|
|
562
|
+
return { type: "png", w, h, ...animated || ihdrs > 1 ? { suspect: true } : {} };
|
|
563
|
+
}
|
|
564
|
+
function imageInfo(uri) {
|
|
565
|
+
const b = typeof uri === "string" && uri.startsWith("data:") ? dataUriHead(uri) : null;
|
|
566
|
+
return b ? imageInfoOf(b) : null;
|
|
567
|
+
}
|
|
568
|
+
function resolveImageSrc(v, who, c) {
|
|
569
|
+
var _a;
|
|
570
|
+
if (v == null || v === "" || v === "#Error") return null;
|
|
571
|
+
if (typeof v === "string" && v.startsWith("embedded:")) {
|
|
572
|
+
const name = v.slice(9);
|
|
573
|
+
const img = c.images && Object.prototype.hasOwnProperty.call(c.images, name) ? c.images[name] : void 0;
|
|
574
|
+
if (typeof img !== "string" || !/^data:image\/[\w.+-]+[;,]/i.test(img)) {
|
|
575
|
+
c.warn(`${who}: there is no embedded image named "${name}" (add it to the report's images as a data:image/… URI)`);
|
|
576
|
+
return null;
|
|
577
|
+
}
|
|
578
|
+
v = img;
|
|
579
|
+
}
|
|
580
|
+
const memo = c.budget ? (_a = c.budget).memo || (_a.memo = /* @__PURE__ */ new Map()) : null;
|
|
581
|
+
let u = memo?.get(v);
|
|
582
|
+
if (u === void 0) {
|
|
583
|
+
u = toDataUri(v);
|
|
584
|
+
memo?.set(v, u);
|
|
585
|
+
}
|
|
586
|
+
if (u == null) {
|
|
587
|
+
c.warn(`${who}: the image data is not a PNG, JPEG, GIF, WebP or SVG image, or is larger than 10 MB`);
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
const s = String(u);
|
|
591
|
+
if (s.startsWith("data:")) {
|
|
592
|
+
const n2 = dataUriBytes(s);
|
|
593
|
+
if (n2 > IMAGE_LIMITS.bytes) {
|
|
594
|
+
c.warn(`${who}: the image is ${(n2 / 1048576).toFixed(1)} MB; images are limited to 10 MB each, so it is left out`);
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
597
|
+
const b = c.budget;
|
|
598
|
+
if (b && !b.seen.has(s)) {
|
|
599
|
+
if (b.bytes + n2 > IMAGE_LIMITS.total) {
|
|
600
|
+
c.warn(`${who}: the report's images pass 50 MB in all, so this one is left out`);
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
b.seen.add(s);
|
|
604
|
+
b.bytes += n2;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return s;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// src/exporters/color.js
|
|
611
|
+
var NAMED = { black: "#000000", white: "#ffffff", red: "#ff0000", green: "#008000", blue: "#0000ff", gray: "#808080", grey: "#808080", orange: "#ffa500", yellow: "#ffff00" };
|
|
612
|
+
function parseColor(c) {
|
|
613
|
+
if (!c || c === "none" || c === "transparent") return null;
|
|
614
|
+
let s = String(c).trim().toLowerCase();
|
|
615
|
+
if (NAMED[s]) s = NAMED[s];
|
|
616
|
+
let m = /^#([0-9a-f]{3,8})$/.exec(s);
|
|
617
|
+
if (m) {
|
|
618
|
+
let h = m[1];
|
|
619
|
+
if (h.length === 3 || h.length === 4) h = [...h].map((x) => x + x).join("");
|
|
620
|
+
const n2 = parseInt(h.slice(0, 6), 16);
|
|
621
|
+
const a = h.length === 8 ? parseInt(h.slice(6, 8), 16) / 255 : 1;
|
|
622
|
+
return { r: (n2 >> 16 & 255) / 255, g: (n2 >> 8 & 255) / 255, b: (n2 & 255) / 255, a };
|
|
623
|
+
}
|
|
624
|
+
m = /^rgba?\(([^)]+)\)$/.exec(s);
|
|
625
|
+
if (m) {
|
|
626
|
+
const [r, g, b, a = 1] = m[1].split(/[\s,/]+/).map(Number);
|
|
627
|
+
return { r: r / 255, g: g / 255, b: b / 255, a };
|
|
628
|
+
}
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
function cssColor(c) {
|
|
632
|
+
const p = parseColor(c);
|
|
633
|
+
if (!p) return "none";
|
|
634
|
+
return `rgba(${Math.round(p.r * 255)},${Math.round(p.g * 255)},${Math.round(p.b * 255)},${p.a})`;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// src/exporters/svg.js
|
|
638
|
+
var fontFamilyCss = (key) => `pw-${key}`;
|
|
639
|
+
var esc = (s) => String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
640
|
+
function imageHref(src, proxyBase = "") {
|
|
641
|
+
const s = String(src ?? "").trim();
|
|
642
|
+
if (/^data:image\/(png|jpe?g|gif|webp|bmp|svg\+xml)[;,]/i.test(s)) return s;
|
|
643
|
+
if (/^https?:\/\//i.test(s)) return `${proxyBase}/api/data/proxy?url=${encodeURIComponent(s)}`;
|
|
644
|
+
if (s && !/^[a-z][a-z0-9+.-]*:/i.test(s) && !s.startsWith("//") && !s.includes("\\")) return s;
|
|
645
|
+
return "";
|
|
646
|
+
}
|
|
647
|
+
var n = (v) => Math.round(v * 100) / 100;
|
|
648
|
+
var ANIM_CSS = "<style>@media (prefers-reduced-motion:no-preference){.pw-a{transform-box:fill-box;animation:pw-fade .55s ease-out both}.pw-a-growY{animation-name:pw-growY}.pw-a-growX{animation-name:pw-growX}.pw-a-scale{animation-name:pw-scale}@keyframes pw-fade{from{opacity:0}}@keyframes pw-growY{from{transform:scaleY(0)}}@keyframes pw-growX{from{transform:scaleX(0)}}@keyframes pw-scale{from{trans\
|
|
649
|
+
form:scale(0);opacity:0}}}</style>";
|
|
650
|
+
var ANIM_KINDS = /* @__PURE__ */ new Set(["growY", "growX", "scale", "fade"]);
|
|
651
|
+
var ANIM_ORIGIN = { bottom: "center bottom", top: "center top", left: "left center", right: "right center" };
|
|
652
|
+
function pageToSvg(page, w, h, { background = true, animate = false, box = void 0, outline = null, proxyBase = "" } = {}) {
|
|
653
|
+
const vb = box ? `${n(box.x)} ${n(box.y)} ${n(box.w)} ${n(box.h)}` : `0 0 ${n(w)} ${n(h)}`;
|
|
654
|
+
const out = [`<svg xmlns="http://www.w3.org/2000/svg" viewBox="${vb}" width="${n(w)}" height="${n(h)}" style="font-kerning:none;font-variant-ligatures:none;text-rendering:geometricPrecision">`];
|
|
655
|
+
if (background) out.push(`<rect width="${n(w)}" height="${n(h)}" fill="#ffffff"/>`);
|
|
656
|
+
let animated = false;
|
|
657
|
+
for (const it of page.items.flatMap((i) => i.t === "field" ? i.draw : [i])) {
|
|
658
|
+
const an = animate && it.anim && ANIM_KINDS.has(it.anim.k) ? it.anim : null;
|
|
659
|
+
if (an) {
|
|
660
|
+
if (!animated) {
|
|
661
|
+
out.push(ANIM_CSS);
|
|
662
|
+
animated = true;
|
|
663
|
+
}
|
|
664
|
+
const origin = ANIM_ORIGIN[an.o] || "center";
|
|
665
|
+
out.push(`<g class="pw-a pw-a-${an.k}" style="transform-origin:${origin};animation-delay:${Math.min(40, Math.max(0, Math.round(Number(an.i)) || 0)) * 25}ms">`);
|
|
666
|
+
}
|
|
667
|
+
if (it.rotate) out.push(`<g transform="rotate(${n(it.rotate.a)} ${n(it.rotate.cx)} ${n(it.rotate.cy)})">`);
|
|
668
|
+
switch (it.t) {
|
|
669
|
+
case "rect": {
|
|
670
|
+
const r = it.radius ? ` rx="${n(it.radius)}"` : "";
|
|
671
|
+
const stroke = it.stroke ? ` stroke="${cssColor(it.stroke)}" stroke-width="${n(it.strokeWidth || 1)}"${it.dash ? ` stroke-dasharray="${it.dash.join(" ")}"` : ""}` : "";
|
|
672
|
+
out.push(`<rect x="${n(it.x)}" y="${n(it.y)}" width="${n(it.w)}" height="${n(it.h)}"${r} fill="${cssColor(it.fill)}"${stroke}/>`);
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
case "ellipse": {
|
|
676
|
+
const stroke = it.stroke ? ` stroke="${cssColor(it.stroke)}" stroke-width="${n(it.strokeWidth || 1)}"` : "";
|
|
677
|
+
out.push(`<ellipse cx="${n(it.cx)}" cy="${n(it.cy)}" rx="${n(it.rx)}" ry="${n(it.ry)}" fill="${cssColor(it.fill)}"${stroke}/>`);
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
case "line":
|
|
681
|
+
out.push(`<line x1="${n(it.x1)}" y1="${n(it.y1)}" x2="${n(it.x2)}" y2="${n(it.y2)}" stroke="${cssColor(it.stroke)}" stroke-width="${n(it.strokeWidth)}"${it.dash ? ` stroke-dasharray="${it.dash.join(" ")}"` : ""}/>`);
|
|
682
|
+
break;
|
|
683
|
+
case "text": {
|
|
684
|
+
const d = outline ? textOutline(it, outline) : null;
|
|
685
|
+
if (d) {
|
|
686
|
+
out.push(`<path d="${d}" fill="${cssColor(it.color)}"/>`);
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
const paint = `font-size="${n(it.size)}" fill="${cssColor(it.color)}"`;
|
|
690
|
+
const attrs = `font-family="${fontFamilyCss(it.font)}" ${paint}`;
|
|
691
|
+
for (const l of it.lines) {
|
|
692
|
+
if (!l.runs) {
|
|
693
|
+
out.push(`<text x="${n(l.x)}" y="${n(l.y)}" ${attrs} xml:space="preserve">${esc(l.text)}</text>`);
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
for (const r of l.runs) {
|
|
697
|
+
if (!r.text.trim()) continue;
|
|
698
|
+
const font = `font-family="${fontFamilyCss(r.font)}" ${paint}`;
|
|
699
|
+
if (!r.shaped && !r.rtl) {
|
|
700
|
+
out.push(`<text x="${n(l.x + r.x)}" y="${n(l.y)}" ${font}>${esc(r.text)}</text>`);
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
const style = (r.rtl ? "direction:rtl;unicode-bidi:bidi-override;" : "") + (r.shaped ? "font-kerning:normal;font-variant-ligatures:normal" : "");
|
|
704
|
+
out.push(`<text x="${n(l.x + r.x + r.w / 2)}" y="${n(l.y)}" text-anchor="middle" ${font} style="${style}">${esc(r.text)}</text>`);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
case "path": {
|
|
710
|
+
const stroke = it.stroke ? ` stroke="${cssColor(it.stroke)}" stroke-width="${n(it.strokeWidth || 1)}" stroke-linecap="${it.lineCap || "round"}" stroke-linejoin="round"${it.dash ? ` stroke-dasharray="${it.dash.join(" ")}"` : ""}` : "";
|
|
711
|
+
out.push(`<path transform="translate(${n(it.x)} ${n(it.y)})" d="${esc(it.d)}" fill="${cssColor(it.fill)}"${stroke}/>`);
|
|
712
|
+
break;
|
|
713
|
+
}
|
|
714
|
+
case "link": {
|
|
715
|
+
const tip = it.tip ?? (it.action ? linkTitle(it.action) : "");
|
|
716
|
+
const title = tip ? `<title>${esc(tip)}</title>` : "";
|
|
717
|
+
const tag = it.d ? "path" : "rect";
|
|
718
|
+
const geo = it.d ? `transform="translate(${n(it.x - (it.x0 ?? it.x))} ${n(it.y - (it.y0 ?? it.y))})" d="${esc(it.d)}"` : `x="${n(it.x)}" y="${n(it.y)}" width="${n(it.w)}" height="${n(it.h)}"`;
|
|
719
|
+
if (!it.action) out.push(`<${tag} class="pw-tip" ${geo} fill="transparent">${title}</${tag}>`);
|
|
720
|
+
else out.push(`<${tag} class="pw-link" ${geo} fill="transparent" data-action="${esc(JSON.stringify(it.action))}" style="cursor:pointer">${title}</${tag}>`);
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
case "image": {
|
|
724
|
+
if (it.fit === "clip") out.push(`<svg x="${n(it.x)}" y="${n(it.y)}" width="${n(it.cw)}" height="${n(it.ch)}" overflow="hidden"><image x="0" y="0" width="${n(it.w)}" height="${n(it.h)}" href="${esc(imageHref(it.src, proxyBase))}" preserveAspectRatio="none"/></svg>`);
|
|
725
|
+
else {
|
|
726
|
+
const par = it.fit === "fill" ? "none" : it.fit === "cover" ? "xMidYMid slice" : "xMidYMid meet";
|
|
727
|
+
out.push(`<image x="${n(it.x)}" y="${n(it.y)}" width="${n(it.w)}" height="${n(it.h)}" href="${esc(imageHref(it.src, proxyBase))}" preserveAspectRatio="${par}"/>`);
|
|
728
|
+
}
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
if (it.rotate) out.push("</g>");
|
|
733
|
+
if (an) out.push("</g>");
|
|
734
|
+
}
|
|
735
|
+
out.push("</svg>");
|
|
736
|
+
return out.join("");
|
|
737
|
+
}
|
|
738
|
+
function linkTitle(a) {
|
|
739
|
+
if (a.type === "url") return a.href;
|
|
740
|
+
if (a.type === "drill") return `Open ${a.report}`;
|
|
741
|
+
if (a.type === "toggle") return "Show or hide details";
|
|
742
|
+
if (a.type === "sort") return "Sort";
|
|
743
|
+
if (a.type === "bookmark") return `Go to ${a.target}`;
|
|
744
|
+
if (a.type === "params") return "Filter the report";
|
|
745
|
+
return "";
|
|
746
|
+
}
|
|
747
|
+
function fontFaceCss(keys, base = "/fonts") {
|
|
748
|
+
return keys.map((k) => {
|
|
749
|
+
const file = fontFile(k), range = unicodeRange(k);
|
|
750
|
+
return `@font-face{font-family:"${fontFamilyCss(k)}";src:url("${base}/${file}")${file.endsWith(".ttf") ? ' format("truetype")' : ""};font-display:block${range ? `;unicode-range:${range}` : ""}}`;
|
|
751
|
+
}).join("\n");
|
|
752
|
+
}
|
|
753
|
+
function fontKeysOf(model) {
|
|
754
|
+
const keys = /* @__PURE__ */ new Set();
|
|
755
|
+
for (const pg of model?.pages || []) for (const it of pg.items.flatMap((i) => i.t === "field" ? [i, ...i.draw] : [i])) {
|
|
756
|
+
if (it.font) keys.add(it.font);
|
|
757
|
+
if (it.t === "text") for (const l of it.lines) for (const r of l.runs || []) keys.add(r.font);
|
|
758
|
+
}
|
|
759
|
+
return [...keys];
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// src/exporters/figures.js
|
|
763
|
+
var FIGURE_KINDS = /* @__PURE__ */ new Set(["image", "chart", "map", "barcode", "shape", "line", "sparkline", "databar", "bullet", "iconset", "rangebar"]);
|
|
764
|
+
function figures(items) {
|
|
765
|
+
const shared = items.some((it, i) => i > 0 && it.tag && it.tag === items[i - 1].tag);
|
|
766
|
+
const same = (a, b) => a === b || !!a && !!b && (a.id != null ? a.id === b.id : !shared && a.k === b.k && a.alt === b.alt && a.h === b.h);
|
|
767
|
+
const out = [];
|
|
768
|
+
let cur = null;
|
|
769
|
+
items.forEach((it, i) => {
|
|
770
|
+
const t = it.tag;
|
|
771
|
+
if (!t || !FIGURE_KINDS.has(t.k)) {
|
|
772
|
+
cur = null;
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
if (cur && same(cur.tag, t) && cur.last === i - 1) {
|
|
776
|
+
cur.items.push(it);
|
|
777
|
+
cur.last = i;
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
cur = { tag: t, items: [it], at: i, last: i };
|
|
781
|
+
out.push(cur);
|
|
782
|
+
});
|
|
783
|
+
for (const f of out) f.box = boxOf(f.items);
|
|
784
|
+
return out.filter((f) => f.box && f.box.w > 0.5 && f.box.h > 0.5);
|
|
785
|
+
}
|
|
786
|
+
function boxOf(items) {
|
|
787
|
+
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
788
|
+
const add = (x, y, rot) => {
|
|
789
|
+
if (rot) {
|
|
790
|
+
const a = rot.a * Math.PI / 180, dx = x - rot.cx, dy = y - rot.cy;
|
|
791
|
+
x = rot.cx + dx * Math.cos(a) - dy * Math.sin(a);
|
|
792
|
+
y = rot.cy + dx * Math.sin(a) + dy * Math.cos(a);
|
|
793
|
+
}
|
|
794
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) return;
|
|
795
|
+
if (x < x0) x0 = x;
|
|
796
|
+
if (x > x1) x1 = x;
|
|
797
|
+
if (y < y0) y0 = y;
|
|
798
|
+
if (y > y1) y1 = y;
|
|
799
|
+
};
|
|
800
|
+
const rect = (x, y, w, h, r) => {
|
|
801
|
+
add(x, y, r);
|
|
802
|
+
add(x + w, y, r);
|
|
803
|
+
add(x, y + h, r);
|
|
804
|
+
add(x + w, y + h, r);
|
|
805
|
+
};
|
|
806
|
+
for (const it of items) {
|
|
807
|
+
const r = it.rotate;
|
|
808
|
+
const sw = (it.stroke ? Number(it.strokeWidth) || 1 : 0) / 2;
|
|
809
|
+
switch (it.t) {
|
|
810
|
+
case "rect":
|
|
811
|
+
case "image":
|
|
812
|
+
rect(it.x - sw, it.y - sw, (it.cw ?? it.w) + 2 * sw, (it.ch ?? it.h) + 2 * sw, r);
|
|
813
|
+
break;
|
|
814
|
+
case "ellipse":
|
|
815
|
+
rect(it.cx - it.rx - sw, it.cy - it.ry - sw, 2 * (it.rx + sw), 2 * (it.ry + sw), r);
|
|
816
|
+
break;
|
|
817
|
+
case "line":
|
|
818
|
+
rect(Math.min(it.x1, it.x2) - sw, Math.min(it.y1, it.y2) - sw, Math.abs(it.x2 - it.x1) + 2 * sw, Math.abs(it.y2 - it.y1) + 2 * sw, r);
|
|
819
|
+
break;
|
|
820
|
+
case "text":
|
|
821
|
+
for (const l of it.lines) rect(l.x, l.y - it.size, l.w ?? l.text.length * it.size * 0.6, it.size * 1.3, r);
|
|
822
|
+
break;
|
|
823
|
+
case "path": {
|
|
824
|
+
const b = pathBox(it.d);
|
|
825
|
+
if (b) rect(it.x + b.x0 - sw, it.y + b.y0 - sw, b.x1 - b.x0 + 2 * sw, b.y1 - b.y0 + 2 * sw, r);
|
|
826
|
+
break;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return x0 === Infinity ? null : { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };
|
|
831
|
+
}
|
|
832
|
+
function pathBox(d) {
|
|
833
|
+
const toks = String(d).match(/[a-zA-Z]|-?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?/g) || [];
|
|
834
|
+
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
835
|
+
const add = (x, y) => {
|
|
836
|
+
if (x < x0) x0 = x;
|
|
837
|
+
if (x > x1) x1 = x;
|
|
838
|
+
if (y < y0) y0 = y;
|
|
839
|
+
if (y > y1) y1 = y;
|
|
840
|
+
};
|
|
841
|
+
let cx = 0, cy = 0, sx = 0, sy = 0, cmd = "M", i = 0;
|
|
842
|
+
const num = () => Number(toks[i++]);
|
|
843
|
+
const ARGS = { M: 2, L: 2, H: 1, V: 1, C: 6, S: 4, Q: 4, T: 2, A: 7, Z: 0 };
|
|
844
|
+
let guard = 0;
|
|
845
|
+
while (i < toks.length && guard++ < 1e6) {
|
|
846
|
+
if (/[a-zA-Z]/.test(toks[i])) cmd = toks[i++];
|
|
847
|
+
const C = cmd.toUpperCase(), rel = cmd !== C, n2 = ARGS[C];
|
|
848
|
+
if (n2 === void 0) break;
|
|
849
|
+
if (C === "Z") {
|
|
850
|
+
cx = sx;
|
|
851
|
+
cy = sy;
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
if (i + n2 > toks.length) break;
|
|
855
|
+
const a = Array.from({ length: n2 }, num);
|
|
856
|
+
if (a.some((v) => !Number.isFinite(v))) break;
|
|
857
|
+
const ox = rel ? cx : 0, oy = rel ? cy : 0;
|
|
858
|
+
if (C === "H") {
|
|
859
|
+
cx = a[0] + ox;
|
|
860
|
+
add(cx, cy);
|
|
861
|
+
} else if (C === "V") {
|
|
862
|
+
cy = a[0] + oy;
|
|
863
|
+
add(cx, cy);
|
|
864
|
+
} else if (C === "A") {
|
|
865
|
+
const ex = a[5] + ox, ey = a[6] + oy;
|
|
866
|
+
add(cx, cy);
|
|
867
|
+
add(ex, ey);
|
|
868
|
+
for (const [px, py] of arcExtremes(cx, cy, a[0], a[1], a[2], a[3], a[4], ex, ey)) add(px, py);
|
|
869
|
+
cx = ex;
|
|
870
|
+
cy = ey;
|
|
871
|
+
} else {
|
|
872
|
+
for (let k = 0; k < n2; k += 2) add(a[k] + ox, a[k + 1] + oy);
|
|
873
|
+
cx = a[n2 - 2] + ox;
|
|
874
|
+
cy = a[n2 - 1] + oy;
|
|
875
|
+
if (C === "M") {
|
|
876
|
+
sx = cx;
|
|
877
|
+
sy = cy;
|
|
878
|
+
cmd = rel ? "l" : "L";
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
return x0 === Infinity ? null : { x0, y0, x1, y1 };
|
|
883
|
+
}
|
|
884
|
+
function arcExtremes(x1, y1, rx, ry, phi, large, sweep, x2, y2) {
|
|
885
|
+
rx = Math.abs(rx);
|
|
886
|
+
ry = Math.abs(ry);
|
|
887
|
+
if (!rx || !ry || x1 === x2 && y1 === y2) return [];
|
|
888
|
+
const f = phi % 360 * Math.PI / 180, cf = Math.cos(f), sf = Math.sin(f);
|
|
889
|
+
const dx = (x1 - x2) / 2, dy = (y1 - y2) / 2;
|
|
890
|
+
const x1p = cf * dx + sf * dy, y1p = -sf * dx + cf * dy;
|
|
891
|
+
const lam = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
|
|
892
|
+
if (lam > 1) {
|
|
893
|
+
rx *= Math.sqrt(lam);
|
|
894
|
+
ry *= Math.sqrt(lam);
|
|
895
|
+
}
|
|
896
|
+
const num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
|
|
897
|
+
const k = (large !== sweep ? 1 : -1) * Math.sqrt(Math.max(0, num / (rx * rx * y1p * y1p + ry * ry * x1p * x1p)));
|
|
898
|
+
const cxp = k * rx * y1p / ry, cyp = -k * ry * x1p / rx;
|
|
899
|
+
const cx = cf * cxp - sf * cyp + (x1 + x2) / 2, cy = sf * cxp + cf * cyp + (y1 + y2) / 2;
|
|
900
|
+
if (Math.abs(sf) > 1e-9) {
|
|
901
|
+
const R = Math.max(rx, ry);
|
|
902
|
+
return [[cx - R, cy - R], [cx + R, cy + R]];
|
|
903
|
+
}
|
|
904
|
+
const ang = (ux, uy) => Math.atan2(uy, ux);
|
|
905
|
+
const t1 = ang((x1p - cxp) / rx, (y1p - cyp) / ry);
|
|
906
|
+
let dt = ang((-x1p - cxp) / rx, (-y1p - cyp) / ry) - t1;
|
|
907
|
+
if (sweep && dt < 0) dt += 2 * Math.PI;
|
|
908
|
+
else if (!sweep && dt > 0) dt -= 2 * Math.PI;
|
|
909
|
+
const out = [];
|
|
910
|
+
for (let q = -4; q <= 4; q++) {
|
|
911
|
+
const t = q * Math.PI / 2, rel = dt >= 0 ? t - t1 : t1 - t;
|
|
912
|
+
const nrm = (rel % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI);
|
|
913
|
+
if (nrm <= Math.abs(dt) + 1e-9) out.push([cx + rx * Math.cos(t) * cf, cy + ry * Math.sin(t)]);
|
|
914
|
+
}
|
|
915
|
+
return out;
|
|
916
|
+
}
|
|
917
|
+
function textOutline(it, fontOf) {
|
|
918
|
+
let d = "";
|
|
919
|
+
const glyph = (f, g, x, y, size) => {
|
|
920
|
+
const s = size / f.unitsPerEm;
|
|
921
|
+
const p = g.path;
|
|
922
|
+
for (const c of p.commands) {
|
|
923
|
+
const a = [];
|
|
924
|
+
for (let k = 0; k < c.args.length; k += 2) a.push(r2(x + c.args[k] * s), r2(y - c.args[k + 1] * s));
|
|
925
|
+
d += { moveTo: "M", lineTo: "L", quadraticCurveTo: "Q", bezierCurveTo: "C", closePath: "Z" }[c.command] + a.join(" ");
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
for (const l of it.lines) {
|
|
929
|
+
if (!l.runs) {
|
|
930
|
+
const f = fontOf(it.font);
|
|
931
|
+
if (!f) return null;
|
|
932
|
+
let x = l.x;
|
|
933
|
+
for (const ch of l.text.replace(/\t/g, " ")) {
|
|
934
|
+
const g = f.glyphForCodePoint(
|
|
935
|
+
/** @type {number} */
|
|
936
|
+
ch.codePointAt(0)
|
|
937
|
+
);
|
|
938
|
+
glyph(f, g, x, l.y, it.size);
|
|
939
|
+
x += g.advanceWidth * it.size / f.unitsPerEm;
|
|
940
|
+
}
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
for (const r of l.runs) {
|
|
944
|
+
const f = fontOf(r.font);
|
|
945
|
+
if (!f) return null;
|
|
946
|
+
const gs = r.glyphs || [];
|
|
947
|
+
for (let k = 0; k < gs.length; k += 4) glyph(f, f.getGlyph(gs[k]), l.x + r.x + gs[k + 1], l.y - gs[k + 2], it.size);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return d;
|
|
951
|
+
}
|
|
952
|
+
var r2 = (v) => Math.round(v * 100) / 100;
|
|
953
|
+
function figureSvg(fig, fontStore) {
|
|
954
|
+
const fontOf = (k) => {
|
|
955
|
+
try {
|
|
956
|
+
return fontStore?.get(k) || null;
|
|
957
|
+
} catch {
|
|
958
|
+
return null;
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
return pageToSvg({ items: fig.items.filter((i) => i.t !== "link" && i.t !== "bookmark") }, fig.box.w, fig.box.h, { background: false, box: fig.box, outline: fontStore ? fontOf : null });
|
|
962
|
+
}
|
|
963
|
+
async function rasterize(svg, w, h, opt = {}) {
|
|
964
|
+
const scale = opt.scale || 2;
|
|
965
|
+
const W = Math.max(1, Math.min(8e3, Math.round(w * scale))), H = Math.max(1, Math.min(8e3, Math.round(h * scale)));
|
|
966
|
+
try {
|
|
967
|
+
if (opt.rasterize) return await opt.rasterize(svg, w, h, scale);
|
|
968
|
+
if (typeof document !== "undefined" && typeof Image !== "undefined") {
|
|
969
|
+
const url = URL.createObjectURL(new Blob([svg], { type: "image/svg+xml" }));
|
|
970
|
+
try {
|
|
971
|
+
const img = new Image();
|
|
972
|
+
img.src = url;
|
|
973
|
+
await img.decode();
|
|
974
|
+
const c = document.createElement("canvas");
|
|
975
|
+
c.width = W;
|
|
976
|
+
c.height = H;
|
|
977
|
+
c.getContext("2d")?.drawImage(img, 0, 0, W, H);
|
|
978
|
+
const blob = await new Promise((res) => c.toBlob(res, "image/png"));
|
|
979
|
+
return blob ? new Uint8Array(await /** @type {Blob} */
|
|
980
|
+
blob.arrayBuffer()) : null;
|
|
981
|
+
} finally {
|
|
982
|
+
URL.revokeObjectURL(url);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
if (
|
|
986
|
+
/** @type {any} */
|
|
987
|
+
globalThis.process?.versions?.node
|
|
988
|
+
) {
|
|
989
|
+
const name = "sharp";
|
|
990
|
+
const sharp = (await import(
|
|
991
|
+
/* @vite-ignore */
|
|
992
|
+
/* webpackIgnore: true */
|
|
993
|
+
/* turbopackIgnore: true */
|
|
994
|
+
name
|
|
995
|
+
)).default;
|
|
996
|
+
return new Uint8Array(await sharp(Buffer.from(svg), { density: 72 * scale }).resize(W, H, { fit: "fill" }).png().toBuffer());
|
|
997
|
+
}
|
|
998
|
+
} catch {
|
|
999
|
+
}
|
|
1000
|
+
return null;
|
|
1001
|
+
}
|
|
1002
|
+
async function figurePicture(fig, opt = {}) {
|
|
1003
|
+
const bytesOf = async (src) => {
|
|
1004
|
+
try {
|
|
1005
|
+
const b = /^data:/i.test(src) ? dataUriBytesOf(src) : opt.fetchImage ? await opt.fetchImage(src) : null;
|
|
1006
|
+
const info = b && b.length <= IMAGE_LIMITS.bytes ? imageInfoOf(b) : null;
|
|
1007
|
+
return info && ["png", "jpeg", "svg"].includes(info.type) ? { b, info } : null;
|
|
1008
|
+
} catch {
|
|
1009
|
+
return null;
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
const items = [];
|
|
1013
|
+
for (const it of fig.items) {
|
|
1014
|
+
if (it.t !== "image") {
|
|
1015
|
+
items.push(it);
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
const got = await bytesOf(it.src);
|
|
1019
|
+
if (!got) continue;
|
|
1020
|
+
const alone = fig.items.every((i) => i === it || i.t === "link" || i.t === "bookmark");
|
|
1021
|
+
if (alone && (got.info.type === "png" || got.info.type === "jpeg") && (it.fit === "fill" || (it.fit || "contain") === "contain" && got.info.w > 0)) {
|
|
1022
|
+
if (it.fit === "fill") return { [got.info.type]: got.b, w: it.w, h: it.h, x: it.x, y: it.y };
|
|
1023
|
+
const s = Math.min(it.w / got.info.w, it.h / got.info.h);
|
|
1024
|
+
const w = got.info.w * s, h = got.info.h * s;
|
|
1025
|
+
return { [got.info.type]: got.b, w, h, x: it.x + (it.w - w) / 2, y: it.y + (it.h - h) / 2 };
|
|
1026
|
+
}
|
|
1027
|
+
items.push({ ...it, src: `data:image/${got.info.type === "svg" ? "svg+xml" : got.info.type};base64,${b64(got.b)}` });
|
|
1028
|
+
}
|
|
1029
|
+
const svg = figureSvg({ items, box: fig.box }, opt.fontStore);
|
|
1030
|
+
const png = await rasterize(svg, fig.box.w, fig.box.h, opt);
|
|
1031
|
+
return { svg, png, w: fig.box.w, h: fig.box.h, x: fig.box.x, y: fig.box.y };
|
|
1032
|
+
}
|
|
1033
|
+
function b64(u8) {
|
|
1034
|
+
if (typeof Buffer !== "undefined") return Buffer.from(u8).toString("base64");
|
|
1035
|
+
let s = "";
|
|
1036
|
+
for (let i = 0; i < u8.length; i += 32768) s += String.fromCharCode.apply(
|
|
1037
|
+
null,
|
|
1038
|
+
/** @type {any} */
|
|
1039
|
+
u8.subarray(i, i + 32768)
|
|
1040
|
+
);
|
|
1041
|
+
return btoa(s);
|
|
1042
|
+
}
|
|
1043
|
+
var hexOrNull = (c) => {
|
|
1044
|
+
const s = String(c || "").trim();
|
|
1045
|
+
const m = /^#?([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(s) || /^#?([0-9a-f]{3})$/i.exec(s);
|
|
1046
|
+
if (!m || m[2] === "00") return null;
|
|
1047
|
+
return (m[1].length === 3 ? [...m[1]].map((x) => x + x).join("") : m[1]).toUpperCase();
|
|
1048
|
+
};
|
|
1049
|
+
var altOf = (tag) => tag?.alt || { chart: "Chart", image: "Picture", map: "Map", barcode: "Barcode", shape: "Shape", line: "Line", sparkline: "Sparkline", databar: "Data bar", bullet: "Bullet chart", iconset: "Status icon", rangebar: "Range bar" }[tag?.k] || "Graphic";
|
|
1050
|
+
function boxDecor(items, i) {
|
|
1051
|
+
const it = items[i], c = it.clip;
|
|
1052
|
+
if (!c) return null;
|
|
1053
|
+
const near = (a, b) => Math.abs(a - b) < 0.05;
|
|
1054
|
+
const isBox = (r) => r && r.t === "rect" && !r.tag && near(r.x, c.x) && near(r.y, c.y) && near(r.w, c.w) && near(r.h, c.h);
|
|
1055
|
+
let fill = null, radius = 0;
|
|
1056
|
+
for (let k = i - 1; k >= Math.max(0, i - 2); k--) if (isBox(items[k]) && items[k].fill) {
|
|
1057
|
+
fill = hexOrNull(items[k].fill);
|
|
1058
|
+
radius = items[k].radius || 0;
|
|
1059
|
+
break;
|
|
1060
|
+
}
|
|
1061
|
+
const sides = {};
|
|
1062
|
+
for (let k = i + 1; k < items.length && k <= i + 6 + 2 * it.lines.length; k++) {
|
|
1063
|
+
const o = items[k];
|
|
1064
|
+
if (o.tag || o.art !== it.art) break;
|
|
1065
|
+
if (o.t === "rect" && isBox(o) && o.stroke) {
|
|
1066
|
+
for (const s of ["top", "right", "bottom", "left"]) sides[s] = o;
|
|
1067
|
+
radius = o.radius || radius;
|
|
1068
|
+
break;
|
|
1069
|
+
}
|
|
1070
|
+
if (o.t !== "line") break;
|
|
1071
|
+
if (near(o.y1, o.y2) && near(o.y1, c.y) && near(Math.min(o.x1, o.x2), c.x)) sides.top = o;
|
|
1072
|
+
else if (near(o.y1, o.y2) && near(o.y1, c.y + c.h) && near(Math.min(o.x1, o.x2), c.x)) sides.bottom = o;
|
|
1073
|
+
else if (near(o.x1, o.x2) && near(o.x1, c.x) && near(Math.min(o.y1, o.y2), c.y)) sides.left = o;
|
|
1074
|
+
else if (near(o.x1, o.x2) && near(o.x1, c.x + c.w) && near(Math.min(o.y1, o.y2), c.y)) sides.right = o;
|
|
1075
|
+
}
|
|
1076
|
+
return fill || Object.keys(sides).length ? { fill, radius, sides } : null;
|
|
1077
|
+
}
|
|
1078
|
+
function cellVisuals(items, used, tables) {
|
|
1079
|
+
const out = /* @__PURE__ */ new Map();
|
|
1080
|
+
if (!tables.size) return out;
|
|
1081
|
+
const geo = [...tables.entries()].map(([key, b]) => {
|
|
1082
|
+
const rows = /* @__PURE__ */ new Map();
|
|
1083
|
+
let left = Infinity;
|
|
1084
|
+
for (const it of b.cells) {
|
|
1085
|
+
const c = it.clip;
|
|
1086
|
+
if (!c) continue;
|
|
1087
|
+
if (!rows.has(it.cell.row)) rows.set(it.cell.row, { y: c.y, h: c.h });
|
|
1088
|
+
left = Math.min(left, c.x - it.cell.cols.slice(0, it.cell.col).reduce((s, w) => s + w, 0));
|
|
1089
|
+
}
|
|
1090
|
+
const cols = b.cells[0].cell.cols;
|
|
1091
|
+
const edges = [left];
|
|
1092
|
+
for (const w of cols) edges.push(edges[edges.length - 1] + w);
|
|
1093
|
+
return { key, rows: [...rows.entries()], left, cols, edges, right: edges[edges.length - 1] };
|
|
1094
|
+
});
|
|
1095
|
+
const near = (a, b) => Math.abs(a - b) < 0.05;
|
|
1096
|
+
items.forEach((it, i) => {
|
|
1097
|
+
if (used.has(i) || it.tag || it.art || !["path", "ellipse", "rect"].includes(it.t)) return;
|
|
1098
|
+
const bx = boxOf([it]);
|
|
1099
|
+
if (!bx) return;
|
|
1100
|
+
const cx = bx.x + bx.w / 2, cy = bx.y + bx.h / 2;
|
|
1101
|
+
for (const g of geo) {
|
|
1102
|
+
if (cx < g.left || cx > g.right) continue;
|
|
1103
|
+
const row = g.rows.find(([, r3]) => cy >= r3.y && cy <= r3.y + r3.h);
|
|
1104
|
+
if (!row) continue;
|
|
1105
|
+
const [rid, r] = row;
|
|
1106
|
+
if (it.t === "rect" && g.edges.some((e) => near(e, it.x)) && g.edges.some((e) => near(e, it.x + it.w))) return;
|
|
1107
|
+
let col = 0, x = g.left;
|
|
1108
|
+
while (col < g.cols.length - 1 && cx > x + g.cols[col]) x += g.cols[col++];
|
|
1109
|
+
const m = out.get(g.key) || out.set(g.key, /* @__PURE__ */ new Map()).get(g.key);
|
|
1110
|
+
const k = `${rid}|${col}`;
|
|
1111
|
+
const v = m.get(k) || m.set(k, { items: [], box: null }).get(k);
|
|
1112
|
+
v.items.push(it);
|
|
1113
|
+
used.add(i);
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
for (const m of out.values()) for (const v of m.values()) v.box = boxOf(v.items);
|
|
1118
|
+
return out;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
export {
|
|
1122
|
+
OPTIONAL_KEYS,
|
|
1123
|
+
FONT_FAMILIES,
|
|
1124
|
+
BUNDLED_FAMILIES,
|
|
1125
|
+
DEFAULT_FAMILY,
|
|
1126
|
+
ALL_FONT_KEYS,
|
|
1127
|
+
CORE_FONT_KEY,
|
|
1128
|
+
PRELOAD_KEYS,
|
|
1129
|
+
SHAPER,
|
|
1130
|
+
fontFile,
|
|
1131
|
+
setCustomFonts,
|
|
1132
|
+
resolveFontKey,
|
|
1133
|
+
fontCss,
|
|
1134
|
+
allFontKeys,
|
|
1135
|
+
fallbackKeys,
|
|
1136
|
+
fallbackKey,
|
|
1137
|
+
fontsForDef,
|
|
1138
|
+
fontsFor,
|
|
1139
|
+
FontStore,
|
|
1140
|
+
IMAGE_LIMITS,
|
|
1141
|
+
PX,
|
|
1142
|
+
dataUriBytes,
|
|
1143
|
+
dataUriBytesOf,
|
|
1144
|
+
svgLength,
|
|
1145
|
+
xmlTags,
|
|
1146
|
+
xmlAttrs,
|
|
1147
|
+
svgRootAttrs,
|
|
1148
|
+
imageInfoOf,
|
|
1149
|
+
imageInfo,
|
|
1150
|
+
resolveImageSrc,
|
|
1151
|
+
parseColor,
|
|
1152
|
+
figures,
|
|
1153
|
+
boxOf,
|
|
1154
|
+
figureSvg,
|
|
1155
|
+
rasterize,
|
|
1156
|
+
figurePicture,
|
|
1157
|
+
hexOrNull,
|
|
1158
|
+
altOf,
|
|
1159
|
+
boxDecor,
|
|
1160
|
+
cellVisuals,
|
|
1161
|
+
fontFamilyCss,
|
|
1162
|
+
pageToSvg,
|
|
1163
|
+
linkTitle,
|
|
1164
|
+
fontFaceCss,
|
|
1165
|
+
fontKeysOf
|
|
1166
|
+
};
|
|
1167
|
+
//# sourceMappingURL=chunk-3D43XGIN.js.map
|