65.9K
CodeProject 正在变化。 阅读更多。
Home

XHtmlDraw - 使用 HTML 标签和锚链接绘制文本

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (32投票s)

2007年7月16日

CPOL

10分钟阅读

viewsIcon

153282

downloadIcon

1522

XHtmlDraw 允许您像使用 DrawText() 一样轻松地显示单行 HTML 文本,包括 Web 链接和 APP: 链接,无需 MFC。

引言

距离我介绍我的 XHtmlStatic 控件已经五年了,它在我工作的许多项目中都证明很有用。我无法使用 XHtmlStatic 的地方是 CWnd 基础控件不起作用的地方——例如,在树控件中作为一行文本。在这些情况下,您显然不希望创建(并重新定位!)许多窗口。因此,我创建了 XHtmlDraw,它可以在对话框或窗口的任何任意点渲染 HTML,而且不使用 MFC。

XHtmlDraw 功能

首先,让我向您展示演示应用程序。

screenshot

以下是主要功能:

  1. 查看文本 按钮会显示一个无模式对话框,其中显示完整的示例文本。

    screenshot

  2. 重置所有 按钮将所有内容(包括示例文本)重置为原始状态。
  3. 在更改示例文本后,可以使用 立即重绘 按钮。
  4. Web 链接App 链接 单选按钮允许您选择与示例文本中单词 nymphs 关联的链接类型。

    screenshot

    Web 链接会带您到 Wikipedia,而 App 链接会向演示应用程序发送消息,然后应用程序会显示一个消息框。

    screenshot

  5. 编辑框包含示例文本,您可以对其进行修改。App Link 1 示例文本如下所示:
    <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. Attributes 选项允许您应用各种字体效果和大小。
  7. Colors 选项允许您查看不同文本和背景颜色的效果。
  8. 第一行输出使用 MS Sans Serif(一种非 TrueType 字体)显示。
  9. 第二行输出使用 Comic Sans MS(一种无衬线 TrueType 字体)显示。
  10. 第三行输出使用 Times New Roman(一种有衬线 TrueType 字体)显示。

XHtmlDraw 功能:详细信息

XHtmlDraw 支持以下 HTML 标签:

Tag 语法 属性
A - Anchor <A>...</A> HREF="https://codeproject.org.cn"
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>

这些标签都是标准的 HTML,除了 FONT 的 BGCOLOR 属性和 A 的 app: 说明符。

使用 FONT 标签

在使用任何 FONT 属性时,请注意它们必须用引号括起来——例如,color="red"

COLOR 和 BGCOLOR

COLORBGCOLOR 属性都接受一个 color string value,这是一个三种形式之一的字符串:

  • "hex-value" - 示例:"#FF0000"。
  • "rgb-value" - 示例:"255,0,0"。
  • "color-name" - 示例:"red"。

    您可以使用任何标准的 命名 HTML 颜色,如右侧表格所示。 screenshot
    点击放大。

    您还可以使用常见的 Windows 系统颜色 的名称。 screenshot
    点击放大。

大小

SIZE 属性目前只接受相对大小调整——加或减。例如,SIZE="+4"SIZE="-2"

FACE

FACE 属性设置字体。目前只能指定一个字体名称。示例:FACE="Courier New"

使用超链接

Web 超链接

XHtmlDraw 支持标准的锚标签 <A>,包括 http://mailto:

APP: 超链接

XHtmlDraw 还支持 APP: 超链接,它允许您显示超链接文本,当用户单击它时,会向您的程序发送一个 Windows 消息。设置 APP: 超链接需要四个步骤:

  1. 定义 APP: 命令表 - 这是一个示例表。
    ///////////////////////////////////////////////////////////////////////////
    //
    // 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") },
    };
    此表有两项,但您可以根据需要添加任意多项。每项有四个元素:第一个是要接收消息的窗口的 HWND;第二个是通过 SendMessage() 发送到窗口的数字消息编号;第三个是用户定义的、将返回在 wParam 成员中的数据;第四个是用于将表项与 HTML 代码关联的字符串。
  2. 持久化表 - 使用 CXHtmlDrawLink 辅助类。
        m_Links.SetAppCommands(AppCommands, 
            sizeof(AppCommands)/sizeof(AppCommands[0]));

    m_Links 定义为:

        CXHtmlDrawLink m_Links;

    CXHtmlDrawLink::SetAppCommands() 函数会复制该表,因此调用者无需确保其持久性。

  3. 在 HTML 中插入链接 - 在 XHtmlDrawTestDlg.cpp 中,APP: 超链接的 HTML 代码如下:
        <a href="app:WM_APP_COMMAND1">nymphs</a>

    app: 后面的字符串 "WM_APP_COMMAND1" 是将超链接与步骤 1 的应用程序命令表关联起来的。请注意,此字符串可以是您想要的任何内容;为了提高可读性,您可以使用实际消息命令常量“字符串形式”。

  4. 添加代码以检测用户单击 - 由于 XHtmlDraw 不派生自 CWnd,因此您的代码需要检测用户对链接的单击。以下是演示应用程序 OnLButtonUp() 函数中的代码:
    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);
    }

    当用户在对话框中单击时,首先检查光标是否位于链接之上。如果是,则使用持久化对象 CXHtmlDrawLink::m_Links 来调用 GotoURL() 函数,该函数会打开一个网页或发送一个 APP: 消息,使用在步骤 1 中创建的 APP: 命令表

使用字符实体

