@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 +218 -2
- package/dist/editor/reshiki.d.ts +93 -0
- package/dist/editor/reshiki.js +3317 -0
- package/dist/editor/reshiki_bg.wasm +0 -0
- package/dist/editor/reshiki_bg.wasm.d.ts +29 -0
- package/dist/licenses/LICENSE +20 -0
- package/dist/licenses/LICENSE-APACHE +202 -0
- package/dist/licenses/LICENSE-MIT +21 -0
- package/dist/licenses/NOTICE +38 -0
- package/dist/licenses/THIRD-PARTY-NOTICES.txt +114672 -0
- package/dist/wasm/reshiki_web.d.ts +91 -0
- package/dist/wasm/reshiki_web.js +537 -0
- package/dist/wasm/reshiki_web_bg.wasm +0 -0
- package/dist/wasm/reshiki_web_bg.wasm.d.ts +20 -0
- package/package.json +49 -4
- package/src/editor.d.ts +58 -0
- package/src/editor.js +57 -0
- package/src/element.d.ts +19 -0
- package/src/element.js +68 -0
- package/src/embed.d.ts +29 -0
- package/src/embed.js +83 -0
- package/src/fonts.d.ts +30 -0
- package/src/fonts.js +95 -0
- package/src/frame.html +53 -0
- package/src/index.d.ts +70 -0
- package/src/index.js +56 -0
package/README.md
CHANGED
|
@@ -1,3 +1,219 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ameyanagi/reshiki
|
|
2
2
|
|
|
3
|
-
|
|
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>;
|