‏نمایش پست‌ها با برچسب iTextSharp. نمایش همه پست‌ها
‏نمایش پست‌ها با برچسب iTextSharp. نمایش همه پست‌ها

۱۳۹۰/۱۱/۱۹

iTextSharp و نمايش صحيح تاريخ در متني راست به چپ


خروجي PDF زير را در نظر بگيريد:

مشكلي را در آن مشاهده مي‌كنيد؟ اصل آن يا صحيح آن بايد به شكل زير باشد:


و اين وارونه نمايش دادن‌ها، دقيقا مشكلي است كه حين كار با iTextSharp براي نمايش متني مثلا به همراه يك تاريخ شمسي وجود دارد. البته اين مشكل هم اساسا به خود استاندارد يونيكد برمي‌گردد كه يك سري كاراكتر را «كاراكتر ضعيف» معرفي كرده؛ براي مثال كاراكتر اسلش بكار رفته در يك تاريخ هم از اين دست است. بنابراين PDF توليدي توسط iTextSharp از ديد استاندارد يونيكد مشكلي ندارد، زيرا يك «نويسه ضعيف» مثل اسلش نمي‌تواند جهت را تغيير دهد؛ مگر اينكه از يك «نويسه قوي» براي دستكاري آن استفاده شود. براي مثال اين نويسه‌ها قوي هستند:

U+202A:   LEFT-TO-RIGHT EMBEDDING (LRE) 
U+202B:   RIGHT-TO-LEFT EMBEDDING (RLE) 
U+202D:   LEFT-TO-RIGHT OVERRIDE (LRO) 
U+202E:   RIGHT-TO-LEFT OVERRIDE (RLO) 
U+202C:   POP DIRECTIONAL FORMATTING (PDF) 

براي رسيدن به تصوير صحيح نمايش داده شده در بالا، متد FixWeakCharacters زير را تهيه كرده‌ام كه حداقل با iTextSharp جواب مي‌ده:

using System;
using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace RleTests
{
    class Program
    {
        const char RightToLeftEmbedding = (char)0x202B;
        const char PopDirectionalFormatting = (char)0x202C;

        static string FixWeakCharacters(string data)
        {
            if (string.IsNullOrWhiteSpace(data)) return string.Empty;
            var weakCharacters = new[] { @"\", "/", "+", "-", "=", ";", "$" };
            foreach (var weakCharacter in weakCharacters)
            {
                data = data.Replace(weakCharacter, RightToLeftEmbedding + weakCharacter + PopDirectionalFormatting);
            }
            return data;
        }

        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
                pdfDoc.Open();

                FontFactory.Register("c:\\windows\\fonts\\Arial.ttf");
                Font tahoma = FontFactory.GetFont("Arial", BaseFont.IDENTITY_H);

                var table1 = new PdfPTable(1);
                table1.WidthPercentage = 100;

                var pdfCell = new PdfPCell
                {
                    RunDirection = PdfWriter.RUN_DIRECTION_RTL,
                    Border = 0,
                    Phrase = new Phrase(FixWeakCharacters(
                        "تاريخ: " + "1390/11/18" + Environment.NewLine +
                        "شماره پروژه: " + "1/2/3/4/56" + Environment.NewLine +
                        "اسلش: " + " 12/A/13 " + Environment.NewLine +
                        "بك اسلش: " + "  12\\13\\14 " + Environment.NewLine +
                        "مساوي و جمع: " + " 2+3=5 " + Environment.NewLine +
                        "سمي كولون: " + " 2=1+1; " + Environment.NewLine +
                        "دلار: " + "12$" + Environment.NewLine +
                        "كاما: " + "12,34,67" + Environment.NewLine +
                        "نقطه: " + "12.34" + Environment.NewLine +
                        "پرانتز: " + "متن (ساده)"
                        ),
                        tahoma)
                };

                table1.AddCell(pdfCell);
                pdfDoc.Add(table1);

            }

            Process.Start("Test.pdf");
        }
    }
}

از اين نوع مشكلات حين كار با HTML هم هست؛ وارونه نمايش داده شدن تاريخ فارسي در بين يك متن راست به چپ. البته در آنجا راه حل زير هم توصيه شده (بدون نياز به دستكاري نويسه‌ها):

<span dir="ltr" style="display:inline">1390/11/19</span>

۱۳۹۰/۱۰/۱۴

تعيين تعداد رديف در صفحه جداول خودكار iTextSharp


پيشنياز : «تكرار خودكار سرستون‌هاي يك جدول در صفحات مختلف، توسط iTextSharp»
همانطور كه در مطلب پيشنياز عنوان شده ذكر گرديد، iTextSharp امكان درج خودكار header و footer به علاوه محاسبه خودكار تعداد رديف‌هاي يك جدول در يك صفحه را بر اساس طول و اندازه محتواي هر رديف، دارد. براي مثال يك صفحه ممكن است 2 رديف شود و يك صفحه 20 رديف. تمام اين‌ها را به صورت خودكار محاسبه مي‌كند و بسيار عالي است. (اين امكان مهمي است كه خيلي از ابزارهاي گزارشگيري موجود هنوز با آن مشكل دارند)
اما اگر فرض را بر اين بگذاريم كه اندازه سلول‌ها و در نتيجه طول هر رديف ثابت است و مثلا تمام صفحات نهايتا از يك تعداد رديف مشخص تشكيل خواهند شد، خاصيتي را به نام number of rows يا rows count و امثال آن‌را ندارد كه مثلا به آن گفت، من در هر صفحه فقط 5 رديف را مي‌خواهم نمايش دهم و نه 20 رديف را.
روش حل اين مساله را در ادامه ملاحظه خواهيد كرد و يك نكته‌ي خيلي ساده و مستند نشده دارد!

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace RowsCountSample
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
                pdfDoc.Open();

                var table1 = new PdfPTable(3);
                table1.HeaderRows = 2;
                table1.FooterRows = 1;

                //header row  
                var headerCell = new PdfPCell(new Phrase("header"));
                headerCell.Colspan = 3;
                headerCell.HorizontalAlignment = Element.ALIGN_CENTER;
                table1.AddCell(headerCell);

                //footer row  
                var footerCell = new PdfPCell(new Phrase("footer"));
                footerCell.Colspan = 3;
                footerCell.HorizontalAlignment = Element.ALIGN_CENTER;
                table1.AddCell(footerCell);

                //adding some rows  
                for (int i = 0; i < 70; i++)
                {
                    //adds a new row
                    table1.AddCell(new Phrase("Cell[0], Row[" + i + "]"));
                    table1.AddCell(new Phrase("Cell[1], Row[" + i + "]"));
                    table1.AddCell(new Phrase("Cell[2], Row[" + i + "]"));

                    //sets the number of rows per page
                    if (i > 0 && table1.Rows.Count % 7 == 0)
                    {
                        pdfDoc.Add(table1);
                        table1.DeleteBodyRows();
                        pdfDoc.NewPage();
                    }
                }

                pdfDoc.Add(table1);
            }

            //open the final file with adobe reader for instance.  
            Process.Start("Test.pdf");
        }
    }
}

نكته جديد اين مثال، قسمت زير است:


if (i > 0 && table1.Rows.Count % 7 == 0)
{
      pdfDoc.Add(table1);
      table1.DeleteBodyRows();
      pdfDoc.NewPage();
}

هر زمان كه table1 به صفحه اضافه شود، header و footer هم اضافه خواهند شد، اما اگر BodyRows آن حذف نشود،‌ دفعه‌ي دومي كه اين table به صفحه اضافه مي‌شود، شامل رديف‌هاي مثلا يك تا 10 خواهد بود بجاي 6 تا 10 .

۱۳۹۰/۱۰/۱۳

نصب خودكار اطلاعات فايل‌هاي PFX در سيستم


در مورد نحوه رمزنگاري فايل‌هاي PDF به كمك روش Public-key encryption توسط iTextSharp مطلبي را پيشتر در اين سايت مطالعه كرده‌ايد.
اين روش يك مشكل مهم دارد: «ارائه فايل PFX و همچنين كلمه عبور آن به كاربر نهايي»
خوب، اين يعني اينكه شما به راحتي مي‌تونيد اطلاعات را رمزگشايي كنيد؛ چون همه چيز سخاوتمندانه در اختيارتان است. بنابراين ضرورت رمزنگاري آن در ابتداي امر زير سؤال مي‌رود.
اكنون اين سؤال مطرح مي‌شود كه آيا مي‌توان اين اطلاعات را تا حد قابل قبولي مخفي كرد؟ مثلا يك برنامه را در اختيار كاربر قرار داد كه اطلاعات فايل PFX را به همراه كلمه عبور آن در سيستم نصب كند.
پاسخ:
دات نت به صورت توكار از اين نوع فايل‌هاي مجوز پشتيباني مي‌كند:

using System.Security.Cryptography.X509Certificates;

namespace InstallPfx
{
    class Program
    {
        private static void InstallCertificate(string cerFileName, string password)
        {
            var certificate = new X509Certificate2(cerFileName, password, X509KeyStorageFlags.PersistKeySet);
            var store = new X509Store(StoreName.My);
            store.Open(OpenFlags.ReadWrite);
            store.Add(certificate);
            store.Close();
        }

        static void Main(string[] args)
        {
            InstallCertificate(@"D:\forTest\file.pfx", "123456");
        }
    }
}

پس از اجراي كد فوق، امكان مشاهده فايل‌هاي PDF رمزنگاري شده به كمك اطلاعات فايل file.pfx، ميسر مي‌شود.
براي مشاهده اين مجوز نصب شده هم مي‌توان در ديالوگ Run ويندوز نوشت : certmgr.msc تا كنسول مديريتي مجوز‌هاي ويندوز ظاهر شود. سپس به قسمت personal certificates بايد مراجعه كرد.

۱۳۹۰/۰۹/۱۵

نمايش گراديان در iTextSharp


كتابخانه iTextSharp نمايش گرادياني از رنگ‌ها را هم پشتيباني مي‌كند و بديهي است اين نمايش برداري است. روش استفاده از آن هم بسيار ساده است؛ مثلا:

PdfShading shading = PdfShading.SimpleAxial(pdfWriter, x0, y0, x1, y1, BaseColor.YELLOW, BaseColor.RED);
PdfShadingPattern pattern = new PdfShadingPattern(shading);
ShadingColor color = new ShadingColor(pattern);

