web-doc 0.11.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/pdf.js +19 -8
- 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/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/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>>;
|
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 }))),
|
package/dist/edit/types.d.ts
CHANGED
|
@@ -214,6 +214,17 @@ export interface AssetOptions {
|
|
|
214
214
|
readonly mimeType?: string;
|
|
215
215
|
readonly signal?: AbortSignal;
|
|
216
216
|
}
|
|
217
|
+
/** A source text object incorporated into another logical text element. */
|
|
218
|
+
export interface TextAnchorMigration {
|
|
219
|
+
/** Zero-based operation in the applied batch, before its text replacement. */
|
|
220
|
+
readonly operationIndex: number;
|
|
221
|
+
readonly sourceElementId: string;
|
|
222
|
+
readonly targetElementId: string;
|
|
223
|
+
/** UTF-16 length retained from the source, excluding generated trailing whitespace. */
|
|
224
|
+
readonly sourceLength: number;
|
|
225
|
+
/** UTF-16 start of the source text in the target before the operation. */
|
|
226
|
+
readonly targetOffset: number;
|
|
227
|
+
}
|
|
217
228
|
export interface EditReceipt {
|
|
218
229
|
readonly sessionId: string;
|
|
219
230
|
/** `state.revision` after the call; unchanged for a dry run or a no-op. */
|
|
@@ -226,6 +237,8 @@ export interface EditReceipt {
|
|
|
226
237
|
readonly removedIds: readonly string[];
|
|
227
238
|
/** Old id → new id, when a format has to rename an element; absent for formats that never do. */
|
|
228
239
|
readonly remappedIds?: Readonly<Record<string, string>>;
|
|
240
|
+
/** Text anchors migrated by an apply (or preview); absent when none migrated. */
|
|
241
|
+
readonly textAnchorMigrations?: readonly TextAnchorMigration[];
|
|
229
242
|
/** Page indexes in the resulting document whose content may have changed; a superset. */
|
|
230
243
|
readonly changedPages: readonly number[];
|
|
231
244
|
readonly pageCount: number;
|
package/dist/viewport.js
CHANGED
|
@@ -155,6 +155,15 @@ export class ViewerViewport {
|
|
|
155
155
|
this.schedule();
|
|
156
156
|
}
|
|
157
157
|
replaceDocument(info, changedPages, revision) {
|
|
158
|
+
// The viewer retires the old adapter after this synchronous handoff.
|
|
159
|
+
// Unfinished paints still belong to that adapter, even on unchanged pages.
|
|
160
|
+
for (const slot of this.#slots.values()) {
|
|
161
|
+
if (!slot.controller)
|
|
162
|
+
continue;
|
|
163
|
+
slot.controller.abort();
|
|
164
|
+
delete slot.controller;
|
|
165
|
+
delete slot.renderKey;
|
|
166
|
+
}
|
|
158
167
|
const previous = this.#info;
|
|
159
168
|
const painted = this.#painted.get(this.#contentRevision);
|
|
160
169
|
this.#painted.clear();
|
|
@@ -533,6 +542,8 @@ export class ViewerViewport {
|
|
|
533
542
|
await rendering?.catch(() => undefined);
|
|
534
543
|
canvas.width = 0;
|
|
535
544
|
canvas.height = 0;
|
|
545
|
+
if (slot.controller === controller)
|
|
546
|
+
delete slot.controller;
|
|
536
547
|
}
|
|
537
548
|
}
|
|
538
549
|
async #buildTextLayers(textLayer, highlightLayer, pageIndex, runs, zoom, signal) {
|
|
@@ -2,7 +2,7 @@ import type { DocumentFormat, DocumentInfo, ResourceLimits, TextRun, ViewerError
|
|
|
2
2
|
import type { EditableFormat } from "./edit/types.js";
|
|
3
3
|
export type DocumentWorkerOperation = "init" | "open" | "get-info" | "render" | "get-text-map" | "close" | "destroy";
|
|
4
4
|
/** Operations of an edit engine worker; payloads mirror the engine interface. */
|
|
5
|
-
export type EditWorkerOperation = "edit-init" | "edit-open" | "edit-validate" | "edit-apply" | "edit-materialize" | "edit-restore" | "edit-put-asset" | "edit-elements" | "edit-element" | "edit-elements-at" | "edit-find-text" | "edit-text-layout" | "edit-text-font" | "edit-position-at" | "edit-range-rects" | "edit-render-without" | "edit-page-layout" | "edit-pptx-slides" | "edit-pptx-layouts" | "edit-docx-revisions" | "edit-docx-text-style" | "edit-pptx-text-style" | "edit-dispose";
|
|
5
|
+
export type EditWorkerOperation = "edit-init" | "edit-open" | "edit-validate" | "edit-apply" | "edit-materialize" | "edit-restore" | "edit-put-asset" | "edit-elements" | "edit-element" | "edit-elements-at" | "edit-find-text" | "edit-text-layout" | "edit-text-paragraph" | "edit-text-font" | "edit-position-at" | "edit-range-rects" | "edit-render-without" | "edit-page-layout" | "edit-pptx-slides" | "edit-pptx-layouts" | "edit-docx-revisions" | "edit-docx-text-style" | "edit-pptx-text-style" | "edit-dispose";
|
|
6
6
|
export type WorkerOperation = DocumentWorkerOperation | EditWorkerOperation;
|
|
7
7
|
export interface WorkerRequest {
|
|
8
8
|
readonly kind: "request";
|