The Old Way Was Painful

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

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

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

There’s a Better Way

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

Prerequisites

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

The New Way: Automated Watermark Audit

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

Step 1 – Scan Every Watermark

Сначала нам нужен полный инвентарь. Метод 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}");
    }
}

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

Step 2 – Verify Required Text Watermark

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

using (var wk = new Watermarker(filePath))
{
    var crit = new TextSearchCriteria(expectedPhrase);
    crit.SkipUnreadableCharacters = true;   // ignore OCR artefacts
    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.

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

using (var wk = new Watermarker(filePath))
{
    var crit = new ImageDctHashSearchCriteria(logoPath);
    crit.MaxDifference = 0.9;   // tolerate moderate scaling / colour shift
    var matches = wk.Search(crit);
    bool ok = matches.Count > 0;
    Console.WriteLine($"  [{(ok ? "PASS" : "FAIL")}] " +
        $"logo instances: {matches.Count}");
    return ok;
}

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

Step 4 – Run a Full Compliance Report

Реальные политики объединяют несколько требований. Первый блок проверяет четыре правила форматирования — наличие текста, шрифт, размер и полужирный стиль — каждое из которых комбинирует 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 или к прямой передаче в систему тикетов.


Side‑by‑Side: Before vs. After

Manual Review Automated Audit
Time Hours per batch Seconds per file
Accuracy Human error prone Deterministic API
Scalability Limited to a few documents Handles thousands
Code required None (but labor intensive) ~30 lines of C#
Output Visual inspection only Structured PASS/FAIL report

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


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

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

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


What Else Can You Do with GroupDocs.Watermark?

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

Text replacement — the old watermark is updated with new text, font, and color

Targeted removal — only watermarks matching both text and formatting criteria are deleted

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

  • Add invisible tracking watermarks that embed a unique ID for leak tracing.
  • Bulk replace outdated logos across an entire archive.
  • Generate PDF‑ready compliance certificates after a successful audit.
  • Integrate with Azure Functions or AWS Lambda for serverless processing.

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


Conclusion

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

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

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


Next Steps

  • Try the free API trial – get a temporary license here: Temp License
  • Read the full documentation for advanced options: Docs
  • Explore the .NET API reference for all classes and methods: API Reference
  • Clone the sample project on GitHub to see a complete console app: GitHub Samples
  • Ask questions or share your use case on the community forum: Forum