web-doc 0.10.0 → 0.11.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/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/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/worker-protocol.d.ts +1 -1
- package/dist/workers/ooxml-edit-worker.js +90 -2
- package/package.json +1 -1
package/dist/adapters/office.js
CHANGED
|
@@ -86,7 +86,6 @@ export class OfficeDocumentAdapter {
|
|
|
86
86
|
useGoogleFonts: false,
|
|
87
87
|
maxZipEntryBytes: context.limits.maxZipEntryBytes,
|
|
88
88
|
mode: "main",
|
|
89
|
-
...(reopening ? { progressiveLayout: true } : {}),
|
|
90
89
|
};
|
|
91
90
|
try {
|
|
92
91
|
const kind = kindFor(format);
|
|
@@ -113,7 +112,10 @@ export class OfficeDocumentAdapter {
|
|
|
113
112
|
}
|
|
114
113
|
const buffer = exactArrayBuffer(data);
|
|
115
114
|
if (kind === "presentation") {
|
|
116
|
-
const backend = await this.#loadPptx(buffer,
|
|
115
|
+
const backend = await this.#loadPptx(buffer, {
|
|
116
|
+
...engineOptions,
|
|
117
|
+
...(reopening ? { progressiveLayout: true } : {}),
|
|
118
|
+
});
|
|
117
119
|
throwIfAborted(context.signal, backend);
|
|
118
120
|
context.reportProgress({
|
|
119
121
|
phase: "parsing",
|
|
@@ -3,10 +3,12 @@ import type { BatchMode, EditEngine, EngineBatch, EngineChange, MaterializedDocu
|
|
|
3
3
|
import { OoxmlPackage } from "../ooxml/package.js";
|
|
4
4
|
import type { EditFindOptions, EditOperation, ElementQuery, OperationIssue, PagePoint, TextTarget } from "../types.js";
|
|
5
5
|
import { DocxModel, type AnyRecord } from "./model.js";
|
|
6
|
-
import type { DocxElement, DocxRevision } from "./types.js";
|
|
6
|
+
import type { DocxElement, DocxRevision, DocxTextStyle } from "./types.js";
|
|
7
|
+
import { type TextSpan } from "../range-style.js";
|
|
7
8
|
/** Reads the DOCX session adds on top of the core, served by the engine and the worker client alike. */
|
|
8
9
|
export interface DocxEngineReads {
|
|
9
10
|
revisions(id: string, signal: AbortSignal): Promise<readonly DocxRevision[]>;
|
|
11
|
+
textStyle(id: string, span: TextSpan | undefined, signal: AbortSignal): Promise<Partial<DocxTextStyle> | undefined>;
|
|
10
12
|
}
|
|
11
13
|
export declare class DocxEditEngine implements EditEngine, DocxEngineReads {
|
|
12
14
|
#private;
|
|
@@ -25,6 +27,12 @@ export declare class DocxEditEngine implements EditEngine, DocxEngineReads {
|
|
|
25
27
|
apply(input: EngineBatch | readonly EditOperation[], signal: AbortSignal): Promise<EngineChange>;
|
|
26
28
|
/** The revisions of a paragraph; none for another element or an unknown id. */
|
|
27
29
|
revisions(id: string, signal: AbortSignal): Promise<readonly DocxRevision[]>;
|
|
30
|
+
/**
|
|
31
|
+
* The style a span of a paragraph shows: what every run it covers shares;
|
|
32
|
+
* the paragraph mark's for an empty paragraph. None for another element,
|
|
33
|
+
* an unknown id or a span past the text.
|
|
34
|
+
*/
|
|
35
|
+
textStyle(id: string, span: TextSpan | undefined, signal: AbortSignal): Promise<Partial<DocxTextStyle> | undefined>;
|
|
28
36
|
materialize(purposeOrSignal?: "show" | "save" | AbortSignal, options?: MaterializeOptions, signal?: AbortSignal): Promise<Uint8Array>;
|
|
29
37
|
/**
|
|
30
38
|
* `save` is the package as the session changed it: ids written only on
|
package/dist/edit/docx/engine.js
CHANGED
|
@@ -7,9 +7,11 @@ import { NO_PAGE, toElement } from "./elements.js";
|
|
|
7
7
|
import { docxHandlers } from "./handlers.js";
|
|
8
8
|
import { freshParagraphId, paragraphsOf } from "./ids.js";
|
|
9
9
|
import { DocxModel } from "./model.js";
|
|
10
|
+
import { resolveTextStyle } from "./style.js";
|
|
10
11
|
import { issueCollector, } from "./operations.js";
|
|
11
12
|
import { docxOperationSchemas } from "./schemas.js";
|
|
12
13
|
import { revisionsOf } from "./tracked.js";
|
|
14
|
+
import { runsCovering, sharedStyle, spanFits, } from "../range-style.js";
|
|
13
15
|
import { attributeProblem, namespacePatches } from "./write.js";
|
|
14
16
|
/*
|
|
15
17
|
* The DOCX edit engine: the package layer under a block index of the body
|
|
@@ -303,6 +305,26 @@ export class DocxEditEngine {
|
|
|
303
305
|
return [];
|
|
304
306
|
return revisionsOf(model.document, record.node);
|
|
305
307
|
}
|
|
308
|
+
/**
|
|
309
|
+
* The style a span of a paragraph shows: what every run it covers shares;
|
|
310
|
+
* the paragraph mark's for an empty paragraph. None for another element,
|
|
311
|
+
* an unknown id or a span past the text.
|
|
312
|
+
*/
|
|
313
|
+
async textStyle(id, span, signal) {
|
|
314
|
+
const model = await this.model(signal);
|
|
315
|
+
const record = model.byId.get(id);
|
|
316
|
+
if (!record || record.kind !== "paragraph")
|
|
317
|
+
return undefined;
|
|
318
|
+
const { text, items } = record.text;
|
|
319
|
+
const range = span ?? { start: 0, end: text.length };
|
|
320
|
+
if (!spanFits(range, text.length))
|
|
321
|
+
return undefined;
|
|
322
|
+
const covered = runsCovering(items, range);
|
|
323
|
+
const mark = record.pPr?.children.find((child) => child.local === "rPr");
|
|
324
|
+
return sharedStyle(covered.length > 0
|
|
325
|
+
? covered.map((item) => resolveTextStyle(model.styles, record.pPr, item.rPr))
|
|
326
|
+
: [resolveTextStyle(model.styles, record.pPr, mark)]);
|
|
327
|
+
}
|
|
306
328
|
async materialize(purposeOrSignal = "show", options = {}, signal = new AbortController().signal) {
|
|
307
329
|
return (await this.materializeDocument(purposeOrSignal, options, signal))
|
|
308
330
|
.bytes;
|
|
@@ -2,7 +2,8 @@ import type { EditEngineContext } from "../engine.js";
|
|
|
2
2
|
import { type OoxmlEditProviderOptions } from "../ooxml/worker.js";
|
|
3
3
|
import { WorkerEngineClient } from "../worker-engine.js";
|
|
4
4
|
import type { DocxEngineReads } from "./engine.js";
|
|
5
|
-
import type {
|
|
5
|
+
import type { TextSpan } from "../range-style.js";
|
|
6
|
+
import type { DocxRevision, DocxTextStyle } from "./types.js";
|
|
6
7
|
export type DocxEditProviderOptions = OoxmlEditProviderOptions;
|
|
7
8
|
/**
|
|
8
9
|
* Starts the OOXML edit worker for `original` as a DOCX engine. Loaded
|
|
@@ -12,5 +13,6 @@ export declare function loadDocxEditEngine(original: Uint8Array, context: EditEn
|
|
|
12
13
|
export declare class DocxEditEngineClient extends WorkerEngineClient implements DocxEngineReads {
|
|
13
14
|
readonly schemas: import("../types.js").OperationSchemaSet;
|
|
14
15
|
revisions(id: string, signal: AbortSignal): Promise<readonly DocxRevision[]>;
|
|
16
|
+
textStyle(id: string, span: TextSpan | undefined, signal: AbortSignal): Promise<Partial<DocxTextStyle> | undefined>;
|
|
15
17
|
start(original: Uint8Array): Promise<void>;
|
|
16
18
|
}
|
|
@@ -24,6 +24,9 @@ export class DocxEditEngineClient extends WorkerEngineClient {
|
|
|
24
24
|
revisions(id, signal) {
|
|
25
25
|
return this.request("edit-docx-revisions", { id }, signal);
|
|
26
26
|
}
|
|
27
|
+
textStyle(id, span, signal) {
|
|
28
|
+
return this.request("edit-docx-text-style", { id, span }, signal);
|
|
29
|
+
}
|
|
27
30
|
async start(original) {
|
|
28
31
|
const data = original.slice().buffer;
|
|
29
32
|
const open = {
|
|
@@ -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, PageRect, ReadItem, ReadOptions, ReadResult, SavedDocument, TextTarget } from "../types.js";
|
|
4
|
-
import type { DocxDeleteElementOperation, DocxEditSession, DocxElement, DocxFields, DocxInsertImageOperation, DocxInsertParagraphOperation, DocxInsertTableOperation, DocxMoveElementOperation, DocxOperation, DocxReplaceTextOperation, DocxRevision, DocxSaveOptions, DocxSetParagraphStyleOperation, DocxSetTableCellOperation, DocxSetTextStyleOperation } from "./types.js";
|
|
3
|
+
import type { ApplyOptions, AssetOptions, EditFindOptions, EditOperation, EditReceipt, EditState, ElementQuery, HistoryOptions, OperationSchemaSet, PagePoint, PageRect, ReadItem, ReadOptions, ReadResult, SavedDocument, TextRange, TextTarget } from "../types.js";
|
|
4
|
+
import type { DocxDeleteElementOperation, DocxEditSession, DocxElement, DocxFields, DocxInsertImageOperation, DocxInsertParagraphOperation, DocxInsertTableOperation, DocxMoveElementOperation, DocxOperation, DocxReplaceTextOperation, DocxRevision, DocxSaveOptions, DocxSetParagraphStyleOperation, DocxSetTableCellOperation, DocxSetTextStyleOperation, DocxTextStyle } from "./types.js";
|
|
5
5
|
/** Per paragraph id (eight hex digits), the union of its runs per page. */
|
|
6
6
|
type Placement = ReadonlyMap<string, ReadonlyMap<number, PageRect>>;
|
|
7
7
|
export declare class DocxSession implements DocxEditSession {
|
|
@@ -33,6 +33,10 @@ export declare class DocxSession implements DocxEditSession {
|
|
|
33
33
|
get tools(): ToolSet;
|
|
34
34
|
callTool(call: ToolCall, options?: ToolCallOptions): Promise<ToolResult>;
|
|
35
35
|
getRevisions(elementId: string, options?: ReadOptions): Promise<ReadResult<DocxRevision>>;
|
|
36
|
+
getTextStyle(fields: {
|
|
37
|
+
readonly target: string;
|
|
38
|
+
readonly range?: TextRange;
|
|
39
|
+
}, options?: ReadOptions): Promise<ReadItem<Partial<DocxTextStyle>>>;
|
|
36
40
|
replaceText(fields: DocxFields<DocxReplaceTextOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
37
41
|
setTextStyle(fields: DocxFields<DocxSetTextStyleOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
38
42
|
setParagraphStyle(fields: DocxFields<DocxSetParagraphStyleOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
@@ -2,6 +2,7 @@ import { readDescription, readOutline } from "../ai/outline.js";
|
|
|
2
2
|
import { resolveTargets } from "../ai/targets.js";
|
|
3
3
|
import { buildToolSet, callTool as runTool } from "../ai/tools.js";
|
|
4
4
|
import { ViewerError } from "../../errors.js";
|
|
5
|
+
import { spanOnTarget } from "../range-style.js";
|
|
5
6
|
export class DocxSession {
|
|
6
7
|
format = "docx";
|
|
7
8
|
#core;
|
|
@@ -134,6 +135,10 @@ export class DocxSession {
|
|
|
134
135
|
getRevisions(elementId, options) {
|
|
135
136
|
return this.#core.readItems(options, (engine, signal) => docxReads(engine).revisions(elementId, signal));
|
|
136
137
|
}
|
|
138
|
+
async getTextStyle(fields, options) {
|
|
139
|
+
const span = spanOnTarget(fields.target, fields.range);
|
|
140
|
+
return this.#core.readItem(options, (engine, signal) => docxReads(engine).textStyle(fields.target, span, signal));
|
|
141
|
+
}
|
|
137
142
|
replaceText(fields, options) {
|
|
138
143
|
return this.apply([{ op: "replaceText", ...fields }], options);
|
|
139
144
|
}
|
|
@@ -461,7 +466,8 @@ function matchesQuery(element, query) {
|
|
|
461
466
|
}
|
|
462
467
|
function docxReads(engine) {
|
|
463
468
|
const candidate = engine;
|
|
464
|
-
if (typeof candidate.revisions !== "function"
|
|
469
|
+
if (typeof candidate.revisions !== "function" ||
|
|
470
|
+
typeof candidate.textStyle !== "function")
|
|
465
471
|
throw new ViewerError("edit-unsupported", "The engine does not provide the DOCX reads", { details: { format: "docx", reason: "no-reads" } });
|
|
466
472
|
return candidate;
|
|
467
473
|
}
|
|
@@ -34,7 +34,7 @@ export declare class DocxStyles {
|
|
|
34
34
|
/** A style and the styles it is based on, nearest first; cycles stop. */
|
|
35
35
|
chain(id: string | undefined): StyleRecord[];
|
|
36
36
|
}
|
|
37
|
-
/** `w:b
|
|
37
|
+
/** `w:b`/`w:i` toggle values: a present element without `w:val` means on. */
|
|
38
38
|
export declare function isOn(value: string | undefined): boolean;
|
|
39
39
|
/** The run property holders in resolution order for a run of a paragraph. */
|
|
40
40
|
export declare function runPropertySources(styles: DocxStyles, pPr: XmlElement | undefined, rPr: XmlElement | undefined): readonly XmlElement[];
|
package/dist/edit/docx/style.js
CHANGED
|
@@ -95,11 +95,12 @@ export class DocxStyles {
|
|
|
95
95
|
if (!id)
|
|
96
96
|
continue;
|
|
97
97
|
const basedOn = part.attribute(node.children.find((child) => child.local === "basedOn") ?? node, "w:val");
|
|
98
|
+
const defaultAttribute = part.attribute(node, "w:default");
|
|
98
99
|
const record = {
|
|
99
100
|
id,
|
|
100
101
|
type: part.attribute(node, "w:type") ?? "paragraph",
|
|
101
102
|
...(basedOn && basedOn !== id ? { basedOn } : {}),
|
|
102
|
-
isDefault: isOn(
|
|
103
|
+
isDefault: defaultAttribute !== undefined && isOn(defaultAttribute),
|
|
103
104
|
...pick(node, "rPr", "pPr"),
|
|
104
105
|
};
|
|
105
106
|
styles.set(id, record);
|
|
@@ -175,7 +176,7 @@ async function readTheme(pkg, themePart, signal) {
|
|
|
175
176
|
return none;
|
|
176
177
|
}
|
|
177
178
|
}
|
|
178
|
-
/** `w:b
|
|
179
|
+
/** `w:b`/`w:i` toggle values: a present element without `w:val` means on. */
|
|
179
180
|
export function isOn(value) {
|
|
180
181
|
return value === undefined || !/^(0|false|off)$/i.test(value);
|
|
181
182
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ApplyOptions, BinaryData, EditColor, EditElement, EditReceipt, EditSessionBase, ReadOptions, ReadResult, SavedDocument, SaveOptions, TextRange } from "../types.js";
|
|
1
|
+
import type { ApplyOptions, BinaryData, EditColor, EditElement, EditReceipt, EditSessionBase, ReadItem, ReadOptions, ReadResult, SavedDocument, SaveOptions, TextRange } from "../types.js";
|
|
2
2
|
export type DocxElementKind = "paragraph" | "table" | "image" | "other";
|
|
3
3
|
export type DocxTextAlign = "left" | "center" | "right" | "justify";
|
|
4
4
|
/** Word's sixteen highlight colours (`w:highlight/@w:val`). */
|
|
@@ -193,6 +193,16 @@ export interface DocxEditSession extends EditSessionBase<DocxOperation, DocxElem
|
|
|
193
193
|
save(options?: DocxSaveOptions): Promise<SavedDocument>;
|
|
194
194
|
/** The tracked changes a paragraph holds, in document order; empty for other elements. */
|
|
195
195
|
getRevisions(elementId: string, options?: ReadOptions): Promise<ReadResult<DocxRevision>>;
|
|
196
|
+
/**
|
|
197
|
+
* The text style a range of a paragraph shows: each property every run it
|
|
198
|
+
* covers shares, a property they differ on left out. Without a range, the
|
|
199
|
+
* whole text; a collapsed range reads the run before it, whose style text
|
|
200
|
+
* typed there takes. `undefined` for an element without text.
|
|
201
|
+
*/
|
|
202
|
+
getTextStyle(fields: {
|
|
203
|
+
readonly target: string;
|
|
204
|
+
readonly range?: TextRange;
|
|
205
|
+
}, options?: ReadOptions): Promise<ReadItem<Partial<DocxTextStyle>>>;
|
|
196
206
|
/** Replaces the whole text of a paragraph, or the part a range covers. */
|
|
197
207
|
replaceText(fields: DocxFields<DocxReplaceTextOperation>, options?: ApplyOptions): Promise<EditReceipt>;
|
|
198
208
|
/** Changes run properties; unspecified ones keep their bytes. */
|
|
@@ -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
|
+
}
|
|
@@ -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-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-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";
|
|
@@ -2166,11 +2166,12 @@ var DocxStyles = class _DocxStyles {
|
|
|
2166
2166
|
node.children.find((child) => child.local === "basedOn") ?? node,
|
|
2167
2167
|
"w:val"
|
|
2168
2168
|
);
|
|
2169
|
+
const defaultAttribute = part.attribute(node, "w:default");
|
|
2169
2170
|
const record = {
|
|
2170
2171
|
id,
|
|
2171
2172
|
type: part.attribute(node, "w:type") ?? "paragraph",
|
|
2172
2173
|
...basedOn && basedOn !== id ? { basedOn } : {},
|
|
2173
|
-
isDefault: isOn(
|
|
2174
|
+
isDefault: defaultAttribute !== void 0 && isOn(defaultAttribute),
|
|
2174
2175
|
...pick(node, "rPr", "pPr")
|
|
2175
2176
|
};
|
|
2176
2177
|
styles.set(id, record);
|
|
@@ -5329,6 +5330,31 @@ function issueCollector(operationIndex, issues) {
|
|
|
5329
5330
|
return (path, code, message) => issues.push({ operationIndex, path, code, message });
|
|
5330
5331
|
}
|
|
5331
5332
|
|
|
5333
|
+
// src/edit/range-style.ts
|
|
5334
|
+
function runsCovering(runs, span) {
|
|
5335
|
+
if (span.start < span.end)
|
|
5336
|
+
return runs.filter((run2) => run2.start < span.end && run2.end > span.start);
|
|
5337
|
+
const before = runs.filter((run2) => run2.start < span.start && run2.end >= span.start).at(-1);
|
|
5338
|
+
const at = before ?? runs.find((run2) => run2.start >= span.start);
|
|
5339
|
+
return at ? [at] : [];
|
|
5340
|
+
}
|
|
5341
|
+
function sharedStyle(styles) {
|
|
5342
|
+
const [first, ...rest] = styles;
|
|
5343
|
+
if (!first) return void 0;
|
|
5344
|
+
const shared = {};
|
|
5345
|
+
for (const key of Object.keys(first)) {
|
|
5346
|
+
const value = JSON.stringify(first[key]);
|
|
5347
|
+
if (rest.every(
|
|
5348
|
+
(style) => key in style && JSON.stringify(style[key]) === value
|
|
5349
|
+
))
|
|
5350
|
+
shared[key] = first[key];
|
|
5351
|
+
}
|
|
5352
|
+
return shared;
|
|
5353
|
+
}
|
|
5354
|
+
function spanFits(span, length) {
|
|
5355
|
+
return span.start >= 0 && span.start <= span.end && span.end <= length;
|
|
5356
|
+
}
|
|
5357
|
+
|
|
5332
5358
|
// src/edit/docx/engine.ts
|
|
5333
5359
|
var UNAUTHORED_PART = "/webdoc/unauthored.xml";
|
|
5334
5360
|
var UNAUTHORED_NS = "urn:web-doc:docx-edit";
|
|
@@ -5602,6 +5628,26 @@ var DocxEditEngine = class _DocxEditEngine {
|
|
|
5602
5628
|
if (!record || record.kind !== "paragraph") return [];
|
|
5603
5629
|
return revisionsOf(model.document, record.node);
|
|
5604
5630
|
}
|
|
5631
|
+
/**
|
|
5632
|
+
* The style a span of a paragraph shows: what every run it covers shares;
|
|
5633
|
+
* the paragraph mark's for an empty paragraph. None for another element,
|
|
5634
|
+
* an unknown id or a span past the text.
|
|
5635
|
+
*/
|
|
5636
|
+
async textStyle(id, span, signal) {
|
|
5637
|
+
const model = await this.model(signal);
|
|
5638
|
+
const record = model.byId.get(id);
|
|
5639
|
+
if (!record || record.kind !== "paragraph") return void 0;
|
|
5640
|
+
const { text, items } = record.text;
|
|
5641
|
+
const range = span ?? { start: 0, end: text.length };
|
|
5642
|
+
if (!spanFits(range, text.length)) return void 0;
|
|
5643
|
+
const covered = runsCovering(items, range);
|
|
5644
|
+
const mark = record.pPr?.children.find((child) => child.local === "rPr");
|
|
5645
|
+
return sharedStyle(
|
|
5646
|
+
covered.length > 0 ? covered.map(
|
|
5647
|
+
(item) => resolveTextStyle(model.styles, record.pPr, item.rPr)
|
|
5648
|
+
) : [resolveTextStyle(model.styles, record.pPr, mark)]
|
|
5649
|
+
);
|
|
5650
|
+
}
|
|
5605
5651
|
async materialize(purposeOrSignal = "show", options = {}, signal = new AbortController().signal) {
|
|
5606
5652
|
return (await this.materializeDocument(purposeOrSignal, options, signal)).bytes;
|
|
5607
5653
|
}
|
|
@@ -6469,7 +6515,10 @@ function readRecord(context, node, parents, parent, readOnly, walk) {
|
|
|
6469
6515
|
...spPr ? { spPr } : {},
|
|
6470
6516
|
...text ? { text } : {},
|
|
6471
6517
|
readOnly,
|
|
6472
|
-
...parent ? { parent } : {}
|
|
6518
|
+
...parent ? { parent } : {},
|
|
6519
|
+
...text && txBody ? {
|
|
6520
|
+
styleOf: (span) => rangeStyleOf(context, placeholder, txBody, text, span)
|
|
6521
|
+
} : {}
|
|
6473
6522
|
};
|
|
6474
6523
|
}
|
|
6475
6524
|
function operationsFor(kind, hasText) {
|
|
@@ -6513,6 +6562,29 @@ function listStylesOf(context, txBody, placeholder) {
|
|
|
6513
6562
|
if (defaults) lists.push(defaults);
|
|
6514
6563
|
return lists;
|
|
6515
6564
|
}
|
|
6565
|
+
function rangeStyleOf(context, placeholder, txBody, text, span) {
|
|
6566
|
+
const lists = listStylesOf(context, txBody, placeholder);
|
|
6567
|
+
const styleOf = (paragraph2, rPr) => resolveTextStyle2({
|
|
6568
|
+
...rPr ? { rPr: { part: context.part, node: rPr } } : {},
|
|
6569
|
+
...paragraph2.pPr ? { pPr: { part: context.part, node: paragraph2.pPr } } : {},
|
|
6570
|
+
level: paragraph2.level,
|
|
6571
|
+
lists,
|
|
6572
|
+
fonts: context.fonts
|
|
6573
|
+
});
|
|
6574
|
+
const runs = text.paragraphs.flatMap(
|
|
6575
|
+
(paragraph2) => paragraph2.runs.map((run2) => ({
|
|
6576
|
+
start: run2.start,
|
|
6577
|
+
end: run2.end,
|
|
6578
|
+
style: () => styleOf(paragraph2, run2.rPr)
|
|
6579
|
+
}))
|
|
6580
|
+
);
|
|
6581
|
+
const covered = runsCovering(runs, span);
|
|
6582
|
+
if (covered.length > 0) return sharedStyle(covered.map((run2) => run2.style()));
|
|
6583
|
+
const paragraph = text.paragraphs.find(
|
|
6584
|
+
(candidate) => candidate.start <= span.start && span.start <= candidate.end
|
|
6585
|
+
) ?? text.paragraphs[0];
|
|
6586
|
+
return paragraph ? sharedStyle([styleOf(paragraph, paragraph.endParaRPr)]) : void 0;
|
|
6587
|
+
}
|
|
6516
6588
|
function textStyleOf(context, placeholder, txBody, text) {
|
|
6517
6589
|
const first = firstTextRun(text);
|
|
6518
6590
|
const paragraph = first?.paragraph ?? text.paragraphs[0];
|
|
@@ -8918,6 +8990,14 @@ var PptxEditEngine = class _PptxEditEngine {
|
|
|
8918
8990
|
});
|
|
8919
8991
|
return out;
|
|
8920
8992
|
}
|
|
8993
|
+
/** The style a span of a shape's text shows; none for an element without text or a span past it. */
|
|
8994
|
+
async textStyle(id, span, signal) {
|
|
8995
|
+
const record = await this.#locate(id, signal);
|
|
8996
|
+
if (!record?.styleOf || !record.text) return void 0;
|
|
8997
|
+
const range = span ?? { start: 0, end: record.text.text.length };
|
|
8998
|
+
if (!spanFits(range, record.text.text.length)) return void 0;
|
|
8999
|
+
return record.styleOf(range);
|
|
9000
|
+
}
|
|
8921
9001
|
async layouts(signal) {
|
|
8922
9002
|
const model = await this.model(signal);
|
|
8923
9003
|
return model.layouts.map((layout) => ({
|
|
@@ -9136,6 +9216,14 @@ function createOoxmlEditHandler() {
|
|
|
9136
9216
|
payload.id,
|
|
9137
9217
|
signal
|
|
9138
9218
|
);
|
|
9219
|
+
case "edit-docx-text-style": {
|
|
9220
|
+
const { id, span } = payload;
|
|
9221
|
+
return docx().textStyle(id, span, signal);
|
|
9222
|
+
}
|
|
9223
|
+
case "edit-pptx-text-style": {
|
|
9224
|
+
const { id, span } = payload;
|
|
9225
|
+
return pptx().textStyle(id, span, signal);
|
|
9226
|
+
}
|
|
9139
9227
|
case "edit-dispose":
|
|
9140
9228
|
await state?.dispose();
|
|
9141
9229
|
state = void 0;
|