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/README.md +90 -45
- package/dist/base64.d.cts +1 -1
- package/dist/base64.d.ts +1 -1
- package/dist/codepage.d.cts +1 -1
- package/dist/codepage.d.ts +1 -1
- package/dist/constructs.cjs +35 -0
- package/dist/constructs.d.cts +13 -2
- package/dist/constructs.d.ts +13 -2
- package/dist/constructs.js +34 -1
- package/dist/{diagnostics-BgG_KAiN.d.cts → diagnostics-CjMoO7x1.d.cts} +3 -1
- package/dist/{diagnostics-BgG_KAiN.d.ts → diagnostics-CjMoO7x1.d.ts} +3 -1
- package/dist/diagnostics.cjs +3 -1
- package/dist/diagnostics.d.cts +1 -1
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.js +3 -1
- package/dist/embedded-object.cjs +215 -0
- package/dist/embedded-object.d.cts +6 -0
- package/dist/embedded-object.d.ts +6 -0
- package/dist/embedded-object.js +213 -0
- package/dist/header.d.cts +1 -1
- package/dist/header.d.ts +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/options.d.cts +1 -1
- package/dist/options.d.ts +1 -1
- package/dist/read.cjs +367 -13
- package/dist/read.d.cts +1 -1
- package/dist/read.d.ts +1 -1
- package/dist/read.js +368 -14
- package/dist/write.cjs +241 -17
- package/dist/write.js +241 -17
- package/package.json +2 -1
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", "
|
|
50
|
-
["objdata", "
|
|
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
|
-
"
|
|
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
|
-
|
|
667
|
+
addBlocks(blocks, inTable) {
|
|
668
|
+
if (blocks.length === 0) return;
|
|
562
669
|
this.flushRun();
|
|
563
|
-
this.blocks.push(
|
|
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
|
|
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 (
|
|
794
|
-
|
|
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.
|
|
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.
|
|
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;
|