Anotações

Adicionar suporte a anotações no visualizador

Anotações no Doconut funcionam em duas direções: os usuários as desenham no widget do navegador e o servidor as persiste por página, ou seu código as cria programaticamente e as carrega em uma sessão aberta. De qualquer forma, elas são renderizadas nas páginas e podem ser incorporadas em exportações PDF/PNG.

O suporte a anotações é controlado pela capacidade de licença Annotation (concedida automaticamente sob uma licença Temporária ativa).

Ativar a interface de anotação

Anotação é um módulo do Viewer, não uma barra de ferramentas independente. A página completa deve incluir os recursos do Viewer, a barra de ferramentas do Viewer, a montagem do Viewer e o objViewer inicializado; o Ribbon de Anotação é então montado e anexado à mesma instância.

Emita os pacotes de anotação junto com os pacotes do viewer — eles são controlados por licença, portanto as tags só aparecem quando a capacidade está disponível:

html
@Html.Raw(Viewer.ReferenceCss(new CssConfig
{
    IncludeViewerCss     = true,
    IncludeAnnotationCss = true   // jquery-ui.min.css + annotationBar.css
}))

@Html.Raw(Viewer.ReferenceScripts(new ScriptConfig
{
    IncludeJQuery             = true,
    IncludeViewerScripts      = true,
    IncludeAnnotationScripts  = true, // jquery-ui, raphael.js, annotation.js
    IncludeAnnotationBar      = true  // the embedded annotation ribbon
}))

Mantenha a composição completa do Viewer visível no markup:

html
<nav id="toolbar" aria-label="Document viewer controls">
    <!-- Controles do visualizador, incluindo o botão que abre Annotation -->
</nav>
<div id="annBarMount"></div>
<div id="divDocViewer"><div id="div_ctlDoc"></div></div>

O pacote de Anotação gera o DOM do Ribbon dentro de annBarMount; você não precisa copiar seus botões ou o markup do diálogo. Inicialize docViewer primeiro, depois crie o Ribbon somente quando o servidor confirmar que Anotação está licenciado:

html
<script>
    let annBar = null;
    let currentToken = '';

    const objViewer = $('#div_ctlDoc').docViewer({
        BasePath: 'doconut',
        ResPath: 'doconut-res/images',
        onAnnLoaded:    () => annBar?.handleAnnLoaded(),
        onAnnSaved:     () => annBar?.handleAnnSaved(),
        onAnnSaveError: () => annBar?.handleAnnSaveError(),
        onAnnClosed:    () => annBar?.handleAnnClosed(),
        onError:        (message) => console.error('Viewer error:', message)
    });

    @if (Viewer.IsAnnotationEnabled)
    {
        <text>
    annBar = $('#annBarMount').doconutAnnotationBar({
        docId: 'ctlDoc',
        getRequestParams: () => ({ token: currentToken }),
        onStatus: (message) => console.log(message),
        onToast: (message, type) => console.log(type, message),
        onLayout: () => requestAnimationFrame(() => objViewer.Refit())
    });
    annBar.attach(objViewer);
        </text>
    }
</script>

Salvar a partir do Ribbon envia os dados através do middleware (AnnSave), que os armazena na sessão do documento por página. Carregar (AnnLoad) ocorre automaticamente quando uma página com anotações é renderizada. Os quatro callbacks onAnn* mantêm o Ribbon sincronizado com o ciclo de vida do viewer.

Abra e feche‑o a partir de qualquer barra de ferramentas do Viewer controlada pelo host:

javascript
annBar.open();
annBar.close();

A API pública do Ribbon é:

MétodoObjetivo
attach(objViewer)Conecta o Ribbon ao visualizador inicializado; necessário uma vez
open() / close()Inicia ou encerra a edição de anotações
reset()Retorna o Ribbon ao estado fechado, sem edição
isOpen() / annotating()Lê o estado do Ribbon / o estado de edição de anotações do visualizador
reopenEditable()Recarrega as anotações da página atual como objetos editáveis
updateActionState()Atualiza a disponibilidade dos controles de salvar/excluir após alterações no host
headerSlot()Obtém o slot opcional de extensão de cabeçalho para controles do host

onStatus, onToast, onLayout, onEditStart e onEditEnd são callbacks opcionais do host. O objeto endpoints pode ainda fornecer exportPdf, exportPng, imageUpload e imageList; controles sem um endpoint configurado permanecem ocultos. Para a sequência de inicialização combinada de Viewer, Pesquisa e Anotação, veja Início Rápido.

O pacote de Anotação adiciona as ferramentas de autoria no navegador, mas os dados ainda pertencem à sessão de documento do lado do servidor identificada pelo token. Reabrir a fonte cria uma nova sessão; persista o XML ou o envelope de anotação codificado em sua aplicação se as anotações precisarem sobreviver além da vida útil da sessão.

Construir anotações em C#

Obtenha um gerenciador vinculado à sessão aberta, adicione anotações e carregue-as (com using Doconut.Annotations; para os tipos e using System.Drawing; para Rectangle/Color):

