Giới thiệu

Doanh nghiệp thường cần gắn thương hiệu hoặc bảo vệ hàng ngàn tệp – hợp đồng, bản thuyết trình, hoá đơn – trong một thao tác duy nhất. Thực hiện thủ công đồng nghĩa với việc mở từng tài liệu, chèn logo hoặc thông báo bảo mật, rồi lưu lại. Không chỉ tốn thời gian, mà còn gây ra lỗi con người và có nguy cơ đánh dấu trùng lặp hoặc bỏ sót tệp.

GroupDocs.Watermark for .NET giải quyết vấn đề bằng một API thống nhất hoạt động trên PDF, DOCX, PPTX, XLSX và các định dạng ảnh phổ biến. Dự án mẫu đi kèm với bốn loại tài liệu (DOCX, PDF, XLSX, PPTX) nên mọi chế độ pipeline đều chạy trên các định dạng thực tế. Trong hướng dẫn này chúng ta sẽ đi qua một pipeline đánh dấu hàng loạt hoàn chỉnh, bao gồm:

  1. Tải giấy phép (hoặc chuyển sang chế độ đánh giá).
  2. Quét thư mục và lọc chỉ các định dạng mà thư viện hỗ trợ.
  3. Áp dụng đánh dấu văn bản dạng lát gạch lên mọi tài liệu.
  4. Áp dụng đánh dấu logo dạng lát gạch với độ trong suốt và góc quay tùy chỉnh.
  5. Thêm đánh dấu chỉ khi chưa tồn tại (xử lý idempotent).
  6. Tìm và thay thế logo công ty cũ bằng logo mới.

Kết thúc bạn sẽ có một giải pháp sẵn sàng chạy, có thể đưa vào bất kỳ dự án .NET nào.

Tại sao Đánh dấu Hàng loạt lại Quan trọng

  • Khả năng mở rộng – Xử lý hàng chục hoặc hàng nghìn tệp chỉ với một vòng lặp.
  • Nhất quán – Phong cách hình ảnh giống nhau được áp dụng cho mọi tài liệu, loại bỏ sự lệch thương hiệu.
  • An toàn – Logic idempotent ngăn ngừa việc tạo đánh dấu trùng lặp khi pipeline được chạy lại.
  • Chuẩn bị cho tương lai – Mã thay thế logo cho phép bạn triển khai tái thương hiệu mà không cần chỉnh sửa từng tệp thủ công.

Yêu cầu trước

  • .NET 6.0 trở lên.
  • Gói NuGet GroupDocs.Watermark (dotnet add package GroupDocs.Watermark).
  • Một tệp giấy phép (tạm thời hoặc vĩnh viễn). Các ví dụ vẫn hoạt động ở chế độ đánh giá nếu tệp không có.
  • Hai thư mục trên đĩa:
    • InputFolder – chứa các tài liệu nguồn.
    • OutputFolder – nơi sẽ ghi các bản sao đã được đánh dấu.

Bước 1 – Tải Giấy phép

Thư viện yêu cầu giấy phép để chạy mà không bị giới hạn đánh giá. Đoạn mã dưới đây cố gắng tải tệp giấy phép và sẽ lặng lẽ chuyển sang chế độ đánh giá nếu không tìm thấy tệp.

try
{
    var license = new License();
    license.SetLicense(LicensePath);
    Console.WriteLine("License applied successfully.");
}
catch
{
    Console.WriteLine("Warning: License not found. Running in evaluation mode.");
}

Điểm quan trọng: Biến LicensePath phải trỏ tới tệp .lic của bạn. Nếu tệp thiếu, mã vẫn tiếp tục, rất hữu ích cho việc thử nhanh.


Bước 2 – Phát hiện Các Tệp Được Hỗ trợ

GroupDocs.Watermark chỉ có thể xử lý một tập hợp các phần mở rộng nhất định. Trợ giúp dưới đây quét một thư mục, tạo một tập hợp các phần mở rộng được hỗ trợ bằng FileType.GetSupportedFileTypes(), và trả về chỉ những tệp khớp.

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;

Điểm quan trọng: Phương thức này đảm bảo các vòng lặp đánh dấu sau này sẽ không gặp định dạng không được hỗ trợ, tránh gây ngoại lệ thời gian chạy.


Bước 3 – Áp dụng Đánh dấu Văn bản Dạng Lát gạch

Đoạn mã sau tạo một đánh dấu “CONFIDENTIAL” màu đỏ, bán trong suốt, quay ‑30°, và lát gạch trên mọi trang bằng 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");

Các điểm quan trọng:

  • TileOptions tạo ra mẫu gạch, khiến đánh dấu khó bị loại bỏ mà không ảnh hưởng tới nội dung gốc.
  • Đoạn mã này hoạt động cho PDF, Word, bảng tính và ảnh vì Watermarker trừu tượng hoá định dạng.

