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:
Here are the main features:
-
The View Text button displays a modeless dialog that
shows the complete sample text:
-
The Reset All button resets everything, including sample text,
to its original state.
-
The Redraw Now button can be used after you have made changes
to the sample text.
-
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.
The Web link takes you to Wikipedia,
and the App links send messages to the demo app, which then displays
a message box:
-
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.
-
The Attributes options allow you to apply various font effects and sizes.
-
The Colors options allow you to see the effect of different
text and background colors.
-
The first output line is displayed using MS Sans Serif, a non-TrueType font.
-
The second output line is displayed using Comic Sans MS, a TrueType
font without serifs.
-
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:
Tag |
Syntax |
Attributes |
A - Anchor |
<A>...</A> |
HREF="http://www.codeproject.com" HREF="mailto:hdietrich@gmail.com" HREF="app:MY_COMMAND_MESSAGE" |
BIG - Big Text |
<BIG>...</BIG> |
|
B - Bold Text |
<B>...</B> |
|
FONT - Font Change |
<FONT>...</FONT> |
COLOR="Color string" BGCOLOR="Color string" SIZE="Size adjustment" FACE="Font face name" |
I - Italic Text |
<I>...</I> |
|
SMALL - Small Text |
<SMALL>...</SMALL> |
|
STRIKE - Strike-through Text |
<STRIKE>...</STRIKE> |
|
SUB - Subscript Text |
<SUB>...</SUB> |
|
SUP - Superscript Text |
<SUP>...</SUP> |
|
U - Underlined Text |
<U>...</U> |
|
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:
|
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:
-
Define the APP: command table - here is a sample table:
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.
-
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.
-
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.
-
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:
- 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;
TCHAR cCode;
TCHAR cSymbol;
};
- The table is specified in XHtmlDraw.cpp:
CXHtmlDraw::CHAR_ENTITIES CXHtmlDraw::m_aCharEntities[] =
{
{ _T("&"), 0, _T('&') },
{ _T("•"), 0, _T('\x95') },
{ _T("¢"), 0, _T('\xA2') },
{ _T("©"), 0, _T('\xA9') },
{ _T("°"), 0, _T('\xB0') },
{ _T("€"), 0, _T('\x80') },
{ _T("½"), 0, _T('\xBD') },
{ _T("¼"), 0, _T('\xBC') },
{ _T(">"), 0, _T('>') },
{ _T("¿"), 0, _T('\xBF') },
{ _T("<"), 0, _T('<') },
{ _T("µ"), 0, _T('\xB5') },
{ _T("·"), 0, _T('\xB7') },
{ _T(" "), 0, _T(' ') },
{ _T("¶"), 0, _T('\xB6') },
{ _T("±"), 0, _T('\xB1') },
{ _T("£"), 0, _T('\xA3') },
{ _T("""), 0, _T('"') },
{ _T("®"), 0, _T('\xAE') },
{ _T("§"), 0, _T('\xA7') },
{ _T("¹"), 0, _T('\xB9') },
{ _T("²"), 0, _T('\xB2') },
{ _T("×"), 0, _T('\xD7') },
{ _T("™"), 0, _T('\x99') },
{ NULL, 0, 0 }
};
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.
- 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:
Entity |
Display Symbol |
Description |
& |
& |
ampersand |
• |
• |
bullet (not in MS SANS SERIF) |
¢ |
¢ |
cent sign |
© |
© |
copyright |
° |
° |
degree sign |
€ |
€ |
euro sign |
½ |
½ |
fraction one half |
¼ |
¼ |
fraction one quarter |
> |
> |
greater than |
¿ |
¿ |
inverted question mark |
< |
< |
less than |
µ |
µ |
micro sign |
· |
· |
middle dot = Georgian comma |
|
|
nonbreaking space |
¶ |
¶ |
pilcrow sign = paragraph sign |
± |
± |
plus-minus sign |
£ |
£ |
pound sign |
" |
" |
quotation mark = double quote |
® |
® |
registered trademark |
§ |
§ |
section sign |
¹ |
¹ |
superscript one |
² |
² |
superscript two |
× |
× |
multiplication sign |
™ |
™ |
trademark (not in MS SANS SERIF) |
Current Limitations
-
The supported HTML tags are limited to those listed above.
-
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.
-
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 "=".
-
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:
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
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.