aboutsummaryrefslogtreecommitdiffhomepage
path: root/public/bluebey-studio/src/zip.js
diff options
context:
space:
mode:
authorYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-07 23:09:40 +0900
committerYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-07 23:09:40 +0900
commitbc2821adb11a30244fc4663f4fafb756877d9508 (patch)
treefea8d30d2eb1e67f895097dbf7ec4d152361243e /public/bluebey-studio/src/zip.js
parent0d50c5ede0812ba7b67b775c9cd0bf52d7b65e02 (diff)
bluebey-studio: public/ の外へ移動し非公開化
Diffstat (limited to 'public/bluebey-studio/src/zip.js')
-rw-r--r--public/bluebey-studio/src/zip.js218
1 files changed, 0 insertions, 218 deletions
diff --git a/public/bluebey-studio/src/zip.js b/public/bluebey-studio/src/zip.js
deleted file mode 100644
index 21a1955..0000000
--- a/public/bluebey-studio/src/zip.js
+++ /dev/null
@@ -1,218 +0,0 @@
-/**
- * Store-only ZIP writer.
- *
- * The studio exports a whole batch of images at once (one PNG per comic panel
- * or sticker), and a single download is far nicer to handle than a dozen files,
- * so the batch is packed into one archive here.
- *
- * PNG payloads are already DEFLATE-compressed, so running deflate over them a
- * second time costs time and saves nothing measurable: every entry is stored
- * verbatim (compression method 0, `compressedSize === uncompressedSize`) and no
- * data descriptor is needed.
- *
- * The module has no imports, never touches the DOM beyond `Blob`, and stamps
- * entries with a fixed DOS date unless one is passed in, so the same input
- * always produces a byte-for-byte identical archive. That keeps the output safe
- * to cache, diff, hash and test.
- *
- * Archive layout (every integer little-endian, no extra fields, no comments, no
- * directory entries):
- *
- * [local file header + stored data] one per file
- * [central directory entry] one per file
- * [end of central directory record]
- */
-
-/** Signature of a local file header ("PK\x03\x04"). */
-const LOCAL_SIGNATURE = 0x04034b50;
-/** Signature of a central directory entry ("PK\x01\x02"). */
-const CENTRAL_SIGNATURE = 0x02014b50;
-/** Signature of the end of central directory record ("PK\x05\x06"). */
-const EOCD_SIGNATURE = 0x06054b50;
-
-/** ZIP 2.0 is the oldest version that covers everything this writer emits. */
-const VERSION_NEEDED = 20;
-/** General purpose bit 11: the entry name is UTF-8, not CP437. */
-const FLAG_UTF8 = 0x0800;
-/** Compression method 0: stored. */
-const METHOD_STORE = 0;
-
-/** Largest value a 32-bit ZIP field can hold. Nothing may reach 4 GiB. */
-const MAX_FIELD = 0xffffffff;
-/** Largest value a 16-bit ZIP field can hold (entry count, name length). */
-const MAX_SHORT = 0xffff;
-
-const LOCAL_HEADER_SIZE = 30;
-const CENTRAL_HEADER_SIZE = 46;
-const EOCD_SIZE = 22;
-
-/** 1980-01-01 00:00 in DOS form: year offset 0, month 1, day 1, midnight. */
-const DEFAULT_TIME = 0;
-const DEFAULT_DATE = (1 << 5) | 1;
-
-/**
- * CRC-32 (polynomial 0xEDB88320, reflected), the checksum every ZIP entry must
- * carry. The table is built once at module load; eight table-driven bits per
- * byte is fast enough that a several-megabyte PNG batch stays imperceptible.
- */
-const CRC_TABLE = (() => {
- const table = new Uint32Array(256);
- for (let i = 0; i < 256; i++) {
- let c = i;
- for (let bit = 0; bit < 8; bit++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
- table[i] = c >>> 0;
- }
- return table;
-})();
-
-/**
- * @param {Uint8Array} bytes
- * @returns {number} unsigned 32-bit CRC-32
- */
-export function crc32(bytes) {
- let c = 0xffffffff;
- for (let i = 0; i < bytes.length; i++) c = CRC_TABLE[(c ^ bytes[i]) & 0xff] ^ (c >>> 8);
- return (c ^ 0xffffffff) >>> 0;
-}
-
-/** Clip a Date into the DOS date/time pair stored in the headers. */
-function toDosDateTime(date) {
- const year = date.getFullYear();
- // The DOS epoch starts in 1980; anything older is clamped to the epoch.
- if (year < 1980) return { time: DEFAULT_TIME, date: DEFAULT_DATE };
- const time = (date.getHours() << 11) | (date.getMinutes() << 5) | (date.getSeconds() >> 1);
- const day = ((year - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate();
- return { time: time & MAX_SHORT, date: day & MAX_SHORT };
-}
-
-/**
- * Entry names live inside the archive, where the separator is always "/": a
- * backslash (what Windows paths use) would be taken as part of the name, and a
- * leading slash would look like an absolute path to some extractors.
- */
-function normalizeName(name) {
- return String(name ?? '')
- .replace(/\\/g, '/')
- .replace(/^\/+/, '');
-}
-
-/** Accept any byte source, plus plain strings meaning UTF-8 text. */
-function toBytes(data, encoder) {
- if (typeof data === 'string') return encoder.encode(data);
- if (data instanceof Uint8Array) return data;
- if (ArrayBuffer.isView(data)) {
- return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
- }
- if (data instanceof ArrayBuffer) return new Uint8Array(data);
- throw new TypeError('ZIP: file data must be a Uint8Array, an ArrayBuffer or a string');
-}
-
-function writeU16(view, offset, value) {
- view.setUint16(offset, value & MAX_SHORT, true);
-}
-
-function writeU32(view, offset, value) {
- view.setUint32(offset, value >>> 0, true);
-}
-
-/**
- * Pack `files` into a stored (uncompressed) ZIP archive.
- *
- * @param {Array<{name: string, data: Uint8Array | string}>} files
- * a string `data` is encoded as UTF-8 text
- * @param {object} [options]
- * @param {Date} [options.date]
- * timestamp for every entry; defaults to 1980-01-01 00:00 so that the same
- * input always yields the same bytes
- * @returns {Blob} an `application/zip` blob
- */
-export function createZip(files, { date } = {}) {
- const encoder = new TextEncoder();
- const stamp = date ? toDosDateTime(date) : { time: DEFAULT_TIME, date: DEFAULT_DATE };
-
- // First pass: normalise the input and measure it, so the output buffer can be
- // allocated exactly once instead of being grown and copied.
- const entries = [];
- let localTotal = 0;
- let centralTotal = 0;
-
- for (const file of files ?? []) {
- const nameBytes = encoder.encode(normalizeName(file.name));
- const data = toBytes(file.data, encoder);
-
- if (data.length > MAX_FIELD) {
- throw new Error('ZIP: a file is 4 GiB or larger; zip64 is not supported');
- }
- if (nameBytes.length > MAX_SHORT) {
- throw new Error('ZIP: a file name is longer than 65535 bytes');
- }
-
- const length = LOCAL_HEADER_SIZE + nameBytes.length + data.length;
- localTotal += length;
- centralTotal += CENTRAL_HEADER_SIZE + nameBytes.length;
- entries.push({ nameBytes, data, crc: crc32(data), length, offset: 0 });
- }
-
- if (entries.length > MAX_SHORT) {
- throw new Error('ZIP: more than 65535 files; zip64 is not supported');
- }
- if (localTotal + centralTotal + EOCD_SIZE > MAX_FIELD) {
- throw new Error('ZIP: the archive is 4 GiB or larger; zip64 is not supported');
- }
-
- const bytes = new Uint8Array(localTotal + centralTotal + EOCD_SIZE);
- const view = new DataView(bytes.buffer);
-
- let offset = 0;
- for (const entry of entries) {
- entry.offset = offset;
- writeU32(view, offset + 0, LOCAL_SIGNATURE);
- writeU16(view, offset + 4, VERSION_NEEDED);
- writeU16(view, offset + 6, FLAG_UTF8);
- writeU16(view, offset + 8, METHOD_STORE);
- writeU16(view, offset + 10, stamp.time);
- writeU16(view, offset + 12, stamp.date);
- writeU32(view, offset + 14, entry.crc);
- writeU32(view, offset + 18, entry.data.length);
- writeU32(view, offset + 22, entry.data.length);
- writeU16(view, offset + 26, entry.nameBytes.length);
- writeU16(view, offset + 28, 0); // extra field length
- bytes.set(entry.nameBytes, offset + LOCAL_HEADER_SIZE);
- bytes.set(entry.data, offset + LOCAL_HEADER_SIZE + entry.nameBytes.length);
- offset += entry.length;
- }
-
- const centralOffset = offset;
- for (const entry of entries) {
- writeU32(view, offset + 0, CENTRAL_SIGNATURE);
- writeU16(view, offset + 4, VERSION_NEEDED); // version made by (host 0 = MS-DOS)
- writeU16(view, offset + 6, VERSION_NEEDED);
- writeU16(view, offset + 8, FLAG_UTF8);
- writeU16(view, offset + 10, METHOD_STORE);
- writeU16(view, offset + 12, stamp.time);
- writeU16(view, offset + 14, stamp.date);
- writeU32(view, offset + 16, entry.crc);
- writeU32(view, offset + 20, entry.data.length);
- writeU32(view, offset + 24, entry.data.length);
- writeU16(view, offset + 28, entry.nameBytes.length);
- writeU16(view, offset + 30, 0); // extra field length
- writeU16(view, offset + 32, 0); // file comment length
- writeU16(view, offset + 34, 0); // disk number start
- writeU16(view, offset + 36, 0); // internal attributes
- writeU32(view, offset + 38, 0); // external attributes
- writeU32(view, offset + 42, entry.offset);
- bytes.set(entry.nameBytes, offset + CENTRAL_HEADER_SIZE);
- offset += CENTRAL_HEADER_SIZE + entry.nameBytes.length;
- }
-
- writeU32(view, offset + 0, EOCD_SIGNATURE);
- writeU16(view, offset + 4, 0); // number of this disk
- writeU16(view, offset + 6, 0); // disk holding the central directory
- writeU16(view, offset + 8, entries.length);
- writeU16(view, offset + 10, entries.length);
- writeU32(view, offset + 12, offset - centralOffset);
- writeU32(view, offset + 16, centralOffset);
- writeU16(view, offset + 20, 0); // archive comment length
-
- return new Blob([bytes], { type: 'application/zip' });
-}