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