Typesetting text with .NET
Asked Answered
W

2

8

I'm writing a WinForms .NET program that needs to lay some text out on a page (with a few basic geometric/vector graphics). Is there an equivalent of OS X's Core Graphics and/or Core Text? So far, I am just using a PrintDocument and using the Graphics object provided by the PrintPageEventArgs to draw text on the page, but there's very little control over things such as interword spacing, interline spacing etc. and a lot of stuff has to be done manually.

I feel like I'm missing something; is there a better way for typesetting text on a page? I don't mind using 3rd party solutions as long as they are free for personal use.

This will be used for typesetting a small variety of documents, including one-page brochures/fliers (where most text is variable but images are static), award certificates (where most text and images are static but some text is variable), timetables, etc.

Witt answered 3/7, 2013 at 13:25 Comment(0)
H
19

WinForms

When you use WinForms it is close to impossible to do proper type setting and yes, almost everything must be done manually. There are a couple of reasons for this:

  1. You don't get detailed glyph information
  2. The text measurement is horrible inaccurate, both the GDI+ and the GDI based version.

Windows Presentation Foundation

If you use WPF this is an entirely different matter as you get detailed geometrical information about each glyph.

However, you can interleave the two, a bit messy, but possible. Although, I do not recommend it as the graphics and bitmaps are not directly interchangeable which can result in slow performance.

If you want to look at the possibility you need to import the System.Windows.Media into your project to access the typeface and glyph capability of WPF.

You will meet a couple of other challenges as well:

  1. The font lists between WinForm and WPF are not identical (more font and font types with WPF).
  2. You cannot just use a WPF glyph and draw it to a WinForm bitmap.
  3. The values from the glyph are, as expected, in em so they need to be converted to pixels based on point size.

However, you can get this (not limited to) information:

enter image description here

All details (too many to list here):
http://msdn.microsoft.com/en-us/library/system.windows.media.glyphtypeface.aspx

Conclusion

But if you stick with GDI+ and WinForms the best approach you can take is probably to use the GDI based TextMetrics class.

You will in any case experience possibly padding issues, you cannot set char spacing and so forth.

You will have to calculate baseline for each typeface and size by using 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);

I do not know of any third-party library that can do this within the WinForm environment, I believe for the reasons mentioned here and the pain it would cause.

In conclusion I can only recommend you to take a look at Windows Presentation Foundation/WPF for a better ground to achieve proper type setting as you will get stuck in a lot of compromises using WinForms for this (I made a font viewer which is where I came across WPF as I wanted to show glyphs and detailed information about it including black-box and so forth - that was painful enough).

UPDATE:

WebBrowser as type-setting engine

A possible work-around of this is to use the WebBrowser control to do the setting. It's not an actual hack, but a bit hack-ish as it establish a initially unnecessary dependency on the IE browser on the user's computer.

However, this can turn out to be flexible when it comes to type setting as you have the same simple controls over text as with any HTML page.

enter image description here

To get a result you attach the HTML with styles and attributes for the text. You can even combine it with images and so forth (obviously).

The control can be for example on another hidden form not visible to the user.

After dropping in the WebControl on the form or creating it manually setting HTML is done in a single step:

    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>";

Next step is to grab what you render as HTML as an image which can be done as this:

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;
}

From: Saving images from a WebBrowser Control

Now you can place the bitmap on to your normal page. ..Or just use the control directly on the form with reduced interactive capability (right-click menu could easily become a problem if not).

Not knowing the exact usage that this is for, this may or may not be a suitable solution but it gives some powerful options and can be in place of a third-party solution.

Components

Looking high and low it seem that this area is not so much targeted. I could find one component that comes close but the pattern continues: one will be hitting a nail with a sledgehammer. It ends up in the same category as the WebBrowser work-around.

This component is really a full fledged editor but by disabling most of it it can perhaps be used to simply display formatted text/graphics with type setting sort of at hand:

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

Another possibility is to use a PDF component to set up the content and use a component to render the PDF as graphics (commercial):
http://www.dynamicpdf.com/Generate-PDF-.NET.aspx
http://www.dynamicpdf.com/Rasterizer-PDF-.NET.aspx

Non-commercial:
http://www.pdfsharp.net/?AspxAutoDetectCookieSupport=1

Using GhostScript to get image:
http://www.codeproject.com/Articles/32274/How-To-Convert-PDF-to-Image-Using-Ghostscript-API

The obvious static alternative

..and corky perhaps, but in any case I'll include it as simplicity sometimes works best -

If it is not a absolute requirement to be able to create these pages dynamically, generate all the text and graphics in Illustrator/Photoshop/inDesign etc. and save the result as images to be displayed.

Hydrolyte answered 10/7, 2013 at 0:27 Comment(7)
I don't know why but I had just assumed there would have been an equivalent of OS X's Core Graphics and Core Text. Or at the very least some sort of class that encapsulates a string and its attributes. For example, the Foundation and AppKit frameworks for OS X have a class called NSAttributedString which wraps around a regular NSString object allowing you to apply different attributes to different sections of a regular old string (font, size, colour, even kerning etc). You can then simply draw this attributed string onto a graphics context. Thanks for the info though, I will mull it over.Witt
@Witt I added a possible alternative solution to this.Hydrolyte
A web browser definitely takes away a lot of the effort, but yes, quite a bit of added overhead; I'll do a bit of benchmarking and see whether it is suitable.Witt
The WebBrowser solution could be avoided if one used one of the other web-renderers, e.g. WebKit.Vercelli
@Vercelli I was thinking of mentioning the web-kit integration (webkitdotnet.sourceforge.net) but considering the purpose circles around type setting/layout this might be an even bigger overkill than just using the built-in WebBrowser component (with all its flaws). But then again..Hydrolyte
I edited my question to give you an idea of the kinds of things I'm trying to generate (and print).Witt
There is definitely more than enough information here to point me in the right direction. +500 for you!Witt
N
0

In order to lay down text: there are several possibilities:

  1. A simple label, that lets you simple text manipulation (like font, placement, color, etc.)
  2. A rich text document that you may change the properties of the control to suite your needs, keep in mind that this one may present some simple images, etc.
  3. An image -> that you may create dynamically, with the graphics object, with the onpaint event. this is not recomended, as its a very low level approach, but as you know as low level as you get, the more power you gain.
  4. Combination of many controls, in a custom control you create, that you hold the information you want to draw / write in local members -> then keeping cont to when you change something, then set a flag (_needToBeRepainted = true;), and then on repainting, if(_needToBeRepainted) draw everything.

please let me know if you need further assistance solving this one.

Negron answered 9/7, 2013 at 12:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.