התשובה הקצרה לפניית התמיכה היא כן, במגבלות. HotPDF 2.730.0 נבנה על Free Pascal 3.2.2 ו־Lazarus 4.6 עבור Win64, ונתיבי היצירה, הטעינה והשמירה הליבתיים עובדים. מה שלא בא איתו הוא כל דבר הנשען על אובייקט codec מקורי שנקשר סטטית או על פונקציות אנונימיות של Delphi
השאלה מגיעה בדרך כלל באותה צורה: צוות מאמץ את Lazarus לכלי חוצה פלטפורמות, או יורש בסיס קוד של Free Pascal, ורוצה את אותו רכיב PDF שהוא כבר רוכש עבור Delphi. העברה של ספריית Delphi בשלה היא לעיתים רחוקות עניין של תחביר. החלק המעניין הוא מה ההעברה חושפת על המקומות שבהם הספרייה הייתה קשורה בשקט לשרשרת כלים אחת, ובמקרה הזה הקישור יושב בשני מקומות ספציפיים מאוד: ה־ABI של קובץ האובייקט של ה־codecs הכלולים, ותכונות המהדר המסתתרות מאחורי סימן גרסה
מה Free Pascal 3.2.2 צריך לפני ש־HPDFDoc מתקמפל
HotPDF מתקמפל תחת Free Pascal רק במצב Delphi, ורק כשספריות היחידות של ה־LCL של Lazarus נמצאות על נתיב החיפוש. אף אחד משניהם אינו נתון למשא ומתן. HotPDF.inc מחליף את המהדר עם {$MODE DELPHI} ו־{$H+} בתוך בלוק ה־{$IFDEF FPC} שלו ומסרב לכל ישן יותר עם {$FATAL} כש־FPC_FULLVERSION נמוך מ־30202, כך שהתקנת 3.0.x נכשלת בקול ולא מייצרת יחידה שבורה. חבילת זמן הריצה של Lazarus HotPDFLaz.lpk מקודדת את השאר: LCL כחבילה נדרשת ו־-Mdelphi כאפשרות מותאמת
דרישת ה־LCL מפתיעה מי שרוצה רק פלט קונסולה, אבל היא מבנית. HPDFFPCCompat מספק את הטיפוסים של ה־VCL של Delphi של־Free Pascal אין להם מקבילה, וממפה את TMetafile ואת TMetafileCanvas על מחלקות מפת סיביות וקנבס של ה־LCL ומכין כינוי TRichEdit ל־TMemo, בזמן ש־HPDFDoc מכין כינוי TPNGObject ל־Graphics.TPortableNetworkGraphic. מתייחסים אליהם כאל ריפודים של זמן קימפול ולא כשוויון תכונות: מחלקת מטאפייל שגבה מפת סיביות משאירה את היחידה מתקמפלת, היא לא גורמת לנתיבי המטאפייל להתנהג כפי שהם מתנהגים ב־Delphi. גם בדיקת העשן שאינה GUI שואבת את Interfaces, וסקריפט הבנייה מעביר -Fu עבור lcl\units\x86_64-win64 ותיקיית הפלט של lazutils
למה D2009+ אינו יכול לשמש גם כשער הגרסה
מפתה להתייחס לבניית Free Pascal כאל מהדר מודרני ופשוט להגדיר את סימן התכונות החדש ביותר של Delphi. HotPDF לא עושה זאת, והסיבה שווה ניסוח בגלוי: D2009+ אינו אומר מחרוזות Unicode בלבד, הוא גם שוער יחידות שה־API הציבורי שלהן מנוסח בפונקציות אנונימיות. Free Pascal 3.2.2 אינו תומך לא בפונקציות אנונימיות של Delphi ולא באותם APIs, ולכן שאילת הסימן הייתה גוררת קוד שאינו יכול להתקמפל. סעיף ה־uses של HPDFDoc נושא לכן שני זנבות מותנים נפרדים, והחפיפה ביניהם מכוונת ולא מקרית
uses
// ...
HPDFJavaScript,
HPDFFormCalcGraph
{$IFDEF FPC}
, HPDFFPCCodecStubs,
HPDFCMS,
HPDFWinCertSigner
{$ENDIF}
{$IFDEF D2009+}
, HPDFXFARuntime,
HPDFCMS,
HPDFWinCertSigner,
HPDFSignVerify,
HPDFSignatureBatch
{$ENDIF};
למה ה־codecs המקוריים נעצרים אצל המקשר?
כי הם אובייקטי Win64 COFF שהופקו על ידי שרשרת כלים אחת מסוימת, ואף מקשר של Free Pascal על Win64 לא יצרוך אותם: לא המקשר הפנימי ולא הנתיב החיצוני של GNU ld. זו בעיה של ABI של קובץ אובייקט, לא בעיה של פסקל, ושום כמות של קוד מותנה לא תתקן אותה. הספרייה לוקחת את הנתיב הכנה היחיד הזמין. כל הנחיית {$L} שמושכת אובייקט codec סטטי עטופה ב־{$IFNDEF FPC}, כך שהבנייה של Free Pascal פשוט מוותרת עליהן, ו־HPDFFPCCodecStubs אז מספק כל סימן חיצוני חסר כסטאב שמעלה חריגה במקום להחזיר
// 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;
טבלת הסטאבים הזאת ארוכה, וקריאתה אומרת לכם בדיוק אילו יכולות ייחודיות ל־Delphi כיום: נקודות הכניסה של deflate של zlib-ng ושל zopfli, הדחיסה והפריקה של libjpeg, ה־codec של OpenJPEG ל־JPEG 2000, libtiff ומאתחלי הדחיסה הפרטניים שלו, קידוד ופענוח JBIG2, נקודות הכניסה של המרת הצבעים של Little-CMS, והפרימיטיבים של AES. בחירת התכנון מאחורי הסטאבים חשובה יותר מהרשימה. סימן חסר בזמן קישור נותן לכם קיר של הפניות לא מוגדרות מיחידה שלא נגעתם בה; סטאב שמעלה ENotSupportedException נותן לכם בנייה שרצה, הודעה שמציינת את הסיבה ומעקב מחסנית שמצביע על אתר הקריאה. זה גם אומר שבניית Free Pascal לעולם לא מייצרת בשקט בייטים שגויים במקום שבו בניית Delphi הייתה מייצרת נכונים. שימו לב גם לאפקט מהדרגה שנייה: הרצת codecs של תמונות לא מהימנות בתהליך מבודד היא החלטה שעולה רק בבנייה של Delphi, כי לבניית Free Pascal אין מפענח מקורי בתוך התהליך להקציף מלכתחילה
דחיסה: השורה הראשונה לשנות היא cmNone
לפני שמעבירים כל דבר אחר, מגדירים את Compression ל־cmNone. THPDFCompressionMethod מציע בדיוק שני ערכים, cmNone ו־cmFlateDecode, והשני מנתב ישר אל נקודות הכניסה של deflate שהן סטאבים בבניית Free Pascal. מאמתים קודם את מודל האובייקטים הליבתי כשהדחיסה כבויה, ואז מחליטים מה עוד צריך. זה הסדר שבו משתמשת בדיקת העשן המשולחת: יוצרים מסמך לא דחוס של עמוד אחד, טוענים אותו מחדש ומוודאים שמספר העמודים חזר כאחד. פלט לא דחוס גדול יותר, והוא עדיין 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 מגיע לסימן 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.
מה קורה לרינדור עמודים מקבילי?
הוא עדיין מתקמפל, עדיין מחזיר מפות סיביות נכונות, ומפסיק להיות מקבילי. THotPDF.RenderLoadedPagesParallel ו־THotPDF.RenderLoadedPagesParallelOrdered בנויים על TThread.CreateAnonymousThread עם סגירת procedure מוטבעת, ש־Free Pascal 3.2.2 אינו יכול לנסח, ולכן הענף של Free Pascal מריץ נפילה טורית דטרמיניסטית: הוא הולך על אינדקסי העמודים לפי הסדר, קורא ל־RenderLoadedPageToBitmap לכל אחד וסופר את ההצלחות. צורת ה־API, ערך ההחזרה ומערך הפלט אינם משתנים, וזה מה שמאפשר לבסיס קוד אחד להיבנות בשתי הדרכים
var
Bitmaps: THPDFBitmapArray;
Info: THPDFParallelRenderPipelineInfo;
Rendered: Integer;
begin
Rendered := Pdf.RenderLoadedPagesParallel([0, 1, 2, 3], 150, 4,
Bitmaps, Info);
// Delphi: Info.WorkerCount הוא מה שתקציב הזיכרון הרשה
// Free Pascal: Info.WorkerCount תמיד 1, עמודים בסדר האינדקס
if Info.WorkerCount = 1 then
LogSerialFallback(Rendered, Info.RequestedWorkerCount);
הנפילה אינה שקטה, וזה החלק ששווה לתכנן סביבו. היא ממלאת את THPDFParallelRenderPipelineInfo ביושר: PageCount מהבקשה, RequestedWorkerCount שמהדהד את מה שביקשתם, WorkerCount שהוגדר 1, ומוני ההשלמה והמסירה שתואמים למה שבאמת חזר. קוד שכבר בוחן את Info כדי למדוד פס התקדמות או תקציב זיכרון ממשיך לעבוד וקורא את האמת במקום הנחה. אם תוכנית התפוקה שלכם תלויה בצינור הרינדור המקבילי ובמודל החזרת הלחץ שלו כפי שתוארו במאמר על צינור הרינדור המקבילי ומודל ה־backpressure, התוכנית הזאת היא תוכנית Delphi; ב־Free Pascal, תקצבו את העלות החד־תהליכונית של רינדור עמוד למפת סיביות כפול מספר העמודים
איזו בנייה באמת כדאי לשלוח?
בוחרים לפי יכולת ולא לפי העדפה. אם זרימת העבודה שלכם היא הרכבת מסמכים, ציור טקסט ווקטורים, מילוי טפסים, טעינה ושמירה, בניית Free Pascal על Win64 מכסה זאת, וכדאי לאמת כשהדחיסה כבויה לפני שמפעילים משהו. אם היא כוללת תמונות JPEG או JPEG 2000 או TIFF או JBIG2, המרות צבע ICC, פלט דחוס או תפוקה התלויה בליבות רבות, נשארים על Delphi או C++Builder בינתיים. הגבול נקבע על ידי ABI של קובץ אובייקט ותכונת שפה חסרה, שניהם נראים בקוד המקור במקום להיות קבורים במטריצת תמיכה, ושניהם נכשלים עם שגיאה ממותנת ולא עם תוצאה שגויה
חבילת Free Pascal ו־Lazarus מגיעה באותה הפצה כמו יחידות Delphi ו־C++Builder, כך שרישיון אחד מכסה את שניהם ואפשר לבחון את נתיב ה־Lazarus מול המסמכים שלכם לפני שמתחייבים אליו; עמוד המוצר של HotPDF Delphi PDF Component נושא את מטריצת התמיכה הנוכחית במהדרים ואת הפניית ה־API המלאה