Sign in to follow this  

[.net] [C#] Drawing a string in different colors?

This topic is 3044 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I've been trying to draw a string in multiple colors for the past couple of hours now and I haven't had much luck. Basically, I want to take the existing functionality I have with Graphics.DrawString and add the ability to change the color sub-strings to be different than the brush color, i.e. change all instances of "foo" to red and "blah" to blue. That's pretty much it. I thought this would be simple enough. First render the string using the base color. Then use Graphics.MeasureCharacterRanges to find all the sub-string regions. Iterate the regions, and re-render all the sub-strings in their special colors. Since I'm not changing any font sizes, styles, layouts, etc., they would align perfectly with the full string and I'd be done. Well hours later and I'm still fiddling with all the parameters for DrawString and MeasureCharacterRanges (mainly string formats) trying to get the damn sub-strings to align. I've messed with every setting I could find, in all manner of combinations. Either the regions are off horizontally by a few pixels, or they're too small and clip/wrap the text, or they're completely off the bitmap altogether. And that's assuming that the sub-strings don't wrap! I haven't even attempted to code that case yet since I get even get the basics working. Has anyone ever attempted this before? Hours on Google and nothing I've seen has either been relevant or worked. For now I've hacked in some offsetting code because it's always off by 7 pixels to the right, but that's a horrible solution.

Share this post


Link to post
Share on other sites
I played around with some code and ran into the same issue and then found this article.

My guess is that the stringformat and textformatflag incompatibility is messing things up.

I haven't got the time to test it but de example code (download at the top of the article) might be of use to you.

Share this post


Link to post
Share on other sites
Oi, try using System.Windows.Forms.TextRenderer. It has DrawText and MeasureText functions that have been accurate for my purposes. IIRC, you may have to fiddle with the TextFormatFlags a wee bit, but TextFormatFlags.TextBoxControl should work nicely.

I'm thinking I've read somewhere that the reason MeasureString and DrawString don't match up is 'cause it uses GDI+, whereas TextRenderer uses GDI. Something about how GDI+ handles pixel/sub-pixel I'm guessing?

Hope this helps!

[Edit: Blergh. Didn't bother to read the article linked in the post above. [disturbed]]

[Edited by - nerd_boy on August 16, 2009 1:06:00 PM]

Share this post


Link to post
Share on other sites
Thanks for the replies, I wasn't even aware that the Forms namespace had text rendering. The problem with MeasureText though is that it measures text in "local space", so to speak. MeasureCharacterRanges allows me to pass in a full string with bounding rectangle, and then ask for a sub-rectangle bounding a particular range of characters (i.e. characters 23-29 in "global space"). I can then (ideally) just render that range of characters in a different color, using the returned sub-rectangle.

If I were to use MeasureText, I would need to split the string using the colored keywords as delimiters, and then render it in pieces. It could work, but it would take a lot more effort than what I have now. I would also have to implement word wrap functionality myself, for instance, but all things considered I believe the extra work is worth finding a robust solution.

Thanks again! I'll let you know how it goes [smile]

Share this post


Link to post
Share on other sites
Using anything other than MeasureCharacterRanges gave me hideously wrong results when I did this. You might try looking at the owner draw code in this code to see how I handle it. The results look like this:

Cleaned up version of dotTrace style visualizer

[Edited by - Promit on August 17, 2009 12:56:54 PM]

Share this post


Link to post
Share on other sites
Quote:
Since I'm not changing any font sizes, styles, layouts, etc., they would align perfectly with the full string and I'd be done.
You'd need to clear underneath the subranges first, as the antialiasing will cause problems otherwise.

Share this post


Link to post
Share on other sites
MeasureCharacterRanges and DrawString will not return the same results for substrings as for the whole string. Let me repeat: what you are trying to do will *not* work.

It's a known limitation of GDI+ text rendering. One potential solution is to use TextRenderer.MeasureText and TextRenderer.DrawText which rely on GDI instead. However, you will have to turn off text antialiasing for this to work, which is not advisable (edit: or clear the subranges, as benryves suggested - not always possible).

Solution: split your text into text runs (one run per [font, style, size, color] combination) and draw the runs sequentially.

Better solution: use a RichTextControl which offers this functionality out of the box.

Another solution: use a WebBrowser Control and format your text using HTML.

Share this post


Link to post
Share on other sites
Quote:
Original post by ernow
Promit, the link to your code is broken. DotTracestyle.cs with a capital 'S' ;)
o_O

I can't fix the link. Every time I change it, the S snaps back to lowercase. It happens without the link too:
http://slimtune.googlecode.com/svn/trunk/SlimTuneUI/DotTracestyle.cs
style.

Share this post


Link to post
Share on other sites
Quote:
Original post by Promit
Quote:
Original post by ernow
Promit, the link to your code is broken. DotTracestyle.cs with a capital 'S' ;)
o_O

