PDFlibPas adjunta un archivo embebido a una página específica en lugar del documento completo, escribiendo un arreglo /AF dentro del diccionario de la página, mientras el payload en sí sigue registrado en el name tree EmbeddedFiles del documento. Esa división es lo que describe ISO 32000-2 §14.13, y es lo que permite a un lector responder la pregunta que un adjunto a nivel de documento no puede: a qué página pertenecen estos datos
Los casos de uso son más específicos que los adjuntos genéricos. Un reporte de sondeo donde cada página carga la serie cruda de mediciones detrás de su gráfico. Un lote escaneado donde cada página conserva el resultado de OCR que produjo su capa de texto. Un set de planos donde cada lámina lleva el extracto de CAD desde el que se renderizó. En cada caso, una lista de adjuntos a nivel de documento sería un montón de archivos con números de página codificados en el nombre, que es una convención, no una estructura
Un payload, dos lugares desde los que se referencia
El punto estructural importante es que la asociación a nivel de página no crea una segunda copia de nada. El archivo se embebe una sola vez y se registra en el name tree EmbeddedFiles exactamente como un adjunto a nivel de documento, usando la misma maquinaria de file specification. Lo que cambia es dónde se escriben la referencia y su clave de relación: en el diccionario de la página en lugar del catálogo del documento
De ahí siguen dos consecuencias. Primera: un lector que solo conoce adjuntos a nivel de documento igual encuentra el payload, porque está en el name tree donde ese lector busca. Segunda: limpiar la asociación de página elimina el binding, no el archivo. ClearPageAssociatedFiles desacopla la página de sus archivos asociados y deja los payloads accesibles a través del name tree, que es el comportamiento conservador: una operación que dice limpiar la asociación no debería destruir silenciosamente datos que otra parte del documento puede referenciar
Esa función tiene una condición de éxito deliberadamente estrecha que conviene conocer. Reporta éxito únicamente cuando la página realmente llevaba una clave /AF. Una página que nunca tuvo asociaciones devuelve falla en lugar de una confirmación amable, así que quien llama no puede confundir un no-op con una limpieza completada
var
Lib: TPDFlib;
Idx, I: Integer;
begin
Lib := TPDFlib.Create(nil);
try
Lib.LoadFromFile('survey-report.pdf');
// Adjunta la serie de mediciones que produjo el gráfico de la página 3
Idx := Lib.AddPageAssociatedFileFromFile(3,
'series-03.csv', // archivo en disco
'measurements.csv', // nombre visible dentro del PDF
'text/csv', // tipo 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;
La cadena de relación no es texto libre en la práctica. ISO 32000-2 define un vocabulario, Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema y Unspecified, y los consumidores se apoyan en él. Data para los números detrás de un gráfico, Source para el documento desde el que se generó una página, Alternative para una representación equivalente. Elija del vocabulario aunque nada en su pipeline lo lea todavía, porque la siguiente herramienta de la cadena podría hacerlo
¿Por qué la misma búsqueda necesita FollowRef en ambas direcciones?
Porque seguir referencias responde a dos preguntas distintas, y el código tiene que saber cuál está haciendo. Una búsqueda por clave que sigue referencias indirectas devuelve el objeto al que la referencia apunta. Una búsqueda que no las sigue devuelve la referencia misma. Ambas son correctas, y usar la equivocada produce un mal comportamiento silencioso en lugar de un error
Leer un archivo asociado demuestra la primera dirección. Para obtener el número de objeto del stream embebido detrás de las claves /EF y /F de la file specification, la búsqueda no debe seguir, porque seguir resuelve la referencia en el objeto stream y el número de objeto se pierde. La regla se generaliza: cualquier ruta de código que necesite la identidad de un objeto en lugar de su contenido tiene que tomar la referencia cruda
El optional content muestra la dirección opuesta, y costó más encontrarla. El diccionario de propiedades de optional content se escribe en el catálogo como objeto indirecto, así que el código que lo lee de vuelta sin seguir obtiene una referencia en lugar de un diccionario. La comprobación de tipo sobre ese valor falla, y la rama de fallback natural, si no hay configuración, crear una, se ejecuta y sobrescribe la configuración que ya estaba. Nada lanza una excepción. Las capas descritas en optional content groups y capas simplemente pierden su estado de visibilidad predeterminado
La lección se generaliza más allá de ambos casos. Cuando una búsqueda puede devolver una referencia o el objeto, una comprobación de tipo a secas no es manejo de errores: es una rama que tarde o temprano se tomará por la razón equivocada. Decida explícitamente qué necesita cada punto de llamada, y prefiera la API pública que responde la pregunta directamente, como una propiedad de conteo de optional content, antes de meterse en un accessor protegido del diccionario del catálogo
// Los adjuntos a nivel de documento y las asociaciones a nivel de página
// coexisten: un archivo embebido puede marcarse asociado también ahí
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));
// Limpiar desacopla el binding de la página; el payload sigue en el name tree
if Lib.ClearPageAssociatedFiles(3) > 0 then
Writeln('page 3 associations removed, payloads still reachable');
Lo que los modos de conformidad hacen con los adjuntos
Los perfiles de archivo restringen qué se puede embeber, y la restricción se aplica en el punto de entrada en lugar de al guardar. PDF/A-1 prohíbe los archivos embebidos por completo, PDF/A-2 permite solo documentos PDF/A embebidos, y PDF/A-3 es el perfil que abrió el embedding a tipos de archivo arbitrarios, que es justamente por lo que los formatos de factura híbridos se construyen sobre él
PDFlibPas rechaza el adjunto cuando el modo de conformidad activo no lo permite, en la llamada, no cientos de operaciones después durante el output. Es una decisión deliberada sobre dónde es más barato actuar ante un error: un rechazo en el punto de llamada nombra el archivo que estaba agregando, mientras que un rechazo al guardar nombra un documento y lo deja a usted descifrando cuál de cuarenta adjuntos lo causó
Esta es también la razón por la que los archivos asociados aparecen tan a menudo en la facturación electrónica. Una factura híbrida es un PDF que una persona lee con un payload XML legible por máquina adjunto y marcado con la relación correcta, y tanto el perfil del contenedor como la clave de relación son parte de la especificación, no convenciones. Esa construcción está cubierta en construcción de facturas híbridas Factur-X y ZUGFeRD, con el lado de metadata en el extension schema XMP de PDF/A-3
¿Cuándo conviene la asociación por página en lugar de por documento?
Cuando un consumidor necesita saber a qué página pertenecen los datos, y solo entonces. Los adjuntos a nivel de documento son más simples, tienen soporte más amplio en los visores y son adecuados siempre que el payload describa el documento completo: un XML de factura, un manifiesto de firmas, un archivo fuente. Recurra a la asociación a nivel de página cuando el payload sea genuinamente acotado a la página y la identidad de la página sea parte de su significado
El soporte es la restricción práctica. Los archivos asociados a nivel de página son una construcción de PDF 2.0, y el soporte en visores es más delgado que para adjuntos a nivel de documento. Como el payload está en el name tree de cualquier forma, un visor que ignora /AF en las páginas igual muestra el archivo en su lista de adjuntos, así que la degradación es graceful. Pero si el binding de página es esencial para su consumidor y no metadata útil, verifique el lector al que realmente apunta en lugar de asumir
Los archivos asociados a nivel de página, los adjuntos a nivel de documento y las compuertas de perfil de archivo que gobiernan a ambos vienen en la PDFlibPas Delphi PDF library. Si además repara archivos viejos en la entrada, el trabajo de metadata y conformidad en conversión a PDF/A con reparación de metadata es lo que decide cuáles de estas rutas de adjuntos tiene disponibles para empezar