//============================================================================== // RichEdit 2.0 windowless drawing routines // For some reason I'm getting the wrong IID from riched20.lib const IID IID_ITextServices = { 0x8d33f740, 0xcf58, 0x11ce, {0xa8, 0x9d, 0x00, 0xaa, 0x00, 0x6c, 0xad, 0xc5}}; ITextServices *g_pts = NULL; int g_x_per_inch = 96; int g_y_per_inch = 96; HRESULT RichEdit_GetTextServices(ITextServices **ppts) { HRESULT hr = E_OUTOFMEMORY; if (g_pts == NULL) { // Put this initialization somewhere... HDC hdcScreen = GetDC(NULL); g_x_per_inch = GetDeviceCaps(hdcScreen, LOGPIXELSX); g_y_per_inch = GetDeviceCaps(hdcScreen, LOGPIXELSY); ReleaseDC(NULL, hdcScreen); // End of screen initialization... TextHost *host = new TextHost(); if (host) { IUnknown *punkTS; hr = CreateTextServices(NULL, host, &punkTS); if (SUCCEEDED(hr)) { // you would think that CreateTextServices would AddRef the host // pointer we just passed it, but alas its COM support it poor. host->AddRef(); hr = punkTS->QueryInterface(IID_ITextServices, (void **)&g_pts); if (SUCCEEDED(hr)) { // nothing to do } punkTS->Release(); } host->Release(); } } if (g_pts) { hr = g_pts->QueryInterface(IID_ITextServices, (void **)ppts); } return hr; } #define Y_PER_INCH 1440 // TWIPS conversion void RichEdit_SetFont(ITextServices *pts, HFONT font, COLORREF color) { LOGFONT lf; if (GetObject(font, sizeof(lf), &lf)) { CHARFORMAT2W char_format; memset(&char_format, 0, sizeof(char_format)); char_format.cbSize = sizeof(char_format); char_format.yHeight = -lf.lfHeight * Y_PER_INCH / g_y_per_inch; char_format.crTextColor = color; char_format.dwEffects = CFM_EFFECTS | CFE_AUTOBACKCOLOR; char_format.dwEffects &= ~(CFE_PROTECTED | CFE_LINK | CFE_AUTOCOLOR); if(lf.lfWeight < FW_BOLD) { char_format.dwEffects &= ~CFE_BOLD; } if(!lf.lfItalic) { char_format.dwEffects &= ~CFE_ITALIC; } if(!lf.lfUnderline) { char_format.dwEffects &= ~CFE_UNDERLINE; } if(!lf.lfStrikeOut) { char_format.dwEffects &= ~CFE_STRIKEOUT; } char_format.dwMask = CFM_ALL | CFM_BACKCOLOR | CFM_STYLE | CFM_COLOR; char_format.bCharSet = lf.lfCharSet; char_format.bPitchAndFamily = lf.lfPitchAndFamily; memcpy(char_format.szFaceName, lf.lfFaceName, sizeof(char_format.szFaceName)); LRESULT lResult; pts->TxSendMessage(EM_SETCHARFORMAT, 0, (LPARAM)&char_format, &lResult); } } #define HIMETRIC_PER_INCH 2540 // 2540 units per inch void RichEdit_MeasureText(HDC hdc, HFONT hfont, const CString &text, int width, SIZE *psize) { psize->cx = 0; psize->cy = 0; ITextServices *pts; HRESULT hr = RichEdit_GetTextServices(&pts); if (SUCCEEDED(hr)) { // Set the right font RichEdit_SetFont(pts, hfont, RGB(0xFF,0xFF,0xFF)); // Set the right text pts->TxSetText(L""); PARAFORMAT pf; ZeroMemory(&pf, sizeof(pf)); pf.cbSize = sizeof(pf); pf.dwMask = PFM_ALIGNMENT; pf.wAlignment = PFA_LEFT; LRESULT lresult; pts->TxSendMessage(EM_SETPARAFORMAT, NULL, (LPARAM)&pf, &lresult); pts->TxSetText(text); // Now do the measurement SIZEL size_long = { LONG_MAX, LONG_MAX }; LONG width_long = LONG_MAX; if (width != INT_MAX && g_x_per_inch != 0) { // TxGetNaturalSize takes sizes in MM_HIMETRIC width_long = (width * HIMETRIC_PER_INCH + g_x_per_inch - 1) / g_x_per_inch; } LONG height_long = LONG_MAX; // *** Mapping mode freak-out time *** // // Ok, here is the deal. RichEdit likes to measure the size of your // font in MM_HIMETRIC mode, it does this to gain accuracy. But the // rendering code in "TxDraw" isn't as nice. It tries to clip the // text to the bounding rectangle that is passed to it, and when it // is doing that measurement, it does it in MM_TEXT mode. Due to // rounding errors, the two will be different - in a bad way. // It is nearly impossible to get TxDraw to do the right thing, so // instead we cripple the TxGetNaturalSize. We apply a transform // such that the font will be scaled down and measured as if it were // only as big in MM_HIMETRIC as it eventually is in MM_TEXT later. Ugh. SetMapMode(hdc, MM_ANISOTROPIC); // Yes RichEdit hard-codes this HIMETRIC_PER_INCH value too SetWindowExtEx(hdc, HIMETRIC_PER_INCH, HIMETRIC_PER_INCH, NULL); SetViewportExtEx(hdc, g_x_per_inch, g_y_per_inch, NULL); hr = pts->TxGetNaturalSize(DVASPECT_CONTENT, hdc, NULL, NULL, TXTNS_FITTOCONTENT, &size_long, &width_long, &height_long); // Restore mapping mode to something sane SetMapMode(hdc, MM_TEXT); // // *** End mapping mode freak-out time *** if (SUCCEEDED(hr)) { // TxGetNaturalSize gives back width and height in terms of MM_HIMETRIC // (2540 units per inch). Let's round up to nearest pixel psize->cx = ((width_long * g_x_per_inch + HIMETRIC_PER_INCH - 1) / HIMETRIC_PER_INCH); psize->cy = ((height_long * g_y_per_inch + HIMETRIC_PER_INCH - 1) / HIMETRIC_PER_INCH); } pts->Release(); } } void RichEdit_DrawText(HDC hdc, const RECT *rect, HFONT hfont, COLORREF color, const CString &text, bool centered) { RECTL rc = { rect->left, rect->top, rect->right, rect->bottom}; ITextServices *pts; HRESULT hr = RichEdit_GetTextServices(&pts); if (SUCCEEDED(hr)) { // Set the right font RichEdit_SetFont(pts, hfont, color); // Set the right text pts->TxSetText(L""); PARAFORMAT pf; ZeroMemory(&pf, sizeof(pf)); pf.cbSize = sizeof(pf); pf.dwMask = PFM_ALIGNMENT; if (centered) { pf.wAlignment = PFA_CENTER; } else { pf.wAlignment = PFA_LEFT; } LRESULT lresult; pts->TxSendMessage(EM_SETPARAFORMAT, NULL, (LPARAM)&pf, &lresult); pts->TxSetText(text); // Now draw the text pts->TxDraw(DVASPECT_CONTENT, 0, NULL, NULL, hdc, NULL, &rc, NULL, NULL, NULL, NULL, TXTVIEW_INACTIVE); pts->Release(); } } void RichEdit_CleanupGlobals() { if (g_pts) { g_pts->Release(); g_pts = NULL; } }