Teknik Makale

Delphi'de PDF Dosya Boyutunu Azaltma: Yazı Tipleri, Görüntüler, LZW

Delphi'de PDF dosya boyutunu azaltmak için losLab PDF Kütüphanesi, en büyük üç şişkinlik kaynağına saldıran üç API sunar: SubsetEmbeddedFonts gömülü her TrueType yazı tipi programını belgenin gerçekten işlediği gliflere indirger, DownsampleImages hedef DPI değerini aşan raster görüntüleri yeniden örnekler ve NormalizeLZWStreams eski LZWDecode sıkıştırmasını FlateDecode ile değiştirir. Her biri değiştirdiği nesne sayısını döndürür, böylece sıfır değeri sessiz bir başarısızlık yerine geçişin etkisiz (no-op) olduğunu söyler

Birleştirilmiş PDF'im neden kaynak dosyalarından daha büyük?

Birleştirilmiş veya programlı olarak oluşturulmuş bir PDF, genellikle şu üç nedenden biri yüzünden aşırı büyüktür: tamamen gömülü yazı tipleri, görüntüleme çözünürlüklerinin çok üzerinde örneklenmiş görüntüler ve hala eski LZW filtresiyle sıkıştırılmış akışlar. ISO 32000-1 §9.9, bir üreticinin yazı tipi programının tamamını gömmesine izin verir ve çoğu üretici, güvenli varsayılan olduğu için tam olarak bunu yapar. Eksiksiz bir Arial FontFile2 programı yüzlerce kilobayta ulaşır; bunu bir düzine kaynak dosyaya gömün, birleştirin ve kimsenin yazmadığı karakterlerin glif ana hatlarının bir düzine kopyasını taşımış olursunuz. Birleştirme işleminin kendisi bu atığı yaratmaz, yalnızca toplamın nihayet görünür hale geldiği tek bir dosyada yoğunlaştırır

Görüntüler ikinci suçludur. Çeyrek sayfalık bir çerçeveye yerleştirilen 4800 piksel genişliğindeki bir tarama, 300 DPI'lık bir baskı iş hattının kullanabileceğinden yaklaşık 40 kat daha fazla piksel verisi taşır. Üçüncüsü ise daha sessizdir: LZWDecode ile filtrelenmiş akışlar. ISO 32000-1 §7.4.4 hem LZWDecode hem de FlateDecode'u belirtir ve Flate'in genellikle en az LZW kadar iyi sıkıştırdığını not eder; uygulamada Flate çıktısı aynı veriler üzerinde sürekli olarak daha küçüktür ve LZW çoğunlukla geçmişlerinin bir noktasında 1990'lar dönemi araçlarından geçen dosyalarda hayatta kalır. Bu makalenin geri kalanı, her bir sorunu düzelten üç losLab PDF Kütüphanesi geçişini inceleyecek ve ardından bunları tek bir iş hattında birleştirecektir

SubsetEmbeddedFonts ile yazı tipi alt kümeleme

SubsetEmbeddedFonts, yüklenen bir belgedeki gömülü her TrueType yazı tipini belgenin gerçekte kullandığı karakterlere küçültür ve saklanacak karakter listesini içerik akışlarının kendisinden çıkardığı için herhangi bir argümana ihtiyaç duymaz. Dahili olarak bu geçiş, her sayfanın içerik akışını GetTextRuns ile tarar, her yazı tipi kaynağı altında başvurulan karakter kodlarını toplar, bir saklama listesi oluşturur ve alt küme üretmek için orijinal yazı tipi programını Windows FontSub motoruna (CreateFontPackage) teslim eder. Yeniden yazılan program, yerindeki FontFile2 akışının yerini alır ve BaseFont adı, ISO 32000-1 §9.6.4'ün alt küme yazı tipleri için tanımladığı altı büyük harf artı artı işareti kuralı olan bir LOSABC+ öneki kazanır. Bu önek aynı zamanda çağrıyı idempotent (eşgüçlü) kılan şeydir: geçişi iki kez çalıştırdığınızda halihazırda alt kümelenmiş yazı tipleri algılanır ve atlanır, böylece dosyaları tekrar ziyaret edebilecek bir toplu işe dahil edilmesi güvenlidir

var
  Lib: TPDFlib;
  Fonts: Integer;
begin
  Lib := TPDFlib.Create;
  try
    if Lib.LoadFromFile('merged-report.pdf', '') = 1 then
    begin
      Fonts := Lib.SubsetEmbeddedFonts;
      // Fonts = number of FontFile2 programs rewritten;
      // 0 means nothing embedded, or everything already subsetted
      Lib.SaveToFile('merged-report-subset.pdf');
    end;
  finally
    Lib.Free;
  end;
end;

