Tehnični članak

Uvoz vektorske EMF na Free Pascalu s PDFlibPas

PDFlibPas pretvori izboljšane metafile (enhanced metafiles) v pravo vsebino strani PDF zapis po zapisu, namesto da jih rastrira, kar je tisto, kar ohranja uvoženi grafikon ali CAD risanje ostro pri kateri koli povečavi. Ta pretvornik ima približno 6500 vrstic in je bil napisan proti VCL, zato je bil, ko je knjižnica pridobila cilj Free Pascal, razvrščen kot neprenosljiv in nadomeščen s štubom (stub). Ta razvrstitev je bila napačna, in način, kako je bila napačna, je koristna lekcija o tem, kako revizirati odvisnost, preden se odločite za prepis okoli nje

Dejanska površina VCL teh 6500 vrstic se je izkazala za majhno: razred bitne slike (bitmap), uporabljen za svojo slikovno obliko, shranjevanje v tok, ročaj (handle), platno in vrstice prekrivanja (scanlines); razred metafile, uporabljen za svojo širino, višino in ročaj; in barvni tip z dvema konstantama. Vsakega od teh je že zagotavljala lastna grafična enota knjižnice, ki obstaja ravno zato, da ima gradnja brez VCL ustrežnike. Pretvornik sploh ni bil blokiran na VCL. Bil je blokiran na enoti Windows Free Pascala

Ločite po osi, od katere koda dejansko je odvisna

Sprememba torej ni bila ponovna implementacija. Bila je en pogoj: iz »prevedi štub, ko se gradi brez VCL« v »prevedi štub, ko se ne gradi za Windows«. To je prava os, in izjava o tem, zakaj, naredi razliko očitno. Izboljšani metafile je vsebnik Windows. Pretvornik je razčlenjevalnik zapisov Windows GDI od vrha do dna. Ali gostujoča aplikacija uporablja VCL, drug nabor gradnikov (widget set) ali pa sploh nobenega, nima nobene zveze z tem, ali se ti zapisi lahko interpretirajo; ali je cilj Windows, pa ima z tem vse

Posledice izbire prave osi padejo ven zastonj. Gradnje C++Builder, ki oddefinirajo simbol platforme Windows v tej knjižnici, obdržijo metajoči štub in se obnašajo natanko kot prej. macOS obdrži štub, pravilno, ker tam ni zapisov GDI za razčlenjevanje. Gradnje Delphi VCL so nedotaknjene. In gradnja Windows z ne-VCL naborom gradnikov pridobi vektorski uvoz EMF kot stranski učinek, ki ga nihče ni moral implementirati. Pogoj, poravnan z resnično odvisnostjo, spremeni delo na platformi v spremembo ene vrstice; pogoj, poravnan z napačno, pa ga spremeni v prepis, ki se nikoli ne razporedi

Pogoj uvoza EMF preosjen iz članstva v VCL na platformo Windows, ki ohranja štube drugje in daje gradnjam Windows brez VCL vektorski uvoz
Premik pogoja štuba na platformo Windows ohranja vedenje vsake obstoječe gradnje in daje ciljem Windows brez VCL vektorski uvoz EMF zastonj

Vrzel Free Pascala so bile deklaracije, ne logika

Tisto, kar je dejansko manjkalo, so bile deklaracije Win32, ki jih zagotavlja enota Windows Delphija in jih tista Free Pascal ne. Zbiranje v eno samo združljivostno enoto namesto raztresanja pogojev skozi pretvornik je ohranilo razčlenjevalnik berljiv. Seznam je poučen, ker pokaže, kako neenakomerno je pokritje glav med dvema RTL: 113 konstant tipov zapisov metafile, dve zastavici razširjenega besedilnega izhoda, tri konstante načina polnjenja z gradientom, tip kazalca tabele ročajev, vzdevki za zapise oglišč gradienta in primitivov ter trije tipi zapisov, ki jih Free Pascal sploh ne deklarira, ki pokrivajo mešanje alfa, prosojno blitting in način upravljanja barv

Nič od tega ni zanimivo posamezno. Vse skupaj mora biti pravilno, preden se razčlenjevalnik prevede, združljivostna enota pa je naravni dom, ker jo je mogoče primerjati z dokumentacijo glav kot enoto

Deklaracije Win32, ki manjkajo v enoti Windows Free Pascala, zbrane v eno združljivostno enoto za vektorski pretvornik EMF v PDF
Konstante zapisov, zastavice, vzdevki in trije odsotni tipi zapisov vsi živijo v eni združljivostni enoti, ki jo je mogoče primerjati z dokumentacijo glav

Tista, ki tiho nariše napačno sliko

Dve od teh deklaracij nita samo manjkajoči, sta prisotni in napačni za ta namen, in to je del, vreden spomina, tudi če se metafila nikoli ne dotaknete

Free Pascal deklarira zapis ustvarjanja čopiča z vanjo vgrajeno strukturo čopiča ob izvajanju in zapis razširjenega peresa z vanjo vgrajeno strukturo peresa ob izvajanju. Obe strukturi ob izvajanju deklarirata svojega člana hatch kot celo število velikosti kazalca, ker v živem klicu GDI lahko ta član nosi ročaj. Metafile pa vedno shranjuje obliko 32 bitov, ker je razporeditev zapisa del serializirane oblike datoteke in se ne spremeni z bitnostjo procesa

