Justifikasi penuh adalah tata letak yang membuat kolom teks sejajar di kedua tepi kiri dan kanan - tampilan yang Anda harapkan dari buku cetak atau laporan resmi. Mudah untuk dijelaskan dan mengejutkan betapa mudahnya dibuat salah, karena jawaban atas pertanyaan "ke mana spasi ekstra pergi" tidak sama untuk bahasa Inggris dan bahasa Jepang, dan karena cara naif untuk mengukur setiap baris mengubah halaman yang cepat menjadi lambat. HotPDF menyediakan justifikasi yang sadar skrip melalui satu pemanggilan tata letak kotak, dan di balik pemanggilan itu terdapat perbaikan performa buku teks yang layak dipahami tersendiri
Artikel ini membahas keduanya. Pertama, aturan tipografi yang memutuskan cara slack didistribusikan untuk skrip dengan jeda kata versus skrip tanpa jeda. Kedua, perubahan pengukuran yang memangkas biaya per halaman untuk justifikasi sekitar delapan puluh kali lipat tanpa perbedaan visual pada keluaran. Keduanya penting jika Anda menghasilkan dokumen dalam jumlah besar dan ingin tampilannya seperti penyusunan huruf asli, bukan keluaran monospaced yang diregangkan agar pas
Apa yang sebenarnya dibutuhkan justifikasi penuh
Baris teks yang digambar pada lebar alaminya hampir tidak pernah mencapai tepi kanan kolomnya. Selalu ada sisa, yaitu slack, antara tempat berakhirnya glyph terakhir dan tempat batas kolom berada. Perataan kiri membiarkan slack itu di sebelah kanan. Perataan kanan memindahkannya ke kiri. Perataan tengah membaginya. Justifikasi penuh menghilangkannya dengan melebarkan baris itu sendiri hingga kedua tepi bertemu dengan kotak, dan satu-satunya cara jujur untuk melakukan itu adalah mendorong glyph-glyph terpisah dari dalam
Aturan yang memisahkan justifikasi yang baik dari yang buruk adalah di mana Anda meletakkan slack. Skrip yang menulis kata-kata dengan spasi di antara mereka, seperti bahasa Inggris dan keluarga Latin lainnya, memiliki jahitan alami di setiap spasi antar kata. Melebarkan spasi tersebut tidak terlihat oleh mata karena pembaca sudah menerima bahwa jarak kata bervariasi. Skrip yang menulis tanpa jeda kata, seperti karakter Han Tionghoa, kana Jepang, atau Hangul Korea, tidak memiliki jahitan seperti itu. Di sana slack harus disebar merata di antara glyph yang berdekatan, yang merupakan prinsip yang disebut penyusun huruf Jepang sebagai kintou-waritsuke, spasi merata. Menaruh peregangan jeda kata gaya Latin pada baris CJK, atau memasukkan semua slack ke satu-satunya tempat yang mengandung spasi dalam baris CJK, menghasilkan aliran dan celah yang menandai keluaran amatir
Cara HotPDF memutuskan di mana spasi diletakkan
HotPDF membuat keputusan itu per celah, bukan per baris. Saat menjustifikasi baris, ia menelusuri setiap pasangan glyph yang berdekatan dan bertanya apakah batas yang bisa diregangkan ada di antara keduanya. Batas bisa diregangkan jika salah satu sisinya adalah spasi atau tab, yaitu kasus Latin, atau jika kedua sisi adalah karakter yang bisa diputus CJK, yaitu kasus spasi merata. Ia menghitung batas-batas tersebut, membagi slack baris secara merata di antara mereka, dan menambahkan bagiannya ke setiap celah yang memenuhi syarat
Konsekuensinya muncul secara alami. Baris bahasa Inggris memiliki batas yang bisa diregangkan hanya pada spasi katanya, sehingga semua slack mendarat di sana dan kata-kata menyebar sementara huruf-huruf di dalam setiap kata mempertahankan spasi alaminya. Baris Han atau kana memiliki batas yang bisa diregangkan di antara hampir setiap pasangan glyph, sehingga slack terdistribusi merata di seluruh baris, persis seperti spasi antar glyph merata yang diperlukan oleh skrip-skrip tersebut. Baris yang merupakan satu kata Latin panjang tanpa spasi internal tidak memiliki batas yang bisa diregangkan sama sekali, sehingga HotPDF membiarkannya pada lebar alaminya daripada merobek kata huruf demi huruf. Logika yang sama menangani baris campuran Latin dan CJK dalam satu baris tanpa penanganan khusus, karena keputusannya bersifat lokal pada setiap batas
Satu batas dengan sengaja dikecualikan di mana pun. Posisi setelah glyph terakhir dari sebuah baris tidak pernah diperlakukan sebagai celah, karena meregangkannya di sana hanya akan memperkenalkan kembali sisa di sebelah kanan, yang merupakan kebalikan dari justifikasi
Mengapa baris terakhir dibiarkan sendiri
Baris terakhir paragraf bersifat istimewa, dan mendapatkannya salah adalah bug justifikasi yang paling umum. Baris terakhir paragraf biasanya pendek, sering hanya beberapa kata, dan meregangkannya ke lebar kolom penuh menyeret kata-kata itu ke seluruh halaman menjadi baris yang jarang dan rusak. Tipografi yang benar membiarkan baris terakhir pada lebar alaminya, sejajar dengan kiri
HotPDF mendeteksi baris trailing berdasarkan posisi. Saat membungkus teks menjadi baris-baris, ia tahu kapan baris yang baru dipotong mencapai akhir string yang diberikan. Baris terakhir itu dikeluarkan dengan perataan kiri biasa dan mempertahankan lebar alaminya. Setiap baris sebelumnya dijustifikasi ke kedua tepi. Pemisah baris keras yang Anda tulis dalam teks dihormati sesuai tulisan, sehingga baris pendek yang disengaja juga tidak pernah diregangkan. Pembaca melihat blok teks persegi panjang yang bersih yang baris terakhirnya berakhir secara alami, yang merupakan apa yang diharapkan mata
Biaya pengukuran yang membuat justifikasi lambat
Untuk menjustifikasi baris, Anda harus mengetahui lebarnya yang tepat, dan Anda harus mengetahui advance setiap glyph agar bisa menempatkan spasi ekstra dengan presisi. Implementasi pertama mendapatkan angka-angka tersebut dengan cara yang jelas. Ia mengukur seluruh baris dengan kueri lebar Unicode penuh, lalu mengukur setiap awalan untuk mendapatkan kembali advance setiap glyph dengan mengambil selisih. Untuk baris N glyph, itu adalah N+1 pemanggilan ke mesin pengukuran, dan setiap pemanggilan adalah round-trip GDI penuh, meminta sistem operasi untuk membentuk dan mengukur teks lalu mengembalikan jawabannya
Per baris itu terdengar murah. Di seluruh halaman tidak demikian. Ambil halaman A4 yang padat dengan teks isi, sekitar empat puluh lima baris dengan sekitar delapan puluh karakter masing-masing. Dengan N+1 round-trip per baris, itu sekitar 81 round-trip untuk setiap baris dan sekitar 3.645 untuk halaman tersebut, hampir semuanya dihabiskan untuk mengukur ulang teks yang sudah dilihat mesin sesaat sebelumnya. Pada pekerjaan batch yang menghasilkan ribuan halaman, overhead itu mendominasi waktu tata letak, dan setiap round-trip melewati batas antara proses Anda dan subsistem grafis
Satu pemanggilan sebagai pengganti N ditambah satu
Perbaikannya adalah jenis perubahan yang terlihat kecil dan menghasilkan keuntungan besar. GDI sudah bisa melaporkan total lebar string dan posisi setiap glyph dalam satu kueri. HotPDF mengekspos ini melalui GetWideCharAdvances, yang mengisi larik dengan advance alami setiap glyph, termasuk kerning, dan mengembalikan total lebar, dalam satu pemanggilan daripada N+1. Rutinitas justifikasi, secara internal _HPDFEmitJustifiedWideLine, meminta semua advance sekaligus, menghitung slack, mendistribusikannya ke batas yang bisa diregangkan, dan memancarkan baris tersebut
Untuk halaman A4 yang sama, pengukuran per baris turun dari sekitar 81 round-trip menjadi satu, sehingga halaman turun dari sekitar 3.645 round-trip menjadi sekitar 45, hampir pengurangan delapan puluh kali lipat. Keluarannya identik byte demi byte, karena tidak ada yang berubah dari pengukuran kecuali berapa kali diminta. Mesin GDI yang sama, metrik font yang sama, kerning yang sama menghasilkan angka yang sama. Hanya jumlah round-trip yang turun. Ketika pengukuran sudah benar, optimasi yang tepat adalah berhenti memintanya berulang kali, bukan mendekatinya
Cara baris mencapai halaman
Setelah slack dibagi, HotPDF memancarkan baris dengan ExtTextOut dan larik advance per glyph, yaitu larik Dx. Setiap entri adalah jarak dari origin satu glyph ke glyph berikutnya, yang merupakan advance alami glyph tersebut ditambah bagiannya dari slack ketika batas yang bisa diregangkan mengikutinya. Ini memetakan langsung ke model pencitraan PDF. Teks yang diposisikan ditulis dengan operator TJ, sebuah larik yang menyelingi run glyph dengan penyesuaian horizontal yang eksplisit, dan nilai Dx menjadi persis penyesuaian tersebut. Itulah mengapa spasi ekstra mendarat di antara glyph pada posisi sub-poin yang presisi daripada dipalsukan dengan karakter padding, dan mengapa baris HotPDF yang dijustifikasi terukur dengan benar jika alat hilir membacanya kembali
Anda tidak memanggil ExtTextOut sendiri untuk paragraf yang dijustifikasi. Titik masuknya adalah WideTextOutBox, yang membungkus string Unicode ke dalam kotak dan menerapkan perataan yang Anda minta. Ia membagi teks menjadi baris-baris yang sesuai dengan lebar kotak, menempatkan setiap baris ke bawah tinggi kotak, dan mengembalikan jumlah karakter yang berhasil dimuat sebelum kehabisan ruang vertikal. Perataan dipilih oleh enum justifikasi
type
THPDFJustificationType = (jtLeft, jtCenter, jtRight, jtJustify);
Tiga yang pertama adalah perataan kiri, tengah, dan kanan yang sudah jelas. Yang keempat, jtJustify, adalah justifikasi penuh kedua tepi yang dijelaskan di sini, dan ini adalah nilai yang dibaca WideTextOutBox untuk mengaktifkan spasi yang sadar skrip
Menjustifikasi paragraf dalam praktik
Contoh lengkap membuat dokumen, mengatur font, dan menuangkan paragraf ke dalam kotak dengan justifikasi penuh. Kode yang sama menjustifikasi teks Latin dan CJK tanpa perubahan flag, karena kesadaran skrip berada di bawah API
uses
HPDFDoc;
procedure JustifyParagraph;
var
Pdf: THotPDF;
Body: WideString;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'Justified.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', 11);
Body :=
'Full justification spreads the slack on each filled line so both ' +
'edges meet the column, while the last line keeps its natural width. ' +
'For scripts with word gaps the space lands between words; for ' +
'scripts without them it spreads evenly between glyphs.';
// X, Y, LineSpacing, BoxWidth, BoxHeight, Text, Align
Pdf.CurrentPage.WideTextOutBox(72, 72, 4, 380, 240, Body, jtJustify);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Untuk menggambar blok yang sama dengan perataan kiri, tengah, atau kanan, ubah hanya argumen terakhir menjadi jtLeft, jtCenter, atau jtRight. Pembungkusan, penempatan baris, dan nilai kembalian tetap sama. Lebar yang diukur yang menggerakkan keempat jalur berasal dari GetWideTextWidth, kueri lebar yang sadar Unicode yang mengukur WideString dengan benar di mana pengukuran berbasis byte lama akan salah mengukur apa pun di luar Latin-1, yang merupakan alasan kotak membungkus teks CJK dan pasangan surrogate di tempat yang tepat sejak awal
Justifikasi adalah satu lapisan dari tumpukan pembentukan teks yang lebih besar. Ketika baris berisi skrip yang menyusun ulang atau menggabungkan glyph-nya, keputusan spasi di sini berada di atas pekerjaan yang dijelaskan dalam artikel kami tentang pembentukan teks skrip kompleks, dan ketika font membawa varian tipografi yang ingin Anda pilih, lihat cara menggerakkan alternatif stilistika OpenType GSUB. Semuanya tersedia dalam HotPDF Component untuk Delphi dan C++Builder, bersama API teks, tata letak, dan dokumen yang lebih luas yang dibahas di seluruh blog ini