rtf-codec 2.1.0 → 3.0.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.js CHANGED
@@ -3,7 +3,8 @@ import { RtfDiagnosticCodes, RtfInputTooLargeError, RtfNestingLimitExceededError
3
3
  import { appendBytes, asciiStringFromBytes, rtfBytesFromLatin1 } from "./bytes.js";
4
4
  import { halfPointsToPoints, pixelsToPoints, twipsToPoints } from "./units.js";
5
5
  import { applyCellDefinitionControlWord, newPendingCell, resolveBorder } from "./cell-format.js";
6
- import { NO_REVISION, bookmarkAnchorDescriptor, coalesceRunConstructs, provenanceDescriptors } from "./constructs.js";
6
+ import { NO_REVISION, bookmarkAnchorDescriptor, coalesceRunConstructs, formFieldContentControl, formFieldControlType, provenanceDescriptors } from "./constructs.js";
7
+ import { readEmbeddedObjectData } from "./embedded-object.js";
7
8
  import { decodeCodepageBytes } from "./codepage.js";
8
9
  import { groupHead, matchingGroupEnd } from "./group.js";
9
10
  import { HEADER_DESTINATIONS, readRtfHeader } from "./header.js";
@@ -22,6 +23,15 @@ const DESTINATION_KINDS = /* @__PURE__ */ new Map([
22
23
  ["listtext", "listText"],
23
24
  ["pntext", "listText"],
24
25
  ["upr", "unicodeWrapper"],
26
+ ["formfield", "formField"],
27
+ ["ffname", "formFieldName"],
28
+ ["ffhelptext", "formFieldHelpText"],
29
+ ["ffl", "formFieldListItem"],
30
+ ["ffdeftext", "skip"],
31
+ ["ffformat", "skip"],
32
+ ["ffstattext", "skip"],
33
+ ["ffentrymcr", "skip"],
34
+ ["ffexitmcr", "skip"],
25
35
  ["footnote", "skip"],
26
36
  ["header", "skip"],
27
37
  ["headerl", "skip"],
@@ -46,10 +56,15 @@ const DESTINATION_KINDS = /* @__PURE__ */ new Map([
46
56
  ["atnicn", "skip"],
47
57
  ["bkmkstart", "bookmarkStart"],
48
58
  ["bkmkend", "bookmarkEnd"],
49
- ["object", "skip"],
50
- ["objdata", "skip"],
59
+ ["object", "object"],
60
+ ["objdata", "objectData"],
51
61
  ["objclass", "skip"],
52
62
  ["objname", "skip"],
63
+ ["oleclsid", "skip"],
64
+ ["objalias", "skip"],
65
+ ["objsect", "skip"],
66
+ ["objtime", "skip"],
67
+ ["result", "skip"],
53
68
  ["do", "skip"],
54
69
  ["shp", "skip"],
55
70
  ["shptxt", "skip"],
@@ -67,6 +82,11 @@ const DESTINATION_KINDS = /* @__PURE__ */ new Map([
67
82
  ["fname", "skip"]
68
83
  ]);
69
84
  const SILENT_SKIP_DESTINATIONS = /* @__PURE__ */ new Set([
85
+ "ffdeftext",
86
+ "ffformat",
87
+ "ffstattext",
88
+ "ffentrymcr",
89
+ "ffexitmcr",
70
90
  "pn",
71
91
  "pnseclvl",
72
92
  "nonshppict",
@@ -81,7 +101,11 @@ const SILENT_SKIP_DESTINATIONS = /* @__PURE__ */ new Set([
81
101
  "atnicn",
82
102
  "objclass",
83
103
  "objname",
84
- "objdata",
104
+ "oleclsid",
105
+ "objalias",
106
+ "objsect",
107
+ "objtime",
108
+ "result",
85
109
  "shpinst",
86
110
  "shptxt",
87
111
  "ftnsep",
@@ -136,6 +160,21 @@ const SECTION_BREAK_TYPES = /* @__PURE__ */ new Map([
136
160
  ["sbkeven", "evenPage"],
137
161
  ["sbkodd", "oddPage"]
138
162
  ]);
163
+ const OBJECT_DATA_HEX_DIGITS = "0123456789abcdef";
164
+ function appendObjectDataHexText(objectData, text) {
165
+ let high = objectData.pendingHexNibble;
166
+ for (const character of text) {
167
+ const value = OBJECT_DATA_HEX_DIGITS.indexOf(character.toLowerCase());
168
+ if (value === -1) continue;
169
+ if (high === void 0) {
170
+ high = value;
171
+ continue;
172
+ }
173
+ objectData.bytes.push(high * 16 + value);
174
+ high = void 0;
175
+ }
176
+ objectData.pendingHexNibble = high;
177
+ }
139
178
  function defaultCharacterState() {
140
179
  return {
141
180
  bold: false,
@@ -174,8 +213,15 @@ function cloneGroupState(state) {
174
213
  para: { ...state.para },
175
214
  field: state.field,
176
215
  picture: state.picture,
216
+ pictureOwner: false,
217
+ objectData: state.objectData,
218
+ object: state.object,
219
+ objectDataOwner: false,
220
+ objectOwner: false,
221
+ resultOf: void 0,
177
222
  bookmark: state.bookmark,
178
- inUnicodeWrapper: state.inUnicodeWrapper
223
+ inUnicodeWrapper: state.inUnicodeWrapper,
224
+ isFieldGroup: state.isFieldGroup
179
225
  };
180
226
  }
181
227
  function runKey(char, fontName, color, hyperlink) {
@@ -250,6 +296,31 @@ function horizontalSpanAt(definitions, index) {
250
296
  while (definitions[index + span]?.horizontalMergeContinuation === true) span += 1;
251
297
  return span;
252
298
  }
299
+ function freshAccumulatorState() {
300
+ return {
301
+ blocks: [],
302
+ runs: [],
303
+ pendingRunKey: void 0,
304
+ pendingRunText: "",
305
+ pendingRunFields: {},
306
+ runProvenance: [],
307
+ pendingRunProvenance: [],
308
+ tableRows: [],
309
+ tableColumnRights: [],
310
+ rowCells: [],
311
+ cellBlocks: [],
312
+ pendingCellRights: [],
313
+ pendingCellDefinitions: [],
314
+ pendingCell: newPendingCell(),
315
+ rowLeftTwips: 0,
316
+ paragraphSerial: 0,
317
+ openBookmarks: /* @__PURE__ */ new Map(),
318
+ sectionBlockExtents: [],
319
+ cellBlockExtents: [],
320
+ pendingRunConstructs: [],
321
+ closingBookmarks: []
322
+ };
323
+ }
253
324
  var ContentBuilder = class {
254
325
  header;
255
326
  sink;
@@ -275,6 +346,8 @@ var ContentBuilder = class {
275
346
  cellBlockExtents = [];
276
347
  pendingRunConstructs = [];
277
348
  closingBookmarks = [];
349
+ openFormFields = [];
350
+ resultScratchStack = [];
278
351
  constructor(header, sink) {
279
352
  this.header = header;
280
353
  this.sink = sink;
@@ -316,6 +389,39 @@ var ContentBuilder = class {
316
389
  }
317
390
  this.closingBookmarks.push(open);
318
391
  }
392
+ startFormField() {
393
+ this.flushRun();
394
+ this.openFormFields.push({
395
+ paragraphSerial: this.paragraphSerial,
396
+ runIndex: this.runs.length
397
+ });
398
+ }
399
+ endFormField(descriptor) {
400
+ this.flushRun();
401
+ const open = this.openFormFields.pop();
402
+ if (open === void 0) return;
403
+ if (descriptor === void 0) {
404
+ this.sink({
405
+ code: RtfDiagnosticCodes.FORM_FIELD_KEYWORD_LOST,
406
+ severity: "warning",
407
+ message: "a form field's contentControl is dropped: its \\*\\fldinst instruction matched a form-field keyword partway through parsing but no longer did once the complete instruction was read"
408
+ });
409
+ return;
410
+ }
411
+ if (open.paragraphSerial !== this.paragraphSerial) {
412
+ this.sink({
413
+ code: RtfDiagnosticCodes.FORM_FIELD_SPAN_DROPPED,
414
+ severity: "warning",
415
+ message: "a form field's contentControl is dropped: its \\fldrslt content crossed a paragraph or table-cell boundary, and this reader's per-paragraph construct extent cannot span one"
416
+ });
417
+ return;
418
+ }
419
+ this.pendingRunConstructs.push({
420
+ descriptor,
421
+ startRun: open.runIndex,
422
+ endRun: this.runs.length
423
+ });
424
+ }
319
425
  appendText(text, char, hyperlink) {
320
426
  if (text.length === 0 || char.hidden) return;
321
427
  const fontName = char.fontIndex === void 0 ? void 0 : this.header.fonts.get(char.fontIndex)?.name;
@@ -558,9 +664,77 @@ var ContentBuilder = class {
558
664
  }
559
665
  return widths;
560
666
  }
561
- addBlock(block) {
667
+ addBlocks(blocks, inTable) {
668
+ if (blocks.length === 0) return;
562
669
  this.flushRun();
563
- this.blocks.push(block);
670
+ (inTable ? this.cellBlocks : this.blocks).push(...blocks);
671
+ }
672
+ captureAccumulatorState() {
673
+ return {
674
+ blocks: this.blocks,
675
+ runs: this.runs,
676
+ pendingRunKey: this.pendingRunKey,
677
+ pendingRunText: this.pendingRunText,
678
+ pendingRunFields: this.pendingRunFields,
679
+ runProvenance: this.runProvenance,
680
+ pendingRunProvenance: this.pendingRunProvenance,
681
+ tableRows: this.tableRows,
682
+ tableColumnRights: this.tableColumnRights,
683
+ rowCells: this.rowCells,
684
+ cellBlocks: this.cellBlocks,
685
+ pendingCellRights: this.pendingCellRights,
686
+ pendingCellDefinitions: this.pendingCellDefinitions,
687
+ pendingCell: this.pendingCell,
688
+ rowLeftTwips: this.rowLeftTwips,
689
+ paragraphSerial: this.paragraphSerial,
690
+ openBookmarks: this.openBookmarks,
691
+ sectionBlockExtents: this.sectionBlockExtents,
692
+ cellBlockExtents: this.cellBlockExtents,
693
+ pendingRunConstructs: this.pendingRunConstructs,
694
+ closingBookmarks: this.closingBookmarks
695
+ };
696
+ }
697
+ restoreAccumulatorState(saved) {
698
+ this.blocks = saved.blocks;
699
+ this.runs = saved.runs;
700
+ this.pendingRunKey = saved.pendingRunKey;
701
+ this.pendingRunText = saved.pendingRunText;
702
+ this.pendingRunFields = saved.pendingRunFields;
703
+ this.runProvenance = saved.runProvenance;
704
+ this.pendingRunProvenance = saved.pendingRunProvenance;
705
+ this.tableRows = saved.tableRows;
706
+ this.tableColumnRights = saved.tableColumnRights;
707
+ this.rowCells = saved.rowCells;
708
+ this.cellBlocks = saved.cellBlocks;
709
+ this.pendingCellRights = saved.pendingCellRights;
710
+ this.pendingCellDefinitions = saved.pendingCellDefinitions;
711
+ this.pendingCell = saved.pendingCell;
712
+ this.rowLeftTwips = saved.rowLeftTwips;
713
+ this.paragraphSerial = saved.paragraphSerial;
714
+ this.openBookmarks = saved.openBookmarks;
715
+ this.sectionBlockExtents = saved.sectionBlockExtents;
716
+ this.cellBlockExtents = saved.cellBlockExtents;
717
+ this.pendingRunConstructs = saved.pendingRunConstructs;
718
+ this.closingBookmarks = saved.closingBookmarks;
719
+ }
720
+ beginResultScratch() {
721
+ this.flushRun();
722
+ this.resultScratchStack.push(this.captureAccumulatorState());
723
+ this.restoreAccumulatorState(freshAccumulatorState());
724
+ }
725
+ endResultScratch(para) {
726
+ this.endParagraph(para, false);
727
+ this.closeTable();
728
+ const blocks = [...this.blocks, ...this.cellBlocks];
729
+ const saved = this.resultScratchStack.pop();
730
+ if (saved !== void 0) this.restoreAccumulatorState(saved);
731
+ return blocks;
732
+ }
733
+ discardUnclosedResultScratches() {
734
+ while (this.resultScratchStack.length > 0) {
735
+ const saved = this.resultScratchStack.pop();
736
+ if (saved !== void 0) this.restoreAccumulatorState(saved);
737
+ }
564
738
  }
565
739
  endSection(section, para) {
566
740
  this.endParagraph(para, false);
@@ -589,6 +763,7 @@ var ContentBuilder = class {
589
763
  this.openBookmarks.clear();
590
764
  }
591
765
  finish(metadata, section, para) {
766
+ this.discardUnclosedResultScratches();
592
767
  this.endSection(section, para);
593
768
  if (this.sections.length === 0) this.sections.push({
594
769
  ...sectionGeometry(section),
@@ -700,9 +875,50 @@ function defaultPictureState() {
700
875
  binary: []
701
876
  };
702
877
  }
878
+ function objectSizeHintClause(object) {
879
+ const widthTwips = object?.widthTwips;
880
+ const heightTwips = object?.heightTwips;
881
+ if (widthTwips !== void 0 && heightTwips !== void 0) return ` (the object's own \\objw/\\objh declared a ${twipsToPoints(widthTwips).toFixed(2)}pt x ${twipsToPoints(heightTwips).toFixed(2)}pt size)`;
882
+ if (widthTwips !== void 0) return ` (the object's own \\objw declared a ${twipsToPoints(widthTwips).toFixed(2)}pt width)`;
883
+ if (heightTwips !== void 0) return ` (the object's own \\objh declared a ${twipsToPoints(heightTwips).toFixed(2)}pt height)`;
884
+ return "";
885
+ }
886
+ function objectDataBytes(objectData) {
887
+ return Uint8Array.from(objectData.bytes);
888
+ }
889
+ function buildEmbeddedObject(objectData, object, sink) {
890
+ const bytes = objectDataBytes(objectData);
891
+ if (bytes.length === 0) {
892
+ sink({
893
+ code: RtfDiagnosticCodes.EMBEDDED_OBJECT_UNREADABLE,
894
+ severity: "warning",
895
+ message: `an \\object destination's \\objdata carried no payload${objectSizeHintClause(object)}`
896
+ });
897
+ return;
898
+ }
899
+ const embedded = readEmbeddedObjectData(bytes);
900
+ if (embedded === void 0) {
901
+ sink({
902
+ code: RtfDiagnosticCodes.EMBEDDED_OBJECT_UNREADABLE,
903
+ severity: "warning",
904
+ message: `an \\object destination's \\objdata is not a payload this reader produced (a real OLE object's OLESaveToStream data has no decoder here), so the object is dropped${objectSizeHintClause(object)}; its \\result fallback content, if any, is read in its place`
905
+ });
906
+ return;
907
+ }
908
+ return {
909
+ ...embedded,
910
+ kind: "embeddedObject"
911
+ };
912
+ }
703
913
  function toggleValue(param) {
704
914
  return param === void 0 || param !== 0;
705
915
  }
916
+ function formFieldValueBit(param) {
917
+ return param !== void 0 && param !== 0;
918
+ }
919
+ function formFieldValueNumber(param) {
920
+ return param ?? 0;
921
+ }
706
922
  function readRtfDetail(input, options) {
707
923
  options.signal?.throwIfAborted();
708
924
  const maxInputBytes = options.maxInputBytes ?? 67108864;
@@ -726,8 +942,15 @@ function readRtfDetail(input, options) {
726
942
  para: defaultParagraphState(),
727
943
  field: void 0,
728
944
  picture: void 0,
945
+ pictureOwner: false,
946
+ objectData: void 0,
947
+ object: void 0,
948
+ objectDataOwner: false,
949
+ objectOwner: false,
950
+ resultOf: void 0,
729
951
  bookmark: void 0,
730
- inUnicodeWrapper: false
952
+ inUnicodeWrapper: false,
953
+ isFieldGroup: false
731
954
  };
732
955
  const stack = [root];
733
956
  let state = root;
@@ -755,6 +978,21 @@ function readRtfDetail(input, options) {
755
978
  state.bookmark.name += text;
756
979
  return;
757
980
  }
981
+ if (state.destination === "formFieldName" && state.field?.formField !== void 0) {
982
+ state.field.formField.name += text;
983
+ return;
984
+ }
985
+ if (state.destination === "formFieldHelpText" && state.field?.formField !== void 0) {
986
+ state.field.formField.helpText += text;
987
+ return;
988
+ }
989
+ if (state.destination === "formFieldListItem" && state.field?.formField !== void 0) {
990
+ const items = state.field.formField.listItems;
991
+ const last = items.length - 1;
992
+ const current = items[last];
993
+ if (current !== void 0) items[last] = current + text;
994
+ return;
995
+ }
758
996
  };
759
997
  let index = 0;
760
998
  let textOffset = 0;
@@ -767,7 +1005,24 @@ function readRtfDetail(input, options) {
767
1005
  const known = head.destination === void 0 ? void 0 : DESTINATION_KINDS.get(head.destination);
768
1006
  const isHeaderTable = head.destination !== void 0 && HEADER_DESTINATIONS.has(head.destination);
769
1007
  const wrapperChild = state.inUnicodeWrapper;
770
- const kind = isHeaderTable || wrapperChild && head.destination !== "ud" ? "skip" : known ?? (head.ignorable ? "skip" : state.destination);
1008
+ const objectState = state.object;
1009
+ const isResultDestination = head.destination === "result" && objectState !== void 0;
1010
+ const isDuplicateResult = isResultDestination && objectState.resultSeen;
1011
+ if (objectState !== void 0 && isResultDestination) objectState.resultSeen = true;
1012
+ if (isDuplicateResult) sink({
1013
+ code: RtfDiagnosticCodes.EMBEDDED_OBJECT_UNREADABLE,
1014
+ severity: "warning",
1015
+ message: "an \\object destination has more than one \\result child, which RTF's own grammar does not allow; only the first is kept and this one is discarded"
1016
+ });
1017
+ const isObjectDataDestination = head.destination === "objdata" && known === "objectData";
1018
+ const isDuplicateObjectData = isObjectDataDestination && objectState?.objectDataSeen === true;
1019
+ if (isDuplicateObjectData) sink({
1020
+ code: RtfDiagnosticCodes.EMBEDDED_OBJECT_UNREADABLE,
1021
+ severity: "warning",
1022
+ message: "an \\object destination has more than one \\objdata child, which RTF's own grammar does not allow; only the first is decoded and this one is discarded"
1023
+ });
1024
+ else if (isObjectDataDestination && objectState !== void 0) objectState.objectDataSeen = true;
1025
+ const kind = isHeaderTable || wrapperChild && head.destination !== "ud" ? "skip" : isDuplicateObjectData || isDuplicateResult ? "skip" : isResultDestination ? "body" : known ?? (head.ignorable ? "skip" : state.destination);
771
1026
  if (head.destination !== void 0 && !isHeaderTable) {
772
1027
  if (head.ignorable && known === void 0) sink({
773
1028
  code: RtfDiagnosticCodes.UNKNOWN_DESTINATION_SKIPPED,
@@ -788,10 +1043,46 @@ function readRtfDetail(input, options) {
788
1043
  if (stack.length >= maxGroupDepth) throw new RtfNestingLimitExceededError(maxGroupDepth);
789
1044
  const child = cloneGroupState(state);
790
1045
  child.inUnicodeWrapper = kind === "unicodeWrapper";
1046
+ child.isFieldGroup = head.destination === "field";
791
1047
  if (known !== void 0) {
792
1048
  child.destination = kind;
793
- if (head.destination === "field") child.field = { instruction: "" };
794
- if (kind === "picture") child.picture = defaultPictureState();
1049
+ if (child.isFieldGroup) child.field = {
1050
+ instruction: "",
1051
+ formField: void 0,
1052
+ formFieldStarted: false
1053
+ };
1054
+ if (head.destination === "formfield" && child.field !== void 0) child.field.formField = {
1055
+ name: "",
1056
+ helpText: "",
1057
+ ownHelp: false,
1058
+ listItems: [],
1059
+ resultIndex: void 0,
1060
+ defaultResultIndex: void 0,
1061
+ protectedField: false
1062
+ };
1063
+ if (head.destination === "ffl" && child.field?.formField !== void 0) child.field.formField.listItems.push("");
1064
+ if (kind === "picture") {
1065
+ child.picture = defaultPictureState();
1066
+ child.pictureOwner = true;
1067
+ }
1068
+ if (kind === "objectData") {
1069
+ child.objectData = {
1070
+ bytes: [],
1071
+ pendingHexNibble: void 0
1072
+ };
1073
+ child.objectDataOwner = true;
1074
+ }
1075
+ if (kind === "object") {
1076
+ child.object = {
1077
+ decoded: false,
1078
+ objectDataSeen: false,
1079
+ resultSeen: false,
1080
+ resultBlocks: void 0,
1081
+ widthTwips: void 0,
1082
+ heightTwips: void 0
1083
+ };
1084
+ child.objectOwner = true;
1085
+ }
795
1086
  if (kind === "bookmarkStart" || kind === "bookmarkEnd") child.bookmark = {
796
1087
  name: "",
797
1088
  columnFirst: void 0,
@@ -799,6 +1090,15 @@ function readRtfDetail(input, options) {
799
1090
  };
800
1091
  index = head.contentStart;
801
1092
  } else index += 1;
1093
+ if (objectState !== void 0 && isResultDestination) {
1094
+ builder.beginResultScratch();
1095
+ child.resultOf = objectState;
1096
+ child.object = void 0;
1097
+ child.para = {
1098
+ ...child.para,
1099
+ inTable: false
1100
+ };
1101
+ }
802
1102
  stack.push(child);
803
1103
  state = child;
804
1104
  textOffset = 0;
@@ -806,9 +1106,26 @@ function readRtfDetail(input, options) {
806
1106
  }
807
1107
  if (token.kind === "groupEnd") {
808
1108
  flushBytes();
809
- if (state.destination === "picture" && state.picture !== void 0) {
1109
+ if (state.destination === "picture" && state.picture !== void 0 && state.pictureOwner) {
810
1110
  const image = buildPicture(state.picture, sink);
811
- if (image !== void 0) builder.addBlock(image);
1111
+ if (image !== void 0) builder.addBlocks([image], state.para.inTable);
1112
+ }
1113
+ if (state.destination === "objectData" && state.objectData !== void 0 && state.objectDataOwner) {
1114
+ const embedded = buildEmbeddedObject(state.objectData, state.object, sink);
1115
+ if (embedded !== void 0) {
1116
+ builder.addBlocks([embedded], state.para.inTable);
1117
+ if (state.object !== void 0) state.object.decoded = true;
1118
+ }
1119
+ }
1120
+ if (state.resultOf !== void 0) state.resultOf.resultBlocks = builder.endResultScratch(state.para);
1121
+ if (state.destination === "object" && state.object !== void 0 && state.objectOwner) {
1122
+ const objectState = state.object;
1123
+ if (!objectState.decoded && objectState.resultBlocks !== void 0) builder.addBlocks(objectState.resultBlocks, state.para.inTable);
1124
+ if (!objectState.objectDataSeen) sink({
1125
+ code: RtfDiagnosticCodes.EMBEDDED_OBJECT_UNREADABLE,
1126
+ severity: "warning",
1127
+ message: objectState.resultSeen ? "an \\object destination has no \\objdata payload at all; its \\result fallback content is used in its place" : "an \\object destination has neither \\objdata nor \\result content; the whole construct is dropped"
1128
+ });
812
1129
  }
813
1130
  if (state.bookmark !== void 0) {
814
1131
  const bookmark = {
@@ -818,6 +1135,14 @@ function readRtfDetail(input, options) {
818
1135
  if (state.destination === "bookmarkStart") builder.startBookmark(bookmark, state.para);
819
1136
  else if (state.destination === "bookmarkEnd") builder.endBookmark(bookmark.name);
820
1137
  }
1138
+ if (state.destination === "fieldInstruction" && state.field !== void 0 && !state.field.formFieldStarted && formFieldControlType(state.field.instruction) !== void 0) {
1139
+ builder.startFormField();
1140
+ state.field.formFieldStarted = true;
1141
+ }
1142
+ if (state.isFieldGroup && state.field?.formFieldStarted === true) {
1143
+ const descriptor = formFieldContentControl(state.field.instruction, state.field.formField);
1144
+ builder.endFormField(descriptor);
1145
+ }
821
1146
  if (stack.length > 1) {
822
1147
  stack.pop();
823
1148
  const parent = stack[stack.length - 1];
@@ -835,6 +1160,7 @@ function readRtfDetail(input, options) {
835
1160
  if (token.kind === "text") {
836
1161
  const slice = textOffset === 0 ? token.bytes : token.bytes.subarray(textOffset);
837
1162
  if (state.destination === "picture" && state.picture !== void 0) state.picture.hex += asciiStringFromBytes(slice);
1163
+ else if (state.destination === "objectData" && state.objectData !== void 0) appendObjectDataHexText(state.objectData, asciiStringFromBytes(slice));
838
1164
  else appendBytes(pendingBytes, slice);
839
1165
  index += 1;
840
1166
  textOffset = 0;
@@ -842,11 +1168,13 @@ function readRtfDetail(input, options) {
842
1168
  }
843
1169
  if (token.kind === "binary") {
844
1170
  if (state.destination === "picture" && state.picture !== void 0) appendBytes(state.picture.binary, token.bytes);
1171
+ else if (state.destination === "objectData" && state.objectData !== void 0) appendBytes(state.objectData.bytes, token.bytes);
845
1172
  index += 1;
846
1173
  continue;
847
1174
  }
848
1175
  if (token.kind === "hex") {
849
1176
  if (state.destination === "picture" && state.picture !== void 0) state.picture.binary.push(token.byte);
1177
+ else if (state.destination === "objectData" && state.objectData !== void 0) state.objectData.bytes.push(token.byte);
850
1178
  else pendingBytes.push(token.byte);
851
1179
  index += 1;
852
1180
  continue;
@@ -935,6 +1263,23 @@ function applyPictureControlWord(name, param, picture) {
935
1263
  default: return;
936
1264
  }
937
1265
  }
1266
+ function applyFormFieldControlWord(name, param, formField) {
1267
+ switch (name) {
1268
+ case "ffres":
1269
+ formField.resultIndex = formFieldValueNumber(param);
1270
+ return true;
1271
+ case "ffdefres":
1272
+ formField.defaultResultIndex = formFieldValueNumber(param);
1273
+ return true;
1274
+ case "ffprot":
1275
+ formField.protectedField = formFieldValueBit(param);
1276
+ return true;
1277
+ case "ffownhelp":
1278
+ formField.ownHelp = toggleValue(param);
1279
+ return true;
1280
+ default: return false;
1281
+ }
1282
+ }
938
1283
  function applyCharacterControlWord(name, param, state, header) {
939
1284
  switch (name) {
940
1285
  case "plain":
@@ -1170,7 +1515,7 @@ function applyStructureControlWord(name, param, state, builder, section, sink) {
1170
1515
  return true;
1171
1516
  case "page":
1172
1517
  builder.endParagraph(state.para, false);
1173
- builder.addBlock({ kind: "pageBreak" });
1518
+ builder.addBlocks([{ kind: "pageBreak" }], state.para.inTable);
1174
1519
  return true;
1175
1520
  case "sect":
1176
1521
  builder.endParagraph(state.para, true);
@@ -1185,6 +1530,12 @@ function applyControlWord(name, param, state, builder, header, section, sink) {
1185
1530
  applyPictureControlWord(name, param, picture);
1186
1531
  return;
1187
1532
  }
1533
+ const object = state.object;
1534
+ if (state.destination === "object" && object !== void 0) {
1535
+ if (name === "objw") object.widthTwips = param;
1536
+ else if (name === "objh") object.heightTwips = param;
1537
+ return;
1538
+ }
1188
1539
  const bookmark = state.bookmark;
1189
1540
  if (bookmark !== void 0 && state.destination === "bookmarkStart") {
1190
1541
  if (name === "bkmkcolf") bookmark.columnFirst = param;
@@ -1192,6 +1543,9 @@ function applyControlWord(name, param, state, builder, header, section, sink) {
1192
1543
  return;
1193
1544
  }
1194
1545
  if (state.destination === "bookmarkEnd") return;
1546
+ const formField = state.field?.formField;
1547
+ if (state.destination === "formField" && formField !== void 0 && applyFormFieldControlWord(name, param, formField)) return;
1548
+ if (state.destination === "formField" || state.destination === "formFieldName" || state.destination === "formFieldHelpText" || state.destination === "formFieldListItem") return;
1195
1549
  if (applyCharacterControlWord(name, param, state, header)) return;
1196
1550
  if (builder.applyCellDefinition(name, param)) return;
1197
1551
  if (applyParagraphControlWord(name, param, state)) return;