diff --git a/docs/theme/layouts/default.hbs b/docs/theme/layouts/default.hbs
index 049bec258..ff804bd5a 100644
--- a/docs/theme/layouts/default.hbs
+++ b/docs/theme/layouts/default.hbs
@@ -95,6 +95,11 @@
PlatformIO (Abstract)
+
+
+ DenoIO
+
+
NodeIO
diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts
index db7203371..239bc210d 100644
--- a/packages/core/src/core.ts
+++ b/packages/core/src/core.ts
@@ -27,7 +27,7 @@ export {
COPY_IDENTITY,
} from './properties';
export { Graph, GraphEdge } from 'property-graph';
-export { PlatformIO, NodeIO, WebIO, ReaderContext, WriterContext } from './io';
+export { DenoIO, PlatformIO, NodeIO, WebIO, ReaderContext, WriterContext } from './io';
export {
BufferUtils,
ColorUtils,
diff --git a/packages/core/src/io/deno-io.ts b/packages/core/src/io/deno-io.ts
new file mode 100644
index 000000000..ccfc71ffd
--- /dev/null
+++ b/packages/core/src/io/deno-io.ts
@@ -0,0 +1,69 @@
+import { PlatformIO } from '.';
+
+declare global {
+ const Deno: {
+ readFile: (path: string) => Promise;
+ readTextFile: (path: string) => Promise;
+ };
+}
+
+interface Path {
+ resolve(directory: string, path: string): string;
+ dirname(uri: string): string;
+}
+
+/**
+ * # DenoIO
+ *
+ * *I/O service for Deno.*
+ *
+ * The most common use of the I/O service is to read/write a {@link Document} with a given path.
+ * Methods are also available for converting in-memory representations of raw glTF files, both
+ * binary (*ArrayBuffer*) and JSON ({@link JSONDocument}).
+ *
+ * Usage:
+ *
+ * ```typescript
+ * import { DenoIO } from 'https://esm.sh/@gltf-transform/core';
+ * import * as path from 'https://deno.land/std/path/mod.ts';
+ *
+ * const io = new DenoIO(path);
+ *
+ * // Read.
+ * let document;
+ * document = io.read('model.glb'); // → Document
+ * document = io.readBinary(glb); // Uint8Array → Document
+ *
+ * // Write.
+ * const glb = io.writeBinary(document); // Document → Uint8Array
+ * ```
+ *
+ * @category I/O
+ */
+export class DenoIO extends PlatformIO {
+ private _path: Path;
+
+ constructor(path: unknown) {
+ super();
+ this._path = path as Path;
+ }
+
+ protected async readURI(uri: string, type: 'view'): Promise;
+ protected async readURI(uri: string, type: 'text'): Promise;
+ protected async readURI(uri: string, type: 'view' | 'text'): Promise {
+ switch (type) {
+ case 'view':
+ return Deno.readFile(uri);
+ case 'text':
+ return Deno.readTextFile(uri);
+ }
+ }
+
+ protected resolve(directory: string, path: string): string {
+ return this._path.resolve(directory, path);
+ }
+
+ protected dirname(uri: string): string {
+ return this._path.dirname(uri);
+ }
+}
diff --git a/packages/core/src/io/index.ts b/packages/core/src/io/index.ts
index a5a5db99c..5582efbfe 100644
--- a/packages/core/src/io/index.ts
+++ b/packages/core/src/io/index.ts
@@ -1,4 +1,5 @@
export { NodeIO } from './node-io';
+export { DenoIO } from './deno-io';
export { PlatformIO } from './platform-io';
export { WebIO } from './web-io';
export { ReaderOptions } from './reader';
diff --git a/packages/core/src/io/node-io.ts b/packages/core/src/io/node-io.ts
index 9b0b7d32d..79e5eea4a 100644
--- a/packages/core/src/io/node-io.ts
+++ b/packages/core/src/io/node-io.ts
@@ -1,7 +1,5 @@
import { Format } from '../constants';
import { Document } from '../document';
-import { JSONDocument } from '../json-document';
-import { GLTF } from '../types/gltf';
import { FileUtils } from '../utils/';
import { PlatformIO } from './platform-io';
@@ -37,12 +35,6 @@ export class NodeIO extends PlatformIO {
private _fs;
private _path;
- /** @hidden */
- public lastReadBytes = 0;
-
- /** @hidden */
- public lastWriteBytes = 0;
-
/** Constructs a new NodeIO service. Instances are reusable. */
constructor() {
super();
@@ -51,72 +43,39 @@ export class NodeIO extends PlatformIO {
this._path = require('path');
}
- /**********************************************************************************************
- * Public.
- */
-
- /** Loads a local path and returns a {@link Document} instance. */
- public async read(uri: string): Promise {
- return await this.readJSON(await this.readAsJSON(uri));
+ protected async readURI(uri: string, type: 'view'): Promise;
+ protected async readURI(uri: string, type: 'text'): Promise;
+ protected async readURI(uri: string, type: 'view' | 'text'): Promise {
+ switch (type) {
+ case 'view':
+ return this._fs.readFile(uri);
+ case 'text':
+ return this._fs.readFile(uri, 'utf8');
+ }
}
- /** Loads a local path and returns a {@link JSONDocument} struct, without parsing. */
- public async readAsJSON(uri: string): Promise {
- const isGLB = !!(uri.match(/\.glb$/) || uri.match(/^data:application\/octet-stream;/));
- return isGLB ? this._readGLB(uri) : this._readGLTF(uri);
+ protected resolve(directory: string, path: string): string {
+ return this._path.resolve(directory, path);
}
- /** Writes a {@link Document} instance to a local path. */
- public async write(uri: string, doc: Document): Promise {
- const isGLB = !!uri.match(/\.glb$/);
- await (isGLB ? this._writeGLB(uri, doc) : this._writeGLTF(uri, doc));
+ protected dirname(uri: string): string {
+ return this._path.dirname(uri);
}
/**********************************************************************************************
- * Protected.
+ * Public.
*/
- /** @internal */
- private async _readResourcesExternal(jsonDoc: JSONDocument, dir: string): Promise {
- const images = jsonDoc.json.images || [];
- const buffers = jsonDoc.json.buffers || [];
- const resources = [...images, ...buffers].map(async (resource: GLTF.IBuffer | GLTF.IImage) => {
- if (resource.uri && !resource.uri.match(/data:/)) {
- const absURI = this._path.resolve(dir, resource.uri);
- jsonDoc.resources[resource.uri] = await this._fs.readFile(absURI);
- this.lastReadBytes += jsonDoc.resources[resource.uri].byteLength;
- }
- });
- await Promise.all(resources);
+ /** Writes a {@link Document} instance to a local path. */
+ public async write(uri: string, doc: Document): Promise {
+ const isGLB = !!uri.match(/\.glb$/);
+ await (isGLB ? this._writeGLB(uri, doc) : this._writeGLTF(uri, doc));
}
/**********************************************************************************************
* Private.
*/
- /** @internal */
- private async _readGLB(uri: string): Promise {
- const buffer: Buffer = await this._fs.readFile(uri);
- this.lastReadBytes = buffer.byteLength;
- const jsonDoc = this._binaryToJSON(buffer);
- // Read external resources first, before Data URIs are replaced.
- await this._readResourcesExternal(jsonDoc, this._path.dirname(uri));
- await this._readResourcesInternal(jsonDoc);
- return jsonDoc;
- }
-
- /** @internal */
- private async _readGLTF(uri: string): Promise {
- this.lastReadBytes = 0;
- const jsonContent = await this._fs.readFile(uri, 'utf8');
- this.lastReadBytes += jsonContent.length;
- const jsonDoc = { json: JSON.parse(jsonContent), resources: {} } as JSONDocument;
- // Read external resources first, before Data URIs are replaced.
- await this._readResourcesExternal(jsonDoc, this._path.dirname(uri));
- await this._readResourcesInternal(jsonDoc);
- return jsonDoc;
- }
-
/** @internal */
private async _writeGLTF(uri: string, doc: Document): Promise {
this.lastWriteBytes = 0;
diff --git a/packages/core/src/io/platform-io.ts b/packages/core/src/io/platform-io.ts
index 0684602cf..e79e50fa1 100644
--- a/packages/core/src/io/platform-io.ts
+++ b/packages/core/src/io/platform-io.ts
@@ -23,7 +23,7 @@ type PublicWriterOptions = Partial>;
* Methods are also available for converting in-memory representations of raw glTF files, both
* binary (*ArrayBuffer*) and JSON ({@link JSONDocument}).
*
- * For platform-specific implementations, see {@link NodeIO} and {@link WebIO}.
+ * For platform-specific implementations, see {@link NodeIO}, {@link WebIO}, and {@link DenoIO}.
*
* @category I/O
*/
@@ -33,6 +33,12 @@ export abstract class PlatformIO {
private _dependencies: { [key: string]: unknown } = {};
private _vertexLayout = VertexLayout.INTERLEAVED;
+ /** @hidden */
+ public lastReadBytes = 0;
+
+ /** @hidden */
+ public lastWriteBytes = 0;
+
/** Sets the {@link Logger} used by this I/O instance. Defaults to Logger.DEFAULT_INSTANCE. */
public setLogger(logger: Logger): this {
this._logger = logger;
@@ -64,11 +70,148 @@ export abstract class PlatformIO {
}
/**********************************************************************************************
- * Common.
+ * Abstract.
+ */
+
+ protected abstract readURI(uri: string, type: 'view'): Promise;
+ protected abstract readURI(uri: string, type: 'text'): Promise;
+ protected abstract readURI(uri: string, type: 'view' | 'text'): Promise;
+
+ protected abstract resolve(directory: string, path: string): string;
+ protected abstract dirname(uri: string): string;
+
+ /**********************************************************************************************
+ * Public Read API.
+ */
+
+ /** Reads a {@link Document} from the given URI. */
+ public async read(uri: string): Promise {
+ return await this.readJSON(await this.readAsJSON(uri));
+ }
+
+ /** Loads a URI and returns a {@link JSONDocument} struct, without parsing. */
+ public async readAsJSON(uri: string): Promise {
+ const isGLB = uri.match(/^data:application\/octet-stream;/) || FileUtils.extension(uri) === 'glb';
+ return isGLB ? this._readGLB(uri) : this._readGLTF(uri);
+ }
+
+ /** Converts glTF-formatted JSON and a resource map to a {@link Document}. */
+ public async readJSON(jsonDoc: JSONDocument): Promise {
+ jsonDoc = this._copyJSON(jsonDoc);
+ this._readResourcesInternal(jsonDoc);
+ return GLTFReader.read(jsonDoc, {
+ extensions: Array.from(this._extensions),
+ dependencies: this._dependencies,
+ logger: this._logger,
+ });
+ }
+
+ /** Converts a GLB-formatted ArrayBuffer to a {@link JSONDocument}. */
+ public async binaryToJSON(glb: Uint8Array): Promise {
+ const jsonDoc = this._binaryToJSON(BufferUtils.assertView(glb));
+ this._readResourcesInternal(jsonDoc);
+ const json = jsonDoc.json;
+
+ // Check for external references, which can't be resolved by this method.
+ if (json.buffers && json.buffers.some((bufferDef) => isExternalBuffer(jsonDoc, bufferDef))) {
+ throw new Error('Cannot resolve external buffers with binaryToJSON().');
+ } else if (json.images && json.images.some((imageDef) => isExternalImage(jsonDoc, imageDef))) {
+ throw new Error('Cannot resolve external images with binaryToJSON().');
+ }
+
+ return jsonDoc;
+ }
+
+ /** Converts a GLB-formatted ArrayBuffer to a {@link Document}. */
+ public async readBinary(glb: Uint8Array): Promise {
+ return this.readJSON(await this.binaryToJSON(BufferUtils.assertView(glb)));
+ }
+
+ /**********************************************************************************************
+ * Public Write API.
*/
- /** @internal */
- protected _readResourcesInternal(jsonDoc: JSONDocument): void {
+ /** Converts a {@link Document} to glTF-formatted JSON and a resource map. */
+ public async writeJSON(doc: Document, _options: PublicWriterOptions = {}): Promise {
+ if (_options.format === Format.GLB && doc.getRoot().listBuffers().length > 1) {
+ throw new Error('GLB must have 0–1 buffers.');
+ }
+ return GLTFWriter.write(doc, {
+ format: _options.format || Format.GLTF,
+ basename: _options.basename || '',
+ logger: this._logger,
+ vertexLayout: this._vertexLayout,
+ dependencies: { ...this._dependencies },
+ extensions: Array.from(this._extensions),
+ } as Required);
+ }
+
+ /** Converts a {@link Document} to a GLB-formatted ArrayBuffer. */
+ public async writeBinary(doc: Document): Promise {
+ const { json, resources } = await this.writeJSON(doc, { format: Format.GLB });
+
+ const header = new Uint32Array([0x46546c67, 2, 12]);
+
+ const jsonText = JSON.stringify(json);
+ const jsonChunkData = BufferUtils.pad(BufferUtils.encodeText(jsonText), 0x20);
+ const jsonChunkHeader = BufferUtils.toView(new Uint32Array([jsonChunkData.byteLength, 0x4e4f534a]));
+ const jsonChunk = BufferUtils.concat([jsonChunkHeader, jsonChunkData]);
+ header[header.length - 1] += jsonChunk.byteLength;
+
+ const binBuffer = Object.values(resources)[0];
+ if (!binBuffer || !binBuffer.byteLength) {
+ return BufferUtils.concat([BufferUtils.toView(header), jsonChunk]);
+ }
+
+ const binChunkData = BufferUtils.pad(binBuffer, 0x00);
+ const binChunkHeader = BufferUtils.toView(new Uint32Array([binChunkData.byteLength, 0x004e4942]));
+ const binChunk = BufferUtils.concat([binChunkHeader, binChunkData]);
+ header[header.length - 1] += binChunk.byteLength;
+
+ return BufferUtils.concat([BufferUtils.toView(header), jsonChunk, binChunk]);
+ }
+
+ /**********************************************************************************************
+ * Internal.
+ */
+
+ private async _readGLTF(uri: string): Promise {
+ this.lastReadBytes = 0;
+ const jsonContent = await this.readURI(uri, 'text');
+ this.lastReadBytes += jsonContent.length;
+ const jsonDoc: JSONDocument = { json: JSON.parse(jsonContent), resources: {} };
+ // Read external resources first, before Data URIs are replaced.
+ await this._readResourcesExternal(jsonDoc, this.dirname(uri));
+ this._readResourcesInternal(jsonDoc);
+ return jsonDoc;
+ }
+
+ private async _readGLB(uri: string): Promise {
+ const view = await this.readURI(uri, 'view');
+ this.lastReadBytes = view.byteLength;
+ const jsonDoc = this._binaryToJSON(view);
+ // Read external resources first, before Data URIs are replaced.
+ await this._readResourcesExternal(jsonDoc, this.dirname(uri));
+ this._readResourcesInternal(jsonDoc);
+ return jsonDoc;
+ }
+
+ private async _readResourcesExternal(jsonDoc: JSONDocument, dir: string): Promise {
+ const images = jsonDoc.json.images || [];
+ const buffers = jsonDoc.json.buffers || [];
+ const pendingResources: Array> = [...images, ...buffers].map(
+ async (resource: GLTF.IBuffer | GLTF.IImage): Promise => {
+ const uri = resource.uri;
+ if (!uri || uri.match(/data:/)) return Promise.resolve();
+
+ jsonDoc.resources[uri] = await this.readURI(this.resolve(dir, uri), 'view');
+ this.lastReadBytes += jsonDoc.resources[uri].byteLength;
+ }
+ );
+ await Promise.all(pendingResources);
+ }
+
+ private _readResourcesInternal(jsonDoc: JSONDocument): void {
// NOTICE: This method may be called more than once during the loading
// process (e.g. WebIO.read) and should handle that safely.
@@ -103,36 +246,6 @@ export abstract class PlatformIO {
buffers.forEach(resolveResource);
}
- /**********************************************************************************************
- * JSON.
- */
-
- /** Converts glTF-formatted JSON and a resource map to a {@link Document}. */
- public async readJSON(jsonDoc: JSONDocument): Promise {
- jsonDoc = this._copyJSON(jsonDoc);
- this._readResourcesInternal(jsonDoc);
- return GLTFReader.read(jsonDoc, {
- extensions: Array.from(this._extensions),
- dependencies: this._dependencies,
- logger: this._logger,
- });
- }
-
- /** Converts a {@link Document} to glTF-formatted JSON and a resource map. */
- public async writeJSON(doc: Document, _options: PublicWriterOptions = {}): Promise {
- if (_options.format === Format.GLB && doc.getRoot().listBuffers().length > 1) {
- throw new Error('GLB must have 0–1 buffers.');
- }
- return GLTFWriter.write(doc, {
- format: _options.format || Format.GLTF,
- basename: _options.basename || '',
- logger: this._logger,
- vertexLayout: this._vertexLayout,
- dependencies: { ...this._dependencies },
- extensions: Array.from(this._extensions),
- } as Required);
- }
-
/**
* Creates a shallow copy of glTF-formatted {@link JSONDocument}.
*
@@ -155,28 +268,8 @@ export abstract class PlatformIO {
return jsonDoc;
}
- /**********************************************************************************************
- * Binary -> JSON.
- */
-
- /** Converts a GLB-formatted ArrayBuffer to a {@link JSONDocument}. */
- public async binaryToJSON(glb: Uint8Array): Promise {
- const jsonDoc = this._binaryToJSON(BufferUtils.assertView(glb));
- this._readResourcesInternal(jsonDoc);
- const json = jsonDoc.json;
-
- // Check for external references, which can't be resolved by this method.
- if (json.buffers && json.buffers.some((bufferDef) => isExternalBuffer(jsonDoc, bufferDef))) {
- throw new Error('Cannot resolve external buffers with binaryToJSON().');
- } else if (json.images && json.images.some((imageDef) => isExternalImage(jsonDoc, imageDef))) {
- throw new Error('Cannot resolve external images with binaryToJSON().');
- }
-
- return jsonDoc;
- }
-
- /** @internal For internal use by WebIO and NodeIO. Does not warn about external resources. */
- protected _binaryToJSON(glb: Uint8Array): JSONDocument {
+ /** Internal version of binaryToJSON; does not warn about external resources. */
+ private _binaryToJSON(glb: Uint8Array): JSONDocument {
// Decode and verify GLB header.
const header = new Uint32Array(glb.buffer, glb.byteOffset, 3);
if (header[0] !== 0x46546c67) {
@@ -214,40 +307,6 @@ export abstract class PlatformIO {
return { json, resources: { [GLB_BUFFER]: binBuffer } };
}
-
- /**********************************************************************************************
- * Binary.
- */
-
- /** Converts a GLB-formatted ArrayBuffer to a {@link Document}. */
- public async readBinary(glb: Uint8Array): Promise {
- return this.readJSON(await this.binaryToJSON(BufferUtils.assertView(glb)));
- }
-
- /** Converts a {@link Document} to a GLB-formatted ArrayBuffer. */
- public async writeBinary(doc: Document): Promise {
- const { json, resources } = await this.writeJSON(doc, { format: Format.GLB });
-
- const header = new Uint32Array([0x46546c67, 2, 12]);
-
- const jsonText = JSON.stringify(json);
- const jsonChunkData = BufferUtils.pad(BufferUtils.encodeText(jsonText), 0x20);
- const jsonChunkHeader = BufferUtils.toView(new Uint32Array([jsonChunkData.byteLength, 0x4e4f534a]));
- const jsonChunk = BufferUtils.concat([jsonChunkHeader, jsonChunkData]);
- header[header.length - 1] += jsonChunk.byteLength;
-
- const binBuffer = Object.values(resources)[0];
- if (!binBuffer || !binBuffer.byteLength) {
- return BufferUtils.concat([BufferUtils.toView(header), jsonChunk]);
- }
-
- const binChunkData = BufferUtils.pad(binBuffer, 0x00);
- const binChunkHeader = BufferUtils.toView(new Uint32Array([binChunkData.byteLength, 0x004e4942]));
- const binChunk = BufferUtils.concat([binChunkHeader, binChunkData]);
- header[header.length - 1] += binChunk.byteLength;
-
- return BufferUtils.concat([BufferUtils.toView(header), jsonChunk, binChunk]);
- }
}
function isExternalBuffer(jsonDocument: JSONDocument, bufferDef: GLTF.IBuffer): boolean {
diff --git a/packages/core/src/io/web-io.ts b/packages/core/src/io/web-io.ts
index 3d3f2d7ff..e690c9ac0 100644
--- a/packages/core/src/io/web-io.ts
+++ b/packages/core/src/io/web-io.ts
@@ -1,6 +1,3 @@
-import { Document } from '../document';
-import { JSONDocument } from '../json-document';
-import { GLTF } from '../types/gltf';
import { PlatformIO } from './platform-io';
const DEFAULT_INIT: RequestInit = {};
@@ -41,72 +38,31 @@ export class WebIO extends PlatformIO {
super();
}
- /**********************************************************************************************
- * Public.
- */
-
- /** Loads a URI and returns a {@link Document} instance. */
- public async read(uri: string): Promise {
- return this.readAsJSON(uri).then((jsonDoc) => this.readJSON(jsonDoc));
- }
-
- /** Loads a URI and returns a {@link JSONDocument} struct, without parsing. */
- public async readAsJSON(uri: string): Promise {
- const isGLB =
- uri.match(/^data:application\/octet-stream;/) ||
- new URL(uri, window.location.href).pathname.match(/\.glb$/);
- return isGLB ? this._readGLB(uri) : this._readGLTF(uri);
- }
-
- /**********************************************************************************************
- * Protected.
- */
-
- /** @internal */
- private _readResourcesExternal(jsonDoc: JSONDocument, dir: string): Promise {
- const images = jsonDoc.json.images || [];
- const buffers = jsonDoc.json.buffers || [];
- const pendingResources: Array> = [...images, ...buffers].map(
- async (resource: GLTF.IBuffer | GLTF.IImage): Promise => {
- const uri = resource.uri;
- if (!uri || uri.match(/data:/)) return Promise.resolve();
-
- const res = await fetch(_resolve(dir, uri), this._fetchConfig);
- jsonDoc.resources[uri] = new Uint8Array(await res.arrayBuffer());
- }
- );
- return Promise.all(pendingResources).then(() => undefined);
+ protected async readURI(uri: string, type: 'view'): Promise;
+ protected async readURI(uri: string, type: 'text'): Promise;
+ protected async readURI(uri: string, type: 'view' | 'text'): Promise {
+ const response = await fetch(uri, this._fetchConfig);
+ switch (type) {
+ case 'view':
+ return new Uint8Array(await response.arrayBuffer());
+ case 'text':
+ return response.text();
+ }
}
- /**********************************************************************************************
- * Private.
- */
-
- /** @internal */
- private async _readGLTF(uri: string): Promise {
- const json = await fetch(uri, this._fetchConfig).then((response) => response.json());
- const jsonDoc: JSONDocument = { json, resources: {} };
- // Read external resources first, before Data URIs are replaced.
- await this._readResourcesExternal(jsonDoc, _dirname(uri));
- this._readResourcesInternal(jsonDoc);
- return jsonDoc;
+ protected resolve(directory: string, path: string): string {
+ return _resolve(directory, path);
}
- /** @internal */
- private async _readGLB(uri: string): Promise {
- const arrayBuffer = await fetch(uri, this._fetchConfig).then((response) => response.arrayBuffer());
- const jsonDoc = this._binaryToJSON(new Uint8Array(arrayBuffer));
- // Read external resources first, before Data URIs are replaced.
- await this._readResourcesExternal(jsonDoc, _dirname(uri));
- this._readResourcesInternal(jsonDoc);
- return jsonDoc;
+ protected dirname(uri: string): string {
+ return _dirname(uri);
}
}
function _dirname(path: string): string {
const index = path.lastIndexOf('/');
if (index === -1) return './';
- return path.substr(0, index + 1);
+ return path.substring(0, index + 1);
}
function _resolve(base: string, path: string) {
diff --git a/packages/core/src/utils/file-utils.ts b/packages/core/src/utils/file-utils.ts
index 50eab227c..e2a6953af 100644
--- a/packages/core/src/utils/file-utils.ts
+++ b/packages/core/src/utils/file-utils.ts
@@ -1,3 +1,7 @@
+// Need a placeholder domain to construct a URL from a relative path. We only
+// access `url.pathname`, so the domain doesn't matter.
+const NULL_DOMAIN = 'https://null.example';
+
/**
* # FileUtils
*
@@ -8,13 +12,15 @@
export class FileUtils {
/** Extracts the basename from a path, e.g. "folder/model.glb" -> "model". */
static basename(path: string): string {
+ path = new URL(path, NULL_DOMAIN).pathname;
const fileName = path.split(/[\\/]/).pop()!;
- return fileName.substr(0, fileName.lastIndexOf('.'));
+ return fileName.substring(0, fileName.lastIndexOf('.'));
}
/** Extracts the extension from a path, e.g. "folder/model.glb" -> "glb". */
static extension(path: string): string {
if (path.indexOf('data:') !== 0) {
+ path = new URL(path, NULL_DOMAIN).pathname;
return path.split(/[\\/]/).pop()!.split(/[.]/).pop()!;
} else if (path.indexOf('data:image/png') === 0) {
return 'png';
diff --git a/packages/core/test/io/web-io.test.ts b/packages/core/test/io/web-io.test.ts
index 8aac78701..9c61323b5 100644
--- a/packages/core/test/io/web-io.test.ts
+++ b/packages/core/test/io/web-io.test.ts
@@ -21,6 +21,9 @@ test('@gltf-transform/core::io | web read glb', (t) => {
mockWindow('https://www.example.com/test');
mockFetch({
arrayBuffer: () => BufferUtils.createBufferFromDataURI(SAMPLE_GLB),
+ text: () => {
+ throw new Error('Do not call.');
+ },
json: () => {
throw new Error('Do not call.');
},
@@ -73,7 +76,10 @@ test('@gltf-transform/core::io | web read glb + resources', (t) => {
mockWindow('https://www.example.com/test');
const fetchedPaths = mockFetch({
arrayBuffer: () => responses.pop(),
- json: () => () => {
+ text: () => {
+ throw new Error('Do not call.');
+ },
+ json: () => {
throw new Error('Do not call.');
},
});
@@ -106,16 +112,17 @@ test('@gltf-transform/core::io | web read gltf', (t) => {
mockWindow('https://www.example.com/test');
const fetchedPaths = mockFetch({
arrayBuffer: () => images.pop(),
- json: () => ({
- asset: { version: '2.0' },
- scenes: [{ name: 'Default Scene' }],
- images: [
- { uri: 'image1.png' },
- { uri: '/abs/path/image2.png' },
- { uri: './rel/path/image3.png' },
- { uri: 'rel/path/image3.png' },
- ],
- }),
+ text: () =>
+ JSON.stringify({
+ asset: { version: '2.0' },
+ scenes: [{ name: 'Default Scene' }],
+ images: [
+ { uri: 'image1.png' },
+ { uri: '/abs/path/image2.png' },
+ { uri: './rel/path/image3.png' },
+ { uri: 'rel/path/image3.png' },
+ ],
+ }),
});
const io = new WebIO();
@@ -151,10 +158,11 @@ test('@gltf-transform/core::io | web read + data URIs', async (t) => {
arrayBuffer: () => {
throw new Error('Do not call.');
},
- json: () => ({
- asset: { version: '2.0' },
- images: uris.map((uri) => ({ uri })),
- }),
+ text: () =>
+ JSON.stringify({
+ asset: { version: '2.0' },
+ images: uris.map((uri) => ({ uri })),
+ }),
});
const io = new WebIO();
@@ -181,6 +189,9 @@ test('@gltf-transform/core::io | web readJSON + data URIs', async (t) => {
arrayBuffer: () => {
throw new Error('Do not call.');
},
+ text: () => {
+ throw new Error('Do not call.');
+ },
json: () => () => {
throw new Error('Do not call.');
},