rtf-codec 2.1.0 → 3.0.1
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/codec.d.cts +2 -2
- package/dist/codec.d.ts +2 -2
- 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 +3 -2
package/dist/write.js
CHANGED
|
@@ -3,6 +3,7 @@ import { RtfDiagnosticCodes, RtfUnsupportedDocumentKindError } from "./diagnosti
|
|
|
3
3
|
import { pointsToHalfPoints, pointsToTwips } from "./units.js";
|
|
4
4
|
import { borderControlWords } from "./cell-format.js";
|
|
5
5
|
import { bookmarkResidueControlWords, dttmFromIso, isBookmarkAnchor } from "./constructs.js";
|
|
6
|
+
import { writeEmbeddedObjectData } from "./embedded-object.js";
|
|
6
7
|
import { parseRtfListNumId } from "./list-id.js";
|
|
7
8
|
import { clampHeadingLevel, colorToRgbHex, flattenTree, resolveCellFillColor } from "document-schema.js";
|
|
8
9
|
//#region src/write.ts
|
|
@@ -13,6 +14,11 @@ const LIST_MARKER_HANG_TWIPS = 360;
|
|
|
13
14
|
const BULLET_LEVEL_TEXT = "\\'01\\u183 ?";
|
|
14
15
|
const ARABIC_LEVEL_TEXT = "\\'02\\'00.";
|
|
15
16
|
const OUTPUT_CODEPAGE = 1252;
|
|
17
|
+
const CELL_BLOCK_KINDS = /* @__PURE__ */ new Set([
|
|
18
|
+
"paragraph",
|
|
19
|
+
"image",
|
|
20
|
+
"embeddedObject"
|
|
21
|
+
]);
|
|
16
22
|
const SECTION_BREAK_CONTROL_WORDS = /* @__PURE__ */ new Map([
|
|
17
23
|
["continuous", "\\sbknone"],
|
|
18
24
|
["evenPage", "\\sbkeven"],
|
|
@@ -124,6 +130,112 @@ function bookmarkStartGroup(descriptor) {
|
|
|
124
130
|
if (!isBookmarkAnchor(descriptor)) return "";
|
|
125
131
|
return `{\\*\\bkmkstart${bookmarkResidueControlWords(descriptor)} ${escapeText(descriptor.name)}}`;
|
|
126
132
|
}
|
|
133
|
+
const FORM_FIELD_SPEC = /* @__PURE__ */ new Map([
|
|
134
|
+
["plainText", {
|
|
135
|
+
instruction: "FORMTEXT",
|
|
136
|
+
fftype: 0
|
|
137
|
+
}],
|
|
138
|
+
["checkbox", {
|
|
139
|
+
instruction: "FORMCHECKBOX",
|
|
140
|
+
fftype: 1
|
|
141
|
+
}],
|
|
142
|
+
["dropDown", {
|
|
143
|
+
instruction: "FORMDROPDOWN",
|
|
144
|
+
fftype: 2
|
|
145
|
+
}]
|
|
146
|
+
]);
|
|
147
|
+
const MAX_DROPDOWN_OPTIONS = 25;
|
|
148
|
+
function formFieldPayload(descriptor, fftype, sink) {
|
|
149
|
+
const fftypeFragment = `\\fftype${String(fftype)}`;
|
|
150
|
+
let ffNameString = "";
|
|
151
|
+
let ffDefTextString = "";
|
|
152
|
+
let ffHelpTextString = "";
|
|
153
|
+
let fflEntries = "";
|
|
154
|
+
let ffOwnHelpFragment = "";
|
|
155
|
+
if (descriptor.alias !== void 0 && descriptor.alias.trim().length > 0) {
|
|
156
|
+
ffOwnHelpFragment = "\\ffownhelp1";
|
|
157
|
+
ffHelpTextString = `{\\*\\ffhelptext ${escapeText(descriptor.alias)}}`;
|
|
158
|
+
}
|
|
159
|
+
let ffProtFragment = "";
|
|
160
|
+
if (descriptor.lock === "content" || descriptor.lock === "both") ffProtFragment = "\\ffprot1";
|
|
161
|
+
if (descriptor.lock === "container" || descriptor.lock === "both") {
|
|
162
|
+
const message = descriptor.lock === "both" ? `a contentControl's 'both' lock also protects the control from removal, which RTF's \\ffprot ([MS-DOC] 2.9.79 FFDataBits.fProt) cannot express -- \\ffprot1 above already carries the content-protection half of 'both', so only the container-removal half is dropped here` : `a contentControl's 'container' lock protects the control from removal, which RTF's \\ffprot ([MS-DOC] 2.9.79 FFDataBits.fProt) cannot express at all -- it names only whether the field's own value can be changed, and a 'container' lock leaves that value editable, so nothing is written for it and the whole lock is dropped, not merely half of it`;
|
|
163
|
+
sink({
|
|
164
|
+
code: RtfDiagnosticCodes.CONSTRUCT_UNREPRESENTED,
|
|
165
|
+
severity: "warning",
|
|
166
|
+
message
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
let controlTypeParams = "";
|
|
170
|
+
if (descriptor.controlType === "checkbox") {
|
|
171
|
+
const value = descriptor.checked === true ? "1" : "0";
|
|
172
|
+
controlTypeParams += `\\ffdefres${value}\\ffres${value}`;
|
|
173
|
+
if (descriptor.value !== void 0 && descriptor.value.length > 0) sink({
|
|
174
|
+
code: RtfDiagnosticCodes.CONSTRUCT_UNREPRESENTED,
|
|
175
|
+
severity: "warning",
|
|
176
|
+
message: `a checkbox contentControl's value '${descriptor.value}' (its on-state export name) is dropped: RTF's \\ffres/\\ffdefres can only carry the field's boolean checked state, with no spelling for a named export value at all`
|
|
177
|
+
});
|
|
178
|
+
if (descriptor.options !== void 0 && descriptor.options.length > 0) sink({
|
|
179
|
+
code: RtfDiagnosticCodes.CONSTRUCT_UNREPRESENTED,
|
|
180
|
+
severity: "warning",
|
|
181
|
+
message: `a checkbox contentControl's options list (${String(descriptor.options.length)} entries) is dropped: a checkbox has no choice list at all, in RTF or in the harmonised contentControl vocabulary itself`
|
|
182
|
+
});
|
|
183
|
+
} else if (descriptor.controlType === "dropDown") {
|
|
184
|
+
controlTypeParams += "\\ffhaslistbox1";
|
|
185
|
+
const allOptions = descriptor.options;
|
|
186
|
+
let options = allOptions;
|
|
187
|
+
if (allOptions !== void 0 && allOptions.length > MAX_DROPDOWN_OPTIONS) {
|
|
188
|
+
sink({
|
|
189
|
+
code: RtfDiagnosticCodes.CONSTRUCT_UNREPRESENTED,
|
|
190
|
+
severity: "warning",
|
|
191
|
+
message: `a dropDown contentControl's ${String(allOptions.length)} options exceed [MS-DOC] 2.9.78 FFData.hsttbDropList's own ${String(MAX_DROPDOWN_OPTIONS)}-entry limit; only the first ${String(MAX_DROPDOWN_OPTIONS)} are written`
|
|
192
|
+
});
|
|
193
|
+
options = allOptions.slice(0, MAX_DROPDOWN_OPTIONS);
|
|
194
|
+
}
|
|
195
|
+
const selectedIndex = options === void 0 || descriptor.value === void 0 ? void 0 : options.indexOf(descriptor.value);
|
|
196
|
+
if (selectedIndex !== void 0 && selectedIndex !== -1) controlTypeParams += `\\ffdefres${String(selectedIndex)}\\ffres${String(selectedIndex)}`;
|
|
197
|
+
else if (descriptor.value !== void 0 && descriptor.value.length > 0) {
|
|
198
|
+
const truncatedAway = allOptions !== void 0 && options !== allOptions && allOptions.includes(descriptor.value);
|
|
199
|
+
sink({
|
|
200
|
+
code: RtfDiagnosticCodes.CONSTRUCT_UNREPRESENTED,
|
|
201
|
+
severity: "warning",
|
|
202
|
+
message: truncatedAway ? `a dropDown contentControl's selected value '${descriptor.value}' is dropped: its matching option was truncated away by [MS-DOC] 2.9.78 FFData.hsttbDropList's own ${String(MAX_DROPDOWN_OPTIONS)}-entry limit, and \\ffres/\\ffdefres can only name a real index into the entries actually written` : `a dropDown contentControl's selected value '${descriptor.value}' is dropped: it does not match any of the field's own options, and \\ffres/\\ffdefres can only name a real index into that list`
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (options !== void 0) for (const option of options) fflEntries += `{\\*\\ffl ${escapeText(option)}}`;
|
|
206
|
+
if (descriptor.checked !== void 0) sink({
|
|
207
|
+
code: RtfDiagnosticCodes.CONSTRUCT_UNREPRESENTED,
|
|
208
|
+
severity: "warning",
|
|
209
|
+
message: `a dropDown contentControl's checked state (${String(descriptor.checked)}) is dropped: a dropdown has no boolean checked state at all, in RTF or in the harmonised contentControl vocabulary itself`
|
|
210
|
+
});
|
|
211
|
+
} else if (descriptor.controlType === "plainText") {
|
|
212
|
+
if (descriptor.value !== void 0 && descriptor.value.length > 0) {
|
|
213
|
+
ffDefTextString = `{\\*\\ffdeftext ${escapeText(descriptor.value)}}`;
|
|
214
|
+
sink({
|
|
215
|
+
code: RtfDiagnosticCodes.CONSTRUCT_UNREPRESENTED,
|
|
216
|
+
severity: "warning",
|
|
217
|
+
message: `a plainText contentControl's value '${descriptor.value}' is written into {\\*\\ffdeftext ...}, FFData.xstzTextDef's default/reset text, not a slot for the field's current value: this codec's own reader does not restore \\ffdeftext back onto \`value\`, so this does not round-trip`
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
if (descriptor.checked !== void 0) sink({
|
|
221
|
+
code: RtfDiagnosticCodes.CONSTRUCT_UNREPRESENTED,
|
|
222
|
+
severity: "warning",
|
|
223
|
+
message: `a plainText contentControl's checked state (${String(descriptor.checked)}) is dropped: a text field has no boolean checked state at all, in RTF or in the harmonised contentControl vocabulary itself`
|
|
224
|
+
});
|
|
225
|
+
if (descriptor.options !== void 0 && descriptor.options.length > 0) sink({
|
|
226
|
+
code: RtfDiagnosticCodes.CONSTRUCT_UNREPRESENTED,
|
|
227
|
+
severity: "warning",
|
|
228
|
+
message: `a plainText contentControl's options list (${String(descriptor.options.length)} entries) is dropped: a text field has no choice list at all, in RTF or in the harmonised contentControl vocabulary itself`
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (descriptor.tag !== void 0 && descriptor.tag.trim().length > 0) ffNameString = `{\\*\\ffname ${escapeText(descriptor.tag)}}`;
|
|
232
|
+
return fftypeFragment + ffOwnHelpFragment + ffProtFragment + controlTypeParams + ffNameString + ffDefTextString + ffHelpTextString + fflEntries;
|
|
233
|
+
}
|
|
234
|
+
function formFieldOpenGroup(descriptor, sink) {
|
|
235
|
+
const spec = FORM_FIELD_SPEC.get(descriptor.controlType);
|
|
236
|
+
if (spec === void 0) return;
|
|
237
|
+
return `{\\field{\\*\\fldinst ${spec.instruction} {\\*\\formfield{${formFieldPayload(descriptor, spec.fftype, sink)}}}}{\\fldrslt `;
|
|
238
|
+
}
|
|
127
239
|
const CHREV_CONTROL_WORDS = {
|
|
128
240
|
insertion: {
|
|
129
241
|
flag: "\\revised",
|
|
@@ -174,9 +286,35 @@ function verticalMergeCoverage(table) {
|
|
|
174
286
|
function nameOf(descriptor) {
|
|
175
287
|
return isBookmarkAnchor(descriptor) ? descriptor.name : "";
|
|
176
288
|
}
|
|
289
|
+
function isContentControlExtent(extent) {
|
|
290
|
+
return extent.descriptor.kind === "contentControl";
|
|
291
|
+
}
|
|
292
|
+
function selectNestableFormFields(extents, sink) {
|
|
293
|
+
const sorted = [...extents].sort((a, b) => a.startRun - b.startRun || b.endRun - a.endRun);
|
|
294
|
+
const stack = [];
|
|
295
|
+
const accepted = [];
|
|
296
|
+
for (const extent of sorted) {
|
|
297
|
+
let top = stack[stack.length - 1];
|
|
298
|
+
while (top !== void 0 && top.endRun <= extent.startRun) {
|
|
299
|
+
stack.pop();
|
|
300
|
+
top = stack[stack.length - 1];
|
|
301
|
+
}
|
|
302
|
+
if (top !== void 0 && extent.endRun > top.endRun) {
|
|
303
|
+
sink({
|
|
304
|
+
code: RtfDiagnosticCodes.CONSTRUCT_UNREPRESENTED,
|
|
305
|
+
severity: "warning",
|
|
306
|
+
message: "a contentControl construct is dropped: it crosses another contentControl extent in the same paragraph (starts before that extent ends but ends after it too), and RTF's \\*\\formfield destination can only nest properly, never cross"
|
|
307
|
+
});
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
stack.push(extent);
|
|
311
|
+
accepted.push(extent);
|
|
312
|
+
}
|
|
313
|
+
return accepted;
|
|
314
|
+
}
|
|
177
315
|
function describeConstructGap(descriptor) {
|
|
178
316
|
switch (descriptor.kind) {
|
|
179
|
-
case "contentControl": return "structured-document-tag equivalent
|
|
317
|
+
case "contentControl": return "block-scoped structured-document-tag equivalent -- a run-scoped plainText/checkbox/dropDown form field mints its own \\*\\formfield instead; any other controlType (richText, comboBox, date, and the rest) has no \\*\\formfield spelling at all";
|
|
180
318
|
case "provenance": return "block-scoped revision mark: its <chrev> production is a character property, so a tracked change reaches RTF only as a run-level extent";
|
|
181
319
|
case "anchor": return `spelling for a '${descriptor.anchorType}' anchor, whose body would need the note or annotation destination this reader does not place`;
|
|
182
320
|
case "field": return "block-scoped field: a field is a character-stream construct, written from a run's own hyperlink rather than from a block marker";
|
|
@@ -184,6 +322,9 @@ function describeConstructGap(descriptor) {
|
|
|
184
322
|
default: return "equivalent construct";
|
|
185
323
|
}
|
|
186
324
|
}
|
|
325
|
+
function describeFormFieldGap(descriptor) {
|
|
326
|
+
return `\\*\\formfield spelling for a '${descriptor.controlType}' controlType -- only plainText/checkbox/dropDown form fields mint one`;
|
|
327
|
+
}
|
|
187
328
|
var RtfWriter = class {
|
|
188
329
|
tables;
|
|
189
330
|
sink;
|
|
@@ -300,11 +441,7 @@ var RtfWriter = class {
|
|
|
300
441
|
this.line("\\page\\pard");
|
|
301
442
|
return;
|
|
302
443
|
case "embeddedObject":
|
|
303
|
-
this.
|
|
304
|
-
code: RtfDiagnosticCodes.EMBEDDED_OBJECT_DROPPED,
|
|
305
|
-
severity: "warning",
|
|
306
|
-
message: `an embedded ${block.objectKind} object is dropped: writing it as an RTF \\object would need the OLE container this package does not build`
|
|
307
|
-
});
|
|
444
|
+
this.writeEmbeddedObjectBlock(block);
|
|
308
445
|
return;
|
|
309
446
|
case "constructStart":
|
|
310
447
|
this.openConstruct(block.descriptor);
|
|
@@ -339,13 +476,22 @@ var RtfWriter = class {
|
|
|
339
476
|
this.raw(" ");
|
|
340
477
|
const bookmarks = (paragraph.constructs ?? []).filter((extent) => isBookmarkAnchor(extent.descriptor));
|
|
341
478
|
const revisions = (paragraph.constructs ?? []).filter((extent) => extent.descriptor.kind === "provenance");
|
|
479
|
+
const formFields = selectNestableFormFields((paragraph.constructs ?? []).filter(isContentControlExtent), this.sink);
|
|
480
|
+
const openedFormFields = [];
|
|
342
481
|
for (const [index, run] of paragraph.runs.entries()) {
|
|
343
482
|
this.writeRunBoundaries(bookmarks, index);
|
|
483
|
+
this.writeFormFieldBoundaries(formFields, index, openedFormFields);
|
|
344
484
|
this.writeRun(run, revisionsCovering(revisions, index));
|
|
345
485
|
}
|
|
346
486
|
this.writeRunBoundaries(bookmarks, paragraph.runs.length);
|
|
487
|
+
this.writeFormFieldBoundaries(formFields, paragraph.runs.length, openedFormFields);
|
|
488
|
+
this.drainOpenedFormFields(openedFormFields);
|
|
347
489
|
if (!inTable) this.line("\\par");
|
|
348
490
|
}
|
|
491
|
+
drainOpenedFormFields(opened) {
|
|
492
|
+
for (let remaining = opened.length; remaining > 0; remaining -= 1) this.raw("}}");
|
|
493
|
+
opened.length = 0;
|
|
494
|
+
}
|
|
349
495
|
writeRunBoundaries(extents, position) {
|
|
350
496
|
for (const extent of extents) if (extent.endRun === position && extent.startRun !== position) this.raw(`{\\*\\bkmkend ${escapeText(nameOf(extent.descriptor))}}`);
|
|
351
497
|
for (const extent of extents) if (extent.startRun === position) {
|
|
@@ -353,6 +499,32 @@ var RtfWriter = class {
|
|
|
353
499
|
if (extent.endRun === position) this.raw(`{\\*\\bkmkend ${escapeText(nameOf(extent.descriptor))}}`);
|
|
354
500
|
}
|
|
355
501
|
}
|
|
502
|
+
writeFormFieldBoundaries(extents, position, opened) {
|
|
503
|
+
let top = opened[opened.length - 1];
|
|
504
|
+
while (top?.endRun === position) {
|
|
505
|
+
opened.pop();
|
|
506
|
+
this.raw("}}");
|
|
507
|
+
top = opened[opened.length - 1];
|
|
508
|
+
}
|
|
509
|
+
for (const extent of extents) {
|
|
510
|
+
if (extent.startRun !== position) continue;
|
|
511
|
+
const open = formFieldOpenGroup(extent.descriptor, this.sink);
|
|
512
|
+
if (open === void 0) {
|
|
513
|
+
this.sink({
|
|
514
|
+
code: RtfDiagnosticCodes.CONSTRUCT_UNREPRESENTED,
|
|
515
|
+
severity: "warning",
|
|
516
|
+
message: `a contentControl construct is dropped: RTF has no ${describeFormFieldGap(extent.descriptor)}`
|
|
517
|
+
});
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
this.raw(open);
|
|
521
|
+
opened.push(extent);
|
|
522
|
+
if (extent.endRun === position) {
|
|
523
|
+
opened.pop();
|
|
524
|
+
this.raw("}}");
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
356
528
|
paragraphProperties(paragraph) {
|
|
357
529
|
let out = "";
|
|
358
530
|
const level = paragraph.headingLevel === void 0 ? void 0 : clampHeadingLevel(paragraph.headingLevel);
|
|
@@ -466,17 +638,48 @@ var RtfWriter = class {
|
|
|
466
638
|
return `${out}\\cellx${String(rightTwips)}`;
|
|
467
639
|
}
|
|
468
640
|
writeCellBlocks(blocks) {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
641
|
+
let wroteBlock = false;
|
|
642
|
+
let blockPending = false;
|
|
643
|
+
for (const [index, block] of blocks.entries()) {
|
|
644
|
+
if (block.kind === "constructStart" || block.kind === "constructEnd") {
|
|
645
|
+
if (blockPending && blocks.slice(index + 1).some((later) => CELL_BLOCK_KINDS.has(later.kind))) {
|
|
646
|
+
this.raw("\\par");
|
|
647
|
+
blockPending = false;
|
|
648
|
+
}
|
|
649
|
+
if (block.kind === "constructStart") this.openConstruct(block.descriptor);
|
|
650
|
+
else this.closeConstruct();
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
if (!CELL_BLOCK_KINDS.has(block.kind)) {
|
|
654
|
+
this.sink({
|
|
655
|
+
code: RtfDiagnosticCodes.CONSTRUCT_UNREPRESENTED,
|
|
656
|
+
severity: "warning",
|
|
657
|
+
message: `a ${block.kind} block inside a table cell is dropped: this writer cannot yet splice a ${block.kind}'s own destination grammar into a table row's own \\intbl flow`
|
|
658
|
+
});
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (block.kind === "image") {
|
|
662
|
+
const decoded = this.decodeImageOrWarn(block);
|
|
663
|
+
if (decoded === void 0) continue;
|
|
664
|
+
if (blockPending) this.raw("\\par");
|
|
665
|
+
this.writeImagePict(decoded, block, true);
|
|
666
|
+
wroteBlock = true;
|
|
667
|
+
blockPending = false;
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
if (blockPending) this.raw("\\par");
|
|
671
|
+
if (block.kind === "paragraph") {
|
|
672
|
+
this.writeParagraph(block, true);
|
|
673
|
+
blockPending = true;
|
|
674
|
+
} else if (block.kind === "embeddedObject") {
|
|
675
|
+
this.writeEmbeddedObjectBlock(block, true);
|
|
676
|
+
blockPending = false;
|
|
677
|
+
}
|
|
678
|
+
wroteBlock = true;
|
|
477
679
|
}
|
|
680
|
+
if (!wroteBlock) this.raw("\\pard\\plain\\intbl ");
|
|
478
681
|
}
|
|
479
|
-
|
|
682
|
+
decodeImageOrWarn(image) {
|
|
480
683
|
if (image.format === "svg" || image.format === "gif") {
|
|
481
684
|
this.sink({
|
|
482
685
|
code: RtfDiagnosticCodes.UNSUPPORTED_PICTURE_FORMAT,
|
|
@@ -485,7 +688,7 @@ var RtfWriter = class {
|
|
|
485
688
|
});
|
|
486
689
|
return;
|
|
487
690
|
}
|
|
488
|
-
const bytes = base64ToBytes(base64);
|
|
691
|
+
const bytes = base64ToBytes(image.base64);
|
|
489
692
|
if (bytes === void 0 || bytes.length === 0) {
|
|
490
693
|
this.sink({
|
|
491
694
|
code: RtfDiagnosticCodes.UNSUPPORTED_PICTURE_FORMAT,
|
|
@@ -494,9 +697,30 @@ var RtfWriter = class {
|
|
|
494
697
|
});
|
|
495
698
|
return;
|
|
496
699
|
}
|
|
700
|
+
return bytes;
|
|
701
|
+
}
|
|
702
|
+
writeImagePict(bytes, image, inTable) {
|
|
497
703
|
const widthTwips = pointsToTwips(image.widthPt);
|
|
498
704
|
const heightTwips = pointsToTwips(image.heightPt);
|
|
499
|
-
|
|
705
|
+
const pict = `\\pard\\plain${inTable ? "\\intbl" : ""} {\\*\\shppict{\\pict\\${image.format === "png" ? "pngblip" : "jpegblip"}\\picwgoal${String(widthTwips)}\\pichgoal${String(heightTwips)}${this.lineEnding}${wrapHex(bytesToHex(bytes), this.lineEnding)}}}`;
|
|
706
|
+
if (inTable) this.raw(pict);
|
|
707
|
+
else this.line(`${pict}\\par`);
|
|
708
|
+
}
|
|
709
|
+
writeImageParagraph(base64, image) {
|
|
710
|
+
const bytes = this.decodeImageOrWarn({
|
|
711
|
+
...image,
|
|
712
|
+
base64
|
|
713
|
+
});
|
|
714
|
+
if (bytes === void 0) return;
|
|
715
|
+
this.writeImagePict(bytes, image, false);
|
|
716
|
+
}
|
|
717
|
+
writeEmbeddedObjectBlock(block, inTable = false) {
|
|
718
|
+
const widthTwips = pointsToTwips(block.frame.widthPt);
|
|
719
|
+
const heightTwips = pointsToTwips(block.frame.heightPt);
|
|
720
|
+
const objdataBytes = writeEmbeddedObjectData(block);
|
|
721
|
+
const object = `\\pard\\plain${inTable ? "\\intbl" : ""} {\\object\\objemb\\objw${String(widthTwips)}\\objh${String(heightTwips)}{\\*\\objclass ${escapeText(block.objectKind)}}{\\*\\objdata${this.lineEnding}${wrapHex(bytesToHex(objdataBytes), this.lineEnding)}}{\\result{\\pard\\plain ${escapeText(`[embedded ${block.objectKind} object]`)}\\par}}}`;
|
|
722
|
+
if (inTable) this.raw(object);
|
|
723
|
+
else this.line(`${object}\\par`);
|
|
500
724
|
}
|
|
501
725
|
};
|
|
502
726
|
function colorIndexOf(color, colors) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rtf-codec",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Hand-written Rich Text Format (RTF 1.9.1) reader and writer against the shared document-schema.js content pivot.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -80,7 +80,8 @@
|
|
|
80
80
|
"license": "MIT",
|
|
81
81
|
"packageManager": "pnpm@11.6.0",
|
|
82
82
|
"dependencies": {
|
|
83
|
-
"
|
|
83
|
+
"archive-codec": "^1.6.1",
|
|
84
|
+
"document-schema.js": "^6.2.0",
|
|
84
85
|
"zod": "^4.4.3"
|
|
85
86
|
},
|
|
86
87
|
"devDependencies": {
|