Tekninen artikkeli

EMF- ja WMF-vektorien tuonti Delphi-PDF-tiedostoihin HotPDF:llä

HotPDF, natiivi Delphi- ja C++Builder-PDF-komponentti, tuo Windowsin EMF- ja WMF-metatiedostoja tulkitsemalla jokaisen GDI-tietueen suoraan PDF-operaattoreiksi sen sijaan, että tiedosto litistettäisiin bittikartaksi: liukuvärjäystäytöistä tulee PDF:n aksiaalisia varjostuskuvioita, viivoitusharjoista tulee PDF:n toistokuvioita, ja keskitetty polkutilan portti estää virheellisiä tietueita turmelemasta tulostetta. Jokainen kaavio, jonka TChart, GDI+-pinta tai tavallinen TCanvas voi viedä laajennettuna metatiedostona, on tämän polun ehdokas, ja ero näkyy heti, kun joku zoomaa sivulle tai lähettää sen tarkkuudeltaan korkealle tulostimelle

Vaihtoehto, johon useimmat Delphi-kehittäjät turvautuvat oletuksena, on metatiedoston rasterointi bittikartaksi ennen sen pudottamista sivulle, ja kustannus paljastuu vasta myöhemmin: pylväskaavio, joka oli näytöllä terävä, muuttuu selvästi rakeiseksi heti, kun PDF tulostetaan 600 DPI:n tarkkuudella tai heijastetaan kokoushuoneen näytölle, ja viivoitustäyttöinen CAD-alue romahtaa yhdeksi tasaiseksi harmaaksi suorakulmioksi, jos täyttötyyliä ei siirretä mukana. Metatiedoston lukeminen ohjelmana kuvan sijaan välttää molemmat ongelmat, ja se on vaikeampi polku toteuttaa oikein, minkä vuoksi alla olevat sudenkuopat kannattaa tuntea ennen raportin julkaisua

Miksi tulkita metatiedosto sen sijaan, että se litistettäisiin bittikartaksi?

HotPDF pitää EMF- ja WMF-tuonnin vektoripolulla, koska Windows-metatiedosto on tallennettu sarja GDI-piirtokutsuja, ei kuva, ja noiden kutsujen toistaminen PDF:n polku-, teksti- ja varjostusoperaattoreina on se, mikä saa lopputuloksen skaalautumaan kuten sivun muukin sisältö. THPDFPage.ShowMetafile ja sen vastine ShowMetafileEx ovat sovelluksen kutsumat aloituspisteet, ja molemmat välittävät metatiedoston luokalle THPDFWmf, joka käy läpi jokaisen GDI-tietueen ja kääntää sen. Jako ei ole ehdoton, eikä HotPDF väitä muutakaan: metatiedoston tietue, joka on aidosti rasteridataa, esimerkiksi StretchDIBits-bittikarttasiirto, upotetaan todellisena PDF-kuva-XObjectina AddImage- ja ShowImage-kutsujen kautta, samojen kutsujen, joita mikä tahansa muukin sivun kuva käyttää, sen sijaan että se pakotettaisiin polkuoperaattoreiksi, jotka eivät voi ilmaista valokuvaa. Viivat, täytöt ja teksti pysyvät vektorimuotoisina; pikselit, jotka olivat pikseleitä jo lähteessä, pysyvät pikseleinä myös tulosteessa. Yksinkertaisin kutsu ei tarvitse muuta kuin ladatun metatiedoston:

var
  Pdf: THotPDF;
  Chart: TMetafile;
begin
  Pdf := THotPDF.Create(nil);
  Chart := TMetafile.Create;
  try
    Chart.LoadFromFile('quarterly-revenue.emf');  // viety TChart:sta tai GDI+:sta
    Pdf.FileName := 'quarterly-report.pdf';
    Pdf.BeginDoc;
    Pdf.CurrentPage.ShowMetafile(Chart);
    Pdf.EndDoc;
  finally
    Chart.Free;
    Pdf.Free;
  end;
end;

Miten tulkki muuttaa GDI-koordinaatit PDF-sivutilaksi?

