HotPDF, den native Delphi- og C++Builder-PDF-komponent, importerer Windows EMF- og WMF-metafiler ved at fortolke hver GDI-post direkte til PDF-operatorer i stedet for at flade filen ud til en bitmap: gradientudfyldninger bliver til PDF axial shading-mønstre, skravurpensler bliver til PDF tiling patterns, og en centraliseret sti-tilstandsspærre blokerer misdannede poster fra at ødelægge outputtet. Ethvert diagram, som en TChart, en GDI+-flade eller et almindeligt TCanvas kan eksportere som en enhanced metafile, er en kandidat til denne vej, og forskellen viser sig, i det øjeblik nogen zoomer ind på siden eller sender den til en højopløsningsprinter
Alternativet de fleste Delphi-udviklere som standard griber til, er at rasterisere metafilen til en bitmap, før den lægges på siden, og omkostningen viser sig først senere: et søjlediagram, der var skarpt på skærmen, bliver tydeligt kantet, i det øjeblik PDF'en udskrives ved 600 DPI eller projiceres på et mødelokaleskærm, og en skraveret CAD-region kollapser til et enkelt fladt grå rektangel, hvis udfyldningsstilen ikke føres med. At læse metafilen som et program i stedet for et billede er det, der undgår begge problemer, og det er den sværere vej at implementere korrekt, hvilket er grunden til, at faldgruberne nedenfor er værd at kende, før en rapport sendes ud
Hvorfor fortolke en metafil i stedet for at flade den ud til en bitmap?
HotPDF holder EMF- og WMF-import på vektorvejen, fordi en Windows-metafil er en optaget sekvens af GDI-tegnekald, ikke et billede, og at afspille de kald som PDF-sti-, tekst- og shading-operatorer er det, der lader resultatet skalere som resten af siden. THPDFPage.ShowMetafile og dens modstykke ShowMetafileEx er de indgangspunkter, en applikation kalder, og begge overdrager metafilen til THPDFWmf, klassen der gennemgår hver GDI-post og oversætter den. Skellet er ikke absolut, og HotPDF lader ikke som om andet: en metafilpost, der reelt er rasterdata, for eksempel en StretchDIBits-bitmap-blit, indlejres som et rigtigt PDF Image XObject via AddImage og ShowImage, det samme par kald ethvert andet billede på siden går igennem, i stedet for at blive tvunget ind i sti-operatorer, der ikke kan udtrykke et fotografi. Linjer, udfyldninger og tekst forbliver vektor; pixels, der allerede var pixels i kilden, forbliver pixels i outputtet. Det simpleste kald kræver intet ud over den indlæste metafil:
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;
Hvordan omsætter fortolkeren GDI-koordinater til PDF-sideplads?
HotPDF svarer på det med én gennemgang af metafilens egen post-strøm i stedet for en anden implementering af GDI. THPDFWmf.Analyse læser metafilens header via Win32-kaldet GetEnhMetaFileHeader, nulstiller sin interne tegnetilstand og kalder EnumEnhMetafile, den samme optællings-API en metafilfremviser ville bruge, så hver EMR_*-post når frem til THPDFWmf.ExecuteRecord i den rækkefølge, den oprindeligt blev optaget. GDI udtrykker koordinater oppefra og ned i enheds- eller logiske enheder valgt af metafilens egen mapping mode; en PDF-side er nedefra og op i user-space-punkter, det koordinatsystem der dækkes i HotPDFs canvas-tegnemodel for stier og udfyldninger. Hver post-handler løser dette misforhold via ScaleX og ScaleY, som kalder ProjectX og ProjectY for at genafspille GDI's egen vindue-til-viewport-formel for de anisotrope og isotrope mapping modes, så en form optaget fem logiske enheder bred lander med den korrekte bredde i PDF-punkter, uanset hvilke vindues- og viewport-udstrækninger kildeapplikationen satte
Hvordan bliver en GDI-gradientudfyldning til et PDF shading-mønster?
En EMR_GRADIENTFILL-post bliver til et rigtigt PDF Type 2 axial shading-mønster (ISO 32000-1 §8.7.4.5), når GDI optog den i en af de to rektangel-tilstande. THPDFWmf.VEMRGradientFill læser postens eget layout direkte fra den rå byte-buffer og følger MS-EMF §2.3.1.6-strukturen: et vertex-array af 16-bit RGBA-hjørner efterfulgt af en liste af rektangler, der hver refererer to af de vertices. For GRADIENT_FILL_RECT_H fejer farver fra venstre mod højre langs rektanglets vandrette midterlinje; for GRADIENT_FILL_RECT_V fejer de oppefra og ned langs den lodrette midterlinje. Under alle omstændigheder går de to hjørnefarver og de projicerede rektangelkoordinater direkte ind i THotPDF.RegisterAxialGradient, som returnerer et mønsternavn, og siden tegner rektanglet og udfylder det gennem det mønster (SetFillPattern) i stedet for et fladt SetRGBFillColor-kald, så en regnearksagtig bånd-header eller et diagrams gradient-plotområde bevarer sin overgang i stedet for at kollapse til én gennemsnitsfarve
Gouraud-trekant-tilstand er det ærlige hul. Når postens ulMode-felt rapporterer GRADIENT_FILL_TRIANGLE, genkender VEMRGradientFill det, logger at trekant-tilstand endnu ikke er implementeret, og springer rektanglet over i stedet for at gætte på en to-farvers tilnærmelse. Per-vertex, per-pixel-interpolation over et vilkårligt trekantmesh reducerer ikke til en to-stop axial eller radial shading, og at udtrykke det korrekt ville betyde at udsende en PDF Type 4- eller Type 5-mesh-shading, den samme shading-familie som HotPDFs siderenderer også lader være umalet, når den læser en PDF tilbage. To urelaterede kodestier lander på samme grænse: mesh-shadings er hullet på både skrive- og læsesiden, og et kildediagram, der bruger Gouraud-trekanter til en jævn radial glød, falder tilbage til, hvad end den sidste solide pensel var, ikke en gengivet tilnærmelse
Skravurpensler bliver tiling patterns, ikke fladet grå
En GDI-skravurpensel beholder sin tekstur i PDF'en, fordi THPDFWmf.SetBrushColor tjekker CurrentBrush.lbStyle for BS_HATCHED, før den nogensinde falder tilbage til en solid udfyldning, og dirigerer det tilfælde til SetHatchBrushPattern i stedet. Den metode skriver en PDF-content-stream på 8-gange-8 enheder af streg-operatorer, m, l og S, valgt ud fra GDI-skravurstilen: en enkelt vandret eller lodret streg for HS_HORIZONTAL og HS_VERTICAL, tre parallelle diagonaler for HS_FDIAGONAL og HS_BDIAGONAL, og kombinationerne vandret-plus-lodret eller begge diagonaler for HS_CROSS og HS_DIAGCROSS. THotPDF.RegisterTilingPattern registrerer den content-stream som et farvet tiling pattern (PaintType 1, ISO 32000-1 §8.7.3.1) med et 8-enheders XStep og YStep, og siden udfylder gennem SetFillPattern på samme måde som en axial shading. En CAD-plantegning eller en teknisk tegning, der bruger skraveringer til at skelne materialer, bevarer det visuelle sprog i PDF'en i stedet for at miste hver region til identisk grå
Ikke enhver pensel får den behandling, og hullet er værd at kende, før en CAD-import sendes ud. EMR_CREATEDIBPATTERNBRUSHPT, posten for en brugerdefineret bitmap-billed-mønsterpensel snarere end en af GDI's seks standard skraveringsstile, registrerer kun sit handle, så senere SELECTOBJECT- og DELETEOBJECT-poster forbliver konsistente; HotPDF eksponerer endnu ikke en PDF Pattern-ressourcepipeline til vilkårlige tile-billeder, så valget af den pensel falder igennem til en solid-farve-fallback i stedet for kildeteksturen. Hvis en udfyldning tegnes fladt, hvor originalen tydeligvis brugte en gentagen billedtekstur, er kildepenslen næsten sikkert et brugerdefineret DIB-mønster snarere end en standardskravering, og det er det ene tilfælde, der er værd at tjekke manuelt først. Konfiguration af en import til en tegning som den går stadig gennem det samme options-objekt:
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;
Hvad forhindrer en misdannet metafil i at ødelægge siden?
HotPDFs svar er én spærre øverst i ExecuteRecord i stedet for et defensivt tjek gentaget i hver af dens omkring firs post-handlere. En GDI-sti-parentes, åbnet af EMR_BEGINPATH og lukket af EMR_ENDPATH eller EMR_ABORTPATH, spores af en privat PathContinue-egenskab bakket op af FPathContinue-feltet. Mens den parentes er åben, lader ExecuteRecord kun sti-konstruktionsposter slippe igennem, move-, line-, polyline-, polygon-, polybezier- og polydraw-varianterne, plus CLOSEFIGURE og et lille sæt transform- og DC-tilstandsposter som SETWORLDTRANSFORM, SAVEDC og RESTOREDC. Enhver anden posttype, der når ExecuteRecord, mens parentesen er åben, en vildfaren EXTTEXTOUT eller en bitmap-blit for eksempel, droppes centralt med et enkelt Exit, i det øjeblik den ankommer
Den spærre findes, fordi en sti-parentes i en håndlavet, værktøjsgenereret eller simpelthen ødelagt metafil ikke er garanteret kun at indeholde det, en velformet fil ville lægge mellem sine åbne- og lukke-poster. En tekst-output-post, der lander mellem EMR_BEGINPATH og EMR_ENDPATH, ville uden en spærre enten forurene den sti-geometri, der er under konstruktion, eller udsende en PDF-tekst-visnings-operator midt i en sekvens, der skal være ren sti-konstruktion, og begge fejltilstande er af den slags, der dukker op på ét misdannet input fra et tredjepartsværktøj, ikke på noget en normal testsuite tilfældigvis dækker. At centralisere tjekket i ExecuteRecord betyder, at de enkelte VEMR*-handlere ikke hver især behøver at forsvare sig mod at blive kaldt på det forkerte tidspunkt; spærren afgør det én gang, før dispatch, i stedet for firs gange bagefter
At placere et vektordiagram ved siden af tekst og billeder på én side
En rapportside indeholder sjældent kun et diagram, og ShowMetafile sammensætter med HotPDFs øvrige side-operatorer nøjagtigt som ethvert andet tegnekald. En overskrift tegnet med TextOut, et skraveret søjlediagram importeret som en EMF, og et logo placeret med ShowImage kan alle lande på samme side i den samme content-stream, hver med bevaret ægte gengivelse, kompositionsmønstret dækket i HotPDFs guide til at layoute tekst, skrifttyper og billeder i en 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);
EMF- og WMF-fortolkeren, de axial shading-mønstre den registrerer til gradientudfyldninger, og tiling-pattern-mapningen for skravurpensler beskrevet her indgår alle som en del af standard-HotPDF-komponenten til Delphi og C++Builder, et nativt VCL-bibliotek uden ekstern DLL-afhængighed for noget af det