@rebodocs/viewer 0.0.0-stage → 0.12.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rebodocs contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,103 @@
1
- # Temporary Holding Version
1
+ <p align="center">
2
+ <img src="https://rebodocs.com/logo.svg" alt="" width="56" height="56">
3
+ </p>
2
4
 
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.
5
+ <h1 align="center">@rebodocs/viewer</h1>
6
+
7
+ <p align="center">
8
+ A DOCX document only to read, in the browser: its pages as Word lays them out, a bar to zoom and print, and its first page as a thumbnail. With or without React.
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/@rebodocs/viewer"><img src="https://img.shields.io/npm/v/@rebodocs/viewer?label=npm&color=0b57d0" alt="npm version"></a>
13
+ <a href="https://github.com/JFranciscoRobles/rebodocs/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-2e6b3c" alt="MIT license"></a>
14
+ </p>
15
+
16
+ <p align="center">
17
+ <a href="https://rebodocs.com/docs/viewer/">Guide</a> ·
18
+ <a href="https://rebodocs.com/docs/packages/">Packages</a>
19
+ </p>
20
+
21
+ ---
22
+
23
+ For an application that shows documents and does not edit them: a file
24
+ manager, a preview, a page where a client reads a contract. The pages are
25
+ those of the Rebodocs editor, read only and with no caret, so a document looks
26
+ as it does in Word and in the editor. Nothing is sent anywhere: the file is
27
+ read in the page.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ npm install @rebodocs/viewer
33
+ ```
34
+
35
+ It brings `@rebodocs/core` and `@rebodocs/editor` with it; a bundler keeps
36
+ only what the viewer uses of them.
37
+
38
+ ## Without a framework
39
+
40
+ ```ts
41
+ import { createViewer, pagePicture, setLocale } from '@rebodocs/viewer';
42
+
43
+ setLocale('es');
44
+ const viewer = createViewer(element, bytes); // the bytes of a .docx; element has a size
45
+ viewer.setLayout('all'); // every page at once, as large as lets them fit; 'pages' for one under another
46
+ viewer.print();
47
+ viewer.destroy();
48
+ ```
49
+
50
+ One page under another, each whole on the screen, and a bar floating over
51
+ them with the page one is on, the zoom, and its views: the grid sets every
52
+ page out at once, as `setLayout('all')` does. `bar: false` leaves it out,
53
+ and the layout and the zoom are then the application's, through
54
+ `viewer.editor`.
55
+
56
+ ## React
57
+
58
+ ```tsx
59
+ 'use client';
60
+ import { DocsViewer } from '@rebodocs/viewer/react';
61
+
62
+ <DocsViewer docx={bytes} locale="es" style={{ height: 600 }} onReady={(viewer) => (printer.current = viewer)} />;
63
+ ```
64
+
65
+ It is safe to render on a server: there it draws an empty element, and
66
+ mounts in the browser. `layout="all"` starts with every page at once;
67
+ `onReady` gives the viewer, to `print()` or `setLayout()`. React is a peer,
68
+ and an optional one: npm does not install it for the page without React.
69
+
70
+ ## A thumbnail
71
+
72
+ ```ts
73
+ import { pagePicture, pageThumbnail } from '@rebodocs/viewer';
74
+
75
+ const jpeg = await pagePicture(bytes, 480); // the first page, its longest side 480 pixels
76
+ const png = await pagePicture(bytes, 240, { type: 'image/png' });
77
+ const element = await pageThumbnail(bytes); // the page as the editor draws it, to show made smaller
78
+ ```
79
+
80
+ Drawn out of sight, one at a time, with the pictures of the page in it. A
81
+ font the document carries is drawn in the one of the system nearest it.
82
+
83
+ ## Also in the package
84
+
85
+ - `setLocale`, `addLocale`, `locales`: the language of the bar.
86
+ - `addFont`, `addFonts`: the fonts a document is written in, when the system
87
+ has not got them.
88
+ - A document already read with `importDocx` of `@rebodocs/core` may be given
89
+ instead of its bytes, to every function.
90
+
91
+ The whole editor, to write as well as read, is
92
+ [`@rebodocs/react`](https://www.npmjs.com/package/@rebodocs/react), which has
93
+ `DocsViewer` too.
94
+
95
+ ## Status
96
+
97
+ 0.11: ready to try in production; the API may change before 1.0. See the
98
+ [changelog](https://github.com/JFranciscoRobles/rebodocs/blob/main/CHANGELOG.md)
99
+ and the [known limits](https://rebodocs.com/docs/status/).
100
+
101
+ ## License
102
+
103
+ [MIT](https://github.com/JFranciscoRobles/rebodocs/blob/main/LICENSE).
@@ -0,0 +1,129 @@
1
+ # Third-party notices
2
+
3
+ Rebodocs is released under the MIT License (see [LICENSE](LICENSE)). The
4
+ published packages include, or install, the following third-party software,
5
+ each under its own license.
6
+
7
+ | Package | Version | License | Where |
8
+ | --- | --- | --- | --- |
9
+ | [WordCraft](https://github.com/storytold/wordcraft) | — | MIT or Apache-2.0, used under MIT | Code ported into `@rebodocs/core` (and so bundled in `@rebodocs/react`); see below. |
10
+ | [fflate](https://github.com/101arrowz/fflate) | 0.8 | MIT | A dependency of `@rebodocs/core`; bundled in `@rebodocs/react`. |
11
+ | [nspell](https://github.com/wooorm/nspell) | 2.1 | MIT | Bundled in the spell-checking worker of `@rebodocs/editor` and `@rebodocs/react`. |
12
+ | [is-buffer](https://github.com/feross/is-buffer) | 2.0 | MIT | A dependency of nspell, bundled with it. |
13
+ | [@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript) | 0.133 | MIT | A dependency of `@rebodocs/mcp` (`@rebodocs/mcp/claude`); installed by npm, not bundled. |
14
+
15
+ Rebodocs ships no spelling dictionaries. An application that uses
16
+ `createSpellChecker` gives its own, under their own licenses: for example,
17
+ the Hunspell dictionaries of [wooorm/dictionaries](https://github.com/wooorm/dictionaries)
18
+ (the Spanish one is GPL-3.0, LGPL-3.0 or MPL-1.1; the English one MIT and BSD).
19
+
20
+ ## WordCraft
21
+
22
+ `packages/core/src/numbering.ts` ports to TypeScript the rules for list
23
+ counters of [WordCraft](https://github.com/storytold/wordcraft)
24
+ (`crates/doc/src/numbering.rs`), and `packages/core/src/layout.ts` follows
25
+ its rule for the line height of list labels. WordCraft is distributed under
26
+ MIT or Apache-2.0; it is used here under MIT:
27
+
28
+ ```
29
+ MIT License
30
+
31
+ Copyright (c) 2026 ArtCraft Team and the WordCraft contributors
32
+
33
+ Permission is hereby granted, free of charge, to any person obtaining a copy
34
+ of this software and associated documentation files (the "Software"), to deal
35
+ in the Software without restriction, including without limitation the rights
36
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
37
+ copies of the Software, and to permit persons to whom the Software is
38
+ furnished to do so, subject to the following conditions:
39
+
40
+ The above copyright notice and this permission notice shall be included in all
41
+ copies or substantial portions of the Software.
42
+
43
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
44
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
45
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
46
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
47
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
48
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
49
+ SOFTWARE.
50
+ ```
51
+
52
+ ## fflate
53
+
54
+ ```
55
+ MIT License
56
+
57
+ Copyright (c) 2026 Arjun Barrett
58
+
59
+ Permission is hereby granted, free of charge, to any person obtaining a copy
60
+ of this software and associated documentation files (the "Software"), to deal
61
+ in the Software without restriction, including without limitation the rights
62
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
63
+ copies of the Software, and to permit persons to whom the Software is
64
+ furnished to do so, subject to the following conditions:
65
+
66
+ The above copyright notice and this permission notice shall be included in all
67
+ copies or substantial portions of the Software.
68
+
69
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
70
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
71
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
72
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
73
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
74
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
75
+ SOFTWARE.
76
+ ```
77
+
78
+ ## nspell
79
+
80
+ ```
81
+ (The MIT License)
82
+
83
+ Copyright (c) 2016 Titus Wormer <tituswormer@gmail.com>
84
+
85
+ Permission is hereby granted, free of charge, to any person obtaining
86
+ a copy of this software and associated documentation files (the
87
+ 'Software'), to deal in the Software without restriction, including
88
+ without limitation the rights to use, copy, modify, merge, publish,
89
+ distribute, sublicense, and/or sell copies of the Software, and to
90
+ permit persons to whom the Software is furnished to do so, subject to
91
+ the following conditions:
92
+
93
+ The above copyright notice and this permission notice shall be
94
+ included in all copies or substantial portions of the Software.
95
+
96
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
97
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
98
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
99
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
100
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
101
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
102
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
103
+ ```
104
+
105
+ ## is-buffer
106
+
107
+ ```
108
+ The MIT License (MIT)
109
+
110
+ Copyright (c) Feross Aboukhadijeh
111
+
112
+ Permission is hereby granted, free of charge, to any person obtaining a copy
113
+ of this software and associated documentation files (the "Software"), to deal
114
+ in the Software without restriction, including without limitation the rights
115
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
116
+ copies of the Software, and to permit persons to whom the Software is
117
+ furnished to do so, subject to the following conditions:
118
+
119
+ The above copyright notice and this permission notice shall be included in
120
+ all copies or substantial portions of the Software.
121
+
122
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
123
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
124
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
125
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
126
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
127
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
128
+ THE SOFTWARE.
129
+ ```
@@ -0,0 +1,200 @@
1
+ import { importDocx } from "@rebodocs/core/internal";
2
+ import { Editor, createStatusBar } from "@rebodocs/editor";
3
+ //#region src/viewer.ts
4
+ var docOf = (source) => source instanceof Uint8Array ? importDocx(source) : source instanceof ArrayBuffer ? importDocx(new Uint8Array(source)) : source;
5
+ var STYLE_ID = "dx-viewer-style";
6
+ var CSS = `
7
+ .dx-viewer .dx-caret{display:none!important}
8
+ .dx-viewer-bar{position:sticky;bottom:14px;z-index:3;display:flex;align-items:center;justify-content:center;height:52px;margin-top:-52px;pointer-events:none}
9
+ .dx-viewer-bar .dx-sb{background:none;border:0;box-shadow:none}
10
+ `;
11
+ function injectStyle() {
12
+ if (document.getElementById(STYLE_ID)) return;
13
+ const style = document.createElement("style");
14
+ style.id = STYLE_ID;
15
+ style.textContent = CSS;
16
+ document.head.appendChild(style);
17
+ }
18
+ /** The least width a page is shown at when all of them are, in pixels: an overview, to be read one page at a time. */
19
+ var LEAST_WIDE = 140;
20
+ /** About the gap the editor leaves between pages, in pixels. */
21
+ var GAP = 24;
22
+ /**
23
+ * The width each page is shown at so that `pages` pages, each `ratio` times as tall as it is wide, all fit in
24
+ * `width` × `height`, as many across as that takes: the largest that does. When none is as wide as a page is
25
+ * still made out at, pages of a set width, and the rest is scrolled to.
26
+ */
27
+ function pageWidthToFit(pages, ratio, width, height) {
28
+ let best = 0;
29
+ for (let across = 1; across <= pages; across++) {
30
+ const wide = (width - GAP * (across + 1)) / across;
31
+ const rows = Math.ceil(pages / across);
32
+ best = Math.max(best, Math.min(wide, (height - GAP * (rows + 1)) / rows / ratio));
33
+ }
34
+ return best >= LEAST_WIDE ? best : Math.max(LEAST_WIDE, Math.min(220, (width - 48) / 2));
35
+ }
36
+ /**
37
+ * A document to read in `container`, which must have a size: the editor, read only, and the status bar floating
38
+ * over its pages. The words are those of the language set with `setLocale`.
39
+ */
40
+ function createViewer(container, source, options = {}) {
41
+ injectStyle();
42
+ const editor = new Editor(container, docOf(source));
43
+ editor.readOnly = true;
44
+ container.classList.add("dx-viewer");
45
+ let status;
46
+ let bar;
47
+ if (options.bar !== false) {
48
+ bar = document.createElement("div");
49
+ bar.className = "dx-viewer-bar";
50
+ container.appendChild(bar);
51
+ status = createStatusBar(bar, editor, {
52
+ layout: "floating",
53
+ page: "view"
54
+ });
55
+ for (const away of bar.querySelectorAll("[data-name=\"spread\"], [data-name=\"flow\"], [data-name=\"notes\"], [data-name=\"language\"]")) away.remove();
56
+ }
57
+ let current = options.layout ?? "pages";
58
+ const setLayout = (mode) => {
59
+ current = mode;
60
+ if (mode === "pages") {
61
+ editor.setView("column");
62
+ editor.setFit("page");
63
+ } else {
64
+ editor.setView("grid");
65
+ const page = container.querySelector(".dx-page");
66
+ const width = (page?.offsetWidth ?? 816) / editor.zoom;
67
+ const ratio = (page?.offsetHeight ?? 1056) / (page?.offsetWidth ?? 816);
68
+ const fits = pageWidthToFit(editor.pageCount, ratio, container.clientWidth, container.clientHeight);
69
+ editor.setZoom(Math.min(1, fits / width));
70
+ let tries = 0;
71
+ const shrink = () => {
72
+ if (current !== "all" || tries++ >= 4 || container.scrollHeight <= container.clientHeight + 1) return;
73
+ const smaller = editor.zoom * (container.clientHeight / container.scrollHeight) * .99;
74
+ if (smaller * width < LEAST_WIDE) return;
75
+ const before = editor.zoom;
76
+ editor.setZoom(smaller);
77
+ if (editor.zoom !== before) requestAnimationFrame(() => requestAnimationFrame(shrink));
78
+ };
79
+ if (fits >= LEAST_WIDE) requestAnimationFrame(() => requestAnimationFrame(shrink));
80
+ }
81
+ container.scrollTop = 0;
82
+ };
83
+ setLayout(current);
84
+ let shownView = editor.view;
85
+ const offChange = editor.onChange(() => {
86
+ if (editor.view === shownView) return;
87
+ shownView = editor.view;
88
+ const mode = editor.view === "grid" ? "all" : editor.view === "column" ? "pages" : void 0;
89
+ if (mode) setTimeout(() => editor.view === shownView && setLayout(mode));
90
+ });
91
+ return {
92
+ editor,
93
+ get pages() {
94
+ return editor.pageCount;
95
+ },
96
+ get layout() {
97
+ return current;
98
+ },
99
+ setLayout,
100
+ print: () => editor.print(),
101
+ destroy() {
102
+ offChange();
103
+ status?.destroy();
104
+ bar?.remove();
105
+ editor.destroy();
106
+ container.classList.remove("dx-viewer");
107
+ }
108
+ };
109
+ }
110
+ /** A blob as a data address, for what is drawn inside a picture, which may load nothing from elsewhere. */
111
+ var dataUrl = (blob) => new Promise((done, fail) => {
112
+ const reader = new FileReader();
113
+ reader.onload = () => done(String(reader.result));
114
+ reader.onerror = () => fail(reader.error);
115
+ reader.readAsDataURL(blob);
116
+ });
117
+ /** Thumbnails made one at a time: each needs an editor laid out for a moment. */
118
+ var making = Promise.resolve();
119
+ /**
120
+ * The first page of a document as the editor draws it, for a thumbnail: drawn once out of sight, and given as a copy
121
+ * of that page at its real size, its pictures in it, inside an element of the editor's class so its rules still hold
122
+ * (`data-width` and `data-height` say the size, in pixels), to be shown made smaller.
123
+ */
124
+ function pageThumbnail(source) {
125
+ const made = making.then(async () => {
126
+ const doc = docOf(source);
127
+ const box = document.createElement("div");
128
+ box.setAttribute("aria-hidden", "true");
129
+ box.style.cssText = "position:fixed;left:-10000px;top:0;width:1000px;height:1400px;overflow:hidden;visibility:hidden;pointer-events:none";
130
+ document.body.appendChild(box);
131
+ const editor = new Editor(box, doc);
132
+ try {
133
+ editor.readOnly = true;
134
+ editor.setZoom(1);
135
+ await new Promise((done) => requestAnimationFrame(() => requestAnimationFrame(done)));
136
+ const page = box.querySelector(".dx-page");
137
+ if (!page) throw new Error("The document has no page to show.");
138
+ const originals = [...page.querySelectorAll("img")];
139
+ await Promise.all(originals.map((img) => img.decode().catch(() => void 0)));
140
+ const copy = page.cloneNode(true);
141
+ await Promise.all([...copy.querySelectorAll("img")].map(async (img, i) => {
142
+ try {
143
+ img.src = await dataUrl(await (await fetch(originals[i].src)).blob());
144
+ } catch {
145
+ img.remove();
146
+ }
147
+ }));
148
+ for (const away of copy.querySelectorAll("[contenteditable], textarea, input, .dx-caret, .dx-sel")) away.remove();
149
+ const shell = document.createElement("div");
150
+ shell.className = "dx-editor dx-readonly";
151
+ shell.dataset.width = String(page.offsetWidth);
152
+ shell.dataset.height = String(page.offsetHeight);
153
+ shell.appendChild(copy);
154
+ return shell;
155
+ } finally {
156
+ editor.destroy();
157
+ box.remove();
158
+ }
159
+ });
160
+ making = made.catch(() => void 0);
161
+ return made;
162
+ }
163
+ /**
164
+ * The first page of a document as a picture, its longest side `size` pixels: what the editor draws, put in an SVG
165
+ * with the editor's stylesheet and drawn on a canvas, its pictures in it. A font the document carries is drawn in
166
+ * the one of the system nearest it.
167
+ */
168
+ async function pagePicture(source, size, options = {}) {
169
+ const shell = await pageThumbnail(source);
170
+ const width = Number(shell.dataset.width) || 816;
171
+ const height = Number(shell.dataset.height) || 1056;
172
+ for (const canvas of shell.querySelectorAll("canvas")) {
173
+ const img = document.createElement("img");
174
+ img.src = canvas.toDataURL();
175
+ img.style.cssText = canvas.style.cssText;
176
+ canvas.replaceWith(img);
177
+ }
178
+ const css = [...document.querySelectorAll("style")].map((style) => style.textContent ?? "").filter((text) => text.includes(".dx-")).join("\n");
179
+ const page = shell.querySelector(".dx-page");
180
+ if (page) page.style.cssText += ";position:absolute;left:0;top:0;margin:0;box-shadow:none;transform:none";
181
+ const xhtml = new XMLSerializer().serializeToString(shell);
182
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"><foreignObject x="0" y="0" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="width:${width}px;height:${height}px;background:#fff;position:relative"><style>${css.replace(/]]>/g, "")}</style>${xhtml}</div></foreignObject></svg>`;
183
+ const picture = new Image();
184
+ picture.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
185
+ await picture.decode();
186
+ const scale = size / Math.max(width, height);
187
+ const canvas = document.createElement("canvas");
188
+ canvas.width = Math.max(1, Math.round(width * scale));
189
+ canvas.height = Math.max(1, Math.round(height * scale));
190
+ const context = canvas.getContext("2d");
191
+ context.fillStyle = "#fff";
192
+ context.fillRect(0, 0, canvas.width, canvas.height);
193
+ context.imageSmoothingQuality = "high";
194
+ context.drawImage(picture, 0, 0, canvas.width, canvas.height);
195
+ return new Promise((done, fail) => canvas.toBlob((blob) => blob ? done(blob) : fail(/* @__PURE__ */ new Error("The page could not be drawn.")), options.type ?? "image/jpeg", options.quality ?? .85));
196
+ }
197
+ //#endregion
198
+ export { pageWidthToFit as i, pagePicture as n, pageThumbnail as r, createViewer as t };
199
+
200
+ //# sourceMappingURL=viewer-CixYfz6K.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewer-CixYfz6K.js","names":[],"sources":["../../src/viewer.ts"],"sourcesContent":["// A document only to read: the editor, read only, its pages one under\n// another, each whole on the screen, with the status bar floating over them\n// (the page one is on, the counts, the fit, the zoom, and the views, whose\n// grid sets all the pages out as large as lets them fit). For a file manager,\n// a preview, a page that shows a contract: anywhere a document is seen and not\n// written. And the first page of a document, for its thumbnail: as the\n// element the editor draws, or as a picture.\n\nimport { type Doc, importDocx } from '@rebodocs/core/internal';\nimport { Editor, type StatusBar, createStatusBar } from '@rebodocs/editor';\n\n/** A document: the bytes of a .docx, or one already read with `importDocx` of `@rebodocs/core`. */\nexport type ViewerSource = Uint8Array | ArrayBuffer | Doc;\n\nconst docOf = (source: ViewerSource): Doc => (source instanceof Uint8Array ? importDocx(source) : source instanceof ArrayBuffer ? importDocx(new Uint8Array(source)) : source);\n\n/** How the pages are set out: one under another, each whole on the screen, or all of them at once. */\nexport type ViewerLayout = 'pages' | 'all';\n\nexport interface ViewerOptions {\n /** How the pages are set out at first: one under another ('pages', as it comes), or all at once ('all'). */\n layout?: ViewerLayout;\n /** False to leave out the bar floating over the pages; the layout and the zoom are then the application's. */\n bar?: boolean;\n}\n\nexport interface Viewer {\n /** The editor that draws the pages, read only. */\n readonly editor: Editor;\n /** How many pages the document has. */\n readonly pages: number;\n readonly layout: ViewerLayout;\n setLayout(mode: ViewerLayout): void;\n /** The browser's print dialog, with the pages as they are laid out: to paper, or to a PDF. */\n print(): void;\n destroy(): void;\n}\n\nconst STYLE_ID = 'dx-viewer-style';\nconst CSS = `\n.dx-viewer .dx-caret{display:none!important}\n.dx-viewer-bar{position:sticky;bottom:14px;z-index:3;display:flex;align-items:center;justify-content:center;height:52px;margin-top:-52px;pointer-events:none}\n.dx-viewer-bar .dx-sb{background:none;border:0;box-shadow:none}\n`;\n\nfunction injectStyle(): void {\n if (document.getElementById(STYLE_ID)) return;\n const style = document.createElement('style');\n style.id = STYLE_ID;\n style.textContent = CSS;\n document.head.appendChild(style);\n}\n\n/** The least width a page is shown at when all of them are, in pixels: an overview, to be read one page at a time. */\nconst LEAST_WIDE = 140;\n/** About the gap the editor leaves between pages, in pixels. */\nconst GAP = 24;\n\n/**\n * The width each page is shown at so that `pages` pages, each `ratio` times as tall as it is wide, all fit in\n * `width` × `height`, as many across as that takes: the largest that does. When none is as wide as a page is\n * still made out at, pages of a set width, and the rest is scrolled to.\n */\nexport function pageWidthToFit(pages: number, ratio: number, width: number, height: number): number {\n let best = 0;\n for (let across = 1; across <= pages; across++) {\n const wide = (width - GAP * (across + 1)) / across;\n const rows = Math.ceil(pages / across);\n best = Math.max(best, Math.min(wide, (height - GAP * (rows + 1)) / rows / ratio));\n }\n return best >= LEAST_WIDE ? best : Math.max(LEAST_WIDE, Math.min(220, (width - GAP * 2) / 2));\n}\n\n/**\n * A document to read in `container`, which must have a size: the editor, read only, and the status bar floating\n * over its pages. The words are those of the language set with `setLocale`.\n */\nexport function createViewer(container: HTMLElement, source: ViewerSource, options: ViewerOptions = {}): Viewer {\n injectStyle();\n const editor = new Editor(container, docOf(source));\n editor.readOnly = true;\n container.classList.add('dx-viewer');\n let status: StatusBar | undefined;\n let bar: HTMLElement | undefined;\n if (options.bar !== false) {\n bar = document.createElement('div');\n bar.className = 'dx-viewer-bar';\n container.appendChild(bar);\n status = createStatusBar(bar, editor, { layout: 'floating', page: 'view' });\n // Pages one under another or all at once; a document only read has no notes to open, nor language to set.\n for (const away of bar.querySelectorAll('[data-name=\"spread\"], [data-name=\"flow\"], [data-name=\"notes\"], [data-name=\"language\"]')) away.remove();\n }\n\n let current: ViewerLayout = options.layout ?? 'pages';\n const setLayout = (mode: ViewerLayout) => {\n current = mode;\n if (mode === 'pages') {\n editor.setView('column');\n editor.setFit('page');\n } else {\n editor.setView('grid');\n // The size of a page at zoom 1, from the one it is shown at now.\n const page = container.querySelector<HTMLElement>('.dx-page');\n const width = (page?.offsetWidth ?? 816) / editor.zoom;\n const ratio = (page?.offsetHeight ?? 1056) / (page?.offsetWidth ?? 816);\n const fits = pageWidthToFit(editor.pageCount, ratio, container.clientWidth, container.clientHeight);\n editor.setZoom(Math.min(1, fits / width));\n // The editor's own gaps and margins are not quite those reckoned: measured once it has drawn, and made smaller\n // until all of it fits, while a page is still made out and the editor goes that small.\n let tries = 0;\n const shrink = () => {\n if (current !== 'all' || tries++ >= 4 || container.scrollHeight <= container.clientHeight + 1) return;\n const smaller = editor.zoom * (container.clientHeight / container.scrollHeight) * 0.99;\n if (smaller * width < LEAST_WIDE) return;\n const before = editor.zoom;\n editor.setZoom(smaller);\n if (editor.zoom !== before) requestAnimationFrame(() => requestAnimationFrame(shrink));\n };\n if (fits >= LEAST_WIDE) requestAnimationFrame(() => requestAnimationFrame(shrink));\n }\n // From the first page, whichever way.\n container.scrollTop = 0;\n };\n setLayout(current);\n\n // The bar's views set the pages out as the editor does; here, so that all of them fit, or each one does.\n let shownView = editor.view;\n const offChange = editor.onChange(() => {\n if (editor.view === shownView) return;\n shownView = editor.view;\n const mode: ViewerLayout | undefined = editor.view === 'grid' ? 'all' : editor.view === 'column' ? 'pages' : undefined;\n // After the bar has done its own, which sets a zoom of its choosing once the view is set.\n if (mode) setTimeout(() => editor.view === shownView && setLayout(mode));\n });\n\n return {\n editor,\n get pages() {\n return editor.pageCount;\n },\n get layout() {\n return current;\n },\n setLayout,\n print: () => editor.print(),\n destroy() {\n offChange();\n status?.destroy();\n bar?.remove();\n editor.destroy();\n container.classList.remove('dx-viewer');\n },\n };\n}\n\n/** A blob as a data address, for what is drawn inside a picture, which may load nothing from elsewhere. */\nconst dataUrl = (blob: Blob) =>\n new Promise<string>((done, fail) => {\n const reader = new FileReader();\n reader.onload = () => done(String(reader.result));\n reader.onerror = () => fail(reader.error);\n reader.readAsDataURL(blob);\n });\n\n/** Thumbnails made one at a time: each needs an editor laid out for a moment. */\nlet making: Promise<unknown> = Promise.resolve();\n\n/**\n * The first page of a document as the editor draws it, for a thumbnail: drawn once out of sight, and given as a copy\n * of that page at its real size, its pictures in it, inside an element of the editor's class so its rules still hold\n * (`data-width` and `data-height` say the size, in pixels), to be shown made smaller.\n */\nexport function pageThumbnail(source: ViewerSource): Promise<HTMLElement> {\n const made = making.then(async () => {\n const doc = docOf(source);\n const box = document.createElement('div');\n box.setAttribute('aria-hidden', 'true');\n box.style.cssText = 'position:fixed;left:-10000px;top:0;width:1000px;height:1400px;overflow:hidden;visibility:hidden;pointer-events:none';\n document.body.appendChild(box);\n const editor = new Editor(box, doc);\n try {\n editor.readOnly = true;\n editor.setZoom(1);\n // Its text is put in the page as it comes into view: a frame or two after.\n await new Promise((done) => requestAnimationFrame(() => requestAnimationFrame(done)));\n const page = box.querySelector<HTMLElement>('.dx-page');\n if (!page) throw new Error('The document has no page to show.');\n // Its pictures as data while the editor holds them: closed, it lets their addresses go.\n const originals = [...page.querySelectorAll('img')];\n await Promise.all(originals.map((img) => img.decode().catch(() => undefined)));\n const copy = page.cloneNode(true) as HTMLElement;\n await Promise.all(\n [...copy.querySelectorAll('img')].map(async (img, i) => {\n try {\n img.src = await dataUrl(await (await fetch(originals[i].src)).blob());\n } catch {\n img.remove();\n }\n }),\n );\n for (const away of copy.querySelectorAll('[contenteditable], textarea, input, .dx-caret, .dx-sel')) away.remove();\n const shell = document.createElement('div');\n shell.className = 'dx-editor dx-readonly';\n shell.dataset.width = String(page.offsetWidth);\n shell.dataset.height = String(page.offsetHeight);\n shell.appendChild(copy);\n return shell;\n } finally {\n editor.destroy();\n box.remove();\n }\n });\n making = made.catch(() => undefined);\n return made;\n}\n\nexport interface PagePictureOptions {\n /** 'image/jpeg', as it comes, or 'image/png'. */\n type?: string;\n /** Of a JPEG, from 0 to 1. */\n quality?: number;\n}\n\n/**\n * The first page of a document as a picture, its longest side `size` pixels: what the editor draws, put in an SVG\n * with the editor's stylesheet and drawn on a canvas, its pictures in it. A font the document carries is drawn in\n * the one of the system nearest it.\n */\nexport async function pagePicture(source: ViewerSource, size: number, options: PagePictureOptions = {}): Promise<Blob> {\n const shell = await pageThumbnail(source);\n const width = Number(shell.dataset.width) || 816;\n const height = Number(shell.dataset.height) || 1056;\n // What is drawn on a canvas inside the page, as a picture: an SVG drawn as a picture reaches nothing outside it.\n for (const canvas of shell.querySelectorAll('canvas')) {\n const img = document.createElement('img');\n img.src = canvas.toDataURL();\n img.style.cssText = canvas.style.cssText;\n canvas.replaceWith(img);\n }\n const css = [...document.querySelectorAll('style')]\n .map((style) => style.textContent ?? '')\n .filter((text) => text.includes('.dx-'))\n .join('\\n');\n const page = shell.querySelector<HTMLElement>('.dx-page');\n if (page) page.style.cssText += ';position:absolute;left:0;top:0;margin:0;box-shadow:none;transform:none';\n const xhtml = new XMLSerializer().serializeToString(shell);\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\"><foreignObject x=\"0\" y=\"0\" width=\"100%\" height=\"100%\"><div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"width:${width}px;height:${height}px;background:#fff;position:relative\"><style>${css.replace(/]]>/g, '')}</style>${xhtml}</div></foreignObject></svg>`;\n const picture = new Image();\n picture.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n await picture.decode();\n const scale = size / Math.max(width, height);\n const canvas = document.createElement('canvas');\n canvas.width = Math.max(1, Math.round(width * scale));\n canvas.height = Math.max(1, Math.round(height * scale));\n const context = canvas.getContext('2d')!;\n context.fillStyle = '#fff';\n context.fillRect(0, 0, canvas.width, canvas.height);\n context.imageSmoothingQuality = 'high';\n context.drawImage(picture, 0, 0, canvas.width, canvas.height);\n return new Promise((done, fail) => canvas.toBlob((blob) => (blob ? done(blob) : fail(new Error('The page could not be drawn.'))), options.type ?? 'image/jpeg', options.quality ?? 0.85));\n}\n"],"mappings":"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"}
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { i as pageWidthToFit, n as pagePicture, r as pageThumbnail, t as createViewer } from "./chunks/viewer-CixYfz6K.js";
2
+ import { addFont, addFonts, addLocale, getLocale, locales, onLocaleChange, setLocale } from "@rebodocs/editor";
3
+ export { addFont, addFonts, addLocale, createViewer, getLocale, locales, onLocaleChange, pagePicture, pageThumbnail, pageWidthToFit, setLocale };
package/package.json CHANGED
@@ -1,6 +1,59 @@
1
1
  {
2
2
  "name": "@rebodocs/viewer",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.12.0",
4
+ "description": "A DOCX document only to read, in the browser: its pages as Word lays them out, a bar to zoom and print, and its first page as a thumbnail. With or without React.",
5
+ "license": "MIT",
6
+ "homepage": "https://rebodocs.com/docs/viewer/",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/JFranciscoRobles/rebodocs.git",
10
+ "directory": "packages/viewer"
11
+ },
12
+ "bugs": "https://github.com/JFranciscoRobles/rebodocs/issues",
13
+ "keywords": [
14
+ "docx",
15
+ "word",
16
+ "viewer",
17
+ "preview",
18
+ "thumbnail",
19
+ "react"
20
+ ],
21
+ "type": "module",
22
+ "sideEffects": false,
23
+ "main": "./index.js",
24
+ "types": "./types/viewer/src/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./types/viewer/src/index.d.ts",
28
+ "import": "./index.js",
29
+ "default": "./index.js"
30
+ },
31
+ "./react": {
32
+ "types": "./types/viewer/src/react.d.ts",
33
+ "import": "./react.js",
34
+ "default": "./react.js"
35
+ }
36
+ },
37
+ "typesVersions": {
38
+ "*": {
39
+ "react": [
40
+ "types/viewer/src/react.d.ts"
41
+ ]
42
+ }
43
+ },
44
+ "dependencies": {
45
+ "@rebodocs/core": "0.12.0",
46
+ "@rebodocs/editor": "0.12.0"
47
+ },
48
+ "peerDependencies": {
49
+ "react": ">=18"
50
+ },
51
+ "peerDependenciesMeta": {
52
+ "react": {
53
+ "optional": true
54
+ }
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ }
59
+ }
package/react.js ADDED
@@ -0,0 +1,45 @@
1
+ "use client";
2
+ import { t as createViewer } from "./chunks/viewer-CixYfz6K.js";
3
+ import { setLocale } from "@rebodocs/editor";
4
+ import { createElement, useEffect, useRef } from "react";
5
+ //#region src/react.ts
6
+ function DocsViewer({ docx, layout, bar, locale, className, style, onReady }) {
7
+ const element = useRef(null);
8
+ const viewer = useRef(void 0);
9
+ const ready = useRef(onReady);
10
+ ready.current = onReady;
11
+ useEffect(() => {
12
+ if (!element.current) return;
13
+ if (locale) setLocale(locale);
14
+ const mounted = createViewer(element.current, docx, {
15
+ layout,
16
+ bar
17
+ });
18
+ viewer.current = mounted;
19
+ ready.current?.(mounted);
20
+ return () => {
21
+ ready.current?.(void 0);
22
+ viewer.current = void 0;
23
+ mounted.destroy();
24
+ };
25
+ }, [docx, bar]);
26
+ useEffect(() => {
27
+ if (layout && viewer.current && viewer.current.layout !== layout) viewer.current.setLayout(layout);
28
+ }, [layout]);
29
+ useEffect(() => {
30
+ if (locale) setLocale(locale);
31
+ }, [locale]);
32
+ return createElement("div", {
33
+ ref: element,
34
+ className,
35
+ style: {
36
+ height: "100%",
37
+ overflow: "auto",
38
+ ...style
39
+ }
40
+ });
41
+ }
42
+ //#endregion
43
+ export { DocsViewer };
44
+
45
+ //# sourceMappingURL=react.js.map
package/react.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.js","names":[],"sources":["../src/react.ts"],"sourcesContent":["// A document to read, as a component of React: an element, and the viewer\n// mounted in it once the page is in the browser, again for\n// another file, and taken away with the component. On the server it draws\n// the empty element and nothing else.\n\nimport { type CSSProperties, createElement, useEffect, useRef } from 'react';\nimport { setLocale } from '@rebodocs/editor';\nimport { type Viewer, type ViewerLayout, type ViewerSource, createViewer } from './viewer';\n\nexport interface DocsViewerProps {\n /** The file: the bytes of a .docx, or a Doc already read. */\n docx: ViewerSource;\n /** How the pages are set out: one under another, each whole on the screen ('pages', as it comes), or all at once ('all'). */\n layout?: ViewerLayout;\n /** False to leave out the bar floating over the pages. */\n bar?: boolean;\n /** The language of its words: 'en', 'es'… As the rest of Rebodocs in the page, unless given. */\n locale?: string;\n className?: string;\n /** The element takes the height of what it is in: something must give it one. */\n style?: CSSProperties;\n /** Called with the viewer once it is there, to print or to set the pages out, and with nothing when it is gone. */\n onReady?: (viewer: Viewer | undefined) => void;\n}\n\nexport function DocsViewer({ docx, layout, bar, locale, className, style, onReady }: DocsViewerProps) {\n const element = useRef<HTMLDivElement>(null);\n const viewer = useRef<Viewer | undefined>(undefined);\n const ready = useRef(onReady);\n ready.current = onReady;\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: mounted again for another file or another bar; the layout follows below\n useEffect(() => {\n if (!element.current) return;\n if (locale) setLocale(locale);\n const mounted = createViewer(element.current, docx, { layout, bar });\n viewer.current = mounted;\n ready.current?.(mounted);\n return () => {\n ready.current?.(undefined);\n viewer.current = undefined;\n mounted.destroy();\n };\n }, [docx, bar]);\n\n useEffect(() => {\n if (layout && viewer.current && viewer.current.layout !== layout) viewer.current.setLayout(layout);\n }, [layout]);\n useEffect(() => {\n if (locale) setLocale(locale);\n }, [locale]);\n\n return createElement('div', { ref: element, className, style: { height: '100%', overflow: 'auto', ...style } });\n}\n"],"mappings":";;;;;AAyBA,SAAgB,WAAW,EAAE,MAAM,QAAQ,KAAK,QAAQ,WAAW,OAAO,WAA4B;CACpG,MAAM,UAAU,OAAuB,IAAI;CAC3C,MAAM,SAAS,OAA2B,KAAA,CAAS;CACnD,MAAM,QAAQ,OAAO,OAAO;CAC5B,MAAM,UAAU;CAGhB,gBAAgB;EACd,IAAI,CAAC,QAAQ,SAAS;EACtB,IAAI,QAAQ,UAAU,MAAM;EAC5B,MAAM,UAAU,aAAa,QAAQ,SAAS,MAAM;GAAE;GAAQ;EAAI,CAAC;EACnE,OAAO,UAAU;EACjB,MAAM,UAAU,OAAO;EACvB,aAAa;GACX,MAAM,UAAU,KAAA,CAAS;GACzB,OAAO,UAAU,KAAA;GACjB,QAAQ,QAAQ;EAClB;CACF,GAAG,CAAC,MAAM,GAAG,CAAC;CAEd,gBAAgB;EACd,IAAI,UAAU,OAAO,WAAW,OAAO,QAAQ,WAAW,QAAQ,OAAO,QAAQ,UAAU,MAAM;CACnG,GAAG,CAAC,MAAM,CAAC;CACX,gBAAgB;EACd,IAAI,QAAQ,UAAU,MAAM;CAC9B,GAAG,CAAC,MAAM,CAAC;CAEX,OAAO,cAAc,OAAO;EAAE,KAAK;EAAS;EAAW,OAAO;GAAE,QAAQ;GAAQ,UAAU;GAAQ,GAAG;EAAM;CAAE,CAAC;AAChH"}
@@ -0,0 +1,3 @@
1
+ export { type PagePictureOptions, type Viewer, type ViewerLayout, type ViewerOptions, type ViewerSource, createViewer, pagePicture, pageThumbnail, pageWidthToFit } from './viewer.js';
2
+ export { type FontSource, type MessageKey, type Messages, type Texts, addFont, addFonts, addLocale, getLocale, locales, onLocaleChange, setLocale } from '@rebodocs/editor';
3
+ export type { Doc } from '@rebodocs/core/internal';