@stll/folio-core 0.40.0 → 0.41.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 (47) hide show
  1. package/dist/ai-edits/clean-text.d.ts +28 -6
  2. package/dist/ai-edits/clean-text.js +28 -13
  3. package/dist/ai-edits/headless.d.ts +12 -0
  4. package/dist/ai-edits/headless.js +26 -3
  5. package/dist/ai-edits/snapshot.d.ts +6 -1
  6. package/dist/ai-edits/snapshot.js +15 -10
  7. package/dist/ai-suggestions/text-positions.js +15 -3
  8. package/dist/compare/inline-atoms.js +2 -2
  9. package/dist/compat/eigenpal.d.ts +3 -2
  10. package/dist/compat/eigenpal.js +2 -1
  11. package/dist/display-list/primitives.d.ts +2 -1
  12. package/dist/document-operations.d.ts +3 -2
  13. package/dist/docx/blockPlainText.d.ts +8 -0
  14. package/dist/docx/blockPlainText.js +40 -0
  15. package/dist/docx/compatibility.d.ts +16 -2
  16. package/dist/docx/compatibility.js +30 -9
  17. package/dist/docx/footnoteParser.js +4 -23
  18. package/dist/docx/graphicFrameLocks.d.ts +22 -0
  19. package/dist/docx/graphicFrameLocks.js +55 -0
  20. package/dist/docx/groupDrawingParser.d.ts +7 -1
  21. package/dist/docx/groupDrawingParser.js +11 -2
  22. package/dist/docx/headerFooterParser.d.ts +5 -1
  23. package/dist/docx/headerFooterParser.js +7 -21
  24. package/dist/docx/imageParser.js +5 -0
  25. package/dist/docx/imageRawXml.d.ts +33 -1
  26. package/dist/docx/imageRawXml.js +56 -1
  27. package/dist/docx/runParser.js +43 -21
  28. package/dist/docx/serializer/runSerializer.js +4 -2
  29. package/dist/docx/server/createBilingualDocument.js +31 -5
  30. package/dist/docx/shapeParser.js +32 -6
  31. package/dist/docx/vmlImageParser.d.ts +11 -1
  32. package/dist/docx/vmlImageParser.js +29 -2
  33. package/dist/index.d.ts +3 -2
  34. package/dist/index.js +2 -1
  35. package/dist/internal/compare/inline-presentation.d.ts +11 -3
  36. package/dist/internal/compare/inline-presentation.js +8 -1
  37. package/dist/prosemirror/attrs/index.js +33 -3
  38. package/dist/prosemirror/conversion/fromProseDoc.d.ts +13 -2
  39. package/dist/prosemirror/conversion/fromProseDoc.js +80 -27
  40. package/dist/prosemirror/conversion/index.d.ts +2 -2
  41. package/dist/prosemirror/conversion/toProseDoc.js +10 -2
  42. package/dist/prosemirror/extensions/nodes/ImageExtension.js +6 -0
  43. package/dist/prosemirror/imageCommit.js +7 -3
  44. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +16 -1
  45. package/dist/prosemirror/runFormattingInlineCarriers.js +20 -1
  46. package/dist/prosemirror/schema/nodes.d.ts +20 -0
  47. package/package.json +2 -2
@@ -87,6 +87,28 @@ function hasUnsupportedRgbColorModifiers(spPr) {
87
87
  function hasUnmodeledFill(spPr) {
88
88
  return findChildByLocalName(spPr, "pattFill") !== null || findChildByLocalName(spPr, "blipFill") !== null || findChildByLocalName(spPr, "grpFill") !== null;
89
89
  }
90
+ /**
91
+ * Shape properties the serializer never emits: `Shape` models geometry, fill,
92
+ * outline and transform only, so a shadow, glow, reflection or 3-D scene would
93
+ * be dropped on save.
94
+ */
95
+ const UNMODELED_EFFECT_ELEMENTS = [
96
+ "effectLst",
97
+ "effectDag",
98
+ "scene3d",
99
+ "sp3d"
100
+ ];
101
+ /**
102
+ * An empty `<a:effectLst/>` is Word's explicit "no effects" marker and models
103
+ * fine; carried attributes (`<a:sp3d extrusionH="…"/>`) or children do not.
104
+ */
105
+ function hasUnmodeledEffects(spPr) {
106
+ return UNMODELED_EFFECT_ELEMENTS.some((localName) => {
107
+ const element = findChildByLocalName(spPr, localName);
108
+ if (!element) return false;
109
+ return getChildElements(element).length > 0 || Object.keys(element.attributes ?? {}).length > 0;
110
+ });
111
+ }
90
112
  function colorNeedsRawPreservation(color) {
91
113
  return color !== void 0 && color.rgb === void 0;
92
114
  }
@@ -97,6 +119,14 @@ function fillNeedsRawPreservation(fill) {
97
119
  return false;
98
120
  }
99
121
  /**
122
+ * The single answer to "does this `wps:spPr` carry something the editable
123
+ * `Shape` model would lose?". `parseShapeFromDrawing` refuses such a shape and
124
+ * `shouldPreserveRawShapeDrawing` claims it for verbatim preservation, so both
125
+ * must read the same predicate: a reason added to only one of them would
126
+ * either drop the drawing or model it lossily.
127
+ */
128
+ const shapeNeedsRawPreservation = (spPr) => hasUnsupportedGeometry(spPr) || hasUnsupportedRgbColorModifiers(spPr) || hasUnmodeledFill(spPr) || hasUnmodeledEffects(spPr) || fillNeedsRawPreservation(parseShapeFill(spPr));
129
+ /**
100
130
  * Parse a `<wps:wsp>` element into a Shape model. Does NOT pick up the
101
131
  * extent / position / wrap fields — those live on the wrapping
102
132
  * `<wp:inline>` / `<wp:anchor>` and are handled by `parseShapeFromDrawing`.
@@ -143,7 +173,7 @@ function parseShapeFromDrawing(drawingEl) {
143
173
  if (!wsp) return null;
144
174
  if (findChildByLocalName(wsp, "txbx") !== null) return null;
145
175
  const spPr = findChildByLocalName(wsp, "spPr");
146
- if (hasUnsupportedGeometry(spPr) || hasUnsupportedRgbColorModifiers(spPr) || hasUnmodeledFill(spPr) || fillNeedsRawPreservation(parseShapeFill(spPr))) return null;
176
+ if (shapeNeedsRawPreservation(spPr)) return null;
147
177
  const shape = parseShape(wsp);
148
178
  const extent = findChildByLocalName(container, "extent");
149
179
  if (extent) {
@@ -178,11 +208,7 @@ function shouldPreserveRawShapeDrawing(drawingEl) {
178
208
  const graphicData = graphic ? findChildByLocalName(graphic, "graphicData") : null;
179
209
  const wsp = graphicData ? findChildByLocalName(graphicData, "wsp") : null;
180
210
  if (!wsp || findChildByLocalName(wsp, "txbx") !== null) return false;
181
- const spPr = findChildByLocalName(wsp, "spPr");
182
- if (hasUnsupportedGeometry(spPr)) return true;
183
- if (hasUnsupportedRgbColorModifiers(spPr)) return true;
184
- if (hasUnmodeledFill(spPr)) return true;
185
- return fillNeedsRawPreservation(parseShapeFill(spPr));
211
+ return shapeNeedsRawPreservation(findChildByLocalName(wsp, "spPr"));
186
212
  }
187
213
  //#endregion
188
214
  export { parseShape, parseShapeFromDrawing, shouldPreserveRawShapeDrawing };
@@ -1,6 +1,16 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { XmlElement } from "./xmlParser.js";
3
3
  //#region src/docx/vmlImageParser.d.ts
4
+ /**
5
+ * Whether a `w:pict` that resolved to no image must still be kept verbatim.
6
+ *
7
+ * VML has no serializer of its own, so an unresolved `w:pict` is only ever
8
+ * preserved or lost. Two owners are excluded: a `v:textbox` belongs to the
9
+ * text-box enrichment pass, which rebuilds it as an editable shape, and a
10
+ * watermark shape belongs to watermarkParser; preserving either would emit the
11
+ * same artwork twice.
12
+ */
13
+ declare function shouldPreserveRawVmlPict(pictElement: XmlElement): boolean;
4
14
  /**
5
15
  * Parse a `w:pict` element into an inline image, or null when it carries no
6
16
  * ordinary VML picture (no resolvable `<v:imagedata>`, or a watermark shape).
@@ -12,4 +22,4 @@ import { XmlElement } from "./xmlParser.js";
12
22
  */
13
23
  declare function parseVmlImageContent(pictElement: XmlElement, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null, rootXmlns?: Record<string, string>): document_d_exports.DrawingContent | null;
14
24
  //#endregion
15
- export { parseVmlImageContent };
25
+ export { parseVmlImageContent, shouldPreserveRawVmlPict };
@@ -4,7 +4,7 @@ import { resolveImageData } from "./imageParser.js";
4
4
  import { captureVerbatimXml } from "./verbatimCapture.js";
5
5
  import { isValidVmlPreviewDimension, parseVmlNumber, parseVmlStyle, renderStandaloneVmlPreview, renderVmlGroupPreview, vmlCssLengthToPx, vmlSvgDataUrl } from "./vmlPreview.js";
6
6
  import { isWatermarkShape } from "./watermarkParser.js";
7
- import { cloneWithXmlnsDeclarations, findAllDeep, findChild, getAttribute, getChildElements, getLocalName } from "./xmlParser.js";
7
+ import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findDeep, getAttribute, getChildElements, getLocalName } from "./xmlParser.js";
8
8
  //#region src/docx/vmlImageParser.ts
