نامرئی کردن Watermarkهای صفحات فایل‌های PDF توسط iTextSharp
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: پنج دقیقه

احتمالا بارها با PDFهایی که یک Watermark بزرگ را در میانه صفحات خود دارند، برخورد داشته‌اید و متاسفانه در اغلب اوقات استفاده ناصحیحی از این قابلیت صورت می‌گیرد. هدف از Watermark دار کردن صفحات PDF، ذکر جملاتی مانند «آزمایشی بودن» یا «محرمانه بودن» است که در هر دو حالت نباید به صورت عمومی منتشر شوند. اما اگر قرار است مطلبی را به صورت عمومی منتشر کنیم، این روش، بدترین حالت تبلیغی برای یک شخص یا شرکت خواهد بود؛ چون مانع خواندن روان متن شده و اعصاب مصرف کننده را به هم خواهد ریخت. بنابراین هیچگونه جنبه تبلیغی مثبتی را در نهایت برای تهیه کننده به همراه نخواهد داشت.
برای نمونه فایل نمونه سؤالات مصاحبات دات را از اینجا دریافت کنید. یک چنین شکلی دارد:

خوب! چطور می‌توان این Watermark را حذف یا حداقل نامرئی کرد؟
برای پاسخ به این سؤال نیاز است ابتدا با نحوه Watermark دار کردن صفحات یک فایل PDF آشناشویم.

الف) ایجاد یک فایل PDF ساده
        private static void createPdfFile(string pdfFile)
        {
            using (var fs = new FileStream(pdfFile, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                using (var doc = new Document(PageSize.A4))
                {
                    using (var witier = PdfWriter.GetInstance(doc, fs))
                    {
                        doc.Open();
                        for (int i = 1; i <= 5; i++)
                        {
                            doc.NewPage();
                            doc.Add(new Paragraph(String.Format("This is page {0}", i)));
                        }
                        doc.Close();
                    }
                }
            }
        }
در اینجا یک فایل PDF با 5 صفحه ایجاد می‌شود.

ب) افزودن Watermark به فایل PDF تهیه شده
        private static void addWatermark(string pdfFile, string watermarkedFile, string watermarkText)
        {
            FontFactory.Register("c:\\windows\\fonts\\tahoma.ttf");
            var tahoma = FontFactory.GetFont("Tahoma", BaseFont.IDENTITY_H, 40, Font.NORMAL, BaseColor.BLACK);

            var reader = new PdfReader(pdfFile);
            using (var fileStream = new FileStream(watermarkedFile, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                using (var stamper = new PdfStamper(reader, fileStream))
                {
                    int pageCount = reader.NumberOfPages;
                    for (int i = 1; i <= pageCount; i++)
                    {
                        var rect = reader.GetPageSize(i);
                        var cb = stamper.GetUnderContent(i);
                        var gState = new PdfGState();
                        gState.FillOpacity = 0.25f;
                        cb.SetGState(gState);

                        ColumnText.ShowTextAligned(
                            canvas: cb,
                            alignment: Element.ALIGN_CENTER,
                            phrase: new Phrase(watermarkText, tahoma),
                            x: rect.Width / 2,
                            y: rect.Height / 2,
                            rotation: 45f,
                            runDirection: PdfWriter.RUN_DIRECTION_LTR,
                            arabicOptions: 0);
                    }
                }
            }
        }
در متد فوق pdfFile نام و مسیر فایل PDF ایی است که قرار است به صفحات آن Watermark اضافه شود. نام و مسیر فایل خروجی توسط watermarkedFile مشخص می‌شود و watermarkText متنی است که در میانه صفحه نمایش داده خواهد شد.
در اینجا توسط PdfReader، فایل موجود گشوده می‌شود. به این ترتیب می‌توان به تک تک صفحات این فایل دسترسی یافت. از PdfStamper برای نوشتن در این فایل باز شده استفاده می‌کنیم.  متد stamper.GetUnderContent، لایه زیرین متن صفحات را در اختیار ما قرار می‌دهد. اگر علاقمند به نوشتن بر روی لایه رویی متون هستید از متد stamper.GetOverContent استفاده کنید. در اینجا از PdfGState برای مشخص سازی میزان شفافیت متن درحال نمایش، با مقدار دهی FillOpacity آن استفاده شده است. نهایتا از متد ColumnText.ShowTextAligned برای نمایش متن مورد نظر در مکان و زاویه‌ای مشخص استفاده می‌کنیم. این متد با زبان فارسی سازگاری دارد و run direction آن قابل تنظیم است.


ج) آشنایی با ساختار سطح پایین Watermark اضافه شده به صفحات

