۱۳۹۰/۰۵/۰۹

Static Reflection


قابليت Dynamic reflection يا به اختصار همان reflection متداول، از اولين نگارش‌هاي دات نت فريم در دسترس است و امكان دسترسي به اطلاعات مرتبط با كلاس‌ها، متدها، خواص و غيره را در زمان اجرا مهيا مي‌سازد. تابحال به كمك اين قابليت، امكان تهيه‌ي ابزارهاي پيشرفته‌ي زير مهيا شده است:
انواع و اقسام
- فريم ورك‌هاي آزمون واحد
- code generators
- ORMs
- ابزارهاي آناليز كد
و ...


براي مثال فرض كنيد كه مي‌خواهيد براي يك كلاس به صورت خودكار، متدهاي آزمون واحد تهيه كنيد (تهيه يك code generator ساده). اولين نياز اين برنامه، دسترسي به امضاي متدها به همراه نام آرگومان‌ها و نوع آن‌ها است. براي حل اين مساله بايد براي مثال يك parser زبان سي شارپ يا اگر بخواهيد كامل‌تر كار كنيد، به ازاي تمام زبان‌هاي قابل استفاده در دات نت فريم ورك بايد parser تهيه كنيد كه ... كار ساده‌اي نيست. اما با وجود reflection به سادگي مي‌توان به اين نوع اطلاعات دسترسي پيدا كرد و نكته‌ي مهم آن هم اين است كه مستقل است از نوع زبان مورد استفاده. به همين جهت است كه اين نوع ابزارها را در فريم ورك‌هايي كه فاقد امكانات reflection هستند، كمتر مي‌توان يافت. براي مثال كيفيت كتابخانه‌هاي آزمون واحد CPP در مقايسه با آنچه كه در دات نت مهيا هستند، اصلا قابل مقايسه نيستند. براي نمونه به يكي از معظم‌ترين فريم ورك‌هاي آزمون واحد CPP كه توسط گوگل تهيه شده مراجعه كنيد : (+)
قابليت Reflection ، مطلب جديدي نيست و براي مثال زبان جاوا هم سال‌ها است كه از آن‌ پشتيباني مي‌كند. اما نگارش سوم دات نت فريم ورك با معرفي lambda expressions ، LINQ و Expressions در يك سطح بالاتر از اين Dynamic reflection متداول قرار گرفت.

تعريف Static Reflection :
استفاده از امكانات Reflection API بدون بكارگيري رشته‌ها، به كمك قابليت اجراي به تعويق افتاده‌ي LINQ، جهت دسترسي به متاديتاي المان‌هاي كد، مانند خواص، متدها و غيره.
براي مثال كد زير را در نظر بگيريد:
//dynamic reflection
PropertyInfo property = typeof (MyClass).GetProperty("Name");
MethodInfo method = typeof (MyClass).GetMethod("SomeMethod");
اين كد، يك نمونه از دسترسي به متاديتاي خواص يا متدها را به كمك Reflection متداول نمايش مي‌دهد. مهم‌ترين ايراد آن استفاده از رشته‌ها است كه تحت نظر كامپايلر نيستند و تنها زمان اجرا است كه مشخص مي‌شود آيا MyClass واقعا خاصيتي به نام Name داشته است يا خير.
چقدر خوب مي‌شد اگر اين قابليت بجاي dynamic بودن (مشخص شدن در زمان اجرا)، استاتيك مي‌بود و در زمان كامپايل قابل بررسي مي‌شد. اين امكان به كمك lambda expressions و expression trees دات نت سه بعد، ميسر شده است. كليدهاي اصلي Static Reflection كلاس‌هاي Func و Expression هستند. با استفاده از كلاس Func مي‌توان lambda expression ايي را تعريف كرد كه مقداري را بر مي‌گرداند و توسط كلاس Expression مي‌توان به محتواي يك delegate دسترسي يافت. تركيب اين دو، قدرت دستيابي به اطلاعاتي مانند PropertyInfo را در زمان طراحي كلاس‌ها، مي‌دهد؛ با توجه به اينكه:
- كاملا توسط intellisense موجود در VS.NET پشتيباني مي‌شود.
- با استفاده از ابزارهاي refactoring قابل كنترل است.
- از همه مهم‌تر، ديگري خبري از رشته‌ها نبوده و همه چيز تحت كنترل كامپايلر قرار مي‌گيرد.

و شايد هيچ قابليتي به اندازه‌ي Static Reflection در اين چندسال اخير بر روي اكوسيستم دات نت فريم ورك تاثيرگذار نبوده باشد. اين روزها كمتر كتابخانه يا فريم وركي را مي‌توانيد پيدا كنيد كه از Static Reflection استفاده نكند. سرآغاز استفاده گسترده از آن به Fluent NHibernate بر مي‌گردد؛ سپس در انواع و اقسام mocking frameworks‌ ، ORMs و غيره استفاده شد و مدتي است كه در ASP.NET MVC نيز مورد استفاده قرار مي‌گيرد (براي مثال TextBoxFor معروف آن):
public string TextBoxFor<T>(Expression<Func<T,object>> expression);
به اين ترتيب حين استفاده از آن ديگري نيازي نخواهد بود تا نام خاصيت مدل مورد نظر را به صورت رشته وارد كرد:
<%= this.TextBoxFor(model => model.FirstName); %>

