PDFium Component שולט בשכבות optional content של PDF (OCGs) ב-Delphi דרך שתי מתודות של TPdf: InspectOptionalContent מפרט כל שכבה יחד עם הנראות ש-PDFium באמת ירנדר, ו-SaveAsOptionalContentConfigured כותב עותק מאומת שבו השכבות שתבחרו מופעלות או מכובות. המתודה השנייה גם מנטרלת את כללי ה-Usage וה-/AS שאחרת היו מבטלים בשקט את העריכה שלכם. שתיהן עובדות על המסמך שכבר פתוח ב-TPdf, כך שאין פרסר שני לתחזק בסנכרון עם מה שה-viewer מציג
הבקשה מגיעה בדרך כלל ממשרד CAD או GIS: מערך השרטוטים נשלח עם מידות, אנוטציות ולוח שער בשכבות נפרדות, והלקוח רוצה עותק שבו המידות מוסתרות לפני שהוא יוצא לספק. PDFium מרנדר optional content נכון, אבל ל-ABI הציבורי שלו אין פונקציה למנות OCGs, לבחור קונפיגורציה או להחליף מצב שכבה. אז צוללים לרמת האובייקטים, עורכים את /OCProperties, שומרים, טוענים מחדש, והשכבה עדיין שם. הסיבה היא הלוגיקה של PDFium לנראות, ושווה להבין אותה לפני שנוגעים בבייט כלשהו
למה עריכת /ON ו-/OFF לא משנה את מה ש-PDFium מרנדר?
עריכת מערכי ה-/ON וה-/OFF של מילון הקונפיגורציה אינה מספקת, כי PDFium נותן למצב מפורש בתוך מילון ה-/Usage של ה-OCG עצמו לנצח את המערכים האלה, וכלל auto-state של /AS יכול אז לדרוס את שניהם. ISO 32000-1 §8.11.4 מתאר קונפיגורציות ומילוני usage בתור שני מנגנונים נפרדים; הרנדרר של PDFium מקפל אותם להחלטה אחת, ו-InspectOptionalContent משחזר אותה בסדר הזה:
- מתחילים מה-
/BaseStateשל הקונפיגורציה, שבו/ONו-/Unchangedשניהם נחשבים נראים ורק/OFFמסתיר - מיישמים את מערך ה-
/ONשל הקונפיגורציה, ואז את מערך ה-/OFFשלה, כך שקבוצה שמופיעה בשניהם מסתיימת מוסתרת - מיישמים את מצב ה-Usage המפורש של הקבוצה עבור ה-usage המבוקש, כמו
/Usage << /View << /ViewState /OFF >> >>, שדורס את כל מה שמעליו - רואים קבוצה שה-
/Intentשלה מכיל לא/Viewולא/Allכנראית, מכיוון שהיא אינה לוקחת חלק בנראות לפי view-intent - לבסוף מריצים את מערך ה-
/ASשל הקונפיגורציה הנבחרת, שהכניסות שלו עבור האירוע המתאים קובעות את המצב של הקבוצות שהן מפרטות
הצעד השלישי הוא זה ששורף אנשים. קובץ שנשמר על ידי כלי פריסה נושא לעתים קרובות /ViewState /ON על כל OCG, ו-PDFium אז מתעלם ממערך ה-/OFF שערכתם בקפידה: השמירה מצליחה, הקובץ נפתח מחדש בצורה נקייה, והשכבה עדיין מצוירת. עבור Print ו-Export, OcExplicitUsageState קורא קודם PrintState או ExportState וחוזר ל-ViewState כשהכניסה הספציפית נעדרת, כך ש-ViewState /ON בודד נועל את השכבה גם להדפסה. תוכן מסומן שמפנה ל-OCMD (§8.11.2.2) נפתר אז מול התוצאות האלה לכל קבוצה, דרך מדיניות ה-/P או, כשקיים, ביטוי הנראות /VE
איך מפרטים את השכבות ש-PDFium באמת יציג?
TPdf.InspectOptionalContent מחזיר TPdfOptionalContentInventory שהמערך Groups שלו נושא לכל OCG את מספר האובייקט, השם, ה-intents, שלושת מצבי ה-Usage, השפה, טווח ה-zoom, דגל Locked, אינדקס קבוצת ה-radio ואת ה-EffectiveVisible המחושב. המתודה קודם גורמת ל-PDFium לשמור את המסמך הנוכחי בזיכרון, מרחיבה object streams, וסורקת את התוצאה, כך שעריכות מוקדמות בסשן משתקפות. אינדקס קונפיגורציה 0 הוא תמיד מילון ה-/D המוגדר כברירת מחדל והכניסות של /Configs באות מאינדקס 1; הארגומנט ברירת המחדל -1 בוחר אינדקס 0. מסמך בלי /OCProperties גורם למתודה להחזיר False עם הסיבה ב-ErrorMessage במקום להעלות חריגה
procedure TFormMain.ListLayers;
var
Inv: TPdfOptionalContentInventory;
G: TPdfOptionalContentGroup;
begin
// Usage ברירת המחדל ocuView; -1 בוחר קונפיגורציה 0, המילון /D
if not Pdf.InspectOptionalContent(Inv) then
begin
Memo1.Lines.Add('No usable layers: ' + Inv.ErrorMessage);
Exit;
end;
Memo1.Lines.Add(Format('Configuration %d: %s',
[Inv.SelectedConfigurationIndex,
string(Inv.Configurations[Inv.SelectedConfigurationIndex].Name)]));
for G in Inv.Groups do
Memo1.Lines.Add(Format('obj %d %s visible=%s locked=%s radio=%d',
[G.ObjectNumber, string(G.Name),
BoolToStr(G.EffectiveVisible, True),
BoolToStr(G.Locked, True), G.RadioGroupIndex]));
end;
המערך Memberships מדווח על כל OCMD עם ה-Policy שלו (ocmpAnyOn, ocmpAllOn, ocmpAnyOff, ocmpAllOff), את טקסט ה-VisibilityExpression הגולמי ואת ה-EffectiveVisible שלו עצמו. כמה כללי קצה מכוונים. /P ברירת המחדל שלו /AnyOn, ו-OCMD ללא קבוצות נחשב נראה. הפניה למספר אובייקט שאינו OCG מוכר מתפרשת בתור נראית במקום להפיל את הביטוי כולו. הערכת /VE נעצרת בעומק קינון 32 ורואה כל דבר עמוק יותר כמוסתר, מה שמונע מביטוי עוין או מתייחס לעצמו להפוך בדיקה לגלישת מחסנית
כתיבת מצב שכבה חדש עם SaveAsOptionalContentConfigured
TPdf.SaveAsOptionalContentConfigured מקבל מערך של רשומות TPdfOptionalContentStateChange (מספר אובייקט של קבוצה בתוספת Visible) וכותב מסמך שבו הקונפיגורציה הנבחרת מניבה בדיוק את המצב הזה. הקונפיגורציה הנבחרת מקבלת /BaseState /ON בתוספת מערכי /ON ו-/OFF שלמים שמכסים כל קבוצה, וכל OCG שכבר מחזיק מילון Usage מקבל ViewState מפורש (או PrintState / ExportState, לפי Options.Usage) שתואם את המצב החדש שלו. עם TPdfOptionalContentConfigureOptions.Default, המפתח /AS של הקונפיגורציה הנבחרת מוסר כדי שאירוע פתיחה, הדפסה או ייצוא לא יוכל להחזיר את השכבות
procedure TFormMain.SaveWithoutDimensions(DimensionsObj, NotesObj: Integer);
var
Changes: TPdfOptionalContentStateChanges;
Options: TPdfOptionalContentConfigureOptions;
Report: TPdfOptionalContentConfigureReport;
begin
SetLength(Changes, 2);
Changes[0].GroupObjectNumber := DimensionsObj;
Changes[0].Visible := False;
Changes[1].GroupObjectNumber := NotesObj;
Changes[1].Visible := True;
// קונפיגורציה 0, ocuView, DisableAutomaticState ו-EnforceRadioGroups True
Options := TPdfOptionalContentConfigureOptions.Default;
if not Pdf.SaveAsOptionalContentConfigured('C:\Out\Drawing-NoDims.pdf',
Changes, Options, Report) then
raise Exception.Create('Layer update rejected: ' + Report.ErrorMessage);
Log(Format('%d of %d groups changed, %d Usage states rewritten, /AS removed: %s',
[Report.ChangedGroupCount, Report.GroupCount,
Report.UpdatedUsageStateCount,
BoolToStr(Report.RemovedAutomaticState, True)]));
end;
נתיב הכתיבה משמר את פלט השמירה של PDFium עצמו בתור קידומת בייט-אחר-בייט ומצרף רק את בעלי הקונפיגורציה שנכתב מחדש ואת אובייקטי ה-OCG שנושאים מילוני Usage, ואחריהם מקטע xref חדש ו-trailer. לפני שבייט אחד מגיע ליעד שלכם, התוצאה נפתחת מחדש ב-TPdf נפרד תחת מדיניות הטעינה הנוקשה, והמתודה נכשלת אם טבלת ההפניות הצולבות לא מאומתת. ה-overload לקובץ צועד צעד אחד נוסף: הוא כותב לקובץ זמני לצד היעד ומחליף את היעד רק אחרי שהאימות מצליח, כך שעדכון נדחה לעולם לא משאיר אחוריו שרטוט חצי-כתוב. זו אותה גישה של מהדורה מצטברת מאומתת שבה משתמש עורך עצי השמות והמספרים ב-PDFium Component
מה השמירה המוגדרת מסרבת לעשות?
השמירה המוגדרת מסרבת לכל שינוי שהמסמך עצמו אוסר או שאינו יכול לייצג בבטחה, וכל סירוב מתרחש לפני שהיעד נוגע. מספר אובייקט שאינו ב-/OCGs נכשל ממש. שינוי קבוצה שרשומה במערך ה-/Locked של הקונפיגורציה נכשל, אם כי ניסוח מחדש של ערכה הנוכחי מותר. עם EnforceRadioGroups דלוק, כל קבוצת /RBGroups שתסתיים עם יותר מחבר נראה אחד נדחית במקום לכבות בשקט את האחרים. מסמכים מוצפנים נדחים כי אובייקטים מצטברים בטקסט גלוי לא יכולים לשאת את מנהל האבטחה הפעיל. מסמכים חתומים מעלים EPdfError אלא אם מעבירים AllowSignedDocument = True, מכיוון ששינוי מה שעמוד מציג יכול לשבור כיסוי חתימה או מדיניות אישור
function TFormMain.SavePrintPreset(Target: TStream;
const Changes: TPdfOptionalContentStateChanges): Boolean;
var
Options: TPdfOptionalContentConfigureOptions;
Report: TPdfOptionalContentConfigureReport;
begin
Options := TPdfOptionalContentConfigureOptions.Default;
Options.Usage := ocuPrint; // כותב /Print << /PrintState ... >>
Options.ConfigurationIndex := 1; // הכניסה הראשונה של /Configs, לא /D
try
Result := Pdf.SaveAsOptionalContentConfigured(Target, Changes, Options,
Report); // AllowSignedDocument נשאר False
if not Result then
ShowMessage(Report.ErrorMessage);
except
on E: EPdfError do
begin
ShowMessage(E.Message); // קובץ חתום: לא נכתב דבר אל Target
Result := False;
end;
end;
end;
הכירו את הפשרות לפני שאתם מחברים את זה לעבודת אצווה. המהדורה המצורפת יושבת על גבי השמירה המלאה מחדש של PDFium, לא על בייטי הקובץ המקורי שלכם, וזה בדיוק הסיבה שקלט חתום זקוק להסכמה מפורשת. הכתיבה מחדש גם מנרמלת את הקונפיגורציה הנבחרת ל-/BaseState /ON, כך שקו בסיס /Unchanged או /OFF של היוצר מוחלף במערכים מפורשים עם אותה נראות מתקבלת. השמטת /AS מסירה טריקים להדפסה בלבד כמו שכבת watermark שמופיעה רק על נייר; הגדירו DisableAutomaticState ל-False כדי להשאיר את הכללים האלה, בהסכמה שהם עשויים לדרוס את המצב המבוקש שלכם עבור אותו אירוע. בצד החיובי, PDF/A-2 (ISO 19005-2 clause 6.9) ו-PDF/UA (ISO 14289-1 clause 7.10) שניהם אוסרים /AS במילוני קונפיגורציה, כך שהפלט המוגדר כברירת מחדל מסירה סוגיה אחת ש-בדיקת ה-preflight של PDF/A עם PDFium Component הייתה מדווחת אחרת
איפה שליטה בשכבות משתלבת ב-viewer של PDF ב-Delphi
ב-viewer, שליטה בשכבות היא רשימת סימון שמונעת על ידי המלאי בתוספת טעינה מחדש של התוצאה השמורה. ממלאים את רשימת הסימון מ-Groups, מנטרלים את הכניסות ש-Locked, רואים חברים שחולקים RadioGroupIndex כבלעדיים זה לזה, ובהחלה כותבים ל-TMemoryStream וטוענים את הזרם הזה בחזרה ל-TPdf כדי שהתצוגה תצייר את המצב החדש. החיווט בין TPdf ל-TPdfView מכוסה במאמר על בניית viewer PDF עשיר בתכונות עם PDFium VCL ב-Delphi. רישוי, הורדות ניסיון ושאר סט התכונות בעמוד המוצר של PDFium Component עבור Delphi