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.
Files changed (58) hide show
  1. package/dist/adapters/office.js +4 -2
  2. package/dist/adapters/pdf.js +19 -8
  3. package/dist/edit/docx/engine.d.ts +9 -1
  4. package/dist/edit/docx/engine.js +22 -0
  5. package/dist/edit/docx/provider.d.ts +3 -1
  6. package/dist/edit/docx/provider.js +3 -0
  7. package/dist/edit/docx/session.d.ts +6 -2
  8. package/dist/edit/docx/session.js +7 -1
  9. package/dist/edit/docx/style.d.ts +1 -1
  10. package/dist/edit/docx/style.js +3 -2
  11. package/dist/edit/docx/types.d.ts +11 -1
  12. package/dist/edit/engine.d.ts +2 -1
  13. package/dist/edit/pdf/engine/document.d.ts +4 -2
  14. package/dist/edit/pdf/engine/document.js +115 -12
  15. package/dist/edit/pdf/engine/elements.d.ts +5 -1
  16. package/dist/edit/pdf/engine/elements.js +88 -6
  17. package/dist/edit/pdf/engine/existing-text.js +16 -61
  18. package/dist/edit/pdf/engine/fonts.d.ts +14 -0
  19. package/dist/edit/pdf/engine/fonts.js +114 -0
  20. package/dist/edit/pdf/engine/handler.js +2 -0
  21. package/dist/edit/pdf/engine/layout.js +1 -1
  22. package/dist/edit/pdf/engine/operations.d.ts +7 -1
  23. package/dist/edit/pdf/engine/paragraph-edit.d.ts +4 -0
  24. package/dist/edit/pdf/engine/paragraph-edit.js +331 -0
  25. package/dist/edit/pdf/engine/paragraph.d.ts +31 -0
  26. package/dist/edit/pdf/engine/paragraph.js +445 -0
  27. package/dist/edit/pdf/engine/pdfium.d.ts +3 -0
  28. package/dist/edit/pdf/engine/text-box.js +16 -3
  29. package/dist/edit/pdf/engine/text-font.js +2 -19
  30. package/dist/edit/pdf/engine/transform.js +14 -3
  31. package/dist/edit/pdf/provider.d.ts +3 -1
  32. package/dist/edit/pdf/provider.js +3 -0
  33. package/dist/edit/pdf/range-map.d.ts +2 -1
  34. package/dist/edit/pdf/range-map.js +38 -8
  35. package/dist/edit/pdf/schemas.d.ts +2 -0
  36. package/dist/edit/pdf/schemas.js +37 -0
  37. package/dist/edit/pdf/session.d.ts +3 -1
  38. package/dist/edit/pdf/session.js +35 -6
  39. package/dist/edit/pdf/types.d.ts +38 -8
  40. package/dist/edit/pptx/elements.d.ts +4 -1
  41. package/dist/edit/pptx/elements.js +35 -0
  42. package/dist/edit/pptx/engine.d.ts +5 -1
  43. package/dist/edit/pptx/engine.js +11 -0
  44. package/dist/edit/pptx/handler.js +8 -0
  45. package/dist/edit/pptx/provider.d.ts +3 -1
  46. package/dist/edit/pptx/provider.js +3 -0
  47. package/dist/edit/pptx/session.d.ts +6 -2
  48. package/dist/edit/pptx/session.js +7 -1
  49. package/dist/edit/pptx/types.d.ts +11 -1
  50. package/dist/edit/range-style.d.ts +24 -0
  51. package/dist/edit/range-style.js +46 -0
  52. package/dist/edit/session.js +5 -0
  53. package/dist/edit/types.d.ts +13 -0
  54. package/dist/viewport.js +11 -0
  55. package/dist/worker-protocol.d.ts +1 -1
  56. package/dist/workers/ooxml-edit-worker.js +90 -2
  57. package/dist/workers/pdf-edit-worker.js +2019 -1003
  58. package/package.json +1 -1
@@ -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-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";
@@ -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(part.attribute(node, "w:default")),
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;