No PDFium Component para Delphi e Lazarus, atribuir TPdf.Active := True nunca levanta quando um PDF falha ao carregar: o TPdf.SetActive captura toda exceção e deixa o componente inativo. Para ver o erro real, chame o TPdf.LoadDocument(Options, Report) em vez. Essa sobrecarga relança a exceção original e preenche um TPdfLoadReport com o status de load, o código de erro nativo do PDFium e se a tabela de cross-reference precisou ser reconstruída
O problema costuma aparecer em código batch. Um job de extração de tabelas percorre uma pasta de 13 PDFs do mundo real com um TPdf compartilhado, e 7 deles voltam como falhas. Nenhum dos 7 arquivos está de fato quebrado. Os blocos except em volta do load nunca disparam, o log culpa os nomes de arquivo errados, e o primeiro erro visível é um EPdfError pelado sobre componente inativo, levantado de uma leitura de propriedade várias linhas depois do load que de fato falhou. Dois comportamentos separados se empilham para produzir esse quadro, e ambos estão funcionando como projetados
Por que TPdf.Active := True não levanta quando um PDF falha ao carregar?
O TPdf.SetActive embrulha o LoadDocument num try..except que engole toda classe de exceção e simplesmente deixa o componente inativo. O engolir é deliberado: o mesmo setter roda quando um designer de formulários alterna o Active na IDE, e um caminho ruim não pode derrubar a IDE. Em run time o TPdf.Active só reporta se existe um handle nativo de documento, então depois de um load falho ele lê False e nada mais acontece. O quer que tenha sido levantado se foi, fosse um EPdfError do parser, um erro de stream ou um EAccessViolation de uma pdfium.dll meio vinculada. As mensagens detalhadas de DLL descritas em diagnosticar falhas de load de pdfium.dll em Delphi só chegam ao seu handler por uma chamada que não as engole
Pdf.FileName := FileName;
try
Pdf.Active := True; // SetActive engole qualquer exceção de load
except
on E: Exception do
Log.Add(FileName + ': ' + E.Message); // nunca executa
end;
// A falha aparece aqui em vez, como um 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: teste Active logo após a atribuição;
// desde a v3.122.1 o LastLoadReport guarda o texto do erro engolido
Pdf.Active := True;
if not Pdf.Active then
Log.Add(FileName + ': load failed: ' + Pdf.LastLoadReport.ErrorMessage);
A falha finalmente aparece na primeira chamada vigiada. O TPdf.PageCount, como a maioria das propriedades de documento, começa com o CheckActive, que levanta um EPdfError nomeando o componente mas não o arquivo e não a causa. Testar o Pdf.Active imediatamente depois da atribuição transforma um crash mal atribuído numa entrada honesta de "falhou". Antes do PDFiumPas v3.122.1 a razão se perdia nesse ponto; desde a v3.122.1 a atribuição falha substitui o LastLoadReport por um relatório plsFailed que carrega o texto do erro, então a causa sobrevive. O objeto de exceção em si e a auditoria a nível de byte ainda exigem um ponto de entrada diferente
Por que reutilizar um TPdf falha a partir do segundo arquivo?
O TPdf.FileName só pode ser atribuído enquanto o componente está inativo, então uma instância compartilhada rejeita o segundo arquivo antes mesmo de tentar carregá-lo. O TPdf.SetFileName começa com o CheckInactive, e o mesmo guard protege Password e FormFill. Depois do primeiro load bem-sucedido a instância fica ativa, a próxima atribuição levanta, e se o loop batch captura essa exceção e segue em frente, o erro cai sob o nome do arquivo novo enquanto o documento velho ainda está aberto. Misturado com as falhas de load engolidas, o log para de bater com a realidade. Na reprodução de 13 arquivos uma instância compartilhada reportou 7 falhas, enquanto um TPdf.Create(nil) novo por documento abriu todos os 13. Setar Active := False entre arquivos também funciona, mas uma instância por documento mantém cada arquivo isolado por construção
O que o TPdf.LoadDocument com um TPdfLoadReport dá a você?
O TPdf.LoadDocument(const Options: TPdfLoadOptions; out Report: TPdfLoadReport) levanta a exceção real e também conta o que aconteceu em forma estruturada. A sobrecarga de arquivo carrega o FileName; sobrecargas irmãs recebem TBytes ou um ponteiro e tamanho, e o LoadCustomDocument(AStream, AOwnsStream, Options, Report) cobre streams. Cada uma valida as opções, checa que a instância está inativa, roda uma auditoria a nível de byte do header, do startxref, das seções de xref e do marcador %%EOF, e então executa o load nativo. A auditoria é limitada pelo mesmo tipo de limites discutidos em orçamentos de recursos do parser para PDFs não confiáveis: o TPdfLoadOptions.Default seta AuditByteLimit para 256 MiB, MaxIssues para 256, MaxXrefSections para 1024 e MaxXrefEntries para 4.000.000. Em falha o método seta Report.Status := plsFailed e relança; como o Report é escrito in place, o conteúdo dele sobrevive à exceção, e uma cópia fica guardada no TPdf.LastLoadReport
Os campos do relatório respondem às perguntas que um log batch de fato precisa. O Status é um de plsNotAttempted, plsLoaded, plsLoadedWithRecovery, plsRejected ou plsFailed. O NativeErrorCode guarda o FPDF_GetLastError, então FPDF_ERR_PASSWORD (4) separa uma senha ausente ou errada de um arquivo danificado reportado como FPDF_ERR_FORMAT (3). UsedRecovery, CrossReferenceTableValid e RecoveryRoute dizem se o PDFium precisou reconstruir a tabela de xref, e o Issues lista cada achado da auditoria com Code, Severity, Offset, ObjectNumber e MessageText, com IssuesTruncated setado quando o MaxIssues cortou a 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); // uma instância por documento
try
Pdf.FileName := Files[I];
try
Pdf.LoadDocument(Options, Report);
except
on E: Exception do
begin
// Report é preenchido mesmo que o LoadDocument tenha levantado
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;
Quando carregar com plmStrict?
Use o plmStrict sempre que um arquivo silenciosamente reparado é pior que um rejeitado, como triagem de acervo, manuseio de evidências ou um pipeline de assinatura. O PDFium reconstrói silenciosamente uma tabela de cross-reference quebrada (ISO 32000-1 §7.5.4) varrendo o arquivo atrás de objetos, o que é ótimo para um viewer e um problema para qualquer coisa que precise processar exatamente os bytes que recebeu. Depois do load nativo, o componente pergunta ao FPDF_DocumentHasValidCrossReferenceTable. No modo plmCompatible uma reconstrução rende plsLoadedWithRecovery mais um aviso plicNativeCrossReferenceRebuild. No modo plmStrict o componente descarrega o documento, seta plsRejected, adiciona plicStrictModeRejected e levanta EPdfError com "Strict PDF load rejected the document". O modo estrito também rejeita qualquer erro de auditoria, e o TPdfLoadOptions.Default(plmStrict) liga o RequireFinalEndOfFileMarker, que promove um %%EOF ausente ou dados depois do final (§7.5.5) de aviso para erro. A auditoria de xref complementa as checagens a nível de objeto em validar object e xref streams com 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, sem erros de auditoria
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;
Onde o TPdf.LastLoadReport para de dizer a verdade?
O TPdf.LastLoadReport só está completo depois de uma sobrecarga do LoadDocument que recebe opções, porque só essas sobrecargas rodam a auditoria de bytes. Um Active := True bem-sucedido grava um relatório em modo compatível sem auditoria de bytes, então o AuditAttempted fica False. Antes do PDFiumPas v3.122.1 um falho não gravava nada, o que significava que numa instância compartilhada o LastLoadReport ainda descrevia o arquivo anterior, muitas vezes com um tranquilizador plsLoaded. Desde a v3.122.1 todo load falho substitui o relatório: um Active := True falho, que ainda deixa o componente inativo sem levantar, e uma chamada falha de LoadDocument ou LoadCustomDocument simples registram plsFailed com o texto do erro, de novo sem auditoria. Mais dois vazios importam na prática. A validação de opções e o CheckInactive rodam antes de o relatório ser inicializado, então um AuditByteLimit negativo ou uma instância já ativa levanta sem produzir relatório. E o NativeErrorCode só significa algo quando o PDFium de fato tentou o parse; para um arquivo ausente o wrapper levanta antes de o PDFium rodar, então logue o ErrorMessage e o texto da exceção em vez
A regra prática é curta. Guarde o Active := True para viewers ligados a designer em que um componente inativo é um desfecho aceitável. Em todo o resto, e sobretudo em código batch e de servidor, crie um TPdf por documento, chame o LoadDocument(Options, Report), capture a exceção que ele levanta e logue Report.Status, NativeErrorCode e os Issues de nível erro junto com o nome do arquivo. O custo é umas linhas por call site, e toda falha é atribuída ao arquivo certo com a causa real
A API de load report, o modo estrito e a auditoria a nível de byte vão com o PDFium Component para Delphi, C++Builder e Lazarus, junto com renderização, extração de texto, preenchimento de formulários e validação PDF/A