PDFlibPas закача вграден файл към една конкретна страница, а не към документа като цяло, като записва /AF масив в речника на страницата, докато самият payload остава регистриран в EmbeddedFiles дървото с имена на документа. Това разделяне описва ISO 32000-2 §14.13 и именно то позволява на читателя да отговори на въпроса, който attachment на ниво документ не може: към коя страница спадат тези данни
Случаите на употреба са по-конкретни от общите attachments. Доклад от снемане, където всяка страница носи суровата серия измервания зад графиката си. Сканирана партида, където всяка страница пази OCR резултата, породил нейния текстов слой. Комплект чертежи, където всеки лист носи CAD извлечението, от което е рендиран. И в трите случая списък с attachments на ниво документ би бил купчина файлове, чиито имена кодират номера на страници – конвенция, а не структура
Един payload, две места с референции към него
Важното структурно положение е, че асоциацията на ниво страница не създава второ копие на нищо. Файлът е вграден веднъж и регистриран в EmbeddedFiles дървото точно както при attachment на ниво документ, с една и съща машина за file specification. Разликата е къде се записват референцията и нейният relationship ключ: в речника на страницата вместо в каталога на документа
Следват две последици. Първо, читател, който знае само за attachments на ниво документ, пак намира payload-а, защото той е в дървото с имена, където такъв читател търси. Второ, изчистването на асоциацията на страницата маха обвързването, не файла. ClearPageAssociatedFiles откача страницата от свързаните ѝ файлове и оставя payload-ите достиними през дървото с имена, което е консервативното поведение: операция, казваща „изчисти асоциацията“, не бива безшумно да унищожава данни, към които друга част от документа може да сочи
Тази функция има едно нарочно тесно условие за успех, което си струва да знаете. Докладва успех само когато страницата наистина е носила /AF ключ. Страница, никога нямала асоциации, връща неуспех, а не жизнерадостно потвърждение, така че викащият не може да обърка no-op с приключено изчистване
var
Lib: TPDFlib;
Idx, I: Integer;
begin
Lib := TPDFlib.Create(nil);
try
Lib.LoadFromFile('survey-report.pdf');
// Закача серията измервания, породила графиката на страница 3
Idx := Lib.AddPageAssociatedFileFromFile(3,
'series-03.csv', // файл на диска
'measurements.csv', // показвано име в PDF-а
'text/csv', // MIME тип
'Raw measurement series for figure 3',
'Data'); // AFRelationship, ISO 32000-2 14.13
if Idx < 0 then
raise Exception.Create('page association refused');
for I := 0 to Lib.GetPageAssociatedFileCount(3) - 1 do
Writeln('page 3 associated file, embedded index ',
Lib.GetPageAssociatedFileEmbeddedIndex(3, I));
Lib.SaveToFile('survey-report-with-data.pdf');
finally
Lib.Free;
end;
end;
Relationship низът на практика не е свободен текст. ISO 32000-2 дефинира речник от стойности – Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema и Unspecified – и консуматорите се опират на него. Data за числата зад една графика, Source за документа, от който е породена страницата, Alternative за еквивалентно представяне. Избирайте от речника дори когато нищо в pipeline-а ви още не го чете, защото следващият инструмент във веригата може би ще го прави
Защо еднаквото търсене има нужда от FollowRef и в двете посоки?
Защото следването на референции отговаря на два различни въпроса, а кодът трябва да знае кой от тях задава. Търсене по ключ, което следва indirect референции, връща обекта, към който сочи референцията. Търсене, което не следва, връща самата референция. И двете са коректни, а ползването на грешното ражда безшумно неправилно поведение вместо грешка
Четенето на свързан файл демонстрира първата посока. За да получите номера на обекта на вградения stream зад ключовете /EF и /F на file specification, търсенето трябва да не следва, защото следването разпъва референцията в stream обекта и номерът на обекта изчезва. Правилото се обобщава: всеки кодов път, който се нуждае от идентичност на обекта, а не от съдържанието му, трябва да вземе суровата референция
Optional content показва обратната посока, а намирането ѝ струва повече. Речникът със свойства на optional content се записва в каталога като indirect обект, така че код, който го прочита обратно без следване, получава референция вместо речник. Type проверка върху тази стойност тогава се проваля, и естественият fallback клон – ако няма конфигурация, създай такава – тръгва и презаписва конфигурацията, която вече е била там. Нищо не вдига изключение. Слоевете, описани в optional content групи и слоеве, просто губят състоянието си на видимост по подразбиране
Урокът се обобщава отвъд двата случая. Когато търсене може да върне референция или обекта, гола type проверка не е обработка на грешки: това е клон, който рано или късно ще бъде тръгнат по погрешна причина. Решете изрично какво трябва на всяко място на извикване и предпочитайте публичното API, отговарящо директно на въпроса – като свойство за брой на optional content – пред достигане до protected accessor за речника на каталога
// Attachments на ниво документ и асоциации на ниво страница съжителстват.
// Вграден файл може да бъде маркиран като свързан и на ниво документ
if Lib.IsEmbeddedFileAssociated(0) = 0 then
Lib.SetEmbeddedFileAssociated(0, 1, 'Supplement');
Writeln('document associated files: ', Lib.GetAssociatedFileCount);
Writeln('page 3 associated files : ',
Lib.GetPageAssociatedFileCount(3));
// Изчистването откача обвързването на страницата; payload-ът остава в дървото с имена
if Lib.ClearPageAssociatedFiles(3) > 0 then
Writeln('page 3 associations removed, payloads still reachable');
Какво правят conformance режимите с attachments
Архивните профили ограничават какво може да бъде вградено, и ограничението се налага на входната точка, а не при запис. PDF/A-1 забранява вградени файлове изцяло, PDF/A-2 позволява само вградени PDF/A документи, а PDF/A-3 е профилът, отворил вграждането към произволни файлови типове – точно затова хибридните фактурни формати се строят върху него
PDFlibPas отказва attachment-а, когато активният conformance режим не го позволява – при извикването, а не стотици операции по-късно, при изхода. Това е съзнателен избор къде грешката е най-евтина за действие: отказът на мястото на извикването назовава файла, който добавяхте, докато отказът при запис назовава документ и оставя на вас да изчислите кой от четиридесетте attachments го е причинил
Затова свързаните файлове се появяват толкова често в електронното фактуриране. Хибридна фактура е PDF, който човек чете, с machine-readable XML payload, закачен и маркиран с правилния relationship, и профилът на контейнера, и relationship ключът са част от спецификацията, а не конвенции. Тази конструкция е обхваната в градене на Factur-X и ZUGFeRD хибридни фактури, а страната на метаданните – в PDF/A-3 XMP extension схемата
Кога асоциацията трябва да е на страница, а не на документ?
Когато консуматор трябва да знае към коя страница спадат данните – и само тогава. Attachments на ниво документ са по-прости, по-широко поддържани от viewer-ите и достатъчни винаги, когато payload-ът описва целия документ: XML на фактура, signature manifest, source архив. Посегнете към асоциация на ниво страница, когато payload-ът е истински обвързан със страницата и идентичността на страницата е част от смисъла му
Поддръжката е практическото ограничение. Свързаните файлове на ниво страница са конструкция от PDF 2.0 и поддръжката в viewer-ите е по-тънка, отколкото за attachments на ниво документ. Тъй като payload-ът и в двата случая стои в дървото с имена, viewer, който игнорира /AF на страниците, пак показва файла в списъка си с attachments, така че деградацията е грациозна. Но ако обвързването със страницата е съществено за консуматора ви, а не просто полезни метаданни, проверете читателя, когото реално целите, вместо да допускате
Свързаните файлове на ниво страница, attachments на ниво документ и архивните profile портици, управляващи и двете, идват с PDFlibPas Delphi PDF библиотеката. Ако по пътя на входа оправяте и по-стари файлове, работата по метаданни и conformance в конвертиране към PDF/A с ремонт на метаданни е това, което решава кой от тези attachment пътища ви е наличен изобщо