Техническа статия

Рендериране на PDF страници в Bitmap в Delphi с HotPDF

HotPDF рендерира заредена PDF страница в Delphi TBitmap чрез едно единствено извикване: RenderLoadedPageToBitmap(PageIndex, DPI); Функцията интерпретира потока от съдържание на страницата и връща притежавана от извикващия 24-битова RGB растерна графика в избраната от вас разделителна способност, което е точно това, от което се нуждаят лента с миниатюри, предварителен преглед при печат или конвейер за експортиране на PDF към изображение; Тази статия разглежда API, а след това и компонента, който отличава един използваем рендериращ модул от обикновена играчка: изчертаване на текст от самите вградени програми за шрифтове, а не от подобни системни шрифтове

Защо рендерирането на PDF страница е по-трудно от изчертаването на изображение?

PDF страницата не е картина; Тя е програма: поток от оператори, които изграждат пътища, избират шрифтове, задават цветове и поставят глифове, изпълнявани спрямо графичния модел, дефиниран в ISO 32000-1 §8; Нищо във файла не казва как изглежда всеки пиксел; За да генерирате растерна графика, трябва да стартирате тази програма — да поддържате текуща матрица на трансформация, стек от графични състояния за q/Q, път за изрязване, цветови пространства за запълване и щриховане — и да растеризирате резултата; Ето защо „простото показване на страница 3 като изображение“ е интерпретатор на потоци от съдържание, а не конвертиране на файлов формат

Рендериращият модул на HotPDF, въведен във v2.253.0, е изграден като шест несвързани модула, отразяващи този модел: ядро за афинни матрици за алгебра на трансформациите в PDF [a b c d e f], стек от графични състояния, софтуер за разрешаване на цветови пространства (DeviceRGB, DeviceGray, DeviceCMYK, Indexed), модул за изграждане на пътища, който свързва PDF операторите за пътища към GDI, слой за метрики на шрифтове, който чете масивите /Widths за правилни отмествания, и интерпретатор, който изпраща оператори и управлява останалите пет; Изображенията XObject преминават през същия стек за декодиране, който библиотеката използва за извличане, така че всеки филтър за изображения, который HotPDF може да декодира за извличане — включително JPEG 2000 изображения, компресирани с JPXDecode — се появява и в рендерирания изход

Рендериране на заредена страница в TBitmap

RenderLoadedPageToBitmap приема индекс на страница с база нула и DPI стойност, където 72 DPI съпоставя една PDF единица от потребителското пространство към един пиксел; Връща nil при неуспех (индекс извън диапазона, липсващи ресурси), вместо да предизвиква изключение, така че визуализаторът може да прескочи повредена страница и да продължи; Извикващият притежава върнатата растерна графика и трябва да я освободи

var
  Pdf: THotPDF;
  Bmp: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('report.pdf') > 0 then
    begin
      Bmp := Pdf.RenderLoadedPageToBitmap(0, 144);  // страница 1 при 144 DPI
      if Bmp <> nil then
      try
        Image1.Picture.Assign(Bmp);
      finally
        Bmp.Free;  // извикващият притежава растерната графика
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

Аргументът за DPI върши работата по мащабиране за всеки често срещан сценарий; Лента с миниатюри се рендерира при 36 или 48 DPI и получава малки, бързи растерни графики; предварителен преглед на екрана при 96 или 144 DPI съответства на типичната плътност на дисплея; експортиране при 300 DPI произвежда изображения с качество за печат; Ротацията на страницата от записа /Rotate и обръщането на началото на /MediaBox (PDF поставя началото долу вляво, GDI горе вляво) се обработват вътре в матрицата страница-към-устройство, така че страница с размер US Letter при 72 DPI се връща като точно 612×792 пиксела, ориентирана правилно

Защо рендерираните миниатюри на PDF показват грешни глифове?

