กรอกฟอร์มราชการลง 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))
ผลที่ได้เป็นรายการยาวๆ ที่อ่านคู่กับกระดาษแล้วจับคู่ได้เร็วมาก เพราะเรียงตามลำดับที่ตาเราไล่อยู่แล้ว ผมพิมพ์ฟอร์มเปล่าออกมาหนึ่งใบ เขียนชื่อช่องด้วยดินสอลงไปข้างๆ ทีละช่อง ใช้เวลาไม่ถึงยี่สิบนาทีต่อฟอร์มหนึ่งใบ แล้วหลังจากนั้นไม่ต้องเดาอีกเลย
ช่องกาถูกในฟอร์มไม่ได้รับค่าจริงเท็จเสมอไป บางไฟล์ค่าที่ถูกต้องคือสายอักขระอย่าง 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] # หลังอบแล้ว ต้องหยิบหน้าใหม่ อ้างอิงเดิมใช้ไม่ได้
นี่คือจุดที่ทำให้ผมงงอยู่พักใหญ่ เพราะโค้ดไม่ได้แจ้งข้อผิดพลาดอะไรเลย แต่ข้อความที่วาดลงไปหลังอบกลับไม่โผล่ในไฟล์ผลลัพธ์ สาเหตุคือตัวแปรหน้าที่ถืออยู่ชี้ไปที่โครงสร้างเดิมที่ถูกแทนที่ไปแล้ว ต้องหยิบหน้าจากเอกสารใหม่อีกครั้งหลังอบเสมอ
ช่องแบบตารางย่อย ต้องเว้นวรรคให้ตรงช่องขั้น ช่องเลขประจำตัวในฟอร์มราชการมักเป็นช่องเดียว แต่มีเส้นแบ่งเป็นกล่องเล็กๆ เรียงกัน แล้วมีช่องขั้นระหว่างกลุ่มตัวเลขด้วย ถ้าเราใส่ตัวเลขติดกันรวดเดียว มันจะไหลไปทับเส้นแบ่ง เพราะตัวโปรแกรมนับกล่องทุกกล่องเท่ากันหมด รวมทั้งกล่องที่เป็นช่องขั้น
ทางแก้คือใส่ช่องว่างเข้าไปตรงตำแหน่งช่องขั้นเอง ให้จำนวนอักขระทั้งหมดเท่ากับจำนวนกล่องพอดี
# เลขประจำตัวตัวอย่าง 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))
ความละเอียด 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.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
ยังไม่มีความคิดเห็น — เป็นคนแรกเลย