feat: PDF自绘阅读器+抓手(空格按住/H切换/顶部按钮)+缩放适应宽度(pdf.js worker注入bundle零服务端依赖)

This commit is contained in:
Aisland
2026-08-17 22:50:14 +08:00
parent eaaef0945e
commit b86b2361e8
12 changed files with 666 additions and 46 deletions
+2
View File
@@ -29,6 +29,8 @@ const sandbox = { self: null,
localStorage: { getItem(){ return null }, setItem(){}, removeItem(){} },
atob: (s) => Buffer.from(s, 'base64').toString('binary'), btoa: (s) => Buffer.from(s, 'binary').toString('base64'),
CustomEvent: class { constructor(t, o){ this.type = t; this.detail = o && o.detail } },
DOMMatrix: class {}, Path2D: class {}, ImageData: class {}, TextMetrics: class {}, OffscreenCanvas: class { getContext(){ return null } }, structuredClone: (v) => JSON.parse(JSON.stringify(v)),
DOMException: class extends Error { constructor(m, n){ super(m); this.name = n } }, AbortController: class { constructor(){ this.signal = {} } abort(){} }, queueMicrotask: (f) => Promise.resolve().then(f), ImageBitmap: class {}, MessageChannel: class { constructor(){ this.port1 = {}; this.port2 = {} } }, EventTarget: class { addEventListener(){} removeEventListener(){} dispatchEvent(){ return true } },
ResizeObserver: class { observe(){} disconnect(){} },
MutationObserver: class { observe(){} disconnect(){} },
navigator: { userAgent: "bundle-eval/1.0", platform: "Win32" }, location: { protocol: "http:", host: "127.0.0.1:3080" },
+32
View File
@@ -0,0 +1,32 @@
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>
endobj
4 0 obj
<< /Length 74 >>
stream
BT /F1 24 Tf 72 770 Td (Hello Worktable PDF) Tj ET
endstream
endobj
5 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
endobj
xref
0 6
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
0000000241 00000 n
0000000341 00000 n
trailer
<< /Size 6 /Root 1 0 R >>
startxref
411
%%EOF
+52 -1
View File
@@ -272,7 +272,58 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
})()`);
console.log('STEP11:', JSON.stringify(step11));
const errors = events.filter((e) =>
// PDF 阅读器:自绘渲染 + 抓手(H 切换/空格临时/拖拽平移)+ 缩放
const step12 = await evaluate(`(async function(){
var out={};
try{
var st=window.__dshWorktable.splitStore;
st.open({id:'t-pdf',title:'pdf',top:null,main:[{id:'q1',title:'q',min:200,content:null}],chatWidth:{default:320,min:240,max:600}});
await new Promise(function(r){setTimeout(r,400)});
st.openTab('main',0,{kind:'file',path:'E:\\\\AI_Workspace\\\\DeepseekHarness\\\\Projects\\\\dsh-worktable\\\\04_test\\\\fixture.pdf'});
await new Promise(function(r){setTimeout(r,6500)});
out.bar=!!document.querySelector('.dsh-wt_pdfBar');
out.pages=document.querySelectorAll('.dsh-wt_pdfPage canvas').length;
out.iframeFallback=!!document.querySelector('.dsh-wt_paneFrame');
var c=document.querySelector('.dsh-wt_pdfPage canvas');
out.canvasW=c?c.width:0;
var pdfEl=document.querySelector('.dsh-wt_pdf');
if(pdfEl){
// 空格临时抓手
window.dispatchEvent(new KeyboardEvent('keydown',{key:' ',code:'Space',bubbles:true}));
await new Promise(function(r){setTimeout(r,200)});
out.panClassAfterSpace=pdfEl.className.indexOf('dsh-wt_pdfPan')>=0;
window.dispatchEvent(new KeyboardEvent('keyup',{key:' ',code:'Space',bubbles:true}));
await new Promise(function(r){setTimeout(r,200)});
out.panClassAfterSpaceUp=pdfEl.className.indexOf('dsh-wt_pdfPan')>=0;
// H 常开抓手
window.dispatchEvent(new KeyboardEvent('keydown',{key:'h',code:'KeyH',bubbles:true}));
await new Promise(function(r){setTimeout(r,200)});
out.panClassAfterH=pdfEl.className.indexOf('dsh-wt_pdfPan')>=0;
// 放大到内容溢出(可滚动;btns 顺序:抓手/缩小/放大/适应宽度)
var btns=document.querySelectorAll('.dsh-wt_pdfBtn');
for(var z=0;z<6 && btns[2];z++){ btns[2].click(); }
await new Promise(function(r){setTimeout(r,1200)});
out.pct=document.querySelector('.dsh-wt_pdfPct')?document.querySelector('.dsh-wt_pdfPct').textContent:null;
out.scrollable=pdfEl.scrollHeight>pdfEl.clientHeight;
var r0=pdfEl.getBoundingClientRect();
var x=r0.left+r0.width/2, y=r0.top+r0.height/2;
pdfEl.dispatchEvent(new PointerEvent('pointerdown',{clientX:x,clientY:y,bubbles:true,pointerId:1}));
pdfEl.dispatchEvent(new PointerEvent('pointermove',{clientX:x,clientY:y-140,bubbles:true,pointerId:1}));
pdfEl.dispatchEvent(new PointerEvent('pointerup',{clientX:x,clientY:y-140,bubbles:true,pointerId:1}));
await new Promise(function(r){setTimeout(r,300)});
out.scrollTopAfterDrag=pdfEl.scrollTop;
window.dispatchEvent(new KeyboardEvent('keydown',{key:'h',code:'KeyH',bubbles:true}));
await new Promise(function(r){setTimeout(r,200)});
out.panClassAfterH2=pdfEl.className.indexOf('dsh-wt_pdfPan')>=0;
}
st.close();
}catch(err){ out.err=String(err) }
return JSON.stringify(out);
})()`);
console.log('STEP12:', JSON.stringify(step12));
const errors = events.filter((e) => {
}).filter((e) =>
e.method === 'Runtime.exceptionThrown' ||
(e.method === 'Log.entryAdded' && e.params.entry.level === 'error') ||
(e.method === 'Runtime.consoleAPICalled' && e.params.type === 'error'),
+21
View File
@@ -0,0 +1,21 @@
// 生成最小合法 PDF 测试夹具(单页 Hello,带正确 xref 偏移)
const fs = require('fs');
const objects = [
'1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n',
'2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n',
'3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>\nendobj\n',
'4 0 obj\n<< /Length 74 >>\nstream\nBT /F1 24 Tf 72 770 Td (Hello Worktable PDF) Tj ET\nendstream\nendobj\n',
'5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n',
];
let body = '%PDF-1.4\n';
const offsets = [0];
for (const o of objects) { offsets.push(body.length); body += o; }
const xrefPos = body.length;
body += 'xref\n0 ' + (objects.length + 1) + '\n';
body += '0000000000 65535 f \n';
for (let i = 1; i <= objects.length; i++) body += String(offsets[i]).padStart(10, '0') + ' 00000 n \n';
body += 'trailer\n<< /Size ' + (objects.length + 1) + ' /Root 1 0 R >>\n';
body += 'startxref\n' + xrefPos + '\n%%EOF\n';
const out = 'E:/AI_Workspace/DeepseekHarness/Projects/dsh-worktable/04_test/fixture.pdf';
fs.writeFileSync(out, body, 'latin1');
console.log('fixture written', fs.statSync(out).size, 'bytes');