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
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The text model of a text body: paragraphs joined by "\n", line breaks
|
|
3
|
+
* (a:br) as "\v" — the character PowerPoint itself uses — and fields as
|
|
4
|
+
* their cached text. Offsets are UTF-16 code units of the joined text.
|
|
5
|
+
*/
|
|
6
|
+
export const PARAGRAPH_BREAK = "\n";
|
|
7
|
+
export const LINE_BREAK = "\u000b";
|
|
8
|
+
/** Reads the text of a `p:txBody` or `a:txBody`. */
|
|
9
|
+
export function readTextModel(part, txBody) {
|
|
10
|
+
const paragraphs = [];
|
|
11
|
+
let text = "";
|
|
12
|
+
const paragraphNodes = txBody.children.filter((child) => child.local === "p");
|
|
13
|
+
paragraphNodes.forEach((paragraph, index) => {
|
|
14
|
+
if (index > 0)
|
|
15
|
+
text += PARAGRAPH_BREAK;
|
|
16
|
+
const start = text.length;
|
|
17
|
+
const runs = [];
|
|
18
|
+
let pPr;
|
|
19
|
+
let endParaRPr;
|
|
20
|
+
for (const child of paragraph.children) {
|
|
21
|
+
switch (child.local) {
|
|
22
|
+
case "pPr":
|
|
23
|
+
pPr = child;
|
|
24
|
+
break;
|
|
25
|
+
case "endParaRPr":
|
|
26
|
+
endParaRPr = child;
|
|
27
|
+
break;
|
|
28
|
+
case "r":
|
|
29
|
+
case "fld": {
|
|
30
|
+
const t = child.children.find((node) => node.local === "t");
|
|
31
|
+
const value = t ? part.textOf(t) : "";
|
|
32
|
+
const rPr = child.children.find((node) => node.local === "rPr");
|
|
33
|
+
runs.push({
|
|
34
|
+
node: child,
|
|
35
|
+
kind: child.local,
|
|
36
|
+
start: text.length,
|
|
37
|
+
end: text.length + value.length,
|
|
38
|
+
text: value,
|
|
39
|
+
...(rPr ? { rPr } : {}),
|
|
40
|
+
});
|
|
41
|
+
text += value;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
case "br": {
|
|
45
|
+
const rPr = child.children.find((node) => node.local === "rPr");
|
|
46
|
+
runs.push({
|
|
47
|
+
node: child,
|
|
48
|
+
kind: "br",
|
|
49
|
+
start: text.length,
|
|
50
|
+
end: text.length + 1,
|
|
51
|
+
text: LINE_BREAK,
|
|
52
|
+
...(rPr ? { rPr } : {}),
|
|
53
|
+
});
|
|
54
|
+
text += LINE_BREAK;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
default:
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const level = pPr ? Number(part.attribute(pPr, "lvl") ?? "0") || 0 : 0;
|
|
62
|
+
paragraphs.push({
|
|
63
|
+
node: paragraph,
|
|
64
|
+
start,
|
|
65
|
+
end: text.length,
|
|
66
|
+
runs,
|
|
67
|
+
...(pPr ? { pPr } : {}),
|
|
68
|
+
level,
|
|
69
|
+
...(endParaRPr ? { endParaRPr } : {}),
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
return { text, paragraphs };
|
|
73
|
+
}
|
|
74
|
+
/** The first run that draws text, with its paragraph; undefined for an empty body. */
|
|
75
|
+
export function firstTextRun(model) {
|
|
76
|
+
for (const paragraph of model.paragraphs)
|
|
77
|
+
for (const run of paragraph.runs)
|
|
78
|
+
if (run.kind !== "br" && run.text.length > 0)
|
|
79
|
+
return { run, paragraph };
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
/** The cells of an `a:tbl`, row by row, as text models. */
|
|
83
|
+
export function readTableCells(part, tbl) {
|
|
84
|
+
const rows = [];
|
|
85
|
+
for (const tr of tbl.children.filter((child) => child.local === "tr")) {
|
|
86
|
+
const cells = [];
|
|
87
|
+
for (const tc of tr.children.filter((child) => child.local === "tc")) {
|
|
88
|
+
const txBody = tc.children.find((child) => child.local === "txBody");
|
|
89
|
+
cells.push({
|
|
90
|
+
node: tc,
|
|
91
|
+
model: txBody
|
|
92
|
+
? readTextModel(part, txBody)
|
|
93
|
+
: { text: "", paragraphs: [] },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
rows.push(cells);
|
|
97
|
+
}
|
|
98
|
+
return rows;
|
|
99
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import type { ApplyOptions, BinaryData, EditColor, EditElement, EditReceipt, EditSessionBase, PagePoint, PageRect, ReadOptions, ReadResult, SavedDocument, SaveOptions, TextRange } from "../types.js";
|
|
2
|
+
export type PptxElementKind = "shape" | "image" | "table" | "connector" | "group" | "other";
|
|
3
|
+
export type PptxTextAlign = "left" | "center" | "right" | "justify";
|
|
4
|
+
/** The style of the first run with text, resolved through the placeholder chain and the theme. */
|
|
5
|
+
export interface PptxTextStyle {
|
|
6
|
+
/** Theme fonts resolved: "+mn-lt" becomes the minor Latin face. */
|
|
7
|
+
readonly fontFamily: string;
|
|
8
|
+
/** Points. */
|
|
9
|
+
readonly fontSize: number;
|
|
10
|
+
readonly bold: boolean;
|
|
11
|
+
readonly italic: boolean;
|
|
12
|
+
readonly underline: boolean;
|
|
13
|
+
/** `#RRGGBB`, `{ theme, mods }` when the file uses a scheme colour, "auto" when unresolvable. */
|
|
14
|
+
readonly color: EditColor;
|
|
15
|
+
readonly align: PptxTextAlign;
|
|
16
|
+
}
|
|
17
|
+
export interface PptxLineStyle {
|
|
18
|
+
readonly color: EditColor | "none";
|
|
19
|
+
/** Points. */
|
|
20
|
+
readonly width: number;
|
|
21
|
+
}
|
|
22
|
+
export interface PptxShapeStyle {
|
|
23
|
+
/** "none" for a:noFill; absent when inherited from the shape style or the theme. */
|
|
24
|
+
readonly fill?: EditColor | "none";
|
|
25
|
+
readonly line?: PptxLineStyle;
|
|
26
|
+
}
|
|
27
|
+
export interface PptxPlaceholder {
|
|
28
|
+
/** `p:ph/@type`, "body" when the file leaves it out. */
|
|
29
|
+
readonly type: string;
|
|
30
|
+
readonly idx?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface PptxElement extends EditElement {
|
|
33
|
+
readonly kind: PptxElementKind;
|
|
34
|
+
/** `p:cNvPr/@name`, as PowerPoint shows it in the selection pane. */
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly placeholder?: PptxPlaceholder;
|
|
37
|
+
/** Present for a shape with a text body. */
|
|
38
|
+
readonly textStyle?: PptxTextStyle;
|
|
39
|
+
/** Present for shapes and connectors. */
|
|
40
|
+
readonly shapeStyle?: PptxShapeStyle;
|
|
41
|
+
/** Present for a table. */
|
|
42
|
+
readonly table?: {
|
|
43
|
+
readonly rows: readonly (readonly string[])[];
|
|
44
|
+
};
|
|
45
|
+
/** `p:cNvPr/@hidden`: listed and editable, but not drawn by the renderer. */
|
|
46
|
+
readonly hidden?: boolean;
|
|
47
|
+
}
|
|
48
|
+
export interface PptxSlideInfo {
|
|
49
|
+
readonly pageIndex: number;
|
|
50
|
+
/** Stable for the session: the slide part's number, as in element ids ("sld3"). */
|
|
51
|
+
readonly key: string;
|
|
52
|
+
/** A `PptxLayoutInfo` id. */
|
|
53
|
+
readonly layout: string;
|
|
54
|
+
readonly hidden: boolean;
|
|
55
|
+
}
|
|
56
|
+
export interface PptxLayoutInfo {
|
|
57
|
+
/** "layout2": from the layout part's number. */
|
|
58
|
+
readonly id: string;
|
|
59
|
+
/** `p:cSld/@name`, for example "Title and Content". */
|
|
60
|
+
readonly name: string;
|
|
61
|
+
/** `p:sldLayout/@type` when present. */
|
|
62
|
+
readonly type?: string;
|
|
63
|
+
/** "master1": from the master part's number. */
|
|
64
|
+
readonly master: string;
|
|
65
|
+
}
|
|
66
|
+
export interface PptxTextStyleChange {
|
|
67
|
+
/** Written as a:latin/@typeface; theme names ("+mn-lt") pass through. */
|
|
68
|
+
readonly fontFamily?: string;
|
|
69
|
+
/** 1–400 points. */
|
|
70
|
+
readonly fontSize?: number;
|
|
71
|
+
readonly bold?: boolean;
|
|
72
|
+
readonly italic?: boolean;
|
|
73
|
+
readonly underline?: boolean;
|
|
74
|
+
/** `#RRGGBB` writes a:srgbClr; `{ theme, mods }` writes a:schemeClr with modifiers. */
|
|
75
|
+
readonly color?: EditColor;
|
|
76
|
+
/** a:pPr/@algn on every paragraph the range touches. */
|
|
77
|
+
readonly align?: PptxTextAlign;
|
|
78
|
+
}
|
|
79
|
+
export interface PptxTableStyle {
|
|
80
|
+
/** Header-row flag of the table; default true. */
|
|
81
|
+
readonly firstRow?: boolean;
|
|
82
|
+
/** Banded-rows flag of the table; default true. */
|
|
83
|
+
readonly bandRow?: boolean;
|
|
84
|
+
}
|
|
85
|
+
export interface PptxLineChange {
|
|
86
|
+
readonly color: EditColor | "none";
|
|
87
|
+
/** Points; kept when absent. */
|
|
88
|
+
readonly width?: number;
|
|
89
|
+
}
|
|
90
|
+
export interface PptxReplaceTextOperation {
|
|
91
|
+
readonly op: "replaceText";
|
|
92
|
+
/** A `shape` element with a text body. */
|
|
93
|
+
readonly target: string;
|
|
94
|
+
readonly text: string;
|
|
95
|
+
/** Both ends on the target; absent, the whole text; collapsed, an insertion. */
|
|
96
|
+
readonly range?: TextRange;
|
|
97
|
+
}
|
|
98
|
+
export interface PptxSetTextStyleOperation {
|
|
99
|
+
readonly op: "setTextStyle";
|
|
100
|
+
readonly target: string;
|
|
101
|
+
readonly range?: TextRange;
|
|
102
|
+
readonly style: PptxTextStyleChange;
|
|
103
|
+
}
|
|
104
|
+
export interface PptxSetShapeStyleOperation {
|
|
105
|
+
readonly op: "setShapeStyle";
|
|
106
|
+
/** A `shape` or `connector` element. */
|
|
107
|
+
readonly target: string;
|
|
108
|
+
/** A colour, "none" for no fill, `null` to drop the explicit fill, absent to keep it. */
|
|
109
|
+
readonly fill?: EditColor | "none" | null;
|
|
110
|
+
/** A line, "none" for no line, `null` to drop the explicit line, absent to keep it. */
|
|
111
|
+
readonly line?: PptxLineChange | "none" | null;
|
|
112
|
+
}
|
|
113
|
+
export interface PptxMoveElementOperation {
|
|
114
|
+
readonly op: "moveElement";
|
|
115
|
+
readonly target: string;
|
|
116
|
+
/** New top-left corner of the element's bounds, in slide space. */
|
|
117
|
+
readonly to?: PagePoint;
|
|
118
|
+
/** Offset in slide space. Exactly one of `to` and `by` is given. */
|
|
119
|
+
readonly by?: {
|
|
120
|
+
readonly dx: number;
|
|
121
|
+
readonly dy: number;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export interface PptxResizeElementOperation {
|
|
125
|
+
readonly op: "resizeElement";
|
|
126
|
+
readonly target: string;
|
|
127
|
+
/** New bounds in slide space; the rotation is kept. */
|
|
128
|
+
readonly rect: PageRect;
|
|
129
|
+
}
|
|
130
|
+
export interface PptxDeleteElementOperation {
|
|
131
|
+
readonly op: "deleteElement";
|
|
132
|
+
readonly target: string;
|
|
133
|
+
}
|
|
134
|
+
export interface PptxInsertTextBoxOperation {
|
|
135
|
+
readonly op: "insertTextBox";
|
|
136
|
+
readonly pageIndex: number;
|
|
137
|
+
readonly rect: PageRect;
|
|
138
|
+
readonly text: string;
|
|
139
|
+
readonly style?: PptxTextStyleChange;
|
|
140
|
+
}
|
|
141
|
+
export interface PptxInsertImageOperation {
|
|
142
|
+
readonly op: "insertImage";
|
|
143
|
+
readonly pageIndex: number;
|
|
144
|
+
readonly rect: PageRect;
|
|
145
|
+
/** PNG or JPEG bytes; base64 over pure-JSON transports. */
|
|
146
|
+
readonly data: BinaryData;
|
|
147
|
+
readonly mimeType: "image/png" | "image/jpeg";
|
|
148
|
+
}
|
|
149
|
+
export interface PptxInsertTableOperation {
|
|
150
|
+
readonly op: "insertTable";
|
|
151
|
+
readonly pageIndex: number;
|
|
152
|
+
readonly rect: PageRect;
|
|
153
|
+
/** Cell text by row; every row has the same number of cells. 1–100 rows, 1–20 columns. */
|
|
154
|
+
readonly rows: readonly (readonly string[])[];
|
|
155
|
+
/** Relative column weights, one per column; equal when omitted. */
|
|
156
|
+
readonly columnWidths?: readonly number[];
|
|
157
|
+
readonly style?: PptxTableStyle;
|
|
158
|
+
}
|
|
159
|
+
export interface PptxSetTableCellOperation {
|
|
160
|
+
readonly op: "setTableCell";
|
|
161
|
+
/** A `table` element. */
|
|
162
|
+
readonly target: string;
|
|
163
|
+
readonly row: number;
|
|
164
|
+
readonly column: number;
|
|
165
|
+
/** The cell's new text; empty clears the cell. */
|
|
166
|
+
readonly text: string;
|
|
167
|
+
}
|
|
168
|
+
export interface PptxInsertSlideOperation {
|
|
169
|
+
readonly op: "insertSlide";
|
|
170
|
+
/** Position of the new slide, 0 to the current slide count. */
|
|
171
|
+
readonly index: number;
|
|
172
|
+
/** A layout id; default: the layout of the slide before the position, else the first layout. */
|
|
173
|
+
readonly layout?: string;
|
|
174
|
+
}
|
|
175
|
+
export interface PptxDuplicateSlideOperation {
|
|
176
|
+
readonly op: "duplicateSlide";
|
|
177
|
+
readonly pageIndex: number;
|
|
178
|
+
/** Position of the copy; default: right after the source. */
|
|
179
|
+
readonly index?: number;
|
|
180
|
+
}
|
|
181
|
+
export interface PptxDeleteSlideOperation {
|
|
182
|
+
readonly op: "deleteSlide";
|
|
183
|
+
readonly pageIndex: number;
|
|
184
|
+
}
|
|
185
|
+
export interface PptxMoveSlideOperation {
|
|
186
|
+
readonly op: "moveSlide";
|
|
187
|
+
readonly from: number;
|
|
188
|
+
/** The slide's index after the move. */
|
|
189
|
+
readonly to: number;
|
|
190
|
+
}
|
|
191
|
+
/** The PPTX operation union; operations are added as they ship. */
|
|
192
|
+
export type PptxOperation = PptxReplaceTextOperation | PptxSetTextStyleOperation | PptxSetShapeStyleOperation | PptxMoveElementOperation | PptxResizeElementOperation | PptxDeleteElementOperation | PptxInsertTextBoxOperation | PptxInsertImageOperation | PptxInsertTableOperation | PptxSetTableCellOperation | PptxInsertSlideOperation | PptxDuplicateSlideOperation | PptxDeleteSlideOperation | PptxMoveSlideOperation;
|
|
193
|
+
/** PPTX saves have no fields of their own yet. */
|
|
194
|
+
export type PptxSaveOptions = SaveOptions;
|
|
195
|
+
/** An operation's fields without its `op`, as the typed methods take them. */
|
|
196
|
+
export type PptxFields<T extends PptxOperation> = T extends unknown ? Omit<T, "op"> : never;
|
|
197
|
+
export interface PptxEditSession extends EditSessionBase<PptxOperation, PptxElement> {
|
|
198
|
+
readonly format: "pptx";
|
|
199
|
+
save(options?: PptxSaveOptions): Promise<SavedDocument>;
|
|
200
|
+
/** The slides in presentation order, with keys that survive reordering. */
|
|
201
|
+
getSlides(options?: ReadOptions): Promise<ReadResult<PptxSlideInfo>>;
|
|
202
|
+
/** Every layout of every master, for `insertSlide`. */
|
|
203
|
+
getLayouts(options?: ReadOptions): Promise<ReadResult<PptxLayoutInfo>>;
|
|
204
|
+
/** Replaces the whole text of a shape, or the part a range covers. */
|
|
205
|
+
replaceText(fields: PptxFields<PptxReplaceTextOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
206
|
+
/** Changes run and paragraph properties; unspecified ones keep their bytes. */
|
|
207
|
+
setTextStyle(fields: PptxFields<PptxSetTextStyleOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
208
|
+
/** Changes or removes a shape's fill and line. */
|
|
209
|
+
setShapeStyle(fields: PptxFields<PptxSetShapeStyleOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
210
|
+
/** Moves an element to a point or by an offset, in slide space. */
|
|
211
|
+
moveElement(fields: PptxFields<PptxMoveElementOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
212
|
+
/** Gives an element new bounds; its rotation is kept. */
|
|
213
|
+
resizeElement(fields: PptxFields<PptxResizeElementOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
214
|
+
/** Removes an element and, for a group, every element in it. */
|
|
215
|
+
deleteElement(fields: PptxFields<PptxDeleteElementOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
216
|
+
/** Adds a text box; its id is in `createdIds`. */
|
|
217
|
+
insertTextBox(fields: PptxFields<PptxInsertTextBoxOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
218
|
+
/** Places a PNG or JPEG inside `rect` as a picture. */
|
|
219
|
+
insertImage(fields: PptxFields<PptxInsertImageOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
220
|
+
/** Adds a table with the given cell text. */
|
|
221
|
+
insertTable(fields: PptxFields<PptxInsertTableOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
222
|
+
/** Changes one cell's text. */
|
|
223
|
+
setTableCell(fields: PptxFields<PptxSetTableCellOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
224
|
+
/** Adds a slide from a layout. */
|
|
225
|
+
insertSlide(fields: PptxFields<PptxInsertSlideOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
226
|
+
/** Copies a slide. */
|
|
227
|
+
duplicateSlide(fields: PptxFields<PptxDuplicateSlideOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
228
|
+
/** Removes a slide; the last slide cannot be removed. */
|
|
229
|
+
deleteSlide(fields: PptxFields<PptxDeleteSlideOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
230
|
+
/** Reorders slides. */
|
|
231
|
+
moveSlide(fields: PptxFields<PptxMoveSlideOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
232
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { JsonSchema, OperationIssue } from "./types.js";
|
|
2
|
+
/** Collects every issue of `value` against `schema`; an empty result means valid. */
|
|
3
|
+
export declare function validateSchema(value: unknown, schema: JsonSchema, operationIndex: number): OperationIssue[];
|
|
4
|
+
/**
|
|
5
|
+
* Throws when a schema uses a keyword the validator does not implement, so a
|
|
6
|
+
* constraint can never be silently ignored. Run by the tests over every
|
|
7
|
+
* operation schema.
|
|
8
|
+
*/
|
|
9
|
+
export declare function assertSupportedSchema(schema: JsonSchema, path?: string): void;
|
|
10
|
+
/** JSON Pointer segment escaping (RFC 6901). */
|
|
11
|
+
export declare function pointerSegment(segment: string | number): string;
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* A validator for the subset of JSON Schema 2020-12 that operation schemas
|
|
3
|
+
* use. The same schemas are exported to clients, so they must stay valid JSON
|
|
4
|
+
* Schema; the only extension is `x-binary`, which marks a value that may be a
|
|
5
|
+
* `Uint8Array` in-process or a base64 string in JSON.
|
|
6
|
+
*/
|
|
7
|
+
const SUPPORTED_KEYWORDS = new Set([
|
|
8
|
+
"$schema",
|
|
9
|
+
"$id",
|
|
10
|
+
"$defs",
|
|
11
|
+
"$ref",
|
|
12
|
+
"title",
|
|
13
|
+
"description",
|
|
14
|
+
"default",
|
|
15
|
+
"examples",
|
|
16
|
+
"type",
|
|
17
|
+
"properties",
|
|
18
|
+
"required",
|
|
19
|
+
"additionalProperties",
|
|
20
|
+
"enum",
|
|
21
|
+
"const",
|
|
22
|
+
"minimum",
|
|
23
|
+
"maximum",
|
|
24
|
+
"exclusiveMinimum",
|
|
25
|
+
"exclusiveMaximum",
|
|
26
|
+
"minLength",
|
|
27
|
+
"maxLength",
|
|
28
|
+
"pattern",
|
|
29
|
+
"items",
|
|
30
|
+
"minItems",
|
|
31
|
+
"maxItems",
|
|
32
|
+
"oneOf",
|
|
33
|
+
"contentEncoding",
|
|
34
|
+
"x-binary",
|
|
35
|
+
]);
|
|
36
|
+
const BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
37
|
+
/** A content-addressed reference to data registered with `addAsset()`. */
|
|
38
|
+
const ASSET_REFERENCE = /^asset:[0-9a-f]{64}$/;
|
|
39
|
+
/** Collects every issue of `value` against `schema`; an empty result means valid. */
|
|
40
|
+
export function validateSchema(value, schema, operationIndex) {
|
|
41
|
+
const issues = [];
|
|
42
|
+
visit(value, schema, schema, "", operationIndex, issues);
|
|
43
|
+
return issues;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Throws when a schema uses a keyword the validator does not implement, so a
|
|
47
|
+
* constraint can never be silently ignored. Run by the tests over every
|
|
48
|
+
* operation schema.
|
|
49
|
+
*/
|
|
50
|
+
export function assertSupportedSchema(schema, path = "#") {
|
|
51
|
+
for (const [keyword, child] of Object.entries(schema)) {
|
|
52
|
+
if (!SUPPORTED_KEYWORDS.has(keyword))
|
|
53
|
+
throw new Error(`Unsupported JSON Schema keyword ${keyword} at ${path}`);
|
|
54
|
+
if (keyword === "properties" || keyword === "$defs")
|
|
55
|
+
for (const [name, nested] of Object.entries(asRecord(child)))
|
|
56
|
+
assertSupportedSchema(asRecord(nested), `${path}/${keyword}/${name}`);
|
|
57
|
+
else if (keyword === "items" || keyword === "additionalProperties") {
|
|
58
|
+
if (isRecord(child))
|
|
59
|
+
assertSupportedSchema(child, `${path}/${keyword}`);
|
|
60
|
+
}
|
|
61
|
+
else if (keyword === "oneOf")
|
|
62
|
+
asArray(child).forEach((nested, index) => assertSupportedSchema(asRecord(nested), `${path}/oneOf/${index}`));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** JSON Pointer segment escaping (RFC 6901). */
|
|
66
|
+
export function pointerSegment(segment) {
|
|
67
|
+
return `/${String(segment).replaceAll("~", "~0").replaceAll("/", "~1")}`;
|
|
68
|
+
}
|
|
69
|
+
function visit(value, schema, root, path, operationIndex, issues) {
|
|
70
|
+
const issue = (code, message) => {
|
|
71
|
+
issues.push({ operationIndex, path, code, message });
|
|
72
|
+
};
|
|
73
|
+
if (typeof schema.$ref === "string") {
|
|
74
|
+
visit(value, resolveRef(schema.$ref, root), root, path, operationIndex, issues);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (schema["x-binary"] === true) {
|
|
78
|
+
if (value instanceof Uint8Array)
|
|
79
|
+
return;
|
|
80
|
+
const base64 = typeof value === "string" && value.length % 4 === 0 && BASE64.test(value);
|
|
81
|
+
if (typeof value !== "string" || (!base64 && !ASSET_REFERENCE.test(value)))
|
|
82
|
+
issue("binary", "Expected bytes, a base64 string or an asset reference");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (Array.isArray(schema.oneOf)) {
|
|
86
|
+
visitOneOf(value, schema.oneOf, root, path, operationIndex, issues);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (schema.const !== undefined && !sameJson(value, schema.const)) {
|
|
90
|
+
issue("const", `Expected ${JSON.stringify(schema.const)}`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (Array.isArray(schema.enum) &&
|
|
94
|
+
!schema.enum.some((candidate) => sameJson(value, candidate))) {
|
|
95
|
+
issue("enum", `Expected one of ${schema.enum.map((candidate) => JSON.stringify(candidate)).join(", ")}`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const types = schemaTypes(schema.type);
|
|
99
|
+
if (types && !types.some((type) => hasType(value, type))) {
|
|
100
|
+
issue("type", `Expected ${types.join(" or ")}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (typeof value === "number")
|
|
104
|
+
checkNumber(value, schema, issue);
|
|
105
|
+
else if (typeof value === "string")
|
|
106
|
+
checkString(value, schema, issue);
|
|
107
|
+
else if (Array.isArray(value))
|
|
108
|
+
checkArray(value, schema, root, path, operationIndex, issues, issue);
|
|
109
|
+
else if (isRecord(value))
|
|
110
|
+
checkObject(value, schema, root, path, operationIndex, issues);
|
|
111
|
+
}
|
|
112
|
+
function visitOneOf(value, branches, root, path, operationIndex, issues) {
|
|
113
|
+
const results = branches.map((branch) => collect(value, branch, root, path, operationIndex));
|
|
114
|
+
const matching = results.filter((result) => result.length === 0).length;
|
|
115
|
+
if (matching === 1)
|
|
116
|
+
return;
|
|
117
|
+
if (matching > 1) {
|
|
118
|
+
issues.push({
|
|
119
|
+
operationIndex,
|
|
120
|
+
path,
|
|
121
|
+
code: "one-of",
|
|
122
|
+
message: "Matches more than one allowed shape",
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// A branch whose discriminating constants all match explains the failure
|
|
127
|
+
// better than a generic message, for example a known `op` with a bad field.
|
|
128
|
+
const discriminated = branches.findIndex((branch) => discriminatorMatches(value, branch, root));
|
|
129
|
+
if (discriminated >= 0) {
|
|
130
|
+
issues.push(...results[discriminated]);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
issues.push({
|
|
134
|
+
operationIndex,
|
|
135
|
+
path,
|
|
136
|
+
code: "one-of",
|
|
137
|
+
message: "Does not match any allowed shape",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function collect(value, schema, root, path, operationIndex) {
|
|
141
|
+
const issues = [];
|
|
142
|
+
visit(value, schema, root, path, operationIndex, issues);
|
|
143
|
+
return issues;
|
|
144
|
+
}
|
|
145
|
+
function discriminatorMatches(value, branch, root) {
|
|
146
|
+
const schema = typeof branch.$ref === "string" ? resolveRef(branch.$ref, root) : branch;
|
|
147
|
+
if (!isRecord(value) || !isRecord(schema.properties))
|
|
148
|
+
return false;
|
|
149
|
+
const constants = Object.entries(schema.properties).filter(([, property]) => isRecord(property) && property.const !== undefined);
|
|
150
|
+
return (constants.length > 0 &&
|
|
151
|
+
constants.every(([name, property]) => sameJson(value[name], property.const)));
|
|
152
|
+
}
|
|
153
|
+
function checkNumber(value, schema, issue) {
|
|
154
|
+
if (typeof schema.minimum === "number" && value < schema.minimum)
|
|
155
|
+
issue("minimum", `Must be at least ${schema.minimum}`);
|
|
156
|
+
if (typeof schema.maximum === "number" && value > schema.maximum)
|
|
157
|
+
issue("maximum", `Must be at most ${schema.maximum}`);
|
|
158
|
+
if (typeof schema.exclusiveMinimum === "number" &&
|
|
159
|
+
value <= schema.exclusiveMinimum)
|
|
160
|
+
issue("minimum", `Must be greater than ${schema.exclusiveMinimum}`);
|
|
161
|
+
if (typeof schema.exclusiveMaximum === "number" &&
|
|
162
|
+
value >= schema.exclusiveMaximum)
|
|
163
|
+
issue("maximum", `Must be less than ${schema.exclusiveMaximum}`);
|
|
164
|
+
}
|
|
165
|
+
function checkString(value, schema, issue) {
|
|
166
|
+
// Lengths count code points, as JSON Schema specifies.
|
|
167
|
+
const length = [...value].length;
|
|
168
|
+
if (typeof schema.minLength === "number" && length < schema.minLength)
|
|
169
|
+
issue("min-length", `Must have at least ${schema.minLength} characters`);
|
|
170
|
+
if (typeof schema.maxLength === "number" && length > schema.maxLength)
|
|
171
|
+
issue("max-length", `Must have at most ${schema.maxLength} characters`);
|
|
172
|
+
if (typeof schema.pattern === "string" &&
|
|
173
|
+
!new RegExp(schema.pattern, "u").test(value))
|
|
174
|
+
issue("pattern", `Must match ${schema.pattern}`);
|
|
175
|
+
}
|
|
176
|
+
function checkArray(value, schema, root, path, operationIndex, issues, issue) {
|
|
177
|
+
if (typeof schema.minItems === "number" && value.length < schema.minItems)
|
|
178
|
+
issue("min-items", `Must have at least ${schema.minItems} items`);
|
|
179
|
+
if (typeof schema.maxItems === "number" && value.length > schema.maxItems)
|
|
180
|
+
issue("max-items", `Must have at most ${schema.maxItems} items`);
|
|
181
|
+
if (isRecord(schema.items))
|
|
182
|
+
value.forEach((item, index) => visit(item, schema.items, root, `${path}${pointerSegment(index)}`, operationIndex, issues));
|
|
183
|
+
}
|
|
184
|
+
function checkObject(value, schema, root, path, operationIndex, issues) {
|
|
185
|
+
const properties = isRecord(schema.properties) ? schema.properties : {};
|
|
186
|
+
// An explicit `undefined` counts as absent, as it does in JSON.
|
|
187
|
+
for (const name of asArray(schema.required))
|
|
188
|
+
if (typeof name === "string" && value[name] === undefined)
|
|
189
|
+
issues.push({
|
|
190
|
+
operationIndex,
|
|
191
|
+
path: `${path}${pointerSegment(name)}`,
|
|
192
|
+
code: "required",
|
|
193
|
+
message: "Required",
|
|
194
|
+
});
|
|
195
|
+
for (const [name, child] of Object.entries(value)) {
|
|
196
|
+
if (child === undefined)
|
|
197
|
+
continue;
|
|
198
|
+
const childPath = `${path}${pointerSegment(name)}`;
|
|
199
|
+
const property = properties[name];
|
|
200
|
+
if (isRecord(property)) {
|
|
201
|
+
visit(child, property, root, childPath, operationIndex, issues);
|
|
202
|
+
}
|
|
203
|
+
else if (schema.additionalProperties === false) {
|
|
204
|
+
issues.push({
|
|
205
|
+
operationIndex,
|
|
206
|
+
path: childPath,
|
|
207
|
+
code: "additional-property",
|
|
208
|
+
message: "Unknown property",
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
else if (isRecord(schema.additionalProperties)) {
|
|
212
|
+
visit(child, schema.additionalProperties, root, childPath, operationIndex, issues);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function resolveRef(reference, root) {
|
|
217
|
+
const prefix = "#/$defs/";
|
|
218
|
+
const definitions = isRecord(root.$defs) ? root.$defs : {};
|
|
219
|
+
const target = reference.startsWith(prefix)
|
|
220
|
+
? definitions[reference.slice(prefix.length)]
|
|
221
|
+
: undefined;
|
|
222
|
+
if (!isRecord(target))
|
|
223
|
+
throw new Error(`Unresolvable JSON Schema reference ${reference}`);
|
|
224
|
+
return target;
|
|
225
|
+
}
|
|
226
|
+
function schemaTypes(type) {
|
|
227
|
+
if (typeof type === "string")
|
|
228
|
+
return [type];
|
|
229
|
+
if (Array.isArray(type))
|
|
230
|
+
return type;
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
function hasType(value, type) {
|
|
234
|
+
switch (type) {
|
|
235
|
+
case "object":
|
|
236
|
+
return isRecord(value);
|
|
237
|
+
case "array":
|
|
238
|
+
return Array.isArray(value);
|
|
239
|
+
case "string":
|
|
240
|
+
return typeof value === "string";
|
|
241
|
+
case "number":
|
|
242
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
243
|
+
case "integer":
|
|
244
|
+
return Number.isInteger(value);
|
|
245
|
+
case "boolean":
|
|
246
|
+
return typeof value === "boolean";
|
|
247
|
+
case "null":
|
|
248
|
+
return value === null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function sameJson(left, right) {
|
|
252
|
+
if (left === right)
|
|
253
|
+
return true;
|
|
254
|
+
if (Array.isArray(left) && Array.isArray(right))
|
|
255
|
+
return (left.length === right.length &&
|
|
256
|
+
left.every((item, index) => sameJson(item, right[index])));
|
|
257
|
+
if (isRecord(left) && isRecord(right)) {
|
|
258
|
+
const leftKeys = Object.keys(left);
|
|
259
|
+
return (leftKeys.length === Object.keys(right).length &&
|
|
260
|
+
leftKeys.every((key) => sameJson(left[key], right[key])));
|
|
261
|
+
}
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
function isRecord(value) {
|
|
265
|
+
return (typeof value === "object" &&
|
|
266
|
+
value !== null &&
|
|
267
|
+
!Array.isArray(value) &&
|
|
268
|
+
!(value instanceof Uint8Array));
|
|
269
|
+
}
|
|
270
|
+
function asRecord(value) {
|
|
271
|
+
return isRecord(value) ? value : {};
|
|
272
|
+
}
|
|
273
|
+
function asArray(value) {
|
|
274
|
+
return Array.isArray(value) ? value : [];
|
|
275
|
+
}
|