Vegastrike 0.5.1 rc1  1.0
Original sources for Vegastrike Evolved
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
painttext.cpp
Go to the documentation of this file.
1 /*
2  * Vega Strike
3  * Copyright (C) 2003 Mike Byron
4  *
5  * http://vegastrike.sourceforge.net/
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */
21 
22 #include "vegastrike.h"
23 
24 #include "painttext.h"
25 
26 #include "vs_globals.h"
27 #include "config_xml.h"
28 #include "gldrv/gl_globals.h"
29 const size_t PaintText::END_LINE = 1000000; //Draw to the end.
30 extern bool useStroke();
31 //This function allows a number of formatting characters. Here are the rules:
32 //-- The formatting char is "#".
33 //-- Format commands are indicated by a single character, which is case-sensitive.
34 //-- The results of errors in the formatting commands is not defined.
35 //-- Commands can take no parameter, a required param, or an optional param.
36 //-- Commands that take parameters always end with another "#".
37 //-- Some kinds of format state are pushed onto a stack. You can pop the stack back
38 //to the previous value, or reset to the original value.
39 //##: One "#". "This is door ##3."
40 //#n[line-space]#: Break a line here. Optional floating-point parameter which is the
41 //line spacing between the current line and the next line only. (This doesn't
42 //affect line spacing after the current line.)
43 //0.0, for instance, would results in overwriting the same line, 2.0 is double-space.
44 //"First line.#n#Second line." "Double space#n2#line#n#line 2".
45 //#l<line-spacing>#: Set the amount of space between lines. Parameter is not optional.
46 //This command does not break the line, it just sets the line spacing for the
47 //following lines. There isn't a stack associated with this setting.
48 //#b[stroke-weight]#: "Bold". "Push" the font stroke weight. Parameter has same semantics
49 //as Font object -- floating-point. Default stroke weight is BOLD_STROKE.
50 //"#b#Price:#-b $50,000" "I must #b4.5#emphasize#!b this."
51 //#c<color-spec>#: "Push" a new text color (doesn't change the background color).
52 //Specify R, then B, G, A as floating-point values between 0 and 1.0 (same spec as
53 //GFXColor). The value are separated by ":", and A is optional. Default for A
54 //is 1.0.
55 //"This is #c1:0:0#red#-c."
56 //#-{bc}: "Pop" a font/color/ off the appropriate stack. Restores previous value.
57 //See font and color examples above.
58 //#!{bc}: "Reset" font/color to the original version. Same parameters as "#-".
59 
60 //Old format characters that need to be converted.
61 static const char OLD_FORMAT_NEWLINE = '\\';
62 
63 //Formatting characters for PaintText() strings.
64 static const char DT_FORMAT_CHAR = '#';
65 
66 static const char DT_FORMAT_NEWLINE_CHAR = 'n'; //Break line. Optional line spacing.
67 static const char DT_FORMAT_LINE_SPACING_CHAR = 'l'; //Set new line spacing.
68 static const char DT_FORMAT_STROKE_CHAR = 'b'; //Push new stroke width. Optional width.
69 static const char DT_FORMAT_COLOR_CHAR = 'c'; //Push new color.
70 static const char DT_FORMAT_POP_CHAR = '-'; //Pop color/stroke.
71 static const char DT_FORMAT_RESET_CHAR = '!'; //Reset to original color/stroke.
72 
73 static const char DT_FORMAT_COLOR_SEP = ':'; //Separator in color specification.
74 
75 static const float BOGUS_LINE_SPACING = -100.0; //"NULL" value for line spacing.
76 
77 //The ellipsis at the end of a line is represented as a special text fragment. We mark
78 //the fragment with this constant as the start position.
79 static const string::size_type ELLIPSIS_FRAGMENT = 64; //@
80 static const string ELLIPSIS_STRING = "...";
81 
82 //This object compares vertical distances to see whether lines fit in rectangles.
83 //These are floating-point calculations, and have some error involved.
84 //Below is a small factor of the "next" line height we use when comparing to see whether
85 //that line will fit in a vertical interval.
86 static const float LINE_HEIGHT_EPSILON = .99; //1% of the line height.
87 
88 //Have a reserve for the lines array so we don't have a lot of copying.
89 static const int LINES_RESERVE = 50;
90 
91 //The outside boundaries to use for drawing.
92 void PaintText::setRect( const Rect &r )
93 {
94  if (m_rect != r) {
95  m_rect = r;
96  m_needLayout = true;
97  }
98 }
99 
100 //The text to draw.
101 void PaintText::setText( const std::string &text )
102 {
103  const string *finalText = &text;
104  string conversionResult;
105  if (text.find( OLD_FORMAT_NEWLINE ) != string::npos) {
106  //We have some old format characters we need to convert.
107  for (string::const_iterator i = text.begin(); i != text.end(); i++) {
108  if (*i == OLD_FORMAT_NEWLINE)
109  conversionResult.append( "#n#" );
110  else
111  conversionResult += (*i);
112  }
113  finalText = &conversionResult;
114  }
115  //OK, now we see whether anything has changed.
116  if (m_text != *finalText) {
117  m_text = *finalText;
118  m_needLayout = true;
119  }
120 }
121 
122 //The initial color of the text.
124 {
125  if ( !equalColors( m_color, c ) ) {
126  m_color = c;
127  m_needLayout = true;
128  }
129 }
130 
131 //The initial Font for text.
132 void PaintText::setFont( const Font &f )
133 {
134  if (m_font != f) {
135  m_font = f;
136  m_needLayout = true;
137  }
138 }
139 
140 //Text justification.
142 {
143  if (m_justification != j) {
144  m_justification = j;
145  m_needLayout = true;
146  }
147 }
148 
149 //What to do when text width exceeds boundary rectangle.
151 {
152  if (m_widthExceeded != w) {
153  m_widthExceeded = w;
154  m_needLayout = true;
155  }
156 }
157 
158 //How many lines are in the current layout.
159 int PaintText::lineCount( void ) const
160 {
162  return m_lines.size();
163 }
164 
165 //How many lines would be painted in a vertical interval.
166 int PaintText::visibleLineCountStartingWith( int lineNumber, float vertInterval ) const
167 {
169  int result = 0;
170  float currentHeight = vertInterval;
171  for (vector< TextLine >::size_type i = lineNumber; i < m_lines.size(); ++i) {
172  const float lineHeight = m_lines[i].height;
173  if (currentHeight-lineHeight*LINE_HEIGHT_EPSILON < 0.0)
174  //Did all the lines.
175  break;
176  currentHeight -= lineHeight;
177  result++;
178  }
179  return result;
180 }
181 
182 //Layout version. This is used to tell whether the layout has changed.
183 int PaintText::layoutVersion( void ) const
184 {
186  return m_layoutVersion;
187 }
188 
189 //Check whether we need to recalc the layout, and do it in const object.
191 {
192  if (m_needLayout) {
193  //calcLayout is a cache. Doesn't change the "real" state of the object.
194  PaintText *s = const_cast< PaintText* > (this);
195  s->calcLayout();
196  }
197 }
198 
199 //Draw a fragment of text. Assumes graphics origin and scaling are correct.
200 static float drawChars( const string &str, int start, int end, const Font &font, const GFXColor &color, float inRasterPos )
201 {
202  //Make sure the graphics state is right.
203  glColor4f( color.r, color.g, color.b, color.a );
204  if ( useStroke() ) {
205  glLineWidth( font.strokeWidth() );
206  } else {
207  static bool setRasterPos = XMLSupport::parse_bool( vs_config->getVariable( "graphics", "set_raster_text_color", "true" ) );
208  if (setRasterPos)
209  glRasterPos2f( inRasterPos/(g_game.x_resolution/2), 0 );
210  }
211  //Draw all the characters.
212  for (int charPos = start; charPos <= end; charPos++)
213  inRasterPos += font.drawChar( str[charPos] );
214  return inRasterPos;
215 }
216 
217 //Draw specified lines of text.
218 void PaintText::drawLines( size_t start, size_t count ) const
219 {
220  //Make sure we hav a display list.
222  //Make sure we have something to do.
223  if ( m_lines.empty() )
224  return;
225  //Initialize the graphics state.
226  GFXToggleTexture( false, 0 );
228  glEnable( GL_LINE_SMOOTH );
231  glPushMatrix();
232  //Keep track of line position.
233  float lineTop = m_rect.top();
234  //Figure ending line index.
235  const size_t end = guiMin( start+count, m_lines.size() );
236  //Loop through the display list lines.
237  for (size_t i = start; i < end; i++) {
238  const TextLine &line = m_lines[i];
239  //Make sure we can paint this line in the vertical space we have left.
240  if (lineTop-line.height*LINE_HEIGHT_EPSILON < m_rect.origin.y)
241  //Not enough space to draw this line.
242  break;
243  //Position at the start of the line.
244  glLoadIdentity();
245  glTranslatef( m_rect.origin.x+line.x, lineTop-line.baseLine, 0.0 );
246  if ( line.fragments.size() )
247  GFXColorf( line.fragments[0].color );
248  if ( !useStroke() )
249  glRasterPos2f( 0, 0 );
250  else
251  glScaled( m_horizontalScaling, m_verticalScaling, 1.0 );
252  float rasterpos = 0;
253  //Draw each fragment.
254  for (vector< TextFragment >::const_iterator frag = line.fragments.begin(); frag != line.fragments.end(); frag++) {
255  if (frag->start == ELLIPSIS_FRAGMENT)
256  //We have a special-case for the ellipsis at the end of a line.
257  drawChars( ELLIPSIS_STRING, 0, 2, frag->font, frag->color, rasterpos );
258  else
259  rasterpos = drawChars( m_text, frag->start, frag->end, frag->font, frag->color, rasterpos );
260  }
261  //Top of next line.
262  lineTop -= line.height;
263  }
264  glRasterPos2f( 0, 0 );
265  //Undo graphics state
266  GFXPopBlendMode();
268  glDisable( GL_LINE_SMOOTH );
269  glPopMatrix();
270  GFXToggleTexture( true, 0 );
271 }
272 
273 //Get a floating-point argument for a PaintText format command.
274 //This will not accept exponential format, just num-plus-decimal.
275 //The argument is optional, but must be ended with a format char.
276 //Examples: #b2.35#, #b#. #b is not allowed.
277 static void parseFormatFloat( const std::string &str, //String.
278  const string::size_type startPos, //First character to examine.
279  const string::size_type endPos, //One past last char to consider.
280  bool *formatSuccess, //OUT: True = It worked.
281  float *resultValue, //OUT: Parsed value. If no value, not changed.
282  string::size_type *resultPos, //OUT: One past last format char.
283  const char optionalTerminator = '\0' //Another terminator besides DT_FORMAT_CHAR
284  )
285 {
286  *formatSuccess = false;
287  std::string num;
288  string::size_type curPos;
289  for (curPos = startPos; curPos < endPos; curPos++) {
290  const char c = str[curPos];
291  if (c == DT_FORMAT_CHAR || c == optionalTerminator) {
292  //Found the trailing end of the format string. Done.
293  *formatSuccess = true;
294  break;
295  }
296  //We only take digits and period, so we only parse simple floating numbers.
297  //We'll take comma for simple localization purposes.
298  if (isdigit( c ) || c == '.' || c == ',')
299  num += c;
300  else
301  //Found a bad character. Stop.
302  break;
303  }
304  *resultPos = curPos+1; //Skip over these chars no matter what.
305  if (formatSuccess && num.size() > 0) {
306  //Convert string to float.
307  //Can't figure out std::locale, so we'll use easy, dumb conversion.
308  *resultValue = atof( num.c_str() );
309  }
310 }
311 
312 //Get a color argument for a PaintText format command.
313 //Format is R:B:G:A, where A is optional. The numbers should be between 0 and 1.
314 //This will not accept exponential format, just num-plus-decimal.
315 //Examples: #c1:.5:0.3:1.0#, #c.5:.5:.5#
316 static void parseFormatColor( const string &str, //String.
317  const string::size_type startPos, //First character to examine.
318  const string::size_type endPos, //One past last char to consider.
319  bool *formatSuccess, //OUT: True = It worked.
320  GFXColor &color, //OUT: Parsed value.
321  string::size_type *resultPos //OUT: One past last format char.
322  )
323 {
324  *formatSuccess = false;
325  string::size_type curPos = startPos;
326  parseFormatFloat( str, curPos, endPos, formatSuccess, &color.r, &curPos, DT_FORMAT_COLOR_SEP );
327  if (!formatSuccess || str[curPos-1] == DT_FORMAT_CHAR) return;
328  parseFormatFloat( str, curPos, endPos, formatSuccess, &color.g, &curPos, DT_FORMAT_COLOR_SEP );
329  if (!formatSuccess || str[curPos-1] == DT_FORMAT_CHAR) return;
330  parseFormatFloat( str, curPos, endPos, formatSuccess, &color.b, &curPos, DT_FORMAT_COLOR_SEP );
331  if (!formatSuccess) return;
332  if (str[curPos-1] != DT_FORMAT_CHAR)
333  //Not done -- still have alpha to do.
334  parseFormatFloat( str, curPos, endPos, formatSuccess, &color.a, &curPos );
335  else
336  //Default alpha value is opaque.
337  color.a = 1.0;
338  *resultPos = curPos;
339 }
340 
341 //Parse a format string in a PaintText string.
342 //The first character should be the one *after* the initial format char.
343 void PaintText::parseFormat( string::size_type startPos, //Location of beginning of string to examine.
344  string::size_type *resultPos, //OUT: Ptr to string past the format string.
345  bool *endLine //OUT: True = Done with current line.
346  )
347 {
348  const string::size_type endPos = m_text.size();
349  //Default return value.
350  *endLine = false;
351  bool formatSuccess = false;
352  string::size_type curPos = startPos;
353  if (curPos < endPos) {
354  //Make sure we have some chars to process.
355  switch (m_text[curPos])
356  {
358  //End of line.
359  {
360  float value = BOGUS_LINE_SPACING; //Bogus value.
361  parseFormatFloat( m_text, curPos+1, endPos, &formatSuccess, &value, &curPos );
362  if (formatSuccess) {
363  *endLine = true; //End of this line.
364  if (value != BOGUS_LINE_SPACING)
366  }
367  break;
368  }
370  //New permanent line spacing.
371  {
372  float value = BOGUS_LINE_SPACING; //Bogus value.
373  parseFormatFloat( m_text, curPos+1, endPos, &formatSuccess, &value, &curPos );
374  if (formatSuccess && value != BOGUS_LINE_SPACING)
376  break;
377  }
379  //"Bold" -- change stroke width of font.
380  {
381  float strokeWeight = BOLD_STROKE;
382  parseFormatFloat( m_text, curPos+1, endPos, &formatSuccess, &strokeWeight, &curPos );
383  if (formatSuccess) {
384  Font f( m_layout.fontStack.back() ); //Make a new font.
385  f.setStrokeWeight( strokeWeight );
386  m_layout.fontStack.push_back( f );
387  }
388  break;
389  }
391  //Change the text color.
392  {
393  GFXColor color;
394  parseFormatColor( m_text, curPos+1, endPos, &formatSuccess, color, &curPos );
395  if (formatSuccess)
396  m_layout.colorStack.push_back( color );
397  break;
398  }
399  case DT_FORMAT_POP_CHAR:
400  //Pop a color/font.
401  curPos++;
402  if (curPos == endPos) {
403  *endLine = true;
404  } else if (m_text[curPos] == DT_FORMAT_STROKE_CHAR) {
405  if (m_layout.fontStack.size() > 1) m_layout.fontStack.pop_back();
406  else if (m_text[curPos] == DT_FORMAT_COLOR_CHAR)
407  if (m_layout.colorStack.size() > 1) m_layout.colorStack.pop_back();
408  }
409  curPos++;
410  break;
412  //Reset colors/fonts back to original. Pops all off stack except bottom.
413  curPos++;
414  if (curPos == endPos)
415  *endLine = true;
416  else if (m_text[curPos] == DT_FORMAT_STROKE_CHAR)
417  while (m_layout.fontStack.size() > 1)
418  m_layout.fontStack.pop_back();
419  else if (m_text[curPos] == DT_FORMAT_COLOR_CHAR)
420  while (m_layout.colorStack.size() > 1)
421  m_layout.colorStack.pop_back();
422  curPos++;
423  break;
424  }
425  }
426  *resultPos = curPos;
427 }
428 
429 //Create a fragment for the next substring of characters that fits in the specified width.
430 //The fragment is added to the specified TextLine.
431 //Formatting commands should have been filtered out already.
432 void PaintText::addFragment( TextLine &line, //Line descriptor.
433  const string::size_type endPos, //One past last char to consider.
434  string::size_type &startPos, //IN/OUT: location of string.
435  double &width //IN/OUT: Reference width of string.
436  )
437 {
438  string::size_type curPos = startPos;
439  const Font &font = m_layout.fontStack.back();
440  //Loop through the characters until we run out of room.
441  while (curPos < endPos) {
442  double charWidth = font.charWidth( m_text[curPos] );
443  if (width-charWidth < 0.0)
444  //The current character goes past the specified width.
445  break;
446  width -= charWidth;
447  curPos++;
448  }
449  //Create the fragment.
450  if (curPos > startPos) {
451  TextFragment frag;
452  frag.start = startPos;
453  frag.end = curPos-1; //Last char in frag, not one past.
454  frag.font = font;
455  frag.color = m_layout.colorStack.back();
456  line.fragments.push_back( frag );
457  }
458  startPos = curPos;
459 }
460 
461 //Return whether a character qualifies as a word break.
462 static bool isWordBreak( char c )
463 {
464  return c == ' ';
465 }
466 
467 //Parse one line of text, create fragments, end line when overflow width.
468 void PaintText::parseFragmentsWithCharBreak( TextLine &line, //Line descriptor.
469  string::size_type startPos, //Location of beginning of string to examine.
470  string::size_type endPos, //Location of one past the last character to examine.
471  float maxWidth, //Can't go beyond this width.
472  bool ellipsis, //True = if line doesn't fit, append ellipsis.
473  string::size_type *resultPos //OUT: Ptr to string past the format string.
474  )
475 {
476  string::size_type curPos = startPos; //Beginning of current part of the string we are working on.
477  double curWidth = maxWidth; //The width left to work with.
478  bool forceEndLine = false; //True = end-of-line through format. False = char width.
479  while (curPos < endPos) {
480  //Is there a format char left in this string?
481  const string::size_type formatPos = m_text.find( DT_FORMAT_CHAR, curPos );
482  if (formatPos == std::string::npos || formatPos >= endPos) {
483  //No format char.
484  addFragment( line, endPos, curPos, curWidth );
485  break;
486  }
487  //Create fragment for characters before the format char.
488  addFragment( line, formatPos, curPos, curWidth );
489  if (curPos < formatPos)
490  //Format is past the max width. We're done with this line.
491  break;
492  //Interpret the format command.
493  assert( m_text[curPos] == DT_FORMAT_CHAR );
494  curPos++; //Look at the command char.
495  if (curPos >= endPos) {
496  //No command char. String ends with single "#". Ignore it.
497  curPos = endPos-1;
498  break;
499  }
500  if (m_text[curPos] == DT_FORMAT_CHAR) {
501  //Double format char. Equals one format char.
502  const string::size_type oldPos = curPos;
503  addFragment( line, curPos+1, curPos, curWidth );
504  if (curPos == oldPos) {
505  //No room in the line for the one char. Leave it for next line.
506  curPos--; //Put chars back in string.
507  break; //End of this line.
508  }
509  } else {
510  parseFormat( curPos, &curPos, &forceEndLine );
511  if (forceEndLine)
512  break;
513  }
514  }
515  if (!forceEndLine && curPos < endPos) {
516  if (ellipsis) {
517  //We need to append an ellipsis. We didn't use the whole string, and we didn't end
518  //the line because of a format command.
519  //We use the font at the end of the line. This is a hack, but it seems like a
520  //reasonable compromise. If we use the font from the beginning of the line, suppose the
521  //line starts out with bold, followed by a non-bold explanation?
522  //We use the color of the last fragment before the ellipsis.
523 
524  const Font &font = m_layout.fontStack.back();
525  const double ellipsisWidth = font.stringWidth( ELLIPSIS_STRING );
526  //If ellipsis doesn't fit in the original width, just ship the truncated string.
527  if (ellipsisWidth < maxWidth) {
528  while (line.fragments.size() > 0) {
529  TextFragment &frag = line.fragments.back();
530  //Remove enough space in the last fragment to be able to append the ellipsis.
531  string::size_type i;
532  for (i = frag.end; i >= frag.start; i--) {
533  curWidth -= frag.font.charWidth( m_text[i] );
534  if (curWidth+ellipsisWidth <= maxWidth) {
535  //If we back up this far, the ellipsis will fit in the max width.
536  frag.end = i;
537  break;
538  }
539  }
540  if (i < frag.start) {
541  //Used up the whole fragment and still didn't find enough space.
542  line.fragments.pop_back();
543  } else {
544  //Create an ellipsis fragment and append it to the line.
545  TextFragment newFrag = frag;
546  newFrag.start = ELLIPSIS_FRAGMENT;
547  line.fragments.push_back( newFrag );
548  curWidth += ellipsisWidth;
549  break;
550  }
551  }
552  }
553  } else {
554  //Get rid of word break chars at the end of this line.
555  //First, make sure to skip over any white space.
556  while ( curPos < endPos && isWordBreak( m_text[curPos] ) )
557  curPos++;
558  //Now, get rid of any word break chars at the tail end of our fragment list.
559  while (line.fragments.size() > 0) {
560  TextFragment &frag = line.fragments.back();
561  string::size_type i;
562  for (i = frag.end; i >= frag.start; i--) {
563  if ( !isWordBreak( m_text[i] ) )
564  break;
565  curWidth -= frag.font.charWidth( m_text[i] );
566  }
567  if (i >= frag.start)
568  //Found something besides a word break in this fragment.
569  break;
570  //Used up the whole fragment. Start on the next one.
571  line.fragments.pop_back();
572  }
573  }
574  }
575  //Set the width of this line.
576  line.width = maxWidth-curWidth;
577  //And make sure we know how far we got in the string.
578  *resultPos = curPos;
579 }
580 
581 //Parse one line of text, create fragments, end line on word break when width overflows.
582 void PaintText::parseFragmentsWithWordBreak( TextLine &line, //Line descriptor.
583  string::size_type startPos, //Location of beginning of string to examine.
584  float maxWidth, //Can't go beyond this width.
585  string::size_type *resultPos //OUT: Ptr to string past the format string.
586  )
587 {
588  string::size_type curPos = startPos; //Beginning of current part of the string we are working on.
589  double curWidth = maxWidth; //The width left to work with.
590  const string::size_type endPos = m_text.size(); //One past the end of the string.
591  bool forceEndLine = false; //True = end-of-line through format. False = char width.
592  LayoutState origLayout = m_layout; //The original layout state before we start the line.
593  string::size_type wordBreakPos = endPos; //Previous word break location in text.
594  //In this loop we just measure the width. We find the end of the current line in m_text,
595  //then call parseFragmentsWithCharBreak once we know how far to go.
596  while (curPos < endPos) {
597  //Is there a format char left in this string?
598  const string::size_type formatPos = m_text.find( DT_FORMAT_CHAR, curPos );
599  string::size_type endFragPos = formatPos;
600  if (formatPos == std::string::npos || formatPos >= endPos)
601  //No format char.
602  endFragPos = endPos;
603  //Loop through the characters until we run out of room.
604  const Font &font = m_layout.fontStack.back();
605  while (curPos < endFragPos) {
606  double charWidth = font.charWidth( m_text[curPos] );
607  if ( isWordBreak( m_text[curPos] ) )
608  wordBreakPos = curPos;
609  if (curWidth-charWidth < 0.0)
610  //The current character goes past the specified width.
611  break;
612  curWidth -= charWidth;
613  curPos++;
614  }
615  if (curPos == endPos) {
616  //The rest of the text is not as wide as the max. We are done with this pass.
617  wordBreakPos = endPos;
618  break;
619  } else if (curPos < endFragPos) {
620  //We found a last character. Go back to the last word break.
621  break;
622  }
623  assert( curPos == formatPos ); //Other other case: we ran into a format command.
624  //Interpret the format command.
625  assert( m_text[curPos] == DT_FORMAT_CHAR );
626  curPos++; //Look at the command char.
627  if (curPos >= endPos) {
628  //No command char. String ends with single "#". Ignore it.
629  curPos = endPos-1;
630  break;
631  }
632  if (m_text[curPos] == DT_FORMAT_CHAR) {
633  //Double format char. Equals one format char.
634  curWidth -= font.charWidth( DT_FORMAT_CHAR );
635  if (curWidth < 0.0) {
636  //No room in the line for the one char. Leave it for next line.
637  curPos--; //Put chars back in string.
638  break; //End of this line.
639  }
640  } else {
641  parseFormat( curPos, &curPos, &forceEndLine );
642  if (forceEndLine) {
643  wordBreakPos = endPos;
644  break;
645  }
646  }
647  }
648  //Now we need to generate the fragments.
649  //NOTE: If the text contains a word that is too long for a line, we get a special case
650  //where we never find a word break. That ends up calling parseFragmentsWithCharBreak with
651  //the end of the string as the limit, so the word is broken on a char boundary, which is
652  //exactly what we want.
653  string::size_type endLinePos = wordBreakPos+1;
654  //Include all extra word break characters.
655  while ( endLinePos+1 < endPos && isWordBreak( m_text[endLinePos] ) )
656  endLinePos++;
657  m_layout = origLayout; //Undo any format changes.
658  parseFragmentsWithCharBreak( line, startPos, endLinePos, maxWidth, false, resultPos );
659 }
660 
661 //The x-origin of a line. Horizontal starting position.
662 float PaintText::lineInset( const TextLine &line )
663 {
664  float result = 0.0; //Assume we will RIGHT_JUSTIFY.
666  result = (m_rect.size.width-line.width*m_horizontalScaling)/2.0;
667  else if (m_justification == LEFT_JUSTIFY)
668  result = m_rect.size.width-line.width*m_horizontalScaling;
669  return result;
670 }
671 
672 //Use the current attributes to create a display list for the text.
673 //This does the real work, and doesn't check whether it needs to be done.
675 {
676  //Clear the old layout.
677  m_lines.clear();
678  //Make sure the version number changes.
679  m_layoutVersion++;
680  //Make sure we don't call this again unless we need to.
681  m_needLayout = false;
682  if ( m_text.empty() )
683  return;
684  //Scaling factors.
687  //Max line width in character reference space.
688  static float font_width_hack = XMLSupport::parse_float( vs_config->getVariable( "graphics", "font_width_hack", "0.925" ) );
689  const float maxLineWidth = m_rect.size.width*font_width_hack/m_horizontalScaling;
690  //The temporary global state for the layout operation.
691  //Make sure this gets initialized at the beginning of an operation.
692  m_layout = LayoutState( 1.0, 1.0 );
693  //Keep track of switches in fonts/colors.
694  m_layout.fontStack.push_back( m_font );
695  m_layout.colorStack.push_back( m_color );
696  //Create the current line.
697  m_lines.reserve( LINES_RESERVE );
698  m_lines.resize( 1 );
699  TextLine *currentLine = &m_lines.back();
700  if (m_widthExceeded != MULTI_LINE) {
701  //SINGLE LINE.
702  currentLine->height = m_rect.size.height;
703  currentLine->baseLine = ( currentLine->height-m_font.size() )/2.0
705 
706  string::size_type ignorePos = 0;
707  bool ellipsis = (m_widthExceeded == ELLIPSIS);
708  parseFragmentsWithCharBreak( *currentLine, 0, m_text.size(), maxLineWidth, ellipsis, &ignorePos );
709 
710  //Need line width before we can set this.
711  currentLine->x = lineInset( *currentLine );
712  //If we got no fragments, get rid of the line.
713  if ( currentLine->fragments.empty() )
714  m_lines.pop_back();
715  } else {
716  //MULTIPLE LINES.
717  int nextLinePos = 0; //The char loc in m_text of the beginning of the next line.
718  while (true) {
719  //Figure vertical measurements before we parse the line.
720  currentLine->height = m_layout.fontStack.back().size()*m_layout.currentLineSpacing;
721  currentLine->baseLine = currentLine->height-m_verticalScaling*REFERENCE_BASELINE_POS;
722 
723  //Get the first line of chars, including the length.
724  string::size_type endNextLinePos = 0;
725  m_layout.currentLineSpacing = BOGUS_LINE_SPACING; //Line spacing for this line only. Bogus value.
726  parseFragmentsWithWordBreak( *currentLine, nextLinePos, maxLineWidth, &endNextLinePos );
728  //We found no format command for current line spacing. Use permanent spacing.
729  //Need to set this *after* we get the line spacing for this line.
731  }
732  //Horizontal starting position.
733  currentLine->x = lineInset( *currentLine );
734  //See if we're done.
735  if ( endNextLinePos >= m_text.size() )
736  //EXIT FROM LOOP
737  break;
738  //Start of next line.
739  nextLinePos = endNextLinePos;
740  m_lines.resize( m_lines.size()+1 ); //Add a new TextLine.
741  currentLine = &m_lines.back();
742  }
743  }
744 }
745 
746 //Pass in RGB values, get out a color command string for those values.
747 std::string colorsToCommandString( float r, float g, float b, float a )
748 {
749  char buf[256];
750  if (a >= 1.0)
751  //Three-color string.
752  sprintf( buf, "#c%.3g:%.3g:%.3g#", r, g, b );
753  else
754  //Four-color string.
755  sprintf( buf, "#c%.3g:%.3g:%.3g:%.3g#", r, g, b, a );
756  return buf;
757 }
758 
759 //CONSTRUCTION
761  m_rect()
762  , m_text()
763  , m_color( GUI_OPAQUE_BLACK() )
764  , m_font()
765  , m_justification( RIGHT_JUSTIFY )
766  , m_widthExceeded( ELLIPSIS )
767  , m_needLayout( true )
768  , m_layoutVersion( 0 )
769  , m_verticalScaling( 0.7 )
770  , m_horizontalScaling( 0.7 )
771 {
772 }
773 
774 PaintText::PaintText( const Rect &r, const std::string &t, const Font &f, const GFXColor &c, Justification j,
775  WidthExceeded w ) :
776  m_rect( r )
777  , m_text()
778  , //Don't set text here.
779  m_color( c )
780  , m_font( f )
781  , m_justification( j )
782  , m_widthExceeded( w )
783  , m_needLayout( true )
784  , m_layoutVersion( 0 )
785  , m_verticalScaling( 0.7 )
786  , m_horizontalScaling( 0.7 )
787 {
788  setText( t ); //Do conversion if necessary.
789 }
790