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):
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:
<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.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étodo | Objetivo |
|---|---|
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,onAnnClosedsão opções dedocViewer({...}), como todo callback de visualizador.Created,Changed,DeletedePropertiessão funções globais encontradas por nome: o id do visualizador precede o evento. Para o visualizador em#div_ctlDoco id éctlDoc, então as funções sãoctlDoc_Created,ctlDoc_Changed,ctlDoc_DeletedectlDoc_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):
// 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):
| Tipo | Observações |
|---|---|
StampAnnotation | Carimbo de texto com tamanho de fonte, borda, cor; suporta Opacity, Rotate |
NoteAnnotation | Nota adesiva com texto, cor de fundo, tamanho de fonte, TitleColor |
RectangleAnnotation | Cores de borda e preenchimento, Title/ShowTitle |
CircleAnnotation | Borda e preenchimento, ShowBorder |
EllipseAnnotation | Borda e preenchimento, ShowBorder |
TriangleAnnotation | Cor da borda, BackColor, ShowBorder |
LineAnnotation | Linha reta com espessura e cor |
ArrowAnnotation | Linha com ponta de seta; Direction configurável (ArrowDirection, pontos cardeais, padrão E) |
FreehandAnnotation | Traço livre a partir de pontos codificados FreehandData |
ImageAnnotation | Imagem 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
| Membro | Propó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
// 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ície | Quem baixa a imagem | Em um host intranet ou de desenvolvimento |
|---|---|---|
| O visualizador no navegador | O navegador do usuário | A imagem aparece |
ExportAnnotationsToPdfAsync / …PngZipAsync | O servidor | Um 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
- Abra o documento e obtenha seu token.
- Carregue XML ou dados codificados previamente armazenados nesse token.
- Permita que o widget leia e edite as anotações da sessão.
- Recupere o XML com
GetAnnotationXML(token)quando sua aplicação decidir persistir. - Exporte PDF ou PNG quando for necessário um entregável plano.
- 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
| Sintoma | Verificação |
|---|---|
| A Faixa de Anotação está ausente | O recurso Annotation e as quatro flags de CSS/script de anotação |
| Salvar relata um erro | Expiração de token ou sessão, e o BasePath do widget |
ctlDoc_Created (ou outra função de evento) nunca é executada | O nome da função: id do visualizador + _ + evento, como função global |
| Anotações C# não aparecem | Os 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ção | O host da imagem tem um endereço privado ou de loopback — veja acima |
| Um documento reaberto não tem anotações | Persista XML ou dados fora da sessão do visualizador, então carregue‑os no novo token |
Esta página foi útil?