Introduction
Les entreprises ont souvent besoin de marquer ou de protéger des milliers de fichiers – contrats, présentations, factures – en une seule opération. Le faire manuellement signifie ouvrir chaque document, insérer un logo ou un avis de confidentialité, puis le sauvegarder à nouveau. Non seulement le processus est chronophage, il introduit des erreurs humaines et crée le risque de filigranes en double ou de fichiers oubliés.
GroupDocs.Watermark for .NET résout le problème avec une API unifiée qui fonctionne sur PDF, DOCX, PPTX, XLSX et les formats d’image courants. Le projet d’exemple comprend quatre types de documents (DOCX, PDF, XLSX, PPTX) afin que chaque mode de pipeline s’exécute sur des formats réels. Dans ce tutoriel, nous parcourrons un pipeline complet de filigranage par lots qui :
- Charge une licence (ou revient en mode d’évaluation).
- Parcourt un dossier et ne conserve que les formats que la bibliothèque peut gérer.
- Applique un filigrane texte en mosaïque à chaque document.
- Applique un filigrane logo en mosaïque avec une opacité et une rotation personnalisées.
- Ajoute un filigrane uniquement s’il n’est pas déjà présent (traitement idempotent).
- Recherche et remplace un logo d’entreprise obsolète par un nouveau.
À la fin, vous disposerez d’une solution prête à l’emploi qui peut être intégrée à n’importe quel projet .NET.
Pourquoi le filigranage par lots est important
- Scalabilité – Traitez des dizaines ou des milliers de fichiers avec une seule boucle.
- Cohérence – Le même style visuel est appliqué à chaque document, éliminant les dérives de marque.
- Sécurité – La logique idempotente empêche les filigranes en double lorsque le pipeline est relancé.
- Préparation au futur – Le code de remplacement de logo vous permet de lancer un re‑branding sans toucher chaque fichier manuellement.
Prérequis
- .NET 6.0 ou version ultérieure.
- GroupDocs.Watermark package NuGet (
dotnet add package GroupDocs.Watermark). - Un fichier de licence (temporaire ou permanent). Les exemples fonctionnent en mode d’évaluation si le fichier est absent.
- Deux dossiers sur le disque :
InputFolder– contient les documents source.OutputFolder– où les copies filigranées seront écrites.
Étape 1 – Charger la licence
La bibliothèque nécessite une licence pour fonctionner sans les limites d’évaluation. L’extrait ci‑dessous tente de charger un fichier de licence et revient silencieusement si le fichier est introuvable.
try
{
var license = new License();
license.SetLicense(LicensePath);
Console.WriteLine("License applied successfully.");
}
catch
{
Console.WriteLine("Warning: License not found. Running in evaluation mode.");
}
Point clé : La variable LicensePath doit pointer vers votre fichier .lic. Si le fichier manque, le code continue, ce qui est pratique pour des tests rapides.
Étape 2 – Découvrir les fichiers pris en charge
GroupDocs.Watermark ne peut traiter qu’un ensemble spécifique d’extensions. L’assistant ci‑dessous parcourt un dossier, construit un ensemble de hachage des extensions prises en charge via FileType.GetSupportedFileTypes(), et ne renvoie que les fichiers correspondants.
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;
Point clé : La méthode garantit que les boucles de filigranage ultérieures ne rencontreront jamais un format non pris en charge, ce qui éviterait autrement une exception d’exécution.
Étape 3 – Appliquer un filigrane texte en mosaïque
Le code suivant crée un filigrane « CONFIDENTIAL » rouge, semi‑transparent, le fait pivoter de ‑30° et le répète sur chaque page à l’aide de 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");
Points clés :
TileOptionscrée un motif en briques, rendant le filigrane difficile à supprimer sans altérer le contenu sous‑jacent.- Le même extrait fonctionne pour les PDF, les fichiers Word, les feuilles de calcul et les images parce que
Watermarkerabstrait le format.
Étape 4 – Appliquer un filigrane logo en mosaïque
Si vous préférez une marque visuelle, remplacez le filigrane texte par une image. Le code ci‑dessous vérifie que le fichier logo existe, puis le répète avec 40 % d’opacité et une rotation de ‑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");
Points clés :
- La même logique
TileOptionsutilisée pour le texte fonctionne pour les images, offrant un rendu cohérent sur toutes les pages. Opacitypermet au contenu sous‑jacent de rester lisible tout en affichant la marque.
Étape 5 – Filigranage idempotent (ignorer les marques existantes)
Exécuter le pipeline plusieurs fois ne doit pas empiler les filigranes les uns sur les autres. Cet extrait recherche une instance exacte du texte du filigrane avant d’en ajouter un nouveau.
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");
Point clé : TextSearchCriteria avec false pour la sensibilité à la casse garantit que nous ne sautons que les documents contenant exactement le filigrane que nous souhaitons ajouter.
Étape 6 – Remplacer un logo obsolète dans tout le dossier
Lorsqu’une entreprise change de logo, il peut être nécessaire d’échanger chaque ancien logo par le nouveau. Le code combine deux stratégies de recherche d’image – hash DCT pour la précision et histogramme de couleur pour la tolérance – puis écrase les données d’image de chaque correspondance.
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");
Points clés :
WatermarkerSettingsavecPdfSearchableObjects.Allrend la recherche capable de voir les logos stockés comme artefacts PDF.- La combinaison des critères DCT‑hash et histogramme de couleur capture à la fois les logos vectoriels exacts (Office) et les versions rasterisées (PDF).
Bonnes pratiques et astuces
- Créer le dossier de sortie une seule fois (
Directory.CreateDirectory) – la méthode est idempotente et évite les conditions de concurrence. - Consigner la progression – la sortie console de chaque étape facilite la visualisation des fichiers réussis ou échoués.
- Ajuster
OpacityetRotateAngleselon les directives de la marque ; une valeur entre 0,3 et 0,5 est généralement suffisante pour être visible sans être intrusive. - Utiliser le batch intelligent idempotent pour toute tâche récurrente (par ex. mises à jour de marque nocturnes).
- Tester le remplacement de logo sur un petit échantillon avant de l’appliquer à l’ensemble du référentiel afin de s’assurer que les critères de recherche sont correctement réglés.
Résolution des problèmes courants
| Symptom | Likely Cause | Fix |
|---|---|---|
| Aucun fichier n’est traité | ScanFolderForSupportedFiles a renvoyé une liste vide |
Vérifier le chemin InputFolder et s’assurer que le dossier contient des formats pris en charge (PDF, DOCX, PPTX, XLSX, PNG, JPG, etc.) |
| Filigrane invisible | Opacité trop faible ou couleur se fond dans le fond | Augmenter Opacity (ex. 0.5) ou changer ForegroundColor pour une teinte contrastante |
| Logos PDF non trouvés lors du remplacement | Logos ajoutés comme opérateurs de flux de contenu (non recherchables) | Lors de l’insertion des logos, utilisez PdfArtifactWatermarkOptions afin qu’ils deviennent des artefacts recherchables |
Exception System.Drawing.Common sous Linux |
Bibliothèques natives GDI+ manquantes | Installer libgdiplus sur la machine Linux cible ou activer le support Unix dans le .csproj (<RuntimeHostConfigurationOption Include="System.Drawing.EnableUnixSupport" Value="true" />). |
Conclusion
Vous disposez maintenant d’un pipeline complet, prêt pour la production qui peut :
- Licencier la bibliothèque.
- Détecter automatiquement les documents pris en charge.
- Appliquer des filigranes texte ou logo en mosaïque.
- S’exécuter en toute sécurité plusieurs fois sans créer de doublons.
- Remplacer un ancien logo d’entreprise dans l’ensemble d’un dossier.
Ces blocs de construction peuvent être combinés et adaptés à tout flux de travail de protection ou de marquage de documents sous .NET.