@stll/folio-core 0.60.0 → 0.62.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 (173) hide show
  1. package/README.md +13 -0
  2. package/dist/ai-edits/apply.js +139 -32
  3. package/dist/ai-edits/headless.d.ts +3 -0
  4. package/dist/ai-edits/headless.js +70 -75
  5. package/dist/ai-edits/note-story-inventory.d.ts +7 -0
  6. package/dist/ai-edits/note-story-inventory.js +11 -0
  7. package/dist/ai-edits/read.js +16 -9
  8. package/dist/ai-edits/revisionStretches.js +52 -1
  9. package/dist/ai-edits/table-row-column-mutations.js +6 -1
  10. package/dist/compare/reproducible-package.js +2 -1
  11. package/dist/compare/verification.js +1 -0
  12. package/dist/compat/eigenpal.d.ts +3 -1
  13. package/dist/controller/canonicalClipboard.js +2 -1
  14. package/dist/controller/canonicalListRendering.d.ts +4 -6
  15. package/dist/controller/canonicalListRendering.js +3 -157
  16. package/dist/controller/headerFooterEditorManager.d.ts +1 -0
  17. package/dist/controller/headerFooterEditorManager.js +11 -0
  18. package/dist/controller/hiddenEditorManager.d.ts +1 -1
  19. package/dist/controller/hiddenEditorManager.js +3 -1
  20. package/dist/controller/layoutPipeline.d.ts +3 -1
  21. package/dist/controller/layoutPipeline.js +13 -4
  22. package/dist/controller/layoutSession.d.ts +2 -1
  23. package/dist/controller/noteEditorManager.d.ts +1 -1
  24. package/dist/display-list/build/imagePrimitives.js +28 -12
  25. package/dist/display-list/build/paragraphPrimitives.js +15 -2
  26. package/dist/display-list/dom/renderDisplayListToDom.js +8 -5
  27. package/dist/display-list/types.d.ts +7 -3
  28. package/dist/docx/canonicalStoryRepack.js +3 -2
  29. package/dist/docx/commentTextParagraph.d.ts +6 -0
  30. package/dist/docx/commentTextParagraph.js +15 -0
  31. package/dist/docx/conformance.js +6 -1
  32. package/dist/docx/diagramPreview.js +2 -1
  33. package/dist/docx/documentParser.d.ts +13 -1
  34. package/dist/docx/documentParser.js +1 -1
  35. package/dist/docx/encryption/compoundFile.d.ts +0 -8
  36. package/dist/docx/encryption/compoundFile.js +2 -1
  37. package/dist/docx/encryption/cryptoBytes.d.ts +2 -2
  38. package/dist/docx/encryption/cryptoBytes.js +3 -18
  39. package/dist/docx/encryption/encryptionInfo.d.ts +0 -5
  40. package/dist/docx/encryption/encryptionInfo.js +2 -1
  41. package/dist/docx/ensureParaIds.js +2 -1
  42. package/dist/docx/groupDrawingParser.js +28 -13
  43. package/dist/docx/listModelNormalization.d.ts +10 -0
  44. package/dist/docx/listModelNormalization.js +166 -0
  45. package/dist/docx/metadataPrivacy.js +3 -2
  46. package/dist/docx/metafileRaster.js +2 -1
  47. package/dist/docx/packageParts.js +6 -5
  48. package/dist/docx/paragraphParser.js +6 -4
  49. package/dist/docx/parser.js +5 -4
  50. package/dist/docx/preservedRunContent.js +11 -2
  51. package/dist/docx/removeHeaderFooterParts.js +8 -7
  52. package/dist/docx/replyToComment.js +3 -13
  53. package/dist/docx/rezip.js +88 -89
  54. package/dist/docx/runParser.js +12 -6
  55. package/dist/docx/runPropertyPresence.d.ts +11 -0
  56. package/dist/docx/runPropertyPresence.js +14 -0
  57. package/dist/docx/selectiveSave.js +15 -14
  58. package/dist/docx/serializer/runSerializer.js +3 -2
  59. package/dist/docx/serializer/tableSerializer.js +2 -1
  60. package/dist/docx/server/applyDocxXmlPatchProposal.js +2 -2
  61. package/dist/docx/server/boundedArchive.js +2 -1
  62. package/dist/docx/server/evaluateDocxXmlPatchProposal.js +2 -1
  63. package/dist/docx/server/inspectDocxPackage.js +5 -1
  64. package/dist/docx/server/validateDocxConformance.js +2 -1
  65. package/dist/docx/tableParser.js +7 -4
  66. package/dist/docx/tableRowPropertyPresence.d.ts +9 -0
  67. package/dist/docx/tableRowPropertyPresence.js +15 -0
  68. package/dist/docx/unzip.js +2 -1
  69. package/dist/docx/xmlEncoding.js +6 -5
  70. package/dist/fonts/fontsourceFaces.js +2 -1
  71. package/dist/fonts/sfnt/parse.js +6 -9
  72. package/dist/fonts/sfnt/subset.js +2 -1
  73. package/dist/fonts/sfnt/tables.js +2 -3
  74. package/dist/fonts/sfnt/woff.js +2 -1
  75. package/dist/i18n/messages/ar.gen.js +1 -0
  76. package/dist/i18n/messages/catalogs.gen.d.ts +17 -0
  77. package/dist/i18n/messages/catalogs.gen.js +17 -0
  78. package/dist/i18n/messages/cs.gen.js +1 -0
  79. package/dist/i18n/messages/de.gen.js +1 -0
  80. package/dist/i18n/messages/en.gen.js +1 -0
  81. package/dist/i18n/messages/es.gen.js +1 -0
  82. package/dist/i18n/messages/et.gen.js +1 -0
  83. package/dist/i18n/messages/fr.gen.js +1 -0
  84. package/dist/i18n/messages/he.gen.js +1 -0
  85. package/dist/i18n/messages/hi.gen.js +1 -0
  86. package/dist/i18n/messages/hu.gen.js +1 -0
  87. package/dist/i18n/messages/lt.gen.js +1 -0
  88. package/dist/i18n/messages/lv.gen.js +1 -0
  89. package/dist/i18n/messages/messages.gen.d.ts +1 -0
  90. package/dist/i18n/messages/pl.gen.js +1 -0
  91. package/dist/i18n/messages/pt-BR.gen.js +1 -0
  92. package/dist/i18n/messages/sk.gen.js +1 -0
  93. package/dist/i18n/messages/tr.gen.js +1 -0
  94. package/dist/i18n/messages/zh-CN.gen.js +1 -0
  95. package/dist/index.d.ts +3 -1
  96. package/dist/internal/revisionResolutionInline.d.ts +2 -1
  97. package/dist/internal/revisionResolutionInline.js +22 -1
  98. package/dist/internal/wholeStoryRevisionResolution.js +24 -7
  99. package/dist/layout-bridge/convert/endnoteLayout.d.ts +1 -1
  100. package/dist/layout-bridge/convert/endnoteLayout.js +1 -3
  101. package/dist/layout-bridge/convert/flowConversionShared.d.ts +2 -0
  102. package/dist/layout-bridge/convert/footnoteLayout.d.ts +4 -0
  103. package/dist/layout-bridge/convert/footnoteLayout.js +13 -3
  104. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +5 -0
  105. package/dist/layout-bridge/convert/headerFooterLayout.js +12 -15
  106. package/dist/layout-bridge/convert/paragraphRuns.js +2 -1
  107. package/dist/layout-engine/index.js +2 -2
  108. package/dist/layout-engine/measure/crossRunWords.js +12 -2
  109. package/dist/layout-engine/noteAreaFlow.d.ts +4 -4
  110. package/dist/layout-engine/noteAreaFlow.js +12 -6
  111. package/dist/layout-engine/types.d.ts +5 -2
  112. package/dist/layout-painter/index.d.ts +1 -1
  113. package/dist/managers/AutoSaveManager.js +2 -1
  114. package/dist/managers/EditorModeManager.d.ts +10 -1
  115. package/dist/managers/EditorModeManager.js +15 -1
  116. package/dist/managers/FindReplaceManager.d.ts +13 -6
  117. package/dist/managers/FindReplaceManager.js +15 -9
  118. package/dist/managers/TableSelectionManager.d.ts +1 -1
  119. package/dist/managers/autoSaveCodec.js +2 -6
  120. package/dist/managers/reviewDisplayMode.d.ts +8 -0
  121. package/dist/managers/reviewDisplayMode.js +15 -0
  122. package/dist/paged-layout/editorScrollRoot.d.ts +3 -1
  123. package/dist/paged-layout/editorScrollRoot.js +2 -2
  124. package/dist/paged-layout/pageText.d.ts +11 -2
  125. package/dist/paged-layout/pageText.js +8 -3
  126. package/dist/pdf/fonts.js +5 -14
  127. package/dist/pdf/images.js +4 -2
  128. package/dist/pdf/objects.d.ts +0 -11
  129. package/dist/pdf/objects.js +7 -9
  130. package/dist/pdf/writePdf.js +2 -1
  131. package/dist/prosemirror/commands/comments.js +51 -13
  132. package/dist/prosemirror/commentRangeInlineAtoms.d.ts +40 -0
  133. package/dist/prosemirror/commentRangeInlineAtoms.js +33 -0
  134. package/dist/prosemirror/containerFinalParagraph.d.ts +8 -8
  135. package/dist/prosemirror/containerFinalParagraph.js +8 -8
  136. package/dist/prosemirror/conversion/fromProseDoc.js +14 -8
  137. package/dist/prosemirror/conversion/toProseDoc.js +16 -23
  138. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +16 -2
  139. package/dist/prosemirror/fieldRepresentation.js +4 -2
  140. package/dist/prosemirror/findReplaceSelection.d.ts +48 -2
  141. package/dist/prosemirror/findReplaceSelection.js +153 -38
  142. package/dist/prosemirror/inlineDisplayText.d.ts +10 -0
  143. package/dist/prosemirror/inlineDisplayText.js +29 -0
  144. package/dist/prosemirror/markupViewProjection.d.ts +10 -1
  145. package/dist/prosemirror/markupViewProjection.js +21 -3
  146. package/dist/prosemirror/plugins/transactionInvariants.js +6 -1
  147. package/dist/prosemirror/replacedAnnotations.d.ts +15 -1
  148. package/dist/prosemirror/replacedAnnotations.js +71 -12
  149. package/dist/prosemirror/runFormattingReconciliation.d.ts +3 -1
  150. package/dist/prosemirror/runFormattingReconciliation.js +10 -2
  151. package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +5 -7
  152. package/dist/prosemirror/runIdentityAcrossRevisions.js +66 -38
  153. package/dist/prosemirror/runPropertyChangeResolution.js +13 -1
  154. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
  155. package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
  156. package/dist/utils/base64.d.ts +1 -31
  157. package/dist/utils/base64.js +19 -33
  158. package/dist/utils/clipboard.js +5 -4
  159. package/dist/utils/colorResolver.js +11 -2
  160. package/dist/utils/findMatchView.d.ts +32 -0
  161. package/dist/utils/findMatchView.js +406 -0
  162. package/dist/utils/findReplace.d.ts +67 -4
  163. package/dist/utils/findReplace.js +359 -37
  164. package/dist/utils/findSearchKey.d.ts +29 -0
  165. package/dist/utils/findSearchKey.js +34 -0
  166. package/dist/utils/replaceText.js +138 -94
  167. package/dist/utils/sliceText.d.ts +2 -0
  168. package/dist/utils/sliceText.js +2 -0
  169. package/dist/utils/textEncoding.d.ts +2 -0
  170. package/dist/utils/textEncoding.js +2 -0
  171. package/dist/utils/tiffConverter.d.ts +0 -9
  172. package/dist/utils/tiffConverter.js +2 -4
  173. package/package.json +51 -25