HotPDF vastaa tähän yhdellä läpikäynnillä metatiedoston omaa tietuevirtaa, ei toisella GDI-toteutuksella. THPDFWmf.Analyse lukee metatiedoston otsikon Win32:n GetEnhMetaFileHeader-kutsun kautta, nollaa sisäisen piirtotilansa ja kutsuu EnumEnhMetafile-funktiota, samaa luettelointi-API:a, jota metatiedoston katseluohjelmakin käyttäisi, joten jokainen EMR_*-tietue saapuu funktioon THPDFWmf.ExecuteRecord siinä järjestyksessä, jossa se alun perin tallennettiin. GDI ilmaisee koordinaatit ylhäältä alas laite- tai loogisina yksikköinä, jotka metatiedoston oma kuvaustila valitsee; PDF-sivu taas on alhaalta ylös käyttäjätilan pisteinä, koordinaatistona, joka käsitellään artikkelissa HotPDF:n canvas-piirtomallista polkuja ja täyttöjä varten. Jokainen tietuekäsittelijä ratkaisee tämän eron funktioilla ScaleX ja ScaleY, jotka kutsuvat funktioita ProjectX ja ProjectY toistaakseen GDI:n oman ikkuna-näyttöalue-kaavan anisotrooppisille ja isotrooppisille kuvaustiloille, joten viiden loogisen yksikön levyinen tallennettu muoto päätyy oikean levyisenä PDF-pisteinä riippumatta siitä, mitkä ikkuna- ja näyttöalue-laajuudet lähdesovellus asetti

Putkikaavio HotPDF:n EMF- ja WMF-metatiedostojen tuonnista Delphissä toistamalla GDI-tietueet THPDFWmfn kautta vektorisiksi PDF-operaattoreiksi, kun taas bittikarttablittit pysyvät kuva-XObjecteina
Jokainen tietue saapuu ExecuteRecord:iin tallennusjärjestyksessä ja laskeutuu projisointeina operaattoreina, kun taas aito pikselidata säilyttää natiivin kuvaobjektimuotonsa

Miten GDI-liukuvärjäystäytöstä tulee PDF-varjostuskuvio?

EMR_GRADIENTFILL-tietueesta tulee todellinen PDF Type 2 -aksiaalinen varjostuskuvio (ISO 32000-1 §8.7.4.5) aina, kun GDI tallensi sen jommassakummassa kahdesta suorakulmiotilasta. THPDFWmf.VEMRGradientFill lukee tietueen oman rakenteen suoraan raa'asta tavupuskurista, noudattaen MS-EMF §2.3.1.6 -rakennetta: 16-bittisten RGBA-kulmien kärkitaulukko, jota seuraa luettelo suorakulmioista, joista kukin viittaa kahteen näistä kärjistä. GRADIENT_FILL_RECT_H-tilassa värit vaihtuvat vasemmalta oikealle suorakulmion vaakasuoraa keskiviivaa pitkin; GRADIENT_FILL_RECT_V-tilassa ne vaihtuvat ylhäältä alas pystysuoraa keskiviivaa pitkin. Kummassakin tapauksessa kaksi kulmaväriä ja projisoidut suorakulmiokoordinaatit menevät suoraan funktioon THotPDF.RegisterAxialGradient, joka palauttaa kuvion nimen, ja sivu piirtää suorakulmion ja täyttää sen kyseisen kuvion kautta (SetFillPattern) tasaisen SetRGBFillColor-kutsun sijaan, joten laskentataulukkomaisen raidoitetun otsikon tai kaavion liukuvärjätyn piirtoalueen sekoitus säilyy sen sijaan, että se romahtaisi yhdeksi keskiarvoväriksi

Gouraud-kolmiotila on rehellinen aukko. Kun tietueen ulMode-kenttä ilmoittaa arvon GRADIENT_FILL_TRIANGLE, VEMRGradientFill tunnistaa sen, kirjaa lokiin, että kolmiotilaa ei ole vielä toteutettu, ja ohittaa suorakulmion sen sijaan, että arvaisi kaksivärisen approksimaation. Kärkikohtainen, pikselikohtainen interpolointi mielivaltaisen kolmioverkon yli ei redusoidu kaksipysäkkiseksi aksiaaliseksi tai säteittäiseksi varjostukseksi, ja sen ilmaiseminen asianmukaisesti tarkoittaisi PDF Type 4- tai Type 5 -verkkovarjostuksen tuottamista, samaa varjostusperhettä, jonka myös HotPDF:n sivunrenderöijä jättää maalaamatta lukiessaan PDF:n takaisin. Kaksi toisiinsa liittymätöntä koodipolkua päätyvät samaan rajaan: verkkovarjostukset ovat aukko sekä kirjoitus- että lukupuolella, ja lähdekaavio, joka käyttää Gouraud-kolmioita pehmeään säteittäiseen hehkuun, palautuu viimeiseen kiinteään siveltimeen sen sijaan, että se renderöitäisiin approksimaationa

