สร้างเอกสาร PDF ด้วย iTextSharp

ที่มา

บ่อยครั้งที่ในชีวิตของโปรแกรมเมอร์จะต้องพบกับความต้องการของลูกค้าที่อยากได้รายงานหรือเอกสารที่สามารถสร้างได้จากระบบ แน่นอนว่าประเภทเอกสารที่ต้องการย่อมมี PDF บรรจุไว้แน่นอนเพราะเป็นเอกสารที่นิยมใช้กันอย่างแพร่หลาย ทั้งนี้ เครื่องมือสำหรับการสร้างเอกสารประเภทดังกล่าวมีอยู่มากมาย แต่จุดสำคัญนั้นอยู่ที่การเลือกใช้งานซึ่งย่อมแตกต่างกันไปตามปัจจัยต่าง ๆ เช่นในบทความนี้ ลูกค้าต้องการเอกสารเพื่อพิมพ์เป็น hard copy ไว้ที่หน่วยงาน ยังไม่ถึงขั้นรายงานนะครับ แค่เอกสารบันทึกข้อความ ดังนั้นผู้เขียนจึงไม่เลือกใช้เครื่องมือที่เก่งกาจเช่น Crystal report หรือ Reporting service และทำการค้นหาเครื่องมือที่มีน้ำหนักเบา (เวลาใช้ไม่กินทรัพยากรเยอะ) แต่ตอบสนองความต้องการได้ในขณะนั้น รวมไปถึงการมี documentation ที่ดี เข้าใจง่าย ปฏิบัติตามได้ไม่ยาก ซึ่งสุดท้ายก็มาเจอกับเครื่องมือที่ชื่อ iTextSharp

คุณสมบัติของเครื่องมือ

iTextSharp เป็นผลงานของ iText ซึ่งทำมาเพื่อการสร้างเอกสาร PDF บน C# platform โดยเฉพาะ ในขณะเดียวกันก็มีเครื่องมือแบบเดียวกันสำหรับ platform อื่น ๆ ด้วย เราจึงจะได้เห็นตัวอย่างใน documentation ของเขาเป็นภาษา Java

ความสามารถของ iTextSharp คือการสร้างเอกสาร PDF รวมไปถึงการตัดต่อเอกสารด้วย เช่น การนำเอกสารมารวมกัน การแยกเอกสารออกจากกัน นับว่าเป็นเครื่องมือที่ครบเครื่องทีเดียวสำหรับจุดประสงค์หลัก ๆ ของเรา แต่ความสามารถอื่น ๆ ก็ยังมีครับแต่ผู้เขียนยังไม่ได้ต้องการใช้งานความสามารถนั้นจริง ๆ จึงยังไม่ได้นำเสนอในบทความนี้

การใช้งานเครื่องมือนี้จะต้องใช้การเขียนโปรแกรม 100% ครับหรือเรียกเป็นภาษาอังกฤษว่า Programmatically ซึ่งยุ่งยากพอควรทีเดียวโดยเฉพาะการจัดวางตำแหน่งของแต่ละส่วน พูดได้ว่าจะต้องจินตนาการหรือร่างแบบลงบนกระดาษเลยทีเดียว ทั้งนี้ทั้งนั้นนี่ก็เป็นข้อดีข้อหนึ่งสำหรับคนชอบเขียนโปรแกรมเพราะเห็นกระบวนการชัดเจน (ไม่ค่อยสะดวกแต่สนุกดี)

ทดลองใช้

ติดตั้งโปรแกรม

ก่อนอื่นเลยก็ต้อง download library มาก่อนครับ (Link)การติดตั้งนั้นไม่ยาก แค่ reference ไปหา dll ที่เค้าให้มาก็พอ ไฟล์ที่ได้มามีทั้งหมด 3 ชุดครับคือ

  1. itextsharp-dll-core
  2. itextsharp-dll-pdfa
  3. itextsharp-dll-xtra

ทั้งหมดนี้ทำหน้าที่แตกต่างกันครับ สำหรับการสร้าง PDF เราใช้แค่ตัว itextsharp-dll-core ก็พอ

องค์ประกอบพื้นฐาน

