From 4690ba0cc66338b1f00e08fb6054ee95c1c0dcc6 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 23 Sep 2020 23:45:21 +0200 Subject: Fallback to cjk font, change font to system noto sans --- src/Text.cpp | 200 +++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 125 insertions(+), 75 deletions(-) (limited to 'src/Text.cpp') diff --git a/src/Text.cpp b/src/Text.cpp index 0517c15..bdd41c3 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -21,31 +21,12 @@ namespace QuickMedia return -1; } - Text::Text(const sf::Font *_font) : - font(_font), - characterSize(0), - vertices(sf::PrimitiveType::Quads), - maxWidth(0.0f), - color(sf::Color::White), - urlColor(URL_COLOR), - dirty(false), - dirtyText(false), - dirtyCaret(false), - plainText(false), - editable(false), - visible(true), - caretMoveDirection(CaretMoveDirection::NONE), - lineSpacing(0.0f), - characterSpacing(0.0f), - caretIndex(0) - { - - } + Text::Text(const sf::Font *_font, const sf::Font *_cjk_font) : Text("", _font, _cjk_font, 0, 0.0f, false) {} - Text::Text(const sf::String &_str, const sf::Font *_font, unsigned int _characterSize, float _maxWidth, bool _plainText) : + Text::Text(sf::String _str, const sf::Font *_font, const sf::Font *_cjk_font, unsigned int _characterSize, float _maxWidth, bool _plainText) : font(_font), + cjk_font(_cjk_font), characterSize(_characterSize), - vertices(sf::PrimitiveType::Quads), maxWidth(_maxWidth), color(sf::Color::White), urlColor(URL_COLOR), @@ -60,19 +41,21 @@ namespace QuickMedia characterSpacing(0.0f), caretIndex(0) { - setString(_str); + vertices[0].setPrimitiveType(sf::PrimitiveType::Quads); + vertices[1].setPrimitiveType(sf::PrimitiveType::Quads); + setString(std::move(_str)); } - void Text::setString(const sf::String &str) + void Text::setString(sf::String str) { if(str != this->str) { - this->str = str; + this->str = std::move(str); dirty = true; dirtyText = true; - if((int)str.getSize() < caretIndex) + if((int)this->str.getSize() < caretIndex) { - caretIndex = str.getSize(); + caretIndex = this->str.getSize(); dirtyCaret = true; } } @@ -177,22 +160,68 @@ namespace QuickMedia { return boundingBox.height; } + + // TODO: Is there a more efficient way to do this? maybe japanese characters have a specific bit-pattern? + static bool is_japanese_codepoint(sf::Uint32 codepoint) { + return (codepoint >= 0x2E80 && codepoint <= 0x2FD5) // Kanji radicals + || (codepoint >= 0x3000 && codepoint <= 0x303F) // Punctuation + || (codepoint >= 0x3041 && codepoint <= 0x3096) // Hiragana + || (codepoint >= 0x30A0 && codepoint <= 0x30FF) // Katakana + || (codepoint >= 0x31F0 && codepoint <= 0x31FF) // Miscellaneous symbols and characters 1 + || (codepoint >= 0x3220 && codepoint <= 0x3243) // Miscellaneous symbols and characters 2 + || (codepoint >= 0x3280 && codepoint <= 0x337F) // Miscellaneous symbols and characters 3 + || (codepoint >= 0x3400 && codepoint <= 0x4DB5) // Kanji 1 + || (codepoint >= 0x4E00 && codepoint <= 0x9FCB) // Kanji 2 + || (codepoint >= 0xF900 && codepoint <= 0xFA6A) // Kanji 3 + || (codepoint >= 0xFF01 && codepoint <= 0xFF5E) // Alphanumeric and punctuation (full width) + || (codepoint >= 0xFF5F && codepoint <= 0xFF9F); // Katakana and punctuation (half width) + } + + static size_t find_end_of_japanese(const sf::Uint32 *str, size_t size) { + for(size_t i = 0; i < size; ++i) { + if(!is_japanese_codepoint(str[i])) + return i; + } + return size; + } + + static size_t find_end_of_non_japanese(const sf::Uint32 *str, size_t size) { + for(size_t i = 0; i < size; ++i) { + if(is_japanese_codepoint(str[i])) + return i; + } + return size; + } + + void Text::splitTextByFont() { + textElements.clear(); + size_t index = 0; + size_t size = str.getSize(); + while(index < size) { + size_t offset; + bool is_japanese = is_japanese_codepoint(str[index]); + if(is_japanese) + offset = find_end_of_japanese(str.getData() + index + 1, size - index - 1); + else + offset = find_end_of_non_japanese(str.getData() + index + 1, size - index - 1); + textElements.push_back({ StringViewUtf32(str.getData() + index, offset + 1), TextElement::Type::TEXT }); + textElements.back().is_japanese = is_japanese; + index += 1 + offset; + } + } // Logic loosely based on https://github.com/SFML/SFML/wiki/Source:-CurvedText - void Text::updateGeometry(bool update_even_if_not_dirty) - { - if(dirtyText) - { - textElements.clear(); - StringViewUtf32 wholeStr(this->str.getData(), this->str.getSize()); - textElements.push_back({ wholeStr, TextElement::Type::TEXT }); + void Text::updateGeometry(bool update_even_if_not_dirty) { + if(dirtyText) { dirtyText = false; + splitTextByFont(); } if(!update_even_if_not_dirty && !dirty) return; - vertices.clear(); + vertices[0].clear(); + vertices[1].clear(); float hspace = font->getGlyph(' ', characterSize, false).advance + characterSpacing; float vspace = font->getLineSpacing(characterSize); @@ -205,14 +234,20 @@ namespace QuickMedia for(usize textElementIndex = 0; textElementIndex < textElements.size(); ++textElementIndex) { TextElement &textElement = textElements[textElementIndex]; + const sf::Font *ff = font; + size_t vertices_index = 0; + if(textElement.is_japanese) { + ff = cjk_font; + vertices_index = 1; + } - usize vertexOffset = vertices.getVertexCount(); - vertices.resize(vertices.getVertexCount() + 4 * (textElement.text.size + 1)); + usize vertexOffset = vertices[vertices_index].getVertexCount(); + vertices[vertices_index].resize(vertices[vertices_index].getVertexCount() + 4 * (textElement.text.size + 1)); textElement.position = glyphPos; for(size_t i = 0; i < textElement.text.size; ++i) { sf::Uint32 codePoint = textElement.text[i]; - float kerning = font->getKerning(prevCodePoint, codePoint, characterSize); + float kerning = ff->getKerning(prevCodePoint, codePoint, characterSize); prevCodePoint = codePoint; glyphPos.x += kerning; @@ -222,10 +257,10 @@ namespace QuickMedia { case ' ': { - vertices[vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertexStart + 1] = { sf::Vector2f(glyphPos.x + hspace, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertexStart + 2] = { sf::Vector2f(glyphPos.x + hspace, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertexStart + 3] = { sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index][vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index][vertexStart + 1] = { sf::Vector2f(glyphPos.x + hspace, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index][vertexStart + 2] = { sf::Vector2f(glyphPos.x + hspace, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index][vertexStart + 3] = { sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; glyphPos.x += hspace; if(glyphPos.x > maxWidth * 0.5f) { @@ -236,10 +271,10 @@ namespace QuickMedia } case '\t': { - vertices[vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertexStart + 1] = { sf::Vector2f(glyphPos.x + hspace * TAB_WIDTH, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertexStart + 2] = { sf::Vector2f(glyphPos.x + hspace * TAB_WIDTH, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertexStart + 3] = { sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index][vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index][vertexStart + 1] = { sf::Vector2f(glyphPos.x + hspace * TAB_WIDTH, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index][vertexStart + 2] = { sf::Vector2f(glyphPos.x + hspace * TAB_WIDTH, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index][vertexStart + 3] = { sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; glyphPos.x += (hspace * TAB_WIDTH); if(glyphPos.x > maxWidth * 0.5f) { @@ -250,17 +285,17 @@ namespace QuickMedia } case '\n': { - vertices[vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertexStart + 1] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertexStart + 2] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertexStart + 3] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index][vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index][vertexStart + 1] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index][vertexStart + 2] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index][vertexStart + 3] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; glyphPos.x = 0.0f; glyphPos.y += floor(vspace + lineSpacing); continue; } } - const sf::Glyph &glyph = font->getGlyph(codePoint, characterSize, false); + const sf::Glyph &glyph = ff->getGlyph(codePoint, characterSize, false); if(glyphPos.x + glyph.advance > maxWidth) { // If there was a space in the text and text width is too long, then we need to word wrap at space index instead, @@ -271,7 +306,7 @@ namespace QuickMedia { for(size_t k = 0; k < 4; ++k) { - sf::Vector2f &vertexPos = vertices[vertexOffset + j * 4 + k].position; + sf::Vector2f &vertexPos = vertices[vertices_index][vertexOffset + j * 4 + k].position; vertexPos.x -= lastSpacingAccumulatedOffset; vertexPos.y += floor(vspace + lineSpacing); } @@ -299,18 +334,18 @@ namespace QuickMedia sf::Color fontColor = (textElement.type == TextElement::Type::TEXT ? color : urlColor); - vertices[vertexStart + 0] = { vertexTopLeft, fontColor, textureTopLeft }; - vertices[vertexStart + 1] = { vertexTopRight, fontColor, textureTopRight }; - vertices[vertexStart + 2] = { vertexBottomRight, fontColor, textureBottomRight }; - vertices[vertexStart + 3] = { vertexBottomLeft, fontColor, textureBottomLeft }; + vertices[vertices_index][vertexStart + 0] = { vertexTopLeft, fontColor, textureTopLeft }; + vertices[vertices_index][vertexStart + 1] = { vertexTopRight, fontColor, textureTopRight }; + vertices[vertices_index][vertexStart + 2] = { vertexBottomRight, fontColor, textureBottomRight }; + vertices[vertices_index][vertexStart + 3] = { vertexBottomLeft, fontColor, textureBottomLeft }; glyphPos.x += glyph.advance + characterSpacing; } - vertices[vertices.getVertexCount() - 4] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertices.getVertexCount() - 3] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertices.getVertexCount() - 2] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertices.getVertexCount() - 1] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index][vertices[vertices_index].getVertexCount() - 4] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index][vertices[vertices_index].getVertexCount() - 3] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index][vertices[vertices_index].getVertexCount() - 2] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index][vertices[vertices_index].getVertexCount() - 1] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; prevCodePoint = 0; } @@ -318,16 +353,19 @@ namespace QuickMedia boundingBox.height = glyphPos.y + lineSpacing; boundingBox.height += vspace; - usize numVertices = vertices.getVertexCount(); - for(usize i = 0; i < numVertices; i += 4) - { - const sf::Vertex &bottomRight = vertices[i + 2]; - boundingBox.width = std::max(boundingBox.width, bottomRight.position.x); + for(size_t vertices_index = 0; vertices_index < 2; ++vertices_index) { + usize numVertices = vertices[vertices_index].getVertexCount(); + for(usize i = 0; i < numVertices; i += 4) + { + const sf::Vertex &bottomRight = vertices[vertices_index][i + 2]; + boundingBox.width = std::max(boundingBox.width, bottomRight.position.x); + } } dirty = false; } +#if 0 void Text::updateCaret() { assert(!dirty && !dirtyText); @@ -339,6 +377,7 @@ namespace QuickMedia return; } + switch(caretMoveDirection) { case CaretMoveDirection::UP: @@ -380,13 +419,13 @@ namespace QuickMedia caretPosition = topLeftVertex.position; } } - + bool Text::isCaretAtEnd() const { assert(!dirty && !dirtyText); return textElements[0].text.size == 0 || caretIndex == (int)textElements[0].text.size; } - + // TODO: This can be optimized by using binary search int Text::getStartOfLine(int startIndex) const { @@ -604,21 +643,22 @@ namespace QuickMedia dirtyCaret = true; } } - +#endif bool Text::draw(sf::RenderTarget &target) { updateGeometry(); +#if 0 if(dirtyCaret || caretMoveDirection != CaretMoveDirection::NONE) { updateCaret(); dirtyCaret = false; caretMoveDirection = CaretMoveDirection::NONE; } +#endif float vspace = font->getLineSpacing(characterSize); - sf::RenderStates states; sf::Vector2f pos = position; pos.y += floor(vspace); // Origin is at bottom left, we want it to be at top left @@ -633,22 +673,29 @@ namespace QuickMedia if(!editable && visible && lastSeenTimer.getElapsedTime().asMilliseconds() > 3000) { visible = false; - vertices.resize(0); + vertices[0].resize(0); + vertices[1].resize(0); } return false; } - if(!visible) + if(!visible) { + visible = true; updateGeometry(true); + } - states.transform.translate(pos); - states.texture = &font->getTexture(characterSize); - target.draw(vertices, states); + const sf::Font *fonts[] = { font, cjk_font }; + for(size_t i = 0; i < 2; ++i) { + sf::RenderStates states; + states.transform.translate(pos); + states.texture = &fonts[i]->getTexture(characterSize); + target.draw(vertices[i], states); + } lastSeenTimer.restart(); - visible = true; pos.y -= floor(vspace); +#if 0 if(!editable) return true; //float rows = floor(totalHeight / (vspace + lineSpacing)); @@ -659,5 +706,8 @@ namespace QuickMedia caretRect.setPosition(sf::Vector2f(floor(pos.x + caretPosition.x), floor(pos.y + caretRow * (vspace + lineSpacing)))); target.draw(caretRect); return true; +#else + return true; +#endif } } -- cgit v1.2.3