Műszaki cikk

EMF- és WMF-vektorok importálása Delphi PDF-ekbe a HotPDF-fel

A HotPDF, a natív Delphi és C++Builder PDF-komponens, úgy importálja a Windows EMF és WMF metafájlokat, hogy minden egyes GDI-rekordot közvetlenül PDF-operátorokra értelmez, ahelyett hogy a fájlt bitképpé laposítaná: a színátmenetes kitöltésekből PDF axiális árnyalási minták (shading pattern) lesznek, a sraffozott ecsetekből PDF csempézett minták (tiling pattern), és egy központosított útvonal-állapot kapu (path-state gate) megakadályozza, hogy hibás formátumú rekordok tönkretegyék a kimenetet. Minden diagram, amelyet egy TChart, egy GDI+ felület vagy egy egyszerű TCanvas ki tud exportálni bővített metafájlként, jelölt erre az útvonalra, és a különbség abban a pillanatban megmutatkozik, amikor valaki ránagyít az oldalra, vagy egy nagy felbontású nyomtatóra küldi

Az alternatíva, amelyhez a legtöbb Delphi-fejlesztő alapból nyúl, a metafájl bitképpé rasterizálása, mielőtt az oldalra kerülne, és a költsége csak később mutatkozik meg: egy oszlopdiagram, amely képernyőn éles volt, láthatóan foltossá válik abban a pillanatban, amikor a PDF-et 600 DPI-n nyomtatják ki, vagy egy tárgyalóterem kivetítőjén vetítik, és egy sraffozott kitöltésű CAD-terület egyetlen sima szürke téglalappá omlik össze, ha a kitöltési stílus nem öröklődik tovább. A metafájl programként, nem pedig képként való olvasása kerüli el mindkét problémát, és ez a nehezebben helyesen megvalósítható út, ezért érdemes ismerni az alábbi buktatókat, mielőtt egy jelentés kimegy

Miért érdemes egy metafájlt értelmezni ahelyett, hogy bitképpé laposítanánk?

A HotPDF azért tartja vektoros úton az EMF- és WMF-importot, mert egy Windows-metafájl GDI-rajzolási hívások rögzített sorozata, nem pedig kép, és ezeknek a hívásoknak PDF útvonal-, szöveg- és árnyalás-operátorokként történő újrajátszása teszi lehetővé, hogy az eredmény az oldal többi részéhez hasonlóan méretezhető legyen. A THPDFPage.ShowMetafile és annak ShowMetafileEx párja azok a belépési pontok, amelyeket az alkalmazás meghív, és mindkettő átadja a metafájlt a THPDFWmf osztálynak, amely végigjárja az összes GDI-rekordot, és lefordítja azokat. A megkülönböztetés nem abszolút, és a HotPDF nem is tesz úgy: egy metafájl-rekord, amely valóban raszteres adat, például egy StretchDIBits bitkép-blit, valódi PDF Image XObjectként ágyazódik be az AddImage és ShowImage segítségével, ugyanazzal a hívópárral, amelyen az oldal minden más képe is átmegy, ahelyett hogy útvonal-operátorokba kényszerülne, amelyek nem tudnak fényképet kifejezni. A vonalak, kitöltések és szöveg vektorosak maradnak; a pixelek, amelyek a forrásban már pixelek voltak, a kimenetben is pixelek maradnak. A legegyszerűbb hívás nem igényel mást, mint a betöltött metafájlt:

var
  Pdf: THotPDF;
  Chart: TMetafile;
begin
  Pdf := THotPDF.Create(nil);
  Chart := TMetafile.Create;
  try
    Chart.LoadFromFile('quarterly-revenue.emf');  // exported from TChart or GDI+
    Pdf.FileName := 'quarterly-report.pdf';
    Pdf.BeginDoc;
    Pdf.CurrentPage.ShowMetafile(Chart);
    Pdf.EndDoc;
  finally
    Chart.Free;
    Pdf.Free;
  end;
