import fs from "node:fs";
import path from "node:path";
import zlib from "node:zlib";
import { strToU8, zipSync } from "fflate";
import { CUBE_STL } from "./helpers";
/** Upload fixtures, built in code so every variation stays readable next to the test using it. */
export const stl = (): Uint8Array => fs.readFileSync(CUBE_STL);
const CRC_TABLE = Array.from({ length: 256 }, (_, n) => {
let c = n;
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
return c >>> 0;
});
function crc32(buf: Buffer): number {
let c = 0xffffffff;
for (const byte of buf) c = CRC_TABLE[(c ^ byte) & 0xff] ^ (c >>> 8);
return (c ^ 0xffffffff) >>> 0;
}
function pngChunk(type: string, data: Buffer): Buffer {
const len = Buffer.alloc(4);
len.writeUInt32BE(data.length);
const body = Buffer.concat([Buffer.from(type, "ascii"), data]);
const crc = Buffer.alloc(4);
crc.writeUInt32BE(crc32(body));
return Buffer.concat([len, body, crc]);
}
/** A solid-colour RGB PNG; real enough for the server to decode and thumbnail. */
export function png([r, g, b]: [number, number, number] = [200, 80, 40], size = 64): Uint8Array {
const ihdr = Buffer.alloc(13);
ihdr.writeUInt32BE(size, 0);
ihdr.writeUInt32BE(size, 4);
ihdr[8] = 8; // bit depth
ihdr[9] = 2; // RGB
const row = Buffer.concat([Buffer.from([0]), Buffer.from(Array.from({ length: size }, () => [r, g, b]).flat())]);
const raw = Buffer.concat(Array.from({ length: size }, () => row));
return Buffer.concat([
Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
pngChunk("IHDR", ihdr),
pngChunk("IDAT", zlib.deflateSync(raw)),
pngChunk("IEND", Buffer.alloc(0)),
]);
}
const CUBE_MODEL = `
`;
/** A 10mm cube as a 3MF package; with `thumbnail`, it carries the embedded preview slicers write. */
export function threeMf({ thumbnail = true } = {}): Uint8Array {
const files: Record = {
"[Content_Types].xml": strToU8(
`` +
`` +
`` +
``,
),
"_rels/.rels": strToU8(
`` +
`` +
``,
),
"3D/3dmodel.model": strToU8(CUBE_MODEL),
};
if (thumbnail) files["Metadata/thumbnail.png"] = png([40, 120, 220]);
return zipSync(files);
}
export const zip = (entries: Record): Uint8Array => zipSync(entries);
export const text = (s: string): Uint8Array => strToU8(s);
/** Writes `files` (relative path -> content) under `dir` and returns `dir`, ready for a folder pick. */
export function writeTree(dir: string, files: Record): string {
for (const [rel, content] of Object.entries(files)) {
const abs = path.join(dir, rel);
fs.mkdirSync(path.dirname(abs), { recursive: true });
fs.writeFileSync(abs, content);
}
return dir;
}
/** Shape `setFiles` takes for files that never touch disk. */
export const file = (name: string, buffer: Uint8Array, mimeType = "application/octet-stream") => ({
name,
mimeType,
buffer: Buffer.from(buffer),
});