Artikel Teknis

Sematkan Font yang Hilang di PDF yang Ada untuk PDF/A di Delphi

losLab PDF Library dapat menyematkan program font yang hilang dari PDF yang sudah dimuat dengan sekali panggilan: EmbedMissingFonts menelusuri setiap kamus font di dalam dokumen, menemukan font sistem terpasang yang cocok berdasarkan nama BaseFont-nya, dan menulis kembali program font tersebut ke dalam file. Bagi tim yang memperbaiki dokumen pihak ketiga yang gagal dalam validasi PDF/A pada penyematan font, ini adalah perbaikan yang membuat kesalahan preflight 00030 hilang

Skenario ini sangat umum terjadi. Alur penyerapan arsip menerima PDF dari pemasok, pelanggan, atau biro pemindaian; dokumen tersebut merender dengan baik di setiap meja di gedung; lalu validator PDF/A menolak seluruh tumpukan dokumen tersebut dengan keluhan yang sama yang diulang sekali per file: setidaknya satu font tidak disematkan. Tidak ada pihak hulu yang akan membuat ulang file tersebut, sehingga alur kerja harus memperbaikinya. Artikel ini membahas jalur perbaikan tersebut. Ini adalah pendamping dari artikel preflight, yang membahas pendeteksian pelanggaran PDF/A and PDF/UA: bagian tersebut memberi tahu Anda dokumen mana yang rusak, artikel ini memperbaiki cara paling sering dokumen tersebut rusak

Mengapa PDF/A mewajibkan setiap font untuk disematkan?

ISO 19005-1 §6.3.4 mewajibkan setiap font yang digunakan oleh dokumen yang sesuai untuk membawa program font-nya di dalam file, karena seluruh janji PDF/A adalah keterulangan (reproducibility): dokumen harus merender secara identik pada mesin lima puluh tahun dari sekarang yang tidak berbagi font dengan mesin yang memproduksinya. Font yang tidak disematkan adalah instruksi untuk mencari Arial di suatu tempat pada sistem penampil, dan posisi standar menyatakan bahwa "di suatu tempat pada sistem penampil" bukanlah jaminan kearsipan. Apapun glif, metrik, dan cakupan yang dimiliki font pengganti, itulah yang didapatkan oleh pembaca, dan itu mungkin bukan apa yang dilihat oleh penulis

Penyebab historisnya adalah konvensi Standard 14. PDF 1.0 menjanjikan bahwa setiap penampil menyertakan Helvetica, Times, Courier, Symbol, dan ZapfDingbats, so generator belajar untuk mereferensikan font tersebut berdasarkan nama dan tidak menyematkan apa pun, dan perkakas selama tiga puluh tahun masih melakukan hal yang persis sama. losLab PDF Library menanggapi persyaratan ini dengan cukup serius sehingga dalam mode pembuatan PDF/A AddStandardFont sengaja dijadikan no-op: pustaka tidak menyertakan program font Standard 14, tidak dapat menyematkan apa yang tidak dimilikinya, dan menolak menulis referensi yang tidak disematkan ke dalam dokumen yang mengklaim kepatuhan. Ini mengembalikan nilai 0 tanpa memilih font, sehingga dokumen PDF/A harus menggunakan AddTrueTypeFont dengan penyematan sebagai gantinya, dan setiap permintaan Embed=0 secara diam-diam ditingkatkan menjadi Embed=1 saat mode PDF/A aktif. Itu adalah sisi penulis. Masalah yang lebih sulit adalah sisi pembaca: dokumen yang sudah ditulis orang lain, penuh dengan kamus font yang tidak Anda buat

Bagaimana EmbedMissingFonts memperbaiki dokumen yang dimuat?

losLab PDF Library memperbaiki font di tempat daripada membangunnya kembali. Ketika generator PDF menulis font TrueType yang tidak disematkan, kamus FontDescriptor yang dihasilkannya sudah lengkap: FontName, FontBBox, Flags, Ascent, Descent, StemV, semuanya ada. Satu-satunya hal yang membedakannya dari font yang disematkan adalah tidak adanya satu entri, yaitu referensi aliran /FontFile2 yang menampung program font yang sebenarnya. So EmbedMissingFonts tidak menyentuh kamus font, pengodean, larik lebar (widths array), atau aliran konten apa pun yang mereferensikan font berdasarkan nama sumber daya. Ini membaca program font yang cocok dari sistem, mengompresnya ke dalam objek aliran baru, dan menambahkan satu referensi /FontFile2 (atau /FontFile3 untuk font CIDFontType0) ke FontDescriptor yang sudah ada di sana. Semua yang ditunjuk oleh halaman dokumen tetap persis di tempatnya semula, yang membuat operasi ini aman untuk dijalankan pada file yang tidak Anda kendalikan

