doc-codec 2.5.0 → 2.7.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.
Files changed (42) hide show
  1. package/README.md +38 -29
  2. package/dist/base64.cjs +48 -0
  3. package/dist/base64.d.cts +5 -0
  4. package/dist/base64.d.ts +5 -0
  5. package/dist/base64.js +46 -0
  6. package/dist/data-stream-BLCj4-V1.d.cts +11 -0
  7. package/dist/data-stream-BLCj4-V1.d.ts +11 -0
  8. package/dist/data-stream.cjs +25 -0
  9. package/dist/data-stream.d.cts +2 -0
  10. package/dist/data-stream.d.ts +2 -0
  11. package/dist/data-stream.js +24 -0
  12. package/dist/encryption.cjs +44 -10
  13. package/dist/encryption.d.cts +2 -2
  14. package/dist/encryption.d.ts +2 -2
  15. package/dist/encryption.js +45 -11
  16. package/dist/index.cjs +5 -0
  17. package/dist/index.d.cts +3 -1
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.js +3 -1
  20. package/dist/pictures-write.cjs +85 -0
  21. package/dist/pictures-write.d.cts +12 -0
  22. package/dist/pictures-write.d.ts +12 -0
  23. package/dist/pictures-write.js +84 -0
  24. package/dist/pictures.cjs +3 -16
  25. package/dist/pictures.js +3 -16
  26. package/dist/prop/sep-write.cjs +16 -9
  27. package/dist/prop/sep-write.d.cts +2 -2
  28. package/dist/prop/sep-write.d.ts +2 -2
  29. package/dist/prop/sep-write.js +16 -9
  30. package/dist/read.cjs +1 -2
  31. package/dist/read.js +2 -3
  32. package/dist/table/write.cjs +28 -3
  33. package/dist/table/write.d.cts +2 -2
  34. package/dist/table/write.d.ts +2 -2
  35. package/dist/table/write.js +28 -3
  36. package/dist/{write-SN_aIwyF.d.ts → write-CMB3qF4i.d.cts} +9 -3
  37. package/dist/{write-SN_aIwyF.d.cts → write-t4rFgnA-.d.ts} +9 -3
  38. package/dist/write.cjs +65 -17
  39. package/dist/write.d.cts +1 -1
  40. package/dist/write.d.ts +1 -1
  41. package/dist/write.js +66 -18
  42. package/package.json +2 -2
@@ -4,10 +4,10 @@ interface DecryptedDocStreams {
4
4
  readonly table: Uint8Array<ArrayBuffer>;
5
5
  }
6
6
  /**
7
- * Decrypts an RC4-encrypted (fEncrypted=1, fObfuscated=0) document's WordDocument and Table streams given the password, verifying it first against the Table stream's own EncryptionHeader.
7
+ * Decrypts an encrypted document's WordDocument and Table streams given the password, dispatching on `fObfuscated` between [MS-DOC] 2.2.6.2's RC4 encryption header and 2.2.6.1's XOR obfuscation (Method 2).
8
8
  *
9
9
  * Throws `DocUnsupportedError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
10
10
  */
11
- declare function decryptDocStreams(wordDocument: Uint8Array<ArrayBuffer>, table: Uint8Array<ArrayBuffer>, password: string | undefined): DecryptedDocStreams;
11
+ declare function decryptDocStreams(wordDocument: Uint8Array<ArrayBuffer>, table: Uint8Array<ArrayBuffer>, password: string | undefined, fObfuscated: boolean): DecryptedDocStreams;
12
12
  //#endregion
13
13
  export { DecryptedDocStreams, decryptDocStreams };
@@ -1,7 +1,7 @@
1
1
  import { DocFormatError, DocUnsupportedError } from "./errors.js";
2
2
  import { readUint16LE, readUint32LE } from "./bytes.js";
3
3
  import "./fib/offsets.js";
4
- import { OFFICE_RC4_DOC_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, decryptOfficeRc4, deriveOfficeRc4BaseHash, md5 } from "archive-codec";
4
+ import { OFFICE_RC4_DOC_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, XOR_OBFUSCATION_ARRAY_LENGTH, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2, createXorObfuscationArray, createXorObfuscationKey, createXorObfuscationPasswordVerifier, decryptOfficeRc4, decryptXorObfuscationMethod2, deriveOfficeRc4BaseHash, md5 } from "archive-codec";
5
5
  //#region src/encryption.ts
6
6
  /** [MS-DOC] 2.2.6.2's own EncryptionHeader field layout, byte offsets within the Table stream's own first FibBase.lKey bytes: EncryptionVersionInfo (vMajor/vMinor, 2 bytes each) at 0, then Salt/EncryptedVerifier/EncryptedVerifierHash, each OFFICE_RC4_VERIFIER_LENGTH (16) bytes, back to back. */
