diff --git a/src/AIS/AIS_TextLabel.cxx b/src/AIS/AIS_TextLabel.cxx index f387fafe56..8344e85da0 100644 --- a/src/AIS/AIS_TextLabel.cxx +++ b/src/AIS/AIS_TextLabel.cxx @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -308,7 +309,9 @@ void AIS_TextLabel::Compute (const Handle(PrsMgr_PresentationManager3d)& /*thePr { aHasOwnAnchor = Standard_False; // always not using own anchor if flipping } - Prs3d_Text::Draw (thePrs->CurrentGroup(), anAsp, myText, anOrientation, aHasOwnAnchor); + Handle(Graphic3d_Text) aText = + Prs3d_Text::Draw (thePrs->CurrentGroup(), anAsp, myText, anOrientation, aHasOwnAnchor); + aText->SetTextFormatter (myFormatter); if (myHasFlipping && isInit) { thePrs->CurrentGroup()->SetFlippingOptions (Standard_False, gp_Ax2()); @@ -316,7 +319,9 @@ void AIS_TextLabel::Compute (const Handle(PrsMgr_PresentationManager3d)& /*thePr } else { - Prs3d_Text::Draw (thePrs->CurrentGroup(), anAsp, myText, aPosition); + Handle(Graphic3d_Text) aText = + Prs3d_Text::Draw (thePrs->CurrentGroup(), anAsp, myText, aPosition); + aText->SetTextFormatter (myFormatter); } if (isInit) diff --git a/src/AIS/AIS_TextLabel.hxx b/src/AIS/AIS_TextLabel.hxx index b3ae257d4a..90b1c5cc01 100644 --- a/src/AIS/AIS_TextLabel.hxx +++ b/src/AIS/AIS_TextLabel.hxx @@ -24,6 +24,8 @@ #include #include +class Font_TextFormatter; + //! Presentation of the text. class AIS_TextLabel : public AIS_InteractiveObject { @@ -121,6 +123,12 @@ public: //! and the colour of backgroubd for the TODT_DEKALE TextDisplayType. Standard_EXPORT void SetColorSubTitle (const Quantity_Color& theColor); + //! Returns text presentation formatter; NULL by default, which means standard text formatter will be used. + const Handle(Font_TextFormatter)& TextFormatter() const { return myFormatter; } + + //! Setup text formatter for presentation. It's empty by default. + void SetTextFormatter (const Handle(Font_TextFormatter)& theFormatter) { myFormatter = theFormatter; } + protected: //! Compute @@ -144,6 +152,8 @@ protected: protected: + Handle(Font_TextFormatter) myFormatter; + TCollection_ExtendedString myText; gp_Ax2 myOrientation3D; Standard_Boolean myHasOrientation3D; diff --git a/src/Font/Font_TextFormatter.cxx b/src/Font/Font_TextFormatter.cxx index 520b3c9270..c32f253f87 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,13 @@ 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;) + for (Font_TextFormatter::Iterator aFormatterIt (*this); aFormatterIt.More(); aFormatterIt.Next()) { - const Standard_Utf32Char aCharThis = *anIter; - const Standard_Utf32Char aCharNext = *++anIter; + const Standard_Utf32Char aCharThis = aFormatterIt.Symbol(); + const Standard_Utf32Char aCharNext = aFormatterIt.SymbolNext(); - 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) + Standard_ShortReal anAdvanceX = 0; + if (IsCommandSymbol (aCharThis)) { continue; // skip unsupported carriage control codes } @@ -136,79 +140,69 @@ 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; + GlyphBoundingBox (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; + case Graphic3d_HTA_LEFT: myMoveVec.x() = -aXMin; break; - } - case Graphic3d_HTA_RIGHT: - { - myMoveVec.x() = (myNewLineNb < myNewLines.Length()) - ? -myNewLines.Value (myNewLineNb) - : -myPen.x(); + case Graphic3d_HTA_RIGHT: myMoveVec.x() = -aXMin + (theMaxLineWidth - (aXMax - aXMin)) - theMaxLineWidth; 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); + 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 +211,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 +226,61 @@ 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) - { - 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) - { - continue; // skip unsupported carriage control codes - } - 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); + Standard_ShortReal aMaxLineWidth = Wrapping(); + if (HasWrapping()) + { + // it is not possible to wrap less than symbol width + aMaxLineWidth = Max (aMaxLineWidth, MaximumSymbolWidth()); + } + else + { + if (myNewLines.IsEmpty()) // If only one line + { + aMaxLineWidth = myPen.x(); + } + else + { + for (int aLineIt = 0; aLineIt < myNewLines.Size(); aLineIt++) + { + aMaxLineWidth = Max (aMaxLineWidth, LineWidth (aLineIt)); + } + aMaxLineWidth = Max (aMaxLineWidth, LineWidth (myNewLines.Size())); // processing the last line also + } + } + + 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; + Standard_Integer aFirstCornerId = myRectLineStart; + + Font_Rect aBndBox; + GlyphBoundingBox (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); + } } - - ++aRectIter; - } - - // If only one line - if (aMaxLineWidth < 0.0f) - { - aMaxLineWidth = myPen.x(); - } - else // Consider last line - { - aMaxLineWidth = Max (aMaxLineWidth, myPen.x() - myNewLines.Value (myNewLineNb - 1)); } myBndWidth = aMaxLineWidth; // move last line - newLine (myRectsNb - 1); + newLine (myCorners.Length() - 1, aMaxLineWidth); // apply vertical alignment style if (myAlignY == Graphic3d_VTA_BOTTOM) @@ -302,6 +298,129 @@ void Font_TextFormatter::Format() if (myAlignY != Graphic3d_VTA_TOP) { - moveY (myCorners, myBndTop, 0, myRectsNb - 1); + moveY (myCorners, myBndTop, 0, myCorners.Length() - 1); } } + +// ======================================================================= +// function : GlyphBoundingBox +// purpose : +// ======================================================================= +Standard_Boolean Font_TextFormatter::GlyphBoundingBox (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 (!GlyphBoundingBox (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; + case Graphic3d_HTA_RIGHT: return myBndWidth; + case Graphic3d_HTA_CENTER: return 0.5f * myBndWidth; + } +} + +// ======================================================================= +// 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..1d917f22bb 100755 --- a/src/Font/Font_TextFormatter.hxx +++ b/src/Font/Font_TextFormatter.hxx @@ -25,10 +25,120 @@ class Font_FTFont; -//! This class intended to prepare formatted text. -class Font_TextFormatter +DEFINE_STANDARD_HANDLE(Font_TextFormatter, Standard_Transient) + +//! This class is intended to prepare formatted text by using:
+//! - font to string combination,
+//! - alignment,
+//! - wrapping.
+//! +//! After text formatting, each symbol of formatted text is placed in some position. +//! Further work with the formatter is using an iterator. +//! The iterator gives an access to each symbol inside the initial row. +//! Also it's possible to get only significant/writable symbols of the text.
+//! Formatter gives an access to geometrical position of a symbol by the symbol index in the text.
+//! Example of correspondence of some text symbol to an index in "row_1\n\nrow_2\n":
+//! "row_1\n" - 0-5 indices;
+//! "\n" - 6 index;
+//! "\n" - 7 index;
+//! "row_2\n" - 8-13 indices.
+//! Pay attention that fonts should have the same LineSpacing value for correct formatting.
+//! Example of the formatter using: +//! @code +//! Handle(Font_TextFormatter) aFormatter = new Font_TextFormatter(); +//! aFormatter->Append(text_1, aFont1); +//! aFormatter->Append(text_2, aFont2); +//! // setting of additional properties such as wrapping or alignment +//! aFormatter->Format(); +//! @endcode +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 formatted symbols. + //! It's possible to filter returned symbols to have only significant ones. + class Iterator + { + public: + //! Constructor with initialization. + Iterator (const Font_TextFormatter& theFormatter, + IterationFilter theFilter = IterationFilter_None) + : myFilter (theFilter), myIter (theFormatter.myString.Iterator()), mySymbolChar (0), mySymbolCharNext (0) + { + 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: + //! Finds index of the next symbol + Standard_Integer readNextSymbol (const Standard_Integer theSymbolStartingFrom, + Standard_Utf32Char& theSymbolChar) + { + Standard_Integer aNextSymbol = theSymbolStartingFrom; + 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(); @@ -48,24 +158,68 @@ public: //! Should not be called more than once after initialization! Standard_EXPORT void Format(); - //! Returns specific glyph rectangle. - inline const NCollection_Vec2& TopLeft (const Standard_Integer theIndex) const + Standard_DEPRECATED("BottomLeft should be used instead") + const NCollection_Vec2& TopLeft (const Standard_Integer theIndex) const { - return myCorners.Value (theIndex); + return BottomLeft (theIndex); } + //! Returns specific glyph rectangle. + 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 GlyphBoundingBox (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 { 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 +232,9 @@ public: return myLineSpacing * Standard_ShortReal(myLinesNb); } + //! @return maximum width of the text symbol + Standard_ShortReal MaximumSymbolWidth() const { return myMaxSymbolWidth; } + //! @param bounding box. inline void BndBox (Font_Rect& theBndBox) const { @@ -98,16 +255,41 @@ 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; } + + //! Returns container of each line position at LF in formatted text + 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 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 @@ -115,19 +297,17 @@ protected: //! @name input data NCollection_Vec2 myPen; //!< current pen position NCollection_Vector < NCollection_Vec2 > - myCorners; //!< The top left corners of a formatted rectangles. - Standard_Integer myRectsNb; //!< rectangles number + myCorners; //!< The bottom left corners of a formatted rectangles. 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 diff --git a/src/Graphic3d/Graphic3d_Text.hxx b/src/Graphic3d/Graphic3d_Text.hxx index c1fd70b9ed..76e2dd47b7 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. +//! Custom 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; NULL by default, which means standard text formatter will be used. + const Handle(Font_TextFormatter)& TextFormatter() const { return myFormatter; } + + //! Setup text default formatter for text within 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_Text.cxx b/src/OpenGl/OpenGl_Text.cxx index 6a9b8448cb..09c63975d1 100644 --- a/src/OpenGl/OpenGl_Text.cxx +++ b/src/OpenGl/OpenGl_Text.cxx @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -659,7 +660,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; } @@ -685,13 +686,16 @@ void OpenGl_Text::render (const Handle(OpenGl_Context)& theCtx, if (myTextures.IsEmpty()) { - Font_TextFormatter aFormatter; + Handle(Font_TextFormatter) aFormatter = myText->TextFormatter(); + if (aFormatter.IsNull()) + { + aFormatter = new Font_TextFormatter(); + } + aFormatter->SetupAlignment (myText->HorizontalAlignment(), myText->VerticalAlignment()); + aFormatter->Reset(); - 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, @@ -701,7 +705,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..f22c094aa9 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; + theFont.RenderGlyph (theCtx, aFormatterIt.Symbol(), aTile); - 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; - } - - ++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, diff --git a/src/Prs3d/Prs3d_Text.cxx b/src/Prs3d/Prs3d_Text.cxx index 78dfb3219b..35b9d3531a 100644 --- a/src/Prs3d/Prs3d_Text.cxx +++ b/src/Prs3d/Prs3d_Text.cxx @@ -29,10 +29,10 @@ // function : Draw // purpose : // ======================================================================= -void Prs3d_Text::Draw (const Handle(Graphic3d_Group)& theGroup, - const Handle(Prs3d_TextAspect)& theAspect, - const TCollection_ExtendedString& theText, - const gp_Pnt& theAttachmentPoint) +Handle(Graphic3d_Text) Prs3d_Text::Draw (const Handle(Graphic3d_Group)& theGroup, + const Handle(Prs3d_TextAspect)& theAspect, + const TCollection_ExtendedString& theText, + const gp_Pnt& theAttachmentPoint) { theGroup->SetPrimitivesAspect (theAspect->Aspect()); @@ -42,17 +42,18 @@ void Prs3d_Text::Draw (const Handle(Graphic3d_Group)& theGroup, aText->SetHorizontalAlignment (theAspect->HorizontalJustification()); aText->SetVerticalAlignment (theAspect->VerticalJustification()); theGroup->AddText (aText); + return aText; } // ======================================================================= // function : Draw // purpose : // ======================================================================= -void Prs3d_Text::Draw (const Handle(Graphic3d_Group)& theGroup, - const Handle(Prs3d_TextAspect)& theAspect, - const TCollection_ExtendedString& theText, - const gp_Ax2& theOrientation, - const Standard_Boolean theHasOwnAnchor) +Handle(Graphic3d_Text) Prs3d_Text::Draw (const Handle(Graphic3d_Group)& theGroup, + const Handle(Prs3d_TextAspect)& theAspect, + const TCollection_ExtendedString& theText, + const gp_Ax2& theOrientation, + const Standard_Boolean theHasOwnAnchor) { theGroup->SetPrimitivesAspect (theAspect->Aspect()); @@ -63,4 +64,5 @@ void Prs3d_Text::Draw (const Handle(Graphic3d_Group)& theGroup, aText->SetHorizontalAlignment (theAspect->HorizontalJustification()); aText->SetVerticalAlignment (theAspect->VerticalJustification()); theGroup->AddText (aText); + return aText; } diff --git a/src/Prs3d/Prs3d_Text.hxx b/src/Prs3d/Prs3d_Text.hxx index 6a26365d3c..c924fec392 100644 --- a/src/Prs3d/Prs3d_Text.hxx +++ b/src/Prs3d/Prs3d_Text.hxx @@ -37,10 +37,11 @@ public: //! @param theAspect presentation attributes //! @param theText text to draw //! @param theAttachmentPoint attachment point - Standard_EXPORT static void Draw (const Handle(Graphic3d_Group)& theGroup, - const Handle(Prs3d_TextAspect)& theAspect, - const TCollection_ExtendedString& theText, - const gp_Pnt& theAttachmentPoint); + //! @return text to draw + Standard_EXPORT static Handle(Graphic3d_Text) Draw (const Handle(Graphic3d_Group)& theGroup, + const Handle(Prs3d_TextAspect)& theAspect, + const TCollection_ExtendedString& theText, + const gp_Pnt& theAttachmentPoint); //! Draws the text label. //! @param theGroup group to add primitives @@ -48,11 +49,12 @@ public: //! @param theText text to draw //! @param theOrientation location and orientation specified in the model 3D space //! @param theHasOwnAnchor - Standard_EXPORT static void Draw (const Handle(Graphic3d_Group)& theGroup, - const Handle(Prs3d_TextAspect)& theAspect, - const TCollection_ExtendedString& theText, - const gp_Ax2& theOrientation, - const Standard_Boolean theHasOwnAnchor = Standard_True); + //! @return text to draw + Standard_EXPORT static Handle(Graphic3d_Text) Draw (const Handle(Graphic3d_Group)& theGroup, + const Handle(Prs3d_TextAspect)& theAspect, + const TCollection_ExtendedString& theText, + const gp_Ax2& theOrientation, + const Standard_Boolean theHasOwnAnchor = Standard_True); public: diff --git a/src/StdPrs/StdPrs_BRepTextBuilder.cxx b/src/StdPrs/StdPrs_BRepTextBuilder.cxx index 4ba004b19c..2554414a62 100644 --- a/src/StdPrs/StdPrs_BRepTextBuilder.cxx +++ b/src/StdPrs/StdPrs_BRepTextBuilder.cxx @@ -15,12 +15,14 @@ #include +#include + // ======================================================================= // Function : Perfrom // Purpose : // ======================================================================= TopoDS_Shape StdPrs_BRepTextBuilder::Perform (StdPrs_BRepFont& theFont, - const Font_TextFormatter& theFormatter, + const Handle(Font_TextFormatter)& theFormatter, const gp_Ax3& thePenLoc) { gp_Trsf aTrsf; @@ -31,34 +33,20 @@ TopoDS_Shape StdPrs_BRepTextBuilder::Perform (StdPrs_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 StdPrs_BRepTextBuilder::Perform (StdPrs_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/StdPrs/StdPrs_BRepTextBuilder.hxx b/src/StdPrs/StdPrs_BRepTextBuilder.hxx index 4e6b60a3b3..5c003a1fce 100644 --- a/src/StdPrs/StdPrs_BRepTextBuilder.hxx +++ b/src/StdPrs/StdPrs_BRepTextBuilder.hxx @@ -25,13 +25,12 @@ class StdPrs_BRepTextBuilder { public: //! Render text as BRep shape. - //! @param theString text in UTF-8 encoding + //! @param theFormatter formatter which defines aligned text //! @param thePenLoc start position and orientation on the baseline - //! @param theFormatter formatter which defines alignment for the text //! @return result shape with pen transformation applied as shape location - Standard_EXPORT TopoDS_Shape Perform (StdPrs_BRepFont& theFont, - const Font_TextFormatter& theFormatter, - const gp_Ax3& thePenLoc = gp_Ax3()); + Standard_EXPORT TopoDS_Shape Perform (StdPrs_BRepFont& theFont, + const Handle(Font_TextFormatter)& theFormatter, + const gp_Ax3& thePenLoc = gp_Ax3()); //! Render text as BRep shape. //! @param theString text in UTF-8 encoding //! @param thePenLoc start position and orientation on the baseline diff --git a/src/ViewerTest/ViewerTest_ObjectCommands.cxx b/src/ViewerTest/ViewerTest_ObjectCommands.cxx index b8dec842a5..2fc6857539 100644 --- a/src/ViewerTest/ViewerTest_ObjectCommands.cxx +++ b/src/ViewerTest/ViewerTest_ObjectCommands.cxx @@ -2390,6 +2390,8 @@ static int VDrawText (Draw_Interpretor& theDI, gp_Dir aDirection; gp_Pnt aPos; + + Handle(Font_TextFormatter) aTextFormatter; for (; anArgIt < theArgsNb; ++anArgIt) { TCollection_AsciiString aParam (theArgVec[anArgIt]); @@ -2517,6 +2519,20 @@ static int VDrawText (Draw_Interpretor& theDI, aTextPrs->SetHeight (Draw::Atof(theArgVec[anArgIt])); } + else if (aParam == "-wrapping") + { + if (++anArgIt >= theArgsNb) + { + Message::SendFail() << "Syntax error: wrong number of values for parameter '" << aParam << "'"; + return 1; + } + + if (aTextFormatter.IsNull()) + { + aTextFormatter = new Font_TextFormatter(); + } + aTextFormatter->SetWrapping ((Standard_ShortReal)Draw::Atof(theArgVec[anArgIt])); + } else if (aParam == "-aspect") { if (++anArgIt >= theArgsNb) @@ -2662,6 +2678,8 @@ static int VDrawText (Draw_Interpretor& theDI, } } + aTextPrs->SetTextFormatter (aTextFormatter); + if (aHasPlane) { aTextPrs->SetOrientation3D (gp_Ax2 (aPos, aNormal, aDirection)); @@ -6628,6 +6646,7 @@ void ViewerTest::ObjectCommands(Draw_Interpretor& theCommands) "\n\t\t: [-angle angle=0]" "\n\t\t: [-zoom {0|1}=0]" "\n\t\t: [-height height=16]" + "\n\t\t: [-wrapping width=40]" "\n\t\t: [-aspect {regular|bold|italic|boldItalic}=regular]" "\n\t\t: [-font font=Times]" "\n\t\t: [-2d]" diff --git a/tests/3rdparty/text3d/text_wrapped b/tests/3rdparty/text3d/text_wrapped new file mode 100644 index 0000000000..3063a2e1be --- /dev/null +++ b/tests/3rdparty/text3d/text_wrapped @@ -0,0 +1,27 @@ +puts "===========" +puts "0030537: Visualization - wrapping text in font text formatter" +puts "" +puts "===========" + +pload MODELING VISUALIZATION +vinit View1 +vclear +vaxo + +box b1 10 0 360 10 180 40 +vdisplay b1 +vdrawtext t1 "Top text on plane yOz\n(not wrapped)" -pos 10 5 400 -color green -plane 1 0 0 0 1 0 -valign top -font SansFont -zoom 1 + +box b2 10 0 240 10 130 60 +vdisplay b2 +vdrawtext t2 "Top text on plane yOz\n(wrapping=120)" -pos 10 5 300 -color green -wrapping 120 -plane 1 0 0 0 1 0 -valign top -font SansFont -zoom 1 + +box b3 10 0 60 10 60 150 +vdisplay b3 +vdrawtext t3 "Top text on plane yOz\n(wrapping=50)" -pos 10 5 200 -color green -wrapping 50 -plane 1 0 0 0 1 0 -valign top -font SansFont -zoom 1 + +vright +vfit +vzoom 0.9 + +vdump $imagedir/${casename}.png