Anotações

Adicionar suporte a anotações ao visualizador

As anotações no Doconut funcionam em duas direções: os usuários as desenham no widget do navegador e o servidor as mantém por página, ou seu código as cria 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 ou 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ções

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

Emita os arquivos de anotação junto com os arquivos do visualizador. Eles são controlados por licença, portanto suas tags aparecem somente quando a capacidade é concedida. No MVC 5, construa as tags no controlador (as visualizações não possuem @inject):

csharp
var viewer = Doconut.DoconutHost.Services.GetRequiredService<Viewer>();
ViewBag.ViewerCss = viewer.ReferenceCss(new CssConfig
{
    IncludeViewerCss     = true,
    IncludeAnnotationCss = true    // jquery-ui.min.css + annotationBar.css
});
ViewBag.ViewerScripts = viewer.ReferenceScripts(new ScriptConfig
{
    IncludeJQuery            = true,
    IncludeViewerScripts     = true,
    IncludeAnnotationScripts = true,  // jquery-ui, raphael.js, annotation.js
    IncludeAnnotationBar     = true   // the embedded annotation ribbon
});
ViewBag.IsAnnotationEnabled = viewer.IsAnnotationEnabled;

Mantenha a composição completa do Viewer na marcação:

html
<nav id="toolbar" aria-label="Document viewer controls">
    <!-- Viewer controls, including the button that opens Annotation -->
</nav>
<div id="annBarMount"></div>
<div id="divDocViewer"><div id="div_ctlDoc"></div></div>

Os arquivos de Anotação geram a Ribbon dentro de annBarMount; você não copia seus botões ou diálogos. Inicialize docViewer primeiro, depois crie a Ribbon somente quando o servidor confirmar que a Anotação está licenciada:

html
@Html.Raw(ViewBag.ViewerScripts)
<script>
    var annBar = null;
    var currentToken = '';

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

    @if ((bool)ViewBag.IsAnnotationEnabled)
    {
        <text>
    annBar = $('#annBarMount').doconutAnnotationBar({
        docId: 'ctlDoc',
        getRequestParams: function () { return { token: currentToken }; },
        onLayout: function () { requestAnimationFrame(function () { objViewer.Refit(); }); }
    });
    annBar.attach(objViewer);
        </text>
    }
</script>

Sem a capacidade, ReferenceScripts omite o script da Ribbon, de modo que o if do lado do servidor impede que uma chamada desprotegida a doconutAnnotationBar(...) quebre a página. No Web Forms, escreva a mesma proteção como <% if (IsAnnotationEnabled) { %> … <% } %>.

Salvar a partir da Ribbon envia os dados para DocImage.axd (AnnSave), que os armazena na sessão do documento por página. O carregamento (AnnLoad) ocorre automaticamente quando uma página com anotações é renderizada.

Abra e feche a Ribbon a partir da sua própria barra de ferramentas com annBar.open() e annBar.close(). Sua API pública:

MétodoObjetivo
attach(objViewer)Conecta a Ribbon ao visualizador inicializado; necessário uma única vez
open() / close()Inicia ou encerra a edição de anotações
reset()Retorna a Ribbon ao estado fechado, sem edição
isOpen() / annotating()Lê o estado da 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 os controles de salvar e excluir após alterações no host
headerSlot()O slot de cabeçalho opcional para controles de propriedade do host

onStatus, onToast, onLayout, onEditStart e onEditEnd são callbacks opcionais do host. O objeto endpoints também pode fornecer exportPdf, exportPng, imageUpload e imageList; controles sem um endpoint configurado permanecem ocultos.

Dois tipos de callback, conectados de duas maneiras

O código de anotação usa dois estilos de callback, e cada um falha silenciosamente quando conectado da outra maneira:

  • onAnnLoaded, onAnnSaved, onAnnSaveError, onAnnClosed são opções de docViewer({...}), como todo callback de visualizador.
  • Created, Changed, Deleted e Properties são funções globais encontradas por nome: o id do visualizador precede o evento. Para o visualizador em #div_ctlDoc o id é ctlDoc, então as funções são ctlDoc_Created, ctlDoc_Changed, ctlDoc_Deleted e ctlDoc_Properties. Uma função escrita incorretamente ou renomeada simplesmente nunca é chamada.

SaveAnnotations() não retorna se salvou: o resultado chega através de onAnnSaved ou onAnnSaveError. O código que lê seu valor de retorno nunca limpa a bandeira de "alterações não salvas".

Criar 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 e Color):

csharp
// POST /Annotations/LoadSample?token=...
[HttpPost]
public ActionResult LoadSample(string token)
{
    var viewer = Doconut.DoconutHost.Services.GetRequiredService<Viewer>();

    // Bound to the open session's page dimensions
    var manager   = viewer.GetAnnotationManager(token);
    var pageCount = viewer.GetPageCount(token);

    // One stamp per page
    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));

    // Load into the session - the viewer fetches them, and the renderer burns them into
    // image/PDF exports.
    viewer.LoadAnnotationData(token, manager);
    return new HttpStatusCodeResult(200);
}

