@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,4 +1,5 @@
1
1
  import { parseHorizontalScalePercent } from "../utils/horizontalScale.js";
2
+ import { sliceText } from "../utils/sliceText.js";
2
3
  import { NO_MODELLED_ATTRIBUTES, attributeRemainder } from "./attributeRemainder.js";
3
4
  import { CAPTURE, dispatchChildrenWithContext, keptUnless, ownedElsewhere, sequencePositions } from "./containerChildren.js";
4
5
  import { parseDiagramPreview } from "./diagramPreview.js";
@@ -10,6 +11,7 @@ import { EmphasisMarkSchema, FontHintSchema, FontThemeSchema, HighlightColorSche
10
11
  import { preserveRunChild } from "./preservedRunContent.js";
11
12
  import { standalonePreviewLedger } from "./previewBudget.js";
12
13
  import { parseBoundaryJoins } from "./reviewResolutionProvenance.js";
14
+ import { isEmptyRunPropertiesAttribute, omitsPreviousRunProperties, retainsEmptyRunProperties } from "./runPropertyPresence.js";
13
15
  import { parseShading } from "./shadingParser.js";
14
16
  import { captureShapeAlternateContent } from "./shapeAlternateContent.js";
15
17
  import { parseShapeFromDrawing, shouldPreserveRawShapeDrawing } from "./shapeParser.js";
@@ -48,7 +50,11 @@ import { DRAWING_RAW_XML_MODES } from "@stll/docx-core/model";
48
50
  * attacker-sized string per run.
49
51
  */
50
52
  const MAX_LANGUAGE_TAG_LENGTH = 35;
51
- const truncateLanguageTag = (value) => value === void 0 ? void 0 : value.slice(0, MAX_LANGUAGE_TAG_LENGTH);
53
+ const truncateLanguageTag = (value) => value === void 0 ? void 0 : sliceText({
54
+ text: value,
55
+ units: "codeUnits",
56
+ end: MAX_LANGUAGE_TAG_LENGTH
57
+ });
52
58
  function parseColorValue({ rgb, themeColor, themeTint, themeShade, element }) {
53
59
  const color = {};
54
60
  if (rgb && rgb !== "auto") color.rgb = rgb;
@@ -429,7 +435,7 @@ function parseRunProperties(rPr, theme, owner) {
429
435
  });
430
436
  if (preserved) formatting.preserved = preserved;
431
437
  const children = getChildElements(rPr);
432
- if (owner === RUN_PROPERTY_OWNERS.paragraphMark && Object.keys(formatting).length === 0 && children.length > 0 && children.every((child) => WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(child) ?? "") && Object.hasOwn(OWNED_BY_A_SIBLING_RECORD.paragraphMark, getLocalName(child.name)))) return;
438
+ if (owner !== RUN_PROPERTY_OWNERS.standalone && Object.keys(formatting).length === 0 && children.length > 0 && children.every((child) => WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(child) ?? "") && Object.hasOwn(OWNED_BY_A_SIBLING_RECORD[owner], getLocalName(child.name)))) return;
433
439
  return formatting;
434
440
  }
435
441
  function parseRunPropertyChanges(rPr, theme, currentFormatting) {
@@ -443,7 +449,7 @@ function parseRunPropertyChanges(rPr, theme, currentFormatting) {
443
449
  const boundaryJoins = parseBoundaryJoins(changeElement);
444
450
  if (boundaryJoins !== void 0) change.boundaryJoins = boundaryJoins;
445
451
  const previousFormatting = parseRunProperties(previousRPr, theme, RUN_PROPERTY_OWNERS.standalone);
446
- if (previousFormatting) change.previousFormatting = previousFormatting;
452
+ if (previousFormatting && (!omitsPreviousRunProperties(changeElement) || Object.keys(previousFormatting).length > 0)) change.previousFormatting = previousFormatting;
447
453
  if (currentFormatting) change.currentFormatting = currentFormatting;
448
454
  return change;
449
455
  });