Грешните или приблизителни глифове в рендерирания PDF изход почти винаги означават, че рендериращият софтуер замества вградения в документа шрифт със системен шрифт; Първият рендериращ модул на HotPDF правеше точно това: премахваше префикса на подмножеството от /BaseFont (превръщайки ABCDEF+Arial в Arial), изискваше от GDI системен шрифт с това име и изчертаваше текста с него; За документ, който използва Arial или Times New Roman със стандартно кодиране, резултатът изглежда близък; Но това е просто апроксимация и тя се срива при определени условия

Вградените подмножества от шрифтове са най-лошият случай; Подмножеството на даден шрифт може да съдържа само четиресетте глифа, които документът действително използва, с кодове на символи, присвоени в ред, частен за този файл — код 1 може да бъде „T“, код 2 „h“ и т.н.; Системният шрифт не знае нищо за това частно разпределение, так че текстът или изчезва, или излиза като напълно грешни знаци; Персонализираните кодирания, шрифтовете за символи, шрифтовете за баркодове и всеки шрифт, който не е инсталиран на машината за рендериране, се провалят по същия начин; Рендериращ софтуер, който спира до заместване със системни шрифтове, произвежда миниатюри, които показват страницата разпознаваемо — докато страницата не използва шрифтовете, които са направили вграждането необходимо на първо място

Рендериране на вградени глифове: изчертаване от самата програма за шрифт

HotPDF затвори тази празнина в рамките на пет версии (от v2.268.0 до v2.272.0) чрез анализиране на вградените програми за шрифтове и възпроизвеждане на техните контури на глифове като запълнени векторни пътища на GDI; Текстът в рендерираната страница вече идва от същите данни за контури, които използва съответстващ визуализатор, което означава, че подмножествата от шрифтове, персонализираните кодирания и неинсталираните шрифтове се изобразяват с техните точни форми; Покритието беше изградено по типове шрифтове:

За шрифтове Type0/CIDFontType2 с вградена TrueType програма (FontFile2), рендериращият модул анализира директно таблиците glyf и loca: квадратичните контури се преобразуват в кубичните криви на Безие, които GDI разбира, възстановяват се имплицитните точки върху кривата между последователни точки извън нея, а сложните глифове се възпроизвеждат рекурсивно; Поддържат се както Identity, така и явни разпределения на потока CIDToGIDMap, а отместванията на CID зачитат записите за ширина /W и /DW, така че двубайтов текст с Identity-H се позиционира правилно

Програмите CFF (FontFile3, независимо дали са CIDFontType0C, Type1C или обвивка OpenType) получават пълен интерпретатор на charstring тип 2: линии, криви, семейството Flex, маски за подсказки и локални/глобални извиквания на подпрограми с правилно отместване на подпрограмите; Програмите CFF, индексирани с CID, съпоставят кодовете на символите чрез набора от знаци на шрифта, което е важно за подмножества от шрифтове, чийто ред на глифове се различава от реда на CID, и се зачита изборът на шрифт-DICT за всеки глиф чрез FDArray/FDSelect; Простите (не-CID) TrueType шрифтове декодират еднобайтови кодове чрез собствената таблица cmap на вградения шрифт със стабилна верига от подтаблици — първо Unicode формати 4 и 12, след това подтаблици за символи с частно огледало F000, след това стари формати на Macintosh — докато простите шрифтове Type1 се декодират чрез вграденото кодиране на CFF програмата

Две подобрения допълват картината; Първо, речниците /Encoding за прости шрифтове се разрешават според приоритета, предписан от ISO 32000-1 §9.6.6: масивите /Differences заменят базовото кодиране, което на свой ред заменя собствената карта на програмата за шрифт — пътят, от който зависят инструментите, базирани на TeX и PostScript, с имена на глифове, разрешаващи се чрез Adobe Glyph List, набора от знаци на CFF или TrueType cmap; Второ, шрифтовете Type3, чиито глифове самите са малки потоци от съдържание, се възпроизвеждат чрез рендериращия модул с композирана матрица на шрифта, размер на шрифта и текстова матрица; ширините в пространството на глифовете /Widths се интерпретират чрез /FontMatrix, както изисква ISO 32000-1 §9.6.5, а процедурите за глифове, деклариращи ограничителна рамка d1, се изрязват до нея, така че деформиран глиф на баркод не може да рисува извън своята клетка; Когато даден код не може да бъде съпоставен — повредена програма, несъпоставен символ — рендериращият модул преминава към изчертаване на този глиф със системен шрифт, вместо да премахне целия текстов блок