متد PdfShading.SimpleAxial بر اساس شيء PdfWriter كه توسط آن به Canvas صفحه دسترسي پيدا مي‌كند، در مختصاتي مشخص، يك طيف رنگي را ايجاد مي‌كند. بر اين اساس مي‌توان به يك ShadingColor هم رسيد كه از آن مثلا به عنوان BackgroundColor يك PdfPCell قابل استفاده است.
تا اينجا ساده به نظر مي‌رسد اما واقعيت اين است كه مختصات ذكر شده، مهم است و آن‌را در مورد مثلا سلول‌هاي يك جدول تنها در زمان تهيه نهايي يك جدول مي‌توان به دست آورد. البته اگر شيء ساده‌اي را روي صفحه رسم كنيم، اين مورد بر اساس مختصات ابتدايي شيء واضح به نظر مي‌رسد.
براي حل اين مشكل در مورد جداول و سلول‌هاي آن خاصيتي به نام CellEvent وجود دارد كه از نوع IPdfPCellEvent است. به عبارتي با ارسال يك وهله از كلاسي كه اينترفيس IPdfPCellEvent را پياده سازي مي‌كند، مي‌توان در زمان نمايش نهايي يك سلول به مختصات دقيق آن دسترسي پيدا كرد.
يك مثال كامل را در مورد پياده سازي IPdfPCellEvent و استفاده از آن جهت نمايش گراديان در Header و footer يك جدول، در ادامه مشاهده خواهيد نمود:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace iTextSharpGradientTest
{
    public class GradientCellEvent : IPdfPCellEvent
    {
        public void CellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases)
        {
            var cb = canvases[PdfPTable.BACKGROUNDCANVAS];
            cb.SaveState();

            var shading = PdfShading.SimpleAxial(
                                    cb.PdfWriter,
                                    position.Left, position.Top, position.Left, position.Bottom,
                                    BaseColor.YELLOW, BaseColor.ORANGE);
            var shadingPattern = new PdfShadingPattern(shading);

            cb.SetShadingFill(shadingPattern);
            cb.Rectangle(position.Left, position.Bottom, position.Width, position.Height);
            cb.Fill();

            cb.RestoreState();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
                pdfDoc.Open();

                var table1 = new PdfPTable(1);
                table1.HeaderRows = 2;
                table1.FooterRows = 1;

                //header row  
                var headerCell = new PdfPCell(new Phrase("header"))
                {
                    HorizontalAlignment = Element.ALIGN_CENTER,
                    Border = 0
                };
                headerCell.CellEvent = new GradientCellEvent();
                table1.AddCell(headerCell);

                //footer row  
                var footerCell = new PdfPCell(new Phrase("footer"))
                {
                    HorizontalAlignment = Element.ALIGN_CENTER,
                    Border = 0
                };
                footerCell.CellEvent = new GradientCellEvent();
                table1.AddCell(footerCell);

                //adding some rows  
                for (int i = 0; i < 70; i++)
                {
                    var rowCell = new PdfPCell(new Phrase("Row " + i)) { BorderColor = BaseColor.LIGHT_GRAY };
                    table1.AddCell(rowCell);
                }

                pdfDoc.Add(table1);
            }

            //open the final file with adobe reader for instance.  
            Process.Start("Test.pdf");
        }
    }
}

با خروجي:


نكته مهم اين مثال نحوه مقدار دهي CellEvent است. به اين ترتيب در زمان نمايش نهايي يك سلول مي‌توان در متد CellLayout، به خواص فقط خواندني آن سلول دسترسي يافت. مثلا position، مختصات نهايي مستطيل مرتبط با سلول جاري را بر مي‌گرداند؛ يا از طريق canvases مي‌توان براي آخرين بار فرصت يافت تا در يك سلول نقاشي كرد.


۱۳۹۰/۰۹/۱۲

شرح حال ابزارهاي گزارشگيري موجود


مدتي هست كه در حال تهيه يك كتابخانه گزارشگيري بر پايه iTextSharp هستم. براي تهيه backlog هم چه جايي بهتر از بررسي سؤالات موجود در انجمن‌ها؛ چيزي مثل اين:


بله، تاپيكي با 13 صفحه كه حتي يك مورد از درخواست‌هاي آن هم داراي پاسخ نبود؛ اما باز هم كاربران با علاقه هرچه تمام‌تر يا مي‌دونيد، از روي عجز درخواستشون رو مطرح مي‌كردند و كسي نبود كه جواب بده. حقيقتش اين است كه مشكل از افراد نيست يا اينكه «كسي نبود» يا «كسي نخواست» كه جواب بده. مشكل اين است كه اكثر برنامه‌هاي گزارشگيري يا گزارش سازي موجود در حد يك Demo ware هستند. «نمي‌تونند» با مشكلات واقعي كاري موجود (در طي 13 صفحه كه ذكر شد) راحت كنار بيان و راه حل بدرد بخوري رو ارائه بدن.

۱۳۹۰/۰۹/۰۸

استفاده از كنترل‌هاي Active-X در WPF


گاهي از اوقات شايد نياز شود تا از يك كنترل Active-X در WPF استفاده شود؛ مثلا هيچ نمايش دهنده‌ي PDF ايي را در ويندوز نمي‌توان يافت كه امكانات و كيفيت آن در حد Acrobat reader و Active-X آن باشد. يك روش استفاده از آن‌را به كمك كنترل WebBrowser در WPF پيشتر در اين سايت مطالعه كرده‌ايد. روش معرفي شده براي WinForm هم در WPF قابل استفاده است كه در ادامه شرح آ‌ن‌ خواهد آمد.

الف) بجاي اضافه كردن يك User control مخصوص WPF يك user control از نوع WinForms را به يك پروژه WPF اضافه كنيد.


سپس مراحل مشابهي را مانند حالت WinForms، بايد طي كرد:
ب) در VS.NET‌ از طريق منوي Tools گزينه‌ي Choose toolbox items ، برگه‌ي Com components را انتخاب كنيد.
ج) سپس گزينه‌ي Adobe PDF reader را انتخاب نمائيد و بر روي دكمه‌ي OK‌ كليك كنيد.


د) اكنون اين كنترل جديد را بر روي فرم user control قسمت الف برنامه قرار دهيد. به صورت خودكار COMReference هاي متناظر هم به پروژه اضافه مي‌شوند.
پس از اينكه كنترل بر روي فرم قرار گرفت بهتر است به خواص آن مراجعه كرده و خاصيت Dock آن‌را با Fill مقدار دهي كرد تا كنترل به صورت خودكار در هر اندازه‌اي كل ناحيه‌ي متناظر را پوشش دهد.


كد‌هاي مرتبط با نمايش فايل PDF اين كنترل هم به شرح زير است:

using System.Windows.Forms;

namespace WpfPdfViewer.Controls
{
    public partial class AcroReader : UserControl
    {
        public AcroReader(string fileName)
        {
            InitializeComponent();
            ShowPdf(fileName);
        }

        public void ShowPdf(string fileName)
        {
            if (string.IsNullOrWhiteSpace(fileName)) return;
            axAcroPDF1.LoadFile(fileName);
            axAcroPDF1.setShowToolbar(true);
            axAcroPDF1.Show();
        }
    }
}


خوب، ما تا اينجا يك كنترل Active-X را از طريق يك User controls مخصوص WinForms به پروژه‌ي WPF جاري اضافه كرده‌ايم. براي اينكه بتوانيم اين كنترل را درون مثلا يك User control از جنس WPF و XAML نمايش دهيم بايد از كنترل WindowsFormsHost استفاده كرد. براي اين منظور نياز است تا ارجاعي را به اسمبلي WindowsFormsIntegration اضافه كنيم. پس از آن كنترل ياد شده قابل استفاده خواهد بود.


براي نمونه كدهاي XAML پنجره اصلي برنامه مي‌تواند به صورت زير باشد:

<Window x:Class="WpfPdfViewer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <WindowsFormsHost x:Name="WindowsFormsHost1" />
    </Grid>
</Window>

سپس جهت استفاده از كنترل WindowsFormsHost خواهيم داشت:

using WpfPdfViewer.Controls;

namespace WpfPdfViewer
{
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();
            WindowsFormsHost1.Child = new AcroReader(@"PageSummary.pdf");
        }
    }
}

فقط كافي است شيء Child اين كنترل را با وهله‌اي از يوزركنترل AcroReader اضافه شده به برنامه مقدار دهي كنيم.

سؤال: اين روش زياد MVVM friendly نيست. به عبارتي Child را نمي‌توان از طريق Binding مقدار دهي كرد. آيا راهي براي آن وجود دارد؟
پاسخ: بله. روش متداول براي حل اين نوع مشكلات، نوشتن يك DependencyObject و Attached property مناسب مي‌باشد كه به آن‌ها Behaviors هم مي‌گويند. براي مثال يك نمونه از اين پياده سازي را در ذيل مشاهده مي‌كنيد:

using System;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Forms.Integration;

namespace WpfPdfViewer.Behaviors
{
    public class WindowsFormsHostBehavior : DependencyObject
    {
        public static readonly DependencyProperty BindableChildProperty =
                                    DependencyProperty.RegisterAttached("BindableChild",
                                    typeof(Control),
                                    typeof(WindowsFormsHostBehavior),
                                    new UIPropertyMetadata(null, BindableChildPropertyChanged));

        public static Control GetBindableChild(DependencyObject obj)
        {
            return (Control)obj.GetValue(BindableChildProperty);
        }

        public static void SetBindableChild(DependencyObject obj, Control value)
        {
            obj.SetValue(BindableChildProperty, value);
        }

        public static void BindableChildPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            var windowsFormsHost = o as WindowsFormsHost;
            if (windowsFormsHost == null)
                throw new InvalidOperationException("This behavior can only be attached to a WindowsFormsHost.");

            var control = (Control)e.NewValue;
            windowsFormsHost.Child = control;
        }
    }
}

كه نهايتا براي استفاده از آن خواهيم داشت:

<WindowsFormsHost 
       Behaviors:WindowsFormsHostBehavior.BindableChild="{Binding ...}" />

و در ViewModel برنامه هم مانند مثال فوق، فقط كافي است يك وهله از new AcroReader به اين خاصيت قابل انقياد از نوع Control، انتساب داده شود.
يا حتي مي‌توان بجاي نوشتن يك BindableChild، براي مثال مسير فايل pdf را به DependencyObject تعريف شده ارسال كرد و سپس در همانجا اين وهله سازي و انتسابات صورت گيرد (بجاي ViewModel برنامه كه اينبار فقط مسير را تنظيم مي‌كند).


۱۳۹۰/۰۹/۰۱

ساخت يك گزارش ساز به كمك iTextSharp و Open Office


iTextSharp پايه كار با فايل‌هاي PDF را ارائه مي‌دهد اما ابزاري را جهت ساده‌تر سازي توليد فايل‌هاي PDF به همراه ندارد؛ هر چند مثلا امكان تبديل HTML به PDF را دارا است اما بايد گفت: «تا حدودي البته». اگر نياز باشد جدولي را ايجاد كنيم بايد كد نويسي كرد، اگر نياز باشد تصويري اضافه شود به همين ترتيب و الي آخر. البته اين را هم بايد در نظر داشت كه كد نويسي انعطاف قابل توجهي را در اختيار برنامه نويس قرار مي‌دهد؛ شايد به همين دليل اين روزها مباحث «Code first» بيشتر مورد توجه برنامه نويس‌ها است، تا مباحث «Wizard first» يك دهه قبل!
اما باز هم داشتن يك طراح بد نيست و مي‌تواند در كاهش مدت زمان توليد نهايي يك فايل PDF مؤثر باشد. براي اين منظور مي‌توان از برنامه‌ي رايگان و معروف Open office استفاده كرد. توسط آن مي‌توان يك فرم PDF را طراحي و سپس فيلدهاي آن‌را (اين قالب تهيه شده را) با iTextSharp پر كرد. اين مورد مي‌تواند براي تهيه گزارش‌هايي كه تهيه آن‌ها با ابزارهاي متداول گزارش سازي عموما ميسر نيست،‌ بسيار مناسب باشد.


