İçeriğe geç

Akışlar ve filtreler

Evidence: Standard-backed

Gerçek bir PDF’nin baytlarının çoğu akışların içindedir: sayfa içeriği, yazı tipleri, görüntüler ve çapraz başvuru akışının kendisi. Bu baytların neredeyse hiçbiri ham olarak saklanmaz; önce bir veya daha fazla filtreden geçer. Bu sayfa, karşılaşacağınız filtreleri, her birinin ne işe yaradığını, nerede sorun çıkardığını ve NextPDF’nin aynı girdinin her zaman aynı baytları üretmesi için sıkıştırmayı neden sabitlediğini ele alır.

Bir akış ile filtresi arasındaki ilişki bir sözleşmedir: “bu baytlar deflate ile sıkıştırıldı, ardından base-85 ile kodlandı — gerçek veriyi elde etmek için bu sırayla kodlarını çözün.” /Filter girdisi baytların gerçekte ne olduğuyla çelişiyorsa ya da /Length yanlışsa veya iki filtre yanlış sırada listelenmişse akışın kodu çözülemez ve taşıdığı nesne kaybolur. Bir okuyucu sezgisel tahmin yürütmez; sözlüğün söylediğini yapar.

İkinci ve daha sessiz bir maliyet daha vardır. Bir kitaplığın sıkıştırıcısı belirlenimci değilse — farklı zlib derlemesi, farklı düzey, farklı iç blok sınırları — aynı PDF’yi üretmesi gereken iki çalıştırma iki farklı dosya üretir. Bu, bayt düzeyinde yeniden üretilebilirliği bozar. Bozulan yeniden üretilebilirlik de altın dosya testlerini, imzalı yapı doğrulamasını ve çıktı farkı alan her ardışık düzeni bozar. Filtreler hem PDF’nin doğru olup olmadığını hem de PDF’nin aynı kalıp kalmadığını belirler.

  • Bir akış nesnesi, streamendstream içine sarılmış bir sözlük ile bir bayt bloğudur; bir /Length ve genellikle bir /Filter taşır.
  • Bu /Filter girdisi, kod çözme filtresini — veya bir ardışık düzen olarak sırayla uygulanan bir filtre dizisini — adlandırır.
  • Filtreler iki aileye ayrılır: sıkıştırma (FlateDecode, LZWDecode, RunLengthDecode, DCTDecode, JPXDecode, JBIG2Decode) ve ASCII taşıma (ASCIIHexDecode, ASCII85Decode); ayrıca şifreleme için özel Crypt filtresi vardır.
  • En sık göreceğiniz filtre FlateDecode’dur — zlib/deflate. İçerik, yazı tipleri ve çapraz başvuru akışı için varsayılan filtredir.
  • NextPDF, aynı girdi baytlarının her zaman aynı çıktı baytlarına sıkıştırılması için Flate çıktısını sabit bir düzeye ve biçime sabitler.

NextPDF, akış nesnelerini tek bir arabellek yardımcısı üzerinden yazar ve tek bir sabitlenmiş sıkıştırıcı üzerinden sıkıştırır — bunu bilinçli olarak yapar.

BinaryBuffer::writeStream() (src/Support/BinaryBuffer.php), akış içeriğini sözlüğüyle birlikte sarar; her zaman gerçek bayt uzunluğuna eşit bir /Length yazar ve çağıranın sağladığı, örneğin /Filter gibi ek girdileri birleştirir. Bildirilen uzunluğun yazılan baytlarla çelişmesine yol açacak bir kod yolu yoktur, çünkü uzunluk içerik dizesinin kendisinden alınır.

Sıkıştırma PinnedZlibCompressor (src/Writer/PinnedZlibCompressor.php) üzerinden gerçekleşir. Bu sınıf tek bir amaçla vardır. Açık bir düzey verilmeden çağrılan gzcompress, derlemeler arasında geçmişte farklılık göstermiş olan zlib çalışma zamanı varsayılanına bırakır. 2 baytlık zlib başlığı düzeyi dolaylı da olsa kodlar; dolayısıyla “varsayılan” kararlı bir çıktı değildir. Sıkıştırıcı, düzeyi RFC 1951 üst sınırına sabitler ve her zaman zlib ile sarmalanmış deflate (RFC 1950 başlığı + Adler-32 kuyruğu) üretir; bu da tam olarak /Filter /FlateDecode’in beklediği şeydir. zlib’ten gelen ciddi bir hata, sessizce sıkıştırılmamış çıktıya geri dönmek yerine türü belirlenmiş bir özel duruma dönüşür — bir akış asla sessizce ham olarak yazılmaz.

Çapraz başvuru akışının kendisi tüm bunların somut bir örneğidir: CrossReferenceStream (src/Core/CrossReferenceStream.php) ikili bir tablo oluşturur, onu sıkıştırır ve /Type /XRef, bir /W alan genişliği dizisi ve /Filter /FlateDecode ile bir akış nesnesi olarak yazar. Bir okuyucunun her nesneyi bulmasını sağlayan dizinin kendisi de filtrelenmiş bir akıştır.

