Click here to Skip to main content
15,880,972 members
Articles / Multimedia / GDI
Article

XHtmlDraw - Draw text with HTML tags and anchor links

Rate me:
Please Sign up or sign in to vote.
4.93/5 (34 votes)
6 Nov 2007CPOL10 min read 150.2K   1.5K   100   16
XHtmlDraw allows you to display a single line HTML text as easily as using DrawText(), including web links and APP: links, no MFC

Introduction

It's been five years since I introduced my XHtmlStatic control, which has turned out to be useful in many projects I have worked on. The one place where I cannot use XHtmlStatic is where a CWnd-based control would not work - for example, as a line of text in a tree control. In these situations you obviously do not want to create (and reposition!) many windows. For this reason I have created XHtmlDraw, which renders HTML at any arbitrary point in a dialog or window, with no use of MFC.

XHtmlDraw Features

To begin with, let me show you the demo app:

screenshot

Here are the main features:

  1. The View Text button displays a modeless dialog that shows the complete sample text:

    screenshot

  2. The Reset All button resets everything, including sample text, to its original state.
  3. The Redraw Now button can be used after you have made changes to the sample text.
  4. The Web link and App link radio buttons allow you to choose the type of link associated with the word nymphs in the sample text.

    screenshot

    The Web link takes you to Wikipedia, and the App links send messages to the demo app, which then displays a message box:

    screenshot

  5. The edit box contains the sample text, which you can modify. The App link 1 sample text is shown below:
    <font size="+6"><b>Deep</b></font> in the <u>ancient</u>² <font color="ForestGreen">forest</font> the <font bgcolor="PaleGreen">wood</font> <a href="app:WM_APP_COMMAND1">nymphs</a> sleep in a lake of <font color="dodgerblue">blue</font> diamonds.

  6. The Attributes options allow you to apply various font effects and sizes.
  7. The Colors options allow you to see the effect of different text and background colors.
  8. The first output line is displayed using MS Sans Serif, a non-TrueType font.
  9. The second output line is displayed using Comic Sans MS, a TrueType font without serifs.
  10. The third output line is displayed using Times New Roman, a TrueType font with serifs.

XHtmlDraw Features: Details

The following HTML tags are supported by XHtmlDraw:

TagSyntaxAttributes
A - Anchor<A>...</A>HREF="http://www.codeproject.com"
HREF="mailto:hdietrich@gmail.com"
HREF="app:MY_COMMAND_MESSAGE"
BIG - Big Text<BIG>...</BIG>Image 5
B - Bold Text<B>...</B>Image 6
FONT - Font Change<FONT>...</FONT>COLOR="Color string"
BGCOLOR="Color string"
SIZE="Size adjustment"
FACE="Font face name"
I - Italic Text<I>...</I>Image 7
SMALL - Small Text<SMALL>...</SMALL>Image 8
STRIKE - Strike-through Text<STRIKE>...</STRIKE>Image 9
SUB - Subscript Text<SUB>...</SUB>Image 10
SUP - Superscript Text<SUP>...</SUP>Image 11
U - Underlined Text<U>...</U>Image 12

All of these tags are standard HTML, except the BGCOLOR attribute for FONT and the app: specifier for A.

Using the FONT Tag

When using any of the FONT attributes, note that they must be enclosed in quotes - for example, color="red".

COLOR and BGCOLOR

The COLOR and BGCOLOR attributes both take a color string value, which is a string in one of three forms:

  • "hex-value" - Example: "#FF0000".
  • "rgb-value" - Example: "255,0,0".
  • "color-name" - Example: "red".

    You can use any of the standard named HTML colors, as shown in the table to the right: screenshot
    Click to enlarge.

    You can also use names of the common Windows system colors: screenshot
    Click to enlarge.

SIZE

The SIZE attribute currently takes only relative size adjustments - plus or minus. For example, SIZE="+4" or SIZE="-2".

FACE

The FACE attribute sets the font face. Currently only one face name may be specified. Example: FACE="Courier New".

Using Hyperlinks

Web Hyperlinks

XHtmlDraw supports the standard anchor tag <A>, including http:// and mailto:.

APP: Hyperlinks