องค์ประกอบพื้นฐานของ itextsharp นั้นมีหลายตัว แต่ตัวที่เพียงพอสำหรับความต้องการของผู้เขียนในตอนนี้มีดังนี้ครับ

  1. Chunk เป็นองค์ประกอบสำหรับ “คำ”
  2. Phrase คือ “ประโยค” ซึ่งประกอบด้วยหลาย Chunk
  3. Paragraph คือ “ย่อหน้า” ซึ่งประกอบด้วยหลาย phrase และ chunk
  4. PdfPCell คือ cell ในตาราง
  5. PdfPTable คือ ตารางประกอบด้วยหลาย PdfPCell
  6. iTextSharp.text.Image คือรูปภาพ

การกำหนด font และการเพิ่ม font

iTextSharp นั้นสามารถเพิ่ม font ได้ครับ โดยการทำตามขั้นตอนดังนี้:

BaseFont bf_bold = BaseFont.CreateFont(HttpContext.Current.Server.MapPath(“~/Regist/Theme/fonts/THSarabunNewBold.ttf”), BaseFont.IDENTITY_H, BaseFont.EMBEDDED);

เท่านี้เราก็จะมี font แบบที่เราอยากได้ไว้ใช้งานในเอกสารครับ หลังจากนั้น เราก็สร้างตัวอักษรเพื่อใช้งาน ตามตัวอย่างนี้ครับ

