rtf-codec 1.0.0 → 1.1.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.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as RtfDiagnostic } from "./diagnostics-DdDFJLNO.cjs";
1
+ import { n as RtfDiagnostic } from "./diagnostics-BgG_KAiN.cjs";
2
2
  import { ReadRtfOptions } from "./options.cjs";
3
3
  import { ContentDocument, DocumentTree } from "document-schema.js";
4
4
  //#region src/read.d.ts
package/dist/read.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as RtfDiagnostic } from "./diagnostics-DdDFJLNO.js";
1
+ import { n as RtfDiagnostic } from "./diagnostics-BgG_KAiN.js";
2
2
  import { ReadRtfOptions } from "./options.js";
3
3
  import { ContentDocument, DocumentTree } from "document-schema.js";
4
4
  //#region src/read.d.ts
package/dist/read.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { bytesToBase64, hexToBytes } from "./base64.js";
2
2
  import { RtfDiagnosticCodes, RtfInputTooLargeError, RtfNestingLimitExceededError, RtfNotAnRtfDocumentError } from "./diagnostics.js";
3
3
  import { appendBytes, asciiStringFromBytes, rtfBytesFromLatin1 } from "./bytes.js";
4
+ import { halfPointsToPoints, pixelsToPoints, twipsToPoints } from "./units.js";
5
+ import { applyCellDefinitionControlWord, newPendingCell, resolveBorder } from "./cell-format.js";
6
+ import { NO_REVISION, bookmarkAnchorDescriptor, coalesceRunConstructs, provenanceDescriptors } from "./constructs.js";
4
7
  import { decodeCodepageBytes } from "./codepage.js";
5
8
  import { groupHead, matchingGroupEnd } from "./group.js";
6
- import { halfPointsToPoints, pixelsToPoints, twipsToPoints } from "./units.js";
7
9
  import { HEADER_DESTINATIONS, readRtfHeader } from "./header.js";
8
10
  import { mintRtfListNumId } from "./list-id.js";
9
11
  import "./options.js";
@@ -42,8 +44,8 @@ const DESTINATION_KINDS = /* @__PURE__ */ new Map([
42
44
  ["atndate", "skip"],
43
45
  ["atnparent", "skip"],
44
46
  ["atnicn", "skip"],
45
- ["bkmkstart", "skip"],
46
- ["bkmkend", "skip"],
47
+ ["bkmkstart", "bookmarkStart"],
48
+ ["bkmkend", "bookmarkEnd"],
47
49
  ["object", "skip"],
48
50
  ["objdata", "skip"],
49
51
  ["objclass", "skip"],
@@ -128,6 +130,12 @@ const ALIGNMENTS = /* @__PURE__ */ new Map([
128
130
  ["qj", "justify"]
129
131
  ]);
130
132
  const PICTURE_FORMATS = /* @__PURE__ */ new Map([["pngblip", "png"], ["jpegblip", "jpeg"]]);
133
+ const SECTION_BREAK_TYPES = /* @__PURE__ */ new Map([
134
+ ["sbknone", "continuous"],
135
+ ["sbkpage", void 0],
136
+ ["sbkeven", "evenPage"],
137
+ ["sbkodd", "oddPage"]
138
+ ]);
131
139
  function defaultCharacterState() {
132
140
  return {
133
141
  bold: false,
@@ -137,7 +145,8 @@ function defaultCharacterState() {
137
145
  hidden: false,
138
146
  fontIndex: void 0,
139
147
  sizeHalfPoints: 24,
140
- colorIndex: void 0
148
+ colorIndex: void 0,
149
+ revision: NO_REVISION
141
150
  };
142
151
  }
143
152
  function defaultParagraphState() {
@@ -165,6 +174,7 @@ function cloneGroupState(state) {
165
174
  para: { ...state.para },
166
175
  field: state.field,
167
176
  picture: state.picture,
177
+ bookmark: state.bookmark,
168
178
  inUnicodeWrapper: state.inUnicodeWrapper
169
179
  };
170
180
  }
@@ -177,7 +187,8 @@ function runKey(char, fontName, color, hyperlink) {
177
187
  fontName ?? "",
178
188
  String(char.sizeHalfPoints),
179
189
  color === void 0 ? "" : `${String(color.r)},${String(color.g)},${String(color.b)}`,
180
- hyperlink ?? ""
190
+ hyperlink ?? "",
191
+ JSON.stringify(char.revision)
181
192
  ].join("|");
182
193
  }
183
194
  const HYPERLINK_TARGET = /HYPERLINK\s+"([^"]*)"/i;
