0023457: Slow text rendering
[occt.git] / src / OpenGl / OpenGl_Text.cxx
index 797f459..27b1871 100644 (file)
@@ -1,6 +1,6 @@
 // Created on: 2011-07-13
 // Created by: Sergey ZERCHANINOV
-// Copyright (c) 2011-2012 OPEN CASCADE SAS
+// Copyright (c) 2011-2013 OPEN CASCADE SAS
 //
 // The content of this file is subject to the Open CASCADE Technology Public
 // License Version 6.5 (the "License"). You may not use the content of this file
 
 #include <OpenGl_GlCore11.hxx>
 #include <OpenGl_Text.hxx>
+
 #include <OpenGl_AspectText.hxx>
-#include <OpenGl_Structure.hxx>
+#include <OpenGl_GraphicDriver.hxx>
 #include <OpenGl_Workspace.hxx>
 
-/*----------------------------------------------------------------------*/
+#include <Font_FontMgr.hxx>
+#include <TCollection_HAsciiString.hxx>
+
+#ifdef HAVE_CONFIG_H
+  #include <config.h>
+#endif
 
-OpenGl_Text::OpenGl_Text (const TCollection_ExtendedString& AText,
-                        const Graphic3d_Vertex& APoint,
-                        const Standard_Real AHeight,
-                        const Graphic3d_HorizontalTextAlignment AHta,
-                        const Graphic3d_VerticalTextAlignment AVta)
-: myString(NULL)
+#ifdef HAVE_GL2PS
+  #include <gl2ps.h>
+#endif
+
+namespace
 {
-  const Techar *str = (const Techar *) AText.ToExtString();
+  static const GLdouble THE_IDENTITY_MATRIX[4][4] =
+  {
+    {1.,0.,0.,0.},
+    {0.,1.,0.,0.},
+    {0.,0.,1.,0.},
+    {0.,0.,0.,1.}
+  };
+
+#ifdef HAVE_GL2PS
+  static char const* TheFamily[] = {"Helvetica", "Courier", "Times"};
+  static char const* TheItalic[] = {"Oblique",   "Oblique", "Italic"};
+  static char const* TheBase[]   = {"", "", "-Roman"};
+
+  //! Convert font name used for rendering to some "good" font names
+  //! that produce good vector text.
+  static void getGL2PSFontName (const char* theSrcFont,
+                                char*       thePsFont)
+  {
+    if (strstr (theSrcFont, "Symbol"))
+    {
+      sprintf (thePsFont, "%s", "Symbol");
+      return;
+    }
+    else if (strstr (theSrcFont, "ZapfDingbats"))
+    {
+      sprintf (thePsFont, "%s", "WingDings");
+      return;
+    }
 
-  //szv: instead of strlen + 1
-  int i = 0; while (str[i++]);
+    int  aFontId  = 0;
+    bool isBold   = false;
+    bool isItalic = false;
+    if (strstr (theSrcFont, "Courier"))
+    {
+      aFontId = 1;
+    }
+    else if (strstr (theSrcFont, "Times"))
+    {
+      aFontId = 2;
+    }
 
-  wchar_t *wstr = new wchar_t[i];
+    if (strstr (theSrcFont, "Bold"))
+    {
+      isBold = true;
+    }
+    if (strstr (theSrcFont, "Italic")
+     || strstr (theSrcFont, "Oblique"))
+    {
+      isItalic = true;
+    }
 
-  //szv: instead of memcpy
-  i = 0; while (wstr[i++] = (wchar_t)(*str++));
-  if (myString) delete[] myString;
-  myString = wstr;
+    if (isBold)
+    {
+      sprintf (thePsFont, "%s-%s", TheFamily[aFontId], "Bold");
+      if (isItalic)
+      {
+        sprintf (thePsFont, "%s%s", thePsFont, TheItalic[aFontId]);
+      }
+    }
+    else if (isItalic)
+    {
+      sprintf (thePsFont, "%s-%s", TheFamily[aFontId], TheItalic[aFontId]);
+    }
+    else
+    {
+      sprintf (thePsFont, "%s%s", TheFamily[aFontId], TheBase[aFontId]);
+    }
+  }
 
-  myAttachPnt.xyz[0] = APoint.X();
-  myAttachPnt.xyz[1] = APoint.Y();
-  myAttachPnt.xyz[2] = APoint.Z();
+  static void exportText (const NCollection_String& theText,
+                          const Standard_Boolean    theIs2d,
+                          const OpenGl_AspectText&  theAspect,
+                          const Standard_Integer    theHeight)
+  {
 
-  myParam.Height = int (AHeight);
+    char aPsFont[64];
+    getGL2PSFontName (theAspect.FontName().ToCString(), aPsFont);
 
-  myParam.HAlign = AHta;
-  myParam.VAlign = AVta;
+    if (theIs2d)
+    {
+      glRasterPos2f (0.0f, 0.0f);
+    }
+    else
+    {
+      glRasterPos3f (0.0f, 0.0f, 0.0f);
+    }
+
+    GLubyte aZero = 0;
+    glBitmap (1, 1, 0, 0, 0, 0, &aZero);
+
+    // Standard GL2PS's alignment isn't used, because it doesn't work correctly
+    // for all formats, therefore alignment is calculated manually relative
+    // to the bottom-left corner, which corresponds to the GL2PS_TEXT_BL value
+    gl2psTextOpt (theText.ToCString(), aPsFont, theHeight, GL2PS_TEXT_BL, theAspect.Angle());
+  }
+#endif
+
+};
+
+// =======================================================================
+// function : OpenGl_Text
+// purpose  :
+// =======================================================================
+OpenGl_Text::OpenGl_Text()
+: myWinX (0.0f),
+  myWinY (0.0f),
+  myWinZ (0.0f),
+  myScaleHeight (1.0f),
+  myPoint  (0.0f, 0.0f, 0.0f),
+  myIs2d   (false)
+{
+  myParams.Height = 10;
+  myParams.HAlign = Graphic3d_HTA_LEFT;
+  myParams.VAlign = Graphic3d_VTA_BOTTOM;
 }
 
