Műszaki cikk

Többmotoros PDF-rajzolás Delphiben

Három raszterelő olvashatja ugyanazt a PDF-et, és mégis vitatkozhatnak azon, mit is mond. A PDF Library for Delphi beépített motorja az, amely extra fájlok nélkül érkezik és mindent hozzáértően kirajzol, ezért érdemli ki az alapértelmezett helyet. A Cairo más átlátszósági és élsimítási munkafolyamatot hoz, és jellemzően akkor nyúlnak hozzá, amikor a lágy maszkok vagy a keverési módok máshol rosszul jönnek ki. A PDFium a Chrome rajzolókódját viszi, tehát az az oldal, amely böngészőben jól néz ki, PDFium alatt is jól néz ki általában — cserébe egy tekintélyes DLL-t kér, és ragaszkodik a bitszélesség egyezéséhez. Elvont értelemben egyik sem helyes a három közül. A helyesség dokumentumfüggő, és az egyetlen őszinte mód annak megtudására, melyik motor bírja el az adott korpuszt, ha átfuttatja azt mindegyiken

Ez az érv amellett, hogy a motort futásidejű, ne fordítási idejű döntésnek tekintsük. A PDF Library for Delphi, a losLab Delphihez és C++Builderhez készült PDF-könyvtára, mind a hármat egyetlen rajzolási felület mögé teszi, így a döntés egyetlen egész számba kerül egy kódelágazás helyett. A többi arról szól, hogyan válasszon köztük biztonságosan, hogyan igazolja, mely motorokat hordozza valójában egy telepített bináris, és hogyan akadályozza meg, hogy a rajzolási állapot csendben megmérgezze a következő feladatot

Három raszterelő egyetlen hívási felület mögött

A könyvtár számozza a motorjait. Az 1-es motor a beépített rajzoló, az alapértelmezés, Windows alatt GDI+ simítási beállításokkal. A 2-es motor a Cairo, a 3-as a PDFium, mindkettő futásidőben választható a SelectRenderer hívással. A két külső motor olyan DLL-ekből töltődik be, amelyek elérési útját a SetCairoFileName és a SetPDFiumFileName hívással adja meg a kiválasztásuk előtt. Bármelyik motor is aktív, a munka ugyanazokon a hívásokon megy át: RenderPageToFile, RenderPageToStream, RenderDocumentToFile. A motorváltás egyetlen szám mozgatása; a rajzolási kódja többi része észre sem veszi

A célmodell messze túlmutat a bitképeken. A rajzolóosztály metafájlokat (WMF, EMF, EMF+), EPS-t, közvetlen eszközkörnyezeteket, nyomtatókat és HTML5-öt is céloz, a Cairo és a PDFium pedig csak akkor jelenik meg további célként, ha befordították őket. A raszteres kimenet az, ahol a három motor a leglátványosabban szétválik, ezért az itteni példák ezt használják

Három PDF-rajzolómotor egyetlen hívási felület mögött: a SelectRenderer a beépített motor, a Cairo és a PDFium között vált, miközben az alkalmazáskód ugyanazokat a rajzolófüggvényeket hívja
A SelectRenderer egyetlen egész szám fejében viszi át a munkát a beépített, a Cairo és a PDFium motor között. Az alkalmazáskód továbbra is a RenderPageToFile és társai hívásait használja, bármelyik motor is állította elő a képpontokat

Soha ne feltételezze, hogy egy motor létezik: vizsgálja meg induláskor

A Cairo és a PDFium feltételes fordítási szolgáltatás, ami azt jelenti, hogy egy bináris teljesen nélkülük is elkészülhet. Ha ez történik, a 2-es vagy 3-as motor kérése nem vált ki semmit. A SelectRenderer egyszerűen a kért azonosítótól eltérő értéket ad vissza, és az a kód, amely figyelmen kívül hagyja a visszatérési értéket, továbbra is azzal a motorral rajzol, amely már aktív volt. A védekezés egy indulási vizsgálat, amely minden motort megkér, hogy azonosítsa magát, és rögzíti a választ:

PDF Library for Delphi: indulási motorvizsgálat folyamatábrája, amelyben minden rajzoló megerősíti a DLL-jének elérési útját és a SelectRenderer válaszát, mielőtt az elérhetőségi összegzés minden rajzolási feladat mellé a naplóba kerül
A sikertelen útvonalhívás a DLL-t vádolja, míg az eltérő SelectRenderer eredmény azt jelenti, hogy a binárisba soha nem fordították bele a motort. A vizsgálat egyszer fut, és az egysoros összegzése a legtöbb ügyféloldali rajzolási kérdést eldönti
function ProbeEngines(PDF: TPDFlib): string;
begin
  Result := 'built-in';                        // az 1-es motor mindig jelen van
  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);                       // az alapértelmezés visszaállítása a valódi munka előtt
