HotPDF, natívny PDF komponent pre Delphi a C++Builder, importuje metasúbory Windows EMF a WMF tak, že každý záznam GDI interpretuje priamo do operátorov PDF namiesto toho, aby súbor sploštil na bitmapu: prechodové výplne sa stávajú axiálnymi vzormi tieňovania PDF, šrafovacie štetce sa stávajú dlaždicovými vzormi PDF a centralizovaná brána stavu cesty blokuje poškodené záznamy, aby nepokazili výstup. Každý graf, ktorý dokáže exportovať TChart, plocha GDI+ alebo obyčajný TCanvas ako rozšírený metasúbor, je kandidátom na túto cestu, a rozdiel sa prejaví hneď, ako niekto priblíži stránku alebo ju odošle na vysokorozlíšovaciu tlačiareň
Alternatíva, po ktorej väčšina vývojárov v Delphi automaticky siahne, je rasterizovať metasúbor na bitmapu ešte pred jeho vložením na stránku, a náklady sa prejavia až neskôr: stĺpcový graf, ktorý bol na obrazovke ostrý, sa viditeľne rozpixeluje vo chvíli, keď sa PDF vytlačí pri 600 DPI alebo premietne na obrazovku v zasadačke, a šrafovaná oblasť z CAD sa zrúti na jediný plochý sivý obdĺžnik, ak sa štýl výplne neprenesie ďalej. Čítanie metasúboru ako programu namiesto obrázka je práve to, čo sa vyhne obom problémom, a zároveň je to náročnejšia cesta na správnu implementáciu, a preto sa oplatí poznať nižšie uvedené úskalia ešte pred odoslaním reportu
Prečo interpretovať metasúbor namiesto jeho sploštenia na bitmapu?
HotPDF drží import EMF a WMF na vektorovej ceste, pretože metasúbor Windows je zaznamenaná postupnosť volaní kreslenia GDI, nie obrázok, a práve prehratie týchto volaní ako operátorov cesty, textu a tieňovania PDF umožňuje, aby sa výsledok škáloval rovnako ako zvyšok stránky. THPDFPage.ShowMetafile a jej náprotivok ShowMetafileEx sú vstupné body, ktoré aplikácia volá, a obe odovzdávajú metasúbor triede THPDFWmf, ktorá prechádza každý záznam GDI a prekladá ho. Toto rozlíšenie nie je absolútne a HotPDF to ani nepredstiera: záznam metasúboru, ktorý je skutočne rastrovými dátami, napríklad bitmapový blit StretchDIBits, sa vloží ako skutočný obrazový XObject PDF cez AddImage a ShowImage, rovnaký pár volaní, akým prechádza akýkoľvek iný obrázok na stránke, namiesto toho, aby sa vynútil do operátorov cesty, ktoré nedokážu vyjadriť fotografiu. Čiary, výplne a text zostávajú vektorové; pixely, ktoré boli pixelmi už v zdroji, zostávajú pixelmi aj vo výstupe. Najjednoduchšie volanie nepotrebuje nič viac než načítaný metasúbor:
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;
Ako interpreter prevádza súradnice GDI do priestoru stránky PDF?
HotPDF na to odpovedá jediným priechodom cez vlastný prúd záznamov metasúboru namiesto druhej implementácie GDI. THPDFWmf.Analyse načíta hlavičku metasúboru cez volanie Win32 GetEnhMetaFileHeader, resetuje svoj vnútorný stav kreslenia a volá EnumEnhMetafile, rovnaké enumeračné API, aké by použil prehliadač metasúborov, takže každý záznam EMR_* sa dostane do THPDFWmf.ExecuteRecord v poradí, v akom bol pôvodne zaznamenaný. GDI vyjadruje súradnice zhora nadol v zariadeniových alebo logických jednotkách zvolených vlastným mapovacím režimom metasúboru; stránka PDF je zdola nahor v bodoch používateľského priestoru, súradnicovom systéme, ktorý pokrýva model kreslenia na plátne HotPDF pre cesty a výplne. Každý obslužný program záznamu rieši tento nesúlad prostredníctvom ScaleX a ScaleY, ktoré volajú ProjectX a ProjectY, aby prehrali vlastný vzorec GDI na prevod okna na viewport pre anizotropné aj izotropné mapovacie režimy, takže tvar zaznamenaný s šírkou päť logických jednotiek skončí so správnou šírkou v bodoch PDF bez ohľadu na to, aké rozsahy okna a viewportu zdrojová aplikácia nastavila
Ako sa z prechodovej výplne GDI stane vzor tieňovania PDF?
Záznam EMR_GRADIENTFILL sa stane skutočným axiálnym vzorom tieňovania PDF typu 2 (ISO 32000-1 §8.7.4.5) vždy, keď ho GDI zaznamenalo v jednom z dvoch obdĺžnikových režimov. THPDFWmf.VEMRGradientFill číta vlastné rozloženie záznamu priamo zo surového bufferu bajtov podľa štruktúry MS-EMF §2.3.1.6: pole vrcholov s 16-bitovými rohmi RGBA, za ktorým nasleduje zoznam obdĺžnikov, z ktorých každý odkazuje na dva z týchto vrcholov. Pre GRADIENT_FILL_RECT_H farby prechádzajú zľava doprava pozdĺž vodorovnej strednej línie obdĺžnika; pre GRADIENT_FILL_RECT_V prechádzajú zhora nadol pozdĺž zvislej strednej línie. V oboch prípadoch obe farby rohov a premietnuté súradnice obdĺžnika idú priamo do THotPDF.RegisterAxialGradient, ktorá vráti názov vzoru, a stránka nakreslí obdĺžnik a vyplní ho cez tento vzor (SetFillPattern) namiesto plochého volania SetRGBFillColor, takže hlavička v štýle tabuľky s pásmi alebo prechodová plocha grafu v grafe si zachová svoj prechod namiesto toho, aby sa zrútila na jednu priemernú farbu
Gouraudov trojuholníkový režim je poctivo priznaná medzera. Keď pole záznamu ulMode hlási GRADIENT_FILL_TRIANGLE, VEMRGradientFill to rozpozná, zaloguje, že trojuholníkový režim ešte nie je implementovaný, a obdĺžnik preskočí namiesto toho, aby hádal dvojfarebnú aproximáciu. Interpolácia po vrcholoch a po pixeloch naprieč ľubovoľnou trojuholníkovou sieťou sa nedá zredukovať na dvojbodové axiálne alebo radiálne tieňovanie, a jej správne vyjadrenie by znamenalo vygenerovať sieťové tieňovanie PDF typu 4 alebo typu 5, tú istú rodinu tieňovania, ktorú vykresľovač stránok HotPDF tiež necháva nevykreslenú pri spätnom čítaní PDF. Dve nesúvisiace cesty kódu narážajú na rovnakú hranicu: sieťové tieňovania sú medzerou na strane zápisu aj na strane čítania, a zdrojový diagram, ktorý používa Gouraudove trojuholníky na hladkú radiálnu žiaru, spadne späť na to, čím bol posledný plný štetec, nie na vykreslenú aproximáciu
Šrafovacie štetce sa stávajú dlaždicovými vzormi, nie sploštenou sivou
Šrafovací štetec GDI si v PDF zachová svoju textúru, pretože THPDFWmf.SetBrushColor skontroluje CurrentBrush.lbStyle na BS_HATCHED ešte predtým, než by sa vôbec vrátil k plnej výplni, a namiesto toho tento prípad presmeruje na SetHatchBrushPattern. Táto metóda zapíše obsahový prúd PDF s rozmerom 8×8 jednotiek zložený z ťahaných čiarových operátorov m, l a S, zvolených podľa štýlu šrafovania GDI: jediný vodorovný alebo zvislý ťah pre HS_HORIZONTAL a HS_VERTICAL, tri rovnobežné diagonály pre HS_FDIAGONAL a HS_BDIAGONAL a kombinácie vodorovný-plus-zvislý alebo obe diagonály pre HS_CROSS a HS_DIAGCROSS. THotPDF.RegisterTilingPattern zaregistruje tento obsahový prúd ako farebný dlaždicový vzor (PaintType 1, ISO 32000-1 §8.7.3.1) s 8-jednotkovým XStep a YStep a stránka vyplní cez SetFillPattern rovnako, ako to robí axiálne tieňovanie. Pôdorys CAD alebo technický výkres, ktorý na rozlíšenie materiálov spolieha na šrafované výplne, si v PDF zachová tento vizuálny jazyk namiesto toho, aby všetky oblasti stratili do rovnakej sivej
Nie každý štetec si túto starostlivosť zaslúži, a túto medzeru sa oplatí poznať ešte pred odoslaním importu z CAD. EMR_CREATEDIBPATTERNBRUSHPT, záznam pre vlastný vzorový štetec s bitmapovým obrázkom namiesto jedného zo šiestich štandardných šrafovacích štýlov GDI, iba zaregistruje svoj handle, aby neskoršie záznamy SELECTOBJECT a DELETEOBJECT zostali konzistentné; HotPDF zatiaľ nesprístupňuje pipeline zdroja vzoru PDF pre ľubovoľné dlaždicové obrázky, takže výber tohto štetca spadne späť na náhradnú plnú farbu namiesto zdrojovej textúry. Ak sa výplň vykreslí plocho tam, kde zdroj zjavne používal opakujúcu sa obrázkovú textúru, zdrojový štetec je takmer isto vlastný vzor DIB, nie štandardné šrafovanie, a práve tento jeden prípad sa oplatí najprv skontrolovať ručne. Konfigurácia importu pre takýto výkres stále prechádza cez rovnaký objekt volieb:
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;
Čo bráni poškodenému metasúboru, aby narušil stránku?
Odpoveďou HotPDF je jediná brána na začiatku ExecuteRecord namiesto obrannej kontroly opakovanej v každom z jeho zhruba osemdesiatich obslužných programov záznamov. Zátvorka cesty GDI, otvorená EMR_BEGINPATH a zatvorená EMR_ENDPATH alebo EMR_ABORTPATH, sa sleduje pomocou súkromnej vlastnosti PathContinue podloženej poľom FPathContinue. Kým je táto zátvorka otvorená, ExecuteRecord prepustí iba záznamy konštrukcie cesty, varianty pohybu, čiary, lomenej čiary, mnohouholníka, polybezier a polydraw, plus CLOSEFIGURE a malú množinu záznamov transformácie a stavu DC, ako sú SETWORLDTRANSFORM, SAVEDC a RESTOREDC. Každý iný typ záznamu, ktorý sa dostane do ExecuteRecord, kým je zátvorka otvorená, napríklad zablúdený EXTTEXTOUT alebo bitmapový blit, sa centrálne zahodí jediným Exit v okamihu, keď príde
Táto brána existuje preto, lebo zátvorka cesty v ručne vytvorenom, nástrojom vygenerovanom alebo jednoducho poškodenom metasúbore nemá zaručené, že obsahuje iba to, čo by správne zostavený súbor vložil medzi svoje otváracie a zatváracie záznamy. Záznam textového výstupu, ktorý by sa ocitol medzi EMR_BEGINPATH a EMR_ENDPATH, by bez brány buď znečistil geometriu konštruovanej cesty, alebo by vygeneroval operátor zobrazenia textu PDF uprostred sekvencie, ktorá má byť čistou konštrukciou cesty, a oba tieto typy zlyhania sú presne tie, ktoré sa prejavia na jednom poškodenom vstupe z nástroja tretej strany, nie na niečom, čo bežná testovacia sada náhodou pokrýva. Centralizácia kontroly v ExecuteRecord znamená, že jednotlivé obslužné programy VEMR* sa nemusia každý osobitne brániť proti tomu, že budú zavolané v nesprávnom čase; brána o tom rozhodne raz, pred rozoslaním, namiesto osemdesiatkrát po ňom
Umiestnenie vektorového grafu vedľa textu a obrázkov na jednu stránku
Stránka reportu len zriedka obsahuje iba graf a ShowMetafile sa skladá s ostatnými operátormi stránky HotPDF presne tak, ako akékoľvek iné volanie kreslenia. Nadpis nakreslený pomocou TextOut, šrafovaný stĺpcový graf importovaný ako EMF a logo umiestnené pomocou ShowImage môžu všetky skončiť na tej istej stránke v tom istom obsahovom prúde, pričom si každý zachová svoju natívnu vernosť, čo je kompozičný vzor opísaný v návode HotPDF na rozloženie textu, písiem a obrázkov v reporte:
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);
Interpreter EMF a WMF, axiálne vzory tieňovania, ktoré registruje pre prechodové výplne, a mapovanie na dlaždicové vzory pre šrafovacie štetce opísané v tomto článku sú súčasťou štandardného komponentu HotPDF pre Delphi a C++Builder, natívnej knižnice VCL bez akejkoľvek závislosti na externom DLL