Artikel Teknis

Menangani PDF Referensi-Hibrida dari Aplikasi Perkantoran di Delphi

Mengekspor sebuah dokumen dari Microsoft Word atau Excel dengan "Simpan sebagai PDF" akan menghasilkan file di cakram yang, hampir dapat dipastikan, merupakan file referensi-hibrida. File tersebut membawa informasi referensi-silangnya dua kali: satu kali sebagai tabel lebar-tetap klasik yang selalu hadir pada bagian akhir setiap PDF hingga versi 1.4, dan satu kali sebagai aliran referensi-silang terkompresi yang mana sebagian besar dokumen sebenarnya bergantung padanya. Sebuah kunci cuplikan tunggal, /XRefStm, menjahit kedua tampilan tersebut, dan apakah sebuah alat melihat keseluruhan dokumen atau tidak bermuara pada apakah alat itu mengikuti kunci tersebut

Artikel ini menilik file hibrida dari sisi yang membacanya: seperti apa rupa byte-byte di akhir file tersebut, bagaimana kedua tampilan terpisah ketika diedit, dan bagaimana sebuah jalur perpipaan Delphi dapat mendeteksi dan merutekan masukan hibrida tersebut. Bagaimana sebuah pemuat menggabungkan kedua tampilannya, dan mengapa urutannya tidak dapat ditawar-tawar, adalah subjek dari artikel HotPDF kami mengenai memuat file referensi-hibrida; artikel ini difokuskan mengenai cara mengenali tata letaknya

Mengapa ekspor Office menulis indeksnya dua kali

PDF 1.5 memperkenalkan dua fitur yang mengubah bentuk dari sebuah file PDF: aliran referensi-silang, yang menyimpan indeks objek sebagai data biner terkompresi alih-alih tabel teks murni (plaintext), dan aliran objek, yang membungkus banyak objek-objek kecil menjadi sebuah kontainer terkompresi-Flate tunggal. Penulis yang menggunakannya akan memproduksi file yang lebih kecil, namun sebuah pembaca PDF 1.4 tidak akan mampu membuka hasilnya, dikarenakan struktur yang menguncinya, yaitu kata kunci xref dan kamus trailer, sudah hilang

ISO 32000-1 §7.5.8.4 mendefinisikan komprominya. Sebuah file referensi-hibrida menulis keduanya: tabel referensi-silang klasik yang menangani objek-objek yang mesti dicapai oleh para pembaca versi lama, katalog dan pohon halaman misalnya, dan aliran referensi-silang yang mengindeks bagian lainnya. Objek-objek yang terlipat (termuat) di dalam aliran-aliran objek ditandai sebagai bebas (free) pada tabel klasiknya, sehingga sebuah pembaca 1.4 akan melewati mereka tanpa rintihan apa-apa; posisi mereka sebenarnya secara utuh mendiami aliran itu. Lantas cuplikan klasik tersebut menghadirkan kunci /XRefStm yang mengandung offset byte dari aliran tadi. Pembaca kuno tidak akan membaca kunci itu serta merender file berlandaskan tampilan tabel belaka. Penampil mutakhir mengikutinya dan bisa menyaksikan seluruh isi secara utuh. Word maupun Excel menghembuskan (membuat file dalam bentuk) tata letak persisnya sedemikian sepanjang bertahun-tahun, yang merupakan hujah kenapa fail-fail hibrida bukanlah kebetulan kecil yang ganjil, melainkan porsi bongsor dari apa yang tiba di jaringan alur penyambung di dunia perkantoran

Seperti apa rupa ekor dari file hibrida

Tata letak ini paling gampang diurai dengan mencermati byte-byte-nya. Di bawah adalah buritan ekor cuplikan dari sebuah file hibrida mini, offsetnya direka menjadi singkat belaka; bagi sebuah ekspor Office asli, angka nilai /XRefStm umumnya menjadi angka offset besar menghinggapi pinggiran akhir file. Alur silsilah rentet membacanya adalah jelajah usut mula-dari-ekor yang diutarakan di ikhtisar struktur file PDF kami: buru susur cari %%EOF, renggut bacalah startxref, loncat melentinglah melompat menyambangi per-tabelnya

