@ameyanagi/reshiki 0.0.0-stage → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,219 @@
1
- # Temporary Holding Version
1
+ # @ameyanagi/reshiki
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ ReShiki in the browser, compiled to WebAssembly: the drawing editor itself,
4
+ and a headless library that converts structures between formats and draws
5
+ them. Both run the same Rust code as the desktop app, without a server.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ bun add @ameyanagi/reshiki
11
+ npm install @ameyanagi/reshiki
12
+ pnpm add @ameyanagi/reshiki
13
+ ```
14
+
15
+ Each ReShiki release publishes the package with the app's version, so
16
+ `@ameyanagi/reshiki@0.13.0` is the editor of ReShiki 0.13.0. Prereleases are
17
+ under the `next` tag (`bun add @ameyanagi/reshiki@next`). The package carries
18
+ the license texts and third-party notices in `dist/licenses`.
19
+
20
+ ## Build
21
+
22
+ ```sh
23
+ scripts/build_wasm.sh # from the repository root
24
+ scripts/build_wasm.sh --test # and run the tests
25
+ ```
26
+
27
+ The script needs Rust (the toolchain in `rust-toolchain.toml` and its
28
+ `wasm32-unknown-unknown` target), `uv` and `bun`. It installs the matching
29
+ `wasm-bindgen` CLI and a pinned binaryen `wasm-opt` under `target/tools`. It
30
+ writes the headless library to `dist/wasm`, the editor to `dist/editor`, and
31
+ the license texts and third-party notices to `dist/licenses`.
32
+
33
+ ## Editor
34
+
35
+ The editor is the desktop app's window, drawn into a page element with
36
+ WebGPU, or WebGL2 where the browser has no WebGPU. It fills the element, so
37
+ give the element a size.
38
+
39
+ ```js
40
+ import { mount } from "@ameyanagi/reshiki/editor";
41
+
42
+ const editor = await mount(document.getElementById("editor"), {
43
+ data: "CN1C=NC2=C1C(=O)N(C(=O)N2C)C",
44
+ format: "smiles",
45
+ onChange: () => console.log("edited"),
46
+ });
47
+ await editor.load(molfile, "mol"); // replaces the drawing in front
48
+ const mol = await editor.save("mol"); // rsk, mol, smiles, inchi, cdxml, cdx, rxn, rsmi
49
+ const svg = await editor.exportImage("svg"); // svg (text), png or pdf (bytes)
50
+ const stop = editor.onChange(() => {}); // returns a function that stops it
51
+ editor.destroy();
52
+ ```
53
+
54
+ `load` takes the same formats as `convert` and rejects with the app's message
55
+ when it cannot read the data; so does `mount` for its `data`, before the
56
+ editor starts. Mount on an element in the page's body: the editor cannot
57
+ start inside a shadow root. `save` and `exportImage` return what the
58
+ headless library's `convert` would for the drawing in front.
59
+
60
+ A page runs one editor: the editor and the page share one thread and one
61
+ WebAssembly instance, and a second `mount`, or one after `destroy`, rejects.
62
+ For several editors, or to keep the page's styles away from the editor, put
63
+ each in an iframe:
64
+
65
+ ```js
66
+ import { embed } from "@ameyanagi/reshiki/embed";
67
+
68
+ const editor = embed(document.getElementById("left"));
69
+ await editor.ready;
70
+ await editor.load("CCO", "smiles");
71
+ ```
72
+
73
+ `embed` returns the same methods as `mount`, sent to `src/frame.html` with
74
+ `postMessage`. Serve that page with the package's `src` and `dist` folders, or
75
+ pass its URL as `frameUrl`.
76
+
77
+ The custom element wraps `mount`:
78
+
79
+ ```html
80
+ <script type="module">
81
+ import "@ameyanagi/reshiki/element";
82
+ </script>
83
+ <reshiki-editor src="caffeine.mol" format="mol" style="height: 600px"></reshiki-editor>
84
+ ```
85
+
86
+ It fires `ready` and `change`, and has `load`, `save` and `exportImage`.
87
+
88
+ ### Fonts
89
+
90
+ The editor can only draw with fonts loaded into it as bytes: it bundles
91
+ Liberation Sans, which has Arial's letter widths and answers to Arial, so
92
+ drawings lay out as on the desktop. Text that font lacks falls back to the
93
+ fonts below.
94
+
95
+ - **Japanese (CJK) text.** The first time a caption has Japanese characters
96
+ that no loaded font draws, the editor loads a fallback once. If the page
97
+ already has Local Font Access permission (Chrome and Edge), it uses the
98
+ computer's own Japanese font, such as Hiragino Sans, Yu Gothic or Meiryo,
99
+ without a prompt. Otherwise it downloads
100
+ [Noto Sans JP](https://github.com/notofonts/noto-cjk) Regular (4.5 MB, SIL
101
+ Open Font License 1.1) from jsDelivr. Pages without Japanese text download
102
+ nothing. The font is not bundled in this package.
103
+ - **The page's own fonts.** `fonts` takes TrueType or OpenType files (or
104
+ collections) as URLs or bytes. They load before the editor starts, and
105
+ text whose font lacks a glyph falls back to them. URLs on another origin
106
+ need CORS.
107
+
108
+ ```js
109
+ await mount(element, {
110
+ fonts: ["/fonts/MyLabels.ttf"],
111
+ cjkFont: "/fonts/NotoSansJP-Regular.otf", // self-host the fallback, or false for none
112
+ onError: (error) => console.warn(error.message), // a font that could not load
113
+ });
114
+ ```
115
+
116
+ `embed()` takes the same options, with fonts as URLs. The custom element
117
+ takes `fonts` (URLs separated by spaces) and `cjk-font` (a URL, or `false`)
118
+ attributes and fires `error` for a font that could not load. Fonts added this
119
+ way also reach `save` and `exportImage` figures.
120
+
121
+ ### Demo
122
+
123
+ ```sh
124
+ scripts/build_wasm.sh # from the repository root
125
+ cd packages/editor && bun examples/serve.ts
126
+ ```
127
+
128
+ Then open <http://localhost:8080/>, which is the editor alone, filling the
129
+ window. <http://localhost:8080/examples/api.html> calls the JavaScript API from
130
+ the page's own buttons, and <http://localhost:8080/examples/iframe.html> shows
131
+ two editors in iframes. `PORT` picks another port.
132
+
133
+ The same editor-only page runs at <https://reshiki.com/demo/>, packaged by
134
+ `scripts/package_web_demo.sh` with a dismissible note that it is a browser
135
+ preview, not the full app. The list below is what it leaves out; point people
136
+ to the [desktop app](https://reshiki.com/guide/install/) whenever they can use
137
+ it.
138
+
139
+ ### What the browser editor leaves out
140
+
141
+ - Naming, NMR prediction and 3D say they need the desktop app. The assistant,
142
+ updates, printing, Office and LibreOffice integration are not built.
143
+ - Open and Save use the browser: Open picks a file, Save and Export download
144
+ one under the drawing's name. Drawing styles, themes, templates and pictures
145
+ cannot be loaded from files yet, Insert cannot insert files, and there are
146
+ no recovery drafts.
147
+ - Copy and paste carry text (SMILES, the drawing as text) through
148
+ `navigator.clipboard`, which asks for permission to read. There are no
149
+ image or ChemDraw clipboard formats.
150
+ - Only Liberation Sans is bundled, so symbols it lacks, such as ℃, ⇌ and
151
+ superscript ⁺ and ⁻, need a font from `fonts`. Japanese text loads a
152
+ fallback (see [Fonts](#fonts)).
153
+ - Shortcuts use Ctrl, also on macOS, where Cmd does not work yet.
154
+ - Work runs on the page's thread, so a long cleanup or import pauses the page.
155
+ - Without WebGPU, lines are drawn without antialiasing: WebGL2 loses most
156
+ canvases with it.
157
+
158
+ ## Library
159
+
160
+ ```js
161
+ import { convert, renderPng, renderSvg, smiles, inchi } from "@ameyanagi/reshiki";
162
+
163
+ const mol = await convert("CN1C=NC2=C1C(=O)N(C(=O)N2C)C", "smiles", "mol");
164
+ const svg = await renderSvg(mol, "mol");
165
+ const png = await renderPng(mol, "mol", { maxWidth: 800, maxHeight: 600 });
166
+ const id = await inchi(mol, "mol");
167
+ ```
168
+
169
+ Each function loads the module on its first call, from `reshiki_web_bg.wasm`
170
+ next to the package's JavaScript. Bundlers such as Vite and webpack follow
171
+ that `new URL(..., import.meta.url)` reference. To load it from somewhere else,
172
+ call `init` first with a URL, a `Response`, the bytes or a compiled
173
+ `WebAssembly.Module`:
174
+
175
+ ```js
176
+ import { init } from "@ameyanagi/reshiki";
177
+ await init(new URL("/assets/reshiki_web_bg.wasm", location.href));
178
+ ```
179
+
180
+ | Function | Result |
181
+ | -------------------------------------------------- | ----------------------------------------------------------------------------- |
182
+ | `convert(data, from, to)` | The structure or drawing in another format. |
183
+ | `renderSvg(data, format)` | The editor's SVG preview, with a transparent surround. |
184
+ | `renderPng(data, format, { maxWidth, maxHeight })` | The editor's PNG preview on white, scaled to fit (1600 × 1000 by default). |
185
+ | `smiles(data, format)` | The canonical SMILES. |
186
+ | `inchi(data, format)` | The standard InChI. |
187
+ | `addFont(font)` | Adds a font (a URL or bytes) that figures fall back to, such as for Japanese. |
188
+ | `version()` | The library version. |
189
+
190
+ `data` is a string or a `Uint8Array`; `cdx` input must be bytes.
191
+
192
+ - Input formats: `rsk` (a ReShiki drawing), `mol`, `smiles`, `inchi`, `cdxml`,
193
+ `cdx`, `rxn`, `rsmi` (reaction SMILES) and `auto`, which tells the text
194
+ formats apart.
195
+ - Output formats: `rsk`, `mol`, `smiles`, `inchi`, `cdxml`, `rxn`, `rsmi` and
196
+ `svg` (strings), and `cdx`, `png` and `pdf` (bytes). `svg`, `png` and `pdf`
197
+ are figures as the desktop's Export makes them.
198
+
199
+ Failures reject with an `Error` that carries the app's message.
200
+
201
+ ## How the library differs from the desktop
202
+
203
+ - SMILES, MOL, InChI and CDXML output matches `reshiki --cli convert` on macOS.
204
+ The tests check this when `RESHIKI_CLI` names a native `reshiki` binary.
205
+ - Labels are measured and drawn with the bundled Liberation Sans, which has
206
+ Arial's metrics. Coordinates near labels can differ from the desktop's by
207
+ under one drawing unit. SVG text names Arial, so a browser draws it with the
208
+ viewer's Arial or a substitute.
209
+ - Everything runs on the calling thread. Large drawings block the page until
210
+ they finish, and InChI and 3D work have no time limit in the browser. Run the
211
+ module in a Web Worker to keep a page responsive.
212
+
213
+ ## Test
214
+
215
+ ```sh
216
+ cd packages/editor
217
+ bun test # needs a built dist/
218
+ RESHIKI_CLI=../../target/debug/reshiki bun test # also compares with the CLI
219
+ ```
@@ -0,0 +1,93 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /**
5
+ * Add a TrueType or OpenType font (or collection) for drawing, measuring and
6
+ * export, before or after `mount`. Text whose own font lacks a glyph falls
7
+ * back to it. Returns how many faces it holds.
8
+ */
9
+ export function addFont(bytes: Uint8Array): number;
10
+
11
+ /**
12
+ * Stop the editor. Its canvas stays until the page removes it.
13
+ */
14
+ export function destroy(): void;
15
+
16
+ /**
17
+ * Replace the drawing in front with `data` read as `format`: `rsk`, `mol`,
18
+ * `smiles`, `inchi`, `cdxml`, `cdx`, `rxn`, `rsmi` or `auto`.
19
+ */
20
+ export function load(data: Uint8Array, format: string): Promise<void>;
21
+
22
+ /**
23
+ * Start the editor in `element`, which it fills, showing `data` read as
24
+ * `format` when given. `on_missing_font` is called once, the first time a
25
+ * caption has Japanese (CJK) text that no loaded font draws. A page can mount one editor; put further editors in
26
+ * iframes.
27
+ */
28
+ export function mount(element: Element, data?: Uint8Array | null, format?: string | null, on_missing_font?: Function | null): Promise<void>;
29
+
30
+ /**
31
+ * Call `callback` after each edit to the drawing in front, or stop with none.
32
+ */
33
+ export function onChange(callback?: Function | null): void;
34
+
35
+ /**
36
+ * The drawing in front as `format`: `rsk`, `mol`, `smiles`, `inchi`, `cdxml`,
37
+ * `cdx`, `rxn`, `rsmi`, or a figure (`svg`, `png` or `pdf`) as Export makes it.
38
+ */
39
+ export function save(format: string): Promise<Uint8Array>;
40
+
41
+ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
42
+
43
+ export interface InitOutput {
44
+ readonly memory: WebAssembly.Memory;
45
+ readonly addFont: (a: number, b: number, c: number) => void;
46
+ readonly destroy: (a: number) => void;
47
+ readonly load: (a: number, b: number, c: number, d: number) => number;
48
+ readonly mount: (a: number, b: number, c: number, d: number, e: number, f: number) => number;
49
+ readonly onChange: (a: number) => void;
50
+ readonly save: (a: number, b: number) => number;
51
+ readonly main: (a: number, b: number) => number;
52
+ readonly __wasm_bindgen_func_elem_12999: (a: number, b: number, c: number, d: number) => void;
53
+ readonly __wasm_bindgen_func_elem_13030: (a: number, b: number, c: number, d: number) => void;
54
+ readonly __wasm_bindgen_func_elem_13030_9: (a: number, b: number, c: number, d: number) => void;
55
+ readonly __wasm_bindgen_func_elem_24945: (a: number, b: number, c: number) => void;
56
+ readonly __wasm_bindgen_func_elem_24945_10: (a: number, b: number, c: number) => void;
57
+ readonly __wasm_bindgen_func_elem_24945_11: (a: number, b: number, c: number) => void;
58
+ readonly __wasm_bindgen_func_elem_24945_12: (a: number, b: number, c: number) => void;
59
+ readonly __wasm_bindgen_func_elem_24945_13: (a: number, b: number, c: number) => void;
60
+ readonly __wasm_bindgen_func_elem_24945_14: (a: number, b: number, c: number) => void;
61
+ readonly __wasm_bindgen_func_elem_24945_15: (a: number, b: number, c: number) => void;
62
+ readonly __wasm_bindgen_func_elem_24945_16: (a: number, b: number, c: number) => void;
63
+ readonly __wasm_bindgen_func_elem_23587: (a: number, b: number) => void;
64
+ readonly __wbindgen_export: (a: number, b: number) => number;
65
+ readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
66
+ readonly __wbindgen_export3: (a: number) => void;
67
+ readonly __wbindgen_export4: (a: number, b: number) => void;
68
+ readonly __wbindgen_export5: (a: number, b: number, c: number) => void;
69
+ readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
70
+ readonly __wbindgen_start: () => void;
71
+ }
72
+
73
+ export type SyncInitInput = BufferSource | WebAssembly.Module;
74
+
75
+ /**
76
+ * Instantiates the given `module`, which can either be bytes or
77
+ * a precompiled `WebAssembly.Module`.
78
+ *
79
+ * @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
80
+ *
81
+ * @returns {InitOutput}
82
+ */
83
+ export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
84
+
85
+ /**
86
+ * If `module_or_path` is {RequestInfo} or {URL}, makes a request and
87
+ * for everything else, calls `WebAssembly.instantiate` directly.
88
+ *
89
+ * @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
90
+ *
91
+ * @returns {Promise<InitOutput>}
92
+ */
93
+ export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;