Text watermark applied to a document


Bước 4 – Áp dụng Đánh dấu Logo Dạng Lát gạch

Nếu bạn muốn dùng biểu tượng hình ảnh, thay thế đánh dấu văn bản bằng hình ảnh. Đoạn mã dưới đây kiểm tra xem tệp logo có tồn tại không, rồi lát gạch nó với độ trong suốt 40 % và quay ‑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");

Các điểm quan trọng:

  • Logic TileOptions giống như với văn bản, mang lại giao diện đồng nhất trên mọi trang.
  • Opacity cho phép nội dung nền vẫn đọc được trong khi vẫn hiển thị thương hiệu.

Tiled logo watermark applied to a document


Bước 5 – Đánh dấu Idempotent (Bỏ qua Đánh dấu Đã Có)

Chạy pipeline nhiều lần không nên chồng lên nhau các đánh dấu. Đoạn mã này tìm kiếm một đánh dấu văn bản chính xác trước khi thêm mới.

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");

Điểm quan trọng:TextSearchCriteria với tham số false cho phép phân biệt chữ hoa/thường đảm bảo chúng ta chỉ bỏ qua những tài liệu đã chứa đúng đánh dấu mà chúng ta dự định thêm.


Bước 6 – Thay Thế Logo Cũ Bằng Logo Mới Trên Toàn Bộ Thư Mục

Khi công ty tái thương hiệu, bạn có thể cần hoán đổi mọi logo cũ bằng logo mới. Đoạn mã này kết hợp hai chiến lược tìm kiếm hình ảnh – DCT‑hash cho độ chính xác và histogram màu cho độ dung sai – rồi ghi đè dữ liệu ảnh của mỗi kết quả tìm được.

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");

Các điểm quan trọng:

  • WatermarkerSettings với PdfSearchableObjects.All cho phép tìm kiếm các logo được lưu dưới dạng artifact trong PDF.
  • Kết hợp tiêu chí DCT‑hash và histogram màu giúp bắt cả logo vector (Office) và phiên bản raster (PDF).

Old logo replaced with new logo


Các Thực Hành Tốt Nhất & Mẹo

  • Tạo thư mục đầu ra một lần (Directory.CreateDirectory) – phương thức này idempotent và tránh các điều kiện tranh chấp.
  • Ghi log tiến độ – đầu ra console ở mỗi bước giúp bạn nhanh chóng biết tệp nào thành công hoặc thất bại.
  • Tinh chỉnh OpacityRotateAngle theo hướng dẫn thương hiệu; giá trị từ 0.3–0.5 thường đủ để nhìn thấy nhưng không gây phiền.
  • Sử dụng batch thông minh idempotent cho bất kỳ công việc định kỳ nào (ví dụ: cập nhật thương hiệu hàng đêm).
  • Kiểm tra thay thế logo trên một mẫu nhỏ trước khi chạy trên toàn bộ kho để đảm bảo tiêu chí tìm kiếm được điều chỉnh chính xác.

Xử Lý Sự Cố Thông Thường

Triệu chứng Nguyên nhân có thể Giải pháp
Không có tệp nào được xử lý ScanFolderForSupportedFiles trả về danh sách rỗng Kiểm tra lại đường dẫn InputFolder và chắc chắn thư mục chứa các định dạng được hỗ trợ (PDF, DOCX, PPTX, XLSX, PNG, JPG, …)
Đánh dấu không hiển thị Độ trong suốt quá thấp hoặc màu trùng nền Tăng Opacity (ví dụ: 0.5) hoặc đổi ForegroundColor sang màu tương phản
Logo PDF không được tìm thấy khi thay thế Logo được thêm dưới dạng toán tử draw trong content‑stream (không thể tìm kiếm) Khi chèn logo, sử dụng PdfArtifactWatermarkOptions để chúng trở thành artifact có thể tìm kiếm
Ngoại lệ System.Drawing.Common trên Linux Thiếu thư viện GDI+ gốc Cài đặt libgdiplus trên máy Linux hoặc bật hỗ trợ Unix trong .csproj (<RuntimeHostConfigurationOption Include="System.Drawing.EnableUnixSupport" Value="true" />).

Kết luận

Bạn đã có một pipeline hoàn chỉnh, sẵn sàng cho môi trường sản xuất có thể:

  • Cấp phép cho thư viện.
  • Tự động phát hiện tài liệu được hỗ trợ.
  • Áp dụng đánh dấu văn bản hoặc logo dạng lát gạch.
  • Chạy an toàn nhiều lần mà không tạo ra bản sao đánh dấu trùng lặp.
  • Thay thế logo công ty cũ trên toàn bộ thư mục.

Các khối xây dựng này có thể được kết hợp linh hoạt để phù hợp với bất kỳ quy trình bảo vệ tài liệu hoặc thương hiệu nào trong .NET.

Tài Nguyên Bổ Sung