@@ -789,7 +795,7 @@ function parseRun(node, _styles, theme, rels = null, media = null, rootXmlns = {
789
795
  };
790
796
  const rPr = findChild(node, "w", "rPr");
791
797
  if (rPr) {
792
- const formattingResult = parseRunProperties(rPr, theme, RUN_PROPERTY_OWNERS.run);
798
+ const formattingResult = parseRunProperties(rPr, theme, RUN_PROPERTY_OWNERS.run) ?? (retainsEmptyRunProperties(node) ? {} : void 0);
793
799
  if (formattingResult) run.formatting = formattingResult;
794
800
  const propertyChangesResult = parseRunPropertyChanges(rPr, theme, run.formatting);
795
801
  if (propertyChangesResult) run.propertyChanges = propertyChangesResult;
@@ -798,8 +804,8 @@ function parseRun(node, _styles, theme, rels = null, media = null, rootXmlns = {
798
804
  const remainder = attributeRemainder({
799
805
  element: node,
800
806
  modelled: NO_MODELLED_ATTRIBUTES
801
- });
802
- if (remainder) run.preservedAttributes = remainder;
807
+ })?.filter((attribute) => !isEmptyRunPropertiesAttribute(attribute));
808
+ if (remainder?.length) run.preservedAttributes = remainder;
803
809
  return run;
804
810
  }
805
811
  /**
@@ -0,0 +1,11 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ import { XmlElement } from "./xmlParser.js";
3
+ //#region src/docx/runPropertyPresence.d.ts
4
+ declare const EMPTY_RUN_PROPERTIES_ATTRIBUTE = "emptyRunProperties";
5
+ declare const retainsEmptyRunProperties: (element: XmlElement) => boolean;
6
+ declare const omitsPreviousRunProperties: (element: XmlElement) => boolean;
7
+ declare const serializePreviousRunProperties: (properties: document_d_exports.TextFormatting | undefined) => string;
8
+ declare const serializeEmptyRunProperties: (run: document_d_exports.Run) => string[];
9
+ declare const isEmptyRunPropertiesAttribute: ({ namespace, name }: Pick<document_d_exports.PreservedAttribute, "namespace" | "name">) => boolean;
10
+ //#endregion
11
+ export { EMPTY_RUN_PROPERTIES_ATTRIBUTE, isEmptyRunPropertiesAttribute, omitsPreviousRunProperties, retainsEmptyRunProperties, serializeEmptyRunProperties, serializePreviousRunProperties };
@@ -0,0 +1,14 @@
1
+ import { OOXML_NAMESPACES } from "./serializer/partNamespaces.js";
2
+ import { getAttributeByNamespaceUri } from "./xmlParser.js";
3
+ import { EMPTY_PROPERTY_SETS } from "@stll/docx-core/ops";
4
+ //#region src/docx/runPropertyPresence.ts
5
+ const EMPTY_RUN_PROPERTIES_ATTRIBUTE = "emptyRunProperties";
6
+ const PREVIOUS_PROPERTIES_ATTRIBUTE = "previousProperties";
7
+ const HISTORY_NAMESPACES = /* @__PURE__ */ new Set([OOXML_NAMESPACES.folio.uri]);
8
+ const retainsEmptyRunProperties = (element) => getAttributeByNamespaceUri(element, HISTORY_NAMESPACES, EMPTY_RUN_PROPERTIES_ATTRIBUTE) === EMPTY_PROPERTY_SETS.KEEP;
9
+ const omitsPreviousRunProperties = (element) => getAttributeByNamespaceUri(element, HISTORY_NAMESPACES, PREVIOUS_PROPERTIES_ATTRIBUTE) === EMPTY_PROPERTY_SETS.OMIT;
10
+ const serializePreviousRunProperties = (properties) => properties === void 0 ? ` folio:${PREVIOUS_PROPERTIES_ATTRIBUTE}="${EMPTY_PROPERTY_SETS.OMIT}"` : "";
11
+ const serializeEmptyRunProperties = (run) => run.propertyChanges?.length && run.formatting !== void 0 && Object.keys(run.formatting).length === 0 ? [`folio:${EMPTY_RUN_PROPERTIES_ATTRIBUTE}="${EMPTY_PROPERTY_SETS.KEEP}"`] : [];
12
+ const isEmptyRunPropertiesAttribute = ({ namespace, name }) => namespace === OOXML_NAMESPACES.folio.uri && name === "emptyRunProperties";
13
+ //#endregion
14
+ export { EMPTY_RUN_PROPERTIES_ATTRIBUTE, isEmptyRunPropertiesAttribute, omitsPreviousRunProperties, retainsEmptyRunProperties, serializeEmptyRunProperties, serializePreviousRunProperties };
@@ -1,3 +1,4 @@
1
+ import { decodeText, encodeUtf8 } from "../utils/textEncoding.js";
1
2
  import { parseCommentsExtended } from "./commentParser.js";
2
3
  import { withoutOrphanCommentRanges } from "./commentRangeIntegrity.js";
3
4
  import { hasUnsynthesizedReplyRanges } from "./commentReplyMarkers.js";
@@ -46,7 +47,7 @@ async function patchNoteParts(zip, doc, candidateIds, updates) {
46
47
  const patchPart = async (conventionalLowerPath, serialize) => {
47
48
  const file = findPart(conventionalLowerPath);
48
49
  if (!file) return true;
49
- const originalXml = await file.async("text");
50
+ const originalXml = await file.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }));
50
51
  const originalIds = collectParaIds(originalXml);
51
52
  const partIds = /* @__PURE__ */ new Set();
52
53
  for (const id of remainingIds) if (originalIds.has(id)) partIds.add(id);
@@ -93,7 +94,7 @@ async function patchCommentsExtended(zip, plan, updates) {
93
94
  const desiredXml = serializeCommentsExtended(plan);
94
95
  const existing = findZipEntryCaseInsensitive(zip, COMMENTS_EXTENDED_PART_LOWER);
95
96
  if (!desiredXml) return existing === null;
96
- const baselineInfo = existing ? parseCommentsExtended(await existing.async("text")) : /* @__PURE__ */ new Map();
97
+ const baselineInfo = existing ? parseCommentsExtended(await existing.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }))) : /* @__PURE__ */ new Map();
97
98
  if (commentsExtendedInfoEqual(parseCommentsExtended(desiredXml), baselineInfo)) return true;
98
99
  updates.set(existing?.name ?? "word/commentsExtended.xml", desiredXml);
99
100
  return ensureCommentsExtendedPackaging(zip, updates);
