0030537: Visualization - wrapping text in font text formatter
[occt.git] / src / Font / Font_TextFormatter.cxx
1 // Created on: 2013-01-29
2 // Created by: Kirill GAVRILOV
3 // Copyright (c) 2013-2014 OPEN CASCADE SAS
4 //
5 // This file is part of Open CASCADE Technology software library.
6 //
7 // This library is free software; you can redistribute it and/or modify it under
8 // the terms of the GNU Lesser General Public License version 2.1 as published
9 // by the Free Software Foundation, with special exception defined in the file
10 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
11 // distribution for complete text of the license and disclaimer of any warranty.
12 //
13 // Alternatively, this file may be used under the terms of Open CASCADE
14 // commercial license or contractual agreement.
15
16 #include <Font_TextFormatter.hxx>
17
18 #include <Font_FTFont.hxx>
19
20 #include <Precision.hxx>
21
22 IMPLEMENT_STANDARD_RTTIEXT (Font_TextFormatter, Standard_Transient)
23
24 namespace
25 {
26   typedef NCollection_Vec2<Standard_ShortReal> Vec2f;
27
28   //! Auxiliary function to translate corners by the vector.
29   inline void move (NCollection_Vector< Vec2f >& theCorners,
30                     const Vec2f&                 theMoveVec,
31                     Standard_Integer             theCharLower,
32                     const Standard_Integer       theCharUpper)
33   {
34     for(; theCharLower <= theCharUpper; ++theCharLower)
35     {
36       theCorners.ChangeValue (theCharLower) += theMoveVec;
37     }
38   }
39
40   //! Auxiliary function to translate corners in vertical direction.
41   inline void moveY (NCollection_Vector<Vec2f>& theCorners,
42                      const Standard_ShortReal   theMoveVec,
43                      Standard_Integer           theCharLower,
44                      const Standard_Integer     theCharUpper)
45   {
46     for(; theCharLower <= theCharUpper; ++theCharLower)
47     {
48       theCorners.ChangeValue (theCharLower).y() += theMoveVec;
49     }
50   }
51
52 }
53
54 // =======================================================================
55 // function : Font_TextFormatter
56 // purpose  :
57 // =======================================================================
58 Font_TextFormatter::Font_TextFormatter()
59 : myAlignX (Graphic3d_HTA_LEFT),
60   myAlignY (Graphic3d_VTA_TOP),
61   myTabSize (8),
62   myWrappingWidth (0.0f),
63   myLastSymbolWidth (0.0f),
64   myMaxSymbolWidth (0.0f),
65   //
66   myPen (0.0f, 0.0f),
67   myLineSpacing (0.0f),
68   myAscender (0.0f),
69   myIsFormatted (false),
70   //
71   myLinesNb (0),
72   myRectLineStart (0),
73   myNewLineNb(0),
74   myPenCurrLine (0.0f),
75   myBndTop   (0.0f),
76   myBndWidth (0.0f),
77   myMoveVec (0.0f, 0.0f)
78 {
79   //
80 }
81
82 // =======================================================================
83 // function : SetupAlignment
84 // purpose  :
85 // =======================================================================
86 void Font_TextFormatter::SetupAlignment (const Graphic3d_HorizontalTextAlignment theAlignX,
87                                          const Graphic3d_VerticalTextAlignment   theAlignY)
88 {
89   myAlignX = theAlignX;
90   myAlignY = theAlignY;
91 }
92
93 // =======================================================================
94 // function : Reset
95 // purpose  :
96 // =======================================================================
97 void Font_TextFormatter::Reset()
98 {
99   myIsFormatted = false;
100   myString.Clear();
101   myPen.x() = myPen.y() = 0.0f;
102   myLineSpacing = myAscender = 0.0f;
103   myCorners.Clear();
104   myNewLines.Clear();
105
106   myLastSymbolWidth = 0.0f;
107   myMaxSymbolWidth = 0.0f;
108 }
109
110 // =======================================================================
111 // function : Append
112 // purpose  :
113 // =======================================================================
114 void Font_TextFormatter::Append (const NCollection_String& theString,
115                                  Font_FTFont&              theFont)
116 {
117   if (theString.IsEmpty())
118   {
119     return;
120   }
121
122   myAscender    = Max (myAscender,    theFont.Ascender());
123   myLineSpacing = Max (myLineSpacing, theFont.LineSpacing());
124   myString     += theString;
125
126   int aSymbolsCounter = 0; // special counter to process tabulation symbols
127
128   // first pass - render all symbols using associated font on single ZERO baseline
129   for (Font_TextFormatter::Iterator aFormatterIt (*this); aFormatterIt.More(); aFormatterIt.Next())
130   {
131     const Standard_Utf32Char aCharThis = aFormatterIt.Symbol();
132     const Standard_Utf32Char aCharNext = aFormatterIt.SymbolNext();
133
134     Standard_ShortReal anAdvanceX = 0;
135     if (IsCommandSymbol (aCharThis))
136     {
137       continue; // skip unsupported carriage control codes
138     }
139     else if (aCharThis == '\x0A') // LF (line feed, new line)
140     {
141       aSymbolsCounter = 0;
142       myNewLines.Append (myPen.x());
143       anAdvanceX = 0; // the symbol has null width
144     }
145     else if (aCharThis == ' ')
146     {
147       anAdvanceX = theFont.AdvanceX (' ', aCharNext);
148     }
149     else if (aCharThis == '\t')
150     {
151       const Standard_Integer aSpacesNum = (myTabSize - (aSymbolsCounter - 1) % myTabSize);
152       anAdvanceX = theFont.AdvanceX (' ', aCharNext) * Standard_ShortReal(aSpacesNum);
153       aSymbolsCounter += aSpacesNum;
154     }
155     else
156     {
157       anAdvanceX = theFont.AdvanceX (aCharThis, aCharNext);
158     }
159     ++aSymbolsCounter;
160     myCorners.Append (myPen);
161     myPen.x() += anAdvanceX;
162     myMaxSymbolWidth = Max (myMaxSymbolWidth, anAdvanceX);
163   }
164   myLastSymbolWidth = myPen.x() - myCorners.Last().x();
165 }
166
167 // =======================================================================
168 // function : newLine
169 // purpose  :
170 // =======================================================================
171 void Font_TextFormatter::newLine (const Standard_Integer theLastRect,
172                                   const Standard_ShortReal theMaxLineWidth)
173 {
174   Standard_Integer aFirstCornerId = myRectLineStart;
175   Standard_Integer aLastCornerId = theLastRect;
176
177   if (aFirstCornerId >= myCorners.Length())
178   {
179     ++myLinesNb;
180     myPenCurrLine -= myLineSpacing;
181     return;
182   }
183
184   Standard_ShortReal aXMin = BottomLeft (aFirstCornerId).x();
185   Font_Rect aBndBox;
186   GlyphBoundingBox (aLastCornerId, aBndBox);
187   Standard_ShortReal aXMax = aBndBox.Right;
188
189   myMoveVec.y() = myPenCurrLine;
190   switch (myAlignX)
191   {
192     default:
193     case Graphic3d_HTA_LEFT:   myMoveVec.x() = -aXMin;
194       break;
195     case Graphic3d_HTA_RIGHT:  myMoveVec.x() = -aXMin +        (theMaxLineWidth - (aXMax - aXMin)) -        theMaxLineWidth;
196       break;
197     case Graphic3d_HTA_CENTER: myMoveVec.x() = -aXMin + 0.5f * (theMaxLineWidth - (aXMax - aXMin)) - 0.5f * theMaxLineWidth;
198       break;
199   }
200
201   move (myCorners, myMoveVec, myRectLineStart, theLastRect);
202
203   ++myLinesNb;
204   myPenCurrLine -= myLineSpacing;
205   myRectLineStart = theLastRect + 1;
206 }
207
208 // =======================================================================
209 // function : Format
210 // purpose  :
211 // =======================================================================
212 void Font_TextFormatter::Format()
213 {
214   if (myCorners.Length() == 0 || myIsFormatted)
215   {
216     return;
217   }
218
219   myIsFormatted = true;
220   myLinesNb = myRectLineStart = 0;
221   myBndTop     = 0.0f;
222   myBndWidth   = 0.0f;
223   myMoveVec.x() = myMoveVec.y() = 0.0f;
224
225   // split text into lines and apply horizontal alignment
226   myPenCurrLine = -myAscender;
227   Standard_Integer aRectIter = 0;
228   myNewLineNb = 0;
229
230   Standard_ShortReal aMaxLineWidth = Wrapping();
231   if (HasWrapping())
232   {
233     // it is not possible to wrap less than symbol width
234     aMaxLineWidth = Max (aMaxLineWidth, MaximumSymbolWidth());
235   }
236   else
237   {
238     if (myNewLines.IsEmpty()) // If only one line
239     {
240       aMaxLineWidth = myPen.x();
241     }
242     else
243     {
244       for (int aLineIt = 0; aLineIt < myNewLines.Size(); aLineIt++)
245       {
246         aMaxLineWidth = Max (aMaxLineWidth, LineWidth (aLineIt));
247       }
248       aMaxLineWidth = Max (aMaxLineWidth, LineWidth (myNewLines.Size())); // processing the last line also
249     }
250   }
251
252   for (Font_TextFormatter::Iterator aFormatterIt(*this);
253        aFormatterIt.More(); aFormatterIt.Next())
254   {
255     const Standard_Utf32Char aCharThis = aFormatterIt.Symbol();
256     aRectIter = aFormatterIt.SymbolPosition();
257
258     if (aCharThis == '\x0A') // LF (line feed, new line)
259     {
260       const Standard_Integer aLastRect = aRectIter; // last rect on current line
261       newLine (aLastRect, aMaxLineWidth);
262       ++myNewLineNb;
263       continue;
264     }
265     else if (HasWrapping()) // wrap lines longer than maximum width
266     {
267       Standard_Integer aFirstCornerId = myRectLineStart;
268
269       Font_Rect aBndBox;
270       GlyphBoundingBox (aRectIter, aBndBox);
271       const Standard_ShortReal aNextXPos = aBndBox.Right - BottomLeft (aFirstCornerId).x();
272       if (aNextXPos > aMaxLineWidth) // wrap the line and do processing of the symbol
273       {
274         const Standard_Integer aLastRect = aRectIter - 1; // last rect on current line
275         newLine (aLastRect, aMaxLineWidth);
276       }
277     }
278   }
279
280   myBndWidth = aMaxLineWidth;
281
282   // move last line
283   newLine (myCorners.Length() - 1, aMaxLineWidth);
284
285   // apply vertical alignment style
286   if (myAlignY == Graphic3d_VTA_BOTTOM)
287   {
288     myBndTop = -myLineSpacing - myPenCurrLine;
289   }
290   else if (myAlignY == Graphic3d_VTA_CENTER)
291   {
292     myBndTop = 0.5f * (myLineSpacing * Standard_ShortReal(myLinesNb));
293   }
294   else if (myAlignY == Graphic3d_VTA_TOPFIRSTLINE)
295   {
296     myBndTop = myAscender;
297   }
298
299   if (myAlignY != Graphic3d_VTA_TOP)
300   {
301     moveY (myCorners, myBndTop, 0, myCorners.Length() - 1);
302   }
303 }
304
305 // =======================================================================
306 // function : GlyphBoundingBox
307 // purpose  :
308 // =======================================================================
309 Standard_Boolean Font_TextFormatter::GlyphBoundingBox (const Standard_Integer theIndex,
310                                                        Font_Rect& theBndBox) const
311 {
312   if (theIndex < 0 || theIndex >= Corners().Size())
313     return Standard_False;
314
315   const NCollection_Vec2<Standard_ShortReal>& aLeftCorner = BottomLeft (theIndex);
316   if (theIndex + 1 < myCorners.Length()) // not the last symbol
317   {
318     const NCollection_Vec2<Standard_ShortReal>& aNextLeftCorner = BottomLeft (theIndex + 1);
319     theBndBox.Left = aLeftCorner.x();
320     theBndBox.Bottom = aLeftCorner.y();
321     theBndBox.Top = theBndBox.Bottom + myLineSpacing;
322     if (Abs (aLeftCorner.y() - aNextLeftCorner.y()) < Precision::Confusion()) // in the same row
323     {
324       theBndBox.Right = aNextLeftCorner.x();
325     }
326     else
327     {
328       // the next symbol is on the next row either by '\n' or by wrapping
329       Standard_ShortReal aLineWidth = LineWidth (LineIndex (theIndex));
330       theBndBox.Left = aLeftCorner.x();
331       switch (myAlignX)
332       {
333         case Graphic3d_HTA_LEFT:   theBndBox.Right = aLineWidth; break;
334         case Graphic3d_HTA_RIGHT:  theBndBox.Right = myBndWidth; break;
335         case Graphic3d_HTA_CENTER: theBndBox.Right = 0.5f * (myBndWidth + aLineWidth); break;
336       }
337     }
338   }
339   else // the last symbol
340   {
341     theBndBox.Left = aLeftCorner.x();
342     theBndBox.Right = aLeftCorner.x() + myLastSymbolWidth;
343     theBndBox.Bottom = aLeftCorner.y();
344     theBndBox.Top = theBndBox.Bottom + myLineSpacing;
345   }
346   return Standard_True;
347 }
348
349
350 // =======================================================================
351 // function : IsLFSymbol
352 // purpose  :
353 // =======================================================================
354 Standard_Boolean Font_TextFormatter::IsLFSymbol (const Standard_Integer theIndex) const
355 {
356   Font_Rect aBndBox;
357   if (!GlyphBoundingBox (theIndex, aBndBox))
358     return Standard_False;
359
360   return Abs (aBndBox.Right - aBndBox.Left) < Precision::Confusion();
361 }
362
363 // =======================================================================
364 // function : FirstPosition
365 // purpose  :
366 // =======================================================================
367 Standard_ShortReal Font_TextFormatter::FirstPosition() const
368 {
369   switch (myAlignX)
370   {
371     default:
372     case Graphic3d_HTA_LEFT:   return 0;
373     case Graphic3d_HTA_RIGHT:  return myBndWidth;
374     case Graphic3d_HTA_CENTER: return 0.5f * myBndWidth;
375   }
376 }
377
378 // =======================================================================
379 // function : LinePositionIndex
380 // purpose  :
381 // =======================================================================
382 Standard_Integer Font_TextFormatter::LinePositionIndex (const Standard_Integer theIndex) const
383 {
384   Standard_Integer anIndex = 0;
385
386   Standard_ShortReal anIndexHeight = BottomLeft (theIndex).y();
387   for (Standard_Integer aPrevIndex = theIndex-1; aPrevIndex >= 0; aPrevIndex--)
388   {
389     if (BottomLeft (aPrevIndex).y() > anIndexHeight)
390     {
391       break;
392     }
393     anIndex++;
394   }
395   return anIndex;
396 }
397
398 // =======================================================================
399 // function : LineIndex
400 // purpose  :
401 // =======================================================================
402 Standard_Integer Font_TextFormatter::LineIndex (const Standard_Integer theIndex) const
403 {
404   if (myLineSpacing < 0.0f)
405     return 0;
406
407   return (Standard_Integer)Abs((BottomLeft (theIndex).y() + myAscender) / myLineSpacing);
408 }
409
410 // =======================================================================
411 // function : LineWidth
412 // purpose  :
413 // =======================================================================
414 Standard_ShortReal Font_TextFormatter::LineWidth (const Standard_Integer theIndex) const
415 {
416   if (theIndex < 0)
417     return 0;
418
419   if (theIndex < myNewLines.Length())
420     return theIndex == 0 ? myNewLines[0] : myNewLines[theIndex] - myNewLines[theIndex -1];
421
422   if (theIndex == myNewLines.Length()) // the last line
423     return theIndex == 0 ? myPen.x() : myPen.x() - myNewLines[theIndex -1];
424
425   return 0;
426 }