68 lines
5.4 KiB
JavaScript
68 lines
5.4 KiB
JavaScript
// Read actual fonts used for bounded, visible message/name/code samples. No renderer evaluation.
|
|
export async function captureElectronPlatformFonts(snapshot,frames,send) {
|
|
const categories=['assistant-name','chinese-body','english-body','user-bubble','code'];
|
|
const samples=[],missing=[];
|
|
const document=snapshot.documents?.find(d=>snapshot.strings[d.frameId]===frames[0].frame_id);
|
|
if(!document)return {samples,missing:categories.map(category=>({category,reason:'Authorized main document absent'}))};
|
|
const strings=snapshot.strings,nodes=document.nodes,layout=document.layout;
|
|
const value=index=>strings[index]??'';
|
|
const className=index=>{const a=nodes.attributes[index]??[];for(let i=0;i<a.length;i+=2)if(value(a[i])==='class')return value(a[i+1]);return ''};
|
|
const hasClass=(index,name)=>className(index).split(' ').includes(name);
|
|
const under=(index,test)=>{for(let n=index;n>=0;n=nodes.parentIndex[n])if(test(n))return true;return false};
|
|
const code=index=>under(index,n=>/^(CODE|PRE)$/.test(value(nodes.nodeName[n])));
|
|
const text=index=>{let out='';for(let n=0;n<nodes.nodeName.length;n++)if(value(nodes.nodeName[n])==='#text'&&under(n,i=>i===index))out+=value(nodes.nodeValue[n]);return out};
|
|
const viewport=snapshot.reaViewport;
|
|
const positionOf=new Map(layout.nodeIndex.map((n,p)=>[n,p]));
|
|
const visible=new Set(layout.nodeIndex.filter((_,position)=>{
|
|
if(!viewport)return true;
|
|
const [x,y,w,h]=layout.bounds[position]??[];
|
|
let left=viewport.page_x,top=viewport.page_y,right=left+viewport.width,bottom=top+viewport.height;
|
|
for(let n=nodes.parentIndex[layout.nodeIndex[position]];n>=0;n=nodes.parentIndex[n]){
|
|
const parentPosition=positionOf.get(n);if(parentPosition===undefined)continue;
|
|
const overflowIndex=snapshot.reaLayoutProperties.indexOf('overflow');
|
|
const overflow=value((layout.styles[parentPosition]??[])[overflowIndex]);
|
|
if(!/auto|scroll|hidden|clip/.test(overflow))continue;
|
|
const [px,py,pw,ph]=layout.bounds[parentPosition];
|
|
left=Math.max(left,px);top=Math.max(top,py);right=Math.min(right,px+pw);bottom=Math.min(bottom,py+ph);
|
|
}
|
|
return w>0&&h>0&&x>=left&&y>=top&&x+w<=right&&y+h<=bottom;
|
|
}));
|
|
const candidates=[];
|
|
for(let n=0;n<nodes.nodeName.length;n++)if(value(nodes.nodeName[n])==='#text'){
|
|
const parent=nodes.parentIndex[n];if(parent>=0&&visible.has(parent)&&!candidates.includes(parent))candidates.push(parent);
|
|
}
|
|
await send('DOM.getDocument',{depth:0});
|
|
for(const category of categories){
|
|
let found=false,reason='No short visible sample of this category in the current document';
|
|
const selected=category==='assistant-name'?layout.nodeIndex.filter(n=>visible.has(n)&&hasClass(n,'cr-agent__name')):candidates;
|
|
for(const index of selected){
|
|
if(under(index,n=>/^(INPUT|TEXTAREA|SELECT)$/.test(value(nodes.nodeName[n]))||(nodes.attributes[n]??[]).some((a,i,attrs)=>i%2===0&&value(a)==='contenteditable'&&value(attrs[i+1])!=='false')))continue;
|
|
const sample=text(index);if(!sample.trim()||sample.length>256)continue;
|
|
const body=under(index,n=>hasClass(n,'cr-agent__content'))&&under(index,n=>hasClass(n,'cr-text-block'))&&!under(index,n=>hasClass(n,'cr-code-like-box'));
|
|
// Plain leaf prose avoids treating a composite bold/inline subtree as one default-body font sample.
|
|
const proseLeaf=!nodes.parentIndex.some((parent,n)=>parent===index&&value(nodes.nodeName[n])!=='#text'&&value(nodes.nodeName[n])!=='BR');
|
|
if((category==='chinese-body'||category==='english-body')&&!proseLeaf)continue;
|
|
const weightProperty=snapshot.reaLayoutProperties.indexOf('font-weight');
|
|
if((category==='chinese-body'||category==='english-body')&&weightProperty>=0&&value((layout.styles[positionOf.get(index)]??[])[weightProperty])!=='400')continue;
|
|
if(category==='chinese-body'&&(!body||code(index)||!/[\u3400-\u9fff]/.test(sample)))continue;
|
|
if(category==='english-body'&&(!body||code(index)||/[\u3400-\u9fff]/.test(sample)||!/[A-Za-z]{3}/.test(sample)))continue;
|
|
if(category==='user-bubble'&&!under(index,n=>hasClass(n,'cr-self-bubble')))continue;
|
|
if(category==='code'&&!code(index))continue;
|
|
const backend=nodes.backendNodeId?.[index];if(!backend){reason='Backend node identity absent';continue;}
|
|
try{
|
|
const pushed=await send('DOM.pushNodesByBackendIdsToFrontend',{backendNodeIds:[backend]});const nodeId=pushed.nodeIds?.[0];if(!nodeId)continue;
|
|
const fonts=(await send('CSS.getPlatformFontsForNode',{nodeId})).fonts;
|
|
if(!Array.isArray(fonts)||!fonts.some(f=>f.glyphCount>0)){reason='No rendered glyph fonts reported';continue;}
|
|
const position=layout.nodeIndex.indexOf(index),styleIndexes=layout.styles[position]??[];
|
|
// The order is supplied by the visuals module, not inferred from font names.
|
|
const properties=snapshot.reaLayoutProperties;
|
|
const styles=Object.fromEntries(properties.map((p,i)=>[p,value(styleIndexes[i])]));
|
|
samples.push({category,text:sample,fontFamily:styles['font-family']??'',...(category==='code'?{codeContext:under(index,n=>value(nodes.nodeName[n])==='PRE')?'block':'inline'}:{}),platformFonts:fonts.map(f=>({familyName:f.familyName,postScriptName:f.postScriptName??'',isCustomFont:f.isCustomFont,glyphCount:f.glyphCount})),bounds:layout.bounds[position],styles});
|
|
found=true;break;
|
|
}catch{reason='Platform font query failed for the visible candidate';}
|
|
}
|
|
if(!found)missing.push({category,reason});
|
|
}
|
|
return {samples,missing};
|
|
}
|