/* DrawHTML() * Drop-in replacement for DrawText() supporting a tiny subset of HTML. */ #include #include #include //use unprintable characters so it doesn't find copied html to convert #define ENDFLAG 0x100 enum { tNONE, tB, tBR, tFONT, tI, tP, tSUB, tSUP, tU, tNUMTAGS }; struct { char *mnemonic; short token, param, block; } Tags[] = { { NULL, tNONE, 0, 0}, { _T("\x04"), tFONT, 1, 0 }, { _T("\x05"), tBR, 0, 1 }, /*{ _T("b"), tB, 0, 0}, { _T("br"), tBR, 0, 1}, { _T("em"), tI, 0, 0}, { _T("font"), tFONT, 1, 0}, { _T("i"), tI, 0, 0}, { _T("p"), tP, 0, 1}, { _T("strong"), tB, 0, 0}, { _T("sub"), tSUB, 0, 0}, { _T("sup"), tSUP, 0, 0}, { _T("u"), tU, 0, 0},*/ }; static int GetToken(LPCTSTR *String, int *Size, int *TokenLength, BOOL *WhiteSpace) { LPCTSTR Start, EndToken; int Length, EntryWhiteSpace, Index, IsEndTag; assert(String != NULL && *String != NULL); assert(Size != NULL); Start = *String; /* check for leading white space, then skip it */ if (WhiteSpace != NULL) { EntryWhiteSpace = *WhiteSpace; *WhiteSpace = EntryWhiteSpace || _istspace(*Start); } else { EntryWhiteSpace = FALSE; } /* if */ while (*Size > 0 && _istspace(*Start)) { Start++; *Size -= 1; } /* if */ if (*Size <= 0) return -1; /* no printable text left */ EndToken = Start; Length = 0; IsEndTag = 0; if (*EndToken == _T('\x01')) { /* might be a HTML tag, check */ EndToken++; Length++; if (Length < *Size && *EndToken == _T('\x03')) { IsEndTag = ENDFLAG; EndToken++; Length++; } /* if */ while (Length < *Size && !_istspace(*EndToken) && *EndToken != _T('\x01') && *EndToken != _T('\x02')) { EndToken++; Length++; } /* while */ for (Index = sizeof Tags / sizeof Tags[0] - 1; Index > 0; Index--) if (!_tcsnicmp(Start + (IsEndTag ? 2 : 1), Tags[Index].mnemonic, _tcslen(Tags[Index].mnemonic))) break; if (Index > 0) { /* so it is a tag, see whether to accept parameters */ if (Tags[Index].param && !IsEndTag) { while (Length < *Size && *EndToken != _T('\x01') && *EndToken != _T('\x02')) { EndToken++; Length++; } /* while */ } else if (*EndToken != _T('\x02')) { /* no parameters, then '>' must follow the tag */ Index = 0; } /* if */ if (WhiteSpace != NULL && Tags[Index].block) *WhiteSpace = FALSE; } /* if */ if (*EndToken == _T('\x02')) { EndToken++; Length++; } /* if */ /* skip trailing white space in some circumstances */ if (Index > 0 && (Tags[Index].block || EntryWhiteSpace)) { while (Length < *Size && _istspace(*EndToken)) { EndToken++; Length++; } /* while */ } /* if */ } else { /* normal word (no tag) */ Index = 0; while (Length < *Size && !_istspace(*EndToken) && *EndToken != _T('\x01')) { EndToken++; Length++; } /* while */ } /* if */ if (TokenLength != NULL) *TokenLength = Length; *Size -= Length; *String = Start; return Tags[Index].token | IsEndTag; } static int HexDigit(TCHAR ch) { if (ch >= _T('0') && ch <= _T('9')) return ch - _T('0'); if (ch >= _T('A') && ch <= _T('F')) return ch - _T('A') + 10; if (ch >= _T('a') && ch <= _T('f')) return ch - _T('a') + 10; return 0; } static COLORREF ParseColor(LPCTSTR String) { int Red, Green, Blue; if (*String == _T('\'') || *String == _T('"')) String++; if (*String == _T('#')) String++; Red = (HexDigit(String[0]) << 4) | HexDigit(String[1]); Green = (HexDigit(String[2]) << 4) | HexDigit(String[3]); Blue = (HexDigit(String[4]) << 4) | HexDigit(String[5]); return RGB(Red, Green, Blue); } #define STACKSIZE 8 static COLORREF stack[STACKSIZE]; static int stacktop; static BOOL PushColor(HDC hdc, COLORREF clr) { if (stacktop < STACKSIZE) stack[stacktop++] = GetTextColor(hdc); SetTextColor(hdc, clr); return TRUE; } static BOOL PopColor(HDC hdc) { COLORREF clr; BOOL okay = (stacktop > 0); if (okay) clr = stack[--stacktop]; else clr = stack[0]; SetTextColor(hdc, clr); return okay; } #define FV_BOLD 0x01 #define FV_ITALIC (FV_BOLD << 1) #define FV_UNDERLINE (FV_ITALIC << 1) #define FV_SUPERSCRIPT (FV_UNDERLINE << 1) #define FV_SUBSCRIPT (FV_SUPERSCRIPT << 1) #define FV_NUMBER (FV_SUBSCRIPT << 1) static HFONT GetFontVariant(HDC hdc, HFONT hfontSource, int Styles) { LOGFONT logFont = { 0 }; SelectObject(hdc, (HFONT)GetStockObject(SYSTEM_FONT)); if (!GetObject(hfontSource, sizeof logFont, &logFont)) return NULL; /* set parameters, create new font */ logFont.lfWeight = (Styles & FV_BOLD) ? FW_BOLD : FW_NORMAL; logFont.lfItalic = (BYTE)(Styles & FV_ITALIC) != 0; logFont.lfUnderline = (BYTE)(Styles & FV_UNDERLINE) != 0; if (Styles & (FV_SUPERSCRIPT | FV_SUBSCRIPT)) logFont.lfHeight = logFont.lfHeight * 7 / 10; return CreateFontIndirect(&logFont); } #if defined __cplusplus extern "C" #endif int __stdcall DrawHTML( HDC hdc, // handle of device context LPCTSTR lpString, // address of string to draw int nCount, // string length, in characters LPRECT lpRect, // address of structure with formatting dimensions UINT uFormat // text-drawing flags ) { LPCTSTR Start; int Left, Top, MaxWidth, MinWidth, Height, MaxHeight; int SavedDC; int Tag, TokenLength; HFONT hfontBase, hfontSpecial[FV_NUMBER]; int Styles, CurStyles; SIZE size; int Index, LineHeight; POINT CurPos; int WidthOfSPace, XPos; BOOL WhiteSpace; RECT rc; if (hdc == NULL || lpString == NULL) return 0; if (nCount < 0) nCount = _tcslen(lpString); MaxHeight = INT_MAX; if (lpRect != NULL) { Left = lpRect->left; Top = lpRect->top; MaxWidth = lpRect->right - lpRect->left; MaxHeight = lpRect->bottom - lpRect->top; } else { GetCurrentPositionEx(hdc, &CurPos); Left = CurPos.x; Top = CurPos.y; MaxWidth = GetDeviceCaps(hdc, HORZRES) - Left; } /* if */ if (MaxWidth < 0) MaxWidth = 0; /* toggle flags we do not support */ uFormat &= ~(DT_CENTER | DT_RIGHT | DT_TABSTOP); uFormat |= (DT_LEFT | DT_NOPREFIX); /* get the "default" font from the DC */ SavedDC = SaveDC(hdc); hfontBase = SelectObject(hdc, (HFONT)GetStockObject(SYSTEM_FONT)); SelectObject(hdc, hfontBase); /* clear the other fonts, they are created "on demand" */ for (Index = 0; Index < FV_NUMBER; Index++) hfontSpecial[Index] = NULL; hfontSpecial[0] = hfontBase; Styles = 0; /* assume the active font is normal weight, roman, non-underlined */ /* get font height (use characters with ascender and descender); * we make the assumption here that changing the font style will * not change the font height */ GetTextExtentPoint32(hdc, _T("Åy"), 2, &size); LineHeight = size.cy; /* run through the string, word for word */ XPos = 0; MinWidth = 0; stacktop = 0; CurStyles = -1; /* force a select of the proper style */ Height = 0; WhiteSpace = FALSE; Start = lpString; for (;; ) { Tag = GetToken(&Start, &nCount, &TokenLength, &WhiteSpace); if (Tag < 0) break; switch (Tag & ~ENDFLAG) { case tP: if ((Tag & ENDFLAG) == 0 && (uFormat & DT_SINGLELINE) == 0) { if (Start != lpString) Height += 3 * LineHeight / 2; XPos = 0; } /* if */ break; case tBR: if ((Tag & ENDFLAG) == 0 && (uFormat & DT_SINGLELINE) == 0) { Height += LineHeight; XPos = 0; } /* if */ break; case tB: Styles = (Tag & ENDFLAG) ? Styles & ~FV_BOLD : Styles | FV_BOLD; break; case tI: Styles = (Tag & ENDFLAG) ? Styles & ~FV_ITALIC : Styles | FV_ITALIC; break; case tU: Styles = (Tag & ENDFLAG) ? Styles & ~FV_UNDERLINE : Styles | FV_UNDERLINE; break; case tSUB: Styles = (Tag & ENDFLAG) ? Styles & ~FV_SUBSCRIPT : Styles | FV_SUBSCRIPT; break; case tSUP: Styles = (Tag & ENDFLAG) ? Styles & ~FV_SUPERSCRIPT : Styles | FV_SUPERSCRIPT; break; case tFONT: if ((Tag & ENDFLAG) == 0) { if (_tcsnicmp(Start + 3, _T("color="), 6) == 0) PushColor(hdc, ParseColor(Start + 9)); } else { PopColor(hdc); } /* if */ break; default: if (Tag == (tNONE | ENDFLAG)) break; if (CurStyles != Styles) { if (hfontSpecial[Styles] == NULL) hfontSpecial[Styles] = GetFontVariant(hdc, hfontBase, Styles); CurStyles = Styles; SelectObject(hdc, hfontSpecial[Styles]); /* get the width of a space character (for word spacing) */ GetTextExtentPoint32(hdc, _T(" "), 1, &size); WidthOfSPace = size.cx; } /* if */ /* check word length, check whether to wrap around */ GetTextExtentPoint32(hdc, Start, TokenLength, &size); if (size.cx > MaxWidth) MaxWidth = size.cx; /* must increase width: long non-breakable word */ if (WhiteSpace) XPos += WidthOfSPace; if (XPos + size.cx > MaxWidth && WhiteSpace) { if ((uFormat & DT_WORDBREAK) != 0) { /* word wrap */ Height += LineHeight; XPos = 0; } else { /* no word wrap, must increase the width */ MaxWidth = XPos + size.cx; } /* if */ } /* if */ /* output text (unless DT_CALCRECT is set) */ if ((uFormat & DT_CALCRECT) == 0) { /* handle negative heights, too (suggestion of "Sims") */ if (Top < 0) SetRect(&rc, Left + XPos, Top - Height, Left + MaxWidth, Top - (Height + LineHeight)); else SetRect(&rc, Left + XPos, Top + Height, Left + MaxWidth, Top + Height + LineHeight); /* reposition subscript text to align below the baseline */ DrawText(hdc, Start, TokenLength, &rc, uFormat | ((Styles & FV_SUBSCRIPT) ? DT_BOTTOM | DT_SINGLELINE : 0)); /* for the underline style, the spaces between words should be * underlined as well */ if (WhiteSpace && (Styles & FV_UNDERLINE) && XPos >= WidthOfSPace) { if (Top < 0) SetRect(&rc, Left + XPos - WidthOfSPace, Top - Height, Left + XPos, Top - (Height + LineHeight)); else SetRect(&rc, Left + XPos - WidthOfSPace, Top + Height, Left + XPos, Top + Height + LineHeight); DrawText(hdc, " ", 1, &rc, uFormat); } /* if */ } /* if */ /* update current position */ XPos += size.cx; if (XPos > MinWidth) MinWidth = XPos; WhiteSpace = FALSE; } /* if */ if ((Height + LineHeight) >= MaxHeight) break; Start += TokenLength; } /* for */ RestoreDC(hdc, SavedDC); for (Index = 1; Index < FV_NUMBER; Index++) /* do not erase hfontSpecial[0] */ if (hfontSpecial[Index] != NULL) DeleteObject(hfontSpecial[Index]); /* store width and height back into the lpRect structure */ if ((uFormat & DT_CALCRECT) != 0 && lpRect != NULL) { lpRect->right = lpRect->left + MinWidth; if (lpRect->top < 0) lpRect->bottom = lpRect->top - (Height + LineHeight); else lpRect->bottom = lpRect->top + Height + LineHeight; } /* if */ return Height; }