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

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

مطالب
تعیین تعداد ردیف در صفحه جداول خودکار 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 .

مطالب
چند ستونه کردن در 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

کتابخانه 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 می‌توان برای آخرین بار فرصت یافت تا در یک سلول نقاشی کرد.


پاسخ به بازخورد‌های پروژه‌ها
تگ a در گزارش
نیازی نیست برای صرفا تبدیل HTML به PDF از کتابخانه PDFReport استفاده کنید. کتابخانه PdfReport برای قسمت‌های تبدیل HTML به PDF خودش از HTMLWorker کتابخانه iTextSharp استفاده می‌کند.
اطلاعات بیشتر

ضمنا این کتابخانه مشکلی با لینک‌ها هم ندارد. یک مثال:

            var html =  @"<a color='blue' href='https://www.dntips.ir'>سایت دات نت</a>";

            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");
                styles.LoadTagStyle(HtmlTags.BODY, HtmlTags.ALIGN, HtmlTags.ALIGN_LEFT);

                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.WidthPercentage = 100;
                table1.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
                table1.AddCell(pdfCell);
                pdfDoc.Add(table1);
            }

پ.ن.
در هر برنامه‌ای یک گزارش خطا زمان قابل رسیدگی خواهد بود که قابلیت تکرار مجدد داشته باشد به همراه ارائه کامل stack trace خطای دریافتی.
مطالب
تبدیل 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 بایند شده حاصل از یک یوزر کنترل است،‌ مورد استفاده قرار گیرد.


مطالب
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 و نمایش صحیح تاریخ در متنی راست به چپ

خروجی 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 و استفاده از قلم‌های محدود فارسی

عموما قلم‌های فارسی، خصوصا مواردی که با 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 مشخص است.



مطالب
سفارشی سازی Header و Footer در PdfReport
صورت مساله:
- می‌خواهیم footer پیش فرض PdfReport را که تاریخ را در یک سمت، و شماره صفحه را در سمتی دیگر نمایش می‌دهد، به عبارت «صفحه x از n» تغییر دهیم.
- می‌خواهیم در Header گزارش بجای Header پیش فرض PdfReport یکی از قالب‌های PDF تهیه شده توسط Open Office را نمایش دهیم (و یا هر ساختار دیگری را).

تمام اجزای PdfReport جهت امکان اعمال تغییرات کلی و توسعه آن‌ها طراحی شده‌اند؛ قالب‌ها، هدر، فوتر، منابع داده، قالب‌های نمایش سلول‌ها، تعریف توابع تجمعی سفارشی و غیره. جهت سهولت کار، به ازای هر یک از این موارد، پیاده سازی‌های پیش فرضی در PdfReport قرار دارند، امکان اگر مورد رضایت شما نیستند ... از بنیان تغییرشان دهید! (و همچنین اگر مورد جالبی را پیاده سازی کردید، می‌توانید به عنوان یک وصله جدید ارائه دهید تا به پروژه اضافه شود)
ضمنا این مطالب سفارشی سازی نیاز به آشنایی با ساختار iTextSharp را نیز دارند؛ در حد ایجاد یک جدول ساده باید با iTextSharp آشنا باشید.

مدل‌های مورد استفاده:
namespace PdfReportSamples.Models
{
    public class Task
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public int PercentCompleted { set; get; }
        public bool IsActive { set; get; }
        public User Assignee { set; get; }
    }
}

using System;

namespace PdfReportSamples.Models
{
    public class User
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public string LastName { set; get; }
        public long Balance { set; get; }
        public DateTime RegisterDate { set; get; }
    }
}
توسط این مدل‌ها قصد داریم تعدادی فعالیت (Task) را که به تعدادی کاربر انتساب یافته است، نمایش دهیم. همچنین نمایش مقادیر خواص تو در تو  نیز در اینجا مد نظر است؛ برای مثال ستونی مانند این:
 column.PropertyName<Task>(x => x.Assignee.Name) 
کدهای کامل مثال را در ادامه ملاحظه خواهید نمود:
using System;
using System.Collections.Generic;
using System.Drawing;
using PdfReportSamples.Models;
using PdfRpt.Core.Contracts;
using PdfRpt.FluentInterface;

