@stll/folio-core 0.32.2 → 0.33.1
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/__fixtures__/paragraphs.d.ts +17 -0
- package/dist/ai-edits/__fixtures__/paragraphs.js +33 -0
- package/dist/ai-edits/apply.d.ts +49 -3
- package/dist/ai-edits/apply.js +652 -86
- package/dist/ai-edits/blockRange.d.ts +7 -3
- package/dist/ai-edits/blockRange.js +14 -6
- package/dist/ai-edits/headless.d.ts +39 -5
- package/dist/ai-edits/headless.js +76 -17
- package/dist/ai-edits/index.d.ts +6 -6
- package/dist/ai-edits/index.js +3 -3
- package/dist/ai-edits/read.d.ts +4 -0
- package/dist/ai-edits/read.js +30 -6
- package/dist/ai-edits/scoped-reading.js +4 -3
- package/dist/ai-edits/snapshot.d.ts +42 -2
- package/dist/ai-edits/snapshot.js +127 -62
- package/dist/ai-edits/table-row-column-mutations.d.ts +24 -1
- package/dist/ai-edits/table-row-column-mutations.js +98 -6
- package/dist/ai-edits/table-targets.d.ts +12 -3
- package/dist/ai-edits/table-targets.js +15 -5
- package/dist/ai-edits/types.d.ts +219 -15
- package/dist/ai-edits/word-diff.d.ts +54 -11
- package/dist/ai-edits/word-diff.js +221 -61
- package/dist/compare/__fixtures__/body-sequence.d.ts +56 -0
- package/dist/compare/__fixtures__/body-sequence.js +136 -0
- package/dist/compare/__fixtures__/nested-table.d.ts +16 -0
- package/dist/compare/__fixtures__/nested-table.js +50 -0
- package/dist/compare/__fixtures__/numbered-list.d.ts +34 -0
- package/dist/compare/__fixtures__/numbered-list.js +85 -0
- package/dist/compare/column-alignment.d.ts +27 -0
- package/dist/compare/column-alignment.js +162 -0
- package/dist/compare/compare.d.ts +107 -0
- package/dist/compare/compare.js +412 -0
- package/dist/compare/formatting.d.ts +29 -0
- package/dist/compare/formatting.js +84 -0
- package/dist/compare/plan.d.ts +22 -0
- package/dist/compare/plan.js +1141 -0
- package/dist/compare/reproducible-package.d.ts +21 -0
- package/dist/compare/reproducible-package.js +47 -0
- package/dist/compare/scenario.d.ts +77 -0
- package/dist/compare/scenario.js +263 -0
- package/dist/compare/types.d.ts +297 -0
- package/dist/compare/types.js +42 -0
- package/dist/compare/verification.d.ts +83 -0
- package/dist/compare/verification.js +189 -0
- package/dist/compat/eigenpal.d.ts +9 -6
- package/dist/compat/eigenpal.js +6 -3
- package/dist/controller/layoutPipeline.d.ts +13 -0
- package/dist/controller/layoutPipeline.js +15 -1
- package/dist/display-list/build/buildContext.d.ts +39 -0
- package/dist/display-list/build/buildContext.js +31 -0
- package/dist/display-list/build/buildDisplayList.d.ts +79 -0
- package/dist/display-list/build/buildDisplayList.js +283 -0
- package/dist/display-list/build/colors.d.ts +20 -0
- package/dist/display-list/build/colors.js +136 -0
- package/dist/display-list/build/floatingImages.d.ts +24 -0
- package/dist/display-list/build/floatingImages.js +41 -0
- package/dist/display-list/build/fontTable.d.ts +31 -0
- package/dist/display-list/build/fontTable.js +97 -0
- package/dist/display-list/build/furniture.d.ts +86 -0
- package/dist/display-list/build/furniture.js +98 -0
- package/dist/display-list/build/glyphs.d.ts +55 -0
- package/dist/display-list/build/glyphs.js +103 -0
- package/dist/display-list/build/headerFooterPrimitives.d.ts +17 -0
- package/dist/display-list/build/headerFooterPrimitives.js +40 -0
- package/dist/display-list/build/imagePrimitives.d.ts +35 -0
- package/dist/display-list/build/imagePrimitives.js +264 -0
- package/dist/display-list/build/pageBorderPrimitives.d.ts +23 -0
- package/dist/display-list/build/pageBorderPrimitives.js +111 -0
- package/dist/display-list/build/pageFurniture.d.ts +35 -0
- package/dist/display-list/build/pageFurniture.js +141 -0
- package/dist/display-list/build/paragraphPrimitives.d.ts +19 -0
- package/dist/display-list/build/paragraphPrimitives.js +819 -0
- package/dist/display-list/build/regions.d.ts +46 -0
- package/dist/display-list/build/regions.js +56 -0
- package/dist/display-list/build/storyPrimitives.d.ts +29 -0
- package/dist/display-list/build/storyPrimitives.js +279 -0
- package/dist/display-list/build/strokes.d.ts +19 -0
- package/dist/display-list/build/strokes.js +78 -0
- package/dist/display-list/build/tablePrimitives.d.ts +38 -0
- package/dist/display-list/build/tablePrimitives.js +409 -0
- package/dist/display-list/build/textBoxPrimitives.d.ts +14 -0
- package/dist/display-list/build/textBoxPrimitives.js +129 -0
- package/dist/display-list/build/textDecorations.d.ts +29 -0
- package/dist/display-list/build/textDecorations.js +29 -0
- package/dist/display-list/build/unsupported.d.ts +57 -0
- package/dist/display-list/build/unsupported.js +0 -0
- package/dist/display-list/build/watermarkPrimitives.d.ts +15 -0
- package/dist/display-list/build/watermarkPrimitives.js +128 -0
- package/dist/display-list/dom/renderDisplayListToDom.d.ts +26 -0
- package/dist/display-list/dom/renderDisplayListToDom.js +620 -0
- package/dist/display-list/editor/displayListPagePainter.d.ts +22 -0
- package/dist/display-list/editor/displayListPagePainter.js +63 -0
- package/dist/display-list/editor/pageRenderer.d.ts +24 -0
- package/dist/display-list/editor/pageRenderer.js +36 -0
- package/dist/display-list/primitives.d.ts +95 -0
- package/dist/display-list/primitives.js +127 -0
- package/dist/display-list/types.d.ts +472 -0
- package/dist/display-list/types.js +0 -0
- package/dist/document-operations.d.ts +39 -8
- package/dist/document-operations.js +169 -16
- package/dist/document-stories.d.ts +16 -0
- package/dist/document-stories.js +48 -4
- package/dist/docx/hyperlinkParser.d.ts +9 -1
- package/dist/docx/hyperlinkParser.js +19 -13
- package/dist/docx/packageParts.d.ts +35 -0
- package/dist/docx/packageParts.js +120 -0
- package/dist/docx/paraIdRangeNormalization.d.ts +40 -0
- package/dist/docx/paraIdRangeNormalization.js +64 -0
- package/dist/docx/paragraphParser.js +101 -26
- package/dist/docx/paragraphTraversal.js +1 -1
- package/dist/docx/renderedPageBreakNormalization.js +3 -1
- package/dist/docx/revisionIdNormalization.d.ts +27 -0
- package/dist/docx/revisionIdNormalization.js +121 -0
- package/dist/docx/rezip.d.ts +6 -0
- package/dist/docx/rezip.js +51 -19
- package/dist/docx/selectiveSave.js +2 -2
- package/dist/docx/serializer/commentSerializer.js +6 -1
- package/dist/docx/serializer/paragraphSerializer.js +73 -20
- package/dist/docx/serializer/runSerializer.js +11 -10
- package/dist/docx/serializer/tableSerializer.js +26 -10
- package/dist/docx/server/applyDocxXmlPatchProposal.js +1 -1
- package/dist/docx/server/createBilingualDocx.js +3 -2
- package/dist/docx/streamingXmlParser.d.ts +13 -1
- package/dist/docx/streamingXmlParser.js +49 -11
- package/dist/docx/unzip.d.ts +1 -2
- package/dist/docx/unzip.js +25 -6
- package/dist/docx/xmlParser.d.ts +3 -1
- package/dist/docx/xmlParser.js +33 -27
- package/dist/export-pdf.d.ts +43 -0
- package/dist/export-pdf.js +98 -0
- package/dist/fonts/headlessMeasure.d.ts +76 -0
- package/dist/fonts/headlessMeasure.js +0 -0
- package/dist/fonts/sfnt/parse.d.ts +51 -0
- package/dist/fonts/sfnt/parse.js +525 -0
- package/dist/fonts/sfnt/subset.d.ts +20 -0
- package/dist/fonts/sfnt/subset.js +350 -0
- package/dist/fonts/sfnt/tables.d.ts +88 -0
- package/dist/fonts/sfnt/tables.js +131 -0
- package/dist/fonts/sfnt/woff.d.ts +14 -0
- package/dist/fonts/sfnt/woff.js +163 -0
- package/dist/generated/text_shaper.js +324 -0
- package/dist/generated/text_shaper_bg.wasm +0 -0
- package/dist/headless-layout.d.ts +75 -0
- package/dist/headless-layout.js +350 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.js +6 -3
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +2 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +2 -0
- package/dist/layout-engine/measure/advanceComposition.d.ts +62 -0
- package/dist/layout-engine/measure/advanceComposition.js +148 -0
- package/dist/layout-engine/measure/cache.d.ts +2 -0
- package/dist/layout-engine/measure/measureContainer.js +13 -0
- package/dist/layout-engine/measure/measureParagraph.js +1 -2
- package/dist/layout-engine/measure/measureTypes.d.ts +12 -0
- package/dist/layout-engine/measure/tableInlinePlacement.d.ts +3 -2
- package/dist/layout-engine/measure/tableInlinePlacement.js +14 -1
- package/dist/layout-engine/tableIndentCompatibility.d.ts +5 -0
- package/dist/layout-engine/tableIndentCompatibility.js +15 -0
- package/dist/layout-engine/types.d.ts +8 -0
- package/dist/layout-painter/renderPage.d.ts +29 -1
- package/dist/layout-painter/renderPage.js +47 -19
- package/dist/layout-painter/renderParagraph.d.ts +21 -2
- package/dist/layout-painter/renderParagraph.js +1 -1
- package/dist/layout-painter/renderTable.js +37 -4
- package/dist/managers/editorShortcuts.d.ts +33 -1
- package/dist/managers/editorShortcuts.js +20 -1
- package/dist/markdown/renderRuns.js +7 -2
- package/dist/markdown/renderTable.js +5 -1
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/pdf/contentStream.d.ts +73 -0
- package/dist/pdf/contentStream.js +143 -0
- package/dist/pdf/fonts.d.ts +96 -0
- package/dist/pdf/fonts.js +593 -0
- package/dist/pdf/images.d.ts +28 -0
- package/dist/pdf/images.js +439 -0
- package/dist/pdf/objects.d.ts +93 -0
- package/dist/pdf/objects.js +237 -0
- package/dist/pdf/pageSpace.d.ts +43 -0
- package/dist/pdf/pageSpace.js +57 -0
- package/dist/pdf/paint.d.ts +32 -0
- package/dist/pdf/paint.js +442 -0
- package/dist/pdf/writePdf.d.ts +56 -0
- package/dist/pdf/writePdf.js +345 -0
- package/dist/prosemirror/commands/comments.js +96 -6
- package/dist/prosemirror/conversion/fromProseDoc.js +41 -7
- package/dist/prosemirror/conversion/toProseDoc.js +41 -4
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +15 -2
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +35 -6
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +2 -2
- package/dist/prosemirror/extensions/features/pasteCleanup.js +4 -3
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +3 -1
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +5 -3
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -1
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +9 -3
- package/dist/prosemirror/extensions/nodes/TableExtension.js +56 -39
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +3 -1
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +5 -3
- package/dist/prosemirror/plugins/suggestionMode.js +1 -0
- package/dist/prosemirror/revisionCarriers.js +19 -2
- package/dist/prosemirror/utils/extractTrackedChanges.d.ts +2 -2
- package/dist/prosemirror/utils/extractTrackedChanges.js +59 -23
- package/dist/prosemirror/validation.js +55 -27
- package/dist/prosemirror/zeroWidthAnchors.d.ts +10 -0
- package/dist/prosemirror/zeroWidthAnchors.js +23 -0
- package/dist/redline.js +12 -79
- package/dist/server.d.ts +3 -3
- package/dist/server.js +2 -2
- package/dist/shaping/placeRun.d.ts +51 -0
- package/dist/shaping/placeRun.js +84 -0
- package/dist/shaping/shaper.d.ts +92 -0
- package/dist/shaping/shaper.js +102 -0
- package/dist/types/block-id.d.ts +18 -2
- package/dist/types/block-id.js +49 -5
- package/dist/types/content.d.ts +2 -2
- package/dist/utils/clipboard.js +4 -3
- package/dist/utils/fontResolver.js +183 -0
- package/dist/utils/htmlComments.d.ts +17 -0
- package/dist/utils/htmlComments.js +22 -0
- package/dist/utils/scriptSegments.d.ts +12 -1
- package/dist/utils/scriptSegments.js +17 -1
- package/dist/version-comparison.d.ts +1 -1
- package/package.json +7 -3
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import JSZip from "jszip";
|
|
2
|
+
//#region src/compare/__fixtures__/numbered-list.ts
|
|
3
|
+
/**
|
|
4
|
+
* A small multi-level numbered list, authored here rather than committed as
|
|
5
|
+
* bytes.
|
|
6
|
+
*
|
|
7
|
+
* The corpus fixtures carry no numbering at all, so a probe that says "an added
|
|
8
|
+
* list item" while editing an unnumbered paragraph is testing something else
|
|
9
|
+
* and saying it is testing numbering. Generating the package keeps its content
|
|
10
|
+
* legible in the diff and keeps the provenance trivial.
|
|
11
|
+
*/
|
|
12
|
+
const NAMESPACE = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
13
|
+
const RELATIONSHIPS = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
14
|
+
const OFFICE_RELATIONSHIPS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
15
|
+
const WORDPROCESSING = "application/vnd.openxmlformats-officedocument.wordprocessingml";
|
|
16
|
+
/**
|
|
17
|
+
* `createFolders: false` because JSZip stamps the folder entries it
|
|
18
|
+
* synthesizes with `new Date()`, which the fixed date above does not reach.
|
|
19
|
+
*/
|
|
20
|
+
const ZIP_ENTRY_OPTIONS = {
|
|
21
|
+
date: new Date(Date.UTC(2e3, 0, 1)),
|
|
22
|
+
createFolders: false
|
|
23
|
+
};
|
|
24
|
+
const LIST_LEVELS = 3;
|
|
25
|
+
const abstractLevels = (topLevelFormat) => Array.from({ length: LIST_LEVELS }, (_unused, level) => {
|
|
26
|
+
return `<w:lvl w:ilvl="${String(level)}"><w:start w:val="1"/><w:numFmt w:val="${level === 0 ? topLevelFormat : "lowerLetter"}"/><w:lvlText w:val="%${String(level + 1)}."/><w:lvlJc w:val="left"/></w:lvl>`;
|
|
27
|
+
}).join("");
|
|
28
|
+
const listParagraph = (level, text) => {
|
|
29
|
+
return `<w:p><w:pPr><w:pStyle w:val="ListParagraph"/>${level === null ? "" : `<w:numPr><w:ilvl w:val="${String(level)}"/><w:numId w:val="1"/></w:numPr>`}</w:pPr><w:r><w:t xml:space="preserve">${text}</w:t></w:r></w:p>`;
|
|
30
|
+
};
|
|
31
|
+
const NUMBERED_LIST_ITEMS = Object.freeze([
|
|
32
|
+
{
|
|
33
|
+
level: 0,
|
|
34
|
+
text: "The supplier shall deliver the goods to the named place."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
level: 1,
|
|
38
|
+
text: "Delivery is complete on unloading at that place."
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
level: 1,
|
|
42
|
+
text: "Risk passes to the buyer on completion of delivery."
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
level: 0,
|
|
46
|
+
text: "The buyer shall pay within thirty days of the invoice date."
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
level: 1,
|
|
50
|
+
text: "Late payment carries interest at the statutory rate."
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
level: 0,
|
|
54
|
+
text: "Either party may terminate for material breach."
|
|
55
|
+
}
|
|
56
|
+
]);
|
|
57
|
+
/** The same list with one item demoted one level: a level change and no text change. */
|
|
58
|
+
const withItemDemoted = (items, index) => items.map((item, at) => at === index ? {
|
|
59
|
+
level: Math.min(LIST_LEVELS - 1, (item.level ?? 0) + 1),
|
|
60
|
+
text: item.text
|
|
61
|
+
} : item);
|
|
62
|
+
/**
|
|
63
|
+
* The same list with one item no longer numbered: the paragraph keeps every
|
|
64
|
+
* word and stops being a list item, which is the edit a level change cannot
|
|
65
|
+
* express.
|
|
66
|
+
*/
|
|
67
|
+
const withItemUnnumbered = (items, index) => items.map((item, at) => at === index ? {
|
|
68
|
+
level: null,
|
|
69
|
+
text: item.text
|
|
70
|
+
} : item);
|
|
71
|
+
const buildNumberedListDocx = async (items = NUMBERED_LIST_ITEMS, { format = "decimal" } = {}) => {
|
|
72
|
+
const parts = {
|
|
73
|
+
"[Content_Types].xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="${WORDPROCESSING}.document.main+xml"/><Override PartName="/word/styles.xml" ContentType="${WORDPROCESSING}.styles+xml"/><Override PartName="/word/numbering.xml" ContentType="${WORDPROCESSING}.numbering+xml"/></Types>`,
|
|
74
|
+
"_rels/.rels": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="${RELATIONSHIPS}"><Relationship Id="rId1" Type="${OFFICE_RELATIONSHIPS}/officeDocument" Target="word/document.xml"/></Relationships>`,
|
|
75
|
+
"word/_rels/document.xml.rels": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="${RELATIONSHIPS}"><Relationship Id="rId1" Type="${OFFICE_RELATIONSHIPS}/styles" Target="styles.xml"/><Relationship Id="rId2" Type="${OFFICE_RELATIONSHIPS}/numbering" Target="numbering.xml"/></Relationships>`,
|
|
76
|
+
"word/styles.xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:styles xmlns:w="${NAMESPACE}"><w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/></w:style><w:style w:type="paragraph" w:styleId="ListParagraph"><w:name w:val="List Paragraph"/></w:style></w:styles>`,
|
|
77
|
+
"word/numbering.xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:numbering xmlns:w="${NAMESPACE}"><w:abstractNum w:abstractNumId="0">${abstractLevels(format)}</w:abstractNum><w:num w:numId="1"><w:abstractNumId w:val="0"/></w:num></w:numbering>`,
|
|
78
|
+
"word/document.xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document xmlns:w="${NAMESPACE}"><w:body>` + items.map(({ level, text }) => listParagraph(level, text)).join("") + "<w:sectPr><w:pgSz w:w=\"12240\" w:h=\"15840\"/><w:pgMar w:top=\"1440\" w:right=\"1440\" w:bottom=\"1440\" w:left=\"1440\"/></w:sectPr></w:body></w:document>"
|
|
79
|
+
};
|
|
80
|
+
const zip = new JSZip();
|
|
81
|
+
for (const name of Object.keys(parts).toSorted()) zip.file(name, parts[name] ?? "", ZIP_ENTRY_OPTIONS);
|
|
82
|
+
return await zip.generateAsync({ type: "arraybuffer" });
|
|
83
|
+
};
|
|
84
|
+
//#endregion
|
|
85
|
+
export { NUMBERED_LIST_ITEMS, buildNumberedListDocx, withItemDemoted, withItemUnnumbered };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FolioAIBlock, FolioAIBlockTableLocation } from "../ai-edits/types.js";
|
|
2
|
+
//#region src/compare/column-alignment.d.ts
|
|
3
|
+
type TableColumnAlignmentStep = {
|
|
4
|
+
type: "baseColumn";
|
|
5
|
+
blocks: readonly FolioAIBlock[];
|
|
6
|
+
location: FolioAIBlockTableLocation;
|
|
7
|
+
columnIndex: number;
|
|
8
|
+
} | {
|
|
9
|
+
type: "targetColumn";
|
|
10
|
+
blocks: readonly FolioAIBlock[];
|
|
11
|
+
location: FolioAIBlockTableLocation;
|
|
12
|
+
columnIndex: number;
|
|
13
|
+
anchor: {
|
|
14
|
+
blockId: string;
|
|
15
|
+
position: "after" | "before";
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
type TableColumnAlignment = {
|
|
19
|
+
steps: TableColumnAlignmentStep[];
|
|
20
|
+
baseBlocks: FolioAIBlock[];
|
|
21
|
+
targetBlocks: FolioAIBlock[];
|
|
22
|
+
baseColumnKeys: ReadonlyMap<number, number>;
|
|
23
|
+
targetColumnKeys: ReadonlyMap<number, number>;
|
|
24
|
+
};
|
|
25
|
+
declare const alignTableColumns: (baseBlocks: readonly FolioAIBlock[], targetBlocks: readonly FolioAIBlock[]) => TableColumnAlignment | null;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { TableColumnAlignment, TableColumnAlignmentStep, alignTableColumns };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { panic } from "better-result";
|
|
2
|
+
//#region src/compare/column-alignment.ts
|
|
3
|
+
const MAX_TABLE_GRID_COLUMNS = 63;
|
|
4
|
+
const MAX_TABLE_GRID_AREA = 1e6;
|
|
5
|
+
const tableGridColumns = (blocks) => {
|
|
6
|
+
const cellsByPhysicalLocation = /* @__PURE__ */ new Map();
|
|
7
|
+
let width = 0;
|
|
8
|
+
let height = 0;
|
|
9
|
+
for (const block of blocks) {
|
|
10
|
+
const location = block.table;
|
|
11
|
+
if (!location) return null;
|
|
12
|
+
const key = `${String(location.rowIndex)}:${String(location.cellIndex)}`;
|
|
13
|
+
const existing = cellsByPhysicalLocation.get(key);
|
|
14
|
+
if (existing) {
|
|
15
|
+
existing.blocks.push(block);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const right = location.gridColumnIndex + location.columnSpan;
|
|
19
|
+
const bottom = location.rowIndex + location.rowSpan;
|
|
20
|
+
if (location.gridColumnIndex < 0 || location.rowIndex < 0 || location.columnSpan < 1 || location.rowSpan < 1 || right > MAX_TABLE_GRID_COLUMNS) return null;
|
|
21
|
+
cellsByPhysicalLocation.set(key, {
|
|
22
|
+
blocks: [block],
|
|
23
|
+
rowIndex: location.rowIndex,
|
|
24
|
+
gridColumnIndex: location.gridColumnIndex,
|
|
25
|
+
columnSpan: location.columnSpan,
|
|
26
|
+
rowSpan: location.rowSpan
|
|
27
|
+
});
|
|
28
|
+
width = Math.max(width, right);
|
|
29
|
+
height = Math.max(height, bottom);
|
|
30
|
+
}
|
|
31
|
+
if (width === 0 || height === 0 || width * height > MAX_TABLE_GRID_AREA) return null;
|
|
32
|
+
const grid = Array.from({ length: height }, () => Array.from({ length: width }));
|
|
33
|
+
const ownedCellsByColumn = Array.from({ length: width }, () => []);
|
|
34
|
+
for (const cell of cellsByPhysicalLocation.values()) {
|
|
35
|
+
ownedCellsByColumn[cell.gridColumnIndex]?.push(cell);
|
|
36
|
+
for (let row = cell.rowIndex; row < cell.rowIndex + cell.rowSpan; row++) for (let column = cell.gridColumnIndex; column < cell.gridColumnIndex + cell.columnSpan; column++) {
|
|
37
|
+
const gridRow = grid[row];
|
|
38
|
+
if (!gridRow || gridRow[column] !== void 0) return null;
|
|
39
|
+
gridRow[column] = cell;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return Array.from({ length: width }, (_unused, columnIndex) => {
|
|
43
|
+
const structuralRows = grid.map((row) => {
|
|
44
|
+
const cell = row[columnIndex];
|
|
45
|
+
return cell ? [
|
|
46
|
+
columnIndex - cell.gridColumnIndex,
|
|
47
|
+
cell.columnSpan,
|
|
48
|
+
cell.rowIndex,
|
|
49
|
+
cell.rowSpan,
|
|
50
|
+
cell.blocks.map(({ text }) => text)
|
|
51
|
+
] : null;
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
index: columnIndex,
|
|
55
|
+
signature: JSON.stringify(structuralRows),
|
|
56
|
+
ownedCells: ownedCellsByColumn[columnIndex] ?? []
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
/** The sole exact ordered embedding of `shorter` in `wider`, or null when ambiguous. */
|
|
61
|
+
const uniqueColumnEmbedding = (shorter, wider) => {
|
|
62
|
+
const counts = Array.from({ length: shorter.length + 1 }, () => Array.from({ length: wider.length + 1 }, () => 0));
|
|
63
|
+
for (let wideIndex = 0; wideIndex <= wider.length; wideIndex++) {
|
|
64
|
+
const finalRow = counts[shorter.length];
|
|
65
|
+
if (finalRow) finalRow[wideIndex] = 1;
|
|
66
|
+
}
|
|
67
|
+
for (let shortIndex = shorter.length - 1; shortIndex >= 0; shortIndex--) for (let wideIndex = wider.length - 1; wideIndex >= 0; wideIndex--) {
|
|
68
|
+
const skip = counts[shortIndex]?.[wideIndex + 1] ?? 0;
|
|
69
|
+
const match = shorter[shortIndex]?.signature === wider[wideIndex]?.signature ? counts[shortIndex + 1]?.[wideIndex + 1] ?? 0 : 0;
|
|
70
|
+
const row = counts[shortIndex];
|
|
71
|
+
if (row) row[wideIndex] = Math.min(2, skip + match);
|
|
72
|
+
}
|
|
73
|
+
if (counts[0]?.[0] !== 1) return null;
|
|
74
|
+
const mapping = [];
|
|
75
|
+
let shortIndex = 0;
|
|
76
|
+
let wideIndex = 0;
|
|
77
|
+
while (shortIndex < shorter.length) {
|
|
78
|
+
if (wideIndex >= wider.length) return panic("A unique column embedding ended before every column was mapped");
|
|
79
|
+
const canMatch = shorter[shortIndex]?.signature === wider[wideIndex]?.signature && (counts[shortIndex + 1]?.[wideIndex + 1] ?? 0) > 0;
|
|
80
|
+
const canSkip = (counts[shortIndex]?.[wideIndex + 1] ?? 0) > 0;
|
|
81
|
+
if (canMatch && !canSkip) {
|
|
82
|
+
mapping.push(wideIndex);
|
|
83
|
+
shortIndex += 1;
|
|
84
|
+
}
|
|
85
|
+
wideIndex += 1;
|
|
86
|
+
}
|
|
87
|
+
return mapping;
|
|
88
|
+
};
|
|
89
|
+
const columnOwnedBlocks = ({ ownedCells }) => {
|
|
90
|
+
if (ownedCells.length === 0 || ownedCells.some(({ columnSpan }) => columnSpan !== 1)) return null;
|
|
91
|
+
return ownedCells.flatMap(({ blocks }) => blocks);
|
|
92
|
+
};
|
|
93
|
+
const alignTableColumns = (baseBlocks, targetBlocks) => {
|
|
94
|
+
const baseColumns = tableGridColumns(baseBlocks);
|
|
95
|
+
const targetColumns = tableGridColumns(targetBlocks);
|
|
96
|
+
if (!baseColumns || !targetColumns || baseColumns.length === targetColumns.length) return null;
|
|
97
|
+
const targetIsWider = targetColumns.length > baseColumns.length;
|
|
98
|
+
const mapping = uniqueColumnEmbedding(targetIsWider ? baseColumns : targetColumns, targetIsWider ? targetColumns : baseColumns);
|
|
99
|
+
if (!mapping) return null;
|
|
100
|
+
const widerColumns = targetIsWider ? targetColumns : baseColumns;
|
|
101
|
+
const mapped = new Set(mapping);
|
|
102
|
+
const unmatchedColumns = widerColumns.filter((_column, index) => !mapped.has(index));
|
|
103
|
+
const ownedColumns = [];
|
|
104
|
+
for (const column of unmatchedColumns) {
|
|
105
|
+
const blocks = columnOwnedBlocks(column);
|
|
106
|
+
if (!blocks) return null;
|
|
107
|
+
ownedColumns.push({
|
|
108
|
+
column,
|
|
109
|
+
blocks
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (ownedColumns.length === 0 || targetIsWider && unmatchedColumns.some(({ ownedCells }) => ownedCells.some(({ rowSpan }) => rowSpan !== 1))) return null;
|
|
113
|
+
const unmatchedIds = new Set(ownedColumns.flatMap(({ blocks }) => blocks.map(({ id }) => id)));
|
|
114
|
+
const baseColumnKeys = /* @__PURE__ */ new Map();
|
|
115
|
+
const targetColumnKeys = /* @__PURE__ */ new Map();
|
|
116
|
+
mapping.forEach((wideIndex, shortIndex) => {
|
|
117
|
+
if (targetIsWider) {
|
|
118
|
+
baseColumnKeys.set(shortIndex, shortIndex);
|
|
119
|
+
targetColumnKeys.set(wideIndex, shortIndex);
|
|
120
|
+
} else {
|
|
121
|
+
baseColumnKeys.set(wideIndex, shortIndex);
|
|
122
|
+
targetColumnKeys.set(shortIndex, shortIndex);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
const steps = [];
|
|
126
|
+
if (!targetIsWider) for (const { column, blocks } of ownedColumns) {
|
|
127
|
+
const location = blocks.at(0)?.table;
|
|
128
|
+
if (!location) return null;
|
|
129
|
+
steps.push({
|
|
130
|
+
type: "baseColumn",
|
|
131
|
+
blocks,
|
|
132
|
+
location,
|
|
133
|
+
columnIndex: column.index
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
else for (const { column, blocks } of ownedColumns) {
|
|
137
|
+
const location = blocks.at(0)?.table;
|
|
138
|
+
const rightBaseIndex = mapping.findIndex((targetIndex) => targetIndex > column.index);
|
|
139
|
+
const leftBaseIndex = mapping.findLastIndex((targetIndex) => targetIndex < column.index);
|
|
140
|
+
const anchorBlock = (rightBaseIndex >= 0 ? baseColumns[rightBaseIndex] : baseColumns[leftBaseIndex])?.ownedCells.at(0)?.blocks.at(0);
|
|
141
|
+
if (!location || !anchorBlock) return null;
|
|
142
|
+
steps.push({
|
|
143
|
+
type: "targetColumn",
|
|
144
|
+
blocks,
|
|
145
|
+
location,
|
|
146
|
+
columnIndex: column.index,
|
|
147
|
+
anchor: {
|
|
148
|
+
blockId: anchorBlock.id,
|
|
149
|
+
position: rightBaseIndex >= 0 ? "before" : "after"
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
steps,
|
|
155
|
+
baseBlocks: baseBlocks.filter(({ id }) => targetIsWider || !unmatchedIds.has(id)),
|
|
156
|
+
targetBlocks: targetBlocks.filter(({ id }) => !targetIsWider || !unmatchedIds.has(id)),
|
|
157
|
+
baseColumnKeys,
|
|
158
|
+
targetColumnKeys
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
//#endregion
|
|
162
|
+
export { alignTableColumns };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { FolioAIEditSnapshot } from "../ai-edits/types.js";
|
|
2
|
+
import { WordDiffGranularity } from "../ai-edits/word-diff.js";
|
|
3
|
+
import { FolioRevisionStamp } from "../ai-edits/apply.js";
|
|
4
|
+
import { FolioDocumentStoryHandle, FolioDocxReviewer } from "../ai-edits/headless.js";
|
|
5
|
+
import { CompareVerification } from "./verification.js";
|
|
6
|
+
import { CompareChange, CompareDocxApplyError, CompareDocxError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxOptions, CompareDocxParseError, CompareDocxSerializeError, CompareResult, CompareUnsupportedPart, InvalidCompareDocxOptionsError } from "./types.js";
|
|
7
|
+
import { CompareStoryPlan } from "./plan.js";
|
|
8
|
+
import { Result } from "better-result";
|
|
9
|
+
//#region src/compare/compare.d.ts
|
|
10
|
+
/**
|
|
11
|
+
* Cap on operations one comparison generates. Both inputs are untrusted
|
|
12
|
+
* documents, and each operation costs a document walk plus revision ids.
|
|
13
|
+
*/
|
|
14
|
+
declare const MAX_COMPARE_OPERATIONS = 10000;
|
|
15
|
+
/** Two stories the comparison will align against one another. */
|
|
16
|
+
type ComparedStoryPair = {
|
|
17
|
+
baseStory: FolioDocumentStoryHandle;
|
|
18
|
+
targetStory: FolioDocumentStoryHandle;
|
|
19
|
+
baseSnapshot: FolioAIEditSnapshot;
|
|
20
|
+
targetSnapshot: FolioAIEditSnapshot;
|
|
21
|
+
};
|
|
22
|
+
/** Everything the later stages need, and nothing they have to re-derive. */
|
|
23
|
+
type ParsedComparison = {
|
|
24
|
+
/** Token size a changed paragraph's redline is cut at. */
|
|
25
|
+
granularity: WordDiffGranularity;
|
|
26
|
+
/**
|
|
27
|
+
* The base package as it arrived. It is the result when nothing changed, but
|
|
28
|
+
* only when it carried no revisions of its own: otherwise the compared base
|
|
29
|
+
* is its accepted view and these bytes are a different document.
|
|
30
|
+
*/
|
|
31
|
+
baseBuffer: ArrayBuffer;
|
|
32
|
+
baseCarriedRevisions: boolean;
|
|
33
|
+
reviewer: FolioDocxReviewer;
|
|
34
|
+
targetReviewer: FolioDocxReviewer;
|
|
35
|
+
revisionStamp: FolioRevisionStamp;
|
|
36
|
+
packageDate: Date;
|
|
37
|
+
pairs: readonly ComparedStoryPair[];
|
|
38
|
+
/** Package-level numbering differences, which belong to no story. */
|
|
39
|
+
numberingChanges: readonly CompareChange[];
|
|
40
|
+
unsupported: readonly CompareUnsupportedPart[];
|
|
41
|
+
};
|
|
42
|
+
/** Stage 1: both packages to editor models, paired story by story. */
|
|
43
|
+
declare const parseComparison: (base: ArrayBuffer, target: ArrayBuffer, options: CompareDocxOptions) => Promise<Result<ParsedComparison, CompareDocxParseError | InvalidCompareDocxOptionsError>>;
|
|
44
|
+
/** One story's plan, kept with the pair it belongs to. */
|
|
45
|
+
type PlannedStoryComparison = {
|
|
46
|
+
pair: ComparedStoryPair;
|
|
47
|
+
plan: CompareStoryPlan;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Stage 2: align every paired story and derive its operations. Pure — no
|
|
51
|
+
* parsing, no serialization, no clock.
|
|
52
|
+
*/
|
|
53
|
+
declare const planComparison: ({ pairs }: ParsedComparison) => Result<readonly PlannedStoryComparison[], CompareDocxOperationLimitError>;
|
|
54
|
+
/** What stage 3 produced: the change list, and whether it was proven. */
|
|
55
|
+
type AppliedComparison = {
|
|
56
|
+
changes: readonly CompareChange[];
|
|
57
|
+
verification: CompareVerification;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Stage 3: write the planned operations into the base document as tracked
|
|
61
|
+
* changes, then check the work rather than trust it. Both directions of the
|
|
62
|
+
* round trip are checked, structure included: accepting the story's generated
|
|
63
|
+
* revisions must reproduce the target, and rejecting them must reproduce the
|
|
64
|
+
* base it was compared from. A difference the operation vocabulary cannot
|
|
65
|
+
* express would otherwise leave a redline that reads plausibly and is wrong.
|
|
66
|
+
*
|
|
67
|
+
* The check reports rather than throws. {@link compareDocx} decides what to do
|
|
68
|
+
* with an unverified result, because "give me your best attempt and tell me
|
|
69
|
+
* what you could not represent" and "give me nothing unless you can prove it"
|
|
70
|
+
* are both legitimate asks and only the caller knows which one it is making.
|
|
71
|
+
*/
|
|
72
|
+
declare const applyComparison: ({ reviewer, revisionStamp, granularity, numberingChanges }: ParsedComparison, planned: readonly PlannedStoryComparison[]) => Result<AppliedComparison, CompareDocxApplyError>;
|
|
73
|
+
/**
|
|
74
|
+
* Stage 4: the result package, with every ZIP entry date pinned.
|
|
75
|
+
*
|
|
76
|
+
* A comparison that found nothing returns the base bytes as they arrived. A
|
|
77
|
+
* change is only ever reported alongside the operations that realize it, so no
|
|
78
|
+
* operations means no changes, and re-serializing then rewrites a document
|
|
79
|
+
* nobody edited: on a 2,200-block pair that was a second of work to reproduce
|
|
80
|
+
* the input.
|
|
81
|
+
*
|
|
82
|
+
* Unless the base carried revisions of its own. Then the compared base was its
|
|
83
|
+
* accepted view, the arriving bytes are a different document, and handing them
|
|
84
|
+
* back would make rejecting the result land before the previous reviewer's
|
|
85
|
+
* edits rather than after them.
|
|
86
|
+
*
|
|
87
|
+
* The short-circuit lives here rather than in {@link compareDocx} so that
|
|
88
|
+
* every caller of the stages sees the same decision. Putting it in the
|
|
89
|
+
* composition let the benchmark's own composition disagree with the shipped
|
|
90
|
+
* one within a single run.
|
|
91
|
+
*/
|
|
92
|
+
declare const serializeComparison: ({ baseBuffer, baseCarriedRevisions, reviewer, packageDate }: ParsedComparison, planned: readonly PlannedStoryComparison[]) => Promise<Result<ArrayBuffer, CompareDocxSerializeError | CompareDocxFinalParagraphMarkError>>;
|
|
93
|
+
/**
|
|
94
|
+
* Compare `base` against `target` and return `base` carrying the tracked
|
|
95
|
+
* changes that turn it into `target`, alongside the change list describing
|
|
96
|
+
* them.
|
|
97
|
+
*
|
|
98
|
+
* The result is verified by default: a redline whose round trip cannot be
|
|
99
|
+
* proven is refused rather than returned, because one that reads plausibly and
|
|
100
|
+
* is wrong is worse than none. `onUnverified: "emit"` asks for the opposite
|
|
101
|
+
* trade — the best redline available, plus the typed list of what could not be
|
|
102
|
+
* represented — for a caller that would rather show something and say what is
|
|
103
|
+
* missing.
|
|
104
|
+
*/
|
|
105
|
+
declare const compareDocx: (base: ArrayBuffer, target: ArrayBuffer, options: CompareDocxOptions) => Promise<Result<CompareResult, CompareDocxError>>;
|
|
106
|
+
//#endregion
|
|
107
|
+
export { AppliedComparison, ComparedStoryPair, MAX_COMPARE_OPERATIONS, ParsedComparison, PlannedStoryComparison, applyComparison, compareDocx, parseComparison, planComparison, serializeComparison };
|