Introdução

Enterprises often need to brand or protect thousands of files – contracts, presentations, invoices – in a single operation. Doing this manually means opening each document, inserting a logo or confidentiality notice, and saving it again. Not only is the process time‑consuming, it introduces human error and leaves the risk of duplicate watermarks or missed files.

GroupDocs.Watermark for .NET solves o problema com uma API unificada que funciona em PDF, DOCX, PPTX, XLSX e formatos de imagem comuns. O projeto de exemplo inclui quatro tipos de documento (DOCX, PDF, XLSX, PPTX) para que cada modo de pipeline seja testado em formatos reais. Neste tutorial vamos percorrer um pipeline completo de marca d’água em lote que:

  1. Carrega uma licença (ou recorre ao modo de avaliação).
  2. Varre uma pasta e filtra apenas os formatos que a biblioteca pode manipular.
  3. Aplica uma marca d’água de texto em mosaico a cada documento.
  4. Aplica uma marca d’água de logotipo em mosaico com opacidade e rotação personalizadas.
  5. Adiciona a marca d’água apenas quando ainda não está presente (processamento idempotente).
  6. Encontra e substitui um logotipo antigo da empresa por um novo.

Ao final você terá uma solução pronta‑para‑executar que pode ser inserida em qualquer projeto .NET.

Por que a Marcação em Lote é Importante

  • Escalabilidade – Processa dezenas ou milhares de arquivos com um único loop.
  • Consistência – O mesmo estilo visual é aplicado a todos os documentos, eliminando desvios de marca.
  • Segurança – A lógica idempotente impede marcas d’água duplicadas quando o pipeline é reexecutado.
  • Preparação para o Futuro – O código de substituição de logotipo permite lançar um rebranding sem tocar manualmente cada arquivo.

Pré-requisitos

  • .NET 6.0 ou posterior.
  • Pacote NuGet GroupDocs.Watermark (dotnet add package GroupDocs.Watermark).
  • Um arquivo de licença (temporário ou permanente). Os exemplos funcionam em modo de avaliação se o arquivo estiver ausente.
  • Duas pastas no disco:
    • InputFolder – contém os documentos de origem.
    • OutputFolder – onde as cópias com marca d’água serão gravadas.

Etapa 1 – Carregar a Licença

A biblioteca requer uma licença para ser executada sem limites de avaliação. O trecho abaixo tenta carregar um arquivo de licença e, caso não o encontre, continua silenciosamente.

try
{
    var license = new License();
    license.SetLicense(LicensePath);
    Console.WriteLine("License applied successfully.");
}
catch
{
    Console.WriteLine("Warning: License not found. Running in evaluation mode.");
}

Ponto chave: A variável LicensePath deve apontar para o seu arquivo .lic. Se o arquivo estiver ausente o código continua, o que é útil para testes rápidos.


Etapa 2 – Descobrir Arquivos Compatíveis

GroupDocs.Watermark só pode processar um conjunto específico de extensões. O helper abaixo varre uma pasta, cria um conjunto de extensões suportadas via FileType.GetSupportedFileTypes() e retorna apenas os arquivos que correspondem.

if (!Directory.Exists(folderPath))
{
    Console.WriteLine($"Folder not found: {folderPath}");
    return new List<string>();
}

var supportedExtensions = FileType.GetSupportedFileTypes()
    .Select(ft => ft.Extension.ToLowerInvariant())
    .ToHashSet();

var supportedFiles = Directory.GetFiles(folderPath, "*.*", SearchOption.AllDirectories)
    .Where(f => supportedExtensions.Contains(Path.GetExtension(f).ToLowerInvariant()))
    .ToList();

Console.WriteLine($"Found {supportedFiles.Count} supported file(s) in '{folderPath}'.");
return supportedFiles;

Ponto chave: O método garante que os loops de marca d’água posteriores nunca encontrem um formato não suportado, o que geraria uma exceção em tempo de execução.


Etapa 3 – Aplicar Marca d’Água de Texto em Mosaico

O código a seguir cria uma marca d’água vermelha, semi‑transparente “CONFIDENTIAL”, rotacionada ‑30°, e a repete em todas as páginas usando TileOptions.

Directory.CreateDirectory(outputFolder);
int processed = 0, failed = 0;

