@stll/folio-core 0.12.0 → 0.13.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.
- package/dist/ai-edits/apply.d.ts +2 -1
- package/dist/ai-edits/apply.js +118 -40
- package/dist/ai-edits/blockRange.js +1 -1
- package/dist/ai-edits/clean-text.js +2 -1
- package/dist/ai-edits/headless.js +3 -3
- package/dist/ai-edits/snapshot.js +21 -3
- package/dist/ai-edits/table-cell-mutations.js +2 -1
- package/dist/ai-edits/table-row-column-mutations.d.ts +9 -1
- package/dist/ai-edits/table-row-column-mutations.js +1 -1
- package/dist/ai-edits/types.d.ts +28 -1
- package/dist/ai-edits/word-diff.js +21 -0
- package/dist/compat/eigenpal.d.ts +3 -3
- package/dist/compat/eigenpal.js +3 -3
- package/dist/controller/headerFooterEditorManager.js +1 -1
- package/dist/controller/hiddenEditorManager.js +12 -1
- package/dist/controller/layoutPipeline.js +1 -1
- package/dist/controller/noteEditorManager.js +1 -1
- package/dist/document-operations.d.ts +13 -13
- package/dist/document-operations.js +31 -21
- package/dist/docx/blockContentParser.js +19 -5
- package/dist/docx/commentRangeIntegrity.d.ts +6 -0
- package/dist/docx/commentRangeIntegrity.js +186 -0
- package/dist/docx/conformance.js +57 -3
- package/dist/docx/encryption/agileDecryption.js +12 -2
- package/dist/docx/encryption/compoundFile.js +6 -1
- package/dist/docx/encryption/encryptionInfo.d.ts +9 -1
- package/dist/docx/encryption/encryptionInfo.js +18 -2
- package/dist/docx/encryption/openEncryptedDocx.js +1 -1
- package/dist/docx/ensureParaIds.js +7 -6
- package/dist/docx/groupDrawingParser.js +10 -5
- package/dist/docx/hyperlinkParser.js +1 -1
- package/dist/docx/imageParser.js +10 -6
- package/dist/docx/metadataPrivacy.js +17 -3
- package/dist/docx/paragraphParser.js +7 -3
- package/dist/docx/rezip.js +19 -8
- package/dist/docx/runParser.js +13 -4
- package/dist/docx/sectionParser.js +17 -4
- package/dist/docx/selectiveSave.js +4 -0
- package/dist/docx/selectiveXmlPatch.d.ts +22 -1
- package/dist/docx/selectiveXmlPatch.js +61 -1
- package/dist/docx/serializer/blockSdtSerializer.js +3 -2
- package/dist/docx/serializer/commentSerializer.js +4 -4
- package/dist/docx/serializer/paragraphSerializer.js +16 -14
- package/dist/docx/serializer/runSerializer.js +21 -19
- package/dist/docx/serializer/sectionPropertiesSerializer.js +2 -1
- package/dist/docx/serializer/tableSerializer.js +9 -7
- package/dist/docx/serializer/xmlUtils.d.ts +13 -1
- package/dist/docx/serializer/xmlUtils.js +27 -1
- package/dist/docx/server/extractDocxText.js +55 -8
- package/dist/docx/settingsParser.js +15 -2
- package/dist/docx/tableParser.d.ts +8 -1
- package/dist/docx/tableParser.js +13 -3
- package/dist/docx/unzip.js +1 -0
- package/dist/docx/vmlImageParser.js +11 -3
- package/dist/docx/xmlParser.js +11 -1
- package/dist/fonts/embeddedFonts.d.ts +33 -5
- package/dist/fonts/embeddedFonts.js +37 -8
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/layout-bridge/convert/toFlowBlocks.js +15 -0
- package/dist/layout-engine/index.js +16 -3
- package/dist/layout-engine/measure/cache.js +2 -0
- package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +8 -2
- package/dist/layout-engine/measure/lineBreakProvider.d.ts +2 -2
- package/dist/layout-engine/measure/lineBreakProvider.js +3 -3
- package/dist/layout-engine/measure/measureBlocks.js +9 -1
- package/dist/layout-engine/measure/measureHelpers.d.ts +10 -1
- package/dist/layout-engine/measure/measureHelpers.js +12 -1
- package/dist/layout-engine/measure/measureParagraph.js +16 -0
- package/dist/layout-engine/types.d.ts +22 -2
- package/dist/layout-painter/renderImage.d.ts +12 -1
- package/dist/layout-painter/renderImage.js +21 -4
- package/dist/layout-painter/renderPage.js +3 -2
- package/dist/layout-painter/renderParagraph.js +33 -9
- package/dist/layout-painter/renderTable.js +2 -1
- package/dist/layout-painter/renderWatermark.d.ts +1 -1
- package/dist/layout-painter/renderWatermark.js +2 -1
- package/dist/markdown/fromMarkdown.js +1 -1
- package/dist/prosemirror/attrs/index.d.ts +1 -1
- package/dist/prosemirror/attrs/index.js +21 -0
- package/dist/prosemirror/commands/comments.d.ts +76 -1
- package/dist/prosemirror/commands/comments.js +401 -28
- package/dist/prosemirror/commands/hyperlink.js +15 -1
- package/dist/prosemirror/commands/image.js +7 -1
- package/dist/prosemirror/commands/index.d.ts +2 -2
- package/dist/prosemirror/commands/index.js +2 -2
- package/dist/prosemirror/commands/tableCellMergeResolution.js +1 -1
- package/dist/prosemirror/commentIdAllocator.js +13 -3
- package/dist/prosemirror/conversion/fromProseDoc.js +122 -5
- package/dist/prosemirror/conversion/index.js +1 -1
- package/dist/prosemirror/conversion/toProseDoc.js +20 -8
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -1
- package/dist/prosemirror/extensions/features/ImagePasteExtension.js +10 -2
- package/dist/prosemirror/extensions/features/pasteCleanup.js +15 -2
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +30 -4
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +49 -13
- package/dist/prosemirror/extensions/nodes/TableExtension.d.ts +1 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.js +5 -3
- package/dist/prosemirror/index.d.ts +1 -1
- package/dist/prosemirror/index.js +1 -1
- package/dist/prosemirror/plugins/contentControlWidgets.d.ts +2 -2
- package/dist/prosemirror/plugins/contentControlWidgets.js +4 -4
- package/dist/prosemirror/plugins/revisionIds.d.ts +23 -0
- package/dist/prosemirror/plugins/revisionIds.js +57 -0
- package/dist/prosemirror/plugins/suggestionMode.js +4 -3
- package/dist/prosemirror/runFormattingMarkNames.d.ts +14 -0
- package/dist/prosemirror/runFormattingMarkNames.js +40 -0
- package/dist/prosemirror/schema/index.d.ts +1 -1
- package/dist/prosemirror/schema/marks.d.ts +27 -4
- package/dist/prosemirror/schema/marks.js +15 -0
- package/dist/prosemirror/schema/nodes.d.ts +50 -2
- package/dist/render-dom/RemoteSelectionOverlay.js +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +3 -3
- package/dist/utils/clipboard.d.ts +10 -1
- package/dist/utils/clipboard.js +33 -6
- package/dist/utils/colorResolver.d.ts +8 -1
- package/dist/utils/colorResolver.js +35 -5
- package/dist/utils/fontResolver.d.ts +2 -1
- package/dist/utils/fontResolver.js +9 -3
- package/dist/utils/hexId.d.ts +11 -1
- package/dist/utils/hexId.js +11 -1
- package/dist/utils/sanitizeImageSrc.d.ts +21 -0
- package/dist/utils/sanitizeImageSrc.js +30 -0
- package/dist/utils/stripXmlDeclarations.d.ts +11 -0
- package/dist/utils/stripXmlDeclarations.js +35 -0
- package/dist/version-comparison.d.ts +22 -2
- package/dist/version-comparison.js +41 -12
- package/dist/watermark/index.d.ts +11 -1
- package/dist/watermark/index.js +22 -1
- package/package.json +3 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseRelationships, resolveRelativePath } from "../docx/relsParser.js";
|
|
2
|
+
import { generateHexId } from "../utils/hexId.js";
|
|
2
3
|
import { unzipDocx } from "../docx/unzip.js";
|
|
3
4
|
import { deobfuscateFont, isValidFontKey } from "./fontDeobfuscation.js";
|
|
4
5
|
import { parseEmbeddedFontTable } from "./embeddedFontTable.js";
|
|
@@ -19,6 +20,16 @@ import { parseEmbeddedFontTable } from "./embeddedFontTable.js";
|
|
|
19
20
|
*
|
|
20
21
|
* Ported from eigenpal/docx-editor (see NOTICE.md).
|
|
21
22
|
*/
|
|
23
|
+
const SCOPED_FAMILY_PREFIX = "folio-embedded";
|
|
24
|
+
/**
|
|
25
|
+
* Build the per-document scoped family name an embedded face registers
|
|
26
|
+
* under. `docNonce` is fresh per document load (see {@link getEmbeddedFontFaces}),
|
|
27
|
+
* so the same original name in two different documents (or two loads of the
|
|
28
|
+
* same document) never collides.
|
|
29
|
+
*/
|
|
30
|
+
function scopeEmbeddedFontFamily(docNonce, originalFamily) {
|
|
31
|
+
return `${SCOPED_FAMILY_PREFIX}-${docNonce}-${originalFamily}`;
|
|
32
|
+
}
|
|
22
33
|
/** Pairs each embed field with the CSS weight/style it renders as. */
|
|
23
34
|
const EMBED_KINDS = [
|
|
24
35
|
{
|
|
@@ -57,7 +68,7 @@ function lookupFontData(resolvedPath, fonts) {
|
|
|
57
68
|
const wanted = normalizePackagePath(resolvedPath);
|
|
58
69
|
for (const [path, data] of fonts) if (normalizePackagePath(path) === wanted) return data;
|
|
59
70
|
}
|
|
60
|
-
function resolveFace(
|
|
71
|
+
function resolveFace(originalFamily, ref, kind, fonts, rels, docNonce) {
|
|
61
72
|
const target = rels.get(ref.relId)?.target;
|
|
62
73
|
if (!target) return null;
|
|
63
74
|
const raw = lookupFontData(resolveRelativePath(FONTTABLE_RELS_BASE_PATH, target), fonts);
|
|
@@ -65,7 +76,8 @@ function resolveFace(family, ref, kind, fonts, rels) {
|
|
|
65
76
|
const bytes = new Uint8Array(raw);
|
|
66
77
|
const data = ref.fontKey && isValidFontKey(ref.fontKey) ? deobfuscateFont(bytes, ref.fontKey) : bytes;
|
|
67
78
|
return {
|
|
68
|
-
family,
|
|
79
|
+
family: scopeEmbeddedFontFamily(docNonce, originalFamily),
|
|
80
|
+
originalFamily,
|
|
69
81
|
style: kind.style,
|
|
70
82
|
weight: kind.weight,
|
|
71
83
|
bytes: data,
|
|
@@ -76,8 +88,13 @@ function resolveFace(family, ref, kind, fonts, rels) {
|
|
|
76
88
|
* Resolve and de-obfuscate every embedded font face declared in a font table.
|
|
77
89
|
* Pure and DOM-free. Faces whose relationship or binary is missing are skipped,
|
|
78
90
|
* so the result only contains faces that produced usable bytes.
|
|
91
|
+
*
|
|
92
|
+
* `docNonce` scopes every face's registered family to one document load (see
|
|
93
|
+
* {@link scopeEmbeddedFontFamily}); defaults to a fresh random id so callers
|
|
94
|
+
* that don't care about a specific value still get per-call isolation. Pass
|
|
95
|
+
* an explicit value for deterministic tests.
|
|
79
96
|
*/
|
|
80
|
-
function getEmbeddedFontFaces(parts) {
|
|
97
|
+
function getEmbeddedFontFaces(parts, docNonce = generateHexId()) {
|
|
81
98
|
const entries = parseEmbeddedFontTable(parts.fontTableXml);
|
|
82
99
|
if (entries.length === 0) return [];
|
|
83
100
|
if (!parts.fontTableRelsXml || parts.fonts.size === 0) return [];
|
|
@@ -86,11 +103,23 @@ function getEmbeddedFontFaces(parts) {
|
|
|
86
103
|
for (const entry of entries) for (const kind of EMBED_KINDS) {
|
|
87
104
|
const ref = entry[kind.ref];
|
|
88
105
|
if (!ref) continue;
|
|
89
|
-
const face = resolveFace(entry.name, ref, kind, parts.fonts, rels);
|
|
106
|
+
const face = resolveFace(entry.name, ref, kind, parts.fonts, rels, docNonce);
|
|
90
107
|
if (face) faces.push(face);
|
|
91
108
|
}
|
|
92
109
|
return faces;
|
|
93
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Build the original DOCX font name → scoped registered family map for a set
|
|
113
|
+
* of resolved embedded faces. Callers thread this into font resolution (see
|
|
114
|
+
* `utils/fontResolver.ts`'s `setEmbeddedFontFamilyMap`) so the document's own
|
|
115
|
+
* runs — which still carry the original name — keep resolving to the scoped
|
|
116
|
+
* face that was actually registered on `document.fonts`.
|
|
117
|
+
*/
|
|
118
|
+
function buildEmbeddedFontFamilyMap(faces) {
|
|
119
|
+
const map = /* @__PURE__ */ new Map();
|
|
120
|
+
for (const face of faces) map.set(face.originalFamily, face.family);
|
|
121
|
+
return map;
|
|
122
|
+
}
|
|
94
123
|
function findFontTableRels(allXml) {
|
|
95
124
|
for (const [path, xml] of allXml) if (normalizePackagePath(path) === FONTTABLE_RELS_PATH) return xml;
|
|
96
125
|
}
|
|
@@ -98,15 +127,15 @@ function findFontTableRels(allXml) {
|
|
|
98
127
|
* Extract every embedded font face from a raw `.docx` buffer. Unzips the
|
|
99
128
|
* package, then resolves + de-obfuscates the faces via
|
|
100
129
|
* {@link getEmbeddedFontFaces}. Returns an empty list for documents with no
|
|
101
|
-
* embedded fonts.
|
|
130
|
+
* embedded fonts. See {@link getEmbeddedFontFaces} for `docNonce`.
|
|
102
131
|
*/
|
|
103
|
-
async function extractEmbeddedFonts(buffer) {
|
|
132
|
+
async function extractEmbeddedFonts(buffer, docNonce = generateHexId()) {
|
|
104
133
|
const raw = await unzipDocx(buffer);
|
|
105
134
|
return getEmbeddedFontFaces({
|
|
106
135
|
fontTableXml: raw.fontTableXml,
|
|
107
136
|
fontTableRelsXml: findFontTableRels(raw.allXml),
|
|
108
137
|
fonts: raw.fonts
|
|
109
|
-
});
|
|
138
|
+
}, docNonce);
|
|
110
139
|
}
|
|
111
140
|
//#endregion
|
|
112
|
-
export { extractEmbeddedFonts, getEmbeddedFontFaces };
|
|
141
|
+
export { buildEmbeddedFontFamilyMap, extractEmbeddedFonts, getEmbeddedFontFaces, scopeEmbeddedFontFamily };
|
package/dist/index.d.ts
CHANGED
|
@@ -27,9 +27,9 @@ import { AcceptAutocompleteResult, AutocompleteSuggestionPluginOptions, Autocomp
|
|
|
27
27
|
import { ImageMeta, ImageRef, MarkdownOptions, MarkdownResult } from "./markdown/types.js";
|
|
28
28
|
import { fromMarkdown } from "./markdown/fromMarkdown.js";
|
|
29
29
|
import { toMarkdown, toMarkdownResult } from "./markdown/index.js";
|
|
30
|
-
import { EmbeddedFont, EmbeddedFontParts, extractEmbeddedFonts, getEmbeddedFontFaces } from "./fonts/embeddedFonts.js";
|
|
31
|
-
import { getGoogleFontsEnabled, setGoogleFontsEnabled } from "./utils/fontResolver.js";
|
|
30
|
+
import { EmbeddedFont, EmbeddedFontParts, buildEmbeddedFontFamilyMap, extractEmbeddedFonts, getEmbeddedFontFaces, scopeEmbeddedFontFamily } from "./fonts/embeddedFonts.js";
|
|
31
|
+
import { getGoogleFontsEnabled, setEmbeddedFontFamilyMap, setGoogleFontsEnabled } from "./utils/fontResolver.js";
|
|
32
32
|
import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
|
|
33
33
|
type Document = document_d_exports.Document;
|
|
34
34
|
type DocxConformanceClass = document_d_exports.DocxConformanceClass;
|
|
35
|
-
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 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_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, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyResult, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAISignatureParty, type FolioBlockId, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocxCompatibilityHost, type FolioDocxCompatibilityProfile, type ImageMeta, type ImageRef, type InspectDocxCompatibilityOptions, InvalidFolioDocumentOperationBatchError, type MarkdownOptions, type MarkdownResult, type PositionalText, type ResolvedAnchor, STELLA_STYLE_SET_NAME, type TemplatePreviewSpan, type TemplatePreviewValue, type TemplatePreviewValues, type TemplateSlashMenuKeyAction, type TemplateSlashMenuState, UnsupportedFolioDocumentOperationVersionError, type WordDiffSegment, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
|
|
35
|
+
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 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_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, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyResult, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAISignatureParty, type FolioBlockId, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocxCompatibilityHost, type FolioDocxCompatibilityProfile, type ImageMeta, type ImageRef, type InspectDocxCompatibilityOptions, InvalidFolioDocumentOperationBatchError, type MarkdownOptions, type MarkdownResult, type PositionalText, type ResolvedAnchor, STELLA_STYLE_SET_NAME, type TemplatePreviewSpan, type TemplatePreviewValue, type TemplatePreviewValues, type TemplateSlashMenuKeyAction, type TemplateSlashMenuState, UnsupportedFolioDocumentOperationVersionError, type WordDiffSegment, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildEmbeddedFontFamilyMap, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, 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
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { getGoogleFontsEnabled, setEmbeddedFontFamilyMap, setGoogleFontsEnabled } from "./utils/fontResolver.js";
|
|
1
2
|
import { deriveBlockId, getFolioParaIdFromBlockId, isFolioBlockId, isSequentialFolioBlockId } from "./types/block-id.js";
|
|
2
3
|
import { createFolioAIEditSnapshot, hashFolioAIBlockText, normalizeFolioAIBlockText } from "./ai-edits/snapshot.js";
|
|
3
|
-
import { getGoogleFontsEnabled, setGoogleFontsEnabled } from "./utils/fontResolver.js";
|
|
4
4
|
import { diffWordSegments } from "./ai-edits/word-diff.js";
|
|
5
5
|
import { applyFolioAIEditOperations } from "./ai-edits/apply.js";
|
|
6
6
|
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, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "./document-operations.js";
|
|
@@ -25,6 +25,6 @@ import { clearTemplateSlashMenu, consumeTemplateSlashQuery, getTemplateSlashMenu
|
|
|
25
25
|
import { DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, acceptAutocompleteSuggestion, acceptAutocompleteWord, appendAutocompleteToken, autocompleteSuggestionKey, autocompleteSuggestionPlugin, clearAutocompleteSuggestion, finishAutocompleteSuggestion, getAutocompleteSuggestion, shouldTriggerAutocomplete, startAutocompleteSuggestion } from "./prosemirror/plugins/autocompleteSuggestion.js";
|
|
26
26
|
import { fromMarkdown } from "./markdown/fromMarkdown.js";
|
|
27
27
|
import { toMarkdown, toMarkdownResult } from "./markdown/index.js";
|
|
28
|
-
import { extractEmbeddedFonts, getEmbeddedFontFaces } from "./fonts/embeddedFonts.js";
|
|
28
|
+
import { buildEmbeddedFontFamilyMap, extractEmbeddedFonts, getEmbeddedFontFaces, scopeEmbeddedFontFamily } from "./fonts/embeddedFonts.js";
|
|
29
29
|
import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
|
|
30
|
-
export { DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DOCX_CONFORMANCE_CLASSES, 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, InvalidFolioDocumentOperationBatchError, STELLA_STYLE_SET_NAME, UnsupportedFolioDocumentOperationVersionError, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
|
|
30
|
+
export { DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DOCX_CONFORMANCE_CLASSES, 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, InvalidFolioDocumentOperationBatchError, STELLA_STYLE_SET_NAME, UnsupportedFolioDocumentOperationVersionError, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildEmbeddedFontFamilyMap, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scopeEmbeddedFontFamily, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setEmbeddedFontFamilyMap, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
|
|
@@ -333,6 +333,10 @@ function extractRunFormatting(marks, theme) {
|
|
|
333
333
|
formatting.changeAuthor = attrs.author;
|
|
334
334
|
if (attrs.date !== void 0) formatting.changeDate = attrs.date;
|
|
335
335
|
formatting.changeRevisionId = attrs.revisionId;
|
|
336
|
+
if (attrs.provenance === "suggested") {
|
|
337
|
+
formatting.isSuggestion = true;
|
|
338
|
+
if (attrs.suggestionId) formatting.suggestionId = attrs.suggestionId;
|
|
339
|
+
}
|
|
336
340
|
break;
|
|
337
341
|
}
|
|
338
342
|
case "deletion": {
|
|
@@ -341,6 +345,10 @@ function extractRunFormatting(marks, theme) {
|
|
|
341
345
|
formatting.changeAuthor = attrs.author;
|
|
342
346
|
if (attrs.date !== void 0) formatting.changeDate = attrs.date;
|
|
343
347
|
formatting.changeRevisionId = attrs.revisionId;
|
|
348
|
+
if (attrs.provenance === "suggested") {
|
|
349
|
+
formatting.isSuggestion = true;
|
|
350
|
+
if (attrs.suggestionId) formatting.suggestionId = attrs.suggestionId;
|
|
351
|
+
}
|
|
344
352
|
break;
|
|
345
353
|
}
|
|
346
354
|
default: break;
|
|
@@ -455,6 +463,9 @@ function buildImageRun(attrs, constrained, pmStart, pmEnd, trackedChange) {
|
|
|
455
463
|
if (attrs.cropRight != null) run.cropRight = attrs.cropRight;
|
|
456
464
|
if (attrs.cropBottom != null) run.cropBottom = attrs.cropBottom;
|
|
457
465
|
if (attrs.cropLeft != null) run.cropLeft = attrs.cropLeft;
|
|
466
|
+
if (attrs.borderWidth != null) run.borderWidth = attrs.borderWidth;
|
|
467
|
+
if (attrs.borderColor) run.borderColor = attrs.borderColor;
|
|
468
|
+
if (attrs.borderStyle) run.borderStyle = attrs.borderStyle;
|
|
458
469
|
if (attrs.position !== void 0) run.position = attrs.position;
|
|
459
470
|
if (attrs.layoutInCell !== void 0) run.layoutInCell = attrs.layoutInCell;
|
|
460
471
|
if (trackedChange?.isInsertion) run.isInsertion = true;
|
|
@@ -858,6 +869,7 @@ function convertParagraphAttrs(pmAttrs, theme, listCounters, listAbstractCounter
|
|
|
858
869
|
const dtf = pmAttrs.defaultTextFormatting;
|
|
859
870
|
if (dtf) {
|
|
860
871
|
if (dtf.fontSize !== void 0) attrs.defaultFontSize = dtf.fontSize / 2;
|
|
872
|
+
if (attrs.listMarker !== void 0 && !attrs.listMarkerHidden && dtf.fontSizeCs !== void 0 && (dtf.fontSize === void 0 || dtf.fontSizeCs > dtf.fontSize)) attrs.listParagraphMarkFontSize = dtf.fontSizeCs / 2;
|
|
861
873
|
if (dtf.fontFamily) {
|
|
862
874
|
const resolvedFamily = resolveWesternThemeFont(dtf.fontFamily, theme);
|
|
863
875
|
if (resolvedFamily) attrs.defaultFontFamily = resolvedFamily;
|
|
@@ -1214,6 +1226,9 @@ function convertImage(node, startPos, pageContentHeight) {
|
|
|
1214
1226
|
if (attrs.cropRight != null) imgBlock.cropRight = attrs.cropRight;
|
|
1215
1227
|
if (attrs.cropBottom != null) imgBlock.cropBottom = attrs.cropBottom;
|
|
1216
1228
|
if (attrs.cropLeft != null) imgBlock.cropLeft = attrs.cropLeft;
|
|
1229
|
+
if (attrs.borderWidth != null) imgBlock.borderWidth = attrs.borderWidth;
|
|
1230
|
+
if (attrs.borderColor) imgBlock.borderColor = attrs.borderColor;
|
|
1231
|
+
if (attrs.borderStyle) imgBlock.borderStyle = attrs.borderStyle;
|
|
1217
1232
|
return imgBlock;
|
|
1218
1233
|
}
|
|
1219
1234
|
/**
|
|
@@ -477,6 +477,16 @@ const tableRowStartsWithRenderedPageBreak = (block, rowIndex) => block.rows[rowI
|
|
|
477
477
|
const firstBlock = cell.blocks.at(0);
|
|
478
478
|
return firstBlock?.kind === "paragraph" && firstBlock.attrs?.renderedPageBreakBefore === true;
|
|
479
479
|
}) ?? false;
|
|
480
|
+
const flowBlockHasTrackedChanges = (block) => {
|
|
481
|
+
if (block.kind === "paragraph") return block.runs.some((run) => {
|
|
482
|
+
if (run.kind === "lineBreak") return false;
|
|
483
|
+
return run.isInsertion || run.isDeletion;
|
|
484
|
+
});
|
|
485
|
+
if (block.kind === "table") return block.rows.some((row) => row.cells.some((cell) => cell.blocks.some(flowBlockHasTrackedChanges)));
|
|
486
|
+
if (block.kind === "textBox") return block.content.some(flowBlockHasTrackedChanges);
|
|
487
|
+
return false;
|
|
488
|
+
};
|
|
489
|
+
const tableRowHasTrackedChanges = (block, rowIndex) => block.rows[rowIndex]?.cells.some((cell) => cell.blocks.some(flowBlockHasTrackedChanges)) ?? false;
|
|
480
490
|
/**
|
|
481
491
|
* Layout a table block onto pages.
|
|
482
492
|
*/
|
|
@@ -522,7 +532,7 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
|
|
|
522
532
|
const rowState = paginator.getCurrentState();
|
|
523
533
|
const rowHeaderOverhead = shouldRepeatHeaderRows(currentRowIndex, 0, rowState) ? headerRowsHeight : 0;
|
|
524
534
|
const rowAvailableHeight = paginator.getAvailableHeight() - rowHeaderOverhead - rowState.trailingSpacing;
|
|
525
|
-
if (!(rowState.cursorY === rowState.topMargin && rowState.page.fragments.length === 0) && tableRowStartsWithRenderedPageBreak(block, currentRowIndex) && rows[currentRowIndex].height > rowAvailableHeight) {
|
|
535
|
+
if (!(rowState.cursorY === rowState.topMargin && rowState.page.fragments.length === 0) && tableRowStartsWithRenderedPageBreak(block, currentRowIndex) && !tableRowHasTrackedChanges(block, currentRowIndex) && rows[currentRowIndex].height > rowAvailableHeight) {
|
|
526
536
|
paginator.forcePageBreak();
|
|
527
537
|
continue;
|
|
528
538
|
}
|
|
@@ -657,7 +667,7 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
|
|
|
657
667
|
}
|
|
658
668
|
}
|
|
659
669
|
function collectTableRowFootnoteIds(row, footnoteHeightById) {
|
|
660
|
-
if (!row || !footnoteHeightById) return [];
|
|
670
|
+
if (!row || !footnoteHeightById || row.hidden) return [];
|
|
661
671
|
const ids = [];
|
|
662
672
|
const walk = (blocks) => {
|
|
663
673
|
for (const block of blocks) {
|
|
@@ -666,7 +676,10 @@ function collectTableRowFootnoteIds(row, footnoteHeightById) {
|
|
|
666
676
|
continue;
|
|
667
677
|
}
|
|
668
678
|
if (block.kind === "table") {
|
|
669
|
-
for (const nestedRow of block.rows)
|
|
679
|
+
for (const nestedRow of block.rows) {
|
|
680
|
+
if (nestedRow.hidden) continue;
|
|
681
|
+
for (const cell of nestedRow.cells) walk(cell.blocks);
|
|
682
|
+
}
|
|
670
683
|
continue;
|
|
671
684
|
}
|
|
672
685
|
if (block.kind === "textBox") walk(block.content);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clearFontResolvedCache } from "./measureHelpers.js";
|
|
1
2
|
import { getLineBreakProviderGeneration } from "./lineBreakProvider.js";
|
|
2
3
|
import { lineBreakPolicyCacheParts } from "./effectiveLineBreakPolicy.js";
|
|
3
4
|
//#region src/layout-engine/measure/cache.ts
|
|
@@ -255,6 +256,7 @@ function clearAllCaches() {
|
|
|
255
256
|
clearTextWidthCache();
|
|
256
257
|
clearFontMetricsCache();
|
|
257
258
|
clearParagraphMeasureCache();
|
|
259
|
+
clearFontResolvedCache();
|
|
258
260
|
}
|
|
259
261
|
/**
|
|
260
262
|
* Get total size of all caches
|
|
@@ -12,8 +12,8 @@ const resolveEffectiveLineBreakPolicy = ({ attrs, run }) => {
|
|
|
12
12
|
provider: {
|
|
13
13
|
...locale ? { locale } : {},
|
|
14
14
|
...attrs?.kinsoku !== void 0 ? { kinsoku: attrs.kinsoku } : {},
|
|
15
|
-
...before && languageMatches(before.language, locale) ? { noLineBreaksBefore: before.characters } : {},
|
|
16
|
-
...after && languageMatches(after.language, locale) ? { noLineBreaksAfter: after.characters } : {},
|
|
15
|
+
...before && languageMatches(before.language, locale) ? { noLineBreaksBefore: toCodePointSet(before.characters) } : {},
|
|
16
|
+
...after && languageMatches(after.language, locale) ? { noLineBreaksAfter: toCodePointSet(after.characters) } : {},
|
|
17
17
|
...rules?.useLegacyEthiopicAmharicRules ? { useLegacyEthiopicAmharicRules: true } : {},
|
|
18
18
|
...automaticHyphenation?.doNotHyphenateCaps !== void 0 ? { doNotHyphenateCaps: automaticHyphenation.doNotHyphenateCaps } : {},
|
|
19
19
|
...run.allCaps === true ? { renderedAllCaps: true } : {}
|
|
@@ -56,5 +56,11 @@ const languageMatches = (ruleLanguage, locale) => {
|
|
|
56
56
|
const active = locale.toLowerCase();
|
|
57
57
|
return active === rule || active.startsWith(`${rule}-`) || rule.startsWith(`${active}-`);
|
|
58
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* Split an already length-capped kinsoku character list (settingsParser.ts)
|
|
61
|
+
* into a code-point Set so line-edge membership checks are O(1) instead of
|
|
62
|
+
* an O(n) `String.includes` scan per character measured.
|
|
63
|
+
*/
|
|
64
|
+
const toCodePointSet = (characters) => new Set(Array.from(characters));
|
|
59
65
|
//#endregion
|
|
60
66
|
export { lineBreakPolicyCacheParts, resolveEffectiveLineBreakPolicy };
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
type LineBreakPolicy = {
|
|
11
11
|
/** BCP-47 language tag resolved from `w:lang` for this run. */locale?: string; /** Apply East Asian first/last-character restrictions (`w:kinsoku`). */
|
|
12
12
|
kinsoku?: boolean; /** Document replacement list: characters that may not begin a line. */
|
|
13
|
-
noLineBreaksBefore?: string
|
|
14
|
-
noLineBreaksAfter?: string
|
|
13
|
+
noLineBreaksBefore?: ReadonlySet<string>; /** Document replacement list: characters that may not end a line. */
|
|
14
|
+
noLineBreaksAfter?: ReadonlySet<string>; /** Use the legacy Ethiopic/Amharic compatibility behavior. */
|
|
15
15
|
useLegacyEthiopicAmharicRules?: boolean; /** Keep words made entirely of capital letters unhyphenated. */
|
|
16
16
|
doNotHyphenateCaps?: boolean; /** The run renders with the DOCX all-caps transform. */
|
|
17
17
|
renderedAllCaps?: boolean;
|
|
@@ -128,9 +128,9 @@ const previousCodePoint = (text, index) => {
|
|
|
128
128
|
if (trailing >= 56320 && trailing <= 57343 && index >= 2) return text.slice(index - 2, index);
|
|
129
129
|
return text[index - 1];
|
|
130
130
|
};
|
|
131
|
-
const isProhibitedLineStart = (character, policy) => character !== void 0 && policy?.kinsoku !== false && (policy?.noLineBreaksBefore !== void 0 ? policy.noLineBreaksBefore.
|
|
132
|
-
const isProhibitedLineEnd = (character, policy) => character !== void 0 && policy?.kinsoku !== false && (policy?.noLineBreaksAfter !== void 0 ? policy.noLineBreaksAfter.
|
|
133
|
-
const isCjkLineBreakParticipant = (character, policy) => character !== void 0 && (CJK_SCRIPT.test(character) || DEFAULT_PROHIBITED_LINE_START.has(character) || DEFAULT_PROHIBITED_LINE_END.has(character) || policy?.noLineBreaksBefore?.
|
|
131
|
+
const isProhibitedLineStart = (character, policy) => character !== void 0 && policy?.kinsoku !== false && (policy?.noLineBreaksBefore !== void 0 ? policy.noLineBreaksBefore.has(character) : DEFAULT_PROHIBITED_LINE_START.has(character));
|
|
132
|
+
const isProhibitedLineEnd = (character, policy) => character !== void 0 && policy?.kinsoku !== false && (policy?.noLineBreaksAfter !== void 0 ? policy.noLineBreaksAfter.has(character) : DEFAULT_PROHIBITED_LINE_END.has(character));
|
|
133
|
+
const isCjkLineBreakParticipant = (character, policy) => character !== void 0 && (CJK_SCRIPT.test(character) || policy?.kinsoku !== false && (DEFAULT_PROHIBITED_LINE_START.has(character) || DEFAULT_PROHIBITED_LINE_END.has(character) || policy?.noLineBreaksBefore?.has(character) === true || policy?.noLineBreaksAfter?.has(character) === true));
|
|
134
134
|
const isLegacyEthiopicBreakCharacter = (character, policy) => {
|
|
135
135
|
if (!policy?.useLegacyEthiopicAmharicRules || character === void 0) return false;
|
|
136
136
|
const codePoint = character.codePointAt(0);
|
|
@@ -17,6 +17,14 @@ import { buildTableCellGrid, getFirstAvailableColumn, getTableCellVerticalBorder
|
|
|
17
17
|
*/
|
|
18
18
|
const NO_WRAP_MEASURE_WIDTH = 1e6;
|
|
19
19
|
/**
|
|
20
|
+
* Sanity cap on the table column count derived from summed cell colSpans
|
|
21
|
+
* when a table has no `tblGrid`/explicit column widths. Mirrors the
|
|
22
|
+
* `MAX_TABLE_COLUMNS` clamp applied to `w:gridSpan` at parse time
|
|
23
|
+
* (`docx/tableParser.ts`); enforced again here since a hostile colSpan sum
|
|
24
|
+
* would otherwise size the fallback column array and cell grid unbounded.
|
|
25
|
+
*/
|
|
26
|
+
const MAX_TABLE_COLUMNS = 63;
|
|
27
|
+
/**
|
|
20
28
|
* Check if an image run is a *text-wrapping* floating image — it
|
|
21
29
|
* occupies an exclusion zone the body text should flow around.
|
|
22
30
|
*
|
|
@@ -59,7 +67,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
59
67
|
let columnWidths = tableBlock.columnWidths ?? [];
|
|
60
68
|
const explicitWidthPx = resolveTableWidthPx(tableBlock.width, tableBlock.widthType, contentWidth);
|
|
61
69
|
if (columnWidths.length === 0 && tableBlock.rows.length > 0) {
|
|
62
|
-
const colCount = tableBlock.rows[0].cells.reduce((sum, cell) => sum + (cell.colSpan ?? 1), 0);
|
|
70
|
+
const colCount = Math.min(tableBlock.rows[0].cells.reduce((sum, cell) => sum + (cell.colSpan ?? 1), 0), MAX_TABLE_COLUMNS);
|
|
63
71
|
const equalWidth = (explicitWidthPx ?? contentWidth) / Math.max(1, colCount);
|
|
64
72
|
columnWidths = Array.from({ length: colCount }, () => equalWidth);
|
|
65
73
|
} else if (columnWidths.length > 0 && explicitWidthPx) {
|
|
@@ -22,6 +22,15 @@ type ResolvedFontCache = {
|
|
|
22
22
|
* Get the resolved font data for a font family, with caching.
|
|
23
23
|
*/
|
|
24
24
|
declare function getResolvedData(fontFamily: string): ResolvedFontCache;
|
|
25
|
+
/**
|
|
26
|
+
* Clear the resolved-font-family cache. `resolveFontFamily`'s output for a
|
|
27
|
+
* given key can change even when the raw font names don't — e.g. when the
|
|
28
|
+
* active document's embedded-font family map changes (see
|
|
29
|
+
* `utils/fontResolver.ts`'s `setEmbeddedFontFamilyMap`) — so this must be
|
|
30
|
+
* cleared whenever that happens, not just on font-name changes. Folded into
|
|
31
|
+
* `clearAllCaches` (`./cache.ts`) so callers get it for free.
|
|
32
|
+
*/
|
|
33
|
+
declare function clearFontResolvedCache(): void;
|
|
25
34
|
/**
|
|
26
35
|
* Build a CSS font string from styling properties
|
|
27
36
|
*
|
|
@@ -79,4 +88,4 @@ declare function halfPtToPx(halfPt: number): number;
|
|
|
79
88
|
*/
|
|
80
89
|
declare function pxToHalfPt(px: number): number;
|
|
81
90
|
//#endregion
|
|
82
|
-
export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, buildFontString, buildRunFontStyle, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
|
|
91
|
+
export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, buildFontString, buildRunFontStyle, clearFontResolvedCache, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
|
|
@@ -62,6 +62,17 @@ function getResolvedFallback(fontFamily) {
|
|
|
62
62
|
return getResolvedData(fontFamily).cssFallback;
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
|
+
* Clear the resolved-font-family cache. `resolveFontFamily`'s output for a
|
|
66
|
+
* given key can change even when the raw font names don't — e.g. when the
|
|
67
|
+
* active document's embedded-font family map changes (see
|
|
68
|
+
* `utils/fontResolver.ts`'s `setEmbeddedFontFamilyMap`) — so this must be
|
|
69
|
+
* cleared whenever that happens, not just on font-name changes. Folded into
|
|
70
|
+
* `clearAllCaches` (`./cache.ts`) so callers get it for free.
|
|
71
|
+
*/
|
|
72
|
+
function clearFontResolvedCache() {
|
|
73
|
+
fontResolvedCache.clear();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
65
76
|
* Build a CSS font string from styling properties
|
|
66
77
|
*
|
|
67
78
|
* Font sizes are in points and need to be converted to pixels for canvas.
|
|
@@ -156,4 +167,4 @@ function pxToHalfPt(px) {
|
|
|
156
167
|
return pxToPt(px) * 2;
|
|
157
168
|
}
|
|
158
169
|
//#endregion
|
|
159
|
-
export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, buildFontString, buildRunFontStyle, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
|
|
170
|
+
export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, buildFontString, buildRunFontStyle, clearFontResolvedCache, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
|
|
@@ -515,12 +515,20 @@ function computeProtectedCrossRunGlueWidths(block) {
|
|
|
515
515
|
}
|
|
516
516
|
return widths;
|
|
517
517
|
}
|
|
518
|
+
/**
|
|
519
|
+
* Cap on the number of runs `collectCrossRunWord` traverses per call. A
|
|
520
|
+
* paragraph split into many tiny (including empty) runs would otherwise let
|
|
521
|
+
* the search walk the full run list for every word-start position it is
|
|
522
|
+
* invoked from, without the per-word text-length budget ever kicking in.
|
|
523
|
+
*/
|
|
524
|
+
const MAX_CROSS_RUN_SEGMENTS = 64;
|
|
518
525
|
/** Collect one lexical word continued through adjacent formatting runs. */
|
|
519
526
|
function collectCrossRunWord({ block, startRunIndex, startChar }) {
|
|
520
527
|
const segments = [];
|
|
521
528
|
let text = "";
|
|
522
529
|
let width = 0;
|
|
523
530
|
for (let runIndex = startRunIndex; runIndex < block.runs.length; runIndex++) {
|
|
531
|
+
if (runIndex - startRunIndex >= MAX_CROSS_RUN_SEGMENTS) return;
|
|
524
532
|
const run = block.runs[runIndex];
|
|
525
533
|
if (!run || !isTextRun(run)) break;
|
|
526
534
|
const start = runIndex === startRunIndex ? startChar : 0;
|
|
@@ -1138,6 +1146,14 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
1138
1146
|
}
|
|
1139
1147
|
}
|
|
1140
1148
|
}
|
|
1149
|
+
const listParagraphMarkFontSize = attrs?.listParagraphMarkFontSize;
|
|
1150
|
+
if (listParagraphMarkFontSize !== void 0 && listParagraphMarkFontSize > currentLine.maxFontSize) {
|
|
1151
|
+
const fontFamily = currentLine.maxFontMetrics?.fontFamily ?? attrs?.defaultFontFamily;
|
|
1152
|
+
updateMaxFont({
|
|
1153
|
+
fontSize: listParagraphMarkFontSize,
|
|
1154
|
+
...fontFamily === void 0 ? {} : { fontFamily }
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1141
1157
|
finalizeLine();
|
|
1142
1158
|
let totalWithSpacing = lines.reduce((sum, line) => sum + measuredLineAdvance(line), 0);
|
|
1143
1159
|
if (spacing?.before) totalWithSpacing += spacing.before;
|
|
@@ -87,6 +87,14 @@ type RunFormatting = {
|
|
|
87
87
|
changeAuthor?: string; /** Date of the tracked change */
|
|
88
88
|
changeDate?: string; /** Revision ID of the tracked change (for sidebar matching) */
|
|
89
89
|
changeRevisionId?: number;
|
|
90
|
+
/**
|
|
91
|
+
* Whether this tracked insertion/deletion is an AI-proposed *suggestion*
|
|
92
|
+
* rather than a user change. Suggestions render with the same grammar but a
|
|
93
|
+
* dotted stroke and a dedicated hue; they are stripped from serialized DOCX
|
|
94
|
+
* until accepted.
|
|
95
|
+
*/
|
|
96
|
+
isSuggestion?: boolean; /** Groups a suggestion's runs for accept/reject and scroll-to. */
|
|
97
|
+
suggestionId?: string;
|
|
90
98
|
};
|
|
91
99
|
/**
|
|
92
100
|
* Hyperlink information for a run.
|
|
@@ -179,7 +187,10 @@ type ImageRun = {
|
|
|
179
187
|
cropTop?: number;
|
|
180
188
|
cropRight?: number;
|
|
181
189
|
cropBottom?: number;
|
|
182
|
-
cropLeft?: number; /**
|
|
190
|
+
cropLeft?: number; /** CSS border width in pixels. eigenpal #1096. */
|
|
191
|
+
borderWidth?: number; /** CSS border color. eigenpal #1096. */
|
|
192
|
+
borderColor?: string; /** CSS border style. eigenpal #1096. */
|
|
193
|
+
borderStyle?: string; /** Whether this picture is itself a tracked insertion (`<w:ins>`). eigenpal #641. */
|
|
183
194
|
isInsertion?: boolean; /** Whether this picture is itself a tracked deletion (`<w:del>`). eigenpal #641. */
|
|
184
195
|
isDeletion?: boolean; /** Author of the tracked change wrapping the picture. eigenpal #641. */
|
|
185
196
|
changeAuthor?: string; /** Date of the tracked change wrapping the picture. eigenpal #641. */
|
|
@@ -377,6 +388,12 @@ type ParagraphAttrs = {
|
|
|
377
388
|
listMarkerHidden?: boolean;
|
|
378
389
|
listMarkerFontFamily?: string;
|
|
379
390
|
listMarkerFontSize?: number;
|
|
391
|
+
/**
|
|
392
|
+
* Effective paragraph-mark size used only to measure a visible list
|
|
393
|
+
* paragraph's final line. Word lets the inherited complex-script size raise
|
|
394
|
+
* that line box without enlarging the marker or Western text glyphs.
|
|
395
|
+
*/
|
|
396
|
+
listParagraphMarkFontSize?: number;
|
|
380
397
|
listMarkerBold?: boolean; /** Horizontal alignment of the marker around the paragraph's list anchor. */
|
|
381
398
|
listMarkerAlignment?: "left" | "center" | "right";
|
|
382
399
|
/**
|
|
@@ -566,7 +583,10 @@ type ImageBlock = {
|
|
|
566
583
|
cropTop?: number;
|
|
567
584
|
cropRight?: number;
|
|
568
585
|
cropBottom?: number;
|
|
569
|
-
cropLeft?: number;
|
|
586
|
+
cropLeft?: number; /** CSS border width in pixels. eigenpal #1096. */
|
|
587
|
+
borderWidth?: number; /** CSS border color. eigenpal #1096. */
|
|
588
|
+
borderColor?: string; /** CSS border style. eigenpal #1096. */
|
|
589
|
+
borderStyle?: string;
|
|
570
590
|
pmStart?: number;
|
|
571
591
|
pmEnd?: number;
|
|
572
592
|
};
|
|
@@ -21,6 +21,17 @@ type ImageVisualAttrs = {
|
|
|
21
21
|
cropBottom?: number;
|
|
22
22
|
cropLeft?: number;
|
|
23
23
|
};
|
|
24
|
+
type ImageBorderAttrs = {
|
|
25
|
+
borderWidth?: number;
|
|
26
|
+
borderColor?: string;
|
|
27
|
+
borderStyle?: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Paint image borders carried through the layout model. Folio's PM schema
|
|
31
|
+
* uses `borderStyle` (not upstream's `borderKind`); the painter mirrors the
|
|
32
|
+
* editor DOM serialization while keeping the authored image box size stable.
|
|
33
|
+
*/
|
|
34
|
+
declare function applyImageBorder(element: HTMLElement, border: ImageBorderAttrs): void;
|
|
24
35
|
/**
|
|
25
36
|
* True when any visual attribute is set. Cheap call-site guard so the no-op
|
|
26
37
|
* common case skips the helper call.
|
|
@@ -86,4 +97,4 @@ type RenderImageFragmentOptions = {
|
|
|
86
97
|
*/
|
|
87
98
|
declare function renderImageFragment(fragment: ImageFragment, block: ImageBlock, _measure: ImageMeasure, _context: RenderContext, options?: RenderImageFragmentOptions): HTMLElement;
|
|
88
99
|
//#endregion
|
|
89
|
-
export { IMAGE_CLASS_NAMES, ImageVisualAttrs, RenderImageFragmentOptions, applyImageVisualAttrs, hasImageCrop, hasImageVisualAttrs, renderImageFragment, wrapImageWithCrop };
|
|
100
|
+
export { IMAGE_CLASS_NAMES, ImageBorderAttrs, ImageVisualAttrs, RenderImageFragmentOptions, applyImageBorder, applyImageVisualAttrs, hasImageCrop, hasImageVisualAttrs, renderImageFragment, wrapImageWithCrop };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { sanitizeExternalUrl } from "../utils/urlSecurity.js";
|
|
2
|
+
import { applySanitizedImageSrc } from "../utils/sanitizeImageSrc.js";
|
|
1
3
|
//#region src/layout-painter/renderImage.ts
|
|
2
4
|
/**
|
|
3
5
|
* CSS class names for image elements
|
|
@@ -7,6 +9,18 @@ const IMAGE_CLASS_NAMES = {
|
|
|
7
9
|
imageAnchored: "layout-image-anchored"
|
|
8
10
|
};
|
|
9
11
|
/**
|
|
12
|
+
* Paint image borders carried through the layout model. Folio's PM schema
|
|
13
|
+
* uses `borderStyle` (not upstream's `borderKind`); the painter mirrors the
|
|
14
|
+
* editor DOM serialization while keeping the authored image box size stable.
|
|
15
|
+
*/
|
|
16
|
+
function applyImageBorder(element, border) {
|
|
17
|
+
if (border.borderWidth == null || border.borderWidth <= 0) return;
|
|
18
|
+
const borderStyle = border.borderStyle || "solid";
|
|
19
|
+
const borderColor = border.borderColor || "#000000";
|
|
20
|
+
element.style.border = `${border.borderWidth}px ${borderStyle} ${borderColor}`;
|
|
21
|
+
element.style.boxSizing = "border-box";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
10
24
|
* True when any visual attribute is set. Cheap call-site guard so the no-op
|
|
11
25
|
* common case skips the helper call.
|
|
12
26
|
*
|
|
@@ -118,7 +132,7 @@ function renderImageFragment(fragment, block, _measure, _context, options = {})
|
|
|
118
132
|
if (fragment.pmStart !== void 0) containerEl.dataset["pmStart"] = String(fragment.pmStart);
|
|
119
133
|
if (fragment.pmEnd !== void 0) containerEl.dataset["pmEnd"] = String(fragment.pmEnd);
|
|
120
134
|
const imgEl = doc.createElement("img");
|
|
121
|
-
imgEl
|
|
135
|
+
applySanitizedImageSrc(imgEl, block.src);
|
|
122
136
|
imgEl.alt = block.alt ?? "";
|
|
123
137
|
imgEl.style.width = "100%";
|
|
124
138
|
imgEl.style.height = "100%";
|
|
@@ -127,9 +141,10 @@ function renderImageFragment(fragment, block, _measure, _context, options = {})
|
|
|
127
141
|
if (block.transform) imgEl.style.transform = block.transform;
|
|
128
142
|
if (hasImageVisualAttrs(block)) applyImageVisualAttrs(imgEl, block);
|
|
129
143
|
imgEl.draggable = false;
|
|
130
|
-
|
|
144
|
+
const hlinkHref = sanitizeExternalUrl(block.hlinkHref);
|
|
145
|
+
if (hlinkHref) {
|
|
131
146
|
const linkEl = doc.createElement("a");
|
|
132
|
-
linkEl.href =
|
|
147
|
+
linkEl.href = hlinkHref;
|
|
133
148
|
linkEl.target = "_blank";
|
|
134
149
|
linkEl.rel = "noopener noreferrer";
|
|
135
150
|
linkEl.style.display = "block";
|
|
@@ -138,7 +153,9 @@ function renderImageFragment(fragment, block, _measure, _context, options = {})
|
|
|
138
153
|
linkEl.append(imgEl);
|
|
139
154
|
containerEl.append(linkEl);
|
|
140
155
|
} else containerEl.append(imgEl);
|
|
156
|
+
if (hasImageCrop(block)) applyImageBorder(containerEl, block);
|
|
157
|
+
else applyImageBorder(imgEl, block);
|
|
141
158
|
return containerEl;
|
|
142
159
|
}
|
|
143
160
|
//#endregion
|
|
144
|
-
export { IMAGE_CLASS_NAMES, applyImageVisualAttrs, hasImageCrop, hasImageVisualAttrs, renderImageFragment, wrapImageWithCrop };
|
|
161
|
+
export { IMAGE_CLASS_NAMES, applyImageBorder, applyImageVisualAttrs, hasImageCrop, hasImageVisualAttrs, renderImageFragment, wrapImageWithCrop };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { eighthsToPixels, pointsToPixels } from "../utils/units.js";
|
|
2
2
|
import { resolveFontFamily } from "../utils/fontResolver.js";
|
|
3
3
|
import { borderToStyle } from "../utils/formatToStyle.js";
|
|
4
|
+
import { applySanitizedImageSrc } from "../utils/sanitizeImageSrc.js";
|
|
4
5
|
import { floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun } from "../layout-engine/types.js";
|
|
5
6
|
import { rectsToFloatingZones } from "../layout-engine/measure/floatingZones.js";
|
|
6
7
|
import { measureParagraph } from "../layout-engine/measure/measureParagraph.js";
|
|
@@ -380,7 +381,7 @@ function renderFloatingImagesLayer(floatingImages, doc, layerMode = "front") {
|
|
|
380
381
|
if (floatImg.pmStart !== void 0) container.dataset["pmStart"] = String(floatImg.pmStart);
|
|
381
382
|
if (floatImg.pmEnd !== void 0) container.dataset["pmEnd"] = String(floatImg.pmEnd);
|
|
382
383
|
const img = doc.createElement("img");
|
|
383
|
-
img
|
|
384
|
+
applySanitizedImageSrc(img, floatImg.src);
|
|
384
385
|
img.style.width = `${floatImg.width}px`;
|
|
385
386
|
img.style.height = `${floatImg.height}px`;
|
|
386
387
|
img.style.display = "block";
|
|
@@ -509,7 +510,7 @@ function renderHeaderFooterContent(content, context, options, layout) {
|
|
|
509
510
|
}
|
|
510
511
|
for (const floatImg of floatingImages) {
|
|
511
512
|
const img = doc.createElement("img");
|
|
512
|
-
img
|
|
513
|
+
applySanitizedImageSrc(img, floatImg.src);
|
|
513
514
|
img.width = floatImg.width;
|
|
514
515
|
img.height = floatImg.height;
|
|
515
516
|
if (floatImg.alt) img.alt = floatImg.alt;
|