يك مثال ساده از تعريف و بكارگيري Static Reflection :
public PropertyInfo GetProperty<T>(Expression<Func<T, object>> expression)
{
var memberExpression = expression.Body as MemberExpression;

if (memberExpression == null)
throw new InvalidOperationException("Not a member access.");

return memberExpression.Member as PropertyInfo;
}
همانطور كه عنوان شد كليدهاي اصلي بهر‌ه‌گيري از امكانات Static reflection ، استفاده از كلاس‌هاي Expression و Func هستند كه در آرگومان متد فوق بكارگرفته شده‌اند و در حقيقت يك expression of a delegate است كه به آن Lambdas as Data نيز گفته مي‌شود. اين delegate پارامتري از نوع T را دريافت كرده و سپس مقداري از نوع object را بر مي‌گرداند. اما زمانيكه از كلاس Expression در اينجا استفاده مي‌شود، اين Func ديگر اجرا نخواهد شد، بلكه از آن به عنوان قطعه‌ كدي كه اطلاعاتش قرار است استخراج شود (Lambdas as Data) استفاده مي‌شود.
براي نمونه Fluent NHibernate‌ در پشت صحنه متد Map ، به كمك متدي شبيه به GetProperty فوق، a => a.Address1 را به رشته متناظر خاصيت Address1 تبديل كرده و جهت تعريف نگاشت‌ها مورد استفاده قرار مي‌دهد:
public class AddressMap : DomainMap<Address>
{
public AddressMap()
{
Map(a => a.Address1);
}
}

جهت اطلاع؛ قابليت استفاده از «كد به عنوان اطلاعات» هم مفهوم جديدي نيست و براي مثال زبان Lisp چند دهه است كه آن‌را ارائه داده است!

براي مطالعه بيشتر:

۱۳۹۰/۰۵/۰۵

مروري بر تاريخچه محدوديت حافظه مصرفي برنامه‌هاي ASP.NET در IIS


زمانيكه اولين نگارش ASP.NET‌ حدود 10 سال قبل منتشر شد،‌ تنها سيستم عاملي كه از آن پشتيباني مي‌كرد، ويندوز سرور 2000 بود، تنها پروسه‌ي اجرايي آن aspnet_wp نام داشت و تنها معماري پشتيباني شده هم X86 بود. به پروسه‌ي aspnet_wp محدوديت مصرف حافظه‌اي اعمال شده بود كه در حين آغاز آن بر اساس مقدار قابل تغيير processModel memoryLimit محاسبه و اعمال مي‌شد (تعريف شده در فايل ماشين كانفيگ). اين عدد به صورت درصدي از ظرفيت RAM فيزيكي سيستم، قابل تعريف و به صورت پيش فرض به 60 درصد تنظيم شده بود. به اين ترتيب اين پروسه مجاز نبود تا تمام حافظه‌ي فيزيكي مهيا را مصرف كند و در صورت وجود نشتي حافظه‌اي در برنامه‌اي خاص، اين پروسه امكان بازيابي مجدد حافظه را پيدا مي‌كرد (recycling). همچنين يك مورد ديگر را هم بايد در نظر داشت و آن هم وجود قابليتي است به نام ASP.NET Cache است كه امكان ذخيره سازي مقادير اشياء را در حافظه‌ي مصرفي اين پروسه مهيا مي‌سازد. هر زمان كه ميزان اين حافظه‌ي مصرفي به حد نزديكي از محدوديت تعريف شده برسد، اين پروسه به صورت خودكار شروع به حذف آن‌ها خواهد كرد.
محدوديت 60 درصدي تعريف شده، براي سيستم‌هايي با ميزان RAM كم بسيار مفيد بود اما در سيستم‌هايي با ميزان RAM بيشتر، مثلا 4 گيگ به 2.4GB حافظه مهيا (60 درصد حافظه فيزيكي سيستم) محدود مي‌شد و همچنين بايد در نظر داشت كه ميزان user mode virtual address space مهيا نيز تنها 2 گيگابايت بود. بنابراين هيچگاه استفاده مؤثري از تمام ظرفيت RAM مهيا صورت نمي‌گرفت و گاها مشاهده مي‌شد كه يك برنامه تنها با مصرف 1.5GB RAM مي‌توانست پيغام OutOfMemoryException را صادر كند. در اين حالت مطابق بررسي‌هاي صورت گرفته مشخص شد كه اگر مقدار processModel memoryLimit به حدود 800 مگابايت تنظيم شود، بهترين عملكرد را براي سيستم‌هاي مختلف مي‌توان مشاهده كرد.

با ارائه‌ي ويندوز سرور 2003 و همچنين ارائه‌ي نسخه‌ي 1.1 دات نت فريم ورك و ASP.NET ، اين وضعيت تغيير كرد. پروسه‌ي جديد در اينجا w3wp نام دارد و اين پروسه تعاريف مرتبط با محدوديت حافظه‌ي خود را از تنظيمات IIS دريافت مي‌كند (قسمت Maximum Used Memory در برگه‌ي Recycling مربوط به خواص Application Pool مرتبط). متاسفانه اين عدد به صورت پيش فرض محدوديتي ندارد و به ظاهر برنامه مجاز است تا حد امكان از حافظه‌ي مهيا استفاده كند. به همين جهت يكي از مواردي را كه بايد در نظر داشت، مقدار دهي Maximum Used Memory ذكر شده است. خصوصا اينكه در نگارش 1.1 ، تنظيمات ميزان مصرف RAM مرتبط با ASP.NET Cache نيز با برنامه يكي است.

در نگارش 2.0 دات نت فريم ورك، تنظيمات مرتبط با ASP.NET cache از تنظيمات ميزان RAM مصرفي يك برنامه‌ي ASP.NET جدا شد و اين مورد توسط قسمت cache privateBytesLimit قابل تنظيم و مديريت است (در فايل IIS Metabase و همچنين فايل web.config برنامه).