طراحي يك فرم PDF با استفاده از برنامه Open Office

آخرين نگارش برنامه Open office را از اينجا مي‌توانيد دريافت كنيد و آنچنان حجمي هم ندارد؛ حدودا 154 مگابايت است.
پس از نصب و اجراي برنامه، حداقل به دو طريق مي‌توان يك فرم جديد را شروع كرد:
الف) آغاز يك XML Form document جديد در Open office سبب خواهد شد كه نوارهاي ابزار طراحي فرم، مانند قرار دادن TextBox ، CheckBox و غيره به صورت خودكار ظاهر شوند.
ب) و يا آغاز يك سند معمولي و سپس مراجعه به منوي View->Toolbars->Form Controls هم همان حالت را به همراه خواهد داشت.


در اينجا براي طراحي يك گزارش يا فرم جديد تنها كافي است همانند روش‌هاي متداول تهيه يك سند معمولي رفتار كنيم و مواردي را كه قرار است توسط iTextSharp مقدار دهي كنيم، با كنترل‌هاي نوار ابزار Form آن بر روي صفحه قرار دهيم كه نمونه‌ي ساده آن‌را در شكل زير ملاحظه مي‌كنيد:


براي گزارش‌هاي فارسي بهتر است Alignment يك كنترل به Right تنظيم شود و Border به حالت Without frame مقدار دهي گردد. نام اين كنترل را هم بخاطر بسپاريد و يا تغيير دهيد. از اين نام‌ها در iTextSharp استفاده خواهيم كرد. (صفحه خواص فوق با دوبار كليك بر روي يك كنترل قرار گرفته بر روي فرم ظاهر مي‌شود)

مرحله بعد، تبديل اين فرم به فايل PDF است. كليك بر روي دكمه تهيه خروجي به صورت PDF در نوار ابزار اصلي آن براي اينكار كفايت مي‌كند. اين گزينه در منوي File نيز موجود است.


فرم‌هاي PDF تهيه شده در اينجا، فقط خواندني هستند. مثلا يك كاربر مي‌تواند آن‌ها را پر كرده و چاپ كند. اما ما از آن‌ها در ادامه به عنوان قالب گزارشات استفاده خواهيم كرد. بنابراين جهت ويرايش فرم‌هاي تهيه شده بهتر است فايل‌هاي اصلي Open Office مرتبط را نيز درجايي نگهداري كرد و هر بار پس از ويرايش، نياز است تا خروجي جديد PDF آن‌ها تهيه شود.


استفاده از iTextSharp جهت مقدار دهي فيلدهاي يك فرم PDF

در ادامه مي‌خواهيم اين قالب گزارشي را كه تهيه كرديم با كمك iTextSharp پر كرده و يك فايل PDF جديد تهيه كنيم. سورس كامل اينكار را در ذيل مشاهده مي‌كنيد:


using System;
using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PdfForm
{
    class Program
    {
        //روش صحيح ثبت و معرفي فونت در اين كتابخانه
        public static iTextSharp.text.Font GetTahoma()
        {
            var fontName = "Tahoma";
            if (!FontFactory.IsRegistered(fontName))
            {
                var fontPath = Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf";
                FontFactory.Register(fontPath);
            }
            return FontFactory.GetFont(fontName, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
        }

        static void Main(string[] args)
        {
            string fileNameExisting = @"form.pdf";
            string fileNameNew = @"newform.pdf";

            using (var existingFileStream = new FileStream(fileNameExisting, FileMode.Open))
            using (var newFileStream = new FileStream(fileNameNew, FileMode.Create))
            {
                var pdfReader = new PdfReader(existingFileStream);
                using (var stamper = new PdfStamper(pdfReader, newFileStream))
                {
                    //نكته مهم جهت كار با اطلاعات فارسي
                    //در غيراينصورت شاهد ثبت اطلاعات نخواهيد بود
                    stamper.AcroFields.AddSubstitutionFont(GetTahoma().BaseFont);

                    //form.Fields.Keys = تمام فيلدهاي موجود در فرم
                    var form = stamper.AcroFields;                   

                    //مقدار دهي فيلدهاي فرم
                    form.SetField("TextBox1", "مقدار1");
                    form.SetField("TextBox2", "مقدار2");

                    // "Yes" and "Off" are valid values here
                    form.SetField("Check Box 1", "Yes");

                    // "" and "Off" are valid values here
                    form.SetField("Option Button 1", "");

                    // نحوه مقدار دهي ليست
                    form.SetListOption("ListBox1", new[] { "1مقدار يك", "مقدار دو1" }, null);
                    form.SetField("ListBox1", null);

                    // به اين ترتيب فرم ديگر توسط كاربر قابل ويرايش نخواهد بود
                    //stamper.PartialFormFlattening --> جهت غيرقابل ويرايش نمودن فيلدي مشخص
                    stamper.FormFlattening = true;

                    stamper.Close();
                    pdfReader.Close();
                }
            }

            Process.Start("newform.pdf");
        }
    }
}

توضيحات:
چون در اينجا فايل PDF، از پيش تهيه شده است، پس بايد از اشياء PdfReader و PdfStamper جهت خواندن و نوشتن اطلاعات در آن‌ها استفاده كرد. سپس توسط شيء stamper.AcroFields مي‌توان به اين فيلدها يا همان كنترل‌هايي كه در برنامه‌ي Open office بر روي فرم قرار داديم، دسترسي پيدا كنيم.
در ابتدا نياز است فونت اين فيلدها توسط متد AddSubstitutionFont مقدار دهي شود. اين مورد براي گزارش‌هاي فارسي الزامي است؛ در غيراينصورت متني را در خروجي مشاهده نخواهيد كرد.
ادامه كار هم مشخص است. توسط متد form.SetField مقداري را به كنترل‌هاي قرار گرفته بر روي فرم نسبت مي‌دهيم. آرگومان اول آن نام كنترل است و آرگومان دوم، مقدار مورد نظر مي‌باشد. اگر كنترل CheckBox را بر روي صفحه قرار داديد، تنها مقدارهاي Yes و Off را مي‌پذيرد (آن هم با توجه به اينكه به كوچكي و بزرگي حروف حساس است). اگر يك Radio button يا در اينجا Option button را بر روي فرم قرار داديد، تنها مقدارهاي خالي و Off را قبول خواهد كرد. نحوه‌ي مقدار دهي يك ليست هم در اينجا ذكر شده است.
در پايان چون نمي‌خواهيم كاربر نهايي قادر به ويرايش اطلاعات باشد، FormFlattening را true خواهيم كرد و به اين ترتيب، كنترل‌ها فقط خواندني خواهند شد. البته اگر همانطور كه ذكر شد، border كنترل‌ها را در حين طراحي حذف كنيد، PDF نهايي توليدي يكپارچه و يك دست به نظر مي‌رسد و اصلا مشخص نخواهد بود كه اين فايل پيشتر يك فرم قابل پر كردن بوده است.

۱۳۹۰/۰۸/۲۹

اضافه كردن پيوست به فايل‌هاي PDF با استفاده از iTextSharp


فايل PDF موجود عجيب و غريبي است. مي‌شود به آن فايل پيوست اضافه كرد. مثلا اگر يك راهنماي آموزشي را با فرمت PDF تهيه مي‌كنيد، لازم نيست تا فايل‌هاي مرتبط با آن‌را جداگانه ارائه دهيد. مي‌شود تمام اين‌ها را داخل همان فايل PDF مدفون كرد. روش انجام اينكار به كمك iTextSharp ساده است اما چند نكته را نيز به همراه دارد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PDFAttachment
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));                
                pdfDoc.Open();

                pdfDoc.Add(new Phrase("Test"));

                var fs = PdfFileSpecification.FileEmbedded(pdfWriter, @"C:\path\logo.png", "logo.png", null);
                pdfWriter.AddFileAttachment("توضيحات",fs);
            }

            Process.Start("Test.pdf");
        }
    }
}

در ساده‌ترين حالت ممكن، با استفاده از متد AddFileAttachmen شيء PdfWriter مي‌توان پيوستي را به يك فايل PDF در حال توليد اضافه كرد. اگر به فايل نهايي مراجعه كنيم و همچنين قسمت attachments را هم دستي در Adobe reader انتخاب نمائيم، شكل زير حاصل خواهد شد:


روش متداول بكارگرفته شده دو مشكل را به همراه دارد:
  • قسمت modified مقدار دهي نشده است.
  • پنل مربوط به پيوست‌ها بايد دستي باز شود.

نحوه مقدار دهي ستون modified پس از تعريف يك PdfDictionary و قرار دادن PdfName.MODDATE در آن، به صورت زير است:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PDFAttachment
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));                
                pdfDoc.Open();

                pdfDoc.Add(new Phrase("Test"));

                var filePath = @"C:\path\logo.png";
                var fileInfo = new FileInfo(filePath);
                var pdfDictionary = new PdfDictionary();
                pdfDictionary.Put(PdfName.MODDATE, new PdfDate(fileInfo.LastWriteTime));
                var fs = PdfFileSpecification.FileEmbedded(pdfWriter, filePath, fileInfo.Name, null, true, null, pdfDictionary);
                pdfWriter.AddFileAttachment("توضيحات", fs);
            }

            Process.Start("Test.pdf");
        }
    }
}

كه اينبار خروجي زير را به همراه دارد:


و براي نمايش خودكار پنل پيوست‌ها در Adobe reader به طوري كه كاربر نهايي متوجه وجود اين فايل‌هاي پيوست شده گردد، مي‌توان ViewerPreferences شيء pdfWriter را مقدار دهي نمود:

pdfWriter.ViewerPreferences = PdfWriter.PageModeUseAttachments;

