/****************************** Module Header ******************************\ * Module Name: drawtext.c * * Copyright (c) 1985 - 1999, Microsoft Corporation * * This module contains common text drawing functions. * * History: * 02-12-92 mikeke Moved Drawtext to the client side \***************************************************************************/ #include "precomp.h" #pragma hdrstop #define CR 13 #define LF 10 #define DT_HFMTMASK 0x03 /***************************************************************************\ * IsMetaFile * * History: * 30-Nov-1992 mikeke Created \***************************************************************************/ BOOL IsMetaFile( HDC hdc) { DWORD dwType = GetObjectType(hdc); return (dwType == OBJ_METAFILE || dwType == OBJ_METADC || dwType == OBJ_ENHMETAFILE || dwType == OBJ_ENHMETADC); } /***************************************************************************\ * DrawTextA (API) * * History: * 30-11-92 mikeke Created \***************************************************************************/ CONST WCHAR gwszNullStr[] = L""; FUNCLOG6(LOG_GENERAL, int, DUMMYCALLINGTYPE, DrawTextExA, HDC, hdc, LPSTR, lpchText, int, cchText, LPRECT, lprc, UINT, format, LPDRAWTEXTPARAMS, lpdtp) int DrawTextExA( HDC hdc, LPSTR lpchText, int cchText, LPRECT lprc, UINT format, LPDRAWTEXTPARAMS lpdtp) { LPWSTR lpwstr; int iRet; int iUniString; WORD wCodePage = (WORD)GdiGetCodePage(hdc); if (cchText == -1) { // USER_AWCONV_COUNTSTRINGSZ does not count/convert trailing \0. cchText = USER_AWCONV_COUNTSTRINGSZ; } else if (cchText < -1) { return 0; } if ((iUniString = MBToWCSEx(wCodePage, lpchText, cchText, &lpwstr, -1, TRUE)) == 0) { if (cchText == USER_AWCONV_COUNTSTRINGSZ) { lpwstr = (LPWSTR)gwszNullStr; format &= ~DT_MODIFYSTRING; } else { return 0; } } /* * Grow the buffer to accomodate the ellipsis (see AddEllipsisAndDrawLine) */ if (format & DT_MODIFYSTRING) { int iNewLen = (iUniString + CCHELLIPSIS + 1) * sizeof(*lpwstr); LPWSTR lpwstrNew = UserLocalReAlloc(lpwstr, iNewLen, HEAP_ZERO_MEMORY); if (lpwstrNew == NULL) { UserLocalFree((HANDLE)lpwstr); return FALSE; } lpwstr = lpwstrNew; } iRet = DrawTextExWorker(hdc, lpwstr, iUniString, lprc, format, lpdtp, GetTextCharset(hdc)); if (format & DT_MODIFYSTRING) { /* * Note that if the buffer grew and the caller provided the string size, * then we won't return the additional characters... fixing this * might break some apps so let's leave it alone until some one complains */ if (cchText < 0) { UserAssert(cchText == USER_AWCONV_COUNTSTRINGSZ); // Guess how many bytes we can put in the buffer... // We can safely assume the maximum bytes available. // At worst, even for DBCS, the buffer size required // will be smaller than or equal to the orignal size, // because some DBCS characters would be substituted // to SBC ".", which is one byte each. // On the other hand, the number of characters converted // is limited by both iUniString and cchText. // if (IS_DBCS_ENABLED()) { cchText = iUniString * DBCS_CHARSIZE; } else { cchText = iUniString * sizeof(CHAR); } } WCSToMBEx(wCodePage, lpwstr, iUniString, &lpchText, cchText, FALSE); } if (lpwstr != gwszNullStr) { UserLocalFree((HANDLE)lpwstr); } return iRet; } /***************************************************************************\ * DrawTextW (API) * * History: * 30-11-92 mikeke Created \***************************************************************************/ FUNCLOG5(LOG_GENERAL, int, DUMMYCALLINGTYPE, DrawTextW, HDC, hdc, LPCWSTR, lpchText, int, cchText, LPRECT, lprc, UINT, format) int DrawTextW( HDC hdc, LPCWSTR lpchText, int cchText, LPRECT lprc, UINT format) { DRAWTEXTPARAMS DTparams; LPDRAWTEXTPARAMS lpDTparams = NULL; /* v-ronaar: fix bug #24985 * Disallow negative string lengths, except -1 (which has special meaning). */ if (cchText < -1) return(0); if (format & DT_TABSTOP) { DTparams.cbSize = sizeof(DRAWTEXTPARAMS); DTparams.iLeftMargin = DTparams.iRightMargin = 0; DTparams.iTabLength = (format & 0xff00) >> 8; lpDTparams = &DTparams; format &= 0xffff00ff; } return DrawTextExW(hdc, (LPWSTR)lpchText, cchText, lprc, format, lpDTparams); } /***************************************************************************\ * DrawTextA (API) * * History: * 30-11-92 mikeke Created \***************************************************************************/ FUNCLOG5(LOG_GENERAL, int, DUMMYCALLINGTYPE, DrawTextA, HDC, hdc, LPCSTR, lpchText, int, cchText, LPRECT, lprc, UINT, format) int DrawTextA( HDC hdc, LPCSTR lpchText, int cchText, LPRECT lprc, UINT format) { DRAWTEXTPARAMS DTparams; LPDRAWTEXTPARAMS lpDTparams = NULL; /* v-ronaar: fix bug #24985 * Disallow negative string lengths, except -1 (which has special meaning). */ if (cchText < -1) return(0); if (format & DT_TABSTOP) { DTparams.cbSize = sizeof(DRAWTEXTPARAMS); DTparams.iLeftMargin = DTparams.iRightMargin = 0; DTparams.iTabLength = (format & 0xff00) >> 8; lpDTparams = &DTparams; format &= 0xffff00ff; } return DrawTextExA(hdc, (LPSTR)lpchText, cchText, lprc, format, lpDTparams); } /***************************************************************************\ * ClientTabTheTextOutForWimps * * effects: Outputs the tabbed text if fDrawTheText is TRUE and returns the * textextent of the tabbed text. * * nCount Count of bytes in string * nTabPositions Count of tabstops in tabstop array * lpintTabStopPositions Tab stop positions in pixels * iTabOrigin Tab stops are with respect to this * * History: * 19-Jan-1993 mikeke Client side * 13-Sep-1996 GregoryW This routine now calls the LPK(s) to handle text out. * If no LPKs are installed, this defaults to calling * UserLpkTabbedTextOut (identical behavior to what we * had before supporting LPKs). \***************************************************************************/ LONG TabTextOut( HDC hdc, int x, int y, LPCWSTR lpstring, int nCount, int nTabPositions, CONST INT *lpTabPositions, int iTabOrigin, BOOL fDrawTheText, int iCharset) { int cxCharWidth; int cyCharHeight = 0; if (nCount == -1 && lpstring) { nCount = wcslen(lpstring); } if (!lpstring || nCount < 0 || nTabPositions < 0) return 0; // Check if it is SysFont AND the mapping mode is MM_TEXT; // Fix made in connection with Bug #8717 --02-01-90 --SANKAR-- if (IsSysFontAndDefaultMode(hdc)) { cxCharWidth = gpsi->cxSysFontChar; cyCharHeight = gpsi->cySysFontChar; } else { cxCharWidth = GdiGetCharDimensions(hdc, NULL, &cyCharHeight); if (cxCharWidth == 0) { RIPMSG0(RIP_WARNING, "TabTextOut: GdiGetCharDimensions failed"); return 0; } } return (*fpLpkTabbedTextOut)(hdc, x, y, lpstring, nCount, nTabPositions, lpTabPositions, iTabOrigin, fDrawTheText, cxCharWidth, cyCharHeight, iCharset); } LONG UserLpkTabbedTextOut( HDC hdc, int x, int y, LPCWSTR lpstring, int nCount, int nTabPositions, CONST INT *lpTabPositions, int iTabOrigin, BOOL fDrawTheText, int cxCharWidth, int cyCharHeight, int iCharset) { SIZE textextent, viewextent, windowextent; int initialx = x; int cch; LPCWSTR lp; int iOneTab = 0; RECT rc; UINT uOpaque = (GetBkMode(hdc) == OPAQUE) ? ETO_OPAQUE : 0; BOOL fStrStart = TRUE; int ySign = 1; //Assume y increases in down direction. UNREFERENCED_PARAMETER(iCharset); //Needed by lpk, but not us /* * If no tabstop positions are specified, then use a default of 8 system * font ave char widths or use the single fixed tab stop. */ if (!lpTabPositions) { // no tab stops specified -- default to a tab stop every 8 characters iOneTab = 8 * cxCharWidth; } else if (nTabPositions == 1) { // one tab stop specified -- treat value as the tab increment, one // tab stop every increment iOneTab = lpTabPositions[0]; if (!iOneTab) iOneTab = 1; } // Calculate if the y increases or decreases in the down direction using // the ViewPortExtent and WindowExtents. // If this call fails, hdc must be invalid if (!GetViewportExtEx(hdc, &viewextent)) return 0; GetWindowExtEx(hdc, &windowextent); if ((viewextent.cy ^ windowextent.cy) & 0x80000000) ySign = -1; rc.left = initialx; rc.top = y; rc.bottom = rc.top + (ySign * cyCharHeight); while (TRUE) { // count the number of characters until the next tab character // this set of characters (substring) will be the working set for // each iteration of this loop for (cch = nCount, lp = lpstring; cch && (*lp != TEXT('\t')); lp++, cch--) { } // Compute the number of characters to be drawn with textout. cch = nCount - cch; // Compute the number of characters remaining. nCount -= cch + 1; // get height and width of substring if (cch == 0) { textextent.cx = 0; textextent.cy = cyCharHeight; } else GetTextExtentPointW(hdc, lpstring, cch, &textextent); if (fStrStart) // first iteration should just spit out the first substring // no tabbing occurs until the first tab character is encountered fStrStart = FALSE; else { // not the first iteration -- tab accordingly int xTab; int i; if (!iOneTab) { // look thru tab stop array for next tab stop after existing // text to put this substring for (i = 0; i < nTabPositions; i++) { xTab = lpTabPositions[i]; if (xTab < 0) // calc length needed to use this right justified tab xTab = (iTabOrigin - xTab) - textextent.cx; else // calc length needed to use this left justified tab xTab = iTabOrigin + xTab; if (x < xTab) { // we found a tab with enough room -- let's use it x = xTab; break; } } if (i == nTabPositions) // we've exhausted all of the given tab positions // go back to default of a tab stop every 8 characters iOneTab = 8 * cxCharWidth; } // we have to recheck iOneTab here (instead of just saying "else") // because iOneTab will be set if we've run out of tab stops if (iOneTab) { if (iOneTab < 0) { // calc next available right justified tab stop xTab = x + textextent.cx - iTabOrigin; xTab = ((xTab / iOneTab) * iOneTab) - iOneTab - textextent.cx + iTabOrigin; } else { // calc next available left justified tab stop xTab = x - iTabOrigin; xTab = ((xTab / iOneTab) * iOneTab) + iOneTab + iTabOrigin; } x = xTab; } } if (fDrawTheText) { /* * Output all text up to the tab (or end of string) and get its * extent. */ rc.right = x + textextent.cx; ExtTextOutW( hdc, x, y, uOpaque, &rc, (LPWSTR)lpstring, cch, NULL); rc.left = rc.right; } // Skip over the tab and the characters we just drew. x += textextent.cx; // Skip over the characters we just drew. lpstring += cch; // See if we have more to draw OR see if this string ends in // a tab character that needs to be drawn. if((nCount > 0) || ((nCount == 0) && (*lpstring == TEXT('\t')))) { lpstring++; // Skip over the tab continue; } else break; // Break from the loop. } return MAKELONG((x - initialx), (short)textextent.cy); } /***************************************************************************\ * TabbedTextOutW * * effects: Outputs the tabbed text and returns the * textextent of the tabbed text. * * nCount Count of bytes in string * nTabPositions Count of tabstops in tabstop array * lpintTabStopPositions Tab stop positions in pixels * iTabOrigin Tab stops are with respect to this * * History: * 19-Jan-1993 mikeke Client side \***************************************************************************/ LONG TabbedTextOutW( HDC hdc, int x, int y, LPCWSTR lpstring, int cchChars, int nTabPositions, CONST INT *lpintTabStopPositions, int iTabOrigin) { return TabTextOut(hdc, x, y, lpstring, cchChars, nTabPositions, lpintTabStopPositions, iTabOrigin, TRUE, -1); } /***************************************************************************\ * TabbedTextOutA (API) * * History: * 30-11-92 mikeke Created \***************************************************************************/ LONG TabbedTextOutA( HDC hdc, int x, int y, LPCSTR pString, int chCount, int nTabPositions, CONST INT *pnTabStopPositions, int nTabOrigin) { LPWSTR lpwstr; BOOL bRet; WORD wCodePage = (WORD)GdiGetCodePage(hdc); int iUniString; if (chCount == -1) { chCount = USER_AWCONV_COUNTSTRINGSZ; } if ((iUniString = MBToWCSEx(wCodePage, pString, chCount, &lpwstr, -1, TRUE)) == 0) { if (chCount == USER_AWCONV_COUNTSTRINGSZ) { lpwstr = (LPWSTR)gwszNullStr; } else { return FALSE; } } bRet = TabTextOut( hdc, x, y, lpwstr, iUniString, nTabPositions, pnTabStopPositions, nTabOrigin, TRUE, GetTextCharset(hdc)); if (lpwstr != gwszNullStr) { UserLocalFree((HANDLE)lpwstr); } return bRet; } DWORD GetTabbedTextExtentW( HDC hdc, LPCWSTR pString, int chCount, int nTabPositions, CONST INT *pnTabStopPositions) { return TabTextOut(hdc, 0, 0, pString, chCount, nTabPositions, pnTabStopPositions, 0, FALSE, -1); } DWORD GetTabbedTextExtentA( HDC hdc, LPCSTR pString, int chCount, int nTabPositions, CONST INT *pnTabStopPositions) { LPWSTR lpwstr; BOOL bRet; WORD wCodePage = (WORD)GdiGetCodePage(hdc); int iUniString; if (chCount == -1) { chCount = USER_AWCONV_COUNTSTRINGSZ; } if ((iUniString = MBToWCSEx(wCodePage, pString, chCount, &lpwstr, -1, TRUE)) == 0) { if (chCount == USER_AWCONV_COUNTSTRINGSZ) { lpwstr = (LPWSTR)gwszNullStr; } else { return FALSE; } } bRet = TabTextOut(hdc, 0, 0, lpwstr, iUniString, nTabPositions, pnTabStopPositions, 0, FALSE, GetTextCharset(hdc)); if (lpwstr != gwszNullStr) { UserLocalFree((HANDLE)lpwstr); } return bRet; } /***************************************************************************\ * PSMTextOut * * Outputs the text and puts and _ below the character with an & * before it. Note that this routine isn't used for menus since menus * have their own special one so that it is specialized and faster... * * History: * 11-13-90 JimA Ported to NT. * 30-Nov-1992 mikeke Client side version * 7-Apr-1998 MCostea Added dwFlags \***************************************************************************/ void PSMTextOut( HDC hdc, int xLeft, int yTop, LPWSTR lpsz, int cch, DWORD dwFlags) { /* * By default this is just a call to UserLpkPSMTextOut. If an * LPK is installed, this calls out to the LPK. The LPK calls * UserLpkPSMTextOut, if necessary. */ (*fpLpkPSMTextOut)(hdc, xLeft, yTop, lpsz, cch, dwFlags); return; } /***************************************************************************\ * UserLpkPSMTextOut * * NOTE: A very similar routine (xxxPSMTextOut) exists on the kernel * side in text.c. Any changes to this routine most likely need * to be made in xxxPSMTextOut as well. * \***************************************************************************/ FUNCLOGVOID6(LOG_GENERAL, DUMMYCALLINGTYPE, UserLpkPSMTextOut, HDC, hdc, int, xLeft, int, yTop, LPWSTR, lpsz, int, cch, DWORD, dwFlags) void UserLpkPSMTextOut( HDC hdc, int xLeft, int yTop, LPWSTR lpsz, int cch, DWORD dwFlags) { int cx; LONG textsize, result; WCHAR achWorkBuffer[255]; WCHAR *pchOut = achWorkBuffer; TEXTMETRICW textMetric; SIZE size; RECT rc; COLORREF color; if (cch > sizeof(achWorkBuffer)/sizeof(WCHAR)) { pchOut = (WCHAR*)UserLocalAlloc(HEAP_ZERO_MEMORY, (cch+1) * sizeof(WCHAR)); if (pchOut == NULL) return; } result = GetPrefixCount(lpsz, cch, pchOut, cch); /* * DT_PREFIXONLY is a new 5.0 option used when switching from keyboard cues off * to on. */ if (!(dwFlags & DT_PREFIXONLY)) { TextOutW(hdc, xLeft, yTop, pchOut, cch - HIWORD(result)); } /* * Any true prefix characters to underline? */ if (LOWORD(result) == 0xFFFF || dwFlags & DT_HIDEPREFIX) { if (pchOut != achWorkBuffer) UserLocalFree(pchOut); return; } if (!GetTextMetricsW(hdc, &textMetric)) { textMetric.tmOverhang = 0; textMetric.tmAscent = 0; } /* * For proportional fonts, find starting point of underline. */ if (LOWORD(result) != 0) { /* * How far in does underline start (if not at 0th byte.). */ GetTextExtentPointW(hdc, pchOut, LOWORD(result), &size); xLeft += size.cx; /* * Adjust starting point of underline if not at first char and there is * an overhang. (Italics or bold fonts.) */ xLeft = xLeft - textMetric.tmOverhang; } /* * Adjust for proportional font when setting the length of the underline and * height of text. */ GetTextExtentPointW(hdc, pchOut + LOWORD(result), 1, &size); textsize = size.cx; /* * Find the width of the underline character. Just subtract out the overhang * divided by two so that we look better with italic fonts. This is not * going to effect embolded fonts since their overhang is 1. */ cx = LOWORD(textsize) - textMetric.tmOverhang / 2; /* * Get height of text so that underline is at bottom. */ yTop += textMetric.tmAscent + 1; /* * Draw the underline using the foreground color. */ SetRect(&rc, xLeft, yTop, xLeft+cx, yTop+1); color = SetBkColor(hdc, GetTextColor(hdc)); ExtTextOutW(hdc, xLeft, yTop, ETO_OPAQUE, &rc, TEXT(""), 0, NULL); SetBkColor(hdc, color); if (pchOut != achWorkBuffer) { UserLocalFree(pchOut); } }