📄 xhtmldraw.cpp
字号:
// XHtmlDraw.cpp Version 1.2 - article available at www.codeproject.com
//
// Author: Hans Dietrich
// hdietrich@gmail.com
//
// 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
// - Minor enhancements
// - Improved performance of GetPlainText() when no html in string
// - added copy and assignment ctors to XHTMLDRAWSTRUCT
//
// Version 1.0 - 2007 July 15
// - Initial public release
//
// License:
// 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.
//
// 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.
//
///////////////////////////////////////////////////////////////////////////////
// NOTE ABOUT PRECOMPILED HEADERS:
// This file does not need to be compiled with precompiled headers (.pch).
// To disable this, go to Project | Settings | C/C++ | Precompiled Headers
// and select "Not using precompiled headers". Be sure to do this for all
// build configurations.
//#include "stdafx.h"
#include <windows.h>
#include <tchar.h>
#include <crtdbg.h>
#include "XNamedColors.h"
#include "XString.h"
#include "XHtmlDraw.h"
#ifndef __noop
#if _MSC_VER < 1300
#define __noop ((void)0)
#endif
#endif
#undef TRACE
#define TRACE __noop
// if you want to see the TRACE output,
// uncomment this line:
//#include "XTrace.h"
#pragma warning(disable : 4127) // for _ASSERTE: conditional expression is constant
#pragma warning(disable : 4996) // disable bogus deprecation warning
///////////////////////////////////////////////////////////////////////////////
//
// HTML CHARACTER ENTITIES
//
// Some common character entities - note that these will be displayed correctly
// ONLY if they are in the currently selected font
//
CXHtmlDraw::CHAR_ENTITIES CXHtmlDraw::m_aCharEntities[] =
{
{ _T("&"), 0, _T('&') }, // ampersand
{ _T("•"), 0, _T('\x95') }, // bullet NOT IN MS SANS SERIF
{ _T("¢"), 0, _T('\xA2') }, // cent sign
{ _T("©"), 0, _T('\xA9') }, // copyright
{ _T("°"), 0, _T('\xB0') }, // degree sign
{ _T("€"), 0, _T('\x80') }, // euro sign
{ _T("½"), 0, _T('\xBD') }, // fraction one half
{ _T("¼"), 0, _T('\xBC') }, // fraction one quarter
{ _T(">"), 0, _T('>') }, // greater than
{ _T("¿"), 0, _T('\xBF') }, // inverted question mark
{ _T("<"), 0, _T('<') }, // less than
{ _T("µ"), 0, _T('\xB5') }, // micro sign
{ _T("·"), 0, _T('\xB7') }, // middle dot = Georgian comma
{ _T(" "), 0, _T(' ') }, // nonbreaking space
{ _T("¶"), 0, _T('\xB6') }, // pilcrow sign = paragraph sign
{ _T("±"), 0, _T('\xB1') }, // plus-minus sign
{ _T("£"), 0, _T('\xA3') }, // pound sign
{ _T("""), 0, _T('"') }, // quotation mark
{ _T("®"), 0, _T('\xAE') }, // registered trademark
{ _T("§"), 0, _T('\xA7') }, // section sign
{ _T("¹"), 0, _T('\xB9') }, // superscript one
{ _T("²"), 0, _T('\xB2') }, // superscript two
{ _T("×"), 0, _T('\xD7') }, // multiplication sign
{ _T("™"), 0, _T('\x99') }, // trademark NOT IN MS SANS SERIF
{ NULL, 0, 0 } // MUST BE LAST
};
///////////////////////////////////////////////////////////////////////////////
// ctor
CXHtmlDraw::CXHtmlDraw(UINT nMaxText /*= XHTMLDRAW_MAX_TEXT*/)
: m_nMaxText(nMaxText),
m_bOverAnchor(FALSE)
{
InitCharEntities();
}
///////////////////////////////////////////////////////////////////////////////
// dtor
CXHtmlDraw::~CXHtmlDraw()
{
}
///////////////////////////////////////////////////////////////////////////////
// Draw
int CXHtmlDraw::Draw(HDC hDC,
LPCTSTR lpszText,
XHTMLDRAWSTRUCT * pXHDS,
BOOL bUnderlineUrl)
{
TRACE(_T("in CXHtmlDraw::Draw: <%s> bUnderlineUrl=%d\n"), lpszText, bUnderlineUrl);
static BOOL bInDraw = FALSE;
if (bInDraw)
return 0;
bInDraw = TRUE;
// check parameters ----------------------------------------------
_ASSERTE(hDC);
_ASSERTE(lpszText);
_ASSERTE(pXHDS);
if (!hDC || !lpszText || (lpszText[0] == _T('\0')) || !pXHDS)
{
bInDraw = FALSE;
return 0;
}
HWND hWnd = ::WindowFromDC(hDC);
if (!::IsWindow(hWnd))
{
TRACE(_T("warn: not a window\n"));
bInDraw = FALSE;
return 0;
}
// check if window is hidden ------------------------------------
if (!::IsWindowVisible(hWnd))
{
TRACE(_T("warn: window invisible\n"));
bInDraw = FALSE;
return 0;
}
RECT rectClip;
int nResult = ::GetClipBox(hDC, &rectClip);
if (nResult == NULLREGION)
{
// window is covered
TRACE(_T("warn: window is covered\n"));
bInDraw = FALSE;
return 0;
}
// initialize for drawing ---------------------------------------
// rectText is used to draw into the dc
RECT rectText = pXHDS->rect;
if ((rectText.left >= rectText.right) ||
(rectText.top >= rectText.bottom))
{
TRACE(_T("warn: bad rect\n"));
bInDraw = FALSE;
return 0;
}
// rectDraw is the rect for the entire drawing area
RECT rectDraw = pXHDS->rect;
int nRectWidth = rectDraw.right - rectDraw.left;
int nRectHeight = rectDraw.bottom - rectDraw.top;
int nXOffset = rectDraw.left;
// set up for double buffering
HDC hMemDC = CreateCompatibleDC(hDC);
HBITMAP hBitmap = CreateCompatibleBitmap(hDC, nRectWidth, nRectHeight);
HBITMAP hOldBitmap = (HBITMAP) SelectObject(hMemDC, hBitmap);
if (pXHDS->bTransparent && !pXHDS->hDC)
{
// save a bitmap of the original drawing area, in case
// there are links, and we need to erase the underline
pXHDS->hDC = CreateCompatibleDC(hDC);
pXHDS->hBitmap = CreateCompatibleBitmap(hDC, nRectWidth, nRectHeight);
pXHDS->hOldBitmap = (HBITMAP) SelectObject(pXHDS->hDC, pXHDS->hBitmap);
BitBlt(pXHDS->hDC, 0, 0, nRectWidth, nRectHeight,
hDC, rectDraw.left, rectDraw.top, SRCCOPY);
BitBlt(hMemDC, 0, 0, nRectWidth, nRectHeight,
hDC, rectDraw.left, rectDraw.top, SRCCOPY);
}
else if (pXHDS->bTransparent && pXHDS->hDC)
{
// restore the original drawing area from saved HDC
BitBlt(hMemDC, 0, 0, nRectWidth, nRectHeight,
pXHDS->hDC, 0, 0, SRCCOPY);
}
pXHDS->rectAnchor = rectText; // save rect in case of anchor
// remap rectText to memory dc - left and top start at 0
rectText.left = 0;
rectText.top = 0;
rectText.right = nRectWidth;
rectText.bottom = nRectHeight;
// create initial font ------------------------------------------
LOGFONT lf = { 0 };
LOGFONT prev_lf = { 0 };
if (pXHDS->bLogFont)
{
TRACE(_T("using logfont\n"));
memcpy(&lf, &pXHDS->lf, sizeof(LOGFONT));
}
else
{
HFONT hfont = (HFONT)::GetCurrentObject(hDC, OBJ_FONT); //+++1.1
if (hfont)
GetObject(hfont, sizeof(LOGFONT), &lf);
else
GetObject(GetStockObject(SYSTEM_FONT), sizeof(LOGFONT), &lf);
}
memcpy(&prev_lf, &lf, sizeof(LOGFONT));
// variable initialization --------------------------------------
TCHAR *pszText = new TCHAR [m_nMaxText+1];
memset(pszText, 0, (m_nMaxText+1)*sizeof(TCHAR));
_tcsncpy(pszText, lpszText, m_nMaxText);
TCHAR *pTextBuffer = pszText; // save buffer address for delete
TCHAR *pszText1 = new TCHAR [m_nMaxText+1];
memset(pszText1, 0, (m_nMaxText+1)*sizeof(TCHAR));
if (pXHDS->pszAnchor)
delete [] pXHDS->pszAnchor;
pXHDS->pszAnchor = NULL;
pXHDS->bHasAnchor = FALSE;
pXHDS->bAnchorIsUnderlined = FALSE;
BOOL bInAnchor = FALSE;
int n = (int) _tcslen(pszText); // n must be int
int i = 0;
int nWidth = 0;
pXHDS->bHasAnchor = FALSE;
pXHDS->nRightX = 0;
COLORREF crText = pXHDS->crText;
if (crText == COLOR_NONE)
crText = GetSysColor(COLOR_WINDOWTEXT);
COLORREF crBackground = pXHDS->crBackground;
if (crBackground == COLOR_NONE)
crBackground = GetSysColor(COLOR_WINDOW);
COLORREF crTextNew = crText;
COLORREF crBkgndNew = crBackground;
// if no transparency, fill entire rect with default bg color
if (!pXHDS->bTransparent)
{
HBRUSH hbrush = CreateSolidBrush(crBkgndNew);
_ASSERTE(hbrush);
FillRect(hMemDC, &rectText, hbrush);
if (hbrush)
DeleteObject(hbrush);
}
BOOL bBold = pXHDS->bBold;
BOOL bItalic = pXHDS->bItalic;
BOOL bUnderline = pXHDS->bUnderline;
BOOL bStrikeThrough = pXHDS->bStrikeThrough;
BOOL bSubscript = FALSE;
BOOL bSuperscript = FALSE;
int nSizeChange = 0;
// replace character entity names in text with codes ------------
TCHAR ent[3] = { _T('\0') };
ent[0] = _T('\001'); // each entity name is replaced with a two-character
// code that begins with \001
BOOL bCharacterEntities = FALSE; // assume no char entities
// we are replacing character entites with a two-character sequence,
// so the resulting string will be shorter
size_t buflen = _tcslen(pszText) + 100;
TCHAR *buf = new TCHAR [buflen];
memset(buf, 0, buflen*sizeof(TCHAR));
for (i = 0; m_aCharEntities[i].pszName != NULL; i++)
{
ent[1] = m_aCharEntities[i].cCode;
int nRep = _tcsistrrep(pszText, m_aCharEntities[i].pszName, ent, buf);
if (nRep > 0)
{
bCharacterEntities = TRUE;
_tcscpy(pszText, buf);
}
}
delete [] buf;
buf = NULL;
TEXTMETRIC tm = { 0 };
n = (int) _tcslen(pszText); // get length again after char entity substitution
int textLen = n;
while ((n > 0) && pszText && (pszText < (pTextBuffer + textLen)))
{
TRACE(_T("start while: n=%d pszText=<%s>\n"), n, pszText);
///////////////////////////////////////////////////////////////////////
if (_tcsnicmp(pszText, _T("<B>"), 3) == 0) // check for <b> or <B>
{
n -= 3;
pszText += 3;
bBold++;// = TRUE;
continue;
}
///////////////////////////////////////////////////////////////////////
else if (_tcsnicmp(pszText, _T("</B>"), 4) == 0) // check for </B>
{
n -= 4;
pszText += 4;
if (bBold)
bBold--;// = FALSE;
continue;
}
///////////////////////////////////////////////////////////////////////
else if (_tcsnicmp(pszText, _T("<I>"), 3) == 0) // check for <I>
{
n -= 3;
pszText += 3;
bItalic++;// = TRUE;
continue;
}
///////////////////////////////////////////////////////////////////////
else if (_tcsnicmp(pszText, _T("</I>"), 4) == 0) // check for </I>
{
n -= 4;
pszText += 4;
if (bItalic)
bItalic--;// = FALSE;
continue;
}
///////////////////////////////////////////////////////////////////////
else if (_tcsnicmp(pszText, _T("<U>"), 3) == 0) // check for <U>
{
n -= 3;
pszText += 3;
bUnderline++;// = TRUE;
continue;
}
///////////////////////////////////////////////////////////////////////
else if (_tcsnicmp(pszText, _T("</U>"), 4) == 0) // check for </U>
{
n -= 4;
pszText += 4;
if (bUnderline)
bUnderline--;// = FALSE;
continue;
}
///////////////////////////////////////////////////////////////////////
else if (_tcsnicmp(pszText, _T("<S>"), 3) == 0) // check for <S>
{
n -= 3;
pszText += 3;
bStrikeThrough++;// = TRUE;
continue;
}
///////////////////////////////////////////////////////////////////////
else if (_tcsnicmp(pszText, _T("</S>"), 4) == 0) // check for </S>
{
n -= 4;
pszText += 4;
if (bStrikeThrough)
bStrikeThrough--;// = FALSE;
continue;
}
///////////////////////////////////////////////////////////////////////
else if (_tcsnicmp(pszText, _T("<BIG>"), 5) == 0) // check for <BIG>
{
n -= 5;
pszText += 5;
if (lf.lfHeight > 0)
lf.lfHeight += 2;
else
lf.lfHeight -= 2;
continue;
}
///////////////////////////////////////////////////////////////////////
else if (_tcsnicmp(pszText, _T("</BIG>"), 6) == 0) // check for </BIG>
{
n -= 6;
pszText += 6;
if (lf.lfHeight > 0)
lf.lfHeight -= 2;
else
lf.lfHeight += 2;
continue;
}
///////////////////////////////////////////////////////////////////////
else if (_tcsnicmp(pszText, _T("<SMALL>"), 7) == 0) // check for <SMALL>
{
n -= 7;
pszText += 7;
if (lf.lfHeight > 0)
lf.lfHeight -= 2;
else
lf.lfHeight += 2;
continue;
}
///////////////////////////////////////////////////////////////////////
else if (_tcsnicmp(pszText, _T("</SMALL>"), 8) == 0) // check for </SMALL>
{
n -= 8;
pszText += 8;
if (lf.lfHeight > 0)
lf.lfHeight += 2;
else
lf.lfHeight -= 2;
continue;
}
///////////////////////////////////////////////////////////////////////
else if (_tcsnicmp(pszText, _T("<SUB>"), 5) == 0) // check for <SUB>
{
n -= 5;
pszText += 5;
bSubscript++;// = TRUE;
continue;
}
///////////////////////////////////////////////////////////////////////
else if (_tcsnicmp(pszText, _T("</SUB>"), 6) == 0) // check for </SUB>
{
n -= 6;
pszText += 6;
if (bSubscript)
bSubscript--;// = FALSE;
continue;
}
///////////////////////////////////////////////////////////////////////
else if (_tcsnicmp(pszText, _T("<SUP>"), 5) == 0) // check for <SUP>
{
n -= 5;
pszText += 5;
bSuperscript++;// = TRUE;
continue;
}
///////////////////////////////////////////////////////////////////////
else if (_tcsnicmp(pszText, _T("</SUP>"), 6) == 0) // check for </SUP>
{
n -= 6;
pszText += 6;
if (bSuperscript)
bSuperscript--;// = FALSE;
continue;
}
///////////////////////////////////////////////////////////////////////
else if (_tcsnicmp(pszText, _T("<FONT"), 5) == 0) // check for <FONT
{
TRACE(_T("found font\n"));
TCHAR *cp = _tcschr(pszText, _T('>'));
if (cp)
{
TCHAR szAttributes[XHTMLDRAW_MAX_TEXT] = { 0 };
_tcsncpy(szAttributes, &pszText[5], cp-pszText-5);
TRACE(_T("szAttributes=<%s>\n"), szAttributes);
size_t m = _tcslen(szAttributes);
n -= (int) (cp - pszText + 1);
pszText = cp + 1;
// loop to parse FONT attributes
while (m > 0)
{
// trim left whitespace
if ((_tcslen(szAttributes) > 0) &&
(szAttributes[0] == _T(' ')))
{
m--;
_tcscpy(szAttributes, &szAttributes[1]);
continue;
}
///////////////////////////////////////////////////////////
if (_tcsnicmp(szAttributes, _T("COLOR"), 5) == 0)
{
TRACE(_T("found color\n"));
TCHAR *cp2 = _tcschr(szAttributes, _T('"'));
if (cp2)
{
m -= (cp2 - szAttributes) + 1;
_tcscpy(szAttributes, cp2+1);
cp2 = _tcschr(szAttributes, _T('"'));
if (cp2)
{
*cp2 = _T('\0');
TCHAR szColor[XHTMLDRAW_MAX_TEXT] = { _T('\0') };
_tcsncpy(szColor, szAttributes, cp2-szAttributes+1);
TRACE(_T("szColor=<%s>\n"), szColor);
CXNamedColors nc(szColor);
if (!pXHDS->bIgnoreColorTag)
crTextNew = nc.GetRGB();
_tcscpy(szAttributes, cp2+1);
m = _tcslen(szAttributes);
}
}
else
break;
}
///////////////////////////////////////////////////////////
else if (_tcsnicmp(szAttributes, _T("BGCOLOR"), 7) == 0)
{
TRACE(_T("found bgcolor\n"));
TCHAR *cp2 = _tcschr(szAttributes, _T('"'));
if (cp2)
{
m -= cp2 - szAttributes + 1;
_tcscpy(szAttributes, cp2+1);
cp2 = _tcschr(szAttributes, _T('"'));
if (cp2)
{
*cp2 = _T('\0');
TCHAR szBgColor[XHTMLDRAW_MAX_TEXT] = { _T('\0') };
_tcsncpy(szBgColor, szAttributes, cp2-szAttributes+1);
TRACE(_T("szBgColor=<%s>\n"), szBgColor);
CXNamedColors nc(szBgColor);
crBkgndNew = nc.GetRGB();
_tcscpy(szAttributes, cp2+1);
m = _tcslen(szAttributes);
}
}
else
break;
}
///////////////////////////////////////////////////////////
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -