Skip to content
Astera Technology

Keep one business line together across printed pages

Inspect actual PDF marker pages after applying a whole-row rule.

Download complete case notes and answer keys ↓

Set the decision and its input

The synthetic page has eighty millimetres of usable height, a seventy-two millimetre spacer, and one twelve-millimetre business row with TOP and BOTTOM halves. Only eight millimetres remain. The whole-row requirement moves both halves to page two; ordinary fragmentation splits them across pages one and two.

Filled local test worksheet
CaseInput JSON
Base test{"keepWhole":true}
Download editable CSV ↓

Run the supplied comparison

The browser prints a real in-memory PDF and extraction identifies each marker’s page. The assertion is about this fixed layout and tested engine; it does not infer behaviour for every printer or a row taller than a page.

Faulty and required outputs
PathOutput JSON
Faulty path{"topPage":1,"bottomPage":2,"pageCount":2}
Required path{"topPage":2,"bottomPage":2,"pageCount":2}
Download editable CSV ↓
Node24.14.1 programme; Playwright1.63 Chromium and PDF.js6.4.299 required
import assert from 'node:assert/strict';
import {createRequire} from 'node:module';
import {pathToFileURL} from 'node:url';
const htmlEscape=s=>String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
async function withPage(action,options={}){
  const require=createRequire(import.meta.url);const {chromium}=require('playwright');
  const browser=await chromium.launch({headless:true});
  try{const context=await browser.newContext(options);const page=await context.newPage();
    const requests=[];await page.route('**/*',route=>{requests.push(route.request().url());return route.abort();});
    return await action(page,context,requests);
  }finally{await browser.close();}
}
async function pdfObservation(html){
  const bytes=await withPage(async page=>{await page.setContent(html);return page.pdf({preferCSSPageSize:true,printBackground:true});});
  const require=createRequire(import.meta.url);
  const {getDocument}=await import(pathToFileURL(require.resolve('pdfjs-dist/legacy/build/pdf.mjs')).href);
  const loadingTask=getDocument({data:Uint8Array.from(bytes),useSystemFonts:true,disableFontFace:true});
  try{const document=await loadingTask.promise;const pages=[];for(let n=1;n<=document.numPages;n++){
    const page=await document.getPage(n);const content=await page.getTextContent();
    pages.push(content.items.filter(x=>typeof x.str==='string'&&x.str).map(x=>({text:x.str,x:x.transform[4],y:x.transform[5]})));
  }return {pageCount:document.numPages,pages};}finally{await loadingTask.destroy();}
}
const printStyle='<style>@page{size:100mm 100mm;margin:10mm}body{margin:0;font:10px Arial}p{margin:0}</style>';

const run=async function run(i,corrected=true){
    if(typeof i.keepWhole!=='boolean')return {held:true};
    const style='<style>.space{height:72mm}.row{'+(corrected&&i.keepWhole?'break-inside:avoid;':'')+'}.part{height:6mm;line-height:6mm;orphans:1;widows:1}</style>';
    const out=await pdfObservation(printStyle+style+'<div class="space"></div><div class="row"><p class="part">TOP</p><p class="part">BOTTOM</p></div>');
    return {topPage:out.pages.findIndex(p=>p.some(x=>x.text==='TOP'))+1,bottomPage:out.pages.findIndex(p=>p.some(x=>x.text==='BOTTOM'))+1,pageCount:out.pageCount};
  };
const cases=[{"input":{"keepWhole":true},"corrected":false,"expected":{"topPage":1,"bottomPage":2,"pageCount":2}},{"input":{"keepWhole":true},"corrected":true,"expected":{"topPage":2,"bottomPage":2,"pageCount":2}},{"input":{"keepWhole":false},"corrected":true,"expected":{"topPage":1,"bottomPage":2,"pageCount":2}},{"input":{"keepWhole":null},"corrected":true,"expected":{"held":true}}];
for(const test of cases){const actual=await run(test.input,test.corrected);assert.deepEqual(actual,test.expected);console.log(actual);}
Download runnable example ↓

Choose from the boundary evidence

If splitting is explicitly permitted, TOP on one and BOTTOM on two are acceptable. Unknown row policy is held before printing.

  1. Measure remaining space and row size
  2. Apply the whole-row condition
  3. Inspect the generated page positions

A taller-than-page row needs a separately supplied continuation policy.

Editable boundary inputs and outputs
ConditionInput JSONExpected output JSON
splitting explicitly permitted{"keepWhole":false}{"topPage":1,"bottomPage":2,"pageCount":2}
row requirement not supplied{"keepWhole":null}{"held":true}
Download editable CSV ↓

Where must both markers appear under the base requirement?

Show the worked answer

Page two together. The twelve-millimetre row cannot fit the remaining eight millimetres on page one.

Example files

Related resources