نكته!
اگر process memory limit و همچنين cache memory limit را تنظيم نكنيد، باز به همان عدد 60 درصد سابق بازخواهيم گشت و اين مورد به صورت خودكار توسط IIS محاسبه و اعمال مي‌شود. البته محدوديت ذكر شده براي پروسه‌هاي 64 بيتي در اين حالت بسيار بهتر خواهد بود. اگر هر دوي اين‌ها را تنظيم كنيد، عدد حداقل بكارگرفته شده، مبناي كار خواهد بود و اگر تنها يكي را تنظيم كنيد ، اين عدد به هر دو حالت اعمال مي‌گردد. براي بررسي بهتر مي‌توان به مقدار Cache.EffectivePrivateBytesLimit و Cache.EffectivePercentagePhysicalMemoryLimit مراجعه كرد.

و ... اكنون بهتر مي‌توانيد به اين سؤال پاسخ دهيد كه «سرور ما بيشتر از 4 گيگ رم دارد و برنامه‌ي ASP.NET من الان فقط 850 مگ رم مصرف كرده (كه البته اين هم نشاني از عدم dispose صحيح منابع است يا عدم تعيين تقدم و تاخر و زمان منقضي شدن، حين تعريف اشياء كش)، اما پيغام out of memory exception را دريافت مي‌كنم. چرا؟!»


بنابراين ايجاد يك Application pool جديد به ازاي هر برنامه‌ي ASP.NET امري است بسيار مهم زيرا:
- به اين ترتيب هر برنامه‌ي ASP.NET در پروسه‌اي ايزوله از پروسه‌ي ديگر اجرا خواهد شد (اين مساله از لحاظ امنيتي هم بسيار مهم است). در اينجا هر برنامه، از پروسه‌ي w3wp.exe مجزاي خاص خود استفاده خواهد كرد (شبيه به مرورگرهايي كه هر tab را در يك پروسه جديد اجرا مي‌كنند).
- اگر پروسه‌اي به حد بالاي مصرف حافظه‌ي خود رسيد با تنظيمات انجام شده در قسمت recycling مرتبط با Application pool اختصاصي آن، به صورت خودكار كار بازيابي حافظه صورت مي‌گيرد و اين امر بر روي ساير برنامه‌ها تاثير نخواهد داشت (كاربران ساير برنامه‌ها مدام شكايت نمي‌كنند كه سشن‌ها پريد. كش خالي شد. زيرا در حالت وجود application pool اختصاصي به ازاي هر برنامه، مديريت حافظه برنامه‌ها از هم ايزوله خواهند بود)
- كرش صورت گرفته در يك برنامه به دليل عدم مديريت خطاها، بر روي ساير برنامه‌ها تاثير منفي نخواهد گذاشت. (زمانيكه ASP.NET worker process به دليل استثنايي مديريت نشده خاتمه يابد بلافاصله و به صورت خودكار مجددا «وهله‌ي ديگري» از آن شروع به كار خواهد كرد؛ يعني تمام سشن‌هاي قبلي از بين خواهند رفت؛ كه در صورت ايزوله سازي ذكر شده، ساير برنامه‌ها در امان خواهند ماند؛ چون در پروسه ايزوله‌ي خود مشغول به كار هستند)
- با وجود application pool اختصاصي به ازاي هر برنامه، مي‌توان براي سايت‌هاي كم ترافيك و پرترافيك، زمان‌هاي recycling متفاوتي را اعمال كرد. به اين ترتيب مديريت حافظه‌ي بهتري قابل پياده سازي مي‌باشد. همچنين در اين حالت مي‌توان مشخص كرد كدام سايت از تعداد worker process بيشتر يا كمتري استفاده كند.
- كاربري كه پروسه‌ي ASP.NET تحت آن اجرا مي‌شود نيز همينجا تعريف مي‌گردد. بنابراين به اين ترتيب مي‌توان به برنامه‌اي دسترسي بيشتر و يا كمتر داد، بدون تاثير گذاري بر روي ساير برنامه‌هاي موجود.

نتيجه گيري:
- از IIS استفاده مي‌كنيد؟ آيا مي‌دانيد Application pool چيست؟
- آيا مي‌دانيد در صورت عدم مقدار دهي پارامترهاي حافظه‌ي يك Application pool ، به صورت پيش فرض چند درصد از حافظه‌ي فيزيكي مهيا در اختيار شما است؟


براي مطالعه بيشتر:

۱۳۹۰/۰۵/۰۲

تكرار خودكار سرستون‌هاي يك جدول در صفحات مختلف، توسط 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 خواهد بود.

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

۱۳۹۰/۰۴/۳۱

بررسي علت CPU Usage بالاي برنامه در حال اجرا


فرض كنيد به يك سرور مراجعه كرده‌ايد و شكايت از CPU Usage مربوط به پروسه w3wp.exe يا همان IIS Worker Process است كه بالاي 90 درصد مي‌باشد. بر روي اين سرور هم هيچ چيز ديگري نصب نيست و مطابق مقررات موجود، قرار هم نيست كه برنامه‌اي نصب شود. اكنون سؤال اين است كه چطور تشخيص مي‌دهيد، كدام قسمت يكي از برنامه‌ها‌ي دات نتي در حال اجرا (در اينجا يكي از برنامه‌هاي ASP.NET هاست شده)، سبب بروز اين مشكل شده است؟ كدام ترد بيشترين زمان CPU را به خود اختصاص داده است؟ چطور بايد خطايابي كرد؟
اولين كاري كه در اين موارد توصيه مي‌شود مراجعه به برنامه‌ي معروف process explorer و بررسي برگه‌ي threads آن است. در اينجا حتي مي‌توان call stacks مرتبط با يك ترد را هم مشاهده كرد. اما ... اين برگه در مورد پروسه‌ها و تردهاي دات نتي، اطلاعات چنداني را در اختيار ما قرار نمي‌دهد.
خوشبختانه امكان ديباگ پروسه‌هاي دات نتي در حال اجرا توسط كتابخانه‌ي MdbgCore.dll پيش بيني شده است. اين فايل را در يكي از مسير‌هاي ذيل مي‌توانيد پيدا كنيد:
C:\Program Files\Microsoft SDKs\Windows\vXYZ\bin\MdbgCore.dll
C:\Program Files\Microsoft SDKs\Windows\vXYZ\bin\NETFX 4.0 Tools\MdbgCore.dll

