@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,345 @@
|
|
|
1
|
+
import { getShaper } from "../shaping/shaper.js";
|
|
2
|
+
import { collectResources, renderContentStream } from "./contentStream.js";
|
|
3
|
+
import { prepareFonts } from "./fonts.js";
|
|
4
|
+
import { decodeImage } from "./images.js";
|
|
5
|
+
import { createPdfDocument, pdfArray, pdfAsciiString, pdfDict, pdfFlateStream, pdfName, pdfNull, pdfNumber, pdfNumberArray, pdfStream, pdfTextString } from "./objects.js";
|
|
6
|
+
import { displayPointToPdf, pxToPt } from "./pageSpace.js";
|
|
7
|
+
import { collectUsage, paintPage } from "./paint.js";
|
|
8
|
+
import { Result, TaggedError } from "better-result";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
//#region src/pdf/writePdf.ts
|
|
11
|
+
/**
|
|
12
|
+
* The PDF backend's entry point.
|
|
13
|
+
*
|
|
14
|
+
* Two calls with the same display list and the same timestamp produce the
|
|
15
|
+
* same bytes. That is a hard requirement rather than a nicety: it is what
|
|
16
|
+
* makes an export diffable, cacheable and comparable against a golden file,
|
|
17
|
+
* and every design decision below that looks fussy (one number formatter,
|
|
18
|
+
* resource names from sorted keys, a `/ID` hashed from the body, a fixed
|
|
19
|
+
* deflate level) exists to keep it true. Nothing here reads a clock or a
|
|
20
|
+
* random source.
|
|
21
|
+
*/
|
|
22
|
+
var WritePdfError = class extends TaggedError("WritePdfError") {};
|
|
23
|
+
const DEFAULT_PRODUCER = "folio";
|
|
24
|
+
/** How many code points the strict-coverage refusal names before counting. */
|
|
25
|
+
const UNENCODABLE_SAMPLE = 5;
|
|
26
|
+
const HEX_DIGITS = 4;
|
|
27
|
+
const formatCodePoint = (codePoint) => `U+${codePoint.toString(16).toUpperCase().padStart(HEX_DIGITS, "0")}`;
|
|
28
|
+
const describeUnencodable = (points) => {
|
|
29
|
+
const named = points.slice(0, UNENCODABLE_SAMPLE).map(({ codePoint }) => formatCodePoint(codePoint)).join(", ");
|
|
30
|
+
const rest = points.length - Math.min(points.length, UNENCODABLE_SAMPLE);
|
|
31
|
+
const suffix = rest === 0 ? "" : `, and ${String(rest)} more`;
|
|
32
|
+
return `no supplied face can encode ${String(points.length)} code point${points.length === 1 ? "" : "s"}: ${named}${suffix}`;
|
|
33
|
+
};
|
|
34
|
+
/** Every `/ProcSet` folio's own output can need. */
|
|
35
|
+
const PROC_SET = [
|
|
36
|
+
"PDF",
|
|
37
|
+
"Text",
|
|
38
|
+
"ImageB",
|
|
39
|
+
"ImageC",
|
|
40
|
+
"ImageI"
|
|
41
|
+
];
|
|
42
|
+
const pad = (value, width) => String(value).padStart(width, "0");
|
|
43
|
+
const YEAR_DIGITS = 4;
|
|
44
|
+
const FIELD_DIGITS = 2;
|
|
45
|
+
/**
|
|
46
|
+
* `D:YYYYMMDDHHmmSS+00'00'`. The instant is written in UTC whatever offset
|
|
47
|
+
* the caller's ISO string carried: one instant has one representation here,
|
|
48
|
+
* so two callers who name the same moment get the same bytes.
|
|
49
|
+
*/
|
|
50
|
+
const formatPdfDate = (timestamp) => {
|
|
51
|
+
const milliseconds = Date.parse(timestamp);
|
|
52
|
+
if (Number.isNaN(milliseconds)) return Result.err(new WritePdfError({ message: `timestamp is not an ISO-8601 instant: ${timestamp}` }));
|
|
53
|
+
const date = new Date(milliseconds);
|
|
54
|
+
return Result.ok(`D:${pad(date.getUTCFullYear(), YEAR_DIGITS)}${pad(date.getUTCMonth() + 1, FIELD_DIGITS)}${pad(date.getUTCDate(), FIELD_DIGITS)}${pad(date.getUTCHours(), FIELD_DIGITS)}${pad(date.getUTCMinutes(), FIELD_DIGITS)}${pad(date.getUTCSeconds(), FIELD_DIGITS)}+00'00'`);
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* A `/URI` is a byte string, so anything above ASCII is percent-encoded as
|
|
58
|
+
* UTF-8 rather than written raw.
|
|
59
|
+
*/
|
|
60
|
+
const asciiUri = (href) => {
|
|
61
|
+
const LAST_ASCII = 126;
|
|
62
|
+
let out = "";
|
|
63
|
+
for (const character of href) out += (character.codePointAt(0) ?? 0) <= LAST_ASCII ? character : encodeURIComponent(character);
|
|
64
|
+
return out;
|
|
65
|
+
};
|
|
66
|
+
const buildImageObject = (document, samples) => {
|
|
67
|
+
const alphaRef = samples.alpha === void 0 ? void 0 : document.add(pdfFlateStream([
|
|
68
|
+
["Type", pdfName("XObject")],
|
|
69
|
+
["Subtype", pdfName("Image")],
|
|
70
|
+
["Width", pdfNumber(samples.widthPx)],
|
|
71
|
+
["Height", pdfNumber(samples.heightPx)],
|
|
72
|
+
["ColorSpace", pdfName("DeviceGray")],
|
|
73
|
+
["BitsPerComponent", pdfNumber(samples.alpha.bitsPerComponent)]
|
|
74
|
+
], samples.alpha.data));
|
|
75
|
+
const entries = [
|
|
76
|
+
["Type", pdfName("XObject")],
|
|
77
|
+
["Subtype", pdfName("Image")],
|
|
78
|
+
["Width", pdfNumber(samples.widthPx)],
|
|
79
|
+
["Height", pdfNumber(samples.heightPx)],
|
|
80
|
+
["ColorSpace", samples.colorSpace],
|
|
81
|
+
["BitsPerComponent", pdfNumber(samples.bitsPerComponent)],
|
|
82
|
+
["Decode", samples.decode === void 0 ? void 0 : pdfNumberArray(samples.decode)],
|
|
83
|
+
["SMask", alphaRef]
|
|
84
|
+
];
|
|
85
|
+
return samples.encoding === "jpeg" ? document.add(pdfStream(pdfDict([...entries, ["Filter", pdfName("DCTDecode")]]), samples.data)) : document.add(pdfFlateStream(entries, samples.data));
|
|
86
|
+
};
|
|
87
|
+
const buildResources = ({ resources, fontRefs, imageRefs }) => {
|
|
88
|
+
const fontEntries = [...resources.fontNames].map(([resourceIndex, name]) => [name, fontRefs.get(resourceIndex)]);
|
|
89
|
+
const imageEntries = [...resources.imageNames].map(([imageIndex, name]) => [name, imageRefs.get(imageIndex)]);
|
|
90
|
+
const stateEntries = [...resources.extGStateNames].map(([alpha, name]) => [name, pdfDict([
|
|
91
|
+
["Type", pdfName("ExtGState")],
|
|
92
|
+
["ca", pdfNumber(Number(alpha))],
|
|
93
|
+
["CA", pdfNumber(Number(alpha))]
|
|
94
|
+
])]);
|
|
95
|
+
return pdfDict([
|
|
96
|
+
["ProcSet", pdfArray(PROC_SET.map(pdfName))],
|
|
97
|
+
["Font", fontEntries.length === 0 ? void 0 : pdfDict(fontEntries)],
|
|
98
|
+
["XObject", imageEntries.length === 0 ? void 0 : pdfDict(imageEntries)],
|
|
99
|
+
["ExtGState", stateEntries.length === 0 ? void 0 : pdfDict(stateEntries)]
|
|
100
|
+
]);
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* `/XYZ` with a null x and zoom: the reader scrolls the point to the top of
|
|
104
|
+
* the window and leaves the horizontal position and magnification alone.
|
|
105
|
+
*/
|
|
106
|
+
const buildDestination = ({ pageRefs, pages, pageIndex, yPx }) => {
|
|
107
|
+
const pageRef = pageRefs[pageIndex];
|
|
108
|
+
const page = pages[pageIndex];
|
|
109
|
+
if (pageRef === void 0 || page === void 0) return Result.err(new WritePdfError({ message: `destination names page ${String(pageIndex)}, which does not exist` }));
|
|
110
|
+
const point = displayPointToPdf(page.heightPx, 0, yPx);
|
|
111
|
+
return Result.ok(pdfArray([
|
|
112
|
+
pageRef,
|
|
113
|
+
pdfName("XYZ"),
|
|
114
|
+
pdfNull,
|
|
115
|
+
pdfNumber(point.y),
|
|
116
|
+
pdfNull
|
|
117
|
+
]));
|
|
118
|
+
};
|
|
119
|
+
const buildAnnotation = ({ document, link, page, pages, pageRefs }) => {
|
|
120
|
+
const topLeft = displayPointToPdf(page.heightPx, link.rect.xPx, link.rect.yPx);
|
|
121
|
+
const bottomRight = displayPointToPdf(page.heightPx, link.rect.xPx + link.rect.widthPx, link.rect.yPx + link.rect.heightPx);
|
|
122
|
+
let action;
|
|
123
|
+
let destination;
|
|
124
|
+
switch (link.target.kind) {
|
|
125
|
+
case "external":
|
|
126
|
+
action = pdfDict([["S", pdfName("URI")], ["URI", pdfAsciiString(asciiUri(link.target.href))]]);
|
|
127
|
+
break;
|
|
128
|
+
case "page": {
|
|
129
|
+
const built = buildDestination({
|
|
130
|
+
pageRefs,
|
|
131
|
+
pages,
|
|
132
|
+
pageIndex: link.target.pageIndex,
|
|
133
|
+
yPx: link.target.yPx
|
|
134
|
+
});
|
|
135
|
+
if (built.isErr()) return Result.err(built.error);
|
|
136
|
+
destination = built.value;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
default: {
|
|
140
|
+
const unreachable = link.target;
|
|
141
|
+
return Result.err(new WritePdfError({ message: `unhandled link target: ${JSON.stringify(unreachable)}` }));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return Result.ok(document.add(pdfDict([
|
|
145
|
+
["Type", pdfName("Annot")],
|
|
146
|
+
["Subtype", pdfName("Link")],
|
|
147
|
+
["Rect", pdfNumberArray([
|
|
148
|
+
Math.min(topLeft.x, bottomRight.x),
|
|
149
|
+
Math.min(topLeft.y, bottomRight.y),
|
|
150
|
+
Math.max(topLeft.x, bottomRight.x),
|
|
151
|
+
Math.max(topLeft.y, bottomRight.y)
|
|
152
|
+
])],
|
|
153
|
+
["Border", pdfNumberArray([
|
|
154
|
+
0,
|
|
155
|
+
0,
|
|
156
|
+
0
|
|
157
|
+
])],
|
|
158
|
+
["Contents", link.tooltip === void 0 ? void 0 : pdfTextString(link.tooltip)],
|
|
159
|
+
["A", action],
|
|
160
|
+
["Dest", destination]
|
|
161
|
+
])));
|
|
162
|
+
};
|
|
163
|
+
const buildOutlineTree = (entries, document) => {
|
|
164
|
+
const roots = [];
|
|
165
|
+
const ancestors = [];
|
|
166
|
+
for (const entry of entries) {
|
|
167
|
+
const node = {
|
|
168
|
+
entry,
|
|
169
|
+
children: [],
|
|
170
|
+
ref: document.allocate()
|
|
171
|
+
};
|
|
172
|
+
while ((ancestors.at(-1)?.entry.level ?? -1) >= entry.level) ancestors.pop();
|
|
173
|
+
const parent = ancestors.at(-1);
|
|
174
|
+
if (parent === void 0) roots.push(node);
|
|
175
|
+
else parent.children.push(node);
|
|
176
|
+
ancestors.push(node);
|
|
177
|
+
}
|
|
178
|
+
return roots;
|
|
179
|
+
};
|
|
180
|
+
/** Every node is open, so a node's `/Count` is its whole subtree. */
|
|
181
|
+
const openDescendantCount = (nodes) => nodes.reduce((total, node) => total + 1 + openDescendantCount(node.children), 0);
|
|
182
|
+
const defineOutlineNodes = ({ document, nodes, parentRef, pages, pageRefs }) => {
|
|
183
|
+
for (const [index, node] of nodes.entries()) {
|
|
184
|
+
const destination = buildDestination({
|
|
185
|
+
pageRefs,
|
|
186
|
+
pages,
|
|
187
|
+
pageIndex: node.entry.pageIndex,
|
|
188
|
+
yPx: node.entry.yPx
|
|
189
|
+
});
|
|
190
|
+
if (destination.isErr()) return destination.error;
|
|
191
|
+
const childCount = openDescendantCount(node.children);
|
|
192
|
+
document.define(node.ref, pdfDict([
|
|
193
|
+
["Title", pdfTextString(node.entry.title)],
|
|
194
|
+
["Parent", parentRef],
|
|
195
|
+
["Prev", nodes[index - 1]?.ref],
|
|
196
|
+
["Next", nodes[index + 1]?.ref],
|
|
197
|
+
["First", node.children.at(0)?.ref],
|
|
198
|
+
["Last", node.children.at(-1)?.ref],
|
|
199
|
+
["Count", childCount === 0 ? void 0 : pdfNumber(childCount)],
|
|
200
|
+
["Dest", destination.value]
|
|
201
|
+
]));
|
|
202
|
+
const error = defineOutlineNodes({
|
|
203
|
+
document,
|
|
204
|
+
nodes: node.children,
|
|
205
|
+
parentRef: node.ref,
|
|
206
|
+
pages,
|
|
207
|
+
pageRefs
|
|
208
|
+
});
|
|
209
|
+
if (error !== null) return error;
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
};
|
|
213
|
+
/**
|
|
214
|
+
* Writes the display list as a PDF.
|
|
215
|
+
*
|
|
216
|
+
* Asynchronous for one reason: a run in a script that shapes needs the shaper,
|
|
217
|
+
* and the shaper is fetched the first time a document actually contains one. A
|
|
218
|
+
* document in Latin, Cyrillic or Greek never awaits anything but the resolved
|
|
219
|
+
* promise this returns.
|
|
220
|
+
*/
|
|
221
|
+
const writePdf = async (list, options) => {
|
|
222
|
+
if (list.pages.length === 0) return Result.err(new WritePdfError({ message: "a PDF needs at least one page" }));
|
|
223
|
+
const stamp = formatPdfDate(options.timestamp);
|
|
224
|
+
if (stamp.isErr()) return Result.err(stamp.error);
|
|
225
|
+
const usage = collectUsage(list.pages, list.fonts.length, list.images.length);
|
|
226
|
+
if (usage.isErr()) return Result.err(new WritePdfError({
|
|
227
|
+
message: usage.error.message,
|
|
228
|
+
cause: usage.error
|
|
229
|
+
}));
|
|
230
|
+
const document = createPdfDocument();
|
|
231
|
+
const catalogRef = document.allocate();
|
|
232
|
+
const pagesRef = document.allocate();
|
|
233
|
+
const infoRef = document.allocate();
|
|
234
|
+
const pageRefs = list.pages.map(() => document.allocate());
|
|
235
|
+
const shaper = usage.value.shapedRunsByFont.size === 0 ? null : await getShaper();
|
|
236
|
+
const { byFontIndex, fontRefByResourceIndex, substitutions, unencodable } = prepareFonts({
|
|
237
|
+
document,
|
|
238
|
+
faces: list.fonts,
|
|
239
|
+
usedCodePoints: usage.value.codePointsByFont,
|
|
240
|
+
shapedRuns: usage.value.shapedRunsByFont,
|
|
241
|
+
shaper,
|
|
242
|
+
source: options.fonts
|
|
243
|
+
});
|
|
244
|
+
if (options.strictGlyphCoverage === true && unencodable.length > 0) return Result.err(new WritePdfError({ message: describeUnencodable(unencodable) }));
|
|
245
|
+
const imageRefs = /* @__PURE__ */ new Map();
|
|
246
|
+
for (const imageIndex of [...usage.value.imageIndices].sort((left, right) => left - right)) {
|
|
247
|
+
const source = list.images[imageIndex];
|
|
248
|
+
if (source === void 0) return Result.err(new WritePdfError({ message: `image ${String(imageIndex)} is outside the image table` }));
|
|
249
|
+
const decoded = decodeImage(source);
|
|
250
|
+
if (decoded.isErr()) return Result.err(new WritePdfError({
|
|
251
|
+
message: `image ${String(imageIndex)} could not be decoded: ${decoded.error.message}`,
|
|
252
|
+
cause: decoded.error
|
|
253
|
+
}));
|
|
254
|
+
imageRefs.set(imageIndex, buildImageObject(document, decoded.value));
|
|
255
|
+
}
|
|
256
|
+
const outlineRoots = buildOutlineTree(list.outline, document);
|
|
257
|
+
const outlineRef = outlineRoots.length === 0 ? void 0 : document.allocate();
|
|
258
|
+
for (const [pageIndex, page] of list.pages.entries()) {
|
|
259
|
+
const pageRef = pageRefs[pageIndex];
|
|
260
|
+
if (pageRef === void 0) return Result.err(new WritePdfError({ message: `page ${String(pageIndex)} lost its object` }));
|
|
261
|
+
const parts = paintPage({
|
|
262
|
+
page,
|
|
263
|
+
fonts: byFontIndex
|
|
264
|
+
});
|
|
265
|
+
const resources = collectResources(parts);
|
|
266
|
+
const contentRef = document.add(pdfFlateStream([], new TextEncoder().encode(renderContentStream(parts, resources))));
|
|
267
|
+
const annotationRefs = [];
|
|
268
|
+
for (const link of page.links) {
|
|
269
|
+
const annotation = buildAnnotation({
|
|
270
|
+
document,
|
|
271
|
+
link,
|
|
272
|
+
page,
|
|
273
|
+
pages: list.pages,
|
|
274
|
+
pageRefs
|
|
275
|
+
});
|
|
276
|
+
if (annotation.isErr()) return Result.err(annotation.error);
|
|
277
|
+
annotationRefs.push(annotation.value);
|
|
278
|
+
}
|
|
279
|
+
document.define(pageRef, pdfDict([
|
|
280
|
+
["Type", pdfName("Page")],
|
|
281
|
+
["Parent", pagesRef],
|
|
282
|
+
["MediaBox", pdfNumberArray([
|
|
283
|
+
0,
|
|
284
|
+
0,
|
|
285
|
+
pxToPt(page.widthPx),
|
|
286
|
+
pxToPt(page.heightPx)
|
|
287
|
+
])],
|
|
288
|
+
["Resources", buildResources({
|
|
289
|
+
resources,
|
|
290
|
+
fontRefs: fontRefByResourceIndex,
|
|
291
|
+
imageRefs
|
|
292
|
+
})],
|
|
293
|
+
["Contents", contentRef],
|
|
294
|
+
["Annots", annotationRefs.length === 0 ? void 0 : pdfArray(annotationRefs)]
|
|
295
|
+
]));
|
|
296
|
+
}
|
|
297
|
+
if (outlineRef !== void 0) {
|
|
298
|
+
const error = defineOutlineNodes({
|
|
299
|
+
document,
|
|
300
|
+
nodes: outlineRoots,
|
|
301
|
+
parentRef: outlineRef,
|
|
302
|
+
pages: list.pages,
|
|
303
|
+
pageRefs
|
|
304
|
+
});
|
|
305
|
+
if (error !== null) return Result.err(error);
|
|
306
|
+
document.define(outlineRef, pdfDict([
|
|
307
|
+
["Type", pdfName("Outlines")],
|
|
308
|
+
["First", outlineRoots.at(0)?.ref],
|
|
309
|
+
["Last", outlineRoots.at(-1)?.ref],
|
|
310
|
+
["Count", pdfNumber(openDescendantCount(outlineRoots))]
|
|
311
|
+
]));
|
|
312
|
+
}
|
|
313
|
+
document.define(pagesRef, pdfDict([
|
|
314
|
+
["Type", pdfName("Pages")],
|
|
315
|
+
["Kids", pdfArray(pageRefs)],
|
|
316
|
+
["Count", pdfNumber(pageRefs.length)]
|
|
317
|
+
]));
|
|
318
|
+
document.define(catalogRef, pdfDict([
|
|
319
|
+
["Type", pdfName("Catalog")],
|
|
320
|
+
["Pages", pagesRef],
|
|
321
|
+
["Outlines", outlineRef],
|
|
322
|
+
["PageMode", outlineRef === void 0 ? void 0 : pdfName("UseOutlines")]
|
|
323
|
+
]));
|
|
324
|
+
const { metadata } = list;
|
|
325
|
+
document.define(infoRef, pdfDict([
|
|
326
|
+
["Title", metadata.title === void 0 ? void 0 : pdfTextString(metadata.title)],
|
|
327
|
+
["Author", metadata.author === void 0 ? void 0 : pdfTextString(metadata.author)],
|
|
328
|
+
["Subject", metadata.subject === void 0 ? void 0 : pdfTextString(metadata.subject)],
|
|
329
|
+
["Keywords", metadata.keywords === void 0 ? void 0 : pdfTextString(metadata.keywords)],
|
|
330
|
+
["Producer", pdfTextString(options.producer ?? DEFAULT_PRODUCER)],
|
|
331
|
+
["CreationDate", pdfAsciiString(stamp.value)],
|
|
332
|
+
["ModDate", pdfAsciiString(stamp.value)]
|
|
333
|
+
]));
|
|
334
|
+
return Result.ok({
|
|
335
|
+
bytes: document.serialize({
|
|
336
|
+
rootRef: catalogRef,
|
|
337
|
+
infoRef,
|
|
338
|
+
idHex: (body) => createHash("sha256").update(body).digest("hex").toUpperCase()
|
|
339
|
+
}),
|
|
340
|
+
substitutions,
|
|
341
|
+
unencodable
|
|
342
|
+
});
|
|
343
|
+
};
|
|
344
|
+
//#endregion
|
|
345
|
+
export { WritePdfError, writePdf };
|
|
@@ -4,9 +4,11 @@ import { markStructuralChange } from "../extensions/features/ParagraphChangeTrac
|
|
|
4
4
|
import { getFolioNodeRevisionCarriers } from "../revisionCarriers.js";
|
|
5
5
|
import { RUN_FORMATTING_MARK_NAMES } from "../runFormattingMarkNames.js";
|
|
6
6
|
import { getTableCellMergeChange } from "../tableCellMergeRevision.js";
|
|
7
|
+
import { holdsNoContent } from "../zeroWidthAnchors.js";
|
|
7
8
|
import { paragraphRejectAttrPatch, paragraphRejectOriginalFormatting, sectionRejectProperties, tableCellRejectAttrPatch, tableRejectAttrPatch, tableRowRejectAttrPatch } from "./propertyChangeScope.js";
|
|
8
9
|
import { hasMatchingCollapsedTableCellMerge, resolveCollapsedTableCellMerge, resolveVisibleTableCellMerge } from "./tableCellMergeResolution.js";
|
|
9
10
|
import { TableMap, removeRow } from "prosemirror-tables";
|
|
11
|
+
import { PARAGRAPH_MARK_CHANGE_KINDS } from "@stll/docx-core/model";
|
|
10
12
|
//#region src/prosemirror/commands/comments.ts
|
|
11
13
|
/**
|
|
12
14
|
* Add a comment mark to the current selection.
|
|
@@ -150,10 +152,36 @@ function resolveChange(from, to, mode, revisionIds) {
|
|
|
150
152
|
continue;
|
|
151
153
|
}
|
|
152
154
|
const joinPos = mappedPos + paragraph.nodeSize;
|
|
153
|
-
|
|
155
|
+
const nextNode = joinPos < tr.doc.content.size ? tr.doc.nodeAt(joinPos) : null;
|
|
156
|
+
if (!(nextNode?.type.name === paragraph.type.name && !(carriesSection(paragraph) && carriesSection(nextNode)))) {
|
|
157
|
+
const resolved = tr.doc.resolve(mappedPos);
|
|
158
|
+
const previous = resolved.nodeBefore;
|
|
159
|
+
const sectionCanMoveBack = !carriesSection(paragraph) || previous?.type.name === paragraph.type.name && !carriesSection(previous);
|
|
160
|
+
const endsItsContainer = resolved.index() === resolved.parent.childCount - 1;
|
|
161
|
+
const canGo = op.markWasAdded || !endsItsContainer;
|
|
162
|
+
if (sectionCanMoveBack && holdsNoContent(paragraph) && canGo && resolved.parent.childCount > 1) {
|
|
163
|
+
if (carriesSection(paragraph) && previous) tr.setNodeMarkup(mappedPos - previous.nodeSize, void 0, {
|
|
164
|
+
...previous.attrs,
|
|
165
|
+
sectionBreakType: paragraph.attrs["sectionBreakType"],
|
|
166
|
+
_sectionProperties: paragraph.attrs["_sectionProperties"]
|
|
167
|
+
});
|
|
168
|
+
tr.delete(mappedPos, mappedPos + paragraph.nodeSize);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
tr.setNodeAttribute(mappedPos, "pPrMark", null);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const formattingOwner = holdsNoContent(paragraph) ? nextNode : paragraph;
|
|
175
|
+
const sectionOwner = carriesSection(paragraph) ? paragraph : nextNode;
|
|
176
|
+
const joinedAttrs = {
|
|
177
|
+
...formattingOwner.attrs,
|
|
178
|
+
pPrMark: nextNode.attrs["pPrMark"],
|
|
179
|
+
sectionBreakType: sectionOwner.attrs["sectionBreakType"],
|
|
180
|
+
_sectionProperties: sectionOwner.attrs["_sectionProperties"]
|
|
181
|
+
};
|
|
154
182
|
try {
|
|
155
183
|
tr.join(joinPos);
|
|
156
|
-
tr.
|
|
184
|
+
tr.setNodeMarkup(mappedPos, void 0, joinedAttrs);
|
|
157
185
|
} catch {}
|
|
158
186
|
}
|
|
159
187
|
tableRowStructuralOps.sort((left, right) => right.rowPos - left.rowPos);
|
|
@@ -164,6 +192,12 @@ function resolveChange(from, to, mode, revisionIds) {
|
|
|
164
192
|
if (!row || row.type.name !== "tableRow") continue;
|
|
165
193
|
if (op.action === "clear") {
|
|
166
194
|
tr.setNodeAttribute(mappedPos, op.attrName, null);
|
|
195
|
+
clearTableRowContentMarks({
|
|
196
|
+
tr,
|
|
197
|
+
rowPos: mappedPos,
|
|
198
|
+
markType: keepType,
|
|
199
|
+
revision: op.revision
|
|
200
|
+
});
|
|
167
201
|
resolvedTableRowStructure = true;
|
|
168
202
|
continue;
|
|
169
203
|
}
|
|
@@ -229,7 +263,8 @@ function collectTableRowStructuralOp(node, rowPos, mode, revisionSet) {
|
|
|
229
263
|
return {
|
|
230
264
|
rowPos,
|
|
231
265
|
attrName,
|
|
232
|
-
action: attrName === "trIns" === (mode === "accept") ? "clear" : "remove"
|
|
266
|
+
action: attrName === "trIns" === (mode === "accept") ? "clear" : "remove",
|
|
267
|
+
revision: marker
|
|
233
268
|
};
|
|
234
269
|
}
|
|
235
270
|
return null;
|
|
@@ -237,6 +272,43 @@ function collectTableRowStructuralOp(node, rowPos, mode, revisionSet) {
|
|
|
237
272
|
function isTableRowRevisionAttr(value) {
|
|
238
273
|
return typeof value === "object" && value !== null && "revisionId" in value && typeof value.revisionId === "number";
|
|
239
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* Whether an inline revision mark inside a row belongs to that row's own
|
|
277
|
+
* structural revision. Folio writes both halves under one id; Word mints a
|
|
278
|
+
* fresh `w:id` per element, so an author + timestamp match counts too. A third
|
|
279
|
+
* party's edit inside the same row matches neither and survives untouched.
|
|
280
|
+
*/
|
|
281
|
+
function markBelongsToRowRevision(mark, revision) {
|
|
282
|
+
if (mark.attrs["revisionId"] === revision.revisionId) return true;
|
|
283
|
+
return revision.author !== void 0 && mark.attrs["author"] === revision.author && (mark.attrs["date"] ?? null) === (revision.date ?? null);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Clear the run-level revision marks a row's structural revision wrote.
|
|
287
|
+
*
|
|
288
|
+
* A tracked row insertion or deletion is marked twice — on the row and around
|
|
289
|
+
* every run in its cells — so resolving one half and leaving the other would
|
|
290
|
+
* hand back a row whose text still reads as inserted (or struck through) after
|
|
291
|
+
* the change was accepted. Both halves resolve in the caller's transaction.
|
|
292
|
+
*/
|
|
293
|
+
function clearTableRowContentMarks({ tr, rowPos, markType, revision }) {
|
|
294
|
+
const row = tr.doc.nodeAt(rowPos);
|
|
295
|
+
if (!row || !markType) return;
|
|
296
|
+
const contentFrom = rowPos + 1;
|
|
297
|
+
const removals = [];
|
|
298
|
+
row.descendants((node, offset) => {
|
|
299
|
+
if (!node.isInline) return true;
|
|
300
|
+
for (const mark of node.marks) if (mark.type === markType && markBelongsToRowRevision(mark, revision)) {
|
|
301
|
+
const from = contentFrom + offset;
|
|
302
|
+
removals.push({
|
|
303
|
+
from,
|
|
304
|
+
to: from + node.nodeSize,
|
|
305
|
+
mark
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return false;
|
|
309
|
+
});
|
|
310
|
+
for (const removal of removals) tr.removeMark(removal.from, removal.to, removal.mark);
|
|
311
|
+
}
|
|
240
312
|
function collectTableCellStructuralOps(node, cellPos, mode, revisionSet) {
|
|
241
313
|
const operations = [];
|
|
242
314
|
const marker = node.attrs["cellMarker"];
|
|
@@ -317,9 +389,11 @@ function collectPPrMarkOp(node, pos, from, to, mode, revisionSet) {
|
|
|
317
389
|
const pPrMark = node.attrs["pPrMark"];
|
|
318
390
|
if (!isPPrMarkAttr(pPrMark)) return null;
|
|
319
391
|
if (revisionSet !== null && !revisionSet.has(pPrMark.info.id)) return null;
|
|
392
|
+
const markWasAdded = paragraphMarkWasAdded(pPrMark.kind);
|
|
320
393
|
return {
|
|
321
394
|
paragraphPos: pos,
|
|
322
|
-
action:
|
|
395
|
+
action: markWasAdded === (mode === "accept") ? "clear" : "join",
|
|
396
|
+
markWasAdded
|
|
323
397
|
};
|
|
324
398
|
}
|
|
325
399
|
function rangeCoversParagraphBoundary(from, to, pos, node) {
|
|
@@ -374,10 +448,26 @@ function isPPrMarkAttr(value) {
|
|
|
374
448
|
if (typeof value !== "object" || value === null) return false;
|
|
375
449
|
const kind = value.kind;
|
|
376
450
|
const info = value.info;
|
|
377
|
-
if (
|
|
451
|
+
if (!PARAGRAPH_MARK_CHANGE_KINDS.some((allowed) => allowed === kind)) return false;
|
|
378
452
|
if (typeof info !== "object" || info === null) return false;
|
|
379
453
|
return typeof info.id === "number";
|
|
380
454
|
}
|
|
455
|
+
/**
|
|
456
|
+
* Whether the mark says the paragraph break was ADDED. A relocation's
|
|
457
|
+
* destination break was added exactly as an insertion's was, and its source
|
|
458
|
+
* break went exactly as a deletion's did; the kinds differ so a reader is told
|
|
459
|
+
* the two ends belong together, not because they resolve differently.
|
|
460
|
+
*/
|
|
461
|
+
const paragraphMarkWasAdded = (kind) => kind === "ins" || kind === "moveTo";
|
|
462
|
+
/**
|
|
463
|
+
* Whether the paragraph's mark carries a section's properties.
|
|
464
|
+
*
|
|
465
|
+
* A section break lives on a paragraph mark, so the paragraph is where the
|
|
466
|
+
* section ends. Removing it removes the section: a document that had two ends
|
|
467
|
+
* up with one, and every setting the dropped section carried — page size,
|
|
468
|
+
* margins, its header and footer references — goes with it.
|
|
469
|
+
*/
|
|
470
|
+
const carriesSection = (paragraph) => paragraph.attrs["sectionBreakType"] != null || paragraph.attrs["_sectionProperties"] != null;
|
|
381
471
|
function readRevisionInfo(info) {
|
|
382
472
|
const revision = {};
|
|
383
473
|
if (typeof info?.author === "string") revision.author = info.author;
|
|
@@ -417,7 +507,7 @@ function findParagraphBoundaryChangeAtPosition(state, pos) {
|
|
|
417
507
|
if (node.type.name !== "paragraph") return null;
|
|
418
508
|
const paragraphPos = $pos.before($pos.depth);
|
|
419
509
|
const pPrMark = node.attrs["pPrMark"];
|
|
420
|
-
if (isPPrMarkAttr(pPrMark)) return toParagraphBoundaryChange(node, paragraphPos, pPrMark.kind
|
|
510
|
+
if (isPPrMarkAttr(pPrMark)) return toParagraphBoundaryChange(node, paragraphPos, paragraphMarkWasAdded(pPrMark.kind) ? "insertion" : "deletion", pPrMark.info);
|
|
421
511
|
const propertyChanges = expectParagraphAttrs(node)._propertyChanges;
|
|
422
512
|
if (!Array.isArray(propertyChanges)) return null;
|
|
423
513
|
for (const change of propertyChanges) {
|
|
@@ -666,25 +666,26 @@ function paragraphAttrsToFormatting(attrs) {
|
|
|
666
666
|
return f;
|
|
667
667
|
}
|
|
668
668
|
function createTrackedRunWrapper(type, info, child) {
|
|
669
|
+
const content = child ? [child] : [];
|
|
669
670
|
if (type === "insertion") return {
|
|
670
671
|
type,
|
|
671
672
|
info,
|
|
672
|
-
content
|
|
673
|
+
content
|
|
673
674
|
};
|
|
674
675
|
if (type === "deletion") return {
|
|
675
676
|
type,
|
|
676
677
|
info,
|
|
677
|
-
content
|
|
678
|
+
content
|
|
678
679
|
};
|
|
679
680
|
if (type === "moveFrom") return {
|
|
680
681
|
type,
|
|
681
682
|
info,
|
|
682
|
-
content
|
|
683
|
+
content
|
|
683
684
|
};
|
|
684
685
|
return {
|
|
685
686
|
type,
|
|
686
687
|
info,
|
|
687
|
-
content
|
|
688
|
+
content
|
|
688
689
|
};
|
|
689
690
|
}
|
|
690
691
|
function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, textBoxAnchorMarkers, skipLeadingRenderedPageBreak = false) {
|
|
@@ -824,16 +825,19 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
824
825
|
const trackedChangeKey = `${type}:${JSON.stringify(info)}`;
|
|
825
826
|
if (linkMark) {
|
|
826
827
|
const linkKey = getLinkKey(linkMark);
|
|
827
|
-
if (!currentTrackedChange || currentTrackedChange.key !== trackedChangeKey || currentTrackedChange.hyperlinkKey !== linkKey) {
|
|
828
|
+
if (!currentTrackedChange || currentTrackedChange.type !== "hyperlink" || currentTrackedChange.key !== trackedChangeKey || currentTrackedChange.hyperlinkKey !== linkKey) {
|
|
828
829
|
const hyperlink = createHyperlink(linkMark);
|
|
829
830
|
const wrapper = createTrackedRunWrapper(type, info, hyperlink);
|
|
830
831
|
content.push(wrapper);
|
|
831
832
|
currentTrackedChange = {
|
|
833
|
+
type: "hyperlink",
|
|
832
834
|
key: trackedChangeKey,
|
|
835
|
+
wrapper,
|
|
833
836
|
hyperlink,
|
|
834
837
|
hyperlinkKey: linkKey
|
|
835
838
|
};
|
|
836
839
|
}
|
|
840
|
+
if (currentTrackedChange.type !== "hyperlink") panic("A tracked hyperlink lost its serialization parent");
|
|
837
841
|
if (node.type.name === "bookmarkBoundary") {
|
|
838
842
|
addNodeToHyperlink({
|
|
839
843
|
hyperlink: currentTrackedChange.hyperlink,
|
|
@@ -850,13 +854,43 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
850
854
|
if (run) currentTrackedChange.hyperlink.children.push(run);
|
|
851
855
|
return;
|
|
852
856
|
}
|
|
853
|
-
currentTrackedChange
|
|
857
|
+
if (!currentTrackedChange || currentTrackedChange.type !== "direct" || currentTrackedChange.key !== trackedChangeKey) {
|
|
858
|
+
const wrapper = createTrackedRunWrapper(type, info);
|
|
859
|
+
content.push(wrapper);
|
|
860
|
+
currentTrackedChange = {
|
|
861
|
+
type: "direct",
|
|
862
|
+
key: trackedChangeKey,
|
|
863
|
+
wrapper
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
if (node.type.name === "bookmarkBoundary") {
|
|
867
|
+
const attrs = expectBookmarkBoundaryAttrs(node);
|
|
868
|
+
const boundary = attrs.type === "start" ? {
|
|
869
|
+
type: "bookmarkStart",
|
|
870
|
+
id: attrs.id,
|
|
871
|
+
name: attrs.name,
|
|
872
|
+
...attrs.colFirst !== void 0 ? { colFirst: attrs.colFirst } : {},
|
|
873
|
+
...attrs.colLast !== void 0 ? { colLast: attrs.colLast } : {}
|
|
874
|
+
} : {
|
|
875
|
+
type: "bookmarkEnd",
|
|
876
|
+
id: attrs.id
|
|
877
|
+
};
|
|
878
|
+
currentTrackedChange.wrapper.content.push(boundary);
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
if (node.type.name === "field" || node.type.name === "structuredField") {
|
|
882
|
+
currentTrackedChange.wrapper.content.push(createFieldFromNode(node, {
|
|
883
|
+
marks: otherMarks,
|
|
884
|
+
textBoxAnchorMarkers
|
|
885
|
+
}));
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
854
888
|
const run = createTrackedChangeRun({
|
|
855
889
|
inheritedFormatting,
|
|
856
890
|
marks: otherMarks,
|
|
857
891
|
node
|
|
858
892
|
});
|
|
859
|
-
if (run) content.push(
|
|
893
|
+
if (run) currentTrackedChange.wrapper.content.push(run);
|
|
860
894
|
return;
|
|
861
895
|
}
|
|
862
896
|
if (noteRefMark && !linkMark) {
|