FiltreAileNe işe yaradığıNerede sorun çıkardığı
FlateDecodeSıkıştırmazlib/deflate; içerik, yazı tipleri ve xref akışları için varsayılanBelirlenimci olmayan bir zlib derlemesi, “aynı” PDF’lerin bayt bayt farklı olmasına neden olur
LZWDecodeSıkıştırmaDaha eski Lempel–Ziv–Welch sıkıştırmasıEski; yerini Flate aldı, eski dosyalarda ara sıra hâlâ görülür
DCTDecodeSıkıştırmaJPEG ile kodlanmış renkli/gri tonlamalı görüntülerKayıplı — zaten DCT olan bir görüntüyü yeniden kodlamak onu yine bozar
JPXDecodeSıkıştırmaJPEG 2000 dalgacık görüntü verisiBazı arşivleme profilleri tarafından izin verilmez; geniş destek tutarsızdır
JBIG2DecodeSıkıştırmaİki düzeyli (1 bit) görüntü sıkıştırmasıSatır içi görüntülerle kullanılmamalıdır; kayıplı kipler taramaları değiştirebilir
RunLengthDecodeSıkıştırmaBayt yönelimli çalışma uzunluğuYalnızca uzun tek baytlı diziler içeren verilerde işe yarar; başka verileri büyütebilir bile
ASCIIHexDecodeTaşımaOn altılık rakamlar olarak ikili veriBoyutu ikiye katlar; yalnızca 7 bit güvenli kanallar için, asla boyut için değil
ASCII85DecodeTaşımabase-85 ASCII olarak ikili veri~%25 ek yük; sıkıştırma değil, bir taşıma kolaylığı
CryptGüvenlikBelgenin güvenlik işleyicisini uygularBir çapraz başvuru akışı bir Crypt filtresini asla kullanmamalıdır

PDF standart filtre kümesi, ailelere göre ve her birinin ilişkili olduğu tipik hatayla birlikte. NextPDF, içerik, yazı tipleri ve çapraz başvuru akışı için FlateDecode yazar; ASCII taşıma filtreleri 7 bitlik kanallar içindir, asla boyut küçültmek için değil.

Filtre mekanizması Spec: ISO 32000-2, §7.4 tarafından tanımlanır. Bir akış sözlüğü, filtrelerini /Filter aracılığıyla adlandırır. Girdi birden fazla filtre listelediğinde, bu filtreler bir kod çözme ardışık düzeni oluşturur ve sırayla uygulanır. Bir yazıcı, bir akışı sıkıştırmak veya onu 7 bit güvenli yapmak için kodlar. Bir okuyucu, özgün veriyi geri elde etmek için karşılık gelen kod çözme filtrelerini çağırır. Evidence: Standard-backed

Standardın filtre tablosu her filtreyi sınıflandırır. FlateDecode, zlib/deflate ile kodlanmış veriyi açar ve özgün metni veya ikili veriyi yeniden üretir. DCTDecode, JPEG aracılığıyla özgününe yaklaşan görüntü örneklerini yeniden üretir — “yaklaşan” sözcüğü, standardın bunun kayıplı olduğunu belirtme biçimidir. LZWDecode, RunLengthDecode, JBIG2Decode, JPXDecode ve Crypt filtresinin her biri de orada tanımlanır; JBIG2 satır içi görüntülerde açıkça yasaklanmıştır.

Çapraz başvuru akışı, biçimin kendi mekanizmasını yine kendisine uygular: bu bir akış nesnesidir (/Type /XRef, Spec: ISO 32000-2, §7.5.8 ) ve onun /W dizisi her giriş alanının bayt genişliğini kodu çözülmüş akışta belirtir. Standart, bu akışın şifrelenmemesini ve bir Crypt filtresi kullanmamasını gerektirir. NextPDF’nin CrossReferenceStream sınıfı buna tam olarak uyar — FlateDecode, açık /W, şifreleme yok.

Flate ile sıkıştırılmış bir sayfa içerik akışı. Son derece yaygın biçim budur: /Length ve /Filter içeren bir sözlük, ardından stream ile endstream arasındaki sıkıştırılmış baytlar.

<?php
declare(strict_types=1);
use NextPDF\Writer\PinnedZlibCompressor;
// The marking operators a page content stream carries, uncompressed.
$content = "BT /F1 12 Tf 72 712 Td (Hello) Tj ET\n";
// NextPDF compresses through the pinned compressor: fixed level,
// fixed zlib-wrapped format. The same $content always yields the
// same $compressed bytes, on any supported PHP/zlib build.
$compressed = PinnedZlibCompressor::compress($content);
// Emitted as a stream object. /Length is the real byte length of
// $compressed; /Filter names the decode the reader must apply.
// N 0 obj
// << /Length <strlen($compressed)> /Filter /FlateDecode >>
// stream
// <$compressed bytes>
// endstream
// endobj