XHtmlDraw also supports the APP: hyperlink, which allows you to display hyperlinked text that sends a Windows message to your program when the user clicks on it. Setting up an APP: hyperlink involves four steps:

  1. Define the APP: command table - here is a sample table:
    ///////////////////////////////////////////////////////////////////////////
    //
    // define app command message used by <a href="app:WM_APP_COMMAND">
    //
    const UINT WM_APP_COMMAND_1 = WM_APP+100;
    const UINT WM_APP_COMMAND_2 = WM_APP+101;
    
    CXHtmlDraw::XHTMLDRAW_APP_COMMAND AppCommands[] = 
    {
        { m_hWnd, WM_APP_COMMAND_1, 123, _T("WM_APP_COMMAND1") },
        { m_hWnd, WM_APP_COMMAND_2, 456, _T("WM_APP_COMMAND2") },
    };
    This table has two entries, but you can add as many entries as you need. Each entry has four elements: the first is the HWND of the window that is to receive the message; the second is the numeric message number that will be sent to the window via SendMessage(); the third is user-defined data that is returned in the wParam member; and the fourth is a string that ties the table entry to the HTML code.
  2. Persist the table by using the CXHtmlDrawLink helper class:
    m_Links.SetAppCommands(AppCommands,
        sizeof(AppCommands)/sizeof(AppCommands[0]));
    

    m_Links is defined as:

    CXHtmlDrawLink m_Links;
    

    The CXHtmlDrawLink::SetAppCommands() function makes a copy of the table, so the caller need not ensure its persistence.

  3. Insert the link in the HTML - in XHtmlDrawTestDlg.cpp, the HTML for the APP: hyperlink is written as:
    <a href="app:WM_APP_COMMAND1">nymphs</a>
    

    The string "WM_APP_COMMAND1" that follows app: is what ties the hyperlink to the app command table of Step 1. Note that this string may be anything you wish; to improve readability, you may want to use the "string form" of the actual message command constant.

  4. Add code to detect a user click - since XHtmlDraw is not derived from CWnd, your code needs to detect a user click on a link. Here is the code from the demo app's OnLButtonUp() function.
    void CXHtmlDrawTestDlg::OnLButtonUp(UINT nFlags, CPoint point) 
    {
        for (int i = 0; i < 3; i++)
        {
            if (CXHtmlDraw::IsOverAnchor(m_hWnd, &m_ds[i]))
            {
                TRACE(_T("over anchor %d\n"), i);
    
                if (m_ds[i].pszAnchor)
                    m_Links.GotoURL(m_ds[i].pszAnchor, SW_SHOW, m_ds[i].nID);
                break;
            }
        }
    
        CDialog::OnLButtonUp(nFlags, point);
    }

    When the user clicks in the dialog, first a check is made to determine if the cursor is over a link. If so, the persistent object CXHtmlDrawLink::m_Links is used for the function GotoURL(), which opens a web page or sends an APP: message, using the APP: command table that was created in Step 1.

Using Character Entities

XHtmlDraw supports a table-driven character entity lookup. Here's how it works:

  1. The entity table is defined in XHtmlDraw.h as
    static CHAR_ENTITIES    m_aCharEntities[];

    Each of the table entries is defined as:

    struct CHAR_ENTITIES
    {
        TCHAR * pszName;    // string entered in HTML - e.g., " "
        TCHAR   cCode;      // code generated by XHtmlDraw
        TCHAR   cSymbol;    // character symbol displayed
    };
  2. The table is specified in XHtmlDraw.cpp:
    CXHtmlDraw::CHAR_ENTITIES CXHtmlDraw::m_aCharEntities[] = 
    {
        { _T("&amp;"),    0, _T('&') },     // ampersand
        { _T("&bull;"),   0, _T('\x95') },  // bullet      NOT IN MS SANS SERIF
        { _T("&cent;"),   0, _T('\xA2') },  // cent sign
        { _T("&copy;"),   0, _T('\xA9') },  // copyright
        { _T("&deg;"),    0, _T('\xB0') },  // degree sign
        { _T("&euro;"),   0, _T('\x80') },  // euro sign
        { _T("&frac12;"), 0, _T('\xBD') },  // fraction one half
        { _T("&frac14;"), 0, _T('\xBC') },  // fraction one quarter
        { _T("&gt;"),     0, _T('>') },     // greater than
        { _T("&iquest;"), 0, _T('\xBF') },  // inverted question mark
        { _T("&lt;"),     0, _T('<') },     // less than
        { _T("&micro;"),  0, _T('\xB5') },  // micro sign
        { _T("&middot;"), 0, _T('\xB7') },  // middle dot = Georgian comma
        { _T(" "),   0, _T(' ') },     // nonbreaking space
        { _T("&para;"),   0, _T('\xB6') },  // pilcrow sign = paragraph sign
        { _T("&plusmn;"), 0, _T('\xB1') },  // plus-minus sign
        { _T("&pound;"),  0, _T('\xA3') },  // pound sign
        { _T("&quot;"),   0, _T('"') },     // quotation mark
        { _T("&reg;"),    0, _T('\xAE') },  // registered trademark
        { _T("&sect;"),   0, _T('\xA7') },  // section sign
        { _T("&sup1;"),   0, _T('\xB9') },  // superscript one
        { _T("&sup2;"),   0, _T('\xB2') },  // superscript two
        { _T("&times;"),  0, _T('\xD7') },  // multiplication sign
        { _T("&trade;"),  0, _T('\x99') },  // trademark   NOT IN MS SANS SERIF
        { NULL,           0, 0 }            // MUST BE LAST
    };

    To add an entry, just follow the same format as for the other entries. You can use the Microsoft Character Map utility, charmap.exe, to obtain the hex display code.

  3. That's it! XHtmlDraw will now substitute the display code whenever it sees the entity name.
