Click here to Skip to main content
15,884,838 members
Articles / Programming Languages / C#
Article

Using MeasureCharacterRanges to Draw Text

Rate me:
Please Sign up or sign in to vote.
3.35/5 (10 votes)
10 Jun 20041 min read 96.7K   1.7K   27   5
How to use MeasureCharacterRanges to calculate the bounding rectangles of charaters in a string, to allow characters to be placed along curves.

Sample Image - TryApp.gif

Introduction

This article will demonstrate the use of MeasureCharacterRanges to draw a string a character at a time. Not very exciting, I hear you cry. Bear with me.

Background

I wrote this short article after seeing a question posted on a newsgroup about being able to draw text along a curve. While drawing a character at a time is easy, I wanted to make sure the character spacing was maintained.

The code

Just set up some text to work with:

C#
string measureString = "This is a test string.";
int    numChars         = measureString.Length;

Initialize the character ranges array, this is used to delimit the blocks of characters in the string, in this example, each character is a 'range'.

C#
//
// Set up the characted ranger array.

Now, initialize the StringFormatFlags, I'm using the NoClips flag to ensure that the character is not clipped when drawing.

C#
//
// Set up the string format
StringFormat stringFormat = new StringFormat();
stringFormat.FormatFlags = StringFormatFlags.NoClip;
stringFormat.SetMeasurableCharacterRanges(characterRanges);

Set up an array to hold the calculated regions:

C#
//
// Set up the array to accept the regions.
Region[] stringRegions = new Region[numChars];
for(int i = 0; i<numChars; i++)
    characterRanges[i] = new CharacterRange(i, 1);

Create a font, and use MeasureCharacterRanges() to calculate the regions for the character ranges.

The regions returned by MeasureCharacterRanges are converted to rectangles by using the GetBounds() function. The rectangle can then be manipulated using offset or any other method to adjust its placement.

In this example, I offset the Y position using a random amount to give wavy text.

C#
//
// The font to use.. 'using' will dispose of it for us
using (Font stringFont = new Font("Times New Roman", 16.0F))
{

    //
    // Get the max width.. for the complete length
    SizeF size = g.MeasureString(measureString, stringFont );

    //
    // Assume the string is in a stratight line, just to work out the 
    // regions. We will adjust the containing rectangles later.
    RectangleF layoutRect = 
        new RectangleF( 0.0f, 0.0f, size.Width, size.Height);

    //
    // Caluclate the regions for each character in the string.
    stringRegions = g.MeasureCharacterRanges(
        measureString,
        stringFont,
        layoutRect,
        stringFormat);

    //
    // Some random offsets, uncomment the DrawRectagle
    // if you want to see the bounding box.
    Random rand = new Random();
    for ( int indx = 0 ; indx < numChars; indx++ )
    {
        Region region = stringRegions[indx] as Region;
        RectangleF rect = region.GetBounds(g);
        rect.Offset( 0f, (float) rand.Next(100) / 10f);
        g.DrawString( measureString.Substring(indx,1), 
              stringFont, Brushes.Yellow, rect, stringFormat );
        // g.DrawRectangle( Pens.Red, Rectangle.Round( rect ));
    }
}

As I said at the beginning, this is not the most efficient code, the calculations could be done outside the drawing routine and cached. However, I hope this demonstrates how easy it is to determine the character positions.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
I've been a developer since 1980 (has it been that long)..

Assembler --> C --> C++ --> C#

A few diversions along the way into Forth, Digital Signal Processing and control systems.

I was born in Scotland, now working in New York City.

Comments and Discussions

 
QuestionWordWrap problem! Pin
Gokhan Erdogdu11-Jan-12 2:45
professionalGokhan Erdogdu11-Jan-12 2:45 
Hi,

I used the following code. But, I observed the problem! for "Character 32".
Does anyone have information about the problem?

C++
// MeasureCharacterRanges.h

//
// class CMeasureCharacterRanges
//
class CMeasureCharacterRanges
{
public:
	//
	// class CCharLayout
	//
	class CCharLayout
	{
	public:
		WCHAR			chr;
		Gdiplus::RectF	bound;
		CCharLayout(const WCHAR chracter)
		{
			chr = chracter;
		};

		CCharLayout& operator=(const CCharLayout& c)
		{
			if (&c != this) {
				chr		= c.chr;
				bound	= c.bound;
			}
			return( *this );
		};
	}; // class CCharLayout

	CMeasureCharacterRanges();
	~CMeasureCharacterRanges();

