ที่มา
บ่อยครั้งที่ในชีวิตของโปรแกรมเมอร์จะต้องพบกับความต้องการของลูกค้าที่อยากได้รายงานหรือเอกสารที่สามารถสร้างได้จากระบบ แน่นอนว่าประเภทเอกสารที่ต้องการย่อมมี 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 ชุดครับคือ
- itextsharp-dll-core
- itextsharp-dll-pdfa
- itextsharp-dll-xtra
ทั้งหมดนี้ทำหน้าที่แตกต่างกันครับ สำหรับการสร้าง PDF เราใช้แค่ตัว itextsharp-dll-core ก็พอ
องค์ประกอบพื้นฐาน
องค์ประกอบพื้นฐานของ itextsharp นั้นมีหลายตัว แต่ตัวที่เพียงพอสำหรับความต้องการของผู้เขียนในตอนนี้มีดังนี้ครับ
- Chunk เป็นองค์ประกอบสำหรับ “คำ”
- Phrase คือ “ประโยค” ซึ่งประกอบด้วยหลาย Chunk
- Paragraph คือ “ย่อหน้า” ซึ่งประกอบด้วยหลาย phrase และ chunk
- PdfPCell คือ cell ในตาราง
- PdfPTable คือ ตารางประกอบด้วยหลาย PdfPCell
- 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;
}
เมื่อทำการเพิ่มเข้าไปเอกสารแล้วจะได้ลักษณะตามภาพ
หลังจากนั้นผมก็เริ่มใส่องค์ประกอบที่เป็นรายละเอียดของส่วนหัวของเอกสารดังนี้
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 ของเอกสาร เมื่อทำการเพิ่มแล้วก็จะได้เอกสารหน้าตาดังนี้
หลังจากนั้นก็ใส่องค์ประกอบต่าง ๆ เพิ่มเข้าไปทีละชิ้น ๆ ตาม 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 ยังคงตอบสนองได้ตามความต้องการ แต่เมื่อความต้องการของลูกค้าเปลี่ยนแปลง เราจะได้เห็นกันว่าเครื่องมือตัวนี้จะทรงพลังพอหรือไม่