Use libraqm for text layout

This commit is contained in:
Benau 2019-06-09 11:26:00 +08:00
parent 43d322c634
commit acb9054dcb
35 changed files with 1510 additions and 459 deletions

View File

@ -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 <algorithm>
#include <numeric>
#include <vector>
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<s32> 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<GlyphLayout> gls, f32 max_line_width,
f32 inverse_shaping, f32 scale, std::vector<std::vector<GlyphLayout> >& result);
}
inline core::dimension2d<u32> getGlyphLayoutsDimension(
const std::vector<GlyphLayout>& gls, s32 height_per_line, f32 inverse_shaping,
f32 scale, s32 cluster = -1)
{
core::dimension2d<f32> dim(0.0f, 0.0f);
core::dimension2d<f32> 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<u32> 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<GlyphLayout>& 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<f32> getGlyphLayoutsWidthPerLine(
const std::vector<GlyphLayout>& gls, f32 inverse_shaping, f32 scale)
{
std::vector<f32> 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<GlyphLayout>& gls, f32 max_line_width,
f32 inverse_shaping, f32 scale)
{
if (gls.size() < 2)
return;
std::vector<std::vector<GlyphLayout> > 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<GlyphLayout>& 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<GlyphLayout> gls, f32 max_line_width,
f32 inverse_shaping, f32 scale,
std::vector<std::vector<GlyphLayout> >& 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

View File

@ -10,10 +10,14 @@
#include "rect.h"
#include "irrString.h"
#include <string>
#include <vector>
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<s32>* 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<GlyphLayout>& gls, const core::rect<s32>& position,
video::SColor color, bool hcenter=false, bool vcenter=false,
const core::rect<s32>* 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<s32>& position,
video::SColor color, bool hcenter=false,
bool vcenter=false, const core::rect<s32>* 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<GlyphLayout>& gls, std::vector<std::u32string>* line_data = NULL) = 0;
virtual f32 getInverseShaping() const = 0;
virtual f32 getScale() const = 0;
virtual void setScale(f32 scale) = 0;
};
} // end namespace gui

View File

@ -8,11 +8,14 @@
#include "IGUIElement.h"
#include "SColor.h"
#include <vector>
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<GlyphLayout>& getGlyphLayouts() const = 0;
virtual void setGlyphLayouts(std::vector<GlyphLayout>& gls) = 0;
virtual void clearGlyphLayouts() = 0;
virtual void setUseGlyphLayoutsOnly(bool gls_only) = 0;
virtual bool useGlyphLayoutsOnly() const = 0;
};

View File

@ -53,6 +53,20 @@ public:
video::SColor color, bool hcenter=false,
bool vcenter=false, const core::rect<s32>* clip=0);
virtual void drawQuick(const core::stringw& text, const core::rect<s32>& position,
video::SColor color, bool hcenter=false,
bool vcenter=false, const core::rect<s32>* clip=0)
{
draw(text, position, color, hcenter, vcenter, clip);
}
virtual void draw(const std::vector<GlyphLayout>& gls, const core::rect<s32>& position,
video::SColor color, bool hcenter=false,
bool vcenter=false, const core::rect<s32>* clip=0) {}
virtual void initGlyphLayouts(const core::stringw& text,
std::vector<GlyphLayout>& gls, std::vector<std::u32string>* line_data = NULL) {}
//! returns the dimension of a text
virtual core::dimension2d<u32> 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:

View File

