Tehnički članak

Vektorski uvoz EMF-a na Free Pascalu uz PDFlibPas

PDFlibPas pretvara enhanced metafile-ove u stvaran sadržaj PDF stranice zapis po zapis, umesto da ih rasterizuje, i upravo to čini uvezeni grafikon ili CAD crtež oštrim pri svakom uvećanju. Taj konvertor ima oko 6500 linija i pisan je uz VCL, pa je kada je biblioteka dobila Free Pascal cilj proglašen neportabilnim i zamenjen stub-om. To svrstavanje bilo je pogrešno, i način na koji je bilo pogrešno koristan je čas o tome kako ispitati zavisnost pre nego što odlučite da pišete oko nje iznova

Stvarna VCL površina tih 6500 linija ispostavila se malom: klasa bitmapa korišćena zbog piksel formata, čuvanja toka, handle-a, canvas-a i scanline-ova; klasa metafajla korišćena zbog širine, visine i handle-a; i tip boje sa dve konstante. Svaku od njih već je obezbeđivala sopstvena grafička jedinica biblioteke, koja postoji upravo da non-VCL build ima ekvivalente. Konvertor uopšte nije bio blokiran na VCL-u. Bio je blokiran na Free Pascal Windows jedinici

Podela po osi od koje kod stvarno zavisi

Promena dakle nije bila reimplementacija. Bila je to jedna uslovna direktiva: sa "kompajliraj stub kada se gradi bez VCL-a" na "kompajliraj stub kada se ne gradi za Windows". To je ispravna osa, i izricanje razloga čini razliku očiglednom. Enhanced metafile je Windows kontejner. Konvertor je parser za Windows GDI zapise od vrha do dna. Da li domaća aplikacija koristi VCL, drugi skup vidžeta ili nijedan nema nikakve veze sa tim da li se ti zapisi mogu interpretirati; da li je cilj Windows ima sve veze sa tim

Posledice izbora ispravne ose dobijaju se besplatno. C++Builder build-ovi, koji uklanjaju simbol Windows platforme u ovoj biblioteci, zadržavaju stub koji baca izuzetak i ponašaju se tačno kao pre. macOS zadržava stub, ispravno, jer nema GDI zapisa za parsiranje tamo. Delphi VCL build-ovi netaknuti. A Windows build sa non-VCL skupom vidžeta dobija vektorski EMF uvoz kao sporedni efekat, što niko nije morao implementirati. Uslovna direktiva usklađena sa stvarnom zavisnošću pretvara platformski rad u promenu od jedne linije; direktiva usklađena sa pogrešnom pretvara ga u prepisivanje koje nikad ne nađe mesta u planu

Uslov EMF uvoza prebačen sa pripadnosti VCL-u na Windows platformu, zadržavajući stub-ove drugde i dajući non-VCL Windows build-ovima vektorski uvoz
Prebacivanje stub uslova na Windows platformu čuva ponašanje svih postojećih build-ova i daje non-VCL Windows ciljevima EMF vektorski uvoz bez dodatnog rada

Praznina u Free Pascal-u bila su deklaracije, ne logika

Ono što je zapravo nedostajalo bile su Win32 deklaracije koje Delphi Windows jedinica obezbeđuje, a Free Pascalova ne. Njihovo sabiranje u jednu kompatibilnu jedinicu umesto rasipanja uslovnih kroz konvertor održalo je parser čitljivim. Popis je poučan jer pokazuje koliko je neujednačena pokrivenost header-a između dve RTL-a: 113 konstanti tipova zapisa metafajla, dve zastavice proširenog tekstualnog izlaza, tri konstante režima gradient popune, tip pokazivača handle tabele, alias-i za gradient vertex i primitivne zapise, i tri tipa zapisa koje Free Pascal uopšte ne deklariše, koji pokrivaju alpha blending, transparentno blitanje i režim upravljanja bojama

Ništa od toga nije zanimljivo pojedinačno. Sve to mora biti ispravno pre nego što se parser kompajlira, a kompatibilna jedinica je prirodno mesto jer se može uporediti (diff) sa header dokumentacijom kao celina

Win32 deklaracije kojih nema u Free Pascal Windows jedinici, sabrane u jednu kompatibilnu jedinicu za EMF u PDF vektorski konvertor
Konstante zapisa, zastavice, alias-i i tri odsutna tipa zapisa svi žive u jednoj kompatibilnoj jedinici koja se može uporediti sa header dokumentacijom

Onaj koji tiho crta pogrešnu sliku

Dve od tih deklaracija nisu samo nepostojeće, one su prisutne i pogrešne za ovu namenu, i to je deo vredan pamćenja čak i ako nikad ne dirate metafajl

Free Pascal deklariše zapis za stvaranje četke sa ugrađenom run-time strukturom četke, i zapis proširenog pera sa ugrađenom run-time strukturom pera. Obe te run-time strukture deklarišu svoj hatch član kao ceo broj veličine pokazivača, jer u živom GDI pozivu taj član može nositi handle. Metafajl, međutim, uvek čuva 32-bitni oblik, jer raspored zapisa deo je serijalizovanog formata datoteke i ne menja se sa bitnošću procesa