-/*----------------------------------------------------------------------*/
+// =======================================================================
+// function : OpenGl_Text
+// purpose  :
+// =======================================================================
+OpenGl_Text::OpenGl_Text (const TCollection_ExtendedString& theText,
+                          const OpenGl_Vec3&                thePoint,
+                          const OpenGl_TextParam&           theParams)
+: myWinX (0.0f),
+  myWinY (0.0f),
+  myWinZ (0.0f),
+  myScaleHeight  (1.0f),
+  myExportHeight (1.0f),
+  myParams (theParams),
+  myString ((Standard_Utf16Char* )theText.ToExtString()),
+  myPoint  (thePoint),
+  myIs2d   (false)
+{
+  //
+}
 
-OpenGl_Text::~OpenGl_Text ()
+// =======================================================================
+// function : SetPosition
+// purpose  :
+// =======================================================================
+void OpenGl_Text::SetPosition (const OpenGl_Vec3& thePoint)
 {
-  if (myString)
-    delete[] myString;
+  myPoint = thePoint;
 }
 
-/*----------------------------------------------------------------------*/
+// =======================================================================
+// function : SetFontSize
+// purpose  :
+// =======================================================================
+void OpenGl_Text::SetFontSize (const Handle(OpenGl_Context)& theCtx,
+                               const Standard_Integer        theFontSize)
+{
+  if (myParams.Height != theFontSize)
+  {
+    Release (theCtx);
+  }
+  myParams.Height = theFontSize;
+}
+
+// =======================================================================
+// function : Init
+// purpose  :
+// =======================================================================
+void OpenGl_Text::Init (const Handle(OpenGl_Context)& theCtx,
+                        const Standard_Utf8Char*      theText,
+                        const OpenGl_Vec3&            thePoint)
+{
+  releaseVbos (theCtx);
+  myIs2d   = false;
+  myPoint  = thePoint;
+  myString.FromUnicode (theText);
+}
+
+// =======================================================================
+// function : Init
+// purpose  :
+// =======================================================================
+void OpenGl_Text::Init (const Handle(OpenGl_Context)& theCtx,
+                        const Standard_Utf8Char*      theText,
+                        const OpenGl_Vec3&            thePoint,
+                        const OpenGl_TextParam&       theParams)
+{
+  if (myParams.Height != theParams.Height)
+  {
+    Release (theCtx);
+  }
+  else
+  {
+    releaseVbos (theCtx);
+  }
+  myIs2d   = false;
+  myParams = theParams;
+  myPoint  = thePoint;
+  myString.FromUnicode (theText);
+}
 
