Tehnički članak

HotPDF na Free Pascal-u i Lazarus-u: granice Win64 podrške

Kratak odgovor na taj support tiket je da, sa ograničenjima. HotPDF 2.730.0 se gradi na Free Pascal 3.2.2 i Lazarus 4.6 za Win64, i osnovni create, load i save putevi rade. Ono što ne sledi je sve što počiva na statički linkovanom nativnom codec objektu ili na Delphi anonimnim metodama

Pitanje obično stiže isto: tim se standardizuje na Lazarus za cross-platform alat, ili nasleđuje Free Pascal bazu koda, i želi istu PDF komponentu koju već licencira za Delphi. Portovanje zrele Delphi biblioteke retko je pitanje sintakse. Zanimljiv deo je ono što port otkriva o mestima gde je biblioteka tiho bila vezana za jedan toolchain, a u ovom slučaju vezanost sedi na dva vrlo specifična mesta: object-file ABI ugrađenih codec-ova i kompajlerske mogućnosti sakrivene iza verzionog simbola

Matrica mogućnosti koja poredi HotPDF Delphi build sa Free Pascal 3.2.2 i Lazarus 4.6 Win64 buildom, pokazujući koji dokumentni putevi su deljeni i koji codec, kompresija, paralelno renderovanje i anonimno-metodni API-ji dospevaju do stuba koji baca izuzetak
Osnovni create, load i save putevi identični su na oba builda, a razlika sedi u celosti u statički linkovanim codec-ovima i anonimno-metodnim API-jima

Šta Free Pascal 3.2.2 traži pre nego što HPDFDoc kompajlira

HotPDF se kompajlira pod Free Pascal-om samo u Delphi mode-u, i samo kada su Lazarus LCL unit direktorijumi na search putanji. Ništa od toga nije pregovarljivo. HotPDF.inc prebacuje kompajler sa {$MODE DELPHI} i {$H+} unutar svog {$IFDEF FPC} bloka i odbija sve starije sa {$FATAL} kada je FPC_FULLVERSION ispod 30202, pa 3.0.x instalacija glasno pada umesto da proizvede pokvaren unit. Lazarus runtime paket HotPDFLaz.lpk kodira ostalo: LCL kao obavezan paket i -Mdelphi kao custom opciju

LCL zahtev iznenađuje one koji žele samo console izlaz, ali je strukturni. HPDFFPCCompat snabdeva Delphi VCL tipove za koje Free Pascal nema ekvivalent, mapirajući TMetafile i TMetafileCanvas na LCL bitmap i canvas klase i aliasujući TRichEdit na TMemo, dok HPDFDoc aliasuje TPNGObject na Graphics.TPortableNetworkGraphic. Tretirajte ih kao compile-time šimove, ne kao paritet mogućnosti: metafile klasa koju drži bitmapa drži unit u kompajliranju, ne čini da metafile putevi rade kao na Delphi-ju. Čak i non-GUI smoke test povlači Interfaces, a build skripta daje -Fu za lcl\units\x86_64-win64 i lazutils izlazni direktorijum

Zašto D2009+ ne može da služi i kao verziona kapija

Primamljivo je tretirati Free Pascal build kao moderan kompajler i jednostavno definisati najnoviji Delphi mogućnosni simbol. HotPDF to ne čini, i razlog vredi da se izrekne jasno: D2009+ ne znači samo Unicode stringove, on takođe kapijuje unit-e čiji je javni API izražen anonimnim metodama. Free Pascal 3.2.2 ne podržava ni Delphi anonimne metode ni te API-je, pa bi pozajmljivanje simbola povuklo kod koji ne može da se kompajlira. Uses klauzula HPDFDoc-a zato nosi dva odvojena uslovna repa, i preklapanje između njih namerno je, a ne slučajno

uses
  // ...
  HPDFJavaScript,
  HPDFFormCalcGraph
{$IFDEF FPC}
  , HPDFFPCCodecStubs,
  HPDFCMS,
  HPDFWinCertSigner
{$ENDIF}
{$IFDEF D2009+}
  , HPDFXFARuntime,
  HPDFCMS,
  HPDFWinCertSigner,
  HPDFSignVerify,
  HPDFSignatureBatch
{$ENDIF};

