wpd-codec 3.1.1 → 3.2.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/read.cjs CHANGED
@@ -10,6 +10,8 @@ const require_stream_eol = require("./stream/eol.cjs");
10
10
  const require_stream_page = require("./stream/page.cjs");
11
11
  const require_stream_style = require("./stream/style.cjs");
12
12
  const require_stream_table = require("./stream/table.cjs");
13
+ const require_stream_box = require("./stream/box.cjs");
14
+ const require_stream_formula = require("./stream/formula.cjs");
13
15
  const require_stream_tab = require("./stream/tab.cjs");
14
16
  const require_stream_tokenise = require("./stream/tokenise.cjs");
15
17
  let document_schema_js = require("document-schema.js");
@@ -30,6 +32,8 @@ const CROSS_REFERENCE_GROUP = 213;
30
32
  const HEADER_FOOTER_GROUP = 214;
31
33
  const FOOTNOTE_ENDNOTE_GROUP = 215;
32
34
  const MERGE_GROUP = 222;
35
+ const MERGE_FIELD_ON = 76;
36
+ const MERGE_FIELD_OFF = 77;
33
37
  const BOX_GROUP = 223;
34
38
  const PARAGRAPH_GROUP = 211;
35
39
  const PARAGRAPH_SET_JUSTIFICATION = 5;