	void SetString(const Gdiplus::Graphics *graphics, 
		const WCHAR *string, 
		const Gdiplus::Font *font, 
		const Gdiplus::RectF &layoutRect, 
		const Gdiplus::StringFormat *stringFormat);
	int GetCharCount(void);
	CCharLayout* GetCharLayout(int index);
	void Clear(void);

private:
	std::vector< CCharLayout >		m_chars;
}; // class CMeasureCharacterRanges



// MeasureCharacterRanges.cpp

// ============================================================================== //
// class CMeasureCharacterRanges
// ============================================================================== //
CMeasureCharacterRanges::CMeasureCharacterRanges()
{
}

CMeasureCharacterRanges::~CMeasureCharacterRanges()
{
	Clear();
}

void CMeasureCharacterRanges::SetString(const Gdiplus::Graphics *graphics, 
										const WCHAR *string, 
										const Gdiplus::Font *font, 
										const Gdiplus::RectF &layoutRect, 
										const Gdiplus::StringFormat *stringFormat)
{
	Clear();
	INT length = 0;
	if ((NULL == string) || ((length = wcslen(string)) == 0)) {
		return;
	}

	INT pie, pos = 0, i, regionCount;
	INT remain = length;
	while (remain > 0) {
		pie = ((remain > 32) ? 32 : remain);
		
		Gdiplus::CharacterRange	*pcharRange = new Gdiplus::CharacterRange[pie];
		for (i = 0; i < pie; i++) {
			pcharRange[i] = Gdiplus::CharacterRange((pos + i), 1);
			m_chars.push_back(CCharLayout(string[pos + i]));
		}

		Gdiplus::StringFormat strFormat(stringFormat);
		strFormat.SetMeasurableCharacterRanges(pie, pcharRange);
		regionCount = strFormat.GetMeasurableCharacterRangeCount();
		Gdiplus::Region *pRegion = new Gdiplus::Region[regionCount];
		graphics->MeasureCharacterRanges(string, length, font, layoutRect, &strFormat, regionCount, pRegion);

		for (i = 0; i < regionCount; i++) {
			pRegion[i].GetBounds(&(m_chars[pos + i].bound), graphics);
			ATLTRACE(TEXT("index=%u, char=(%u)%c, x=%u, y=%u, w=%u, h=%u\n"), 
				pos + i,
				(INT)m_chars[pos + i].chr, m_chars[pos + i].chr,
				(INT)m_chars[pos + i].bound.X, 
				(INT)m_chars[pos + i].bound.Y, 
				(INT)m_chars[pos + i].bound.Width, 
				(INT)m_chars[pos + i].bound.Height);
		}

		SAFE_DELETE( pRegion );
		SAFE_DELETE( pcharRange );

		pos		+= pie;
		remain	-= pie;
	}
}

int CMeasureCharacterRanges::GetCharCount(void)
{
	return( m_chars.size() );
}

CMeasureCharacterRanges::CCharLayout* CMeasureCharacterRanges::GetCharLayout(int index)
{
	int count = m_chars.size();
	if ((count == 0) || (index < 0) || (index >= count)) {
		return( NULL );
	}
	return( &(m_chars[index]) );
}

void CMeasureCharacterRanges::Clear(void)
{
	m_chars.clear();
}


Output

SQL
Text = "GDI+" & vbTab & "text" & vbTab & "layout is resolution independent," & vbCrLf & "and thus different from GDI."

