PDFlibPas konverterer enhanced metafiles til ægte PDF-sideindhold record for record i stedet for at rasterisere dem, og det er netop dét, der holder et importeret diagram eller en CAD-tegning skarp ved enhver zoom. Den konverter er på omkring 6500 linjer og var skrevet mod VCL, så da biblioteket fik et Free Pascal-mål, blev den klassificeret som uportérbar og erstattet af en stub. Den klassifikation var forkert, og måden den var forkert på er en nyttig lektie i, hvordan man reviderer en afhængighed, før man beslutter sig til at omskrive rundt om den
Den faktiske VCL-overflade af de 6500 linjer viste sig at være lille: en bitmapklasse brugt for sin pixelformat, stream-gemning, handle, canvas og scanlines; en metafilklasse brugt for sin bredde, højde og handle; og farvetypen med to konstanter. Hver eneste af dem blev allerede leveret af bibliotekets egen grafikunit, som netop findes, for at non-VCL-bygget skal have ækvivalenter. Konverteren blokerede slet ikke på VCL. Den blokerede på Free Pascal Windows-uniten
Del langs den akse, koden reelt afhænger af
Ændringen var altså ingen reimplementering. Den var én betinget kompilering: fra "kompilér stubben, når der bygges uden VCL" til "kompilér stubben, når der ikke bygges til Windows". Det er den korrekte akse, og at sige hvorfor gør forskellen oplagt. En enhanced metafile er en Windows-container. Konverteren er fra top til bund en parser for Windows GDI-records. Hvidt værtsapplikationen bruger VCL, et andet widget-sæt eller slet ingen widget-sæt har intet med at gøre, om de records kan fortolkes; hvorvidt målet er Windows har alt med det at gøre
Konsekvenserne af at vælge den rigtige akse falder gratis ud af det. C++Builder-byg, som underfiner Windows-platformsymbolet i dette bibliotek, beholder den kastende stub og opfører sig præcis som før. macOS beholder stubben, korrekt, for der er ingen GDI-records at parse dér. Delphi VCL-byg berøres ikke. Og et Windows-build med et non-VCL widget-sæt får vektor-EMF-import som sidegevinst, som ingen behøvede at implementere. En betinget kompilering, der er justeret efter den reelle afhængighed, gør platformsarbejde til en ændring på én linje; en, der er justeret efter den forkerte, gør det til en omskrivning, der aldrig bliver planlagt
Free Pascal-hullet var deklarationer, ikke logik
Det, der reelt manglede, var Win32-deklarationerne, som Delphi Windows-uniten leverer, og Free Pascal-uniten ikke gør. At samle dem i én kompatibilitetsunit frem for at sprede betingede kompileringer gennem konverteren holdt parseren læsbar. Listen er oplærnde, fordi den viser, hvor ujævn header-dækningen er mellem de to RTL'er: 113 konstanter for metafilrecord-typer, to flags for extended text-output, tre konstanter for gradient fill mode, en pointertype til handle-tabellen, aliaser for gradient vertex- og primitive-records og tre record-typer, som Free Pascal slet ikke deklarerer, dækkende alpha blending, transparent blitting og colour-management mode
Ingen af det er interessant hver for sig. Det hele skal være korrekt, før parseren kompilerer, og en kompatibilitetsunit er det naturlige hjem, fordi den kan diffes mod header-dokumentationen som én helhed
Den, der i stilhed tegner det forkerte billede
To af de deklarationer er ikke blot manglende, de er til stede og forkerte til dette formål, og det er den del, der er værd at huske, selv hvis du aldrig rører en metafil
Free Pascal deklarerer brush-oprettelsesrecorden med run-time brush-strukturen indlejret i den, og extended-pen-recorden med run-time pen-strukturen indlejret i den. Begge de run-time-strukturer deklarerer deres hatch-medlem som et heltal i pointerstørrelse, for i et levende GDI-kald kan det medlem bære et handle. En metafil gemmer derimod altid 32-bit-formen, for record-layoutet er en del af det serialiserede filformat og ændrer sig ikke med processens bitvidde
På 32-bit-byg er de to enige, og der sker intet. På Win64 er det pointerstørrelse medlem otte bytes, hvor filen har fire, så hvert felt efter hatch-medlemmet læses fra forkert offset. Der er ingen exception, ingen parsefejl og ingen advarsel. Metafilen renderes bare forkert: farver fra de forkerte bytes, pennebredder fra de forkerte bytes og et billede, der ligner en renderingsfejl snarere end en struct-layout-fejl. Delphi leverer netop af denne grund eksplicitte 32-bit-varianter af begge strukturer, og kompatibilitetsuniten redeclarerer dem på samme måde
// Forkert på Win64: Hatch er i pointerstørrelse, filen gemmer 32 bits,
// og hvert efterfølgende felt forskubbes med fire bytes uden fejl
type
TLogBrushRuntime = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: ULONG_PTR; // 8 bytes i en 64-bit-proces
end;
// Rigtigt: det serialiserede layout, fast bredde uanset bitvidde
type
TLogBrush32 = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: DWORD; // altid 4 bytes, som gemt i metafilen
end;
Den generelle regel: enhver struktur, der optræder både som run-time API-argument og som serialiseret felt-layout, behøver to deklarationer, og den serialiserede skal bruge typer med fast bredde hele vejen igennem. Medlemmer i pointerstørrelse i et filformat er altid en fejl, der venter på et 64-bit-build
Signaturforskelle hører hjemme i en wrapper, ikke ved hvert call site
De resterende forskelle var almindelige signatur-uoverensstemmelser, og måden at absorbere dem på er en videresendende wrapper frem for en betinget kompilering ved hvert call site. Transformkombinationsfunktionen tager pointere under Free Pascal, hvor Delphi tager referenceparametre, så wrapperen tager referencer og sender adresser. Den kopierer også begge kildeargumenter ind i lokale variabler først, for konverteren har call sites, hvor destinationsmatricen samtidig er en af kilderne, og at sende den samme adresse to gange til en funktion, der skriver, mens den læser, giver en transform, der er diskret forkert på en måde, som kun viser sig på roteret indhold
function CombineTransformCompat(var Dest: TXForm;
const A, B: TXForm): BOOL;
var
SrcA, SrcB: TXForm;
begin
// Kopiér først: kaldere sender med rette Dest som A eller B
SrcA := A;
SrcB := B;
{$IFDEF FPC}
Result := Windows.CombineTransform(@Dest, @SrcA, @SrcB);
{$ELSE}
Result := Windows.CombineTransform(Dest, SrcA, SrcB);
{$ENDIF}
end;
Rektangel- og punkttyperne er det andet tilfælde. Free Pascal behandler metafilens rektangel- og punktrecords som adskilte typer fra de generelle grafiktyper, så otte tildelingssteder behøvede en eksplicit cast mellem records med identisk layout. Begge compilere accepterer cast-formen, så de steder bærer slet ingen betinget kompilering, hvilket er lidt grimhed værd
Hvad det ændrer for en Free Pascal-udrulning
Vektor-EMF-import virker på Windows under Free Pascal og producerer det samme sideindhold som Delphi-bygget: paths som paths, gradients som pattern content, tekst som tekst. Uden for Windows forbliver rastervejen svaret, og det er en begrænsning i formatet snarere end i porteringen. Koordinat- og clipping-tilstanden, som konverteren føder ind i, er beskrevet i artiklen om content stream CTM- og clipping-trackeren, og de vektorprimitiver, den udsender, er dækket i vektorgrafik, shaders og gradients
Hvis du reviderer din egen kodebase for samme mulighed, er den nyttige øvelse den, der startede det hele: list de medlemmer, du reelt bruger, fra det framework, du tror, du afhænger af. Svaret er ofte meget kortere, end importlisten antyder, og den reelle begrænsning ligger som regel et helt andet sted. Device-context-baserede importveje beskrives generelt i artiklen om print preview og device context, og platform- og toolchain-dækning er opført på produktsiden losLab PDF Developer Library