EST. 2026 — บันทึกงานเซิร์ฟเวอร์จริง SYSADMIN · SECURITY · AI · DIRECTADMIN เล่มที่ 01
THE SYSADMIN ALMANAC
หน้าหลัก / AI / กรอกฟอร์มราชการลง PDF ด้วยสคริปต์ และทำสำเนาบัตรให้ปลอดภัย
AI

กรอกฟอร์มราชการลง PDF ด้วยสคริปต์ และทำสำเนาบัตรให้ปลอดภัย

โดย Ton-Or ·22 ต.ค. 2026·อ่าน 10 นาที
กรอกฟอร์มราชการลง PDF ด้วยสคริปต์ และทำสำเนาบัตรให้ปลอดภัย

สำเนาบัตรที่ไม่ได้เขียนว่าใช้ทำอะไร คือเอกสารที่เอาไปใช้ต่อได้ทุกเรื่อง ลายน้ำที่ระบุวัตถุประสงค์ไม่ใช่เรื่องสวยงาม แต่เป็นชั้นป้องกันที่ถูกที่สุดที่เราทำได้เอง

พอดีผมมีงานยื่นเอกสารกับหน่วยงานราชการอยู่ชุดหนึ่ง ที่ต้องทำซ้ำหลายรอบในเวลาไล่เลี่ยกัน แต่ละรอบมีแบบฟอร์มสองสามใบกับเอกสารแนบอีกหลายใบ ถ้ากรอกด้วยมือทีละรอบ นอกจากช้าแล้วยังพิมพ์ผิดทุกครั้งด้วย ผมเลยเขียนสคริปต์กรอกให้จบในคำสั่งเดียว

เรื่องนี้ไม่ใช่การทำเอกสารขึ้นมาใหม่จากหน้าเว็บแล้วสั่งพิมพ์ ซึ่งเป็นอีกแนวทางที่ผมเคยเล่าไว้แล้ว แต่เป็นงานอีกแบบหนึ่ง คือเรามีไฟล์ฟอร์มของทางราชการอยู่ในมือ ห้ามเปลี่ยนหน้าตา ห้ามพิมพ์ใหม่ ต้องกรอกลงไปในช่องที่เขาทำมาให้ แล้วพิมพ์ออกมายื่น

ชื่อ นามสกุล เลขประจำตัว ที่อยู่ วันเดือนปี และเลขที่คำขอทั้งหมดในบทความนี้ผมสมมติขึ้นใหม่ทั้งชุดให้ดูออกชัดเจนว่าเป็นตัวอย่าง ส่วนเทคนิค พิกัด และโค้ด เป็นของจริงจากงานนั้น

ฟอร์มที่กรอกได้ กับฟอร์มที่กรอกภาษาไทยได้ ไม่ใช่เรื่องเดียวกัน

ฟอร์มราชการที่ดาวน์โหลดมาส่วนใหญ่เป็นเอกสารที่มีช่องกรอกฝังอยู่จริง พิมพ์ลงไปในโปรแกรมอ่านเอกสารได้ ปัญหาคือช่องพวกนั้นถูกสร้างด้วยฟอนต์ที่ไม่มีตัวอักษรไทยครบ ผลที่ได้เวลากรอกภาษาไทยแล้วสั่งให้มันวาดเอง คือช่องว่างเปล่า หรือกล่องสี่เหลี่ยมเรียงกัน

ทางเดินของหนึ่งฟอร์มไฟล์ต้นฉบับฟอร์มของราชการ->ไล่ชื่อช่องดูว่าช่องไหนชื่ออะไร->กรอกผ่านช่องเฉพาะตัวเลขและอังกฤษ->อบให้เป็นเนื้อเอกสารแก้ไม่ได้อีก->วาดข้อความไทยทับด้วยฟอนต์ของเราเอง->เรนเดอร์เป็นรูปตรวจ