Na gradnjah 32 bitov se ta dve ujemata in nič se ne zgodi. Na Win64 je član velikosti kazalca osem bajtov, kjer ima datoteka štiri, zato je vsako polje za članom hatch brano z napačnega odmika. Ni izjeme, ni napake razčlenjevanja in ni opozorila. Metafile se samo napačno upodobi: barve iz napačnih bajtov, širine peres iz napačnih bajtov in slika, ki izgleda kot hrošč upodabljanja in ne kot hrošč razporeditve struktur. Delphi izkazno dostavi izrecno 32-bitne različice obeh struktur ravno iz tega razloga, združljivostna enota pa jih ponovno deklarira na enak način

Razporeditev bajtov zapisa čopiča EMF, ki prikazuje polje hatch velikosti kazalca, ki zamakne poznejša polja za štiri bajte na Win64 v primerjavi s fiksno 32-bitno razporeditvijo
Serializiran zapis vedno shranjuje 4-bajtni hatch, zato struktura ob izvajanju velikosti kazalca tiho napačno bere vsako poznejše polje na Win64
// Napačno na Win64: Hatch je velikosti kazalca, datoteka shranjuje
// 32 bitov, in vsako naslednje polje se zamakne za štiri bajte brez napake
type
  TLogBrushRuntime = record
    lbStyle: UINT;
    lbColor: COLORREF;
    lbHatch: ULONG_PTR;      // 8 bajtov v 64-bitnem procesu
  end;

// Pravilno: serializirana razporeditev, fiksna širina ne glede na bitnost
type
  TLogBrush32 = record
    lbStyle: UINT;
    lbColor: COLORREF;
    lbHatch: DWORD;          // vedno 4 bajti, kot je shranjeno v metafilu
  end;

Splošno pravilo: vsaka struktura, ki se pojavlja tako kot argument API-ja ob izvajanju kot razporeditev polj serializacije, potrebuje dve deklaraciji, serializirana pa mora povsod uporabljati tipe fiksne širine. Člani velikosti kazalca v obliki datoteke so vedno hrošč, ki čaka na 64-bitno gradnjo

Razlike podpisov pripadajo v ovijalnik, ne na vsako mesto klica

Preostale razlike so bile običajna neskladja podpisov, način za njihovo absorbiranje pa je posredujoči ovijalnik namesto pogoja na vsakem od mest klica. Funkcija za kombiniranje transformacij vzame kazalce pod Free Pascalom, kjer Delphi vzame referenčne parametre, zato ovijalnik vzame reference in posreduje naslove. Prav tako najprej skopira oba izvorna argumenta v lokalne spremenljivke, ker ima pretvornik mesta klica, kjer je ciljna matrika hkrati eden od virov, in podajanje istega naslova dvakrat funkciji, ki piše, ko bere, proizvede transformacijo, ki je subtilno napačna na način, ki se pokaže samo na zasukani vsebini

function CombineTransformCompat(var Dest: TXForm;
  const A, B: TXForm): BOOL;
var
  SrcA, SrcB: TXForm;
begin
  // Najprej kopiraj: klicatelji upravičeno posredujejo Dest kot A ali B
  SrcA := A;
  SrcB := B;
{$IFDEF FPC}
  Result := Windows.CombineTransform(@Dest, @SrcA, @SrcB);
{$ELSE}
  Result := Windows.CombineTransform(Dest, SrcA, SrcB);
{$ENDIF}
end;

Tipa pravokotnika in točke sta drugi primer. Free Pascal obravnava zapise pravokotnika in točke metafila kot ločene tipe od splošnih grafičnih, zato je osem mest dodelitve potrebovalo izrecno pretvorbo (cast) med zapisi enake razporeditve. Oba prevajalnika sprejemata obliko pretvorbe, zato ta mesta sploh ne nosijo pogoja, kar je vredno malce grdobije

Kaj to spremeni za namestitev Free Pascala

Vektorski uvoz EMF deluje na Windows pod Free Pascalom in proizvaja enako vsebino strani kot gradnja Delphi: poti kot poti, gradienti kot vsebina vzorca, besedilo kot besedilo. Zunaj Windows ostane rastrska pot odgovor in to je omejitev oblike, ne prenosa. Stanje koordinat in izrezovanja (clipping), v katerega se pretvornik napaja, je opisano v članku o sledilniku CTM in izrezovanja toka vsebine, vektorski primitivi, ki jih izda, pa so pokriti v vektorski grafiki, senčnikih in gradientih

Če revizirate svojo lastno kodno bazo za isto priložnost, je koristna vaja tista, ki je to začela: naštejte člane, ki jih dejansko uporabljate iz ogrodja, od katerega menite, da ste odvisni. Odgovor je pogosto veliko krajši, kot nakazuje uvozni seznam, prava omejitev pa je običajno povsem drugje. Poti uvoza na osnovi konteksta naprave (device context) na splošno so opisane v članku o predogledu tiskanja in kontekstu naprave, pokritost platform in orodjarn pa je navedena na strani produkta losLab PDF Developer Library