// // 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(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