در ادامه مي‌خواهيم توسط امكانات اين كتابخانه، به stack trace تردهاي در حال اجراي يك برنامه دات نتي دسترسي پيدا كرده و سپس نام متدهاي مرتبط را نمايش دهيم:
using System;
using System.Collections;
using System.Diagnostics;
using Microsoft.Samples.Debugging.MdbgEngine;

namespace CpuAnalyzer
{
class Program
{
static void Main(string[] args)
{
var engine = new MDbgEngine();

var processesByName = Process.GetProcessesByName("MyApp");
if (processesByName.Length == 0)
throw new InvalidOperationException("specified process not found.");
var processId = processesByName[0].Id;

var process = engine.Attach(processId);
process.Go().WaitOne();

foreach (MDbgThread thread in (IEnumerable)process.Threads)
{
foreach (MDbgFrame frame in thread.Frames)
{
if (frame == null || frame.Function == null) continue;
Console.WriteLine(frame.Function.FullName);
}
}

process.Detach().WaitOne();
}
}
}
در اينجا در ابتدا نياز است تا pid يا process-id مرتبط با برنامه در حال اجرا يافت شود. سپس از اين pid جهت اتصال (engine.Attach) به پروسه مورد نظر استفاده خواهيم كرد. در ادامه كليه تردهاي اين پروسه در حال ديباگ ليست شده و سپس MDbgFrameهاي اين ترد بررسي مي‌شوند و نام متدهاي مرتبط در كنسول نمايش داده خواهند شد.
خوب در مرحله بعد شايد بد نباشد كه اين متدها را بر اساس CPU usage آن‌ها مرتب كنيم. به اين ترتيب بهتر مي‌توان تشخيص داد كه كدام متد مشكل ساز بوده است. براي اين منظور بايد به API ويندوز و تابع GetThreadTimes مراجعه كرد و اولين پارامتر ورودي آن، همان thread.CorThread.Handle اولين حلقه مثال فوق مي‌باشد. هر كدام كه جمع KernelTime + UserTime بيشتري داشت، همان است كه مشكل درست كرده است.
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GetThreadTimes(IntPtr handle, out long creation, out long exit, out long kernel, out long user);
اين مورد را به عنوان تمرين بررسي كرده و ادامه دهيد! همچنين بهتر است جهت دستيابي به اطلاعاتي معتبر، اولين حلقه برنامه فوق، حداقل 10 بار اجرا شود تا اطلاعات آماري بهتري را بتوان ارائه داد. البته در اين حالت نكته‌ي زير بايد رعايت شود:
for (int i = 0; i < 10; i++)
{
foreach (MDbgThread thread in (IEnumerable)process.Threads)
{
//...
}
process.Go();
Thread.Sleep(1000);
process.AsyncStop().WaitOne();
}

در كل اين مثال جاي كار زياد دارد. براي مثال طراحي يك رابط كاربري براي آن و نمايش جزئيات بيشتر. به همين منظور حداقل سه پروژه مشابه را مي‌توان نام برد:

۱۳۹۰/۰۴/۲۹

اندكي به روز رساني


ليست RSS وبلاگ‌هاي IT‌ ايراني و همچنين فايل خلاصه وبلاگ را به روز كردم كه از طريق منوي سمت راست صفحه قسمت گزيده‌ها، قابل دريافت هستند ( + و + ).


۱۳۹۰/۰۴/۲۵

ويديوهاي آموزشي QT


در كمال ناباوري، يك مجموعه‌‌ي آموزشي 88 قسمتي، از فريم وركي به نام QT را در يوتيوب پيدا كردم [+] كه نتيجه‌اش دريافت و آپلود مجدد آن‌ها جهت سهولت دريافت بود. حجم كل آن‌ها 2.14 گيگابايت است كه در 13 قسمت جهت علاقمندان آپلود شده است (مجوز ويديوهاي يوتيوب، Creative Commons است و امكان توزيع مجدد آن‌ها وجود دارد: [+] و [+]). براي باز كردن مجموعه فايل‌هاي دريافت شده از برنامه 7-Zip مي‌شود استفاده كرد.





۱۳۹۰/۰۴/۲۳

چك ليست تهيه يك گزارش خوب


مشخصات يك گزارش خوب عموما به شرح زير است:

