mirror of
https://github.com/love2d/love.git
synced 2026-08-16 08:11:02 +02:00
Merge branch 'sdf-font-hintingmode' of https://github.com/Labrium/love into sdf-font-hintingmode
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
|
||||
// LOVE
|
||||
#include "BMFontRasterizer.h"
|
||||
#include "GenericShaper.h"
|
||||
#include "filesystem/Filesystem.h"
|
||||
#include "image/Image.h"
|
||||
|
||||
@@ -147,7 +148,7 @@ BMFontRasterizer::BMFontRasterizer(love::filesystem::FileData *fontdef, const st
|
||||
// The parseConfig function will try to load any missing page images.
|
||||
for (int i = 0; i < (int) imagelist.size(); i++)
|
||||
{
|
||||
if (imagelist[i]->getFormat() != PIXELFORMAT_RGBA8)
|
||||
if (imagelist[i]->getFormat() != PIXELFORMAT_RGBA8_UNORM)
|
||||
throw love::Exception("Only 32-bit RGBA images are supported in BMFonts.");
|
||||
|
||||
images[i] = imagelist[i];
|
||||
@@ -164,6 +165,14 @@ BMFontRasterizer::~BMFontRasterizer()
|
||||
|
||||
void BMFontRasterizer::parseConfig(const std::string &configtext)
|
||||
{
|
||||
{
|
||||
BMFontCharacter nullchar = {};
|
||||
nullchar.page = -1;
|
||||
nullchar.glyph = 0;
|
||||
characters.push_back(nullchar);
|
||||
characterIndices[0] = (int)characters.size() - 1;
|
||||
}
|
||||
|
||||
std::stringstream ss(configtext);
|
||||
std::string line;
|
||||
|
||||
@@ -211,7 +220,7 @@ void BMFontRasterizer::parseConfig(const std::string &configtext)
|
||||
|
||||
ImageData *imagedata = imagemodule->newImageData(data.get());
|
||||
|
||||
if (imagedata->getFormat() != PIXELFORMAT_RGBA8)
|
||||
if (imagedata->getFormat() != PIXELFORMAT_RGBA8_UNORM)
|
||||
{
|
||||
imagedata->release();
|
||||
throw love::Exception("Only 32-bit RGBA images are supported in BMFonts.");
|
||||
@@ -237,7 +246,10 @@ void BMFontRasterizer::parseConfig(const std::string &configtext)
|
||||
c.metrics.bearingY = -cline.getAttributeInt("yoffset");
|
||||
c.metrics.advance = cline.getAttributeInt("xadvance");
|
||||
|
||||
characters[id] = c;
|
||||
c.glyph = id;
|
||||
|
||||
characters.push_back(c);
|
||||
characterIndices[id] = (int) characters.size() - 1;
|
||||
}
|
||||
else if (tag == "kerning")
|
||||
{
|
||||
@@ -257,13 +269,15 @@ void BMFontRasterizer::parseConfig(const std::string &configtext)
|
||||
bool guessheight = lineHeight == 0;
|
||||
|
||||
// Verify the glyph character attributes.
|
||||
for (const auto &cpair : characters)
|
||||
for (const auto &c : characters)
|
||||
{
|
||||
const BMFontCharacter &c = cpair.second;
|
||||
if (c.glyph == 0)
|
||||
continue;
|
||||
|
||||
int width = c.metrics.width;
|
||||
int height = c.metrics.height;
|
||||
|
||||
if (!unicode && cpair.first > 127)
|
||||
if (!unicode && c.glyph > 127)
|
||||
throw love::Exception("Invalid BMFont character id (only unicode and ASCII are supported)");
|
||||
|
||||
if (c.page < 0 || images[c.page].get() == nullptr)
|
||||
@@ -272,13 +286,13 @@ void BMFontRasterizer::parseConfig(const std::string &configtext)
|
||||
const image::ImageData *id = images[c.page].get();
|
||||
|
||||
if (!id->inside(c.x, c.y))
|
||||
throw love::Exception("Invalid coordinates for BMFont character %u.", cpair.first);
|
||||
throw love::Exception("Invalid coordinates for BMFont character %u.", c.glyph);
|
||||
|
||||
if (width > 0 && !id->inside(c.x + width - 1, c.y))
|
||||
throw love::Exception("Invalid width %d for BMFont character %u.", width, cpair.first);
|
||||
throw love::Exception("Invalid width %d for BMFont character %u.", width, c.glyph);
|
||||
|
||||
if (height > 0 && !id->inside(c.x, c.y + height - 1))
|
||||
throw love::Exception("Invalid height %d for BMFont character %u.", height, cpair.first);
|
||||
throw love::Exception("Invalid height %d for BMFont character %u.", height, c.glyph);
|
||||
|
||||
if (guessheight)
|
||||
lineHeight = std::max(lineHeight, c.metrics.height);
|
||||
@@ -292,22 +306,37 @@ int BMFontRasterizer::getLineHeight() const
|
||||
return lineHeight;
|
||||
}
|
||||
|
||||
GlyphData *BMFontRasterizer::getGlyphData(uint32 glyph) const
|
||||
int BMFontRasterizer::getGlyphSpacing(uint32 glyph) const
|
||||
{
|
||||
auto it = characters.find(glyph);
|
||||
auto it = characterIndices.find(glyph);
|
||||
if (it == characterIndices.end())
|
||||
return 0;
|
||||
|
||||
return characters[it->second].metrics.advance;
|
||||
}
|
||||
|
||||
int BMFontRasterizer::getGlyphIndex(uint32 glyph) const
|
||||
{
|
||||
auto it = characterIndices.find(glyph);
|
||||
if (it == characterIndices.end())
|
||||
return 0;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
GlyphData *BMFontRasterizer::getGlyphDataForIndex(int index) const
|
||||
{
|
||||
// Return an empty GlyphData if we don't have the glyph character.
|
||||
if (it == characters.end())
|
||||
return new GlyphData(glyph, GlyphMetrics(), PIXELFORMAT_RGBA8);
|
||||
if (index < 0 || index >= (int) characters.size())
|
||||
return new GlyphData(0, GlyphMetrics(), PIXELFORMAT_RGBA8_UNORM);
|
||||
|
||||
const BMFontCharacter &c = it->second;
|
||||
const BMFontCharacter& c = characters[index];
|
||||
const auto &imagepair = images.find(c.page);
|
||||
|
||||
if (imagepair == images.end())
|
||||
return new GlyphData(glyph, GlyphMetrics(), PIXELFORMAT_RGBA8);
|
||||
return new GlyphData(c.glyph, GlyphMetrics(), PIXELFORMAT_RGBA8_UNORM);
|
||||
|
||||
image::ImageData *imagedata = imagepair->second.get();
|
||||
GlyphData *g = new GlyphData(glyph, c.metrics, PIXELFORMAT_RGBA8);
|
||||
GlyphData *g = new GlyphData(c.glyph, c.metrics, PIXELFORMAT_RGBA8_UNORM);
|
||||
|
||||
size_t pixelsize = imagedata->getPixelSize();
|
||||
|
||||
@@ -333,7 +362,7 @@ int BMFontRasterizer::getGlyphCount() const
|
||||
|
||||
bool BMFontRasterizer::hasGlyph(uint32 glyph) const
|
||||
{
|
||||
return characters.find(glyph) != characters.end();
|
||||
return characterIndices.find(glyph) != characterIndices.end();
|
||||
}
|
||||
|
||||
float BMFontRasterizer::getKerning(uint32 leftglyph, uint32 rightglyph) const
|
||||
@@ -352,6 +381,11 @@ Rasterizer::DataType BMFontRasterizer::getDataType() const
|
||||
return DATA_IMAGE;
|
||||
}
|
||||
|
||||
TextShaper *BMFontRasterizer::newTextShaper()
|
||||
{
|
||||
return new GenericShaper(this);
|
||||
}
|
||||
|
||||
bool BMFontRasterizer::accepts(love::filesystem::FileData *fontdef)
|
||||
{
|
||||
const char *data = (const char *) fontdef->getData();
|
||||
|
||||
@@ -47,11 +47,14 @@ public:
|
||||
|
||||
// Implements Rasterizer.
|
||||
int getLineHeight() const override;
|
||||
GlyphData *getGlyphData(uint32 glyph) const override;
|
||||
int getGlyphSpacing(uint32 glyph) const override;
|
||||
int getGlyphIndex(uint32 glyph) const override;
|
||||
GlyphData *getGlyphDataForIndex(int index) const override;
|
||||
int getGlyphCount() const override;
|
||||
bool hasGlyph(uint32 glyph) const override;
|
||||
float getKerning(uint32 leftglyph, uint32 rightglyph) const override;
|
||||
DataType getDataType() const override;
|
||||
TextShaper *newTextShaper() override;
|
||||
|
||||
static bool accepts(love::filesystem::FileData *fontdef);
|
||||
|
||||
@@ -63,6 +66,7 @@ private:
|
||||
int y;
|
||||
int page;
|
||||
GlyphMetrics metrics;
|
||||
uint32 glyph;
|
||||
};
|
||||
|
||||
void parseConfig(const std::string &config);
|
||||
@@ -72,8 +76,10 @@ private:
|
||||
// Image pages, indexed by their page id.
|
||||
std::unordered_map<int, StrongRef<image::ImageData>> images;
|
||||
|
||||
// Glyph characters, indexed by their glyph id.
|
||||
std::unordered_map<uint32, BMFontCharacter> characters;
|
||||
std::vector<BMFontCharacter> characters;
|
||||
|
||||
// Glyph character indices, indexed by their glyph id.
|
||||
std::unordered_map<uint32, int> characterIndices;
|
||||
|
||||
// Kerning information, indexed by two (packed) characters.
|
||||
std::unordered_map<uint64, int> kerning;
|
||||
|
||||
+13
-12
@@ -22,6 +22,7 @@
|
||||
#include "Font.h"
|
||||
#include "BMFontRasterizer.h"
|
||||
#include "ImageRasterizer.h"
|
||||
#include "data/DataModule.h"
|
||||
|
||||
#include "libraries/utf8/utf8.h"
|
||||
|
||||
@@ -30,28 +31,28 @@ namespace love
|
||||
namespace font
|
||||
{
|
||||
|
||||
// Default TrueType font.
|
||||
#include "Vera.ttf.h"
|
||||
// Default TrueType font, gzip-compressed.
|
||||
#include "NotoSans-Regular.ttf.gzip.h"
|
||||
|
||||
class DefaultFontData : public love::Data
|
||||
Font::Font()
|
||||
{
|
||||
public:
|
||||
auto compressedbytes = (const char *) NotoSans_Regular_ttf_gzip;
|
||||
size_t compressedsize = NotoSans_Regular_ttf_gzip_len;
|
||||
|
||||
Data *clone() const override { return new DefaultFontData(); }
|
||||
void *getData() const override { return Vera_ttf; }
|
||||
size_t getSize() const override { return sizeof(Vera_ttf); }
|
||||
};
|
||||
size_t rawsize = 0;
|
||||
char *fontdata = data::decompress(data::Compressor::FORMAT_GZIP, compressedbytes, compressedsize, rawsize);
|
||||
|
||||
defaultFontData.set(new data::ByteData(fontdata, rawsize, true), Acquire::NORETAIN);
|
||||
}
|
||||
|
||||
Rasterizer *Font::newTrueTypeRasterizer(int size, TrueTypeRasterizer::Hinting hinting)
|
||||
{
|
||||
StrongRef<DefaultFontData> data(new DefaultFontData, Acquire::NORETAIN);
|
||||
return newTrueTypeRasterizer(data.get(), size, hinting);
|
||||
return newTrueTypeRasterizer(defaultFontData.get(), size, hinting);
|
||||
}
|
||||
|
||||
Rasterizer *Font::newTrueTypeRasterizer(int size, float dpiscale, TrueTypeRasterizer::Hinting hinting)
|
||||
{
|
||||
StrongRef<DefaultFontData> data(new DefaultFontData, Acquire::NORETAIN);
|
||||
return newTrueTypeRasterizer(data.get(), size, dpiscale, hinting);
|
||||
return newTrueTypeRasterizer(defaultFontData.get(), size, dpiscale, hinting);
|
||||
}
|
||||
|
||||
Rasterizer *Font::newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &images, float dpiscale)
|
||||
|
||||
@@ -43,6 +43,7 @@ class Font : public Module
|
||||
|
||||
public:
|
||||
|
||||
Font();
|
||||
virtual ~Font() {}
|
||||
|
||||
virtual Rasterizer *newRasterizer(love::filesystem::FileData *data) = 0;
|
||||
@@ -64,6 +65,10 @@ public:
|
||||
virtual ModuleType getModuleType() const { return M_FONT; }
|
||||
virtual const char *getName() const = 0;
|
||||
|
||||
private:
|
||||
|
||||
StrongRef<Data> defaultFontData;
|
||||
|
||||
}; // Font
|
||||
|
||||
} // font
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2023 LOVE Development Team
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
|
||||
// LOVE
|
||||
#include "GenericShaper.h"
|
||||
#include "Rasterizer.h"
|
||||
#include "common/Optional.h"
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace font
|
||||
{
|
||||
|
||||
GenericShaper::GenericShaper(Rasterizer *rasterizer)
|
||||
: TextShaper(rasterizer)
|
||||
{
|
||||
}
|
||||
|
||||
GenericShaper::~GenericShaper()
|
||||
{
|
||||
}
|
||||
|
||||
void GenericShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector<GlyphPosition> *positions, std::vector<IndexedColor> *colors, TextInfo *info)
|
||||
{
|
||||
if (!range.isValid())
|
||||
range = Range(0, codepoints.cps.size());
|
||||
|
||||
if (rasterizers[0]->getDataType() == Rasterizer::DATA_TRUETYPE)
|
||||
offset.y += getBaseline();
|
||||
|
||||
// Spacing counter and newline handling.
|
||||
Vector2 curpos = offset;
|
||||
|
||||
int maxwidth = 0;
|
||||
uint32 prevglyph = 0;
|
||||
|
||||
if (positions)
|
||||
positions->reserve(range.getSize());
|
||||
|
||||
int colorindex = 0;
|
||||
int ncolors = (int) codepoints.colors.size();
|
||||
Optional<Colorf> colorToAdd;
|
||||
|
||||
// Make sure the right color is applied to the start of the glyph list,
|
||||
// when the start isn't 0.
|
||||
if (colors && range.getOffset() > 0 && !codepoints.colors.empty())
|
||||
{
|
||||
for (; colorindex < ncolors; colorindex++)
|
||||
{
|
||||
if (codepoints.colors[colorindex].index >= (int) range.getOffset())
|
||||
break;
|
||||
colorToAdd.set(codepoints.colors[colorindex].color);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = (int) range.getMin(); i <= (int) range.getMax(); i++)
|
||||
{
|
||||
uint32 g = codepoints.cps[i];
|
||||
|
||||
// Do this before anything else so we don't miss colors corresponding
|
||||
// to newlines. The actual add to the list happens after newline
|
||||
// handling, to make sure the resulting index is valid in the positions
|
||||
// array.
|
||||
if (colors && colorindex < ncolors && codepoints.colors[colorindex].index == i)
|
||||
{
|
||||
colorToAdd.set(codepoints.colors[colorindex].color);
|
||||
colorindex++;
|
||||
}
|
||||
|
||||
if (g == '\n')
|
||||
{
|
||||
if (curpos.x > maxwidth)
|
||||
maxwidth = (int)curpos.x;
|
||||
|
||||
// Wrap newline, but do not output a position for it.
|
||||
curpos.y += floorf(getHeight() * getLineHeight() + 0.5f);
|
||||
curpos.x = offset.x;
|
||||
prevglyph = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore carriage returns
|
||||
if (g == '\r')
|
||||
{
|
||||
prevglyph = g;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (colorToAdd.hasValue && colors && positions)
|
||||
{
|
||||
IndexedColor c = {colorToAdd.value, (int) positions->size()};
|
||||
colors->push_back(c);
|
||||
colorToAdd.clear();
|
||||
}
|
||||
|
||||
// Add kerning to the current horizontal offset.
|
||||
curpos.x += getKerning(prevglyph, g);
|
||||
|
||||
GlyphIndex glyphindex;
|
||||
int advance = getGlyphAdvance(g, &glyphindex);
|
||||
|
||||
if (positions)
|
||||
positions->push_back({ Vector2(curpos.x, curpos.y), glyphindex });
|
||||
|
||||
// Advance the x position for the next glyph.
|
||||
curpos.x += advance;
|
||||
|
||||
// Account for extra spacing given to space characters.
|
||||
if (g == ' ' && extraspacing != 0.0f)
|
||||
curpos.x = floorf(curpos.x + extraspacing);
|
||||
|
||||
prevglyph = g;
|
||||
}
|
||||
|
||||
if (curpos.x > maxwidth)
|
||||
maxwidth = (int)curpos.x;
|
||||
|
||||
if (info != nullptr)
|
||||
{
|
||||
info->width = maxwidth - offset.x;
|
||||
info->height = curpos.y - offset.y;
|
||||
if (curpos.x > offset.x)
|
||||
info->height += floorf(getHeight() * getLineHeight() + 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
int GenericShaper::computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width)
|
||||
{
|
||||
if (!range.isValid())
|
||||
range = Range(0, codepoints.cps.size());
|
||||
|
||||
uint32 prevglyph = 0;
|
||||
|
||||
float w = 0.0f;
|
||||
float outwidth = 0.0f;
|
||||
float widthbeforelastspace = 0.0f;
|
||||
int wrapindex = -1;
|
||||
int lastspaceindex = -1;
|
||||
|
||||
for (int i = (int)range.getMin(); i <= (int)range.getMax(); i++)
|
||||
{
|
||||
uint32 g = codepoints.cps[i];
|
||||
|
||||
if (g == '\r')
|
||||
{
|
||||
prevglyph = g;
|
||||
continue;
|
||||
}
|
||||
|
||||
float newwidth = w + getKerning(prevglyph, g) + getGlyphAdvance(g);
|
||||
|
||||
// Only wrap when there's a non-space character.
|
||||
if (newwidth > wraplimit && !isWhitespace(g))
|
||||
{
|
||||
// Rewind to the last seen space when wrapping.
|
||||
if (lastspaceindex != -1)
|
||||
{
|
||||
wrapindex = lastspaceindex;
|
||||
outwidth = widthbeforelastspace;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Don't count trailing spaces in the output width.
|
||||
if (isWhitespace(g))
|
||||
{
|
||||
lastspaceindex = i;
|
||||
if (!isWhitespace(prevglyph))
|
||||
widthbeforelastspace = w;
|
||||
}
|
||||
else
|
||||
outwidth = newwidth;
|
||||
|
||||
w = newwidth;
|
||||
prevglyph = g;
|
||||
wrapindex = i;
|
||||
}
|
||||
|
||||
if (width)
|
||||
*width = outwidth;
|
||||
|
||||
return wrapindex;
|
||||
}
|
||||
|
||||
} // font
|
||||
} // love
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2023 LOVE Development Team
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
// LOVE
|
||||
#include "TextShaper.h"
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace font
|
||||
{
|
||||
|
||||
class GenericShaper : public love::font::TextShaper
|
||||
{
|
||||
public:
|
||||
|
||||
GenericShaper(Rasterizer *rasterizer);
|
||||
virtual ~GenericShaper();
|
||||
|
||||
void computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector<GlyphPosition> *positions, std::vector<IndexedColor> *colors, TextInfo *info) override;
|
||||
int computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) override;
|
||||
|
||||
private:
|
||||
|
||||
}; // GenericShaper
|
||||
|
||||
} // font
|
||||
} // love
|
||||
@@ -24,10 +24,6 @@
|
||||
// UTF-8
|
||||
#include "libraries/utf8/utf8.h"
|
||||
|
||||
// stdlib
|
||||
#include <iostream>
|
||||
#include <cstddef>
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace font
|
||||
@@ -41,7 +37,7 @@ GlyphData::GlyphData(uint32 glyph, GlyphMetrics glyphMetrics, PixelFormat f)
|
||||
, data(nullptr)
|
||||
, format(f)
|
||||
{
|
||||
if (f != PIXELFORMAT_LA8 && f != PIXELFORMAT_RGBA8)
|
||||
if (f != PIXELFORMAT_LA8_UNORM && f != PIXELFORMAT_RGBA8_UNORM)
|
||||
throw love::Exception("Invalid GlyphData pixel format.");
|
||||
|
||||
if (metrics.width > 0 && metrics.height > 0)
|
||||
@@ -78,7 +74,7 @@ void *GlyphData::getData() const
|
||||
|
||||
size_t GlyphData::getPixelSize() const
|
||||
{
|
||||
return getPixelFormatSize(format);
|
||||
return getPixelFormatBlockSize(format);
|
||||
}
|
||||
|
||||
void *GlyphData::getData(int x, int y) const
|
||||
|
||||
@@ -20,8 +20,9 @@
|
||||
|
||||
// LOVE
|
||||
#include "ImageRasterizer.h"
|
||||
|
||||
#include "GenericShaper.h"
|
||||
#include "common/Exception.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
namespace love
|
||||
@@ -31,18 +32,17 @@ namespace font
|
||||
|
||||
static_assert(sizeof(Color32) == 4, "sizeof(Color32) must equal 4 bytes!");
|
||||
|
||||
ImageRasterizer::ImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int numglyphs, int extraspacing, float dpiscale)
|
||||
ImageRasterizer::ImageRasterizer(love::image::ImageData *data, const uint32 *glyphs, int numglyphs, int extraspacing, float dpiscale)
|
||||
: imageData(data)
|
||||
, glyphs(glyphs)
|
||||
, numglyphs(numglyphs)
|
||||
, numglyphs(numglyphs + 1) // Always have a null glyph at the start of the array.
|
||||
, extraSpacing(extraspacing)
|
||||
{
|
||||
this->dpiScale = dpiscale;
|
||||
|
||||
if (data->getFormat() != PIXELFORMAT_RGBA8)
|
||||
if (data->getFormat() != PIXELFORMAT_RGBA8_UNORM)
|
||||
throw love::Exception("Only 32-bit RGBA images are supported in Image Fonts!");
|
||||
|
||||
load();
|
||||
load(glyphs, numglyphs);
|
||||
}
|
||||
|
||||
ImageRasterizer::~ImageRasterizer()
|
||||
@@ -54,21 +54,38 @@ int ImageRasterizer::getLineHeight() const
|
||||
return getHeight();
|
||||
}
|
||||
|
||||
GlyphData *ImageRasterizer::getGlyphData(uint32 glyph) const
|
||||
int ImageRasterizer::getGlyphSpacing(uint32 glyph) const
|
||||
{
|
||||
auto it = glyphIndices.find(glyph);
|
||||
if (it == glyphIndices.end())
|
||||
return 0;
|
||||
return imageGlyphs[it->second].width + extraSpacing;
|
||||
}
|
||||
|
||||
int ImageRasterizer::getGlyphIndex(uint32 glyph) const
|
||||
{
|
||||
auto it = glyphIndices.find(glyph);
|
||||
if (it == glyphIndices.end())
|
||||
return 0;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
GlyphData *ImageRasterizer::getGlyphDataForIndex(int index) const
|
||||
{
|
||||
GlyphMetrics gm = {};
|
||||
uint32 glyph = 0;
|
||||
|
||||
// Set relevant glyph metrics if the glyph is in this ImageFont
|
||||
std::map<uint32, ImageGlyphData>::const_iterator it = imageGlyphs.find(glyph);
|
||||
if (it != imageGlyphs.end())
|
||||
if (index >= 0 && index < (int) imageGlyphs.size())
|
||||
{
|
||||
gm.width = it->second.width;
|
||||
gm.advance = it->second.width + extraSpacing;
|
||||
gm.width = imageGlyphs[index].width;
|
||||
gm.advance = imageGlyphs[index].width + extraSpacing;
|
||||
glyph = imageGlyphs[index].glyph;
|
||||
}
|
||||
|
||||
gm.height = metrics.height;
|
||||
|
||||
GlyphData *g = new GlyphData(glyph, gm, PIXELFORMAT_RGBA8);
|
||||
GlyphData *g = new GlyphData(glyph, gm, PIXELFORMAT_RGBA8_UNORM);
|
||||
|
||||
if (gm.width == 0)
|
||||
return g;
|
||||
@@ -82,7 +99,7 @@ GlyphData *ImageRasterizer::getGlyphData(uint32 glyph) const
|
||||
// copy glyph pixels from imagedata to glyphdata
|
||||
for (int i = 0; i < g->getWidth() * g->getHeight(); i++)
|
||||
{
|
||||
Color32 p = imagepixels[it->second.x + (i % gm.width) + (imageData->getWidth() * (i / gm.width))];
|
||||
Color32 p = imagepixels[imageGlyphs[index].x + (i % gm.width) + (imageData->getWidth() * (i / gm.width))];
|
||||
|
||||
// Use transparency instead of the spacer color
|
||||
if (p == spacer)
|
||||
@@ -94,7 +111,7 @@ GlyphData *ImageRasterizer::getGlyphData(uint32 glyph) const
|
||||
return g;
|
||||
}
|
||||
|
||||
void ImageRasterizer::load()
|
||||
void ImageRasterizer::load(const uint32 *glyphs, int glyphcount)
|
||||
{
|
||||
auto pixels = (const Color32 *) imageData->getData();
|
||||
|
||||
@@ -113,7 +130,16 @@ void ImageRasterizer::load()
|
||||
int start = 0;
|
||||
int end = 0;
|
||||
|
||||
for (int i = 0; i < numglyphs; ++i)
|
||||
{
|
||||
ImageGlyphData nullglyph;
|
||||
nullglyph.x = 0;
|
||||
nullglyph.width = 0;
|
||||
nullglyph.glyph = 0;
|
||||
imageGlyphs.push_back(nullglyph);
|
||||
glyphIndices[0] = (int) imageGlyphs.size() - 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < glyphcount; ++i)
|
||||
{
|
||||
start = end;
|
||||
|
||||
@@ -133,8 +159,10 @@ void ImageRasterizer::load()
|
||||
ImageGlyphData imageGlyph;
|
||||
imageGlyph.x = start;
|
||||
imageGlyph.width = end - start;
|
||||
imageGlyph.glyph = glyphs[i];
|
||||
|
||||
imageGlyphs[glyphs[i]] = imageGlyph;
|
||||
imageGlyphs.push_back(imageGlyph);
|
||||
glyphIndices[glyphs[i]] = (int) imageGlyphs.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +173,7 @@ int ImageRasterizer::getGlyphCount() const
|
||||
|
||||
bool ImageRasterizer::hasGlyph(uint32 glyph) const
|
||||
{
|
||||
return imageGlyphs.find(glyph) != imageGlyphs.end();
|
||||
return glyphIndices.find(glyph) != glyphIndices.end();
|
||||
}
|
||||
|
||||
Rasterizer::DataType ImageRasterizer::getDataType() const
|
||||
@@ -153,5 +181,10 @@ Rasterizer::DataType ImageRasterizer::getDataType() const
|
||||
return DATA_IMAGE;
|
||||
}
|
||||
|
||||
TextShaper *ImageRasterizer::newTextShaper()
|
||||
{
|
||||
return new GenericShaper(this);
|
||||
}
|
||||
|
||||
} // font
|
||||
} // love
|
||||
|
||||
@@ -39,15 +39,18 @@ namespace font
|
||||
class ImageRasterizer : public Rasterizer
|
||||
{
|
||||
public:
|
||||
ImageRasterizer(love::image::ImageData *imageData, uint32 *glyphs, int numglyphs, int extraspacing, float dpiscale);
|
||||
ImageRasterizer(love::image::ImageData *imageData, const uint32 *glyphs, int numglyphs, int extraspacing, float dpiscale);
|
||||
virtual ~ImageRasterizer();
|
||||
|
||||
// Implement Rasterizer
|
||||
int getLineHeight() const override;
|
||||
GlyphData *getGlyphData(uint32 glyph) const override;
|
||||
int getGlyphSpacing(uint32 glyph) const override;
|
||||
int getGlyphIndex(uint32 glyph) const override;
|
||||
GlyphData *getGlyphDataForIndex(int index) const override;
|
||||
int getGlyphCount() const override;
|
||||
bool hasGlyph(uint32 glyph) const override;
|
||||
DataType getDataType() const override;
|
||||
TextShaper *newTextShaper() override;
|
||||
|
||||
|
||||
private:
|
||||
@@ -57,23 +60,23 @@ private:
|
||||
{
|
||||
int x;
|
||||
int width;
|
||||
uint32 glyph;
|
||||
};
|
||||
|
||||
// Load all the glyph positions into memory
|
||||
void load();
|
||||
void load(const uint32 *glyphs, int glyphcount);
|
||||
|
||||
// The image data
|
||||
StrongRef<love::image::ImageData> imageData;
|
||||
|
||||
// The glyphs in the font
|
||||
uint32 *glyphs;
|
||||
|
||||
// Number of glyphs in the font
|
||||
int numglyphs;
|
||||
|
||||
int extraSpacing;
|
||||
|
||||
std::map<uint32, ImageGlyphData> imageGlyphs;
|
||||
|
||||
std::vector<ImageGlyphData> imageGlyphs;
|
||||
std::map<uint32, int> glyphIndices;
|
||||
|
||||
// Color used to identify glyph separation in the source ImageData
|
||||
Color32 spacer;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -55,6 +55,11 @@ int Rasterizer::getDescent() const
|
||||
return metrics.descent;
|
||||
}
|
||||
|
||||
GlyphData *Rasterizer::getGlyphData(uint32 glyph) const
|
||||
{
|
||||
return getGlyphDataForIndex(getGlyphIndex(glyph));
|
||||
}
|
||||
|
||||
GlyphData *Rasterizer::getGlyphData(const std::string &text) const
|
||||
{
|
||||
uint32 codepoint = 0;
|
||||
|
||||
@@ -31,6 +31,8 @@ namespace love
|
||||
namespace font
|
||||
{
|
||||
|
||||
class TextShaper;
|
||||
|
||||
/**
|
||||
* Holds the specific font metrics.
|
||||
**/
|
||||
@@ -84,17 +86,32 @@ public:
|
||||
**/
|
||||
virtual int getLineHeight() const = 0;
|
||||
|
||||
/**
|
||||
* Gets the spacing of the given unicode glyph.
|
||||
**/
|
||||
virtual int getGlyphSpacing(uint32 glyph) const = 0;
|
||||
|
||||
/**
|
||||
* Gets a rasterizer-specific index associated with the given glyph.
|
||||
**/
|
||||
virtual int getGlyphIndex(uint32 glyph) const = 0;
|
||||
|
||||
/**
|
||||
* Gets a specific glyph.
|
||||
* @param glyph The (UNICODE) glyph codepoint to get data for.
|
||||
**/
|
||||
virtual GlyphData *getGlyphData(uint32 glyph) const = 0;
|
||||
GlyphData *getGlyphData(uint32 glyph) const;
|
||||
|
||||
/**
|
||||
* Gets a specific glyph.
|
||||
* @param text The (UNICODE) glyph character to get the data for.
|
||||
**/
|
||||
virtual GlyphData *getGlyphData(const std::string &text) const;
|
||||
GlyphData *getGlyphData(const std::string &text) const;
|
||||
|
||||
/**
|
||||
* Gets a specific glyph for the given rasterizer glyph index.
|
||||
**/
|
||||
virtual GlyphData *getGlyphDataForIndex(int index) const = 0;
|
||||
|
||||
/**
|
||||
* Gets the number of glyphs the rasterizer has data for.
|
||||
@@ -120,6 +137,10 @@ public:
|
||||
|
||||
virtual DataType getDataType() const = 0;
|
||||
|
||||
virtual ptrdiff_t getHandle() const { return 0; }
|
||||
|
||||
virtual TextShaper *newTextShaper() = 0;
|
||||
|
||||
float getDPIScale() const;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2023 LOVE Development Team
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
|
||||
// LOVE
|
||||
#include "TextShaper.h"
|
||||
#include "Rasterizer.h"
|
||||
#include "common/Exception.h"
|
||||
|
||||
#include "libraries/utf8/utf8.h"
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace font
|
||||
{
|
||||
|
||||
void getCodepointsFromString(const std::string &text, std::vector<uint32> &codepoints)
|
||||
{
|
||||
codepoints.reserve(text.size());
|
||||
|
||||
try
|
||||
{
|
||||
utf8::iterator<std::string::const_iterator> i(text.begin(), text.begin(), text.end());
|
||||
utf8::iterator<std::string::const_iterator> end(text.end(), text.begin(), text.end());
|
||||
|
||||
while (i != end)
|
||||
{
|
||||
uint32 g = *i++;
|
||||
codepoints.push_back(g);
|
||||
}
|
||||
}
|
||||
catch (utf8::exception &e)
|
||||
{
|
||||
throw love::Exception("UTF-8 decoding error: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void getCodepointsFromString(const std::vector<ColoredString> &strs, ColoredCodepoints &codepoints)
|
||||
{
|
||||
if (strs.empty())
|
||||
return;
|
||||
|
||||
codepoints.cps.reserve(strs[0].str.size());
|
||||
|
||||
for (const ColoredString &cstr : strs)
|
||||
{
|
||||
// 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.
|
||||
if (cstr.str.size() == 0)
|
||||
continue;
|
||||
|
||||
IndexedColor c = { cstr.color, (int)codepoints.cps.size() };
|
||||
codepoints.colors.push_back(c);
|
||||
|
||||
getCodepointsFromString(cstr.str, codepoints.cps);
|
||||
}
|
||||
|
||||
if (codepoints.colors.size() == 1)
|
||||
{
|
||||
IndexedColor c = codepoints.colors[0];
|
||||
|
||||
if (c.index == 0 && c.color == Colorf(1.0f, 1.0f, 1.0f, 1.0f))
|
||||
codepoints.colors.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
love::Type TextShaper::type("TextShaper", &Object::type);
|
||||
|
||||
TextShaper::TextShaper(Rasterizer *rasterizer)
|
||||
: rasterizers{rasterizer}
|
||||
, dpiScales{rasterizer->getDPIScale()}
|
||||
, height(floorf(rasterizer->getHeight() / rasterizer->getDPIScale() + 0.5f))
|
||||
, lineHeight(1)
|
||||
, useSpacesForTab(false)
|
||||
{
|
||||
if (!rasterizer->hasGlyph('\t'))
|
||||
useSpacesForTab = true;
|
||||
}
|
||||
|
||||
TextShaper::~TextShaper()
|
||||
{
|
||||
}
|
||||
|
||||
float TextShaper::getHeight() const
|
||||
{
|
||||
return height;
|
||||
}
|
||||
|
||||
void TextShaper::setLineHeight(float h)
|
||||
{
|
||||
lineHeight = h;
|
||||
}
|
||||
|
||||
float TextShaper::getLineHeight() const
|
||||
{
|
||||
return lineHeight;
|
||||
}
|
||||
|
||||
int TextShaper::getAscent() const
|
||||
{
|
||||
return floorf(rasterizers[0]->getAscent() / rasterizers[0]->getDPIScale() + 0.5f);
|
||||
}
|
||||
|
||||
int TextShaper::getDescent() const
|
||||
{
|
||||
return floorf(rasterizers[0]->getDescent() / rasterizers[0]->getDPIScale() + 0.5f);
|
||||
}
|
||||
|
||||
float TextShaper::getBaseline() const
|
||||
{
|
||||
float ascent = getAscent();
|
||||
if (ascent != 0.0f)
|
||||
return ascent;
|
||||
else if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE)
|
||||
return floorf(getHeight() / 1.25f + 0.5f); // 1.25 is magic line height for true type fonts
|
||||
else
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
bool TextShaper::hasGlyph(uint32 glyph) const
|
||||
{
|
||||
for (const StrongRef<Rasterizer> &r : rasterizers)
|
||||
{
|
||||
if (r->hasGlyph(glyph))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TextShaper::hasGlyphs(const std::string &text) const
|
||||
{
|
||||
if (text.size() == 0)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
utf8::iterator<std::string::const_iterator> i(text.begin(), text.begin(), text.end());
|
||||
utf8::iterator<std::string::const_iterator> end(text.end(), text.begin(), text.end());
|
||||
|
||||
while (i != end)
|
||||
{
|
||||
uint32 codepoint = *i++;
|
||||
|
||||
if (!hasGlyph(codepoint))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (utf8::exception &e)
|
||||
{
|
||||
throw love::Exception("UTF-8 decoding error: %s", e.what());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
float TextShaper::getKerning(uint32 leftglyph, uint32 rightglyph)
|
||||
{
|
||||
uint64 packedglyphs = ((uint64)leftglyph << 32) | (uint64)rightglyph;
|
||||
|
||||
const auto it = kerning.find(packedglyphs);
|
||||
if (it != kerning.end())
|
||||
return it->second;
|
||||
|
||||
float k = 0.0f;
|
||||
bool found = false;
|
||||
|
||||
for (const auto &r : rasterizers)
|
||||
{
|
||||
if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph))
|
||||
{
|
||||
found = true;
|
||||
k = floorf(r->getKerning(leftglyph, rightglyph) / r->getDPIScale() + 0.5f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
k = floorf(rasterizers[0]->getKerning(leftglyph, rightglyph) / rasterizers[0]->getDPIScale() + 0.5f);
|
||||
|
||||
kerning[packedglyphs] = k;
|
||||
return k;
|
||||
}
|
||||
|
||||
float TextShaper::getKerning(const std::string &leftchar, const std::string &rightchar)
|
||||
{
|
||||
uint32 left = 0;
|
||||
uint32 right = 0;
|
||||
|
||||
try
|
||||
{
|
||||
left = utf8::peek_next(leftchar.begin(), leftchar.end());
|
||||
right = utf8::peek_next(rightchar.begin(), rightchar.end());
|
||||
}
|
||||
catch (utf8::exception &e)
|
||||
{
|
||||
throw love::Exception("UTF-8 decoding error: %s", e.what());
|
||||
}
|
||||
|
||||
return getKerning(left, right);
|
||||
}
|
||||
|
||||
int TextShaper::getGlyphAdvance(uint32 glyph, GlyphIndex *glyphindex)
|
||||
{
|
||||
const auto it = glyphAdvances.find(glyph);
|
||||
if (it != glyphAdvances.end())
|
||||
{
|
||||
if (glyphindex)
|
||||
*glyphindex = it->second.second;
|
||||
return it->second.first;
|
||||
}
|
||||
|
||||
int rasterizeri = 0;
|
||||
uint32 realglyph = glyph;
|
||||
|
||||
if (glyph == '\t' && isUsingSpacesForTab())
|
||||
realglyph = ' ';
|
||||
|
||||
for (size_t i = 0; i < rasterizers.size(); i++)
|
||||
{
|
||||
if (rasterizers[i]->hasGlyph(realglyph))
|
||||
{
|
||||
rasterizeri = (int) i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto &r = rasterizers[rasterizeri];
|
||||
int advance = floorf(r->getGlyphSpacing(realglyph) / r->getDPIScale() + 0.5f);
|
||||
|
||||
if (glyph == '\t' && realglyph == ' ')
|
||||
advance *= SPACES_PER_TAB;
|
||||
|
||||
GlyphIndex glyphi = {r->getGlyphIndex(realglyph), rasterizeri};
|
||||
|
||||
glyphAdvances[glyph] = std::make_pair(advance, glyphi);
|
||||
if (glyphindex)
|
||||
*glyphindex = glyphi;
|
||||
return advance;
|
||||
}
|
||||
|
||||
int TextShaper::getWidth(const std::string &str)
|
||||
{
|
||||
if (str.size() == 0) return 0;
|
||||
|
||||
ColoredCodepoints codepoints;
|
||||
getCodepointsFromString(str, codepoints.cps);
|
||||
|
||||
TextInfo info;
|
||||
computeGlyphPositions(codepoints, Range(), Vector2(0.0f, 0.0f), 0.0f, nullptr, nullptr, &info);
|
||||
|
||||
return info.width;
|
||||
}
|
||||
|
||||
static size_t findNewline(const ColoredCodepoints &codepoints, size_t start)
|
||||
{
|
||||
for (size_t i = start; i < codepoints.cps.size(); i++)
|
||||
{
|
||||
if (codepoints.cps[i] == '\n')
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return codepoints.cps.size();
|
||||
}
|
||||
|
||||
void TextShaper::getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector<Range> &lineranges, std::vector<int> *linewidths)
|
||||
{
|
||||
size_t nextnewline = findNewline(codepoints, 0);
|
||||
|
||||
for (size_t i = 0; i < codepoints.cps.size();)
|
||||
{
|
||||
if (nextnewline < i)
|
||||
nextnewline = findNewline(codepoints, i);
|
||||
|
||||
if (nextnewline == i) // Empty line.
|
||||
{
|
||||
lineranges.push_back(Range());
|
||||
if (linewidths)
|
||||
linewidths->push_back(0);
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
Range r(i, nextnewline - i);
|
||||
float width = 0.0f;
|
||||
int wrapindex = computeWordWrapIndex(codepoints, r, wraplimit, &width);
|
||||
|
||||
if (wrapindex >= (int) i)
|
||||
{
|
||||
r = Range(i, (size_t) wrapindex + 1 - i);
|
||||
i = (size_t)wrapindex + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
r = Range();
|
||||
i++;
|
||||
}
|
||||
|
||||
// We've already handled this line, skip the newline character.
|
||||
if (nextnewline == i)
|
||||
i++;
|
||||
|
||||
lineranges.push_back(r);
|
||||
if (linewidths)
|
||||
linewidths->push_back(width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextShaper::getWrap(const std::vector<ColoredString> &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *linewidths)
|
||||
{
|
||||
ColoredCodepoints cps;
|
||||
getCodepointsFromString(text, cps);
|
||||
|
||||
std::vector<Range> codepointranges;
|
||||
getWrap(cps, wraplimit, codepointranges, linewidths);
|
||||
|
||||
std::string line;
|
||||
|
||||
for (const auto &range : codepointranges)
|
||||
{
|
||||
line.clear();
|
||||
|
||||
if (range.isValid())
|
||||
{
|
||||
line.reserve(range.getSize());
|
||||
|
||||
for (size_t i = range.getMin(); i <= range.getMax(); i++)
|
||||
{
|
||||
char character[5] = { '\0' };
|
||||
char *end = utf8::unchecked::append(cps.cps[i], character);
|
||||
line.append(character, end - character);
|
||||
}
|
||||
}
|
||||
|
||||
lines.push_back(line);
|
||||
}
|
||||
}
|
||||
|
||||
void TextShaper::setFallbacks(const std::vector<Rasterizer*> &fallbacks)
|
||||
{
|
||||
for (Rasterizer *r : fallbacks)
|
||||
{
|
||||
if (r->getDataType() != rasterizers[0]->getDataType())
|
||||
throw love::Exception("Font fallbacks must be of the same font type.");
|
||||
}
|
||||
|
||||
// Clear caches.
|
||||
kerning.clear();
|
||||
glyphAdvances.clear();
|
||||
|
||||
rasterizers.resize(1);
|
||||
dpiScales.resize(1);
|
||||
|
||||
for (Rasterizer *r : fallbacks)
|
||||
{
|
||||
rasterizers.push_back(r);
|
||||
dpiScales.push_back(r->getDPIScale());
|
||||
}
|
||||
}
|
||||
|
||||
} // font
|
||||
} // love
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2023 LOVE Development Team
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
// LOVE
|
||||
#include "common/Object.h"
|
||||
#include "common/Vector.h"
|
||||
#include "common/int.h"
|
||||
#include "common/Color.h"
|
||||
#include "common/Range.h"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace font
|
||||
{
|
||||
|
||||
class Rasterizer;
|
||||
|
||||
struct ColoredString
|
||||
{
|
||||
std::string str;
|
||||
Colorf color;
|
||||
};
|
||||
|
||||
struct IndexedColor
|
||||
{
|
||||
Colorf color;
|
||||
int index;
|
||||
};
|
||||
|
||||
struct ColoredCodepoints
|
||||
{
|
||||
std::vector<uint32> cps;
|
||||
std::vector<IndexedColor> colors;
|
||||
};
|
||||
|
||||
void getCodepointsFromString(const std::string &str, std::vector<uint32> &codepoints);
|
||||
void getCodepointsFromString(const std::vector<ColoredString> &strs, ColoredCodepoints &codepoints);
|
||||
|
||||
class TextShaper : public Object
|
||||
{
|
||||
public:
|
||||
|
||||
struct GlyphIndex
|
||||
{
|
||||
int index;
|
||||
int rasterizerIndex;
|
||||
};
|
||||
|
||||
struct GlyphPosition
|
||||
{
|
||||
Vector2 position;
|
||||
GlyphIndex glyphIndex;
|
||||
};
|
||||
|
||||
struct TextInfo
|
||||
{
|
||||
int width;
|
||||
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;
|
||||
|
||||
virtual ~TextShaper();
|
||||
|
||||
const std::vector<StrongRef<Rasterizer>> &getRasterizers() const { return rasterizers; }
|
||||
bool isUsingSpacesForTab() const { return useSpacesForTab; }
|
||||
|
||||
float getHeight() const;
|
||||
|
||||
/**
|
||||
* Sets the line height (which should be a number to multiply the font size by,
|
||||
* example: line height = 1.2 and size = 12 means that rendered line height = 12*1.2)
|
||||
* @param height The new line height.
|
||||
**/
|
||||
void setLineHeight(float height);
|
||||
|
||||
/**
|
||||
* Returns the line height.
|
||||
**/
|
||||
float getLineHeight() const;
|
||||
|
||||
// Extra font metrics
|
||||
int getAscent() const;
|
||||
int getDescent() const;
|
||||
float getBaseline() const;
|
||||
|
||||
bool hasGlyph(uint32 glyph) const;
|
||||
bool hasGlyphs(const std::string &text) const;
|
||||
|
||||
float getKerning(uint32 leftglyph, uint32 rightglyph);
|
||||
float getKerning(const std::string &leftchar, const std::string &rightchar);
|
||||
|
||||
int getGlyphAdvance(uint32 glyph, GlyphIndex *glyphindex = nullptr);
|
||||
|
||||
int getWidth(const std::string &str);
|
||||
|
||||
void getWrap(const std::vector<ColoredString> &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *linewidths = nullptr);
|
||||
void getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector<Range> &lineranges, std::vector<int> *linewidths = nullptr);
|
||||
|
||||
virtual void setFallbacks(const std::vector<Rasterizer *> &fallbacks);
|
||||
|
||||
virtual void computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector<GlyphPosition> *positions, std::vector<IndexedColor> *colors, TextInfo *info) = 0;
|
||||
virtual int computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) = 0;
|
||||
|
||||
protected:
|
||||
|
||||
TextShaper(Rasterizer *rasterizer);
|
||||
|
||||
static inline bool isWhitespace(uint32 codepoint) { return codepoint == ' ' || codepoint == '\t'; }
|
||||
|
||||
std::vector<StrongRef<Rasterizer>> rasterizers;
|
||||
std::vector<float> dpiScales;
|
||||
|
||||
private:
|
||||
|
||||
int height;
|
||||
float lineHeight;
|
||||
|
||||
bool useSpacesForTab;
|
||||
|
||||
// maps glyphs to advance and glyph+rasterizer index.
|
||||
std::unordered_map<uint32, std::pair<int, GlyphIndex>> glyphAdvances;
|
||||
|
||||
// map of left/right glyph pairs to horizontal kerning.
|
||||
std::unordered_map<uint64, float> kerning;
|
||||
|
||||
}; // TextShaper
|
||||
|
||||
} // font
|
||||
} // love
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,415 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2023 LOVE Development Team
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
|
||||
// LOVE
|
||||
#include "HarfbuzzShaper.h"
|
||||
#include "TrueTypeRasterizer.h"
|
||||
#include "common/Optional.h"
|
||||
|
||||
// harfbuzz
|
||||
#include <hb.h>
|
||||
#include <hb-ft.h>
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace font
|
||||
{
|
||||
namespace freetype
|
||||
{
|
||||
|
||||
HarfbuzzShaper::HarfbuzzShaper(TrueTypeRasterizer *rasterizer)
|
||||
: TextShaper(rasterizer)
|
||||
, spaceGlyphIndex()
|
||||
, tabSpacesAdvanceX(0)
|
||||
, tabSpacesAdvanceY(0)
|
||||
{
|
||||
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())
|
||||
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()
|
||||
{
|
||||
for (hb_buffer_t *buffer : hbBuffers)
|
||||
hb_buffer_destroy(buffer);
|
||||
for (hb_font_t *font : hbFonts)
|
||||
hb_font_destroy(font);
|
||||
}
|
||||
|
||||
void HarfbuzzShaper::setFallbacks(const std::vector<Rasterizer*> &fallbacks)
|
||||
{
|
||||
for (size_t i = 1; i < rasterizers.size(); i++)
|
||||
{
|
||||
hb_buffer_destroy(hbBuffers[i]);
|
||||
hb_font_destroy(hbFonts[i]);
|
||||
}
|
||||
|
||||
TextShaper::setFallbacks(fallbacks);
|
||||
|
||||
hbFonts.resize(rasterizers.size());
|
||||
hbBuffers.resize(rasterizers.size());
|
||||
|
||||
for (size_t i = 1; i < rasterizers.size(); i++)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HarfbuzzShaper::isValidGlyph(uint32 glyphindex, const std::vector<uint32> &codepoints, uint32 codepointindex)
|
||||
{
|
||||
if (glyphindex != 0)
|
||||
return true;
|
||||
|
||||
uint32 codepoint = codepoints[codepointindex];
|
||||
if (codepoint == '\n' || codepoint == '\r' || (codepoint == '\t' && isUsingSpacesForTab()))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HarfbuzzShaper::computeBufferRanges(const ColoredCodepoints &codepoints, Range range, std::vector<BufferRange> &bufferranges)
|
||||
{
|
||||
bufferranges.clear();
|
||||
|
||||
if (codepoints.cps.size() == 0)
|
||||
return;
|
||||
|
||||
// Less computation for the typical case (no fallback fonts).
|
||||
if (rasterizers.size() == 1)
|
||||
{
|
||||
hb_buffer_reset(hbBuffers[0]);
|
||||
hb_buffer_add_codepoints(hbBuffers[0], codepoints.cps.data(), codepoints.cps.size(), (unsigned int)range.getOffset(), (int)range.getSize());
|
||||
|
||||
// TODO: Expose APIs for direction and script?
|
||||
hb_buffer_guess_segment_properties(hbBuffers[0]);
|
||||
|
||||
hb_shape(hbFonts[0], hbBuffers[0], nullptr, 0);
|
||||
|
||||
bufferranges.push_back({0, (int) range.first, Range(0, hb_buffer_get_length(hbBuffers[0]))});
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Range> fallbackranges = { range };
|
||||
|
||||
// For each font, figure out the ranges of valid glyphs in the given string,
|
||||
// and add the rest to a list to be shaped by the next fallback font.
|
||||
// Harfbuzz doesn't have its own fallback API.
|
||||
for (size_t rasti = 0; rasti < rasterizers.size(); rasti++)
|
||||
{
|
||||
hb_buffer_t *hbb = hbBuffers[rasti];
|
||||
hb_buffer_reset(hbb);
|
||||
|
||||
for (Range r : fallbackranges)
|
||||
hb_buffer_add_codepoints(hbb, codepoints.cps.data(), codepoints.cps.size(), (unsigned int)r.getOffset(), (int)r.getSize());
|
||||
|
||||
hb_buffer_guess_segment_properties(hbb);
|
||||
|
||||
hb_shape(hbFonts[rasti], hbb, nullptr, 0);
|
||||
|
||||
int glyphcount = (int)hb_buffer_get_length(hbb);
|
||||
const hb_glyph_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbb, nullptr);
|
||||
hb_direction_t direction = hb_buffer_get_direction(hbb);
|
||||
|
||||
fallbackranges.clear();
|
||||
|
||||
for (int i = 0; i < glyphcount; i++)
|
||||
{
|
||||
if (isValidGlyph(glyphinfos[i].codepoint, codepoints.cps, glyphinfos[i].cluster))
|
||||
{
|
||||
if (bufferranges.empty() || bufferranges.back().index != rasti || bufferranges.back().range.getMax() + 1 != i)
|
||||
bufferranges.push_back({(int)rasti, (int)glyphinfos[i].cluster, Range(i, 1)});
|
||||
else
|
||||
bufferranges.back().range.last++;
|
||||
}
|
||||
else if (rasti == rasterizers.size() - 1)
|
||||
{
|
||||
// Use the first font for remaining invalid glyphs when no
|
||||
// fallback font supports them.
|
||||
if (bufferranges.empty() || bufferranges.back().index != 0 || bufferranges.back().range.getMax() + 1 != i)
|
||||
bufferranges.push_back({0, (int)glyphinfos[i].cluster, Range(i, 1)});
|
||||
else
|
||||
bufferranges.back().range.last++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Harfbuzz puts RTL text into the buffer in reverse order, so
|
||||
// it'll start with the last cluster (character index).
|
||||
if (fallbackranges.empty() || (direction == HB_DIRECTION_RTL ? fallbackranges.back().getMin() : fallbackranges.back().getMax()) != glyphinfos[i - 1].cluster)
|
||||
fallbackranges.push_back(Range(glyphinfos[i].cluster, 1));
|
||||
else
|
||||
fallbackranges.back().encapsulate(glyphinfos[i].cluster);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(bufferranges.begin(), bufferranges.end(), [](const BufferRange &a, const BufferRange &b)
|
||||
{
|
||||
if (a.codepointStart != b.codepointStart)
|
||||
return a.codepointStart < b.codepointStart;
|
||||
if (a.index != b.index)
|
||||
return a.index < b.index;
|
||||
return a.range.first < b.range.first;
|
||||
});
|
||||
}
|
||||
|
||||
void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector<GlyphPosition> *positions, std::vector<IndexedColor> *colors, TextInfo *info)
|
||||
{
|
||||
if (!range.isValid() && !codepoints.cps.empty())
|
||||
range = Range(0, codepoints.cps.size());
|
||||
|
||||
offset.y += getBaseline();
|
||||
Vector2 curpos = offset;
|
||||
|
||||
int colorindex = 0;
|
||||
int ncolors = (int)codepoints.colors.size();
|
||||
Optional<Colorf> colorToAdd;
|
||||
|
||||
// Make sure the right color is applied to the start of the glyph list,
|
||||
// when the start isn't 0.
|
||||
if (colors && range.getOffset() > 0 && !codepoints.colors.empty())
|
||||
{
|
||||
for (; colorindex < ncolors; colorindex++)
|
||||
{
|
||||
if (codepoints.colors[colorindex].index >= (int) range.getOffset())
|
||||
break;
|
||||
colorToAdd.set(codepoints.colors[colorindex].color);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<BufferRange> bufferranges;
|
||||
computeBufferRanges(codepoints, range, bufferranges);
|
||||
|
||||
int maxwidth = (int)curpos.x;
|
||||
|
||||
for (const auto &bufferrange : bufferranges)
|
||||
{
|
||||
if (positions)
|
||||
positions->reserve(positions->size() + bufferrange.range.getSize());
|
||||
|
||||
hb_buffer_t *hbbuffer = hbBuffers[bufferrange.index];
|
||||
|
||||
const 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_direction_t direction = hb_buffer_get_direction(hbbuffer);
|
||||
|
||||
for (size_t i = bufferrange.range.first; i <= bufferrange.range.last; i++)
|
||||
{
|
||||
const hb_glyph_info_t &info = glyphinfos[i];
|
||||
hb_glyph_position_t &glyphpos = glyphpositions[i];
|
||||
|
||||
// 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
|
||||
// cluster.
|
||||
if (colors && colorindex < ncolors && codepoints.colors[colorindex].index == info.cluster)
|
||||
{
|
||||
colorToAdd.set(codepoints.colors[colorindex].color);
|
||||
colorindex++;
|
||||
}
|
||||
|
||||
uint32 clustercodepoint = codepoints.cps[info.cluster];
|
||||
|
||||
// Harfbuzz doesn't handle newlines itself, but it does leave them in
|
||||
// the glyph list so we can do it manually.
|
||||
if (clustercodepoint == '\n')
|
||||
{
|
||||
if (curpos.x > maxwidth)
|
||||
maxwidth = (int)curpos.x;
|
||||
|
||||
// Wrap newline, but do not output a position for it.
|
||||
curpos.y += floorf(getHeight() * getLineHeight() + 0.5f);
|
||||
curpos.x = offset.x;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore carriage returns
|
||||
if (clustercodepoint == '\r')
|
||||
continue;
|
||||
|
||||
// This is a glyph index at this point, despite the name.
|
||||
GlyphIndex gindex = { (int) info.codepoint, bufferrange.index };
|
||||
|
||||
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)
|
||||
{
|
||||
IndexedColor c = {colorToAdd.value, (int) positions->size()};
|
||||
colors->push_back(c);
|
||||
colorToAdd.clear();
|
||||
}
|
||||
|
||||
if (positions)
|
||||
{
|
||||
GlyphPosition p = { curpos, gindex };
|
||||
|
||||
// Harfbuzz position coordinate systems are based on the given font.
|
||||
// Freetype uses 26.6 fixed point coordinates, so harfbuzz does too.
|
||||
p.position.x += floorf((glyphpos.x_offset >> 6) / dpiScales[0] + 0.5f);
|
||||
p.position.y += floorf((glyphpos.y_offset >> 6) / dpiScales[0] + 0.5f);
|
||||
|
||||
positions->push_back(p);
|
||||
}
|
||||
|
||||
curpos.x += floorf((glyphpos.x_advance >> 6) / dpiScales[0] + 0.5f);
|
||||
curpos.y += floorf((glyphpos.y_advance >> 6) / dpiScales[0] + 0.5f);
|
||||
|
||||
// Account for extra spacing given to space characters.
|
||||
if (clustercodepoint == ' ' && extraspacing != 0.0f)
|
||||
curpos.x = floorf(curpos.x + extraspacing);
|
||||
}
|
||||
}
|
||||
|
||||
if (curpos.x > maxwidth)
|
||||
maxwidth = (int)curpos.x;
|
||||
|
||||
if (info != nullptr)
|
||||
{
|
||||
info->width = maxwidth - offset.x;
|
||||
info->height = curpos.y - offset.y;
|
||||
if (curpos.x > offset.x)
|
||||
info->height += floorf(getHeight() * getLineHeight() + 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width)
|
||||
{
|
||||
if (!range.isValid())
|
||||
range = Range(0, codepoints.cps.size());
|
||||
|
||||
float w = 0.0f;
|
||||
float outwidth = 0.0f;
|
||||
float widthbeforelastspace = 0.0f;
|
||||
int wrapindex = -1;
|
||||
int lastspaceindex = -1;
|
||||
|
||||
uint32 prevcodepoint = 0;
|
||||
|
||||
std::vector<BufferRange> bufferranges;
|
||||
computeBufferRanges(codepoints, range, bufferranges);
|
||||
|
||||
for (const auto &bufferrange : bufferranges)
|
||||
{
|
||||
hb_buffer_t *hbbuffer = hbBuffers[bufferrange.index];
|
||||
|
||||
const 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_direction_t direction = hb_buffer_get_direction(hbbuffer);
|
||||
|
||||
for (size_t i = bufferrange.range.first; i <= bufferrange.range.last; i++)
|
||||
{
|
||||
const hb_glyph_info_t &info = glyphinfos[i];
|
||||
hb_glyph_position_t &glyphpos = glyphpositions[i];
|
||||
|
||||
uint32 clustercodepoint = codepoints.cps[info.cluster];
|
||||
|
||||
if (clustercodepoint == '\r')
|
||||
{
|
||||
prevcodepoint = clustercodepoint;
|
||||
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);
|
||||
|
||||
// Only wrap when there's a non-space character.
|
||||
if (newwidth > wraplimit && !isWhitespace(clustercodepoint))
|
||||
{
|
||||
// Rewind to the last seen space when wrapping.
|
||||
if (lastspaceindex != -1)
|
||||
{
|
||||
wrapindex = lastspaceindex;
|
||||
outwidth = widthbeforelastspace;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Don't count trailing spaces in the output width.
|
||||
if (isWhitespace(clustercodepoint))
|
||||
{
|
||||
lastspaceindex = info.cluster;
|
||||
if (!isWhitespace(prevcodepoint))
|
||||
widthbeforelastspace = w;
|
||||
}
|
||||
else
|
||||
outwidth = newwidth;
|
||||
|
||||
w = newwidth;
|
||||
prevcodepoint = clustercodepoint;
|
||||
wrapindex = info.cluster;
|
||||
}
|
||||
}
|
||||
|
||||
if (width)
|
||||
*width = outwidth;
|
||||
|
||||
return wrapindex;
|
||||
}
|
||||
|
||||
} // freetype
|
||||
} // font
|
||||
} // love
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2023 LOVE Development Team
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
// LOVE
|
||||
#include "font/TextShaper.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
typedef struct hb_font_t hb_font_t;
|
||||
typedef struct hb_buffer_t hb_buffer_t;
|
||||
}
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace font
|
||||
{
|
||||
namespace freetype
|
||||
{
|
||||
|
||||
class TrueTypeRasterizer;
|
||||
|
||||
class HarfbuzzShaper : public love::font::TextShaper
|
||||
{
|
||||
public:
|
||||
|
||||
HarfbuzzShaper(TrueTypeRasterizer *rasterizer);
|
||||
virtual ~HarfbuzzShaper();
|
||||
|
||||
void setFallbacks(const std::vector<Rasterizer *> &fallbacks) override;
|
||||
void computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector<GlyphPosition> *positions, std::vector<IndexedColor> *colors, TextInfo *info) override;
|
||||
int computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) override;
|
||||
|
||||
private:
|
||||
|
||||
struct BufferRange
|
||||
{
|
||||
int index;
|
||||
int codepointStart;
|
||||
Range range;
|
||||
};
|
||||
|
||||
void updateSpacesForTabInfo();
|
||||
bool isValidGlyph(uint32 glyphindex, const std::vector<uint32> &codepoints, uint32 codepointindex);
|
||||
void computeBufferRanges(const ColoredCodepoints &codepoints, Range range, std::vector<BufferRange> &bufferranges);
|
||||
|
||||
std::vector<hb_font_t *> hbFonts;
|
||||
std::vector<hb_buffer_t *> hbBuffers;
|
||||
|
||||
GlyphIndex spaceGlyphIndex;
|
||||
int tabSpacesAdvanceX;
|
||||
int tabSpacesAdvanceY;
|
||||
|
||||
}; // HarfbuzzShaper
|
||||
|
||||
} // freetype
|
||||
} // font
|
||||
} // love
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
// LOVE
|
||||
#include "TrueTypeRasterizer.h"
|
||||
#include "HarfbuzzShaper.h"
|
||||
#include "common/Exception.h"
|
||||
|
||||
// C
|
||||
@@ -75,7 +76,30 @@ int TrueTypeRasterizer::getLineHeight() const
|
||||
return (int)(getHeight() * 1.25);
|
||||
}
|
||||
|
||||
GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const
|
||||
int TrueTypeRasterizer::getGlyphSpacing(uint32 glyph) const
|
||||
{
|
||||
FT_Glyph ftglyph;
|
||||
FT_Error err = FT_Err_Ok;
|
||||
FT_UInt loadoption = hintingToLoadOption(hinting);
|
||||
|
||||
// Initialize
|
||||
err = FT_Load_Glyph(face, FT_Get_Char_Index(face, glyph), FT_LOAD_DEFAULT | loadoption);
|
||||
if (err != FT_Err_Ok)
|
||||
return 0;
|
||||
|
||||
err = FT_Get_Glyph(face->glyph, &ftglyph);
|
||||
if (err != FT_Err_Ok)
|
||||
return 0;
|
||||
|
||||
return (int)(ftglyph->advance.x >> 16);
|
||||
}
|
||||
|
||||
int TrueTypeRasterizer::getGlyphIndex(uint32 glyph) const
|
||||
{
|
||||
return FT_Get_Char_Index(face, glyph);
|
||||
}
|
||||
|
||||
GlyphData *TrueTypeRasterizer::getGlyphDataForIndex(int index) const
|
||||
{
|
||||
love::font::GlyphMetrics glyphMetrics = {};
|
||||
FT_Glyph ftglyph;
|
||||
@@ -84,7 +108,7 @@ GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const
|
||||
FT_UInt loadoption = hintingToLoadOption(hinting);
|
||||
|
||||
// Initialize
|
||||
err = FT_Load_Glyph(face, FT_Get_Char_Index(face, glyph), FT_LOAD_DEFAULT | loadoption);
|
||||
err = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT | loadoption);
|
||||
|
||||
if (err != FT_Err_Ok)
|
||||
throw love::Exception("TrueType Font glyph error: FT_Load_Glyph failed (0x%x)", err);
|
||||
@@ -115,7 +139,8 @@ GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const
|
||||
}
|
||||
}
|
||||
FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph) ftglyph;
|
||||
FT_Bitmap &bitmap = bitmap_glyph->bitmap;//just to make things easier
|
||||
const FT_Bitmap &bitmap = bitmap_glyph->bitmap; //just to make things easier
|
||||
|
||||
// Get metrics
|
||||
glyphMetrics.bearingX = bitmap_glyph->left;
|
||||
glyphMetrics.bearingY = bitmap_glyph->top;
|
||||
@@ -123,7 +148,8 @@ GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const
|
||||
glyphMetrics.width = bitmap.width;
|
||||
glyphMetrics.advance = (int) (ftglyph->advance.x >> 16);
|
||||
|
||||
GlyphData *glyphData = new GlyphData(glyph, glyphMetrics, PIXELFORMAT_LA8);
|
||||
// TODO: https://stackoverflow.com/questions/60526004/how-to-get-glyph-unicode-using-freetype/69730502#69730502
|
||||
GlyphData *glyphData = new GlyphData(0, glyphMetrics, PIXELFORMAT_LA8_UNORM);
|
||||
|
||||
const uint8 *pixels = bitmap.buffer;
|
||||
uint8 *dest = (uint8 *) glyphData->getData();
|
||||
@@ -196,6 +222,11 @@ Rasterizer::DataType TrueTypeRasterizer::getDataType() const
|
||||
return DATA_TRUETYPE;
|
||||
}
|
||||
|
||||
TextShaper *TrueTypeRasterizer::newTextShaper()
|
||||
{
|
||||
return new HarfbuzzShaper(this);
|
||||
}
|
||||
|
||||
bool TrueTypeRasterizer::accepts(FT_Library library, love::Data *data)
|
||||
{
|
||||
const FT_Byte *fbase = (const FT_Byte *) data->getData();
|
||||
|
||||
@@ -49,11 +49,16 @@ public:
|
||||
|
||||
// Implement Rasterizer
|
||||
int getLineHeight() const override;
|
||||
GlyphData *getGlyphData(uint32 glyph) const override;
|
||||
int getGlyphSpacing(uint32 glyph) const override;
|
||||
int getGlyphIndex(uint32 glyph) const override;
|
||||
GlyphData *getGlyphDataForIndex(int index) const override;
|
||||
int getGlyphCount() const override;
|
||||
bool hasGlyph(uint32 glyph) const override;
|
||||
float getKerning(uint32 leftglyph, uint32 rightglyph) const override;
|
||||
DataType getDataType() const override;
|
||||
TextShaper *newTextShaper() override;
|
||||
|
||||
ptrdiff_t getHandle() const override { return (ptrdiff_t) face; }
|
||||
|
||||
static bool accepts(FT_Library library, love::Data *data);
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ int w_newTrueTypeRasterizer(lua_State *L)
|
||||
if (lua_type(L, 1) == LUA_TNUMBER || lua_isnone(L, 1))
|
||||
{
|
||||
// First argument is a number: use the default TrueType font.
|
||||
int size = (int) luaL_optinteger(L, 1, 12);
|
||||
int size = (int) luaL_optinteger(L, 1, 13);
|
||||
|
||||
const char *hintstr = lua_isnoneornil(L, 2) ? nullptr : luaL_checkstring(L, 2);
|
||||
if (hintstr && !TrueTypeRasterizer::getConstant(hintstr, hinting))
|
||||
|
||||
Reference in New Issue
Block a user