Introduzione

Le aziende spesso hanno bisogno di marchiare o proteggere migliaia di file – contratti, presentazioni, fatture – in un’unica operazione. Farlo manualmente significa aprire ogni documento, inserire un logo o un avviso di riservatezza e salvarlo nuovamente. Non solo il processo è dispendioso in termini di tempo, ma introduce errori umani e il rischio di watermark duplicati o file dimenticati.

GroupDocs.Watermark for .NET risolve il problema con un’API unificata che funziona su PDF, DOCX, PPTX, XLSX e formati immagine comuni. Il progetto di esempio include quattro tipi di documento (DOCX, PDF, XLSX, PPTX) così ogni modalità di pipeline viene testata su formati reali. In questo tutorial percorreremo una pipeline batch di watermark completa che:

  1. Carica una licenza (o ricade in modalità di valutazione).
  2. Scansiona una cartella e filtra solo i formati gestiti dalla libreria.
  3. Applica un watermark di testo a piastrelle a ogni documento.
  4. Applica un watermark di logo a piastrelle con opacità e rotazione personalizzate.
  5. Aggiunge un watermark solo se non è già presente (elaborazione idempotente).
  6. Trova e sostituisce un logo aziendale obsoleto con uno nuovo.

Al termine avrai una soluzione pronta all’uso che può essere inserita in qualsiasi progetto .NET.

Perché il Watermarking di Massa è Importante

  • Scalabilità – Elabora decine o migliaia di file con un unico ciclo.
  • Coerenza – Lo stesso stile visivo viene applicato a tutti i documenti, eliminando la deriva del brand.
  • Sicurezza – La logica idempotente impedisce watermark duplicati quando la pipeline viene rieseguita.
  • Prospettiva futura – Il codice di sostituzione del logo consente di lanciare un re‑brand senza toccare manualmente ogni file.

Prerequisiti

  • .NET 6.0 o versioni successive.
  • Pacchetto NuGet GroupDocs.Watermark (dotnet add package GroupDocs.Watermark).
  • Un file di licenza (temporaneo o permanente). Gli esempi funzionano in modalità di valutazione se il file è assente.
  • Due cartelle sul disco:
    • InputFolder – contiene i documenti sorgente.
    • OutputFolder – dove verranno scritte le copie marchiate.

Passo 1 – Caricare la Licenza

La libreria richiede una licenza per funzionare senza i limiti della valutazione. Lo snippet qui sotto tenta di caricare un file di licenza e, in caso di assenza, passa silenziosamente alla modalità di valutazione.

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

Punto chiave: La variabile LicensePath deve puntare al tuo file .lic. Se il file manca il codice continua, il che è utile per test rapidi.


Passo 2 – Scoprire i File Supportati

GroupDocs.Watermark può elaborare solo un insieme specifico di estensioni. L’aiutante qui sotto scansiona una cartella, costruisce un set di hash delle estensioni supportate tramite FileType.GetSupportedFileTypes() e restituisce solo i file che corrispondono.

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;

Punto chiave: Il metodo garantisce che i cicli di watermark successivi non incontrino mai un formato non supportato, evitando così eccezioni a runtime.


Passo 3 – Applicare un Watermark di Testo a Piastrelle

Il codice seguente crea un watermark rosso, semi‑trasparente “CONFIDENTIAL”, lo ruota di ‑30° e lo dispone a piastrelle su ogni pagina 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");

Punti chiave:

  • TileOptions crea un pattern a mattoncini, rendendo il watermark difficile da rimuovere senza alterare il contenuto sottostante.
  • Lo stesso snippet funziona per PDF, file Word, fogli di calcolo e immagini perché Watermarker astrae il formato.

Watermark di testo applicato a un documento


Passo 4 – Applicare un Watermark di Logo a Piastrelle

Se preferisci un marchio visivo, sostituisci il watermark di testo con un’immagine. Il codice qui sotto verifica che il file del logo esista, poi lo dispone a piastrelle con opacità al 40 % e rotazione ‑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");