@@ -1,12 +1,4 @@
1
1
  //#region src/docx/encryption/compoundFile.d.ts
2
- /**
3
- * Read-only MS-CFB compound file access for encrypted OOXML packages.
4
- *
5
- * Encrypted `.docx` files store `EncryptionInfo` and `EncryptedPackage` streams
6
- * at the compound-file root. This reader implements only what MS-OFFCRYPTO needs.
7
- *
8
- * @see https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb/
9
- */
10
2
  type CompoundFileReader = {
11
3
  readStream: (absolutePath: string) => Uint8Array | null;
12
4
  };
@@ -1,3 +1,4 @@
1
+ import { decodeText } from "../../utils/textEncoding.js";
1
2
  import { joinBytes } from "./cryptoBytes.js";
2
3
  //#region src/docx/encryption/compoundFile.ts
3
4
  /**
@@ -39,7 +40,7 @@ const u64 = (view, offset) => {
39
40
  return BigInt(view.getUint32(offset + 4, true)) << 32n | low;
40
41
  };
41
42
  const decodeName = (nameBytes) => {
42
- const decoded = new TextDecoder("utf-16le").decode(nameBytes);
43
+ const decoded = decodeText(nameBytes, { encoding: "utf-16le" });
43
44
  let end = decoded.length;
44
45
  while (end > 0 && decoded.charCodeAt(end - 1) === 0) end--;
45
46
  return decoded.slice(0, end);
@@ -2,10 +2,10 @@
2
2
  /** Byte helpers shared by MS-CFB and MS-OFFCRYPTO agile decryption. */
