From ecb9da86aa95e93b04085285d49352055f78b10d Mon Sep 17 00:00:00 2001 From: Noel Berry Date: Fri, 8 Jan 2021 12:01:39 -0800 Subject: [PATCH 1/7] batcher and spritefont support utf8 strings --- include/blah/containers/str.h | 8 +++++ src/containers/str.cpp | 44 +++++++++++++++++++++++ src/drawing/batch.cpp | 24 ++++++++----- src/drawing/spritefont.cpp | 66 +++++++++++++++++++++++++---------- 4 files changed, 114 insertions(+), 28 deletions(-) diff --git a/include/blah/containers/str.h b/include/blah/containers/str.h index 2d45097..f372b24 100644 --- a/include/blah/containers/str.h +++ b/include/blah/containers/str.h @@ -75,6 +75,14 @@ namespace Blah // ensures the string has the given capacity void reserve(int capacity); + + // Returns the unicode value at the given index. + // Assumes the index is a valid utf8 starting point. + uint32_t utf8_at(int index) const; + + // Returns the byte-length of the utf8 character. + // Assumes the index is a valid utf8 starting point. + int utf8_length(int index) const; // appends the given character Str& append(char c); diff --git a/src/containers/str.cpp b/src/containers/str.cpp index c5ad1e9..0da9db1 100644 --- a/src/containers/str.cpp +++ b/src/containers/str.cpp @@ -79,6 +79,50 @@ void Str::set_length(int len) m_length = len; } +uint32_t Str::utf8_at(int index) const +{ + uint32_t charcode = 0; + + int t = (unsigned char)(this->operator[](index++)); + if (t < 128) + return t; + + int high_bit_mask = (1 << 6) - 1; + int high_bit_shift = 0; + int total_bits = 0; + int other_bits = 6; + + while ((t & 0xC0) == 0xC0) + { + t <<= 1; + t &= 0xff; + total_bits += 6; + high_bit_mask >>= 1; + high_bit_shift++; + charcode <<= other_bits; + charcode |= ((unsigned char)(this->operator[](index++))) & ((1 << other_bits) - 1); + } + charcode |= ((t >> high_bit_shift) & high_bit_mask) << total_bits; + + return charcode; +} + +int Str::utf8_length(int index) const +{ + auto c = this->operator[](index); + if ((c & 0xFE) == 0xFC) + return 6; + if ((c & 0xFC) == 0xF8) + return 5; + if ((c & 0xF8) == 0xF0) + return 4; + else if ((c & 0xF0) == 0xE0) + return 3; + else if ((c & 0xE0) == 0xC0) + return 2; + return 1; +} + Str& Str::append(char c) { reserve(m_length + 1); diff --git a/src/drawing/batch.cpp b/src/drawing/batch.cpp index d67df25..6fd527f 100644 --- a/src/drawing/batch.cpp +++ b/src/drawing/batch.cpp @@ -1046,6 +1046,7 @@ void Batch::str(const SpriteFont& font, const String& text, const Vec2& pos, Tex else offset.y = (font.ascent + font.descent + font.height() - font.height_of(text)) * 0.5f; + uint32_t last = 0; for (int i = 0, l = text.length(); i < l; i++) { if (text[i] == '\n') @@ -1061,29 +1062,34 @@ void Batch::str(const SpriteFont& font, const String& text, const Vec2& pos, Tex else offset.x = -font.width_of_line(text, i + 1) * 0.5f; + last = 0; continue; } - // TODO: - // This doesn't parse Unicode! - // It will assume it's a 1-byte ASCII char which is incorrect - const auto& ch = font[text[i]]; + // get the character + uint32_t next = text.utf8_at(i); + const auto& ch = font[next]; + // draw it, if the subtexture exists if (ch.subtexture.texture) { Vec2 at = offset + ch.offset; if (i > 0 && text[i - 1] != '\n') - { - // TODO: - // This doesn't parse Unicode! - at.x += font.get_kerning(text[i - 1], text[i]); - } + at.x += font.get_kerning(last, next); tex(ch.subtexture, at, color); } + // move forward offset.x += ch.advance; + + // increment past current character + // (minus 1 since the for loop iterator increments as well) + i += text.utf8_length(i) - 1; + + // keep last codepoint for next char for kerning + last = next; } pop_matrix(); diff --git a/src/drawing/spritefont.cpp b/src/drawing/spritefont.cpp index cff0a63..86b6d0a 100644 --- a/src/drawing/spritefont.cpp +++ b/src/drawing/spritefont.cpp @@ -77,18 +77,33 @@ SpriteFont& SpriteFont::operator=(SpriteFont && src) noexcept float SpriteFont::width_of(const String& text) const { float width = 0; - float lineWidth = 0; - for (auto it = text.begin(); it != text.end(); it++) + float line_width = 0; + + uint32_t last; + for (int i = 0; i < text.length(); i ++) { - if (*it == '\n') - lineWidth = 0; + if (text[i] == '\n') + { + line_width = 0; + continue; + } - // TODO: this doesn't account for Unicode values! - uint32_t codepoint = *it; + // get codepoint + auto next = text.utf8_at(i); - lineWidth += this->operator[](codepoint).advance; - if (lineWidth > width) - width = lineWidth; + // increment length + line_width += this->operator[](next).advance; + + // add kerning + if (i > 0) + line_width += get_kerning(last, next); + + if (line_width > width) + width = line_width; + + // move to thext utf8 character + i += text.utf8_length(i) - 1; + last = next; } return width; @@ -99,19 +114,31 @@ float SpriteFont::width_of_line(const String& text, int start) const if (start < 0) return 0; if (start >= text.length()) return 0; - float lineWidth = 0; - for (auto it = text.begin() + start; it != text.end(); it++) + float width = 0; + + uint32_t last; + for (int i = start; i < text.length(); i ++) { - if (*it == '\n') - return lineWidth; + if (text[i] == '\n') + return width; - // TODO: this doesn't account for Unicode values! - uint32_t codepoint = *it; + // get codepoint + auto next = text.utf8_at(i); - lineWidth += this->operator[](codepoint).advance; + // increment length + width += this->operator[](next).advance; + + // add kerning + if (i > 0) + width += get_kerning(last, next); + + // move to thext utf8 character + i += text.utf8_length(i) - 1; + + last = next; } - return lineWidth; + return width; } float SpriteFont::height_of(const String& text) const @@ -120,10 +147,11 @@ float SpriteFont::height_of(const String& text) const return 0; float height = line_height(); - for (auto it = text.begin(); it != text.end(); it++) + for (int i = 0; i < text.length(); i ++) { - if (*it == '\n') + if (text[i] == '\n') height += line_height(); + i += text.utf8_length(i) - 1; } return height - line_gap; From 6aa9d3b41272a611fc47b4073c0788faf9074bae Mon Sep 17 00:00:00 2001 From: Noel Berry Date: Mon, 11 Jan 2021 00:25:34 -0800 Subject: [PATCH 2/7] stackvector fix --- include/blah/containers/stackvector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/blah/containers/stackvector.h b/include/blah/containers/stackvector.h index 671c2f9..e7e7130 100644 --- a/include/blah/containers/stackvector.h +++ b/include/blah/containers/stackvector.h @@ -146,7 +146,7 @@ namespace Blah return &data()[count]; } - return m_buffer; + return (T*)m_buffer; } template From 1063f1ff29681c2ed60c663bcbc28ddc634d4479 Mon Sep 17 00:00:00 2001 From: Noel Berry Date: Mon, 11 Jan 2021 00:25:51 -0800 Subject: [PATCH 3/7] vec2 bool comparisons should be const --- include/blah/math/vec2.h | 4 ++-- src/math/vec2.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/blah/math/vec2.h b/include/blah/math/vec2.h index 8f4c51a..4e872b5 100644 --- a/include/blah/math/vec2.h +++ b/include/blah/math/vec2.h @@ -31,8 +31,8 @@ namespace Blah Vec2& operator /=(float rhs); Vec2& operator *=(float rhs); - bool operator ==(const Vec2& rhs); - bool operator !=(const Vec2& rhs); + bool operator ==(const Vec2& rhs) const; + bool operator !=(const Vec2& rhs) const; Vec2 normal() const; Vec2 turn_right() const; diff --git a/src/math/vec2.cpp b/src/math/vec2.cpp index 4518fad..6ad6c82 100644 --- a/src/math/vec2.cpp +++ b/src/math/vec2.cpp @@ -25,8 +25,8 @@ Vec2& Vec2::operator *=(const Vec2& rhs) { x *= rhs.x; y *= rhs.y; return *this; Vec2& Vec2::operator/=(float rhs) { x /= rhs; y /= rhs; return *this; } Vec2& Vec2::operator*=(float rhs) { x *= rhs; y *= rhs; return *this; } -bool Vec2::operator ==(const Vec2& rhs) { return x == rhs.x && y == rhs.y; } -bool Vec2::operator !=(const Vec2& rhs) { return x != rhs.x || y != rhs.y; } +bool Vec2::operator ==(const Vec2& rhs) const { return x == rhs.x && y == rhs.y; } +bool Vec2::operator !=(const Vec2& rhs) const { return x != rhs.x || y != rhs.y; } Vec2 Vec2::normal() const { From ded52d40aa0ab64cc89fce67f64f8b1b2bbcff6f Mon Sep 17 00:00:00 2001 From: Noel Berry Date: Tue, 12 Jan 2021 20:57:14 -0800 Subject: [PATCH 4/7] removed Stream::write_cstr and added conditions to Stream::write numeric --- include/blah/streams/stream.h | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/include/blah/streams/stream.h b/include/blah/streams/stream.h index 2b19a1f..c49af69 100644 --- a/include/blah/streams/stream.h +++ b/include/blah/streams/stream.h @@ -97,27 +97,21 @@ namespace Blah return write_from(buffer, length); } - // writes a null-terminated string, and returns the amount written - int64_t write_cstr(const Str& string) + // writes the contents of a string to the stream + int64_t write(const String& string) { - return write(string.cstr(), string.length() + 1); - } - - // writes a null-terminated string, and returns the amount written - int64_t write_cstr(const char* cstr) - { - return write(cstr, strlen(cstr) + 1); + return write_from(string.begin(), string.length()); } // writes a number - template + template::value, T>::type> int64_t write(const T& value) { return write(value, Endian::Little); } // writes a number - template + template::value, T>::type> int64_t write(const T& value, Endian endian) { T writing = value; From 51e08f0efebcb41508c3f281f7b9e5a0d2a9c95e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Acc=C3=A1cio=20Nogueira?= Date: Wed, 13 Jan 2021 09:38:31 +0100 Subject: [PATCH 5/7] update links --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a6da4b7..9326014 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,14 @@ Goal is to be simple and use as few dependencies as possible, to maintain easy b #### building - Requires C++17 and CMake 3.12+ - Platform Backend - - [SDL2](https://github.com/NoelFB/blah/blob/master/private/blah/internal/platform_backend_sdl2.cpp) can be enabled in CMake with `SDL2_ENABLED`, and setting `SDL2_INCLUDE_DIRS` and `SDL2_LIBRARIES` + - [SDL2](https://github.com/NoelFB/blah/blob/master/src/internal/platform_backend_sdl2.cpp) can be enabled in CMake with `SDL2_ENABLED`, and setting `SDL2_INCLUDE_DIRS` and `SDL2_LIBRARIES` - Graphics Backend - - [OpenGL](https://github.com/NoelFB/blah/blob/master/private/blah/internal/graphics_backend_gl.cpp) can be enabled in CMake with `OPENGL_ENABLED`. - - [D3D11](https://github.com/NoelFB/blah/blob/master/private/blah/internal/graphics_backend_d3d11.cpp) (unfinished) can be enabled in CMake with `D3D11_ENABLED`. - - Other backends can be added by implementing the [Platform Backend](https://github.com/NoelFB/blah/blob/master/private/blah/internal/platform_backend.h) or [Graphics Backend](https://github.com/NoelFB/blah/blob/master/private/blah/internal/graphics_backend.h). - + - [OpenGL](https://github.com/NoelFB/blah/blob/master/src/internal/graphics_backend_gl.cpp) can be enabled in CMake with `OPENGL_ENABLED`. + - [D3D11](https://github.com/NoelFB/blah/blob/master/src/internal/graphics_backend_d3d11.cpp) (unfinished) can be enabled in CMake with `D3D11_ENABLED`. + - Other backends can be added by implementing the [Platform Backend](https://github.com/NoelFB/blah/blob/master/src/internal/platform_backend.h) or [Graphics Backend](https://github.com/NoelFB/blah/blob/master/src/internal/graphics_backend.h). + #### notes - - There's no Shader abstraction, so the [Sprite Batcher](https://github.com/NoelFB/blah/blob/master/public/blah/drawing/batch.h) has hard-coded GLSL/HLSL. This will need to change. + - There's no Shader abstraction, so the [Sprite Batcher](https://github.com/NoelFB/blah/blob/master/include/blah/drawing/batch.h) has hard-coded GLSL/HLSL. This will need to change. - Only floatN/mat3x2/mat4x4 uniforms are supported. - There's no Audio API or backend implementation yet. - No threaded rendering so it will explode if you try that. From 261e2fd5e7461bba107033d3a68096327e46e8d1 Mon Sep 17 00:00:00 2001 From: Noel Berry Date: Thu, 14 Jan 2021 12:21:59 -0800 Subject: [PATCH 6/7] stream.h cleanup & commenting --- include/blah/streams/stream.h | 50 ++++++----------------------------- src/streams/stream.cpp | 43 +++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 43 deletions(-) diff --git a/include/blah/streams/stream.h b/include/blah/streams/stream.h index c49af69..369dade 100644 --- a/include/blah/streams/stream.h +++ b/include/blah/streams/stream.h @@ -43,45 +43,20 @@ namespace Blah int64_t read(void* buffer, int64_t length) { return read_into(buffer, length); } // reads a string. if length < 0, assumes null-terminated - String read_string(int length = -1) - { - String result; + String read_string(int length = -1); - if (length < 0) - { - char next; - while (read(&next, 1) && next != '\0') - result.append(next); - } - else - { - result.set_length(length); - read_into(result.cstr(), length); - } - - return result; - } - - String read_line() - { - String result; - - char next; - while (read(&next, 1) && next != '\n' && next != '\0') - result.append(next); - - return result; - } + // reads a string until a newline '\n' or null-terminator '\0' is found + String read_line(); // reads a number - template + template::value, T>::type> T read() { return read(Endian::Little); } // reads a number - template + template::value, T>::type> T read(Endian endian) { T result; @@ -92,16 +67,10 @@ namespace Blah } // writes the amount of bytes to the stream from the given buffer, and returns the amount written - int64_t write(const void* buffer, int64_t length) - { - return write_from(buffer, length); - } + int64_t write(const void* buffer, int64_t length); // writes the contents of a string to the stream - int64_t write(const String& string) - { - return write_from(string.begin(), string.length()); - } + int64_t write(const String& string); // writes a number template::value, T>::type> @@ -129,7 +98,4 @@ namespace Blah // writes from the stream from the given buffer, and returns the number of bytes written virtual int64_t write_from(const void* buffer, int64_t length) = 0; }; -} - -#undef BLAH_SWAP_ENDIAN -#undef BLAH_BIG_ENDIAN \ No newline at end of file +} \ No newline at end of file diff --git a/src/streams/stream.cpp b/src/streams/stream.cpp index 682b0a6..b393537 100644 --- a/src/streams/stream.cpp +++ b/src/streams/stream.cpp @@ -26,4 +26,45 @@ int64_t Stream::pipe(Stream& stream, int64_t length) } return result; -} \ No newline at end of file +} + +// reads a string. if length < 0, assumes null-terminated +String Stream::read_string(int length) +{ + String result; + + if (length < 0) + { + char next; + while (read(&next, 1) && next != '\0') + result.append(next); + } + else + { + result.set_length(length); + read_into(result.cstr(), length); + } + + return result; +} + +String Stream::read_line() +{ + String result; + + char next; + while (read(&next, 1) && next != '\n' && next != '\0') + result.append(next); + + return result; +} + +int64_t Stream::write(const void* buffer, int64_t length) +{ + return write_from(buffer, length); +} + +int64_t Stream::write(const String& string) +{ + return write_from(string.begin(), string.length()); +} From 69e5e7447bd2bb53db1901a96843e736b567d49f Mon Sep 17 00:00:00 2001 From: Noel Berry Date: Thu, 14 Jan 2021 18:42:12 -0800 Subject: [PATCH 7/7] added Path::join utility method --- include/blah/core/filesystem.h | 7 +++++++ src/core/filesystem.cpp | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/include/blah/core/filesystem.h b/include/blah/core/filesystem.h index 4025f90..7e3790a 100644 --- a/include/blah/core/filesystem.h +++ b/include/blah/core/filesystem.h @@ -37,5 +37,12 @@ namespace Blah FilePath get_path_after(const FilePath& path, const FilePath& after); FilePath get_directory_name(const FilePath& path); FilePath normalize(const FilePath& path); + FilePath join(const FilePath& a, const FilePath& b); + + template + FilePath join(const FilePath& a, const FilePath& b, const Args&... args) + { + return join(a, join(b, args...)); + } } } \ No newline at end of file diff --git a/src/core/filesystem.cpp b/src/core/filesystem.cpp index fa10728..e88f52d 100644 --- a/src/core/filesystem.cpp +++ b/src/core/filesystem.cpp @@ -133,4 +133,9 @@ FilePath Path::normalize(const FilePath& path) } return normalized; +} + +FilePath Path::join(const FilePath& a, const FilePath& b) +{ + return normalize(FilePath(a).append("/").append(b)); } \ No newline at end of file