foreach (var filePath in files)
{
    try
    {
        using var watermarker = new Watermarker(filePath);
        var watermark = new TextWatermark(watermarkText,
            new Font("Arial", 19, FontStyle.Bold))
        {
            ForegroundColor = Color.Red,
            Opacity = 0.3,
            RotateAngle = -30,
            TileOptions = new TileOptions
            {
                LineSpacing = new MeasureValue
                {
                    MeasureType = TileMeasureType.Percent,
                    Value = 12
                },
                WatermarkSpacing = new MeasureValue
                {
                    MeasureType = TileMeasureType.Percent,
                    Value = 10
                }
            }
        };

        watermarker.Add(watermark);
        var outPath = Path.Combine(outputFolder, Path.GetFileName(filePath));
        watermarker.Save(outPath);
        processed++;
        Console.WriteLine($"[OK] {Path.GetFileName(filePath)}");
    }
    catch (Exception ex)
    {
        failed++;
        Console.WriteLine($"[FAIL] {Path.GetFileName(filePath)}: {ex.Message}");
    }
}

Console.WriteLine($"Text watermark: {processed} processed, {failed} failed");

Pontos chave:

  • TileOptions cria um padrão tipo tijolo, dificultando a remoção da marca d’água sem afetar o conteúdo subjacente.
  • O mesmo trecho funciona para PDFs, arquivos Word, planilhas e imagens porque Watermarker abstrai o formato.

Text watermark applied to a document


Etapa 4 – Aplicar Marca d’Água de Logotipo em Mosaico

Se preferir um elemento visual, substitua a marca de texto por uma imagem. O código abaixo verifica se o arquivo de logotipo existe, então o repete com 40 % de opacidade e rotação ‑30°.

if (!File.Exists(logoPath))
{
    Console.WriteLine($"Logo not found: {logoPath}. Skipping image mode.");
    return;
}

Directory.CreateDirectory(outputFolder);
int processed = 0, failed = 0;

foreach (var filePath in files)
{
    try
    {
        using var watermarker = new Watermarker(filePath);
        using var watermark = new ImageWatermark(logoPath)
        {
            Opacity = 0.4,
            RotateAngle = -30,
            TileOptions = new TileOptions
            {
                LineSpacing = new MeasureValue
                {
                    MeasureType = TileMeasureType.Percent,
                    Value = 15
                },
                WatermarkSpacing = new MeasureValue
                {
                    MeasureType = TileMeasureType.Percent,
                    Value = 12
                }
            }
        };

        watermarker.Add(watermark);
        var outPath = Path.Combine(outputFolder, Path.GetFileName(filePath));
        watermarker.Save(outPath);
        processed++;
        Console.WriteLine($"[OK] {Path.GetFileName(filePath)} - logo applied");
    }
    catch (Exception ex)
    {
        failed++;
        Console.WriteLine($"[FAIL] {Path.GetFileName(filePath)}: {ex.Message}");
    }
}

Console.WriteLine($"Logo watermark: {processed} processed, {failed} failed");

Pontos chave:

  • A mesma lógica de TileOptions usada para texto funciona para imagens, garantindo um visual consistente em todas as páginas.
  • Opacity permite que o conteúdo subjacente continue legível enquanto exibe a marca.

Tiled logo watermark applied to a document


Etapa 5 – Marcação Idempotente (Ignorar Marcas Existentes)

Executar o pipeline várias vezes não deve empilhar marcas d’água. Este trecho procura por uma instância exata do texto da marca antes de adicionar uma nova.

Directory.CreateDirectory(outputFolder);
int applied = 0, skipped = 0, failed = 0;

foreach (var filePath in files)
{
    try
    {
        using var watermarker = new Watermarker(filePath);
        var criteria = new TextSearchCriteria(watermarkText, false);
        var existing = watermarker.Search(criteria);

        if (existing.Count > 0)
        {
            skipped++;
            Console.WriteLine($"[SKIP] {Path.GetFileName(filePath)} – already contains watermark");
            continue;
        }

        var watermark = new TextWatermark(watermarkText,
            new Font("Arial", 19, FontStyle.Bold))
        {
            ForegroundColor = Color.Red,
            Opacity = 0.3,
            RotateAngle = -30,
            TileOptions = new TileOptions
            {
                LineSpacing = new MeasureValue
                {
                    MeasureType = TileMeasureType.Percent,
                    Value = 12
                },
                WatermarkSpacing = new MeasureValue
                {
                    MeasureType = TileMeasureType.Percent,
                    Value = 10
                }
            }
        };

        watermarker.Add(watermark);
        var outPath = Path.Combine(outputFolder, Path.GetFileName(filePath));
        watermarker.Save(outPath);
        applied++;
        Console.WriteLine($"[OK] {Path.GetFileName(filePath)} – watermark applied");
    }
    catch (Exception ex)
    {
        failed++;
        Console.WriteLine($"[FAIL] {Path.GetFileName(filePath)}: {ex.Message}");
    }
}

Console.WriteLine($"Smart batch: {applied} applied, {skipped} skipped, {failed} failed");

Ponto chave:TextSearchCriteria com false para sensibilidade a maiúsculas garante que só pulemos documentos que já contenham a marca d’água exata que pretendemos adicionar.


