0030537: Visualization - wrapping text in font text formatter
[occt.git] / src / Font / Font_TextFormatter.cxx
CommitLineData
317d68c9 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
d2eddacc 18#include <Font_FTFont.hxx>
19
60f7b225 20#include <Precision.hxx>
21
22IMPLEMENT_STANDARD_RTTIEXT (Font_TextFormatter, Standard_Transient)
23
317d68c9 24namespace
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
317d68c9 52}
53
54// =======================================================================
55// function : Font_TextFormatter
56// purpose :
57// =======================================================================
58Font_TextFormatter::Font_TextFormatter()
59: myAlignX (Graphic3d_HTA_LEFT),
60 myAlignY (Graphic3d_VTA_TOP),
61 myTabSize (8),
60f7b225 62 myWrappingWidth (0.0f),
63 myLastSymbolWidth (0.0f),
64 myMaxSymbolWidth (0.0f),
317d68c9 65 //
66 myPen (0.0f, 0.0f),
317d68c9 67 myLineSpacing (0.0f),
68 myAscender (0.0f),
69 myIsFormatted (false),
70 //
71 myLinesNb (0),
72 myRectLineStart (0),
317d68c9 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// =======================================================================
86void 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// =======================================================================
97void Font_TextFormatter::Reset()
98{
99 myIsFormatted = false;
100 myString.Clear();
101 myPen.x() = myPen.y() = 0.0f;
317d68c9 102 myLineSpacing = myAscender = 0.0f;
103 myCorners.Clear();
104 myNewLines.Clear();
60f7b225 105
106 myLastSymbolWidth = 0.0f;
107 myMaxSymbolWidth = 0.0f;
317d68c9 108}
109
110// =======================================================================
111// function : Append
112// purpose :
113// =======================================================================
114void 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
60f7b225 129 for (Font_TextFormatter::Iterator aFormatterIt (*this); aFormatterIt.More(); aFormatterIt.Next())
317d68c9 130 {
60f7b225 131 const Standard_Utf32Char aCharThis = aFormatterIt.Symbol();
132 const Standard_Utf32Char aCharNext = aFormatterIt.SymbolNext();
133
134 Standard_ShortReal anAdvanceX = 0;
135 if (IsCommandSymbol (aCharThis))
317d68c9 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());
60f7b225 143 anAdvanceX = 0; // the symbol has null width
317d68c9 144 }
145 else if (aCharThis == ' ')
146 {
60f7b225 147 anAdvanceX = theFont.AdvanceX (' ', aCharNext);
317d68c9 148 }
149 else if (aCharThis == '\t')
150 {
151 const Standard_Integer aSpacesNum = (myTabSize - (aSymbolsCounter - 1) % myTabSize);
60f7b225 152 anAdvanceX = theFont.AdvanceX (' ', aCharNext) * Standard_ShortReal(aSpacesNum);
317d68c9 153 aSymbolsCounter += aSpacesNum;
317d68c9 154 }
60f7b225 155 else
156 {
157 anAdvanceX = theFont.AdvanceX (aCharThis, aCharNext);
158 }
317d68c9 159 ++aSymbolsCounter;
317d68c9 160 myCorners.Append (myPen);
60f7b225 161 myPen.x() += anAdvanceX;
162 myMaxSymbolWidth = Max (myMaxSymbolWidth, anAdvanceX);
317d68c9 163 }
60f7b225 164 myLastSymbolWidth = myPen.x() - myCorners.Last().x();
317d68c9 165}
166
167// =======================================================================
168// function : newLine
169// purpose :
170// =======================================================================
60f7b225 171void Font_TextFormatter::newLine (const Standard_Integer theLastRect,
172 const Standard_ShortReal theMaxLineWidth)
317d68c9 173{
60f7b225 174 Standard_Integer aFirstCornerId = myRectLineStart;
175 Standard_Integer aLastCornerId = theLastRect;
176
177 if (aFirstCornerId >= myCorners.Length())
317d68c9 178 {
179 ++myLinesNb;
180 myPenCurrLine -= myLineSpacing;
181 return;
182 }
183
60f7b225 184 Standard_ShortReal aXMin = BottomLeft (aFirstCornerId).x();
185 Font_Rect aBndBox;
186 GlyphBoundingBox (aLastCornerId, aBndBox);
187 Standard_ShortReal aXMax = aBndBox.Right;
188
317d68c9 189 myMoveVec.y() = myPenCurrLine;
190 switch (myAlignX)
191 {
192 default:
60f7b225 193 case Graphic3d_HTA_LEFT: myMoveVec.x() = -aXMin;
317d68c9 194 break;
60f7b225 195 case Graphic3d_HTA_RIGHT: myMoveVec.x() = -aXMin + (theMaxLineWidth - (aXMax - aXMin)) - theMaxLineWidth;
317d68c9 196 break;
60f7b225 197 case Graphic3d_HTA_CENTER: myMoveVec.x() = -aXMin + 0.5f * (theMaxLineWidth - (aXMax - aXMin)) - 0.5f * theMaxLineWidth;
317d68c9 198 break;
317d68c9 199 }
200
201 move (myCorners, myMoveVec, myRectLineStart, theLastRect);
202
203 ++myLinesNb;
204 myPenCurrLine -= myLineSpacing;
60f7b225 205 myRectLineStart = theLastRect + 1;
317d68c9 206}
207
208// =======================================================================
209// function : Format
210// purpose :
211// =======================================================================
212void Font_TextFormatter::Format()
213{
60f7b225 214 if (myCorners.Length() == 0 || myIsFormatted)
317d68c9 215 {
216 return;
217 }
218
219 myIsFormatted = true;
60f7b225 220 myLinesNb = myRectLineStart = 0;
317d68c9 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;
60f7b225 229
230 Standard_ShortReal aMaxLineWidth = Wrapping();
231 if (HasWrapping())
317d68c9 232 {
60f7b225 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
317d68c9 239 {
60f7b225 240 aMaxLineWidth = myPen.x();
317d68c9 241 }
60f7b225 242 else
317d68c9 243 {
60f7b225 244 for (int aLineIt = 0; aLineIt < myNewLines.Size(); aLineIt++)
317d68c9 245 {
60f7b225 246 aMaxLineWidth = Max (aMaxLineWidth, LineWidth (aLineIt));
317d68c9 247 }
60f7b225 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();
317d68c9 257
60f7b225 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);
317d68c9 262 ++myNewLineNb;
263 continue;
264 }
60f7b225 265 else if (HasWrapping()) // wrap lines longer than maximum width
317d68c9 266 {
60f7b225 267 Standard_Integer aFirstCornerId = myRectLineStart;
317d68c9 268
60f7b225 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 }
317d68c9 278 }
279
280 myBndWidth = aMaxLineWidth;
281
282 // move last line
60f7b225 283 newLine (myCorners.Length() - 1, aMaxLineWidth);
317d68c9 284
285 // apply vertical alignment style
286 if (myAlignY == Graphic3d_VTA_BOTTOM)
287 {
288 myBndTop = -myLineSpacing - myPenCurrLine;
317d68c9 289 }
290 else if (myAlignY == Graphic3d_VTA_CENTER)
291 {
292 myBndTop = 0.5f * (myLineSpacing * Standard_ShortReal(myLinesNb));
ac84fcf6 293 }
294 else if (myAlignY == Graphic3d_VTA_TOPFIRSTLINE)
295 {
296 myBndTop = myAscender;
297 }
298
299 if (myAlignY != Graphic3d_VTA_TOP)
300 {
60f7b225 301 moveY (myCorners, myBndTop, 0, myCorners.Length() - 1);
317d68c9 302 }
303}
60f7b225 304
305// =======================================================================
306// function : GlyphBoundingBox
307// purpose :
308// =======================================================================
309Standard_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// =======================================================================
354Standard_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// =======================================================================
367Standard_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// =======================================================================
382Standard_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// =======================================================================
402Standard_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// =======================================================================
414Standard_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}