csharp
app.MapPost("/api/annotations/load-sample", (string token, Viewer viewer) =>
{
    // Vinculado às dimensões da página da sessão aberta
    var manager = viewer.GetAnnotationManager(token);
    var pageCount = viewer.GetPageCount(token);

    // Um carimbo por página
    for (int page = 1; page <= pageCount; page++)
    {
        manager.Add(new StampAnnotation(page, new Rectangle(30, 20, 240, 90),
            $"PAGE {page}", 28, 4, Color.Maroon)
        {
            Opacity = 60,
            Rotate  = -8
        });
    }

    manager.Add(new NoteAnnotation(1, new Rectangle(420, 150, 220, 120),
        "Loaded from C# code.", Color.FromArgb(255, 255, 255, 170), 14));

    // Carregar na sessão — o widget os obtém via AnnLoad e o
    // renderizador os incorpora nas exportações de imagem/PDF.
    viewer.LoadAnnotationData(token, manager);
    return Results.Ok();
});

Tipos de anotação

Todos os tipos estão em Doconut.Annotations e herdam de BaseAnnotation (número da página + Rectangle delimitador):

TipoObservações
StampAnnotationCarimbo de texto com tamanho de fonte, borda, cor; suporta Opacity, Rotate
NoteAnnotationNota adesiva com texto, cor de fundo, tamanho de fonte, TitleColor
RectangleAnnotationCores de borda e preenchimento, Title/ShowTitle
CircleAnnotationBorda + preenchimento, ShowBorder
EllipseAnnotationBorda + preenchimento, ShowBorder
TriangleAnnotationCor da borda, BackColor, ShowBorder
LineAnnotationLinha reta com largura e cor
ArrowAnnotationLinha com ponta de seta; Direction configurável (tipo ArrowDirection, pontos cardeais, padrão E)
FreehandAnnotationTraço livre a partir de pontos codificados FreehandData
ImageAnnotationImagem a partir de uma URL. Uma URL relativa é resolvida em relação ao host da requisição quando a anotação é adicionada (apenas a obtenção da imagem ocorre na hora da gravação) — ela deve ser acessível a partir do servidor (por exemplo, um arquivo em wwwroot servido por UseStaticFiles)

API do AnnotationManager

MembroPropósito
Add(BaseAnnotation)Enfileira uma anotação
GetAnnotations() / GetAnnotations(int page)Inspeciona o que o gerenciador contém
ClearAnnotations() / ClearAnnotations(int page)Remove tudo / por página
GetAnnotationData() / GetAnnotationData(int page)String de dados de anotação codificada — um envelope Base64 (o que o widget consome)
GetAnnotationXml()Forma XML

Viewer espelha as operações de carregamento/leitura em relação a uma sessão: LoadAnnotationData(token, manager) ou LoadAnnotationData(token, encodedData) (o envelope Base64 de GetAnnotationData()), LoadAnnotationXML(token, xml), GetAnnotationXML(token).

Exportar com anotações incorporadas

csharp
// PDF de todas as páginas com anotações renderizadas sobre elas
app.MapGet("/api/annotations/export-pdf", async (string token, Viewer viewer) =>
{
    byte[] pdf = await viewer.ExportAnnotationsToPdfAsync(token, zoom: 100);
    return Results.File(pdf, "application/pdf", "export.pdf");
});

// Ou um ZIP de PNGs por página
app.MapGet("/api/annotations/export-png-zip", async (string token, Viewer viewer) =>
{
    byte[] zip = await viewer.ExportAnnotationsToPngZipAsync(token, zoom: 100);
    return Results.File(zip, "application/zip", "annotations-png.zip");
});

As exportações usam o mesmo mecanismo de gravação da renderização na tela, portanto o que os usuários veem é o que o arquivo contém.

Fluxo de persistência

  1. Abra o documento e obtenha seu token.
  2. Carregue o XML ou os dados codificados previamente armazenados nesse token.
  3. Permita que o widget leia e edite as anotações da sessão.
  4. Recupere o XML com GetAnnotationXML(token) quando sua aplicação decidir persistir.
  5. Exporte PDF/PNG quando for necessário um entregável achatado.
  6. Feche a sessão do documento.

Não use o token opaco do visualizador como identificador permanente de anotação. Associe os dados de anotação persistidos aos seus próprios identificadores de documento e versão.

Segurança e notas de renderização

  • As solicitações de anotação usam a mesma segurança de sessão/token das solicitações de página.
  • Um URL relativo de ImageAnnotation é resolvido a partir do host da solicitação e deve permanecer acessível ao servidor no momento da renderização.
  • Valide e controle qualquer URL de imagem fornecida pelo usuário para evitar falsificação de solicitações no lado do servidor.
  • As exportações aplicam a mesma decisão de licença/marca d'água personalizada que a renderização de página na tela.
  • Grandes cargas de desenhos à mão livre e exportações em alta resolução aumentam o uso de memória; teste documentos realistas e valores de zoom.

Solução de Problemas

SintomaVerificação
Faixa de anotação está ausenteAnnotation capacidade e as quatro flags de CSS/script de anotação
Callback de salvamento relata um erroExpiração de token/sessão e middleware BasePath
Anotações C# não aparecemA numeração de páginas começa em 1 e os dados foram carregados no token ativo
Anotação de imagem aparece na tela, mas não na exportaçãoO servidor pode acessar o URL da imagem durante a renderização
Documento reaberto não tem anotaçõesPersistir XML/dados fora da sessão do visualizador, então carregá-los no novo token

Esta página foi útil?