mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-12 10:00:35 +08:00
macOS saves Retina screenshots at 144 DPI and we rendered them at twice the size they had on screen. We now scale PNGs with a 2x or 3x density down; everything else, including 300 DPI print exports, stays 1:1.
113 lines
3.3 KiB
TypeScript
113 lines
3.3 KiB
TypeScript
import { describe, expect, test } from "vitest";
|
|
|
|
import { basename, embedPreviewKind, retinaWidth } from "@app/lib/embeds";
|
|
|
|
describe("basename", () => {
|
|
test.each([
|
|
["/home/alice/cat.png", "cat.png"],
|
|
["C:\\Users\\alice\\cat.png", "cat.png"],
|
|
["cat.png", "cat.png"],
|
|
])("of %j is %j", (path, expected) => {
|
|
expect(basename(path)).toBe(expected);
|
|
});
|
|
});
|
|
|
|
test.each([
|
|
["image/png", "image"],
|
|
["application/pdf", "document"],
|
|
["video/mp4", "video"],
|
|
["audio/ogg", "audio"],
|
|
["text/plain", undefined],
|
|
[null, undefined],
|
|
])("a %j embed previews as %j", (mimeType, expected) => {
|
|
expect(embedPreviewKind(mimeType)).toBe(expected);
|
|
});
|
|
|
|
function u32(n: number): number[] {
|
|
return [(n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff];
|
|
}
|
|
|
|
function bytesOf(text: string): number[] {
|
|
return Array.from(text, c => c.charCodeAt(0));
|
|
}
|
|
|
|
function chunk(type: string, data: number[]): number[] {
|
|
return [...u32(data.length), ...bytesOf(type), ...data, 0, 0, 0, 0];
|
|
}
|
|
|
|
const signature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
|
|
|
function ihdr(width: number): number[] {
|
|
return chunk("IHDR", [...u32(width), ...u32(100), 8, 6, 0, 0, 0]);
|
|
}
|
|
|
|
function png(width: number, ...chunks: number[][]): Uint8Array {
|
|
return new Uint8Array([
|
|
...signature,
|
|
...ihdr(width),
|
|
...chunks.flat(),
|
|
...chunk("IEND", []),
|
|
]);
|
|
}
|
|
|
|
function pHYs(x: number, y: number = x, unit: number = 1): number[] {
|
|
return chunk("pHYs", [...u32(x), ...u32(y), unit]);
|
|
}
|
|
|
|
const retina = 5669;
|
|
|
|
describe("retinaWidth", () => {
|
|
test("halves a 144 DPI PNG", () => {
|
|
expect(retinaWidth(png(3680, pHYs(retina)))).toBe(1840);
|
|
});
|
|
|
|
test("divides a 216 DPI PNG by three", () => {
|
|
expect(retinaWidth(png(3000, pHYs(8504)))).toBe(1000);
|
|
});
|
|
|
|
test("finds pHYs after other chunks", () => {
|
|
const text = chunk("tEXt", bytesOf("a\0b"));
|
|
expect(retinaWidth(png(3680, text, pHYs(retina)))).toBe(1840);
|
|
});
|
|
|
|
test("reads bytes at an offset in a larger buffer", () => {
|
|
const bytes = png(3680, pHYs(retina));
|
|
const buffer = new Uint8Array(bytes.length + 3);
|
|
buffer.set(bytes, 3);
|
|
expect(retinaWidth(buffer.subarray(3))).toBe(1840);
|
|
});
|
|
|
|
test.each([
|
|
["72 DPI", png(3680, pHYs(2835))],
|
|
["300 DPI", png(3680, pHYs(11811))],
|
|
["a density of zero", png(3680, pHYs(0))],
|
|
["the maximum density", png(3680, pHYs(0xffffffff))],
|
|
["no pHYs chunk", png(3680)],
|
|
["an unknown unit", png(3680, pHYs(retina, retina, 0))],
|
|
["non-square pixels", png(3680, pHYs(retina, 2835))],
|
|
["pHYs after IDAT", png(3680, chunk("IDAT", [0]), pHYs(retina))],
|
|
[
|
|
"a pHYs chunk of the wrong length",
|
|
png(3680, chunk("pHYs", [...u32(retina), ...u32(retina), 1, 0])),
|
|
],
|
|
["a truncated pHYs chunk", png(3680, pHYs(retina)).subarray(0, 45)],
|
|
[
|
|
"no IHDR chunk first",
|
|
new Uint8Array([...signature, ...pHYs(retina), ...ihdr(3680)]),
|
|
],
|
|
["a truncated IHDR chunk", png(3680, pHYs(retina)).subarray(0, 18)],
|
|
])("ignores a PNG with %s", (_, bytes) => {
|
|
expect(retinaWidth(bytes)).toBeUndefined();
|
|
});
|
|
|
|
test.each([
|
|
["empty", new Uint8Array()],
|
|
[
|
|
"a JPEG",
|
|
new Uint8Array([0xff, 0xd8, 0xff, 0xe0, ...new Array(30).fill(0)]),
|
|
],
|
|
])("ignores %s input", (_, bytes) => {
|
|
expect(retinaWidth(bytes)).toBeUndefined();
|
|
});
|
|
});
|