Cakupannya mencakup kedua arsitektur font yang akan Anda temui dalam praktiknya: font TrueType sederhana dan font Type0/CID komposit, jenis yang diproduksi untuk teks CJK dan output Unicode modern. Penelusuran ini sengaja menghitung setiap kamus Font di pohon objek dokumen daripada mengandalkan penelusuran sumber daya halaman demi halaman, sehingga font yang direferensikan dari anotasi atau dibagikan di seluruh halaman juga ikut diambil. API-nya adalah panggilan tunggal pada dokumen yang dimuat

var
  PDF: TPDFlib;
  Repaired: Integer;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('supplier-invoice.pdf', '') <> 1 then
      raise Exception.Create('Could not load PDF');

    // Menelusuri setiap kamus Font; mengembalikan berapa banyak font
    // yang mendapatkan program font. Font yang programnya tidak dapat
    // ditemukan di sistem akan dilewati, bukan gagal.
    Repaired := PDF.EmbedMissingFonts;
    Writeln(Format('%d font program(s) embedded', [Repaired]));

    PDF.SaveToFile('supplier-invoice-repaired.pdf');
  finally
    PDF.Free;
  end;
end;

Satu detail yang perlu diketahui karena menjelaskan mengapa pencocokan nama berfungsi lebih baik daripada perbandingan string sederhana: pustaka menormalkan nama BaseFont sebelum mencarinya. Awalan subset (pola ABCDEF+ dari enam huruf besar dan tanda plus) dilucuti, akhiran gaya PostScript seperti ArialMT diselesaikan menjadi Arial, dan file TrueType Collection dideteksi serta dibongkar sehingga jenis huruf yang ada di dalam .ttc tetap tersemat dengan benar

Memverifikasi perbaikan dengan laporan preflight

CreatePreflightReport adalah langkah verifikasi, dan perulangannya sengaja ditutup: audit yang sama yang menyatakan file tersebut rusak harus menjadi audit yang membersihkannya. Kode kesalahan 00030 adalah temuan audit mendalam PDF/A yang berbunyi "At least one font is not embedded (FontFile/FontFile2/FontFile3 missing)", dan ini dilaporkan terhadap file secara keseluruhan, sehingga satu font saja yang terlewat akan membuatnya tetap aktif. Jalankan laporan pada file sumber, perbaiki, simpan, dan jalankan lagi pada output

function HasFontEmbeddingViolation(PDF: TPDFlib;
  const FileName: string): Boolean;
var
  Report: string;
begin
  // ComplianceTests = 1 memilih pemeriksaan PDF/A
  Report := PDF.CreatePreflightReport(FileName, '', 1, 0);
  Result := Pos('00030', Report) > 0;
end;

Untuk tampilan per-font daripada keputusan per-file, muat lagi dokumen yang diperbaiki dan hitung: FindFonts diikuti oleh SelectFont dan GetFontIsEmbedded melaporkan status penyematan font demi font, yang merupakan alat yang tepat ketika pekerjaan batch perlu mencatat dengan tepat jenis huruf mana di file mana yang tidak dapat diperbaiki. Pola enumerasi yang sama muncul di artikel tentang mengekstrak teks, gambar, dan font dari PDF yang dimuat, yang mana ini mendukung ekstraksi alih-alih perbaikan

Apa yang terjadi jika font tidak terpasang di sistem?

EmbedMissingFonts melewati font apa pun yang programnya tidak dapat ditemukan, dan melaporkan tindakan melewati tersebut melalui nilai kembaliannya: jika jumlah yang dikembalikan lebih rendah dari jumlah font tidak tersemat yang Anda hitung, selisihnya adalah font yang tidak dimiliki sistem. Ini adalah mode kegagalan yang jujur, dan lebih baik daripada alternatifnya, karena menciptakan program pengganti untuk font yang dinamai dalam dokumen akan mengubah perenderan, yang merupakan hal yang tidak boleh dilakukan oleh perbaikan kearsipan. Untuk kasus seperti ini, losLab PDF Library menyediakan EmbedFontProgramFromFile, yang menyematkan .ttf or .otf yang disediakan pemanggil ke dalam font bernama, sehingga alur kerja dapat mengirimkan font korporat yang diperkirakan akan ditemui dan beralih ke font tersebut dengan sengaja

