UI: Add a generic word wrapper class.

This commit is contained in:
Unknown W. Brackets committed 2016-07-04 16:03:22 -07:00
1 parent cbb1ab17ca
commit 30e99206c9
11 files changed
+287 -2

No files matched your search

+2
View File
@@ -1042,6 +1042,8 @@ add_library(native STATIC
ext/native/util/text/utf8.cpp
ext/native/util/text/parsers.h
ext/native/util/text/parsers.cpp
ext/native/util/text/wrap_text.h
ext/native/util/text/wrap_text.cpp
ext/native/util/const_map.h
ext/native/ext/jpge/jpgd.cpp
ext/native/ext/jpge/jpgd.h
+2
View File
@@ -101,6 +101,7 @@ SOURCES += \
$$P/ext/native/thread/*.cpp \
$$P/ext/native/ui/*.cpp \
$$P/ext/native/util/hash/hash.cpp \
$$P/ext/native/util/text/wrap_text.cpp \
$$P/ext/native/util/text/utf8.cpp \
$$P/ext/native/util/text/parsers.cpp
@@ -134,5 +135,6 @@ HEADERS += \
$$P/ext/native/ui/*.h \
$$P/ext/native/util/hash/hash.h \
$$P/ext/native/util/random/*.h \
$$P/ext/native/util/text/wrap_text.h \
$$P/ext/native/util/text/utf8.h \
$$P/ext/native/util/text/parsers.h
+1
View File
@@ -92,6 +92,7 @@ LOCAL_SRC_FILES :=\
ui/virtual_input.cpp \
util/text/utf8.cpp \
util/text/parsers.cpp \
util/text/wrap_text.cpp \
util/hash/hash.cpp
LOCAL_CFLAGS := -O3 -DUSING_GLES2 -fsigned-char -fno-strict-aliasing -Wall -Wno-multichar -D__STDC_CONSTANT_MACROS
+26
View File
@@ -13,6 +13,7 @@
#include "gfx_es2/draw_text.h"
#include "gfx_es2/glsl_program.h"
#include "util/text/utf8.h"
#include "util/text/wrap_text.h"
enum {
// Enough?
@@ -331,6 +332,31 @@ void DrawBuffer::DrawImage2GridH(ImageID atlas_image, float x1, float y1, float
DrawTexRect(xb, y1, x2, y2, um, v1, u2, v2, color);
}
class AtlasWordWrapper : public WordWrapper {
public:
// Note: maxW may be height if rotated.
AtlasWordWrapper(AtlasFont &atlasfont, float scale, const char *str, float maxW) : WordWrapper(str, maxW), atlasfont_(atlasfont), scale_(scale) {
}
protected:
float MeasureWidth(const char *str, size_t bytes) override;
AtlasFont &atlasfont_;
float scale_;
};
float AtlasWordWrapper::MeasureWidth(const char *str, size_t bytes) {
float w = 0.0f;
for (UTF8 utf(str); utf.byteIndex() < bytes; ) {
const AtlasChar *ch = atlasfont_.getChar(utf.next());
if (!ch)
ch = atlasfont_.getChar('?');
w += ch->wx * scale_;
}
return w;
}
void DrawBuffer::MeasureTextCount(int font, const char *text, int count, float *w, float *h) {
const AtlasFont &atlasfont = *atlas->fonts[font];
+28 -2
View File
@@ -2,6 +2,7 @@
#include "base/stringutil.h"
#include "thin3d/thin3d.h"
#include "util/hash/hash.h"
#include "util/text/wrap_text.h"
#include "util/text/utf8.h"
#include "gfx_es2/draw_text.h"
@@ -12,6 +13,23 @@
#include <QtOpenGL/QGLWidget>
#endif
class TextDrawerWordWrapper : public WordWrapper {
public:
TextDrawerWordWrapper(TextDrawer *drawer, const char *str, float maxW) : WordWrapper(str, maxW), drawer_(drawer) {
}
protected:
float MeasureWidth(const char *str, size_t bytes) override;
TextDrawer *drawer_;
};
float TextDrawerWordWrapper::MeasureWidth(const char *str, size_t bytes) {
float w, h;
drawer_->MeasureString(str, bytes, &w, &h);
return w;
}
#if defined(_WIN32) && !defined(USING_QT_UI)
#define WIN32_LEAN_AND_MEAN
@@ -114,13 +132,17 @@ void TextDrawer::SetFont(uint32_t fontHandle) {
}
void TextDrawer::MeasureString(const char *str, float *w, float *h) {
MeasureString(str, strlen(str), w, h);
}
void TextDrawer::MeasureString(const char *str, size_t len, float *w, float *h) {
auto iter = fontMap_.find(fontHash_);
if (iter != fontMap_.end()) {
SelectObject(ctx_->hDC, iter->second->hFont);
}
SIZE size;
std::wstring wstr = ConvertUTF8ToWString(ReplaceAll(str, "\n", "\r\n"));
std::wstring wstr = ConvertUTF8ToWString(ReplaceAll(std::string(str, len), "\n", "\r\n"));
GetTextExtentPoint32(ctx_->hDC, wstr.c_str(), (int)wstr.size(), &size);
*w = size.cx * fontScaleX_;
*h = size.cy * fontScaleY_;
@@ -250,10 +272,14 @@ void TextDrawer::SetFont(uint32_t fontHandle) {
}
void TextDrawer::MeasureString(const char *str, float *w, float *h) {
MeasureString(str, strlen(str), w, h);
}
void TextDrawer::MeasureString(const char *str, size_t len, float *w, float *h) {
#ifdef USING_QT_UI
QFont* font = fontMap_.find(fontHash_)->second;
QFontMetrics fm(*font);
QSize size = fm.size(0, QString::fromUtf8(str));
QSize size = fm.size(0, QString::fromUtf8(str, (int)len));
*w = (float)size.width() * fontScaleX_;
*h = (float)size.height() * fontScaleY_;
#else
+1
View File
@@ -49,6 +49,7 @@ public:
void SetFontScale(float xscale, float yscale);
void MeasureString(const char *str, float *w, float *h);
void MeasureString(const char *str, size_t len, float *w, float *h);
void DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT);
void DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align);
// Use for housekeeping like throwing out old strings.
+2
View File
@@ -234,6 +234,7 @@
<ClInclude Include="gfx\gl_debug_log.h" />
<ClInclude Include="gfx\gl_lost_manager.h" />
<ClInclude Include="gfx\texture_atlas.h" />
<ClInclude Include="util\text\wrap_text.h" />
<ClInclude Include="gfx_es2\draw_buffer.h" />
<ClInclude Include="gfx_es2\draw_text.h" />
<ClInclude Include="gfx_es2\gl3stub.h" />
@@ -689,6 +690,7 @@
<ClCompile Include="gfx\gl_debug_log.cpp" />
<ClCompile Include="gfx\gl_lost_manager.cpp" />
<ClCompile Include="gfx\texture_atlas.cpp" />
<ClCompile Include="util\text\wrap_text.cpp" />
<ClCompile Include="gfx_es2\draw_buffer.cpp" />
<ClCompile Include="gfx_es2\draw_text.cpp" />
<ClCompile Include="gfx_es2\gl3stub.c">
+6
View File
@@ -311,6 +311,9 @@
<ClInclude Include="net\sinks.h">
<Filter>net</Filter>
</ClInclude>
<ClInclude Include="util\text\wrap_text.h">
<Filter>util</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="gfx\gl_debug_log.cpp">
@@ -751,6 +754,9 @@
<ClCompile Include="net\sinks.cpp">
<Filter>net</Filter>
</ClCompile>
<ClCompile Include="util\text\wrap_text.cpp">
<Filter>util</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="gfx">
+1
View File
@@ -1,6 +1,7 @@
set(SRCS
hash/hash.cpp
text/utf8.cpp
text/wrap_text.cpp
)
set(SRCS ${SRCS})
+183
View File
@@ -0,0 +1,183 @@
#include <cstring>
#include "util/text/utf8.h"
#include "util/text/wrap_text.h"
bool WordWrapper::IsCJK(uint32_t c) {
if (c < 0x1000) {
return false;
}
// CJK characters can be wrapped more freely.
bool result = (c >= 0x1100 && c <= 0x11FF); // Hangul Jamo.
result = result || (c >= 0x2E80 && c <= 0x2FFF); // Kangxi Radicals etc.
#if 0
result = result || (c >= 0x3040 && c <= 0x31FF); // Hiragana, Katakana, Hangul Compatibility Jamo etc.
result = result || (c >= 0x3200 && c <= 0x32FF); // CJK Enclosed
result = result || (c >= 0x3300 && c <= 0x33FF); // CJK Compatibility
result = result || (c >= 0x3400 && c <= 0x4DB5); // CJK Unified Ideographs Extension A
#else
result = result || (c >= 0x3040 && c <= 0x4DB5); // Above collapsed
#endif
result = result || (c >= 0x4E00 && c <= 0x9FBB); // CJK Unified Ideographs
result = result || (c >= 0xAC00 && c <= 0xD7AF); // Hangul Syllables
result = result || (c >= 0xF900 && c <= 0xFAD9); // CJK Compatibility Ideographs
result = result || (c >= 0x20000 && c <= 0x2A6D6); // CJK Unified Ideographs Extension B
result = result || (c >= 0x2F800 && c <= 0x2FA1D); // CJK Compatibility Supplement
return result;
}
bool WordWrapper::IsPunctuation(uint32_t c) {
switch (c) {
// TODO: This list of punctuation is very incomplete.
case ',':
case '.':
case ':':
case '!':
case ')':
case '?':
case 0x00AD: // SOFT HYPHEN
case 0x3001: // IDEOGRAPHIC COMMA
case 0x3002: // IDEOGRAPHIC FULL STOP
case 0x06D4: // ARABIC FULL STOP
case 0xFF01: // FULLWIDTH EXCLAMATION MARK
case 0xFF09: // FULLWIDTH RIGHT PARENTHESIS
case 0xFF1F: // FULLWIDTH QUESTION MARK
return true;
default:
return false;
}
}
bool WordWrapper::IsSpace(uint32_t c) {
switch (c) {
case '\t':
case ' ':
case 0x2002: // EN SPACE
case 0x2003: // EM SPACE
case 0x3000: // IDEOGRAPHIC SPACE
return true;
default:
return false;
}
}
bool WordWrapper::IsShy(uint32_t c) {
return c == 0x00AD; // SOFT HYPHEN
}
std::string WordWrapper::Wrapped() {
if (out_.empty()) {
Wrap();
}
return out_;
}
void WordWrapper::WrapBeforeWord() {
if (x_ + wordWidth_ > maxW_) {
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";
x_ = 0.0f;
forceEarlyWrap_ = false;
}
}
void WordWrapper::AppendWord(int endIndex, bool addNewline) {
WrapBeforeWord();
// This will include the newline.
out_ += std::string(str_ + lastIndex_, endIndex - lastIndex_);
if (addNewline) {
out_ += "\n";
}
lastIndex_ = endIndex;
}
void WordWrapper::Wrap() {
out_.clear();
// First, let's check if it fits as-is.
size_t len = strlen(str_);
if (MeasureWidth(str_, len) <= maxW_) {
// If it fits, we don't need to go through each character.
out_ = str_;
return;
}
for (UTF8 utf(str_); !utf.end(); ) {
int beforeIndex = utf.byteIndex();
uint32_t c = utf.next();
int afterIndex = utf.byteIndex();
// Is this a newline character, hard wrapping?
if (c == '\n') {
// This will include the newline character.
AppendWord(afterIndex, false);
x_ = 0.0f;
wordWidth_ = 0.0f;
// We wrapped once, so stop forcing.
forceEarlyWrap_ = false;
continue;
}
float newWordWidth = 0.0f;
if (c == '\n') {
newWordWidth = wordWidth_;
} else {
// Measure the entire word for kerning purposes. May not be 100% perfect.
newWordWidth = MeasureWidth(str_ + lastIndex_, afterIndex - lastIndex_);
}
// Is this the end of a word (space)?
if (wordWidth_ > 0.0f && IsSpace(c)) {
AppendWord(afterIndex, false);
// We include the space in the x increase.
// If the space takes it over, we'll wrap on the next word.
x_ += newWordWidth;
wordWidth_ = 0.0f;
continue;
}
// Can the word fit on a line even all by itself so far?
if (wordWidth_ > 0.0f && newWordWidth > maxW_) {
// Nope. Let's drop what's there so far onto its own line.
if (x_ > 0.0f && x_ + wordWidth_ > maxW_ && beforeIndex > lastIndex_) {
// Let's put as many characters as will fit on the previous line.
// This word can't fit on one line even, so it's going to be cut into pieces anyway.
// Better to avoid huge gaps, in that case.
forceEarlyWrap_ = true;
// Now rewind back to where the word started so we can wrap at the opportune moment.
wordWidth_ = 0.0f;
while (utf.byteIndex() > lastIndex_) {
utf.bwd();
}
continue;
}
// Now, add the word so far (without this latest character) and break.
AppendWord(beforeIndex, true);
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?
if (wordWidth_ > 0.0f && (IsCJK(c) || IsPunctuation(c) || forceEarlyWrap_)) {
// CJK doesn't require spaces, so we treat each letter as its own word.
AppendWord(afterIndex, false);
x_ += wordWidth_;
wordWidth_ = 0.0f;
}
}
// Now insert the rest of the string - the last word.
AppendWord((int)len, false);
}
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include <string>
class WordWrapper {
public:
WordWrapper(const char *str, float maxW)
: str_(str), maxW_(maxW), lastIndex_(0), x_(0.0f), forceEarlyWrap_(false) {
}
std::string Wrapped();
protected:
virtual float MeasureWidth(const char *str, size_t bytes) = 0;
void Wrap();
void WrapBeforeWord();
void AppendWord(int endIndex, bool addNewline);
static bool IsCJK(uint32_t c);
static bool IsPunctuation(uint32_t c);
static bool IsSpace(uint32_t c);
static bool IsShy(uint32_t c);
const char *const str_;
const float maxW_;
std::string out_;
// Index of last output / start of current word.
int lastIndex_;
// Position the current word starts at.
float x_;
// Most recent width of word since last index.
float wordWidth_;
// Force the next word to cut partially and wrap.
bool forceEarlyWrap_;
};