ลำดับนี้สำคัญมาก โดยเฉพาะตำแหน่งของขั้นตอนอบเอกสาร ถ้าเราวาดข้อความไทยลงไปก่อนแล้วค่อยอบ ข้อความไทยจะโดนหน้าตาของช่องกรอกวาดทับหายไปเลย ต้องอบให้ช่องกรอกกลายเป็นเนื้อเอกสารจริงเสียก่อน แล้วค่อยวาดของเราลงไปข้างบน

ไล่ชื่อช่องออกมาดูก่อน อย่าเดา

ชื่อช่องในฟอร์มราชการแทบไม่มีความหมายอะไรเลย เพราะคนทำใช้ชื่อที่โปรแกรมตั้งให้อัตโนมัติ หน้าตาประมาณ Text25 หรือ Check Box19 สิ่งเดียวที่ทำได้คือไล่ออกมาทั้งหมดพร้อมพิกัด แล้วจับคู่กับหน้ากระดาษที่พิมพ์ออกมาดูด้วยตา

# dump_fields.py — ไล่ชื่อช่องทั้งหมดพร้อมพิกัด เรียงจากบนลงล่าง ซ้ายไปขวา
import fitz

doc = fitz.open('form_template.pdf')
for pno, page in enumerate(doc, start=1):
    widgets = list(page.widgets())
    print('===== หน้า', pno, ':', len(widgets), 'ช่อง')
    # พิกัด y ของ PDF นับจากล่างขึ้นบน แปลงเป็นนับจากบนลงล่างให้ตรงกับที่ตาเห็น
    for w in sorted(widgets, key=lambda w: (-w.rect.y1, w.rect.x0)):
        r = [round(v) for v in w.rect]
        ytop = round(page.rect.height) - r[3]
        extra = ''
        if w.field_type_string == 'RadioButton':
            extra = ' options=' + str(w.button_states())
        print('y%3d x%3d-%3d h%2d %-10s %s%s'
              % (ytop, r[0], r[2], r[3] - r[1], w.field_type_string, w.field_name, extra))

ผลที่ได้เป็นรายการยาวๆ ที่อ่านคู่กับกระดาษแล้วจับคู่ได้เร็วมาก เพราะเรียงตามลำดับที่ตาเราไล่อยู่แล้ว ผมพิมพ์ฟอร์มเปล่าออกมาหนึ่งใบ เขียนชื่อช่องด้วยดินสอลงไปข้างๆ ทีละช่อง ใช้เวลาไม่ถึงยี่สิบนาทีต่อฟอร์มหนึ่งใบ แล้วหลังจากนั้นไม่ต้องเดาอีกเลย

ช่องกาถูกมักไม่ได้ชื่อว่า true

ช่องกาถูกในฟอร์มไม่ได้รับค่าจริงเท็จเสมอไป บางไฟล์ค่าที่ถูกต้องคือสายอักขระอย่าง Yes หรือ On หรือแม้แต่ชื่อตัวเลือกของมันเอง ถ้าใส่แล้วไม่ติด ให้ไล่ดูสถานะที่ช่องนั้นรับได้ก่อน แล้วค่อยใส่ค่าที่มันยอมรับ อย่าเถียงกับไฟล์

กรอกผ่านช่อง แล้วอบให้เป็นเนื้อเอกสารจริง

ขั้นตอนกรอกตรงไปตรงมา คือวนดูช่องทั้งหมด ถ้าชื่อตรงกับที่เราเตรียมไว้ก็ใส่ค่าลงไป ที่ต้องระวังคือปุ่มล้างข้อมูลที่ฟอร์มราชการหลายใบแถมมาให้ ถ้าปล่อยไว้มันจะติดไปกับไฟล์ที่ยื่น และถ้าใครกดโดนคือข้อมูลหายทั้งใบ ผมลบทิ้งก่อนอบเสมอ

# fill_form.py — กรอกผ่านช่อง แล้วอบให้กลายเป็นเนื้อเอกสาร แก้ต่อไม่ได้
import fitz