7
7
  const HEADER_OFFSET = {
@@ -43,27 +43,61 @@ function verifyPassword(baseHash, header) {
43
43
  if (!(computedHash.length === decryptedVerifierHash.length && computedHash.every((byte, index) => byte === decryptedVerifierHash[index]))) throw new DocUnsupportedError("incorrect password for RC4-encrypted document");
44
44
  }
45
45
  /** Decrypts everything after `prefixLength` bytes of `stream`, leaving the prefix itself untouched -- WORD_DOCUMENT_UNENCRYPTED_PREFIX for WordDocument, FibBase.lKey for Table, each stream's own block-number counter starting fresh at its own byte 0 (this file's own top comment, point 3). */
46
- function decryptStream(baseHash, stream, prefixLength) {
46
+ function decryptStreamRc4(baseHash, stream, prefixLength) {
47
47
  const decrypted = new Uint8Array(stream.length);
48
48
  decrypted.set(stream.subarray(0, prefixLength), 0);
49
49
  decrypted.set(decryptOfficeRc4(baseHash, prefixLength, stream.subarray(prefixLength), OFFICE_RC4_DOC_BLOCK_SIZE), prefixLength);
50
50
  return decrypted;
51
51
  }
52
- /**
53
- * Decrypts an RC4-encrypted (fEncrypted=1, fObfuscated=0) document's WordDocument and Table streams given the password, verifying it first against the Table stream's own EncryptionHeader.
54
- *
55
- * Throws `DocUnsupportedError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
56
- */
57
- function decryptDocStreams(wordDocument, table, password) {
58
- if (password === void 0) throw new DocUnsupportedError("this document is RC4-encrypted ([MS-DOC] 2.2.6.2); call readDocContent with a password to decrypt it");
52
+ /** Decrypts an RC4-encrypted (fEncrypted=1, fObfuscated=0) document's WordDocument and Table streams given the password, verifying it first against the Table stream's own EncryptionHeader. */
53
+ function decryptDocStreamsRc4(wordDocument, table, password) {
59
54
  const header = readRc4Header(table);
60
55
  const baseHash = deriveOfficeRc4BaseHash(password, header.salt);
61
56
  verifyPassword(baseHash, header);
62
57
  const lKey = readUint32LE(wordDocument, 14);
63
58
  return {
64
- wordDocument: decryptStream(baseHash, wordDocument, WORD_DOCUMENT_UNENCRYPTED_PREFIX),
65
- table: decryptStream(baseHash, table, lKey)
59
+ wordDocument: decryptStreamRc4(baseHash, wordDocument, WORD_DOCUMENT_UNENCRYPTED_PREFIX),
60
+ table: decryptStreamRc4(baseHash, table, lKey)
66
61
  };
67
62
  }
63
+ /** Decrypts everything after `prefixLength` bytes of `stream` against Method 2's own transform, leaving the prefix itself untouched -- `initialIndex` is `prefixLength % 16`, the XorArrayIndex the decrypted span's own first byte starts at (confirmed against LibreOffice's own `ww8par.cxx` `DecryptXOR`, whose `InitCipher(); Skip(nSt)` is exactly this: reset to 0, then advance by the skipped prefix's own length mod 16). */
64
+ function decryptStreamXor(array, stream, prefixLength) {
65
+ const decrypted = new Uint8Array(stream.length);
66
+ decrypted.set(stream.subarray(0, prefixLength), 0);
67
+ decrypted.set(decryptXorObfuscationMethod2(array, stream.subarray(prefixLength), prefixLength % XOR_OBFUSCATION_ARRAY_LENGTH), prefixLength);
68
+ return decrypted;
69
+ }
70
+ /**
71
+ * Decrypts an XOR-obfuscated (fEncrypted=1, fObfuscated=1) document's WordDocument and Table streams given the password, verifying it first against FibBase's own lKey field -- not a Table-stream EncryptionHeader the way RC4 needs, see this file's own top comment for why. The Table stream carries no unencrypted prefix under this scheme, unlike RC4's own FibBase.lKey-byte EncryptionHeader; Data (also obfuscated per [MS-DOC], from its own byte 0) is out of scope, matching decryptDocStreamsRc4 and this package's own read.ts, which does not read the Data stream at all.
72
+ */
73
+ function decryptDocStreamsXor(wordDocument, table, password) {
74
+ const lKey = readUint32LE(wordDocument, 14);
75
+ const headerKey = lKey >>> 16 & 65535;
76
+ const headerVerifier = lKey & 65535;
77
+ let computedKey;
78
+ let computedVerifier;
79
+ try {
80
+ computedKey = createXorObfuscationKey(password);
81
+ computedVerifier = createXorObfuscationPasswordVerifier(password);
82
+ } catch (error) {
83
+ if (error instanceof RangeError) throw new DocUnsupportedError("incorrect password for XOR-obfuscated document");
84
+ throw error;
85
+ }
86
+ if (computedKey !== headerKey || computedVerifier !== headerVerifier) throw new DocUnsupportedError("incorrect password for XOR-obfuscated document");
87
+ const array = createXorObfuscationArray(password, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2);
88
+ return {
89
+ wordDocument: decryptStreamXor(array, wordDocument, WORD_DOCUMENT_UNENCRYPTED_PREFIX),
90
+ table: decryptStreamXor(array, table, 0)
91
+ };
92
+ }
93
+ /**
94
+ * Decrypts an encrypted document's WordDocument and Table streams given the password, dispatching on `fObfuscated` between [MS-DOC] 2.2.6.2's RC4 encryption header and 2.2.6.1's XOR obfuscation (Method 2).
95
+ *
96
+ * Throws `DocUnsupportedError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
97
+ */
98
+ function decryptDocStreams(wordDocument, table, password, fObfuscated) {
99
+ if (password === void 0) throw new DocUnsupportedError(`this document is ${fObfuscated ? "XOR-obfuscated ([MS-DOC] 2.2.6.1)" : "RC4-encrypted ([MS-DOC] 2.2.6.2)"}; call readDocContent with a password to decrypt it`);
100
+ return fObfuscated ? decryptDocStreamsXor(wordDocument, table, password) : decryptDocStreamsRc4(wordDocument, table, password);
101
+ }
68
102
  //#endregion
69
103
  export { decryptDocStreams };
package/dist/index.cjs CHANGED
@@ -1,6 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_base64 = require("./base64.cjs");
2
3
  const require_errors = require("./errors.cjs");
3
4
  const require_bytes = require("./bytes.cjs");
5
+ const require_data_stream = require("./data-stream.cjs");
4
6
  const require_fib_offsets = require("./fib/offsets.cjs");
5
7
  const require_detect = require("./detect.cjs");
6
8
  const require_plc = require("./plc.cjs");
@@ -34,6 +36,7 @@ exports.CELL_MARK = require_text_special.CELL_MARK;
34
36
  exports.COMPRESSED_CHARACTER_MAP = require_text_characters.COMPRESSED_CHARACTER_MAP;
35
37
  exports.DATA_STREAM = require_detect.DATA_STREAM;
36
38
  exports.DRAWN_OBJECT = require_text_special.DRAWN_OBJECT;
39
+ exports.DataStreamBuilder = require_data_stream.DataStreamBuilder;
37
40
  exports.DocFormatError = require_errors.DocFormatError;
38
41
  exports.DocUnsupportedError = require_errors.DocUnsupportedError;
39
42
  exports.FC_LCB_VALUE_INDEX = require_fib_offsets.FC_LCB_VALUE_INDEX;
@@ -71,6 +74,7 @@ exports.WORD_DOCUMENT_STREAM = require_detect.WORD_DOCUMENT_STREAM;
71
74
  exports.applyCharacterSprms = require_prop_chp.applyCharacterSprms;
72
75
  exports.applyParagraphSprms = require_prop_pap.applyParagraphSprms;
73
76
  exports.applySectionSprms = require_prop_sep.applySectionSprms;
77
+ exports.base64ToBytes = require_base64.base64ToBytes;
74
78
  exports.buildChpxPages = require_prop_fkp_write.buildChpxPages;
75
79
  exports.buildFib = require_fib_write.buildFib;
76
80
  exports.buildFontTable = require_style_fonts.buildFontTable;
@@ -80,6 +84,7 @@ exports.buildPropertyBinTable = require_prop_fkp_write.buildPropertyBinTable;
80
84
  exports.buildSepx = require_prop_sep_write.buildSepx;
81
85
  exports.buildStshForStyles = require_style_stsh.buildStshForStyles;
82
86
  exports.buildTextClx = require_text_piece_table_write.buildTextClx;
87
+ exports.bytesToBase64 = require_base64.bytesToBase64;
83
88
  exports.characterIstdFromGrpprl = require_prop_chp.characterIstdFromGrpprl;
84
89
  exports.characterOffset = require_text_piece_table.characterOffset;
85
90
  exports.characterSize = require_text_piece_table.characterSize;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,6 @@
1
+ import { base64ToBytes, bytesToBase64 } from "./base64.cjs";
1
2
  import { readInt16LE, readInt32LE, readUint16LE, readUint32LE, readUint8, slice } from "./bytes.cjs";
3
+ import { t as DataStreamBuilder } from "./data-stream-BLCj4-V1.cjs";
2
4
  import { DATA_STREAM, SUMMARY_INFORMATION_STREAM, WORD_DOCUMENT_STREAM, isDocBytes } from "./detect.cjs";
3
5
  import { DocFormatError, DocUnsupportedError } from "./errors.cjs";
4
6
  import { i as tableStreamName, n as parseFib, r as peekFibBaseFlags, t as Fib } from "./fib-6BFIx153.cjs";
@@ -28,4 +30,4 @@ import { readSubdocumentStories, storyText } from "./subdocument.cjs";
28
30
  import { Comment, Footnote, NoteBodies, readNoteBodies } from "./notes.cjs";
29
31
  import { DocContent, DocStreams, readDocContent, readDocStreams } from "./read.cjs";
30
32
  import { WriteDocContentOptions, writeDocContent } from "./write.cjs";
31
- export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, CharacterProperties, ChpxFkp, ChpxRunToWrite, Comment, DATA_STREAM, DRAWN_OBJECT, DocContent, DocFormatError, DocSectionProperties, DocStreams, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, Fib, FibWriteSpec, Footnote, HeaderFooterSlot, HeaderFooterStories, HeaderFooterStory, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NUMBER_FORMAT_BY_NFC, NoteBodies, NumberingDefinition, NumberingDefinitions, NumberingLevel, PARAGRAPH_MARK, PapxFkp, PapxLookup, PapxParagraphToWrite, PapxRecord, ParagraphEntry, ParagraphProperties, Piece, PieceTable, Plc, Prl, PropertyBinTable, ReadContext, ResolvedStyleFormatting, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, SectionProperties, Sprm, Style, StyleSheet, TextRange, WORD_DOCUMENT_STREAM, WriteDocContentOptions, applyCharacterSprms, applyParagraphSprms, applySectionSprms, buildChpxPages, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildStshForStyles, buildTextClx, characterIstdFromGrpprl, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, peekFibBaseFlags, readAllSectionProperties, readDocContent, readDocStreams, readGrpprl, readHeaderFooterStories, readInt16LE, readInt32LE, readNoteBodies, readNumberingDefinitions, readParagraphs, readSubdocumentStories, readTextRange, readUint16LE, readUint32LE, readUint8, resolveStyleFormatting, slice, splitEntriesByBoundaries, storyText, tableStreamName, writeDocContent };
33
+ export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, CharacterProperties, ChpxFkp, ChpxRunToWrite, Comment, DATA_STREAM, DRAWN_OBJECT, DataStreamBuilder, DocContent, DocFormatError, DocSectionProperties, DocStreams, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, Fib, FibWriteSpec, Footnote, HeaderFooterSlot, HeaderFooterStories, HeaderFooterStory, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NUMBER_FORMAT_BY_NFC, NoteBodies, NumberingDefinition, NumberingDefinitions, NumberingLevel, PARAGRAPH_MARK, PapxFkp, PapxLookup, PapxParagraphToWrite, PapxRecord, ParagraphEntry, ParagraphProperties, Piece, PieceTable, Plc, Prl, PropertyBinTable, ReadContext, ResolvedStyleFormatting, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, SectionProperties, Sprm, Style, StyleSheet, TextRange, WORD_DOCUMENT_STREAM, WriteDocContentOptions, applyCharacterSprms, applyParagraphSprms, applySectionSprms, base64ToBytes, buildChpxPages, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildStshForStyles, buildTextClx, bytesToBase64, characterIstdFromGrpprl, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, peekFibBaseFlags, readAllSectionProperties, readDocContent, readDocStreams, readGrpprl, readHeaderFooterStories, readInt16LE, readInt32LE, readNoteBodies, readNumberingDefinitions, readParagraphs, readSubdocumentStories, readTextRange, readUint16LE, readUint32LE, readUint8, resolveStyleFormatting, slice, splitEntriesByBoundaries, storyText, tableStreamName, writeDocContent };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
+ import { base64ToBytes, bytesToBase64 } from "./base64.js";
1
2
  import { readInt16LE, readInt32LE, readUint16LE, readUint32LE, readUint8, slice } from "./bytes.js";
3
+ import { t as DataStreamBuilder } from "./data-stream-BLCj4-V1.js";
2
4
  import { DATA_STREAM, SUMMARY_INFORMATION_STREAM, WORD_DOCUMENT_STREAM, isDocBytes } from "./detect.js";
3
5
  import { DocFormatError, DocUnsupportedError } from "./errors.js";
4
6
  import { i as tableStreamName, n as parseFib, r as peekFibBaseFlags, t as Fib } from "./fib-6BFIx153.js";
@@ -28,4 +30,4 @@ import { readSubdocumentStories, storyText } from "./subdocument.js";
28
30
  import { Comment, Footnote, NoteBodies, readNoteBodies } from "./notes.js";
29
31
  import { DocContent, DocStreams, readDocContent, readDocStreams } from "./read.js";
30
32
  import { WriteDocContentOptions, writeDocContent } from "./write.js";
31
- export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, CharacterProperties, ChpxFkp, ChpxRunToWrite, Comment, DATA_STREAM, DRAWN_OBJECT, DocContent, DocFormatError, DocSectionProperties, DocStreams, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, Fib, FibWriteSpec, Footnote, HeaderFooterSlot, HeaderFooterStories, HeaderFooterStory, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NUMBER_FORMAT_BY_NFC, NoteBodies, NumberingDefinition, NumberingDefinitions, NumberingLevel, PARAGRAPH_MARK, PapxFkp, PapxLookup, PapxParagraphToWrite, PapxRecord, ParagraphEntry, ParagraphProperties, Piece, PieceTable, Plc, Prl, PropertyBinTable, ReadContext, ResolvedStyleFormatting, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, SectionProperties, Sprm, Style, StyleSheet, TextRange, WORD_DOCUMENT_STREAM, WriteDocContentOptions, applyCharacterSprms, applyParagraphSprms, applySectionSprms, buildChpxPages, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildStshForStyles, buildTextClx, characterIstdFromGrpprl, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, peekFibBaseFlags, readAllSectionProperties, readDocContent, readDocStreams, readGrpprl, readHeaderFooterStories, readInt16LE, readInt32LE, readNoteBodies, readNumberingDefinitions, readParagraphs, readSubdocumentStories, readTextRange, readUint16LE, readUint32LE, readUint8, resolveStyleFormatting, slice, splitEntriesByBoundaries, storyText, tableStreamName, writeDocContent };
33
+ export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, CharacterProperties, ChpxFkp, ChpxRunToWrite, Comment, DATA_STREAM, DRAWN_OBJECT, DataStreamBuilder, DocContent, DocFormatError, DocSectionProperties, DocStreams, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, Fib, FibWriteSpec, Footnote, HeaderFooterSlot, HeaderFooterStories, HeaderFooterStory, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NUMBER_FORMAT_BY_NFC, NoteBodies, NumberingDefinition, NumberingDefinitions, NumberingLevel, PARAGRAPH_MARK, PapxFkp, PapxLookup, PapxParagraphToWrite, PapxRecord, ParagraphEntry, ParagraphProperties, Piece, PieceTable, Plc, Prl, PropertyBinTable, ReadContext, ResolvedStyleFormatting, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, SectionProperties, Sprm, Style, StyleSheet, TextRange, WORD_DOCUMENT_STREAM, WriteDocContentOptions, applyCharacterSprms, applyParagraphSprms, applySectionSprms, base64ToBytes, buildChpxPages, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildStshForStyles, buildTextClx, bytesToBase64, characterIstdFromGrpprl, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, peekFibBaseFlags, readAllSectionProperties, readDocContent, readDocStreams, readGrpprl, readHeaderFooterStories, readInt16LE, readInt32LE, readNoteBodies, readNumberingDefinitions, readParagraphs, readSubdocumentStories, readTextRange, readUint16LE, readUint32LE, readUint8, resolveStyleFormatting, slice, splitEntriesByBoundaries, storyText, tableStreamName, writeDocContent };
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
+ import { base64ToBytes, bytesToBase64 } from "./base64.js";
1
2
  import { DocFormatError, DocUnsupportedError } from "./errors.js";