Tipos de anotação

Todos os tipos estão em Doconut.Annotations e herdam de BaseAnnotation (número da página mais o 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 e preenchimento, ShowBorder
EllipseAnnotationBorda e preenchimento, ShowBorder
TriangleAnnotationCor da borda, BackColor, ShowBorder
LineAnnotationLinha reta com espessura e cor
ArrowAnnotationLinha com ponta de seta; Direction configurável (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 atual quando a anotação é adicionada — veja a nota de exportação abaixo

A 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 todas / por página
GetAnnotationData() / GetAnnotationData(int page)Dados de anotação codificados — o envelope que o widget consome
GetAnnotationXml()Forma XML

Viewer espelha as operações de carregamento e leitura contra uma sessão: LoadAnnotationData(token, manager) ou LoadAnnotationData(token, encodedData), LoadAnnotationXML(token, xml), e GetAnnotationXML(token).

Exportar com anotações incorporadas

csharp
// PDF of all pages with annotations rendered onto them
public async Task<ActionResult> ExportPdf(string token)
{
    var viewer = Doconut.DoconutHost.Services.GetRequiredService<Viewer>();
    byte[] pdf = await viewer.ExportAnnotationsToPdfAsync(token, zoom: 100, ct: Response.ClientDisconnectedToken);
    return File(pdf, "application/pdf", "export.pdf");
}

// Or a ZIP of per-page PNGs
public async Task<ActionResult> ExportPngZip(string token)
{
    var viewer = Doconut.DoconutHost.Services.GetRequiredService<Viewer>();
    byte[] zip = await viewer.ExportAnnotationsToPngZipAsync(token, zoom: 100, ct: Response.ClientDisconnectedToken);
    return File(zip, "application/zip", "annotations-png.zip");
}

As exportações usam o mesmo queimador que a renderização na tela, e a mesma decisão de licença e marca d'água personalizada.

Anotações de imagem em um host intranet exportam como "Imagem bloqueada"

O visualizador e a exportação buscam a imagem de uma ImageAnnotation em locais diferentes:

SuperfícieQuem baixa a imagemEm um host intranet ou de desenvolvimento
O visualizador no navegadorO navegador do usuárioA imagem aparece
ExportAnnotationsToPdfAsync / …PngZipAsyncO servidorUm placeholder vermelho "Imagem bloqueada"

Quando o servidor busca uma imagem para incorporá‑la, ele rejeita endereços que apontam de volta para redes privadas: loopback (localhost, 127.0.0.1), 0.0.0.0, link‑local (169.254.x.x), os intervalos privados 10.x, 172.16–31.x e 192.168.x, NAT de operadora (100.64.0.0/10) e endereços IPv6 link‑local e unique‑local. Isso protege o servidor de ser usado para alcançar sistemas internos através de uma URL de imagem manipulada, e não há uma opção para desativá‑lo.

Um URL de imagem relativo é resolvido em relação ao host da requisição atual, portanto em localhost, em um servidor intranet ou em qualquer site acessado por um endereço privado, a imagem é exibida no visualizador e a exportação contém o placeholder em vez disso. Isso é a proteção funcionando, não uma falha de renderização. Se as exportações precisam conter a imagem, sirva‑a a partir de um host com endereço público e use um URL absoluto. Um URL de imagem que não pode ser resolvido de forma alguma faz com que aquela anotação desapareça sem erro — verifique os pixels renderizados ou o log de rede, não um código de status.

Fluxo de persistência

  1. Abra o documento e obtenha seu token.
  2. Carregue XML ou 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 ou PNG quando for necessário um entregável plano.
  6. Feche a sessão do documento.

Não use o token do visualizador como identificador permanente de anotação — ele expira com a sessão, e uma reciclagem do pool de aplicações encerra todas as sessões em um único servidor. Armazene os dados de anotação associados aos seus próprios identificadores de documento e versão.

Notas de segurança e renderização

  • Solicitações de anotação usam a mesma segurança de sessão e token das solicitações de página.
  • Valide e controle qualquer URL de imagem fornecida pelo usuário.
  • Cargas grandes de desenho 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
A Faixa de Anotação está ausenteO recurso Annotation e as quatro flags de CSS/script de anotação
Salvar relata um erroExpiração de token ou sessão, e o BasePath do widget
ctlDoc_Created (ou outra função de evento) nunca é executadaO nome da função: id do visualizador + _ + evento, como função global
Anotações C# não aparecemOs números de página são baseados em 1, e os dados foram carregados no token ativo
Uma anotação de imagem aparece na tela mas não na exportaçãoO host da imagem tem um endereço privado ou de loopback — veja acima
Um documento reaberto não tem anotaçõesPersista XML ou dados fora da sessão do visualizador, então carregue‑os no novo token

Esta página foi útil?