使用.NET排版文本

8
我正在编写一个WinForms .NET程序,需要在页面上排版一些文本(包括一些基本的几何/矢量图形)。是否有类似于OS X的Core Graphics和/或Core Text的等效物?到目前为止,我只是使用PrintDocument并使用PrintPageEventArgs提供的Graphics对象在页面上绘制文本,但是对于诸如字间距、行间距等方面几乎没有控制,并且许多工作都必须手动完成。
我感觉自己缺少了什么;是否有更好的排版文本方式?只要个人免费,我不介意使用第三方解决方案。
这将用于排版各种小型文档,包括一页手册/传单(其中大部分文本是可变的,但图像是静态的),奖状证书(其中大部分文本和图像是静态的,但某些文本是可变的),时间表等。
2个回答

19

WinForms

使用 WinForms 很难做到良好的排版,几乎所有工作都必须手动完成。原因如下:

  1. 无法获取详细的字形信息。
  2. GDI+ 和基于 GDI 的版本都存在文本测量不准确的问题。

Windows Presentation Foundation

如果使用 WPF 就完全不同了,因为您可以获取每个字形的详细几何信息。

不过,你可以混用它们,虽然有些混乱,但是可以实现。但我不建议这样做,因为图形和位图不能直接互换,可能会导致性能缓慢。

如果想要尝试,请在项目中导入 System.Windows.Media 以访问 WPF 的字体和字形功能。

您还将遇到一些其他挑战:

  1. WinForm 和 WPF 之间的字体列表不相同(WPF 具有更多的字体和字体类型)。
  2. 您不能仅仅使用 WPF 字形并将其绘制到 WinForm 位图上。
  3. 字形中的值如预期地以 em 为单位,因此需要根据点大小将其转换为像素。

不过,您可以获得(不仅限于)以下信息:

enter image description here

所有细节(太多了,无法在这里列出):
http://msdn.microsoft.com/en-us/library/system.windows.media.glyphtypeface.aspx

结论

但如果坚持使用 GDI+ 和 WinForms,则最好的方法可能是使用基于 GDI 的 TextMetrics 类。

您在任何情况下都可能遇到填充问题,不能设置字符间距等等。

您将需要通过使用 top + ascent 计算每个字体和大小的基线:

FontFamily ff = myFont.FontFamily;

float lineSpace = ff.GetLineSpacing(myFont.Style);
float ascent = ff.GetCellAscent(myFont.Style);
float baseline =  myFont.GetHeight(ev.Graphics) * ascent / lineSpace;

PointF renderPt = new PointF(pt.X, pt.Y  - baseline));
ev.Graphics.DrawString("My baselined text", myFont, textBrush, renderPt);

我不知道在WinForm环境中有任何第三方库能够做到这一点,我认为这是因为在这里提到的原因和它可能引起的麻烦。

总之,我只能建议您查看Windows Presentation Foundation / WPF以获得更好的排版效果,因为使用WinForms将会陷入许多妥协(我制作了一个字体查看器,那是我发现WPF的地方,因为我想显示字形和详细信息,包括黑匣子等-那已经够痛苦的了)。

更新:

WebBrowser作为排版引擎

解决这个问题的一个可能方法是使用WebBrowser控件进行设置。这不是一个实际的黑客,但是有点像黑客,因为它在用户计算机上建立了最初不必要的IE浏览器依赖关系。

然而,当涉及到排版时,这可能是灵活的,因为您对文本具有与任何HTML页面相同的简单控制。

要获得结果,请附加带有文本样式和属性的HTML。您甚至可以结合图像等内容(显然)。

该控件可以放置在另一个对用户不可见的隐藏表单上。

在将WebControl放置在表单上或手动创建它之后,可以通过单个步骤完成设置HTML:

    WebBrowser1.DocumentText = "<span style='font-size:24px;'>Type-setting</span> <span style='font-family:sans-serif;font-size:18px;font-style:italic;'>using the browser component.</span>";

下一步是将您呈现为 HTML 的内容截取为图像,可以通过以下方式实现:

