From acb9054dcb9bcd98cc5dde49e6cb4e5f48456c17 Mon Sep 17 00:00:00 2001 From: Benau Date: Sun, 9 Jun 2019 11:26:00 +0800 Subject: [PATCH] Use libraqm for text layout --- lib/irrlicht/include/GlyphLayout.h | 304 +++++++++++++ lib/irrlicht/include/IGUIFont.h | 34 ++ lib/irrlicht/include/IGUIStaticText.h | 9 + lib/irrlicht/source/Irrlicht/CGUIFont.h | 19 + .../source/Irrlicht/CGUIStaticText.cpp | 301 ++----------- lib/irrlicht/source/Irrlicht/CGUIStaticText.h | 11 +- src/addons/zip.cpp | 1 + src/config/hardware_stats.cpp | 1 + src/config/user_config.cpp | 1 + src/font/bold_face.hpp | 2 +- src/font/digit_face.hpp | 3 +- src/font/face_ttf.hpp | 50 +-- src/font/font_manager.cpp | 419 +++++++++++++++++- src/font/font_manager.hpp | 36 +- src/font/font_with_face.cpp | 317 +++++++++---- src/font/font_with_face.hpp | 42 +- src/graphics/irr_driver.cpp | 14 +- src/graphics/stk_text_billboard.cpp | 8 +- src/guiengine/scalable_font.cpp | 80 +++- src/guiengine/scalable_font.hpp | 27 +- src/guiengine/widgets/spinner_widget.cpp | 1 + src/io/xml_node.cpp | 1 + src/network/network.cpp | 1 + src/network/rewind_info.cpp | 1 + src/network/server_config.cpp | 2 + src/network/transport_address.cpp | 1 + src/race/grand_prix_manager.cpp | 1 + src/race/highscore_manager.cpp | 1 + src/race/highscores.cpp | 1 + src/tracks/track.hpp | 1 + src/utils/string_utils.cpp | 165 ++++++- src/utils/string_utils.hpp | 30 +- src/utils/time.cpp | 1 + src/utils/utf8/checked.h | 48 ++ src/utils/utf8/unchecked.h | 35 ++ 35 files changed, 1510 insertions(+), 459 deletions(-) create mode 100644 lib/irrlicht/include/GlyphLayout.h diff --git a/lib/irrlicht/include/GlyphLayout.h b/lib/irrlicht/include/GlyphLayout.h new file mode 100644 index 000000000..c47c541da --- /dev/null +++ b/lib/irrlicht/include/GlyphLayout.h @@ -0,0 +1,304 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __GLYPH_LAYOUT_H_INCLUDED__ +#define __GLYPH_LAYOUT_H_INCLUDED__ + +#include "irrTypes.h" +#include "dimension2d.h" + +#include +#include +#include + +namespace irr +{ +namespace gui +{ + +enum GlyphLayoutFlag +{ +GLF_RTL_LINE = 1, /* This line from this glyph is RTL. */ +GLF_RTL_CHAR = 2, /* This character(s) from this glyph is RTL. */ +GLF_BREAKABLE = 4, /* This glyph is breakable when line breaking. */ +GLF_QUICK_DRAW = 8, /* This glyph is not created by libraqm, which get x_advance_x directly from font. */ +GLF_NEWLINE = 16 /* This glyph will start a newline. */ +}; + +//! GlyphLayout copied from libraqm. +struct GlyphLayout +{ +u32 index; +s32 x_advance; +//s32 y_advance; we don't use y_advance atm +s32 x_offset; +s32 y_offset; +/* Above variable is same for raqm_glyph_t */ +// If some characters share the same glyph +std::vector cluster; +//! used to sorting back the visual order after line breaking +u32 original_index; +u16 flags; +//! this is the face_idx used in stk face ttf +u16 face_idx; +}; + +namespace Private +{ + inline void breakLine(std::vector gls, f32 max_line_width, + f32 inverse_shaping, f32 scale, std::vector >& result); +} + +inline core::dimension2d getGlyphLayoutsDimension( + const std::vector& gls, s32 height_per_line, f32 inverse_shaping, + f32 scale, s32 cluster = -1) +{ + core::dimension2d dim(0.0f, 0.0f); + core::dimension2d this_line(0.0f, height_per_line); + + for (unsigned i = 0; i < gls.size(); i++) + { + const GlyphLayout& glyph = gls[i]; + if ((glyph.flags & GLF_NEWLINE) != 0) + { + dim.Height += this_line.Height; + if (dim.Width < this_line.Width) + dim.Width = this_line.Width; + this_line.Width = 0; + continue; + } + f32 cur_width = (s32)(glyph.x_advance * inverse_shaping) * scale; + bool found_cluster = false; + // Cursor positioning + if (cluster != -1) + { + auto it = std::find(glyph.cluster.begin(), glyph.cluster.end(), cluster); + if (it != glyph.cluster.end() && + (i == gls.size() - 1 || cluster != gls[i + 1].cluster.front())) + { + found_cluster = true; + // Get cluster ratio to total glyph width, so for example + // cluster 0 in ffi glyph will be 0.333 + f32 ratio = f32(it - glyph.cluster.begin() + 1) / + (f32)glyph.cluster.size(); + // Show cursor in left side, so no need to add width + if ((glyph.flags & GLF_RTL_CHAR) != 0) + cur_width = 0; + else + cur_width *= ratio; + } + } + this_line.Width += cur_width; + if (found_cluster) + break; + } + + dim.Height += this_line.Height; + if (dim.Width < this_line.Width) + dim.Width = this_line.Width; + + core::dimension2d ret_dim(0, 0); + ret_dim.Width = (u32)(dim.Width + 0.9f); // round up + ret_dim.Height = (u32)(dim.Height + 0.9f); + + return ret_dim; +} + +inline s32 getCurosrFromDimension(f32 x, f32 y, + const std::vector& gls, s32 height_per_line, + f32 inverse_shaping, f32 scale) +{ + if (gls.empty()) + return 0; + f32 total_width = 0.0f; + for (unsigned i = 0; i < gls.size(); i++) + { + const GlyphLayout& glyph = gls[i]; + if ((glyph.flags & GLF_NEWLINE) != 0) + { + // TODO: handling newline + break; + } + f32 cur_width = (s32)(glyph.x_advance * inverse_shaping) * scale; + if (glyph.cluster.size() == 1) + { + if (i == 0 && cur_width * 0.5 > x) + return 0; + if (total_width + cur_width * 0.5 > x) + return glyph.cluster.front(); + } + else if (total_width + cur_width > x) + { + // Handle glyph like 'ffi' + f32 each_cluster_width = cur_width / (f32)glyph.cluster.size(); + f32 remain_width = x - total_width; + total_width = 0.0f; + for (unsigned j = 0; j < glyph.cluster.size(); j++) + { + if (total_width + each_cluster_width * 0.5 > remain_width) + return glyph.cluster[j]; + total_width += each_cluster_width; + } + return glyph.cluster.back(); + } + total_width += cur_width; + } + return gls.back().cluster.back() + 1; +} + +inline std::vector getGlyphLayoutsWidthPerLine( + const std::vector& gls, f32 inverse_shaping, f32 scale) +{ + std::vector result; + f32 cur_width = 0.0f; + for (auto& glyph : gls) + { + if ((glyph.flags & GLF_NEWLINE) != 0) + { + result.push_back(cur_width); + cur_width = 0; + continue; + } + cur_width += (s32)(glyph.x_advance * inverse_shaping) * scale; + } + + result.push_back(cur_width); + return result; +} + +inline void breakGlyphLayouts(std::vector& gls, f32 max_line_width, + f32 inverse_shaping, f32 scale) +{ + if (gls.size() < 2) + return; + std::vector > broken_line; + u32 start = 0; + for (u32 i = 0; i < gls.size(); i++) + { + GlyphLayout& glyph = gls[i]; + if ((glyph.flags & GLF_NEWLINE) != 0) + { + Private::breakLine({ gls.begin() + start, gls.begin() + i}, + max_line_width, inverse_shaping, scale, broken_line); + start = i + 1; + } + } + if (start - gls.size() - 1 > 0) + { + Private::breakLine({ gls.begin() + start, gls.begin() + gls.size() }, + max_line_width, inverse_shaping, scale, broken_line); + } + + gls.clear(); + // Sort glyphs in original order + for (u32 i = 0; i < broken_line.size(); i++) + { + if (i != 0) + { + gui::GlyphLayout gl = { 0 }; + gl.flags = gui::GLF_NEWLINE; + gls.push_back(gl); + } + auto& line = broken_line[i]; + std::sort(line.begin(), line.end(), [] + (const irr::gui::GlyphLayout& a_gi, + const irr::gui::GlyphLayout& b_gi) + { + return a_gi.original_index < b_gi.original_index; + }); + for (auto& glyph : line) + gls.push_back(glyph); + } +} + +namespace Private +{ + /** Used it only for single line (ie without line breaking mark). */ + inline f32 getGlyphLayoutsWidth(const std::vector& gls, + f32 inverse_shaping, f32 scale) + { + return std::accumulate(gls.begin(), gls.end(), 0, + [inverse_shaping, scale] (const f32 previous, + const irr::gui::GlyphLayout& cur_gi) + { + return previous + (s32)(cur_gi.x_advance * inverse_shaping) * scale; + }); + } + + inline void breakLine(std::vector gls, f32 max_line_width, + f32 inverse_shaping, f32 scale, + std::vector >& result) + { + const f32 line_size = getGlyphLayoutsWidth(gls, inverse_shaping, scale); + if (line_size <= max_line_width) + { + result.emplace_back(std::move(gls)); + return; + } + + // Sort glyphs in logical order + std::sort(gls.begin(), gls.end(), [] + (const GlyphLayout& a_gi, const GlyphLayout& b_gi) + { + return a_gi.cluster.front() < b_gi.cluster.front(); + }); + + u32 end = 0; + s32 start = 0; + f32 total_width = 0.0f; + + for (; end < gls.size(); end++) + { + f32 cur_width = (s32)(gls[end].x_advance * inverse_shaping) * scale; + if (cur_width > max_line_width) + { + // Very large glyph + result.push_back({gls[end]}); + start = end; + } + else if (cur_width + total_width <= max_line_width) + { + total_width += cur_width; + } + else + { + int break_point = end - 1; + do + { + if (break_point <= 0 || break_point == start) + { + // Forcely break at line ending position if no break + // mark, this fix text without space of out network + // lobby + result.push_back( + {gls.begin() + start, gls.begin() + end}); + start = end; + total_width = (s32)(gls[end].x_advance * inverse_shaping) * scale; + break; + } + if ((gls[break_point].flags & GLF_BREAKABLE) != 0) + { + result.push_back( + {gls.begin() + start, gls.begin() + break_point + 1}); + end = start = break_point + 1; + total_width = (s32)(gls[end].x_advance * inverse_shaping) * scale; + break; + } + break_point--; + } + while (break_point >= start); + } + } + if (gls.begin() + start != gls.end()) + { + result.push_back({gls.begin() + start, gls.end()}); + } + } +} + +} // end namespace gui +} // end namespace irr + +#endif + diff --git a/lib/irrlicht/include/IGUIFont.h b/lib/irrlicht/include/IGUIFont.h index 4746c81a7..dd371693a 100644 --- a/lib/irrlicht/include/IGUIFont.h +++ b/lib/irrlicht/include/IGUIFont.h @@ -10,10 +10,14 @@ #include "rect.h" #include "irrString.h" +#include +#include + namespace irr { namespace gui { + struct GlyphLayout; //! An enum for the different types of GUI font. enum EGUI_FONT_TYPE @@ -52,6 +56,23 @@ public: video::SColor color, bool hcenter=false, bool vcenter=false, const core::rect* clip=0) = 0; + //! Draws glyphs and clips it to the specified rectangle if wanted. + /** \param gls: List of GlyphLaout + \param position: Rectangle specifying position where to draw the text. + \param color: Color of the text + \param hcenter: Specifies if the text should be centered horizontally into the rectangle. + \param vcenter: Specifies if the text should be centered vertically into the rectangle. + \param clip: Optional pointer to a rectangle against which the text will be clipped. + If the pointer is null, no clipping will be done. */ + virtual void draw(const std::vector& gls, const core::rect& position, + video::SColor color, bool hcenter=false, bool vcenter=false, + const core::rect* clip=0) = 0; + + //! Draws some text and clips it to the specified rectangle if wanted (without text shaping) + virtual void drawQuick(const core::stringw& text, const core::rect& position, + video::SColor color, bool hcenter=false, + bool vcenter=false, const core::rect* clip=0) = 0; + //! Calculates the width and height of a given string of text. /** \return Returns width and height of the area covered by the text if it would be drawn. */ @@ -89,12 +110,25 @@ public: //! Returns the distance between letters virtual s32 getKerningHeight() const = 0; + //! Returns the height per line (including padding between 2 lines) + virtual s32 getHeightPerLine() const = 0; + //! Define which characters should not be drawn by the font. /** For example " " would not draw any space which is usually blank in most fonts. \param s String of symbols which are not send down to the videodriver */ virtual void setInvisibleCharacters( const wchar_t *s ) = 0; + + //! Convert text to glyph layouts for fast rendering with caching enabled + /* If line_data is not null, each broken line u32string will be saved and + can be used for advanced glyph and text mapping, and cache will be disabled. */ + virtual void initGlyphLayouts(const core::stringw& text, + std::vector& gls, std::vector* line_data = NULL) = 0; + + virtual f32 getInverseShaping() const = 0; + virtual f32 getScale() const = 0; + virtual void setScale(f32 scale) = 0; }; } // end namespace gui diff --git a/lib/irrlicht/include/IGUIStaticText.h b/lib/irrlicht/include/IGUIStaticText.h index 30325bc81..1b767669f 100644 --- a/lib/irrlicht/include/IGUIStaticText.h +++ b/lib/irrlicht/include/IGUIStaticText.h @@ -8,11 +8,14 @@ #include "IGUIElement.h" #include "SColor.h" +#include + namespace irr { namespace gui { class IGUIFont; + struct GlyphLayout; //! Multi or single line text label. class IGUIStaticText : public IGUIElement @@ -125,6 +128,12 @@ namespace gui //! Checks whether the text in this element should be interpreted as right-to-left virtual bool isRightToLeft() const = 0; + + virtual const std::vector& getGlyphLayouts() const = 0; + virtual void setGlyphLayouts(std::vector& gls) = 0; + virtual void clearGlyphLayouts() = 0; + virtual void setUseGlyphLayoutsOnly(bool gls_only) = 0; + virtual bool useGlyphLayoutsOnly() const = 0; }; diff --git a/lib/irrlicht/source/Irrlicht/CGUIFont.h b/lib/irrlicht/source/Irrlicht/CGUIFont.h index ada89c854..713e4f053 100644 --- a/lib/irrlicht/source/Irrlicht/CGUIFont.h +++ b/lib/irrlicht/source/Irrlicht/CGUIFont.h @@ -53,6 +53,20 @@ public: video::SColor color, bool hcenter=false, bool vcenter=false, const core::rect* clip=0); + virtual void drawQuick(const core::stringw& text, const core::rect& position, + video::SColor color, bool hcenter=false, + bool vcenter=false, const core::rect* clip=0) + { + draw(text, position, color, hcenter, vcenter, clip); + } + + virtual void draw(const std::vector& gls, const core::rect& position, + video::SColor color, bool hcenter=false, + bool vcenter=false, const core::rect* clip=0) {} + + virtual void initGlyphLayouts(const core::stringw& text, + std::vector& gls, std::vector* line_data = NULL) {} + //! returns the dimension of a text virtual core::dimension2d getDimension(const wchar_t* text) const; @@ -66,6 +80,8 @@ public: virtual void setKerningWidth (s32 kerning); virtual void setKerningHeight (s32 kerning); + virtual s32 getHeightPerLine() const { return (s32)getDimension(L"A").Height + getKerningHeight(); } + //! set an Pixel Offset on Drawing ( scale position on width ) virtual s32 getKerningWidth(const wchar_t* thisLetter=0, const wchar_t* previousLetter=0) const; virtual s32 getKerningHeight() const; @@ -77,6 +93,9 @@ public: virtual u32 getSpriteNoFromChar(const wchar_t *c) const; virtual void setInvisibleCharacters( const wchar_t *s ); + virtual void setScale(f32 scale) {} + virtual f32 getInverseShaping() const { return 1.0f; } + virtual f32 getScale() const { return 1.0f; } private: diff --git a/lib/irrlicht/source/Irrlicht/CGUIStaticText.cpp b/lib/irrlicht/source/Irrlicht/CGUIStaticText.cpp index 904580b22..9489defff 100644 --- a/lib/irrlicht/source/Irrlicht/CGUIStaticText.cpp +++ b/lib/irrlicht/source/Irrlicht/CGUIStaticText.cpp @@ -10,7 +10,6 @@ #include "IGUIFont.h" #include "IVideoDriver.h" #include "rect.h" -#include "utfwrapping.h" namespace irr { @@ -27,7 +26,7 @@ CGUIStaticText::CGUIStaticText(const wchar_t* text, bool border, Border(border), OverrideColorEnabled(false), OverrideBGColorEnabled(false), WordWrap(false), Background(background), RestrainTextInside(true), RightToLeft(false), OverrideColor(video::SColor(101,255,255,255)), BGColor(video::SColor(101,210,210,210)), - OverrideFont(0), LastBreakFont(0) + OverrideFont(0), LastBreakFont(0), m_use_glyph_layouts_only(false) { #ifdef _DEBUG setDebugName("CGUIStaticText"); @@ -80,63 +79,39 @@ void CGUIStaticText::draw() frameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X); } - // draw the text - if (Text.size()) + // draw the text layout + if (!m_glyph_layouts.empty()) { IGUIFont* font = getActiveFont(); if (font) { - if (!WordWrap) + if (font != LastBreakFont) + breakText(); + + core::rect r = frameRect; + auto dim = getGlyphLayoutsDimension(m_glyph_layouts, + font->getHeightPerLine(), font->getInverseShaping(), font->getScale()); + + s32 totalHeight = dim.Height; + if (VAlign == EGUIA_CENTER) { - if (VAlign == EGUIA_LOWERRIGHT) - { - frameRect.UpperLeftCorner.Y = frameRect.LowerRightCorner.Y - - font->getDimension(L"A").Height - font->getKerningHeight(); - } - if (HAlign == EGUIA_LOWERRIGHT) - { - frameRect.UpperLeftCorner.X = frameRect.LowerRightCorner.X - - font->getDimension(Text.c_str()).Width; - } - - font->draw(Text.c_str(), frameRect, - OverrideColorEnabled ? OverrideColor : skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT), - HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER, (RestrainTextInside ? &AbsoluteClippingRect : NULL)); + r.UpperLeftCorner.Y = r.getCenter().Y - (totalHeight / 2); } - else + else if (VAlign == EGUIA_LOWERRIGHT) { - if (font != LastBreakFont) - breakText(); - - core::rect r = frameRect; - s32 height = font->getDimension(L"A").Height + font->getKerningHeight(); - s32 totalHeight = height * BrokenText.size(); - if (VAlign == EGUIA_CENTER) - { - r.UpperLeftCorner.Y = r.getCenter().Y - (totalHeight / 2); - } - else if (VAlign == EGUIA_LOWERRIGHT) - { - r.UpperLeftCorner.Y = r.LowerRightCorner.Y - totalHeight; - } - - for (u32 i=0; igetDimension(BrokenText[i].c_str()).Width; - } - - font->draw(BrokenText[i].c_str(), r, - OverrideColorEnabled ? OverrideColor : skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT), - HAlign == EGUIA_CENTER, false, (RestrainTextInside ? &AbsoluteClippingRect : NULL)); - - r.LowerRightCorner.Y += height; - r.UpperLeftCorner.Y += height; - } + r.UpperLeftCorner.Y = r.LowerRightCorner.Y - totalHeight; } + + + if (HAlign == EGUIA_LOWERRIGHT) + { + r.UpperLeftCorner.X = frameRect.LowerRightCorner.X - dim.Width; + } + + font->draw(m_glyph_layouts, r, + OverrideColorEnabled ? OverrideColor : skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT), + HAlign == EGUIA_CENTER, false, (RestrainTextInside ? &AbsoluteClippingRect : NULL)); } } @@ -307,10 +282,7 @@ bool CGUIStaticText::isRightToLeft() const //! Breaks the single text line. void CGUIStaticText::breakText() { - if (!WordWrap) - return; - - BrokenText.clear(); + m_glyph_layouts.clear(); IGUISkin* skin = Environment->getSkin(); IGUIFont* font = getActiveFont(); @@ -319,194 +291,13 @@ void CGUIStaticText::breakText() LastBreakFont = font; - core::stringw line; - core::stringw word; - core::stringw whitespace; - s32 size = Text.size(); - s32 length = 0; - s32 elWidth = RelativeRect.getWidth(); + f32 elWidth = RelativeRect.getWidth(); if (Border) elWidth -= 2*skin->getSize(EGDS_TEXT_DISTANCE_X); - wchar_t c; - - // We have to deal with right-to-left and left-to-right differently - // However, most parts of the following code is the same, it's just - // some order and boundaries which change. - if (!RightToLeft) - { - // regular (left-to-right) - for (s32 i=0; igetDimension(word.c_str()).Width; - - if (length && (length + wordlgth > elWidth)) - { // too long to fit inside - // break to next line - unsigned int where = 1; - while (where != line.size()) //Find the first breakable position - { - if (UtfNoEnding(Text[i - where]) || //Prevent unsuitable character from displaying - UtfNoStarting(Text[i - where]) || //at the position of starting or ending of a line - UtfNoStarting(Text[i + 1 - where])) //Handle case which more than one non-newline-starting characters are together - { - where++; - continue; - } - if (breakable(Text[i - where])) - break; - else - where++; - } - if (where != line.size()) - { - core::stringw first = line.subString(0, line.size() + 1 - where); - core::stringw second = line.subString(line.size() + 1 - where , where - 1); - if (first.lastChar() == wchar_t(0x00AD)) - BrokenText.push_back(first + L"-"); //Print the Unicode Soft HYphen (SHY / 00AD) character - else - BrokenText.push_back(first); - const s32 secondLength = font->getDimension(second.c_str()).Width; - - length = secondLength + wordlgth; - line = second + word; - } - else if (breakable(c) || UtfNoEnding(c) || UtfNoStarting(c)) //Unusual case - { - BrokenText.push_back(line); //Force breaking to next line too if last word is breakable, - line = word; //it happens when someone writes too many non-newline-starting - length = wordlgth; //chars in the first line, so we ignore the rules. - } - // No suitable place to break words, so there's nothing more we can do - // break to next line - else - { - line += word; - length += wordlgth; - } - } - else - { - line += word; - length += wordlgth; - } - - word = L""; - - } - // compute line break - if (lineBreak) - { - line += word; - BrokenText.push_back(line); - line = L""; - word = L""; - length = 0; - } - } - - line += word; - BrokenText.push_back(line); - } - else - { - // right-to-left - for (s32 i=size; i>=0; --i) - { - c = Text[i]; - bool lineBreak = false; - - if (c == L'\r') // Mac or Windows breaks - { - lineBreak = true; - if ((i>0) && Text[i-1] == L'\n') // Windows breaks - { - Text.erase(i-1); - --size; - } - c = '\0'; - } - else if (c == L'\n') // Unix breaks - { - lineBreak = true; - c = '\0'; - } - - if (c==L' ' || c==0 || i==0) - { - if (word.size()) - { - // here comes the next whitespace, look if - // we must break the last word to the next line. - const s32 whitelgth = font->getDimension(whitespace.c_str()).Width; - const s32 wordlgth = font->getDimension(word.c_str()).Width; - - if (length && (length + wordlgth + whitelgth > elWidth)) - { - // break to next line - BrokenText.push_back(line); - length = wordlgth; - line = word; - } - else - { - // add word to line - line = whitespace + line; - line = word + line; - length += whitelgth + wordlgth; - } - - word = L""; - whitespace = L""; - } - - if (c != 0) - whitespace = core::stringw(&c, 1) + whitespace; - - // compute line break - if (lineBreak) - { - line = whitespace + line; - line = word + line; - BrokenText.push_back(line); - line = L""; - word = L""; - whitespace = L""; - length = 0; - } - } - else - { - // yippee this is a word.. - word = core::stringw(&c, 1) + word; - } - } - - line = whitespace + line; - line = word + line; - BrokenText.push_back(line); - } + if (!m_use_glyph_layouts_only) + font->initGlyphLayouts(Text, m_glyph_layouts); + if (WordWrap) + breakGlyphLayouts(m_glyph_layouts, elWidth, font->getInverseShaping(), font->getScale()); } @@ -532,39 +323,23 @@ s32 CGUIStaticText::getTextHeight() const if (!font) return 0; - s32 height = font->getDimension(L"A").Height + font->getKerningHeight(); + auto dim = getGlyphLayoutsDimension(m_glyph_layouts, + font->getHeightPerLine(), font->getInverseShaping(), font->getScale()); - if (WordWrap) - height *= BrokenText.size(); - - return height; + return dim.Height; } s32 CGUIStaticText::getTextWidth() const { IGUIFont * font = getActiveFont(); - if(!font) + if (!font) return 0; - if(WordWrap) - { - s32 widest = 0; + auto dim = getGlyphLayoutsDimension(m_glyph_layouts, + font->getHeightPerLine(), font->getInverseShaping(), font->getScale()); - for(u32 line = 0; line < BrokenText.size(); ++line) - { - s32 width = font->getDimension(BrokenText[line].c_str()).Width; - - if(width > widest) - widest = width; - } - - return widest; - } - else - { - return font->getDimension(Text.c_str()).Width; - } + return dim.Width; } diff --git a/lib/irrlicht/source/Irrlicht/CGUIStaticText.h b/lib/irrlicht/source/Irrlicht/CGUIStaticText.h index 33daa98a1..ee34eee04 100644 --- a/lib/irrlicht/source/Irrlicht/CGUIStaticText.h +++ b/lib/irrlicht/source/Irrlicht/CGUIStaticText.h @@ -10,6 +10,7 @@ #include "IGUIStaticText.h" #include "irrArray.h" +#include "GlyphLayout.h" namespace irr { @@ -115,6 +116,12 @@ namespace gui //! Reads attributes of the element virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options); + virtual const std::vector& getGlyphLayouts() const { return m_glyph_layouts; } + virtual void setGlyphLayouts(std::vector& gls) { m_glyph_layouts = gls; } + virtual void clearGlyphLayouts() { m_glyph_layouts.clear(); } + virtual void setUseGlyphLayoutsOnly(bool gls_only) { m_use_glyph_layouts_only = gls_only; } + virtual bool useGlyphLayoutsOnly() const { return m_use_glyph_layouts_only; } + private: //! Breaks the single text line. @@ -133,7 +140,9 @@ namespace gui gui::IGUIFont* OverrideFont; gui::IGUIFont* LastBreakFont; // stored because: if skin changes, line break must be recalculated. - core::array< core::stringw > BrokenText; + //! If true, setText / updating this element will not reshape with Text object + bool m_use_glyph_layouts_only; + std::vector m_glyph_layouts; }; } // end namespace gui diff --git a/src/addons/zip.cpp b/src/addons/zip.cpp index a0cca0983..96061ffff 100644 --- a/src/addons/zip.cpp +++ b/src/addons/zip.cpp @@ -21,6 +21,7 @@ #include "graphics/irr_driver.hpp" #include "io/file_manager.hpp" +#include "utils/log.hpp" #include "utils/string_utils.hpp" #include diff --git a/src/config/hardware_stats.cpp b/src/config/hardware_stats.cpp index b59ec1645..929e2abec 100644 --- a/src/config/hardware_stats.cpp +++ b/src/config/hardware_stats.cpp @@ -29,6 +29,7 @@ #include "graphics/glwrap.hpp" #include "graphics/irr_driver.hpp" #include "online/http_request.hpp" +#include "utils/log.hpp" #include "utils/random_generator.hpp" #include diff --git a/src/config/user_config.cpp b/src/config/user_config.cpp index 1fdc6a97c..b75144378 100644 --- a/src/config/user_config.cpp +++ b/src/config/user_config.cpp @@ -38,6 +38,7 @@ static std::vector all_params; #include "io/utf_writer.hpp" #include "io/xml_node.hpp" #include "race/race_manager.hpp" +#include "utils/log.hpp" #include "utils/string_utils.hpp" #include "utils/translation.hpp" diff --git a/src/font/bold_face.hpp b/src/font/bold_face.hpp index 2f07bd0c4..678c28a0b 100644 --- a/src/font/bold_face.hpp +++ b/src/font/bold_face.hpp @@ -30,7 +30,7 @@ class FaceTTF; class BoldFace : public FontWithFace { private: - virtual unsigned int getGlyphPageSize() const OVERRIDE { return 1024; } + virtual unsigned int getGlyphPageSize() const OVERRIDE { return 512; } // ------------------------------------------------------------------------ virtual float getScalingFactorOne() const OVERRIDE { return 0.3f; } // ------------------------------------------------------------------------ diff --git a/src/font/digit_face.hpp b/src/font/digit_face.hpp index 86896b1f8..ea7e6cf37 100644 --- a/src/font/digit_face.hpp +++ b/src/font/digit_face.hpp @@ -45,7 +45,8 @@ public: virtual void init() OVERRIDE; // ------------------------------------------------------------------------ virtual void reset() OVERRIDE; - + // ------------------------------------------------------------------------ + virtual bool disableTextShaping() const OVERRIDE { return true; } }; // DigitFace #endif diff --git a/src/font/face_ttf.hpp b/src/font/face_ttf.hpp index 4228fabd2..93609ba27 100644 --- a/src/font/face_ttf.hpp +++ b/src/font/face_ttf.hpp @@ -19,7 +19,6 @@ #ifndef HEADER_FACE_TTF_HPP #define HEADER_FACE_TTF_HPP -#include "utils/leak_check.hpp" #include "utils/no_copy.hpp" #include @@ -51,7 +50,7 @@ struct FontArea }; /** This class will load a list of TTF files from \ref FontManager, and save - * them inside \ref m_faces for \ref FontWithFace to load glyph. + * them inside \ref m_ft_faces for \ref FontWithFace to load glyph. * \ingroup font */ class FaceTTF : public NoCopy @@ -60,7 +59,7 @@ class FaceTTF : public NoCopy private: /** Contains all FT_Face with a list of loaded glyph index with the * \ref FontArea. */ - std::vector > > m_faces; + std::vector > > m_ft_faces; #endif public: LEAK_CHECK() @@ -72,55 +71,44 @@ public: void reset() { #ifndef SERVER_ONLY - for (unsigned int i = 0; i < m_faces.size(); i++) - m_faces[i].second.clear(); -#endif - } - // ------------------------------------------------------------------------ - /* Return a white-space font area, which is the first glyph in ttf. */ - const FontArea* getFirstFontArea() const - { -#ifdef SERVER_ONLY - static FontArea area; - return &area; -#else - if (m_faces.empty()) - return NULL; - if (m_faces.front().second.empty()) - return NULL; - return &(m_faces.front().second.begin()->second); + for (unsigned int i = 0; i < m_ft_faces.size(); i++) + m_ft_faces[i].second.clear(); #endif } #ifndef SERVER_ONLY // ------------------------------------------------------------------------ - void loadFaces(std::vector faces) + void loadTTF(std::vector faces) { for (unsigned int i = 0; i < faces.size(); i++) - m_faces.emplace_back(faces[i], std::map()); + m_ft_faces.emplace_back(faces[i], std::map()); } // ------------------------------------------------------------------------ - /** Return a TTF in \ref m_faces. - * \param i index of TTF file in \ref m_faces. + /** Return a TTF in \ref m_ft_faces. + * \param i index of TTF file in \ref m_ft_faces. */ FT_Face getFace(unsigned int i) const { - assert(i < m_faces.size()); - return m_faces[i].first; + assert(i < m_ft_faces.size()); + return m_ft_faces[i].first; } // ------------------------------------------------------------------------ /** Return the total TTF files loaded. */ - unsigned int getTotalFaces() const { return (unsigned int)m_faces.size(); } + unsigned int getTotalFaces() const + { return (unsigned int)m_ft_faces.size(); } // ------------------------------------------------------------------------ void insertFontArea(const FontArea& a, unsigned font_index, unsigned glyph_index) { - auto& ttf = m_faces.at(font_index).second; + auto& ttf = m_ft_faces.at(font_index).second; ttf[glyph_index] = a; } // ------------------------------------------------------------------------ + bool enabledForFont(unsigned idx) const + { return m_ft_faces.size() > idx && m_ft_faces[idx].first != NULL; } + // ------------------------------------------------------------------------ const FontArea* getFontArea(unsigned font_index, unsigned glyph_index) { - auto& ttf = m_faces.at(font_index).second; + auto& ttf = m_ft_faces.at(font_index).second; auto it = ttf.find(glyph_index); if (it != ttf.end()) return &it->second; @@ -129,9 +117,9 @@ public: // ------------------------------------------------------------------------ bool getFontAndGlyphFromChar(uint32_t c, unsigned* font, unsigned* glyph) { - for (unsigned i = 0; i < m_faces.size(); i++) + for (unsigned i = 0; i < m_ft_faces.size(); i++) { - unsigned glyph_index = FT_Get_Char_Index(m_faces[i].first, c); + unsigned glyph_index = FT_Get_Char_Index(m_ft_faces[i].first, c); if (glyph_index > 0) { *font = i; diff --git a/src/font/font_manager.cpp b/src/font/font_manager.cpp index eea3effea..b4340fb4b 100644 --- a/src/font/font_manager.cpp +++ b/src/font/font_manager.cpp @@ -28,6 +28,11 @@ #include "utils/string_utils.hpp" #include "utils/translation.hpp" +#ifndef SERVER_ONLY +#include +#include +#endif + FontManager *font_manager = NULL; // ---------------------------------------------------------------------------- /** Constructor. It will initialize the \ref m_ft_library. @@ -36,6 +41,8 @@ FontManager::FontManager() { #ifndef SERVER_ONLY m_ft_library = NULL; + m_digit_face = NULL; + m_shaping_dpi = 128; if (ProfileWorld::isNoGraphics()) return; @@ -57,7 +64,9 @@ FontManager::~FontManager() return; for (unsigned int i = 0; i < m_faces.size(); i++) - checkFTError(FT_Done_Face(m_faces[i]), "removing faces"); + checkFTError(FT_Done_Face(m_faces[i]), "removing faces for shaping"); + if (m_digit_face != NULL) + checkFTError(FT_Done_Face(m_digit_face), "removing digit face"); checkFTError(FT_Done_FreeType(m_ft_library), "removing freetype library"); #endif } // ~FontManager @@ -85,6 +94,399 @@ std::vector } return ret; } // loadTTF + +// ============================================================================ +namespace LineBreakingRules +{ + // Here a list of characters that don't start or end a line for + // chinese/japanese/korean. Only commonly use and full width characters are + // included. You should use full width characters when writing CJK, + // like using "。"instead of a ".", you can add more characters if needed. + // For full list please visit: + // http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/kinsoku.html + bool noStartingLine(char32_t c) + { + switch (c) + { + // ’ + case 8217: + return true; + // ” + case 8221: + return true; + // 々 + case 12293: + return true; + // 〉 + case 12297: + return true; + // 》 + case 12299: + return true; + // 」 + case 12301: + return true; + // } + case 65373: + return true; + // 〕 + case 12309: + return true; + // ) + case 65289: + return true; + // 』 + case 12303: + return true; + // 】 + case 12305: + return true; + // 〗 + case 12311: + return true; + // ! + case 65281: + return true; + // % + case 65285: + return true; + // ? + case 65311: + return true; + // ` + case 65344: + return true; + // , + case 65292: + return true; + // : + case 65306: + return true; + // ; + case 65307: + return true; + // . + case 65294: + return true; + // 。 + case 12290: + return true; + // 、 + case 12289: + return true; + default: + return false; + } + } // noStartingLine + //------------------------------------------------------------------------- + bool noEndingLine(char32_t c) + { + switch (c) + { + // ‘ + case 8216: + return true; + // “ + case 8220: + return true; + // 〈 + case 12296: + return true; + // 《 + case 12298: + return true; + // 「 + case 12300: + return true; + // { + case 65371: + return true; + // 〔 + case 12308: + return true; + // ( + case 65288: + return true; + // 『 + case 12302: + return true; + // 【 + case 12304: + return true; + // 〖 + case 12310: + return true; + default: + return false; + } + } // noEndingLine + //------------------------------------------------------------------------- + // Helper function + bool breakable(char32_t c) + { + if ((c > 12287 && c < 40960) || // Common CJK words + (c > 44031 && c < 55204) || // Hangul + (c > 63743 && c < 64256) || // More Chinese + c == 173 || c == 32 || // Soft hyphen and white space + c == 47 || c == 92) // Slash and blackslash + return true; + return false; + } // breakable + //------------------------------------------------------------------------- + void insertBreakMark(const std::u32string& str, std::vector& result) + { + assert(str.size() == result.size()); + for (unsigned i = 0; i < result.size(); i++) + { + char32_t c = str[i]; + char32_t nextline_char = 20; + if (i < result.size() - 1) + nextline_char = str[i + 1]; + if (breakable(c) && !noEndingLine(c) && + !noStartingLine(nextline_char)) + { + result[i] = true; + } + } + } // insertBreakMark +} // namespace LineBreakingRules + +// ============================================================================ +namespace RTLRules +{ + //------------------------------------------------------------------------- + void insertRTLMark(const std::u32string& str, std::vector& line, + std::vector& char_bool) + { + // Check if first character has strong RTL, then consider this line is + // RTL + std::vector types; + std::vector levels; + types.resize(str.size(), 0); + levels.resize(str.size(), 0); + FriBidiParType par_type = FRIBIDI_PAR_ON; + const FriBidiChar* fribidi_str = (const FriBidiChar*)str.c_str(); + fribidi_get_bidi_types(fribidi_str, str.size(), types.data()); +#if FRIBIDI_MAJOR_VERSION >= 1 + std::vector btypes; + btypes.resize(str.size(), 0); + fribidi_get_bracket_types(fribidi_str, str.size(), types.data(), + btypes.data()); + int max_level = fribidi_get_par_embedding_levels_ex(types.data(), + btypes.data(), str.size(), &par_type, levels.data()); +#else + int max_level = fribidi_get_par_embedding_levels(types.data(), + str.size(), &par_type, levels.data()); +#endif + (void)max_level; + bool cur_rtl = par_type == FRIBIDI_PAR_RTL; + if (cur_rtl) + { + for (unsigned i = 0; i < line.size(); i++) + line[i] = true; + } + int cur_level = levels[0]; + for (unsigned i = 0; i < char_bool.size(); i++) + { + if (levels[i] != cur_level) + { + cur_rtl = !cur_rtl; + cur_level = levels[i]; + } + char_bool[i] = cur_rtl; + } + } // insertRTLMark +} // namespace RTLRules + +// ---------------------------------------------------------------------------- +/* Turn text into glyph layout for rendering by libraqm. */ +void FontManager::shape(const std::u32string& text, + std::vector& gls, + std::vector* line_data) + +{ + if (text.empty()) + return; + + auto lines = StringUtils::split(text, U'\n'); + // If the text end with and newline, it will miss a newline height, so we + // it back here + if (text.back() == U'\n') + lines.push_back(U""); + + for (unsigned l = 0; l < lines.size(); l++) + { + if (l != 0) + { + gui::GlyphLayout gl = { 0 }; + gl.flags = gui::GLF_NEWLINE; + gls.push_back(gl); + } + + std::u32string& str = lines[l]; + str.erase(std::remove(str.begin(), str.end(), U'\r'), str.end()); + str.erase(std::remove(str.begin(), str.end(), U'\t'), str.end()); + if (str.empty()) + { + if (line_data) + line_data->push_back(str); + continue; + } + + raqm_t* rq = raqm_create(); + if (!rq) + { + Log::error("FontManager", "Failed to raqm_create."); + gls.clear(); + if (line_data) + line_data->clear(); + return; + } + + const int length = (int)str.size(); + const uint32_t* string_array = (const uint32_t*)str.c_str(); + + if (!raqm_set_text(rq, string_array, length)) + { + Log::error("FontManager", "Failed to raqm_set_text."); + raqm_destroy(rq); + gls.clear(); + if (line_data) + line_data->clear(); + return; + } + + for (int i = 0; i < length; i++) + { + FT_Face cur_face = m_faces.front(); + for (unsigned j = 0; j < m_faces.size(); j++) + { + unsigned glyph_index = FT_Get_Char_Index(m_faces[j], str[i]); + if (glyph_index > 0) + { + cur_face = m_faces[j]; + break; + } + } + checkFTError(FT_Set_Pixel_Sizes(cur_face, 0, + m_shaping_dpi), "setting DPI"); + if (!raqm_set_freetype_face_range(rq, cur_face, i, 1)) + { + Log::error("FontManager", + "Failed to raqm_set_freetype_face_range."); + raqm_destroy(rq); + gls.clear(); + if (line_data) + line_data->clear(); + return; + } + } + + if (raqm_layout(rq)) + { + std::vector cur_line; + std::vector rtl_line, rtl_char, breakable; + rtl_line.resize(str.size(), false); + rtl_char.resize(str.size(), false); + breakable.resize(str.size(), false); + LineBreakingRules::insertBreakMark(str, breakable); + RTLRules::insertRTLMark(str, rtl_line, rtl_char); + size_t count = 0; + raqm_glyph_t* glyphs = raqm_get_glyphs(rq, &count); + for (unsigned idx = 0; idx < (unsigned)count; idx++) + { + gui::GlyphLayout gl = { 0 }; + gl.index = glyphs[idx].index; + gl.cluster.push_back(glyphs[idx].cluster); + gl.x_advance = glyphs[idx].x_advance / BEARING; + gl.x_offset = glyphs[idx].x_offset / BEARING; + gl.y_offset = glyphs[idx].y_offset / BEARING; + gl.face_idx = m_ft_faces_to_index.at(glyphs[idx].ftface); + gl.original_index = idx; + if (rtl_line[glyphs[idx].cluster]) + gl.flags |= gui::GLF_RTL_LINE; + if (rtl_char[glyphs[idx].cluster]) + gl.flags |= gui::GLF_RTL_CHAR; + if (breakable[glyphs[idx].cluster]) + gl.flags |= gui::GLF_BREAKABLE; + cur_line.push_back(gl); + } + // Sort glyphs in logical order + std::sort(cur_line.begin(), cur_line.end(), [] + (const gui::GlyphLayout& a_gi, const gui::GlyphLayout& b_gi) + { + return a_gi.cluster.front() < b_gi.cluster.front(); + }); + for (unsigned l = 0; l < cur_line.size(); l++) + { + const int cur_cluster = cur_line[l].cluster.front(); + // Last cluster handling, add the remaining clusters if missing + if (l == cur_line.size() - 1) + { + for (int extra_cluster = cur_cluster + 1; + extra_cluster <= (int)str.size() - 1; extra_cluster++) + cur_line[l].cluster.push_back(extra_cluster); + } + else + { + // Make sure there is every cluster values appear in the + // list at least once, it will be used for cursor + // positioning later + const int next_cluster = cur_line[l + 1].cluster.front(); + for (int extra_cluster = cur_cluster + 1; + extra_cluster <= next_cluster - 1; extra_cluster++) + cur_line[l].cluster.push_back(extra_cluster); + } + } + // Sort glyphs in visual order + std::sort(cur_line.begin(), cur_line.end(), [] + (const gui::GlyphLayout& a_gi, + const gui::GlyphLayout& b_gi) + { + return a_gi.original_index < b_gi.original_index; + }); + gls.insert(gls.end(), cur_line.begin(), cur_line.end()); + raqm_destroy(rq); + if (line_data) + line_data->push_back(str); + } + else + { + Log::error("FontManager", "Failed to raqm_layout."); + raqm_destroy(rq); + gls.clear(); + if (line_data) + line_data->clear(); + return; + } + } +} // shape + +// ---------------------------------------------------------------------------- +/** Convert text to glyph layouts for fast rendering with caching enabled + * If line_data is not null, each broken line u32string will be saved and + * can be used for advanced glyph and text mapping, and cache will be + * disabled, no newline characters are allowed in text if line_data is not + * NULL. + */ +void FontManager::initGlyphLayouts(const core::stringw& text, + std::vector& gls, + std::vector* line_data) +{ + if (ProfileWorld::isNoGraphics() || text.empty()) + return; + + if (line_data != NULL) + { + shape(StringUtils::wideToUtf32(text), gls, line_data); + return; + } + + auto& cached_gls = m_cached_gls[text]; + if (cached_gls.empty()) + shape(StringUtils::wideToUtf32(text), cached_gls); + gls = cached_gls; +} // initGlyphLayouts #endif // ---------------------------------------------------------------------------- @@ -95,14 +497,22 @@ void FontManager::loadFonts() #ifndef SERVER_ONLY // First load the TTF files required by each font std::vector normal_ttf = loadTTF(stk_config->m_normal_ttf); + // We use 16bit face idx in GlyphLayout class + if (normal_ttf.size() > 65535) + normal_ttf.resize(65535); + for (uint16_t i = 0; i < normal_ttf.size(); i++) + m_ft_faces_to_index[normal_ttf[i]] = i; + std::vector digit_ttf = loadTTF(stk_config->m_digit_ttf); + if (!digit_ttf.empty()) + m_digit_face = digit_ttf.front(); #endif // Now load fonts with settings of ttf file unsigned int font_loaded = 0; RegularFace* regular = new RegularFace(); #ifndef SERVER_ONLY - regular->getFaceTTF()->loadFaces(normal_ttf); + regular->getFaceTTF()->loadTTF(normal_ttf); #endif regular->init(); m_fonts.push_back(regular); @@ -110,7 +520,7 @@ void FontManager::loadFonts() BoldFace* bold = new BoldFace(); #ifndef SERVER_ONLY - bold->getFaceTTF()->loadFaces(normal_ttf); + bold->getFaceTTF()->loadTTF(normal_ttf); #endif bold->init(); m_fonts.push_back(bold); @@ -118,7 +528,7 @@ void FontManager::loadFonts() DigitFace* digit = new DigitFace(); #ifndef SERVER_ONLY - digit->getFaceTTF()->loadFaces(digit_ttf); + digit->getFaceTTF()->loadTTF(digit_ttf); #endif digit->init(); m_fonts.push_back(digit); @@ -126,7 +536,6 @@ void FontManager::loadFonts() #ifndef SERVER_ONLY m_faces.insert(m_faces.end(), normal_ttf.begin(), normal_ttf.end()); - m_faces.insert(m_faces.end(), digit_ttf.begin(), digit_ttf.end()); #endif } // loadFonts diff --git a/src/font/font_manager.hpp b/src/font/font_manager.hpp index e34bd8ecf..624be5e47 100644 --- a/src/font/font_manager.hpp +++ b/src/font/font_manager.hpp @@ -28,6 +28,7 @@ #include "utils/no_copy.hpp" #include +#include #include #include #include @@ -35,6 +36,9 @@ #ifndef SERVER_ONLY #include #include FT_FREETYPE_H + +#include "irrString.h" +#include "GlyphLayout.h" #endif class FontWithFace; @@ -52,14 +56,28 @@ private: /** A FreeType library, it holds the FT_Face internally inside freetype. */ FT_Library m_ft_library; - /** List of ttf files loaded. */ + /** List of TTF files for complex text shaping. */ std::vector m_faces; + + /** TTF file for digit font in STK. */ + FT_Face m_digit_face; + + /** DPI used when shaping, each face will apply an inverse of this and the + * dpi of itself when rendering, so all faces can share the same glyph + * layout cache. */ + unsigned m_shaping_dpi; + + /** Map FT_Face to index for quicker layout. */ + std::map m_ft_faces_to_index; + + /** Text drawn to glyph layouts cache. */ + std::map > m_cached_gls; #endif /** Map type for each \ref FontWithFace with a index, save getting time in * \ref getFont. */ std::unordered_map m_font_type_map; - public: LEAK_CHECK() // ------------------------------------------------------------------------ @@ -99,13 +117,25 @@ public: FT_Library getFTLibrary() const { return m_ft_library; } // ------------------------------------------------------------------------ std::vector loadTTF(const std::vector& ttf_list); + // ------------------------------------------------------------------------ + unsigned getShapingDPI() const { return m_shaping_dpi; } + // ------------------------------------------------------------------------ + void shape(const std::u32string& text, + std::vector& gls, + std::vector* line_data = NULL); + // ------------------------------------------------------------------------ + std::vector& getCachedLayouts + (const irr::core::stringw& str) { return m_cached_gls[str]; } + // ------------------------------------------------------------------------ + void initGlyphLayouts(const irr::core::stringw& text, + std::vector& gls, + std::vector* line_data = NULL); #endif // ------------------------------------------------------------------------ void loadFonts(); // ------------------------------------------------------------------------ void unitTesting(); - }; // FontManager extern FontManager *font_manager; diff --git a/src/font/font_with_face.cpp b/src/font/font_with_face.cpp index 3baeb0d1e..460d3d5d4 100644 --- a/src/font/font_with_face.cpp +++ b/src/font/font_with_face.cpp @@ -31,7 +31,9 @@ #include "guiengine/skin.hpp" #include "modes/profile_world.hpp" #include "utils/string_utils.hpp" +#include "utils/utf8.h" +#include "GlyphLayout.h" #include // ---------------------------------------------------------------------------- @@ -50,7 +52,8 @@ FontWithFace::FontWithFace(const std::string& name) m_fallback_font_scale = 1.0f; m_glyph_max_height = 0; m_face_ttf = new FaceTTF(); - + m_face_dpi = 40; + m_inverse_shaping = 1.0f; } // FontWithFace // ---------------------------------------------------------------------------- /** Destructor. Clears the glyph page and sprite bank. @@ -172,17 +175,18 @@ void FontWithFace::createNewGlyphPage() // ---------------------------------------------------------------------------- /** Render a glyph for a character into bitmap and save it into the glyph page. - * \param c \ref GlyphInfo for the character. + * \param font_number Font number in \ref FaceTTF ttf list + * \param glyph_index Glyph index in ttf */ -void FontWithFace::insertGlyph(const GlyphInfo& gi) +void FontWithFace::insertGlyph(unsigned font_number, unsigned glyph_index) { #ifndef SERVER_ONLY if (ProfileWorld::isNoGraphics()) return; - assert(gi.glyph_index > 0); - assert(gi.font_number < m_face_ttf->getTotalFaces()); - FT_Face cur_face = m_face_ttf->getFace(gi.font_number); + assert(glyph_index > 0); + assert(font_number < m_face_ttf->getTotalFaces()); + FT_Face cur_face = m_face_ttf->getFace(font_number); FT_GlyphSlot slot = cur_face->glyph; // Same face may be shared across the different FontWithFace, @@ -190,7 +194,7 @@ void FontWithFace::insertGlyph(const GlyphInfo& gi) font_manager->checkFTError(FT_Set_Pixel_Sizes(cur_face, 0, getDPI()), "setting DPI"); - font_manager->checkFTError(FT_Load_Glyph(cur_face, gi.glyph_index, + font_manager->checkFTError(FT_Load_Glyph(cur_face, glyph_index, FT_LOAD_DEFAULT), "loading a glyph"); font_manager->checkFTError(shapeOutline(&(slot->outline)), @@ -272,7 +276,7 @@ void FontWithFace::insertGlyph(const GlyphInfo& gi) a.offset_y = m_glyph_max_height - cur_height + cur_offset_y; a.offset_y_bt = -cur_offset_y; a.spriteno = f.rectNumber; - m_face_ttf->insertFontArea(a, gi.font_number, gi.glyph_index); + m_face_ttf->insertFontArea(a, font_number, glyph_index); // Store used area m_used_width += texture_size.Width; @@ -293,7 +297,7 @@ void FontWithFace::updateCharactersList() for (const wchar_t& c : m_new_char_holder) { const GlyphInfo& gi = getGlyphInfo(c); - insertGlyph(gi); + insertGlyph(gi.font_number, gi.glyph_index); } m_new_char_holder.clear(); @@ -357,9 +361,33 @@ void FontWithFace::setDPI() factorTwo += UserConfigParams::m_fonts_size * 5 - 10; m_face_dpi = int(factorTwo * getScalingFactorOne() * scale); - +#ifndef SERVER_ONLY + if (!disableTextShaping()) + { + m_inverse_shaping = (1.0f / (float)font_manager->getShapingDPI()) * + float(m_face_dpi); + } +#endif } // setDPI +// ---------------------------------------------------------------------------- +/* Get the question mark glyph to show unsupported charaters. */ +const FontArea* FontWithFace::getUnknownFontArea() const +{ +#ifdef SERVER_ONLY + static FontArea area; + return &area; +#else + std::map::const_iterator n = + m_character_glyph_info_map.find(L'?'); + assert(n != m_character_glyph_info_map.end()); + const FontArea* area = m_face_ttf->getFontArea(n->second.font_number, + n->second.glyph_index); + assert(area != NULL); + return area; +#endif +} // getUnknownFontArea + // ---------------------------------------------------------------------------- /** Return the \ref FontArea about a character. * \param c The character to get. @@ -372,7 +400,7 @@ const FontArea& FontWithFace::getAreaFromCharacter(const wchar_t c, m_character_glyph_info_map.find(c); // Not found, return the first font area, which is a white-space if (n == m_character_glyph_info_map.end()) - return *(m_face_ttf->getFirstFontArea()); + return *getUnknownFontArea(); #ifndef SERVER_ONLY const FontArea* area = m_face_ttf->getFontArea(n->second.font_number, @@ -394,7 +422,7 @@ const FontArea& FontWithFace::getAreaFromCharacter(const wchar_t c, *fallback_font = false; #endif - return *(m_face_ttf->getFirstFontArea()); + return *getUnknownFontArea(); } // getAreaFromCharacter // ---------------------------------------------------------------------------- @@ -404,8 +432,8 @@ const FontArea& FontWithFace::getAreaFromCharacter(const wchar_t c, * \param font_settings \ref FontSettings to use. * \return The dimension of text */ -core::dimension2d FontWithFace::getDimension(const wchar_t* text, - FontSettings* font_settings) +core::dimension2d FontWithFace::getDimension(const core::stringw& text, + FontSettings* font_settings) { #ifdef SERVER_ONLY return core::dimension2d(1, 1); @@ -414,42 +442,18 @@ core::dimension2d FontWithFace::getDimension(const wchar_t* text, return core::dimension2d(1, 1); const float scale = font_settings ? font_settings->getScale() : 1.0f; - // Test if lazy load char is needed - insertCharacters(text); - updateCharactersList(); - - core::dimension2d dim(0.0f, 0.0f); - core::dimension2d this_line(0.0f, m_font_max_height * scale); - - for (const wchar_t* p = text; *p; ++p) + if (disableTextShaping()) { - if (*p == L'\r' || // Windows breaks - *p == L'\n' ) // Unix breaks - { - if (*p==L'\r' && p[1] == L'\n') // Windows breaks - ++p; - dim.Height += this_line.Height; - if (dim.Width < this_line.Width) - dim.Width = this_line.Width; - this_line.Width = 0; - continue; - } - - bool fallback = false; - const FontArea &area = getAreaFromCharacter(*p, &fallback); - - this_line.Width += getCharWidth(area, fallback, scale); + return gui::getGlyphLayoutsDimension(text2GlyphsWithoutShaping(text), + m_font_max_height, 1.0f/*inverse shaping*/, scale); } - dim.Height += this_line.Height; - if (dim.Width < this_line.Width) - dim.Width = this_line.Width; + auto& gls = font_manager->getCachedLayouts(text); + if (gls.empty() && !text.empty()) + font_manager->shape(StringUtils::wideToUtf32(text), gls); - core::dimension2d ret_dim(0, 0); - ret_dim.Width = (u32)(dim.Width + 0.9f); // round up - ret_dim.Height = (u32)(dim.Height + 0.9f); - - return ret_dim; + return gui::getGlyphLayoutsDimension(gls, + m_font_max_height * scale, m_inverse_shaping, scale); #endif } // getDimension @@ -488,7 +492,7 @@ int FontWithFace::getCharacterFromPos(const wchar_t* text, int pixel_x, // ---------------------------------------------------------------------------- /** Render text and clip it to the specified rectangle if wanted, it will also * do checking for missing characters in font and lazy load them. - * \param text The text to be rendering. + * \param gl GlyphLayout rendered by libraqm to be rendering. * \param position The position to be rendering. * \param color The color used when rendering. * \param hcenter If rendered horizontally center. @@ -497,7 +501,7 @@ int FontWithFace::getCharacterFromPos(const wchar_t* text, int pixel_x, * \param font_settings \ref FontSettings to use. * \param char_collector \ref FontCharCollector to render billboard text. */ -void FontWithFace::render(const core::stringw& text, +void FontWithFace::render(const std::vector& gl, const core::rect& position, const video::SColor& color, bool hcenter, bool vcenter, const core::rect* clip, @@ -505,14 +509,13 @@ void FontWithFace::render(const core::stringw& text, FontCharCollector* char_collector) { #ifndef SERVER_ONLY - if (ProfileWorld::isNoGraphics()) + if (ProfileWorld::isNoGraphics() || gl.empty()) return; const bool black_border = font_settings ? font_settings->useBlackBorder() : false; const bool colored_border = font_settings ? font_settings->useColoredBorder() : false; - const bool rtl = font_settings ? font_settings->isRTL() : false; const float scale = font_settings ? font_settings->getScale() : 1.0f; const float shadow = font_settings ? font_settings->useShadow() : false; @@ -525,7 +528,7 @@ void FontWithFace::render(const core::stringw& text, core::rect shadowpos = position; shadowpos.LowerRightCorner.X += 2; shadowpos.LowerRightCorner.Y += 2; - render(text, shadowpos, font_settings->getShadowColor(), hcenter, + render(gl, shadowpos, font_settings->getShadowColor(), hcenter, vcenter, clip, font_settings); // Set back @@ -535,18 +538,28 @@ void FontWithFace::render(const core::stringw& text, core::position2d offset(float(position.UpperLeftCorner.X), float(position.UpperLeftCorner.Y)); core::dimension2d text_dimension; + auto width_per_line = gui::getGlyphLayoutsWidthPerLine(gl, + m_inverse_shaping, scale); + if (width_per_line.empty()) + return; - if (rtl || hcenter || vcenter || clip) + // The offset must be round to integer when setting the offests + // or * m_inverse_shaping, so the glyph is drawn without blurring effects + if (hcenter || vcenter || clip) { - text_dimension = getDimension(text.c_str(), font_settings); + text_dimension = gui::getGlyphLayoutsDimension( + gl, m_font_max_height * scale, m_inverse_shaping, scale); if (hcenter) - offset.X += (position.getWidth() - text_dimension.Width) / 2; - else if (rtl) - offset.X += (position.getWidth() - text_dimension.Width); - + { + offset.X += (s32)( + (position.getWidth() - width_per_line[0]) / 2.0f); + } if (vcenter) - offset.Y += (position.getHeight() - text_dimension.Height) / 2; + { + offset.Y += (s32)( + (position.getHeight() - text_dimension.Height) / 2.0f); + } if (clip) { core::rect clippedRect(core::position2d @@ -557,40 +570,86 @@ void FontWithFace::render(const core::stringw& text, } // Collect character locations - const unsigned int text_size = text.size(); + const unsigned int text_size = gl.size(); core::array indices(text_size); core::array> offsets(text_size); std::vector fallback(text_size); - // Test again if lazy load char is needed, - // as some text isn't drawn with getDimension - insertCharacters(text.c_str()); - updateCharactersList(); + // Check if the line is RTL + bool rtl = (gl[0].flags & gui::GLF_RTL_LINE) != 0; + if (!hcenter && rtl) + offset.X += (s32)(position.getWidth() - width_per_line[0]); - for (u32 i = 0; i < text_size; i++) + unsigned cur_line = 0; + bool line_changed = false; + for (unsigned i = 0; i < gl.size(); i++) { - wchar_t c = text[i]; - - if (c == L'\r' || // Windows breaks - c == L'\n' ) // Unix breaks + const gui::GlyphLayout& glyph_layout = gl[i]; + if ((glyph_layout.flags & gui::GLF_NEWLINE) != 0) { - if (c==L'\r' && text[i+1]==L'\n') - c = text[++i]; + // Y doesn't matter because we don't use advance y in harfbuzz offset.Y += m_font_max_height * scale; - offset.X = float(position.UpperLeftCorner.X); - if (hcenter) - offset.X += (position.getWidth() - text_dimension.Width) >> 1; + cur_line++; + line_changed = true; continue; - } // if lineBreak + } + if (line_changed) + { + line_changed = false; + rtl = (gl[i].flags & gui::GLF_RTL_LINE) != 0; + offset.X = float(position.UpperLeftCorner.X); + if (hcenter) + { + offset.X += (s32)( + (position.getWidth() - width_per_line.at(cur_line)) / 2.f); + } + else if (rtl) + { + offset.X += + (s32)(position.getWidth() - width_per_line.at(cur_line)); + } + } bool use_fallback_font = false; - const FontArea &area = getAreaFromCharacter(c, &use_fallback_font); + const FontArea* area = NULL; + if (glyph_layout.index == 0) + area = getUnknownFontArea(); + if (area == NULL) + { + if (m_face_ttf->enabledForFont(glyph_layout.face_idx)) + { + area = m_face_ttf->getFontArea( + glyph_layout.face_idx, glyph_layout.index); + if (area == NULL) + { + insertGlyph(glyph_layout.face_idx, glyph_layout.index); + area = m_face_ttf->getFontArea( + glyph_layout.face_idx, glyph_layout.index); + } + } + else if (m_fallback_font && m_fallback_font + ->m_face_ttf->enabledForFont(glyph_layout.face_idx)) + { + use_fallback_font = true; + area = m_fallback_font->m_face_ttf->getFontArea( + glyph_layout.face_idx, glyph_layout.index); + if (area == NULL) + { + m_fallback_font->insertGlyph(glyph_layout.face_idx, + glyph_layout.index); + area = m_fallback_font->m_face_ttf->getFontArea( + glyph_layout.face_idx, glyph_layout.index); + } + } + } fallback[i] = use_fallback_font; if (char_collector == NULL) { - float glyph_offset_x = area.bearing_x * + float glyph_offset_x = (int)(area->bearing_x + + glyph_layout.x_offset * m_inverse_shaping) * (fallback[i] ? m_fallback_font_scale : scale); - float glyph_offset_y = area.offset_y * + float glyph_offset_y = (int)(area->offset_y - + glyph_layout.y_offset * m_inverse_shaping) * (fallback[i] ? m_fallback_font_scale : scale); offset.X += glyph_offset_x; offset.Y += glyph_offset_y; @@ -601,9 +660,11 @@ void FontWithFace::render(const core::stringw& text, else { // Billboard text specific, use offset_y_bt instead - float glyph_offset_x = area.bearing_x * + float glyph_offset_x = (int)(area->bearing_x + + glyph_layout.x_offset * m_inverse_shaping) * (fallback[i] ? m_fallback_font_scale : scale); - float glyph_offset_y = area.offset_y_bt * + float glyph_offset_y = (int)(area->offset_y_bt + + glyph_layout.y_offset * m_inverse_shaping) * (fallback[i] ? m_fallback_font_scale : scale); offset.X += glyph_offset_x; offset.Y += glyph_offset_y; @@ -612,8 +673,16 @@ void FontWithFace::render(const core::stringw& text, offset.Y -= glyph_offset_y; } - indices.push_back(area.spriteno); - offset.X += getCharWidth(area, fallback[i], scale); + indices.push_back(area->spriteno); + if ((glyph_layout.flags & gui::GLF_QUICK_DRAW) != 0) + { + offset.X += glyph_layout.x_advance * scale; + } + else + { + offset.X += + (int)(glyph_layout.x_advance * m_inverse_shaping) * scale; + } } // for i < text_size // Do the actual rendering @@ -773,3 +842,89 @@ float FontWithFace::getCharWidth(const FontArea& area, bool fallback, else return area.advance_x * scale; } // getCharWidth + +// ---------------------------------------------------------------------------- +/* Cached version of render to make drawing as fast as possible. */ +void FontWithFace::drawText(const core::stringw& text, + const core::rect& position, + const video::SColor& color, bool hcenter, + bool vcenter, const core::rect* clip, + FontSettings* font_settings, + FontCharCollector* char_collector) + +{ +#ifndef SERVER_ONLY + if (text.empty() || ProfileWorld::isNoGraphics()) + return; + + if (disableTextShaping()) + { + render(text2GlyphsWithoutShaping(text), position, color, hcenter, + vcenter, clip, font_settings, char_collector); + return; + } + + auto& gls = font_manager->getCachedLayouts(text); + if (gls.empty() && !text.empty()) + font_manager->shape(StringUtils::wideToUtf32(text), gls); + + render(gls, position, color, hcenter, vcenter, clip, + font_settings, char_collector); +#endif +} // drawText + +// ---------------------------------------------------------------------------- +/* No text shaping and bidi operation of drawText. */ +void FontWithFace::drawTextQuick(const core::stringw& text, + const core::rect& position, + const video::SColor& color, bool hcenter, + bool vcenter, const core::rect* clip, + FontSettings* font_settings, + FontCharCollector* char_collector) +{ +#ifndef SERVER_ONLY + if (text.empty() || ProfileWorld::isNoGraphics()) + return; + + render(text2GlyphsWithoutShaping(text), position, color, hcenter, + vcenter, clip, font_settings, char_collector); +#endif +} // drawTextQuick + +// ---------------------------------------------------------------------------- +/** Convert text to drawable GlyphLayout without text shaping, used in digit + * font or debugging message, it will only use the preloaded characters. */ +std::vector FontWithFace:: + text2GlyphsWithoutShaping(const core::stringw& t) +{ + std::vector layouts; +#ifndef SERVER_ONLY + for (unsigned i = 0; i < t.size(); i++) + { + wchar_t c = t[i]; + gui::GlyphLayout gl = { 0 }; + if (c == L'\r' || // Windows breaks + c == L'\n' ) // Unix breaks + { + if (c == L'\r' && i != t.size() - 1 && t[i + 1] == L'\n') + i++; + gl.flags = gui::GLF_NEWLINE; + layouts.push_back(gl); + continue; + } + auto ret = m_character_glyph_info_map.find(c); + if (ret == m_character_glyph_info_map.end()) + continue; + const FontArea* area = m_face_ttf->getFontArea + (ret->second.font_number, ret->second.glyph_index); + if (area == NULL) + continue; + gl.index = ret->second.glyph_index; + gl.x_advance = area->advance_x; + gl.face_idx = ret->second.font_number; + gl.flags = gui::GLF_QUICK_DRAW; + layouts.push_back(gl); + } +#endif + return layouts; +} // text2GlyphsWithoutShaping diff --git a/src/font/font_with_face.hpp b/src/font/font_with_face.hpp index ed23f8f13..9509877ac 100644 --- a/src/font/font_with_face.hpp +++ b/src/font/font_with_face.hpp @@ -160,6 +160,9 @@ private: /** The dpi of this font. */ unsigned int m_face_dpi; + /** Used to undo the scale on text shaping, only need to take care of + * width. */ + float m_inverse_shaping; /** Store a list of loaded and tested character to a \ref GlyphInfo. */ std::map m_character_glyph_info_map; @@ -213,8 +216,6 @@ private: /** Add a character into \ref m_new_char_holder for lazy loading later. */ void addLazyLoadChar(wchar_t c) { m_new_char_holder.insert(c); } // ------------------------------------------------------------------------ - void insertGlyph(const GlyphInfo& gi); - // ------------------------------------------------------------------------ void setDPI(); // ------------------------------------------------------------------------ /** Override it if sub-class should not do lazy loading characters. */ @@ -233,6 +234,11 @@ private: /** Override it if sub-class has bold outline. */ virtual bool isBold() const { return false; } // ------------------------------------------------------------------------ + const FontArea* getUnknownFontArea() const; + // ------------------------------------------------------------------------ + std::vector text2GlyphsWithoutShaping( + const core::stringw& t); + // ------------------------------------------------------------------------ #ifndef SERVER_ONLY /** Override it if any outline shaping is needed to be done before * rendering the glyph into bitmap. @@ -251,18 +257,32 @@ public: // ------------------------------------------------------------------------ virtual void reset(); // ------------------------------------------------------------------------ - core::dimension2d getDimension(const wchar_t* text, - FontSettings* font_settings = NULL); + virtual core::dimension2d getDimension(const core::stringw& text, + FontSettings* font_settings = NULL); // ------------------------------------------------------------------------ int getCharacterFromPos(const wchar_t* text, int pixel_x, FontSettings* font_settings = NULL) const; // ------------------------------------------------------------------------ - void render(const core::stringw& text, const core::rect& position, - const video::SColor& color, bool hcenter, bool vcenter, - const core::rect* clip, + void render(const std::vector& gl, + const core::rect& position, const video::SColor& color, + bool hcenter, bool vcenter, const core::rect* clip, FontSettings* font_settings, FontCharCollector* char_collector = NULL); // ------------------------------------------------------------------------ + virtual void drawText(const core::stringw& text, + const core::rect& position, + const video::SColor& color, bool hcenter, + bool vcenter, const core::rect* clip, + FontSettings* font_settings, + FontCharCollector* char_collector = NULL); + // ------------------------------------------------------------------------ + void drawTextQuick(const core::stringw& text, + const core::rect& position, + const video::SColor& color, bool hcenter, bool vcenter, + const core::rect* clip, + FontSettings* font_settings, + FontCharCollector* char_collector = NULL); + // ------------------------------------------------------------------------ void dumpGlyphPage(const std::string& name); // ------------------------------------------------------------------------ void dumpGlyphPage(); @@ -277,6 +297,14 @@ public: unsigned int getDPI() const { return m_face_dpi; } // ------------------------------------------------------------------------ FaceTTF* getFaceTTF() const { return m_face_ttf; } + // ------------------------------------------------------------------------ + void insertGlyph(unsigned font_number, unsigned glyph_index); + // ------------------------------------------------------------------------ + int getFontMaxHeight() const { return m_font_max_height; } + // ------------------------------------------------------------------------ + virtual bool disableTextShaping() const { return false; } + // ------------------------------------------------------------------------ + float getInverseShaping() const { return m_inverse_shaping; } }; // FontWithFace #endif diff --git a/src/graphics/irr_driver.cpp b/src/graphics/irr_driver.cpp index 24601902e..6719bf416 100644 --- a/src/graphics/irr_driver.cpp +++ b/src/graphics/irr_driver.cpp @@ -1756,7 +1756,7 @@ void IrrDriver::displayFPS() gui::IGUIFont* font = GUIEngine::getSmallFont(); core::rect position; - const int fheight = font->getDimension(L"X").Height; + const int fheight = font->getHeightPerLine(); if (UserConfigParams::m_artist_debug_mode) position = core::rect(51, 0, 30*fheight+51, 2*fheight + fheight / 3); else @@ -1795,7 +1795,7 @@ void IrrDriver::displayFPS() no_trust--; static video::SColor fpsColor = video::SColor(255, 0, 0, 0); - font->draw(StringUtils::insertValues (L"FPS: ... Ping: %dms", ping), + font->drawQuick(StringUtils::insertValues (L"FPS: ... Ping: %dms", ping), core::rect< s32 >(100,0,400,50), fpsColor, false); return; } @@ -1839,7 +1839,7 @@ void IrrDriver::displayFPS() static video::SColor fpsColor = video::SColor(255, 0, 0, 0); - font->draw( fps_string.c_str(), position, fpsColor, false ); + font->drawQuick( fps_string.c_str(), position, fpsColor, false ); #endif } // updateFPS @@ -2030,16 +2030,16 @@ void IrrDriver::renderNetworkDebug() (int)d, (int)h, (int)m, (int)s, (int)f); gui::IGUIFont* font = GUIEngine::getFont(); - unsigned height = font->getDimension(L"X").Height + 2; + unsigned height = font->getHeightPerLine() + 2; background_rect.UpperLeftCorner.X += 5; static video::SColor black = video::SColor(255, 0, 0, 0); - font->draw(StringUtils::insertValues( + font->drawQuick(StringUtils::insertValues( L"Server time: %s Server state frequency: %d", str, NetworkConfig::get()->getStateFrequency()), background_rect, black, false); background_rect.UpperLeftCorner.Y += height; - font->draw(StringUtils::insertValues( + font->drawQuick(StringUtils::insertValues( L"Upload speed (KBps): %f Download speed (KBps): %f", (float)STKHost::get()->getUploadSpeed() / 1024.0f, (float)STKHost::get()->getDownloadSpeed() / 1024.0f, @@ -2047,7 +2047,7 @@ void IrrDriver::renderNetworkDebug() false); background_rect.UpperLeftCorner.Y += height; - font->draw(StringUtils::insertValues( + font->drawQuick(StringUtils::insertValues( L"Packet loss: %d Packet loss variance: %d", peer->getENetPeer()->packetLoss, peer->getENetPeer()->packetLossVariance, diff --git a/src/graphics/stk_text_billboard.cpp b/src/graphics/stk_text_billboard.cpp index 359d8b3b5..8cea4670a 100644 --- a/src/graphics/stk_text_billboard.cpp +++ b/src/graphics/stk_text_billboard.cpp @@ -88,8 +88,8 @@ void STKTextBillboard::updateAbsolutePosition() void STKTextBillboard::init(core::stringw text, FontWithFace* face) { m_chars = new std::vector(); - core::dimension2du size = face->getDimension(text.c_str()); - face->render(text, core::rect(0, 0, size.Width, size.Height), + core::dimension2du size = face->getDimension(text); + face->drawText(text, core::rect(0, 0, size.Width, size.Height), video::SColor(255,255,255,255), false, false, NULL, NULL, this); const float scale = 0.02f; @@ -258,8 +258,8 @@ void STKTextBillboard::init(core::stringw text, FontWithFace* face) void STKTextBillboard::initLegacy(core::stringw text, FontWithFace* face) { m_chars = new std::vector(); - core::dimension2du size = face->getDimension(text.c_str()); - face->render(text, core::rect(0, 0, size.Width, size.Height), + core::dimension2du size = face->getDimension(text); + face->drawText(text, core::rect(0, 0, size.Width, size.Height), video::SColor(255,255,255,255), false, false, NULL, NULL, this); const float scale = 0.02f; diff --git a/src/guiengine/scalable_font.cpp b/src/guiengine/scalable_font.cpp index 7356d53a7..688344473 100644 --- a/src/guiengine/scalable_font.cpp +++ b/src/guiengine/scalable_font.cpp @@ -18,10 +18,9 @@ #include "guiengine/scalable_font.hpp" -#include "font/face_ttf.hpp" +#include "font/font_manager.hpp" #include "font/font_settings.hpp" #include "font/font_with_face.hpp" -#include "utils/translation.hpp" namespace irr { @@ -31,8 +30,7 @@ namespace gui ScalableFont::ScalableFont(FontWithFace* face) { m_face = face; - m_font_settings = new FontSettings(false/*black_border*/, - translations->isRTLLanguage()); + m_font_settings = new FontSettings(); } // ScalableFont // ---------------------------------------------------------------------------- @@ -44,7 +42,6 @@ ScalableFont::~ScalableFont() // ---------------------------------------------------------------------------- void ScalableFont::updateRTL() { - m_font_settings->setRTL(translations->isRTLLanguage()); } // updateRTL // ---------------------------------------------------------------------------- @@ -53,27 +50,32 @@ void ScalableFont::setShadow(const irr::video::SColor &col) m_font_settings->setShadow(true); m_font_settings->setShadowColor(col); } // setShadow + // ---------------------------------------------------------------------------- void ScalableFont::disableShadow() { m_font_settings->setShadow(false); } // disableShadow + // ---------------------------------------------------------------------------- void ScalableFont::setBlackBorder(bool enabled) { m_font_settings->setBlackBorder(enabled); } // setBlackBorder + // ---------------------------------------------------------------------------- void ScalableFont::setColoredBorder(const irr::video::SColor &col) { m_font_settings->setColoredBorder(true); m_font_settings->setBorderColor(col); } // setColoredBorder + // ---------------------------------------------------------------------------- void ScalableFont::setThinBorder(bool thin) { m_font_settings->setThinBorder(thin); } // setThinBorder + // ---------------------------------------------------------------------------- void ScalableFont::disableColoredBorder() { @@ -104,10 +106,18 @@ void ScalableFont::draw(const core::stringw& text, bool hcenter, bool vcenter, const core::rect* clip) { -#ifndef SERVER_ONLY - m_face->render(text, position, color, hcenter, vcenter, clip, + m_face->drawText(text, position, color, hcenter, vcenter, clip, + m_font_settings); +} // draw + +// ---------------------------------------------------------------------------- +void ScalableFont::draw(const std::vector& gls, + const core::rect& position, video::SColor color, + bool hcenter, bool vcenter, + const core::rect* clip) +{ + m_face->render(gls, position, color, hcenter, vcenter, clip, m_font_settings); -#endif } // draw // ---------------------------------------------------------------------------- @@ -116,17 +126,18 @@ void ScalableFont::draw(const core::stringw& text, const video::SColor& color, bool hcenter, bool vcenter, const core::rect* clip, bool ignoreRTL) { -#ifndef SERVER_ONLY - bool previousRTL = m_font_settings->isRTL(); - if (ignoreRTL) - m_font_settings->setRTL(false); - - m_face->render(text, position, color, hcenter, vcenter, clip, + m_face->drawText(text, position, color, hcenter, vcenter, clip, m_font_settings); +} // draw - if (ignoreRTL) - m_font_settings->setRTL(previousRTL); -#endif +// ---------------------------------------------------------------------------- +void ScalableFont::drawQuick(const core::stringw& text, + const core::rect& position, + const video::SColor color, bool hcenter, + bool vcenter, const core::rect* clip) +{ + m_face->drawTextQuick(text, position, color, hcenter, vcenter, clip, + m_font_settings); } // draw // ---------------------------------------------------------------------------- @@ -141,13 +152,36 @@ IGUISpriteBank* ScalableFont::getSpriteBank() const return m_face->getSpriteBank(); } // getSpriteBank -// ------------------------------------------------------------------------ -u32 ScalableFont::getSpriteNoFromChar(const wchar_t *c) const +// ---------------------------------------------------------------------------- +s32 ScalableFont::getHeightPerLine() const { - const FontArea& area = - m_face->getAreaFromCharacter(*c, NULL/*fallback_font*/); - return area.spriteno; -} // getSpriteNoFromChar + return m_face->getFontMaxHeight() * m_font_settings->getScale(); +} // getHeightPerLine + +// ---------------------------------------------------------------------------- +/** Convert text to glyph layouts for fast rendering with caching enabled + * If line_data is not null, each broken line u32string will be saved and + * can be used for advanced glyph and text mapping, and cache will be + * disabled. + */ +void ScalableFont::initGlyphLayouts(const core::stringw& text, + std::vector& gls, + std::vector* line_data) +{ +#ifndef SERVER_ONLY + font_manager->initGlyphLayouts(text, gls, line_data); +#endif +} // initGlyphLayouts + +// ---------------------------------------------------------------------------- +f32 ScalableFont::getInverseShaping() const +{ +#ifndef SERVER_ONLY + return m_face->getInverseShaping(); +#else + return 1.0f; +#endif +} // getShapingScale } // end namespace gui } // end namespace irr diff --git a/src/guiengine/scalable_font.hpp b/src/guiengine/scalable_font.hpp index 56fcbc1a8..234786a6e 100644 --- a/src/guiengine/scalable_font.hpp +++ b/src/guiengine/scalable_font.hpp @@ -49,9 +49,9 @@ public: // ------------------------------------------------------------------------ const FontSettings* getFontSettings() const { return m_font_settings; } // ------------------------------------------------------------------------ - void setScale(float scale); + virtual void setScale(float scale); // ------------------------------------------------------------------------ - float getScale() const; + virtual float getScale() const; // ------------------------------------------------------------------------ void setShadow(const irr::video::SColor &col); // ------------------------------------------------------------------------ @@ -78,9 +78,26 @@ public: video::SColor color, bool hcenter = false, bool vcenter = false, const core::rect* clip = 0); // ------------------------------------------------------------------------ + virtual void drawQuick(const core::stringw& text, + const core::rect& position, + video::SColor color, bool hcenter = false, + bool vcenter = false, + const core::rect* clip = 0); + // ------------------------------------------------------------------------ + virtual void draw(const std::vector& gls, + const core::rect& position, + video::SColor color, bool hcenter = false, + bool vcenter = false, const core::rect* clip = 0); + // ------------------------------------------------------------------------ + virtual void initGlyphLayouts(const core::stringw& text, + std::vector& gls, + std::vector* line_data = NULL); + // ------------------------------------------------------------------------ /** returns the dimension of a text */ virtual core::dimension2d getDimension(const wchar_t* text) const; // ------------------------------------------------------------------------ + virtual s32 getHeightPerLine() const; + // ------------------------------------------------------------------------ /** Calculates the index of the character in the text which is on a * specific position. */ virtual s32 getCharacterFromPos(const wchar_t* text, s32 pixel_x) const; @@ -91,8 +108,8 @@ public: /** gets the sprite bank */ virtual IGUISpriteBank* getSpriteBank() const; // ------------------------------------------------------------------------ - /** returns the sprite number from a given character */ - virtual u32 getSpriteNoFromChar(const wchar_t *c) const; + /** returns the sprite number from a given character, unused in STK */ + virtual u32 getSpriteNoFromChar(const wchar_t *c) const { return 0; } // ------------------------------------------------------------------------ // Below is not used: /** set an Pixel Offset on Drawing ( scale position on width ) */ @@ -108,6 +125,8 @@ public: virtual s32 getKerningHeight() const { return 0; } // ------------------------------------------------------------------------ virtual void setInvisibleCharacters( const wchar_t *s ) {} + // ------------------------------------------------------------------------ + virtual f32 getInverseShaping() const; }; diff --git a/src/guiengine/widgets/spinner_widget.cpp b/src/guiengine/widgets/spinner_widget.cpp index f7b9b1416..1334a7d83 100644 --- a/src/guiengine/widgets/spinner_widget.cpp +++ b/src/guiengine/widgets/spinner_widget.cpp @@ -23,6 +23,7 @@ #include "guiengine/widgets/spinner_widget.hpp" #include "io/file_manager.hpp" #include "utils/string_utils.hpp" +#include "utils/log.hpp" #include "utils/translation.hpp" #include diff --git a/src/io/xml_node.cpp b/src/io/xml_node.cpp index 25a40f59b..9ef5eabbb 100644 --- a/src/io/xml_node.cpp +++ b/src/io/xml_node.cpp @@ -19,6 +19,7 @@ #include "io/file_manager.hpp" #include "io/xml_node.hpp" #include "utils/interpolation_array.hpp" +#include "utils/log.hpp" #include "utils/vec3.hpp" #include diff --git a/src/network/network.cpp b/src/network/network.cpp index 00016d6b1..f64e3c771 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -23,6 +23,7 @@ #include "network/network_config.hpp" #include "network/network_string.hpp" #include "network/transport_address.hpp" +#include "utils/log.hpp" #include "utils/time.hpp" #include diff --git a/src/network/rewind_info.cpp b/src/network/rewind_info.cpp index 9b53753c7..4f9f2a890 100644 --- a/src/network/rewind_info.cpp +++ b/src/network/rewind_info.cpp @@ -22,6 +22,7 @@ #include "network/rewinder.hpp" #include "network/rewind_manager.hpp" #include "items/projectile_manager.hpp" +#include "utils/log.hpp" /** Constructor for a state: it only takes the size, and allocates a buffer * for all state info. diff --git a/src/network/server_config.cpp b/src/network/server_config.cpp index e2f7cb9ae..abb318241 100644 --- a/src/network/server_config.cpp +++ b/src/network/server_config.cpp @@ -16,6 +16,8 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#include "utils/log.hpp" + #include // The order here is important. If all_params is declared later (e.g. after diff --git a/src/network/transport_address.cpp b/src/network/transport_address.cpp index 8a0fee3fb..bbbc64be1 100644 --- a/src/network/transport_address.cpp +++ b/src/network/transport_address.cpp @@ -17,6 +17,7 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "network/transport_address.hpp" +#include "utils/log.hpp" #ifdef WIN32 # include diff --git a/src/race/grand_prix_manager.cpp b/src/race/grand_prix_manager.cpp index 17d793fc7..4f7aa46db 100644 --- a/src/race/grand_prix_manager.cpp +++ b/src/race/grand_prix_manager.cpp @@ -20,6 +20,7 @@ #include "config/user_config.hpp" #include "io/file_manager.hpp" +#include "utils/log.hpp" #include "utils/string_utils.hpp" #include diff --git a/src/race/highscore_manager.cpp b/src/race/highscore_manager.cpp index d8a456edb..50409004f 100644 --- a/src/race/highscore_manager.cpp +++ b/src/race/highscore_manager.cpp @@ -26,6 +26,7 @@ #include "io/utf_writer.hpp" #include "race/race_manager.hpp" #include "utils/constants.hpp" +#include "utils/log.hpp" #include "utils/string_utils.hpp" #include "utils/translation.hpp" diff --git a/src/race/highscores.cpp b/src/race/highscores.cpp index a317b521a..ab95312a4 100644 --- a/src/race/highscores.cpp +++ b/src/race/highscores.cpp @@ -21,6 +21,7 @@ #include "io/utf_writer.hpp" #include "io/xml_node.hpp" #include "race/race_manager.hpp" +#include "utils/log.hpp" #include #include diff --git a/src/tracks/track.hpp b/src/tracks/track.hpp index ef9d3b9a7..4739402c3 100644 --- a/src/tracks/track.hpp +++ b/src/tracks/track.hpp @@ -36,6 +36,7 @@ using namespace irr; #include "LinearMath/btTransform.h" #include "utils/aligned_array.hpp" +#include "utils/log.hpp" #include "utils/translation.hpp" #include "utils/vec3.hpp" #include "utils/ptr_vector.hpp" diff --git a/src/utils/string_utils.cpp b/src/utils/string_utils.cpp index 6ea03169c..3a657a53e 100644 --- a/src/utils/string_utils.cpp +++ b/src/utils/string_utils.cpp @@ -21,9 +21,12 @@ #include "utils/string_utils.hpp" #include "config/stk_config.hpp" +#include "utils/constants.hpp" #include "utils/log.hpp" #include "utils/time.hpp" +#include "utils/types.hpp" #include "utils/utf8.h" +#include "irrArray.h" #include "coreutil.h" @@ -214,6 +217,67 @@ namespace StringUtils } } // split + //------------------------------------------------------------------------- + /** Splits a string into substrings separated by a certain character, and + * returns a std::vector of all those substring. E.g.: + * split("a b=c d=e",' ') --> ["a", "b=c", "d=e"] + * \param s The string to split. + * \param c The character by which the string is split. + */ + std::vector split(const std::u32string& s, char32_t c, + bool keepSplitChar) + { + std::vector result; + + try + { + std::u32string::size_type start=0; + while(start < (unsigned int) s.size()) + { + std::u32string::size_type i=s.find(c, start); + if (i!=std::u32string::npos) + { + if (keepSplitChar) + { + int from = (int)start-1; + if (from < 0) from = 0; + + result.push_back(std::u32string(s, from, i-from)); + } + else result.push_back(std::u32string(s,start, i-start)); + + start=i+1; + } + else // end of string reached + { + if (keepSplitChar && start != 0) + result.push_back(std::u32string(s,start-1)); + else + result.push_back(std::u32string(s,start)); + return result; + } + } + return result; + } + catch (std::exception& e) + { + Log::error("StringUtils", + "Error in split(std::string) : %s @ line %i : %s.", + __FILE__, __LINE__, e.what()); + Log::error("StringUtils", "Splitting '%s'.", + wideToUtf8(utf32ToWide(s)).c_str()); + + for (int n=0; n<(int)result.size(); n++) + { + Log::error("StringUtils", "Split : %s", + wideToUtf8(utf32ToWide(result[n])).c_str()); + } + + assert(false); // in debug mode, trigger debugger + exit(1); + } + } // split + //------------------------------------------------------------------------- /** Splits a string into substrings separated by a certain character, and * returns a std::vector of all those substring. E.g.: @@ -791,7 +855,16 @@ namespace StringUtils std::string wideToUtf8(const wchar_t* input) { std::vector utf8line; - utf8::utf16to8(input, input + wcslen(input), back_inserter(utf8line)); + if (sizeof(wchar_t) == 2) + { + utf8::utf16to8(input, input + wcslen(input), + back_inserter(utf8line)); + } + else if (sizeof(wchar_t) == 4) + { + utf8::utf32to8(input, input + wcslen(input), + back_inserter(utf8line)); + } utf8line.push_back(0); return std::string(&utf8line[0]); } // wideToUtf8 @@ -808,10 +881,19 @@ namespace StringUtils /** Converts the irrlicht wide string to an utf8-encoded std::string. */ irr::core::stringw utf8ToWide(const char* input) { - std::vector utf16line; - utf8::utf8to16(input, input + strlen(input), back_inserter(utf16line)); - utf16line.push_back(0); - return irr::core::stringw(&utf16line[0]); + std::vector wchar_line; + if (sizeof(wchar_t) == 2) + { + utf8::utf8to16(input, input + strlen(input), + back_inserter(wchar_line)); + } + else if (sizeof(wchar_t) == 4) + { + utf8::utf8to32(input, input + strlen(input), + back_inserter(wchar_line)); + } + wchar_line.push_back(0); + return irr::core::stringw(&wchar_line[0]); } // utf8ToWide // ------------------------------------------------------------------------ @@ -1162,6 +1244,59 @@ namespace StringUtils #endif } // partOfLongUnicodeChar + // ------------------------------------------------------------------------ + irr::core::stringw utf32ToWide(const std::u32string& input) + { + std::vector wchar_line; + if (sizeof(wchar_t) == 2) + { + const uint32_t* chars = (const uint32_t*)input.c_str(); + utf8::utf16to32(chars, chars + input.size(), + back_inserter(wchar_line)); + } + else if (sizeof(wchar_t) == sizeof(char32_t)) + { + wchar_line.resize(input.size()); + memcpy(wchar_line.data(), input.c_str(), + input.size() * sizeof(char32_t)); + } + wchar_line.push_back(0); + return irr::core::stringw(&wchar_line[0]); + } // utf32ToWide + + // ------------------------------------------------------------------------ + std::u32string utf8ToUtf32(const std::string &input) + { + std::u32string result; + utf8::utf8to32(input.c_str(), input.c_str() + input.size(), + back_inserter(result)); + return result; + } // utf8ToUtf32 + + // ------------------------------------------------------------------------ + std::string utf32ToUtf8(const std::u32string& input) + { + std::string result; + utf8::utf32to8(input.c_str(), input.c_str() + input.size(), + back_inserter(result)); + return result; + } // utf32ToUtf8 + + // ------------------------------------------------------------------------ + std::u32string wideToUtf32(const irr::core::stringw& input) + { + std::u32string utf32_line; + if (sizeof(wchar_t) != sizeof(char32_t)) + { + const uint16_t* chars = (const uint16_t*)input.c_str(); + utf8::utf16to32(chars, chars + input.size(), + back_inserter(utf32_line)); + } + else if (sizeof(wchar_t) == sizeof(char32_t)) + utf32_line = (const char32_t*)input.c_str(); + return utf32_line; + } // wideToUtf32 + // ------------------------------------------------------------------------ /** At the moment only versionToInt is tested. */ @@ -1181,8 +1316,26 @@ namespace StringUtils assert(versionToInt("1-rc9" ) == 10000029); assert(versionToInt("1.0-rc1" ) == 10000021); // same as 1-rc1 } // unitTesting + // ------------------------------------------------------------------------ + std::string getUserAgentString() + { + std::string uagent(std::string("SuperTuxKart/") + STK_VERSION); +#ifdef WIN32 + uagent += (std::string)" (Windows)"; +#elif defined(__APPLE__) + uagent += (std::string)" (Macintosh)"; +#elif defined(__FreeBSD__) + uagent += (std::string)" (FreeBSD)"; +#elif defined(ANDROID) + uagent += (std::string)" (Android)"; +#elif defined(linux) + uagent += (std::string)" (Linux)"; +#else + // Unknown system type +#endif + return uagent; + } // getUserAgentString } // namespace StringUtils - /* EOF */ diff --git a/src/utils/string_utils.hpp b/src/utils/string_utils.hpp index 9b1863fb3..493a3ad17 100644 --- a/src/utils/string_utils.hpp +++ b/src/utils/string_utils.hpp @@ -27,9 +27,7 @@ #include #include #include -#include "utils/constants.hpp" -#include "utils/types.hpp" -#include "utils/log.hpp" +#include namespace StringUtils { @@ -58,6 +56,8 @@ namespace StringUtils std::string toLowerCase(const std::string&); std::vector split(const std::string& s, char c, bool keepSplitChar=false); + std::vector split(const std::u32string& s, char32_t c, + bool keepSplitChar=false); std::vector split(const irr::core::stringw& s, char c, bool keepSplitChar=false); std::vector splitToUInt(const std::string& s, char c, @@ -244,34 +244,20 @@ namespace StringUtils irr::core::stringw utf8ToWide(const char* input); irr::core::stringw utf8ToWide(const std::string &input); + std::u32string utf8ToUtf32(const std::string &input); std::string wideToUtf8(const wchar_t* input); std::string wideToUtf8(const irr::core::stringw& input); + std::string utf32ToUtf8(const std::u32string& input); std::string findAndReplace(const std::string& source, const std::string& find, const std::string& replace); std::string removeWhitespaces(const std::string& input); void breakText(const std::wstring& input, std::vector &output, unsigned int max_width, irr::gui::IGUIFont* font, bool right_to_left=false); bool breakable (wchar_t c); bool partOfLongUnicodeChar (wchar_t c); + irr::core::stringw utf32ToWide(const std::u32string& input); + std::u32string wideToUtf32(const irr::core::stringw& input); - inline std::string getUserAgentString() - { - std::string uagent(std::string("SuperTuxKart/") + STK_VERSION); -#ifdef WIN32 - uagent += (std::string)" (Windows)"; -#elif defined(__APPLE__) - uagent += (std::string)" (Macintosh)"; -#elif defined(__FreeBSD__) - uagent += (std::string)" (FreeBSD)"; -#elif defined(ANDROID) - uagent += (std::string)" (Android)"; -#elif defined(linux) - uagent += (std::string)" (Linux)"; -#else - // Unknown system type -#endif - return uagent; - } - + std::string getUserAgentString(); /** * Returns the hostname part of an url (if any) * diff --git a/src/utils/time.cpp b/src/utils/time.cpp index 1ad3bf0b1..4aba847fb 100644 --- a/src/utils/time.cpp +++ b/src/utils/time.cpp @@ -19,6 +19,7 @@ #include "utils/time.hpp" #include "graphics/irr_driver.hpp" +#include "utils/log.hpp" #include "utils/translation.hpp" #include diff --git a/src/utils/utf8/checked.h b/src/utils/utf8/checked.h index b6a5f9d84..aa9c6a7d2 100644 --- a/src/utils/utf8/checked.h +++ b/src/utils/utf8/checked.h @@ -230,6 +230,36 @@ namespace utf8 return result; } + template + u32bit_iterator utf16to32 (u16bit_iterator start, u16bit_iterator end, u32bit_iterator result) + { + while (start != end) + { + uint32_t cp = internal::mask16(*start++); + // Take care of surrogate pairs first + if (internal::is_lead_surrogate(cp)) + { + if (start != end) + { + uint32_t trail_surrogate = internal::mask16(*start++); + if (internal::is_trail_surrogate(trail_surrogate)) + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + else + throw invalid_utf16(static_cast(trail_surrogate)); + } + else + throw invalid_utf16(static_cast(cp)); + + } + // Lone trail surrogate + else if (internal::is_trail_surrogate(cp)) + throw invalid_utf16(static_cast(cp)); + + (*result++) = cp; + } + return result; + } + template u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) { @@ -254,6 +284,24 @@ namespace utf8 return result; } + template + u16bit_iterator utf32to16 (u32bit_iterator start, u32bit_iterator end, u16bit_iterator result) + { + while (start != end) + { + uint32_t cp = start++; + if (cp > 0xffff) + { + //make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } + template u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) { diff --git a/src/utils/utf8/unchecked.h b/src/utils/utf8/unchecked.h index 58d95521e..f4b41e98d 100644 --- a/src/utils/utf8/unchecked.h +++ b/src/utils/utf8/unchecked.h @@ -141,6 +141,23 @@ namespace utf8 return result; } + template + u32bit_iterator utf16to32 (u16bit_iterator start, u16bit_iterator end, u32bit_iterator result) + { + while (start != end) + { + uint32_t cp = internal::mask16(*start++); + // Take care of surrogate pairs first + if (internal::is_lead_surrogate(cp)) + { + uint32_t trail_surrogate = internal::mask16(*start++); + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + } + (*result++) = cp; + } + return result; + } + template u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) { @@ -165,6 +182,24 @@ namespace utf8 return result; } + template + u16bit_iterator utf32to16 (u32bit_iterator start, u32bit_iterator end, u16bit_iterator result) + { + while (start != end) + { + uint32_t cp = start++; + if (cp > 0xffff) + { + //make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } + template u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) {