FreeType in OpenCASCADE
FreeType in OpenCASCADE
Abstract. FreeType is required for text display in the 3D viewer. FreeType is a software font engine that is designed to be small, efficient, highly customizable, and portable while capable of producing high-quality output(glyph images). It can be used in graphics libraries, display servers, font conversion tools, text image generation tools, and many other products as well. The blog is focus on the FreeType usage in OpenCASCADE to convert text to BRep shape.
Key Words. FreeType, OpenCASCADE, Text, BRep
1.Introduction
FreeType 2被设计为一种占用空间小的、高效的、高度可定制的、并且可以产生可移植的高品质输出(符号图像)。可以被用在诸如图像库、展出服务器、字体转换工具、图像文字产生工具等多种其它产品上。
注意FreeType 2是一种字体服务而没有提供为实现文字布局或图形化处理这样高阶的功能使用的API(比如带色文字渲染之类的)。然而,它提供一个简单的、易用的并且统一的接口实现对多种字体文件的访问,从而大大简化了这些高级的任务。
FreeType 2的发行遵循两个开源许可:我们自己的BSD样式的FreeType License和GPL(通用公共许可证)。它可以被用在任何程序中,无论是专有与否。
在常见的图形库中,如OpenSceneGraph, OpenCASCADE, HOOPS, Qt,等涉及到文字处理的,都会用到FreeType. 在一些游戏开发中,也会用到FreeType.本文主要对FreeType的用法作简单介绍,这样对FreeType有个直观认识。然后再介绍FreeType对文字轮廓的表示方法,及如何生成三维文字。
在OpenCASCADE中文字可以二维的方式显示,也可以三维的方式显示,三维方式可以有线框和渲染模式,如下图所示:
Figure 1. 2D Text in Length Dimension
Figure 2. 3D Wireframe Text in Dimension
Figure 3. 3D Shading Text in Dimension
2.FreeType Usage
在FreeType的官网上有详细的教程说明FreeType的用法,网址为:https://www.freetype.org/freetype2/docs/tutorial/index.html
主要的步骤如下:
v 包含头文件 Header Files;
v 库的初始化 Library Initialization;
v 加载字体 Loading a Font Face;
v 访问字体数据 Accessing the Face Data;
v 设置当前像素大小 Setting the Current Pixel Size;
v 加载文字 Loading a Glyph Image;
v 简单的显示 Simple Text Rendering;
下面代码是上述过程的一个实现,忽略了错误处理:
#include <ft2build.h>
#include FT_FREETYPE_H #pragma comment(lib, "freetype.lib") void test(void)
{
FT_Face aFace = NULL;
FT_Library aLibrary = NULL; // Library Initialization.
FT_Init_FreeType(&aLibrary); // Loading a Font Face.
FT_New_Face(aLibrary, "C:/Windows/Fonts/arial.ttf", , &aFace); // Setting the Current Pixel Size.
FT_Set_Char_Size(
aFace, /* handle to face object */
, /* char_width in 1/64th of points */
*, /* char_height in 1/64th of points */
, /* horizontal device resolution */
); /* vertical device resolution */ // Loading a Glyph Image
// a. Converting a Character Code Into a Glyph Index
FT_UInt aGlyphIndex = FT_Get_Char_Index( aFace, 'A' ); // b. Loading a Glyph From the Face
// Once you have a glyph index, you can load the corresponding glyph image.
FT_Load_Glyph(
aFace, /* handle to face object */
aGlyphIndex, /* glyph index */
FT_LOAD_DEFAULT); /* load flags */ // Simple Text Rendering
FT_GlyphSlot aGlyphSlot = aFace->glyph;
} int main(int argc, char* argv[])
{
test(); return ;
}
调试程序可以看出Face中已经有了一些数据。
3.FreeType Outlines
FreeType中的一个文字轮廓Outline是由二维空间闭合的线围成。每一个轮廓线是由一系列的直线段和Bezier曲线组成。根据字体文件格式的不同,他们可能是二阶或三阶多项式。二阶的通常称为quadratic或conic弧,他们用于TrueType格式。三阶的称为cubic弧,通常用于PostScript Type1, CFF, 和CFF2格式中。详细描述见:
https://www.freetype.org/freetype2/docs/glyphs/glyphs-6.html
Bezier曲线是B样条曲线的一个特例,他的特点就是曲线的阶数与控制点的个数相关,即给定控制顶点就可以确定Bezier曲线。所以OpenCASCADE中对于Bezier曲线有这样的构造函数:
每一段曲线弧都由起点start,终点end和控制顶点control points来描述。描述轮廓线的每个点都有一个特定的标记Tag来区别是线段还是Bezier曲线。
两个连续的on点确定了线段的两个端点;
在两个on点之间的一个conic off点组成了一个conic Bezier曲线;
在两个on点之间的两个cubic off点组成了一个cubic Bezier曲线;
理解了上述内容就可以得到文字的轮廓线了。OpenCASCADE中对轮廓线的处理代码如下所示:
// =======================================================================
// function : renderGlyph
// purpose :
// =======================================================================
Standard_Boolean Font_BRepFont::renderGlyph (const Standard_Utf32Char theChar,
TopoDS_Shape& theShape)
{
theShape.Nullify();
if (!loadGlyph (theChar)
|| myFTFace->glyph->format != FT_GLYPH_FORMAT_OUTLINE)
{
return Standard_False;
}
else if (myCache.Find (theChar, theShape))
{
return !theShape.IsNull();
} FT_Outline& anOutline = myFTFace->glyph->outline; if (!anOutline.n_contours)
return Standard_False; TopLoc_Location aLoc;
TopoDS_Face aFaceDraft;
myBuilder.MakeFace (aFaceDraft, mySurface, myPrecision); // Get orientation is useless since it doesn't retrieve any in-font information and just computes orientation.
// Because it fails in some cases - leave this to ShapeFix.
//const FT_Orientation anOrient = FT_Outline_Get_Orientation (&anOutline);
for (short aContour = , aStartIndex = ; aContour < anOutline.n_contours; ++aContour)
{
const FT_Vector* aPntList = &anOutline.points[aStartIndex];
const char* aTags = &anOutline.tags[aStartIndex];
const short anEndIndex = anOutline.contours[aContour];
const short aPntsNb = (anEndIndex - aStartIndex) + ;
aStartIndex = anEndIndex + ;
if (aPntsNb < )
{
// closed contour can not be constructed from < 3 points
continue;
} BRepBuilderAPI_MakeWire aWireMaker; gp_XY aPntPrev;
gp_XY aPntCurr = readFTVec (aPntList[aPntsNb - ]);
gp_XY aPntNext = readFTVec (aPntList[]); Standard_Integer aLinePnts = (FT_CURVE_TAG(aTags[aPntsNb - ]) == FT_Curve_Tag_On) ? : ;
gp_XY aPntLine1 = aPntCurr; // see http://freetype.sourceforge.net/freetype2/docs/glyphs/glyphs-6.html
// for a full description of FreeType tags.
for (short aPntId = ; aPntId < aPntsNb; ++aPntId)
{
aPntPrev = aPntCurr;
aPntCurr = aPntNext;
aPntNext = readFTVec (aPntList[(aPntId + ) % aPntsNb]); // process tags
if (FT_CURVE_TAG(aTags[aPntId]) == FT_Curve_Tag_On)
{
if (aLinePnts < )
{
aPntLine1 = aPntCurr;
aLinePnts = ;
continue;
} const gp_XY aDirVec = aPntCurr - aPntLine1;
const Standard_Real aLen = aDirVec.Modulus();
if (aLen <= myPrecision)
{
aPntLine1 = aPntCurr;
aLinePnts = ;
continue;
} if (myIsCompositeCurve)
{
Handle(Geom2d_TrimmedCurve) aLine = GCE2d_MakeSegment (gp_Pnt2d (aPntLine1), gp_Pnt2d (aPntCurr));
myConcatMaker.Add (aLine, myPrecision);
}
else
{
Handle(Geom_Curve) aCurve3d;
Handle(Geom2d_Line) aCurve2d = new Geom2d_Line (gp_Pnt2d (aPntLine1), gp_Dir2d (aDirVec));
if (to3d (aCurve2d, GeomAbs_C1, aCurve3d))
{
TopoDS_Edge anEdge = BRepLib_MakeEdge (aCurve3d, 0.0, aLen);
myBuilder.UpdateEdge (anEdge, aCurve2d, mySurface, aLoc, myPrecision);
aWireMaker.Add (anEdge);
}
}
aPntLine1 = aPntCurr;
}
else if (FT_CURVE_TAG(aTags[aPntId]) == FT_Curve_Tag_Conic)
{
aLinePnts = ;
gp_XY aPntPrev2 = aPntPrev;
gp_XY aPntNext2 = aPntNext; // previous point is either the real previous point (an "on" point),
// or the midpoint between the current one and the previous "conic off" point
if (FT_CURVE_TAG(aTags[(aPntId - + aPntsNb) % aPntsNb]) == FT_Curve_Tag_Conic)
{
aPntPrev2 = (aPntCurr + aPntPrev) * 0.5;
} // next point is either the real next point or the midpoint
if (FT_CURVE_TAG(aTags[(aPntId + ) % aPntsNb]) == FT_Curve_Tag_Conic)
{
aPntNext2 = (aPntCurr + aPntNext) * 0.5;
} my3Poles.SetValue (, aPntPrev2);
my3Poles.SetValue (, aPntCurr);
my3Poles.SetValue (, aPntNext2);
Handle(Geom2d_BezierCurve) aBezierArc = new Geom2d_BezierCurve (my3Poles);
if (myIsCompositeCurve)
{
myConcatMaker.Add (aBezierArc, myPrecision);
}
else
{
Handle(Geom_Curve) aCurve3d;
if (to3d (aBezierArc, GeomAbs_C1, aCurve3d))
{
TopoDS_Edge anEdge = BRepLib_MakeEdge (aCurve3d);
myBuilder.UpdateEdge (anEdge, aBezierArc, mySurface, aLoc, myPrecision);
aWireMaker.Add (anEdge);
}
}
}
else if (FT_CURVE_TAG(aTags[aPntId]) == FT_Curve_Tag_Cubic
&& FT_CURVE_TAG(aTags[(aPntId + ) % aPntsNb]) == FT_Curve_Tag_Cubic)
{
aLinePnts = ;
my4Poles.SetValue (, aPntPrev);
my4Poles.SetValue (, aPntCurr);
my4Poles.SetValue (, aPntNext);
my4Poles.SetValue (, gp_Pnt2d(readFTVec (aPntList[(aPntId + ) % aPntsNb])));
Handle(Geom2d_BezierCurve) aBezier = new Geom2d_BezierCurve (my4Poles);
if (myIsCompositeCurve)
{
myConcatMaker.Add (aBezier, myPrecision);
}
else
{
Handle(Geom_Curve) aCurve3d;
if (to3d (aBezier, GeomAbs_C1, aCurve3d))
{
TopoDS_Edge anEdge = BRepLib_MakeEdge (aCurve3d);
myBuilder.UpdateEdge (anEdge, aBezier, mySurface, aLoc, myPrecision);
aWireMaker.Add (anEdge);
}
}
}
} if (myIsCompositeCurve)
{
Handle(Geom2d_BSplineCurve) aDraft2d = myConcatMaker.BSplineCurve();
if (aDraft2d.IsNull())
{
continue;
} const gp_Pnt2d aFirstPnt = aDraft2d->StartPoint();
const gp_Pnt2d aLastPnt = aDraft2d->EndPoint();
if (!aFirstPnt.IsEqual (aLastPnt, myPrecision))
{
Handle(Geom2d_TrimmedCurve) aLine = GCE2d_MakeSegment (aLastPnt, aFirstPnt);
myConcatMaker.Add (aLine, myPrecision);
} Handle(Geom2d_BSplineCurve) aCurve2d = myConcatMaker.BSplineCurve();
Handle(Geom_Curve) aCurve3d;
if (to3d (aCurve2d, GeomAbs_C0, aCurve3d))
{
TopoDS_Edge anEdge = BRepLib_MakeEdge (aCurve3d);
myBuilder.UpdateEdge (anEdge, aCurve2d, mySurface, aLoc, myPrecision);
aWireMaker.Add (anEdge);
}
myConcatMaker.Clear();
}
else
{
if (!aWireMaker.IsDone())
{
continue;
} TopoDS_Vertex aFirstV, aLastV;
TopExp::Vertices (aWireMaker.Wire(), aFirstV, aLastV);
gp_Pnt aFirstPoint = BRep_Tool::Pnt (aFirstV);
gp_Pnt aLastPoint = BRep_Tool::Pnt (aLastV);
if (!aFirstPoint.IsEqual (aLastPoint, myPrecision))
{
aWireMaker.Add (BRepLib_MakeEdge (aFirstV, aLastV));
}
} if (!aWireMaker.IsDone())
{
continue;
} TopoDS_Wire aWireDraft = aWireMaker.Wire();
//if (anOrient == FT_ORIENTATION_FILL_LEFT)
//{
// According to the TrueType specification, clockwise contours must be filled
aWireDraft.Reverse();
//}
myBuilder.Add (aFaceDraft, aWireDraft);
} myFixer.Init (aFaceDraft);
myFixer.Perform();
theShape = myFixer.Result();
if (!theShape.IsNull()
&& theShape.ShapeType() != TopAbs_FACE)
{
// shape fix can not fix orientation within the single call
TopoDS_Compound aComp;
myBuilder.MakeCompound (aComp);
for (TopExp_Explorer aFaceIter (theShape, TopAbs_FACE); aFaceIter.More(); aFaceIter.Next())
{
TopoDS_Face aFace = TopoDS::Face (aFaceIter.Current());
myFixer.Init (aFace);
myFixer.Perform();
myBuilder.Add (aComp, myFixer.Result());
}
theShape = aComp;
} myCache.Bind (theChar, theShape);
return !theShape.IsNull();
}
4.Text 3D
在一些图形库中都可以生成三维文字,如下图所示:
理解了FreeType的用法后,实现上述功能也是很简单的。这里简要说明实现步骤:
l 使用FreeType得到文字的轮廓线;
l 将闭合的轮廓线生成Wire->Face;
l 将轮廓线生成的Face进行拉伸得到Solid体。
在OpenCASCADE中得到文字轮廓线生成的Face的类是:Font_BRepFont。下面给出示例得到指定文字的面。
#include <BRepTools.hxx> #include <Font_BRepFont.hxx>
#include <Font_BRepTextBuilder.hxx> #pragma comment(lib, "TKernel.lib")
#pragma comment(lib, "TKMath.lib") #pragma comment(lib, "TKG2d.lib")
#pragma comment(lib, "TKG3d.lib")
#pragma comment(lib, "TKGeomBase.lib")
#pragma comment(lib, "TKGeomAlgo.lib") #pragma comment(lib, "TKBRep.lib")
#pragma comment(lib, "TKTopAlgo.lib") #pragma comment(lib, "TKService.lib") void text2brep()
{
Font_BRepFont aBrepFont("C:/Windows/Fonts/arial.ttf", 3.5);
Font_BRepTextBuilder aTextBuilder;
TopoDS_Shape aTextShape = aTextBuilder.Perform(aBrepFont, NCollection_String("eryar@163.com")); BRepTools::Dump(aTextShape, std::cout);
BRepTools::Write(aTextShape, "d:/text.brep");
} int main(int argc, char* argv[])
{
text2brep(); return ;
}
在Draw Test Harness中显示出文字的轮廓text.brep如下图所示:
如果要显示出文字的填充效果,则需要有三角化工具将文字轮廓网格化。OpenCASCADE中将轮廓生成Wire->Face,即可以生成显示数据了:
5.Conclusion
FreeType的文字处理功能很强大,几乎所有的三维造型内核中文字的处理都是使用的FreeType。
FreeType的文字轮廓使用了线段和Bezier曲线来表达,Bezier曲线是B样条曲线的特例。理解Bezier曲线就可以自己绘制文字轮廓了。
FreeType使用简单,可以方便得到文字的轮廓数据。将轮廓数据生成Face即可以拉伸出三维文字效果。
6.References
1. https://www.freetype.org/freetype2/docs/tutorial/index.html
2. http://www.cppblog.com/eryar/archive/2014/08/17/OpenCascade_Text_Rendering.html
3. https://www.freetype.org/freetype2/docs/glyphs/glyphs-6.html
FreeType in OpenCASCADE的更多相关文章
- OpenCascade Chinese Text Rendering
OpenCascade Chinese Text Rendering eryar@163.com Abstract. OpenCascade uses advanced text rendering ...
- Building third-party products of OpenCascade
Building third-party products of OpenCascade eryar@163.com Available distributives of third-party pr ...
- Building OpenCascade on Windows with Visual Studio
Building OpenCascade on Windows with Visual Studio eryar@163.com 摘要Abstract:详细说明OpenCascade的编译配置过程,希 ...
- The Installation and Compilation of OpenCASCADE
OpenCASCADE的编译 The Installation and Compilation of OpenCASCADE eryar@163.com 一. 安装OpenCASCADE 可以从Ope ...
- OpenCASCADE AIS Manipulator
OpenCASCADE AIS Manipulator eryar@163.com Abstract. OpenCASCADE7.1.0 introduces new built-in interac ...
- Convert BSpline Curve to Arc Spline in OpenCASCADE
Convert BSpline Curve to Arc Spline in OpenCASCADE eryar@163.com Abstract. The paper based on OpenCA ...
- OpenCASCADE Shape Location
OpenCASCADE Shape Location eryar@163.com Abstract. The TopLoc package of OpenCASCADE gives resources ...
- OpenCASCADE BRep Projection
OpenCASCADE BRep Projection eryar@163.com 一网友发邮件问我下图所示的效果如何在OpenCASCADE中实现,我的想法是先构造出螺旋线,再将螺旋线投影到面上. ...
- OpenCASCADE Expression Interpreter by Flex & Bison
OpenCASCADE Expression Interpreter by Flex & Bison eryar@163.com Abstract. OpenCASCADE provide d ...
随机推荐
- 12个Sublime Text应用技巧和诀窍
本文为您提供Sublime Text编辑器的12个技巧和诀窍,深入挖掘这个看似简洁的代码编辑器,背后所隐藏的实现各种高级功能的无限可能. 1) 选择 以下是一些Sublime Text选择文本的快捷键 ...
- Java课程设计-随机密码生成器
1.团队课程设计博客链接 团队课程设计博客地址 2.个人负责模板 随即密码生成器算法 3.自己的代码提交记录截图 4.自己负责模块或任务详细说明 负责随机密码算法设计实现 通过不同种类选择下生成密码, ...
- Java程序设计-表达式运算(个人博客)
1.团队课程设计博客链接 洪亚文博客链接:http://www.cnblogs.com/201521123065hyw/ 郑晓丽博客链接:http://www.cnblogs.com/zxl3066/ ...
- Servlet第四篇【request对象常用方法、应用】
什么是HttpServletRequest HttpServletRequest**对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,**HTTP请求头中的所有信息都封装在这个对象中,开发人 ...
- SublimeTest3设置【中文乱码】
SublimeTest出现乱码! 使用Ctrl+`快捷键或者通过View->Show Console菜单打开命令行,粘贴如下代码 import urllib.request,os; pf = ' ...
- python 实现登录程序
本文介绍一个用python 实现的登录程序.python新手们可以参考一下. 用户信息存放于一个文件中,需要引入文件,校验输入的用户名.密码是否跟用户列表中的用户名密码相匹配,如果匹配,这登录成功,否 ...
- 我的Spring学习记录(二)
本篇就简单的说一下Bean的装配和AOP 本篇的项目是在上一篇我的Spring学习记录(一) 中项目的基础上进行开发的 1. 使用setter方法和构造方法装配Bean 1.1 前期准备 使用sett ...
- RabbitMQ消息队列之一:RabbitMQ的环境安装及配置
RabbitMQ简介: MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们 ...
- 方法--printStackTrace()
java抛出异常的方法有很多,其中最常用的两个: System.out.println(e),这个方法打印出异常,并且输出在哪里出现的异常,不过它和另外一个e.printStackTrace()方法不 ...
- 树状数组初步_ZERO
原博客:树状数组 1 一维树状数组 1 什么是树状数组 树状数组是一个查询和修改复杂度都为log(n)的数据结构,假设数组A[1..n],那么查询A[1]+-+A[n]的时,间是log级 ...