TEXT_FIELDS = {
    'Text25':  'Mr. Somchai Tuayang',                    # ชื่อภาษาอังกฤษ
    'Text26':  '99 Moo 9, Tambon Tuayang, Mueang District,',
    'Text27':  'Sample Province 10000, Thailand',
    'Text78':  'Singapore',                              # ประเทศปลายทาง
    'Text79.0': '2568',                                  # ปีภาษี
    'Text98.1': '3',                                     # จำนวนเอกสารแนบ
}
CHECK_FIELDS = {'Check Box2', 'Check Box6.0', 'Check Box19', 'Check Box20'}

doc = fitz.open('form_template.pdf')
page = doc[0]

for w in page.widgets():
    if w.field_name in TEXT_FIELDS:
        w.field_value = TEXT_FIELDS[w.field_name]
        w.update()
    elif w.field_name in CHECK_FIELDS and w.field_type_string == 'CheckBox':
        w.field_value = True
        w.update()

# ลบปุ่มล้างข้อมูลที่ฟอร์มแถมมา ก่อนอบ
for w in list(page.widgets()):
    if w.field_type_string == 'Button':
        page.delete_widget(w)

doc.bake()        # ช่องกรอกกลายเป็นเนื้อเอกสาร พิมพ์แล้วไม่หาย และแก้กลับไม่ได้
page = doc[0]     # หลังอบแล้ว ต้องหยิบหน้าใหม่ อ้างอิงเดิมใช้ไม่ได้
หลังอบเอกสารแล้ว อ้างอิงหน้าเดิมใช้ต่อไม่ได้

นี่คือจุดที่ทำให้ผมงงอยู่พักใหญ่ เพราะโค้ดไม่ได้แจ้งข้อผิดพลาดอะไรเลย แต่ข้อความที่วาดลงไปหลังอบกลับไม่โผล่ในไฟล์ผลลัพธ์ สาเหตุคือตัวแปรหน้าที่ถืออยู่ชี้ไปที่โครงสร้างเดิมที่ถูกแทนที่ไปแล้ว ต้องหยิบหน้าจากเอกสารใหม่อีกครั้งหลังอบเสมอ

ช่องแบบตารางย่อย ต้องเว้นวรรคให้ตรงช่องขั้น ช่องเลขประจำตัวในฟอร์มราชการมักเป็นช่องเดียว แต่มีเส้นแบ่งเป็นกล่องเล็กๆ เรียงกัน แล้วมีช่องขั้นระหว่างกลุ่มตัวเลขด้วย ถ้าเราใส่ตัวเลขติดกันรวดเดียว มันจะไหลไปทับเส้นแบ่ง เพราะตัวโปรแกรมนับกล่องทุกกล่องเท่ากันหมด รวมทั้งกล่องที่เป็นช่องขั้น

ทางแก้คือใส่ช่องว่างเข้าไปตรงตำแหน่งช่องขั้นเอง ให้จำนวนอักขระทั้งหมดเท่ากับจำนวนกล่องพอดี

ช่องเดียว 17 กล่อง กับเลขตัวอย่าง 13 หลัก31234567890121 ตัว4 ตัว5 ตัว2 ตัว1 ตัวช่องขั้นช่องขั้นช่องขั้นช่องขั้นกลุ่ม 1-4-5-2-1 รวม 13 หลัก บวกช่องขั้น 4 ช่อง ได้ 17 กล่องพอดี
ช่องเลขประจำตัวมี 17 กล่อง ช่องขั้นระหว่างกลุ่มนับเป็นกล่องด้วย ต้องใส่ช่องว่างตรงตำแหน่งนั้นเอง ไม่งั้นตัวเลขเลื่อนไปหนึ่งกล่อง
# เลขประจำตัวตัวอย่าง 13 หลัก ในช่องที่แบ่งเป็น 17 กล่อง
# รูปแบบกลุ่มคือ 1-4-5-2-1 และมีช่องขั้นคั่น 4 ช่อง รวมเป็น 17 กล่อง
SAMPLE_ID = '3123456789012'          # ตัวอย่างล้วน ไม่ใช่เลขของใคร