در مورد فايل‌هاي موجود چطور؟ آيا مي‌توان به يك فايل PDF از پيش تهيه شده هم فايل پيوست كرد؟
پاسخ: بله. بايد از امكانات شيء PdfReader استفاده كرد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PDFAttachment
{
    class Program
    {
        static void Main(string[] args)
        {
            var reader = new PdfReader("Test.pdf");
            using (var stamper = new PdfStamper(reader, new FileStream("newTest.pdf", FileMode.Create)))
            {
                var filePath = @"C:\path\logo.png";
                addAttachment(stamper, filePath, "توضيحات");
                stamper.Close();
            }

            Process.Start("newTest.pdf");
        }

        private static void addAttachment(PdfStamper stamper, string filePath, string description)
        {
            var fileInfo = new FileInfo(filePath);
            var pdfDictionary = new PdfDictionary();
            pdfDictionary.Put(PdfName.MODDATE, new PdfDate(fileInfo.LastWriteTime));
            var pdfWriter = stamper.Writer;
            var fs = PdfFileSpecification.FileEmbedded(pdfWriter, filePath, fileInfo.Name, null, true, null, pdfDictionary);
            stamper.AddFileAttachment(description, fs);
        }
    }
}

در اينجا به كمك كلاس PdfReader، يك فايل موجود خوانده شده و سپس با استفاده از امكانات كلاس PdfStamper كه خاصيت Writer آن همان pdfWriter است مي‌توان فايل مورد نظر را به فايل موجود افزود.


۱۳۹۰/۰۸/۲۱

رمزنگاري فايل‌هاي PDF با استفاده از كليد عمومي توسط iTextSharp


دو نوع رمزنگاري را مي‌توان توسط iTextSharp به PDF توليدي و يا موجود، اعمال كرد:
الف) رمزنگاري با استفاده از كلمه عبور
ب) رمزنگاري توسط كليد عمومي

الف) رمزنگاري با استفاده از كلمه عبور
در اينجا امكان تنظيم read password و edit password به كمك متد SetEncryption شيء pdfWrite وجود دارد. همچنين مي‌توان مشخص كرد كه مثلا آيا كاربر مي‌تواند فايل PDF را چاپ كند يا خير (PdfWriter.ALLOW_PRINTING).
ذكر read password اختياري است؛ اما جهت اعمال permissions حتما نياز است تا edit password ذكر گردد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Text;

namespace EncryptPublicKey
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));

                var readPassword = Encoding.UTF8.GetBytes("123");//it can be null.
                var editPassword = Encoding.UTF8.GetBytes("456");
                int permissions = PdfWriter.ALLOW_PRINTING | PdfWriter.ALLOW_COPY;
                pdfWriter.SetEncryption(readPassword, editPassword, permissions, PdfWriter.STRENGTH128BITS);

                pdfDoc.Open();

                pdfDoc.Add(new Phrase("tst 0"));
                pdfDoc.NewPage();
                pdfDoc.Add(new Phrase("tst 1"));
            }

            Process.Start("TestEnc.pdf");
        }
    }
}


اگر read password ذكر شود، كاربران براي مشاهده محتويات فايل نياز خواهند داشت تا كلمه‌ي عبور مرتبط را وارد نمايند:


اين روش آنچنان امنيتي ندارد. هستند برنامه‌هايي كه اين نوع فايل‌ها را «آني» به نمونه‌ي غير رمزنگاري شده تبديل مي‌كنند (حتي نيازي هم ندارند كه از شما كلمه‌ي عبوري را سؤال كنند). بنابراين اگر كاربران شما آنچنان حرفه‌اي نيستند، اين روش خوب است؛ در غيراينصورت از آن صرفنظر كنيد.


ب) رمزنگاري توسط كليد عمومي
اين روش نسبت به حالت الف بسيار پيشرفته‌تر بوده و امنيت قابل توجهي هم دارد و «نيستند» برنامه‌هايي كه بتوانند اين فايل‌ها را بدون داشتن اطلاعات كافي، به سادگي رمزگشايي كنند.

براي شروع به كار با public key encryption نياز است يك فايل PFX يا Personal Information Exchange داشته باشيم. يا مي‌توان اين نوع فايل‌ها را از CA's يا Certificate Authorities خريد، كه بسيار هم نيكو يا اينكه مي‌توان فعلا براي آزمايش، نمونه‌ي self signed اين‌ها را هم تهيه كرد. مثلا با استفاده از اين برنامه.


در ادامه نياز خواهيم داشت تا اطلاعات اين فايل PFX را جهت استفاده توسط iTextSharp استخراج كنيم. كلاس‌هاي زير اينكار را انجام مي‌دهند و نهايتا كليدهاي عمومي و خصوصي ذخيره شده در فايل PFX را بازگشت خواهند داد:

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.X509;

namespace EncryptPublicKey
{
    /// <summary>
    /// A Personal Information Exchange File Info
    /// </summary>
    public class PfxData
    {
        /// <summary>
        /// Represents an X509 certificate
        /// </summary>
        public X509Certificate[] X509PrivateKeys { set; get; }

        /// <summary>
        /// Certificate's public key
        /// </summary>
        public ICipherParameters PublicKey { set; get; }
    }
}

using System;
using System.IO;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;

namespace EncryptPublicKey
{
    /// <summary>
    /// A Personal Information Exchange File Reader
    /// </summary>
    public class PfxReader
    {
        X509Certificate[] _chain;
        AsymmetricKeyParameter _asymmetricKeyParameter;
       
        /// <summary>
        /// Reads A Personal Information Exchange File.
        /// </summary>
        /// <param name="pfxPath">Certificate file's path</param>
        /// <param name="pfxPassword">Certificate file's password</param>
        public PfxData ReadCertificate(string pfxPath, string pfxPassword)
        {
            using (var stream = new FileStream(pfxPath, FileMode.Open, FileAccess.Read))
            {
                var pkcs12Store = new Pkcs12Store(stream, pfxPassword.ToCharArray());
                var alias = findThePublicKey(pkcs12Store);
                _asymmetricKeyParameter = pkcs12Store.GetKey(alias).Key;
                constructChain(pkcs12Store, alias);
                return new PfxData { X509PrivateKeys = _chain, PublicKey = _asymmetricKeyParameter };
            }
        }

        private void constructChain(Pkcs12Store pkcs12Store, string alias)
        {
            var certificateChains = pkcs12Store.GetCertificateChain(alias);
            _chain = new X509Certificate[certificateChains.Length];

            for (int k = 0; k < certificateChains.Length; ++k)
                _chain[k] = certificateChains[k].Certificate;
        }

        private static string findThePublicKey(Pkcs12Store pkcs12Store)
        {
            string alias = string.Empty;
            foreach (string entry in pkcs12Store.Aliases)
            {
                if (pkcs12Store.IsKeyEntry(entry) && pkcs12Store.GetKey(entry).Key.IsPrivate)
                {
                    alias = entry;
                    break;
                }
            }

            if (string.IsNullOrEmpty(alias))
                throw new NullReferenceException("Provided certificate is invalid.");

            return alias;
        }
    }
}


اكنون رمزنگاري فايل PDF توليدي توسط كليد عمومي، به سادگي چند سطر كد زير خواهد بود:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace EncryptPublicKey
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));

                var certs = new PfxReader().ReadCertificate(@"D:\path\cert.pfx", "123");
                pdfWriter.SetEncryption(
                          certs: certs.X509PrivateKeys,
                          permissions: new int[] { PdfWriter.ALLOW_PRINTING, PdfWriter.ALLOW_COPY },
                          encryptionType: PdfWriter.ENCRYPTION_AES_128);

                pdfDoc.Open();

                pdfDoc.Add(new Phrase("tst 0"));
                pdfDoc.NewPage();
                pdfDoc.Add(new Phrase("tst 1"));
            }

            Process.Start("Test.pdf");
        }
    }
}

پيش از فراخواني متد Open بايد تنظيمات رمزنگاري مشخص شوند. در اينجا ابتدا فايل PFX خوانده شده و كليدهاي عمومي و خصوصي آن استخراج مي‌شوند. سپس به متد SetEncryption جهت استفاده نهايي ارسال خواهند شد.

نحوه استفاده از اين نوع فايل‌هاي رمزنگاري شده:
اگر سعي در گشودن اين فايل رمزنگاري شده نمائيم با خطاي زير مواجه خواهيم شد:


كاربران براي اينكه بتوانند اين فايل‌هاي PDF را بار كنند نياز است تا فايل PFX شما را در سيستم خود نصب كنند. ويندوز فايل‌هاي PFX را مي‌شناسد و نصب آن‌ها با دوبار كليك بر روي فايل و چندبار كليك بر روي دكمه‌ي Next و وارد كردن كلمه عبور آن، به پايان مي‌رسد.

سؤال: آيا مي‌توان فايل‌هاي PDF موجود را هم به همين روش رمزنگاري كرد؟
بله. iTextSharp علاوه بر PdfWriter داراي PdfReader نيز مي‌باشد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace EncryptPublicKey
{
    class Program
    {
        static void Main(string[] args)
        {
            PdfReader reader = new PdfReader("TestDec.pdf");
            using (var stamper = new PdfStamper(reader, new FileStream("TestEnc.pdf", FileMode.Create)))
            {
                var certs = new PfxReader().ReadCertificate(@"D:\path\cert.pfx", "123");
                stamper.SetEncryption(
                         certs: certs.X509PrivateKeys,
                         permissions: new int[] { PdfWriter.ALLOW_PRINTING, PdfWriter.ALLOW_COPY },
                         encryptionType: PdfWriter.ENCRYPTION_AES_128);
                stamper.Close();
            }

            Process.Start("TestEnc.pdf");
        }
    }
}


سؤال: آيا مي‌توان نصب كليد عمومي را خودكار كرد؟
سورس برنامه SelfCert كه معرفي شد، در دسترس است. اين برنامه قابليت انجام نصب خودكار مجوزها را دارد.

۱۳۹۰/۰۸/۱۲

نكته‌اي تكميلي در مورد مجوز استفاده از iTextSharp


يكي از سوء برداشت‌هاي متداول از كارهاي سورس باز موجود اين است:
«من مجازم از اين كتابخانه‌ي سورس باز هرجايي و هر طوري كه دوست دارم استفاده كنم.»

در كل اين يك «توهم» بزرگ است. بسته به مجوز پروژه (^)، جمله‌ي فوق مي‌تواند صحيح يا كاملا نادرست باشد.
براي نمونه من خيلي‌ها رو مي‌بينم كه مي‌گن: «از MySQL استفاده كن كه رايگانه». نه دوست عزيز؛ اشتباه مي‌كنيد! فقط براي كارهاي سورس باز رايگان است. مجوز نگارش Community و رايگان آن در رده‌ي مجوز‌هاي GPL است (^). به اين معنا كه اگر روزي مطابق قوانين كپي رايت قرار شد رفتار شود، به سراغ كار سورس بسته شما كه دارد از MySQL رايگان استفاده مي‌كند، خواهند آمد. جهت اطلاع!
به همين جهت كساني كه كار تجاري سورس بسته انجام مي‌دهند از طرف كتابخانه‌هاي داراي مجوز GPL حتي رد هم نمي‌شوند؛ چه برسد به اينكه بخواهند آزادانه از آن استفاده كنند.

