Efficient text rendering with Freetype
FreeType is a free font rendering software library. It is written in C language. The design is small, efficient, highly customizable and portable. At the same time, it can produce high-quality output (font image) in most vector and bitmap font formats.
Freetype use
Header file
/* Contains various macro declarations that will be used later to #include the appropriate public FreeType 2 header file */ #include <ft2build.h> /* Contains freetype2 API header files. Macro ft should be used_ FREETYPE_ H to achieve this, as shown in the following example.*/ #include FT_FREETYPE_H #include FT_BITMAP_H #include FT_OUTLINE_H
Library initialization
To initialize the freetype library, you need to create an FT named library_ A variable of type library and call the function FT_Init_FreeType.
FT_Library library; error = FT_Init_FreeType( &library ); if ( error ) { ... an error occurred during library initialization ... }
-
Create a new instance of the FreeType 2 library and set the handle library for it
-
Load each module supported by FreeType in the library, such as TrueType, Type 1, CID - Key & OpenType / CFF fonts.
Load font appearance
The project needs to load fonts from files. Here, take loading from files as an example
By calling FT_New_Face creates a new font appearance. A font appearance describes a given font and style. For example, "Times New Roman Regular" and "Times New Roman Italic" correspond to two different font appearances.
FT_Library library; /* handle to library */ FT_Face face; /* handle to face object */ error = FT_Init_FreeType( &library ); if ( error ) { ... } error = FT_New_Face( library, "/usr/share/fonts/truetype/arial.ttf", 0, &face ); if ( error == FT_Err_Unknown_File_Format ) { ... the font file could be opened and read, but it appears ... that its font format is unsupported } else if ( error ) { ... another error code means that the font file could not ... be opened or read, or that it is broken... }
FT_New_Face parameter description
- Library: handle to the FreeType library instance that creates the font appearance object
- filepathname: font file pathname (standard C string)
- face_index:
- Generally, font formats allow multiple font appearances to be embedded in a file
- This index tells you which appearance to load and returns an error if its value is too large
- But index 0 is always valid
- To know how much appearance a given font file contains, set face_ Set index to - 1 and check face - > num_ The value of faces, which indicates how many appearances are embedded in the font file
- face: a pointer to the appearance of the font. If an error occurs, it is set to NULL.
To create a font interface from a file:
/** * @brief load font * @details * Loads a font from a file based on the font attributes \n * passed in and stores it in an array of fonts in CfontManager * @param library Font library, initialize before use * @param filename The font path * @param face_index font face, Usually 0 * @param tall font tall in pixels, also known as font size * @param bold bold or not * @param italic italic or not * @param antialias antialias or not * @return true create successfully * @return false Font creation failed */ bool CFont::create(FT_Library library, const char* filename, FT_Long face_index, int tall, bool bold, bool italic, bool antialias) { FT_Error err; // bind the font face in the file to m_face if ((err = FT_New_Face(library, filename, face_index, &m_face)) != FT_Err_Ok) { printf("FT_New_Face() Error %d\n", err); return false; } // Sets the render pixel size in preparation for bitmap generation if ((err = FT_Set_Pixel_Sizes(m_face, 0, tall)) != FT_Err_Ok) { printf("FT_Set_Pixel_Sizes() Error %d\n", err); return false; } string path = filename; m_name = CStringUtil::getNameFromSrc(path); m_library = library; m_antialias = antialias; m_bold = bold; m_tall = tall; if (italic) { FT_Matrix m; m.xx = 0x10000L; m.xy = 0.5f * 0x10000L; m.yx = 0; m.yy = 0x10000L; FT_Set_Transform(m_face, &m, NULL); } return true; }
Read appearance data
Font appearance object is an integrated description of all data describing font appearance. Generally, you can directly use - > to access the data in the font appearance object, or you can see the data contained in the font appearance object in the debugging window. Its complete data is defined by ft_ Given by facerec
The following is a detailed description of some core data:
- num_glyphs: this variable provides the number of glyphs available in the font appearance. A glyph can be understood as a character image
- face_flags: a 32-bit integer containing bit flags that describe some surface properties. For example, flag FT_FACE_FLAG_SCALABLE the font format that represents the appearance of a font is scalable and can be rendered as characters of any size. For more information, read the FreeType 2 API reference.
- units_per_EM: this field is only valid for scalable formats (otherwise set to 0). It represents the number of font units covered by EM.
- num_fixed_sizes: this field gives the number of bitmaps embedded in the current font appearance. A strike can be understood as a series of font data rendered for a given character pixel size
- available_sizes: a point to FT_ Bitmap_ Pointer to the size element array. Each FT_Bitmap_Size indicates the horizontal and vertical character pixel size of each strike that appears in the appearance.
Set pixel size
In the previous step, we learned the pixel size of the optional character rendering. Only after the pixel size of the character is correctly set, can the character be rendered into a bitmap.
FreeType 2 uses size objects to model all information related to a given character size for a given font appearance. For example, for a 12 point character size, the size object holds the values of some indicators, such as the top half (or bottom half) of the letter or the text height, expressed in 1 / 64 of the pixel (however, these values are rounded to an integer, i.e. a multiple of 64)). When calling ft_ New_ When the face function (or one of its siblings) is, it automatically creates a new size object for the returned font appearance. This size object can be accessed directly as face - > size.
FreeType provides the following two ways to set the character size:
Method 1: FT_Set_Char_Size
error = FT_Set_Char_Size( face, /* handle to face object */ 0, /* char_width in 1/64th of points */ 16*64, /* char_height in 1/64th of points */ 300, /* horizontal device resolution */ 300 ); /* vertical device resolution */
be careful
- The character width and height are specified in 1 / 64 of the dot. The point is the physical distance, equal to 1 / 72 inch. Usually, it is not equivalent to a pixel.
- A character width value of 0 means "same as the character height", and a character height value of 0 means "same as the character width". Otherwise, you can specify different character widths and heights.
- Horizontal and vertical device resolutions are expressed in dots per inch or dpi. For display devices such as screens, the standard value is 72 or 96 dpi. Resolution is used to calculate the character pixel size according to the character point size. A horizontal resolution of 0 means "same as vertical resolution", and a vertical resolution of 0 means "same as horizontal resolution". If both values are zero, both dimensions use 72 dpi.
Method 2: FT_Set_Pixel_Sizes
error = FT_Set_Pixel_Sizes( face, /* handle to face object */ 0, /* pixel_width */ 16 ); /* pixel_height */
This example sets the character pixel size to 16 × 16 pixels. As mentioned earlier, a value of 0 for one dimension means "same as the other dimension". Notice that both functions return error codes. Usually, when trying to set the pixel size to face - > fixed_ Fixed size font formats (such as FNT or PCF) can cause errors when values are not listed in the sizes array.
Load glyph symbols
**Step 1 convert character codes to glyph indexes**
Typically, applications want to load glyph images based on character codes, which are unique values that define a given encoded character. For example, code 65 (0x41) represents the character "a" in ASCII encoding. A face object contains one or more tables called charmaps, which are used to convert character codes into font indexes. For example, most older TrueType fonts contain two character mappings: one for converting Unicode character codes to glyph indexes and the other for converting Apple Roman codes to glyph indexes. Such fonts can be used on Windows (using Unicode) and older MacOS versions (using apple Roman). Also note that a given charmap may not map to all glyphs that exist in the font.
By default, FreeType attempts to select the Unicode character set when creating a new font appearance object. If the font does not contain such a character mapping table, it simulates the Unicode character mapping table according to the font name. Note that if the font name is not standard, the font may be omitted.
glyph_index = FT_Get_Char_Index( face, charcode );
FreeType provides the above interface to obtain font index
Note that this is one of the rare FreeType functions that does not return an error code. However, when the given character code has no font image on the face, the value 0 will be returned. By convention, it always corresponds to a special font image, called missing font, which is usually displayed as a box or a space.
**Step 2 read the data to the font slot by self indexing**
Once the font index is available, the corresponding font image can be loaded. The latter can be stored in font files in various formats. For fixed size formats such as FNT or PCF, each image is a bitmap.
Glyph images are always stored in special objects called glyph slots. As the name suggests, a glyph slot is a container that can hold one glyph image at a time. It can be a bitmap, outline or other thing. Each font appearance object has a separate font slot object that can be accessed as face - > glyph.
error = FT_Load_Glyph( face, /* handle to face object */ glyph_index, /* glyph index */ load_flags ); /* load flags, see below */
load_ The flags value is a set of bit flags used to indicate some special operations. Default FT_LOAD_DEFAULT is 0. This function attempts to load the corresponding font image from the font appearance. If a bitmap corresponding to the font and pixel size is found, it is loaded into the slot. Embedded bitmaps are always preferred over native image formats because we assume that they are a higher quality version of the same glyph. This can be done by using FT_LOAD_NO_BITMAP flag to change.
Step 3 render glyph slots to bitmaps
error = FT_Render_Glyph( face->glyph, /* glyph slot */ render_mode ); /* render mode */
Parameter render_mode is a set of bit flags that specify how glyph images are rendered. Default FT_RENDER_MODE_NORMAL renders an antialiasing overlay bitmap (also known as a pixel map) with 256 gray levels because this is the default. If you want to generate a 1-bit monochrome bitmap, you can also use FT_RENDER_MODE_MONO. More values are available for FT_Render_Mode enumeration value.
Once you have a bitmap glyph image, you can access it directly through glyph - > bitmap (a simple descriptor of bitmap or pixel graph) and through glyph - > bitmap_left and glyph - > bitmap_top locate it. Note that bitmap_left is the horizontal distance from the current brush position to the leftmost boundary of the font bitmap, and bitmap_top is the vertical distance from the brush position (on the baseline) to the uppermost boundary of the glyph bitmap. Indicates that the upward distance is positive.
/** * @brief Reads character data from glyphs and caches it * @param code character code * @param metrics character metrics * @param screen pixel matrix * @param position pixel position */ void CFont::loadChar(int code, glyphMetrics *metrics, unsigned char* screen, Vec2i& position) { FT_Error err; FT_UInt glyph_index = FT_Get_Char_Index(m_face, (FT_ULong)code); // glyph_index is valid if (glyph_index > 0) { if ((err = FT_Load_Glyph(m_face, glyph_index, FT_OUTLINE_HIGH_PRECISION)) == FT_Err_Ok) { // get font glyph FT_GlyphSlot glyph = m_face->glyph; FT_Render_Mode render_mode = m_antialias ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO; if (m_antialias && m_bold) { if ((err = FT_Outline_EmboldenXY(&glyph->outline, 64, 64)) != FT_Err_Ok) { printf("FT_Outline_EmboldenXY() Error %d\n", err); } } if ((err = FT_Render_Glyph(glyph, render_mode)) == FT_Err_Ok) { // get bitmap FT_Bitmap *bitmap = &glyph->bitmap; switch (bitmap->pixel_mode) { //MONO mode uses only 1 bit to save every pixel, only black and white. case FT_PIXEL_MODE_MONO: { if (!m_antialias && m_bold) { if ((err = FT_Bitmap_Embolden(m_library, bitmap, 60, 0)) != FT_Err_Ok) { printf("FT_Bitmap_Embolden() Error %d\n", err); } } CChar* tm_char = new CChar(code, ImageType::Mono); Draw_MONO_Bitmap(glyph, screen, position, tm_char); //Draw_Bitmap(glyph, screen, position, tm_char); break; } //GRAY mode 1 pixel is saved with 1 byte. case FT_PIXEL_MODE_GRAY: { CChar* tm_char = new CChar(code, ImageType::Gray); Draw_Bitmap(glyph, screen, position, tm_char); //Draw_Bitmap(glyph->bitmap.buffer, glyph->bitmap.pitch, //glyph->bitmap.rows, data, 30); //ConvertGRAYToRGBA(bitmap, m_rgba); break; } default: { //memset(m_rgba, 0xFF, m_rgbaSize); break; } } return; } else { printf("FT_Render_Glyph() Error %d\n", err); } } else { printf("FT_Load_Glyph() Error %d\n", err); } } memset(metrics, 0, sizeof(glyphMetrics)); return ; }
Design of font rendering module
In order to improve the rendering efficiency of font and reduce hardware communication and interaction, the pixel copy strategy is adopted here
The whole project includes four core categories, which are introduced one by one below:
CChar
It mainly stores the font data, character code, type and bitmap data of characters. In order to unify and facilitate operation, the bitmap data uniformly stores the gray mode. After loading and rendering characters, the character objects will be stored in the cache of font class, and priority will be given to searching from the cache when rendering fonts
enum class ImageType { Gray, /**< GRAY Mode 1 pixel is saved with 1 byte */ Mono /**< MONO Each pixel of the mode is only saved with 1 bit, only black and white */ }; class CChar { public: CChar(int code, ImageType type); virtual ~CChar(); void setInfo(glyphMetrics* metrics); void getInfo(glyphMetrics* metrics); unsigned char* getOrCreateBuffer(size_t size); //unsigned char& operator [] (size_t i); int code() const; ImageType type() const; private: int m_code; /**< char code. */ unsigned char* m_data; /**< char bitmap data. */ glyphMetrics m_metrics; /**< char metrics data. */ size_t m_size; /**< char bitmap data size. */ ImageType m_type; /**< char bitmap type. */ };
CFont
CFont is the core class for the module to deal with fonts, which is responsible for font loading, character rendering and caching; CFont object is an object instantiated by font parameters. Different font sizes of the same font need to be stored with different objects. It can be understood that a CFont corresponds to a font appearance of Freetype
class CFont { public: CFont(); virtual ~CFont(); bool create(FT_Library library, const char *filename, FT_Long face_index, int tall, bool bold, bool italic, bool antialias); int getFontTall(void) const; void renderChar(int code, glyphMetrics *metrics, unsigned char* screen, Vec2i& position); void setColor(Color color); void setOutline(bool type); void setOutlineColor(Color color); string fontName() const; private: void loadChar(int code, glyphMetrics *metrics, unsigned char* screen, Vec2i& position); void Draw_Bitmap(FT_GlyphSlot& glyph, unsigned char* screen, Vec2i& position, CChar* charBuffer); void Draw_Bitmap(CChar* charBuffer, unsigned char* screen, Vec2i& position); void Draw_MONO_Bitmap(FT_GlyphSlot& glyph, unsigned char* screen, Vec2i& position, CChar* charBuffer); void Draw_MONO_Bitmap(CChar* charBuffer, unsigned char* screen, Vec2i& position); bool isFontEdge(unsigned char* buffer,int row, int col,int width,int height); //typedef std::map<int, CChar *> TCharMap; //TCharMap m_chars; CBuffer<int, CChar> m_charBuffers; /**< char buffer. */ FT_Library m_library; /**< font library. */ FT_Face m_face; /**< font face. */ bool m_antialias; /**< font property: antialias. */ bool m_bold; /**< font property: bold. */ bool m_outline; /**< font property: outline. */ Color m_color; /**< font color: font. */ Color m_outline_color; /**< font color: outline. */ Color m_bg_color; /**< font color: background. */ int m_tall; /**< font property: tall (or size). */ string m_name; /**< font property: font name. */ };
A key problem here is how to copy pixels to make characters neat and beautiful. This requires character typesetting (or geometric data). The official website gives the specific meaning and diagram of typesetting parameters. Please understand:
Width this is the width of the glyph image bounding box. It is independent of the layout direction.
Height this is the height of the glyph image bounding box. It is independent of the layout direction. Be careful not to associate it with ft_ Size_ The height field in the metrics structure is confused.
horiBearingX for horizontal text layout, this is the horizontal distance from the current cursor position to the leftmost edge of the font image bounding box.
Horibearingyfor horizontal text layout, this is the vertical distance from the current cursor position (on the baseline) to the uppermost boundary of the glyph image bounding box.
horiAdvance for horizontal text layout, this is to increase the horizontal distance of the pen position when the font is drawn as part of the text string.
vertBearingX for vertical text layout, this is the horizontal distance from the current cursor position to the leftmost edge of the glyph image bounding box.
vertBearingY for vertical text layout, this is the vertical distance from the current cursor position (on the baseline) to the uppermost boundary of the glyph image bounding box.
vertAdvance for vertical text layout, this is the vertical distance used to increase the pen position when the glyph is drawn as part of the text string.
The following is an example of a copied pixel:
/** * @brief Draws Gray mode characters to the pixel matrix * @details * From glyphs to load layout data and bitmap data, \n * copy to pixel matrix and cache * @param glyph font glyph * @param screen pixel matrix * @param position pixel position * @param charBuffer character buffer */ void CFont::Draw_Bitmap(FT_GlyphSlot& glyph, unsigned char* screen, Vec2i& position, CChar* charBuffer) { FT_Bitmap *bitmap = (&glyph->bitmap); if (bitmap == NULL) { printf("bitmap error!\n"); return; } int span = glyph->metrics.horiAdvance / 64; int posX = glyph->metrics.horiBearingX / 64; int posY = m_tall - glyph->metrics.horiBearingY / 64; int width = glyph->metrics.width / 64; int height = glyph->metrics.height / 64; unsigned char* data = charBuffer->getOrCreateBuffer(width * height); for (int i = 0; i < height; i++) { int index = (posY + i + position[1]) * screenWidth * 4 + (posX + position[0]) * 4; for (int j = 0; j < width; j++) { int color = bitmap->buffer[i * width + j]; data[i * width + j] = color; if (color == 0) { screen[index++] = m_bg_color[3]; screen[index++] = m_bg_color[2]; screen[index++] = m_bg_color[1]; screen[index++] = m_bg_color[0]; //cout << " "; } else { if(m_outline && isFontEdge(bitmap->buffer, i, j, width, height)) { screen[index++] = m_outline_color[3]; screen[index++] = m_outline_color[2]; screen[index++] = m_outline_color[1]; screen[index++] = m_outline_color[0]; //cout<< "+"; } else { screen[index++] = color * 1.0 / 255 * m_color[3]; screen[index++] = color * 1.0 / 255 * m_color[2]; screen[index++] = color * 1.0 / 255 * m_color[1]; screen[index++] = color * 1.0 / 255 * m_color[0]; //cout << "*"; } } } } cout << endl; position[0] += span; glyphMetrics* metrics = new glyphMetrics; metrics->width = glyph->metrics.width ; metrics->height = glyph->metrics.height; metrics->horiBearingX = glyph->metrics.horiBearingX; metrics->horiBearingY = glyph->metrics.horiBearingY; metrics->horiAdvance = glyph->metrics.horiAdvance; metrics->vertBearingX = glyph->metrics.vertBearingX; metrics->vertBearingY = glyph->metrics.vertBearingY; charBuffer->setInfo(metrics); if (metrics != 0x00) delete metrics; m_charBuffers.addBuffer(charBuffer->code(), charBuffer); /* if ((m_rear + 1) > m_capacity) m_chars.insert({charBuffer->code(), charBuffer}); else { }*/ //writePngFile("./test.png", data, span, 30, 8); }
CFontManager
Responsible for the storage of loaded fonts and matching the correct fonts according to the font parameters of external rendering
class CFontManager { public: CFontManager(); ~CFontManager(); int initialize(const char* path); int createFont(const char *filename, int face, int tall, bool bold, bool italic, bool antialias);//Create font int getFontTall(int font_index); void renderChar(int font_index, int code, unsigned char* screen, Vec2i& position, Color color); int getFontIndex(string name, int fontSize); void setStartPosition(Vec2i pos); private: FT_Library m_library; vector<CFont*> m_fonts; Vec2i m_start; string m_fontPath; };
CFontRender
Responsible for font rendering. The core is a screen size pixel array. All rendering is operating this pixel data
class CFontRender { public: CFontRender(int screenWidth, int screenHeight); virtual ~CFontRender(); int initialize(const char* path); void loadFont(const char* path, int face, int tall, bool bold, bool italic, bool antialias); int render(wstring text, Vec2i pos, int fontSize, string fontName, Color fontColor); int render(wstring text, Vec2i pos, int fontIndex, Color fontColor); void exportScreenImage(const char* path); CFontManager* getFontManager() const; void clearScreen(); unsigned char* getData() const; private: unsigned char* m_screen; CFontManager * m_fontManager; };
Here is a rendering:
reference resources:
[OpenGL] use FreeType library to load fonts and draw text in GL