index=0, char=(71)G, x=3, y=0, w=12, h=19
index=1, char=(68)D, x=15, y=0, w=12, h=19
index=2, char=(73)I, x=27, y=0, w=5, h=19
index=3, char=(43)+, x=32, y=0, w=11, h=19
index=4, char=(9)vbTab, x=43, y=0, w=24, h=19
index=5, char=(116)t, x=67, y=0, w=6, h=19
index=6, char=(101)e, x=73, y=0, w=10, h=19
index=7, char=(120)x, x=83, y=0, w=10, h=19
index=8, char=(116)t, x=93, y=0, w=6, h=19
index=9, char=(9)vbTab, x=99, y=0, w=32, h=19
index=10, char=(108)l, x=131, y=0, w=5, h=19
index=11, char=(97)a, x=136, y=0, w=9, h=19
index=12, char=(121)y, x=145, y=0, w=9, h=19
index=13, char=(111)o, x=154, y=0, w=10, h=19
index=14, char=(117)u, x=164, y=0, w=9, h=19
index=15, char=(116)t, x=173, y=0, w=5, h=19
index=16, char=(32) , x=178, y=0, w=5, h=19
index=17, char=(105)i, x=183, y=0, w=5, h=19
index=18, char=(115)s, x=188, y=0, w=8, h=19
index=19, char=(32) , x=0, y=0, w=0, h=0 (on WordWrap)
index=20, char=(114)r, x=3, y=19, w=5, h=19
index=21, char=(101)e, x=8, y=19, w=9, h=19
index=22, char=(115)s, x=17, y=19, w=8, h=19
index=23, char=(111)o, x=25, y=19, w=10, h=19
index=24, char=(108)l, x=35, y=19, w=5, h=19
index=25, char=(117)u, x=40, y=19, w=9, h=19
index=26, char=(116)t, x=49, y=19, w=5, h=19
index=27, char=(105)i, x=54, y=19, w=5, h=19
index=28, char=(111)o, x=59, y=19, w=10, h=19
index=29, char=(110)n, x=69, y=19, w=9, h=19
index=30, char=(32) , x=78, y=19, w=5, h=19
index=31, char=(105)i, x=83, y=19, w=5, h=19
index=32, char=(110)n, x=88, y=19, w=9, h=19
index=33, char=(100)d, x=97, y=19, w=11, h=19
index=34, char=(101)e, x=108, y=19, w=9, h=19
index=35, char=(112)p, x=117, y=19, w=10, h=19
index=36, char=(101)e, x=127, y=19, w=9, h=19
index=37, char=(110)n, x=136, y=19, w=9, h=19
index=38, char=(100)d, x=145, y=19, w=11, h=19
index=39, char=(101)e, x=156, y=19, w=9, h=19
index=40, char=(110)n, x=165, y=19, w=9, h=19
index=41, char=(116)t, x=174, y=19, w=5, h=19
index=42, char=(44),, x=179, y=19, w=4, h=19
index=43, char=(13)vbCr, x=0, y=0, w=0, h=0
index=44, char=(10)vbLf, x=0, y=0, w=0, h=0 (on WordWrap)
index=45, char=(97)a, x=3, y=38, w=9, h=18
index=46, char=(110)n, x=12, y=38, w=9, h=18
index=47, char=(100)d, x=21, y=38, w=11, h=18
index=48, char=(32) , x=32, y=38, w=6, h=18
index=49, char=(116)t, x=38, y=38, w=5, h=18
index=50, char=(104)h, x=43, y=38, w=9, h=18
index=51, char=(117)u, x=52, y=38, w=9, h=18
index=52, char=(115)s, x=61, y=38, w=8, h=18
index=53, char=(32) , x=69, y=38, w=5, h=18
index=54, char=(100)d, x=74, y=38, w=11, h=18
index=55, char=(105)i, x=85, y=38, w=5, h=18
index=56, char=(102)f, x=90, y=38, w=5, h=18
index=57, char=(102)f, x=95, y=38, w=5, h=18
index=58, char=(101)e, x=100, y=38, w=9, h=18
index=59, char=(114)r, x=109, y=38, w=5, h=18
index=60, char=(101)e, x=114, y=38, w=9, h=18
index=61, char=(110)n, x=123, y=38, w=9, h=18
index=62, char=(116)t, x=132, y=38, w=5, h=18
index=63, char=(32) , x=137, y=38, w=5, h=18
index=64, char=(102)f, x=142, y=38, w=5, h=18
index=65, char=(114)r, x=147, y=38, w=5, h=18
index=66, char=(111)o, x=152, y=38, w=10, h=18
index=67, char=(109)m, x=162, y=38, w=13, h=18
index=68, char=(32) , x=0, y=0, w=0, h=0 (on WordWrap)
index=69, char=(71)G, x=3, y=57, w=12, h=18
index=70, char=(68)D, x=15, y=57, w=12, h=18
index=71, char=(73)I, x=27, y=57, w=5, h=18
index=72, char=(46)., x=32, y=57, w=4, h=18

GeneralFinding Character Positions Pin
MOHAMMADALARIQI2-Sep-09 2:54
MOHAMMADALARIQI2-Sep-09 2:54 
GeneralGDI+ Ressources Pin
maddin123421-Aug-04 11:20
maddin123421-Aug-04 11:20 
GeneralI found a way around the limitation :P Pin
FocusedWolf16-Aug-06 7:24
FocusedWolf16-Aug-06 7:24 
GeneralRe: I found a way around the limitation :P Pin
Mewes Kochheim26-Jul-11 13:50
Mewes Kochheim26-Jul-11 13:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.