@@ -49,6 +53,7 @@ const JUSTIFICATION = [
49
53
  const THREE_THOUSAND_SIX_HUNDREDTHS_PER_POINT = 50;
50
54
  const COLOR_COMPONENT_MAX = 255;
51
55
  const NO_SPAN = 1;
56
+ const MAX_STYLE_RESOLUTION_DEPTH = 16;
52
57
  function sameAttributes(a, b) {
53
58
  return a.bold === b.bold && a.italic === b.italic && a.underline === b.underline && a.strike === b.strike;
54
59
  }
@@ -80,29 +85,35 @@ function flushRun(state) {
80
85
  function targetBlocks(state) {
81
86
  return state.table === void 0 ? state.blocks : state.table.cellBlocks;
82
87
  }
83
- function flushParagraph(state) {
88
+ function flushParagraph(state, sink) {
84
89
  flushRun(state);
90
+ if (state.openMergeFieldStartRun !== void 0) {
91
+ state.openMergeFieldStartRun = void 0;
92
+ reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.MergeFieldSpansParagraphs, "A merge field's own On/Off pair straddled a paragraph boundary, which the run-scoped field construct cannot express; its text became ordinary paragraph text with no field tag.");
93
+ }
85
94
  const alignment = state.pendingAlignment ?? state.alignment;
95
+ const constructs = state.pendingConstructs.splice(0, state.pendingConstructs.length);
86
96
  const paragraph = {
87
97
  kind: "paragraph",
88
98
  runs: state.runs.splice(0, state.runs.length),
89
99
  ...alignment === void 0 ? {} : { alignment },
90
100
  ...state.pendingHeadingLevel === void 0 ? {} : { headingLevel: state.pendingHeadingLevel },
91
- ...state.pendingListLevel === void 0 ? {} : { list: { level: state.pendingListLevel } }
101
+ ...state.pendingListLevel === void 0 ? {} : { list: { level: state.pendingListLevel } },
102
+ ...constructs.length === 0 ? {} : { constructs }
92
103
  };
93
104
  state.pendingHeadingLevel = void 0;
94
105
  state.pendingListLevel = void 0;
95
106
  state.pendingAlignment = void 0;
96
107
  targetBlocks(state).push(paragraph);
97
108
  }
98
- function flushParagraphIfContent(state) {
109
+ function flushParagraphIfContent(state, sink) {
99
110
  if (state.text.length === 0 && state.runs.length === 0) return;
100
- flushParagraph(state);
111
+ flushParagraph(state, sink);
101
112
  }
102
113
  function effectiveStyle(state) {
103
114
  for (let index = state.styleScopes.length - 1; index >= 0; index -= 1) {
104
- const scope = state.styleScopes[index];
105
- if (scope !== void 0) return scope;
115
+ const semantics = state.styleScopes[index]?.semantics;
116
+ if (semantics !== void 0) return semantics;
106
117
  }
107
118
  }
108
119
  function appendText(state, text) {
@@ -123,6 +134,9 @@ function readCellAttributes(state, nonDeletable, sink) {
123
134
  const spanning = require_stream_table.findEmbeddedSubfunction(subfunctions, 133);
124
135
  const fill = require_stream_table.findEmbeddedSubfunction(subfunctions, 134);
125
136
  const row = require_stream_table.findEmbeddedSubfunction(subfunctions, 128);
137
+ const formulaData = require_stream_table.findEmbeddedSubfunction(subfunctions, 129);
138
+ const formula = formulaData === void 0 ? void 0 : require_stream_formula.readTableFormula(formulaData);
139
+ if (formulaData !== void 0 && formula === void 0) reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.TableFormulaUnresolved, "A table cell carries a formula this reader could not decode with confidence, so the cell keeps its displayed text but not the formula that produced it.");
126
140
  const cellSpanning = spanning === void 0 ? void 0 : require_stream_table.readCellSpanning(spanning);
127
141
  const cellFill = fill === void 0 ? void 0 : require_stream_table.readCellFill(fill);
128
142
  if (cellFill?.blended === true) reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.CellFillBlended, "A cell is filled with a shaded blend of two colours, resolved to a 'pattern' fill whose density is this reader's own best-effort derivation, not a value confirmed against a specification.");
@@ -132,13 +146,14 @@ function readCellAttributes(state, nonDeletable, sink) {
132
146
  background: cellFill?.fill,
133
147
  columnSpan: cellSpanning?.columnSpan ?? NO_SPAN,
134
148
  rowSpan: cellSpanning?.rowSpan ?? NO_SPAN,
135
- covered: cellSpanning?.coveredFromLeft === true || cellSpanning?.coveredFromAbove === true
149
+ covered: cellSpanning?.coveredFromLeft === true || cellSpanning?.coveredFromAbove === true,
150
+ formula
136
151
  },
137
152
  rowHeightPt: row === void 0 ? void 0 : require_stream_table.readRowInformation(row)?.heightPt
138
153
  };
139
154
  }
140
- function closeCell(state, table, attributes) {
141
- flushParagraphIfContent(state);
155
+ function closeCell(state, table, attributes, sink) {
156
+ flushParagraphIfContent(state, sink);
142
157
  const blocks = table.cellBlocks;
143
158
  table.cellBlocks = [];
144
159
  if (attributes.covered) return;
@@ -149,7 +164,8 @@ function closeCell(state, table, attributes) {
149
164
  blocks,
150
165
  ...attributes.columnSpan > NO_SPAN ? { colSpan: attributes.columnSpan } : {},
151
166
  ...attributes.rowSpan > NO_SPAN ? { rowSpan: attributes.rowSpan } : {},
152
- ...attributes.background === void 0 ? {} : { background: attributes.background }
167
+ ...attributes.background === void 0 ? {} : { background: attributes.background },
168
+ ...attributes.formula === void 0 ? {} : { formula: attributes.formula }
153
169
  };
154
170
  table.cells.push(cell);
155
171
  }
@@ -181,14 +197,14 @@ function applyEolMapping(state, mapping, nonDeletable, sink) {
181
197
  appendText(state, " ");
182
198
  return;
183
199
  case "hardReturn":
184
- flushParagraph(state);
200
+ flushParagraph(state, sink);
185
201
  return;
186
202
  case "hardEndOfColumn":
187
203
  reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.ColumnBreakFlattened, "A column break became a paragraph break.");
188
- flushParagraph(state);
204
+ flushParagraph(state, sink);
189
205
  return;
190
206
  case "hardEndOfPage":
191
- flushParagraph(state);
207
+ flushParagraph(state, sink);
192
208
  targetBlocks(state).push({ kind: "pageBreak" });
193
209
  return;
194
210
  case "tableCell":
@@ -198,13 +214,13 @@ function applyEolMapping(state, mapping, nonDeletable, sink) {
198
214
  const table = state.table;
199
215
  if (table === void 0) {
200
216
  reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.TableFlattened, "A table cell or row boundary appeared with no table definition open; its text became a paragraph.");
201
- flushParagraph(state);
217
+ flushParagraph(state, sink);
202
218
  return;
203
219
  }
204
220
  const { attributes, rowHeightPt } = readCellAttributes(state, nonDeletable, sink);
205
221
  if (rowHeightPt !== void 0) table.rowHeightPt = rowHeightPt;
206
222
  const cellIsOpen = state.text.length > 0 || state.runs.length > 0 || table.cellBlocks.length > 0;
207
- if (mapping !== "tableOff" || cellIsOpen) closeCell(state, table, attributes);
223
+ if (mapping !== "tableOff" || cellIsOpen) closeCell(state, table, attributes, sink);
208
224
  if (mapping === "tableCell") return;
209
225
  closeRow(table);
210
226
  if (mapping === "tableOff") closeTable(state);
@@ -233,13 +249,13 @@ function applySingleByteFunction(state, code, sink) {
233
249
  appendText(state, "-");
234
250
  return;
235
251
  case DORMANT_HARD_RETURN:
236
- flushParagraph(state);
252
+ flushParagraph(state, sink);
237
253
  return;
238
254
  case SOFT_END_OF_CENTER_ALIGN:
239
255
  appendText(state, " ");
240
256
  return;
241
257
  case HARD_END_OF_CENTER_ALIGN:
242
- flushParagraph(state);
258
+ flushParagraph(state, sink);
243
259
  return;
244
260
  case START_OF_TEXT_TO_SKIP:
245
261
  state.skipDepth += 1;
@@ -301,13 +317,56 @@ function applyColumnGroup(state, token, sink) {
301
317
  if (token.subgroup === 0) setPageDimension(state, "leftPt", margin, sink);
302
318
  else if (token.subgroup === 1) setPageDimension(state, "rightPt", margin, sink);
303
319
  }
304
- function applyStyleGroup(state, token) {
320
+ function snapshotFormatting(state) {
321
+ return {
322
+ activeAttributes: new Set(state.activeAttributes),
323
+ fontFamily: state.fontFamily,
324
+ sizePt: state.sizePt,
325
+ color: state.color
326
+ };
327
+ }
328
+ function restoreFormattingSnapshot(state, snapshot) {
329
+ flushRun(state);
330
+ state.activeAttributes.clear();
331
+ for (const attribute of snapshot.activeAttributes) state.activeAttributes.add(attribute);
332
+ state.attributes = require_stream_attributes.runAttributesFrom(state.activeAttributes);
333
+ state.fontFamily = snapshot.fontFamily;
334
+ state.sizePt = snapshot.sizePt;
335
+ state.color = snapshot.color;
336
+ }
337
+ function applyStylePacketBegin(state, token, container, sink) {
338
+ const prefixId = token.prefixIds[0];
339
+ if (prefixId === void 0) return;
340
+ const packet = require_container_prefix.packetByPrefixId(container.packets, prefixId);
341
+ if (packet?.packetType !== 48) return;
342
+ const beginBlock = require_stream_style.readStyleBeginBlock(packet.bytes);
343
+ if (beginBlock === void 0) return;
344
+ if (state.styleResolutionDepth >= MAX_STYLE_RESOLUTION_DEPTH) {
345
+ reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.StyleResolutionDepthExceeded, "A chain of styles resolving one another's own packets ran deeper than this reader will follow, so the deepest style's own direct formatting was not applied.");
346
+ return;
347
+ }
348
+ const snapshot = snapshotFormatting(state);
349
+ const tokens = require_stream_tokenise.tokeniseDocumentArea(beginBlock, 0, beginBlock.length);
350
+ state.styleResolutionDepth += 1;
351
+ for (const beginToken of tokens) applyToken(state, beginToken, container, sink);
352
+ state.styleResolutionDepth -= 1;
353
+ return snapshot;
354
+ }
355
+ function applyStyleGroup(state, token, container, sink) {
305
356
  if (require_stream_style.isStyleScopeOpener(token.subgroup)) {
306
357
  const systemStyleNumber = require_stream_style.readSystemStyleNumber(token.nonDeletable);
307
- state.styleScopes.push(systemStyleNumber === void 0 ? void 0 : require_stream_style.styleSemanticsFor(systemStyleNumber));
358
+ const semantics = systemStyleNumber === void 0 ? void 0 : require_stream_style.styleSemanticsFor(systemStyleNumber);
359
+ const snapshot = applyStylePacketBegin(state, token, container, sink);
360
+ state.styleScopes.push({
361
+ semantics,
362
+ snapshot
363
+ });
308
364
  return;
309
365
  }
310
- if (require_stream_style.isStyleScopeCloser(token.subgroup)) state.styleScopes.pop();
366
+ if (require_stream_style.isStyleScopeCloser(token.subgroup)) {
367
+ const entry = state.styleScopes.pop();
368
+ if (entry?.snapshot !== void 0) restoreFormattingSnapshot(state, entry.snapshot);
369
+ }
311
370
  }
312
371
  function applyDisplayNumberGroup(state, token, sink) {
313
372
  if (require_stream_style.isParagraphNumberDisplayOn(token.subgroup)) {
@@ -323,7 +382,7 @@ function applyCharacterGroup(state, token, container, sink) {
323
382
  switch (token.subgroup) {
324
383
  case 42:
325
384
  closeTable(state);
326
- flushParagraphIfContent(state);
385
+ flushParagraphIfContent(state, sink);
327
386
  state.table = {
328
387
  columnWidthsPt: [],
329
388
  rows: [],
@@ -368,6 +427,104 @@ function applyCharacterGroup(state, token, container, sink) {
368
427
  default: return;
369
428
  }
370
429
  }
430
+ function applyMergeGroup(state, token, sink) {
431
+ if (token.subgroup === MERGE_FIELD_ON) {
432
+ flushRun(state);
433
+ state.openMergeFieldStartRun = state.runs.length;
434
+ return;
435
+ }
436
+ if (token.subgroup === MERGE_FIELD_OFF) {
437
+ const startRun = state.openMergeFieldStartRun;
438
+ state.openMergeFieldStartRun = void 0;
439
+ if (startRun === void 0) return;
440
+ flushRun(state);
441
+ const endRun = state.runs.length;
442
+ const instruction = state.runs.slice(startRun, endRun).map((run) => run.text).join("");
443
+ state.pendingConstructs.push({
444
+ descriptor: {
445
+ kind: "field",
446
+ instruction
447
+ },
448
+ startRun,
449
+ endRun
450
+ });
451
+ return;
452
+ }
453
+ reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.MergeCodeDropped, "This document contains merge codes, which are a form-letter template's placeholders rather than text.");
454
+ }
455
+ function plainTextOf(blocks) {
456
+ return blocks.filter((block) => block.kind === "paragraph").map((paragraph) => paragraph.runs.map((run) => run.text).join("")).join("\n");
457
+ }
458
+ function applyBoxGroup(state, token, container, sink) {
459
+ const boxContent = require_stream_box.readBoxContent(token.nonDeletable, token.prefixIds);
460
+ if (boxContent === void 0) {
461
+ reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.BoxDropped, "This document contains a box -- a figure, text box, equation, or graphic -- whose function-level override names no content this reader can resolve.");
462
+ return;
463
+ }
464
+ const packet = boxContent.contentType === 1 || boxContent.contentType === 2 || boxContent.contentType === 4 ? require_container_prefix.packetByPrefixId(container.packets, boxContent.contentPrefixId) : void 0;
465
+ const textBlocks = packet?.packetType !== 8 ? void 0 : require_container_prefix.readGeneralWpTextBlocks(packet.bytes);
466
+ if (textBlocks === void 0) {
467
+ reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.BoxContentUnresolved, "This document contains a box whose content this reader could not read -- an image, OLE object, or other content type this reader does not yet decode into the shared schema.");
468
+ return;
469
+ }
470
+ if (boxContent.frame === void 0) {
471
+ reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.BoxFrameUnresolved, "This document contains a box whose content this reader could read, but whose function-level override states no width and height this reader can trust, so its content was not lifted.");
472
+ return;
473
+ }
474
+ flushParagraphIfContent(state, sink);
475
+ const nestedTokens = require_stream_tokenise.tokeniseDocumentArea(textBlocks, 0, textBlocks.length);
476
+ const frame = {
477
+ xPt: boxContent.frame.xPt,
478
+ yPt: boxContent.frame.yPt,
479
+ widthPt: boxContent.frame.widthPt,
480
+ heightPt: boxContent.frame.heightPt
481
+ };
482
+ if (boxContent.contentType === 4) {
483
+ const { blocks } = foldTokens(nestedTokens, container, sink);
484
+ const embed = {
485
+ kind: "embeddedObject",
486
+ objectKind: "formula",
487
+ frame,
488
+ document: {
489
+ kind: "formula",
490
+ metadata: {},
491
+ formula: {
492
+ mathml: [],
493
+ source: {
494
+ format: "wpd",
495
+ xml: plainTextOf(blocks)
496
+ }
497
+ }
498
+ }
499
+ };
500
+ targetBlocks(state).push(embed);
501
+ return;
502
+ }
503
+ const { blocks, page } = foldTokens(nestedTokens, container, sink);
504
+ const embed = {
505
+ kind: "embeddedObject",
506
+ objectKind: "wordprocessing",
507
+ frame,
508
+ document: {
509
+ kind: "wordprocessing",
510
+ metadata: {},
511
+ sections: [{
512
+ pageSize: {
513
+ widthPt: page.widthPt ?? 612,
514
+ heightPt: page.heightPt ?? 792
515
+ },
516
+ margins: {
517
+ topPt: page.topPt ?? 72,
518
+ rightPt: page.rightPt ?? 72,
519
+ bottomPt: page.bottomPt ?? 72,
520
+ leftPt: page.leftPt ?? 72
521
+ },
522
+ blocks
523
+ }]
524
+ }
525
+ };
526
+ targetBlocks(state).push(embed);
527
+ }
371
528
  function applyVariableFunction(state, token, container, sink) {
372
529
  switch (token.group) {
373
530
  case 208: {
@@ -398,7 +555,7 @@ function applyVariableFunction(state, token, container, sink) {
398
555
  return;
399
556
  }
400
557
  case 221:
401
- applyStyleGroup(state, token);
558
+ applyStyleGroup(state, token, container, sink);
402
559
  return;
403
560
  case 218:
404
561
  applyDisplayNumberGroup(state, token, sink);
@@ -413,10 +570,10 @@ function applyVariableFunction(state, token, container, sink) {
413
570
  reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.NoteDropped, "This document contains a footnote or endnote; its text lives in a prefix packet the flat content model has nowhere to put.");
414
571
  return;
415
572
  case MERGE_GROUP:
416
- reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.MergeCodeDropped, "This document contains merge codes, which are a form-letter template's placeholders rather than text.");
573
+ applyMergeGroup(state, token, sink);
417
574
  return;
418
575
  case BOX_GROUP:
419
- reportOnce(state, sink, require_diagnostics.WpdDiagnosticCodes.BoxDropped, "This document contains a box -- a figure, text box, equation, or graphic -- whose contents were not read.");
576
+ applyBoxGroup(state, token, container, sink);
420
577
  return;
421
578
  default: return;
422
579
  }
@@ -450,6 +607,32 @@ function applyFixedFunction(state, token, sink) {
450
607
  flushRun(state);
451
608
  state.attributes = next;
452
609
  }
610
+ function applyToken(state, token, container, sink) {
611
+ switch (token.kind) {
612
+ case "character": {
613
+ const character = require_stream_characters.decodeSingleByteCharacter(token.byte);
614
+ if (character === void 0) {
615
+ sink({
616
+ code: require_diagnostics.WpdDiagnosticCodes.UnmappedCharacter,
617
+ message: `Byte ${token.byte} in the document area has no character mapping and was rendered as U+FFFD.`
618
+ });
619
+ appendText(state, "�");
620
+ return;
621
+ }
622
+ appendText(state, character);
623
+ return;
624
+ }
625
+ case "singleByteFunction":
626
+ applySingleByteFunction(state, token.code, sink);
627
+ return;
628
+ case "variableFunction":
629
+ applyVariableFunction(state, token, container, sink);
630
+ return;
631
+ case "fixedFunction":
632
+ applyFixedFunction(state, token, sink);
633
+ return;
634
+ }
635
+ }
453
636
  function foldTokens(tokens, container, sink) {
454
637
  const state = {
455
638
  blocks: [],
@@ -463,6 +646,7 @@ function foldTokens(tokens, container, sink) {
463
646
  alignment: void 0,
464
647
  skipDepth: 0,
465
648
  styleScopes: [],
649
+ styleResolutionDepth: 0,
466
650
  pendingHeadingLevel: void 0,
467
651
  pendingListLevel: void 0,
468
652
  pendingAlignment: void 0,
@@ -477,32 +661,13 @@ function foldTokens(tokens, container, sink) {
477
661
  changeReported: false
478
662
  },
479
663
  table: void 0,
480
- reported: /* @__PURE__ */ new Set()
664
+ reported: /* @__PURE__ */ new Set(),
665
+ pendingConstructs: [],
666
+ openMergeFieldStartRun: void 0
481
667
  };
482
- for (const token of tokens) switch (token.kind) {
483
- case "character": {
484
- const character = require_stream_characters.decodeSingleByteCharacter(token.byte);
485
- if (character === void 0) {
486
- sink({
487
- code: require_diagnostics.WpdDiagnosticCodes.UnmappedCharacter,
488
- message: `Byte ${token.byte} in the document area has no character mapping and was rendered as U+FFFD.`
489
- });
490
- appendText(state, "�");
491
- break;
492
- }
493
- appendText(state, character);
494
- break;
495
- }
496
- case "singleByteFunction":
497
- applySingleByteFunction(state, token.code, sink);
498
- break;
499
- case "variableFunction":
500
- applyVariableFunction(state, token, container, sink);
501
- break;
502
- case "fixedFunction": applyFixedFunction(state, token, sink);
503
- }
668
+ for (const token of tokens) applyToken(state, token, container, sink);
504
669
  flushRun(state);
505
- if (state.runs.length > 0) flushParagraph(state);
670
+ if (state.runs.length > 0) flushParagraph(state, sink);
506
671
  if (state.table !== void 0) {
507
672
  closeRow(state.table);
508
673
  closeTable(state);
package/dist/read.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { i as WpdDiagnosticSink } from "./diagnostics-CR0pXOyb.cjs";
1
+ import { i as WpdDiagnosticSink } from "./diagnostics-Dtb2uPw4.cjs";
2
2
  import { ContentDocument, DocumentTree } from "document-schema.js";
3
3
  //#region src/read.d.ts
4
4
  interface ReadWpdOptions {
package/dist/read.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { i as WpdDiagnosticSink } from "./diagnostics-CR0pXOyb.js";
1
+ import { i as WpdDiagnosticSink } from "./diagnostics-Dtb2uPw4.js";
2
2
  import { ContentDocument, DocumentTree } from "document-schema.js";
3
3
  //#region src/read.d.ts
4
4
  interface ReadWpdOptions {