comb = '%s %s %s %s %s' % (SAMPLE_ID[0], SAMPLE_ID[1:5], SAMPLE_ID[5:10],
                           SAMPLE_ID[10:12], SAMPLE_ID[12])
assert len(comb) == 17, 'จำนวนอักขระต้องเท่ากับจำนวนกล่องพอดี'
TEXT_FIELDS['Text102'] = comb

บรรทัดตรวจสอบความยาวข้างล่างผมใส่ไว้เพราะเคยพลาดมาแล้ว คือแก้รูปแบบกลุ่มตัวเลขแล้วลืมนับใหม่ ผลคือไฟล์ออกมาสวยดี แต่ตัวเลขเลื่อนไปหนึ่งกล่องตลอดทั้งแถว ซึ่งมองผ่านๆ ไม่เห็น เห็นตอนเจ้าหน้าที่ทักเท่านั้น

ข้อความไทย ต้องวาดเอง และต้องยกวรรณยุกต์เอง

หลังอบเอกสารแล้ว เราวาดข้อความลงไปตรงๆ ได้ด้วยฟอนต์ของเราเอง ซึ่งเลือกฟอนต์ที่มีตัวอักษรไทยครบ

FONT_TH = r'C:/Windows/Fonts/leelawad.ttf'

# (x, y จากขอบล่าง, ข้อความ, ขนาด) — ได้พิกัดจากรายการช่องที่ไล่ไว้ตอนแรก
THAI_TEXT = [
    (452,  48, '4',                     11),   # เลขหลังเส้นประหัวฟอร์ม
    (400, 483, 'มกราคม',                10),   # เดือนเริ่ม
    (518, 483, 'ธันวาคม',               10),   # เดือนสิ้นสุด
    (305, 784, 'นายสมชาย ตัวอย่าง',      11),   # ชื่อในวงเล็บใต้ลายเซ็น
]
for x, y, s, size in THAI_TEXT:
    page.insert_text((x, y), s, fontname='leela', fontfile=FONT_TH, fontsize=size)

doc.save('form_filled.pdf', deflate=True)

ทีนี้มาถึงปัญหาที่คนทำเอกสารไทยด้วยโปรแกรมเจอกันทุกคน คือเวลาคำมีสระบนกับวรรณยุกต์ซ้อนกัน เช่นคำว่ามีไม้เอกอยู่บนสระอิ ตัววรรณยุกต์จะไปวางทับสระจนอ่านไม่ออก เพราะไลบรารีวาดตัวอักษรเรียงกันไปทีละตัวโดยไม่รู้ว่าตัวไหนควรลอยสูงกว่าตัวไหน

ทางแก้ที่ผมใช้คือวาดทีละอักขระเอง แล้วยกวรรณยุกต์ขึ้นเมื่ออักขระก่อนหน้าเป็นสระบน

# -*- coding: utf-8 -*-
from reportlab.pdfbase import pdfmetrics

UPPER_VOWELS = set('ัิีึื็')     # สระที่วางอยู่เหนือพยัญชนะ
RAISED_MARKS = set('่้๊๋์')        # วรรณยุกต์และทัณฑฆาตที่ต้องลอยสูงขึ้นถ้าซ้อน
RISE_FACTOR = 0.34                 # ยกขึ้นเท่ากับ 34% ของขนาดตัวอักษร

