DrawHTML.C 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. /* DrawHTML()
  2. * Drop-in replacement for DrawText() supporting a tiny subset of HTML.
  3. */
  4. #include <windows.h>
  5. #include <tchar.h>
  6. #include <assert.h>
  7. #define ENDFLAG 0x100
  8. enum { tNONE, tB, tBR, tFONT, tI, tP, tSUB, tSUP, tU, tNUMTAGS };
  9. struct {
  10. char *mnemonic;
  11. short token,param,block;
  12. } Tags[] = {
  13. { NULL, tNONE, 0, 0},
  14. { _T("b"), tB, 0, 0},
  15. { _T("br"), tBR, 0, 1},
  16. { _T("em"), tI, 0, 0},
  17. { _T("font"), tFONT, 1, 0},
  18. { _T("i"), tI, 0, 0},
  19. { _T("p"), tP, 0, 1},
  20. { _T("strong"), tB, 0, 0},
  21. { _T("sub"), tSUB, 0, 0},
  22. { _T("sup"), tSUP, 0, 0},
  23. { _T("u"), tU, 0, 0},
  24. };
  25. static int GetToken(LPCTSTR *String, int *Size, int *TokenLength, BOOL *WhiteSpace)
  26. {
  27. LPCTSTR Start, EndToken;
  28. int Length, EntryWhiteSpace, Index, IsEndTag;
  29. assert(String != NULL && *String != NULL);
  30. assert(Size != NULL);
  31. Start = *String;
  32. /* check for leading white space, then skip it */
  33. if (WhiteSpace != NULL) {
  34. EntryWhiteSpace = *WhiteSpace;
  35. *WhiteSpace = EntryWhiteSpace || _istspace(*Start);
  36. } else {
  37. EntryWhiteSpace = FALSE;
  38. } /* if */
  39. while (*Size > 0 && _istspace(*Start)) {
  40. Start++;
  41. *Size -= 1;
  42. } /* if */
  43. if (*Size <= 0)
  44. return -1; /* no printable text left */
  45. EndToken = Start;
  46. Length = 0;
  47. IsEndTag = 0;
  48. if (*EndToken == _T('<')) {
  49. /* might be a HTML tag, check */
  50. EndToken++;
  51. Length++;
  52. if (Length < *Size && *EndToken == _T('/')) {
  53. IsEndTag = ENDFLAG;
  54. EndToken++;
  55. Length++;
  56. } /* if */
  57. while (Length < *Size && !_istspace(*EndToken)
  58. && *EndToken != _T('<') && *EndToken != _T('>'))
  59. {
  60. EndToken++;
  61. Length++;
  62. } /* while */
  63. for (Index = sizeof Tags / sizeof Tags[0] - 1; Index > 0; Index--)
  64. if (!_tcsnicmp(Start + (IsEndTag ? 2 : 1), Tags[Index].mnemonic,
  65. _tcslen(Tags[Index].mnemonic)))
  66. break;
  67. if (Index > 0) {
  68. /* so it is a tag, see whether to accept parameters */
  69. if (Tags[Index].param && !IsEndTag) {
  70. while (Length < *Size
  71. && *EndToken != _T('<') && *EndToken != _T('>'))
  72. {
  73. EndToken++;
  74. Length++;
  75. } /* while */
  76. } else if (*EndToken != _T('>')) {
  77. /* no parameters, then '>' must follow the tag */
  78. Index = 0;
  79. } /* if */
  80. if (WhiteSpace != NULL && Tags[Index].block)
  81. *WhiteSpace = FALSE;
  82. } /* if */
  83. if (*EndToken == _T('>')) {
  84. EndToken++;
  85. Length++;
  86. } /* if */
  87. /* skip trailing white space in some circumstances */
  88. if (Index > 0 && (Tags[Index].block || EntryWhiteSpace)) {
  89. while (Length < *Size && _istspace(*EndToken)) {
  90. EndToken++;
  91. Length++;
  92. } /* while */
  93. } /* if */
  94. } else {
  95. /* normal word (no tag) */
  96. Index = 0;
  97. while (Length < *Size && !_istspace(*EndToken) && *EndToken != _T('<')) {
  98. EndToken++;
  99. Length++;
  100. } /* while */
  101. } /* if */
  102. if (TokenLength != NULL)
  103. *TokenLength = Length;
  104. *Size -= Length;
  105. *String = Start;
  106. return Tags[Index].token | IsEndTag;
  107. }
  108. static int HexDigit(TCHAR ch)
  109. {
  110. if (ch >= _T('0') && ch <= _T('9'))
  111. return ch - _T('0');
  112. if (ch >= _T('A') && ch <= _T('F'))
  113. return ch - _T('A') + 10;
  114. if (ch >= _T('a') && ch <= _T('f'))
  115. return ch - _T('a') + 10;
  116. return 0;
  117. }
  118. static COLORREF ParseColor(LPCTSTR String)
  119. {
  120. int Red, Green, Blue;
  121. if (*String == _T('\'') || *String == _T('"'))
  122. String++;
  123. if (*String == _T('#'))
  124. String++;
  125. Red = (HexDigit(String[0]) << 4) | HexDigit(String[1]);
  126. Green = (HexDigit(String[2]) << 4) | HexDigit(String[3]);
  127. Blue = (HexDigit(String[4]) << 4) | HexDigit(String[5]);
  128. return RGB(Red, Green, Blue);
  129. }
  130. #define STACKSIZE 8
  131. static COLORREF stack[STACKSIZE];
  132. static int stacktop;
  133. static BOOL PushColor(HDC hdc, COLORREF clr)
  134. {
  135. if (stacktop < STACKSIZE)
  136. stack[stacktop++] = GetTextColor(hdc);
  137. SetTextColor(hdc, clr);
  138. return TRUE;
  139. }
  140. static BOOL PopColor(HDC hdc)
  141. {
  142. COLORREF clr;
  143. BOOL okay = (stacktop > 0);
  144. if (okay)
  145. clr = stack[--stacktop];
  146. else
  147. clr = stack[0];
  148. SetTextColor(hdc, clr);
  149. return okay;
  150. }
  151. #define FV_BOLD 0x01
  152. #define FV_ITALIC (FV_BOLD << 1)
  153. #define FV_UNDERLINE (FV_ITALIC << 1)
  154. #define FV_SUPERSCRIPT (FV_UNDERLINE << 1)
  155. #define FV_SUBSCRIPT (FV_SUPERSCRIPT << 1)
  156. #define FV_NUMBER (FV_SUBSCRIPT << 1)
  157. static HFONT GetFontVariant(HDC hdc, HFONT hfontSource, int Styles)
  158. {
  159. LOGFONT logFont = { 0 };
  160. SelectObject(hdc, (HFONT)GetStockObject(SYSTEM_FONT));
  161. if (!GetObject(hfontSource, sizeof logFont, &logFont))
  162. return NULL;
  163. /* set parameters, create new font */
  164. logFont.lfWeight = (Styles & FV_BOLD) ? FW_BOLD : FW_NORMAL;
  165. logFont.lfItalic = (BYTE)(Styles & FV_ITALIC) != 0;
  166. logFont.lfUnderline = (BYTE)(Styles & FV_UNDERLINE) != 0;
  167. if (Styles & (FV_SUPERSCRIPT | FV_SUBSCRIPT))
  168. logFont.lfHeight = logFont.lfHeight * 7 / 10;
  169. return CreateFontIndirect(&logFont);
  170. }
  171. #if defined __cplusplus
  172. extern "C"
  173. #endif
  174. int __stdcall DrawHTML(
  175. HDC hdc, // handle of device context
  176. LPCTSTR lpString, // address of string to draw
  177. int nCount, // string length, in characters
  178. LPRECT lpRect, // address of structure with formatting dimensions
  179. UINT uFormat // text-drawing flags
  180. )
  181. {
  182. LPCTSTR Start;
  183. int Left, Top, MaxWidth, MinWidth, Height;
  184. int SavedDC;
  185. int Tag, TokenLength;
  186. HFONT hfontBase, hfontSpecial[FV_NUMBER];
  187. int Styles, CurStyles;
  188. SIZE size;
  189. int Index, LineHeight;
  190. POINT CurPos;
  191. int WidthOfSPace, XPos;
  192. BOOL WhiteSpace;
  193. RECT rc;
  194. if (hdc == NULL || lpString == NULL)
  195. return 0;
  196. if (nCount < 0)
  197. nCount = _tcslen(lpString);
  198. if (lpRect != NULL) {
  199. Left = lpRect->left;
  200. Top = lpRect->top;
  201. MaxWidth = lpRect->right - lpRect->left;
  202. } else {
  203. GetCurrentPositionEx(hdc, &CurPos);
  204. Left = CurPos.x;
  205. Top = CurPos.y;
  206. MaxWidth = GetDeviceCaps(hdc, HORZRES) - Left;
  207. } /* if */
  208. if (MaxWidth < 0)
  209. MaxWidth = 0;
  210. /* toggle flags we do not support */
  211. uFormat &= ~(DT_CENTER | DT_RIGHT | DT_TABSTOP);
  212. uFormat |= (DT_LEFT | DT_NOPREFIX);
  213. /* get the "default" font from the DC */
  214. SavedDC = SaveDC(hdc);
  215. hfontBase = SelectObject(hdc, (HFONT)GetStockObject(SYSTEM_FONT));
  216. SelectObject(hdc, hfontBase);
  217. /* clear the other fonts, they are created "on demand" */
  218. for (Index = 0; Index < FV_NUMBER; Index++)
  219. hfontSpecial[Index] = NULL;
  220. hfontSpecial[0] = hfontBase;
  221. Styles = 0; /* assume the active font is normal weight, roman, non-underlined */
  222. /* get font height (use characters with ascender and descender);
  223. * we make the assumption here that changing the font style will
  224. * not change the font height
  225. */
  226. GetTextExtentPoint32(hdc, _T("Åy"), 2, &size);
  227. LineHeight = size.cy;
  228. /* run through the string, word for word */
  229. XPos = 0;
  230. MinWidth = 0;
  231. stacktop = 0;
  232. CurStyles = -1; /* force a select of the proper style */
  233. Height = 0;
  234. WhiteSpace = FALSE;
  235. Start = lpString;
  236. for ( ;; ) {
  237. Tag = GetToken(&Start, &nCount, &TokenLength, &WhiteSpace);
  238. if (Tag < 0)
  239. break;
  240. switch (Tag & ~ENDFLAG) {
  241. case tP:
  242. if ((Tag & ENDFLAG) == 0 && (uFormat & DT_SINGLELINE) == 0) {
  243. if (Start != lpString)
  244. Height += 3 * LineHeight / 2;
  245. XPos = 0;
  246. } /* if */
  247. break;
  248. case tBR:
  249. if ((Tag & ENDFLAG) == 0 && (uFormat & DT_SINGLELINE) == 0) {
  250. Height += LineHeight;
  251. XPos = 0;
  252. } /* if */
  253. break;
  254. case tB:
  255. Styles = (Tag & ENDFLAG) ? Styles & ~FV_BOLD : Styles | FV_BOLD;
  256. break;
  257. case tI:
  258. Styles = (Tag & ENDFLAG) ? Styles & ~FV_ITALIC : Styles | FV_ITALIC;
  259. break;
  260. case tU:
  261. Styles = (Tag & ENDFLAG) ? Styles & ~FV_UNDERLINE : Styles | FV_UNDERLINE;
  262. break;
  263. case tSUB:
  264. Styles = (Tag & ENDFLAG) ? Styles & ~FV_SUBSCRIPT : Styles | FV_SUBSCRIPT;
  265. break;
  266. case tSUP:
  267. Styles = (Tag & ENDFLAG) ? Styles & ~FV_SUPERSCRIPT : Styles | FV_SUPERSCRIPT;
  268. break;
  269. case tFONT:
  270. if ((Tag & ENDFLAG) == 0) {
  271. if (_tcsnicmp(Start + 6, _T("color="), 6) == 0)
  272. PushColor(hdc, ParseColor(Start + 12));
  273. } else {
  274. PopColor(hdc);
  275. } /* if */
  276. break;
  277. default:
  278. if (Tag == (tNONE | ENDFLAG))
  279. break;
  280. if (CurStyles != Styles) {
  281. if (hfontSpecial[Styles] == NULL)
  282. hfontSpecial[Styles] = GetFontVariant(hdc, hfontBase, Styles);
  283. CurStyles = Styles;
  284. SelectObject(hdc, hfontSpecial[Styles]);
  285. /* get the width of a space character (for word spacing) */
  286. GetTextExtentPoint32(hdc, _T(" "), 1, &size);
  287. WidthOfSPace = size.cx;
  288. } /* if */
  289. /* check word length, check whether to wrap around */
  290. GetTextExtentPoint32(hdc, Start, TokenLength, &size);
  291. if (size.cx > MaxWidth)
  292. MaxWidth = size.cx; /* must increase width: long non-breakable word */
  293. if (WhiteSpace)
  294. XPos += WidthOfSPace;
  295. if (XPos + size.cx > MaxWidth && WhiteSpace) {
  296. if ((uFormat & DT_WORDBREAK) != 0) {
  297. /* word wrap */
  298. Height += LineHeight;
  299. XPos = 0;
  300. } else {
  301. /* no word wrap, must increase the width */
  302. MaxWidth = XPos + size.cx;
  303. } /* if */
  304. } /* if */
  305. /* output text (unless DT_CALCRECT is set) */
  306. if ((uFormat & DT_CALCRECT) == 0) {
  307. /* handle negative heights, too (suggestion of "Sims") */
  308. if (Top < 0)
  309. SetRect(&rc, Left + XPos, Top - Height,
  310. Left + MaxWidth, Top - (Height + LineHeight));
  311. else
  312. SetRect(&rc, Left + XPos, Top + Height,
  313. Left + MaxWidth, Top + Height + LineHeight);
  314. /* reposition subscript text to align below the baseline */
  315. DrawText(hdc, Start, TokenLength, &rc,
  316. uFormat | ((Styles & FV_SUBSCRIPT) ? DT_BOTTOM | DT_SINGLELINE : 0));
  317. /* for the underline style, the spaces between words should be
  318. * underlined as well
  319. */
  320. if (WhiteSpace && (Styles & FV_UNDERLINE) && XPos >= WidthOfSPace) {
  321. if (Top < 0)
  322. SetRect(&rc, Left + XPos - WidthOfSPace, Top - Height,
  323. Left + XPos, Top - (Height + LineHeight));
  324. else
  325. SetRect(&rc, Left + XPos - WidthOfSPace, Top + Height,
  326. Left + XPos, Top + Height + LineHeight);
  327. DrawText(hdc, " ", 1, &rc, uFormat);
  328. } /* if */
  329. } /* if */
  330. /* update current position */
  331. XPos += size.cx;
  332. if (XPos > MinWidth)
  333. MinWidth = XPos;
  334. WhiteSpace = FALSE;
  335. } /* if */
  336. Start += TokenLength;
  337. } /* for */
  338. RestoreDC(hdc, SavedDC);
  339. for (Index = 1; Index < FV_NUMBER; Index++) /* do not erase hfontSpecial[0] */
  340. if (hfontSpecial[Index] != NULL)
  341. DeleteObject(hfontSpecial[Index]);
  342. /* store width and height back into the lpRect structure */
  343. if ((uFormat & DT_CALCRECT) != 0 && lpRect!=NULL) {
  344. lpRect->right = lpRect->left + MinWidth;
  345. if (lpRect->top < 0)
  346. lpRect->bottom = lpRect->top - (Height + LineHeight);
  347. else
  348. lpRect->bottom = lpRect->top + Height + LineHeight;
  349. } /* if */
  350. return Height;
  351. }