mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-27 23:40:39 +00:00
0e3a84b4a8
It works after the move, on Windows and Android at least. Deletes the D3DX9 shader compiler loader, which was not used.
254 lines
7.2 KiB
C++
254 lines
7.2 KiB
C++
#include <cstring>
|
|
#include "Common/Render/DrawBuffer.h"
|
|
#include "Common/Data/Encoding/Utf8.h"
|
|
#include "Common/Data/Text/WrapText.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_;
|
|
}
|
|
|
|
bool WordWrapper::WrapBeforeWord() {
|
|
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_;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void WordWrapper::AppendWord(int endIndex, bool addNewline) {
|
|
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_, lastWordStartIndex);
|
|
while (lastWordStartIndex < endIndex) {
|
|
const uint32_t c = utf8Word.next();
|
|
if (!IsSpace(c)) {
|
|
break;
|
|
}
|
|
lastWordStartIndex = utf8Word.byteIndex();
|
|
}
|
|
}
|
|
|
|
// This will include the newline.
|
|
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");
|
|
if (pos != out_.npos) {
|
|
lastLineStart_ += pos;
|
|
}
|
|
}
|
|
lastIndex_ = endIndex;
|
|
}
|
|
|
|
void WordWrapper::Wrap() {
|
|
out_.clear();
|
|
|
|
// First, let's check if it fits as-is.
|
|
size_t len = strlen(str_);
|
|
|
|
// We know it'll be approximately this size. It's fine if the guess is a little off.
|
|
out_.reserve(len + len / 16);
|
|
|
|
if (MeasureWidth(str_, len) <= maxW_) {
|
|
// If it fits, we don't need to go through each character.
|
|
out_ = str_;
|
|
return;
|
|
}
|
|
|
|
if (flags_ & FLAG_ELLIPSIZE_TEXT) {
|
|
ellipsisWidth_ = MeasureWidth("...", 3);
|
|
}
|
|
|
|
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;
|
|
scanForNewline_ = false;
|
|
continue;
|
|
}
|
|
|
|
if (scanForNewline_) {
|
|
// We're discarding the rest of the characters until a newline (no wrapping.)
|
|
lastIndex_ = afterIndex;
|
|
continue;
|
|
}
|
|
|
|
// Measure the entire word for kerning purposes. May not be 100% perfect.
|
|
float newWordWidth = MeasureWidth(str_ + lastIndex_, afterIndex - lastIndex_);
|
|
|
|
// Is this the end of a word (space)?
|
|
if (wordWidth_ > 0.0f && IsSpace(c)) {
|
|
AppendWord(afterIndex, false);
|
|
// To account for kerning around spaces, we recalculate the entire line width.
|
|
x_ = MeasureWidth(out_.c_str() + lastLineStart_, out_.size() - lastLineStart_);
|
|
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);
|
|
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?
|
|
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);
|
|
}
|