3
3
  declare const joinBytes: (parts: readonly Uint8Array[]) => Uint8Array;
4
4
  declare const writeUint32Le: (value: number) => Uint8Array;
5
- declare const passwordToUtf16Le: (password: string) => Uint8Array;
5
+ declare const passwordToUtf16Le: (text: string) => Uint8Array<ArrayBuffer>;
6
6
  declare const bytesEqual: (left: Uint8Array, right: Uint8Array) => boolean;
7
7
  declare const toArrayBuffer: (bytes: Uint8Array) => ArrayBuffer;
8
- declare const decodeBase64: (encoded: string) => Uint8Array;
8
+ declare const decodeBase64: (text: string) => Uint8Array<ArrayBuffer>;
9
9
  declare const padToBlock: (bytes: Uint8Array, blockSize: number, padByte?: number) => Uint8Array;
10
10
  //#endregion
11
11
  export { bytesEqual, decodeBase64, joinBytes, padToBlock, passwordToUtf16Le, toArrayBuffer, writeUint32Le };
@@ -1,3 +1,4 @@
1
+ import { base64ToBytes, encodeUtf16LE } from "../../utils/textEncoding.js";
1
2
  //#region src/docx/encryption/cryptoBytes.ts
2
3
  /** Byte helpers shared by MS-CFB and MS-OFFCRYPTO agile decryption. */
