Files
ycode/scripts/rea/patches/CdpElectronPlatformFonts.js
T

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};
}