def draw_thai(c, x, y, text, font, size, bg=False):
    if bg and text.strip():
        w = pdfmetrics.stringWidth(text, font, size)
        c.saveState()
        c.setFillColorRGB(1, 1, 1)                       # พื้นขาวทับเส้นประของฟอร์ม
        c.rect(x - 2, y - size * 0.28, w + 4, size * 1.38, fill=1, stroke=0)
        c.restoreState()
    c.setFont(font, size)
    cx, prev = x, ''
    for ch in text:
        if ch in RAISED_MARKS and prev in UPPER_VOWELS:
            c.drawString(cx, y + size * RISE_FACTOR, ch)  # ยกให้พ้นสระบน
        else:
            c.drawString(cx, y, ch)
        w = pdfmetrics.stringWidth(ch, font, size)
        cx += w
        # อักขระที่ไม่กินความกว้างไม่นับเป็นตัวก่อนหน้า ไม่งั้นตรรกะเพี้ยน
        if w > 0.01 * size or ch not in RAISED_MARKS:
            prev = ch
    return cx - x
พื้นขาวใต้ข้อความ คือของที่ทำให้เอกสารดูเหมือนพิมพ์มาแต่แรก

ฟอร์มราชการมีเส้นประให้เขียนอยู่แทบทุกช่อง ถ้าเราวาดข้อความทับลงไปเฉยๆ เส้นประจะพาดกลางตัวอักษรจนอ่านยาก การวาดสี่เหลี่ยมสีขาวไว้ใต้ข้อความก่อนหนึ่งชั้น ทำให้ผลลัพธ์สะอาดทันที ความสูงของสี่เหลี่ยมต้องเผื่อสระบนกับสระล่างด้วย ผมใช้ประมาณหนึ่งเท่าสามส่วนของขนาดตัวอักษร และเยื้องลงจากเส้นฐานประมาณสามส่วนสิบ ซึ่งพอดีกับฟอนต์ไทยส่วนใหญ่

สำเนาบัตรบน A4 ต้องได้ขนาดเท่าของจริง

งานยื่นเอกสารเกือบทุกชุดต้องแนบสำเนาบัตร ซึ่งคนส่วนใหญ่ถ่ายรูปบัตรด้วยมือถือแล้วแปะลงหน้ากระดาษ ผลคือสำเนาที่ใหญ่กว่าบัตรจริงสองเท่า ซึ่งเจ้าหน้าที่รับได้แต่ดูไม่เรียบร้อย และบางหน่วยงานขอให้พิมพ์เท่าขนาดจริง

ขนาดบัตรมาตรฐานคือ 85.6 คูณ 53.98 มิลลิเมตร แปลงเป็นจุดภาพที่ความละเอียด 300 จุดต่อนิ้วได้ตัวเลขที่ใช้ได้เลย

# -*- coding: utf-8 -*-
from PIL import Image

DPI = 300
A4_PX = (2480, 3508)      # A4 ที่ 300 dpi
CARD_PX = (1011, 638)     # 85.6 x 53.98 mm ที่ 300 dpi = ขนาดบัตรจริง

card = Image.open('card_scan.jpg').resize(CARD_PX, Image.LANCZOS)
page = Image.new('RGBA', A4_PX, (255, 255, 255, 255))
cx = (A4_PX[0] - CARD_PX[0]) // 2      # กึ่งกลางแนวนอน
cy = 520                                # เว้นหัวกระดาษไว้พอสมควร
page.paste(card, (cx, cy))
2480x3508A4 ที่ 300 dpi
1011x638ขนาดบัตรจริง
18 องศามุมเอียงลายน้ำ
170ความทึบลายน้ำบนบัตร
200 dpiความละเอียดตอนแบนเอกสาร

ความละเอียด 300 จุดต่อนิ้วเป็นค่าที่ผมยืนยันว่าต้องใช้อย่างต่ำ ต่ำกว่านั้นตัวหนังสือเล็กบนบัตรจะอ่านไม่ออกหลังพิมพ์ ส่วนสูงกว่านั้นไฟล์ใหญ่ขึ้นโดยที่เครื่องพิมพ์สำนักงานทั่วไปพิมพ์ไม่ออกอยู่ดี

ลายน้ำที่ระบุวัตถุประสงค์ คือชั้นป้องกันที่ถูกที่สุด

สำเนาบัตรที่ไม่เขียนว่าใช้ทำอะไร คือเอกสารที่เอาไปใช้ทำอะไรก็ได้