end;

Futtassa ezt a vizsgálatot egyszer induláskor, és írja az eredményét a naplóba minden rajzolási feladat mellé. A leggyakoribb kérdés, amikor egy ügyfél rajzolási eltérést jelent, az, hogy a telepítése valójában mely motorokkal rendelkezik, és egy egysoros válasz a naplóban ezt távoli asztali munkamenet nélkül tisztázza. Hasznos mellékhatás: ha maga a SetPDFiumFileName ad vissza 0 értéket, akkor már tudja, hogy a DLL a gond (rossz útvonal, rossz bitszélesség, hiányzó függőség), nem pedig egy PDFium-támogatás nélkül fordított bináris, mert az útvonalhívás semmit sem oldott fel, még mielőtt a SelectRenderer egyáltalán lefutott volna

Tíz kimeneti formátum egyetlen Options egész szám mögött

A rajzolási hívások Options paramétere választja ki a kimeneti kódolást: 0 a BMP, 1 a JPEG, 2 a WMF, 3 az EMF, 4 az EPS, 5 a PNG, 6 a GIF, 7 a TIFF, 8 az EMF+, 9 pedig a HTML5. Az 5-ös PNG az ésszerű alapértelmezés előnézetekhez és archiválási oldalképekhez. Az 1-es JPEG a SetJPEGQuality hívással párosítva jobb választás fényképszerű beolvasásokhoz, ahol a fájlméret többet számít, mint az éles kontúrok

Az egyik formátum követelményt rejt a célfolyammal szemben. A BMP-út először a képadatokat írja ki, majd visszaugrik a 0x26 eltoláshoz, hogy megfoltozza a fejléc felbontásmezőit. Irányítsa ezt egy csak előre haladó folyamra, egy tömörítő burkolóra vagy egy hálózati aljzatra, és a hívás úgy hal el, hogy az motorhibának olvasódik, pedig nem az. Ha a nem pozicionálható cél elkerülhetetlen, rajzoljon inkább PNG-t, vagy állomásoztassa a BMP-t egy memóriafolyamon, és másolja tovább, ha elkészült

Az átadott DPI nem az a DPI, amit kap

Minden rajzolási hívás kap egy DPI argumentumot, de a ténylegesen kapott felbontás ez az érték a globális rajzolási méretaránnyal megszorozva. A SetRenderScale 1,0 értékről indul, és ha egyszer megváltoztatja, az új szorzó némán érvényesül az adott példány minden későbbi rajzolására:

PDF.SetRenderScale(2.0);                    // minden későbbi rajzolás megkétszereződik
PDF.RenderPageToFile(150, 1, 5, 'p1.png');  // gyakorlatilag 300 DPI
PDF.SetRenderScale(1.0);                    // állítsa vissza, különben óriási bélyegképeket kap

Ugyanez a ragadósság vonatkozik a SetRenderCropType hívásra és a JPEG-minőség beállítására. Egy olyan szolgáltatásban, amely bélyegképeket, előnézeteket és nyomtatási felbontású képeket állít elő egyetlen közös példányból, ezek az ottfelejtett beállítások állnak valójában az alkalmankénti „a bélyegképek hirtelen 40 MB-osak” hibajegy mögött. Két tiszta kiút van: vagy visszaállítja az érintett állapotot minden művelet elején, vagy külön példányt szentel minden kimeneti profilnak, hogy semmi ne szivárogjon át közöttük

Hangolja az alapértelmezett motort, mielőtt másikhoz nyúlna

A „másik motorra van szükségünk” kérések meglepően nagy hányadáról derül ki, hogy álruhába bújt beállítási gondok. A beépített rajzoló a SetGDIPlusOptions hívással és a tágabb SetRenderOptions családdal teszi elérhetővé a simítási viselkedését, a SetGDIPlusFileName pedig lehetővé teszi, hogy egy adott GDI+ futtatókörnyezetre irányítsa, amikor a telepítési környezet szokatlant szállít. Recés vonalas ábrák alacsony DPI-n, homályos szöveg a bélyegképeken, sávosodás a színátmenetekben: mindegyik reagál ezekre a gombokra, és a tekergetésük semmibe sem kerül a telepítőben. A Cairo vagy a PDFium hozzáadása ezzel szemben több DLL szállítását, egy második vagy harmadik bitszélességű változat követését és a frissítésük kötelezettségét jelenti

