觀念介紹
Excel並非像Word一樣在設計上原本就有浮水印功能,因為其設計初衷是為了做資料的存儲及運算而非一般要被打印出來處理的文件,所以不論使用哪種方式實作都只是仿造出”類浮水印”效果,這也是為甚麼在google上查詢關鍵字如「OpenXml Excel浮水印」、「OpenXml Excel add watermark」等等都查不太到教學的原因,這也使得要實作這個功能的過程異常艱難,因為除了先得要熟悉Excel的浮水印仿造機制外,還得要了解Excel架構的一些規則。
首先先來介紹一下Excel的兩種浮水印仿造機制,一種是添加到Excel圖片背景,另一種是添加到列印背景圖,兩種方式的差異如以下表格所示。
差異如下
圖片背景 | 列印背景 | |
示意圖 | ||
自動重複 | 是 | 否 |
閱覽Excel時呈現 | 是 | 否 |
列印時呈現 | 否 | 是 |
轉檔Pdf時呈現 | 否 | 是 |
Excel Sheet OuterXml基本觀念
Excel的每個Sheet(分頁)內其實都有自己的OuterXml(如下圖),
除了記載資料以外,也記載了設定之類的相關資訊,值得一提的是OuterXml的每個Tag是有順序性的,如果把Tag按照錯誤的順序擺放打開Excel時就會顯示內容損壞的提示(如下圖),
所以我們在透過OpenXml套件Append設定的時候也要遵循此規則,由於對於編譯器來說即使順序錯誤也不會報錯,所以撰寫上要謹記兩個大原則
- 在內部的節點要先append其內容,例:OddHeader要先被Append進HeaderFooter才能將HeaderFooter Append到Worksheet。
- 在Xml內較靠前的內容要先被Append,例:LegacyDrawingHeaderFooter要比HeaderFooter先輩Append到Worksheet。
步驟
加入成背景圖片
- WorksheetPart加入ImagePart
- ImagePart加入圖檔
- 加入一個與ImagePart同Id的Picture
- 將Picture加入Worksheet
加入成列印&轉檔背景圖片
- 建立一個vmlDrawingPart
- 設定好vmlDrawingPart(詳見程式碼)
- vmlDrawingPart加入ImagePart
- ImagePart加入圖檔
- 建立一個與好vmlDrawingPart同Id的LegacyDrawingHeaderFooter
- 建立一個HeaderFooter
- 建立一個OddHeader並設定Text = “&L&G”(L是指自訂頁首的左(L)區塊,G是指頁首自訂類型為圖片)
- 將OddHeader加入(Append)到HeaderFooter
- 將HeaderFooter加入(Append)到Worksheet
- 將的LegacyDrawingHeaderFooter加入(Append)到Worksheet
※要注意8~10是有順序性的,若順序不對會造成Excel文件損壞
程式碼範例
加入成背景圖片
此處的part=WorksheetPart
imagePart1 = part.AddNewPart<ImagePart>(“image/png”, “rId1”);
//rId可自行命名,不重複即可
FileStream imgstream = new FileStream(“你的圖片路徑“, FileMode.Open);
imagePart1.FeedData(imgstream);
imgstream.Close();
//取得浮水印圖片的FileStream儲存在在imagePart1內
string str = part.GetIdOfPart(imagePart1);
Picture picture1 = new Picture() { Id = str };
Worksheet worksheet1 = part.Worksheet;
worksheet1.Append(picture1);
//新增一個picture物件(Id與imagePart1相同)讓其對應,使得背景圖片物件(Picture)找得到圖片檔案(ImagePart),最後把picture物件append到我們的worksheet
加入成列印&轉檔背景圖片
此處的part=WorksheetPart
VmlDrawingPart vmlDrawingPart1 = part.AddNewPart<VmlDrawingPart>(“rId2”);
System.Xml.XmlTextWriter writer = new System.Xml.XmlTextWriter(vmlDrawingPart1.GetStream(System.IO.FileMode.Create), System.Text.Encoding.UTF8);
//新增一個VmlDrawingPart物件(設定固定)
string xmlStr = ”
<xml xmlns:v=\”urn:schemas-microsoft-com:vml\”\r\n xmlns:o=\”urn:schemas-microsoft-com:office:office\”\r\n
xmlns:x=\”urn:schemas-microsoft-com:office:excel\”>\r\n <o:shapelayout v:ext=\”edit\”>\r\n
<o:idmap v:ext=\”edit\” data=\”1\” />\r\n
</o:shapelayout>
<v:shapetype id=\”_x0000_t75\” coordsize=\”21600,21600\” o:spt=\”75\”\r\n o:preferrelative=\”t\”
path=\”m@4@5l@4@11@9@11@9@5xe\” filled=\”f\” stroked=\”f\”>\r\n
<v:stroke joinstyle=\”miter\” />\r\n <v:formulas>\r\n
<v:f eqn=\”if lineDrawn pixelLineWidth 0\” />\r\n
<v:f eqn=\”sum @0 1 0\” />\r\n
<v:f eqn=\”sum 0 0 @1\” />\r\n
<v:f eqn=\”prod @2 1 2\” />\r\n
<v:f eqn=\”prod @3 21600 pixelWidth\” />\r\n
<v:f eqn=\”prod @3 21600 pixelHeight\” />\r\n
<v:f eqn=\”sum @0 0 1\” />\r\n
<v:f eqn=\”prod @6 1 2\” />\r\n
<v:f eqn=\”prod @7 21600 pixelWidth\” />\r\n
<v:f eqn=\”sum @8 21600 0\” />\r\n
<v:f eqn=\”prod @7 21600 pixelHeight\” />\r\n
<v:f eqn=\”sum @10 21600 0\” />\r\n
</v:formulas>\r\n
<v:path o:extrusionok=\”f\” gradientshapeok=\”t\” o:connecttype=\”rect\” />\r\n
<o:lock v:ext=\”edit\” aspectratio=\”t\” />\r\n
</v:shapetype>
<v:shape id=\”LH\” o:spid=\”_x0000_s1025\” type=\”#_x0000_t75\”\r\n
style=\’;margin-left:0;margin-top:0;width:414pt;height:312pt;\r\n z-index:1\’>\r\n
<v:imagedata o:relid=\”rId1\” o:title=\”WOPI\” />\r\n
<o:lock v:ext=\”edit\” rotation=\”t\” />\r\n
</v:shape>
</xml>”
//基本上除了紅字的地方都固定就好,width跟height代表浮水印的長寬,relid則代表對應的ImagePart Id
writer.WriteRaw(“xmlStr”);
writer.Flush();
writer.Close();
ImagePart imageParthead = vmlDrawingPart1.AddNewPart<ImagePart>(“image/png”, “rId1”);
FileStream imgstream = new FileStream(“你的圖片路徑”, FileMode.Open);
imageParthead.FeedData(imgstream);
imgstream.Close();
//新增一個ImagePart
LegacyDrawingHeaderFooter dhf = new LegacyDrawingHeaderFooter { Id = “rId2” };
//新增一個LegacyDrawingHeaderFooter
HeaderFooter hf = new HeaderFooter();
//新增一個HeaderFooter
OddHeader odh = new OddHeader { Text = “&L&G” };
//新增一個OddHeader
hf.Append(odh);
//將OddHeader新增到HeaderFooter
part.Worksheet.Append(hf);
//將HeaderFooter新增到WorksheetPart
part.Worksheet.Append(dhf);
//將LegacyDrawingHeaderFooter新增到WorksheetPart
總結來說,Excel常用於編寫資料若非必要還是不建議加上此浮水印功能,除非資料具有機密性打印及檢視時使用此功能可防止偷拍或側錄,不然無論是使用哪種方式仿製的浮水印若使用者獲取檔案都可以輕易地將其移除,實務上作用其實相當雞肋。