@stll/folio-core 0.32.1 → 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 +692 -75
- 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 +235 -7
- 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 +42 -9
- package/dist/document-operations.js +171 -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 +4 -4
- 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,620 @@
|
|
|
1
|
+
import { STROKE_DASH_FACTORS, walkRegions } from "../primitives.js";
|
|
2
|
+
import { panic } from "better-result";
|
|
3
|
+
//#region src/display-list/dom/renderDisplayListToDom.ts
|
|
4
|
+
/**
|
|
5
|
+
* The DOM backend for the display list.
|
|
6
|
+
*
|
|
7
|
+
* Every mark is positioned absolutely from the coordinates the producer
|
|
8
|
+
* already decided. Nothing here re-derives a position from inline flow
|
|
9
|
+
* (`text-align`, `text-indent`, floats, line boxes): a second opinion about
|
|
10
|
+
* where a glyph goes is the divergence the display list exists to remove.
|
|
11
|
+
*
|
|
12
|
+
* A glyph run is one element, and the display list places its origin. Where
|
|
13
|
+
* each glyph lands inside the run is the browser's, because shaping is the
|
|
14
|
+
* only thing that forms a ligature, applies a kern, or picks a cursive
|
|
15
|
+
* letter's initial, medial or final form, and none of those can be recovered
|
|
16
|
+
* from advances alone. The painted extent of a run can therefore differ from
|
|
17
|
+
* its declared extent by the shaper's rounding.
|
|
18
|
+
*
|
|
19
|
+
* That residue is measured rather than assumed. It is small when the list was
|
|
20
|
+
* built by the same engine that paints it, which is the editor's own path, and
|
|
21
|
+
* larger when a list built from font-table advances is painted by a shaper
|
|
22
|
+
* that kerns; the harness reports both and gates only on the first.
|
|
23
|
+
*
|
|
24
|
+
* This module takes a type-only edge to the paint IR and a runtime edge to its
|
|
25
|
+
* companions in `../primitives`, which carry no layout fact. It has no access
|
|
26
|
+
* to the layout engine, so a fact it needs and the display list does not carry
|
|
27
|
+
* has to be added to the contract, where the PDF backend sees it too.
|
|
28
|
+
*
|
|
29
|
+
* `DisplayList.unsupported` is not paint and is not consumed here: the caller
|
|
30
|
+
* surfaces it, because a page missing marks must say so.
|
|
31
|
+
*
|
|
32
|
+
* Object URLs minted for images and embedded fonts outlive this call, one per
|
|
33
|
+
* image and one per embedded face for the whole render. A caller that discards
|
|
34
|
+
* the returned elements should revoke them (`img[src^="blob:"]` and the `src`
|
|
35
|
+
* of each `@font-face` rule).
|
|
36
|
+
*/
|
|
37
|
+
const PAGE_CLASS_NAME = "layout-page";
|
|
38
|
+
/**
|
|
39
|
+
* Anchor ids are the display list's own page indices, not `pageNumber`, which
|
|
40
|
+
* the engine may restart or skip. One numbering, so a link target and the id
|
|
41
|
+
* it addresses cannot drift apart.
|
|
42
|
+
*/
|
|
43
|
+
const PAGE_ELEMENT_ID_PREFIX = "page-";
|
|
44
|
+
const DEGREES_PER_RADIAN = 180 / Math.PI;
|
|
45
|
+
const BASE64_CHUNK_SIZE = 32768;
|
|
46
|
+
const IMAGE_MIME_TYPES = {
|
|
47
|
+
png: "image/png",
|
|
48
|
+
jpeg: "image/jpeg"
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* The display list does not say whether an embedded face is sfnt or WOFF, so a
|
|
52
|
+
* data URL declares neither and the browser sniffs; an `@font-face` `src` needs
|
|
53
|
+
* no `format()` hint to load.
|
|
54
|
+
*/
|
|
55
|
+
const FONT_MIME_TYPE = "application/octet-stream";
|
|
56
|
+
/**
|
|
57
|
+
* Which implementation draws a rect's stroke. Every pattern with a period or a
|
|
58
|
+
* second line goes through the `line` renderer, so a dashed cell border and a
|
|
59
|
+
* dashed underline cannot come out as two different dashes. `solid` has no
|
|
60
|
+
* pattern to get wrong, so a CSS border expresses it exactly and cheaply.
|
|
61
|
+
*/
|
|
62
|
+
const RECT_STROKE_PAINTERS = {
|
|
63
|
+
solid: "border",
|
|
64
|
+
dashed: "edges",
|
|
65
|
+
dotted: "edges",
|
|
66
|
+
double: "edges",
|
|
67
|
+
wavy: "edges"
|
|
68
|
+
};
|
|
69
|
+
const px = (value) => `${value}px`;
|
|
70
|
+
/**
|
|
71
|
+
* `rgb()` when opaque so serialized styles stay stable and diffable against
|
|
72
|
+
* the PDF backend's colour dump.
|
|
73
|
+
*/
|
|
74
|
+
const cssColor = ({ r, g, b, a }) => a === 1 ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
75
|
+
const quoteFontFamily = (family) => `"${family.replaceAll("\"", "\\\"")}"`;
|
|
76
|
+
/**
|
|
77
|
+
* An embedded face wins over any installed face of the same name by being
|
|
78
|
+
* named first, under the id its `@font-face` rule was registered with.
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* The stack the measurer resolved, rebuilt in CSS.
|
|
82
|
+
*
|
|
83
|
+
* Every entry, not just the first: the browser picks the first family it has,
|
|
84
|
+
* and so did the measurement. A shorter stack here paints a different face on
|
|
85
|
+
* any host missing the first one.
|
|
86
|
+
*/
|
|
87
|
+
const fontFamilyStack = ({ family, fallbacks, generic, embedded }) => {
|
|
88
|
+
const resolved = `${[family, ...fallbacks].map(quoteFontFamily).join(", ")}, ${generic}`;
|
|
89
|
+
return embedded === void 0 ? resolved : `${quoteFontFamily(embedded.id)}, ${resolved}`;
|
|
90
|
+
};
|
|
91
|
+
const resolveFont = (ref, fonts) => {
|
|
92
|
+
const face = ref < 0 ? void 0 : fonts.at(ref);
|
|
93
|
+
if (face === void 0) panic(`renderDisplayListToDom: font ref ${ref} is out of range (${fonts.length} faces)`);
|
|
94
|
+
return face;
|
|
95
|
+
};
|
|
96
|
+
const resolveImage = (ref, images) => {
|
|
97
|
+
const source = ref < 0 ? void 0 : images.at(ref);
|
|
98
|
+
if (source === void 0) panic(`renderDisplayListToDom: image ref ${ref} is out of range (${images.length} images)`);
|
|
99
|
+
return source;
|
|
100
|
+
};
|
|
101
|
+
const toBase64 = (bytes) => {
|
|
102
|
+
let binary = "";
|
|
103
|
+
for (let offset = 0; offset < bytes.length; offset += BASE64_CHUNK_SIZE) binary += String.fromCharCode(...bytes.subarray(offset, offset + BASE64_CHUNK_SIZE));
|
|
104
|
+
return btoa(binary);
|
|
105
|
+
};
|
|
106
|
+
/** A `Blob` part cannot be backed by shared memory, so prove it is not. */
|
|
107
|
+
const isBlobPart = (bytes) => bytes.buffer instanceof ArrayBuffer;
|
|
108
|
+
const binarySrc = (bytes, mimeType) => {
|
|
109
|
+
if (isBlobPart(bytes) && typeof Blob === "function" && typeof URL !== "undefined" && typeof URL.createObjectURL === "function") return URL.createObjectURL(new Blob([bytes], { type: mimeType }));
|
|
110
|
+
if (typeof btoa === "function") return `data:${mimeType};base64,${toBase64(bytes)}`;
|
|
111
|
+
panic("renderDisplayListToDom: no Blob or btoa to build a binary source from");
|
|
112
|
+
};
|
|
113
|
+
const fontFaceRule = (face, embedded, src) => `@font-face { font-family: ${quoteFontFamily(embedded.id)}; src: url("${src}"); font-weight: ${face.weight}; font-style: ${face.italic ? "italic" : "normal"}; }`;
|
|
114
|
+
/**
|
|
115
|
+
* `@font-face` rules for the faces whose bytes travel with the list. Resolved
|
|
116
|
+
* once per render and shared by every page: an id maps to one blob, so the
|
|
117
|
+
* bytes are held once and one revoke releases them.
|
|
118
|
+
*/
|
|
119
|
+
const embeddedFontFaceCss = (fonts) => {
|
|
120
|
+
const srcById = /* @__PURE__ */ new Map();
|
|
121
|
+
const rules = [];
|
|
122
|
+
for (const face of fonts) {
|
|
123
|
+
const embedded = face.embedded;
|
|
124
|
+
if (embedded === void 0) continue;
|
|
125
|
+
const cached = srcById.get(embedded.id);
|
|
126
|
+
const src = cached ?? binarySrc(embedded.bytes, FONT_MIME_TYPE);
|
|
127
|
+
if (cached === void 0) srcById.set(embedded.id, src);
|
|
128
|
+
rules.push(fontFaceRule(face, embedded, src));
|
|
129
|
+
}
|
|
130
|
+
return rules.join("\n");
|
|
131
|
+
};
|
|
132
|
+
const createAbsoluteDiv = (context, rect) => {
|
|
133
|
+
const element = context.doc.createElement("div");
|
|
134
|
+
element.style.position = "absolute";
|
|
135
|
+
element.style.left = px(rect.xPx - context.originXPx);
|
|
136
|
+
element.style.top = px(rect.yPx - context.originYPx);
|
|
137
|
+
element.style.width = px(rect.widthPx);
|
|
138
|
+
element.style.height = px(rect.heightPx);
|
|
139
|
+
return element;
|
|
140
|
+
};
|
|
141
|
+
const createFilledDiv = (context, rect, fill) => {
|
|
142
|
+
const element = createAbsoluteDiv(context, rect);
|
|
143
|
+
if (fill !== void 0) element.style.backgroundColor = cssColor(fill);
|
|
144
|
+
return element;
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* A group that transforms its children without changing their coordinates: a
|
|
148
|
+
* zero-sized positioned box at the container's origin is the containing block,
|
|
149
|
+
* so a child keeps the coordinates it had one level up.
|
|
150
|
+
*/
|
|
151
|
+
const createTransparentGroupDiv = (context) => {
|
|
152
|
+
const element = context.doc.createElement("div");
|
|
153
|
+
element.style.position = "absolute";
|
|
154
|
+
element.style.left = "0px";
|
|
155
|
+
element.style.top = "0px";
|
|
156
|
+
element.style.width = "0px";
|
|
157
|
+
element.style.height = "0px";
|
|
158
|
+
return element;
|
|
159
|
+
};
|
|
160
|
+
const HORIZONTAL_BAR_AXES = {
|
|
161
|
+
main: "to right",
|
|
162
|
+
cross: "to bottom"
|
|
163
|
+
};
|
|
164
|
+
const VERTICAL_BAR_AXES = {
|
|
165
|
+
main: "to bottom",
|
|
166
|
+
cross: "to right"
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* How far a stroke reaches across its path. A dash pattern stays within the
|
|
170
|
+
* thickness; `double` spans two lines plus the shared gap, and `wavy` the peak
|
|
171
|
+
* to peak amplitude plus the thickness the curve itself is drawn with.
|
|
172
|
+
*/
|
|
173
|
+
const strokeCrossExtentPx = (stroke) => {
|
|
174
|
+
switch (stroke.pattern) {
|
|
175
|
+
case "solid":
|
|
176
|
+
case "dashed":
|
|
177
|
+
case "dotted": return stroke.thicknessPx;
|
|
178
|
+
case "double": return stroke.thicknessPx * 3;
|
|
179
|
+
case "wavy": return stroke.thicknessPx * 3;
|
|
180
|
+
default: {
|
|
181
|
+
const unreachable = stroke.pattern;
|
|
182
|
+
panic(`renderDisplayListToDom: unhandled stroke pattern ${JSON.stringify(unreachable)}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
const repeatingBars = (color, axis, barPx, gapPx) => `repeating-linear-gradient(${axis}, ${color} 0px, ${color} ${barPx}px, transparent ${barPx}px, transparent ${barPx + gapPx}px)`;
|
|
187
|
+
const strokeBackground = (stroke, axes) => {
|
|
188
|
+
const color = cssColor(stroke.color);
|
|
189
|
+
switch (stroke.pattern) {
|
|
190
|
+
case "solid": return color;
|
|
191
|
+
case "dashed":
|
|
192
|
+
case "dotted": {
|
|
193
|
+
const { dash, gap } = STROKE_DASH_FACTORS[stroke.pattern];
|
|
194
|
+
return repeatingBars(color, axes.main, stroke.thicknessPx * dash, stroke.thicknessPx * gap);
|
|
195
|
+
}
|
|
196
|
+
case "double": {
|
|
197
|
+
const rulePercent = 100 / 3;
|
|
198
|
+
return `linear-gradient(${axes.cross}, ${color} 0%, ${color} ${rulePercent}%, transparent ${rulePercent}%, transparent ${100 - rulePercent}%, ${color} ${100 - rulePercent}%)`;
|
|
199
|
+
}
|
|
200
|
+
case "wavy": {
|
|
201
|
+
const halfPeriod = stroke.thicknessPx * 6 / 2;
|
|
202
|
+
return repeatingBars(color, "45deg", halfPeriod, halfPeriod);
|
|
203
|
+
}
|
|
204
|
+
default: {
|
|
205
|
+
const unreachable = stroke.pattern;
|
|
206
|
+
panic(`renderDisplayListToDom: unhandled stroke pattern ${JSON.stringify(unreachable)}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
/** Strokes are centred on their path, so half the cross extent falls each side. */
|
|
211
|
+
const paintLineSegment = (line, context) => {
|
|
212
|
+
const { x1Px, y1Px, x2Px, y2Px, stroke } = line;
|
|
213
|
+
const crossExtentPx = strokeCrossExtentPx(stroke);
|
|
214
|
+
const halfCrossExtentPx = crossExtentPx / 2;
|
|
215
|
+
const element = context.doc.createElement("div");
|
|
216
|
+
element.style.position = "absolute";
|
|
217
|
+
element.style.boxSizing = "content-box";
|
|
218
|
+
if (y1Px === y2Px) {
|
|
219
|
+
element.style.left = px(Math.min(x1Px, x2Px) - context.originXPx);
|
|
220
|
+
element.style.top = px(y1Px - halfCrossExtentPx - context.originYPx);
|
|
221
|
+
element.style.width = px(Math.abs(x2Px - x1Px));
|
|
222
|
+
element.style.height = px(crossExtentPx);
|
|
223
|
+
element.style.background = strokeBackground(stroke, HORIZONTAL_BAR_AXES);
|
|
224
|
+
} else if (x1Px === x2Px) {
|
|
225
|
+
element.style.left = px(x1Px - halfCrossExtentPx - context.originXPx);
|
|
226
|
+
element.style.top = px(Math.min(y1Px, y2Px) - context.originYPx);
|
|
227
|
+
element.style.width = px(crossExtentPx);
|
|
228
|
+
element.style.height = px(Math.abs(y2Px - y1Px));
|
|
229
|
+
element.style.background = strokeBackground(stroke, VERTICAL_BAR_AXES);
|
|
230
|
+
} else {
|
|
231
|
+
const dx = x2Px - x1Px;
|
|
232
|
+
const dy = y2Px - y1Px;
|
|
233
|
+
element.style.left = px(x1Px - context.originXPx);
|
|
234
|
+
element.style.top = px(y1Px - halfCrossExtentPx - context.originYPx);
|
|
235
|
+
element.style.width = px(Math.hypot(dx, dy));
|
|
236
|
+
element.style.height = px(crossExtentPx);
|
|
237
|
+
element.style.transformOrigin = "0 50%";
|
|
238
|
+
element.style.transform = `rotate(${Math.atan2(dy, dx) * DEGREES_PER_RADIAN}deg)`;
|
|
239
|
+
element.style.background = strokeBackground(stroke, HORIZONTAL_BAR_AXES);
|
|
240
|
+
}
|
|
241
|
+
context.parent.append(element);
|
|
242
|
+
};
|
|
243
|
+
/**
|
|
244
|
+
* Reapply what the measurer added to the advances.
|
|
245
|
+
*
|
|
246
|
+
* The browser shapes the run's text and advances it by what the font says, and
|
|
247
|
+
* none of these three is in the font: without them the painted run is as wide
|
|
248
|
+
* as the glyphs alone, which is not the width the line was fitted at. The CSS
|
|
249
|
+
* is the same CSS the legacy painter emits, so the two agree by construction
|
|
250
|
+
* rather than by coincidence.
|
|
251
|
+
*
|
|
252
|
+
* Spacing goes on before the transform scales it, so the painted-pixel numbers
|
|
253
|
+
* the list carries are divided by the scale first.
|
|
254
|
+
*/
|
|
255
|
+
const applyAdjustments = (span, adjustments) => {
|
|
256
|
+
if (adjustments === void 0) return;
|
|
257
|
+
const { letterSpacingPx, horizontalScale, wordSpacingPx } = adjustments;
|
|
258
|
+
if (letterSpacingPx !== 0) span.style.letterSpacing = px(letterSpacingPx / horizontalScale);
|
|
259
|
+
if (wordSpacingPx !== 0) span.style.wordSpacing = px(wordSpacingPx / horizontalScale);
|
|
260
|
+
if (horizontalScale !== 1) {
|
|
261
|
+
span.style.transform = `scaleX(${String(horizontalScale)})`;
|
|
262
|
+
span.style.transformOrigin = "left center";
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Where a run's glyphs came from in the editable model, on the element that
|
|
267
|
+
* paints them.
|
|
268
|
+
*
|
|
269
|
+
* An editing surface maps a click and a selection through these, and the
|
|
270
|
+
* browser's own hit-tester resolves the offset inside the run. The story is
|
|
271
|
+
* written beside the range because a page is not one document: the same number
|
|
272
|
+
* addresses a different character in the body, in each header and footer part,
|
|
273
|
+
* and in each note, so a surface has to know which one it landed in before it
|
|
274
|
+
* can use the number at all.
|
|
275
|
+
*/
|
|
276
|
+
const applyModelRange = (span, range) => {
|
|
277
|
+
if (range === void 0) return;
|
|
278
|
+
span.className = "layout-run-text";
|
|
279
|
+
span.dataset["pmStart"] = String(range.start);
|
|
280
|
+
span.dataset["pmEnd"] = String(range.end);
|
|
281
|
+
const { story } = range;
|
|
282
|
+
span.dataset["story"] = story.kind;
|
|
283
|
+
switch (story.kind) {
|
|
284
|
+
case "body": break;
|
|
285
|
+
case "header":
|
|
286
|
+
case "footer":
|
|
287
|
+
if (story.rId !== null) span.dataset["hfRid"] = story.rId;
|
|
288
|
+
break;
|
|
289
|
+
case "footnote":
|
|
290
|
+
case "endnote":
|
|
291
|
+
span.dataset["noteKind"] = story.kind;
|
|
292
|
+
span.dataset["noteId"] = String(story.id);
|
|
293
|
+
break;
|
|
294
|
+
default: panic(`renderDisplayListToDom: unhandled story ${JSON.stringify(story)}`);
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
const paintGlyphRun = (run, context) => {
|
|
298
|
+
if ([...run.text].length !== run.advancesPx.length) panic(`renderDisplayListToDom: glyph run carries ${run.advancesPx.length} advances for ${[...run.text].length} code points`);
|
|
299
|
+
const face = resolveFont(run.font, context.fonts);
|
|
300
|
+
const advanceSum = run.advancesPx.reduce((total, advance) => total + advance, 0);
|
|
301
|
+
const ascentPx = face.fontBoxAscentRatio * run.fontSizePx;
|
|
302
|
+
const descentPx = face.fontBoxDescentRatio * run.fontSizePx;
|
|
303
|
+
const span = context.doc.createElement("span");
|
|
304
|
+
span.style.position = "absolute";
|
|
305
|
+
span.style.left = px(run.xPx - context.originXPx);
|
|
306
|
+
span.style.top = px(run.baselineYPx - ascentPx - context.originYPx);
|
|
307
|
+
span.style.lineHeight = px(ascentPx + descentPx);
|
|
308
|
+
span.style.display = "inline-block";
|
|
309
|
+
span.style.boxSizing = "content-box";
|
|
310
|
+
span.style.width = px(advanceSum / (run.adjustments?.horizontalScale ?? 1));
|
|
311
|
+
span.style.whiteSpace = "pre";
|
|
312
|
+
span.style.fontFamily = fontFamilyStack(face);
|
|
313
|
+
span.style.fontSize = px(run.fontSizePx);
|
|
314
|
+
span.style.fontWeight = String(face.weight);
|
|
315
|
+
span.style.fontStyle = face.italic ? "italic" : "normal";
|
|
316
|
+
span.style.color = cssColor(run.color);
|
|
317
|
+
span.style.direction = run.direction;
|
|
318
|
+
if (run.stroke !== void 0) span.style.webkitTextStroke = `${px(run.stroke.thicknessPx)} ${cssColor(run.stroke.color)}`;
|
|
319
|
+
span.style.fontKerning = run.kerning ? "normal" : "none";
|
|
320
|
+
if (run.smallCaps) span.style.fontVariant = "small-caps";
|
|
321
|
+
applyAdjustments(span, run.adjustments);
|
|
322
|
+
span.dataset["advanceSum"] = String(advanceSum);
|
|
323
|
+
applyModelRange(span, run.pmRange);
|
|
324
|
+
if (run.collapsedEdge !== void 0) {
|
|
325
|
+
span.dataset[run.collapsedEdge.side === "leading" ? "collapsedLeadingSpaces" : "collapsedTrailingSpaces"] = "true";
|
|
326
|
+
span.dataset["collapsedSpaceAdvance"] = String(run.collapsedEdge.spaceAdvancePx);
|
|
327
|
+
span.style.fontSize = "0";
|
|
328
|
+
span.style.letterSpacing = "0";
|
|
329
|
+
span.style.wordSpacing = "0";
|
|
330
|
+
}
|
|
331
|
+
span.textContent = run.text;
|
|
332
|
+
context.parent.append(span);
|
|
333
|
+
};
|
|
334
|
+
const grownRect = (rect, amountPx) => ({
|
|
335
|
+
xPx: rect.xPx - amountPx / 2,
|
|
336
|
+
yPx: rect.yPx - amountPx / 2,
|
|
337
|
+
widthPx: rect.widthPx + amountPx,
|
|
338
|
+
heightPx: rect.heightPx + amountPx
|
|
339
|
+
});
|
|
340
|
+
const rectEdges = ({ xPx, yPx, widthPx, heightPx }) => {
|
|
341
|
+
const rightPx = xPx + widthPx;
|
|
342
|
+
const bottomPx = yPx + heightPx;
|
|
343
|
+
return [
|
|
344
|
+
{
|
|
345
|
+
x1Px: xPx,
|
|
346
|
+
y1Px: yPx,
|
|
347
|
+
x2Px: rightPx,
|
|
348
|
+
y2Px: yPx
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
x1Px: rightPx,
|
|
352
|
+
y1Px: yPx,
|
|
353
|
+
x2Px: rightPx,
|
|
354
|
+
y2Px: bottomPx
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
x1Px: xPx,
|
|
358
|
+
y1Px: bottomPx,
|
|
359
|
+
x2Px: rightPx,
|
|
360
|
+
y2Px: bottomPx
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
x1Px: xPx,
|
|
364
|
+
y1Px: yPx,
|
|
365
|
+
x2Px: xPx,
|
|
366
|
+
y2Px: bottomPx
|
|
367
|
+
}
|
|
368
|
+
];
|
|
369
|
+
};
|
|
370
|
+
const paintRect = ({ rect, fill, stroke }, context) => {
|
|
371
|
+
if (stroke === void 0) {
|
|
372
|
+
context.parent.append(createFilledDiv(context, rect, fill));
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const painter = RECT_STROKE_PAINTERS[stroke.pattern];
|
|
376
|
+
switch (painter) {
|
|
377
|
+
case "border": {
|
|
378
|
+
const element = createFilledDiv(context, grownRect(rect, stroke.thicknessPx), fill);
|
|
379
|
+
element.style.boxSizing = "border-box";
|
|
380
|
+
element.style.border = `${px(stroke.thicknessPx)} solid ${cssColor(stroke.color)}`;
|
|
381
|
+
context.parent.append(element);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
case "edges":
|
|
385
|
+
context.parent.append(createFilledDiv(context, rect, fill));
|
|
386
|
+
for (const edge of rectEdges(rect)) paintLineSegment({
|
|
387
|
+
kind: "line",
|
|
388
|
+
...edge,
|
|
389
|
+
stroke
|
|
390
|
+
}, context);
|
|
391
|
+
return;
|
|
392
|
+
default: panic(`renderDisplayListToDom: unhandled rect stroke painter ${JSON.stringify(painter)}`);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
const paintImage = ({ image, rect, crop, opacity }, context) => {
|
|
396
|
+
const source = resolveImage(image, context.images);
|
|
397
|
+
const clip = createAbsoluteDiv(context, rect);
|
|
398
|
+
clip.style.overflow = "hidden";
|
|
399
|
+
const element = context.doc.createElement("img");
|
|
400
|
+
element.style.position = "absolute";
|
|
401
|
+
element.style.opacity = String(opacity);
|
|
402
|
+
element.style.left = "0px";
|
|
403
|
+
element.style.top = "0px";
|
|
404
|
+
element.style.width = px(rect.widthPx);
|
|
405
|
+
element.style.height = px(rect.heightPx);
|
|
406
|
+
if (crop !== void 0) {
|
|
407
|
+
const visibleWidth = 1 - crop.l - crop.r;
|
|
408
|
+
const visibleHeight = 1 - crop.t - crop.b;
|
|
409
|
+
if (visibleWidth <= 0 || visibleHeight <= 0) panic(`renderDisplayListToDom: image crop leaves nothing visible (${JSON.stringify(crop)})`);
|
|
410
|
+
const fullWidth = rect.widthPx / visibleWidth;
|
|
411
|
+
const fullHeight = rect.heightPx / visibleHeight;
|
|
412
|
+
element.style.left = px(-crop.l * fullWidth);
|
|
413
|
+
element.style.top = px(-crop.t * fullHeight);
|
|
414
|
+
element.style.width = px(fullWidth);
|
|
415
|
+
element.style.height = px(fullHeight);
|
|
416
|
+
}
|
|
417
|
+
element.alt = "";
|
|
418
|
+
element.src = binarySrc(source.bytes, IMAGE_MIME_TYPES[source.format]);
|
|
419
|
+
clip.append(element);
|
|
420
|
+
context.parent.append(clip);
|
|
421
|
+
};
|
|
422
|
+
const paintClipGroup = ({ rect, children, regions }, context) => {
|
|
423
|
+
const element = createAbsoluteDiv(context, rect);
|
|
424
|
+
element.style.overflow = "hidden";
|
|
425
|
+
context.parent.append(element);
|
|
426
|
+
const inner = {
|
|
427
|
+
...context,
|
|
428
|
+
parent: element,
|
|
429
|
+
originXPx: rect.xPx,
|
|
430
|
+
originYPx: rect.yPx
|
|
431
|
+
};
|
|
432
|
+
paintRegionTree({
|
|
433
|
+
primitives: children,
|
|
434
|
+
regions
|
|
435
|
+
}, inner);
|
|
436
|
+
};
|
|
437
|
+
const paintRotateGroup = ({ degrees, originXPx, originYPx, children }, context) => {
|
|
438
|
+
const element = createTransparentGroupDiv(context);
|
|
439
|
+
element.style.transformOrigin = `${px(originXPx - context.originXPx)} ${px(originYPx - context.originYPx)}`;
|
|
440
|
+
element.style.transform = `rotate(${degrees}deg)`;
|
|
441
|
+
context.parent.append(element);
|
|
442
|
+
const inner = {
|
|
443
|
+
...context,
|
|
444
|
+
parent: element
|
|
445
|
+
};
|
|
446
|
+
for (const child of children) paintPrimitive(child, inner);
|
|
447
|
+
};
|
|
448
|
+
const paintOpacityGroup = ({ opacity, children }, context) => {
|
|
449
|
+
const element = createTransparentGroupDiv(context);
|
|
450
|
+
element.style.opacity = String(opacity);
|
|
451
|
+
context.parent.append(element);
|
|
452
|
+
const inner = {
|
|
453
|
+
...context,
|
|
454
|
+
parent: element
|
|
455
|
+
};
|
|
456
|
+
for (const child of children) paintPrimitive(child, inner);
|
|
457
|
+
};
|
|
458
|
+
const paintPrimitive = (primitive, context) => {
|
|
459
|
+
switch (primitive.kind) {
|
|
460
|
+
case "glyphRun": return paintGlyphRun(primitive, context);
|
|
461
|
+
case "rect": return paintRect(primitive, context);
|
|
462
|
+
case "line": return paintLineSegment(primitive, context);
|
|
463
|
+
case "image": return paintImage(primitive, context);
|
|
464
|
+
case "clipGroup": return paintClipGroup(primitive, context);
|
|
465
|
+
case "rotateGroup": return paintRotateGroup(primitive, context);
|
|
466
|
+
case "opacityGroup": return paintOpacityGroup(primitive, context);
|
|
467
|
+
default: panic(`renderDisplayListToDom: unhandled display primitive ${JSON.stringify(primitive)}`);
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
/**
|
|
471
|
+
* A page target loses its `yPx`: a fragment identifier addresses an element,
|
|
472
|
+
* not an offset within one.
|
|
473
|
+
*/
|
|
474
|
+
const linkHref = (target) => {
|
|
475
|
+
switch (target.kind) {
|
|
476
|
+
case "external": return target.href;
|
|
477
|
+
case "page": return `#${PAGE_ELEMENT_ID_PREFIX}${target.pageIndex}`;
|
|
478
|
+
default: panic(`renderDisplayListToDom: unhandled link target ${JSON.stringify(target)}`);
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
const paintLink = ({ rect, target, tooltip }, context) => {
|
|
482
|
+
const element = context.doc.createElement("a");
|
|
483
|
+
element.style.position = "absolute";
|
|
484
|
+
element.style.left = px(rect.xPx - context.originXPx);
|
|
485
|
+
element.style.top = px(rect.yPx - context.originYPx);
|
|
486
|
+
element.style.width = px(rect.widthPx);
|
|
487
|
+
element.style.height = px(rect.heightPx);
|
|
488
|
+
element.href = linkHref(target);
|
|
489
|
+
if (tooltip !== void 0) element.title = tooltip;
|
|
490
|
+
context.parent.append(element);
|
|
491
|
+
};
|
|
492
|
+
const renderPage = (page, options) => {
|
|
493
|
+
const element = options.doc.createElement("div");
|
|
494
|
+
element.className = PAGE_CLASS_NAME;
|
|
495
|
+
element.id = `${PAGE_ELEMENT_ID_PREFIX}${options.pageIndex}`;
|
|
496
|
+
element.style.position = "relative";
|
|
497
|
+
element.style.width = px(page.widthPx);
|
|
498
|
+
element.style.height = px(page.heightPx);
|
|
499
|
+
element.style.overflow = "hidden";
|
|
500
|
+
if (options.pageBackground !== void 0) element.style.backgroundColor = cssColor(options.pageBackground);
|
|
501
|
+
if (options.fontFaceCss.length > 0) {
|
|
502
|
+
const style = options.doc.createElement("style");
|
|
503
|
+
style.textContent = options.fontFaceCss;
|
|
504
|
+
element.append(style);
|
|
505
|
+
}
|
|
506
|
+
const context = {
|
|
507
|
+
doc: options.doc,
|
|
508
|
+
fonts: options.fonts,
|
|
509
|
+
images: options.images,
|
|
510
|
+
parent: element,
|
|
511
|
+
originXPx: 0,
|
|
512
|
+
originYPx: 0
|
|
513
|
+
};
|
|
514
|
+
paintRegionTree(page, context);
|
|
515
|
+
for (const link of page.links) paintLink(link, context);
|
|
516
|
+
return element;
|
|
517
|
+
};
|
|
518
|
+
/**
|
|
519
|
+
* The element a region becomes.
|
|
520
|
+
*
|
|
521
|
+
* The class names are the ones the interaction layer has always read; naming
|
|
522
|
+
* them here rather than in a painter is what lets a reader keep working while
|
|
523
|
+
* the thing that emits them changes underneath.
|
|
524
|
+
*/
|
|
525
|
+
const REGION_CLASS = {
|
|
526
|
+
pageContent: "layout-page-content",
|
|
527
|
+
headerSlot: "layout-page-header",
|
|
528
|
+
footerSlot: "layout-page-footer",
|
|
529
|
+
note: "layout-note",
|
|
530
|
+
paragraph: "layout-paragraph",
|
|
531
|
+
line: "layout-line",
|
|
532
|
+
emptyRun: "layout-empty-run",
|
|
533
|
+
tab: "layout-run-tab",
|
|
534
|
+
image: "layout-run-image",
|
|
535
|
+
table: "layout-table",
|
|
536
|
+
tableRow: "layout-table-row",
|
|
537
|
+
tableCell: "layout-table-cell",
|
|
538
|
+
textBox: "layout-text-box"
|
|
539
|
+
};
|
|
540
|
+
/** Model facts a surface reads off the element, as the painter wrote them. */
|
|
541
|
+
const applyRegionModel = (element, region) => {
|
|
542
|
+
const model = region.model;
|
|
543
|
+
if (model === void 0) return;
|
|
544
|
+
if (model.pmRange !== void 0) {
|
|
545
|
+
element.dataset["pmStart"] = String(model.pmRange.start);
|
|
546
|
+
element.dataset["pmEnd"] = String(model.pmRange.end);
|
|
547
|
+
}
|
|
548
|
+
if (model.blockId !== void 0) element.dataset["blockId"] = model.blockId;
|
|
549
|
+
if (model.commentIds !== void 0 && model.commentIds.length > 0) element.dataset["commentId"] = String(model.commentIds[0]);
|
|
550
|
+
if (model.rowIndex !== void 0) element.dataset["rowIndex"] = String(model.rowIndex);
|
|
551
|
+
if (model.columnIndex !== void 0) element.dataset["columnIndex"] = String(model.columnIndex);
|
|
552
|
+
const story = model.story ?? model.pmRange?.story;
|
|
553
|
+
if (story === void 0) return;
|
|
554
|
+
element.dataset["story"] = story.kind;
|
|
555
|
+
switch (story.kind) {
|
|
556
|
+
case "body": break;
|
|
557
|
+
case "header":
|
|
558
|
+
case "footer":
|
|
559
|
+
if (story.rId === null) break;
|
|
560
|
+
element.dataset["rid"] = story.rId;
|
|
561
|
+
element.dataset["hfRid"] = story.rId;
|
|
562
|
+
element.dataset["hfRId"] = story.rId;
|
|
563
|
+
element.dataset["hfSlotKind"] = story.kind;
|
|
564
|
+
element.dataset["hfKind"] = story.kind;
|
|
565
|
+
break;
|
|
566
|
+
case "footnote":
|
|
567
|
+
case "endnote":
|
|
568
|
+
element.dataset["noteKind"] = story.kind;
|
|
569
|
+
element.dataset["noteId"] = String(story.id);
|
|
570
|
+
break;
|
|
571
|
+
default: panic(`renderDisplayListToDom: unhandled story ${JSON.stringify(story)}`);
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
const paintRegionTree = ({ primitives, regions }, context) => {
|
|
575
|
+
let current = context;
|
|
576
|
+
const stack = [];
|
|
577
|
+
walkRegions(primitives, regions, {
|
|
578
|
+
onPrimitive: (primitive) => {
|
|
579
|
+
paintPrimitive(primitive, current);
|
|
580
|
+
},
|
|
581
|
+
enter: (region) => {
|
|
582
|
+
const element = current.doc.createElement("div");
|
|
583
|
+
element.className = REGION_CLASS[region.kind];
|
|
584
|
+
element.style.position = "absolute";
|
|
585
|
+
element.style.left = px(region.rect.xPx - current.originXPx);
|
|
586
|
+
element.style.top = px(region.rect.yPx - current.originYPx);
|
|
587
|
+
element.style.width = px(region.rect.widthPx);
|
|
588
|
+
element.style.height = px(region.rect.heightPx);
|
|
589
|
+
applyRegionModel(element, region);
|
|
590
|
+
current.parent.append(element);
|
|
591
|
+
stack.push(current);
|
|
592
|
+
current = {
|
|
593
|
+
...current,
|
|
594
|
+
parent: element,
|
|
595
|
+
originXPx: region.rect.xPx,
|
|
596
|
+
originYPx: region.rect.yPx
|
|
597
|
+
};
|
|
598
|
+
},
|
|
599
|
+
exit: () => {
|
|
600
|
+
current = stack.pop() ?? context;
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
};
|
|
604
|
+
const renderDisplayPageToDom = (page, options) => renderPage(page, {
|
|
605
|
+
...options,
|
|
606
|
+
fontFaceCss: embeddedFontFaceCss(options.fonts)
|
|
607
|
+
});
|
|
608
|
+
/** One `div.layout-page` per display page, in page order. */
|
|
609
|
+
const renderDisplayListToDom = (list, options) => {
|
|
610
|
+
const fontFaceCss = embeddedFontFaceCss(list.fonts);
|
|
611
|
+
return list.pages.map((page, pageIndex) => renderPage(page, {
|
|
612
|
+
...options,
|
|
613
|
+
fonts: list.fonts,
|
|
614
|
+
images: list.images,
|
|
615
|
+
pageIndex,
|
|
616
|
+
fontFaceCss
|
|
617
|
+
}));
|
|
618
|
+
};
|
|
619
|
+
//#endregion
|
|
620
|
+
export { renderDisplayListToDom, renderDisplayPageToDom };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Page } from "../../layout-engine/types.js";
|
|
2
|
+
import { DisplayList } from "../types.js";
|
|
3
|
+
import { BuildDisplayListOptions } from "../build/buildDisplayList.js";
|
|
4
|
+
//#region src/display-list/editor/displayListPagePainter.d.ts
|
|
5
|
+
type DisplayListPagePainterOptions = BuildDisplayListOptions & {
|
|
6
|
+
/** Document to create elements in. */
|
|
7
|
+
readonly doc: Document;
|
|
8
|
+
};
|
|
9
|
+
type DisplayListPagePainter = {
|
|
10
|
+
/** Matches `RenderPageOptions.paintPage`. */
|
|
11
|
+
readonly paintPage: (request: {
|
|
12
|
+
readonly page: Page;
|
|
13
|
+
}) => HTMLElement | null;
|
|
14
|
+
/**
|
|
15
|
+
* The list as far as the painter has built it, for tests and diagnostics.
|
|
16
|
+
* Pages nobody painted are absent, because nobody built them.
|
|
17
|
+
*/
|
|
18
|
+
readonly list: () => DisplayList;
|
|
19
|
+
};
|
|
20
|
+
declare const createDisplayListPagePainter: (options: DisplayListPagePainterOptions) => DisplayListPagePainter;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { DisplayListPagePainter, DisplayListPagePainterOptions, createDisplayListPagePainter };
|