9
9
  const VML_POSITION_ABSOLUTE = "absolute";
10
10
  const IMAGE_WRAP_INLINE = "inline";
@@ -102,6 +102,33 @@ const previewDrawing = (pictElement, preview, rootXmlns) => {
102
102
  default: return preview;
103
103
  }
104
104
  };
105
+ /** VML elements that paint something, i.e. content a save must not lose. */
106
+ const VML_DRAWABLE_SHAPES = [
107
+ "shape",
108
+ "group",
109
+ "rect",
110
+ "roundrect",
111
+ "oval",
112
+ "line",
113
+ "polyline",
114
+ "curve",
115
+ "arc",
116
+ "image"
117
+ ];
118
+ /**
119
+ * Whether a `w:pict` that resolved to no image must still be kept verbatim.
120
+ *
121
+ * VML has no serializer of its own, so an unresolved `w:pict` is only ever
122
+ * preserved or lost. Two owners are excluded: a `v:textbox` belongs to the
123
+ * text-box enrichment pass, which rebuilds it as an editable shape, and a
124
+ * watermark shape belongs to watermarkParser; preserving either would emit the
125
+ * same artwork twice.
126
+ */
127
+ function shouldPreserveRawVmlPict(pictElement) {
128
+ if (findDeep(pictElement, "v", "textbox")) return false;
129
+ const shapes = VML_DRAWABLE_SHAPES.flatMap((localName) => findAllDeep(pictElement, "v", localName));
130
+ return shapes.length > 0 && !shapes.some((shape) => isWatermarkShape(shape));
131
+ }
105
132
  /**
106
133
  * Read the relationship id off a `v:imagedata` element. Word writes `r:id`;
107
134
  * some legacy / third-party generators use `r:embed` or the office-namespace
@@ -173,4 +200,4 @@ function parseVmlImageContent(pictElement, rels, media, rootXmlns = {}) {
173
200
  return null;
174
201
  }
175
202
  //#endregion
176
- export { parseVmlImageContent };
203
+ export { parseVmlImageContent, shouldPreserveRawVmlPict };
package/dist/index.d.ts CHANGED
@@ -21,7 +21,8 @@ import { mergeDocumentContent } from "./utils/mergeDocumentContent.js";
21
21
  import { DocumentStyleCatalog, DocumentStyleCatalogEntry, ExtractDocumentStyleSetOptions, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, inspectDocumentStyles, inspectDocumentStylesFromDocx } from "./style-sets/extract.js";
22
22
  import { STELLA_STYLE_SET_NAME, createStellaStyleDocumentPreset, createStellaStyleSet } from "./style-sets/stellaStyle.js";
23
23
  import { createDocx } from "./docx/rezip.js";
24
- import { DocxCompatibility, DocxCompatibilityContext, DocxCompatibilityIssue, DocxCompatibilityLocation, DocxCompatibilityPart, FolioDocxCompatibilityHost, FolioDocxCompatibilityProfile, InspectDocxCompatibilityOptions, inspectDocxCompatibility } from "./docx/compatibility.js";
24
+ import { DRAWING_SAFETY_CLASSES, DrawingSafetyClass } from "./docx/imageRawXml.js";
25
+ import { DocxCompatibility, DocxCompatibilityContext, DocxCompatibilityIssue, DocxCompatibilityLocation, DocxCompatibilityPart, DocxDrawingClassification, FolioDocxCompatibilityHost, FolioDocxCompatibilityProfile, InspectDocxCompatibilityOptions, inspectDocxCompatibility } from "./docx/compatibility.js";
25
26
  import { BlockRect } from "./paged-layout/blockGeometry.js";
26
27
  import { setAISuggestionsMeta, setFocusedSuggestionMeta } from "./prosemirror/plugins/aiSuggestionDecorations.js";
27
28
  import { scrollFolioPositionIntoView } from "./paged-layout/scrollToPmPosition.js";
@@ -40,4 +41,4 @@ import { getGoogleFontsEnabled, setEmbeddedFontFamilyMap, setGoogleFontsEnabled
40
41
  import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
41
42
  type Document = document_d_exports.Document;
42
43
  type DocxConformanceClass = document_d_exports.DocxConformanceClass;
43
- export { type AIBarStatus, type AIChatMode, type AICitation, type AICitationRange, type AICitationSource, type AIGenerateInput, type AISuggestion, type AISuggestionApplyMode, type AISuggestionPreset, type AISuggestionSeverity, type AISuggestionStatus, type AcceptAutocompleteResult, type AnonymizationMatch, type AnonymizationTerm, type ApplyFolioDocumentOperationsOptions, type ApplyResult, type AutocompleteSuggestionPluginOptions, type AutocompleteSuggestionState, type AutocompleteSuggestionStatus, type AutocompleteTriggerCheck, type AutocompleteTriggerOptions, type AutocompleteTriggerSkipReason, type BlockRect, COMPARE_REVISION_FORMATS, COMPARE_UNSUPPORTED_REASONS, COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, type CompareChange, type CompareChangeLocation, type CompareCompatibility, type CompareContentOptions, CompareDocxApplyError, type CompareDocxError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, type CompareDocxOptions, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, type CompareFolioRequirement, type CompareResult, type CompareRevisionFormat, type CompareUnsupportedPart, type CompareUnsupportedReason, type CompareVerification, type CompareVerificationCause, type CompareVerificationFailure, type CompareVerificationInvariant, type CreateEmptyDocumentOptions, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DOCX_CONFORMANCE_CLASSES, type DeriveBlockIdInput, type DirectiveKind, type DirectiveRange, type Document, type DocumentPreset, type DocumentStyleCatalog, type DocumentStyleCatalogEntry, type DocumentStyleSet, type DocxCompatibility, type DocxCompatibilityContext, type DocxCompatibilityIssue, type DocxCompatibilityLocation, type DocxCompatibilityPart, type DocxConformanceClass, type EmbeddedFont, type EmbeddedFontParts, type ExtractDocumentStyleSetOptions, FOLIO_CONTENT_COMPARISON_LIMITS, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, type FinalParagraphMarkRevision, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIBlockStructuralBoundary, type FolioAIBlockTableLocation, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyOutcome, type FolioAIEditApplyResult, type FolioAIEditNormalization, type FolioAIEditNormalizationCode, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAIInlineBooleanProperty, type FolioAIInlineFormatting, type FolioAIInlineFormattingPatch, type FolioAIParagraphSpacing, type FolioAISignatureParty, type FolioBlockId, type FolioContentBlock, type FolioContentBlockProperty, type FolioContentComparison, type FolioContentComparisonError, type FolioContentComparisonEvent, type FolioContentComparisonLimit, FolioContentComparisonLimitError, type FolioContentContainerPathEntry, type FolioContentFormatRange, type FolioContentFormattingChange, type FolioContentIdStability, type FolioContentInlineBooleanProperty, type FolioContentInlineComparisonResult, type FolioContentInlineFormatting, type FolioContentInlineFormattingPatch, FolioContentInlinePresentationProjectionError, type FolioContentLineSpacingRule, type FolioContentParagraphAlignment, type FolioContentParagraphFormattingPatch, type FolioContentParagraphKind, type FolioContentParagraphSpacing, type FolioContentRun, type FolioContentSnapshot, type FolioContentStructuralChange, type FolioContentTableLocation, type FolioContentTextSegment, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationResultBase, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocxCompatibilityHost, type FolioDocxCompatibilityProfile, type FolioRevisionStamp, type FolioWordDiffOptions, type ImageMeta, type ImageRef, type InspectDocxCompatibilityOptions, InvalidCompareDocxOptionsError, InvalidFolioContentComparisonError, InvalidFolioDocumentOperationBatchError, MAX_COMPARE_OPERATIONS, type MarkdownOptions, type MarkdownResult, type PositionalText, type ResolvedAnchor, STELLA_STYLE_SET_NAME, type TemplatePreviewSpan, type TemplatePreviewValue, type TemplatePreviewValues, type TemplateSlashMenuKeyAction, type TemplateSlashMenuState, UnsupportedFolioDocumentOperationVersionError, WORD_DIFF_GRANULARITIES, type WordDiffGranularity, type WordDiffNormalization, type WordDiffOptions, type WordDiffSegment, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildEmbeddedFontFamilyMap, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, compareContent, compareDocx, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, currentFolioBlockId, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioAIContentBlock, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, mergeDocumentContent, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scopeEmbeddedFontFamily, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setEmbeddedFontFamilyMap, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
44
+ export { type AIBarStatus, type AIChatMode, type AICitation, type AICitationRange, type AICitationSource, type AIGenerateInput, type AISuggestion, type AISuggestionApplyMode, type AISuggestionPreset, type AISuggestionSeverity, type AISuggestionStatus, type AcceptAutocompleteResult, type AnonymizationMatch, type AnonymizationTerm, type ApplyFolioDocumentOperationsOptions, type ApplyResult, type AutocompleteSuggestionPluginOptions, type AutocompleteSuggestionState, type AutocompleteSuggestionStatus, type AutocompleteTriggerCheck, type AutocompleteTriggerOptions, type AutocompleteTriggerSkipReason, type BlockRect, COMPARE_REVISION_FORMATS, COMPARE_UNSUPPORTED_REASONS, COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, type CompareChange, type CompareChangeLocation, type CompareCompatibility, type CompareContentOptions, CompareDocxApplyError, type CompareDocxError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, type CompareDocxOptions, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, type CompareFolioRequirement, type CompareResult, type CompareRevisionFormat, type CompareUnsupportedPart, type CompareUnsupportedReason, type CompareVerification, type CompareVerificationCause, type CompareVerificationFailure, type CompareVerificationInvariant, type CreateEmptyDocumentOptions, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DOCX_CONFORMANCE_CLASSES, DRAWING_SAFETY_CLASSES, type DeriveBlockIdInput, type DirectiveKind, type DirectiveRange, type Document, type DocumentPreset, type DocumentStyleCatalog, type DocumentStyleCatalogEntry, type DocumentStyleSet, type DocxCompatibility, type DocxCompatibilityContext, type DocxCompatibilityIssue, type DocxCompatibilityLocation, type DocxCompatibilityPart, type DocxConformanceClass, type DocxDrawingClassification, type DrawingSafetyClass, type EmbeddedFont, type EmbeddedFontParts, type ExtractDocumentStyleSetOptions, FOLIO_CONTENT_COMPARISON_LIMITS, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, type FinalParagraphMarkRevision, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIBlockStructuralBoundary, type FolioAIBlockTableLocation, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyOutcome, type FolioAIEditApplyResult, type FolioAIEditNormalization, type FolioAIEditNormalizationCode, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAIInlineBooleanProperty, type FolioAIInlineFormatting, type FolioAIInlineFormattingPatch, type FolioAIParagraphSpacing, type FolioAISignatureParty, type FolioBlockId, type FolioContentBlock, type FolioContentBlockProperty, type FolioContentComparison, type FolioContentComparisonError, type FolioContentComparisonEvent, type FolioContentComparisonLimit, FolioContentComparisonLimitError, type FolioContentContainerPathEntry, type FolioContentFormatRange, type FolioContentFormattingChange, type FolioContentIdStability, type FolioContentInlineBooleanProperty, type FolioContentInlineComparisonResult, type FolioContentInlineFormatting, type FolioContentInlineFormattingPatch, FolioContentInlinePresentationProjectionError, type FolioContentLineSpacingRule, type FolioContentParagraphAlignment, type FolioContentParagraphFormattingPatch, type FolioContentParagraphKind, type FolioContentParagraphSpacing, type FolioContentRun, type FolioContentSnapshot, type FolioContentStructuralChange, type FolioContentTableLocation, type FolioContentTextSegment, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationResultBase, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocxCompatibilityHost, type FolioDocxCompatibilityProfile, type FolioRevisionStamp, type FolioWordDiffOptions, type ImageMeta, type ImageRef, type InspectDocxCompatibilityOptions, InvalidCompareDocxOptionsError, InvalidFolioContentComparisonError, InvalidFolioDocumentOperationBatchError, MAX_COMPARE_OPERATIONS, type MarkdownOptions, type MarkdownResult, type PositionalText, type ResolvedAnchor, STELLA_STYLE_SET_NAME, type TemplatePreviewSpan, type TemplatePreviewValue, type TemplatePreviewValues, type TemplateSlashMenuKeyAction, type TemplateSlashMenuState, UnsupportedFolioDocumentOperationVersionError, WORD_DIFF_GRANULARITIES, type WordDiffGranularity, type WordDiffNormalization, type WordDiffOptions, type WordDiffSegment, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildEmbeddedFontFamilyMap, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, compareContent, compareDocx, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, currentFolioBlockId, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioAIContentBlock, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, mergeDocumentContent, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scopeEmbeddedFontFamily, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setEmbeddedFontFamilyMap, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ import { COMPARE_REVISION_FORMATS, COMPARE_UNSUPPORTED_REASONS, CompareDocxApply
11
11
  import { COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS } from "./compare/verification.js";
12
12
  import { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "./document-operations.js";
13
13
  import { inspectDocxCompatibility } from "./docx/compatibility.js";
14
+ import { DRAWING_SAFETY_CLASSES } from "./docx/imageRawXml.js";
14
15
  import { createDocx } from "./docx/rezip.js";
15
16
  import { buildEmbeddedFontFamilyMap, extractEmbeddedFonts, getEmbeddedFontFaces, scopeEmbeddedFontFamily } from "./fonts/embeddedFonts.js";
16
17
  import { fromMarkdown } from "./markdown/fromMarkdown.js";
@@ -32,4 +33,4 @@ import { createEmptyDocument } from "./utils/createDocument.js";
32
33
  import { getGoogleFontsEnabled, setEmbeddedFontFamilyMap, setGoogleFontsEnabled } from "./utils/fontResolver.js";
33
34
  import { mergeDocumentContent } from "./utils/mergeDocumentContent.js";
34
35
  import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
35
- export { COMPARE_REVISION_FORMATS, COMPARE_UNSUPPORTED_REASONS, COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, CompareDocxApplyError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DOCX_CONFORMANCE_CLASSES, FOLIO_CONTENT_COMPARISON_LIMITS, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FolioContentComparisonLimitError, FolioContentInlinePresentationProjectionError, InvalidCompareDocxOptionsError, InvalidFolioContentComparisonError, InvalidFolioDocumentOperationBatchError, MAX_COMPARE_OPERATIONS, STELLA_STYLE_SET_NAME, UnsupportedFolioDocumentOperationVersionError, WORD_DIFF_GRANULARITIES, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildEmbeddedFontFamilyMap, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, compareContent, compareDocx, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, currentFolioBlockId, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioAIContentBlock, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, mergeDocumentContent, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scopeEmbeddedFontFamily, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setEmbeddedFontFamilyMap, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
36
+ export { COMPARE_REVISION_FORMATS, COMPARE_UNSUPPORTED_REASONS, COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, CompareDocxApplyError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DOCX_CONFORMANCE_CLASSES, DRAWING_SAFETY_CLASSES, FOLIO_CONTENT_COMPARISON_LIMITS, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FolioContentComparisonLimitError, FolioContentInlinePresentationProjectionError, InvalidCompareDocxOptionsError, InvalidFolioContentComparisonError, InvalidFolioDocumentOperationBatchError, MAX_COMPARE_OPERATIONS, STELLA_STYLE_SET_NAME, UnsupportedFolioDocumentOperationVersionError, WORD_DIFF_GRANULARITIES, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildEmbeddedFontFamilyMap, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, compareContent, compareDocx, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, currentFolioBlockId, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioAIContentBlock, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, mergeDocumentContent, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scopeEmbeddedFontFamily, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setEmbeddedFontFamilyMap, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
@@ -1,7 +1,15 @@
1
- import { FolioContentBlock, FolioContentInlineComparisonResult } from "../../compare/content-types.js";
1
+ import { FolioContentBlock, FolioContentInlineComparisonResult, FolioContentInlineFormatting } from "../../compare/content-types.js";
2
2
  //#region src/internal/compare/inline-presentation.d.ts
3
- /** Derived from the descriptor grammar; tests use it to prove full coverage. */
4
- declare const CANONICAL_INLINE_PRESENTATION_PROPERTIES: readonly ("bold" | "italic" | "underline" | "strike" | "fontFamily" | "fontSizePt" | "color")[];
3
+ type InlineFormattingProperty = keyof FolioContentInlineFormatting;
4
+ /**
5
+ * Derived from the descriptor grammar; tests use it to prove full coverage.
6
+ *
7
+ * Annotated, against the usual rule, for the reason given on
8
+ * `DISPLAY_PRIMITIVE_KINDS`: the inferred union's member order is not stable
9
+ * across declaration builds, and naming the alias keeps the emitted `.d.ts`
10
+ * reproducible.
11
+ */
12
+ declare const CANONICAL_INLINE_PRESENTATION_PROPERTIES: readonly InlineFormattingProperty[];
5
13
  type InlinePresentationBlock = Pick<FolioContentBlock, "previewRuns" | "text">;
