0030782: Visualization, Font_FTFont - use predefined fallback fonts for extended...
[occt.git] / src / Font / Font_BRepFont.cxx
1 // Created on: 2013-09-16
2 // Copyright (c) 2013-2014 OPEN CASCADE SAS
3 //
4 // This file is part of Open CASCADE Technology software library.
5 //
6 // This library is free software; you can redistribute it and/or modify it under
7 // the terms of the GNU Lesser General Public License version 2.1 as published
8 // by the Free Software Foundation, with special exception defined in the file
9 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
10 // distribution for complete text of the license and disclaimer of any warranty.
11 //
12 // Alternatively, this file may be used under the terms of Open CASCADE
13 // commercial license or contractual agreement.
14
15 #include <Font_BRepFont.hxx>
16
17 #include <BRep_Tool.hxx>
18 #include <BRepTopAdaptor_FClass2d.hxx>
19 #include <BRepBuilderAPI_MakeFace.hxx>
20 #include <BRepBuilderAPI_MakeWire.hxx>
21 #include <BRepLib_MakeEdge.hxx>
22 #include <Font_FTLibrary.hxx>
23 #include <Font_FontMgr.hxx>
24 #include <Font_TextFormatter.hxx>
25 #include <GCE2d_MakeSegment.hxx>
26 #include <GC_MakeSegment.hxx>
27 #include <Geom_BezierCurve.hxx>
28 #include <Geom_BSplineCurve.hxx>
29 #include <Geom2d_TrimmedCurve.hxx>
30 #include <Geom_Plane.hxx>
31 #include <Geom2d_BezierCurve.hxx>
32 #include <Geom2d_BSplineCurve.hxx>
33 #include <Geom2d_TrimmedCurve.hxx>
34 #include <Geom2d_Line.hxx>
35 #include <GeomAPI.hxx>
36 #include <GeomAdaptor_HSurface.hxx>
37 #include <GeomLib.hxx>
38 #include <gp_Pln.hxx>
39 #include <TColGeom2d_HSequenceOfBoundedCurve.hxx>
40 #include <TCollection_AsciiString.hxx>
41 #include <TCollection_HAsciiString.hxx>
42 #include <TopExp.hxx>
43 #include <TopExp_Explorer.hxx>
44 #include <TopoDS.hxx>
45 #include <TopoDS_Compound.hxx>
46 #include <TopoDS_Vertex.hxx>
47 #include <TopTools_DataMapOfShapeInteger.hxx>
48 #include <TopTools_DataMapOfShapeSequenceOfShape.hxx>
49
50 #include <ft2build.h>
51 #include FT_FREETYPE_H
52 #include FT_OUTLINE_H
53
54 IMPLEMENT_STANDARD_RTTIEXT(Font_BRepFont,Font_FTFont)
55
56 namespace
57 {
58   // pre-defined font rendering options
59   static const unsigned int THE_FONT_SIZE      = 72;
60   static const unsigned int THE_RESOLUTION_DPI = 4800;
61   static const Font_FTFontParams THE_FONT_PARAMS (THE_FONT_SIZE, THE_RESOLUTION_DPI);
62
63   // compute scaling factor for specified font size
64   inline Standard_Real getScale (const Standard_Real theSize)
65   {
66     return theSize / Standard_Real(THE_FONT_SIZE) * 72.0 / Standard_Real(THE_RESOLUTION_DPI);
67   }
68
69   //! Auxiliary method to convert FT_Vector to gp_XY
70   static gp_XY readFTVec (const FT_Vector& theVec,
71                           const Standard_Real theScaleUnits,
72                           const Standard_Real theWidthScaling = 1.0)
73   {
74     return gp_XY (theScaleUnits * Standard_Real(theVec.x) * theWidthScaling / 64.0, theScaleUnits * Standard_Real(theVec.y) / 64.0);
75   }
76
77   //! Auxiliary method for classification wire theW2 with respect to wire theW1
78   static TopAbs_State classifyWW (const TopoDS_Wire& theW1,
79                                   const TopoDS_Wire& theW2,
80                                   const TopoDS_Face& theF)
81   {
82     TopAbs_State aRes = TopAbs_UNKNOWN;
83
84     TopoDS_Face aF = TopoDS::Face (theF.EmptyCopied());
85     aF.Orientation (TopAbs_FORWARD);
86     BRep_Builder aB;
87     aB.Add (aF, theW1);
88     BRepTopAdaptor_FClass2d aClass2d (aF, ::Precision::PConfusion());
89     for (TopoDS_Iterator anEdgeIter (theW2); anEdgeIter.More(); anEdgeIter.Next())
90     {
91       const TopoDS_Edge& anEdge = TopoDS::Edge (anEdgeIter.Value());
92       Standard_Real aPFirst = 0.0, aPLast = 0.0;
93       Handle(Geom2d_Curve) aCurve2d = BRep_Tool::CurveOnSurface (anEdge, theF, aPFirst, aPLast);
94       if (aCurve2d.IsNull())
95       {
96         continue;
97       }
98
99       gp_Pnt2d aPnt2d = aCurve2d->Value ((aPFirst + aPLast) / 2.0);
100       TopAbs_State aState = aClass2d.Perform (aPnt2d, Standard_False);
101       if (aState == TopAbs_OUT
102        || aState == TopAbs_IN)
103       {
104         if (aRes == TopAbs_UNKNOWN)
105         {
106           aRes = aState;
107         }
108         else if (aRes != aState)
109         {
110           return TopAbs_UNKNOWN;
111         }
112       }
113     }
114     return aRes;
115   }
116
117 }
118
119 // =======================================================================
120 // function : Constructor
121 // purpose  :
122 // =======================================================================
123 Font_BRepFont::Font_BRepFont ()
124 : myPrecision  (Precision::Confusion()),
125   myScaleUnits (1.0),
126   myIsCompositeCurve (Standard_False),
127   my3Poles     (1, 3),
128   my4Poles     (1, 4)
129 {
130   init();
131 }
132
133 // =======================================================================
134 // function : init
135 // purpose  :
136 // =======================================================================
137 void Font_BRepFont::init()
138 {
139   mySurface        = new Geom_Plane (gp_Pln (gp::XOY()));
140   myCurve2dAdaptor = new Geom2dAdaptor_HCurve();
141   Handle(Adaptor3d_HSurface) aSurfAdaptor = new GeomAdaptor_HSurface (mySurface);
142   myCurvOnSurf.Load (aSurfAdaptor);
143 }
144
145 // =======================================================================
146 // function : Constructor
147 // purpose  :
148 // =======================================================================
149 Font_BRepFont::Font_BRepFont (const NCollection_String& theFontPath,
150                               const Standard_Real       theSize)
151 : myPrecision  (Precision::Confusion()),
152   myScaleUnits (1.0),
153   myIsCompositeCurve (Standard_False),
154   my3Poles     (1, 3),
155   my4Poles     (1, 4)
156 {
157   init();
158   if (theSize <= myPrecision * 100.0)
159   {
160     return;
161   }
162
163   myScaleUnits = getScale (theSize);
164   Font_FTFont::Init (theFontPath.ToCString(), THE_FONT_PARAMS);
165 }
166
167 // =======================================================================
168 // function : Constructor
169 // purpose  :
170 // =======================================================================
171 Font_BRepFont::Font_BRepFont (const NCollection_String& theFontName,
172                               const Font_FontAspect     theFontAspect,
173                               const Standard_Real       theSize,
174                               const Font_StrictLevel    theStrictLevel)
175 : myPrecision  (Precision::Confusion()),
176   myScaleUnits (1.0),
177   myIsCompositeCurve (Standard_False),
178   my3Poles     (1, 3),
179   my4Poles     (1, 4)
180 {
181   init();
182   if (theSize <= myPrecision * 100.0)
183   {
184     return;
185   }
186
187   myScaleUnits = getScale (theSize);
188   Font_FTFont::FindAndInit (theFontName.ToCString(), theFontAspect, THE_FONT_PARAMS, theStrictLevel);
189 }
190
191 // =======================================================================
192 // function : Release
193 // purpose  :
194 // =======================================================================
195 void Font_BRepFont::Release()
196 {
197   myCache.Clear();
198   Font_FTFont::Release();
199 }
200
201 // =======================================================================
202 // function : SetCompositeCurveMode
203 // purpose  :
204 // =======================================================================
205 void Font_BRepFont::SetCompositeCurveMode (const Standard_Boolean theToConcatenate)
206 {
207   if (myIsCompositeCurve != theToConcatenate)
208   {
209     myIsCompositeCurve = theToConcatenate;
210     myCache.Clear();
211   }
212 }
213
214 // =======================================================================
215 // function : Init
216 // purpose  :
217 // =======================================================================
218 bool Font_BRepFont::Init (const NCollection_String& theFontPath,
219                           const Standard_Real       theSize)
220 {
221   if (theSize <= myPrecision * 100.0)
222   {
223     return false;
224   }
225
226   myScaleUnits = getScale (theSize);
227   return Font_FTFont::Init (theFontPath.ToCString(), THE_FONT_PARAMS);
228 }
229
230 // =======================================================================
231 // function : FindAndInit
232 // purpose  :
233 // =======================================================================
234 bool Font_BRepFont::FindAndInit (const TCollection_AsciiString& theFontName,
235                                  const Font_FontAspect  theFontAspect,
236                                  const Standard_Real    theSize,
237                                  const Font_StrictLevel theStrictLevel)
238 {
239   if (theSize <= myPrecision * 100.0)
240   {
241     return false;
242   }
243
244   myScaleUnits = getScale (theSize);
245   return Font_FTFont::FindAndInit (theFontName.ToCString(), theFontAspect, THE_FONT_PARAMS, theStrictLevel);
246 }
247
248 // =======================================================================
249 // function : RenderGlyph
250 // purpose  :
251 // =======================================================================
252 TopoDS_Shape Font_BRepFont::RenderGlyph (const Standard_Utf32Char& theChar)
253 {
254   TopoDS_Shape aShape;
255   Standard_Mutex::Sentry aSentry (myMutex);
256   renderGlyph (theChar, aShape);
257   return aShape;
258 }
259
260 // =======================================================================
261 // function : to3d
262 // purpose  :
263 // =======================================================================
264 bool Font_BRepFont::to3d (const Handle(Geom2d_Curve)& theCurve2d,
265                           const GeomAbs_Shape        theContinuity,
266                           Handle(Geom_Curve)&        theCurve3d)
267 {
268   Standard_Real aMaxDeviation   = 0.0;
269   Standard_Real anAverDeviation = 0.0;
270   myCurve2dAdaptor->ChangeCurve2d().Load (theCurve2d);
271   const Handle(Adaptor2d_HCurve2d)& aCurve = myCurve2dAdaptor; // to avoid ambiguity
272   myCurvOnSurf.Load (aCurve);
273   GeomLib::BuildCurve3d (myPrecision, myCurvOnSurf,
274                          myCurve2dAdaptor->FirstParameter(), myCurve2dAdaptor->LastParameter(),
275                          theCurve3d, aMaxDeviation, anAverDeviation, theContinuity);
276   return !theCurve3d.IsNull();
277 }
278
279
280 // =======================================================================
281 // function : buildFaces
282 // purpose  :
283 // =======================================================================
284 Standard_Boolean Font_BRepFont::buildFaces (const NCollection_Sequence<TopoDS_Wire>& theWires,
285                                             TopoDS_Shape& theRes)
286 {
287   // classify wires
288   NCollection_DataMap<TopoDS_Shape, NCollection_Sequence<TopoDS_Wire>, TopTools_ShapeMapHasher> aMapOutInts;
289   TopTools_DataMapOfShapeInteger aMapNbOuts;
290   TopoDS_Face aF;
291   myBuilder.MakeFace (aF, mySurface, myPrecision);
292   Standard_Integer aWireIter1Index = 1;
293   for (NCollection_Sequence<TopoDS_Wire>::Iterator aWireIter1 (theWires); aWireIter1.More(); ++aWireIter1Index, aWireIter1.Next())
294   {
295     const TopoDS_Wire& aW1 = aWireIter1.Value();
296     if (!aMapNbOuts.IsBound (aW1))
297     {
298       const Standard_Integer aNbOuts = 0;
299       aMapNbOuts.Bind (aW1, aNbOuts);
300     }
301
302     NCollection_Sequence<TopoDS_Wire>* anIntWs = aMapOutInts.Bound (aW1, NCollection_Sequence<TopoDS_Wire>());
303     Standard_Integer aWireIter2Index = 1;
304     for (NCollection_Sequence<TopoDS_Wire>::Iterator aWireIter2 (theWires); aWireIter2.More(); ++aWireIter2Index, aWireIter2.Next())
305     {
306       if (aWireIter1Index == aWireIter2Index)
307       {
308         continue;
309       }
310
311       const TopoDS_Wire& aW2 = aWireIter2.Value();
312       const TopAbs_State aClass = classifyWW (aW1, aW2, aF);
313       if (aClass == TopAbs_IN)
314       {
315         anIntWs->Append (aW2);
316         if (Standard_Integer* aNbOutsPtr = aMapNbOuts.ChangeSeek (aW2))
317         {
318           ++(*aNbOutsPtr);
319         }
320         else
321         {
322           const Standard_Integer aNbOuts = 1;
323           aMapNbOuts.Bind (aW2, aNbOuts);
324         }
325       }
326     }
327   }
328
329   // check out wires and remove "not out" wires from maps
330   for (TopTools_DataMapIteratorOfDataMapOfShapeInteger anOutIter (aMapNbOuts); anOutIter.More(); anOutIter.Next())
331   {
332     const Standard_Integer aTmp = anOutIter.Value() % 2;
333     if (aTmp > 0)
334     {
335       // not out wire
336       aMapOutInts.UnBind (anOutIter.Key());
337     }
338   }
339
340   // create faces for out wires
341   TopTools_MapOfShape anUsedShapes;
342   TopoDS_Compound aFaceComp;
343   myBuilder.MakeCompound (aFaceComp);
344   for (; !aMapOutInts.IsEmpty(); )
345   {
346     // find out wire with max number of outs
347     TopoDS_Shape aW;
348     Standard_Integer aMaxNbOuts = -1;
349     for (NCollection_DataMap<TopoDS_Shape, NCollection_Sequence<TopoDS_Wire>, TopTools_ShapeMapHasher>::Iterator itMOI (aMapOutInts);
350          itMOI.More(); itMOI.Next())
351     {
352       const TopoDS_Shape& aKey = itMOI.Key();
353       const Standard_Integer aNbOuts = aMapNbOuts.Find (aKey);
354       if (aNbOuts > aMaxNbOuts)
355       {
356         aMaxNbOuts = aNbOuts;
357         aW = aKey;
358       }
359     }
360
361     // create face for selected wire
362     TopoDS_Face aNewF;
363     myBuilder.MakeFace (aNewF, mySurface, myPrecision);
364     myBuilder.Add (aNewF, aW);
365     anUsedShapes.Add (aW);
366     const NCollection_Sequence<TopoDS_Wire>& anIns = aMapOutInts.Find (aW);
367     for (NCollection_Sequence<TopoDS_Wire>::Iterator aWireIter (anIns); aWireIter.More(); aWireIter.Next())
368     {
369       TopoDS_Wire aWin = aWireIter.Value();
370       if (anUsedShapes.Contains (aWin))
371       {
372         continue;
373       }
374
375       aWin.Reverse();
376       myBuilder.Add (aNewF, aWin);
377       anUsedShapes.Add (aWin);
378     }
379
380     myBuilder.Add (aFaceComp, aNewF);
381     aMapOutInts.UnBind (aW);
382   }
383
384   if (aFaceComp.NbChildren() == 0)
385   {
386     return Standard_False;
387   }
388
389   if (aFaceComp.NbChildren() == 1)
390   {
391     theRes = TopoDS_Iterator (aFaceComp).Value();
392   }
393   else
394   {
395     theRes = aFaceComp;
396   }
397   return Standard_True;
398 }
399
400
401 // =======================================================================
402 // function : renderGlyph
403 // purpose  :
404 // =======================================================================
405 Standard_Boolean Font_BRepFont::renderGlyph (const Standard_Utf32Char theChar,
406                                              TopoDS_Shape&            theShape)
407 {
408   theShape.Nullify();
409   if (!loadGlyph (theChar)
410    || myActiveFTFace->glyph->format != FT_GLYPH_FORMAT_OUTLINE)
411   {
412     return Standard_False;
413   }
414   else if (myCache.Find (theChar, theShape))
415   {
416     return !theShape.IsNull();
417   }
418
419   const FT_Outline& anOutline = myActiveFTFace->glyph->outline;
420   if (!anOutline.n_contours)
421     return Standard_False;
422
423   TopLoc_Location aLoc;
424   NCollection_Sequence<TopoDS_Wire> aWires;
425   TopoDS_Compound aFaceCompDraft;
426
427   // Get orientation is useless since it doesn't retrieve any in-font information and just computes orientation.
428   // Because it fails in some cases - leave this to ShapeFix.
429   //const FT_Orientation anOrient = FT_Outline_Get_Orientation (&anOutline);
430   for (short aContour = 0, aStartIndex = 0; aContour < anOutline.n_contours; ++aContour)
431   {
432     const FT_Vector* aPntList = &anOutline.points[aStartIndex];
433     const char* aTags      = &anOutline.tags[aStartIndex];
434     const short anEndIndex = anOutline.contours[aContour];
435     const short aPntsNb    = (anEndIndex - aStartIndex) + 1;
436     aStartIndex = anEndIndex + 1;
437     if (aPntsNb < 3 && !myFontParams.IsSingleStrokeFont)
438     {
439       // closed contour can not be constructed from < 3 points
440       continue;
441     }
442
443     BRepBuilderAPI_MakeWire aWireMaker;
444
445     gp_XY aPntPrev;
446     gp_XY aPntCurr = readFTVec (aPntList[aPntsNb - 1], myScaleUnits, myWidthScaling);
447     gp_XY aPntNext = readFTVec (aPntList[0], myScaleUnits, myWidthScaling);
448
449     bool isLineSeg = !myFontParams.IsSingleStrokeFont
450                   && FT_CURVE_TAG(aTags[aPntsNb - 1]) == FT_Curve_Tag_On;
451     gp_XY aPntLine1 = aPntCurr;
452
453     // see http://freetype.sourceforge.net/freetype2/docs/glyphs/glyphs-6.html
454     // for a full description of FreeType tags.
455     for (short aPntId = 0; aPntId < aPntsNb; ++aPntId)
456     {
457       aPntPrev = aPntCurr;
458       aPntCurr = aPntNext;
459       aPntNext = readFTVec (aPntList[(aPntId + 1) % aPntsNb], myScaleUnits, myWidthScaling);
460
461       // process tags
462       if (FT_CURVE_TAG(aTags[aPntId]) == FT_Curve_Tag_On)
463       {
464         if (!isLineSeg)
465         {
466           aPntLine1 = aPntCurr;
467           isLineSeg = true;
468           continue;
469         }
470
471         const gp_XY         aDirVec  = aPntCurr - aPntLine1;
472         const Standard_Real aLen     = aDirVec.Modulus();
473         if (aLen <= myPrecision)
474         {
475           aPntLine1 = aPntCurr;
476           isLineSeg = true;
477           continue;
478         }
479
480         if (myIsCompositeCurve)
481         {
482           Handle(Geom2d_TrimmedCurve) aLine = GCE2d_MakeSegment (gp_Pnt2d (aPntLine1), gp_Pnt2d (aPntCurr));
483           myConcatMaker.Add (aLine, myPrecision);
484         }
485         else
486         {
487           Handle(Geom_Curve)  aCurve3d;
488           Handle(Geom2d_Line) aCurve2d = new Geom2d_Line (gp_Pnt2d (aPntLine1), gp_Dir2d (aDirVec));
489           if (to3d (aCurve2d, GeomAbs_C1, aCurve3d))
490           {
491             TopoDS_Edge anEdge = BRepLib_MakeEdge (aCurve3d, 0.0, aLen);
492             myBuilder.UpdateEdge (anEdge, aCurve2d, mySurface, aLoc, myPrecision);
493             aWireMaker.Add (anEdge);
494           }
495         }
496         aPntLine1 = aPntCurr;
497       }
498       else if (FT_CURVE_TAG(aTags[aPntId]) == FT_Curve_Tag_Conic)
499       {
500         isLineSeg = false;
501         gp_XY aPntPrev2 = aPntPrev;
502         gp_XY aPntNext2 = aPntNext;
503
504         // previous point is either the real previous point (an "on" point),
505         // or the midpoint between the current one and the previous "conic off" point
506         if (FT_CURVE_TAG(aTags[(aPntId - 1 + aPntsNb) % aPntsNb]) == FT_Curve_Tag_Conic)
507         {
508           aPntPrev2 = (aPntCurr + aPntPrev) * 0.5;
509         }
510
511         // next point is either the real next point or the midpoint
512         if (FT_CURVE_TAG(aTags[(aPntId + 1) % aPntsNb]) == FT_Curve_Tag_Conic)
513         {
514           aPntNext2 = (aPntCurr + aPntNext) * 0.5;
515         }
516
517         my3Poles.SetValue (1, aPntPrev2);
518         my3Poles.SetValue (2, aPntCurr);
519         my3Poles.SetValue (3, aPntNext2);
520         Handle(Geom2d_BezierCurve) aBezierArc = new Geom2d_BezierCurve (my3Poles);
521         if (myIsCompositeCurve)
522         {
523           myConcatMaker.Add (aBezierArc, myPrecision);
524         }
525         else
526         {
527           Handle(Geom_Curve) aCurve3d;
528           if (to3d (aBezierArc, GeomAbs_C1, aCurve3d))
529           {
530             TopoDS_Edge anEdge = BRepLib_MakeEdge (aCurve3d);
531             myBuilder.UpdateEdge (anEdge, aBezierArc, mySurface, aLoc, myPrecision);
532             aWireMaker.Add (anEdge);
533           }
534         }
535       }
536       else if (FT_CURVE_TAG(aTags[aPntId])                 == FT_Curve_Tag_Cubic
537             && FT_CURVE_TAG(aTags[(aPntId + 1) % aPntsNb]) == FT_Curve_Tag_Cubic)
538       {
539         isLineSeg = false;
540         my4Poles.SetValue (1, aPntPrev);
541         my4Poles.SetValue (2, aPntCurr);
542         my4Poles.SetValue (3, aPntNext);
543         my4Poles.SetValue (4, gp_Pnt2d(readFTVec (aPntList[(aPntId + 2) % aPntsNb], myScaleUnits, myWidthScaling)));
544         Handle(Geom2d_BezierCurve) aBezier = new Geom2d_BezierCurve (my4Poles);
545         if (myIsCompositeCurve)
546         {
547           myConcatMaker.Add (aBezier, myPrecision);
548         }
549         else
550         {
551           Handle(Geom_Curve) aCurve3d;
552           if (to3d (aBezier, GeomAbs_C1, aCurve3d))
553           {
554             TopoDS_Edge anEdge = BRepLib_MakeEdge (aCurve3d);
555             myBuilder.UpdateEdge (anEdge, aBezier, mySurface, aLoc, myPrecision);
556             aWireMaker.Add (anEdge);
557           }
558         }
559       }
560     }
561
562     if (myIsCompositeCurve)
563     {
564       Handle(Geom2d_BSplineCurve) aDraft2d = myConcatMaker.BSplineCurve();
565       if (aDraft2d.IsNull())
566       {
567         continue;
568       }
569
570       const gp_Pnt2d aFirstPnt = aDraft2d->StartPoint();
571       const gp_Pnt2d aLastPnt  = aDraft2d->EndPoint();
572       if (!myFontParams.IsSingleStrokeFont
573        && !aFirstPnt.IsEqual (aLastPnt, myPrecision))
574       {
575         Handle(Geom2d_TrimmedCurve) aLine = GCE2d_MakeSegment (aLastPnt, aFirstPnt);
576         myConcatMaker.Add (aLine, myPrecision);
577       }
578
579       Handle(Geom2d_BSplineCurve) aCurve2d = myConcatMaker.BSplineCurve();
580       Handle(Geom_Curve)          aCurve3d;
581       if (to3d (aCurve2d, GeomAbs_C0, aCurve3d))
582       {
583         TopoDS_Edge anEdge = BRepLib_MakeEdge (aCurve3d);
584         myBuilder.UpdateEdge (anEdge, aCurve2d, mySurface, aLoc, myPrecision);
585         aWireMaker.Add (anEdge);
586       }
587       myConcatMaker.Clear();
588     }
589     else
590     {
591       if (!aWireMaker.IsDone())
592       {
593         continue;
594       }
595
596       TopoDS_Vertex aFirstV, aLastV;
597       TopExp::Vertices (aWireMaker.Wire(), aFirstV, aLastV);
598       gp_Pnt aFirstPoint = BRep_Tool::Pnt (aFirstV);
599       gp_Pnt aLastPoint  = BRep_Tool::Pnt (aLastV);
600       if (!myFontParams.IsSingleStrokeFont
601        && !aFirstPoint.IsEqual (aLastPoint, myPrecision))
602       {
603         aWireMaker.Add (BRepLib_MakeEdge (aFirstV, aLastV));
604       }
605     }
606
607     if (!aWireMaker.IsDone())
608     {
609       continue;
610     }
611
612     TopoDS_Wire aWireDraft = aWireMaker.Wire();
613     if (!myFontParams.IsSingleStrokeFont)
614     {
615       // collect all wires and set CCW orientation
616       TopoDS_Face aFace;
617       myBuilder.MakeFace (aFace, mySurface, myPrecision);
618       myBuilder.Add (aFace, aWireDraft);
619       BRepTopAdaptor_FClass2d aClass2d (aFace, ::Precision::PConfusion());
620       TopAbs_State aState = aClass2d.PerformInfinitePoint();
621       if (aState != TopAbs_OUT)
622       {
623         // need to reverse
624         aWireDraft.Reverse();
625       }
626       aWires.Append (aWireDraft);
627     }
628     else
629     {
630       if (aFaceCompDraft.IsNull())
631       {
632         myBuilder.MakeCompound (aFaceCompDraft);
633       }
634       myBuilder.Add (aFaceCompDraft, aWireDraft);
635     }
636   }
637
638   if (!aWires.IsEmpty())
639   {
640     buildFaces (aWires, theShape);
641   }
642   else if (!aFaceCompDraft.IsNull())
643   {
644     theShape = aFaceCompDraft;
645   }
646
647   myCache.Bind (theChar, theShape);
648   return !theShape.IsNull();
649 }