Старый способ был болезненным

Представьте сотрудника по соблюдению нормативов, которому поручено убедиться, что каждый контракт в общей папке содержит слово “CONFIDENTIAL” и логотип компании на каждой странице. Текущий процесс выглядит так:

  1. Открыть файл в просмотрщике.
  2. Перелистывать каждую страницу в поисках фразы или изображения.
  3. Делать заметки в таблице.
  4. Повторять для тысяч PDF‑файлов, Word‑документов и презентаций.

Один пропущенный водяной знак может вызвать дорогостоящий пересмотр, а ручные усилия легко превышают 8 часов в неделю для небольшой команды. При этом вращающийся текст, разорванные слова или логотипы, сохранённые как изображения, часто ускользают от человеческого глаза, оставляя организацию уязвимой.

Есть лучший способ

GroupDocs.Watermark for .NET устраняет все догадки. Его независимый от формата движок умеет читать более 100 типов документов, находить текстовые, графические и даже стилизованные водяные знаки, а также предоставлять всю релевантную метаинформацию через чистый API. Ниже представлено руководство, показывающее, как несколько лаконичных фрагментов кода заменяют ручной цикл автоматизированным, повторяемым рабочим процессом.

Предварительные требования

  • .NET 6.0 или новее.
  • NuGet‑пакет GroupDocs.Watermark (dotnet add package GroupDocs.Watermark).
  • (Опционально) временная лицензия — см. ссылку в конце статьи.

Новый способ: автоматический аудит водяных знаков

Ниже мы рассматриваем четыре основных операции. Каждый блок — самостоятельный пример, который можно вставить в консольное приложение, шаг CI или фоновый сервис.

Шаг 1 — Сканировать все водяные знаки

Сначала нам нужен полный инвентарь. Метод Search() возвращает коллекцию, где каждая запись содержит текст (или изображение), позицию, угол поворота, номер страницы и исходный размер изображения.

using (var wk = new Watermarker(filePath))
{
    var all = wk.Search();
    Console.WriteLine($"Found {all.Count} watermark(s) in " +
        $"'{Path.GetFileName(filePath)}':");
    int i = 0;
    foreach (var wm in all)
    {
        Console.WriteLine($"  #{++i}: {(wm.Text ?? "[image]")} ");
        Console.WriteLine($"     Page {wm.PageNumber}, " +
            $"Pos X={wm.X}, Y={wm.Y}, Rot={wm.RotateAngle}°");
        Console.WriteLine($"     Size {wm.Width}×{wm.Height}");
        if (wm.ImageData != null)
            Console.WriteLine($"     Image bytes {wm.ImageData.Length}");
    }
}

Ключевой момент: цикл выполняется менее чем за секунду для типичного PDF‑файла из 50 страниц.

Шаг 2 — Проверить обязательный текстовый водяной знак

Политика соответствия часто требует конкретную фразу (например, “CONFIDENTIAL”). TextSearchCriteria с включённым SkipUnreadableCharacters автоматически обрабатывает разорванный или вращённый текст.

using (var wk = new Watermarker(filePath))
{
    var crit = new TextSearchCriteria(expectedPhrase);
    crit.SkipUnreadableCharacters = true;   // игнорировать артефакты OCR
    var hits = wk.Search(crit);
    bool ok = hits.Count > 0;
    Console.WriteLine($"  [{(ok ? "PASS" : "FAIL")}] " +
        $"'{expectedPhrase}' found {hits.Count} time(s)");
    return ok;
}

Метод возвращает true, когда фраза встречается хотя бы один раз, давая мгновенный индикатор PASS/FAIL.

Шаг 3 — Проверить логотип компании

Логотипы обычно сохраняются как растровые изображения, и их внешний вид может слегка изменяться из‑за сжатия. ImageDctHashSearchCriteria создаёт перцептивный хеш эталонного логотипа и сравнивает его с заданным порогом допуска.

using (var wk = new Watermarker(filePath))
{
    var crit = new ImageDctHashSearchCriteria(logoPath);
    crit.MaxDifference = 0.9;   // допускаем умеренное масштабирование / изменение цвета
    var matches = wk.Search(crit);
    bool ok = matches.Count > 0;
    Console.WriteLine($"  [{(ok ? "PASS" : "FAIL")}] " +
        $"logo instances: {matches.Count}");
    return ok;
}

Даже копия логотипа низкого разрешения будет распознана.

Шаг 4 — Сформировать полный отчёт о соответствии

В реальных политиках объединяется несколько требований. Первый блок проверяет четыре правила форматирования — наличие текста, шрифт, размер и полужирное начертание — каждое из которых комбинирует TextSearchCriteria с TextFormattingSearchCriteria через .And():

