编程知识 cdmana.com

[C++] Font file analysis (FreeType)

目录

The font file parsing

一、前言

二、The basic layout concept

1.字体文件

2.Character image and the character table

3.Character and font index

三、字形轮廓

四、字形指标

1.基线、Pen and layout

2.Indicators and the bounding box

3.Bearing and step by step

4.The effect of the grid fitting

5.The width of the text and bounding box

五、代码实现

六、使用实例

七、Merge the cache optimization


The font file parsing

一、前言

        In the application显示文本,一般有两个方案,One is the call of the operating system interface;其二是解析字体文件Getting font image,And indirectly show.Here we discuss plans for a second,The advantage of this solution is not depend on the operating system,比较灵活.但缺点是,开发难度较高,Manage fonts image need overhead.

        Of course the mainstream font parsing library isFreeType,官网如下:

The FreeType Project

        The compiler can only rely onzlib,Rather than some people say that must rely onpnglib.如果只考虑windows平台,它也提供了vs工程文件,Easy to compile for the dynamic link library.

        From the second to the fourth quarter is the website of the tutorial a few,Can understand simple browsing concept(If you need more complex functions,May need to carefully to the website to read the full article).Followed by the code implementation.接下来开始吧!

二、The basic layout concept

1.字体文件

        首先,在FreeTypeThe basic font unit isFace,例如simkai.tff通过加载,变为一个FT_Face句柄.And can be multiple font in the broadest sense of theFace的组合,例如“Palatino”字体,包含“Palatino常规”、“Palatino斜体”两个不同的Face,They are separate files.So we agreed the terms字体(font)为单个Face,And a set of fonts contain multiple file we call字体集合(font collection).

2.Character image and the character table

        Images of the characters is called字形(glyphs),And if one character can have multiple font.While a glyph,Can also be used for multiple characters(不同的字符,Can the same).

        We can only focus on two concepts:A font file contains more than one glyph,Each can be stored for a figure、矢量、Or any other solution.而我们通过字形索引访问.

        其二,Font file contains one or more tables,称为字符表(character maps).It can be a character encoding(ASCII、Unicode、GB2312、BIG5等)To glyph index.(Through the glyph index can get glyph,To get a image of the characters)

