PdfSharp對中文字型支援及排版格式的調整
最近用PdfSharp生成pdf文件發現使用微軟雅黑畫中文字型顯示亂碼,查了官方文件說是不支援中日韓等語言(FAQ連結),但是嘗試更換字型為 “黑體” 後中文是可以正常顯示的,然後萌生出一個想法就是能不能根據輸入的文字來動態識別一個字型是否被支援,而用字型去判斷是否支援指定的文字是行不通的,因為單從字型來說,就微軟雅黑而言是支援中/英多種文字的。 通過閱讀原始碼,最終在CMapInfo的AddChars方法裡找到了解決方案,AddChars方法內部在新增字元的時候會呼叫OpenTypeDescriptor.CharCodeToGlyphIndex方法將unicode字元對映到相應glyph的索引上,如果對映的索引為0,則說明該字元使用的字型在pdfSharp是不支援的,那麼我們就可以通過遍歷對映結果字典CharacterToGlyphIndex,只要字典值裡有任何一個0值就說明提供的文字在該字型上有不被支援的字元,一旦被列印就會有亂碼出現,這時就需要換一種字型來顯示了。
1 //CMapInfo.AddChars方法原始碼 2 public void AddChars(string text) 3 { 4 if (text != null) 5 { 6 bool symbol = _descriptor.FontFace.cmap.symbol; 7 int length = text.Length; 8 for (int idx = 0; idx < length; idx++)9 { 10 char ch = text[idx]; 11 if (!CharacterToGlyphIndex.ContainsKey(ch)) 12 { 13 char ch2 = ch; 14 if (symbol) 15 { 16 // Remap ch for symbol fonts.17 ch2 = (char)(ch | (_descriptor.FontFace.os2.usFirstCharIndex & 0xFF00)); // @@@ refactor 18 } 19 int glyphIndex = _descriptor.CharCodeToGlyphIndex(ch2); //glyphIndex=0時說明該字元不被PdfSharp支援 20 CharacterToGlyphIndex.Add(ch, glyphIndex); 21 GlyphIndices[glyphIndex] = null; 22 MinChar = (char)Math.Min(MinChar, ch); 23 MaxChar = (char)Math.Max(MaxChar, ch); 24 } 25 } 26 } 27 }
然而CMapInfo類被設計成internal了,外部是訪問不到裡面方法的,然後就只能把原始碼down下來魔改一波了 ^_^ 。我的方案是在XFont上寫一個擴充套件方法,內部去構造CMapInfo,然後呼叫AddChars方法,擴充套件方法所在的類應該和PdfSharp在同一專案,程式碼如下:
1 /// <summary> 2 /// 該字型是否支援字串的繪製 3 /// </summary> 4 /// <param name="font"></param> 5 /// <param name="text"></param> 6 /// <returns></returns> 7 public static bool IsSupport(this XFont font, string text) 8 { 9 OpenTypeDescriptor descriptor = FontDescriptorCache.GetOrCreateDescriptorFor(font) as OpenTypeDescriptor; 10 if (descriptor != null) 11 { 12 var mapInfo = new CMapInfo(descriptor); 13 mapInfo.AddChars(text); 14 var maps = mapInfo.CharacterToGlyphIndex; //CharacterToGlyphIndex是AddChars方法對映結果字典 15 return !maps.Values.Any(x => x == 0); 16 } 17 return false; 18 }
IsSupport方法的使用:
public XFont BuildFont(string text, double size) { var fontFamilies = new string[] { "微軟雅黑", "黑體" }; //根據需要字型列表自己控制數量 XFont font = null; foreach (var name in fontFamilies) { font = new XFont(name, size, FontStyle.Regular); if (font.IsSupport(text)) return font; } return font; }
字型亂碼解決之後,又有遇到一個問題,XTextFormatter是pdfsharp內建實現字串自動換行功能的,對於英文文字自動換行是沒問題的,但是它的換行單元拆分是基於空格的,中文句子中間如果沒有空格就死翹翹了,整個文字就畫在一行了,等於啥都沒幹。修改思路是在原來基礎上加一個分支,判斷當前字串的寬度加上下一個字元的寬度,如果超過區域的寬度時,拆分一塊。
//..... 省略上面程式碼邏輯 else { //-------------------以下為新加入的程式碼邏輯-------------- if (startIndex + blockLength < _text.Length - 1) { string token = _text.Substring(startIndex, blockLength); var width = _gfx.MeasureString(token, _font).Width; var nextCharWidth = _gfx.MeasureString(_text[startIndex + blockLength + 1].ToString(), _font).Width; if (width + nextCharWidth >= LayoutRectangle.Width - 2) //加上下一個字元超過塊寬度時,強制拆分換行 { _blocks.Add(new Block(token, BlockType.Text, _gfx.MeasureString(token, _font).Width)); startIndex = idx + 1; blockLength = 0; continue; } } //-------------------以上 為新加入的程式碼邏輯------------------------ inNonWhiteSpace = true; blockLength++; } //..... 省略下文程式碼
和自動換行需求類似的功能是,在一個區域內列印一行文字,如果文字超出區域寬度時不是換行,而直接截斷後面的內容顯示省略號...代替,方法很簡單,也是基於XFont類的擴充套件方法,直接上程式碼:
1 /// <summary> 2 /// 獲取縮略資訊,文字寬度超過指定的寬度時顯示省略號... 3 /// </summary> 4 /// <param name="font"></param> 5 /// <param name="text"></param> 6 /// <param name="width"></param> 7 /// <returns></returns> 8 public static string GetOmitText(this XFont font, string text, double width) 9 { 10 var maxWidth = FontHelper.MeasureString(text, font, XStringFormats.Default); 11 if (maxWidth.Width <= width) return text; 12 var omitWidth = FontHelper.MeasureString("...", font, XStringFormats.Default); 13 14 int index = 1; 15 while (index < text.Length) 16 { 17 var part = text.Substring(0, index + 1); 18 var currentWidth = FontHelper.MeasureString(part, font, XStringFormats.Default); 19 var nextCharWidth = FontHelper.MeasureString(text.Substring(index + 1, 1), font, XStringFormats.Default); 20 if (currentWidth.Width + omitWidth.Width + nextCharWidth.Width > width) return part + "..."; 21 index++; 22 } 23 return text; 24 }
以上就是近來使用PdfSharp遇到的坑,後面遇到新的再補充。