namespace PdfReportSamples.CustomHeaderFooter
{
    public class CustomHeaderFooterPdfReport
    {
        readonly CustomHeader _customHeader = new CustomHeader();
        public IPdfReportData CreatePdfReport()
        {
            return new PdfReport().DocumentPreferences(doc =>
            {
                doc.RunDirection(PdfRunDirection.LeftToRight);
                doc.Orientation(PageOrientation.Portrait);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" });
            })
            .DefaultFonts(fonts =>
            {
                fonts.Path(Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf",
                                  Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
            })
            .PagesFooter(footer =>
            {
                footer.CustomFooter(new CustomFooter(footer.PdfFont, PdfRunDirection.LeftToRight));
            })
            .PagesHeader(header =>
            {
                header.CustomHeader(_customHeader);
            })
            .MainTableTemplate(template =>
            {
                template.BasicTemplate(BasicTemplate.SilverTemplate);
            })
            .MainTablePreferences(table =>
            {
                table.ColumnsWidthsType(TableColumnWidthType.Relative);
                table.MultipleColumnsPerPage(new MultipleColumnsPerPage
                {
                    ColumnsGap = 22,
                    ColumnsPerPage = 2,
                    ColumnsWidth = 250,
                    IsRightToLeft = false,
                    TopMargin = 7
                });
            })
            .MainTableDataSource(dataSource =>
            {
                var rows = new List<Task>();
                var rnd = new Random();
                for (int i = 1; i < 210; i++)
                {
                    rows.Add(new Task
                    {
                        Assignee = new User
                        {
                            Id = i,
                            Name = "user-" + i
                        },
                        IsActive = rnd.Next(0, 2) == 1 ? true : false,
                        Name = "task-" + i
                    });
                }
                dataSource.StronglyTypedList(rows);
            })
            .MainTableColumns(columns =>
            {
                columns.AddColumn(column =>
                {
                    column.PropertyName("rowNo");
                    column.IsRowNumber(true);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(0);
                    column.Width(1);
                    column.HeaderCell("#");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName<Task>(x => x.Name);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(1);
                    column.Width(3);
                    column.HeaderCell("Task Name");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName<Task>(x => x.Assignee.Name); // nested property support
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(2);
                    column.Width(3);
                    column.HeaderCell("Assignee");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName<Task>(x => x.IsActive);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(3);
                    column.Width(2);
                    column.HeaderCell("Active");
                    column.ColumnItemsTemplate(template =>
                    {
                        template.Checkmark(checkmarkFillColor: Color.Green, crossSignFillColor: Color.DarkRed);
                    });
                });
            })
            .MainTableEvents(events =>
            {
                events.DataSourceIsEmpty(message: "There is no data available to display.");
            })
            .Export(export =>
            {
                export.ToExcel();
            })
            .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\CustomHeaderFooterPdfReportSample.pdf"));
        }
    }
}

به همراه Header سفارشی:
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;

namespace PdfReportSamples.CustomHeaderFooter
{
    public class CustomHeader : IPageHeader
    {
        public PdfPTable RenderingGroupHeader(Document pdfDoc, PdfWriter pdfWriter, IList<CellData> rowdata, IList<SummaryCellData> summaryData)
        {
            return null;
        }

        Image _image;
        public PdfPTable RenderingReportHeader(Document pdfDoc, PdfWriter pdfWriter, IList<SummaryCellData> summaryData)
        {
            if (_image == null) //cache is empty
            {
                var templatePath = AppPath.ApplicationPath + "\\data\\PdfHeaderTemplate.pdf";
                _image = PdfImageHelper.GetITextSharpImageFromPdfTemplate(pdfWriter, templatePath);
            }

            var table = new PdfPTable(1);
            var cell = new PdfPCell(_image, true) { Border = 0 };
            table.AddCell(cell);
            return table;
        }
    }
}

و Footer سفارشی استفاده شده:
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;
using PdfRpt.Core.Contracts;

namespace PdfReportSamples.CustomHeaderFooter
{
    public class CustomFooter : IPageFooter
    {
        PdfContentByte _pdfContentByte;
        readonly IPdfFont _pdfRptFont;
        readonly Font _font;
        readonly PdfRunDirection _direction;
        PdfTemplate _template;

        public CustomFooter(IPdfFont pdfRptFont, PdfRunDirection direction)
        {
            _direction = direction;
            _pdfRptFont = pdfRptFont;
            _font = _pdfRptFont.Fonts[0];
        }

        public void ClosingDocument(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData)
        {
            _template.BeginText();
            _template.SetFontAndSize(_pdfRptFont.Fonts[0].BaseFont, 8);
            _template.SetTextMatrix(0, 0);
            _template.ShowText((writer.PageNumber - 1).ToString());
            _template.EndText();
        }

        public void PageFinished(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData)
        {
            var pageSize = document.PageSize;
            var text = "Page " + writer.PageNumber + " / ";
            var textLen = _font.BaseFont.GetWidthPoint(text, _font.Size);
            var center = (pageSize.Left + pageSize.Right) / 2;
            var align = _direction == PdfRunDirection.RightToLeft ? Element.ALIGN_RIGHT : Element.ALIGN_LEFT;

            ColumnText.ShowTextAligned(
                        canvas: _pdfContentByte,
                        alignment: align,
                        phrase: new Phrase(text, _font),
                        x: center,
                        y: pageSize.GetBottom(25),
                        rotation: 0,
                        runDirection: (int)_direction,
                        arabicOptions: 0);

            var x = _direction == PdfRunDirection.RightToLeft ? center - textLen : center + textLen;
            _pdfContentByte.AddTemplate(_template, x, pageSize.GetBottom(25));
        }

