Mercurial > hg > openttd
changeset 20676:7e084f1014bd draft
-Fix: Textbuf caret rendering for complex scripts (e.g. Tamil).
author | Michael Lutz <michi@icosahedron.de> |
---|---|
date | Wed, 17 Jul 2013 22:46:06 +0200 |
parents | 63f66b340b9a |
children | 9795e5780df4 |
files | src/gfx.cpp src/gfx_func.h src/gfx_layout.cpp src/gfx_layout.h src/textbuf.cpp src/textbuf_type.h |
diffstat | 6 files changed, 106 insertions(+), 25 deletions(-) [+] |
line wrap: on
line diff
--- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -708,6 +708,20 @@ } /** + * Get the leading corner of a character in a single-line string relative + * to the start of the string. + * @param str String containing the character. + * @param ch Pointer to the character in the string. + * @param start_fontsize Font size to start the text with. + * @return Upper left corner of the glyph associated with the character. + */ +Point GetCharPosInString(const char *str, const char *ch, FontSize start_fontsize) +{ + Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize); + return layout.GetCharPosition(ch); +} + +/** * Draw single character horizontally centered around (x,y) * @param c Character (glyph) to draw * @param x X position to draw character
--- a/src/gfx_func.h +++ b/src/gfx_func.h @@ -126,6 +126,7 @@ Dimension GetStringMultiLineBoundingBox(StringID str, const Dimension &suggestion); Dimension GetStringMultiLineBoundingBox(const char *str, const Dimension &suggestion); void LoadStringWidthTable(bool monospace = false); +Point GetCharPosInString(const char *str, const char *ch, FontSize start_fontsize = FS_NORMAL); void DrawDirtyBlocks(); void SetDirtyBlocks(int left, int top, int right, int bottom);
--- a/src/gfx_layout.cpp +++ b/src/gfx_layout.cpp @@ -422,8 +422,9 @@ * @param maxw The maximum width. * @param colour The colour of the font. * @param fontsize The size of font to use. + * @param caret Draw the caret at the position of this character. */ -Layouter::Layouter(const char *str, int maxw, TextColour colour, FontSize fontsize) +Layouter::Layouter(const char *str, int maxw, TextColour colour, FontSize fontsize) : string(str) { FontState state(colour, fontsize); WChar c = 0; @@ -513,6 +514,59 @@ } /** + * Get the position of a character in the layout. + * @param ch Character to get the position of. + * @return Upper left corner of the character relative to the start of the string. + * @note Will only work right for single-line strings. + */ +Point Layouter::GetCharPosition(const char *ch) const +{ + /* Find the code point index which corresponds to the char + * pointer into our UTF-8 source string. */ + size_t index = 0; + const char *str = this->string; + while (str < ch) { + WChar c; + size_t len = Utf8Decode(&c, str); + if (c == '\0' || c == '\n') break; + str += len; +#ifdef WITH_ICU + /* ICU uses UTF-16 internally which means we need to account for surrogate pairs. */ + index += len < 4 ? 1 : 2; +#else + index++; +#endif + } + + if (str == ch) { + /* Valid character. */ + const ParagraphLayout::Line *line = *this->Begin(); + + /* Pointer to the end-of-string marker? Return total line width. */ + if (*ch == '\0') { + Point p = { line->getWidth(), 0 }; + return p; + } + + /* Scan all runs until we've found found our code point index. */ + for (int run_index = 0; run_index < line->countRuns(); run_index++) { + const ParagraphLayout::VisualRun *run = line->getVisualRun(run_index); + + for (int i = 0; i < run->getGlyphCount(); i++) { + /* Matching glyph? Return position. */ + if ((size_t)run->getGlyphToCharMap()[i] == index) { + Point p = { run->getPositions()[i * 2], run->getPositions()[i * 2 + 1] }; + return p; + } + } + } + } + + Point p = { 0, 0 }; + return p; +} + +/** * Get a static font instance. */ Font *Layouter::GetFont(FontSize size, TextColour colour)
--- a/src/gfx_layout.h +++ b/src/gfx_layout.h @@ -168,6 +168,8 @@ typedef WChar CharType; ///< The type of character used within the layouter. #endif /* WITH_ICU */ + const char *string; ///< Pointer to the original string. + size_t AppendToBuffer(CharType *buff, const CharType *buffer_last, WChar c); ParagraphLayout *GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &fontMapping); @@ -209,6 +211,7 @@ public: Layouter(const char *str, int maxw = INT32_MAX, TextColour colour = TC_FROMSTRING, FontSize fontsize = FS_NORMAL); Dimension GetBounds(); + Point GetCharPosition(const char *ch) const; static void ResetFontCache(FontSize size); static void ResetLineCache();
--- a/src/textbuf.cpp +++ b/src/textbuf.cpp @@ -81,18 +81,17 @@ if (backspace) s = Utf8PrevChar(s); uint16 len = (uint16)Utf8Decode(&c, s); - uint width = GetCharacterWidth(FS_NORMAL, c); - - this->pixels -= width; - if (backspace) { - this->caretpos -= len; - this->caretxoffs -= width; - } /* Move the remaining characters over the marker */ memmove(s, s + len, this->bytes - (s - this->buf) - len); this->bytes -= len; this->chars--; + + this->UpdateWidth(); + if (backspace) { + this->caretpos -= len; + this->UpdateCaretPosition(); + } } /** @@ -159,17 +158,16 @@ */ bool Textbuf::InsertChar(WChar key) { - const byte charwidth = GetCharacterWidth(FS_NORMAL, key); uint16 len = (uint16)Utf8CharLen(key); if (this->bytes + len <= this->max_bytes && this->chars + 1 <= this->max_chars) { memmove(this->buf + this->caretpos + len, this->buf + this->caretpos, this->bytes - this->caretpos); Utf8Encode(this->buf + this->caretpos, key); this->chars++; this->bytes += len; - this->pixels += charwidth; + this->UpdateWidth(); this->caretpos += len; - this->caretxoffs += charwidth; + this->UpdateCaretPosition(); return true; } return false; @@ -187,7 +185,7 @@ if (!GetClipboardContents(utf8_buf, lengthof(utf8_buf))) return false; - uint16 pixels = 0, bytes = 0, chars = 0; + uint16 bytes = 0, chars = 0; WChar c; for (const char *ptr = utf8_buf; (c = Utf8Consume(&ptr)) != '\0';) { if (!IsValidChar(c, this->afilter)) break; @@ -196,9 +194,6 @@ if (this->bytes + bytes + len > this->max_bytes) break; if (this->chars + chars + 1 > this->max_chars) break; - byte char_pixels = GetCharacterWidth(FS_NORMAL, c); - - pixels += char_pixels; bytes += len; chars++; } @@ -207,8 +202,6 @@ memmove(this->buf + this->caretpos + bytes, this->buf + this->caretpos, this->bytes - this->caretpos); memcpy(this->buf + this->caretpos, utf8_buf, bytes); - this->pixels += pixels; - this->caretxoffs += pixels; this->bytes += bytes; this->chars += chars; @@ -217,6 +210,9 @@ assert(this->chars <= this->max_chars); this->buf[this->bytes - 1] = '\0'; // terminating zero + this->UpdateWidth(); + this->UpdateCaretPosition(); + return true; } @@ -242,7 +238,7 @@ const char *s = Utf8PrevChar(this->buf + this->caretpos); Utf8Decode(&c, s); this->caretpos = s - this->buf; - this->caretxoffs -= GetCharacterWidth(FS_NORMAL, c); + this->UpdateCaretPosition(); return c; } @@ -267,12 +263,24 @@ WChar c; this->caretpos += (uint16)Utf8Decode(&c, this->buf + this->caretpos); - this->caretxoffs += GetCharacterWidth(FS_NORMAL, c); + this->UpdateCaretPosition(); Utf8Decode(&c, this->buf + this->caretpos); return c; } +/** Update pixel width of the text. */ +void Textbuf::UpdateWidth() +{ + this->pixels = GetStringBoundingBox(this->buf, FS_NORMAL).width; +} + +/** Update pixel position of the caret. */ +void Textbuf::UpdateCaretPosition() +{ + this->caretxoffs = this->chars > 1 ? GetCharPosInString(this->buf, this->buf + this->caretpos, FS_NORMAL).x : 0; +} + /** * Handle text navigation with arrow keys left/right. * This defines where the caret will blink and the next character interaction will occur @@ -336,12 +344,12 @@ case WKC_HOME: this->caretpos = 0; - this->caretxoffs = 0; + this->UpdateCaretPosition(); return true; case WKC_END: this->caretpos = this->bytes - 1; - this->caretxoffs = this->pixels; + this->UpdateCaretPosition(); return true; default: @@ -418,21 +426,20 @@ { const char *buf = this->buf; - this->pixels = 0; this->chars = this->bytes = 1; // terminating zero WChar c; while ((c = Utf8Consume(&buf)) != '\0') { - this->pixels += GetCharacterWidth(FS_NORMAL, c); this->bytes += Utf8CharLen(c); this->chars++; } - assert(this->bytes <= this->max_bytes); assert(this->chars <= this->max_chars); this->caretpos = this->bytes - 1; - this->caretxoffs = this->pixels; + this->UpdateWidth(); + + this->UpdateCaretPosition(); } /**