Beispiel #1
0
struct TEXT_FONT_GLYPH *textRenderGlyphToCache(TEXT_FONT *font, unsigned int glyph) {
	int glyph_index, x1, y1, x2, y2;
	struct TEXT_FONT_CACHE *next;
	
	next = font->cache;
	glyph_index = stbtt_FindGlyphIndex(&font->face, glyph);
	
	/* Just in case the coords aren't reported correctly o_O */
	x1 = y1 = x2 = y2 = 0;
//	stbtt_GetCodepointBox(&font->face, glyph_index, &x1, &y1, &x2, &y2);
	stbtt_GetCodepointBitmapBox(&font->face, glyph, font->scale, font->scale, &x1, &y1, &x2, &y2);

#if 0
	x1 = font->scale * x1;
	y1 = font->scale * y1;
	x2 = font->scale * x2;
	y2 = font->scale * y2;
#endif
	
	while (next != NULL) {
		if (textWillGlyphFit(next, x2 - x1, y2 - y1) == 0)
			break;
		next = next->next;
	}
	
	if (next == NULL)
		if ((next = textAppendCache(font, font->cache, font->tex_w, font->tex_h)) == NULL)
			return NULL;
	
	if (font->cache == NULL)
		font->cache = next;
	
	return textRenderGlyph(next, glyph, glyph_index, font);
}
Beispiel #2
0
void UniFont::CacheGlyphDefault(const unsigned int* str, int index, int len, int cellCols, int cellRows, Texture* tex, bool* outIsDirty) {
	int codePoint = str[index];
	int charSize = fontScale + 2;
	if (GetInfoForCodepoint(codePoint) == nullptr) {
		stbtt_fontinfo* font = nullptr;
		unsigned char* cBmp = nullptr;
		int cW = 0, cH = 0;
		for (int j = 0; j < fontInfos.count; j++) {
			if (fontInfos.data[j].low <= codePoint && codePoint <= fontInfos.data[j].high) {
				float pixelScale = stbtt_ScaleForPixelHeight(&fontInfos.data[j].info, fontScale);
				int glyphIdx = stbtt_FindGlyphIndex(&fontInfos.data[j].info, codePoint);
				cBmp = stbtt_GetCodepointBitmap(&fontInfos.data[j].info, 0, pixelScale, codePoint, &cW, &cH, 0, 0);
				if (cBmp) {
					font = &fontInfos.data[j].info;
					break;
				}
			}
		}

		if (font != nullptr) {
			*outIsDirty = true;
			int ascent, descent, lineGap;
			stbtt_GetFontVMetrics(font, &ascent, &descent, &lineGap);

			float pixelScale = stbtt_ScaleForPixelHeight(font, fontScale);

			int cellY = cacheCursor / cellCols;
			int cellX = cacheCursor % cellCols;

			int startX = cellX * charSize;
			int startY = cellY * charSize;

			GlyphBlit(tex->texMem, tex->width, tex->height, startX, startY, cBmp, cW, cH);

			free(cBmp);

			int advanceWidth = 0, leftSideBearing = 0;
			stbtt_GetCodepointHMetrics(font, codePoint, &advanceWidth, &leftSideBearing);

			int x0, y0, x1, y1;
			stbtt_GetCodepointBitmapBox(font, codePoint, pixelScale, pixelScale, &x0, &y0, &x1, &y1);
			CodepointInfo pointInfo = {};
			pointInfo.codepoint = codePoint;
			pointInfo.x = startX;
			pointInfo.y = startY;
			pointInfo.w = cW;
			pointInfo.h = cH;
			pointInfo.xOffset = x0;
			pointInfo.yOffset = y0;
			pointInfo.xAdvance = pixelScale * advanceWidth;

			codepointListing.EnsureCapacity(cacheCursor + 1);
			codepointListing.count = BNS_MAX(codepointListing.count, cacheCursor + 1);
			codepointListing.data[cacheCursor] = pointInfo;

			cacheCursor = (cacheCursor + 1) % (cellCols * cellRows);
		}
	}
}
Beispiel #3
0
void Font::measureText(const std::wstring str, int& width, int& height, int max_width) {
	if(!isLoaded())
	{
		return NULL;
	}

	Image char_render;
	std::vector<unsigned char> char_raster;
	int bx, by, bw, bh;
	int ascent, descent, lineGap;
	int sx = 0, sy = 0;

	stbtt_GetFontVMetrics(&m_info, &ascent, &descent, &lineGap);

	ascent *= m_scale;
	descent *= m_scale;
	lineGap *= m_scale;

	width = 0;
	height = 0;

	for(int i = 0; i < str.length(); i++) {
		if(str[i] == L'\n') {
			sy += ascent - descent + lineGap;
			sx = 0;
			continue;
		}

		stbtt_GetCodepointBitmapBox(&m_info, str[i], m_scale, m_scale,
				&bx, &by, &bw, &bh);

		int cwidth = bw - bx;
		int cheight = bh - by;
		int oy = ascent + by;

		if(max_width > 0 && sx + cwidth > max_width) {
			sy += ascent - descent + lineGap;
			sx = 0;
		}

		int advance;
		stbtt_GetCodepointHMetrics(&m_info, str[i], &advance, 0);
		if(sy + oy + cheight > height) {
			height = sy + oy + cheight;
		}
		if(sx + cwidth > width) {
			width = sx + cwidth;
		}

		sx += advance * m_scale;

		int kerning = stbtt_GetCodepointKernAdvance(&m_info, str[i], str[i + 1]);

		sx += kerning * m_scale;
	}
}
Beispiel #4
0
	fm::rect2i FontRenderer::getGlyphRect(const fm::Uint32 &letter,Glyph::Style /* style */) const
	{
		float scale = stbtt_ScaleForMappingEmToPixels((stbtt_fontinfo*)m_stbFontInfo, m_currentSize);
			
		int ix0,ix1,iy0,iy1;
		stbtt_GetCodepointBitmapBox((stbtt_fontinfo*)m_stbFontInfo, letter, scale, scale, &ix0, &iy0, &ix1, &iy1);
		
		int leftSideBearing, advanceWidth;
		stbtt_GetCodepointHMetrics((stbtt_fontinfo*)m_stbFontInfo, letter, &advanceWidth, &leftSideBearing);
		
		return fm::rect2i(fm::vec2i(leftSideBearing*scale,iy0),fm::vec2i(ix1-ix0,iy1-iy0));
	}	
