Creating outlined text!

Started by
13 comments, last by Darkmoon 22 years, 5 months ago
One more time...

CD3DFont in the SDK...
Author, "Real Time Rendering Tricks and Techniques in DirectX", "Focus on Curves and Surfaces", A third book on advanced lighting and materials
Advertisement
Bitmapped fonts would provide you with a solution but they do not scale very well on different resolutions. Also, creating lots of different typefaces would be a nightmare.

There is a method which would mean a lot more programming but would a) work with any resolution and indeed any size for your font and b) work with any style of font.

By using the Win32 function GetGlyphOutline you can actually get the meta information for drawing characters from a chosen font. For each character it will return a list of drawing primitives, line and curves that make up the shape of the character.

This is a mechanism I used when I first looked at the Macromedia Flash SDK, because it had no functionality for displaying text. Like I said it is more work but it does work.

It will also allow you to change colours easily, and you can then fill the text with a bitmap brush, solid colour, whatever you want.

If you want an example I can post it here, although it''s specifically for the Flash SDK but you could munge it for whatever purpose.
"Absorb what is useful, reject what is useless, and add what is specifically your own." - Lee Jun Fan
Sure, I''d like an example =)
I started putting the example code in but it was just turning into a nightmare because of the other SDK so heavily integrated. Plus the SDK used quadratic splines which made it easy, PolyBezier (the function we would use to draw on a device context) uses bezier curves.

What I''ll do is modify the code so that it works, then post it as a self contained function (hopefully).

In fact, in the meantime I have found an MSDN article that does half the work. Take a look at article #Q243285 - this will convert splines to beziers. This takes the output from the GetGlyphOutline function.