İki uygulama detayı bilinmeye değerdir çünkü bunlar API'nin sınırlarını açıklar. İlk olarak, geçiş FontFile2 programını hedefler, bu nedenle gömülü TrueType programlarını kapsar; Type 1 veya çıplak CFF olarak gömülen yazı tipleri riske atılmak yerine dokunulmadan bırakılır. İkinci olarak, FontSub motoruna dayanır, bu da SubsetEmbeddedFonts işlevini yalnızca Windows'a özel kılar. Uygulamadan daha ince bir nokta: bir yazı tipinin uygun olup olmadığı, gömülü bayrak sezgisel yöntemine güvenilerek değil, FontDescriptorFontFile2 referans zincirinin gerçekten çözümlenmesiyle belirlenir; çünkü yüklenen bir belgedeki yazı tipleri hiçbir zaman bu tür bayrakları ayarlayan oluşturma tarafı defter tutma işlemlerinden geçmemiştir. Çözümlenen akış mevcutsa, yazı tipi bir adaydır; değilse, hatasız bir şekilde atlanır

Dürüst takas şudur: bir alt küme yazı tipi yalnızca alt kümeleme anında mevcut olan glifleri içerir. Bir ardıl araç veya kendi kodunuz daha sonra aynı yazı tipinde metin eklerse, alt kümenin dışındaki herhangi bir karakterin ana hattı olmayacaktır ve eksik glif olarak işlenecektir. Alt kümelemeyi son içerik değiştirme adımı olarak yapın, asla bir düzenleme aşamasından önce yapmayın. Yazı tipini daha sonra yeniden kullanmak üzere geri çekmeyi planlıyorsanız aynı dikkat geçerlidir; PDFlibPas ile metin, görüntü ve yazı tipi ayıklama hakkındaki makale, ayıklanan bir alt küme programının size ne verip ne veremeyeceğini kapsar

DownsampleImages hangi görüntülerin küçültüleceğine nasıl karar verir?

DownsampleImages(MaxDPI, Quality, Filter), kasıtlı olarak muhafazakar bir DPI tahmini kullanarak yalnızca aşırı örneklenmiş olduğundan emin olduğu görüntüleri yeniden örnekler. Bir PDF görüntü XObject'i piksel boyutlarını depolar ancak güvenilir bir fiziksel çözünürlük depolamaz ve kaynak görüntüden gelen herhangi bir DPI etiketi nadiren bir yükle-düzenle-kaydet döngüsünden sağ çıkar. Bu nedenle geçiş, SrcDPI = PixelWidth / 8.5 şeklinde tahminde bulunur ve aslında şunu sorar: bu görüntü bir Letter sayfasının tam genişliğini kaplasaydı çözünürlüğü ne olurdu? Yalnızca tahmini MaxDPI değerini aşan görüntülere dokunulur. Bu sapma kasıtlıdır: sayfaya küçük yerleştirilen bir görüntünün gerçek DPI değeri tahminden daha yüksektir, bu nedenle geçiş ölçemediği baskı kalitesindeki bir varlığı bozmak yerine eksik tetiklenmeyi tercih eder

1 ila 100 arasındaki Quality parametresi JPEG yeniden kodlama kalitesini seçerken, 0 değeri çıktıyı kayıpsız PNG tarzı Flate olarak tutar; Filter yeniden örnekleme çekirdeğini seçer, kutu ortalaması için 0 ve bilineer için 1. Taranmış ofis belgeleri için DownsampleImages(150, 75, 1) makul bir başlangıç noktasıdır; yeniden yazdırılabilecek her şey için MaxDPI değerini 300'e yükseltin veya geçişi tamamen atlayın. Alt örnekleme, üç adım arasında kayıplı olan tek adımdır, bu nedenle kullanıcılarınızın kapatabileceği bir ayarın arkasında yer almalıdır

Eski LZW akışlarını NormalizeLZWStreams ile dönüştürme

NormalizeLZWStreams zahmetsiz bir kazançtır: her LZWDecode akışını kayıpsız olarak açar ve yerinde FlateDecode ile yeniden sıkıştırarak dönüştürülen akışların sayısını döndürür. Hem tek bir /Filter /LZWDecode girişini hem de bir filtre zinciri dizisi içinde görünen LZW'yi işler; burada yalnızca LZW bağlantısı değiştirilir ve zincirin geri kalanı korunur. Tahmin edici parametreleri (Predictor, Columns, Colors, BitsPerComponent) akışın DecodeParms sözlüğünden okunur ve açıcıya aktarılır, böylece tahmin edici kodlu görüntü verileri doğru bir şekilde döner. Her iki filtre de bit düzeyinde tam kod çözücüler olduğundan, kodu çözülen baytlar öncesinde ve sonrasında aynıdır; yalnızca konteyner sıkıştırması değişir, bu nedenle bu geçişin her dosyada koşulsuz olarak çalıştırılması güvenlidir

Hiçbir LZW akışı içermeyen bir belgede çağrı basitçe 0 döndürür ve hiçbir şeye dokunmaz, ki bu kütüphanenin regresyon paketi tarafından açıkça test edilir: yeni oluşturulmuş yalnızca Flate içeren bir dosya sıfır dönüşüm bildirmelidir. Bu etkisiz çalışma (no-op) garantisi, geçişin bazıları 2024 ve bazıları 1998 yılından kalan binlerce heterojen dosyayı işleyen bir iş hattında yer alması durumunda önem kazanır

