Repository navigation
Expand file tree
/
Copy pathsample_layout.js
More file actions
72 lines (72 loc) · 4.29 KB
/
Copy pathsample_layout.js
File metadata and controls
72 lines (72 loc) · 4.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/* Measurement only. Inject into a loaded HyperFrames preview, then await
collectDirectorObservations([0.1, 2, 4, ...]). Does not author a composition. */
window.collectDirectorObservations = async function (times) {
if (!window.__player?.renderSeek) throw new Error('HyperFrames renderSeek unavailable; wait for preview readiness');
await document.fonts.ready;
const root = document.querySelector('[data-composition-id]');
if (!root) throw new Error('No composition root');
const duration = Number(root.dataset.duration);
if (!times.length || times.some(t => !Number.isFinite(t) || t < 0 || (duration > 0 && t >= duration)))
throw new Error('Provide nonempty sample times within the composition duration');
const color = value => {
const m = value.match(/^rgba?\(([^)]+)\)$/);
if (!m) return null;
const a = m[1].split(/[, /]+/).map(Number);
return a.length < 4 || a[3] === 1 ? a.slice(0, 3) : null;
};
const luminance = c => c.map(v => v / 255).map(v => v <= .04045 ? v / 12.92 : ((v + .055) / 1.055) ** 2.4)
.reduce((s, v, i) => s + v * [.2126, .7152, .0722][i], 0);
const rect = r => [r.left, r.top, r.right, r.bottom].map(v => Math.round(v * 100) / 100);
function capture(time) {
const frame = root.getBoundingClientRect();
const items = [];
for (const el of root.querySelectorAll('*')) {
if (el.closest('[data-review-ignore]')) continue;
const nodes = [...el.childNodes].filter(n => n.nodeType === 3 && n.textContent.trim());
if (!nodes.length || ['SCRIPT', 'STYLE'].includes(el.tagName)) continue;
let hidden = false;
for (let p = el; p; p = p.parentElement) {
const c = getComputedStyle(p);
if (c.display === 'none' || c.visibility === 'hidden' || Number(c.opacity) < .05) hidden = true;
}
if (hidden) continue;
const css = getComputedStyle(el);
const boxes = nodes.flatMap(n => {
const range = document.createRange(); range.selectNodeContents(n);
return [...range.getClientRects()];
}).filter(r => r.width > 0 && r.height > 0);
if (!boxes.length) continue;
let clipped = boxes.some(r => r.left < frame.left - 1 || r.top < frame.top - 1 || r.right > frame.right + 1 || r.bottom > frame.bottom + 1);
let bg = null, uncertain = false, faded = false;
for (let p = el; p; p = p.parentElement) {
const c = getComputedStyle(p), pr = p.getBoundingClientRect();
if (Number(c.opacity) < .99) faded = true;
if (['hidden', 'clip', 'scroll', 'auto'].includes(c.overflowX) && boxes.some(r => r.left < pr.left - 1 || r.right > pr.right + 1)) clipped = true;
if (['hidden', 'clip', 'scroll', 'auto'].includes(c.overflowY) && boxes.some(r => r.top < pr.top - 1 || r.bottom > pr.bottom + 1)) clipped = true;
if (!bg && c.backgroundImage !== 'none') uncertain = true;
if (!bg) bg = color(c.backgroundColor);
}
// Siblings/footage/pseudo-elements may occlude the inferred background.
const fg = color(css.color);
const contrast = bg && fg && !uncertain && !faded ?
(Math.max(luminance(bg), luminance(fg)) + .05) / (Math.min(luminance(bg), luminance(fg)) + .05) : null;
items.push({id: el.id || el.tagName + ':' + items.length,
text: nodes.map(n => n.textContent.trim()).join(' '), boxes: boxes.map(rect), clipped,
contrast, contrast_basis: 'opaque ancestor estimate; occlusion not modeled',
font: css.fontFamily, caption: !!el.closest('[data-caption], .caption, .captions, [role="doc-subtitle"]')});
}
return {time, items};
}
async function seek(t) {
window.__player.renderSeek(t);
if (window.__hfWaitForSeekCompletion) await window.__hfWaitForSeekCompletion();
return capture(t); // Bounding-box reads force layout; no wall-clock playback.
}
const samples = [];
for (const t of times) samples.push(await seek(t));
const again = await seek(times[0]);
return {sampled_times: times, samples,
repeat_geometry_equal: JSON.stringify(samples[0].items) === JSON.stringify(again.items),
failed_fonts: [...document.fonts].filter(f => f.status === 'error').map(f => f.family),
limitations: 'Top document only; range boxes approximate glyphs. Missing unregistered fonts, subcomposition iframes, masks, image contrast and pixel nondeterminism need official checks/manual review.'};
};