O PDFlibPas dá a desenvolvedores Delphi e C++Builder três tipos de ação para navegação que deixa a página atual para trás: GoToR (Go To Remote) abre uma página específica em outro arquivo PDF, GoToE (Go To Embedded) abre um arquivo PDF embutido dentro do documento atual, e Launch roda um programa externo ou abre um arquivo através do shell do sistema operacional. Todas as três vivem na ISO 32000-1 §12.6.4, a seção de Tipos de Ação que também define a ação GoTo cotidiana, e cada uma carrega sua própria armadilha para o desavisado: um número de página que significa algo diferente dependendo de qual chamada o constrói, um alvo que é um nome, e não um caminho de arquivo, e um par de parâmetros de string que parecem idênticos, mas servem a dois visualizadores diferentes
Nada disso é hipotético. Um pacote de referência técnica — um manual principal, um PDF de especificações que um distribuidor atualiza em seu próprio cronograma, um utilitário de calibração instalado ao lado dos dois — se apoia exatamente nesse tipo de fiação entre documentos: uma referência cruzada que precisa pousar na página 5 do arquivo de especificações, uma folha de dados que vale a pena enviar dentro do manual, em vez de ao lado dele, um link que passa direto para a ferramenta de calibração. Este artigo é a imagem espelhada de ler ações de marcador e anotação de volta de um PDF existente: aquela peça cobre consumir uma ação GoToR, Launch, ou GoToE que outro produtor já escreveu em um arquivo; esta cobre construir esses mesmos três tipos de ação do zero, incluindo as regras em nível de campo que o PDFlibPas impõe antes de comprometer um único byte
Três formas de uma ação de PDF deixar a página atual
O PDFlibPas separa a navegação local de tudo mais na chave /S da ação, e GoToR, GoToE, e Launch são os três subtipos cujo alvo fica fora da página atual: GoToR sob a ISO 32000-1 §12.6.4.3, GoToE sob §12.6.4.4, e Launch sob §12.6.4.5, todos dentro da seção mais ampla §12.6.4 Tipos de Ação que também define a ação GoTo cotidiana. O destino de uma ação GoTo simples nomeia um objeto de página que já existe dentro do documento, então o PDFlibPas consegue validá-lo imediatamente; GoToR e GoToE não conseguem fazer isso da mesma forma, já que o arquivo externo pode nem existir nesta máquina e a contagem de página de um arquivo embutido não é algo que o documento hospedeiro rastreia, então ambos carregam uma referência não resolvida, em vez de um link rígido — uma especificação de arquivo mais um destino para GoToR, um nome de arquivo embutido mais uma página alvo para GoToE — enquanto Launch descarta completamente o conceito de destino e apenas nomeia algo para o sistema operacional rodar ou abrir. Essa divisão aparece como duas famílias de chamada do lado de escrita: construtores de alto nível, de uma etapa, como AddLinkToFile, AddLinkToFileEx, AddLinkToEmbeddedPDF, e AddLinkToLocalFile criam uma anotação de link de hotspot de página e sua ação juntos, cobrindo a maioria dos layouts reais — uma linha de texto ou um ícone em que um leitor clica — enquanto setters de nível mais baixo, como SetActionRemoteDestinationEx, SetActionLaunchOptions, e suas contrapartes AddActionNext* anexam ou substituem uma ação em algo que você já tem um handle: um marcador existente, um gatilho de campo de formulário, ou um evento de ciclo de vida em nível de documento ou página. Ambas as famílias acabam escrevendo as mesmas formas de dicionário; a diferença é onde você está parado quando as chama, e, como a próxima seção cobre, o que um número de página significa quando você o faz
Como você constrói um link GoToR que abre uma página em outro arquivo PDF?
Uma ação GoToR precisa de duas coisas — uma especificação de arquivo e um destino dentro desse arquivo — e o PDFlibPas expõe duas chamadas diferentes para fornecer a segunda parte, cada uma com sua própria convenção de numeração de página. AddLinkToFile e AddLinkToFileEx, os construtores de hotspot de página de alto nível, validam seu argumento Page ou DestPage como maior que zero, a mesma numeração baseada em 1 que o PDFlibPas usa em todo lugar mais, incluindo SelectPage. SetActionRemoteDestinationEx, o setter de nível mais baixo usado para anexar ou substituir uma ação GoToR em algo que você já tem um handle, em vez disso valida DestPage como maior ou igual a zero e o escreve diretamente no array de destino explícito da ação sem nenhum ajuste: ele quer o índice de página bruto, baseado em zero, do documento alvo, a numeração que a ISO 32000-1 especifica para um destino explícito remoto. Chame o setter de baixo nível com o mesmo número que você entregaria ao construtor de alto nível e o link abre uma página cedo demais
var
Lib: TPDFlib;
ActionID: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('manual.pdf', '') = 1 then
begin
Lib.SelectPage(12);
// Page is 1-based here, same as SelectPage above: this opens
// the fifth page of specs.pdf.
Lib.AddLinkToFile(72, 700, 200, 16, 'specs.pdf', 5, 0, 0, 0);
// A later maintenance pass repoints the same link at a
// reorganized file. SetActionRemoteDestinationEx edits the
// action directly, and DestPage here is the zero-based index
// PDF itself uses for a remote explicit destination -- "the
// fifth page" is now 4, not 5.
ActionID := Lib.GetAnnotActionID(1);
Lib.SetActionRemoteDestinationEx(ActionID, 'specs-2026.pdf',
4, Ord(dkFit), 0, 0, 0, 0, 0, 0, -1);
end;
finally
Lib.Free;
end;
end;
O resto dos argumentos de SetActionRemoteDestinationEx é igualmente literal. ValueMask é um conjunto de bits — 1 para esquerda, 2 para topo, 4 para direita, 8 para base, 16 para zoom — e o PDFlibPas o verifica contra DestType antes de escrever qualquer coisa: um destino dkFitR precisa fornecer exatamente 15 (todas as quatro bordas, sem zoom), dkFit e dkFitB precisam fornecer 0, e dkFitH/dkFitV aceitam apenas sua única coordenada relevante. Bits que você deixa não definidos dentro de uma máscara por outro lado válida não são omitidos do array; são escritos como um null explícito de PDF, que a ISO 32000-1 trata como "mantenha qualquer valor que o visualizador já tenha" para aquela coordenada — uma forma legítima de dizer "salte para esta página, deixe o zoom em paz", em vez de um descuido. O próprio zoom é armazenado como uma fração do valor que você passa, então uma chamada pedindo 150 por cento entrega ao array um valor armazenado de 1.5, e a faixa de entrada válida é 0 a 6400
Como você faz link para um PDF embutido dentro do seu próprio documento?
AddLinkToEmbeddedPDF constrói a ação GoToE, e seu argumento alvo, EmbeddedFileName, é um nome, e não um caminho: precisa corresponder à string Title já passada para EmbedFile quando o anexo foi feito, porque esse título é a chave literal que o PDFlibPas armazena na árvore de nomes /EmbeddedFiles do documento, e GoToE resolve procurando esse nome, não tocando o sistema de arquivos novamente. A função só verifica se EmbeddedFileName não está vazio e se TargetPage é pelo menos 1 — passe um nome que nunca foi de fato embutido e a chamada ainda retorna sucesso, a ação ainda é escrita, e o link simplesmente falha ao se resolver para todo leitor que clica nele
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.NewDocument;
Lib.NewPage;
// The Title argument becomes the key PDFlibPas stores in the
// document's EmbeddedFiles name tree -- that string, not
// "datasheet.pdf", is the target GoToE resolves against.
if Lib.EmbedFile('Datasheet', 'datasheet.pdf', 'application/pdf') = 1 then
Lib.AddLinkToEmbeddedPDF(72, 700, 200, 16, 'Datasheet', 3, 0, 0);
Lib.SaveToFile('manual.pdf');
finally
Lib.Free;
end;
end;
Dois pisos de versão se empilham aqui, não um. EmbedFile precisa de PDF 1.4 para a árvore de nomes /EmbeddedFiles, e AddLinkToEmbeddedPDF separadamente eleva o piso para PDF 1.6 para o próprio tipo de ação GoToE, então o mínimo efetivo para qualquer documento que use esse recurso é 1.6, não 1.4. Note também que TargetPage aqui é baseado em 1, a convenção comum do PDFlibPas — um contraste deliberado com o DestPage baseado em zero que a seção anterior acabou de cobrir, e um lembrete de que qual esquema de numeração de página se aplica depende do tipo de ação e da chamada específica, não de uma regra geral única. O dicionário de destino da ação também pode carregar uma entrada /R de C para filho ou P para pai, suportando uma cadeia de dois saltos em um arquivo embutido ou de volta para seu contêiner, embora AddLinkToEmbeddedPDF só construa a direção filho, já que é a que faz sentido a partir de um documento que está fazendo o embutimento, em vez de sendo embutido
Ações Launch: um FileName, dois alvos de string que não são intercambiáveis
SetActionLaunchOptions escreve o alvo de arquivo de uma ação Launch em duas chaves diferentes a partir de um único argumento FileName, e as duas chaves guardam dois tipos diferentes de string. A chave de nível superior /F recebe um dicionário de especificação de arquivo, construído pela mesma conversão de caminho que o PDFlibPas usa para GoToR, que é a forma portátil que a ISO 32000-1 §7.11.3 define para um dicionário de especificação de arquivo. O sub-dicionário /Win, quando o PDFlibPas escreve um, recebe sua própria chave /F definida para o valor bruto de FileName exatamente como passado, sem nenhuma conversão, porque /Win /F é documentado na ISO 32000-1 §12.6.4.5 como uma string de caminho Windows simples destinada apenas a ser lida por um visualizador Windows. Passe um caminho portátil, já convertido, esperando que ambas as chaves acabem idênticas e a cópia /Win vai carregar o que quer que você tenha entregue à função, intocado
var
Lib: TPDFlib;
ActionID: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('manual.pdf', '') = 1 then
begin
Lib.SelectPage(1);
Lib.AddLinkToLocalFile(72, 660, 220, 16, 'calibrate.exe', 0);
ActionID := Lib.GetAnnotActionID(1);
// Operation 0 leaves this as a normal open -- pass 1 to ask a
// Windows viewer to print instead. Parameters and
// DefaultDirectory only ever reach /Win /P and /Win /D, never
// the top-level /F.
Lib.SetActionLaunchOptions(ActionID, 'calibrate.exe',
'/silent /profile:default', 'C:\Tools\Calibration', 0, -1);
end;
finally
Lib.Free;
end;
end;
Trate Launch como a ação de maior atrito das três, porque todo seu propósito é rodar um programa ou abrir um arquivo fora do sandbox do PDF, e todo visualizador comum a trata de acordo. A Enhanced Security do Adobe Acrobat bloqueia ou pergunta em ações Launch por padrão, a menos que o alvo esteja em uma localização explicitamente confiável, e a maioria das implantações corporativas do Acrobat deixa essa proteção ligada. Uma ação Launch em um documento entregue ao público, portanto, não é um gatilho confiável: planeje que seja bloqueada, perguntada, ou silenciosamente ignorada por qualquer visualizador que abra o arquivo, e guarde-a para ambientes fechados onde você também controla as configurações de confiança do visualizador — um quiosque interno, um lançamento corporativo controlado, um documento que nunca sai de uma máquina que você gerencia
O portão PDF/A: por que chamadas GoToR e Launch podem retornar zero
SetActionRemoteDestinationEx e SetActionLaunchOptions ambos se recusam completamente quando o documento alvo está em qualquer modo de conformidade PDF/A: ambos verificam o modo PDF/A do documento como sua primeiríssima condição e saem com um resultado de 0 antes de tocar a ação, nenhuma exceção levantada. Isso é deliberado. As restrições do PDF/A sobre ações interativas excluem Launch especificamente, já que dar a um arquivo arquivístico a capacidade de rodar um programa arbitrário é exatamente o tipo de comportamento dependente de ambiente que formatos de arquivamento de longo prazo existem para prevenir, e o PDFlibPas aplica o mesmo portão conservador ao setter de ir-para remoto no mesmo caminho de código. A consequência prática é fácil de perder durante o desenvolvimento: a chamada idêntica que funciona em um PDF comum vai compilar, rodar, e silenciosamente não fazer nada em um documento carregado com um nível de conformidade PDF/A definido, então verifique o valor de retorno em vez de assumir sucesso — um 0 aqui não é um erro de entrada malformada, é a biblioteca se recusando a uma requisição que conflita com a própria declaração de conformidade do documento
Onde GoToR, GoToE, e Launch se encaixam em um fluxo de trabalho maior do PDFlibPas
Os três tipos de ação neste artigo não alcançam todos os mesmos lugares. O artigo complementar sobre gatilhos de ação de ciclo de vida de documento e página cobre SetDocumentAction e SetPageAction, que podem anexar uma ação GoToR ou Launch a um gatilho como WillClose por meio das constantes compartilhadas PDF_ACTION_BUILDER_REMOTE_DESTINATION e PDF_ACTION_BUILDER_LAUNCH — o mesmo construtor que também cobre um gatilho de URI simples ou JavaScript. GoToE não tem essa constante e nenhum caminho para esse construtor genérico de forma alguma; AddLinkToEmbeddedPDF é a única forma pela qual o PDFlibPas constrói uma, o que a torna estritamente uma ação de hotspot de página, nunca um gatilho em nível de documento ou página. Onde GoToR e Launch de fato alcançam o construtor genérico, a compensação é controle: ele constrói um GoToR apontando apenas para um destino remoto nomeado e uma ação Launch com apenas um nome de arquivo e parâmetros, enquanto o endereçamento explícito de página e tipo de ajuste e as opções de launch específicas do Windows cobertas neste artigo só são alcançadas por meio de SetActionRemoteDestinationEx e SetActionLaunchOptions diretamente
Uma propriedade de segurança vale a pena conhecer antes de construir uma ferramenta de manutenção em torno desses setters. SetActionRemoteDestinationEx e SetActionLaunchOptions constroem toda a ação de substituição em um dicionário de rascunho primeiro, e só excluem e copiam as chaves /F, /D ou /Win, e /NewWindow para a ação ao vivo uma vez que essa cópia de rascunho se valida — de modo que uma chamada que falha na validação, seja por um ValueMask fora da faixa ou um FileName vazio, deixa a ação original, e qualquer cadeia /Next já pendurada nela, completamente intocada, em vez de meio sobrescrita. Isso importa porque ações GoToR e Launch podem ambas sentar dentro de uma cadeia /Next construída com AddActionNextRemoteDestinationEx, AddActionNextLaunchEx, ou o mais geral AddActionNextEx, deixando um único gatilho disparar uma entrada de log JavaScript e depois um salto remoto em sequência. A construção de GoToR, GoToE, e Launch descrita aqui faz parte do PDFlibPas, a biblioteca PDF nativa para Delphi e C++Builder