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), });