feat(diagram-render): __downscaleRaster for print-resolution image normalization

Data-URI rasters re-encode in their own format (JPEG stays JPEG at q0.9 —
PNG-encoding photos bloats them) at an explicit target pixel width. Used by
make-pdf's pre-pass for the 300dpi content-box ceiling (eng-review D4).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan 2026-06-11 23:59:40 -07:00
parent a311cb1ec5
commit 69bf3b07a1
No known key found for this signature in database
GPG Key ID: C1F69E85C74EFE1D
3 changed files with 35 additions and 3 deletions

View File

@ -1,7 +1,7 @@
{
"name": "gstack-diagram-render",
"sha256": "9b1da250ef93f176bfb7b96c6eb735645a879702539bb6a58c378273d90aca23",
"bytes": 9644924,
"sha256": "0ee91aef5a8da85c8941c26ebf2991bbeba82412644bb070d5c5dd2e23538b81",
"bytes": 9645503,
"bunVersion": "1.3.13",
"deps": {
"@excalidraw/excalidraw": "0.18.0",

File diff suppressed because one or more lines are too long

View File

@ -34,6 +34,7 @@ declare global {
__mermaidToExcalidraw: (text: string) => Promise<string>;
__excalidrawToSvg: (sceneJson: string) => Promise<string>;
__rasterize: (svgText: string, targetWidthPx: number) => Promise<string>;
__downscaleRaster: (dataUri: string, targetWidthPx: number, mime: string) => Promise<string>;
__mountForScreenshot: (svgText: string, targetWidthPx: number) => string;
__probeImage: (src: string) => Promise<string>;
EXCALIDRAW_ASSET_PATH?: string;
@ -152,6 +153,37 @@ window.__mountForScreenshot = (svgText: string, targetWidthPx: number): string =
return `mounted:${targetWidthPx}`;
};
/**
* Downscale a raster image (data URI) to targetWidthPx, preserving aspect.
* Re-encodes in the requested mime JPEG photos stay JPEG (q0.9); PNG-encoding
* a photo would bloat it past the original. Data URIs are same-origin, so the
* canvas never taints.
*/
window.__downscaleRaster = async (
dataUri: string,
targetWidthPx: number,
mime: string,
): Promise<string> => {
if (!(targetWidthPx > 0 && targetWidthPx <= 10000)) {
throw new Error(`targetWidthPx out of range: ${targetWidthPx}`);
}
const img = new Image();
await new Promise<void>((resolve, reject) => {
img.onload = () => resolve();
img.onerror = () => reject(new Error("image decode failed"));
img.src = dataUri;
});
const scale = targetWidthPx / (img.naturalWidth || targetWidthPx);
const canvas = document.createElement("canvas");
canvas.width = Math.round(img.naturalWidth * scale);
canvas.height = Math.round(img.naturalHeight * scale);
const ctx = canvas.getContext("2d");
if (!ctx) throw new Error("2d canvas context unavailable");
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const outMime = mime === "image/jpeg" ? "image/jpeg" : "image/png";
return outMime === "image/jpeg" ? canvas.toDataURL(outMime, 0.9) : canvas.toDataURL(outMime);
};
/** Probe intrinsic dimensions of an image (data URI or URL). Returns JSON. */
window.__probeImage = async (src: string): Promise<string> => {
const img = new Image();