/*
* SchriftGX2 is a wrapper class for libFreeType which renders a compiled
* FreeType parsable font so a GX texture for Wii homebrew development.
* Copyright (C) 2008 Armin Tamzarian
* Modified by Dimok, 2015 for WiiU GX2
*
* This file is part of SchriftGX2.
*
* SchriftGX2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SchriftGX2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with SchriftGX2. If not, see .
*/
#include "SchriftGX2.h"
#include "schrift.h"
#include "shaders/Texture2DShader.h"
#include "utils/logger.h"
using namespace std;
/**
* Default constructor for the SchriftGX2 class.
*/
SchriftGX2::SchriftGX2(const uint8_t *fontBuffer, uint32_t bufferSize) {
GX2InitSampler(&ftSampler, GX2_TEX_CLAMP_MODE_CLAMP_BORDER, GX2_TEX_XY_FILTER_MODE_LINEAR);
ftPointSize = 0;
pFont.xScale = 20;
pFont.yScale = 20,
pFont.flags = SFT_DOWNWARD_Y;
pFont.font = sft_loadmem(fontBuffer, bufferSize);
if (!pFont.font) {
DEBUG_FUNCTION_LINE_ERR("Failed to load font!!");
} else {
OSMemoryBarrier();
}
uint32_t heapSize = 1024 * 1024;
glyphHeap = (uint8_t *) MEMAllocFromMappedMemoryForGX2Ex(heapSize, 0x100);
if (!glyphHeap) {
heapSize = 512 * 1024;
glyphHeap = (uint8_t *) MEMAllocFromMappedMemoryForGX2Ex(heapSize, 0x100);
}
if (glyphHeap) {
glyphHeapHandle = MEMCreateExpHeapEx((void *) (glyphHeap), heapSize, 1);
if (!glyphHeapHandle) {
OSFatal("NotificationModule: Failed to create heap for glyphData");
}
} else {
OSFatal("NotificationModule: Failed to alloc heap for glyphData");
}
ftKerningEnabled = false;
}
/**
* Default destructor for the SchriftGX2 class.
*/
SchriftGX2::~SchriftGX2() {
unloadFont();
sft_freefont(pFont.font);
pFont.font = nullptr;
MEMFreeToMappedMemory(glyphHeap);
glyphHeap = nullptr;
}
/**
* Convert a short char string to a wide char string.
*
* This routine converts a supplied short character string into a wide character string.
* Note that it is the user's responsibility to clear the returned buffer once it is no longer needed.
*
* @param strChar Character string to be converted.
* @return Wide character representation of supplied character string.
*/
wchar_t *SchriftGX2::charToWideChar(const char *strChar) {
if (!strChar) return nullptr;
auto *strWChar = new (std::nothrow) wchar_t[strlen(strChar) + 1];
if (!strWChar) return nullptr;
int32_t bt = mbstowcs(strWChar, strChar, strlen(strChar));
if (bt > 0) {
strWChar[bt] = 0;
return strWChar;
}
wchar_t *tempDest = strWChar;
while ((*tempDest++ = *strChar++))
;
return strWChar;
}
char *SchriftGX2::wideCharToUTF8(const wchar_t *strChar) {
if (!strChar) {
return nullptr;
}
size_t len = 0;
wchar_t wc;
for (size_t i = 0; strChar[i]; ++i) {
wc = strChar[i];
if (wc < 0x80)
++len;
else if (wc < 0x800)
len += 2;
else if (wc < 0x10000)
len += 3;
else
len += 4;
}
char *pOut = new (std::nothrow) char[len];
if (!pOut)
return nullptr;
size_t n = 0;
for (size_t i = 0; strChar[i]; ++i) {
wc = strChar[i];
if (wc < 0x80)
pOut[n++] = (char) wc;
else if (wc < 0x800) {
pOut[n++] = (char) ((wc >> 6) | 0xC0);
pOut[n++] = (char) ((wc & 0x3F) | 0x80);
} else if (wc < 0x10000) {
pOut[n++] = (char) ((wc >> 12) | 0xE0);
pOut[n++] = (char) (((wc >> 6) & 0x3F) | 0x80);
pOut[n++] = (char) ((wc & 0x3F) | 0x80);
} else {
pOut[n++] = (char) (((wc >> 18) & 0x07) | 0xF0);
pOut[n++] = (char) (((wc >> 12) & 0x3F) | 0x80);
pOut[n++] = (char) (((wc >> 6) & 0x3F) | 0x80);
pOut[n++] = (char) ((wc & 0x3F) | 0x80);
}
}
return pOut;
}
/**
* Clears all loaded font glyph data.
*
* This routine clears all members of the font map structure and frees all allocated memory back to the system.
*/
void SchriftGX2::unloadFont() {
std::lock_guard lock(fontDataMutex);
for (auto &dataForSize : fontData) {
for (auto &cur : dataForSize.second.ftgxCharMap) {
if (cur.second.texture) {
if (cur.second.texture->surface.image) {
MEMFreeToExpHeap(glyphHeapHandle, cur.second.texture->surface.image);
}
delete cur.second.texture;
cur.second.texture = nullptr;
}
}
}
fontData.clear();
}
/**
* Caches the given font glyph in the instance font texture buffer.
*
* This routine renders and stores the requested glyph's bitmap and relevant information into its own quickly addressible
* structure within an instance-specific map.
*
* @param charCode The requested glyph's character code.
* @return A pointer to the allocated font structure.
*/
ftgxCharData *SchriftGX2::cacheGlyphData(wchar_t charCode, int16_t pixelSize) {
std::lock_guard lock(fontDataMutex);
auto itr = fontData.find(pixelSize);
if (itr != fontData.end()) {
auto itr2 = itr->second.ftgxCharMap.find(charCode);
if (itr2 != itr->second.ftgxCharMap.end()) {
return &itr2->second;
}
}
//!Cache ascender and decender as well
ftGX2Data *ftData = &fontData[pixelSize];
uint16_t textureWidth = 0, textureHeight = 0;
if (ftPointSize != pixelSize) {
ftPointSize = pixelSize;
pFont.xScale = ftPointSize;
pFont.yScale = ftPointSize;
SFT_LMetrics metrics;
sft_lmetrics(&pFont, &metrics);
ftData->ftgxAlign.ascender = (int16_t) metrics.ascender;
ftData->ftgxAlign.descender = (int16_t) metrics.descender;
ftData->ftgxAlign.max = 0;
ftData->ftgxAlign.min = 0;
}
SFT_Glyph gid; // unsigned long gid;
if (sft_lookup(&pFont, charCode, &gid) >= 0) {
SFT_GMetrics mtx;
if (sft_gmetrics(&pFont, gid, &mtx) < 0) {
DEBUG_FUNCTION_LINE_ERR("bad glyph metrics");
}
textureWidth = (mtx.minWidth + 3) & ~3;
textureHeight = mtx.minHeight;
SFT_Image img = {
.width = textureWidth,
.height = textureHeight,
};
if (textureWidth == 0) {
textureWidth = 4;
}
if (textureHeight == 0) {
textureHeight = 4;
}
img.pixels = malloc(img.width * img.height);
if (!img.pixels) {
return nullptr;
}
if (sft_render(&pFont, gid, img) < 0) {
free(img.pixels);
DEBUG_FUNCTION_LINE_ERR("sft_render failed.");
} else {
ftgxCharData charData;
charData.renderOffsetX = (int16_t) mtx.leftSideBearing;
charData.renderOffsetY = (int16_t) -mtx.yOffset;
charData.glyphAdvanceX = (uint16_t) mtx.advanceWidth;
charData.glyphAdvanceY = (uint16_t) 0;
charData.glyphIndex = (uint32_t) gid;
charData.renderOffsetMax = (int16_t) (short) -mtx.yOffset;
charData.renderOffsetMin = (int16_t) (short) (img.height - (-mtx.yOffset));
//! Initialize texture
charData.texture = new (std::nothrow) GX2Texture;
if (!charData.texture) {
free(img.pixels);
return nullptr;
}
GX2InitTexture(charData.texture, textureWidth, textureHeight, 1, 0, GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8, GX2_SURFACE_DIM_TEXTURE_2D, GX2_TILE_MODE_LINEAR_ALIGNED);
if (!loadGlyphData(&img, &charData, ftData)) {
DEBUG_FUNCTION_LINE_ERR("Failed to load glyph %d", charCode);
delete charData.texture;
charData.texture = nullptr;
free(img.pixels);
return nullptr;
} else {
free(img.pixels);
ftData->ftgxCharMap[charCode] = charData;
}
return &ftData->ftgxCharMap[charCode];
}
}
return nullptr;
}
/**
* Loads the rendered bitmap into the relevant structure's data buffer.
*
* This routine does a simple byte-wise copy of the glyph's rendered 8-bit grayscale bitmap into the structure's buffer.
* Each byte is converted from the bitmap's intensity value into the a uint32_t RGBA value.
*
* @param bmp A pointer to the most recently rendered glyph's bitmap.
* @param charData A pointer to an allocated ftgxCharData structure whose data represent that of the last rendered glyph.
*/
bool SchriftGX2::loadGlyphData(SFT_Image *bmp, ftgxCharData *charData, ftGX2Data *ftData) {
if (charData == nullptr || charData->texture->surface.imageSize == 0 || charData->texture->surface.alignment == 0 || ftData == nullptr) {
DEBUG_FUNCTION_LINE_ERR("Input data was NULL");
return false;
}
charData->texture->surface.image = (uint8_t *) MEMAllocFromExpHeapEx(glyphHeapHandle, charData->texture->surface.imageSize, charData->texture->surface.alignment);
if (!charData->texture->surface.image) {
DEBUG_FUNCTION_LINE_INFO("Cache is full, let's clear it");
for (auto &cur : ftData->ftgxCharMap) {
if (cur.second.texture) {
if (cur.second.texture->surface.image) {
MEMFreeToExpHeap(glyphHeapHandle, cur.second.texture->surface.image);
}
delete cur.second.texture;
}
}
ftData->ftgxCharMap.clear();
charData->texture->surface.image = (uint8_t *) MEMAllocFromExpHeapEx(glyphHeapHandle, charData->texture->surface.imageSize, charData->texture->surface.alignment);
if (!charData->texture->surface.image) {
return false;
}
}
memset(charData->texture->surface.image, 0x00, charData->texture->surface.imageSize);
auto *src = (uint8_t *) bmp->pixels;
auto *dst = (uint32_t *) charData->texture->surface.image;
uint32_t x, y;
for (y = 0; y < bmp->height; y++) {
for (x = 0; x < bmp->width; x++) {
uint8_t val = src[y * bmp->width + x];
dst[y * charData->texture->surface.pitch + x] = val << 24 | val << 16 | val << 8 | val;
}
}
GX2Invalidate(GX2_INVALIDATE_MODE_CPU_TEXTURE, charData->texture->surface.image, charData->texture->surface.imageSize);
return true;
}
/**
* Determines the x offset of the rendered string.
*
* This routine calculates the x offset of the rendered string based off of a supplied positional format parameter.
*
* @param width Current pixel width of the string.
* @param format Positional format of the string.
*/
int16_t SchriftGX2::getStyleOffsetWidth(uint16_t width, uint16_t format) {
if (format & FTGX_JUSTIFY_LEFT)
return 0;
else if (format & FTGX_JUSTIFY_CENTER)
return -(width >> 1);
else if (format & FTGX_JUSTIFY_RIGHT)
return -width;
return 0;
}
/**
* Determines the y offset of the rendered string.
*
* This routine calculates the y offset of the rendered string based off of a supplied positional format parameter.
*
* @param offset Current pixel offset data of the string.
* @param format Positional format of the string.
*/
int16_t SchriftGX2::getStyleOffsetHeight(int16_t format, uint16_t pixelSize) {
std::lock_guard lock(fontDataMutex);
std::map::iterator itr = fontData.find(pixelSize);
if (itr == fontData.end()) return 0;
switch (format & FTGX_ALIGN_MASK) {
case FTGX_ALIGN_TOP:
return itr->second.ftgxAlign.descender;
case FTGX_ALIGN_MIDDLE:
default:
return (itr->second.ftgxAlign.ascender + itr->second.ftgxAlign.descender + 1) >> 1;
case FTGX_ALIGN_BOTTOM:
return itr->second.ftgxAlign.ascender;
case FTGX_ALIGN_BASELINE:
return 0;
case FTGX_ALIGN_GLYPH_TOP:
return itr->second.ftgxAlign.max;
case FTGX_ALIGN_GLYPH_MIDDLE:
return (itr->second.ftgxAlign.max + itr->second.ftgxAlign.min + 1) >> 1;
case FTGX_ALIGN_GLYPH_BOTTOM:
return itr->second.ftgxAlign.min;
}
return 0;
}
/**
* Processes the supplied text string and prints the results at the specified coordinates.
*
* This routine processes each character of the supplied text string, loads the relevant preprocessed bitmap buffer,
* a texture from said buffer, and loads the resultant texture into the EFB.
*
* @param x Screen X coordinate at which to output the text.
* @param y Screen Y coordinate at which to output the text. Note that this value corresponds to the text string origin and not the top or bottom of the glyphs.
* @param text NULL terminated string to output.
* @param color Optional color to apply to the text characters. If not specified default value is ftgxWhite: (GXColor){0xff, 0xff, 0xff, 0xff}
* @param textStyle Flags which specify any styling which should be applied to the rendered string.
* @return The number of characters printed.
*/
uint16_t SchriftGX2::drawText(int16_t x, int16_t y, int16_t z, const wchar_t *text, int16_t pixelSize, const glm::vec4 &color, uint16_t textStyle, uint16_t textWidth, const float &textBlur, const float &colorBlurIntensity, const glm::vec4 &blurColor) {
if (!text) {
return 0;
}
std::lock_guard lock(fontDataMutex);
// uint16_t fullTextWidth = (textWidth > 0) ? textWidth : getWidth(text, pixelSize);
uint16_t x_pos = x, printed = 0;
uint16_t x_offset = 0, y_offset = 0;
if (textStyle & FTGX_JUSTIFY_MASK) {
//x_offset = getStyleOffsetWidth(fullTextWidth, textStyle);
}
if (textStyle & FTGX_ALIGN_MASK) {
//y_offset = getStyleOffsetHeight(textStyle, pixelSize);
}
int32_t i = 0;
while (text[i]) {
ftgxCharData *glyphData = cacheGlyphData(text[i], pixelSize);
if (glyphData != nullptr) {
if (ftKerningEnabled && i > 0) {
SFT_Kerning kerning;
sft_kerning(&pFont, fontData[pixelSize].ftgxCharMap[text[i - 1]].glyphIndex, glyphData->glyphIndex, &kerning);
x_pos += (kerning.xShift);
}
copyTextureToFramebuffer(glyphData->texture, x_pos + glyphData->renderOffsetX + x_offset, y + glyphData->renderOffsetY - y_offset, z, color, textBlur, colorBlurIntensity, blurColor);
x_pos += glyphData->glyphAdvanceX;
++printed;
}
++i;
}
return printed;
}
/**
* Processes the supplied string and return the width of the string in pixels.
*
* This routine processes each character of the supplied text string and calculates the width of the entire string.
* Note that if precaching of the entire font set is not enabled any uncached glyph will be cached after the call to this function.
*
* @param text NULL terminated string to calculate.
* @return The width of the text string in pixels.
*/
uint16_t SchriftGX2::getWidth(const wchar_t *text, int16_t pixelSize) {
if (!text) {
return 0;
}
std::lock_guard lock(fontDataMutex);
uint16_t strWidth = 0;
int32_t i = 0;
while (text[i]) {
ftgxCharData *glyphData = cacheGlyphData(text[i], pixelSize);
if (glyphData != nullptr) {
if (ftKerningEnabled && (i > 0)) {
SFT_Kerning kerning;
sft_kerning(&pFont, fontData[pixelSize].ftgxCharMap[text[i - 1]].glyphIndex, glyphData->glyphIndex, &kerning);
strWidth += kerning.xShift;
}
strWidth += glyphData->glyphAdvanceX;
}
++i;
}
return strWidth;
}
/**
* Single char width
*/
uint16_t SchriftGX2::getCharWidth(const wchar_t wChar, int16_t pixelSize, const wchar_t prevChar) {
std::lock_guard lock(fontDataMutex);
uint16_t strWidth = 0;
ftgxCharData *glyphData = cacheGlyphData(wChar, pixelSize);
if (glyphData != nullptr) {
if (ftKerningEnabled && prevChar != 0x0000) {
SFT_Kerning kerning;
sft_kerning(&pFont, fontData[pixelSize].ftgxCharMap[prevChar].glyphIndex, glyphData->glyphIndex, &kerning);
strWidth += kerning.xShift;
}
strWidth += glyphData->glyphAdvanceX;
}
return strWidth;
}
/**
* Processes the supplied string and return the height of the string in pixels.
*
* This routine processes each character of the supplied text string and calculates the height of the entire string.
* Note that if precaching of the entire font set is not enabled any uncached glyph will be cached after the call to this function.
*
* @param text NULL terminated string to calculate.
* @return The height of the text string in pixels.
*/
uint16_t SchriftGX2::getHeight(const wchar_t *text, int16_t pixelSize) {
std::lock_guard lock(fontDataMutex);
getOffset(text, pixelSize);
return fontData[pixelSize].ftgxAlign.max - fontData[pixelSize].ftgxAlign.min;
}
/**
* Get the maximum offset above and minimum offset below the font origin line.
*
* This function calculates the maximum pixel height above the font origin line and the minimum
* pixel height below the font origin line and returns the values in an addressible structure.
*
* @param text NULL terminated string to calculate.
* @param offset returns the max and min values above and below the font origin line
*
*/
void SchriftGX2::getOffset(const wchar_t *text, int16_t pixelSize, uint16_t widthLimit) {
if (!text) {
return;
}
std::lock_guard lock(fontDataMutex);
int16_t strMax = 0, strMin = 9999;
uint16_t currWidth = 0;
int32_t i = 0;
while (text[i]) {
if (widthLimit > 0 && currWidth >= widthLimit) break;
ftgxCharData *glyphData = cacheGlyphData(text[i], pixelSize);
if (glyphData != nullptr) {
strMax = glyphData->renderOffsetMax > strMax ? glyphData->renderOffsetMax : strMax;
strMin = glyphData->renderOffsetMin < strMin ? glyphData->renderOffsetMin : strMin;
currWidth += glyphData->glyphAdvanceX;
}
++i;
}
if (ftPointSize != pixelSize) {
ftPointSize = pixelSize;
pFont.xScale = ftPointSize;
pFont.yScale = ftPointSize;
}
SFT_LMetrics metrics;
sft_lmetrics(&pFont, &metrics);
fontData[pixelSize].ftgxAlign.ascender = metrics.ascender;
fontData[pixelSize].ftgxAlign.descender = metrics.descender;
fontData[pixelSize].ftgxAlign.max = strMax;
fontData[pixelSize].ftgxAlign.min = strMin;
}
/**
* Copies the supplied texture quad to the EFB.
*
* This routine uses the in-built GX quad builder functions to define the texture bounds and location on the EFB target.
*
* @param texObj A pointer to the glyph's initialized texture object.
* @param texWidth The pixel width of the texture object.
* @param texHeight The pixel height of the texture object.
* @param screenX The screen X coordinate at which to output the rendered texture.
* @param screenY The screen Y coordinate at which to output the rendered texture.
* @param color Color to apply to the texture.
*/
void SchriftGX2::copyTextureToFramebuffer(GX2Texture *texture, int16_t x, int16_t y, int16_t z, const glm::vec4 &color, const float &defaultBlur, const float &blurIntensity, const glm::vec4 &blurColor) {
static const float imageAngle = 0.0f;
static const float blurScale = (2.0f);
float widthScaleFactor = 1.0f / (float) 1280;
float heightScaleFactor = 1.0f / (float) 720;
float offsetLeft = 2.0f * ((float) x + 0.5f * (float) texture->surface.width) * widthScaleFactor;
float offsetTop = 2.0f * ((float) y - 0.5f * (float) texture->surface.height) * heightScaleFactor;
float widthScale = blurScale * (float) texture->surface.width * widthScaleFactor;
float heightScale = blurScale * (float) texture->surface.height * heightScaleFactor;
glm::vec3 positionOffsets(offsetLeft, offsetTop, (float) z);
//! blur doubles due to blur we have to scale the texture
glm::vec3 scaleFactor(widthScale, heightScale, 1.0f);
glm::vec3 blurDirection;
blurDirection[2] = 1.0f;
Texture2DShader::instance()->setShaders();
Texture2DShader::instance()->setAttributeBuffer();
Texture2DShader::instance()->setAngle(imageAngle);
Texture2DShader::instance()->setOffset(positionOffsets);
Texture2DShader::instance()->setScale(scaleFactor);
Texture2DShader::instance()->setTextureAndSampler(texture, &ftSampler);
if (blurIntensity > 0.0f) {
//! glow blur color
Texture2DShader::instance()->setColorIntensity(blurColor);
//! glow blur horizontal
blurDirection[0] = blurIntensity;
blurDirection[1] = 0.0f;
Texture2DShader::instance()->setBlurring(blurDirection);
Texture2DShader::instance()->draw();
//! glow blur vertical
blurDirection[0] = 0.0f;
blurDirection[1] = blurIntensity;
Texture2DShader::instance()->setBlurring(blurDirection);
Texture2DShader::instance()->draw();
}
//! set text color
Texture2DShader::instance()->setColorIntensity(color);
//! blur horizontal
blurDirection[0] = defaultBlur;
blurDirection[1] = 0.0f;
Texture2DShader::instance()->setBlurring(blurDirection);
Texture2DShader::instance()->draw();
//! blur vertical
blurDirection[0] = 0.0f;
blurDirection[1] = defaultBlur;
Texture2DShader::instance()->setBlurring(blurDirection);
Texture2DShader::instance()->draw();
}