Na 32-bitnim build-ovima dve strukture se slažu i ništa se ne dešava. Na Win64 član veličine pokazivača ima osam bajtova gde datoteka ima četiri, pa se svako polje posle hatch člana čita sa pogrešnog ofseta. Nema izuzetka, nema greške parsiranja, nema upozorenja. Metafajl se jednostavno prikazuje pogrešno: boje iz pogrešnih bajtova, širine pera iz pogrešnih bajtova, i slika koja deluje kao render greška a ne kao greška rasporeda struktura. Delphi isporučuje eksplicitno 32-bitne varijante obe strukture upravo iz tog razloga, i kompatibilna jedinica ih ponovo deklariše na isti način

Raspored bajtova EMF zapisa četke prikazuje hatch polje veličine pokazivača koje pomera kasnija polja za četiri bajta na Win64 naspram fiksnog 32-bitnog rasporeda
Serijalizovani zapis uvek čuva 4-bajtni hatch, pa struktura run-time veličine pokazivača tiho pogrešno čita svako kasnije polje na Win64
// Pogrešno na Win64: Hatch je veličine pokazivača, datoteka čuva
// 32 bita, i svako sledeće polje pomera se za četiri bajta bez greške
type
  TLogBrushRuntime = record
    lbStyle: UINT;
    lbColor: COLORREF;
    lbHatch: ULONG_PTR;      // 8 bajtova u 64-bitnom procesu
  end;

// Ispravno: serijalizovani raspored, fiksna širina bez obzira na bitnost
type
  TLogBrush32 = record
    lbStyle: UINT;
    lbColor: COLORREF;
    lbHatch: DWORD;          // uvek 4 bajta, kao što se čuva u metafajlu
  end;

Opšte pravilo: svaka struktura koja se pojavljuje i kao run-time API argument i kao serijalizovani raspored polja traži dve deklaracije, i serijalizovana mora koristiti tipove fiksne širine od početka do kraja. Članovi veličine pokazivača u formatu datoteke uvek su greška koja čeka 64-bitni build

Razlike u potpisima funkcija pripadaju wrapper-u, ne na svakom mestu poziva

Preostale razlike bile su obična neslaganja potpisa, i način da se apsorbuju jest forwarding wrapper umesto uslovnog na svakom od mesta poziva. Funkcija kombinovanja transformacija prima pokazivače pod Free Pascal-om gde Delphi prima referentne parametre, pa wrapper prima reference i prosleđuje adrese. On takođe kopira oba izvorna argumenta u lokalne promenljive prvo, jer konvertor ima mesta poziva gde je odredišna matrica istovremeno jedan od izvora, i prosleđivanje iste adrese dvaput funkciji koja piše dok čita proizvodi transformaciju koja je suptilno pogrešna na način koji se pokazuje samo na rotiranom sadržaju

function CombineTransformCompat(var Dest: TXForm;
  const A, B: TXForm): BOOL;
var
  SrcA, SrcB: TXForm;
begin
  // Prvo kopiraj: pozivaoci legitimno prosleđuju Dest kao A ili B
  SrcA := A;
  SrcB := B;
{$IFDEF FPC}
  Result := Windows.CombineTransform(@Dest, @SrcA, @SrcB);
{$ELSE}
  Result := Windows.CombineTransform(Dest, SrcA, SrcB);
{$ENDIF}
end;

Tipovi pravougaonika i tačke su drugi slučaj. Free Pascal tretira zapise pravougaonika i tačke metafajla kao odvojene tipove od opštih grafičkih, pa je osam mesta dodele tražilo eksplicitan cast između zapisa identičnog rasporeda. Oba kompajlera prihvataju cast oblik, pa ta mesta ne nose nikakav uslovni, što vredi malo ružnoće

Šta ovo menja za Free Pascal raspoređivanje

Vektorski EMF uvoz radi na Windows-u pod Free Pascal-om, proizvodeći isti sadržaj stranice kao Delphi build: putanje kao putanje, gradient-i kao sadržaj šablona, tekst kao tekst. Van Windows-a rasterski put ostaje odgovor, i to je ograničenje formata a ne porta. Koordinatno i clipping stanje u koje se konvertor uliva opisano je u članku o CTM i clipping praćenju toka sadržaja, a vektorske primitve koje emituje pokrivene su u vektorskoj grafici, shader-ima i gradient-ima

Ako ispitujete sopstvenu bazu koda za istu priliku, korisna vežba je ona koja je ovo pokrenula: navedite članove koje stvarno koristite iz okvira od kojeg mislite da zavisite. Odgovor je često mnogo kraći nego što sugeriše popis uvoza, i stvarno ograničenje je obično negde sasvim drugde. Putovi uvoza zasnovani na device kontekstu opšte su opisani u članku o pregledu štampe i device kontekstu, a pokrivenost platformi i toolchain-a navedena je na stranici proizvoda losLab PDF Developer Library