rtf-codec 0.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/LICENSE +21 -0
- package/README.md +226 -0
- package/dist/base64.cjs +66 -0
- package/dist/base64.d.cts +7 -0
- package/dist/base64.d.ts +7 -0
- package/dist/base64.js +62 -0
- package/dist/bytes.cjs +25 -0
- package/dist/bytes.d.cts +6 -0
- package/dist/bytes.d.ts +6 -0
- package/dist/bytes.js +22 -0
- package/dist/cell-format.cjs +142 -0
- package/dist/cell-format.d.cts +25 -0
- package/dist/cell-format.d.ts +25 -0
- package/dist/cell-format.js +137 -0
- package/dist/codec.cjs +29 -0
- package/dist/codec.d.cts +2344 -0
- package/dist/codec.d.ts +2344 -0
- package/dist/codec.js +26 -0
- package/dist/codepage.cjs +98 -0
- package/dist/codepage.d.cts +10 -0
- package/dist/codepage.d.ts +10 -0
- package/dist/codepage.js +92 -0
- package/dist/constructs.cjs +131 -0
- package/dist/constructs.d.cts +31 -0
- package/dist/constructs.d.ts +31 -0
- package/dist/constructs.js +121 -0
- package/dist/diagnostics-BgG_KAiN.d.cts +52 -0
- package/dist/diagnostics-BgG_KAiN.d.ts +52 -0
- package/dist/diagnostics.cjs +76 -0
- package/dist/diagnostics.d.cts +2 -0
- package/dist/diagnostics.d.ts +2 -0
- package/dist/diagnostics.js +68 -0
- package/dist/group.cjs +40 -0
- package/dist/group.d.cts +11 -0
- package/dist/group.d.ts +11 -0
- package/dist/group.js +38 -0
- package/dist/header.cjs +433 -0
- package/dist/header.d.cts +44 -0
- package/dist/header.d.ts +44 -0
- package/dist/header.js +431 -0
- package/dist/index.cjs +28 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/list-id.cjs +30 -0
- package/dist/list-id.d.cts +16 -0
- package/dist/list-id.d.ts +16 -0
- package/dist/list-id.js +27 -0
- package/dist/options.cjs +7 -0
- package/dist/options.d.cts +18 -0
- package/dist/options.d.ts +18 -0
- package/dist/options.js +5 -0
- package/dist/read.cjs +1211 -0
- package/dist/read.d.cts +16 -0
- package/dist/read.d.ts +16 -0
- package/dist/read.js +1209 -0
- package/dist/tokenize.cjs +155 -0
- package/dist/tokenize.d.cts +25 -0
- package/dist/tokenize.d.ts +25 -0
- package/dist/tokenize.js +154 -0
- package/dist/units.cjs +43 -0
- package/dist/units.d.cts +17 -0
- package/dist/units.d.ts +17 -0
- package/dist/units.js +29 -0
- package/dist/write.cjs +532 -0
- package/dist/write.d.cts +7 -0
- package/dist/write.d.ts +7 -0
- package/dist/write.js +530 -0
- package/package.json +95 -2
package/dist/read.js
ADDED
|
@@ -0,0 +1,1209 @@
|
|
|
1
|
+
import { bytesToBase64, hexToBytes } from "./base64.js";
|
|
2
|
+
import { RtfDiagnosticCodes, RtfInputTooLargeError, RtfNestingLimitExceededError, RtfNotAnRtfDocumentError } from "./diagnostics.js";
|
|
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";
|
|
7
|
+
import { decodeCodepageBytes } from "./codepage.js";
|
|
8
|
+
import { groupHead, matchingGroupEnd } from "./group.js";
|
|
9
|
+
import { HEADER_DESTINATIONS, readRtfHeader } from "./header.js";
|
|
10
|
+
import { mintRtfListNumId } from "./list-id.js";
|
|
11
|
+
import "./options.js";
|
|
12
|
+
import { tokenizeRtf } from "./tokenize.js";
|
|
13
|
+
import { assembleTree } from "document-schema.js";
|
|
14
|
+
//#region src/read.ts
|
|
15
|
+
const DESTINATION_KINDS = /* @__PURE__ */ new Map([
|
|
16
|
+
["fldrslt", "body"],
|
|
17
|
+
["ud", "body"],
|
|
18
|
+
["shppict", "body"],
|
|
19
|
+
["field", "body"],
|
|
20
|
+
["pict", "picture"],
|
|
21
|
+
["fldinst", "fieldInstruction"],
|
|
22
|
+
["listtext", "listText"],
|
|
23
|
+
["pntext", "listText"],
|
|
24
|
+
["upr", "unicodeWrapper"],
|
|
25
|
+
["footnote", "skip"],
|
|
26
|
+
["header", "skip"],
|
|
27
|
+
["headerl", "skip"],
|
|
28
|
+
["headerr", "skip"],
|
|
29
|
+
["headerf", "skip"],
|
|
30
|
+
["footer", "skip"],
|
|
31
|
+
["footerl", "skip"],
|
|
32
|
+
["footerr", "skip"],
|
|
33
|
+
["footerf", "skip"],
|
|
34
|
+
["ftnsep", "skip"],
|
|
35
|
+
["ftnsepc", "skip"],
|
|
36
|
+
["ftncn", "skip"],
|
|
37
|
+
["aftnsep", "skip"],
|
|
38
|
+
["aftnsepc", "skip"],
|
|
39
|
+
["aftncn", "skip"],
|
|
40
|
+
["annotation", "skip"],
|
|
41
|
+
["atnid", "skip"],
|
|
42
|
+
["atnauthor", "skip"],
|
|
43
|
+
["atnref", "skip"],
|
|
44
|
+
["atndate", "skip"],
|
|
45
|
+
["atnparent", "skip"],
|
|
46
|
+
["atnicn", "skip"],
|
|
47
|
+
["bkmkstart", "bookmarkStart"],
|
|
48
|
+
["bkmkend", "bookmarkEnd"],
|
|
49
|
+
["object", "skip"],
|
|
50
|
+
["objdata", "skip"],
|
|
51
|
+
["objclass", "skip"],
|
|
52
|
+
["objname", "skip"],
|
|
53
|
+
["do", "skip"],
|
|
54
|
+
["shp", "skip"],
|
|
55
|
+
["shptxt", "skip"],
|
|
56
|
+
["shpinst", "skip"],
|
|
57
|
+
["nonshppict", "skip"],
|
|
58
|
+
["xe", "skip"],
|
|
59
|
+
["tc", "skip"],
|
|
60
|
+
["tcn", "skip"],
|
|
61
|
+
["pn", "skip"],
|
|
62
|
+
["pnseclvl", "skip"],
|
|
63
|
+
["template", "skip"],
|
|
64
|
+
["comment", "skip"],
|
|
65
|
+
["falt", "skip"],
|
|
66
|
+
["panose", "skip"],
|
|
67
|
+
["fname", "skip"]
|
|
68
|
+
]);
|
|
69
|
+
const SILENT_SKIP_DESTINATIONS = /* @__PURE__ */ new Set([
|
|
70
|
+
"pn",
|
|
71
|
+
"pnseclvl",
|
|
72
|
+
"nonshppict",
|
|
73
|
+
"falt",
|
|
74
|
+
"panose",
|
|
75
|
+
"fname",
|
|
76
|
+
"atnid",
|
|
77
|
+
"atnauthor",
|
|
78
|
+
"atnref",
|
|
79
|
+
"atndate",
|
|
80
|
+
"atnparent",
|
|
81
|
+
"atnicn",
|
|
82
|
+
"objclass",
|
|
83
|
+
"objname",
|
|
84
|
+
"objdata",
|
|
85
|
+
"shpinst",
|
|
86
|
+
"shptxt",
|
|
87
|
+
"ftnsep",
|
|
88
|
+
"ftnsepc",
|
|
89
|
+
"ftncn",
|
|
90
|
+
"aftnsep",
|
|
91
|
+
"aftnsepc",
|
|
92
|
+
"aftncn",
|
|
93
|
+
"template",
|
|
94
|
+
"comment",
|
|
95
|
+
"xe",
|
|
96
|
+
"tc",
|
|
97
|
+
"tcn"
|
|
98
|
+
]);
|
|
99
|
+
const SPECIAL_CHARACTER_TEXT = /* @__PURE__ */ new Map([
|
|
100
|
+
["tab", " "],
|
|
101
|
+
["line", "\n"],
|
|
102
|
+
["softline", "\n"],
|
|
103
|
+
["emdash", "—"],
|
|
104
|
+
["endash", "–"],
|
|
105
|
+
["bullet", "•"],
|
|
106
|
+
["lquote", "‘"],
|
|
107
|
+
["rquote", "’"],
|
|
108
|
+
["ldblquote", "“"],
|
|
109
|
+
["rdblquote", "”"],
|
|
110
|
+
["emspace", " "],
|
|
111
|
+
["enspace", " "],
|
|
112
|
+
["qmspace", " "],
|
|
113
|
+
["zwj", ""],
|
|
114
|
+
["zwnj", ""],
|
|
115
|
+
["ltrmark", ""],
|
|
116
|
+
["rtlmark", ""]
|
|
117
|
+
]);
|
|
118
|
+
const SPECIAL_SYMBOL_TEXT = /* @__PURE__ */ new Map([
|
|
119
|
+
["~", "\xA0"],
|
|
120
|
+
["-", ""],
|
|
121
|
+
["_", "‑"],
|
|
122
|
+
["\\", "\\"],
|
|
123
|
+
["{", "{"],
|
|
124
|
+
["}", "}"]
|
|
125
|
+
]);
|
|
126
|
+
const ALIGNMENTS = /* @__PURE__ */ new Map([
|
|
127
|
+
["ql", "left"],
|
|
128
|
+
["qc", "center"],
|
|
129
|
+
["qr", "right"],
|
|
130
|
+
["qj", "justify"]
|
|
131
|
+
]);
|
|
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
|
+
]);
|
|
139
|
+
function defaultCharacterState() {
|
|
140
|
+
return {
|
|
141
|
+
bold: false,
|
|
142
|
+
italic: false,
|
|
143
|
+
underline: false,
|
|
144
|
+
strike: false,
|
|
145
|
+
hidden: false,
|
|
146
|
+
fontIndex: void 0,
|
|
147
|
+
sizeHalfPoints: 24,
|
|
148
|
+
colorIndex: void 0,
|
|
149
|
+
revision: NO_REVISION
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function defaultParagraphState() {
|
|
153
|
+
return {
|
|
154
|
+
alignment: void 0,
|
|
155
|
+
indentLeftTwips: 0,
|
|
156
|
+
indentFirstLineTwips: 0,
|
|
157
|
+
spaceBeforeTwips: 0,
|
|
158
|
+
spaceAfterTwips: 0,
|
|
159
|
+
lineSpacingTwips: void 0,
|
|
160
|
+
lineSpacingIsMultiple: false,
|
|
161
|
+
styleIndex: void 0,
|
|
162
|
+
outlineLevel: void 0,
|
|
163
|
+
listOverrideIndex: void 0,
|
|
164
|
+
listLevel: 0,
|
|
165
|
+
inTable: false,
|
|
166
|
+
pageBreakBefore: false
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function cloneGroupState(state) {
|
|
170
|
+
return {
|
|
171
|
+
destination: state.destination,
|
|
172
|
+
uc: state.uc,
|
|
173
|
+
char: { ...state.char },
|
|
174
|
+
para: { ...state.para },
|
|
175
|
+
field: state.field,
|
|
176
|
+
picture: state.picture,
|
|
177
|
+
bookmark: state.bookmark,
|
|
178
|
+
inUnicodeWrapper: state.inUnicodeWrapper
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function runKey(char, fontName, color, hyperlink) {
|
|
182
|
+
return [
|
|
183
|
+
char.bold ? "b" : "",
|
|
184
|
+
char.italic ? "i" : "",
|
|
185
|
+
char.underline ? "u" : "",
|
|
186
|
+
char.strike ? "s" : "",
|
|
187
|
+
fontName ?? "",
|
|
188
|
+
String(char.sizeHalfPoints),
|
|
189
|
+
color === void 0 ? "" : `${String(color.r)},${String(color.g)},${String(color.b)}`,
|
|
190
|
+
hyperlink ?? "",
|
|
191
|
+
JSON.stringify(char.revision)
|
|
192
|
+
].join("|");
|
|
193
|
+
}
|
|
194
|
+
const HYPERLINK_TARGET = /HYPERLINK\s+"([^"]*)"/i;
|
|
195
|
+
const HYPERLINK_BARE_TARGET = /HYPERLINK\s+([^\s"\\]\S*)/i;
|
|
196
|
+
const HYPERLINK_ANCHOR = /\\l\s+"([^"]*)"/i;
|
|
197
|
+
function hyperlinkFromInstruction(instruction) {
|
|
198
|
+
const quoted = HYPERLINK_TARGET.exec(instruction);
|
|
199
|
+
const anchor = HYPERLINK_ANCHOR.exec(instruction);
|
|
200
|
+
const target = quoted?.[1] ?? HYPERLINK_BARE_TARGET.exec(instruction)?.[1];
|
|
201
|
+
if (target === void 0) return anchor?.[1] === void 0 ? void 0 : `#${anchor[1]}`;
|
|
202
|
+
return anchor?.[1] === void 0 ? target : `${target}#${anchor[1]}`;
|
|
203
|
+
}
|
|
204
|
+
function skipUnicodeFallback(tokens, from, count) {
|
|
205
|
+
let { index, textOffset } = from;
|
|
206
|
+
let remaining = count;
|
|
207
|
+
while (remaining > 0 && index < tokens.length) {
|
|
208
|
+
const token = tokens[index];
|
|
209
|
+
if (token === void 0) break;
|
|
210
|
+
if (token.kind === "groupStart" || token.kind === "groupEnd") break;
|
|
211
|
+
if (token.kind === "text") {
|
|
212
|
+
const available = token.bytes.length - textOffset;
|
|
213
|
+
const consumed = Math.min(available, remaining);
|
|
214
|
+
remaining -= consumed;
|
|
215
|
+
textOffset += consumed;
|
|
216
|
+
if (textOffset >= token.bytes.length) {
|
|
217
|
+
index += 1;
|
|
218
|
+
textOffset = 0;
|
|
219
|
+
}
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
remaining -= 1;
|
|
223
|
+
index += 1;
|
|
224
|
+
textOffset = 0;
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
index,
|
|
228
|
+
textOffset
|
|
229
|
+
};
|
|
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
|
+
}
|
|
253
|
+
var ContentBuilder = class {
|
|
254
|
+
header;
|
|
255
|
+
sink;
|
|
256
|
+
sections = [];
|
|
257
|
+
blocks = [];
|
|
258
|
+
runs = [];
|
|
259
|
+
pendingRunKey;
|
|
260
|
+
pendingRunText = "";
|
|
261
|
+
pendingRunFields = {};
|
|
262
|
+
runProvenance = [];
|
|
263
|
+
pendingRunProvenance = [];
|
|
264
|
+
tableRows = [];
|
|
265
|
+
tableColumnRights = [];
|
|
266
|
+
rowCells = [];
|
|
267
|
+
cellBlocks = [];
|
|
268
|
+
pendingCellRights = [];
|
|
269
|
+
pendingCellDefinitions = [];
|
|
270
|
+
pendingCell = newPendingCell();
|
|
271
|
+
rowLeftTwips = 0;
|
|
272
|
+
paragraphSerial = 0;
|
|
273
|
+
openBookmarks = /* @__PURE__ */ new Map();
|
|
274
|
+
sectionBlockExtents = [];
|
|
275
|
+
cellBlockExtents = [];
|
|
276
|
+
pendingRunConstructs = [];
|
|
277
|
+
closingBookmarks = [];
|
|
278
|
+
constructor(header, sink) {
|
|
279
|
+
this.header = header;
|
|
280
|
+
this.sink = sink;
|
|
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
|
+
}
|
|
319
|
+
appendText(text, char, hyperlink) {
|
|
320
|
+
if (text.length === 0 || char.hidden) return;
|
|
321
|
+
const fontName = char.fontIndex === void 0 ? void 0 : this.header.fonts.get(char.fontIndex)?.name;
|
|
322
|
+
const color = char.colorIndex === void 0 ? void 0 : this.header.colors[char.colorIndex];
|
|
323
|
+
const key = runKey(char, fontName, color, hyperlink);
|
|
324
|
+
if (this.pendingRunKey !== key) {
|
|
325
|
+
this.flushRun();
|
|
326
|
+
this.pendingRunKey = key;
|
|
327
|
+
this.pendingRunFields = buildRunFields(char, fontName, color, hyperlink);
|
|
328
|
+
this.pendingRunProvenance = provenanceDescriptors(char.revision, this.header.revisionAuthors);
|
|
329
|
+
}
|
|
330
|
+
this.pendingRunText += text;
|
|
331
|
+
}
|
|
332
|
+
flushRun() {
|
|
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
|
+
}
|
|
340
|
+
this.pendingRunText = "";
|
|
341
|
+
this.pendingRunKey = void 0;
|
|
342
|
+
this.pendingRunFields = {};
|
|
343
|
+
this.pendingRunProvenance = [];
|
|
344
|
+
}
|
|
345
|
+
endParagraph(para, force) {
|
|
346
|
+
this.flushRun();
|
|
347
|
+
if (!force && this.runs.length === 0) return;
|
|
348
|
+
const target = para.inTable ? this.cellBlocks : this.blocks;
|
|
349
|
+
if (!para.inTable) this.closeTable();
|
|
350
|
+
const blockIndex = target.length;
|
|
351
|
+
target.push(this.buildParagraph(para));
|
|
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);
|
|
386
|
+
}
|
|
387
|
+
buildParagraph(para) {
|
|
388
|
+
const style = para.styleIndex === void 0 ? void 0 : this.header.styles.get(para.styleIndex);
|
|
389
|
+
const headingLevel = para.outlineLevel === void 0 ? style?.headingLevel : para.outlineLevel + 1;
|
|
390
|
+
const constructs = this.takeRunConstructs();
|
|
391
|
+
const paragraph = {
|
|
392
|
+
kind: "paragraph",
|
|
393
|
+
runs: this.runs,
|
|
394
|
+
...constructs.length === 0 ? {} : { constructs }
|
|
395
|
+
};
|
|
396
|
+
const withStyle = style?.name === void 0 || style.name.length === 0 ? paragraph : {
|
|
397
|
+
...paragraph,
|
|
398
|
+
styleId: style.name
|
|
399
|
+
};
|
|
400
|
+
return {
|
|
401
|
+
...headingLevel === void 0 ? withStyle : {
|
|
402
|
+
...withStyle,
|
|
403
|
+
headingLevel
|
|
404
|
+
},
|
|
405
|
+
...para.alignment === void 0 ? {} : { alignment: para.alignment },
|
|
406
|
+
...para.indentLeftTwips === 0 ? {} : { indentLeftPt: twipsToPoints(para.indentLeftTwips) },
|
|
407
|
+
...para.indentFirstLineTwips === 0 ? {} : { indentFirstLinePt: twipsToPoints(para.indentFirstLineTwips) },
|
|
408
|
+
...para.spaceBeforeTwips === 0 ? {} : { spacingBeforePt: twipsToPoints(para.spaceBeforeTwips) },
|
|
409
|
+
...para.spaceAfterTwips === 0 ? {} : { spacingAfterPt: twipsToPoints(para.spaceAfterTwips) },
|
|
410
|
+
...this.lineSpacingFields(para),
|
|
411
|
+
...para.pageBreakBefore ? { pageBreakBefore: true } : {},
|
|
412
|
+
...this.listFields(para)
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
lineSpacingFields(para) {
|
|
416
|
+
const value = para.lineSpacingTwips;
|
|
417
|
+
if (value === void 0 || value === 0 || !para.lineSpacingIsMultiple) return {};
|
|
418
|
+
const multiple = Math.abs(value) / 240;
|
|
419
|
+
return multiple > 0 ? { lineSpacing: multiple } : {};
|
|
420
|
+
}
|
|
421
|
+
listFields(para) {
|
|
422
|
+
const overrideIndex = para.listOverrideIndex;
|
|
423
|
+
if (overrideIndex === void 0 || overrideIndex === 0) return {};
|
|
424
|
+
const list = this.header.lists.get(overrideIndex);
|
|
425
|
+
const level = list?.levels[para.listLevel] ?? list?.levels[0];
|
|
426
|
+
const type = level?.numberFormat === 23 ? "bullet" : "ordered";
|
|
427
|
+
return { list: {
|
|
428
|
+
numId: mintRtfListNumId({
|
|
429
|
+
listOverrideIndex: overrideIndex,
|
|
430
|
+
type,
|
|
431
|
+
...level?.startAt === void 0 ? {} : { start: level.startAt }
|
|
432
|
+
}),
|
|
433
|
+
level: para.listLevel
|
|
434
|
+
} };
|
|
435
|
+
}
|
|
436
|
+
startRowDefinition() {
|
|
437
|
+
this.pendingCellRights = [];
|
|
438
|
+
this.pendingCellDefinitions = [];
|
|
439
|
+
this.pendingCell = newPendingCell();
|
|
440
|
+
this.rowLeftTwips = 0;
|
|
441
|
+
}
|
|
442
|
+
setRowLeft(twips) {
|
|
443
|
+
this.rowLeftTwips = twips;
|
|
444
|
+
}
|
|
445
|
+
applyCellDefinition(name, param) {
|
|
446
|
+
return applyCellDefinitionControlWord(name, param, this.pendingCell);
|
|
447
|
+
}
|
|
448
|
+
addCellBoundary(rightTwips) {
|
|
449
|
+
this.pendingCellRights.push(rightTwips);
|
|
450
|
+
this.pendingCellDefinitions.push(this.pendingCell);
|
|
451
|
+
this.pendingCell = newPendingCell();
|
|
452
|
+
}
|
|
453
|
+
endCell(para) {
|
|
454
|
+
this.endParagraph(para, false);
|
|
455
|
+
this.flushClosingBookmarks(true, this.cellBlocks.length);
|
|
456
|
+
this.rowCells.push({ blocks: insertConstructMarkers(this.cellBlocks, this.cellBlockExtents) });
|
|
457
|
+
this.cellBlocks = [];
|
|
458
|
+
this.cellBlockExtents = [];
|
|
459
|
+
}
|
|
460
|
+
endRow(para) {
|
|
461
|
+
if (this.cellBlocks.length > 0 || this.pendingRunText.length > 0) this.endCell(para);
|
|
462
|
+
if (this.rowCells.length === 0) {
|
|
463
|
+
this.sink({
|
|
464
|
+
code: RtfDiagnosticCodes.TABLE_ROW_WITHOUT_DEFINITION,
|
|
465
|
+
severity: "warning",
|
|
466
|
+
message: "a \\row closed a table row that contained no \\cell marks"
|
|
467
|
+
});
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
this.tableRows.push({
|
|
471
|
+
cells: this.rowCells,
|
|
472
|
+
definitions: this.pendingCellDefinitions
|
|
473
|
+
});
|
|
474
|
+
if (this.tableColumnRights.length === 0) this.tableColumnRights = [...this.pendingCellRights];
|
|
475
|
+
this.rowCells = [];
|
|
476
|
+
}
|
|
477
|
+
closeTable() {
|
|
478
|
+
if (this.tableRows.length === 0) return;
|
|
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)));
|
|
481
|
+
this.blocks.push({
|
|
482
|
+
kind: "table",
|
|
483
|
+
rows,
|
|
484
|
+
columnWidthsPt: this.columnWidths(columnCount)
|
|
485
|
+
});
|
|
486
|
+
this.tableRows = [];
|
|
487
|
+
this.tableColumnRights = [];
|
|
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
|
+
}
|
|
539
|
+
columnWidths(columnCount) {
|
|
540
|
+
const rights = this.tableColumnRights;
|
|
541
|
+
const widths = [];
|
|
542
|
+
let previous = this.rowLeftTwips;
|
|
543
|
+
for (const right of rights.slice(0, columnCount)) {
|
|
544
|
+
widths.push(twipsToPoints(right - previous));
|
|
545
|
+
previous = right;
|
|
546
|
+
}
|
|
547
|
+
const usable = twipsToPoints(this.header.page.paperWidthTwips - this.header.page.marginLeftTwips - this.header.page.marginRightTwips);
|
|
548
|
+
if (widths.length !== columnCount || widths.some((width) => width <= 0)) {
|
|
549
|
+
this.sink({
|
|
550
|
+
code: RtfDiagnosticCodes.TABLE_COLUMN_WIDTH_INVALID,
|
|
551
|
+
severity: "warning",
|
|
552
|
+
message: "the row's \\cellxN boundaries do not describe increasing column widths for every column; falling back to an even split of the page's text width"
|
|
553
|
+
});
|
|
554
|
+
return Array.from({ length: columnCount }, () => usable / columnCount);
|
|
555
|
+
}
|
|
556
|
+
return widths;
|
|
557
|
+
}
|
|
558
|
+
addBlock(block) {
|
|
559
|
+
this.flushRun();
|
|
560
|
+
this.blocks.push(block);
|
|
561
|
+
}
|
|
562
|
+
endSection(section, para) {
|
|
563
|
+
this.endParagraph(para, false);
|
|
564
|
+
this.closeTable();
|
|
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
|
+
}
|
|
573
|
+
this.sections.push({
|
|
574
|
+
...sectionGeometry(section),
|
|
575
|
+
...section.breakType === void 0 ? {} : { breakType: section.breakType },
|
|
576
|
+
blocks
|
|
577
|
+
});
|
|
578
|
+
this.blocks = [];
|
|
579
|
+
}
|
|
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);
|
|
590
|
+
if (this.sections.length === 0) this.sections.push({
|
|
591
|
+
...sectionGeometry(section),
|
|
592
|
+
blocks: []
|
|
593
|
+
});
|
|
594
|
+
return {
|
|
595
|
+
kind: "wordprocessing",
|
|
596
|
+
metadata,
|
|
597
|
+
sections: this.sections
|
|
598
|
+
};
|
|
599
|
+
}
|
|
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
|
+
}
|
|
626
|
+
function buildRunFields(char, fontName, color, hyperlink) {
|
|
627
|
+
return {
|
|
628
|
+
...char.bold ? { bold: true } : {},
|
|
629
|
+
...char.italic ? { italic: true } : {},
|
|
630
|
+
...char.underline ? { underline: true } : {},
|
|
631
|
+
...char.strike ? { strike: true } : {},
|
|
632
|
+
...fontName === void 0 || fontName.length === 0 ? {} : { fontFamily: fontName },
|
|
633
|
+
sizePt: halfPointsToPoints(char.sizeHalfPoints),
|
|
634
|
+
...color === void 0 ? {} : { color },
|
|
635
|
+
...hyperlink === void 0 ? {} : { hyperlink }
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function buildPicture(picture, sink) {
|
|
639
|
+
if (picture.format === void 0) {
|
|
640
|
+
sink({
|
|
641
|
+
code: RtfDiagnosticCodes.UNSUPPORTED_PICTURE_FORMAT,
|
|
642
|
+
severity: "warning",
|
|
643
|
+
message: `a \\pict destination declared ${picture.unsupportedFormat ?? "no"} picture format; ContentImageBlock carries PNG and JPEG only, so this picture is dropped`
|
|
644
|
+
});
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
const bytes = picture.binary.length > 0 ? Uint8Array.from(picture.binary) : hexToBytes(picture.hex);
|
|
648
|
+
if (bytes.length === 0) {
|
|
649
|
+
sink({
|
|
650
|
+
code: RtfDiagnosticCodes.UNSUPPORTED_PICTURE_FORMAT,
|
|
651
|
+
severity: "warning",
|
|
652
|
+
message: "a \\pict destination carried no picture payload"
|
|
653
|
+
});
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
const scaleX = picture.scaleXPercent / 100;
|
|
657
|
+
const scaleY = picture.scaleYPercent / 100;
|
|
658
|
+
const widthPt = picture.widthGoalTwips === void 0 ? picture.widthPixels === void 0 ? void 0 : pixelsToPoints(picture.widthPixels) : twipsToPoints(picture.widthGoalTwips);
|
|
659
|
+
const heightPt = picture.heightGoalTwips === void 0 ? picture.heightPixels === void 0 ? void 0 : pixelsToPoints(picture.heightPixels) : twipsToPoints(picture.heightGoalTwips);
|
|
660
|
+
if (widthPt === void 0 || heightPt === void 0) {
|
|
661
|
+
sink({
|
|
662
|
+
code: RtfDiagnosticCodes.PICTURE_SIZE_UNSTATED,
|
|
663
|
+
severity: "warning",
|
|
664
|
+
message: "a \\pict destination stated neither \\picwgoalN/\\pichgoalN nor \\picwN/\\pichN, so its rendered size is unknown and the picture is dropped; decoding the payload's own intrinsic size would need an image decoder this package deliberately does not carry"
|
|
665
|
+
});
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
const scaledWidth = widthPt * scaleX;
|
|
669
|
+
const scaledHeight = heightPt * scaleY;
|
|
670
|
+
if (scaledWidth <= 0 || scaledHeight <= 0) {
|
|
671
|
+
sink({
|
|
672
|
+
code: RtfDiagnosticCodes.PICTURE_SIZE_UNSTATED,
|
|
673
|
+
severity: "warning",
|
|
674
|
+
message: "a \\pict destination's stated size scaled to zero or less, which ContentImageBlock cannot express"
|
|
675
|
+
});
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
return {
|
|
679
|
+
kind: "image",
|
|
680
|
+
format: picture.format,
|
|
681
|
+
base64: bytesToBase64(bytes),
|
|
682
|
+
widthPt: scaledWidth,
|
|
683
|
+
heightPt: scaledHeight
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
function defaultPictureState() {
|
|
687
|
+
return {
|
|
688
|
+
format: void 0,
|
|
689
|
+
unsupportedFormat: void 0,
|
|
690
|
+
widthGoalTwips: void 0,
|
|
691
|
+
heightGoalTwips: void 0,
|
|
692
|
+
widthPixels: void 0,
|
|
693
|
+
heightPixels: void 0,
|
|
694
|
+
scaleXPercent: 100,
|
|
695
|
+
scaleYPercent: 100,
|
|
696
|
+
hex: "",
|
|
697
|
+
binary: []
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
function toggleValue(param) {
|
|
701
|
+
return param === void 0 || param !== 0;
|
|
702
|
+
}
|
|
703
|
+
function readRtfDetail(input, options) {
|
|
704
|
+
options.signal?.throwIfAborted();
|
|
705
|
+
const maxInputBytes = options.maxInputBytes ?? 67108864;
|
|
706
|
+
if (input.length > maxInputBytes) throw new RtfInputTooLargeError(input.length, maxInputBytes);
|
|
707
|
+
const maxGroupDepth = options.maxGroupDepth ?? 256;
|
|
708
|
+
const diagnostics = [];
|
|
709
|
+
const callerSink = options.sink;
|
|
710
|
+
const sink = (diagnostic) => {
|
|
711
|
+
diagnostics.push(diagnostic);
|
|
712
|
+
callerSink?.(diagnostic);
|
|
713
|
+
};
|
|
714
|
+
const tokens = tokenizeRtf(input);
|
|
715
|
+
assertRtfHeaderPresent(tokens);
|
|
716
|
+
const header = readRtfHeader(tokens, sink);
|
|
717
|
+
const builder = new ContentBuilder(header, sink);
|
|
718
|
+
const section = defaultSectionState(header);
|
|
719
|
+
const root = {
|
|
720
|
+
destination: "body",
|
|
721
|
+
uc: 1,
|
|
722
|
+
char: defaultCharacterState(),
|
|
723
|
+
para: defaultParagraphState(),
|
|
724
|
+
field: void 0,
|
|
725
|
+
picture: void 0,
|
|
726
|
+
bookmark: void 0,
|
|
727
|
+
inUnicodeWrapper: false
|
|
728
|
+
};
|
|
729
|
+
const stack = [root];
|
|
730
|
+
let state = root;
|
|
731
|
+
let pendingBytes = [];
|
|
732
|
+
const activeCodepage = () => {
|
|
733
|
+
return (state.char.fontIndex === void 0 ? void 0 : header.fonts.get(state.char.fontIndex)?.codepage) ?? header.codepage;
|
|
734
|
+
};
|
|
735
|
+
const flushBytes = () => {
|
|
736
|
+
if (pendingBytes.length === 0) return;
|
|
737
|
+
const text = decodeCodepageBytes(Uint8Array.from(pendingBytes), activeCodepage(), sink);
|
|
738
|
+
pendingBytes = [];
|
|
739
|
+
emitText(text);
|
|
740
|
+
};
|
|
741
|
+
const emitText = (text) => {
|
|
742
|
+
if (state.destination === "body") {
|
|
743
|
+
const hyperlink = state.field === void 0 ? void 0 : hyperlinkFromInstruction(state.field.instruction);
|
|
744
|
+
builder.appendText(text, state.char, hyperlink);
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
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
|
+
}
|
|
755
|
+
};
|
|
756
|
+
let index = 0;
|
|
757
|
+
let textOffset = 0;
|
|
758
|
+
while (index < tokens.length) {
|
|
759
|
+
const token = tokens[index];
|
|
760
|
+
if (token === void 0) break;
|
|
761
|
+
if (token.kind === "groupStart") {
|
|
762
|
+
flushBytes();
|
|
763
|
+
const head = groupHead(tokens, index);
|
|
764
|
+
const known = head.destination === void 0 ? void 0 : DESTINATION_KINDS.get(head.destination);
|
|
765
|
+
const isHeaderTable = head.destination !== void 0 && HEADER_DESTINATIONS.has(head.destination);
|
|
766
|
+
const wrapperChild = state.inUnicodeWrapper;
|
|
767
|
+
const kind = isHeaderTable || wrapperChild && head.destination !== "ud" ? "skip" : known ?? (head.ignorable ? "skip" : state.destination);
|
|
768
|
+
if (head.destination !== void 0 && !isHeaderTable) {
|
|
769
|
+
if (head.ignorable && known === void 0) sink({
|
|
770
|
+
code: RtfDiagnosticCodes.UNKNOWN_DESTINATION_SKIPPED,
|
|
771
|
+
severity: "info",
|
|
772
|
+
message: `the ignorable destination \\${head.destination} is not recognised and its content is discarded, as the specification requires`
|
|
773
|
+
});
|
|
774
|
+
else if (known === "skip" && !SILENT_SKIP_DESTINATIONS.has(head.destination)) sink({
|
|
775
|
+
code: RtfDiagnosticCodes.CONTENT_DESTINATION_SKIPPED,
|
|
776
|
+
severity: "warning",
|
|
777
|
+
message: `the \\${head.destination} destination's content is discarded: no ContentDocument position carries it`
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
if (kind === "skip") {
|
|
781
|
+
index = matchingGroupEnd(tokens, index) + 1;
|
|
782
|
+
textOffset = 0;
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
if (stack.length >= maxGroupDepth) throw new RtfNestingLimitExceededError(maxGroupDepth);
|
|
786
|
+
const child = cloneGroupState(state);
|
|
787
|
+
child.inUnicodeWrapper = kind === "unicodeWrapper";
|
|
788
|
+
if (known !== void 0) {
|
|
789
|
+
child.destination = kind;
|
|
790
|
+
if (head.destination === "field") child.field = { instruction: "" };
|
|
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
|
+
};
|
|
797
|
+
index = head.contentStart;
|
|
798
|
+
} else index += 1;
|
|
799
|
+
stack.push(child);
|
|
800
|
+
state = child;
|
|
801
|
+
textOffset = 0;
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
if (token.kind === "groupEnd") {
|
|
805
|
+
flushBytes();
|
|
806
|
+
if (state.destination === "picture" && state.picture !== void 0) {
|
|
807
|
+
const image = buildPicture(state.picture, sink);
|
|
808
|
+
if (image !== void 0) builder.addBlock(image);
|
|
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
|
+
}
|
|
818
|
+
if (stack.length > 1) {
|
|
819
|
+
stack.pop();
|
|
820
|
+
const parent = stack[stack.length - 1];
|
|
821
|
+
if (parent !== void 0) state = parent;
|
|
822
|
+
} else sink({
|
|
823
|
+
code: RtfDiagnosticCodes.UNBALANCED_GROUP,
|
|
824
|
+
severity: "warning",
|
|
825
|
+
message: "a closing brace appeared with no group open; the extra brace is ignored",
|
|
826
|
+
tokenIndex: index
|
|
827
|
+
});
|
|
828
|
+
index += 1;
|
|
829
|
+
textOffset = 0;
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
if (token.kind === "text") {
|
|
833
|
+
const slice = textOffset === 0 ? token.bytes : token.bytes.subarray(textOffset);
|
|
834
|
+
if (state.destination === "picture" && state.picture !== void 0) state.picture.hex += asciiStringFromBytes(slice);
|
|
835
|
+
else appendBytes(pendingBytes, slice);
|
|
836
|
+
index += 1;
|
|
837
|
+
textOffset = 0;
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
if (token.kind === "binary") {
|
|
841
|
+
if (state.destination === "picture" && state.picture !== void 0) appendBytes(state.picture.binary, token.bytes);
|
|
842
|
+
index += 1;
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
if (token.kind === "hex") {
|
|
846
|
+
if (state.destination === "picture" && state.picture !== void 0) state.picture.binary.push(token.byte);
|
|
847
|
+
else pendingBytes.push(token.byte);
|
|
848
|
+
index += 1;
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
if (token.kind === "controlSymbol") {
|
|
852
|
+
const text = SPECIAL_SYMBOL_TEXT.get(token.symbol);
|
|
853
|
+
if (text !== void 0) {
|
|
854
|
+
flushBytes();
|
|
855
|
+
emitText(text);
|
|
856
|
+
}
|
|
857
|
+
index += 1;
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
if (token.name === "u") {
|
|
861
|
+
flushBytes();
|
|
862
|
+
const code = token.param;
|
|
863
|
+
if (code !== void 0) emitText(String.fromCharCode(code < 0 ? code + 65536 : code));
|
|
864
|
+
const skipped = skipUnicodeFallback(tokens, {
|
|
865
|
+
index: index + 1,
|
|
866
|
+
textOffset: 0
|
|
867
|
+
}, state.uc);
|
|
868
|
+
index = skipped.index;
|
|
869
|
+
textOffset = skipped.textOffset;
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
const special = SPECIAL_CHARACTER_TEXT.get(token.name);
|
|
873
|
+
if (special !== void 0) {
|
|
874
|
+
flushBytes();
|
|
875
|
+
emitText(special);
|
|
876
|
+
index += 1;
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
flushBytes();
|
|
880
|
+
applyControlWord(token.name, token.param, state, builder, header, section, sink);
|
|
881
|
+
index += 1;
|
|
882
|
+
}
|
|
883
|
+
flushBytes();
|
|
884
|
+
if (stack.length > 1) sink({
|
|
885
|
+
code: RtfDiagnosticCodes.UNBALANCED_GROUP,
|
|
886
|
+
severity: "warning",
|
|
887
|
+
message: `${String(stack.length - 1)} group(s) were still open at the end of the input; each is treated as closing there`
|
|
888
|
+
});
|
|
889
|
+
return {
|
|
890
|
+
document: builder.finish(header.metadata, section, root.para),
|
|
891
|
+
diagnostics
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
function assertRtfHeaderPresent(tokens) {
|
|
895
|
+
const first = tokens[0];
|
|
896
|
+
const second = tokens[1];
|
|
897
|
+
if (first?.kind !== "groupStart" || second?.kind !== "controlWord" || second.name !== "rtf") throw new RtfNotAnRtfDocumentError();
|
|
898
|
+
}
|
|
899
|
+
function applyPictureControlWord(name, param, picture) {
|
|
900
|
+
const format = PICTURE_FORMATS.get(name);
|
|
901
|
+
if (format !== void 0) {
|
|
902
|
+
picture.format = format;
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
switch (name) {
|
|
906
|
+
case "emfblip":
|
|
907
|
+
case "macpict":
|
|
908
|
+
case "wmetafile":
|
|
909
|
+
case "pmmetafile":
|
|
910
|
+
case "dibitmap":
|
|
911
|
+
case "wbitmap":
|
|
912
|
+
picture.unsupportedFormat = `\\${name}`;
|
|
913
|
+
return;
|
|
914
|
+
case "picwgoal":
|
|
915
|
+
picture.widthGoalTwips = param;
|
|
916
|
+
return;
|
|
917
|
+
case "pichgoal":
|
|
918
|
+
picture.heightGoalTwips = param;
|
|
919
|
+
return;
|
|
920
|
+
case "picw":
|
|
921
|
+
picture.widthPixels = param;
|
|
922
|
+
return;
|
|
923
|
+
case "pich":
|
|
924
|
+
picture.heightPixels = param;
|
|
925
|
+
return;
|
|
926
|
+
case "picscalex":
|
|
927
|
+
if (param !== void 0) picture.scaleXPercent = param;
|
|
928
|
+
return;
|
|
929
|
+
case "picscaley":
|
|
930
|
+
if (param !== void 0) picture.scaleYPercent = param;
|
|
931
|
+
return;
|
|
932
|
+
default: return;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
function applyCharacterControlWord(name, param, state, header) {
|
|
936
|
+
switch (name) {
|
|
937
|
+
case "plain":
|
|
938
|
+
state.char = defaultCharacterState();
|
|
939
|
+
return true;
|
|
940
|
+
case "b":
|
|
941
|
+
state.char.bold = toggleValue(param);
|
|
942
|
+
return true;
|
|
943
|
+
case "i":
|
|
944
|
+
state.char.italic = toggleValue(param);
|
|
945
|
+
return true;
|
|
946
|
+
case "strike":
|
|
947
|
+
state.char.strike = toggleValue(param);
|
|
948
|
+
return true;
|
|
949
|
+
case "v":
|
|
950
|
+
state.char.hidden = toggleValue(param);
|
|
951
|
+
return true;
|
|
952
|
+
case "ulnone":
|
|
953
|
+
state.char.underline = false;
|
|
954
|
+
return true;
|
|
955
|
+
case "f":
|
|
956
|
+
state.char.fontIndex = param ?? header.defaultFontIndex;
|
|
957
|
+
return true;
|
|
958
|
+
case "fs":
|
|
959
|
+
state.char.sizeHalfPoints = param ?? 24;
|
|
960
|
+
return true;
|
|
961
|
+
case "cf":
|
|
962
|
+
state.char.colorIndex = param;
|
|
963
|
+
return true;
|
|
964
|
+
case "uc":
|
|
965
|
+
if (param !== void 0 && param >= 0) state.uc = param;
|
|
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;
|
|
1039
|
+
default:
|
|
1040
|
+
if (name.startsWith("ul") && name !== "ulc") {
|
|
1041
|
+
state.char.underline = toggleValue(param);
|
|
1042
|
+
return true;
|
|
1043
|
+
}
|
|
1044
|
+
return false;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
function applyParagraphControlWord(name, param, state) {
|
|
1048
|
+
const alignment = ALIGNMENTS.get(name);
|
|
1049
|
+
if (alignment !== void 0) {
|
|
1050
|
+
state.para.alignment = alignment;
|
|
1051
|
+
return true;
|
|
1052
|
+
}
|
|
1053
|
+
switch (name) {
|
|
1054
|
+
case "pard":
|
|
1055
|
+
state.para = defaultParagraphState();
|
|
1056
|
+
return true;
|
|
1057
|
+
case "s":
|
|
1058
|
+
state.para.styleIndex = param;
|
|
1059
|
+
return true;
|
|
1060
|
+
case "outlinelevel":
|
|
1061
|
+
state.para.outlineLevel = param === void 0 || param > 8 ? void 0 : param;
|
|
1062
|
+
return true;
|
|
1063
|
+
case "li":
|
|
1064
|
+
case "lin":
|
|
1065
|
+
state.para.indentLeftTwips = param ?? 0;
|
|
1066
|
+
return true;
|
|
1067
|
+
case "fi":
|
|
1068
|
+
state.para.indentFirstLineTwips = param ?? 0;
|
|
1069
|
+
return true;
|
|
1070
|
+
case "sb":
|
|
1071
|
+
state.para.spaceBeforeTwips = param ?? 0;
|
|
1072
|
+
return true;
|
|
1073
|
+
case "sa":
|
|
1074
|
+
state.para.spaceAfterTwips = param ?? 0;
|
|
1075
|
+
return true;
|
|
1076
|
+
case "sl":
|
|
1077
|
+
state.para.lineSpacingTwips = param;
|
|
1078
|
+
return true;
|
|
1079
|
+
case "slmult":
|
|
1080
|
+
state.para.lineSpacingIsMultiple = toggleValue(param);
|
|
1081
|
+
return true;
|
|
1082
|
+
case "pagebb":
|
|
1083
|
+
state.para.pageBreakBefore = true;
|
|
1084
|
+
return true;
|
|
1085
|
+
case "ls":
|
|
1086
|
+
state.para.listOverrideIndex = param;
|
|
1087
|
+
return true;
|
|
1088
|
+
case "ilvl":
|
|
1089
|
+
state.para.listLevel = param ?? 0;
|
|
1090
|
+
return true;
|
|
1091
|
+
case "intbl":
|
|
1092
|
+
state.para.inTable = true;
|
|
1093
|
+
return true;
|
|
1094
|
+
default: return false;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
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) {
|
|
1139
|
+
switch (name) {
|
|
1140
|
+
case "par":
|
|
1141
|
+
builder.endParagraph(state.para, true);
|
|
1142
|
+
return true;
|
|
1143
|
+
case "trowd":
|
|
1144
|
+
state.para.inTable = true;
|
|
1145
|
+
builder.startRowDefinition();
|
|
1146
|
+
return true;
|
|
1147
|
+
case "trleft":
|
|
1148
|
+
builder.setRowLeft(param ?? 0);
|
|
1149
|
+
return true;
|
|
1150
|
+
case "cellx":
|
|
1151
|
+
if (param !== void 0) builder.addCellBoundary(param);
|
|
1152
|
+
return true;
|
|
1153
|
+
case "cell":
|
|
1154
|
+
builder.endCell(state.para);
|
|
1155
|
+
return true;
|
|
1156
|
+
case "row":
|
|
1157
|
+
builder.endRow(state.para);
|
|
1158
|
+
state.para.inTable = false;
|
|
1159
|
+
return true;
|
|
1160
|
+
case "nestcell":
|
|
1161
|
+
case "nestrow":
|
|
1162
|
+
sink({
|
|
1163
|
+
code: RtfDiagnosticCodes.NESTED_TABLE_FLATTENED,
|
|
1164
|
+
severity: "warning",
|
|
1165
|
+
message: "a nested table's cell/row marks are read as ordinary cell content; the inner table's own structure is not reconstructed"
|
|
1166
|
+
});
|
|
1167
|
+
return true;
|
|
1168
|
+
case "page":
|
|
1169
|
+
builder.endParagraph(state.para, false);
|
|
1170
|
+
builder.addBlock({ kind: "pageBreak" });
|
|
1171
|
+
return true;
|
|
1172
|
+
case "sect":
|
|
1173
|
+
builder.endParagraph(state.para, true);
|
|
1174
|
+
builder.endSection(section, state.para);
|
|
1175
|
+
return true;
|
|
1176
|
+
default: return false;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
function applyControlWord(name, param, state, builder, header, section, sink) {
|
|
1180
|
+
const picture = state.picture;
|
|
1181
|
+
if (state.destination === "picture" && picture !== void 0) {
|
|
1182
|
+
applyPictureControlWord(name, param, picture);
|
|
1183
|
+
return;
|
|
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;
|
|
1192
|
+
if (applyCharacterControlWord(name, param, state, header)) return;
|
|
1193
|
+
if (builder.applyCellDefinition(name, param)) return;
|
|
1194
|
+
if (applyParagraphControlWord(name, param, state)) return;
|
|
1195
|
+
if (applySectionControlWord(name, param, section, header, sink)) return;
|
|
1196
|
+
applyStructureControlWord(name, param, state, builder, section, sink);
|
|
1197
|
+
}
|
|
1198
|
+
function readRtfContent(input, options = {}) {
|
|
1199
|
+
return readRtfDetail(typeof input === "string" ? rtfBytesFromLatin1(input) : input, options);
|
|
1200
|
+
}
|
|
1201
|
+
function readRtf(input, options = {}) {
|
|
1202
|
+
const { document, diagnostics } = readRtfContent(input, options);
|
|
1203
|
+
return {
|
|
1204
|
+
documentPackage: assembleTree(document),
|
|
1205
|
+
diagnostics
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
//#endregion
|
|
1209
|
+
export { readRtf, readRtfContent };
|