One caution when using character entities: some fonts, like MS Sans Serif, are very limited in the available glyphs. So you should verify that they display properly, or select another font before displaying them.

The following table shows how the character entities will be displayed:

EntityDisplay SymbolDescription
&amp;&ampersand
&bull;bullet (not in MS SANS SERIF)
&cent;¢cent sign
&copy;©copyright
&deg;°degree sign
&euro;euro sign
&frac12;½fraction one half
&frac14;¼fraction one quarter
&gt;>greater than
&iquest;¿inverted question mark
&lt;<less than
&micro;µmicro sign
&middot;·middle dot = Georgian comma
&nbsp;Image 15nonbreaking space
&para;pilcrow sign = paragraph sign
&plusmn;±plus-minus sign
&pound;£pound sign
&quot;"quotation mark = double quote
&reg;®registered trademark
&sect;§section sign
&sup1;¹superscript one
&sup2;²superscript two
&times;×multiplication sign
&trade;trademark (not in MS SANS SERIF)

Current Limitations

  1. The supported HTML tags are limited to those listed above.
  2. Currently changing the size of fonts on the same line does not produce the desired result. For anyone who wishes to do so, this can be fixed by keeping track of baseline and adjusting rect for drawing text.
  3. There are some simplifying assumptions made when parsing the HTML - for example, it is assumed there are no spaces between the < and the beginning of the tag. Another assumption is that the sequence "<a href=" will only have one space between the "a" and the "href", and that there will be no spaces between the "href" and the "=".
  4. There can be only one anchor link per HTML string.

Implementation Notes

There were several points of interest in the implementation. One of them was when I tried italic fonts for the first time. This is what they looked like:

screenshot

I quickly realized what was happening: the HTML is written in pieces, each piece terminating when a HTML tag is encountered. So the tags surrounding Deep, for example, was causing it to be written separately from the text that followed. This meant that the drawing rect of the text that followed was clipping the drawing rect for Deep, resulting in a partially obscured p. In the above screenshot, the red arrows point to places where clipping occurred. Obviously the non-Truetype font is more affected by this than the two Truetype fonts, but you can still see where letters have been obscured.

What to do about this? After reading up on kerning, overhangs, and underhangs, I thought I had the answer: I found Alexandru Matei's excellent article on italic fonts. In his article Matei explains how to allow for the overhang that italic text produces, by looking for the last text pixel that is written. When I tried this approach, the results were mediocre, with some inter-word spacing far too large.

I came close to giving up on a solution, when I tried something simple: transparent text. Suddenly I realized that when text was drawn using

SetBkMode(hMemDC, TRANSPARENT);

the drawing rects were no longer clipping each other, and it all appeared seamless. This was great! Problem solved!

That is, it was solved until I started thinking about a common enhancement request for my XHtmlStatic control - you guessed it, transparent drawing. Soon I had this option implemented, and I saw a new problem - with the transparent option, the drawing rect was no longer being initially filled with a color, which meant that the link underline was never being erased, once it had been displayed. I tried erasing the drawing rect in the CDialog class of the demo app, but this generated a lot of flickering.

After leaving this problem for a little while, I came back to it and found the solution almost immediately. For transparent mode, the first time the HTML string was drawn, I would capture the background of the drawing area, and save it in a DC. Subsequently, I would use the saved DC to initialize a memory DC, where I would do all the drawing, and finally BitBlt() the memory DC to the drawing DC.

This worked perfectly. Drawing in transparent mode is now as rock-solid as non-transparent, and italic fonts work properly in either mode.

How To Use