@@ -106,12 +107,12 @@ async function patchCommentsExtended(zip, plan, updates) {
106
107
  */
107
108
  async function ensureCommentsExtendedPackaging(zip, updates) {
108
109
  const ctPath = "[Content_Types].xml";
109
- const ctXml = updates.get(ctPath) ?? await zip.file(ctPath)?.async("text");
110
+ const ctXml = updates.get(ctPath) ?? await zip.file(ctPath)?.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }));
110
111
  if (ctXml === void 0) return false;
111
112
  const nextCt = addCommentsExtendedOverride(ctXml);
112
113
  if (nextCt !== ctXml) updates.set(ctPath, nextCt);
113
114
  const relsPath = "word/_rels/document.xml.rels";
114
- const relsXml = updates.get(relsPath) ?? await zip.file(relsPath)?.async("text");
115
+ const relsXml = updates.get(relsPath) ?? await zip.file(relsPath)?.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }));
115
116
  if (relsXml === void 0) return false;
116
117
  const nextRels = addCommentsExtendedRelationship(relsXml);
117
118
  if (nextRels !== relsXml) updates.set(relsPath, nextRels);
@@ -146,7 +147,7 @@ async function patchNumberingPart(zip, doc, updates) {
146
147
  }
147
148
  }
148
149
  if (!file) return false;
149
- const originalXml = await file.async("text");
150
+ const originalXml = await file.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }));
150
151
  const baselineXml = serializeNumberingXml(parseNumbering(originalXml).definitions);
151
152
  const currentXml = serializeNumberingXml(numbering);
152
153
  if (currentXml === baselineXml) return true;
@@ -177,7 +178,7 @@ const queueSettingsUpdates = async (zip, updates) => {
177
178
  const settingsFile = zip.file("word/settings.xml");
178
179
  if (!settingsFile) return;
179
180
  const relsFile = zip.file("word/_rels/settings.xml.rels");
180
- const filtered = withoutAttachedTemplate(await settingsFile.async("text"), await relsFile?.async("text"));
181
+ const filtered = withoutAttachedTemplate(await settingsFile.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true })), await relsFile?.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true })));
181
182
  if (filtered.settingsXml !== void 0) updates.set("word/settings.xml", filtered.settingsXml);
182
183
  if (filtered.relsXml !== void 0) updates.set("word/_rels/settings.xml.rels", filtered.relsXml);
183
184
  };
@@ -212,12 +213,12 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
212
213
  for (const [path, file] of Object.entries(zip.files)) {
213
214
  const lowerPath = path.toLowerCase();
214
215
  if (file.dir || !lowerPath.startsWith("word/") || !lowerPath.endsWith(".xml")) continue;
215
- sourceParts.set(path, await file.async("text"));
216
+ sourceParts.set(path, await file.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true })));
216
217
  }
217
218
  for (const [path, xml] of normalizeImportedNumericIds(sourceParts)) if (xml !== sourceParts.get(path)) writeZipPart({
218
219
  zip,
219
220
  path,
220
- data: xml
221
+ data: encodeUtf8(xml)
221
222
  });
222
223
  }
223
224
  if (options.bodyAuthority === "canonical") await publishCanonicalImageResources({
@@ -229,7 +230,7 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
229
230
  if (changedParaIds.size > 0 || structuralChange) {
230
231
  const docXmlFile = zip.file("word/document.xml");
231
232
  if (!docXmlFile) return null;
232
- const originalDocXml = await docXmlFile.async("text");
233
+ const originalDocXml = await docXmlFile.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }));
233
234
  const serializedDocXml = serializeDocument(doc, readRootNamespaceBindings(originalDocXml), {
234
235
  xml: originalDocXml,
235
236
  bodyAuthority: options.bodyAuthority,
@@ -239,7 +240,7 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
239
240
  const originalBodyParaIds = structuralChange ? collectParaIds(originalDocXml) : void 0;
240
241
  if (structuralChange && doc.package.numbering) {
241
242
  const numberingFile = findZipEntryCaseInsensitive(zip, "word/numbering.xml");
242
- const added = collectAddedNumberingDefs(numberingFile ? serializeNumberingXml(parseNumbering(await numberingFile.async("text")).definitions) : "", serializeNumberingXml(doc.package.numbering));
243
+ const added = collectAddedNumberingDefs(numberingFile ? serializeNumberingXml(parseNumbering(await numberingFile.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }))).definitions) : "", serializeNumberingXml(doc.package.numbering));
243
244
  if (added.abstractNums.size > 0 || added.nums.size > 0) return null;
244
245
  }
245
246
  const bodyChangedIds = /* @__PURE__ */ new Set();
@@ -262,7 +263,7 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
262
263
  }
263
264
  const sourceCommentsFile = findZipEntryCaseInsensitive(zip, "word/comments.xml");
264
265
  const commentsPartPath = sourceCommentsFile?.name ?? "word/comments.xml";
265
- const sourceCommentsXml = sourceCommentsFile ? await sourceCommentsFile.async("text") : void 0;
266
+ const sourceCommentsXml = sourceCommentsFile ? await sourceCommentsFile.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true })) : void 0;
266
267
  const commentsUnchanged = sourceCommentsXml !== void 0 && canReplayCommentsPart(doc, sourceCommentsXml);
