Techninis straipsnis

EMF ir WMF vektorių importavimas į Delphi PDF naudojant HotPDF

HotPDF, savasis Delphi ir C++Builder PDF komponentas, importuoja Windows EMF ir WMF metad failus interpretuodamas kiekvieną GDI įrašą tiesiogiai kaip PDF operatorius, o ne suplokštindamas failą į taškinį vaizdą: gradientiniai užpildai tampa PDF ašinio šešėliavimo šablonais, brūkšniniai teptukai tampa PDF raštų šablonais, o centralizuoti kelio būsenos vartai neleidžia pažeistiems įrašams sugadinti išvesties. Kiekviena diagrama, kurią TChart, GDI+ paviršius ar paprastas TCanvas gali eksportuoti kaip išplėstinį metad failą, gali naudoti šį kelią, o skirtumas pasimato vos kas nors padidina puslapį arba išsiunčia jį didelės raiškos spausdintuvui

Alternatyva, kurią dauguma Delphi kūrėjų pagal nutylėjimą renkasi, yra metad failo rastravimas į taškinį vaizdą prieš įdedant jį į puslapį, o kaina išryškėja tik vėliau: ekrane ryški stulpelinė diagrama tampa akivaizdžiai grūdėta, kai PDF spausdinamas 600 DPI raiška arba rodomas posėdžių salės ekrane, o brūkšniais užpildyta CAD sritis virsta vienu plokščiu pilku stačiakampiu, jei užpildo stilius neperduodamas. Metad failo skaitymas kaip programos, o ne paveikslėlio, išvengia abiejų problemų, tačiau šį kelią sunkiau tinkamai įgyvendinti, todėl verta žinoti toliau aprašytas spragas prieš išsiunčiant ataskaitą

Kodėl interpretuoti metad failą, o ne suplokštinti jį į taškinį vaizdą?

HotPDF EMF ir WMF importą palieka vektoriniame kelyje, nes Windows metad failas yra įrašyta GDI piešimo iškvietimų seka, o ne paveikslėlis, ir būtent šių iškvietimų atkūrimas PDF kelio, teksto ir šešėliavimo operatoriais leidžia rezultatui mastelėti taip pat kaip likusiam puslapiui. THPDFPage.ShowMetafile ir jo atitikmuo ShowMetafileEx yra įvesties taškai, kuriuos iškviečia programa, ir abu perduoda metad failą į THPDFWmf, klasę, kuri pereina per kiekvieną GDI įrašą ir jį išverčia. Skirtumas nėra absoliutus, ir HotPDF to neslepia: metad failo įrašas, kuris iš tikrųjų yra rastriniai duomenys, pavyzdžiui, StretchDIBits taškinio vaizdo perkėlimas, įterpiamas kaip tikras PDF Image XObject per AddImage ir ShowImage, tą pačią iškvietimų porą, per kurią pereina bet kuris kitas puslapio paveikslėlis, o ne verčiamas į kelio operatorius, kurie negali išreikšti nuotraukos. Linijos, užpildai ir tekstas lieka vektoriniai; pikseliai, kurie šaltinyje jau buvo pikseliai, išvestyje lieka pikseliais. Paprasčiausiam iškvietimui nieko daugiau nei įkeltas metad failas nereikia:

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;

Kaip interpretatorius GDI koordinates paverčia PDF puslapio erdve?

HotPDF tai atlieka vienu perėjimu per paties metad failo įrašų srautą, o ne antru GDI įgyvendinimu. THPDFWmf.Analyse perskaito metad failo antraštę per Win32 iškvietimą GetEnhMetaFileHeader, iš naujo nustato vidinę piešimo būseną ir iškviečia EnumEnhMetafile, tą pačią enumeravimo API, kurią naudotų metad failų peržiūros programa, todėl kiekvienas EMR_* įrašas pasiekia THPDFWmf.ExecuteRecord tokia tvarka, kokia iš pradžių buvo įrašytas. GDI reiškia koordinates iš viršaus į apačią įrenginio arba loginiais vienetais, parinktais paties metad failo atvaizdavimo režimo; PDF puslapis naudotojo taškų erdvėje eina iš apačios į viršų, o ši koordinačių sistema aprašyta HotPDF drobės piešimo modelyje keliams ir užpildams. Kiekvienas įrašo apdorotuvas šį neatitikimą išsprendžia per ScaleX ir ScaleY, kurie iškviečia ProjectX ir ProjectY, kad atkurtų paties GDI lango į peržiūros sritį formulę anizotropiniams ir izotropiniams atvaizdavimo režimams, todėl penkių loginių vienetų pločio forma PDF taškuose atsiduria tinkamo pločio, nesvarbu, kokius lango ir peržiūros srities matmenis nustatė šaltinio programa