3.Character and font index

        Each glyph image contains a variety of指标(Metrics ,These indicators describes how the when rendering text placement and management.指标包含Glyph location光标前进文本布局.

        Scalable format also contains global index,以字体单位,描述同一FaceAll the font properties.Such as maximum glyph border、The rise of font、Drop and the text height.

        Do not expand format,Also contains some indicators.Applies only to a set of given the size of the character、分辨率.Generally in pixels.

三、字形轮廓

        FreetypeNot by pixel to store the font,But by the shapes of the characters,我们称之为轮廓(outlines).It USES the point for the unit,The following formula to pixel unit:

        pixel_size = point_size * resolution / 72

        其中resolution为分辨率,以dpi(每英寸点数)为单位.

        Stored in a file within the data according to give priority to outline,以点作为单位.When the map is converted to a,需要进行缩放,这个步骤需要进行Grid fitting(grid-fitting)得到图像,It has several different algorithms,But we simply understand concepts can be.

四、字形指标

1.基线、Pen and layout

        基线(baseline)Is an imaginary line,Such as horizontal lines on the exercise books,Make us convenient alignment position.It can be horizontal,Can also be a set of.The pen on the baseline point,Used to locate the font.

        水平布局:The font on the baseline(May be more than the baseline below,比如字母q),Through the left or right to increase the position of the pen to locate glyphs.

        Two consecutive pen position(Small black squares line in the figure below)Glyph of closer about,称为The step width(advance width).It is always positive,Even in Arabic from right to left(When we layout for processing).

        In addition the position of the pen has always been on the baseline.

        While vertical baseline in the middle of the font layout,如下图所示:

2.Indicators and the bounding box

        For all glyph definition of various fontFace指标:

  • Ascent:The distance from the baseline to the highest contour points.正值,因为Y轴向上
  • Descent:The distance from the baseline to the lowest outline point.负值,But some of the font is positive.
  • Linegap:Must be placed on the distance between the two lines of text.

        The distance between the two baseline(The standard line spacing):linespace = ascent - descent + linegap

  • 边界框(bounding box):由xMin、yMin、xMax、yMaxRepresentation of the bounding box,Can contain all the font.简写为“bbox”.
  • Internal leading:Used in traditional typesetting,计算公式为:internal leading = ascent - descent - EM_size
  • External leading:与Linegap相同.

        (Note that there are the size of theSome words of the unit,通过face可以访问,We set the pixel size of had nothing to do with.To obtain the specified font pixel size related data,需要先调用FT_Set_Char_Size,然后再通过ft_face->size->metrics获得FT_Size_Metrics.But in the end I did not do,But simply use font pixel size as the basic line spacing)

3.Bearing and step by step

        Each glyph has belongs to own方位(bearing)步进(advance).The actual value and relevant layout,The horizontal and vertical layout is different value.

  • On the left side of the bearing:The tip to the glyph on the left side of thebbox的水平距离.Tend to exist horizontal layout.在FreeType中叫bearingX,简称“lsb”.
  • The top side of bearing:The baseline to the glyphbboxAt the top of the vertical distance.Usually a horizontal layout is,Vertical layout is negative.在FreeType中叫bearingY.
  • The step width:After rendering itself,The tip should offset the horizontal distance(From right to left is it).Vertical layout it always be0.在FreeType中叫advanceX.
  • Step height:After rendering itself,The vertical distance pen should reduce(It is in,因为Y轴向上,The writing is down).水平布局为0.在FreeType中叫advanceY.
  • The font width:glyph width = bbox.xMax - bbox.xMin
  • The font height:glyph height = bbox.yMax - bbox.yMin
  • The right of bearing:步进到bboxOn the right side of the distance,Only used for horizontal layout,一般为正值.缩写为“rsb”.

        You can carefully controlled below,In order to write the correct code to achieve the desired layout:

水平布局
垂直布局

4.The effect of the grid fitting

        Grid fitting in order to make the control points and pixel alignment,May modify the size of the character images,Thus affecting glyph index.

5.The width of the text and bounding box

        字形的对齐(origin)点Is the tip at the position of the baseline.This alignment points are not usually glyph ofbbox上.But the step width and font width also is not the same thing.

        对于整个字符串来说:

  • The entire string bounding box contains no文本光标,And it is not going on the Angle of.
  • The step width of string has nothing to do with the bounding box.特别的是,There are Spaces before and after、制表符.
  • Such as kerning additional processing,Can make the overall size has nothing to do with a single glyph index.

五、代码实现

        包含头文件.If you need to get the font stroke image,还需包含FT_STROKER

#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_STROKER_H

        定义封装类FreeType,其中FT_LibraryAs the basic handle,FT_FaceFor a single file handle:

class FreeType
{
public:

private:
	FT_Library _library;
	vector<FT_Face> _vecFace;
};

         初始化与释放,使用FT_Init_FreeTypeFT_Done_FreeType,FT_Face通过FT_Done_Face释放(If you no longer use a font,You can releaseFT_Face):

FreeType()
{
	if (FT_Init_FreeType(&_library))
	{
		debug_err("FreeType初始化失败!");
		_library = nullptr;
	}
}

~FreeType()
{
	for (auto& iter : _vecFace)
		FT_Done_Face(iter);
	FT_Done_FreeType(_library);
}

        加载字体文件,使用FT_New_Face,并保存FT_Face到容器,Where is itid:

//! 加载字体文件,返回编号,-1为失败
size_t LoadFace(string_view path_name)
{
	FT_Face face;
	if (FT_New_Face(_library, String::cvt_u8_mb(path_name).c_str(), 0, &face))
	{
		debug_err("The font file loading failure: " + string{path_name});
		return -1;
	}
	//从内存加载
	//FT_New_Memory_Face(library, (FT_Byte*)buffer, size, 0, &face.face)

	_vecFace.push_back(face);
	return _vecFace.size() - 1;
}

        We define the interface of two classes,一个输入CharInfo、一个输出CharImage,通过CharInfoHashYou can define a hash table,用于保存到CharSprite对应关系,To avoid repetition and generate the elves(But we here temporarily do not need to useCharInfoHash和CharSprite):

unordered_map<CharInfo, CharSprite, CharInfoHash> _hashCharImage;
//! 字符信息
struct CharInfo
{
	size_t _font;//字体id
	utf_char _ch;//我这里是char32_t,可以替换为wchar_t
	size_t _size;//字体大小
	size_t _outline;//描边大小
	bool operator==(const CharInfo& b) const
	{
		return _font == b._font
			&& _ch == b._ch
			&& _size == b._size
			&& _outline == b._outline;
	}
};

//! CharInfo的哈希函数
struct CharInfoHash
{
	//8 + 16 + 8 + 32
	size_t operator()(const CharInfo& info) const
	{
		uint64_t n = ((uint64_t)info._font << 56)
			| ((uint64_t)info._size << 40)
			| ((uint64_t)info._outline << 32)
			| ((uint64_t)info._ch);
		return std::hash<uint64_t>()(n);
	}
};

//! Character image data
struct CharImage
{
	Image* _image;
	Image* _imageOutline;
	Vector2 _pos;			//! 锚点
	float _advance;			//! Level step by step
	~CharImage();
};

        So the core of packaging function isGetChar,Because we want to get the font描边图像,The code will be many complex(The official code changes and to):

CharImage* GetChar(const CharInfo& info);

        首先检查传入参数,And to give the incoming parameters take a simplified name:

//错误输出
auto fn_debug = [&](string_view str)
{
	debug_err(format("{}:{},{},{},{}",
		str, info._font, to_string(info._ch), info._size, info._outline));
};

if (info._font >= _vecFace.size())
{
	fn_debug("字体id越界");
	return nullptr;
}

const char32_t& ch = info._ch;
const size_t& size = info._size;
const size_t& outline = info._outline;

        Set the coding table,通常设为FT_ENCODING_UNICODE,即unicode编码:

FT_Face ft_face = _vecFace[info._font];
if (FT_Select_Charmap(ft_face, FT_ENCODING_UNICODE))
{
	fn_debug("设置编码失败");
	return nullptr;
}

        设置字体大小,我们的size单位是像素,Need the following conversion:

if (FT_Set_Char_Size(ft_face, FT_F26Dot6(size << 6), FT_F26Dot6(size << 6), 72, 72))
{
	fn_debug("Set the font size failure");
	return nullptr;
}

        To get the font,标记FT_LOAD_NO_BITMAPSet is not generated bitmap(Behind our regeneration into):

FT_UInt gindex = FT_Get_Char_Index(ft_face, ch);
if (FT_Load_Glyph(ft_face, gindex, FT_LOAD_NO_BITMAP))
{
	fn_debug("Glyph loading failure");
	return nullptr;
}

        For property whether support stroke,No we simply return an error(Here can improve,一般来说都支持):

if (ft_face->glyph->format != FT_GLYPH_FORMAT_OUTLINE)
{
	fn_debug("Do not support the stroke");
	return nullptr;
}

        The more troublesome,We need the sample code defined4个东西,首先是Span类,According to horizontal continuous and a pixel of the same colour,其中xy是位置,w是宽度,而coverage是颜色.coverage的范围是[0, 255],表示透明度.In the end we need to generate a white with transparent channel of the字符图像,Staining can implement different color font:

//Said the level of a continuous data
struct Span
{
	int _x;
	int _y;
	int _w;
	int _coverage;//为uint8_t透明度

	Span(){}
	Span(int x, int y, int w, int coverage): 
		_x(x), _y(y), _w(w), _coverage(coverage)
	{}
};

        Need a function with a callback,We call back twiceRenderSpans会生成两个Spans,One is the general image,A stroke is image:

//The renderer callback,写入Span
static void RasterCallback(int y, int count, const FT_Span* spans, void* user)
{
	vector<Span>* sptr = (vector<Span>*)user;
	for (int i = 0; i < count; ++i)
		sptr->push_back(Span(spans[i].x, y, spans[i].len, spans[i].coverage));
}
// Setting up the grating parameters,And render stroke
void RenderSpans(FT_Library& library, FT_Outline* outline, vector<Span>* spans)
{
	FT_Raster_Params params;
	memset(&params, 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, &params);
}

        The last class is similar toRect的概念,Bounding box used to calculate the two images public:

struct CharRect
{
	float _xMin;
	float _xMax;
	float _yMin;
	float _yMax;

	CharRect() {}
	CharRect(float left, float top, float right, float bottom): 
		_xMin(left), _xMax(right), _yMin(top), _yMax(bottom) 
	{}

	void Include(int x, int y)
	{
		_xMin = min(_xMin, float(x));
		_yMin = min(_yMin, float(y));
		_xMax = max(_xMax, float(x));
		_yMax = max(_yMax, float(y));
	}

	float Width() { return _xMax - _xMin + 1; }
	float Height() { return _yMax - _yMin + 1; }
};

        好了,Can finally return to ourGetChar函数了,First render to ordinaryspans

//渲染到 spans
vector<Span> spans;
RenderSpans(_library, &ft_face->glyph->outline, &spans);

        Then set the brush,并渲染到spans_outline,其中outlineIs the pixel size of a stroke:

//The next rendering to spans_outline
vector<Span> spans_outline;

//设置画笔
FT_Stroker stroker;
FT_Stroker_New(_library, &stroker);
FT_Stroker_Set(stroker,
	(int)(outline * 64),
	FT_STROKER_LINECAP_ROUND,
	FT_STROKER_LINEJOIN_ROUND,
	0);

FT_Glyph glyph;
if (FT_Get_Glyph(ft_face->glyph, &glyph))
{
	fn_debug("Failed to get the font");
	return nullptr;
}

FT_Glyph_StrokeBorder(&glyph, stroker, 0, 1);
if (glyph->format == FT_GLYPH_FORMAT_OUTLINE)
{
	//绘制outline到 outline_spans
	FT_Outline* o =
		&reinterpret_cast<FT_OutlineGlyph>(glyph)->outline;
	RenderSpans(_library, o, &spans_outline);
}

        Now the data has been saved to thespans中,To clean up resources,然后我们检查一下spans是否为空,有些情况会返回空(For example, blank characters):

//Does not clean up behind the use of resources
FT_Stroker_Done(stroker);
FT_Done_Glyph(glyph);

if (spans.empty())
{
	fn_debug("spans为空(Maybe print control characters)");
	return nullptr;
}

        And the following bounding box calculation:

//Computing the bounding box(Stroke more)
CharRect rect(
	float(spans.front()._x),
	float(spans.front()._y),
	float(spans.front()._x),
	float(spans.front()._y));
for (Span& s : spans)
{
	rect.Include(s._x, s._y);
	rect.Include(s._x + s._w - 1, s._y);
}
for (Span& s : spans_outline)
{
	rect.Include(s._x, s._y);
	rect.Include(s._x + s._w - 1, s._y);
}

        然后复制数据到Image,First based on bounding box size generating images of the two colorless(One for the normal、One for the stroke),And then in white+Transparency in the corresponding data,如下所示:

//Obtain the necessary properties
unsigned img_w = (unsigned)rect.Width();
unsigned img_h = (unsigned)rect.Height();


//Allocate memory image,以0颜色
Image* img_outline = Image::Create({ img_w ,img_h}, ColorDef::NONE);
Image* img = Image::Create({ img_w ,img_h }, ColorDef::NONE);

//这里取得image bufferA pointer to the assignment
uint32_t* p_lock = img_outline->GetData();

//复制到img_outline
for (Span& s : spans_outline)
{
	for (int w = 0; w < s._w; ++w)
	{
		size_t y = img_h - 1 - (s._y - rect._yMin);
		size_t index = y * img_w + s._x - rect._xMin + w;
		p_lock[index] = uint32_t(s._coverage) << 24 | 0x00ffffff;
	}
}
//复制到img
p_lock = img->GetData();
for (Span& s : spans)
{
	for (int w = 0; w < s._w; ++w)
	{
		size_t y = img_h - 1 - (s._y - rect._yMin);
		size_t index = y * img_w + s._x - rect._xMin + w;
		p_lock[index] = uint32_t(s._coverage) << 24 | 0x00ffffff;
	}
}

        Finally returned to the necessary data,Completed the final operation:

float bearingX = float(ft_face->glyph->metrics.horiBearingX >> 6);
float bearingY = float(ft_face->glyph->metrics.horiBearingY >> 6);
float advance = float(ft_face->glyph->advance.x >> 6);

CharImage* ret = new CharImage;

ret->_advance = advance;
ret->_pos[0] = -bearingX;
ret->_pos[1] = bearingY;
ret->_image = img;
ret->_imageOutline = img_outline;

return ret;

        Complete code for the final list

六、使用实例

        The first loading fonts,然后调用GetCharReturn to specify the size of the character images:

g_factory->LoadFont("font/syht.otf");

CharImage* ci = g_factory->GetChar({0, U'中', 72, 2});
ci->_image->SaveToFile("temp/ch.png");
ci->_imageOutline->SaveToFile("temp/ch_outline.png");

        为了方便观察,我在psWithin a black background,Contrast and placed together:

七、Merge the cache optimization

        当然,We need to repeatedly use the same character image,Practical engineering on cache and merged into a texture we need.

        I use the following dynamic loading algorithm,源码文件为DND.TexturePack.ixx,Including a static loading algorithm,All pictures into a larger image can be used for the known.And here we use dynamic loading,When get a character image,In the right place,Don't know in advance, of course, all the characters:

//! Dynamic loading(DH为2时,32、31、30Will be in the same line)
template<unsigned DH>
class Dynamic
{
public:
	//! According to the height of storage 每一行
	struct Line
	{
		unsigned _y;//! 所在y
		unsigned _h;//! 最大高度
		unsigned _x;//! 当前x
		bool _free;//! If there is a vacancy tag
	};

	/**
	* @brief 清空初始化 或 再次使用
	* @param[in] size 箱子大小
	*/
	void Reset(const Size& size)
	{
		_size = size;
		_regY = 0;
		_bFull = false;

		_vecLine.clear();
	}

	/**
	* @brief 添加一个,失败返回false
	* @param[in] size Load the size
	* @param[out] rect Success is returned location
	*/
	bool Add(const Size& size, RectU& rect)
	{
		if (_bFull)
			return false;

		unsigned w = size[0];
		unsigned h = size[1];
		//找到一个h比自己大的,But no more thanDH(Then can put downx)
		auto iter = find_if(_vecLine.begin(), _vecLine.end(), [&](Line& line)
			{
				if (line._free
					&& line._h >= h // 30 >= 28
					&& line._h <= h + DH) // 30 <= 28 + 2   {28,29,30}
				{
					if (line._x + w > _size[0])
					{
						line._free = false;
						return false;
					}
					else
						return true;
				}
				return false;
			});

		if (iter == _vecLine.end())
		{//没有就创建一个
			if (h + _regY > _size[1])
			{
				_bFull = true;
				return false;
			}

			Line line;
			line._h = h;
			line._y = _regY;
			line._x = 0;
			line._free = true;

			_regY += h;
			_vecLine.push_back(line);

			iter = _vecLine.end() - 1;
		}

		rect = { iter->_x, iter->_y, iter->_x + w , iter->_y + h };

		iter->_x += w;

		//越界检查
		assert(rect[2] <= _size[0] && rect[3] <= _size[1]);

		return true;
	}

private:
	Size _size;
	unsigned _regY;//当前y
	bool _bFull;//Marked up
	vector<Line> _vecLine;
};

        The final will produce the following texture(我在psAdd in the black background is convenient to observe):

         从FreeTypeAfter gaining the character images,Written to the texture again,We can display text.The text typography is another thing,这里我就不详细说明了,也比较麻烦,You can refer to my sourceDND.Text.ixx,最后效果如下:

        其中DND.FactoryImp.ixx有以下成员,Ideas which character is used,Generates the character images,然后reg_imageRegistered with the texture,生成uv,成为精灵.通过TextClass management more characters elf,进行布局:

//Character spirit
struct CharSprite
{
	struct
	{
		size_t _idTex;
		RectU _rect;//In a larger area
		Vector2 _uv[4];//计算出的uv
	}_data[2];//The stroke 和 描边
	Vector2 _anchor;//锚点(相对于基线)
	float _advance;//步进
};
//CharInfo -> CharSprite
unordered_map<CharInfo, CharSprite, CharInfoHash> _hashCharImage;

using TexPack = TexturePack::Dynamic<2>;
//Using dynamic texture area
vector<TexPack> _allTexPack;

//Register an image(内部使用)
//返回tex_idAnd texture area,不存在rect_id
//Drawing before batch submission
bool _reg_image(Image* image, size_t& id_tex, RectU& rect);

        源码位置:DND: 应用程序框架.

        觉得有用,点赞、收藏、关注一下吧. 

        与FreeTypeRelated to the complete code:

/**
* @file		DND.FreeType.ixx
* @brief	基于Freetype2The font analytic
*
*
* @version	1.0
* @author	lveyou
* @date		22-09-10
*
*/
module;
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_STROKER_H
export module DND.FreeType;

import DND.Std;
import DND.Debug;
import DND.Color;
import DND.CodeCvt;
export import DND.Font;

export namespace dnd
{ 

class FreeType
{
public:
	//Said the level of a continuous data
	struct Span
	{
		int _x;
		int _y;
		int _w;
		int _coverage;//为uint8_t透明度

		Span(){}
		Span(int x, int y, int w, int coverage): 
			_x(x), _y(y), _w(w), _coverage(coverage)
		{}
	};
	//The renderer callback,写入Span
	static void RasterCallback(int y, int count, const FT_Span* spans, void* user)
	{
		vector<Span>* sptr = (vector<Span>*)user;
		for (int i = 0; i < count; ++i)
			sptr->push_back(Span(spans[i].x, y, spans[i].len, spans[i].coverage));
	}
	// Setting up the grating parameters,And render stroke
	void RenderSpans(FT_Library& library, FT_Outline* outline, vector<Span>* spans)
	{
		FT_Raster_Params params;
		memset(&params, 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, &params);
	}
	struct CharRect
	{
		float _xMin;
		float _xMax;
		float _yMin;
		float _yMax;

		CharRect() {}
		CharRect(float left, float top, float right, float bottom): 
			_xMin(left), _xMax(right), _yMin(top), _yMax(bottom) 
		{}

		void Include(int x, int y)
		{
			_xMin = min(_xMin, float(x));
			_yMin = min(_yMin, float(y));
			_xMax = max(_xMax, float(x));
			_yMax = max(_yMax, float(y));
		}

		float Width() { return _xMax - _xMin + 1; }
		float Height() { return _yMax - _yMin + 1; }
	};

	FreeType()
	{
		if (FT_Init_FreeType(&_library))
		{
			debug_err("FreeType初始化失败!");
			_library = nullptr;
		}
	}
	//! 加载字体文件,返回编号,-1为失败
	size_t LoadFace(string_view path_name)
	{
		FT_Face face;
		if (FT_New_Face(_library, CodeCvt::cvt_u8_mb(path_name).c_str(), 0, &face))
		{
			debug_err("The font file loading failure: " + string{path_name});
			return -1;
		}
		assert(FT_IS_SCALABLE(face));
		//从内存加载
		//FT_New_Memory_Face(library, (FT_Byte*)buffer, size, 0, &face.face)

		//Print some of the properties
		debug_msg(format("Successfully loaded a font:{},{}", _vecFace.size(), path_name));
		debug(format("名称:{}", face->family_name));
		debug(format("字形数:{}", face->num_glyphs));
		//debug(format("包围盒:({}, {}),({}, {})",
		//	face->bbox.xMin, face->bbox.xMax,
		//	face->bbox.yMin, face->bbox.yMax));
		
		_vecFace.push_back(face);
		return _vecFace.size() - 1;
	}

	CharImage* GetChar(const CharInfo& info)
	{
		//错误输出
		auto fn_debug = [&](string_view str)
		{
			debug_err(format("{}:{},{},{},{}",
				str, info._font, to_string(info._ch), info._size, info._outline));
		};

		if (info._font >= _vecFace.size())
		{
			fn_debug("字体id越界");
			return nullptr;
		}

		const char32_t& ch = info._ch;
		const size_t& size = info._size;
		const size_t& outline = info._outline;

		FT_Face ft_face = _vecFace[info._font];
		if (FT_Select_Charmap(ft_face, FT_ENCODING_UNICODE))
		{
			fn_debug("设置编码失败");
			return nullptr;
		}

		if (FT_Set_Char_Size(ft_face, FT_F26Dot6(size << 6), FT_F26Dot6(size << 6), 72, 72))
		{
			fn_debug("Set the font size failure");
			return nullptr;
		}
		

		FT_UInt gindex = FT_Get_Char_Index(ft_face, ch);
		if (FT_Load_Glyph(ft_face, gindex, FT_LOAD_NO_BITMAP))
		{
			fn_debug("Glyph loading failure");
			return nullptr;
		}
		if (ft_face->glyph->format != FT_GLYPH_FORMAT_OUTLINE)
		{
			fn_debug("Do not support the stroke");
			return nullptr;
		}
		//渲染到 spans
		vector<Span> spans;
		RenderSpans(_library, &ft_face->glyph->outline, &spans);

		//The next rendering to spans_outline
		vector<Span> spans_outline;

		//设置画笔
		FT_Stroker stroker;
		FT_Stroker_New(_library, &stroker);
		FT_Stroker_Set(stroker,
			(int)(outline * 64),
			FT_STROKER_LINECAP_ROUND,
			FT_STROKER_LINEJOIN_ROUND,
			0);

		FT_Glyph glyph;
		if (FT_Get_Glyph(ft_face->glyph, &glyph))
		{
			fn_debug("Failed to get the font");
			return nullptr;
		}

		FT_Glyph_StrokeBorder(&glyph, stroker, 0, 1);
		if (glyph->format == FT_GLYPH_FORMAT_OUTLINE)
		{
			//绘制outline到 outline_spans
			FT_Outline* o =
				&reinterpret_cast<FT_OutlineGlyph>(glyph)->outline;
			RenderSpans(_library, o, &spans_outline);
		}

		//Does not clean up behind the use of resources
		FT_Stroker_Done(stroker);
		FT_Done_Glyph(glyph);

		if (spans.empty())
		{
			fn_debug("spans为空(Maybe print control characters)");
			return nullptr;
		}
		//Computing the bounding box(Stroke more)
		CharRect rect(
			float(spans.front()._x),
			float(spans.front()._y),
			float(spans.front()._x),
			float(spans.front()._y));
		for (Span& s : spans)
		{
			rect.Include(s._x, s._y);
			rect.Include(s._x + s._w - 1, s._y);
		}
		for (Span& s : spans_outline)
		{
			rect.Include(s._x, s._y);
			rect.Include(s._x + s._w - 1, s._y);
		}

		//Obtain the necessary properties
		unsigned img_w = (unsigned)rect.Width();
		unsigned img_h = (unsigned)rect.Height();


		//Allocate memory image,以0颜色
		Image* img_outline = Image::Create({ img_w ,img_h}, ColorDef::NONE);
		Image* img = Image::Create({ img_w ,img_h }, ColorDef::NONE);

		//这里取得image bufferA pointer to the assignment
		uint32_t* p_lock = img_outline->GetData();

		//复制到img_outline
		for (Span& s : spans_outline)
		{
			for (int w = 0; w < s._w; ++w)
			{
				size_t y = img_h - 1 - (s._y - rect._yMin);
				size_t index = y * img_w + s._x - rect._xMin + w;
				p_lock[index] = uint32_t(s._coverage) << 24 | 0x00ffffff;
			}
		}
		//复制到img
		p_lock = img->GetData();
		for (Span& s : spans)
		{
			for (int w = 0; w < s._w; ++w)
			{
				size_t y = img_h - 1 - (s._y - rect._yMin);
				size_t index = y * img_w + s._x - rect._xMin + w;
				p_lock[index] = uint32_t(s._coverage) << 24 | 0x00ffffff;
			}
		}
			
		float bearingX = float(ft_face->glyph->metrics.horiBearingX >> 6);
		float bearingY = float(ft_face->glyph->metrics.horiBearingY >> 6);
		float advance = float(ft_face->glyph->advance.x >> 6);


		CharImage* ret = new CharImage;

		ret->_advance = advance;
		ret->_bearingX = bearingX;
		ret->_bearingY = bearingY;
		ret->_image = img;
		ret->_imageOutline = img_outline;

		return ret;
	}


	size_t GetFontSize()
	{
		return _vecFace.size();
	}

	~FreeType()
	{
		for (auto& iter : _vecFace)
			FT_Done_Face(iter);
		FT_Done_FreeType(_library);
	}
private:
	FT_Library _library;
	vector<FT_Face> _vecFace;
};

FreeType* g_freetype;

}

版权声明
本文为[Take a short tour]所创,转载请带上原文链接,感谢
https://cdmana.com/2022/266/202209230821099250.html

Scroll to Top