% ... objek tubuh, termasuk aliran objek dan, pada byte 116,
% aliran referensi-silang (sebuah objek aliran dengan /Type /XRef) ...

xref                    % bagian klasik: apa yang ditunjuk startxref
0 4
0000000000 65535 f      % slot 0: kepala dari senarai bebas, selalu hadir
0000000017 00000 n      % objek 1: katalog, terlihat bagi pembaca mana pun
0000000000 65535 f      % objek 2: ditandai bebas -- hidup di dalam sebuah aliran objek
0000000000 65535 f      % objek 3: sama; hanya tampilan aliran yang dapat menemukannya
trailer
<<
  /Size 4
  /Root 1 0 R
  /XRefStm 116          % offset byte dari aliran referensi-silang
>>
startxref
7164                    % offset byte dari kata kunci 'xref' di atas
%%EOF

Dua detail pada keping cuplikan di atas menyandang beban dari seutuh keseluruhan mekanisme ini. Yang sulung (pertama), kata kuncinya, yakni startxref menukik merujuk membidik kepada porsi sang blokir (bagian) klasik secara sengaja (by design): ke alamat tulah yang wajar sewajarnya seorang pelahap pembaca purba mesti menancapkan sauh laras kakinya merapat berlabuh menjejak mendarat (land on). Pias Aliran dari si raupan referensi-silang mutlak dapat semata dipanjat dijamah dititi dipijak (reachable) sebatas membedah menerobos lewat lorong celah kunci /XRefStm di relung terdalam dari si sang cuplikan lurus kamus per-cuplikan, imbasnya adalah niscaya tiada sudi rupa raupan sepotong pengurai ujud parser yang melewatkan luaran urung menyingkap rupa lalai tak-mengendus abai melewat mencari sudi kata laras kunci itu lurus sudi melurus mengetahui tersadar luaran paham menginsafi bahwasanya aliran si aliran tadi wujud merupa wujud eksis berdiam menjejak mengada kenyataannya laras di-dunia. Menyusul (kedua) yang belakangan, objek kepingan 2 laras se-beserta keping objek urut 3 mendapuk wujud peran rupa selaku lurus-sebagai set sebangun deretan lakon bualan pepesan dusta luput bohongan (lies) wujud luaran dusta rupa dari ujud galur keturunan jenis aliran lurus mutlak rupa yang aman jinak jinak ramah merupa ramah tak-berbahaya raupan-tidak-berbahaya wujud-jinaknya. Pias Si tabel lawas per-tabel yang jadul lawasnya mutlak-klasik mendaulat mendakwahkan mencanangkan murni mencap mendeklarasikan murni luaran murni per-mendeklarasikan ujud-menyatakannya se-mereka luaran-bebas merupa bebas set laras lenggang-bebas kelindan tiada purna se-bebas rupa kosong, namun raupan ujud per-namun mereka rupa pias se-mereka lurus mutlak merupa perwujudan wujud per-objek laras objek yang sesungguhnya (real) mutlak raupan objek rupa-benda se-riil nyata lurus asli senyatanya mutlak asli rupa duduk bermukim menetap diam sudi bernaung meringkuk semat bersarang bercokol bermastautin (sitting) merupa lekat mendiami bersemayam wujud pias memendam ujud murni-terdiam lurus mendiami kelindan dalam raupan sebuah serapan dari rupa wujud se-wadah ujud peti bejana raupan kontainer kelindan-wadah ter-tekan (terkompresi) set pias terpadatkan termampatkan luaran lurus-terkompresi; set laras penandaan si jejak tera cap cap rupa penanda rupa bebas kosong wujud dari bebasnya kelindan luaran serapan itulah kunci wujud mutlak raupan yang mutlak laras wujudnya sudi rupa menjaga set purna menghalangi luaran menghindarkan menyelamatkan memelihara melindungi memagari menjaga mencegah set seorang raupan rupa si laras pelahap laras pembaca luaran rupa 1.4 sudi memuai luaran kelindan agar se-agar lurus dari laju wujud supaya laras tiada urung luput tiada pias mutlak batal dari laras terantuk serapan se-tergelincir terantuk tersandung luaran jatuh terpelecok (tripping over) se-melangkahi ujud menyandung rupa terhambat pada menyandung-antuk wujud pada rupa mutlak butir luaran entri-entri luaran se-entri yang luaran murni ia kelindan-ia raupan serapan urung murni mustahil tiada mampu sudi tak murni purna sanggup tak-mampu laju-bisa sudi memakainya mutlak rupa menggunakannya memanfaatkannya luaran men-dayagunakannya mempergunakan lajunya. Sesosok sang pias muat seorang laras wujud pengonsumsi luaran mutlak pihak-konsumen konsumen pelahap laras pengguna pias per-konsumen yang se-semata hanya set cuma set-belaka mutlak sudi wujud-percaya sudi beriman meyakini lurus percaya luaran memercayai murni serapan laju wujud menaruh percaya wujud mutlak ber-pegang laju percaya sudi pada memercayai pias kepada se-tampilan muka ujud pias per-tampilan wujud tampilan wajah lurus rupa klasik purna luaran-klasik wujud sendirian rupa-belaka wujud sendirinya laras pias secara soliter sudi wujud menyendiri semata tunggal murninya mutlak luaran akan lurus laju berujung menyudahi murni kelindan memutus menelurkan set mengambil merangkum laras memetik pias membuahkan ujud-menyimpulkan wujud menyimpulkan laras mengambil simpulan wujud purna bahwasanya set bahwa laras bagian wujud laju se-porsi se-besar set rupa sebagian rupa bagian-bagian mayoritas se-mayoritas rupa porsi se-sebagian pias-besar rupa terbesar besar luaran dari dokumen se-dokumen laras wujud ini lurus kenyataan lajunya murni pada raupan se-nyata lajunya serapan rupa wujud lajur tiada murni ujud per-ada merupa rupa luaran tak rupa tiada eksis tak-ada luaran laras absen eksis lurus lajunya