end;

Hogyan alakítja az értelmező a GDI-koordinátákat PDF-oldaltérré?

A HotPDF erre egyetlen végigfutással válaszol a metafájl saját rekordfolyamán, nem pedig a GDI egy második megvalósításával. A THPDFWmf.Analyse beolvassa a metafájl fejlécét a Win32 GetEnhMetaFileHeader hívásán keresztül, visszaállítja belső rajzolási állapotát, és meghívja az EnumEnhMetafile-t, ugyanazt a felsoroló API-t, amelyet egy metafájl-megjelenítő is használna, így minden EMR_* rekord az eredeti rögzítés sorrendjében jut el a THPDFWmf.ExecuteRecord-hoz. A GDI a koordinátákat fentről lefelé fejezi ki, a metafájl saját leképezési módja (mapping mode) által választott eszköz- vagy logikai egységekben; egy PDF-oldal lentről felfelé épül fel, felhasználói térbeli pontokban, ez az a koordinátarendszer, amelyet a HotPDF útvonal- és kitöltésrajzolási vászonmodellje tárgyal. Minden rekordkezelő a ScaleX és ScaleY segítségével oldja fel ezt az eltérést, amelyek a ProjectX és ProjectY hívásával a GDI saját ablak-nézőterület képletét játsszák újra az anizotróp és izotróp leképezési módokhoz, így egy öt logikai egység szélesnek rögzített alakzat a helyes szélességgel érkezik meg PDF-pontokban, függetlenül attól, hogy a forrásalkalmazás milyen ablak- és nézőterület-kiterjedéseket állított be

Hogyan lesz egy GDI színátmenetes kitöltésből PDF árnyalási minta?

Egy EMR_GRADIENTFILL rekord valódi PDF Type 2 axiális árnyalási mintává (ISO 32000-1 §8.7.4.5) válik, valahányszor a GDI a két téglalap-mód egyikében rögzítette azt. A THPDFWmf.VEMRGradientFill közvetlenül a nyers bájtpufferből olvassa be a rekord saját elrendezését, az MS-EMF §2.3.1.6 szerkezetét követve: egy 16 bites RGBA-sarkokból álló csúcstömböt, majd egy téglalaplistát, amelyek mindegyike e csúcsok közül kettőre hivatkozik. A GRADIENT_FILL_RECT_H esetén a színek balról jobbra söpörnek a téglalap vízszintes középvonala mentén; a GRADIENT_FILL_RECT_V esetén fentről lefelé söpörnek a függőleges középvonal mentén. Mindkét esetben a két sarokszín és a vetített téglalap-koordináták közvetlenül a THotPDF.RegisterAxialGradient-be kerülnek, amely egy mintanevet ad vissza, és az oldal a téglalapot ezen a mintán keresztül (SetFillPattern) rajzolja meg és tölti ki, nem pedig egy sima SetRGBFillColor hívással, így egy táblázatszerű sávozott fejléc vagy egy diagram színátmenetes ábraterülete megőrzi az átmenetét ahelyett, hogy egyetlen átlagos színné omlana össze

A Gouraud-háromszög mód az őszinte hiányosság. Amikor a rekord ulMode mezője GRADIENT_FILL_TRIANGLE-t jelez, a VEMRGradientFill felismeri ezt, naplózza, hogy a háromszög mód még nincs megvalósítva, és kihagyja a téglalapot ahelyett, hogy egy kétszínű közelítést találgatna. A csúcsonkénti, pixelenkénti interpoláció egy tetszőleges háromszöghálón nem redukálható két megállási pontos axiális vagy sugárirányú árnyalásra, és a helyes kifejezéséhez egy PDF Type 4 vagy Type 5 hálóárnyalást (mesh shading) kellene kibocsátani, ugyanazt az árnyalási családot, amelyet a HotPDF oldalmegjelenítője is festetlenül hagy, amikor egy PDF-et visszaolvas. Két, egymással össze nem függő kódútvonal ugyanarra a határra fut ki: a hálóárnyalások hiányoznak mind az írás, mind az olvasás oldalán, és egy forrásdiagram, amely Gouraud-háromszögeket használ egy sima sugárirányú fényhatáshoz, visszaesik az utolsó tömör ecsetre, nem egy megjelenített közelítésre