Egy minőségi panasznak tehát természetes műveleti sorrendje van. Először reprodukálja az ügyfél pontos DPI-jén és méretarányán, mert az esetek felében az eltérés elpárolog, amint ezek egyeznek. Utána próbálja ki a beépített motor simítási beállításait. Csak ezután állítsa egymás mellé az oldalt a motorokon át, minden más változót rögzítve: rajzolja ki PNG-be az 1-es, a 2-es és a 3-as motorral azonos DPI-n, és csatolja mind a hármat. A háromból általában kettő egyetért, és ez a többség árulja el, hogy a kilógó eset a dokumentum eltérő értelmezése-e, vagy a saját kiindulási elvárása téves. Három konkrét kép sokkal gyorsabban zárja le a „rosszul rajzol” vitát, mint egy bekezdésnyi jelző

Tartaléklánc, amely megmagyarázza magát

Ha a vizsgálat és az állapotfegyelem a helyén van, maga a tartaléklánc rövid. A hiba észlelése a LastRenderError értékén múlik, amely a legutóbbi rajzolásra vonatkozóan a motor saját üzenetszövegét tartalmazza, és üres, ha a rajzolás sikerült:

PDF-rajzolási tartaléklánc: először a beépített motor próbálkozik, a hibák naplóba kerülnek, a PDFium újrapróbálkozik, és kiváltott kivétel jelzi, ha az összes elérhető motor elhasal egy oldalon
Minden próbálkozás ellenőrzi a LastRenderError értékét, és naplózza az okot a motorváltás előtt. A lánc csak akkor vált ki kivételt, ha minden telepített motor elhasalt, és az összegyűjtött okok addigra már a naplóban ülnek
procedure RenderPageWithFallback(PDF: TPDFlib; Page: Integer; const OutFile: string);
begin
  PDF.SelectRenderer(1);                            // először a beépített
  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                 // a PDFium a nehézsúlyú tartalék
  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;

Két tervezési szempont nyom itt a latban. A lánc rögzíti, miért történt az egyes váltások, mert az a naplósor, amely szerint „ez az oldal a 3.7-es kiadás óta a PDFiumra esik vissza”, olyan regressziós jelzés, amelyet inkább a felügyeletben szeretne trendként látni, mint elveszíteni. Maga a tartalék sorrendje munkaterhelésenként megválasztandó irányelv. A beépített motor extra DLL nélkül telepszik, ami a legtöbb telepítésben helyes első próbává teszi, míg az átlátszósági csoportokkal vagy szokatlan árnyalásokkal terhelt dokumentumok a szokásos ok arra, hogy egy csapat egyáltalán alternatív motort drótozzon be. Általánosságban egyik motor sem a leggyorsabb, és éppen ez a hívásonkénti választás lényege: mérje meg mindegyiket a valódi dokumentumainak egy mintáján, a valódi DPI-jén, és nézze meg újra ezt a mérést, valahányszor a motor-DLL-ek vagy a dokumentumösszetétel megváltozik. A korpusz mindig megnyeri a vitát

Az egyes oldalakon túl: TIFF-kötegek és élő eszközkörnyezetek

Az oldalankénti hívások két szomszédja teszi teljessé az eszköztárat. A RenderAsMultipageTIFFToFile egy oldaltartomány-kifejezést közvetlenül többoldalas TIFF-be rajzol, ami a természetes forma az archiválási átadásokhoz olyan dokumentumkezelő rendszerek felé, amelyek megelőzik a PDF-et. A RenderPageToDC közvetlenül egy Windows-eszközkörnyezetre fest előnézeti vezérlők számára, és a saját három ragadós beállítása (SetRenderDCOffset, SetRenderDCErasePage, plusz a vágási típus) irányítja, amelyek ugyanazt a visszaállítási fegyelmet igénylik, mint a méretarány. A képernyős előnézet és a nyomtatási úton történő rajzolás elég saját csapdát hordoz ahhoz, hogy külön cikket érdemeljen, amelyre lentebb hivatkozunk

Merre tovább?

Egy szokás, amelyet érdemes magával vinnie: mivel a SelectRenderer a példány minden későbbi hívására érvénybe lép, egyetlen makacs oldal újrapróbálható másik motoron, miközben a dokumentum többi része az alapértelmezésen marad. Az előnézeti festéshez, a nyomtatóválasztáshoz és a DevMode kezeléséhez folytassa a nyomtatási előnézetről és az eszközkörnyezetről szóló cikkel. Ha a rajzolás nagy áteresztésű munkafolyamatot lát el nagyon nagy fájlokon, a közvetlen eléréses útmutató leíró alapú megközelítése természetesen párosul a DARenderPageToFile oldalankénti rajzolásával

A motorok csomagolását, a támogatott formátumokat és a próbaverziókat a PDF Library for Delphi termékoldala részletezi