Вступ
Підприємствам часто потрібно брендувати або захищати тисячі файлів – контракти, презентації, рахунки – в одній операції. Робити це вручну означає відкривати кожний документ, вставляти логотип або повідомлення про конфіденційність і зберігати його знову. Це не лише забирає багато часу, а й створює ризик людської помилки та появи дублікатів водяних знаків або пропущених файлів.
GroupDocs.Watermark for .NET вирішує цю проблему за допомогою уніфікованого API, який працює з PDF, DOCX, PPTX, XLSX та поширеними форматами зображень. Прикладний проєкт постачається з чотирма типами документів (DOCX, PDF, XLSX, PPTX), тому кожен режим конвеєра працює з реальними форматами. У цьому посібнику ми пройдемо повний конвеєр пакетного водяного знаку, який:
- Завантажує ліцензію (або переходить у режим оцінки).
- Сканує папку та фільтрує лише формати, які підтримує бібліотека.
- Накладає плитковий текстовий водяний знак на кожен документ.
- Накладає плитковий логотип з налаштованою прозорістю та обертанням.
- Додає водяний знак лише коли його ще немає (ідемпотентна обробка).
- Знаходить і замінює застарілий логотип компанії новим.
Після завершення у вас буде готове рішення, яке можна вставити в будь‑який .NET‑проєкт.
Чому важливе пакетне водянування
- Масштабованість – обробка десятків або тисяч файлів в одному циклі.
- Послідовність – один і той самий візуальний стиль застосовується до кожного документа, усуваючи розбіжності бренду.
- Безпека – ідемпотентна логіка запобігає дублюванню водяних знаків при повторному запуску конвеєра.
- Підготовка до майбутнього – код заміни логотипу дозволяє швидко провести ребрендинг без ручного редагування кожного файлу.
Передумови
- .NET 6.0 або новіша версія.
- GroupDocs.Watermark пакет NuGet (
dotnet add package GroupDocs.Watermark). - Файл ліцензії (тимчасовий або постійний). Приклади працюють у режимі оцінки, якщо файл відсутній.
- Дві папки на диску:
InputFolder– містить вихідні документи.OutputFolder– куди будуть записані копії з водяними знаками.
Крок 1 – Завантаження ліцензії
Бібліотека потребує ліцензії, щоб працювати без обмежень оцінки. Нижче наведений фрагмент коду намагається завантажити файл ліцензії і тихо переходить у режим оцінки, якщо файл не знайдено.
try
{
var license = new License();
license.SetLicense(LicensePath);
Console.WriteLine("License applied successfully.");
}
catch
{
Console.WriteLine("Warning: License not found. Running in evaluation mode.");
}
Ключовий момент: Змінна LicensePath повинна вказувати на ваш файл .lic. Якщо файл відсутній, код продовжує виконання, що зручно для швидкого тестування.
Крок 2 – Пошук підтримуваних файлів
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;
Ключовий момент: Метод гарантує, що подальші цикли водянування ніколи не зустрінуть непідтримуваний формат, що інакше призвело б до виключення під час виконання.
Крок 3 – Накладання плиткового текстового водяного знаку
Наступний код створює червоний, напівпрозорий «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");
Ключові моменти:
TileOptionsстворює шаблон у вигляді цеглин, що ускладнює видалення водяного знаку без пошкодження вмісту.- Той самий фрагмент працює з PDF, Word‑файлами, електронними таблицями та зображеннями, оскільки
Watermarkerабстрагує формат.
Крок 4 – Накладання плиткового логотипу
Якщо ви віддаєте перевагу візуальному брендовому знаку, замініть текстовий водяний знак на зображення. Нижче наведений код перевіряє наявність файлу логотипу, а потім розташовує його плиткою з прозорістю 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");
Ключові моменти:
- Така ж логіка
TileOptions, що використовується для тексту, працює і для зображень, забезпечуючи однорідний вигляд у всіх сторінках. Opacityдозволяє залишити вміст читабельним, одночасно демонструючи бренд.
Крок 5 – Ідемпотентне водянування (пропуск вже існуючих знаків)
Запуск конвеєра кілька разів не повинен накладати водяні знаки один на одного. Цей фрагмент коду шукає точну копію тексту водяного знаку перед додаванням нового.
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");
Ключовий момент: TextSearchCriteria з параметром false для чутливості до регістру гарантує, що ми пропускаємо лише ті документи, які вже містять точно той водяний знак, який плануємо додати.
Крок 6 – Замінити застарілий логотип у всій папці
Коли компанія змінює бренд, може знадобитися замінити кожен старий логотип новим. Код поєднує два підходи пошуку зображень – DCT‑хеш для точності та колірну гістограму для допуску – а потім переписує дані зображення у кожному знайденому випадку.
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");
Ключові моменти:
WatermarkerSettingsзPdfSearchableObjects.Allдозволяє пошукати логотипи, збережені у PDF як артефакти.- Поєднання критеріїв DCT‑хеш і колірної гістограми дозволяє виявити як точні векторні логотипи (Office), так і растрові варіанти (PDF).
Кращі практики та поради
- Створюйте папку виводу один раз (
Directory.CreateDirectory) – метод ідемпотентний і запобігає стану гонки. - Логуйте прогрес – вивід у консоль на кожному кроці полегшує відстеження успішних та невдалих файлів.
- Налаштуйте
OpacityтаRotateAngleвідповідно до вимог бренду; значення між 0.3–0.5 зазвичай достатньо, щоб бути помітним, але не нав’язливим. - Використовуйте ідемпотентний «розумний» пакет для будь‑яких повторюваних завдань (наприклад, нічних оновлень бренду).
- Тестуйте заміну логотипу на невеликій вибірці перед запуском на всій колекції, щоб переконатися у правильності налаштувань пошуку.
Устранення типових проблем
| Симптом | Ймовірна причина | Виправлення |
|---|---|---|
| Файли не обробляються | ScanFolderForSupportedFiles повернув порожній список |
Перевірте шлях InputFolder і чи містить папка підтримувані формати (PDF, DOCX, PPTX, XLSX, PNG, JPG тощо) |
| Водяний знак не видно | Прозорість встановлена занадто низько або колір зливається з фоном | Збільшіть Opacity (наприклад, 0.5) або змініть ForegroundColor на контрастний колір |
| Логотипи PDF не знайдено під час заміни | Логотипи додані як оператори draw у контент‑стрімі (не підлягають пошуку) | При додаванні логотипів використовуйте PdfArtifactWatermarkOptions, щоб вони стали пошуковими артефактами |
Виключення System.Drawing.Common у Linux |
Відсутні нативні бібліотеки GDI+ | Встановіть libgdiplus на цільовій Linux‑машині або увімкніть підтримку Unix у .csproj (<RuntimeHostConfigurationOption Include="System.Drawing.EnableUnixSupport" Value="true" />). |
Висновок
Тепер у вас є повний, готовий до продакшн конвеєр, який може:
- Ліцензувати бібліотеку.
- Автоматично виявляти підтримувані документи.
- Накладати плиткові текстові або логотипні водяні знаки.
- Безпечно запускатися кілька разів без створення дублікатів.
- Замінювати старий корпоративний логотип у всій папці.
Ці будівельні блоки можна комбінувати та адаптувати під будь‑який процес брендування або захисту документів у .NET.