A sraffozott ecsetekből csempézett minták lesznek, nem laposított szürke

Egy GDI sraffozott ecset megtartja textúráját a PDF-ben, mert a THPDFWmf.SetBrushColor a CurrentBrush.lbStyle-ban a BS_HATCHED-et ellenőrzi, mielőtt egyáltalán visszaesne egy tömör kitöltésre, ezt az esetet inkább a SetHatchBrushPattern-hez irányítva. Ez a metódus egy 8-szor-8 egységes PDF tartalomfolyamot ír meg körvonalazott vonal-operátorokból, m, l, és S, amelyeket a GDI sraffozási stílusa választ ki: egyetlen vízszintes vagy függőleges vonás a HS_HORIZONTAL és HS_VERTICAL esetén, három párhuzamos átló a HS_FDIAGONAL és HS_BDIAGONAL esetén, és a vízszintes-plusz-függőleges, illetve mindkét-átlós kombinációk a HS_CROSS és HS_DIAGCROSS esetén. A THotPDF.RegisterTilingPattern ezt a tartalomfolyamot színes csempézett mintaként (PaintType 1, ISO 32000-1 §8.7.3.1) regisztrálja 8 egységes XStep és YStep értékkel, és az oldal ugyanúgy tölti ki a SetFillPattern-en keresztül, mint egy axiális árnyalás esetén. Egy CAD-alaprajz vagy egy műszaki rajz, amely sraffozott kitöltésekre támaszkodik az anyagok megkülönböztetéséhez, megtartja ezt a vizuális nyelvet a PDF-ben ahelyett, hogy minden területet azonos szürkévé veszítene

Nem minden ecset érdemli ki ezt a bánásmódot, és a hiányosságot érdemes ismerni, mielőtt egy CAD-import kimegy. Az EMR_CREATEDIBPATTERNBRUSHPT, amely egy egyéni bitkép-mintaecset rekordja, nem a GDI hat gyári sraffozási stílusának egyike, csak a handle-jét regisztrálja, hogy a későbbi SELECTOBJECT és DELETEOBJECT rekordok konzisztensek maradjanak; a HotPDF még nem tár fel PDF Pattern-erőforrás-csővezetéket tetszőleges csempeképekhez, így ennek az ecsetnek a kiválasztása egy tömör színű visszaesésbe torkollik a forrás textúrája helyett. Ha egy kitöltés laposan jelenik meg ott, ahol az eredeti egyértelműen ismétlődő kép-textúrát használt, a forrás ecset szinte biztosan egyéni DIB-minta, nem pedig szabványos sraffozás, és ez az az egy eset, amelyet érdemes elsőként kézzel ellenőrizni. Egy ilyen rajz importjának beállítása még mindig ugyanazon opcióobjektumon keresztül történik:

var
  Pdf: THotPDF;
  Drawing: TMetafile;
  Options: THPDFEmfOptions;
begin
  Pdf := THotPDF.Create(nil);
  Drawing := TMetafile.Create;
  Options := THPDFEmfOptions.Create;
  try
    Drawing.LoadFromFile('floor-plan.emf');
    Options.Assign(Pdf.EmfOptions);   // start from the document-wide defaults
    Options.Redraw := False;          // interpret the original EMF bytes, no GDI re-record pass
    Options.ShowNullBrush := True;    // keep explicitly unfilled CAD regions visible
    Options.UseFrame := True;         // clip output to the frame the EMF header declares
    Pdf.FileName := 'floor-plan.pdf';
    Pdf.BeginDoc;
    Pdf.CurrentPage.ShowMetafileEx(Drawing, Options);
    Pdf.EndDoc;
  finally
    Options.Free;
    Drawing.Free;
    Pdf.Free;
  end;