Bir okuyucu tersini yapar: /Length kadar bayt okur, /Filter öyle dediği için onları FlateDecode’dan geçirir ve özgün işleçleri geri elde eder. Sıkıştırıcıyı sabitlediğinizde bu gidiş gelişin yalnızca doğru olmakla kalmadığını da görürsünüz. Her seferinde aynıdır; altın dosya ve imzalı yapı denetimleri tam olarak buna dayanır.

Yaygın tuzak, ASCII filtrelerini sıkıştırma olarak görmektir. ASCIIHexDecode ve ASCII85Decode bir akışı büyütür — sırasıyla kabaca iki katına ve kabaca %25 oranında. Yer kazanmak için değil, ikili veriyi yalnızca 7 bitlik metin için güvenli olan bir kanaldan geçirmek için vardırlar. Bir PDF’yi “küçültmek” için ASCII85 seçmek bunun tam tersini yapar. Aynı yanlış anlamanın ikinci yarısı, FlateDecode’un görüntüler için “bedavaya” kayıpsız olduğuna inanmaktır. Flate kayıpsız olsa da görüntü zaten DCT (JPEG) ile kodlanmışsa onu yeniden sarmak veya kayıplı bir filtreden geçirerek dönüştürmek, Flate çevresinde ne yaparsanız yapın onu bozar. Filtre ardışık düzeni, ona verdiğiniz şeyi tam olarak korur — buna kazara verdiğiniz bir yeniden sıkıştırma artefaktı da dahildir.

Bu sayfa, filtrelerin nasıl bildirildiğini ve uygulandığını ele alır; her birinin içerdiği bit düzeyindeki algoritmayı değil. Belirlenimcilik güvencesi özellikle NextPDF’nin yazdığı akışlar için Flate çıktısıyla ilgilidir. Bu güvence, PHP ara sürümleri ve standarda uyumlu zlib derlemeleri arasında geçerlidir; ancak standart, bir deflate kodlayıcısının farklı iç blok sınırları seçmesine açıkça izin verir, dolayısıyla gerçekten farklı zlib uygulamaları arasında (örneğin standart zlib ile zlib-ng) bayt bayt aynı çıktı vaat edilmez. Yapı ortamı tam da bu nedenle sabitlenmiştir.

NextPDF, yazdığı veri için FlateDecode’u ve ASCII taşıma filtrelerini seçer. Bir görüntü dönüştürücüsü değildir. Gelen rastgele bir JPEG2000 veya JBIG2 akışını yeniden paketlemeyi vaat etmez; kayıplı görüntü ödünleşimleri, bir yazıcının telafi edebileceği bir şey değil, kaynak verinin bir özelliğidir.

FlateDecode neden her yerde? Kayıpsızdır, genel amaçlıdır, iyi desteklenir ve çoğu PDF’nin metin ve işleç içeriği için iyi bir seçimdir. İçerik akışları, gömülü yazı tipleri ve çapraz başvuru akışı için güvenli varsayılandır.

Sıkıştırmayı kapatabilir miyim? /Filter’ı atlayıp ham baytları saklayabilirsiniz ve bir okuyucu bunu kabul eder. Dosya büyür ve başka hiçbir şey iyileşmez; hata ayıklama dışında nadiren bir neden vardır.

Sıkıştırma düzeyi neden sabitleniyor? Çıktının yeniden üretilebilir olması için. Sabitlenmemiş bir düzey (veya zlib derlemesi), açılmış içeriği değiştirmeden sıkıştırılmış baytları değiştirebilir — çıktı doğru olabilir, ama aynı değildir; bu da bayt düzeyinde doğrulamayı boşa çıkarır.

  • Bir PDF aslında nedir — bu sayfadaki akışların parçası olduğu nesne modeli.
  • Yazı tipleri: zor kısım — gömülü yazı tipi programları, kendi hata kiplerine sahip filtrelenmiş akışlardır.
  • PDF 2.0: neler değişti — 2.0 temelinin akışları ve NextPDF’nin varsayılan olarak kullandığı çapraz başvuru akışını nasıl ele aldığı.
  • Akış nesnesistream ile endstream arasındaki bir bayt bloğu ve ona eşlik eden bir sözlük; bir /Length ve genellikle bir /Filter taşır.
  • Filtre — bir okuyucunun bir akışın baytlarına uyguladığı, adlandırılmış bir kod çözme dönüşümü (örneğin FlateDecode).
  • Filtre ardışık düzeni — sırayla uygulanan bir filtre dizisi; dizi sırası, kod çözme sırasıdır.
  • FlateDecode — zlib/deflate filtresi; içerik, yazı tipleri ve çapraz başvuru akışları için varsayılan sıkıştırma.
  • DCTDecode — JPEG görüntü filtresi; kayıplı olduğundan yeniden kodlamak görüntüyü yine bozar.
  • ASCII taşıma filtresi — ASCIIHexDecode / ASCII85Decode; veriyi boyut pahasına 7 bit güvenli yapar — sıkıştırma değildir.
  • Belirlenimci sıkıştırma — aynı girdi için bayt bayt aynı sıkıştırılmış çıktı üretmek; sıkıştırıcının düzeyini ve biçimini sabitleyerek sağlanır.