var
  I, FontID: Integer;
begin
  PDF.FindFonts;
  for I := 1 to PDF.FontCount do
  begin
    FontID := PDF.GetFontID(I);
    if (FontID > 0) and (PDF.SelectFont(FontID) = 1) then
      if PDF.GetFontIsEmbedded = 0 then
        // Coba font sistem yang terpasang terlebih dahulu, lalu beralih
        // ke file font yang dikirimkan bersama aplikasi
        if PDF.EmbedFontProgram(PDF.FontName) = 0 then
          PDF.EmbedFontProgramFromFile(PDF.FontName,
            'fonts\CorporateSans.ttf');
  end;
end;

Dua batasan perlu dinyatakan dengan jelas. Pertama, font Type1 tidak diperbaiki dalam implementasi saat ini: entri /FontFile mereka memerlukan struktur PFB tiga segmen dengan kunci panjang eksplisit, dan pustaka melewatkannya alih-alih menulis aliran yang cacat; mereka jarang ditemukan dalam dokumen modern tetapi mereka muncul di arsip lama. Kedua, menyematkan font adalah tindakan lisensi. Izin penyematan font TrueType milik perancang font-nya (foundry), dan alur perbaikan yang memasukkan program font berlisensi ke dalam dokumen yang keluar dari organisasi harus memastikan bahwa lisensi font tersebut benar-benar mengizinkannya. Pustaka akan melakukan apa yang Anda minta; apakah Anda boleh memintanya adalah pertanyaan untuk departemen hukum Anda, bukan kompiler Anda

Penyematan itu perlu, tetapi tidak cukup

Memperbaiki font hanya membersihkan kesalahan 00030, dan tidak ada yang lain. Dokumen yang gagal PDF/A pada enkripsi, pada metadata XMP yang hilang, pada ruang warna yang bergantung pada perangkat tanpa OutputIntent, atau pada peta ToUnicode yang tidak ada akan tetap gagal setelah setiap font disematkan, itulah sebabnya perbaikan ini harus berada di dalam perulangan yang digerakkan oleh preflight alih-alih menggantikannya. Jalankan laporan lengkap, perbaiki apa yang disebutkannya, dan biarkan laporan memberi tahu Anda saat Anda selesai. Ada juga dimensi biaya: program font CJK lengkap dapat berukuran megabita, sehingga menyematkan beberapa di antaranya dapat menggembungkan dokumen kecil secara dramatis. Penyeimbangnya adalah subsetting, yang dibahas dalam artikel tentang optimalisasi ukuran file PDF dan subsetting font, yang memotong setiap program yang disematkan menjadi hanya glif yang sebenarnya dirender oleh dokumen

Menjaga dokumen baru agar tidak mengalami kemunduran

SetEmbedAllFonts adalah bagian pencegahan dari fitur yang sama: pelindung sisi penulis yang menghentikan kode Anda sendiri memproduksi dokumen yang diperbaiki oleh artikel ini. Dengan mengaktifkan SetEmbedAllFonts(1), setiap panggilan AddTrueTypeFont berikutnya yang meminta Embed=0 akan ditingkatkan menjadi referensi tersemat, yang memperluas jaminan yang sudah diberlakukan oleh mode PDF/A ke setiap dokumen. Ini memengaruhi font yang ditambahkan setelah panggilan, bukan font yang sudah ada di file yang dimuat, sehingga pembagian kerjanya bersih: SetEmbedAllFonts untuk dokumen yang Anda buat, EmbedMissingFonts untuk dokumen yang Anda warisi

PDF.NewDocument;
PDF.SetEmbedAllFonts(1);
// Mulai sekarang, AddTrueTypeFont(Name, 0) berperilaku
// seperti AddTrueTypeFont(Name, 1): tidak ada referensi
// tidak tersemat yang dapat masuk ke file output

Kedua bagian, pelindung sisi penulis dan jalur muat-perbaiki-simpan, adalah bagian dari losLab PDF Library untuk Delphi, C# dan VB.NET, bersama dengan mesin preflight yang memverifikasi hasilnya; halaman produk menyertakan referensi API font lengkap termasuk panggilan penyematan dan subsetting per font