Teknisk artikkel

Import av EMF- og WMF-vektorer i Delphi-PDF-er med HotPDF

HotPDF, den native PDF-komponenten for Delphi og C++Builder, importerer Windows EMF- og WMF-metafiler ved å tolke hver GDI-record direkte til PDF-operatorer i stedet for å flate ut filen til et bitkart: gradientfyllinger blir PDF-aksiale skyggeleggingsmønstre, skravurpensler blir PDF-flisleggingsmønstre, og en sentralisert bane-tilstandssperre hindrer feilformede records fra å ødelegge utdataene. Ethvert diagram en TChart, en GDI+-overflate, eller et rent TCanvas kan eksportere som en utvidet metafil, er en kandidat for denne veien, og forskjellen viser seg i det øyeblikket noen zoomer inn på siden eller sender den til en høyoppløselig skriver

Alternativet de fleste Delphi-utviklere griper til som standard, er å rasterisere metafilen til et bitkart før den plasseres på siden, og kostnaden viser seg først senere: et stolpediagram som var skarpt på skjermen, blir synlig blokkete i det øyeblikket PDF-en skrives ut i 600 DPI eller projiseres på en styrerom-skjerm, og et skravurfylt CAD-område kollapser til et enkelt flatt grått rektangel hvis fyllstilen ikke føres videre. Å lese metafilen som et program i stedet for et bilde er det som unngår begge problemene, og det er den vanskeligere veien å implementere riktig, noe som er grunnen til at fallgruvene nedenfor er verdt å kjenne til før en rapport sendes ut

Hvorfor tolke en metafil i stedet for å flate den ut til et bitkart?

HotPDF holder EMF- og WMF-import på vektorveien fordi en Windows-metafil er en innspilt sekvens av GDI-tegnekall, ikke et bilde, og å spille av disse kallene på nytt som PDF-bane-, tekst- og skyggeleggingsoperatorer er det som lar resultatet skalere som resten av siden. THPDFPage.ShowMetafile og motstykket ShowMetafileEx er inngangspunktene en applikasjon kaller, og begge overleverer metafilen til THPDFWmf, klassen som går gjennom hver GDI-record og oversetter den. Skillet er ikke absolutt, og HotPDF later ikke som noe annet: en metafil-record som er reelle rasterdata, for eksempel en StretchDIBits-bitkart-blitt, bygges inn som et ekte PDF Image XObject via AddImage og ShowImage, det samme paret av kall ethvert annet bilde på siden går gjennom, i stedet for å tvinges inn i baneoperatorer som ikke kan uttrykke et fotografi. Linjer, fyllinger og tekst forblir vektor; piksler som allerede var piksler i kilden, forblir piksler i utdataene. Det enkleste kallet trenger ingenting utover den innlastede metafilen:

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 omgjør tolkeren GDI-koordinater til PDF-sideplass?

HotPDF svarer på det med én enkelt gjennomgang av metafilens egen record-strøm i stedet for en ny implementasjon av GDI. THPDFWmf.Analyse leser metafil-headeren gjennom Win32-kallet GetEnhMetaFileHeader, tilbakestiller sin interne tegnetilstand, og kaller EnumEnhMetafile, det samme opplistings-API-et en metafil-fremviser ville brukt, slik at hver EMR_*-record når THPDFWmf.ExecuteRecord i den rekkefølgen den opprinnelig ble spilt inn. GDI uttrykker koordinater ovenfra-og-ned i enheter eller logiske enheter valgt av metafilens egen mappingmodus; en PDF-side er nedenfra-og-opp i brukerromspunkter, koordinatsystemet dekket i HotPDFs canvas-tegnemodell for baner og fyllinger. Hver record-håndterer løser det misforholdet gjennom ScaleX og ScaleY, som kaller ProjectX og ProjectY for å spille av GDIs egen vindu-til-viewport-formel for de anisotropiske og isotropiske mappingmodusene på nytt, slik at en form spilt inn med fem logiske enheters bredde havner med korrekt bredde i PDF-punkter, uavhengig av hvilke vindu- og viewport-utstrekninger kildeapplikasjonen satte

