Tři rasterizéry mohou přečíst totéž PDF a neshodnout se na tom, co v něm stojí. Vestavěný engine v PDF Library for Delphi je ten, který se dodává bez jediného souboru navíc a všechno vykresluje kompetentně, a právě proto si zaslouží pozici výchozí volby. Cairo přináší jinou pipeline pro průhlednost a vyhlazování hran a bývá tím, po čem lidé sáhnou, když jinde vycházejí měkké masky nebo režimy prolnutí špatně. PDFium nese vykreslovací kód Chrome, takže stránka, která vypadá správně v prohlížeči, vypadá obvykle správně i pod PDFium, a to za cenu objemné DLL a bitové šířky, na jejíž shodě trvá. Ani jeden z těch tří není správný v abstraktní rovině. Správnost je záležitostí konkrétního dokumentu a jediný poctivý způsob, jak zjistit, který engine daný korpus zvládá, je ten korpus prohnat každým z nich
To je argument pro to brát engine jako volbu za běhu, ne při sestavení. PDF Library for Delphi, knihovna PDF pro Delphi a C++Builder od losLab, staví všechny tři za jediné vykreslovací rozhraní, takže rozhodnutí stojí jedno celé číslo místo větve v kódu. Zbytek se scvrkne na bezpečné přepínání mezi nimi, na ověření, které enginy nasazená binárka skutečně nese, a na udržení vykreslovacího stavu tak, aby tiše neotrávil další úlohu
Tři rasterizéry za jedním rozhraním volání
Knihovna své enginy čísluje. Engine 1 je vestavěný renderer, tedy výchozí volba, na Windows s možnostmi vyhlazování přes GDI+. Engine 2 je Cairo a engine 3 je PDFium, oba se vybírají za běhu přes SelectRenderer. Oba externí enginy se načítají z DLL, jejichž cesty dodáte pomocí SetCairoFileName a SetPDFiumFileName ještě před jejich výběrem. Ať je aktivní kterýkoli engine, práce jde přes tatáž volání: RenderPageToFile, RenderPageToStream, RenderDocumentToFile. Přepnutí enginu posune jedno číslo; zbytek vašeho vykreslovacího kódu si toho vůbec nevšimne
Model cílů sahá daleko za bitmapy. Třída rendereru cílí také na metasoubory (WMF, EMF, EMF+), EPS, přímé kontexty zařízení, tiskárny a HTML5, přičemž Cairo a PDFium se objevují jako další cíle jen tehdy, když byly zakompilovány. Nejviditelněji se ty tři enginy rozcházejí u rastrového výstupu, a proto jej používají i příklady zde
Nikdy nepředpokládejte, že engine existuje: sondujte při startu
Cairo a PDFium jsou funkce podmíněného překladu, což znamená, že binárku lze sestavit zcela bez nich. Když k tomu dojde, požadavek na engine 2 nebo 3 nic nevyvolá. SelectRenderer jednoduše vrátí jinou hodnotu, než jaké ID jste žádali, a kód, který návratovou hodnotu ignoruje, vykresluje dál tím enginem, jenž byl aktivní už předtím. Obranou je startovní sonda, která každý engine požádá, aby se identifikoval, a odpověď zaznamená:
function ProbeEngines(PDF: TPDFlib): string;
begin
Result := 'built-in'; // engine 1 je přítomen vždy
if (PDF.SetCairoFileName('cairo.dll') = 1) and (PDF.SelectRenderer(2) = 2) then
Result := Result + ', cairo';
if (PDF.SetPDFiumFileName('pdfium.dll') = 1) and (PDF.SelectRenderer(3) = 3) then
Result := Result + ', pdfium';
PDF.SelectRenderer(1); // před skutečnou prací obnovte výchozí volbu
end;
Spusťte tu sondu jednou při startu a její výsledek zapište do logu vedle každé vykreslovací úlohy. Naprosto nejčastější otázkou ve chvíli, kdy zákazník hlásí rozdíl ve vykreslení, je, které enginy jeho instalace vlastně má, a jednořádková odpověď ležící v logu to vyřeší bez relace vzdálené plochy. Užitečný vedlejší efekt: pokud vrátí 0 samo SetPDFiumFileName, víte hned, že problémem je DLL (špatná cesta, špatná bitová šířka, chybějící závislost), a nikoli binárka přeložená bez podpory PDFium, protože volání s cestou nic nenašlo ještě dřív, než se SelectRenderer vůbec spustil
Deset výstupních formátů za jedním celým číslem Options
Parametr Options u vykreslovacích volání vybírá kódování výstupu: 0 je BMP, 1 JPEG, 2 WMF, 3 EMF, 4 EPS, 5 PNG, 6 GIF, 7 TIFF, 8 EMF+ a 9 HTML5. PNG (5) je rozumnou výchozí volbou pro náhledy a archivní obrázky stránek. JPEG (1) ve dvojici se SetJPEGQuality je lepší volbou pro fotografické skeny, kde na velikosti souboru záleží víc než na ostrých hranách
Jeden formát skrývá požadavek na cílový stream. Cesta pro BMP nejprve zapíše obrazová data a pak se vrátí na offset 0x26, aby v hlavičce opravila pole s rozlišením. Namiřte to na stream, který umí jen dopředu, tedy na kompresní obálku nebo síťový soket, a volání selže způsobem, jenž se čte jako chyba enginu, ale není jí. Když se cíli bez možnosti skákat na pozici nelze vyhnout, vykreslujte místo toho PNG, nebo BMP připravte přes paměťový stream a zkopírujte jej dál, až bude hotové
DPI, které předáte, není DPI, které dostanete
Každé vykreslovací volání bere argument DPI, jenže rozlišení, které skutečně dostanete, je tato hodnota vynásobená globálním měřítkem vykreslení. SetRenderScale začíná na 1.0 a jakmile jej změníte, nový faktor se tiše uplatní na každé další vykreslení na dané instanci:
PDF.SetRenderScale(2.0); // každé další vykreslení je zdvojnásobené
PDF.RenderPageToFile(150, 1, 5, 'p1.png'); // fakticky 300 DPI
PDF.SetRenderScale(1.0); // resetujte, jinak vám náhledy vyjdou obří
Táž lepivost platí pro SetRenderCropType i pro nastavení kvality JPEG. Ve službě, která z jedné sdílené instance vyrábí náhledy, previewy i obrázky v tiskovém rozlišení, stojí právě tato zbytková nastavení za občasným ticketem „náhledy najednou mají 40 MB“. Ven vedou dvě čisté cesty: buď příslušný stav resetovat na začátku každé operace, nebo každému výstupnímu profilu vyhradit vlastní instanci, aby mezi nimi nic neprosakovalo
Doladění výchozího enginu dřív, než sáhnete po jiném
Překvapivý podíl požadavků typu „potřebujeme jiný engine“ se nakonec ukáže být problémem nastavení v přestrojení. Vestavěný renderer zpřístupňuje své chování při vyhlazování přes SetGDIPlusOptions a širší rodinu SetRenderOptions a SetGDIPlusFileName vám dovolí namířit jej na konkrétní běhové prostředí GDI+, když nasazovací prostředí dodává nějaké neobvyklé. Zubatá čárová grafika při nízkém DPI, rozmazaný text v náhledech, pruhování v přechodech: na tohle všechno zmíněné ovladače reagují a jejich otočení nestojí v instalátoru nic. Přidání Cairo nebo PDFium naproti tomu znamená dodávat další DLL, sledovat druhou či třetí variantu bitové šířky a převzít závazek je aktualizovat
Stížnost na kvalitu má tedy přirozené pořadí kroků. Nejprve ji reprodukujte při přesně témž DPI a měřítku, jaké má zákazník, protože v polovině případů se rozdíl po jejich srovnání vypaří. Dále vyzkoušejte volby vyhlazování vestavěného enginu. Teprve pak dejte stránku vedle sebe napříč enginy se všemi ostatními proměnnými zafixovanými: vykreslete ji do PNG enginy 1, 2 i 3 při totožném DPI a přiložte všechny tři. Obvykle se dva ze tří shodnou a tato většina vám řekne, zda je odchylka v tom, že se dokument interpretuje jinak, nebo v tom, že je posunuté vaše vlastní očekávání. Tři konkrétní obrázky vyřeší spor o „vykresluje se to špatně“ mnohem rychleji než odstavec přídavných jmen
Řetěz záložních variant, který se vysvětlí sám
Jakmile jsou sondování a disciplína stavu na místě, je samotný záložní řetěz krátký. Detekce selhání se opírá o LastRenderError, které nese vlastní text zprávy enginu k poslednímu vykreslení a je prázdné, když vykreslení uspělo:
procedure RenderPageWithFallback(PDF: TPDFlib; Page: Integer; const OutFile: string);
begin
PDF.SelectRenderer(1); // nejprve vestavěný
PDF.RenderPageToFile(200, Page, 5, OutFile); // 5 = PNG
if PDF.LastRenderError = '' then Exit;
LogEngineFailure('built-in', Page, PDF.LastRenderError);
if PDF.SelectRenderer(3) = 3 then // PDFium jako těžká záloha
begin
PDF.RenderPageToFile(200, Page, 5, OutFile);
if PDF.LastRenderError = '' then Exit;
LogEngineFailure('pdfium', Page, PDF.LastRenderError);
end;
raise Exception.CreateFmt('Page %d failed on all available engines', [Page]);
end;
Dva návrhové body tu mají váhu. Řetěz zaznamenává, proč k jednotlivým přepnutím došlo, protože řádek v logu znějící „tato stránka od vydání 3.7 spadá zpět na PDFium“ je regresní signál, který chcete sledovat v monitoringu jako trend, ne jej ztratit. Samo pořadí záloh je politika, kterou stojí za to volit podle typu zátěže. Vestavěný engine se nasazuje bez jediné DLL navíc, což z něj ve většině instalací dělá správný první pokus, zatímco dokumenty nabité skupinami průhlednosti nebo neobvyklým stínováním bývají vůbec tím důvodem, proč tým alternativní engine zapojí. Žádný engine není nejrychlejší obecně, což je celý smysl volby na úrovni jednotlivého volání: proměřte každý z nich na vzorku svých skutečných dokumentů při svém skutečném DPI a k tomu měření se vracejte pokaždé, když se změní DLL enginů nebo skladba dokumentů. Spor vždycky vyhraje korpus
Za hranicí jednotlivých stránek: dávky TIFF a živé kontexty zařízení
Sadu nástrojů dotvářejí dva sousedé volání pro jednu stránku. RenderAsMultipageTIFFToFile vykreslí výraz s rozsahem stránek rovnou do vícestránkového TIFF, což je přirozený tvar pro archivní předávky do systémů správy dokumentů starších než PDF. RenderPageToDC kreslí přímo do kontextu zařízení Windows pro náhledové ovládací prvky a řídí se vlastní trojicí lepivých nastavení (SetRenderDCOffset, SetRenderDCErasePage a typ ořezu), která vyžadují stejnou resetovací disciplínu jako faktor měřítka. Náhled na obrazovce a vykreslování pro tisk mají dost vlastních pastí na to, aby si zasloužily samostatný článek, odkazovaný níže
Kam dál
Jeden zvyk stojí za to si odnést: protože SelectRenderer platí pro každé další volání na instanci, lze jedinou zatvrzelou stránku zkusit znovu jiným enginem, zatímco zbytek dokumentu zůstane na výchozím. K malování náhledů, výběru tiskárny a práci s DevMode pokračujte článkem o náhledu tisku a kontextu zařízení. Když vykreslování napájí vysokoobjemovou pipeline nad velmi rozsáhlými soubory, snoubí se s vykreslováním po stránkách přes DARenderPageToFile přirozeně handlový přístup popsaný v průvodci přímým přístupem
Balení enginů, podporované formáty a zkušební sestavení jsou podrobně popsány na produktové stránce PDF Library for Delphi