        public void DocumentOpened(PdfWriter writer, IList<SummaryCellData> columnCellsSummaryData)
        {
            _pdfContentByte = writer.DirectContent;
            _template = _pdfContentByte.CreateTemplate(50, 50);
        }
    }
}

البته لازم به ذکر است که تمام این کدها به پوشه Samples سورس پروژه نیز جهت سهولت دسترسی، اضافه شده‌اند .

توضیحات:

برای پیاده سازی Header و Footer سفارشی در PdfReport نیاز خواهید داشت تا دو اینترفیس IPageHeader و IPageFooter را پیاده سازی کنید.
ساختار IPageHeader را در ذیل ملاحظه می‌کنید:
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PdfRpt.Core.Contracts
{
    public interface IPageHeader
    {
        PdfPTable RenderingGroupHeader(Document pdfDoc, PdfWriter pdfWriter, IList<CellData> newGroupInfo, IList<SummaryCellData> summaryData);

        PdfPTable RenderingReportHeader(Document pdfDoc, PdfWriter pdfWriter, IList<SummaryCellData> summaryData);
    }
}

RenderingGroupHeader مرتبط است به مباحث گروه بندی اطلاعات و گزارشات master-detail که در قسمت‌های بعد به آن‌ها اشاره خواهد شد. چون در اینجا به آن نیازی نداشتیم، تنها کافی است متد متناظر با آن، null بر گرداند که در کلاس CustomHeader فوق قابل مشاهده است.
متد RenderingReportHeader به ازای تولید هر صفحه جدید، فراخوانی خواهد شد. به عبارتی می‌توانید در صفحات مختلف، هدرهای مختلفی را نمایش دهید.
خروجی هر دو متد در اینجا یک جدول از نوع PdfPTable است. بنابراین هر نوع ساختار دلخواهی را که علاقمند هستید به شکل یک PdfPTable ایجاد کرده و بازگشت دهید. این جدول در هدر صفحات ظاهر خواهد شد.
برای نمونه در کلاس CustomHeader، یک قالب تهیه شده توسط Open Office توسط متد توکار PdfImageHelper.GetITextSharpImageFromPdfTemplate دریافت و تبدیل به تصویر می‌شود. این تصویر از نوع تصاویر قابل درک توسط iTextSharp است و نه اینکه واقعا تبدیل به یک تصویر معمولی مثلا از نوع bmp شود. سپس این تصویر، در یک ردیف از جدولی قرار داده شده و این جدول بازگشت داده می‌شود.
در کل یا توسط کار با PdfPTable می‌توانید یک هدر غیرپیش فرض را طراحی کنید و یا می‌توانید توسط ابزارهای بصری مانند Open Office یک قالب خاص را برای آن تهیه کرده و به روشی که ذکر شد و کدهای آن‌را ملاحظه می‌کنید، بارگذاری و استفاده کنید. این قالب‌ها در مسیر Bin\Data سورس‌های پروژه قرار داده شده‌اند.

ساختار IPageFooter به صورت زیر است:
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Collections.Generic;

namespace PdfRpt.Core.Contracts
{
    public interface IPageFooter
    {
        void DocumentOpened(PdfWriter writer, IList<SummaryCellData> columnCellsSummaryData);

        void PageFinished(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData);

        void ClosingDocument(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData);
    }
}

برای طراحی یک Footer سفارشی کافی است اینترفیس فوق را پیاده سازی کنید که نمونه‌ای از آن‌را در کدهای کلاس CustomFooter ملاحظه می‌نمائید.
متد DocumentOpened، با وهله سازی شیء Document فراخوانی می‌شود.
متد PageFinished هر بار پیش از اتمام کار صفحه جاری و افزوده شدن آن به Document فراخوانی می‌گردد.
متد ClosingDocument، در زمان بسته شدن شیء Document فراخوانی خواهد شد.

اگر به امضای این متدها دقت کنید، شیء PdfWriter در اختیار شما قرار گرفته است که توسط آن می‌توان مستقیما بر روی فایل PDF، محتوایی را قرار داد. شیء Document نیز در دسترس است. مثلا توسط آن می‌توان اندازه دقیق صفحه را بدست آورد.
به علاوه پارامتر columnCellsSummaryData نیز امکان دسترسی به مقادیر ردیف‌های قبلی را در اختیار شما قرار می‌دهد. برای مثال اگر نیاز دارید تا بر اساس مقادیر ستون‌ها و ردیف‌های قبلی، محاسباتی را انجام داده و در پایین صفحات درج کنید، به این ترتیب دسترسی کاملی به آن‌ها، خواهید داشت.

استفاده از این کلاس‌های سفارشی نیز همواره به شکل زیر خواهد بود:
readonly CustomHeader _customHeader = new CustomHeader();
//...
.PagesFooter(footer =>
{
   footer.CustomFooter(new CustomFooter(footer.PdfFont, PdfRunDirection.LeftToRight));
})
.PagesHeader(header =>
{
  header.CustomHeader(_customHeader);
})
کلا در PdfReport هر جایی متدی به نام CustomXYZ را مشاهده کردید، این متد یک اینترفیس را دریافت می‌کند. به عبارتی این امکان را خواهید داشت تا از متدهای پیش فرض مهیا صرفنظر کرده و مطابق نیاز، نسبت به پیاده سازی و استفاده از وهله جدیدی از این اینترفیس تعریف شده، اقدام کنید.