En el PDFium Component para Delphi y Lazarus, asignar TPdf.Active := True jamás lanza cuando un PDF falla al cargar: TPdf.SetActive captura toda excepción y deja el componente inactivo. Para ver el error real, llama en su lugar a TPdf.LoadDocument(Options, Report). Esa sobrecarga re-lanza la excepción original y rellena un TPdfLoadReport con el estado de carga, el código de error nativo de PDFium y si la tabla de referencias cruzadas tuvo que reconstruirse
El problema suele aflorar en código por lotes. Un trabajo de extracción de tablas recorre una carpeta de 13 PDFs reales con un único TPdf compartido, y 7 vuelven como fallos. Ninguno de los 7 archivos está realmente roto. Los bloques except alrededor de la carga nunca se disparan, el log culpa a nombres de archivo equivocados, y el primer error visible es un EPdfError desnudo sobre un componente inactivo, lanzado desde una lectura de propiedad varias líneas después de la carga que realmente falló. Dos comportamientos separados se apilan para producir ese cuadro, y ambos funcionan como fueron diseñados
¿Por qué TPdf.Active := True no lanza cuando un PDF falla al cargar?
TPdf.SetActive envuelve LoadDocument en un try..except que se traga cualquier clase de excepción y simplemente deja el componente inactivo. La absorción es deliberada: el mismo setter corre cuando un diseñador de formularios conmuta Active en el IDE, y una ruta mala no debe tumbar el IDE. En tiempo de ejecución TPdf.Active solo reporta si existe un handle de documento nativo, así que tras una carga fallida lee False y no ocurre nada más. Lo que se lanzó desapareció, fuera un EPdfError del parser, un error de stream o un EAccessViolation de un pdfium.dll a medio enlazar. Los mensajes detallados de la DLL descritos en diagnosticar fallos de carga de pdfium.dll en Delphi solo llegan a tu handler mediante una llamada que no se los trague
Pdf.FileName := FileName;
try
Pdf.Active := True; // SetActive se traga cualquier excepción de carga
except
on E: Exception do
Log.Add(FileName + ': ' + E.Message); // nunca se ejecuta
end;
// El fallo aflora aquí en cambio, como un EPdfError genérico:
// 'Cannot perform this operation on an inactive Pdf1 component'
Log.Add(Format('%s: %d pages', [FileName, Pdf.PageCount]));
// Fix mínimo para código existente: comprueba Active justo tras la asignación;
// desde v3.122.1 LastLoadReport conserva el texto del error absorbido
Pdf.Active := True;
if not Pdf.Active then
Log.Add(FileName + ': load failed: ' + Pdf.LastLoadReport.ErrorMessage);
El fallo por fin aparece en la primera llamada protegida. TPdf.PageCount, como la mayoría de propiedades de documento, empieza por CheckActive, que lanza un EPdfError que nombra al componente pero no al archivo ni a la causa. Comprobar Pdf.Active inmediatamente después de la asignación convierte un crash mal atribuido en una entrada «falló» honesta. Antes de PDFiumPas v3.122.1 la razón se perdía en ese punto; desde v3.122.1 la asignación fallida sustituye LastLoadReport por un report plsFailed que lleva el texto del error, así que la causa sobrevive. El objeto de excepción en sí y la auditoría a nivel de bytes siguen necesitando otro punto de entrada
¿Por qué reutilizar un TPdf falla desde el segundo archivo?
TPdf.FileName solo puede asignarse con el componente inactivo, así que una instancia compartida rechaza el segundo archivo antes de siquiera intentar cargarlo. TPdf.SetFileName empieza por CheckInactive, y la misma guardia protege Password y FormFill. Tras la primera carga exitosa la instancia sigue activa, la siguiente asignación lanza, y si el bucle del lote captura esa excepción y sigue, el error aterriza bajo el nombre del archivo nuevo mientras el documento viejo sigue abierto. Mezclado con las cargas fallidas absorbidas, el log deja de coincidir con la realidad. En la reproducción de 13 archivos, una instancia compartida reportó 7 fallos, mientras que un TPdf.Create(nil) fresco por documento abrió los 13. Poner Active := False entre archivos también funciona, pero una instancia por documento deja cada archivo aislado por construcción
¿Qué te da TPdf.LoadDocument con un TPdfLoadReport?
TPdf.LoadDocument(const Options: TPdfLoadOptions; out Report: TPdfLoadReport) lanza la excepción real y además te cuenta qué pasó en forma estructurada. La sobrecarga de archivo carga FileName; las sobrecargas hermanas toman TBytes o un puntero y tamaño, y LoadCustomDocument(AStream, AOwnsStream, Options, Report) cubre streams. Cada una valida las opciones, comprueba que la instancia está inactiva, ejecuta una auditoría a nivel de bytes de la cabecera, el startxref, las secciones xref y el marcador %%EOF, y luego realiza la carga nativa. La auditoría está acotada por el mismo tipo de límites que se discuten en los presupuestos de recursos del parser para PDFs que no son de fiar: TPdfLoadOptions.Default pone AuditByteLimit a 256 MiB, MaxIssues a 256, MaxXrefSections a 1024 y MaxXrefEntries a 4.000.000. En fallo el método pone Report.Status := plsFailed y re-lanza; como Report se escribe in situ, su contenido sobrevive a la excepción, y una copia se guarda en TPdf.LastLoadReport
Los campos del report responden las preguntas que un log por lotes realmente necesita. Status es uno de plsNotAttempted, plsLoaded, plsLoadedWithRecovery, plsRejected o plsFailed. NativeErrorCode guarda FPDF_GetLastError, así que FPDF_ERR_PASSWORD (4) separa una contraseña ausente o errónea de un archivo dañado reportado como FPDF_ERR_FORMAT (3). UsedRecovery, CrossReferenceTableValid y RecoveryRoute dicen si PDFium tuvo que reconstruir la tabla xref, y Issues lista cada hallazgo de la auditoría con Code, Severity, Offset, ObjectNumber y MessageText, con IssuesTruncated activado cuando MaxIssues cortó la lista
uses
SysUtils, Classes, TypInfo, FPdfView, PDFium;
procedure ProcessBatch(Files, Log: TStrings);
var
I: Integer;
Pdf: TPdf;
Options: TPdfLoadOptions;
Report: TPdfLoadReport;
begin
Options := TPdfLoadOptions.Default(plmCompatible);
for I := 0 to Files.Count - 1 do
begin
Pdf := TPdf.Create(nil); // una instancia por documento
try
Pdf.FileName := Files[I];
try
Pdf.LoadDocument(Options, Report);
except
on E: Exception do
begin
// Report queda relleno aunque LoadDocument haya lanzado
if Report.NativeErrorCode = FPDF_ERR_PASSWORD then
Log.Add(Files[I] + ': password required')
else
Log.Add(Format('%s: %s (%s)', [Files[I],
GetEnumName(TypeInfo(TPdfLoadStatus), Ord(Report.Status)),
E.Message]));
Continue;
end;
end;
if Report.UsedRecovery then
Log.Add(Files[I] + ': opened after PDFium rebuilt the xref table');
ExtractTables(Pdf, Log);
finally
Pdf.Free;
end;
end;
end;
¿Cuándo deberías cargar con plmStrict?
Usa plmStrict siempre que un archivo reparado en silencio sea peor que uno rechazado, como la entrada de archivo, el manejo de evidencias o un pipeline de firma. PDFium reconstruye discretamente una tabla de referencias cruzadas rota (ISO 32000-1 §7.5.4) escaneando el archivo en busca de objetos, estupendo para un visor y un problema para cualquier cosa que deba procesar exactamente los bytes que recibió. Tras la carga nativa, el componente pregunta a FPDF_DocumentHasValidCrossReferenceTable. En modo plmCompatible una reconstrucción produce plsLoadedWithRecovery más un aviso plicNativeCrossReferenceRebuild. En modo plmStrict el componente descarga el documento, pone plsRejected, añade plicStrictModeRejected y lanza EPdfError con "Strict PDF load rejected the document". El modo estricto también rechaza cualquier error de auditoría, y TPdfLoadOptions.Default(plmStrict) activa RequireFinalEndOfFileMarker, que asciende un %%EOF ausente o datos tras el final (§7.5.5) de aviso a error. La auditoría del xref complementa las comprobaciones a nivel de objeto de validar streams de objeto y xref con PDFium VCL
function AcceptForArchive(const FileName: string; out Reason: string): Boolean;
var
Pdf: TPdf;
Report: TPdfLoadReport;
I: Integer;
begin
Result := False;
Reason := '';
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
try
Pdf.LoadDocument(TPdfLoadOptions.Default(plmStrict), Report);
Result := True; // xref válido, sin errores de auditoría
except
on E: EPdfError do
begin
Reason := E.Message;
for I := 0 to High(Report.Issues) do
if Report.Issues[I].Severity = plisError then
Reason := Reason + sLineBreak + Format(' at offset %d: %s',
[Report.Issues[I].Offset, Report.Issues[I].MessageText]);
end;
end;
finally
Pdf.Free;
end;
end;
¿Dónde deja de decir la verdad TPdf.LastLoadReport?
TPdf.LastLoadReport está completo solo tras una sobrecarga de LoadDocument que tome opciones, porque solo esas sobrecargas ejecutan la auditoría de bytes. Un Active := True exitoso escribe un report de modo compatible sin auditoría de bytes, así que AuditAttempted se queda en False. Antes de PDFiumPas v3.122.1 uno fallido no escribía nada, lo que significaba que en una instancia compartida LastLoadReport seguía describiendo el archivo anterior, a menudo con un tranquilizador plsLoaded. Desde v3.122.1 toda carga fallida sustituye el report: un Active := True fallido, que sigue dejando el componente inactivo sin lanzar, y una llamada fallida a LoadDocument o LoadCustomDocument simple registran plsFailed con el texto del error, también sin auditoría. Otros dos huecos importan en la práctica. La validación de opciones y CheckInactive corren antes de que se inicialice el report, así que un AuditByteLimit negativo o una instancia ya activa lanzan sin producir report. Y NativeErrorCode solo significa algo cuando PDFium intentó de verdad el parseo; para un archivo ausente el wrapper lanza antes de que PDFium corra, así que registra ErrorMessage y el texto de la excepción en su lugar
La regla práctica es corta. Reserva Active := True para visores atados al diseñador donde un componente inactivo es un desenlace aceptable. En todas las demás partes, y sobre todo en código por lotes y de servidor, crea un TPdf por documento, llama a LoadDocument(Options, Report), captura la excepción que lanza y registra Report.Status, NativeErrorCode y los Issues de nivel error junto con el nombre del archivo. El coste son unas pocas líneas por punto de llamada, y cada fallo queda atribuido al archivo correcto con su causa real
La API de load report, el modo estricto y la auditoría a nivel de bytes se envían con el PDFium Component para Delphi, C++Builder y Lazarus, junto con renderizado, extracción de texto, relleno de formularios y validación PDF/A