المقدمة

غالبًا ما تحتاج المؤسسات إلى وضع علامة تجارية أو حماية آلاف الملفات – عقود، عروض تقديمية، فواتير – في عملية واحدة. القيام بذلك يدويًا يعني فتح كل مستند، إدراج شعار أو إشعار سرية، ثم حفظه مرة أخرى. ليست العملية مستهلكة للوقت فحسب، بل تُدخل أخطاء بشرية وتترك خطر وجود علامات مائية مكررة أو ملفات مفقودة.

GroupDocs.Watermark for .NET يحل المشكلة من خلال واجهة برمجة تطبيقات موحدة تعمل عبر PDF و DOCX و PPTX و XLSX وصيغ الصور الشائعة. يأتي المشروع النموذجي مع أربعة أنواع من المستندات (DOCX، PDF، XLSX، PPTX) بحيث يعمل كل وضع من أوضاع خط الأنابيب على صيغ واقعية. في هذا الدرس سنستعرض خط أنابيب علامة مائية دفعي كامل يتضمن:

  1. تحميل ترخيص (أو الرجوع إلى وضع التقييم).
  2. مسح مجلد وتصفية الصيغ التي يمكن للمكتبة التعامل معها فقط.
  3. تطبيق علامة مائية نصية متكررة على كل مستند.
  4. تطبيق علامة مائية شعار متكررة مع شفافية وتدوير مخصصين.
  5. إضافة علامة مائية فقط عندما لا تكون موجودة بالفعل (معالجة إيديمبوتنت).
  6. العثور على واستبدال شعار الشركة القديم بشعار جديد.

بنهاية هذا الدرس ستحصل على حل جاهز للتنفيذ يمكن دمجه في أي مشروع .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 يُجرد الصيغة.

Text watermark applied to a document


الخطوة 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 يسمح بقراءة المحتوى الأساسي مع إبراز العلامة التجارية.

Tiled logo watermark applied to a document


الخطوة 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‑hash للدقة و colour‑histogram للمرونة – ثم يكتب بيانات الصورة لكل تطابق.

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‑hash ومعيار colour‑histogram يلتقط كلًا من الشعارات المتجهية الدقيقة (Office) والإصدارات النقطية (PDF).

Old logo replaced with new logo


أفضل الممارسات والنصائح

  • إنشاء مجلد الإخراج مرة واحدة (Directory.CreateDirectory) – الطريقة إيديمبوتنت وتتفادى مشاكل التزامن.
  • تسجيل التقدم – مخرجات وحدة التحكم في كل خطوة تجعل من السهل معرفة الملفات التي نجحت أو فشلت.
  • ضبط Opacity و RotateAngle وفقًا لإرشادات العلامة التجارية؛ عادةً تكون القيمة بين 0.3–0.5 كافية لتكون مرئية دون إزعاج.
  • استخدام الدفعة الذكية الإيديمبوتنت لأي مهمة متكررة (مثل تحديث العلامة التجارية الليلي).
  • اختبار استبدال الشعار على عينة صغيرة قبل تشغيله على المستودع بالكامل لضمان ضبط معايير البحث بشكل صحيح.

استكشاف الأخطاء الشائعة وإصلاحها

العَرَض السبب المحتمل الحل
لا يتم معالجة أي ملفات ScanFolderForSupportedFiles أعاد قائمة فارغة تحقق من مسار InputFolder وتأكد من وجود صيغ مدعومة (PDF، DOCX، PPTX، XLSX، PNG، JPG، إلخ)
العلامة المائية غير مرئية تم ضبط الشفافية منخفضة جدًا أو اللون يندمج مع الخلفية زد Opacity (مثلاً 0.5) أو غيّر ForegroundColor إلى لون متباين
عدم العثور على شعارات PDF أثناء الاستبدال الشعارات أضيفت كـ draw operators في تدفق المحتوى (غير قابلة للبحث) عند إضافة الشعارات، استخدم PdfArtifactWatermarkOptions لجعلها عناصر قابلة للبحث
استثناء System.Drawing.Common على لينكس نقص مكتبات GDI+ الأصلية ثبّت libgdiplus على الجهاز المستهدف أو فعّل دعم Unix في ملف .csproj (<RuntimeHostConfigurationOption Include="System.Drawing.EnableUnixSupport" Value="true" />).

الخلاصة

أصبح لديك الآن خط أنابيب كامل جاهز للإنتاج يمكنه:

  • ترخيص المكتبة.
  • اكتشاف المستندات المدعومة تلقائيًا.
  • تطبيق علامات مائية نصية أو شعارات متكررة.
  • التشغيل بأمان عدة مرات دون إنشاء نسخ مكررة.
  • استبدال شعار الشركة القديم عبر مجلد كامل.

يمكن دمج هذه اللبنات الأساسية معًا لتناسب أي سير عمل للعلامة التجارية أو حماية المستندات في .NET.

موارد إضافية