Artikel Teknis

HotPDF CompressDocument: Font Subset Kompak di Delphi

HotPDF THotPDF.CompressDocument adalah satu saklar yang membuat BeginDoc menghasilkan PDF lossless terkecil yang sanggup ditulis komponen ini: FlateDecode di level maksimum, cross-reference stream dengan object stream, font subsetting, dan font subset kompak yang menomori ulang glyph yang disimpan di balik /CIDToGIDMap eksplisit. EndDoc lalu mengembalikan setelan Anda sendiri. Dokumen uji tiga halaman Arial dan SimSun turun dari 10,2 MB menjadi 20 KB dengan rendering yang identik

Apa saja yang benar-benar dinyalakan CompressDocument?

CompressDocument menimpa enam setelan writer, plus batas object stream, untuk satu dokumen dan mengembalikan semuanya setelahnya. Saat BeginDoc, sebelum versi PDF dikunci, HotPDF mencatat nilai Anda dan menyetel Compression ke cmFlateDecode, CompressionLevel ke clMaximum, menyalakan EnableFontSubsetting dan CompactFontSubsetting, serta mengaktifkan UseXRefStream plus UseObjectStreams (ISO 32000-1 §7.5.7 dan §7.5.8). Object stream butuh PDF 1.5, jadi Version yang lebih tua dinaikkan ke 1.5 selama tidak dikunci. PDF/A-1 melarang kedua struktur itu, jadi dokumen PDF/A-1 mempertahankan cross-reference table klasiknya dan hanya mendapat pekerjaan Flate dan font. Gambar dibiarkan persis seperti saat Anda menempelkannya

Diagram siklus hidup CompressDocument HotPDF di Delphi: BeginDoc mencatat nilai milik writer sendiri, menimpa enam setelan termasuk Compression dan UseObjectStreams untuk satu dokumen, dan EndDoc mengembalikan setiap nilai pinjaman di finally terluarnya sementara properti CompressDocument sendiri tetap True
Enam setelan writer dan batas object stream dipinjam untuk tepat satu dokumen dan dikembalikan saat EndDoc berjalan, sehingga report yang gagal tak pernah meninggalkan komponen terjebak di kompresi maksimum
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.AutoLaunch := False;
    Pdf.FileName := 'invoice-2026-1042.pdf';
    Pdf.CompressDocument := True;    // diterapkan BeginDoc, dibatalkan EndDoc
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 12);
    Pdf.CurrentPage.TextOut(40, 40, 0, 'Invoice 2026-1042');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Pemulihannya terjadi di finally terluar milik EndDoc, jadi exception di tengah report tidak meninggalkan komponen berumur panjang terjebak di kompresi maksimum untuk job berikutnya. Properti CompressDocument sendiri tetap True; hanya enam setelan yang dipinjamnya yang kembali. Versinya ditangani dengan lebih hati-hati. HotPDF membatalkan kenaikan 1.5 miliknya sendiri hanya kalau dokumen masih berakhir di 1.5, jadi ketika fitur lain mendorong file ke 1.6 selama proses (misalnya font OpenType yang ditanam), versi yang lebih tinggi bertahan, persis seperti yang terjadi tanpa kompresi

Kenapa font subset tetap besar tanpa pemadatan?

Subset TrueType klasik membuang outline yang tak pernah Anda gambar tapi menyimpan setiap glyph ID di posisinya, dan penomoran itulah yang membuatnya berat. Content stream menampilkan CID yang sama dengan GID aslinya, jadi subset harus menyimpan satu offset loca dan satu entri hmtx untuk setiap slot sampai glyph tertinggi yang ia simpan, kosong atau tidak. Untuk huruf Latin beban itu tak terasa. Untuk huruf CJK seperti SimSun, yang ideografnya duduk jauh di dalam tabel glyph yang sangat besar, dua karakter Mandarin menyeret tabel-tabel yang diukur untuk seluruh font. Aturan closure subset font untuk glyph hasil shaping menentukan glyph mana yang selamat; pemadatan soal berapa biaya para penyelamat itu