JNIEXPORT void JNICALL Java_com_badlogic_gdx_graphics_g2d_stbtt_StbTrueType_getCodepointBitmapBox(JNIEnv* env, jclass clazz, jlong info, jint codePoint, jfloat scaleX, jfloat scaleY, jintArray obj_box) {
	int* box = (int*)env->GetPrimitiveArrayCritical(obj_box, 0);


//@line:147

		int x0, y0, x1, y1;
		x0 = y0 = x1 = y1 = 0;
		stbtt_GetCodepointBitmapBox((stbtt_fontinfo*)info, codePoint, scaleX, scaleY, &x0, &y0, &x1, &y1);
		box[0] = x0;
		box[1] = y0;
		box[2] = x1;
		box[3] = y1;
	
	env->ReleasePrimitiveArrayCritical(obj_box, box, 0);

}
Beispiel #6
0
bool TruetypeFont::renderGlyph(char32_t c)
{
    bool result = false;
    GlyphPtr glyph = char2glyph[c];
    if(!glyph)
    {
        result = true;
        glyph.reset(new Glyph);
//    glyph->render(face, size, c);

        int advance, lsb;
        stbtt_GetCodepointHMetrics(_fontinfo, c, &advance, &lsb);

        int ix0, iy0, ix1, iy1;
        stbtt_GetCodepointBitmapBox(_fontinfo, c, _vscale, _vscale, &ix0, &iy0, &ix1, &iy1);
        int width = ix1-ix0;
        int height = iy1-iy0;
        unsigned char* bmpmem = (unsigned char*)malloc(width*height);
        stbtt_MakeCodepointBitmapSubpixel(_fontinfo, bmpmem, width, height, width, _vscale, _vscale, .0, .0, c);

        BitmapPtr result(new Bitmap(width,
                                    height,
                                    GL_RGBA,
                                    GL_ALPHA,
                                    bmpmem));
        result->flip();
        free(bmpmem);
        glyph->bitmap = result;
        glyph->advance = floorf(advance*_vscale);
        glyph->xoffset = floorf(lsb*_vscale);
        glyph->yoffset = -iy1;

        char2glyph[c] = glyph;
        glyphs.push_back(glyph);
    }
    return result;
}
Beispiel #7
0
void Font::drawStringUnicode(int x, int y, const std::wstring& str, Color color, bool top_screen, bool side)
{
	if(!isLoaded())
	{
		return;
	}
	
	Image char_render;
	std::vector<unsigned char> char_raster;
	int bx, by, bw, bh;
	int ascent, descent, lineGap;
	int sx = 0, sy = 0;

	stbtt_GetFontVMetrics(&m_info, &ascent, &descent, &lineGap);

	ascent *= m_scale;
	descent *= m_scale;

	for (unsigned int i = 0; i < str.length(); i++)
	{
		if(str[i + 1] == L'\n') {
			sy += lineGap * m_scale;
			sx = 0;
			continue;
		}
		
		stbtt_GetCodepointBitmapBox(&m_info, str[i], m_scale, m_scale,
			&bx, &by, &bw, &bh);

		int width = bw - bx;
		int height = bh - by;

		int oy = ascent + by;

		char_raster.resize(width * height);

		stbtt_MakeCodepointBitmap(&m_info, &char_raster[0], width, height,
			width, m_scale, m_scale, str[i]);

		char_render.create(width, height, Color(0, 0, 0, 0));

		for (int ix = 0; ix < width; ix++) {
			for (int iy = 0; iy < height; iy++) {
				if (char_raster[ix + iy * width] != 0)
					char_render.putPixel(ix, iy, Color(color.r, color.g, color.b,
					char_raster[ix + iy * width]));
			}
		}

		char_render.draw(sx + x, sy + y + oy, top_screen, side);

		int advance;
		stbtt_GetCodepointHMetrics(&m_info, str[i], &advance, 0);

		sx += advance * m_scale;

		int kerning = stbtt_GetCodepointKernAdvance(&m_info, str[i], str[i + 1]);

		sx += kerning * m_scale;
	}
}
Beispiel #8
0
	fg::Image FontRenderer::renderGlyph(const fm::Uint32 &letter,Glyph::Style style,fm::vec2 *leftDown) const
	{
		if (!m_fileContent) 
			return fg::Image();
		
		if (letter == ' ')
		{
			float scl = stbtt_ScaleForMappingEmToPixels((stbtt_fontinfo*)m_stbFontInfo, m_currentSize);
			
			int ix0,ix1,iy0,iy1;
			
			stbtt_GetCodepointBitmapBox((stbtt_fontinfo*)m_stbFontInfo, 'w', scl, scl, &ix0, &iy0, &ix1, &iy1);
			
			return fg::Image(fm::vec2s(ix1-ix0,iy1-iy0),fg::Color(255,255,255,0));
		}
		
		unsigned char *bitmap;
		fm::vec2i size,offset;

		bitmap = stbtt_GetCodepointBitmap((stbtt_fontinfo*)m_stbFontInfo, 0,stbtt_ScaleForMappingEmToPixels((stbtt_fontinfo*)m_stbFontInfo, m_currentSize), letter, &size.w, &size.h, &offset.x, &offset.y);
		
		if (leftDown)
			*leftDown = offset;
		
		fg::Image img;
		img.create(size);
		
		Cv(size)
			img.set(p,fg::Color(255,bitmap[p.x + p.y*size.w]));
		
		stbtt_FreeBitmap(bitmap,0);
		
		return img;
		
		(void)style;
/*
		// if rendering upper/lower index decrease size
		unsigned int originalSize = m_currentSize;
		if ((style & Glyph::Subscript) xor (style & Glyph::Superscript ))
			setCharacterSize(m_currentSize*.7);

		// get glyph index
		FT_UInt glyphIndex = FT_Get_Char_Index(*((FT_Face*)m_ftFaceData),letter);

		// load glyph data
		int error = FT_Load_Glyph(*((FT_Face*)m_ftFaceData),glyphIndex,FT_LOAD_FORCE_AUTOHINT | FT_LOAD_DEFAULT);

		if (error)
			return fg::Image();

		FT_Face face = (*((FT_Face*)m_ftFaceData));

		// embolden and italicise
		if (style & fg::Glyph::Bold)
			FT_GlyphSlot_Embolden(face->glyph);

		if (style & fg::Glyph::Italic)
			FT_GlyphSlot_Oblique(face->glyph);

		// render glyph image
		error = FT_Render_Glyph( face->glyph,FT_RENDER_MODE_NORMAL);

		if (error)
			return fg::Image();

		// get bitmap details
		fm::Size width  = face->glyph->bitmap.width;
		fm::Size height = face->glyph->bitmap.rows;

		std::vector<fm::Uint8> bitmap(face->glyph->bitmap.buffer,face->glyph->bitmap.buffer+width*height);

		float offsetx = (face->glyph->metrics.horiBearingX>>6);
		float offsety = (face->glyph->metrics.horiBearingY>>6)-float(height);

		// manually create upper index if requested
		if (style & fg::Glyph::Superscript && !(style & fg::Glyph::Subscript))
		{
			fm::Size deltaH = height*0.4f;
			bitmap.resize(width*(height+deltaH),0);
			offsety+=deltaH;
			height+=deltaH;
		}

		// manually create lower index if requested
		if (style & fg::Glyph::Subscript && !(style & fg::Glyph::Superscript))
		{
			fm::Size deltaH = height*0.4f;
			bitmap.resize(width*(height+deltaH),0);
			offsety-=deltaH;
			Cxy(width,height)
			{
				fm::Uint8 tmp = bitmap[x+int(height+deltaH-y-1)*width];
				bitmap[x+int(height+deltaH-y-1)*width] = bitmap[x+int(height-y-1)*width];
				bitmap[x+int(height-y-1)*width] = tmp;
			}
			height+=height/2.f;
		}

		// reset the size back if needed
		if ((style & fg::Glyph::Subscript) xor (style & fg::Glyph::Superscript))
			setCharacterSize(originalSize);

		// manually strike through and/or underline if requested
		for (int i=0;i<bool(style & fg::Glyph::Crossed)+bool(style & fg::Glyph::Underlined);i++)
		{
			bool crossed = ((style & fg::Glyph::Crossed) && i==0);

			int ymax = offsety+height;
			float &ymin = offsety;

			// if the line would be out of the image (take account the y offset like in '~' as the leeter might not "sit" on the baseline)
			int lineW = int(m_metrics.maxH/15.f);
			int lineP = int(m_metrics.maxH*(crossed ? 0.2 : 0.0) );
			if (ymin > lineP)
			{
				bitmap.resize(width*(height+int(ymin-lineP)),0);

				ymin = lineP;
				height = ymax-ymin;
			}
			else if (ymax < lineW+lineP)
			{
				bitmap.resize(width*(height+(lineW+lineP-ymax)),0);

				ymax = lineW+lineP;
				height = ymax-ymin;
			}

			// simply set the value to one for every pixel in the line
			Cx(width)
				Cy((unsigned int)(lineW))
					bitmap[x+(height-1-y+int(ymin)-lineP)*width] = 255;
		}

		// manually create outline if requested
		if (style & fg::Glyph::Outline)
		{
			std::vector<fm::Uint8> oldData = bitmap;

			bitmap.resize((width+2)*(height+2));

			// get a bigger bitmap
			Cxy(width+2,height+2)
			{
				int db=0,sum=0;
				int ax=int(x)-1,ay=int(y)-1;
				int curVal = fm::rect2i(0,0,width-1,height-1).contains(fm::vec2i(ax,ay)) ? oldData[ax+ay*width] : 0;

				// this algorithm uses the difference between the neighbour pixels (the bigger the difference the bigger the output will be)
				for(int x1=-1;x1<=1;x1++) for(int y1=-1;y1<=1;y1++)
				{
					if (fm::vec2(x1,y1).LENGTH()>=1)
					{
						int deltaVal=(fm::rect2i(0,0,width-1,height-1).contains(fm::vec2i(ax+x1,ay+y1))) ? oldData[(ax+x1)+(ay+y1)*width]-curVal : -curVal;
						deltaVal*=(deltaVal > 0 ? .9 : 1.4);
						sum+=(deltaVal < 0 ? -deltaVal : deltaVal),
						db++;
					}
				}

				// do some scaling on the value and clamp it to [0;255]
				bitmap[x+y*(width+2)] = fm::math::min(255.0,db ? sum/db*1.6 : 0.0);
			}

			// update glyph image details
			width+=2,height+=2;offsetx--,offsety--;
		}

		Cxy(width,height)
		{
			float f = bitmap[y*width + x] / 255.0;
			bitmap[y*width + x] = fm::math::sqrt3(f) * 255.0;
		}

		if (leftDown)
			leftDown->x = offsetx,
			leftDown->y = offsety;

		// convert the bitmap to fg::Image
		fg::Image img;
		img.create(width,height);

		Cxy(width,height)
			img.setPixel(x,y,fg::Color(255,255,255,bitmap[y*width + x]));
*/
		return img;
	}