1- بايد هر سطر گزارش شماره رديف داشته باشد. (بايد امكان ارجاع به هر سطر در صورت بروز مشكل ميسر باشد)
2- بايد در هر صفحه، شماره صفحه و تعداد كل صفحات ذكر شود. (اگر چاپ شد بر اين اساس بتوان ارتباط بين صفحات را يافت)
3- در هر صفحه بايد تاريخ و ساعت روز تهيه گزارش حتما ذكر شود. (بعدا جهت رفع اختلافات لازم مي‌شود. مثلا مي‌گويند اين عدد اشتباه است. اما واقعا اين عدد در زمان تهيه گزارش درست بوده، اما الان بر اساس اطلاعات جديد ... بله ... چيزي ديگري است، يا به قول آن‌ها اشتباه است)
4- در پايان هر صفحه، يك سري از ستون‌هاي عددي بايد جمع كل داشته باشد.
5- در ابتداي هر صفحه بايد "نقل از صفحه قبل" يا همان سطر جمع كل صفحه قبل ذكر شود.
6- هدر گزارش بايد در تمام صفحات تكرار شود. (بايد مشخص باشد اين صفحه گزارش كه الان به دست من رسيده متعلق به كجاست، عنوانش چيست حداقل؟)
7- سر ستون‌ها هم بايد در هر صفحه تكرار شوند. (مثلا الان صفحه 20 يك گزارش پيش روي شما است. بايد بدانيد معناي اين ستون سوم ظاهر شده در گزارش چيست)
8- تمام اعداد موجود در گزارش بايد جداكننده سه رقمي داشته باشند. (خواندن 4446327531 ساده‌تر است يا خواندن 4,446,327,531 ؟)
9- تمام اعداد گزارش بايد فارسي نمايش داده شوند. (اين مورد را مي‌شود با فونت‌هاي دستكاري شده كه احتمالا شما هم يك دوجين از آن‌ها را داريد، حل كرد. فونت‌هايي كه با يك فونت اديتور مثل برنامه معروف FontCreator ويرايش شده و بجاي اعداد انگليسي آن‌ها، همان اعداد فارسي قرار گرفته‌اند)

۱۳۹۰/۰۴/۲۲

منابع مطالعاتي بيشتر در مورد iTextSharp


  • آشنايي با صفحه بندي در iTextSharp : [+]
  • تعريف هدر و فوتر: [+]
  • افزودن متن ساده در iTextSharp: [+]
  • كار با فونت‌هاي مختلف در iTextSharp: [+]
  • نحوه‌ي افزودن جدول در iTextSharp: [+]
  • ترسيم اشكال گرافيكي با iTextSharp: [+]
  • كار با تصاوير در iTextSharp: [+] و [+]
  • امكان تبديل HTML به PDF در iTextSharp: [+]، [+]، [+] و [+]
  • نحوه‌ي تعريف لينك در iTextSharp: [+]
  • نحوه‌ي تعريف ليست در iTextSharp: [+]
  • افزودن نمودار به كمك كنترل‌هاي چارت مايكروسافت در iTextSharp: [+]
  • امكان تعريف باركد در iTextSharp: [+]
  • يك سري مثال: [+]
  • يكي كردن چند فايل پي دي اف موجود با هم توسط iTextSharp: [+]

۱۳۹۰/۰۴/۲۰

نمايش يك فايل PDF در WinForms ، WPF و سيلورلايت


شايد PDF را بشود تنها فرمت گزارشگيري دانست كه همه‌جا و در تمام سيستم عامل‌ها پشتيباني مي‌شود. از ويندوز تا لينوكس از وب تا WPF تا سيلورلايت تا همه جا و از همه مهم‌تر اينكه خروجي آن دقيقا همان چيزي است كه كاربر نهايي مي‌خواهد: من مي‌خوام اون چيزي رو كه مي‌بينم، دقيقا همان را، بدون كم و كاست و با همان صفحه بندي، بتوانم چاپ كنم.
براي توليد PDF مي‌شود از كتابخانه‌ي iTextSharp استفاده كرد اما براي نمايش آن حداقل در ويندوز بهترين راه حل استفاده از COM Components‌ شركت Adobe است كه به همراه برنامه رايگان Adobe PDF reader ارائه مي‌شود. در ادامه نحوه‌ي استفاده از اين Active-X را بررسي خواهيم كرد.

نمايش PDF در WPF
در تمام حالت‌ها هدف اين است كه به نحوي به اكتيوايكس شركت Adobe دسترسي پيدا كنيم؛ يا با اضافه كردن آن به پروژه يا استفاده از امكانات يكپارچه مرورگرها. در WPF از زمان ارائه سرويس پك يك دات نت سه و نيم (به بعد)، كنترل مرورگر وب هم به جمع كنترل‌هاي قابل استفاده در آن اضافه شده است. در اينجا به سادگي چند سطر زير مي‌شود يك فايل PDF را در WPF نمايش داد:
<Window x:Class="WpfAppTests.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Pdf Report" Height="495" WindowState="Maximized"
WindowStartupLocation="CenterScreen" Width="703">
<Grid>
<WebBrowser x:Name="WebBrowser1"/>
</Grid>
</Window>
و بعد هم در كدهاي برنامه تنها كافي است كه مقدار Source كنترل WebBrowser را مقدار دهي كرد:
WebBrowser1.Source = new Uri(PdfFilePath);


نمايش PDF در WinForms
اكتيوايكس نمايش دهنده PDF شركت Adobe اساسا در فايل ذيل قرار گرفته است:
C:\Program Files\Common Files\Adobe\Acrobat\ActiveX\AcroPDF.dll
بنابراين براي استفاده از آن در يك برنامه‌ي WinForms بايد مراحل ذيل طي شود:
الف) در VS.NET‌ از طريق منوي Tools گزينه‌ي Choose toolbox items ، برگه‌ي Com components را انتخاب كنيد.
ب) سپس گزينه‌ي Adobe PDF reader كه به همان مسير dll فوق اشاره مي‌كند را انتخاب نمائيد و بر روي دكمه‌ي OK‌ كليك كنيد.
ج) اكنون اين كنترل جديد را بر روي فرم برنامه قرار دهيد. به صورت خودكار COMReference هاي متناظر به پروژه اضافه مي‌شوند.

