I’m generating a PDF using iText (version 9.2.0) in C#. The PDF contains Persian (Farsi) text, but the characters do not display correctly — they appear separated or broken.
here is my code :
public async Task<byte[]> GenerateOrderPdf(Guid orderId, CancellationToken cancellationToken)
{
var order = await _orderRepository.GetOrderById(orderId, cancellationToken);
var orderServicesList = await _orderServicesRepository.GetOrderServices(orderId, cancellationToken);
var serviceIds = orderServicesList.Select(x => x.ServiceId);
var services = await _serviceRepository.GetServices(serviceIds, cancellationToken);
using var stream = new MemoryStream();
var writer = new PdfWriter(stream);
var pdf = new PdfDocument(writer);
var document = new Document(pdf);
var vazirFontPath = Path.Combine(AppContext.BaseDirectory, "Resources", "Fonts", "Vazirmatn-Regular.ttf");
var vazirFont = PdfFontFactory.CreateFont(vazirFontPath, PdfEncodings.IDENTITY_H);
document.Add(new Paragraph($"جزئیات سفارش - کد #{order.OrderCode}")
.SetFontSize(20)
.SetTextAlignment(TextAlignment.RIGHT)
.SetBaseDirection(iText.Layout.Properties.BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont));
document.Add(new Paragraph("وضعیت : " + order.Status)
.SetTextAlignment(TextAlignment.RIGHT)
.SetBaseDirection(iText.Layout.Properties.BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont));
document.Add(new Paragraph("تاریخ ایجاد : " + order.CreateDate.ToString("yyyy-MM-dd"))
.SetTextAlignment(TextAlignment.RIGHT)
.SetBaseDirection(iText.Layout.Properties.BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont));
document.Add(new Paragraph("تاریخ پرداخت : " + order.ProcessedAt?.ToString("yyyy-MM-dd"))
.SetTextAlignment(TextAlignment.RIGHT)
.SetBaseDirection(iText.Layout.Properties.BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont));
document.Add(new Paragraph("آخرین تغییر وضعیت : " + order.LastStateChange?.ToString("yyyy-MM-dd"))
.SetTextAlignment(TextAlignment.RIGHT)
.SetBaseDirection(iText.Layout.Properties.BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont));
document.Add(new Paragraph(" ")
.SetFontSize(15));
var table = new Table(new float[] { 1, 1, 1, 1 });
table.SetWidth(UnitValue.CreatePercentValue(100))
.SetFont(vazirFont)
.SetTextAlignment(TextAlignment.CENTER);
var headerBgColor = new iText.Kernel.Colors.DeviceRgb(238, 238, 238);
table.AddHeaderCell(new Cell()
.SetBackgroundColor(headerBgColor)
.Add(new Paragraph("خدمت")
.SetBaseDirection(iText.Layout.Properties.BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont)));
table.AddHeaderCell(new Cell()
.SetBackgroundColor(headerBgColor)
.Add(new Paragraph("سیستم خدمت")
.SetBaseDirection(iText.Layout.Properties.BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont)));
table.AddHeaderCell(new Cell()
.SetBackgroundColor(headerBgColor)
.Add(new Paragraph("وضعیت")
.SetBaseDirection(iText.Layout.Properties.BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont)));
table.AddHeaderCell(new Cell()
.SetBackgroundColor(headerBgColor)
.Add(new Paragraph("جزئیات")
.SetBaseDirection(iText.Layout.Properties.BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont)));
foreach (var os in orderServicesList)
{
var serviceName = services.FirstOrDefault(x => x.Id == os.ServiceId)?.Name?.GetValue() ?? "";
table.AddCell(new Cell().Add(new Paragraph(serviceName)
.SetBaseDirection(iText.Layout.Properties.BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont)));
table.AddCell(new Cell().Add(new Paragraph(os.ServiceSystem.ToString())
.SetBaseDirection(iText.Layout.Properties.BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont)));
var statusCell = new Cell().Add(new Paragraph(os.Status.ToString())
.SetBaseDirection(iText.Layout.Properties.BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont));
if (os.Status.ToString() == "ناموفق")
{
var redBgColor = new iText.Kernel.Colors.DeviceRgb(255, 204, 204);
statusCell.SetBackgroundColor(redBgColor);
}
table.AddCell(statusCell);
table.AddCell(new Cell().Add(new Paragraph(os.DeleteStatus.ToString())
.SetBaseDirection(iText.Layout.Properties.BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont)));
}
document.Add(table);
document.Close();
return stream.ToArray();
}
Here is a part of pdf that shows the issues :
I tried embedding Persian fonts (Vazirmatn, IRANSans, etc.) I tried using PdfFontFactory.createFont() with PdfEncodings.IDENTITY_H I saw that iText has something called PdfCalligraph, but I’m not sure how to use it
using System.Text;
public class PersianTextShaper
{
private static readonly Dictionary<char, char[]> PersianForms = new Dictionary<char, char[]>
{
{ 'ا', new char[] { '\u0627', '\uFE8E', '\u0627', '\u0627' } },
{ 'ب', new char[] { '\uFE8F', '\uFE90', '\uFE91', '\uFE92' } },
{ 'پ', new char[] { '\uFB56', '\uFB57', '\uFB58', '\uFB59' } },
{ 'ت', new char[] { '\uFE95', '\uFE96', '\uFE97', '\uFE98' } },
{ 'ث', new char[] { '\uFE99', '\uFE9A', '\uFE9B', '\uFE9C' } },
{ 'ج', new char[] { '\uFE9D', '\uFE9E', '\uFE9F', '\uFEA0' } },
{ 'چ', new char[] { '\uFB7A', '\uFB7B', '\uFB7C', '\uFB7D' } },
{ 'ح', new char[] { '\uFEA1', '\uFEA2', '\uFEA3', '\uFEA4' } },
{ 'خ', new char[] { '\uFEA5', '\uFEA6', '\uFEA7', '\uFEA8' } },
{ 'د', new char[] { '\uFEA9', '\uFEAA', '\uFEA9', '\uFEA9' } },
{ 'ذ', new char[] { '\uFEAB', '\uFEAC', '\uFEAB', '\uFEAB' } },
{ 'ر', new char[] { '\uFEAD', '\uFEAE', '\uFEAD', '\uFEAD' } },
{ 'ز', new char[] { '\uFEAF', '\uFEB0', '\uFEAF', '\uFEAF' } },
{ 'ژ', new char[] { '\uFB8A', '\uFB8B', '\uFB8A', '\uFB8A' } },
{ 'س', new char[] { '\uFEB1', '\uFEB2', '\uFEB3', '\uFEB4' } },
{ 'ش', new char[] { '\uFEB5', '\uFEB6', '\uFEB7', '\uFEB8' } },
{ 'ص', new char[] { '\uFEB9', '\uFEBA', '\uFEBB', '\uFEBC' } },
{ 'ض', new char[] { '\uFEBD', '\uFEBE', '\uFEBF', '\uFEC0' } },
{ 'ط', new char[] { '\uFEC1', '\uFEC2', '\uFEC3', '\uFEC4' } },
{ 'ظ', new char[] { '\uFEC5', '\uFEC6', '\uFEC7', '\uFEC8' } },
{ 'ع', new char[] { '\uFEC9', '\uFECA', '\uFECB', '\uFECC' } },
{ 'غ', new char[] { '\uFECD', '\uFECE', '\uFECF', '\uFED0' } },
{ 'ف', new char[] { '\uFED1', '\uFED2', '\uFED3', '\uFED4' } },
{ 'ق', new char[] { '\uFED5', '\uFED6', '\uFED7', '\uFED8' } },
{ 'ک', new char[] { '\uFED9', '\uFEDA', '\uFEDB', '\uFEDC' } },
{ 'گ', new char[] { '\uFB92', '\uFB93', '\uFB94', '\uFB95' } },
{ 'ل', new char[] { '\uFEDD', '\uFEDE', '\uFEDF', '\uFEE0' } },
{ 'م', new char[] { '\uFEE1', '\uFEE2', '\uFEE3', '\uFEE4' } },
{ 'ن', new char[] { '\uFEE5', '\uFEE6', '\uFEE7', '\uFEE8' } },
{ 'و', new char[] { '\uFEED', '\uFEEE', '\uFEED', '\uFEED' } },
{ 'ه', new char[] { '\uFEE9', '\uFEEA', '\uFEEB', '\uFEEC' } },
{ 'ی', new char[] { '\uFEF1', '\uFEF2', '\uFEF3', '\uFEF4' } }
};
private static readonly HashSet<char> NonConnectors = new HashSet<char>
{
'ا', 'د', 'ذ', 'ر', 'ز', 'ژ', 'و'
};
public static string ShapePersianText(string text)
{
if (string.IsNullOrEmpty(text))
return text;
var result = new StringBuilder();
var chars = text.ToCharArray();
for (int i = 0; i < chars.Length; i++)
{
char currentChar = chars[i];
if (!PersianForms.ContainsKey(currentChar))
{
result.Append(currentChar);
continue;
}
bool hasLeft = i > 0 && IsConnectable(chars[i - 1]);
bool hasRight = i < chars.Length - 1 && IsConnectable(chars[i + 1]) && !NonConnectors.Contains(currentChar);
int formIndex = 0; // isolated
if (hasLeft && hasRight) formIndex = 3; // medial
else if (hasLeft) formIndex = 1; // final
else if (hasRight) formIndex = 2; // initial
result.Append(PersianForms[currentChar][formIndex]);
}
return result.ToString();
}
private static bool IsConnectable(char c)
{
return PersianForms.ContainsKey(c) && !char.IsWhiteSpace(c) && !char.IsPunctuation(c);
}
}
public async Task<byte[]> GenerateOrderPdf(Guid orderId, CancellationToken cancellationToken)
{
var order = await _orderRepository.GetOrderById(orderId, cancellationToken);
var orderServicesList = await _orderServicesRepository.GetOrderServices(orderId, cancellationToken);
var serviceIds = orderServicesList.Select(x => x.ServiceId);
var services = await _serviceRepository.GetServices(serviceIds, cancellationToken);
using var stream = new MemoryStream();
var writer = new PdfWriter(stream);
var pdf = new PdfDocument(writer);
var document = new Document(pdf);
var vazirFontPath = Path.Combine(AppContext.BaseDirectory, "Resources", "Fonts", "Vazirmatn-Regular.ttf");
var vazirFont = PdfFontFactory.CreateFont(vazirFontPath, PdfEncodings.IDENTITY_H);
Paragraph CreatePersianParagraph(string text, float fontSize = 12)
{
var shapedText = PersianTextShaper.ShapePersianText(text);
return new Paragraph(shapedText)
.SetFontSize(fontSize)
.SetTextAlignment(TextAlignment.RIGHT)
.SetBaseDirection(BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont);
}
document.Add(CreatePersianParagraph($"جزئیات سفارش - کد #{order.OrderCode}", 20));
document.Add(CreatePersianParagraph("وضعیت : " + order.Status));
document.Add(CreatePersianParagraph("تاریخ ایجاد : " + order.CreateDate.ToString("yyyy-MM-dd")));
document.Add(CreatePersianParagraph("تاریخ پرداخت : " + order.ProcessedAt?.ToString("yyyy-MM-dd")));
document.Add(CreatePersianParagraph("آخرین تغییر وضعیت : " + order.LastStateChange?.ToString("yyyy-MM-dd")));
document.Add(new Paragraph(" ").SetFontSize(15));
var table = new Table(new float[] { 1, 1, 1, 1 });
table.SetWidth(UnitValue.CreatePercentValue(100))
.SetFont(vazirFont)
.SetTextAlignment(TextAlignment.CENTER);
var headerBgColor = new DeviceRgb(238, 238, 238);
table.AddHeaderCell(new Cell()
.SetBackgroundColor(headerBgColor)
.Add(new Paragraph(PersianTextShaper.ShapePersianText("خدمت"))
.SetBaseDirection(BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont)));
table.AddHeaderCell(new Cell()
.SetBackgroundColor(headerBgColor)
.Add(new Paragraph(PersianTextShaper.ShapePersianText("سیستم خدمت"))
.SetBaseDirection(BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont)));
table.AddHeaderCell(new Cell()
.SetBackgroundColor(headerBgColor)
.Add(new Paragraph(PersianTextShaper.ShapePersianText("وضعیت"))
.SetBaseDirection(BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont)));
table.AddHeaderCell(new Cell()
.SetBackgroundColor(headerBgColor)
.Add(new Paragraph(PersianTextShaper.ShapePersianText("جزئیات"))
.SetBaseDirection(BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont)));
foreach (var os in orderServicesList)
{
var serviceName = services.FirstOrDefault(x => x.Id == os.ServiceId)?.Name?.GetValue() ?? "";
table.AddCell(new Cell().Add(new Paragraph(PersianTextShaper.ShapePersianText(serviceName))
.SetBaseDirection(BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont)));
table.AddCell(new Cell().Add(new Paragraph(PersianTextShaper.ShapePersianText(os.ServiceSystem.ToString()))
.SetBaseDirection(BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont)));
var statusCell = new Cell().Add(new Paragraph(PersianTextShaper.ShapePersianText(os.Status.ToString()))
.SetBaseDirection(BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont));
if (os.Status.ToString() == "ناموفق")
{
var redBgColor = new DeviceRgb(255, 204, 204);
statusCell.SetBackgroundColor(redBgColor);
}
table.AddCell(statusCell);
table.AddCell(new Cell().Add(new Paragraph(PersianTextShaper.ShapePersianText(os.DeleteStatus.ToString()))
.SetBaseDirection(BaseDirection.RIGHT_TO_LEFT)
.SetFont(vazirFont)));
}
document.Add(table);
document.Close();
return stream.ToArray();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With