En el PDFium Component for Delphi and Lazarus, asignar TPdf.Active := True jamás lanza cuando un PDF falla al cargar: TPdf.SetActive atrapa toda excepción y deja el componente inactivo. Para ver el error de verdad, llame TPdf.LoadDocument(Options, Report). Esa sobrecarga relanza la excepción original y llena 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 salir a la luz en código por lotes. Un trabajo de extracción de tablas recorre una carpeta de 13 PDFs reales con un solo TPdf compartido, y 7 de ellos vuelven como fallas. Ninguno de esos 7 archivos está roto de verdad. Los bloques except alrededor de la carga nunca se disparan, el log culpa a los nombres de archivo equivocados, y el primer error visible es un EPdfError pelado sobre un componente inactivo, lanzado desde una lectura de propiedad varias líneas después de la carga que realmente falló. Dos comportamientos distintos 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 a LoadDocument en un try..except que se traga toda clase de excepción y simplemente deja el componente inactivo. La digestió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 pasa nada más. Lo que se lanzó se perdió, fuera un EPdfError del parser, un error de stream o un EAccessViolation de una pdfium.dll medio enlazada. Los mensajes detallados de la DLL descritos en diagnosticar fallos de carga de pdfium.dll en Delphi solo llegan a su handler por 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;
// La falla sale 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: pruebe Active justo tras la asignación;
// desde v3.122.1 LastLoadReport guarda el texto del error tragado
Pdf.Active := True;
if not Pdf.Active then
Log.Add(FileName + ': load failed: ' + Pdf.LastLoadReport.ErrorMessage);
La falla por fin aparece en la primera llamada protegida. TPdf.PageCount, como casi toda propiedad de documento, arranca con CheckActive, que lanza un EPdfError nombrando al componente pero ni al archivo ni a la causa. Probar Pdf.Active inmediatamente después de la asignación convierte un crash mal atribuido en una entrada honesta de "falló". Antes de PDFiumPas v3.122.1 la razón se perdía en ese punto; desde la v3.122.1 la asignación fallida reemplaza LastLoadReport con un reporte plsFailed que carga el texto del error, así que la causa sobrevive. El objeto de excepción en sí y la auditoría a nivel de bytes todavía requieren un punto de entrada distinto
¿Por qué reutilizar un TPdf falla desde el segundo archivo en adelante?
TPdf.FileName solo puede asignarse con el componente inactivo, así que una instancia compartida rechaza el segundo archivo antes de que siquiera intente cargarlo. TPdf.SetFileName arranca con CheckInactive, y la misma guardia protege Password y FormFill. Tras la primera carga exitosa la instancia se queda activa, la siguiente asignación lanza, y si el bucle del lote atrapa esa excepción y sigue, el error aterriza bajo el nombre del archivo nuevo mientras el documento viejo sigue abierto. Mezclado con las fallas de carga tragadas, el log deja de cuadrar con la realidad. En la reproducción de 13 archivos, una instancia compartida reportaba 7 fallas, mientras que un TPdf.Create(nil) fresco por documento abrió los 13. Fijar Active := False entre archivos también funciona, pero una instancia por documento deja cada archivo aislado por construcción
¿Qué le da TPdf.LoadDocument con un TPdfLoadReport?
TPdf.LoadDocument(const Options: TPdfLoadOptions; out Report: TPdfLoadReport) lanza la excepción real y además le dice 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, chequea que la instancia esté inactiva, corre una auditoría a nivel de bytes del header, startxref, las secciones xref y el marcador %%EOF, y después ejecuta la carga nativa. La auditoría está acotada por el mismo tipo de límites discutidos en presupuestos de recursos del parser para PDFs no confiables: TPdfLoadOptions.Default fija AuditByteLimit en 256 MiB, MaxIssues en 256, MaxXrefSections en 1024 y MaxXrefEntries en 4.000.000. En fallo el método fija Report.Status := plsFailed y relanza; como Report se escribe in situ, su contenido sobrevive a la excepción, y una copia queda guardada en TPdf.LastLoadReport
Los campos del reporte responden las preguntas que un log por lotes realmente necesita. Status es uno de plsNotAttempted, plsLoaded, plsLoadedWithRecovery, plsRejected o plsFailed. NativeErrorCode trae FPDF_GetLastError, así que FPDF_ERR_PASSWORD (4) separa una contraseña faltante o equivocada 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 auditoría con Code, Severity, Offset, ObjectNumber y MessageText, con IssuesTruncated fijado 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 se llena 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 conviene cargar con plmStrict?
Use plmStrict siempre que un archivo reparado en silencio sea peor que uno rechazado, como admisión de archivos, manejo de evidencia o un pipeline de firma. PDFium reconstruye calladamente una tabla de referencias cruzadas rota (ISO 32000-1 §7.5.4) escaneando el archivo en busca de objetos, lo que es genial para un visor y un problema para cualquier cosa que deba procesar exactamente los bytes que le dieron. Tras la carga nativa, el componente pregunta a FPDF_DocumentHasValidCrossReferenceTable. En modo plmCompatible una reconstrucción produce plsLoadedWithRecovery más un warning plicNativeCrossReferenceRebuild. En modo plmStrict el componente descarga el documento, fija plsRejected, suma plicStrictModeRejected y lanza EPdfError con "Strict PDF load rejected the document". El modo estricto además rechaza cualquier error de auditoría, y TPdfLoadOptions.Default(plmStrict) activa RequireFinalEndOfFileMarker, que promueve un %%EOF faltante o datos después del final (§7.5.5) de warning a error. La auditoría de xref complementa los chequeos a nivel de objeto de validar object y xref streams 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 TPdf.LastLoadReport de decir la verdad?
TPdf.LastLoadReport está completo solo tras una sobrecarga de LoadDocument que tome opciones, porque solo esas sobrecargas corren la auditoría de bytes. Un Active := True exitoso escribe un reporte en 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, muchas veces con un tranquilizador plsLoaded. Desde la v3.122.1 toda carga fallida reemplaza el reporte: un Active := True fallido, que sigue dejando el componente inactivo sin lanzar, y una llamada fallida a LoadDocument o LoadCustomDocument plana registran plsFailed con el texto del error, también sin auditoría. Dos huecos más importan en la práctica. La validación de opciones y CheckInactive corren antes de que el reporte se inicialice, así que un AuditByteLimit negativo o una instancia ya activa lanzan sin producir reporte. Y NativeErrorCode solo significa algo cuando PDFium de verdad intentó el parseo; para un archivo faltante el wrapper lanza antes de que PDFium corra, así que registre ErrorMessage y el texto de la excepción en su lugar
La regla práctica es corta. Reserve Active := True para visores atados al diseñador donde un componente inactivo es un desenlace aceptable. En todo lo demás, y sobre todo en código por lotes y de servidor, cree un TPdf por documento, llame LoadDocument(Options, Report), atrape la excepción que lanza y registre Report.Status, NativeErrorCode y los Issues de nivel error junto con el nombre del archivo. El costo son unas pocas líneas por sitio de llamada, y cada falla queda atribuida al archivo correcto con su causa real
El API de load report, el modo estricto y la auditoría a nivel de bytes vienen con el PDFium Component for Delphi, C++Builder and Lazarus, junto con rendering, extracción de texto, llenado de formularios y validación PDF/A