有時,你需要在PDF中繪制不依賴於任何圖片文件的矢量圖形。iTextSharp既包含了繪制簡單矢量圖功能,也包含了繪制復雜矢量圖的功能。這篇文章將會幫助你入門。
在前面的文章所講述的內容中,直到現在為止,所有添加到PDF文檔的內容都只是依賴於將頁面中的內容加入到排版流中的簡單iText排版.簡單的iText排版還負責如果文字內容溢出當前頁面,則生成新的頁面。而對於處理矢量圖來說,就需要另一種方法了。那就是使用PdfContentByte()對象,這個對象的實例可以從PdfWriter對象的DirectContent屬性獲得.這也意味着不像前面那樣僅僅是使用PdfWriter.GetInstance方法了,我們還需要獲得PdfWrite類的實例:
string pdfpath = Server.MapPath("PDFs"); Document doc = new Document(); try { PdfWriter writer = PdfWriter.GetInstance(doc, new FileStream(pdfpath + "/Graphics.pdf", FileMode.Create)); doc.Open(); PdfContentByte cb = writer.DirectContent; ...
現在我們就可以使用PdfContentByte對象來畫矢量圖了:
cb.MoveTo(doc.PageSize.Width / 2, doc.PageSize.Height / 2); cb.LineTo(doc.PageSize.Width / 2, doc.PageSize.Height); cb.Stroke(); cb.MoveTo(0, doc.PageSize.Height/2); cb.LineTo(doc.PageSize.Width, doc.PageSize.Height / 2); cb.Stroke();
第一行代碼將光標移動到傳入的X,Y坐標軸參數,也就是文檔的正中間.下一行代碼從當前光標的位置畫一條線到傳入的坐標參數點。這里就是從文檔中間畫一條線到文檔上邊。當然,這個方法還沒有開始”畫”這條線,這個方法僅僅是記錄我們要畫這條線的意圖,直到調用了Stroke()方法后,這條線才能真正寫入PDF文檔。第二段代碼從文檔中間的最左邊畫到中間的最右邊,形成了一個上面2分的文檔大綱:
使用同樣的方法,我們可以在文檔左上的格子中加入一個正方形:
cb.MoveTo(100f, 700f); cb.LineTo(200f, 700f); cb.LineTo(200f, 600f); cb.LineTo(100f, 600f); cb.ClosePath(); cb.Stroke();
我們並沒有直接指定畫出正方形的最后一條邊,使用ClosePath()方法可以直接從當前點畫回到原點坐標.其實,我們還可以使用iTextSharp所提供的畫正方形的快捷方法,下面代碼展示如何利用這個快捷方法在右上方畫出正方形:
cb.Rectangle(doc.PageSize.Width-200f, 600f, 100f, 100f); cb.Stroke();
讓我們繼續在頁面添加四個正方形,每個正方形最后都使用了不同於Stroke()的方法來將其寫入文檔,如果你是用過Photoshop或是firework就應該知道,實際上Stroke()其實是對象的輪廓,而Fill實際上就是對象的填充色,在這里我使用CMYK顏色,將邊框顏色設置成藍綠色,而填充顏色設置為黃色:
cb.SetColorStroke(new CMYKColor(1f, 0f, 0f, 0f)); cb.SetColorFill(new CMYKColor(0f, 0f, 1f, 0f)); cb.MoveTo(70, 200); cb.LineTo(170, 200); cb.LineTo(170, 300); cb.LineTo(70, 300); //Path closed and stroked cb.ClosePathStroke(); cb.MoveTo(190, 200); cb.LineTo(290, 200); cb.LineTo(290, 300); cb.LineTo(190, 300); //Filled, but not stroked or closed cb.Fill(); cb.MoveTo(310, 200); cb.LineTo(410, 200); cb.LineTo(410, 300); cb.LineTo(310, 300); //Filled, stroked, but path not closed cb.FillStroke(); cb.MoveTo(430, 200); cb.LineTo(530, 200); cb.LineTo(530, 300); cb.LineTo(430, 300); //Path closed, stroked and filled cb.ClosePathFillStroke();
當你使用Rectangle對象而不是手動去畫矩形時,傳入的第一個和第二個參數是矩形右下角的坐標,而其他兩個參數分別為矩形的寬和高.iTextSharp中還包括其他矢量圖,比如說圓,圓中傳入的坐標則是圓心的坐標,第三個參數則為半徑的大小。上面的四個正方形中不難看出正方形的邊長為100像素,正方形的中心坐標則為120 x, 250 y,下面代碼可以在正方形中加入一個圓:
cb.SetCMYKColorStroke(255, 255, 0, 0); cb.SetCMYKColorFill(0, 255, 255, 0); cb.SetLineWidth(2f); cb.Circle(120f, 250f, 50f); cb.Fill();
我猜想你是不是在想我在代碼中用過的CMYK的四個參數分別代表什么。這並不直觀,CMYK分別代表四種顏色所占的百分比,比如暖紅色實際上是C:0%,M100%,Y100%,K100%.CMYK的構造函數需要的四個參數都為float類型,你可以傳入代表着百分比的數字.我還可以傳入0和1,其實傳入的參數可以是任何大小的float類型,百分比實際上是幾個參數的比例。所以如果我想設置成100%的藍色(藍綠),我可以使用3000f,0,0,0這樣的參數.注意這里不要不小心多打了一個0.
SetCMYKColorFill()方法接受一個int類型的參數.我覺得可能是因為這個方法是基於百分比來設置CMYK的吧,所以對於暖紅色的設置我傳入了0,100,100,0,但我得到的卻是一個粉色。我認為這是一個bug.是由於之前使用過一次SetCMYKColorFill()方法而影響到了這次的顏色,所以我重置了CMYK的顏色並再次實驗,還是不行。最后我發現CMYKColor對象是派生於ExtendedColor類,而所能接受的最大int值為255,這和RGB有些類似,如果你不知道這個類的工作原理,那你會很迷惑。所以我按照這個進行了設置,終於得到了我想要的顏色.為了進行驗證,我還傳入了大於255的數字,最后發現任何大於255的數字都會被iTextSharp截斷,比如256就等於1,510就等於255.至少,這是在我的測試中得到的結論.
讓我們回到正題,下面的第一個正方形中放入了一個紅色有着藍色邊框(2px)的圓:
接下來,讓我們來看看使用iTextSharp提供的預定義的其他形狀,橢圓。首先,定義一個長方形,然后將一個橢圓加入到長方形之內:
// x, y of bottom left corner, width, height cb.Rectangle(100f, 200f, 300f, 100f); cb.Stroke(); //Bottom left and top right coordinates cb.Ellipse(100f, 200f, 400f, 300f); cb.Stroke();
如果上面兩個圖形中第一個和第三個參數不同,第二個和第四個參數相同,最后結果會是一個圓。
接下來的代碼展示如何使用另一個預定義的圖形:圓角矩形.圓角矩形的構造函數分別為左下角的坐標,寬和高,以及圓角的度數。為了演示圓角矩形圓角的度數原理,我還在這個圓角矩形的左下角加入一個圓:
//Bottom left coordinates followed by width, height and radius of corners cb.RoundRectangle(100f, 500f, 200f, 200f, 20f); cb.Stroke(); cb.Circle(120f, 520f, 20f); cb.Stroke(); cb.MoveTo(118f, 520f); cb.LineTo(122f, 520f); cb.Stroke(); cb.MoveTo(120f, 518f); cb.LineTo(120f, 522f); cb.Stroke();
三角形就更容易畫了,只需要畫三條線就好,下面代碼展示了如何畫一個直角三角形,並標出直角:
cb.MoveTo(350f, 450f); cb.LineTo(350f, 600f); cb.LineTo(500f, 450f); cb.ClosePathStroke(); cb.MoveTo(350f, 460f); cb.LineTo(360f, 460f); cb.LineTo(360f, 450f); cb.Stroke();
曲線和弧
貝塞爾曲線是矢量圖中很重要的一種。這種曲線的畫法是基於數學公式而不是僅僅點到點。貝塞爾曲線的畫法是通過指定一個開始點,一個結束點,和兩個控制點。第一個控制點產生的效果是基於開始點,第二個控制點產生的效果是基於結束點。貝塞爾曲線從開始點朝向結束點,中間的彎曲是取決於控制點。但曲線本身會很接近控制點,但不會穿過控制點,如果你使用過PhotoShop或是Firework,控制點可以理解為你進行拖拽用來控制弧度的”控鍵(handles)”:
下面是一個畫曲線的例子,開始點設置為(200,10),結束點設置為(350,150):
//Start Point cb.MoveTo(200f, 10f); //Control Point 1, Control Point 2, End Point cb.CurveTo(150f, 30f, 450f, 70f, 350f, 150f); cb.Stroke();
這個曲線由開始點朝向第一個控制點,並向第二個控制點彎曲,在到達結束點之前,曲線如下:
上面的圖片或許並不是那么容易讓人理解,所以我加上了一些”控鍵(handles)”:
cb.MoveTo(200f, 10f); cb.LineTo(150f, 30f); cb.Stroke(); cb.MoveTo(450f, 70f); cb.LineTo(350f, 150f); cb.Stroke(); cb.Circle(450f, 70f, 1f); cb.Stroke(); cb.Circle(150f, 30f, 1f); cb.Stroke();
第一個控制點的“控鍵(handles)”相對比較短,所以曲線僅僅朝第一個控制點彎曲了一點,第二個控制點相對較長,所以曲線在其影響下朝着第二個控制點彎曲比較多,理解這個意思的最好辦法是將第二個控制點的“控鍵(handles)”再次延長:
cb.SetColorStroke(Color.GREEN); //start point (x,y) cb.MoveTo(200f, 10f); //control point 1 (x,y), control point 2 (x,y), end point (x,y) cb.CurveTo(150f, 30f, 550f, 100f, 350f, 150f); cb.Stroke(); cb.MoveTo(550f, 100f); cb.LineTo(350f, 150f); cb.Stroke(); cb.Circle(550f, 100f, 1f); cb.Stroke(); cb.SetColorStroke(Color.LIGHT_GRAY); //Bottom Left(x,y), Top Right(x,y), Start Angle, Extent cb.Arc(350f, 70f, 550f, 130f, 270f, 90f); cb.SetLineDash(3f, 3f); cb.Stroke(); cb.SetLineDash(0f); cb.MoveTo(550f, 100f); cb.LineTo(535f, 95f); cb.Stroke(); cb.MoveTo(550f, 100f); cb.LineTo(552f, 88f); cb.Stroke();
原來的曲線用黑色顯示.后加入的曲線用綠色顯示。現在你可以看到綠色的控制點已經展示出它的影響.從開始點開始,綠色曲線開始慢慢的低於黑色曲線,然后分開.因為第二個控制點離曲線的結束點比較遠,使得綠色的曲線更加的圓一些。也就是在到達終點之前朝向控制點偏離的更多一些。
對於上面展示的新添加的代碼,還有其他一些需要說明的地方。其中之一就是Arc對象,這里用於畫一個帶箭頭的從原來的控制點到新的控制點的曲線。弧是曲線的一部分.這里弧從左下角的(350,70)到右上角的(550,130).弧的度數默認是按逆時針算的。這里的弧度是270,相當於順時針90度.除此之外,SetLineDash()方法的用途是將所畫的弧線變為虛線。