6
14
  type CanonicalInlinePresentationSegmentsOptions = {
7
15
  baseBlock: InlinePresentationBlock;
@@ -28,7 +28,14 @@ const INLINE_PRESENTATION_GRAMMAR = {
28
28
  color: ["color"]
29
29
  };
30
30
  const INLINE_PRESENTATION_HOT_PATH_GRAMMAR = INLINE_PRESENTATION_GRAMMAR;
31
- /** Derived from the descriptor grammar; tests use it to prove full coverage. */
31
+ /**
32
+ * Derived from the descriptor grammar; tests use it to prove full coverage.
33
+ *
34
+ * Annotated, against the usual rule, for the reason given on
35
+ * `DISPLAY_PRIMITIVE_KINDS`: the inferred union's member order is not stable
36
+ * across declaration builds, and naming the alias keeps the emitted `.d.ts`
37
+ * reproducible.
38
+ */
32
39
  const CANONICAL_INLINE_PRESENTATION_PROPERTIES = Object.freeze([
33
40
  ...INLINE_PRESENTATION_GRAMMAR.boolean,
34
41
  ...INLINE_PRESENTATION_GRAMMAR.string,
@@ -1,3 +1,5 @@
1
+ import { GRAPHIC_FRAME_LOCK_KEYS } from "../../docx/graphicFrameLocks.js";
2
+ import { allowsDirectDrawingEdit, isDrawingRawXmlMode } from "../../docx/imageRawXml.js";
1
3
  import { EMPHASIS_MARK_VALUES, FIELD_TYPE_VALUES, FONT_HINT_VALUES, FONT_THEME_VALUES, HIGHLIGHT_COLOR_VALUES, IMAGE_HORIZONTAL_ALIGNMENT_VALUES, IMAGE_HORIZONTAL_RELATIVE_TO_VALUES, IMAGE_VERTICAL_ALIGNMENT_VALUES, IMAGE_VERTICAL_RELATIVE_TO_VALUES, IMAGE_WRAP_TEXT_VALUES, IMAGE_WRAP_TYPE_VALUES, LINE_SPACING_RULE_VALUES, NUMBER_FORMAT_VALUES, OUTLINE_STYLE_ATTR_VALUES, PARAGRAPH_ALIGNMENT_VALUES, POSITIONAL_TAB_ALIGNMENT_VALUES, POSITIONAL_TAB_LEADER_VALUES, POSITIONAL_TAB_RELATIVE_TO_VALUES, SDT_LOCK_VALUES, SDT_TYPE_VALUES, SHADING_PATTERN_VALUES, TABLE_CELL_TEXT_DIRECTION_VALUES, TABLE_CELL_VERTICAL_ALIGNMENT_VALUES, TABLE_JUSTIFICATION_VALUES, TABLE_ROW_HEIGHT_RULE_VALUES, TABLE_WIDTH_TYPE_VALUES, TAB_LEADER_VALUES, TAB_STOP_ALIGNMENT_VALUES, TEXT_EFFECT_VALUES, THEME_COLOR_SLOT_VALUES, UNDERLINE_STYLE_VALUES } from "../../types/documentEnumValues.js";
2
4
  import { canonicalJson } from "../../utils/canonicalJson.js";
3
5
  import { normalizeHorizontalScalePercent } from "../../utils/horizontalScale.js";
@@ -395,8 +397,18 @@ const readImageAttrs = (node) => {
395
397
  optionalNumber(attrs, "distBottom", "image.attrs.distBottom", issues);
396
398
  optionalNumber(attrs, "distLeft", "image.attrs.distLeft", issues);
397
399
  optionalNumber(attrs, "distRight", "image.attrs.distRight", issues);
400
+ optionalNumber(attrs, "opacity", "image.attrs.opacity", issues);
401
+ optionalNumber(attrs, "cropTop", "image.attrs.cropTop", issues);
402
+ optionalNumber(attrs, "cropRight", "image.attrs.cropRight", issues);
403
+ optionalNumber(attrs, "cropBottom", "image.attrs.cropBottom", issues);
404
+ optionalNumber(attrs, "cropLeft", "image.attrs.cropLeft", issues);
405
+ optionalNumber(attrs, "paddingTop", "image.attrs.paddingTop", issues);
406
+ optionalNumber(attrs, "paddingRight", "image.attrs.paddingRight", issues);
407
+ optionalNumber(attrs, "paddingBottom", "image.attrs.paddingBottom", issues);
408
+ optionalNumber(attrs, "paddingLeft", "image.attrs.paddingLeft", issues);
398
409
  optionalImagePosition(attrs, "position", "image.attrs.position", issues);
399
410
  optionalBoolean(attrs, "layoutInCell", "image.attrs.layoutInCell", issues);
411
+ optionalImageFrameLocks(attrs, "frameLocks", "image.attrs.frameLocks", issues);
400
412
  optionalNumber(attrs, "borderWidth", "image.attrs.borderWidth", issues);
401
413
  optionalString(attrs, "borderColor", "image.attrs.borderColor", issues);
402
414
  optionalString(attrs, "borderStyle", "image.attrs.borderStyle", issues);
@@ -405,10 +417,11 @@ const readImageAttrs = (node) => {
405
417
  optionalString(attrs, "hlinkRId", "image.attrs.hlinkRId", issues);
406
418
  optionalString(attrs, "_docxRawXml", "image.attrs._docxRawXml", issues);
407
419
  optionalOneOf(attrs, "_docxRawXmlMode", "image.attrs._docxRawXmlMode", issues, Object.values(DRAWING_RAW_XML_MODES));
420
+ optionalString(attrs, "_docxRawImageFingerprint", "image.attrs._docxRawImageFingerprint", issues);
408
421
  const rawXml = attrs["_docxRawXml"];
409
- if (attrs["_docxRawXmlMode"] === DRAWING_RAW_XML_MODES.PRESERVE_ONLY && (typeof rawXml !== "string" || rawXml.trim().length === 0)) issues.push({
422
+ if (isDrawingRawXmlMode(attrs["_docxRawXmlMode"]) && (typeof rawXml !== "string" || rawXml.trim().length === 0)) issues.push({
410
423
  path: "image.attrs._docxRawXml",
411
- message: "Preservation-only drawings require raw XML."
424
+ message: "Classified drawings require raw XML."
412
425
  });
413
426
  optionalBoolean(attrs, "_docxObjectPreview", "image.attrs._docxObjectPreview", issues);
414
427
  return attrsResult(attrs, issues);
@@ -821,6 +834,7 @@ const IMAGE_RESOURCE_ATTRS = /* @__PURE__ */ new Set([
821
834
  "rId",
822
835
  "_docxRawXml",
823
836
  "_docxRawXmlMode",
837
+ "_docxRawImageFingerprint",
824
838
  "_docxObjectPreview"
825
839
  ]);
826
840
  const imageAttrValuesEqual = (left, right) => {
@@ -831,7 +845,11 @@ const imageAttrValuesEqual = (left, right) => {
831
845
  const mergeImageAttrs = (node, patch) => {
832
846
  const current = expectImageAttrs(node);
833
847
  const merged = mergeNodeAttrs(node, readImageAttrs, "image attrs", patch);
834
- if (!Object.entries(patch).some(([key, value]) => !IMAGE_RESOURCE_ATTRS.has(key) && !imageAttrValuesEqual(Reflect.get(current, key), value)) || current._docxRawXml === void 0 || current._docxRawXmlMode === DRAWING_RAW_XML_MODES.PRESERVE_ONLY) return merged;
848
+ if (!Object.entries(patch).some(([key, value]) => !IMAGE_RESOURCE_ATTRS.has(key) && !imageAttrValuesEqual(Reflect.get(current, key), value)) || current._docxRawXml === void 0) return merged;
849
+ if (!allowsDirectDrawingEdit(current._docxRawXmlMode)) {
850
+ const { _docxRawImageFingerprint: _discardedFingerprint, ...editedAttrs } = merged;
851
+ return editedAttrs;
852
+ }
835
853
  const { _docxRawXml: _discardedRawXml, ...editableAttrs } = merged;
836
854
  return editableAttrs;
837
855
  };
@@ -1769,6 +1787,18 @@ const validatePropertyChangeInfo = (value, path, issues) => {
1769
1787
  optionalOneOf(value, "provenance", `${path}.provenance`, issues, TRACKED_CHANGE_PROVENANCE_VALUES);
1770
1788
  optionalString(value, "suggestionId", `${path}.suggestionId`, issues);
1771
1789
  };
1790
+ const optionalImageFrameLocks = (attrs, key, path, issues) => {
1791
+ const value = attrs[key];
1792
+ if (value === void 0 || value === null) return;
1793
+ if (!isRecord(value)) {
1794
+ issues.push({
1795
+ path,
1796
+ message: "Expected an object."
1797
+ });
1798
+ return;
1799
+ }
1800
+ for (const lock of GRAPHIC_FRAME_LOCK_KEYS) optionalBoolean(value, lock, `${path}.${lock}`, issues);
1801
+ };
1772
1802
  const optionalImagePosition = (attrs, key, path, issues) => {
1773
1803
  const value = attrs[key];
1774
1804
  if (value === void 0 || value === null) return;
@@ -5,6 +5,14 @@ import { Mark, Node } from "prosemirror-model";
5
5
  //#region src/prosemirror/conversion/fromProseDoc.d.ts
6
6
  /** Convert a ProseMirror document to the document model. */
7
7
  declare function fromProseDoc(pmDoc: Node, baseDocument?: document_d_exports.Document): document_d_exports.Document;
8
+ /**
9
+ * What a field carrying no stored result reports.
10
+ *
11
+ * `serializerFallback` writes the visible page number a save has always given a
12
+ * result-less PAGE/NUMPAGES field; `authored` reports the result the document
13
+ * actually holds, so a text read of a story reads the same loaded and unloaded.
14
+ */
15
+ type EmptyFieldResultMode = "serializerFallback" | "authored";
8
16
  /**
9
17
  * Convert ProseMirror marks to TextFormatting
10
18
  */
@@ -28,6 +36,9 @@ declare function tableCellAttrsToFormatting(attrs: TableCellAttrs): document_d_e
28
36
  * Preserves all non-content parts of the original document
29
37
  */
30
38
  declare function updateDocumentContent(originalDocument: document_d_exports.Document, pmDoc: Node): document_d_exports.Document;
39
+ type ProseDocToBlocksOptions = {
40
+ emptyFieldResult?: EmptyFieldResultMode;
41
+ };
31
42
  /**
32
43
  * Convert a ProseMirror document back to an array of `BlockContent` blocks
33
44
  * (paragraphs, tables, and block-level content controls).
@@ -35,6 +46,6 @@ declare function updateDocumentContent(originalDocument: document_d_exports.Docu
35
46
  * Used for converting edited header/footer PM content back to the document
36
47
  * model.
37
48
  */
38
- declare function proseDocToBlocks(pmDoc: Node, baseContent?: document_d_exports.BlockContent[], styles?: NonNullable<document_d_exports.Document["package"]>["styles"]): document_d_exports.BlockContent[];
49
+ declare function proseDocToBlocks(pmDoc: Node, baseContent?: document_d_exports.BlockContent[], styles?: NonNullable<document_d_exports.Document["package"]>["styles"], options?: ProseDocToBlocksOptions): document_d_exports.BlockContent[];
39
50
  //#endregion
40
- export { fromProseDoc, marksToTextFormatting, proseDocToBlocks, standaloneTableCellFromProseMirror, tableAttrsToFormatting, tableCellAttrsToFormatting, tableRowAttrsToFormatting, updateDocumentContent };
51
+ export { EmptyFieldResultMode, ProseDocToBlocksOptions, fromProseDoc, marksToTextFormatting, proseDocToBlocks, standaloneTableCellFromProseMirror, tableAttrsToFormatting, tableCellAttrsToFormatting, tableRowAttrsToFormatting, updateDocumentContent };
@@ -1,4 +1,4 @@
1
- import { imageRawXmlFingerprint } from "../../docx/imageRawXml.js";
1
+ import { EDITED_PREVIEW_FINGERPRINT, imageRawXmlFingerprint } from "../../docx/imageRawXml.js";
2
2
  import { PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR, ParagraphPropertySourceValidationError, copyDocumentParagraphPropertySourceContract, copyDocumentParagraphPropertySources, copyParagraphPropertyCapture, copyParagraphPropertySource, decodeTableCellParagraphSourcePayload, getDocumentParagraphPropertySourceContract, getParagraphPropertySource, getParagraphPropertySourceCandidate, getParagraphPropertySourceToken, getParagraphPropertySourceTransferId, getProseDocumentParagraphPropertySourceContract, getProseParagraphPropertySourceToken, isParagraphPropertySourceToken, linkParagraphPropertySourceCandidate, paragraphPropertySourceBelongsToDocument, paragraphPropertySourceTokenMatchesContract, recreateProseNodeWithParagraphPropertySource, restoreTableCellsWithParagraphPropertySources, visitDocumentStoryParagraphs, visitTableCellParagraphPropertySourceBindings } from "../../docx/paragraphPropertySource.js";
3
3
  import { visitDocxParagraphs } from "../../docx/paragraphTraversal.js";
4
4
  import { ShapeOutlineStyleSchema, narrowEnum } from "../../docx/parserEnums.js";
@@ -429,9 +429,42 @@ function materializeNumberedRefValues(doc) {
429
429
  };
430
430
  return visit(doc);
431
431
  }
432
- function extractBlocks(inputDoc, refResolution = "resolve", styleResolver = null) {
432
+ const SERIALIZER_EMPTY_PAGE_FIELD_RESULT = "1";
433
+ const fieldResultHasExplicitPageBreak = (node) => extractParagraphContent(node, void 0, void 0, /* @__PURE__ */ new Map(), false).filter((content) => content.type === "run" || content.type === "hyperlink").some((content) => {
434
+ return (content.type === "run" ? [content] : content.children).some((run) => run.type === "run" && run.content.some((runContent) => runContent.type === "break" && runContent.breakType === "page"));
435
+ });
436
+ /**
437
+ * PAGE and NUMPAGES have a stable visible fallback. Other empty fields may
438
+ * intentionally have no result (for example an empty TOC); inventing a space
439
+ * changes their authored result on every save/reopen cycle. A field whose
440
+ * result already carries an explicit page break owns that break's run, so the
441
+ * fallback would displace it.
442
+ */
443
+ function materializeSerializerFieldFallbacks(doc) {
444
+ const visit = (node) => {
445
+ if (node.type.name === "field" || node.type.name === "structuredField") {
446
+ const attrs = expectFieldAttrs(node);
447
+ return (attrs.fieldType === "PAGE" || attrs.fieldType === "NUMPAGES") && !attrs.displayText && !fieldResultHasExplicitPageBreak(node) ? recreateProseNodeWithParagraphPropertySource(node, { attrs: {
448
+ ...node.attrs,
449
+ displayText: SERIALIZER_EMPTY_PAGE_FIELD_RESULT
450
+ } }) : node;
451
+ }
452
+ if (node.childCount === 0) return node;
453
+ const children = [];
454
+ let changed = false;
455
+ node.forEach((child) => {
456
+ const mappedChild = visit(child);
457
+ children.push(mappedChild);
458
+ changed ||= mappedChild !== child;
459
+ });
460
+ return changed ? recreateProseNodeWithParagraphPropertySource(node, { content: Fragment.fromArray(children) }) : node;
461
+ };
462
+ return visit(doc);
463
+ }
464
+ function extractBlocks(inputDoc, refResolution = "resolve", styleResolver = null, emptyFieldResult = "serializerFallback") {
433
465
  const strippedDoc = stripSuggestedProvenance(inputDoc, styleResolver);
434
- const pmDoc = refResolution === "resolve" ? materializeNumberedRefValues(strippedDoc) : strippedDoc;
466
+ const refResolvedDoc = refResolution === "resolve" ? materializeNumberedRefValues(strippedDoc) : strippedDoc;
467
+ const pmDoc = emptyFieldResult === "serializerFallback" ? materializeSerializerFieldFallbacks(refResolvedDoc) : refResolvedDoc;
435
468
  const blocks = [];
436
469
  const textBoxAnchorMarkers = /* @__PURE__ */ new Map();
437
470
  const documentCounts = buildDocumentTrackedChangeCounts(pmDoc);
@@ -474,7 +507,7 @@ function extractBlocks(inputDoc, refResolution = "resolve", styleResolver = null
474
507
  pendingPageBreaks = 0;
475
508
  } else if (node.type.name === "blockSdt") {
476
509
  if (pendingPageBreaks > 0 && !appendPendingPageBreaksToPreviousParagraph()) flushPendingPageBreaks();
477
- blocks.push(convertPMBlockSdt(node, styleResolver));
510
+ blocks.push(convertPMBlockSdt(node, styleResolver, emptyFieldResult));
478
511
  previousStandaloneTextBox = null;
479
512
  }
480
513
  });
@@ -482,7 +515,7 @@ function extractBlocks(inputDoc, refResolution = "resolve", styleResolver = null
482
515
  removeUnresolvedTextBoxAnchors(blocks, textBoxAnchorMarkers);
483
516
  return blocks;
484
517
  }
485
- function convertPMBlockSdt(node, styleResolver) {
518
+ function convertPMBlockSdt(node, styleResolver, emptyFieldResult) {
486
519
  const attrs = expectBlockSdtAttrs(node);
487
520
  const properties = { sdtType: attrs.sdtType };
488
521
  if (attrs.alias) properties.alias = attrs.alias;
@@ -500,7 +533,7 @@ function convertPMBlockSdt(node, styleResolver) {
500
533
  if (attrs.rawEndPropertiesXml) properties.rawEndPropertiesXml = attrs.rawEndPropertiesXml;
501
534
  if (attrs.rawSdtChildrenBeforeContent) properties.rawSdtChildrenBeforeContent = attrs.rawSdtChildrenBeforeContent;
502
535
  if (attrs.rawSdtChildrenAfterContent) properties.rawSdtChildrenAfterContent = attrs.rawSdtChildrenAfterContent;
503
- const extracted = extractBlocks(node.type.schema.node("doc", null, node.content), "inherit", styleResolver);
536
+ const extracted = extractBlocks(node.type.schema.node("doc", null, node.content), "inherit", styleResolver, emptyFieldResult);
504
537
  return {
505
538
  type: "blockSdt",
506
539
  properties,
@@ -1691,18 +1724,7 @@ function createFieldFromNode(node, { baseParagraphFormatting, inheritedFormattin
1691
1724
  paragraphMarkPrecedesStyle: paragraphMarkPrecedesStyle ?? false,
1692
1725
  styleResolver: styleResolver ?? null
1693
1726
  }).filter((content) => content.type === "run" || content.type === "hyperlink");
1694
- const hasExplicitPageBreak = extractedContent.some((content) => {
1695
- return (content.type === "run" ? [content] : content.children).some((run) => run.type === "run" && run.content.some((runContent) => runContent.type === "break" && runContent.breakType === "page"));
1696
- });
1697
- let displayText = attrs.displayText ?? "";
1698
- if (!displayText && !hasExplicitPageBreak) switch (attrs.fieldType) {
1699
- case "PAGE":
1700
- displayText = "1";
1701
- break;
1702
- case "NUMPAGES":
1703
- displayText = "1";
1704
- break;
1705
- }
1727
+ const displayText = attrs.displayText ?? "";
1706
1728
  const displayRun = {
1707
1729
  type: "run",
1708
1730
  content: [{
@@ -1822,6 +1844,7 @@ function createImageRun(node) {
1822
1844
  const imagePosition = imagePositionFromAttrs(attrs.position);
1823
1845
  if (imagePosition) image.position = imagePosition;
1824
1846
  if (attrs.layoutInCell !== void 0) image.layoutInCell = attrs.layoutInCell;
1847
+ if (attrs.frameLocks !== void 0) image.frameLocks = { ...attrs.frameLocks };
1825
1848
  if (attrs.borderWidth && attrs.borderWidth > 0) {
1826
1849
  const outline = {
1827
1850
  width: pixelsToEmu(attrs.borderWidth),
@@ -1853,21 +1876,51 @@ function createImageRun(node) {
1853
1876
  if (cropLeft !== void 0) crop.left = cropLeft;
1854
1877
  image.crop = crop;
1855
1878
  }
1879
+ const { paddingTop, paddingRight, paddingBottom, paddingLeft } = attrs;
1880
+ if (paddingTop !== void 0 || paddingRight !== void 0 || paddingBottom !== void 0 || paddingLeft !== void 0) {
1881
+ const padding = {};
1882
+ if (paddingTop !== void 0) padding.top = paddingTop;
1883
+ if (paddingRight !== void 0) padding.right = paddingRight;
1884
+ if (paddingBottom !== void 0) padding.bottom = paddingBottom;
1885
+ if (paddingLeft !== void 0) padding.left = paddingLeft;
1886
+ image.padding = padding;
1887
+ }
1856
1888
  return {
1857
1889
  type: "run",
1858
- content: [attrs._docxRawXmlMode === DRAWING_RAW_XML_MODES.PRESERVE_ONLY ? {
1890
+ content: [drawingFromImageAttrs(image, attrs)]
1891
+ };
1892
+ }
1893
+ /**
1894
+ * Rebuild the `DrawingContent` union member the image node came from.
1895
+ *
1896
+ * Each branch is listed in full rather than spread over a base object, so a
1897
+ * field from one mode cannot leak into another.
1898
+ */
1899
+ const drawingFromImageAttrs = (image, attrs) => {
1900
+ const mode = attrs._docxRawXmlMode;
1901
+ switch (mode) {
1902
+ case void 0: return {
1903
+ type: "drawing",
1904
+ image,
1905
+ ...attrs._docxRawXml ? { rawXml: attrs._docxRawXml } : {},
1906
+ ...attrs._docxRawXml ? { rawImageFingerprint: imageRawXmlFingerprint(image) } : {}
1907
+ };
1908
+ case DRAWING_RAW_XML_MODES.PRESERVE_ONLY: return {
1859
1909
  type: "drawing",
1860
1910
  image,
1861
1911
  rawXml: attrs._docxRawXml ?? panic("Preservation-only ProseMirror image attrs must include raw XML."),
1862
1912
  rawXmlMode: DRAWING_RAW_XML_MODES.PRESERVE_ONLY
1863
- } : {
1913
+ };
1914
+ case DRAWING_RAW_XML_MODES.PREVIEW_ONLY: return {
1864
1915
  type: "drawing",
1865
1916
  image,
1866
- ...attrs._docxRawXml ? { rawXml: attrs._docxRawXml } : {},
1867
- ...attrs._docxRawXml ? { rawImageFingerprint: imageRawXmlFingerprint(image) } : {}
1868
- }]
1869
- };
1870
- }
1917
+ rawXml: attrs._docxRawXml ?? panic("Preview-only ProseMirror image attrs must include raw XML."),
1918
+ rawImageFingerprint: attrs._docxRawImageFingerprint === void 0 ? EDITED_PREVIEW_FINGERPRINT : imageRawXmlFingerprint(image),
1919
+ rawXmlMode: DRAWING_RAW_XML_MODES.PREVIEW_ONLY
1920
+ };
1921
+ default: return mode;
1922
+ }
1923
+ };
1871
1924
  /**
1872
1925
  * Create a Run from a ProseMirror shape node
1873
1926
  */
@@ -2973,8 +3026,8 @@ function updateDocumentContent(originalDocument, pmDoc) {
2973
3026
  * Used for converting edited header/footer PM content back to the document
2974
3027
  * model.
2975
3028
  */
2976
- function proseDocToBlocks(pmDoc, baseContent, styles) {
2977
- const blocks = extractBlocks(pmDoc, "resolve", styles ? createStyleEngine(styles) : null);
3029
+ function proseDocToBlocks(pmDoc, baseContent, styles, options) {
3030
+ const blocks = extractBlocks(pmDoc, "resolve", styles ? createStyleEngine(styles) : null, options?.emptyFieldResult ?? "serializerFallback");
2978
3031
  const linkedSources = restoreLinkedParagraphPropertySources(blocks);
2979
3032
  if (baseContent) restoreParagraphPropertySources(blocks, baseContent, linkedSources.targets, linkedSources.sources);
2980
3033
  return blocks;
@@ -1,4 +1,4 @@
1
- import { fromProseDoc, proseDocToBlocks, updateDocumentContent } from "./fromProseDoc.js";
1
+ import { EmptyFieldResultMode, ProseDocToBlocksOptions, fromProseDoc, proseDocToBlocks, updateDocumentContent } from "./fromProseDoc.js";
2
2
  import { ToProseDocOptions, createEmptyDoc, headerFooterToProseDoc, toProseDoc } from "./toProseDoc.js";
3
3
  import { ProseMirrorDocumentValidationIssue, ValidateProseMirrorDocumentResult, assertValidProseMirrorDocument, formatProseMirrorDocumentIssues, validateProseMirrorDocument } from "../validation.js";
4
- export { type ProseMirrorDocumentValidationIssue, type ToProseDocOptions, type ValidateProseMirrorDocumentResult, assertValidProseMirrorDocument, createEmptyDoc, formatProseMirrorDocumentIssues, fromProseDoc, headerFooterToProseDoc, proseDocToBlocks, toProseDoc, updateDocumentContent, validateProseMirrorDocument };
4
+ export { type EmptyFieldResultMode, type ProseDocToBlocksOptions, type ProseMirrorDocumentValidationIssue, type ToProseDocOptions, type ValidateProseMirrorDocumentResult, assertValidProseMirrorDocument, createEmptyDoc, formatProseMirrorDocumentIssues, fromProseDoc, headerFooterToProseDoc, proseDocToBlocks, toProseDoc, updateDocumentContent, validateProseMirrorDocument };
@@ -24,6 +24,7 @@ import { resolveEffectiveTableCellFormatting } from "./effectiveTableCellFormatt
24
24
  import { shadingToRunShadingAttrs } from "./runShadingMark.js";
25
25
  import { sdtAttrsFromProperties } from "./sdtAttrs.js";
26
26
  import { TaggedError, panic } from "better-result";
27
+ import { DRAWING_RAW_XML_MODES } from "@stll/docx-core/model";
27
28
  //#region src/prosemirror/conversion/toProseDoc.ts
28
29
  /** DOCX content that cannot be preserved by the editable ProseMirror model. */
29
30
  var UnsupportedDocxToProseMirrorConversionError = class extends TaggedError("UnsupportedDocxToProseMirrorConversionError") {};
@@ -1588,7 +1589,8 @@ function convertRunContent(content, marks, formatting, textBoxAnchors) {
1588
1589
  case "drawing": return [withRunBoundaryMarks(convertImage({
1589
1590
  image: content.image,
1590
1591
  rawXml: content.rawXml,
1591
- rawXmlMode: content.rawXmlMode
1592
+ rawXmlMode: content.rawXmlMode,
1593
+ rawImageFingerprint: content.rawXmlMode === DRAWING_RAW_XML_MODES.PRESERVE_ONLY ? void 0 : content.rawImageFingerprint
1592
1594
  }), marks)];
1593
1595
  case "shape": {
1594
1596
  const shp = content.shape;
@@ -1628,7 +1630,7 @@ function withRunBoundaryMarks(node, marks) {
1628
1630
  if (!marks.some(({ type }) => type.name === "hyperlink" || type.name === "pageBreakRunOwner")) return node;
1629
1631
  return node.mark(marks);
1630
1632
  }
1631
- function convertImage({ image, rawXml, rawXmlMode }) {
1633
+ function convertImage({ image, rawXml, rawXmlMode, rawImageFingerprint }) {
1632
1634
  const imageSize = image.size;
1633
1635
  const widthPx = imageSize?.width ? emuToPixels(imageSize.width) : void 0;
1634
1636
  const heightPx = imageSize?.height ? emuToPixels(imageSize.height) : void 0;
@@ -1720,8 +1722,13 @@ function convertImage({ image, rawXml, rawXmlMode }) {
1720
1722
  cropRight: image.crop?.right,
1721
1723
  cropBottom: image.crop?.bottom,
1722
1724
  cropLeft: image.crop?.left,
1725
+ paddingTop: image.padding?.top,
1726
+ paddingRight: image.padding?.right,
1727
+ paddingBottom: image.padding?.bottom,
1728
+ paddingLeft: image.padding?.left,
1723
1729
  position,
1724
1730
  layoutInCell: image.layoutInCell,
1731
+ frameLocks: image.frameLocks ? { ...image.frameLocks } : void 0,
1725
1732
  borderWidth,
1726
1733
  borderColor,
1727
1734
  borderStyle,
@@ -1730,6 +1737,7 @@ function convertImage({ image, rawXml, rawXmlMode }) {
1730
1737
  hlinkRId: image.hlinkRId,
1731
1738
  _docxRawXml: rawXml,
1732
1739
  _docxRawXmlMode: rawXmlMode,
1740
+ _docxRawImageFingerprint: rawImageFingerprint,
1733
1741
  _docxObjectPreview: rawXml !== void 0 && /<(?:[A-Za-z_][\w.-]*:)?object(?:\s|>)/u.test(rawXml)
1734
1742
  });
1735
1743
  }
@@ -33,8 +33,13 @@ const ImageExtension = createNodeExtension({
33
33
  cropRight: { default: null },
34
34
  cropBottom: { default: null },
35
35
  cropLeft: { default: null },
36
+ paddingTop: { default: null },
37
+ paddingRight: { default: null },
38
+ paddingBottom: { default: null },
39
+ paddingLeft: { default: null },
36
40
  position: { default: null },
37
41
  layoutInCell: { default: null },
42
+ frameLocks: { default: null },
38
43
  borderWidth: { default: null },
39
44
  borderColor: { default: null },
40
45
  borderStyle: { default: null },
@@ -43,6 +48,7 @@ const ImageExtension = createNodeExtension({
43
48
  hlinkRId: { default: null },
44
49
  _docxRawXml: { default: null },
45
50
  _docxRawXmlMode: { default: null },
51
+ _docxRawImageFingerprint: { default: null },
46
52
  _docxObjectPreview: { default: null }
47
53
  },
48
54
  parseDOM: [{