// Bold
BaseFont bf_bold = BaseFont.CreateFont(HttpContext.Current.Server.MapPath(“~/Regist/Theme/fonts/THSarabunNewBold.ttf”), BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
h1 = new Font(bf_bold, 18);
bold = new Font(bf_bold, 16);
smallBold = new Font(bf_bold, 14);

// Normal
BaseFont bf_normal = BaseFont.CreateFont(HttpContext.Current.Server.MapPath(“~/Regist/Theme/fonts/THSarabunNew.ttf”), BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
normal = new Font(bf_normal, 16);
smallNormal = new Font(bf_normal, 14);

เริ่มต้นสร้างเอกสาร PDF

เมื่อเรากำหนด font ไว้ใช้งานเสร็จแล้ว เราก็สามารถสร้างเอกสารได้เลยครับ  การสร้างเอกสารจะเริ่มจากการกำหนดขนาดเอกสารครับ ตามตัวอย่างนี้

 

Document pdfDoc = new Document(PageSize.A4, 30, 30, 20, 20);
PdfWriter pdfWriter = PdfWriter.GetInstance(pdfDoc, Response.OutputStream);
pdfDoc.Open();

 

โดยตัวเลข 4 ตัวหลัง layout ของกระดาษคือระยะขอบครับ

เริ่มใส่องค์ประกอบลงในเอกสาร

ตัวเอกสารของผมจะเริ่มจากส่วนที่เป็นหัวของเอกสารก่อน ซึ่งจะประกอบด้วยรูปภาพ 1 รูปและข้อความ 1 ข้อความ แต่ปัญหาอยู่การจัดการ layout เพราะเมื่อเราใส่องค์ประกอบเข้าไปมันจะชิดทางด้านซ้ายให้โดยอัตโนมัติ ในกรณีนี้จึงต้องใช้ PdfPTable มาช่วยจัดส่วนหัวเอกสารก่อนครับ ทำการกำหนดขนาดของตารางแล้วจัดองค์ประกอบเข้าไปอยู่ในแต่ละ cell

private PdfPTable GetHeader() {

PdfPTable headerTable = new PdfPTable(2);
headerTable.TotalWidth = 530f;
headerTable.HorizontalAlignment = 0;
headerTable.SpacingAfter = 20;
//headerTable.DefaultCell.Border = Rectangle.NO_BORDER;

float[] headerTableColWidth = new float[2];
headerTableColWidth[0] = 220f;
headerTableColWidth[1] = 310f;

headerTable.SetWidths(headerTableColWidth);
headerTable.LockedWidth = true;

iTextSharp.text.Image png = iTextSharp.text.Image.GetInstance(imagePath + “/thai_gov.png”);
png.ScaleAbsolute(40, 40);

PdfPCell headerTableCell_0 = new PdfPCell(png);
headerTableCell_0.HorizontalAlignment = Element.ALIGN_LEFT;
headerTableCell_0.Border = Rectangle.NO_BORDER;
headerTable.AddCell(headerTableCell_0);

PdfPCell headerTableCell_1 = new PdfPCell(new Phrase(“บันทึกข้อความ”, h1));
headerTableCell_1.HorizontalAlignment = Element.ALIGN_LEFT;
headerTableCell_1.VerticalAlignment = Element.ALIGN_BOTTOM;
headerTableCell_1.Border = Rectangle.NO_BORDER;
headerTable.AddCell(headerTableCell_1);

return headerTable;
}

เมื่อทำการเพิ่มเข้าไปเอกสารแล้วจะได้ลักษณะตามภาพ

pdf_header

หลังจากนั้นผมก็เริ่มใส่องค์ประกอบที่เป็นรายละเอียดของส่วนหัวของเอกสารดังนี้

private PdfPTable GetHeaderDetail() {
PdfPTable table = new PdfPTable(2);
table.TotalWidth = 530f;
table.HorizontalAlignment = 0;
table.SpacingAfter = 10;

float[] tableWidths = new float[2];
tableWidths[0] = 400f;
tableWidths[1] = 130f;

table.SetWidths(tableWidths);
table.LockedWidth = true;

Chunk blank = new Chunk(” “, normal);

Phrase p = new Phrase();

p.Add(new Chunk(“ส่วนราชการ”, bold));
p.Add(new Chunk(blank));
p.Add(new Chunk(“วิเทศสัมพันธ์”, normal));

PdfPCell cell0 = new PdfPCell(p);
cell0.Border = Rectangle.NO_BORDER;

table.AddCell(cell0);

p = new Phrase();
p.Add(new Chunk(“โทร”, bold));
p.Add(new Chunk(blank));
p.Add(new Chunk(“2012”, normal));

PdfPCell cell1 = new PdfPCell(p);
cell1.Border = Rectangle.NO_BORDER;

table.AddCell(cell1);

p = new Phrase();
p.Add(new Chunk(“ที่ มอ”, bold));
p.Add(new Chunk(blank));
p.Add(new Chunk(master.ApplicationNo, normal));

cell0 = new PdfPCell(p);
cell0.Border = Rectangle.NO_BORDER;

table.AddCell(cell0);

p = new Phrase();
p.Add(new Chunk(“วันที่”, bold));
p.Add(new Chunk(blank));
p.Add(new Chunk(master.CreatedDate, normal));

cell1 = new PdfPCell(p);
cell1.Border = Rectangle.NO_BORDER;

table.AddCell(cell1);

p = new Phrase();
p.Add(new Chunk(“เรื่อง”, bold));
p.Add(new Chunk(blank));
p.Add(new Chunk(“ขออนุมัติเดินทางไปต่างประเทศ”, normal));

cell0 = new PdfPCell(p);
cell0.Border = Rectangle.NO_BORDER;
cell0.Colspan = 2;

table.AddCell(cell0);

return table;
}

เช่นเคยครับ เราจะต้องใช้ Table มากำหนด layout ของเอกสาร เมื่อทำการเพิ่มแล้วก็จะได้เอกสารหน้าตาดังนี้

pdf_header_detail

หลังจากนั้นก็ใส่องค์ประกอบต่าง ๆ เพิ่มเข้าไปทีละชิ้น ๆ ตาม source code ด้านล่าง

 

private Paragraph GetBodyHeader()
{
Phrase p = new Phrase();

p.Add(new Chunk(“เรียน “, normal));
p.Add(new Chunk(“รองอธิการบดีฝ่ายองค์กรสัมพันธ์และสารสนเทศ”, normal));

Paragraph para = new Paragraph(p);
para.SpacingBefore = 20;
para.SpacingAfter = 20;

return para;
}

private Paragraph GetBody() {
Paragraph para = new Paragraph();

para.FirstLineIndent = 38.1f;

para.Add(new Phrase(“ด้วย”, normal));
para.Add(new Phrase(“งานวิเทศสัมพันธ์”, normal));
para.Add(new Phrase(“ขออนุมัติให้นักศึกษาจำนวน ” + master.StudentCount + ” คน เดินทางไปราชการต่างประเทศระหว่างวันที่ ” + master.StartDate + ” ถึงวันที่ ” + master.EndDate + ” รวม ” + master.PeriodDay + ” วัน เพื่อดำเนินกิจกรรมดังต่อไปนี้”, normal));

return para;
}

private PdfPTable GetActivities()
{
PdfPTable table = new PdfPTable(3);

table.TotalWidth = 530f;
table.HorizontalAlignment = 0;
table.SpacingBefore = 20;
table.SpacingAfter = 20;

// ชื่อกิจกรรม
// สถาบัน
// ประเทศ

float[] columnWidths = new float[3];
columnWidths[0] = 200f;
columnWidths[1] = 200f;
columnWidths[2] = 130f;

table.SetWidths(columnWidths);
table.LockedWidth = true;

PdfPCell cell0 = new PdfPCell(new Phrase(“กิจกรรม”, bold));
cell0.HorizontalAlignment = Element.ALIGN_LEFT;
cell0.Border = Rectangle.NO_BORDER;
table.AddCell(cell0);

PdfPCell cell1 = new PdfPCell(new Phrase(“สภานที่”, bold));
cell1.HorizontalAlignment = Element.ALIGN_LEFT;
cell1.Border = Rectangle.NO_BORDER;
table.AddCell(cell1);

PdfPCell cell2 = new PdfPCell(new Phrase(“ประเทศ”, bold));
cell2.HorizontalAlignment = Element.ALIGN_LEFT;
cell2.Border = Rectangle.NO_BORDER;
table.AddCell(cell2);

List<MasterActivity> activity = master.GetActivities();

foreach (MasterActivity a in activity)
{
cell0 = new PdfPCell(new Phrase(a.ActivityNameThai, normal));
cell0.HorizontalAlignment = Element.ALIGN_LEFT;
cell0.Border = Rectangle.NO_BORDER;
table.AddCell(cell0);

cell1 = new PdfPCell(new Phrase(a.HostName, normal));
cell1.HorizontalAlignment = Element.ALIGN_LEFT;
cell1.Border = Rectangle.NO_BORDER;
table.AddCell(cell1);

Institution host = Institution.GetById(a.HostId);

cell2 = new PdfPCell(new Phrase(host.CountryName, normal));
cell2.HorizontalAlignment = Element.ALIGN_LEFT;
cell2.Border = Rectangle.NO_BORDER;
table.AddCell(cell2);
}

return table;
}

private Paragraph GetBodyFooter()
{
Paragraph para = new Paragraph(new Phrase(“จึงเรียนมาเพื่อโปรดพิจารณาอนุมัติด้วย จักเป็นพระคุณยิ่ง”, normal));
para.FirstLineIndent = 38.1f;
para.SpacingAfter = 25;
return para;
}

private void GetSignature(Document pdfDoc) {

Paragraph para;
Phrase p;
Chunk dotLine = new Chunk(“……………………………………………”, normal);

//if (master.LevelId.Equals(“D”))
//{
// p = new Phrase(dotLine);
// p.Add(new Chunk(“หัวหน้าภาควิชา”, normal));
// para = new Paragraph(p);
// pdfDoc.Add(para);
//}

p = new Phrase(dotLine);
p.Add(new Chunk(“หัวหน้าภาควิชา”, normal));
para = new Paragraph(p);
para.SpacingAfter = 15;
pdfDoc.Add(para);

p = new Phrase(dotLine);
p.Add(new Chunk(“คณบดี”, normal));
para = new Paragraph(p);
para.SpacingAfter = 15;
pdfDoc.Add(para);
}

 

private PdfPTable GetStudentList() {

Phrase p;

PdfPTable table = new PdfPTable(8);
table.TotalWidth = 530f;
table.HorizontalAlignment = 0;
//table.SpacingAfter = 20;
//headerTable.DefaultCell.Border = Rectangle.NO_BORDER;

float[] colWidths = new float[8];
colWidths[0] = 30f;
colWidths[1] = 70f;
colWidths[2] = 70f;
colWidths[3] = 70f;
colWidths[4] = 70f;
colWidths[5] = 70f;
colWidths[6] = 70f;
colWidths[7] = 70f;

table.SetWidths(colWidths);
table.LockedWidth = true;

PdfPCell cell;

p = new Phrase(“รายชื่อผู้เดินทางจาก ” + master.StartDate + ” ถึง ” + master.EndDate, normal);

cell = new PdfPCell(p);
cell.Colspan = 8;
cell.HorizontalAlignment = Element.ALIGN_CENTER;
cell.Border = Rectangle.NO_BORDER;
cell.PaddingBottom = 10;
table.AddCell(cell);

#region Header

cell = new PdfPCell(new Phrase(“ที่”, smallBold));
cell.HorizontalAlignment = Element.ALIGN_CENTER;
table.AddCell(cell);

cell = new PdfPCell(new Phrase(“รหัสนักศึกษา”, smallBold));
cell.HorizontalAlignment = Element.ALIGN_CENTER;
table.AddCell(cell);

cell = new PdfPCell(new Phrase(“คำนำหน้า”, smallBold));
cell.HorizontalAlignment = Element.ALIGN_CENTER;
table.AddCell(cell);

cell = new PdfPCell(new Phrase(“ชื่อ”, smallBold));
cell.HorizontalAlignment = Element.ALIGN_CENTER;
table.AddCell(cell);

cell = new PdfPCell(new Phrase(“สกุล”, smallBold));
cell.HorizontalAlignment = Element.ALIGN_CENTER;
table.AddCell(cell);

cell = new PdfPCell(new Phrase(“คณะ”, smallBold));
cell.HorizontalAlignment = Element.ALIGN_CENTER;
table.AddCell(cell);

cell = new PdfPCell(new Phrase(“วันที่เริ่ม”, smallBold));
cell.HorizontalAlignment = Element.ALIGN_CENTER;
table.AddCell(cell);

cell = new PdfPCell(new Phrase(“วันที่สิ้นสุด”, smallBold));
cell.HorizontalAlignment = Element.ALIGN_CENTER;
table.AddCell(cell);

#endregion

#region Data

List<OutboundApplication> apps = master.GetApplications();

int i = 0;

foreach (OutboundApplication app in apps)
{
cell = new PdfPCell(new Phrase((i + 1).ToString(), smallNormal));
cell.HorizontalAlignment = Element.ALIGN_RIGHT;
table.AddCell(cell);

cell = new PdfPCell(new Phrase(app.StudentId, smallNormal));
cell.HorizontalAlignment = Element.ALIGN_CENTER;
table.AddCell(cell);

cell = new PdfPCell(new Phrase(app.TitleName, smallNormal));
cell.HorizontalAlignment = Element.ALIGN_LEFT;
table.AddCell(cell);

cell = new PdfPCell(new Phrase(app.Firstname, smallNormal));
cell.HorizontalAlignment = Element.ALIGN_LEFT;
table.AddCell(cell);

cell = new PdfPCell(new Phrase(app.Lastname, smallNormal));
cell.HorizontalAlignment = Element.ALIGN_LEFT;
table.AddCell(cell);

cell = new PdfPCell(new Phrase(app.FacultyNameThai, smallNormal));
cell.HorizontalAlignment = Element.ALIGN_LEFT;
table.AddCell(cell);

cell = new PdfPCell(new Phrase(app.StartDate, smallNormal));
cell.HorizontalAlignment = Element.ALIGN_CENTER;
table.AddCell(cell);

cell = new PdfPCell(new Phrase(app.EndDate, smallNormal));
cell.HorizontalAlignment = Element.ALIGN_CENTER;
table.AddCell(cell);

i += 1;
}

#endregion

return table;
}

และส่วนของ Main program ก็จะเป็นแบบนี้ครับ

protected void Page_Load(object sender, EventArgs e)
{
InitElements();

try
{
// Create PDF document
Document pdfDoc = new Document(PageSize.A4, 30, 30, 20, 20);
PdfWriter pdfWriter = PdfWriter.GetInstance(pdfDoc, Response.OutputStream);
pdfDoc.Open();

pdfDoc.Add(GetHeader());
pdfDoc.Add(GetHeaderDetail());

LineSeparator line = new LineSeparator();

pdfDoc.Add(line);

pdfDoc.Add(GetBodyHeader());
pdfDoc.Add(GetBody());
pdfDoc.Add(GetActivities());
pdfDoc.Add(GetBodyFooter());
GetSignature(pdfDoc);
pdfDoc.NewPage();
pdfDoc.Add(GetStudentList());

pdfWriter.CloseStream = false;
pdfDoc.Close();
Response.Buffer = true;
Response.ContentType = “application/pdf”;
Response.AddHeader(“content-disposition”, “attachment;filename=Example.pdf”);
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Write(pdfDoc);
Response.End();
}
catch (Exception ex)
{
Response.Write(ex.Message);
}
}

สุดท้ายแล้วเราก็จะได้เอกสาร PDF หน้าตาแบบนี้ครับ Example

บทสรุป

จะเห็นได้ว่าการสร้างเอกสาร PDF ด้วยเครื่องมือนี้จะกินแรงพอควรนะครับ แต่ข้อดีของ iTextSharp คือน้ำหนักเบาและด้วยการเขียนโค้ดของเรา เราสามารถจะนำอะไรก็ได้ (ตามที่มันมีให้) ไปใส่ในเอกสารได้อย่างอิสระ สำหรับตอนนี้ iTextSharp ยังคงตอบสนองได้ตามความต้องการ แต่เมื่อความต้องการของลูกค้าเปลี่ยนแปลง เราจะได้เห็นกันว่าเครื่องมือตัวนี้จะทรงพลังพอหรือไม่