using mshtml;
using System.Drawing;
using System.Runtime.InteropServices;

[ComImport, InterfaceType((short)1), Guid("3050F669-98B5-11CF-BB82-00AA00BDCE0B")]
private interface IHTMLElementRenderFixed
{
    void DrawToDC(IntPtr hdc);
    void SetDocumentPrinter(string bstrPrinterName, IntPtr hdc);
}

public Bitmap GetImage(string id)
{
    HtmlElement e = webBrowser1.Document.GetElementById(id);
    IHTMLImgElement img = (IHTMLImgElement)e.DomElement;
    IHTMLElementRenderFixed render = (IHTMLElementRenderFixed)img;

    Bitmap bmp = new Bitmap(e.OffsetRectangle.Width, e.OffsetRectangle.Height);
    Graphics g = Graphics.FromImage(bmp);
    IntPtr hdc = g.GetHdc();
    render.DrawToDC(hdc);
    g.ReleaseHdc(hdc);

    return bmp;
}

从:Saving images from a WebBrowser Control

现在,您可以将位图放置在普通页面上。..或者直接在表单上使用控件,但交互能力会降低(如果不注意,右键菜单可能会成为问题)。

由于不知道这是用于什么目的,因此这可能是一个合适的解决方案,但它提供了一些强大的选项,可代替第三方解决方案。

组件

搜遍各处,似乎这个领域并没有受到那么多关注。我找到了一个接近的组件,但模式是相同的:用大锤子打钉子。它最终与WebBrowser解决方法处于同一类别。

这个组件确实是一个完整的编辑器,但通过禁用大部分功能,它也许可以用来显示格式化文本/图像,并且排版也算方便:

http://www.textcontrol.com/en_US/products/dotnet/overview/

另一种可能性是使用PDF组件设置内容,并使用组件将PDF呈现为图形(商业):
http://www.dynamicpdf.com/Generate-PDF-.NET.aspx
http://www.dynamicpdf.com/Rasterizer-PDF-.NET.aspx

非商业:
http://www.pdfsharp.net/?AspxAutoDetectCookieSupport=1

使用GhostScript获取图像:
http://www.codeproject.com/Articles/32274/How-To-Convert-PDF-to-Image-Using-Ghostscript-API

显而易见的静态替代方案

..或许有点老土,但无论如何,我将其包含在内,因为有时简单就是最好的选择 -

如果没有必要动态创建这些页面,则在Illustrator / Photoshop / inDesign等中生成所有文本和图形,并将结果保存为图像以供显示。


一个网络浏览器当然可以减少很多工作量,但是确实会增加相当大的开销。我将进行一些基准测试,看看是否适合使用。 - dreamlax
如果使用其他网络渲染器,例如WebKit,则可以避免使用WebBrowser解决方案。 - Moo-Juice
@Moo-Juice 我在考虑提到 web-kit 集成 (http://webkitdotnet.sourceforge.net/),但考虑到它的目的围绕着排版/布局,这可能比仅使用内置的 WebBrowser 组件(带有所有缺陷)更加过度。但另一方面... - user1693593
我编辑了我的问题,为你提供了我正在尝试生成(和打印)的内容类型的想法。 - dreamlax
2
这里绝对有足够的信息可以指引我朝着正确的方向前进。+500分给你! - dreamlax
显示剩余2条评论

0

为了排版文本,有几种可能性:

  1. 一个简单的标签,让您进行简单的文本操作(如字体、位置、颜色等)
  2. 一个富文本文档,您可以更改控件的属性以适应您的需求,但请记住,这可能会呈现一些简单的图像等。
  3. 一个图像 -> 您可以使用图形对象和onpaint事件动态创建,但不建议这样做,因为这是一种非常低级别的方法,但是您知道,您越低级别,获得的能力就越大。
  4. 在您创建的自定义控件中,将您想要绘制/编写的信息保存在本地成员中,然后保持对更改的控制,然后设置一个标志 (_needToBeRepainted = true;),然后在重新绘制时,如果(_needToBeRepainted)则绘制所有内容。

如果您需要进一步的帮助解决此问题,请告诉我。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接