Technical Article

Линеаризация на PDF и Fast Web View: Как работи

Поставете сканиран отчет от 80 MB зад връзка, отворете го в браузър и вижте какво се случва: програмата за преглед стои на празен екран, докато не пристигне голяма част от тези байтове, след което изведнъж изобразява първа страница. Преминете на страница 40 и при лошо конструиран файл цялото изтегляне може да започне отначало. Разочароващата част е, че четецът е искал само първата страница. Линеаризацията е структурният отговор на този проблем. Тя пренарежда PDF файла, така че програмата за преглед да може да визуализира първата страница от малък префикс на файла и да извлича останалата част при поискване â€?ето защо Adobe популяризира тази функция като „Fast Web Viewâ€?(Бърз уеб преглед).

Нищо от това не представлява различен файлов формат. Линеаризираният PDF е обикновен PDF файл, който съвместимият четец ще отвори без специална обработка. Трикът е изцяло в начина на подреждане на байтовете и в две допълнителни структури, които файлът носи. Стандартът ISO 32000-1 описва цялата подредба в Приложение F, и след като видите оформлението, поведението спира да изглежда като магия и започва да изглежда като съзнателен компромис на реда на файловете в името на по-ниското време за изобразяване на първата страница.

Какво всъщност пренарежда линеаризацията

Нормалният PDF файл може да разпръсква своите обекти в почти всякакъв ред. Таблицата с кръстосани препратки (cross-reference table) в края на файла прави това възможно: четецът отива до края, прочита показалеца startxref, зарежда таблицата и оттам може да локализира всеки обект по неговото отместване. Този дизайн е отличен за локални файлове, където търсенето до края не струва нищо, но е лош за файл, който се предава по мрежа, където краят е точно частта, която пристига последна. За да визуализира първа страница, конвенционалният четец се нуждае от обекта на страницата, нейното съдържание (content stream), шрифтовете, които реферира, и всички изображения, които изобразява, а в неподреден файл те могат да се намират навсякъде, включително в последния мегабайт.

Линеаризацията коригира този ред. Обектите, необходими за показване на първата страница, се събират в непрекъснат блок близо до началото, веднага след малка заглавна част, така че да пристигнат рано в потока от байтове. Всичко останало â€?следващите страници и ресурсите, които те споделят â€?следва в предвидима последователност. Втора, пълна таблица с кръстосани препратки все още съществува в края на файла за четци, които игнорират тази оптимизация, но линеаризираният файл също така поставя кръстосани препратки за първата страница и параметрите, от които се нуждае потокът, най-отпред. Четецът вече не трябва да достига до края, преди да може да изрисува каквото и да било.

Наборът от обекти на първа страница и параметричният речник за линеаризация

Първият обект в линеаризирания файл, веднага след заглавната част %PDF, е параметричният речник за линеаризация (linearization parameter dictionary). Точно него търси четецът, за да реши дали оптимизацията присъства и как да я използва. Речникът записва дължината на целия файл, байтовото отместване, откъдето започва основната таблица с кръстосани препратки, номера на обекта на първата страница, както и местоположението и дължината на следващия hint поток (hint stream). С тези числа четецът знае само от началните килобайти колко данни трябва да извлече, за да покаже първа страница, и къде да търси индекса, който му позволява да прескочи другаде.

Приложение F е строго по отношение на това какво означава „първа страницаâ€?тук. Секцията за първата страница трябва да съдържа самия обект на страницата, неговите потоци от съдържание и ресурсите, които тези потоци реферира, така че страницата да бъде самодостатъчна веднага щом този префикс се изтегли. Споделените ресурси (шрифт, използван на всяка страница, или лого, което се повтаря в горната част) се обработват специално: те се появяват достатъчно рано, за да обслужат първата страница, но са маркирани като споделени, така че четецът да не ги изтегля отново, когато по-късно изобразява страница 30. Тази разлика между частни за страницата и споделени обекти е частта, която повечето домашно написани „оптимизаториâ€?бъркат, а тази грешка води до файл, който твърди, че е линеаризиран, но все пак бави визуализацията.

