Files
codex-bot 2b3b3a1ae4
Verify and deploy Tabularium / verify-and-deploy (push) Successful in 5m47s
Expand document collections with Pretext tutorial and simplify library navigation
2026-10-28 13:42:51 +08:00

90 lines
5.0 KiB
JavaScript

// Tutorial mechanism probe, not the official performance or accuracy gate.
// Run from any directory; see README.md for runtime and Playwright setup.
const fs = require('node:fs');
const path = require('node:path');
const http = require('node:http');
const {stripTypeScriptTypes} = require('node:module');
const assert = require('node:assert/strict');
const {chromium} = require(process.env.PLAYWRIGHT_MODULE || 'playwright');
const src = path.resolve(__dirname, '../../src');
const outputFlag = process.argv.indexOf('--output');
if (outputFlag !== -1 && !process.argv[outputFlag + 1]) throw new Error('--output requires a file path');
const modules = new Map();
function compile(dir) {
for (const entry of fs.readdirSync(dir,{withFileTypes:true})) {
const file = path.join(dir,entry.name);
if(entry.isDirectory()) compile(file);
else if(file.endsWith('.ts') && !file.endsWith('.test.ts')) {
modules.set('/'+path.relative(src,file).replace(/\.ts$/,'.js'),stripTypeScriptTypes(fs.readFileSync(file,'utf8'),{mode:'transform'}));
}
}
}
compile(src);
const server = http.createServer((req,res)=>{
res.setHeader('Content-Type',req.url==='/'?'text/html':'text/javascript');
if(req.url !== '/' && !modules.has(req.url)) { res.statusCode=404; res.end('Missing module'); return; }
res.end(req.url==='/'?'<html lang="en"><body></body></html>':modules.get(req.url));
});
(async()=>{
await new Promise((resolve,reject)=>{server.once('error',reject);server.listen(0,'127.0.0.1',resolve)});
let browser;
try {
browser = await chromium.launch({headless:true,...(process.env.CHROME_EXECUTABLE ? {executablePath:process.env.CHROME_EXECUTABLE} : {})});
const page = await browser.newPage();
await page.goto(`http://127.0.0.1:${server.address().port}/`);
const result = await page.evaluate(async()=>{
const api = await import('/layout.js');
let calls=0,units=0;
const original=OffscreenCanvasRenderingContext2D.prototype.measureText;
OffscreenCanvasRenderingContext2D.prototype.measureText=function(text){calls++;units+=text.length;return original.call(this,text)};
const font='16px "Helvetica Neue", "PingFang SC", "Geeza Pro"';
const cases=[
{name:'latin',text:'The quick brown fox jumps over the lazy dog. Accurate text layout needs cached measurements and careful line breaking. '.repeat(12)},
{name:'cjk',text:'高性能文本布局的关键,是复用字体测量结果,并正确处理中文标点和换行。'.repeat(12)},
{name:'pre-wrap',text:'A long line with\ttabs\n preserved spaces and a discretionary soft\u00adhyphen.\n'.repeat(12),options:{whiteSpace:'pre-wrap'}},
];
const out=[];
for(const c of cases){
api.clearCache();calls=0;units=0;
const p=api.prepare(c.text,font,c.options);
const first={calls,units};calls=0;units=0;
api.prepare(c.text,font,c.options);
const second={calls,units};calls=0;
const widths=[80,121,200,319,320,321,480,800];
const heights=widths.map(w=>api.layout(p,w,24).height);
const layoutCalls=calls;
const el=document.createElement('div');
Object.assign(el.style,{font,lineHeight:'24px',overflowWrap:'break-word',whiteSpace:c.options?.whiteSpace||'normal',position:'absolute'});
el.textContent=c.text;document.body.appendChild(el);
const domHeights=widths.map(w=>{el.style.width=w+'px';return el.getBoundingClientRect().height});
let sink=0;
for(let i=0;i<3000;i++)sink+=api.layout(p,widths[i%8],24).height;
const pretextMs=[],domMs=[];
for(let r=0;r<5;r++){
let t=performance.now();
for(let i=0;i<3000;i++)sink+=api.layout(p,widths[i%8],24).height;
pretextMs.push(performance.now()-t);
t=performance.now();
for(let i=0;i<3000;i++){el.style.width=widths[i%8]+'px';sink+=el.getBoundingClientRect().height;}
domMs.push(performance.now()-t);
}
el.remove();
const withSegments=api.prepareWithSegments(c.text,font,c.options);
const lineApiCounts=widths.map(w=>api.layoutWithLines(withSegments,w,24).lines.length);
const median=a=>a.sort((a,b)=>a-b)[2];
out.push({name:c.name,chars:c.text.length,first,second,layoutCalls,fastPath:p.simpleLineWalkFastPath,widths,heights,domHeights,lineApiCounts,pretextMedianMs:median(pretextMs),domInterleavedMedianMs:median(domMs),sink});
}
return {userAgent:navigator.userAgent,devicePixelRatio,language:document.documentElement.lang,font,iterations:3000,rounds:5,out};
});
for(const row of result.out){
assert.ok(row.first.calls > 0, row.name+': first preparation must measure');
assert.equal(row.second.calls,0,row.name+': repeated preparation must reuse Canvas metrics');
assert.equal(row.layoutCalls,0,row.name+': layout must not measure');
assert.deepEqual(row.heights,row.domHeights,row.name+': heights must match DOM for these fixtures');
assert.deepEqual(row.lineApiCounts,row.heights.map(h=>h/24),row.name+': line APIs must agree');
}
if(outputFlag !== -1) fs.writeFileSync(path.resolve(process.argv[outputFlag + 1]),JSON.stringify(result,null,2)+'\n');
console.log(JSON.stringify(result,null,2));
} finally {if(browser)await browser.close();server.close();}
})().catch(e=>{console.error(e);process.exitCode=1});