Teknik Makale

HotPDF CompressDocument: Delphi'de kompakt font alt kümeleri

HotPDFin THotPDF.CompressDocumentı, BeginDocin bileşenin yazabileceği en küçük kayıpsız PDFi üretmesini sağlayan tek anahtardır: maksimum seviyede FlateDecode, object streamli bir cross-reference stream, font subsetting ve saklanan glyphleri açık bir /CIDToGIDMapin arkasında yeniden numaralandıran kompakt font alt kümeleri. EndDoc sonra kendi ayarlarınızı geri koyar. Üç sayfalık Arial ve SimSun test belgesi, birebir aynı render ile 10.2 MBden 20 KBya indi

CompressDocument aslında neleri açar?

CompressDocument, tek bir belge için altı yazıcı ayarını ve object-stream tavanını geçersiz kılar, sonra hepsini eski hâline getirir. PDF sürümü belirlenmeden önce, BeginDocta HotPDF sizin değerlerinizi kaydeder ve Compressionı cmFlateDecodee, CompressionLevelı clMaximuma set eder, EnableFontSubsetting ile CompactFontSubsettingü açar ve UseXRefStream ile UseObjectStreamsu etkinleştirir (ISO 32000-1 §7.5.7 ve §7.5.8). Object streamler PDF 1.5 ister; kilitli olmayan daha eski bir Version 1.5e çekilir. PDF/A-1 iki yapıyı da yasaklar; bu yüzden PDF/A-1 belgesi klasik cross-reference tablosunu korur ve yalnızca Flate ile font işini alır. İmagelere, siz onları gömerken bıraktığınız hâliyle dokunulmaz

Delphide HotPDF CompressDocument yaşam döngüsü şeması: BeginDoc yazıcının kendi değerlerini kaydeder, tek belge için Compression ile UseObjectStreams dahil altı ayarı geçersiz kılar ve EndDoc, CompressDocument propertysi kendisi True kalırken ödünç alınan her değeri en dış finallyde geri verir
Altı yazıcı ayarı ve object-stream tavanı tam olarak tek bir belge için ödünç alınır ve EndDoc çalışınca geri verilir; böylece başarısız bir rapor bileşeni maksimum sıkıştırmada sıkışıp bırakmaz
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.AutoLaunch := False;
    Pdf.FileName := 'invoice-2026-1042.pdf';
    Pdf.CompressDocument := True;    // BeginDoc uygular, EndDoc geri alır
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 12);
    Pdf.CurrentPage.TextOut(40, 40, 0, 'Invoice 2026-1042');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Geri alma, EndDocun en dış finallysinde olur; böylece bir raporun yarısında patlayan istisna, uzun ömürlü bir bileşeni sonraki iş için maksimum sıkıştırmada bırakmaz. CompressDocument propertysinin kendisi True kalır; geri dönen yalnızca ödünç aldığı altı ayardır. Sürüm daha özenli ele alınır. HotPDF, kendi 1.5e çekmesini yalnızca belge hâlâ 1.5te bitiyorsa geri alır; yani başka bir özellik çalışma sırasında dosyayı 1.6ya ittiyse (gömülü bir OpenType fontu diyelim), daha yüksek sürüm kalır — sıkıştırma olmasaydı da tam olarak öyle olurdu

Kompaksiyon olmadan font alt kümeleri neden hâlâ büyüktür?

Klasik bir TrueType subset, hiç çizmediğiniz outlineları atar ama her glyph IDyi yerinde tutar; onu ağır yapan da bu numaralandırmadır. Content stream, özgün GIDLere eşit CIDler gösterir; dolayısıyla subset, tuttuğu en yüksek glyphe kadar — boş olsa da olmasa da — her slot için bir loca ofseti ve bir hmtx girdisi taşımak zorundadır. Latin bir font için bu ek yük gürültüden ibarettir. SimSun gibi, ideograflarının çok geniş bir glyph tablosunun derinlerinde durduğu CJK bir font için ise iki Çince karakter, bütün fonta göre boyutlanmış tabloları sürükler. Şekillendirilmiş glyphler için font subset kapanış kuralları hangi glyphlerin hayatta kalacağına karar verir; kompaksiyon ise hayatta kalanların neye mal olduğuna dairdir

