מבוא
חברות רבות צריכות למתג או להגן על אלפי קבצים – חוזים, מצגות, חשבוניות – בפעולה אחת. ביצוע זה ידנית משמעותו לפתוח כל מסמך, להכניס לוגו או הודעת סודיות, ולשמור אותו מחדש. לא רק שההליך גוזל זמן, הוא גם גורם לטעויות אנוש ומשאיר סיכון של הוספת סימני מים כפולים או קבצים שנשכחו.
GroupDocs.Watermark for .NET פותרת את הבעיה עם API מאוחד הפועל על PDF, DOCX, PPTX, XLSX ותבניות תמונה נפוצות. פרויקט הדוגמה כולל ארבעה סוגי מסמכים (DOCX, PDF, XLSX, PPTX) כך שכל מצב צינור רץ על פורמטים מהעולם האמיתי. במדריך זה נעבור על צינור סימון מים באצווה שלם ש:
- טוען רישיון (או חוזר למצב הערכה).
- סורק תיקייה ומסנן רק את הפורמטים שהספרייה יכולה לטפל בהם.
- מוסיף סימן מים בטקסט מתארג לכל מסמך.
- מוסיף סימן מים בלוגו מתארג עם שקיפות וסיבוב מותאמים.
- מוסיף סימן מים רק כאשר הוא עדיין לא קיים (עיבוד אידמפוטנטי).
- מוצא ומחליף לוגו חברה מיושן בלוגו חדש.
בסיום יהיה ברשותכם פתרון מוכן להרצה שניתן לשלב בכל פרויקט .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מאגד את הפורמט.
שלב 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מאפשר לתוכן הבסיס להישאר קריא תוך כדי הצגת המותג.
שלב 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 artifacts.- שילוב קריטריוני DCT‑hash והיסטוגרמת צבעים תופס גם לוגואים וקטוריים מדויקים (Office) וגם גרסאות רסטריות (PDF).
שיטות עבודה מומלצות & טיפים
- צור את תיקיית הפלט פעם אחת (
Directory.CreateDirectory) – הפונקציה אידמפוטנטית ומונעת מצבי מרוץ. - תעד את ההתקדמות – פלט הקונסול בכל שלב מאפשר לראות אילו קבצים הצליחו או נכשלו.
- כוון
Opacityו‑RotateAngleלפי קווי המותג; ערך בין 0.3–0.5 בדרך כלל מספיק כדי להיות נראה אך לא חודרני. - השתמש בצינור האידמפוטנטי לכל משימה חוזרת (למשל עדכוני מותג לילה).
- בדוק החלפת לוגו על מדגם קטן לפני הרצה על כל המאגר כדי לוודא שהקריטריונים מכוונים כראוי.
פתרון בעיות נפוצים
| סימפטום | סיבה אפשרית | תיקון |
|---|---|---|
| אין קבצים שמעובדים | ScanFolderForSupportedFiles החזיר רשימה ריקה |
ודא שהנתיב InputFolder נכון ושבתיקייה יש פורמטים נתמכים (PDF, DOCX, PPTX, XLSX, PNG, JPG וכו') |
| סימן המים לא נראה | שקיפות נמוכה מדי או שהצבע מתמזג עם הרקע | הגדל את Opacity (למשל 0.5) או שנה את ForegroundColor לצבע מנוגד |
| לוגו PDF לא נמצא במהלך ההחלפה | הלוגו נוסף כ‑draw operators ב‑content‑stream (לא ניתן לחיפוש) | בעת הוספת הלוגו, השתמש ב‑PdfArtifactWatermarkOptions כך שהוא יהפוך ל‑artifact שניתן לחיפוש |
חריגה System.Drawing.Common בלינוקס |
חסרות ספריות GDI+ מקומיות | התקן libgdiplus במכונה הלינוקס או אפשר תמיכה ב‑Unix בקובץ .csproj (<RuntimeHostConfigurationOption Include="System.Drawing.EnableUnixSupport" Value="true" />). |
סיכום
כעת יש ברשותכם צינור שלם, מוכן לייצור, שיכול:
- לרשום רישיון לספרייה.
- לזהות מסמכים נתמכים באופן אוטומטי.
- להחיל סימני מים בטקסט או בלוגו מתארג.
- לפעול בבטחה מספר פעמים ללא יצירת כפילויות.
- להחליף לוגו תאגידי ישן בכל התיקייה.
ניתן לשלב את הבלוקים הבסיסיים הללו לפי הצורך בכל זרימת עבודה של מיתוג או הגנת מסמכים ב‑.NET.