web-doc 0.10.0 → 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/dist/adapters/office.js +4 -2
- package/dist/adapters/pdf.js +19 -8
- package/dist/edit/docx/engine.d.ts +9 -1
- package/dist/edit/docx/engine.js +22 -0
- package/dist/edit/docx/provider.d.ts +3 -1
- package/dist/edit/docx/provider.js +3 -0
- package/dist/edit/docx/session.d.ts +6 -2
- package/dist/edit/docx/session.js +7 -1
- package/dist/edit/docx/style.d.ts +1 -1
- package/dist/edit/docx/style.js +3 -2
- package/dist/edit/docx/types.d.ts +11 -1
- package/dist/edit/engine.d.ts +2 -1
- package/dist/edit/pdf/engine/document.d.ts +4 -2
- package/dist/edit/pdf/engine/document.js +115 -12
- package/dist/edit/pdf/engine/elements.d.ts +5 -1
- package/dist/edit/pdf/engine/elements.js +88 -6
- package/dist/edit/pdf/engine/existing-text.js +16 -61
- package/dist/edit/pdf/engine/fonts.d.ts +14 -0
- package/dist/edit/pdf/engine/fonts.js +114 -0
- package/dist/edit/pdf/engine/handler.js +2 -0
- package/dist/edit/pdf/engine/layout.js +1 -1
- package/dist/edit/pdf/engine/operations.d.ts +7 -1
- package/dist/edit/pdf/engine/paragraph-edit.d.ts +4 -0
- package/dist/edit/pdf/engine/paragraph-edit.js +331 -0
- package/dist/edit/pdf/engine/paragraph.d.ts +31 -0
- package/dist/edit/pdf/engine/paragraph.js +445 -0
- package/dist/edit/pdf/engine/pdfium.d.ts +3 -0
- package/dist/edit/pdf/engine/text-box.js +16 -3
- package/dist/edit/pdf/engine/text-font.js +2 -19
- package/dist/edit/pdf/engine/transform.js +14 -3
- package/dist/edit/pdf/provider.d.ts +3 -1
- package/dist/edit/pdf/provider.js +3 -0
- package/dist/edit/pdf/range-map.d.ts +2 -1
- package/dist/edit/pdf/range-map.js +38 -8
- package/dist/edit/pdf/schemas.d.ts +2 -0
- package/dist/edit/pdf/schemas.js +37 -0
- package/dist/edit/pdf/session.d.ts +3 -1
- package/dist/edit/pdf/session.js +35 -6
- package/dist/edit/pdf/types.d.ts +38 -8
- package/dist/edit/pptx/elements.d.ts +4 -1
- package/dist/edit/pptx/elements.js +35 -0
- package/dist/edit/pptx/engine.d.ts +5 -1
- package/dist/edit/pptx/engine.js +11 -0
- package/dist/edit/pptx/handler.js +8 -0
- package/dist/edit/pptx/provider.d.ts +3 -1
- package/dist/edit/pptx/provider.js +3 -0
- package/dist/edit/pptx/session.d.ts +6 -2
- package/dist/edit/pptx/session.js +7 -1
- package/dist/edit/pptx/types.d.ts +11 -1
- package/dist/edit/range-style.d.ts +24 -0
- package/dist/edit/range-style.js +46 -0
- package/dist/edit/session.js +5 -0
- package/dist/edit/types.d.ts +13 -0
- package/dist/viewport.js +11 -0
- package/dist/worker-protocol.d.ts +1 -1
- package/dist/workers/ooxml-edit-worker.js +90 -2
- package/dist/workers/pdf-edit-worker.js +2019 -1003
- package/package.json +1 -1
|
@@ -10,17 +10,23 @@ export function mapRangeThrough(range, records) {
|
|
|
10
10
|
return { start: start.position, end: end.position };
|
|
11
11
|
}
|
|
12
12
|
function carry(position, records) {
|
|
13
|
-
const carried = { position, gone: false };
|
|
13
|
+
const carried = { position, gone: false, migrations: [] };
|
|
14
14
|
for (const record of records) {
|
|
15
15
|
const forward = record.kind === "apply" || record.kind === "redo";
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
const indexed = record.operations.map((operation, index) => ({
|
|
17
|
+
operation,
|
|
18
|
+
index,
|
|
19
|
+
}));
|
|
20
|
+
for (const { operation, index } of forward ? indexed : indexed.reverse()) {
|
|
21
|
+
const migrations = (record.textAnchorMigrations ?? []).filter((migration) => migration.operationIndex === index);
|
|
22
|
+
if (forward) {
|
|
23
|
+
migrateForward(carried, migrations);
|
|
21
24
|
applyForward(carried, operation);
|
|
22
|
-
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
23
27
|
applyBackward(carried, operation);
|
|
28
|
+
migrateBackward(carried, migrations);
|
|
29
|
+
}
|
|
24
30
|
}
|
|
25
31
|
const { receipt } = record;
|
|
26
32
|
const id = carried.position.elementId;
|
|
@@ -34,6 +40,30 @@ function carry(position, records) {
|
|
|
34
40
|
}
|
|
35
41
|
return carried;
|
|
36
42
|
}
|
|
43
|
+
function migrateForward(carried, migrations) {
|
|
44
|
+
const migration = migrations.find((entry) => entry.sourceElementId === carried.position.elementId);
|
|
45
|
+
if (!migration || carried.gone)
|
|
46
|
+
return;
|
|
47
|
+
carried.migrations.push(migration);
|
|
48
|
+
carried.position = {
|
|
49
|
+
elementId: migration.targetElementId,
|
|
50
|
+
offset: migration.targetOffset +
|
|
51
|
+
Math.min(carried.position.offset, migration.sourceLength),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function migrateBackward(carried, migrations) {
|
|
55
|
+
const previous = carried.migrations.at(-1);
|
|
56
|
+
if (!previous ||
|
|
57
|
+
carried.position.elementId !== previous.targetElementId ||
|
|
58
|
+
!migrations.some((entry) => entry.sourceElementId === previous.sourceElementId &&
|
|
59
|
+
entry.targetElementId === previous.targetElementId))
|
|
60
|
+
return;
|
|
61
|
+
carried.migrations.pop();
|
|
62
|
+
carried.position = {
|
|
63
|
+
elementId: previous.sourceElementId,
|
|
64
|
+
offset: Math.max(0, Math.min(previous.sourceLength, carried.position.offset - previous.targetOffset)),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
37
67
|
function applyForward(carried, operation) {
|
|
38
68
|
const id = carried.position.elementId;
|
|
39
69
|
if (operation.op === "deleteElement" && targetOf(operation) === id) {
|
|
@@ -84,7 +114,7 @@ function applyBackward(carried, operation) {
|
|
|
84
114
|
};
|
|
85
115
|
}
|
|
86
116
|
function asReplace(operation, elementId) {
|
|
87
|
-
if (operation.op !== "replaceText")
|
|
117
|
+
if (operation.op !== "replaceText" && operation.op !== "replaceParagraphText")
|
|
88
118
|
return undefined;
|
|
89
119
|
const candidate = operation;
|
|
90
120
|
if (candidate.target !== elementId || typeof candidate.text !== "string")
|
|
@@ -9,3 +9,5 @@ export declare const tableMarkSchema: JsonSchema;
|
|
|
9
9
|
/** The mark on a table's text objects: the id alone. */
|
|
10
10
|
export declare const tableMemberMarkSchema: JsonSchema;
|
|
11
11
|
export declare const textBoxMarkSchema: JsonSchema;
|
|
12
|
+
/** Persisted native imported paragraph; no unvalidated layout inputs are trusted. */
|
|
13
|
+
export declare const paragraphMarkSchema: JsonSchema;
|
package/dist/edit/pdf/schemas.js
CHANGED
|
@@ -143,6 +143,11 @@ export const pdfOperationSchemas = Object.freeze({
|
|
|
143
143
|
target: { $ref: "#/$defs/target" },
|
|
144
144
|
style: { $ref: "#/$defs/textBoxStyle" },
|
|
145
145
|
}, ["target", "style"]),
|
|
146
|
+
replaceParagraphText: operation("replaceParagraphText", {
|
|
147
|
+
target: { $ref: "#/$defs/target" },
|
|
148
|
+
text: { type: "string", maxLength: 20000 },
|
|
149
|
+
range: { $ref: "#/$defs/textRange" },
|
|
150
|
+
}, ["target", "text"]),
|
|
146
151
|
resizeElement: operation("resizeElement", {
|
|
147
152
|
target: { $ref: "#/$defs/target" },
|
|
148
153
|
rect: { $ref: "#/$defs/rect" },
|
|
@@ -292,3 +297,35 @@ export const textBoxMarkSchema = {
|
|
|
292
297
|
},
|
|
293
298
|
$defs: definitions,
|
|
294
299
|
};
|
|
300
|
+
/** Persisted native imported paragraph; no unvalidated layout inputs are trusted. */
|
|
301
|
+
export const paragraphMarkSchema = {
|
|
302
|
+
type: "object",
|
|
303
|
+
required: ["kind", "id", "rect", "text", "lines", "style", "baselineOffset"],
|
|
304
|
+
additionalProperties: false,
|
|
305
|
+
properties: {
|
|
306
|
+
kind: { const: "paragraph" },
|
|
307
|
+
id: { $ref: "#/$defs/target" },
|
|
308
|
+
rect: { $ref: "#/$defs/rect" },
|
|
309
|
+
text: { type: "string", maxLength: 20000 },
|
|
310
|
+
baselineOffset: { type: "number", minimum: 0, maximum: 2 },
|
|
311
|
+
lines: {
|
|
312
|
+
type: "array",
|
|
313
|
+
maxItems: 20000,
|
|
314
|
+
items: { type: "string", maxLength: 20000 },
|
|
315
|
+
},
|
|
316
|
+
style: {
|
|
317
|
+
type: "object",
|
|
318
|
+
required: [
|
|
319
|
+
"fontFamily",
|
|
320
|
+
"fontSize",
|
|
321
|
+
"bold",
|
|
322
|
+
"italic",
|
|
323
|
+
"color",
|
|
324
|
+
"lineHeight",
|
|
325
|
+
],
|
|
326
|
+
additionalProperties: false,
|
|
327
|
+
properties: definitions.textBoxStyle.properties,
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
$defs: definitions,
|
|
331
|
+
};
|
|
@@ -2,7 +2,7 @@ import type { TextSelection } from "../../contracts.js";
|
|
|
2
2
|
import type { DescribeOptions, DocumentDescription, EditCheckpoint, OutlineOptions, OutlineResult, TargetCandidate, TargetQuery, ToolCall, ToolCallOptions, ToolResult, ToolSet } from "../ai/types.js";
|
|
3
3
|
import type { EditSessionCore } from "../engine.js";
|
|
4
4
|
import type { ApplyOptions, AssetOptions, EditFindOptions, EditOperation, EditReceipt, EditState, ElementQuery, HistoryOptions, OperationSchemaSet, PagePoint, PageRect, ReadItem, ReadOptions, ReadResult, SavedDocument, TextPosition, TextRange, TextTarget } from "../types.js";
|
|
5
|
-
import type { DeleteElementOperation, DeletePageOperation, Fields, InsertImageOperation, InsertPageOperation, InsertShapeOperation, InsertTableOperation, InsertTextBoxOperation, MoveElementOperation, MovePageOperation, PdfEditSession, PdfElement, PdfOperation, PdfSaveOptions, ReplaceTextOperation, ResizeElementOperation, RotatePageOperation, SetShapeStyleOperation, SetTableCellOperation, PageBitmap, PageLayout, RenderOptions, SetTextStyleOperation, TextFont, TextLayout } from "./types.js";
|
|
5
|
+
import type { DeleteElementOperation, DeletePageOperation, Fields, InsertImageOperation, InsertPageOperation, InsertShapeOperation, InsertTableOperation, InsertTextBoxOperation, MoveElementOperation, MovePageOperation, PdfEditSession, PdfElement, PdfOperation, PdfSaveOptions, PdfTextParagraph, ReplaceParagraphTextOperation, ReplaceTextOperation, ResizeElementOperation, RotatePageOperation, SetShapeStyleOperation, SetTableCellOperation, PageBitmap, PageLayout, RenderOptions, SetTextStyleOperation, TextFont, TextLayout } from "./types.js";
|
|
6
6
|
/**
|
|
7
7
|
* The PDF session: the core session narrowed to PDF operations and elements.
|
|
8
8
|
* Typed methods are added here as their operations ship; each is `apply()`
|
|
@@ -38,6 +38,8 @@ export declare class PdfSession implements PdfEditSession {
|
|
|
38
38
|
dropCheckpoint(id: string): void;
|
|
39
39
|
get tools(): ToolSet;
|
|
40
40
|
callTool(call: ToolCall, options?: ToolCallOptions): Promise<ToolResult>;
|
|
41
|
+
getTextParagraph(elementId: string, options?: ReadOptions): Promise<ReadItem<PdfTextParagraph>>;
|
|
42
|
+
replaceParagraphText(fields: Fields<ReplaceParagraphTextOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
41
43
|
getTextLayout(elementId: string, options?: ReadOptions): Promise<ReadItem<TextLayout>>;
|
|
42
44
|
getTextFont(elementId: string, options?: ReadOptions): Promise<ReadItem<TextFont>>;
|
|
43
45
|
positionAt(pageIndex: number, point: PagePoint, options?: ReadOptions): Promise<ReadItem<TextPosition>>;
|
package/dist/edit/pdf/session.js
CHANGED
|
@@ -66,22 +66,25 @@ export class PdfSession {
|
|
|
66
66
|
if (receipt.dryRun || receipt.revision === last)
|
|
67
67
|
return;
|
|
68
68
|
this.#refreshGeometry(receipt);
|
|
69
|
-
let involved =
|
|
69
|
+
let involved = {
|
|
70
|
+
operations,
|
|
71
|
+
textAnchorMigrations: receipt.textAnchorMigrations ?? [],
|
|
72
|
+
};
|
|
70
73
|
switch (kind) {
|
|
71
74
|
case "apply":
|
|
72
|
-
this.#applied.push(
|
|
75
|
+
this.#applied.push(involved);
|
|
73
76
|
this.#undone = [];
|
|
74
77
|
break;
|
|
75
78
|
case "undo":
|
|
76
|
-
involved = this.#applied.pop() ??
|
|
79
|
+
involved = this.#applied.pop() ?? involved;
|
|
77
80
|
this.#undone.push(involved);
|
|
78
81
|
break;
|
|
79
82
|
case "redo":
|
|
80
|
-
involved = this.#undone.pop() ??
|
|
83
|
+
involved = this.#undone.pop() ?? involved;
|
|
81
84
|
this.#applied.push(involved);
|
|
82
85
|
break;
|
|
83
86
|
case "reset":
|
|
84
|
-
involved = this.#applied
|
|
87
|
+
involved = combineBatches(this.#applied);
|
|
85
88
|
this.#applied = [];
|
|
86
89
|
this.#undone = [];
|
|
87
90
|
break;
|
|
@@ -95,7 +98,8 @@ export class PdfSession {
|
|
|
95
98
|
this.#log.push({
|
|
96
99
|
revision: receipt.revision,
|
|
97
100
|
kind,
|
|
98
|
-
operations: involved,
|
|
101
|
+
operations: involved.operations,
|
|
102
|
+
textAnchorMigrations: involved.textAnchorMigrations,
|
|
99
103
|
receipt,
|
|
100
104
|
});
|
|
101
105
|
if (this.#log.length > LOG_LIMIT)
|
|
@@ -195,6 +199,17 @@ export class PdfSession {
|
|
|
195
199
|
callTool(call, options) {
|
|
196
200
|
return runTool(this, this.tools, call, options);
|
|
197
201
|
}
|
|
202
|
+
getTextParagraph(elementId, options) {
|
|
203
|
+
return this.#core.readItem(options, (engine, signal) => {
|
|
204
|
+
const reads = pdfReads(engine);
|
|
205
|
+
if (!reads.textParagraph)
|
|
206
|
+
throw new ViewerError("edit-unsupported", "This engine does not resolve PDF paragraphs");
|
|
207
|
+
return reads.textParagraph(elementId, signal);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
replaceParagraphText(fields, options) {
|
|
211
|
+
return this.apply([{ op: "replaceParagraphText", ...fields }], options);
|
|
212
|
+
}
|
|
198
213
|
getTextLayout(elementId, options) {
|
|
199
214
|
return this.#core.readItem(options, (engine, signal) => pdfReads(engine).textLayout(elementId, signal));
|
|
200
215
|
}
|
|
@@ -302,6 +317,20 @@ export class PdfSession {
|
|
|
302
317
|
}
|
|
303
318
|
/** How many committed calls `mapRange` can look back over. */
|
|
304
319
|
const LOG_LIMIT = 512;
|
|
320
|
+
/** Reset reverses all committed batches, retaining each migration's operation. */
|
|
321
|
+
function combineBatches(batches) {
|
|
322
|
+
const operations = [];
|
|
323
|
+
const textAnchorMigrations = [];
|
|
324
|
+
for (const batch of batches) {
|
|
325
|
+
for (const migration of batch.textAnchorMigrations)
|
|
326
|
+
textAnchorMigrations.push({
|
|
327
|
+
...migration,
|
|
328
|
+
operationIndex: operations.length + migration.operationIndex,
|
|
329
|
+
});
|
|
330
|
+
operations.push(...batch.operations);
|
|
331
|
+
}
|
|
332
|
+
return { operations, textAnchorMigrations };
|
|
333
|
+
}
|
|
305
334
|
/** The engine behind a PDF session answers the overlay reads; a stand-in may not. */
|
|
306
335
|
function pdfReads(engine) {
|
|
307
336
|
const reads = engine;
|
package/dist/edit/pdf/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TextSelection } from "../../contracts.js";
|
|
2
2
|
import type { ApplyOptions, BinaryData, EditElement, EditReceipt, EditSessionBase, PagePoint, PageRect, ReadItem, ReadOptions, ReadResult, SavedDocument, SaveOptions, TextPosition, TextRange } from "../types.js";
|
|
3
|
-
export type PdfElementKind = "text" | "image" | "shape" | "textBox" | "table" | "other";
|
|
3
|
+
export type PdfElementKind = "text" | "image" | "shape" | "textBox" | "paragraph" | "table" | "other";
|
|
4
4
|
export type PdfTextAlign = "left" | "center" | "right";
|
|
5
5
|
export interface PdfTextStyle {
|
|
6
6
|
readonly fontFamily: string;
|
|
@@ -12,7 +12,7 @@ export interface PdfTextStyle {
|
|
|
12
12
|
readonly color: string;
|
|
13
13
|
/** Text boxes only. */
|
|
14
14
|
readonly align?: PdfTextAlign;
|
|
15
|
-
/** Text boxes
|
|
15
|
+
/** Text boxes and paragraphs; a multiple of the font size. */
|
|
16
16
|
readonly lineHeight?: number;
|
|
17
17
|
}
|
|
18
18
|
export interface PdfShapeStyle {
|
|
@@ -26,7 +26,9 @@ export interface PdfShapeStyle {
|
|
|
26
26
|
}
|
|
27
27
|
export interface PdfElement extends EditElement {
|
|
28
28
|
readonly kind: PdfElementKind;
|
|
29
|
-
/**
|
|
29
|
+
/** Canonical native paragraph target, when this imported row belongs to one. */
|
|
30
|
+
readonly textEditingTarget?: string;
|
|
31
|
+
/** Present for `text`, `textBox` and `paragraph`. */
|
|
30
32
|
readonly textStyle?: PdfTextStyle;
|
|
31
33
|
/** Present for `shape` and `table`. */
|
|
32
34
|
readonly shapeStyle?: PdfShapeStyle;
|
|
@@ -57,7 +59,7 @@ export interface InsertTextBoxOperation {
|
|
|
57
59
|
}
|
|
58
60
|
export interface ReplaceTextOperation {
|
|
59
61
|
readonly op: "replaceText";
|
|
60
|
-
/** A `textBox` or `
|
|
62
|
+
/** A `textBox`, `text` or `paragraph` element; supported fields depend on its capabilities. */
|
|
61
63
|
readonly target: string;
|
|
62
64
|
readonly text: string;
|
|
63
65
|
/**
|
|
@@ -68,9 +70,33 @@ export interface ReplaceTextOperation {
|
|
|
68
70
|
*/
|
|
69
71
|
readonly range?: TextRange;
|
|
70
72
|
}
|
|
73
|
+
/** A confidently resolved homogeneous paragraph of imported horizontal text. */
|
|
74
|
+
export interface PdfTextParagraph {
|
|
75
|
+
readonly id: string;
|
|
76
|
+
readonly pageIndex: number;
|
|
77
|
+
readonly text: string;
|
|
78
|
+
readonly bounds: PageRect;
|
|
79
|
+
readonly textStyle: PdfTextStyle & {
|
|
80
|
+
readonly lineHeight: number;
|
|
81
|
+
};
|
|
82
|
+
readonly memberIds: readonly string[];
|
|
83
|
+
/** Half-open UTF-16 spans in the paragraph's logical text. */
|
|
84
|
+
readonly members: readonly {
|
|
85
|
+
readonly elementId: string;
|
|
86
|
+
readonly start: number;
|
|
87
|
+
readonly end: number;
|
|
88
|
+
}[];
|
|
89
|
+
}
|
|
90
|
+
export interface ReplaceParagraphTextOperation {
|
|
91
|
+
readonly op: "replaceParagraphText";
|
|
92
|
+
readonly target: string;
|
|
93
|
+
readonly text: string;
|
|
94
|
+
/** Both endpoints name the paragraph id, not an individual visual row. */
|
|
95
|
+
readonly range?: TextRange;
|
|
96
|
+
}
|
|
71
97
|
export interface SetTextStyleOperation {
|
|
72
98
|
readonly op: "setTextStyle";
|
|
73
|
-
/** A `textBox` or `
|
|
99
|
+
/** A `textBox`, `text`, or `paragraph` element. */
|
|
74
100
|
readonly target: string;
|
|
75
101
|
readonly style: PdfTextBoxStyle;
|
|
76
102
|
}
|
|
@@ -211,7 +237,7 @@ export type RotatePageOperation = {
|
|
|
211
237
|
readonly rotation?: never;
|
|
212
238
|
};
|
|
213
239
|
/** The PDF operation union; operations are added as they ship. */
|
|
214
|
-
export type PdfOperation = InsertTextBoxOperation | ReplaceTextOperation | SetTextStyleOperation | ResizeElementOperation | MoveElementOperation | DeleteElementOperation | InsertPageOperation | DeletePageOperation | MovePageOperation | RotatePageOperation | InsertShapeOperation | SetShapeStyleOperation | InsertImageOperation | InsertTableOperation | SetTableCellOperation;
|
|
240
|
+
export type PdfOperation = InsertTextBoxOperation | ReplaceTextOperation | ReplaceParagraphTextOperation | SetTextStyleOperation | ResizeElementOperation | MoveElementOperation | DeleteElementOperation | InsertPageOperation | DeletePageOperation | MovePageOperation | RotatePageOperation | InsertShapeOperation | SetShapeStyleOperation | InsertImageOperation | InsertTableOperation | SetTableCellOperation;
|
|
215
241
|
export interface PdfSaveOptions extends SaveOptions {
|
|
216
242
|
/**
|
|
217
243
|
* `full` rewrites the file, so deleted content is gone and the bytes do not
|
|
@@ -315,10 +341,14 @@ export type Fields<T extends PdfOperation> = T extends unknown ? Omit<T, "op"> :
|
|
|
315
341
|
export interface PdfEditSession extends EditSessionBase<PdfOperation, PdfElement> {
|
|
316
342
|
readonly format: "pdf";
|
|
317
343
|
save(options?: PdfSaveOptions): Promise<SavedDocument>;
|
|
318
|
-
/**
|
|
344
|
+
/** Resolves an imported row or a paragraph id; undefined when grouping is unsafe. */
|
|
345
|
+
getTextParagraph(elementId: string, options?: ReadOptions): Promise<ReadItem<PdfTextParagraph>>;
|
|
346
|
+
/** Reflows the complete paragraph as one atomic history change. */
|
|
347
|
+
replaceParagraphText(fields: Fields<ReplaceParagraphTextOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
348
|
+
/** Lines, glyph boxes and styles of a `text`, `textBox`, `paragraph` or `table` element. */
|
|
319
349
|
getTextLayout(elementId: string, options?: ReadOptions): Promise<ReadItem<TextLayout>>;
|
|
320
350
|
/**
|
|
321
|
-
* The font a `text` or `
|
|
351
|
+
* The font a `text`, `textBox` or `paragraph` element is drawn in, as a browser loads it,
|
|
322
352
|
* or why it cannot be loaded; see `TextFont`.
|
|
323
353
|
*/
|
|
324
354
|
getTextFont(elementId: string, options?: ReadOptions): Promise<ReadItem<TextFont>>;
|
|
@@ -4,7 +4,8 @@ import { type Matrix, type PlacedFrame, type Xfrm } from "./geometry.js";
|
|
|
4
4
|
import type { DeckModel, SlideRecord } from "./model.js";
|
|
5
5
|
import { type StyleSource } from "./style.js";
|
|
6
6
|
import { type TextModel } from "./text.js";
|
|
7
|
-
import
|
|
7
|
+
import { type TextSpan } from "../range-style.js";
|
|
8
|
+
import type { PptxElement, PptxPlaceholder, PptxTextStyle } from "./types.js";
|
|
8
9
|
/** What the engine knows about one element beyond what it reports. */
|
|
9
10
|
export interface ShapeRecord {
|
|
10
11
|
readonly element: PptxElement;
|
|
@@ -28,6 +29,8 @@ export interface ShapeRecord {
|
|
|
28
29
|
/** Inside the fallback branch of `mc:AlternateContent`. */
|
|
29
30
|
readonly readOnly: boolean;
|
|
30
31
|
readonly parent?: ShapeRecord;
|
|
32
|
+
/** The style a span of the shape's text shows: what every run it covers shares. */
|
|
33
|
+
readonly styleOf?: (span: TextSpan) => Partial<PptxTextStyle> | undefined;
|
|
31
34
|
}
|
|
32
35
|
export interface SlideElements {
|
|
33
36
|
readonly slide: SlideRecord;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { groupMatrix, IDENTITY, multiply, placeFrame, readXfrm, } from "./geometry.js";
|
|
2
2
|
import { resolveTextStyle, readShapeStyle } from "./style.js";
|
|
3
3
|
import { firstTextRun, readTableCells, readTextModel, } from "./text.js";
|
|
4
|
+
import { runsCovering, sharedStyle } from "../range-style.js";
|
|
4
5
|
/*
|
|
5
6
|
* The elements of a slide, read from its XML: every child of the shape tree
|
|
6
7
|
* with its frame (own, or inherited from the layout and master for a
|
|
@@ -236,6 +237,11 @@ function readRecord(context, node, parents, parent, readOnly, walk) {
|
|
|
236
237
|
...(text ? { text } : {}),
|
|
237
238
|
readOnly,
|
|
238
239
|
...(parent ? { parent } : {}),
|
|
240
|
+
...(text && txBody
|
|
241
|
+
? {
|
|
242
|
+
styleOf: (span) => rangeStyleOf(context, placeholder, txBody, text, span),
|
|
243
|
+
}
|
|
244
|
+
: {}),
|
|
239
245
|
};
|
|
240
246
|
}
|
|
241
247
|
function operationsFor(kind, hasText) {
|
|
@@ -285,6 +291,35 @@ export function listStylesOf(context, txBody, placeholder) {
|
|
|
285
291
|
lists.push(defaults);
|
|
286
292
|
return lists;
|
|
287
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* The style a span of a text body shows: each run it covers resolves through
|
|
296
|
+
* its paragraph and the inherited lists, and the properties they all share
|
|
297
|
+
* are kept. An empty paragraph reads its end-of-paragraph properties.
|
|
298
|
+
*/
|
|
299
|
+
function rangeStyleOf(context, placeholder, txBody, text, span) {
|
|
300
|
+
const lists = listStylesOf(context, txBody, placeholder);
|
|
301
|
+
const styleOf = (paragraph, rPr) => resolveTextStyle({
|
|
302
|
+
...(rPr ? { rPr: { part: context.part, node: rPr } } : {}),
|
|
303
|
+
...(paragraph.pPr
|
|
304
|
+
? { pPr: { part: context.part, node: paragraph.pPr } }
|
|
305
|
+
: {}),
|
|
306
|
+
level: paragraph.level,
|
|
307
|
+
lists,
|
|
308
|
+
fonts: context.fonts,
|
|
309
|
+
});
|
|
310
|
+
const runs = text.paragraphs.flatMap((paragraph) => paragraph.runs.map((run) => ({
|
|
311
|
+
start: run.start,
|
|
312
|
+
end: run.end,
|
|
313
|
+
style: () => styleOf(paragraph, run.rPr),
|
|
314
|
+
})));
|
|
315
|
+
const covered = runsCovering(runs, span);
|
|
316
|
+
if (covered.length > 0)
|
|
317
|
+
return sharedStyle(covered.map((run) => run.style()));
|
|
318
|
+
const paragraph = text.paragraphs.find((candidate) => candidate.start <= span.start && span.start <= candidate.end) ?? text.paragraphs[0];
|
|
319
|
+
return paragraph
|
|
320
|
+
? sharedStyle([styleOf(paragraph, paragraph.endParaRPr)])
|
|
321
|
+
: undefined;
|
|
322
|
+
}
|
|
288
323
|
function textStyleOf(context, placeholder, txBody, text) {
|
|
289
324
|
const first = firstTextRun(text);
|
|
290
325
|
const paragraph = first?.paragraph ?? text.paragraphs[0];
|
|
@@ -3,11 +3,13 @@ import type { EditEngine, EngineBatch, EngineChange, MaterializedDocument, Mater
|
|
|
3
3
|
import { OoxmlPackage } from "../ooxml/package.js";
|
|
4
4
|
import type { EditFindOptions, EditOperation, ElementQuery, OperationIssue, PagePoint, TextTarget } from "../types.js";
|
|
5
5
|
import { DeckModel } from "./model.js";
|
|
6
|
-
import type { PptxElement, PptxLayoutInfo, PptxSlideInfo } from "./types.js";
|
|
6
|
+
import type { PptxElement, PptxLayoutInfo, PptxSlideInfo, PptxTextStyle } from "./types.js";
|
|
7
|
+
import { type TextSpan } from "../range-style.js";
|
|
7
8
|
/** The reads behind `getSlides` and `getLayouts`, beyond the core engine interface. */
|
|
8
9
|
export interface PptxEngineReads {
|
|
9
10
|
slides(signal: AbortSignal): Promise<readonly PptxSlideInfo[]>;
|
|
10
11
|
layouts(signal: AbortSignal): Promise<readonly PptxLayoutInfo[]>;
|
|
12
|
+
textStyle(id: string, span: TextSpan | undefined, signal: AbortSignal): Promise<Partial<PptxTextStyle> | undefined>;
|
|
11
13
|
}
|
|
12
14
|
export declare class PptxEditEngine implements EditEngine, PptxEngineReads {
|
|
13
15
|
#private;
|
|
@@ -32,6 +34,8 @@ export declare class PptxEditEngine implements EditEngine, PptxEngineReads {
|
|
|
32
34
|
elementsAt(pageIndex: number, point: PagePoint, signal: AbortSignal): Promise<readonly PptxElement[]>;
|
|
33
35
|
findText(query: string, options: EditFindOptions, signal: AbortSignal): Promise<readonly TextTarget[]>;
|
|
34
36
|
slides(signal: AbortSignal): Promise<readonly PptxSlideInfo[]>;
|
|
37
|
+
/** The style a span of a shape's text shows; none for an element without text or a span past it. */
|
|
38
|
+
textStyle(id: string, span: TextSpan | undefined, signal: AbortSignal): Promise<Partial<PptxTextStyle> | undefined>;
|
|
35
39
|
layouts(signal: AbortSignal): Promise<readonly PptxLayoutInfo[]>;
|
|
36
40
|
dispose(): Promise<void>;
|
|
37
41
|
}
|
package/dist/edit/pptx/engine.js
CHANGED
|
@@ -8,6 +8,7 @@ import { DeckModel } from "./model.js";
|
|
|
8
8
|
import { pptxHandlers } from "./handlers.js";
|
|
9
9
|
import { issueCollector, } from "./operations.js";
|
|
10
10
|
import { pptxOperationSchemas } from "./schemas.js";
|
|
11
|
+
import { spanFits } from "../range-style.js";
|
|
11
12
|
const SLIDE_PART = /^\/ppt\/slides\/slide(\d+)\.xml$/i;
|
|
12
13
|
const SLIDE_RELS = /^\/ppt\/slides\/_rels\/slide(\d+)\.xml\.rels$/i;
|
|
13
14
|
export class PptxEditEngine {
|
|
@@ -357,6 +358,16 @@ export class PptxEditEngine {
|
|
|
357
358
|
});
|
|
358
359
|
return out;
|
|
359
360
|
}
|
|
361
|
+
/** The style a span of a shape's text shows; none for an element without text or a span past it. */
|
|
362
|
+
async textStyle(id, span, signal) {
|
|
363
|
+
const record = await this.#locate(id, signal);
|
|
364
|
+
if (!record?.styleOf || !record.text)
|
|
365
|
+
return undefined;
|
|
366
|
+
const range = span ?? { start: 0, end: record.text.text.length };
|
|
367
|
+
if (!spanFits(range, record.text.text.length))
|
|
368
|
+
return undefined;
|
|
369
|
+
return record.styleOf(range);
|
|
370
|
+
}
|
|
360
371
|
async layouts(signal) {
|
|
361
372
|
const model = await this.model(signal);
|
|
362
373
|
return model.layouts.map((layout) => ({
|
|
@@ -88,6 +88,14 @@ export function createOoxmlEditHandler() {
|
|
|
88
88
|
return pptx().layouts(signal);
|
|
89
89
|
case "edit-docx-revisions":
|
|
90
90
|
return docx().revisions(payload.id, signal);
|
|
91
|
+
case "edit-docx-text-style": {
|
|
92
|
+
const { id, span } = payload;
|
|
93
|
+
return docx().textStyle(id, span, signal);
|
|
94
|
+
}
|
|
95
|
+
case "edit-pptx-text-style": {
|
|
96
|
+
const { id, span } = payload;
|
|
97
|
+
return pptx().textStyle(id, span, signal);
|
|
98
|
+
}
|
|
91
99
|
case "edit-dispose":
|
|
92
100
|
await state?.dispose();
|
|
93
101
|
state = undefined;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { EditEngineContext } from "../engine.js";
|
|
2
2
|
import { type OoxmlEditProviderOptions } from "../ooxml/worker.js";
|
|
3
3
|
import { WorkerEngineClient } from "../worker-engine.js";
|
|
4
|
+
import type { TextSpan } from "../range-style.js";
|
|
4
5
|
import type { PptxEngineReads } from "./engine.js";
|
|
5
|
-
import type { PptxLayoutInfo, PptxSlideInfo } from "./types.js";
|
|
6
|
+
import type { PptxLayoutInfo, PptxSlideInfo, PptxTextStyle } from "./types.js";
|
|
6
7
|
export type PptxEditProviderOptions = OoxmlEditProviderOptions;
|
|
7
8
|
/**
|
|
8
9
|
* Starts the OOXML edit worker for `original` as a PPTX engine. Loaded
|
|
@@ -14,4 +15,5 @@ export declare class PptxEditEngineClient extends WorkerEngineClient implements
|
|
|
14
15
|
start(original: Uint8Array): Promise<void>;
|
|
15
16
|
slides(signal: AbortSignal): Promise<readonly PptxSlideInfo[]>;
|
|
16
17
|
layouts(signal: AbortSignal): Promise<readonly PptxLayoutInfo[]>;
|
|
18
|
+
textStyle(id: string, span: TextSpan | undefined, signal: AbortSignal): Promise<Partial<PptxTextStyle> | undefined>;
|
|
17
19
|
}
|
|
@@ -37,4 +37,7 @@ export class PptxEditEngineClient extends WorkerEngineClient {
|
|
|
37
37
|
layouts(signal) {
|
|
38
38
|
return this.request("edit-pptx-layouts", undefined, signal);
|
|
39
39
|
}
|
|
40
|
+
textStyle(id, span, signal) {
|
|
41
|
+
return this.request("edit-pptx-text-style", { id, span }, signal);
|
|
42
|
+
}
|
|
40
43
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DescribeOptions, DocumentDescription, EditCheckpoint, OutlineOptions, OutlineResult, TargetCandidate, TargetQuery, ToolCall, ToolCallOptions, ToolResult, ToolSet } from "../ai/types.js";
|
|
2
2
|
import type { EditSessionAccess, EditSessionCore } from "../engine.js";
|
|
3
|
-
import type { ApplyOptions, AssetOptions, EditFindOptions, EditOperation, EditReceipt, EditState, ElementQuery, HistoryOptions, OperationSchemaSet, PagePoint, ReadItem, ReadOptions, ReadResult, SavedDocument, TextTarget } from "../types.js";
|
|
4
|
-
import type { PptxDeleteElementOperation, PptxDeleteSlideOperation, PptxDuplicateSlideOperation, PptxEditSession, PptxElement, PptxFields, PptxInsertImageOperation, PptxInsertSlideOperation, PptxInsertTableOperation, PptxInsertTextBoxOperation, PptxLayoutInfo, PptxMoveElementOperation, PptxMoveSlideOperation, PptxOperation, PptxReplaceTextOperation, PptxResizeElementOperation, PptxSaveOptions, PptxSetShapeStyleOperation, PptxSetTableCellOperation, PptxSetTextStyleOperation, PptxSlideInfo } from "./types.js";
|
|
3
|
+
import type { ApplyOptions, AssetOptions, EditFindOptions, EditOperation, EditReceipt, EditState, ElementQuery, HistoryOptions, OperationSchemaSet, PagePoint, ReadItem, ReadOptions, ReadResult, SavedDocument, TextRange, TextTarget } from "../types.js";
|
|
4
|
+
import type { PptxDeleteElementOperation, PptxDeleteSlideOperation, PptxDuplicateSlideOperation, PptxEditSession, PptxElement, PptxFields, PptxInsertImageOperation, PptxInsertSlideOperation, PptxInsertTableOperation, PptxInsertTextBoxOperation, PptxLayoutInfo, PptxMoveElementOperation, PptxMoveSlideOperation, PptxOperation, PptxReplaceTextOperation, PptxResizeElementOperation, PptxSaveOptions, PptxSetShapeStyleOperation, PptxSetTableCellOperation, PptxSetTextStyleOperation, PptxSlideInfo, PptxTextStyle } from "./types.js";
|
|
5
5
|
/**
|
|
6
6
|
* The PPTX session: the core session narrowed to PPTX operations and
|
|
7
7
|
* elements, plus the slide and layout reads. Each typed method is `apply()`
|
|
@@ -42,6 +42,10 @@ export declare class PptxSession implements PptxEditSession {
|
|
|
42
42
|
callTool(call: ToolCall, options?: ToolCallOptions): Promise<ToolResult>;
|
|
43
43
|
getSlides(options?: ReadOptions): Promise<ReadResult<PptxSlideInfo>>;
|
|
44
44
|
getLayouts(options?: ReadOptions): Promise<ReadResult<PptxLayoutInfo>>;
|
|
45
|
+
getTextStyle(fields: {
|
|
46
|
+
readonly target: string;
|
|
47
|
+
readonly range?: TextRange;
|
|
48
|
+
}, options?: ReadOptions): Promise<ReadItem<Partial<PptxTextStyle>>>;
|
|
45
49
|
replaceText(fields: PptxFields<PptxReplaceTextOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
46
50
|
setTextStyle(fields: PptxFields<PptxSetTextStyleOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
47
51
|
setShapeStyle(fields: PptxFields<PptxSetShapeStyleOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
@@ -2,6 +2,7 @@ import { ViewerError } from "../../errors.js";
|
|
|
2
2
|
import { readDescription, readOutline } from "../ai/outline.js";
|
|
3
3
|
import { resolveTargets } from "../ai/targets.js";
|
|
4
4
|
import { buildToolSet, callTool as runTool } from "../ai/tools.js";
|
|
5
|
+
import { spanOnTarget } from "../range-style.js";
|
|
5
6
|
/**
|
|
6
7
|
* The PPTX session: the core session narrowed to PPTX operations and
|
|
7
8
|
* elements, plus the slide and layout reads. Each typed method is `apply()`
|
|
@@ -118,6 +119,10 @@ export class PptxSession {
|
|
|
118
119
|
getLayouts(options) {
|
|
119
120
|
return this.#core.readItems(options, (engine, signal) => pptxReads(engine).layouts(signal));
|
|
120
121
|
}
|
|
122
|
+
async getTextStyle(fields, options) {
|
|
123
|
+
const span = spanOnTarget(fields.target, fields.range);
|
|
124
|
+
return this.#core.readItem(options, (engine, signal) => pptxReads(engine).textStyle(fields.target, span, signal));
|
|
125
|
+
}
|
|
121
126
|
replaceText(fields, options) {
|
|
122
127
|
return this.apply([{ op: "replaceText", ...fields }], options);
|
|
123
128
|
}
|
|
@@ -165,7 +170,8 @@ export class PptxSession {
|
|
|
165
170
|
function pptxReads(engine) {
|
|
166
171
|
const candidate = engine;
|
|
167
172
|
if (typeof candidate.slides !== "function" ||
|
|
168
|
-
typeof candidate.layouts !== "function"
|
|
173
|
+
typeof candidate.layouts !== "function" ||
|
|
174
|
+
typeof candidate.textStyle !== "function")
|
|
169
175
|
throw new ViewerError("edit-unsupported", "The engine does not provide the PPTX reads", { details: { format: "pptx", reason: "no-reads" } });
|
|
170
176
|
return candidate;
|
|
171
177
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ApplyOptions, BinaryData, EditColor, EditElement, EditReceipt, EditSessionBase, PagePoint, PageRect, ReadOptions, ReadResult, SavedDocument, SaveOptions, TextRange } from "../types.js";
|
|
1
|
+
import type { ApplyOptions, BinaryData, EditColor, EditElement, EditReceipt, EditSessionBase, PagePoint, PageRect, ReadItem, ReadOptions, ReadResult, SavedDocument, SaveOptions, TextRange } from "../types.js";
|
|
2
2
|
export type PptxElementKind = "shape" | "image" | "table" | "connector" | "group" | "other";
|
|
3
3
|
export type PptxTextAlign = "left" | "center" | "right" | "justify";
|
|
4
4
|
/** The style of the first run with text, resolved through the placeholder chain and the theme. */
|
|
@@ -201,6 +201,16 @@ export interface PptxEditSession extends EditSessionBase<PptxOperation, PptxElem
|
|
|
201
201
|
getSlides(options?: ReadOptions): Promise<ReadResult<PptxSlideInfo>>;
|
|
202
202
|
/** Every layout of every master, for `insertSlide`. */
|
|
203
203
|
getLayouts(options?: ReadOptions): Promise<ReadResult<PptxLayoutInfo>>;
|
|
204
|
+
/**
|
|
205
|
+
* The text style a range of a shape's text shows: each property every run
|
|
206
|
+
* it covers shares, a property they differ on left out. Without a range,
|
|
207
|
+
* the whole text; a collapsed range reads the run before it, whose style
|
|
208
|
+
* text typed there takes. `undefined` for an element without text.
|
|
209
|
+
*/
|
|
210
|
+
getTextStyle(fields: {
|
|
211
|
+
readonly target: string;
|
|
212
|
+
readonly range?: TextRange;
|
|
213
|
+
}, options?: ReadOptions): Promise<ReadItem<Partial<PptxTextStyle>>>;
|
|
204
214
|
/** Replaces the whole text of a shape, or the part a range covers. */
|
|
205
215
|
replaceText(fields: PptxFields<PptxReplaceTextOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
206
216
|
/** Changes run and paragraph properties; unspecified ones keep their bytes. */
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { TextRange } from "./types.js";
|
|
2
|
+
/** Half-open UTF-16 offsets in an element's text. */
|
|
3
|
+
export interface TextSpan {
|
|
4
|
+
readonly start: number;
|
|
5
|
+
readonly end: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The span a range names on its target, or `undefined` for no range (the
|
|
9
|
+
* whole text). Both ends must lie on the target.
|
|
10
|
+
*/
|
|
11
|
+
export declare function spanOnTarget(target: string, range: TextRange | undefined): TextSpan | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* The runs a span covers. A collapsed span reads the run before it, whose
|
|
14
|
+
* style text typed there takes, or the first run after it at the very start.
|
|
15
|
+
*/
|
|
16
|
+
export declare function runsCovering<R extends TextSpan>(runs: readonly R[], span: TextSpan): R[];
|
|
17
|
+
/**
|
|
18
|
+
* What every style has in common: a property two of them differ on, or one
|
|
19
|
+
* some leave out, is left out. Values compare as JSON, so a theme colour and
|
|
20
|
+
* its plain form differ.
|
|
21
|
+
*/
|
|
22
|
+
export declare function sharedStyle<T extends object>(styles: readonly T[]): Partial<T> | undefined;
|
|
23
|
+
/** Whether a span lies inside a text of `length` code units. */
|
|
24
|
+
export declare function spanFits(span: TextSpan, length: number): boolean;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ViewerError } from "../errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* The span a range names on its target, or `undefined` for no range (the
|
|
4
|
+
* whole text). Both ends must lie on the target.
|
|
5
|
+
*/
|
|
6
|
+
export function spanOnTarget(target, range) {
|
|
7
|
+
if (!range)
|
|
8
|
+
return undefined;
|
|
9
|
+
if (range.start.elementId !== target || range.end.elementId !== target)
|
|
10
|
+
throw new ViewerError("invalid-operation", "Both ends of the range must lie on the target", { details: { target } });
|
|
11
|
+
return { start: range.start.offset, end: range.end.offset };
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The runs a span covers. A collapsed span reads the run before it, whose
|
|
15
|
+
* style text typed there takes, or the first run after it at the very start.
|
|
16
|
+
*/
|
|
17
|
+
export function runsCovering(runs, span) {
|
|
18
|
+
if (span.start < span.end)
|
|
19
|
+
return runs.filter((run) => run.start < span.end && run.end > span.start);
|
|
20
|
+
const before = runs
|
|
21
|
+
.filter((run) => run.start < span.start && run.end >= span.start)
|
|
22
|
+
.at(-1);
|
|
23
|
+
const at = before ?? runs.find((run) => run.start >= span.start);
|
|
24
|
+
return at ? [at] : [];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* What every style has in common: a property two of them differ on, or one
|
|
28
|
+
* some leave out, is left out. Values compare as JSON, so a theme colour and
|
|
29
|
+
* its plain form differ.
|
|
30
|
+
*/
|
|
31
|
+
export function sharedStyle(styles) {
|
|
32
|
+
const [first, ...rest] = styles;
|
|
33
|
+
if (!first)
|
|
34
|
+
return undefined;
|
|
35
|
+
const shared = {};
|
|
36
|
+
for (const key of Object.keys(first)) {
|
|
37
|
+
const value = JSON.stringify(first[key]);
|
|
38
|
+
if (rest.every((style) => key in style && JSON.stringify(style[key]) === value))
|
|
39
|
+
shared[key] = first[key];
|
|
40
|
+
}
|
|
41
|
+
return shared;
|
|
42
|
+
}
|
|
43
|
+
/** Whether a span lies inside a text of `length` code units. */
|
|
44
|
+
export function spanFits(span, length) {
|
|
45
|
+
return span.start >= 0 && span.start <= span.end && span.end <= length;
|
|
46
|
+
}
|
package/dist/edit/session.js
CHANGED
|
@@ -739,6 +739,11 @@ export class EditSessionController {
|
|
|
739
739
|
...(change.remappedIds
|
|
740
740
|
? { remappedIds: Object.freeze({ ...change.remappedIds }) }
|
|
741
741
|
: {}),
|
|
742
|
+
...(change.textAnchorMigrations?.length
|
|
743
|
+
? {
|
|
744
|
+
textAnchorMigrations: Object.freeze(change.textAnchorMigrations.map((migration) => Object.freeze({ ...migration }))),
|
|
745
|
+
}
|
|
746
|
+
: {}),
|
|
742
747
|
changedPages: Object.freeze([...change.changedPages]),
|
|
743
748
|
pageCount: change.pageCount,
|
|
744
749
|
warnings: Object.freeze(change.warnings.map((warning) => Object.freeze({ ...warning }))),
|