이전 방식은 고통스러웠다
컴플라이언스 담당자가 공유 드라이브에 있는 모든 계약서에 **“CONFIDENTIAL”**이라는 단어와 회사 로고가 각 페이지에 포함되어 있는지 확인해야 한다고 상상해 보세요. 현재 프로세스는 다음과 같습니다:
- 뷰어에서 파일을 엽니다.
- 각 페이지를 뒤져가며 문구나 이미지를 찾습니다.
- 스프레드시트에 메모를 적습니다.
- 수천 개의 PDF, Word 파일, 프레젠테이션에 대해 반복합니다.
워터마크 하나를 놓치면 비용이 많이 드는 검토가 발생하고, 수작업 노력은 작은 팀이라도 주당 8시간을 쉽게 초과합니다. 또한 회전된 텍스트, 분리된 단어, 이미지로 저장된 로고는 사람 눈에 잘 잡히지 않아 조직이 위험에 노출됩니다.
더 나은 방법이 있습니다
GroupDocs.Watermark for .NET은 모든 추측 단계를 없애줍니다. 포맷에 구애받지 않는 엔진은 100가지가 넘는 문서 유형을 읽고, 텍스트, 이미지 및 스타일이 적용된 워터마크를 찾아내며, 깔끔한 API를 통해 모든 관련 메타데이터를 노출합니다. 아래 튜토리얼은 몇 개의 간결한 스니펫으로 수작업 루프를 자동화된 반복 가능한 워크플로우로 대체하는 방법을 보여줍니다.
사전 요구 사항
- .NET 6.0 이상.
- GroupDocs.Watermark NuGet 패키지 (
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}");
}
}
핵심 포인트: 루프는 일반적인 50페이지 PDF에서 1초 미만에 실행됩니다.
단계 2 – 필수 텍스트 워터마크 확인
컴플라이언스 정책은 종종 특정 구문(예: “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 플래그를 제공합니다.
단계 3 – 회사 로고 확인
로고는 래스터 이미지 형태로 존재하며 압축으로 인해 약간씩 다르게 보일 수 있습니다. 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;
}
저해상도 복사본이라도 로고를 인식합니다.
단계 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 형태로 내보내거나 티켓팅 시스템에 바로 전달할 수 있습니다.
비교: 이전 vs. 이후
| 수동 검토 | 자동 감사 | |
|---|---|---|
| 시간 | 배치당 시간 | 파일당 초 |
| 정확도 | 인간 오류 가능 | 결정적 API |
| 확장성 | 몇 개 문서에 제한 | 수천 개 처리 |
| 필요 코드 | 없음 (하지만 노동 집약적) | ~30줄 C# |
| 출력 | 시각적 검사만 | 구조화된 PASS/FAIL 보고서 |
대조가 뚜렷합니다: 하루 종일 걸리던 작업이 이제 백그라운드 잡으로 실행됩니다.
실제 사례: 법률 계약 라이브러리
한 로펌은 공유 폴더에 15 000개의 계약서를 보관하고 있습니다. 정책에 따라 모든 페이지에 **“CONFIDENTIAL – CLIENT XYZ”**라는 문구와 회사 인장이 있어야 합니다. 위 스니펫을 야간 PowerShell 스크립트에 통합함으로써 다음을 달성했습니다:
- 100 % 누락 탐지 (이전에는 8 %가 빠짐).
- 감사에 소요되는 수작업 시간 0.
- 향후 규제 검토를 위해 내부 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);
}
스크립트는 무인으로 실행되며 매일 아침 요약을 이메일로 전송합니다.
GroupDocs.Watermark로 할 수 있는 다른 일은?
감사 외에도 샘플 프로젝트는 워터마크를 교체하고 제거하는 방법을 보여줍니다. 아래 스크린샷은 실제 PDF에서 두 작업을 시연합니다:
같은 API를 사용해 구축할 수 있는 다른 시나리오:
- 유출 추적을 위한 고유 ID를 삽입하는 보이지 않는 추적 워터마크 추가.
- 전체 아카이브에서 오래된 로고를 대량 교체.
- 감사 성공 후 PDF 준비된 컴플라이언스 인증서 생성.
- 서버리스 처리를 위한 Azure Functions 또는 AWS Lambda와 통합.
각 시나리오는 동일한 핵심 API를 사용합니다 – 검색 기준이나 워터마크 유형만 교체하면 됩니다.
결론
한때 팀이 페이지를 넘겨가며 메모를 남기고 누락된 워터마크를 놓칠 위험이 있던 작업이 이제 몇 초의 코드로 감사 가능한 PASS/FAIL 보고서를 생성합니다. GroupDocs.Watermark for .NET을 사용하면 다음을 얻을 수 있습니다:
- 모든 워터마크에 대한 완전한 가시성.
- 텍스트, 스타일 텍스트 및 로고에 대한 신뢰할 수 있는 탐지.
- 자동 컴플라이언스 보고서 생성.
- 워터마크를 프로그래밍 방식으로 업데이트하거나 제거하는 기능.
한 번 시도해 보시고 워터마크 컴플라이언스 프로세스를 골칫거리에서 반복 가능한 서비스로 전환해 보세요.
다음 단계
- 무료 API 체험 – 여기에서 임시 라이선스를 받으세요: Temp License
- 고급 옵션을 위한 전체 문서 읽기: Docs
- 모든 클래스와 메서드를 확인할 수 있는 .NET API 레퍼런스 탐색: API Reference
- 완전한 콘솔 앱을 확인하려면 샘플 프로젝트를 GitHub에서 복제: GitHub Samples
- 커뮤니티 포럼에서 질문하거나 사용 사례를 공유: Forum