CompactFontSubsetting, tutulan glyphleri sıfırdan başlayan yoğun bir aralığa yeniden numaralandırır ve CIDFont üzerine, ISO 32000-1 §9.7.4.2nin CID ile indekslenen iki baytlık GID tablosu olarak tanımladığı bir /CIDToGIDMap streami yazar. Hile tamamen bu tablodadır. Content streamler, /W genişlik dizisi ve ToUnicode CMap özgün CIDleri tutmaya devam eder; yazılmış olan hiçbir şeyin değişmesi gerekmez, CIDden glyphe bakış tek başına haritaya taşınır. Özelliği doğuran testte, iki karakterli SimSun 24.8 KBlik font verisinden 3.1 KBya indi

Seyrek HotPDF font subseti ile CompactFontSubsetting çıktısının karşılaştırması: seyreği, tutulan en yüksek GIDe kadar her özgün glyph ID için loca ve hmtx girdilerini saklar; kompakt olan tutulan glyphleri sıfırdan yoğun biçimde yeniden numaralandırır ve CIDLeri CIDToGIDMap streamiyle eşlerken content streamler, /W ile ToUnicode değişmez
Yeniden numaralandırma maliyeti font programından çıkarıp tek bir küçük map streamine taşır — iki SimSun karakteri, yazılmış contente tek bayt dokunmadan 24.8 KBden 3.1 KBya indi

Kompaksiyonun katı sınırları vardır ve başarısız olmak yerine sessizce gerilemeye geçer. HotPDF kompakt subsetleri yalnızca Type 0 TrueType fontları için kurar; hem SetFont ile subsetting açık set edilenler hem de RegisterUnicodeTTF üzerinden kaydedilen font. Basit bir TrueType, glyphlerini font programının içindeki cmapten bulur; yeniden numaralandırma onu bozar, bu yüzden seyrek subseti tutar. OpenType-CFF fontlarının da kompakt yolu yoktur. Kompakt kurulum başarısız olursa istisna fırlatmak yerine seyrek subsete döner. Property varsayılan olarak kapalıdır, yani mevcut çıktı bayt olarak özdeş kalır; PDF/A altında ise kayıtlı Unicode fontu her zaman kompakt subset alır

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

Paketli yazıcı dosya yapısını nasıl sıkıştırır?

Fontlar ve streamler küçülünce sözlükler ile cross-reference verisi kalan en büyük maliyet olur; CompressDocumentin arkasındaki object-stream yazıcısı da onları kırpır. Object streamler ve artımlı güncellemeler rehberi kap formatını kapsar; sıkıştırma yolu üzerine dört rafinaj ekler:

  • ISO 32000-1 §7.2.2ye göre kompakt sözdizimi: boşluk yalnızca normal karakterler olarak bitişik yazılacak iki tokenin arasına yazılır, böylece /Type /Page, /Type/Page olur
  • Cross-reference stream alanları §7.5.8.2nin izlediği her genişliği alır; 16 MBin altındaki bir dosya her ofseti 4 yerine 3 baytta saklar
  • ConfigureAdaptiveObjectStreamPacking ile kendi tavanınızı set etmedikçe, her zamanki 100 yerine her object streamine 250e kadar object girer
  • Dosya şifreli olmadığında Catalog ile Info sözlüğü de object streamlerine paketlenir; şifreli çıktı onları en üst düzeyde tutar

