PDFlibPas convertește metafișierele enhanced în conținut real de pagină PDF înregistrare cu înregistrare, în loc să le rastruze, iar acest lucru menține un grafic sau un desen CAD importat clar la orice zoom. Acel convertor are circa 6500 de linii și a fost scris împotriva VCL, astfel încât, când biblioteca a căpătat o țintă Free Pascal, a fost clasificat drept inportabil și înlocuit cu un stub. Clasificarea a fost greșită, iar modul în care a fost greșită este o lecție utilă despre cum să auditați o dependență înainte să decideți să rescrieți în jurul ei
Suprafața VCL efectiv folosită din acele 6500 de linii s-a dovedit mică: o clasă bitmap folosită pentru formatul de pixel, salvarea în stream, handle, canvas și scanlines; o clasă de metafișier folosită pentru lățime, înălțime și handle; și tipul de culoare cu două constante. Fiecare dintre acestea era deja furnizată de unitatea grafică proprie a bibliotecii, care există tocmai pentru ca build-ul non-VCL să aibă echivalente. Convertorul nu a fost blocat deloc de VCL. A fost blocat de unitatea Windows a Free Pascal
Separare pe axa de care depinde efectiv codul
Deci schimbarea nu a fost o reimplementare. A fost o singură condițională: din „compilează stub-ul când se construiește fără VCL" în „compilează stub-ul când nu se construiește pentru Windows". Aceasta este axa corectă, iar enunțarea motivului face diferența evidentă. Un metafișier enhanced este un container Windows. Convertorul este, de la un capăt la altul, un parser pentru înregistrări GDI Windows. Dacă aplicația gazdă folosește VCL, alt set de widget-uri sau niciun set de widget-uri nu are nicio legătură cu posibilitatea de a interpreta acele înregistrări; faptul că ținta este Windows are tot atât de mult de-a face cu asta
Consecințele alegerii axei corecte rezultă gratis. Build-urile C++Builder, care nedefinesc simbolul de platformă Windows în această bibliotecă, păstrează stub-ul care aruncă excepție și se comportă exact ca înainte. macOS păstrează stub-ul, corect, deoarece acolo nu există înregistrări GDI de interpretat. Build-urile Delphi VCL rămân neatinse. Iar un build Windows cu un set de widget-uri non-VCL câștigă importul vectorial EMF ca efect secundar, fără ca cineva să fi trebuit să îl implementeze. O condițională aliniată cu dependența reală transformă munca de platformă într-o schimbare de o linie; una aliniată cu dependența greșită o transformă într-o rescriere care nu ajunge niciodată în planificare
Lacuna Free Pascal a fost declarații, nu logică
Ce lipsea efectiv erau declarațiile Win32 pe care unitatea Windows din Delphi le furnizează, iar cea din Free Pascal nu. Adunarea lor într-o singură unitate de compatibilitate, în loc să risipiți condiționale prin convertor, a menținut parserul lizibil. Lista este edificatoare, deoarece arată cât de neuniformă este acoperirea header-elor între cele două RTL: 113 constante de tip de înregistrare de metafișier, două fanioane pentru ieșire text extinsă, trei constante de mod de umplere gradient, un tip de pointer pentru tabela de handle, aliasuri pentru înregistrările de vârf gradient și primitive, și trei tipuri de înregistrare pe care Free Pascal nu le declară deloc, care acoperă amestecarea alfa, blitting transparent și modul de gestiune a culorilor
Nimic din toate acestea nu este interesant individual. Totul trebuie să fie corect înainte ca parserul să compileze, iar o unitate de compatibilitate este căminul natural, deoarece poate fi comparată diff cu documentația header-elor ca unitate
Cea care desenează în tăcere imaginea greșită
Două dintre acele declarații nu sunt doar absente: sunt prezente și greșite pentru acest scop, iar aceasta este partea care merită reținută chiar dacă nu atingeți niciodată un metafișier
Free Pascal declară înregistrarea de creare a pensulei cu structura de pensulă de la execuție înglobată în ea, iar înregistrarea stiloului extins cu structura stiloului de la execuție înglobată în ea. Ambele structuri de execuție își declară membrul hatch ca un întreg de mărimea pointerului, deoarece într-un apel GDI real acel membru poate transporta un handle. Un metafișier, în schimb, stochează întotdeauna forma pe 32 de biți, deoarece aspectul înregistrării face parte din formatul de fișier serializat și nu se schimbă odată cu bitness-ul procesului
Pe build-urile pe 32 de biți cele două coincid și nu se întâmplă nimic. Pe Win64 membrul de mărimea pointerului are opt octeți acolo unde fișierul are patru, deci fiecare câmp de după membrul hatch este citit de la offset-ul greșit. Nu există nicio excepție, nicio eroare de parsare și niciun avertisment. Metafișierul pur și simplu se randează greșit: culori din octeții greșiți, lățimi de stilou din octeții greșiți și o imagine care pare un bug de randare, nu un bug de aspect al structurii. Delphi livrează variante explicit pe 32 de biți pentru ambele structuri tocmai din acest motiv, iar unitatea de compatibilitate le redeclară la fel
// Greșit pe Win64: Hatch este de mărimea pointerului, fișierul stochează
// 32 de biți, iar fiecare câmp următor se deplasează cu patru octeți fără eroare
type
TLogBrushRuntime = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: ULONG_PTR; // 8 octeți într-un proces pe 64 de biți
end;
// Corect: aspectul serializat, lățime fixă indiferent de bitness
type
TLogBrush32 = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: DWORD; // întotdeauna 4 octeți, așa cum este stocat în metafișier
end;
Regula generală: orice structură care apare atât ca argument de API la execuție, cât și ca aspect de câmp serializat are nevoie de două declarații, iar cea serializată trebuie să folosească tipuri cu lățime fixă peste tot. Membrii de mărimea pointerului într-un format de fișier sunt întotdeauna un bug care așteaptă un build pe 64 de biți
Diferențele de semnătură aparțin unui wrapper, nu fiecărui punct de apel
Diferențele rămase erau nepotriviri obișnuite de semnătură, iar modul de a le absorbi este un wrapper de redirecționare, nu o condițională la fiecare punct de apel. Funcția de combinare a transformărilor primește pointeri sub Free Pascal, acolo unde Delphi primește parametri prin referință, deci wrapper-ul preia referințe și transmite adrese. De asemenea, copiază mai întâi ambele argumente sursă în variabile locale, deoarece convertorul are puncte de apel în care matricea destinație este simultan una dintre surse, iar transmiterea aceleiași adrese de două ori către o funcție care scrie pe măsură ce citește produce o transformare subtil greșită, într-un mod care se vede doar pe conținut rotit
function CombineTransformCompat(var Dest: TXForm;
const A, B: TXForm): BOOL;
var
SrcA, SrcB: TXForm;
begin
// Copiere mai întâi: apelanții transmit în mod legitim Dest ca A sau B
SrcA := A;
SrcB := B;
{$IFDEF FPC}
Result := Windows.CombineTransform(@Dest, @SrcA, @SrcB);
{$ELSE}
Result := Windows.CombineTransform(Dest, SrcA, SrcB);
{$ENDIF}
end;
Tipurile de dreptunghi și punct sunt celălalt caz. Free Pascal tratează înregistrările de dreptunghi și punct ale metafișierului drept tipuri distincte de cele grafice generale, deci opt puncte de atribuire au avut nevoie de un cast explicit între înregistrări cu aspect identic. Ambele compilatoare acceptă forma cu cast, deci acele puncte nu poartă nicio condițională, ceea ce merită puțină urâțenie
Ce schimbă acest lucru pentru o instalare Free Pascal
Importul vectorial EMF funcționează pe Windows sub Free Pascal, producând același conținut de pagină ca build-ul Delphi: trasee ca trasee, gradienți ca conținut de tipar, text ca text. În afara Windows, calea raster rămâne răspunsul, iar aceasta este o limitare a formatului, nu a portării. Starea de coordonate și decupare în care alimentează convertorul este descrisă în articolul despre trackerul CTM și decupare al content stream, iar primitivele vectoriale emise sunt tratate în grafică vectorială, shaders și gradienți
Dacă auditați propria bază de cod pentru aceeași oportunitate, exercițiul util este cel care a declanșat totul: listați membrii pe care îi folosiți efectiv din framework-ul de care credeți că depindeți. Răspunsul este adesea mult mai scurt decât sugerează lista de import, iar restricția reală se află de obicei în cu totul altă parte. Căile de import bazate pe context de dispozitiv sunt descrise în general în articolul despre print preview și context de dispozitiv, iar acoperirea de platforme și toolchain este listată pe pagina de produs losLab PDF Developer Library