اكنون نحوه‌ي استفاده از اين شيء COM به همراه آزاد سازي منابع مرتبط به شرح زير خواهند بود:
using System.Windows.Forms;

namespace WindowsFormsAppTests
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Load += Form1_Load;
this.FormClosing += Form1_FormClosing;
}

void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
axAcroPDF1.Dispose();
}

void Form1_Load(object sender, System.EventArgs e)
{
axAcroPDF1.LoadFile(PdfFilePath);
axAcroPDF1.setShowToolbar(true);
axAcroPDF1.Show();
}
}
}

نمايش PDF در Silverlight

در Silverlight هم از نسخه‌ي 4 به بعد كنترل WebBrowser همانند آنچه كه در WPF موجود است، اضافه شده است؛ اما اين كنترل فقط در حالت اجراي در خارج از مرورگر برنامه Silverlight در دسترس مي‌باشد. بنابراين روش ديگري را بايد انتخاب كرد. اين روش بر اساس تعامل سيلورلايت با كدهاي HTML صفحه كار مي‌كند. يك IFrame مخفي را در صفحه بالاي شيء مرتبط با سيلورلايت قرار خواهيم داد. سپس در سيلورلايت Src اين IFrame را به مسير فايل PDF تنظيم مي‌كنيم و همين. به اين ترتيب فايل PDF نمايش داده مي‌شود.
اين IFrame به صورت زير در همان صفحه‌ي aspx ايي كه object مرتبط با Silverlight نمايش داده مي‌شود قرار مي‌گيرد:
<iframe id="pdfFrame" style="visibility:hidden; position:absolute"><b>No Content</b></iframe>
<div id="silverlightControlHost">
سپس در كدهاي سيلورلايت، ابتدا اين IFrame يافت شده:
var iFrame = HtmlPage.Document.GetElementById("pdfFrame");
در ادامه بر اساس اطلاعات مكاني يك Grid ساده به نام pdfHost كه در صفحه قرار گرفته، اين iFrame بالاتر از سطح Grid (بر اساس z-index تنظيم شده) نمايش داده مي‌شود:
var gt = pdfHost.TransformToVisual(Application.Current.RootVisual);
var offset = gt.Transform(new Point(0, 0));
var controlLeft = (int)offset.X;
var controlTop = (int)offset.Y;
iFrame.SetStyleAttribute("left", string.Format("{0}px", controlLeft));
iFrame.SetStyleAttribute("top", string.Format("{0}px", controlTop));
iFrame.SetStyleAttribute("visibility", "visible");
iFrame.SetStyleAttribute("height", string.Format("{0}px", pdfHost.ActualHeight));
iFrame.SetStyleAttribute("width", string.Format("{0}px", pdfHost.ActualWidth));
iFrame.SetStyleAttribute("z-index", "1000");
و در آخر نام فايلي را كه مي‌خواهيم مشاهده كنيم به يك صفحه‌ي aspx در همان سايت ارسال مي‌كنيم:
iFrame.SetProperty("src", "ShowPdf.aspx?file=" + fileName);
كدهاي اين صفحه در حد يك Response.Redirect ساده براي نمايش دادن فايل pdf در مرورگر كافي هستند. در كل در اينجا سيلورلايت تنها نقش انتخاب فايل را به عهده دارد و كار اصلي را خود مرورگر انجام مي‌دهد.

۱۳۹۰/۰۴/۱۷

نمايش تعداد كل صفحات در iTextSharp


در مورد نحوه‌ي نمايش شماره صفحه جاري در مثلا header يك گزارش PDF تهيه شده به كمك writer.PageNumber و ارث بري از كلاس PdfPageEventHelper،‌ در پايان مطلب فارسي نويسي در iTextSharp توضيح داده شد. اين مورد جزو ضروريات يك گزارش خوب است، اما عموما نياز است تا تعداد كل صفحات هم نمايش داده شود. مثلا صفحه n از 100 جايي در تمام صفحات درج شود و ... هيچ خاصيتي به نام TotalNumberOfPages را در اين كتابخانه نمي‌توان يافت. علت هم اين است كه تعداد واقعي كل صفحات فقط در حين بسته شدن شيء Document مشخص مي‌شود و نه در هنگام تهيه صفحات. بنابراين نكته تهيه و نمايش تعداد صفحات، در iTextSharp به صورت خلاصه به شرح زير است:
الف) بايد در همان كلاسي كه از PdfPageEventHelper به ارث رسيده است، متد OnCloseDocument را تحريف (override) كرد. در اينجا به خاصيت writer.PageNumber دسترسي داريم و writer.PageNumber - 1 مساوي است با تعداد كل صفحات.
ب) در مرحله بعد نياز است تا اين عدد را به نحوي به تمام صفحات توليد شده اضافه كنيم. اين كار هم ساده است و مبتني است بر بكارگيري يك PdfTemplate :
  • در متد تحريف شده‌ي OnOpenDocument ، يك قالب PDF ساده را توليد مي‌كنيم (مثلا يك مستطيل كوچك خالي).
  • سپس در متد OnEndPage ، اين قالب را به انتهاي تمام صفحات در حال توليد اضافه خواهيم كرد.
  • زمانيكه متد OnCloseDocument فراخوانده شد، عدد تعداد كل صفحات را در اين قالب كه به تمام صفحات اضافه شده، درج خواهيم كرد. به اين ترتيب اين عدد به صورت خودكار در تمام صفحات نمايش داده خواهد شد.