@ -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<s32> 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<s32> 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; i<BrokenText.size(); ++i)
{
if (HAlign == EGUIA_LOWERRIGHT)
{
r.UpperLeftCorner.X = frameRect.LowerRightCorner.X -
font->getDimension(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; i<size; ++i)
{
c = Text[i];
bool lineBreak = false;
if (c == L'\r') // Mac or Windows breaks
{
lineBreak = true;
if (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';
}
word += c;
if (word.size())
{
const s32 wordlgth = font->getDimension(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;
}

View File

@ -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<GlyphLayout>& getGlyphLayouts() const { return m_glyph_layouts; }
virtual void setGlyphLayouts(std::vector<GlyphLayout>& 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<GlyphLayout> m_glyph_layouts;
};
} // end namespace gui

View File

@ -21,6 +21,7 @@
#include "graphics/irr_driver.hpp"
#include "io/file_manager.hpp"
#include "utils/log.hpp"
#include "utils/string_utils.hpp"
#include <IWriteFile.h>

View File

@ -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 <fstream>

View File

@ -38,6 +38,7 @@ static std::vector<UserConfigParam*> 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"

View File

@ -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; }
// ------------------------------------------------------------------------

View File

@ -45,7 +45,8 @@ public:
virtual void init() OVERRIDE;
// ------------------------------------------------------------------------
virtual void reset() OVERRIDE;
// ------------------------------------------------------------------------
virtual bool disableTextShaping() const OVERRIDE { return true; }
}; // DigitFace
#endif

View File

@ -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 <cassert>
@ -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<std::pair<FT_Face, std::map<unsigned, FontArea> > > m_faces;
std::vector<std::pair<FT_Face, std::map<unsigned, FontArea> > > 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<FT_Face> faces)
void loadTTF(std::vector<FT_Face> faces)
{
for (unsigned int i = 0; i < faces.size(); i++)
m_faces.emplace_back(faces[i], std::map<unsigned, FontArea>());
m_ft_faces.emplace_back(faces[i], std::map<unsigned, FontArea>());
}
// ------------------------------------------------------------------------
/** 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;

View File

@ -28,6 +28,11 @@
#include "utils/string_utils.hpp"
#include "utils/translation.hpp"
#ifndef SERVER_ONLY
#include <fribidi/fribidi.h>
#include <raqm.h>
#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<FT_Face>
}
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<bool>& 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<bool>& line,
std::vector<bool>& char_bool)
{
// Check if first character has strong RTL, then consider this line is
// RTL
std::vector<FriBidiCharType> types;
std::vector<FriBidiLevel> 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<FriBidiBracketType> 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<irr::gui::GlyphLayout>& gls,
std::vector<std::u32string>* 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<gui::GlyphLayout> cur_line;
std::vector<bool> 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<irr::gui::GlyphLayout>& gls,
std::vector<std::u32string>* 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<FT_Face> 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<FT_Face> 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

View File

@ -28,6 +28,7 @@
#include "utils/no_copy.hpp"
#include <string>
#include <map>
#include <typeindex>
#include <unordered_map>
#include <vector>
@ -35,6 +36,9 @@
#ifndef SERVER_ONLY
#include <ft2build.h>
#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<FT_Face> 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<FT_Face, uint16_t> m_ft_faces_to_index;
/** Text drawn to glyph layouts cache. */
std::map<irr::core::stringw,
std::vector<irr::gui::GlyphLayout> > m_cached_gls;
#endif
/** Map type for each \ref FontWithFace with a index, save getting time in
* \ref getFont. */
std::unordered_map<std::type_index, int> m_font_type_map;
public:
LEAK_CHECK()
// ------------------------------------------------------------------------
@ -99,13 +117,25 @@ public:
FT_Library getFTLibrary() const { return m_ft_library; }
// ------------------------------------------------------------------------
std::vector<FT_Face> loadTTF(const std::vector<std::string>& ttf_list);
// ------------------------------------------------------------------------
unsigned getShapingDPI() const { return m_shaping_dpi; }
// ------------------------------------------------------------------------
void shape(const std::u32string& text,
std::vector<irr::gui::GlyphLayout>& gls,
std::vector<std::u32string>* line_data = NULL);
// ------------------------------------------------------------------------
std::vector<irr::gui::GlyphLayout>& getCachedLayouts
(const irr::core::stringw& str) { return m_cached_gls[str]; }
// ------------------------------------------------------------------------
void initGlyphLayouts(const irr::core::stringw& text,
std::vector<irr::gui::GlyphLayout>& gls,
std::vector<std::u32string>* line_data = NULL);
#endif
// ------------------------------------------------------------------------
void loadFonts();
// ------------------------------------------------------------------------
void unitTesting();
}; // FontManager
extern FontManager *font_manager;

View File