در مورد مجوز كتابخانه‌ي iTextSharp پيشتر مطلبي را در اين سايت خوانده‌ايد:
مجوز اين كتابخانه، GNU Affero General Public License است. به اين معنا كه شما موظفيد، تغييري در قسمت تهيه كننده خواص فايل PDF توليدي كه به صورت خودكار به نام كتابخانه تنظيم مي‌شود، ندهيد. اگر مي‌خواهيد اين قسمت را تغيير دهيد بايد هزينه كنيد. همچنين با توجه به اينكه اين مجوز، GPL است يعني زمانيكه از آن استفاده كرديد بايد كار خود را به صورت سورس باز ارائه دهيد (^).

و ... نكته تكميلي مهم اينكه:
اين كتابخانه تا نگارش 4.1.7 تحت مجوز MPL/LGPL ارائه شده و «بدون مشكل» در كارهاي تجاري سورس بسته قابل استفاده است. از نگارش 5 به بعد، AGPL شده و براي كارهاي تجاري سورس بسته «رايگان نيست» (^).
براي نمونه سورس نسخه 4.1.7 از اين آدرس قابل دريافت است.
اين سورس را از پروژه "FDFToolkit .NET" اينجا نقل كردم چون تهيه كننده اين پروژه دقيقا به اين مطلب اشاره كرده و كار خود را به نگارش 4.1.7 كتابخانه iTextSharp عمدا محدود كرده است.

۱۳۹۰/۰۷/۲۳

استفاده از گرافيك برداري در iTextSharp



در مورد «ترسيم اشكال گرافيكي با iTextSharp» مطلب مفصلي را در اينجا مي‌توانيد مطالعه كنيد؛ كه قصد تكرار مجدد آن‌را ندارم. فقط اين روش‌ها يك مشكل مهم دارند : «كار من ترسيم اين نوع اشكال گرافيكي نيست!». مثلا من الان نياز دارم در گزارشي، بجاي ستون Boolean آن در مواردي كه مقدار رديف true هست، مثلا يك «چك مارك» را بجاي true/false يا بله/خير نمايش دهم. مي‌شود اينكار را با يك تصوير معمولي هم انجام داد. فقط حجم فايل حاصل، بيش از اندازه بالا مي‌رود و همچنين نتيجه استفاده از يك bitmap، به زيبايي بكارگيري گرافيك برداري با قابليت تغيير ابعاد بدون نگراني در مورد از دست دادن كيفيت آن، نيست.

خوشبختانه هستند سايت‌هايي كه اين نوع تصاوير برداري را به رايگان ارائه دهند؛ براي مثال: سايت Openclipart، تعداد قابل توجهي فايل با فرمت SVG دارد. فايل‌هاي SVG را مستقيما نمي‌توان توسط iTextSharp استفاده كرد؛ اما يك سري برنامه‌ي كمكي براي تبديل فرمت SVG به مثلا XAML (قابل توجه برنامه نويس‌هاي WPF و Silverlight) يا WMF و غيره وجود دارد. براي نمونه iTextSharp امكان خواندن فايل‌هاي WMF را داشته (توسط همان متد معروف Image.GetInstance آن) و اينبار اين Image حاصل، يك تصوير برداري است و نه يك Bitmap.
در بين اين برنامه‌هاي تبديل كننده‌ فرمت‌هاي برداري، برنامه‌ي معروف و سورس باز Inkscape، در صدر محبوبيت قرار دارد. تنها كافي است فايل SVG خود را در آن گشوده و سپس به انواع و اقسام فرمت‌هاي ديگر تبديل (Save As) كنيد:



يكي از فرمت‌هاي جالب خروجي آن، Tex است (مربوط به يك برنامه اديتور، به نام LaTeX است). فرض كنيد يكي از اين «چك مارك»هاي سايت Openclipart را در برنامه Inkscape باز كرده‌ و سپس با فرمت Tex ذخيره كرده‌ايم. خروجي فايل متني آن مثلا به شكل زير خواهد بود:

%LaTeX with PSTricks extensions
%%Creator: 0.48.0
%%Please note this file requires PSTricks extensions
\psset{xunit=.5pt,yunit=.5pt,runit=.5pt}
\begin{pspicture}(190,190)
{
\newrgbcolor{curcolor}{0 0 0}
\pscustom[linestyle=none,fillstyle=solid,fillcolor=curcolor]
{
\newpath
\moveto(52.73079005,101.89500456)
\curveto(31.29686559,101.89500456)(13.84575258,84.04652127)(13.8457479,62.12456369)
\curveto(13.8457479,40.20259605)(31.29686559,22.35412714)(52.73079005,22.35412235)
\curveto(74.16470983,22.35412235)(91.6158322,40.20259605)(91.61582751,62.12456369)
\curveto(91.61582751,71.60188248)(88.48023622,80.07729424)(83.15553076,87.02034164)
\lineto(79.49425309,82.58209245)
\curveto(84.13622847,76.73639073)(85.95313131,70.24630402)(85.95313131,62.12456369)
\curveto(85.95313131,43.33817595)(71.09893654,28.1547277)(52.73079005,28.1547277)
\curveto(34.36263419,28.15473249)(19.50844879,43.33817595)(19.50844879,62.12456369)
\curveto(19.50844879,80.91094185)(34.36264355,96.10336589)(52.73079005,96.10336589)
\curveto(58.55122776,96.10336589)(62.90459266,95.2476225)(67.65721002,92.5630926)
\lineto(71.13570481,97.23509821)
\curveto(65.57113223,100.3782653)(59.52269945,101.89500456)(52.73079005,101.89500456)
\closepath
}
}
{
\newrgbcolor{curcolor}{0 0 0}
\pscustom[linestyle=none,fillstyle=solid,fillcolor=curcolor]
{
\newpath
\moveto(38.33889376,67.35513328)
\curveto(39.90689547,67.35509017)(41.09296342,66.03921993)(41.89711165,63.40748424)
\curveto(43.50531445,58.47289182)(44.65118131,56.00562195)(45.33470755,56.0056459)
\curveto(45.85735449,56.00562195)(46.40013944,56.41682961)(46.96305772,57.23928802)
\curveto(58.2608517,75.74384316)(68.7143666,90.71198997)(78.32362116,102.14379168)
\curveto(80.81631349,105.10443984)(84.77658911,106.58480942)(90.20445269,106.58489085)
\curveto(91.49097185,106.58480942)(92.35539361,106.46145048)(92.79773204,106.21480444)
\curveto(93.23991593,105.96799555)(93.4610547,105.65958382)(93.46113432,105.28956447)
\curveto(93.4610547,104.71379041)(92.7976618,103.58294901)(91.47094155,101.89705463)
\curveto(75.95141033,82.81670149)(61.55772504,62.66726353)(48.28984822,41.44869669)
\curveto(47.36506862,39.96831273)(45.47540199,39.22812555)(42.62081088,39.22813992)
\curveto(39.72597184,39.22812555)(38.0172148,39.35149407)(37.49457722,39.5982407)
\curveto(36.12755286,40.2150402)(34.51931728,43.36081778)(32.66987047,49.03557823)
\curveto(30.57914689,55.32711903)(29.53378743,59.27475848)(29.53381085,60.87852533)
\curveto(29.53378743,62.60558406)(30.94099884,64.27099685)(33.75542165,65.87476369)
\curveto(35.48425582,66.86164481)(37.01207517,67.35509017)(38.33889376,67.35513328)
}
}

\end{pspicture}


استفاده از اين خروجي در iTextSharp بسيار ساده است. براي مثال:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace HtmlToPdf
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
                pdfDoc.Open();

                var cb = pdfWriter.DirectContent;
                                
                cb.MoveTo(52.73079005f, 101.89500456f);
                cb.CurveTo(31.29686559f, 101.89500456f, 13.84575258f, 84.04652127f, 13.8457479f, 62.12456369f);
                cb.CurveTo(13.8457479f, 40.20259605f, 31.29686559f, 22.35412714f, 52.73079005f, 22.35412235f);
                cb.CurveTo(74.16470983f, 22.35412235f, 91.6158322f, 40.20259605f, 91.61582751f, 62.12456369f);
                cb.CurveTo(91.61582751f, 71.60188248f, 88.48023622f, 80.07729424f, 83.15553076f, 87.02034164f);
                cb.LineTo(79.49425309f, 82.58209245f);
                cb.CurveTo(84.13622847f, 76.73639073f, 85.95313131f, 70.24630402f, 85.95313131f, 62.12456369f);
                cb.CurveTo(85.95313131f, 43.33817595f, 71.09893654f, 28.1547277f, 52.73079005f, 28.1547277f);
                cb.CurveTo(34.36263419f, 28.15473249f, 19.50844879f, 43.33817595f, 19.50844879f, 62.12456369f);
                cb.CurveTo(19.50844879f, 80.91094185f, 34.36264355f, 96.10336589f, 52.73079005f, 96.10336589f);
                cb.CurveTo(58.55122776f, 96.10336589f, 62.90459266f, 95.2476225f, 67.65721002f, 92.5630926f);
                cb.LineTo(71.13570481f, 97.23509821f);
                cb.CurveTo(65.57113223f, 100.3782653f, 59.52269945f, 101.89500456f, 52.73079005f, 101.89500456f);
                                
                cb.MoveTo(38.33889376f, 67.35513328f);
                cb.CurveTo(39.90689547f, 67.35509017f, 41.09296342f, 66.03921993f, 41.89711165f, 63.40748424f);
                cb.CurveTo(43.50531445f, 58.47289182f, 44.65118131f, 56.00562195f, 45.33470755f, 56.0056459f);
                cb.CurveTo(45.85735449f, 56.00562195f, 46.40013944f, 56.41682961f, 46.96305772f, 57.23928802f);
                cb.CurveTo(58.2608517f, 75.74384316f, 68.7143666f, 90.71198997f, 78.32362116f, 102.14379168f);
                cb.CurveTo(80.81631349f, 105.10443984f, 84.77658911f, 106.58480942f, 90.20445269f, 106.58489085f);
                cb.CurveTo(91.49097185f, 106.58480942f, 92.35539361f, 106.46145048f, 92.79773204f, 106.21480444f);
                cb.CurveTo(93.23991593f, 105.96799555f, 93.4610547f, 105.65958382f, 93.46113432f, 105.28956447f);
                cb.CurveTo(93.4610547f, 104.71379041f, 92.7976618f, 103.58294901f, 91.47094155f, 101.89705463f);
                cb.CurveTo(75.95141033f, 82.81670149f, 61.55772504f, 62.66726353f, 48.28984822f, 41.44869669f);
                cb.CurveTo(47.36506862f, 39.96831273f, 45.47540199f, 39.22812555f, 42.62081088f, 39.22813992f);
                cb.CurveTo(39.72597184f, 39.22812555f, 38.0172148f, 39.35149407f, 37.49457722f, 39.5982407f);
                cb.CurveTo(36.12755286f, 40.2150402f, 34.51931728f, 43.36081778f, 32.66987047f, 49.03557823f);
                cb.CurveTo(30.57914689f, 55.32711903f, 29.53378743f, 59.27475848f, 29.53381085f, 60.87852533f);
                cb.CurveTo(29.53378743f, 62.60558406f, 30.94099884f, 64.27099685f, 33.75542165f, 65.87476369f);
                cb.CurveTo(35.48425582f, 66.86164481f, 37.01207517f, 67.35509017f, 38.33889376f, 67.35513328f);
                
                cb.SetRGBColorFill(0, 0, 0);
                cb.Fill();
            }

            Process.Start("Test.pdf");
        }
    }
}

