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