mirror of
https://github.com/love2d/love.git
synced 2026-08-20 04:30:09 +02:00
font rendering: reimplement tab-as-spaces support.
This commit is contained in:
@@ -167,7 +167,7 @@ int GenericShaper::computeWordWrapIndex(const ColoredCodepoints& codepoints, Ran
|
|||||||
float newwidth = w + getKerning(prevglyph, g) + getGlyphAdvance(g);
|
float newwidth = w + getKerning(prevglyph, g) + getGlyphAdvance(g);
|
||||||
|
|
||||||
// Only wrap when there's a non-space character.
|
// Only wrap when there's a non-space character.
|
||||||
if (newwidth > wraplimit && g != ' ')
|
if (newwidth > wraplimit && !isWhitespace(g))
|
||||||
{
|
{
|
||||||
// Rewind to the last seen space when wrapping.
|
// Rewind to the last seen space when wrapping.
|
||||||
if (lastspaceindex != -1)
|
if (lastspaceindex != -1)
|
||||||
@@ -179,10 +179,10 @@ int GenericShaper::computeWordWrapIndex(const ColoredCodepoints& codepoints, Ran
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Don't count trailing spaces in the output width.
|
// Don't count trailing spaces in the output width.
|
||||||
if (g == ' ')
|
if (isWhitespace(g))
|
||||||
{
|
{
|
||||||
lastspaceindex = i;
|
lastspaceindex = i;
|
||||||
if (prevglyph != ' ')
|
if (!isWhitespace(prevglyph))
|
||||||
widthbeforelastspace = w;
|
widthbeforelastspace = w;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace love
|
|||||||
namespace font
|
namespace font
|
||||||
{
|
{
|
||||||
|
|
||||||
void getCodepointsFromString(const std::string& text, std::vector<uint32>& codepoints)
|
void getCodepointsFromString(const std::string &text, std::vector<uint32> &codepoints)
|
||||||
{
|
{
|
||||||
codepoints.reserve(text.size());
|
codepoints.reserve(text.size());
|
||||||
|
|
||||||
@@ -45,20 +45,20 @@ void getCodepointsFromString(const std::string& text, std::vector<uint32>& codep
|
|||||||
codepoints.push_back(g);
|
codepoints.push_back(g);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (utf8::exception& e)
|
catch (utf8::exception &e)
|
||||||
{
|
{
|
||||||
throw love::Exception("UTF-8 decoding error: %s", e.what());
|
throw love::Exception("UTF-8 decoding error: %s", e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void getCodepointsFromString(const std::vector<ColoredString>& strs, ColoredCodepoints& codepoints)
|
void getCodepointsFromString(const std::vector<ColoredString> &strs, ColoredCodepoints &codepoints)
|
||||||
{
|
{
|
||||||
if (strs.empty())
|
if (strs.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
codepoints.cps.reserve(strs[0].str.size());
|
codepoints.cps.reserve(strs[0].str.size());
|
||||||
|
|
||||||
for (const ColoredString& cstr : strs)
|
for (const ColoredString &cstr : strs)
|
||||||
{
|
{
|
||||||
// No need to add the color if the string is empty anyway, and the code
|
// No need to add the color if the string is empty anyway, and the code
|
||||||
// further on assumes no two colors share the same starting position.
|
// further on assumes no two colors share the same starting position.
|
||||||
@@ -87,7 +87,10 @@ TextShaper::TextShaper(Rasterizer *rasterizer)
|
|||||||
, dpiScales{rasterizer->getDPIScale()}
|
, dpiScales{rasterizer->getDPIScale()}
|
||||||
, height(floorf(rasterizer->getHeight() / rasterizer->getDPIScale() + 0.5f))
|
, height(floorf(rasterizer->getHeight() / rasterizer->getDPIScale() + 0.5f))
|
||||||
, lineHeight(1)
|
, lineHeight(1)
|
||||||
|
, useSpacesForTab(false)
|
||||||
{
|
{
|
||||||
|
if (!rasterizer->hasGlyph('\t'))
|
||||||
|
useSpacesForTab = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextShaper::~TextShaper()
|
TextShaper::~TextShaper()
|
||||||
@@ -141,7 +144,7 @@ bool TextShaper::hasGlyph(uint32 glyph) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TextShaper::hasGlyphs(const std::string& text) const
|
bool TextShaper::hasGlyphs(const std::string &text) const
|
||||||
{
|
{
|
||||||
if (text.size() == 0)
|
if (text.size() == 0)
|
||||||
return false;
|
return false;
|
||||||
@@ -178,7 +181,7 @@ float TextShaper::getKerning(uint32 leftglyph, uint32 rightglyph)
|
|||||||
float k = 0.0f;
|
float k = 0.0f;
|
||||||
bool found = false;
|
bool found = false;
|
||||||
|
|
||||||
for (const auto& r : rasterizers)
|
for (const auto &r : rasterizers)
|
||||||
{
|
{
|
||||||
if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph))
|
if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph))
|
||||||
{
|
{
|
||||||
@@ -195,7 +198,7 @@ float TextShaper::getKerning(uint32 leftglyph, uint32 rightglyph)
|
|||||||
return k;
|
return k;
|
||||||
}
|
}
|
||||||
|
|
||||||
float TextShaper::getKerning(const std::string& leftchar, const std::string& rightchar)
|
float TextShaper::getKerning(const std::string &leftchar, const std::string &rightchar)
|
||||||
{
|
{
|
||||||
uint32 left = 0;
|
uint32 left = 0;
|
||||||
uint32 right = 0;
|
uint32 right = 0;
|
||||||
@@ -215,8 +218,6 @@ float TextShaper::getKerning(const std::string& leftchar, const std::string& rig
|
|||||||
|
|
||||||
int TextShaper::getGlyphAdvance(uint32 glyph, GlyphIndex *glyphindex)
|
int TextShaper::getGlyphAdvance(uint32 glyph, GlyphIndex *glyphindex)
|
||||||
{
|
{
|
||||||
// TODO: use spaces for tab
|
|
||||||
|
|
||||||
const auto it = glyphAdvances.find(glyph);
|
const auto it = glyphAdvances.find(glyph);
|
||||||
if (it != glyphAdvances.end())
|
if (it != glyphAdvances.end())
|
||||||
{
|
{
|
||||||
@@ -226,10 +227,14 @@ int TextShaper::getGlyphAdvance(uint32 glyph, GlyphIndex *glyphindex)
|
|||||||
}
|
}
|
||||||
|
|
||||||
int rasterizeri = 0;
|
int rasterizeri = 0;
|
||||||
|
uint32 realglyph = glyph;
|
||||||
|
|
||||||
|
if (glyph == '\t' && isUsingSpacesForTab())
|
||||||
|
realglyph = ' ';
|
||||||
|
|
||||||
for (size_t i = 0; i < rasterizers.size(); i++)
|
for (size_t i = 0; i < rasterizers.size(); i++)
|
||||||
{
|
{
|
||||||
if (rasterizers[i]->hasGlyph(glyph))
|
if (rasterizers[i]->hasGlyph(realglyph))
|
||||||
{
|
{
|
||||||
rasterizeri = (int) i;
|
rasterizeri = (int) i;
|
||||||
break;
|
break;
|
||||||
@@ -237,9 +242,12 @@ int TextShaper::getGlyphAdvance(uint32 glyph, GlyphIndex *glyphindex)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto &r = rasterizers[rasterizeri];
|
const auto &r = rasterizers[rasterizeri];
|
||||||
int advance = floorf(r->getGlyphSpacing(glyph) / r->getDPIScale() + 0.5f);
|
int advance = floorf(r->getGlyphSpacing(realglyph) / r->getDPIScale() + 0.5f);
|
||||||
|
|
||||||
GlyphIndex glyphi = {r->getGlyphIndex(glyph), rasterizeri};
|
if (glyph == '\t' && realglyph == ' ')
|
||||||
|
advance *= SPACES_PER_TAB;
|
||||||
|
|
||||||
|
GlyphIndex glyphi = {r->getGlyphIndex(realglyph), rasterizeri};
|
||||||
|
|
||||||
glyphAdvances[glyph] = std::make_pair(advance, glyphi);
|
glyphAdvances[glyph] = std::make_pair(advance, glyphi);
|
||||||
if (glyphindex)
|
if (glyphindex)
|
||||||
@@ -260,7 +268,7 @@ int TextShaper::getWidth(const std::string &str)
|
|||||||
return info.width;
|
return info.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t findNewline(const ColoredCodepoints& codepoints, size_t start)
|
static size_t findNewline(const ColoredCodepoints &codepoints, size_t start)
|
||||||
{
|
{
|
||||||
for (size_t i = start; i < codepoints.cps.size(); i++)
|
for (size_t i = start; i < codepoints.cps.size(); i++)
|
||||||
{
|
{
|
||||||
@@ -273,7 +281,7 @@ static size_t findNewline(const ColoredCodepoints& codepoints, size_t start)
|
|||||||
return codepoints.cps.size();
|
return codepoints.cps.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextShaper::getWrap(const ColoredCodepoints& codepoints, float wraplimit, std::vector<Range>& lineranges, std::vector<int>* linewidths)
|
void TextShaper::getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector<Range> &lineranges, std::vector<int> *linewidths)
|
||||||
{
|
{
|
||||||
size_t nextnewline = findNewline(codepoints, 0);
|
size_t nextnewline = findNewline(codepoints, 0);
|
||||||
|
|
||||||
@@ -317,7 +325,7 @@ void TextShaper::getWrap(const ColoredCodepoints& codepoints, float wraplimit, s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextShaper::getWrap(const std::vector<ColoredString>& text, float wraplimit, std::vector<std::string>& lines, std::vector<int>* linewidths)
|
void TextShaper::getWrap(const std::vector<ColoredString> &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *linewidths)
|
||||||
{
|
{
|
||||||
ColoredCodepoints cps;
|
ColoredCodepoints cps;
|
||||||
getCodepointsFromString(text, cps);
|
getCodepointsFromString(text, cps);
|
||||||
@@ -327,7 +335,7 @@ void TextShaper::getWrap(const std::vector<ColoredString>& text, float wraplimit
|
|||||||
|
|
||||||
std::string line;
|
std::string line;
|
||||||
|
|
||||||
for (const auto& range : codepointranges)
|
for (const auto &range : codepointranges)
|
||||||
{
|
{
|
||||||
line.clear();
|
line.clear();
|
||||||
|
|
||||||
@@ -347,9 +355,9 @@ void TextShaper::getWrap(const std::vector<ColoredString>& text, float wraplimit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextShaper::setFallbacks(const std::vector<Rasterizer*>& fallbacks)
|
void TextShaper::setFallbacks(const std::vector<Rasterizer*> &fallbacks)
|
||||||
{
|
{
|
||||||
for (Rasterizer* r : fallbacks)
|
for (Rasterizer *r : fallbacks)
|
||||||
{
|
{
|
||||||
if (r->getDataType() != rasterizers[0]->getDataType())
|
if (r->getDataType() != rasterizers[0]->getDataType())
|
||||||
throw love::Exception("Font fallbacks must be of the same font type.");
|
throw love::Exception("Font fallbacks must be of the same font type.");
|
||||||
@@ -362,7 +370,7 @@ void TextShaper::setFallbacks(const std::vector<Rasterizer*>& fallbacks)
|
|||||||
rasterizers.resize(1);
|
rasterizers.resize(1);
|
||||||
dpiScales.resize(1);
|
dpiScales.resize(1);
|
||||||
|
|
||||||
for (Rasterizer* r : fallbacks)
|
for (Rasterizer *r : fallbacks)
|
||||||
{
|
{
|
||||||
rasterizers.push_back(r);
|
rasterizers.push_back(r);
|
||||||
dpiScales.push_back(r->getDPIScale());
|
dpiScales.push_back(r->getDPIScale());
|
||||||
|
|||||||
@@ -81,11 +81,15 @@ public:
|
|||||||
int height;
|
int height;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// This will be used if the Rasterizer doesn't have a tab character itself.
|
||||||
|
static const int SPACES_PER_TAB = 4;
|
||||||
|
|
||||||
static love::Type type;
|
static love::Type type;
|
||||||
|
|
||||||
virtual ~TextShaper();
|
virtual ~TextShaper();
|
||||||
|
|
||||||
const std::vector<StrongRef<Rasterizer>> &getRasterizers() const { return rasterizers; }
|
const std::vector<StrongRef<Rasterizer>> &getRasterizers() const { return rasterizers; }
|
||||||
|
bool isUsingSpacesForTab() const { return useSpacesForTab; }
|
||||||
|
|
||||||
float getHeight() const;
|
float getHeight() const;
|
||||||
|
|
||||||
@@ -128,6 +132,8 @@ protected:
|
|||||||
|
|
||||||
TextShaper(Rasterizer *rasterizer);
|
TextShaper(Rasterizer *rasterizer);
|
||||||
|
|
||||||
|
static inline bool isWhitespace(uint32 codepoint) { return codepoint == ' ' || codepoint == '\t'; }
|
||||||
|
|
||||||
std::vector<StrongRef<Rasterizer>> rasterizers;
|
std::vector<StrongRef<Rasterizer>> rasterizers;
|
||||||
std::vector<float> dpiScales;
|
std::vector<float> dpiScales;
|
||||||
|
|
||||||
@@ -136,6 +142,8 @@ private:
|
|||||||
int height;
|
int height;
|
||||||
float lineHeight;
|
float lineHeight;
|
||||||
|
|
||||||
|
bool useSpacesForTab;
|
||||||
|
|
||||||
// maps glyphs to advance and glyph+rasterizer index.
|
// maps glyphs to advance and glyph+rasterizer index.
|
||||||
std::unordered_map<uint32, std::pair<int, GlyphIndex>> glyphAdvances;
|
std::unordered_map<uint32, std::pair<int, GlyphIndex>> glyphAdvances;
|
||||||
|
|
||||||
|
|||||||
@@ -36,30 +36,70 @@ namespace freetype
|
|||||||
|
|
||||||
HarfbuzzShaper::HarfbuzzShaper(TrueTypeRasterizer *rasterizer)
|
HarfbuzzShaper::HarfbuzzShaper(TrueTypeRasterizer *rasterizer)
|
||||||
: TextShaper(rasterizer)
|
: TextShaper(rasterizer)
|
||||||
|
, spaceGlyphIndex()
|
||||||
|
, tabSpacesAdvanceX(0)
|
||||||
|
, tabSpacesAdvanceY(0)
|
||||||
{
|
{
|
||||||
hbFonts.push_back(hb_ft_font_create_referenced((FT_Face)rasterizer->getHandle()));
|
hbFonts.push_back(hb_ft_font_create_referenced((FT_Face)rasterizer->getHandle()));
|
||||||
|
hbBuffers.push_back(hb_buffer_create());
|
||||||
|
|
||||||
if (hbFonts[0] == nullptr || hbFonts[0] == hb_font_get_empty())
|
if (hbFonts[0] == nullptr || hbFonts[0] == hb_font_get_empty())
|
||||||
throw love::Exception("");
|
throw love::Exception("Could not create Harfbuzz font object.");
|
||||||
|
|
||||||
|
if (hbBuffers[0] == nullptr || hbBuffers[0] == hb_buffer_get_empty())
|
||||||
|
throw love::Exception("Could not create Harfbuzz buffer object.");
|
||||||
|
|
||||||
|
updateSpacesForTabInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
HarfbuzzShaper::~HarfbuzzShaper()
|
HarfbuzzShaper::~HarfbuzzShaper()
|
||||||
{
|
{
|
||||||
|
for (hb_buffer_t *buffer : hbBuffers)
|
||||||
|
hb_buffer_destroy(buffer);
|
||||||
for (hb_font_t *font : hbFonts)
|
for (hb_font_t *font : hbFonts)
|
||||||
hb_font_destroy(font);
|
hb_font_destroy(font);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HarfbuzzShaper::setFallbacks(const std::vector<Rasterizer*>& fallbacks)
|
void HarfbuzzShaper::setFallbacks(const std::vector<Rasterizer*> &fallbacks)
|
||||||
{
|
{
|
||||||
for (size_t i = 1; i < hbFonts.size(); i++)
|
for (size_t i = 1; i < rasterizers.size(); i++)
|
||||||
|
{
|
||||||
|
hb_buffer_destroy(hbBuffers[i]);
|
||||||
hb_font_destroy(hbFonts[i]);
|
hb_font_destroy(hbFonts[i]);
|
||||||
|
}
|
||||||
|
|
||||||
TextShaper::setFallbacks(fallbacks);
|
TextShaper::setFallbacks(fallbacks);
|
||||||
|
|
||||||
hbFonts.resize(rasterizers.size());
|
hbFonts.resize(rasterizers.size());
|
||||||
|
hbBuffers.resize(rasterizers.size());
|
||||||
|
|
||||||
for (size_t i = 1; i < hbFonts.size(); i++)
|
for (size_t i = 1; i < rasterizers.size(); i++)
|
||||||
|
{
|
||||||
hbFonts[i] = hb_ft_font_create_referenced((FT_Face)rasterizers[i]->getHandle());
|
hbFonts[i] = hb_ft_font_create_referenced((FT_Face)rasterizers[i]->getHandle());
|
||||||
|
hbBuffers[i] = hb_buffer_create();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSpacesForTabInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HarfbuzzShaper::updateSpacesForTabInfo()
|
||||||
|
{
|
||||||
|
if (!isUsingSpacesForTab())
|
||||||
|
return;
|
||||||
|
|
||||||
|
hb_codepoint_t glyphid = 0;
|
||||||
|
for (size_t i = 0; i < hbFonts.size(); i++)
|
||||||
|
{
|
||||||
|
hb_font_t *hbfont = hbFonts[i];
|
||||||
|
if (hb_font_get_glyph(hbfont, ' ', 0, &glyphid))
|
||||||
|
{
|
||||||
|
spaceGlyphIndex.index = glyphid;
|
||||||
|
spaceGlyphIndex.rasterizerIndex = i;
|
||||||
|
tabSpacesAdvanceX = hb_font_get_glyph_h_advance(hbfont, glyphid) * SPACES_PER_TAB;
|
||||||
|
tabSpacesAdvanceY = hb_font_get_glyph_v_advance(hbfont, glyphid) * SPACES_PER_TAB;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector<GlyphPosition> *positions, std::vector<IndexedColor> *colors, TextInfo *info)
|
void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector<GlyphPosition> *positions, std::vector<IndexedColor> *colors, TextInfo *info)
|
||||||
@@ -70,18 +110,21 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints,
|
|||||||
offset.y += getBaseline();
|
offset.y += getBaseline();
|
||||||
Vector2 curpos = offset;
|
Vector2 curpos = offset;
|
||||||
|
|
||||||
hb_buffer_t *hbbuffer = hb_buffer_create();
|
hb_buffer_t *hbbuffer = hbBuffers[0];
|
||||||
|
hb_buffer_reset(hbbuffer);
|
||||||
|
|
||||||
hb_buffer_add_codepoints(hbbuffer, codepoints.cps.data(), codepoints.cps.size(), (unsigned int) range.getOffset(), (int) range.getSize());
|
hb_buffer_add_codepoints(hbbuffer, codepoints.cps.data(), codepoints.cps.size(), (unsigned int) range.getOffset(), (int) range.getSize());
|
||||||
|
|
||||||
// TODO: Expose APIs for direction and script?
|
// TODO: Expose APIs for direction and script?
|
||||||
hb_buffer_guess_segment_properties(hbbuffer);
|
hb_buffer_guess_segment_properties(hbbuffer);
|
||||||
|
|
||||||
|
hb_direction_t direction = hb_buffer_get_direction(hbbuffer);
|
||||||
|
|
||||||
hb_shape(hbFonts[0], hbbuffer, nullptr, 0);
|
hb_shape(hbFonts[0], hbbuffer, nullptr, 0);
|
||||||
|
|
||||||
int glyphcount = (int) hb_buffer_get_length(hbbuffer);
|
int glyphcount = (int) hb_buffer_get_length(hbbuffer);
|
||||||
hb_glyph_info_t* glyphinfos = hb_buffer_get_glyph_infos(hbbuffer, nullptr);
|
hb_glyph_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbbuffer, nullptr);
|
||||||
hb_glyph_position_t* glyphpositions = hb_buffer_get_glyph_positions(hbbuffer, nullptr);
|
hb_glyph_position_t *glyphpositions = hb_buffer_get_glyph_positions(hbbuffer, nullptr);
|
||||||
|
|
||||||
if (positions)
|
if (positions)
|
||||||
positions->reserve(positions->size() + glyphcount);
|
positions->reserve(positions->size() + glyphcount);
|
||||||
@@ -93,7 +136,7 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints,
|
|||||||
for (int i = 0; i < glyphcount; i++)
|
for (int i = 0; i < glyphcount; i++)
|
||||||
{
|
{
|
||||||
bool adding = false;
|
bool adding = false;
|
||||||
if (glyphinfos[i].codepoint == 0)
|
if (glyphinfos[i].codepoint == 0 && (codepoints.cps[glyphinfos[i].cluster] != '\t' || !isUsingSpacesForTab()))
|
||||||
{
|
{
|
||||||
if (fallbackranges.empty() || fallbackranges.back().getMax() != glyphinfos[i-1].cluster)
|
if (fallbackranges.empty() || fallbackranges.back().getMax() != glyphinfos[i-1].cluster)
|
||||||
fallbackranges.push_back(Range(glyphinfos[i].cluster, 1));
|
fallbackranges.push_back(Range(glyphinfos[i].cluster, 1));
|
||||||
@@ -106,9 +149,7 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints,
|
|||||||
{
|
{
|
||||||
for (size_t rasti = 1; rasti < rasterizers.size(); rasti++)
|
for (size_t rasti = 1; rasti < rasterizers.size(); rasti++)
|
||||||
{
|
{
|
||||||
hb_buffer_t* hbbuffer = hb_buffer_create();
|
|
||||||
|
|
||||||
hb_buffer_destroy(hbbuffer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,11 +173,10 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints,
|
|||||||
int maxwidth = (int)curpos.x;
|
int maxwidth = (int)curpos.x;
|
||||||
|
|
||||||
// TODO: fallbacks
|
// TODO: fallbacks
|
||||||
// TODO: use spaces for tab
|
|
||||||
for (int i = 0; i < glyphcount; i++)
|
for (int i = 0; i < glyphcount; i++)
|
||||||
{
|
{
|
||||||
const hb_glyph_info_t& info = glyphinfos[i];
|
const hb_glyph_info_t &info = glyphinfos[i];
|
||||||
const hb_glyph_position_t& glyphpos = glyphpositions[i];
|
hb_glyph_position_t &glyphpos = glyphpositions[i];
|
||||||
|
|
||||||
// TODO: this doesn't handle situations where the user inserted a color
|
// TODO: this doesn't handle situations where the user inserted a color
|
||||||
// change in the middle of some characters that get combined into a single
|
// change in the middle of some characters that get combined into a single
|
||||||
@@ -166,6 +206,21 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints,
|
|||||||
if (clustercodepoint == '\r')
|
if (clustercodepoint == '\r')
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// This is a glyph index at this point, despite the name.
|
||||||
|
GlyphIndex gindex = { info.codepoint, 0 };
|
||||||
|
|
||||||
|
if (clustercodepoint == '\t' && isUsingSpacesForTab())
|
||||||
|
{
|
||||||
|
gindex = spaceGlyphIndex;
|
||||||
|
|
||||||
|
// This should be safe to overwrite.
|
||||||
|
// TODO: RTL support?
|
||||||
|
glyphpos.x_offset = 0;
|
||||||
|
glyphpos.y_offset = 0;
|
||||||
|
glyphpos.x_advance = HB_DIRECTION_IS_HORIZONTAL(direction) ? tabSpacesAdvanceX : 0;
|
||||||
|
glyphpos.y_advance = HB_DIRECTION_IS_VERTICAL(direction) ? tabSpacesAdvanceY : 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (colorToAdd.hasValue && colors && positions)
|
if (colorToAdd.hasValue && colors && positions)
|
||||||
{
|
{
|
||||||
IndexedColor c = {colorToAdd.value, positions->size()};
|
IndexedColor c = {colorToAdd.value, positions->size()};
|
||||||
@@ -175,8 +230,6 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints,
|
|||||||
|
|
||||||
if (positions)
|
if (positions)
|
||||||
{
|
{
|
||||||
// Despite the name this is a glyph index at this point.
|
|
||||||
GlyphIndex gindex = { info.codepoint, 0 };
|
|
||||||
GlyphPosition p = { curpos, gindex };
|
GlyphPosition p = { curpos, gindex };
|
||||||
|
|
||||||
// Harfbuzz position coordinate systems are based on the given font.
|
// Harfbuzz position coordinate systems are based on the given font.
|
||||||
@@ -195,8 +248,6 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints,
|
|||||||
curpos.x = floorf(curpos.x + extraspacing);
|
curpos.x = floorf(curpos.x + extraspacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
hb_buffer_destroy(hbbuffer);
|
|
||||||
|
|
||||||
if (curpos.x > maxwidth)
|
if (curpos.x > maxwidth)
|
||||||
maxwidth = (int)curpos.x;
|
maxwidth = (int)curpos.x;
|
||||||
|
|
||||||
@@ -209,23 +260,26 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints& codepoints, Range range, float wraplimit, float *width)
|
int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width)
|
||||||
{
|
{
|
||||||
if (!range.isValid())
|
if (!range.isValid())
|
||||||
range = Range(0, codepoints.cps.size());
|
range = Range(0, codepoints.cps.size());
|
||||||
|
|
||||||
hb_buffer_t* hbbuffer = hb_buffer_create();
|
hb_buffer_t *hbbuffer = hbBuffers[0];
|
||||||
|
hb_buffer_reset(hbbuffer);
|
||||||
|
|
||||||
hb_buffer_add_codepoints(hbbuffer, codepoints.cps.data(), codepoints.cps.size(), (unsigned int)range.getOffset(), (int)range.getSize());
|
hb_buffer_add_codepoints(hbbuffer, codepoints.cps.data(), codepoints.cps.size(), (unsigned int)range.getOffset(), (int)range.getSize());
|
||||||
|
|
||||||
// TODO: Expose APIs for direction and script?
|
// TODO: Expose APIs for direction and script?
|
||||||
hb_buffer_guess_segment_properties(hbbuffer);
|
hb_buffer_guess_segment_properties(hbbuffer);
|
||||||
|
|
||||||
|
hb_direction_t direction = hb_buffer_get_direction(hbbuffer);
|
||||||
|
|
||||||
hb_shape(hbFonts[0], hbbuffer, nullptr, 0);
|
hb_shape(hbFonts[0], hbbuffer, nullptr, 0);
|
||||||
|
|
||||||
int glyphcount = (int)hb_buffer_get_length(hbbuffer);
|
int glyphcount = (int)hb_buffer_get_length(hbbuffer);
|
||||||
hb_glyph_info_t* glyphinfos = hb_buffer_get_glyph_infos(hbbuffer, nullptr);
|
hb_glyph_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbbuffer, nullptr);
|
||||||
hb_glyph_position_t* glyphpositions = hb_buffer_get_glyph_positions(hbbuffer, nullptr);
|
hb_glyph_position_t *glyphpositions = hb_buffer_get_glyph_positions(hbbuffer, nullptr);
|
||||||
|
|
||||||
float w = 0.0f;
|
float w = 0.0f;
|
||||||
float outwidth = 0.0f;
|
float outwidth = 0.0f;
|
||||||
@@ -237,8 +291,8 @@ int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints& codepoints, Ra
|
|||||||
|
|
||||||
for (int i = 0; i < glyphcount; i++)
|
for (int i = 0; i < glyphcount; i++)
|
||||||
{
|
{
|
||||||
const hb_glyph_info_t& info = glyphinfos[i];
|
const hb_glyph_info_t &info = glyphinfos[i];
|
||||||
const hb_glyph_position_t& glyphpos = glyphpositions[i];
|
hb_glyph_position_t &glyphpos = glyphpositions[i];
|
||||||
|
|
||||||
uint32 clustercodepoint = codepoints.cps[info.cluster];
|
uint32 clustercodepoint = codepoints.cps[info.cluster];
|
||||||
|
|
||||||
@@ -248,10 +302,20 @@ int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints& codepoints, Ra
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (clustercodepoint == '\t' && isUsingSpacesForTab())
|
||||||
|
{
|
||||||
|
// This should be safe to overwrite.
|
||||||
|
// TODO: RTL support?
|
||||||
|
glyphpos.x_offset = 0;
|
||||||
|
glyphpos.y_offset = 0;
|
||||||
|
glyphpos.x_advance = HB_DIRECTION_IS_HORIZONTAL(direction) ? tabSpacesAdvanceX : 0;
|
||||||
|
glyphpos.y_advance = HB_DIRECTION_IS_VERTICAL(direction) ? tabSpacesAdvanceY : 0;
|
||||||
|
}
|
||||||
|
|
||||||
float newwidth = w + floorf((glyphpos.x_advance >> 6) / dpiScales[0] + 0.5f);
|
float newwidth = w + floorf((glyphpos.x_advance >> 6) / dpiScales[0] + 0.5f);
|
||||||
|
|
||||||
// Only wrap when there's a non-space character.
|
// Only wrap when there's a non-space character.
|
||||||
if (newwidth > wraplimit && clustercodepoint != ' ')
|
if (newwidth > wraplimit && !isWhitespace(clustercodepoint))
|
||||||
{
|
{
|
||||||
// Rewind to the last seen space when wrapping.
|
// Rewind to the last seen space when wrapping.
|
||||||
if (lastspaceindex != -1)
|
if (lastspaceindex != -1)
|
||||||
@@ -263,10 +327,10 @@ int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints& codepoints, Ra
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Don't count trailing spaces in the output width.
|
// Don't count trailing spaces in the output width.
|
||||||
if (clustercodepoint == ' ')
|
if (isWhitespace(clustercodepoint))
|
||||||
{
|
{
|
||||||
lastspaceindex = info.cluster;
|
lastspaceindex = info.cluster;
|
||||||
if (prevcodepoint != ' ')
|
if (!isWhitespace(prevcodepoint))
|
||||||
widthbeforelastspace = w;
|
widthbeforelastspace = w;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -277,8 +341,6 @@ int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints& codepoints, Ra
|
|||||||
wrapindex = info.cluster;
|
wrapindex = info.cluster;
|
||||||
}
|
}
|
||||||
|
|
||||||
hb_buffer_destroy(hbbuffer);
|
|
||||||
|
|
||||||
if (width)
|
if (width)
|
||||||
*width = outwidth;
|
*width = outwidth;
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
typedef struct hb_font_t hb_font_t;
|
typedef struct hb_font_t hb_font_t;
|
||||||
|
typedef struct hb_buffer_t hb_buffer_t;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace love
|
namespace love
|
||||||
@@ -50,7 +51,14 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
void updateSpacesForTabInfo();
|
||||||
|
|
||||||
std::vector<hb_font_t *> hbFonts;
|
std::vector<hb_font_t *> hbFonts;
|
||||||
|
std::vector<hb_buffer_t *> hbBuffers;
|
||||||
|
|
||||||
|
GlyphIndex spaceGlyphIndex;
|
||||||
|
int tabSpacesAdvanceX;
|
||||||
|
int tabSpacesAdvanceY;
|
||||||
|
|
||||||
}; // HarfbuzzShaper
|
}; // HarfbuzzShaper
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ Font::Font(love::font::Rasterizer *r, const SamplerState &s)
|
|||||||
, textureHeight(128)
|
, textureHeight(128)
|
||||||
, samplerState()
|
, samplerState()
|
||||||
, dpiScale(r->getDPIScale())
|
, dpiScale(r->getDPIScale())
|
||||||
, useSpacesAsTab(false)
|
|
||||||
, textureCacheID(0)
|
, textureCacheID(0)
|
||||||
{
|
{
|
||||||
samplerState.minFilter = s.minFilter;
|
samplerState.minFilter = s.minFilter;
|
||||||
@@ -92,10 +91,6 @@ Font::Font(love::font::Rasterizer *r, const SamplerState &s)
|
|||||||
if (pixelFormat == PIXELFORMAT_LA8_UNORM && !gfx->isPixelFormatSupported(pixelFormat, PIXELFORMATUSAGEFLAGS_SAMPLE))
|
if (pixelFormat == PIXELFORMAT_LA8_UNORM && !gfx->isPixelFormatSupported(pixelFormat, PIXELFORMATUSAGEFLAGS_SAMPLE))
|
||||||
pixelFormat = PIXELFORMAT_RGBA8_UNORM;
|
pixelFormat = PIXELFORMAT_RGBA8_UNORM;
|
||||||
|
|
||||||
uint32 tab = '\t';
|
|
||||||
if (!r->hasGlyph(tab)) // No tab character in the Rasterizer.
|
|
||||||
useSpacesAsTab = true;
|
|
||||||
|
|
||||||
loadVolatile();
|
loadVolatile();
|
||||||
++fontCount;
|
++fontCount;
|
||||||
}
|
}
|
||||||
@@ -232,24 +227,6 @@ void Font::unloadVolatile()
|
|||||||
love::font::GlyphData *Font::getRasterizerGlyphData(love::font::TextShaper::GlyphIndex glyphindex, float &dpiscale)
|
love::font::GlyphData *Font::getRasterizerGlyphData(love::font::TextShaper::GlyphIndex glyphindex, float &dpiscale)
|
||||||
{
|
{
|
||||||
const auto &r = shaper->getRasterizers()[glyphindex.rasterizerIndex];
|
const auto &r = shaper->getRasterizers()[glyphindex.rasterizerIndex];
|
||||||
|
|
||||||
// Use spaces for the tab 'glyph'. FIXME
|
|
||||||
if (/*glyph == '\t' &&*/ false && useSpacesAsTab)
|
|
||||||
{
|
|
||||||
love::font::GlyphData *spacegd = r->getGlyphData(32);
|
|
||||||
PixelFormat fmt = spacegd->getFormat();
|
|
||||||
|
|
||||||
love::font::GlyphMetrics gm = {};
|
|
||||||
gm.advance = spacegd->getAdvance() * SPACES_PER_TAB;
|
|
||||||
gm.bearingX = spacegd->getBearingX();
|
|
||||||
gm.bearingY = spacegd->getBearingY();
|
|
||||||
|
|
||||||
spacegd->release();
|
|
||||||
|
|
||||||
dpiscale = r->getDPIScale();
|
|
||||||
return new love::font::GlyphData('\t', gm, fmt);
|
|
||||||
}
|
|
||||||
|
|
||||||
dpiscale = r->getDPIScale();
|
dpiscale = r->getDPIScale();
|
||||||
return r->getGlyphDataForIndex(glyphindex.index);
|
return r->getGlyphDataForIndex(glyphindex.index);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,8 +201,6 @@ private:
|
|||||||
int textureX, textureY;
|
int textureX, textureY;
|
||||||
int rowHeight;
|
int rowHeight;
|
||||||
|
|
||||||
bool useSpacesAsTab;
|
|
||||||
|
|
||||||
// ID which is incremented when the texture cache is invalidated.
|
// ID which is incremented when the texture cache is invalidated.
|
||||||
uint32 textureCacheID;
|
uint32 textureCacheID;
|
||||||
|
|
||||||
@@ -211,9 +209,6 @@ private:
|
|||||||
// use, for edge antialiasing.
|
// use, for edge antialiasing.
|
||||||
static const int TEXTURE_PADDING = 2;
|
static const int TEXTURE_PADDING = 2;
|
||||||
|
|
||||||
// This will be used if the Rasterizer doesn't have a tab character itself.
|
|
||||||
static const int SPACES_PER_TAB = 4;
|
|
||||||
|
|
||||||
static StringMap<AlignMode, ALIGN_MAX_ENUM>::Entry alignModeEntries[];
|
static StringMap<AlignMode, ALIGN_MAX_ENUM>::Entry alignModeEntries[];
|
||||||
static StringMap<AlignMode, ALIGN_MAX_ENUM> alignModes;
|
static StringMap<AlignMode, ALIGN_MAX_ENUM> alignModes;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user