تقریبا اکثر Watermarkهایی که بر روی صفحات PDF درج می‌شوند، نیمه شفاف هستند تا بتوان متن زیر آن‌ها را مطالعه کرد. این شفافیت همانطور که ذکر شد توسط مقدار دهی شیء PdfGState حاصل می‌شود. اگر فایل PDF تولیدی در قسمت ب را توسط برنامه iText Rups باز کنیم، به شکل زیر خواهیم رسید:


هدف ما این است که به شیء PdfGState موجود در هر صفحه، دسترسی یافته و مقدار FillOpacity آن‌را صفر کنیم. به این ترتیب این Watermark، کاملا شفاف یا نامرئی خواهد شد. در PDF نهایی، چیزی به نام شیء PdfGState وجود ندارد، بلکه با یک سری Dictionary و Array سر و کار داریم.
همانطور که در شکل فوق ملاحظه می‌کنید، برای رسیدن به Gstateها باید مراحل زیر طی شوند:
1- فایل PDF گشوده شده و سپس به هر صفحه دسترسی یافت.
2- نیاز است RESOURCES صفحه جاری استخراج شوند.
3- در این منابع، باید EXTGSTATE را که همان PdfGStateها هستند، بیابیم.
4- سپس مقدار ca این EXTGSTATE یافت شده را به صفر مقدار دهی کنیم.

        private static void removeWatermark(string watermarkedFile, string unwatermarkedFile)
        {
            PdfReader.unethicalreading = true;
            PdfReader reader = new PdfReader(watermarkedFile);
            reader.RemoveUnusedObjects();
            int pageCount = reader.NumberOfPages;
            for (int i = 1; i <= pageCount; i++)
            {
                var page = reader.GetPageN(i);
                PdfDictionary resources = page.GetAsDict(PdfName.RESOURCES);
                PdfDictionary extGStates = resources.GetAsDict(PdfName.EXTGSTATE);
                if (extGStates == null)
                    continue;

                foreach (PdfName name in extGStates.Keys)
                {
                    var obj = extGStates.Get(name);
                    PdfDictionary extGStateObject = (PdfDictionary)PdfReader.GetPdfObject(obj);
                    var stateNumber = extGStateObject.Get(PdfName.ca_);
                    if (stateNumber == null)
                        continue;

                    var caNumber = (PdfNumber)PdfReader.GetPdfObject(stateNumber);
                    if (caNumber.FloatValue != 1f)
                    {
                        extGStateObject.Remove(PdfName.ca_);
                        //با تنظیم مقدار به صفر، نامرئی خواهد شد
                        extGStateObject.Put(PdfName.ca_, new PdfNumber(0f));
                    }
                }
            }

            using (FileStream fs = new FileStream(unwatermarkedFile, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                using (PdfStamper stamper = new PdfStamper(reader, fs))
                {
                    stamper.SetFullCompression();
                    stamper.Close();
                }
            }
        }
نحوه پیاده سازی مراحل یاد شده را در کدهای فوق ملاحظه می‌کنید. ابتدا توسط  PdfReader فایل موجود باز شده و سپس تک تک صفحات آن‌را بررسی می‌کنیم. در این صفحات اگر EXTGSTATE ایی یافت شد، مقدار ca آن‌را به صفر تنظیم خواهیم کرد. از مقدار 1 صرفنظر شده، چون این مقدار عموما برای Watermark دار کردن صفحات استفاده نمی‌شود.
در این متد، watermarkedFile فایلی است که باید watermark آن نامرئی شود و unwatermarkedFile فایل تولیدی حاصل است.

  • #
    ‫۸ سال و ۵ ماه قبل، پنجشنبه ۱۹ فروردین ۱۳۹۵، ساعت ۱۵:۲۵
    ممنون؛ اگه بخوایم واترمارک رو کاملا حذف کنیم چیکار باید کرد؟ کجای کد باید تغییر کنه؟
      • #
        ‫۸ سال و ۵ ماه قبل، چهارشنبه ۲۵ فروردین ۱۳۹۵، ساعت ۰۱:۰۳
        ممنونم ، من به نسخه 5.5.9.0 بروزرسانی کردم و از این PdfName.ca_ خطا میگیره میگه چنین مشخصه (ca_) وجود نداره.
        ممنون میشم اگه راهنمایی کنین که چه مشخصه باید جایگزین بشه چون بنظر میرسه در نسخه جدید به چیز دیگه ای تغییر کرده.
        متن خطا :
        'PdfName' does not contain a definition for 'ca_
         
      • #
        ‫۸ سال و ۵ ماه قبل، چهارشنبه ۲۵ فروردین ۱۳۹۵، ساعت ۰۱:۱۴
        اون خطا Pdfname.ca_ رو رفع کردم آقای نصیری ممنونم ، بجاش نوشتن ca
        یه سوال دیگه ای هم که داشتم درمورد ترکیب این دو مطلب که گفتین  برای حذف کلی واترمارک ، راستش من زیاد با itextsharp  آشنایی ندارم کاش یه راهنمایی میکردین که چیکار باید بکنم تا بتونم حذفش کنم ، دوتا مطلب یه جورایی باهم تفاوت داشت چون یکی با استریم کار میکرد یکی با آبجکت ، لطف میکنین یه راهنمایی کنین.
        • #
          ‫۸ سال و ۵ ماه قبل، چهارشنبه ۲۵ فروردین ۱۳۹۵، ساعت ۰۲:۰۸
          راه حل حذف کلی، همان مطلب حذف لایه‌ها هست که عنوان شد؛ چون متن واترمارک، به صورت یک استریم مجزا به صفحه اضافه می‌شود:

          تصویر فوق مربوط هست به PDF مثال بحث جاری (متد createPdfFile ابتدای بحث). stream اول آن حاوی This is page است و stream دوم آن حاوی متن واترمارک. بنابراین یا از روش بحث جاری استفاده کنید که فقط متن واترمارک را نامرئی می‌کند و یا از روش حذف لایه‌ها، برای حذف استریم مجزای مربوط به واترمارک.
          • #
            ‫۸ سال و ۵ ماه قبل، چهارشنبه ۲۵ فروردین ۱۳۹۵، ساعت ۰۲:۱۹
            تشکر ، ولی بازم یه مشکلی هست اینکه بیشتر واترمارکها تصویرن یعنی یه لوگو در پس زمینه قرار گرفته و نمیشه مثل لایه متن باهاش برخورد کرد (تبدیل متن وارد شده به استریم سپس جستجو در محتوا )، دراین مورد چیکار باید کرد؟
            • #
              ‫۸ سال و ۵ ماه قبل، چهارشنبه ۲۵ فروردین ۱۳۹۵، ساعت ۰۳:۰۵
              با استفاده از itextrups فایل را آنالیز کنید تا مشخص شود stream آن قسمت کدام است.
              • #
                ‫۸ سال و ۵ ماه قبل، پنجشنبه ۲ اردیبهشت ۱۳۹۵، ساعت ۱۵:۴۴
                خیلی ممنون، من یکی از فایلهام که واترمارک داره رو با این برنامه باز کردم اما جدا استریم‌ها در هر page تعدادشون زیاده و گیج کننده ، چطور باید مشخص بشه که یک استریم مربوط به واترمارک یا یک متن خاص هستش؟
                شما خودتون چطور تشخیص دادین که این استریم مربوطه به واترمارک اون صفحه ست؟