CompactFontSubsetting menomori ulang glyph yang disimpan menjadi rentang rapat mulai dari nol dan menulis stream /CIDToGIDMap pada CIDFont, yang didefinisikan ISO 32000-1 §9.7.4.2 sebagai tabel GID dua byte yang diindeks CID. Tabel itulah seluruh triknya. Content stream, array lebar /W, dan CMap ToUnicode semuanya mempertahankan CID asli, jadi tak ada yang sudah tertulis yang harus berubah; hanya lookup dari CID ke glyph yang pindah ke map. Di test yang memotivasi fitur ini, SimSun dengan dua karakter turun dari 24,8 KB data font menjadi 3,1 KB

Perbandingan font subset HotPDF yang jarang, yang mempertahankan entri loca dan hmtx untuk setiap glyph ID asli sampai GID tertinggi yang disimpan, dengan output CompactFontSubsetting yang menomori ulang glyph tersimpan secara rapat dari nol dan memetakan CID lewat stream CIDToGIDMap sementara content stream, /W dan ToUnicode tak berubah
Penomoran ulang memindahkan biayanya keluar dari font program ke dalam satu map stream kecil — dua karakter SimSun turun dari 24,8 KB ke 3,1 KB tanpa menyentuh satu byte pun konten yang sudah tertulis

Pemadatan punya batas tegas, dan ia menurun kualitasnya secara senyap alih-alih gagal. HotPDF membangun subset kompak hanya untuk face TrueType Type 0, baik yang disetel lewat SetFont dengan subsetting menyala maupun face yang didaftarkan lewat RegisterUnicodeTTF. Font TrueType sederhana menemukan glyph-nya lewat cmap di dalam font program, yang akan rusak oleh penomoran ulang, jadi ia mempertahankan subset jarangnya. Face OpenType-CFF juga tak punya jalur kompak. Build kompak yang gagal jatuh kembali ke subset jarang alih-alih raise. Propertinya mati secara default, jadi output yang sudah ada tetap identik byte-nya, sementara di bawah PDF/A face Unicode yang terdaftar selalu mendapat subset kompak