Viivoitusharjoista tulee toistokuvioita, ei litistettyä harmaata

GDI-viivoitusharja säilyttää tekstuurinsa PDF:ssä, koska THPDFWmf.SetBrushColor tarkistaa CurrentBrush.lbStyle-arvon BS_HATCHED-tyylin varalta, ennen kuin se koskaan palautuu tasaiseen täyttöön, ohjaten tämän tapauksen funktiolle SetHatchBrushPattern. Tuo metodi kirjoittaa 8x8-yksikön kokoisen PDF-sisältövirran ääriviivaoperaattoreista, m, l ja S, jotka valitaan GDI:n viivoitustyylin mukaan: yksi vaaka- tai pystyviiva tyyleille HS_HORIZONTAL ja HS_VERTICAL, kolme yhdensuuntaista vinoviivaa tyyleille HS_FDIAGONAL ja HS_BDIAGONAL, sekä vaaka- ja pystyviivan tai molempien vinoviivojen yhdistelmät tyyleille HS_CROSS ja HS_DIAGCROSS. THotPDF.RegisterTilingPattern rekisteröi tuon sisältövirran väritettynä toistokuviona (PaintType 1, ISO 32000-1 §8.7.3.1), jonka XStep ja YStep ovat 8 yksikköä, ja sivu täyttää sen SetFillPattern-kutsun kautta samalla tavalla kuin aksiaalinen varjostuskin. CAD-pohjapiirustus tai tekninen piirustus, joka nojaa viivoitustäyttöihin materiaalien erottamiseksi, säilyttää tuon visuaalisen kielen PDF:ssä sen sijaan, että jokainen alue muuttuisi samaksi harmaaksi

Jokainen sivellin ei ansaitse tätä kohtelua, ja aukko kannattaa tuntea ennen CAD-tuonnin julkaisua. EMR_CREATEDIBPATTERNBRUSHPT, tietue mukautetulle bittikartta-kuvakuviosiveltimelle GDI:n kuuden vakioviivoitustyylin sijaan, rekisteröi vain kahvansa, jotta myöhemmät SELECTOBJECT- ja DELETEOBJECT-tietueet pysyvät johdonmukaisina; HotPDF ei vielä tarjoa PDF Pattern -resurssiputkea mielivaltaisille toistokuville, joten tuon siveltimen valitseminen palautuu tasaväriseen varakeinoon lähteen tekstuurin sijaan. Jos täyttö renderöityy tasaisena, kun alkuperäinen selvästi käytti toistuvaa kuvatekstuuria, lähdesivellin on lähes varmasti mukautettu DIB-kuvio eikä vakioviivoitus, ja se on juuri se tapaus, joka kannattaa tarkistaa ensin käsin. Tällaisen piirustuksen tuonnin määrittäminen kulkee silti saman asetusolion kautta:

HotPDF: kuvauskaavio GDI:n EMR_GRADIENTFILL-suorakulmiotiloista PDF:n Type 2 -aksiaalisiin varjostuskuvioihin ja BS_HATCHED-siveltimistä laatoituskuvioihin, kun kolmiomeshit ja DIB-laatat ovat dokumentoidut vararatkaisut
Suorakulmaiset liukuvärit rekisteröityvät aksiaalisiksi varjostuksiksi ja vakioristikkokuviot kahdeksan yksikön laatoituskuvioksi, jolloin kolmioverkot ja mukautetut DIB-laatat jäävät tunnetuiksi varatoiminnoiksi
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);   // aloita dokumentinlaajuisista oletusarvoista
    Options.Redraw := False;          // tulkitse alkuperäiset EMF-tavut, ei GDI-uudelleentallennuskierrosta
    Options.ShowNullBrush := True;    // pidä eksplisiittisesti täyttämättömät CAD-alueet näkyvinä
    Options.UseFrame := True;         // rajaa tuloste EMF-otsikon ilmoittamaan kehykseen
    Pdf.FileName := 'floor-plan.pdf';
    Pdf.BeginDoc;
    Pdf.CurrentPage.ShowMetafileEx(Drawing, Options);
    Pdf.EndDoc;
  finally
    Options.Free;
    Drawing.Free;
    Pdf.Free;
  end;
end;