I can't fix the link. Every time I change it, the S snaps back to lowercase. It happens without the link too:
http://slimtune.googlecode.com/svn/trunk/SlimTuneUI/DotTracestyle.cs
style.

You can get around it using HTML entities.

http://slimtune.googlecode.com/svn/trunk/SlimTuneUI/DotTraceStyle.cs =>
http://slimtune.googlecode.com/svn/trunk/SlimTuneUI/DotTraceStyle.cs

Share this post


Link to post
Share on other sites
Quote:
Original post by Fiddler
Solution: split your text into text runs (one run per [font, style, size, color] combination) and draw the runs sequentially.

The problem is that if the run word-wraps, it won't wrap with respect to the full bounding rectangle of the whole string, just the bounding rectangle for that individual run.

Quote:
Better solution: use a RichTextControl which offers this functionality out of the box.

I looked into this, but there were two problems: firstly it doesn't support drawing to a bitmap and I would have to use some pInvoke magic to get that to work, and secondly it doesn't support a transparent background so I'd have to subclass my own custom control and add that support.

Quote:
Another solution: use a WebBrowser Control and format your text using HTML.

I didn't think about this control, but it appears to have the same two limitations as RichTextControl.

At this point I'm just thinking of giving up and not supporting this feature. It's simply too much effort for something I thought would be simple.

Share this post


Link to post
Share on other sites
I ended up going with a RichTextBox that renders to a bitmap using a transparent background. Once I had the low-level code set up (which I was mostly able to grab from various forum posts on the web, such as this and this), it was dead simple to format the text. It may not be the cleanest solution, but it has the best functionality-to-effort ratio.

Share this post


Link to post
Share on other sites
Hey again,

I have one last issue related to the RichTextBox. Right now when I create the text box, the graphics object returned through CreateGraphics is at 96 DPI, presumably because the control assumes it will be rendered to the display. However I need the text to render at 400 DPI. I can manually scale up the font size by a factor of (400/96), but the font is still only anti-aliased at 96 DPI and looks jaggier than other text drawn using Graphics.DrawString at 400 DPI.

Is there a way I can "trick" the RichTextBox into thinking that it should draw at 400 DPI? I apologize for sounding like an idiot, but this is the first time in a while I've had to dive into the specifics of text rendering.

Share this post


Link to post
Share on other sites
RichTextBox is using GDI, while Graphics.DrawString is using GDI+ - their antialiasing algorithms are different and incompatible. To be more precise, plain GDI does not perform y-direction antialiasing, which results in the jagged text you are observing (it has nothing to do with DPI in this case).

It *might* be possible to force your RichTextBox to use GDI+ rendering by calling Application.UseCompatibleTextRendering(true) before you initialize your application, but I doubt this will affect the RichTextBox (as it is a wrapper of the unmanaged API with the same name).

Welcome to the wonderful world of Windows.Forms. :)

Edit: one solution is to avoid the render-to-bitmap step and send the RichTextBox directly to the printer. This *should* ensure correct text rendering.

Share this post


Link to post
Share on other sites
I tried changing the computability mode and it didn't have any effect, so you're likely correct in that it's just wrapping the unmanaged GDI version.

Unfortunately, I can't send the images directly to the printer because we're not the ones printing the images :) The tool I made generates the images at 400 DPI, but they're sent off to a printing company.

I'll have our artist look at the text, because it probably won't be more than 8pt at 96 DPI (scalled to 33.3pt for 400 DPI) and I personally don't think it looks horrible. Plus the major win of having rich text functionality is hard to pass up.

Share this post


Link to post
Share on other sites

This topic is 3044 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this