2
3
  import { readInt16LE, readInt32LE, readUint16LE, readUint32LE, readUint8, slice } from "./bytes.js";
4
+ import { DataStreamBuilder } from "./data-stream.js";
3
5
  import { FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, LW_OFFSET } from "./fib/offsets.js";
4
6
  import { DATA_STREAM, SUMMARY_INFORMATION_STREAM, WORD_DOCUMENT_STREAM, isDocBytes } from "./detect.js";
5
7
  import { findLargestAtMost, parsePlc } from "./plc.js";
@@ -28,4 +30,4 @@ import { NUMBER_FORMAT_BY_NFC, readNumberingDefinitions } from "./list/numbering
28
30
  import { readNoteBodies } from "./notes.js";
29
31
  import { readDocContent, readDocStreams } from "./read.js";
30
32
  import { writeDocContent } from "./write.js";
31
- export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, DATA_STREAM, DRAWN_OBJECT, DocFormatError, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NUMBER_FORMAT_BY_NFC, PARAGRAPH_MARK, PropertyBinTable, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, WORD_DOCUMENT_STREAM, applyCharacterSprms, applyParagraphSprms, applySectionSprms, buildChpxPages, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildStshForStyles, buildTextClx, characterIstdFromGrpprl, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, peekFibBaseFlags, readAllSectionProperties, readDocContent, readDocStreams, readGrpprl, readHeaderFooterStories, readInt16LE, readInt32LE, readNoteBodies, readNumberingDefinitions, readParagraphs, readSubdocumentStories, readTextRange, readUint16LE, readUint32LE, readUint8, resolveStyleFormatting, slice, splitEntriesByBoundaries, storyText, tableStreamName, writeDocContent };
33
+ export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, DATA_STREAM, DRAWN_OBJECT, DataStreamBuilder, DocFormatError, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NUMBER_FORMAT_BY_NFC, PARAGRAPH_MARK, PropertyBinTable, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, WORD_DOCUMENT_STREAM, applyCharacterSprms, applyParagraphSprms, applySectionSprms, base64ToBytes, buildChpxPages, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildStshForStyles, buildTextClx, bytesToBase64, characterIstdFromGrpprl, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, peekFibBaseFlags, readAllSectionProperties, readDocContent, readDocStreams, readGrpprl, readHeaderFooterStories, readInt16LE, readInt32LE, readNoteBodies, readNumberingDefinitions, readParagraphs, readSubdocumentStories, readTextRange, readUint16LE, readUint32LE, readUint8, resolveStyleFormatting, slice, splitEntriesByBoundaries, storyText, tableStreamName, writeDocContent };
@@ -0,0 +1,85 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_base64 = require("./base64.cjs");
3
+ const require_errors = require("./errors.cjs");
4
+ //#region src/pictures-write.ts
5
+ const PICF_SIZE = 68;
6
+ const PICF_MM_OFFSET = 6;
7
+ const PICF_DXA_GOAL_OFFSET = 28;
8
+ const PICF_DYA_GOAL_OFFSET = 30;
9
+ const PICF_MX_OFFSET = 32;
10
+ const PICF_MY_OFFSET = 34;
11
+ /** MFPF.mm's MM_SHAPE value, [MS-DOC] 2.9.181 -- the plain, no-source-filename form; this writer never emits MM_SHAPEFILE's own cchPicName/stPicName pair, matching what pictures.ts's own reader treats as the common case. */
12
+ const MM_SHAPE = 100;
13
+ const RECORD_HEADER_SIZE = 8;
14
+ /** OfficeArtBlipJPEG / OfficeArtBlipPNG record types, [MS-ODRAW] 2.2.27/2.2.28. */
15
+ const BLIP_JPEG = 61469;
16
+ const BLIP_PNG = 61470;
17
+ /** rh.recInstance for the single-rgbUid (16-byte) form of each blip -- [MS-ODRAW] 2.2.27's own table for JPEG (RGB), 2.2.28's for PNG; the identical values pictures.ts's own ONE_UID_INSTANCES set already recognises on read. */
18
+ const BLIP_INSTANCE_JPEG = 1130;
19
+ const BLIP_INSTANCE_PNG = 1760;
20
+ const BLIP_UID_SIZE = 16;
21
+ /** The one byte following rgbUid in every OfficeArtBlip variant pictures.ts reads -- [MS-ODRAW]'s own BLIPFileTag, 0xFF for a non-metafile blip (PNG/JPEG are never compressed the way a WMF/EMF metafile blip's own tag byte would state). */
22
+ const BLIP_FILE_TAG = 255;
23
+ const BLIP_TAG_SIZE = 1;
24
+ const TWIPS_PER_POINT = 20;
25
+ /** PICMID.mx/my, [MS-DOC]: "the ratio, measured in tenths of a percent, between the final display width/height and the initial picture width/height" -- this writer always states dxaGoal/dyaGoal as the image's own real size and mx/my as "no scaling" (1000, one thousand tenths-of-a-percent = 100%), matching pictures.ts's own read-side arithmetic (dxaGoal * mx / 1000) exactly at mx = 1000. */
26
+ const NO_SCALING = 1e3;
27
+ /** PICMID.dxaGoal/dyaGoal are a signed 16-bit FieldFormatting value in twips -- [MS-DOC] states no narrower bound than that field width itself. */
28
+ const MAX_INT16 = 32767;
29
+ function recordHeaderBytes(recType, recInstance, recLen) {
30
+ const bytes = new Uint8Array(RECORD_HEADER_SIZE);
31
+ const view = new DataView(bytes.buffer);
32
+ view.setUint16(0, recInstance << 4, true);
33
+ view.setUint16(2, recType, true);
34
+ view.setUint32(4, recLen, true);
35
+ return bytes;
36
+ }
37
+ function twipsFromPt(pt, field) {
38
+ const twips = Math.round(pt * TWIPS_PER_POINT);
39
+ if (twips < 0 || twips > MAX_INT16) throw new require_errors.DocFormatError(`an image's ${field} of ${String(pt)}pt is ${String(twips)} twips, outside the 0..${String(MAX_INT16)} range PICMID.dxaGoal/dyaGoal (a signed 16-bit field) can hold`);
40
+ return twips;
41
+ }
42
+ /** Builds one inline picture's own PICFAndOfficeArtData bytes -- everything pictures.ts's readInlinePicture needs given the Data-stream offset it will end up placed at, which this function does not itself decide (see WrittenInlinePicture's own comment). */
43
+ function buildInlinePicture(image) {
44
+ const recType = image.format === "png" ? BLIP_PNG : image.format === "jpeg" ? BLIP_JPEG : void 0;
45
+ if (recType === void 0) throw new require_errors.DocUnsupportedError(`doc-codec's writer can only write a 'png' or 'jpeg' inline picture -- the two raster formats its own reader decodes from a real OfficeArtBlip; got '${image.format}'`);
46
+ const recInstance = image.format === "png" ? BLIP_INSTANCE_PNG : BLIP_INSTANCE_JPEG;
47
+ const payload = require_base64.base64ToBytes(image.base64);
48
+ const picf = new Uint8Array(PICF_SIZE);
49
+ const picfView = new DataView(picf.buffer);
50
+ picfView.setUint16(PICF_MM_OFFSET, MM_SHAPE, true);
51
+ picfView.setInt16(PICF_DXA_GOAL_OFFSET, twipsFromPt(image.widthPt, "widthPt"), true);
52
+ picfView.setInt16(PICF_DYA_GOAL_OFFSET, twipsFromPt(image.heightPt, "heightPt"), true);
53
+ picfView.setUint16(PICF_MX_OFFSET, NO_SCALING, true);
54
+ picfView.setUint16(PICF_MY_OFFSET, NO_SCALING, true);
55
+ const shapeHeader = recordHeaderBytes(61444, 0, 0);
56
+ const uid = new Uint8Array(BLIP_UID_SIZE);
57
+ const blipHeader = recordHeaderBytes(recType, recInstance, uid.length + BLIP_TAG_SIZE + payload.length);
58
+ const data = new Uint8Array(picf.length + shapeHeader.length + blipHeader.length + uid.length + BLIP_TAG_SIZE + payload.length);
59
+ let cursor = 0;
60
+ data.set(picf, cursor);
61
+ cursor += picf.length;
62
+ data.set(shapeHeader, cursor);
63
+ cursor += shapeHeader.length;
64
+ data.set(blipHeader, cursor);
65
+ cursor += blipHeader.length;
66
+ data.set(uid, cursor);
67
+ cursor += uid.length;
68
+ data[cursor] = BLIP_FILE_TAG;
69
+ cursor += 1;
70
+ data.set(payload, cursor);
71
+ return {
72
+ data,
73
+ buildGrpprl: buildPicLocationGrpprl
74
+ };
75
+ }
76
+ /** sprmCPicLocation, [MS-DOC] 2.6.1 -- a signed 32-bit offset into the Data stream, little-endian. */
77
+ function buildPicLocationGrpprl(dataStreamOffset) {
78
+ const grpprl = [3, 106];
79
+ const operand = /* @__PURE__ */ new Uint8Array(4);
80
+ new DataView(operand.buffer).setInt32(0, dataStreamOffset, true);
81
+ grpprl.push(...operand);
82
+ return grpprl;
83
+ }
84
+ //#endregion
85
+ exports.buildInlinePicture = buildInlinePicture;
@@ -0,0 +1,12 @@
1
+ import { ContentImageBlock } from "document-schema.js";
2
+ //#region src/pictures-write.d.ts
3
+ interface WrittenInlinePicture {
4
+ /** The whole PICFAndOfficeArtData byte blob to append to the Data stream at whatever offset it ends up placed. */
5
+ readonly data: Uint8Array<ArrayBuffer>;
6
+ /** sprmCPicLocation's own grpprl bytes, complete except for its 4-byte operand, which the caller fills in with wherever `data` was actually placed (buildPicLocationGrpprl below) -- the two are split because only the caller (write.ts's own Data-stream accumulator) knows that offset before `data` is placed. */
7
+ readonly buildGrpprl: (dataStreamOffset: number) => number[];
8
+ }
9
+ /** Builds one inline picture's own PICFAndOfficeArtData bytes -- everything pictures.ts's readInlinePicture needs given the Data-stream offset it will end up placed at, which this function does not itself decide (see WrittenInlinePicture's own comment). */
10
+ declare function buildInlinePicture(image: ContentImageBlock): WrittenInlinePicture;
11
+ //#endregion
12
+ export { WrittenInlinePicture, buildInlinePicture };
@@ -0,0 +1,12 @@
1
+ import { ContentImageBlock } from "document-schema.js";
2
+ //#region src/pictures-write.d.ts
3
+ interface WrittenInlinePicture {
4
+ /** The whole PICFAndOfficeArtData byte blob to append to the Data stream at whatever offset it ends up placed. */
5
+ readonly data: Uint8Array<ArrayBuffer>;
6
+ /** sprmCPicLocation's own grpprl bytes, complete except for its 4-byte operand, which the caller fills in with wherever `data` was actually placed (buildPicLocationGrpprl below) -- the two are split because only the caller (write.ts's own Data-stream accumulator) knows that offset before `data` is placed. */
7
+ readonly buildGrpprl: (dataStreamOffset: number) => number[];
8
+ }
9
+ /** Builds one inline picture's own PICFAndOfficeArtData bytes -- everything pictures.ts's readInlinePicture needs given the Data-stream offset it will end up placed at, which this function does not itself decide (see WrittenInlinePicture's own comment). */
10
+ declare function buildInlinePicture(image: ContentImageBlock): WrittenInlinePicture;
11
+ //#endregion
12
+ export { WrittenInlinePicture, buildInlinePicture };
@@ -0,0 +1,84 @@
1
+ import { base64ToBytes } from "./base64.js";
2
+ import { DocFormatError, DocUnsupportedError } from "./errors.js";
3
+ //#region src/pictures-write.ts
4
+ const PICF_SIZE = 68;
5
+ const PICF_MM_OFFSET = 6;
6
+ const PICF_DXA_GOAL_OFFSET = 28;
7
+ const PICF_DYA_GOAL_OFFSET = 30;
8
+ const PICF_MX_OFFSET = 32;
9
+ const PICF_MY_OFFSET = 34;
10
+ /** MFPF.mm's MM_SHAPE value, [MS-DOC] 2.9.181 -- the plain, no-source-filename form; this writer never emits MM_SHAPEFILE's own cchPicName/stPicName pair, matching what pictures.ts's own reader treats as the common case. */
11
+ const MM_SHAPE = 100;
12
+ const RECORD_HEADER_SIZE = 8;
13
+ /** OfficeArtBlipJPEG / OfficeArtBlipPNG record types, [MS-ODRAW] 2.2.27/2.2.28. */
14
+ const BLIP_JPEG = 61469;
15
+ const BLIP_PNG = 61470;
16
+ /** rh.recInstance for the single-rgbUid (16-byte) form of each blip -- [MS-ODRAW] 2.2.27's own table for JPEG (RGB), 2.2.28's for PNG; the identical values pictures.ts's own ONE_UID_INSTANCES set already recognises on read. */
17
+ const BLIP_INSTANCE_JPEG = 1130;
18
+ const BLIP_INSTANCE_PNG = 1760;
19
+ const BLIP_UID_SIZE = 16;
20
+ /** The one byte following rgbUid in every OfficeArtBlip variant pictures.ts reads -- [MS-ODRAW]'s own BLIPFileTag, 0xFF for a non-metafile blip (PNG/JPEG are never compressed the way a WMF/EMF metafile blip's own tag byte would state). */
21
+ const BLIP_FILE_TAG = 255;
22
+ const BLIP_TAG_SIZE = 1;
23
+ const TWIPS_PER_POINT = 20;
24
+ /** PICMID.mx/my, [MS-DOC]: "the ratio, measured in tenths of a percent, between the final display width/height and the initial picture width/height" -- this writer always states dxaGoal/dyaGoal as the image's own real size and mx/my as "no scaling" (1000, one thousand tenths-of-a-percent = 100%), matching pictures.ts's own read-side arithmetic (dxaGoal * mx / 1000) exactly at mx = 1000. */
25
+ const NO_SCALING = 1e3;
26
+ /** PICMID.dxaGoal/dyaGoal are a signed 16-bit FieldFormatting value in twips -- [MS-DOC] states no narrower bound than that field width itself. */
27
+ const MAX_INT16 = 32767;
28
+ function recordHeaderBytes(recType, recInstance, recLen) {
29
+ const bytes = new Uint8Array(RECORD_HEADER_SIZE);
30
+ const view = new DataView(bytes.buffer);
31
+ view.setUint16(0, recInstance << 4, true);
32
+ view.setUint16(2, recType, true);
33
+ view.setUint32(4, recLen, true);
34
+ return bytes;
35
+ }
36
+ function twipsFromPt(pt, field) {
37
+ const twips = Math.round(pt * TWIPS_PER_POINT);
38
+ if (twips < 0 || twips > MAX_INT16) throw new DocFormatError(`an image's ${field} of ${String(pt)}pt is ${String(twips)} twips, outside the 0..${String(MAX_INT16)} range PICMID.dxaGoal/dyaGoal (a signed 16-bit field) can hold`);
39
+ return twips;
40
+ }
41
+ /** Builds one inline picture's own PICFAndOfficeArtData bytes -- everything pictures.ts's readInlinePicture needs given the Data-stream offset it will end up placed at, which this function does not itself decide (see WrittenInlinePicture's own comment). */
42
+ function buildInlinePicture(image) {
43
+ const recType = image.format === "png" ? BLIP_PNG : image.format === "jpeg" ? BLIP_JPEG : void 0;
44
+ if (recType === void 0) throw new DocUnsupportedError(`doc-codec's writer can only write a 'png' or 'jpeg' inline picture -- the two raster formats its own reader decodes from a real OfficeArtBlip; got '${image.format}'`);
45
+ const recInstance = image.format === "png" ? BLIP_INSTANCE_PNG : BLIP_INSTANCE_JPEG;
46
+ const payload = base64ToBytes(image.base64);
47
+ const picf = new Uint8Array(PICF_SIZE);
48
+ const picfView = new DataView(picf.buffer);
49
+ picfView.setUint16(PICF_MM_OFFSET, MM_SHAPE, true);
50
+ picfView.setInt16(PICF_DXA_GOAL_OFFSET, twipsFromPt(image.widthPt, "widthPt"), true);
51
+ picfView.setInt16(PICF_DYA_GOAL_OFFSET, twipsFromPt(image.heightPt, "heightPt"), true);
52
+ picfView.setUint16(PICF_MX_OFFSET, NO_SCALING, true);
53
+ picfView.setUint16(PICF_MY_OFFSET, NO_SCALING, true);
54
+ const shapeHeader = recordHeaderBytes(61444, 0, 0);
55
+ const uid = new Uint8Array(BLIP_UID_SIZE);
56
+ const blipHeader = recordHeaderBytes(recType, recInstance, uid.length + BLIP_TAG_SIZE + payload.length);
57
+ const data = new Uint8Array(picf.length + shapeHeader.length + blipHeader.length + uid.length + BLIP_TAG_SIZE + payload.length);
58
+ let cursor = 0;
59
+ data.set(picf, cursor);
60
+ cursor += picf.length;
61
+ data.set(shapeHeader, cursor);
62
+ cursor += shapeHeader.length;
63
+ data.set(blipHeader, cursor);
64
+ cursor += blipHeader.length;
65
+ data.set(uid, cursor);
66
+ cursor += uid.length;
67
+ data[cursor] = BLIP_FILE_TAG;
68
+ cursor += 1;
69
+ data.set(payload, cursor);
70
+ return {
71
+ data,
72
+ buildGrpprl: buildPicLocationGrpprl
73
+ };
74
+ }
75
+ /** sprmCPicLocation, [MS-DOC] 2.6.1 -- a signed 32-bit offset into the Data stream, little-endian. */
76
+ function buildPicLocationGrpprl(dataStreamOffset) {
77
+ const grpprl = [3, 106];
78
+ const operand = /* @__PURE__ */ new Uint8Array(4);
79
+ new DataView(operand.buffer).setInt32(0, dataStreamOffset, true);
80
+ grpprl.push(...operand);
81
+ return grpprl;
82
+ }
83
+ //#endregion
84
+ export { buildInlinePicture };
package/dist/pictures.cjs CHANGED
@@ -1,4 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_base64 = require("./base64.cjs");
2
3
  const require_bytes = require("./bytes.cjs");
