DrawText rewrite

Medland, Bill Bill.Medland at accpac.com
Mon Jan 7 15:35:47 CST 2002


 <<diff40.txt>> 
-------------- next part --------------
Bill Medland (medbi01 at 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


More information about the wine-patches mailing list