fix(cv): align preview pagination with native PDF
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
// 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}`;
|
||||
}
|
||||
Reference in New Issue
Block a user