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 ...
随机推荐
- 201521123011 《Java程序设计》 第二周学习总结
1. 本周学习总结 记录本周学习中的重点 原则:少而精,自己写.即使不超过5行也可,但请一定不要简单的复制粘贴. 知道了c语言中string的定义与java不同的地方. 学习了枚举,数组等方法 Tnt ...
- RSA原理、ssl认证、Tomcat中配置数字证书以及网络传输数据中的密码学知识
情形一:接口的加.解密与加.验签 rsa不是只有加密解密,除此外还有加签和验签.之前一直误以为加密就是加签,解密就是验签.这是错误的! 正确的理解是: 数据传输的机密性:公钥加密私钥解密是密送,保 ...
- xml是什么,为什么要用xml
XML概念 众所周知,xml常用语数据存储和传输,文件后缀为 .xml: 它是可扩展标记语言(Extensible Markup Language,简称XML),是一种标记语言. 标记,指计算机所能理 ...
- 03_Ext_Viewport_Window_Dialog
Viewport Viewport 代表整个浏览器窗口,直接渲染到document.body节点,取代页面中的所有内容.一般作为应用程序主界面. 随着浏览器显示区域的大小自动改变,一个页面中只能有一个 ...
- 【SQL】- 基础知识梳理(三) - SQL连接查询
一.引言 有时为了得到一张报表的完整数据,需要从两个或更多的表中获取结果,这时就用到了"连接查询". 二.连接查询 连接查询的定义: 数据库中的表通过键将彼此联系起来,从而获取这些 ...
- mapreduce新旧api对比
对比:hadoop版本1.x 新版,hadoop版本0.x 旧版 1.新api引用包一般是mapreduce ,旧版api引用的包一般是mapred 2.新api使用Job,旧版api使用JobCon ...
- 跨Storyboard调用
在开发中我们会有这种需求从一个故事板跳到另一个故事板 modal UIStoryboard *secondStoryboard = [UIStoryboard storyboardWithName:@ ...
- 利用Docker快速创建Nginx负载均衡节点
本文版权归博客园和作者吴双本人共同所有 转载和爬虫请注明原文地址 www.cnblogs.com/tdws 一.Self-Host Kestrel 1. 在vs2017中新建dotnet core2. ...
- 谈javascript变量声明
之前的面试中遇到过一道面试题 var a =10;(function(){ console.log(a); var a =20;})() 短短5行代码log的结果是什么? 如果把var a = 20; ...
- CentOS7下安装MariaDB
环境:Window10 上建立 VMWare 虚拟机,EasyInstaller 方式安装 CentOS 7 1. “失败”的经历 备份原 repo 文件,并更改 yum 源(方法详见修改yum源)为 ...