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