267
268
  if (!commentsUnchanged && (hasComments || ownsCommentsPart || sourceCommentsFile)) {
268
269
  const commentsXml = serializeComments(commentPlan, sourceCommentsXml === void 0 ? void 0 : readRootNamespaceBindings(sourceCommentsXml));
@@ -271,13 +272,13 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
271
272
  if (hasComments || ownsCommentsPart) {
272
273
  const ctFile = zip.file("[Content_Types].xml");
273
274
  if (ctFile) {
274
- const ctXml = await ctFile.async("text");
275
+ const ctXml = await ctFile.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }));
275
276
  if (!ctXml.toLowerCase().includes("/word/comments.xml")) updates.set("[Content_Types].xml", ctXml.replace("</Types>", `<Override PartName="/${commentsPartPath}" ContentType="${COMMENTS_CONTENT_TYPE}"/></Types>`));
276
277
  }
277
278
  const relsPath = "word/_rels/document.xml.rels";
278
279
  const relsFile = zip.file(relsPath);
279
280
  if (relsFile) {
280
- const relsXml = await relsFile.async("text");
281
+ const relsXml = await relsFile.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }));
281
282
  if (!relsXml.toLowerCase().includes("comments.xml")) {
282
283
  const maxId = findMaxRId(relsXml);
283
284
  updates.set(relsPath, relsXml.replace("</Relationships>", `<Relationship Id="rId${maxId + 1}" Type="${RELATIONSHIP_TYPES.comments}" Target="${commentsPartPath.slice(5)}"/></Relationships>`));
@@ -307,7 +308,7 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
307
308
  await queueSettingsUpdates(zip, updates);
308
309
  const corePropsFile = zip.file("docProps/core.xml");
309
310
  if (corePropsFile) {
310
- const corePropsXml = await corePropsFile.async("text");
311
+ const corePropsXml = await corePropsFile.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }));
311
312
  updates.set("docProps/core.xml", updateCoreProperties(corePropsXml, { updateModifiedDate: true }));
312
313
  }