Delphi'de eksiksiz boyut optimizasyonu iş hattı

Üç geçiş, tek bir yükle-optimize et-kaydet işlevinde birleşir ve çalışma sıraları beklediğinizden daha az önemlidir çünkü farklı nesne türleri üzerinde çalışırlar: yazı tipleri, görüntü XObject'leri ve akış filtreleri. Yine de alt kümelemeyi ilk çalıştırmak düzenli bir seçimdir, çünkü düzenleme sırası kısıtlaması olan geçiş budur

function OptimizePDF(const Src, Dst: string): Boolean;
var
  Lib: TPDFlib;
  Fonts, Images, Streams: Integer;
begin
  Result := False;
  Lib := TPDFlib.Create;
  try
    if Lib.LoadFromFile(Src, '') <> 1 then
      Exit;
    Fonts   := Lib.SubsetEmbeddedFonts;        // TrueType FontFile2 -> subset
    Images  := Lib.DownsampleImages(150, 75, 1); // >150 DPI -> JPEG q75, bilinear
    Streams := Lib.NormalizeLZWStreams;        // LZWDecode -> FlateDecode
    Result := Lib.SaveToFile(Dst) = 1;
    // Log Fonts/Images/Streams: three zeros mean the file was already lean
  finally
    Lib.Free;
  end;
end;

İş hattını kütüphanenin kendisini doğruladığı şekilde doğrulayın: çift yönlü (round-trip). v3.130 regresyon testleri bir belge oluşturur, kaydeder, yeniden yükler, optimizasyonu çalıştırır, tekrar kaydeder ve ardından üç şeyi doğrular: çıktı daha küçüktür, dönen sayılar beklentilerle eşleşir ve optimize edilmiş dosyanın yeniden yüklenmesi hala ayrıştırılır ve işlenir. Bu oluştur-optimize et-yeniden yükle döngüsünü kendi üretim dosyalarınızdan bir örnek üzerinde yeniden üretmek ve öncesi ile sonrası ayıklanan metinleri karşılaştırmak, bir müşteri bozuk bir fatura açmadan çok önce entegrasyon hatalarını yakalayan bir saatlik bir yatırımdır

// Round-trip check: the optimized file must still load cleanly
Lib := TPDFlib.Create;
try
  Assert(Lib.LoadFromFile('merged-report-opt.pdf', '') = 1);
  Assert(Lib.GetPageCount > 0);
finally
  Lib.Free;
end;

İş hattı bir birleştirme iş akışında nereye uyar? Birleştirmeden sonra, birleştirme sırasında değil. Önce birleştirip ardından tek sonucu optimize etmek, kaynak dosya başına değil, kullanılan tüm karakterlerin birleşimi üzerinden gömülü her yazı tipinin bir kez alt kümelenmesi anlamına gelir. Birleştirme verimi darboğaz oluşturuyorsa, PDFlibPas, bayt referans kaydırma ile hızlı PDF birleştirme hakkındaki makalede açıklanan ve tam nesne ayrıştırmasını önleyen bayt düzeyinde hızlı bir yol sunar; belleğe tamamen sığamayacak kadar büyük girdiler için ise, büyük PDF'ler için doğrudan erişimli birleştirme ve bölme akış yolunu kapsar. Her ikisi de birleştirilmiş çıktı üzerindeki son optimizasyon geçişi ile doğal olarak eşleşir

Üç geçişin yapmayacağı şeyler

losLab PDF Kütüphanesi'nin optimizasyon üçlüsü, belge semantiğini değiştiren her şeyi kasıtlı olarak hariç tutar. SubsetEmbeddedFonts, birleştirilen kaynaklardaki yinelenen yazı tiplerini tek bir programda birleştirmez, her birini bağımsız olarak küçültür; tekilleştirme farklı ve daha riskli bir dönüştürmedir. DownsampleImages, bir insan çerçevesi için çok büyük olduğunu söyleyebilse bile, muhafazakar DPI tahmini eşiğin altında kalan bir görüntüyü es geçecektir. Ve geçişlerin hiçbiri belge yapısına dokunmaz, bu nedenle binlerce artık nesne nedeniyle şişmiş bir dosya bu akış düzeyindeki geçişler yerine yeniden yazma tarzı bir kaydetmeye ihtiyaç duyar. Bu sınırlar dahilinde, yazı tipi alt kümeleme, görüntü alt örnekleme ve LZW-Flate normalizasyonu kombinasyonu, PDF şişkinliğinin üç klasik kaynağını her biri için öngörülebilir tek bir API çağrısıyla ortadan kaldırır. Bu üç işlev, yukarıda tartışılan birleştirme, ayıklama ve işleme API'lerinin yanı sıra Delphi, C# ve VB.NET için losLab PDF Library'nin bir parçası olarak sunulur