From 7a0f6621e7c2164af340535dcb2c1dee3b08a5fb Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Mon, 26 Dec 2022 19:06:08 -0400 Subject: [PATCH 01/11] Initial (WIP) Harfbuzz text shaping integration. This a significant refactor / rewrite of much of the love.graphics Font code. Text positioning is now split into TextShaper classes in the love.font module. This isn't exposed to users yet. BMFonts and ImageFonts use the same text shaping as before (in the new TextShaper API). Fonts loaded via FreeType now use Harfbuzz for text shaping. The new code isn't at feature-parity with the old code yet. Harfbuzz-based text shaping enables kerning support in more fonts, character combining into ligature glyphs, and directions other than left-to-right (this isn't supported in love yet). --- CMakeLists.txt | 9 + src/modules/font/BMFontRasterizer.cpp | 64 +- src/modules/font/BMFontRasterizer.h | 12 +- src/modules/font/GenericShaper.cpp | 203 ++++++ src/modules/font/GenericShaper.h | 46 ++ src/modules/font/GlyphData.cpp | 4 - src/modules/font/ImageRasterizer.cpp | 63 +- src/modules/font/ImageRasterizer.h | 17 +- src/modules/font/Rasterizer.cpp | 5 + src/modules/font/Rasterizer.h | 25 +- src/modules/font/TextShaper.cpp | 373 +++++++++++ src/modules/font/TextShaper.h | 148 +++++ src/modules/font/freetype/HarfbuzzShaper.cpp | 290 +++++++++ src/modules/font/freetype/HarfbuzzShaper.h | 59 ++ .../font/freetype/TrueTypeRasterizer.cpp | 38 +- .../font/freetype/TrueTypeRasterizer.h | 7 +- src/modules/graphics/Deprecations.cpp | 2 +- src/modules/graphics/Font.cpp | 608 ++++-------------- src/modules/graphics/Font.h | 65 +- src/modules/graphics/Graphics.cpp | 10 +- src/modules/graphics/Graphics.h | 10 +- src/modules/graphics/TextBatch.cpp | 22 +- src/modules/graphics/TextBatch.h | 14 +- src/modules/graphics/wrap_Font.cpp | 6 +- src/modules/graphics/wrap_Font.h | 2 +- src/modules/graphics/wrap_Graphics.cpp | 6 +- src/modules/graphics/wrap_TextBatch.cpp | 8 +- 27 files changed, 1490 insertions(+), 626 deletions(-) create mode 100644 src/modules/font/GenericShaper.cpp create mode 100644 src/modules/font/GenericShaper.h create mode 100644 src/modules/font/TextShaper.cpp create mode 100644 src/modules/font/TextShaper.h create mode 100644 src/modules/font/freetype/HarfbuzzShaper.cpp create mode 100644 src/modules/font/freetype/HarfbuzzShaper.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1897e3e34..5e633e9cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,6 +99,7 @@ if(MEGA) set(LOVE_LINK_LIBRARIES ${MEGA_FREETYPE} + ${MEGA_HARFBUZZ} ${MEGA_LIBOGG} ${MEGA_LIBVORBISFILE} ${MEGA_LIBVORBIS} @@ -169,6 +170,7 @@ Please see https://github.com/love2d/megasource endif() find_package(Freetype REQUIRED) + find_package(harfbuzz REQUIRED) find_package(ModPlug REQUIRED) find_package(OpenAL REQUIRED) find_package(OpenGL REQUIRED) @@ -196,6 +198,7 @@ Please see https://github.com/love2d/megasource ${OPENGL_gl_LIBRARY} ${SDL2_LIBRARY} ${FREETYPE_LIBRARY} + ${HARFBUZZ_LIBRARY} ${OPENAL_LIBRARY} ${MODPLUG_LIBRARY} ${THEORA_LIBRARY} @@ -473,12 +476,16 @@ set(LOVE_SRC_MODULE_FONT_ROOT src/modules/font/BMFontRasterizer.h src/modules/font/Font.cpp src/modules/font/Font.h + src/modules/font/GenericShaper.cpp + src/modules/font/GenericShaper.h src/modules/font/GlyphData.cpp src/modules/font/GlyphData.h src/modules/font/ImageRasterizer.cpp src/modules/font/ImageRasterizer.h src/modules/font/Rasterizer.cpp src/modules/font/Rasterizer.h + src/modules/font/TextShaper.cpp + src/modules/font/TextShaper.h src/modules/font/TrueTypeRasterizer.cpp src/modules/font/TrueTypeRasterizer.h src/modules/font/wrap_Font.cpp @@ -492,6 +499,8 @@ set(LOVE_SRC_MODULE_FONT_ROOT set(LOVE_SRC_MODULE_FONT_FREETYPE src/modules/font/freetype/Font.cpp src/modules/font/freetype/Font.h + src/modules/font/freetype/HarfbuzzShaper.cpp + src/modules/font/freetype/HarfbuzzShaper.h src/modules/font/freetype/TrueTypeRasterizer.cpp src/modules/font/freetype/TrueTypeRasterizer.h ) diff --git a/src/modules/font/BMFontRasterizer.cpp b/src/modules/font/BMFontRasterizer.cpp index 2a70a57f8..8477f3430 100644 --- a/src/modules/font/BMFontRasterizer.cpp +++ b/src/modules/font/BMFontRasterizer.cpp @@ -20,6 +20,7 @@ // LOVE #include "BMFontRasterizer.h" +#include "GenericShaper.h" #include "filesystem/Filesystem.h" #include "image/Image.h" @@ -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; @@ -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_UNORM); + 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_UNORM); + return new GlyphData(c.glyph, GlyphMetrics(), PIXELFORMAT_RGBA8_UNORM); image::ImageData *imagedata = imagepair->second.get(); - GlyphData *g = new GlyphData(glyph, c.metrics, PIXELFORMAT_RGBA8_UNORM); + 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(); diff --git a/src/modules/font/BMFontRasterizer.h b/src/modules/font/BMFontRasterizer.h index 43585f5e4..ed2fcf288 100644 --- a/src/modules/font/BMFontRasterizer.h +++ b/src/modules/font/BMFontRasterizer.h @@ -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> images; - // Glyph characters, indexed by their glyph id. - std::unordered_map characters; + std::vector characters; + + // Glyph character indices, indexed by their glyph id. + std::unordered_map characterIndices; // Kerning information, indexed by two (packed) characters. std::unordered_map kerning; diff --git a/src/modules/font/GenericShaper.cpp b/src/modules/font/GenericShaper.cpp new file mode 100644 index 000000000..74d1b50d4 --- /dev/null +++ b/src/modules/font/GenericShaper.cpp @@ -0,0 +1,203 @@ +/** + * Copyright (c) 2006-2022 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 *positions, std::vector *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 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, 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 && 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 (g == ' ') + { + lastspaceindex = i; + if (prevglyph != ' ') + widthbeforelastspace = w; + } + else + outwidth = newwidth; + + w = newwidth; + prevglyph = g; + wrapindex = i; + } + + if (width) + *width = outwidth; + + return wrapindex; +} + +} // font +} // love diff --git a/src/modules/font/GenericShaper.h b/src/modules/font/GenericShaper.h new file mode 100644 index 000000000..41c2982e7 --- /dev/null +++ b/src/modules/font/GenericShaper.h @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2006-2022 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 *positions, std::vector *colors, TextInfo *info) override; + int computeWordWrapIndex(const ColoredCodepoints& codepoints, Range range, float wraplimit, float *width) override; + +private: + +}; // GenericShaper + +} // font +} // love diff --git a/src/modules/font/GlyphData.cpp b/src/modules/font/GlyphData.cpp index 14bb1d5fd..a59735ab8 100644 --- a/src/modules/font/GlyphData.cpp +++ b/src/modules/font/GlyphData.cpp @@ -24,10 +24,6 @@ // UTF-8 #include "libraries/utf8/utf8.h" -// stdlib -#include -#include - namespace love { namespace font diff --git a/src/modules/font/ImageRasterizer.cpp b/src/modules/font/ImageRasterizer.cpp index 7291e1981..c3506edf2 100644 --- a/src/modules/font/ImageRasterizer.cpp +++ b/src/modules/font/ImageRasterizer.cpp @@ -20,8 +20,9 @@ // LOVE #include "ImageRasterizer.h" - +#include "GenericShaper.h" #include "common/Exception.h" + #include namespace love @@ -31,10 +32,9 @@ 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; @@ -42,7 +42,7 @@ ImageRasterizer::ImageRasterizer(love::image::ImageData *data, uint32 *glyphs, i 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,16 +54,33 @@ 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::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; @@ -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 diff --git a/src/modules/font/ImageRasterizer.h b/src/modules/font/ImageRasterizer.h index fd29bce4c..64489eeab 100644 --- a/src/modules/font/ImageRasterizer.h +++ b/src/modules/font/ImageRasterizer.h @@ -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 imageData; - // The glyphs in the font - uint32 *glyphs; - // Number of glyphs in the font int numglyphs; int extraSpacing; - std::map imageGlyphs; + + std::vector imageGlyphs; + std::map glyphIndices; // Color used to identify glyph separation in the source ImageData Color32 spacer; diff --git a/src/modules/font/Rasterizer.cpp b/src/modules/font/Rasterizer.cpp index f15c88184..3be78f165 100644 --- a/src/modules/font/Rasterizer.cpp +++ b/src/modules/font/Rasterizer.cpp @@ -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; diff --git a/src/modules/font/Rasterizer.h b/src/modules/font/Rasterizer.h index 6cd6b49a8..4510df33c 100644 --- a/src/modules/font/Rasterizer.h +++ b/src/modules/font/Rasterizer.h @@ -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: diff --git a/src/modules/font/TextShaper.cpp b/src/modules/font/TextShaper.cpp new file mode 100644 index 000000000..0b384081d --- /dev/null +++ b/src/modules/font/TextShaper.cpp @@ -0,0 +1,373 @@ +/** + * Copyright (c) 2006-2022 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& codepoints) +{ + codepoints.reserve(text.size()); + + try + { + utf8::iterator i(text.begin(), text.begin(), text.end()); + utf8::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& 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) +{ +} + +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& 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 i(text.begin(), text.begin(), text.end()); + utf8::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) +{ + // TODO: use spaces for tab + + const auto it = glyphAdvances.find(glyph); + if (it != glyphAdvances.end()) + { + if (glyphindex) + *glyphindex = it->second.second; + return it->second.first; + } + + int rasterizeri = 0; + + for (size_t i = 0; i < rasterizers.size(); i++) + { + if (rasterizers[i]->hasGlyph(glyph)) + { + rasterizeri = (int) i; + break; + } + } + + const auto &r = rasterizers[rasterizeri]; + int advance = floorf(r->getGlyphSpacing(glyph) / r->getDPIScale() + 0.5f); + + GlyphIndex glyphi = {r->getGlyphIndex(glyph), 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& lineranges, std::vector* 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& text, float wraplimit, std::vector& lines, std::vector* linewidths) +{ + ColoredCodepoints cps; + getCodepointsFromString(text, cps); + + std::vector 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& 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 diff --git a/src/modules/font/TextShaper.h b/src/modules/font/TextShaper.h new file mode 100644 index 000000000..72b92653b --- /dev/null +++ b/src/modules/font/TextShaper.h @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2006-2022 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 +#include +#include + +namespace love +{ +namespace font +{ + +class Rasterizer; + +struct ColoredString +{ + std::string str; + Colorf color; +}; + +struct IndexedColor +{ + Colorf color; + int index; +}; + +struct ColoredCodepoints +{ + std::vector cps; + std::vector colors; +}; + +void getCodepointsFromString(const std::string& str, std::vector& codepoints); +void getCodepointsFromString(const std::vector& 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; + }; + + static love::Type type; + + virtual ~TextShaper(); + + const std::vector> &getRasterizers() const { return rasterizers; } + + 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& text, float wraplimit, std::vector& lines, std::vector* line_widths = nullptr); + void getWrap(const ColoredCodepoints& codepoints, float wraplimit, std::vector &lineranges, std::vector *linewidths = nullptr); + + virtual void setFallbacks(const std::vector &fallbacks); + + virtual void computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector *positions, std::vector *colors, TextInfo *info) = 0; + virtual int computeWordWrapIndex(const ColoredCodepoints& codepoints, Range range, float wraplimit, float *width) = 0; + +protected: + + TextShaper(Rasterizer *rasterizer); + + std::vector> rasterizers; + std::vector dpiScales; + +private: + + int height; + float lineHeight; + + // maps glyphs to advance and glyph+rasterizer index. + std::unordered_map> glyphAdvances; + + // map of left/right glyph pairs to horizontal kerning. + std::unordered_map kerning; + +}; // TextShaper + +} // font +} // love diff --git a/src/modules/font/freetype/HarfbuzzShaper.cpp b/src/modules/font/freetype/HarfbuzzShaper.cpp new file mode 100644 index 000000000..8f0b0037d --- /dev/null +++ b/src/modules/font/freetype/HarfbuzzShaper.cpp @@ -0,0 +1,290 @@ +/** + * Copyright (c) 2006-2022 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 +#include + +namespace love +{ +namespace font +{ +namespace freetype +{ + +HarfbuzzShaper::HarfbuzzShaper(TrueTypeRasterizer *rasterizer) + : TextShaper(rasterizer) +{ + hbFonts.push_back(hb_ft_font_create_referenced((FT_Face)rasterizer->getHandle())); + + if (hbFonts[0] == nullptr || hbFonts[0] == hb_font_get_empty()) + throw love::Exception(""); +} + +HarfbuzzShaper::~HarfbuzzShaper() +{ + for (hb_font_t *font : hbFonts) + hb_font_destroy(font); +} + +void HarfbuzzShaper::setFallbacks(const std::vector& fallbacks) +{ + for (size_t i = 1; i < hbFonts.size(); i++) + hb_font_destroy(hbFonts[i]); + + TextShaper::setFallbacks(fallbacks); + + hbFonts.resize(rasterizers.size()); + + for (size_t i = 1; i < hbFonts.size(); i++) + hbFonts[i] = hb_ft_font_create_referenced((FT_Face)rasterizers[i]->getHandle()); +} + +void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector *positions, std::vector *colors, TextInfo *info) +{ + if (!range.isValid()) + range = Range(0, codepoints.cps.size()); + + offset.y += getBaseline(); + Vector2 curpos = offset; + + hb_buffer_t *hbbuffer = hb_buffer_create(); + + 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? + hb_buffer_guess_segment_properties(hbbuffer); + + hb_shape(hbFonts[0], hbbuffer, nullptr, 0); + + int glyphcount = (int) hb_buffer_get_length(hbbuffer); + hb_glyph_info_t* glyphinfos = hb_buffer_get_glyph_infos(hbbuffer, nullptr); + hb_glyph_position_t* glyphpositions = hb_buffer_get_glyph_positions(hbbuffer, nullptr); + + if (positions) + positions->reserve(positions->size() + glyphcount); + + if (rasterizers.size() > 1) + { + std::vector fallbackranges; + + for (int i = 0; i < glyphcount; i++) + { + bool adding = false; + if (glyphinfos[i].codepoint == 0) + { + if (fallbackranges.empty() || fallbackranges.back().getMax() != glyphinfos[i-1].cluster) + fallbackranges.push_back(Range(glyphinfos[i].cluster, 1)); + else + fallbackranges.back().encapsulate(glyphinfos[i].cluster); + } + } + + if (!fallbackranges.empty()) + { + for (size_t rasti = 1; rasti < rasterizers.size(); rasti++) + { + hb_buffer_t* hbbuffer = hb_buffer_create(); + + hb_buffer_destroy(hbbuffer); + } + } + } + + int colorindex = 0; + int ncolors = (int)codepoints.colors.size(); + Optional 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); + } + } + + int maxwidth = (int)curpos.x; + + // TODO: fallbacks + // TODO: use spaces for tab + for (int i = 0; i < glyphcount; i++) + { + const hb_glyph_info_t& info = glyphinfos[i]; + const 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; + + if (colorToAdd.hasValue && colors && positions) + { + IndexedColor c = {colorToAdd.value, positions->size()}; + colors->push_back(c); + colorToAdd.clear(); + } + + if (positions) + { + // Despite the name this is a glyph index at this point. + GlyphIndex gindex = { info.codepoint, 0 }; + 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); + } + + hb_buffer_destroy(hbbuffer); + + 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()); + + hb_buffer_t* hbbuffer = hb_buffer_create(); + + 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? + hb_buffer_guess_segment_properties(hbbuffer); + + hb_shape(hbFonts[0], hbbuffer, nullptr, 0); + + int glyphcount = (int)hb_buffer_get_length(hbbuffer); + hb_glyph_info_t* glyphinfos = hb_buffer_get_glyph_infos(hbbuffer, nullptr); + hb_glyph_position_t* glyphpositions = hb_buffer_get_glyph_positions(hbbuffer, nullptr); + + float w = 0.0f; + float outwidth = 0.0f; + float widthbeforelastspace = 0.0f; + int wrapindex = -1; + int lastspaceindex = -1; + + uint32 prevcodepoint = 0; + + for (int i = 0; i < glyphcount; i++) + { + const hb_glyph_info_t& info = glyphinfos[i]; + const hb_glyph_position_t& glyphpos = glyphpositions[i]; + + uint32 clustercodepoint = codepoints.cps[info.cluster]; + + if (clustercodepoint == '\r') + { + prevcodepoint = clustercodepoint; + continue; + } + + float newwidth = w + floorf((glyphpos.x_advance >> 6) / dpiScales[0] + 0.5f); + + // Only wrap when there's a non-space character. + if (newwidth > wraplimit && 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 (clustercodepoint == ' ') + { + lastspaceindex = info.cluster; + if (prevcodepoint != ' ') + widthbeforelastspace = w; + } + else + outwidth = newwidth; + + w = newwidth; + prevcodepoint = clustercodepoint; + wrapindex = info.cluster; + } + + hb_buffer_destroy(hbbuffer); + + if (width) + *width = outwidth; + + return wrapindex; +} + +} // freetype +} // font +} // love diff --git a/src/modules/font/freetype/HarfbuzzShaper.h b/src/modules/font/freetype/HarfbuzzShaper.h new file mode 100644 index 000000000..f26d362c7 --- /dev/null +++ b/src/modules/font/freetype/HarfbuzzShaper.h @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2006-2022 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; +} + +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& fallbacks) override; + void computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector *positions, std::vector *colors, TextInfo *info) override; + int computeWordWrapIndex(const ColoredCodepoints& codepoints, Range range, float wraplimit, float *width) override; + +private: + + std::vector hbFonts; + +}; // HarfbuzzShaper + +} // freetype +} // font +} // love diff --git a/src/modules/font/freetype/TrueTypeRasterizer.cpp b/src/modules/font/freetype/TrueTypeRasterizer.cpp index 68ce62fdc..812222394 100644 --- a/src/modules/font/freetype/TrueTypeRasterizer.cpp +++ b/src/modules/font/freetype/TrueTypeRasterizer.cpp @@ -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); @@ -104,7 +128,7 @@ GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const throw love::Exception("TrueType Font glyph error: FT_Glyph_To_Bitmap failed (0x%x)", err); 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; @@ -113,7 +137,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_UNORM); + // 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(); @@ -186,6 +211,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(); diff --git a/src/modules/font/freetype/TrueTypeRasterizer.h b/src/modules/font/freetype/TrueTypeRasterizer.h index deee5562a..34d7ca8a0 100644 --- a/src/modules/font/freetype/TrueTypeRasterizer.h +++ b/src/modules/font/freetype/TrueTypeRasterizer.h @@ -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); diff --git a/src/modules/graphics/Deprecations.cpp b/src/modules/graphics/Deprecations.cpp index cde7659c5..f0b0a216e 100644 --- a/src/modules/graphics/Deprecations.cpp +++ b/src/modules/graphics/Deprecations.cpp @@ -90,7 +90,7 @@ void Deprecations::draw(Graphics *gfx) int maxcount = 4; int remaining = std::max(0, total - maxcount); - std::vector strings; + std::vector strings; Colorf white(1, 1, 1, 1); // Grab the newest deprecation notices first. diff --git a/src/modules/graphics/Font.cpp b/src/modules/graphics/Font.cpp index fb4e52702..9785532aa 100644 --- a/src/modules/graphics/Font.cpp +++ b/src/modules/graphics/Font.cpp @@ -21,8 +21,6 @@ #include "Font.h" #include "font/GlyphData.h" -#include "libraries/utf8/utf8.h" - #include "common/math.h" #include "common/Matrix.h" #include "Graphics.h" @@ -42,15 +40,23 @@ static inline uint16 normToUint16(double n) return (uint16) (n * LOVE_UINT16_MAX); } +static inline uint64 packGlyphIndex(love::font::TextShaper::GlyphIndex glyphindex) +{ + return ((uint64)glyphindex.rasterizerIndex << 32) | (uint64)glyphindex.index; +} + +static inline love::font::TextShaper::GlyphIndex unpackGlyphIndex(uint64 packedindex) +{ + return {(int) (packedindex & 0xFFFFFFFF), (int) (packedindex >> 32)}; +} + love::Type Font::type("Font", &Object::type); int Font::fontCount = 0; const CommonFormat Font::vertexFormat = CommonFormat::XYf_STus_RGBAub; Font::Font(love::font::Rasterizer *r, const SamplerState &s) - : rasterizers({r}) - , height(r->getHeight()) - , lineHeight(1) + : shaper(r->newTextShaper(), Acquire::NORETAIN) , textureWidth(128) , textureHeight(128) , samplerState() @@ -66,7 +72,7 @@ Font::Font(love::font::Rasterizer *r, const SamplerState &s) // largest texture size if no rough match is found. while (true) { - if ((height * 0.8) * height * 30 <= textureWidth * textureHeight) + if ((shaper->getHeight() * 0.8) * shaper->getHeight() * 30 <= textureWidth * textureHeight) break; TextureSize nextsize = getNextTextureSize(); @@ -86,7 +92,8 @@ Font::Font(love::font::Rasterizer *r, const SamplerState &s) if (pixelFormat == PIXELFORMAT_LA8_UNORM && !gfx->isPixelFormatSupported(pixelFormat, PIXELFORMATUSAGEFLAGS_SAMPLE)) pixelFormat = PIXELFORMAT_RGBA8_UNORM; - if (!r->hasGlyph(9)) // No tab character in the Rasterizer. + uint32 tab = '\t'; + if (!r->hasGlyph(tab)) // No tab character in the Rasterizer. useSpacesAsTab = true; loadVolatile(); @@ -170,7 +177,7 @@ void Font::createTexture() // and transparent black otherwise. std::vector emptydata(datasize, 0); - if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE) + if (shaper->getRasterizers()[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE) { if (pixelFormat == PIXELFORMAT_LA8_UNORM) { @@ -204,15 +211,15 @@ void Font::createTexture() { textureCacheID++; - std::vector glyphstoadd; + std::vector glyphstoadd; for (const auto &glyphpair : glyphs) - glyphstoadd.push_back(glyphpair.first); + glyphstoadd.push_back(unpackGlyphIndex(glyphpair.first)); glyphs.clear(); - for (uint32 g : glyphstoadd) - addGlyph(g); + for (auto glyphindex : glyphstoadd) + addGlyph(glyphindex); } } @@ -222,12 +229,14 @@ void Font::unloadVolatile() textures.clear(); } -love::font::GlyphData *Font::getRasterizerGlyphData(uint32 glyph, float &dpiscale) +love::font::GlyphData *Font::getRasterizerGlyphData(love::font::TextShaper::GlyphIndex glyphindex, float &dpiscale) { - // Use spaces for the tab 'glyph'. - if (glyph == 9 && useSpacesAsTab) + const auto &r = shaper->getRasterizers()[glyphindex.rasterizerIndex]; + + // Use spaces for the tab 'glyph'. FIXME + if (/*glyph == '\t' &&*/ false && useSpacesAsTab) { - love::font::GlyphData *spacegd = rasterizers[0]->getGlyphData(32); + love::font::GlyphData *spacegd = r->getGlyphData(32); PixelFormat fmt = spacegd->getFormat(); love::font::GlyphMetrics gm = {}; @@ -237,27 +246,18 @@ love::font::GlyphData *Font::getRasterizerGlyphData(uint32 glyph, float &dpiscal spacegd->release(); - dpiscale = rasterizers[0]->getDPIScale(); - return new love::font::GlyphData(glyph, gm, fmt); + dpiscale = r->getDPIScale(); + return new love::font::GlyphData('\t', gm, fmt); } - for (const StrongRef &r : rasterizers) - { - if (r->hasGlyph(glyph)) - { - dpiscale = r->getDPIScale(); - return r->getGlyphData(glyph); - } - } - - dpiscale = rasterizers[0]->getDPIScale(); - return rasterizers[0]->getGlyphData(glyph); + dpiscale = r->getDPIScale(); + return r->getGlyphDataForIndex(glyphindex.index); } -const Font::Glyph &Font::addGlyph(uint32 glyph) +const Font::Glyph &Font::addGlyph(love::font::TextShaper::GlyphIndex glyphindex) { float glyphdpiscale = getDPIScale(); - StrongRef gd(getRasterizerGlyphData(glyph, glyphdpiscale), Acquire::NORETAIN); + StrongRef gd(getRasterizerGlyphData(glyphindex, glyphdpiscale), Acquire::NORETAIN); int w = gd->getWidth(); int h = gd->getHeight(); @@ -279,15 +279,13 @@ const Font::Glyph &Font::addGlyph(uint32 glyph) // Makes sure the above code for checking if the glyph can fit at // the current position in the texture is run again for this glyph. - return addGlyph(glyph); + return addGlyph(glyphindex); } } Glyph g; - g.texture = 0; - g.spacing = floorf(gd->getAdvance() / glyphdpiscale + 0.5f); - + g.texture = nullptr; memset(g.vertices, 0, sizeof(GlyphVertex) * 4); // Don't waste space for empty glyphs. @@ -357,151 +355,77 @@ const Font::Glyph &Font::addGlyph(uint32 glyph) rowHeight = std::max(rowHeight, h + TEXTURE_PADDING); } - glyphs[glyph] = g; - return glyphs[glyph]; + uint64 packedindex = packGlyphIndex(glyphindex); + glyphs[packedindex] = g; + return glyphs[packedindex]; } -const Font::Glyph &Font::findGlyph(uint32 glyph) +const Font::Glyph &Font::findGlyph(love::font::TextShaper::GlyphIndex glyphindex) { - const auto it = glyphs.find(glyph); + uint64 packedindex = packGlyphIndex(glyphindex); + const auto it = glyphs.find(packedindex); if (it != glyphs.end()) return it->second; - return addGlyph(glyph); + return addGlyph(glyphindex); } float Font::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 = floorf(rasterizers[0]->getKerning(leftglyph, rightglyph) / dpiScale + 0.5f); - - for (const auto &r : rasterizers) - { - if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph)) - { - k = floorf(r->getKerning(leftglyph, rightglyph) / r->getDPIScale() + 0.5f); - break; - } - } - - kerning[packedglyphs] = k; - return k; + return shaper->getKerning(leftglyph, rightglyph); } float Font::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); -} - -void Font::getCodepointsFromString(const std::string &text, Codepoints &codepoints) -{ - codepoints.reserve(text.size()); - - try - { - utf8::iterator i(text.begin(), text.begin(), text.end()); - utf8::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 Font::getCodepointsFromString(const std::vector &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(); - } + return shaper->getKerning(leftchar, rightchar); } float Font::getHeight() const { - return (float) floorf(height / dpiScale + 0.5f); + return shaper->getHeight(); } -std::vector Font::generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantcolor, std::vector &vertices, float extra_spacing, Vector2 offset, TextInfo *info) +std::vector Font::generateVertices(const love::font::ColoredCodepoints &codepoints, Range range, const Colorf &constantcolor, std::vector &vertices, float extra_spacing, Vector2 offset, love::font::TextShaper::TextInfo *info) { - // Spacing counter and newline handling. - float dx = offset.x; - float dy = offset.y; + std::vector glyphpositions; + std::vector colors; + shaper->computeGlyphPositions(codepoints, range, offset, extra_spacing, &glyphpositions, &colors, info); - float heightoffset = 0.0f; + size_t vertstartsize = vertices.size(); + vertices.reserve(vertstartsize + glyphpositions.size() * 4); - if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE) - heightoffset = getBaseline(); + Colorf linearconstantcolor = gammaCorrectColor(constantcolor); + Color32 curcolor = toColor32(constantcolor); - int maxwidth = 0; + int curcolori = 0; + int ncolors = (int)colors.size(); // Keeps track of when we need to switch textures in our vertex array. std::vector commands; - // Pre-allocate space for the maximum possible number of vertices. - size_t vertstartsize = vertices.size(); - vertices.reserve(vertstartsize + codepoints.cps.size() * 4); - - uint32 prevglyph = 0; - - Colorf linearconstantcolor = gammaCorrectColor(constantcolor); - - Color32 curcolor = toColor32(constantcolor); - int curcolori = -1; - int ncolors = (int) codepoints.colors.size(); - - for (int i = 0; i < (int) codepoints.cps.size(); i++) + for (int i = 0; i < (int) glyphpositions.size(); i++) { - uint32 g = codepoints.cps[i]; + const auto &info = glyphpositions[i]; - if (curcolori + 1 < ncolors && codepoints.colors[curcolori + 1].index == i) + uint32 cacheid = textureCacheID; + + const Glyph &glyph = findGlyph(info.glyphIndex); + + // If findGlyph invalidates the texture cache, restart the loop. + if (cacheid != textureCacheID) { - Colorf c = codepoints.colors[++curcolori].color; + i = -1; // The next iteration will increment this to 0. + commands.clear(); + vertices.resize(vertstartsize); + curcolori = 0; + curcolor = toColor32(constantcolor); + continue; + } + + if (curcolori < ncolors && colors[curcolori].index == i) + { + Colorf c = colors[curcolori].color; c.r = std::min(std::max(c.r, 0.0f), 1.0f); c.g = std::min(std::max(c.g, 0.0f), 1.0f); @@ -513,54 +437,17 @@ std::vector Font::generateVertices(const ColoredCodepoints &c unGammaCorrectColor(c); curcolor = toColor32(c); + curcolori++; } - if (g == '\n') - { - if (dx > maxwidth) - maxwidth = (int) dx; - - // Wrap newline, but do not print it. - dy += floorf(getHeight() * getLineHeight() + 0.5f); - dx = offset.x; - prevglyph = 0; - continue; - } - - // Ignore carriage returns - if (g == '\r') - continue; - - uint32 cacheid = textureCacheID; - - const Glyph &glyph = findGlyph(g); - - // If findGlyph invalidates the texture cache, re-start the loop. - if (cacheid != textureCacheID) - { - i = -1; // The next iteration will increment this to 0. - maxwidth = 0; - dx = offset.x; - dy = offset.y; - commands.clear(); - vertices.resize(vertstartsize); - prevglyph = 0; - curcolori = -1; - curcolor = toColor32(constantcolor); - continue; - } - - // Add kerning to the current horizontal offset. - dx += getKerning(prevglyph, g); - if (glyph.texture != nullptr) { // Copy the vertices and set their colors and relative positions. for (int j = 0; j < 4; j++) { vertices.push_back(glyph.vertices[j]); - vertices.back().x += dx; - vertices.back().y += dy + heightoffset; + vertices.back().x += info.position.x; + vertices.back().y += info.position.y; vertices.back().color = curcolor; } @@ -569,7 +456,7 @@ std::vector Font::generateVertices(const ColoredCodepoints &c { // Add a new draw command if the texture has changed. DrawCommand cmd; - cmd.startvertex = (int) vertices.size() - 4; + cmd.startvertex = (int)vertices.size() - 4; cmd.vertexcount = 0; cmd.texture = glyph.texture; commands.push_back(cmd); @@ -577,15 +464,6 @@ std::vector Font::generateVertices(const ColoredCodepoints &c commands.back().vertexcount += 4; } - - // Advance the x position for the next glyph. - dx += glyph.spacing; - - // Account for extra spacing given to space characters. - if (g == ' ' && extra_spacing != 0.0f) - dx = floorf(dx + extra_spacing); - - prevglyph = g; } const auto drawsort = [](const DrawCommand &a, const DrawCommand &b) -> bool @@ -599,19 +477,10 @@ std::vector Font::generateVertices(const ColoredCodepoints &c std::sort(commands.begin(), commands.end(), drawsort); - if (dx > maxwidth) - maxwidth = (int) dx; - - if (info != nullptr) - { - info->width = maxwidth - offset.x; - info->height = (int) dy + (dx > 0.0f ? floorf(getHeight() * getLineHeight() + 0.5f) : 0) - offset.y; - } - return commands; } -std::vector Font::generateVerticesFormatted(const ColoredCodepoints &text, const Colorf &constantcolor, float wrap, AlignMode align, std::vector &vertices, TextInfo *info) +std::vector Font::generateVerticesFormatted(const love::font::ColoredCodepoints &text, const Colorf &constantcolor, float wrap, AlignMode align, std::vector &vertices, love::font::TextShaper::TextInfo *info) { wrap = std::max(wrap, 0.0f); @@ -620,17 +489,22 @@ std::vector Font::generateVerticesFormatted(const ColoredCode std::vector drawcommands; vertices.reserve(text.cps.size() * 4); + std::vector ranges; std::vector widths; - std::vector lines; - - getWrap(text, wrap, lines, &widths); + shaper->getWrap(text, wrap, ranges, &widths); float y = 0.0f; float maxwidth = 0.0f; - for (int i = 0; i < (int) lines.size(); i++) + for (int i = 0; i < (int)ranges.size(); i++) { - const auto &line = lines[i]; + const auto& range = ranges[i]; + + if (!range.isValid()) + { + y += getHeight() * getLineHeight(); + continue; + } float width = (float) widths[i]; love::Vector2 offset(0.0f, floorf(y)); @@ -648,7 +522,9 @@ std::vector Font::generateVerticesFormatted(const ColoredCode break; case ALIGN_JUSTIFY: { - float numspaces = (float) std::count(line.cps.begin(), line.cps.end(), ' '); + auto start = text.cps.begin() + range.getOffset(); + auto end = start + range.getSize(); + float numspaces = std::count(start, end, ' '); if (width < wrap && numspaces >= 1) extraspacing = (wrap - width) / numspaces; else @@ -660,7 +536,7 @@ std::vector Font::generateVerticesFormatted(const ColoredCode break; } - std::vector newcommands = generateVertices(line, constantcolor, vertices, extraspacing, offset); + std::vector newcommands = generateVertices(text, range, constantcolor, vertices, extraspacing, offset); if (!newcommands.empty()) { @@ -724,21 +600,21 @@ void Font::printv(graphics::Graphics *gfx, const Matrix4 &t, const std::vector &text, const Matrix4 &m, const Colorf &constantcolor) +void Font::print(graphics::Graphics *gfx, const std::vector &text, const Matrix4 &m, const Colorf &constantcolor) { - ColoredCodepoints codepoints; - getCodepointsFromString(text, codepoints); + love::font::ColoredCodepoints codepoints; + love::font::getCodepointsFromString(text, codepoints); std::vector vertices; - std::vector drawcommands = generateVertices(codepoints, constantcolor, vertices); + std::vector drawcommands = generateVertices(codepoints, Range(), constantcolor, vertices); printv(gfx, m, drawcommands, vertices); } -void Font::printf(graphics::Graphics *gfx, const std::vector &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantcolor) +void Font::printf(graphics::Graphics *gfx, const std::vector &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantcolor) { - ColoredCodepoints codepoints; - getCodepointsFromString(text, codepoints); + love::font::ColoredCodepoints codepoints; + love::font::getCodepointsFromString(text, codepoints); std::vector vertices; std::vector drawcommands = generateVerticesFormatted(codepoints, constantcolor, wrap, align, vertices); @@ -748,241 +624,32 @@ void Font::printf(graphics::Graphics *gfx, const std::vector &tex int Font::getWidth(const std::string &str) { - if (str.size() == 0) return 0; - - std::istringstream iss(str); - std::string line; - int max_width = 0; - - while (getline(iss, line, '\n')) - { - int width = 0; - uint32 prevglyph = 0; - try - { - utf8::iterator i(line.begin(), line.begin(), line.end()); - utf8::iterator end(line.end(), line.begin(), line.end()); - - while (i != end) - { - uint32 c = *i++; - - // Ignore carriage returns - if (c == '\r') - continue; - - const Glyph &g = findGlyph(c); - width += g.spacing + getKerning(prevglyph, c); - - prevglyph = c; - } - } - catch (utf8::exception &e) - { - throw love::Exception("UTF-8 decoding error: %s", e.what()); - } - - max_width = std::max(max_width, width); - } - - return max_width; + return shaper->getWidth(str); } int Font::getWidth(uint32 glyph) { - const Glyph &g = findGlyph(glyph); - return g.spacing; + return shaper->getGlyphAdvance(glyph); } -void Font::getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector &lines, std::vector *linewidths) +void Font::getWrap(const love::font::ColoredCodepoints &codepoints, float wraplimit, std::vector &ranges, std::vector *linewidths) { - // Per-line info. - float width = 0.0f; - float widthbeforelastspace = 0.0f; - float widthoftrailingspace = 0.0f; - uint32 prevglyph = 0; - - int lastspaceindex = -1; - - // Keeping the indexed colors "in sync" is a bit tricky, since we split - // things up and we might skip some glyphs but we don't want to skip any - // color which starts at those indices. - Colorf curcolor(1.0f, 1.0f, 1.0f, 1.0f); - bool addcurcolor = false; - int curcolori = -1; - int endcolori = (int) codepoints.colors.size() - 1; - - // A wrapped line of text. - ColoredCodepoints wline; - - int i = 0; - while (i < (int) codepoints.cps.size()) - { - uint32 c = codepoints.cps[i]; - - // Determine the current color before doing anything else, to make sure - // it's still applied to future glyphs even if this one is skipped. - if (curcolori < endcolori && codepoints.colors[curcolori + 1].index == i) - { - curcolor = codepoints.colors[curcolori + 1].color; - curcolori++; - addcurcolor = true; - } - - // Split text at newlines. - if (c == '\n') - { - lines.push_back(wline); - - // Ignore the width of any trailing spaces, for individual lines. - if (linewidths) - linewidths->push_back(width - widthoftrailingspace); - - // Make sure the new line keeps any color that was set previously. - addcurcolor = true; - - width = widthbeforelastspace = widthoftrailingspace = 0.0f; - prevglyph = 0; // Reset kerning information. - lastspaceindex = -1; - wline.cps.clear(); - wline.colors.clear(); - i++; - - continue; - } - - // Ignore carriage returns - if (c == '\r') - { - i++; - continue; - } - - const Glyph &g = findGlyph(c); - float charwidth = g.spacing + getKerning(prevglyph, c); - float newwidth = width + charwidth; - - // Wrap the line if it exceeds the wrap limit. Don't wrap yet if we're - // processing a newline character, though. - if (c != ' ' && newwidth > wraplimit) - { - // If this is the first character in the line and it exceeds the - // limit, skip it completely. - if (wline.cps.empty()) - i++; - else if (lastspaceindex != -1) - { - // 'Rewind' to the last seen space, if the line has one. - // FIXME: This could be more efficient... - while (!wline.cps.empty() && wline.cps.back() != ' ') - wline.cps.pop_back(); - - while (!wline.colors.empty() && wline.colors.back().index >= (int) wline.cps.size()) - wline.colors.pop_back(); - - // Also 'rewind' to the color that the last character is using. - for (int colori = curcolori; colori >= 0; colori--) - { - if (codepoints.colors[colori].index <= lastspaceindex) - { - curcolor = codepoints.colors[colori].color; - curcolori = colori; - break; - } - } - - // Ignore the width of trailing spaces in wrapped lines. - width = widthbeforelastspace; - - i = lastspaceindex; - i++; // Start the next line after the space. - } - - lines.push_back(wline); - - if (linewidths) - linewidths->push_back(width); - - addcurcolor = true; - - prevglyph = 0; - width = widthbeforelastspace = widthoftrailingspace = 0.0f; - wline.cps.clear(); - wline.colors.clear(); - lastspaceindex = -1; - - continue; - } - - if (prevglyph != ' ' && c == ' ') - widthbeforelastspace = width; - - width = newwidth; - prevglyph = c; - - if (addcurcolor) - { - wline.colors.push_back({curcolor, (int) wline.cps.size()}); - addcurcolor = false; - } - - wline.cps.push_back(c); - - // Keep track of the last seen space, so we can "rewind" to it when - // wrapping. - if (c == ' ') - { - lastspaceindex = i; - widthoftrailingspace += charwidth; - } - else if (c != '\n') - widthoftrailingspace = 0.0f; - - i++; - } - - // Push the last line. - lines.push_back(wline); - - // Ignore the width of any trailing spaces, for individual lines. - if (linewidths) - linewidths->push_back(width - widthoftrailingspace); + shaper->getWrap(codepoints, wraplimit, ranges, linewidths); } -void Font::getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *linewidths) +void Font::getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *linewidths) { - ColoredCodepoints cps; - getCodepointsFromString(text, cps); - - std::vector codepointlines; - getWrap(cps, wraplimit, codepointlines, linewidths); - - std::string line; - - for (const ColoredCodepoints &codepoints : codepointlines) - { - line.clear(); - line.reserve(codepoints.cps.size()); - - for (uint32 codepoint : codepoints.cps) - { - char character[5] = {'\0'}; - char *end = utf8::unchecked::append(codepoint, character); - line.append(character, end - character); - } - - lines.push_back(line); - } + shaper->getWrap(text, wraplimit, lines, linewidths); } void Font::setLineHeight(float height) { - lineHeight = height; + shaper->setLineHeight(height); } float Font::getLineHeight() const { - return lineHeight; + return shaper->getLineHeight(); } void Font::setSamplerState(const SamplerState &s) @@ -1002,75 +669,44 @@ const SamplerState &Font::getSamplerState() const int Font::getAscent() const { - return floorf(rasterizers[0]->getAscent() / dpiScale + 0.5f); + return shaper->getAscent(); } int Font::getDescent() const { - return floorf(rasterizers[0]->getDescent() / dpiScale + 0.5f); + return shaper->getDescent(); } float Font::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; + return shaper->getBaseline(); } bool Font::hasGlyph(uint32 glyph) const { - for (const StrongRef &r : rasterizers) - { - if (r->hasGlyph(glyph)) - return true; - } - - return false; + return shaper->hasGlyph(glyph); } bool Font::hasGlyphs(const std::string &text) const { - if (text.size() == 0) - return false; - - try - { - utf8::iterator i(text.begin(), text.begin(), text.end()); - utf8::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; + return shaper->hasGlyphs(text); } void Font::setFallbacks(const std::vector &fallbacks) { - for (const Font *f : fallbacks) - { - if (f->rasterizers[0]->getDataType() != this->rasterizers[0]->getDataType()) - throw love::Exception("Font fallbacks must be of the same font type."); - } + std::vector rasterizerfallbacks; + for (const Font* f : fallbacks) + rasterizerfallbacks.push_back(f->shaper->getRasterizers()[0]); - rasterizers.resize(1); + shaper->setFallbacks(rasterizerfallbacks); - // NOTE: this won't invalidate already-rasterized glyphs. - for (const Font *f : fallbacks) - rasterizers.push_back(f->rasterizers[0]); + // Invalidate existing textures. + textureCacheID++; + glyphs.clear(); + while (textures.size() > 1) + textures.pop_back(); + + rowHeight = textureX = textureY = TEXTURE_PADDING; } float Font::getDPIScale() const diff --git a/src/modules/graphics/Font.h b/src/modules/graphics/Font.h index df9f52edc..f3cc0afd4 100644 --- a/src/modules/graphics/Font.h +++ b/src/modules/graphics/Font.h @@ -33,6 +33,7 @@ #include "common/Vector.h" #include "font/Rasterizer.h" +#include "font/TextShaper.h" #include "Texture.h" #include "vertex.h" #include "Volatile.h" @@ -64,30 +65,6 @@ public: ALIGN_MAX_ENUM }; - struct ColoredString - { - std::string str; - Colorf color; - }; - - struct IndexedColor - { - Colorf color; - int index; - }; - - struct ColoredCodepoints - { - std::vector cps; - std::vector colors; - }; - - struct TextInfo - { - int width; - int height; - }; - // Used to determine when to change textures in the generated vertex array. struct DrawCommand { @@ -100,20 +77,17 @@ public: virtual ~Font(); - std::vector generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantColor, std::vector &vertices, - float extra_spacing = 0.0f, Vector2 offset = {}, TextInfo *info = nullptr); + std::vector generateVertices(const love::font::ColoredCodepoints &codepoints, Range range, const Colorf &constantColor, std::vector &vertices, + float extra_spacing = 0.0f, Vector2 offset = {}, love::font::TextShaper::TextInfo *info = nullptr); - std::vector generateVerticesFormatted(const ColoredCodepoints &text, const Colorf &constantColor, float wrap, AlignMode align, - std::vector &vertices, TextInfo *info = nullptr); - - static void getCodepointsFromString(const std::string &str, Codepoints &codepoints); - static void getCodepointsFromString(const std::vector &strs, ColoredCodepoints &codepoints); + std::vector generateVerticesFormatted(const love::font::ColoredCodepoints &text, const Colorf &constantColor, float wrap, AlignMode align, + std::vector &vertices, love::font::TextShaper::TextInfo *info = nullptr); /** * Draws the specified text. **/ - void print(graphics::Graphics *gfx, const std::vector &text, const Matrix4 &m, const Colorf &constantColor); - void printf(graphics::Graphics *gfx, const std::vector &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantColor); + void print(graphics::Graphics *gfx, const std::vector &text, const Matrix4 &m, const Colorf &constantColor); + void printf(graphics::Graphics *gfx, const std::vector &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantColor); /** * Returns the height of the font. @@ -141,8 +115,8 @@ public: * @param max_width Optional output of the maximum width * Returns a vector with the lines. **/ - void getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *line_widths = nullptr); - void getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector &lines, std::vector *line_widths = nullptr); + void getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *line_widths = nullptr); + void getWrap(const love::font::ColoredCodepoints &codepoints, float wraplimit, std::vector &ranges, std::vector *line_widths = nullptr); /** * Sets the line height (which should be a number to multiply the font size by, @@ -191,7 +165,6 @@ private: struct Glyph { Texture *texture; - int spacing; GlyphVertex vertices[4]; }; @@ -204,26 +177,20 @@ private: void createTexture(); TextureSize getNextTextureSize() const; - love::font::GlyphData *getRasterizerGlyphData(uint32 glyph, float &dpiscale); - const Glyph &addGlyph(uint32 glyph); - const Glyph &findGlyph(uint32 glyph); + love::font::GlyphData *getRasterizerGlyphData(love::font::TextShaper::GlyphIndex glyphindex, float &dpiscale); + const Glyph &addGlyph(love::font::TextShaper::GlyphIndex glyphindex); + const Glyph &findGlyph(love::font::TextShaper::GlyphIndex glyphindex); void printv(Graphics *gfx, const Matrix4 &t, const std::vector &drawcommands, const std::vector &vertices); - std::vector> rasterizers; - - int height; - float lineHeight; + StrongRef shaper; int textureWidth; int textureHeight; - std::vector> textures; + std::vector> textures; - // maps glyphs to glyph texture information - std::unordered_map glyphs; - - // map of left/right glyph pairs to horizontal kerning. - std::unordered_map kerning; + // maps packed glyph index values to glyph texture information + std::unordered_map glyphs; PixelFormat pixelFormat; diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 9c9f71171..dcebc1cb4 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -439,7 +439,7 @@ Mesh *Graphics::newMesh(const std::vector &attributes, Pr return new Mesh(attributes, drawmode); } -love::graphics::TextBatch *Graphics::newTextBatch(graphics::Font *font, const std::vector &text) +love::graphics::TextBatch *Graphics::newTextBatch(graphics::Font *font, const std::vector &text) { return new TextBatch(font, text); } @@ -1893,7 +1893,7 @@ void Graphics::drawShaderVertices(Buffer *indexbuffer, int indexcount, int insta draw(cmd); } -void Graphics::print(const std::vector &str, const Matrix4 &m) +void Graphics::print(const std::vector &str, const Matrix4 &m) { checkSetDefaultFont(); @@ -1901,12 +1901,12 @@ void Graphics::print(const std::vector &str, const Matrix4 print(str, states.back().font.get(), m); } -void Graphics::print(const std::vector &str, Font *font, const Matrix4 &m) +void Graphics::print(const std::vector &str, Font *font, const Matrix4 &m) { font->print(this, str, m, states.back().color); } -void Graphics::printf(const std::vector &str, float wrap, Font::AlignMode align, const Matrix4 &m) +void Graphics::printf(const std::vector &str, float wrap, Font::AlignMode align, const Matrix4 &m) { checkSetDefaultFont(); @@ -1914,7 +1914,7 @@ void Graphics::printf(const std::vector &str, float wrap, F printf(str, states.back().font.get(), wrap, align, m); } -void Graphics::printf(const std::vector &str, Font *font, float wrap, Font::AlignMode align, const Matrix4 &m) +void Graphics::printf(const std::vector &str, Font *font, float wrap, Font::AlignMode align, const Matrix4 &m) { font->printf(this, str, wrap, align, m, states.back().color); } diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index e99aaed11..d53471981 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -460,7 +460,7 @@ public: Mesh *newMesh(const std::vector &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferDataUsage usage); Mesh *newMesh(const std::vector &attributes, PrimitiveType drawmode); - TextBatch *newTextBatch(Font *font, const std::vector &text = {}); + TextBatch *newTextBatch(Font *font, const std::vector &text = {}); data::ByteData *readbackBuffer(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset); GraphicsReadback *readbackBufferAsync(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset); @@ -702,14 +702,14 @@ public: /** * Draws text at the specified coordinates **/ - void print(const std::vector &str, const Matrix4 &m); - void print(const std::vector &str, Font *font, const Matrix4 &m); + void print(const std::vector &str, const Matrix4 &m); + void print(const std::vector &str, Font *font, const Matrix4 &m); /** * Draws formatted text on screen at the specified coordinates. **/ - void printf(const std::vector &str, float wrap, Font::AlignMode align, const Matrix4 &m); - void printf(const std::vector &str, Font *font, float wrap, Font::AlignMode align, const Matrix4 &m); + void printf(const std::vector &str, float wrap, Font::AlignMode align, const Matrix4 &m); + void printf(const std::vector &str, Font *font, float wrap, Font::AlignMode align, const Matrix4 &m); /** * Draws a series of points at the specified positions. diff --git a/src/modules/graphics/TextBatch.cpp b/src/modules/graphics/TextBatch.cpp index 23a17b5ad..362c32ed1 100644 --- a/src/modules/graphics/TextBatch.cpp +++ b/src/modules/graphics/TextBatch.cpp @@ -30,7 +30,7 @@ namespace graphics love::Type TextBatch::type("TextBatch", &Drawable::type); -TextBatch::TextBatch(Font *font, const std::vector &text) +TextBatch::TextBatch(Font *font, const std::vector &text) : font(font) , vertexAttributes(Font::vertexFormat, 0) , vertexData(nullptr) @@ -112,13 +112,13 @@ void TextBatch::addTextData(const TextData &t) std::vector vertices; std::vector newcommands; - Font::TextInfo textinfo; + love::font::TextShaper::TextInfo textinfo; Colorf constantcolor = Colorf(1.0f, 1.0f, 1.0f, 1.0f); // We only have formatted text if the align mode is valid. if (t.align == Font::ALIGN_MAX_ENUM) - newcommands = font->generateVertices(t.codepoints, constantcolor, vertices, 0.0f, Vector2(0.0f, 0.0f), &textinfo); + newcommands = font->generateVertices(t.codepoints, Range(), constantcolor, vertices, 0.0f, Vector2(0.0f, 0.0f), &textinfo); else newcommands = font->generateVerticesFormatted(t.codepoints, constantcolor, t.wrap, t.align, vertices, &textinfo); @@ -172,31 +172,31 @@ void TextBatch::addTextData(const TextData &t) regenerateVertices(); } -void TextBatch::set(const std::vector &text) +void TextBatch::set(const std::vector &text) { return set(text, -1.0f, Font::ALIGN_MAX_ENUM); } -void TextBatch::set(const std::vector &text, float wrap, Font::AlignMode align) +void TextBatch::set(const std::vector &text, float wrap, Font::AlignMode align) { if (text.empty() || (text.size() == 1 && text[0].str.empty())) return clear(); - Font::ColoredCodepoints codepoints; - Font::getCodepointsFromString(text, codepoints); + love::font::ColoredCodepoints codepoints; + love::font::getCodepointsFromString(text, codepoints); addTextData({codepoints, wrap, align, {}, false, false, Matrix4()}); } -int TextBatch::add(const std::vector &text, const Matrix4 &m) +int TextBatch::add(const std::vector &text, const Matrix4 &m) { return addf(text, -1.0f, Font::ALIGN_MAX_ENUM, m); } -int TextBatch::addf(const std::vector &text, float wrap, Font::AlignMode align, const Matrix4 &m) +int TextBatch::addf(const std::vector &text, float wrap, Font::AlignMode align, const Matrix4 &m) { - Font::ColoredCodepoints codepoints; - Font::getCodepointsFromString(text, codepoints); + love::font::ColoredCodepoints codepoints; + love::font::getCodepointsFromString(text, codepoints); addTextData({codepoints, wrap, align, {}, true, true, m}); diff --git a/src/modules/graphics/TextBatch.h b/src/modules/graphics/TextBatch.h index ec466827c..f9a61fff1 100644 --- a/src/modules/graphics/TextBatch.h +++ b/src/modules/graphics/TextBatch.h @@ -40,14 +40,14 @@ public: static love::Type type; - TextBatch(Font *font, const std::vector &text = {}); + TextBatch(Font *font, const std::vector &text = {}); virtual ~TextBatch(); - void set(const std::vector &text); - void set(const std::vector &text, float wrap, Font::AlignMode align); + void set(const std::vector &text); + void set(const std::vector &text, float wrap, Font::AlignMode align); - int add(const std::vector &text, const Matrix4 &m); - int addf(const std::vector &text, float wrap, Font::AlignMode align, const Matrix4 &m); + int add(const std::vector &text, const Matrix4 &m); + int addf(const std::vector &text, float wrap, Font::AlignMode align, const Matrix4 &m); void clear(); @@ -71,10 +71,10 @@ private: struct TextData { - Font::ColoredCodepoints codepoints; + love::font::ColoredCodepoints codepoints; float wrap; Font::AlignMode align; - Font::TextInfo textInfo; + love::font::TextShaper::TextInfo textInfo; bool useMatrix; bool appendVertices; Matrix4 matrix; diff --git a/src/modules/graphics/wrap_Font.cpp b/src/modules/graphics/wrap_Font.cpp index d59a116b6..425453b92 100644 --- a/src/modules/graphics/wrap_Font.cpp +++ b/src/modules/graphics/wrap_Font.cpp @@ -30,9 +30,9 @@ namespace love namespace graphics { -void luax_checkcoloredstring(lua_State *L, int idx, std::vector &strings) +void luax_checkcoloredstring(lua_State *L, int idx, std::vector &strings) { - Font::ColoredString coloredstr; + love::font::ColoredString coloredstr; coloredstr.color = Colorf(1.0f, 1.0f, 1.0f, 1.0f); if (lua_istable(L, idx)) @@ -103,7 +103,7 @@ int w_Font_getWrap(lua_State *L) { Font *t = luax_checkfont(L, 1); - std::vector text; + std::vector text; luax_checkcoloredstring(L, 2, text); float wrap = (float) luaL_checknumber(L, 3); diff --git a/src/modules/graphics/wrap_Font.h b/src/modules/graphics/wrap_Font.h index c97cb0eda..3ed11708a 100644 --- a/src/modules/graphics/wrap_Font.h +++ b/src/modules/graphics/wrap_Font.h @@ -30,7 +30,7 @@ namespace graphics { Font *luax_checkfont(lua_State *L, int idx); -void luax_checkcoloredstring(lua_State *L, int idx, std::vector &strings); +void luax_checkcoloredstring(lua_State *L, int idx, std::vector &strings); extern "C" int luaopen_font(lua_State *L); } // graphics diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index 76d6a3e64..169cc853b 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -2112,7 +2112,7 @@ int w_newTextBatch(lua_State *L) luax_catchexcept(L, [&](){ t = instance()->newTextBatch(font); }); else { - std::vector text; + std::vector text; luax_checkcoloredstring(L, 2, text); luax_catchexcept(L, [&](){ t = instance()->newTextBatch(font, text); }); @@ -3132,7 +3132,7 @@ int w_drawShaderVertices(lua_State *L) int w_print(lua_State *L) { - std::vector str; + std::vector str; luax_checkcoloredstring(L, 1, str); if (luax_istype(L, 2, Font::type)) @@ -3157,7 +3157,7 @@ int w_print(lua_State *L) int w_printf(lua_State *L) { - std::vector str; + std::vector str; luax_checkcoloredstring(L, 1, str); Font *font = nullptr; diff --git a/src/modules/graphics/wrap_TextBatch.cpp b/src/modules/graphics/wrap_TextBatch.cpp index d49f12602..16d57378c 100644 --- a/src/modules/graphics/wrap_TextBatch.cpp +++ b/src/modules/graphics/wrap_TextBatch.cpp @@ -36,7 +36,7 @@ int w_TextBatch_set(lua_State *L) { TextBatch *t = luax_checktextbatch(L, 1); - std::vector newtext; + std::vector newtext; luax_checkcoloredstring(L, 2, newtext); luax_catchexcept(L, [&](){ t->set(newtext); }); @@ -54,7 +54,7 @@ int w_TextBatch_setf(lua_State *L) if (!Font::getConstant(alignstr, align)) return luax_enumerror(L, "align mode", Font::getConstants(align), alignstr); - std::vector newtext; + std::vector newtext; luax_checkcoloredstring(L, 2, newtext); luax_catchexcept(L, [&](){ t->set(newtext, wraplimit, align); }); @@ -68,7 +68,7 @@ int w_TextBatch_add(lua_State *L) int index = 0; - std::vector text; + std::vector text; luax_checkcoloredstring(L, 2, text); if (luax_istype(L, 3, math::Transform::type)) @@ -102,7 +102,7 @@ int w_TextBatch_addf(lua_State *L) int index = 0; - std::vector text; + std::vector text; luax_checkcoloredstring(L, 2, text); float wrap = (float) luaL_checknumber(L, 3); From 9502942b7b5764e49dfa0129b9077cadb7580c4a Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Wed, 28 Dec 2022 19:46:48 -0400 Subject: [PATCH 02/11] font rendering: reimplement tab-as-spaces support. --- src/modules/font/GenericShaper.cpp | 6 +- src/modules/font/TextShaper.cpp | 46 +++++--- src/modules/font/TextShaper.h | 8 ++ src/modules/font/freetype/HarfbuzzShaper.cpp | 118 ++++++++++++++----- src/modules/font/freetype/HarfbuzzShaper.h | 8 ++ src/modules/graphics/Font.cpp | 23 ---- src/modules/graphics/Font.h | 5 - 7 files changed, 136 insertions(+), 78 deletions(-) diff --git a/src/modules/font/GenericShaper.cpp b/src/modules/font/GenericShaper.cpp index 74d1b50d4..d89b11cfa 100644 --- a/src/modules/font/GenericShaper.cpp +++ b/src/modules/font/GenericShaper.cpp @@ -167,7 +167,7 @@ int GenericShaper::computeWordWrapIndex(const ColoredCodepoints& codepoints, Ran float newwidth = w + getKerning(prevglyph, g) + getGlyphAdvance(g); // 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. if (lastspaceindex != -1) @@ -179,10 +179,10 @@ int GenericShaper::computeWordWrapIndex(const ColoredCodepoints& codepoints, Ran } // Don't count trailing spaces in the output width. - if (g == ' ') + if (isWhitespace(g)) { lastspaceindex = i; - if (prevglyph != ' ') + if (!isWhitespace(prevglyph)) widthbeforelastspace = w; } else diff --git a/src/modules/font/TextShaper.cpp b/src/modules/font/TextShaper.cpp index 0b384081d..8d78b2ac3 100644 --- a/src/modules/font/TextShaper.cpp +++ b/src/modules/font/TextShaper.cpp @@ -30,7 +30,7 @@ namespace love namespace font { -void getCodepointsFromString(const std::string& text, std::vector& codepoints) +void getCodepointsFromString(const std::string &text, std::vector &codepoints) { codepoints.reserve(text.size()); @@ -45,20 +45,20 @@ void getCodepointsFromString(const std::string& text, std::vector& codep codepoints.push_back(g); } } - catch (utf8::exception& e) + catch (utf8::exception &e) { throw love::Exception("UTF-8 decoding error: %s", e.what()); } } -void getCodepointsFromString(const std::vector& strs, ColoredCodepoints& codepoints) +void getCodepointsFromString(const std::vector &strs, ColoredCodepoints &codepoints) { if (strs.empty()) return; 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 // further on assumes no two colors share the same starting position. @@ -87,7 +87,10 @@ TextShaper::TextShaper(Rasterizer *rasterizer) , dpiScales{rasterizer->getDPIScale()} , height(floorf(rasterizer->getHeight() / rasterizer->getDPIScale() + 0.5f)) , lineHeight(1) + , useSpacesForTab(false) { + if (!rasterizer->hasGlyph('\t')) + useSpacesForTab = true; } TextShaper::~TextShaper() @@ -141,7 +144,7 @@ bool TextShaper::hasGlyph(uint32 glyph) const return false; } -bool TextShaper::hasGlyphs(const std::string& text) const +bool TextShaper::hasGlyphs(const std::string &text) const { if (text.size() == 0) return false; @@ -178,7 +181,7 @@ float TextShaper::getKerning(uint32 leftglyph, uint32 rightglyph) float k = 0.0f; bool found = false; - for (const auto& r : rasterizers) + for (const auto &r : rasterizers) { if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph)) { @@ -195,7 +198,7 @@ float TextShaper::getKerning(uint32 leftglyph, uint32 rightglyph) 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 right = 0; @@ -215,8 +218,6 @@ float TextShaper::getKerning(const std::string& leftchar, const std::string& rig int TextShaper::getGlyphAdvance(uint32 glyph, GlyphIndex *glyphindex) { - // TODO: use spaces for tab - const auto it = glyphAdvances.find(glyph); if (it != glyphAdvances.end()) { @@ -226,10 +227,14 @@ int TextShaper::getGlyphAdvance(uint32 glyph, GlyphIndex *glyphindex) } int rasterizeri = 0; + uint32 realglyph = glyph; + + if (glyph == '\t' && isUsingSpacesForTab()) + realglyph = ' '; for (size_t i = 0; i < rasterizers.size(); i++) { - if (rasterizers[i]->hasGlyph(glyph)) + if (rasterizers[i]->hasGlyph(realglyph)) { rasterizeri = (int) i; break; @@ -237,9 +242,12 @@ int TextShaper::getGlyphAdvance(uint32 glyph, GlyphIndex *glyphindex) } 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); if (glyphindex) @@ -260,7 +268,7 @@ int TextShaper::getWidth(const std::string &str) 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++) { @@ -273,7 +281,7 @@ static size_t findNewline(const ColoredCodepoints& codepoints, size_t start) return codepoints.cps.size(); } -void TextShaper::getWrap(const ColoredCodepoints& codepoints, float wraplimit, std::vector& lineranges, std::vector* linewidths) +void TextShaper::getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector &lineranges, std::vector *linewidths) { 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& text, float wraplimit, std::vector& lines, std::vector* linewidths) +void TextShaper::getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *linewidths) { ColoredCodepoints cps; getCodepointsFromString(text, cps); @@ -327,7 +335,7 @@ void TextShaper::getWrap(const std::vector& text, float wraplimit std::string line; - for (const auto& range : codepointranges) + for (const auto &range : codepointranges) { line.clear(); @@ -347,9 +355,9 @@ void TextShaper::getWrap(const std::vector& text, float wraplimit } } -void TextShaper::setFallbacks(const std::vector& fallbacks) +void TextShaper::setFallbacks(const std::vector &fallbacks) { - for (Rasterizer* r : fallbacks) + for (Rasterizer *r : fallbacks) { if (r->getDataType() != rasterizers[0]->getDataType()) throw love::Exception("Font fallbacks must be of the same font type."); @@ -362,7 +370,7 @@ void TextShaper::setFallbacks(const std::vector& fallbacks) rasterizers.resize(1); dpiScales.resize(1); - for (Rasterizer* r : fallbacks) + for (Rasterizer *r : fallbacks) { rasterizers.push_back(r); dpiScales.push_back(r->getDPIScale()); diff --git a/src/modules/font/TextShaper.h b/src/modules/font/TextShaper.h index 72b92653b..72e0425e9 100644 --- a/src/modules/font/TextShaper.h +++ b/src/modules/font/TextShaper.h @@ -81,11 +81,15 @@ public: 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> &getRasterizers() const { return rasterizers; } + bool isUsingSpacesForTab() const { return useSpacesForTab; } float getHeight() const; @@ -128,6 +132,8 @@ protected: TextShaper(Rasterizer *rasterizer); + static inline bool isWhitespace(uint32 codepoint) { return codepoint == ' ' || codepoint == '\t'; } + std::vector> rasterizers; std::vector dpiScales; @@ -136,6 +142,8 @@ private: int height; float lineHeight; + bool useSpacesForTab; + // maps glyphs to advance and glyph+rasterizer index. std::unordered_map> glyphAdvances; diff --git a/src/modules/font/freetype/HarfbuzzShaper.cpp b/src/modules/font/freetype/HarfbuzzShaper.cpp index 8f0b0037d..e8b73b169 100644 --- a/src/modules/font/freetype/HarfbuzzShaper.cpp +++ b/src/modules/font/freetype/HarfbuzzShaper.cpp @@ -36,30 +36,70 @@ 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(""); + 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& fallbacks) +void HarfbuzzShaper::setFallbacks(const std::vector &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]); + } TextShaper::setFallbacks(fallbacks); 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()); + 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 *positions, std::vector *colors, TextInfo *info) @@ -70,18 +110,21 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, offset.y += getBaseline(); 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()); // TODO: Expose APIs for direction and script? hb_buffer_guess_segment_properties(hbbuffer); + hb_direction_t direction = hb_buffer_get_direction(hbbuffer); + hb_shape(hbFonts[0], hbbuffer, nullptr, 0); int glyphcount = (int) hb_buffer_get_length(hbbuffer); - 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_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbbuffer, nullptr); + hb_glyph_position_t *glyphpositions = hb_buffer_get_glyph_positions(hbbuffer, nullptr); if (positions) positions->reserve(positions->size() + glyphcount); @@ -93,7 +136,7 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, for (int i = 0; i < glyphcount; i++) { 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) 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++) { - 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; // TODO: fallbacks - // TODO: use spaces for tab for (int i = 0; i < glyphcount; i++) { - const hb_glyph_info_t& info = glyphinfos[i]; - const hb_glyph_position_t& glyphpos = glyphpositions[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 @@ -166,6 +206,21 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, if (clustercodepoint == '\r') 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) { IndexedColor c = {colorToAdd.value, positions->size()}; @@ -175,8 +230,6 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, if (positions) { - // Despite the name this is a glyph index at this point. - GlyphIndex gindex = { info.codepoint, 0 }; GlyphPosition p = { curpos, gindex }; // 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); } - hb_buffer_destroy(hbbuffer); - if (curpos.x > maxwidth) 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()) 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()); // TODO: Expose APIs for direction and script? hb_buffer_guess_segment_properties(hbbuffer); + hb_direction_t direction = hb_buffer_get_direction(hbbuffer); + hb_shape(hbFonts[0], hbbuffer, nullptr, 0); int glyphcount = (int)hb_buffer_get_length(hbbuffer); - 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_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbbuffer, nullptr); + hb_glyph_position_t *glyphpositions = hb_buffer_get_glyph_positions(hbbuffer, nullptr); float w = 0.0f; float outwidth = 0.0f; @@ -237,8 +291,8 @@ int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints& codepoints, Ra for (int i = 0; i < glyphcount; i++) { - const hb_glyph_info_t& info = glyphinfos[i]; - const hb_glyph_position_t& glyphpos = glyphpositions[i]; + const hb_glyph_info_t &info = glyphinfos[i]; + hb_glyph_position_t &glyphpos = glyphpositions[i]; uint32 clustercodepoint = codepoints.cps[info.cluster]; @@ -248,10 +302,20 @@ int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints& codepoints, Ra 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 && clustercodepoint != ' ') + if (newwidth > wraplimit && !isWhitespace(clustercodepoint)) { // Rewind to the last seen space when wrapping. if (lastspaceindex != -1) @@ -263,10 +327,10 @@ int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints& codepoints, Ra } // Don't count trailing spaces in the output width. - if (clustercodepoint == ' ') + if (isWhitespace(clustercodepoint)) { lastspaceindex = info.cluster; - if (prevcodepoint != ' ') + if (!isWhitespace(prevcodepoint)) widthbeforelastspace = w; } else @@ -277,8 +341,6 @@ int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints& codepoints, Ra wrapindex = info.cluster; } - hb_buffer_destroy(hbbuffer); - if (width) *width = outwidth; diff --git a/src/modules/font/freetype/HarfbuzzShaper.h b/src/modules/font/freetype/HarfbuzzShaper.h index f26d362c7..33d52fd9b 100644 --- a/src/modules/font/freetype/HarfbuzzShaper.h +++ b/src/modules/font/freetype/HarfbuzzShaper.h @@ -26,6 +26,7 @@ extern "C" { typedef struct hb_font_t hb_font_t; +typedef struct hb_buffer_t hb_buffer_t; } namespace love @@ -50,7 +51,14 @@ public: private: + void updateSpacesForTabInfo(); + std::vector hbFonts; + std::vector hbBuffers; + + GlyphIndex spaceGlyphIndex; + int tabSpacesAdvanceX; + int tabSpacesAdvanceY; }; // HarfbuzzShaper diff --git a/src/modules/graphics/Font.cpp b/src/modules/graphics/Font.cpp index 9785532aa..be55cc070 100644 --- a/src/modules/graphics/Font.cpp +++ b/src/modules/graphics/Font.cpp @@ -61,7 +61,6 @@ Font::Font(love::font::Rasterizer *r, const SamplerState &s) , textureHeight(128) , samplerState() , dpiScale(r->getDPIScale()) - , useSpacesAsTab(false) , textureCacheID(0) { 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)) pixelFormat = PIXELFORMAT_RGBA8_UNORM; - uint32 tab = '\t'; - if (!r->hasGlyph(tab)) // No tab character in the Rasterizer. - useSpacesAsTab = true; - loadVolatile(); ++fontCount; } @@ -232,24 +227,6 @@ void Font::unloadVolatile() love::font::GlyphData *Font::getRasterizerGlyphData(love::font::TextShaper::GlyphIndex glyphindex, float &dpiscale) { 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(); return r->getGlyphDataForIndex(glyphindex.index); } diff --git a/src/modules/graphics/Font.h b/src/modules/graphics/Font.h index f3cc0afd4..67792da11 100644 --- a/src/modules/graphics/Font.h +++ b/src/modules/graphics/Font.h @@ -201,8 +201,6 @@ private: int textureX, textureY; int rowHeight; - bool useSpacesAsTab; - // ID which is incremented when the texture cache is invalidated. uint32 textureCacheID; @@ -211,9 +209,6 @@ private: // use, for edge antialiasing. 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::Entry alignModeEntries[]; static StringMap alignModes; From 4ace4bc3b6f032aaa6c798eeea517779b59a1679 Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Sat, 31 Dec 2022 21:45:12 -0400 Subject: [PATCH 03/11] Initial fallback font support for Harfbuzz text shaping. --- src/modules/font/TextShaper.h | 2 +- src/modules/font/freetype/HarfbuzzShaper.cpp | 393 +++++++++++-------- src/modules/font/freetype/HarfbuzzShaper.h | 9 + 3 files changed, 235 insertions(+), 169 deletions(-) diff --git a/src/modules/font/TextShaper.h b/src/modules/font/TextShaper.h index 72e0425e9..266c8bd25 100644 --- a/src/modules/font/TextShaper.h +++ b/src/modules/font/TextShaper.h @@ -120,7 +120,7 @@ public: int getWidth(const std::string &str); - void getWrap(const std::vector& text, float wraplimit, std::vector& lines, std::vector* line_widths = nullptr); + void getWrap(const std::vector& text, float wraplimit, std::vector& lines, std::vector* linewidths = nullptr); void getWrap(const ColoredCodepoints& codepoints, float wraplimit, std::vector &lineranges, std::vector *linewidths = nullptr); virtual void setFallbacks(const std::vector &fallbacks); diff --git a/src/modules/font/freetype/HarfbuzzShaper.cpp b/src/modules/font/freetype/HarfbuzzShaper.cpp index e8b73b169..b9d0bc5d9 100644 --- a/src/modules/font/freetype/HarfbuzzShaper.cpp +++ b/src/modules/font/freetype/HarfbuzzShaper.cpp @@ -102,6 +102,97 @@ void HarfbuzzShaper::updateSpacesForTabInfo() } } +bool HarfbuzzShaper::isValidGlyph(uint32 glyphindex, const std::vector& 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 &bufferranges) +{ + bufferranges.clear(); + + // 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 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); + + 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() != 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() != i) + bufferranges.push_back({0, (int)glyphinfos[i].cluster, Range(i, 1)}); + else + bufferranges.back().range.last++; + } + else + { + if (fallbackranges.empty() || 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 *positions, std::vector *colors, TextInfo *info) { if (!range.isValid()) @@ -110,50 +201,6 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, offset.y += getBaseline(); Vector2 curpos = offset; - 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()); - - // TODO: Expose APIs for direction and script? - hb_buffer_guess_segment_properties(hbbuffer); - - hb_direction_t direction = hb_buffer_get_direction(hbbuffer); - - hb_shape(hbFonts[0], hbbuffer, nullptr, 0); - - int glyphcount = (int) hb_buffer_get_length(hbbuffer); - hb_glyph_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbbuffer, nullptr); - hb_glyph_position_t *glyphpositions = hb_buffer_get_glyph_positions(hbbuffer, nullptr); - - if (positions) - positions->reserve(positions->size() + glyphcount); - - if (rasterizers.size() > 1) - { - std::vector fallbackranges; - - for (int i = 0; i < glyphcount; i++) - { - bool adding = false; - if (glyphinfos[i].codepoint == 0 && (codepoints.cps[glyphinfos[i].cluster] != '\t' || !isUsingSpacesForTab())) - { - if (fallbackranges.empty() || fallbackranges.back().getMax() != glyphinfos[i-1].cluster) - fallbackranges.push_back(Range(glyphinfos[i].cluster, 1)); - else - fallbackranges.back().encapsulate(glyphinfos[i].cluster); - } - } - - if (!fallbackranges.empty()) - { - for (size_t rasti = 1; rasti < rasterizers.size(); rasti++) - { - - } - } - } - int colorindex = 0; int ncolors = (int)codepoints.colors.size(); Optional colorToAdd; @@ -170,82 +217,96 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, } } + std::vector bufferranges; + computeBufferRanges(codepoints, range, bufferranges); + int maxwidth = (int)curpos.x; - // TODO: fallbacks - for (int i = 0; i < glyphcount; i++) + for (const auto &bufferrange : bufferranges) { - 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 = { 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) - { - IndexedColor c = {colorToAdd.value, positions->size()}; - colors->push_back(c); - colorToAdd.clear(); - } - 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++) { - GlyphPosition p = { curpos, gindex }; + const hb_glyph_info_t &info = glyphinfos[i]; + hb_glyph_position_t &glyphpos = glyphpositions[i]; - // 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); + // 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++; + } - positions->push_back(p); + 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 = { 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, 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); } - - 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) @@ -265,22 +326,6 @@ int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints &codepoints, Ra if (!range.isValid()) range = Range(0, codepoints.cps.size()); - 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()); - - // TODO: Expose APIs for direction and script? - hb_buffer_guess_segment_properties(hbbuffer); - - hb_direction_t direction = hb_buffer_get_direction(hbbuffer); - - hb_shape(hbFonts[0], hbbuffer, nullptr, 0); - - int glyphcount = (int)hb_buffer_get_length(hbbuffer); - hb_glyph_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbbuffer, nullptr); - hb_glyph_position_t *glyphpositions = hb_buffer_get_glyph_positions(hbbuffer, nullptr); - float w = 0.0f; float outwidth = 0.0f; float widthbeforelastspace = 0.0f; @@ -289,56 +334,68 @@ int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints &codepoints, Ra uint32 prevcodepoint = 0; - for (int i = 0; i < glyphcount; i++) + std::vector bufferranges; + computeBufferRanges(codepoints, range, bufferranges); + + for (const auto &bufferrange : bufferranges) { - const hb_glyph_info_t &info = glyphinfos[i]; - hb_glyph_position_t &glyphpos = glyphpositions[i]; + hb_buffer_t* hbbuffer = hbBuffers[bufferrange.index]; - uint32 clustercodepoint = codepoints.cps[info.cluster]; + 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); - if (clustercodepoint == '\r') + for (size_t i = bufferrange.range.first; i <= bufferrange.range.last; i++) { - prevcodepoint = clustercodepoint; - continue; - } + const hb_glyph_info_t &info = glyphinfos[i]; + hb_glyph_position_t &glyphpos = glyphpositions[i]; - 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; - } + uint32 clustercodepoint = codepoints.cps[info.cluster]; - 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) + if (clustercodepoint == '\r') { - wrapindex = lastspaceindex; - outwidth = widthbeforelastspace; + prevcodepoint = clustercodepoint; + continue; } - break; - } - // Don't count trailing spaces in the output width. - if (isWhitespace(clustercodepoint)) - { - lastspaceindex = info.cluster; - if (!isWhitespace(prevcodepoint)) - widthbeforelastspace = w; - } - else - outwidth = newwidth; + 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; + } - w = newwidth; - prevcodepoint = clustercodepoint; - wrapindex = info.cluster; + 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) diff --git a/src/modules/font/freetype/HarfbuzzShaper.h b/src/modules/font/freetype/HarfbuzzShaper.h index 33d52fd9b..ea4453d61 100644 --- a/src/modules/font/freetype/HarfbuzzShaper.h +++ b/src/modules/font/freetype/HarfbuzzShaper.h @@ -51,7 +51,16 @@ public: private: + struct BufferRange + { + int index; + int codepointStart; + Range range; + }; + void updateSpacesForTabInfo(); + bool isValidGlyph(uint32 glyphindex, const std::vector& codepoints, uint32 codepointindex); + void computeBufferRanges(const ColoredCodepoints& codepoints, Range range, std::vector &bufferranges); std::vector hbFonts; std::vector hbBuffers; From 6f14a18da0593382040e0840ed720e7715687016 Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Sun, 1 Jan 2023 12:20:01 -0400 Subject: [PATCH 04/11] Update copyright year in TextShaper files --- src/modules/font/GenericShaper.cpp | 2 +- src/modules/font/GenericShaper.h | 2 +- src/modules/font/TextShaper.cpp | 2 +- src/modules/font/TextShaper.h | 2 +- src/modules/font/freetype/HarfbuzzShaper.cpp | 2 +- src/modules/font/freetype/HarfbuzzShaper.h | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/modules/font/GenericShaper.cpp b/src/modules/font/GenericShaper.cpp index d89b11cfa..7665d822d 100644 --- a/src/modules/font/GenericShaper.cpp +++ b/src/modules/font/GenericShaper.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2006-2022 LOVE Development Team + * 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 diff --git a/src/modules/font/GenericShaper.h b/src/modules/font/GenericShaper.h index 41c2982e7..a9f4d6526 100644 --- a/src/modules/font/GenericShaper.h +++ b/src/modules/font/GenericShaper.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2006-2022 LOVE Development Team + * 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 diff --git a/src/modules/font/TextShaper.cpp b/src/modules/font/TextShaper.cpp index 8d78b2ac3..404e06ff5 100644 --- a/src/modules/font/TextShaper.cpp +++ b/src/modules/font/TextShaper.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2006-2022 LOVE Development Team + * 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 diff --git a/src/modules/font/TextShaper.h b/src/modules/font/TextShaper.h index 266c8bd25..b7609cebd 100644 --- a/src/modules/font/TextShaper.h +++ b/src/modules/font/TextShaper.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2006-2022 LOVE Development Team + * 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 diff --git a/src/modules/font/freetype/HarfbuzzShaper.cpp b/src/modules/font/freetype/HarfbuzzShaper.cpp index b9d0bc5d9..48023bf40 100644 --- a/src/modules/font/freetype/HarfbuzzShaper.cpp +++ b/src/modules/font/freetype/HarfbuzzShaper.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2006-2022 LOVE Development Team + * 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 diff --git a/src/modules/font/freetype/HarfbuzzShaper.h b/src/modules/font/freetype/HarfbuzzShaper.h index ea4453d61..19ed2ec79 100644 --- a/src/modules/font/freetype/HarfbuzzShaper.h +++ b/src/modules/font/freetype/HarfbuzzShaper.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2006-2022 LOVE Development Team + * 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 From 616ed0e7f888cdb65ee7fc338d2717fbea396d45 Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Sun, 1 Jan 2023 12:52:38 -0400 Subject: [PATCH 05/11] Fix compile errors with some compilers. --- src/modules/font/GenericShaper.cpp | 2 +- src/modules/font/freetype/HarfbuzzShaper.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/font/GenericShaper.cpp b/src/modules/font/GenericShaper.cpp index 7665d822d..1d8962b3f 100644 --- a/src/modules/font/GenericShaper.cpp +++ b/src/modules/font/GenericShaper.cpp @@ -105,7 +105,7 @@ void GenericShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, R if (colorToAdd.hasValue && colors && positions) { - IndexedColor c = {colorToAdd.value, positions->size()}; + IndexedColor c = {colorToAdd.value, (int) positions->size()}; colors->push_back(c); colorToAdd.clear(); } diff --git a/src/modules/font/freetype/HarfbuzzShaper.cpp b/src/modules/font/freetype/HarfbuzzShaper.cpp index 48023bf40..bd25dc040 100644 --- a/src/modules/font/freetype/HarfbuzzShaper.cpp +++ b/src/modules/font/freetype/HarfbuzzShaper.cpp @@ -283,7 +283,7 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, if (colorToAdd.hasValue && colors && positions) { - IndexedColor c = {colorToAdd.value, positions->size()}; + IndexedColor c = {colorToAdd.value, (int) positions->size()}; colors->push_back(c); colorToAdd.clear(); } From de2b986787be7d24660f7673180aba61ffafbf2b Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Sun, 1 Jan 2023 15:07:37 -0400 Subject: [PATCH 06/11] Code style fixes --- src/modules/font/GenericShaper.cpp | 2 +- src/modules/font/GenericShaper.h | 2 +- src/modules/font/TextShaper.cpp | 8 ++++---- src/modules/font/TextShaper.h | 12 ++++++------ src/modules/font/freetype/HarfbuzzShaper.cpp | 14 +++++++------- src/modules/font/freetype/HarfbuzzShaper.h | 8 ++++---- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/modules/font/GenericShaper.cpp b/src/modules/font/GenericShaper.cpp index 1d8962b3f..ef5854602 100644 --- a/src/modules/font/GenericShaper.cpp +++ b/src/modules/font/GenericShaper.cpp @@ -141,7 +141,7 @@ void GenericShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, R } } -int GenericShaper::computeWordWrapIndex(const ColoredCodepoints& codepoints, Range range, float wraplimit, float *width) +int GenericShaper::computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) { if (!range.isValid()) range = Range(0, codepoints.cps.size()); diff --git a/src/modules/font/GenericShaper.h b/src/modules/font/GenericShaper.h index a9f4d6526..c77b5b309 100644 --- a/src/modules/font/GenericShaper.h +++ b/src/modules/font/GenericShaper.h @@ -36,7 +36,7 @@ public: virtual ~GenericShaper(); void computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector *positions, std::vector *colors, TextInfo *info) override; - int computeWordWrapIndex(const ColoredCodepoints& codepoints, Range range, float wraplimit, float *width) override; + int computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) override; private: diff --git a/src/modules/font/TextShaper.cpp b/src/modules/font/TextShaper.cpp index 404e06ff5..43854e719 100644 --- a/src/modules/font/TextShaper.cpp +++ b/src/modules/font/TextShaper.cpp @@ -135,7 +135,7 @@ float TextShaper::getBaseline() const bool TextShaper::hasGlyph(uint32 glyph) const { - for (const StrongRef& r : rasterizers) + for (const StrongRef &r : rasterizers) { if (r->hasGlyph(glyph)) return true; @@ -162,7 +162,7 @@ bool TextShaper::hasGlyphs(const std::string &text) const return false; } } - catch (utf8::exception& e) + catch (utf8::exception &e) { throw love::Exception("UTF-8 decoding error: %s", e.what()); } @@ -208,7 +208,7 @@ float TextShaper::getKerning(const std::string &leftchar, const std::string &rig left = utf8::peek_next(leftchar.begin(), leftchar.end()); right = utf8::peek_next(rightchar.begin(), rightchar.end()); } - catch (utf8::exception& e) + catch (utf8::exception &e) { throw love::Exception("UTF-8 decoding error: %s", e.what()); } @@ -346,7 +346,7 @@ void TextShaper::getWrap(const std::vector &text, float wraplimit for (size_t i = range.getMin(); i <= range.getMax(); i++) { char character[5] = { '\0' }; - char* end = utf8::unchecked::append(cps.cps[i], character); + char *end = utf8::unchecked::append(cps.cps[i], character); line.append(character, end - character); } } diff --git a/src/modules/font/TextShaper.h b/src/modules/font/TextShaper.h index b7609cebd..b5417514c 100644 --- a/src/modules/font/TextShaper.h +++ b/src/modules/font/TextShaper.h @@ -56,8 +56,8 @@ struct ColoredCodepoints std::vector colors; }; -void getCodepointsFromString(const std::string& str, std::vector& codepoints); -void getCodepointsFromString(const std::vector& strs, ColoredCodepoints& codepoints); +void getCodepointsFromString(const std::string &str, std::vector &codepoints); +void getCodepointsFromString(const std::vector &strs, ColoredCodepoints &codepoints); class TextShaper : public Object { @@ -114,19 +114,19 @@ public: bool hasGlyphs(const std::string &text) const; float getKerning(uint32 leftglyph, uint32 rightglyph); - float getKerning(const std::string& leftchar, const std::string& rightchar); + 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& text, float wraplimit, std::vector& lines, std::vector* linewidths = nullptr); - void getWrap(const ColoredCodepoints& codepoints, float wraplimit, std::vector &lineranges, std::vector *linewidths = nullptr); + void getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *linewidths = nullptr); + void getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector &lineranges, std::vector *linewidths = nullptr); virtual void setFallbacks(const std::vector &fallbacks); virtual void computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector *positions, std::vector *colors, TextInfo *info) = 0; - virtual int computeWordWrapIndex(const ColoredCodepoints& codepoints, Range range, float wraplimit, float *width) = 0; + virtual int computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) = 0; protected: diff --git a/src/modules/font/freetype/HarfbuzzShaper.cpp b/src/modules/font/freetype/HarfbuzzShaper.cpp index bd25dc040..d2ab9a729 100644 --- a/src/modules/font/freetype/HarfbuzzShaper.cpp +++ b/src/modules/font/freetype/HarfbuzzShaper.cpp @@ -102,7 +102,7 @@ void HarfbuzzShaper::updateSpacesForTabInfo() } } -bool HarfbuzzShaper::isValidGlyph(uint32 glyphindex, const std::vector& codepoints, uint32 codepointindex) +bool HarfbuzzShaper::isValidGlyph(uint32 glyphindex, const std::vector &codepoints, uint32 codepointindex) { if (glyphindex != 0) return true; @@ -140,7 +140,7 @@ void HarfbuzzShaper::computeBufferRanges(const ColoredCodepoints &codepoints, Ra // 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_t *hbb = hbBuffers[rasti]; hb_buffer_reset(hbb); for (Range r : fallbackranges) @@ -229,8 +229,8 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, 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); + 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++) @@ -339,10 +339,10 @@ int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints &codepoints, Ra for (const auto &bufferrange : bufferranges) { - hb_buffer_t* hbbuffer = hbBuffers[bufferrange.index]; + 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); + 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++) diff --git a/src/modules/font/freetype/HarfbuzzShaper.h b/src/modules/font/freetype/HarfbuzzShaper.h index 19ed2ec79..3a71a5b8b 100644 --- a/src/modules/font/freetype/HarfbuzzShaper.h +++ b/src/modules/font/freetype/HarfbuzzShaper.h @@ -45,9 +45,9 @@ public: HarfbuzzShaper(TrueTypeRasterizer *rasterizer); virtual ~HarfbuzzShaper(); - void setFallbacks(const std::vector& fallbacks) override; + void setFallbacks(const std::vector &fallbacks) override; void computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector *positions, std::vector *colors, TextInfo *info) override; - int computeWordWrapIndex(const ColoredCodepoints& codepoints, Range range, float wraplimit, float *width) override; + int computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) override; private: @@ -59,8 +59,8 @@ private: }; void updateSpacesForTabInfo(); - bool isValidGlyph(uint32 glyphindex, const std::vector& codepoints, uint32 codepointindex); - void computeBufferRanges(const ColoredCodepoints& codepoints, Range range, std::vector &bufferranges); + bool isValidGlyph(uint32 glyphindex, const std::vector &codepoints, uint32 codepointindex); + void computeBufferRanges(const ColoredCodepoints &codepoints, Range range, std::vector &bufferranges); std::vector hbFonts; std::vector hbBuffers; From d980b366da6f347fb09a3cffd9b4e096c3beb776 Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Sun, 1 Jan 2023 18:33:25 -0400 Subject: [PATCH 07/11] Add TextShaper file references to Xcode project --- .../xcode/liblove.xcodeproj/project.pbxproj | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index 43bdc7230..d50b845bd 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -50,6 +50,15 @@ 217DFC101D9F6D490055D849 /* url.lua.h in Headers */ = {isa = PBXBuildFile; fileRef = 217DFBD41D9F6D490055D849 /* url.lua.h */; }; 217DFC111D9F6D490055D849 /* usocket.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBD51D9F6D490055D849 /* usocket.c */; }; 217DFC121D9F6D490055D849 /* usocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 217DFBD61D9F6D490055D849 /* usocket.h */; }; + D9DAB9222961F0EE00C64820 /* HarfbuzzShaper.h in Headers */ = {isa = PBXBuildFile; fileRef = D9DAB9202961F0EE00C64820 /* HarfbuzzShaper.h */; }; + D9DAB9232961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */; }; + D9DAB9242961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */; }; + D9DAB9292961F10000C64820 /* GenericShaper.h in Headers */ = {isa = PBXBuildFile; fileRef = D9DAB9252961F0FF00C64820 /* GenericShaper.h */; }; + D9DAB92A2961F10000C64820 /* GenericShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9262961F0FF00C64820 /* GenericShaper.cpp */; }; + D9DAB92B2961F10000C64820 /* GenericShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9262961F0FF00C64820 /* GenericShaper.cpp */; }; + D9DAB92C2961F10000C64820 /* TextShaper.h in Headers */ = {isa = PBXBuildFile; fileRef = D9DAB9272961F0FF00C64820 /* TextShaper.h */; }; + D9DAB92D2961F10000C64820 /* TextShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9282961F10000C64820 /* TextShaper.cpp */; }; + D9DAB92E2961F10000C64820 /* TextShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9282961F10000C64820 /* TextShaper.cpp */; }; FA0A3A5F23366CE9001C269E /* floattypes.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0A3A5D23366CE9001C269E /* floattypes.h */; }; FA0A3A6023366CE9001C269E /* floattypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0A3A5E23366CE9001C269E /* floattypes.cpp */; }; FA0A3A6123366CE9001C269E /* floattypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0A3A5E23366CE9001C269E /* floattypes.cpp */; }; @@ -1391,6 +1400,12 @@ 217DFBD41D9F6D490055D849 /* url.lua.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = url.lua.h; sourceTree = ""; }; 217DFBD51D9F6D490055D849 /* usocket.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = usocket.c; sourceTree = ""; }; 217DFBD61D9F6D490055D849 /* usocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = usocket.h; sourceTree = ""; }; + D9DAB9202961F0EE00C64820 /* HarfbuzzShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HarfbuzzShaper.h; sourceTree = ""; }; + D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HarfbuzzShaper.cpp; sourceTree = ""; }; + D9DAB9252961F0FF00C64820 /* GenericShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GenericShaper.h; sourceTree = ""; }; + D9DAB9262961F0FF00C64820 /* GenericShaper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GenericShaper.cpp; sourceTree = ""; }; + D9DAB9272961F0FF00C64820 /* TextShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextShaper.h; sourceTree = ""; }; + D9DAB9282961F10000C64820 /* TextShaper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextShaper.cpp; sourceTree = ""; }; FA08F5AE16C7525600F007B5 /* liblove-macosx.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "liblove-macosx.plist"; path = "macosx/liblove-macosx.plist"; sourceTree = ""; }; FA0A3A5D23366CE9001C269E /* floattypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = floattypes.h; sourceTree = ""; }; FA0A3A5E23366CE9001C269E /* floattypes.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = floattypes.cpp; sourceTree = ""; }; @@ -2810,6 +2825,8 @@ FA0B7B731A95902C000E1D17 /* Font.cpp */, FA0B7B741A95902C000E1D17 /* Font.h */, FA0B7B751A95902C000E1D17 /* freetype */, + D9DAB9262961F0FF00C64820 /* GenericShaper.cpp */, + D9DAB9252961F0FF00C64820 /* GenericShaper.h */, FA0B7B7A1A95902C000E1D17 /* GlyphData.cpp */, FA0B7B7B1A95902C000E1D17 /* GlyphData.h */, FA0B7B7C1A95902C000E1D17 /* ImageRasterizer.cpp */, @@ -2817,6 +2834,8 @@ FA522D5923FA5ED40059EE3C /* NotoSans-Regular.ttf.gzip.h */, FA0B7B7E1A95902C000E1D17 /* Rasterizer.cpp */, FA0B7B7F1A95902C000E1D17 /* Rasterizer.h */, + D9DAB9282961F10000C64820 /* TextShaper.cpp */, + D9DAB9272961F0FF00C64820 /* TextShaper.h */, FAB2D5A81AABDD8A008224A4 /* TrueTypeRasterizer.cpp */, FAB2D5A91AABDD8A008224A4 /* TrueTypeRasterizer.h */, FA0B7B811A95902C000E1D17 /* wrap_Font.cpp */, @@ -2834,6 +2853,8 @@ children = ( FA0B7B761A95902C000E1D17 /* Font.cpp */, FA0B7B771A95902C000E1D17 /* Font.h */, + D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */, + D9DAB9202961F0EE00C64820 /* HarfbuzzShaper.h */, FA0B7B781A95902C000E1D17 /* TrueTypeRasterizer.cpp */, FA0B7B791A95902C000E1D17 /* TrueTypeRasterizer.h */, ); @@ -4100,6 +4121,7 @@ FABDA9B82552448300B5C523 /* b2_motor_joint.h in Headers */, FA0B7AC11A958EA3000E1D17 /* callbacks.h in Headers */, FA3C5E491F8D80CA0003C579 /* ShaderStage.h in Headers */, + D9DAB9292961F10000C64820 /* GenericShaper.h in Headers */, FA0B7D8F1A95902C000E1D17 /* ddsHandler.h in Headers */, FAB2D5AC1AABDD8A008224A4 /* TrueTypeRasterizer.h in Headers */, FABDAA042552448300B5C523 /* b2_edge_shape.h in Headers */, @@ -4137,6 +4159,7 @@ FA0B7EDD1A95902D000E1D17 /* Touch.h in Headers */, FA0B7EDE1A95902D000E1D17 /* Touch.h in Headers */, FAC7CD861FE35E95006A60C7 /* physfs.h in Headers */, + D9DAB92C2961F10000C64820 /* TextShaper.h in Headers */, FAF6C9E523C2DE2900D7B5BC /* spirv.hpp in Headers */, FA522D4F23F9FE380059EE3C /* MP3Decoder.h in Headers */, 217DFBEE1D9F6D490055D849 /* luasocket.h in Headers */, @@ -4181,6 +4204,7 @@ FAF6C9E823C2DE2900D7B5BC /* GLSL.ext.EXT.h in Headers */, FACA02F71F5E396B0084B28F /* wrap_DataModule.h in Headers */, FABDA9BE2552448300B5C523 /* Box2D.h in Headers */, + D9DAB9222961F0EE00C64820 /* HarfbuzzShaper.h in Headers */, FA56AA3A1FAFF02000A43D5F /* memory.h in Headers */, FA0B7E441A95902C000E1D17 /* wrap_CircleShape.h in Headers */, FA0B7EB41A95902C000E1D17 /* System.h in Headers */, @@ -4660,6 +4684,7 @@ FADF540E1E3D7CDD00012CC0 /* wrap_Video.cpp in Sources */, FA0B7D4C1A95902C000E1D17 /* Shader.cpp in Sources */, FA0B792A1A958E3B000E1D17 /* Matrix.cpp in Sources */, + D9DAB92B2961F10000C64820 /* GenericShaper.cpp in Sources */, FAF140981E20934C00F898D2 /* PpTokens.cpp in Sources */, FAF140AA1E20934C00F898D2 /* SymbolTable.cpp in Sources */, FABDA9892552448300B5C523 /* b2_contact.cpp in Sources */, @@ -4778,6 +4803,7 @@ FA59A2D31C06481400328DBA /* ParticleSystem.cpp in Sources */, FA0B7E131A95902C000E1D17 /* GearJoint.cpp in Sources */, FABDA99B2552448300B5C523 /* b2_polygon_contact.cpp in Sources */, + D9DAB9242961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */, FA0B7DC21A95902C000E1D17 /* wrap_Joystick.cpp in Sources */, FA0B7CD41A95902C000E1D17 /* Source.cpp in Sources */, FAA3A9AF1B7D465A00CED060 /* android.cpp in Sources */, @@ -4926,6 +4952,7 @@ FABDA9FE2552448300B5C523 /* b2_edge_shape.cpp in Sources */, FA0B7D7A1A95902C000E1D17 /* Quad.cpp in Sources */, FA620A3B1AA305F6005DB4C2 /* types.cpp in Sources */, + D9DAB92E2961F10000C64820 /* TextShaper.cpp in Sources */, FA0B7DD41A95902C000E1D17 /* BezierCurve.cpp in Sources */, FA0B7E7C1A95902C000E1D17 /* wrap_World.cpp in Sources */, FAF6C9F923C2DE2900D7B5BC /* doc.cpp in Sources */, @@ -5087,6 +5114,7 @@ FA0B7EA31A95902C000E1D17 /* SoundData.cpp in Sources */, FA0B79291A958E3B000E1D17 /* Matrix.cpp in Sources */, FA8951A21AA2EDF300EC385A /* wrap_Event.cpp in Sources */, + D9DAB92A2961F10000C64820 /* GenericShaper.cpp in Sources */, FAF140691E20934C00F898D2 /* glslang_tab.cpp in Sources */, FA0B7ABF1A958EA3000E1D17 /* host.c in Sources */, FA0B7D4B1A95902C000E1D17 /* Shader.cpp in Sources */, @@ -5205,6 +5233,7 @@ FAF140551E20934C00F898D2 /* Link.cpp in Sources */, FABDA9792552448200B5C523 /* b2_joint.cpp in Sources */, FAF140841E20934C00F898D2 /* ParseHelper.cpp in Sources */, + D9DAB9232961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */, FA0B7D7F1A95902C000E1D17 /* Volatile.cpp in Sources */, FA1BA0B11E16FD0800AA2803 /* Shader.cpp in Sources */, FABDA99A2552448300B5C523 /* b2_polygon_contact.cpp in Sources */, @@ -5353,6 +5382,7 @@ FA0B7E881A95902C000E1D17 /* Decoder.cpp in Sources */, FA0B7E3C1A95902C000E1D17 /* wrap_Body.cpp in Sources */, FA0B7D791A95902C000E1D17 /* Quad.cpp in Sources */, + D9DAB92D2961F10000C64820 /* TextShaper.cpp in Sources */, FABDA9FD2552448300B5C523 /* b2_edge_shape.cpp in Sources */, FAC756F51E4F99B400B91289 /* Effect.cpp in Sources */, FA620A3A1AA305F6005DB4C2 /* types.cpp in Sources */, From bb82a448bee5fb230a18d605e5412e6646176a6c Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Tue, 3 Jan 2023 20:35:31 -0400 Subject: [PATCH 08/11] Fix another compile error with some compilers. --- src/modules/font/freetype/HarfbuzzShaper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/font/freetype/HarfbuzzShaper.cpp b/src/modules/font/freetype/HarfbuzzShaper.cpp index d2ab9a729..8b6ad01c5 100644 --- a/src/modules/font/freetype/HarfbuzzShaper.cpp +++ b/src/modules/font/freetype/HarfbuzzShaper.cpp @@ -267,7 +267,7 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, continue; // This is a glyph index at this point, despite the name. - GlyphIndex gindex = { info.codepoint, bufferrange.index }; + GlyphIndex gindex = { (int) info.codepoint, bufferrange.index }; if (clustercodepoint == '\t' && isUsingSpacesForTab()) { From b8a0e58fa669e9da5a730010746b42f1a04e9f37 Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Tue, 3 Jan 2023 20:36:11 -0400 Subject: [PATCH 09/11] macOS: add harfbuzz.framework reference to xcode project. --- platform/xcode/liblove.xcodeproj/project.pbxproj | 7 +++++++ platform/xcode/love.xcodeproj/project.pbxproj | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index d50b845bd..e538d89d1 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -59,6 +59,7 @@ D9DAB92C2961F10000C64820 /* TextShaper.h in Headers */ = {isa = PBXBuildFile; fileRef = D9DAB9272961F0FF00C64820 /* TextShaper.h */; }; D9DAB92D2961F10000C64820 /* TextShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9282961F10000C64820 /* TextShaper.cpp */; }; D9DAB92E2961F10000C64820 /* TextShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9282961F10000C64820 /* TextShaper.cpp */; }; + D9DAB9322963CD7500C64820 /* harfbuzz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9DAB9312963CD7500C64820 /* harfbuzz.framework */; }; FA0A3A5F23366CE9001C269E /* floattypes.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0A3A5D23366CE9001C269E /* floattypes.h */; }; FA0A3A6023366CE9001C269E /* floattypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0A3A5E23366CE9001C269E /* floattypes.cpp */; }; FA0A3A6123366CE9001C269E /* floattypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0A3A5E23366CE9001C269E /* floattypes.cpp */; }; @@ -1406,6 +1407,7 @@ D9DAB9262961F0FF00C64820 /* GenericShaper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GenericShaper.cpp; sourceTree = ""; }; D9DAB9272961F0FF00C64820 /* TextShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextShaper.h; sourceTree = ""; }; D9DAB9282961F10000C64820 /* TextShaper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextShaper.cpp; sourceTree = ""; }; + D9DAB9312963CD7500C64820 /* harfbuzz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = harfbuzz.framework; path = macosx/Frameworks/harfbuzz.framework; sourceTree = ""; }; FA08F5AE16C7525600F007B5 /* liblove-macosx.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "liblove-macosx.plist"; path = "macosx/liblove-macosx.plist"; sourceTree = ""; }; FA0A3A5D23366CE9001C269E /* floattypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = floattypes.h; sourceTree = ""; }; FA0A3A5E23366CE9001C269E /* floattypes.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = floattypes.cpp; sourceTree = ""; }; @@ -2326,6 +2328,7 @@ FA577AC516C7513400860150 /* libmodplug.framework in Frameworks */, FADF4CC62663D0EC004F95C1 /* libz.tbd in Frameworks */, FA577AC816C7513C00860150 /* ogg.framework in Frameworks */, + D9DAB9322963CD7500C64820 /* harfbuzz.framework in Frameworks */, FA577ACA16C7514100860150 /* OpenGL.framework in Frameworks */, FA577ACD16C7514C00860150 /* vorbis.framework in Frameworks */, ); @@ -3387,6 +3390,7 @@ FA577A7916C71A1700860150 /* Cocoa.framework */, FAA627CD18E7E1560080752D /* CoreServices.framework */, FAD43ECB1FF312D800831BB8 /* freetype.framework */, + D9DAB9312963CD7500C64820 /* harfbuzz.framework */, FA577A8216C71A5300860150 /* libmodplug.framework */, FADF4CC52663D0EC004F95C1 /* libz.tbd */, FA577A6D16C719EA00860150 /* Lua.framework */, @@ -5746,6 +5750,7 @@ "$(PROJECT_DIR)/macosx/Frameworks/freetype.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers", + "$(PROJECT_DIR)/macosx/Frameworks/harfbuzz.framework/Headers", ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; @@ -5781,6 +5786,7 @@ "$(PROJECT_DIR)/macosx/Frameworks/freetype.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers", + "$(PROJECT_DIR)/macosx/Frameworks/harfbuzz.framework/Headers", ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; @@ -5817,6 +5823,7 @@ "$(PROJECT_DIR)/macosx/Frameworks/freetype.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers", + "$(PROJECT_DIR)/macosx/Frameworks/harfbuzz.framework/Headers", ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; diff --git a/platform/xcode/love.xcodeproj/project.pbxproj b/platform/xcode/love.xcodeproj/project.pbxproj index e91c5bec8..0d7b4911a 100644 --- a/platform/xcode/love.xcodeproj/project.pbxproj +++ b/platform/xcode/love.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ A93E6EED10420BA8007D418B /* love.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A93E6A3410420AC0007D418B /* love.cpp */; }; A9F169AD109E825000FC83D1 /* libmodplug.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A9F16926109E7BAD00FC83D1 /* libmodplug.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; CE73F8001EEB64150052DAB3 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE73F7FF1EEB64150052DAB3 /* AVFoundation.framework */; }; + D9DAB9372963CF6900C64820 /* harfbuzz.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D9DAB9352963CF5F00C64820 /* harfbuzz.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; FA0797991BF480A200034B7C /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA0797981BF480A200034B7C /* GameController.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; FA08F69616C766E000F007B5 /* love.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA08F69116C765A200F007B5 /* love.framework */; }; FA08F69716C766E700F007B5 /* love.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = FA08F69116C765A200F007B5 /* love.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; @@ -87,6 +88,7 @@ FAD4B1731C1F50A3004CF150 /* theora.framework in Copy Frameworks */, FAAFF04716CB120000CCDE45 /* OpenAL-Soft.framework in Copy Frameworks */, FAD43ED01FF3136500831BB8 /* freetype.framework in Copy Frameworks */, + D9DAB9372963CF6900C64820 /* harfbuzz.framework in Copy Frameworks */, A9F169AD109E825000FC83D1 /* libmodplug.framework in Copy Frameworks */, A9255F58104324E100BA1496 /* ogg.framework in Copy Frameworks */, A9255E031043195A00BA1496 /* vorbis.framework in Copy Frameworks */, @@ -108,6 +110,7 @@ A97E3842132A9EDE00198A2F /* love-macosx.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "love-macosx.plist"; path = "macosx/love-macosx.plist"; sourceTree = ""; }; A9F16926109E7BAD00FC83D1 /* libmodplug.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = libmodplug.framework; path = macosx/Frameworks/libmodplug.framework; sourceTree = ""; }; CE73F7FF1EEB64150052DAB3 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.3.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; }; + D9DAB9352963CF5F00C64820 /* harfbuzz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = harfbuzz.framework; path = macosx/Frameworks/harfbuzz.framework; sourceTree = ""; }; FA0797981BF480A200034B7C /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.1.sdk/System/Library/Frameworks/GameController.framework; sourceTree = DEVELOPER_DIR; }; FA08F69116C765A200F007B5 /* love.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = love.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FA0B7F061A95AAF3000E1D17 /* love.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = love.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -233,6 +236,7 @@ children = ( 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, FAD43ECF1FF3133700831BB8 /* freetype.framework */, + D9DAB9352963CF5F00C64820 /* harfbuzz.framework */, A9F16926109E7BAD00FC83D1 /* libmodplug.framework */, FA08F69116C765A200F007B5 /* love.framework */, A93E6E5310420B57007D418B /* Lua.framework */, From 82e516f2fb350e92d0041ba8af6aa8d3fdb9b0cc Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Tue, 3 Jan 2023 21:12:16 -0400 Subject: [PATCH 10/11] Untested attempt at linux support for harfbuzz dependency --- platform/unix/configure.ac | 5 ++++- platform/unix/debian/control.in | 3 ++- platform/unix/deps.m4 | 3 +++ platform/unix/genmodules | 4 ++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/platform/unix/configure.ac b/platform/unix/configure.ac index fd639a91e..210d54d41 100644 --- a/platform/unix/configure.ac +++ b/platform/unix/configure.ac @@ -67,7 +67,10 @@ ACLOVE_DEP_PTHREAD # Conditional dependencies AS_VAR_IF([enable_module_audio], [yes], [ACLOVE_DEP_OPENAL], []) -AS_VAR_IF([enable_module_font], [yes], [ACLOVE_DEP_FREETYPE2], []) +AS_VAR_IF([enable_module_font], [yes], [ + ACLOVE_DEP_FREETYPE2 + ACLOVE_DEP_HARFBUZZ +], []) AS_VAR_IF([enable_module_sound], [yes], [ ACLOVE_DEP_LIBMODPLUG ACLOVE_DEP_VORBISFILE diff --git a/platform/unix/debian/control.in b/platform/unix/debian/control.in index e2dc757fc..e435df0ff 100644 --- a/platform/unix/debian/control.in +++ b/platform/unix/debian/control.in @@ -8,12 +8,13 @@ Build-Depends: debhelper (>= 9), libtool, g++ (>= 4.7.0), libfreetype6-dev, + libharfbuzz-dev, luajit, libluajit-5.1-dev, libmodplug-dev, libopenal-dev, libphysfs-dev, - libsdl2-dev (>= 2.0.1), + libsdl2-dev (>= 2.0.9), libogg-dev, libvorbis-dev, libtheora-dev, diff --git a/platform/unix/deps.m4 b/platform/unix/deps.m4 index e087a88e8..868bd0244 100644 --- a/platform/unix/deps.m4 +++ b/platform/unix/deps.m4 @@ -1,6 +1,9 @@ AC_DEFUN([ACLOVE_DEP_FREETYPE2], [ PKG_CHECK_MODULES([freetype2], [freetype2], [], [LOVE_MSG_ERROR([FreeType2])])]) +AC_DEFUN([ACLOVE_DEP_HARFBUZZ], [ + PKG_CHECK_MODULES([harfbuzz], [harfbuzz], [], [LOVE_MSG_ERROR([Harfbuzz])])]) + AC_DEFUN([ACLOVE_DEP_OPENAL], [ PKG_CHECK_MODULES([openal], [openal], [], [LOVE_MSG_ERROR([OpenAL])])]) diff --git a/platform/unix/genmodules b/platform/unix/genmodules index 78551842b..3d7cdca0d 100644 --- a/platform/unix/genmodules +++ b/platform/unix/genmodules @@ -107,7 +107,7 @@ inc_libraries="$inc_current/libraries" cat > src/Makefile.am << EOF AM_CPPFLAGS = -I$inc_current -I$inc_modules -I$inc_libraries -I$inc_libraries/enet/libenet/include -I$inc_libraries/box2d \$(LOVE_INCLUDES) \$(FILE_OFFSET)\ - \$(SDL_CFLAGS) \$(lua_CFLAGS) \$(freetype2_CFLAGS)\ + \$(SDL_CFLAGS) \$(lua_CFLAGS) \$(freetype2_CFLAGS) \$(harfbuzz_CFLAGS)\ \$(openal_CFLAGS) \$(zlib_CFLAGS) \$(libmodplug_CFLAGS)\ \$(vorbisfile_CFLAGS) \$(theora_CFLAGS) AUTOMAKE_OPTIONS = subdir-objects @@ -139,7 +139,7 @@ endif lib_LTLIBRARIES = liblove${love_suffix}.la liblove${love_amsuffix}_la_LDFLAGS = -module -export-dynamic \$(LDFLAGS) -release \$(PACKAGE_VERSION) liblove${love_amsuffix}_la_LIBADD = \ - \$(SDL_LIBS) \$(freetype2_LIBS) \$(lua_LIBS)\ + \$(SDL_LIBS) \$(freetype2_LIBS) \$(harfbuzz_LIBS) \$(lua_LIBS)\ \$(openal_LIBS) \$(zlib_LIBS) \$(libmodplug_LIBS)\ \$(vorbisfile_LIBS) \$(theora_LIBS) From 5c9ccd165233b7e777a0f50388a816579a052920 Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Sun, 8 Jan 2023 20:25:38 -0400 Subject: [PATCH 11/11] iOS: Add harfbuzz.xcframework to xcode project --- platform/xcode/liblove.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index e538d89d1..eef3d95b9 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ 217DFC101D9F6D490055D849 /* url.lua.h in Headers */ = {isa = PBXBuildFile; fileRef = 217DFBD41D9F6D490055D849 /* url.lua.h */; }; 217DFC111D9F6D490055D849 /* usocket.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBD51D9F6D490055D849 /* usocket.c */; }; 217DFC121D9F6D490055D849 /* usocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 217DFBD61D9F6D490055D849 /* usocket.h */; }; + D923E7D3296B85B9002FF1B3 /* harfbuzz.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */; }; D9DAB9222961F0EE00C64820 /* HarfbuzzShaper.h in Headers */ = {isa = PBXBuildFile; fileRef = D9DAB9202961F0EE00C64820 /* HarfbuzzShaper.h */; }; D9DAB9232961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */; }; D9DAB9242961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */; }; @@ -1401,6 +1402,7 @@ 217DFBD41D9F6D490055D849 /* url.lua.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = url.lua.h; sourceTree = ""; }; 217DFBD51D9F6D490055D849 /* usocket.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = usocket.c; sourceTree = ""; }; 217DFBD61D9F6D490055D849 /* usocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = usocket.h; sourceTree = ""; }; + D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = harfbuzz.xcframework; path = ios/libraries/harfbuzz.xcframework; sourceTree = ""; }; D9DAB9202961F0EE00C64820 /* HarfbuzzShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HarfbuzzShaper.h; sourceTree = ""; }; D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HarfbuzzShaper.cpp; sourceTree = ""; }; D9DAB9252961F0FF00C64820 /* GenericShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GenericShaper.h; sourceTree = ""; }; @@ -2305,6 +2307,7 @@ buildActionMask = 2147483647; files = ( FACFB751276D7E3B0089F78D /* freetype.xcframework in Frameworks */, + D923E7D3296B85B9002FF1B3 /* harfbuzz.xcframework in Frameworks */, FA84DE7A277D4C88002674C6 /* modplug.xcframework in Frameworks */, FA84DE7C277E045E002674C6 /* ogg.xcframework in Frameworks */, FACFB753276D7F860089F78D /* Lua.xcframework in Frameworks */, @@ -3558,6 +3561,7 @@ FA5D24A31A96D2C300C6FC8F /* ios */ = { isa = PBXGroup; children = ( + D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */, FACFB750276D7E2B0089F78D /* freetype.xcframework */, FACFB752276D7F6F0089F78D /* Lua.xcframework */, FA84DE79277D4C88002674C6 /* modplug.xcframework */,