@stll/folio-core 0.48.0 → 0.50.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.js +565 -121
- package/dist/ai-edits/headless.d.ts +37 -1
- package/dist/ai-edits/headless.js +47 -16
- package/dist/ai-edits/minimal-replacement.d.ts +64 -0
- package/dist/ai-edits/minimal-replacement.js +282 -0
- package/dist/controller/layoutPipeline.js +11 -1
- package/dist/display-list/build/paragraphPrimitives.js +63 -7
- package/dist/display-list/build/textBoxPrimitives.js +1 -1
- package/dist/display-list/build/unsupported.d.ts +2 -2
- package/dist/display-list/build/unsupported.js +0 -0
- package/dist/display-list/dom/renderDisplayListToDom.d.ts +10 -1
- package/dist/display-list/dom/renderDisplayListToDom.js +8 -8
- package/dist/display-list/html/renderDisplayListToHtml.d.ts +21 -0
- package/dist/display-list/html/renderDisplayListToHtml.js +126 -0
- package/dist/display-list/selectDisplayPages.d.ts +6 -0
- package/dist/display-list/selectDisplayPages.js +44 -0
- package/dist/docx/blockContentParser.js +2 -2
- package/dist/docx/bulletMarkers.d.ts +18 -2
- package/dist/docx/bulletMarkers.js +27 -2
- package/dist/docx/drawingGroupChildren.d.ts +60 -0
- package/dist/docx/drawingGroupChildren.js +176 -0
- package/dist/docx/drawingUtils.js +3 -0
- package/dist/docx/groupDrawingParser.d.ts +7 -1
- package/dist/docx/groupDrawingParser.js +123 -32
- package/dist/docx/packageParts.d.ts +45 -1
- package/dist/docx/packageParts.js +93 -2
- package/dist/docx/paraIdAttribute.d.ts +3 -2
- package/dist/docx/paragraphParser.js +91 -7
- package/dist/docx/paragraphTextBoxEnrichment.js +126 -4
- package/dist/docx/runParser.js +10 -0
- package/dist/docx/selectiveSaveFlags.d.ts +10 -3
- package/dist/docx/selectiveSaveFlags.js +1 -1
- package/dist/docx/selectiveXmlPatch.d.ts +56 -34
- package/dist/docx/selectiveXmlPatch.js +222 -115
- package/dist/docx/serializer/groupTextBoxWriteBack.d.ts +12 -0
- package/dist/docx/serializer/groupTextBoxWriteBack.js +101 -0
- package/dist/docx/serializer/paragraphSerializer.js +5 -4
- package/dist/docx/serializer/runSerializer.d.ts +5 -1
- package/dist/docx/serializer/runSerializer.js +4 -3
- package/dist/docx/shapeAlternateContent.d.ts +25 -0
- package/dist/docx/shapeAlternateContent.js +52 -0
- package/dist/docx/textBoxParser.d.ts +10 -2
- package/dist/docx/textBoxParser.js +9 -3
- package/dist/export-pdf.d.ts +35 -2
- package/dist/export-pdf.js +51 -20
- package/dist/fonts/fontsourceFaces.d.ts +47 -0
- package/dist/fonts/fontsourceFaces.js +230 -0
- package/dist/internal/headlessRevisionResolution.js +50 -13
- package/dist/layout-bridge/convert/flowBlockPostprocessing.d.ts +8 -0
- package/dist/layout-bridge/convert/flowBlockPostprocessing.js +106 -0
- package/dist/layout-bridge/convert/flowBorders.d.ts +17 -0
- package/dist/layout-bridge/convert/flowBorders.js +31 -0
- package/dist/layout-bridge/convert/flowConversionShared.d.ts +111 -0
- package/dist/layout-bridge/convert/flowConversionShared.js +23 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +2 -1
- package/dist/layout-bridge/convert/footnoteLayout.js +2 -1
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +2 -1
- package/dist/layout-bridge/convert/imageConversion.d.ts +33 -0
- package/dist/layout-bridge/convert/imageConversion.js +118 -0
- package/dist/layout-bridge/convert/listMarkers.d.ts +28 -0
- package/dist/layout-bridge/convert/listMarkers.js +98 -0
- package/dist/layout-bridge/convert/noteReferences.d.ts +14 -0
- package/dist/layout-bridge/convert/noteReferences.js +29 -0
- package/dist/layout-bridge/convert/pageBreakSplitting.d.ts +16 -0
- package/dist/layout-bridge/convert/pageBreakSplitting.js +140 -0
- package/dist/layout-bridge/convert/paragraphAttrs.d.ts +20 -0
- package/dist/layout-bridge/convert/paragraphAttrs.js +234 -0
- package/dist/layout-bridge/convert/paragraphConversion.d.ts +12 -0
- package/dist/layout-bridge/convert/paragraphConversion.js +112 -0
- package/dist/layout-bridge/convert/paragraphRuns.d.ts +15 -0
- package/dist/layout-bridge/convert/paragraphRuns.js +281 -0
- package/dist/layout-bridge/convert/runFormattingMerge.d.ts +19 -0
- package/dist/layout-bridge/convert/runFormattingMerge.js +167 -0
- package/dist/layout-bridge/convert/runMarkFormatting.d.ts +11 -0
- package/dist/layout-bridge/convert/runMarkFormatting.js +291 -0
- package/dist/layout-bridge/convert/sectionBoundaries.d.ts +35 -0
- package/dist/layout-bridge/convert/sectionBoundaries.js +114 -0
- package/dist/layout-bridge/convert/tableConversion.d.ts +12 -0
- package/dist/layout-bridge/convert/tableConversion.js +303 -0
- package/dist/layout-bridge/convert/textBoxFill.d.ts +20 -0
- package/dist/layout-bridge/convert/textBoxFill.js +42 -0
- package/dist/layout-bridge/convert/textFormattingConversion.d.ts +22 -0
- package/dist/layout-bridge/convert/textFormattingConversion.js +120 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +5 -113
- package/dist/layout-bridge/convert/toFlowBlocks.js +11 -2060
- package/dist/layout-bridge/engine/hitTest.js +1 -1
- package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
- package/dist/layout-bridge/engine/measuring/index.js +2 -1
- package/dist/layout-bridge/engine/selectionRects.js +1 -1
- package/dist/layout-engine/imageLayout.d.ts +9 -0
- package/dist/layout-engine/imageLayout.js +48 -0
- package/dist/layout-engine/index.d.ts +4 -19
- package/dist/layout-engine/index.js +14 -910
- package/dist/layout-engine/keep-together.d.ts +5 -2
- package/dist/layout-engine/keep-together.js +5 -1
- package/dist/layout-engine/layoutFlowShared.d.ts +6 -0
- package/dist/layout-engine/layoutFlowShared.js +8 -0
- package/dist/layout-engine/measure/advanceComposition.js +49 -3
- package/dist/layout-engine/measure/crossRunWords.d.ts +45 -0
- package/dist/layout-engine/measure/crossRunWords.js +172 -0
- package/dist/layout-engine/measure/floatingLineClearance.d.ts +24 -0
- package/dist/layout-engine/measure/floatingLineClearance.js +44 -0
- package/dist/layout-engine/measure/inlineObjectMetrics.d.ts +6 -0
- package/dist/layout-engine/measure/inlineObjectMetrics.js +52 -0
- package/dist/layout-engine/measure/justification.d.ts +32 -0
- package/dist/layout-engine/measure/justification.js +83 -0
- package/dist/layout-engine/measure/lineFitting.d.ts +48 -0
- package/dist/layout-engine/measure/lineFitting.js +145 -0
- package/dist/layout-engine/measure/lineTypography.d.ts +44 -0
- package/dist/layout-engine/measure/lineTypography.js +124 -0
- package/dist/layout-engine/measure/measureBlocks.js +7 -4
- package/dist/layout-engine/measure/measureContainer.js +46 -3
- package/dist/layout-engine/measure/measureHelpers.js +13 -3
- package/dist/layout-engine/measure/measureParagraph.d.ts +1 -20
- package/dist/layout-engine/measure/measureParagraph.js +41 -780
- package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +109 -0
- package/dist/layout-engine/measure/paragraphMeasureShared.js +82 -0
- package/dist/layout-engine/measure/paragraphTabs.d.ts +18 -0
- package/dist/layout-engine/measure/paragraphTabs.js +78 -0
- package/dist/layout-engine/measure/smallCapsCasing.d.ts +56 -0
- package/dist/layout-engine/measure/smallCapsCasing.js +88 -0
- package/dist/layout-engine/paginator.d.ts +2 -0
- package/dist/layout-engine/paginator.js +5 -1
- package/dist/layout-engine/paragraphLayout.d.ts +23 -0
- package/dist/layout-engine/paragraphLayout.js +204 -0
- package/dist/layout-engine/paragraphSpacing.d.ts +4 -2
- package/dist/layout-engine/paragraphSpacing.js +2 -1
- package/dist/layout-engine/sectionLayout.d.ts +45 -0
- package/dist/layout-engine/sectionLayout.js +216 -0
- package/dist/layout-engine/tableLayout.d.ts +17 -0
- package/dist/layout-engine/tableLayout.js +369 -0
- package/dist/layout-engine/tableRowBreak.d.ts +8 -1
- package/dist/layout-engine/tableRowBreak.js +22 -1
- package/dist/layout-engine/textBoxLayout.d.ts +15 -0
- package/dist/layout-engine/textBoxLayout.js +137 -0
- package/dist/layout-engine/types.d.ts +21 -2
- package/dist/layout-painter/renderPage.js +1 -0
- package/dist/layout-painter/renderParagraph.js +41 -4
- package/dist/layout-painter/renderTextBox.d.ts +12 -2
- package/dist/layout-painter/renderTextBox.js +26 -2
- package/dist/paged-layout/sectionBlockWidths.d.ts +2 -1
- package/dist/paged-layout/sectionBlockWidths.js +1 -1
- package/dist/prosemirror/alternateContentAttrs.d.ts +9 -0
- package/dist/prosemirror/alternateContentAttrs.js +35 -0
- package/dist/prosemirror/attrs/index.js +90 -0
- package/dist/prosemirror/commands/comments.js +51 -41
- package/dist/prosemirror/contentControlRevisions.d.ts +43 -0
- package/dist/prosemirror/contentControlRevisions.js +84 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +46 -22
- package/dist/prosemirror/conversion/toProseDoc.js +42 -19
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +7 -7
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -7
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +6 -3
- package/dist/prosemirror/extensions/nodes/SdtExtension.js +9 -2
- package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +3 -0
- package/dist/prosemirror/listMarker.js +2 -2
- package/dist/prosemirror/paragraphDirection.d.ts +29 -2
- package/dist/prosemirror/paragraphDirection.js +21 -2
- package/dist/prosemirror/plugins/suggestionMode.js +37 -8
- package/dist/prosemirror/rejoinRunCarriers.d.ts +27 -0
- package/dist/prosemirror/rejoinRunCarriers.js +66 -0
- package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +10 -0
- package/dist/prosemirror/runIdentityAcrossRevisions.js +90 -0
- package/dist/prosemirror/schema/nodes.d.ts +37 -5
- package/dist/server.d.ts +3 -2
- package/dist/types/content.d.ts +2 -2
- package/package.json +2 -2
- package/dist/layout-engine/justifiedLineFit.d.ts +0 -7
- package/dist/layout-engine/justifiedLineFit.js +0 -6
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Shape, ShapeAlternateContent, ShapeContent } from "../types/content.js";
|
|
2
|
+
import { XmlElement } from "./xmlParser.js";
|
|
3
|
+
//#region src/docx/shapeAlternateContent.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Capture the `mc:AlternateContent` a shape was read from, when the shape's
|
|
6
|
+
* drawing is the sole child of the branch it came from.
|
|
7
|
+
*/
|
|
8
|
+
declare const captureShapeAlternateContent: ({ shape, alternateContent, branch, drawing }: {
|
|
9
|
+
shape: Shape;
|
|
10
|
+
alternateContent: XmlElement;
|
|
11
|
+
branch: XmlElement;
|
|
12
|
+
drawing: XmlElement;
|
|
13
|
+
}) => ShapeAlternateContent | undefined;
|
|
14
|
+
/** Re-fingerprint a capture for a shape rebuilt from an unedited projection. */
|
|
15
|
+
declare const refingerprintShapeAlternateContent: (shape: Shape, verbatimXml: string) => ShapeAlternateContent;
|
|
16
|
+
/**
|
|
17
|
+
* The captured `mc:AlternateContent` to write in place of the shape's drawing,
|
|
18
|
+
* or undefined when the shape must be regenerated.
|
|
19
|
+
*
|
|
20
|
+
* Replay requires the modeled shape to still match the capture. An edited shape
|
|
21
|
+
* is regenerated as a bare `w:drawing` without the stale Fallback.
|
|
22
|
+
*/
|
|
23
|
+
declare const replayableShapeAlternateContent: (content: ShapeContent) => string | undefined;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { captureShapeAlternateContent, refingerprintShapeAlternateContent, replayableShapeAlternateContent };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { canonicalJson } from "../utils/canonicalJson.js";
|
|
2
|
+
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
3
|
+
import { getChildElements, getLocalName } from "./xmlParser.js";
|
|
4
|
+
//#region src/docx/shapeAlternateContent.ts
|
|
5
|
+
/**
|
|
6
|
+
* Every modeled field, text body included: the Fallback repeats the shape's
|
|
7
|
+
* geometry and its text, so an edit to either makes the captured element stale.
|
|
8
|
+
*/
|
|
9
|
+
const shapeFingerprint = (shape) => canonicalJson(shape);
|
|
10
|
+
/**
|
|
11
|
+
* Whether `element` is the only child element of its `mc:Choice` or
|
|
12
|
+
* `mc:Fallback` branch.
|
|
13
|
+
*
|
|
14
|
+
* A branch holding more than the one drawing cannot be written back by the
|
|
15
|
+
* drawing's shape alone: the siblings are parsed into their own run content,
|
|
16
|
+
* and replaying the whole element would write them twice.
|
|
17
|
+
*/
|
|
18
|
+
const isSoleBranchChild = (branch, element) => {
|
|
19
|
+
const children = getChildElements(branch);
|
|
20
|
+
return children.length === 1 && children[0] === element;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Capture the `mc:AlternateContent` a shape was read from, when the shape's
|
|
24
|
+
* drawing is the sole child of the branch it came from.
|
|
25
|
+
*/
|
|
26
|
+
const captureShapeAlternateContent = ({ shape, alternateContent, branch, drawing }) => {
|
|
27
|
+
if (getLocalName(alternateContent.name ?? "") !== "AlternateContent") return;
|
|
28
|
+
if (!isSoleBranchChild(branch, drawing)) return;
|
|
29
|
+
return {
|
|
30
|
+
verbatimXml: captureVerbatimXml(alternateContent),
|
|
31
|
+
verbatimFingerprint: shapeFingerprint(shape)
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
/** Re-fingerprint a capture for a shape rebuilt from an unedited projection. */
|
|
35
|
+
const refingerprintShapeAlternateContent = (shape, verbatimXml) => ({
|
|
36
|
+
verbatimXml,
|
|
37
|
+
verbatimFingerprint: shapeFingerprint(shape)
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* The captured `mc:AlternateContent` to write in place of the shape's drawing,
|
|
41
|
+
* or undefined when the shape must be regenerated.
|
|
42
|
+
*
|
|
43
|
+
* Replay requires the modeled shape to still match the capture. An edited shape
|
|
44
|
+
* is regenerated as a bare `w:drawing` without the stale Fallback.
|
|
45
|
+
*/
|
|
46
|
+
const replayableShapeAlternateContent = (content) => {
|
|
47
|
+
const captured = content.alternateContent;
|
|
48
|
+
if (captured === void 0) return;
|
|
49
|
+
return captured.verbatimFingerprint === shapeFingerprint(content.shape) ? captured.verbatimXml : void 0;
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
export { captureShapeAlternateContent, refingerprintShapeAlternateContent, replayableShapeAlternateContent };
|
|
@@ -119,8 +119,16 @@ declare function resolveTextBoxOutlineColor(textBox: document_d_exports.TextBox)
|
|
|
119
119
|
* Get outline width in pixels
|
|
120
120
|
*/
|
|
121
121
|
declare function getTextBoxOutlineWidthPx(textBox: document_d_exports.TextBox): number;
|
|
122
|
+
/** A text-box `w:drawing`, with the `mc:AlternateContent` branch it sits in, if any. */
|
|
123
|
+
type TextBoxDrawing = {
|
|
124
|
+
drawing: XmlElement;
|
|
125
|
+
alternateContent?: {
|
|
126
|
+
element: XmlElement;
|
|
127
|
+
branch: XmlElement;
|
|
128
|
+
};
|
|
129
|
+
};
|
|
122
130
|
type TextBoxRunScan = {
|
|
123
|
-
textBoxDrawings:
|
|
131
|
+
textBoxDrawings: TextBoxDrawing[];
|
|
124
132
|
vmlTextBoxes: XmlElement[];
|
|
125
133
|
hasNonTextBoxContent: boolean;
|
|
126
134
|
};
|
|
@@ -143,4 +151,4 @@ type ScanRunForTextBoxDrawingsOptions = {
|
|
|
143
151
|
};
|
|
144
152
|
declare const scanRunForTextBoxDrawings: ({ xmlRun, claimedByRunParser }: ScanRunForTextBoxDrawingsOptions) => TextBoxRunScan;
|
|
145
153
|
//#endregion
|
|
146
|
-
export { ParagraphParserFn, ScanRunForTextBoxDrawingsOptions, TableParserFn, TextBoxRunScan, extractTextBoxContentElements, getTextBoxContentElement, getTextBoxDimensionsPx, getTextBoxHeightPx, getTextBoxMarginsPx, getTextBoxOutlineWidthPx, getTextBoxText, getTextBoxWidthPx, hasTextBoxContent, hasTextBoxFill, hasTextBoxOutline, isFloatingTextBox, isShapeTextBox, isTextBoxDrawing, parseTextBox, parseTextBoxContent, parseTextBoxFromShape, resolveTextBoxFillColor, resolveTextBoxOutlineColor, scanRunForTextBoxDrawings };
|
|
154
|
+
export { ParagraphParserFn, ScanRunForTextBoxDrawingsOptions, TableParserFn, TextBoxDrawing, TextBoxRunScan, extractTextBoxContentElements, getTextBoxContentElement, getTextBoxDimensionsPx, getTextBoxHeightPx, getTextBoxMarginsPx, getTextBoxOutlineWidthPx, getTextBoxText, getTextBoxWidthPx, hasTextBoxContent, hasTextBoxFill, hasTextBoxOutline, isFloatingTextBox, isShapeTextBox, isTextBoxDrawing, parseTextBox, parseTextBoxContent, parseTextBoxFromShape, resolveTextBoxFillColor, resolveTextBoxOutlineColor, scanRunForTextBoxDrawings };
|
|
@@ -328,9 +328,12 @@ const scanRunForTextBoxDrawings = ({ xmlRun, claimedByRunParser }) => {
|
|
|
328
328
|
const textBoxDrawings = [];
|
|
329
329
|
const vmlTextBoxes = [];
|
|
330
330
|
let hasNonTextBoxContent = false;
|
|
331
|
-
const visitDrawing = (drawingEl) => {
|
|
331
|
+
const visitDrawing = (drawingEl, alternateContent) => {
|
|
332
332
|
if (isTextBoxDrawing(drawingEl)) {
|
|
333
|
-
textBoxDrawings.push(
|
|
333
|
+
textBoxDrawings.push({
|
|
334
|
+
drawing: drawingEl,
|
|
335
|
+
...alternateContent === void 0 ? {} : { alternateContent }
|
|
336
|
+
});
|
|
334
337
|
return;
|
|
335
338
|
}
|
|
336
339
|
hasNonTextBoxContent = true;
|
|
@@ -355,7 +358,10 @@ const scanRunForTextBoxDrawings = ({ xmlRun, claimedByRunParser }) => {
|
|
|
355
358
|
if (!branch) return false;
|
|
356
359
|
let found = false;
|
|
357
360
|
for (const innerEl of getChildElements(branch)) if (getLocalName(innerEl.name ?? "") === "drawing") {
|
|
358
|
-
visitDrawing(innerEl
|
|
361
|
+
visitDrawing(innerEl, {
|
|
362
|
+
element: el,
|
|
363
|
+
branch
|
|
364
|
+
});
|
|
359
365
|
found = true;
|
|
360
366
|
}
|
|
361
367
|
return found;
|
package/dist/export-pdf.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DocxInput } from "./utils/docxInput.js";
|
|
2
|
-
import { DisplayMetadata, DisplayUnsupported } from "./display-list/types.js";
|
|
2
|
+
import { DisplayList, DisplayMetadata, DisplayUnsupported } from "./display-list/types.js";
|
|
3
3
|
import { HeadlessFontSource, HeadlessFontSubstitution } from "./fonts/headlessMeasure.js";
|
|
4
4
|
import { HeadlessLayoutGap } from "./headless-layout.js";
|
|
5
5
|
import { PdfSubstitution } from "./pdf/fonts.js";
|
|
@@ -23,6 +23,11 @@ type ExportDocxToPdfOptions = {
|
|
|
23
23
|
readonly timestamp: string;
|
|
24
24
|
readonly metadata?: DisplayMetadata;
|
|
25
25
|
readonly producer?: string;
|
|
26
|
+
/**
|
|
27
|
+
* 0-based indices of the pages to write, in order. Links and outline
|
|
28
|
+
* entries to other pages are dropped. Omit for every page.
|
|
29
|
+
*/
|
|
30
|
+
readonly pages?: readonly number[];
|
|
26
31
|
};
|
|
27
32
|
type ExportDocxToPdfResult = {
|
|
28
33
|
readonly bytes: Uint8Array;
|
|
@@ -38,6 +43,34 @@ type ExportDocxToPdfResult = {
|
|
|
38
43
|
/** Code points painted as `.notdef` because no supplied face covers them. */
|
|
39
44
|
readonly unencodable: readonly PdfUnencodable[];
|
|
40
45
|
};
|
|
46
|
+
type BuildDocxDisplayListOptions = {
|
|
47
|
+
/** Supplies face binaries for measurement. */
|
|
48
|
+
readonly fonts: HeadlessFontSource;
|
|
49
|
+
readonly metadata?: DisplayMetadata;
|
|
50
|
+
};
|
|
51
|
+
type BuildDocxDisplayListResult = {
|
|
52
|
+
/** The pages every backend paints: the PDF writer, the DOM and HTML backends. */
|
|
53
|
+
readonly list: DisplayList;
|
|
54
|
+
/** Stories the headless pipeline does not paginate. */
|
|
55
|
+
readonly layoutGaps: readonly HeadlessLayoutGap[];
|
|
56
|
+
/** Faces measured with a stand-in. */
|
|
57
|
+
readonly measurementSubstitutions: readonly HeadlessFontSubstitution[];
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Lay a package out headlessly and build its display list: the one structure
|
|
61
|
+
* the PDF writer and the DOM backend both paint. Runs one at a time with
|
|
62
|
+
* every other headless build and export, for the reason
|
|
63
|
+
* {@link runExclusively} gives.
|
|
64
|
+
*/
|
|
65
|
+
declare const buildDocxDisplayList: (input: DocxInput, options: BuildDocxDisplayListOptions) => Promise<Result<BuildDocxDisplayListResult, ExportPdfError>>;
|
|
41
66
|
declare const exportDocxToPdf: (input: DocxInput, options: ExportDocxToPdfOptions) => Promise<Result<ExportDocxToPdfResult, ExportPdfError>>;
|
|
67
|
+
type WriteDisplayListPdfOptions = Pick<ExportDocxToPdfOptions, "fonts" | "timestamp" | "producer">;
|
|
68
|
+
type WriteDisplayListPdfResult = Pick<ExportDocxToPdfResult, "bytes" | "pageCount" | "embeddingSubstitutions" | "unencodable">;
|
|
69
|
+
/**
|
|
70
|
+
* Write a display list built by {@link buildDocxDisplayList} (or narrowed by
|
|
71
|
+
* `selectDisplayPages`) as a PDF, embedding faces from the same source the
|
|
72
|
+
* layout measured with.
|
|
73
|
+
*/
|
|
74
|
+
declare const writeDisplayListPdf: (list: DisplayList, options: WriteDisplayListPdfOptions) => Promise<Result<WriteDisplayListPdfResult, ExportPdfError>>;
|
|
42
75
|
//#endregion
|
|
43
|
-
export { ExportDocxToPdfOptions, ExportDocxToPdfResult, ExportPdfError, exportDocxToPdf };
|
|
76
|
+
export { BuildDocxDisplayListOptions, BuildDocxDisplayListResult, ExportDocxToPdfOptions, ExportDocxToPdfResult, ExportPdfError, WriteDisplayListPdfOptions, WriteDisplayListPdfResult, buildDocxDisplayList, exportDocxToPdf, writeDisplayListPdf };
|
package/dist/export-pdf.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildDisplayList } from "./display-list/build/buildDisplayList.js";
|
|
2
2
|
import { displayCommentsFrom } from "./display-list/build/commentAnnotations.js";
|
|
3
|
+
import { selectDisplayPages } from "./display-list/selectDisplayPages.js";
|
|
3
4
|
import { installHeadlessMeasureProvider } from "./fonts/headlessMeasure.js";
|
|
4
5
|
import { layoutDocxHeadless } from "./headless-layout.js";
|
|
5
6
|
import { getMeasureProvider, setMeasureProvider } from "./layout-engine/measure/measureProvider.js";
|
|
@@ -52,16 +53,59 @@ const runExclusively = (run) => {
|
|
|
52
53
|
exportQueue = current.then(() => void 0, () => void 0);
|
|
53
54
|
return current;
|
|
54
55
|
};
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Lay a package out headlessly and build its display list: the one structure
|
|
58
|
+
* the PDF writer and the DOM backend both paint. Runs one at a time with
|
|
59
|
+
* every other headless build and export, for the reason
|
|
60
|
+
* {@link runExclusively} gives.
|
|
61
|
+
*/
|
|
62
|
+
const buildDocxDisplayList = (input, options) => runExclusively(async () => {
|
|
57
63
|
const callerProvider = getMeasureProvider();
|
|
58
64
|
try {
|
|
59
|
-
return await
|
|
65
|
+
return await buildWithHeadlessProvider(input, options);
|
|
60
66
|
} finally {
|
|
61
67
|
setMeasureProvider(callerProvider);
|
|
62
68
|
}
|
|
69
|
+
});
|
|
70
|
+
const exportDocxToPdf = async (input, options) => {
|
|
71
|
+
const built = await buildDocxDisplayList(input, {
|
|
72
|
+
fonts: options.fonts,
|
|
73
|
+
...options.metadata === void 0 ? {} : { metadata: options.metadata }
|
|
74
|
+
});
|
|
75
|
+
if (built.isErr()) return Result.err(built.error);
|
|
76
|
+
const list = options.pages === void 0 ? built.value.list : selectDisplayPages(built.value.list, options.pages);
|
|
77
|
+
const written = await writeDisplayListPdf(list, options);
|
|
78
|
+
if (written.isErr()) return Result.err(written.error);
|
|
79
|
+
return Result.ok({
|
|
80
|
+
...written.value,
|
|
81
|
+
unsupported: list.unsupported,
|
|
82
|
+
layoutGaps: built.value.layoutGaps,
|
|
83
|
+
measurementSubstitutions: built.value.measurementSubstitutions
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Write a display list built by {@link buildDocxDisplayList} (or narrowed by
|
|
88
|
+
* `selectDisplayPages`) as a PDF, embedding faces from the same source the
|
|
89
|
+
* layout measured with.
|
|
90
|
+
*/
|
|
91
|
+
const writeDisplayListPdf = async (list, options) => {
|
|
92
|
+
const written = await writePdf(list, {
|
|
93
|
+
fonts: { load: (face) => options.fonts.load(toFontRequest(face)) },
|
|
94
|
+
timestamp: options.timestamp,
|
|
95
|
+
...options.producer === void 0 ? {} : { producer: options.producer }
|
|
96
|
+
});
|
|
97
|
+
if (written.isErr()) return Result.err(new ExportPdfError({
|
|
98
|
+
message: written.error.message,
|
|
99
|
+
cause: written.error
|
|
100
|
+
}));
|
|
101
|
+
return Result.ok({
|
|
102
|
+
bytes: written.value.bytes,
|
|
103
|
+
pageCount: list.pages.length,
|
|
104
|
+
embeddingSubstitutions: written.value.substitutions,
|
|
105
|
+
unencodable: written.value.unencodable
|
|
106
|
+
});
|
|
63
107
|
};
|
|
64
|
-
const
|
|
108
|
+
const buildWithHeadlessProvider = async (input, options) => {
|
|
65
109
|
const headless = installHeadlessMeasureProvider(options.fonts);
|
|
66
110
|
const laidOut = await layoutDocxHeadless(input, { pageGap: 0 });
|
|
67
111
|
if (laidOut.isErr()) return Result.err(new ExportPdfError({
|
|
@@ -77,24 +121,11 @@ const exportWithHeadlessProvider = async (input, options) => {
|
|
|
77
121
|
...laidOut.value.document.package.document.comments === void 0 ? {} : { comments: displayCommentsFrom(laidOut.value.document.package.document.comments) },
|
|
78
122
|
...options.metadata === void 0 ? {} : { metadata: options.metadata }
|
|
79
123
|
});
|
|
80
|
-
const written = await writePdf(list, {
|
|
81
|
-
fonts: { load: (face) => options.fonts.load(toFontRequest(face)) },
|
|
82
|
-
timestamp: options.timestamp,
|
|
83
|
-
...options.producer === void 0 ? {} : { producer: options.producer }
|
|
84
|
-
});
|
|
85
|
-
if (written.isErr()) return Result.err(new ExportPdfError({
|
|
86
|
-
message: written.error.message,
|
|
87
|
-
cause: written.error
|
|
88
|
-
}));
|
|
89
124
|
return Result.ok({
|
|
90
|
-
|
|
91
|
-
pageCount: list.pages.length,
|
|
92
|
-
unsupported: list.unsupported,
|
|
125
|
+
list,
|
|
93
126
|
layoutGaps: laidOut.value.unsupported,
|
|
94
|
-
measurementSubstitutions: headless.substitutions()
|
|
95
|
-
embeddingSubstitutions: written.value.substitutions,
|
|
96
|
-
unencodable: written.value.unencodable
|
|
127
|
+
measurementSubstitutions: headless.substitutions()
|
|
97
128
|
});
|
|
98
129
|
};
|
|
99
130
|
//#endregion
|
|
100
|
-
export { ExportPdfError, exportDocxToPdf };
|
|
131
|
+
export { ExportPdfError, buildDocxDisplayList, exportDocxToPdf, writeDisplayListPdf };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { HeadlessFontSource } from "./headlessMeasure.js";
|
|
2
|
+
//#region src/fonts/fontsourceFaces.d.ts
|
|
3
|
+
/** Where `@fontsource` files come from: `null` when the package or file is absent. */
|
|
4
|
+
type FontsourceFiles = {
|
|
5
|
+
/** Bytes of `@fontsource/<packageName>/<relativePath>`. */
|
|
6
|
+
readonly read: (packageName: string, relativePath: string) => Uint8Array | null;
|
|
7
|
+
};
|
|
8
|
+
/** Each bundled family and the `@fontsource` package that ships it. */
|
|
9
|
+
declare const FONTSOURCE_PACKAGES: {
|
|
10
|
+
readonly Arimo: "arimo";
|
|
11
|
+
readonly Caladea: "caladea";
|
|
12
|
+
readonly Carlito: "carlito";
|
|
13
|
+
readonly Cousine: "cousine";
|
|
14
|
+
readonly Lato: "lato";
|
|
15
|
+
readonly "Noto Sans Arabic": "noto-sans-arabic";
|
|
16
|
+
readonly "Source Sans 3": "source-sans-3";
|
|
17
|
+
readonly Tinos: "tinos";
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* A CSS `<string>` token, quoted and escaped. A family name comes from an
|
|
21
|
+
* authored document and is emitted into a `<style>` element, so the
|
|
22
|
+
* backslash is escaped first, then the quote, and line terminators become hex
|
|
23
|
+
* escapes, which CSS strings require.
|
|
24
|
+
*/
|
|
25
|
+
declare const cssString: (value: string) => string;
|
|
26
|
+
type FontsourceFontFaceCssOptions = {
|
|
27
|
+
/**
|
|
28
|
+
* Families to declare beyond the names the faces answer to directly. Pass a
|
|
29
|
+
* display list's own `fonts` families: a face reached through the fallback
|
|
30
|
+
* chain has no rule of its own, and the page names it by its authored name.
|
|
31
|
+
*/
|
|
32
|
+
readonly families?: readonly string[];
|
|
33
|
+
};
|
|
34
|
+
type FontsourceFaces = {
|
|
35
|
+
/** Binaries for the headless measurer and the PDF writer. */
|
|
36
|
+
readonly source: HeadlessFontSource;
|
|
37
|
+
/**
|
|
38
|
+
* `@font-face` rules with the `.woff` bytes inlined as `data:` URLs, one
|
|
39
|
+
* rule per served subset of each face, so a browser paints each code point
|
|
40
|
+
* from the binary that was measured.
|
|
41
|
+
*/
|
|
42
|
+
readonly fontFaceCss: (options?: FontsourceFontFaceCssOptions) => string;
|
|
43
|
+
};
|
|
44
|
+
/** The `@fontsource` faces, read through `files` and cached per face. */
|
|
45
|
+
declare const createFontsourceFaces: (files: FontsourceFiles) => FontsourceFaces;
|
|
46
|
+
//#endregion
|
|
47
|
+
export { FONTSOURCE_PACKAGES, FontsourceFaces, FontsourceFiles, FontsourceFontFaceCssOptions, createFontsourceFaces, cssString };
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { bytesToDataUrl } from "../utils/base64.js";
|
|
2
|
+
import { FONT_MAPPING } from "../utils/fontLoader.js";
|
|
3
|
+
import { parseFontFamilyList, resolveFontFamily } from "../utils/fontResolver.js";
|
|
4
|
+
//#region src/fonts/fontsourceFaces.ts
|
|
5
|
+
/**
|
|
6
|
+
* The subsets served, in resolution priority: a code point two subsets cover
|
|
7
|
+
* resolves to the earlier one on both sides.
|
|
8
|
+
*/
|
|
9
|
+
const SUBSET_PRIORITY = [
|
|
10
|
+
"latin",
|
|
11
|
+
"latin-ext",
|
|
12
|
+
"arabic"
|
|
13
|
+
];
|
|
14
|
+
/** Folio paints 400 and 700 only, as `DisplayFontFace` states. */
|
|
15
|
+
const REGULAR_WEIGHT = 400;
|
|
16
|
+
const BOLD_WEIGHT = 700;
|
|
17
|
+
/** Each bundled family and the `@fontsource` package that ships it. */
|
|
18
|
+
const FONTSOURCE_PACKAGES = {
|
|
19
|
+
Arimo: "arimo",
|
|
20
|
+
Caladea: "caladea",
|
|
21
|
+
Carlito: "carlito",
|
|
22
|
+
Cousine: "cousine",
|
|
23
|
+
Lato: "lato",
|
|
24
|
+
"Noto Sans Arabic": "noto-sans-arabic",
|
|
25
|
+
"Source Sans 3": "source-sans-3",
|
|
26
|
+
Tinos: "tinos"
|
|
27
|
+
};
|
|
28
|
+
/** A CSS stack ends in a generic, so a generic must resolve to a bundled family too. */
|
|
29
|
+
const GENERIC_FAMILIES = {
|
|
30
|
+
serif: "Tinos",
|
|
31
|
+
"sans-serif": "Arimo",
|
|
32
|
+
monospace: "Cousine"
|
|
33
|
+
};
|
|
34
|
+
const isBundledFamily = (name) => Object.hasOwn(FONTSOURCE_PACKAGES, name);
|
|
35
|
+
const isGenericFamily = (name) => Object.hasOwn(GENERIC_FAMILIES, name);
|
|
36
|
+
/** Every name a bundled family answers to: its own, and the authored names mapped to it. */
|
|
37
|
+
const familyIndexEntries = () => {
|
|
38
|
+
const entries = [];
|
|
39
|
+
for (const family of Object.keys(FONTSOURCE_PACKAGES)) if (isBundledFamily(family)) entries.push([family.toLowerCase(), family]);
|
|
40
|
+
for (const [authored, mapped] of Object.entries(FONT_MAPPING)) {
|
|
41
|
+
if (isBundledFamily(mapped)) {
|
|
42
|
+
entries.push([authored.toLowerCase(), mapped]);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (isGenericFamily(mapped)) entries.push([authored.toLowerCase(), GENERIC_FAMILIES[mapped]]);
|
|
46
|
+
}
|
|
47
|
+
for (const [generic, bundled] of Object.entries(GENERIC_FAMILIES)) entries.push([generic, bundled]);
|
|
48
|
+
return entries;
|
|
49
|
+
};
|
|
50
|
+
const FAMILY_INDEX = new Map(familyIndexEntries());
|
|
51
|
+
/** Every family name the faces answer to, in a stable order for the CSS. */
|
|
52
|
+
const bundledFamilyAliases = () => [...Object.keys(FONTSOURCE_PACKAGES), ...Object.keys(FONT_MAPPING).filter((authored) => FAMILY_INDEX.has(authored.toLowerCase()))].sort((a, b) => a.localeCompare(b, "en"));
|
|
53
|
+
/**
|
|
54
|
+
* The bundled family a requested name resolves to: directly, or through the
|
|
55
|
+
* same fallback chain the editor's font resolver walks.
|
|
56
|
+
*/
|
|
57
|
+
const resolveBundledFamily = (family) => {
|
|
58
|
+
const direct = FAMILY_INDEX.get(family.trim().toLowerCase());
|
|
59
|
+
if (direct !== void 0) return direct;
|
|
60
|
+
for (const candidate of parseFontFamilyList(resolveFontFamily(family).cssFallback)) {
|
|
61
|
+
const resolved = FAMILY_INDEX.get(candidate.trim().toLowerCase());
|
|
62
|
+
if (resolved !== void 0) return resolved;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
};
|
|
66
|
+
const weightOf = (bold) => bold ? BOLD_WEIGHT : REGULAR_WEIGHT;
|
|
67
|
+
/** `@fontsource` ships the ranges it cut each subset with, beside the files. */
|
|
68
|
+
const UNICODE_RANGE_FILE = "unicode.json";
|
|
69
|
+
const RANGE_TOKEN_RE = /^U\+([0-9A-Fa-f]+)(?:-([0-9A-Fa-f]+))?$/u;
|
|
70
|
+
const parseRangeToken = (token) => {
|
|
71
|
+
const match = RANGE_TOKEN_RE.exec(token.trim());
|
|
72
|
+
if (match === null) return [];
|
|
73
|
+
const first = Number.parseInt(match[1] ?? "", 16);
|
|
74
|
+
const last = match[2] === void 0 ? first : Number.parseInt(match[2], 16);
|
|
75
|
+
if (Number.isNaN(first) || Number.isNaN(last)) return [];
|
|
76
|
+
return [[first, last]];
|
|
77
|
+
};
|
|
78
|
+
const parseRangeList = (declared) => declared.split(",").flatMap(parseRangeToken);
|
|
79
|
+
const parseDeclaredRanges = (bytes) => {
|
|
80
|
+
const ranges = /* @__PURE__ */ new Map();
|
|
81
|
+
const parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
82
|
+
if (typeof parsed !== "object" || parsed === null) return ranges;
|
|
83
|
+
for (const [subset, declared] of Object.entries(parsed)) if (typeof declared === "string") ranges.set(subset, parseRangeList(declared));
|
|
84
|
+
return ranges;
|
|
85
|
+
};
|
|
86
|
+
const withoutRange = (range, [start, end]) => {
|
|
87
|
+
const [first, last] = range;
|
|
88
|
+
if (end < first || start > last) return [range];
|
|
89
|
+
const remainder = [];
|
|
90
|
+
if (start > first) remainder.push([first, start - 1]);
|
|
91
|
+
if (end < last) remainder.push([end + 1, last]);
|
|
92
|
+
return remainder;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* `subject` minus everything a higher-priority subset already serves. The
|
|
96
|
+
* declared ranges overlap (`latin` and `latin-ext` both claim some combining
|
|
97
|
+
* marks); cutting the overlap out of the lower-priority rule makes a browser,
|
|
98
|
+
* which resolves an overlap by declaration order, agree with this table.
|
|
99
|
+
*/
|
|
100
|
+
const subtractRanges = (subject, taken) => {
|
|
101
|
+
let remaining = subject;
|
|
102
|
+
for (const range of taken) remaining = remaining.flatMap((candidate) => withoutRange(candidate, range));
|
|
103
|
+
return remaining;
|
|
104
|
+
};
|
|
105
|
+
const hex = (codePoint) => codePoint.toString(16).toUpperCase().padStart(4, "0");
|
|
106
|
+
const formatRangeList = (ranges) => ranges.map(([first, last]) => first === last ? `U+${hex(first)}` : `U+${hex(first)}-${hex(last)}`).join(",");
|
|
107
|
+
/**
|
|
108
|
+
* A CSS `<string>` token, quoted and escaped. A family name comes from an
|
|
109
|
+
* authored document and is emitted into a `<style>` element, so the
|
|
110
|
+
* backslash is escaped first, then the quote, and line terminators become hex
|
|
111
|
+
* escapes, which CSS strings require.
|
|
112
|
+
*/
|
|
113
|
+
const cssString = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\n", "\\A ").replaceAll("\r", "\\D ").replaceAll("\f", "\\C ")}"`;
|
|
114
|
+
const FACE_VARIANTS = [
|
|
115
|
+
{
|
|
116
|
+
weight: REGULAR_WEIGHT,
|
|
117
|
+
italic: false
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
weight: REGULAR_WEIGHT,
|
|
121
|
+
italic: true
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
weight: BOLD_WEIGHT,
|
|
125
|
+
italic: false
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
weight: BOLD_WEIGHT,
|
|
129
|
+
italic: true
|
|
130
|
+
}
|
|
131
|
+
];
|
|
132
|
+
/** The media type a `.woff` face is inlined under. */
|
|
133
|
+
const WOFF_MEDIA_TYPE = "font/woff";
|
|
134
|
+
const fontFaceRule = ({ family, dataUrl, weight, italic, unicodeRange }) => [
|
|
135
|
+
"@font-face {",
|
|
136
|
+
` font-family: ${cssString(family)};`,
|
|
137
|
+
` src: url(${dataUrl}) format("woff");`,
|
|
138
|
+
` font-weight: ${String(weight)};`,
|
|
139
|
+
` font-style: ${italic ? "italic" : "normal"};`,
|
|
140
|
+
` unicode-range: ${formatRangeList(unicodeRange)};`,
|
|
141
|
+
" font-display: block;",
|
|
142
|
+
"}"
|
|
143
|
+
].join("\n");
|
|
144
|
+
/** The `@fontsource` faces, read through `files` and cached per face. */
|
|
145
|
+
const createFontsourceFaces = (files) => {
|
|
146
|
+
const faceCache = /* @__PURE__ */ new Map();
|
|
147
|
+
const rangeCache = /* @__PURE__ */ new Map();
|
|
148
|
+
const dataUrlCache = /* @__PURE__ */ new Map();
|
|
149
|
+
const faceBinaries = ({ family, weight, italic }) => {
|
|
150
|
+
const bundled = resolveBundledFamily(family);
|
|
151
|
+
if (bundled === null) return [];
|
|
152
|
+
const packageName = FONTSOURCE_PACKAGES[bundled];
|
|
153
|
+
const style = italic ? "italic" : "normal";
|
|
154
|
+
const key = `${packageName}|${String(weight)}|${style}`;
|
|
155
|
+
const cached = faceCache.get(key);
|
|
156
|
+
if (cached !== void 0) return cached;
|
|
157
|
+
const binaries = SUBSET_PRIORITY.flatMap((subset) => {
|
|
158
|
+
const bytes = files.read(packageName, `files/${packageName}-${subset}-${String(weight)}-${style}.woff`);
|
|
159
|
+
return bytes === null ? [] : [{
|
|
160
|
+
subset,
|
|
161
|
+
bytes
|
|
162
|
+
}];
|
|
163
|
+
});
|
|
164
|
+
faceCache.set(key, binaries);
|
|
165
|
+
return binaries;
|
|
166
|
+
};
|
|
167
|
+
const declaredRanges = (packageName) => {
|
|
168
|
+
const cached = rangeCache.get(packageName);
|
|
169
|
+
if (cached !== void 0) return cached;
|
|
170
|
+
const bytes = files.read(packageName, UNICODE_RANGE_FILE);
|
|
171
|
+
const ranges = bytes === null ? /* @__PURE__ */ new Map() : parseDeclaredRanges(bytes);
|
|
172
|
+
rangeCache.set(packageName, ranges);
|
|
173
|
+
return ranges;
|
|
174
|
+
};
|
|
175
|
+
const dataUrlOf = (bytes) => {
|
|
176
|
+
const cached = dataUrlCache.get(bytes);
|
|
177
|
+
if (cached !== void 0) return cached;
|
|
178
|
+
const encoded = bytesToDataUrl(bytes, WOFF_MEDIA_TYPE);
|
|
179
|
+
dataUrlCache.set(bytes, encoded);
|
|
180
|
+
return encoded;
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* The rules for one variant of one family name. A subset whose range a
|
|
184
|
+
* higher-priority subset already serves, or that `unicode.json` does not
|
|
185
|
+
* describe, is not declared: a rule with no `unicode-range` would cover
|
|
186
|
+
* every code point.
|
|
187
|
+
*/
|
|
188
|
+
const variantRules = ({ family, weight, italic }) => {
|
|
189
|
+
const bundled = resolveBundledFamily(family);
|
|
190
|
+
if (bundled === null) return [];
|
|
191
|
+
const ranges = declaredRanges(FONTSOURCE_PACKAGES[bundled]);
|
|
192
|
+
const rules = [];
|
|
193
|
+
const taken = [];
|
|
194
|
+
for (const binary of faceBinaries({
|
|
195
|
+
family,
|
|
196
|
+
weight,
|
|
197
|
+
italic
|
|
198
|
+
})) {
|
|
199
|
+
const unicodeRange = subtractRanges(ranges.get(binary.subset) ?? [], taken);
|
|
200
|
+
if (unicodeRange.length === 0) continue;
|
|
201
|
+
rules.push(fontFaceRule({
|
|
202
|
+
family,
|
|
203
|
+
dataUrl: dataUrlOf(binary.bytes),
|
|
204
|
+
weight,
|
|
205
|
+
italic,
|
|
206
|
+
unicodeRange
|
|
207
|
+
}));
|
|
208
|
+
taken.push(...unicodeRange);
|
|
209
|
+
}
|
|
210
|
+
return rules;
|
|
211
|
+
};
|
|
212
|
+
return {
|
|
213
|
+
source: { load: ({ family, bold, italic }) => faceBinaries({
|
|
214
|
+
family,
|
|
215
|
+
weight: weightOf(bold),
|
|
216
|
+
italic
|
|
217
|
+
}).map(({ bytes }) => bytes) },
|
|
218
|
+
fontFaceCss: (options = {}) => {
|
|
219
|
+
const declared = /* @__PURE__ */ new Map();
|
|
220
|
+
for (const family of [...bundledFamilyAliases(), ...options.families ?? []]) declared.set(family.trim().toLowerCase(), family.trim());
|
|
221
|
+
return [...declared.values()].flatMap((family) => FACE_VARIANTS.flatMap(({ weight, italic }) => variantRules({
|
|
222
|
+
family,
|
|
223
|
+
weight,
|
|
224
|
+
italic
|
|
225
|
+
}))).join("\n");
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
//#endregion
|
|
230
|
+
export { FONTSOURCE_PACKAGES, createFontsourceFaces, cssString };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { recreateProseNodeWithParagraphPropertySource } from "../docx/paragraphPropertySource.js";
|
|
2
2
|
import { expectRunPropertyChangeMarkAttrs } from "../prosemirror/attrs/index.js";
|
|
3
|
+
import { resolutionRemovesControl, withoutResolvedEnclosures } from "../prosemirror/contentControlRevisions.js";
|
|
3
4
|
import "../prosemirror/extensions/nodes/SdtExtension.js";
|
|
5
|
+
import { continuedRunMarks } from "../prosemirror/rejoinRunCarriers.js";
|
|
4
6
|
import { RUN_FORMATTING_MARK_NAMES } from "../prosemirror/runFormattingMarkNames.js";
|
|
5
7
|
import { reconstructRejectedRunFormattingMarks } from "../prosemirror/runPropertyChangeResolution.js";
|
|
6
8
|
import { paragraphRunStyleContext } from "../prosemirror/runStyleFormatting.js";
|
|
@@ -75,8 +77,26 @@ const resolveInlineNode = ({ node, context, paragraphScope }) => {
|
|
|
75
77
|
if (context.keepType) marks = marks.filter((mark) => mark.type !== context.keepType);
|
|
76
78
|
return marksEqual(marks, node.marks) ? node : node.mark(marks);
|
|
77
79
|
};
|
|
80
|
+
/**
|
|
81
|
+
* The pieces a revision split off its run, one run again: the headless half of
|
|
82
|
+
* `rejoinRunsAt`, over the children of one inline container.
|
|
83
|
+
*/
|
|
84
|
+
const rejoinResolvedRuns = ({ children, resolvedBoundaries, paragraphScope, context }) => {
|
|
85
|
+
for (const index of [...resolvedBoundaries].toSorted((left, right) => left - right)) {
|
|
86
|
+
const left = children[index - 1];
|
|
87
|
+
const right = children[index];
|
|
88
|
+
if (!left || !right) continue;
|
|
89
|
+
const marks = continuedRunMarks({
|
|
90
|
+
left,
|
|
91
|
+
right,
|
|
92
|
+
context: () => resolveParagraphRunStyleScope(paragraphScope, context.styleResolver),
|
|
93
|
+
styleResolver: context.styleResolver
|
|
94
|
+
});
|
|
95
|
+
if (marks !== null) children[index] = right.mark(marks);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
78
98
|
const resolveInlineContent = ({ node, position, context, inheritedParagraphScope }) => {
|
|
79
|
-
const paragraphScope = node.type.name === "paragraph"
|
|
99
|
+
const paragraphScope = node.type.name === "paragraph" ? { paragraph: node } : inheritedParagraphScope;
|
|
80
100
|
const replacementRangeStart = context.replacementRanges.length;
|
|
81
101
|
let resolvedNode = node;
|
|
82
102
|
if (node.isInline) {
|
|
@@ -96,7 +116,21 @@ const resolveInlineContent = ({ node, position, context, inheritedParagraphScope
|
|
|
96
116
|
resolvedNode = resolved;
|
|
97
117
|
if (resolvedNode.isLeaf) return resolvedNode;
|
|
98
118
|
}
|
|
119
|
+
if (resolutionRemovesControl({
|
|
120
|
+
control: resolvedNode,
|
|
121
|
+
removeType: context.removeType,
|
|
122
|
+
resolves: () => true
|
|
123
|
+
})) {
|
|
124
|
+
context.replacementRanges.push({
|
|
125
|
+
from: position,
|
|
126
|
+
to: position + node.nodeSize,
|
|
127
|
+
newSize: 0
|
|
128
|
+
});
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
99
131
|
const children = [];
|
|
132
|
+
/** Indices into `children` where resolved content began or ended. */
|
|
133
|
+
const resolvedBoundaries = /* @__PURE__ */ new Set();
|
|
100
134
|
const contentStart = resolvedNode.type.name === "doc" ? 0 : position + 1;
|
|
101
135
|
let contentChanged = false;
|
|
102
136
|
resolvedNode.forEach((child, offset) => {
|
|
@@ -106,20 +140,23 @@ const resolveInlineContent = ({ node, position, context, inheritedParagraphScope
|
|
|
106
140
|
context,
|
|
107
141
|
...paragraphScope !== void 0 ? { inheritedParagraphScope: paragraphScope } : {}
|
|
108
142
|
});
|
|
143
|
+
if (resolved !== child) {
|
|
144
|
+
contentChanged = true;
|
|
145
|
+
resolvedBoundaries.add(children.length);
|
|
146
|
+
}
|
|
109
147
|
if (resolved) {
|
|
110
148
|
children.push(resolved);
|
|
111
|
-
|
|
112
|
-
}
|
|
149
|
+
if (resolved !== child) resolvedBoundaries.add(children.length);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
const resolvedAttrs = resolvedNode.type.name === "sdt" ? withoutResolvedEnclosures(resolvedNode, () => true) ?? resolvedNode.attrs : resolvedNode.attrs;
|
|
153
|
+
if (!contentChanged && resolvedAttrs === resolvedNode.attrs) return resolvedNode;
|
|
154
|
+
if (contentChanged && resolvedNode.inlineContent) rejoinResolvedRuns({
|
|
155
|
+
children,
|
|
156
|
+
resolvedBoundaries,
|
|
157
|
+
paragraphScope,
|
|
158
|
+
context
|
|
113
159
|
});
|
|
114
|
-
if (!contentChanged) return resolvedNode;
|
|
115
|
-
if (children.length === 0 && node.childCount > 0 && resolvedNode.type.name === "sdt") {
|
|
116
|
-
context.replacementRanges.splice(replacementRangeStart, context.replacementRanges.length - replacementRangeStart, {
|
|
117
|
-
from: position,
|
|
118
|
-
to: position + node.nodeSize,
|
|
119
|
-
newSize: 0
|
|
120
|
-
});
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
160
|
let resolvedContent = Fragment.fromArray(children);
|
|
124
161
|
if (!resolvedNode.type.validContent(resolvedContent)) {
|
|
125
162
|
const fitted = resolvedNode.type.createAndFill(resolvedNode.attrs, resolvedContent, resolvedNode.marks);
|
|
@@ -143,7 +180,7 @@ const resolveInlineContent = ({ node, position, context, inheritedParagraphScope
|
|
|
143
180
|
from: position,
|
|
144
181
|
to: position + resolvedNode.nodeSize
|
|
145
182
|
});
|
|
146
|
-
return rebuildNode(node,
|
|
183
|
+
return rebuildNode(node, resolvedAttrs, resolvedContent, resolvedNode.marks);
|
|
147
184
|
};
|
|
148
185
|
const coalesceReplacementRanges = (replacementRanges) => {
|
|
149
186
|
const coalesced = [];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FlowBlock } from "../../layout-engine/types.js";
|
|
2
|
+
//#region src/layout-bridge/convert/flowBlockPostprocessing.d.ts
|
|
3
|
+
declare function suppressFinalEmptyParagraphAfterTable(blocks: FlowBlock[]): void;
|
|
4
|
+
declare function suppressFinalParagraphInRepeatedEmptySuffix(blocks: FlowBlock[]): void;
|
|
5
|
+
declare function reserveLeadingEmptyOutlineHeight(blocks: FlowBlock[]): void;
|
|
6
|
+
declare function mergeRunInParagraphs(blocks: FlowBlock[]): FlowBlock[];
|
|
7
|
+
//#endregion
|
|
8
|
+
export { mergeRunInParagraphs, reserveLeadingEmptyOutlineHeight, suppressFinalEmptyParagraphAfterTable, suppressFinalParagraphInRepeatedEmptySuffix };
|