跳至內容
Astera Technology

打印跨頁時,把同一明細行留在一起

套整行規則後,查 PDF 兩個行內標記究竟在哪頁。

下載完整個案資料及參考答案 ↓

訂判斷及所需輸入

本例可用頁高八十毫米,前段佔七十二,同一明細行十二,內有 TOP、BOTTOM 兩半。餘八毫米不足。整行要求要把兩半移到第二頁,普通拆頁卻分在第一、二頁。

已填本地測試工作表
情況輸入 JSON
原例測試{"keepWhole":true}
下載可編輯 CSV ↓

跑所附比較

瀏覽器真正產生記憶體 PDF,抽字可定位標記在哪頁。核對只限此固定排版及測試引擎,不推所有打印機,也未涵蓋整行比一頁還高的情況。

錯誤及所需輸出
路徑輸出 JSON
錯誤路徑{"topPage":1,"bottomPage":2,"pageCount":2}
所需路徑{"topPage":2,"bottomPage":2,"pageCount":2}
下載可編輯 CSV ↓
Node24.14.1 程序;須 Playwright1.63 Chromium 及 PDF.js6.4.299
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);}
下載可執行例子 ↓

按邊界依據作判斷

明訂可拆,TOP 第一頁、BOTTOM 第二頁可接受;整行規則未知,打印前先停。

  1. 核餘位及行高
  2. 套整行條件
  3. 核新 PDF 的頁位置

整行超過頁高,須另訂延續方式。

可編輯邊界輸入及結果
條件輸入 JSON預期輸出 JSON
明准行可拆開{"keepWhole":false}{"topPage":1,"bottomPage":2,"pageCount":2}
整行要求未訂{"keepWhole":null}{"held":true}
下載可編輯 CSV ↓

原例要求整行,兩標記應在哪裏?

查看參考答案

都在第二頁。十二毫米行放不進第一頁餘下八毫米。

例子檔案

相關資源