در اينجا، pdfWriter.DirectContent يك Canvas را جهت ترسيمات گرافيكي در اختيار ما قرار مي‌دهد. سپس مابقي هم آن مشخص است و يك تناظر يك به يك را مي‌شود بين خروجي Tex و متدهاي فراخواني شده، مشاهده كرد. PDF خروجي هم به شكل زير است:



تا اينجا يك مرحله پيشرفت است. مشكل از اينجا شروع مي‌شود كه خوب! من كه يك «چك مارك» اين اندازه‌اي لازم ندارم! آن هم قرار گرفته در پايين صفحه. يك راه حل اين مشكل استفاده از متد Transform شيء cb فوق است. اين متد يك System.Drawing.Drawing2D.Matrix را دريافت مي‌كند و سپس مي‌شود توسط آن، اعمال تغيير اندازه (Scale)، تغيير مكان (Translate) و غيره را اعمال كرد. راه ديگر تعريف يك Template از دستورات فوق است. سپس متد Image.GetInstance كتابخانه iTextSharp ورودي از نوع Template را هم قبول مي‌كند. خروجي حاصل يك تصوير برداري خواهد بود كه اكنون با اكثر اشياء iTextSharp سازگار است. براي مثال متد سازنده PdfPCell، آرگومان از نوع Image را هم قبول مي‌كند. به علاوه شيء Image در اينجا متدهاي تغيير اندازه و امثال آن‌را نيز به همراه دارد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace HtmlToPdf
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
                pdfDoc.Open();

                var cb = pdfWriter.DirectContent;
                var template = createCheckMark(cb);

                var image = Image.GetInstance(template);
                image.ScaleAbsolute(40, 40);

                var table = new PdfPTable(3);
                var cell = new PdfPCell(image) 
                {
                    HorizontalAlignment = Element.ALIGN_CENTER
                };

                for (int i = 0; i < 9; i++)
                    table.AddCell(cell);

                pdfDoc.Add(table);
            }

            Process.Start("Test.pdf");
        }

        private static PdfTemplate createCheckMark(PdfContentByte cb)
        {
            var template = cb.CreateTemplate(140, 140);

            template.MoveTo(52.73079005f, 101.89500456f);
            template.CurveTo(31.29686559f, 101.89500456f, 13.84575258f, 84.04652127f, 13.8457479f, 62.12456369f);
            template.CurveTo(13.8457479f, 40.20259605f, 31.29686559f, 22.35412714f, 52.73079005f, 22.35412235f);
            template.CurveTo(74.16470983f, 22.35412235f, 91.6158322f, 40.20259605f, 91.61582751f, 62.12456369f);
            template.CurveTo(91.61582751f, 71.60188248f, 88.48023622f, 80.07729424f, 83.15553076f, 87.02034164f);
            template.LineTo(79.49425309f, 82.58209245f);
            template.CurveTo(84.13622847f, 76.73639073f, 85.95313131f, 70.24630402f, 85.95313131f, 62.12456369f);
            template.CurveTo(85.95313131f, 43.33817595f, 71.09893654f, 28.1547277f, 52.73079005f, 28.1547277f);
            template.CurveTo(34.36263419f, 28.15473249f, 19.50844879f, 43.33817595f, 19.50844879f, 62.12456369f);
            template.CurveTo(19.50844879f, 80.91094185f, 34.36264355f, 96.10336589f, 52.73079005f, 96.10336589f);
            template.CurveTo(58.55122776f, 96.10336589f, 62.90459266f, 95.2476225f, 67.65721002f, 92.5630926f);
            template.LineTo(71.13570481f, 97.23509821f);
            template.CurveTo(65.57113223f, 100.3782653f, 59.52269945f, 101.89500456f, 52.73079005f, 101.89500456f);

            template.MoveTo(38.33889376f, 67.35513328f);
            template.CurveTo(39.90689547f, 67.35509017f, 41.09296342f, 66.03921993f, 41.89711165f, 63.40748424f);
            template.CurveTo(43.50531445f, 58.47289182f, 44.65118131f, 56.00562195f, 45.33470755f, 56.0056459f);
            template.CurveTo(45.85735449f, 56.00562195f, 46.40013944f, 56.41682961f, 46.96305772f, 57.23928802f);
            template.CurveTo(58.2608517f, 75.74384316f, 68.7143666f, 90.71198997f, 78.32362116f, 102.14379168f);
            template.CurveTo(80.81631349f, 105.10443984f, 84.77658911f, 106.58480942f, 90.20445269f, 106.58489085f);
            template.CurveTo(91.49097185f, 106.58480942f, 92.35539361f, 106.46145048f, 92.79773204f, 106.21480444f);
            template.CurveTo(93.23991593f, 105.96799555f, 93.4610547f, 105.65958382f, 93.46113432f, 105.28956447f);
            template.CurveTo(93.4610547f, 104.71379041f, 92.7976618f, 103.58294901f, 91.47094155f, 101.89705463f);
            template.CurveTo(75.95141033f, 82.81670149f, 61.55772504f, 62.66726353f, 48.28984822f, 41.44869669f);
            template.CurveTo(47.36506862f, 39.96831273f, 45.47540199f, 39.22812555f, 42.62081088f, 39.22813992f);
            template.CurveTo(39.72597184f, 39.22812555f, 38.0172148f, 39.35149407f, 37.49457722f, 39.5982407f);
            template.CurveTo(36.12755286f, 40.2150402f, 34.51931728f, 43.36081778f, 32.66987047f, 49.03557823f);
            template.CurveTo(30.57914689f, 55.32711903f, 29.53378743f, 59.27475848f, 29.53381085f, 60.87852533f);
            template.CurveTo(29.53378743f, 62.60558406f, 30.94099884f, 64.27099685f, 33.75542165f, 65.87476369f);
            template.CurveTo(35.48425582f, 66.86164481f, 37.01207517f, 67.35509017f, 38.33889376f, 67.35513328f);

            template.SetRGBColorFill(0, 0, 0);
            template.Fill();

            return template;
        }
    }
}

در اين مثال، با كمك متد CreateTemplate مرتبط با Canvas دريافتي، يك قالب جديد ايجاد و سپس روي آن نقاشي خواهيم كرد. اكنون مي‌توان از اين قالب تهيه شده، يك Image دريافت كرده و سپس مثلا در سلول‌هاي يك جدول نمايش داد. اينبار خروجي نهايي ما به شكل زير خواهد بود:



۱۳۹۰/۰۶/۲۲

iTextSharp و استفاده از قلم‌هاي محدود فارسي


عموما قلم‌هاي فارسي، خصوصا مواردي كه با B شروع مي‌شوند مانند B Zar و امثال آن، فاقد تعاريف حروف مرتبط با glyphs الفباي انگليسي است. نتيجه اين خواهد شد كه اگر متن شما مخلوطي از كلمات و حروف فارسي و انگليسي باشد، فقط قسمت فارسي نمايش داده مي‌شود و از قسمت انگليسي صرفنظر خواهد شد. مرورگرها در اين حالت هوشمندانه عمل مي‌كنند و به يك قلم پيش فرض مانند Times و همانند آن جهت نمايش اينگونه متون مراجعه خواهند كرد؛ اما اينجا چنين اتفاقي نخواهد افتاد.
براي حل اين مشكل، كلاسي به نام FontSelector در كتابخانه‌ي iTextSharp وجود دارد. مثالي در اين رابطه:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace HeadersAndFooters
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
                pdfDoc.Open();

                FontFactory.Register("c:\\windows\\fonts\\bzar.ttf");
                Font bZar = FontFactory.GetFont("b zar", BaseFont.IDENTITY_H);

                FontFactory.Register("c:\\windows\\fonts\\tahoma.ttf");
                Font tahoma = FontFactory.GetFont("tahoma", BaseFont.IDENTITY_H);

                FontSelector fontSelector = new FontSelector();

                //قلم اصلي
                if (bZar.Familyname != "unknown")
                {
                    fontSelector.AddFont(bZar);
                }

                //قلم پيش فرض در صورت نبود تعاريف مناسب در قلم اصلي
                if (tahoma.Familyname != "unknown")
                {
                    fontSelector.AddFont(tahoma);
                }

                var table1 = new PdfPTable(1);
                table1.WidthPercentage = 100;
                table1.RunDirection = PdfWriter.RUN_DIRECTION_RTL;

                var pdfCell = new PdfPCell { RunDirection = PdfWriter.RUN_DIRECTION_RTL, Border = 0 };
                pdfCell.Phrase = fontSelector.Process("نمايش مخلوطي از متن فارسي و English با هم توسط قلمي كه كاراكترهاي انگليسي را پشتيباني نمي‌كند");

                table1.AddCell(pdfCell);
                pdfDoc.Add(table1);

            }

            //open the final file with adobe reader for instance.
            Process.Start("Test.pdf");
        }
    }
}

در اين مثال از قلم B Zar استفاده شده است. اولين قلمي كه به يك FontSelector اضافه مي‌شود، قلم اصلي خواهد بود. قلم‌ بعدي اضافه شده، قلم پيش فرض نام خواهد گرفت؛ به اين معنا كه در مثال فوق اگر قلم B Zar توانايي نمايش حرف جاري را داشت كه خيلي هم خوب، در غيراينصورت به قلم بعدي مراجعه خواهد كرد و همينطور الي آخر. بنابراين اين ترتيب اضافه كردن قلم‌ها به FontSelector مهم است. نحوه استفاده نهايي از FontSelector تعريف شده هم در قسمت pdfCell.Phrase = fontSelector.Process مشخص است.



۱۳۹۰/۰۶/۱۲

تبديل HTML به PDF با استفاده از كتابخانه‌ي iTextSharp


روش متداول كار با كتابخانه‌ي iTextSharp ، ايجاد شيء Document ، سپس ايجاد PdfWriter براي نوشتن در آن، گشودن سند و ... افزودن اشيايي مانند Paragraph ، PdfPTable ، PdfPCell و غيره به آن است و در نهايت بستن سند. راه ميانبري هم براي كار با اين كتابخانه وجود دارد و آن هم استفاده از امكانات فضاي نام iTextSharp.text.html.simpleparser آن مي‌باشد. به اين ترتيب مي‌توان به صورت خودكار، يك محتواي HTML را تبديل به فايل PDF كرد.

مثال : نمايش يك متن HTML ساده انگليسي
using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.html.simpleparser;
using iTextSharp.text.pdf;