-void OpenGl_Text::Render (const Handle(OpenGl_Workspace) &AWorkspace) const
+// =======================================================================
+// function : Init
+// purpose  :
+// =======================================================================
+void OpenGl_Text::Init (const Handle(OpenGl_Context)&     theCtx,
+                        const TCollection_ExtendedString& theText,
+                        const OpenGl_Vec2&                thePoint,
+                        const OpenGl_TextParam&           theParams)
 {
-  if ( AWorkspace->DegenerateModel > 0 && AWorkspace->SkipRatio >= 1.f )
+  if (myParams.Height != theParams.Height)
+  {
+    Release (theCtx);
+  }
+  else
+  {
+    releaseVbos (theCtx);
+  }
+  myIs2d       = true;
+  myParams     = theParams;
+  myPoint.xy() = thePoint;
+  myPoint.z()  = 0.0f;
+  myString.FromUnicode ((Standard_Utf16Char* )theText.ToExtString());
+}
+
+// =======================================================================
+// function : ~OpenGl_Text
+// purpose  :
+// =======================================================================
+OpenGl_Text::~OpenGl_Text()
+{
+  //
+}
+
+// =======================================================================
+// function : releaseVbos
+// purpose  :
+// =======================================================================
+void OpenGl_Text::releaseVbos (const Handle(OpenGl_Context)& theCtx)
+{
+  for (Standard_Integer anIter = 0; anIter < myVertsVbo.Length(); ++anIter)
+  {
+    Handle(OpenGl_VertexBuffer)& aVerts = myVertsVbo.ChangeValue (anIter);
+    Handle(OpenGl_VertexBuffer)& aTCrds = myTCrdsVbo.ChangeValue (anIter);
+
+    if (!theCtx.IsNull())
+    {
+      theCtx->DelayedRelease (aVerts);
+      theCtx->DelayedRelease (aTCrds);
+    }
+    aVerts.Nullify();
+    aTCrds.Nullify();
+  }
+  myTextures.Clear();
+  myVertsVbo.Clear();
+  myTCrdsVbo.Clear();
+  myVertsArray.Clear();
+  myTCrdsArray.Clear();
+}
+
+// =======================================================================
+// function : Release
+// purpose  :
+// =======================================================================
+void OpenGl_Text::Release (const Handle(OpenGl_Context)& theCtx)
+{
+  releaseVbos (theCtx);
+  if (!myFont.IsNull())
+  {
+    Handle(OpenGl_Context) aCtx = theCtx;
+    const TCollection_AsciiString aKey = myFont->ResourceKey();
+    myFont.Nullify();
+    aCtx->ReleaseResource (aKey, Standard_True);
+  }
+}
+
+// =======================================================================
+// function : StringSize
+// purpose  :
+// =======================================================================
+void OpenGl_Text::StringSize (const Handle(OpenGl_Context)& theCtx,
+                              const NCollection_String&     theText,
+                              const OpenGl_AspectText&      theTextAspect,
+                              const OpenGl_TextParam&       theParams,
+                              Standard_ShortReal&           theWidth,
+                              Standard_ShortReal&           theAscent,
+                              Standard_ShortReal&           theDescent)
+{
+  theWidth   = 0.0f;
+  theAscent  = 0.0f;
+  theDescent = 0.0f;
+  const TCollection_AsciiString aFontKey = FontKey (theTextAspect, theParams.Height);
+  Handle(OpenGl_Font) aFont = FindFont (theCtx, theTextAspect, theParams.Height, aFontKey);
+  if (aFont.IsNull() || !aFont->IsValid())
+  {
     return;
+  }
+
+  theAscent  = aFont->Ascender();
+  theDescent = aFont->Descender();
+
+  GLfloat aWidth = 0.0f;
+  for (NCollection_Utf8Iter anIter = theText.Iterator(); *anIter != 0;)
+  {
+    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)
+    {
+      theWidth = Max (theWidth, aWidth);
+      aWidth   = 0.0f;
+      continue;
+    }
+    else if (aCharThis == ' ')
+    {
+      aWidth += aFont->AdvanceX (aCharThis, aCharNext);
+      continue;
+    }
+    else if (aCharThis == '\t')
+    {
+      aWidth += aFont->AdvanceX (' ', aCharNext) * 8.0f;
+      continue;
+    }
+
+    aWidth += aFont->AdvanceX (aCharThis, aCharNext);
+  }
+  theWidth = Max (theWidth, aWidth);
+
+  Handle(OpenGl_Context) aCtx = theCtx;
+  aFont.Nullify();
+  aCtx->ReleaseResource (aFontKey, Standard_True);
+}
+
+// =======================================================================
+// function : Render
+// purpose  :
+// =======================================================================
+void OpenGl_Text::Render (const Handle(OpenGl_Workspace)& theWorkspace) const
+{
+  if (theWorkspace->DegenerateModel > 0 && theWorkspace->SkipRatio >= 1.0f)
+  {
+    return;
+  }
+
+  const OpenGl_AspectText* aTextAspect = theWorkspace->AspectText (Standard_True);
+  const Handle(OpenGl_Texture) aPrevTexture = theWorkspace->DisableTexture();
+
+  // use highlight color or colors from aspect
+  if (theWorkspace->NamedStatus & OPENGL_NS_HIGHLIGHT)
+  {
+    render (theWorkspace->PrinterContext(),
+            theWorkspace->GetGlContext(),
+            *aTextAspect,
+            *theWorkspace->HighlightColor,
+            *theWorkspace->HighlightColor);
+  }
+  else
+  {
+    render (theWorkspace->PrinterContext(),
+            theWorkspace->GetGlContext(),
+            *aTextAspect,
+            aTextAspect->Color(),
+            aTextAspect->SubtitleColor());
+  }
 
-  const OpenGl_AspectText *aspect_text = AWorkspace->AspectText( Standard_True );
+  // restore aspects
+  if (!aPrevTexture.IsNull())
+  {
+    theWorkspace->EnableTexture (aPrevTexture);
+  }
+}
 
