Вкладення файлів PDF зберігаються в дереві вбудованих файлів документа - структурі, яку більшість переглядачів відображають у вигляді панелі зі скріпкою або бічної панелі вкладень. У коді Delphi PDFium Component відкриває доступ до цього дерева через невеликий набір індексованих властивостей у TPdf: ви можете ітерувати за цілочисельним індексом, читати імена та байтові дані, створювати нові слоти та видаляти наявні. Інтерфейс API є доволі вузьким; існує лише кілька обмежень щодо порядку викликів та одне правило очищення (sanitization), які варто знати перед написанням робочого коду
Вкладення читаються з embedded-file tree відкритого документа: спочатку можна отримати імена, а повний TBytes payload завантажувати лише тоді, коли користувач справді просить файл
Читання вкладень у відкритому документі
Властивість AttachmentCount повертає кількість вбудованих файлів, оголошених у документі. Вона читає значення безпосередньо з внутрішнього виклику PDFium, тому відображає лише те, що фактично містить PDF. Після цього AttachmentName[Index] повертає ім'я для відображення як WString, а Attachment[Index] надає сирі байти у вигляді масиву TBytes. Обидва індекси починаються з нуля. Документ має бути відкритим (Pdf.Active = True) перед тим, як ви звернетеся до будь-якої з цих властивостей; виклик їх на закритому документі поверне нуль або порожній результат без генерації винятку
Слід мати на увазі одну річ: Attachment[Index] виділяє пам'ять і повертає повний вміст файлу під час кожного читання. Для документа, що містить великий вбудований ресурс, ітерація по всіх вкладеннях для побудови списку відображення означатиме витрати на виділення пам'яті при кожному виклику. Якщо вам потрібні лише імена для відображення, спочатку прочитайте AttachmentName і відкладіть отримання байтів до моменту, коли користувач фактично запитає файл
procedure ListAttachments(Pdf: TPdf);
var
I: Integer;
Data: TBytes;
begin
if not Pdf.Active then
Exit;
for I := 0 to Pdf.AttachmentCount - 1 do
begin
Data := Pdf.Attachment[I];
Writeln(Format('%d: %s (%d bytes)',
[I, Pdf.AttachmentName[I], Length(Data)]));
end;
end;
Вилучення вкладення на диск
У бібліотеці немає допоміжної функції SaveAttachment. Ви читаєте байти та записуєте їх туди, куди вам потрібно, що повністю перекладає завдання побудови та очищення шляхів на ваш код. Це важливо, коли імена вкладень надходять із ненадійних документів. Імена вкладень PDF - це рядки, що зберігаються всередині файлу; вони можуть містити розділювачі шляхів, символи Unicode, схожі на інші, та інші символи, які призведуть до непередбачуваних результатів, якщо передати їх безпосередньо в TFileStream.Create. Завжди пропускайте ім'я через ExtractFileName перед побудовою будь-якого вихідного шляху, і розгляньте можливість відхилення імен, які починаються з крапки або містять символи, що виходять за межі очікуваних вашою системою
Масив байтів, який повертає Attachment[Index], належить стороні, що викликає код (caller). Запишіть його за допомогою звичайного TFileStream, і ви зможете робити з ним усе, що завгодно, включно з перевіркою перших кількох байтів для підтвердження фактичного формату файлу замість того, щоб довіряти заявленому імені
procedure ExtractAttachment(Pdf: TPdf; Index: Integer; const OutputDir: string);
var
SafeName: string;
OutPath: string;
Data: TBytes;
FS: TFileStream;
begin
SafeName := ExtractFileName(Pdf.AttachmentName[Index]);
if SafeName = '' then
SafeName := Format('attachment_%d', [Index]);
OutPath := IncludeTrailingPathDelimiter(OutputDir) + SafeName;
Data := Pdf.Attachment[Index];
FS := TFileStream.Create(OutPath, fmCreate);
try
if Length(Data) > 0 then
FS.WriteBuffer(Data[0], Length(Data));
finally
FS.Free;
end;
end;
Додавання вкладень і двохетапний запис
Створення вкладення вимагає двох викликів, а не одного. CreateAttachment(Name) реєструє новий слот у дереві вбудованих файлів і повертає True у разі успіху. Цей слот спочатку є порожнім. Потім ви призначаєте корисне навантаження, записуючи дані в Attachment[AttachmentCount - 1], вказуючи на щойно створений запис. Якщо CreateAttachment повертає False, слот не було створено, і присвоєння може пошкодити вкладення під тим індексом, який виявиться останнім
Після модифікації списку вкладень зміни існують лише в пам'яті. Викличте SaveAs, щоб записати новий файл із оновленим деревом вбудованих файлів. PDFium Component не підтримує збереження назад у той самий файл, який наразі відкритий, оскільки рушій утримує дескриптор читання джерела. Стандартний патерн для оновлення на місці (in-place) полягає в збереженні у тимчасовий шлях, закритті документа, видаленні або перейменуванні оригіналу, а потім перейменуванні тимчасового файлу на потрібне місце та його повторному відкритті
procedure AddFileAttachment(Pdf: TPdf; const FilePath: string);
var
FS: TFileStream;
Data: TBytes;
AttachName: string;
begin
if not Pdf.Active then
Exit;
FS := TFileStream.Create(FilePath, fmOpenRead or fmShareDenyWrite);
try
SetLength(Data, FS.Size);
if FS.Size > 0 then
FS.ReadBuffer(Data[0], FS.Size);
finally
FS.Free;
end;
AttachName := ExtractFileName(FilePath);
if Pdf.CreateAttachment(AttachName) then
Pdf.Attachment[Pdf.AttachmentCount - 1] := Data;
end;
Інформація про тип вкладення
Окрім імені та байтового навантаження, AttachmentType[Index] повертає рядок MIME-типу, що зберігається у словнику вбудованих файлів PDF, якщо він був записаний під час початкового прикріплення файлу. Багато генераторів залишають це поле порожнім або встановлюють для нього загальне значення, наприклад application/octet-stream, тому ви не можете покладатися на нього для визначення формату у виробничому конвеєрі. Для надійної ідентифікації прочитайте перші кілька байтів вмісту та перевірте наявність відомих сигнатур файлів: %PDF для вкладеного PDF, заголовок локального файлу ZIP PK\x03\x04 для документів Office Open XML, \xD0\xCF\x11\xE0 для старих складених бінарних файлів (compound-file). Інформацію про тип зі словника цілком прийнятно відображати в мітці інтерфейсу користувача, але вона не повинна керувати рішеннями щодо обробки, коли у вас є фактичні байти
Видалення вкладень
Функція DeleteAttachment(Index) видаляє запис на вказаній позиції та повертає True у разі успіху. Після видалення решта записів зміщуються вниз, тому якщо ви видаляєте кілька вкладень у циклі, ви повинні ітерувати від останнього індексу донизу, а не вперед, щоб уникнути пропуску записів після кожного зсуву. Зміни зберігаються в пам'яті до моменту виклику SaveAs
Поширеним сценарієм у конвеєрах обробки документів є видалення всіх вкладень із вхідного PDF перед його передачею далі - з міркувань безпеки або зменшення розміру. Отримайте кількість один раз перед циклом і виконуйте ітерацію у зворотному порядку:
procedure StripAllAttachments(Pdf: TPdf);
var
I: Integer;
begin
for I := Pdf.AttachmentCount - 1 downto 0 do
Pdf.DeleteAttachment(I);
end;
Де вкладення PDF зустрічаються на практиці
API вкладень працює з будь-яким PDF, який може відкрити PDFium, але документи, де ви фактично стикаєтеся з вбудованими файлами, групуються навколо кількох конкретних випадків. Стандарт PDF/A-3 (ISO 19005-3) прямо дозволяє відповідні вбудовані файли як механізм для пакування вихідних даних разом з архівним представленням; електронні рахунки ZUGFeRD та Factur-X покладаються саме на це, щоб вбудувати структуроване навантаження XML у зрозумілий для людини макет PDF. PDF-файли, створені з електронних листів, іноді містять оригінальні вкладення повідомлень, перенесені в дерево вбудованих файлів. Технічна документація, що походить із систем структурованого створення, іноді пакує допоміжні ресурси таким же чином
Коли ваша програма обробляє вхідні PDF-файли ззовні вашої організації, перевірку AttachmentCount як частину прийому документа варто робити з двох незалежних причин. По-перше, вбудовані файли можуть містити дані, які ви хочете вилучити та обробити, наприклад XML всередині PDF-рахунку. По-друге, вбудовані файли можуть містити довільний виконуваний вміст, тому знання про те, що саме присутнє, має значення навіть тоді, коли ви ніколи не збираєтеся його вилучати. Жодна з причин не вимагає від вас робити нічого складного: прочитайте кількість, перевірте імена та вирішіть, що робити з байтами
Властивості вкладень, показані тут, є частиною PDFium Component component для Delphi та C++Builder