diff --git a/desktop_version/src/Editor.cpp b/desktop_version/src/Editor.cpp index 87e707a0..51ae16b7 100644 --- a/desktop_version/src/Editor.cpp +++ b/desktop_version/src/Editor.cpp @@ -11,6 +11,7 @@ #include "DeferCallbacks.h" #include "Entity.h" #include "Enums.h" +#include "Font.h" #include "Game.h" #include "Graphics.h" #include "GraphicsUtil.h" @@ -1154,7 +1155,7 @@ void editorrender(void) } short lines; - message = graphics.string_wordwrap(message, 312, &lines); + message = font::string_wordwrap(message, 312, &lines); short textheight = 8*lines; graphics.fill_rect(0,238-textheight,320,240, graphics.getRGB(32,32,32)); @@ -1256,7 +1257,7 @@ void editorrender(void) else if (ed.textmod) { short lines; - std::string wrapped = graphics.string_wordwrap(ed.textdesc, 312, &lines); + std::string wrapped = font::string_wordwrap(ed.textdesc, 312, &lines); short textheight = 8*lines+8; graphics.fill_rect(0, 238-textheight, 320, 240, graphics.getRGB(32, 32, 32)); @@ -1590,7 +1591,7 @@ void editorrender(void) if(ed.notedelay>0 || ed.oldnotedelay>0) { short lines; - std::string wrapped = graphics.string_wordwrap(ed.note, 304, &lines); + std::string wrapped = font::string_wordwrap(ed.note, 304, &lines); short textheight = 8+(lines-1)*10; short banner_y = 120 - textheight/2 - 5; diff --git a/desktop_version/src/Font.cpp b/desktop_version/src/Font.cpp index dfd12275..6db08dab 100644 --- a/desktop_version/src/Font.cpp +++ b/desktop_version/src/Font.cpp @@ -107,6 +107,18 @@ static GlyphInfo* find_glyphinfo(const Font* f, const uint32_t codepoint) return NULL; } +int get_advance(const Font* f, const uint32_t codepoint) +{ + // Get the width of a single character in a font + GlyphInfo* glyph = find_glyphinfo(f, codepoint); + if (glyph == NULL) + { + return f->glyph_w; + } + + return glyph->advance; +} + static bool decode_xml_range(tinyxml2::XMLElement* elem, unsigned* start, unsigned* end) { // We do support hexadecimal start/end like "0x10FFFF" @@ -319,16 +331,219 @@ void destroy(void) } } -int get_advance(const Font* f, const uint32_t codepoint) -{ - /* Get the width of a single character in a font */ - GlyphInfo* glyph = find_glyphinfo(f, codepoint); - if (glyph == NULL) +static bool next_wrap( + size_t* start, + size_t* len, + const char* str, + const int maxwidth +) { + /* This function is UTF-8 aware. But start/len still are bytes. */ + size_t idx = 0; + size_t lenfromlastspace = 0; + size_t lastspace = 0; + int linewidth = 0; + *len = 0; + + if (str[idx] == '\0') { - return f->glyph_w; + return false; } - return glyph->advance; + while (true) + { + /* FIXME: This only checks one byte, not multiple! */ + if ((str[idx] & 0xC0) == 0x80) + { + /* Skip continuation byte. */ + goto next; + } + + linewidth += get_advance(&font::temp_bfont, str[idx]); + + switch (str[idx]) + { + case ' ': + if (loc::get_langmeta()->autowordwrap) + { + lenfromlastspace = idx; + lastspace = *start; + } + break; + case '\n': + case '|': + *start += 1; + SDL_FALLTHROUGH; + case '\0': + return true; + } + + if (linewidth > maxwidth) + { + if (lenfromlastspace != 0) + { + *len = lenfromlastspace; + *start = lastspace + 1; + } + return true; + } + +next: + idx += 1; + *start += 1; + *len += 1; + } +} + +static bool next_wrap_s( + char buffer[], + const size_t buffer_size, + size_t* start, + const char* str, + const int maxwidth +) { + size_t len = 0; + const size_t prev_start = *start; + + const bool retval = next_wrap(start, &len, &str[*start], maxwidth); + + if (retval) + { + /* Like next_split_s(), don't use SDL_strlcpy() here. */ + const size_t length = SDL_min(buffer_size - 1, len); + SDL_memcpy(buffer, &str[prev_start], length); + buffer[length] = '\0'; + } + + return retval; +} + +std::string string_wordwrap(const std::string& s, int maxwidth, short *lines /*= NULL*/) +{ + /* Return a string wordwrapped to a maximum limit by adding newlines. + * CJK will need to have autowordwrap disabled and have manually inserted newlines. */ + + if (lines != NULL) + { + *lines = 1; + } + + const char* orig = s.c_str(); + + std::string result; + size_t start = 0; + bool first = true; + + while (true) + { + size_t len = 0; + const char* part = &orig[start]; + + const bool retval = next_wrap(&start, &len, part, maxwidth); + + if (!retval) + { + return result; + } + + if (first) + { + first = false; + } + else + { + result.push_back('\n'); + + if (lines != NULL) + { + (*lines)++; + } + } + result.append(part, len); + } +} + +std::string string_wordwrap_balanced(const std::string& s, int maxwidth) +{ + /* Return a string wordwrapped to a limit of maxwidth by adding newlines. + * Try to fill the lines as far as possible, and return result where lines are most filled. + * Goal is to have all lines in textboxes be about as long and to avoid wrapping just one word to a new line. + * CJK will need to have autowordwrap disabled and have manually inserted newlines. */ + + if (!loc::get_langmeta()->autowordwrap) + { + return s; + } + + short lines; + string_wordwrap(s, maxwidth, &lines); + + int bestwidth = maxwidth; + if (lines > 1) + { + for (int curlimit = maxwidth; curlimit > 1; curlimit -= 8) + { + short try_lines; + string_wordwrap(s, curlimit, &try_lines); + + if (try_lines > lines) + { + bestwidth = curlimit + 8; + break; + } + } + } + + return string_wordwrap(s, bestwidth); +} + +std::string string_unwordwrap(const std::string& s) +{ + /* Takes a string wordwrapped by newlines, and turns it into a single line, undoing the wrapping. + * Also trims any leading/trailing whitespace and collapses multiple spaces into one (to undo manual centering) + * Only applied to English, so langmeta.autowordwrap isn't used here (it'd break looking up strings) */ + + std::string result; + std::back_insert_iterator inserter = std::back_inserter(result); + std::string::const_iterator iter = s.begin(); + bool latest_was_space = true; // last character was a space (or the beginning, don't want leading whitespace) + int consecutive_newlines = 0; // number of newlines currently encountered in a row (multiple newlines should stay!) + while (iter != s.end()) + { + uint32_t ch = utf8::unchecked::next(iter); + + if (ch == '\n') + { + if (consecutive_newlines == 0) + { + ch = ' '; + } + else if (consecutive_newlines == 1) + { + // The last character was already a newline, so change it back from the space we thought it should have become. + result[result.size()-1] = '\n'; + } + consecutive_newlines++; + } + else + { + consecutive_newlines = 0; + } + + if (ch != ' ' || !latest_was_space) + { + utf8::unchecked::append(ch, inserter); + } + + latest_was_space = (ch == ' ' || ch == '\n'); + } + + // We could have one trailing space + if (!result.empty() && result[result.size()-1] == ' ') + { + result.erase(result.end()-1); + } + + return result; } static int print_char( @@ -390,6 +605,21 @@ static PrintFlags decode_print_flags(uint32_t flags) } #undef FLAG_PART +int len(const uint32_t flags, const std::string& t) +{ + PrintFlags pf = decode_print_flags(flags); + // TODO flags! + + int text_len = 0; + std::string::const_iterator iter = t.begin(); + while (iter != t.end()) + { + int cur = utf8::unchecked::next(iter); + text_len += get_advance(&font::temp_bfont, cur); + } + return text_len; +} + void print( const uint32_t flags, int x, @@ -493,7 +723,7 @@ int print_wrap( { // Correct for the height of the resulting print. size_t len = 0; - while (graphics.next_wrap(&start, &len, &str[start], maxwidth)) + while (next_wrap(&start, &len, &str[start], maxwidth)) { y += linespacing; } @@ -501,7 +731,7 @@ int print_wrap( start = 0; } - while (graphics.next_wrap_s(buffer, sizeof(buffer), &start, str, maxwidth)) + while (next_wrap_s(buffer, sizeof(buffer), &start, str, maxwidth)) { print(flags, x, y, buffer, r, g, b); diff --git a/desktop_version/src/Font.h b/desktop_version/src/Font.h index 6ebeb845..47d66d5a 100644 --- a/desktop_version/src/Font.h +++ b/desktop_version/src/Font.h @@ -104,7 +104,11 @@ void load_custom(void); void unload_custom(void); void destroy(void); -int get_advance(const Font* f, uint32_t codepoint); // TODO de-api +std::string string_wordwrap(const std::string& s, int maxwidth, short *lines = NULL); +std::string string_wordwrap_balanced(const std::string& s, int maxwidth); +std::string string_unwordwrap(const std::string& s); + +int len(uint32_t flags, const std::string& t); void print( uint32_t flags, diff --git a/desktop_version/src/Game.cpp b/desktop_version/src/Game.cpp index bda92e17..23847c67 100644 --- a/desktop_version/src/Game.cpp +++ b/desktop_version/src/Game.cpp @@ -672,7 +672,7 @@ void Game::crewmate_textbox(const int r, const int g, const int b) /* This is a special case for wrapping, we MUST have two lines. * So just make sure it can't fit in one line. */ const char* text = loc::gettext("You have rescued a crew member!"); - std::string wrapped = graphics.string_wordwrap_balanced(text, graphics.len(text)-1); + std::string wrapped = font::string_wordwrap_balanced(text, graphics.len(text)-1); size_t startline = 0; size_t newline; diff --git a/desktop_version/src/Graphics.cpp b/desktop_version/src/Graphics.cpp index 1162e188..70366458 100644 --- a/desktop_version/src/Graphics.cpp +++ b/desktop_version/src/Graphics.cpp @@ -354,92 +354,6 @@ void Graphics::PrintAlpha( int x, int y, const std::string& text, int r, int g, font::print(PR_ALPHA(a), x, y, text, r, g, b); } -bool Graphics::next_wrap( - size_t* start, - size_t* len, - const char* str, - const int maxwidth -) { - /* This function is UTF-8 aware. But start/len still are bytes. */ - size_t idx = 0; - size_t lenfromlastspace = 0; - size_t lastspace = 0; - int linewidth = 0; - *len = 0; - - if (str[idx] == '\0') - { - return false; - } - - while (true) - { - /* FIXME: This only checks one byte, not multiple! */ - if ((str[idx] & 0xC0) == 0x80) - { - /* Skip continuation byte. */ - goto next; - } - - linewidth += font::get_advance(&font::temp_bfont, str[idx]); - - switch (str[idx]) - { - case ' ': - if (loc::get_langmeta()->autowordwrap) - { - lenfromlastspace = idx; - lastspace = *start; - } - break; - case '\n': - case '|': - *start += 1; - SDL_FALLTHROUGH; - case '\0': - return true; - } - - if (linewidth > maxwidth) - { - if (lenfromlastspace != 0) - { - *len = lenfromlastspace; - *start = lastspace + 1; - } - return true; - } - -next: - idx += 1; - *start += 1; - *len += 1; - } -} - -bool Graphics::next_wrap_s( - char buffer[], - const size_t buffer_size, - size_t* start, - const char* str, - const int maxwidth -) { - size_t len = 0; - const size_t prev_start = *start; - - const bool retval = next_wrap(start, &len, &str[*start], maxwidth); - - if (retval) - { - /* Like next_split_s(), don't use SDL_strlcpy() here. */ - const size_t length = SDL_min(buffer_size - 1, len); - SDL_memcpy(buffer, &str[prev_start], length); - buffer[length] = '\0'; - } - - return retval; -} - int Graphics::PrintWrap( const int x, int y, @@ -481,142 +395,8 @@ void Graphics::bigbprint(int x, int y, const std::string& text, int r, int g, in int Graphics::len(const std::string& t) { - int text_len = 0; - std::string::const_iterator iter = t.begin(); - while (iter != t.end()) { - int cur = utf8::unchecked::next(iter); - text_len += font::get_advance(&font::temp_bfont, cur); - } - return text_len; -} - -std::string Graphics::string_wordwrap(const std::string& s, int maxwidth, short *lines /*= NULL*/) -{ - // Return a string wordwrapped to a maximum limit by adding newlines. - // CJK will need to have autowordwrap disabled and have manually inserted newlines. - - if (lines != NULL) - { - *lines = 1; - } - - const char* orig = s.c_str(); - - std::string result; - size_t start = 0; - bool first = true; - - while (true) - { - size_t len = 0; - const char* part = &orig[start]; - - const bool retval = next_wrap(&start, &len, part, maxwidth); - - if (!retval) - { - return result; - } - - if (first) - { - first = false; - } - else - { - result.push_back('\n'); - - if (lines != NULL) - { - (*lines)++; - } - } - result.append(part, len); - } -} - -std::string Graphics::string_wordwrap_balanced(const std::string& s, int maxwidth) -{ - // Return a string wordwrapped to a limit of maxwidth by adding newlines. - // Try to fill the lines as far as possible, and return result where lines are most filled. - // Goal is to have all lines in textboxes be about as long and to avoid wrapping just one word to a new line. - // CJK will need to have autowordwrap disabled and have manually inserted newlines. - - if (!loc::get_langmeta()->autowordwrap) - { - return s; - } - - short lines; - string_wordwrap(s, maxwidth, &lines); - - int bestwidth = maxwidth; - if (lines > 1) - { - for (int curlimit = maxwidth; curlimit > 1; curlimit -= 8) - { - short try_lines; - string_wordwrap(s, curlimit, &try_lines); - - if (try_lines > lines) - { - bestwidth = curlimit + 8; - break; - } - } - } - - return string_wordwrap(s, bestwidth); -} - -std::string Graphics::string_unwordwrap(const std::string& s) -{ - /* Takes a string wordwrapped by newlines, and turns it into a single line, undoing the wrapping. - * Also trims any leading/trailing whitespace and collapses multiple spaces into one (to undo manual centering) - * Only applied to English, so langmeta.autowordwrap isn't used here (it'd break looking up strings) */ - - std::string result; - std::back_insert_iterator inserter = std::back_inserter(result); - std::string::const_iterator iter = s.begin(); - bool latest_was_space = true; // last character was a space (or the beginning, don't want leading whitespace) - int consecutive_newlines = 0; // number of newlines currently encountered in a row (multiple newlines should stay!) - while (iter != s.end()) - { - uint32_t ch = utf8::unchecked::next(iter); - - if (ch == '\n') - { - if (consecutive_newlines == 0) - { - ch = ' '; - } - else if (consecutive_newlines == 1) - { - // The last character was already a newline, so change it back from the space we thought it should have become. - result[result.size()-1] = '\n'; - } - consecutive_newlines++; - } - else - { - consecutive_newlines = 0; - } - - if (ch != ' ' || !latest_was_space) - { - utf8::unchecked::append(ch, inserter); - } - - latest_was_space = (ch == ' ' || ch == '\n'); - } - - // We could have one trailing space - if (!result.empty() && result[result.size()-1] == ' ') - { - result.erase(result.end()-1); - } - - return result; + // DEPRECATED + return font::len(0, t); } void Graphics::bprint( int x, int y, const std::string& text, int r, int g, int b, bool cen /*= false*/ ) { @@ -2058,12 +1838,12 @@ void Graphics::drawtrophytext(void) short lines; if (top_text != NULL) { - string_wordwrap(top_text, 304, &lines); + font::string_wordwrap(top_text, 304, &lines); PrintWrap(-1, 11-(lines-1)*5, top_text, temp, temp2, temp3, true); } if (bottom_text != NULL) { - string_wordwrap(bottom_text, 304, &lines); + font::string_wordwrap(bottom_text, 304, &lines); PrintWrap(-1, 221-(lines-1)*5, bottom_text, temp, temp2, temp3, true); } } @@ -3337,7 +3117,7 @@ int Graphics::textboxwrap(int pad) vlog_error("textboxwrap() has no first line!"); return 16; } - std::string wrapped = string_wordwrap_balanced(textboxes[m].lines[0], 36*8 - pad*8); + std::string wrapped = font::string_wordwrap_balanced(textboxes[m].lines[0], 36*8 - pad*8); textboxes[m].lines.clear(); size_t startline = 0; diff --git a/desktop_version/src/Graphics.h b/desktop_version/src/Graphics.h index 1f857c5d..e44bbcd8 100644 --- a/desktop_version/src/Graphics.h +++ b/desktop_version/src/Graphics.h @@ -206,10 +206,6 @@ public: void PrintAlpha(int _x, int _y, const std::string& _s, int r, int g, int b, int a, bool cen = false); - bool next_wrap(size_t* start, size_t* len, const char* str, int maxwidth); - - bool next_wrap_s(char buffer[], size_t buffer_size, size_t* start, const char* str, int maxwidth); - int PrintWrap(int x, int y, const std::string& s, int r, int g, int b, bool cen = false, int linespacing = -1, int maxwidth = -1); void bprint(int x, int y, const std::string& t, int r, int g, int b, bool cen = false); @@ -217,9 +213,6 @@ public: void bprintalpha(int x, int y, const std::string& t, int r, int g, int b, int a, bool cen = false); int len(const std::string& t); - std::string string_wordwrap(const std::string& s, int maxwidth, short *lines = NULL); - std::string string_wordwrap_balanced(const std::string& s, int maxwidth); - std::string string_unwordwrap(const std::string& s); void bigprint( int _x, int _y, const std::string& _s, int r, int g, int b, bool cen = false, int sc = 2 ); void bigbprint(int x, int y, const std::string& s, int r, int g, int b, bool cen = false, int sc = 2); diff --git a/desktop_version/src/LocalizationMaint.cpp b/desktop_version/src/LocalizationMaint.cpp index 18d0664c..6001c678 100644 --- a/desktop_version/src/LocalizationMaint.cpp +++ b/desktop_version/src/LocalizationMaint.cpp @@ -6,6 +6,7 @@ #include "Alloc.h" #include "FileSystemUtils.h" +#include "Font.h" #include "Graphics.h" #include "Script.h" #include "Vlogging.h" @@ -198,7 +199,7 @@ static void sync_lang_file(const std::string& langcode) } size_t alloc_len; - const std::string eng_unwrapped = graphics.string_unwordwrap(eng); + const std::string eng_unwrapped = font::string_unwordwrap(eng); char* eng_prefixed = add_disambiguator(subElem->UnsignedAttribute("case", 1), eng_unwrapped.c_str(), &alloc_len); if (eng_prefixed == NULL) { diff --git a/desktop_version/src/LocalizationStorage.cpp b/desktop_version/src/LocalizationStorage.cpp index fef76fb8..61bd3b86 100644 --- a/desktop_version/src/LocalizationStorage.cpp +++ b/desktop_version/src/LocalizationStorage.cpp @@ -6,6 +6,7 @@ #include "Constants.h" #include "CustomLevels.h" #include "FileSystemUtils.h" +#include "Font.h" #include "Graphics.h" #include "Unused.h" #include "UtilityClass.h" @@ -309,7 +310,7 @@ static bool max_check_string(const char* str, const char* max) else { short lines; - graphics.string_wordwrap(str, max_w_px, &lines); + font::string_wordwrap(str, max_w_px, &lines); does_overflow = lines > (short) max_h; } @@ -651,7 +652,7 @@ static void loadtext_cutscenes(bool custom_level) { continue; } - const std::string eng_unwrapped = graphics.string_unwordwrap(eng); + const std::string eng_unwrapped = font::string_unwordwrap(eng); char* eng_prefixed = add_disambiguator(subElem->UnsignedAttribute("case", 1), eng_unwrapped.c_str(), NULL); if (eng_prefixed == NULL) { diff --git a/desktop_version/src/Script.cpp b/desktop_version/src/Script.cpp index 279edeb4..b852580a 100644 --- a/desktop_version/src/Script.cpp +++ b/desktop_version/src/Script.cpp @@ -10,6 +10,7 @@ #include "Entity.h" #include "Enums.h" #include "Exit.h" +#include "Font.h" #include "GlitchrunnerMode.h" #include "Graphics.h" #include "KeyPoll.h" @@ -2466,7 +2467,7 @@ void scriptclass::translate_dialogue(void) eng.append(txt[i]); } - eng = graphics.string_unwordwrap(eng); + eng = font::string_unwordwrap(eng); const loc::TextboxFormat* format = loc::gettext_cutscene(scriptname, eng, tc); if (format == NULL || format->text == NULL || format->text[0] == '\0') { @@ -2489,7 +2490,7 @@ void scriptclass::translate_dialogue(void) } else { - tra = graphics.string_wordwrap_balanced(format->text, format->wraplimit); + tra = font::string_wordwrap_balanced(format->text, format->wraplimit); } textcentertext = format->centertext;