پياده سازي اين توضيحات را در ادامه ملاحظه خواهيد كرد:
using System;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace iTextSharpTests
{
public class PdfWriterPageEvents : PdfPageEventHelper
{
PdfContentByte _pdfContentByte;
// عدد نهايي تعداد كل صفحات را در اين قالب قرار خواهيم داد
PdfTemplate _template;
BaseFont _baseFont;

public override void OnOpenDocument(PdfWriter writer, Document document)
{
_baseFont = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
_pdfContentByte = writer.DirectContent;
_template = _pdfContentByte.CreateTemplate(50, 50);
}

public override void OnEndPage(PdfWriter writer, Document document)
{
base.OnEndPage(writer, document);
String text = writer.PageNumber + "/";
float len = _baseFont.GetWidthPoint(text, 8);
Rectangle pageSize = document.PageSize;
_pdfContentByte.SetRGBColorFill(100, 100, 100);
_pdfContentByte.BeginText();
_pdfContentByte.SetFontAndSize(_baseFont, 8);
_pdfContentByte.SetTextMatrix(pageSize.GetLeft(40), pageSize.GetBottom(30));
_pdfContentByte.ShowText(text);
_pdfContentByte.EndText();
//در پايان هر صفحه يك جاي خالي را مخصوص تعداد كل صفحات رزرو خواهيم كرد
_pdfContentByte.AddTemplate(_template, pageSize.GetLeft(40) + len, pageSize.GetBottom(30));
}
public override void OnCloseDocument(PdfWriter writer, Document document)
{
base.OnCloseDocument(writer, document);
_template.BeginText();
_template.SetFontAndSize(_baseFont, 8);
_template.SetTextMatrix(0, 0);
//درج تعداد كل صفحات در تمام قالب‌هاي اضافه شده
_template.ShowText((writer.PageNumber - 1).ToString());
_template.EndText();
}
}

public class AddTotalNoPages
{
public static void CreateTestPdf()
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("tpn.pdf", FileMode.Create));
pdfWriter.PageEvent = new PdfWriterPageEvents();
pdfDoc.Open();


pdfDoc.Add(new Phrase("Page1"));
pdfDoc.NewPage();
pdfDoc.Add(new Phrase("Page2"));
pdfDoc.NewPage();
pdfDoc.Add(new Phrase("Page3"));
}
}
}
}

۱۳۹۰/۰۴/۱۳

مشكلات طراحي API مرتبط با iTextSharp


كتابخانه‌ي iTextSharp،‌ يا همان برگردان iText جاوا،‌ انصافا اينقدر حرف براي گفتن دارد كه يك كتاب 600 صفحه‌اي براي آن چاپ شده است، اما ... در حين استفاده از آن مشكل زير (كه به شكل وسيعي در قسمت‌هاي مختلف آن وجود دارد) قابل هضم نيست:
يكي از مواردي را كه در حين طراحي يك API خوب بايد در نظر گرفت، كمك به استفاده كننده در عدم بكارگيري Magic numbers است. حالا اين Magic numbers يعني چي؟
براي مثال قطعه كد زير را در نظر بگيريد:
new Font(baseFont, 10, 0, BaseColor.BLACK)
مقدار پارامتر 3 در اينجا بي‌معنا است، مگر اينكه به مستندات مربوطه مراجعه كنيم.
نگارش بهبود يافته كد فوق به شرح زير است:
new Font(baseFont, 10, Font.NORMAL, BaseColor.BLACK)
كمي بهتر شد، اينبار ثوابت به يك كلاس منتقل شده‌اند و به اين ترتيب معناي مقدار بكارگرفته شده بدون نياز به مستندات متد فوق قابل درك است. اما اين روش هم (يعني همان روش متداول iTextSharp) دو مشكل مهم دارد:
  • استفاده كننده محدوديتي در بكارگيري مقادير ندارد، چون آرگومان‌ها از نوع int معرفي شده‌اند. ممكن است اشتباهي رخ دهد.
  • باز هم نياز است به مستندات كتابخانه مراجعه كرد، زيرا نوع int هيچ نوع منوي intellisense خاصي را ظاهر نمي‌كند.
راه درست، استفاده از enum است بجاي يك كلاس ساده كه فقط يك سري از ثوابت در آن تعريف شده‌اند. نوع int را بايد با enum زير جايگزين كرد (يا ... بهتر است اينگونه بشود در كتابخانه‌ي اصلي ... روزي!)
public enum PdfFontStyle
{
Normal = 0,
Bold = 1,
Italic = 2,
Underline = 4,
Strikethru = 8,
BoldItalic = Bold | Italic
}
اگر در طراحي آن از ابتدا اين روش پي‌گرفته مي‌شد، منوي intellisense تبديل به بهترين مستند اين كتابخانه مي‌شد.

۱۳۹۰/۰۴/۱۰

نمودار سازماني مايكروسافت






به نظر اين «جريانات انحرافي» همه جا هستند ...

ماخذ


فارسى نويسى و iTextSharp


شرح يك سري سعي و خطا!
سعي اول:
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace iTextSharpTests
{
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 chunk = new Chunk("آزمايش");
pdfDoc.Add(chunk);
}
}
}
}

نتيجه:



بله! هيچي!

مشكل از كجاست؟
در iTextSharp بر اساس نوع فونت انتخابي و encoding مرتبط،‌ نحوه‌ي رندر سازي حروف مشخص مي‌شود:



همانطور كه ملاحظه مي‌كنيد، فونت پايه متني كه قرار است اضافه شود، null است.