313
314
  if ([...updates.keys()].some((path) => {
@@ -9,6 +9,7 @@ import { canReplayEditableImageRawXml } from "../imageRawXml.js";
9
9
  import { serializeNonVisualDrawingNames } from "../nonVisualDrawingProps.js";
10
10
  import { serializeBoundaryJoins } from "../reviewResolutionProvenance.js";
11
11
  import { runHoldsPayload } from "../runPayload.js";
12
+ import { serializeEmptyRunProperties, serializePreviousRunProperties } from "../runPropertyPresence.js";
12
13
  import { replayableShapeAlternateContent } from "../shapeAlternateContent.js";
13
14
  import { requiredWrapPolygon, serializeWrapPolygon } from "../wrapPolygon.js";
14
15
  import { serializeParagraph } from "./paragraphSerializer.js";
@@ -52,7 +53,7 @@ function getUniqueId(id) {
52
53
  }
53
54
  function serializeRunPropertyChange(change) {
54
55
  const previousRPrXml = serializeTextFormatting(change.previousFormatting) || "<w:rPr/>";
55
- return `<w:rPrChange ${serializeTrackedChangeAttributes(change.info)}${serializeBoundaryJoins(change.boundaryJoins)}>${previousRPrXml}</w:rPrChange>`;
56
+ return `<w:rPrChange ${serializeTrackedChangeAttributes(change.info)}${serializeBoundaryJoins(change.boundaryJoins)}${serializePreviousRunProperties(change.previousFormatting)}>${previousRPrXml}</w:rPrChange>`;
56
57
  }
57
58
  function serializeRunProperties(formatting, propertyChanges) {
58
59
  const propertyChange = getSingularRunPropertyChange(propertyChanges);
@@ -567,7 +568,7 @@ function serializeRun(run) {
567
568
  const contentXml = serializeRunContent(content);
568
569
  if (contentXml) parts.push(contentXml);
569
570
  }
570
- const attrs = serializePreservedAttributes([], run.preservedAttributes);
571
+ const attrs = serializePreservedAttributes(serializeEmptyRunProperties(run), run.preservedAttributes);
571
572
  return `<w:r${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""}>${parts.join("")}</w:r>`;
572
573
  }
573
574
  /**
@@ -5,6 +5,7 @@ import { withPreservedChildren } from "../containerChildren.js";
5
5
  import { TRANSITIONAL_NAME_BY_STRICT_NAME } from "../strictNames.gen.js";
6
6
  import { TABLE_LOOK_FLAGS } from "../tableLook.js";
7
7
  import { parseTableCellProperties, parseTableGrid, parseTableProperties, parseTablePropertyExceptions, parseTableRowProperties } from "../tableParser.js";
8
+ import { serializeEmptyRowProperties } from "../tableRowPropertyPresence.js";
8
9
  import { OOXML_NAMESPACE_SCOPE, parseXml } from "../xmlParser.js";
9
10
  import { serializeBlockCustomXml } from "./blockCustomXmlSerializer.js";
10
11
  import { serializeBlockSdt } from "./blockSdtSerializer.js";
@@ -555,7 +556,7 @@ function serializeTableRow(row, serializeParagraph) {
555
556
  carriers: carriersFor(cell),
556
557
  xml: serializeTableCell(cell, serializeParagraph)
557
558
  })), positionedChildren(row.preserved, row.bookmarks), preservedChild), 0));
558
- const attrs = serializePreservedAttributes([], row.preservedAttributes);
559
+ const attrs = serializePreservedAttributes(serializeEmptyRowProperties(row), row.preservedAttributes);
559
560
  return `<w:tr${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""}>${parts.join("")}</w:tr>`;
560
561
  }
561
562
  /**
@@ -1,3 +1,4 @@
1
+ import { encodeUtf8 } from "../../utils/textEncoding.js";
1
2
  import { loadDocxArchive } from "./boundedArchive.js";
2
3
  import { FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, InvalidFolioDocxXmlPatchProposalError, evaluateDocxXmlPatchProposal, parseFolioDocxXmlPatchProposal } from "./evaluateDocxXmlPatchProposal.js";
3
4
  import { FOLIO_DOCX_CONFORMANCE_PROFILE, validateDocxConformance } from "./validateDocxConformance.js";
@@ -96,9 +97,8 @@ const applyReplacements = async ({ bytes, proposal }) => {
96
97
  writeZipPart({
97
98
  zip,
98
99
  path: replacement.path,
99
- data: new TextEncoder().encode(replacement.replacementXml),
100
+ data: encodeUtf8(replacement.replacementXml),
100
101
  options: {
101
- binary: true,
102
102
  date: current.date,
103
103
  comment: current.comment,
104
104
  unixPermissions: current.unixPermissions,
@@ -1,3 +1,4 @@
1
+ import { decodeText } from "../../utils/textEncoding.js";
1
2
  import { compressionRatioLimitFor, countCentralDirectoryRecords, createInflationBudget, exceedsCompressionRatio, getZipEntrySizes, inflateEntryWithinLimits } from "../archiveInflation.js";
2
3
  import { FOLIO_XML_RESOURCE_LIMITS, assertXmlResourceLimits, createXmlPackageBudget } from "../xmlResourceLimits.js";
3
4
  import { TaggedError } from "better-result";
@@ -170,7 +171,7 @@ const loadDocxArchive = async (bytes, options = {}) => {
170
171
  async readEntryString(path) {
171
172
  const content = await readEntry(path);
172
173
  if (content === null) return null;
173
- const xml = new TextDecoder("utf-8", { ignoreBOM: true }).decode(content);
174
+ const xml = decodeText(content, { ignoreBOM: true });
174
175
  const lower = path.toLowerCase();
175
176
  if (!lower.endsWith(".xml") && !lower.endsWith(".rels")) return xml;
176
177
  const charged = countedParts.has(path);
@@ -1,3 +1,4 @@
1
+ import { encodeUtf8 } from "../../utils/textEncoding.js";
1
2
  import { getDocxXmlSafetyIssue } from "../xmlSafety.js";
2
3
  import { DocxArchiveError } from "./boundedArchive.js";
3
4
  import { FolioDocxPackageInspectionError, inspectDocxPackage } from "./inspectDocxPackage.js";
@@ -264,7 +265,7 @@ const evaluateDocxXmlPatchProposal = async ({ bytes, proposal: rawProposal, allo
264
265
  });
265
266
  }
266
267
  if (exceedsPartLowerBound || totalLimitReported) continue;
267
- const replacementBytes = new TextEncoder().encode(replacement.replacementXml);
268
+ const replacementBytes = encodeUtf8(replacement.replacementXml);
268
269
  const exceedsEncodedPart = replacementBytes.byteLength > limits.maxPartBytes;
269
270
  const nextEncodedTotalBytes = encodedTotalBytes + replacementBytes.byteLength;
270
271
  if (exceedsEncodedPart) issues.push({
@@ -1,3 +1,4 @@
1
+ import { decodeText } from "../../utils/textEncoding.js";
1
2
  import { getAttributeAnyPrefix, getLocalName, parseXmlDocument } from "../xmlParser.js";
2
3
  import { DocxArchiveError, loadDocxArchive } from "./boundedArchive.js";
3
4
  import { Result, TaggedError } from "better-result";
@@ -33,7 +34,10 @@ const decodeXml = (bytes, part) => {
33
34
  if (bytes.length >= 2 && (bytes[0] === 255 && bytes[1] === 254 || bytes[0] === 60 && bytes[1] === 0)) encoding = "utf-16le";
34
35
  else if (bytes.length >= 2 && (bytes[0] === 254 && bytes[1] === 255 || bytes[0] === 0 && bytes[1] === 60)) encoding = "utf-16be";
35
36
  const decoded = Result.try({
36
- try: () => new TextDecoder(encoding, { fatal: true }).decode(bytes),
37
+ try: () => decodeText(bytes, {
38
+ encoding,
39
+ fatal: true
40
+ }),
37
41
  catch: (cause) => new FolioDocxPackageInspectionError({
38
42
  message: `Failed to decode XML package part "${part}"`,
39
43
  code: "xml-decode-failed",
@@ -1,3 +1,4 @@
1
+ import { decodeText } from "../../utils/textEncoding.js";
1
2
  import { detectDocxConformanceClass } from "../conformance.js";
2
3
  import { DOCX_CONTAINER_TYPES, detectDocxContainerType } from "../encryption/containerFormat.js";
3
4
  import { DocxModelValidationError, validateFolioDocumentModel } from "../modelValidation.js";
@@ -131,7 +132,7 @@ const readPartForInspection = async (archive, part) => {
131
132
  } catch (error) {
132
133
  if (!(error instanceof XmlResourceLimitError) || error.limit !== "syntax") throw error;
133
134
  const bytes = await archive.readEntryUint8(part);
134
- return bytes === null ? null : new TextDecoder("utf-8", { ignoreBOM: true }).decode(bytes);
135
+ return bytes === null ? null : decodeText(bytes, { ignoreBOM: true });
135
136
  }
136
137
  };
137
138
  const validateXmlParts = async (archive, report) => {
@@ -11,10 +11,11 @@ import { captureSdtSiblingMarkers, parseSdtProperties } from "./sdtProperties.js
11
11
  import { parseShading } from "./shadingParser.js";
12
12
  import { findChildAnySpelling } from "./strictNames.js";
13
13
  import { TABLE_LOOK_FLAGS } from "./tableLook.js";
14
+ import { isEmptyRowPropertiesAttribute, retainsEmptyRowProperties } from "./tableRowPropertyPresence.js";
14
15
  import { parsePropertyChangeInfo, parseTrackedChangeInfo } from "./trackedChangeInfo.js";
15
16
  import { percentageSpelling, transitionalSlotEncoding } from "./transitionalSpelling.js";
16
17
  import { captureVerbatimXml } from "./verbatimCapture.js";
17
- import { WORDPROCESSINGML_NAMESPACE_URIS, cloneElement, findChild, findChildren, findWordprocessingChild, getAttribute, getAttributeByNamespaceUri, getLocalName, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, parseOnOffAttribute, parseTableMeasurementValue, selectAlternateContentBranch } from "./xmlParser.js";
18
+ import { WORDPROCESSINGML_NAMESPACE_URIS, cloneElement, findChild, findChildren, findWordprocessingChild, getAttribute, getAttributeByNamespaceUri, getChildElements, getLocalName, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, parseOnOffAttribute, parseTableMeasurementValue, selectAlternateContentBranch } from "./xmlParser.js";
18
19
  import { PARSE_WARNING_CODES } from "@stll/docx-core/model";
19
20
  //#region src/docx/tableParser.ts
20
21
  /**
@@ -637,6 +638,8 @@ function parseTableRowProperties(trPrElement) {
637
638
  context: formatting
638
639
  });
639
640
  if (preserved) formatting.preserved = preserved;
641
+ const children = getChildElements(trPrElement);
642
+ if (Object.keys(formatting).length === 0 && children.length > 0 && Object.keys(trPrElement.attributes ?? {}).every((name) => name === "xmlns" || name.startsWith("xmlns:")) && children.every((child) => WORDPROCESSINGML_NAMESPACE_URIS.has(child.namespaceUri ?? "") && REVISION_PROPERTY_CHILDREN.has(getLocalName(child.name)))) return void 0;
640
643
  return withSourceXml(formatting, trPrElement);
641
644
  }
642
645
  /**
@@ -1099,7 +1102,7 @@ function parseTableRow(trElement, styles, theme, numbering, rels, media, options
1099
1102
  const exceptionChanges = parseTablePropertyExceptionChanges(tblPrExElement, exceptions);
1100
1103
  if (exceptionChanges !== void 0) row.tablePropertyExceptionChanges = exceptionChanges;
1101
1104
  const trPrElement = findChild(trElement, "w", "trPr");
1102
- const formatting = parseTableRowProperties(trPrElement);
1105
+ const formatting = parseTableRowProperties(trPrElement) ?? (trPrElement && retainsEmptyRowProperties(trElement) ? withSourceXml({}, trPrElement) : void 0);
1103
1106
  if (formatting) row.formatting = formatting;
1104
1107
  const rowPropChanges = parseTableRowPropertyChanges(trPrElement, formatting);
1105
1108
  if (rowPropChanges !== void 0) row.propertyChanges = rowPropChanges;
@@ -1126,8 +1129,8 @@ function parseTableRow(trElement, styles, theme, numbering, rels, media, options
1126
1129
  const remainingAttributes = attributeRemainder({
1127
1130
  element: trElement,
1128
1131
  modelled: NO_MODELLED_ATTRIBUTES
1129
- });
1130
- if (remainingAttributes) row.preservedAttributes = remainingAttributes;
1132
+ })?.filter((attribute) => !isEmptyRowPropertiesAttribute(attribute));
1133
+ if (remainingAttributes?.length) row.preservedAttributes = remainingAttributes;
1131
1134
  return row;
1132
1135
  }
1133
1136
  /**
@@ -0,0 +1,9 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ import { XmlElement } from "./xmlParser.js";
3
+ //#region src/docx/tableRowPropertyPresence.d.ts
4
+ declare const EMPTY_ROW_PROPERTIES_ATTRIBUTE = "emptyRowProperties";
5
+ declare const retainsEmptyRowProperties: (element: XmlElement) => boolean;
6
+ declare const serializeEmptyRowProperties: (row: document_d_exports.TableRow) => string[];
7
+ declare const isEmptyRowPropertiesAttribute: ({ namespace, name }: Pick<document_d_exports.PreservedAttribute, "namespace" | "name">) => boolean;
8
+ //#endregion
9
+ export { EMPTY_ROW_PROPERTIES_ATTRIBUTE, isEmptyRowPropertiesAttribute, retainsEmptyRowProperties, serializeEmptyRowProperties };
@@ -0,0 +1,15 @@
1
+ import { OOXML_NAMESPACES } from "./serializer/partNamespaces.js";
2
+ import { getAttributeByNamespaceUri } from "./xmlParser.js";
3
+ import { EMPTY_PROPERTY_SETS } from "@stll/docx-core/ops";
4
+ //#region src/docx/tableRowPropertyPresence.ts
5
+ const EMPTY_ROW_PROPERTIES_ATTRIBUTE = "emptyRowProperties";
6
+ const HISTORY_NAMESPACES = /* @__PURE__ */ new Set([OOXML_NAMESPACES.folio.uri]);
7
+ const retainsEmptyRowProperties = (element) => getAttributeByNamespaceUri(element, HISTORY_NAMESPACES, EMPTY_ROW_PROPERTIES_ATTRIBUTE) === EMPTY_PROPERTY_SETS.KEEP;
8
+ const serializeEmptyRowProperties = (row) => {
9
+ if (row.formatting === void 0 || row.structuralChange === void 0 && !row.propertyChanges?.length) return [];
10
+ const { sourceXml: _source, ...properties } = row.formatting;
11
+ return Object.keys(properties).length === 0 ? [`folio:${EMPTY_ROW_PROPERTIES_ATTRIBUTE}="${EMPTY_PROPERTY_SETS.KEEP}"`] : [];
12
+ };
13
+ const isEmptyRowPropertiesAttribute = ({ namespace, name }) => namespace === OOXML_NAMESPACES.folio.uri && name === "emptyRowProperties";
14
+ //#endregion
15
+ export { EMPTY_ROW_PROPERTIES_ATTRIBUTE, isEmptyRowPropertiesAttribute, retainsEmptyRowProperties, serializeEmptyRowProperties };
@@ -1,4 +1,5 @@
1
1
  import { bytesToDataUrl } from "../utils/base64.js";
2
+ import { encodeUtf8 } from "../utils/textEncoding.js";
2
3
  import { compressionRatioLimitFor, countCentralDirectoryRecords, createInflationBudget, exceedsCompressionRatio, getZipEntrySizes, inflateEntryWithinLimits, isStoredZipEntry } from "./archiveInflation.js";
3
4
  import { DOCX_CONTAINER_TYPES, detectDocxContainerType } from "./encryption/containerFormat.js";
4
5
  import { openDocxBuffer } from "./encryption/openEncryptedDocx.js";
@@ -286,7 +287,7 @@ const replaceRawDocxXmlParts = async (content, parts) => {
286
287
  writeZipPart({
287
288
  zip: content.originalZip,
288
289
  path,
289
- data: xmlContent
290
+ data: encodeUtf8(xmlContent)
290
291
  });
291
292
  identitiesChanged = true;
292
293
  }
@@ -1,12 +1,13 @@
1
+ import { decodeText } from "../utils/textEncoding.js";
1
2
  //#region src/docx/xmlEncoding.ts
2
3
  /** Decode the UTF-8 and UTF-16 encodings XML processors must recognize. */
3
4
  const decodeXmlBytes = (bytes) => {
4
- if (bytes[0] === 255 && bytes[1] === 254) return new TextDecoder("utf-16le").decode(bytes.subarray(2));
5
- if (bytes[0] === 254 && bytes[1] === 255) return new TextDecoder("utf-16be").decode(bytes.subarray(2));
6
- if (bytes[0] === 60 && bytes[1] === 0 && bytes[2] === 63 && bytes[3] === 0) return new TextDecoder("utf-16le").decode(bytes);
7
- if (bytes[0] === 0 && bytes[1] === 60 && bytes[2] === 0 && bytes[3] === 63) return new TextDecoder("utf-16be").decode(bytes);
5
+ if (bytes[0] === 255 && bytes[1] === 254) return decodeText(bytes.subarray(2), { encoding: "utf-16le" });
6
+ if (bytes[0] === 254 && bytes[1] === 255) return decodeText(bytes.subarray(2), { encoding: "utf-16be" });
7
+ if (bytes[0] === 60 && bytes[1] === 0 && bytes[2] === 63 && bytes[3] === 0) return decodeText(bytes, { encoding: "utf-16le" });
8
+ if (bytes[0] === 0 && bytes[1] === 60 && bytes[2] === 0 && bytes[3] === 63) return decodeText(bytes, { encoding: "utf-16be" });
8
9
  const utf8Offset = bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191 ? 3 : 0;
9
- return new TextDecoder("utf-8").decode(bytes.subarray(utf8Offset));
10
+ return decodeText(bytes.subarray(utf8Offset), { encoding: "utf-8" });
10
11
  };
11
12
  //#endregion
12
13
  export { decodeXmlBytes };
@@ -1,6 +1,7 @@
1
1
  import { bytesToDataUrl } from "../utils/base64.js";
2
2
  import { FONT_MAPPING } from "../utils/fontLoader.js";
3
3
  import { parseFontFamilyList, resolveFontFamily } from "../utils/fontResolver.js";
4
+ import { decodeText } from "../utils/textEncoding.js";
4
5
  //#region src/fonts/fontsourceFaces.ts
5
6
  /**
6
7
  * The subsets served, in resolution priority: a code point two subsets cover
@@ -78,7 +79,7 @@ const parseRangeToken = (token) => {
78
79
  const parseRangeList = (declared) => declared.split(",").flatMap(parseRangeToken);
79
80
  const parseDeclaredRanges = (bytes) => {
80
81
  const ranges = /* @__PURE__ */ new Map();
81
- const parsed = JSON.parse(new TextDecoder().decode(bytes));
82
+ const parsed = JSON.parse(decodeText(bytes));
82
83
  if (typeof parsed !== "object" || parsed === null) return ranges;
83
84
  for (const [subset, declared] of Object.entries(parsed)) if (typeof declared === "string") ranges.set(subset, parseRangeList(declared));
84
85
  return ranges;
@@ -1,3 +1,4 @@
1
+ import { decodeText } from "../../utils/textEncoding.js";
1
2
  import { SFNT_TABLE, SFNT_VERSION, fitsInView, readLocaOffsets, readSfntDirectory, readSfntVersion } from "./tables.js";
2
3
  import { Result, TaggedError } from "better-result";
3
4
  //#region src/fonts/sfnt/parse.ts
@@ -344,17 +345,13 @@ const sanitizePostScriptName = (raw) => {
344
345
  return name.slice(0, MAX_POSTSCRIPT_NAME_LENGTH);
345
346
  };
346
347
  const decodeUtf16Be = (view, offset, length) => {
347
- let text = "";
348
- for (let index = 0; index + 1 < length; index += 2) text += String.fromCharCode(view.getUint16(offset + index));
349
- return text;
348
+ return decodeText(new Uint8Array(view.buffer, view.byteOffset + offset, length - length % 2), {
349
+ encoding: "utf-16be",
350
+ ignoreBOM: true
351
+ });
350
352
  };
351
353
  const decodeMacRomanAscii = (view, offset, length) => {
352
- let text = "";
353
- for (let index = 0; index < length; index++) {
354
- const code = view.getUint8(offset + index);
355
- text += code < 128 ? String.fromCharCode(code) : "";
356
- }
357
- return text;
354
+ return decodeText(new Uint8Array(view.buffer, view.byteOffset + offset, length).filter((byte) => byte < 128), { encoding: "binary" });
358
355
  };
359
356
  /**
360
357
  * name id 6, preferring the Windows UTF-16BE record over the Macintosh one.
@@ -1,3 +1,4 @@
1
+ import { encodeBinaryString } from "../../utils/textEncoding.js";
1
2
  import { SFNT_TABLE, SFNT_VERSION, fitsInView, readLocaOffsets, readSfntDirectory } from "./tables.js";
2
3
  import { Result, TaggedError, panic } from "better-result";
3
4
  //#region src/fonts/sfnt/subset.ts
@@ -223,7 +224,7 @@ const assembleSfnt = (tables) => {
223
224
  let dataOffset = 12 + directorySize;
224
225
  let headOffset = -1;
225
226
  for (const table of sorted) {
226
- for (let index = 0; index < table.tag.length; index++) view.setUint8(entry + index, table.tag.charCodeAt(index));
227
+ output.set(encodeBinaryString(table.tag), entry);
227
228
  view.setUint32(entry + 4, checksumOf(table.data));
228
229
  view.setUint32(entry + 8, dataOffset);
229
230
  view.setUint32(entry + 12, table.data.byteLength);
@@ -1,3 +1,4 @@
1
+ import { decodeText } from "../../utils/textEncoding.js";
1
2
  import { Result, TaggedError } from "better-result";
2
3
  //#region src/fonts/sfnt/tables.ts
3
4
  /**
@@ -65,9 +66,7 @@ const fitsInView = (view, offset, length) => Number.isInteger(offset) && Number.
65
66
  /** The four-byte tag at `offset`, or `""` when it does not fit. */
66
67
  const readTag = (view, offset) => {
67
68
  if (!fitsInView(view, offset, TAG_LENGTH)) return "";
68
- let tag = "";
69
- for (let index = 0; index < TAG_LENGTH; index++) tag += String.fromCharCode(view.getUint8(offset + index));
70
- return tag;
69
+ return decodeText(new Uint8Array(view.buffer, view.byteOffset + offset, TAG_LENGTH), { encoding: "binary" });
71
70
  };
72
71
  /** The leading version word, or `undefined` when the buffer is shorter than it. */
73
72
  const readSfntVersion = (bytes) => {
@@ -1,3 +1,4 @@
1
+ import { encodeBinaryString } from "../../utils/textEncoding.js";
1
2
  import { SFNT_VERSION, readSfntVersion, readTag } from "./tables.js";
2
3
  import { Result, TaggedError } from "better-result";
3
4
  import { inflateSync } from "node:zlib";
@@ -76,7 +77,7 @@ const buildSfnt = (flavor, tables) => {
76
77
  let entry = 12;
77
78
  let dataOffset = 12 + directorySize;
78
79
  for (const table of sorted) {
79
- for (let index = 0; index < table.tag.length; index++) view.setUint8(entry + index, table.tag.charCodeAt(index));
80
+ output.set(encodeBinaryString(table.tag), entry);
80
81
  view.setUint32(entry + 4, table.checksum);
81
82
  view.setUint32(entry + 8, dataOffset);
82
83
  view.setUint32(entry + 12, table.data.byteLength);
@@ -362,6 +362,7 @@ const messages = { "folio": {
362
362
  "previous": "النتيجة السابقة",
363
363
  "previousShortcut": "النتيجة السابقة (Shift+Enter)",
364
364
  "replaceAll": "استبدال الكل",
365
+ "replaceAllSummary": "تم استبدال {replacedCount} وتخطي {skippedCount}",
365
366
  "replaceCurrent": "استبدال النتيجة الحالية",
366
367
  "replacePlaceholder": "نص الاستبدال...",
367
368
  "replaceText": "نص الاستبدال",