@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,136 @@
|
|
|
1
|
+
import { BLACK, WHITE } from "../primitives.js";
|
|
2
|
+
//#region src/display-list/build/colors.ts
|
|
3
|
+
/**
|
|
4
|
+
* CSS colour → {@link DisplayColor}.
|
|
5
|
+
*
|
|
6
|
+
* The display list's contract is that colours are already resolved: a backend
|
|
7
|
+
* never sees a custom property, a named OOXML highlight, or `currentColor`.
|
|
8
|
+
* Every such value is collapsed here, at the one place that still knows what
|
|
9
|
+
* the editor's stylesheet means.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The editor's document custom properties, materialised. A PDF has no theme
|
|
13
|
+
* and no stylesheet: `--doc-canvas` is the paper, `--doc-canvas-text` the ink,
|
|
14
|
+
* `--suggestion-color` the AI-proposal hue (`renderParagraph.ts`'s
|
|
15
|
+
* `SUGGESTION_COLOR_CSS` fallback).
|
|
16
|
+
*/
|
|
17
|
+
const DOC_CANVAS = WHITE;
|
|
18
|
+
const DOC_CANVAS_TEXT = BLACK;
|
|
19
|
+
const SUGGESTION_COLOR = {
|
|
20
|
+
r: 109,
|
|
21
|
+
g: 59,
|
|
22
|
+
b: 214,
|
|
23
|
+
a: 1
|
|
24
|
+
};
|
|
25
|
+
const TRANSPARENT = {
|
|
26
|
+
r: 0,
|
|
27
|
+
g: 0,
|
|
28
|
+
b: 0,
|
|
29
|
+
a: 0
|
|
30
|
+
};
|
|
31
|
+
/** Word's automatic-colour keywords (`w:color w:val="auto"`). */
|
|
32
|
+
const AUTOMATIC_COLOR_VALUES = /* @__PURE__ */ new Set([
|
|
33
|
+
"auto",
|
|
34
|
+
"windowtext",
|
|
35
|
+
"currentcolor",
|
|
36
|
+
"inherit"
|
|
37
|
+
]);
|
|
38
|
+
/**
|
|
39
|
+
* OOXML's named highlight palette (§17.18.40) plus the CSS basic colours the
|
|
40
|
+
* bridge can pass through. Kept small on purpose: the bridge resolves named
|
|
41
|
+
* highlights to hex before layout, so this is a safety net for the values that
|
|
42
|
+
* still arrive as names.
|
|
43
|
+
*
|
|
44
|
+
* A `Map`, not an object: the key is an authored string, and an object lookup
|
|
45
|
+
* would answer `constructor`, `toString` or `__proto__` from the prototype and
|
|
46
|
+
* hand a function to {@link parseHex}.
|
|
47
|
+
*/
|
|
48
|
+
const NAMED_COLORS = /* @__PURE__ */ new Map([
|
|
49
|
+
["black", "#000000"],
|
|
50
|
+
["blue", "#0000ff"],
|
|
51
|
+
["cyan", "#00ffff"],
|
|
52
|
+
["darkblue", "#000080"],
|
|
53
|
+
["darkcyan", "#008080"],
|
|
54
|
+
["darkgray", "#808080"],
|
|
55
|
+
["darkgrey", "#808080"],
|
|
56
|
+
["darkgreen", "#008000"],
|
|
57
|
+
["darkmagenta", "#800080"],
|
|
58
|
+
["darkred", "#800000"],
|
|
59
|
+
["darkyellow", "#808000"],
|
|
60
|
+
["gray", "#808080"],
|
|
61
|
+
["grey", "#808080"],
|
|
62
|
+
["green", "#00ff00"],
|
|
63
|
+
["lightgray", "#c0c0c0"],
|
|
64
|
+
["lightgrey", "#c0c0c0"],
|
|
65
|
+
["magenta", "#ff00ff"],
|
|
66
|
+
["red", "#ff0000"],
|
|
67
|
+
["silver", "#c0c0c0"],
|
|
68
|
+
["white", "#ffffff"],
|
|
69
|
+
["yellow", "#ffff00"]
|
|
70
|
+
]);
|
|
71
|
+
const DOC_CUSTOM_PROPERTIES = {
|
|
72
|
+
"--doc-canvas": DOC_CANVAS,
|
|
73
|
+
"--doc-canvas-text": DOC_CANVAS_TEXT,
|
|
74
|
+
"--suggestion-color": SUGGESTION_COLOR
|
|
75
|
+
};
|
|
76
|
+
const HEX_PATTERN = /^#?(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/iu;
|
|
77
|
+
const RGB_PATTERN = /^rgba?\(\s*([\d.]+%?)\s*[, ]\s*([\d.]+%?)\s*[, ]\s*([\d.]+%?)\s*(?:[,/]\s*([\d.]+%?)\s*)?\)$/iu;
|
|
78
|
+
const VAR_PATTERN = /^var\(\s*(--[\w-]+)\s*(?:,\s*(.+))?\)$/iu;
|
|
79
|
+
const clampChannel = (value) => Math.min(255, Math.max(0, Math.round(value)));
|
|
80
|
+
const clampAlpha = (value) => Math.min(1, Math.max(0, value));
|
|
81
|
+
const parseHex = (raw) => {
|
|
82
|
+
const hex = raw.replace(/^#/u, "");
|
|
83
|
+
const expand = (part) => Number.parseInt(part.repeat(2), 16);
|
|
84
|
+
if (hex.length === 3 || hex.length === 4) return {
|
|
85
|
+
r: expand(hex[0]),
|
|
86
|
+
g: expand(hex[1]),
|
|
87
|
+
b: expand(hex[2]),
|
|
88
|
+
a: hex.length === 4 ? expand(hex[3]) / 255 : 1
|
|
89
|
+
};
|
|
90
|
+
if (hex.length === 6 || hex.length === 8) return {
|
|
91
|
+
r: Number.parseInt(hex.slice(0, 2), 16),
|
|
92
|
+
g: Number.parseInt(hex.slice(2, 4), 16),
|
|
93
|
+
b: Number.parseInt(hex.slice(4, 6), 16),
|
|
94
|
+
a: hex.length === 8 ? Number.parseInt(hex.slice(6, 8), 16) / 255 : 1
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
const parseComponent = (raw, scale) => raw.endsWith("%") ? Number.parseFloat(raw) / 100 * scale : Number.parseFloat(raw);
|
|
98
|
+
const parseRgb = (raw) => {
|
|
99
|
+
const match = RGB_PATTERN.exec(raw);
|
|
100
|
+
if (!match) return;
|
|
101
|
+
const r = clampChannel(parseComponent(match[1], 255));
|
|
102
|
+
const g = clampChannel(parseComponent(match[2], 255));
|
|
103
|
+
const b = clampChannel(parseComponent(match[3], 255));
|
|
104
|
+
const alphaRaw = match[4];
|
|
105
|
+
return {
|
|
106
|
+
r,
|
|
107
|
+
g,
|
|
108
|
+
b,
|
|
109
|
+
a: alphaRaw === void 0 ? 1 : clampAlpha(parseComponent(alphaRaw, 1))
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Resolve an authored CSS colour. Returns `undefined` for a value this module
|
|
114
|
+
* cannot turn into concrete channels (`color-mix()`, `oklch()`, an unknown
|
|
115
|
+
* name) so the caller can report it rather than paint a guess.
|
|
116
|
+
*/
|
|
117
|
+
const parseDisplayColor = (color) => {
|
|
118
|
+
if (color === void 0) return;
|
|
119
|
+
const trimmed = color.trim();
|
|
120
|
+
if (trimmed.length === 0) return;
|
|
121
|
+
const lower = trimmed.toLowerCase();
|
|
122
|
+
if (lower === "transparent" || lower === "none") return TRANSPARENT;
|
|
123
|
+
if (AUTOMATIC_COLOR_VALUES.has(lower)) return DOC_CANVAS_TEXT;
|
|
124
|
+
const varMatch = VAR_PATTERN.exec(trimmed);
|
|
125
|
+
if (varMatch) {
|
|
126
|
+
const known = DOC_CUSTOM_PROPERTIES[varMatch[1].toLowerCase()];
|
|
127
|
+
if (known) return known;
|
|
128
|
+
return parseDisplayColor(varMatch[2]);
|
|
129
|
+
}
|
|
130
|
+
const named = NAMED_COLORS.get(lower);
|
|
131
|
+
if (named !== void 0) return parseHex(named);
|
|
132
|
+
if (HEX_PATTERN.test(trimmed)) return parseHex(trimmed);
|
|
133
|
+
return parseRgb(lower);
|
|
134
|
+
};
|
|
135
|
+
//#endregion
|
|
136
|
+
export { DOC_CANVAS, DOC_CANVAS_TEXT, SUGGESTION_COLOR, TRANSPARENT, parseDisplayColor };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ImageRun, Page, ParagraphBlock } from "../../layout-engine/types.js";
|
|
2
|
+
import { PageGeometry } from "../../layout-painter/anchoredImagePosition.js";
|
|
3
|
+
import { DisplayPrimitive } from "../types.js";
|
|
4
|
+
import { BuildContext } from "./buildContext.js";
|
|
5
|
+
//#region src/display-list/build/floatingImages.d.ts
|
|
6
|
+
declare const pageGeometryOf: (page: Page) => PageGeometry;
|
|
7
|
+
type FloatingImagePlacement = {
|
|
8
|
+
readonly run: ImageRun;
|
|
9
|
+
/** `w:wrap behind` paints under the body text; everything else paints over it. */
|
|
10
|
+
readonly behindDoc: boolean;
|
|
11
|
+
readonly primitives: readonly DisplayPrimitive[];
|
|
12
|
+
};
|
|
13
|
+
type CollectFloatingImagesOptions = {
|
|
14
|
+
readonly block: ParagraphBlock;
|
|
15
|
+
/** The paragraph fragment's y, page-absolute. */
|
|
16
|
+
readonly fragmentYPx: number;
|
|
17
|
+
readonly geometry: PageGeometry;
|
|
18
|
+
readonly context: BuildContext;
|
|
19
|
+
/** Runs already placed on this page, so a split paragraph cannot double-paint. */
|
|
20
|
+
readonly placed: Set<ImageRun>;
|
|
21
|
+
};
|
|
22
|
+
declare const collectFloatingImages: ({ block, fragmentYPx, geometry, context, placed }: CollectFloatingImagesOptions) => readonly FloatingImagePlacement[];
|
|
23
|
+
//#endregion
|
|
24
|
+
export { CollectFloatingImagesOptions, FloatingImagePlacement, collectFloatingImages, pageGeometryOf };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { isFloatingImageRun } from "../../layout-engine/types.js";
|
|
2
|
+
import { resolveAnchoredImagePosition } from "../../layout-painter/anchoredImagePosition.js";
|
|
3
|
+
import { paintImage } from "./imagePrimitives.js";
|
|
4
|
+
//#region src/display-list/build/floatingImages.ts
|
|
5
|
+
const pageGeometryOf = (page) => ({
|
|
6
|
+
pageWidth: page.size.w,
|
|
7
|
+
pageHeight: page.size.h,
|
|
8
|
+
marginLeft: page.margins.left,
|
|
9
|
+
marginTop: page.margins.top,
|
|
10
|
+
marginRight: page.margins.right,
|
|
11
|
+
marginBottom: page.margins.bottom,
|
|
12
|
+
...page.authoredMargins === void 0 ? {} : { authoredMargins: page.authoredMargins },
|
|
13
|
+
contentWidth: page.size.w - page.margins.left - page.margins.right,
|
|
14
|
+
contentHeight: page.size.h - page.margins.top - page.margins.bottom
|
|
15
|
+
});
|
|
16
|
+
const collectFloatingImages = ({ block, fragmentYPx, geometry, context, placed }) => {
|
|
17
|
+
const placements = [];
|
|
18
|
+
for (const run of block.runs) {
|
|
19
|
+
if (run.kind !== "image" || !isFloatingImageRun(run) || placed.has(run)) continue;
|
|
20
|
+
placed.add(run);
|
|
21
|
+
const anchor = resolveAnchoredImagePosition(run, fragmentYPx - geometry.marginTop, geometry);
|
|
22
|
+
placements.push({
|
|
23
|
+
run,
|
|
24
|
+
behindDoc: run.wrapType === "behind",
|
|
25
|
+
primitives: paintImage({
|
|
26
|
+
source: run,
|
|
27
|
+
rect: {
|
|
28
|
+
xPx: geometry.marginLeft + anchor.x,
|
|
29
|
+
yPx: geometry.marginTop + anchor.y,
|
|
30
|
+
widthPx: run.width,
|
|
31
|
+
heightPx: run.height
|
|
32
|
+
},
|
|
33
|
+
context,
|
|
34
|
+
label: "floating image"
|
|
35
|
+
})
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return placements;
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
export { collectFloatingImages, pageGeometryOf };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { EmbeddedFont } from "../../fonts/embeddedFonts.js";
|
|
2
|
+
import { DisplayFontFace, DisplayFontRef } from "../types.js";
|
|
3
|
+
import { FontStyle } from "../../layout-engine/measure/measureTypes.js";
|
|
4
|
+
//#region src/display-list/build/fontTable.d.ts
|
|
5
|
+
type FontFaceRequest = {
|
|
6
|
+
readonly fontFamily: string;
|
|
7
|
+
readonly alternateFontFamily?: string;
|
|
8
|
+
readonly bold?: boolean;
|
|
9
|
+
readonly italic?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* The style the run measures with. The face's font-box ratios come from the
|
|
12
|
+
* measurer, not from a table of our own, so a backend places a baseline
|
|
13
|
+
* where the engine placed it.
|
|
14
|
+
*/
|
|
15
|
+
readonly measureStyle: FontStyle;
|
|
16
|
+
};
|
|
17
|
+
declare class FontTable {
|
|
18
|
+
private readonly faces;
|
|
19
|
+
private readonly indexByKey;
|
|
20
|
+
private readonly embeddedByKey;
|
|
21
|
+
/**
|
|
22
|
+
* `embeddedFonts` are the package's own faces (`fonts/embeddedFonts.ts`).
|
|
23
|
+
* Without them a face the measurer used but no host can supply travels as a
|
|
24
|
+
* family name a backend has no way to resolve.
|
|
25
|
+
*/
|
|
26
|
+
constructor(embeddedFonts?: readonly EmbeddedFont[]);
|
|
27
|
+
intern({ fontFamily, alternateFontFamily, bold, italic, measureStyle }: FontFaceRequest): DisplayFontRef;
|
|
28
|
+
snapshot(): readonly DisplayFontFace[];
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { FontFaceRequest, FontTable };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { ptToPx } from "../../layout-engine/measure/measureHelpers.js";
|
|
2
|
+
import { getFontMetrics } from "../../layout-engine/measure/measureProvider.js";
|
|
3
|
+
import { resolveFontFamily } from "../../utils/fontResolver.js";
|
|
4
|
+
//#region src/display-list/build/fontTable.ts
|
|
5
|
+
const CSS_NORMAL_WEIGHT = 400;
|
|
6
|
+
const CSS_BOLD_WEIGHT = 700;
|
|
7
|
+
const GENERIC_FAMILIES = {
|
|
8
|
+
serif: "serif",
|
|
9
|
+
"sans-serif": "sans-serif",
|
|
10
|
+
monospace: "monospace",
|
|
11
|
+
cursive: "cursive",
|
|
12
|
+
fantasy: "fantasy"
|
|
13
|
+
};
|
|
14
|
+
const DEFAULT_GENERIC = "sans-serif";
|
|
15
|
+
const splitFallbackStack = (cssFallback) => cssFallback.split(",").map((entry) => entry.trim().replace(/^["']|["']$/gu, ""));
|
|
16
|
+
/**
|
|
17
|
+
* The category `resolveFontFamily` chose. It is the last CSS generic in the
|
|
18
|
+
* stack it built, which is where `detectFontCategory` puts it; reading it back
|
|
19
|
+
* is cheaper and less brittle than re-deriving the category from the name.
|
|
20
|
+
*/
|
|
21
|
+
const genericOf = (stack) => {
|
|
22
|
+
for (let index = stack.length - 1; index >= 0; index -= 1) {
|
|
23
|
+
const generic = GENERIC_FAMILIES[stack[index].toLowerCase()];
|
|
24
|
+
if (generic !== void 0) return generic;
|
|
25
|
+
}
|
|
26
|
+
return DEFAULT_GENERIC;
|
|
27
|
+
};
|
|
28
|
+
const faceKey = (family, weight, italic) => `${family.toLowerCase()} ${weight} ${italic ? "i" : "r"}`;
|
|
29
|
+
/**
|
|
30
|
+
* Package faces keyed by every family name a run can resolve to.
|
|
31
|
+
*
|
|
32
|
+
* `resolveFontFamily` substitutes the scoped `folio-embedded-{nonce}-{name}`
|
|
33
|
+
* family only while the document's embedded-family map is installed; a build
|
|
34
|
+
* that never installed it resolves the same run to the authored name. Both
|
|
35
|
+
* names therefore key the same face, and the bytes are interned by id so one
|
|
36
|
+
* face's binary appears once however many names reach it.
|
|
37
|
+
*/
|
|
38
|
+
const indexEmbeddedFonts = (faces) => {
|
|
39
|
+
const byId = /* @__PURE__ */ new Map();
|
|
40
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
41
|
+
for (const face of faces) {
|
|
42
|
+
const id = `${face.family}:${face.weight}:${face.style}`;
|
|
43
|
+
const embedded = byId.get(id) ?? {
|
|
44
|
+
id,
|
|
45
|
+
bytes: face.bytes
|
|
46
|
+
};
|
|
47
|
+
byId.set(id, embedded);
|
|
48
|
+
const italic = face.style === "italic";
|
|
49
|
+
byKey.set(faceKey(face.family, face.weight, italic), embedded);
|
|
50
|
+
byKey.set(faceKey(face.originalFamily, face.weight, italic), embedded);
|
|
51
|
+
}
|
|
52
|
+
return byKey;
|
|
53
|
+
};
|
|
54
|
+
var FontTable = class {
|
|
55
|
+
faces = [];
|
|
56
|
+
indexByKey = /* @__PURE__ */ new Map();
|
|
57
|
+
embeddedByKey;
|
|
58
|
+
/**
|
|
59
|
+
* `embeddedFonts` are the package's own faces (`fonts/embeddedFonts.ts`).
|
|
60
|
+
* Without them a face the measurer used but no host can supply travels as a
|
|
61
|
+
* family name a backend has no way to resolve.
|
|
62
|
+
*/
|
|
63
|
+
constructor(embeddedFonts = []) {
|
|
64
|
+
this.embeddedByKey = indexEmbeddedFonts(embeddedFonts);
|
|
65
|
+
}
|
|
66
|
+
intern({ fontFamily, alternateFontFamily, bold, italic, measureStyle }) {
|
|
67
|
+
const stack = splitFallbackStack(resolveFontFamily(fontFamily, alternateFontFamily).cssFallback);
|
|
68
|
+
const family = stack.at(0) ?? fontFamily;
|
|
69
|
+
const weight = bold ? CSS_BOLD_WEIGHT : CSS_NORMAL_WEIGHT;
|
|
70
|
+
const isItalic = italic === true;
|
|
71
|
+
const key = faceKey(family, weight, isItalic);
|
|
72
|
+
const existing = this.indexByKey.get(key);
|
|
73
|
+
if (existing !== void 0) return existing;
|
|
74
|
+
const metrics = getFontMetrics(measureStyle);
|
|
75
|
+
const fontSizePx = ptToPx(measureStyle.fontSize ?? metrics.fontSize);
|
|
76
|
+
const scale = fontSizePx > 0 ? 1 / fontSizePx : 0;
|
|
77
|
+
const index = this.faces.length;
|
|
78
|
+
const embedded = this.embeddedByKey.get(key);
|
|
79
|
+
this.faces.push({
|
|
80
|
+
family,
|
|
81
|
+
weight,
|
|
82
|
+
italic: isItalic,
|
|
83
|
+
generic: genericOf(stack),
|
|
84
|
+
fallbacks: stack.slice(1).filter((entry) => GENERIC_FAMILIES[entry.toLowerCase()] === void 0),
|
|
85
|
+
fontBoxAscentRatio: metrics.fontBoxAscent * scale,
|
|
86
|
+
fontBoxDescentRatio: metrics.fontBoxDescent * scale,
|
|
87
|
+
...embedded === void 0 ? {} : { embedded }
|
|
88
|
+
});
|
|
89
|
+
this.indexByKey.set(key, index);
|
|
90
|
+
return index;
|
|
91
|
+
}
|
|
92
|
+
snapshot() {
|
|
93
|
+
return this.faces;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
//#endregion
|
|
97
|
+
export { FontTable };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { document_d_exports } from "../../types/document.js";
|
|
2
|
+
import { FootnoteContent, HeaderFooterContent, Page } from "../../layout-engine/types.js";
|
|
3
|
+
import { DisplayPrimitive } from "../types.js";
|
|
4
|
+
import { BuildContext } from "./buildContext.js";
|
|
5
|
+
import { PageBorders } from "./pageBorderPrimitives.js";
|
|
6
|
+
import { PageComposer } from "./regions.js";
|
|
7
|
+
//#region src/display-list/build/furniture.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* Everything the builder cannot derive from a `Layout`. Every field is
|
|
10
|
+
* optional, and every one of them means the same thing when absent: the
|
|
11
|
+
* construct is not painted, and it is reported wherever the layout or the
|
|
12
|
+
* caller's `documentFeatures` shows the document had one.
|
|
13
|
+
*/
|
|
14
|
+
type PageFurnitureInputs = {
|
|
15
|
+
/**
|
|
16
|
+
* `w:pgBorders` for the section. Absent: no border is painted, and the gap is
|
|
17
|
+
* reported unless `documentFeatures` says the document has none.
|
|
18
|
+
*/
|
|
19
|
+
readonly pageBorders?: PageBorders;
|
|
20
|
+
/** Resolves `w:themeColor` on a page border. Absent: authored colours only. */
|
|
21
|
+
readonly theme?: document_d_exports.Theme | null;
|
|
22
|
+
/**
|
|
23
|
+
* The watermark for every page with no more specific match in
|
|
24
|
+
* {@link watermarkByHeaderRId}. Absent with that map also absent: no
|
|
25
|
+
* watermark is painted, and the gap is reported unless `documentFeatures`
|
|
26
|
+
* says the document has none.
|
|
27
|
+
*/
|
|
28
|
+
readonly watermark?: document_d_exports.Watermark;
|
|
29
|
+
/**
|
|
30
|
+
* Per-header-rId watermarks, authoritative when present: a page whose active
|
|
31
|
+
* header carries none paints none, rather than inheriting {@link watermark}.
|
|
32
|
+
*/
|
|
33
|
+
readonly watermarkByHeaderRId?: ReadonlyMap<string, document_d_exports.Watermark>;
|
|
34
|
+
/**
|
|
35
|
+
* Resolved `data:` source for a picture watermark. Absent: the picture is
|
|
36
|
+
* reported, because the relationship id → asset mapping belongs to the
|
|
37
|
+
* package layer and the builder does no I/O.
|
|
38
|
+
*/
|
|
39
|
+
readonly watermarkImageSrc?: string;
|
|
40
|
+
/**
|
|
41
|
+
* The header for every page, or for pages 2+ when `titlePg` is set. Absent
|
|
42
|
+
* with the per-rId map also absent: no header is painted, and a page whose
|
|
43
|
+
* `headerFooterRefs` name one says so through `unsupported`.
|
|
44
|
+
*/
|
|
45
|
+
readonly headerContent?: HeaderFooterContent;
|
|
46
|
+
/** The footer counterpart of {@link headerContent}. */
|
|
47
|
+
readonly footerContent?: HeaderFooterContent;
|
|
48
|
+
/** Headers by part relationship id, for section-scoped and even/odd headers. */
|
|
49
|
+
readonly headerContentByRId?: ReadonlyMap<string, HeaderFooterContent>;
|
|
50
|
+
/** Footers by part relationship id. */
|
|
51
|
+
readonly footerContentByRId?: ReadonlyMap<string, HeaderFooterContent>;
|
|
52
|
+
/** The first page's header when `titlePg` is set and the layout carries no section refs. */
|
|
53
|
+
readonly firstPageHeaderContent?: HeaderFooterContent;
|
|
54
|
+
/** The first page's footer when `titlePg` is set and the layout carries no section refs. */
|
|
55
|
+
readonly firstPageFooterContent?: HeaderFooterContent;
|
|
56
|
+
/** `w:titlePg` for the first section. Absent: the first page is not special-cased. */
|
|
57
|
+
readonly titlePg?: boolean;
|
|
58
|
+
/** Overrides `w:pgMar`'s `w:header`. Absent: the page's own margin is used. */
|
|
59
|
+
readonly headerDistancePx?: number;
|
|
60
|
+
/** Overrides `w:pgMar`'s `w:footer`. Absent: the page's own margin is used. */
|
|
61
|
+
readonly footerDistancePx?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Footnote bodies by `w:footnote` id. Absent, or missing an id a page
|
|
64
|
+
* carries: the band still opens with its rule and reserves the paginator's
|
|
65
|
+
* height, and the missing bodies are reported.
|
|
66
|
+
*/
|
|
67
|
+
readonly footnoteContentById?: ReadonlyMap<number, FootnoteContent>;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Furniture split by where it sits in the page's paint order: `behind` goes
|
|
71
|
+
* under the body, `above` over it. `renderPage.ts` establishes both.
|
|
72
|
+
*/
|
|
73
|
+
type PageFurniturePrimitives = {
|
|
74
|
+
/** Painted under the body: a back page border, the watermark. */
|
|
75
|
+
readonly behind: readonly DisplayPrimitive[];
|
|
76
|
+
/** Painted over it, into the composer, so its regions nest. */
|
|
77
|
+
readonly above: (composer: PageComposer) => void;
|
|
78
|
+
};
|
|
79
|
+
type PaintPageFurnitureOptions = {
|
|
80
|
+
readonly page: Page;
|
|
81
|
+
readonly furniture: PageFurnitureInputs;
|
|
82
|
+
readonly context: BuildContext;
|
|
83
|
+
};
|
|
84
|
+
declare const paintPageFurniture: ({ page, furniture, context }: PaintPageFurnitureOptions) => PageFurniturePrimitives;
|
|
85
|
+
//#endregion
|
|
86
|
+
export { PageFurnitureInputs, PageFurniturePrimitives, PaintPageFurnitureOptions, paintPageFurniture };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { applySectionHeaderFooterOptions, selectSectionHeaderFooterRIds } from "../../layout-painter/renderPage.js";
|
|
2
|
+
import { paintHeaderFooter } from "./headerFooterPrimitives.js";
|
|
3
|
+
import { paintPageBorders } from "./pageBorderPrimitives.js";
|
|
4
|
+
import { paintFootnoteArea } from "./pageFurniture.js";
|
|
5
|
+
import { UNSUPPORTED_CONSTRUCT } from "./unsupported.js";
|
|
6
|
+
import { paintWatermark } from "./watermarkPrimitives.js";
|
|
7
|
+
//#region src/display-list/build/furniture.ts
|
|
8
|
+
const suppliesHeaderFooter = (furniture) => furniture.headerContent !== void 0 || furniture.footerContent !== void 0 || furniture.headerContentByRId !== void 0 || furniture.footerContentByRId !== void 0 || furniture.firstPageHeaderContent !== void 0 || furniture.firstPageFooterContent !== void 0;
|
|
9
|
+
const suppliesWatermark = (furniture) => furniture.watermark !== void 0 || furniture.watermarkByHeaderRId !== void 0;
|
|
10
|
+
/**
|
|
11
|
+
* The header, footer and watermark this page paints, chosen by the painter's
|
|
12
|
+
* own rule (`w:titlePg` first-page swap, `w:evenAndOddHeaders`, section-scoped
|
|
13
|
+
* parts) rather than by a second implementation of it.
|
|
14
|
+
*/
|
|
15
|
+
const selectForPage = (page, furniture) => {
|
|
16
|
+
const source = {
|
|
17
|
+
...furniture.headerContent === void 0 ? {} : { headerContent: furniture.headerContent },
|
|
18
|
+
...furniture.footerContent === void 0 ? {} : { footerContent: furniture.footerContent },
|
|
19
|
+
...furniture.headerContentByRId === void 0 ? {} : { headerContentByRId: furniture.headerContentByRId },
|
|
20
|
+
...furniture.footerContentByRId === void 0 ? {} : { footerContentByRId: furniture.footerContentByRId },
|
|
21
|
+
...furniture.watermark === void 0 ? {} : { watermark: furniture.watermark },
|
|
22
|
+
...furniture.watermarkByHeaderRId === void 0 ? {} : { watermarkByHeaderRId: furniture.watermarkByHeaderRId }
|
|
23
|
+
};
|
|
24
|
+
const selected = { ...source };
|
|
25
|
+
if (applySectionHeaderFooterOptions(page, selected, source)) return selected;
|
|
26
|
+
if (furniture.titlePg !== true || page.number !== 1) return selected;
|
|
27
|
+
if (furniture.firstPageHeaderContent === void 0) delete selected.headerContent;
|
|
28
|
+
else selected.headerContent = furniture.firstPageHeaderContent;
|
|
29
|
+
if (furniture.firstPageFooterContent === void 0) delete selected.footerContent;
|
|
30
|
+
else selected.footerContent = furniture.firstPageFooterContent;
|
|
31
|
+
return selected;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Name a header or footer the page asks for and the caller did not supply. A
|
|
35
|
+
* backend cannot tell a page with no header from a page whose header never
|
|
36
|
+
* reached the producer.
|
|
37
|
+
*/
|
|
38
|
+
const reportMissingStories = (page, furniture, selected, context) => {
|
|
39
|
+
if (page.headerFooterRefs === void 0) return;
|
|
40
|
+
if (!suppliesHeaderFooter(furniture)) {
|
|
41
|
+
context.unsupported.report(UNSUPPORTED_CONSTRUCT.headerFooterContent, context.pageIndex, "the page names header and footer parts, but no story content was supplied to the builder");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const { headerRId, footerRId } = selectSectionHeaderFooterRIds(page);
|
|
45
|
+
const namesHeader = headerRId !== void 0;
|
|
46
|
+
const namesFooter = footerRId !== void 0;
|
|
47
|
+
const headerCarriedAWatermark = headerRId !== void 0 && furniture.watermarkByHeaderRId?.has(headerRId) === true;
|
|
48
|
+
if (namesHeader && !headerCarriedAWatermark && selected.headerContent === void 0) context.unsupported.report(UNSUPPORTED_CONSTRUCT.headerFooterContent, context.pageIndex, "the header part this page selects was not among the supplied stories");
|
|
49
|
+
if (namesFooter && selected.footerContent === void 0) context.unsupported.report(UNSUPPORTED_CONSTRUCT.headerFooterContent, context.pageIndex, "the footer part this page selects was not among the supplied stories");
|
|
50
|
+
};
|
|
51
|
+
const paintPageFurniture = ({ page, furniture, context }) => {
|
|
52
|
+
const selected = selectForPage(page, furniture);
|
|
53
|
+
reportMissingStories(page, furniture, selected, context);
|
|
54
|
+
const borders = furniture.pageBorders === void 0 ? [] : paintPageBorders({
|
|
55
|
+
page,
|
|
56
|
+
borders: furniture.pageBorders,
|
|
57
|
+
...furniture.theme === void 0 ? {} : { theme: furniture.theme },
|
|
58
|
+
context
|
|
59
|
+
});
|
|
60
|
+
const paintsBorderBehind = furniture.pageBorders?.zOrder === "back";
|
|
61
|
+
const watermark = !suppliesWatermark(furniture) || selected.watermark === void 0 ? [] : paintWatermark({
|
|
62
|
+
page,
|
|
63
|
+
watermark: selected.watermark,
|
|
64
|
+
...furniture.watermarkImageSrc === void 0 ? {} : { imageSrc: furniture.watermarkImageSrc },
|
|
65
|
+
context
|
|
66
|
+
});
|
|
67
|
+
const above = (composer) => {
|
|
68
|
+
paintFootnoteArea({
|
|
69
|
+
composer,
|
|
70
|
+
page,
|
|
71
|
+
context,
|
|
72
|
+
...furniture.footnoteContentById === void 0 ? {} : { contentById: furniture.footnoteContentById }
|
|
73
|
+
});
|
|
74
|
+
if (selected.headerContent !== void 0) paintHeaderFooter({
|
|
75
|
+
composer,
|
|
76
|
+
page,
|
|
77
|
+
section: "header",
|
|
78
|
+
content: selected.headerContent,
|
|
79
|
+
...furniture.headerDistancePx === void 0 ? {} : { distancePx: furniture.headerDistancePx },
|
|
80
|
+
context
|
|
81
|
+
});
|
|
82
|
+
if (selected.footerContent !== void 0) paintHeaderFooter({
|
|
83
|
+
composer,
|
|
84
|
+
page,
|
|
85
|
+
section: "footer",
|
|
86
|
+
content: selected.footerContent,
|
|
87
|
+
...furniture.footerDistancePx === void 0 ? {} : { distancePx: furniture.footerDistancePx },
|
|
88
|
+
context
|
|
89
|
+
});
|
|
90
|
+
if (!paintsBorderBehind) composer.push(borders);
|
|
91
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
behind: [...paintsBorderBehind ? borders : [], ...watermark],
|
|
94
|
+
above
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
//#endregion
|
|
98
|
+
export { paintPageFurniture };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { DisplayRunAdjustments } from "../types.js";
|
|
2
|
+
import { FontStyle } from "../../layout-engine/measure/measureTypes.js";
|
|
3
|
+
//#region src/display-list/build/glyphs.d.ts
|
|
4
|
+
type Glyphs = {
|
|
5
|
+
readonly text: string;
|
|
6
|
+
readonly advancesPx: number[];
|
|
7
|
+
readonly widthPx: number;
|
|
8
|
+
/** Whether these advances were measured with kerning on. */
|
|
9
|
+
readonly kerning: boolean;
|
|
10
|
+
/** Whether they were measured as small capitals. */
|
|
11
|
+
readonly smallCaps: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* What went into the advances beyond the glyphs' own widths. Absent when
|
|
14
|
+
* nothing did, which keeps the common run's primitive as small as it was.
|
|
15
|
+
*/
|
|
16
|
+
readonly adjustments?: DisplayRunAdjustments;
|
|
17
|
+
};
|
|
18
|
+
type BuildGlyphsOptions = {
|
|
19
|
+
readonly text: string;
|
|
20
|
+
readonly style: FontStyle;
|
|
21
|
+
readonly allCaps: boolean;
|
|
22
|
+
/** Justification delta applied to every compressible space. */
|
|
23
|
+
readonly spaceDeltaPx: number;
|
|
24
|
+
/**
|
|
25
|
+
* A line-edge space run Word keeps addressable but paints with no advance
|
|
26
|
+
* (the painter zeroes the split-off span's font size instead).
|
|
27
|
+
*/
|
|
28
|
+
readonly collapsed: boolean;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* `measureRun` returns `charWidths` indexed by UTF-16 unit with the trailing
|
|
32
|
+
* unit of a surrogate pair set to 0, because that is what ProseMirror offsets
|
|
33
|
+
* need. The display list wants code points, so the pair folds into one entry.
|
|
34
|
+
* An uppercase transform that expands a code point (ß → SS) keeps the source
|
|
35
|
+
* advance on the first output code point and gives the rest zero, so the sum
|
|
36
|
+
* still equals what the measurer decided the line on.
|
|
37
|
+
*/
|
|
38
|
+
declare const buildGlyphs: (options: BuildGlyphsOptions) => Glyphs;
|
|
39
|
+
/**
|
|
40
|
+
* Everything a measured run contributes to a `glyphRun`.
|
|
41
|
+
*
|
|
42
|
+
* One helper rather than three fields at each emission site: what went into the
|
|
43
|
+
* advances has to travel with them, and a call site that carried the advances
|
|
44
|
+
* and forgot the adjustments would silently paint a run the line was not fitted
|
|
45
|
+
* with.
|
|
46
|
+
*/
|
|
47
|
+
declare const glyphRunText: (glyphs: Glyphs) => {
|
|
48
|
+
adjustments?: DisplayRunAdjustments;
|
|
49
|
+
text: string;
|
|
50
|
+
advancesPx: number[];
|
|
51
|
+
kerning: boolean;
|
|
52
|
+
smallCaps: boolean;
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
export { BuildGlyphsOptions, Glyphs, buildGlyphs, glyphRunText };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { measureRun, measureTextWidth } from "../../layout-engine/measure/measureProvider.js";
|
|
2
|
+
import { FONT_KERNING_MODE, getFontKerningMode } from "../../layout-engine/measure/textMeasurementPolicy.js";
|
|
3
|
+
import { getHorizontalScaleFactor } from "../../utils/horizontalScale.js";
|
|
4
|
+
//#region src/display-list/build/glyphs.ts
|
|
5
|
+
/**
|
|
6
|
+
* Text → one advance per code point, taken from the measure seam.
|
|
7
|
+
*
|
|
8
|
+
* Shared by everything that paints glyphs (body lines, list markers, tab
|
|
9
|
+
* leaders, watermark text) so no caller invents a width of its own: the numbers
|
|
10
|
+
* here are the numbers line breaking and pagination were decided on.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* `measureRun` returns `charWidths` indexed by UTF-16 unit with the trailing
|
|
14
|
+
* unit of a surrogate pair set to 0, because that is what ProseMirror offsets
|
|
15
|
+
* need. The display list wants code points, so the pair folds into one entry.
|
|
16
|
+
* An uppercase transform that expands a code point (ß → SS) keeps the source
|
|
17
|
+
* advance on the first output code point and gives the rest zero, so the sum
|
|
18
|
+
* still equals what the measurer decided the line on.
|
|
19
|
+
*/
|
|
20
|
+
const buildGlyphs = (options) => {
|
|
21
|
+
const { text, style, allCaps, spaceDeltaPx, collapsed } = options;
|
|
22
|
+
const adjustments = adjustmentsOf(options);
|
|
23
|
+
const { charWidths } = measureRun(text, style);
|
|
24
|
+
const advancesPx = [];
|
|
25
|
+
let painted = "";
|
|
26
|
+
let widthPx = 0;
|
|
27
|
+
let unitOffset = 0;
|
|
28
|
+
const shapedScale = shapedScaleOf(text, style, charWidths);
|
|
29
|
+
for (const char of text) {
|
|
30
|
+
const measured = (charWidths[unitOffset] ?? 0) * shapedScale;
|
|
31
|
+
unitOffset += char.length;
|
|
32
|
+
const advance = collapsed ? 0 : measured + (char === " " ? spaceDeltaPx : 0);
|
|
33
|
+
widthPx += advance;
|
|
34
|
+
let isFirst = true;
|
|
35
|
+
for (const outputChar of allCaps ? char.toLocaleUpperCase() : char) {
|
|
36
|
+
painted += outputChar;
|
|
37
|
+
advancesPx.push(isFirst ? advance : 0);
|
|
38
|
+
isFirst = false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
text: painted,
|
|
43
|
+
advancesPx,
|
|
44
|
+
widthPx,
|
|
45
|
+
kerning: getFontKerningMode(style) === FONT_KERNING_MODE.enabled,
|
|
46
|
+
smallCaps: style.fontVariant === "small-caps",
|
|
47
|
+
...adjustments === void 0 ? {} : { adjustments }
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* What every per-character width has to be multiplied by for the run to occupy
|
|
52
|
+
* the width the line was fitted at.
|
|
53
|
+
*
|
|
54
|
+
* The two numbers come from the same measurer and differ by what shaping does
|
|
55
|
+
* across a boundary: a pair that kerns or ligates is narrower in the string
|
|
56
|
+
* than the two characters are apart. Line breaking used the string's width, so
|
|
57
|
+
* that is the run's width, and advances that summed to anything else would
|
|
58
|
+
* declare an extent no backend paints.
|
|
59
|
+
*
|
|
60
|
+
* Spread over every character rather than dropped on the last, because the
|
|
61
|
+
* difference is not one boundary's: concentrating it would move the run's final
|
|
62
|
+
* glyph by the whole of it.
|
|
63
|
+
*/
|
|
64
|
+
const shapedScaleOf = (text, style, charWidths) => {
|
|
65
|
+
const perCharacterPx = charWidths.reduce((total, width) => total + width, 0);
|
|
66
|
+
if (perCharacterPx <= 0) return 1;
|
|
67
|
+
return measureTextWidth(text, style) / perCharacterPx;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* The three adjustments named, or nothing when none applies.
|
|
71
|
+
*
|
|
72
|
+
* A collapsed run carries no advances at all, so it has nothing to reapply
|
|
73
|
+
* either: naming a spacing on a run painted at zero width would have a backend
|
|
74
|
+
* widen what the line was fitted without.
|
|
75
|
+
*/
|
|
76
|
+
const adjustmentsOf = ({ style, spaceDeltaPx, collapsed }) => {
|
|
77
|
+
const horizontalScale = getHorizontalScaleFactor(style.horizontalScale);
|
|
78
|
+
const letterSpacingPx = collapsed ? 0 : (style.letterSpacing ?? 0) * horizontalScale;
|
|
79
|
+
const wordSpacingPx = collapsed ? 0 : spaceDeltaPx;
|
|
80
|
+
if (letterSpacingPx === 0 && wordSpacingPx === 0 && horizontalScale === 1) return;
|
|
81
|
+
return {
|
|
82
|
+
letterSpacingPx,
|
|
83
|
+
horizontalScale,
|
|
84
|
+
wordSpacingPx
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Everything a measured run contributes to a `glyphRun`.
|
|
89
|
+
*
|
|
90
|
+
* One helper rather than three fields at each emission site: what went into the
|
|
91
|
+
* advances has to travel with them, and a call site that carried the advances
|
|
92
|
+
* and forgot the adjustments would silently paint a run the line was not fitted
|
|
93
|
+
* with.
|
|
94
|
+
*/
|
|
95
|
+
const glyphRunText = (glyphs) => ({
|
|
96
|
+
text: glyphs.text,
|
|
97
|
+
advancesPx: glyphs.advancesPx,
|
|
98
|
+
kerning: glyphs.kerning,
|
|
99
|
+
smallCaps: glyphs.smallCaps,
|
|
100
|
+
...glyphs.adjustments === void 0 ? {} : { adjustments: glyphs.adjustments }
|
|
101
|
+
});
|
|
102
|
+
//#endregion
|
|
103
|
+
export { buildGlyphs, glyphRunText };
|