Pdf.EnableFontSubsetting := True;
Pdf.CompactFontSubsetting := True;   // bisa dipakai tanpa CompressDocument
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('SimSun', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, WideString('Total: '#$4E2D#$6587));
Pdf.EndDoc;

Bagaimana writer terpadatkan memeras struktur file?

Begitu font dan stream kecil, dictionary dan data cross-reference menjadi biaya tersisa terbesar, jadi object-stream writer di balik CompressDocument memangkas itu juga. Panduan object stream dan update inkremental membahas format kontainernya sendiri; jalur kompresi menambah empat penyempurnaan di atasnya:

  • Sintaks kompak menurut ISO 32000-1 §7.2.2: spasi hanya ditulis di antara dua token yang kalau tidak akan melekat sebagai karakter biasa, sehingga /Type /Page menjadi /Type/Page
  • Field cross-reference stream memakai lebar apa pun yang diizinkan §7.5.8.2, sehingga file di bawah 16 MB menyimpan setiap offset dalam 3 byte alih-alih 4
  • Hingga 250 objek masuk ke setiap object stream alih-alih 100 seperti biasanya, kecuali Anda menyetel batas sendiri lewat ConfigureAdaptiveObjectStreamPacking
  • Ketika file tak terenkripsi, Catalog dan dictionary Info juga dipadatkan ke object stream; output terenkripsi mempertahankannya di level teratas

Sintaks kompak datang dengan jebakan yang layak diketahui kalau Anda memperluas writer. Signing mengisi signature setelah file tertulis dengan mencari byte untuk placeholder literal /ByteRange ( dan /Contents <, dan ejaan kompak akan mengubahnya menjadi /ByteRange( dan /Contents<, yang tak pernah ditemukan pencarian itu. Dictionary signature (Type Sig atau DocTimeStamp, FT Sig) dan dictionary enkripsi karena itu mempertahankan tata letak berspasi. Cacat terkait mengenai build sebelum v2.766.41: setiap save object stream, termasuk CompressDocument, diawali dua baris header %PDF-, jadi upgrade kalau validator ketat menandai output Anda

Bisakah PDF yang sudah termuat ikut dikompresi?

Bisa, lewat overload options CompressLoadedDocument(Options, Info), yang menjalankan langkah lossless yang sama pada file yang sudah ada. Dengan THPDFLoadedDocumentCompressionOptions.Default ia menghapus resource halaman tak terpakai, menggabungkan font dan form yang identik, melakukan subsetting pada font tertanam dengan subset kompak menyala, mengompresi ulang stream tanpa filter, Flate, LZW, ASCII, dan RunLength dengan Flate saat hasilnya lebih kecil, dan membuat save berikutnya memakai object stream. HighRatioFlate mati secara default, dan object stream dilewati untuk PDF/A-1 dan save inkremental. Overload CompressLoadedDocument tanpa parameter adalah panggilan lama yang lebih sempit, yang hanya mengompresi Flate stream yang belum terkompresi

Alur CompressLoadedDocument HotPDF di Delphi: panggilan menghapus resource halaman tak terpakai, menggabungkan font dan form identik, melakukan subsetting font tertanam dengan subset kompak, mengompresi ulang stream dengan Flate hanya saat hasilnya lebih kecil, dan menyalakan object stream untuk save berikutnya, sementara field signature memicu RefusedBySignaturePolicy dan membiarkan file tak tersentuh
Setiap langkah menulis ulang byte yang dicakup signature, jadi seluruh dokumen ditolak kecuali Anda secara eksplisit mengizinkan invalidasi — Info.BytesSaved lalu menjumlahkan hanya pekerjaan resource, font, dan stream
var
  Doc: THotPDF;
  Options: THPDFLoadedDocumentCompressionOptions;
  Info: THPDFLoadedDocumentCompressionInfo;
begin
  Doc := THotPDF.Create(nil);
  try
    Doc.AutoLaunch := False;
    Doc.LoadFromFile('quarterly-report.pdf');
    Options := THPDFLoadedDocumentCompressionOptions.Default;
    Doc.CompressLoadedDocument(Options, Info);
    if Info.RefusedBySignaturePolicy then
      Writeln(Format('Left untouched: %d signature fields', [Info.SignatureCount]))
    else
    begin
      Writeln(Format('Compact fonts: %d, stream bytes saved: %d',
        [Info.Fonts.CompactSubsetFontCount, Info.BytesSaved]));
      Doc.SaveLoadedDocument('quarterly-report-compact.pdf');
    end;
  finally
    Doc.Free;
  end;
end;

Dua batas penting di jalur termuat. Setiap langkah menulis ulang byte yang dicakup signature, jadi dokumen dengan field signature ditolak secara utuh: panggilan mengembalikan 0, menyetel RefusedBySignaturePolicy, dan tak mengubah apa pun, kecuali Anda menyetel AllowSignatureInvalidation, setelah itu Info.SignaturesInvalidated memberi tahu apa yang Anda korbankan. Pemadatan juga lebih konservatif di sini daripada di jalur pembuatan. HotPDF memadatkan hanya font program yang dipakai semata-mata oleh font CIDFontType2 dengan /CIDToGIDMap Identity, tempat CID sama dengan GID, dan melewati program dengan map stream yang sudah ada, sebuah /CIDSet, atau tabel glyph warna seperti COLR, sbix, CBDT, atau SVG, karena pembangunan ulang kompak akan membuang lapisan warnanya. Perhatikan juga Info.BytesSaved hanya menjumlahkan langkah resource, font, dan stream; keuntungan object stream muncul saat file ditulis

Hasil seperti apa yang layak Anda harapkan dalam praktik?

Keuntungannya mengikuti seberapa besar bagian file yang berupa struktur tak terkompresi dan data font yang kebesaran, bukan berapa banyak halamannya. Sampel tiga halaman Arial dan SimSun menyusut dari 10,2 MB menjadi 20 KB saat dibuat dengan CompressDocument, dan dari 10,2 MB menjadi 19,8 KB ketika asli yang belum terkompresi dimuat dan dijalankan lewat CompressLoadedDocument, dengan rendering identik di kedua cara. PDF yang sudah kompak nyaris tak bergeser: di set regresi, file semacam itu hemat dalam rentang -0,07% sampai +0,06% dari ukuran aslinya. File kaya foto untung sedikit, karena tak satu pun jalur menyentuh data gambar

Kalau Anda membuat laporan CJK yang sama setiap malam, pasangkan subset kompak dengan cache subset font persisten di disk supaya pekerjaan subsetting tak diulang setiap run, dan bandingkan output terkompresi menurut konten objek alih-alih byte, karena satu field yang berubah me-Flate ulang satu object stream utuh. Referensi properti dan record lengkap ada di halaman produk HotPDF Delphi PDF component