Bỏ qua

Hướng dẫn PDF overlay

Cách LUONVUITUOI-CERT biến một file PDF template cùng một dòng dữ liệu học viên thành chứng chỉ tải về được.

Mô hình tư duy

Template của bạn là một file PDF nhiều trang. Mỗi trang là một biến thể chứng chỉ để trống, ứng với một tổ hợp (môn, kết quả). Engine sẽ vẽ tên, trường, lớp và các thông tin khác của học viên lên trên, tại đúng tọa độ bạn chỉ định trong cert.config.json#layout.fields.

templates/main.pdf
├── trang 1: nền giải Vàng (chưa có tên, trường)
├── trang 2: nền giải Bạc
└── trang 3: nền giải Đồng
                                 ↓   + overlay
                               final.pdf  (một trang, có dữ liệu học viên)

Hệ tọa độ

Đơn vị là points (1/72 inch). Gốc tọa độ nằm tại góc dưới bên trái của trang, theo quy ước của reportlab. Trang A5 nằm ngang có kích thước (842, 595); "một inch tính từ đáy lên" tương ứng y = 72.

layout.page_size trong config chỉ là gợi ý cho người thiết kế. Renderer dùng MediaBox thực tế của trang template, nên nếu hai giá trị khác nhau thì template được ưu tiên.

Preset theo hướng trang

Engine không phân biệt hướng: dọc (portrait) hay ngang (landscape) chỉ là kích thước MediaBox khác nhau, mọi tọa độ đều là điểm tuyệt đối tính từ góc dưới-trái. Chọn hướng bằng cách xuất template PDF đúng hướng đó; config chỉ giữ tọa độ. Các khổ giấy thường gặp:

Khổ Dọc (w, h) Ngang (w, h)
A4 (595, 842) (842, 595)
A5 (420, 595) (595, 420)
Letter (612, 792) (792, 612)

Đây là tọa độ ví dụ demo-academy đang dùng, đều căn giữa theo chiều ngang (x = chiều rộng trang / 2), QR nằm sát góc dưới-phải bên trong viền.

Dọc (portrait) — A4, (595, 842):

"layout": {
  "page_size": [595, 842],
  "fields": {
    "name":   { "x": 298, "y": 492, "font": "script", "size": 32, "color": "#1E3A8A", "align": "center" },
    "grade":  { "x": 298, "y": 450, "font": "serif",  "size": 16, "color": "#334155", "align": "center" },
    "school": { "x": 298, "y": 424, "font": "serif",  "size": 16, "color": "#0F172A", "align": "center", "wrap": 60 }
  }
},
"features": { "qr_verify": { "enabled": true, "x": 456, "y": 59, "size_pt": 80 } }

Ngang (landscape) — A4, (842, 595):

"layout": {
  "page_size": [842, 595],
  "fields": {
    "name":   { "x": 421, "y": 302, "font": "script", "size": 32, "color": "#1E3A8A", "align": "center" },
    "grade":  { "x": 421, "y": 260, "font": "serif",  "size": 16, "color": "#334155", "align": "center" },
    "school": { "x": 421, "y": 234, "font": "serif",  "size": 16, "color": "#0F172A", "align": "center", "wrap": 60 }
  }
},
"features": { "qr_verify": { "enabled": true, "x": 703, "y": 59, "size_pt": 80 } }

Render cả hai hướng (prepare_demo.py sinh template; chạy bản dọc bằng lvt-cert dev --config cert.config.portrait.json):

Chứng nhận demo ngang với tên, khối, trường và QR

Chứng nhận demo dọc với tên, khối, trường và QR

Đây là điểm khởi đầu, không phải tọa độ khớp sẵn cho template của bạn. Làm theo quy trình thiết kế để căn lại theo nền riêng.

Spec field