สำเนาบัตรที่เซ็นรับรองแล้วแต่ไม่ระบุวัตถุประสงค์ เป็นเอกสารที่นำไปใช้ต่อได้ทุกเรื่อง ทั้งเปิดบัญชี สมัครบริการ หรือยื่นแทนเราที่อื่น ซึ่งเราไม่มีทางรู้เลย ลายน้ำที่เขียนว่าใช้สำหรับเรื่องอะไรและลงวันที่กำกับ ทำให้เอกสารใบนั้นใช้กับเรื่องอื่นไม่ได้ และถ้ามีคนพยายามใช้ ก็เห็นได้ทันทีว่าผิดวัตถุประสงค์ ต้นทุนของการทำคือโค้ดยี่สิบบรรทัด เทียบกับความเสียหายแล้วคุ้มมาก

ลายน้ำที่ผมทำมีสองบรรทัด บรรทัดบนคือวัตถุประสงค์ บรรทัดล่างคือวันที่ วาดลงบนชั้นโปร่งใสแยก แล้วหมุนก่อนวางทับเฉพาะบริเวณบัตร ไม่ใช่ทับทั้งหน้ากระดาษ เพราะถ้าทับทั้งหน้าแล้วลายน้ำจะบังกล่องรับรองสำเนาถูกต้องด้วย

# -*- coding: utf-8 -*-
from PIL import Image, ImageDraw, ImageFont

WM_TEXT = 'ใช้สำหรับยื่นคำขอฉบับนี้เท่านั้น'
WM_DATE = '13 พฤศจิกายน 2569'
FONT_BOLD = r'C:/Windows/Fonts/leelawdb.ttf'