Hvordan blir en GDI-gradientfylling et PDF-skyggeleggingsmønster?

En EMR_GRADIENTFILL-record blir et ekte PDF Type 2 aksialt skyggeleggingsmønster (ISO 32000-1 §8.7.4.5) når GDI spilte den inn i en av de to rektangelmodusene. THPDFWmf.VEMRGradientFill leser recordens egen layout direkte fra den rå bytebufferen, etter MS-EMF §2.3.1.6-strukturen: et vertex-array av 16-bit RGBA-hjørner, etterfulgt av en liste med rektangler som hver refererer to av disse verticene. For GRADIENT_FILL_RECT_H feier fargene fra venstre til høyre langs rektangelets horisontale midtlinje; for GRADIENT_FILL_RECT_V feier de ovenfra og ned langs den vertikale midtlinjen. Uansett går de to hjørnefargene og de projiserte rektangelkoordinatene rett inn i THotPDF.RegisterAxialGradient, som returnerer et mønsternavn, og siden tegner rektangelet og fyller det gjennom det mønsteret (SetFillPattern) i stedet for et flatt SetRGBFillColor-kall, slik at en regneark-aktig stripete topptekst eller et diagrams gradient-plottområde beholder overgangen sin i stedet for å kollapse til én gjennomsnittsfarge

Gouraud-trekantmodus er det ærlige gapet. Når recordens ulMode-felt rapporterer GRADIENT_FILL_TRIANGLE, gjenkjenner VEMRGradientFill det, logger at trekantmodus ennå ikke er implementert, og hopper over rektangelet i stedet for å gjette på en to-fargers tilnærming. Per-vertex, per-piksel-interpolasjon over et vilkårlig trekantmaske reduseres ikke til en to-stopps aksial eller radial skyggelegging, og å uttrykke det riktig ville bety å utstede en PDF Type 4- eller Type 5-maskeskyggelegging, den samme skyggeleggingsfamilien som HotPDFs sidefremviser også lar stå umalt når den leser en PDF tilbake. To urelaterte kodeveier havner på den samme grensen: maskeskyggelegginger er gapet både på skrivesiden og lesesiden, og et kildediagram som bruker Gouraud-trekanter for en jevn radial glød, faller tilbake til hva enn den siste heldekkende penselen var, ikke en gjengitt tilnærming

Skravurpensler blir flisleggingsmønstre, ikke utflatet grått

En GDI-skravurpensel beholder sin tekstur i PDF-en fordi THPDFWmf.SetBrushColor sjekker CurrentBrush.lbStyle for BS_HATCHED før den noen gang faller tilbake til en heldekkende fylling, og ruter det tilfellet til SetHatchBrushPattern i stedet. Den metoden skriver en 8×8-enhets PDF-innholdsstrøm av strekoperatorer, m, l, og S, valgt av GDI-skravurstilen: et enkelt horisontalt eller vertikalt strøk for HS_HORIZONTAL og HS_VERTICAL, tre parallelle diagonaler for HS_FDIAGONAL og HS_BDIAGONAL, og kombinasjonene horisontal-pluss-vertikal eller begge diagonaler for HS_CROSS og HS_DIAGCROSS. THotPDF.RegisterTilingPattern registrerer den innholdsstrømmen som et farget flisleggingsmønster (PaintType 1, ISO 32000-1 §8.7.3.1) med en 8-enhets XStep og YStep, og siden fyller gjennom SetFillPattern på samme måte som en aksial skyggelegging gjør. En CAD-plantegning eller en teknisk tegning som lener seg på skravurfyllinger for å skille materialer, beholder det visuelle språket i PDF-en i stedet for at hvert område går tapt til identisk grått