-  const TEL_COLOUR *tcolor, *scolor;
+// =======================================================================
+// function : Render
+// purpose  :
+// =======================================================================
+void OpenGl_Text::Render (const Handle(OpenGl_PrinterContext)& thePrintCtx,
+                          const Handle(OpenGl_Context)&        theCtx,
+                          const OpenGl_AspectText&             theTextAspect) const
+{
+  render (thePrintCtx, theCtx, theTextAspect, theTextAspect.Color(), theTextAspect.SubtitleColor());
+}
 
-  // Use highlight colours
-  if( AWorkspace->NamedStatus & OPENGL_NS_HIGHLIGHT )
+// =======================================================================
+// function : setupMatrix
+// purpose  :
+// =======================================================================
+void OpenGl_Text::setupMatrix (const Handle(OpenGl_PrinterContext)& thePrintCtx,
+                               const Handle(OpenGl_Context)&        theCtx,
+                               const OpenGl_AspectText&             theTextAspect,
+                               const OpenGl_Vec3                    theDVec) const
+{
+  // setup matrix
+  if (myIs2d)
   {
-    tcolor = scolor = AWorkspace->HighlightColor;
+    glLoadIdentity();
+    glTranslatef (myPoint.x() + theDVec.x(), myPoint.y() + theDVec.y(), 0.0f);
+    glRotatef (180.0f, 1.0f, 0.0f, 0.0f);
   }
   else
   {
-    tcolor = &aspect_text->Color();
-    scolor = &aspect_text->SubtitleColor();
+    // align coordinates to the nearest integer
+    // to avoid extra interpolation issues
+    GLdouble anObjX, anObjY, anObjZ;
+    gluUnProject (std::floor (myWinX + (GLdouble )theDVec.x()),
+                  std::floor (myWinY + (GLdouble )theDVec.y()),
+                  myWinZ + (GLdouble )theDVec.z(),
+                  (GLdouble* )THE_IDENTITY_MATRIX, myProjMatrix, myViewport,
+                  &anObjX, &anObjY, &anObjZ);
+
+    glLoadIdentity();
+    glTranslated (anObjX, anObjY, anObjZ);
+    glRotated (theTextAspect.Angle(), 0.0, 0.0, 1.0);
+    if (!theTextAspect.IsZoomable())
+    {
+    #ifdef _WIN32
+      // if the context has assigned printer context, use it's parameters
+      if (!thePrintCtx.IsNull())
+      {
+        // get printing scaling in x and y dimensions
+        GLfloat aTextScalex = 1.0f, aTextScaley = 1.0f;
+        thePrintCtx->GetScale (aTextScalex, aTextScaley);
+
+        // text should be scaled in all directions with same
+        // factor to save its proportions, so use height (y) scaling
+        // as it is better for keeping text/3d graphics proportions
+        glScalef (aTextScaley, aTextScaley, aTextScaley);
+      }
+    #endif
+      glScaled (myScaleHeight, myScaleHeight, myScaleHeight);
+    }
+  }
+}
+
+// =======================================================================
+// function : drawText
+// purpose  :
+// =======================================================================
+void OpenGl_Text::drawText (const Handle(OpenGl_PrinterContext)& thePrintCtx,
+                            const Handle(OpenGl_Context)&        theCtx,
+                            const OpenGl_AspectText&             theTextAspect) const
+{
+#ifdef HAVE_GL2PS
+  if (theCtx->IsFeedback())
+  {
+    // position of the text and alignment is calculated by transformation matrix
+    exportText (myString, myIs2d, theTextAspect, (Standard_Integer )myExportHeight);
+    return;
   }
+#endif
 
-  // Handle annotation style
-  GLboolean flag_zbuffer = GL_FALSE;
-  if (aspect_text->StyleType() == Aspect_TOST_ANNOTATION)
+  if (myVertsVbo.Length() == myTextures.Length())
   {
-    flag_zbuffer = glIsEnabled(GL_DEPTH_TEST);
-    if (flag_zbuffer) glDisable(GL_DEPTH_TEST);
+    for (Standard_Integer anIter = 0; anIter < myTextures.Length(); ++anIter)
+    {
+      const GLuint aTexId = myTextures.Value (anIter);
+      const Handle(OpenGl_VertexBuffer)& aVerts = myVertsVbo.Value (anIter);
+      const Handle(OpenGl_VertexBuffer)& aTCrds = myTCrdsVbo.Value (anIter);
+      aVerts->BindFixed (theCtx, GL_VERTEX_ARRAY);
+      aTCrds->BindFixed (theCtx, GL_TEXTURE_COORD_ARRAY);
+      glBindTexture (GL_TEXTURE_2D, aTexId);
+
+      glDrawArrays (GL_TRIANGLES, 0, GLsizei(aVerts->GetElemsNb()));
+
+      glBindTexture (GL_TEXTURE_2D, 0);
+      aTCrds->UnbindFixed (theCtx, GL_VERTEX_ARRAY);
+      aVerts->UnbindFixed (theCtx, GL_VERTEX_ARRAY);
+    }
   }
+  else if (myVertsArray.Length() == myTextures.Length())
+  {
+    glEnableClientState (GL_VERTEX_ARRAY);
+    glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+    for (Standard_Integer anIter = 0; anIter < myTextures.Length(); ++anIter)
+    {
+      const GLuint aTexId = myTextures.Value (anIter);
+      const Handle(OpenGl_Vec2Array)& aVerts = myVertsArray.Value (anIter);
+      const Handle(OpenGl_Vec2Array)& aTCrds = myTCrdsArray.Value (anIter);
+
+      glVertexPointer   (2, GL_FLOAT, 0, (GLfloat* )&aVerts->First());
+      glTexCoordPointer (2, GL_FLOAT, 0, (GLfloat* )&aTCrds->First());
+      glBindTexture (GL_TEXTURE_2D, aTexId);
 
-  AWorkspace->SetTextParam(&myParam);
+      glDrawArrays (GL_TRIANGLES, 0, aVerts->Length());
 
-  GLdouble        modelMatrix[16], projMatrix[16];
-  GLint           viewport[4];
-  GLdouble        objrefX, objrefY, objrefZ;
-  GLdouble        objX, objY, objZ;
-  GLdouble        obj1X, obj1Y, obj1Z;
-  GLdouble        obj2X, obj2Y, obj2Z;
-  GLdouble        obj3X, obj3Y, obj3Z;
-  GLdouble        winx1, winy1, winz1;
-  GLdouble        winx, winy, winz;
-  GLint           status;
+      glBindTexture (GL_TEXTURE_2D, 0);
+    }
+    glDisableClientState (GL_TEXTURE_COORD_ARRAY);
+    glDisableClientState (GL_VERTEX_ARRAY);
+  }
+}
 
