אתה קורא ל-AddText כדי להטביע שורה על עמוד PDF עם PDFiumPas, ואז מיד קורא ל-FindFirst כדי לאשר שההטבעה נחתה, והחיפוש חוזר ריק. הטקסט על העמוד — Acrobat מציג אותו — אבל רכיב ה-TPdf של PDFiumPas שומר מבנה FPDF_TEXTPAGE נפרד במטמון, מפוענח פעם אחת מזרם התוכן של העמוד, ועריכה לא מעדכנת רטרואקטיבית את המבנה ההוא בעצמה. שאל אותו לפני שהוא רוענן ואתה קורא את העמוד בדיוק כפי שהוא נראה לפני השינוי שלך, לא אחריו
למה PDFium מחזירה טקסט מיושן מיד אחרי עריכה?
PDFiumPas עוטפת את מנוע העיבוד PDFium של Google עבור Delphi ו-C++Builder, וקריאות הטקסט והעריכה שלה מגיעות לשתי תת-מערכות שונות בתוך המנוע ההוא. FPDF_TEXTPAGE שייך לצד הקריאה: FPDFText_LoadPage עוברת על זרם התוכן של העמוד פעם אחת ובונה את עמוד-הטקסט — קודי תו, מיקומים, מדדי גופן, גבולות מילים — ו-PDFiumPas שומרת את המבנה ההוא במטמון כל עוד העמוד נשאר טעון. קריאות עריכה כמו FPDFPage_InsertObject או FPDFPage_GenerateContent פועלות על ייצוג שונה לחלוטין, גרף האובייקטים וזרם-התוכן של העמוד, ו-PDFium לא דוחפת את השינויים האלה לתוך עמוד-טקסט כבר-פתוח בעצמה. בנייתו מחדש בכל עריכה הייתה הופכת עריכת-אצווה לאיטית בלתי-מתקבלת על הדעת, כך שהעיצוב מחליף את העלות ההיא בכלל במקום זאת — מי שמחזיק את הידית סוגר אותה אחרי עריכה משנת-תוכן, והקריאה הבאה בונה אחת טרייה
בתוך מטמון הטקסט של TPdf: FTextPage, LoadTextPage, ו-UnloadTextPage
TPdf עוקבת אחר הידית במטמון בשדה פרטי בודד, FTextPage, ועוטפת את מחזור-החיים שלה בשתי פונקציות. LoadTextPage בודקת אם FTextPage הוא nil ורק במקרה ההוא קוראת ל-FPDFText_LoadPage מול העמוד הנוכחי; אם ידית כבר קיימת, LoadTextPage עושה בה שימוש חוזר בלי לשאול אם העמוד השתנה מאז שהיא נבנתה. UnloadTextPage הוא החצי השני: הוא סוגר את הידית הילידית עם FPDFText_ClosePage, מגדיר את FTextPage בחזרה ל-nil, וגם מפיל את רשימת הקישורים-המקושרים במטמון וכל סשן חיפוש בתהליך, שכן שניהם נגזרו מאותו עמוד-טקסט ומתיישנים מאותה סיבה
התנהגות השימוש-החוזר-בלי-לבדוק של LoadTextPage היא בדיוק הסיבה שהסידור חשוב. כל שאילתת טקסט ב-TPdf — Text, FindFirst, GetWebLinks — מוזרמת דרך LoadTextPage קודם, כך שכל עוד FTextPage עדיין מחזיקה את הידית מלפני-העריכה, לאף אחת מהקריאות האלה אין דרך לדעת ששינוי קרה. ניווט עמוד אף פעם לא היה הסיכון כאן: UnloadPage, שרצה במעברי עמוד, טעינות מחדש, וסגירת מסמך, תמיד סגרה את עמוד-הטקסט יחד עם העמוד עצמו. השאלה הפתוחה תמיד הייתה לגבי עריכות שיושמו על העמוד שאתה עדיין יושב עליו
אילו פונקציות PDFiumPas מרעננות את המטמון אוטומטית?
פונקציות עריכת-העמוד של TPdf עצמה — AddText, SetText, SetTextPositions, AddPath, RemoveObject, ו-InsertFormObjectFromXObject — כל אחת קוראת ל-UnloadTextPage לפני שהיא קוראת ל-UpdatePage (ה-FPDFPage_GenerateContent של PDFium) כדי לסדר את השינוי לתוך זרם התוכן. קרא לאחת מהן ואותה קריאת Text, FindFirst, או GetWebLinks הבאה בונה מחדש את עמוד-הטקסט מהתוכן כפי שהוא כרגע עומד, ללא קריאה נוספת נדרשת מצדך
var
Pdf: TPdf;
Index: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'invoice.pdf';
Pdf.Active := True;
Pdf.PageNumber := 1;
Pdf.AddText('Reviewed by J. Alvarez', 'Helvetica', 10, 72, 40, clBlack, 255, 0);
// AddText already closed the cached text page, so this FindFirst
// call rebuilds it fresh before it searches
Index := Pdf.FindFirst('Reviewed by J. Alvarez');
if Index >= 0 then
ShowMessage('Stamp confirmed at character ' + IntToStr(Index));
finally
Pdf.Free;
end;
end;
התבנית שעדיין נשברת: שמירת ידית ה-TextPage הגולמית במטמון
TPdf חושפת את הידית החיה דרך מאפיין TextPage לקריאה-בלבד, עבור המקרה הנדיר שבו אתה זקוק לקרוא לפונקציית FPDFText_* ש-PDFiumPas לא עטפה. פתח-המילוט הזה הוא גם המקום היחיד שהביטול-האוטומטי לא יכול לעזור בו: ברגע שאתה מעתיק את ערך ה-FPDF_TEXTPAGE החוצה מהמאפיין לתוך משתנה מקומי, ל-PDFiumPas אין דרך לדעת שאתה עדיין מחזיק אותו, ואין דרך לעדכן את ההעתק שלך כאשר UnloadTextPage רצה במקום אחר בקוד שלך
var
Pdf: TPdf;
RawHandle: FPDF_TEXTPAGE;
StaleCount: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'contract.pdf';
Pdf.Active := True;
Pdf.PageNumber := 1;
RawHandle := Pdf.TextPage; // FPDFText_LoadPage handle, cached in FTextPage
Pdf.SetText(0, 'Amended Clause 4.2');
// SetText already closed RawHandle and set Pdf.TextPage back to nil.
// Calling any FPDFText_* function against the old value now touches a
// handle PDFium has already freed — undefined behavior, not a bug you
// can catch with a nil check
StaleCount := FPDFText_CountChars(RawHandle);
finally
Pdf.Free;
end;
end;
שימוש בידית אחרי ש-FPDFText_ClosePage רצה עליה הוא התנהגות בלתי-מוגדרת ב-PDFium עצמה, לא מוסכמת PDFiumPas שאתה יכול לבחור להתעלם ממנה — היא עשויה להחזיר את הנתונים הידועים-לאחרונה, להחזיר כלום, או להקריס את התהליך, ומה מהם קורה בבנייה נתונה אינו משהו שקוד אפליקציה צריך להישען עליו. הכלל הבטוח צר: קרא Pdf.TextPage טרי, מיד לפני קריאת FPDFText_* שזקוקה לו, ואף פעם אל תחזיק העתק על פני הוראה שעשויה לערוך את העמוד
בצע אצוות של העריכות שלך, ואז שאל פעם אחת
שום דבר מזה לא אומר שכל קריאת AddText או RemoveObject זקוקה לשאילתת-טקסט הגנתית מיד אחריה כדי לבדוק את התוצאה. כל פונקציית עריכה כבר משלמת את העלות של סגירת עמוד-הטקסט פעם אחת; שאילתה אחרי כל עריכה בודדת בתוך לולאה משלמת את העלות הזו שוב ללא תועלת, שכן FPDFText_LoadPage עוברת מחדש על כל זרם התוכן בכל פעם שהיא רצה
var
Pdf: TPdf;
I: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'watermarked.pdf';
Pdf.Active := True;
Pdf.PageNumber := 1;
// Strip every text object that looks like a draft watermark. Each
// RemoveObject call already invalidates the cache on its own, so
// nothing needs refreshing by hand between iterations
for I := Pdf.ObjectCount - 1 downto 0 do
if (Pdf.ObjectType[I] = otText) and (Pdf.ObjectBounds[I].Top > 700) then
Pdf.RemoveObject(I, True);
// Query once, after the whole batch is done, not once per removal
if Pdf.FindFirst('DRAFT') < 0 then
ShowMessage('Watermark cleared');
finally
Pdf.Free;
end;
end;
אותה לוגיקת-אצווה חלה על מצב-חיפוש ספציפית. FindNext ו-FindPrevious ממשיכות סשן שהתחיל על ידי FindFirst, והסשן ההוא נהרס על ידי UnloadTextPage יחד עם כל השאר, כך שקריאה ל-FindNext שוב אחרי עריכה — במקום קריאה ל-FindFirst שוב — מעלה חריגה במקום להמשיך בשקט חיפוש מול תוכן שכבר לא קיים. התייחס לכל עריכה כגבול קשיח הן עבור תוכן-הטקסט והן עבור מיקום-החיפוש, ותן ל-FindFirst טרי בודד בצד השני של העריכות שלך לחדש את החיפוש
איפה זה משתלב עם עבודת חילוץ והערות
חילוץ טקסט פשוט — קריאת הטקסט של עמוד בלי לשנות כלום — אף פעם לא נתקל בכלום מזה, משום ששום דבר לא מבטל ידית שאף עריכה לא נגעה בה. לגבי איך Text, מלבני תווים, וגבולות מילים עובדים על עמוד בלתי-משונה, המאמר הנלווה על חילוץ טקסט עם PDFiumPas מכסה את הקרקע הזו בלי מחזור-החיים של מטמון עמוד-הטקסט שהמאמר הזה מוסיף מעל
מחזור-החיים של המטמון הכי חשוב בזרימות עבודה שעורכות ואז מיד פועלות על התוצאה: הטבעת תיקון וחיפוש אחריו, מחיקה-מוחלטת (redact) של פסקה ואישור שהיא נעלמה, או איתור ביטוי כדי לעגן הערת סימון מיד אחרי הכנסת טקסט לידו. המקרה האחרון ההוא שווה לדגל בעצמו — הערות סימון מבוססות quad-point ממוקמות ממלבני תווים שנקראים מעמוד-הטקסט, כך שהערה שנבנתה מקואורדינטות שנלכדו לפני עריכה מסתיימת בהדגשת המקום הלא-נכון ברגע שהעריכה נוחתת
ה-API-ים של עריכה וטקסט של TPdf הם חלק מרכיב PDFium עבור Delphi ו-C++Builder, ודף המוצר נושא את מסמך העזר המלא לפונקציות עבור משטחי העריכה, החילוץ, והחיפוש המכוסים כאן