I''ll post the working code, once it works.
"Absorb what is useful, reject what is useless, and add what is specifically your own." - Lee Jun Fan
Goes a little something like this - after a bit of editing

  void OutlineText(HDC hdc, const char *font, const char *text, int PointSize, int xStart, int yStart){	HDC hdcScreen;	HFONT hFont;	HGDIOBJ hOldFont;	int nHeight;	TTPOLYGONHEADER *header;	TTPOLYGONHEADER *lastheader = NULL;	int nWidths[256];	// create a device context	hdcScreen = CreateCompatibleDC(hdc);	// set the mapping mode so that we can calculate the font height accurately	SetMapMode(hdcScreen, MM_TEXT);	nHeight = -MulDiv(PointSize, GetDeviceCaps(hdcScreen, LOGPIXELSY), 72);	// create a font with the requested height	hFont = CreateFont(nHeight, 0, 0, 0, FW_REGULAR, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, font);	// and select it into the device context	hOldFont = SelectObject(hdcScreen, hFont);	// now get the widths of the glyphs so that we can update the coordinates for the shapes when we add each letter	GetCharWidth32(hdcScreen, 0, 255, nWidths);	// move the starting point to the requested location	MoveToEx(hdc, xStart, yStart, NULL);	// process each character	for (const char *ch = text; *ch != 0; ch++) {		// Don't move the advance for the first character, but for every one after that.		if (ch - text > 0) {			xStart += nWidths[*(ch - 1)];		}		int c2 = *ch;		GLYPHMETRICS gm;		DWORD size;		MAT2 matrix;		FIXED f1;		f1.value = 1;		f1.fract = 0;		FIXED f0;		f0.value = 0;		f0.fract = 0;		matrix.eM11 = f1;		matrix.eM12 = f0;		matrix.eM21 = f0;		matrix.eM22 = f1;		char *buffer;		if (c2 != ' ') {			// get the size of the buffer needed for this character			if ((size = GetGlyphOutline(hdcScreen, c2, GGO_NATIVE, &gm, 0, NULL, &matrix)) != GDI_ERROR) {				buffer = new char[size+1];			}			// get the glyph for this character			if ((size = GetGlyphOutline(hdcScreen, c2, GGO_NATIVE, &gm, size, buffer, &matrix)) != GDI_ERROR) {				DrawT2Outline(hdc, (TTPOLYGONHEADER *)buffer, size, xStart, yStart);				delete[] buffer;			}		} // if (c2 != ' ')	}	// clean up	SelectObject(hdcScreen, hOldFont);	DeleteObject(hFont);	DeleteObject(hdcScreen);	return;}/**************************************************************************** *  FUNCTION   : IntFromFixed *  RETURNS    : int value approximating the FIXED value. ****************************************************************************/ int PASCAL NEAR IntFromFixed(FIXED f){    if (f.fract >= 0x8000)    return(f.value + 1);    else    return(f.value);}/**************************************************************************** *  FUNCTION   : fxDiv2 *  RETURNS    : (val1 + val2)/2 for FIXED values ****************************************************************************/ FIXED PASCAL NEAR fxDiv2(FIXED fxVal1, FIXED fxVal2){    long l;    l = (*((long far *)&(fxVal1)) + *((long far *)&(fxVal2)))/2;    return(*(FIXED *)&l);}/**************************************************************************** *  FUNCTION   : MakeBezierFromLine * *  PURPOSE    : Converts a line define by two points to a four point Bezier *               spline representation of the line in pPts. * * *  RETURNS    : number of Bezier points placed into the pPts POINT array. ****************************************************************************/ UINT MakeBezierFromLine( POINT *pPts, POINT startpt, POINT endpt ){    UINT cTotal = 0;    // starting point of Bezier    pPts[cTotal] = startpt;    cTotal++;    // 1rst Control, pt == endpoint makes Bezier a line    pPts[cTotal].x = endpt.x;    pPts[cTotal].y = endpt.y;    cTotal++;    // 2nd Control, pt == startpoint makes Bezier a line    pPts[cTotal].x = startpt.x;    pPts[cTotal].y = startpt.y;    cTotal++;    // ending point of Bezier    pPts[cTotal] = endpt;    cTotal++;        return cTotal;}/**************************************************************************** *  FUNCTION   : MakeBezierFromQBSpline * *  PURPOSE    : Converts a quadratic spline in pSline to a four point Bezier *               spline in pPts. * * *  RETURNS    : number of Bezier points placed into the pPts POINT array. ****************************************************************************/ UINT MakeBezierFromQBSpline( POINT *pPts, POINTFX *pSpline ){    POINT   P0,         // Quadratic on curve start point            P1,         // Quadratic control point            P2;         // Quadratic on curve end point    UINT    cTotal = 0;    // Convert the Quadratic points to integer    P0.x = IntFromFixed( pSpline[0].x );    P0.y = IntFromFixed( pSpline[0].y );    P1.x = IntFromFixed( pSpline[1].x );    P1.y = IntFromFixed( pSpline[1].y );    P2.x = IntFromFixed( pSpline[2].x );    P2.y = IntFromFixed( pSpline[2].y );    // conversion of a quadratic to a cubic    // Cubic P0 is the on curve start point    pPts[cTotal] = P0;    cTotal++;        // Cubic P1 in terms of Quadratic P0 and P1    pPts[cTotal].x = P0.x + 2*(P1.x - P0.x)/3;    pPts[cTotal].y = P0.y + 2*(P1.y - P0.y)/3;    cTotal++;    // Cubic P2 in terms of Qudartic P1 and P2    pPts[cTotal].x = P1.x + 1*(P2.x - P1.x)/3;    pPts[cTotal].y = P1.y + 1*(P2.y - P1.y)/3;    cTotal++;    // Cubic P3 is the on curve end point    pPts[cTotal] = P2;    cTotal++;    return cTotal;}/**************************************************************************** *  FUNCTION   : AppendPolyLineToBezier * *  PURPOSE    : Converts line segments into their Bezier point  *               representation and appends them to a list of Bezier points.  * *               WARNING - The array must have at least one valid *               start point prior to the address of the element passed. * *  RETURNS    : number of Bezier points added to the POINT array. ****************************************************************************/ UINT AppendPolyLineToBezier( LPPOINT pt, POINTFX start, LPTTPOLYCURVE lpCurve ){    int     i;    UINT    cTotal = 0;    POINT   endpt;    POINT   startpt;    POINT   bezier[4];    endpt.x = IntFromFixed(start.x);    endpt.y = IntFromFixed(start.y);    for (i = 0; i < lpCurve->cpfx; i++)    {        // define the line segment        startpt = endpt;        endpt.x = IntFromFixed(lpCurve->apfx.x);<br>        endpt.y = IntFromFixed(lpCurve-&gt;apfx.y);<br><br>        // convert a line to a bezier representation<br>        MakeBezierFromLine( bezier, startpt, endpt );<br><br>        // append the Bezier to the existing ones<br>                                    // Point 0 is Point 3 of previous.<br>        pt[cTotal++] = bezier[1];   // Point 1<br>        pt[cTotal++] = bezier[2];   // Point 2<br>        pt[cTotal++] = bezier[3];   // Point 3<br><br>    }<br><br>    return cTotal;<br>}<br><br><br>/****************************************************************************<br> *  FUNCTION   : AppendQuadBSplineToBezier<br> *<br> *  PURPOSE    : Converts Quadratic spline segments into their Bezier point <br> *               representation and appends them to a list of Bezier points. <br> *<br> *               WARNING - The array must have at least one valid<br> *               start point prior to the address of the element passed.<br> *<br> *  RETURNS    : number of Bezier points added to the POINT array.<br> ****************************************************************************/ <br>UINT AppendQuadBSplineToBezier( LPPOINT pt, POINTFX start, LPTTPOLYCURVE lpCurve )<br>{<br>    WORD                i;<br>    UINT                cTotal = 0;<br>    POINTFX             spline[3];  // a Quadratic is defined by 3 points<br>    POINT               bezier[4];  // a Cubic by 4<br><br>    // The initial A point is on the curve.<br>    spline[0] = start;<br><br>    for (i = 0; i &lt; lpCurve-&gt;cpfx;)<br>    {<br>        // The B point.<br>        spline[1] = lpCurve-&gt;apfx[i++];<br><br>        // Calculate the C point.<br>        if (i == (lpCurve-&gt;cpfx - 1))<br>        {<br>            // The last C point is described explicitly<br>            // i.e. it is on the curve.<br>            spline[2] = lpCurve-&gt;apfx[i++];<br>        }     <br>        else<br>        {<br>            // C is midpoint between B and next B point<br>            // because that is the on curve point of <br>            // a Quadratic B-Spline.<br>            spline[2].x = fxDiv2(<br>                lpCurve-&gt;apfx[i-1].x,<br>                lpCurve-&gt;apfx.x<br>                );<br>            spline[2].y = fxDiv2(<br>                lpCurve-&gt;apfx[i-1].y,<br>                lpCurve-&gt;apfx.y<br>                );<br>        }<br><br>        // convert the Q Spline to a Bezier<br>        MakeBezierFromQBSpline( bezier, spline );<br>        <br>        // append the Bezier to the existing ones<br>                                    // Point 0 is Point 3 of previous.<br>        pt[cTotal++] = bezier[1];   // Point 1<br>        pt[cTotal++] = bezier[2];   // Point 2<br>        pt[cTotal++] = bezier[3];   // Point 3<br><br>        // New A point for next slice of spline is the <br>        // on curve C point of this B-Spline<br>        spline[0] = spline[2];<br>    }<br><br>    return cTotal;<br>}<br><br>/****************************************************************************<br> *  FUNCTION   : CloseContour<br> *<br> *  PURPOSE    : Adds a bezier line to close the circuit defined in pt.<br> *<br> *<br> *  RETURNS    : number of points aded to the pt POINT array.<br> ****************************************************************************/ <br>UINT CloseContour( LPPOINT pt, UINT cTotal )<br>{<br>    POINT               endpt, <br>                        startpt;    // definition of a line<br>    POINT               bezier[4];<br><br>    // connect the first and last points by a line segment<br>    startpt = pt[cTotal-1];<br>    endpt = pt[0];<br><br>    // convert a line to a bezier representation<br>    MakeBezierFromLine( bezier, startpt, endpt );<br><br>    // append the Bezier to the existing ones<br>                                // Point 0 is Point 3 of previous.<br>    pt[cTotal++] = bezier[1];   // Point 1<br>    pt[cTotal++] = bezier[2];   // Point 2<br>    pt[cTotal++] = bezier[3];   // Point 3<br><br>    return 3;<br>}<br><br>/****************************************************************************<br> *  FUNCTION   : DrawT2Outline<br> *<br> *  PURPOSE    : Decode the GGO_NATIVE outline, create a sequence of Beziers<br> *               for each contour, draw with PolyBezier.  Color and relative <br> *               positioning provided by caller. The coordinates of hDC are<br> *               assumed to have MM_TEXT orientation.<br> *<br> *               The outline data is not scaled. To draw a glyph unhinted<br> *               the caller should create the font at its EMSquare size<br> *               and retrieve the outline data. Then setup a mapping mode<br> *               prior to calling this function.<br> *<br> *  RETURNS    : none.<br> ****************************************************************************/</font> <br><font color="blue">void</font> DrawT2Outline(HDC hDC, LPTTPOLYGONHEADER lpHeader, DWORD size, <font color="blue">int</font> x, <font color="blue">int</font> y) <br>{<br>    WORD                i;<br>    U<font color="blue">INT</font>                cTotal = 0; <font color="gray">// Total points in a contour.<br></font><br>    LPTTPOLYGONHEADER   lpStart;    <font color="gray">// the start of the buffer<br></font><br>    LPTTPOLYCURVE       lpCurve;    <font color="gray">// the current curve of a contour<br></font><br>    LPPO<font color="blue">INT</font>             pt;         <font color="gray">// the bezier buffer<br></font><br>    POINTFX             ptStart;    <font color="gray">// The starting point of a curve<br></font><br>    DWORD               dwMaxPts = size/<font color="blue">sizeof</font>(POINTFX); <font color="gray">// max possible pts.<br></font><br>    DWORD               dwBuffSize;<br><br>    dwBuffSize = dwMaxPts *     <font color="gray">// Maximum possible # of contour points.<br></font><br>                 <font color="blue">sizeof</font>(POINT) * <font color="gray">// sizeof buffer element<br></font><br>                 3;             <font color="gray">// Worst case multiplier of one additional point<br></font><br>                                <font color="gray">// of line expanding to three points of a bezier<br></font><br><br>   lpStart = lpHeader;<br>   pt = (LPPOINT)malloc( dwBuffSize );<br><br>    <font color="gray">// Loop until we have processed the entire buffer of contours.<br></font><br>    <font color="gray">// The buffer may contain one or more contours that begin with<br></font><br>    <font color="gray">// a TTPOLYGONHEADER. We have them all when we the end of the buffer.<br></font><br>    <font color="blue">while</font> ((DWORD)lpHeader &lt; (DWORD)(((LPSTR)lpStart) + size) && pt != NULL)<br>    {<br>        <font color="blue">if</font> (lpHeader-&gt;dwType == TT_POLYGON_TYPE)<br>        <font color="gray">// Draw each coutour, currently this is the only valid<br></font><br>        <font color="gray">// type of contour.<br></font><br>        {<br>            <font color="gray">// Convert the starting point. It is an on curve point.<br></font><br>            <font color="gray">// All other points are continuous from the "last" <br></font><br>            <font color="gray">// point of the contour. Thus the start point the next<br></font><br>            <font color="gray">// bezier is always pt[cTotal-1] - the last point of the <br></font><br>            <font color="gray">// previous bezier. See PolyBezier.<br></font><br>            cTotal = 1;<br>            pt[<font color="purple">0</font>].x = IntFromFixed(lpHeader-&gt;pfxStart.x);<br>            pt[<font color="purple">0</font>].y = IntFromFixed(lpHeader-&gt;pfxStart.y);<br><br>            <font color="gray">// Get to first curve of contour - <br></font><br>            <font color="gray">// it starts at the next byte beyond header<br></font><br>            lpCurve = (LPTTPOLYCURVE) (lpHeader + 1);<br><br>            <font color="gray">// Walk this contour and process each curve( or line ) segment <br></font><br>            <font color="gray">// and add it to the Beziers<br></font><br>            <font color="blue">while</font> ((DWORD)lpCurve &lt; (DWORD)(((LPSTR)lpHeader) + lpHeader-&gt;cb))<br>            {<br>                <font color="gray"><font color="gray">//**********************************************<br></font></font><br>                <font color="gray">// Format assumption:<br></font><br>                <font color="gray">//   The bytes immediately preceding a POLYCURVE<br></font><br>                <font color="gray">//   structure contain a valid POINTFX.<br></font><br>                <font color="gray"><font color="gray">// <br></font></font><br>                <font color="gray">//   If this is first curve, this points to the <br></font><br>                <font color="gray">//      pfxStart of the POLYGONHEADER.<br></font><br>                <font color="gray">//   Otherwise, this points to the last point of<br></font><br>                <font color="gray">//      the previous POLYCURVE.<br></font><br>                <font color="gray"><font color="gray">// <br></font></font><br>                <font color="gray">//   In either case, this is representative of the<br></font><br>                <font color="gray">//      previous curve's last point.<br></font><br>                <font color="gray"><font color="gray">//**********************************************<br></font></font><br><br>                ptStart = *(LPPOINTFX)((LPSTR)lpCurve - <font color="blue">sizeof</font>(POINTFX));<br>                <font color="blue">if</font> (lpCurve-&gt;wType == TT_PRIM_LINE)<br>                {<br>                    <font color="gray">// convert the line segments to Bezier segments<br></font><br>                    cTotal += AppendPolyLineToBezier( &pt[<font color="purple">cTotal</font>], ptStart, lpCurve );<br>                    i = lpCurve-&gt;cpfx;<br>                }<br>                <font color="blue">else</font> <font color="blue">if</font> (lpCurve-&gt;wType == TT_PRIM_QSPLINE)<br>                {<br>                    <font color="gray">// Decode each Quadratic B-Spline segment, convert to bezier,<br></font><br>                    <font color="gray">// and append to the Bezier segments<br></font><br>                    cTotal += AppendQuadBSplineToBezier( &pt[<font color="purple">cTotal</font>], ptStart, lpCurve );<br>                    i = lpCurve-&gt;cpfx;<br>                }<br>                <font color="blue">else</font><br>                    <font color="gray">// Oops! A POLYCURVE format we don't understand.<br></font><br>                    ; <font color="gray"><font color="gray">// error, error, error<br></font></font><br><br>            <font color="gray">// Move on to next curve in the contour.<br></font><br>            lpCurve = (LPTTPOLYCURVE)&(lpCurve-&gt;apfx[<font color="purple">i</font>]);<br>            }<br><br>            <font color="gray">// Add points to close the contour.<br></font><br>            <font color="gray">// All contours are implied closed by TrueType definition.<br></font><br>            <font color="gray">// Depending on the specific font and glyph being used, these<br></font><br>            <font color="gray">// may not always be needed.<br></font><br>            <font color="blue">if</font> ( pt[<font color="purple">cTotal-1</font>].x != pt[<font color="purple">0</font>].x || pt[<font color="purple">cTotal-1</font>].y != pt[<font color="purple">0</font>].y )<br>            {<br>                cTotal += CloseContour( pt, cTotal );<br>            }<br><br>            <font color="gray">// flip coordinates to get glyph right side up (Windows coordinates)<br></font><br>            <font color="gray">// TT native coordiantes are zero originate at lower-left.<br></font><br>            <font color="gray">// Windows MM_TEXT are zero originate at upper-left.<br></font><br>            <font color="blue">for</font> (i = 0; i &lt; cTotal; i++) {<br>                pt[<font color="purple">i</font>].y = 0 - pt[<font color="purple">i</font>].y;<br>				pt[<font color="purple">i</font>].x += x;<br>				pt[<font color="purple">i</font>].y += y;<br>			}<br><br>            <font color="gray">// Draw the contour<br></font><br>            PolyBezier( hDC, pt, cTotal );<br>        }<br>        <font color="blue">else</font><br>            <font color="gray">// Bad, bail, must have a bogus buffer.<br></font><br>            break; <font color="gray"><font color="gray">// error, error, error<br></font></font><br><br>        <font color="gray">// Move on to next Contour.<br></font><br>        <font color="gray">// Its header starts immediate after this contour<br></font><br>        lpHeader = (LPTTPOLYGONHEADER)(((LPSTR)lpHeader) + lpHeader-&gt;cb);<br>    }<br><br>    free( pt );<br>} <br>  </pre></font></td></tr></table></center><!–ENDSCRIPT–><br><br>Note: I have included the MSDN code too because I have modified bits of it. Hope this is OK Microsoft?<br><br>This is by no means perfect so please, no flames. I may even post a cleaner solution another time. But now it's past my bedtime so I'll bid you adieu! <br><br>Edited by - JY on November 13, 2001 7:20:41 PM<br><br>Edited by - JY on November 13, 2001 7:24:22 PM     <br><br>Edited by - JY on November 13, 2001 7:25:02 PM<br><br>Edited by - JY on November 13, 2001 7:26:30 PM    
"Absorb what is useful, reject what is useless, and add what is specifically your own." - Lee Jun Fan

This topic is closed to new replies.

Advertisement