החזיקו לחוץ את כפתור הזום במציג PDF נאיבי וצפו בגרף ה-CPU. לחיצה אחת על פקד זום בעל חזרה-אוטומטית מפעילה תריסר צעדי זום או יותר בשנייה, ואם כל צעד מתחיל רינדור מחדש באיכות מלאה של הדף הנראה, הרינדורים נערמים מהר יותר משהם מסתיימים. הדף עובר רסטור בסדר בבידוד, אולי 180 אלפיות שנייה לסריקת A4, אך אתם כעת מריצים תריסר רינדורים של 180 אלפיות שנייה כנגד עבודה שהמשתמש כבר חלף על פניה. המציג נתקע, ליבה ננעצת ב-100%, ועד שהמסך משלים את הפער המשתמש עצר ברמת זום מארבעה רינדורים אחורה. התרופה אינה רסטרייזר מהיר יותר. זהו מטמון שמחזיר דפים מוגמרים מיידית ולולאת רינדור המוכנה לנטוש עבודה ברגע שהיא מתיישנת
PDFium Component מוסר לכם את החלקים לשניהם ונשאר מחוץ למדיניות. אתם מקבלים מפות סיביות בבעלות הקורא, רסטרייזר הדרגתי הלוקח אסימון ביטול, מצבי התאמה המחשבים מחדש זום בעת שינוי גודל, וקריאת ריצוף לדפים גדולים מדי לרסטור שלם. מה שהוא אינו מספק בכוונה הוא המטמון עצמו, מפני שמדיניות הפינוי הנכונה תלויה באזור התצוגה שלכם, בתקרת הזיכרון של הפלטפורמה שלכם, ובאופן שבו המשתמשים שלכם גוללים. אותה החלטה היא שלכם לעשות נכון, וההשלכות של עשייה שגויה הן בדיוק הקפיאה והדליפה
לאן הולכות אלפיות השנייה והמגה-בייטים
שימו מספרים על העלות לפני שאתם מתכננים דבר. דף A4 ב-96 DPI הוא בערך 794 על 1123 פיקסלים, כ-3.5 MB כמפת סיביות 32-סיביות. זמו ל-200% וזה מתרבע. ב-400% בתצוגת High-DPI אתם מקצים וממלאים מפת סיביות של דף יחיד של 50 עד 60 MB, ומציג גלילה רציפה שומר כמה דפים חיים בו-זמנית. עלות הרסטור עוקבת אחר פיקסלי הפלט, ולכן כל הכפלה של זום מרבעת בקירוב את זמן הרינדור והזיכרון יחד
שתי השלכות נופלות ישר מהחשבון הזה. מטמון שמפתחו מתעלם מרמת הזום חסר-ערך, מפני שהמחווה עצמה שעליו להאיץ, זום, מפיקה מפת סיביות חדשה בכל פעם. ומטמון לא-חסום ימצה את מרחב הכתובות של תהליך 32-סיביות בדיוק במסמכים שעליהם אנשים זומים הכי חזק: סריקות שטרי-בעלות צפופות, שרטוטי הנדסה, מפות בפורמט גדול. על המטמון להיות ממופתח נכון ומוגבל בתוקף, ואף אחד מהם אינו אופציונלי
מה שייך למפתח המטמון
מפת סיביות שבמטמון בטוחה לשימוש חוזר רק כשכל קלט שעיצב את הפיקסלים שלה עדיין תואם. פירוש הדבר מספר הדף, הזום האפקטיבי (או באופן שקול ממדי פיקסלי הפלט), הסיבוב, ה-DPI של המסך, ואפשרויות הרינדור שהיו בתוקף כשהיא הופקה. דף שעבר רינדור עם reAnnotations הוא תמונה שונה מאותו דף בלעדיהן, ומעבר גווני-אפור דרך reGrayscale שונה שוב. השמיטו אחד מאלה מהמפתח והבאגים צפויים: שכבת הערה הנשארת אחרי שסוקר מוחק את ההערה, או דף שהופך מטושטש ברגע שמשתמש גורר את החלון מלוח מחשב נייד לצג 4K חיצוני וה-DPI משתנה תחת מפת סיביות מיושנת
function TPageCache.Acquire(Pdf: TPdf; PageNo: Integer; ZoomPct: Single;
Rotation: TRotation; Opts: TRenderOptions): TBitmap;
var
Key: string;
begin
Key := Format('%d|%.0f|%d|%d|%d',
[PageNo, ZoomPct, Ord(Rotation), Screen.PixelsPerInch, OptionsMask(Opts)]);
if FBitmaps.TryGetValue(Key, Result) then
Exit;
Pdf.PageNumber := PageNo;
Result := Pdf.RenderPage(0, 0, OutputWidth(PageNo, ZoomPct),
OutputHeight(PageNo, ZoomPct), Rotation, Opts);
FBitmaps.Add(Key, Result); // the cache now owns this bitmap
end;
בפגיעה זה חוזר באלפיות-מיליונית, וזו כל הנקודה. השאלה הקשה יותר היא מה קורה למפות הסיביות שנושרות מהמטמון, וזה מתברר כשאלה על מי הבעלים שלהן
מי משחרר את מפת הסיביות
צורת הפונקציה של RenderPage מחזירה TBitmap שהקורא הוא הבעלים שלו. בייצוא חד-פעמי אותה בעלות ברורה וקלה לכבד. בתוך מטמון היא הופכת לדליפה הנפוצה ביותר במציגי PDF ב-Delphi, מפני שהמילון מחזיק כעת את ההפניה היחידה לכל מפת סיביות, ו-TDictionary פשוט משחרר מפתחות וערכים עבורכם רק אם הם טיפוסים מנוהלים. TBitmap אינו. פנו רשומה מבלי לקרוא ל-Free והפיקסלים נשארים מוקצים כשדבר אינו מצביע עליהם
הסיבה שזה מחליק מבעד היא תזמון. מבחן עשן של עשר דקות לעולם אינו זומם מספיק דפים מובחנים כדי להבחין; הדליפה מראה את עצמה רק אחרי שמישהו גלל וזימם מסמך ארוך כמה שעות, ובאותה נקודה התהליך מחזיק מאות מפות סיביות דף יתומות והמכונה מתחילה לדפדף. לכן הפינוי שייך לגרסה הראשונה של המטמון, לא למאוחרת יותר. הגבילו את המטמון לפי בייטים מוערכים, המחושבים כרוחב כפול גובה כפול ארבע, פנו את הדפים שנעשה בהם שימוש לפני זמן רב ביותר היושבים מחוץ לאזור התצוגה וחלון הטעינה-מראש, ושחררו כל מפת סיביות כשאתם מסירים אותה. עבור ציורים זמניים באמת, ההעמסות המרנדרות לתוך TBitmap שסיפק הקורא או ישר אל HDC מאפשרות לכם לדלג על ריקוד הבעלות לחלוטין. תצוגה מקדימה להדפסה היא המקרה הברור, מכיוון שאתם מרנדרים כל גיליון פעם אחת ושמירתו במטמון אינה קונה דבר
רינדור הדרגתי וביטול ישר
העמסות ה-RenderPage הפשוטות חוסמות עד שהדף מסתיים, וזו בדיוק ההתנהגות שאינכם רוצים בעוד המשתמש עדיין מזיז את פקד הזום. לשם כך אתם פונים אל RenderPageProgressive. הוא לוקח IPdfCancellationToken ומחזיר אחד מ-prsDone, prsCancelled או prsFailed. הפרט ההתנהגותי שתופס אנשים לא מוכנים הוא שהביטול אינו מיידי. האסימון נדגם בגבולות חלקים בתוך הרינדור, ולכן אסימון שאתם מסמנים באמצע חלק נכנס לתוקף רק כשאותו חלק מסתיים. בדף מורכב ההשהיה בין הבקשה לעצירה מגיעה לעשרות אלפיות שנייה. תכננו סביב הפער הזה במקום לקוות שייעלם: בטלו את האסימון הקודם ברגע שערך זום חדש מגיע, אך אל תניחו שהרינדור הישן נעצר ברגע שאתם מבקשים
procedure TViewerForm.RequestRender(TargetZoom: Single);
var
Status: TPdfProgressiveStatus;
begin
if FTokenSource <> nil then
FTokenSource.Cancel; // abandon the previous in-flight render
FTokenSource := TPdfCancellationTokenSource.New; // FPdfAsync unit
Status := Pdf.RenderPageProgressive(FBackBuffer, 0, 0,
FBackBuffer.Width, FBackBuffer.Height, FTokenSource.Token,
ro0, [reAnnotations]);
case Status of
prsDone: PresentBackBuffer;
prsCancelled: ; // superseded by a newer request: drop silently
prsFailed: ShowRenderFailure;
end;
end;
במהלך אינטראקציה, prsCancelled הוא התוצאה הרגילה, לא החריגה. רוב הרינדורים שמחוות זום מתחילה יוחלפו לפני שיסתיימו, ולכן התייחסו לביטול כשגרתי והשמיטו את התוצאה בשקט. תור רינדור שרושם כל ביטול כאזהרה יקבור את הכשל היחיד שבאמת חשוב תחת אלפי שורות של רעש. כדי לשמור על המסך מלהיראות מת בעוד הרינדור האמיתי רץ, צמדו את המסלול ההדרגתי עם תחליף זול: שנו קנה-מידה של מפת הסיביות הקודמת שבמטמון לזום החדש והציגו אותה מיידית. היא נראית רכה למאה אלפיות שנייה או שתיים, אך היא נקראת כמיידית, והיא קונה לרינדור באיכות מלאה את הזמן שהוא צריך כדי להסתיים או להתבטל על-ידי המחווה הבאה
מצב ההתאמה שזום מכבה בשקט
מאפיין ה-FitMode של מציג, המוגדר ל-pfmFitPage או ל-pfmFitWidth, מחשב מחדש זום בכל שינוי גודל כך שהדף ממשיך להתאים ככל שהחלון משתנה. המלכוד הוא שהקצאת Zoom ישירות מאפסת את FitMode בחזרה ל-pfmNone. כברירת מחדל זה נכון: משתמש שהקליד בכוונה 150% אינו רוצה ששינוי הגודל הבא של החלון יזרוק זאת. אך זה מפתיע כל מי שמחווט כפתור הגדל-זום כ-Zoom := Zoom * 1.25 ואז אינו יכול להבין מדוע התאמה-לרוחב הפסיקה להגיב אחרי הלחיצה הראשונה. אם סרגל הכלים שלכם מציע גם זום מפורש וגם מצבי התאמה, עליכם לזכור את בחירת ההתאמה האחרונה של המשתמש בעצמכם ולהקצותה מחדש כשהם לוחצים על כפתור ההתאמה שוב. הרכיב לא ישחזר מצב שהקצאת זום זה עתה ניקתה, והוא לא אמור
תקציב זיכרון שתוכלו להגן עליו
תקציב שתוכלו לרשום הוא תקציב שתוכלו לטעון לטובתו בסקירת קוד, אז התחילו מתרחיש קונקרטי. נניח שגלילה רציפה שומרת את הדף הנראה ועוד דף טעון-מראש מעל ומתחת, לצד רצועת תמונות ממוזערות. ב-100% בתצוגת 96-DPI שלוש מפות הסיביות בגודל-מלא האלה מגיעות לכ-3.5 MB כל אחת, וזה כלום. ב-300% בתצוגת 4K אותן שלוש מפות סיביות הן בערך 30 MB כל אחת, וזה לפני שהמטמון שמר דף היסטורי אחד. הגידול הוא במחווה, לא במסמך
ברירת מחדל בריאה לתהליך 32-סיביות ב-Delphi היא תקציב מפת סיביות של 256 MB תחת פינוי LRU. ב-64-סיביות תוכלו להתאים לפי RAM פיזי, אך שמרו על תקרה קשיחה ללא קשר, מפני שהכשל שמפניו אתם מגנים אינו קריסת התהליך שלכם. זהו כל המכונה המדפדפת בקובץ הדף שלה בעוד המציג שלכם טכנית ממשיך לרוץ והמשתמש תוהה מדוע כל השאר האט. תקרה קשיחה נכשלת בצורה צפויה; מטמון לא-חסום נכשל על-ידי לקיחת שולחן העבודה איתו. תמונות ממוזערות ראויות לטיפול משלהן: רנדרו כל אחת פעם אחת בגודל היעד הקטן שלה והחזיקו אותה במאגר נפרד שהיגיון ה-LRU לעולם אינו נוגע בו. הפקה מחדש של תמונה ממוזערת בת 120 פיקסלים על-ידי הקטנת מפת סיביות דף-מלא של 60 MB היא הדרך הבזבזנית ביותר האפשרית להפיק בול דואר
כמה דפים יחידים מביסים כל תקציב. שרטוט הנדסה בגודל E או מפה גדולה שעברו רינדור שלם ב-400% הם הקצאה של מאות מגה-בייטים, ושום מדיניות פינוי אינה הופכת זאת למקובל. התשובה שם היא להפסיק לרנדר דפים שלמים. RenderTile עושה רסטור רק לאזור בהיסט פיקסל (Left, Top) בתוך דף שסולם רעיונית ל-PageWidth על PageHeight, ולכן אתם מרנדרים רק את המלבן הנראה בתוספת שולי ריצוף אחד סביבו לגלילה חלקה, ואתם מקפלים את היסטי הריצוף לתוך מפתח המטמון לצד הזום. שמרו על ממדי הריצוף קבועים לאורך המסמך. ריצופים קבועים פירושם ששינוי DPI מבטל את כל הרשת בנקיות, בעוד ריצופים משתנים משאירים אתכם רודפים אחר תפרים נראים בין אזורים שעברו רינדור בקני-מידה שונים מעט
שתי תכונות סמוכות מוסיפות בשקט לכל זה. מעברי מסנן-צבע כגון גווני-אפור או היפוך רצים אחרי הרינדור ומפיקים מפת סיביות שנייה בגודל-מלא בכל פעם, מכפילים את טביעת הרגל לכל-דף של כל תצוגה המשתמשת בהם; אותה עלות היא הנושא של סינון צבע לכבדי-ראייה במציגי PDF ב-Delphi. ומציג המדגיש מילים במהלך טקסט-לדיבור מבטל את התצוגה המרונדרת בכל מילה מדוברת, ולכן האינטראקציה בין ציורי-מחדש של הדגשה לבין קצב הדיבור חשובה יותר משהיא נראית בתחילה, כמכוסה בהדגשת TTS מילה-במילה
העמסות הרינדור, קודי הסטטוס ההדרגתי ורכיב המציג עצמו מתועדים בדף המוצר של PDFium Component