Експортуйте документ із Microsoft Word або Excel за допомогою Зберегти як PDF, і файл на диску найчастіше виявиться гібридно-довідковим файлом. Він містить інформацію про перехресні посилання двічі: один раз як класичну таблицю фіксованої ширини, якою закінчувався кожен PDF до версії 1.4, і один раз як стиснений потік перехресних посилань, від якого насправді залежить більша частина документа. Єдиний ключ трейлера, /XRefStm, зшиває ці два види разом, і те, чи бачить інструмент увесь документ, зводиться до того, чи слідує він цьому ключу
Ця стаття розглядає гібридні файли з боку споживання: як виглядають байти в кінці файлу, як два види розходяться при редагуванні, і як конвеєр Delphi може виявляти та маршрутизувати гібридні вхідні дані. Як завантажувач об'єднує види, і чому порядок не підлягає обговоренню, є темою нашої статті про HotPDF щодо завантаження гібридно-довідкових файлів; ця стаття присвячена розпізнаванню макета в першу чергу
Чому офісні експорти записують індекс двічі
PDF 1.5 представив дві функції, які змінили форму файлу: потоки перехресних посилань, які зберігають індекс об'єктів як стиснені двійкові дані замість таблиці у вигляді звичайного тексту, і потоки об'єктів, які пакують багато дрібних об'єктів в один контейнер зі стисненням Flate. Записувач, який використовує їх, створює менші файли, але читач PDF 1.4 не може відкрити результат, оскільки структури, на які він орієнтується, ключове слово xref та словник trailer, зникли
ISO 32000-1 §7.5.8.4 визначає компроміс. Гібридно-довідковий файл записує обидва варіанти: класичну таблицю перехресних посилань, що адресує об'єкти, до яких має дістатися старий читач, серед яких каталог і дерево сторінок, а також потік перехресних посилань, який індексує все інше. Об'єкти, згорнуті в потоки об'єктів, позначаються як вільні в класичній таблиці, тому читач 1.4 пропускає їх без нарікань; їхні реальні розташування існують лише в потоці. Тоді класичний трейлер несе ключ /XRefStm, що містить зміщення в байтах цього потоку. Старий переглядач ніколи не читає ключ і візуалізує файл із подання таблиці. Сучасний переглядач слідує за ним і бачить увесь документ. Word та Excel випускають саме такий макет роками, тому гібридні файли — це не екзотичний поодинокий випадок, а значна частка того, що отримують бізнес-конвеєри
Як виглядає хвіст гібридного файлу
Макет найпростіше зрозуміти з байтів. Ось хвіст невеликого гібридного файлу зі скороченими зміщеннями; у реальному експорті з Office значення /XRefStm зазвичай є великим зміщенням ближче до кінця файлу. Порядок читання — це прохід із хвоста, описаний в нашому огляді структури файлу PDF: знайти %%EOF, прочитати startxref, перейти до таблиці
% ... body objects, including object streams and, at byte 116,
% the cross-reference stream (a stream object with /Type /XRef) ...
xref % classic section: what startxref points at
0 4
0000000000 65535 f % slot 0: head of the free list, always present
0000000017 00000 n % object 1: the catalog, visible to any reader
0000000000 65535 f % object 2: marked free -- lives in an object stream
0000000000 65535 f % object 3: same; only the stream view locates it
trailer
<<
/Size 4
/Root 1 0 R
/XRefStm 116 % byte offset of the cross-reference stream
>>
startxref
7164 % byte offset of the 'xref' keyword above
%%EOF
Дві деталі в цьому дампі несуть на собі весь механізм. По-перше, startxref вказує на класичний розділ, навмисно: це та адреса, на яку має потрапити старий читач. Потік перехресних посилань доступний лише через ключ /XRefStm у словнику трейлера, тому парсер, який ніколи не шукає цей ключ, ніколи не дізнається про існування потоку. По-друге, об'єкти 2 і 3 — це невинна брехня. Класична таблиця оголошує їх вільними, але це реальні об'єкти, що знаходяться в стиснутому контейнері; позначення вільними — це те, що не дає читачу 1.4 спіткнутися об записи, які він не може використати. Споживач, який довіряє лише класичному вигляду, доходить висновку, що більша частина цього документа не існує
Як два види розходяться
Гібридний файл щойно з Word є внутрішньо узгодженим: обидва види описують один і той самий документ, кожен у межах своєї заявленої області. Неприємності починаються, коли файл редагується інструментом, який розуміє лише один із видів. Уявіть собі утиліту для штампування, яка додає інкрементне оновлення в класичному стилі: нові об'єкти, новий розділ xref, ланцюжок /Prev до попереднього розділу та новий трейлер. Якщо цей трейлер відкидає ключ /XRefStm, вид потоку залишається сиротою; якщо він копіює старе значення вперед, вид потоку все ще описує документ таким, яким він був до редагування. У будь-якому випадку два індекси тепер не згодні щодо того, що містить файл
Отриманий файл має характерну ознаку помилки: об'єкти, видимі в одному виді, відсутні або застарілі в іншому. Читач, який розв'язує через вигляд потоку, знаходить передедитовану версію оновленого об'єкта або взагалі не знаходить запису для доданого. Читач табличного вигляду бачить редагування, але втрачає слід стиснутих об'єктів, які визначає лише потік. На практиці це виглядає як поля форми, які виживають в одному переглядачі та зникають в іншому, анотації, які, здається, були видалені під час проходу штампування, або пошуки, які потрапляють зовсім не на той об'єкт
Що робить ці файли дорогими для налагодження, так це те, що Adobe Acrobat зазвичай відкриває їх без скарг: коли індекс не збігається з байтами, він тихо перебудовує дані перехресних посилань, скануючи заголовки об'єктів, тому той, хто створив зламаний файл, не бачить нічого поганого. Помилка спливає пізніше, коли файл потрапляє до суворого споживача, валідатора переддрукарської підготовки, сервісу підпису, завдання архівного поглинання, який довіряє заявленій структурі та повідомляє про відсутні об'єкти або невідповідність перехресних посилань. «Він чудово відкривається в Acrobat» — саме так починається майже кожен тікет про розсинхронізацію гібридів
Виявлення гібридного файлу в чистому Delphi
Класифікація вхідних даних не вимагає бібліотеки PDF. Ключ /XRefStm може зустрічатися лише всередині класичного словника трейлера, а активний трейлер розташований в межах останніх кількох кілобайтів файлу, оскільки специфікація вимагає появи %%EOF поблизу фізичного кінця. Читання обмеженого вікна хвоста та його пошук є достатнім для сортування:
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;
// Every keyword involved is 7-bit ASCII, so a byte-wise decode is safe
Tail := TEncoding.ANSI.GetString(Buf);
// Find the LAST 'trailer' keyword: with incremental updates,
// the newest trailer is the one that governs the 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; // no classic trailer: a pure xref-stream file, not hybrid
// A hybrid trailer carries /XRefStm between 'trailer' and 'startxref'
KeyPos := PosEx('/XRefStm', Tail, TrailerPos);
StartXrefPos := PosEx('startxref', Tail, TrailerPos);
Result := (KeyPos > 0) and
((StartXrefPos = 0) or (KeyPos < StartXrefPos));
end;
Три результати збігаються з трьома макетами. Файл лише в класичному форматі має трейлер, але не має /XRefStm: False. Файл, який повністю переходить на потоки перехресних посилань, взагалі не має ключового слова trailer, ключі його трейлера знаходяться у словнику потоку: також False, і це правильно, оскільки такий файл стиснутий, а не гібридний. Тільки макет із подвійним індексуванням повертає True
Для використання у виробництві два посилення варті додаткових рядків. Розберіть ціле число після /XRefStm, перейдіть до цього зміщення та переконайтеся, що об'єкт потоку з /Type /XRef дійсно там знаходиться; обрізаний файл може містити ключ, у той час як потік зник, що належить до іншої категорії, ніж здоровий гібрид. І розглядайте розмір вікна як параметр: 2 КБ охоплюють звичайний вивід Office, але незвично великий словник трейлера може витіснити ключове слово за межі діапазону, і розширення вікна краще, ніж випадкове оголошення файлу класичним
Маршрутизація гібридних файлів через конвеєр Delphi
Виявлення дає вам рішення про маршрутизацію. Для файлів, які лише читаються, візуалізуються або перевіряються, використовуйте завантажувач, який розв'язує обидва види, а потім перевіряйте поведінку, а не байти. Компонент PDFium розбирає ланцюжок /XRefStm під час завантаження, тому таблиця об'єктів, яку бачить ваш код, є об'єднаною, і перевірки, описані в нашій статті про перевірку потоків об'єктів і перехресних посилань, застосовуються без змін. Якщо десинхронізований гібрид настільки сильно пошкоджений, що відмовляється завантажуватися, рушій повідомляє про це через свій набір помилок, FPDF_ERR_SUCCESS, FPDF_ERR_UNKNOWN, FPDF_ERR_FILE, FPDF_ERR_FORMAT, FPDF_ERR_PASSWORD, FPDF_ERR_SECURITY та FPDF_ERR_PAGE, причому FPDF_ERR_FORMAT виникає через структурне пошкодження. Однак не покладайтеся на цей сигнал: PDFium поблажливий за своїм дизайном і тихо перебудовує більшість невідповідних файлів, тому успішне завантаження доводить, що файл можна було відновити, а не те, що його два види збігаються. Значуща перевірка узгодженості полягає в порівнянні того, що знаходить повний обхід об'єктів, з тим, що заявляє /Size трейлера
Для файлів, які модифікує ваш конвеєр, найбезпечнішою політикою є зробити так, щоб вони взагалі перестали бути гібридними. Завантаження з наступним повним збереженням через HotPDF перезаписує документ із єдиним, самоузгодженим перехресним посиланням в одній формі: жодного /XRefStm, жодного другого виду, який міг би розсинхронізуватися, кожним об'єктом володіє рівно один запис індексу. Саме такої нормалізації ви хочете перед архівацією, перед суворим подальшим RIP або сервісом підпису, і після будь-якого редагування гібридних вхідних даних. Вона працює, тому що завантажувач правильно об'єднав види під час надходження — цей механізм детально розглядається в статті про гібридні посилання HotPDF
Єдиний клас файлів, які варто залишити в спокої — це документи з цифровим підписом. Повний перезапис переміщує кожен байт, що робить недійсним будь-який підпис, обчислений для початкових діапазонів. Зміна підписаного гібрида повинна входити як належне інкрементне оновлення, яке зберігає обидва види; файл, який потребує лише читання, має проходити недоторканим. Нормалізація призначена для файлів, якими ви володієте; підписані файли ви лише доповнюєте
Гібридно-довідкові PDF-файли не є неправильно сформованими; це власний міст сумісності формату, і програми Office продовжуватимуть створювати їх доти, доки читачі PDF 1.4 зберігатимуться в базі встановлень. Конвеєр, який може помітити ключ /XRefStm, перевірити об'єднаний документ за допомогою PDFium Component та перегенерувати чистий вивід з єдиним індексом за допомогою HotPDF Component, ставиться до них так, як вони є: як до звичайних вхідних даних з одним додатковим вказівником у трейлері