소스 검색

add soft hyphens

David Rose 22 년 전
부모
커밋
b661e7d400
3개의 변경된 파일112개의 추가작업 그리고 122개의 파일을 삭제
  1. 20 1
      panda/src/text/config_text.cxx
  2. 3 0
      panda/src/text/config_text.h
  3. 89 121
      panda/src/text/textFont.cxx

+ 20 - 1
panda/src/text/config_text.cxx

@@ -26,6 +26,7 @@
 #include "unicodeLatinMap.h"
 #include "unicodeLatinMap.h"
 
 
 #include "dconfig.h"
 #include "dconfig.h"
+#include "config_express.h"
 
 
 Configure(config_text);
 Configure(config_text);
 NotifyCategoryDef(text, "");
 NotifyCategoryDef(text, "");
@@ -48,6 +49,18 @@ const bool text_small_caps = config_text.GetBool("text-small-caps", false);
 const float text_small_caps_scale = config_text.GetFloat("text-small-caps-scale", 0.8f);
 const float text_small_caps_scale = config_text.GetFloat("text-small-caps-scale", 0.8f);
 const string text_default_font = config_text.GetString("text-default-font", "");
 const string text_default_font = config_text.GetString("text-default-font", "");
 
 
+// This is the decimal character number that, embedded in a string, is
+// identified as the soft-hyphen character.
+const int text_soft_hyphen_key = config_text.GetInt("text-soft-hyphen-key", 3);
+
+// This is the string that is output, encoded in the default encoding,
+// to represent the soft-hyphen character.
+wstring *text_soft_hyphen_output;
+
+// If the rightmost whitespace character falls before this fraction of
+// the line, hyphenate a word to the right of that if possible.
+const float text_hyphen_ratio = config_text.GetFloat("text-hyphen-ratio", 0.7);
+
 Texture::FilterType text_minfilter = Texture::FT_invalid;
 Texture::FilterType text_minfilter = Texture::FT_invalid;
 Texture::FilterType text_magfilter = Texture::FT_invalid;
 Texture::FilterType text_magfilter = Texture::FT_invalid;
 
 
@@ -103,5 +116,11 @@ init_libtext() {
       << "Invalid text-magfilter: " << text_magfilter_str << "\n";
       << "Invalid text-magfilter: " << text_magfilter_str << "\n";
     text_magfilter = Texture::FT_linear;
     text_magfilter = Texture::FT_linear;
   }
   }
-  
+
+  // Make sure libexpress is initialized before we ask something of
+  // TextEncoder.
+  init_libexpress();
+  string encoded = config_text.GetString("text-soft-hyphen-output", "-");
+  TextEncoder encoder;
+  text_soft_hyphen_output = new wstring(encoder.decode_text(encoded));
 }
 }

+ 3 - 0
panda/src/text/config_text.h

@@ -40,6 +40,9 @@ extern const float text_scale_factor;
 extern const bool text_small_caps;
 extern const bool text_small_caps;
 extern const float text_small_caps_scale;
 extern const float text_small_caps_scale;
 extern const string text_default_font;
 extern const string text_default_font;
+extern const int text_soft_hyphen_key;
+extern wstring *text_soft_hyphen_output;
+extern const float text_hyphen_ratio;
 
 
 extern Texture::FilterType text_minfilter;
 extern Texture::FilterType text_minfilter;
 extern Texture::FilterType text_magfilter;
 extern Texture::FilterType text_magfilter;

+ 89 - 121
panda/src/text/textFont.cxx

@@ -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.