web-doc 0.6.2 → 0.7.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/THIRD_PARTY_NOTICES.md +26 -1
- package/dist/adapters/docx-images.d.ts +13 -4
- package/dist/adapters/docx-images.js +3 -0
- package/dist/adapters/docx-paragraphs.d.ts +65 -0
- package/dist/adapters/docx-paragraphs.js +165 -0
- package/dist/adapters/docx-prepass.d.ts +17 -0
- package/dist/adapters/docx-prepass.js +230 -0
- package/dist/adapters/office.d.ts +31 -5
- package/dist/adapters/office.js +60 -29
- package/dist/adapters/pdf.d.ts +9 -0
- package/dist/adapters/pdf.js +10 -0
- package/dist/assets/pdfium/pdfium.wasm +0 -0
- package/dist/contracts.d.ts +47 -2
- package/dist/edit/assets.d.ts +23 -0
- package/dist/edit/assets.js +75 -0
- package/dist/edit/docx/elements.d.ts +6 -0
- package/dist/edit/docx/elements.js +90 -0
- package/dist/edit/docx/engine.d.ts +51 -0
- package/dist/edit/docx/engine.js +473 -0
- package/dist/edit/docx/handlers.d.ts +3 -0
- package/dist/edit/docx/handlers.js +15 -0
- package/dist/edit/docx/ids.d.ts +45 -0
- package/dist/edit/docx/ids.js +101 -0
- package/dist/edit/docx/model.d.ts +92 -0
- package/dist/edit/docx/model.js +339 -0
- package/dist/edit/docx/operations.d.ts +37 -0
- package/dist/edit/docx/operations.js +3 -0
- package/dist/edit/docx/provider.d.ts +13 -0
- package/dist/edit/docx/provider.js +34 -0
- package/dist/edit/docx/schemas.d.ts +6 -0
- package/dist/edit/docx/schemas.js +192 -0
- package/dist/edit/docx/session.d.ts +37 -0
- package/dist/edit/docx/session.js +426 -0
- package/dist/edit/docx/structure-ops.d.ts +6 -0
- package/dist/edit/docx/structure-ops.js +494 -0
- package/dist/edit/docx/style.d.ts +45 -0
- package/dist/edit/docx/style.js +375 -0
- package/dist/edit/docx/table-ops.d.ts +4 -0
- package/dist/edit/docx/table-ops.js +219 -0
- package/dist/edit/docx/text-ops.d.ts +18 -0
- package/dist/edit/docx/text-ops.js +458 -0
- package/dist/edit/docx/text.d.ts +34 -0
- package/dist/edit/docx/text.js +263 -0
- package/dist/edit/docx/types.d.ts +191 -0
- package/dist/edit/docx/types.js +1 -0
- package/dist/edit/docx/write.d.ts +50 -0
- package/dist/edit/docx/write.js +352 -0
- package/dist/edit/engine.d.ts +117 -0
- package/dist/edit/engine.js +1 -0
- package/dist/edit/history.d.ts +53 -0
- package/dist/edit/history.js +101 -0
- package/dist/edit/ooxml/names.d.ts +20 -0
- package/dist/edit/ooxml/names.js +61 -0
- package/dist/edit/ooxml/opc.d.ts +50 -0
- package/dist/edit/ooxml/opc.js +150 -0
- package/dist/edit/ooxml/package.d.ts +82 -0
- package/dist/edit/ooxml/package.js +233 -0
- package/dist/edit/ooxml/patch.d.ts +51 -0
- package/dist/edit/ooxml/patch.js +250 -0
- package/dist/edit/ooxml/transaction.d.ts +39 -0
- package/dist/edit/ooxml/transaction.js +316 -0
- package/dist/edit/ooxml/worker.d.ts +8 -0
- package/dist/edit/ooxml/worker.js +12 -0
- package/dist/edit/ooxml/writer.d.ts +21 -0
- package/dist/edit/ooxml/writer.js +187 -0
- package/dist/edit/ooxml/xml.d.ts +74 -0
- package/dist/edit/ooxml/xml.js +451 -0
- package/dist/edit/ooxml/zip.d.ts +54 -0
- package/dist/edit/ooxml/zip.js +280 -0
- package/dist/edit/operations.d.ts +19 -0
- package/dist/edit/operations.js +137 -0
- package/dist/edit/pdf/engine/compact.d.ts +6 -0
- package/dist/edit/pdf/engine/compact.js +442 -0
- package/dist/edit/pdf/engine/document.d.ts +95 -0
- package/dist/edit/pdf/engine/document.js +868 -0
- package/dist/edit/pdf/engine/elements.d.ts +45 -0
- package/dist/edit/pdf/engine/elements.js +313 -0
- package/dist/edit/pdf/engine/existing-text.d.ts +4 -0
- package/dist/edit/pdf/engine/existing-text.js +424 -0
- package/dist/edit/pdf/engine/fonts.d.ts +78 -0
- package/dist/edit/pdf/engine/fonts.js +466 -0
- package/dist/edit/pdf/engine/geometry.d.ts +36 -0
- package/dist/edit/pdf/engine/geometry.js +97 -0
- package/dist/edit/pdf/engine/handler.d.ts +21 -0
- package/dist/edit/pdf/engine/handler.js +108 -0
- package/dist/edit/pdf/engine/images.d.ts +33 -0
- package/dist/edit/pdf/engine/images.js +188 -0
- package/dist/edit/pdf/engine/layout.d.ts +23 -0
- package/dist/edit/pdf/engine/layout.js +177 -0
- package/dist/edit/pdf/engine/operations.d.ts +67 -0
- package/dist/edit/pdf/engine/operations.js +3 -0
- package/dist/edit/pdf/engine/pages.d.ts +6 -0
- package/dist/edit/pdf/engine/pages.js +98 -0
- package/dist/edit/pdf/engine/pdfium.d.ts +195 -0
- package/dist/edit/pdf/engine/pdfium.js +249 -0
- package/dist/edit/pdf/engine/shapes.d.ts +4 -0
- package/dist/edit/pdf/engine/shapes.js +183 -0
- package/dist/edit/pdf/engine/tables.d.ts +44 -0
- package/dist/edit/pdf/engine/tables.js +315 -0
- package/dist/edit/pdf/engine/text-box.d.ts +71 -0
- package/dist/edit/pdf/engine/text-box.js +317 -0
- package/dist/edit/pdf/engine/text-layout.d.ts +28 -0
- package/dist/edit/pdf/engine/text-layout.js +67 -0
- package/dist/edit/pdf/engine/transform.d.ts +5 -0
- package/dist/edit/pdf/engine/transform.js +137 -0
- package/dist/edit/pdf/provider.d.ts +35 -0
- package/dist/edit/pdf/provider.js +92 -0
- package/dist/edit/pdf/range-map.d.ts +14 -0
- package/dist/edit/pdf/range-map.js +107 -0
- package/dist/edit/pdf/schemas.d.ts +11 -0
- package/dist/edit/pdf/schemas.js +291 -0
- package/dist/edit/pdf/selection.d.ts +5 -0
- package/dist/edit/pdf/selection.js +152 -0
- package/dist/edit/pdf/session.d.ts +53 -0
- package/dist/edit/pdf/session.js +273 -0
- package/dist/edit/pdf/types.d.ts +336 -0
- package/dist/edit/pdf/types.js +1 -0
- package/dist/edit/pptx/elements.d.ts +74 -0
- package/dist/edit/pptx/elements.js +301 -0
- package/dist/edit/pptx/engine.d.ts +37 -0
- package/dist/edit/pptx/engine.js +466 -0
- package/dist/edit/pptx/geometry.d.ts +72 -0
- package/dist/edit/pptx/geometry.js +193 -0
- package/dist/edit/pptx/handler.d.ts +7 -0
- package/dist/edit/pptx/handler.js +91 -0
- package/dist/edit/pptx/handlers.d.ts +3 -0
- package/dist/edit/pptx/handlers.js +21 -0
- package/dist/edit/pptx/image-table-ops.d.ts +5 -0
- package/dist/edit/pptx/image-table-ops.js +233 -0
- package/dist/edit/pptx/model.d.ts +69 -0
- package/dist/edit/pptx/model.js +170 -0
- package/dist/edit/pptx/operations.d.ts +50 -0
- package/dist/edit/pptx/operations.js +11 -0
- package/dist/edit/pptx/provider.d.ts +17 -0
- package/dist/edit/pptx/provider.js +40 -0
- package/dist/edit/pptx/schemas.d.ts +6 -0
- package/dist/edit/pptx/schemas.js +220 -0
- package/dist/edit/pptx/session.d.ts +44 -0
- package/dist/edit/pptx/session.js +114 -0
- package/dist/edit/pptx/shape-ops.d.ts +10 -0
- package/dist/edit/pptx/shape-ops.js +486 -0
- package/dist/edit/pptx/slide-ops.d.ts +6 -0
- package/dist/edit/pptx/slide-ops.js +353 -0
- package/dist/edit/pptx/style.d.ts +31 -0
- package/dist/edit/pptx/style.js +181 -0
- package/dist/edit/pptx/text-ops.d.ts +13 -0
- package/dist/edit/pptx/text-ops.js +267 -0
- package/dist/edit/pptx/text-write.d.ts +67 -0
- package/dist/edit/pptx/text-write.js +293 -0
- package/dist/edit/pptx/text.d.ts +41 -0
- package/dist/edit/pptx/text.js +99 -0
- package/dist/edit/pptx/types.d.ts +232 -0
- package/dist/edit/pptx/types.js +1 -0
- package/dist/edit/schema.d.ts +11 -0
- package/dist/edit/schema.js +275 -0
- package/dist/edit/session.d.ts +66 -0
- package/dist/edit/session.js +701 -0
- package/dist/edit/sessions.d.ts +8 -0
- package/dist/edit/sessions.js +1 -0
- package/dist/edit/types.d.ts +276 -0
- package/dist/edit/types.js +1 -0
- package/dist/edit/worker-engine.d.ts +31 -0
- package/dist/edit/worker-engine.js +91 -0
- package/dist/fonts/THIRD_PARTY_NOTICES.md +3 -0
- package/dist/fonts/manifest.json +6 -1
- package/dist/fonts/noto-sans-latin-cyrillic.ttf +0 -0
- package/dist/fonts.d.ts +2 -0
- package/dist/fonts.js +4 -0
- package/dist/headless.d.ts +4 -0
- package/dist/headless.js +4 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/limits.js +3 -0
- package/dist/ooxml-edit-worker.d.ts +1 -0
- package/dist/ooxml-edit-worker.js +8 -0
- package/dist/pdf-edit-worker.d.ts +1 -0
- package/dist/pdf-edit-worker.js +35 -0
- package/dist/spreadsheet-viewport.d.ts +5 -0
- package/dist/spreadsheet-viewport.js +11 -0
- package/dist/ui.js +7 -0
- package/dist/viewer.d.ts +6 -0
- package/dist/viewer.js +293 -19
- package/dist/viewport.d.ts +13 -0
- package/dist/viewport.js +126 -1
- package/dist/worker-protocol.d.ts +39 -2
- package/dist/workers/ooxml-edit-worker.js +8671 -0
- package/dist/workers/pdf-edit-worker.js +10844 -0
- package/package.json +3 -3
package/dist/adapters/office.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { abortError, ViewerError } from "../errors.js";
|
|
2
|
-
import {
|
|
2
|
+
import { DocxSession } from "../edit/docx/session.js";
|
|
3
|
+
import { PptxSession } from "../edit/pptx/session.js";
|
|
4
|
+
import { createDocxParagraphIdResolver, } from "./docx-paragraphs.js";
|
|
5
|
+
import { prepareDocxForDisplay } from "./docx-prepass.js";
|
|
3
6
|
import { enforceContainerLimits } from "../limits.js";
|
|
4
7
|
const MODERN_FORMATS = [
|
|
5
8
|
"docx",
|
|
@@ -17,12 +20,38 @@ const ROW_HEADER_WIDTH = 50;
|
|
|
17
20
|
const COLUMN_HEADER_HEIGHT = 22;
|
|
18
21
|
export class OfficeDocumentAdapter {
|
|
19
22
|
id = "office";
|
|
23
|
+
/**
|
|
24
|
+
* PPTX and DOCX editing on the OOXML package layer. The worker and the
|
|
25
|
+
* engine client are imported on the first `edit()`; viewing never loads
|
|
26
|
+
* them.
|
|
27
|
+
*/
|
|
28
|
+
edit = {
|
|
29
|
+
formats: ["pptx", "pptm", "ppsx", "docx", "docm"],
|
|
30
|
+
load: async (original, context) => context.format === "docx"
|
|
31
|
+
? (await import("../edit/docx/provider.js")).loadDocxEditEngine(original, context, this.#options.edit ?? {})
|
|
32
|
+
: (await import("../edit/pptx/provider.js")).loadPptxEditEngine(original, context, this.#options.edit ?? {}),
|
|
33
|
+
createSession: (core, access) => core.format === "docx"
|
|
34
|
+
? new DocxSession(core, access)
|
|
35
|
+
: new PptxSession(core),
|
|
36
|
+
};
|
|
20
37
|
formats = [...MODERN_FORMATS, ...LEGACY_FORMATS];
|
|
21
38
|
#options;
|
|
22
39
|
constructor(options = {}) {
|
|
23
40
|
this.#options = options;
|
|
24
41
|
}
|
|
25
42
|
async open(input, context) {
|
|
43
|
+
return this.#open(input, context, false);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Edited bytes open as a fresh document (the engine owns no reusable
|
|
47
|
+
* state); a presentation lays its slides out progressively so the slide
|
|
48
|
+
* on screen paints without waiting for the whole deck — Firefox takes
|
|
49
|
+
* seconds for a 500-slide preflight. The viewer closes `previous`.
|
|
50
|
+
*/
|
|
51
|
+
reopen(_previous, data, context) {
|
|
52
|
+
return this.#open(data, context, true);
|
|
53
|
+
}
|
|
54
|
+
async #open(input, context, reopening) {
|
|
26
55
|
throwIfAborted(context.signal);
|
|
27
56
|
let format = context.format;
|
|
28
57
|
let data = input;
|
|
@@ -57,12 +86,16 @@ export class OfficeDocumentAdapter {
|
|
|
57
86
|
useGoogleFonts: false,
|
|
58
87
|
maxZipEntryBytes: context.limits.maxZipEntryBytes,
|
|
59
88
|
mode: "main",
|
|
89
|
+
...(reopening ? { progressiveLayout: true } : {}),
|
|
60
90
|
};
|
|
61
|
-
const buffer = exactArrayBuffer(data);
|
|
62
91
|
try {
|
|
63
92
|
const kind = kindFor(format);
|
|
64
93
|
if (kind === "document") {
|
|
65
|
-
|
|
94
|
+
// What the renderer sees: oversized inline pictures fitted and every
|
|
95
|
+
// paragraph carrying an id; the bytes a session saves are the input.
|
|
96
|
+
const display = await prepareDocxForDisplay(data, context.limits, context.signal);
|
|
97
|
+
throwIfAborted(context.signal);
|
|
98
|
+
const backend = await this.#loadDocx(exactArrayBuffer(display.bytes), engineOptions);
|
|
66
99
|
throwIfAborted(context.signal, backend);
|
|
67
100
|
context.reportProgress({
|
|
68
101
|
phase: "parsing",
|
|
@@ -70,8 +103,15 @@ export class OfficeDocumentAdapter {
|
|
|
70
103
|
total: 1,
|
|
71
104
|
ratio: 1,
|
|
72
105
|
});
|
|
73
|
-
return {
|
|
106
|
+
return {
|
|
107
|
+
kind,
|
|
108
|
+
format: context.format,
|
|
109
|
+
backend,
|
|
110
|
+
warnings,
|
|
111
|
+
paragraphIdOf: createDocxParagraphIdResolver(docxModelOf(backend)),
|
|
112
|
+
};
|
|
74
113
|
}
|
|
114
|
+
const buffer = exactArrayBuffer(data);
|
|
75
115
|
if (kind === "presentation") {
|
|
76
116
|
const backend = await this.#loadPptx(buffer, engineOptions);
|
|
77
117
|
throwIfAborted(context.signal, backend);
|
|
@@ -232,6 +272,7 @@ export class OfficeDocumentAdapter {
|
|
|
232
272
|
return runs.map((run) => {
|
|
233
273
|
const logicalStart = logicalOffset;
|
|
234
274
|
logicalOffset += run.text.length;
|
|
275
|
+
const paragraphId = run.paragraphId ?? handle.paragraphIdOf(run.source);
|
|
235
276
|
return {
|
|
236
277
|
text: run.text,
|
|
237
278
|
x: run.x,
|
|
@@ -240,6 +281,7 @@ export class OfficeDocumentAdapter {
|
|
|
240
281
|
height: run.h,
|
|
241
282
|
font: run.font,
|
|
242
283
|
fontSize: run.fontSize,
|
|
284
|
+
...(paragraphId === undefined ? {} : { paragraphId }),
|
|
243
285
|
...(run.letterSpacingPx === undefined
|
|
244
286
|
? {}
|
|
245
287
|
: { letterSpacingPx: run.letterSpacingPx }),
|
|
@@ -326,11 +368,10 @@ export class OfficeDocumentAdapter {
|
|
|
326
368
|
return convertInWorker(data, format, workerUrl, moduleUrl, context.signal, context.limits.maxOperationMs);
|
|
327
369
|
}
|
|
328
370
|
async #loadDocx(data, options) {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
return backend;
|
|
371
|
+
if (this.#options.engines?.docx)
|
|
372
|
+
return this.#options.engines.docx(data, options);
|
|
373
|
+
const { DocxDocument } = await import("@silurus/ooxml/docx");
|
|
374
|
+
return DocxDocument.load(data, options);
|
|
334
375
|
}
|
|
335
376
|
async #loadXlsx(data, options) {
|
|
336
377
|
if (this.#options.engines?.xlsx)
|
|
@@ -341,13 +382,22 @@ export class OfficeDocumentAdapter {
|
|
|
341
382
|
async #loadPptx(data, options) {
|
|
342
383
|
if (this.#options.engines?.pptx)
|
|
343
384
|
return this.#options.engines.pptx(data, options);
|
|
344
|
-
const { PptxPresentation } = await import("@silurus/ooxml
|
|
385
|
+
const { PptxPresentation } = await import("@silurus/ooxml/pptx");
|
|
345
386
|
return PptxPresentation.load(data, options);
|
|
346
387
|
}
|
|
347
388
|
}
|
|
348
389
|
export function createOfficeAdapter(options = {}) {
|
|
349
390
|
return new OfficeDocumentAdapter(options);
|
|
350
391
|
}
|
|
392
|
+
/** The engine's model when it is reachable; a worker-mode getter throws. */
|
|
393
|
+
function docxModelOf(backend) {
|
|
394
|
+
try {
|
|
395
|
+
return backend.document;
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
return undefined;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
351
401
|
export function sanitizeOfficeHyperlink(target) {
|
|
352
402
|
if (!target)
|
|
353
403
|
return undefined;
|
|
@@ -672,22 +722,3 @@ function normalizeOfficeError(error) {
|
|
|
672
722
|
function textDirection(text) {
|
|
673
723
|
return /[\u0590-\u08ff\ufb1d-\ufefc]/u.test(text) ? "rtl" : "ltr";
|
|
674
724
|
}
|
|
675
|
-
/**
|
|
676
|
-
* Oversized inline pictures are shrunk to the section's content box before
|
|
677
|
-
* the engine paginates (the layout is built lazily on first page access).
|
|
678
|
-
* The model is reachable in `main` mode only; a worker-mode engine keeps
|
|
679
|
-
* Word's geometry.
|
|
680
|
-
*/
|
|
681
|
-
function fitDocxInlineImages(backend) {
|
|
682
|
-
if (backend.mode === "worker")
|
|
683
|
-
return;
|
|
684
|
-
let model;
|
|
685
|
-
try {
|
|
686
|
-
model = backend.document;
|
|
687
|
-
}
|
|
688
|
-
catch {
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
if (model)
|
|
692
|
-
fitInlineImagesToPage(model);
|
|
693
|
-
}
|
package/dist/adapters/pdf.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { AdapterOpenContext, DocumentAdapter, DocumentInfo, PageSize, RenderViewport, TextRun } from "../contracts.js";
|
|
2
|
+
import type { EditEngineProvider } from "../edit/engine.js";
|
|
3
|
+
import type { PdfEditProviderOptions } from "../edit/pdf/provider.js";
|
|
2
4
|
export interface PdfBackend {
|
|
3
5
|
readonly pageCount: number;
|
|
4
6
|
pageSize?(pageIndex: number, signal?: AbortSignal): PageSize | Promise<PageSize>;
|
|
@@ -13,6 +15,8 @@ export interface PdfAdapterOptions {
|
|
|
13
15
|
readonly wasmUrl?: string | URL;
|
|
14
16
|
readonly iccUrl?: string | URL;
|
|
15
17
|
readonly open?: (data: Uint8Array, context: AdapterOpenContext) => Promise<PdfBackend>;
|
|
18
|
+
/** Where the PDF edit worker and its WebAssembly live. */
|
|
19
|
+
readonly edit?: PdfEditProviderOptions;
|
|
16
20
|
}
|
|
17
21
|
interface PdfHandle {
|
|
18
22
|
readonly backend: PdfBackend;
|
|
@@ -21,6 +25,11 @@ export declare class PdfDocumentAdapter implements DocumentAdapter<PdfHandle> {
|
|
|
21
25
|
#private;
|
|
22
26
|
readonly id = "pdf";
|
|
23
27
|
readonly formats: readonly ["pdf"];
|
|
28
|
+
/**
|
|
29
|
+
* PDFium-backed editing. The worker, its WebAssembly and the engine client
|
|
30
|
+
* are imported on the first `edit()`; viewing never loads them.
|
|
31
|
+
*/
|
|
32
|
+
readonly edit: EditEngineProvider;
|
|
24
33
|
constructor(options?: PdfAdapterOptions);
|
|
25
34
|
open(data: Uint8Array, context: AdapterOpenContext): Promise<PdfHandle>;
|
|
26
35
|
getInfo(handle: PdfHandle): Promise<DocumentInfo>;
|
package/dist/adapters/pdf.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { abortError, ViewerError } from "../errors.js";
|
|
2
|
+
import { PdfSession } from "../edit/pdf/session.js";
|
|
2
3
|
// Preserve the public zoom=1 contract: one PDF point maps to one renderer CSS
|
|
3
4
|
// pixel. Per-page geometry still prevents the viewport from coercing pages to
|
|
4
5
|
// A4 or changing their width while virtualizing.
|
|
@@ -7,6 +8,15 @@ export class PdfDocumentAdapter {
|
|
|
7
8
|
id = "pdf";
|
|
8
9
|
formats = ["pdf"];
|
|
9
10
|
#options;
|
|
11
|
+
/**
|
|
12
|
+
* PDFium-backed editing. The worker, its WebAssembly and the engine client
|
|
13
|
+
* are imported on the first `edit()`; viewing never loads them.
|
|
14
|
+
*/
|
|
15
|
+
edit = {
|
|
16
|
+
formats: ["pdf"],
|
|
17
|
+
load: async (original, context) => (await import("../edit/pdf/provider.js")).loadPdfEditEngine(original, context, this.#options.edit ?? {}),
|
|
18
|
+
createSession: (core) => new PdfSession(core),
|
|
19
|
+
};
|
|
10
20
|
constructor(options = {}) {
|
|
11
21
|
this.#options = options;
|
|
12
22
|
}
|
|
Binary file
|
package/dist/contracts.d.ts
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
|
+
import type { EditEngineProvider } from "./edit/engine.js";
|
|
2
|
+
import type { EditSession } from "./edit/sessions.js";
|
|
3
|
+
import type { DocumentChange, LayoutChange, EditOptions, EditStateChange, PageHit, PageRect, ViewportRect } from "./edit/types.js";
|
|
1
4
|
export declare const supportedFormats: readonly ["docx", "docm", "xlsx", "xlsm", "pptx", "pptm", "ppsx", "doc", "xls", "ppt", "pdf", "csv", "tsv", "png", "jpeg", "gif", "webp", "svg", "bmp", "tiff"];
|
|
2
5
|
export type DocumentFormat = (typeof supportedFormats)[number];
|
|
3
6
|
export type ViewerLocale = "en" | "ru";
|
|
4
7
|
export type FitMode = "none" | "page" | "width";
|
|
5
8
|
export type ViewerStatus = "idle" | "loading" | "ready" | "error" | "destroyed";
|
|
6
9
|
export type RenderUnit = "page" | "slide" | "sheet" | "image";
|
|
7
|
-
export type ViewerErrorCode = "unsupported-format" | "fidelity-unsupported" | "invalid-file" | "encrypted-document" | "resource-limit" | "network-error" | "aborted" | "font-unavailable" | "render-failed" | "worker-crashed" | "lifecycle-error" | "
|
|
8
|
-
|
|
10
|
+
export type ViewerErrorCode = "unsupported-format" | "fidelity-unsupported" | "invalid-file" | "encrypted-document" | "resource-limit" | "network-error" | "aborted" | "font-unavailable" | "render-failed" | "worker-crashed" | "lifecycle-error" | "edit-unsupported" | "invalid-operation" | "edit-conflict" | "edit-failed"
|
|
11
|
+
/** An OOXML package the editing layer cannot open: ZIP64, encryption, an unknown method, several disks. */
|
|
12
|
+
| "unsupported-package"
|
|
13
|
+
/** An OOXML part that can be read but not patched: not UTF-8, or not byte-stable through decoding. */
|
|
14
|
+
| "unsupported-part"
|
|
15
|
+
/** An XML part the scanner cannot parse. */
|
|
16
|
+
| "malformed-xml"
|
|
17
|
+
/** A patch that overlaps, is stale, is not well-formed, or does not read back as expected. */
|
|
18
|
+
| "invalid-patch" | "internal";
|
|
19
|
+
export type ViewerWarningCode = "format-hint-mismatch" | "unsupported-feature" | "font-substitution" | "font-unavailable" | "external-resource-blocked" | "fidelity-degraded" | "privacy-not-guaranteed";
|
|
9
20
|
export interface ViewerErrorData {
|
|
10
21
|
readonly name: "ViewerError";
|
|
11
22
|
readonly code: ViewerErrorCode;
|
|
@@ -34,6 +45,12 @@ export interface ResourceLimits {
|
|
|
34
45
|
readonly maxDocumentUnits: number;
|
|
35
46
|
readonly maxConcurrentRenders: number;
|
|
36
47
|
readonly maxOperationMs: number;
|
|
48
|
+
/** Operations in one `EditSession.apply()` call. */
|
|
49
|
+
readonly maxEditOperations: number;
|
|
50
|
+
/** Undoable edit batches kept; older ones are folded into the starting point. */
|
|
51
|
+
readonly maxEditHistory: number;
|
|
52
|
+
/** Memory for retained edit checkpoints; fewer are kept when a file is big. */
|
|
53
|
+
readonly maxEditCheckpointBytes: number;
|
|
37
54
|
}
|
|
38
55
|
export type BinaryDocumentSource = ArrayBuffer | Uint8Array | Blob;
|
|
39
56
|
export type DocumentSource = BinaryDocumentSource | URL | string;
|
|
@@ -164,6 +181,14 @@ export interface TextRun {
|
|
|
164
181
|
readonly hyperlink?: HyperlinkTarget;
|
|
165
182
|
readonly row?: number;
|
|
166
183
|
readonly column?: number;
|
|
184
|
+
/**
|
|
185
|
+
* DOCX: the `w:p` of the source XML this run was laid out from, as the
|
|
186
|
+
* file's `w14:paraId` or the deterministic id the viewer assigns to a
|
|
187
|
+
* paragraph without one (eight hex digits). The runs of a paragraph that
|
|
188
|
+
* continues on the next page share it. Absent when the run belongs to no
|
|
189
|
+
* source paragraph.
|
|
190
|
+
*/
|
|
191
|
+
readonly paragraphId?: string;
|
|
167
192
|
}
|
|
168
193
|
export type HyperlinkTarget = {
|
|
169
194
|
readonly kind: "external";
|
|
@@ -216,6 +241,9 @@ export interface ViewerEventMap {
|
|
|
216
241
|
};
|
|
217
242
|
readonly viewchange: ViewerState;
|
|
218
243
|
readonly searchchange: SearchResult | null;
|
|
244
|
+
readonly editstatechange: EditStateChange;
|
|
245
|
+
readonly documentchange: DocumentChange;
|
|
246
|
+
readonly layoutchange: LayoutChange;
|
|
219
247
|
}
|
|
220
248
|
/**
|
|
221
249
|
* Tuning for the fuzzy fallback that runs when the exact search finds
|
|
@@ -389,6 +417,8 @@ export interface DocumentCapabilities {
|
|
|
389
417
|
readonly cellSelection: boolean;
|
|
390
418
|
readonly search: boolean;
|
|
391
419
|
readonly thumbnails: boolean;
|
|
420
|
+
/** True when `edit()` is available for this document. */
|
|
421
|
+
readonly editing: boolean;
|
|
392
422
|
}
|
|
393
423
|
export type DocumentMetadata = DocumentInfo;
|
|
394
424
|
export interface AdapterOpenContext {
|
|
@@ -410,6 +440,13 @@ export interface DocumentAdapter<THandle = unknown> {
|
|
|
410
440
|
getTextMap?(handle: THandle, pageIndex: number, signal?: AbortSignal): Promise<readonly TextRun[]>;
|
|
411
441
|
close(handle: THandle): void | Promise<void>;
|
|
412
442
|
destroy?(): void | Promise<void>;
|
|
443
|
+
/** Editing support for some of this adapter's formats. */
|
|
444
|
+
readonly edit?: EditEngineProvider;
|
|
445
|
+
/**
|
|
446
|
+
* Opens edited bytes of a document, reusing what `previous` holds (for
|
|
447
|
+
* example a worker). `previous` stays open; the viewer closes it afterwards.
|
|
448
|
+
*/
|
|
449
|
+
reopen?(previous: THandle, data: Uint8Array, context: AdapterOpenContext): Promise<THandle>;
|
|
413
450
|
}
|
|
414
451
|
export type ViewerEventListener<K extends keyof ViewerEventMap> = (event: ViewerEventMap[K]) => void;
|
|
415
452
|
export interface ViewerApi {
|
|
@@ -445,6 +482,14 @@ export interface ViewerApi {
|
|
|
445
482
|
copySelection(): Promise<string>;
|
|
446
483
|
getOriginalBytes(): Uint8Array | undefined;
|
|
447
484
|
downloadOriginal(fileName?: string): Blob;
|
|
485
|
+
/** Starts editing the loaded document, or returns the session already started. */
|
|
486
|
+
edit(options?: EditOptions): Promise<EditSession>;
|
|
487
|
+
/** The active session of the loaded document, if `edit()` was called. */
|
|
488
|
+
getEditSession(): EditSession | undefined;
|
|
489
|
+
/** Client-space rectangle of a page-space rectangle; undefined if the page is not mounted. */
|
|
490
|
+
pageToClient(pageIndex: number, rect: PageRect): ViewportRect | undefined;
|
|
491
|
+
/** Page under a client-space point and the point in page space; undefined outside pages. */
|
|
492
|
+
clientToPage(clientX: number, clientY: number): PageHit | undefined;
|
|
448
493
|
on<K extends keyof ViewerEventMap>(type: K, listener: ViewerEventListener<K>): () => void;
|
|
449
494
|
destroy(): Promise<void>;
|
|
450
495
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { BinaryData, JsonSchema } from "./types.js";
|
|
2
|
+
export declare function isAssetReference(value: unknown): value is string;
|
|
3
|
+
/** The content-addressed id of `bytes`. */
|
|
4
|
+
export declare function assetIdOf(bytes: Uint8Array): Promise<string>;
|
|
5
|
+
/** Something that answers an asset reference with its bytes. */
|
|
6
|
+
export interface AssetSource {
|
|
7
|
+
get(id: string): Uint8Array | undefined;
|
|
8
|
+
}
|
|
9
|
+
export declare class AssetStore implements AssetSource {
|
|
10
|
+
#private;
|
|
11
|
+
get(id: string): Uint8Array | undefined;
|
|
12
|
+
has(id: string): boolean;
|
|
13
|
+
set(id: string, bytes: Uint8Array): void;
|
|
14
|
+
/** Bytes held, for limits. */
|
|
15
|
+
get byteLength(): number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Bytes of a `BinaryData` value that passed schema validation: inline bytes,
|
|
19
|
+
* a base64 string, or an asset reference the store knows.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveBinary(data: BinaryData, assets: AssetSource): Uint8Array;
|
|
22
|
+
/** Names of an operation's top-level properties its schema marks as binary. */
|
|
23
|
+
export declare function binaryFields(schema: JsonSchema | undefined): string[];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { ViewerError } from "../errors.js";
|
|
2
|
+
/*
|
|
3
|
+
* Binary payloads of operations live in a content-addressed store for the
|
|
4
|
+
* session: an operation refers to them as `asset:<sha-256 hex>`, so the
|
|
5
|
+
* history, undo, redo, dry runs and recovery never copy image bytes again.
|
|
6
|
+
*/
|
|
7
|
+
const REFERENCE = /^asset:[0-9a-f]{64}$/;
|
|
8
|
+
export function isAssetReference(value) {
|
|
9
|
+
return typeof value === "string" && REFERENCE.test(value);
|
|
10
|
+
}
|
|
11
|
+
/** The content-addressed id of `bytes`. */
|
|
12
|
+
export async function assetIdOf(bytes) {
|
|
13
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes.slice().buffer);
|
|
14
|
+
let hex = "";
|
|
15
|
+
for (const byte of new Uint8Array(digest))
|
|
16
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
17
|
+
return `asset:${hex}`;
|
|
18
|
+
}
|
|
19
|
+
export class AssetStore {
|
|
20
|
+
#bytes = new Map();
|
|
21
|
+
#total = 0;
|
|
22
|
+
get(id) {
|
|
23
|
+
return this.#bytes.get(id);
|
|
24
|
+
}
|
|
25
|
+
has(id) {
|
|
26
|
+
return this.#bytes.has(id);
|
|
27
|
+
}
|
|
28
|
+
set(id, bytes) {
|
|
29
|
+
if (this.#bytes.has(id))
|
|
30
|
+
return;
|
|
31
|
+
this.#bytes.set(id, bytes);
|
|
32
|
+
this.#total += bytes.byteLength;
|
|
33
|
+
}
|
|
34
|
+
/** Bytes held, for limits. */
|
|
35
|
+
get byteLength() {
|
|
36
|
+
return this.#total;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Bytes of a `BinaryData` value that passed schema validation: inline bytes,
|
|
41
|
+
* a base64 string, or an asset reference the store knows.
|
|
42
|
+
*/
|
|
43
|
+
export function resolveBinary(data, assets) {
|
|
44
|
+
if (data instanceof Uint8Array)
|
|
45
|
+
return data;
|
|
46
|
+
if (isAssetReference(data)) {
|
|
47
|
+
const bytes = assets.get(data);
|
|
48
|
+
if (!bytes)
|
|
49
|
+
throw new ViewerError("invalid-operation", `Unknown asset ${data}`, {
|
|
50
|
+
details: {
|
|
51
|
+
issues: [
|
|
52
|
+
{
|
|
53
|
+
operationIndex: -1,
|
|
54
|
+
path: "",
|
|
55
|
+
code: "unknown-asset",
|
|
56
|
+
message: `Unknown asset ${data}`,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
return bytes;
|
|
62
|
+
}
|
|
63
|
+
return Uint8Array.from(atob(data), (character) => character.charCodeAt(0));
|
|
64
|
+
}
|
|
65
|
+
/** Names of an operation's top-level properties its schema marks as binary. */
|
|
66
|
+
export function binaryFields(schema) {
|
|
67
|
+
const properties = schema?.properties;
|
|
68
|
+
if (!properties || typeof properties !== "object")
|
|
69
|
+
return [];
|
|
70
|
+
return Object.entries(properties)
|
|
71
|
+
.filter(([, property]) => typeof property === "object" &&
|
|
72
|
+
property !== null &&
|
|
73
|
+
property["x-binary"] === true)
|
|
74
|
+
.map(([name]) => name);
|
|
75
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PageRect } from "../types.js";
|
|
2
|
+
import { type AnyRecord, type DocxModel } from "./model.js";
|
|
3
|
+
import type { DocxElement } from "./types.js";
|
|
4
|
+
export declare const NO_PAGE = -1;
|
|
5
|
+
export declare const EMPTY_RECT: PageRect;
|
|
6
|
+
export declare function toElement(model: DocxModel, record: AnyRecord): DocxElement;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { tableRows, tableText, } from "./model.js";
|
|
2
|
+
import { IMPLEMENTED_OPERATIONS } from "./schemas.js";
|
|
3
|
+
import { resolveParagraphStyle, resolveTextStyle } from "./style.js";
|
|
4
|
+
import { firstTextItem } from "./text.js";
|
|
5
|
+
/*
|
|
6
|
+
* `DocxElement` values from the model's records, without geometry: the
|
|
7
|
+
* engine never lays out, so `pageIndex` is −1 and `bounds` empty until
|
|
8
|
+
* the session joins the renderer's runs on the main thread.
|
|
9
|
+
*/
|
|
10
|
+
export const NO_PAGE = -1;
|
|
11
|
+
export const EMPTY_RECT = { x: 0, y: 0, width: 0, height: 0 };
|
|
12
|
+
/** The operations each kind accepts once its handler ships. */
|
|
13
|
+
const OPERATIONS = {
|
|
14
|
+
paragraph: [
|
|
15
|
+
"replaceText",
|
|
16
|
+
"setTextStyle",
|
|
17
|
+
"setParagraphStyle",
|
|
18
|
+
"insertParagraph",
|
|
19
|
+
"insertTable",
|
|
20
|
+
"insertImage",
|
|
21
|
+
"moveElement",
|
|
22
|
+
"deleteElement",
|
|
23
|
+
],
|
|
24
|
+
table: [
|
|
25
|
+
"setTableCell",
|
|
26
|
+
"insertParagraph",
|
|
27
|
+
"insertTable",
|
|
28
|
+
"insertImage",
|
|
29
|
+
"moveElement",
|
|
30
|
+
"deleteElement",
|
|
31
|
+
],
|
|
32
|
+
image: ["deleteElement"],
|
|
33
|
+
other: [],
|
|
34
|
+
};
|
|
35
|
+
/** A read-only paragraph only accepts siblings placed next to it. */
|
|
36
|
+
const INSERTIONS = ["insertParagraph", "insertTable", "insertImage"];
|
|
37
|
+
function operationsOf(kind, readOnly) {
|
|
38
|
+
return OPERATIONS[kind].filter((name) => IMPLEMENTED_OPERATIONS.includes(name) &&
|
|
39
|
+
(!readOnly || INSERTIONS.includes(name)));
|
|
40
|
+
}
|
|
41
|
+
export function toElement(model, record) {
|
|
42
|
+
const base = {
|
|
43
|
+
pageIndex: NO_PAGE,
|
|
44
|
+
bounds: EMPTY_RECT,
|
|
45
|
+
fragments: [],
|
|
46
|
+
story: { kind: "body" },
|
|
47
|
+
};
|
|
48
|
+
if (record.kind === "paragraph")
|
|
49
|
+
return paragraphElement(model, record, base);
|
|
50
|
+
if (record.kind === "table")
|
|
51
|
+
return {
|
|
52
|
+
...base,
|
|
53
|
+
id: record.elementId,
|
|
54
|
+
kind: "table",
|
|
55
|
+
text: tableText(record),
|
|
56
|
+
table: { rows: tableRows(record) },
|
|
57
|
+
operations: operationsOf("table", false),
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
...base,
|
|
61
|
+
id: record.elementId,
|
|
62
|
+
kind: record.kind,
|
|
63
|
+
parentId: record.paragraph.elementId,
|
|
64
|
+
...(record.kind === "image" && record.extent
|
|
65
|
+
? {
|
|
66
|
+
imageSize: {
|
|
67
|
+
width: record.extent.cx / EMU_PER_PX,
|
|
68
|
+
height: record.extent.cy / EMU_PER_PX,
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
: {}),
|
|
72
|
+
operations: operationsOf(record.kind, false),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** EMU per CSS pixel at 96 dpi. */
|
|
76
|
+
const EMU_PER_PX = 9525;
|
|
77
|
+
function paragraphElement(model, record, base) {
|
|
78
|
+
const first = firstTextItem(record.text);
|
|
79
|
+
return {
|
|
80
|
+
...base,
|
|
81
|
+
id: record.elementId,
|
|
82
|
+
kind: "paragraph",
|
|
83
|
+
text: record.text.text,
|
|
84
|
+
...(record.table ? { parentId: record.table.elementId } : {}),
|
|
85
|
+
textStyle: resolveTextStyle(model.styles, record.pPr, first?.rPr),
|
|
86
|
+
paragraphStyle: resolveParagraphStyle(model.styles, record.pPr),
|
|
87
|
+
...(record.readOnlyReason ? { readOnlyReason: record.readOnlyReason } : {}),
|
|
88
|
+
operations: operationsOf("paragraph", record.readOnlyReason !== undefined),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ResourceLimits } from "../../contracts.js";
|
|
2
|
+
import type { EditEngine, EngineBatch, EngineChange, MaterializedDocument, MaterializeOptions, RestoreTarget } from "../engine.js";
|
|
3
|
+
import { OoxmlPackage } from "../ooxml/package.js";
|
|
4
|
+
import type { EditFindOptions, EditOperation, ElementQuery, OperationIssue, PagePoint, TextTarget } from "../types.js";
|
|
5
|
+
import { DocxModel, type AnyRecord } from "./model.js";
|
|
6
|
+
import type { DocxElement } from "./types.js";
|
|
7
|
+
export declare class DocxEditEngine implements EditEngine {
|
|
8
|
+
#private;
|
|
9
|
+
readonly schemas: import("../types.js").OperationSchemaSet;
|
|
10
|
+
private constructor();
|
|
11
|
+
/** Opens the package and reads the block index, so a broken document fails here. */
|
|
12
|
+
static open(bytes: Uint8Array, limits: ResourceLimits, signal?: AbortSignal): Promise<DocxEditEngine>;
|
|
13
|
+
/** The package behind the engine, for tests and the operations. */
|
|
14
|
+
get package(): OoxmlPackage;
|
|
15
|
+
/** The engine has no pages of its own; the renderer counts them. */
|
|
16
|
+
get pageCount(): number;
|
|
17
|
+
/** The block index at the current revision. */
|
|
18
|
+
model(signal?: AbortSignal): Promise<DocxModel>;
|
|
19
|
+
validate(operations: readonly EditOperation[], signal: AbortSignal): Promise<readonly OperationIssue[]>;
|
|
20
|
+
/** Plain operation arrays, as the unit tests pass them, become the next batch. */
|
|
21
|
+
apply(input: EngineBatch | readonly EditOperation[], signal: AbortSignal): Promise<EngineChange>;
|
|
22
|
+
materialize(purposeOrSignal?: "show" | "save" | AbortSignal, options?: MaterializeOptions, signal?: AbortSignal): Promise<Uint8Array>;
|
|
23
|
+
/**
|
|
24
|
+
* `save` is the package as the session changed it: ids written only on
|
|
25
|
+
* the paragraphs the session rebuilt or created. `show` also stamps every
|
|
26
|
+
* other paragraph with the id the engine knows it by, in a copy, so the
|
|
27
|
+
* display pre-pass and the viewer's runs name the engine's paragraphs
|
|
28
|
+
* after edits that moved paragraphs around. Without changes both are
|
|
29
|
+
* the original bytes.
|
|
30
|
+
*/
|
|
31
|
+
materializeDocument(purposeOrSignal?: "show" | "save" | AbortSignal, _options?: MaterializeOptions, signal?: AbortSignal): Promise<MaterializedDocument>;
|
|
32
|
+
restore(input: RestoreTarget | readonly (readonly EditOperation[])[], signal: AbortSignal): Promise<void>;
|
|
33
|
+
putAsset(id: string, data: Uint8Array, _signal: AbortSignal): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Every element of the body story, in document order, without geometry.
|
|
36
|
+
* `pageIndex` and `intersects` are the session's to apply once the runs
|
|
37
|
+
* are joined; `kinds` filters here.
|
|
38
|
+
*/
|
|
39
|
+
getElements(query: ElementQuery, signal: AbortSignal): Promise<readonly DocxElement[]>;
|
|
40
|
+
getElement(id: string, signal: AbortSignal): Promise<DocxElement | undefined>;
|
|
41
|
+
/** The record an element id names, at the current revision. */
|
|
42
|
+
locate(id: string, signal?: AbortSignal): Promise<AnyRecord | undefined>;
|
|
43
|
+
/** Hit-testing needs the renderer's geometry; the session answers it. */
|
|
44
|
+
elementsAt(_pageIndex: number, _point: PagePoint, _signal: AbortSignal): Promise<readonly DocxElement[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Matches in paragraph text, in document order, without rectangles or
|
|
47
|
+
* pages: the session adds those from the renderer's runs.
|
|
48
|
+
*/
|
|
49
|
+
findText(query: string, options: EditFindOptions, signal: AbortSignal): Promise<readonly TextTarget[]>;
|
|
50
|
+
dispose(): Promise<void>;
|
|
51
|
+
}
|