Shaping teks di komponen PDFium melewati satu objek yang dapat dipasang. ConfigureTextShaper memasang shaper yang dilalui setiap entry point shaping, mengganti dan membebaskan apa pun yang ada di sana; ActiveTextShaper mengembalikan yang terpasang dan membuat default platform pada pemakaian pertama; ActiveTextShaperName melaporkan backend mana yang hidup; ClearTextShaper menjatuhkan pemasangannya dan membiarkan default dibuat lagi. Di Windows default-nya adalah TPdfUniscribeTextShaper. Di bawah Free Pascal ada TPdfHarfBuzzTextShaper, yang mengikat libharfbuzz pada run time sehingga pustaka yang hilang adalah kondisi yang dilaporkan alih-alih kegagalan muat
Satu interface, dua backend yang membagi pekerjaan dengan cara yang sama sekali berbeda. Memahami asimetri itulah yang menghentikan jalur portabel menghasilkan teks yang ter-shaping dengan benar tetapi diposisikan dengan keliru
Mengapa backend Windows satu kelas dan yang portabel tiga bagian?
Karena Uniscribe adalah empat API yang berpura-pura menjadi satu. ScriptItemize mensegmentasi string berdasarkan script dan menyelesaikan level bidireksional; ScriptShape memetakan karakter ke glyph; ScriptPlace menghitung advance dan offset; ScriptLayout menata run yang dihasilkan ke urutan visual. Backend yang dibangun di atasnya karenanya tidak punya apa pun lagi untuk ditambahkan, dan itulah mengapa shaper Windows adalah satu kelas dengan satu method
HarfBuzz mencakup dua di tengah. Ia men-shaping dan menempatkan run yang arah dan script-nya sudah diputuskan caller, dan ia tidak punya pendapat tentang bagaimana paragraf terbelah menjadi run atau urutan apa run-run itu muncul. Jadi backend portabel menyediakan sisanya: algoritma bidireksional menyelesaikan embedding level, fungsi Unicode HarfBuzz mensegmentasi teks berdasarkan script, dan run-run ditata dalam urutan visual yang dihasilkan aturan L2 UAX #9. Separuh bidireksional itu cukup substansial untuk menjadi unitnya sendiri, dijelaskan dalam artikel embedding level UAX #9
Shaper tidak menyelesaikan font, dan itu disengaja
Uniscribe membaca binary font dari sebuah device context GDI. Tidak ada padanan portabel untuk itu, dan mengarang satu di dalam unit shaping berarti memutuskan, mewakili setiap aplikasi, apakah font datang dari fontconfig, dari CoreText, dari folder font aplikasi, atau dari database. Jadi backend HarfBuzz menerima resolver: callback yang memetakan nama font ke byte TrueType atau OpenType. Mengembalikan False menggagalkan permintaan shaping dengan cara yang sama seperti font GDI yang tak terbaca menggagalkannya di Windows
uses
FPdfTextShaping
{$IFDEF FPC}
, FPdfTextShapingHb
{$ENDIF}
;
function TFontCatalogue.Resolve(const FontName: WideString;
out FontData: TBytes): Boolean;
var
Path: string;
begin
// Kebijakan Anda: fontconfig, CoreText, folder font aplikasi, database
Result := FLookup.TryGetValue(LowerCase(FontName), Path);
if Result then
FontData := TFile.ReadAllBytes(Path);
end;
procedure InstallShaper(Catalogue: TFontCatalogue);
begin
{$IFDEF FPC}
// Kepemilikan berpindah ke unit; panggil sekali saat start-up,
// sebelum ada yang men-shaping teks
ConfigureTextShaper(TPdfHarfBuzzTextShaper.Create(Catalogue.Resolve));
{$ENDIF}
// Di Delphi default platform (Uniscribe) dibuat sesuai permintaan,
// sehingga tidak perlu pemasangan sama sekali
LogInfo('shaping backend: ' + ActiveTextShaperName);
end;
Menjaga penemuan font di luar shaper punya manfaat kedua yang muncul di server: proses yang sama bisa men-shaping dengan set font tertanam yang tidak ada sangkut pindanya dengan apa yang terpasang di mesin, yang Anda inginkan ketika output harus dapat direproduksi byte lintas host. Komponen juga memaparkan provider font sistem host untuk kasus-kasus di mana Anda memang menginginkan font terpasang, dibahas dalam artikel provider font sistem
Record hasil bersifat netral-backend, dan cluster adalah alasannya
Kedua backend mengisi TPdfShapedText yang sama: teks sumber, nama font, ukuran, byte font, array run, lebar total, jumlah glyph, dan jumlah karakter logis. Setiap TPdfShapedRun membawa rentangnya di teks sumber, posisi X visualnya, lebarnya, level bidireksionalnya, dan flag kanan-ke-kiri, plus glyph-glyphnya. Setiap TPdfShapedGlyph membawa identifier glyph, advance, offset X dan Y, dan cluster tempatnya berada sebagai awal dan panjang di teks sumber
Field cluster itulah yang membuat record dapat dipakai, bukan sekadar informatif. Shaping bukan pemetaan satu-ke-satu: satu suku kata Devanagari menjadi satu glyph dari empat karakter, satu ligatur Arab menggabungkan dua, dan satu karakter bisa menghasilkan beberapa tanda. Tanpa rentang cluster Anda tidak bisa menempatkan caret, hit-test sebuah klik, atau menyorot seleksi, karena Anda tidak bisa mengatakan glyph itu milik karakter mana. Dengan itu, aritmetikanya lokal dan kode yang sama bekerja untuk kedua backend
var
Shaped: TPdfShapedText;
R, G: Integer;
begin
if ShapePdfText(Line, 'Noto Sans Arabic', 14, ptdAuto, Shaped) then
for R := 0 to High(Shaped.Runs) do
begin
// Run sudah datang dalam urutan visual dengan VisualX terisi
X := Shaped.Runs[R].VisualX;
for G := 0 to High(Shaped.Runs[R].Glyphs) do
begin
EmitGlyph(Shaped.Runs[R].Glyphs[G].GlyphID,
X + Shaped.Runs[R].Glyphs[G].OffsetX,
Shaped.Runs[R].Glyphs[G].OffsetY);
X := X + Shaped.Runs[R].Glyphs[G].Advance;
end;
end;
end;
Budget berada di record options
TPdfTextShapingOptions membawa arah plus tiga batas: karakter maksimum, glyph maksimum, dan run maksimum, dengan class function Default yang mengisi nilai masuk akal. Batas-batas itu bukan paranoia tentang input cacat; mereka aritmetika. Shaping mengembang: font dengan substitusi kontekstual yang agresif bisa memancarkan lebih banyak glyph daripada karakter input, dan paragraf yang berganti script setiap beberapa karakter menghasilkan satu run per pergantian. Dokumen yang dirakit untuk memaksimalkan keduanya mengubah string sederhana menjadi alokasi besar, dan layanan yang men-shaping teks dari PDF tak tepercaya membutuhkan batas yang ia pilih alih-alih batas yang dipaksakan mesin
Menetapkan arah secara eksplisit alih-alih membiarkannya otomatis layak dilakukan kapan pun Anda sudah mengetahuinya. Otomatis menerapkan aturan arah paragraf untuk menebak dari karakter kuat pertama, yang benar untuk teks bebas dan keliru untuk field form yang arahnya properti field, bukan properti nilai yang diketik seseorang ke dalamnya
Binding run time, bukan dependensi build
Backend HarfBuzz memuat pustaka secara dinamis. Itu keputusan deployment dengan konsekuensi nyata: satu binary berjalan di mesin dengan HarfBuzz dan di mesin tanpa itu, melaporkan kemampuan yang berkurang pada kasus kedua alih-alih gagal memulai. Untuk pustaka yang dikirim ke developer lain itu susunan satu-satunya yang bisa dikerjakan, karena Anda tidak bisa mensyaratkan setiap konsumen komponen PDF mengakuisisi dan mencocokkan versi pustaka shaping yang mungkin tidak mereka butuhkan
Aturan yang bersesuaian bagi caller adalah memeriksa. ActiveTextShaper mengembalikan nil ketika platform tidak punya default dan tidak ada yang dikonfigurasi, dan entry point shaping melaporkannya sebagai shaper tidak tersedia alih-alih kegagalan shaping. Itu masalah yang berbeda dan layak pesan berbeda: satu adalah celah deployment, yang lain masalah font atau teks
Pasang sekali, sebelum ada yang men-shaping
Pemasangan mengganti dan membebaskan shaper sebelumnya, sehingga memanggilnya berulang kali aman tetapi tidak bermakna, dan memanggilnya saat thread lain sedang men-shaping sama sekali tidak aman. Lakukan saat start-up. Jika Anda perlu jatuh kembali ke default platform nanti, berikan nil, yang juga cara Anda membatalkan test double di akhir uji
Setelah backend terpasang, pengukuran dan pembungkusan berperilaku sama di kedua platform, karena keduanya mengonsumsi metrik run dan glyph alih-alih memanggil platform langsung; model pembungkusannya dijelaskan dalam artikel pengukuran teks dan word wrap. Platform dan toolchain yang didukung untuk komponen tercantum di halaman produk PDFium Delphi component