Как да направите бързи повторни рендерирания?

Отговорът, който HotPDF доставя, е кеш на последно използваните страници: RenderLoadedPageToBitmapCached запазва до RenderCacheCapacity рендерирани страници (по подразбиране 8), индексирани по индекс на страница и DPI, а съвпадение в кеша връща ново, притежавано от извикващия копие, без да докосва потока от съдържание — обикновено хиляди пъти по-бързо от повторното интерпретиране на страницата; Този модел пасва точно на визуализаторите: потребител, прелистващ между две страници, или събитие за преоразмеряване, което изисква отново същата страница при същия DPI, удря кеша всеки път

// Лента с миниатюри: първото преминаване рендерира, превъртането назад удря кеша
for I := 0 to ThumbCount - 1 do
begin
  Bmp := Pdf.RenderLoadedPageToBitmapCached(I, 48);
  if Bmp <> nil then
  try
    ThumbList.AddThumbnail(I, Bmp);
  finally
    Bmp.Free;
  end;
end;

// След редактиране на заредена страница на място:
Pdf.InvalidateRenderedPageCache;  // следващото рендериране отразява промяната

Бъдете честни за паметта, преди да увеличите капацитета; Страница с размер US Letter при 300 DPI е 2550×3300 пиксела, около 25 MB как 24-битова растерна графика, така че осем кеширани страници с разделителна способност за експортиране заемат приблизително 200 MB; При DPI за миниатюри същите осем записа струват под един мегабайт; Разпределете RenderCacheCapacity за DPI, при който действително кеширате, и извикайте InvalidateRenderedPageCache след всяко редактиране на място — кешът е индексиран само по страница и DPI и не може да види, че базовото съдържание се е променило; Зареждането на нов документ го изчиства автоматично

Втори кеш работи под кеша на страниците: декодираните изображения XObject се пазят в хранилище с бюджет за байтове, ограничено от ImageCacheMaxBytes (по подразбиране 32 MB), с извеждане на най-отдавна използваните; Лого или изображение на бланка, повтарящо се на всяка страница, се декодира веднъж при зареждане на документа, вместо при всеки оператор Do, което приблизително наполовина намалява времето за рендериране на страници със споделени изображения и ускорява експортирането на многостраничен TIFF със същата мярка; InvalidateRenderedPageCache изчиства и този кеш

Какво все още се рендерира приблизително

Рендериращият софтуер е насочен към общото подмножество документи на PDF и е добре да се знае къде са границите; Цветовите пространства CalRGB, Lab и ICC-базирани се апроксимират, а не се управляват цветово — поддържат се цветовите пространства на устройствата, Indexed палитрите и взетите като проби цветови справки с функции от тип 0, но файл за професионален печат, разчитащ на ICC намерения за рендериране, няма да бъде колориметрично точен; Шаблоните за засенчване (sh) и режимите на смесване извън простото алфа състояние също са извън обхвата, а рекурсията на Form XObject е ограничена по дълбочина за защита срещу цикли; За фактури, отчети, договори и формуляри — страници, съставени от текст, пътища и изображения — изходът е верен; за дизайнерски проект, пълен с градиенти и групи с прозрачност, третирайте растерната графика като предварителен преглед, а не като доказателство за печат

Практическото разбиране: ако вашият конвейер генерира документи с HotPDF или обработва типични бизнес PDF файлове, RenderLoadedPageToBitmap ги рендерира с точните форми на вградените глифове, правилните CID отмествания и правилната геометрия на страницата; Приближенията живеят в ъглите на графичния модел, които бизнес документите рядко посещават

RenderLoadedPageToBitmap, неговият кеширан вариант и описаният тук конвейер за рендериране на вградени глифове се доставят като част от HotPDF Component за Delphi и C++Builder — собствена VCL библиотека без външни DLL зависимости, покриваща създаване, редактиране на PDF, извличане на текст и рендериране на страници в един пакет