CV saya bukan file Word, tapi HTML plus CSS yang dirender jadi PDF lewat Chrome headless dengan print-to-PDF. Ada enam varian, tiga gaya dikali dua bahasa, dan semuanya lolos review mata saya: font rapi, bullet sejajar, heading section tegas. Waktu saya menyesuaikan satu varian untuk sebuah lamaran, iseng saya jalankan pdftotext ke hasil PDF-nya, sekadar untuk melihat teks yang benar-benar dibaca mesin. Hasilnya bikin saya berhenti mengetik.
Setiap bullet, tanda • yang di layar duduk manis di depan tiap baris pengalaman, di stream teks PDF justru berbaris di paling bawah, setelah section Education. Bukan bergeser sedikit, tapi semua bullet terkumpul jadi deretan • yatim di ujung dokumen. Parser ATS yang membaca PDF berdasarkan urutan stream, bukan posisi visual, akan salah menempelkan seluruh konten bullet ke section yang keliru, atau menganggapnya sampah.
Ada gejala kedua yang tak kalah jahat. Heading section seperti Professional Experience keluar dari pdftotext sebagai potongan glyph terpisah: P RO F ES S I O N A L. Buat manusia masih terbaca, tapi buat ATS yang mencocokkan kata kunci "Professional Experience", string dengan spasi acak di tengahnya itu tidak akan pernah match.
Menelusuri jejaknya
Tebakan pertama saya: font. Mungkin proses subset font mengacak urutan glyph waktu Chrome menulis PDF. Saya ganti ke font sistem yang polos, render ulang, hasilnya sama persis. Bullet tetap terjun ke bawah.
Tebakan kedua: mungkin ini cuma keanehan pdftotext. Saya coba ekstraktor lain, lalu saya buka PDF-nya di viewer dan menyeleksi teksnya pakai mouse, copy, paste ke editor. Urutannya tetap sama: bullet menumpuk di akhir. Jadi ini bukan bug tools, melainkan bug yang benar-benar ada di isi PDF-nya.
Baru setelah itu saya berhenti menebak dan mengisolasi. Saya bikin HTML minimal, cuma satu daftar berpoin, render, cek streamnya. Bullet dari li::before mendarat di bawah. Lalu saya hapus rule ::before itu dan pakai marker daftar bawaan, render lagi: bullet menempel rapi di barisnya. Di situ akar masalahnya ketahuan.
Kenapa ini terjadi
Bullet di CV saya dibuat pakai trik yang umum dipakai orang supaya jarak dan warna bullet gampang diatur:
li {
position: relative;
list-style: none;
}
li::before {
content: "•";
position: absolute;
left: 0;
}Masalahnya, ::before itu generated content, dan di sini ia dipasang pada elemen yang diposisikan absolut. Chrome print-to-PDF melukis generated content pada fase paint yang lebih belakang dibanding teks normal-flow. Karena posisinya absolut, koordinat visualnya tetap benar di layar, jadi mata tidak pernah curiga. Tapi urutan glyph ditulis ke content stream PDF mengikuti urutan paint, bukan urutan visual. Akibatnya semua glyph • ditulis setelah seluruh teks daftar, yang di layout saya kebetulan datang setelah section Education. Parser stream-order membaca persis urutan itu.
Untuk heading, penyebabnya beda tapi keluarganya sama. Saya pasang letter-spacing: 1.4px pada h2 section biar terlihat lega dan elegan. Dengan tracking selebar itu, Chrome memposisikan tiap glyph secara individual dengan jarak yang cukup jauh. pdftotext melihat celah antar-glyph yang besar itu lalu menyisipkan spasi di antaranya, jadi Professional berubah menjadi P RO F ES S I O N A L.
Perbaikannya
Untuk bullet, buang trik ::before dan kembali ke marker daftar bawaan:
li {
list-style: disc;
padding-left: 14px;
}Marker list-style native ditulis inline bersama list item-nya di stream, dengan urutan yang benar. Bullet langsung balik menempel ke barisnya di teks hasil ekstraksi.
Untuk heading, turunkan letter-spacing dari 1.4px ke 0.3px:
h2 {
letter-spacing: 0.3px;
}Di 0.3px, celah antar-glyph cukup kecil sehingga pdftotext menganggap huruf-hurufnya satu kata utuh. Secara visual heading masih terasa lega, tapi mesin membacanya sebagai satu string.
Verifikasi selalu lewat teks yang benar-benar dibaca mesin, bukan tampilan:
pdftotext cv.pdf - | lessSekarang bullet duduk di barisnya dan heading terbaca sebagai kata utuh. Catatan penting: baru varian terbaru yang saya perbaiki. Enam sumber CV lama masih membawa dua bug ini, jadi saya tandai untuk dibereskan sebelum dipakai lagi. Satu template rusak, semua turunannya ikut rusak diam-diam.
Checklist
- Jangan percaya tampilan visual PDF. Uji dengan
pdftotextuntuk melihat urutan stream yang benar-benar dibaca ATS. - Hindari
li::beforeuntuk bullet di dokumen yang akan diparse. Chrome mencetak generated content di fase paint belakang, jadi bullet pindah ke bawah stream. - Pakai
list-style: discbawaan supaya marker keluar inline dengan barisnya. - Letter-spacing lebar seperti 1.4px memecah heading jadi glyph terpisah di
pdftotext. Turunkan ke sekitar 0.3px. - Kalau punya banyak varian dokumen dari satu template, perbaiki semuanya, bukan cuma yang sedang dipakai.