Kaip GDI gradientinis užpildas tampa PDF šešėliavimo šablonu?

EMR_GRADIENTFILL įrašas tampa tikru PDF 2 tipo ašiniu šešėliavimo šablonu (ISO 32000-1 §8.7.4.5), kai GDI jį įrašė vienu iš dviejų stačiakampio režimų. THPDFWmf.VEMRGradientFill tiesiogiai perskaito įrašo išdėstymą iš neapdoroto baitų buferio pagal MS-EMF §2.3.1.6 struktūrą: 16 bitų RGBA kampų viršūnių masyvą, po kurio eina stačiakampių sąrašas, kuriame kiekvienas stačiakampis nurodo dvi iš šių viršūnių. Naudojant GRADIENT_FILL_RECT_H, spalvos kinta iš kairės į dešinę išilgai stačiakampio horizontalios vidurio linijos; naudojant GRADIENT_FILL_RECT_V, jos kinta iš viršaus į apačią išilgai vertikalios vidurio linijos. Abiem atvejais dvi kampų spalvos ir projektuotos stačiakampio koordinatės perduodamos tiesiai į THotPDF.RegisterAxialGradient, kuri grąžina šablono pavadinimą, o puslapis stačiakampį piešia ir užpildo per šį šabloną (SetFillPattern), o ne plokščiu SetRGBFillColor iškvietimu, todėl skaičiuoklės tipo juostinė antraštė ar diagramos gradientinė braižymo sritis išlaiko savo perėjimą ir nevirsta viena vidutine spalva

Gouraud trikampio režimas yra sąžiningai pripažįstama spraga. Kai įrašo laukas ulMode nurodo GRADIENT_FILL_TRIANGLE, VEMRGradientFill tai atpažįsta, užregistruoja, kad trikampio režimas dar neįgyvendintas, ir praleidžia stačiakampį, užuot spėjusi dviejų spalvų aproksimaciją. Viršūnės ir pikselio lygių interpoliacija per savavališką trikampių tinklą nesumažėja iki dviejų taškų ašinio ar radialinio šešėliavimo, o tinkamam išreiškimui reikėtų generuoti PDF 4 arba 5 tipo tinklo šešėliavimą, tą pačią šešėliavimo šeimą, kurią HotPDF puslapio vaizdavimo modulis taip pat palieka nenudažytą skaitydamas PDF. Du nesusiję kodo keliai baigiasi ta pačia riba: tinklo šešėliavimas yra spraga ir rašymo, ir skaitymo pusėje, todėl šaltinio diagrama, kuri tolygaus radialinio švytėjimo efektui naudoja Gouraud trikampius, grįžta prie paskutinio vientiso teptuko, o ne prie vaizduojamos aproksimacijos

Brūkšniniai teptukai tampa raštų šablonais, o ne suplokštinta pilka spalva

GDI brūkšninis teptukas išlaiko savo tekstūrą PDF faile, nes THPDFWmf.SetBrushColor patikrina CurrentBrush.lbStyle, ar tai BS_HATCHED, dar prieš grįždama prie vientiso užpildo, ir tokiu atveju nukreipia į SetHatchBrushPattern. Šis metodas įrašo 8 x 8 vienetų PDF turinio srautą iš brūkšniuotų linijų operatorių m, l ir S, parinktų pagal GDI brūkšnio stilių: vieną horizontalią arba vertikalią liniją, skirtą HS_HORIZONTAL ir HS_VERTICAL, tris lygiagrečias įstrižaines, skirtas HS_FDIAGONAL ir HS_BDIAGONAL, bei horizontalių ir vertikalių arba abiejų įstrižainių derinius, skirtus HS_CROSS ir HS_DIAGCROSS. THotPDF.RegisterTilingPattern registruoja šį turinio srautą kaip spalvotą raštų šabloną (PaintType 1, ISO 32000-1 §8.7.3.1) su 8 vienetų XStep ir YStep, o puslapis užpildo per SetFillPattern taip pat, kaip ašinis šešėliavimas. CAD grindų planas ar inžinerinis brėžinys, kuriame medžiagoms atskirti naudojami brūkšniniai užpildai, PDF faile išlaiko šią vaizdinę kalbą, užuot kiekvieną sritį pavertęs vienoda pilka

