前言
PdfPCell類繼承於Rectangle類,因此也繼承了很多修改邊框和背景色的屬性和方法,后續我們會討論到,但現在我們先要說明PdfPCell的內容模式。在iText的內部PdfPCell的內容被保存在ColumnText對象里面,如果你對ColumnText類有比較清晰的了解那么PdfPCell就很好理解了。但如果大家跳過了第三節直接到這里的話,那還是希望大家先學習第三節中關於ColumnText的內容。
PdfPCell in text mode
在這個子節中我們會用Phrase和Chunk對象作為填充表格的內容,以下為效果圖:
這里不能使用Paragraph,List或者Image對象,因為我們處於文本模式(text mode),而且要注意的時:對齊選項和行高是通過PdfPCell類的屬性來設置的。
COLSPAN, ROWSPAN, AND THE ALIGNMENT OF A CELL
好了,直接上代碼
listing 4.6 MovieTextMode.cs
List<Movie> movies = PojoFactory.GetMovies(conn); foreach (Movie movie in movies) { PdfPTable table = new PdfPTable(2); table.SetWidths(new int[] { 1, 4 }); PdfPCell cell; cell = new PdfPCell(new Phrase(movie.Title, FilmFonts.BOLD)); cell.HorizontalAlignment = Element.ALIGN_CENTER; cell.Colspan = 2; table.AddCell(cell); if (movie.OriginalTitle != null) { cell = new PdfPCell(PojoToElementFactory.GetOriginalTitlePhrase(movie)); cell.Colspan = 2; cell.HorizontalAlignment = Element.ALIGN_RIGHT; table.AddCell(cell); } List<Director> directors = movie.Directors; cell = new PdfPCell(new Phrase("Directors:")); cell.Rowspan = directors.Count; cell.VerticalAlignment = Element.ALIGN_MIDDLE; table.AddCell(cell); int count = 0; foreach (Director pojo in directors) { cell = new PdfPCell(PojoToElementFactory.GetDirectorPhrase(pojo)); cell.Indent = 10 * count++; table.AddCell(cell); } table.DefaultCell.HorizontalAlignment = Element.ALIGN_RIGHT; table.AddCell("Year:"); table.AddCell(movie.Year.ToString()); table.AddCell("Run Length :"); table.AddCell(movie.Duration.ToString()); List<Country> countries = movie.Countries; cell = new PdfPCell(new Phrase("Countries:")); cell.Rowspan = countries.Count; cell.VerticalAlignment = Element.ALIGN_BOTTOM; table.AddCell(cell); foreach (Country country in countries) { table.AddCell(country.C_Country); } document.Add(table); }
在上面的代碼中,單元格是通過以下兩種方式添加到表格中的:
- 通過PdfPCell對象----我們傳入Phrase對象的參數構建一個PdfPCell對象,然后通過AddCell方法將其添加到表格。在添加到表格之前我們設置了PdfPCell的一些屬性如:HorizontalAlignment和VerticalAlignment 。
- 不通過PdfPCell對象----我們直接將string或者Phrase對象通過AddCell方法添加到表格中,這個時候我們就通過DefaultCell獲取表格的默認單元格來設置其屬性。
就如同ColumnText對象一樣我們可以設置Indent(第一行的縮進)和FollowingIndent,SpaceCharRatio等屬性。
SPACING IN CELLS
上圖中右邊的單元格顯示的是有不同的行高(leading)和padding。大家要注意的是在文本模式下單元格中內容的行高就等於字體的大小,在內部其是如同這樣設置的:SetLeading(0,1)。所以Phrase在PdfPcell中和不在其中的行高是不同的。不過我們可以像ColumnText對象一樣通過SetLeading方法來改變此設置。以下為圖中前五行的設置代碼:
listing 4.7 Spacing.cs
PdfPCell cell = new PdfPCell(p); //row 1 table.AddCell("default leading/ spacing"); table.AddCell(cell); //row 2 table.AddCell("absolute leading: 20"); cell.SetLeading(20f, 0f); table.AddCell(cell); //row 3 table.AddCell("absolute leading: 3; relative leading: 1.2"); cell.SetLeading(3f, 1.2f); table.AddCell(cell); //row 4 table.AddCell("absolute leading: 0; relative leading: 1.2"); cell.SetLeading(0f, 1.2f); table.AddCell(cell); //row 5 table.AddCell("no leading at all"); cell.SetLeading(0f, 0f); table.AddCell(cell);
這里要說明的是:行高設置為零是不推薦的做法,從圖上可以看到行高為零的內容會溢出單元格並和前一列的內容重疊了。PdfPCell內的padding的默認大小為2pt,但也可以通過Padding屬性來改變,以下為6到8行的padding設置代碼:
listing 4.8 Spacing.cs (contiuned)
//row 6 cell = new PdfPCell(new Phrase("Dr. iText or: How I Learned to Stop Worrying and Love PDF")); table.AddCell("padding 10"); cell.Padding = 10; table.AddCell(cell); //row 6 table.AddCell("padding 0"); cell.Padding = 0; table.AddCell(cell); //row 7 table.AddCell("different padding for left,right, top and bottom"); cell.PaddingLeft = 20; cell.PaddingRight = 50; cell.PaddingTop = 0; cell.PaddingBottom = 5; table.AddCell(cell);
以上代碼中的Padding屬性和HTML中table標簽的cellPadding屬性有類似的行為,但在iText中沒有對應的CellSpacing屬性,不過我們會在第五節是通過單元格的事件來達到同樣的效果。
我們可以根據單元格第一行的ascender來調整padding值。bottom padding的值則適應單元格中的最后一行的descender值。以下的代碼為12行的設置代碼:
listing 4.9 Spacing.cs (contiuned)
table.DefaultCell.UseDescender = true; table.DefaultCell.UseAscender = true; table.AddCell("padding 2; ascender, descender"); cell.Padding = 2; cell.UseAscender = true; cell.UseDescender = true; table.AddCell(p);
這里的ascender和descender是在前幾節中丈量文本時已經提到的:ascender是在基准線上需要的空間,descender是在基准線下需要的空間。通過行間距,padding和ascender,descender會對單元格的高度有影響,擴展起來就是對一行的高度有影響。
ROW HEIGHT
每一行的高度是需要計算的,但iText並不是每次都有足夠的數據來計算,具體參考以下代碼:
listing 4.10 TableHeight.cs
PdfPTable table = CreateFirestTable(); document.Add(new Paragraph(string.Format("Table height before document.Add(): {0}", table.TotalHeight))); document.Add(new Paragraph(string.Format("Height of the first row : {0}", table.GetRowHeight(0)))); document.Add(table); document.Add(new Paragraph(string.Format("Table height after document.Add(): {0}", table.TotalHeight))); document.Add(new Paragraph(string.Format("Height of the firest row: {0}", table.GetRowHeight(0)))); table = CreateFirestTable(); document.Add(new Paragraph(string.Format("Table height before SetTotalWidth(): {0}", table.TotalHeight))); document.Add(new Paragraph(string.Format("Height of the first row: {0}", table.GetRowHeight(0)))); table.TotalWidth = 50; table.LockedWidth = true; document.Add(new Paragraph(string.Format("Table height after SetTotalWidth(): {0}", table.TotalHeight))); document.Add(new Paragraph(string.Format("Height of the first row: {0}", table.GetRowHeight(0)))); document.Add(table);
在以上的代碼中,表格的高度在添加到Document之前為零,添加之后為48。這個是正常的:在iText不知道表格的寬度時是不能計算出高度的。同樣的table在設置了50pt的固定寬度時就有192pt的高度,但由於其寬度遠遠小余頁面寬度的80%,里面的文本就被包裹起來。但也可以設置NoWrap屬性來改變。具體的說明參考下圖和代碼:
liting 4.11 CellHeights.cs
// a long phrase with newlines p = new Phrase("Dr. iText or:\nHow I Learned to Stop Worrying\nand Love PDF."); cell = new PdfPCell(p); // the phrase fits the fixed height table.AddCell("fixed height(more than sufficient)"); cell.FixedHeight = 72f; table.AddCell(cell); // the phrase doesn't fit the fixed height table.AddCell("fixed height(not sufficient)"); cell.FixedHeight = 36f; table.AddCell(cell); // The minimum height is exceeded table.AddCell("minimum height"); cell = new PdfPCell(new Phrase("Dr. iText")); cell.MinimumHeight = 36f; table.AddCell(cell); // The last row is extended table.ExtendLastRow = true; table.AddCell("extend last row"); table.AddCell(cell); document.Add(table);
通過以上的代碼和圖大家可以看到如果設置NoWrap為True的話文本就有可能溢出單元格,所以一般情況下還是不要設置。以上的行高都是計算出來的,但我們也可以通過FixedHeight 屬性來設置行高,以上代碼中我們設置了兩個行高:72和36,然后將文本:Dr.iText or : How I learned to stop worrying and love PDF添加進單元格,從圖中可以看到36pt的行高不夠因此and Love PDF就沒有顯示出來。這就可以回答了在學習ColumnText時提到的問題:如果文本不能完全填充時有什么結果。所以我們調用ColumnText的方法時要確認文本能夠完全填充或者你希望通過這個方法減少一些文本。設置FixedHeight的值太少會導致文本減少,但我們還可以設置MinimumHeight屬性:如果文本可以填充的話行高就不變,如果不能填充行高就會相應的變大。最后一行的單元格延伸到了頁面的下邊距,但這不是PdfPCell的屬性,是通過PdfPTable的ExtendLastRow來實現的。
目前為止我們都是在文本模式的情況下討論PdfPCell的屬性,但除了leading, horizontal alignment和indentation屬性之外,其它的屬性如cell height,padding等在組合模式時也是有效的。有效的屬性也包括從Rectangle類繼承的屬性。
ROTATION, BACKGROUND COLOR, BORDERS, AND BORDER COLORS
這里就直接上圖和代碼:
listing 4.12 RotationAndColors.cs
// row 1, cell 1 cell = new PdfPCell(new Phrase("COLOR")); cell.Rotation = 90; cell.VerticalAlignment = Element.ALIGN_TOP; table.AddCell(cell); // row 1, cell 2 cell = new PdfPCell(new Phrase("red/ no borders")); cell.Border = Rectangle.NO_BORDER; cell.BackgroundColor = BaseColor.RED; table.AddCell(cell); // row 1, cell 3 cell = new PdfPCell(new Phrase("green/ black bottom border")); cell.Border = Rectangle.BOTTOM_BORDER; cell.BorderColor = BaseColor.BLACK; cell.BorderWidthBottom = 10f; cell.BackgroundColor = BaseColor.GREEN; table.AddCell(cell); // row 1, cell 4 cell = new PdfPCell(new Phrase("cyan / blue top border + padding")); cell.Border = Rectangle.TOP_BORDER; cell.UseBorderPadding = true; cell.BorderWidthTop = 5f; cell.BorderColorTop = BaseColor.BLUE; cell.BackgroundColor = BaseColor.CYAN; table.AddCell(cell); // row 2, cell 1 cell = new PdfPCell(new Phrase("GRAY")); cell.Rotation = 90; cell.VerticalAlignment = Element.ALIGN_MIDDLE; table.AddCell(cell); // row 2, cell 2 cell = new PdfPCell(new Phrase("0.6")); cell.Border = Rectangle.NO_BORDER; cell.GrayFill = 0.6f; table.AddCell(cell);
通過單元格的Rotation屬性我們設置其內容的旋轉,和Image對象一樣默認是逆時針旋轉。如第二行的第一個單元格是通過Rotaion和VerticalAlignment來實現的。從圖上大家會發現這個表格比較花哨,其中包含了很多的顏色。第一行的第二個單元格就是通過BackgroundColor 來設置背景色,而第二行的背景色是通過GrayFill來設置。邊框的設置是通過Border屬性設置,其中Rectangle.NO_BORDER就是沒有邊框,上下左右的邊框對應的就是TOP_BORDER,BOTTOM_BORDER,LEFT_BORDER,RIGHT_BORDER。大家記得在學習Image的時候Rectangle.BOX會同時設置上下左右邊框,這個值也是單元格的默認選項。
以上的代碼還設置了UseBorderPadding屬性為True,這樣border就會計算在padding內,如果不設置的話文本就會覆蓋border,就如同第三行的第二個單元格一樣。border的設置可以通過Border的相應屬性統一設置,也可以分別設置,但他們之間還是有點小區別:
listing 4.13 RotationAndColors.cs (continued)
// row 3, cell 3 cell = new PdfPCell(new Phrase("with correct padding")); cell.UseBorderPadding = true; cell.BorderWidthLeft = 16f; cell.BorderWidthBottom = 12f; cell.BorderWidthRight = 8f; cell.BorderWidthTop = 4f; cell.BorderColorLeft = BaseColor.RED; cell.BorderColorBottom = BaseColor.ORANGE; cell.BorderColorRight = BaseColor.YELLOW; cell.BorderColorTop = BaseColor.GREEN; table.AddCell(cell); // row 3, cell 4 cell = new PdfPCell(new Phrase("red border")); cell.BorderWidth = 8f; cell.BorderColor = BaseColor.RED; table.AddCell(cell);
但我們設置某一個border時,UseVariableBorders屬性就會被設置為true,這就會將border畫進單元格里面。但如果通過統一的Border屬性設置的話,UseVariableBorders屬性就不會變(當然我們是可以手動將其設置為true),所以圖中的最后一個單元格的border就超出了單元格的邊界。
PdfPCell in composite mode
在文本模式下,我們只可以添加Chunk和Phrase對象,如果要添加Lists和Image就需要組合模式。大家要注意的是代碼:
PdfPCell cell = new PdfPCell(new Paragraph("some text"));
和代碼:
PdfPCell cell = new PdfPCell(); cell.AddElement(new Paragraph("some text"));
以上的兩份代碼是有很大的區別 在第一個代碼中,Paragraph對象的一些屬性如leading,alignment是不起作用的,相反要設置對應PdfPCell對象的屬性。在第二個代碼中,我們通過AddElement方法轉換為組合模式,這里PdfPCell對象的leading,alignment,indentation屬性就不起作用了。這種架構和我們學到的ColumnText對象一樣。
電影列表
現在我們用table來創建電影列表信息。具體看下圖:
listing 4.14 MovieCompositeMode.cs
cell = new PdfPCell(); // a cell with paragraphs and lists Paragraph p = new Paragraph(movie.Title, FilmFonts.BOLD); p.Alignment = Element.ALIGN_CENTER; p.SpacingAfter = p.SpacingBefore = 5; cell.AddElement(p); cell.Border = PdfPCell.NO_BORDER; if (movie.OriginalTitle != null) { p = new Paragraph(movie.OriginalTitle, FilmFonts.ITALIC); p.Alignment = Element.ALIGN_RIGHT; cell.AddElement(p); } list = PojoToElementFactory.GetDirectorList(movie); list.IndentationLeft = 30; cell.AddElement(list); p = new Paragraph(string.Format("Year: {0}", movie.Year), FilmFonts.NORMAL); p.IndentationLeft = 15; p.Leading = 24; cell.AddElement(p); p = new Paragraph(string.Format("Run length: {0}", movie.Duration), FilmFonts.NORMAL); p.Leading = 14; p.IndentationLeft = 30; cell.AddElement(p); list = PojoToElementFactory.GetCountryList(movie); list.IndentationLeft = 40; cell.AddElement(list); table.AddCell(cell);
以上代碼中table包含兩個單元格:第一個為電影的海報,第二個為電影的其他信息。第二個單元格由不同alignment,leading,spacing和indentation的Paragraph,List對象組成。因為處於組合模式下我們要為單獨的類設置屬性。對於Image對象我們可以設置是否將其縮放。
將圖片添加到表格
好了直接上圖:
listing 4.15 XMen.cs
// first movie table.DefaultCell.HorizontalAlignment = Element.ALIGN_CENTER; table.DefaultCell.VerticalAlignment = Element.ALIGN_TOP; table.AddCell("X-Men"); // we wrap he image in a PdfPCell PdfPCell cell = new PdfPCell(img[0]); table.AddCell(cell); // second movie table.DefaultCell.VerticalAlignment = Element.ALIGN_MIDDLE; table.AddCell("X2"); // we wrap the image in a PdfPCell and let iText scale it cell = new PdfPCell(img[1], true); table.AddCell(cell); // third movie table.DefaultCell.VerticalAlignment = Element.ALIGN_BOTTOM; table.AddCell("X-Men:The Last Stand"); // we add the image with addCell() table.AddCell(img[2]); // fourth movie table.AddCell("Superman Returns"); cell = new PdfPCell(); // we add it with addElement(); it can only take 50% of the width. img[3].WidthPercentage = 50; cell.AddElement(img[3]); table.AddCell(cell); // we complete the table (otherwise the last row won't be rendered) table.CompleteRow(); document.Add(table);
上圖中我們用4種不同的方法顯示4部電影海報。其中前兩個圖片(X戰警1和2)是將Image對象作為pdfPCell的構造器參數傳入,第三個圖片(X戰警3)Image對象是直接通過AddCell方法添加,最后一個圖片(超人歸來)Image是被PdfPCell的AddElement方法添加進去。將Image對象作為PdfPCell的構造器參數時,單元格的默認padding就從2pt變為0pt。PdfPCell的相應構造器還有一個bool類型的參數,為true時iText會將圖片縮放,但默認為false,因此如果沒設置的畫圖片可能會溢出單元格。通過AddCell方法添加圖片時圖片會自動縮放,但默認單元格的設置會影響圖片的屬性,如第三張圖片的padding就為2pt,而且是向下對其。最后通過AddElement方法添加時圖片就會縮放以便100%填充單元格的寬度,但可以通過WidthPercentage來設置。除了Image之外,我們還可以在表中添加表格,也就是嵌套表格。
嵌套表格
在iText以前還不支持rowspan屬性的時候,要完成類似的功能就只能用嵌套表格的方法。下圖就是一個嵌套表格的效果圖:
在上圖中單元格1.1和1.2為一個嵌入的表格,單元格12.1和單元格12.2也為一個嵌入的表格,通過這種方法單元格13,14,15看起來就有一個rowspan為2的屬性。一下為具體的代碼:
listing 4.16 NestedTable.cs
PdfPTable table = new PdfPTable(4); PdfPTable nested1 = new PdfPTable(2); nested1.AddCell("1.1"); nested1.AddCell("1.2"); PdfPTable nested2 = new PdfPTable(1); nested2.AddCell("12.1"); nested2.AddCell("12.2"); for (int k = 0; k < 16; k++) { if (k == 1) { table.AddCell(nested1); } else if (k == 12) { table.AddCell(new PdfPCell(nested2)); } else { table.AddCell("cell" + k); } } document.Add(table);
就如同添加Image對象一樣,當PdfPTable對象通過AddCell方法直接添加時padding為2pt,但將PdfPTable對象包裹在PdfPCell中時padding就為0pt。
復雜的表格布局
直接上圖:
listing 4.17 NestedTables.cs
public override void CreateFile(string fileName) { // step 1 Document document = new Document(); using (document) { // step 2 PdfWriter writer = PdfWriter.GetInstance(document, new FileStream(fileName, FileMode.Create)); // step 3 document.Open(); string connStr = ConfigurationManager.AppSettings["SQLiteConnStr"]; SQLiteConnection conn = new SQLiteConnection(connStr); using (conn) { conn.Open(); List<DateTime> days = PojoFactory.GetDays(conn); foreach (DateTime day in days) { document.Add(GetTable(conn, day)); document.NewPage(); } } } } public PdfPTable GetTable(IDbConnection conn, DateTime day) { // Create a table with only one column PdfPTable table = new PdfPTable(1); table.WidthPercentage = 100; // add the cell with the date … List<Screening> screeings = PojoFactory.GetScreenings(conn, day); foreach (Screening screening in screeings) { table.AddCell(GetTable(conn, screening)); } return table; } private PdfPTable GetTable(IDbConnection conn, Screening screening) { // Create a table with 4 columns PdfPTable table = new PdfPTable(4); table.SetWidths(new int[] { 1, 5, 10, 10 }); // Get the movie Movie movie = screening.Movie; // A cell with the title as a nested table spanning the complete row PdfPCell cell = new PdfPCell(); // nesting is done with addElement() in this example cell.AddElement(FullTitle(screening)); cell.UseAscender = cell.UseDescender = true; … // cell with the list of countries … return table; } private PdfPTable FullTitle(Screening screening) { PdfPTable table = new PdfPTable(3); … return table; }
FullTitle方法創建的table是通過AddElement方法添加的,這個和通過AddCell方法以及PdfPCell構造器有一些區別。AddElement方法表格會被添加到單元格中的ColumnText對象中,而且我們還可以同個單元格中添加其他的內容。
總結
這一些主要是介紹PdfPCell的文本模式和組合模式,其架構和ColumnText一致。表格除了添加一些基本構建塊之外還可以添加圖片,我們還可以通過嵌套表格來完成一些復雜的頁面布局。不過這里的數據量比較少,下一節會介紹處理大數據量的內容,最后是代碼下載。
同步
此文章已同步到目錄索引:iText in Action 2nd 讀書筆記。