@@ -217,6 +228,28 @@ function skipUnicodeFallback(tokens, from, count) {
217
228
  textOffset
218
229
  };
219
230
  }
231
+ function insertConstructMarkers(blocks, extents) {
232
+ if (extents.length === 0) return [...blocks];
233
+ const ordered = [...extents].sort((left, right) => left.startIndex - right.startIndex || right.endIndex - left.endIndex);
234
+ const closing = [...ordered].reverse();
235
+ const out = [];
236
+ for (let index = 0; index <= blocks.length; index += 1) {
237
+ for (const extent of closing) if (extent.endIndex === index) out.push({ kind: "constructEnd" });
238
+ for (const extent of ordered) if (extent.startIndex === index) out.push({
239
+ kind: "constructStart",
240
+ descriptor: extent.descriptor
241
+ });
242
+ const block = blocks[index];
243
+ if (block !== void 0) out.push(block);
244
+ }
245
+ return out;
246
+ }
247
+ function horizontalSpanAt(definitions, index) {
248
+ if (definitions[index]?.horizontalMergeFirst !== true) return 1;
249
+ let span = 1;
250
+ while (definitions[index + span]?.horizontalMergeContinuation === true) span += 1;
251
+ return span;
252
+ }
220
253
  var ContentBuilder = class {
221
254
  header;
222
255
  sink;
@@ -226,16 +259,63 @@ var ContentBuilder = class {
226
259
  pendingRunKey;
227
260
  pendingRunText = "";
228
261
  pendingRunFields = {};
262
+ runProvenance = [];
263
+ pendingRunProvenance = [];
229
264
  tableRows = [];
230
265
  tableColumnRights = [];
231
266
  rowCells = [];
232
267
  cellBlocks = [];
233
268
  pendingCellRights = [];
269
+ pendingCellDefinitions = [];
270
+ pendingCell = newPendingCell();
234
271
  rowLeftTwips = 0;
272
+ paragraphSerial = 0;
273
+ openBookmarks = /* @__PURE__ */ new Map();
274
+ sectionBlockExtents = [];
275
+ cellBlockExtents = [];
276
+ pendingRunConstructs = [];
277
+ closingBookmarks = [];
235
278
  constructor(header, sink) {
236
279
  this.header = header;
237
280
  this.sink = sink;
238
281
  }
282
+ startBookmark(bookmark, para) {
283
+ this.flushRun();
284
+ const name = bookmark.name;
285
+ if (name.length === 0) return;
286
+ this.openBookmarks.set(name, {
287
+ descriptor: bookmarkAnchorDescriptor(name, bookmark.columnFirst === void 0 && bookmark.columnLast === void 0 ? void 0 : {
288
+ first: bookmark.columnFirst,
289
+ last: bookmark.columnLast
290
+ }),
291
+ paragraphSerial: this.paragraphSerial,
292
+ runIndex: this.runs.length,
293
+ inTable: para.inTable,
294
+ blockIndex: void 0
295
+ });
296
+ }
297
+ endBookmark(name) {
298
+ this.flushRun();
299
+ const open = this.openBookmarks.get(name);
300
+ if (open === void 0) {
301
+ this.sink({
302
+ code: RtfDiagnosticCodes.BOOKMARK_UNPAIRED,
303
+ severity: "warning",
304
+ message: `a \\bkmkend named '${name}' has no matching \\bkmkstart, so no anchor construct is produced for it`
305
+ });
306
+ return;
307
+ }
308
+ this.openBookmarks.delete(name);
309
+ if (open.paragraphSerial === this.paragraphSerial) {
310
+ this.pendingRunConstructs.push({
311
+ descriptor: open.descriptor,
312
+ startRun: open.runIndex,
313
+ endRun: this.runs.length
314
+ });
315
+ return;
316
+ }
317
+ this.closingBookmarks.push(open);
318
+ }
239
319
  appendText(text, char, hyperlink) {
240
320
  if (text.length === 0 || char.hidden) return;
241
321
  const fontName = char.fontIndex === void 0 ? void 0 : this.header.fonts.get(char.fontIndex)?.name;
@@ -245,32 +325,73 @@ var ContentBuilder = class {
245
325
  this.flushRun();
246
326
  this.pendingRunKey = key;
247
327
  this.pendingRunFields = buildRunFields(char, fontName, color, hyperlink);
328
+ this.pendingRunProvenance = provenanceDescriptors(char.revision, this.header.revisionAuthors);
248
329
  }
249
330
  this.pendingRunText += text;
250
331
  }
251
332
  flushRun() {
252
- if (this.pendingRunText.length > 0) this.runs.push({
253
- ...this.pendingRunFields,
254
- text: this.pendingRunText
255
- });
333
+ if (this.pendingRunText.length > 0) {
334
+ this.runs.push({
335
+ ...this.pendingRunFields,
336
+ text: this.pendingRunText
337
+ });
338
+ this.runProvenance.push(this.pendingRunProvenance);
339
+ }
256
340
  this.pendingRunText = "";
257
341
  this.pendingRunKey = void 0;
258
342
  this.pendingRunFields = {};
343
+ this.pendingRunProvenance = [];
259
344
  }
260
345
  endParagraph(para, force) {
261
346
  this.flushRun();
262
347
  if (!force && this.runs.length === 0) return;
263
348
  const target = para.inTable ? this.cellBlocks : this.blocks;
264
349
  if (!para.inTable) this.closeTable();
350
+ const blockIndex = target.length;
265
351
  target.push(this.buildParagraph(para));
266
352
  this.runs = [];
353
+ this.runProvenance = [];
354
+ this.resolveBookmarkPositions(para, blockIndex);
355
+ this.paragraphSerial += 1;
356
+ }
357
+ resolveBookmarkPositions(para, blockIndex) {
358
+ for (const open of this.openBookmarks.values()) if (open.blockIndex === void 0 && open.paragraphSerial === this.paragraphSerial) open.blockIndex = blockIndex;
359
+ this.flushClosingBookmarks(para.inTable, blockIndex + 1);
360
+ }
361
+ flushClosingBookmarks(inTable, endIndex) {
362
+ if (this.closingBookmarks.length === 0) return;
363
+ const target = inTable ? this.cellBlockExtents : this.sectionBlockExtents;
364
+ for (const closing of this.closingBookmarks) {
365
+ if (closing.inTable !== inTable) {
366
+ this.sink({
367
+ code: RtfDiagnosticCodes.BOOKMARK_UNPAIRED,
368
+ severity: "warning",
369
+ message: `the bookmark '${closing.descriptor.name}' spans a table cell boundary; a construct extent cannot straddle two block lists, so no anchor construct is produced for it`
370
+ });
371
+ continue;
372
+ }
373
+ target.push({
374
+ descriptor: closing.descriptor,
375
+ startIndex: closing.blockIndex ?? Math.max(0, endIndex - 1),
376
+ endIndex
377
+ });
378
+ }
379
+ this.closingBookmarks = [];
380
+ }
381
+ takeRunConstructs() {
382
+ const extents = [...this.pendingRunConstructs, ...coalesceRunConstructs(this.runProvenance)].filter((extent) => extent.endRun <= this.runs.length);
383
+ this.pendingRunConstructs = [];
384
+ this.runProvenance = [];
385
+ return extents.sort((left, right) => left.startRun - right.startRun || left.endRun - right.endRun);
267
386
  }
268
387
  buildParagraph(para) {
269
388
  const style = para.styleIndex === void 0 ? void 0 : this.header.styles.get(para.styleIndex);
270
389
  const headingLevel = para.outlineLevel === void 0 ? style?.headingLevel : para.outlineLevel + 1;
390
+ const constructs = this.takeRunConstructs();
271
391
  const paragraph = {
272
392
  kind: "paragraph",
273
- runs: this.runs
393
+ runs: this.runs,
394
+ ...constructs.length === 0 ? {} : { constructs }
274
395
  };
275
396
  const withStyle = style?.name === void 0 || style.name.length === 0 ? paragraph : {
276
397
  ...paragraph,
@@ -314,18 +435,27 @@ var ContentBuilder = class {
314
435
  }
315
436
  startRowDefinition() {
316
437
  this.pendingCellRights = [];
438
+ this.pendingCellDefinitions = [];
439
+ this.pendingCell = newPendingCell();
317
440
  this.rowLeftTwips = 0;
318
441
  }
319
442
  setRowLeft(twips) {
320
443
  this.rowLeftTwips = twips;
321
444
  }
445
+ applyCellDefinition(name, param) {
446
+ return applyCellDefinitionControlWord(name, param, this.pendingCell);
447
+ }
322
448
  addCellBoundary(rightTwips) {
323
449
  this.pendingCellRights.push(rightTwips);
450
+ this.pendingCellDefinitions.push(this.pendingCell);
451
+ this.pendingCell = newPendingCell();
324
452
  }
325
453
  endCell(para) {
326
454
  this.endParagraph(para, false);
327
- this.rowCells.push({ blocks: this.cellBlocks });
455
+ this.flushClosingBookmarks(true, this.cellBlocks.length);
456
+ this.rowCells.push({ blocks: insertConstructMarkers(this.cellBlocks, this.cellBlockExtents) });
328
457
  this.cellBlocks = [];
458
+ this.cellBlockExtents = [];
329
459
  }
330
460
  endRow(para) {
331
461
  if (this.cellBlocks.length > 0 || this.pendingRunText.length > 0) this.endCell(para);
@@ -337,21 +467,75 @@ var ContentBuilder = class {
337
467
  });
338
468
  return;
339
469
  }
340
- this.tableRows.push({ cells: this.rowCells });
470
+ this.tableRows.push({
471
+ cells: this.rowCells,
472
+ definitions: this.pendingCellDefinitions
473
+ });
341
474
  if (this.tableColumnRights.length === 0) this.tableColumnRights = [...this.pendingCellRights];
342
475
  this.rowCells = [];
343
476
  }
344
477
  closeTable() {
345
478
  if (this.tableRows.length === 0) return;
346
- const columnCount = Math.max(...this.tableRows.map((row) => row.cells.length));
479
+ const rows = this.resolveRows();
480
+ const columnCount = Math.max(this.tableColumnRights.length, ...rows.map((row) => row.cells.reduce((total, cell) => total + (cell.colSpan ?? 1), 0)));
347
481
  this.blocks.push({
348
482
  kind: "table",
349
- rows: this.tableRows,
483
+ rows,
350
484
  columnWidthsPt: this.columnWidths(columnCount)
351
485
  });
352
486
  this.tableRows = [];
353
487
  this.tableColumnRights = [];
354
488
  }
489
+ resolveRows() {
490
+ const rows = this.tableRows;
491
+ const columnIndices = rows.map((row) => {
492
+ const indices = [];
493
+ let column = 0;
494
+ for (const [index] of row.cells.entries()) {
495
+ indices.push(column);
496
+ column += horizontalSpanAt(row.definitions, index);
497
+ }
498
+ return indices;
499
+ });
500
+ return rows.map((row, rowIndex) => ({ cells: row.cells.map((cell, cellIndex) => ({
501
+ cell,
502
+ cellIndex
503
+ })).filter(({ cellIndex }) => row.definitions[cellIndex]?.horizontalMergeContinuation !== true).map(({ cell, cellIndex }) => {
504
+ const definition = row.definitions[cellIndex];
505
+ if (definition?.verticalMergeContinuation === true) return { blocks: [] };
506
+ const colSpan = horizontalSpanAt(row.definitions, cellIndex);
507
+ const column = columnIndices[rowIndex]?.[cellIndex];
508
+ let rowSpan = 1;
509
+ if (definition?.verticalMergeFirst === true && column !== void 0) for (let next = rowIndex + 1; next < rows.length; next += 1) {
510
+ const matchIndex = columnIndices[next]?.indexOf(column) ?? -1;
511
+ if ((matchIndex === -1 ? void 0 : rows[next]?.definitions[matchIndex])?.verticalMergeContinuation !== true) break;
512
+ rowSpan += 1;
513
+ }
514
+ const borders = this.resolveBorders(definition);
515
+ const background = definition?.backgroundIndex === void 0 ? void 0 : this.header.colors[definition.backgroundIndex];
516
+ return {
517
+ blocks: cell.blocks,
518
+ ...colSpan > 1 ? { colSpan } : {},
519
+ ...rowSpan > 1 ? { rowSpan } : {},
520
+ ...background === void 0 ? {} : { background },
521
+ ...borders === void 0 ? {} : { borders }
522
+ };
523
+ }) }));
524
+ }
525
+ resolveBorders(definition) {
526
+ if (definition === void 0) return;
527
+ const colorAt = (index) => this.header.colors[index];
528
+ const present = [
529
+ "top",
530
+ "left",
531
+ "bottom",
532
+ "right"
533
+ ].map((side) => {
534
+ const pending = definition.borders[side];
535
+ return [side, pending === void 0 ? void 0 : resolveBorder(pending, colorAt)];
536
+ }).filter((entry) => entry[1] !== void 0);
537
+ return present.length === 0 ? void 0 : Object.fromEntries(present);
538
+ }
355
539
  columnWidths(columnCount) {
356
540
  const rights = this.tableColumnRights;
357
541
  const widths = [];
@@ -375,22 +559,36 @@ var ContentBuilder = class {
375
559
  this.flushRun();
376
560
  this.blocks.push(block);
377
561
  }
378
- endSection(pageSize, margins, para) {
562
+ endSection(section, para) {
379
563
  this.endParagraph(para, false);
380
564
  this.closeTable();
381
- if (this.blocks.length === 0 && this.sections.length > 0) return;
565
+ this.flushClosingBookmarks(false, this.blocks.length);
566
+ this.reportUnclosedBookmarks();
567
+ const blocks = insertConstructMarkers(this.blocks, this.sectionBlockExtents);
568
+ this.sectionBlockExtents = [];
569
+ if (blocks.length === 0 && this.sections.length > 0) {
570
+ this.blocks = [];
571
+ return;
572
+ }
382
573
  this.sections.push({
383
- pageSize,
384
- margins,
385
- blocks: this.blocks
574
+ ...sectionGeometry(section),
575
+ ...section.breakType === void 0 ? {} : { breakType: section.breakType },
576
+ blocks
386
577
  });
387
578
  this.blocks = [];
388
579
  }
389
- finish(metadata, pageSize, margins, para) {
390
- this.endSection(pageSize, margins, para);
580
+ reportUnclosedBookmarks() {
581
+ for (const open of this.openBookmarks.values()) this.sink({
582
+ code: RtfDiagnosticCodes.BOOKMARK_UNPAIRED,
583
+ severity: "warning",
584
+ message: `the bookmark '${open.descriptor.name}' has no matching \\bkmkend within its own block flow, so no anchor construct is produced for it`
585
+ });
586
+ this.openBookmarks.clear();
587
+ }
588
+ finish(metadata, section, para) {
589
+ this.endSection(section, para);
391
590
  if (this.sections.length === 0) this.sections.push({
392
- pageSize,
393
- margins,
591
+ ...sectionGeometry(section),
394
592
  blocks: []
395
593
  });
396
594
  return {
@@ -400,6 +598,31 @@ var ContentBuilder = class {
400
598
  };
401
599
  }
402
600
  };
601
+ function sectionGeometry(section) {
602
+ return {
603
+ pageSize: {
604
+ widthPt: twipsToPoints(section.paperWidthTwips),
605
+ heightPt: twipsToPoints(section.paperHeightTwips)
606
+ },
607
+ margins: {
608
+ topPt: twipsToPoints(section.marginTopTwips),
609
+ rightPt: twipsToPoints(section.marginRightTwips),
610
+ bottomPt: twipsToPoints(section.marginBottomTwips),
611
+ leftPt: twipsToPoints(section.marginLeftTwips)
612
+ }
613
+ };
614
+ }
615
+ function defaultSectionState(header) {
616
+ return {
617
+ paperWidthTwips: header.page.paperWidthTwips,
618
+ paperHeightTwips: header.page.paperHeightTwips,
619
+ marginLeftTwips: header.page.marginLeftTwips,
620
+ marginRightTwips: header.page.marginRightTwips,
621
+ marginTopTwips: header.page.marginTopTwips,
622
+ marginBottomTwips: header.page.marginBottomTwips,
623
+ breakType: void 0
624
+ };
625
+ }
403
626
  function buildRunFields(char, fontName, color, hyperlink) {
404
627
  return {
405
628
  ...char.bold ? { bold: true } : {},
@@ -492,16 +715,7 @@ function readRtfDetail(input, options) {
492
715
  assertRtfHeaderPresent(tokens);
493
716
  const header = readRtfHeader(tokens, sink);
494
717
  const builder = new ContentBuilder(header, sink);
495
- const pageSize = {
496
- widthPt: twipsToPoints(header.page.paperWidthTwips),
497
- heightPt: twipsToPoints(header.page.paperHeightTwips)
498
- };
499
- const margins = {
500
- topPt: twipsToPoints(header.page.marginTopTwips),
501
- rightPt: twipsToPoints(header.page.marginRightTwips),
502
- bottomPt: twipsToPoints(header.page.marginBottomTwips),
503
- leftPt: twipsToPoints(header.page.marginLeftTwips)
504
- };
718
+ const section = defaultSectionState(header);
505
719
  const root = {
506
720
  destination: "body",
507
721
  uc: 1,
@@ -509,6 +723,7 @@ function readRtfDetail(input, options) {
509
723
  para: defaultParagraphState(),
510
724
  field: void 0,
511
725
  picture: void 0,
726
+ bookmark: void 0,
512
727
  inUnicodeWrapper: false
513
728
  };
514
729
  const stack = [root];
@@ -529,7 +744,14 @@ function readRtfDetail(input, options) {
529
744
  builder.appendText(text, state.char, hyperlink);
530
745
  return;
531
746
  }
532
- if (state.destination === "fieldInstruction" && state.field !== void 0) state.field.instruction += text;
747
+ if (state.destination === "fieldInstruction" && state.field !== void 0) {
748
+ state.field.instruction += text;
749
+ return;
750
+ }
751
+ if ((state.destination === "bookmarkStart" || state.destination === "bookmarkEnd") && state.bookmark !== void 0) {
752
+ state.bookmark.name += text;
753
+ return;
754
+ }
533
755
  };
534
756
  let index = 0;
535
757
  let textOffset = 0;
@@ -567,6 +789,11 @@ function readRtfDetail(input, options) {
567
789
  child.destination = kind;
568
790
  if (head.destination === "field") child.field = { instruction: "" };
569
791
  if (kind === "picture") child.picture = defaultPictureState();
792
+ if (kind === "bookmarkStart" || kind === "bookmarkEnd") child.bookmark = {
793
+ name: "",
794
+ columnFirst: void 0,
795
+ columnLast: void 0
796
+ };
570
797
  index = head.contentStart;
571
798
  } else index += 1;
572
799
  stack.push(child);
@@ -580,6 +807,14 @@ function readRtfDetail(input, options) {
580
807
  const image = buildPicture(state.picture, sink);
581
808
  if (image !== void 0) builder.addBlock(image);
582
809
  }
810
+ if (state.bookmark !== void 0) {
811
+ const bookmark = {
812
+ ...state.bookmark,
813
+ name: state.bookmark.name.trim()
814
+ };
815
+ if (state.destination === "bookmarkStart") builder.startBookmark(bookmark, state.para);
816
+ else if (state.destination === "bookmarkEnd") builder.endBookmark(bookmark.name);
817
+ }
583
818
  if (stack.length > 1) {
584
819
  stack.pop();
585
820
  const parent = stack[stack.length - 1];
@@ -642,7 +877,7 @@ function readRtfDetail(input, options) {
642
877
  continue;
643
878
  }
644
879
  flushBytes();
645
- applyControlWord(token.name, token.param, state, builder, header, sink);
880
+ applyControlWord(token.name, token.param, state, builder, header, section, sink);
646
881
  index += 1;
647
882
  }
648
883
  flushBytes();
@@ -652,7 +887,7 @@ function readRtfDetail(input, options) {
652
887
  message: `${String(stack.length - 1)} group(s) were still open at the end of the input; each is treated as closing there`
653
888
  });
654
889
  return {
655
- document: builder.finish(header.metadata, pageSize, margins, root.para),
890
+ document: builder.finish(header.metadata, section, root.para),
656
891
  diagnostics
657
892
  };
658
893
  }
@@ -729,6 +964,78 @@ function applyCharacterControlWord(name, param, state, header) {
729
964
  case "uc":
730
965
  if (param !== void 0 && param >= 0) state.uc = param;
731
966
  return true;
967
+ case "revised":
968
+ state.char.revision = {
969
+ ...state.char.revision,
970
+ revised: toggleValue(param)
971
+ };
972
+ return true;
973
+ case "revauth":
974
+ state.char.revision = {
975
+ ...state.char.revision,
976
+ revisedAuthor: param
977
+ };
978
+ return true;
979
+ case "revdttm":
980
+ state.char.revision = {
981
+ ...state.char.revision,
982
+ revisedDateTime: param
983
+ };
984
+ return true;
985
+ case "deleted":
986
+ state.char.revision = {
987
+ ...state.char.revision,
988
+ deleted: toggleValue(param)
989
+ };
990
+ return true;
991
+ case "revauthdel":
992
+ state.char.revision = {
993
+ ...state.char.revision,
994
+ deletedAuthor: param
995
+ };
996
+ return true;
997
+ case "revdttmdel":
998
+ state.char.revision = {
999
+ ...state.char.revision,
1000
+ deletedDateTime: param
1001
+ };
1002
+ return true;
1003
+ case "mvf":
1004
+ state.char.revision = {
1005
+ ...state.char.revision,
1006
+ moved: toggleValue(param) ? "moveFrom" : void 0
1007
+ };
1008
+ return true;
1009
+ case "mvt":
1010
+ state.char.revision = {
1011
+ ...state.char.revision,
1012
+ moved: toggleValue(param) ? "moveTo" : void 0
1013
+ };
1014
+ return true;
1015
+ case "mvauth":
1016
+ state.char.revision = {
1017
+ ...state.char.revision,
1018
+ movedAuthor: param
1019
+ };
1020
+ return true;
1021
+ case "mvdate":
1022
+ state.char.revision = {
1023
+ ...state.char.revision,
1024
+ movedDateTime: param
1025
+ };
1026
+ return true;
1027
+ case "crauth":
1028
+ state.char.revision = {
1029
+ ...state.char.revision,
1030
+ formatAuthor: param
1031
+ };
1032
+ return true;
1033
+ case "crdate":
1034
+ state.char.revision = {
1035
+ ...state.char.revision,
1036
+ formatDateTime: param
1037
+ };
1038
+ return true;
732
1039
  default:
733
1040
  if (name.startsWith("ul") && name !== "ulc") {
734
1041
  state.char.underline = toggleValue(param);
@@ -787,7 +1094,48 @@ function applyParagraphControlWord(name, param, state) {
787
1094
  default: return false;
788
1095
  }
789
1096
  }
790
- function applyStructureControlWord(name, param, state, builder, sink) {
1097
+ function applySectionControlWord(name, param, section, header, sink) {
1098
+ if (name === "sectd") {
1099
+ Object.assign(section, defaultSectionState(header));
1100
+ return true;
1101
+ }
1102
+ if (name === "sbkcol") {
1103
+ sink({
1104
+ code: RtfDiagnosticCodes.SECTION_BREAK_UNREPRESENTED,
1105
+ severity: "info",
1106
+ message: "\\sbkcol starts the section at a new column; ContentSection.breakType names page-level breaks only, so the break kind is dropped and the section itself is kept"
1107
+ });
1108
+ section.breakType = void 0;
1109
+ return true;
1110
+ }
1111
+ if (SECTION_BREAK_TYPES.has(name)) {
1112
+ section.breakType = SECTION_BREAK_TYPES.get(name);
1113
+ return true;
1114
+ }
1115
+ if (param === void 0) return false;
1116
+ switch (name) {
1117
+ case "pgwsxn":
1118
+ section.paperWidthTwips = param;
1119
+ return true;
1120
+ case "pghsxn":
1121
+ section.paperHeightTwips = param;
1122
+ return true;
1123
+ case "marglsxn":
1124
+ section.marginLeftTwips = param;
1125
+ return true;
1126
+ case "margrsxn":
1127
+ section.marginRightTwips = param;
1128
+ return true;
1129
+ case "margtsxn":
1130
+ section.marginTopTwips = param;
1131
+ return true;
1132
+ case "margbsxn":
1133
+ section.marginBottomTwips = param;
1134
+ return true;
1135
+ default: return false;
1136
+ }
1137
+ }
1138
+ function applyStructureControlWord(name, param, state, builder, section, sink) {
791
1139
  switch (name) {
792
1140
  case "par":
793
1141
  builder.endParagraph(state.para, true);
@@ -823,20 +1171,29 @@ function applyStructureControlWord(name, param, state, builder, sink) {
823
1171
  return true;
824
1172
  case "sect":
825
1173
  builder.endParagraph(state.para, true);
1174
+ builder.endSection(section, state.para);
826
1175
  return true;
827
- case "sectd": return true;
828
1176
  default: return false;
829
1177
  }
830
1178
  }
831
- function applyControlWord(name, param, state, builder, header, sink) {
1179
+ function applyControlWord(name, param, state, builder, header, section, sink) {
832
1180
  const picture = state.picture;
833
1181
  if (state.destination === "picture" && picture !== void 0) {
834
1182
  applyPictureControlWord(name, param, picture);
835
1183
  return;
836
1184
  }
1185
+ const bookmark = state.bookmark;
1186
+ if (bookmark !== void 0 && state.destination === "bookmarkStart") {
1187
+ if (name === "bkmkcolf") bookmark.columnFirst = param;
1188
+ else if (name === "bkmkcoll") bookmark.columnLast = param;
1189
+ return;
1190
+ }
1191
+ if (state.destination === "bookmarkEnd") return;
837
1192
  if (applyCharacterControlWord(name, param, state, header)) return;
1193
+ if (builder.applyCellDefinition(name, param)) return;
838
1194
  if (applyParagraphControlWord(name, param, state)) return;
839
- applyStructureControlWord(name, param, state, builder, sink);
1195
+ if (applySectionControlWord(name, param, section, header, sink)) return;
1196
+ applyStructureControlWord(name, param, state, builder, section, sink);
840
1197
  }
841
1198
  function readRtfContent(input, options = {}) {
842
1199
  return readRtfDetail(typeof input === "string" ? rtfBytesFromLatin1(input) : input, options);