3
4
  const joinBytes = (parts) => {
@@ -19,15 +20,7 @@ const writeUint32Le = (value) => {
19
20
  out[3] = value >>> 24 & 255;
20
21
  return out;
21
22
  };
22
- const passwordToUtf16Le = (password) => {
23
- const out = new Uint8Array(password.length * 2);
24
- for (let i = 0; i < password.length; i++) {
25
- const code = password.charCodeAt(i);
26
- out[i * 2] = code & 255;
27
- out[i * 2 + 1] = code >> 8;
28
- }
29
- return out;
30
- };
23
+ const passwordToUtf16Le = encodeUtf16LE;
31
24
  const bytesEqual = (left, right) => {
32
25
  if (left.length !== right.length) return false;
33
26
  let mismatch = 0;
@@ -38,15 +31,7 @@ const toArrayBuffer = (bytes) => {
38
31
  if (bytes.buffer instanceof ArrayBuffer && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength) return bytes.buffer;
39
32
  return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
40
33
  };
41
- const decodeBase64 = (encoded) => {
42
- if (typeof atob === "function") {
43
- const binary = atob(encoded);
44
- const out = new Uint8Array(binary.length);
45
- for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
46
- return out;
47
- }
48
- return new Uint8Array(Buffer.from(encoded, "base64"));
49
- };
34
+ const decodeBase64 = base64ToBytes;
50
35
  const padToBlock = (bytes, blockSize, padByte = 54) => {
51
36
  if (bytes.length >= blockSize) return bytes.subarray(0, blockSize);
52
37
  const out = new Uint8Array(blockSize);
@@ -1,9 +1,4 @@
1
1
  //#region src/docx/encryption/encryptionInfo.d.ts
2
- /**
3
- * Parse the EncryptionInfo stream for Agile Encryption (Office 2010+).
4
- *
5
- * @see https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/87020a34-e73f-4139-99bc-bbdf6cf6fa55
6
- */
7
2
  type AgileKeyMaterial = {
8
3
  packageSalt: Uint8Array;
9
4
  packageKeyBits: number;
@@ -1,3 +1,4 @@
1
+ import { decodeText } from "../../utils/textEncoding.js";
1
2
  import { decodeBase64 } from "./cryptoBytes.js";
2
3
  import { DOCX_ENCRYPTION_ERROR_CODES, DocxEncryptionError } from "./errors.js";
3
4
  //#region src/docx/encryption/encryptionInfo.ts
@@ -85,7 +86,7 @@ const requireSpinCount = (attrs, element) => {
85
86
  return spinCount;
86
87
  };
87
88
  const parseAgileXml = (xmlBytes) => {
88
- const xml = new TextDecoder("utf-8").decode(xmlBytes);
89
+ const xml = decodeText(xmlBytes, { encoding: "utf-8" });
89
90
  const keyDataTag = findElement(xml, "keyData");
90
91
  const integrityTag = findElement(xml, "dataIntegrity");
91
92
  const encryptedKeyTag = findElement(xml, "encryptedKey");
@@ -1,4 +1,5 @@
1
1
  import { deterministicHexId } from "../utils/hexId.js";
2
+ import { encodeUtf8 } from "../utils/textEncoding.js";
2
3
  import { resolvePackageRelationshipTarget } from "./packageParts.js";
3
4
  import { spliceXml } from "./selectiveXmlPatch.js";
4
5
  import { loadDocxArchive } from "./server/boundedArchive.js";
@@ -390,7 +391,7 @@ const ensureParaIdsInternal = async (docx, options) => {
390
391
  writeZipPart({
391
392
  zip,
392
393
  path: partPath,
393
- data: content,
394
+ data: encodeUtf8(content),
394
395
  options: {
395
396
  compression: "DEFLATE",
396
397
  compressionOptions: { level: 6 },
@@ -1,3 +1,4 @@
1
+ import { sliceText } from "../utils/sliceText.js";
1
2
  import { emuToPixels } from "../utils/units.js";
2
3
  import { collectGroupTextBoxes, readTransform } from "./drawingGroupChildren.js";
3
4
  import { parseImage, resolveImageData } from "./imageParser.js";
@@ -15,6 +16,11 @@ const CROP_SCALE = 1e5;
15
16
  const MAX_PATH_COMMANDS = 1e4;
16
17
  const MAX_TEXT_CHARACTERS = 2e4;
17
18
  const MAX_SVG_CHARACTERS = 1e6;
19
+ const NON_RENDERING_GROUP_CHILDREN = /* @__PURE__ */ new Set([
20
+ "cNvGrpSpPr",
21
+ "grpSpPr",
22
+ "extLst"
23
+ ]);
18
24
  const numericAttr = (element, name) => {
19
25
  const direct = parseNumericAttribute(element, null, name);
20
26
  if (direct !== void 0) return direct;
@@ -142,7 +148,11 @@ const renderTextBox = (wsp, unitScale) => {
142
148
  const color = colorFrom(findAllDeep(wsp, "w", "color").at(0) ?? null, DEFAULT_TEXT_COLOR);
143
149
  const fontSize = halfPoints * HALF_POINT_TO_EMU / unitScale;
144
150
  const maxCharacters = Math.max(1, Math.floor(width / (fontSize * .38)));
145
- const lines = paragraphs.flatMap((paragraph) => wrapLine(getTextContent(paragraph).slice(0, MAX_TEXT_CHARACTERS), maxCharacters)).map(escapeXmlText);
151
+ const lines = paragraphs.flatMap((paragraph) => wrapLine(sliceText({
152
+ text: getTextContent(paragraph),
153
+ units: "codeUnits",
154
+ end: MAX_TEXT_CHARACTERS
155
+ }), maxCharacters)).map(escapeXmlText);
146
156
  if (lines.length === 0) return "";
147
157
  const lineHeight = fontSize * 1.15;
148
158
  const svgFontSize = 1e3;
@@ -170,13 +180,14 @@ const renderPicture = (picture, index, rels, media) => {
170
180
  const clipId = `group-picture-${index}`;
171
181
  return `<defs><clipPath id="${clipId}"><rect x="${x}" y="${y}" width="${width}" height="${height}"/></clipPath></defs><g clip-path="url(#${clipId})">${image}</g>`;
172
182
  };
173
- const groupViewBox = (group, width, height) => {
174
- const transform = readTransform(findChildByLocalName(findChildByLocalName(group, "grpSpPr"), "xfrm"));
183
+ const effectiveRootTransform = (group, width, height) => {
184
+ const authored = readTransform(findChildByLocalName(findChildByLocalName(group, "grpSpPr"), "xfrm"));
175
185
  return {
176
- x: transform.childX,
177
- y: transform.childY,
178
- width: transform.childWidth > 0 ? transform.childWidth : width,
179
- height: transform.childHeight > 0 ? transform.childHeight : height
186
+ ...authored,
187
+ width: authored.width > 0 ? authored.width : width,
188
+ height: authored.height > 0 ? authored.height : height,
189
+ childWidth: authored.childWidth > 0 ? authored.childWidth : width,
190
+ childHeight: authored.childHeight > 0 ? authored.childHeight : height
180
191
  };
181
192
  };
182
193
  /**
@@ -221,20 +232,22 @@ const createSvg = (group, width, height, rels, media, hostedTextBoxes) => {
221
232
  if (nested === null) return null;
222
233
  piece = nested ? wrapTransformed(transform, `<g transform="${nestedGroupTransform(transform)}">${nested}</g>`) : "";
223
234
  total -= nested.length;
224
- } else continue;
235
+ } else if (NON_RENDERING_GROUP_CHILDREN.has(localName)) continue;
236
+ else return null;
225
237
  content += piece;
226
238
  total += piece.length;
227
239
  if (total > MAX_SVG_CHARACTERS) return null;
228
240
  }
229
241
  return content;
230
242
  };
231
- const viewBox = groupViewBox(group, width, height);
243
+ const root = effectiveRootTransform(group, width, height);
232
244
  const content = render(group, {
233
- x: width / viewBox.width,
234
- y: height / viewBox.height
245
+ x: root.width / root.childWidth,
246
+ y: root.height / root.childHeight
235
247
  }, 0);
236
248
  if (content === null || !content && hostedTextBoxes.size === 0) return null;
237
- return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}" width="${emuToPixels(width)}" height="${emuToPixels(height)}">${content}</svg>`;
249
+ const transformed = content ? wrapTransformed(root, `<g transform="${nestedGroupTransform(root)}">${content}</g>`) : "";
250
+ return `<svg xmlns="http://www.w3.org/2000/svg" data-folio-preview="wpGroup" viewBox="0 0 ${width} ${height}" width="${emuToPixels(width)}" height="${emuToPixels(height)}">${transformed}</svg>`;
238
251
  };
239
252
  const groupElement = (drawing) => {
240
253
  return findChildByLocalName(findAllDeep(drawing, "a", "graphicData").at(0) ?? null, "wgp");
@@ -268,7 +281,9 @@ const parseGroupDrawing = (drawing, previews, rels, media) => {
268
281
  if (!group) return null;
269
282
  const image = parseImage(drawing, void 0, void 0);
270
283
  if (!image || image.size.width <= 0 || image.size.height <= 0) return null;
271
- const hosted = hostsTextBoxes(image) ? collectGroupTextBoxes(group, image.size.width, image.size.height) : [];
284
+ const rootTransform = effectiveRootTransform(group, image.size.width, image.size.height);
285
+ const rootTurnsChildren = rootTransform.rotation !== 0 || rootTransform.flipH || rootTransform.flipV;
286
+ const hosted = hostsTextBoxes(image) && !rootTurnsChildren ? collectGroupTextBoxes(group, image.size.width, image.size.height) : [];
272
287
  const svg = createSvg(group, image.size.width, image.size.height, rels, media, new Set(hosted.map(({ wsp }) => wsp)));
273
288
  if (!svg || svg.length > MAX_SVG_CHARACTERS) return null;
274
289
  const { rId: _rId, src: _src, mimeType: _mimeType, filename: _filename, ...frame } = image;
@@ -0,0 +1,10 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ import { DocumentOp } from "@stll/docx-core/ops";
3
+ //#region src/docx/listModelNormalization.d.ts
4
+ type CanonicalListNormalization = {
5
+ document: document_d_exports.Document;
6
+ inverse: DocumentOp[];
7
+ };
8
+ declare const normalizeListModelRendering: (document: document_d_exports.Document) => CanonicalListNormalization;
9
+ //#endregion
10
+ export { normalizeListModelRendering };
@@ -0,0 +1,166 @@
1
+ import { effectiveParagraphNumberingReference, paragraphNumberingSourcesFromBoundary } from "../prosemirror/numberingAttr.js";
2
+ import { paragraphIndentationFromFormatting } from "../prosemirror/paragraphIndentation.js";
3
+ import { listIndentationProvenancePatch } from "../prosemirror/styles/resolvedStyleAttrs.js";
4
+ import { createStyleResolver } from "../prosemirror/styles/styleResolver.js";
5
+ import { canonicalJson } from "../utils/canonicalJson.js";
6
+ import { foldListNumberFields, planParagraphListNumberFold, unfoldedListNumberContent } from "./foldedListNumberFields.js";
7
+ import { computeListMarker } from "./listMarkerComputation.js";
8
+ import { listRenderingDefinitionsMatch } from "./listRendering.js";
9
+ import { computeListRendering, getCachedNumberingMap } from "./numberingParser.js";
10
+ import { assignParagraphIndentationProjection, cloneParagraphWithPropertySource, paragraphFormattingWithAuthoredIndentation, sameParagraphIndentationProjection } from "./paragraphPropertySource.js";
11
+ import { panic } from "better-result";
12
+ import { DOCUMENT_OP_TYPES, EMPTY_PROPERTY_SETS, OP_STORIES, registerParagraphPropertySnapshot, withBodyContent } from "@stll/docx-core/ops";
13
+ //#region src/docx/listModelNormalization.ts
14
+ /** Canonical edits retain authored fields; list rendering follows the current numbering. */
15
+ const normalizeListModelRendering = (document) => {
16
+ const inverse = [];
17
+ const styles = createStyleResolver(document.package.styles);
18
+ const numbering = document.package.numbering === void 0 ? null : getCachedNumberingMap(document.package.numbering);
19
+ const normalizeBody = (body, story) => {
20
+ const counters = {
21
+ numbering,
22
+ listCounters: /* @__PURE__ */ new Map(),
23
+ abstractCounters: /* @__PURE__ */ new Map(),
24
+ restartedNumIds: /* @__PURE__ */ new Set(),
25
+ previousList: {
26
+ abstractNumId: null,
27
+ fromStyle: false,
28
+ numId: null
29
+ }
30
+ };
31
+ const content = body.content.map((paragraph) => {
32
+ if (paragraph.type !== "paragraph") return paragraph;
33
+ registerParagraphPropertySnapshot({
34
+ paragraph,
35
+ snapshot: paragraphFormattingWithAuthoredIndentation(paragraph)
36
+ });
37
+ const formatting = paragraph.formatting;
38
+ const next = cloneParagraphWithPropertySource(paragraph, {});
39
+ const styleNumbering = formatting?.styleId ? styles.resolveParagraphStyle(formatting.styleId).paragraphFormatting?.numPr : void 0;
40
+ if (styleNumbering !== void 0 && (formatting?.numPr === void 0 || formatting.numPr.kind === "levelOnly")) next.formatting = {
41
+ ...formatting,
42
+ numPrFromStyle: styleNumbering
43
+ };
44
+ if (formatting?.numPrFromStyle !== void 0 && (formatting.styleId !== void 0 && styleNumbering === void 0 || formatting.numPr?.kind === "reference" || formatting.numPr?.kind === "none")) {
45
+ next.formatting = { ...formatting };
46
+ delete next.formatting.numPrFromStyle;
47
+ }
48
+ if (canonicalJson(next.formatting?.numPrFromStyle) !== canonicalJson(formatting?.numPrFromStyle)) inverse.unshift({
49
+ type: DOCUMENT_OP_TYPES.SET_PARAGRAPH_PROPS,
50
+ story,
51
+ blockId: paragraph.paraId ?? panic("List normalization requires paragraph identities."),
52
+ patch: { numPrFromStyle: formatting?.numPrFromStyle ?? null },
53
+ whenEmpty: formatting === void 0 ? EMPTY_PROPERTY_SETS.OMIT : EMPTY_PROPERTY_SETS.KEEP
54
+ });
55
+ const numPr = effectiveParagraphNumberingReference(paragraphNumberingSourcesFromBoundary({
56
+ numPr: next.formatting?.numPr,
57
+ numPrFromStyle: next.formatting?.numPrFromStyle
58
+ }));
59
+ if (paragraph.listRendering === void 0 && formatting?.numPrFromStyle === void 0 && next.formatting?.numPrFromStyle === void 0 && (numPr?.kind !== "reference" || numbering === null)) {
60
+ computeListMarker(paragraph, counters);
61
+ return paragraph;
62
+ }
63
+ const definition = numPr?.kind === "reference" && numbering !== null ? computeListRendering(numPr, numbering) : null;
64
+ const cached = paragraph.listRendering;
65
+ const rendering = definition !== null && cached !== void 0 && listRenderingDefinitionsMatch(cached, definition) ? { ...cached } : definition;
66
+ const authoredFormatting = paragraphFormattingWithAuthoredIndentation(next);
67
+ assignParagraphIndentationProjection({
68
+ paragraph: next,
69
+ authored: authoredFormatting,
70
+ inherited: paragraphIndentationFromFormatting(listIndentationProvenancePatch({
71
+ direct: paragraphIndentationFromFormatting(authoredFormatting),
72
+ styleFormatting: styles.resolveParagraphStyle(next.formatting?.styleId).paragraphFormatting,
73
+ numberingSource: next.formatting?.numPr?.kind === "reference" || next.formatting?.numPrFromStyle === void 0 ? "paragraph" : "style",
74
+ numPr: numPr?.kind === "reference" ? {
75
+ numId: numPr.numId,
76
+ ilvl: numPr.ilvl ?? 0
77
+ } : void 0,
78
+ numbering
79
+ })._resolvedFormatting) ?? {}
80
+ });
81
+ if (canonicalJson(paragraphIndentationFromFormatting(next.formatting)) !== canonicalJson(paragraphIndentationFromFormatting(formatting))) inverse.unshift({
82
+ type: DOCUMENT_OP_TYPES.SET_PARAGRAPH_PROPS,
83
+ story,
84
+ blockId: paragraph.paraId ?? panic("Canonical numbering normalization requires paragraph identities."),
85
+ patch: {
86
+ indentLeft: formatting?.indentLeft ?? null,
87
+ indentRight: formatting?.indentRight ?? null,
88
+ indentFirstLine: formatting?.indentFirstLine ?? null,
89
+ hangingIndent: formatting?.hangingIndent ?? null
90
+ },
91
+ whenEmpty: formatting === void 0 ? EMPTY_PROPERTY_SETS.OMIT : EMPTY_PROPERTY_SETS.KEEP
92
+ });
93
+ if (rendering === null) delete next.listRendering;
94
+ else {
95
+ const fields = foldListNumberFields(paragraph.content.map(unfoldedListNumberContent), () => void 0).fieldCount;
96
+ if (fields === 0) delete rendering.implicitChildLevelAdvances;
97
+ else rendering.implicitChildLevelAdvances = fields;
98
+ const template = rendering.markerTemplate ?? rendering.marker;
99
+ const fold = planParagraphListNumberFold(paragraph.content, !rendering.isBullet && template !== "" && !template.includes(" "));
100
+ if (fold.suffix === void 0) delete rendering.foldedMarkerSuffix;
101
+ else rendering.foldedMarkerSuffix = fold.suffix;
102
+ const child = numbering?.getLevel(rendering.numId, rendering.level + 1)?.pPr;
103
+ if (fold.suffix !== void 0 && child?.hangingIndent === true && child.indentFirstLine !== void 0 && child.indentFirstLine < 0) rendering.markerSecondSlotOffsetTwips = -child.indentFirstLine;
104
+ else delete rendering.markerSecondSlotOffsetTwips;
105
+ rendering.marker = rendering.foldedMarkerSuffix !== void 0 && !template.includes(" ") ? `${template}\t${rendering.foldedMarkerSuffix}` : template;
106
+ next.listRendering = rendering;
107
+ }
108
+ computeListMarker(next, counters);
109
+ registerParagraphPropertySnapshot({
110
+ paragraph: next,
111
+ snapshot: authoredFormatting
112
+ });
113
+ if (sameParagraphIndentationProjection({
114
+ left: next,
115
+ right: paragraph
116
+ }) && canonicalJson(next.formatting) === canonicalJson(formatting) && canonicalJson(next.listRendering) === canonicalJson(cached)) return paragraph;
117
+ return next;
118
+ });
119
+ return content.every((paragraph, index) => paragraph === body.content[index]) ? body : {
120
+ ...body,
121
+ ...withBodyContent(body, content)
122
+ };
123
+ };
124
+ const pkg = document.package;
125
+ const body = normalizeBody(pkg.document, OP_STORIES.MAIN);
126
+ const normalizeParts = (parts, kind) => {
127
+ if (parts === void 0) return parts;
128
+ const normalized = new Map([...parts].map(([id, part]) => [id, normalizeBody(part, {
129
+ kind,
130
+ rId: id
131
+ })]));
132
+ return [...normalized].every(([id, part]) => part === parts.get(id)) ? parts : normalized;
133
+ };
134
+ const normalizeNotes = (notes, kind) => {
135
+ if (notes === void 0) return notes;
136
+ const normalized = notes.map((note) => normalizeBody(note, {
137
+ kind,
138
+ id: note.id
139
+ }));
140
+ return normalized.every((note, index) => note === notes[index]) ? notes : normalized;
141
+ };
142
+ const headers = normalizeParts(pkg.headers, "header");
143
+ const footers = normalizeParts(pkg.footers, "footer");
144
+ const footnotes = normalizeNotes(pkg.footnotes, "footnote");
145
+ const endnotes = normalizeNotes(pkg.endnotes, "endnote");
146
+ if (body === pkg.document && headers === pkg.headers && footers === pkg.footers && footnotes === pkg.footnotes && endnotes === pkg.endnotes) return {
147
+ document,
148
+ inverse
149
+ };
150
+ return {
151
+ inverse,
152
+ document: {
153
+ ...document,
154
+ package: {
155
+ ...pkg,
156
+ document: body,
157
+ ...headers === void 0 ? {} : { headers },
158
+ ...footers === void 0 ? {} : { footers },
159
+ ...footnotes === void 0 ? {} : { footnotes },
160
+ ...endnotes === void 0 ? {} : { endnotes }
161
+ }
162
+ }
163
+ };
164
+ };
165
+ //#endregion
166
+ export { normalizeListModelRendering };
@@ -1,3 +1,4 @@
1
+ import { decodeText, encodeUtf8 } from "../utils/textEncoding.js";
1
2
  import { countCentralDirectoryRecords, createInflationBudget, inflateEntryWithinLimits } from "./archiveInflation.js";
2
3
  import { elementToXml, getLocalName, parseXmlDocument } from "./xmlParser.js";
3
4
  import { TaggedError } from "better-result";
@@ -131,7 +132,7 @@ const readCoreProperties = async (entry) => {
131
132
  message: "Document privacy core properties exceeded the part-size limit",
132
133
  reason: "core-properties-too-large"
133
134
  });
134
- return new TextDecoder("utf-8", { ignoreBOM: true }).decode(result.bytes);
135
+ return decodeText(result.bytes, { ignoreBOM: true });
135
136
  };
136
137
  /** Rewrite selected package metadata fields without changing other package parts. */
137
138
  const rewriteDocxMetadataPrivacy = async (buffer, { transforms }) => {
@@ -156,7 +157,7 @@ const rewriteDocxMetadataPrivacy = async (buffer, { transforms }) => {
156
157
  writeZipPart({
157
158
  zip,
158
159
  path: coreProperties.name,
159
- data: rewritten.xml
160
+ data: encodeUtf8(rewritten.xml)
160
161
  });
161
162
  return {
162
163
  buffer: await zip.generateAsync({
@@ -1,3 +1,4 @@
1
+ import { decodeText } from "../utils/textEncoding.js";
1
2
  //#region src/docx/metafileRaster.ts
2
3
  function indexOfBytes(haystack, needle, from = 0) {
3
4
  outer: for (let i = from; i + needle.length <= haystack.length; i++) {
@@ -25,7 +26,7 @@ function extractPng(bytes) {
25
26
  const b2 = bytes[off + 2];
26
27
  const b3 = bytes[off + 3];
27
28
  const len = b0 << 24 | b1 << 16 | b2 << 8 | b3;
28
- const type = String.fromCharCode(bytes[off + 4], bytes[off + 5], bytes[off + 6], bytes[off + 7]);
29
+ const type = decodeText(bytes.subarray(off + 4, off + 8), { encoding: "binary" });
29
30
  const next = off + 12 + (len >>> 0);
30
31
  if (type === "IEND") return bytes.slice(start, off + 12);
31
32
  if (next <= off || next > bytes.length) break;
@@ -1,3 +1,4 @@
1
+ import { decodeText, encodeUtf8 } from "../utils/textEncoding.js";
1
2
  import { Result } from "better-result";
2
3
  import { writeZipPart } from "@stll/docx-core/zip";
3
4
  //#region src/docx/packageParts.ts
@@ -124,7 +125,7 @@ const readRelationshipParts = async (zip) => {
124
125
  if (file.dir || !normalizePartPath(path).endsWith(".rels")) continue;
125
126
  parts.push({
126
127
  path,
127
- xml: await file.async("text")
128
+ xml: await file.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }))
128
129
  });
129
130
  }
130
131
  return parts;
@@ -133,7 +134,7 @@ const writePart = ({ zip, path, xml, compressionLevel }) => {
133
134
  writeZipPart({
134
135
  zip,
135
136
  path,
136
- data: xml,
137
+ data: encodeUtf8(xml),
137
138
  options: {
138
139
  compression: "DEFLATE",
139
140
  compressionOptions: { level: compressionLevel }
@@ -186,7 +187,7 @@ const reconcilePackageReferences = async (zip, compressionLevel) => {
186
187
  if (!file) continue;
187
188
  const idPattern = [...ids].map((id) => escapeRegExp(id)).join("|");
188
189
  const referencePattern = new RegExp(`\\s+r:[A-Za-z]+\\s*=\\s*(?<quote>["'])(?<id>${idPattern})\\k<quote>`, "gu");
189
- const xml = await file.async("text");
190
+ const xml = await file.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }));
190
191
  const removedIds = /* @__PURE__ */ new Set();
191
192
  const scrubbed = xml.replaceAll(referencePattern, (...args) => {
192
193
  const groups = args.at(-1);
@@ -206,7 +207,7 @@ const reconcilePackageReferences = async (zip, compressionLevel) => {
206
207
  const contentTypes = zip.file(CONTENT_TYPES_PATTERN)[0];
207
208
  if (!contentTypes) return repair;
208
209
  let droppedOverride = false;
209
- const prunedContentTypes = (await contentTypes.async("text")).replaceAll(CONTENT_TYPE_ELEMENT, (element) => {
210
+ const prunedContentTypes = (await contentTypes.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }))).replaceAll(CONTENT_TYPE_ELEMENT, (element) => {
210
211
  const partName = PART_NAME_ATTRIBUTE.exec(element)?.groups?.["value"];
211
212
  if (partName === void 0 || isPresent(present, partName)) return element;
212
213
  repair.danglingOverrides.push(normalizePartPath(decodePartName(partName) ?? partName));
@@ -256,7 +257,7 @@ const checkPackageIntegrity = async (zip) => {
256
257
  }
257
258
  for (const [path, file] of Object.entries(zip.files)) {
258
259
  if (file.dir || !normalizePartPath(path).endsWith(".xml")) continue;
259
- const xml = await file.async("text");
260
+ const xml = await file.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }));
260
261
  const ids = idsByOwningPart.get(path);
261
262
  for (const match of xml.matchAll(REFERENCE_ATTRIBUTE)) {
262
263
  const id = match.groups?.["id"];
@@ -52,16 +52,18 @@ function extractMathText(el) {
52
52
  * Capture the authored paragraph properties separately from structural and
53
53
  * revision children, which have their own model fields and lifecycles.
54
54
  */
55
- const captureParagraphPropertySource = (pPr) => captureVerbatimXml(cloneElement(pPr, { elements: (pPr.elements ?? []).flatMap((child) => {
55
+ const captureParagraphPropertySource = (pPr, paragraph) => captureVerbatimXml(cloneElement(pPr, { elements: (pPr.elements ?? []).flatMap((child) => {
56
56
  if (child.type !== "element") return [child];
57
57
  const localName = getLocalName(child.name);
58
58
  if (WORDPROCESSINGML_NAMESPACE_URIS.has(child.namespaceUri ?? "") && (localName === "sectPr" || localName === "pPrChange")) return [];
59
59
  if (localName !== "rPr" || !WORDPROCESSINGML_NAMESPACE_URIS.has(child.namespaceUri ?? "")) return [child];
60
- return [cloneElement(child, { elements: (child.elements ?? []).filter((runProperty) => {
60
+ const elements = (child.elements ?? []).filter((runProperty) => {
61
61
  if (runProperty.type !== "element") return true;
62
62
  const runPropertyName = getLocalName(runProperty.name);
63
63
  return !WORDPROCESSINGML_NAMESPACE_URIS.has(runProperty.namespaceUri ?? "") || !PARAGRAPH_MARK_CHANGE_KINDS.some((kind) => kind === runPropertyName);
64
- }) })];
64
+ });
65
+ if (paragraph.formatting?.runProperties === void 0 && elements.every((element) => element.type === "text" && (element.text === void 0 || typeof element.text === "string" && element.text.trim() === ""))) return [];
66
+ return [cloneElement(child, { elements })];
65
67
  }) }));
66
68
  const RENDERED_BREAK_INLINE_WRAPPERS = /* @__PURE__ */ new Set([
67
69
  "hyperlink",
@@ -1328,7 +1330,7 @@ function parseParagraph(node, styles, theme, numbering, rels = null, media = nul
1328
1330
  }
1329
1331
  }
1330
1332
  }
1331
- if (pPr) assignParagraphPropertySource(paragraph, captureParagraphPropertySource(pPr));
1333
+ if (pPr) assignParagraphPropertySource(paragraph, captureParagraphPropertySource(pPr, paragraph));
1332
1334
  return paragraph;
1333
1335
  }
1334
1336
  /**
@@ -1,5 +1,6 @@
1
1
  import { toArrayBuffer } from "../utils/docxInput.js";
2
2
  import { loadFontsWithMapping } from "../utils/fontLoader.js";
3
+ import { decodeText, encodeUtf8 } from "../utils/textEncoding.js";
3
4
  import { MAX_PACKAGE_TIFF_PIXELS, convertTiffToPngDataUrl, isTiffMimeType } from "../utils/tiffConverter.js";
4
5
  import { ALT_CHUNK_PLAIN_TEXT_LIMIT_BYTES, getAltChunkRelationshipId, isAltChunkMarkup } from "./altChunk.js";
5
6
  import { normalizeBookmarkIdsInXmlParts } from "./bookmarkIdNormalization.js";
@@ -122,7 +123,7 @@ const extractAltChunkPlainText = async (xml, context) => {
122
123
  if (bytes.byteLength > context.budget.remainingBytes) return;
123
124
  context.budget.remainingBytes -= bytes.byteLength;
124
125
  try {
125
- return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
126
+ return decodeText(bytes, { fatal: true });
126
127
  } catch {
127
128
  return;
128
129
  }
@@ -560,7 +561,7 @@ async function buildMediaMap(raw, rels) {
560
561
  }
561
562
  const emfSvg = isReferenced && mimeType === EMF_MIME_TYPE && remainingEmfPreviewBytes > 0 ? renderEmfSvg(data) : null;
562
563
  if (emfSvg) {
563
- const svgBytes = new TextEncoder().encode(emfSvg);
564
+ const svgBytes = encodeUtf8(emfSvg);
564
565
  if (svgBytes.byteLength <= remainingEmfPreviewBytes) {
565
566
  remainingEmfPreviewBytes -= svgBytes.byteLength;
566
567
  const mediaFile = {
@@ -589,8 +590,8 @@ async function buildMediaMap(raw, rels) {
589
590
  if (normalizedPath !== path) media.set(normalizedPath, mediaFile);
590
591
  }
591
592
  for (const [path, xml] of raw.allXml.entries()) {
592
- if (!/^word\/diagrams\/[^/]+\.xml$/iu.test(path) || new TextEncoder().encode(xml).byteLength > FOLIO_XML_RESOURCE_LIMITS.maxBytes) continue;
593
- const data = new TextEncoder().encode(xml).buffer;
593
+ if (!/^word\/diagrams\/[^/]+\.xml$/iu.test(path) || encodeUtf8(xml).byteLength > FOLIO_XML_RESOURCE_LIMITS.maxBytes) continue;
594
+ const data = encodeUtf8(xml).buffer;
594
595
  const file = {
595
596
  path,
596
597
  filename: path.split("/").pop() ?? path,
@@ -1,3 +1,4 @@
1
+ import { sliceText } from "../utils/sliceText.js";
1
2
  import { captureVerbatimXml } from "./verbatimCapture.js";
2
3
  import { findChildrenByLocalName, getChildElements, getLocalName, getTextContent } from "./xmlParser.js";
3
4
  //#region src/docx/preservedRunContent.ts
@@ -31,7 +32,11 @@ const preservedRunChildText = (element) => {
31
32
  if (sources === void 0) return "";
32
33
  const parts = [];
33
34
  for (const localName of sources) for (const source of findChildrenByLocalName(element, localName)) collectTextContent(source, parts);
34
- return parts.join("").slice(0, MAX_PRESERVED_TEXT_LENGTH);
35
+ return sliceText({
36
+ text: parts.join(""),
37
+ units: "codeUnits",
38
+ end: MAX_PRESERVED_TEXT_LENGTH
39
+ });
35
40
  };
36
41
  /**
37
42
  * Capture one unmodelled run child.
@@ -90,7 +95,11 @@ const preserveInlineChild = (element) => {
90
95
  return {
91
96
  type: "preservedInline",
92
97
  xml: captureVerbatimXml(element),
93
- text: parts.join("").slice(0, MAX_PRESERVED_TEXT_LENGTH)
98
+ text: sliceText({
99
+ text: parts.join(""),
100
+ units: "codeUnits",
101
+ end: MAX_PRESERVED_TEXT_LENGTH
102
+ })
94
103
  };
95
104
  };
96
105
  //#endregion
@@ -1,3 +1,4 @@
1
+ import { decodeText, encodeUtf8 } from "../utils/textEncoding.js";
1
2
  import { isUnsafePackagePath } from "./packageParts.js";
2
3
  import { parseRelationships, resolveRelativePath } from "./relsParser.js";
3
4
  import { captureVerbatimXml } from "./verbatimCapture.js";
@@ -18,7 +19,7 @@ const removeResolvedHeaderFooterParts = async ({ document, zip, removedReference
18
19
  if (removedReferences.length === 0) return;
19
20
  const relsFile = zip.file(DOCUMENT_RELS_PATH);
20
21
  if (!relsFile) return panic("A resolved header/footer part has no document relationships");
21
- const relsXml = await relsFile.async("text");
22
+ const relsXml = await relsFile.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }));
22
23
  const relationships = parseRelationships(relsXml);
23
24
  const removedIds = /* @__PURE__ */ new Set();
24
25
  const candidates = /* @__PURE__ */ new Set();
@@ -39,7 +40,7 @@ const removeResolvedHeaderFooterParts = async ({ document, zip, removedReference
39
40
  }
40
41
  for (const file of Object.values(zip.files)) {
41
42
  if (file.dir || !file.name.endsWith(".rels") || file.name === DOCUMENT_RELS_PATH) continue;
42
- const otherRelationships = parseRelationships(await file.async("text"));
43
+ const otherRelationships = parseRelationships(await file.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true })));
43
44
  for (const relationship of otherRelationships.values()) if (relationship.targetMode !== "External") candidates.delete(resolveRelativePath(file.name, relationship.target));
44
45
  }
45
46
  const retiredMedia = /* @__PURE__ */ new Set();
@@ -48,7 +49,7 @@ const removeResolvedHeaderFooterParts = async ({ document, zip, removedReference
48
49
  const relsPath = `${path.slice(0, slash + 1)}_rels/${path.slice(slash + 1)}.rels`;
49
50
  const partRels = zip.file(relsPath);
50
51
  if (!partRels) continue;
51
- for (const relationship of parseRelationships(await partRels.async("text")).values()) {
52
+ for (const relationship of parseRelationships(await partRels.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }))).values()) {
52
53
  if (!officeRelationshipHasType(relationship.type, "image") || relationship.targetMode === "External") continue;
53
54
  const mediaPath = resolveRelativePath(relsPath, relationship.target);
54
55
  if (!isUnsafePackagePath(mediaPath) && mediaPath.startsWith("word/media/")) retiredMedia.add(mediaPath);
@@ -58,7 +59,7 @@ const removeResolvedHeaderFooterParts = async ({ document, zip, removedReference
58
59
  writeZipPart({
59
60
  zip,
60
61
  path: DOCUMENT_RELS_PATH,
61
- data: withoutChildren(relsXml, (child) => getNamespaceUri(child) === OOXML_NS.pr && getLocalName(child.name) === "Relationship" && removedIds.has(getAttribute(child, null, "Id") ?? "")),
62
+ data: encodeUtf8(withoutChildren(relsXml, (child) => getNamespaceUri(child) === OOXML_NS.pr && getLocalName(child.name) === "Relationship" && removedIds.has(getAttribute(child, null, "Id") ?? ""))),
62
63
  options: {
63
64
  compression: "DEFLATE",
64
65
  compressionOptions
@@ -75,7 +76,7 @@ const removeResolvedHeaderFooterParts = async ({ document, zip, removedReference
75
76
  }
76
77
  for (const file of Object.values(zip.files)) {
77
78
  if (file.dir || !file.name.endsWith(".rels")) continue;
78
- for (const relationship of parseRelationships(await file.async("text")).values()) if (relationship.targetMode !== "External") retiredMedia.delete(resolveRelativePath(file.name, relationship.target));
79
+ for (const relationship of parseRelationships(await file.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }))).values()) if (relationship.targetMode !== "External") retiredMedia.delete(resolveRelativePath(file.name, relationship.target));
79
80
  }
80
81
  for (const path of retiredMedia) {
81
82
  zip.remove(path);
@@ -83,11 +84,11 @@ const removeResolvedHeaderFooterParts = async ({ document, zip, removedReference
83
84
  }
84
85
  const contentTypes = zip.file("[Content_Types].xml");
85
86
  if (!contentTypes) return panic("The package has no content types");
86
- const contentTypesXml = await contentTypes.async("text");
87
+ const contentTypesXml = await contentTypes.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }));
87
88
  writeZipPart({
88
89
  zip,
89
90
  path: "[Content_Types].xml",
90
- data: withoutChildren(contentTypesXml, (child) => getNamespaceUri(child) === OOXML_NS.ct && getLocalName(child.name) === "Override" && candidates.has((getAttribute(child, null, "PartName") ?? "").replace(/^\//u, ""))),
91
+ data: encodeUtf8(withoutChildren(contentTypesXml, (child) => getNamespaceUri(child) === OOXML_NS.ct && getLocalName(child.name) === "Override" && candidates.has((getAttribute(child, null, "PartName") ?? "").replace(/^\//u, "")))),
91
92
  options: {
92
93
  compression: "DEFLATE",
93
94
  compressionOptions