Ne kiekvienas teptukas nusipelno tokio apdorojimo, todėl šią spragą verta žinoti prieš išleidžiant CAD importą. EMR_CREATEDIBPATTERNBRUSHPT, įrašas, skirtas pasirinktiniam taškinio vaizdo rašto teptukui, o ne vienam iš šešių standartinių GDI brūkšnių stilių, tik užregistruoja savo rankeną, kad vėlesni SELECTOBJECT ir DELETEOBJECT įrašai išliktų nuoseklūs; HotPDF dar neturi PDF Pattern išteklių konvejerio savavališkiems vaizdų plytelių raštams, todėl pasirinkus tokį teptuką vietoje šaltinio tekstūros naudojamas vientisos spalvos atsarginis variantas. Jei užpildas atvaizduojamas plokščias, nors originale aiškiai naudota pasikartojanti vaizdo tekstūra, šaltinio teptukas beveik neabejotinai yra pasirinktinis DIB raštas, o ne standartinis brūkšnys, ir tai yra vienintelis atvejis, kurį pirmiausia verta patikrinti rankiniu būdu. Tokio brėžinio importas vis tiek konfigūruojamas per tą patį parinkčių 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;

Kas neleidžia pažeistam metad failui sugadinti puslapio?

HotPDF atsakymas yra vieni vartai ExecuteRecord viršuje, o ne apsauginė patikra, kartojama kiekviename iš maždaug aštuoniasdešimties įrašų apdorotuvų. GDI kelio bloką, atidaromą EMR_BEGINPATH ir uždaromą EMR_ENDPATH arba EMR_ABORTPATH, seka privati PathContinue savybė, paremta lauku FPathContinue. Kol šis blokas atidarytas, ExecuteRecord praleidžia tik kelio kūrimo įrašus: perkėlimo, linijos, polilinijos, daugiakampio, polibezjė ir polibrėžinio variantus, taip pat CLOSEFIGURE bei nedidelį transformavimo ir DC būsenos įrašų rinkinį, pavyzdžiui, SETWORLDTRANSFORM, SAVEDC ir RESTOREDC. Bet kuris kitas įrašo tipas, pasiekiantis ExecuteRecord, kol blokas atidarytas, pavyzdžiui, netikėtas EXTTEXTOUT ar taškinio vaizdo perkėlimas, centralizuotai atmetamas viena Exit iškart jam atėjus

Šie vartai reikalingi todėl, kad ranka sukurtame, įrankio sugeneruotame ar tiesiog sugadintame metad faile kelio bloke nebūtinai yra tik tai, ką tvarkingas failas įrašytų tarp atidarymo ir uždarymo įrašų. Teksto išvesties įrašas tarp EMR_BEGINPATH ir EMR_ENDPATH be vartų arba užterštų kuriamą kelio geometriją, arba išvestų PDF teksto rodymo operatorių sekos viduryje, nors ši seka turėtų būti vien tik kelio kūrimas, o abi gedimo rūšys gali pasirodyti su vienu netvarkingu trečiosios šalies įrankio įvadu, kurio įprastas testų rinkinys neapima. Patikros centralizavimas ExecuteRecord reiškia, kad atskiriems VEMR* apdorotuvams nebereikia kiekvienam gintis nuo iškvietimo netinkamu metu; vartai sprendimą priima vieną kartą prieš perduodant įrašą, o ne aštuoniasdešimt kartų po jo

Vektorinės diagramos, teksto ir paveikslėlių išdėstymas viename puslapyje

Ataskaitos puslapyje retai būna tik diagrama, o ShowMetafile dera su kitais HotPDF puslapio operatoriais taip pat kaip bet kuris kitas piešimo iškvietimas. TextOut nupiešta antraštė, kaip EMF importuota brūkšniais užpildyta stulpelinė diagrama ir su ShowImage įdėtas logotipas gali atsidurti tame pačiame puslapyje ir tame pačiame turinio sraute, kiekvienas išlaikydamas savo pradinę kokybę; šis komponavimo būdas aprašytas HotPDF vadove apie teksto, šriftų ir paveikslėlių išdėstymą ataskaitoje:

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);

Čia aprašytas EMF ir WMF interpretatorius, gradientiniams užpildams registruojami ašinio šešėliavimo šablonai ir brūkšniniams teptukams taikomas raštų šablonų susiejimas pateikiami kaip standartinio HotPDF Component dalis, skirta Delphi ir C++Builder, tai savoji VCL biblioteka, kuriai nereikia jokios išorinės DLL priklausomybės