From: nds Date: Mon, 2 Sep 2019 15:44:07 +0000 (+0300) Subject: 0030537: Visualization - wrapping text in font text formatter X-Git-Url: http://git.dev.opencascade.org/gitweb/?a=commitdiff_plain;h=45c4365a7994b22cf1e2920774c045f7e91ad0fb;p=occt-copy.git 0030537: Visualization - wrapping text in font text formatter Font_TextFormatter inherits Standard_Transient, now it is given as a handle in functions. Graphic3d_Text - extended with Font_TextFormatter to be able to have it filled out of text render. If it is not defined here, the default text formatter of context is used. OpenGl_Context - has default Font_TextFormatter for rendering OpenGl_Text. (cherry picked from commit 5001dd8fd5cdaba17014b3b54f5053245541b3f3) --- diff --git a/src/Font/Font_BRepTextBuilder.cxx b/src/Font/Font_BRepTextBuilder.cxx index 2304c19957..b1c979cfa3 100644 --- a/src/Font/Font_BRepTextBuilder.cxx +++ b/src/Font/Font_BRepTextBuilder.cxx @@ -15,12 +15,14 @@ #include +#include + // ======================================================================= // Function : Perfrom // Purpose : // ======================================================================= TopoDS_Shape Font_BRepTextBuilder::Perform (Font_BRepFont& theFont, - const Font_TextFormatter& theFormatter, + const Handle(Font_TextFormatter)& theFormatter, const gp_Ax3& thePenLoc) { gp_Trsf aTrsf; @@ -31,34 +33,20 @@ TopoDS_Shape Font_BRepTextBuilder::Perform (Font_BRepFont& theFont, myBuilder.MakeCompound (aResult); - Standard_Integer aSymbolCounter = 0; - Standard_Real aScaleUnits = theFont.Scale(); - for (NCollection_Utf8Iter anIter = theFormatter.String().Iterator(); *anIter != 0; ++anIter) + Standard_Real aScaleUnits = theFont.Scale(); + for (Font_TextFormatter::Iterator aFormatterIt (*theFormatter, Font_TextFormatter::IterationFilter_ExcludeInvisible); + aFormatterIt .More(); aFormatterIt .Next()) { - const Standard_Utf32Char aCharCurr = *anIter; - if (aCharCurr == '\x0D' // CR (carriage return) - || aCharCurr == '\a' // BEL (alarm) - || aCharCurr == '\f' // FF (form feed) NP (new page) - || aCharCurr == '\b' // BS (backspace) - || aCharCurr == '\v' // VT (vertical tab) - || aCharCurr == ' ' - || aCharCurr == '\t' - || aCharCurr == '\n') - { - continue; // skip unsupported carriage control codes - } + const NCollection_Vec2& aCorner = theFormatter->BottomLeft (aFormatterIt.SymbolPosition()); - const NCollection_Vec2& aCorner = theFormatter.TopLeft (aSymbolCounter); aPen.SetCoord (aCorner.x() * aScaleUnits, aCorner.y() * aScaleUnits, 0.0); - aGlyphShape = theFont.RenderGlyph (aCharCurr); + aGlyphShape = theFont.RenderGlyph (aFormatterIt.Symbol()); if (!aGlyphShape.IsNull()) { aTrsf.SetTranslation (gp_Vec (aPen)); aGlyphShape.Move (aTrsf); myBuilder.Add (aResult, aGlyphShape); } - - ++aSymbolCounter; } aTrsf.SetTransformation (thePenLoc, gp_Ax3 (gp::XOY())); @@ -77,13 +65,13 @@ TopoDS_Shape Font_BRepTextBuilder::Perform (Font_BRepFont& const Graphic3d_HorizontalTextAlignment theHAlign, const Graphic3d_VerticalTextAlignment theVAlign) { - Font_TextFormatter aFormatter; + Handle(Font_TextFormatter) aFormatter = new Font_TextFormatter(); - aFormatter.Reset(); - aFormatter.SetupAlignment (theHAlign, theVAlign); + aFormatter->Reset(); + aFormatter->SetupAlignment (theHAlign, theVAlign); - aFormatter.Append (theString, *(reinterpret_cast (&theFont))); - aFormatter.Format(); + aFormatter->Append (theString, *(reinterpret_cast (&theFont))); + aFormatter->Format(); return Perform (theFont, aFormatter, thePenLoc); } diff --git a/src/Font/Font_BRepTextBuilder.hxx b/src/Font/Font_BRepTextBuilder.hxx index af03f79e3d..fe46b34a5e 100644 --- a/src/Font/Font_BRepTextBuilder.hxx +++ b/src/Font/Font_BRepTextBuilder.hxx @@ -17,9 +17,10 @@ #define Font_BRepTextBuilder_Header #include -#include #include +class Font_TextFormatter; + //! Represents class for applying text formatting. class Font_BRepTextBuilder { @@ -30,7 +31,7 @@ public: //! @param theFormatter formatter which defines alignment for the text //! @return result shape with pen transformation applied as shape location Standard_EXPORT TopoDS_Shape Perform (Font_BRepFont& theFont, - const Font_TextFormatter& theFormatter, + const Handle(Font_TextFormatter)& theFormatter, const gp_Ax3& thePenLoc = gp_Ax3()); //! Render text as BRep shape. //! @param theString text in UTF-8 encoding diff --git a/src/Font/Font_TextFormatter.cxx b/src/Font/Font_TextFormatter.cxx index 520b3c9270..dfb764f4e8 100644 --- a/src/Font/Font_TextFormatter.cxx +++ b/src/Font/Font_TextFormatter.cxx @@ -17,6 +17,10 @@ #include +#include + +IMPLEMENT_STANDARD_RTTIEXT (Font_TextFormatter, Standard_Transient) + namespace { typedef NCollection_Vec2 Vec2f; @@ -55,16 +59,17 @@ Font_TextFormatter::Font_TextFormatter() : myAlignX (Graphic3d_HTA_LEFT), myAlignY (Graphic3d_VTA_TOP), myTabSize (8), + myWrappingWidth (0.0f), + myLastSymbolWidth (0.0f), + myMaxSymbolWidth (0.0f), // myPen (0.0f, 0.0f), - myRectsNb (0), myLineSpacing (0.0f), myAscender (0.0f), myIsFormatted (false), // myLinesNb (0), myRectLineStart (0), - myRectWordStart (0), myNewLineNb(0), myPenCurrLine (0.0f), myBndTop (0.0f), @@ -94,10 +99,12 @@ void Font_TextFormatter::Reset() myIsFormatted = false; myString.Clear(); myPen.x() = myPen.y() = 0.0f; - myRectsNb = 0; myLineSpacing = myAscender = 0.0f; myCorners.Clear(); myNewLines.Clear(); + + myLastSymbolWidth = 0.0f; + myMaxSymbolWidth = 0.0f; } // ======================================================================= @@ -119,16 +126,14 @@ void Font_TextFormatter::Append (const NCollection_String& theString, int aSymbolsCounter = 0; // special counter to process tabulation symbols // first pass - render all symbols using associated font on single ZERO baseline - for (NCollection_Utf8Iter anIter = theString.Iterator(); *anIter != 0;) + Standard_Utf32Char aCharThis; + for (Font_TextFormatter::Iterator aFormatterIt (*this); aFormatterIt .More(); aFormatterIt .Next()) { - const Standard_Utf32Char aCharThis = *anIter; - const Standard_Utf32Char aCharNext = *++anIter; - - if (aCharThis == '\x0D' // CR (carriage return) - || aCharThis == '\a' // BEL (alarm) - || aCharThis == '\f' // FF (form feed) NP (new page) - || aCharThis == '\b' // BS (backspace) - || aCharThis == '\v') // VT (vertical tab) + aCharThis = aFormatterIt.Symbol(); + const Standard_Utf32Char aCharNext = aFormatterIt.SymbolNext(); + + Standard_ShortReal anAdvanceX = 0; + if (IsCommandSymbol (aCharThis)) { continue; // skip unsupported carriage control codes } @@ -136,79 +141,66 @@ void Font_TextFormatter::Append (const NCollection_String& theString, { aSymbolsCounter = 0; myNewLines.Append (myPen.x()); - continue; // will be processed on second pass + anAdvanceX = 0; // the symbol has null width } else if (aCharThis == ' ') { ++aSymbolsCounter; - myPen.x() += theFont.AdvanceX (' ', aCharNext); - continue; + anAdvanceX = theFont.AdvanceX (' ', aCharNext); } else if (aCharThis == '\t') { const Standard_Integer aSpacesNum = (myTabSize - (aSymbolsCounter - 1) % myTabSize); - myPen.x() += theFont.AdvanceX (' ', aCharNext) * Standard_ShortReal(aSpacesNum); + anAdvanceX = theFont.AdvanceX (' ', aCharNext) * Standard_ShortReal(aSpacesNum); aSymbolsCounter += aSpacesNum; - continue; } + else + anAdvanceX = theFont.AdvanceX (aCharThis, aCharNext); ++aSymbolsCounter; - myCorners.Append (myPen); - - myPen.x() += theFont.AdvanceX (aCharThis, aCharNext); - - ++myRectsNb; + myPen.x() += anAdvanceX; + myMaxSymbolWidth = Max (myMaxSymbolWidth, anAdvanceX); } + myLastSymbolWidth = myPen.x() - myCorners.Last().x(); } // ======================================================================= // function : newLine // purpose : // ======================================================================= -void Font_TextFormatter::newLine (const Standard_Integer theLastRect) +void Font_TextFormatter::newLine (const Standard_Integer theLastRect, + const Standard_ShortReal theMaxLineWidth) { - if (myRectLineStart >= myRectsNb) + Standard_Integer aFirstCornerId = myRectLineStart; + Standard_Integer aLastCornerId = theLastRect; + + if (aFirstCornerId >= myCorners.Length()) { ++myLinesNb; myPenCurrLine -= myLineSpacing; return; } + Standard_ShortReal aXMin = BottomLeft (aFirstCornerId).x(); + Font_Rect aBndBox; + BndBox (aLastCornerId, aBndBox); + Standard_ShortReal aXMax = aBndBox.Right; + myMoveVec.y() = myPenCurrLine; switch (myAlignX) { default: - case Graphic3d_HTA_LEFT: - { - myMoveVec.x() = (myNewLineNb > 0) ? -myNewLines.Value (myNewLineNb - 1) : 0.0f; - break; - } - case Graphic3d_HTA_RIGHT: - { - myMoveVec.x() = (myNewLineNb < myNewLines.Length()) - ? -myNewLines.Value (myNewLineNb) - : -myPen.x(); - break; - } - case Graphic3d_HTA_CENTER: - { - const Standard_ShortReal aFrom = (myNewLineNb > 0) - ? myNewLines.Value (myNewLineNb - 1) - : 0.0f; - const Standard_ShortReal aTo = (myNewLineNb < myNewLines.Length()) - ? myNewLines.Value (myNewLineNb) - : myPen.x(); - myMoveVec.x() = -0.5f * (aFrom + aTo); - break; - } + case Graphic3d_HTA_LEFT: myMoveVec.x() = -aXMin; break; + case Graphic3d_HTA_RIGHT: myMoveVec.x() = -aXMin + (theMaxLineWidth - (aXMax - aXMin)) - theMaxLineWidth; break; + case Graphic3d_HTA_CENTER: myMoveVec.x() = -aXMin + 0.5f * (theMaxLineWidth - (aXMax - aXMin)) - 0.5f * theMaxLineWidth; break; } move (myCorners, myMoveVec, myRectLineStart, theLastRect); ++myLinesNb; myPenCurrLine -= myLineSpacing; - myRectLineStart = myRectWordStart = theLastRect + 1; + myRectLineStart = theLastRect + 1; } // ======================================================================= @@ -217,13 +209,13 @@ void Font_TextFormatter::newLine (const Standard_Integer theLastRect) // ======================================================================= void Font_TextFormatter::Format() { - if (myRectsNb == 0 || myIsFormatted) + if (myCorners.Length() == 0 || myIsFormatted) { return; } myIsFormatted = true; - myLinesNb = myRectLineStart = myRectWordStart = 0; + myLinesNb = myRectLineStart = 0; myBndTop = 0.0f; myBndWidth = 0.0f; myMoveVec.x() = myMoveVec.y() = 0.0f; @@ -232,59 +224,56 @@ void Font_TextFormatter::Format() myPenCurrLine = -myAscender; Standard_Integer aRectIter = 0; myNewLineNb = 0; - Standard_ShortReal aMaxLineWidth = -1.0f; - for (NCollection_Utf8Iter anIter = myString.Iterator(); *anIter != 0; ++anIter) + + Standard_ShortReal aMaxLineWidth = Wrapping(); + if (HasWrapping()) { - const Standard_Utf32Char aCharThis = *anIter; - if (aCharThis == '\x0D' // CR (carriage return) - || aCharThis == '\a' // BEL (alarm) - || aCharThis == '\f' // FF (form feed) NP (new page) - || aCharThis == '\b' // BS (backspace) - || aCharThis == '\v') // VT (vertical tab) + aMaxLineWidth = Max (aMaxLineWidth, MaximumSymbolWidth()); // it is not possible to wrap less than symbol width + } + else + { + if (myNewLines.IsEmpty()) // If only one line + aMaxLineWidth = myPen.x(); + else { - continue; // skip unsupported carriage control codes + for (int aLineIt = 0; aLineIt < myNewLines.Size(); aLineIt++) + aMaxLineWidth = Max (aMaxLineWidth, LineWidth (aLineIt)); + aMaxLineWidth = Max (aMaxLineWidth, LineWidth (myNewLines.Size())); // processing the last line also } - else if (aCharThis == '\x0A') // LF (line feed, new line) - { - // calculate max line width - if (myNewLineNb == 0) - { - aMaxLineWidth = myNewLines.Value(0); - } - else - { - aMaxLineWidth = Max (aMaxLineWidth, myNewLines.Value (myNewLineNb) - myNewLines.Value (myNewLineNb - 1)); - } + } - const Standard_Integer aLastRect = aRectIter - 1; // last rect on current line - newLine (aLastRect); + for (Font_TextFormatter::Iterator aFormatterIt (*this); + aFormatterIt .More(); aFormatterIt .Next()) + { + const Standard_Utf32Char aCharThis = aFormatterIt.Symbol(); + aRectIter = aFormatterIt.SymbolPosition(); + + if (aCharThis == '\x0A') // LF (line feed, new line) + { + const Standard_Integer aLastRect = aRectIter; // last rect on current line + newLine (aLastRect, aMaxLineWidth); ++myNewLineNb; continue; } - else if (aCharThis == ' ' - || aCharThis == '\t') + else if (HasWrapping()) // wrap lines longer than maximum width { - myRectWordStart = aRectIter; - continue; - } - - ++aRectIter; - } + Standard_Integer aFirstCornerId = myRectLineStart; - // If only one line - if (aMaxLineWidth < 0.0f) - { - aMaxLineWidth = myPen.x(); - } - else // Consider last line - { - aMaxLineWidth = Max (aMaxLineWidth, myPen.x() - myNewLines.Value (myNewLineNb - 1)); + Font_Rect aBndBox; + BndBox (aRectIter, aBndBox); + const Standard_ShortReal aNextXPos = aBndBox.Right - BottomLeft (aFirstCornerId).x(); + if (aNextXPos > aMaxLineWidth) // wrap the line and do processing of the symbol + { + const Standard_Integer aLastRect = aRectIter - 1; // last rect on current line + newLine (aLastRect, aMaxLineWidth); + } + } } myBndWidth = aMaxLineWidth; // move last line - newLine (myRectsNb - 1); + newLine (myCorners.Length() - 1, aMaxLineWidth); // apply vertical alignment style if (myAlignY == Graphic3d_VTA_BOTTOM) @@ -302,6 +291,126 @@ void Font_TextFormatter::Format() if (myAlignY != Graphic3d_VTA_TOP) { - moveY (myCorners, myBndTop, 0, myRectsNb - 1); + moveY (myCorners, myBndTop, 0, myCorners.Length() - 1); } } + +// ======================================================================= +// function : BndBox +// purpose : +// ======================================================================= +Standard_Boolean Font_TextFormatter::BndBox (const Standard_Integer theIndex, Font_Rect& theBndBox) const +{ + if (theIndex < 0 || theIndex >= Corners().Size()) + return Standard_False; + + const NCollection_Vec2& aLeftCorner = BottomLeft (theIndex); + if (theIndex + 1 < myCorners.Length()) // not the last symbol + { + const NCollection_Vec2& aNextLeftCorner = BottomLeft (theIndex + 1); + theBndBox.Left = aLeftCorner.x(); + theBndBox.Bottom = aLeftCorner.y(); + theBndBox.Top = theBndBox.Bottom + myLineSpacing; + if (Abs (aLeftCorner.y() - aNextLeftCorner.y()) < Precision::Confusion()) // in the same row + { + theBndBox.Right = aNextLeftCorner.x(); + } + else + { + // the next symbol is on the next row either by '\n' or by wrapping + Standard_ShortReal aLineWidth = LineWidth (LineIndex (theIndex)); + theBndBox.Left = aLeftCorner.x(); + switch (myAlignX) + { + case Graphic3d_HTA_LEFT: theBndBox.Right = aLineWidth; break; + case Graphic3d_HTA_RIGHT: theBndBox.Right = myBndWidth; break; + case Graphic3d_HTA_CENTER: theBndBox.Right = 0.5f * (myBndWidth + aLineWidth); break; + } + } + } + else // the last symbol + { + theBndBox.Left = aLeftCorner.x(); + theBndBox.Right = aLeftCorner.x() + myLastSymbolWidth; + theBndBox.Bottom = aLeftCorner.y(); + theBndBox.Top = theBndBox.Bottom + myLineSpacing; + } + return Standard_True; +} + + +// ======================================================================= +// function : IsLFSymbol +// purpose : +// ======================================================================= +Standard_Boolean Font_TextFormatter::IsLFSymbol (const Standard_Integer theIndex) const +{ + Font_Rect aBndBox; + if (!BndBox (theIndex, aBndBox)) + return Standard_False; + + return Abs (aBndBox.Right - aBndBox.Left) < Precision::Confusion(); +} + +// ======================================================================= +// function : FirstPosition +// purpose : +// ======================================================================= +Standard_ShortReal Font_TextFormatter::FirstPosition() const +{ + switch (myAlignX) + { + default: + case Graphic3d_HTA_LEFT: return 0; break; + case Graphic3d_HTA_RIGHT: return myBndWidth; break; + case Graphic3d_HTA_CENTER: return 0.5f * myBndWidth; break; + } +} + +// ======================================================================= +// function : LinePositionIndex +// purpose : +// ======================================================================= +Standard_Integer Font_TextFormatter::LinePositionIndex (const Standard_Integer theIndex) const +{ + Standard_Integer anIndex = 0; + + Standard_ShortReal anIndexHeight = BottomLeft (theIndex).y(); + for (Standard_Integer aPrevIndex = theIndex-1; aPrevIndex >= 0; aPrevIndex--) + { + if (BottomLeft (aPrevIndex).y() > anIndexHeight) + break; + anIndex++; + } + return anIndex; +} + +// ======================================================================= +// function : LineIndex +// purpose : +// ======================================================================= +Standard_Integer Font_TextFormatter::LineIndex (const Standard_Integer theIndex) const +{ + if (myLineSpacing < 0.0f) + return 0; + + return (Standard_Integer)Abs((BottomLeft (theIndex).y() + myAscender) / myLineSpacing); +} + +// ======================================================================= +// function : LineWidth +// purpose : +// ======================================================================= +Standard_ShortReal Font_TextFormatter::LineWidth (const Standard_Integer theIndex) const +{ + if (theIndex < 0) + return 0; + + if (theIndex < myNewLines.Length()) + return theIndex == 0 ? myNewLines[0] : myNewLines[theIndex] - myNewLines[theIndex -1]; + + if (theIndex == myNewLines.Length()) // the last line + return theIndex == 0 ? myPen.x() : myPen.x() - myNewLines[theIndex -1]; + + return 0; +} diff --git a/src/Font/Font_TextFormatter.hxx b/src/Font/Font_TextFormatter.hxx index 19d68e7e39..64a277acf6 100755 --- a/src/Font/Font_TextFormatter.hxx +++ b/src/Font/Font_TextFormatter.hxx @@ -25,10 +25,111 @@ class Font_FTFont; +DEFINE_STANDARD_HANDLE(Font_TextFormatter, Standard_Transient) + //! This class intended to prepare formatted text. -class Font_TextFormatter +//! Case of the formatter using: +//! Handle(Font_TextFormatter) aFormatter = new Font_TextFormatter(); +//! aFormatter->Append(text_1, aFont1); +//! aFormatter->Append(text_2, aFont2); +//! aFormatter->Format(); +//! +//! Example of corners indices for the text: +//! "row_1\n" - 0-5 +//! "\n" - 6 +//! "\n" - 7 +//! "row_2\n" - 8-13 +//! Processing of \n symbol: +//! - it is placed on the row where it appears +//! - BndBox for \n has zero width +//! - if the last symbol is \n, use LastBndBox() to get position on the next row +//! +//! Pay attention that fonts should have the same LineSpacing value +//! +class Font_TextFormatter : public Standard_Transient { public: + //! Iteration filter flags. Command symbols are skipped with any filter. + enum IterationFilter + { + IterationFilter_None = 0x0000, //!< no filter + IterationFilter_ExcludeInvisible = 0x0002, //!< exclude ' ', '\t', '\n' + }; + + //! Iterator through light sources. + class Iterator + { + public: + //! Constructor with initialization. + Iterator (const Font_TextFormatter& theFormatter, + IterationFilter theFilter = IterationFilter_None) + : myFilter (theFilter), myIter (theFormatter.myString.Iterator()) + { + mySymbolPosition = readNextSymbol (-1, mySymbolChar); + mySymbolNext = readNextSymbol (mySymbolPosition, mySymbolCharNext); + } + + //! Returns TRUE if iterator points to a valid item. + Standard_Boolean More() const { return mySymbolPosition >= 0; } + + //! Returns TRUE if next item exists + Standard_Boolean HasNext() const { return mySymbolNext >= 0; } + + //! Returns current symbol. + Standard_Utf32Char Symbol() const { return mySymbolChar; } + + //! Returns the next symbol if exists. + Standard_Utf32Char SymbolNext() const { return mySymbolCharNext; } + + //! Returns current symbol position. + Standard_Integer SymbolPosition() const { return mySymbolPosition; } + + //! Returns the next symbol position. + Standard_Integer SymbolPositionNext() const { return mySymbolNext; } + + //! Moves to the next item. + void Next() + { + mySymbolPosition = mySymbolNext; + mySymbolChar = mySymbolCharNext; + mySymbolNext = readNextSymbol (mySymbolPosition, mySymbolCharNext); + } + + protected: + Standard_Integer readNextSymbol (const Standard_Integer aSymbolStartingFrom, Standard_Utf32Char& theSymbolChar) + { + Standard_Integer aNextSymbol = aSymbolStartingFrom; + for (; *myIter != 0; ++myIter) + { + const Standard_Utf32Char aCharCurr = *myIter; + if (Font_TextFormatter::IsCommandSymbol (aCharCurr)) + { + continue; // skip unsupported carriage control codes + } + aNextSymbol++; + if ((myFilter & IterationFilter_ExcludeInvisible) != 0) + { + if (aCharCurr == '\x0A'|| // LF (line feed, new line) + aCharCurr == ' ' || + aCharCurr == '\t') + continue; + } + ++myIter; + theSymbolChar = aCharCurr; + return aNextSymbol; // found the first next, not command and not filtered symbol + } + return -1; // the next symbol is not found + } + + protected: + IterationFilter myFilter; //!< possibility to filter not-necessary symbols + + NCollection_Utf8Iter myIter; //!< the next symbol iterator value over the text formatter string + Standard_Integer mySymbolPosition; //!< the current position + Standard_Utf32Char mySymbolChar; //!< the current symbol + Standard_Integer mySymbolNext; //!< position of the next symbol in iterator, if zero, the iterator is finished + Standard_Utf32Char mySymbolCharNext; //!< the current symbol + }; //! Default constructor. Standard_EXPORT Font_TextFormatter(); @@ -49,16 +150,32 @@ public: Standard_EXPORT void Format(); //! Returns specific glyph rectangle. - inline const NCollection_Vec2& TopLeft (const Standard_Integer theIndex) const - { - return myCorners.Value (theIndex); - } + inline const NCollection_Vec2& BottomLeft (const Standard_Integer theIndex) const + { return myCorners.Value (theIndex); } - //! Returns current rendering string. - inline const NCollection_String& String() const - { - return myString; - } + //! Returns symbol bounding box + //! @param bounding box. + Standard_EXPORT Standard_Boolean BndBox (const Standard_Integer theIndex, Font_Rect& theBndBox) const; + + //! Returns the line height + //! \param theIndex a line index, obtained by LineIndex() + Standard_ShortReal LineHeight (const Standard_Integer theIndex) const + { return theIndex == 0 ? myAscender : myLineSpacing; } + + //!< Returns width of a line + Standard_EXPORT Standard_ShortReal LineWidth (const Standard_Integer theIndex) const; + + //! Returns true if the symbol by the index is '\n'. The width of the symbol is zero. + Standard_EXPORT Standard_Boolean IsLFSymbol (const Standard_Integer theIndex) const; + + //! Returns position of the first symbol in a line using alignment + Standard_EXPORT Standard_ShortReal FirstPosition() const; + + //! Returns column index of the corner index in the current line + Standard_EXPORT Standard_Integer LinePositionIndex (const Standard_Integer theIndex) const; + + //! Returns row index of the corner index among text lines + Standard_EXPORT Standard_Integer LineIndex (const Standard_Integer theIndex) const; //! Returns tab size. inline Standard_Integer TabSize() const @@ -66,6 +183,21 @@ public: return myTabSize; } + //!< Returns horizontal alignment style + Graphic3d_HorizontalTextAlignment HorizontalTextAlignment() const { return myAlignX; } + + //!< Returns vertical alignment style + Graphic3d_VerticalTextAlignment VerticalTextAlignment() const { return myAlignY; } + + //!< Sets text wrapping width, zero means that the text is not bounded by width + void SetWrapping (const Standard_ShortReal theWidth) { myWrappingWidth = theWidth; } + + //!< Returns text maximum width, zero means that the text is not bounded by width + Standard_Boolean HasWrapping() const { return myWrappingWidth > 0; } + + //!< Returns text maximum width, zero means that the text is not bounded by width + Standard_ShortReal Wrapping() const { return myWrappingWidth; } + //! @return width of formatted text. inline Standard_ShortReal ResultWidth() const { @@ -78,6 +210,9 @@ public: return myLineSpacing * Standard_ShortReal(myLinesNb); } + //!< @return maximum width of the text symbol + inline Standard_ShortReal MaximumSymbolWidth() const { return myMaxSymbolWidth; } + //! @param bounding box. inline void BndBox (Font_Rect& theBndBox) const { @@ -98,16 +233,40 @@ public: theBndBox.Bottom = theBndBox.Top - myLineSpacing * Standard_ShortReal(myLinesNb); } + //!< Returns internal container of the top left corners of a formatted rectangles. + const NCollection_Vector < NCollection_Vec2 >& Corners() const { return myCorners; } + + const NCollection_Vector& NewLines() const { return myNewLines; } + + //!< Returns true if the symbol is CR, BEL, FF, NP, BS or VT + static inline Standard_Boolean IsCommandSymbol (const Standard_Utf32Char& theSymbol) + { + if (theSymbol == '\x0D' // CR (carriage return) + || theSymbol == '\a' // BEL (alarm) + || theSymbol == '\f' // FF (form feed) NP (new page) + || theSymbol == '\b' // BS (backspace) + || theSymbol == '\v') // VT (vertical tab) + return Standard_True; + + return Standard_False; + } + + DEFINE_STANDARD_RTTIEXT (Font_TextFormatter, Standard_Transient) + protected: //! @name class auxiliary methods //! Move glyphs on the current line to correct position. - Standard_EXPORT void newLine (const Standard_Integer theLastRect); + Standard_EXPORT void newLine (const Standard_Integer theLastRect, + const Standard_ShortReal theMaxLineWidth); protected: //! @name configuration Graphic3d_HorizontalTextAlignment myAlignX; //!< horizontal alignment style Graphic3d_VerticalTextAlignment myAlignY; //!< vertical alignment style Standard_Integer myTabSize; //!< horizontal tabulation width (number of space symbols) + Standard_ShortReal myWrappingWidth; //!< text is wrapped by the width if it is defined (more 0) + Standard_ShortReal myLastSymbolWidth; //!< width of the last symbol + Standard_ShortReal myMaxSymbolWidth; //!< maximum symbol width of the formatter string protected: //! @name input data @@ -116,18 +275,16 @@ protected: //! @name input data myPen; //!< current pen position NCollection_Vector < NCollection_Vec2 > myCorners; //!< The top left corners of a formatted rectangles. - Standard_Integer myRectsNb; //!< rectangles number NCollection_Vector myNewLines; //!< position at LF Standard_ShortReal myLineSpacing; //!< line spacing (computed as maximum of all fonts involved in text formatting) - Standard_ShortReal myAscender; //!< + Standard_ShortReal myAscender; //!< line spacing for the first line bool myIsFormatted; //!< formatting state protected: //! @name temporary variables for formatting routines Standard_Integer myLinesNb; //!< overall (new)lines number (including splitting by width limit) Standard_Integer myRectLineStart; //!< id of first rectangle on the current line - Standard_Integer myRectWordStart; //!< id of first rectangle in the current word Standard_Integer myNewLineNb; Standard_ShortReal myPenCurrLine; //!< current baseline position @@ -135,6 +292,8 @@ protected: //! @name temporary variables for formatting routines Standard_ShortReal myBndWidth; NCollection_Vec2 myMoveVec; //!< local variable + + friend Iterator; }; #endif // Font_TextFormatter_Header diff --git a/src/Graphic3d/Graphic3d_Text.hxx b/src/Graphic3d/Graphic3d_Text.hxx index c1fd70b9ed..671442f0ff 100644 --- a/src/Graphic3d/Graphic3d_Text.hxx +++ b/src/Graphic3d/Graphic3d_Text.hxx @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -31,6 +32,7 @@ //! - text formatter. Formatter contains text, height and alignment parameter. //! //! This class also has parameters of the text height and H/V alignments. +//! More compilated formatting is available using Font_TextFormatter. class Graphic3d_Text : public Standard_Transient { DEFINE_STANDARD_RTTIEXT(Graphic3d_Text, Standard_Transient) @@ -55,6 +57,12 @@ public: //! Sets text value. void SetText (Standard_CString theText) { myText = theText; } + //! @return text formatter + const Handle(Font_TextFormatter)& TextFormatter() const { return myFormatter; } + + //! @return default formatter of text withing this context + void SetTextFormatter (const Handle(Font_TextFormatter)& theFormatter) { myFormatter = theFormatter; } + //! The 3D point of attachment is projected. //! If the orientation is defined, the text is written in the plane of projection. const gp_Pnt& Position() const { return myOrientation.Location(); } @@ -99,6 +107,8 @@ public: void SetVerticalAlignment (const Graphic3d_VerticalTextAlignment theJustification) { myVAlign = theJustification; } protected: + Handle(Font_TextFormatter) myFormatter; //!< text formatter + NCollection_String myText; //!< text value gp_Ax2 myOrientation; //!< Text orientation in 3D space. diff --git a/src/OpenGl/OpenGl_Context.cxx b/src/OpenGl/OpenGl_Context.cxx index 1623c3a91d..e5ed533d8e 100644 --- a/src/OpenGl/OpenGl_Context.cxx +++ b/src/OpenGl/OpenGl_Context.cxx @@ -36,6 +36,8 @@ #include #include +#include + #include #include @@ -286,6 +288,8 @@ OpenGl_Context::OpenGl_Context (const Handle(OpenGl_Caps)& theCaps) memset (myFuncs.operator->(), 0, sizeof(OpenGl_GlFunctions)); myShaderManager = new OpenGl_ShaderManager (this); + + myDefaultFormatter = new Font_TextFormatter(); } // ======================================================================= diff --git a/src/OpenGl/OpenGl_Context.hxx b/src/OpenGl/OpenGl_Context.hxx index 9808544835..b8eb1f02ec 100644 --- a/src/OpenGl/OpenGl_Context.hxx +++ b/src/OpenGl/OpenGl_Context.hxx @@ -136,6 +136,7 @@ template struct OpenGl_TmplCore45; typedef OpenGl_TmplCore45 OpenGl_GlCore45Back; typedef OpenGl_TmplCore45 OpenGl_GlCore45; +class Font_TextFormatter; class Graphic3d_PresentationAttributes; class OpenGl_Aspects; class OpenGl_FrameBuffer; @@ -490,6 +491,9 @@ public: //! @return tool for management of shader programs within this context. inline const Handle(OpenGl_ShaderManager)& ShaderManager() const { return myShaderManager; } + //! @return default formatter of text withing this context + inline const Handle(Font_TextFormatter)& DefaultTextFormatter() const { return myDefaultFormatter; } + public: //! Either GL_CLAMP_TO_EDGE (1.2+) or GL_CLAMP (1.1). @@ -1123,6 +1127,7 @@ private: // context info Graphic3d_TextureUnit myPBRSpecIBLMapTexUnit; //!< samplerCube occSpecIBLMap, texture unit where specular IBL map is expected to be binded (0 if PBR is not supported) Handle(OpenGl_ShaderManager) myShaderManager; //! support object for managing shader programs + Handle(Font_TextFormatter) myDefaultFormatter;//!< default text formatter, an alternative to text params private: //! @name fields tracking current state diff --git a/src/OpenGl/OpenGl_Text.cxx b/src/OpenGl/OpenGl_Text.cxx index d2c0cc6eec..45a3d90e6c 100644 --- a/src/OpenGl/OpenGl_Text.cxx +++ b/src/OpenGl/OpenGl_Text.cxx @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -607,7 +608,7 @@ void OpenGl_Text::render (const Handle(OpenGl_Context)& theCtx, const OpenGl_Vec4& theColorSubs, unsigned int theResolution) const { - if (myText->Text().IsEmpty()) + if (myText->Text().IsEmpty() && myText->TextFormatter().IsNull()) { return; } @@ -633,13 +634,16 @@ void OpenGl_Text::render (const Handle(OpenGl_Context)& theCtx, if (myTextures.IsEmpty()) { - Font_TextFormatter aFormatter; - - aFormatter.SetupAlignment (myText->HorizontalAlignment(), myText->VerticalAlignment()); - aFormatter.Reset(); + Handle(Font_TextFormatter) aFormatter = myText->TextFormatter(); + if (aFormatter.IsNull()) + { + aFormatter = theCtx->DefaultTextFormatter(); + aFormatter->SetupAlignment (myText->HorizontalAlignment(), myText->VerticalAlignment()); + aFormatter->Reset(); - aFormatter.Append (myText->Text(), *myFont->FTFont()); - aFormatter.Format(); + aFormatter->Append (myText->Text(), *myFont->FTFont()); + aFormatter->Format(); + } OpenGl_TextBuilder aBuilder; aBuilder.Perform (aFormatter, @@ -649,7 +653,7 @@ void OpenGl_Text::render (const Handle(OpenGl_Context)& theCtx, myVertsVbo, myTCrdsVbo); - aFormatter.BndBox (myBndBox); + aFormatter->BndBox (myBndBox); if (!myBndVertsVbo.IsNull()) { myBndVertsVbo->Release (theCtx.get()); diff --git a/src/OpenGl/OpenGl_TextBuilder.cxx b/src/OpenGl/OpenGl_TextBuilder.cxx index 9505f7443d..7c97ed0451 100644 --- a/src/OpenGl/OpenGl_TextBuilder.cxx +++ b/src/OpenGl/OpenGl_TextBuilder.cxx @@ -17,6 +17,7 @@ #include #include +#include namespace { @@ -44,7 +45,7 @@ OpenGl_TextBuilder::OpenGl_TextBuilder() // function : createGlyphs // purpose : // ======================================================================= -void OpenGl_TextBuilder::createGlyphs (const Font_TextFormatter& theFormatter, +void OpenGl_TextBuilder::createGlyphs (const Handle(Font_TextFormatter)& theFormatter, const Handle(OpenGl_Context)& theCtx, OpenGl_Font& theFont, NCollection_Vector& theTextures, @@ -58,51 +59,16 @@ void OpenGl_TextBuilder::createGlyphs (const Font_TextFormatter& theTCrdsPerTexture.Clear(); OpenGl_Font::Tile aTile = {Font_Rect(), Font_Rect(), 0u}; - OpenGl_Vec2 aPen (0.0f, 0.0f); - Standard_Integer aRectsNb = 0; - Standard_Integer aSymbolsCounter = 0; - - for (NCollection_Utf8Iter anIter = theFormatter.String().Iterator(); *anIter != 0;) + for (Font_TextFormatter::Iterator aFormatterIt (*theFormatter, Font_TextFormatter::IterationFilter_ExcludeInvisible); + aFormatterIt .More(); aFormatterIt .Next()) { - const Standard_Utf32Char aCharThis = *anIter; - const Standard_Utf32Char aCharNext = *++anIter; - - if (aCharThis == '\x0D' // CR (carriage return) - || aCharThis == '\a' // BEL (alarm) - || aCharThis == '\f' // FF (form feed) NP (new page) - || aCharThis == '\b' // BS (backspace) - || aCharThis == '\v') // VT (vertical tab) - { - continue; // skip unsupported carriage control codes - } - else if (aCharThis == '\x0A') // LF (line feed, new line) - { - aSymbolsCounter = 0; - continue; // will be processed on second pass - } - else if (aCharThis == ' ') - { - ++aSymbolsCounter; - aPen.x() += theFont.FTFont()->AdvanceX (' ', aCharNext); - continue; - } - else if (aCharThis == '\t') - { - const Standard_Integer aSpacesNum = (theFormatter.TabSize() - (aSymbolsCounter - 1) % theFormatter.TabSize()); - aPen.x() += theFont.FTFont()->AdvanceX (' ', aCharNext) * Standard_ShortReal(aSpacesNum); - aSymbolsCounter += aSpacesNum; - continue; - } + theFont.RenderGlyph (theCtx, aFormatterIt.Symbol(), aTile); - ++aSymbolsCounter; - - theFont.RenderGlyph (theCtx, aCharThis, aTile); - - const OpenGl_Vec2& aTopLeft = theFormatter.TopLeft (aRectsNb); - aTile.px.Right += aTopLeft.x(); - aTile.px.Left += aTopLeft.x(); - aTile.px.Bottom += aTopLeft.y(); - aTile.px.Top += aTopLeft.y(); + const OpenGl_Vec2& aBottomLeft = theFormatter->BottomLeft (aFormatterIt.SymbolPosition()); + aTile.px.Right += aBottomLeft.x(); + aTile.px.Left += aBottomLeft.x(); + aTile.px.Bottom += aBottomLeft.y(); + aTile.px.Top += aBottomLeft.y(); const Font_Rect& aRectUV = aTile.uv; const GLuint aTexture = aTile.texture; @@ -139,8 +105,6 @@ void OpenGl_TextBuilder::createGlyphs (const Font_TextFormatter& aTCrds.Append (aRectUV.BottomRight (aVec)); aTCrds.Append (aRectUV.TopRight (aVec)); aTCrds.Append (aRectUV.BottomLeft (aVec)); - - ++aRectsNb; } } @@ -148,7 +112,7 @@ void OpenGl_TextBuilder::createGlyphs (const Font_TextFormatter& // function : CreateTextures // purpose : // ======================================================================= -void OpenGl_TextBuilder::Perform (const Font_TextFormatter& theFormatter, +void OpenGl_TextBuilder::Perform (const Handle(Font_TextFormatter)& theFormatter, const Handle(OpenGl_Context)& theCtx, OpenGl_Font& theFont, NCollection_Vector& theTextures, diff --git a/src/OpenGl/OpenGl_TextBuilder.hxx b/src/OpenGl/OpenGl_TextBuilder.hxx index 6ed37b819e..2fcd775e33 100644 --- a/src/OpenGl/OpenGl_TextBuilder.hxx +++ b/src/OpenGl/OpenGl_TextBuilder.hxx @@ -16,8 +16,6 @@ #ifndef OpenGl_TextBuilder_Header #define OpenGl_TextBuilder_Header -#include - #include #include #include @@ -27,6 +25,7 @@ #include #include +class Font_TextFormatter; //! This class generates primitive array required for rendering textured text using OpenGl_Font instance. class OpenGl_TextBuilder @@ -37,7 +36,7 @@ public: Standard_EXPORT OpenGl_TextBuilder(); //! Creates texture quads for the given text. - Standard_EXPORT void Perform (const Font_TextFormatter& theFormatter, + Standard_EXPORT void Perform (const Handle(Font_TextFormatter)& theFormatter, const Handle(OpenGl_Context)& theContext, OpenGl_Font& theFont, NCollection_Vector& theTextures, @@ -46,7 +45,7 @@ public: protected: //! @name class auxillary methods - Standard_EXPORT void createGlyphs (const Font_TextFormatter& theFormatter, + Standard_EXPORT void createGlyphs (const Handle(Font_TextFormatter)& theFormatter, const Handle(OpenGl_Context)& theCtx, OpenGl_Font& theFont, NCollection_Vector& theTextures,