Ikke alle pensler får den behandlingen, og gapet er verdt å kjenne til før en CAD-import sendes ut. EMR_CREATEDIBPATTERNBRUSHPT, recorden for en tilpasset bitkart-bildemønsterpensel snarere enn en av GDIs seks standard skravurstiler, registrerer bare sitt handle slik at senere SELECTOBJECT- og DELETEOBJECT-records forblir konsistente; HotPDF eksponerer ennå ikke en PDF Pattern-ressurspipeline for vilkårlige flisbilder, så å velge den penselen faller gjennom til en enkeltfarget reserveløsning i stedet for kildeteksturen. Hvis en fylling gjengis flatt der originalen tydelig brukte en gjentakende bildetekstur, er kildepenselen nesten helt sikkert et tilpasset DIB-mønster snarere enn en standard skravur, og det er det ene tilfellet som er verdt å sjekke for hånd først. Å konfigurere en import for en slik tegning går fortsatt gjennom det samme opsjonsobjektet:

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;

Hva hindrer en feilformet metafil fra å ødelegge siden?

HotPDFs svar er én enkelt sperre øverst i ExecuteRecord i stedet for en defensiv sjekk gjentatt i hver av de rundt åtti record-håndtererne. En GDI-baneklamme, åpnet av EMR_BEGINPATH og lukket av EMR_ENDPATH eller EMR_ABORTPATH, spores av en privat PathContinue-egenskap støttet av feltet FPathContinue. Mens den klammen er åpen, slipper ExecuteRecord bare gjennom banekonstruksjons-records, variantene move, line, polyline, polygon, polybezier og polydraw, pluss CLOSEFIGURE og et lite sett med transformasjons- og DC-tilstands-records slik som SETWORLDTRANSFORM, SAVEDC, og RESTOREDC. Enhver annen recordtype som når ExecuteRecord mens klammen er åpen, for eksempel en villfaren EXTTEXTOUT eller en bitkart-blitt, forkastes sentralt med én enkelt Exit i det øyeblikket den ankommer

Denne sperren finnes fordi en baneklamme i en håndlaget, verktøygenerert, eller ganske enkelt korrupt metafil ikke er garantert å inneholde bare det en velformet fil ville plassert mellom sine åpne- og lukke-records. En tekstutdata-record som havner mellom EMR_BEGINPATH og EMR_ENDPATH ville, uten en sperre, enten forurense banegeometrien under konstruksjon eller utstede en PDF-tekstvisningsoperator midt i en sekvens som er ment å være ren banekonstruksjon, og begge feilmodusene er av den typen som dukker opp på ett enkelt feilformet inndata fra et tredjepartsverktøy, ikke på noe en vanlig testsuite tilfeldigvis dekker. Å sentralisere sjekken i ExecuteRecord betyr at de individuelle VEMR*-håndtererne ikke hver for seg trenger å forsvare seg mot å bli kalt på feil tidspunkt; sperren avgjør det én gang, før utsendelse, i stedet for åtti ganger etterpå

Å plassere et vektordiagram ved siden av tekst og bilder på én side

En rapportside inneholder sjelden bare et diagram, og ShowMetafile komponerer med HotPDFs andre sideoperatorer akkurat slik ethvert annet tegnekall gjør. En overskrift tegnet med TextOut, et skravurfylt stolpediagram importert som en EMF, og en logo plassert med ShowImage kan alle havne på samme side i den samme innholdsstrømmen, hver med bevart opprinnelig troskap, komposisjonsmønsteret dekket i HotPDFs veiledning for å legge ut tekst, skrifttyper og bilder 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-tolkeren, de aksiale skyggeleggingsmønstrene den registrerer for gradientfyllinger, og flisleggingsmønster-kartleggingen for skravurpensler beskrevet her, følger alle med som en del av den standard HotPDF-komponenten for Delphi og C++Builder, et nativt VCL-bibliotek uten eksterne DLL-avhengigheter for noe av dette