Beispiel #9
0
void Text::setText(const char* text)
{
	int length = strlen(text);
	if(length > 255) length = 255;

	memset(pTextBuffer, 0x00, 256);
	memcpy(pTextBuffer, text, length);

	const char* p;

	int width=0,height=0;

    /* calculate font scaling */
	LOCK_ACQUIRE(gFtLock);
    //float scale = stbtt_ScaleForPixelHeight(gFontInfo, pPixelSize);
    float scale = stbtt_ScaleForMappingEmToPixels(gFontInfo, pPixelSize);

    int ascent, descent, lineGap;
    stbtt_GetFontVMetrics(gFontInfo, &ascent, &descent, &lineGap);

    ascent *= scale;
    descent *= scale;

    height = ascent;
    // calculate bitmap size
    for (p = pTextBuffer; *p; p++)
    {
        /* how wide is this character */
        int ax;
        stbtt_GetCodepointHMetrics(gFontInfo, p[0], &ax, 0);
        width += ax * scale;

        /* add kerning */
        int kern;
        kern = stbtt_GetCodepointKernAdvance(gFontInfo, p[0], p[1]);
        width += kern * scale;
    }

    //check if old bitmap exists, and delete it
	uint8_t* oldBitmap = pBitmap;
	pBitmap = (uint8_t*)malloc(width*height);
	if(oldBitmap)
	{
		free(oldBitmap);
	}


	memset(pBitmap,0,width*height);

	pBitmapWidth = width;
	pBitmapHeight = height;

	setSizeN(
			2.f*((float)pBitmapWidth)/GetCore()->screen_width,
			2.f*((float)pBitmapHeight)/GetCore()->screen_width
	);

	int x=0,y=0;

	// render text to buffer
	for (p = pTextBuffer; *p; p++)
    {
        /* get bounding box for character (may be offset to account for chars that dip above or below the line */
        int c_x1, c_y1, c_x2, c_y2;
        stbtt_GetCodepointBitmapBox(gFontInfo, p[0], scale, scale, &c_x1, &c_y1, &c_x2, &c_y2);

        /* compute y (different characters have different heights */
        y = ascent + c_y1;

        /* render character (stride and offset is important here) */
        int byteOffset = x + (y  * width);
        stbtt_MakeCodepointBitmap(gFontInfo, pBitmap + byteOffset, c_x2 - c_x1, c_y2 - c_y1, width, scale, scale, p[0]);

        /* how wide is this character */
        int ax;
        stbtt_GetCodepointHMetrics(gFontInfo, p[0], &ax, 0);
        x += ax * scale;

        /* add kerning */
        int kern;
        kern = stbtt_GetCodepointKernAdvance(gFontInfo, p[0], p[1]);
        x += kern * scale;
    }
	LOCK_RELEASE(gFtLock);

	updateBitmap = true;
}
Beispiel #10
0
void Font::drawStringToBuffer(int x, int y, const std::wstring& str, Color color, unsigned char* buffer, int buffer_width, int buffer_height, int bitsperpixel, int max_width)
{
	std::vector<unsigned char> char_raster;
	int bx, by, bw, bh;
	int ascent, descent, lineGap;
	int sx = 0, sy = 0;
	int width, height;

	stbtt_GetFontVMetrics(&m_info, &ascent, &descent, &lineGap);

	ascent *= m_scale;
	descent *= m_scale;
	lineGap *= m_scale;

	this->measureText(str, width, height, max_width);

	for (unsigned int i = 0; i < str.length(); i++)
	{
		if(str[i] == L'\n') {
			sy += ascent - descent + lineGap;
			sx = 0;
			continue;
		}

		stbtt_GetCodepointBitmapBox(&m_info, str[i], m_scale, m_scale,
				&bx, &by, &bw, &bh);

		int char_width = bw - bx;
		int char_height = bh - by;
		int oy = ascent + by;

		if(max_width > 0 && sx + char_width > max_width) {
			sy += ascent - descent + lineGap;
			sx = 0;
		}

		char_raster.resize(char_width * char_height);

		stbtt_MakeCodepointBitmap(&m_info, &char_raster[0], char_width, char_height,
				char_width, m_scale, m_scale, str[i]);

		for (int ix = 0; ix < char_width; ix++) {
			for (int iy = 0; iy < char_height; iy++) {
				int xpos = x + sx + ix;
				int ypos = buffer_height - (y + sy + oy + iy) -1;
				if (char_raster[ix + iy * char_width] != 0 && xpos < buffer_width && ypos < buffer_height) {
					unsigned int alpha = char_raster[ix + iy * char_width];
					unsigned int inv_alpha = 255 - alpha;
					if (bitsperpixel == 24) {
						unsigned char bg_r = buffer[3 * (xpos + ypos * buffer_width) + 0];
						unsigned char bg_g = buffer[3 * (xpos + ypos * buffer_width) + 1];
						unsigned char bg_b = buffer[3 * (xpos + ypos * buffer_width) + 2];

						unsigned char r = (unsigned char)((alpha * color.r + inv_alpha * bg_r) >> 8);
						unsigned char g = (unsigned char)((alpha * color.g + inv_alpha * bg_g) >> 8);
						unsigned char b = (unsigned char)((alpha * color.b + inv_alpha * bg_b) >> 8);

						buffer[3 * (xpos + ypos * buffer_width) + 0] = b;
						buffer[3 * (xpos + ypos * buffer_width) + 1] = g;
						buffer[3 * (xpos + ypos * buffer_width) + 2] = r;
					} else {
						unsigned char bg_r = buffer[4 * (xpos + ypos * buffer_width) + 0];
						unsigned char bg_g = buffer[4 * (xpos + ypos * buffer_width) + 1];
						unsigned char bg_b = buffer[4 * (xpos + ypos * buffer_width) + 2];
						unsigned char bg_a = buffer[4 * (xpos + ypos * buffer_width) + 3];

						unsigned char a;
						if(alpha == 255 || bg_a == 255) {
							a = 255;
						} else {
							a = alpha + (inv_alpha * bg_a) / 256;
						}
						unsigned int r,g,b;
						if(a == 0) {
							r = 0;
							g = 0;
							b = 0;
						} else {
							r = (alpha * color.r + (inv_alpha * bg_a * bg_r) / 256) / a;
							b = (alpha * color.b + (inv_alpha * bg_a * bg_b) / 256) / a;
							g = (alpha * color.g + (inv_alpha * bg_a * bg_g) / 256) / a;
						}
						buffer[4 * (xpos + ypos * buffer_width) + 0] = b > 255 ? 255 : b;
						buffer[4 * (xpos + ypos * buffer_width) + 1] = g > 255 ? 255 : g;
						buffer[4 * (xpos + ypos * buffer_width) + 2] = r > 255 ? 255 : r;
						buffer[4 * (xpos + ypos * buffer_width) + 3] = a;
					}
				}
			}
Beispiel #11
0
Bytes& StbFontRenderer::RenderText(
    const FontPtr&        data,
    const FontProperties& properties,
    cstring               text,
    Bytes&                output,
    Size&                 imageSize)
{
    using irect = rect<int>;

    auto stb_data = &data->info;
    auto scale    = properties.scale;

    Bytes stringData = Bytes::CreateString(text);
    szptr bit_w      = 0;
    int   x          = 0;

    /* We use this to find kerning character */
    auto shadowChar = stringData.begin();

    for(auto c : stringData)
    {
        irect bbox = {};
        stbtt_GetCodepointBitmapBox(
            stb_data, c, scale, scale, &bbox.x, &bbox.y, &bbox.w, &bbox.h);

        int y = properties.ascent + bbox.y, ax = 0, kern = 0;

        stbtt_GetCodepointHMetrics(stb_data, c, &ax, nullptr);
        kern = stbtt_GetCodepointKernAdvance(stb_data, c, *shadowChar);

        x += scale * (ax + kern);

        imageSize.w = CMath::max<u32>(imageSize.w, x + (bbox.w - bbox.x));
        imageSize.h = CMath::max<u32>(imageSize.h, y + (bbox.h - bbox.y));
    }

    bit_w      = C_FCAST<szptr>(imageSize.w);
    output     = Bytes::Alloc(imageSize.area());
    shadowChar = stringData.begin();
    x          = 0;

    for(auto c : stringData)
    {
        ++shadowChar;

        irect bbox = {};
        stbtt_GetCodepointBitmapBox(
            stb_data, c, scale, scale, &bbox.x, &bbox.y, &bbox.w, &bbox.h);

        int y = properties.ascent + bbox.y, ax = 0, kern = 0;

        szptr offset = C_FCAST<szptr>(x + y * bit_w);

        stbtt_MakeCodepointBitmap(
            stb_data,
            &output[offset],
            (bbox.w - bbox.x),
            (bbox.h - bbox.y),
            bit_w,
            scale,
            scale,
            c);

        stbtt_GetCodepointHMetrics(stb_data, c, &ax, nullptr);
        kern = stbtt_GetCodepointKernAdvance(stb_data, c, *shadowChar);

        x += scale * (ax + kern);
    }

    return output;
}