Câu trả lời ngắn cho ticket hỗ trợ đó là có, kèm giới hạn. HotPDF 2.730.0 dựng trên Free Pascal 3.2.2 và Lazarus 4.6 cho Win64, và các đường tạo, nạp, lưu cốt lõi đều chạy. Những gì không đi theo là bất cứ thứ gì dựa trên một codec object bản địa liên kết tĩnh hoặc trên anonymous method của Delphi
Câu hỏi thường đến theo cùng một kiểu: một nhóm chuẩn hoá lên Lazarus cho một công cụ đa nền tảng, hoặc thừa hưởng một codebase Free Pascal, và muốn có đúng component PDF họ đang mua bản quyền cho Delphi. Port một thư viện Delphi đã trưởng thành hiếm khi chỉ là chuyện cú pháp. Phần thú vị là quá trình port phơi ra những chỗ thư viện lặng lẽ dính vào một toolchain, và ở đây sự dính chặt nằm ở hai vị trí rất cụ thể: ABI object-file của các codec đi kèm, và các tính năng ngôn ngữ nấp sau một symbol phiên bản
Free Pascal 3.2.2 cần gì trước khi HPDFDoc biên dịch được
HotPDF biên dịch được dưới Free Pascal chỉ ở chế độ Delphi, và chỉ khi các thư mục unit LCL của Lazarus nằm trên đường tìm kiếm. Cả hai đều không thể thương lượng. HotPDF.inc chuyển compiler bằng {$MODE DELPHI} và {$H+} bên trong khối {$IFDEF FPC} của nó, và từ chối mọi phiên bản cũ hơn bằng một {$FATAL} khi FPC_FULLVERSION thấp hơn 30202, nên một bản cài 3.0.x fail ầm ĩ thay vì sinh ra một unit hỏng. Package runtime Lazarus HotPDFLaz.lpk mã hoá phần còn lại: LCL là package bắt buộc và -Mdelphi là tuỳ chọn tuỳ biến
Yêu cầu LCL khiến những người chỉ muốn output console ngạc nhiên, nhưng nó mang tính cấu trúc. HPDFFPCCompat cung cấp các kiểu VCL Delphi mà Free Pascal không có tương đương, map TMetafile và TMetafileCanvas lên các lớp bitmap và canvas của LCL, alias TRichEdit sang TMemo, còn HPDFDoc alias TPNGObject sang Graphics.TPortableNetworkGraphic. Hãy coi đó là các shim thời gian biên dịch, chứ không phải ngang hàng tính năng: một lớp metafile chống lưng bằng bitmap giữ cho unit biên dịch được, nhưng không làm các đường metafile vận hành như trên Delphi. Ngay cả smoke test phi GUI cũng kéo theo Interfaces, và script dựng truyền -Fu cho lcl\units\x86_64-win64 cùng thư mục output của lazutils
Vì sao D2009+ không thể kiêm luôn cổng phiên bản
Thật hấp dẫn nếu coi bản dựng Free Pascal là một compiler hiện đại và đơn giản define symbol tính năng Delphi mới nhất. HotPDF không làm vậy, và lý do đáng nói thẳng: D2009+ không chỉ có nghĩa là chuỗi Unicode, nó còn là cổng cho các unit mà API công khai được biểu đạt bằng anonymous method. Free Pascal 3.2.2 không hỗ trợ anonymous method của Delphi lẫn các API đó, nên mượn symbol sẽ kéo theo code không thể biên dịch. Mệnh đề uses của HPDFDoc vì thế mang hai đuôi điều kiện riêng biệt, và phần chồng lấn giữa chúng là chủ đích chứ không phải tình cờ
uses
// ...
HPDFJavaScript,
HPDFFormCalcGraph
{$IFDEF FPC}
, HPDFFPCCodecStubs,
HPDFCMS,
HPDFWinCertSigner
{$ENDIF}
{$IFDEF D2009+}
, HPDFXFARuntime,
HPDFCMS,
HPDFWinCertSigner,
HPDFSignVerify,
HPDFSignatureBatch
{$ENDIF};
Vì sao các codec bản địa dừng ở linker?
Vì chúng là các object Win64 COFF do một toolchain cụ thể phát ra, và không linker Free Pascal nào trên Win64 chịu tiêu thụ chúng: linker nội bộ thì không, đường GNU ld ngoài cũng thế. Đây là vấn đề ABI object-file, không phải vấn đề Pascal, và bao nhiêu nguồn điều kiện cũng chẳng khắc phục được. Thư viện chọn con đường trung thực duy nhất còn lại. Mọi chỉ thị {$L} kéo vào một codec object tĩnh đều được bọc trong {$IFNDEF FPC}, nên bản dựng Free Pascal đơn giản là bỏ qua chúng, và HPDFFPCCodecStubs sau đó cung cấp từng symbol ngoài còn thiếu dưới dạng một stub raise thay vì trả về
// 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;
Bảng stub đó dài, và đọc nó cho bạn biết chính xác những năng lực nào hôm nay chỉ có trên Delphi: các entry point deflate của zlib-ng và zopfli, nén và giải nén libjpeg, codec JPEG 2000 của OpenJPEG, libtiff cùng các bộ khởi tạo theo từng kiểu nén, encode và decode JBIG2, các entry point chuyển đổi màu của Little-CMS, và các primitive AES. Lựa chọn thiết kế đằng sau các stub quan trọng hơn danh sách. Một symbol thiếu lúc link đưa cho bạn một bức tường undefined reference từ một unit bạn chưa từng đụng tới; một stub raise ENotSupportedException đưa cho bạn một bản dựng chạy được, một thông điệp nêu rõ lý do, và một stack trace chỉ thẳng vào chỗ gọi. Nó cũng có nghĩa là bản dựng Free Pascal không bao giờ lặng lẽ sinh ra byte sai nơi bản dựng Delphi sinh ra byte đúng. Còn một hệ quả bậc hai đáng chú ý: chạy các codec ảnh không tin cậy trong một tiến trình cách ly là quyết định chỉ nảy sinh trên bản dựng Delphi, vì bản dựng Free Pascal từ đầu đã không có decoder bản địa trong tiến trình để sandbox
Nén: dòng đầu tiên cần đổi là cmNone
Trước khi port bất cứ thứ gì khác, hãy đặt Compression thành cmNone. THPDFCompressionMethod có đúng hai giá trị, cmNone và cmFlateDecode, và giá trị thứ hai đi thẳng vào các entry point deflate vốn là stub trong bản dựng Free Pascal. Hãy xác minh mô hình object cốt lõi trước với nén tắt, rồi quyết định bạn còn cần gì. Đó là trình tự mà smoke test đi kèm sử dụng: tạo một tài liệu một trang không nén, nạp lại, và khẳng định số trang trả về là một. Output không nén thì lớn hơn, và nó vẫn là một PDF hoàn toàn hợp lệ
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 chạm vào một symbol đã bị stub
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.
Điều gì xảy ra với render trang song song?
Nó vẫn biên dịch, vẫn trả về bitmap đúng, và thôi không còn song song. THotPDF.RenderLoadedPagesParallel và THotPDF.RenderLoadedPagesParallelOrdered được dựng trên TThread.CreateAnonymousThread với một closure procedure inline, thứ mà Free Pascal 3.2.2 không biểu đạt được, nên nhánh Free Pascal chạy một phương án dự phòng tuần tự tất định: nó đi qua các chỉ số trang theo thứ tự, gọi RenderLoadedPageToBitmap cho từng trang, và đếm số lần thành công. Hình dạng API, giá trị trả về và mảng output không đổi, và đó là điều cho phép một codebase duy nhất dựng theo cả hai cách
var
Bitmaps: THPDFBitmapArray;
Info: THPDFParallelRenderPipelineInfo;
Rendered: Integer;
begin
Rendered := Pdf.RenderLoadedPagesParallel([0, 1, 2, 3], 150, 4,
Bitmaps, Info);
// Delphi: Info.WorkerCount là mức mà ngân sách bộ nhớ cho phép
// Free Pascal: Info.WorkerCount luôn là 1, các trang theo thứ tự chỉ số
if Info.WorkerCount = 1 then
LogSerialFallback(Rendered, Info.RequestedWorkerCount);
Phương án dự phòng không im lặng, và đó là phần đáng để thiết kế xoay quanh. Nó điền THPDFParallelRenderPipelineInfo một cách trung thực: PageCount từ yêu cầu, RequestedWorkerCount phản lại đúng cái bạn xin, WorkerCount đặt bằng 1, và các con số hoàn thành với giao trả khớp với những gì thực sự về. Code vốn đã soát Info để chốt kích thước thanh tiến trình hoặc ngân sách bộ nhớ vẫn chạy tốt và đọc được sự thật thay vì một giả định. Nếu kế hoạch thông lượng của bạn dựa vào pipeline render song song và mô hình backpressure của nó, thì đó là kế hoạch cho Delphi; trên Free Pascal, hãy trù phí chi phí đơn luồng của render một trang ra bitmap nhân với số trang
Bạn thực sự nên ship bản dựng nào?
Hãy chọn theo năng lực, không theo sở thích. Nếu quy trình của bạn là lắp ráp tài liệu, vẽ văn bản và vector, điền biểu mẫu, nạp và lưu, thì bản dựng Free Pascal trên Win64 bao trùm được, và bạn nên xác minh với nén tắt trước khi bật bất cứ thứ gì. Nếu nó dính tới ảnh JPEG hay JPEG 2000 hay TIFF hay JBIG2, chuyển đổi màu ICC, output nén, hoặc thông lượng phụ thuộc nhiều lõi, hãy ở lại Delphi hoặc C++Builder lúc này. Ranh giới được vẽ bởi một ABI object-file và một tính năng ngôn ngữ còn thiếu, cả hai đều thấy được trong mã nguồn thay vì bị chôn trong một ma trận hỗ trợ, và cả hai đều fail bằng một lỗi có tên thay vì một kết quả sai
Package Free Pascal và Lazarus được giao trong cùng bộ phân phối với các unit Delphi và C++Builder, nên một giấy phép bao phủ cả hai và bạn có thể thử đường Lazarus trên chính tài liệu của mình trước khi cam kết; trang sản phẩm HotPDF Delphi PDF Component mang ma trận hỗ trợ compiler hiện tại và toàn bộ tài liệu tham khảo API