@stll/folio-core 0.32.2 → 0.33.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/__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 +649 -85
- 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 +33 -0
- package/dist/compare/__fixtures__/body-sequence.js +67 -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 +406 -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 +1059 -0
- package/dist/compare/reproducible-package.d.ts +17 -0
- package/dist/compare/reproducible-package.js +30 -0
- package/dist/compare/scenario.d.ts +77 -0
- package/dist/compare/scenario.js +263 -0
- package/dist/compare/types.d.ts +282 -0
- package/dist/compare/types.js +32 -0
- package/dist/compare/verification.d.ts +55 -0
- package/dist/compare/verification.js +146 -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/packageParts.d.ts +35 -0
- package/dist/docx/packageParts.js +120 -0
- package/dist/docx/paragraphParser.js +22 -22
- package/dist/docx/paragraphTraversal.js +1 -1
- package/dist/docx/renderedPageBreakNormalization.js +3 -1
- package/dist/docx/revisionIdNormalization.d.ts +13 -0
- package/dist/docx/revisionIdNormalization.js +103 -0
- package/dist/docx/rezip.js +26 -17
- package/dist/docx/selectiveSave.js +2 -2
- package/dist/docx/serializer/commentSerializer.js +6 -1
- package/dist/docx/serializer/paragraphSerializer.js +33 -4
- package/dist/docx/serializer/runSerializer.js +11 -10
- package/dist/docx/serializer/tableSerializer.js +2 -1
- 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 +91 -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,283 @@
|
|
|
1
|
+
import { HIT_REGION_KINDS } from "../primitives.js";
|
|
2
|
+
import { AuthorColorTable } from "./buildContext.js";
|
|
3
|
+
import { DOC_CANVAS } from "./colors.js";
|
|
4
|
+
import { collectFloatingImages, pageGeometryOf } from "./floatingImages.js";
|
|
5
|
+
import { FontTable } from "./fontTable.js";
|
|
6
|
+
import { paintPageFurniture } from "./furniture.js";
|
|
7
|
+
import { ImageTable, paintImageFragment } from "./imagePrimitives.js";
|
|
8
|
+
import { paintColumnSeparators, paintPageBackground } from "./pageFurniture.js";
|
|
9
|
+
import { paintParagraphFragment } from "./paragraphPrimitives.js";
|
|
10
|
+
import { blockRegion, createPageComposer } from "./regions.js";
|
|
11
|
+
import { paintTableFragment } from "./tablePrimitives.js";
|
|
12
|
+
import { paintTextBoxFragment } from "./textBoxPrimitives.js";
|
|
13
|
+
import { UNSUPPORTED_CONSTRUCT, UnsupportedCollector } from "./unsupported.js";
|
|
14
|
+
//#region src/display-list/build/buildDisplayList.ts
|
|
15
|
+
/** Document-wide gaps are attributed to the first page, the only one certain to exist. */
|
|
16
|
+
const FIRST_PAGE_INDEX = 0;
|
|
17
|
+
/**
|
|
18
|
+
* Every feature a source document can have that a `Layout` does not record and
|
|
19
|
+
* that nothing on a page betrays, with the reason it paints nothing when the
|
|
20
|
+
* caller supplies neither the construct nor a flag denying it. The list is the
|
|
21
|
+
* single source of truth: {@link DocumentFeatures} is derived from it, so a
|
|
22
|
+
* feature added here cannot be left without a flag or without a reason.
|
|
23
|
+
*
|
|
24
|
+
* Footnote bodies and header/footer stories are deliberately absent: a page
|
|
25
|
+
* that carries them says so itself (`footnoteIds`, `headerFooterRefs`), so
|
|
26
|
+
* their gaps are reported per page from the layout rather than from a
|
|
27
|
+
* document-wide flag the caller would have to remember to set.
|
|
28
|
+
*/
|
|
29
|
+
const DOCUMENT_FEATURE_GAPS = [["pageBorders", "w:pgBorders reach the painter through render options, not through Layout, and none were supplied to the builder"], ["watermark", "watermarks reach the painter through render options, not through Layout, and none was supplied to the builder"]];
|
|
30
|
+
const paragraphTextOf = (block) => block.runs.map((run) => run.kind === "text" ? run.text : "").join("").trim();
|
|
31
|
+
/**
|
|
32
|
+
* Resolve everything that needs the whole document before any page is painted:
|
|
33
|
+
* where each bookmark lands (a `#name` hyperlink cannot become a page target
|
|
34
|
+
* until then) and the heading outline.
|
|
35
|
+
*/
|
|
36
|
+
const collectDocumentTargets = (layout, blockLookup) => {
|
|
37
|
+
const bookmarkTargets = /* @__PURE__ */ new Map();
|
|
38
|
+
const outline = [];
|
|
39
|
+
for (const [pageIndex, page] of layout.pages.entries()) for (const fragment of page.fragments) {
|
|
40
|
+
if (fragment.kind !== "paragraph") continue;
|
|
41
|
+
const block = blockLookup.get(String(fragment.blockId))?.block;
|
|
42
|
+
if (block?.kind !== "paragraph") continue;
|
|
43
|
+
for (const name of block.bookmarks ?? []) if (!bookmarkTargets.has(name)) bookmarkTargets.set(name, {
|
|
44
|
+
kind: "page",
|
|
45
|
+
pageIndex,
|
|
46
|
+
yPx: fragment.y
|
|
47
|
+
});
|
|
48
|
+
const level = block.attrs?.outlineLevel;
|
|
49
|
+
if (level !== void 0 && fragment.continuesFromPrev !== true) {
|
|
50
|
+
const title = paragraphTextOf(block);
|
|
51
|
+
if (title.length > 0) outline.push({
|
|
52
|
+
title,
|
|
53
|
+
level,
|
|
54
|
+
pageIndex,
|
|
55
|
+
yPx: fragment.y
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
bookmarkTargets,
|
|
61
|
+
outline
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
const bordersOfNeighbour = (entry) => entry?.block.kind === "paragraph" ? entry.block.attrs?.borders : void 0;
|
|
65
|
+
const paintFragment = ({ fragment, entry, context, composer, prevEntry, nextEntry }) => {
|
|
66
|
+
const { block, measure } = entry;
|
|
67
|
+
const mismatch = () => {
|
|
68
|
+
context.unsupported.report(UNSUPPORTED_CONSTRUCT.measureMismatch, context.pageIndex, `${fragment.kind} fragment ${String(fragment.blockId)} has a ${block.kind} block and a ${measure.kind} measure`);
|
|
69
|
+
};
|
|
70
|
+
switch (fragment.kind) {
|
|
71
|
+
case "paragraph": {
|
|
72
|
+
if (block.kind !== "paragraph" || measure.kind !== "paragraph") {
|
|
73
|
+
mismatch();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const prevBorders = bordersOfNeighbour(prevEntry);
|
|
77
|
+
const nextBorders = bordersOfNeighbour(nextEntry);
|
|
78
|
+
paintParagraphFragment({
|
|
79
|
+
fragment,
|
|
80
|
+
block,
|
|
81
|
+
measure,
|
|
82
|
+
context,
|
|
83
|
+
composer,
|
|
84
|
+
...prevBorders === void 0 ? {} : { prevBorders },
|
|
85
|
+
...nextBorders === void 0 ? {} : { nextBorders }
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
case "table":
|
|
90
|
+
if (block.kind === "table" && measure.kind === "table") {
|
|
91
|
+
paintTableFragment({
|
|
92
|
+
fragment,
|
|
93
|
+
block,
|
|
94
|
+
measure,
|
|
95
|
+
context,
|
|
96
|
+
composer,
|
|
97
|
+
paintTextBox: paintTextBoxFragment
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
mismatch();
|
|
102
|
+
return;
|
|
103
|
+
case "image":
|
|
104
|
+
if (block.kind === "image") {
|
|
105
|
+
composer.push(paintImageFragment(fragment, block, context));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
mismatch();
|
|
109
|
+
return;
|
|
110
|
+
case "textBox":
|
|
111
|
+
if (block.kind === "textBox" && measure.kind === "textBox") {
|
|
112
|
+
paintTextBoxFragment({
|
|
113
|
+
composer,
|
|
114
|
+
fragment,
|
|
115
|
+
block,
|
|
116
|
+
measure,
|
|
117
|
+
context
|
|
118
|
+
});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
mismatch();
|
|
122
|
+
return;
|
|
123
|
+
default: {
|
|
124
|
+
const unreachable = fragment;
|
|
125
|
+
context.unsupported.report(UNSUPPORTED_CONSTRUCT.fragmentKind, context.pageIndex, `fragment kind ${JSON.stringify(unreachable)} has no builder`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const buildPage = ({ page, pageIndex, totalPages, blockLookup, pageBackground, bookmarkTargets, fonts, images, unsupported, authorColors, furniture }) => {
|
|
130
|
+
const links = [];
|
|
131
|
+
const context = {
|
|
132
|
+
fonts,
|
|
133
|
+
images,
|
|
134
|
+
unsupported,
|
|
135
|
+
authorColors,
|
|
136
|
+
links,
|
|
137
|
+
bookmarkTargets,
|
|
138
|
+
story: { kind: "body" },
|
|
139
|
+
pageIndex,
|
|
140
|
+
pageNumber: page.logicalNumber,
|
|
141
|
+
totalPages
|
|
142
|
+
};
|
|
143
|
+
const entries = page.fragments.map((fragment) => blockLookup.get(String(fragment.blockId)));
|
|
144
|
+
const geometry = pageGeometryOf(page);
|
|
145
|
+
const placedFloats = /* @__PURE__ */ new Set();
|
|
146
|
+
const behindFloats = [];
|
|
147
|
+
const frontFloats = [];
|
|
148
|
+
for (const [index, fragment] of page.fragments.entries()) {
|
|
149
|
+
const block = entries[index]?.block;
|
|
150
|
+
if (fragment.kind !== "paragraph" || block?.kind !== "paragraph") continue;
|
|
151
|
+
for (const placement of collectFloatingImages({
|
|
152
|
+
block,
|
|
153
|
+
fragmentYPx: fragment.y,
|
|
154
|
+
geometry,
|
|
155
|
+
context,
|
|
156
|
+
placed: placedFloats
|
|
157
|
+
})) (placement.behindDoc ? behindFloats : frontFloats).push(...placement.primitives);
|
|
158
|
+
}
|
|
159
|
+
const { behind, above } = paintPageFurniture({
|
|
160
|
+
page,
|
|
161
|
+
furniture,
|
|
162
|
+
context
|
|
163
|
+
});
|
|
164
|
+
const composer = createPageComposer();
|
|
165
|
+
composer.push([
|
|
166
|
+
paintPageBackground(page, pageBackground),
|
|
167
|
+
...behind,
|
|
168
|
+
...behindFloats
|
|
169
|
+
]);
|
|
170
|
+
composer.region({
|
|
171
|
+
kind: HIT_REGION_KINDS.pageContent,
|
|
172
|
+
rect: {
|
|
173
|
+
xPx: page.margins.left,
|
|
174
|
+
yPx: page.margins.top,
|
|
175
|
+
widthPx: page.size.w - page.margins.left - page.margins.right,
|
|
176
|
+
heightPx: page.size.h - page.margins.top - page.margins.bottom
|
|
177
|
+
}
|
|
178
|
+
}, () => {
|
|
179
|
+
for (const [index, fragment] of page.fragments.entries()) {
|
|
180
|
+
const entry = entries[index];
|
|
181
|
+
if (!entry) {
|
|
182
|
+
unsupported.report(UNSUPPORTED_CONSTRUCT.missingBlock, pageIndex, `no block lookup entry for ${fragment.kind} fragment ${String(fragment.blockId)}`);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
composer.region(blockRegion({
|
|
186
|
+
fragment,
|
|
187
|
+
kind: FRAGMENT_REGION_KIND[fragment.kind],
|
|
188
|
+
context
|
|
189
|
+
}), () => {
|
|
190
|
+
paintFragment({
|
|
191
|
+
fragment,
|
|
192
|
+
entry,
|
|
193
|
+
context,
|
|
194
|
+
composer,
|
|
195
|
+
prevEntry: entries[index - 1],
|
|
196
|
+
nextEntry: entries[index + 1]
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
composer.push(frontFloats);
|
|
202
|
+
composer.push(paintColumnSeparators(page));
|
|
203
|
+
above(composer);
|
|
204
|
+
return {
|
|
205
|
+
pageNumber: page.number,
|
|
206
|
+
widthPx: page.size.w,
|
|
207
|
+
heightPx: page.size.h,
|
|
208
|
+
orientation: page.orientation ?? (page.size.w > page.size.h ? "landscape" : "portrait"),
|
|
209
|
+
primitives: composer.primitives(),
|
|
210
|
+
regions: composer.regions(),
|
|
211
|
+
links
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
/** Which kind of region a fragment paints into. */
|
|
215
|
+
const FRAGMENT_REGION_KIND = {
|
|
216
|
+
paragraph: HIT_REGION_KINDS.paragraph,
|
|
217
|
+
table: HIT_REGION_KINDS.table,
|
|
218
|
+
image: HIT_REGION_KINDS.image,
|
|
219
|
+
textBox: HIT_REGION_KINDS.textBox
|
|
220
|
+
};
|
|
221
|
+
/** Whether the caller handed over the construct this document-wide gap names. */
|
|
222
|
+
const SUPPLIED_BY = {
|
|
223
|
+
pageBorders: (furniture) => furniture.pageBorders !== void 0,
|
|
224
|
+
watermark: (furniture) => furniture.watermark !== void 0 || furniture.watermarkByHeaderRId !== void 0
|
|
225
|
+
};
|
|
226
|
+
const createDisplayListBuilder = ({ layout, blockLookup, metadata, pageBackground, documentFeatures, embeddedFonts, ...furniture }) => {
|
|
227
|
+
const fonts = new FontTable(embeddedFonts ?? []);
|
|
228
|
+
const images = new ImageTable();
|
|
229
|
+
const unsupported = new UnsupportedCollector();
|
|
230
|
+
const authorColors = new AuthorColorTable();
|
|
231
|
+
const { bookmarkTargets, outline } = collectDocumentTargets(layout, blockLookup);
|
|
232
|
+
if (layout.pages.length > 0) for (const [feature, detail] of DOCUMENT_FEATURE_GAPS) {
|
|
233
|
+
if (SUPPLIED_BY[feature](furniture)) continue;
|
|
234
|
+
if (documentFeatures !== void 0 && !documentFeatures[feature]) continue;
|
|
235
|
+
unsupported.report(UNSUPPORTED_CONSTRUCT[feature], FIRST_PAGE_INDEX, detail);
|
|
236
|
+
}
|
|
237
|
+
const built = /* @__PURE__ */ new Map();
|
|
238
|
+
const pageAt = (pageIndex) => {
|
|
239
|
+
const existing = built.get(pageIndex);
|
|
240
|
+
if (existing !== void 0) return existing;
|
|
241
|
+
const page = layout.pages[pageIndex];
|
|
242
|
+
if (page === void 0) return;
|
|
243
|
+
const display = buildPage({
|
|
244
|
+
page,
|
|
245
|
+
pageIndex,
|
|
246
|
+
totalPages: layout.pages.length,
|
|
247
|
+
blockLookup,
|
|
248
|
+
pageBackground: pageBackground ?? DOC_CANVAS,
|
|
249
|
+
bookmarkTargets,
|
|
250
|
+
fonts,
|
|
251
|
+
images,
|
|
252
|
+
unsupported,
|
|
253
|
+
authorColors,
|
|
254
|
+
furniture
|
|
255
|
+
});
|
|
256
|
+
built.set(pageIndex, display);
|
|
257
|
+
return display;
|
|
258
|
+
};
|
|
259
|
+
return {
|
|
260
|
+
pageCount: layout.pages.length,
|
|
261
|
+
pageAt,
|
|
262
|
+
snapshot: () => ({
|
|
263
|
+
pages: [...built.entries()].sort(([left], [right]) => left - right).map(([, page]) => page),
|
|
264
|
+
fonts: fonts.snapshot(),
|
|
265
|
+
images: images.snapshot(),
|
|
266
|
+
outline,
|
|
267
|
+
metadata: metadata ?? {},
|
|
268
|
+
unsupported: unsupported.snapshot()
|
|
269
|
+
})
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
/**
|
|
273
|
+
* The whole document's display list. Every page is built, so the tables, the
|
|
274
|
+
* outline and the gap report are complete; an editor that paints a window of
|
|
275
|
+
* pages uses {@link createDisplayListBuilder} instead.
|
|
276
|
+
*/
|
|
277
|
+
const buildDisplayList = (options) => {
|
|
278
|
+
const builder = createDisplayListBuilder(options);
|
|
279
|
+
for (let pageIndex = 0; pageIndex < builder.pageCount; pageIndex += 1) builder.pageAt(pageIndex);
|
|
280
|
+
return builder.snapshot();
|
|
281
|
+
};
|
|
282
|
+
//#endregion
|
|
283
|
+
export { buildDisplayList, createDisplayListBuilder };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DisplayColor } from "../types.js";
|
|
2
|
+
//#region src/display-list/build/colors.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* The editor's document custom properties, materialised. A PDF has no theme
|
|
5
|
+
* and no stylesheet: `--doc-canvas` is the paper, `--doc-canvas-text` the ink,
|
|
6
|
+
* `--suggestion-color` the AI-proposal hue (`renderParagraph.ts`'s
|
|
7
|
+
* `SUGGESTION_COLOR_CSS` fallback).
|
|
8
|
+
*/
|
|
9
|
+
declare const DOC_CANVAS: DisplayColor;
|
|
10
|
+
declare const DOC_CANVAS_TEXT: DisplayColor;
|
|
11
|
+
declare const SUGGESTION_COLOR: DisplayColor;
|
|
12
|
+
declare const TRANSPARENT: DisplayColor;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve an authored CSS colour. Returns `undefined` for a value this module
|
|
15
|
+
* cannot turn into concrete channels (`color-mix()`, `oklch()`, an unknown
|
|
16
|
+
* name) so the caller can report it rather than paint a guess.
|
|
17
|
+
*/
|
|
18
|
+
declare const parseDisplayColor: (color: string | undefined) => DisplayColor | undefined;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { DOC_CANVAS, DOC_CANVAS_TEXT, SUGGESTION_COLOR, TRANSPARENT, parseDisplayColor };
|
|
@@ -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 };
|