سعي دوم:
اينبار فونت را تنظيم مي‌كنيم:
using System;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace iTextSharpTests
{
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 fontPath = Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf";
var baseFont = BaseFont.CreateFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
var tahomaFont = new Font(baseFont, 10, Font.NORMAL, BaseColor.BLACK);

var chunk = new Chunk("آزمايش",tahomaFont);
pdfDoc.Add(chunk);
}
}
}
}

توضيحات:
متد BaseFont.CreateFont مي‌تواند مسيري از فونت مورد نظر را دريافت كند. اين حالت خصوصا براي برنامه‌هاي وب كه ممكن است فونت مورد نظر آن‌ها در سرور نصب نشده باشد، بسيار مفيد است و لزومي ندارد كه الزاما فونت مورد استفاده در پوشه fonts‌ ويندوز نصب شده باشد.
نكات مهم ديگر بكار گرفته شده در اين متد، استفاده از BaseFont.IDENTITY_H و BaseFont.EMBEDDED است. به اين صورت encoding متن، جهت نوشتن متون غير Ansi تنظيم مي‌شود و در اين حالت حتما بايد فونت را در فايل، مدفون (embed) نمود. از اين لحاظ كه عموما اين نوع فونت‌ها در سيستم‌هاي كاربران نصب نيستند.

نتيجه:



بد نيست! حداقل حروف نمايش داده شدند؛ اما نياز است تا چرخانده يا معكوس شوند. براي انجام خودكار آن حداقل دو كار را مي‌توان انجام داد.

الف) استفاده از ColumnText و اعمال تنظيمات راست به چپ آن
using System;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace iTextSharpTests
{
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 fontPath = Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf";
var baseFont = BaseFont.CreateFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
var tahomaFont = new Font(baseFont, 10, Font.NORMAL, BaseColor.BLACK);

ColumnText ct = new ColumnText(pdfWriter.DirectContent);
ct.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
ct.SetSimpleColumn(100, 100, 500, 800, 24, Element.ALIGN_RIGHT);

var chunk = new Chunk("آزمايش", tahomaFont);

ct.AddElement(chunk);
ct.Go();
}
}
}
}

توضيحات:
در اينجا يك ColumnTex جديد تعريف و سپس خصوصيات اين ستون تنظيم شده، به همراه RunDirection آن كه اصل قضيه است. سپس chunk تعريف شده را به اين ستون اضافه كرده‌ايم.

نتيجه:



بله! كار كرد!

ب) استفاده از PdfTable و اعمال تنظيمات راست به چپ آن
using System;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace iTextSharpTests
{
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 fontPath = Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf";
var baseFont = BaseFont.CreateFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
var tahomaFont = new Font(baseFont, 10, Font.NORMAL, BaseColor.BLACK);

PdfPTable table = new PdfPTable(numColumns: 1);
table.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
table.ExtendLastRow = true;

PdfPCell pdfCell = new PdfPCell(new Phrase("آزمايش", tahomaFont));
pdfCell.RunDirection = PdfWriter.RUN_DIRECTION_RTL;

table.AddCell(pdfCell);
pdfDoc.Add(table);
}
}
}
}

در حين استفاده از PdfTable هم لازم است تا RunDirection مربوط به خود جدول و همچنين هر سلول اضافه شده به آن به RTL تنظيم شوند.

اين نكات در هر جايي كه با اين كتابخانه سر و كار داريم بايد اعمال شوند. براي مثال:

افزودن Header به صفحات Pdf :
افزودن header در نگارش‌هاي جديد iTextSharp شامل نكته استفاده از كلاس PdfPageEventHelper به شرح زير است (و مثال‌هايي را كه در وب پيدا خواهيد كرد، هيچكدام با آخرين نگارش موجود iTextSharp كار نمي‌كنند):
using System;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace iTextSharpTests
{
public class PageEvents : PdfPageEventHelper
{
Font _font;
public PageEvents()
{
var fontPath = Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf";
var baseFont = BaseFont.CreateFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
_font = new Font(baseFont, 10, Font.NORMAL, BaseColor.BLACK);
}

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

PdfPTable table = new PdfPTable(numColumns: 1);
table.RunDirection = PdfWriter.RUN_DIRECTION_RTL;

PdfPCell pdfCell = new PdfPCell(new Phrase("سر صفحه در صفحه: " + writer.PageNumber, _font));
pdfCell.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
pdfCell.HorizontalAlignment = Element.ALIGN_CENTER;

table.AddCell(pdfCell);
document.Add(table);
}
}

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));
pdfWriter.PageEvent = new PageEvents();
pdfDoc.Open();

var fontPath = Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf";
var baseFont = BaseFont.CreateFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
var tahomaFont = new Font(baseFont, 10, Font.NORMAL, BaseColor.BLACK);

PdfPTable table = new PdfPTable(numColumns: 1);
table.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
table.ExtendLastRow = true;

PdfPCell pdfCell = new PdfPCell(new Phrase("آزمايش", tahomaFont));
pdfCell.RunDirection = PdfWriter.RUN_DIRECTION_RTL;

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

pdfDoc.NewPage();
}
}
}
}

نتيجه:



تنها نكته‌اي كه اينجا اضافه شده، تعريف كلاس PageEvents است كه از كلاس PdfPageEventHelper مشتق شده است. در اين كلاس مي‌توان يك سري متد كلاس پايه را تحريف كرد و header و footer و غيره را اضافه نمود. سپس جهت اضافه كردن آن، pdfWriter.PageEvent بايد مقدار دهي شود.
در اينجا هم اگر نوع فونت، encoding و PdfTable به همراه RunDirection آن اضافه نمي‌شد، يا چيزي در header صفحه قابل مشاهده نبود يا متن مورد نظر معكوس نمايش داده مي‌شد.