Change the way that DrawText parses the next line to display, ready to
support multiline ellipsification etc. Rather than measuring the text each time we add a character and breaking once it is too long and WORDBREAK is enabled, we copy a whole text segment and then measure it; GetTextExtentPointEx is designed to tell us how much fitted. This may result in a little rescanning if wordbreak is enabled but will be well worth while when multiline path ellipsification is brought down into the NextLine function. Note also that the wordbreak calculation is a little more complete (e.g. including break-within-word for DT_EDITCONTROL).
This commit is contained in:
parent
0e3c9d454b
commit
7af9098f6b
393
dlls/user/text.c
393
dlls/user/text.c
|
@ -269,6 +269,192 @@ void TEXT_CopySansPrefix (int noprefix, WCHAR *d, int *len_d, const WCHAR *s, in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*********************************************************************
|
||||||
|
* 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 this function should be called when the first character that
|
||||||
|
* doesn't fit is a space or tab, so that it can swallow the space(s)
|
||||||
|
*
|
||||||
|
* 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 monotonically
|
||||||
|
* 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; /* The character that doesn't 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 ((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_count [out] The updated count
|
||||||
|
* new_str [out] The updated pointer
|
||||||
|
* 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 (int *new_count, const WCHAR **new_str,
|
||||||
|
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
|
* TEXT_Reprefix
|
||||||
*
|
*
|
||||||
|
@ -330,112 +516,121 @@ static int TEXT_Reprefix (const WCHAR *str, unsigned int n1, unsigned int n2,
|
||||||
* len - dest buffer size in chars on input, copied length into dest on output.
|
* len - dest buffer size in chars on input, copied length into dest on output.
|
||||||
* width - maximum width of line in pixels.
|
* width - maximum width of line in pixels.
|
||||||
* format - format type passed to DrawText.
|
* format - format type passed to DrawText.
|
||||||
|
* retsize - returned size of the line in pixels.
|
||||||
*
|
*
|
||||||
* Returns pointer to next char in str after end of the line
|
* Returns pointer to next char in str after end of the line
|
||||||
* or NULL if end of str reached.
|
* or NULL if end of str reached.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
static const WCHAR *TEXT_NextLineW( HDC hdc, const WCHAR *str, int *count,
|
static const WCHAR *TEXT_NextLineW( HDC hdc, const WCHAR *str, int *count,
|
||||||
WCHAR *dest, int *len, int width, WORD format)
|
WCHAR *dest, int *len, int width, WORD format,
|
||||||
|
SIZE *retsize)
|
||||||
{
|
{
|
||||||
int i = 0, j = 0;
|
int i = 0, j = 0;
|
||||||
int plen = 0;
|
int plen = 0;
|
||||||
int seg_plen; /* plen at the beginning of the current text segment */
|
|
||||||
SIZE size;
|
SIZE size;
|
||||||
int wb_i = 0, wb_j = 0, wb_count = 0;
|
|
||||||
int maxl = *len;
|
int maxl = *len;
|
||||||
int normal_char;
|
int seg_i, seg_count, seg_j;
|
||||||
int seg_j; /* j at the beginning of the current text segment */
|
int max_seg_width;
|
||||||
|
int num_fit;
|
||||||
|
int word_broken;
|
||||||
|
|
||||||
seg_j = j;
|
|
||||||
seg_plen = plen;
|
|
||||||
while (*count && j < maxl)
|
|
||||||
{
|
|
||||||
normal_char = 1;
|
|
||||||
switch (str[i])
|
|
||||||
{
|
|
||||||
case CR:
|
|
||||||
case LF:
|
|
||||||
if (!(format & DT_SINGLELINE))
|
|
||||||
{
|
|
||||||
if ((*count > 1) && (((str[i] == CR) && (str[i+1] == LF)) ||
|
|
||||||
((str[i] == LF) && (str[i+1] == CR))))
|
|
||||||
{
|
|
||||||
(*count)--;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
*len = j;
|
|
||||||
(*count)--;
|
|
||||||
return (&str[i]);
|
|
||||||
}
|
|
||||||
/* else it's just another character */
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PREFIX:
|
/* For each text segment in the line */
|
||||||
if (!(format & DT_NOPREFIX) && *count > 1)
|
|
||||||
{
|
|
||||||
if (str[++i] == PREFIX)
|
|
||||||
(*count)--; /* and treat it as just another character */
|
|
||||||
else {
|
|
||||||
prefix_offset = j;
|
|
||||||
normal_char = 0; /* we are skipping the PREFIX itself */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TAB:
|
retsize->cy = 0;
|
||||||
if (format & DT_EXPANDTABS)
|
while (*count)
|
||||||
{
|
{
|
||||||
if ((format & DT_WORDBREAK))
|
|
||||||
|
/* Skip any leading tabs */
|
||||||
|
|
||||||
|
if (str[i] == TAB && (format & DT_EXPANDTABS))
|
||||||
{
|
{
|
||||||
wb_i = i+1;
|
|
||||||
wb_j = j;
|
|
||||||
wb_count = *count;
|
|
||||||
plen = ((plen/tabwidth)+1)*tabwidth;
|
plen = ((plen/tabwidth)+1)*tabwidth;
|
||||||
seg_plen = plen;
|
(*count)--; if (j < maxl) dest[j++] = str[i++]; else i++;
|
||||||
|
while (*count && str[i] == TAB)
|
||||||
|
{
|
||||||
|
plen += tabwidth;
|
||||||
|
(*count)--; if (j < maxl) dest[j++] = str[i++]; else i++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
normal_char = 0;
|
|
||||||
dest[j++] = str[i++];
|
/* Now copy as far as the next tab or cr/lf or eos */
|
||||||
|
|
||||||
|
seg_i = i;
|
||||||
|
seg_count = *count;
|
||||||
seg_j = j;
|
seg_j = j;
|
||||||
|
|
||||||
|
while (*count &&
|
||||||
|
(str[i] != TAB || !(format & DT_EXPANDTABS)) &&
|
||||||
|
((str[i] != CR && str[i] != LF) || (format & DT_SINGLELINE)))
|
||||||
|
{
|
||||||
|
if (str[i] == PREFIX && !(format & DT_NOPREFIX) && *count > 1)
|
||||||
|
{
|
||||||
|
(*count)--, i++; /* Throw away the prefix itself */
|
||||||
|
if (str[i] == PREFIX)
|
||||||
|
{
|
||||||
|
/* Swallow it before we see it again */
|
||||||
|
(*count)--; if (j < maxl) dest[j++] = str[i++]; else i++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
prefix_offset = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(*count)--; if (j < maxl) dest[j++] = str[i++]; else i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Measure the whole text segment and possibly WordBreak it */
|
||||||
|
|
||||||
|
max_seg_width = width - plen;
|
||||||
|
GetTextExtentExPointW (hdc, dest + seg_j, j - seg_j, max_seg_width, &num_fit, NULL, &size);
|
||||||
|
|
||||||
|
word_broken = 0;
|
||||||
|
if (num_fit < j-seg_j && (format & DT_WORDBREAK))
|
||||||
|
{
|
||||||
|
const WCHAR *s;
|
||||||
|
int chars_used;
|
||||||
|
int j_in_seg = j-seg_j;
|
||||||
|
TEXT_WordBreak (hdc, dest+seg_j, maxl-seg_j, &j_in_seg,
|
||||||
|
max_seg_width, format, num_fit, &chars_used, &size);
|
||||||
|
/* and correct the counts */
|
||||||
|
j = seg_j + j_in_seg;
|
||||||
|
TEXT_SkipChars (count, &s, seg_count, str+seg_i, i-seg_i,
|
||||||
|
chars_used, !(format & DT_NOPREFIX));
|
||||||
|
i = s - str;
|
||||||
|
word_broken = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
plen += size.cx;
|
||||||
|
if (size.cy > retsize->cy)
|
||||||
|
retsize->cy = size.cy;
|
||||||
|
|
||||||
|
if (word_broken)
|
||||||
|
break;
|
||||||
|
else if (!*count)
|
||||||
|
break;
|
||||||
|
else if (str[i] == CR || str[i] == LF)
|
||||||
|
{
|
||||||
|
count--, i++;
|
||||||
|
if (count && (str[i] == CR || str[i] == LF) && str[i] != str[i-1])
|
||||||
|
{
|
||||||
|
count--, i++;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SPACE:
|
|
||||||
if ((format & DT_WORDBREAK))
|
|
||||||
{
|
|
||||||
wb_i = i+1;
|
|
||||||
wb_j = j;
|
|
||||||
wb_count = *count;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
|
|
||||||
}
|
|
||||||
if (normal_char)
|
|
||||||
{
|
|
||||||
dest[j++] = str[i++];
|
|
||||||
if ((format & DT_WORDBREAK))
|
|
||||||
{
|
|
||||||
if (!GetTextExtentPointW(hdc, &dest[seg_j], j-seg_j, &size))
|
|
||||||
return NULL;
|
|
||||||
plen = seg_plen + size.cx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(*count)--;
|
|
||||||
if ((format & DT_WORDBREAK) && plen > width && wb_j)
|
|
||||||
{
|
|
||||||
*len = wb_j;
|
|
||||||
*count = wb_count - 1;
|
|
||||||
return (&str[wb_i]);
|
|
||||||
}
|
}
|
||||||
|
/* else it was a Tab and we go around again */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
retsize->cx = plen;
|
||||||
*len = j;
|
*len = j;
|
||||||
|
if (*count)
|
||||||
|
return (&str[i]);
|
||||||
|
else
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -476,39 +671,6 @@ static void TEXT_DrawUnderscore (HDC hdc, int x, int y, const WCHAR *str, int of
|
||||||
DeleteObject (hpen);
|
DeleteObject (hpen);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We are only going to need this until we correct the measuring in
|
|
||||||
* TEXT_NextLineW (coming soon)
|
|
||||||
*/
|
|
||||||
static int TEXT_TextExtent (HDC hdc, UINT flags, const WCHAR *line, int len, SIZE *size)
|
|
||||||
{
|
|
||||||
if ((flags & DT_EXPANDTABS))
|
|
||||||
{
|
|
||||||
SIZE tsize;
|
|
||||||
const WCHAR *p;
|
|
||||||
size->cx = 0;
|
|
||||||
size->cy = 0;
|
|
||||||
while (len)
|
|
||||||
{
|
|
||||||
p = line; while (p < line+len && *p != TAB) p++;
|
|
||||||
if (!GetTextExtentPointW (hdc, line, p-line, &tsize)) return 0;
|
|
||||||
size->cx += tsize.cx;
|
|
||||||
if (tsize.cy > size->cy) size->cy = tsize.cy;
|
|
||||||
len -= (p-line);
|
|
||||||
line=p;
|
|
||||||
if (len)
|
|
||||||
{
|
|
||||||
assert (*line == TAB);
|
|
||||||
len--;
|
|
||||||
line++;
|
|
||||||
size->cx = ((size->cx/tabwidth)+1)*tabwidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return GetTextExtentPointW(hdc, line, len, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/***********************************************************************
|
/***********************************************************************
|
||||||
* DrawTextExW (USER32.@)
|
* DrawTextExW (USER32.@)
|
||||||
*
|
*
|
||||||
|
@ -574,9 +736,8 @@ INT WINAPI DrawTextExW( HDC hdc, LPWSTR str, INT i_count,
|
||||||
{
|
{
|
||||||
prefix_offset = -1;
|
prefix_offset = -1;
|
||||||
len = MAX_STATIC_BUFFER;
|
len = MAX_STATIC_BUFFER;
|
||||||
strPtr = TEXT_NextLineW(hdc, strPtr, &count, line, &len, width, flags);
|
strPtr = TEXT_NextLineW(hdc, strPtr, &count, line, &len, width, flags, &size);
|
||||||
|
|
||||||
if (!TEXT_TextExtent(hdc, flags, line, len, &size)) return 0;
|
|
||||||
if (flags & DT_CENTER) x = (rect->left + rect->right -
|
if (flags & DT_CENTER) x = (rect->left + rect->right -
|
||||||
size.cx) / 2;
|
size.cx) / 2;
|
||||||
else if (flags & DT_RIGHT) x = rect->right - size.cx;
|
else if (flags & DT_RIGHT) x = rect->right - size.cx;
|
||||||
|
|
Loading…
Reference in New Issue