HotPDF, nativní komponenta pro PDF pro Delphi a C++Builder, importuje metasoubory Windows EMF a WMF tak, že každý záznam GDI interpretuje přímo na operátory PDF místo toho, aby soubor zploštil na bitmapu: gradientní výplně se stanou axiálními stínovacími vzory PDF, šrafovací štětce se stanou dlaždicovými vzory PDF a centralizovaná brána stavu cesty blokuje, aby poškozené záznamy zkorumpovaly výstup. Každý graf, který dokáže TChart, plocha GDI+ nebo prostý TCanvas exportovat jako rozšířený metasoubor, je kandidátem na tuto cestu, a rozdíl se projeví v okamžiku, kdy někdo stránku přiblíží nebo ji pošle na tiskárnu s vysokým rozlišením
Alternativa, ke které většina vývojářů v Delphi ve výchozím stavu sáhne, je rasterizovat metasoubor na bitmapu ještě před vložením na stránku, a náklad se projeví až později: sloupcový graf, který byl na obrazovce ostrý, se při tisku PDF na 600 DPI nebo při promítání na plátno v zasedací místnosti viditelně rozpixeluje, a oblast CAD výkresu vyplněná šrafováním se zhroutí do jediného plochého šedého obdélníku, pokud se styl výplně neponese dál. Číst metasoubor jako program, ne jako obrázek, je to, co se oběma problémům vyhne, a je to také těžší cesta na správnou implementaci — proto se níže uvedená úskalí vyplatí znát dřív, než se report odešle
Proč metasoubor interpretovat místo zploštění na bitmapu?
HotPDF drží import EMF a WMF na vektorové cestě, protože metasoubor Windows je zaznamenaná posloupnost volání kreslení GDI, ne obrázek, a přehrání těchto volání jako operátorů PDF pro cestu, text a stínování je to, co umožňuje, aby výsledek škáloval stejně jako zbytek stránky. THPDFPage.ShowMetafile a jeho protějšek ShowMetafileEx jsou vstupní body, které aplikace volá, a oba předávají metasoubor třídě THPDFWmf, která prochází každý záznam GDI a překládá jej. Rozlišení není absolutní a HotPDF to ani nepředstírá: záznam metasouboru, který je opravdu rastrová data, například blit bitmapy StretchDIBits, se vloží jako skutečný obrazový XObject PDF přes AddImage a ShowImage, přesně stejnou dvojici volání, přes kterou prochází jakýkoli jiný obrázek na stránce, místo aby byl vynucen do operátorů cesty, které fotografii vyjádřit neumí. Čáry, výplně a text zůstávají vektorové; pixely, které byly pixely už ve zdroji, zůstávají pixely i ve výstupu. Nejjednodušší volání nepotřebuje nic víc než už načtený metasoubor:
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;
Jak interpret převádí souřadnice GDI do prostoru stránky PDF?
HotPDF na to odpovídá jediným průchodem přes vlastní proud záznamů metasouboru místo druhé implementace GDI. THPDFWmf.Analyse čte hlavičku metasouboru přes volání Win32 GetEnhMetaFileHeader, resetuje svůj vnitřní stav kreslení a volá EnumEnhMetafile, stejné enumerační API, jaké by použil prohlížeč metasouborů, takže každý záznam EMR_* dorazí do THPDFWmf.ExecuteRecord v pořadí, v jakém byl původně zaznamenán. GDI vyjadřuje souřadnice shora dolů v zařízeních nebo logických jednotkách zvolených vlastním mapovacím režimem metasouboru; stránka PDF je zdola nahoru v bodech uživatelského prostoru, souřadnicovém systému, který popisuje model kreslení na plátno HotPDF pro cesty a výplně. Každý obsluhovač záznamu tento nesoulad řeší přes ScaleX a ScaleY, které volají ProjectX a ProjectY a přehrávají vlastní vzorec GDI pro převod okna na viewport pro anizotropní i izotropní mapovací režimy, takže tvar zaznamenaný jako pět logických jednotek široký skončí ve správné šířce v bodech PDF bez ohledu na to, jaké rozsahy okna a viewportu zdrojová aplikace nastavila
Jak se z gradientní výplně GDI stane stínovací vzor PDF?
Záznam EMR_GRADIENTFILL se stane skutečným axiálním stínovacím vzorem PDF typu 2 (ISO 32000-1 §8.7.4.5), kdykoli jej GDI zaznamenalo v jednom ze dvou obdélníkových režimů. THPDFWmf.VEMRGradientFill čte vlastní rozvržení záznamu přímo ze surového bufferu bajtů podle struktury MS-EMF §2.3.1.6: pole vrcholů 16bitových rohů RGBA, následované seznamem obdélníků, z nichž každý odkazuje na dva z těchto vrcholů. U GRADIENT_FILL_RECT_H barvy plynou zleva doprava podél vodorovné střední linie obdélníku; u GRADIENT_FILL_RECT_V plynou shora dolů podél svislé střední linie. V obou případech jdou obě rohové barvy a promítnuté souřadnice obdélníku přímo do THotPDF.RegisterAxialGradient, která vrátí název vzoru, a stránka pak obdélník nakreslí a vyplní přes tento vzor (SetFillPattern) místo plochého volání SetRGBFillColor — takže záhlaví v pruhovaném stylu tabulkového procesoru nebo gradientní plocha grafu si podrží svůj přechod místo toho, aby se zhroutily do jedné průměrné barvy
Režim Gouraudových trojúhelníků je poctivá mezera. Když pole ulMode záznamu hlásí GRADIENT_FILL_TRIANGLE, VEMRGradientFill to rozpozná, zaloguje, že režim trojúhelníků zatím není implementovaný, a obdélník přeskočí, místo aby hádala dvoubarevnou aproximaci. Interpolace na úrovni vrcholů a pixelů napříč libovolnou sítí trojúhelníků se nedá zredukovat na axiální nebo radiální stínování se dvěma zarážkami, a jeho správné vyjádření by znamenalo vydat mřížkové stínování PDF typu 4 nebo typu 5, stejnou rodinu stínování, kterou nevykresluje ani vykreslovač stránek HotPDF při zpětném čtení PDF. Dvě nesouvisející cesty kódu narážejí na stejnou hranici: mřížková stínování jsou mezerou jak na straně zápisu, tak na straně čtení, a zdrojový diagram, který používá Gouraudovy trojúhelníky pro hladkou radiální záři, spadne na to, jaký byl poslední plný štětec, ne na vykreslenou aproximaci
Šrafovací štětce se stávají dlaždicovými vzory, ne zploštělou šedí
Šrafovací štětec GDI si v PDF podrží svou texturu, protože THPDFWmf.SetBrushColor zkontroluje CurrentBrush.lbStyle na BS_HATCHED ještě předtím, než by kdy spadl na plnou výplň, a tento případ místo toho nasměruje na SetHatchBrushPattern. Tato metoda zapíše proud obsahu PDF o rozměru 8×8 jednotek s operátory tažené čáry, m, l a S, vybranými podle stylu šrafování GDI: jediný vodorovný nebo svislý tah pro HS_HORIZONTAL a HS_VERTICAL, tři rovnoběžné diagonály pro HS_FDIAGONAL a HS_BDIAGONAL a kombinace vodorovné plus svislé nebo obou diagonál pro HS_CROSS a HS_DIAGCROSS. THotPDF.RegisterTilingPattern registruje tento proud obsahu jako barevný dlaždicový vzor (PaintType 1, ISO 32000-1 §8.7.3.1) s 8jednotkovým XStep a YStep, a stránka vyplňuje přes SetFillPattern stejně jako u axiálního stínování. Půdorys CAD nebo technický výkres, který se u odlišení materiálů spoléhá na šrafovací výplně, si tento vizuální jazyk podrží i v PDF místo toho, aby všechny oblasti ztratily do stejné šedi
Ne každý štětec si toto zacházení zaslouží a tuto mezeru se vyplatí znát dřív, než se import z CAD odešle. EMR_CREATEDIBPATTERNBRUSHPT, záznam pro vlastní vzorový štětec s bitmapovým obrázkem místo jednoho z šesti standardních šrafovacích stylů GDI, jen zaregistruje svůj handle, aby pozdější záznamy SELECTOBJECT a DELETEOBJECT zůstaly konzistentní; HotPDF zatím nevystavuje pipeline zdroje PDF Pattern pro libovolné dlaždicové obrázky, takže výběr tohoto štětce spadne na náhradní plnou barvu místo zdrojové textury. Pokud se výplň vykreslí plochá tam, kde originál zjevně používal opakující se obrazovou texturu, je zdrojový štětec téměř jistě vlastní vzor DIB, ne standardní šrafování, a to je ten jeden případ, který se vyplatí nejdřív zkontrolovat ručně. Nastavení importu pro takový výkres stále prochází stejným objektem voleb:
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;
Co brání poškozenému metasouboru zkorumpovat stránku?
Odpovědí HotPDF je jediná brána na začátku ExecuteRecord místo obranné kontroly opakované v každém z jeho zhruba osmdesáti obsluhovačů záznamů. Závorka cesty GDI, otevřená záznamem EMR_BEGINPATH a uzavřená záznamem EMR_ENDPATH nebo EMR_ABORTPATH, se sleduje přes soukromou vlastnost PathContinue podloženou polem FPathContinue. Dokud je tato závorka otevřená, ExecuteRecord propustí jen záznamy pro konstrukci cesty — varianty pro pohyb, čáru, lomenou čáru, mnohoúhelník, polybezier a polydraw, plus CLOSEFIGURE a malou sadu záznamů transformace a stavu DC, jako SETWORLDTRANSFORM, SAVEDC a RESTOREDC. Každý jiný typ záznamu, který dorazí do ExecuteRecord, dokud je závorka otevřená — třeba zbloudilý EXTTEXTOUT nebo blit bitmapy — se centrálně zahodí jediným Exit v okamžiku, kdy dorazí
Tato brána existuje proto, že u závorky cesty v ručně vytvořeném, nástrojem vygenerovaném nebo prostě poškozeném metasouboru není zaručeno, že bude obsahovat jen to, co by mezi svými otevírajícími a uzavírajícími záznamy vložil dobře formovaný soubor. Záznam pro výstup textu, který by se ocitl mezi EMR_BEGINPATH a EMR_ENDPATH, by bez brány buď znečistil geometrii cesty, která se právě staví, nebo by vydal operátor zobrazení textu PDF uprostřed sekvence, jež má být čistou konstrukcí cesty — a oba tyto typy selhání jsou přesně ten druh, který se projeví na jednom poškozeném vstupu od nástroje třetí strany, ne na ničem, co by normální testovací sada náhodou pokryla. Centralizace kontroly v ExecuteRecord znamená, že jednotlivé obsluhovače VEMR* se nemusejí každý bránit proti tomu, že by byly zavolány ve špatnou chvíli; brána o tom rozhodne jednou, před dispatchem, místo osmdesátkrát po něm
Umístění vektorového grafu vedle textu a obrázků na jedné stránce
Stránka reportu jen zřídka obsahuje pouze graf a ShowMetafile se skládá s ostatními operátory stránky HotPDF přesně tak, jako jakékoli jiné volání kreslení. Nadpis nakreslený pomocí TextOut, šrafovaný sloupcový graf importovaný jako EMF a logo umístěné přes ShowImage mohou všechny skončit na stejné stránce ve stejném proudu obsahu, každý si podrží svou nativní věrnost — kompozici popisuje průvodce HotPDF pro rozvržení textu, fontů a obrázků v reportu:
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);
Interpret EMF a WMF, axiální stínovací vzory, které registruje pro gradientní výplně, a mapování na dlaždicové vzory pro šrafovací štětce, popsané zde, jsou všechny součástí standardní komponenty HotPDF pro Delphi a C++Builder, nativní knihovny VCL bez jakékoli externí závislosti na DLL