PDFiumPas, el envoltorio para Delphi y C++Builder alrededor del motor PDFium de Google, guarda un documento en una versión exacta de PDF de la 1.3 a la 1.7 mediante el parámetro PdfVersion del método TPdf.SaveAs. La propia llamada FPDF_SaveWithVersion de PDFium solo reescribe el encabezado %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 nivel de extensión de Adobe 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 exacta de PDF y una herramienta de preflight o un RIP rechaza cualquier cosa que silenciosamente contradiga su propio encabezado, un escenario cubierto desde el lado de salida en la validación de documentos PDF/X listos para impresión con PDFiumPas. SaveAs expone el destino como el enum TPdfVersion, pv13 a pv17 junto a los valores más antiguos pv10 a pv12, más un TSaveOption independiente para reescrituras incrementales o completas. Pase PdfVersion y PDFiumPas hace dos trabajos en una llamada: le pide a PDFium que estampe el encabezado solicitado, y luego relee 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é es incorrecto confiar en la última definición de objeto del archivo?
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 para ese número hoy. Un PDF que ha pasado por varias actualizaciones incrementales no tiene un solo grafo de objetos, tiene un historial de ellos apilados dentro de un único archivo, y cada ciclo de anexado puede liberar un objeto, redefinirlo bajo un nuevo número de generación, o dejar su antiguo cuerpo físico sentado 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 de redacción huérfana por una reescritura posterior de objeto de página, o un diccionario /MarkInfo dejado físicamente presente sin ninguna entrada xref que apunte a él, todavía podía aparecer en un escaneo de bytes y todavía podía 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 superado una característica en su revisión actual todavía podía ser bloqueado de guardarse en una versión inferior debido a 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 activo de la misma manera que lo hace un lector conforme, recorriendo la cadena de referencia cruzada en lugar de escanear bytes en busca de encabezados de objeto. El resolvedor 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 tablas de referencia cruzada clásicas, flujos híbridos enlazados por /XRefStm, y flujos de referencia cruzada puros en el camino. El recorrido se ejecuta de más reciente a más antiguo y fija 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 reemplaza
Los miembros de flujo de objetos reciben una comprobación extra que una simple búsqueda de desplazamiento no puede proveer por sí sola, un mecanismo cubierto con más profundidad en la validación de flujos de objetos y referencia cruzada con PDFiumPas. Un objeto comprimido recuperado de un /ObjStm debe 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 del encabezado 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 que la entrada /XRefStm del trailer simultáneamente define ese mismo objeto como un miembro comprimido en otro lugar; PDFiumPas fusiona el flujo xref suplementario en la misma revisión antes de que se apliquen las entradas clásicas, así que la definición comprimida gana de la manera en que la especificación pretende
Niveles de extensión de Adobe: la puerta por encima del número de versión
Un encabezado %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 salieron 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 registrado en el diccionario /Extensions del catálogo del documento bajo un prefijo de desarrollador, ADBE para las propias extensiones de Adobe, así que un lector puede distinguir un archivo PDF 1.7 simple de uno que también 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 puerta de versión explícita?
PDFiumPas comprueba una lista específica 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 /SMaskInData explícita o un valor /BitsPerComponent de 16 requieren ambos PDF 1.5, siguiendo el caso de dieciséis bits directamente las reglas de componente de imagen de PDF Reference 1.5 sección 4.8. 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 de Medida geoespacial y las anotaciones de Proyección 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 especificación que vale la pena conocer si alguna vez construye su propia lógica acotada por versión encima de PDFiumPas. La Tabla 254 de ISO 32000-1 marca la entrada /Type del diccionario de Medida 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 mapeo rutinariamente omite /Type en el diccionario de Medida y solo escribe /Subtype /GEO, así que el detector geoespacial de PDFiumPas coincide solo con /Subtype en lugar de requerir ambas claves de la forma en que su detector de 3D PRC puede hacerlo con seguridad. Requerir /Type en ambos diccionarios habría dejado que el contenido GeoPDF conforme pasara la puerta sin detectarse, aterrizando en un archivo PDF 1.7 simple sin ninguna declaración de nivel de extensión que lo respalde
¿Degrada PDFiumPas automáticamente las características no soportadas?
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 pueden soportar, SaveAs lanza una excepción con 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 silenciosamente. El único lugar donde PDFiumPas sí reescribe contenido automáticamente es un destino PDF 1.3, donde elimina los valores predeterminados de transparencia semánticamente neutros /BM /Normal, /CA 1, y /ca 1 que PDFium siempre escribe en los diccionarios ExtGState sin importar la versión de destino, porque esos valores específicos no llevan ningún significado visual y PDF 1.3 es anterior por completo a esas claves
// 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 genuina no predeterminada y las máscaras suaves de imagen todavía fallan por completo en un destino PDF 1.3, porque eliminarlas cambiaría cómo realmente se ve la página, y PDFiumPas no tomará esa decisión en su nombre. Dos límites relacionados vale la pena planificar antes de que una versión exacta entre a 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 que resulta coincidir con los perfiles PDF/X y PDF/A que de todos modos prohíben el cifrado, pero sí significa que el descifrado es un paso separado en su flujo de trabajo en lugar de algo que SaveAs hace por usted. 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 contiene RichMedia, 3D PRC, o contenido geoespacial pero carece de esa declaración no pasará la puerta sin importar qué PdfVersion solicite; la declaración ya tiene que existir en el origen, típicamente porque la herramienta de creación la escribió, o la característica tiene que salir antes del guardado. La propiedad de solo lectura TPdf.PdfVersion vale la pena comprobarla antes de siquiera intentar un guardado de versión exacta, ya que resuelve la misma versión efectiva consciente del catálogo, encabezado 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);
Trate una excepción de SaveAs en un destino de versión exacta como un informe de preflight en lugar de un bug: el mensaje nombra la cláusula exacta que el documento de origen está violando, que es precisamente la información que necesita una imprenta o un pipeline de archivo antes de que un archivo vaya más lejos. La ruta de guardado de versión explícita, el resolvedor de revisiones xref activo, y las comprobaciones de nivel de extensión de Adobe descritos aquí se incluyen como parte del componente PDFiumPas estándar para Delphi y C++Builder; la página del producto lleva la referencia completa de TPdf.SaveAs junto con el resto de la API de conformidad y formularios