Bagaimana kedua tampilan melayang terpisah

Sebuah file hibrida yang baru keluar dari Word konsisten secara internal: kedua tampilan mendeskripsikan dokumen yang sama, masing-masing dalam cakupannya yang dideklarasikan. Masalah bermula tatkala file tersebut disunting oleh alat yang hanya memahami salah satu dari tampilan tersebut. Pertimbangkan sebuah utilitas pengecapan (stamping) yang melampirkan sebuah pembaruan inkremental bergaya-klasik: objek-objek baru, bagian xref yang baru, sebuah rantai /Prev ke bagian sebelumnya, dan sebuah cuplikan baru. Jika cuplikan tersebut menghilangkan kunci /XRefStm, tampilan alirannya menjadi yatim piatu; jika menyalin nilainya ke depan, tampilan alirannya masih mendeskripsikan dokumen sebagaimana keadaannya sebelum suntingan. Bagaimanapun juga, kedua indeks tersebut sekarang tidak sepakat tentang isi file

File yang dihasilkan memiliki tanda-tanda kegagalan yang khas: objek yang terlihat di satu tampilan hilang atau basi di tampilan lain. Pembaca yang mencari jalan lewat tampilan aliran akan menemukan versi prapemutakhiran dari objek yang telah diperbarui, atau tidak ada entri sama sekali bagi objek yang baru dilampirkan. Pembaca pada tampilan tabel melihat pembaruan itu namun kehilangan jejak akan objek-objek terkompresi yang posisinya hanya dilacak oleh tampilan aliran. Dalam praktiknya ini bermanifestasi berupa isian formulir yang bertahan di satu penampil namun sirna di penampil lainnya, anotasi yang tampak terhapus oleh proses pengecapan, atau pencarian yang mendarat ke objek yang salah sepenuhnya

