Files
2026-08-28 16:41:54 -05:00

81 lines
3.0 KiB
TypeScript

import sharp from "sharp";
/** Real encoded images, generated rather than checked in, so a fixture can never drift from what the
* pipeline actually accepts. */
export async function makeImage(options: {
readonly width: number;
readonly height: number;
readonly format: "png" | "jpeg" | "webp";
readonly alpha?: boolean;
readonly colour?: { r: number; g: number; b: number };
}): Promise<Uint8Array> {
const colour = options.colour ?? { b: 40, g: 120, r: 200 };
const pipeline = sharp({
create: {
background: { ...colour, alpha: options.alpha === true ? 0.5 : 1 },
channels: options.alpha === true ? 4 : 3,
height: options.height,
width: options.width,
},
});
const encoded =
options.format === "png"
? await pipeline.png().toBuffer()
: options.format === "jpeg"
? await pipeline.jpeg().toBuffer()
: await pipeline.webp().toBuffer();
return new Uint8Array(encoded);
}
/** A genuine two-frame animated WebP. Signage refuses animation: a signage slide is a still frame,
* and an animated one would loop out of step with the authoritative slot deadline. */
export async function makeAnimatedWebp(): Promise<Uint8Array> {
const frame = (red: number) =>
sharp({
create: { background: { alpha: 1, b: 0, g: 0, r: red }, channels: 4, height: 8, width: 8 },
})
.raw()
.toBuffer();
const frames = Buffer.concat([await frame(255), await frame(0)]);
const encoded = await sharp(frames, {
raw: { channels: 4, height: 16, pageHeight: 8, width: 8 },
})
.webp({ delay: [100, 100], loop: 0 })
.toBuffer();
return new Uint8Array(encoded);
}
/** A genuine two-frame animated GIF. GIF is refused outright, but animation is detected first, so
* this proves the *animation* rule rather than the format rule. */
export async function makeAnimatedGif(): Promise<Uint8Array> {
const frame = (red: number) =>
sharp({
create: { background: { alpha: 1, b: 0, g: 0, r: red }, channels: 4, height: 8, width: 8 },
})
.raw()
.toBuffer();
const frames = Buffer.concat([await frame(255), await frame(0)]);
const encoded = await sharp(frames, {
raw: { channels: 4, height: 16, pageHeight: 8, width: 8 },
})
.gif({ delay: [100, 100], loop: 0 })
.toBuffer();
return new Uint8Array(encoded);
}
export function bytesOf(value: string): Uint8Array {
return new TextEncoder().encode(value);
}
/** A minimal, genuinely well-formed SVG. Signage refuses it: an SVG is a document with script and
* external-reference capability, not a raster image. */
export const SVG_DOCUMENT =
'<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><rect width="10" height="10"/></svg>';
/** The first bytes of a PDF. Refused for the same reason as everything else that is not a still
* raster image. */
export const PDF_DOCUMENT = "%PDF-1.7\n1 0 obj<</Type/Catalog>>endobj\ntrailer<</Root 1 0 R>>";
/** A GIF89a header with one tiny frame. */
export const GIF_DOCUMENT_BASE64 = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";