A ausência de recálculo em campos de formulários que funcionam no Acrobat mas permanecem estáticos no seu visualizador Delphi raramente decorre de falhas de renderização. O PDFium desativa por padrão a execução de JavaScript nos documentos, a menos que a aplicação host disponibilize uma plataforma de JS. O PDFium Component gere esta associação automaticamente (desde a versão 2.13.2), executando scripts de AcroForms, rotinas de documento e cálculos de campos sempre que a DLL com suporte a V8 estiver carregada. A aplicação mantém o controlo do processo através de eventos que permitem exibir, redirecionar ou vetar as ações do script
Esta lógica resolve falhas comuns reportadas como: "o meu formulário de encomenda calcula totais no Acrobat mas não na minha aplicação", "a rotina app.alert não é executada" ou "o campo de data não formata o valor". Todas estas situações decorrem do mesmo requisito de inicialização, cujo conhecimento é fundamental uma vez que define também as políticas de segurança contra ficheiros corrompidos
Por que razão os cálculos de formulários PDF não funcionam no PDFium?
O PDFium trata a execução de JavaScript nos documentos como opcional: se o membro m_pJsPlatform do ambiente de formulários for NULL, todos os scripts são omitidos. Sem mensagens de erro ou execuções parciais. Os campos calculados retêm o último valor gravado, a chamada app.alert é ignorada e rotinas de formatação não são executadas. O Acrobat, que inclui o seu próprio motor de JavaScript, executa as instruções corretamente, o que gera a falsa impressão de uma falha na sua aplicação (quando se trata de um comportamento padrão do PDFium)
O PDFium Component continha ainda outra limitação histórica: até à versão 2.13.1, a associação de m_pJsPlatform ocorreu apenas na inicialização de formulários XFA. Assim, documentos AcroForms convencionais (a maioria dos PDFs com scripts) não acediam à plataforma de JavaScript mesmo com a DLL V8 disponível. A partir da versão 2.13.2, esta lógica foi separada: o método InitializeFormFill configura o mapeamento IPDF_JsPlatform sempre que V8FeaturesAvailable for verdadeiro. Desta forma, scripts de nível de documento, cadeias de cálculo e a rotina app.alert funcionam também em AcroForms comuns
Qual a DLL V8 necessária para JavaScript em AcroForms no Delphi?
O JavaScript de AcroForms requer a DLL do PDFium compilada com o motor V8, que o PDFium Component carrega sob o nome pdfium.v8.dll se a propriedade global EnableV8Engine for verdadeira antes do carregamento inicial da biblioteca. Nota importante: o componente deteta automaticamente formulários XFA analisando a presença de marcas /XFA e ativando EnableV8Engine, mas documentos AcroForms com scripts não possuem esta marca, pelo que não são identificados. Se a sua aplicação necessitar de executar scripts em formulários, defina a propriedade manualmente antes de carregar o primeiro documento (uma vez carregada a DLL padrão pdfium.dll no processo, não é possível alternar motores)
uses PDFium;
procedure TViewerForm.FormCreate(Sender: TObject);
begin
// Deve ser executado antes do carregamento inicial da biblioteca PDFium;
// após carregar a DLL padrão pdfium.dll, não é possível alternar
EnableV8Engine := True;
end;
procedure TViewerForm.OpenDocument(const FileName: string);
begin
FPdf.LoadDocument(FileName);
if not V8FeaturesAvailable then
StatusBar.SimpleText :=
'JavaScript desativado: pdfium.v8.dll não disponível';
end;
A função V8FeaturesAvailable permite validar o estado em tempo de execução: indica se a DLL carregada expõe as rotinas de V8, independentemente do ficheiro distribuído pelo instalador. O mesmo motor suporta também o desenho de formulários XFA dinâmicos, nos termos abordados no artigo sobre deteção e extração de pacotes XFA
Eventos do host para alertas, impressão, correio e submissão
As ações do script são encaminhadas para a sua aplicação através de eventos de TPdf, associados por padrão a rotinas vazias seguras. O evento OnJavaScriptAlert recebe a mensagem, título, tipo de botão e ícone de app.alert (permitindo retornar o botão premido), OnJavaScriptResponse gere pedidos de texto de app.response (incluindo máscaras de palavra-passe), e OnJavaScriptBeep, OnJavaScriptPrint, OnJavaScriptMail e OnJavaScriptSubmitForm expõem pedidos de som, impressão, correio e submissão. Se omitir estes eventos, os cálculos e renderização funcionam normalmente, mas o documento não poderá abrir caixas de diálogo, imprimir ou transmitir dados
procedure TViewerForm.PdfJavaScriptAlert(Sender: TObject;
const Msg, Title: WString; ButtonType, Icon: Integer;
var Result_: Integer; var Handled: Boolean);
begin
// Encaminhar app.alert para a VCL para que a janela seja proprietária do diálogo
Result_ := MessageBox(Handle, PWideChar(Msg), PWideChar(Title),
MB_OK or MB_ICONINFORMATION);
Handled := True;
end;
procedure TViewerForm.PdfJavaScriptSubmitForm(Sender: TObject;
const Url: WString; const Data: TBytes);
begin
// Nenhuma informação é enviada a menos que a rotina de eventos decida transmitir
LogAudit(Format('O documento solicitou a submissão do formulário para %s (%d bytes)',
[Url, Length(Data)]));
end;
Esta separação de comportamentos é importante para a segurança. Os eventos OnJavaScriptMail e OnJavaScriptSubmitForm funcionam apenas como notificações: o PDFium Component não efetua chamadas de rede ou MAPI por si só, pelo que chamadas maliciosas a this.submitForm ou this.mailDoc não terão qualquer efeito se a aplicação host não implementar a lógica de rede. A transmissão de dados requer sempre o desenvolvimento das rotinas de envio no código da sua aplicação
Como impedir que ficheiros PDF maliciosos abram links?
Até à versão 2.13.1, a rotina FFI_DoURIActionWithKeyboardModifier executava diretamente qualquer URI fornecido por campos XFA via ShellExecuteW, o que permitia a abertura de links externos em cliques. O evento OnXfaUriAction corrige este comportamento: o componente consulta o evento antes da execução e, se a propriedade Handled for definida como verdadeira, o comportamento de ShellExecuteW é cancelado. A ausência de associação do evento mantém a lógica anterior por compatibilidade, sendo recomendável implementá-la em visualizadores que exijam segurança acrescida:
procedure TViewerForm.PdfXfaUriAction(Sender: TObject;
const Uri: WString; Modifiers: Integer; var Handled: Boolean);
begin
// Rejeitar qualquer protocolo diferente de https; caso contrário,
// a biblioteca executaria a chamada ShellExecuteW direta
if not SameText(Copy(Uri, 1, 8), 'https://') then
begin
LogAudit('Ação de URI bloqueada: ' + Uri);
Handled := True;
end;
end;
Recomenda-se criar listas de permissões para protocolos; abordagens mais seguras solicitam confirmação ao utilizador ou utilizam browsers embutidos em sandbox. Esta lógica de controlo aplica-se também aos eventos de v2.13.1: OnXfaEmail, OnXfaHttpRequest e OnXfaOpenFile expõem os parâmetros dos pedidos sem que o componente execute ações de rede ou ficheiros autónomas. Estes controlos integram-se na arquitetura analisada no artigo sobre visualização segura de ficheiros PDF em Delphi
Escrever no campo ativo com SetFocusedFormFieldText
O método TPdf.SetFocusedFormFieldText (desde a versão 2.13.2) complementa o campo FocusedFormFieldText: seleciona o texto ativo via FORM_SelectAllText e substitui-o por FORM_ReplaceSelection, emulando a introdução de dados pelo utilizador. Visto que a escrita acede ao fluxo de edição interativo, são executados os scripts de validação e cálculo associados ao campo, aspeto importante no preenchimento automatizado com JavaScript ativo. A navegação de foco é abordada no artigo sobre foco e navegação de campos de formulários:
if FPdf.FocusedFormFieldIndex >= 0 then
begin
if not FPdf.SetFocusedFormFieldText('42.50') then
ShowMessage('Nenhum campo ativo aceita escrita nesta página');
// AcroForm: o valor é gravado em /V quando o foco deixa o campo.
// XFA: a alteração é mantida apenas no buffer em memória e
// não é persistida na gravação
end;
Esta diferença de comportamento é estrutural. Em campos de texto e listas AcroForms, o valor editado é persistido na chave /V ao perder o foco e gravado no ficheiro. Em formulários XFA, as alterações afetam apenas o buffer em memória, uma vez que o PDFium não expõe rotinas públicas para sincronizar os controlos visuais com a secção de dados datasets do XFA, pelo que a alteração perde-se ao gravar. Para edições persistentes em XFA, altere diretamente a estrutura XML de datasets em vez do controlo visual
Limitações antes de implementar em produção
Limitações importantes: primeiro, a API de JavaScript implementada no PDFium consiste num subconjunto focado no controlo de formulários (como acesso a campos, cálculos, formatações, caixas app.alert/app.response e pedidos de correio ou submissão). Ficheiros com scripts complexos e exclusivos do Acrobat apresentarão limitações (frequentemente silenciosas), sendo recomendável testar a compatibilidade dos formulários. Segundo, o suporte a V8 obriga a distribuir o ficheiro pdfium.v8.dll, cuja dimensão é superior à da DLL padrão. Visualizadores que dispensem XFA ou scripts podem optar pela DLL mais leve sem associar a plataforma de JS, reduzindo a superfície de exposição a falhas
Recomendações de arquitetura: inicializar EnableV8Engine no arranque, associar os eventos de alerta e resposta a diálogos nativos, gerir o evento OnXfaUriAction e auditar pedidos de correio ou submissão, mantendo os restantes elementos inativos até ser necessário. A documentação das rotinas de eventos integra a página do PDFium Component