<<diff40.txt>>
Bill Medland (medbi01@accpac.com) Major rewrite of the DrawText functions which should provide a reasonable base for further development. It includes improved handling of tabs and ellipses. Index: wine/dlls/user/text.c =================================================================== RCS file: /home/wine/wine/dlls/user/text.c,v retrieving revision 1.16 diff -u -r1.16 text.c --- wine/dlls/user/text.c 2002/01/04 21:26:56 1.16 +++ wine/dlls/user/text.c 2002/01/07 19:54:46 @@ -6,6 +6,7 @@ */ #include <string.h> +#include <assert.h> #include "windef.h" #include "wingdi.h" @@ -25,249 +26,706 @@ #define SPACE 32 #define PREFIX 38 -#define ELLIPSIS "..." #define FORWARD_SLASH '/' #define BACK_SLASH '\\' -static const WCHAR SPACEW[] = {' ', 0}; -static const WCHAR oW[] = {'o', 0}; static const WCHAR ELLIPSISW[] = {'.','.','.', 0}; -static const WCHAR FORWARD_SLASHW[] = {'/', 0}; -static const WCHAR BACK_SLASHW[] = {'\\', 0}; -#define SWAP_INT(a,b) { int t = a; a = b; b = t; } +#define countof(a) (sizeof(a)/sizeof(a[0])) -static int tabstop = 8; -static int tabwidth; -static int spacewidth; -static int prefix_offset; +/* A structure to describe a segment or portion of a line, to simplify handling + * of tabs. This ensures that the we don't keep parsing the line. + */ +typedef struct tab_sDrawTextLineSegment +{ + WCHAR *str; // Where the segment is stored + int nChar; // The number of characters in the segment + int xStart; // The offset of the string in logical coordinates + // from the line's origin +} sDrawTextLineSegment; -/* ### start build ### */ -extern WORD CALLBACK TEXT_CallTo16_word_wlw(GRAYSTRINGPROC16,WORD,LONG,WORD); -/* ### stop build ### */ +/* A structure to pass information between the functions of the DrawText + * processing. in/out refers to passing to the "next line" function. + */ +typedef struct tab_sDrawTextLine +{ + HDC hdc; // [in] The handle to the Device Context to use + int tabwidth; // [in] The width in logical units of a tab column + int max_width; // [in] Maximum width in logical coordinates + const WCHAR *str; // [in/out] The pointer to the remainder of the string + // to be processed. + int count; // [in/out] The number of characters remaining in the + // string. (Strictly, the number of WCHAR's + // which, according to MSDN, may include some + // Unicode surrogates in two WCHARs) + UINT format; // [in] The format flags bitset passed to the DrawText + // (or similar) function + int last_line; // [in] Set TRUE if this is the last line for which + // there is room as far as ellipsification is + // concerned. Note that this may be FALSE and yet + // no further line will be output. + int buflen; // [in] The number of WCHARs in buffer + WCHAR *buffer; // [in*out] A buffer provided by the caller into which + // the callee transfers the string, possibly + // including modifications. + sDrawTextLineSegment *seg; // [in*out] An array to receive the line + // segments + int max_segs; // [in] The number of entries in the seg[] array + int num_segs; // [out] The number of entries used in the seg[] array + int prefix_segment; // [out] The segment number containing the prefixed + // character to be underlined; < 0 if none + int prefix_offset; // [out] Which character of the segment (see prefix_ + // segment) should be underlined. + SIZE size; // [out] Actual size of the line (for centring etc) + int total_chars; // [out] Total of the seg->nChars; +} sDrawTextLine; -struct gray_string_info +/********************************************************************* + * TEXT_Ellipsify (static) + * + * Add an ellipsis to the end of the given string whilst ensuring it fits. + * + * If the ellipsis alone doesn't fit then it will be returned anyway. + * + * Arguments + * hdc [in] The handle to the DC that defines the font. + * str [in/out] The string that needs to be modified + * max_str [in] The dimension of str (number of WCHAR). + * len_str [in/out] The number of characters in str + * width [in] The maximum width permitted (in logical coordinates) + * size [out] The dimensions of the text + * + * See for example Microsoft article Q249678. + * + * For now we will simply use three dots rather than worrying about whether + * the font contains an explicit ellipsis character. + */ +static void TEXT_Ellipsify (HDC hdc, WCHAR *str, unsigned int max_len, + unsigned int *len_str, int width, SIZE *size) { - GRAYSTRINGPROC16 proc; - LPARAM param; -}; + unsigned int len_ellipsis; -/* callback for 16-bit gray string proc */ -static BOOL CALLBACK gray_string_callback( HDC hdc, LPARAM param, INT len ) + len_ellipsis = strlenW (ELLIPSISW); + if (len_ellipsis > max_len) len_ellipsis = max_len; + if (*len_str > max_len - len_ellipsis) + *len_str = max_len - len_ellipsis; + + for ( ; ; ) + { + strncpyW (str + *len_str, ELLIPSISW, len_ellipsis); + + if (!GetTextExtentExPointW (hdc, str, *len_str + len_ellipsis, width, + NULL, NULL, size)) break; + + if (!*len_str || size->cx <= width) break; + + (*len_str)--; + } + *len_str += len_ellipsis; +} + +/********************************************************************* + * TEXT_PathElllipsify (static) + * + * Add an ellipsis to the provided string in order to make it fit within + * the width. The ellipsis is added as specified for the DT_PATH_ELLIPSIS + * flag. + * + * See Also TEXT_Ellipsify + * + * Arguments + * hdc [in] The handle to the DC that defines the font. + * str [in/out] The string that needs to be modified + * max_str [in] The dimension of str (number of WCHAR). + * len_str [in/out] The number of characters in str + * width [in] The maximum width permitted (in logical coordinates) + * size [out] The dimensions of the text + * + * For now we will simply use three dots rather than worrying about whether + * the font contains an explicit ellipsis character. + * + * The resulting string consists of as much as possible of the following: + * 1. The ellipsis itself + * 2. The last \ or / of the string (if any) + * 3. Everything after the last \ or / of the string (if any) or the whole + * string if there is no / or \. + * 4. All the stuff before the / or \, which is placed before the ellipsis. + */ +static void TEXT_PathEllipsify (HDC hdc, WCHAR *str, unsigned int max_len, + unsigned int *len_str, int width, SIZE *size) { - const struct gray_string_info *info = (struct gray_string_info *)param; - return TEXT_CallTo16_word_wlw( info->proc, hdc, info->param, len ); + int len_ellipsis; + int len_trailing; + WCHAR *lastBkSlash, *lastFwdSlash, *lastSlash; + + len_ellipsis = strlenW (ELLIPSISW); + if (!max_len) return; + if (len_ellipsis >= max_len) len_ellipsis = max_len - 1; + if (*len_str + len_ellipsis >= max_len) + *len_str = max_len - len_ellipsis-1; + /* Hopefully this will never happen, otherwise it would probably lose + * the wrong character + */ + str[*len_str] = '\0'; /* to simplify things */ + + lastBkSlash = strrchrW (str, BACK_SLASH); + lastFwdSlash = strrchrW (str, FORWARD_SLASH); + lastSlash = lastBkSlash > lastFwdSlash ? lastBkSlash : lastFwdSlash; + if (!lastSlash) lastSlash = str; + len_trailing = *len_str - (lastSlash - str); + + /* overlap-safe movement to the right */ + memmove (lastSlash+len_ellipsis, lastSlash, len_trailing * sizeof(WCHAR)); + strncpyW (lastSlash, ELLIPSISW, len_ellipsis); + len_trailing += len_ellipsis; + /* From this point on lastSlash actually points to the ellipsis in front + * of the last slash and len_trailing includes the ellipsis + */ + + for ( ; ; ) + { + if (!GetTextExtentExPointW (hdc, str, *len_str + len_ellipsis, width, + NULL, NULL, size)) break; + + if (lastSlash == str || size->cx <= width) break; + + /* overlap-safe movement to the left */ + memmove (lastSlash-1, lastSlash, len_trailing * sizeof(WCHAR)); + lastSlash--; + + assert (*len_str); + (*len_str)--; + } + *len_str += len_ellipsis; } +/********************************************************************* + * TEXT_WordBreak (static) + * + * Perform wordbreak processing on the given string + * + * Assumes that DT_WORDBREAK has been specified and not all the characters + * fit. + * + * Note that the Windows processing has some strange properties. In particular + * leading and trailing spaces are not stripped except for the first space of a + * line created by a wordbreak. + * + * Arguments + * hdc [in] The handle to the DC that defines the font. + * str [in/out] The string that needs to be broken. + * max_str [in] The dimension of str (number of WCHAR). + * len_str [in/out] The number of characters in str + * width [in] The maximum width permitted + * format [in] The format flags in effect + * chars_fit [in] The maximum number of characters of str that are already + * known to fit; chars_fit+1 is known not to fit. + * chars_used [out] The number of characters of str that have been "used" and + * do not need to be included in later text. For example this will + * include any spaces that have been discarded from the start of + * the next line. + * size [out] The size of the returned text in logical coordinates + * + * Pedantic assumption - Assumes that the text length is montonically increasing + * with number of characters (i.e. no weird kernings) + * + * Algorithm + * + * Work back from the last character that did fit to either a space or the last + * character of a word, whichever is met first. + * If there was one or the first character didn't fit then + * break the line after that character + * and if the next character is a space then discard it. + * Suppose there was none (and the first character did fit). + * If Break Within Word is permitted + * break the word after the last character that fits (there must be + * at least one; none is caught earlier). + * Otherwise + * discard any trailing space. + * include the whole word; it may be ellipsified later + * + * Break Within Word is permitted under a set of circumstances that are not + * totally clear yet. Currently our best guess is: + * If DT_EDITCONTROL is in effect and neither DT_WORD_ELLIPSIS nor + * DT_PATH_ELLIPSIS is + */ +static void TEXT_WordBreak (HDC hdc, WCHAR *str, unsigned int max_str, + unsigned int *len_str, + int width, int format, unsigned int chars_fit, + unsigned int *chars_used, SIZE *size) +{ + WCHAR *p; + int word_fits; + assert (format & DT_WORDBREAK); + assert (chars_fit < *len_str); + + /* Work back from the last character that did fit to either a space or the + * last character of a word, whichever is met first. + */ + p = str + chars_fit; + word_fits = TRUE; + if (!chars_fit) + ; /* we pretend that it fits anyway */ + else if (*p == SPACE) /* chars_fit < *len_str so this is valid */ + p--; /* the word just fitted */ + else + { + while (p > str && *(--p) != SPACE) + ; + word_fits = (p != str || *p == SPACE); + } + /* If there was one or the first character didn't fit then */ + if (word_fits) + { + /* break the line after that character */ + p++; + *len_str = p - str; + /* and if the next character is a space then discard it. */ + *chars_used = *len_str; + if (*p == SPACE) + (*chars_used)++; + } + /* Suppose there was none. */ + else + { + /* If DT_EDITCONTROL is in effect and DT_WORD_ELLIPSIS is not then */ + if ((format & (DT_EDITCONTROL | DT_WORD_ELLIPSIS | DT_PATH_ELLIPSIS)) == + DT_EDITCONTROL) + { + /* break the word after the last character that fits (there must be + * at least one; none is caught earlier). + */ + *len_str = chars_fit; + *chars_used = chars_fit; + + /* FIXME - possible error. Since the next character is now removed + * this could make the text longer so that it no longer fits, and + * so we need a loop to test and shrink. + */ + } + /* Otherwise */ + else + { + /* discard any trailing space. */ + const WCHAR *e = str + *len_str; + p = str + chars_fit; + while (p < e && *p != SPACE) + p++; + *chars_used = p - str; + if (p < e) /* i.e. loop failed because *p == SPACE */ + (*chars_used)++; + + /* include the whole word; it may be ellipsified later */ + *len_str = p - str; + /* Possible optimisation; if DT_WORD_ELLIPSIS only use chars_fit+1 + * so that it will be too long + */ + } + } + /* Remeasure the string */ + GetTextExtentExPointW (hdc, str, *len_str, 0, NULL, NULL, size); +} + +/********************************************************************* + * TEXT_SkipChars + * + * Skip over the given number of characters, bearing in mind prefix + * substitution and the fact that a character may take more than one + * WCHAR (Unicode surrogates are two words long) (and there may have been + * a trailing &) + * + * Parameters + * new_str [out] The updated pointer + * new_count [out] The updated count + * start_count [in] The count of remaining characters corresponding to the + * start of the string + * start_str [in] The starting point of the string + * max [in] The number of characters actually in this segment of the + * string (the & counts) + * n [in] The number of characters to skip (if prefix then + * &c counts as one) + * prefix [in] Apply prefix substitution + * + * Return Values + * none + * + * Remarks + * There must be at least n characters in the string + * We need max because the "line" may have ended with a & followed by a tab + * or newline etc. which we don't want to swallow + */ + +static void TEXT_SkipChars (const WCHAR **new_str, int *new_count, + int start_count, const WCHAR *start_str, + int max, int n, int prefix) +{ + /* This is specific to wide characters, MSDN doesn't say anything much + * about Unicode surrogates yet and it isn't clear if _wcsinc will + * correctly handle them so we'll just do this the easy way for now + */ + + if (prefix) + { + const WCHAR *str_on_entry = start_str; + assert (max >= n); + max -= n; + while (n--) + if (*start_str++ == PREFIX && max--) + start_str++; + else; + start_count -= (start_str - str_on_entry); + } + else + { + start_str += n; + start_count -= n; + } + *new_str = start_str; + *new_count = start_count; +} + /********************************************************************* + * TEXT_Reprefix + * + * Reanalyse the text to find the prefixed character. This is called when + * wordbreaking or ellipsification has shortened the string such that the + * previously noted prefixed character is no longer visible. + * + * Parameters + * str [in] The original string segment (including all characters) + * n1 [in] The number of characters visible before the path ellipsis + * n2 [in] The number of characters replaced by the path ellipsis + * ne [in] The number of characters in the path ellipsis, ignored if + * n2 is zero + * n3 [in] The number of characters visible after the path ellipsis + * + * Return Values + * The prefix offset within the new string segment (the one that contains the + * ellipses and does not contain the prefix characters) (-1 if none) + * + * Remarks + * We know that n1+n2+n3 must be strictly less than the length of the segment + * (because otherwise there would be no need to call this function) + */ + +static int TEXT_Reprefix (const WCHAR *str, unsigned int n1, unsigned int n2, + unsigned int ne, unsigned int n3) +{ + int result = -1; + unsigned int i = 0; + unsigned int n = n1 + n2 + n3; + if (!n2) ne = 0; + while (i < n) + { + if (i == n1) + { + /* Reached the path ellipsis; jump over it */ + str += n2; + i += n2; + if (!n3) break; /* Nothing after the path ellipsis */ + } + if (*str++ == PREFIX) + { + result = (i < n1) ? i : i - n2 + ne; + str++; + } + else; + i++; + } + return result; +} + +/********************************************************************* + * Returns true if and only if the remainder of the line is a single + * newline representation or nothing + */ + +static int remainder_is_none_or_newline (int num_chars, const WCHAR *str) +{ + if (!num_chars) return TRUE; + if (*str != LF && *str != CR) return FALSE; + if (!--num_chars) return TRUE; + if (*str == *(str+1)) return FALSE; + str++; + if (*str != CR && *str != LF) return FALSE; + if (--num_chars) return FALSE; + return TRUE; +} + +/********************************************************************* + * TEXT_NextLineW + * * Return next line of text from a string. - * - * hdc - handle to DC. - * str - string to parse into lines. - * count - length of str. - * dest - destination in which to return line. - * len - dest buffer size in chars on input, copied length into dest on output. - * width - maximum width of line in pixels. - * format - format type passed to DrawText. - * - * Returns pointer to next char in str after end of the line - * or NULL if end of str reached. - * - * FIXME: - * GetTextExtentPoint is used to get the width of each character, - * rather than GetCharABCWidth... So the whitespace between - * characters is ignored, and the reported len is too great. - */ -static const WCHAR *TEXT_NextLineW( HDC hdc, const WCHAR *str, int *count, - WCHAR *dest, int *len, int width, WORD format) -{ - int i = 0, j = 0, k; - int plen = 0; - int numspaces; + * + * Use GetTextExtentExPoint so that we don't need to think about how to measure + * the text + */ +static void TEXT_NextLineW (sDrawTextLine *line) +{ + WCHAR *pdest; + const WCHAR *pEndBuf; + int x_so_far; + sDrawTextLineSegment *pseg; + int num_chars; SIZE size; - int lasttab = 0; - int wb_i = 0, wb_j = 0, wb_count = 0; - int maxl = *len; + int max_seg_width; + int ellipsified; + int line_fits; + + line->prefix_segment = -1; + line->num_segs = 0; + line->total_chars = 0; + line->size.cx = 0; + line->size.cy = 0; + pdest = line->buffer; + x_so_far = 0; + pseg = line->seg; + pEndBuf = pdest + line->buflen; - while (*count && j < maxl) + /* For each line segment */ + while (line->count) /* and several break statements at the bottom */ { - switch (str[i]) - { - case CR: - case LF: - if (!(format & DT_SINGLELINE)) - { - if ((*count > 1) && (str[i] == CR) && (str[i+1] == LF)) + int len_before_ellipsis; + int prefix_offset; + int word_broken; + int seg_start_count; + const WCHAR *seg_start_str; + + word_broken = FALSE; + /* Skip leading tabs */ + if (line->count && *line->str == TAB && (line->format & DT_EXPANDTABS)) + { + /* optimised to reduce unnecessary division */ + x_so_far = ((x_so_far/line->tabwidth)+1)*line->tabwidth; /* Skip TO tab */ + line->count--, line->str++; + while (line->count && *line->str == TAB) + { + line->count--, line->str++; + x_so_far += line->tabwidth; /* Skip BY tab */ + } + } + + seg_start_count = line->count; + seg_start_str = line->str; + + /* Set up the segment */ + if (line->num_segs >= line->max_segs) + { + FIXME ("Need to handle more line segments\n"); + break; + } + pseg->str = pdest; + pseg->xStart = x_so_far; + prefix_offset = -1; + + /* Copy the string to the buffer with prefix substitution */ + while (line->count) + { + if (*line->str == TAB && (line->format & DT_EXPANDTABS)) + break; + else if ((*line->str == CR || *line->str == LF) && !(line->format & DT_SINGLELINE)) + break; + else + { + --line->count; + if (*line->str == PREFIX && !(line->format & DT_NOPREFIX)) { - (*count)--; - i++; + line->str++; + prefix_offset = pdest - pseg->str; + /* Win95. If there is no next char or it is an effective + * CR, LF or TAB then it still overrides the previous one. + */ } - i++; - *len = j; - (*count)--; - return (&str[i]); - } - dest[j++] = str[i++]; - if (!(format & DT_NOCLIP) || !(format & DT_NOPREFIX) || - (format & DT_WORDBREAK)) - { - if (!GetTextExtentPointW(hdc, &dest[j-1], 1, &size)) - return NULL; - plen += size.cx; - } - break; - - case PREFIX: - if (!(format & DT_NOPREFIX) && *count > 1) + else if (pdest < pEndBuf) + *pdest++ = *line->str++; + else { - if (str[++i] == PREFIX) - (*count)--; - else { - prefix_offset = j; - break; + FIXME ("Buffer overflow\n"); + return; } - } - dest[j++] = str[i++]; - if (!(format & DT_NOCLIP) || !(format & DT_NOPREFIX) || - (format & DT_WORDBREAK)) - { - if (!GetTextExtentPointW(hdc, &dest[j-1], 1, &size)) - return NULL; - plen += size.cx; } - break; - - case TAB: - if (format & DT_EXPANDTABS) - { - wb_i = ++i; - wb_j = j; - wb_count = *count; - - if (!GetTextExtentPointW(hdc, &dest[lasttab], j - lasttab, &size)) - return NULL; - - numspaces = (tabwidth - size.cx) / spacewidth; - for (k = 0; k < numspaces; k++) - dest[j++] = SPACE; - plen += tabwidth - size.cx; - lasttab = wb_j + numspaces; - } - else - { - dest[j++] = str[i++]; - if (!(format & DT_NOCLIP) || !(format & DT_NOPREFIX) || - (format & DT_WORDBREAK)) - { - if (!GetTextExtentPointW(hdc, &dest[j-1], 1, &size)) - return NULL; - plen += size.cx; - } - } - break; - - case SPACE: - dest[j++] = str[i++]; - if (!(format & DT_NOCLIP) || !(format & DT_NOPREFIX) || - (format & DT_WORDBREAK)) - { - wb_i = i; - wb_j = j - 1; - wb_count = *count; - if (!GetTextExtentPointW(hdc, &dest[j-1], 1, &size)) - return NULL; - plen += size.cx; - } - break; - - default: - dest[j++] = str[i++]; - if (!(format & DT_NOCLIP) || !(format & DT_NOPREFIX) || - (format & DT_WORDBREAK)) - { - if (!GetTextExtentPointW(hdc, &dest[j-1], 1, &size)) - return NULL; - plen += size.cx; - } - } - - (*count)--; - if (!(format & DT_NOCLIP) || (format & DT_WORDBREAK)) - { - if (plen > width) - { - if (format & DT_WORDBREAK) - { - if (wb_j) - { - *len = wb_j; - *count = wb_count - 1; - return (&str[wb_i]); - } - } - else - { - *len = j; - return (&str[i]); - } - } - } + } + /* Beware. pprefix_offset may be beyond the string */ + pseg->nChar = pdest - pseg->str; + + /* What if nChars is zero */ + /* Measure the segment */ + max_seg_width = line->max_width - x_so_far; + GetTextExtentExPointW (line->hdc, pseg->str, pseg->nChar, max_seg_width, + &num_chars, NULL, &size); + + /* Perform word break and ellipsification */ + + /* The Microsoft handling of various combinations of formats is wierd. + * The following may very easily be incorrect if several formats are + * combined. + */ + ellipsified = FALSE; + line_fits = (num_chars >= pseg->nChar); + if ((line->format & DT_WORDBREAK) && !line_fits) + { + int chars_used; + TEXT_WordBreak (line->hdc, pseg->str, pEndBuf-pseg->str, + &pseg->nChar, max_seg_width, line->format, + num_chars, &chars_used, &size); + line_fits = (size.cx <= max_seg_width); + /* So now we need to recorrect the line->str etc. and prefix */ + pdest = pseg->str + pseg->nChar; + TEXT_SkipChars (&line->str, &line->count, + seg_start_count, seg_start_str, + line->str - seg_start_str, chars_used, + !(line->format & DT_NOPREFIX)); + /* potential optimisation; only if there were any PREFIX */ + + word_broken = TRUE; + } + if ((line->format & DT_PATH_ELLIPSIS) && !line_fits) + { + TEXT_PathEllipsify (line->hdc, pseg->str, pEndBuf-pseg->str, + &pseg->nChar, max_seg_width, &size); + line_fits = (size.cx <= max_seg_width); + } + if ((line->format & DT_WORD_ELLIPSIS) && !line_fits) + { + ellipsified = TRUE; + TEXT_Ellipsify (line->hdc, pseg->str, pEndBuf-pseg->str, + &pseg->nChar, max_seg_width, &size); + line_fits = (size.cx <= max_seg_width); + } + /* NB we may end up ellipsifying a word-broken string */ + if ((line->format & DT_END_ELLIPSIS) && !ellipsified && + ((line->last_line && line->count) || + (remainder_is_none_or_newline (line->count, line->str) && + !line_fits))) + + { + ellipsified = TRUE; + TEXT_Ellipsify (line->hdc, pseg->str, pEndBuf-pseg->str, + &pseg->nChar, max_seg_width, &size); + } + len_before_ellipsis = pseg->nChar; + if (ellipsified) len_before_ellipsis -= strlenW (ELLIPSISW); + if (prefix_offset >= len_before_ellipsis) + prefix_offset = TEXT_Reprefix (seg_start_str, len_before_ellipsis, 0, 3, 0); + + x_so_far += size.cx; + line->total_chars += pseg->nChar; + if (size.cy > line->size.cy) line->size.cy = size.cy; + + if (prefix_offset >= 0) + { + line->prefix_segment = line->num_segs; + line->prefix_offset = prefix_offset; + } + + /* See if that was the last segment of the line */ + line->num_segs++; + pseg++; + + if (word_broken) + break; + else if (!line->count) + break; /* end of the input string */ + else if (*line->str == CR) + { + line->count--, line->str++; + if (line->count && *line->str == LF) + line->count--, line->str++; + break; + } + else if (*line->str == LF) + { + line->count--, line->str++; + if (line->count && *line->str == CR) + line->count--, line->str++; + break; + } + /* Else it was a Tab and we go around again */ } - - *len = j; - return NULL; + line->size.cx = x_so_far; } /*********************************************************************** - * DrawText (USER.85) + * TEXT_DrawUnderscore + * + * Draw the underline under the prefixed character + * + * Parameters + * hdc [in] The handle of the DC for drawing + * x [in] The x location of the line segment (logical coordinates) + * y [in] The y location of where the underscore should appear + * (logical coordinates) + * str [in] The text of the line segment + * offset [in] The offset of the underscored character within str */ -INT16 WINAPI DrawText16( HDC16 hdc, LPCSTR str, INT16 count, LPRECT16 rect, UINT16 flags ) + +static void TEXT_DrawUnderscore (HDC hdc, int x, int y, const WCHAR *str, int offset) { - INT16 ret; + int prefix_x; + int prefix_end; + SIZE size; + HPEN hpen; + HPEN oldPen; - if (rect) - { - RECT rect32; - CONV_RECT16TO32( rect, &rect32 ); - ret = DrawTextA( hdc, str, count, &rect32, flags ); - CONV_RECT32TO16( &rect32, rect ); - } - else ret = DrawTextA( hdc, str, count, NULL, flags); - return ret; + GetTextExtentPointW (hdc, str, offset, &size); + prefix_x = x + size.cx; + GetTextExtentPointW (hdc, str, offset+1, &size); + prefix_end = x + size.cx - 1; + /* The above method may eventually be slightly wrong due to kerning etc. */ + + hpen = CreatePen (PS_SOLID, 1, GetTextColor (hdc)); + oldPen = SelectObject (hdc, hpen); + MoveToEx (hdc, prefix_x, y, NULL); + LineTo (hdc, prefix_end, y); + SelectObject (hdc, oldPen); + DeleteObject (hpen); } - /*********************************************************************** - * DrawTextExW (USER32.@) + * TEXT_DrawTextW + * + * The core of the DrawText/DrawTextEx functions. + * + * Parameters + * + * The parameters are as for DrawText or DrawTextEx, except for fEx. + * + * fEx 0 if called from one of the DrawText functions and non-0 if called from + * one of the DrawTextEx functions. */ #define MAX_STATIC_BUFFER 1024 -INT WINAPI DrawTextExW( HDC hdc, LPWSTR str, INT i_count, - LPRECT rect, UINT flags, LPDRAWTEXTPARAMS dtp ) +#define MAX_LINE_SEGS 256 +static INT WINAPI TEXT_DrawTextW (HDC hdc, LPWSTR str, INT count, + LPRECT rect, UINT flags, LPDRAWTEXTPARAMS dtp, + int fEx) { - SIZE size; - const WCHAR *strPtr; - static WCHAR line[MAX_STATIC_BUFFER]; - int len, lh, count=i_count; - int prefix_x = 0; - int prefix_end = 0; + /* Why are we using static buffers anyway? */ + static WCHAR line_buffer[MAX_STATIC_BUFFER]; + static sDrawTextLineSegment seg_buffer [MAX_LINE_SEGS]; + int lh; TEXTMETRICW tm; int lmargin = 0, rmargin = 0; int x = rect->left, y = rect->top; - int width = rect->right - rect->left; int max_width = 0; + sDrawTextLine line; TRACE("%s, %d , [(%d,%d),(%d,%d)]\n", debugstr_wn (str, count), count, rect->left, rect->top, rect->right, rect->bottom); - if (dtp) TRACE("Params: iTabLength=%d, iLeftMargin=%d, iRightMargin=%d\n", + if (dtp) TRACE("Params: iTabLength=%d, iLeftMargin=%d, iRightMargin=%d\n", dtp->iTabLength, dtp->iLeftMargin, dtp->iRightMargin); + /* if (flags & (DT_PREFIXONLY | DT_HIDEPREFIX | DT_NOFULLWIDTHCHARBREAK ) + FIXME ("Using new DT_* settings\n"); */ + if (!str) return 0; if (count == -1) count = strlenW(str); if (count == 0) return 0; - strPtr = str; + + if (flags & DT_SINGLELINE) + flags &= ~DT_WORDBREAK; GetTextMetricsW(hdc, &tm); if (flags & DT_EXTERNALLEADING) @@ -282,165 +740,94 @@ if (!(flags & (DT_CENTER | DT_RIGHT))) x += lmargin; dtp->uiLengthDrawn = 0; /* This param RECEIVES number of chars processed */ + /* Should rmargin modify the rectangle width ? */ } - if (flags & DT_TABSTOP) - tabstop = dtp ? dtp->iTabLength : flags >> 8; - - if (flags & DT_EXPANDTABS) + /* Tab settings */ { - GetTextExtentPointW(hdc, SPACEW, 1, &size); - spacewidth = size.cx; - GetTextExtentPointW(hdc, oW, 1, &size); - tabwidth = size.cx * tabstop; + int tabstop; + tabstop = (flags & DT_TABSTOP) ? + fEx ? + (dtp ? dtp->iTabLength : 8) : + (flags >> 8) & 0xff : + 8; + if ((!fEx) && (flags & DT_TABSTOP)) + flags &= 0xffff00ff; + /* Not yet confirmed that the flags corresponding to bits 8-15 are + * functional in the Ex functions, but I am sure they will be so we + * don't clear them. + */ + + if (flags & DT_EXPANDTABS) + { + line.tabwidth = tm.tmAveCharWidth * tabstop; + } } - if (flags & DT_CALCRECT) flags |= DT_NOCLIP; + if (flags & DT_CALCRECT) flags |= DT_NOCLIP; /* Are you sure? */ - do - { - prefix_offset = -1; - len = MAX_STATIC_BUFFER; - strPtr = TEXT_NextLineW(hdc, strPtr, &count, line, &len, width, flags); + line.hdc = hdc; + line.str = str; + line.max_width = rect->right - rect->left; + line.count = count; + line.format = flags; + line.buffer = line_buffer; + line.buflen = countof(line_buffer); + line.seg = seg_buffer; + line.max_segs = countof(seg_buffer); - if (prefix_offset != -1) - { - GetTextExtentPointW(hdc, line, prefix_offset, &size); - prefix_x = size.cx; - GetTextExtentPointW(hdc, line, prefix_offset + 1, &size); - prefix_end = size.cx - 1; - } - if (!GetTextExtentPointW(hdc, line, len, &size)) return 0; + do /* for each line */ + { + line.last_line = !(flags & DT_NOCLIP) && y + ((flags & DT_EDITCONTROL) ? 2*lh-1 : lh) > rect->bottom; + TEXT_NextLineW(&line); + if (flags & DT_CENTER) x = (rect->left + rect->right - - size.cx) / 2; - else if (flags & DT_RIGHT) x = rect->right - size.cx; + line.size.cx) / 2; + else if (flags & DT_RIGHT) x = rect->right - line.size.cx; if (flags & DT_SINGLELINE) { if (flags & DT_VCENTER) y = rect->top + - (rect->bottom - rect->top) / 2 - size.cy / 2; - else if (flags & DT_BOTTOM) y = rect->bottom - size.cy; + (rect->bottom - rect->top) / 2 - line.size.cy / 2; + else if (flags & DT_BOTTOM) y = rect->bottom - line.size.cy; - if (flags & (DT_PATH_ELLIPSIS | DT_END_ELLIPSIS | DT_WORD_ELLIPSIS)) - { - WCHAR swapStr[sizeof(line)]; - WCHAR* fnameDelim = NULL; - int totalLen = i_count >= 0 ? i_count : strlenW(str); - - if (size.cx > width) - { - int fnameLen = totalLen; - - /* allow room for '...' */ - count = min(totalLen+3, sizeof(line)-3); - - if (flags & DT_WORD_ELLIPSIS) - flags |= DT_WORDBREAK; - - if (flags & DT_PATH_ELLIPSIS) - { - WCHAR* lastBkSlash = NULL; - WCHAR* lastFwdSlash = NULL; - strncpyW(line, str, totalLen); - line[totalLen] = '\0'; - lastBkSlash = strrchrW(line, BACK_SLASHW[0]); - lastFwdSlash = strrchrW(line, FORWARD_SLASHW[0]); - fnameDelim = lastBkSlash > lastFwdSlash ? lastBkSlash : lastFwdSlash; - - if (fnameDelim) - fnameLen = &line[totalLen] - fnameDelim; - else - fnameDelim = (WCHAR*)str; - - strcpyW(swapStr, ELLIPSISW); - strncpyW(swapStr+strlenW(swapStr), fnameDelim, fnameLen); - swapStr[fnameLen+3] = '\0'; - strncpyW(swapStr+strlenW(swapStr), str, totalLen - fnameLen); - swapStr[totalLen+3] = '\0'; - } - else /* DT_END_ELLIPSIS | DT_WORD_ELLIPSIS */ - { - strcpyW(swapStr, ELLIPSISW); - strncpyW(swapStr+strlenW(swapStr), str, totalLen); - } - - len = MAX_STATIC_BUFFER; - TEXT_NextLineW(hdc, swapStr, &count, line, &len, width, flags); - - /* if only the ELLIPSIS will fit, just let it be clipped */ - len = max(3, len); - GetTextExtentPointW(hdc, line, len, &size); - - /* FIXME: - * NextLine uses GetTextExtentPoint for each character, - * rather than GetCharABCWidth... So the whitespace between - * characters is ignored in the width measurement, and the - * reported len is too great. To compensate, we must get - * the width of the entire line and adjust len accordingly. - */ - while ((size.cx > width) && (len > 3)) - { - line[--len] = '\0'; - GetTextExtentPointW(hdc, line, len, &size); - } - - if (fnameLen < len-3) /* some of the path will fit */ - { - /* put the ELLIPSIS between the path and filename */ - strncpyW(swapStr, &line[fnameLen+3], len-3-fnameLen); - swapStr[len-3-fnameLen] = '\0'; - strcatW(swapStr, ELLIPSISW); - strncpyW(swapStr+strlenW(swapStr), &line[3], fnameLen); - } - else - { - /* move the ELLIPSIS to the end */ - strncpyW(swapStr, &line[3], len-3); - swapStr[len-3] = '\0'; - strcpyW(swapStr+strlenW(swapStr), ELLIPSISW); - } - - strncpyW(line, swapStr, len); - line[len] = '\0'; - strPtr = NULL; - } - if (flags & DT_MODIFYSTRING) - strcpyW(str, swapStr); - } } if (!(flags & DT_CALCRECT)) { - if (!ExtTextOutW( hdc, x, y, - ((flags & DT_NOCLIP) ? 0 : ETO_CLIPPED) | - ((flags & DT_RTLREADING) ? ETO_RTLREADING : 0), - rect, line, len, NULL )) return 0; - if (prefix_offset != -1) + sDrawTextLineSegment *seg; + for (seg = line.seg; line.num_segs--; seg++) + { + if (!ExtTextOutW( hdc, x+seg->xStart, y, + ((flags & DT_NOCLIP) ? 0 : ETO_CLIPPED) | + ((flags & DT_RTLREADING) ? ETO_RTLREADING : 0), + rect, seg->str, seg->nChar, NULL )) return 0; + } + if (line.prefix_segment >= 0) { - HPEN hpen = CreatePen( PS_SOLID, 1, GetTextColor(hdc) ); - HPEN oldPen = SelectObject( hdc, hpen ); - MoveToEx(hdc, x + prefix_x, y + tm.tmAscent + 1, NULL ); - LineTo(hdc, x + prefix_end + 1, y + tm.tmAscent + 1 ); - SelectObject( hdc, oldPen ); - DeleteObject( hpen ); + seg = line.seg + line.prefix_segment; + TEXT_DrawUnderscore (hdc, x + seg->xStart, y + tm.tmAscent + 1, + seg->str, line.prefix_offset); } } - else if (size.cx > max_width) - max_width = size.cx; + else if (line.size.cx > max_width) + max_width = line.size.cx; + /* What if DT_EDITCONTROL? */ + if (dtp) + dtp->uiLengthDrawn += line.total_chars; y += lh; - if (strPtr) + if (line.count) { if (!(flags & DT_NOCLIP)) { - if (y > rect->bottom - lh) + if (((flags & DT_EDITCONTROL) ? y + lh - 1 : y) > rect->bottom) break; + /* NB Even with DT_EDITCONTROL the first line is always drawn */ } } - if (dtp) - dtp->uiLengthDrawn += len; } - while (strPtr); + while (line.count); if (flags & DT_CALCRECT) { @@ -448,15 +835,23 @@ rect->bottom = y; if (dtp) rect->right += lmargin + rmargin; + /* Are you sure ? */ } return y - rect->top; } /*********************************************************************** - * DrawTextExA (USER32.@) + * TEXT_DrawTextA + * + * A-W conversion wrapper for TEXT_DrawTextW + * + * Parameters + * + * The parameters are as for TEXT_DrawTextW except that str is in A format */ -INT WINAPI DrawTextExA( HDC hdc, LPSTR str, INT count, - LPRECT rect, UINT flags, LPDRAWTEXTPARAMS dtp ) +static INT WINAPI TEXT_DrawTextA( HDC hdc, LPSTR str, int count, + LPRECT rect, UINT flags, LPDRAWTEXTPARAMS dtp, + int fEx) { WCHAR *wstr; INT ret = 0; @@ -469,7 +864,7 @@ if (wstr) { MultiByteToWideChar( CP_ACP, 0, str, count, wstr, wcount ); - ret = DrawTextExW( hdc, wstr, wcount, rect, flags, NULL ); + ret = TEXT_DrawTextW( hdc, wstr, wcount, rect, flags, dtp, fEx ); if (flags & DT_MODIFYSTRING) WideCharToMultiByte( CP_ACP, 0, wstr, -1, str, count, NULL, NULL ); HeapFree(GetProcessHeap(), 0, wstr); @@ -478,11 +873,29 @@ } /*********************************************************************** + * DrawTextExW (USER32.@) + */ +INT WINAPI DrawTextExW( HDC hdc, LPWSTR str, INT count, + LPRECT rect, UINT flags, LPDRAWTEXTPARAMS dtp ) +{ + return TEXT_DrawTextW( hdc, str, count, rect, flags, dtp, 1 ); +} + +/*********************************************************************** + * DrawTextExA (USER32.@) + */ +INT WINAPI DrawTextExA( HDC hdc, LPSTR str, INT count, + LPRECT rect, UINT flags, LPDRAWTEXTPARAMS dtp ) +{ + return TEXT_DrawTextA( hdc, str, count, rect, flags, dtp, 1 ); +} + +/*********************************************************************** * DrawTextW (USER32.@) */ INT WINAPI DrawTextW( HDC hdc, LPCWSTR str, INT count, LPRECT rect, UINT flags ) { - return DrawTextExW(hdc, (LPWSTR)str, count, rect, flags, NULL); + return TEXT_DrawTextW( hdc, (LPWSTR)str, count, rect, flags, NULL, 0 ); } /*********************************************************************** @@ -490,10 +903,48 @@ */ INT WINAPI DrawTextA( HDC hdc, LPCSTR str, INT count, LPRECT rect, UINT flags ) { - return DrawTextExA( hdc, (LPSTR)str, count, rect, flags, NULL ); + return TEXT_DrawTextA( hdc, (LPSTR)str, count, rect, flags, NULL, 0 ); } /*********************************************************************** + * DrawText (USER.85) + */ +INT16 WINAPI DrawText16( HDC16 hdc, LPCSTR str, INT16 count, LPRECT16 rect, UINT16 flags ) +{ + INT16 ret; + + if (rect) + { + RECT rect32; + CONV_RECT16TO32( rect, &rect32 ); + ret = TEXT_DrawTextA( hdc, (LPSTR)str, count, &rect32, flags, NULL, 0 ); + CONV_RECT32TO16( &rect32, rect ); + } + else ret = TEXT_DrawTextA( hdc, (LPSTR)str, count, NULL, flags, NULL, 0 ); + return ret; +} + +/*----------------------------------------------------------------------------*/ + +/* ### start build ### */ +extern WORD CALLBACK TEXT_CallTo16_word_wlw(GRAYSTRINGPROC16,WORD,LONG,WORD); +/* ### stop build ### */ + +struct gray_string_info +{ + GRAYSTRINGPROC16 proc; + LPARAM param; +}; + +/* callback for 16-bit gray string proc */ +static BOOL CALLBACK gray_string_callback( HDC hdc, LPARAM param, INT len ) +{ + const struct gray_string_info *info = (struct gray_string_info *)param; + return TEXT_CallTo16_word_wlw( info->proc, hdc, info->param, len ); +} + + +/*********************************************************************** * TEXT_GrayString * * FIXME: The call to 16-bit code only works because the wine GDI is a 16-bit