Introduction
Підприємствам часто потрібно брендувати або захищати тисячі файлів – контракти, презентації, рахунки – в одній операції. Робити це вручну означає відкривати кожний документ, вставляти логотип або повідомлення про конфіденційність і зберігати його знову. Це не лише забирає багато часу, а й створює ризик людської помилки та дублікатів водяних знаків або пропущених файлів.
GroupDocs.Watermark for .NET вирішує проблему за допомогою уніфікованого API, який працює з PDF, DOCX, PPTX, XLSX та поширеними форматами зображень. Прикладний проєкт постачається з чотирма типами документів (DOCX, PDF, XLSX, PPTX), тому кожен режим конвеєра працює з реальними форматами. У цьому посібнику ми пройдемо повний конвеєр пакетного водяного знака, який:
- Завантажує ліцензію (або переходить у режим оцінки).
- Сканує папку і фільтрує лише формати, які підтримує бібліотека.
- Накладає плитковий текстовий водяний знак на кожен документ.
- Накладає плитковий логотип з налаштованою прозорістю та обертанням.
- Додає водяний знак тільки коли його ще немає (ідемпотентна обробка).
- Знаходить і замінює застарілий логотип компанії новим.
Після завершення у вас буде готове рішення, яке можна вставити в будь‑який .NET‑проєкт.
Why Batch Watermarking Matters
- Масштабованість – обробка десятків або тисяч файлів у одному циклі.
- Послідовність – один і той самий візуальний стиль застосовується до кожного документа, усуваючи розбіжності бренду.
- Безпека – ідемпотентна логіка запобігає дублюванню водяних знаків при повторному запуску конвеєра.
- Підготовка до майбутнього – код заміни логотипу дозволяє провести ребрендинг без ручного редагування кожного файлу.
Prerequisites
- .NET 6.0 або новіша версія.
- GroupDocs.Watermark пакет NuGet (
dotnet add package GroupDocs.Watermark). - Файл ліцензії (тимчасовий або постійний). Приклади працюють у режимі оцінки, якщо файл відсутній.
- Дві папки на диску:
InputFolder– містить вихідні документи.OutputFolder– куди будуть записані копії з водяними знаками.
Step 1 – Load the License
Бібліотека вимагає ліцензію для роботи без обмежень оцінки. Нижче наведений фрагмент коду намагається завантажити файл ліцензії і тихо переходить у режим оцінки, якщо файл не знайдено.
try
{
var license = new License();
license.SetLicense(LicensePath);
Console.WriteLine("License applied successfully.");
}
catch
{
Console.WriteLine("Warning: License not found. Running in evaluation mode.");
}
Key point: Змінна LicensePath повинна вказувати на ваш файл .lic. Якщо файл відсутній, код продовжує виконання, що зручно для швидкого тестування.
Step 2 – Discover Supported Files
GroupDocs.Watermark може обробляти лише певний набір розширень. Наведений нижче помічник сканує папку, формує хеш‑множину підтримуваних розширень за допомогою FileType.GetSupportedFileTypes() і повертає лише файли, що відповідають цим розширенням.
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;
Key point: Метод гарантує, що подальші цикли водяного знака ніколи не зіткнуться з непідтримуваним форматом, що могло б викликати виключення під час виконання.
Step 3 – Apply a Tiled Text Watermark
Наступний код створює червоний, напівпрозорий «CONFIDENTIAL» водяний знак, обертає його на ‑30° і розташовує плиткою по всіх сторінках за допомогою 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");
Key points:
TileOptionsстворює шаблон у вигляді цеглин, що ускладнює видалення водяного знака без пошкодження вмісту.- Той самий фрагмент працює з PDF, Word, електронними таблицями та зображеннями, оскільки
Watermarkerабстрагує формат.
Step 4 – Apply a Tiled Logo Watermark
Якщо ви віддаєте перевагу візуальному бренду, замініть текстовий водяний знак на зображення. Нижче код перевіряє наявність файлу логотипу, а потім розташовує його плиткою з прозорістю 40 % і обертанням ‑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");
Key points:
- Така ж логіка
TileOptions, що використовується для тексту, працює і для зображень, забезпечуючи однорідний вигляд у всіх сторінках. Opacityдозволяє залишити вміст читабельним, одночасно демонструючи бренд.
Step 5 – Idempotent Watermarking (Skip Existing Marks)
Запуск конвеєра кілька разів не повинен накладати водяні знаки один на одного. Цей фрагмент шукає точну копію тексту водяного знака перед додаванням нового.
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");
Key point: TextSearchCriteria з параметром false (чутливість до регістру) гарантує, що ми пропускаємо лише ті документи, які вже містять точно той водяний знак, який плануємо додати.
Step 6 – Replace an Outdated Logo Across the Folder
Коли компанія змінює бренд, може знадобитися замінити кожен старий логотип новим. Код комбінує два підходи пошуку зображень – DCT‑hash для точності та колірну гістограму для допуску – а потім перезаписує дані зображення у кожному знайденому випадку.
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");
Key points:
WatermarkerSettingsзPdfSearchableObjects.Allдозволяє пошукати логотипи, збережені у PDF як артефакти.- Комбінація критеріїв DCT‑hash і колірної гістограми охоплює як точні векторні логотипи (Office), так і растрові версії (PDF).
Best Practices & Tips
- Створюйте папку виводу один раз (
Directory.CreateDirectory) – метод ідемпотентний і запобігає стану гонки. - Логуйте прогрес – вивід у консоль на кожному кроці полегшує відстеження успішних та невдалих файлів.
- Налаштуйте
OpacityтаRotateAngleзгідно з вимогами бренду; значення між 0.3–0.5 зазвичай достатньо, щоб бути помітним, але не нав’язливим. - Використовуйте ідемпотентний «smart batch» для будь‑якої повторюваної задачі (наприклад, нічних оновлень бренду).
- Тестуйте заміну логотипу на невеликій вибірці перед запуском на всій колекції, щоб переконатися у правильності налаштувань пошуку.
Troubleshooting Common Issues
| Symptom | Likely Cause | Fix |
|---|---|---|
| Не обробляються файли | ScanFolderForSupportedFiles повернув порожній список |
Перевірте шлях InputFolder і чи містить папка підтримувані формати (PDF, DOCX, PPTX, XLSX, PNG, JPG тощо) |
| Водяний знак не видно | Прозорість встановлена занадто низько або колір зливається з фоном | Збільшіть Opacity (наприклад, 0.5) або змініть ForegroundColor на контрастний колір |
| Логотипи у PDF не знайдено під час заміни | Логотипи додані як оператори потоку вмісту (не підлягають пошуку) | При додаванні логотипів використовуйте PdfArtifactWatermarkOptions, щоб вони стали пошуковими артефактами |
Виключення System.Drawing.Common у Linux |
Відсутні нативні бібліотеки GDI+ | Встановіть libgdiplus на цільовій Linux‑машині або увімкніть підтримку Unix у .csproj (<RuntimeHostConfigurationOption Include="System.Drawing.EnableUnixSupport" Value="true" />). |
Conclusion
Тепер у вас є повний, готовий до продакшну конвеєр, який може:
- Ліцензувати бібліотеку.
- Автоматично виявляти підтримувані документи.
- Накладати плиткові текстові або логотипні водяні знаки.
- Безпечно працювати багаторазово без створення дублікатів.
- Замінювати старий корпоративний логотип у всій папці.
Ці будівельні блоки можна комбінувати та адаптувати під будь‑який процес брендування або захисту документів у .NET.