VSTO中Word轉換Range為Image的方法 前言 "VSTO" 是一套用於創建自定義Office應用程式的Visual Studio工具包,通過Interop提供的增強Office對象,可以對Word文檔進行編程操作。 "Range" 是Word中執行操作的一個單元,可以理解成文檔中一個選中 ...
VSTO中Word轉換Range為Image的方法
前言
VSTO是一套用於創建自定義Office應用程式的Visual Studio工具包,通過Interop提供的增強Office對象,可以對Word文檔進行編程操作。Range是Word中執行操作的一個單元,可以理解成文檔中一個選中的部分或者區域,針對這個選中部分,可以應用格式、修改文字和顏色等功能。出於各種業務的需求,常常需要將Word文檔或者Word文檔的一部分變成圖片。本文對常見的Range轉換為Image的方法進行了討論和分析,並提出一些改良的地方。
剪貼板作為中介
原理很簡單,就是將Range.Copy()到剪貼板中,剪貼板是windows進程間通信的一種方式,然後從剪貼板中獲取metafile的信息還原成一個圖片。
//原理示意
//source為Word.Range對象
//range.Copy()會將range中的內容複製到剪貼板中。
source.Copy();
//從剪貼板中獲取metafile的數據
var metaData = GetMetaFileFromCicpBoard();
//根據metafile的信息構建出一個圖片對象
Image image = GetImageFromMetaData(metaData);
這種方法在網上很常見,效果和手動在Word文檔中複製一段文字,然後粘貼在<畫圖>軟體上一樣。然而在程式中,這個效果比較差。實際過程中,從剪貼板中獲取數據,經常會失敗,無法獲取數據,推測是其他進程操作了剪貼板,數據丟失了。而且解析剪貼板的數據,需要引入的庫已經很多年沒維護了,經常出異常。
通過EnhMetaFileBits生成圖片
這是最正常的方法了,直接通過API生成圖片,效果好,速度快,圖片的內容就是Word文檔中的顯示內容。
/// <summary>
/// 獲取原生的range變為圖片的方式
/// </summary>
/// <param name="range">選中部分</param>
/// <returns>圖片,Image對象</returns>
public static Image GetRangeRealImage(Word.Range range)
{
if (range == null)
{
throw new Exception("range is empty");
}
var bits = (byte[])range.EnhMetaFileBits;
System.IO.MemoryStream ms = new System.IO.MemoryStream(bits);
var ret = Image.FromStream(ms);
return ret;
}
在實際使用過程中,發現這種方法也存在缺陷。在不足一頁的range中,可以顯示全部內容;在多頁的range中,生成的圖片只能顯示第一頁的內容。
獲取多頁Range的圖片
為瞭解決這個問題,一個通常的思路是:既然可以獲取一頁的圖片,把多頁分成一頁一頁的,獲取每一頁的圖片,然後合併起來,就是多頁內容的圖片了。
//偽代碼
/// <summary>
/// 將range轉換為完全的image圖片
/// </summary>
/// <param name="range">選中部分</param>
/// <returns>圖片</returns>
public Image RangeToImage(Word.Range range)
{
var ret = RangeImage.GetRangeRealImage(range);
//如果圖片高度小於一頁,直接返回
var limitHeight = 2000;
if (ret.Height < limitHeight)
{
return ret;
}
//可能大於一頁
return GetBigRangePic(range);
}
從range中按頁切割其實很困難,然而頁是Word文檔中的一個單位。可以把range複製到一個新文檔B中,獲取B文檔的頁數,獲取B文檔的每一頁,然後就可以獲取每一頁的range部分了。
//偽代碼
/// <summary>
/// 一種生成range大圖的方法。word中range只能顯示一頁的區域,
/// range超過一頁,也只能顯示一頁。
/// 將range複製到新文檔中,拼接文檔中
/// 每一頁的圖片,可以生成range的全圖。
/// </summary>
/// <param name="source">需要生成圖片的range</param>
/// <returns>生成好的image對象</returns>
public Image GetBigRangePic(Word.Range source)
{
Image ret = null;
var wordDoc = newTmpDoc();
//將source的內容複製的新文檔中
addRangeToDoc(wordDoc, source);
//獲取新文檔的頁數
var num = wordDoc.ComputeStatistics(Word.WdStatistic.wdStatisticPages);
//獲取每一頁的圖片,併合並
for (int i= 1; i<=num; i++)
{
object page_num = i;
wordDoc.Application.Selection.GoTo(Word.WdGoToItem.wdGoToPage, Word.WdGoToDirection.wdGoToAbsolute, num, page_num);
var pageRange = wordDoc.Application.Selection.Bookmarks[@"\Page"].Range;
var pageImage = RangeImage.GetRangeRealImage(pageRange);
//第一頁等於圖片,第二頁以後合併前面的部分
if (ret == null)
{
ret = pageImage;
}else
{
ret = MergeImageVertical(ret, pageImage);
}
}
//手動GC
GC.Collect();
//保證線程安全
lock (objlock)
{
//關閉文檔
try
{
object saveChange = Word.WdSaveOptions.wdDoNotSaveChanges;
wordDoc.Close(ref saveChange, ref Nothing, ref Nothing);
}
catch (System.Runtime.InteropServices.COMException e)
{
Log.GetInstance().Error("close err:" + e.Message);
}
}
return ret;
}
這種方式成功獲取了多頁range的圖片。其缺點在於是合併多頁的圖片,多頁間的部分合併了看起來起來不自然;還有合併多頁的圖片,轉換為多個Bitmap,然後開始合併,cpu負載高,消耗的記憶體大。
range替換為image的shape
將特定部分的range直接替換成一個圖片,並刪除原內容。打個比方,就像是魔術表演中,魔術師把手中的帽子變成兔子一樣。
由於在Word中插入一個圖片,必須是本地文件系統中的圖片文件,range獲取到的圖片必須先存儲在本地的臨時目錄,然後再插入到Word的相應位置中。
//處理流程
Range->圖片->保存在本地,以png格式->插入到文檔中->刪除原先range的內容
//偽代碼
//range為Word.Range對象
//獲取圖片
var image = RangeImage.GetRangeRealImage(range);
//保存圖片到本地
var FileName = Config.GetInstance().Get("runtimePath") + System.IO.Path.DirectorySeparatorChar + "tmpTable.png";
image.Save(FileName, System.Drawing.Imaging.ImageFormat.Png);
//在原位置插入圖片
object SaveWithDocument = true;
object LinkToFile = false;
var wordDoc = range.Document;
object Anchor = wordDoc.Range(range.Start);
var t = wordDoc.Application.ActiveDocument.InlineShapes.AddPicture(FileName, ref LinkToFile, ref SaveWithDocument, ref Anchor);
//刪除原內容
range.Delete();