0023457: Slow text rendering
[occt.git] / src / OpenGl / OpenGl_TextFormatter.cxx
1 // Created on: 2013-01-29
2 // Created by: Kirill GAVRILOV
3 // Copyright (c) 2013 OPEN CASCADE SAS
4 //
5 // The content of this file is subject to the Open CASCADE Technology Public
6 // License Version 6.5 (the "License"). You may not use the content of this file
7 // except in compliance with the License. Please obtain a copy of the License
8 // at http://www.opencascade.org and read it completely before using this file.
9 //
10 // The Initial Developer of the Original Code is Open CASCADE S.A.S., having its
11 // main offices at: 1, place des Freres Montgolfier, 78280 Guyancourt, France.
12 //
13 // The Original Code and all software distributed under the License is
14 // distributed on an "AS IS" basis, without warranty of any kind, and the
15 // Initial Developer hereby disclaims all such warranties, including without
16 // limitation, any warranties of merchantability, fitness for a particular
17 // purpose or non-infringement. Please see the License for the specific terms
18 // and conditions governing the rights and limitations under the License.
19
20 #include <OpenGl_TextFormatter.hxx>
21
22 #include <OpenGl_VertexBuffer.hxx>
23
24 #include <cmath>
25
26 namespace
27 {
28
29   //! Auxiliary function to translate rectangle by the vector.
30   inline void move (Font_FTFont::Rect& theRect,
31                     const OpenGl_Vec2& theVec)
32   {
33     theRect.Left   += theVec.x();
34     theRect.Right  += theVec.x();
35     theRect.Top    += theVec.y();
36     theRect.Bottom += theVec.y();
37   }
38
39   //! Auxiliary function to translate rectangles by the vector.
40   inline void move (NCollection_Vector<OpenGl_Font::Tile>& theRects,
41                     const OpenGl_Vec2&                     theMoveVec,
42                     Standard_Integer                       theCharLower,
43                     const Standard_Integer                 theCharUpper)
44   {
45     for(; theCharLower <= theCharUpper; ++theCharLower)
46     {
47       Font_FTFont::Rect& aRect = theRects.ChangeValue (theCharLower).px;
48       move (aRect, theMoveVec);
49     }
50   }
51
52   //! Auxiliary function to translate rectangles in horizontal direction.
53   inline void moveX (NCollection_Vector<OpenGl_Font::Tile>& theRects,
54                      const Standard_ShortReal               theMoveVec,
55                      Standard_Integer                       theCharLower,
56                      const Standard_Integer                 theCharUpper)
57   {
58     for (; theCharLower <= theCharUpper; ++theCharLower)
59     {
60       Font_FTFont::Rect& aRect = theRects.ChangeValue (theCharLower).px;
61       aRect.Left  += theMoveVec;
62       aRect.Right += theMoveVec;
63     }
64   }
65
66   //! Auxiliary function to translate rectangles in vertical direction.
67   inline void moveY (NCollection_Vector<OpenGl_Font::Tile>& theRects,
68                      const Standard_ShortReal               theMoveVec,
69                      Standard_Integer                       theCharLower,
70                      const Standard_Integer                 theCharUpper)
71   {
72     for(; theCharLower <= theCharUpper; ++theCharLower)
73     {
74       Font_FTFont::Rect& aRect = theRects.ChangeValue (theCharLower).px;
75       aRect.Top    += theMoveVec;
76       aRect.Bottom += theMoveVec;
77     }
78   }
79
80   //! Apply floor to vector components.
81   //! @param  theVec - vector to change (by reference!)
82   //! @return modified vector
83   inline OpenGl_Vec2& floor (OpenGl_Vec2& theVec)
84   {
85     theVec.x() = std::floor (theVec.x());
86     theVec.y() = std::floor (theVec.y());
87     return theVec;
88   }
89
90 };
91
92 IMPLEMENT_STANDARD_HANDLE (OpenGl_TextFormatter, Standard_Transient)
93 IMPLEMENT_STANDARD_RTTIEXT(OpenGl_TextFormatter, Standard_Transient)
94
95 // =======================================================================
96 // function : OpenGl_TextFormatter
97 // purpose  :
98 // =======================================================================
99 OpenGl_TextFormatter::OpenGl_TextFormatter()
100 : myAlignX (Graphic3d_HTA_LEFT),
101   myAlignY (Graphic3d_VTA_TOP),
102   myTabSize (8),
103   //
104   myPen (0.0f, 0.0f),
105   myRectsNb (0),
106   myLineSpacing (0.0f),
107   myAscender (0.0f),
108   myIsFormatted (false),
109   //
110   myLinesNb (0),
111   myRectLineStart (0),
112   myRectWordStart (0),
113   myPenCurrLine (0.0f),
114   myLineLeft (0.0f),
115   myLineTail (0.0f),
116   myBndTop   (0.0f),
117   myBndWidth (0.0f),
118   myMoveVec (0.0f, 0.0f)
119 {
120   //
121 }
122
123 // =======================================================================
124 // function : SetupAlignment
125 // purpose  :
126 // =======================================================================
127 void OpenGl_TextFormatter::SetupAlignment (const Graphic3d_HorizontalTextAlignment theAlignX,
128                                            const Graphic3d_VerticalTextAlignment   theAlignY)
129 {
130   myAlignX = theAlignX;
131   myAlignY = theAlignY;
132 }
133
134 // =======================================================================
135 // function : Reset
136 // purpose  :
137 // =======================================================================
138 void OpenGl_TextFormatter::Reset()
139 {
140   myIsFormatted = false;
141   myString.Clear();
142   myPen.x() = myPen.y() = 0.0f;
143   myRectsNb = 0;
144   myLineSpacing = myAscender = 0.0f;
145   myRects.Clear();
146   myNewLines.Clear();
147 }
148
149 // =======================================================================
150 // function : Result
151 // purpose  :
152 // =======================================================================
153 void OpenGl_TextFormatter::Result (NCollection_Vector<GLuint>& theTextures,
154                                    NCollection_Vector< NCollection_Handle <NCollection_Vector <OpenGl_Vec2> > >& theVertsPerTexture,
155                                    NCollection_Vector< NCollection_Handle <NCollection_Vector <OpenGl_Vec2> > >& theTCrdsPerTexture) const
156 {
157   OpenGl_Vec2 aVec (0.0f, 0.0f);
158   theTextures.Clear();
159   theVertsPerTexture.Clear();
160   theTCrdsPerTexture.Clear();
161   for (Standard_Integer aRectIter = 0; aRectIter < myRectsNb; ++aRectIter)
162   {
163     const Font_FTFont::Rect& aRect    = myRects.Value (aRectIter).px;
164     const Font_FTFont::Rect& aRectUV  = myRects.Value (aRectIter).uv;
165     const GLuint             aTexture = myRects.Value (aRectIter).texture;
166
167     Standard_Integer aListId = 0;
168     for (aListId = 0; aListId < theTextures.Length(); ++aListId)
169     {
170       if (theTextures.Value (aListId) == aTexture)
171       {
172         break;
173       }
174     }
175     if (aListId >= theTextures.Length())
176     {
177       theTextures.Append (aTexture);
178       theVertsPerTexture.Append (new NCollection_Vector<OpenGl_Vec2>());
179       theTCrdsPerTexture.Append (new NCollection_Vector<OpenGl_Vec2>());
180     }
181
182     NCollection_Vector<OpenGl_Vec2>& aVerts = *theVertsPerTexture.ChangeValue (aListId);
183     NCollection_Vector<OpenGl_Vec2>& aTCrds = *theTCrdsPerTexture.ChangeValue (aListId);
184
185     // apply floor on position to avoid blurring issues
186     // due to cross-pixel coordinates
187     aVerts.Append (floor(aRect.BottomLeft (aVec)));
188     aVerts.Append (floor(aRect.TopLeft    (aVec)));
189     aVerts.Append (floor(aRect.TopRight   (aVec)));
190     aTCrds.Append (aRectUV.BottomLeft (aVec));
191     aTCrds.Append (aRectUV.TopLeft    (aVec));
192     aTCrds.Append (aRectUV.TopRight   (aVec));
193
194     aVerts.Append (floor(aRect.BottomLeft  (aVec)));
195     aVerts.Append (floor(aRect.TopRight    (aVec)));
196     aVerts.Append (floor(aRect.BottomRight (aVec)));
197     aTCrds.Append (aRectUV.BottomLeft  (aVec));
198     aTCrds.Append (aRectUV.TopRight    (aVec));
199     aTCrds.Append (aRectUV.BottomRight (aVec));
200   }
201 }
202
203 // =======================================================================
204 // function : Result
205 // purpose  :
206 // =======================================================================
207 void OpenGl_TextFormatter::Result (const Handle(OpenGl_Context)&                    theCtx,
208                                    NCollection_Vector<GLuint>&                      theTextures,
209                                    NCollection_Vector<Handle(OpenGl_VertexBuffer)>& theVertsPerTexture,
210                                    NCollection_Vector<Handle(OpenGl_VertexBuffer)>& theTCrdsPerTexture) const
211 {
212   NCollection_Vector< NCollection_Handle <NCollection_Vector <OpenGl_Vec2> > > aVertsPerTexture;
213   NCollection_Vector< NCollection_Handle <NCollection_Vector <OpenGl_Vec2> > > aTCrdsPerTexture;
214   Result (theTextures, aVertsPerTexture, aTCrdsPerTexture);
215
216   if (theVertsPerTexture.Length() != theTextures.Length())
217   {
218     for (Standard_Integer aTextureIter = 0; aTextureIter < theVertsPerTexture.Length(); ++aTextureIter)
219     {
220       theVertsPerTexture.Value (aTextureIter)->Release (theCtx.operator->());
221       theTCrdsPerTexture.Value (aTextureIter)->Release (theCtx.operator->());
222     }
223     theVertsPerTexture.Clear();
224     theTCrdsPerTexture.Clear();
225
226     while (theVertsPerTexture.Length() < theTextures.Length())
227     {
228       Handle(OpenGl_VertexBuffer) aVertsVbo = new OpenGl_VertexBuffer();
229       Handle(OpenGl_VertexBuffer) aTcrdsVbo = new OpenGl_VertexBuffer();
230       theVertsPerTexture.Append (aVertsVbo);
231       theTCrdsPerTexture.Append (aTcrdsVbo);
232       aVertsVbo->Create (theCtx);
233       aTcrdsVbo->Create (theCtx);
234     }
235   }
236
237   for (Standard_Integer aTextureIter = 0; aTextureIter < theTextures.Length(); ++aTextureIter)
238   {
239     const NCollection_Vector<OpenGl_Vec2>& aVerts = *aVertsPerTexture.Value (aTextureIter);
240     Handle(OpenGl_VertexBuffer)& aVertsVbo = theVertsPerTexture.ChangeValue (aTextureIter);
241     if (!aVertsVbo->Init (theCtx, 2, aVerts.Length(), (GLfloat* )NULL)
242      || !myVboEditor.Init (theCtx, aVertsVbo))
243     {
244       continue;
245     }
246     for (Standard_Integer aVertIter = 0; aVertIter < aVerts.Length(); ++aVertIter, myVboEditor.Next())
247     {
248       myVboEditor.Value() = aVerts.Value (aVertIter);
249     }
250     myVboEditor.Flush();
251
252     const NCollection_Vector<OpenGl_Vec2>& aTCrds = *aTCrdsPerTexture.Value (aTextureIter);
253     Handle(OpenGl_VertexBuffer)& aTCrdsVbo = theTCrdsPerTexture.ChangeValue (aTextureIter);
254     if (!aTCrdsVbo->Init (theCtx, 2, aVerts.Length(), (GLfloat* )NULL)
255      || !myVboEditor.Init (theCtx, aTCrdsVbo))
256     {
257       continue;
258     }
259     for (Standard_Integer aVertIter = 0; aVertIter < aVerts.Length(); ++aVertIter, myVboEditor.Next())
260     {
261       myVboEditor.Value() = aTCrds.Value (aVertIter);
262     }
263     myVboEditor.Flush();
264   }
265   myVboEditor.Init (NULL, NULL);
266 }
267
268 // =======================================================================
269 // function : Result
270 // purpose  :
271 // =======================================================================
272 void OpenGl_TextFormatter::Result (const Handle(OpenGl_Context)&                 theCtx,
273                                    NCollection_Vector<GLuint>&                   theTextures,
274                                    NCollection_Vector<Handle(OpenGl_Vec2Array)>& theVertsPerTexture,
275                                    NCollection_Vector<Handle(OpenGl_Vec2Array)>& theTCrdsPerTexture) const
276 {
277   NCollection_Vector< NCollection_Handle <NCollection_Vector <OpenGl_Vec2> > > aVertsPerTexture;
278   NCollection_Vector< NCollection_Handle <NCollection_Vector <OpenGl_Vec2> > > aTCrdsPerTexture;
279   Result (theTextures, aVertsPerTexture, aTCrdsPerTexture);
280
281   theVertsPerTexture.Clear();
282   theTCrdsPerTexture.Clear();
283
284   for (Standard_Integer aTextureIter = 0; aTextureIter < theTextures.Length(); ++aTextureIter)
285   {
286     const NCollection_Vector<OpenGl_Vec2>& aVerts = *aVertsPerTexture.Value (aTextureIter);
287     const NCollection_Vector<OpenGl_Vec2>& aTCrds = *aTCrdsPerTexture.Value (aTextureIter);
288     Handle(OpenGl_Vec2Array) aVertsArray = new OpenGl_Vec2Array (1, aVerts.Length());
289     Handle(OpenGl_Vec2Array) aTCrdsArray = new OpenGl_Vec2Array (1, aVerts.Length());
290     theVertsPerTexture.Append (aVertsArray);
291     theTCrdsPerTexture.Append (aTCrdsArray);
292
293     for (Standard_Integer aVertIter = 0; aVertIter < aVerts.Length(); ++aVertIter)
294     {
295       aVertsArray->ChangeValue (aVertIter + 1) = aVerts.Value (aVertIter);
296     }
297     for (Standard_Integer aVertIter = 0; aVertIter < aVerts.Length(); ++aVertIter)
298     {
299       aTCrdsArray->ChangeValue (aVertIter + 1) = aTCrds.Value (aVertIter);
300     }
301   }
302 }
303
304 // =======================================================================
305 // function : Append
306 // purpose  :
307 // =======================================================================
308 void OpenGl_TextFormatter::Append (const Handle(OpenGl_Context)& theCtx,
309                                    const NCollection_String&     theString,
310                                    OpenGl_Font&                  theFont)
311 {
312   if (theString.IsEmpty())
313   {
314     return;
315   }
316
317   myAscender    = Max (myAscender,    theFont.Ascender());
318   myLineSpacing = Max (myLineSpacing, theFont.LineSpacing());
319   myString     += theString;
320
321   int aSymbolsCounter = 0; // special counter to process tabulation symbols
322
323   // first pass - render all symbols using associated font on single ZERO baseline
324   OpenGl_Font::Tile aTile;
325   memset (&aTile, 0, sizeof(aTile));
326   for (NCollection_Utf8Iter anIter = theString.Iterator(); *anIter != 0;)
327   {
328     const Standard_Utf32Char aCharThis =   *anIter;
329     const Standard_Utf32Char aCharNext = *++anIter;
330
331     if (aCharThis == '\x0D' // CR  (carriage return)
332      || aCharThis == '\a'   // BEL (alarm)
333      || aCharThis == '\f'   // FF  (form feed) NP (new page)
334      || aCharThis == '\b'   // BS  (backspace)
335      || aCharThis == '\v')  // VT  (vertical tab)
336     {
337       continue; // skip unsupported carriage control codes
338     }
339     else if (aCharThis == '\x0A') // LF (line feed, new line)
340     {
341       aSymbolsCounter = 0;
342       myNewLines.Append (myPen.x());
343       continue; // will be processed on second pass
344     }
345     else if (aCharThis == ' ')
346     {
347       ++aSymbolsCounter;
348       myPen.x() += theFont.AdvanceX (' ', aCharNext);
349       continue;
350     }
351     else if (aCharThis == '\t')
352     {
353       const Standard_Integer aSpacesNum = (myTabSize - (aSymbolsCounter - 1) % myTabSize);
354       myPen.x() += theFont.AdvanceX (' ', aCharNext) * Standard_ShortReal(aSpacesNum);
355       aSymbolsCounter += aSpacesNum;
356       continue;
357     }
358
359     ++aSymbolsCounter;
360     theFont.RenderGlyph (theCtx,
361                          aCharThis, aCharNext,
362                          aTile, myPen);
363     myRects.Append (aTile);
364
365     ++myRectsNb;
366   }
367 }
368
369 // =======================================================================
370 // function : newLine
371 // purpose  :
372 // =======================================================================
373 void OpenGl_TextFormatter::newLine (const Standard_Integer theLastRect)
374 {
375   if (myRectLineStart >= myRectsNb)
376   {
377     ++myLinesNb;
378     myPenCurrLine -= myLineSpacing;
379     return;
380   }
381
382   myMoveVec.y() = myPenCurrLine;
383   switch (myAlignX)
384   {
385     default:
386     case Graphic3d_HTA_LEFT:
387     {
388       myMoveVec.x() = (myNewLineNb > 0) ? -myNewLines.Value (myNewLineNb - 1) : 0.0f;
389       break;
390     }
391     case Graphic3d_HTA_RIGHT:
392     {
393       myMoveVec.x() = (myNewLineNb < myNewLines.Length())
394                     ? -myNewLines.Value (myNewLineNb)
395                     : -myPen.x();
396       break;
397     }
398     case Graphic3d_HTA_CENTER:
399     {
400       const Standard_ShortReal aFrom = (myNewLineNb > 0)
401                                      ? myNewLines.Value (myNewLineNb - 1)
402                                      : 0.0f;
403       const Standard_ShortReal aTo   = (myNewLineNb < myNewLines.Length())
404                                      ? myNewLines.Value (myNewLineNb)
405                                      : myPen.x();
406       myMoveVec.x() = -0.5f * (aFrom + aTo);
407       break;
408     }
409   }
410
411   move (myRects, myMoveVec, myRectLineStart, theLastRect);
412
413   ++myLinesNb;
414   myPenCurrLine -= myLineSpacing;
415   myRectLineStart = myRectWordStart = theLastRect + 1;
416   if (myRectLineStart < myRectsNb)
417   {
418     myLineLeft = myRects.Value (myRectLineStart).px.Left;
419   }
420 }
421
422 // =======================================================================
423 // function : Format
424 // purpose  :
425 // =======================================================================
426 void OpenGl_TextFormatter::Format()
427 {
428   if (myRectsNb == 0 || myIsFormatted)
429   {
430     return;
431   }
432
433   myIsFormatted = true;
434   myLinesNb = myRectLineStart = myRectWordStart = 0;
435   myLineLeft   = 0.0f;
436   myLineTail   = 0.0f;
437   myBndTop     = 0.0f;
438   myBndWidth   = 0.0f;
439   myMoveVec.x() = myMoveVec.y() = 0.0f;
440
441   // split text into lines and apply horizontal alignment
442   myPenCurrLine = -myAscender;
443   Standard_Integer aRectIter = 0;
444   myNewLineNb = 0;
445   for (NCollection_Utf8Iter anIter = myString.Iterator(); *anIter != 0; ++anIter)
446   {
447     const Standard_Utf32Char aCharThis = *anIter;
448     if (aCharThis == '\x0D' // CR  (carriage return)
449      || aCharThis == '\a'   // BEL (alarm)
450      || aCharThis == '\f'   // FF  (form feed) NP (new page)
451      || aCharThis == '\b'   // BS  (backspace)
452      || aCharThis == '\v')  // VT  (vertical tab)
453     {
454       continue; // skip unsupported carriage control codes
455     }
456     else if (aCharThis == '\x0A') // LF (line feed, new line)
457     {
458       const Standard_Integer aLastRect = aRectIter - 1; // last rect on current line
459       newLine (aLastRect);
460       ++myNewLineNb;
461       continue;
462     }
463     else if (aCharThis == ' '
464           || aCharThis == '\t')
465     {
466       myRectWordStart = aRectIter;
467       continue;
468     }
469
470     Standard_ShortReal aWidth = myRects.Value (aRectIter).px.Right - myLineLeft;
471     myBndWidth = Max (myBndWidth, aWidth);
472
473     ++aRectIter;
474   }
475
476   // move last line
477   newLine (myRectsNb - 1);
478
479   // apply vertical alignment style
480   if (myAlignY == Graphic3d_VTA_BOTTOM)
481   {
482     myBndTop = -myLineSpacing - myPenCurrLine;
483     moveY (myRects, myBndTop, 0, myRectsNb - 1);
484   }
485   else if (myAlignY == Graphic3d_VTA_CENTER)
486   {
487     myBndTop = 0.5f * (myLineSpacing * Standard_ShortReal(myLinesNb));
488     moveY (myRects, myBndTop, 0, myRectsNb - 1);
489   }
490 }