Hal yang menjadikan proses pelacakan-kutu (debug) dari file-file ini mahal adalah kenyataan bahwa Adobe Acrobat acap kali dapat membukanya tanpa keluhan: saat indeks tak lagi akur dengan barisan byte-nya, dengan diam-diam Acrobat merakit-ulang data referensi-silang tersebut dengan cara memindai kepala-kepala objek, sehingga orang yang memproduksi file rusak itu tidak melihat adanya masalah. Kegagalannya baru mencuat belakangan, di saat filenya tiba di konsumen yang ketat, validator prapenerbangan, layanan tanda tangan, atau tugas penelan arsip, yang memercayai struktur yang dideklarasikan lalu melaporkan objek yang hilang atau ketidakcocokan referensi-silang. "Itu terbuka dengan baik di Acrobat" adalah bagaimana hampir setiap tiket desinkronisasi hibrida bermula

Mendeteksi file hibrida dalam Delphi murni

Mengklasifikasi asupan masukan tidaklah membutuhkan pustaka PDF. Kunci /XRefStm hanya dapat muncul di dalam sebuah kamus cuplikan klasik, dan cuplikan yang aktif berada dalam beberapa kilobyte terakhir dari file, karena spesifikasi mengharuskan %%EOF untuk muncul di dekat akhir fisik. Membaca sebuah jendela ekor berbatas dan mencarinya sudah cukup untuk melakukan pemilahan (triage):

uses
  System.SysUtils, System.Classes, System.StrUtils, System.Math;

function IsHybridReferencePdf(const FileName: string): Boolean;
const
  TailWindow = 2048;
var
  Stream: TFileStream;
  Buf: TBytes;
  Tail: string;
  Len, TrailerPos, NextPos, KeyPos, StartXrefPos: Integer;
begin
  Result := False;
  Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    if Stream.Size < 48 then
      Exit;
    Len := Min(TailWindow, Integer(Stream.Size));
    SetLength(Buf, Len);
    Stream.Position := Stream.Size - Len;
    Stream.ReadBuffer(Buf[0], Len);
  finally
    Stream.Free;
  end;

  // Setiap kata kunci yang terlibat adalah ASCII 7-bit, sehingga dekode tingkat-byte adalah aman
  Tail := TEncoding.ANSI.GetString(Buf);

  // Temukan kata kunci 'trailer' TERAKHIR: dengan pembaruan inkremental,
  // cuplikan terbaru adalah salah satu yang mengatur file
  TrailerPos := 0;
  NextPos := Pos('trailer', Tail);
  while NextPos > 0 do
  begin
    TrailerPos := NextPos;
    NextPos := PosEx('trailer', Tail, NextPos + 1);
  end;
  if TrailerPos = 0 then
    Exit;  // tidak ada cuplikan klasik: sebuah file aliran-xref murni, bukan hibrida

  // Sebuah cuplikan hibrida membawa /XRefStm antara 'trailer' dan 'startxref'
  KeyPos := PosEx('/XRefStm', Tail, TrailerPos);
  StartXrefPos := PosEx('startxref', Tail, TrailerPos);
  Result := (KeyPos > 0) and
    ((StartXrefPos = 0) or (KeyPos < StartXrefPos));
end;

Tiga hasil ini sejalan dengan tiga tata letaknya. File hanya-klasik memiliki sebuah cuplikan namun tanpa /XRefStm: False. File yang sepenuhnya berkomitmen pada aliran referensi-silang sama sekali tidak memiliki kata kunci trailer, kunci cuplikannya berada di dalam kamus aliran: juga False, secara benar, karena file tersebut terkompresi, bukan hibrida. Hanya tata letak berindeks-ganda yang mengembalikan True

Untuk penggunaan produksi, dua pengerasan patut untuk baris ekstra tersebut. Uraikan bilangan bulat setelah /XRefStm, cari (seek) ke offset itu, dan pastikan bahwa objek aliran dengan /Type /XRef benar-benar berada di sana; file yang terpotong dapat membawa kunci tersebut meskipun alirannya telah hilang, yang mana ini berada dalam keranjang yang berbeda dari hibrida yang sehat. Dan perlakukan ukuran jendela sebagai sebuah parameter: 2 KB mencakup keluaran Office biasa, tetapi sebuah kamus cuplikan yang sangat besar dapat mendorong kata kunci ke luar rentang, dan melebarkan jendelanya lebih baik daripada tanpa sengaja menyatakan file tersebut sebagai file klasik