Kompakt sözdizimi, yazıcıyı genişletecekseniz bilinmesi gereken bir tuzakla geldi. İmzalama, dosya yazıldıktan sonra baytlarda /ByteRange ( ve /Contents < literal yer tutucularını arayarak imzayı doldurur; kompakt yazım onları /ByteRange( ve /Contents<e çevirirdi ve arama asla bulamazdı. İmza sözlükleri (Type Sig ya da DocTimeStamp, FT Sig) ve şifreleme sözlüğü bu yüzden boşluklu düzeni korur. İlgili bir kusur v2.766.41 öncesi derlemeleri etkiliyordu: CompressDocument dahil her object-stream kaydı iki %PDF- başlık satırıyla başlıyordu; katı bir validator çıktınızı işaretliyorsa yükseltin

Hâlihazırda yüklenmiş bir PDFi sıkıştırabilir misiniz?

Evet, CompressLoadedDocument(Options, Info) opsiyon overloadı üzerinden; mevcut bir dosyada aynı kayıpsız adımları çalıştırır. THPDFLoadedDocumentCompressionOptions.Default ile kullanılmayan sayfa kaynaklarını kaldırır, özdeş font ve formları birleştirir, gömülü fontları kompakt subsetlerle subsetler, filtresiz, Flate, LZW, ASCII ve RunLength streamlerini sonuç daha küçükse Flate ile yeniden sıkıştırır ve sonraki kaydın object streamleri kullanmasını sağlar. HighRatioFlate varsayılan olarak kapalıdır; object streamler PDF/A-1 ve artımlı kayıtlar için atlanır. Parametresiz CompressLoadedDocument overloadu ise yalnızca sıkıştırılmamış streamleri Flate ile sıkıştıran daha eski, daha dar çağrıdır

Delphide HotPDF CompressLoadedDocument akışı: çağrı kullanılmayan sayfa kaynaklarını kaldırır, özdeş font ve formları birleştirir, gömülü fontları kompakt subsetlerle subsetler, streamleri yalnızca sonuç daha küçükse Flate ile yeniden sıkıştırır ve sonraki kayıt için object streamleri açar; imza alanları RefusedBySignaturePolicy tetikler ve dosyaya dokunmaz
Her adım bir imzanın kapladığı baytları yeniden yazar; bu yüzden geçersiz kılmayı açıkça kabul etmedikçe bütün belge reddedilir — Info.BytesSaved o zaman yalnızca kaynak, font ve stream işini toplar
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;

Yüklenen yolda iki sınır önemlidir. Her adım bir imzanın kapladığı baytları yeniden yazar; dolayısıyla imza alanı taşıyan bir belge bütünüyle reddedilir: çağrı 0 döndürür, RefusedBySignaturePolicy set eder ve AllowSignatureInvalidation set etmedikçe hiçbir şeyi değiştirmez. Set ettikten sonra Info.SignaturesInvalidated, neye vazgeçtiğinizi söyler. Kompaksiyon burada, üretim yolundan daha muhafazakârdır. HotPDF yalnızca Identity /CIDToGIDMapli, CIDin GIDe eşit olduğu CIDFontType2 fontları tarafından kullanılan font programlarını kompaktlar ve mevcut bir map streami, bir /CIDSet ya da COLR, sbix, CBDT, SVG gibi color glyph tabloları taşıyan programları atlar, çünkü kompakt yeniden kurulum renk katmanlarını düşürür. Ayrıca Info.BytesSavedin yalnızca kaynak, font ve stream adımlarını topladığını unutmayın; object-stream kazancı dosya yazıldığında görünür

Pratikte hangi sonuçları beklemelisiniz?

Kazanç, dosyanın ne kadarının sıkıştırılmamış yapı ve şişkin font verisi olduğuna göre değişir; sayfa sayısına göre değil. Üç sayfalık Arial ve SimSun örneği, CompressDocument ile üretilirken 10.2 MBden 20 KBya indi; sıkıştırılmamış özgün hâli yüklenip CompressLoadedDocumentten geçirildiğinde 10.2 MBden 19.8 KBya. İki yolda da render birebir aynı. Hâlihazırda kompakt bir PDF neredeyse kıpırdamaz: regresyon setinde bu tür dosyalar özgün boyutlarının -%0,07 ile +%0,06si kadar tasarruf etti. Fotoğraf ağırlıklı dosyalar az kazanır, çünkü iki yol da image verisine dokunmaz

Her gece aynı CJK raporlarını üretiyorsanız kompakt subsetleri diskteki kalıcı font subset cachei ile eşleştirin; böylece subsetting işi her çalıştırmada tekrarlanmaz ve sıkıştırılmış çıktıları bayt bazında değil object içerik bazında karşılaştırın, çünkü tek bir değişen alan bütün bir object streamini yeniden Flate eder. Tam property ve record referansları HotPDF Delphi PDF component ürün sayfasındadır