@sportsdataverse/sdvplot 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/NOTICE.md +109 -0
- package/README.md +984 -2
- package/dist/_web-D7Aa5Rd8.d.ts +69 -0
- package/dist/aaf-GJ3SH34T.js +13 -0
- package/dist/aaf-HCYN5T6Y.js +37 -0
- package/dist/aaf-ZAE5KS2Y.js +13 -0
- package/dist/ahl-E66MDVCU.js +66 -0
- package/dist/ahl-NPQQWZEV.js +249 -0
- package/dist/ahl-QRDU73XX.js +309 -0
- package/dist/axis-CraWQRUJ.d.ts +49 -0
- package/dist/bins.d.ts +174 -0
- package/dist/bins.js +4 -0
- package/dist/bins.js.map +1 -0
- package/dist/cfb-F42SVS4X.js +5000 -0
- package/dist/cfb-P3SHEYRZ.js +692 -0
- package/dist/cfb-WXRZANLP.js +10226 -0
- package/dist/chartjs-surface.d.ts +42 -0
- package/dist/chartjs-surface.js +65 -0
- package/dist/chartjs-surface.js.map +1 -0
- package/dist/chartjs.d.ts +162 -0
- package/dist/chartjs.js +342 -0
- package/dist/chartjs.js.map +1 -0
- package/dist/chunk-4C3GX6QO.js +92 -0
- package/dist/chunk-4C3GX6QO.js.map +1 -0
- package/dist/chunk-57WFSI4Q.js +128 -0
- package/dist/chunk-57WFSI4Q.js.map +1 -0
- package/dist/chunk-7ULOCCFL.js +313 -0
- package/dist/chunk-7ULOCCFL.js.map +1 -0
- package/dist/chunk-AMXASQHT.js +120 -0
- package/dist/chunk-AMXASQHT.js.map +1 -0
- package/dist/chunk-B2MP37SR.js +175 -0
- package/dist/chunk-B2MP37SR.js.map +1 -0
- package/dist/chunk-C2CDY5OG.js +137 -0
- package/dist/chunk-C2CDY5OG.js.map +1 -0
- package/dist/chunk-DYMUVE6K.js +127 -0
- package/dist/chunk-DYMUVE6K.js.map +1 -0
- package/dist/chunk-E7G7VSLV.js +43 -0
- package/dist/chunk-E7G7VSLV.js.map +1 -0
- package/dist/chunk-H2C3FMMA.js +300 -0
- package/dist/chunk-H2C3FMMA.js.map +1 -0
- package/dist/chunk-J4P4HDBF.js +175 -0
- package/dist/chunk-J4P4HDBF.js.map +1 -0
- package/dist/chunk-J6R2LEJ7.js +58 -0
- package/dist/chunk-J6R2LEJ7.js.map +1 -0
- package/dist/chunk-M3XD2OSN.js +132 -0
- package/dist/chunk-M3XD2OSN.js.map +1 -0
- package/dist/chunk-OIILARXI.js +30 -0
- package/dist/chunk-OIILARXI.js.map +1 -0
- package/dist/chunk-RPVVJY74.js +170 -0
- package/dist/chunk-RPVVJY74.js.map +1 -0
- package/dist/chunk-UL5M6KDE.js +205 -0
- package/dist/chunk-UL5M6KDE.js.map +1 -0
- package/dist/chunk-WQT4IVLX.js +10 -0
- package/dist/chunk-WQT4IVLX.js.map +1 -0
- package/dist/chunk-ZAEQXFJM.js +556 -0
- package/dist/chunk-ZAEQXFJM.js.map +1 -0
- package/dist/cricket-KPCYG2HL.js +493 -0
- package/dist/cricket-NDXLE25B.js +116 -0
- package/dist/cricket-YUUF7P6M.js +160 -0
- package/dist/cytoscape.d.ts +188 -0
- package/dist/cytoscape.js +219 -0
- package/dist/cytoscape.js.map +1 -0
- package/dist/d3.d.ts +234 -0
- package/dist/d3.js +198 -0
- package/dist/d3.js.map +1 -0
- package/dist/deckgl.d.ts +232 -0
- package/dist/deckgl.js +197 -0
- package/dist/deckgl.js.map +1 -0
- package/dist/echarts.d.ts +121 -0
- package/dist/echarts.js +228 -0
- package/dist/echarts.js.map +1 -0
- package/dist/echl-3CR4RGFC.js +241 -0
- package/dist/echl-NPC32C23.js +107 -0
- package/dist/echl-PI33DGJC.js +64 -0
- package/dist/export.d.ts +130 -0
- package/dist/export.js +258 -0
- package/dist/export.js.map +1 -0
- package/dist/g2.d.ts +251 -0
- package/dist/g2.js +102 -0
- package/dist/g2.js.map +1 -0
- package/dist/headshots-B2J_LOO8.d.ts +40 -0
- package/dist/index.d.ts +183 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/interact.d.ts +567 -0
- package/dist/interact.js +809 -0
- package/dist/interact.js.map +1 -0
- package/dist/lightweight-charts.d.ts +217 -0
- package/dist/lightweight-charts.js +155 -0
- package/dist/lightweight-charts.js.map +1 -0
- package/dist/maplibre.d.ts +271 -0
- package/dist/maplibre.js +175 -0
- package/dist/maplibre.js.map +1 -0
- package/dist/marks-DH-tQIP-.d.ts +75 -0
- package/dist/mbb-3TRLVW3H.js +3619 -0
- package/dist/mbb-DQUHCHZP.js +5111 -0
- package/dist/mbb-PRAIO2P5.js +371 -0
- package/dist/milb-6IQ6NCEW.js +291 -0
- package/dist/milb-EYY5MD4T.js +1752 -0
- package/dist/milb-EYZ4SZLI.js +797 -0
- package/dist/mlb-4PBUJSVQ.js +35 -0
- package/dist/mlb-FHPY3GQJ.js +701 -0
- package/dist/mlb-REVGL3N2.js +665 -0
- package/dist/mui.d.ts +338 -0
- package/dist/mui.js +184 -0
- package/dist/mui.js.map +1 -0
- package/dist/nba-ATT775GS.js +485 -0
- package/dist/nba-LKM4UHVU.js +482 -0
- package/dist/nba-YX7PXM3R.js +35 -0
- package/dist/nbagl-3WGZCZ4S.js +37 -0
- package/dist/nbagl-LP4XJRAL.js +229 -0
- package/dist/nbagl-VTTOU7LN.js +499 -0
- package/dist/ncaa_baseball-UV75LNVR.js +2670 -0
- package/dist/ncaa_baseball-VD5J5C72.js +419 -0
- package/dist/ncaa_baseball-YRBS32D6.js +4592 -0
- package/dist/ncaa_mhockey-IMYHS7QS.js +1033 -0
- package/dist/ncaa_mhockey-TXTKWHE3.js +115 -0
- package/dist/ncaa_mhockey-U3MFW5VT.js +663 -0
- package/dist/ncaa_softball-6WR6KS64.js +420 -0
- package/dist/ncaa_softball-RZCTIE56.js +2654 -0
- package/dist/ncaa_softball-VOD4PVGY.js +4599 -0
- package/dist/ncaa_whockey-6XXGUTBK.js +607 -0
- package/dist/ncaa_whockey-AKKYKY67.js +53 -0
- package/dist/ncaa_whockey-KBEKXFBB.js +293 -0
- package/dist/nfl-BDB2HYAR.js +625 -0
- package/dist/nfl-EHYHDLJD.js +37 -0
- package/dist/nfl-VF4NJCHY.js +514 -0
- package/dist/nfl_gsis-UY64JO37.js +4 -0
- package/dist/nhl-6WPHME7H.js +511 -0
- package/dist/nhl-EMIJFZHY.js +884 -0
- package/dist/nhl-ISFUKRFU.js +37 -0
- package/dist/nivo.d.ts +193 -0
- package/dist/nivo.js +8 -0
- package/dist/nivo.js.map +1 -0
- package/dist/ohl-2FEM554G.js +113 -0
- package/dist/ohl-C5Q4W6JB.js +32 -0
- package/dist/ohl-TQ4J46VC.js +66 -0
- package/dist/perspective.d.ts +198 -0
- package/dist/perspective.js +171 -0
- package/dist/perspective.js.map +1 -0
- package/dist/phf-H7POU2KW.js +32 -0
- package/dist/phf-K5IFMZUM.js +13 -0
- package/dist/phf-TWJI3BOW.js +20 -0
- package/dist/placement-UWkqUzpa.d.ts +32 -0
- package/dist/plot.d.ts +451 -0
- package/dist/plot.js +712 -0
- package/dist/plot.js.map +1 -0
- package/dist/plotly.d.ts +129 -0
- package/dist/plotly.js +360 -0
- package/dist/plotly.js.map +1 -0
- package/dist/pwhl-OX3BO4JI.js +53 -0
- package/dist/pwhl-W44OC3C6.js +17 -0
- package/dist/pwhl-ZJGG3H7W.js +23 -0
- package/dist/qmjhl-5HISEHLL.js +35 -0
- package/dist/qmjhl-DA3UHKE5.js +125 -0
- package/dist/qmjhl-YG4BOQPF.js +614 -0
- package/dist/react.d.ts +377 -0
- package/dist/react.js +522 -0
- package/dist/react.js.map +1 -0
- package/dist/recharts.d.ts +162 -0
- package/dist/recharts.js +8 -0
- package/dist/recharts.js.map +1 -0
- package/dist/resolve-CQW-jzvr.d.ts +28 -0
- package/dist/selection-D4T3PkRt.d.ts +140 -0
- package/dist/shadcn.d.ts +83 -0
- package/dist/shadcn.js +88 -0
- package/dist/shadcn.js.map +1 -0
- package/dist/shots.d.ts +364 -0
- package/dist/shots.js +86 -0
- package/dist/shots.js.map +1 -0
- package/dist/soccer-7BH3ZLVL.js +7898 -0
- package/dist/soccer-MRU25OGE.js +2636 -0
- package/dist/soccer-PF4IPPVA.js +12342 -0
- package/dist/surface-TR7YFJW3.js +4 -0
- package/dist/surface-TR7YFJW3.js.map +1 -0
- package/dist/surfaces-MIG7HOFU.js +6 -0
- package/dist/surfaces-MIG7HOFU.js.map +1 -0
- package/dist/surfaces-W-odWLpT.d.ts +47 -0
- package/dist/svelte/HeadshotMark.svelte +25 -0
- package/dist/svelte/HeadshotMark.svelte.d.ts +10 -0
- package/dist/svelte/HeadshotMark.svelte.d.ts.map +1 -0
- package/dist/svelte/LogoMark.svelte +25 -0
- package/dist/svelte/LogoMark.svelte.d.ts +10 -0
- package/dist/svelte/LogoMark.svelte.d.ts.map +1 -0
- package/dist/svelte/Marks.svelte +25 -0
- package/dist/svelte/Marks.svelte.d.ts +10 -0
- package/dist/svelte/Marks.svelte.d.ts.map +1 -0
- package/dist/svelte/Surface.svelte +29 -0
- package/dist/svelte/Surface.svelte.d.ts +9 -0
- package/dist/svelte/Surface.svelte.d.ts.map +1 -0
- package/dist/svelte/SurfaceBackdrop.svelte +26 -0
- package/dist/svelte/SurfaceBackdrop.svelte.d.ts +11 -0
- package/dist/svelte/SurfaceBackdrop.svelte.d.ts.map +1 -0
- package/dist/svelte/index.d.ts +13 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +11 -0
- package/dist/svelte-core.d.ts +139 -0
- package/dist/svelte-core.js +123 -0
- package/dist/svelte-core.js.map +1 -0
- package/dist/tanstack.d.ts +240 -0
- package/dist/tanstack.js +110 -0
- package/dist/tanstack.js.map +1 -0
- package/dist/testing.d.ts +75 -0
- package/dist/testing.js +239 -0
- package/dist/testing.js.map +1 -0
- package/dist/three.d.ts +223 -0
- package/dist/three.js +50 -0
- package/dist/three.js.map +1 -0
- package/dist/tiers-BN4md-GB.d.ts +65 -0
- package/dist/types-C7n6gJF3.d.ts +62 -0
- package/dist/ufl-7VLZTLTT.js +143 -0
- package/dist/ufl-CCMBLPSW.js +100 -0
- package/dist/ufl-F2BJRXME.js +16 -0
- package/dist/unovis.d.ts +127 -0
- package/dist/unovis.js +108 -0
- package/dist/unovis.js.map +1 -0
- package/dist/uplot.d.ts +120 -0
- package/dist/uplot.js +110 -0
- package/dist/uplot.js.map +1 -0
- package/dist/usfl-4CR5RMZJ.js +32 -0
- package/dist/usfl-LD7HP2YK.js +14 -0
- package/dist/usfl-VU4YSJQD.js +19 -0
- package/dist/ushl-CPG2LB4L.js +73 -0
- package/dist/ushl-PQL6HJAD.js +88 -0
- package/dist/ushl-ZT2ATPHK.js +22 -0
- package/dist/vega.d.ts +85 -0
- package/dist/vega.js +362 -0
- package/dist/vega.js.map +1 -0
- package/dist/victory.d.ts +107 -0
- package/dist/victory.js +38 -0
- package/dist/victory.js.map +1 -0
- package/dist/wbb-3KHCDYTK.js +5087 -0
- package/dist/wbb-BMX53Q3Q.js +369 -0
- package/dist/wbb-QNRN2CJJ.js +3620 -0
- package/dist/whl-4ATYZWUB.js +97 -0
- package/dist/whl-RVFPL6MH.js +79 -0
- package/dist/whl-Y7ASKYMM.js +28 -0
- package/dist/wnba-FWX5SFVI.js +232 -0
- package/dist/wnba-GX6HZR4B.js +220 -0
- package/dist/wnba-NTMWIPP6.js +20 -0
- package/dist/xfl-43R2JMJK.js +30 -0
- package/dist/xfl-H3BXQPZZ.js +16 -0
- package/dist/xfl-XON7A7FI.js +69 -0
- package/package.json +366 -3
package/dist/export.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { roundHalfEven, onColor } from './chunk-J6R2LEJ7.js';
|
|
2
|
+
import { InputError, OptionalDependencyError, warn, DownloadError } from './chunk-E7G7VSLV.js';
|
|
3
|
+
|
|
4
|
+
// src/export/index.ts
|
|
5
|
+
var GRAVITY = [
|
|
6
|
+
"center",
|
|
7
|
+
"north",
|
|
8
|
+
"south",
|
|
9
|
+
"east",
|
|
10
|
+
"west",
|
|
11
|
+
"northwest",
|
|
12
|
+
"northeast",
|
|
13
|
+
"southwest",
|
|
14
|
+
"southeast"
|
|
15
|
+
];
|
|
16
|
+
function parseGravity(gravity) {
|
|
17
|
+
const g = String(gravity).toLowerCase();
|
|
18
|
+
const hit = GRAVITY.find((x) => x === g);
|
|
19
|
+
if (hit === void 0)
|
|
20
|
+
throw new InputError(`gravity must be one of ${GRAVITY.join(", ")}, got ${JSON.stringify(gravity)}`);
|
|
21
|
+
return hit;
|
|
22
|
+
}
|
|
23
|
+
function checkColor(name, value) {
|
|
24
|
+
if (!/^[#a-zA-Z0-9(),.%/ -]+$/.test(value))
|
|
25
|
+
throw new InputError(`${name} must be a CSS color, got ${JSON.stringify(value)}`);
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
var pyFloat = (s) => Number(typeof s === "string" ? s.replace(/(?<=\d)_(?=\d)/g, "") : s);
|
|
29
|
+
function parseAspect(aspect) {
|
|
30
|
+
let ratio = Number.NaN;
|
|
31
|
+
if (typeof aspect === "string" && /[:x]/.test(aspect)) {
|
|
32
|
+
const parts = aspect.split(/[:x]/);
|
|
33
|
+
if (parts.length === 2) ratio = pyFloat(parts[0]) / pyFloat(parts[1]);
|
|
34
|
+
} else if (typeof aspect === "number" || typeof aspect === "string") ratio = pyFloat(aspect);
|
|
35
|
+
if (!(ratio > 0 && Number.isFinite(ratio))) {
|
|
36
|
+
throw new InputError(
|
|
37
|
+
`aspect could not be read as a ratio: got ${String(aspect)}; use "1:1", "16:9", "4x5" or a number like 1.91`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return ratio;
|
|
41
|
+
}
|
|
42
|
+
function canvasFor(width, height, ratio) {
|
|
43
|
+
return width / height > ratio ? [width, roundHalfEven(width / ratio)] : [roundHalfEven(height * ratio), height];
|
|
44
|
+
}
|
|
45
|
+
function offsetFor(dx, dy, gravity) {
|
|
46
|
+
const x = gravity.endsWith("west") ? 0 : gravity.endsWith("east") ? dx : Math.floor(dx / 2);
|
|
47
|
+
const y = gravity.startsWith("north") ? 0 : gravity.startsWith("south") ? dy : Math.floor(dy / 2);
|
|
48
|
+
return [x, y];
|
|
49
|
+
}
|
|
50
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
51
|
+
var XLINK_NS = "http://www.w3.org/1999/xlink";
|
|
52
|
+
var OPEN_TAG = /<svg\b[^>]*>/i;
|
|
53
|
+
var attrOf = (tag, name) => new RegExp(`\\s${name}=["']([^"']+)["']`, "i").exec(tag)?.[1];
|
|
54
|
+
var userUnits = (v) => Number(v?.trim().replace(/px$/i, "") || Number.NaN);
|
|
55
|
+
function svgSize(svg) {
|
|
56
|
+
const open = OPEN_TAG.exec(svg)?.[0];
|
|
57
|
+
if (!open) throw new InputError("socialCard: no <svg> root element");
|
|
58
|
+
const vb = attrOf(open, "viewBox");
|
|
59
|
+
const parts = vb?.trim().split(/[\s,]+/).map(Number);
|
|
60
|
+
const w = userUnits(attrOf(open, "width"));
|
|
61
|
+
const h = userUnits(attrOf(open, "height"));
|
|
62
|
+
const width = Number.isFinite(w) ? w : parts?.[2];
|
|
63
|
+
const height = Number.isFinite(h) ? h : parts?.[3];
|
|
64
|
+
if (width === void 0 || height === void 0 || !(width > 0) || !(height > 0)) {
|
|
65
|
+
throw new InputError("socialCard: the <svg> needs width/height attributes or a viewBox");
|
|
66
|
+
}
|
|
67
|
+
return { width, height, viewBox: vb ?? `0 0 ${width} ${height}` };
|
|
68
|
+
}
|
|
69
|
+
function socialCard(svg, { aspect = "1:1", padding = 60, background = "white", gravity = "center", color } = {}) {
|
|
70
|
+
const ratio = parseAspect(aspect);
|
|
71
|
+
const place = parseGravity(gravity);
|
|
72
|
+
checkColor("background", background);
|
|
73
|
+
if (color !== void 0) checkColor("color", color);
|
|
74
|
+
if (!(Number.isFinite(padding) && padding >= 0))
|
|
75
|
+
throw new InputError(`padding must be a non-negative number of pixels, got ${String(padding)}`);
|
|
76
|
+
const pad = Math.trunc(padding);
|
|
77
|
+
const { width, height, viewBox } = svgSize(svg);
|
|
78
|
+
const [cw, ch] = canvasFor(width + 2 * pad, height + 2 * pad, ratio);
|
|
79
|
+
const [ox, oy] = offsetFor(cw - (width + 2 * pad), ch - (height + 2 * pad), place);
|
|
80
|
+
const ink = color ?? (/^#(?:[0-9a-f]{3}){1,2}$/i.test(background) ? onColor(background) : void 0);
|
|
81
|
+
const open = OPEN_TAG.exec(svg)?.[0] ?? "<svg>";
|
|
82
|
+
const stripped = open.replace(/\s(?:x|y|width|height|viewBox)=["'][^"']*["']/gi, "");
|
|
83
|
+
const inner = svg.replace(
|
|
84
|
+
open,
|
|
85
|
+
() => stripped.replace(
|
|
86
|
+
/^<svg/i,
|
|
87
|
+
() => `<svg x="${ox + pad}" y="${oy + pad}" width="${width}" height="${height}" viewBox="${viewBox}"`
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
|
+
const inked = ink === void 0 ? "" : ` color="${ink}"`;
|
|
91
|
+
return `<svg xmlns="${SVG_NS}"${inked} width="${cw}" height="${ch}" viewBox="0 0 ${cw} ${ch}"><rect width="${cw}" height="${ch}" fill="${background}"/>${inner}</svg>`;
|
|
92
|
+
}
|
|
93
|
+
function peerMissing(e, name) {
|
|
94
|
+
const quoted = new RegExp(`['"]${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}['"]`);
|
|
95
|
+
return e instanceof Error && (/MODULE_NOT_FOUND/.test(String(e.code)) && quoted.test(e.message) || peerMissing(e.cause, name));
|
|
96
|
+
}
|
|
97
|
+
async function download(url) {
|
|
98
|
+
let r;
|
|
99
|
+
try {
|
|
100
|
+
r = await fetch(url);
|
|
101
|
+
if (r.ok) return new Uint8Array(await r.arrayBuffer());
|
|
102
|
+
} catch (e) {
|
|
103
|
+
throw Object.assign(new DownloadError(`toPNG could not download ${url}: ${String(e)}`, url), {
|
|
104
|
+
cause: e
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
throw new DownloadError(`toPNG could not download ${url}: it answered ${r.status}`, url, r.status);
|
|
108
|
+
}
|
|
109
|
+
var FETCH_LIMIT = 8;
|
|
110
|
+
async function mapLimit(xs, limit, fn) {
|
|
111
|
+
const out = new Array(xs.length);
|
|
112
|
+
let next = 0;
|
|
113
|
+
const worker = async () => {
|
|
114
|
+
while (next < xs.length) {
|
|
115
|
+
const i = next++;
|
|
116
|
+
try {
|
|
117
|
+
out[i] = await fn(xs[i]);
|
|
118
|
+
} catch (e) {
|
|
119
|
+
next = xs.length;
|
|
120
|
+
throw e;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
await Promise.all(Array.from({ length: Math.min(limit, xs.length) }, worker));
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
var hrefAttr = (url) => {
|
|
128
|
+
const re = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
129
|
+
return new RegExp(`(href=)(["'])(?:${re(url)}|${re(url.replaceAll("&", "&"))})\\2`, "g");
|
|
130
|
+
};
|
|
131
|
+
async function toPNG(svg, { width, scale = 1, background, color, images = "fetch", fonts = {} } = {}) {
|
|
132
|
+
if (!(Number.isFinite(scale) && scale > 0))
|
|
133
|
+
throw new InputError(`scale must be a positive number, got ${String(scale)}`);
|
|
134
|
+
if (width !== void 0 && !(Number.isFinite(width) && width > 0))
|
|
135
|
+
throw new InputError(`width must be a positive number of pixels, got ${String(width)}`);
|
|
136
|
+
if (background !== void 0) checkColor("background", background);
|
|
137
|
+
if (color !== void 0) checkColor("color", color);
|
|
138
|
+
if (images !== "fetch" && images !== "skip")
|
|
139
|
+
throw new InputError(`images must be "fetch" or "skip", got ${JSON.stringify(images)}`);
|
|
140
|
+
const font = await fontOptions(fonts);
|
|
141
|
+
let text = typeof svg === "string" ? svg : svg.outerHTML;
|
|
142
|
+
const mod = await import('@resvg/resvg-js').catch((e) => {
|
|
143
|
+
throw new OptionalDependencyError(
|
|
144
|
+
peerMissing(e, "@resvg/resvg-js") ? "toPNG needs the optional peer @resvg/resvg-js, which is not installed: pnpm add @resvg/resvg-js" : `toPNG could not load the optional peer @resvg/resvg-js (installed, but it failed to load): ${String(e)}`,
|
|
145
|
+
{ cause: e }
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
const fitTo = width !== void 0 ? { mode: "width", value: Math.round(width * scale) } : { mode: "zoom", value: scale };
|
|
149
|
+
const opts = { fitTo, font, ...background !== void 0 && { background } };
|
|
150
|
+
const open = OPEN_TAG.exec(text)?.[0];
|
|
151
|
+
if (open !== void 0) {
|
|
152
|
+
let add = "";
|
|
153
|
+
if (!/\sxmlns=/i.test(open)) add += ` xmlns="${SVG_NS}"`;
|
|
154
|
+
if (/\sxlink:[\w-]+=/i.test(text) && !/\sxmlns:xlink=/i.test(open)) add += ` xmlns:xlink="${XLINK_NS}"`;
|
|
155
|
+
let ink = color;
|
|
156
|
+
if (ink === void 0 && background !== void 0 && !/\scolor=/i.test(open) && /currentcolor/i.test(text)) {
|
|
157
|
+
const p = new mod.Resvg(`<svg xmlns="${SVG_NS}" width="1" height="1"/>`, { background }).render().pixels;
|
|
158
|
+
ink = onColor(`#${[p[0], p[1], p[2]].map((v) => (v ?? 0).toString(16).padStart(2, "0")).join("")}`);
|
|
159
|
+
}
|
|
160
|
+
let tag = open;
|
|
161
|
+
if (ink !== void 0) {
|
|
162
|
+
tag = tag.replace(/\scolor=["'][^"']*["']/i, "");
|
|
163
|
+
add += ` color="${ink}"`;
|
|
164
|
+
}
|
|
165
|
+
text = text.replace(open, () => tag.replace(/^<svg/i, () => `<svg${add}`));
|
|
166
|
+
}
|
|
167
|
+
const probe = new mod.Resvg(text, NO_FONTS);
|
|
168
|
+
const hrefs = [...new Set(probe.imagesToResolve())];
|
|
169
|
+
let bodies = [];
|
|
170
|
+
if (hrefs.length > 0 && images === "skip") {
|
|
171
|
+
warn(
|
|
172
|
+
`toPNG:skip:${hrefs.join(" ")}`,
|
|
173
|
+
`toPNG left out ${hrefs.length} remote image(s): ${hrefs.join(", ")}`
|
|
174
|
+
);
|
|
175
|
+
} else if (hrefs.length > 0) {
|
|
176
|
+
bodies = await mapLimit(hrefs, FETCH_LIMIT, download);
|
|
177
|
+
const zoom = fitTo.mode === "width" ? fitTo.value / probe.width : scale;
|
|
178
|
+
const tags = text.match(/<image\b[^>]*>/gi) ?? [];
|
|
179
|
+
for (const [i, href] of hrefs.entries()) {
|
|
180
|
+
const b = bodies[i];
|
|
181
|
+
let uri = dataURI(b);
|
|
182
|
+
const re = hrefAttr(href);
|
|
183
|
+
const small = shrink(
|
|
184
|
+
mod,
|
|
185
|
+
uri,
|
|
186
|
+
tags.filter((t) => t.search(re) >= 0),
|
|
187
|
+
zoom
|
|
188
|
+
);
|
|
189
|
+
if (small !== void 0) uri = dataURI(small);
|
|
190
|
+
text = text.replace(re, (_m, attr, q) => `${attr}${q}${uri}${q}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const resvg = new mod.Resvg(text, opts);
|
|
194
|
+
if (bodies.length > 0) {
|
|
195
|
+
const left = new Set(resvg.imagesToResolve());
|
|
196
|
+
for (const [i, href] of hrefs.entries())
|
|
197
|
+
if (left.has(href)) resvg.resolveImage(href, Buffer.from(bodies[i]));
|
|
198
|
+
}
|
|
199
|
+
return new Uint8Array(resvg.render().asPng());
|
|
200
|
+
}
|
|
201
|
+
var NO_FONTS = { font: { loadSystemFonts: false } };
|
|
202
|
+
async function fontOptions(fonts) {
|
|
203
|
+
if (typeof fonts !== "object" || fonts === null || Array.isArray(fonts))
|
|
204
|
+
throw new InputError(
|
|
205
|
+
`fonts must be an object { files, defaultFamily, system }, got ${JSON.stringify(fonts)}`
|
|
206
|
+
);
|
|
207
|
+
const { files = [], defaultFamily, system = true } = fonts;
|
|
208
|
+
if (!Array.isArray(files) || files.some((f) => typeof f !== "string"))
|
|
209
|
+
throw new InputError(`fonts.files must be an array of font file paths, got ${JSON.stringify(files)}`);
|
|
210
|
+
if (defaultFamily !== void 0 && typeof defaultFamily !== "string")
|
|
211
|
+
throw new InputError(`fonts.defaultFamily must be a string, got ${JSON.stringify(defaultFamily)}`);
|
|
212
|
+
if (typeof system !== "boolean")
|
|
213
|
+
throw new InputError(`fonts.system must be a boolean, got ${JSON.stringify(system)}`);
|
|
214
|
+
if (files.length > 0) {
|
|
215
|
+
const { statSync } = await import('fs');
|
|
216
|
+
for (const f of files)
|
|
217
|
+
if (!statSync(f, { throwIfNoEntry: false })?.isFile())
|
|
218
|
+
throw new InputError(`fonts.files: no font file at ${JSON.stringify(f)}`);
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
loadSystemFonts: system,
|
|
222
|
+
fontFiles: [...files],
|
|
223
|
+
...defaultFamily !== void 0 && { defaultFontFamily: defaultFamily }
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
var OVERSAMPLE = 2;
|
|
227
|
+
function shrink(mod, uri, uses, zoom) {
|
|
228
|
+
if (uses.length < 2) return void 0;
|
|
229
|
+
let side = 0;
|
|
230
|
+
for (const tag of uses) {
|
|
231
|
+
const w2 = userUnits(attrOf(tag, "width"));
|
|
232
|
+
const h2 = userUnits(attrOf(tag, "height"));
|
|
233
|
+
if (!(w2 > 0 && h2 > 0)) return void 0;
|
|
234
|
+
side = Math.max(side, w2, h2);
|
|
235
|
+
}
|
|
236
|
+
const own = new mod.Resvg(
|
|
237
|
+
`<svg xmlns="${SVG_NS}" width="1" height="1"><image href="${uri}"/></svg>`,
|
|
238
|
+
NO_FONTS
|
|
239
|
+
).getBBox();
|
|
240
|
+
const k = own ? Math.ceil(OVERSAMPLE * zoom * side) / Math.max(own.width, own.height) : 1;
|
|
241
|
+
if (!own || !(k < 1)) return void 0;
|
|
242
|
+
const [w, h] = [Math.max(1, Math.round(own.width * k)), Math.max(1, Math.round(own.height * k))];
|
|
243
|
+
return new mod.Resvg(
|
|
244
|
+
`<svg xmlns="${SVG_NS}" width="${w}" height="${h}"><image href="${uri}" width="${w}" height="${h}" preserveAspectRatio="none"/></svg>`,
|
|
245
|
+
NO_FONTS
|
|
246
|
+
).render().asPng();
|
|
247
|
+
}
|
|
248
|
+
var dataURI = (b) => `data:${imageType(b)};base64,${Buffer.from(b).toString("base64")}`;
|
|
249
|
+
function imageType(b) {
|
|
250
|
+
if (new TextDecoder().decode(b.subarray(0, 64)).trimStart().startsWith("<")) return "image/svg+xml";
|
|
251
|
+
if (b[0] === 255 && b[1] === 216) return "image/jpeg";
|
|
252
|
+
if (b[0] === 71 && b[1] === 73 && b[2] === 70) return "image/gif";
|
|
253
|
+
return "image/png";
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export { canvasFor, checkColor, offsetFor, parseAspect, parseGravity, peerMissing, socialCard, svgSize, toPNG };
|
|
257
|
+
//# sourceMappingURL=export.js.map
|
|
258
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/export/index.ts"],"names":["w","h"],"mappings":";;;;AAqEA,IAAM,OAAA,GAA8B;AAAA,EAClC,QAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA;AAAA,EACA,WAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAA;AAEO,SAAS,aAAa,OAAA,EAA0B;AACrD,EAAA,MAAM,CAAA,GAAI,MAAA,CAAO,OAAO,CAAA,CAAE,WAAA,EAAY;AACtC,EAAA,MAAM,MAAM,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,MAAM,CAAC,CAAA;AACvC,EAAA,IAAI,GAAA,KAAQ,MAAA;AACV,IAAA,MAAM,IAAI,UAAA,CAAW,CAAA,uBAAA,EAA0B,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAC,CAAA,MAAA,EAAS,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAA,CAAE,CAAA;AACrG,EAAA,OAAO,GAAA;AACT;AAKO,SAAS,UAAA,CAAW,MAAc,KAAA,EAAuB;AAC9D,EAAA,IAAI,CAAC,yBAAA,CAA0B,IAAA,CAAK,KAAK,CAAA;AACvC,IAAA,MAAM,IAAI,WAAW,CAAA,EAAG,IAAI,6BAA6B,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA,CAAE,CAAA;AAClF,EAAA,OAAO,KAAA;AACT;AAGA,IAAM,OAAA,GAAU,CAAC,CAAA,KACf,MAAA,CAAO,OAAO,CAAA,KAAM,QAAA,GAAW,CAAA,CAAE,OAAA,CAAQ,iBAAA,EAAmB,EAAE,CAAA,GAAI,CAAC,CAAA;AAG9D,SAAS,YAAY,MAAA,EAAwB;AAClD,EAAA,IAAI,QAAQ,MAAA,CAAO,GAAA;AACnB,EAAA,IAAI,OAAO,MAAA,KAAW,QAAA,IAAY,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA,EAAG;AACrD,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,MAAM,CAAA;AACjC,IAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG,KAAA,GAAQ,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAW,CAAA,GAAI,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAW,CAAA;AAAA,EAC1F,CAAA,MAAA,IAAW,OAAO,MAAA,KAAW,QAAA,IAAY,OAAO,MAAA,KAAW,QAAA,EAAU,KAAA,GAAQ,OAAA,CAAQ,MAAM,CAAA;AAC3F,EAAA,IAAI,EAAE,KAAA,GAAQ,CAAA,IAAK,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,CAAA,EAAI;AAC1C,IAAA,MAAM,IAAI,UAAA;AAAA,MACR,CAAA,yCAAA,EAA4C,MAAA,CAAO,MAAM,CAAC,CAAA,gDAAA;AAAA,KAC5D;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT;AAGO,SAAS,SAAA,CAAU,KAAA,EAAe,MAAA,EAAgB,KAAA,EAAiC;AACxF,EAAA,OAAO,KAAA,GAAQ,MAAA,GAAS,KAAA,GACpB,CAAC,OAAO,aAAA,CAAc,KAAA,GAAQ,KAAK,CAAC,IACpC,CAAC,aAAA,CAAc,MAAA,GAAS,KAAK,GAAG,MAAM,CAAA;AAC5C;AAGO,SAAS,SAAA,CAAU,EAAA,EAAY,EAAA,EAAY,OAAA,EAAoC;AACpF,EAAA,MAAM,CAAA,GAAI,OAAA,CAAQ,QAAA,CAAS,MAAM,IAAI,CAAA,GAAI,OAAA,CAAQ,QAAA,CAAS,MAAM,CAAA,GAAI,EAAA,GAAK,IAAA,CAAK,KAAA,CAAM,KAAK,CAAC,CAAA;AAC1F,EAAA,MAAM,CAAA,GAAI,OAAA,CAAQ,UAAA,CAAW,OAAO,IAAI,CAAA,GAAI,OAAA,CAAQ,UAAA,CAAW,OAAO,CAAA,GAAI,EAAA,GAAK,IAAA,CAAK,KAAA,CAAM,KAAK,CAAC,CAAA;AAChG,EAAA,OAAO,CAAC,GAAG,CAAC,CAAA;AACd;AAEA,IAAM,MAAA,GAAS,4BAAA;AACf,IAAM,QAAA,GAAW,8BAAA;AACjB,IAAM,QAAA,GAAW,eAAA;AACjB,IAAM,MAAA,GAAS,CAAC,GAAA,EAAa,IAAA,KAC3B,IAAI,MAAA,CAAO,CAAA,GAAA,EAAM,IAAI,CAAA,iBAAA,CAAA,EAAqB,GAAG,CAAA,CAAE,IAAA,CAAK,GAAG,IAAI,CAAC,CAAA;AAE9D,IAAM,SAAA,GAAY,CAAC,CAAA,KAAkC,MAAA,CAAO,CAAA,EAAG,IAAA,EAAK,CAAE,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA,IAAK,MAAA,CAAO,GAAG,CAAA;AAMhG,SAAS,QAAQ,GAAA,EAAsB;AAC5C,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,IAAA,CAAK,GAAG,IAAI,CAAC,CAAA;AACnC,EAAA,IAAI,CAAC,IAAA,EAAM,MAAM,IAAI,WAAW,mCAAmC,CAAA;AACnE,EAAA,MAAM,EAAA,GAAK,MAAA,CAAO,IAAA,EAAM,SAAS,CAAA;AACjC,EAAA,MAAM,KAAA,GAAQ,IACV,IAAA,EAAK,CACN,MAAM,QAAQ,CAAA,CACd,IAAI,MAAM,CAAA;AACb,EAAA,MAAM,CAAA,GAAI,SAAA,CAAU,MAAA,CAAO,IAAA,EAAM,OAAO,CAAC,CAAA;AACzC,EAAA,MAAM,CAAA,GAAI,SAAA,CAAU,MAAA,CAAO,IAAA,EAAM,QAAQ,CAAC,CAAA;AAC1C,EAAA,MAAM,QAAQ,MAAA,CAAO,QAAA,CAAS,CAAC,CAAA,GAAI,CAAA,GAAI,QAAQ,CAAC,CAAA;AAChD,EAAA,MAAM,SAAS,MAAA,CAAO,QAAA,CAAS,CAAC,CAAA,GAAI,CAAA,GAAI,QAAQ,CAAC,CAAA;AACjD,EAAA,IAAI,KAAA,KAAU,UAAa,MAAA,KAAW,MAAA,IAAa,EAAE,KAAA,GAAQ,CAAA,CAAA,IAAM,EAAE,MAAA,GAAS,CAAA,CAAA,EAAI;AAChF,IAAA,MAAM,IAAI,WAAW,kEAAkE,CAAA;AAAA,EACzF;AACA,EAAA,OAAO,EAAE,OAAO,MAAA,EAAQ,OAAA,EAAS,MAAM,CAAA,IAAA,EAAO,KAAK,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA,EAAG;AAClE;AAOO,SAAS,UAAA,CACd,GAAA,EACA,EAAE,MAAA,GAAS,OAAO,OAAA,GAAU,EAAA,EAAI,UAAA,GAAa,OAAA,EAAS,OAAA,GAAU,QAAA,EAAU,KAAA,EAAM,GAAuB,EAAC,EAChG;AACR,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAM,CAAA;AAChC,EAAA,MAAM,KAAA,GAAQ,aAAa,OAAO,CAAA;AAClC,EAAA,UAAA,CAAW,cAAc,UAAU,CAAA;AACnC,EAAA,IAAI,KAAA,KAAU,MAAA,EAAW,UAAA,CAAW,OAAA,EAAS,KAAK,CAAA;AAClD,EAAA,IAAI,EAAE,MAAA,CAAO,QAAA,CAAS,OAAO,KAAK,OAAA,IAAW,CAAA,CAAA;AAC3C,IAAA,MAAM,IAAI,UAAA,CAAW,CAAA,qDAAA,EAAwD,MAAA,CAAO,OAAO,CAAC,CAAA,CAAE,CAAA;AAChG,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA;AAC9B,EAAA,MAAM,EAAE,KAAA,EAAO,MAAA,EAAQ,OAAA,EAAQ,GAAI,QAAQ,GAAG,CAAA;AAC9C,EAAA,MAAM,CAAC,EAAA,EAAI,EAAE,CAAA,GAAI,SAAA,CAAU,KAAA,GAAQ,CAAA,GAAI,GAAA,EAAK,MAAA,GAAS,CAAA,GAAI,GAAA,EAAK,KAAK,CAAA;AACnE,EAAA,MAAM,CAAC,EAAA,EAAI,EAAE,CAAA,GAAI,SAAA,CAAU,EAAA,IAAM,KAAA,GAAQ,CAAA,GAAI,GAAA,CAAA,EAAM,EAAA,IAAM,MAAA,GAAS,CAAA,GAAI,MAAM,KAAK,CAAA;AAEjF,EAAA,MAAM,GAAA,GAAM,UAAU,0BAAA,CAA2B,IAAA,CAAK,UAAU,CAAA,GAAI,OAAA,CAAQ,UAAU,CAAA,GAAI,MAAA,CAAA;AAC1F,EAAA,MAAM,OAAO,QAAA,CAAS,IAAA,CAAK,GAAG,CAAA,GAAI,CAAC,CAAA,IAAK,OAAA;AACxC,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,OAAA,CAAQ,iDAAA,EAAmD,EAAE,CAAA;AAEnF,EAAA,MAAM,QAAQ,GAAA,CAAI,OAAA;AAAA,IAAQ,IAAA;AAAA,IAAM,MAC9B,QAAA,CAAS,OAAA;AAAA,MACP,QAAA;AAAA,MACA,MAAM,CAAA,QAAA,EAAW,EAAA,GAAK,GAAG,CAAA,KAAA,EAAQ,EAAA,GAAK,GAAG,CAAA,SAAA,EAAY,KAAK,CAAA,UAAA,EAAa,MAAM,CAAA,WAAA,EAAc,OAAO,CAAA,CAAA;AAAA;AACpG,GACF;AACA,EAAA,MAAM,KAAA,GAAQ,GAAA,KAAQ,MAAA,GAAY,EAAA,GAAK,WAAW,GAAG,CAAA,CAAA,CAAA;AACrD,EAAA,OAAO,eAAe,MAAM,CAAA,CAAA,EAAI,KAAK,CAAA,QAAA,EAAW,EAAE,aAAa,EAAE,CAAA,eAAA,EAAkB,EAAE,CAAA,CAAA,EAAI,EAAE,kBAAkB,EAAE,CAAA,UAAA,EAAa,EAAE,CAAA,QAAA,EAAW,UAAU,MAAM,KAAK,CAAA,MAAA,CAAA;AAChK;AAuBO,SAAS,WAAA,CAAY,GAAY,IAAA,EAAuB;AAC7D,EAAA,MAAM,MAAA,GAAS,IAAI,MAAA,CAAO,CAAA,IAAA,EAAO,KAAK,OAAA,CAAQ,qBAAA,EAAuB,MAAM,CAAC,CAAA,IAAA,CAAM,CAAA;AAClF,EAAA,OACE,aAAa,KAAA,KACX,kBAAA,CAAmB,IAAA,CAAK,MAAA,CAAQ,EAAyB,IAAI,CAAC,CAAA,IAAK,MAAA,CAAO,KAAK,CAAA,CAAE,OAAO,KACxF,WAAA,CAAY,CAAA,CAAE,OAAO,IAAI,CAAA,CAAA;AAE/B;AAGA,eAAe,SAAS,GAAA,EAAkC;AACxD,EAAA,IAAI,CAAA;AACJ,EAAA,IAAI;AACF,IAAA,CAAA,GAAI,MAAM,MAAM,GAAG,CAAA;AACnB,IAAA,IAAI,CAAA,CAAE,IAAI,OAAO,IAAI,WAAW,MAAM,CAAA,CAAE,aAAa,CAAA;AAAA,EACvD,SAAS,CAAA,EAAG;AACV,IAAA,MAAM,MAAA,CAAO,MAAA,CAAO,IAAI,aAAA,CAAc,CAAA,yBAAA,EAA4B,GAAG,CAAA,EAAA,EAAK,MAAA,CAAO,CAAC,CAAC,CAAA,CAAA,EAAI,GAAG,CAAA,EAAG;AAAA,MAC3F,KAAA,EAAO;AAAA,KACR,CAAA;AAAA,EACH;AACA,EAAA,MAAM,IAAI,aAAA,CAAc,CAAA,yBAAA,EAA4B,GAAG,CAAA,cAAA,EAAiB,EAAE,MAAM,CAAA,CAAA,EAAI,GAAA,EAAK,CAAA,CAAE,MAAM,CAAA;AACnG;AAGA,IAAM,WAAA,GAAc,CAAA;AAGpB,eAAe,QAAA,CAAe,EAAA,EAAkB,KAAA,EAAe,EAAA,EAAwC;AACrG,EAAA,MAAM,GAAA,GAAM,IAAI,KAAA,CAAS,EAAA,CAAG,MAAM,CAAA;AAClC,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,MAAM,SAAS,YAA2B;AACxC,IAAA,OAAO,IAAA,GAAO,GAAG,MAAA,EAAQ;AACvB,MAAA,MAAM,CAAA,GAAI,IAAA,EAAA;AACV,MAAA,IAAI;AACF,QAAA,GAAA,CAAI,CAAC,CAAA,GAAI,MAAM,EAAA,CAAG,EAAA,CAAG,CAAC,CAAM,CAAA;AAAA,MAC9B,SAAS,CAAA,EAAG;AACV,QAAA,IAAA,GAAO,EAAA,CAAG,MAAA;AACV,QAAA,MAAM,CAAA;AAAA,MACR;AAAA,IACF;AAAA,EACF,CAAA;AACA,EAAA,MAAM,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,EAAA,CAAG,MAAM,CAAA,EAAE,EAAG,MAAM,CAAC,CAAA;AAC5E,EAAA,OAAO,GAAA;AACT;AAGA,IAAM,QAAA,GAAW,CAAC,GAAA,KAAwB;AACxC,EAAA,MAAM,KAAK,CAAC,CAAA,KAAc,CAAA,CAAE,OAAA,CAAQ,uBAAuB,MAAM,CAAA;AACjE,EAAA,OAAO,IAAI,MAAA,CAAO,CAAA,gBAAA,EAAmB,EAAA,CAAG,GAAG,CAAC,CAAA,CAAA,EAAI,EAAA,CAAG,GAAA,CAAI,WAAW,GAAA,EAAK,OAAO,CAAC,CAAC,QAAQ,GAAG,CAAA;AAC7F,CAAA;AAqBA,eAAsB,KAAA,CACpB,GAAA,EACA,EAAE,KAAA,EAAO,QAAQ,CAAA,EAAG,UAAA,EAAY,KAAA,EAAO,MAAA,GAAS,SAAS,KAAA,GAAQ,EAAC,EAAE,GAAkB,EAAC,EAClE;AACrB,EAAA,IAAI,EAAE,MAAA,CAAO,QAAA,CAAS,KAAK,KAAK,KAAA,GAAQ,CAAA,CAAA;AACtC,IAAA,MAAM,IAAI,UAAA,CAAW,CAAA,qCAAA,EAAwC,MAAA,CAAO,KAAK,CAAC,CAAA,CAAE,CAAA;AAC9E,EAAA,IAAI,UAAU,MAAA,IAAa,EAAE,OAAO,QAAA,CAAS,KAAK,KAAK,KAAA,GAAQ,CAAA,CAAA;AAC7D,IAAA,MAAM,IAAI,UAAA,CAAW,CAAA,+CAAA,EAAkD,MAAA,CAAO,KAAK,CAAC,CAAA,CAAE,CAAA;AACxF,EAAA,IAAI,UAAA,KAAe,MAAA,EAAW,UAAA,CAAW,YAAA,EAAc,UAAU,CAAA;AACjE,EAAA,IAAI,KAAA,KAAU,MAAA,EAAW,UAAA,CAAW,OAAA,EAAS,KAAK,CAAA;AAClD,EAAA,IAAI,MAAA,KAAW,WAAW,MAAA,KAAW,MAAA;AACnC,IAAA,MAAM,IAAI,UAAA,CAAW,CAAA,sCAAA,EAAyC,KAAK,SAAA,CAAU,MAAM,CAAC,CAAA,CAAE,CAAA;AACxF,EAAA,MAAM,IAAA,GAAO,MAAM,WAAA,CAAY,KAAK,CAAA;AACpC,EAAA,IAAI,IAAA,GAAO,OAAO,GAAA,KAAQ,QAAA,GAAW,MAAM,GAAA,CAAI,SAAA;AAC/C,EAAA,MAAM,MAAM,MAAM,OAAO,iBAAiB,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA,KAAe;AAChE,IAAA,MAAM,IAAI,uBAAA;AAAA,MACR,WAAA,CAAY,GAAG,iBAAiB,CAAA,GAC5B,oGACA,CAAA,2FAAA,EAA8F,MAAA,CAAO,CAAC,CAAC,CAAA,CAAA;AAAA,MAC3G,EAAE,OAAO,CAAA;AAAE,KACb;AAAA,EACF,CAAC,CAAA;AACD,EAAA,MAAM,QACJ,KAAA,KAAU,MAAA,GACN,EAAE,IAAA,EAAM,SAAkB,KAAA,EAAO,IAAA,CAAK,KAAA,CAAM,KAAA,GAAQ,KAAK,CAAA,EAAE,GAC3D,EAAE,IAAA,EAAM,MAAA,EAAiB,OAAO,KAAA,EAAM;AAC5C,EAAA,MAAM,IAAA,GAAO,EAAE,KAAA,EAAO,IAAA,EAAM,GAAI,UAAA,KAAe,MAAA,IAAa,EAAE,UAAA,EAAW,EAAG;AAE5E,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,IAAA,CAAK,IAAI,IAAI,CAAC,CAAA;AACpC,EAAA,IAAI,SAAS,MAAA,EAAW;AACtB,IAAA,IAAI,GAAA,GAAM,EAAA;AACV,IAAA,IAAI,CAAC,WAAA,CAAY,IAAA,CAAK,IAAI,CAAA,EAAG,GAAA,IAAO,WAAW,MAAM,CAAA,CAAA,CAAA;AACrD,IAAA,IAAI,kBAAA,CAAmB,IAAA,CAAK,IAAI,CAAA,IAAK,CAAC,iBAAA,CAAkB,IAAA,CAAK,IAAI,CAAA,EAAG,GAAA,IAAO,CAAA,cAAA,EAAiB,QAAQ,CAAA,CAAA,CAAA;AACpG,IAAA,IAAI,GAAA,GAAM,KAAA;AACV,IAAA,IACE,GAAA,KAAQ,MAAA,IACR,UAAA,KAAe,MAAA,IACf,CAAC,WAAA,CAAY,IAAA,CAAK,IAAI,CAAA,IACtB,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAA,EACzB;AAGA,MAAA,MAAM,CAAA,GAAI,IAAI,GAAA,CAAI,KAAA,CAAM,CAAA,YAAA,EAAe,MAAM,CAAA,wBAAA,CAAA,EAA4B,EAAE,UAAA,EAAY,CAAA,CAAE,MAAA,EAAO,CAC7F,MAAA;AACH,MAAA,GAAA,GAAM,OAAA,CAAQ,CAAA,CAAA,EAAI,CAAC,CAAA,CAAE,CAAC,CAAA,EAAG,CAAA,CAAE,CAAC,CAAA,EAAG,CAAA,CAAE,CAAC,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,KAAA,CAAO,CAAA,IAAK,CAAA,EAAG,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAE,IAAA,CAAK,EAAE,CAAC,CAAA,CAAE,CAAA;AAAA,IACpG;AACA,IAAA,IAAI,GAAA,GAAM,IAAA;AACV,IAAA,IAAI,QAAQ,MAAA,EAAW;AACrB,MAAA,GAAA,GAAM,GAAA,CAAI,OAAA,CAAQ,yBAAA,EAA2B,EAAE,CAAA;AAC/C,MAAA,GAAA,IAAO,WAAW,GAAG,CAAA,CAAA,CAAA;AAAA,IACvB;AACA,IAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,MAAM,GAAA,CAAI,OAAA,CAAQ,QAAA,EAAU,MAAM,CAAA,IAAA,EAAO,GAAG,CAAA,CAAE,CAAC,CAAA;AAAA,EAC3E;AAGA,EAAA,MAAM,KAAA,GAAQ,IAAI,GAAA,CAAI,KAAA,CAAM,MAAM,QAAQ,CAAA;AAC1C,EAAA,MAAM,KAAA,GAAQ,CAAC,GAAG,IAAI,IAAI,KAAA,CAAM,eAAA,EAAiB,CAAC,CAAA;AAClD,EAAA,IAAI,SAAuB,EAAC;AAC5B,EAAA,IAAI,KAAA,CAAM,MAAA,GAAS,CAAA,IAAK,MAAA,KAAW,MAAA,EAAQ;AACzC,IAAA,IAAA;AAAA,MACE,CAAA,WAAA,EAAc,KAAA,CAAM,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA;AAAA,MAC7B,kBAAkB,KAAA,CAAM,MAAM,qBAAqB,KAAA,CAAM,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,KACrE;AAAA,EACF,CAAA,MAAA,IAAW,KAAA,CAAM,MAAA,GAAS,CAAA,EAAG;AAC3B,IAAA,MAAA,GAAS,MAAM,QAAA,CAAS,KAAA,EAAO,WAAA,EAAa,QAAQ,CAAA;AACpD,IAAA,MAAM,OAAO,KAAA,CAAM,IAAA,KAAS,UAAU,KAAA,CAAM,KAAA,GAAQ,MAAM,KAAA,GAAQ,KAAA;AAClE,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,kBAAkB,KAAK,EAAC;AAEhD,IAAA,KAAA,MAAW,CAAC,CAAA,EAAG,IAAI,CAAA,IAAK,KAAA,CAAM,SAAQ,EAAG;AACvC,MAAA,MAAM,CAAA,GAAI,OAAO,CAAC,CAAA;AAClB,MAAA,IAAI,GAAA,GAAM,QAAQ,CAAC,CAAA;AACnB,MAAA,MAAM,EAAA,GAAK,SAAS,IAAI,CAAA;AACxB,MAAA,MAAM,KAAA,GAAQ,MAAA;AAAA,QACZ,GAAA;AAAA,QACA,GAAA;AAAA,QACA,IAAA,CAAK,OAAO,CAAC,CAAA,KAAM,EAAE,MAAA,CAAO,EAAE,KAAK,CAAC,CAAA;AAAA,QACpC;AAAA,OACF;AACA,MAAA,IAAI,KAAA,KAAU,MAAA,EAAW,GAAA,GAAM,OAAA,CAAQ,KAAK,CAAA;AAC5C,MAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,EAAA,EAAI,CAAC,IAAI,IAAA,EAAc,CAAA,KAAc,CAAA,EAAG,IAAI,GAAG,CAAC,CAAA,EAAG,GAAG,CAAA,EAAG,CAAC,CAAA,CAAE,CAAA;AAAA,IAClF;AAAA,EACF;AACA,EAAA,MAAM,KAAA,GAAQ,IAAI,GAAA,CAAI,KAAA,CAAM,MAAM,IAAI,CAAA;AACtC,EAAA,IAAI,MAAA,CAAO,SAAS,CAAA,EAAG;AAErB,IAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,KAAA,CAAM,iBAAiB,CAAA;AAC5C,IAAA,KAAA,MAAW,CAAC,CAAA,EAAG,IAAI,CAAA,IAAK,MAAM,OAAA,EAAQ;AACpC,MAAA,IAAI,IAAA,CAAK,GAAA,CAAI,IAAI,CAAA,EAAG,KAAA,CAAM,YAAA,CAAa,IAAA,EAAM,MAAA,CAAO,IAAA,CAAK,MAAA,CAAO,CAAC,CAAe,CAAC,CAAA;AAAA,EACrF;AACA,EAAA,OAAO,IAAI,UAAA,CAAW,KAAA,CAAM,MAAA,EAAO,CAAE,OAAO,CAAA;AAC9C;AAEA,IAAM,WAAW,EAAE,IAAA,EAAM,EAAE,eAAA,EAAiB,OAAM,EAAE;AAGpD,eAAe,YAAY,KAAA,EAA8B;AACvD,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,UAAU,IAAA,IAAQ,KAAA,CAAM,QAAQ,KAAK,CAAA;AACpE,IAAA,MAAM,IAAI,UAAA;AAAA,MACR,CAAA,8DAAA,EAAiE,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,KACxF;AACF,EAAA,MAAM,EAAE,KAAA,GAAQ,IAAI,aAAA,EAAe,MAAA,GAAS,MAAK,GAAI,KAAA;AACrD,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,IAAK,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,OAAO,CAAA,KAAM,QAAQ,CAAA;AAClE,IAAA,MAAM,IAAI,UAAA,CAAW,CAAA,qDAAA,EAAwD,KAAK,SAAA,CAAU,KAAK,CAAC,CAAA,CAAE,CAAA;AACtG,EAAA,IAAI,aAAA,KAAkB,MAAA,IAAa,OAAO,aAAA,KAAkB,QAAA;AAC1D,IAAA,MAAM,IAAI,UAAA,CAAW,CAAA,0CAAA,EAA6C,KAAK,SAAA,CAAU,aAAa,CAAC,CAAA,CAAE,CAAA;AACnG,EAAA,IAAI,OAAO,MAAA,KAAW,SAAA;AACpB,IAAA,MAAM,IAAI,UAAA,CAAW,CAAA,oCAAA,EAAuC,KAAK,SAAA,CAAU,MAAM,CAAC,CAAA,CAAE,CAAA;AACtF,EAAA,IAAI,KAAA,CAAM,SAAS,CAAA,EAAG;AACpB,IAAA,MAAM,EAAE,QAAA,EAAS,GAAI,MAAM,OAAO,IAAS,CAAA;AAC3C,IAAA,KAAA,MAAW,CAAA,IAAK,KAAA;AACd,MAAA,IAAI,CAAC,SAAS,CAAA,EAAG,EAAE,gBAAgB,KAAA,EAAO,GAAG,MAAA,EAAO;AAClD,QAAA,MAAM,IAAI,UAAA,CAAW,CAAA,6BAAA,EAAgC,KAAK,SAAA,CAAU,CAAC,CAAC,CAAA,CAAE,CAAA;AAAA,EAC9E;AACA,EAAA,OAAO;AAAA,IACL,eAAA,EAAiB,MAAA;AAAA,IACjB,SAAA,EAAW,CAAC,GAAG,KAAK,CAAA;AAAA,IACpB,GAAI,aAAA,KAAkB,MAAA,IAAa,EAAE,mBAAmB,aAAA;AAAc,GACxE;AACF;AAEA,IAAM,UAAA,GAAa,CAAA;AAOnB,SAAS,MAAA,CACP,GAAA,EACA,GAAA,EACA,IAAA,EACA,IAAA,EACwB;AACxB,EAAA,IAAI,IAAA,CAAK,MAAA,GAAS,CAAA,EAAG,OAAO,MAAA;AAC5B,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,MAAMA,EAAAA,GAAI,SAAA,CAAU,MAAA,CAAO,GAAA,EAAK,OAAO,CAAC,CAAA;AACxC,IAAA,MAAMC,EAAAA,GAAI,SAAA,CAAU,MAAA,CAAO,GAAA,EAAK,QAAQ,CAAC,CAAA;AAEzC,IAAA,IAAI,EAAED,EAAAA,GAAI,CAAA,IAAKC,EAAAA,GAAI,IAAI,OAAO,MAAA;AAC9B,IAAA,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,IAAA,EAAMD,EAAAA,EAAGC,EAAC,CAAA;AAAA,EAC5B;AAEA,EAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,KAAA;AAAA,IAClB,CAAA,YAAA,EAAe,MAAM,CAAA,oCAAA,EAAuC,GAAG,CAAA,SAAA,CAAA;AAAA,IAC/D;AAAA,IACA,OAAA,EAAQ;AACV,EAAA,MAAM,CAAA,GAAI,GAAA,GAAM,IAAA,CAAK,IAAA,CAAK,aAAa,IAAA,GAAO,IAAI,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,KAAA,EAAO,GAAA,CAAI,MAAM,CAAA,GAAI,CAAA;AACxF,EAAA,IAAI,CAAC,GAAA,IAAO,EAAE,CAAA,GAAI,IAAI,OAAO,MAAA;AAC7B,EAAA,MAAM,CAAC,CAAA,EAAG,CAAC,CAAA,GAAI,CAAC,KAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,KAAA,GAAQ,CAAC,CAAC,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,MAAM,GAAA,CAAI,MAAA,GAAS,CAAC,CAAC,CAAC,CAAA;AAC/F,EAAA,OAAO,IAAI,GAAA,CAAI,KAAA;AAAA,IACb,CAAA,YAAA,EAAe,MAAM,CAAA,SAAA,EAAY,CAAC,CAAA,UAAA,EAAa,CAAC,CAAA,eAAA,EAAkB,GAAG,CAAA,SAAA,EAAY,CAAC,CAAA,UAAA,EAAa,CAAC,CAAA,oCAAA,CAAA;AAAA,IAChG;AAAA,GACF,CACG,MAAA,EAAO,CACP,KAAA,EAAM;AACX;AAEA,IAAM,OAAA,GAAU,CAAC,CAAA,KAA0B,CAAA,KAAA,EAAQ,UAAU,CAAC,CAAC,CAAA,QAAA,EAAW,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,CAAE,QAAA,CAAS,QAAQ,CAAC,CAAA,CAAA;AAG3G,SAAS,UAAU,CAAA,EAAuB;AACxC,EAAA,IAAI,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,EAAE,QAAA,CAAS,CAAA,EAAG,EAAE,CAAC,EAAE,SAAA,EAAU,CAAE,UAAA,CAAW,GAAG,GAAG,OAAO,eAAA;AACpF,EAAA,IAAI,CAAA,CAAE,CAAC,CAAA,KAAM,GAAA,IAAQ,EAAE,CAAC,CAAA,KAAM,KAAM,OAAO,YAAA;AAC3C,EAAA,IAAI,CAAA,CAAE,CAAC,CAAA,KAAM,EAAA,IAAQ,CAAA,CAAE,CAAC,CAAA,KAAM,EAAA,IAAQ,CAAA,CAAE,CAAC,CAAA,KAAM,EAAA,EAAM,OAAO,WAAA;AAC5D,EAAA,OAAO,WAAA;AACT","file":"export.js","sourcesContent":["import { onColor, roundHalfEven } from \"../contrast.js\";\nimport { DownloadError, InputError, OptionalDependencyError, warn } from \"../errors.js\";\n\n/** A canvas ratio, width to height: `\"16:9\"`, `\"4x5\"`, a number such as 1.91, or a numeric string such as `\"1.91\"`. */\nexport type Aspect = \"1:1\" | \"16:9\" | \"4:5\" | \"9:16\" | \"1.91:1\" | number | (string & {});\n/** Where the figure sits on the canvas, as in ImageMagick (and sdvplot/sdvplotR `gt_social_crop`). */\nexport type Gravity =\n | \"center\"\n | \"north\"\n | \"south\"\n | \"east\"\n | \"west\"\n | \"northwest\"\n | \"northeast\"\n | \"southwest\"\n | \"southeast\";\n/** Options for {@link toPNG}. */\nexport interface ToPNGOptions {\n /** output width in CSS pixels, the height following (multiplied by `scale`); absent: the SVG's own size */\n width?: number;\n /** device-pixel multiplier, a positive number; default 1 */\n scale?: number;\n /** a color painted under transparent areas; absent: transparent */\n background?: string;\n /**\n * the root `color`, which `currentColor` (Plot's axes and text) resolves to; absent: the SVG's own root `color`, else\n * black or white, whichever contrasts more with `background` (black when there is no background)\n */\n color?: string;\n /**\n * remote `<image href=\"http(s)://…\">` (logos, headshots): `\"fetch\"` (default) downloads each once with the global\n * `fetch` and draws it, a failed download throwing `DownloadError`; `\"skip\"` leaves them out with one warning\n */\n images?: \"fetch\" | \"skip\";\n /**\n * the fonts `<text>` is drawn with. resvg draws text only in fonts it has loaded, and with none (a host without\n * system fonts: slim Docker images, minimal CI runners) every label renders as nothing, with no error. `files`: font\n * files (.ttf, .otf, .ttc) on disk to load, each checked to exist; `defaultFamily`: the family for text whose own\n * families are not loaded (absent: resvg's choice, the first font loaded); `system`: also load the system fonts,\n * default true. Absent: the system fonts only.\n */\n fonts?: { files?: readonly string[]; defaultFamily?: string; system?: boolean };\n}\n/** Options for {@link socialCard}; defaults are Python/R `gt_social_crop`'s. */\nexport interface SocialCardOptions {\n /** the canvas ratio; default `\"1:1\"` */\n aspect?: Aspect;\n /** pixels left around the figure before the canvas grows to the ratio (truncated to an integer); default 60 */\n padding?: number;\n /** the canvas color; default `\"white\"` */\n background?: string;\n /** where the padded figure sits on the canvas; default `\"center\"` */\n gravity?: Gravity;\n /**\n * the canvas root `color`, which the figure's `currentColor` (Plot's axes and text) inherits; absent: black or white,\n * whichever contrasts more with a hex `background` (none for a named background: give `color`, or a hex)\n */\n color?: string;\n}\n/** An SVG's size, from its `width`/`height` attributes or else its `viewBox`. */\nexport interface SvgSize {\n /** user units: the width attribute, else the viewBox width */\n width: number;\n /** user units: the height attribute, else the viewBox height */\n height: number;\n /** the root viewBox, else `0 0 width height` */\n viewBox: string;\n}\n\nconst GRAVITY: readonly Gravity[] = [\n \"center\",\n \"north\",\n \"south\",\n \"east\",\n \"west\",\n \"northwest\",\n \"northeast\",\n \"southwest\",\n \"southeast\",\n];\n/** Port of Python `_check_gravity`: one of the nine magick gravities, case-insensitive; anything else is an InputError. */\nexport function parseGravity(gravity: string): Gravity {\n const g = String(gravity).toLowerCase();\n const hit = GRAVITY.find((x) => x === g);\n if (hit === undefined)\n throw new InputError(`gravity must be one of ${GRAVITY.join(\", \")}, got ${JSON.stringify(gravity)}`);\n return hit;\n}\n/**\n * A CSS color written into markup: names, hex, rgb()/hsl() (with `/` alpha too); never a quote, angle bracket,\n * semicolon or equals sign.\n */\nexport function checkColor(name: string, value: string): string {\n if (!/^[#a-zA-Z0-9(),.%/ -]+$/.test(value))\n throw new InputError(`${name} must be a CSS color, got ${JSON.stringify(value)}`);\n return value;\n}\n\n/** Python `float()` of a string: an underscore is allowed only between two digits (\"1_0\" is 10). */\nconst pyFloat = (s: string | number): number =>\n Number(typeof s === \"string\" ? s.replace(/(?<=\\d)_(?=\\d)/g, \"\") : s);\n\n/** Port of Python `_ratio`: \"16:9\", \"4x5\" (exactly two parts) or a positive finite number, numeric strings included. */\nexport function parseAspect(aspect: Aspect): number {\n let ratio = Number.NaN;\n if (typeof aspect === \"string\" && /[:x]/.test(aspect)) {\n const parts = aspect.split(/[:x]/);\n if (parts.length === 2) ratio = pyFloat(parts[0] as string) / pyFloat(parts[1] as string); // Python `a, b = re.split(...)`\n } else if (typeof aspect === \"number\" || typeof aspect === \"string\") ratio = pyFloat(aspect); // Python float(\"1.91\")\n if (!(ratio > 0 && Number.isFinite(ratio))) {\n throw new InputError(\n `aspect could not be read as a ratio: got ${String(aspect)}; use \"1:1\", \"16:9\", \"4x5\" or a number like 1.91`,\n );\n }\n return ratio;\n}\n\n/** Port of Python `_canvas`: expand the short side until the ratio is met; never shrink. Rounds half to even (Python/R `round`). */\nexport function canvasFor(width: number, height: number, ratio: number): [number, number] {\n return width / height > ratio\n ? [width, roundHalfEven(width / ratio)]\n : [roundHalfEven(height * ratio), height];\n}\n\n/** Port of Python `_extent` placement: the offset of a figure with `dx`/`dy` spare pixels around it. */\nexport function offsetFor(dx: number, dy: number, gravity: Gravity): [number, number] {\n const x = gravity.endsWith(\"west\") ? 0 : gravity.endsWith(\"east\") ? dx : Math.floor(dx / 2);\n const y = gravity.startsWith(\"north\") ? 0 : gravity.startsWith(\"south\") ? dy : Math.floor(dy / 2);\n return [x, y];\n}\n\nconst SVG_NS = \"http://www.w3.org/2000/svg\";\nconst XLINK_NS = \"http://www.w3.org/1999/xlink\";\nconst OPEN_TAG = /<svg\\b[^>]*>/i;\nconst attrOf = (tag: string, name: string): string | undefined =>\n new RegExp(`\\\\s${name}=[\"']([^\"']+)[\"']`, \"i\").exec(tag)?.[1];\n/** A plain or `px` length; a percent or relative unit (`em`) is NaN, so the size comes from the viewBox. */\nconst userUnits = (v: string | undefined): number => Number(v?.trim().replace(/px$/i, \"\") || Number.NaN);\n\n/**\n * The root `<svg>`'s size: `width`/`height` attributes in user units or px, else the viewBox's (D3 output often has\n * only a viewBox; a `100%` size is relative to a container a standalone SVG does not have).\n */\nexport function svgSize(svg: string): SvgSize {\n const open = OPEN_TAG.exec(svg)?.[0];\n if (!open) throw new InputError(\"socialCard: no <svg> root element\");\n const vb = attrOf(open, \"viewBox\");\n const parts = vb\n ?.trim()\n .split(/[\\s,]+/)\n .map(Number);\n const w = userUnits(attrOf(open, \"width\"));\n const h = userUnits(attrOf(open, \"height\"));\n const width = Number.isFinite(w) ? w : parts?.[2];\n const height = Number.isFinite(h) ? h : parts?.[3];\n if (width === undefined || height === undefined || !(width > 0) || !(height > 0)) {\n throw new InputError(\"socialCard: the <svg> needs width/height attributes or a viewBox\");\n }\n return { width, height, viewBox: vb ?? `0 0 ${width} ${height}` };\n}\n\n/**\n * Port of sdvplot/sdvplotR `gt_social_crop` framing for an SVG figure: pad, then extend the short side to the ratio\n * (the figure is never cropped). Arguments are checked in Python's order (ratio, gravity, background, padding) before\n * anything is built. A final rescale (`width=` in Python/R) is `toPNG(socialCard(svg), { width })`.\n */\nexport function socialCard(\n svg: string,\n { aspect = \"1:1\", padding = 60, background = \"white\", gravity = \"center\", color }: SocialCardOptions = {},\n): string {\n const ratio = parseAspect(aspect);\n const place = parseGravity(gravity);\n checkColor(\"background\", background);\n if (color !== undefined) checkColor(\"color\", color);\n if (!(Number.isFinite(padding) && padding >= 0))\n throw new InputError(`padding must be a non-negative number of pixels, got ${String(padding)}`);\n const pad = Math.trunc(padding); // Python _pixels: int(whitespace)\n const { width, height, viewBox } = svgSize(svg);\n const [cw, ch] = canvasFor(width + 2 * pad, height + 2 * pad, ratio);\n const [ox, oy] = offsetFor(cw - (width + 2 * pad), ch - (height + 2 * pad), place);\n // ponytail: contrast is judged for an opaque hex background only; a named one needs `color` (no CSS name table here)\n const ink = color ?? (/^#(?:[0-9a-f]{3}){1,2}$/i.test(background) ? onColor(background) : undefined);\n const open = OPEN_TAG.exec(svg)?.[0] ?? \"<svg>\";\n const stripped = open.replace(/\\s(?:x|y|width|height|viewBox)=[\"'][^\"']*[\"']/gi, \"\");\n // replacer functions: a `$&`, `$'` or `` $` `` in the figure's own attributes is text, not a replacement pattern\n const inner = svg.replace(open, () =>\n stripped.replace(\n /^<svg/i,\n () => `<svg x=\"${ox + pad}\" y=\"${oy + pad}\" width=\"${width}\" height=\"${height}\" viewBox=\"${viewBox}\"`,\n ),\n );\n const inked = ink === undefined ? \"\" : ` color=\"${ink}\"`;\n return `<svg xmlns=\"${SVG_NS}\"${inked} width=\"${cw}\" height=\"${ch}\" viewBox=\"0 0 ${cw} ${ch}\"><rect width=\"${cw}\" height=\"${ch}\" fill=\"${background}\"/>${inner}</svg>`;\n}\n\n/**\n * True when `e` is Node's \"cannot find\" (`ERR_MODULE_NOT_FOUND` / `MODULE_NOT_FOUND`) for the optional peer `name`\n * itself, on the error or under a loader's wrapper (vitest's mock error, say); a file missing inside an installed peer\n * is not.\n *\n * @example\n * ```ts\n * import { createRequire } from \"node:module\";\n * import { peerMissing } from \"@sportsdataverse/sdvplot/export\";\n *\n * // Node's resolver, asked for a package that is not installed, throws MODULE_NOT_FOUND naming it\n * const name = \"@sportsdataverse/no-such-peer\";\n * let error: unknown;\n * try {\n * createRequire(import.meta.url).resolve(name);\n * } catch (e) {\n * error = e;\n * }\n * peerMissing(error, name); // true; peerMissing(error, \"@resvg/resvg-js\") is false\n * ```\n */\nexport function peerMissing(e: unknown, name: string): boolean {\n const quoted = new RegExp(`['\"]${name.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}['\"]`);\n return (\n e instanceof Error &&\n ((/MODULE_NOT_FOUND/.test(String((e as { code?: unknown }).code)) && quoted.test(e.message)) ||\n peerMissing(e.cause, name))\n );\n}\n\n/** The bytes at `url`; a network failure or a non-2xx answer is a DownloadError. */\nasync function download(url: string): Promise<Uint8Array> {\n let r: Response;\n try {\n r = await fetch(url);\n if (r.ok) return new Uint8Array(await r.arrayBuffer());\n } catch (e) {\n throw Object.assign(new DownloadError(`toPNG could not download ${url}: ${String(e)}`, url), {\n cause: e,\n });\n }\n throw new DownloadError(`toPNG could not download ${url}: it answered ${r.status}`, url, r.status);\n}\n\n/** At most this many image downloads in flight at once: a figure with hundreds of logos must not open hundreds of sockets. */\nconst FETCH_LIMIT = 8;\n\n/** `fn` over `xs` with at most `limit` calls in flight; results keep input order, and the first failure stops new calls. */\nasync function mapLimit<T, R>(xs: readonly T[], limit: number, fn: (x: T) => Promise<R>): Promise<R[]> {\n const out = new Array<R>(xs.length);\n let next = 0;\n const worker = async (): Promise<void> => {\n while (next < xs.length) {\n const i = next++;\n try {\n out[i] = await fn(xs[i] as T);\n } catch (e) {\n next = xs.length;\n throw e;\n }\n }\n };\n await Promise.all(Array.from({ length: Math.min(limit, xs.length) }, worker));\n return out;\n}\n\n/** `href=\"url\"` / `xlink:href='url'` attributes, the URL as written or with `&` escaped as `&`. */\nconst hrefAttr = (url: string): RegExp => {\n const re = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return new RegExp(`(href=)([\"'])(?:${re(url)}|${re(url.replaceAll(\"&\", \"&\"))})\\\\2`, \"g\");\n};\n\n/**\n * Rasterize an SVG with `@resvg/resvg-js` (optional peer; Node only). Throws OptionalDependencyError when it is missing.\n * An HTML-serialized figure (a DOM element or its `outerHTML`, as Observable Plot makes) gets the `xmlns` (and\n * `xmlns:xlink`) declarations it lacks. Remote images are downloaded and drawn unless `images` is `\"skip\"`. Text is\n * drawn in the system fonts; on a host without any, pass `fonts.files` or every label comes out blank.\n *\n * @example\n * ```ts\n * import { dirname, join } from \"node:path\";\n * import { fileURLToPath } from \"node:url\";\n * import { toPNG } from \"@sportsdataverse/sdvplot/export\";\n *\n * // any .ttf/.otf on disk; this one is Source Sans 3 (OFL) from the sdvplot-js repo's examples/fonts\n * const font = join(dirname(fileURLToPath(import.meta.url)), \"../../../../fonts/SourceSans3-Regular.ttf\");\n * const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"240\" height=\"60\"><text x=\"8\" y=\"42\" font-size=\"32\">Giants 27</text></svg>`;\n * // the same pixels with or without system fonts: only the given file is loaded\n * await toPNG(svg, { background: \"white\", fonts: { files: [font], system: false } });\n * ```\n */\nexport async function toPNG(\n svg: string | { outerHTML: string },\n { width, scale = 1, background, color, images = \"fetch\", fonts = {} }: ToPNGOptions = {},\n): Promise<Uint8Array> {\n if (!(Number.isFinite(scale) && scale > 0))\n throw new InputError(`scale must be a positive number, got ${String(scale)}`);\n if (width !== undefined && !(Number.isFinite(width) && width > 0))\n throw new InputError(`width must be a positive number of pixels, got ${String(width)}`);\n if (background !== undefined) checkColor(\"background\", background);\n if (color !== undefined) checkColor(\"color\", color);\n if (images !== \"fetch\" && images !== \"skip\")\n throw new InputError(`images must be \"fetch\" or \"skip\", got ${JSON.stringify(images)}`);\n const font = await fontOptions(fonts);\n let text = typeof svg === \"string\" ? svg : svg.outerHTML;\n const mod = await import(\"@resvg/resvg-js\").catch((e: unknown) => {\n throw new OptionalDependencyError(\n peerMissing(e, \"@resvg/resvg-js\")\n ? \"toPNG needs the optional peer @resvg/resvg-js, which is not installed: pnpm add @resvg/resvg-js\"\n : `toPNG could not load the optional peer @resvg/resvg-js (installed, but it failed to load): ${String(e)}`,\n { cause: e },\n );\n });\n const fitTo =\n width !== undefined\n ? { mode: \"width\" as const, value: Math.round(width * scale) }\n : { mode: \"zoom\" as const, value: scale };\n const opts = { fitTo, font, ...(background !== undefined && { background }) };\n\n const open = OPEN_TAG.exec(text)?.[0];\n if (open !== undefined) {\n let add = \"\";\n if (!/\\sxmlns=/i.test(open)) add += ` xmlns=\"${SVG_NS}\"`; // resvg parses XML; HTML serialization drops xmlns\n if (/\\sxlink:[\\w-]+=/i.test(text) && !/\\sxmlns:xlink=/i.test(open)) add += ` xmlns:xlink=\"${XLINK_NS}\"`;\n let ink = color;\n if (\n ink === undefined &&\n background !== undefined &&\n !/\\scolor=/i.test(open) &&\n /currentcolor/i.test(text)\n ) {\n // the background as resvg paints it (names and rgb() too): a 1x1 render\n // ponytail: pixels are premultiplied, so a translucent background is judged as if over black\n const p = new mod.Resvg(`<svg xmlns=\"${SVG_NS}\" width=\"1\" height=\"1\"/>`, { background }).render()\n .pixels;\n ink = onColor(`#${[p[0], p[1], p[2]].map((v) => (v ?? 0).toString(16).padStart(2, \"0\")).join(\"\")}`);\n }\n let tag = open;\n if (ink !== undefined) {\n tag = tag.replace(/\\scolor=[\"'][^\"']*[\"']/i, \"\"); // an explicit `color` option replaces the figure's own\n add += ` color=\"${ink}\"`;\n }\n text = text.replace(open, () => tag.replace(/^<svg/i, () => `<svg${add}`));\n }\n\n // the remote hrefs resvg cannot load itself, from a parse without the system-font scan (text does not change them)\n const probe = new mod.Resvg(text, NO_FONTS);\n const hrefs = [...new Set(probe.imagesToResolve())];\n let bodies: Uint8Array[] = [];\n if (hrefs.length > 0 && images === \"skip\") {\n warn(\n `toPNG:skip:${hrefs.join(\" \")}`,\n `toPNG left out ${hrefs.length} remote image(s): ${hrefs.join(\", \")}`,\n );\n } else if (hrefs.length > 0) {\n bodies = await mapLimit(hrefs, FETCH_LIMIT, download);\n const zoom = fitTo.mode === \"width\" ? fitTo.value / probe.width : scale;\n const tags = text.match(/<image\\b[^>]*>/gi) ?? [];\n // every image goes in as a data URI, so ONE Resvg draws them all: resolveImage costs ~150 ms a call\n for (const [i, href] of hrefs.entries()) {\n const b = bodies[i] as Uint8Array;\n let uri = dataURI(b);\n const re = hrefAttr(href);\n const small = shrink(\n mod,\n uri,\n tags.filter((t) => t.search(re) >= 0),\n zoom,\n );\n if (small !== undefined) uri = dataURI(small);\n text = text.replace(re, (_m, attr: string, q: string) => `${attr}${q}${uri}${q}`);\n }\n }\n const resvg = new mod.Resvg(text, opts);\n if (bodies.length > 0) {\n // an href escaped in a way hrefAttr does not match is still drawn, the slow way (resolveImage: raster only)\n const left = new Set(resvg.imagesToResolve());\n for (const [i, href] of hrefs.entries())\n if (left.has(href)) resvg.resolveImage(href, Buffer.from(bodies[i] as Uint8Array));\n }\n return new Uint8Array(resvg.render().asPng());\n}\n\nconst NO_FONTS = { font: { loadSystemFonts: false } };\n\n/** `fonts` as resvg's `font` option; a missing font file is an InputError (resvg would skip it, drawing no text). */\nasync function fontOptions(fonts: ToPNGOptions[\"fonts\"]) {\n if (typeof fonts !== \"object\" || fonts === null || Array.isArray(fonts))\n throw new InputError(\n `fonts must be an object { files, defaultFamily, system }, got ${JSON.stringify(fonts)}`,\n );\n const { files = [], defaultFamily, system = true } = fonts;\n if (!Array.isArray(files) || files.some((f) => typeof f !== \"string\"))\n throw new InputError(`fonts.files must be an array of font file paths, got ${JSON.stringify(files)}`);\n if (defaultFamily !== undefined && typeof defaultFamily !== \"string\")\n throw new InputError(`fonts.defaultFamily must be a string, got ${JSON.stringify(defaultFamily)}`);\n if (typeof system !== \"boolean\")\n throw new InputError(`fonts.system must be a boolean, got ${JSON.stringify(system)}`);\n if (files.length > 0) {\n const { statSync } = await import(\"node:fs\"); // ponytail: loaded only when files are given; /export is Node-only\n for (const f of files)\n if (!statSync(f, { throwIfNoEntry: false })?.isFile())\n throw new InputError(`fonts.files: no font file at ${JSON.stringify(f)}`);\n }\n return {\n loadSystemFonts: system,\n fontFiles: [...files],\n ...(defaultFamily !== undefined && { defaultFontFamily: defaultFamily }),\n };\n}\n/** A shared image's one raster is this many times its largest box: headroom for a transform that enlarges it. */\nconst OVERSAMPLE = 2;\n\n/**\n * resvg decodes an `<image>` per element, at the image's own size: 2000 uses of a 500 px logo held 2 GB for 6 s (a\n * `<use>` or resolveImage costs the same). An image used more than once is therefore drawn once, at OVERSAMPLE times its\n * largest box in output pixels, and every use inlines that small PNG. A single use keeps the original.\n */\nfunction shrink(\n mod: typeof import(\"@resvg/resvg-js\"),\n uri: string,\n uses: readonly string[],\n zoom: number,\n): Uint8Array | undefined {\n if (uses.length < 2) return undefined;\n let side = 0;\n for (const tag of uses) {\n const w = userUnits(attrOf(tag, \"width\"));\n const h = userUnits(attrOf(tag, \"height\"));\n // ponytail: a use without a numeric size (auto, %) keeps the full image, and its per-use decode\n if (!(w > 0 && h > 0)) return undefined;\n side = Math.max(side, w, h);\n }\n // the image's own size, read from its header (an image with no width/height is drawn at it)\n const own = new mod.Resvg(\n `<svg xmlns=\"${SVG_NS}\" width=\"1\" height=\"1\"><image href=\"${uri}\"/></svg>`,\n NO_FONTS,\n ).getBBox();\n const k = own ? Math.ceil(OVERSAMPLE * zoom * side) / Math.max(own.width, own.height) : 1;\n if (!own || !(k < 1)) return undefined;\n const [w, h] = [Math.max(1, Math.round(own.width * k)), Math.max(1, Math.round(own.height * k))];\n return new mod.Resvg(\n `<svg xmlns=\"${SVG_NS}\" width=\"${w}\" height=\"${h}\"><image href=\"${uri}\" width=\"${w}\" height=\"${h}\" preserveAspectRatio=\"none\"/></svg>`,\n NO_FONTS,\n )\n .render()\n .asPng();\n}\n\nconst dataURI = (b: Uint8Array): string => `data:${imageType(b)};base64,${Buffer.from(b).toString(\"base64\")}`;\n\n/** A downloaded image's data-URI type from its first bytes: SVG markup, JPEG, GIF, else PNG. */\nfunction imageType(b: Uint8Array): string {\n if (new TextDecoder().decode(b.subarray(0, 64)).trimStart().startsWith(\"<\")) return \"image/svg+xml\";\n if (b[0] === 0xff && b[1] === 0xd8) return \"image/jpeg\";\n if (b[0] === 0x47 && b[1] === 0x49 && b[2] === 0x46) return \"image/gif\";\n return \"image/png\";\n}\n"]}
|
package/dist/g2.d.ts
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { R as Row, a as DrawnMark } from './_web-D7Aa5Rd8.js';
|
|
2
|
+
export { e as embedSources } from './_web-D7Aa5Rd8.js';
|
|
3
|
+
import { E as EspnHeadshotLeague } from './headshots-B2J_LOO8.js';
|
|
4
|
+
import { L as League, S as SeasonInput, V as Variant, M as MarkType, I as IdSystem, W as Which, H as HeadshotIdSystem } from './types-C7n6gJF3.js';
|
|
5
|
+
import './resolve-CQW-jzvr.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The G2 5 adapter, and so Ant Design Charts (`@ant-design/plots`), whose configs are G2 specs underneath: team colours
|
|
9
|
+
* as a `scale.color` (`teamColorScale`) or a list (`teamColorPalette`), logos / wordmarks / headshots as G2 `image`
|
|
10
|
+
* marks (`logoMark`, `wordmarkMark`, `headshotMark`), and logos as a band axis' labels (`axisLogos`). Each returns
|
|
11
|
+
* plain spec pieces: a mark goes in a view's `children` (`chart.options(...)`) or an Ant Design Charts config's
|
|
12
|
+
* `annotations`, a scale in `scale.color`, the axis labels in `axis.x`. Pure: @antv/g2 is never imported (the types
|
|
13
|
+
* are structural, checked against G2 5.4's own). Call after `loadLeague(league)` / `preloadAll()`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** `teamColorPalette` / `teamColorScale` options. */
|
|
17
|
+
interface G2ColorOptions {
|
|
18
|
+
/** "primary" (default) or "secondary" colour. */
|
|
19
|
+
which?: Which;
|
|
20
|
+
/** The season the teams are resolved for (a number or string). */
|
|
21
|
+
season?: SeasonInput;
|
|
22
|
+
/** How the team values are read. Default "auto". */
|
|
23
|
+
idSystem?: IdSystem;
|
|
24
|
+
/** The colour of an unresolved team. Default "#808080". */
|
|
25
|
+
fallback?: string;
|
|
26
|
+
/**
|
|
27
|
+
* The chart's background. Given, a team colour under 3:1 contrast against it is swapped for the team's other
|
|
28
|
+
* colour, else moved towards white (on a dark background) or black (a light one) until it reads: dark mode. A
|
|
29
|
+
* colour that would then sit too close to an earlier team's (CIE76 ΔE under 20) takes the team's other colour, or
|
|
30
|
+
* the nearest colour to its own that is distinct and still reads; failing that it is kept, with one warning.
|
|
31
|
+
*/
|
|
32
|
+
background?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* One colour per team, in order, for anything that takes colours by position. With `background`, every colour keeps
|
|
36
|
+
* 3:1 contrast against it (dark mode) and aims to stay distinct from the others. `teamColorScale` puts the same
|
|
37
|
+
* colours on a G2 `scale.color`, keyed by team.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
42
|
+
* import { teamColorPalette } from "@sportsdataverse/sdvplot/g2";
|
|
43
|
+
*
|
|
44
|
+
* await loadLeague("nfl");
|
|
45
|
+
* teamColorPalette("nfl", ["KC", "LAC", "DEN", "LV"], { background: "#141414" });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
declare function teamColorPalette(league: League, teams: readonly unknown[], o?: G2ColorOptions): string[];
|
|
49
|
+
/** A G2 `scale.color`: an ordinal scale from each team value to its colour. */
|
|
50
|
+
interface G2ColorScale<T = unknown> {
|
|
51
|
+
/** Ordinal: a numeric team column (ESPN ids, say) keeps one colour per id, never a gradient. */
|
|
52
|
+
type: "ordinal";
|
|
53
|
+
/** The team values exactly as the data holds them (numbers stay numbers), each once and in order. */
|
|
54
|
+
domain: T[];
|
|
55
|
+
/** Their colours, in the same order. */
|
|
56
|
+
range: string[];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Team colours as a G2 `scale.color` (`{ type: "ordinal", domain, range }`): the mark's `encode.color` field holds the
|
|
60
|
+
* team values, and each draws in its own colour; the legend lists the teams in this order. With `background` the
|
|
61
|
+
* colours read on it (dark mode). Pass the teams as the data holds them: G2 matches the domain to the data by exact
|
|
62
|
+
* value, so a numeric id stays a number here (12, not "12"); a value given twice (also 12 and "12") is kept once.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
67
|
+
* import { teamColorScale } from "@sportsdataverse/sdvplot/g2";
|
|
68
|
+
*
|
|
69
|
+
* await loadLeague("nfl");
|
|
70
|
+
* // { type: "interval", encode: { x: "team", y: "wins", color: "team" }, scale: { color: <this> } }
|
|
71
|
+
* teamColorScale("nfl", ["KC", "LAC", "DEN", "LV"]);
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
declare function teamColorScale<T>(league: League, teams: readonly T[], o?: G2ColorOptions): G2ColorScale<T>;
|
|
75
|
+
interface Shared {
|
|
76
|
+
/** Image height in px, 1 or more (G2 sizes an image mark in px). Default 24. */
|
|
77
|
+
size?: number;
|
|
78
|
+
/** Opacity in [0, 1]. Default 1. */
|
|
79
|
+
alpha?: number;
|
|
80
|
+
/** url → data URI map from `embedSources`, to inline the images. */
|
|
81
|
+
embed?: ReadonlyMap<string, string>;
|
|
82
|
+
}
|
|
83
|
+
/** `logoMark` / `wordmarkMark` options. */
|
|
84
|
+
interface G2MarkOptions extends Shared {
|
|
85
|
+
/** Name of the row field holding each mark's x position (the field the chart's x channel encodes). */
|
|
86
|
+
x: string;
|
|
87
|
+
/** Name of the row field holding each mark's y position. */
|
|
88
|
+
y: string;
|
|
89
|
+
/** Name of the row field holding the team (abbreviation, name or id; see `idSystem`). */
|
|
90
|
+
team: string;
|
|
91
|
+
/** The league the teams belong to, e.g. "nfl". */
|
|
92
|
+
league: League;
|
|
93
|
+
/** A season (number or string) for every row, or the name of a row field holding each row's season. */
|
|
94
|
+
season?: SeasonInput;
|
|
95
|
+
/** Logo variant, e.g. "dark". Default "default". */
|
|
96
|
+
variant?: Variant;
|
|
97
|
+
/** How the `team` values are read. Default "auto". */
|
|
98
|
+
idSystem?: IdSystem;
|
|
99
|
+
}
|
|
100
|
+
/** `headshotMark` options. */
|
|
101
|
+
interface G2HeadshotOptions extends Shared {
|
|
102
|
+
/** Name of the row field holding each headshot's x position. */
|
|
103
|
+
x: string;
|
|
104
|
+
/** Name of the row field holding each headshot's y position. */
|
|
105
|
+
y: string;
|
|
106
|
+
/** Name of the row field holding the player id. */
|
|
107
|
+
player: string;
|
|
108
|
+
/** The league the players belong to, e.g. "nfl". */
|
|
109
|
+
league: EspnHeadshotLeague;
|
|
110
|
+
/** ESPN athlete ids, or nflverse gsis ids (needs `loadGsis()` first). Default "espn". */
|
|
111
|
+
idSystem?: HeadshotIdSystem;
|
|
112
|
+
}
|
|
113
|
+
/** One datum of an sdvplot image mark: the caller's row and what it draws. */
|
|
114
|
+
type G2ImageDatum = Row & {
|
|
115
|
+
/** The resolved team id or player id. */
|
|
116
|
+
sdv_id: string;
|
|
117
|
+
/** The image's URL (or data URI). */
|
|
118
|
+
sdv_src: string;
|
|
119
|
+
/** Its size in px. */
|
|
120
|
+
sdv_width: number;
|
|
121
|
+
sdv_height: number;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* A G2 `image` mark (G2 5's `ImageMark`, structurally): one image per drawable row, centred on the row's (x, y) in
|
|
125
|
+
* the view's own scales. Out of the tooltip and the legend.
|
|
126
|
+
*/
|
|
127
|
+
interface G2ImageMark {
|
|
128
|
+
type: "image";
|
|
129
|
+
data: G2ImageDatum[];
|
|
130
|
+
encode: {
|
|
131
|
+
x: string;
|
|
132
|
+
y: string;
|
|
133
|
+
src: "sdv_src";
|
|
134
|
+
};
|
|
135
|
+
style: {
|
|
136
|
+
width: (d: G2ImageDatum) => number;
|
|
137
|
+
height: (d: G2ImageDatum) => number;
|
|
138
|
+
opacity: number;
|
|
139
|
+
};
|
|
140
|
+
tooltip: false;
|
|
141
|
+
legend: false;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Team logos as a G2 `image` mark: one logo per row, `size` px tall, centred on the row's (`x`, `y`) fields in the
|
|
145
|
+
* view's scales. Put it last in a view's `children` (G2: `chart.options({ type: "view", children: [..., mark] })`), or
|
|
146
|
+
* in an Ant Design Charts config's `annotations`, which reach G2 as they are. A team with no logo draws nothing and
|
|
147
|
+
* warns once.
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
152
|
+
* import { logoMark } from "@sportsdataverse/sdvplot/g2";
|
|
153
|
+
*
|
|
154
|
+
* await loadLeague("nfl");
|
|
155
|
+
* const rows = [
|
|
156
|
+
* { team: "KC", pf: 385, pa: 326 },
|
|
157
|
+
* { team: "LAC", pf: 402, pa: 301 },
|
|
158
|
+
* ];
|
|
159
|
+
* // chart.options({ type: "view", data: rows, children: [{ type: "point", encode: { x: "pf", y: "pa" } }, <this>] })
|
|
160
|
+
* logoMark(rows, { x: "pf", y: "pa", team: "team", league: "nfl", size: 32 });
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
declare function logoMark(rows: readonly Row[], o: G2MarkOptions): G2ImageMark;
|
|
164
|
+
/**
|
|
165
|
+
* Team wordmarks as a G2 `image` mark, `size` px tall at their own aspect ratio; as `logoMark` otherwise.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
170
|
+
* import { drawnMarks, wordmarkMark } from "@sportsdataverse/sdvplot/g2";
|
|
171
|
+
*
|
|
172
|
+
* await loadLeague("nfl");
|
|
173
|
+
* // [team id, x, y, height in px, wordmark URL]: each wordmark 20 px tall, as wide as its own shape
|
|
174
|
+
* drawnMarks(wordmarkMark([{ team: "LV", wins: 4, week: 18 }], { x: "week", y: "wins", team: "team", league: "nfl", size: 20 }));
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
declare function wordmarkMark(rows: readonly Row[], o: G2MarkOptions): G2ImageMark;
|
|
178
|
+
/**
|
|
179
|
+
* ESPN player headshots as a G2 `image` mark, from the row's `player` field; an id with no headshot draws nothing and
|
|
180
|
+
* warns once. `idSystem: "gsis"` needs `loadGsis()` first.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* import { headshotMark } from "@sportsdataverse/sdvplot/g2";
|
|
185
|
+
*
|
|
186
|
+
* // Patrick Mahomes (ESPN id 3139477) at his 2024 passing yards and touchdowns, 40 px tall
|
|
187
|
+
* const rows = [{ qb: "3139477", yards: 3928, tds: 26 }];
|
|
188
|
+
* headshotMark(rows, { x: "yards", y: "tds", player: "qb", league: "nfl", size: 40 }).data;
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
declare function headshotMark(rows: readonly Row[], o: G2HeadshotOptions): G2ImageMark;
|
|
192
|
+
/** `axisLogos` options: the axis' categories are the teams. */
|
|
193
|
+
interface G2AxisOptions {
|
|
194
|
+
/** The league the teams belong to, e.g. "nfl". */
|
|
195
|
+
league: League;
|
|
196
|
+
/** The season the axis' teams are resolved for. */
|
|
197
|
+
season?: SeasonInput;
|
|
198
|
+
/** Image height in px, 1 or more. Default 24. */
|
|
199
|
+
size?: number;
|
|
200
|
+
/** Logo variant, e.g. "dark". Default "default". */
|
|
201
|
+
variant?: Variant;
|
|
202
|
+
/** "logo" (default) or "wordmark". */
|
|
203
|
+
markType?: MarkType;
|
|
204
|
+
/** How the axis categories are read. Default "auto". */
|
|
205
|
+
idSystem?: IdSystem;
|
|
206
|
+
/** url → data URI map from `embedSources`, to inline the images. */
|
|
207
|
+
embed?: ReadonlyMap<string, string>;
|
|
208
|
+
}
|
|
209
|
+
/** What G2 hands an axis' `labelRender`: the subset read here. */
|
|
210
|
+
interface G2AxisDatum {
|
|
211
|
+
/** The tick's label: the category, as the axis' `labelFormatter` shows it. */
|
|
212
|
+
label?: unknown;
|
|
213
|
+
}
|
|
214
|
+
/** Axis options for a G2 band axis whose categories are teams: spread them into `axis.x`. */
|
|
215
|
+
interface G2AxisLabels {
|
|
216
|
+
/** HTML for each label: the team's logo, or the category's own text where it has none. */
|
|
217
|
+
labelRender: (datum: G2AxisDatum) => string;
|
|
218
|
+
/** G2's theme draws labels at 0.45 opacity: a logo is drawn whole. */
|
|
219
|
+
labelOpacity: 1;
|
|
220
|
+
/** No label is hidden or rotated: each logo box is one band wide, so they never overlap. */
|
|
221
|
+
labelAutoHide: false;
|
|
222
|
+
labelAutoRotate: false;
|
|
223
|
+
transform: [];
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Team logos as a horizontal band axis' labels (a column chart's `axis.x`), through G2's `labelRender`: each label is
|
|
227
|
+
* HTML, the logo `size` px tall and centred on its tick. Needs `@antv/component` 2.1.12 or later (G2's axis package,
|
|
228
|
+
* an optional peer): 2.1.11 and earlier lay an HTML label out differently, and each logo would sit half a band left. A category with no logo keeps its text and warns once. G2
|
|
229
|
+
* lays an HTML label out one band wide along a horizontal axis only; for a vertical axis (a bar chart's, or under
|
|
230
|
+
* `transpose`) draw a `logoMark` at the axis' position instead. HTML labels are page elements over the chart, not
|
|
231
|
+
* pixels on its canvas: a `toDataURL` export leaves them out.
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```ts
|
|
235
|
+
* import { loadLeague } from "@sportsdataverse/sdvplot";
|
|
236
|
+
* import { axisLogos } from "@sportsdataverse/sdvplot/g2";
|
|
237
|
+
*
|
|
238
|
+
* await loadLeague("nfl");
|
|
239
|
+
* // { type: "interval", encode: { x: "team", y: "wins" }, axis: { x: { ...<this>, title: false } } }
|
|
240
|
+
* axisLogos({ league: "nfl", size: 28 }).labelRender({ label: "KC" });
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
declare function axisLogos(o: G2AxisOptions): G2AxisLabels;
|
|
244
|
+
/**
|
|
245
|
+
* Test hook: [id, x, y, height in px, url] per image of every sdvplot mark in `spec`: a mark, a list of them, a G2
|
|
246
|
+
* spec (its `children`, at any depth) or an Ant Design Charts config (its `children` and `annotations`). The height is
|
|
247
|
+
* what the mark's own style hands G2.
|
|
248
|
+
*/
|
|
249
|
+
declare function drawnMarks(spec: unknown): DrawnMark[];
|
|
250
|
+
|
|
251
|
+
export { DrawnMark, type G2AxisDatum, type G2AxisLabels, type G2AxisOptions, type G2ColorOptions, type G2ColorScale, type G2HeadshotOptions, type G2ImageDatum, type G2ImageMark, type G2MarkOptions, Row, axisLogos, drawnMarks, headshotMark, logoMark, teamColorPalette, teamColorScale, wordmarkMark };
|