HotPDF, izvorna Delphi i C++Builder PDF komponenta, uvozi Windows EMF i WMF metadatoteke tako da svaki GDI zapis izravno interpretira u PDF operatore umjesto da datoteku spljošti u bitmapu: gradijentna ispunjenja postaju PDF aksijalni uzorci sjenčanja, šrafure postaju PDF ponavljajući uzorci (tiling patterns), a centralizirana provjera stanja putanje sprječava da neispravni zapisi ošteti izlaz. Svaki grafikon koji TChart, GDI+ površina ili obični TCanvas može izvesti kao proširenu metadatoteku (enhanced metafile) kandidat je za ovu putanju, a razlika se pokazuje čim netko zumira stranicu ili je pošalje na pisač visoke rezolucije
Alternativa kojoj većina Delphi programera pribjegava prema zadanim postavkama jest rasteriziranje metadatoteke u bitmapu prije nego što je se stavi na stranicu, a cijena se pokazuje tek kasnije: stupčani grafikon koji je bio oštar na ekranu postaje vidljivo grub čim se PDF ispiše pri 600 DPI ili projicira na ekran u sali za sastanke, a CAD područje ispunjeno šrafurom svede se na jedan ravni sivi pravokutnik ako se stil ispune ne prenese. Čitanje metadatoteke kao programa, a ne kao slike, izbjegava oba problema, no to je i teža putanja za ispravnu implementaciju, zbog čega je vrijedno znati zamke opisane dolje prije nego što izvještaj krene u produkciju
Zašto interpretirati metadatoteku umjesto da je se spljošti u bitmapu?
HotPDF zadržava uvoz EMF i WMF formata na vektorskoj putanji jer je Windows metadatoteka zabilježen niz GDI poziva za crtanje, a ne slika, i upravo ponovno odigravanje tih poziva kao PDF operatora za putanje, tekst i sjenčanje omogućuje da se rezultat skalira jednako kao i ostatak stranice. THPDFPage.ShowMetafile i njegov pandan ShowMetafileEx ulazne su točke koje aplikacija poziva, a oboje predaju metadatoteku klasi THPDFWmf, koja prolazi kroz svaki GDI zapis i prevodi ga. Ta razlika nije apsolutna, i HotPDF to i ne prikriva: GDI zapis metadatoteke koji je istinski rasterski podatak, primjerice StretchDIBits prijenos bitmape (bitmap blit), ugrađuje se kao pravi PDF Image XObject preko AddImage i ShowImage, istog para poziva kroz koji prolazi svaka druga slika na stranici, umjesto da bude prisiljen u operatore putanje koji ne mogu izraziti fotografiju. Linije, ispune i tekst ostaju vektorski; pikseli koji su u izvoru već bili pikseli ostaju pikseli i u izlazu. Najjednostavniji poziv ne traži ništa osim učitane metadatoteke:
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;
Kako interpreter pretvara GDI koordinate u prostor PDF stranice?
HotPDF na to odgovara jednim prolazom kroz vlastiti tok zapisa metadatoteke, umjesto drugom implementacijom GDI-ja. THPDFWmf.Analyse čita zaglavlje metadatoteke pozivom Win32 funkcije GetEnhMetaFileHeader, resetira svoje interno stanje crtanja i poziva EnumEnhMetafile, isti API za nabrajanje koji bi koristio i preglednik metadatoteka, tako da svaki EMR_* zapis stiže do THPDFWmf.ExecuteRecord onim redoslijedom kojim je izvorno zabilježen. GDI izražava koordinate odozgo prema dolje u uređajnim ili logičkim jedinicama koje odabire vlastiti način mapiranja (mapping mode) metadatoteke; PDF stranica je odozdo prema gore u točkama korisničkog prostora, koordinatni sustav opisan u HotPDF-ovom modelu crtanja platna za putanje i ispune. Svaki rukovatelj zapisa rješava tu neusklađenost pomoću ScaleX i ScaleY, koji pozivaju ProjectX i ProjectY kako bi ponovno primijenili GDI-jevu vlastitu formulu prozor-u-viewport za anizotropni i izotropni način mapiranja, tako da oblik zabilježen širine pet logičkih jedinica završi na ispravnoj širini u PDF točkama, bez obzira na to koje su opsege prozora i viewporta postavile izvorna aplikacija
Kako GDI gradijentno ispunjenje postaje uzorak PDF sjenčanja?
Zapis EMR_GRADIENTFILL postaje pravi PDF uzorak aksijalnog sjenčanja tipa 2 (ISO 32000-1 §8.7.4.5) kad god ga je GDI zabilježio u jednom od dva pravokutna načina. THPDFWmf.VEMRGradientFill čita raspored samog zapisa izravno iz sirovog bajtnog spremnika, prateći strukturu MS-EMF §2.3.1.6: niz vrhova od 16-bitnih RGBA kutova, nakon kojeg slijedi popis pravokutnika od kojih se svaki poziva na dva od tih vrhova. Za GRADIENT_FILL_RECT_H boje se prelijevaju slijeva nadesno duž vodoravne sredine pravokutnika; za GRADIENT_FILL_RECT_V prelijevaju se odozgo prema dolje duž okomite sredine. U oba slučaja obje boje kutova i projicirane koordinate pravokutnika idu izravno u THotPDF.RegisterAxialGradient, koja vraća naziv uzorka, a stranica crta pravokutnik i ispunjava ga kroz taj uzorak (SetFillPattern) umjesto ravnim pozivom SetRGBFillColor, tako da tablično zaglavlje s trakama ili gradijentno područje crtanja u grafikonu zadržava svoj prijelaz umjesto da se svede na jednu prosječnu boju
Gouraudov trokutasti način iskren je nedostatak. Kad polje ulMode zapisa prijavi GRADIENT_FILL_TRIANGLE, VEMRGradientFill to prepoznaje, zabilježi u dnevnik da trokutasti način još nije implementiran i preskoči pravokutnik umjesto da nagađa dvobojnu aproksimaciju. Interpolacija po vrhu, po pikselu, kroz proizvoljnu mrežu trokuta ne svodi se na aksijalno ili radijalno sjenčanje s dvije prijelazne točke, a ispravan izraz zahtijevao bi generiranje PDF mrežnog sjenčanja tipa 4 ili tipa 5, iste obitelji sjenčanja koju HotPDF-ov renderer stranica također ostavlja neobojenom pri čitanju PDF-a natrag. Dvije nepovezane putanje koda dolaze do iste granice: mrežna sjenčanja nedostatak su i na strani pisanja i na strani čitanja, a izvorni dijagram koji koristi Gouraudove trokute za glatki radijalni sjaj vraća se na to koji je bio zadnji puni kist, a ne na iscrtanu aproksimaciju
Šrafure postaju ponavljajući uzorci, a ne spljoštena siva
GDI kist za šrafuru zadržava svoju teksturu u PDF-u jer THPDFWmf.SetBrushColor provjerava vrijednost CurrentBrush.lbStyle za BS_HATCHED prije nego što uopće posegne za rezervnim punim ispunom, usmjeravajući taj slučaj umjesto toga na SetHatchBrushPattern. Ta metoda zapisuje PDF sadržajni tok veličine 8x8 jedinica s operatorima za crtanje linija — m, l i S — odabranim prema GDI stilu šrafure: jedan vodoravni ili okomiti potez za HS_HORIZONTAL i HS_VERTICAL, tri paralelne dijagonale za HS_FDIAGONAL i HS_BDIAGONAL, te kombinacije vodoravno-plus-okomito ili obje dijagonale za HS_CROSS i HS_DIAGCROSS. THotPDF.RegisterTilingPattern registrira taj sadržajni tok kao obojeni ponavljajući uzorak (PaintType 1, ISO 32000-1 §8.7.3.1) s vrijednostima XStep i YStep od 8 jedinica, a stranica se ispunjava kroz SetFillPattern na isti način kao i kod aksijalnog sjenčanja. CAD tlocrt ili tehnički crtež koji se oslanja na šrafirane ispune za razlikovanje materijala zadržava taj vizualni jezik u PDF-u umjesto da svako područje izgubi u istovjetnoj sivoj boji
Ne dobiva svaki kist taj tretman, i taj je nedostatak vrijedno znati prije nego CAD uvoz krene u produkciju. EMR_CREATEDIBPATTERNBRUSHPT, zapis za prilagođeni kist s uzorkom slike u bitmapi, a ne jedan od šest GDI standardnih stilova šrafure, samo registrira svoj handle kako bi kasniji zapisi SELECTOBJECT i DELETEOBJECT ostali dosljedni; HotPDF još ne izlaže cjevovod za PDF Pattern resurse za proizvoljne pločice slika, pa odabir tog kista propada na rezervno puno ispunjenje bojom umjesto na izvornu teksturu. Ako se ispuna iscrtava ravno tamo gdje je izvornik očito koristio teksturu slike koja se ponavlja, izvorni je kist gotovo sigurno prilagođeni DIB uzorak, a ne standardna šrafura, i to je taj jedan slučaj koji se isplati prvo ručno provjeriti. Konfiguriranje uvoza za takav crtež i dalje ide kroz isti objekt opcija:
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;
Što sprječava neispravnu metadatoteku da ošteti stranicu?
HotPDF-ov odgovor jedna je jedina provjera na vrhu funkcije ExecuteRecord, umjesto obrambene provjere ponovljene u svakom od otprilike osamdeset rukovatelja zapisima. GDI zagradu putanje, otvorenu zapisom EMR_BEGINPATH i zatvorenu zapisom EMR_ENDPATH ili EMR_ABORTPATH, prati privatno svojstvo PathContinue koje se oslanja na polje FPathContinue. Dok je ta zagrada otvorena, ExecuteRecord propušta samo zapise za izgradnju putanje — varijante move, line, polyline, polygon, polybezier i polydraw — plus CLOSEFIGURE i mali skup zapisa za transformaciju i stanje uređajnog konteksta poput SETWORLDTRANSFORM, SAVEDC i RESTOREDC. Svaka druga vrsta zapisa koja stigne do ExecuteRecord dok je zagrada otvorena — primjerice zalutali EXTTEXTOUT ili prijenos bitmape — centralno se odbacuje jednim pozivom Exit u trenutku kad stigne
Ta provjera postoji jer zagrada putanje u ručno izrađenoj, alatom generiranoj ili jednostavno oštećenoj metadatoteci nema jamstvo da sadrži samo ono što bi dobro oblikovana datoteka stavila između svojih zapisa za otvaranje i zatvaranje. Zapis za izlaz teksta koji bi se našao između EMR_BEGINPATH i EMR_ENDPATH bi, bez te provjere, ili onečistio geometriju putanje koja se gradi ili generirao PDF operator za prikaz teksta usred niza koji bi trebao biti čista izgradnja putanje, a oba načina kvara upravo su ona vrsta koja se pokaže na jednom neispravnom ulazu iz alata treće strane, a ne na nečemu što bi obični paket testova slučajno pokrio. Centraliziranje te provjere u ExecuteRecord znači da se pojedinačni rukovatelji VEMR* ne moraju svaki zasebno braniti od poziva u pogrešnom trenutku; provjera to odlučuje jednom, prije raspodjele, umjesto osamdeset puta nakon nje
Postavljanje vektorskog grafikona uz tekst i slike na istoj stranici
Stranica izvještaja rijetko sadrži samo grafikon, a ShowMetafile se sastavlja s ostalim HotPDF-ovim operatorima stranice potpuno jednako kao i svaki drugi poziv za crtanje. Naslov nacrtan pomoću TextOut, stupčani grafikon ispunjen šrafurom uvezen kao EMF i logotip postavljen pomoću ShowImage mogu se svi naći na istoj stranici u istom sadržajnom toku, svaki zadržavajući svoju izvornu vjernost — obrazac sastavljanja opisan u HotPDF-ovom vodiču za raspoređivanje teksta, fontova i slika u izvještaju:
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 i WMF formata, uzorci aksijalnog sjenčanja koje registrira za gradijentna ispunjenja i preslikavanje ponavljajućih uzoraka za šrafure ovdje opisani, svi su dio standardne HotPDF komponente za Delphi i C++Builder, izvorne VCL biblioteke bez ikakve ovisnosti o vanjskoj DLL datoteci za bilo koji od tih dijelova