To integrate XHtmlDraw into your own app, you first need to add the following files to your project:

  • XHtmlDraw.cpp
  • XHtmlDraw.h
  • XNamedColors.cpp
  • XNamedColors.h
  • XString.cpp
  • XString.h

If you want to use links in your HTML, you must also include:

  • XHtmlDrawLink.cpp
  • XHtmlDrawLink.h

If you want to use links, you should create a class member variable for the CXHtmlDraw::XHTMLDRAWSTRUCT structs, so that the anchor information is retained. Here is the code from XHtmlDrawTestDlg.h:

CXHtmlDraw::XHTMLDRAWSTRUCT m_ds[3];

Next, fill in the CXHtmlDraw::XHTMLDRAWSTRUCT struct with the desired options - see CXHtmlDrawTestDlg::InitDrawStruct() in XHtmlDrawTestDlg.cpp for an example.

Finally, draw the HTML:

CXHtmlDraw htmldraw;
htmldraw.Draw(pDC->m_hDC, m_strText, &m_ds[index], FALSE);

Note that there is no reason to persist the CXHtmlDraw object.

Other Implementations

Acknowledgments

I am indebted to Christian Rodemeyer and David Hall for their excellent work on encapsulating colors and color names.

References

Here are the links I have mentioned in this article. I have also included links to my articles on CodeProject, which I have used in the demo app.

Revision History

Version 1.2 - 2007 November 6

  • Fixed problem where malformed HTML could cause infinite loop, reported by bolivar123.
  • Removed unnecessary calls to CRT; increased performance by about 25%.

Version 1.1 - 2007 August 15

  • Version used in XHtmlTree, not released separately.

Version 1.0 - 2007 July 15

  • Initial public release

Usage

This software is released into the public domain. You are free to use it in any way you like, except that you may not sell this source code. If you modify it or extend it, please to consider posting new code here for everyone to share. This software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software may cause.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Hans Dietrich Software
United States United States
I attended St. Michael's College of the University of Toronto, with the intention of becoming a priest. A friend in the University's Computer Science Department got me interested in programming, and I have been hooked ever since.

Recently, I have moved to Los Angeles where I am doing consulting and development work.

For consulting and custom software development, please see www.hdsoft.org.






Comments and Discussions

 
Generalonly in C++ Pin
Thanks for all the fish6-Nov-07 7:31
Thanks for all the fish6-Nov-07 7:31 
GeneralRe: only in C++ Pin
Martin Pollard6-Nov-07 8:42
Martin Pollard6-Nov-07 8:42 
GeneralRe: only in C++ Pin
Thanks for all the fish6-Nov-07 9:08
Thanks for all the fish6-Nov-07 9:08 
Yep, your Wow! is not the first one on this board Laugh | :laugh: , looks loke C++ is the most exciting place to be, where do I sign up, or all seats are taken by funny people.
GeneralInfinite loop Pin
bolivar1235-Nov-07 10:17
bolivar1235-Nov-07 10:17 
GeneralRe: Infinite loop Pin
Hans Dietrich5-Nov-07 12:24
mentorHans Dietrich5-Nov-07 12:24 
GeneralRe: Infinite loop Pin
Hans Dietrich6-Nov-07 6:23
mentorHans Dietrich6-Nov-07 6:23 
QuestionWord-wrap? Slider-bar? Pin
l_d_allan18-Aug-07 9:34
l_d_allan18-Aug-07 9:34 
QuestionWork with Pocket-PC? Pin
l_d_allan15-Aug-07 0:35
l_d_allan15-Aug-07 0:35 
GeneralHTML Viewer Pin
mo_kondori5-Aug-07 21:23
mo_kondori5-Aug-07 21:23 
GeneralCool Pin
ETA19-Jul-07 22:55
ETA19-Jul-07 22:55 
GeneralTruely Fantastic Pin
Ben Daniel16-Jul-07 18:24
Ben Daniel16-Jul-07 18:24 
Generalnice but... Pin
JimmyO16-Jul-07 14:23
JimmyO16-Jul-07 14:23 
GeneralBeautiful Pin
JoseMenendez16-Jul-07 3:37
JoseMenendez16-Jul-07 3:37 
GeneralRe: Beautiful Pin
mo_kondori16-Jul-07 7:03
mo_kondori16-Jul-07 7:03 
GeneralRe: Beautiful Pin
Wuzza6-Nov-07 21:29
Wuzza6-Nov-07 21:29 
GeneralWow! Pin
Uwe Keim15-Jul-07 21:24
sitebuilderUwe Keim15-Jul-07 21:24 

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.