diff --git a/Core/Util/PPGeDraw.cpp b/Core/Util/PPGeDraw.cpp index b310c28b2c..93db095e59 100644 --- a/Core/Util/PPGeDraw.cpp +++ b/Core/Util/PPGeDraw.cpp @@ -703,6 +703,8 @@ void PPGeMeasureText(float *w, float *h, int *n, float mw, mh; textDrawer->SetFontScale(scale, scale); int dtalign = (WrapType & PPGE_LINE_WRAP_WORD) ? FLAG_WRAP_TEXT : 0; + if (WrapType & PPGE_LINE_USE_ELLIPSIS) + dtalign |= FLAG_ELLIPSIZE_TEXT; Bounds b(0, 0, wrapWidth <= 0 ? 480.0f : wrapWidth, 272.0f); textDrawer->MeasureStringRect(text, strlen(text), b, &mw, &mh, dtalign); @@ -792,6 +794,7 @@ int GetPow2(int x) { static PPGeTextDrawerImage PPGeGetTextImage(const char *text, int align, float scale, float maxWidth, bool wrap) { int tdalign = (align & PPGE_ALIGN_HCENTER) ? ALIGN_HCENTER : 0; + tdalign |= FLAG_ELLIPSIZE_TEXT; if (wrap) { tdalign |= FLAG_WRAP_TEXT; } @@ -806,7 +809,6 @@ static PPGeTextDrawerImage PPGeGetTextImage(const char *text, int align, float s } else { std::vector bitmapData; textDrawer->SetFontScale(scale, scale); - // TODO: Ellipsis on long lines... Bounds b(0, 0, maxWidth, 272.0f); textDrawer->DrawStringBitmapRect(bitmapData, im.entry, Draw::DataFormat::R8_UNORM, text, b, tdalign); diff --git a/ext/native/gfx_es2/draw_buffer.cpp b/ext/native/gfx_es2/draw_buffer.cpp index 78eb6733ab..dbe024fe24 100644 --- a/ext/native/gfx_es2/draw_buffer.cpp +++ b/ext/native/gfx_es2/draw_buffer.cpp @@ -365,7 +365,7 @@ void DrawBuffer::DrawImage2GridH(ImageID atlas_image, float x1, float y1, float class AtlasWordWrapper : public WordWrapper { public: // Note: maxW may be height if rotated. - AtlasWordWrapper(const AtlasFont &atlasfont, float scale, const char *str, float maxW) : WordWrapper(str, maxW), atlasfont_(atlasfont), scale_(scale) { + AtlasWordWrapper(const AtlasFont &atlasfont, float scale, const char *str, float maxW, int flags) : WordWrapper(str, maxW, flags), atlasfont_(atlasfont), scale_(scale) { } protected: @@ -442,14 +442,15 @@ void DrawBuffer::MeasureTextRect(FontID font_id, const char *text, int count, co } std::string toMeasure = std::string(text, count); - if (align & FLAG_WRAP_TEXT) { + int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT); + if (wrap) { const AtlasFont *font = atlas->getFont(font_id); if (!font) { *w = 0.0f; *h = 0.0f; return; } - AtlasWordWrapper wrapper(*font, fontscalex, toMeasure.c_str(), bounds.w); + AtlasWordWrapper wrapper(*font, fontscalex, toMeasure.c_str(), bounds.w, wrap); toMeasure = wrapper.Wrapped(); } MeasureTextCount(font_id, toMeasure.c_str(), (int)toMeasure.length(), w, h); @@ -491,8 +492,9 @@ void DrawBuffer::DrawTextRect(FontID font, const char *text, float x, float y, f } std::string toDraw = text; - if (align & FLAG_WRAP_TEXT) { - AtlasWordWrapper wrapper(*atlas->getFont(font), fontscalex, toDraw.c_str(), w); + int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT); + if (wrap) { + AtlasWordWrapper wrapper(*atlas->getFont(font), fontscalex, toDraw.c_str(), w, wrap); toDraw = wrapper.Wrapped(); } diff --git a/ext/native/gfx_es2/draw_buffer.h b/ext/native/gfx_es2/draw_buffer.h index 211fc4fb95..a702fd6693 100644 --- a/ext/native/gfx_es2/draw_buffer.h +++ b/ext/native/gfx_es2/draw_buffer.h @@ -39,6 +39,7 @@ enum { FLAG_DYNAMIC_ASCII = 2048, FLAG_NO_PREFIX = 4096, // means to not process ampersands FLAG_WRAP_TEXT = 8192, + FLAG_ELLIPSIZE_TEXT = 16384, }; namespace Draw { diff --git a/ext/native/gfx_es2/draw_text.cpp b/ext/native/gfx_es2/draw_text.cpp index 239f4556ce..ce9b51b379 100644 --- a/ext/native/gfx_es2/draw_text.cpp +++ b/ext/native/gfx_es2/draw_text.cpp @@ -24,8 +24,8 @@ float TextDrawerWordWrapper::MeasureWidth(const char *str, size_t bytes) { return w; } -void TextDrawer::WrapString(std::string &out, const char *str, float maxW) { - TextDrawerWordWrapper wrapper(this, str, maxW); +void TextDrawer::WrapString(std::string &out, const char *str, float maxW, int flags) { + TextDrawerWordWrapper wrapper(this, str, maxW, flags); out = wrapper.Wrapped(); } @@ -59,9 +59,10 @@ void TextDrawer::DrawStringRect(DrawBuffer &target, const char *str, const Bound } std::string toDraw = str; - if (align & FLAG_WRAP_TEXT) { + int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT); + if (wrap) { bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; - WrapString(toDraw, str, rotated ? bounds.h : bounds.w); + WrapString(toDraw, str, rotated ? bounds.h : bounds.w, wrap); } DrawString(target, toDraw.c_str(), x, y, color, align); @@ -69,9 +70,10 @@ void TextDrawer::DrawStringRect(DrawBuffer &target, const char *str, const Bound void TextDrawer::DrawStringBitmapRect(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, const Bounds &bounds, int align) { std::string toDraw = str; - if (align & FLAG_WRAP_TEXT) { + int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT); + if (wrap) { bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; - WrapString(toDraw, str, rotated ? bounds.h : bounds.w); + WrapString(toDraw, str, rotated ? bounds.h : bounds.w, wrap); } DrawStringBitmap(bitmapData, entry, texFormat, toDraw.c_str(), align); diff --git a/ext/native/gfx_es2/draw_text.h b/ext/native/gfx_es2/draw_text.h index a9a5a35927..f6bf8e3e34 100644 --- a/ext/native/gfx_es2/draw_text.h +++ b/ext/native/gfx_es2/draw_text.h @@ -78,7 +78,7 @@ protected: Draw::DrawContext *draw_; virtual void ClearCache() = 0; - void WrapString(std::string &out, const char *str, float maxWidth); + void WrapString(std::string &out, const char *str, float maxWidth, int flags); struct CacheKey { bool operator < (const CacheKey &other) const { @@ -102,7 +102,7 @@ protected: class TextDrawerWordWrapper : public WordWrapper { public: - TextDrawerWordWrapper(TextDrawer *drawer, const char *str, float maxW) : WordWrapper(str, maxW), drawer_(drawer) { + TextDrawerWordWrapper(TextDrawer *drawer, const char *str, float maxW, int flags) : WordWrapper(str, maxW, flags), drawer_(drawer) { } protected: diff --git a/ext/native/gfx_es2/draw_text_android.cpp b/ext/native/gfx_es2/draw_text_android.cpp index b5afccbf96..f2513b99e3 100644 --- a/ext/native/gfx_es2/draw_text_android.cpp +++ b/ext/native/gfx_es2/draw_text_android.cpp @@ -115,9 +115,10 @@ void TextDrawerAndroid::MeasureStringRect(const char *str, size_t len, const Bou } std::string toMeasure = std::string(str, len); - if (align & FLAG_WRAP_TEXT) { + int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT); + if (wrap) { bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; - WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w); + WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w, wrap); } std::vector lines; diff --git a/ext/native/gfx_es2/draw_text_qt.cpp b/ext/native/gfx_es2/draw_text_qt.cpp index 406507518f..d792f656d8 100644 --- a/ext/native/gfx_es2/draw_text_qt.cpp +++ b/ext/native/gfx_es2/draw_text_qt.cpp @@ -76,9 +76,10 @@ void TextDrawerQt::MeasureString(const char *str, size_t len, float *w, float *h void TextDrawerQt::MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align) { std::string toMeasure = std::string(str, len); - if (align & FLAG_WRAP_TEXT) { + int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT); + if (wrap) { bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; - WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w); + WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w, wrap); } QFont* font = fontMap_.find(fontHash_)->second; diff --git a/ext/native/gfx_es2/draw_text_win.cpp b/ext/native/gfx_es2/draw_text_win.cpp index 3062c05225..2570236feb 100644 --- a/ext/native/gfx_es2/draw_text_win.cpp +++ b/ext/native/gfx_es2/draw_text_win.cpp @@ -154,9 +154,10 @@ void TextDrawerWin32::MeasureStringRect(const char *str, size_t len, const Bound } std::string toMeasure = std::string(str, len); - if (align & FLAG_WRAP_TEXT) { + int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT); + if (wrap) { bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; - WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w); + WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w, wrap); } TEXTMETRIC metrics{}; diff --git a/ext/native/util/text/wrap_text.cpp b/ext/native/util/text/wrap_text.cpp index edf3354673..c052056034 100644 --- a/ext/native/util/text/wrap_text.cpp +++ b/ext/native/util/text/wrap_text.cpp @@ -1,4 +1,5 @@ #include +#include "gfx_es2/draw_buffer.h" #include "util/text/utf8.h" #include "util/text/wrap_text.h" @@ -75,39 +76,58 @@ std::string WordWrapper::Wrapped() { } bool WordWrapper::WrapBeforeWord() { - if (x_ + wordWidth_ > maxW_ && out_.size() > 0) { - if (IsShy(out_[out_.size() - 1])) { - // Soft hyphen, replace it with a real hyphen since we wrapped at it. - // TODO: There's an edge case here where the hyphen might not fit. - out_[out_.size() - 1] = '-'; + if (flags_ & FLAG_WRAP_TEXT) { + if (x_ + wordWidth_ > maxW_ && !out_.empty()) { + if (IsShy(out_[out_.size() - 1])) { + // Soft hyphen, replace it with a real hyphen since we wrapped at it. + // TODO: There's an edge case here where the hyphen might not fit. + out_[out_.size() - 1] = '-'; + } + out_ += "\n"; + lastLineStart_ = out_.size(); + x_ = 0.0f; + forceEarlyWrap_ = false; + return true; + } + } + if (flags_ & FLAG_ELLIPSIZE_TEXT) { + if (x_ + wordWidth_ > maxW_) { + if (!out_.empty() && IsSpace(out_[out_.size() - 1])) { + out_[out_.size() - 1] = '.'; + out_ += ".."; + } else { + out_ += "..."; + } + x_ = maxW_; } - out_ += "\n"; - lastLineStart_ = out_.size(); - x_ = 0.0f; - forceEarlyWrap_ = false; - return true; } return false; } void WordWrapper::AppendWord(int endIndex, bool addNewline) { - int nextWordIndex = lastIndex_; + int lastWordStartIndex = lastIndex_; if (WrapBeforeWord()) { // Advance to the first non-whitespace UTF-8 character in the following word (if any) to prevent starting the new line with a whitespace - UTF8 utf8Word(str_, nextWordIndex); - while (nextWordIndex < endIndex) { + UTF8 utf8Word(str_, lastWordStartIndex); + while (lastWordStartIndex < endIndex) { const uint32_t c = utf8Word.next(); if (!IsSpace(c)) { break; } - nextWordIndex = utf8Word.byteIndex(); + lastWordStartIndex = utf8Word.byteIndex(); } } + // This will include the newline. - out_.append(str_ + nextWordIndex, str_ + endIndex); - if (addNewline) { + if (x_ < maxW_) { + out_.append(str_ + lastWordStartIndex, str_ + endIndex); + } else { + scanForNewline_ = true; + } + if (addNewline && (flags_ & FLAG_WRAP_TEXT)) { out_ += "\n"; lastLineStart_ = out_.size(); + scanForNewline_ = false; } else { // We may have appended a newline - check. size_t pos = out_.substr(lastLineStart_).find_last_of("\n"); @@ -129,6 +149,10 @@ void WordWrapper::Wrap() { return; } + if (flags_ & FLAG_ELLIPSIZE_TEXT) { + ellipsisWidth_ = MeasureWidth("...", 3); + } + for (UTF8 utf(str_); !utf.end(); ) { int beforeIndex = utf.byteIndex(); uint32_t c = utf.next(); @@ -142,6 +166,13 @@ void WordWrapper::Wrap() { wordWidth_ = 0.0f; // We wrapped once, so stop forcing. forceEarlyWrap_ = false; + scanForNewline_ = false; + continue; + } + + if (scanForNewline_) { + // We're discarding the rest of the characters until a newline (no wrapping.) + lastIndex_ = afterIndex; continue; } @@ -175,13 +206,33 @@ void WordWrapper::Wrap() { } // Now, add the word so far (without this latest character) and break. AppendWord(beforeIndex, true); - x_ = 0.0f; + if (lastLineStart_ != out_.size()) { + x_ = MeasureWidth(out_.c_str() + lastLineStart_, out_.size() - lastLineStart_); + } else { + x_ = 0.0f; + } wordWidth_ = 0.0f; forceEarlyWrap_ = false; // The current character will be handled as part of the next word. continue; } + if ((flags_ & FLAG_ELLIPSIZE_TEXT) && wordWidth_ > 0.0f && x_ + newWordWidth + ellipsisWidth_ > maxW_) { + if ((flags_ & FLAG_WRAP_TEXT) == 0) { + // Now, add the word so far (without this latest character) and show the ellipsis. + AppendWord(beforeIndex, true); + if (lastLineStart_ != out_.size()) { + x_ = MeasureWidth(out_.c_str() + lastLineStart_, out_.size() - lastLineStart_); + } else { + x_ = 0.0f; + } + wordWidth_ = 0.0f; + forceEarlyWrap_ = false; + // The current character will be handled as part of the next word. + continue; + } + } + wordWidth_ = newWordWidth; // Is this the end of a word via punctuation / CJK? diff --git a/ext/native/util/text/wrap_text.h b/ext/native/util/text/wrap_text.h index 61049bccef..554210e3cf 100644 --- a/ext/native/util/text/wrap_text.h +++ b/ext/native/util/text/wrap_text.h @@ -4,8 +4,8 @@ class WordWrapper { public: - WordWrapper(const char *str, float maxW) - : str_(str), maxW_(maxW) { + WordWrapper(const char *str, float maxW, int flags) + : str_(str), maxW_(maxW), flags_(flags) { } std::string Wrapped(); @@ -23,7 +23,9 @@ protected: const char *const str_; const float maxW_; + const int flags_; std::string out_; + // Index of last output / start of current word. int lastIndex_ = 0; // Index of last line start. @@ -32,6 +34,10 @@ protected: float x_ = 0.0f; // Most recent width of word since last index. float wordWidth_ = 0.0f; + // Width of "..." when flag is set, zero otherwise. + float ellipsisWidth_ = 0.0f; // Force the next word to cut partially and wrap. bool forceEarlyWrap_ = false; + // Skip all characters until the next newline. + bool scanForNewline_ = false; };