UNPKG

@tldraw/utils

Version:

tldraw infinite canvas SDK (private utilities).

8 lines (7 loc) 6.01 kB
{ "version": 3, "sources": ["../../../src/lib/media/apng.ts"], "sourcesContent": ["/*!\n * MIT License: https://github.com/vHeemstra/is-apng/blob/main/license\n * Copyright (c) Philip van Heemstra\n */\n\nexport function isApngAnimated(buffer: ArrayBuffer): boolean {\n\tconst view = new Uint8Array(buffer)\n\n\tif (\n\t\t!view ||\n\t\t!((typeof Buffer !== 'undefined' && Buffer.isBuffer(view)) || view instanceof Uint8Array) ||\n\t\tview.length < 16\n\t) {\n\t\treturn false\n\t}\n\n\tconst isPNG =\n\t\tview[0] === 0x89 &&\n\t\tview[1] === 0x50 &&\n\t\tview[2] === 0x4e &&\n\t\tview[3] === 0x47 &&\n\t\tview[4] === 0x0d &&\n\t\tview[5] === 0x0a &&\n\t\tview[6] === 0x1a &&\n\t\tview[7] === 0x0a\n\n\tif (!isPNG) {\n\t\treturn false\n\t}\n\n\t/**\n\t * Returns the index of the first occurrence of a sequence in an typed array, or -1 if it is not present.\n\t *\n\t * Works similar to `Array.prototype.indexOf()`, but it searches for a sequence of array values (bytes).\n\t * The bytes in the `haystack` array are decoded (UTF-8) and then used to search for `needle`.\n\t *\n\t * @param haystack `Uint8Array`\n\t * Array to search in.\n\t *\n\t * @param needle `string | RegExp`\n\t * The value to locate in the array.\n\t *\n\t * @param fromIndex `number`\n\t * The array index at which to begin the search.\n\t *\n\t * @param upToIndex `number`\n\t * The array index up to which to search.\n\t * If omitted, search until the end.\n\t *\n\t * @param chunksize `number`\n\t * Size of the chunks used when searching (default 1024).\n\t *\n\t * @returns boolean\n\t * Whether the array holds Animated PNG data.\n\t */\n\tfunction indexOfSubstring(\n\t\thaystack: Uint8Array,\n\t\tneedle: string | RegExp,\n\t\tfromIndex: number,\n\t\tupToIndex?: number,\n\t\tchunksize = 1024 /* Bytes */\n\t) {\n\t\t/**\n\t\t * Adopted from: https://stackoverflow.com/a/67771214/2142071\n\t\t */\n\n\t\tif (!needle) {\n\t\t\treturn -1\n\t\t}\n\t\tneedle = new RegExp(needle, 'g')\n\n\t\t// The needle could get split over two chunks.\n\t\t// So, at every chunk we prepend the last few characters\n\t\t// of the last chunk.\n\t\tconst needle_length = needle.source.length\n\t\tconst decoder = new TextDecoder()\n\n\t\t// Handle search offset in line with\n\t\t// `Array.prototype.indexOf()` and `TypedArray.prototype.subarray()`.\n\t\tconst full_haystack_length = haystack.length\n\t\tif (typeof upToIndex === 'undefined') {\n\t\t\tupToIndex = full_haystack_length\n\t\t}\n\t\tif (fromIndex >= full_haystack_length || upToIndex <= 0 || fromIndex >= upToIndex) {\n\t\t\treturn -1\n\t\t}\n\t\thaystack = haystack.subarray(fromIndex, upToIndex)\n\n\t\tlet position = -1\n\t\tlet current_index = 0\n\t\tlet full_length = 0\n\t\tlet needle_buffer = ''\n\n\t\touter: while (current_index < haystack.length) {\n\t\t\tconst next_index = current_index + chunksize\n\t\t\t// subarray doesn't copy\n\t\t\tconst chunk = haystack.subarray(current_index, next_index)\n\t\t\tconst decoded = decoder.decode(chunk, { stream: true })\n\n\t\t\tconst text = needle_buffer + decoded\n\n\t\t\tlet match: RegExpExecArray | null\n\t\t\tlet last_index = -1\n\t\t\twhile ((match = needle.exec(text)) !== null) {\n\t\t\t\tlast_index = match.index - needle_buffer.length\n\t\t\t\tposition = full_length + last_index\n\t\t\t\tbreak outer\n\t\t\t}\n\n\t\t\tcurrent_index = next_index\n\t\t\tfull_length += decoded.length\n\n\t\t\t// Check that the buffer doesn't itself include the needle\n\t\t\t// this would cause duplicate finds (we could also use a Set to avoid that).\n\t\t\tconst needle_index =\n\t\t\t\tlast_index > -1 ? last_index + needle_length : decoded.length - needle_length\n\t\t\tneedle_buffer = decoded.slice(needle_index)\n\t\t}\n\n\t\t// Correct for search offset.\n\t\tif (position >= 0) {\n\t\t\tposition += fromIndex >= 0 ? fromIndex : full_haystack_length + fromIndex\n\t\t}\n\n\t\treturn position\n\t}\n\n\t// APNGs have an animation control chunk ('acTL') preceding the IDATs.\n\t// See: https://en.wikipedia.org/wiki/APNG#File_format\n\tconst idatIdx = indexOfSubstring(view, 'IDAT', 12)\n\tif (idatIdx >= 12) {\n\t\tconst actlIdx = indexOfSubstring(view, 'acTL', 8, idatIdx)\n\t\treturn actlIdx >= 8\n\t}\n\n\treturn false\n}\n\n// globalThis.isApng = isApng\n\n// (new TextEncoder()).encode('IDAT')\n// Decimal: [73, 68, 65, 84]\n// Hex: [0x49, 0x44, 0x41, 0x54]\n\n// (new TextEncoder()).encode('acTL')\n// Decimal: [97, 99, 84, 76]\n// Hex: [0x61, 0x63, 0x54, 0x4C]\n\n// const idatIdx = buffer.indexOf('IDAT')\n// const actlIdx = buffer.indexOf('acTL')\n"], "mappings": "AAAA;AAAA;AAAA;AAAA;AAKO,SAAS,eAAe,QAA8B;AAC5D,QAAM,OAAO,IAAI,WAAW,MAAM;AAElC,MACC,CAAC,QACD,EAAG,OAAO,WAAW,eAAe,OAAO,SAAS,IAAI,KAAM,gBAAgB,eAC9E,KAAK,SAAS,IACb;AACD,WAAO;AAAA,EACR;AAEA,QAAM,QACL,KAAK,CAAC,MAAM,OACZ,KAAK,CAAC,MAAM,MACZ,KAAK,CAAC,MAAM,MACZ,KAAK,CAAC,MAAM,MACZ,KAAK,CAAC,MAAM,MACZ,KAAK,CAAC,MAAM,MACZ,KAAK,CAAC,MAAM,MACZ,KAAK,CAAC,MAAM;AAEb,MAAI,CAAC,OAAO;AACX,WAAO;AAAA,EACR;AA2BA,WAAS,iBACR,UACA,QACA,WACA,WACA,YAAY,MACX;AAKD,QAAI,CAAC,QAAQ;AACZ,aAAO;AAAA,IACR;AACA,aAAS,IAAI,OAAO,QAAQ,GAAG;AAK/B,UAAM,gBAAgB,OAAO,OAAO;AACpC,UAAM,UAAU,IAAI,YAAY;AAIhC,UAAM,uBAAuB,SAAS;AACtC,QAAI,OAAO,cAAc,aAAa;AACrC,kBAAY;AAAA,IACb;AACA,QAAI,aAAa,wBAAwB,aAAa,KAAK,aAAa,WAAW;AAClF,aAAO;AAAA,IACR;AACA,eAAW,SAAS,SAAS,WAAW,SAAS;AAEjD,QAAI,WAAW;AACf,QAAI,gBAAgB;AACpB,QAAI,cAAc;AAClB,QAAI,gBAAgB;AAEpB,UAAO,QAAO,gBAAgB,SAAS,QAAQ;AAC9C,YAAM,aAAa,gBAAgB;AAEnC,YAAM,QAAQ,SAAS,SAAS,eAAe,UAAU;AACzD,YAAM,UAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAEtD,YAAM,OAAO,gBAAgB;AAE7B,UAAI;AACJ,UAAI,aAAa;AACjB,cAAQ,QAAQ,OAAO,KAAK,IAAI,OAAO,MAAM;AAC5C,qBAAa,MAAM,QAAQ,cAAc;AACzC,mBAAW,cAAc;AACzB,cAAM;AAAA,MACP;AAEA,sBAAgB;AAChB,qBAAe,QAAQ;AAIvB,YAAM,eACL,aAAa,KAAK,aAAa,gBAAgB,QAAQ,SAAS;AACjE,sBAAgB,QAAQ,MAAM,YAAY;AAAA,IAC3C;AAGA,QAAI,YAAY,GAAG;AAClB,kBAAY,aAAa,IAAI,YAAY,uBAAuB;AAAA,IACjE;AAEA,WAAO;AAAA,EACR;AAIA,QAAM,UAAU,iBAAiB,MAAM,QAAQ,EAAE;AACjD,MAAI,WAAW,IAAI;AAClB,UAAM,UAAU,iBAAiB,MAAM,QAAQ,GAAG,OAAO;AACzD,WAAO,WAAW;AAAA,EACnB;AAEA,SAAO;AACR;", "names": [] }