Expand document collections with Pretext tutorial and simplify library navigation
Verify and deploy Tabularium / verify-and-deploy (push) Successful in 5m47s
Verify and deploy Tabularium / verify-and-deploy (push) Successful in 5m47s
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"note": "Original exploratory run. Fixed order, repeated fixtures, headless DPR 1; not an official benchmark or accuracy baseline. Date: 2026-10-28 Asia/Shanghai.",
|
||||
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/156.0.0.0 Safari/537.36",
|
||||
"iterations": 3000,
|
||||
"rounds": 5,
|
||||
"out": [
|
||||
{
|
||||
"name": "latin",
|
||||
"chars": 1428,
|
||||
"first": {
|
||||
"calls": 62,
|
||||
"units": 529
|
||||
},
|
||||
"second": {
|
||||
"calls": 0,
|
||||
"units": 0
|
||||
},
|
||||
"layoutCalls": 0,
|
||||
"fastPath": true,
|
||||
"widths": [
|
||||
80,
|
||||
121,
|
||||
200,
|
||||
319,
|
||||
320,
|
||||
321,
|
||||
480,
|
||||
800
|
||||
],
|
||||
"heights": [
|
||||
3744,
|
||||
2592,
|
||||
1440,
|
||||
864,
|
||||
864,
|
||||
864,
|
||||
576,
|
||||
336
|
||||
],
|
||||
"domHeights": [
|
||||
3744,
|
||||
2592,
|
||||
1440,
|
||||
864,
|
||||
864,
|
||||
864,
|
||||
576,
|
||||
336
|
||||
],
|
||||
"pretextMedianMs": 1.7000000476837158,
|
||||
"domInterleavedMedianMs": 86,
|
||||
"sink": 46530000
|
||||
},
|
||||
{
|
||||
"name": "cjk",
|
||||
"chars": 408,
|
||||
"first": {
|
||||
"calls": 53,
|
||||
"units": 67
|
||||
},
|
||||
"second": {
|
||||
"calls": 0,
|
||||
"units": 0
|
||||
},
|
||||
"layoutCalls": 0,
|
||||
"fastPath": true,
|
||||
"widths": [
|
||||
80,
|
||||
121,
|
||||
200,
|
||||
319,
|
||||
320,
|
||||
321,
|
||||
480,
|
||||
800
|
||||
],
|
||||
"heights": [
|
||||
2016,
|
||||
1440,
|
||||
864,
|
||||
528,
|
||||
504,
|
||||
504,
|
||||
336,
|
||||
216
|
||||
],
|
||||
"domHeights": [
|
||||
2016,
|
||||
1440,
|
||||
864,
|
||||
528,
|
||||
504,
|
||||
504,
|
||||
336,
|
||||
216
|
||||
],
|
||||
"pretextMedianMs": 1.5,
|
||||
"domInterleavedMedianMs": 57.80000019073486,
|
||||
"sink": 26433000
|
||||
},
|
||||
{
|
||||
"name": "pre-wrap",
|
||||
"chars": 888,
|
||||
"first": {
|
||||
"calls": 50,
|
||||
"units": 497
|
||||
},
|
||||
"second": {
|
||||
"calls": 0,
|
||||
"units": 0
|
||||
},
|
||||
"layoutCalls": 0,
|
||||
"fastPath": false,
|
||||
"widths": [
|
||||
80,
|
||||
121,
|
||||
200,
|
||||
319,
|
||||
320,
|
||||
321,
|
||||
480,
|
||||
800
|
||||
],
|
||||
"heights": [
|
||||
2592,
|
||||
1728,
|
||||
864,
|
||||
864,
|
||||
864,
|
||||
864,
|
||||
576,
|
||||
576
|
||||
],
|
||||
"domHeights": [
|
||||
2592,
|
||||
1728,
|
||||
864,
|
||||
864,
|
||||
864,
|
||||
864,
|
||||
576,
|
||||
576
|
||||
],
|
||||
"pretextMedianMs": 6.299999952316284,
|
||||
"domInterleavedMedianMs": 93.90000009536743,
|
||||
"sink": 36828000
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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});
|
||||
Reference in New Issue
Block a user