Zašto se nativni codec-ovi zaustavljaju na linkeru?

Jer su Win64 COFF objekti koje je emitovao jedan konkretan toolchain, i nijedan Free Pascal linker na Win64 neće da ih konzumira: ni interni linker, ni eksterni GNU ld put. Ovo je object-file ABI problem, ne Pascal problem, i nijedna količina uslovnog izvornog koda to ne rešava. Biblioteka uzima jedini pošten dostupan put. Svaki {$L} direktiv koji povlači statički codec objekat umotan je u {$IFNDEF FPC}, pa Free Pascal build ih jednostavno izostavlja, a HPDFFPCCodecStubs zatim snabdeva svaki nedostajući eksterni simbol stubom koji baca umesto da vrati

// HPDFFPCCodecStubs.pas
function HPDFFPCNativeCodecUnavailable: PtrUInt;
begin
  raise ENotSupportedException.Create(
    'This native codec is not available in the Free Pascal build');
end;

function HPDFFPCStub_deflate: PtrUInt; cdecl;
  public name 'deflate';
begin
  Result := HPDFFPCNativeCodecUnavailable;
end;

Ta stub tabela je dugačka, i čitanje je kaže tačno koje mogućnosti su danas Delphi-only: zlib-ng i zopfli deflate ulazne tačke, libjpeg kompresija i dekompresija, OpenJPEG JPEG 2000 codec, libtiff i njegovi inicijalizatori po kompresiji, JBIG2 kodiranje i dekodiranje, Little-CMS transformacija boja ulazne tačke, i AES primitivi. Dizajn odluka iza stubova važnija je od liste. Nedostajući simbol u link vremenu daje vam zid nedefinisanih referenci iz unit-a kojeg niste ni dirali; stub koji baca ENotSupportedException daje vam build koji radi, poruku koja imenuje razlog i stack trace koji pokazuje na mesto poziva. To takođe znači da Free Pascal build nikad tiho ne proizvodi pogrešne bajtove tamo gde bi Delphi build proizveo ispravne. Primetite i efekat drugog reda: pokretanje nepoverljivih image codec-ova u izolovanom procesu odluka je koja se javlja samo na Delphi buildu, jer Free Pascal build nema in-process nativni dekoder koji bi se sandboksovao

Na Delphi-ju HotPDF statički codec objekti se linkuju i rade nativno, dok Free Pascal Win64 build preskače link direktive i usmerava svaki nedostajući eksterni simbol ka stubu koji baca imenovani izuzetak na mestu poziva
Preskakanje link direktiva i stub-ovanje svakog eksternog simbola pretvara zid nedefinisanih referenci u build koji radi i imenuje sopstvene granice

Kompresija: prva linija koju treba promeniti je cmNone

Pre nego što portujete išta drugo, postavite Compression na cmNone. THPDFCompressionMethod nudi tačno dve vrednosti, cmNone i cmFlateDecode, i druga ide pravo u deflate ulazne tačke koje su stubovi u Free Pascal buildu. Prvo proverite osnovni model objekata sa isključenom kompresijom, pa odlučite šta još trebate. To je redosled koji isporučeni smoke test koristi: napravi jednostranični nekompresovan dokument, ponovo ga učita i tvrdi da se broj stranica vratio kao jedan. Nekompresovan izlaz je veći, i i dalje je sasvim validan PDF

program HotPDFLazarusSmoke;

{$mode delphi}
{$H+}

uses
  Interfaces, SysUtils, HPDFDoc;

var
  Pdf, Reloaded: THotPDF;
  OutputFile: string;
  PageCount: Integer;