3
4
  //#region src/pictures.ts
4
5
  const PICF_SIZE = 68;
@@ -59,10 +60,11 @@ function readInlinePicture(dataStream, picLocation) {
59
60
  if (uidBytes === void 0) return void 0;
60
61
  const blipDataStart = cursor + RECORD_HEADER_SIZE + uidBytes + BLIP_TAG_SIZE;
61
62
  const blipDataLength = blipHeader.recLen - uidBytes - BLIP_TAG_SIZE;
63
+ const blipBytes = require_bytes.slice(dataStream, blipDataStart, blipDataLength, "OfficeArtBlip file data in the Data stream");
62
64
  return {
63
65
  kind: "image",
64
66
  format,
65
- base64: bytesToBase64(require_bytes.slice(dataStream, blipDataStart, blipDataLength, "OfficeArtBlip file data in the Data stream")),
67
+ base64: require_base64.bytesToBase64(blipBytes),
66
68
  widthPt: dxaGoal * mx / SCALE_DENOMINATOR / TWIPS_PER_POINT,
67
69
  heightPt: dyaGoal * my / SCALE_DENOMINATOR / TWIPS_PER_POINT
68
70
  };
@@ -74,20 +76,5 @@ function blipFormat(recType) {
74
76
  default: return;
75
77
  }
76
78
  }
