0023015: TKOpenGl redesign regression in text printing
[occt.git] / src / OpenGl / OpenGl_Display_1.cxx
1 // File:      OpenGl_Display_1.cxx
2 // Created:   25 October 2011
3 // Author:    Sergey ZERCHANINOV
4 // Copyright: OPEN CASCADE 2011
5
6 #include <OpenGl_tgl_all.hxx>
7 #include <OpenGl_Display.hxx>
8
9 #include <TCollection_AsciiString.hxx>
10 #include <TCollection_HAsciiString.hxx>
11
12 #include <OpenGl_FontMgr.hxx>
13 #include <OpenGl_PrinterContext.hxx>
14 #include <OpenGl_AspectText.hxx>
15
16 #ifdef HAVE_GL2PS
17 #include <gl2ps.h>
18 #endif
19
20 /*-----------------------------------------------------------------------------*/
21 /*
22 * Prototypes variables statiques
23 */                                                 
24
25 struct FontMapNode
26 {
27   const char *    EnumName;
28   const char *    FontName;
29   OSD_FontAspect  FontAspect;
30 };
31
32 static const FontMapNode myFontMap[] =
33 {
34
35 #ifdef WNT
36
37   { "Courier"                  , "Courier New"    , OSD_FA_Regular },
38   { "Times-Roman"              , "Times New Roman", OSD_FA_Regular  },
39   { "Times-Bold"               , "Times New Roman", OSD_FA_Bold },
40   { "Times-Italic"             , "Times New Roman", OSD_FA_Italic  },
41   { "Times-BoldItalic"         , "Times New Roman", OSD_FA_BoldItalic  },
42   { "ZapfChancery-MediumItalic", "Script"         , OSD_FA_Regular  },
43   { "Symbol"                   , "Symbol"         , OSD_FA_Regular  },
44   { "ZapfDingbats"             , "WingDings"      , OSD_FA_Regular  },
45   { "Rock"                     , "Arial"          , OSD_FA_Regular  },
46   { "Iris"                     , "Lucida Console" , OSD_FA_Regular  }
47
48 #else   //X11
49
50   { "Courier"                  , "Courier"      , OSD_FA_Regular },
51   { "Times-Roman"              , "Times"        , OSD_FA_Regular  },
52   { "Times-Bold"               , "Times"        , OSD_FA_Bold },
53   { "Times-Italic"             , "Times"        , OSD_FA_Italic  },
54   { "Times-BoldItalic"         , "Times"        , OSD_FA_BoldItalic  },
55   { "Arial"                    , "Helvetica"    , OSD_FA_Regular  }, 
56   { "ZapfChancery-MediumItalic", "-adobe-itc zapf chancery-medium-i-normal--*-*-*-*-*-*-iso8859-1"              , OSD_FA_Regular  },
57   { "Symbol"                   , "-adobe-symbol-medium-r-normal--*-*-*-*-*-*-adobe-fontspecific"                , OSD_FA_Regular  },
58   { "ZapfDingbats"             , "-adobe-itc zapf dingbats-medium-r-normal--*-*-*-*-*-*-adobe-fontspecific"     , OSD_FA_Regular  },
59   { "Rock"                     , "-sgi-rock-medium-r-normal--*-*-*-*-p-*-iso8859-1"                             , OSD_FA_Regular  },
60   { "Iris"                     , "--iris-medium-r-normal--*-*-*-*-m-*-iso8859-1"                                , OSD_FA_Regular  }
61 #endif
62
63 };
64
65 #define NUM_FONT_ENTRIES (sizeof(myFontMap)/sizeof(FontMapNode))
66
67 /*-----------------------------------------------------------------------------*/
68
69 /*
70 *  Constants
71 */
72
73 #ifdef HAVE_GL2PS
74 void OpenGl_Display::getGL2PSFontName (const char *src_font, char *ps_font)
75 {
76   /* 
77   Convert font name used for rendering to some "good" font names
78   that produce good vector text 
79   */
80   static char const *family[] = {"Helvetica", "Courier", "Times"};
81   static char const *italic[] = {"Oblique", "Oblique", "Italic"};
82   static char const *base[] = {"", "", "-Roman"};
83
84   int font = 0;
85   int isBold = 0;
86   int isItalic = 0;
87
88   if( strstr( src_font, "Symbol" ) ){
89     sprintf( ps_font, "%s", "Symbol" );
90     return;
91   }
92
93   if( strstr( src_font, "ZapfDingbats" ) ){
94     sprintf( ps_font, "%s", "WingDings" );
95     return;
96   }
97
98   if ( strstr( src_font, "Courier" ) ){
99     font = 1;
100   }
101   else if ( strstr( src_font, "Times" ) ){
102     font = 2;
103   }
104
105   if ( strstr( src_font, "Bold" ) ){
106     isBold = 1;
107   }
108
109   if ( strstr( src_font, "Italic" ) || strstr( src_font, "Oblique" ) ){
110     isItalic = 1;
111   }
112
113   if ( isBold ){
114     sprintf( ps_font, "%s-%s", family[font], "Bold");
115     if ( isItalic ){
116       sprintf(ps_font, "%s%s", ps_font, italic[font]);
117     }
118   }
119   else if ( isItalic )
120   {
121     sprintf( ps_font, "%s-%s", family[font], italic[font] );
122   }
123   else
124   {
125     sprintf( ps_font, "%s%s", family[font], base[font] );
126   }
127 }
128 #endif
129
130 /*-----------------------------------------------------------------------------*/
131
132 /*
133 * Fonctions publiques 
134 */
135
136 /*-----------------------------------------------------------------------------*/
137
138 int OpenGl_Display::FindFont (const char* AFontName, const OSD_FontAspect AFontAspect,
139                              const int ABestSize, const float AXScale, const float AYScale)
140 {   
141   if (!AFontName)
142     return -1;
143
144   if (ABestSize != -1)
145     myFontSize = ABestSize;
146
147   OpenGl_FontMgr* mgr = OpenGl_FontMgr::instance();
148
149   Handle(TCollection_HAsciiString) family_name = new TCollection_HAsciiString(AFontName);
150   myFont = mgr->request_font( family_name, AFontAspect, myFontSize );
151
152   if( myFont == -1 )
153   {
154     //try to use font names mapping
155     FontMapNode newTempFont = myFontMap[0];
156     for ( int i = 0; i < NUM_FONT_ENTRIES; ++i )
157     {
158       if ( TCollection_AsciiString(myFontMap[i].EnumName).IsEqual( family_name->ToCString() ) )
159       {
160         newTempFont = myFontMap[i];
161         break;
162       }
163     }
164     family_name = new TCollection_HAsciiString(newTempFont.FontName);
165     myFont = mgr->request_font( family_name, newTempFont.FontAspect, myFontSize );
166   }
167
168   // Requested family name not found -> serach for any font family with given aspect and height
169   if ( myFont == -1 )
170   {
171     family_name = new TCollection_HAsciiString( "" );
172     myFont = mgr->request_font( family_name, AFontAspect, myFontSize );
173   }
174
175   // The last resort: trying to use ANY font available in the system
176   if ( myFont == -1 )
177   {
178     myFont = mgr->request_font( family_name, OSD_FA_Undefined, -1 );
179   }
180
181   if ( myFont != -1 )
182     mgr->setCurrentScale( AXScale, AYScale );
183
184   return myFont;
185 }
186
187 /*-----------------------------------------------------------------------------*/
188
189 void OpenGl_Display::StringSize (const wchar_t *str, int &width, int &ascent, int &descent)
190 {
191   ascent = 0;
192   descent = 0;
193   width = 0;
194   if (myFont != -1) {
195     OpenGl_FontMgr* mgr = OpenGl_FontMgr::instance();
196     const FTFont* font = mgr->fontById( myFont );
197     if ( font ) {
198       width = int( mgr->computeWidth( myFont, str ) );
199       ascent = int( font->Ascender() );
200       descent = int( font->Descender() );
201     }
202   }
203 }
204
205 /*
206   Class       : MultilineTextRenderer
207   Description : Class for constructing text and multi-line text
208 */
209
210 class MultilineTextRenderer
211 {
212   private:
213
214   Standard_Integer        myLFNum;        // Number of '\n' (Line Feed) '\x00\x0A' 
215   Standard_Integer        myCurrPos;      // Position of the current substring
216   Standard_Integer        myNewStrLen;    // Length of the new string 
217   Standard_Integer        mySubstrNum;    // Number of substrings
218   wchar_t                 *myNewStr;      // New string
219   const wchar_t           *myStrPtr;      // Pointer to the original string
220
221   public:
222
223   // ----------------------------------------------
224   // Function: MultilineTextRenderer
225   // Purpose:  Constructor with initialization
226   // ----------------------------------------------
227   MultilineTextRenderer ( const wchar_t           *theStr,
228                           GLdouble                &theXdis,
229                           GLdouble                &theYdis,
230                           const OpenGl_TextParam  *theParam,
231                           const FTFont            *theFnt,
232                           GLint                   theWidthFont,
233                           GLint                   theAscentFont,
234                           GLint                   theDescentFont ) :
235
236       myLFNum         (0),
237       myCurrPos       (0),
238       myNewStrLen     (0),
239       mySubstrNum     (0),
240       myNewStr        (0),
241       myStrPtr        (&theStr[0])
242   {
243     const Standard_Integer  aStrLen       = wcslen(theStr); // Length of the original string
244     Standard_Integer        aNextCRChar   = 0;  // Character after '\r' (Carriage Return)        '\x00\x0D'
245     Standard_Integer        aHTNum        = 0;  // Number of       '\t' (Horizontal Tabulation)  '\x00\x09'
246     Standard_Integer        aDumpNum      = 0;  // Number of '\a', '\b', '\v' and '\f'
247     Standard_Integer        anAllSpaces   = 0;  // Number of spaces instead of all '\t' characters
248     Standard_Integer        aMaxSubstrLen = 0;  // Length of the largest substring in the new string
249
250     Standard_Integer        aTimeVar      = 0;
251
252     // Calculation index after last '\r' character
253     for ( Standard_Integer anIndex = 0; anIndex < aStrLen; ++anIndex )
254     {
255       if ( theStr[anIndex] == '\r' )  aNextCRChar = anIndex+1;
256     }
257
258     // Calculation numbers of the some control characters
259     for (Standard_Integer anIndex = aNextCRChar; anIndex < aStrLen; anIndex++)
260     {
261       ++aTimeVar;
262       switch ( theStr[anIndex] )
263       {
264       case '\n':
265         ++myLFNum;
266         aTimeVar = 0;
267         break;
268       case '\b':
269       case '\v':
270       case '\f':
271       case '\a':
272         ++aDumpNum;
273         --aTimeVar;
274         break;
275       case '\t':
276         ++aHTNum;
277         anAllSpaces += ( 8 - ( aTimeVar - 1 )%8 );
278         aTimeVar = 0;
279         break;
280       }
281     }
282
283     // Calculation length of the new string
284     myStrPtr += aNextCRChar;
285     myNewStrLen = aStrLen - aNextCRChar + anAllSpaces - aHTNum - aDumpNum;
286
287     myNewStr = new wchar_t[myNewStrLen + 1];
288     myNewStr[myNewStrLen]='\0';
289
290     CalcString (aStrLen, aMaxSubstrLen);
291     CalcHAlign (theXdis, theParam, theWidthFont, aStrLen, aMaxSubstrLen);
292     CalcVAlign (theYdis, theParam, theFnt, theAscentFont, theDescentFont);
293   }
294
295   // ----------------------------------------------
296   // Function: ~MultilineTextRenderer
297   // Purpose:  Default destructor
298   // ----------------------------------------------
299   ~MultilineTextRenderer ()
300   {
301     delete[] myNewStr;
302   }
303
304   // ----------------------------------------------
305   // Function: Next
306   // Purpose:  Calculate position of the next substring
307   // ----------------------------------------------
308   void Next ()
309   {
310     for ( Standard_Integer anIndex = 0; anIndex <= myNewStrLen; ++anIndex )
311     {
312       if ( myNewStr[myCurrPos + anIndex] == '\0' )
313       {
314         ++mySubstrNum;
315         myCurrPos += anIndex + 1;
316         break;
317       }
318     }
319   }
320
321   // ----------------------------------------------
322   // Function: More
323   // Purpose:  Calculate position of the next substring
324   // ----------------------------------------------
325   Standard_Boolean More ()
326   {
327     if ( mySubstrNum <= myLFNum )  return Standard_True;
328     else                           return Standard_False;
329   }
330
331   // ----------------------------------------------
332   // Function: GetValue
333   // Purpose:  Returns current substring
334   // ----------------------------------------------
335   wchar_t* GetValue ()
336   {
337     return ( myNewStr + myCurrPos );
338   }
339
340   private:
341
342   // ----------------------------------------------
343   // Function: CalcString
344   // Purpose:  Calculate new string separated by '\0' without '\n', '\t', '\b', '\v', '\f', '\a'
345   // ----------------------------------------------
346   void CalcString ( const Standard_Integer theStrLen, Standard_Integer &theMaxSubstrLen )
347   {
348     Standard_Integer
349         aHelpIndex  = 0,
350         aTimeVar    = 0,
351         aSpacesNum  = 0;
352
353     for ( Standard_Integer anIndex1 = 0, anIndex2 = 0;
354           (anIndex1 < theStrLen)&&(anIndex2 < myNewStrLen);
355           ++anIndex1, ++anIndex2 )
356     {
357       ++aTimeVar;
358
359       if ( *(myStrPtr + anIndex1) == '\n' ) aTimeVar = 0;
360
361       while ( (*(myStrPtr + anIndex1)=='\b')||(*(myStrPtr + anIndex1)=='\f')
362             ||(*(myStrPtr + anIndex1)=='\v')||(*(myStrPtr + anIndex1)=='\a') )
363       {
364         ++anIndex1;
365       }
366
367       if ( *(myStrPtr + anIndex1) == '\t' )
368       {
369         aSpacesNum = ( 8 - ( aTimeVar - 1 )%8 );
370
371         for ( aHelpIndex = 0; aHelpIndex < aSpacesNum; aHelpIndex++ )
372         {
373           myNewStr[anIndex2 + aHelpIndex] = ' ';
374         }
375         anIndex2 += aHelpIndex - 1;
376         aTimeVar = 0;
377       }
378       else 
379       {
380         myNewStr[anIndex2] = *(myStrPtr + anIndex1);
381       }
382     }
383
384     // After this part of code we will have a string separated by '\0' characters
385     Standard_Integer aHelpLength = 0;
386
387     if( myNewStr )
388     {
389       for( Standard_Integer anIndex = 0; anIndex <= myNewStrLen; ++anIndex )
390       {
391         if ( myNewStr[anIndex] == '\n' )
392         {
393           myNewStr[anIndex] = '\0';
394         }
395
396         // Calculating length of the largest substring of the new string.
397         // It's needed for horizontal alignment
398         if ( myNewStr[anIndex] != '\0' )
399         {
400           ++aHelpLength;
401         }
402         else
403         {
404           if ( aHelpLength > theMaxSubstrLen ) theMaxSubstrLen = aHelpLength;
405
406           aHelpLength = 0;
407         }
408       }
409     }
410   }
411
412   // ----------------------------------------------
413   // Function: CalcVAlign
414   // Purpose:  Calculate vertical alignment for text
415   // ----------------------------------------------
416   void CalcVAlign ( GLdouble                &theYdis,
417                     const OpenGl_TextParam  *theParam,
418                     const FTFont            *theFnt,
419                     GLint                   theAscentFont,
420                     GLint                   theDescentFont )
421   {
422     switch (theParam->VAlign)
423     {
424     case Graphic3d_VTA_BOTTOM:
425       theYdis = (GLdouble)(myLFNum * theFnt->FaceSize());
426       break;
427     case Graphic3d_VTA_CENTER:
428       if ( (myLFNum%2) == 0 )   // An odd number of strings
429       {
430         theYdis = (GLdouble)((myLFNum/2.0) * theFnt->FaceSize()) + theDescentFont; 
431       }
432       else                      // An even number of strings
433       {
434         theYdis = (GLdouble)((myLFNum - 1)/2.0 * theFnt->FaceSize()) - theDescentFont/2.0;
435       }
436       break;
437     case Graphic3d_VTA_TOP:
438       theYdis = -(GLdouble)theAscentFont - theDescentFont;
439       break;
440     default:
441       theYdis = (GLdouble)(myLFNum * theFnt->FaceSize());
442       break;
443     }
444   }
445
446   // ----------------------------------------------
447   // Function: CalcHAlign
448   // Purpose:  Calculate horizontal alignment for text
449   // ----------------------------------------------
450   void CalcHAlign ( GLdouble                &theXdis,
451                     const OpenGl_TextParam  *theParam,
452                     GLint                   theWidthFont,
453                     const Standard_Integer  theStrLen,
454                     Standard_Integer        theMaxSubstrLen)
455   {
456     GLdouble aWidth = (GLdouble)(theMaxSubstrLen * theWidthFont)/theStrLen;
457
458     switch (theParam->HAlign)
459     {
460     case Graphic3d_HTA_LEFT:
461       theXdis = 0.;
462       break;
463     case Graphic3d_HTA_CENTER:
464       theXdis = -0.5 * (GLdouble)aWidth;
465       break;
466     case Graphic3d_HTA_RIGHT:
467       theXdis = -(GLdouble)aWidth;
468       break;
469     default:
470       theXdis = 0.;
471       break;
472     }
473   }
474 };
475
476 /*-----------------------------------------------------------------------------*/
477
478 void OpenGl_Display::RenderText (const wchar_t* str, const int is2d, const float x, const float y, const float z,
479                                 const OpenGl_AspectText *aspect, const OpenGl_TextParam *param)
480 {
481   OpenGl_FontMgr* mgr = OpenGl_FontMgr::instance();
482   const FTFont* fnt = mgr->fontById( myFont );
483   if ( !fnt )
484     return; 
485
486   // FTFont changes texture state when it renders and computes size for the text
487   glPushAttrib(GL_TEXTURE_BIT);
488
489   int widthFont, ascentFont, descentFont;
490   StringSize( str, widthFont, ascentFont, descentFont );
491
492   GLdouble xdis = 0., ydis = 0.;
493
494   float export_h = 1.;
495
496   MultilineTextRenderer aRenderer( str,
497                                    xdis,
498                                    ydis,
499                                    param,
500                                    fnt,
501                                    widthFont,
502                                    ascentFont,
503                                    descentFont );
504
505   glMatrixMode(GL_MODELVIEW);
506   glPushMatrix();
507   if (is2d)
508   {
509     glLoadIdentity();
510     glTranslatef(x, y, 0.f);
511     glRotatef( 180, 1, 0, 0 );
512   }
513   else
514   {
515     const GLdouble identityMatrix[4][4] =
516     {
517       {1.,0.,0.,0.},
518       {0.,1.,0.,0.},
519       {0.,0.,1.,0.},
520       {0.,0.,0.,1.}
521     };
522
523     GLdouble projMatrix[4][4], modelMatrix[4][4];
524     GLint viewport[4];
525
526     GLdouble wx, wy, wz;
527     GLdouble x1, y1, z1;
528     GLdouble x2, y2, z2;
529
530     glGetDoublev( GL_MODELVIEW_MATRIX, (GLdouble*)modelMatrix );
531     glGetDoublev( GL_PROJECTION_MATRIX, (GLdouble*)projMatrix );
532     glGetIntegerv( GL_VIEWPORT, (GLint*)viewport );
533
534     gluProject( x, y, z,
535       (GLdouble*)modelMatrix,
536       (GLdouble*)projMatrix,
537       (GLint*)viewport,
538       &wx, &wy, &wz );
539     glLoadIdentity();
540     gluUnProject( wx, wy, wz, 
541       (GLdouble*)identityMatrix, (GLdouble*)projMatrix, (GLint*)viewport,
542       &x1, &y1 , &z1 );    
543
544     GLdouble h = (GLdouble)fnt->FaceSize();
545
546     gluUnProject( wx, wy + h - 1., wz,
547       (GLdouble*)identityMatrix, (GLdouble*)projMatrix, (GLint*)viewport,
548       &x2, &y2, &z2 );
549
550     h = (y2-y1)/h;
551
552     glTranslated( x1, y1 , z1 );   
553     glRotated(aspect->Angle(), 0, 0, 1);
554     glTranslated(xdis, ydis, 0);  
555
556     if( !aspect->IsZoomable() )
557     {
558 #ifdef WNT
559       // if the context has assigned printer context, use it's parameters
560       OpenGl_PrinterContext* aPrinterContext = 
561         OpenGl_PrinterContext::GetPrinterContext( GET_GL_CONTEXT() );
562       if( aPrinterContext )
563       {
564         // get printing scaling in x and y dimensions
565         GLfloat aTextScalex = 1, aTextScaley = 1;
566         aPrinterContext->GetScale( aTextScalex, aTextScaley );
567         
568         // text should be scaled in all directions with same
569         // factor to save its proportions, so use height (y) scaling
570         // as it is better for keeping text/3d graphics proportions
571         glScalef( aTextScaley, aTextScaley, aTextScaley );
572       }
573 #endif
574       glScaled( h, h, h );
575     }
576     else
577     {
578       export_h = (float)h;
579     }
580   }
581
582   GLint renderMode;  
583   glGetIntegerv(GL_RENDER_MODE, &renderMode);
584   if ( renderMode == GL_FEEDBACK ) 
585   {
586 #ifdef HAVE_GL2PS
587     export_h = (float)fnt->FaceSize() / export_h;
588     for ( ; aRenderer.More(); aRenderer.Next() )
589     {
590       // x, y, z coordinates are used here as zero values, because position of the text
591       // and alignment is calculated in the code above using glTranslated methods
592       ExportText( aRenderer.GetValue(), is2d, 0, 0, 0, aspect, param, (short)export_h );
593       glTranslated(0, -(GLdouble)fnt->FaceSize(), 0);
594     }
595 #endif
596   }
597   else
598   {
599     for ( ; aRenderer.More(); aRenderer.Next() )
600     {
601       mgr->render_text( myFont, aRenderer.GetValue(), is2d );
602       glTranslated(0, -(GLdouble)fnt->FaceSize(), 0);
603     }
604   }
605   glPopMatrix();
606   glPopAttrib();
607 }
608
609 /*-----------------------------------------------------------------------------*/
610
611 void OpenGl_Display::ExportText (const wchar_t* text, const int is2d, const float x, const float y, const float z,
612                                 const OpenGl_AspectText *aspect, const OpenGl_TextParam *param, const short height)
613 {
614 #ifdef HAVE_GL2PS
615
616   const char* fontname = aspect->Font();
617   float angle = aspect->Angle();
618
619   GLubyte zero = 0;
620   char ps_font[64];
621
622   getGL2PSFontName(fontname, ps_font);
623
624   if( is2d )
625     glRasterPos2f( x, y );
626   else
627     glRasterPos3f( x, y, z );
628
629   glBitmap( 1, 1, 0, 0, 0, 0, &zero );
630
631   //szv: workaround for gl2ps!
632   const int len = 4 * (wcslen(text) + 1); //szv: should be more than enough
633   char *astr = new char[len];
634   wcstombs(astr,text,len);
635
636   // Standard GL2PS's alignment isn't used, because it doesn't work correctly
637   // for all formats, therefore alignment is calculated manually relative
638   // to the bottom-left corner, which corresponds to the GL2PS_TEXT_BL value
639   gl2psTextOpt(astr, ps_font, height, GL2PS_TEXT_BL, angle);
640   delete[] astr;
641
642 #endif
643 }