Hint потоци: индексът, който прави скоковете между страниците евтини

Бързото показване на първа страница е само половината от стойността. Другата половина е прескачането до произволна страница, без да се изтегля всичко по средата, и именно това осигуряват hint потоците. Линеаризираният файл съдържа таблица с подсказки за отместването на страниците (page offset hint table) и таблица с подсказки за споделени обекти (shared object hint table), съхранявани като поток, рефериран от параметричния речник. Таблицата за отместване на страници записва за всяка страница къде започват нейните обекти във файла и каква е тяхната дължина. Таблицата за споделени обекти прави същото за ресурсите, използвани на множество страници.

Благодарение на тези таблици, четецът, който иска страница 40, не анализира файла последователно. Той се консултира с таблицата с подсказки, за да разбере байтовия диапазон, който заема страница 40, изисква точно този диапазон от сървъра и изобразява страницата, когато тези байтове пристигнат, изтегляйки всички споделени ресурси, които все още няма, чрез същия механизъм. Hint потокът на практика е карта за произволен достъп, положена над документа, и именно това е причината добре линеаризиран файл от 500 страници да се усеща бърз при бавна връзка, докато неоптимизиран файл със същия размер бави потребителя.

Защо сървърът трябва да сътрудничи

Линеаризацията предполага, че транспортният протокол може да доставя произволни части от файла, и тази предпоставка си струва да бъде проверена, преди да обвинявате формата за лоши резултати. Механизмът е HTTP byte-serving: четецът изпраща заявки за диапазони (range requests), а сървърът им отговаря с 206 Partial Content. Ако сървърът не рекламира Accept-Ranges: bytes, или ако прокси или CDN пред него превърнат заявките за диапазони в пълни трансфери, четецът няма как да извлече страница 40 изолирано и се връща към изтегляне на целия файл. В този случай структурата в PDF файла е перфектна, но напълно безполезна.

Това е повредата, която най-често се диагностицира погрешно как „линеаризацията не работиâ€? Файлът е наред; пътят на доставка не е. Преди да пренастроите документ, потвърдете с условна заявка, че хостът наистина връща частично съдържание за URL адреса, който четецът достъпва. Много статични хостове правят това по подразбиране, но много лошо конфигурирани сървъри за приложения и кеширащи слоеве не го поддържат.

Инкременталните актуализации тихо развалят линеаризацията

Ето го ограничението, което изненадва хората, които генерират линеаризирани файлове правилно и след това се чудят защо оптимизацията изчезва. Линеаризацията зависи от едно-единствено, внимателно подредено оформление с неговия индекс най-отпред. Инкременталната актуализация (incremental update) нарушава това по дизайн. Когато даден инструмент добавя подпис, попълва поле във формуляр или добавя анотация чрез инкрементално записване, той не пренаписва целия файл. Той добавя променените обекти, нова таблица с кръстосани препратки и нов trailer в самия край, оставяйки оригиналните байтове непокътнати. Това добавяне е целият смисъл на инкременталните актуализации: то е бързо и запазва по-ранната ревизия за одит или валидиране на подписа.

Страничният ефект е, че файлът вече има най-новите си данни за кръстосани препратки в края, след внимателно поставения блок за първата страница, а параметричният речник за линеаризация най-отпред описва оформлението, което вече не съответства на файла. Съвместимият четец открива несъответствието и третира документа като обикновен, нелинеаризиран PDF файл. Fast Web View изчезва, въпреки че оригиналната линеаризирана структура все още стои в първата половина на файла. Ако добавите няколко актуализации, всяка от тях натрупва нова ревизия накрая и разликата между остарелия индекс отпред и реалното състояние се увеличава.

