FreeType in OpenCASCADE

eryar@163.com

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的更多相关文章

  1. OpenCascade Chinese Text Rendering

    OpenCascade Chinese Text Rendering eryar@163.com Abstract. OpenCascade uses advanced text rendering ...

  2. Building third-party products of OpenCascade

    Building third-party products of OpenCascade eryar@163.com Available distributives of third-party pr ...

  3. Building OpenCascade on Windows with Visual Studio

    Building OpenCascade on Windows with Visual Studio eryar@163.com 摘要Abstract:详细说明OpenCascade的编译配置过程,希 ...

  4. The Installation and Compilation of OpenCASCADE

    OpenCASCADE的编译 The Installation and Compilation of OpenCASCADE eryar@163.com 一. 安装OpenCASCADE 可以从Ope ...

  5. OpenCASCADE AIS Manipulator

    OpenCASCADE AIS Manipulator eryar@163.com Abstract. OpenCASCADE7.1.0 introduces new built-in interac ...

  6. 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 ...

  7. OpenCASCADE Shape Location

    OpenCASCADE Shape Location eryar@163.com Abstract. The TopLoc package of OpenCASCADE gives resources ...

  8. OpenCASCADE BRep Projection

    OpenCASCADE BRep Projection eryar@163.com 一网友发邮件问我下图所示的效果如何在OpenCASCADE中实现,我的想法是先构造出螺旋线,再将螺旋线投影到面上. ...

  9. OpenCASCADE Expression Interpreter by Flex & Bison

    OpenCASCADE Expression Interpreter by Flex & Bison eryar@163.com Abstract. OpenCASCADE provide d ...

随机推荐

  1. 201521123104《Java程序设计》第1周学习总结

    1. 本章学习总结 1.初步了解了什么是Java,以及该如何编写简单的Java程序: 2.认识到相比于C和C++来说,Java更复杂一些,所以学习要花费更大的精力: 3.在编写Java程序过程中,认识 ...

  2. 201521123066 《java程序设计》第一周学习总结

    本周学习总结 (1)学习了Java的跨平台运行是因为有虚拟机,其特点是具有简单性,结构中立. (2)老师使用了新的作业模式,要学会发现其中的优势并好好学习使用. 书面作业 (1)为什么java程序可以 ...

  3. java课程设计--WeTalk(201521123072秦贞一)

    在线群聊系统 1,团队课程设计博客链接 http://www.cnblogs.com/slickghost/p/7018105.html 个人负责模块或任务说明 负责模块:界面设计与实现 2,自己的代 ...

  4. 201521123014 《Java程序设计》第11周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 Q1 互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问) 1.1 除了使用sync ...

  5. 201521123009 《Java程序设计》第1周学习总结

    1. 本周学习总结 对Java进行了了解与简单的学习.第一次接触Java觉得比较难理解. 希望之后的深入学习可以解决目前的一些问题. 2. 书面作业 为什么java程序可以跨平台运行?执行java程序 ...

  6. Oracle-SQL-按月统计自助终端交易量

    SQL实现的目标: 基本情况 现金交易情况 转账情况 转账交易情况(明细) 其它业务情况 交易量汇总 日均交易量 交易金额 绩效情况(万元) 支行名 支行号 所属网点 网点号 管理员帐户 管理员 终端 ...

  7. uvalive 3708 Graveyard

    https://vjudge.net/problem/UVALive-3708 题意: 一个长度为10000的圆环上放着n个雕塑,每个雕塑之间的距离均相等,即这个圆环被n个点均分.现在需要加入m个雕塑 ...

  8. vue+element搭建的后台管理系统

    最近工作不是很忙,自己在学习vue,在网上找了一个简单的项目练练手..... 这是本人的gitHub 上的项目地址:https://github.com/shixiaoyanyan/vue-admin ...

  9. JVM菜鸟进阶高手之路二(JVM的重要性,Xmn是跟请求量有关。)

    转载请注明原创出处,谢谢! 今天看群聊jvm,通常会问ygc合适吗? 阿飞总结,可能需要2个维度,1.单位时间执行次数,2.执行时间 ps -p pid -o etime 查看下进程的运行时间, 17 ...

  10. Nodejs最好的ORM - TypeORM

    TypeORM是一个采用TypeScript编写的用于Node.js的优秀ORM框架,支持使用TypeScript或Javascript(ES5, ES6, ES7)开发.目标是保持支持最新的Java ...