הערת PDF היא מילון המחובר לדף, לא סימון המצויר עליו. ISO 32000-1 §12.5 מגדיר כעשרים וכמה תתי-סוגים, וכל אחד נושא /Subtype, מלבן בקואורדינטות הדף, קבוצת דגלים ובדרך כלל זרם מראה שמחליט מה המציג אכן מצייר. תתי-הסוגים לא כולם אומרים אותו דבר לאדם שסוקר מסמך. Highlight ו-Ink stroke הם הערות; Link הוא ניווט; Popup הוא החלון הקטן שנפתח כשלוחצים על פתק, מאוחסן כאובייקט בפני עצמו ומוצבע על ידי הורה. תשובות הן הערות Text מלאות המפנות להערה שהן עונות עליה דרך ערך in-reply-to. כך שמערך ההערות ברמת הדף אינו רשימת ההערות של הסוקר. זה שקית שטוחה המכילה הערות, את האינסטלציה שמחברת אותן, ומספר דברים שאף סוקר לא יכנה הערה. פאנל שמתייחס למערך כרשימת ההערות יחלוק עם כל מציג אחר שהלקוח מריץ
בניית זרימת עבודה לסקירת הערות על PDFium Component, רכיב ה-VCL/LCL מבוסס PDFium עבור Delphi, C++Builder ו-Lazarus, משמעה התמקדות בנקודות שבהן הפער בין המערך הגולמי לתצוגה האנושית גורם לצרות: ספירה, אינדקסים, צביעה מחדש של סימונים שהמנוע כבר הקפיא, מחיקה ללא שיירים והוספת סימונים משלך
מדוע הספירה שלך לעולם לא מתאימה לחלונית ההערות של Acrobat
פתח חוזה עם סימונים במציג שלך וב-Acrobat זה לצד זה והסיכומים לעיתים נדירות מסכימים. Acrobat מציג תצוגה מאורגנת: סימונים מקובצים לשרשורי תשובות, חלונות קופצים מקופלים לתוך הפתקים שהם שייכים להם, קישורים ווידג'טים של טפסים מושמטים. המערך הגולמי מחזיק את הכל ללא הבחנה, כך שספירה תמימה גבוהה בדרכים מסוימות ונמוכה באחרות בו-זמנית
חלונות קופצים מנפחים את הסך, כי כל פתק מגיע עם אובייקט Popup נפרד וספירת שניהם מכפילה את הפתק. תשובות מנמיכות אותו אם מסננים על סימונים גלויים, כי תשובה היא הערת Text שאינה מצוירת עד שמישהו מרחיב את השרשור, ואי הכללתה מאבדת את הדיון. הדגלים Hidden ו-NoView מסירים הערה מהמסך מבלי להסיר אותה מהמערך, כך שספירה עיוורת לדגל כוללת סימונים שהמשתמש אינו יכול לראות. הערות Link יושבות באותו מערך כמו ההערות ואינן שייכות לא לספירה ולא לרשימה. קבע את כלל הספירה לפני שתכתוב את הלולאה, וכתוב את ההחלטה, כי "מדוע הפאנל שלך מציג מספר שונה מ-Acrobat" הוא הכרטיס הראשון שתכונת סקירה מרוויחה
אנדקס הכל פעם אחת, ואל תפרסר מחדש דף
כלל עיצוב אחד מניע את כל מה שבא אחריו: סינון לפי מחבר, סוג או דף לא חייב לפרסר מחדש אובייקטי דף. במסמך עם 300 דפים וסימונים כבדים, פרסור מחדש בכל שינוי ברשימה הנפתחת הופך את הפאנל למשהו שמגמגם במשך שניות. הרכיב חושף את AnnotationCount ואת מאפיין Annotation[] המאונדקס, שניהם מוגדרים לדף הנטען כרגע, והרשומה TPdfAnnotation שהם מחזירים נושאת את מה שתצוגת רשימה צריכה: Subtype, Flags, Color, Rectangle, ContentsText, AuthorText. הצעד הנכון הוא לסרוק כל דף פעם אחת בזמן הפתיחה ולשמור אינדקס שטוח משלך:
procedure TReviewPanel.BuildIndex;
var
PageNo, i: Integer;
A: TPdfAnnotation;
begin
FItems.Clear;
for PageNo := 1 to Pdf.PageCount do
begin
Pdf.PageNumber := PageNo;
for i := 0 to Pdf.AnnotationCount - 1 do
begin
A := Pdf.Annotation[i];
// Keep reviewer-relevant subtypes only; record the page and
// index pair because all later edits are addressed by it
if A.Subtype in [anText, anHighlight, anInk] then
FItems.Add(TReviewItem.Create(PageNo, i,
A.AuthorText, A.ContentsText, A.Rectangle, A.Color));
end;
end;
end;
הזוג שראוי להדגיש הוא (PageNo, i). כל מוטציה מאוחרת, בין אם צביעה מחדש ובין אם מחיקה, מטופלת לפי מספר דף בתוספת אינדקס הערה, והאינדקס שביר: הסרת הערה ממספרת מחדש את כל מה שבא אחריה בדף. לכן תכנן לבנות מחדש את הערכים של הדף המושפע לאחר כל מחיקה במקום לתקן מספרי אינדקס במקום. הבנייה מחדש עולה מילישנייה. אינדקס מיושן, לעומת זאת, מוחק את ההערה של הסוקר הלא נכון, וזה סוג הבאג שמרסק את האמון בכל התכונה
שרשורים ראויים לחריץ באינדקס אפילו אם הגרסה הראשונה שלך רק סופרת תשובות במקום להציג אותן. קבץ פריטים לפי הפניית ההורה שלהם בזמן שהדף פתוח, כדי שהפאנל יוכל מאוחר יותר לקפל שרשור כמו ש-Acrobat עושה. שחזור הקיבוץ הזה באופן עצלן במהלך גלילה מבטל את כל הנקודה של אינדקוס פעם אחת, כי הוא פותח מחדש דפים שכבר שילמת כדי לפרסר. גאומטריה רוצה את אותה משמעת. ה-Rectangle בכל רשומה הוא מרחב-הדף, והמרה לקואורדינטות תצוגה שייכת לעוזר משותף אחד, לא מפוזרת בקוד. פאנלים צוברים באגי קואורדינטות כשבחירה, בדיקת פגיעה וציור כל אחד ממציאים את מתמטיקת הזום והסיבוב שלהם; נתב את שלושתם דרך המרה אחת ואז הדגשה, שורתה ברשימה ויעד הלחיצה שלה נשארים מקובעים לאותה דיו
צביעת סימונים מחדש ווטו זרם המראה
שינוי הדגשה מצהוב לענבר נשמע כשורה בודדת, ולפעמים זה כך. המלכוד הוא ISO 32000-1 §12.5.5. כשהערה נושאת זרם מראה /AP, מציג תקני מצייר את הזרם המובנה הזה ומתייחס לערך הצבע במילון כמטא-נתונים מת. Acrobat כותב זרמי מראה כמעט לכל מה שהוא יוצר, כך שרוב ההערות המגיעות מלקוחות כבר במצב זה, והצבע שהגדרת בביטחון לעולם לא מגיע למסך. צביעה מחדש היא קרא-שנה-כתוב דרך מאפיין Annotation[], והרכיב כן לגבי הסתירה: כשהמנוע מסרב לאפשר לצבע מילון לעקוף מראה אפויה, הכתיבה מעלה EPdfError
A := Pdf.Annotation[Item.Index];
A.HasColor := True;
A.Color := $0000B0FF; // amber
A.ColorAlpha := 160;
try
Pdf.Annotation[Item.Index] := A;
except
on EPdfError do
begin
// The annotation owns a pre-rendered /AP stream; the dictionary
// color alone cannot change what viewers paint
Item.AppearanceLocked := True;
StatusBar.SimpleText := 'Color is fixed by the annotation appearance';
end;
end;
תפוס את החריגה הזו בכל פעם, והתייחס אליה כמידע ולא ככישלון. דלג על השמירה והפאנל שלך יציג בשמחה ענבר ברשימה שלו בזמן שהדף ממשיך לצייר צהוב; המשתמש יגיש תלונה שבועות מאוחר יותר בתור "המציג שלך מתעלם מהעריכות שלי", ותבלה צהריים בניסיון שנכשל לשחזר זאת על קובץ שקורה שאין לו זרם מראה. ברגע שאתה יודע שהמראה נעולה, יש לך שתי תגובות כנות: צבע את שכבת-העל של הבחירה שלך במקום ההערה, כדי שלפחות הסוקר יראה את ההדגשה שהוא בחר, או סמן את השורה כנעולת מראה כדי שאף אחד לא יצפה שהשינוי ישמר
מחיקת הערות ללא שיירים
DeleteAnnotation מסיר את האובייקט מעץ ההערות של הדף הנוכחי, אך משאיר את ה-raster השמור של הדף לבד. צייר מיד לאחר הקריאה וההדגשה שנמחקה עדיין על המסך, יושבת בביטמאפ שכבר אינו מתאים למודל המסמך מאחוריו. התיקון הוא לטפל בעיבוד-מחדש כחלק מהמחיקה, לא שלב שהקורא עשוי לשכוח:
Pdf.PageNumber := Item.PageNo;
Pdf.DeleteAnnotation(Item.Index); // raises EPdfError on failure
Bmp := Pdf.RenderPage(0, 0, ViewWidth, ViewHeight, ro0, [reAnnotations]);
try
PaintPageBitmap(Bmp);
finally
Bmp.Free; // RenderPage hands bitmap ownership to the caller
end;
RebuildPageEntries(Item.PageNo); // indices after Item.Index shifted
שני פרטים בבלוק הזה קל לטעות בהם. האפשרות reAnnotations חייבת להיות קיימת, אחרת ה-raster החדש מפיל כל הערה שנותרת והדף נראה כאילו מחקת את כל קבוצת ההערות במקום סימון אחד. וה-Bmp.Free אינו אופציונלי: עומס-היתר RenderPage בסגנון פונקציה מעביר בעלות ביטמאפ לקורא, כך שחסרון בשחרור דולף raster של דף שלם בכל מחיקה בודדת, מה שסוקר שעובד על מסמך ארוך יהפוך ללחץ זיכרון אמיתי תוך דקות
הוספת סימוני סוקר מממשק המשתמש שלך
יצירת הערות עוברת דרך CreateAnnotation, אשר מקבלת רשומה TPdfAnnotation מלאה (תת-סוג, מלבן, צבע, תוכן, מחבר) ומצרפת אותה לדף הנוכחי. פתק, תת-סוג anText, הוא המקרה הקל: הגדר את המיקום, התוכן והמחבר וסיימת. הערות Ink הן המקום שבו אנשים נתקעים. המלבן של הרשומה רק מגדיר גבולות לציור; השבצים עצמם הם מערכי נקודות שצריך לצרף בנפרד דרך קריאת שבץ-ink של המנוע, FPDFAnnot_AddInkStroke שמוזן בנתוני FS_POINTF, שנלכד מקלט עכבר או עט שבץ אחר שבץ. בנה הערת ink ממלבן בלבד ותקבל שרבוט ריק שמתעבד כמרחב ריק, מה שנראה כבאג במנוע ובאמת הוא הערה חצי-גמורה
הסדר את מדיניות הייחוס באותה נשימה. כל סימון שממשק המשתמש שלך יוצר אמור לשאת AuthorText עקבי, כי מסנן הסוקר שתבנה בחודש הבא טוב רק כמו השמות שאתה מטביע על ההערות היום. מחרוזות מחבר ריקות או לא עקביות לא ניתן לתקן בדיעבד מבלי לפתוח מחדש כל קובץ
הוצאת הסקירה מהמציג
נתוני סקירה מרוויחים כשהם יכולים לעזוב את המציג, כסיכום שמנהל הפרויקט קורא מבלי לפתוח את הקובץ או CSV שמזין גיליון מעקב. ייצא מהאינדקס שכבר בנית, לא מפרסור טרי, ובחר דרך יציבה להפנות בחזרה לכל סימון. מספר דף מזווג עם מלבן ההערה שורד מחזורים שאינדקס מערך אינו יכול, כי המחיקה הבאה מספרת מחדש בשקט את האינדקסים וה-CSV שלך מתחיל להצביע על ההערות הלא נכונות
שורה שכדאי לשמור נושאת את הדף, תת-הסוג, המחבר, חותמת הזמן של היצירה כשהקובץ מתעד אחת, טקסט התוכן ועמודת סטטוס שאתה הבעלים שלה ולא כזו שה-PDF מספק. אותו מעבר אינדוקס שימושי קודם לכן, במהלך קבלה, כשמסמך מגיע מחוץ לצוות ואתה רוצה לדעת מה בתוכו לפני שמישהו סוקר אותו. מאמר ה-workbench לקבלת PDF עובר על הטריאז' הזה, וניווט שדות טופס מכסה את הבעיה הספקולרית: סקירת מסמכים שנבנו לאסוף נתונים ולא הערות
מקרה אחד שהמערך לא יראה לך
מצב כישלון אחד ראוי לדגל כי הוא נראה כפגם בקוד שלך ואינו. לקוח מדווח על הדגשות גלויות בכל הדף, אך הפאנל שלך לא מציג דבר, ו-AnnotationCount מחזיר אפס. ההסבר הרגיל הוא שהסימונים שוטחו במקום כלשהו במעלה הזרם. שיטוח אופה את מראות ההערות לתוך תוכן דף רגיל, כך שההדגשות הופכות לחלק מגרפיקת הדף ומפסיקות להתקיים כאובייקטי הערות כלל. לא נותר שום דבר שממשק API של הערות יוכל למנות, לצבוע מחדש או למחוק. כשאתה רואה סימונים מצוירים עם ספירה אפסית, הפסק לחפש את הבאג בלולאת המניה שלך ושאל איך הקובץ הופק
משטח ההערות המשמש כאן, ממניה ויצירה דרך צביעה מחדש, מחיקה ואפשרויות העיבוד ששומרות על התצוגה כנה, מגיע עם PDFium Component עבור Delphi, C++Builder ו-Lazarus/FPC