שלושה מרסטרים יכולים לקרוא את אותו PDF ולחלוק על מה שהוא אומר. המנוע המובנה ב-PDFlibPas הוא זה שנשלח ללא קבצים נוספים ומרנדר הכל באופן מוכשר, וזו הסיבה שהוא מרוויח את מקום ברירת המחדל. Cairo מביא צינור שקיפות ואנטי-אליאסינג שונה, ונוטה להיות זה שאנשים מגיעים אליו כשמסיכות רכות או מצבי מיזוג יוצאים לא נכון במקומות אחרים. PDFium נושא את קוד הרנדור של Chrome, כך שעמוד שנראה נכון בדפדפן בדרך כלל נראה נכון תחת PDFium גם כן, בעלות של DLL גדול ו-bitness שהוא מתעקש להתאים. אף אחד מהשלושה אינו נכון בצורה מופשטת. נכונות היא לפי מסמך, והדרך הכנה היחידה ללמוד איזה מנוע מטפל בקורפוס נתון היא להריץ אותו קורפוס דרך כל אחד מהם
זה המקרה לטיפול במנוע כבחירת זמן ריצה ולא כבחירת זמן בנייה. PDFlibPas, ספריית ה-PDF של Delphi ו-C++Builder מ-losLab, מציב את כל השלושה מאחורי משטח רנדור אחד כך שהחלטה עולה integer אחד במקום ענף קוד. שאר הדברים מצטמצמים לבחירה ביניהם בבטחה, לאשר אילו מנועים הבינארי שנפרס בפועל נושא, ולשמור על מצב הרנדור מלהרעיל בשקט את העבודה הבאה
שלושה מרסטרים מאחורי משטח קריאה אחד
הספרייה ממספרת את המנועים שלה. מנוע 1 הוא המרנדר המובנה, ברירת המחדל, עם אפשרויות החלקת GDI+ על Windows. מנוע 2 הוא Cairo ומנוע 3 הוא PDFium, שניהם נבחרים בזמן ריצה דרך SelectRenderer. שני המנועים החיצוניים נטענים מ-DLLs שנתיביהם אתם מספקים עם SetCairoFileName ו-SetPDFiumFileName לפני בחירתם. לא משנה איזה מנוע פעיל, העבודה עוברת דרך אותן קריאות: RenderPageToFile, RenderPageToStream, RenderDocumentToFile. החלפת מנועים מזיזה מספר אחד; שאר קוד הרנדור שלכם אף פעם לא מבחין
מודל היעד מגיע הרבה מעבר לבמפות. מחלקת המרנדר מכוונת גם למטה-קבצים (WMF, EMF, EMF+), EPS, הקשרי מכשיר ישירים, מדפסות ו-HTML5, עם Cairo ו-PDFium שמופיעים כיעדים נוספים רק כאשר הם נוכללו בזמן הקמפול. פלט רסטר הוא המקום שבו שלשת המנועים חלוקים הכי בצורה גלויה, ולכן זה מה שהדוגמאות כאן משתמשות בו
לעולם אל תניחו שמנוע קיים: בצעו בדיקה בהפעלה
Cairo ו-PDFium הם תכונות קמפול מותנות, מה שאומר שבינארי יכול להיבנות ללא אחד מהם לחלוטין. כשזה קורה, בקשת מנוע 2 או 3 לא מעלה שום דבר. SelectRenderer פשוט מחזיר ערך שאינו ה-ID שביקשתם, וקוד שמתעלם מהערך המוחזר ממשיך לרנדר עם כל מנוע שהיה פעיל כבר. ההגנה היא בדיקת הפעלה שמבקשת מכל מנוע לזהות את עצמו ומתעדת את התשובה:
function ProbeEngines(PDF: TPDFlib): string;
begin
Result := 'built-in'; // engine 1 is always present
if (PDF.SetCairoFileName('cairo.dll') = 1) and (PDF.SelectRenderer(2) = 2) then
Result := Result + ', cairo';
if (PDF.SetPDFiumFileName('pdfium.dll') = 1) and (PDF.SelectRenderer(3) = 3) then
Result := Result + ', pdfium';
PDF.SelectRenderer(1); // restore the default before real work
end;
הריצו את הבדיקה הזאת פעם אחת בהפעלה וכתבו את התוצאה ליומן לצד כל עבודת רנדור. השאלה הנפוצה ביותר כאשר לקוח מדווח על הבדל רנדור היא אילו מנועים ההתקנה שלהם בפועל מכילה, ותשובה של שורה אחת שיושבת ביומן מסיימת את זה ללא הפעלה מרחוק. תופעת לוואי שימושית: אם SetPDFiumFileName עצמה מחזירה 0, אתם כבר יודעים ש-DLL הוא הבעיה (נתיב לא נכון, bitness שגוי, תלות חסרה) ולא בינארי שנקמפל ללא תמיכת PDFium, כיוון שקריאת הנתיב לא פתרה כלום לפני ש-SelectRenderer רץ אי פעם
עשרה פורמטי פלט מאחורי integer אחד של Options
פרמטר Options בקריאות הרנדור בוחר את קידוד הפלט: 0 הוא BMP, 1 JPEG, 2 WMF, 3 EMF, 4 EPS, 5 PNG, 6 GIF, 7 TIFF, 8 EMF+, ו-9 HTML5. PNG (5) הוא ברירת המחדל הסבירה לתמונות תצוגה מקדימה וארכיון עמודים. JPEG (1), בשילוב עם SetJPEGQuality, הוא הבחירה הטובה יותר לסריקות צילום שבהן גודל הקובץ חשוב יותר מקצוות חדים
פורמט אחד מסתיר דרישה לגבי זרם היעד. נתיב ה-BMP כותב את נתוני התמונה תחילה, ואז מחפש חזרה להיסט 0x26 כדי לתקן את שדות הרזולוציה בכותרת. כוונו אותו לזרם רק-קדימה, עטיפת דחיסה או שקע רשת, והקריאה נכשלת בדרך שנראית כמו תקלת מנוע אך אינה. כאשר יעד שאינו-ניתן-לחיפוש הוא בלתי נמנע, רנדרו PNG במקום, או ברצו את ה-BMP דרך זרם זיכרון והעתיקו אותו קדימה לאחר שהוא שלם
ה-DPI שאתם מעבירים אינו ה-DPI שאתם מקבלים
כל קריאת רנדור מקבלת ארגומנט DPI, אך הרזולוציה שאתם מקבלים בפועל היא הערך הזה כפול קנה מידה הרנדור הגלובלי. SetRenderScale מתחיל ב-1.0, וברגע שתשנו אותו הגורם החדש מוחל בשקט על כל רנדור מאוחר יותר על אותו מופע:
PDF.SetRenderScale(2.0); // every later render is doubled
PDF.RenderPageToFile(150, 1, 5, 'p1.png'); // effectively 300 DPI
PDF.SetRenderScale(1.0); // reset, or your thumbnails arrive huge
אותה דביקות חלה על SetRenderCropType ועל הגדרת איכות JPEG. בשירות שמייצר תמונות ממוזערות, תצוגות מקדימות ותמונות ברזולוציה לדפוס ממופע משותף אחד, ההגדרות הנותרות האלה הן מה שעומד מאחורי הכרטיס המדי פעם "תמונות ממוזערות לפתע 40 MB". שתי דרכים נקיות לצאת: אפסו את המצב הרלוונטי בראש כל פעולה, או הקדישו מופע נפרד לכל פרופיל פלט כך שדבר לא ידלף ביניהם
כוונון המנוע ברירת המחדל לפני הגעה לאחר
חלק מפתיע מבקשות "אנחנו צריכים מנוע אחר" מתגלות כבעיות הגדרה שלבשו תחפושת. המרנדר המובנה חושף את התנהגות ההחלקה שלו דרך SetGDIPlusOptions ומשפחת SetRenderOptions הרחבה יותר, ו-SetGDIPlusFileName מאפשר לכם לכוון אותו ל-GDI+ runtime ספציפי כאשר סביבת פריסה מספקת גרסה יוצאת דופן. קווים מחוספסים ב-DPI נמוך, טקסט מטושטש בתמונות ממוזערות, פסים על פני מעברי צבע: כל אלה מגיבים לאותם ידיות, ופנייה אליהן לא עולה כלום במתקין. הוספת Cairo או PDFium, לעומת זאת, אומרת לשלוח יותר DLLs, לעקוב אחרי גרסת bitness שנייה או שלישית, ולהחזיק באחריות לעדכן אותם
לכן תלונת איכות יש לה סדר פעולות טבעי. שחזרו אותה תחילה ב-DPI ובקנה מידה המדויקים של הלקוח, שכן חצי מהפעמים ההבדל מתאדה ברגע שאלה תואמים. נסו את אפשרויות ההחלקה של המנוע המובנה בשלב הבא. רק אז הניחו את העמוד זה לצד זה בין מנועים עם כל משתנה אחר שנשמר קבוע: רנדרו אותו ל-PNG דרך מנועים 1, 2 ו-3 ב-DPI זהה וצרפו את כל השלושה. בדרך כלל שניים מהשלושה מסכימים, ורוב זה אומר לכם אם הקוצר הוא מסמך שמפורש אחרת או הציפייה הבסיסית שלכם עצמה שגויה. שלוש תמונות קונקרטיות מסיימות מחלוקת "מרנדר לא נכון" הרבה יותר מהר מפסקה של שמות תואר
שרשרת גיבוי שמסבירה את עצמה
ברגע שבדיקה ומשמעת מצב נמצאות במקום, שרשרת הגיבוי עצמה קצרה. זיהוי כשל מסתמך על LastRenderError, שמכיל את טקסט ההודעה של המנוע עצמו לרנדור האחרון וריק כאשר הרנדור הצליח:
procedure RenderPageWithFallback(PDF: TPDFlib; Page: Integer; const OutFile: string);
begin
PDF.SelectRenderer(1); // built-in first
PDF.RenderPageToFile(200, Page, 5, OutFile); // 5 = PNG
if PDF.LastRenderError = '' then Exit;
LogEngineFailure('built-in', Page, PDF.LastRenderError);
if PDF.SelectRenderer(3) = 3 then // PDFium as the heavy fallback
begin
PDF.RenderPageToFile(200, Page, 5, OutFile);
if PDF.LastRenderError = '' then Exit;
LogEngineFailure('pdfium', Page, PDF.LastRenderError);
end;
raise Exception.CreateFmt('Page %d failed on all available engines', [Page]);
end;
שתי נקודות עיצוב נושאות משקל כאן. השרשרת מתעדת מדוע כל מעבר קרה, כיוון ששורת יומן שקוראת "עמוד זה נפל חזרה ל-PDFium מאז גרסה 3.7" הוא אות רגרסיה שאתם רוצים שיתנדנד בניטור ולא יאבד. סדר הגיבוי עצמו הוא מדיניות שכדאי לבחור לפי עומס עבודה. המנוע המובנה נפרס ללא DLLs נוספים, מה שהופך אותו לניסיון הראשון הנכון ברוב ההתקנות, בעוד שמסמכים כבדי קבוצות שקיפות או הצללה יוצאת דופן הם הסיבה הרגילה שצוות מחבר מנוע חלופי בכלל. שום מנוע אינו המהיר ביותר בצורה כללית, וזה הנקודה כולה בבחירה לפי קריאה: בנצ'מרקו כל אחד מהם על מדגם מהמסמכים האמיתיים שלכם ב-DPI האמיתי שלכם, וחזרו על מדידה זו בכל פעם שה-DLLs של המנוע או תמהיל המסמכים משתנים. הקורפוס מנצח את הטיעון בכל פעם
מעבר לעמודים בודדים: אצוות TIFF והקשרי מכשיר חיים
שני שכנים של קריאות לפי-עמוד משלימים את ארגז הכלים. RenderAsMultipageTIFFToFile מרנדר ביטוי של טווח-עמודים ישירות ל-TIFF רב-עמודים, הצורה הטבעית למסירות ארכיון למערכות ניהול מסמכים שקדמו ל-PDF. RenderPageToDC מצייר ישירות על הקשר מכשיר של Windows לפקדי תצוגה מקדימה, שנשלט על ידי שלישיית ההגדרות הדביקות שלו (SetRenderDCOffset, SetRenderDCErasePage, בתוספת סוג ה-crop) שזקוקות לאותה משמעת אפוס כמו גורם הקנה מידה. לתצוגה מקדימה על מסך ורנדור נתיב-הדפסה יש מספיק מלכודות משלהם שמצדיק מאמר ייעודי, מקושר למטה
לאן להמשיך
הרגל אחד שכדאי להמשיך: כיוון ש-SelectRenderer נכנס לתוקף לכל קריאה מאוחרת יותר על המופע, עמוד עקשן יחיד יכול להיות מנוסה מחדש על מנוע אחר בזמן ששאר המסמך נשאר על ברירת המחדל. לציור תצוגה מקדימה, בחירת מדפסת וטיפול ב-DevMode, המשיכו עם מאמר תצוגה מקדימה להדפסה והקשר מכשיר. כאשר רנדורים מזינים צינור בנפח גבוה על קבצים גדולים מאוד, גישת ה-handle במדריך הגישה הישירה מתאימה באופן טבעי לרנדור לפי-עמוד דרך DARenderPageToFile
אריזת מנוע, פורמטים נתמכים ובנייות לניסיון מפורטות בדף המוצר PDFlibPas