Merutekan file hibrida melalui jalur perpipaan Delphi

Deteksi ini memberi Anda sebuah keputusan perutean (routing). Untuk file yang hanya dibaca, dirender, atau divalidasi, gunakan pemuat yang menyelesaikan (resolves) kedua tampilan, lalu verifikasi perilakunya alih-alih byte-nya. Komponen PDFium mengurai rantai /XRefStm saat pemuatan, sehingga tabel objek yang dilihat kode Anda adalah gabungan dari keduanya, dan pemeriksaan yang dijelaskan dalam artikel kami tentang memvalidasi aliran objek dan referensi-silang berlaku tanpa perubahan. Jika sebuah hibrida yang desinkronisasi rusak sedemikian parah hingga menolak pemuatan, mesin akan melaporkannya melalui kumpulan galatnya, FPDF_ERR_SUCCESS, FPDF_ERR_UNKNOWN, FPDF_ERR_FILE, FPDF_ERR_FORMAT, FPDF_ERR_PASSWORD, FPDF_ERR_SECURITY dan FPDF_ERR_PAGE, dengan FPDF_ERR_FORMAT sebagai yang dihasilkan oleh kerusakan struktural. Namun, jangan bergantung penuh pada sinyal itu: PDFium dirancang untuk bersikap longgar (lenient) dan membangun-ulang sebagian besar file yang tak konsisten secara hening, sehingga sebuah pemuatan yang sukses hanya membuktikan file itu dapat dipulihkan, bukannya bahwa kedua tampilannya sepakat. Pemeriksaan konsistensi yang bermakna adalah membandingkan apa yang ditemukan oleh sebuah penelusuran objek penuh terhadap apa yang dideklarasikan oleh /Size dari cuplikannya

Untuk file-file yang dimodifikasi oleh jalur perpipaan Anda, kebijakan paling aman adalah menghentikan mereka dari menjadi hibrida sama sekali. Sebuah pemuatan yang diikuti oleh sebuah penyimpanan penuh melalui HotPDF akan menulis ulang dokumen dengan sebuah referensi-silang tunggal yang konsisten pada dirinya sendiri dalam satu wujud: tiada /XRefStm, tiada tampilan kedua yang bisa luput dari sinkronisasi, dan setiap objek dimiliki oleh tepat satu entri indeks. Normalisasi semacam itulah yang Anda inginkan sebelum proses asupan arsip (archival ingest), sebelum sebuah RIP hilir yang ketat atau layanan tanda tangan, dan sesudah suntingan apa pun yang diterapkan pada sebuah masukan hibrida. Hal ini berhasil karena pemuat menggabungkan tampilannya dengan benar di saat masuk, sebuah mekanisme yang diulas rinci pada artikel referensi-hibrida HotPDF

Satu-satunya kelas file yang mesti dibiarkan apa adanya adalah dokumen yang ditandatangani secara digital. Sebuah penulisan ulang penuh memindahkan setiap byte, yang mana membatalkan tanda tangan apa pun yang dihitung pada rentang orisinalnya. Perubahan pada sebuah hibrida yang bertanda tangan mesti dimasukkan sebagai pembaruan inkremental utuh yang mempertahankan kedua tampilan tersebut; sebuah file yang semata-mata butuh dibaca mestinya lewat tanpa dijamah. Normalisasi diperuntukkan bagi file-file yang Anda miliki; sementara file-file bertanda tangan hanyalah file yang semata-mata Anda tambahkan (append) kepadanya

PDF referensi-hibrida bukanlah format cacat (malformed); mereka merupakan jembatan kompatibilitas kepunyaan format itu sendiri, dan aplikasi Office akan terus memproduksinya selama pembaca PDF 1.4 bertahan dalam basis pengguna. Sebuah jalur perpipaan yang sanggup melihat kunci /XRefStm, memvalidasi dokumen yang tergabung dengan Komponen PDFium, dan menghasilkan ulang keluaran berindeks-tunggal yang bersih dengan Komponen HotPDF akan memperlakukan mereka sebagaimana aslinya mereka: masukan biasa yang menyandang satu penunjuk tambahan pada cuplikannya