namespace HeadersAndFooters
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
                pdfDoc.Open();

                var html = @"<span style='color:blue'><b>Testing</b></span>
                             <i>iTextSharp's</i> <u>HTML to PDF capabilities</u>";
                var parsedHtmlElements = HTMLWorker.ParseToList(new StringReader(html), null);

                foreach (var htmlElement in parsedHtmlElements)
                {
                    pdfDoc.Add(htmlElement);
                }
            }

            //open the final file with adobe reader for instance.
            Process.Start("Test.pdf");
        }
    }
}


نكته‌ي جديد كد فوق، استفاده از متد HTMLWorker.ParseToList است. به اين ترتيب parser كتابخانه‌ي iTextSharp وارد عمل شده و html تعريف شده را به معادل المان‌هاي بومي خودش تبديل مي‌كند؛ مثلا تبديل به chunk يا pdfptable و امثال آن. در نهايت در طي يك حلقه، اين عناصر به صفحه اضافه مي‌شوند.
البته بايد دقت داشت كه HTMLWorker امكان تبديل عناصر پيچيده، تودرتو و چندلايه HTML را ندارد؛ اما بهتر از هيچي است!

همه‌ي اين‌ها خوب! اما به درد ما فارسي زبان‌ها نمي‌خورد. همين متغير html فوق را با يك متن فارسي جايگزين كنيد، چيزي نمايش داده نخواهد شد. البته اين هم نكته دارد كه در ادامه ذكر خواهد شد.
جهت نمايش متون فارسي نياز است تا نكات ذكر شده در مطلب «فارسي نويسي و iTextSharp» رعايت شوند كه شامل:
- تعيين صريح قلم
- تعيين encoding
- استفاده از عناصر دربرگيرنده‌اي است كه خاصيت RunDirection را پشتيباني مي‌كنند؛ مانند PdfPCell و غيره


به اين ترتيب خواهيم داشت:
using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.html.simpleparser;
using iTextSharp.text.pdf;
using iTextSharp.text.html;

namespace HeadersAndFooters
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
                pdfDoc.Open();                

                //روش صحيح تعريف فونت
                FontFactory.Register("c:\\windows\\fonts\\tahoma.ttf");

                StyleSheet styles = new StyleSheet();
                styles.LoadTagStyle(HtmlTags.BODY, HtmlTags.FONTFAMILY, "tahoma");
                styles.LoadTagStyle(HtmlTags.BODY, HtmlTags.ENCODING, "Identity-H");                

                var html = @"<span style='color:blue'><b>آزمايش</b></span>
                                كتابخانه <i>iTextSharp</i> <u>جهت بررسى فارسى نويسى</u>";
                var parsedHtmlElements = HTMLWorker.ParseToList(new StringReader(html), styles);

                PdfPCell pdfCell = new PdfPCell { Border = 0 };
                pdfCell.RunDirection = PdfWriter.RUN_DIRECTION_RTL;

                foreach (var htmlElement in parsedHtmlElements)
                {
                    pdfCell.AddElement(htmlElement);
                }

                var table1 = new PdfPTable(1);
                table1.AddCell(pdfCell);
                pdfDoc.Add(table1);
            }

            //open the final file with adobe reader for instance.
            Process.Start("Test.pdf");
        }
    }
}

همانطور كه ملاحظه مي‌كنيد ابتدا قلمي در cache قلم‌هاي اين كتابخانه ثبت مي‌شود (FontFactory.Register). سپس نوع قلم و encoding آن توسط يك StyleSheet تعريف شده و به HTMLWorker.ParseToList ارسال مي‌گردد و در نهايت به كمك يك المان داراي RunDirection، در صفحه نمايش داده مي‌شود.



نكته:
ممكن است كه به متغير html ، يك table ساده html را نسبت دهيد. در اين حالت پس از تنظيم style ياد شده، در هر سلول اين html table ، متون فارسي به صورت معكوس نمايش داده خواهند شد كه اين هم يك نكته‌ي كوچك ديگر دارد:

foreach (var htmlElement in parsedHtmlElements)
                {
                    if (htmlElement is PdfPTable)
                    {
                        var table = (PdfPTable)htmlElement;
                        table.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
                        foreach (var row in table.Rows)
                        {
                            foreach (var cell in row.GetCells())
                            {
                                cell.RunDirection = PdfWriter.RUN_DIRECTION_RTL;                               
                            }
                        }
                    }

                    pdfCell.AddElement(htmlElement);
                }

در قسمتي كه قرار است المان‌هاي معادل به pdfCell اضافه شوند، آن‌ها را بررسي كرده و RunDirection آن‌ها را RTL خواهيم كرد.


كاربردها:
بديهي است اين حالت براي تهيه گزارشات پيشرفته‌تر براي مثال تهيه قالب‌هايي كه در حين تهيه PDF ، قسمت‌هايي از آن‌ها توسط برنامه نويس Replace مي‌شوند، بسيار مناسب است.
همچنين مطلب «بارگذاري يك يوزركنترل با استفاده از جي‌كوئري» و متد RenderUserControl مطرح شده در آن كه در نهايت يك قطعه كد HTML را به صورت رشته به ما تحويل مي‌دهد، مي‌تواند جهت تهيه گزارش‌هاي پويايي كه براي مثال قسمتي از آن يك GridView بايند شده حاصل از يك يوزر كنترل است،‌ مورد استفاده قرار گيرد.


۱۳۹۰/۰۶/۰۸

تهيه فيد از تغييرات SVN


كتابخانه‌ي iTextSharp 5.1.2 هفته‌ي قبل منتشر شده و ... من هر چقدر سايتي، بلاگي جايي را جستجو كردم كه خلاصه‌اي از تغييرات انجام شده آن‌را گزارش دهد، چيزي نيافتم. ولي خوب، مطابق روال متداول كتابخانه‌هاي سورس باز، حداقل مي‌توان به change log مرتبط با سورس كنترل آن‌ها مراجعه كرد. مثلا:


البته اين هم خوب است ولي اي‌كاش مي‌شد مثلا يك فيد هم از اين تغييرات تهيه كرد. يك سري از سايت‌هاي هاستينگ مثل CodePlex و GitHub يك چنين فيدهايي را دارند. اما به نظر SourceForge از اين لحاظ اندكي ضعيف است.
سايت روسي زير مي‌تواند با گرفتن آدرس يك مخزن كد SVN (براي مثال: https://itextsharp.svn.sourceforge.net/svnroot/itextsharp/trunk/ ) يك فيد RSS از آن تهيه كند:


در همين راستا برنامه‌ي CommitMonitor هم موجود است.



۱۳۹۰/۰۶/۰۲

تعريف رنگ در iTextSharp


در كتابخانه‌ي iTextSharp به جهت سازگاري با كتابخانه‌ي اصلي، رنگ‌ها را بر اساس كلاسي به نام BaseColor تعريف كرده‌اند؛ كه اي‌كاش به جاي اين‌كار، همه را با كلاس Color فضاي نام استاندارد System.Drawing جايگزين مي‌كردند. همين مشكل با فونت هم هست. يك كلاس فونت در فضاي نام iTextSharp.text وجود دارد به علاوه كلاس فونت تعريف شده در فضاي نام استاندارد System.Drawing دات نت؛ كه خيلي سريع مي‌تواند به خطاي كامپايل زير ختم شود:

'Font' is an ambiguous reference between 'iTextSharp.text.Font' and 'System.Drawing.Font'	

و در نهايت مجبور خواهيم شد كه به صورت صريح علام كنيم، iTextSharp.text.Font منظور ما است و نه آن يكي.
در كل اگر با كلاس Color فضاي نام استاندارد System.Drawing بيشتر راحت هستيد به صورت زير هم مي‌توان رنگ‌هاي متداول را مورد استفاده قرار داد:

تعريف رنگ‌ها بر اساس نام آن‌ها:

var color = new BaseColor(Color.LightGray);

تعريف رنگ‌ها بر اساس مقادير Hex متداول در المان‌هاي HTML :

var color = new BaseColor(ColorTranslator.FromHtml("#1C5E55"));

اين نكته‌اي است كه شايد خيلي‌ها از وجود آن بي‌اطلاع باشند. به صورت پيش فرض در كلاس استاندارد ColorTranslator، امكان دريافت رنگ‌هاي بكاررفته در المان‌هاي HTML به كمك متد ColorTranslator.FromHtml مهيا است.
البته اگر زماني خواستيد خودتان اين متد را پياده سازي كنيد، نكته‌ي آن به صورت زير است:

string htmlColor = "#1C5E55";
int x = Convert.ToInt32(htmlColor.Replace("#", "0x"), 16);
byte red   = (byte)((x & 0xff0000) >> 16);
byte green = (byte)((x & 0xff00) >> 8);
byte blue  = (byte)(x & 0xff);

سپس كلاس‌هاي Color و همچنين BaseColor امكان پذيرش اين اجزاي حاصل را دارند (به كمك متد Color.FromRgb و يا سازنده‌ي BaseColor).
علت ذكر ColorTranslator.FromHtml به اين بر مي‌گردد كه تركيبات رنگ جالبي را مي‌توان از جداول HTML ايي موجود در سايت‌هاي مختلف ايده گرفت و يا حتي از قالب‌هاي پيش فرض GridView در ASP.NET مثلا.


اكنون براي ساخت جدولي مانند شكل فوق، به ازاي هر سلولي كه مشاهده مي‌كنيد بايد يكبار BorderColor و BackgroundColor تنظيم شوند. رنگ متن هم از رنگ فونت دريافت مي‌شود:

var pdfCell = new PdfPCell(new Phrase(Text, Font))
            {
                RunDirection = ...,
                BorderColor = ...,
                BackgroundColor = ...
            };


۱۳۹۰/۰۵/۳۱

روش صحيح تعريف قلم در iTextSharp


روش متداول تعريف فونت در iTextSharp به صورت زير است:

public static iTextSharp.text.Font Tahoma()
{
     var fontPath = Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf";
     var baseFont = BaseFont.CreateFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
     return new Font(baseFont);
}

از آنجائيكه خصوصا براي متون فارسي نياز است تا به ازاي هر المان كوچكي اين فونت تنظيم شود و‌ در غير اينصورت متني نمايش داده نخواهد شد، با سربار بالايي مواجه خواهيم شد. بنابراين به نظر مي‌رسد كه بهتر باشد اين توليد اشياء فونت را كش كنيم. خوشبختانه iTextSharp سيستم كش كردن تعريف قلم‌هاي متفاوت را هم به صورت توكار دارا است:

public static iTextSharp.text.Font GetTahoma()
{
    var fontName = "Tahoma";
    if (!FontFactory.IsRegistered(fontName))
    {
         var fontPath = Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf";
         FontFactory.Register(fontPath);
    }
    return FontFactory.GetFont(fontName, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
}

كلاس FontFactory كار ثبت و بازيابي قلم‌هاي متفاوت را به عهده دارد. تنها كافي است يكبار قلمي در آن ثبت شود (FontFactory.Register)، بار ديگر اطلاعات قلم به سادگي از كش FontFactory خوانده خواهد شد (FontFactory.GetFont).

۱۳۹۰/۰۵/۲۶

چند ستونه كردن در iTextSharp


فرض كنيد جدولي داريد با چند ستون محدود كه نتيجه‌ي نهايي گزارش آن مثلا 100 صفحه است. جهت صرفه جويي در كاغذ مصرفي شايد بهتر باشد كه اين جدول را به صورت چند ستوني مثلا 5 ستون در يك صفحه نمايش داد؛ چيزي شبيه به شكل زير:


روش انجام اينكار به كمك iTextSharp به صورت زير است:


using System;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Diagnostics;

class Program
{
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
                pdfDoc.Open();

                var table1 = new PdfPTable(1);
                table1.WidthPercentage = 100f;
                table1.HeaderRows = 2;
                table1.FooterRows = 1;

                //header row
                var headerCell = new PdfPCell(new Phrase("header"));
                table1.AddCell(headerCell);

                //footer row
                var footerCell = new PdfPCell(new Phrase(" "));
                table1.AddCell(footerCell);

                //adding some rows
                for (int i = 0; i < 400; i++)
                {
                    var rowCell = new PdfPCell(new Phrase(i.ToString()));
                    table1.AddCell(rowCell);
                }

                // wrapping table1 in multiple columns
                ColumnText ct = new ColumnText(pdfWriter.DirectContent);
                ct.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
                ct.AddElement(table1);

                int status = 0;
                int count = 0;
                int l = 0;
                int columnsWidth = 100;
                int columnsMargin = 7;
                int columnsPerPage = 4;
                int r = columnsWidth;
                bool isRtl = true;

                // render the column as long as it has content
                while (ColumnText.HasMoreText(status))
                {
                    if (isRtl)
                    {
                        ct.SetSimpleColumn(
                            pdfDoc.Right - l, pdfDoc.Bottom,
                            pdfDoc.Right - r, pdfDoc.Top
                        );
                    }
                    else
                    {
                        ct.SetSimpleColumn(
                            pdfDoc.Left + l, pdfDoc.Bottom,
                            pdfDoc.Left + r, pdfDoc.Top
                        );
                    }

                    var delta = columnsWidth + columnsMargin;
                    l += delta;
                    r += delta;

                    // render as much content as possible
                    status = ct.Go();

                    // go to a new page if you've reached the last column
                    if (++count > columnsPerPage)
                    {
                        count = 0;
                        l = 0;
                        r = columnsWidth;
                        pdfDoc.NewPage();
                    }
                }
            }

            //open the final file with adobe reader for instance.
            Process.Start("Test.pdf");
        }
}