def make_wm_layer(size_px, angle=18, alpha=170, font_px=72):
    f1 = ImageFont.truetype(FONT_BOLD, font_px)
    f2 = ImageFont.truetype(FONT_BOLD, int(font_px * 0.6))
    probe = ImageDraw.Draw(Image.new('RGBA', (10, 10)))
    w1 = int(probe.textlength(WM_TEXT, font=f1))
    w2 = int(probe.textlength(WM_DATE, font=f2))

    strip = Image.new('RGBA', (w1 + 40, int(font_px * 2.6)), (0, 0, 0, 0))
    sd = ImageDraw.Draw(strip)
    col = (60, 60, 60, alpha)                       # เทาโปร่ง อ่านออกแต่ไม่บังรูป
    draw_thai_pil(sd, (20, int(font_px * 0.25)), WM_TEXT, f1, col)
    draw_thai_pil(sd, (20 + (w1 - w2) // 2, int(font_px * 1.55)), WM_DATE, f2, col)

    strip = strip.rotate(angle, expand=True, resample=Image.BICUBIC)
    layer = Image.new('RGBA', size_px, (0, 0, 0, 0))
    layer.paste(strip, ((size_px[0] - strip.width) // 2,
                        (size_px[1] - strip.height) // 2), strip)
    return layer

# วางทับเฉพาะบริเวณบัตร เผื่อขอบออกไปด้านละ 130 จุดภาพ
wm = make_wm_layer((CARD_PX[0] + 260, CARD_PX[1] + 260))
page.alpha_composite(wm, (cx - 130, cy - 130))

ใต้บัตรลงมาคือกล่องรับรองสำเนาถูกต้อง ซึ่งผมวาดเป็นสามบรรทัดคือข้อความรับรอง เส้นให้เซ็น และชื่อในวงเล็บ จัดกึ่งกลางให้ตรงกับบัตร

def certify_block(draw, x_center, y, name, font_path, size=44):
    f = ImageFont.truetype(font_path, size)
    lines = ['รับรองสำเนาถูกต้อง',
             '.' * 55,
             '( ' + name + ' )']
    for i, line in enumerate(lines):
        w = draw.textlength(line, font=f)
        draw_thai_pil(draw, (x_center - w / 2, y + i * 90), line, f, (40, 40, 40, 255))

d = ImageDraw.Draw(page)
certify_block(d, A4_PX[0] // 2, cy + CARD_PX[1] + 260,
              'นายสมชาย ตัวอย่าง', r'C:/Windows/Fonts/leelawad.ttf')

สำหรับเอกสารแนบที่เป็นใบรับรองซึ่งมีตราของหน่วยงานกับรหัสตรวจสอบพิมพ์อยู่ ผมย้ายกล่องรับรองไปไว้ที่มุมล่างขวาในพื้นที่ว่างแทน เพราะถ้าเซ็นทับตราหรือทับรหัสตรวจสอบ เจ้าหน้าที่อาจสแกนตรวจไม่ได้ แล้วตีกลับทั้งชุด

ตรวจงานด้วยการเรนเดอร์เป็นรูป ไม่ใช่เปิดดูในโปรแกรมอ่าน

เรื่องนี้เป็นนิสัยที่ผมติดมาจากการโดนตีกลับ คือหลังสร้างไฟล์เสร็จ ให้สคริปต์เรนเดอร์หน้าแรกออกมาเป็นรูปทันที แล้วเปิดดูรูปนั้น ไม่ใช่เปิดไฟล์เอกสารในโปรแกรมอ่าน

  1. สคริปต์บันทึกไฟล์เอกสารเสร็จ แล้วเปิดไฟล์ที่เพิ่งเขียนขึ้นมาอ่านใหม่ ไม่ใช้ของที่ค้างในหน่วยความจำ
  2. เรนเดอร์หน้าแรกที่ตัวคูณ 1.5 เท่า บันทึกเป็นรูปลงโฟลเดอร์ตรวจงาน
  3. เปิดรูปดูด้วยตา เทียบกับฟอร์มเปล่าที่พิมพ์ไว้ตอนแรก ดูว่าตัวเลขอยู่ในกล่องครบ วรรณยุกต์ไม่ชนกัน และไม่มีช่องไหนว่างที่ไม่ควรว่าง
  4. พิมพ์ออกกระดาษจริงหนึ่งใบก่อนยื่น เฉพาะรอบแรกของฟอร์มแต่ละแบบ เพื่อวัดขนาดสำเนาบัตรด้วยไม้บรรทัด
  5. พอผ่านรอบแรกแล้ว รอบถัดไปดูจากรูปที่เรนเดอร์อย่างเดียวพอ
# ตรวจงาน: อ่านไฟล์ที่เพิ่งเขียน แล้วเรนเดอร์ออกมาเป็นรูป
import fitz

d2 = fitz.open('form_filled.pdf')
pix = d2[0].get_pixmap(matrix=fitz.Matrix(1.5, 1.5))
pix.save('verify/form_filled.png')
print('ตรวจที่ verify/form_filled.png')

เหตุผลที่ต้องอ่านไฟล์ใหม่แทนการใช้ของที่ค้างในหน่วยความจำ คือเราอยากตรวจสิ่งที่บันทึกลงดิสก์จริง ไม่ใช่สิ่งที่เราคิดว่าบันทึกไป เคสที่ผมเจอคือบันทึกไม่ผ่านเพราะไฟล์ถูกเปิดค้างอยู่ในโปรแกรมอ่าน แต่สคริปต์รายงานว่าสำเร็จ แล้วผมก็ยื่นไฟล์เก่าไปหนึ่งรอบ

เรื่องข้อมูลส่วนบุคคลที่ต้องคิดตั้งแต่บรรทัดแรก

ไฟล์งานชุดนี้คือกองข้อมูลส่วนบุคคล ไม่ใช่โค้ดธรรมดา

โฟลเดอร์งานแบบนี้มีทั้งรูปบัตร รูปทะเบียนบ้าน ลายเซ็นที่ตัดพื้นหลังออกแล้ว และไฟล์เอกสารที่กรอกครบ ซึ่งรวมกันแล้วพอสำหรับสวมรอยได้จริง ผมกันไว้สี่ชั้น หนึ่ง โฟลเดอร์นี้อยู่นอกพื้นที่ที่ซิงก์ขึ้นคลาวด์ สอง ไม่เข้าระบบควบคุมเวอร์ชันเด็ดขาด ให้ใส่ไว้ในรายการยกเว้นตั้งแต่วันแรก สาม ค่าที่เป็นข้อมูลบุคคลอยู่ในไฟล์แยกไฟล์เดียว ไม่ปนในสคริปต์ สี่ ลบทิ้งเมื่องานจบ ไม่เก็บไว้เผื่อ

ข้อที่สามมีประโยชน์กว่าที่คิด เพราะตอนเขียนบทความนี้ ผมแค่เปลี่ยนไฟล์ค่าตัวแปรเป็นชุดตัวอย่าง แล้วรันซ้ำ ได้ภาพผลลัพธ์สำหรับเอาไปอธิบายโดยไม่มีข้อมูลจริงติดไปแม้แต่ตัวเดียว ถ้าค่าพวกนี้ฝังอยู่ในสคริปต์ ผมต้องนั่งไล่ลบทีละบรรทัดและมีโอกาสหลงเหลือ

# ของที่ต้องมีก่อนเริ่ม
pip install pymupdf pillow reportlab

# รันแล้วดูผลที่รูปตรวจงาน ไม่ใช่เปิดไฟล์เอกสาร
python fill_form.py && python make_card_copy.py

สรุป

งานกรอกฟอร์มราชการด้วยสคริปต์ไม่ได้ยากที่ตัวไลบรารี แต่ยากที่รายละเอียดซึ่งไม่มีใครเขียนไว้ในเอกสารประกอบ คือช่องที่กรอกไทยไม่ได้ ช่องตารางย่อยที่ต้องนับกล่องให้ตรง วรรณยุกต์ที่ต้องยกเอง และลำดับของขั้นตอนอบเอกสารที่พลาดแล้วเงียบ

ส่วนตัวผมเก็บได้สามข้อ ข้อแรกไล่ชื่อช่องออกมาดูก่อนเสมอ ยี่สิบนาทีตรงนั้นประหยัดเวลาเดาไปหลายชั่วโมง ข้อสองอบเอกสารก่อน แล้วค่อยวาดภาษาไทยทับ และอย่าลืมหยิบหน้าใหม่หลังอบ ข้อสามสำเนาบัตรต้องมีลายน้ำที่ระบุวัตถุประสงค์และวันที่เสมอ ไม่ว่าจะยื่นให้ใคร

ใครจะใช้ไลบรารีตัวอื่นแทนก็ตามถนัดครับ แนวคิดเรื่องลำดับขั้นตอนกับการยกวรรณยุกต์ใช้ได้เหมือนกันหมด เปลี่ยนแค่ชื่อฟังก์ชัน

ปล. ของที่ผมยังทำไม่เสร็จคือการอ่านค่าพิกัดจากไฟล์กำหนดค่าแทนการฝังตัวเลขไว้ในสคริปต์ ตอนนี้พอเปลี่ยนฟอร์มเป็นรุ่นใหม่ที่ขยับตำแหน่งไปสองสามมิลลิเมตร ผมต้องไปไล่แก้ตัวเลขในโค้ด ซึ่งผิดหลักที่ควรจะเป็น เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ

อยากให้เราทำให้ไหม?

ให้เราช่วยติดตั้ง ตั้งค่า ย้ายระบบ หรือแก้ปัญหาเซิร์ฟเวอร์ เพื่อให้คุณมีเวลาไปทำงานที่สำคัญกว่า

คุยเรื่องงานกับเรา

เคยเจองานแบบนี้ไหม? ติดตรงไหน?

มาแลกวิธีกันต่อในบอร์ด LinuxThai

→ คุยต่อในบอร์ด

ความคิดเห็น 0

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