Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,45 @@ const pdfBytes = await ctx.pdfDoc.save();

Note that only page rendering (`toImage`/`encodePng`) is parallelizable this way — PDF assembly, including the final `pdfDoc.save()`, is a single main-thread operation regardless of how many Workers rendered pages.

### Reading Atelier `.spd` files

`.spd` files, produced by the Supernote Atelier app, are a different format from `.note` files: a SQLite database of image tiles rather than the custom binary layout `SupernoteX` parses. `SupernoteAtelier.open` reads it (via [sql.js](https://github.com/sql-js/sql.js)) and exposes the tiles per surface (layer — surface names vary per file, e.g. `surface_1` or a `surface_9999` "Reference Layer"), plus best-effort decoded metadata (viewport, canvas size, layer names). Its `.spd` schema and `ls` layer encoding aren't officially documented; the reverse-engineered details are noted in [src/atelier.ts](./src/atelier.ts).

- `toImage(surfaceName)` stitches one surface's tiles into a single image, sized and positioned against every surface's tiles in the file so that different layers' images line up and can be composited on top of each other.
- `toCompositeImage()` flattens every surface into one final image directly, layered bottom-to-top by `layers` order (best-effort, see `toImage`'s note about `ls`) — the simplest way to get one finished picture out of a `.spd` file without handling individual layers yourself.

```ts
import { SupernoteAtelier } from 'supernote-typescript';

const note = await SupernoteAtelier.open(buffer);

// One surface (layer) at a time:
const image = await note.toImage('surface_1');

// Or every surface flattened into one final image:
const flattened = await note.toCompositeImage();
```

#### Bundling for the browser or mobile

`SupernoteAtelier.open`'s second argument is passed straight through to `sql.js`'s `initSqlJs`, so a bundler that can embed the `.wasm` file as bytes (e.g. esbuild's `binary` loader) can hand it over as `wasmBinary`, instead of `sql.js` fetching/reading a sibling `sql-wasm.wasm` file at runtime via `locateFile` — the one thing that would otherwise differ between Node/Electron and a mobile browser runtime:

```ts
// esbuild.config.mjs
loader: { '.wasm': 'binary' }, // resolves a `.wasm` import to a decoded Uint8Array

// your code
import sqlWasmBinary from 'sql.js/dist/sql-wasm.wasm';

const wasmBinary = sqlWasmBinary.buffer.slice(
sqlWasmBinary.byteOffset,
sqlWasmBinary.byteOffset + sqlWasmBinary.byteLength,
);
const note = await SupernoteAtelier.open(buffer, { wasmBinary });
```

Used this way in the [Supernote Obsidian Plugin](https://github.com/philips/supernote-obsidian-plugin/pull/137), which runs unmodified on both desktop and mobile.

## Developer Notes

### Test Individual Suite
Expand Down
28 changes: 27 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@eslint/js": "10.0.1",
"@types/fs-extra": "11.0.4",
"@types/node": "26.1.1",
"@types/sql.js": "^1.4.11",
"@typescript-eslint/eslint-plugin": "8.65.0",
"@typescript-eslint/parser": "8.65.0",
"@vitest/coverage-v8": "4.1.10",
Expand All @@ -55,6 +56,7 @@
"color": "^5.0.3",
"fs-extra": "^11.4.0",
"image-js": "^1.7.0",
"pdf-lib": "^1.17.1"
"pdf-lib": "^1.17.1",
"sql.js": "^1.14.1"
}
}
Loading