@ -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 <array>
// ----------------------------------------------------------------------------
@ -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<wchar_t, GlyphInfo>::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<u32> FontWithFace::getDimension(const wchar_t* text,
FontSettings* font_settings)
core::dimension2d<u32> FontWithFace::getDimension(const core::stringw& text,
FontSettings* font_settings)
{
#ifdef SERVER_ONLY
return core::dimension2d<u32>(1, 1);
@ -414,42 +442,18 @@ core::dimension2d<u32> FontWithFace::getDimension(const wchar_t* text,
return core::dimension2d<u32>(1, 1);
const float scale = font_settings ? font_settings->getScale() : 1.0f;
// Test if lazy load char is needed
insertCharacters(text);
updateCharactersList();
core::dimension2d<float> dim(0.0f, 0.0f);
core::dimension2d<float> 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<u32> 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<gui::GlyphLayout>& gl,
const core::rect<s32>& position,
const video::SColor& color, bool hcenter,
bool vcenter, const core::rect<s32>* 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<s32> 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<float> offset(float(position.UpperLeftCorner.X),
float(position.UpperLeftCorner.Y));
core::dimension2d<s32> 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<s32> clippedRect(core::position2d<s32>
@ -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<s32> indices(text_size);
core::array<core::position2d<float>> offsets(text_size);
std::vector<bool> 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<s32>& position,
const video::SColor& color, bool hcenter,
bool vcenter, const core::rect<s32>* 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<s32>& position,
const video::SColor& color, bool hcenter,
bool vcenter, const core::rect<s32>* 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<gui::GlyphLayout> FontWithFace::
text2GlyphsWithoutShaping(const core::stringw& t)
{
std::vector<gui::GlyphLayout> 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

View File

@ -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<wchar_t, GlyphInfo> 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<gui::GlyphLayout> 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<u32> getDimension(const wchar_t* text,
FontSettings* font_settings = NULL);
virtual core::dimension2d<u32> 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<s32>& position,
const video::SColor& color, bool hcenter, bool vcenter,
const core::rect<s32>* clip,
void render(const std::vector<gui::GlyphLayout>& gl,
const core::rect<s32>& position, const video::SColor& color,
bool hcenter, bool vcenter, const core::rect<s32>* clip,
FontSettings* font_settings,
FontCharCollector* char_collector = NULL);
// ------------------------------------------------------------------------
virtual void drawText(const core::stringw& text,
const core::rect<s32>& position,
const video::SColor& color, bool hcenter,
bool vcenter, const core::rect<s32>* clip,
FontSettings* font_settings,
FontCharCollector* char_collector = NULL);
// ------------------------------------------------------------------------
void drawTextQuick(const core::stringw& text,
const core::rect<s32>& position,
const video::SColor& color, bool hcenter, bool vcenter,
const core::rect<s32>* 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

View File

@ -1756,7 +1756,7 @@ void IrrDriver::displayFPS()
gui::IGUIFont* font = GUIEngine::getSmallFont();
core::rect<s32> position;
const int fheight = font->getDimension(L"X").Height;
const int fheight = font->getHeightPerLine();
if (UserConfigParams::m_artist_debug_mode)
position = core::rect<s32>(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,

View File

@ -88,8 +88,8 @@ void STKTextBillboard::updateAbsolutePosition()
void STKTextBillboard::init(core::stringw text, FontWithFace* face)
{
m_chars = new std::vector<STKTextBillboardChar>();
core::dimension2du size = face->getDimension(text.c_str());
face->render(text, core::rect<s32>(0, 0, size.Width, size.Height),
core::dimension2du size = face->getDimension(text);
face->drawText(text, core::rect<s32>(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<STKTextBillboardChar>();
core::dimension2du size = face->getDimension(text.c_str());
face->render(text, core::rect<s32>(0, 0, size.Width, size.Height),
core::dimension2du size = face->getDimension(text);
face->drawText(text, core::rect<s32>(0, 0, size.Width, size.Height),
video::SColor(255,255,255,255), false, false, NULL, NULL, this);
const float scale = 0.02f;

View File

@ -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<s32>* 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<GlyphLayout>& gls,
const core::rect<s32>& position, video::SColor color,
bool hcenter, bool vcenter,
const core::rect<s32>* 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<s32>* 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<s32>& position,
const video::SColor color, bool hcenter,
bool vcenter, const core::rect<s32>* 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<GlyphLayout>& gls,
std::vector<std::u32string>* 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

View File

@ -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<s32>* clip = 0);
// ------------------------------------------------------------------------
virtual void drawQuick(const core::stringw& text,
const core::rect<s32>& position,
video::SColor color, bool hcenter = false,
bool vcenter = false,
const core::rect<s32>* clip = 0);
// ------------------------------------------------------------------------
virtual void draw(const std::vector<GlyphLayout>& gls,
const core::rect<s32>& position,
video::SColor color, bool hcenter = false,
bool vcenter = false, const core::rect<s32>* clip = 0);
// ------------------------------------------------------------------------
virtual void initGlyphLayouts(const core::stringw& text,
std::vector<GlyphLayout>& gls,
std::vector<std::u32string>* line_data = NULL);
// ------------------------------------------------------------------------
/** returns the dimension of a text */
virtual core::dimension2d<u32> 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;
};

View File

@ -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 <IGUIElement.h>

View File

@ -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 <stdexcept>

View File

@ -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 <string.h>

View File

@ -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.

View File

@ -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 <vector>
// The order here is important. If all_params is declared later (e.g. after

View File

@ -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 <iphlpapi.h>

View File

@ -20,6 +20,7 @@
#include "config/user_config.hpp"
#include "io/file_manager.hpp"
#include "utils/log.hpp"
#include "utils/string_utils.hpp"
#include <algorithm>

View File

@ -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"

View File

@ -21,6 +21,7 @@
#include "io/utf_writer.hpp"
#include "io/xml_node.hpp"
#include "race/race_manager.hpp"
#include "utils/log.hpp"
#include <stdexcept>
#include <fstream>

View File

@ -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"

View File

@ -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<std::u32string> split(const std::u32string& s, char32_t c,
bool keepSplitChar)
{
std::vector<std::u32string> 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<char> 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<wchar_t> utf16line;
utf8::utf8to16(input, input + strlen(input), back_inserter(utf16line));
utf16line.push_back(0);
return irr::core::stringw(&utf16line[0]);
std::vector<wchar_t> 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_t> 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 */

View File

@ -27,9 +27,7 @@
#include <sstream>
#include <irrString.h>
#include <IGUIFont.h>
#include "utils/constants.hpp"
#include "utils/types.hpp"
#include "utils/log.hpp"
#include <irrTypes.h>
namespace StringUtils
{
@ -58,6 +56,8 @@ namespace StringUtils
std::string toLowerCase(const std::string&);
std::vector<std::string> split(const std::string& s, char c,
bool keepSplitChar=false);
std::vector<std::u32string> split(const std::u32string& s, char32_t c,
bool keepSplitChar=false);
std::vector<irr::core::stringw> split(const irr::core::stringw& s,
char c, bool keepSplitChar=false);
std::vector<uint32_t> 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<std::wstring> &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)
*

View File

@ -19,6 +19,7 @@
#include "utils/time.hpp"
#include "graphics/irr_driver.hpp"
#include "utils/log.hpp"
#include "utils/translation.hpp"
#include <ctime>

View File

@ -230,6 +230,36 @@ namespace utf8
return result;
}
template <typename u16bit_iterator, typename u32bit_iterator>
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<uint16_t>(trail_surrogate));
}
else
throw invalid_utf16(static_cast<uint16_t>(cp));
}
// Lone trail surrogate
else if (internal::is_trail_surrogate(cp))
throw invalid_utf16(static_cast<uint16_t>(cp));
(*result++) = cp;
}
return result;
}
template <typename u16bit_iterator, typename octet_iterator>
u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result)
{
@ -254,6 +284,24 @@ namespace utf8
return result;
}
template <typename u16bit_iterator, typename u32bit_iterator>
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<uint16_t>((cp >> 10) + internal::LEAD_OFFSET);
*result++ = static_cast<uint16_t>((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN);
}
else
*result++ = static_cast<uint16_t>(cp);
}
return result;
}
template <typename octet_iterator, typename u32bit_iterator>
u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result)
{

View File

@ -141,6 +141,23 @@ namespace utf8
return result;
}
template <typename u16bit_iterator, typename u32bit_iterator>
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 <typename u16bit_iterator, typename octet_iterator>
u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result)
{
@ -165,6 +182,24 @@ namespace utf8
return result;
}
template <typename u16bit_iterator, typename u32bit_iterator>
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<uint16_t>((cp >> 10) + internal::LEAD_OFFSET);
*result++ = static_cast<uint16_t>((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN);
}
else
*result++ = static_cast<uint16_t>(cp);
}
return result;
}
template <typename octet_iterator, typename u32bit_iterator>
u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result)
{