UNPKG

@loaders.gl/polyfills

Version:

Polyfills for TextEncoder/TextDecoder

178 lines (159 loc) 5.44 kB
// loaders.gl // SPDX-License-Identifier: MIT // Copyright vis.gl contributors // Forked from @gozala's web-blob under MIT license https://github.com/Gozala/web-blob import {BlobStream} from './blob-stream'; /** * Forked from @gozala's web-blob under MIT license * @see https://github.com/Gozala/web-blob */ export class BlobPolyfill { // implements Blob { /** The MIME type of the data contained in the Blob. If type is unknown, string is empty. */ readonly type: string; /** The size, in bytes, of the data contained in the Blob object. */ size: number; private parts: Uint8Array[]; /** * @param [init] * @param [options] */ constructor(init: BlobPart[] = [], options: BlobPropertyBag = {}) { this.parts = []; this.size = 0; for (const part of init) { if (typeof part === 'string') { const bytes = new TextEncoder().encode(part); this.parts.push(bytes); this.size += bytes.byteLength; } else if (part instanceof BlobPolyfill) { this.size += part.size; // @ts-ignore - `parts` is marked private so TS will complain about // accessing it. this.parts.push(...part.parts); } else if (part instanceof ArrayBuffer) { this.parts.push(new Uint8Array(part)); this.size += part.byteLength; } else if (part instanceof Uint8Array) { this.parts.push(part); this.size += part.byteLength; } else if (ArrayBuffer.isView(part)) { const {buffer, byteOffset, byteLength} = part; this.parts.push(new Uint8Array(buffer, byteOffset, byteLength)); this.size += byteLength; } else { const bytes = new TextEncoder().encode(String(part)); this.parts.push(bytes); this.size += bytes.byteLength; } } /** @private */ this.type = readType(options.type); } /** * Returns a new Blob object containing the data in the specified range of * bytes of the blob on which it's called. * @param start=0 - An index into the Blob indicating the first * byte to include in the new Blob. If you specify a negative value, it's * treated as an offset from the end of the Blob toward the beginning. For * example, `-10` would be the 10th from last byte in the Blob. The default * value is `0`. If you specify a value for start that is larger than the * size of the source Blob, the returned Blob has size 0 and contains no * data. * @param end - An index into the `Blob` indicating the first byte * that will *not* be included in the new `Blob` (i.e. the byte exactly at * this index is not included). If you specify a negative value, it's treated * as an offset from the end of the Blob toward the beginning. For example, * `-10` would be the 10th from last byte in the `Blob`. The default value is * size. * @param type - The content type to assign to the new Blob; * this will be the value of its type property. The default value is an empty * string. */ slice(start: number = 0, end: number = this.size, type: string = ''): Blob { const {size, parts: parts} = this; let offset = start < 0 ? Math.max(size + start, 0) : Math.min(start, size); let limit = end < 0 ? Math.max(size + end, 0) : Math.min(end, size); const span = Math.max(limit - offset, 0); const blob = new BlobPolyfill([], {type}); if (span === 0) { // @ts-ignore return blob; } let blobSize = 0; const blobParts: Uint8Array[] = []; for (const part of parts) { const {byteLength} = part; if (offset > 0 && byteLength <= offset) { offset -= byteLength; limit -= byteLength; } else { const chunk = part.subarray(offset, Math.min(byteLength, limit)); blobParts.push(chunk); blobSize += chunk.byteLength; // no longer need to take that into account offset = 0; // don't add the overflow to new blobParts if (blobSize >= span) { break; } } } blob.parts = blobParts; blob.size = blobSize; // @ts-ignore return blob; } /** * Returns a promise that resolves with an ArrayBuffer containing the entire * contents of the Blob as binary data. */ // eslint-disable-next-line require-await async arrayBuffer(): Promise<ArrayBuffer> { return this._toArrayBuffer(); } /** * Returns a promise that resolves with a USVString containing the entire * contents of the Blob interpreted as UTF-8 text. */ // eslint-disable-next-line require-await async text(): Promise<string> { const decoder = new TextDecoder(); let text = ''; for (const part of this.parts) { text += decoder.decode(part); } return text; } /** */ // @ts-ignore stream(): BlobStream<any> { return new BlobStream<any>(this.parts); } /** * @returns {string} */ toString() { return '[object Blob]'; } get [Symbol.toStringTag]() { return 'Blob'; } _toArrayBuffer(): ArrayBuffer { const buffer = new ArrayBuffer(this.size); const bytes = new Uint8Array(buffer); let offset = 0; for (const part of this.parts) { bytes.set(part, offset); offset += part.byteLength; } return buffer; } } /** */ function readType(input: string = ''): string { const type = String(input).toLowerCase(); return /[^\u0020-\u007E]/.test(type) ? '' : type; }