Ако вашият работен процес изисква както редактиране, така и Fast Web View, правилото произтича директно от структурата: редактирайте инкрементално, докато документът е в процес на промяна, и след това го линеаризирайте отново накрая. Пълното пренаписване е това, което възстановява оформлението. В контекста на HotPDF това означава, че редакцията в процес на изпълнение минава през BeginIncrementalUpdate и SaveIncrementalUpdate, които добавят делта, докато последната стъпка зарежда целия документ и го сериализира наново с LoadFromFile, последвано от SaveLoadedDocument, което изхвърля натрупаните стари ревизии и излъчва едно чисто оформление. Същият компромис се появява и при потоците от обекти: активирането на UseObjectStreams заедно с UseXRefStream компресира таблицата с кръстосани препратки и опакова обектите плътно, което помага за размера на файла, но като всеки структурен избор трябва да бъде приложено по време на това финално пренаписване, а не просто прикачено към добавена ревизия.

// In-flight edits: append a delta, keep prior revisions intact.
// This leaves the file NOT linearized.
Pdf.BeginIncrementalUpdate('report.pdf');
Pdf.AddPage;
Pdf.CurrentPage.TextOut(72, 760, 0, 'Addendum');
Pdf.SaveIncrementalUpdate('report.pdf');

// Finishing step: full re-serialization produces one clean layout,
// dropping the stacked revisions. Re-run your linearizer on the output.
Pdf.LoadFromFile('report.pdf');
Pdf.SaveLoadedDocument('report-final.pdf');

HotPDF не предлага извикване с една функция за линеаризация, така че практическият модел е да се произведе чист, напълно пренаписан файл и върху него да се изпълни специализиран оптимизатор. Инструментите за команден ред се справят с пренареждането директно. qpdf пренаписва файл в линеаризирана форма с един единствен флаг:

qpdf --linearize report-final.pdf report-web.pdf

Как да разберете дали файлът е линеаризиран

Не се доверявайте на името на файла или на инструмента, който твърди, че го е създал; проверете байтовете. Най-директната проверка е в началото на файла: отворете го и потърсете параметричния речник за линеаризация като първия обект след заглавната част, носещ ключ /Linearized. Пътят за бърза проверка от потребителя е диалоговият прозорец Document Properties на Acrobat, който съобщава „Fast Web View: Yesâ€?само когато структурата е реално налична и актуална.

За автоматизирани проверки qpdf съобщава както за присъствието, така и за целостта на структурата â€?което е важно, тъй като файлът може да носи речник за линеаризация, който вече не отразява неговото оформление (точно състоянието, което инкременталната актуализация оставя след себе си):

# Reports "File is linearized" and validates hint tables against the layout
qpdf --check report-web.pdf

# Dumps the linearization parameters and hint data in detail
qpdf --show-linearization report-web.pdf

Стъпката на валидиране е тази, която наистина си заслужава усилията. Проверка, която само потвърждава съществуването на речника, ще одобри с удоволствие файл, чийто индекс сочи към грешни отмествания; проверка, която съгласува hint таблиците с действителните позиции на обектите, е това, което ви казва дали оптимизацията ще издържи под реалните заявки за диапазони на четеца.

Линеаризацията все още си струва да се прилага към всеки голям документ, обслужван по уеб, особено за мобилни четци с нестабилна връзка, а тя струва само няколко процента от размера на файла за индекса в началото. Двете неща, които трябва да имате предвид, са, че структурата в PDF файла и byte-serving извън него трябва да бъдат правилно настроени, както и че всяко редактиране след това отменя оптимизацията, докато не пренапишете файла. Третирайте ре-линеаризацията като последна стъпка в процеса, след като всяка друга промяна е приключила. Описаното тук поведение при кръстосани препратки, потоци от обекти и инкрементални актуализации е част от структурния модел, който HotPDF Component за Delphi и C++Builder имплементира; за по-широка информация относно оформлението на файлове вижте как е структуриран PDF файлът, а за инкременталните актуализации и работата с големи файлове в код вижте обработка на големи PDF файлове от Delphi.