1 // SGText.cxx - Manage text in the scene graph
2 // Copyright (C) 2009 Torsten Dreyer Torsten (_at_) t3r *dot* de
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License as
6 // published by the Free Software Foundation; either version 2 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful, but
10 // WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 // General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 //
18 
19 #ifdef HAVE_CONFIG_H
20 #  include <simgear_config.h>
21 #endif
22 
23 #include <cstdio>
24 
25 #include "SGText.hxx"
26 
27 #include <simgear/math/SGMath.hxx>
28 #include <simgear/misc/sg_path.hxx>
29 #include <simgear/misc/strutils.hxx>
30 
31 #include <osg/Geode>
32 #include <osg/MatrixTransform>
33 #include <osgText/Text>
34 #include <osgText/Font>
35 
36 using std::string;
37 
38 class SGText::UpdateCallback : public osg::NodeCallback {
39 public:
UpdateCallback(osgText::Text * aText,SGConstPropertyNode_ptr aProperty,double aScale,double aOffset,bool aTruncate,bool aNumeric,const char * aFormat)40   UpdateCallback( osgText::Text * aText, SGConstPropertyNode_ptr aProperty, double aScale, double aOffset, bool aTruncate, bool aNumeric, const char * aFormat ) :
41     text( aText ),
42     property( aProperty ),
43     scale( aScale ),
44     offset( aOffset ),
45     truncate( aTruncate ),
46     numeric( aNumeric ),
47     format( simgear::strutils::sanitizePrintfFormat( aFormat ) )
48   {
49     if( format.empty() ) {
50       if( numeric ) format = "%f";
51       else format = "%s";
52     }
53   }
54 
55 private:
56   virtual void operator()(osg::Node * node, osg::NodeVisitor *nv );
57   osgText::Text * text;
58   SGConstPropertyNode_ptr property;
59   double scale;
60   double offset;
61   bool truncate;
62   bool numeric;
63   string format;
64 };
65 
operator ()(osg::Node * node,osg::NodeVisitor * nv)66 void SGText::UpdateCallback::operator()(osg::Node * node, osg::NodeVisitor *nv )
67 {
68   // FIXME:
69   // hopefully the users never specifies bad formats here
70   // this should better be something more robust
71   char buf[256];
72   if( numeric ) {
73     double d = property->getDoubleValue() * scale + offset;
74     if (truncate)  d = (d < 0) ? -floor(-d) : floor(d);
75     snprintf( buf, sizeof(buf)-1, format.c_str(), d );
76   } else {
77     snprintf( buf, sizeof(buf)-1, format.c_str(), property->getStringValue() );
78   }
79   if( text->getText().createUTF8EncodedString().compare( buf )  ) {
80     // be lazy and set the text only if the property has changed.
81     // update() computes the glyph representation which looks
82     // more expensive than a the above string compare.
83     text->setText( buf, osgText::String::ENCODING_UTF8 );
84     text->update();
85   }
86   traverse( node, nv );
87 }
88 
appendText(const SGPropertyNode * configNode,SGPropertyNode * modelRoot,const osgDB::Options * options)89 osg::Node * SGText::appendText(const SGPropertyNode* configNode,
90   SGPropertyNode* modelRoot, const osgDB::Options* options)
91 {
92   SGConstPropertyNode_ptr p;
93 
94   osgText::Text * text = new osgText::Text();
95   osg::Geode * g = new osg::Geode;
96   g->addDrawable( text );
97 
98   SGPath path("Fonts" );
99   path.append( configNode->getStringValue( "font", "Helvetica" ));
100   text->setFont( path.utf8Str() );
101 
102   text->setCharacterSize(configNode->getDoubleValue("character-size", 1.0 ),
103                          configNode->getDoubleValue("character-aspect-ratio", 1.0 ));
104 
105   if( (p = configNode->getNode( "font-resolution" )) != NULL )
106     text->setFontResolution( p->getIntValue( "width", 32 ), p->getIntValue( "height", 32 ) );
107 
108   if( (p = configNode->getNode( "kerning" )) != NULL ) {
109     string kerning = p->getStringValue();
110     if( kerning.compare( "default" ) == 0 ) {
111       text->setKerningType( osgText::KERNING_DEFAULT );
112     } else if( kerning.compare( "unfitted" ) == 0 ) {
113       text->setKerningType( osgText::KERNING_UNFITTED );
114     } else if( kerning.compare( "none" ) == 0 ) {
115       text->setKerningType( osgText::KERNING_NONE );
116     } else {
117       SG_LOG(SG_GENERAL, SG_ALERT, "ignoring unknown kerning'" << kerning << "'." );
118     }
119   }
120 
121   if( ( p = configNode->getNode( "axis-alignment" )) != NULL ) {
122     string axisAlignment = p->getStringValue();
123     if( axisAlignment.compare( "xy-plane" ) == 0 ) {
124       text->setAxisAlignment( osgText::Text::XY_PLANE );
125     } else if( axisAlignment.compare( "reversed-xy-plane" ) == 0 ) {
126       text->setAxisAlignment( osgText::Text::REVERSED_XY_PLANE );
127     } else if( axisAlignment.compare( "xz-plane" ) == 0 ) {
128       text->setAxisAlignment( osgText::Text::XZ_PLANE );
129     } else if( axisAlignment.compare( "reversed-xz-plane" ) == 0 ) {
130       text->setAxisAlignment( osgText::Text::REVERSED_XZ_PLANE );
131     } else if( axisAlignment.compare( "yz-plane" ) == 0 ) {
132       text->setAxisAlignment( osgText::Text::YZ_PLANE );
133     } else if( axisAlignment.compare( "reversed-yz-plane" ) == 0 ) {
134       text->setAxisAlignment( osgText::Text::REVERSED_YZ_PLANE );
135     } else if( axisAlignment.compare( "screen" ) == 0 ) {
136       text->setAxisAlignment( osgText::Text::SCREEN );
137     } else {
138       SG_LOG(SG_GENERAL, SG_ALERT, "ignoring unknown axis-alignment'" << axisAlignment << "'." );
139     }
140   }
141 
142   unsigned drawMode = osgText::Text::TEXT;
143   if( (p = configNode->getNode( "draw-text" )) != NULL && p->getBoolValue() == false )
144     drawMode &= ~ osgText::Text::TEXT;
145 
146   if( (p = configNode->getNode( "draw-alignment" )) != NULL && p->getBoolValue() == true )
147     drawMode |= osgText::Text::ALIGNMENT;
148 
149   if( (p = configNode->getNode( "draw-boundingbox" )) != NULL && p->getBoolValue() == true )
150     drawMode |= osgText::Text::BOUNDINGBOX;
151 
152   text->setDrawMode( drawMode );
153 
154   if( (p = configNode->getNode( "alignment" )) != NULL ) {
155     string alignment = p->getStringValue();
156     if( alignment.compare( "left-top" ) == 0 ) {
157       text->setAlignment( osgText::Text::LEFT_TOP );
158     } else if( alignment.compare( "left-center" ) == 0 ) {
159       text->setAlignment( osgText::Text::LEFT_CENTER );
160     } else if( alignment.compare( "left-bottom" ) == 0 ) {
161       text->setAlignment( osgText::Text::LEFT_BOTTOM );
162     } else if( alignment.compare( "center-top" ) == 0 ) {
163       text->setAlignment( osgText::Text::CENTER_TOP );
164     } else if( alignment.compare( "center-center" ) == 0 ) {
165       text->setAlignment( osgText::Text::CENTER_CENTER );
166     } else if( alignment.compare( "center-bottom" ) == 0 ) {
167       text->setAlignment( osgText::Text::CENTER_BOTTOM );
168     } else if( alignment.compare( "right-top" ) == 0 ) {
169       text->setAlignment( osgText::Text::RIGHT_TOP );
170     } else if( alignment.compare( "right-center" ) == 0 ) {
171       text->setAlignment( osgText::Text::RIGHT_CENTER );
172     } else if( alignment.compare( "right-bottom" ) == 0 ) {
173       text->setAlignment( osgText::Text::RIGHT_BOTTOM );
174     } else if( alignment.compare( "left-baseline" ) == 0 ) {
175       text->setAlignment( osgText::Text::LEFT_BASE_LINE );
176     } else if( alignment.compare( "center-baseline" ) == 0 ) {
177       text->setAlignment( osgText::Text::CENTER_BASE_LINE );
178     } else if( alignment.compare( "right-baseline" ) == 0 ) {
179       text->setAlignment( osgText::Text::RIGHT_BASE_LINE );
180     } else if( alignment.compare( "baseline" ) == 0 ) {
181       text->setAlignment( osgText::Text::BASE_LINE );
182     } else {
183       SG_LOG(SG_GENERAL, SG_ALERT, "ignoring unknown text-alignment '" << alignment <<"'." );
184     }
185   }
186 
187   if( (p = configNode->getNode( "layout" )) != NULL ) {
188     string layout = p->getStringValue();
189     if( layout.compare( "left-to-right" ) == 0 ) {
190       text->setLayout( osgText::Text::LEFT_TO_RIGHT );
191     } else if( layout.compare( "right-to-left" ) == 0 ) {
192       text->setLayout( osgText::Text::RIGHT_TO_LEFT );
193     } else if( layout.compare( "vertical" ) == 0 ) {
194       text->setLayout( osgText::Text::VERTICAL );
195     } else {
196       SG_LOG(SG_GENERAL, SG_ALERT, "ignoring unknown layout '" << layout <<"'." );
197     }
198   }
199 
200   if( (p = configNode->getNode( "max-width" )) != NULL )
201     text->setMaximumWidth( p->getDoubleValue() );
202 
203   if( (p = configNode->getNode( "max-height" )) != NULL )
204     text->setMaximumHeight( p->getDoubleValue() );
205 
206   // FIXME: Should we support <chunks><chunk/><chunk/></chunks> here?
207   string type = configNode->getStringValue( "type", "literal" );
208   if( type == "literal" ) {
209     text->setText( configNode->getStringValue( "text", "" ) );
210   } else {
211     SGConstPropertyNode_ptr property = modelRoot->getNode( configNode->getStringValue( "property", "foo" ), true );
212     const char * format = configNode->getStringValue( "format", "" );
213     double scale = configNode->getDoubleValue( "scale", 1.0 );
214     double offset = configNode->getDoubleValue( "offset", 0.0 );
215     bool   truncate = configNode->getBoolValue( "truncate", false );
216 
217     if( (p = configNode->getNode( "property")) != NULL ) {
218       p = modelRoot->getNode( p->getStringValue(), true );
219       UpdateCallback * uc = new UpdateCallback( text, property, scale, offset, truncate, type == "number-value", format );
220       g->setUpdateCallback( uc );
221     }
222   }
223 
224   osg::Node * reply = NULL;
225   if( (p = configNode->getNode( "offsets")) == NULL ) {
226     reply = g;
227   } else {
228     // Set up the alignment node ("stolen" from animation.cxx)
229     // XXX Order of rotations is probably not correct.
230     osg::MatrixTransform *align = new osg::MatrixTransform;
231     osg::Matrix res_matrix;
232     res_matrix.makeRotate(
233         p->getFloatValue("pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
234         osg::Vec3(0, 1, 0),
235         p->getFloatValue("roll-deg", 0.0)*SG_DEGREES_TO_RADIANS,
236         osg::Vec3(1, 0, 0),
237         p->getFloatValue("heading-deg", 0.0)*SG_DEGREES_TO_RADIANS,
238         osg::Vec3(0, 0, 1));
239 
240     osg::Matrix tmat;
241     tmat.makeTranslate(configNode->getFloatValue("offsets/x-m", 0.0),
242                        configNode->getFloatValue("offsets/y-m", 0.0),
243                        configNode->getFloatValue("offsets/z-m", 0.0));
244 
245     align->setMatrix(res_matrix * tmat);
246     align->addChild( g );
247     reply = align;
248   }
249 
250   if( (p = configNode->getNode( "name" )) != NULL )
251     reply->setName(p->getStringValue());
252   else
253     reply->setName("text");
254   return reply;
255 }
256