-  /* display type of text */
-  if (aspect_text->DisplayType() != Aspect_TODT_NORMAL)
+// =======================================================================
+// function : FontKey
+// purpose  :
+// =======================================================================
+TCollection_AsciiString OpenGl_Text::FontKey (const OpenGl_AspectText& theAspect,
+                                              const Standard_Integer   theHeight)
+{
+  const Font_FontAspect anAspect = (theAspect.FontAspect() != Font_FA_Undefined) ? theAspect.FontAspect() : Font_FA_Regular;
+  return theAspect.FontName()
+       + TCollection_AsciiString(":") + Standard_Integer(anAspect)
+       + TCollection_AsciiString(":") + theHeight;
+}
+
+// =======================================================================
+// function : FindFont
+// purpose  :
+// =======================================================================
+Handle(OpenGl_Font) OpenGl_Text::FindFont (const Handle(OpenGl_Context)& theCtx,
+                                           const OpenGl_AspectText&      theAspect,
+                                           const Standard_Integer        theHeight,
+                                           const TCollection_AsciiString theKey)
+{
+  Handle(OpenGl_Font) aFont;
+  if (theHeight < 2)
   {
-    /* Optimisation: il faudrait ne faire le Get qu'une fois par Redraw */
-    glGetIntegerv (GL_VIEWPORT, viewport);
-    glGetDoublev (GL_MODELVIEW_MATRIX, modelMatrix);
-    glGetDoublev (GL_PROJECTION_MATRIX, projMatrix);
+    return aFont; // invalid parameters
+  }
 
-    switch (aspect_text->DisplayType())
+  if (!theCtx->GetResource (theKey, aFont))
+  {
+    Handle(Font_FontMgr) aFontMgr = Font_FontMgr::GetInstance();
+    const Handle(TCollection_HAsciiString) aFontName = new TCollection_HAsciiString (theAspect.FontName());
+    const Font_FontAspect anAspect = (theAspect.FontAspect() != Font_FA_Undefined) ? theAspect.FontAspect() : Font_FA_Regular;
+    Handle(Font_SystemFont) aRequestedFont = aFontMgr->FindFont (aFontName, anAspect, theHeight);
+    if (aRequestedFont.IsNull())
     {
+      return aFont;
+    }
+
+    Handle(Font_FTFont) aFontFt = new Font_FTFont (NULL);
+    if (!aFontFt->Init (aRequestedFont->FontPath()->ToCString(), theHeight))
+    {
+      return aFont;
+    }
+
+    Handle(OpenGl_Context) aCtx = theCtx;
+    glPushAttrib (GL_TEXTURE_BIT);
+    aFont = new OpenGl_Font (aFontFt, theKey);
+    if (!aFont->Init (aCtx))
+    {
+      //glPopAttrib();
+      //return aFont; // out of resources?
+    }
+    glPopAttrib(); // texture bit
+
+    aCtx->ShareResource (theKey, aFont);
+  }
+  return aFont;
+}
+
+// =======================================================================
+// function : render
+// purpose  :
+// =======================================================================
+void OpenGl_Text::render (const Handle(OpenGl_PrinterContext)& thePrintCtx,
+                          const Handle(OpenGl_Context)&        theCtx,
+                          const OpenGl_AspectText&             theTextAspect,
+                          const TEL_COLOUR&                    theColorText,
+                          const TEL_COLOUR&                    theColorSubs) const
+{
+  if (myString.IsEmpty())
+  {
+    return;
+  }
+
+  const TCollection_AsciiString aFontKey = FontKey (theTextAspect, myParams.Height);
+  if (!myFont.IsNull()
+   && !myFont->ResourceKey().IsEqual (aFontKey))
+  {
+    // font changed
+    const_cast<OpenGl_Text* > (this)->Release (theCtx);
+  }
+
+  if (myFont.IsNull())
+  {
+    myFont = FindFont (theCtx, theTextAspect, myParams.Height, aFontKey);
+    if (myFont.IsNull())
+    {
+      return;
+    }
+  }
+
+  if (myTextures.IsEmpty())
+  {
+    OpenGl_TextFormatter aFormatter;
+    aFormatter.SetupAlignment (myParams.HAlign, myParams.VAlign);
+    aFormatter.Reset();
+    aFormatter.Append (theCtx, myString, *myFont.operator->());
+    aFormatter.Format();
+
+    if (OpenGl_GraphicDriver::ToUseVBO() && theCtx->core15 != NULL)
+    {
+      aFormatter.Result (theCtx, myTextures, myVertsVbo, myTCrdsVbo);
+    }
+    else
+    {
+      aFormatter.Result (theCtx, myTextures, myVertsArray, myTCrdsArray);
+    }
+    aFormatter.BndBox (myBndBox);
+  }
+
+  if (myTextures.IsEmpty())
+  {
+    return;
+  }
+
+  myExportHeight = 1.0f;
+  myScaleHeight  = 1.0f;
+
+  glMatrixMode (GL_MODELVIEW);
+  glPushMatrix();
+  if (!myIs2d)
+  {
+    // retrieve active matrices for project/unproject calls
+    glGetDoublev  (GL_MODELVIEW_MATRIX,  myModelMatrix);
+    glGetDoublev  (GL_PROJECTION_MATRIX, myProjMatrix);
+    glGetIntegerv (GL_VIEWPORT,          myViewport);
+    gluProject (myPoint.x(), myPoint.y(), myPoint.z(),
+                myModelMatrix, myProjMatrix, myViewport,
+                &myWinX, &myWinY, &myWinZ);
+
+    // compute scale factor for constant text height
+    GLdouble x1, y1, z1;
+    gluUnProject (myWinX, myWinY, myWinZ,
+                  (GLdouble* )THE_IDENTITY_MATRIX, myProjMatrix, myViewport,
+                  &x1, &y1, &z1);
+
+    GLdouble x2, y2, z2;
+    const GLdouble h = (GLdouble )myFont->FTFont()->PointSize();
+    gluUnProject (myWinX, myWinY + h - 1.0, myWinZ,
+                  (GLdouble* )THE_IDENTITY_MATRIX, myProjMatrix, myViewport,
+                  &x2, &y2, &z2);
+
+    myScaleHeight = (y2 - y1) / h;
+    if (theTextAspect.IsZoomable())
+    {
+      myExportHeight = (float )h;
+    }
+  }
+  myExportHeight = (float )myFont->FTFont()->PointSize() / myExportHeight;
+
+  // push enabled flags to the stack
+  glPushAttrib (GL_ENABLE_BIT);
+
+  // setup depth test
+  if (!myIs2d
+   && theTextAspect.StyleType() != Aspect_TOST_ANNOTATION)
+  {
+    glEnable (GL_DEPTH_TEST);
+  }
+  else
+  {
+    glDisable (GL_DEPTH_TEST);
+  }
+
+  // setup alpha test
+  GLint aTexEnvParam = GL_REPLACE;
+  glGetTexEnviv (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &aTexEnvParam);
+  if (aTexEnvParam != GL_REPLACE)
+  {
+    glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+  }
+  glAlphaFunc (GL_GEQUAL, 0.285f);
+  glEnable (GL_ALPHA_TEST);
+
+  // setup blending
+  glEnable (GL_BLEND);
+  glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // GL_ONE
+
+  // activate texture unit
+  glDisable (GL_TEXTURE_1D);
+  glEnable  (GL_TEXTURE_2D);
+  if (theCtx->core13 != NULL)
+  {
+    theCtx->core13->glActiveTexture (GL_TEXTURE0);
+  }
+
+  // extra drawings
+  switch (theTextAspect.DisplayType())
+  {
     case Aspect_TODT_BLEND:
-      glEnable(GL_COLOR_LOGIC_OP);
-      glLogicOp(GL_XOR);
+    {
+      glEnable  (GL_COLOR_LOGIC_OP);
+      glLogicOp (GL_XOR);
       break;
+    }
     case Aspect_TODT_SUBTITLE:
     {
-      int sWidth, sAscent, sDescent;
-      AWorkspace->StringSize(myString, sWidth, sAscent, sDescent);
-
-      objrefX = (float)myAttachPnt.xyz[0];
-      objrefY = (float)myAttachPnt.xyz[1];
-      objrefZ = (float)myAttachPnt.xyz[2];
-      status = gluProject (objrefX, objrefY, objrefZ, modelMatrix, projMatrix, viewport,
-        &winx1, &winy1, &winz1);
-
-      winx = winx1;
-      winy = winy1-sDescent;
-      winz = winz1+0.00001;
-      status = gluUnProject (winx, winy, winz, modelMatrix, projMatrix, viewport,
-        &objX, &objY, &objZ);
-
-      winx = winx1 + sWidth;
-      winy = winy1-sDescent;
-      winz = winz1+0.00001; /* il vaut mieux F+B / 1000000 ? */
-      status = gluUnProject (winx, winy, winz, modelMatrix, projMatrix, viewport,
-        &obj1X, &obj1Y, &obj1Z);
-
-      winx = winx1 + sWidth;
-      winy = winy1 + sAscent;
-      winz = winz1+0.00001;
-      status = gluUnProject (winx, winy, winz, modelMatrix, projMatrix, viewport,
-        &obj2X, &obj2Y, &obj2Z);
-
-      winx = winx1;
-      winy = winy1+ sAscent;
-      winz = winz1+0.00001;
-      status = gluUnProject (winx, winy, winz, modelMatrix, projMatrix, viewport,
-        &obj3X, &obj3Y, &obj3Z);
-
-      glColor3fv( scolor->rgb );
-      glBegin(GL_POLYGON);
-      glVertex3d(objX, objY, objZ);
-      glVertex3d(obj1X, obj1Y, obj1Z);
-      glVertex3d(obj2X, obj2Y, obj2Z);
-      glVertex3d(obj3X, obj3Y, obj3Z);
+      glColor3fv  (theColorSubs.rgb);
+      setupMatrix (thePrintCtx, theCtx, theTextAspect, OpenGl_Vec3 (0.0f, 0.0f, 0.00001f));
+
+      glBindTexture (GL_TEXTURE_2D, 0);
+      glBegin (GL_QUADS);
+      glVertex2f (myBndBox.Left,  myBndBox.Top);
+      glVertex2f (myBndBox.Right, myBndBox.Top);
+      glVertex2f (myBndBox.Right, myBndBox.Bottom);
+      glVertex2f (myBndBox.Left,  myBndBox.Bottom);
       glEnd();
       break;
     }
-
     case Aspect_TODT_DEKALE:
-      objrefX = (float)myAttachPnt.xyz[0];
-      objrefY = (float)myAttachPnt.xyz[1];
-      objrefZ = (float)myAttachPnt.xyz[2];
-      status = gluProject (objrefX, objrefY, objrefZ, modelMatrix, projMatrix, viewport,
-        &winx1, &winy1, &winz1);
-
-      winx = winx1+1;
-      winy = winy1+1;
-      winz = winz1+0.00001;
-      status = gluUnProject (winx, winy, winz, modelMatrix, projMatrix, viewport,
-        &objX, &objY, &objZ);
-
-      glColor3fv( scolor->rgb );
-      AWorkspace->RenderText( myString, 0, (float)objX, (float)objY,(float)objZ );
-      winx = winx1-1;
-      winy = winy1-1;
-      status = gluUnProject (winx, winy, winz, modelMatrix, projMatrix, viewport,
-        &objX, &objY, &objZ);
-
-      AWorkspace->RenderText( myString, 0, (float)objX, (float)objY,(float)objZ );
-      winx = winx1-1;
-      winy = winy1+1;
-      status = gluUnProject (winx, winy, winz, modelMatrix, projMatrix, viewport,
-        &objX, &objY, &objZ);
-
-      AWorkspace->RenderText( myString, 0, (float)objX, (float)objY,(float)objZ );
-      winx = winx1+1;
-      winy = winy1-1;
-      status = gluUnProject (winx, winy, winz, modelMatrix, projMatrix, viewport,
-        &objX, &objY, &objZ);
-      AWorkspace->RenderText( myString, 0, (float)objX, (float)objY,(float)objZ );
+    {
+      glColor3fv  (theColorSubs.rgb);
+      setupMatrix (thePrintCtx, theCtx, theTextAspect, OpenGl_Vec3 (+1.0f, +1.0f, 0.00001f));
+      drawText    (thePrintCtx, theCtx, theTextAspect);
+      setupMatrix (thePrintCtx, theCtx, theTextAspect, OpenGl_Vec3 (-1.0f, -1.0f, 0.00001f));
+      drawText    (thePrintCtx, theCtx, theTextAspect);
+      setupMatrix (thePrintCtx, theCtx, theTextAspect, OpenGl_Vec3 (-1.0f, +1.0f, 0.00001f));
+      drawText    (thePrintCtx, theCtx, theTextAspect);
+      setupMatrix (thePrintCtx, theCtx, theTextAspect, OpenGl_Vec3 (+1.0f, -1.0f, 0.00001f));
+      drawText    (thePrintCtx, theCtx, theTextAspect);
+      break;
+    }
+    case Aspect_TODT_NORMAL:
+    {
       break;
     }
   }
 
-  glColor3fv( tcolor->rgb );
-  AWorkspace->RenderText( myString, 0, (float)myAttachPnt.xyz[0], (float)myAttachPnt.xyz[1],(float)myAttachPnt.xyz[2] );
-  /* maj attributs */
-  if (flag_zbuffer) glEnable(GL_DEPTH_TEST);
-  if (aspect_text->DisplayType() == Aspect_TODT_BLEND)
-  {
-    glDisable(GL_COLOR_LOGIC_OP);
-  }
-}
+  // main draw call
+  glColor3fv  (theColorText.rgb);
+  setupMatrix (thePrintCtx, theCtx, theTextAspect, OpenGl_Vec3 (0.0f, 0.0f, 0.0f));
+  drawText    (thePrintCtx, theCtx, theTextAspect);
 
-void OpenGl_Text::Release (const Handle(OpenGl_Context)& theContext)
-{
-  //
+  // revert OpenGL state
+  glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, aTexEnvParam);
+  glPopAttrib(); // enable bit
+  glPopMatrix(); // model view matrix was modified
 }