77
- const BASE64_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
78
- function bytesToBase64(bytes) {
79
- let out = "";
80
- const len = bytes.length;
81
- for (let index = 0; index < len; index += 3) {
82
- const b0 = bytes[index] ?? 0;
83
- const b1 = index + 1 < len ? bytes[index + 1] ?? 0 : 0;
84
- const b2 = index + 2 < len ? bytes[index + 2] ?? 0 : 0;
85
- out += BASE64_TABLE.charAt(b0 >> 2);
86
- out += BASE64_TABLE.charAt((b0 & 3) << 4 | b1 >> 4);
87
- out += index + 1 < len ? BASE64_TABLE.charAt((b1 & 15) << 2 | b2 >> 6) : "=";
88
- out += index + 2 < len ? BASE64_TABLE.charAt(b2 & 63) : "=";
89
- }
90
- return out;
91
- }
92
79
  //#endregion
93
80
  exports.readInlinePicture = readInlinePicture;
package/dist/pictures.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { bytesToBase64 } from "./base64.js";
1
2
  import { readInt16LE, readUint16LE, readUint32LE, readUint8, slice } from "./bytes.js";
2
3
  //#region src/pictures.ts
3
4
  const PICF_SIZE = 68;
@@ -58,10 +59,11 @@ function readInlinePicture(dataStream, picLocation) {
58
59
  if (uidBytes === void 0) return void 0;
59
60
  const blipDataStart = cursor + RECORD_HEADER_SIZE + uidBytes + BLIP_TAG_SIZE;
60
61
  const blipDataLength = blipHeader.recLen - uidBytes - BLIP_TAG_SIZE;
62
+ const blipBytes = slice(dataStream, blipDataStart, blipDataLength, "OfficeArtBlip file data in the Data stream");
61
63
  return {
62
64
  kind: "image",
63
65
  format,
64
- base64: bytesToBase64(slice(dataStream, blipDataStart, blipDataLength, "OfficeArtBlip file data in the Data stream")),
66
+ base64: bytesToBase64(blipBytes),
65
67
  widthPt: dxaGoal * mx / SCALE_DENOMINATOR / TWIPS_PER_POINT,
66
68
  heightPt: dyaGoal * my / SCALE_DENOMINATOR / TWIPS_PER_POINT
67
69
  };
@@ -73,20 +75,5 @@ function blipFormat(recType) {
73
75
  default: return;
74
76
  }
75
77
  }