Etapa 6 – Substituir um Logotipo Desatualizado na Pasta

Quando a empresa faz um rebranding, pode ser necessário trocar todos os logotipos antigos pelo novo. O código combina duas estratégias de busca de imagem – DCT‑hash para precisão e histograma de cores para tolerância – e sobrescreve os dados da imagem de cada correspondência.

if (!File.Exists(oldLogoPath) || !File.Exists(newLogoPath))
{
    Console.WriteLine("Old or new logo file missing – aborting replacement.");
    return;
}

Directory.CreateDirectory(outputFolder);
byte[] newLogoData = File.ReadAllBytes(newLogoPath);
int replaced = 0, notFound = 0;

var settings = new WatermarkerSettings
{
    SearchableObjects = new SearchableObjects
    {
        PdfSearchableObjects = PdfSearchableObjects.All
    }
};

foreach (var filePath in files)
{
    try
    {
        using var watermarker = new Watermarker(filePath, settings);
        var dct = new ImageDctHashSearchCriteria(oldLogoPath) { MaxDifference = 0.4 };
        var hist = new ImageColorHistogramSearchCriteria(oldLogoPath) { MaxDifference = 0.5 };
        var criteria = dct.Or(hist);
        var found = watermarker.Search(criteria);

        if (found.Count == 0)
        {
            notFound++;
            Console.WriteLine($"[--] {Path.GetFileName(filePath)} – old logo not found");
            continue;
        }

        foreach (PossibleWatermark wm in found)
        {
            try
            {
                wm.ImageData = newLogoData;
            }
            catch
            {
                // Some watermark types cannot be overwritten – ignore.
            }
        }

        var outPath = Path.Combine(outputFolder, Path.GetFileName(filePath));
        watermarker.Save(outPath);
        replaced++;
        Console.WriteLine($"[OK] {Path.GetFileName(filePath)} – {found.Count} logo(s) replaced");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"[FAIL] {Path.GetFileName(filePath)}: {ex.Message}");
    }
}

Console.WriteLine($"Logo replacement: {replaced} updated, {notFound} without old logo");

Pontos chave:

  • WatermarkerSettings com PdfSearchableObjects.All permite que a busca veja logotipos armazenados como artefatos PDF.
  • Combinar critérios DCT‑hash e histograma de cores captura tanto logotipos vetoriais exatos (Office) quanto versões rasterizadas (PDF).

Old logo replaced with new logo


Melhores Práticas e Dicas

  • Crie a pasta de saída uma única vez (Directory.CreateDirectory) – o método é idempotente e evita condições de corrida.
  • Registre o progresso – a saída no console em cada etapa facilita identificar quais arquivos foram bem‑sucedidos ou falharam.
  • Ajuste Opacity e RotateAngle conforme as diretrizes da marca; valores entre 0.3‑0.5 costumam ser suficientes para serem visíveis sem serem intrusivos.
  • Use o batch inteligente idempotente para qualquer tarefa recorrente (por exemplo, atualizações noturnas de branding).
  • Teste a substituição de logotipo em uma amostra pequena antes de executar em todo o repositório para garantir que os critérios de busca estejam corretos.

Solução de Problemas Comuns

Sintoma Causa Provável Correção
Nenhum arquivo foi processado ScanFolderForSupportedFiles retornou uma lista vazia Verifique o caminho de InputFolder e se a pasta contém formatos suportados (PDF, DOCX, PPTX, XLSX, PNG, JPG, etc.)
Marca d’água não visível Opacidade definida muito baixa ou a cor se mistura com o fundo Aumente Opacity (ex.: 0.5) ou altere ForegroundColor para uma cor contrastante
Logotipos PDF não encontrados durante a substituição Logotipos adicionados como operadores de desenho no fluxo de conteúdo (não pesquisáveis) Ao inserir logotipos, adicione-os com PdfArtifactWatermarkOptions para que se tornem artefatos pesquisáveis
Exceção System.Drawing.Common no Linux Bibliotecas nativas GDI+ ausentes Instale libgdiplus na máquina Linux alvo ou habilite o suporte Unix no .csproj (<RuntimeHostConfigurationOption Include="System.Drawing.EnableUnixSupport" Value="true" />).

Conclusão

Você agora possui um pipeline completo e pronto para produção que pode:

  • Licenciar a biblioteca.
  • Detectar documentos suportados automaticamente.
  • Aplicar marcas d’água de texto ou logotipo em mosaico.
  • Executar com segurança múltiplas vezes sem criar duplicatas.
  • Substituir um logotipo corporativo antigo em toda a pasta.

Esses blocos de construção podem ser combinados e adaptados para qualquer fluxo de trabalho de branding ou proteção de documentos em .NET.

Recursos Adicionais