@brianmsm/quarto-bento 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +93 -2
- package/bin/qbento-npm.mjs +62 -0
- package/distribution/LICENSE +21 -0
- package/distribution/PACKAGING.md +59 -0
- package/distribution/bin/qbento +5 -0
- package/distribution/bin/qbento.cmd +4 -0
- package/distribution/manifest.json +720 -0
- package/distribution/runtime/LICENSE +21 -0
- package/distribution/runtime/_extensions/qbento/_extension.yml +24 -0
- package/distribution/runtime/_extensions/qbento/capture.lua +21 -0
- package/distribution/runtime/_extensions/qbento/guard.lua +7 -0
- package/distribution/runtime/_extensions/qbento/post.ts +2 -0
- package/distribution/runtime/_extensions/qbento/pre.ts +2 -0
- package/distribution/runtime/_extensions/qbento/producer-activation.js +129 -0
- package/distribution/runtime/src/bridge/browser.js +168 -0
- package/distribution/runtime/src/bridge/isolation.ts +55 -0
- package/distribution/runtime/src/browser/chromium.ts +218 -0
- package/distribution/runtime/src/cli.ts +232 -0
- package/distribution/runtime/src/core/agent.ts +546 -0
- package/distribution/runtime/src/core/annotations.ts +384 -0
- package/distribution/runtime/src/core/capture.ts +203 -0
- package/distribution/runtime/src/core/component-actions.ts +550 -0
- package/distribution/runtime/src/core/component-frames.ts +59 -0
- package/distribution/runtime/src/core/component-sync.ts +290 -0
- package/distribution/runtime/src/core/design-diagnostics.ts +78 -0
- package/distribution/runtime/src/core/design-registry.ts +287 -0
- package/distribution/runtime/src/core/graph-relations.ts +354 -0
- package/distribution/runtime/src/core/graph-scope.ts +63 -0
- package/distribution/runtime/src/core/graph-sync.ts +228 -0
- package/distribution/runtime/src/core/io.ts +27 -0
- package/distribution/runtime/src/core/kpi-appearance.ts +205 -0
- package/distribution/runtime/src/core/layouts.ts +245 -0
- package/distribution/runtime/src/core/project.ts +851 -0
- package/distribution/runtime/src/core/reconcile.ts +384 -0
- package/distribution/runtime/src/core/recovery.ts +1255 -0
- package/distribution/runtime/src/core/slide-actions.ts +158 -0
- package/distribution/runtime/src/core/source-scope.ts +50 -0
- package/distribution/runtime/src/core/sync.ts +2557 -0
- package/distribution/runtime/src/core/widgets.ts +300 -0
- package/distribution/runtime/src/integration/install.ts +98 -0
- package/distribution/runtime/src/integration/invocation.ts +100 -0
- package/distribution/runtime/src/integration/quarto.ts +457 -0
- package/distribution/runtime/src/render/assets.ts +66 -0
- package/distribution/runtime/src/render/capture.lua +8 -0
- package/distribution/runtime/src/render/chart.ts +165 -0
- package/distribution/runtime/src/render/components.ts +199 -0
- package/distribution/runtime/src/render/design.ts +820 -0
- package/distribution/runtime/src/render/diagnostics.ts +36 -0
- package/distribution/runtime/src/render/graphs.ts +747 -0
- package/distribution/runtime/src/render/html.ts +82 -0
- package/distribution/runtime/src/render/images.ts +268 -0
- package/distribution/runtime/src/render/inline.ts +259 -0
- package/distribution/runtime/src/render/kpi.ts +385 -0
- package/distribution/runtime/src/render/lists.ts +183 -0
- package/distribution/runtime/src/render/math.ts +413 -0
- package/distribution/runtime/src/render/measure.ts +185 -0
- package/distribution/runtime/src/render/native-layout.ts +174 -0
- package/distribution/runtime/src/render/producer-capture.ts +319 -0
- package/distribution/runtime/src/render/producer-environment.R +49 -0
- package/distribution/runtime/src/render/producer-environment.ts +96 -0
- package/distribution/runtime/src/render/producer-profile.R +91 -0
- package/distribution/runtime/src/render/producer-site-profile.R +18 -0
- package/distribution/runtime/src/render/project.ts +1806 -0
- package/distribution/runtime/src/render/quarto.ts +127 -0
- package/distribution/runtime/src/render/registry.ts +233 -0
- package/distribution/runtime/src/render/resources.ts +292 -0
- package/distribution/runtime/src/render/shell.ts +110 -0
- package/distribution/runtime/src/render/source-inline.ts +169 -0
- package/distribution/runtime/src/render/table.ts +220 -0
- package/distribution/runtime/src/render/text-overflow.ts +208 -0
- package/distribution/runtime/src/render/types.ts +71 -0
- package/distribution/runtime/src/render/validate.ts +572 -0
- package/distribution/runtime/src/rich/project.ts +117 -0
- package/distribution/runtime/src/rich/table.ts +374 -0
- package/distribution/runtime/src/source/annotations.ts +490 -0
- package/distribution/runtime/src/source/attributes.ts +415 -0
- package/distribution/runtime/src/source/chart.ts +165 -0
- package/distribution/runtime/src/source/code-language.ts +277 -0
- package/distribution/runtime/src/source/component-insertion.ts +160 -0
- package/distribution/runtime/src/source/deletion.ts +278 -0
- package/distribution/runtime/src/source/graphs.ts +517 -0
- package/distribution/runtime/src/source/identities.ts +256 -0
- package/distribution/runtime/src/source/identity-id.ts +6 -0
- package/distribution/runtime/src/source/image.ts +50 -0
- package/distribution/runtime/src/source/kpi.ts +196 -0
- package/distribution/runtime/src/source/mod.ts +876 -0
- package/distribution/runtime/src/source/notes.ts +308 -0
- package/distribution/runtime/src/source/structure.ts +647 -0
- package/distribution/runtime/src/source/table.ts +290 -0
- package/distribution/runtime/src/source/text-conversion.ts +146 -0
- package/distribution/runtime/src/state/components.ts +281 -0
- package/distribution/runtime/src/state/graphs.ts +200 -0
- package/distribution/runtime/src/state/json.ts +162 -0
- package/distribution/runtime/src/state/merge.ts +1034 -0
- package/distribution/runtime/src/state/mod.ts +5 -0
- package/distribution/runtime/src/state/store.ts +226 -0
- package/distribution/runtime/src/state/types.ts +151 -0
- package/distribution/runtime/src/studio/client.js +892 -0
- package/distribution/runtime/src/studio/components.js +188 -0
- package/distribution/runtime/src/studio/index.html +283 -0
- package/distribution/runtime/src/studio/layouts.js +170 -0
- package/distribution/runtime/src/studio/registry.js +283 -0
- package/distribution/runtime/src/studio/relations.js +169 -0
- package/distribution/runtime/src/studio/server.ts +461 -0
- package/distribution/runtime/src/widgets/deck.ts +39 -0
- package/distribution/runtime/src/widgets/plotly.ts +354 -0
- package/distribution/runtime/src/widgets/presenter.ts +367 -0
- package/distribution/runtime/src/widgets/snapshot.ts +116 -0
- package/distribution/runtime/vendor/bento/FONT_NOTICES.md +49 -0
- package/distribution/runtime/vendor/bento/LICENSE +21 -0
- package/distribution/runtime/vendor/bento/THIRD_PARTY_NOTICES.md +51 -0
- package/distribution/runtime/vendor/bento/font-licenses/fraunces-OFL.txt +93 -0
- package/distribution/runtime/vendor/bento/font-licenses/instrument-sans-OFL.txt +93 -0
- package/distribution/runtime/vendor/bento/pin.json +69 -0
- package/distribution/runtime/vendor/bento/provenance/slides.package-lock.json +1928 -0
- package/distribution/runtime/vendor/bento/provenance/slides.package.json +28 -0
- package/distribution/runtime/vendor/bento/runtime-dependencies.json +244 -0
- package/distribution/runtime/vendor/bento/runtime-licenses.json +4038 -0
- package/distribution/runtime/vendor/bento/shell.html +269 -0
- package/distribution/runtime/vendor/bento/slides.schema.json +2379 -0
- package/package.json +40 -4
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/** Content bounds from the pinned native renderer, never a character estimate. */
|
|
2
|
+
import { loadBentoShell } from "./shell.ts";
|
|
3
|
+
import { withHeadlessChromium } from "../browser/chromium.ts";
|
|
4
|
+
import {
|
|
5
|
+
ISOLATION_BOOTSTRAP,
|
|
6
|
+
OFFLINE_CSP,
|
|
7
|
+
prepareIsolatedDocument,
|
|
8
|
+
} from "../bridge/isolation.ts";
|
|
9
|
+
import { spliceDocument } from "./html.ts";
|
|
10
|
+
import type { BentoDoc, Diagnostic } from "./types.ts";
|
|
11
|
+
|
|
12
|
+
export const TEXT_DIAGNOSTIC_CODES = [
|
|
13
|
+
"TEXT_OVERFLOW",
|
|
14
|
+
"TEXT_MEASUREMENT_UNAVAILABLE",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
/** A warning must not make an otherwise browser-free save/render destructive.
|
|
18
|
+
* All supported text forms use the real runtime (including lists, KaTeX and
|
|
19
|
+
* highlighted code). Rotation and painted effects are outside these DOM bounds.
|
|
20
|
+
* A single local browser measures the complete deck; no network/font download.
|
|
21
|
+
*/
|
|
22
|
+
// Bound reuse to identical document bytes within this process, for concurrent
|
|
23
|
+
// Studio inspection of one saved snapshot. Failures are retried (a browser may
|
|
24
|
+
// become available); successful entries expire after 30s and at most eight stay.
|
|
25
|
+
// A local font installation change may require expiry/restart, never a document
|
|
26
|
+
// edit to invalidate the snapshot.
|
|
27
|
+
const cache = new Map<string, { at: number; result: Promise<Diagnostic[]> }>();
|
|
28
|
+
export async function textOverflowDiagnostics(
|
|
29
|
+
document: BentoDoc,
|
|
30
|
+
): Promise<Diagnostic[]> {
|
|
31
|
+
const bytes = new TextEncoder().encode(JSON.stringify(document));
|
|
32
|
+
const key = Array.from(
|
|
33
|
+
new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)),
|
|
34
|
+
(n) => n.toString(16).padStart(2, "0"),
|
|
35
|
+
).join("");
|
|
36
|
+
const now = Date.now();
|
|
37
|
+
const previous = cache.get(key);
|
|
38
|
+
if (previous && now - previous.at < 30_000) {
|
|
39
|
+
return structuredClone(await previous.result);
|
|
40
|
+
}
|
|
41
|
+
cache.delete(key);
|
|
42
|
+
while (cache.size >= 8) cache.delete(cache.keys().next().value!);
|
|
43
|
+
const result = measureTextOverflow(document);
|
|
44
|
+
cache.set(key, { at: now, result });
|
|
45
|
+
const diagnostics = await result;
|
|
46
|
+
if (diagnostics.some((d) => d.code === "TEXT_MEASUREMENT_UNAVAILABLE")) {
|
|
47
|
+
cache.delete(key);
|
|
48
|
+
}
|
|
49
|
+
return structuredClone(diagnostics);
|
|
50
|
+
}
|
|
51
|
+
async function measureTextOverflow(
|
|
52
|
+
document: BentoDoc,
|
|
53
|
+
): Promise<Diagnostic[]> {
|
|
54
|
+
const targets = document.slides.flatMap((slide) =>
|
|
55
|
+
slide.elements.filter((e) => e.type === "text" || e.type === "code").map(
|
|
56
|
+
(element) => ({ slideId: slide.id, element }),
|
|
57
|
+
)
|
|
58
|
+
);
|
|
59
|
+
if (!targets.length) return [];
|
|
60
|
+
const diagnostics: Diagnostic[] = [];
|
|
61
|
+
const measured = targets.filter(({ slideId, element }) => {
|
|
62
|
+
if (
|
|
63
|
+
Number(element.rotation ?? 0) !== 0 ||
|
|
64
|
+
(element.type === "text" &&
|
|
65
|
+
/\{\{\s*(?:page|pages|title|date|time|author|company|subject|event)(?::[^}]*?)?\s*\}\}/i
|
|
66
|
+
.test(String(element.html)))
|
|
67
|
+
) {
|
|
68
|
+
diagnostics.push({
|
|
69
|
+
code: "TEXT_MEASUREMENT_UNAVAILABLE",
|
|
70
|
+
slideId,
|
|
71
|
+
elementId: element.id,
|
|
72
|
+
message:
|
|
73
|
+
"Native text bounds were not checked for rotated text or document-field substitutions. Inspect the final slide; resize or simplify the text if it does not fit.",
|
|
74
|
+
});
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
});
|
|
79
|
+
if (!measured.length) return diagnostics;
|
|
80
|
+
// Unique probe IDs preserve duplicate per-slide source IDs. Only placement is
|
|
81
|
+
// neutralized; geometry, native content, typography and embedded fonts stay exact.
|
|
82
|
+
const probes = prepareIsolatedDocument({
|
|
83
|
+
...document,
|
|
84
|
+
slides: [{
|
|
85
|
+
id: "qbento-text-metrics",
|
|
86
|
+
background: document.theme.background,
|
|
87
|
+
transition: "fade",
|
|
88
|
+
notes: "",
|
|
89
|
+
elements: measured.map(({ element }, index) => ({
|
|
90
|
+
...element,
|
|
91
|
+
id: `qbento-probe-${index}`,
|
|
92
|
+
x: 0,
|
|
93
|
+
y: 0,
|
|
94
|
+
})),
|
|
95
|
+
}],
|
|
96
|
+
});
|
|
97
|
+
try {
|
|
98
|
+
const shell = await loadBentoShell();
|
|
99
|
+
const html = spliceDocument(
|
|
100
|
+
shell,
|
|
101
|
+
probes,
|
|
102
|
+
ISOLATION_BOOTSTRAP,
|
|
103
|
+
OFFLINE_CSP,
|
|
104
|
+
);
|
|
105
|
+
const bounds = await withHeadlessChromium({}, async (browser) => {
|
|
106
|
+
const tree = await browser.send<{ frameTree: { frame: { id: string } } }>(
|
|
107
|
+
"Page.getFrameTree",
|
|
108
|
+
);
|
|
109
|
+
await browser.send("Page.setDocumentContent", {
|
|
110
|
+
frameId: tree.frameTree.frame.id,
|
|
111
|
+
html,
|
|
112
|
+
});
|
|
113
|
+
const response = await browser.send<
|
|
114
|
+
{
|
|
115
|
+
result: {
|
|
116
|
+
value?: {
|
|
117
|
+
error?: string;
|
|
118
|
+
left?: number;
|
|
119
|
+
top?: number;
|
|
120
|
+
right?: number;
|
|
121
|
+
bottom?: number;
|
|
122
|
+
}[];
|
|
123
|
+
};
|
|
124
|
+
exceptionDetails?: unknown;
|
|
125
|
+
}
|
|
126
|
+
>("Runtime.evaluate", {
|
|
127
|
+
expression: `(async()=>{
|
|
128
|
+
const count=${measured.length};
|
|
129
|
+
for(let i=0;i<240&&!window.bento?.doc;i++)await new Promise(r=>setTimeout(r,25));
|
|
130
|
+
if(!window.bento?.doc)throw new Error('Pinned Bento renderer did not load');
|
|
131
|
+
for(let i=0;i<120&&document.querySelectorAll('.ed-canvas-wrap [data-el-id]').length<count;i++)await new Promise(r=>setTimeout(r,25));
|
|
132
|
+
document.querySelectorAll('.ed-canvas-wrap [data-el-id]').forEach(node=>node.getBoundingClientRect());
|
|
133
|
+
await document.fonts.ready;
|
|
134
|
+
return Array.from({length:count},(_,index)=>{
|
|
135
|
+
const el=window.bento.doc.slides[0].elements[index];
|
|
136
|
+
const root=document.querySelector('.ed-canvas-wrap [data-el-id="qbento-probe-'+index+'"]');
|
|
137
|
+
if(!root)return {error:'Native canvas text probe missing'};
|
|
138
|
+
const content=root.querySelector('.bento-text-inner');
|
|
139
|
+
if(!content)return {error:'Native text content missing'};
|
|
140
|
+
const box=root.getBoundingClientRect(), scale=box.width/el.w;
|
|
141
|
+
if(!(scale>0)||!Number.isFinite(scale))return {error:'Invalid native probe scale'};
|
|
142
|
+
const range=document.createRange();range.selectNodeContents(content);
|
|
143
|
+
const rects=[content.getBoundingClientRect(),...range.getClientRects()].filter(r=>r.width>0||r.height>0);
|
|
144
|
+
const left=Math.min(...rects.map(r=>r.left)),right=Math.max(...rects.map(r=>r.right));
|
|
145
|
+
const top=Math.min(...rects.map(r=>r.top)),bottom=Math.max(...rects.map(r=>r.bottom));
|
|
146
|
+
return {left:(box.left-left)/scale,top:(box.top-top)/scale,right:(right-box.right)/scale,bottom:(bottom-box.bottom)/scale};
|
|
147
|
+
});
|
|
148
|
+
})()`,
|
|
149
|
+
awaitPromise: true,
|
|
150
|
+
returnByValue: true,
|
|
151
|
+
});
|
|
152
|
+
const values = response.result?.value;
|
|
153
|
+
if (
|
|
154
|
+
response.exceptionDetails || !values ||
|
|
155
|
+
values.length !== measured.length || values.some((row) =>
|
|
156
|
+
!row.error && [row.left, row.top, row.right, row.bottom].some((n) =>
|
|
157
|
+
!Number.isFinite(n)
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
) {
|
|
161
|
+
throw new Error("Incomplete native text measurement");
|
|
162
|
+
}
|
|
163
|
+
return values;
|
|
164
|
+
});
|
|
165
|
+
bounds.forEach((bound, index) => {
|
|
166
|
+
// One CSS pixel tolerance absorbs subpixel layout rounding, not extra lines.
|
|
167
|
+
if (bound.error) {
|
|
168
|
+
const { slideId, element } = measured[index];
|
|
169
|
+
diagnostics.push({
|
|
170
|
+
code: "TEXT_MEASUREMENT_UNAVAILABLE",
|
|
171
|
+
slideId,
|
|
172
|
+
elementId: element.id,
|
|
173
|
+
message:
|
|
174
|
+
`Native text bounds could not be checked: ${bound.error}. Inspect this element in the final slide.`,
|
|
175
|
+
});
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (Object.values(bound).some((n) => typeof n === "number" && n > 1)) {
|
|
179
|
+
const { slideId, element } = measured[index];
|
|
180
|
+
diagnostics.push({
|
|
181
|
+
code: "TEXT_OVERFLOW",
|
|
182
|
+
slideId,
|
|
183
|
+
elementId: element.id,
|
|
184
|
+
message:
|
|
185
|
+
`Native ${element.type} content exceeds its saved box (left ${
|
|
186
|
+
Math.max(0, bound.left!).toFixed(1)
|
|
187
|
+
}px, top ${Math.max(0, bound.top!).toFixed(1)}px, right ${
|
|
188
|
+
Math.max(0, bound.right!).toFixed(1)
|
|
189
|
+
}px, bottom ${
|
|
190
|
+
Math.max(0, bound.bottom!).toFixed(1)
|
|
191
|
+
}px). Increase its size or adjust typography/content; saved geometry and QMD are preserved.`,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
} catch (error) {
|
|
196
|
+
for (const { slideId, element } of measured) {
|
|
197
|
+
diagnostics.push({
|
|
198
|
+
code: "TEXT_MEASUREMENT_UNAVAILABLE",
|
|
199
|
+
slideId,
|
|
200
|
+
elementId: element.id,
|
|
201
|
+
message: `Native text bounds could not be checked: ${
|
|
202
|
+
String(error).slice(0, 350)
|
|
203
|
+
}. Configure an installed local Chromium or inspect the final slide. Rendering and saved work remain available.`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return diagnostics;
|
|
208
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export type Json = null | boolean | number | string | Json[] | {
|
|
2
|
+
[key: string]: Json;
|
|
3
|
+
};
|
|
4
|
+
export interface Element {
|
|
5
|
+
id: string;
|
|
6
|
+
type: string;
|
|
7
|
+
[key: string]: Json;
|
|
8
|
+
}
|
|
9
|
+
export interface Slide {
|
|
10
|
+
id: string;
|
|
11
|
+
elements: Element[];
|
|
12
|
+
[key: string]: Json | Element[];
|
|
13
|
+
}
|
|
14
|
+
export interface BentoDoc {
|
|
15
|
+
format: "bento/slides";
|
|
16
|
+
version: number;
|
|
17
|
+
docId: string;
|
|
18
|
+
title: string;
|
|
19
|
+
size: { width: number; height: number };
|
|
20
|
+
theme: {
|
|
21
|
+
background: string;
|
|
22
|
+
color: string;
|
|
23
|
+
accent: string;
|
|
24
|
+
fontFamily: string;
|
|
25
|
+
};
|
|
26
|
+
slides: Slide[];
|
|
27
|
+
}
|
|
28
|
+
export interface Diagnostic {
|
|
29
|
+
code: string;
|
|
30
|
+
message: string;
|
|
31
|
+
elementId?: string;
|
|
32
|
+
slideId?: string;
|
|
33
|
+
/** Original managed design reference; minimal traces identify the directory only. */
|
|
34
|
+
reference?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface Origin {
|
|
37
|
+
origin: "authored" | "computed";
|
|
38
|
+
sourceId?: string;
|
|
39
|
+
producer?: string;
|
|
40
|
+
/** A generated design-only background verified against an original card or KPI div. */
|
|
41
|
+
designOnly?: true;
|
|
42
|
+
/** Exact bounded source graph role/relationship receipt, verified before persistence. */
|
|
43
|
+
graph?: Json;
|
|
44
|
+
/** Stable content receipt from a fresh, source-checked static table capture. */
|
|
45
|
+
richTable?: Json;
|
|
46
|
+
/** Exact authored local image/caption pair and input-asset digest. */
|
|
47
|
+
image?: Json;
|
|
48
|
+
}
|
|
49
|
+
export interface Projection {
|
|
50
|
+
document: BentoDoc;
|
|
51
|
+
origins: Record<string, Record<string, Origin>>;
|
|
52
|
+
diagnostics: Diagnostic[];
|
|
53
|
+
notes?: Record<string, { sourceId: string; codec: number }>;
|
|
54
|
+
kpiAppearanceRescues?: { slideId: string; elementIds: string[] }[];
|
|
55
|
+
design?: Record<
|
|
56
|
+
string,
|
|
57
|
+
Record<string, {
|
|
58
|
+
ownedPaths: string[];
|
|
59
|
+
bindings: Record<string, { sourceId: string; attribute: string }>;
|
|
60
|
+
}>
|
|
61
|
+
>;
|
|
62
|
+
}
|
|
63
|
+
// Pandoc JSON is validated by individual handlers; unknown nodes fail explicitly.
|
|
64
|
+
export interface Node {
|
|
65
|
+
t: string;
|
|
66
|
+
c?: unknown;
|
|
67
|
+
}
|
|
68
|
+
export interface Ast {
|
|
69
|
+
blocks: Node[];
|
|
70
|
+
meta: Record<string, { t: string; c: unknown }>;
|
|
71
|
+
}
|