توضيحات:
تا قسمت تعريف جدول و اضافه كردن سطرها و ستون‌هاي مورد نظر، همان بحث «تكرار خودكار سرستون‌هاي يك جدول در صفحات مختلف، توسط iTextSharp» مي‌باشد.
اصل مطلب از قسمت ColumnText شروع مي‌شود. با استفاده از شيء ColumnText مي‌توان محتواي خاصي را در طي چند ستون در صفحه نمايش داد. عرض اين ستون‌ها هم توسط متد SetSimpleColumn مشخص مي‌شود و همچنين محل دقيق قرارگيري آن‌ها در صفحه. در اينجا دو حالت راست به چپ و چپ به راست در نظر گرفته شده است.
اگر حالت راست به چپ را در نظر بگيريم، محل قرارگيري اولين ستون از سمت راست صفحه (pdfDoc.Right) بايد تعيين شود. سپس هربار به اندازه‌ي عرضي كه مد نظر است بايد محل شروع ستون را مشخص كرد (pdfDoc.Right - l). هر زمانيكه ct.Go فراخواني مي‌شود، تاجايي كه ميسر باشد، اطلاعات جدول 1 در يك ستون درج مي‌شود. سپس بررسي مي‌شود كه تا اين لحظه چند ستون در صفحه نمايش داده شده است. اگر تعداد مورد نظر ما (columnsPerPage) تامين شده باشد، كار را در صفحه‌ي بعد ادامه خواهيم داد (pdfDoc.NewPage)، در غيراينصورت مجددا مكان يك ستون ديگر در همان صفحه تعيين شده و كار افزودن اطلاعات به آن آغاز خواهد شد و اين حلقه تا جايي كه تمام محتواي جدول 1 را درج كند، ادامه خواهد يافت.


۱۳۹۰/۰۵/۲۵

فرمت مناسب تصاوير جهت استفاده در iTextSharp


عموما هنگام تهيه يك مستند يا گزارش، هرچقدر حجم نهايي كمتر باشد، توزيع آن ساده‌تر خواهد بود. در اينجا اينطور به نظر مي‌رسد كه اگر مثلا از تصاويري با فرمت jpg يا png استفاده كنيم، كمترين حجم نهايي را مي‌توان بدست آورد. اما حين استفاده از iTextSharp شما با استفاده از تصاويري با فرمت BMP بهترين نتيجه را خواهيد گرفت: كمترين حجم و بهترين كيفيت! البته يك نكته‌ي ريز دارد كه بايد رعايت شود:


using (var pdfDoc = new Document(PageSize.A4))  
{  
    var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("tpn.pdf", FileMode.Create));  
    pdfWriter.SetPdfVersion(new PdfName("1.5"));
    pdfWriter.CompressionLevel = PdfStream.BEST_COMPRESSION;
    //...  

}


در اينجا pdf version و همچنين compression level بايد تنظيم شوند. پس از آن فشرده سازي تصاوير BMP به صورت خودكار حين تهيه فايل نهايي انجام خواهد شد.

۱۳۹۰/۰۵/۲۳

نمايش تصوير پس زمينه در تمام صفحات توسط iTextSharp


فرض كنيد مي‌خواهيم تصويري را در پس زمينه‌ي تمام صفحات pdf توليدي توسط iTextSharp قرار دهيم. براي اين منظور شبيه به مطلب «نمايش تعداد كل صفحات در iTextSharp» مي‌توان از رخدادهاي صفحات استفاده كرد. در متد رويداد گردان OnOpenDocument، يك قالب را به اندازه‌ي يك صفحه‌ي متني تهيه مي‌كنيم. سپس در متد OnStartPage، اين قالب را به تمام صفحات اضافه خواهيم كرد. در حقيقت فضايي را به اين شكل رزرو مي‌كنيم و در نهايت در متد OnCloseDocument ، تصوير مورد نظر را دريافت كرده، Alignment آن‌را طوري تنظيم خواهيم كرد كه زير متون صفحات قرار گيرد و به كمك متد AddImage ، آن‌را به قالب تعريف شده اضافه مي‌كنيم. به اين ترتيب، تصوير اضافه شده به صورت خودكار به تمام صفحات اضافه مي‌شود:


public class PageEvents : PdfPageEventHelper
{
        PdfTemplate _backgroundImageTemplate;

        public override void OnStartPage(PdfWriter writer, Document document)
        {
            base.OnStartPage(writer, document);
            writer.DirectContent.AddTemplate(_backgroundImageTemplate, 0, 0);
        }

        public override void OnOpenDocument(PdfWriter writer, Document document)
        {
            _backgroundImageTemplate = writer.DirectContent.CreateTemplate(document.PageSize.Width, document.PageSize.Height);
        }

        public override void OnCloseDocument(PdfWriter writer, Document document)
        {
            base.OnCloseDocument(writer, document);

            iTextSharp.text.Image img = iTextSharp.text.Image.GetInstance(
			@"C:\My Pictures\bg.png");
            img.Alignment = iTextSharp.text.Image.UNDERLYING;
            img.SetAbsolutePosition((document.PageSize.Width - img.Width) / 2, (document.PageSize.Height - img.Height) / 2);
            _backgroundImageTemplate.AddImage(img);
        }
}


۱۳۹۰/۰۵/۰۲

تكرار خودكار سرستون‌هاي يك جدول در صفحات مختلف، توسط iTextSharp


يكي از نيازهاي تهيه يك گزارش خوب، تكرار سرستون‌ها در صفحات مختلف است. شايد در ابتدا اين ايده مطرح شود كه مثلا مي‌خواهيم 25 رديف را در هر صفحه نمايش دهيم. بر همين اساس مي‌توان هر 25 رديف يكبار، يك سطر footer و در ادامه در صفحه بعد يك سطر header را اضافه كرد و همينطور الي آخر. مهمترين ايراد اين روش آن است كه الزامي ندارد كه واقعا 25 رديف در يك صفحه جا شوند. عموما بر اساس اندازه‌ي محتواي نمايش داده شده، ممكن است يك صفحه 20 رديف شود، صفحه‌اي ديگر 10 رديف. اين مورد تمام محاسبات را به هم خواهد ريخت. به همين جهت دو خاصيت مهم به نام‌هاي HeaderRows و FooterRows در شيء PdfPTable قابل تنظيم است. اين دو خاصيت نياز به اندكي توضيح دارند كه در ادامه ذكر خواهد شد:
using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace HeadersAndFooters
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();

var table1 = new PdfPTable(1);
table1.HeaderRows = 2;
table1.FooterRows = 1;

//header row
table1.AddCell(new Phrase("header"));

//footer row
table1.AddCell(new Phrase("footer"));

//adding some rows
for (int i = 0; i < 70; i++)
{
table1.AddCell(new Phrase("Row " + i));
}

pdfDoc.Add(table1);
}

//open the final file with adobe reader for instance.
Process.Start("Test.pdf");
}
}
}
در اين مثال، يك جدول ساده با يك ستون تعريف شده و سپس HeaderRows آن به 2 و FooterRows آن به 1 مقدار دهي شده‌‌اند.
HeaderRows = 2 به اين معنا است كه 2 سطري را كه بلافاصله در ادامه اضافه مي‌كنيد، در محاسبات نمايش خودكار header يا footer قرار مي‌گيرند. FooterRows = 1 به اين معنا است كه از اين تعداد، آخرين سطر، معناي footer را مي‌دهد. بنابراين اولين table1.AddCell ، همان header خودكار نمايش داده شده در بالاي تمام صفحات خواهد بود و table1.AddCell بعدي جهت نمايش footer خودكار بكار مي‌رود. اين دو سطر كاربرد ديگري ندارند.
مثالي ديگر جهت مشخص شدن اين مفاهيم:
table1.HeaderRows = 3;
table1.FooterRows = 1;
در اينجا HeaderRows = 3 يعني پس از تعريف وهله‌اي از شيء PdfPTable، سه سطر اولي كه اضافه مي‌شوند جزو حيطه‌ي header و footer خودكار قرار دارند. در اين بين چون FooterRows = 1 تعريف شده، 2 سطر اول header تكرار شونده صفحات مختلف را تشكيل مي‌دهند و سومين سطر همان footer خواهد بود.

از اين طراحي لذت مي‌بريد؟!