76
- const BASE64_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
77
- function bytesToBase64(bytes) {
78
- let out = "";
79
- const len = bytes.length;
80
- for (let index = 0; index < len; index += 3) {
81
- const b0 = bytes[index] ?? 0;
82
- const b1 = index + 1 < len ? bytes[index + 1] ?? 0 : 0;
83
- const b2 = index + 2 < len ? bytes[index + 2] ?? 0 : 0;
84
- out += BASE64_TABLE.charAt(b0 >> 2);
85
- out += BASE64_TABLE.charAt((b0 & 3) << 4 | b1 >> 4);
86
- out += index + 1 < len ? BASE64_TABLE.charAt((b1 & 15) << 2 | b2 >> 6) : "=";
87
- out += index + 2 < len ? BASE64_TABLE.charAt(b2 & 63) : "=";
88
- }
89
- return out;
90
- }
91
78
  //#endregion
92
79
  export { readInlinePicture };
@@ -45,16 +45,23 @@ function buildSepx(grpprl) {
45
45
  bytes.set(grpprl, 2);
46
46
  return bytes;
47
47
  }
48
- /** PlcfSed for exactly one section: two CPs (0 and ccpText) bracketing the single Sed ([MS-DOC] 2.9.269) this writer ever emits, whose fcSepx names where buildSepx's own bytes were placed in the WordDocument stream and whose fn/fnMpr/fcMpr fields carry the values [MS-DOC] states are ignored. */
49
- function buildPlcfSed(ccpText, fcSepx) {
50
- const bytes = /* @__PURE__ */ new Uint8Array(20);
48
+ /** PlcfSed for `startCps.length` sections, [MS-DOC] 2.9.269/2.8.26: `startCps` (each section's own PlcfSed.aCp[i], "the beginning of a range of text ... that constitutes a section") plus a trailing `ccpText` -- the "last CP does not begin a new section" terminator -- bracketing one 12-byte Sed per section, each naming where that section's own buildSepx bytes were placed in the WordDocument stream (`fcSepxList`, the same order as `startCps`) and whose fn/fnMpr/fcMpr fields carry the values [MS-DOC] states are ignored. A single-section document is simply the `startCps.length === 1` case. */
49
+ function buildPlcfSed(startCps, ccpText, fcSepxList) {
50
+ if (startCps.length !== fcSepxList.length) throw new require_errors.DocFormatError(`internal defect: buildPlcfSed was given ${String(startCps.length)} section start CPs but ${String(fcSepxList.length)} Sepx offsets -- these must be the same length`);
51
+ const keys = [...startCps, ccpText];
52
+ const keyBytes = keys.length * 4;
53
+ const bytes = new Uint8Array(keyBytes + fcSepxList.length * 12);
51
54
  const view = new DataView(bytes.buffer);
52
- view.setUint32(0, 0, true);
53
- view.setUint32(4, ccpText, true);
54
- view.setUint16(8, 0, true);
55
- view.setUint32(10, fcSepx, true);
56
- view.setUint16(14, 0, true);
57
- view.setUint32(16, 4294967295, true);
55
+ keys.forEach((cp, index) => {
56
+ view.setUint32(index * 4, cp, true);
57
+ });
58
+ fcSepxList.forEach((fcSepx, index) => {
59
+ const base = keyBytes + index * 12;
60
+ view.setUint16(base, 0, true);
61
+ view.setUint32(base + 2, fcSepx, true);
62
+ view.setUint16(base + 6, 0, true);
63
+ view.setUint32(base + 8, 4294967295, true);
64
+ });
58
65
  return bytes;
59
66
  }
60
67
  //#endregion
@@ -3,7 +3,7 @@ import { ContentSection } from "document-schema.js";
3
3
  declare function encodeSectionGrpprl(section: Pick<ContentSection, "pageSize" | "margins">): number[];
4
4
  /** Sepx, [MS-DOC] 2.9.279: a 2-byte cb (grpprl's own length) followed by the grpprl itself. */
5
5
  declare function buildSepx(grpprl: readonly number[]): Uint8Array<ArrayBuffer>;
6
- /** PlcfSed for exactly one section: two CPs (0 and ccpText) bracketing the single Sed ([MS-DOC] 2.9.269) this writer ever emits, whose fcSepx names where buildSepx's own bytes were placed in the WordDocument stream and whose fn/fnMpr/fcMpr fields carry the values [MS-DOC] states are ignored. */
7
- declare function buildPlcfSed(ccpText: number, fcSepx: number): Uint8Array<ArrayBuffer>;
6
+ /** PlcfSed for `startCps.length` sections, [MS-DOC] 2.9.269/2.8.26: `startCps` (each section's own PlcfSed.aCp[i], "the beginning of a range of text ... that constitutes a section") plus a trailing `ccpText` -- the "last CP does not begin a new section" terminator -- bracketing one 12-byte Sed per section, each naming where that section's own buildSepx bytes were placed in the WordDocument stream (`fcSepxList`, the same order as `startCps`) and whose fn/fnMpr/fcMpr fields carry the values [MS-DOC] states are ignored. A single-section document is simply the `startCps.length === 1` case. */
7
+ declare function buildPlcfSed(startCps: readonly number[], ccpText: number, fcSepxList: readonly number[]): Uint8Array<ArrayBuffer>;
8
8
  //#endregion
9
9
  export { buildPlcfSed, buildSepx, encodeSectionGrpprl };
@@ -3,7 +3,7 @@ import { ContentSection } from "document-schema.js";
3
3
  declare function encodeSectionGrpprl(section: Pick<ContentSection, "pageSize" | "margins">): number[];
4
4
  /** Sepx, [MS-DOC] 2.9.279: a 2-byte cb (grpprl's own length) followed by the grpprl itself. */
5
5
  declare function buildSepx(grpprl: readonly number[]): Uint8Array<ArrayBuffer>;
6
- /** PlcfSed for exactly one section: two CPs (0 and ccpText) bracketing the single Sed ([MS-DOC] 2.9.269) this writer ever emits, whose fcSepx names where buildSepx's own bytes were placed in the WordDocument stream and whose fn/fnMpr/fcMpr fields carry the values [MS-DOC] states are ignored. */
7
- declare function buildPlcfSed(ccpText: number, fcSepx: number): Uint8Array<ArrayBuffer>;
6
+ /** PlcfSed for `startCps.length` sections, [MS-DOC] 2.9.269/2.8.26: `startCps` (each section's own PlcfSed.aCp[i], "the beginning of a range of text ... that constitutes a section") plus a trailing `ccpText` -- the "last CP does not begin a new section" terminator -- bracketing one 12-byte Sed per section, each naming where that section's own buildSepx bytes were placed in the WordDocument stream (`fcSepxList`, the same order as `startCps`) and whose fn/fnMpr/fcMpr fields carry the values [MS-DOC] states are ignored. A single-section document is simply the `startCps.length === 1` case. */
7
+ declare function buildPlcfSed(startCps: readonly number[], ccpText: number, fcSepxList: readonly number[]): Uint8Array<ArrayBuffer>;
8
8
  //#endregion
9
9
  export { buildPlcfSed, buildSepx, encodeSectionGrpprl };
@@ -44,16 +44,23 @@ function buildSepx(grpprl) {
44
44
  bytes.set(grpprl, 2);
45
45
  return bytes;
46
46
  }
47
- /** PlcfSed for exactly one section: two CPs (0 and ccpText) bracketing the single Sed ([MS-DOC] 2.9.269) this writer ever emits, whose fcSepx names where buildSepx's own bytes were placed in the WordDocument stream and whose fn/fnMpr/fcMpr fields carry the values [MS-DOC] states are ignored. */
48
- function buildPlcfSed(ccpText, fcSepx) {
49
- const bytes = /* @__PURE__ */ new Uint8Array(20);
47
+ /** PlcfSed for `startCps.length` sections, [MS-DOC] 2.9.269/2.8.26: `startCps` (each section's own PlcfSed.aCp[i], "the beginning of a range of text ... that constitutes a section") plus a trailing `ccpText` -- the "last CP does not begin a new section" terminator -- bracketing one 12-byte Sed per section, each naming where that section's own buildSepx bytes were placed in the WordDocument stream (`fcSepxList`, the same order as `startCps`) and whose fn/fnMpr/fcMpr fields carry the values [MS-DOC] states are ignored. A single-section document is simply the `startCps.length === 1` case. */
48
+ function buildPlcfSed(startCps, ccpText, fcSepxList) {
49
+ if (startCps.length !== fcSepxList.length) throw new DocFormatError(`internal defect: buildPlcfSed was given ${String(startCps.length)} section start CPs but ${String(fcSepxList.length)} Sepx offsets -- these must be the same length`);
50
+ const keys = [...startCps, ccpText];
51
+ const keyBytes = keys.length * 4;
52
+ const bytes = new Uint8Array(keyBytes + fcSepxList.length * 12);
50
53
  const view = new DataView(bytes.buffer);
51
- view.setUint32(0, 0, true);
52
- view.setUint32(4, ccpText, true);
53
- view.setUint16(8, 0, true);
54
- view.setUint32(10, fcSepx, true);
55
- view.setUint16(14, 0, true);
56
- view.setUint32(16, 4294967295, true);
54
+ keys.forEach((cp, index) => {
55
+ view.setUint32(index * 4, cp, true);
56
+ });
57
+ fcSepxList.forEach((fcSepx, index) => {
58
+ const base = keyBytes + index * 12;
59
+ view.setUint16(base, 0, true);
60
+ view.setUint32(base + 2, fcSepx, true);
61
+ view.setUint16(base + 6, 0, true);
62
+ view.setUint32(base + 8, 4294967295, true);
63
+ });
57
64
  return bytes;
58
65
  }
59
66
  //#endregion
package/dist/read.cjs CHANGED
@@ -39,8 +39,7 @@ function readDocStreams(bytes, password) {
39
39
  let wordDocument = wordDocumentStream.bytes;
40
40
  let table = tableStream.bytes;
41
41
  if (flags.fEncrypted) {
42
- if (flags.fObfuscated) throw new require_errors.DocUnsupportedError("this document is XOR-obfuscated ([MS-DOC] 2.2.6.1); doc-codec cannot decrypt it, and reading its streams as plaintext would produce arbitrary text rather than the document's own");
43
- const decrypted = require_encryption.decryptDocStreams(wordDocument, table, password);
42
+ const decrypted = require_encryption.decryptDocStreams(wordDocument, table, password, flags.fObfuscated);
44
43
  wordDocument = decrypted.wordDocument;
45
44
  table = decrypted.table;
46
45
  }
package/dist/read.js CHANGED
@@ -1,4 +1,4 @@
1
- import { DocFormatError, DocUnsupportedError } from "./errors.js";
1
+ import { DocFormatError } from "./errors.js";
2
2
  import { slice } from "./bytes.js";
3
3
  import { DATA_STREAM, SUMMARY_INFORMATION_STREAM, WORD_DOCUMENT_STREAM } from "./detect.js";
4
4
  import { decryptDocStreams } from "./encryption.js";
@@ -38,8 +38,7 @@ function readDocStreams(bytes, password) {
38
38
  let wordDocument = wordDocumentStream.bytes;
39
39
  let table = tableStream.bytes;
40
40
  if (flags.fEncrypted) {
41
- if (flags.fObfuscated) throw new DocUnsupportedError("this document is XOR-obfuscated ([MS-DOC] 2.2.6.1); doc-codec cannot decrypt it, and reading its streams as plaintext would produce arbitrary text rather than the document's own");
42
- const decrypted = decryptDocStreams(wordDocument, table, password);
41
+ const decrypted = decryptDocStreams(wordDocument, table, password, flags.fObfuscated);
43
42
  wordDocument = decrypted.wordDocument;
44
43
  table = decrypted.table;
45
44
  }