mirror of
https://github.com/libretro/ppsspp.git
synced 2024-12-11 18:33:41 +00:00
Merge pull request #8847 from unknownbrackets/ui-textwrap
Allow text wrapping in UI TextViews
This commit is contained in:
commit
1f8afd7467
@ -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
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include "base/functional.h"
|
||||
#include "base/colorutil.h"
|
||||
#include "base/display.h"
|
||||
#include "base/timeutil.h"
|
||||
#include "gfx_es2/draw_buffer.h"
|
||||
#include "math/curves.h"
|
||||
@ -231,7 +232,8 @@ void PromptScreen::CreateViews() {
|
||||
ViewGroup *leftColumn = new AnchorLayout(new LinearLayoutParams(1.0f));
|
||||
root_->Add(leftColumn);
|
||||
|
||||
leftColumn->Add(new TextView(message_, ALIGN_LEFT, false, new AnchorLayoutParams(10, 10, NONE, NONE)))->SetClip(false);
|
||||
float leftColumnWidth = dp_xres - actionMenuMargins.left - actionMenuMargins.right - 300.0f;
|
||||
leftColumn->Add(new TextView(message_, ALIGN_LEFT | FLAG_WRAP_TEXT, false, new AnchorLayoutParams(leftColumnWidth, WRAP_CONTENT, 10, 10, NONE, NONE)))->SetClip(false);
|
||||
|
||||
ViewGroup *rightColumnItems = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins));
|
||||
root_->Add(rightColumnItems);
|
||||
|
@ -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(const 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;
|
||||
|
||||
const AtlasFont &atlasfont_;
|
||||
const 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];
|
||||
|
||||
@ -367,6 +393,16 @@ void DrawBuffer::MeasureTextCount(int font, const char *text, int count, float *
|
||||
if (h) *h = atlasfont.height * fontscaley * lines;
|
||||
}
|
||||
|
||||
void DrawBuffer::MeasureTextRect(int font, const char *text, int count, const Bounds &bounds, float *w, float *h, int align) {
|
||||
std::string toMeasure = std::string(text, count);
|
||||
if (align & FLAG_WRAP_TEXT) {
|
||||
AtlasWordWrapper wrapper(*atlas->fonts[font], fontscalex, toMeasure.c_str(), bounds.w);
|
||||
toMeasure = wrapper.Wrapped();
|
||||
}
|
||||
|
||||
MeasureTextCount(font, toMeasure.c_str(), (int)toMeasure.length(), w, h);
|
||||
}
|
||||
|
||||
void DrawBuffer::MeasureText(int font, const char *text, float *w, float *h) {
|
||||
return MeasureTextCount(font, text, (int)strlen(text), w, h);
|
||||
}
|
||||
@ -402,7 +438,12 @@ void DrawBuffer::DrawTextRect(int font, const char *text, float x, float y, floa
|
||||
y += h;
|
||||
}
|
||||
|
||||
DrawText(font, text, x, y, color, align);
|
||||
std::string toDraw = text;
|
||||
if (align & FLAG_WRAP_TEXT) {
|
||||
AtlasWordWrapper wrapper(*atlas->fonts[font], fontscalex, toDraw.c_str(), w);
|
||||
toDraw = wrapper.Wrapped();
|
||||
}
|
||||
DrawText(font, toDraw.c_str(), x, y, color, align);
|
||||
}
|
||||
|
||||
// ROTATE_* doesn't yet work right.
|
||||
|
@ -38,7 +38,8 @@ enum {
|
||||
// Avoids using system font drawing as it's too slow.
|
||||
// Not actually used here but is reserved for whatever system wraps DrawBuffer.
|
||||
FLAG_DYNAMIC_ASCII = 2048,
|
||||
FLAG_NO_PREFIX = 4096 // means to not process ampersands
|
||||
FLAG_NO_PREFIX = 4096, // means to not process ampersands
|
||||
FLAG_WRAP_TEXT = 8192,
|
||||
};
|
||||
|
||||
class Thin3DShaderSet;
|
||||
@ -126,6 +127,7 @@ public:
|
||||
|
||||
// NOTE: Count is in plain chars not utf-8 chars!
|
||||
void MeasureTextCount(int font, const char *text, int count, float *w, float *h);
|
||||
void MeasureTextRect(int font, const char *text, int count, const Bounds &bounds, float *w, float *h, int align = 0);
|
||||
|
||||
void DrawTextRect(int font, const char *text, float x, float y, float w, float h, Color color = 0xFFFFFFFF, int align = 0);
|
||||
void DrawText(int font, const char *text, float x, float y, Color color = 0xFFFFFFFF, int align = 0);
|
||||
|
@ -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,18 +132,51 @@ 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_;
|
||||
}
|
||||
|
||||
void TextDrawer::MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align) {
|
||||
auto iter = fontMap_.find(fontHash_);
|
||||
if (iter != fontMap_.end()) {
|
||||
SelectObject(ctx_->hDC, iter->second->hFont);
|
||||
}
|
||||
|
||||
std::string toMeasure = std::string(str, len);
|
||||
if (align & FLAG_WRAP_TEXT) {
|
||||
bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0;
|
||||
WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w);
|
||||
}
|
||||
|
||||
std::vector<std::string> lines;
|
||||
SplitString(toMeasure, '\n', lines);
|
||||
float total_w = 0.0f;
|
||||
float total_h = 0.0f;
|
||||
for (size_t i = 0; i < lines.size(); i++) {
|
||||
SIZE size;
|
||||
std::wstring wstr = ConvertUTF8ToWString(lines[i].length() == 0 ? " " : lines[i]);
|
||||
GetTextExtentPoint32(ctx_->hDC, wstr.c_str(), (int)wstr.size(), &size);
|
||||
if (total_w < size.cx * fontScaleX_) {
|
||||
total_w = size.cx * fontScaleX_;
|
||||
}
|
||||
total_h += size.cy * fontScaleY_;
|
||||
}
|
||||
*w = total_w;
|
||||
*h = total_h;
|
||||
}
|
||||
|
||||
void TextDrawer::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) {
|
||||
if (!strlen(str))
|
||||
return;
|
||||
@ -250,10 +301,33 @@ 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
|
||||
*w = 0;
|
||||
*h = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void TextDrawer::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) {
|
||||
bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0;
|
||||
WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w);
|
||||
}
|
||||
|
||||
#ifdef USING_QT_UI
|
||||
QFont* font = fontMap_.find(fontHash_)->second;
|
||||
QFontMetrics fm(*font);
|
||||
QSize size = fm.size(0, QString::fromUtf8(toMeasure.c_str(), (int)toMeasure.size()));
|
||||
*w = (float)size.width() * fontScaleX_;
|
||||
*h = (float)size.height() * fontScaleY_;
|
||||
#else
|
||||
@ -325,6 +399,11 @@ void TextDrawer::DrawString(DrawBuffer &target, const char *str, float x, float
|
||||
|
||||
#endif
|
||||
|
||||
void TextDrawer::WrapString(std::string &out, const char *str, float maxW) {
|
||||
TextDrawerWordWrapper wrapper(this, str, maxW);
|
||||
out = wrapper.Wrapped();
|
||||
}
|
||||
|
||||
void TextDrawer::SetFontScale(float xscale, float yscale) {
|
||||
fontScaleX_ = xscale;
|
||||
fontScaleY_ = xscale;
|
||||
@ -344,7 +423,13 @@ void TextDrawer::DrawStringRect(DrawBuffer &target, const char *str, const Bound
|
||||
y = bounds.y2();
|
||||
}
|
||||
|
||||
DrawString(target, str, x, y, color, align);
|
||||
std::string toDraw = str;
|
||||
if (align & FLAG_WRAP_TEXT) {
|
||||
bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0;
|
||||
WrapString(toDraw, str, rotated ? bounds.h : bounds.w);
|
||||
}
|
||||
|
||||
DrawString(target, toDraw.c_str(), x, y, color, align);
|
||||
}
|
||||
|
||||
void TextDrawer::OncePerFrame() {
|
||||
|
@ -49,6 +49,8 @@ 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 MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT);
|
||||
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.
|
||||
@ -57,6 +59,8 @@ public:
|
||||
private:
|
||||
Thin3DContext *thin3d_;
|
||||
|
||||
void WrapString(std::string &out, const char *str, float maxWidth);
|
||||
|
||||
int frameCount_;
|
||||
float fontScaleX_;
|
||||
float fontScaleY_;
|
||||
|
@ -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">
|
||||
|
@ -138,8 +138,23 @@ void UIContext::MeasureTextCount(const UI::FontStyle &style, const char *str, in
|
||||
Draw()->MeasureTextCount(style.atlasFont, str, count, x, y);
|
||||
} else {
|
||||
textDrawer_->SetFontScale(fontScaleX_, fontScaleY_);
|
||||
std::string subset(str, count);
|
||||
textDrawer_->MeasureString(subset.c_str(), x, y);
|
||||
textDrawer_->MeasureString(str, count, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void UIContext::MeasureTextRect(const UI::FontStyle &style, const char *str, int count, const Bounds &bounds, float *x, float *y, int align) const {
|
||||
if ((align & FLAG_WRAP_TEXT) == 0) {
|
||||
MeasureTextCount(style, str, count, x, y, align);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!textDrawer_ || (align & FLAG_DYNAMIC_ASCII)) {
|
||||
float sizeFactor = (float)style.sizePts / 24.0f;
|
||||
Draw()->SetFontScale(fontScaleX_ * sizeFactor, fontScaleY_ * sizeFactor);
|
||||
Draw()->MeasureTextRect(style.atlasFont, str, count, bounds, x, y, align);
|
||||
} else {
|
||||
textDrawer_->SetFontScale(fontScaleX_, fontScaleY_);
|
||||
textDrawer_->MeasureStringRect(str, count, bounds, x, y, align);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,7 @@ public:
|
||||
void SetFontScale(float scaleX, float scaleY);
|
||||
void MeasureTextCount(const UI::FontStyle &style, const char *str, int count, float *x, float *y, int align = 0) const;
|
||||
void MeasureText(const UI::FontStyle &style, const char *str, float *x, float *y, int align = 0) const;
|
||||
void MeasureTextRect(const UI::FontStyle &style, const char *str, int count, const Bounds &bounds, float *x, float *y, int align = 0) const;
|
||||
void DrawText(const char *str, float x, float y, uint32_t color, int align = 0);
|
||||
void DrawTextShadow(const char *str, float x, float y, uint32_t color, int align = 0);
|
||||
void DrawTextRect(const char *str, const Bounds &bounds, uint32_t color, int align = 0);
|
||||
|
@ -630,20 +630,9 @@ void Thin3DTextureView::Draw(UIContext &dc) {
|
||||
}
|
||||
|
||||
void TextView::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
|
||||
// MeasureText doesn't seem to work with line breaks, so do something more sophisticated.
|
||||
std::vector<std::string> lines;
|
||||
SplitString(text_, '\n', lines);
|
||||
float total_w = 0.f;
|
||||
float total_h = 0.f;
|
||||
for (size_t i = 0; i < lines.size(); i++) {
|
||||
float temp_w, temp_h;
|
||||
dc.MeasureText(small_ ? dc.theme->uiFontSmall : dc.theme->uiFont, lines[i].c_str(), &temp_w, &temp_h);
|
||||
if (temp_w > total_w)
|
||||
total_w = temp_w;
|
||||
total_h += temp_h;
|
||||
}
|
||||
w = total_w;
|
||||
h = total_h;
|
||||
// We don't have the bounding w/h yet, so stick with hardset layout params.
|
||||
Bounds bounds(0, 0, layoutParams_->width, layoutParams_->height);
|
||||
dc.MeasureTextRect(small_ ? dc.theme->uiFontSmall : dc.theme->uiFont, text_.c_str(), (int)text_.length(), bounds, &w, &h, textAlign_);
|
||||
}
|
||||
|
||||
void TextView::Draw(UIContext &dc) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
set(SRCS
|
||||
hash/hash.cpp
|
||||
text/utf8.cpp
|
||||
text/wrap_text.cpp
|
||||
)
|
||||
|
||||
set(SRCS ${SRCS})
|
||||
|
183
ext/native/util/text/wrap_text.cpp
Normal file
183
ext/native/util/text/wrap_text.cpp
Normal 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
ext/native/util/text/wrap_text.h
Normal file
35
ext/native/util/text/wrap_text.h
Normal 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_;
|
||||
};
|
Loading…
Reference in New Issue
Block a user