Punti chiave:

  • La stessa logica TileOptions usata per il testo funziona per le immagini, garantendo un aspetto coerente su tutte le pagine.
  • Opacity permette al contenuto sottostante di rimanere leggibile mantenendo comunque visibile il brand.

Watermark di logo a piastrelle applicato a un documento


Passo 5 – Watermarking Idempotente (Ignora i Segni Esistenti)

Eseguire la pipeline più volte non dovrebbe sovrapporre watermark. Questo snippet cerca un’istanza esatta del testo del watermark prima di aggiungerne uno nuovo.

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");

Punto chiave:TextSearchCriteria con false per la sensibilità al maiuscolo/minuscolo garantisce di saltare solo i documenti che contengono già esattamente il watermark che intendiamo aggiungere.


Passo 6 – Sostituire un Logo Obsoleto nella Cartella

Quando un’azienda cambia brand, potresti dover scambiare ogni vecchio logo con quello nuovo. Il codice combina due strategie di ricerca immagine – DCT‑hash per precisione e istogramma di colore per tolleranza – quindi sovrascrive i dati immagine di ogni corrispondenza.

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");

Punti chiave:

  • WatermarkerSettings con PdfSearchableObjects.All abilita la ricerca di loghi memorizzati come artefatti PDF.
  • La combinazione di criteri DCT‑hash e istogramma di colore cattura sia i loghi vettoriali esatti (Office) sia le versioni rasterizzate (PDF).

Logo vecchio sostituito con il nuovo logo


Buone Pratiche e Suggerimenti

  • Crea la cartella di output una sola volta (Directory.CreateDirectory) – il metodo è idempotente e evita condizioni di gara.
  • Registra i progressi – l’output console in ogni fase rende facile vedere quali file hanno avuto successo o sono falliti.
  • Regola Opacity e RotateAngle secondo le linee guida del brand; un valore tra 0.3–0.5 è solitamente sufficiente per essere visibile ma non invasivo.
  • Usa il batch idempotente per qualsiasi lavoro ricorrente (ad esempio aggiornamenti notturni del branding).
  • Testa la sostituzione del logo su un piccolo campione prima di eseguirla su tutto il repository per assicurarti che i criteri di ricerca siano correttamente sintonizzati.

Risoluzione dei Problemi Comuni

Sintomo Probabile Causa Soluzione
Nessun file viene elaborato ScanFolderForSupportedFiles ha restituito una lista vuota Verifica il percorso di InputFolder e che la cartella contenga formati supportati (PDF, DOCX, PPTX, XLSX, PNG, JPG, ecc.)
Watermark non visibile Opacità impostata troppo bassa o colore che si confonde con lo sfondo Aumenta Opacity (es. 0.5) o cambia ForegroundColor con una tonalità più contrastante
Loghi PDF non trovati durante la sostituzione Loghi aggiunti come operatori di stream di contenuto (non ricercabili) Quando inserisci i loghi, aggiungili con PdfArtifactWatermarkOptions così diventano artefatti ricercabili
Eccezione System.Drawing.Common su Linux Librerie native GDI+ mancanti Installa libgdiplus sulla macchina Linux di destinazione o abilita il supporto Unix nel .csproj (<RuntimeHostConfigurationOption Include="System.Drawing.EnableUnixSupport" Value="true" />).

Conclusione

Ora disponi di una pipeline completa e pronta per la produzione che può:

  • Licenziare la libreria.
  • Rilevare automaticamente i documenti supportati.
  • Applicare watermark di testo o logo a piastrelle.
  • Eseguire in modo sicuro più volte senza creare duplicati.
  • Sostituire un logo aziendale obsoleto in tutta una cartella.

Questi blocchi costitutivi possono essere mescolati e abbinati per adattarsi a qualsiasi flusso di lavoro di branding o protezione dei documenti in .NET.

Risorse Aggiuntive