Mikä estää virheellistä metatiedostoa turmelemasta sivua?

HotPDF:n vastaus on yksi ainoa portti funktion ExecuteRecord alussa sen sijaan, että puolustava tarkistus toistettaisiin jokaisessa sen noin kahdeksassakymmenessä tietuekäsittelijässä. GDI-polkusulkeen, jonka EMR_BEGINPATH avaa ja EMR_ENDPATH tai EMR_ABORTPATH sulkee, tilaa seuraa yksityinen PathContinue-ominaisuus, jonka takana on FPathContinue-kenttä. Kun tuo sulje on auki, ExecuteRecord päästää läpi vain polunrakennustietueet, siirto-, viiva-, murtoviiva-, monikulmio-, polybezier- ja polydraw-muunnelmat, sekä CLOSEFIGURE-tietueen ja pienen joukon muunnos- ja laitekontekstitilan tietueita, kuten SETWORLDTRANSFORM, SAVEDC ja RESTOREDC. Jokainen muu tietuetyyppi, joka saapuu funktioon ExecuteRecord sulkeen ollessa auki, esimerkiksi eksynyt EXTTEXTOUT tai bittikarttasiirto, hylätään keskitetysti yhdellä Exit-lauseella heti sen saapuessa

Tämä portti on olemassa, koska käsin laadittu, työkalulla tuotettu tai yksinkertaisesti vioittunut metatiedosto ei takaa, että sen polkusulkeen sisällä on vain sitä, mitä hyvin muodostettu tiedosto laittaisi avaus- ja sulkutietueidensa väliin. Tekstintulostustietue, joka päätyy EMR_BEGINPATH- ja EMR_ENDPATH-tietueiden väliin, ilman porttia joko saastuttaisi rakenteilla olevan polun geometrian tai tuottaisi PDF:n tekstinäyttöoperaattorin keskelle sarjaa, jonka pitäisi olla puhdasta polunrakennusta, ja molemmat vikatilat ovat sellaisia, jotka paljastuvat yhdellä kolmannen osapuolen työkalun tuottamalla virheellisellä syötteellä, ei millään, mitä tavallinen testisarja sattuu kattamaan. Tarkistuksen keskittäminen funktioon ExecuteRecord tarkoittaa, ettei yksittäisten VEMR*-käsittelijöiden kunkin tarvitse puolustautua väärään aikaan kutsumista vastaan; portti päättää sen kerran, ennen jakelua, sen sijaan että se tehtäisiin kahdeksankymmentä kertaa sen jälkeen

Kaavio HotPDF ExecuteRecord -polkutilaportista, joka päästää sisään vain polkujen rakentamistietueet GDI-polkusulun ollessa auki ja pudottaa harhailut teksti- tai bittikarttatietueet keskitetysti
Vain sallituslistan tietueet jatkavat, kunhan hakasulku on auki, joten harhainen tietue poistuu portilla sen sijaan että saavuttaisi kahdeksankymmentä erillistä käsittelijää

Vektorikaavion asettaminen tekstin ja kuvien viereen samalle sivulle

Raporttisivulla on harvoin vain kaavio, ja ShowMetafile yhdistyy HotPDF:n muiden sivuoperaattoreiden kanssa aivan kuten mikä tahansa muukin piirtokutsu. TextOut-funktiolla piirretty otsikko, EMF:nä tuotu viivoitustäyttöinen pylväskaavio ja ShowImage-funktiolla sijoitettu logo voivat kaikki päätyä samalle sivulle samaan sisältövirtaan, kukin säilyttäen natiivin tarkkuutensa, kompositiomallin, joka käsitellään artikkelissa HotPDF:n opas tekstin, fonttien ja kuvien asetteluun raportissa:

Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Q2 Regional Sales');
Pdf.CurrentPage.ShowMetafile(RegionChart);   // viivoitustäytöiset pylväät, silti vektorimuotoisia
Pdf.CurrentPage.ShowImage(LogoIndex, 450, 760, 90, 30, 0);

EMF- ja WMF-tulkki, sen liukuvärjäystäytöille rekisteröimät aksiaaliset varjostuskuviot ja tässä kuvattu viivoitusharjojen kartoitus toistokuvioiksi kaikki toimitetaan osana vakiomuotoista HotPDF-Delphi-komponenttia Delphille ja C++Builderille, natiivia VCL-kirjastoa ilman ulkoista DLL-riippuvuutta mistään näistä toiminnoista