|
@@ -22,15 +22,15 @@
|
|
|
|
|
|
|
|
TypeHandle TextFont::_type_handle;
|
|
TypeHandle TextFont::_type_handle;
|
|
|
|
|
|
|
|
-
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////
|
|
|
-// Function: isblank
|
|
|
|
|
|
|
+// Function: isbreakpoint
|
|
|
// Description: An internal function, similar to isspace(), except it
|
|
// Description: An internal function, similar to isspace(), except it
|
|
|
-// does not consider newlines to be whitespace.
|
|
|
|
|
|
|
+// does not consider newlines to be whitespace. It also
|
|
|
|
|
+// includes the soft-hyphen character.
|
|
|
////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////
|
|
|
-INLINE bool
|
|
|
|
|
-isblank(unsigned int ch) {
|
|
|
|
|
- return (ch == ' ' || ch == '\t');
|
|
|
|
|
|
|
+static INLINE bool
|
|
|
|
|
+isbreakpoint(unsigned int ch) {
|
|
|
|
|
+ return (ch == ' ' || ch == '\t' || ch == (unsigned int)text_soft_hyphen_key);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////
|
|
@@ -38,7 +38,7 @@ isblank(unsigned int ch) {
|
|
|
// Description: An internal function that works like isspace() but is
|
|
// Description: An internal function that works like isspace() but is
|
|
|
// safe to call for a wide character.
|
|
// safe to call for a wide character.
|
|
|
////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////
|
|
|
-INLINE bool
|
|
|
|
|
|
|
+static INLINE bool
|
|
|
isspacew(unsigned int ch) {
|
|
isspacew(unsigned int ch) {
|
|
|
return isascii(ch) && isspace(ch);
|
|
return isascii(ch) && isspace(ch);
|
|
|
}
|
|
}
|
|
@@ -118,114 +118,25 @@ calc_width(const string &line) {
|
|
|
string TextFont::
|
|
string TextFont::
|
|
|
wordwrap_to(const string &text, float wordwrap_width,
|
|
wordwrap_to(const string &text, float wordwrap_width,
|
|
|
bool preserve_trailing_whitespace) {
|
|
bool preserve_trailing_whitespace) {
|
|
|
- string output_text;
|
|
|
|
|
-
|
|
|
|
|
- size_t p = 0;
|
|
|
|
|
-
|
|
|
|
|
- // Preserve any initial whitespace and newlines.
|
|
|
|
|
- float initial_width = 0.0f;
|
|
|
|
|
- while (p < text.length() && isspace((unsigned int)text[p])) { // dbg runtime will bomb if text[p]>=128 without (unsigned int) cast
|
|
|
|
|
- if (text[p] == '\n') {
|
|
|
|
|
- initial_width = 0.0f;
|
|
|
|
|
- } else {
|
|
|
|
|
- initial_width += calc_width(text[p]);
|
|
|
|
|
- }
|
|
|
|
|
- output_text += text[p];
|
|
|
|
|
- p++;
|
|
|
|
|
|
|
+ // Elevate the string to a wstring for this operation.
|
|
|
|
|
+ wstring wtext;
|
|
|
|
|
+ wtext.reserve(text.size());
|
|
|
|
|
+ for (string::const_iterator ti = text.begin(); ti != text.end(); ++ti) {
|
|
|
|
|
+ wtext += (unsigned int)(*ti);
|
|
|
}
|
|
}
|
|
|
- bool needs_newline = false;
|
|
|
|
|
-
|
|
|
|
|
- while (p < text.length()) {
|
|
|
|
|
- nassertr(!isspace((unsigned int)text[p]), string());
|
|
|
|
|
-
|
|
|
|
|
- // Scan the next n characters, until the end of the string or an
|
|
|
|
|
- // embedded newline character, or we exceed wordwrap_width.
|
|
|
|
|
-
|
|
|
|
|
- size_t q = p;
|
|
|
|
|
- bool any_spaces = false;
|
|
|
|
|
- bool overflow = false;
|
|
|
|
|
-
|
|
|
|
|
- float width = initial_width;
|
|
|
|
|
- while (q < text.length() && text[q] != '\n') {
|
|
|
|
|
- if (isspace((unsigned int)text[q])) {
|
|
|
|
|
- any_spaces = true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- width += calc_width(text[q]);
|
|
|
|
|
- q++;
|
|
|
|
|
-
|
|
|
|
|
- if (width > wordwrap_width) {
|
|
|
|
|
- // Oops, too many.
|
|
|
|
|
- q--;
|
|
|
|
|
- overflow = true;
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (overflow && any_spaces) {
|
|
|
|
|
- // If we stopped because we exceeded the wordwrap width, then
|
|
|
|
|
- // back up to the end of the last complete word.
|
|
|
|
|
- while (q > p && !isspace((unsigned int)text[q])) {
|
|
|
|
|
- q--;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Skip additional whitespace between the lines.
|
|
|
|
|
- size_t next_start = q;
|
|
|
|
|
- while (next_start < text.length() && isblank(text[next_start])) {
|
|
|
|
|
- next_start++;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Trim off any more blanks on the end.
|
|
|
|
|
- while (q > p && isspace(text[q - 1])) {
|
|
|
|
|
- q--;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (next_start == p) {
|
|
|
|
|
- // No characters got in at all. This could only happen if the
|
|
|
|
|
- // wordwrap width is narrower than a single character, or if we
|
|
|
|
|
- // have a substantial number of leading spaces in a line.
|
|
|
|
|
- q++;
|
|
|
|
|
- next_start++;
|
|
|
|
|
- while (next_start < text.length() && isblank(text[next_start])) {
|
|
|
|
|
- next_start++;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (needs_newline) {
|
|
|
|
|
- output_text += '\n';
|
|
|
|
|
- }
|
|
|
|
|
- needs_newline = true;
|
|
|
|
|
|
|
|
|
|
- if (preserve_trailing_whitespace) {
|
|
|
|
|
- q = next_start;
|
|
|
|
|
- }
|
|
|
|
|
- output_text += text.substr(p, q - p);
|
|
|
|
|
-
|
|
|
|
|
- // Now prepare to wrap the next line.
|
|
|
|
|
|
|
+ wtext = wordwrap_to(wtext, wordwrap_width, preserve_trailing_whitespace);
|
|
|
|
|
|
|
|
- if (next_start < text.length() && text[next_start] == '\n') {
|
|
|
|
|
- // Preserve a single embedded newline.
|
|
|
|
|
- output_text += '\n';
|
|
|
|
|
- next_start++;
|
|
|
|
|
- needs_newline = false;
|
|
|
|
|
- }
|
|
|
|
|
- p = next_start;
|
|
|
|
|
-
|
|
|
|
|
- // Preserve any initial whitespace and newlines.
|
|
|
|
|
- initial_width = 0.0f;
|
|
|
|
|
- while (p < text.length() && isspace((unsigned int)text[p])) {
|
|
|
|
|
- if (text[p] == '\n') {
|
|
|
|
|
- initial_width = 0.0f;
|
|
|
|
|
- } else {
|
|
|
|
|
- initial_width += calc_width(text[p]);
|
|
|
|
|
- }
|
|
|
|
|
- output_text += text[p];
|
|
|
|
|
- p++;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // Back down from wstring to string.
|
|
|
|
|
+ string result;
|
|
|
|
|
+ result.reserve(wtext.size());
|
|
|
|
|
+ for (wstring::const_iterator wi = wtext.begin();
|
|
|
|
|
+ wi != wtext.end();
|
|
|
|
|
+ ++wi) {
|
|
|
|
|
+ result += (char)(*wi);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return output_text;
|
|
|
|
|
|
|
+ return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////
|
|
@@ -295,17 +206,53 @@ wordwrap_to(const wstring &text, float wordwrap_width,
|
|
|
|
|
|
|
|
size_t q = p;
|
|
size_t q = p;
|
|
|
bool any_spaces = false;
|
|
bool any_spaces = false;
|
|
|
|
|
+ size_t last_space = 0;
|
|
|
|
|
+ float last_space_width = 0.0f;
|
|
|
|
|
+
|
|
|
|
|
+ bool any_hyphens = false;
|
|
|
|
|
+ size_t last_hyphen = 0;
|
|
|
|
|
+ bool output_hyphen = false;
|
|
|
|
|
+
|
|
|
bool overflow = false;
|
|
bool overflow = false;
|
|
|
|
|
|
|
|
|
|
+ bool last_was_space = false;
|
|
|
|
|
+ float hyphen_width = calc_width(*text_soft_hyphen_output);
|
|
|
float width = initial_width;
|
|
float width = initial_width;
|
|
|
while (q < text.length() && text[q] != '\n') {
|
|
while (q < text.length() && text[q] != '\n') {
|
|
|
if (isspacew(text[q])) {
|
|
if (isspacew(text[q])) {
|
|
|
- any_spaces = true;
|
|
|
|
|
|
|
+ if (!last_was_space) {
|
|
|
|
|
+ any_spaces = true;
|
|
|
|
|
+ // We only care about logging whether there is a soft-hyphen
|
|
|
|
|
+ // character to the right of the rightmost space. Each time
|
|
|
|
|
+ // we encounter a space, we reset this counter.
|
|
|
|
|
+ any_hyphens = false;
|
|
|
|
|
+ last_space = q;
|
|
|
|
|
+ last_space_width = width;
|
|
|
|
|
+ last_was_space = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ last_was_space = false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- width += calc_width(text[q]);
|
|
|
|
|
- q++;
|
|
|
|
|
|
|
+ // A soft hyphen character is not printed, but marks a point
|
|
|
|
|
+ // that we might hyphenate a word if we need to.
|
|
|
|
|
+ if (text[q] == text_soft_hyphen_key) {
|
|
|
|
|
+ // We only consider this as a possible hyphenation point if
|
|
|
|
|
+ // (a) it is not the very first character, and (b) there is
|
|
|
|
|
+ // enough room for a hyphen character to be printed following
|
|
|
|
|
+ // it.
|
|
|
|
|
+ if (q != p && width + hyphen_width <= wordwrap_width) {
|
|
|
|
|
+ any_hyphens = true;
|
|
|
|
|
+ last_hyphen = q;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Some normal, printable character.
|
|
|
|
|
+ width += calc_width(text[q]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ q++;
|
|
|
|
|
+
|
|
|
if (width > wordwrap_width) {
|
|
if (width > wordwrap_width) {
|
|
|
// Oops, too many.
|
|
// Oops, too many.
|
|
|
q--;
|
|
q--;
|
|
@@ -313,18 +260,31 @@ wordwrap_to(const wstring &text, float wordwrap_width,
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- if (overflow && any_spaces) {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (overflow) {
|
|
|
// If we stopped because we exceeded the wordwrap width, then
|
|
// If we stopped because we exceeded the wordwrap width, then
|
|
|
- // back up to the end of the last complete word.
|
|
|
|
|
- while (q > p && !isspacew(text[q])) {
|
|
|
|
|
- q--;
|
|
|
|
|
|
|
+ // try to find an appropriate place to wrap the line or to
|
|
|
|
|
+ // hyphenate, if necessary.
|
|
|
|
|
+ if (any_spaces && last_space_width / wordwrap_width >= text_hyphen_ratio) {
|
|
|
|
|
+ // If we have a space that ended up within our safety margin,
|
|
|
|
|
+ // don't use any soft-hyphen characters.
|
|
|
|
|
+ any_hyphens = false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (any_hyphens) {
|
|
|
|
|
+ // If we still have a soft-hyphen character, use it.
|
|
|
|
|
+ q = last_hyphen;
|
|
|
|
|
+ output_hyphen = true;
|
|
|
|
|
+
|
|
|
|
|
+ } else if (any_spaces) {
|
|
|
|
|
+ // Otherwise, break at a space if we can.
|
|
|
|
|
+ q = last_space;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Skip additional whitespace between the lines.
|
|
// Skip additional whitespace between the lines.
|
|
|
size_t next_start = q;
|
|
size_t next_start = q;
|
|
|
- while (next_start < text.length() && isblank(text[next_start])) {
|
|
|
|
|
|
|
+ while (next_start < text.length() && isbreakpoint(text[next_start])) {
|
|
|
next_start++;
|
|
next_start++;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -339,7 +299,7 @@ wordwrap_to(const wstring &text, float wordwrap_width,
|
|
|
// have a substantial number of leading spaces in a line.
|
|
// have a substantial number of leading spaces in a line.
|
|
|
q++;
|
|
q++;
|
|
|
next_start++;
|
|
next_start++;
|
|
|
- while (next_start < text.length() && isblank(text[next_start])) {
|
|
|
|
|
|
|
+ while (next_start < text.length() && isbreakpoint(text[next_start])) {
|
|
|
next_start++;
|
|
next_start++;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -352,7 +312,15 @@ wordwrap_to(const wstring &text, float wordwrap_width,
|
|
|
if (preserve_trailing_whitespace) {
|
|
if (preserve_trailing_whitespace) {
|
|
|
q = next_start;
|
|
q = next_start;
|
|
|
}
|
|
}
|
|
|
- output_text += text.substr(p, q - p);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ for (size_t pi = p; pi < q; pi++) {
|
|
|
|
|
+ if (text[pi] != text_soft_hyphen_key) {
|
|
|
|
|
+ output_text += text[pi];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (output_hyphen) {
|
|
|
|
|
+ output_text += *text_soft_hyphen_output;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// Now prepare to wrap the next line.
|
|
// Now prepare to wrap the next line.
|
|
|
|
|
|