begin
  OutputFile := IncludeTrailingPathDelimiter(GetTempDir) +
    'HotPDF-FPC-Smoke.pdf';
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := OutputFile;
    Pdf.Compression := cmNone;   // cmFlateDecode dospeva do stub-ovanog simbola
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 12);
    Pdf.CurrentPage.TextOut(72, 72, 0, 'HotPDF Free Pascal smoke test');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;

  Reloaded := THotPDF.Create(nil);
  try
    PageCount := Reloaded.LoadFromFile(OutputFile);
    if PageCount <> 1 then
      raise Exception.CreateFmt('Expected one page, got %d', [PageCount]);
  finally
    Reloaded.Free;
  end;
end.

Šta se dešava sa paralelnim renderovanjem stranica?

I dalje se kompajlira, i dalje vraća ispravne bitmape, i prestaje biti paralelno. THotPDF.RenderLoadedPagesParallel i THotPDF.RenderLoadedPagesParallelOrdered građeni su na TThread.CreateAnonymousThread sa inline procedure closure-om, koji Free Pascal 3.2.2 ne ume da izrazi, pa Free Pascal grana izvodi determinističku serial rezervu: prelazi indekse stranica redom, poziva RenderLoadedPageToBitmap za svaki i broji uspehe. Oblik API-ja, povratna vrednost i izlazni niz nepromenjeni su, što omogućava jednoj bazi koda da se gradi na oba načina

Isti HotPDF paralelno render poziv radi na preklapajućim radnim nitima pod Delphi-jem i prelazi indekse stranica serialno pod Free Pascal-om, pri čemu pipeline info zapis prijavljuje broj radnika jedan umesto da sakrije rezervu
Free Pascal grana zadržava oblik API-ja i izlazni niz dok prijavljuje broj radnika jedan, pa kod koji već čita info zapis vidi istinu
var
  Bitmaps: THPDFBitmapArray;
  Info: THPDFParallelRenderPipelineInfo;
  Rendered: Integer;
begin
  Rendered := Pdf.RenderLoadedPagesParallel([0, 1, 2, 3], 150, 4,
    Bitmaps, Info);
  // Delphi: Info.WorkerCount je šta god je memorijski budžet dozvolio
  // Free Pascal: Info.WorkerCount je uvek 1, stranice po redu indeksa
  if Info.WorkerCount = 1 then
    LogSerialFallback(Rendered, Info.RequestedWorkerCount);

Rezerva nije tiha, i to je deo vredan dizajniranja oko nje. Ispunjava THPDFParallelRenderPipelineInfo pošteno: PageCount iz zahteva, RequestedWorkerCount odjekujući ono što ste tražili, WorkerCount postavljen na 1, i završeni i isporučeni brojači poklapaju se sa onim što je zaista stiglo. Kod koji već ispituje Info radi dimenzionisanja progresa trake ili memorijskog budžeta i dalje radi i čita istinu umesto pretpostavke. Ako vaš plan propusnosti zavisi od paralelnog render pipeline-a i njegovog backpressure modela, taj plan je Delphi plan; na Free Pascal-u, budžetirajte jednonitni trošak renderovanja stranice u bitmapu pomnožen brojem stranica

Koji biste build zaista isporučili?

Birajte po mogućnosti, ne po sklonosti. Ako je vaš tok rada skupljanje dokumenta, tekst i vektorsko crtanje, popunjavanje formulara, učitavanje i čuvanje, Free Pascal build na Win64 to pokriva, i trebalo bi da proverite sa isključenom kompresijom pre nego što bilo šta uključite. Ako uključuje JPEG ili JPEG 2000 ili TIFF ili JBIG2 slike, ICC transformacije boja, kompresovan izlaz, ili propusnost koja zavisi od mnogih jezgara, ostanite na Delphi ili C++Builder-u za sada. Granicu crtaju object-file ABI i nedostajuća jezička mogućnost, obe vidljive u izvornom kodu umesto zakopane u matrici podrške, i obe padaju sa imenovanom greškom umesto pogrešnim rezultatom

Free Pascal i Lazarus paket stiže u istoj distribuciji kao Delphi i C++Builder unit-i, pa jedna licenca pokriva oba i možete testirati Lazarus put protiv svojih dokumenata pre nego što mu se obavežete; HotPDF Delphi PDF Component stranica proizvoda nosi trenutnu matricu podrške kompajlera i kompletnu API referencu