"fields": {
  "name": {
    "x": 421, "y": 330,
    "font": "script",
    "size": 44,
    "color": "#1E3A8A",
    "align": "center"
  },
  "school": {
    "x": 421, "y": 265,
    "font": "serif",
    "size": 18,
    "align": "center",
    "wrap": 60
  }
}
Key Ý nghĩa
x, y Vị trí baseline của dòng chữ. Với align: center hoặc right, x là cạnh giữa hoặc cạnh phải.
font Khóa trỏ tới registry fonts ở cấp cao nhất.
size Cỡ chữ tính theo points.
color Chuỗi màu hex; mặc định #000000.
align Một trong left, center, right.
wrap Số nguyên tùy chọn. Kích hoạt cơ chế xuống dòng đơn giản tại số ký tự cho trước. Văn bản nhiều dòng trải xuống dưới tính từ y, với khoảng cách dòng 1.2 lần.

Tên trường

Các trường dựng sẵn được engine điền từ data_mapping:

Trường layout Nguồn
name data_mapping.name_col
dob, school, grade, phone data_mapping.*_col tương ứng, nếu có khai báo
Mọi mục trong data_mapping.extra_cols Cột cơ sở dữ liệu cùng tên

Trường khai báo trong layout.fields mà handler không điền sẽ bị bỏ qua trong im lặng; giá trị thừa trong dict của handler không khớp với trường layout nào sẽ bị loại bỏ. Nghĩa là chỉ cần thêm cột mới vào Excel nguồn rồi khai báo trong data_mapping.extra_cols là đủ để hiển thị, không cần sửa handler.

Vị trí QR

features.qr_verify.{x, y, size_pt} chỉ định vị trí vẽ mã QR đã ký. Tọa độ là góc dưới bên trái của ô vuông QR; size_pt là độ dài cạnh. Vị trí phổ biến là góc dưới bên phải:

"qr_verify": { "enabled": true, "x": 720, "y": 40, "size_pt": 80 }

Quy trình thiết kế

  1. Thiết kế template trống bằng công cụ bạn chọn (InDesign, Figma xuất ra PDF, LibreOffice, Canva). Mỗi ô giải thưởng một trang.
  2. Mở PDF trong trình xem có hiển thị tọa độ (Preview trên macOS, đa số trình chỉnh sửa PDF, hoặc nạp vào reportlab để vẽ điểm ngắm tại vị trí đã biết).
  3. Ước lượng baseline nơi muốn đặt tên. Thử "y": page_height / 2, sau đó chỉnh ±10 points cho đến khi trông vừa ý.
  4. Chạy lvt-cert dev, truy cập / với học viên dữ liệu mẫu; tải PDF về và kiểm tra.
  5. Lặp lại việc chỉnh tọa độ cho đến khi bố cục đã đúng vị trí.

An toàn dữ liệu đầu vào

  • Giá trị mỗi trường bị giới hạn ở 1000 ký tự (MAX_FIELD_LENGTH). Đầu vào quá dài sẽ làm phát sinh OverlayError, nhờ đó một dòng dữ liệu độc hại không thể làm phình to PDF khi render.
  • Giá trị chỉ gồm khoảng trắng sẽ bị bỏ qua (không overlay phần trống).
  • Giá trị không phải chuỗi sẽ được ép kiểu qua str() sau khi kiểm tra độ dài; số, boolean và các kiểu khác đều hiển thị dưới dạng chuỗi.

Fonts

Toolkit đi kèm các font TrueType có giấy phép cho phép phân phối lại (SIL OFL, Apache 2.0 và tương tự). Đặt file .ttf tại đường dẫn khai báo trong fonts.<key>; registry sẽ đăng ký font với reportlab ở lần dùng đầu tiên và lưu cache theo đường dẫn đã phân giải, nhờ đó hai project trong cùng một process dùng chung một khóa nhưng trỏ tới file khác nhau sẽ không xung đột.

Những việc engine KHÔNG làm

  • Không tạo hình vector hay dàn trang kiểu CSS. Bản thân PDF là phần thiết kế; engine chỉ overlay chữ.
  • Không điền vào trường biểu mẫu của PDF. Hãy dùng cách lấy template làm nền.
  • Không tự ngắt từ bằng dấu gạch nối. wrap chỉ ngắt theo khoảng trắng.
  • Không overlay hình ảnh nào ngoài QR. Hãy đặt logo trực tiếp vào template.

Với chứng chỉ nhiều trang hoặc cần đóng watermark, hãy hậu xử lý kết quả đầu ra của engine bằng pypdf trong một handler tùy chỉnh.