PPGe: Show ellipsis for overly long lines.

To match PPGE_LINE_USE_ELLIPSIS when using TextDrawer.
This commit is contained in:
Unknown W. Brackets 2020-03-10 19:06:30 -07:00
parent 6652fe261f
commit 08a6047768
10 changed files with 106 additions and 39 deletions

View File

@ -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<uint8_t> 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);

View File

@ -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();
}

View File

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

View File

@ -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<uint8_t> &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);

View File

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

View File

@ -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<std::string> lines;

View File

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

View File

@ -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{};

View File

@ -1,4 +1,5 @@
#include <cstring>
#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?

View File

@ -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;
};