end;

Mi akadályozza meg, hogy egy hibás formátumú metafájl tönkretegye az oldalt?

A HotPDF válasza egyetlen kapu az ExecuteRecord tetején, nem pedig egy védekező ellenőrzés, amely megismétlődik a mintegy nyolcvan rekordkezelő mindegyikében. Egy GDI útvonal-zárójel, amelyet az EMR_BEGINPATH nyit meg és az EMR_ENDPATH vagy az EMR_ABORTPATH zár le, egy privát PathContinue tulajdonság követi nyomon, amelyet az FPathContinue mező támogat. Amíg ez a zárójel nyitva van, az ExecuteRecord csak útvonal-építő rekordokat enged át, a mozgás, vonal, törtvonal, sokszög, poligörbe és polydraw változatokat, valamint a CLOSEFIGURE-t és néhány transzformációs és DC-állapot rekordot, mint például a SETWORLDTRANSFORM, SAVEDC, és RESTOREDC. Minden más rekordtípus, amely az ExecuteRecord-hoz ér, miközben a zárójel nyitva van, például egy eltévedt EXTTEXTOUT vagy egy bitkép-blit, központilag eldobásra kerül egyetlen Exit-tel abban a pillanatban, amikor megérkezik

Ez a kapu azért létezik, mert egy kézzel írt, eszközzel generált vagy egyszerűen sérült metafájlban egy útvonal-zárójel nem garantáltan csak azt tartalmazza, amit egy jól formázott fájl tenne a nyitó és záró rekordjai közé. Egy szövegkiíró rekord, amely az EMR_BEGINPATH és az EMR_ENDPATH közé kerül, kapu nélkül vagy beszennyezné az épülő útvonal-geometriát, vagy egy PDF szövegmegjelenítő operátort bocsátana ki egy olyan sorozat közepén, amelynek tiszta útvonal-építésnek kellene lennie, és mindkét hibamód olyan típusú, amely egy harmadik féltől származó eszköz egyetlen hibás formátumú bemenetén jelenik meg, nem pedig bármin, amit egy normál tesztkészlet lefed. Az ellenőrzés központosítása az ExecuteRecord-ban azt jelenti, hogy az egyes VEMR* kezelőknek nem kell egyenként védekezniük a rossz időpontban történő meghívás ellen; a kapu ezt egyszer dönti el, a diszpécselés előtt, nem pedig nyolcvanszor utána

Egy vektoros diagram elhelyezése szöveg és képek mellett egy oldalon

Egy jelentésoldal ritkán tartalmaz csak egy diagramot, és a ShowMetafile ugyanúgy komponálódik a HotPDF többi oldalműveletével, mint bármely más rajzolási hívás. Egy TextOut-tal rajzolt címsor, egy EMF-ként importált, sraffozott kitöltésű oszlopdiagram és egy ShowImage-dzsel elhelyezett logó mind ugyanarra az oldalra, ugyanabba a tartalomfolyamba kerülhet, mindegyik megtartva natív hűségét, ez a kompozíciós minta, amelyet a HotPDF útmutatója a szöveg, betűtípusok és képek elrendezéséhez egy jelentésben tárgyal:

Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Q2 Regional Sales');
Pdf.CurrentPage.ShowMetafile(RegionChart);   // hatch-filled bars, still vector
Pdf.CurrentPage.ShowImage(LogoIndex, 450, 760, 90, 30, 0);

Az EMF- és WMF-értelmező, az általa színátmenetes kitöltésekhez regisztrált axiális árnyalási minták, valamint az itt leírt, sraffozott ecsetekhez tartozó csempézettminta-leképezés mind a szabványos, Delphihez és C++Builderhez készült HotPDF komponens részeként érkezik, egy natív VCL könyvtárként, amely mindehhez semmilyen külső DLL-függőséget nem igényel