You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
176 lines
7.3 KiB
C++
176 lines
7.3 KiB
C++
//
|
|
// Created by Matthew on 2024/1/4.
|
|
//
|
|
|
|
#include "TextPaint.h"
|
|
|
|
namespace puttext
|
|
{
|
|
void RasterCallback(const int y, const int count, const FT_Span * const spans, void * const user)
|
|
{
|
|
Spans *sptr = (Spans *)user;
|
|
for (int i = 0; i < count; ++i)
|
|
sptr->push_back(Span(spans[i].x, y, spans[i].len, spans[i].coverage));
|
|
}
|
|
|
|
void RenderSpans(FT_Library &library, FT_Outline * const outline, Spans *spans)
|
|
{
|
|
FT_Raster_Params params;
|
|
memset(¶ms, 0, sizeof(params));
|
|
params.flags = FT_RASTER_FLAG_AA | FT_RASTER_FLAG_DIRECT;
|
|
params.gray_spans = RasterCallback;
|
|
params.user = spans;
|
|
|
|
FT_Outline_Render(library, outline, ¶ms);
|
|
}
|
|
|
|
TextPaint::TextPaint()
|
|
{
|
|
library = NULL;
|
|
face = NULL;
|
|
|
|
FT_Init_FreeType(&library);
|
|
}
|
|
|
|
TextPaint::~TextPaint()
|
|
{
|
|
// Clean up the library
|
|
FT_Done_FreeType(library);
|
|
}
|
|
|
|
bool TextPaint::LoadFont(const char* fontFilePath)
|
|
{
|
|
return FT_New_Face(library, fontFilePath, 0, &face) == 0;
|
|
}
|
|
|
|
bool TextPaint::DrawText(Pixel32 *pxl, const wchar_t* str, int size, const Pixel32 &fontCol, const Pixel32 &outlineCol, float outlineWidth)
|
|
{
|
|
// Set the size to use.
|
|
if (FT_Set_Char_Size(face, size << 6, size << 6, 90, 90) != 0) {
|
|
return false;
|
|
}
|
|
|
|
const wchar_t* ch = str;
|
|
for (; *ch != 0; ch++)
|
|
{
|
|
// Load the glyph we are looking for.
|
|
FT_UInt gindex = FT_Get_Char_Index(face, *ch);
|
|
if (FT_Load_Glyph(face, gindex, FT_LOAD_NO_BITMAP) == 0)
|
|
{
|
|
// Need an outline for this to work.
|
|
if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE)
|
|
{
|
|
// Render the basic glyph to a span list.
|
|
Spans spans;
|
|
RenderSpans(library, &face->glyph->outline, &spans);
|
|
|
|
// Next we need the spans for the outline.
|
|
Spans outlineSpans;
|
|
|
|
// Set up a stroker.
|
|
FT_Stroker stroker;
|
|
FT_Stroker_New(library, &stroker);
|
|
FT_Stroker_Set(stroker,(int)(outlineWidth * 64),FT_STROKER_LINECAP_ROUND,FT_STROKER_LINEJOIN_ROUND,0);
|
|
|
|
FT_Glyph glyph;
|
|
if (FT_Get_Glyph(face->glyph, &glyph) == 0)
|
|
{
|
|
FT_Glyph_StrokeBorder(&glyph, stroker, 0, 1);
|
|
// Again, this needs to be an outline to work.
|
|
if (glyph->format == FT_GLYPH_FORMAT_OUTLINE)
|
|
{
|
|
// Render the outline spans to the span list
|
|
FT_Outline *o = &reinterpret_cast<FT_OutlineGlyph>(glyph)->outline;
|
|
RenderSpans(library, o, &outlineSpans);
|
|
}
|
|
|
|
// Clean up afterwards.
|
|
FT_Stroker_Done(stroker);
|
|
FT_Done_Glyph(glyph);
|
|
|
|
// Now we need to put it all together.
|
|
if (!spans.empty())
|
|
{
|
|
// Figure out what the bounding rect is for both the span lists.
|
|
Rect rect(spans.front().x, spans.front().y, spans.front().x, spans.front().y);
|
|
for (Spans::iterator s = spans.begin(); s != spans.end(); ++s)
|
|
{
|
|
rect.Include(Vec2(s->x, s->y));
|
|
rect.Include(Vec2(s->x + s->width - 1, s->y));
|
|
}
|
|
for (Spans::iterator s = outlineSpans.begin(); s != outlineSpans.end(); ++s)
|
|
{
|
|
rect.Include(Vec2(s->x, s->y));
|
|
rect.Include(Vec2(s->x + s->width - 1, s->y));
|
|
}
|
|
|
|
#if 0
|
|
// This is unused in this test but you would need this to draw
|
|
// more than one glyph.
|
|
float bearingX = face->glyph->metrics.horiBearingX >> 6;
|
|
float bearingY = face->glyph->metrics.horiBearingY >> 6;
|
|
float advance = face->glyph->advance.x >> 6;
|
|
#endif
|
|
|
|
// Get some metrics of our image.
|
|
int imgWidth = rect.Width(),
|
|
imgHeight = rect.Height(),
|
|
imgSize = imgWidth * imgHeight;
|
|
|
|
// Allocate data for our image and clear it out to transparent.
|
|
// Pixel32 *pxl = new Pixel32[imgSize];
|
|
// memset(pxl, 0, sizeof(Pixel32) * imgSize);
|
|
|
|
// Loop over the outline spans and just draw them into the
|
|
// image.
|
|
for (Spans::iterator s = outlineSpans.begin(); s != outlineSpans.end(); ++s)
|
|
for (int w = 0; w < s->width; ++w)
|
|
{
|
|
pxl[(int)((imgHeight - 1 - (s->y - rect.ymin)) * imgWidth
|
|
+ s->x - rect.xmin + w)] =
|
|
Pixel32(outlineCol.r, outlineCol.g, outlineCol.b,
|
|
s->coverage);
|
|
}
|
|
|
|
// Then loop over the regular glyph spans and blend them into
|
|
// the image.
|
|
for (Spans::iterator s = spans.begin(); s != spans.end(); ++s)
|
|
{
|
|
for (int w = 0; w < s->width; ++w) {
|
|
Pixel32 &dst =
|
|
pxl[(int) (
|
|
(imgHeight - 1 - (s->y - rect.ymin)) * imgWidth
|
|
+ s->x - rect.xmin + w)];
|
|
Pixel32 src = Pixel32(fontCol.r, fontCol.g, fontCol.b,
|
|
s->coverage);
|
|
dst.r = (int) (dst.r + ((src.r - dst.r) * src.a) / 255.0f);
|
|
dst.g = (int) (dst.g + ((src.g - dst.g) * src.a) / 255.0f);
|
|
dst.b = (int) (dst.b + ((src.b - dst.b) * src.a) / 255.0f);
|
|
dst.a = MIN(255, dst.a + src.a);
|
|
}
|
|
}
|
|
// Dump the image to disk.
|
|
// WriteTGA(fileName, pxl, imgWidth, imgHeight);
|
|
|
|
// delete [] pxl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#if 0
|
|
int TextPaint::DrawText(Pixel32 *pxl, const char * fontFilePath)
|
|
{
|
|
// Dump out a single glyph to a tga.
|
|
WriteGlyphAsTGA(pxl, 100,
|
|
Pixel32(255, 90, 30),
|
|
Pixel32(255, 255, 255),
|
|
3.0f);
|
|
|
|
// Now that we are done it is safe to delete the memory.
|
|
|
|
}
|
|
}
|
|
#endif
|
|
} // namespace puttext
|