XHtmlDraw 支持一个表驱动的字符实体查找。工作原理如下:

  1. 实体表在 XHtmlDraw.h 中定义为:
    static CHAR_ENTITIES    m_aCharEntities[];

    表中的每个条目定义为:

    struct CHAR_ENTITIES
    {
        TCHAR * pszName;    // string entered in HTML - e.g., " "
        TCHAR   cCode;      // code generated by XHtmlDraw
        TCHAR   cSymbol;    // character symbol displayed
    };
  2. 表在 XHtmlDraw.cpp 中指定:
    CXHtmlDraw::CHAR_ENTITIES CXHtmlDraw::m_aCharEntities[] = 
    {
        { _T("&"),    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
    };

    要添加条目,只需遵循与其他条目相同的格式即可。您可以使用 Microsoft 的 Character Map 工具 charmap.exe 来获取十六进制显示代码。

  3. 就这样!XHtmlDraw 现在将在每次看到实体名称时替换显示代码。
使用字符实体时的一个警告:某些字体,如 MS Sans Serif,可用的字形非常有限。因此,您应该验证它们是否正确显示,或者在显示它们之前选择另一种字体。

下表显示了字符实体将如何显示:

实体 显示符号 描述
& & ampersand
&bull; bullet (MS SANS SERIF 中不存在)
&cent; ¢ cent sign
&copy; © 版权
&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; nonbreaking 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 (MS SANS SERIF 中不存在)

当前限制

  1. 支持的 HTML 标签仅限于上述列表。
  2. 目前,更改同一行上的字体大小不会产生所需的结果。对于任何希望这样做的人来说,可以通过跟踪基线并调整绘图矩形来修复这个问题。
  3. 在解析 HTML 时做出了一些简化假设——例如,假设标签开头和 '<' 之间没有空格。另一个假设是序列 "每个 HTML 字符串只能有一个锚链接。

实现说明

在实现中有几个值得关注的点。其中一个是我第一次尝试 斜体字体。它们看起来是这样的:

screenshot

我很快意识到发生了什么:HTML 是分块编写的,每个块在遇到 HTML 标签时终止。因此,围绕 Deep 的标签,例如,导致它与后面的文本分开编写。这意味着后面文本的绘图矩形会裁剪 Deep 的绘图矩形,从而导致 p 被部分遮挡。在上面的屏幕截图中,红箭头指向发生裁剪的地方。显然,非 TrueType 字体比两种 TrueType 字体受到的影响更大,但您仍然可以看到字母被遮挡的地方。

如何解决这个问题?在阅读了字偶间距、上悬和下悬的资料后,我认为我找到了答案:我发现了 Alexandru Matei 关于斜体字的出色文章。在他的文章中,Matei 解释了如何通过查找写入的最后一个文本像素来考虑斜体字产生的上悬。当我尝试这种方法时,结果好坏参半,一些单词之间的间距太大。

我几乎放弃了解决方案,当时我尝试了一些简单的方法:透明文本。突然我意识到,当使用以下方法绘制文本时:

    SetBkMode(hMemDC, TRANSPARENT);

绘图矩形不再相互裁剪,一切看起来都无缝衔接。这太棒了!问题解决了!

也就是说,直到我开始考虑我的 XHtmlStatic 控件的一个常见增强请求——您猜对了,透明绘图。很快我就实现了这个选项,我看到了一个新的问题——使用透明选项时,绘图矩形不再被初始填充颜色,这意味着链接下划线一旦显示就不会被擦除。我尝试在演示应用程序的 CDialog 类中擦除绘图矩形,但这产生了大量的闪烁。

在将这个问题搁置一段时间后,我重新回来,几乎立即找到了解决方案。对于透明模式,首次绘制 HTML 字符串时,我会捕获绘图区域的背景,并将其保存在 DC 中。之后,我将使用保存的 DC 来初始化一个内存 DC,在那里我将执行所有绘图,最后将内存 DC BitBlt() 到绘图 DC。

这非常有效。现在,透明模式下的绘图与非透明模式一样可靠,并且 斜体字体 在两种模式下都能正常工作。

如何使用

要将 XHtmlDraw 集成到您自己的应用程序中,您首先需要将以下文件添加到您的项目中:

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

如果您想在 HTML 中使用链接,还必须包含:

  • XHtmlDrawLink.cpp
  • XHtmlDrawLink.h

如果您想使用链接,应该为 CXHtmlDraw::XHTMLDRAWSTRUCT 结构 创建一个类成员变量,以便保留锚信息。以下是 XHtmlDrawTestDlg.h 中的代码:

    CXHtmlDraw::XHTMLDRAWSTRUCT m_ds[3];

接下来,用所需的选项填充 CXHtmlDraw::XHTMLDRAWSTRUCT struct ——请参阅 XHtmlDrawTestDlg.cpp 中的 CXHtmlDrawTestDlg::InitDrawStruct() 作为示例。

最后,绘制 HTML:

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

请注意,没有理由使 CXHtmlDraw 对象持久化。

其他实现

致谢

我非常感谢 Christian RodemeyerDavid Hall 在封装颜色和颜色名称方面所做的出色工作。

参考文献

以下是我在本文中提到的链接。我还包括了指向 CodeProject 上我文章的链接,这些文章我在演示应用程序中使用过。

修订历史

版本 1.2 - 2007 年 11 月 6 日

  • 修复了格式错误的 HTML 可能导致无限循环的问题,由 bolivar123 报告。
  • 移除了不必要的 CRT 调用;性能提高了约 25%。

版本 1.1 - 2007 年 8 月 15 日

  • XHtmlTree 中使用的版本,未单独发布。

版本 1.0 - 2007 年 7 月 15 日

  • 首次公开发布

用法

此软件已进入公共领域。您可以以任何方式使用它,但不得销售此源代码。如果您修改或扩展它,请考虑将新代码发布在此处供大家共享。此软件按“原样”提供,不附带任何明示或暗示的保证。我对因使用此软件而导致的任何损害或业务损失概不负责。

© . All rights reserved.