PDFiumPas, el envoltorio para Delphi y C++Builder alrededor del motor PDFium de Google, guarda un documento con una versión PDF exacta de 1.3 a 1.7 mediante el parámetro PdfVersion del método TPdf.SaveAs. La propia llamada FPDF_SaveWithVersion de PDFium solo reescribe la cabecera %PDF-M.m, sin comprobar si el contenido real del documento es legal en esa versión. PDFiumPas cierra esa brecha con una pasada de conformidad posterior al guardado que recorre la cadena de revisiones de referencia cruzada activa y comprueba las declaraciones de Adobe Extension Level antes de que el archivo salga del método
Esa distinción importa más en la producción de impresión, donde un perfil PDF/X nombra una versión PDF exacta y una herramienta de preflight o un RIP rechaza cualquier cosa que discrepe silenciosamente de su propia cabecera, un escenario cubierto desde el lado de la salida en la validación de documentos PDF/X listos para imprenta con PDFiumPas. SaveAs expone el destino como la enumeración TPdfVersion, pv13 a pv17 junto con los valores más antiguos pv10 a pv12, más una TSaveOption independiente para reescrituras incrementales o completas. Pasad PdfVersion y PDFiumPas hace dos trabajos en una llamada: pide a PDFium que estampe la cabecera solicitada, y después vuelve a leer los bytes recién escritos y se niega a devolver un archivo cuyo contenido activo no pueda existir legalmente en esa versión
var
Pdf: TPdf;
begin
Pdf:= TPdf.Create(nil);
try
Pdf.FileName:= 'source.pdf';
Pdf.Active:= True;
try
Pdf.SaveAs('press-ready.pdf', saNoIncremental, pv17);
except
on E: Exception do
// E.Message names the offending feature and the version or
// extension level it actually needs, for example:
// "RichMedia annotations and RichMediaExecute actions require
// /Extensions /ADBE with /BaseVersion /1.7 and /ExtensionLevel 3
// or newer."
raise;
end;
finally
Pdf.Free;
end;
end;
¿Por qué la última definición de objeto en el archivo es lo que no hay que fiarse?
El último objeto físico con un número dado en un archivo PDF no es necesariamente el objeto que un lector conforme resolvería hoy para ese número. Un PDF que ha pasado por varias actualizaciones incrementales no tiene un único grafo de objetos, tiene un historial de ellos apilados dentro de un mismo archivo, y cada ciclo de anexado puede liberar un objeto, redefinirlo con un nuevo número de generación, o dejar su antiguo cuerpo físico alojado entre dos marcadores endobj sin ninguna entrada de referencia cruzada que ya apunte a él
PDFiumPas se topó exactamente con ese modo de fallo antes de rastrear explícitamente las revisiones xref: una anotación Redact huérfana por una reescritura posterior de objeto de página, o un diccionario /MarkInfo dejado físicamente presente sin ninguna entrada xref que apuntara a él, todavía podían aparecer en un escaneo de bytes y aun así disparar una comprobación de característica de versión que ya no se aplicaba al documento que un lector realmente abriría. La dirección del fallo era el rechazo falso, no la aceptación falsa: un archivo que genuinamente había dejado atrás una característica en su revisión actual todavía podía bloquearse al intentar guardarse en una versión inferior por culpa de contenido al que ya nadie podía llegar
¿Cómo determina PDFiumPas qué definiciones de objeto están realmente activas?
PDFiumPas resuelve el conjunto de objetos activos del mismo modo que lo hace un lector conforme, recorriendo la cadena de referencias cruzadas en lugar de escanear bytes en busca de cabeceras de objeto. El resolutor empieza en el último desplazamiento startxref del archivo y sigue cada enlace /Prev hacia atrás a través de revisiones más antiguas, analizando por el camino tablas de referencia cruzada clásicas, flujos híbridos enlazados por /XRefStm y flujos de referencia cruzada puros. El recorrido va de lo más nuevo a lo más antiguo y resuelve cada número de objeto la primera vez que se ve, así que una entrada libre en una revisión posterior eclipsa correctamente un cuerpo de objeto escrito en una anterior, y una redefinición bajo un nuevo desplazamiento o generación siempre gana sobre lo que sustituye
Los miembros de flujo de objetos reciben una comprobación adicional que una simple búsqueda de desplazamiento no puede proporcionar por sí sola, un mecanismo cubierto con más profundidad en la validación de flujos de objetos y de referencia cruzada con PDFiumPas. Un objeto comprimido recuperado de un /ObjStm tiene que tener su flujo padre confirmado como activo en el mismo recorrido, y su índice tiene que coincidir con la propia posición del miembro dentro de la cabecera de ese flujo antes de que PDFiumPas lo trate como contenido vivo. ISO 32000-1 sección 7.5.8.4 incluso describe un caso de referencia híbrida donde una tabla de compatibilidad clásica marca un objeto como libre mientras la entrada /XRefStm del trailer define simultáneamente ese mismo objeto como un miembro comprimido en otro lugar; PDFiumPas fusiona el flujo xref suplementario en la misma revisión antes de aplicar las entradas clásicas, así que la definición comprimida gana del modo que pretende la especificación
Los niveles de extensión de Adobe: la compuerta por encima del número de versión
Una cabecera %PDF-1.7 solo promete el conjunto de características que ISO 32000-1 estandarizó en 2008, mientras que varias capacidades en las que confían hoy los productores de PDF se distribuyeron después como suplementos exclusivos de Adobe superpuestos sobre ese mismo número de versión. Adobe registró cada suplemento como un par BaseVersion y ExtensionLevel anotado en el diccionario /Extensions del catálogo de documento bajo un prefijo de desarrollador, ADBE para las extensiones propias de Adobe, así que un lector puede distinguir un archivo PDF 1.7 plano de uno que además implementa un nivel de extensión numerado. Guardar en pv17 sin esa declaración no es un error por sí solo; solo se convierte en uno en el momento en que el contenido activo realmente depende de una característica que se supone que cubre la declaración
¿Qué características de versión alta disparan la compuerta de versión explícita?
PDFiumPas comprueba una lista concreta basada en la especificación en lugar de adivinar solo a partir del número de versión. Los diccionarios de imagen que llevan una entrada explícita /SMaskInData o un valor /BitsPerComponent de 16 requieren ambos PDF 1.5, y el caso de dieciséis bits sigue directamente las reglas de componente de imagen de la sección 4.8 de PDF Reference 1.5. Las anotaciones RichMedia y las acciones RichMediaExecute requieren /BaseVersion /1.7 con /ExtensionLevel 3 o superior. Los flujos 3D PRC, identificados por un diccionario que lleva tanto /Type /3D como /Subtype /PRC, requieren la misma versión base pero solo /ExtensionLevel 1. Los diccionarios Measure geoespaciales y las anotaciones Projection requieren /BaseVersion /1.7 con /ExtensionLevel 3, el mismo suplemento de Adobe del que depende RichMedia
La comprobación geoespacial lleva un detalle de lectura de la especificación que merece la pena conocer si alguna vez construís vuestra propia lógica acotada por versión sobre PDFiumPas. La Tabla 254 de ISO 32000-1 marca la entrada /Type del diccionario Measure como opcional, señalando solo que «si está presente, debe ser Measure», mientras que la Tabla 311 hace obligatorio /Type para el diccionario de flujo 3D donde vive el contenido PRC. La salida GeoPDF real de las herramientas de cartografía omite rutinariamente /Type en el diccionario Measure y solo escribe /Subtype /GEO, así que el detector geoespacial de PDFiumPas coincide solo con /Subtype en lugar de exigir ambas claves como sí puede hacer con seguridad su detector de PRC 3D. Exigir /Type en ambos diccionarios habría dejado que contenido GeoPDF conforme se colara por la compuerta sin detectarse, aterrizando en un archivo PDF 1.7 plano sin ninguna declaración de nivel de extensión que lo respaldara
¿Degrada PDFiumPas automáticamente las características no admitidas?
No como capacidad general, y asumir lo contrario es el error que hay que evitar aquí. SaveAs canaliza la versión de destino a través de una rutina interna, ValidatePdfVersionCompliance, y cuando esa rutina encuentra una característica que la versión de destino o su declaración de nivel de extensión no puede admitir, SaveAs lanza una excepción que lleva el texto de error de la rutina en lugar de escribir el archivo; quien llama recibe de vuelta una razón precisa, nombrada por característica, nunca un documento reescrito en silencio. El único sitio donde PDFiumPas sí reescribe contenido automáticamente es un destino PDF 1.3, donde elimina los valores por defecto de transparencia semánticamente neutros /BM /Normal, /CA 1 y /ca 1 que PDFium siempre escribe en los diccionarios ExtGState independientemente de la versión de destino, porque esos valores concretos no llevan ningún significado visual y PDF 1.3 es anterior a esas claves por completo
// PDF 1.3 targets rewrite the saved bytes to strip transparency
// defaults PDFium always emits, so incremental mode cannot apply
Pdf.SaveAs('legacy-archive.pdf', saIncremental, pv13);
// raises: PDF 1.3 normalization is incompatible with incremental
// save mode
La transparencia genuinamente no predeterminada y las máscaras suaves de imagen todavía fallan de plano en un destino PDF 1.3, porque eliminarlas cambiaría el aspecto real de la página, y PDFiumPas no va a tomar esa decisión en vuestro nombre. Merecen planificarse dos límites relacionados antes de que una versión exacta entre en un pipeline por lotes. La salida de versión explícita nunca lleva un diccionario /Encrypt; el guardado falla de inmediato si el origen está protegido, lo cual coincide con los perfiles PDF/X y PDF/A que en cualquier caso prohíben la cifrado, pero sí significa que el descifrado es un paso aparte en vuestro flujo de trabajo y no algo que SaveAs haga por vosotros. PDFiumPas tampoco tiene ningún método público para escribir una declaración /Extensions /ADBE en un catálogo, así que un archivo de origen que contenga RichMedia, PRC 3D o contenido geoespacial pero carezca de esa declaración no pasará la compuerta sin importar qué PdfVersion solicitéis; la declaración tiene que existir ya en el origen, típicamente porque la herramienta de creación la escribió, o la característica tiene que eliminarse antes del guardado. Merece la pena comprobar la propiedad de solo lectura TPdf.PdfVersion incluso antes de intentar un guardado de versión exacta, ya que resuelve la misma versión efectiva consciente del catálogo, cabecera o anulación /Version, la que esté vigente, en la que se apoya el propio validador en tiempo de guardado
Pdf.FileName:= 'incoming.pdf';
Pdf.Active:= True;
// PdfVersion resolves the same catalog-aware effective version the
// save-time validator uses, so a mismatch here is worth investigating
// before spending a full SaveAs attempt on it
LogSourceVersion('incoming.pdf', Pdf.PdfVersion);
Tratad una excepción de SaveAs en un destino de versión exacta como un informe de preflight y no como un fallo: el mensaje nombra la cláusula exacta que el documento de origen está infringiendo, que es precisamente la información que necesita una imprenta o un pipeline de archivado antes de que un archivo vaya más allá. La vía de guardado con versión explícita, el resolutor de revisiones xref activas y las comprobaciones de Adobe Extension Level descritos aquí forman parte del componente PDFiumPas estándar para Delphi y C++Builder; la página de producto recoge la referencia completa de TPdf.SaveAs junto con el resto de la API de conformidad y formularios