HotPDF, de native Delphi- en C++Builder-PDF-component, importeert Windows EMF- en WMF-metabestanden door elk GDI-record rechtstreeks te interpreteren naar PDF-operatoren in plaats van het bestand te herleiden tot een bitmap: verloopvullingen worden PDF-axiale shading-patronen, arceerpenselen worden PDF-tegelpatronen, en een gecentraliseerde padstatus-poort blokkeert misvormde records die de output zouden kunnen corrumperen. Elke grafiek die een TChart, een GDI+-oppervlak, of een gewone TCanvas kan exporteren als een enhanced metafile is een kandidaat voor dit pad, en het verschil wordt zichtbaar zodra iemand inzoomt op de pagina of deze naar een printer met hoge resolutie stuurt
Het alternatief waar de meeste Delphi-ontwikkelaars standaard naar grijpen, is het metabestand rasteriseren naar een bitmap voordat het op de pagina wordt geplaatst, en de kosten worden pas later zichtbaar: een staafdiagram dat op het scherm scherp was, wordt zichtbaar blokkerig zodra de PDF op 600 DPI wordt afgedrukt of geprojecteerd op een schermin een bestuurskamer, en een met arcering gevulde CAD-regio valt terug naar een enkel vlak grijs rechthoek als de vulstijl niet wordt doorgevoerd. Het metabestand lezen als een programma in plaats van een afbeelding is wat beide problemen vermijdt, en het is het moeilijkere pad om correct te implementeren, wat verklaart waarom de valkuilen hieronder de moeite waard zijn om te kennen voordat een rapport wordt verzonden
Waarom een metabestand interpreteren in plaats van het naar een bitmap te herleiden?
HotPDF houdt EMF- en WMF-import op het vectorpad omdat een Windows-metabestand een opgenomen reeks GDI-tekenaanroepen is, geen afbeelding, en het opnieuw afspelen van die aanroepen als PDF-pad-, tekst- en shading-operatoren is wat het resultaat laat schalen zoals de rest van de pagina. THPDFPage.ShowMetafile en zijn tegenhanger ShowMetafileEx zijn de toegangspunten die een applicatie aanroept, en beide geven het metabestand door aan THPDFWmf, de klasse die elk GDI-record doorloopt en vertaalt. Het onderscheid is niet absoluut, en HotPDF doet ook niet alsof: een metabestandrecord dat werkelijk rasterdata is, bijvoorbeeld een StretchDIBits-bitmap-blit, wordt ingebed als een echte PDF Image XObject via AddImage en ShowImage, hetzelfde paar aanroepen dat elke andere afbeelding op de pagina doorloopt, in plaats van geforceerd te worden in padoperatoren die geen foto kunnen uitdrukken. Lijnen, vullingen en tekst blijven vector; pixels die in de bron al pixels waren, blijven pixels in de output. De eenvoudigste aanroep heeft niets anders nodig dan het geladen metabestand
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;
Hoe zet de interpreter GDI-coördinaten om naar PDF-paginaruimte?
HotPDF beantwoordt dit met één enkele doorloop over de eigen recordstream van het metabestand in plaats van een tweede implementatie van GDI. THPDFWmf.Analyse leest de header van het metabestand via de Win32-aanroep GetEnhMetaFileHeader, reset zijn interne tekenstatus, en roept EnumEnhMetafile aan, dezelfde enumeratie-API die een metabestandviewer zou gebruiken, zodat elk EMR_*-record THPDFWmf.ExecuteRecord bereikt in de volgorde waarin het oorspronkelijk werd opgenomen. GDI drukt coördinaten van boven naar beneden uit in apparaat- of logische eenheden gekozen door de eigen mapping-modus van het metabestand; een PDF-pagina is van onder naar boven in gebruikersruimte-punten, het coördinatensysteem dat wordt behandeld in HotPDF's canvas-tekenmodel voor paden en vullingen. Elke recordhandler lost die mismatch op via ScaleX en ScaleY, die ProjectX en ProjectY aanroepen om de eigen venster-naar-viewport-formule van GDI opnieuw af te spelen voor de anisotrope en isotrope mapping-modi, zodat een vorm die is opgenomen met een logische breedte van vijf eenheden op de juiste breedte in PDF-punten terechtkomt, ongeacht welke venster- en viewport-omvang de bronapplicatie heeft ingesteld
Hoe wordt een GDI-verloopvulling een PDF-shading-patroon?
Een EMR_GRADIENTFILL-record wordt een echt PDF Type 2-axiaal shading-patroon (ISO 32000-1 §8.7.4.5) wanneer GDI het opnam in een van de twee rechthoekmodi. THPDFWmf.VEMRGradientFill leest de eigen indeling van het record rechtstreeks uit de ruwe bytebuffer, volgens de structuur van MS-EMF §2.3.1.6: een vertex-array van 16-bit RGBA-hoekpunten, gevolgd door een lijst van rechthoeken die elk naar twee van die vertices verwijzen. Voor GRADIENT_FILL_RECT_H lopen kleuren van links naar rechts langs de horizontale middellijn van de rechthoek; voor GRADIENT_FILL_RECT_V lopen ze van boven naar beneden langs de verticale middellijn. Hoe dan ook gaan de twee hoekkleuren en de geprojecteerde rechthoekcoördinaten rechtstreeks naar THotPDF.RegisterAxialGradient, wat een patroonnaam teruggeeft, en de pagina tekent de rechthoek en vult deze via dat patroon (SetFillPattern) in plaats van een vlakke SetRGBFillColor-aanroep, zodat een spreadsheet-achtige gebande koptekst of het verloopplotgebied van een grafiek zijn overvloeiing behoudt in plaats van te vervallen tot één gemiddelde kleur
Gouraud-driehoeksmodus is het eerlijke gat. Wanneer het ulMode-veld van het record GRADIENT_FILL_TRIANGLE meldt, herkent VEMRGradientFill dit, logt dat de driehoeksmodus nog niet is geïmplementeerd, en slaat de rechthoek over in plaats van te gokken naar een tweekleurige benadering. Interpolatie per vertex, per pixel over een willekeurig driehoeksraster laat zich niet herleiden tot een axiale of radiale shading met twee stopkleuren, en dit correct uitdrukken zou betekenen dat er een PDF Type 4- of Type 5-meshshading wordt uitgevoerd, dezelfde shadingfamilie die HotPDF's paginarenderer ook ongeschilderd laat bij het terug inlezen van een PDF. Twee ongerelateerde codepaden komen op dezelfde grens uit: meshshadings zijn het gat op zowel de schrijf- als de leeskant, en een brondiagram dat Gouraud-driehoeken gebruikt voor een vloeiende radiale gloed valt terug op wat het laatste vaste penseel ook was, geen gerenderde benadering
Arceerpenselen worden tegelpatronen, geen platte grijstint
Een GDI-arceerpenseel behoudt zijn textuur in de PDF omdat THPDFWmf.SetBrushColor CurrentBrush.lbStyle controleert op BS_HATCHED voordat het ooit terugvalt op een vlakke vulling, en dat geval doorverwijst naar SetHatchBrushPattern. Die methode schrijft een PDF-inhoudsstroom van 8-bij-8-eenheden met gestreken lijnoperatoren, m, l, en S, gekozen op basis van de GDI-arceerstijl: één horizontale of verticale streek voor HS_HORIZONTAL en HS_VERTICAL, drie parallelle diagonalen voor HS_FDIAGONAL en HS_BDIAGONAL, en de combinaties horizontaal-plus-verticaal of beide diagonalen voor HS_CROSS en HS_DIAGCROSS. THotPDF.RegisterTilingPattern registreert die inhoudsstroom als een gekleurd tegelpatroon (PaintType 1, ISO 32000-1 §8.7.3.1) met een XStep en YStep van 8 eenheden, en de pagina vult via SetFillPattern op dezelfde manier als een axiale shading dat doet. Een CAD-plattegrond of een technische tekening die op arceervullingen leunt om materialen te onderscheiden, behoudt die visuele taal in de PDF in plaats van elke regio te verliezen aan identiek grijs
Niet elk penseel verdient die behandeling, en het gat is de moeite waard om te kennen voordat een CAD-import wordt verzonden. EMR_CREATEDIBPATTERNBRUSHPT, het record voor een aangepast bitmap-afbeeldingspatroonpenseel in plaats van een van GDI's zes standaard arceerstijlen, registreert alleen zijn handle zodat latere SELECTOBJECT- en DELETEOBJECT-records consistent blijven; HotPDF ontsluit nog geen PDF Pattern-resourcepijplijn voor willekeurige tegelafbeeldingen, dus het selecteren van dat penseel valt terug op een vaste-kleur-fallback in plaats van de brontextuur. Als een vulling vlak wordt weergegeven waar het origineel duidelijk een herhalende afbeeldingstextuur gebruikte, is het bronpenseel vrijwel zeker een aangepast DIB-patroon in plaats van een standaardarcering, en dat is het ene geval dat het waard is om eerst met de hand te controleren. Het configureren van een import voor zo'n tekening loopt nog steeds via hetzelfde optiesobject
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;
Wat weerhoudt een misvormd metabestand ervan de pagina te corrumperen?
HotPDF's antwoord is één enkele poort bovenaan ExecuteRecord in plaats van een defensieve controle die wordt herhaald in elk van de ongeveer tachtig recordhandlers. Een GDI-padhaakje, geopend door EMR_BEGINPATH en gesloten door EMR_ENDPATH of EMR_ABORTPATH, wordt bijgehouden door een private PathContinue-eigenschap ondersteund door het FPathContinue-veld. Zolang dat haakje open is, laat ExecuteRecord alleen padconstructie-records door, de varianten move, line, polyline, polygon, polybezier en polydraw, plus CLOSEFIGURE en een kleine set transform- en DC-statusrecords zoals SETWORLDTRANSFORM, SAVEDC en RESTOREDC. Elk ander recordtype dat ExecuteRecord bereikt terwijl het haakje open is, bijvoorbeeld een verdwaalde EXTTEXTOUT of een bitmap-blit, wordt centraal weggegooid met een enkele Exit op het moment dat het binnenkomt
Die poort bestaat omdat een padhaakje in een handmatig opgesteld, door tooling gegenereerd, of simpelweg beschadigd metabestand niet gegarandeerd alleen bevat wat een correct gevormd bestand tussen de open- en sluitrecords zou plaatsen. Een tekstuitvoerrecord dat tussen EMR_BEGINPATH en EMR_ENDPATH terechtkomt, zou zonder poort ofwel de padgeometrie in opbouw vervuilen ofwel een PDF-tekstweergaveoperator afgeven midden in een reeks die zuivere padconstructie hoort te zijn, en beide faalmodi zijn het type dat opduikt bij één misvormde invoer van een externe tool, niet bij iets wat een normale testsuite toevallig dekt. Door de controle te centraliseren in ExecuteRecord hoeven de individuele VEMR*-handlers zich niet elk te wapenen tegen aanroep op het verkeerde moment; de poort beslist dat eenmaal, vóór verzending, in plaats van tachtig keer erna
Een vectorgrafiek naast tekst en afbeeldingen op één pagina plaatsen
Een rapportpagina bevat zelden alleen een grafiek, en ShowMetafile combineert met HotPDF's andere pagina-operatoren precies zoals elke andere tekenaanroep dat doet. Een kop getekend met TextOut, een met arcering gevuld staafdiagram geïmporteerd als EMF, en een logo geplaatst met ShowImage kunnen allemaal op dezelfde pagina in dezelfde inhoudsstroom terechtkomen, elk met behoud van zijn native getrouwheid, het compositiepatroon dat wordt behandeld in HotPDF's gids voor het opmaken van tekst, lettertypen en afbeeldingen in een rapport
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);
De EMF- en WMF-interpreter, de axiale shading-patronen die deze registreert voor verloopvullingen, en de tegelpatroon-mapping voor arceerpenselen die hier beschreven wordt, maken allemaal deel uit van de standaard HotPDF-component voor Delphi en C++Builder, een native VCL-bibliotheek zonder externe DLL-afhankelijkheid voor al deze functionaliteit