mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-26 19:58:36 +01:00
UI: Add a generic word wrapper class.
This commit is contained in:
1 parent
cbb1ab17ca
commit
30e99206c9
11 files changed
+287
-2
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,6 +1,7 @@
|
||||
set(SRCS
|
||||
hash/hash.cpp
|
||||
text/utf8.cpp
|
||||
text/wrap_text.cpp
|
||||
)
|
||||
|
||||
set(SRCS ${SRCS})
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_;
|
||||
};
|
||||
Reference in new issue
Block a user