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遇到的坑,后面遇到新的再补充。

  

posted @ 2020-10-12 19:31  mingjian_zhang  阅读(2969)  评论(3编辑  收藏  举报