44 lines
3.1 KiB
TypeScript
44 lines
3.1 KiB
TypeScript
// Use browser fragmentation, rather than estimating breaks in a tall screen document.
|
|
// The renderer's print rules and physical geometry remain authoritative.
|
|
export function previewDocumentHtml(html: string, fallbackHeightPx: number, pageIndex = 0): string {
|
|
const script = `<script data-cv-preview-pagination>(async()=>{
|
|
const started=performance.now();
|
|
const page=document.querySelector('.page');if(!page)return;
|
|
await document.fonts.ready;
|
|
await Promise.all(Array.from(document.images).map(image=>image.decode().catch(()=>{})));
|
|
const width=page.getBoundingClientRect().width;
|
|
const height=parseFloat(getComputedStyle(page).minHeight)||${fallbackHeightPx};
|
|
const margin=parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--cv-page-margin'))*96/25.4;
|
|
const printRules=Array.from(document.styleSheets).flatMap(sheet=>Array.from(sheet.cssRules))
|
|
.filter(rule=>rule instanceof CSSMediaRule&&rule.conditionText==='print')
|
|
.flatMap(rule=>Array.from(rule.cssRules).map(rule=>rule.cssText)).join(' ');
|
|
const style=document.createElement('style');
|
|
// Chromium's print fragmentainer rounds the usable CSS height up to a pixel.
|
|
// A native 1px ruler verifies this for A4, Letter and density-adjusted margins.
|
|
const usableHeight=Math.ceil(height-2*margin);
|
|
style.textContent=printRules+'html,body{overflow:hidden!important;background:transparent!important;}body{padding-top:'+margin+'px;}'+
|
|
'[data-cv-preview-flow]{width:'+width+'px;height:'+usableHeight+'px;column-width:'+width+'px;column-gap:0;column-fill:auto;transform-origin:top left;}';
|
|
document.head.append(style);
|
|
const flow=document.createElement('div');flow.setAttribute('data-cv-preview-flow','');page.before(flow);flow.append(page);
|
|
// Keep the existing print decisions, substituting the screen fragment type.
|
|
const breaks=[];
|
|
page.querySelectorAll('*').forEach(element=>{
|
|
const css=getComputedStyle(element);
|
|
['break-inside','break-before','break-after'].forEach(property=>{
|
|
if(css.getPropertyValue(property)==='avoid-page')breaks.push([element,property]);
|
|
});
|
|
});
|
|
breaks.forEach(([element,property])=>element.style.setProperty(property,'avoid-column'));
|
|
await new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)));
|
|
const pages=Math.max(1,Math.round(flow.scrollWidth/width));
|
|
const overflow=Array.from(page.querySelectorAll('*')).some(element=>
|
|
Array.from(element.getClientRects()).some(rect=>rect.width>width+2));
|
|
flow.style.transform='translateX(-'+(${Math.max(0, pageIndex)}*width)+'px)';
|
|
Object.assign(document.documentElement.dataset,{cvPreviewPages:String(pages),cvPreviewWidth:String(width),
|
|
cvPreviewHeight:String(height),cvPreviewUsableHeight:String(usableHeight),cvPreviewOverflow:String(overflow),
|
|
cvPreviewDurationMs:String(performance.now()-started),cvPreviewReady:'true'});
|
|
window.dispatchEvent(new Event('cv-preview-ready'));
|
|
})();</script>`;
|
|
return html.includes('</body>') ? html.replace('</body>', `${script}</body>`) : `${html}${script}`;
|
|
}
|