using (var wk = new Watermarker(filePath))
{
    int passed = 0, failed = 0;

    var txtCrit = new TextSearchCriteria(expectedPhrase);
    bool hasText = wk.Search(txtCrit).Count > 0;
    Console.WriteLine($"  [{(hasText ? "PASS" : "FAIL")}] Text present");
    if (hasText) passed++; else failed++;

    var fontCrit = new TextFormattingSearchCriteria { FontName = expFont };
    bool hasFont = wk.Search(txtCrit.And(fontCrit)).Count > 0;
    Console.WriteLine($"  [{(hasFont ? "PASS" : "FAIL")}] Font {expFont}");
    if (hasFont) passed++; else failed++;

    var sizeCrit = new TextFormattingSearchCriteria { MinFontSize = minSize };
    bool hasSize = wk.Search(txtCrit.And(sizeCrit)).Count > 0;
    Console.WriteLine($"  [{(hasSize ? "PASS" : "FAIL")}] Size >= {minSize}");
    if (hasSize) passed++; else failed++;

    var boldCrit = new TextFormattingSearchCriteria { FontBold = true };
    bool hasBold = wk.Search(txtCrit.And(boldCrit)).Count > 0;
    Console.WriteLine($"  [{(hasBold ? "PASS" : "FAIL")}] Bold formatting");
    if (hasBold) passed++; else failed++;

Пятое правило проверяет покрытие страниц — водяной знак должен присутствовать на каждой странице. Затем итоговый вердикт агрегирует все результаты:

    var perPage = wk.Search(txtCrit);
    var pages = new HashSet<int>();
    foreach (var wm in perPage)
        if (wm.PageNumber.HasValue) pages.Add(wm.PageNumber.Value);
    var allPages = wk.Search();
    int max = 0;
    foreach (var wm in allPages)
        max = Math.Max(max, wm.PageNumber ?? 0);
    bool full = max > 0 && pages.Count == max;
    Console.WriteLine($"  [{(full ? "PASS" : "FAIL")}] " +
        $"Pages covered {pages.Count}/{max}");
    if (full) passed++; else failed++;

    string verdict = failed == 0 ? "COMPLIANT" : "NON-COMPLIANT";
    Console.WriteLine($"\nResult: {verdict} " +
        $"({passed} passed, {failed} failed)");
}

Отчёт можно экспортировать в JSON, CSV или сразу передать в систему тикетов.


Сравнение «до» и «после»

Ручной обзор Автоматический аудит
Время Часы за партию Секунды за файл
Точность Подвержена человеческим ошибкам Детерминированный API
Масштабируемость Ограничена несколькими документами Обрабатывает тысячи
Требуемый код Нет (но трудоёмко) ~30 строк C#
Вывод Только визуальная проверка Структурный отчёт PASS/FAIL

Контраст очевиден: то, что раньше занимало целый рабочий день, теперь выполняется в фоновом режиме.


Реальный пример: библиотека юридических контрактов

Юридическая фирма хранит 15 000 контрактов в общей папке. Их политика требует фразу “CONFIDENTIAL – CLIENT XYZ” и печать фирмы на каждой странице. Интегрировав приведённые выше фрагменты в ночной скрипт PowerShell, фирма добилась:

  • 100 % обнаружения отсутствующих знаков (ранее 8 % ускользали).
  • Ноль ручных часов на аудит.
  • Журнал аудита, сохраняемый во внутренний список SharePoint для будущих регуляторных проверок.
// Пример точки входа ночного задания
var folder = @"\\fileserver\Contracts";
foreach (var pdf in Directory.GetFiles(folder, "*.pdf", SearchOption.AllDirectories))
{
    // повторно используем методы из шагов 1‑4
    ScanAll(pdf);
    VerifyText(pdf, "CONFIDENTIAL – CLIENT XYZ");
    VerifyLogo(pdf, @"C:\Logos\firm-seal.png");
    RunReport(pdf);
}

Скрипт работает без присмотра и каждое утро отправляет сводку по электронной почте.


Что ещё можно сделать с GroupDocs.Watermark?

Помимо аудита, пример проекта показывает, как заменять и удалять водяные знаки программно. Скриншоты ниже демонстрируют обе операции на реальном PDF:

Замена текста — старый водяной знак обновлён новым текстом, шрифтом и цветом

Целевое удаление — удаляются только водяные знаки, соответствующие одновременно текстовым и форматным критериям

Другие сценарии, которые можно построить с тем же API:

  • Добавлять невидимые отслеживающие водяные знаки, содержащие уникальный ID для трассировки утечек.
  • Массово заменять устаревшие логотипы во всей архиве.
  • Генерировать сертификаты соответствия в PDF после успешного аудита.
  • Интегрировать с Azure Functions или AWS Lambda для безсерверной обработки.

Каждый сценарий использует один и тот же базовый API — меняем только критерии поиска или тип водяного знака.


Заключение

То, что раньше требовало от команды листать страницы, делать заметки и рисковать пропустить знаки, теперь превращается в пару секунд кода, генерирующего проверяемый отчёт PASS/FAIL. С GroupDocs.Watermark for .NET вы получаете:

  • Полный обзор всех водяных знаков.
  • Надёжное обнаружение текста, стилизованного текста и логотипов.
  • Автоматическую генерацию отчётов о соответствии.
  • Возможность программно обновлять или удалять водяные знаки.

Попробуйте и превратите процесс контроля водяных знаков из головной боли в повторяемый сервис.


Следующие шаги

  • Попробуйте бесплатную пробную версию API — получите временную лицензию здесь: Temp License
  • Изучите полную документацию для расширенных возможностей: Docs
  • Ознакомьтесь с .NET API reference для всех классов и методов: API Reference
  • Склонируйте пример проекта на GitHub, чтобы увидеть готовое консольное приложение: GitHub Samples
  • Задавайте вопросы или делитесь своим кейсом на форуме сообщества: Forum