ทำเอกสารยื่นราชการหลายสิบแผ่นจาก HTML ให้กดพิมพ์เองแล้วตรงขอบ
เอกสารยื่นราชการชุดหนึ่งมีห้าสิบกว่าแผ่น เจ้าหน้าที่ตรวจทีละช่อง ถ้าตัวหนังสือริมขอบขาดไปบรรทัดเดียวก็ถือว่าเอกสารไม่สมบูรณ์ ต้องยื่นใหม่ งานนี้เลยไม่ใช่งานจัดหน้าให้สวย แต่เป็นงานวัดขนาดให้ตรง
พอดีผมทำระบบเอกสารสำหรับงานยื่นขออนุญาตกับหน่วยงานราชการชุดหนึ่ง ตัวเอกสารมีห้าสิบกว่าแผ่น แต่ละแผ่นคือหนึ่งหัวข้อตามแบบตรวจประเมิน มีหัวกระดาษของกิจการ มีเลขข้อกำกับ มีช่องลงนามท้ายแผ่น และหลายแผ่นต้องแปะรูปเอกสารแนบเข้าไปด้วย โจทย์คือคนหน้างานเปิดเว็บแล้วกดปุ่มพิมพ์ในเบราว์เซอร์เอง สั่งพิมพ์ทั้งชุดหรือทีละแผ่นก็ได้ แล้วต้องออกมาตรงขอบทุกแผ่นโดยไม่ต้องมานั่งไล่ปรับ
ชื่อกิจการ เลขทะเบียน ที่อยู่ ชื่อผู้ลงนาม และคีย์ทั้งหมดในบทความนี้ผมเปลี่ยนเป็นตัวอย่างหมดแล้ว ส่วนขนาด ตัวเลข CSS และโครงโค้ดคือของจริงจากงานนั้น
เรื่องนี้คนละแกนกับการสั่งเบราว์เซอร์พิมพ์ PDF เอง
ผมเคยเขียนเรื่องออกเอกสาร PDF ด้วย Chromium แบบไม่มีหน้าจอไว้แล้วที่ ออกใบเสนอราคา PDF ภาษาไทยจาก HTML ด้วย Chromium ใครจะทำระบบที่ฝั่งเซิร์ฟเวอร์เป็นคนสร้างไฟล์ ให้ไปอ่านเล่มนั้น ผมไม่เล่าซ้ำในนี้
คุมทุกตัวแปรได้ แต่ต้องมีเบราว์เซอร์บนเครื่อง
- ระบุระยะขอบ ขนาดกระดาษ เลขหน้า ผ่านพารามิเตอร์ได้ตรงๆ
- ผลลัพธ์เหมือนกันทุกเครื่อง เพราะออกจากเบราว์เซอร์ตัวเดียว
- ต้องลงและคุมเวอร์ชันเบราว์เซอร์บนเซิร์ฟเวอร์
- ผู้ใช้แก้อะไรเองไม่ได้ ต้องรอรอบใหม่
ไม่ต้องมีอะไรบนเซิร์ฟเวอร์ แต่คุมยากกว่ามาก
- ระยะขอบมาจากกล่องพิมพ์ของเบราว์เซอร์ ผู้ใช้เปลี่ยนได้
- แก้ข้อความในหน้าแล้วสั่งพิมพ์ต่อได้ทันที
- ต้องออกแบบให้พอดีหน้า ไม่ใช่พึ่งการย่ออัตโนมัติ
- ต้องมีตัวตรวจหน้าล้น ไม่งั้นรู้ตัวตอนกระดาษออกมาแล้ว
แกนของบทความนี้อยู่ฝั่งขวา คือทุกอย่างต้องพอดีตั้งแต่ตอนออกแบบ เพราะไม่มีขั้นตอนย่ออัตโนมัติมาช่วยเก็บกวาดทีหลัง
หนึ่งแผ่นมีพื้นที่ใช้ได้จริงกี่มิลลิเมตร
อันนี้คือตัวเลขที่ผมต้องจำขึ้นใจตลอดทั้งงาน กระดาษ A4 กว้าง 210 มม. ผมตั้งระยะขอบซ้ายขวาไว้ข้างละ 16 มม. พื้นที่พิมพ์จริงเลยเหลือ 178 มม. และผมตั้งกฎกับตัวเองว่ากรอบอะไรก็ตามที่วางในหน้า ห้ามกว้างเกิน 174 มม. เผื่อไว้ 4 มม. สำหรับเส้นขอบกรอบกับความคลาดเคลื่อนของเครื่องพิมพ์
:root { --green: #14532d; --line: #d7dbd8; --sub: #4b5563; }
/* หนึ่ง .page = หนึ่งแผ่น A4 · ล่างเผื่อ 52mm ไว้ให้ช่องลงนามกับท้ายกระดาษ */
.page {
width: 210mm;
min-height: 297mm;
background: #fff;
margin: 16px auto; /* ระยะห่างตอนดูบนจอเท่านั้น ตอนพิมพ์เป็น 0 */
padding: 18mm 16mm 52mm; /* บน | ซ้ายขวา | ล่าง */
box-shadow: 0 3px 18px rgba(0,0,0,.35);
position: relative;
page-break-after: always;
}
.page:last-of-type { page-break-after: auto; }
/* กรอบรูปทุกชนิดห้ามเกิน 174mm — พื้นที่พิมพ์ 178mm หักเส้นขอบและความคลาดเคลื่อน */
.tf-hold { max-width: 174mm; margin: 0 auto; overflow: hidden;
border: 1px solid #e5e7eb; border-radius: 4px; background: #fff;
display: flex; align-items: center; justify-content: center; }
.tf-hold img { width: 100%; height: 100%; object-fit: contain; display: block; }
ส่วนแนวตั้ง 297 มม. หัก 18 มม. บนกับ 52 มม. ล่างแล้วเหลือประมาณ 227 มม. แต่พอเป็นแผ่นที่มีรูปเอกสารเต็มหน้า ผมบีบส่วนหัวเรื่องลง (จะเล่าต่อข้างล่าง) แล้วปล่อยให้รูปสูงได้ถึง 196 มม. ซึ่งคือเพดานที่ผมใช้ทั้งงาน
ตัวเลข 1123 พิกเซลมาจาก 297 มม. คูณ 96 หารด้วย 25.4 ผมใช้เลขนี้เป็นเส้นตัดสินว่าหน้าไหนล้น เพราะบนจอทุกอย่างวัดเป็นพิกเซล แต่กระดาษวัดเป็นมิลลิเมตร งานนี้เลยต้องแปลงไปมาตลอด
หัวและท้ายที่ต้องซ้ำทุกหน้า ห้ามใช้ position fixed
ตอนแรกผมทำโปรแกรมแก้เอกสารแบบพิมพ์แล้วเห็นผลทันที ที่ต้องมีหัวกระดาษกับท้ายกระดาษซ้ำทุกหน้าเหมือนโปรแกรมเวิร์ด วิธีแรกที่ทุกคนคิดถึงคือ position: fixed แล้วปักไว้บนล่าง ซึ่งบนจอมันสวยมาก แต่พอสั่งพิมพ์จริงมันไปทับเนื้อหาหน้าที่สองเป็นต้นไป
ตอนเรนเดอร์เพื่อพิมพ์ เบราว์เซอร์ตัดหน้าจากกระแสเนื้อหาปกติ แต่กล่องที่เป็น fixed ถูกถอดออกจากกระแสนั้นไปแล้ว ผลคือมันวาดซ้อนทับเนื้อหาแทนที่จะกันที่ให้ ของที่ถูกทับคือย่อหน้าบนสุดของทุกหน้าตั้งแต่หน้าสอง ซึ่งบนหน้าจอไม่มีทางเห็น ต้องพิมพ์ออกมาจริงถึงจะรู้
วิธีที่ใช้ได้จริงคือให้ตารางทำงานแทน วางหัวกระดาษไว้ใน thead วางเนื้อหาไว้ใน tbody วางท้ายกระดาษไว้ใน tfoot แล้วบอกเบราว์เซอร์ว่าให้ทำซ้ำเป็นกลุ่มหัวและกลุ่มท้ายของตาราง อันนี้เป็นพฤติกรรมที่มีมาตั้งแต่ยุคแรกของ CSS สำหรับสื่อสิ่งพิมพ์ และเบราว์เซอร์ทำให้ถูกโดยไม่ต้องขอ
/* หัว-ท้ายซ้ำทุกหน้าแบบเวิร์ด: ให้ตารางเป็นคนทำ ไม่ใช่ position:fixed */
thead { display: table-header-group; } /* ซ้ำบนสุดของทุกหน้า */
tfoot { display: table-footer-group; } /* ซ้ำล่างสุดของทุกหน้า */
tr, .no-break { break-inside: avoid; } /* ห้ามตัดกลางแถว */
.sign-block { break-inside: avoid; } /* บล็อกลายเซ็นห้ามหลุดไปคนละหน้ากับเนื้อหา */
/* โลโก้ทำเป็นพื้นหลังของหัวกระดาษ ไม่ฝัง base64 ซ้ำทุกหน้า ไฟล์จะบวมทันที */
.head .logo { width: 34px; height: 30px; flex: none;
background: url('data:image/png;base64,...') center/contain no-repeat; }
เรื่องโลโก้นี่คือของแถมที่เจอตอนทำจริง ถ้าเอาโลโก้ฝังเป็น base64 ใส่แท็กรูปทุกหน้า พอห้าสิบกว่าแผ่นเข้าไปไฟล์บวมขึ้นมหาศาลและเบราว์เซอร์เรนเดอร์ช้าลงชัดเจน ทำเป็นพื้นหลังของ CSS คลาสเดียว ทุกหน้าใช้ตัวเดียวกันหมด
ทั้งเล่มผมใช้ฟังก์ชันต้นแบบหน้าตัวเดียว รับหมายเลขข้อ ชื่อหัวข้อ และเนื้อหาเข้าไป ที่เหลือมันจัดให้เหมือนกันหมด แบบนี้พอต้องแก้หัวกระดาษ แก้ทีเดียวได้ทั้งห้าสิบกว่าแผ่น
<?php
// ต้นแบบหนึ่งแผ่น A4 (หัวกระดาษ + เนื้อหา + ช่องลงนาม + ท้ายกระดาษ)
function doc_page($no, $title, $content, $nosign = false, $opts = []) {
?>
<div class="page doc" spellcheck="false">
<div class="head">
<div class="logo"></div>
<div class="co">
<div class="nm">ห้างหุ้นส่วนจำกัด ตัวอย่าง การผลิต</div>
<div class="ad">999 หมู่ 9 ต.ตัวอย่าง อ.ตัวอย่าง จ.ตัวอย่าง 10000
· <b>ทะเบียนนิติบุคคลเลขที่</b> 0000000000000</div>
</div>
</div>
<div class="hrule"></div>
<div class="meta">
<span>เลขที่เอกสาร <span class="f"> </span></span>
<?php if (empty($opts['nodate'])): ?><span>วันที่ <span class="f"> </span></span><?php endif; ?>
</div>
<?php if ($no !== '' || $title !== ''): ?>
<div class="title">
<?php if ($no !== ''): ?><div class="no">ข้อ <?= $no ?></div><?php endif; ?>
<div class="tt"><?= $title ?></div>
</div>
<div class="subtitle">ประกอบคำขออนุญาต · แบบตรวจประเมินสถานที่ผลิต (ขั้นตอนที่ 2)</div>
<?php endif; ?>
<?= $content ?>
<?php if (!$nosign): ?>
<div class="sign">
<div class="sig">
<div class="ln"> </div>
<div>( ................................................ )</div>
<div class="rl">ผู้มีอำนาจลงนาม / ผู้จัดทำ</div>
</div>
</div>
<?php endif; ?>
<div class="foot"><span>ห้างหุ้นส่วนจำกัด ตัวอย่าง การผลิต</span></div>
</div>
<?php
}
กรอบรูปสี่แบบ แยกตามชนิดของสิ่งที่อยู่ในกรอบ
ตรงนี้คือหัวใจของงาน ผมเคยพยายามใช้กรอบแบบเดียวกับทุกอย่าง ผลคือรูปเอกสารโดนครอปหลุดขอบ ซึ่งในงานยื่นราชการแปลว่าเอกสารไม่สมบูรณ์ ต้องยื่นใหม่ สุดท้ายผมแยกออกเป็นสี่แบบตามชนิดของสิ่งที่จะใส่
- กรอบรูปขั้นตอน สี่กรอบต่อแผ่น กรอบละประมาณ 85 คูณ 64 มม. ใช้กับภาพถ่ายกระบวนการทำงาน รูปพวกนี้ครอปได้ ไม่มีข้อมูลที่ริมขอบ
- กรอบเอกสารแนบ หนึ่งแผ่นหนึ่งรูป ไล่ชื่อไฟล์ต่อท้ายเป็นเลขได้เรื่อยๆ เพิ่มแผ่นเองอัตโนมัติเมื่อมีรูปเพิ่ม ถ้ายังไม่มีรูปก็แสดงเป็นใบปะหน้าให้แนบเอกสารจริงต่อจากแผ่นนั้น
- กรอบขนาดจริง ระบุความกว้างและความสูงเป็นมิลลิเมตรตรงๆ ใช้กับเอกสารที่ต้องพิมพ์เท่าของจริงเท่านั้น เช่นสำเนาบัตรพลาสติกขนาดมาตรฐาน
- กรอบโตสุดตามสัดส่วน ไม่ล็อกขนาด ปล่อยให้รูปโตเท่าที่หน้ารับได้ตามสัดส่วนของรูปจริง ใช้กับเอกสารแผ่นใหญ่ที่ต้องเห็นครบทุกมุม
สองแบบหลังคือของที่ผมเสียเวลาไปเยอะสุด เพราะมันสวนทางกับสัญชาตญาณของคนทำเว็บ ที่ปกติจะอยากให้รูปเต็มกรอบสวยๆ
/** กรอบขนาดจริงเป็นมิลลิเมตร — ใช้เฉพาะเอกสารที่ต้องพิมพ์เท่าของจริง */
function truefig($key, $wmm, $hmm, $fallback = '') {
global $STEPDIR;
$up = is_file($STEPDIR.'/'.$key.'.jpg');
$src = $up ? ('doc.php?stepimg='.$key.'&v='.filemtime($STEPDIR.'/'.$key.'.jpg'))
: ($fallback ? assetsrc($fallback) : '');
// อัตราส่วนกรอบคำนวณจากขนาดจริง เพื่อส่งให้ตัวปรับรูปฝั่งเบราว์เซอร์ใช้เป็นกรอบครอป
$ar = round(((float)$wmm) / max(0.1, (float)$hmm), 4);
$o = '<div class="stepbox truebox" data-key="'.$key.'" data-ar="'.$ar.'" data-reload="1">';
$o .= '<div class="tf-hold" style="width:'.$wmm.'mm;height:'.$hmm.'mm" id="fr_'.$key.'">';
$o .= $src ? '<img src="'.$src.'" alt="สำเนาเอกสาร">' : '<span class="sb-empty">ยังไม่มีรูป</span>';
return $o.'</div></div>';
}
/** กรอบโตสุดเท่าที่หน้ารับได้ ตามสัดส่วนรูปจริง — ไม่ตัดขอบเด็ดขาด */
function docfig($key, $fallback = '', $maxh = '196mm', $ar = '1.3333') {
global $STEPDIR;
$up = is_file($STEPDIR.'/'.$key.'.jpg');
$src = $up ? ('doc.php?stepimg='.$key.'&v='.filemtime($STEPDIR.'/'.$key.'.jpg'))
: ($fallback ? assetsrc($fallback) : '');
$o = '<div class="stepbox docbox" data-key="'.$key.'" data-ar="'.$ar.'" data-reload="1">';
$o .= '<div class="df-hold" style="max-height:'.$maxh.'" id="fr_'.$key.'">';
$o .= $src ? '<img style="max-height:'.$maxh.'" src="'.$src.'" alt="สำเนาเอกสาร">'
: '<span class="sb-empty">กดปุ่มด้านล่างเพื่อแนบรูปเอกสาร</span>';
return $o.'</div></div>';
}
หัวกระดาษ เลขรหัส ช่องข้อมูลซ้ายขวา วันที่ ลายเซ็น ตราประทับ ต้องอยู่ครบทุกมุม เพราะเจ้าหน้าที่ตรวจสำเนาทีละช่อง ถ้าขอบขาดถือว่าเอกสารไม่สมบูรณ์ ตอนต้องเลือกระหว่าง "ใหญ่" กับ "ครบ" ให้เลือกครบก่อนแล้วย่อรูปลง กรอบแสดงรูปเอกสารต้องใช้ object-fit แบบ contain หรือ max-width เสมอ ห้ามใช้ cover
บีบหัวเรื่องอัตโนมัติด้วย has() เพื่อคืนพื้นที่ให้รูป
พอแผ่นไหนมีรูปเอกสารเต็มหน้า พื้นที่ที่เสียไปกับหัวเรื่อง คำบรรยายใต้หัวเรื่อง และช่องลงนาม กลายเป็นตัวบีบรูปให้เล็กลงจนอ่านไม่ออก ผมไม่อยากเขียนคลาสพิเศษไปแปะทีละแผ่น เลยใช้ตัวเลือก :has() ให้ CSS มองเข้าไปข้างในหน้าเองว่ามีกล่องรูปชนิดไหนอยู่ แล้วบีบส่วนหัวลงให้เอง
/* หน้าไหนมีกล่องรูปเอกสาร ให้บีบส่วนหัวลงเองโดยไม่ต้องแปะคลาสพิเศษทีละแผ่น */
.page:has(.attbox), .page:has(.truebox), .page:has(.docbox) { padding-bottom: 26mm; }
.page:has(.attbox) .subtitle, .page:has(.truebox) .subtitle,
.page:has(.docbox) .subtitle { display: none; }
.page:has(.attbox) .title, .page:has(.docbox) .title { margin: 8px 0 2px; }
.page:has(.attbox) .title .tt,.page:has(.docbox) .title .tt{ font-size: 16.5px; }
.page:has(.attbox) .sheetlabel,.page:has(.docbox) .sheetlabel { font-size: 13px; margin: 4px 0 6px; }
.page:has(.attbox) .sign, .page:has(.docbox) .sign { bottom: 10mm; }
.page:has(.attbox) .sig .rl, .page:has(.docbox) .sig .rl { display: none; } /* เหลือบรรทัดเดียว */
/* รูปเอกสารเลยสูงได้ถึง 196mm บนแผ่นที่ถูกบีบแล้ว */
.stepbox.attbox .sb-img img { width: auto; height: auto; max-width: 100%;
max-height: 196mm; border: 1px solid #e5e7eb; border-radius: 4px; }
.stepbox.attbox .sb-img.empty { height: 186mm; border: 1.5px dashed #cbd5e1;
border-radius: 10px; background: #fff; }
ที่ผมชอบวิธีนี้คือมันย้ายความรู้เรื่องการจัดหน้าไปไว้ใน CSS ที่เดียว ไม่กระจายไปอยู่ในโค้ดที่สร้างเนื้อหา คนเพิ่มหัวข้อใหม่แค่เลือกใช้ฟังก์ชันกรอบให้ถูกชนิด ที่เหลือหน้ามันบีบตัวเองเป็น
ตรวจหน้าล้นด้วยหุ่นยนต์ ไม่ใช่ด้วยตา
ห้าสิบกว่าแผ่น ถ้าเปิดดูทีละแผ่นแล้วกะด้วยสายตาว่าล้นหรือยัง ผมทำไม่ไหวและทำผิดแน่นอน ผมเลยเขียนสคริปต์ Playwright สั้นๆ ไว้สองตัว ตัวแรกตรวจสองอย่างคือ หน้าไหนสูงเกินหนึ่งหน้ากระดาษ กับมี element ตัวไหนหลุดออกไปนอกขอบพิมพ์บ้าง
// over.js — ตรวจหน้าล้นและของหลุดขอบ ทีเดียวทั้งเล่ม
const r = await page.evaluate(() => {
const MM = 96 / 25.4, over = [], bleed = [];
document.querySelectorAll('.page').forEach((pg, i) => {
const h = pg.getBoundingClientRect().height;
if (h > 1124) over.push((i + 1) + ':' + Math.round(h) + 'px'); // 297mm ที่ 96dpi = 1123
});
// ของที่ยื่นออกไปเลยเส้นขอบพิมพ์ (เผื่อ 15mm) — ข้ามของที่ไม่ถูกพิมพ์อยู่แล้ว
document.querySelectorAll('.page').forEach((pg, i) => {
const pr = pg.getBoundingClientRect();
pg.querySelectorAll('*').forEach(el => {
const r = el.getBoundingClientRect();
if (r.width > 0 &&
(r.left < pr.left + 15 * MM - 2 || r.right > pr.right - 15 * MM + 2) &&
!el.closest('.noprint') && el.className !== 'page doc')
bleed.push((i + 1) + ':' + (el.className || el.tagName));
});
});
return { over, bleed: [...new Set(bleed)].slice(0, 20),
n: document.querySelectorAll('.page').length };
});
console.log(JSON.stringify(r, null, 1));
ตัวที่สองไว้วัดว่ากรอบและรูปในแต่ละหน้ากว้างยาวกี่มิลลิเมตร แล้วเหลือที่ว่างเหนือช่องลงนามอีกเท่าไร อันนี้ผมใช้ตอนจูนขนาดกรอบ เพราะมันบอกได้ว่าจะขยายรูปเพิ่มได้อีกกี่มิลลิเมตรก่อนจะไปชนลายเซ็น
// diag.js — รายงานขนาดจริงเป็นมิลลิเมตร ไม่ต้องเดาจากพิกเซล
const MM = 96 / 25.4;
const boxes = [...pg.querySelectorAll('.tf-hold, .stepbox.attbox .sb-img')].map(el => {
const er = el.getBoundingClientRect();
const im = el.querySelector('img');
const ir = im ? im.getBoundingClientRect() : null;
return {
box: Math.round(er.width / MM) + 'x' + Math.round(er.height / MM), // ขนาดกรอบ
img: ir ? Math.round(ir.width / MM) + 'x' + Math.round(ir.height / MM) : null,
nat: im ? im.naturalWidth + 'x' + im.naturalHeight : null, // ขนาดไฟล์จริง
};
});
// ที่ว่างระหว่างของชิ้นล่างสุด กับขอบบนของช่องลงนาม -> ขยายรูปได้อีกกี่ mm
const lastBottom = Math.max(0, ...[...pg.querySelectorAll('.stepbox, .figbox')]
.map(e => e.getBoundingClientRect().bottom));
const freeBelow = Math.round((signTop - lastBottom) / MM);
สคริปต์สองตัวนี้รันจบในไม่กี่วินาที ผมเลยตั้งเป็นนิสัยว่าแก้ CSS อะไรก็ตามที่เกี่ยวกับการจัดหน้า ต้องรันตัวตรวจหน้าล้นก่อนเสมอ ค่าที่ต้องได้คือรายการว่างทั้งสองช่อง ถ้ามีชื่อคลาสโผล่มาในช่องของหลุดขอบ แปลว่ามีของยื่นออกไปนอกพื้นที่พิมพ์แล้ว ยังไม่ต้องไปเปิดกล่องพิมพ์ดู
พิมพ์ทีละแผ่นโดยไม่ต้องแยกไฟล์
ของที่คนหน้างานขอบ่อยที่สุดคือ "ขอพิมพ์ใหม่แค่แผ่นเดียว" เพราะแก้ตัวเลขผิดไปช่องเดียว ผมไม่อยากแตกเป็นไฟล์ละแผ่นให้สับสน เลยทำเป็นปุ่มเล็กๆ ที่มุมของแต่ละแผ่น กดแล้วมันติดคลาสให้หน้านั้นหน้าเดียว แล้วเรียกกล่องพิมพ์
@media print {
body { background: #fff; }
.toolbar, .hint, .noprint { display: none !important; }
*:focus { outline: none !important; }
.page { width: 210mm; min-height: 297mm; margin: 0; padding: 18mm 16mm 52mm; box-shadow: none; }
@page { size: A4; margin: 0; }
/* โหมดพิมพ์ทีละแผ่น: ซ่อนทุกหน้า เหลือเฉพาะหน้าที่ติดคลาส printme */
body.printone .page { display: none !important; }
body.printone .page.printme { display: block !important; page-break-after: auto !important; }
}
/* ปุ่มพิมพ์เฉพาะหน้านี้ โผล่ตอนเอาเมาส์ไปวางบนแผ่น และไม่ถูกพิมพ์ออกไปด้วย */
.pgprint { opacity: .35; transition: opacity .15s; }
.page:hover .pgprint { opacity: 1; }
ตัวเรียกฝั่งจาวาสคริปต์สั้นมาก แต่มีจุดที่ต้องระวังคือ ต้องถอดคลาสออกหลังกล่องพิมพ์ปิด ไม่งั้นหน้าอื่นหายไปทั้งหมดในสายตาผู้ใช้ แล้วเขาจะนึกว่าเว็บพัง
function printOne(btn) {
const pg = btn.closest('.page');
document.body.classList.add('printone');
pg.classList.add('printme');
// คืนสภาพเสมอ ไม่ว่าผู้ใช้จะกดพิมพ์จริงหรือกดยกเลิก
const restore = () => {
document.body.classList.remove('printone');
pg.classList.remove('printme');
window.removeEventListener('afterprint', restore);
};
window.addEventListener('afterprint', restore);
window.print();
setTimeout(restore, 1500); // เบราว์เซอร์บางตัวไม่ยิง afterprint ต้องมีตาข่ายรับ
}
อยากได้รูปความละเอียดสูง อย่าไปฝืนพื้นที่วาดในเบราว์เซอร์
อีกอย่างที่ต้องส่งให้เจ้าหน้าที่บ่อยคือรูปภาพของแผ่นเอกสาร ไม่ใช่ไฟล์ PDF ผมเคยทำด้วยไลบรารีที่แปลง DOM เป็นพื้นที่วาดในเบราว์เซอร์ แล้วเลือกความละเอียดได้ตั้งแต่ 300 ถึง 2400 จุดต่อนิ้ว ใช้ได้จริงแต่ ที่ความละเอียดสูงมันช้ามากและชนเพดานขนาดพื้นที่วาดของเบราว์เซอร์
ทางที่เร็วกว่าและได้ความละเอียดเต็มจริงคือ ให้เบราว์เซอร์พิมพ์ออกมาเป็น PDF ก่อนหนึ่งรอบ แล้วค่อยเอา PDF นั้นไปเรนเดอร์เป็นรูปด้วยไลบรารีฝั่ง Python
# แปลงแผ่นเอกสารเป็นรูปความละเอียดสูง โดยไม่ต้องฝืนพื้นที่วาดของเบราว์เซอร์
import fitz # PyMuPDF
doc = fitz.open("document.pdf")
for i, page in enumerate(doc, 1):
pix = page.get_pixmap(dpi=2400) # ไม่มีเพดานแบบ canvas ในเบราว์เซอร์
pix.save("sheet_%02d_2400dpi.jpg" % i)
print("เรนเดอร์ %d แผ่นเสร็จ" % len(doc))
จุดที่ผมอยากให้จำคือ เบราว์เซอร์เก่งเรื่องจัดหน้า แต่ไม่ได้เก่งเรื่องแปลงเป็นรูปความละเอียดสูง ส่วนไลบรารีอ่าน PDF เก่งเรื่องแปลงเป็นรูป แต่ไม่ได้เก่งเรื่องจัดหน้า ใช้ให้ถูกงานแล้วสองอันนี้ต่อกันได้สวย
สรุปสิ่งที่ผมยึดในงานแบบนี้
ส่วนตัวผมมีอยู่สามข้อที่ยึดตลอดทั้งงาน หนึ่งคือคิดเป็นมิลลิเมตรตั้งแต่บรรทัดแรกของ CSS อย่าไปเริ่มจากพิกเซลแล้วค่อยแปลงทีหลัง สองคือหัวและท้ายที่ต้องซ้ำทุกหน้า ให้ตารางเป็นคนทำเสมอ ไม่มีข้อยกเว้น สามคือทุกครั้งที่แตะเรื่องการจัดหน้า ต้องมีสคริปต์ตรวจหน้าล้นรันตามหลัง เพราะข้อผิดพลาดชนิดนี้มองด้วยตาบนจอไม่เห็น
ที่เหลือเรื่องดีไซน์ ขนาดตัวอักษร สีหัวกระดาษ ก็แล้วแต่ความชอบและแล้วแต่หน่วยงานปลายทางจะกำหนดมาครับ
ปล. เรื่องกล่องพิมพ์ของเบราว์เซอร์ที่มีตัวเลือกระยะขอบให้ผู้ใช้ปรับเองได้นั้น ผมยังไม่มีทางบังคับที่สะอาดพอ ตอนนี้ใช้วิธีเขียนบอกไว้บนหน้าเว็บว่าต้องตั้งระยะขอบเป็นศูนย์และปิดหัวท้ายของเบราว์เซอร์ ใครมีวิธีที่ดีกว่านี้บอกกันได้ เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ
อยากให้เราทำให้ไหม?
ให้เราช่วยติดตั้ง ตั้งค่า ย้ายระบบ หรือแก้ปัญหาเซิร์ฟเวอร์ เพื่อให้คุณมีเวลาไปทำงานที่สำคัญกว่า
เคยเจองานแบบนี้ไหม? ติดตรงไหน?
มาแลกวิธีกันต่อในบอร์ด LinuxThai



ความคิดเห็น 0
ยังไม่มีความคิดเห็น — เป็นคนแรกเลย