00001 #include "OSDLFont.h"
00002
00003 #include "OSDLSurface.h"
00004 #include "OSDLPixel.h"
00005
00006 #include "SDL_gfxPrimitives.h"
00007
00008 #include "SDL_ttf.h"
00009
00010 #include <list>
00011
00012
00013 #ifdef OSDL_USES_CONFIG_H
00014 #include <OSDLConfig.h>
00015 #endif // OSDL_USES_CONFIG_H
00016
00017
00018 using std::list ;
00019 using std::pair ;
00020
00021 using std::string ;
00022
00023
00024 using namespace Ceylan ;
00025 using namespace Ceylan::Log ;
00026
00027 using namespace OSDL::Video::TwoDimensional ;
00028
00029
00030
00031 string Text::Font::FontPathEnvironmentVariable = "FONT_PATH" ;
00032
00033
00034 Ceylan::System::FileLocator Text::Font::FontFileLocator(
00035 FontPathEnvironmentVariable ) ;
00036
00037
00038
00039 TextException::TextException( const string & reason ) throw() :
00040 VideoException( "TextException : " + reason )
00041 {
00042
00043 }
00044
00045
00046 TextException::~TextException() throw()
00047 {
00048
00049 }
00050
00051
00052
00053
00054 using namespace OSDL::Video::TwoDimensional::Text ;
00055
00056
00057
00058
00059
00060
00061
00062 const RenderingStyle Font::Normal = TTF_STYLE_NORMAL ;
00063 const RenderingStyle Font::Bold = TTF_STYLE_BOLD ;
00064 const RenderingStyle Font::Italic = TTF_STYLE_ITALIC ;
00065 const RenderingStyle Font::Underline = TTF_STYLE_UNDERLINE ;
00066
00067
00068 const Ceylan::System::Size Font::DefaultGlyphCachedQuota = 4 * 1024 * 1024 ;
00069 const Ceylan::System::Size Font::DefaultWordCachedQuota = 6 * 1024 * 1024 ;
00070 const Ceylan::System::Size Font::DefaultTextCachedQuota = 8 * 1024 * 1024 ;
00071
00072 const Ceylan::Uint8 Font::DefaultSpaceBasedAlineaWidth = 6 ;
00073
00074
00075
00076 Font::Font(
00077 bool convertToDisplay,
00078 RenderCache cacheSettings,
00079 AllowedCachePolicy cachePolicy,
00080 Ceylan::System::Size quota
00081 ) throw() :
00082 _renderingStyle( Normal ),
00083 _convertToDisplay( convertToDisplay ),
00084 _cacheSettings( cacheSettings ),
00085 _glyphCache( 0 ),
00086 _textCache( 0 ),
00087 _backgroundColor( Pixels::Black ),
00088 _spaceWidth( 0 ),
00089 _alineaWidth( 0 )
00090 {
00091
00092
00093
00094
00095
00096
00097
00098
00099 switch( _cacheSettings )
00100 {
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114
00115
00116 case None:
00117
00118 #if OSDL_DEBUG_FONT
00119 LogPlug::debug( "Font created with no rendering cache." ) ;
00120 #endif // OSDL_DEBUG_FONT
00121
00122
00123 break ;
00124
00125
00126 case GlyphCached:
00127
00128 #if OSDL_DEBUG_FONT
00129 LogPlug::debug( "Font created with glyph rendering cache." ) ;
00130 #endif // OSDL_DEBUG_FONT
00131
00132 if ( quota == 0 )
00133 quota = DefaultGlyphCachedQuota ;
00134 SmartResourceManager<CharColorQualityKey>::CachePolicy
00135 actualGlyphPolicy ;
00136
00137
00138
00139
00140
00141
00142 switch( cachePolicy )
00143 {
00144 case NeverDrop:
00145 actualGlyphPolicy =
00146 SmartResourceManager<CharColorQualityKey>::NeverDrop ;
00147 break ;
00148
00149 case DropLessRequestedFirst:
00150 actualGlyphPolicy =
00151 SmartResourceManager<CharColorQualityKey>::DropLessRequestedFirst ;
00152 break ;
00153
00154 default:
00155 Ceylan::emergencyShutdown(
00156 "OSDL::Video::TwoDimensional::Font constructor "
00157 "with glych cache : forbidden cache settings" ) ;
00158
00159 break ;
00160 }
00161
00162 _glyphCache = new SmartResourceManager<CharColorQualityKey>( quota,
00163 actualGlyphPolicy ) ;
00164 break ;
00165
00166
00167 case WordCached:
00168
00169 #if OSDL_DEBUG_FONT
00170 LogPlug::debug( "Font created with word rendering cache." ) ;
00171 #endif // OSDL_DEBUG_FONT
00172
00173 if ( quota == 0 )
00174 quota = DefaultWordCachedQuota ;
00175
00176 SmartResourceManager<StringColorQualityKey>::CachePolicy
00177 actualWordPolicy ;
00178
00179
00180
00181
00182
00183
00184 switch( cachePolicy )
00185 {
00186
00187 case NeverDrop:
00188 actualWordPolicy =
00189 SmartResourceManager<StringColorQualityKey>::NeverDrop ;
00190 break ;
00191
00192 case DropLessRequestedFirst:
00193 actualWordPolicy =
00194 SmartResourceManager<StringColorQualityKey>::DropLessRequestedFirst ;
00195 break ;
00196
00197 default:
00198 Ceylan::emergencyShutdown(
00199 "OSDL::Video::TwoDimensional::Font constructor "
00200 "with word cache : forbidden cache settings" ) ;
00201
00202 break ;
00203 }
00204
00205 _textCache = new SmartResourceManager<StringColorQualityKey>(
00206 quota, actualWordPolicy ) ;
00207 break ;
00208
00209
00210 case TextCached:
00211
00212 #if OSDL_DEBUG_FONT
00213 LogPlug::debug( "Font created with text rendering cache." ) ;
00214 #endif // OSDL_DEBUG_FONT
00215
00216 if ( quota == 0 )
00217 quota = DefaultTextCachedQuota ;
00218
00219 SmartResourceManager<StringColorQualityKey>::CachePolicy
00220 actualTextPolicy ;
00221
00222
00223
00224
00225
00226
00227 switch( cachePolicy )
00228 {
00229 case NeverDrop:
00230 actualTextPolicy =
00231 SmartResourceManager<StringColorQualityKey>::NeverDrop ;
00232 break ;
00233
00234 case DropLessRequestedFirst:
00235 actualTextPolicy =
00236 SmartResourceManager<StringColorQualityKey>::DropLessRequestedFirst ;
00237 break ;
00238
00239 default:
00240 Ceylan::emergencyShutdown(
00241 "OSDL::Video::TwoDimensional::Font constructor "
00242 "with text cache : forbidden cache settings" ) ;
00243 break ;
00244 }
00245
00246 _textCache = new SmartResourceManager<StringColorQualityKey>(
00247 quota, actualTextPolicy ) ;
00248 break ;
00249
00250
00251 default:
00252 Ceylan::emergencyShutdown(
00253 "OSDL::Video::TwoDimensional::Font constructor : "
00254 "unexpected cache settings" ) ;
00255 break ;
00256
00257 }
00258
00259
00260
00261 }
00262
00263
00264
00265 Font::~Font() throw()
00266 {
00267
00268 if ( _glyphCache != 0 )
00269 delete _glyphCache ;
00270
00271 if ( _textCache != 0 )
00272 delete _textCache ;
00273
00274 }
00275
00276
00277
00278 RenderingStyle Font::getRenderingStyle() const throw()
00279 {
00280
00281 return _renderingStyle ;
00282
00283 }
00284
00285
00286
00287 void Font::setRenderingStyle( RenderingStyle newStyle ) throw( TextException )
00288 {
00289
00290 _renderingStyle = newStyle ;
00291
00292 }
00293
00294
00295
00296
00297 void Font::setBackgroundColor( Pixels::ColorDefinition newBackgroundColor )
00298 throw()
00299 {
00300
00301 _backgroundColor = newBackgroundColor ;
00302
00303 }
00304
00305
00306 OSDL::Video::Pixels::ColorDefinition Font::getBackgroundColor() const throw()
00307 {
00308
00309 return _backgroundColor ;
00310
00311 }
00312
00313
00314
00315 Width Font::getAlineaWidth() const throw()
00316
00317 {
00318 return _alineaWidth ;
00319
00320 }
00321
00322
00323 void Font::setAlineaWidth( Width newAlineaWidth ) throw()
00324 {
00325
00326 _alineaWidth = newAlineaWidth ;
00327
00328 }
00329
00330
00331
00332 std::string Font::describeGlyphFor( Ceylan::Latin1Char character ) const throw()
00333 {
00334
00335 list<string> res ;
00336
00337 res.push_back( "Advance is "
00338 + Ceylan::toString( getAdvance( character ) ) ) ;
00339
00340 res.push_back( "Width is " + Ceylan::toString( getWidth( character ) ) ) ;
00341
00342 res.push_back( "Width offset is "
00343 + Ceylan::toString( getWidthOffset( character ) ) ) ;
00344
00345 res.push_back( "Height above baseline is "
00346 + Ceylan::toString( getHeightAboveBaseline( character ) ) ) ;
00347
00348 return "Informations about the glyph corresponding to the character '"
00349 + Ceylan::toString( character ) + "' : "
00350 + Ceylan::formatStringList( res ) ;
00351
00352 }
00353
00354
00355
00356 Width Font::getInterGlyphWidth() const throw()
00357 {
00358
00359
00360 static Width inter = static_cast<Width>(
00361 Ceylan::Maths::Max<float>( 1, 0.1 * getWidth( 'a' ) ) ) ;
00362
00363 return inter ;
00364
00365 }
00366
00367
00368
00369 OSDL::Video::Surface & Font::renderLatin1Text( const std::string & text,
00370 RenderQuality quality, Pixels::ColorDefinition textColor )
00371 throw( TextException )
00372 {
00373
00374
00375
00376
00377 switch( _cacheSettings )
00378 {
00379
00380 case None:
00381 case GlyphCached:
00382
00383
00384
00385
00386
00387
00388
00389 return basicRenderLatin1Text( text, quality, textColor ) ;
00390 break ;
00391
00392
00393 case WordCached:
00394 return renderLatin1TextWithWordCached( text, quality, textColor ) ;
00395 break ;
00396
00397
00398 case TextCached:
00399 return renderLatin1TextWithTextCached( text, quality, textColor ) ;
00400 break ;
00401
00402
00403 default:
00404 Ceylan::emergencyShutdown(
00405 "OSDL::Video::TwoDimensional::Font::renderLatin1Text : "
00406 "unexpected cache settings" ) ;
00407 break ;
00408
00409 }
00410
00411
00412 throw TextException( "Font::renderLatin1Text : unexpected end of method" ) ;
00413
00414 }
00415
00416
00417
00418 void Font::blitLatin1Text( Surface & targetSurface, Coordinate x, Coordinate y,
00419 const std::string & text, RenderQuality quality,
00420 Pixels::ColorDefinition textColor ) throw( TextException )
00421 {
00422
00423
00424
00425
00426
00427
00428
00429
00430
00431
00432 if ( _cacheSettings == TextCached )
00433 {
00434
00435 StringColorQualityKey renderKey( text, textColor, quality ) ;
00436
00437 const Resource * res = _textCache->get( renderKey ) ;
00438
00439
00440 if ( res != 0 )
00441 {
00442
00443 const Surface * toReturn = dynamic_cast<const Surface *>( res ) ;
00444
00445 #if OSDL_DEBUG_FONT
00446
00447 LogPlug::debug( "Font::blitLatin1Text : cache hit, "
00448 "returning clone of prerendered text." ) ;
00449
00450 if ( toReturn == 0 )
00451 Ceylan::emergencyShutdown( "Font::blitLatin1Text : "
00452 "clone is not a Surface." ) ;
00453
00454 #endif // OSDL_DEBUG_FONT
00455
00456 toReturn->blitTo( targetSurface, x, y ) ;
00457
00458 return ;
00459
00460 }
00461
00462 }
00463
00464
00465
00466
00467
00468
00469
00470 Surface & res = renderLatin1Text( text, quality, textColor ) ;
00471
00472
00473
00474
00475 try
00476 {
00477 res.blitTo( targetSurface, x, y ) ;
00478 }
00479 catch( const VideoException & e )
00480 {
00481
00482 throw TextException( "Font::blitLatin1Text : blit failed : "
00483 + e.toString() ) ;
00484 }
00485
00486 delete & res ;
00487
00488 }
00489
00490
00491
00492 OSDL::Video::Surface & Font::renderLatin1MultiLineText(
00493 Length width, Length height, const std::string & text,
00494 TextIndex & renderIndex, Coordinate & lastOrdinateUsed,
00495 RenderQuality quality, Pixels::ColorDefinition textColor, bool justified )
00496 throw( TextException )
00497 {
00498
00499 ColorMask redMask, greenMask, blueMask ;
00500
00501 Pixels::getRecommendedColorMasks( redMask, greenMask, blueMask ) ;
00502
00503 Surface & res = * new Surface( Surface::Hardware | Surface::ColorkeyBlit,
00504 width, height, 32, redMask, greenMask, blueMask,
00505 0 ) ;
00506
00507
00508
00509
00510
00511
00512
00513
00514 Pixels::ColorDefinition colorKey ;
00515
00516 if ( Pixels::areEqual( textColor, Pixels::Black, false ) )
00517 {
00518 colorKey = Pixels::White ;
00519 res.fill( colorKey ) ;
00520 }
00521 else
00522 {
00523
00524 colorKey = Pixels::Black ;
00525
00526
00527
00528
00529
00530
00531 }
00532
00533 Length lineSkip = getLineSkip() ;
00534
00535
00536
00537
00538
00539
00540 LineNumber maxLines = height / lineSkip ;
00541
00542 if ( maxLines == 0 )
00543 throw TextException( "Font::renderLatin1MultiLineText : box height ("
00544 + Ceylan::toString( height )
00545 + ") is not enough even for one line of text, whose height is "
00546 + Ceylan::toString( lineSkip ) + "." ) ;
00547
00548 renderIndex = 0 ;
00549
00550 #if OSDL_DEBUG_FONT
00551
00552 LogPlug::debug( "Font::renderLatin1MultiLineText : "
00553 + Ceylan::toString( maxLines )
00554 + " line(s) available to render following text : '" + text + "'." ) ;
00555
00556 #endif // OSDL_DEBUG_FONT
00557
00558
00559
00560
00561
00562
00563
00564
00565
00566
00567
00568 Height lineHeight = 0 ;
00569
00570
00571 list<string> words ;
00572
00573 string currentWord ;
00574
00575 Length currentWidth, storedWidth ;
00576
00577 Width wordWidth ;
00578 const Surface * wordSurface ;
00579
00580 Width totalWordWidth ;
00581
00582 list<string> wordsOnTheLine ;
00583
00584 bool lineFull ;
00585
00586
00587
00588
00589
00590
00591
00592
00593
00594
00595
00596
00597
00598
00599
00600
00601
00602
00603 bool createTemporaryWordCache =
00604 ( _cacheSettings == GlyphCached || _cacheSettings == None ) ;
00605
00606 if ( createTemporaryWordCache )
00607 {
00608
00609 #if OSDL_DEBUG_FONT
00610
00611 if ( _textCache != 0 )
00612 throw TextException( "Font::renderLatin1MultiLineText : "
00613 "unable to create temporary word cache
00614 "since already existing." ) ;
00615
00616 LogPlug::debug( "Font::renderLatin1MultiLineText : "
00617 "creating a temporary word cache" ) ;
00618
00619 #endif // OSDL_DEBUG_FONT
00620
00621 _textCache = new SmartResourceManager<StringColorQualityKey>(
00622 DefaultTextCachedQuota,
00623 SmartResourceManager<StringColorQualityKey>::DropLessRequestedFirst ) ;
00624
00625 }
00626
00627 /*
00628 * Hence in all cases we can rely on having a word cache
00629 * (even though it starts empty).
00630 *
00631 */
00632
00633 list<string> paragraphs = Ceylan::splitIntoParagraphs( text ) ;
00634
00635 words = Ceylan::splitIntoWords( paragraphs.front() ) ;
00636 paragraphs.pop_front() ;
00637 currentWidth = _alineaWidth ;
00638
00639
00640 // Here we use a (word) cache, and we draw the lines one by one :
00641 for ( TextIndex currentLine = 0; currentLine < maxLines; currentLine++ )
00642 {
00643
00644 // Save current width for later restore :
00645 storedWidth = currentWidth ;
00646
00647 /*
00648 * Start from the left edge, and select as many words as possible
00649 * within this line :
00650 *
00651 */
00652 totalWordWidth = 0 ;
00653
00654 lineFull = false ;
00655 wordsOnTheLine.clear() ;
00656
00657 while ( ! words.empty() && ! lineFull )
00658 {
00659
00660 currentWord = words.front() ;
00661
00662 // Multiple whitespaces in a row can lead to empty words :
00663 if ( currentWord.empty() )
00664 {
00665 wordSurface = 0 ;
00666 wordWidth = 0 ;
00667 }
00668 else
00669 {
00670 wordSurface = & getConstLatin1WordFromCache( currentWord,
00671 quality, textColor ) ;
00672 wordWidth = wordSurface->getWidth() ;
00673 }
00674
00675 if ( currentWidth + wordWidth <= width )
00676 {
00677
00678 // Word accepted for this line :
00679 renderIndex += currentWord.size() + /* trailing space */ 1 ;
00680 totalWordWidth += wordWidth ;
00681
00682 if ( justified )
00683 wordsOnTheLine.push_back( currentWord ) ;
00684 else
00685 if ( wordSurface != 0 )
00686 wordSurface->blitTo( res, currentWidth, lineHeight ) ;
00687
00688 currentWidth += wordWidth + _spaceWidth ;
00689 words.pop_front() ;
00690 }
00691 else
00692 {
00693 // With this last word, the line would be too long :
00694 lineFull = true ;
00695 }
00696
00697 }
00698
00699 // Words are selected for the current line, now time to render them.
00700
00701 System::Size wordCount = wordsOnTheLine.size() ;
00702 currentWidth = storedWidth ;
00703
00704 /*
00705 * Last part of a paragraph should not be justified : it would
00706 * result in huge inter-word spaces, instead the text can stop
00707 * anywhere before the line's end.
00708 * Hence we check that 'words' is not empty.
00709 *
00710 * Zero word or only one word ? Do nothing special to justify text.
00711 *
00712 */
00713 if ( justified && ! words.empty() && wordCount > 1 )
00714 {
00715
00716 for ( list<string>::const_iterator it = wordsOnTheLine.begin();
00717 it != wordsOnTheLine.end(); it++ )
00718 {
00719
00720 if ( (*it ).empty() )
00721 {
00722 wordWidth = 0 ;
00723 }
00724 else
00725 {
00726
00727 wordSurface = & getConstLatin1WordFromCache( (*it),
00728 quality, textColor ) ;
00729
00730 wordSurface->blitTo( res, currentWidth, lineHeight ) ;
00731
00732 wordWidth = wordSurface->getWidth() ;
00733
00734 }
00735
00736 /*
00737 * For justified text, space width is computed with
00738 * pixel-perfect accuracy.
00739 * Knowing exactly what words fit with normal space width,
00740 * a new space width is computed so that these words are
00741 * dispatched regularly on the line, and begin and end with
00742 * it, provided it is not a paragraph end.
00743 *
00744 * As this space width has to be an integer, round off errors
00745 * would accumulate if a constant corrected space width
00746 * was used, and the last word of the line would not end
00747 * perfectly at the end of it, which would lead to a rather
00748 * unpleasant visual effect : the right edge of the text
00749 * would not be vertically aligned.
00750 *
00751 * To correct that, after each word the perfect space width
00752 * for this step is computed, considering only what remains
00753 * to be written.
00754 * Hence the space width is adapted and the text fit
00755 * perfectly on the line.
00756 *
00757 * Better round to lowest integer (ceil or static_cast) than
00758 * to nearest, since if space width is rounded up (floor)
00759 * the text might be clipped by the line edge.
00760 *
00761 * Number of spaces is equal to number of remaining words
00762 * minus one, the width of the current justified space is
00763 * the one that would be chosen if it was divided equally
00764 * for all remaining spaces.
00765 *
00766 */
00767 wordCount-- ;
00768
00769 currentWidth += wordWidth + /* justified space */
00770 static_cast<Width>( Maths::Round(
00771 static_cast<Ceylan::Float32>(
00772 width - currentWidth - totalWordWidth )
00773 / wordCount ) ) ;
00774
00775 totalWordWidth -= wordWidth ;
00776
00777 }
00778
00779 }
00780 else
00781 {
00782
00783 // We do not justify text here :
00784
00785 for ( list<string>::const_iterator it = wordsOnTheLine.begin();
00786 it != wordsOnTheLine.end(); it++ )
00787 {
00788
00789 if ( ! (*it).empty() )
00790 {
00791
00792 wordSurface = & getConstLatin1WordFromCache( (*it),
00793 quality, textColor ) ;
00794 wordSurface->blitTo( res, currentWidth, lineHeight ) ;
00795 currentWidth += wordSurface->getWidth() + _spaceWidth ;
00796
00797 }
00798
00799 }
00800
00801 }
00802
00803 lineHeight += lineSkip ;
00804
00805
00806 if ( words.empty() )
00807 {
00808
00809 if ( paragraphs.empty() )
00810 break ;
00811 words = Ceylan::splitIntoWords( paragraphs.front() ) ;
00812 paragraphs.pop_front() ;
00813
00814 // One empty line between paragraphs :
00815 currentLine++ ;
00816 lineHeight += lineSkip ;
00817 currentWidth = _alineaWidth ;
00818
00819 }
00820 else
00821 {
00822 currentWidth = 0 ;
00823 }
00824
00825 }
00826
00827
00828 if ( createTemporaryWordCache )
00829 {
00830
00831
00832 #if OSDL_DEBUG_FONT
00833
00834 LogPlug::debug( "Font::renderLatin1MultiLineText : "
00835 "deleting temporary word cache : " + _textCache->toString() ) ;
00836
00837 #endif // OSDL_DEBUG_FONT
00838
00839 delete _textCache ;
00840 _textCache = 0 ;
00841
00842 }
00843
00844
00845 // No ordinate beyond container height should be retured :
00846 lastOrdinateUsed = lineHeight ;
00847
00848 // To inspect justified text :
00849 //res.drawEdges() ;
00850
00851 #if OSDL_DEBUG_FONT
00852
00853 if ( renderIndex == text.size() )
00854 LogPlug::debug(
00855 "Font::renderLatin1MultiLineText : full text fit in box." ) ;
00856 else
00857 LogPlug::debug( "Font::renderLatin1MultiLineText : only "
00858 + Ceylan::toString( renderIndex ) + " characters out of "
00859 + Ceylan::toString( text.size() )
00860 + " characters of the full text could be rendered in the box." ) ;
00861
00862 #endif // OSDL_DEBUG_FONT
00863
00864
00865 /*
00866 * Comment out following two lines to see blit blocks
00867 * (as black rectangles):
00868 *
00869 */
00870 res.setColorKey( Surface::ColorkeyBlit | Surface::RLEColorkeyBlit,
00871 convertColorDefinitionToPixelColor( res.getPixelFormat(),
00872 colorKey ) ) ;
00873
00874
00875 if ( _convertToDisplay )
00876 {
00877
00878 /*
00879 * We want to keep our colorkey, so we do not choose to add alpha.
00880 * Surface will be RLE encoded here :
00881 *
00882 */
00883 res.convertToDisplay( /* alphaChannelWanted */ false ) ;
00884 }
00885
00886 return res ;
00887
00888 }
00889
00890
00891
00892 void Font::blitLatin1MultiLineText( Surface & targetSurface,
00893 const UprightRectangle & clientArea, const std::string & text,
00894 TextIndex & renderIndex, RenderQuality quality,
00895 Pixels::ColorDefinition textColor, bool justified )
00896 throw( TextException )
00897 {
00898
00899 #if OSDL_DEBUG_FONT
00900
00901 LogPlug::debug( "Font::blitLatin1MultiLineText : rendering multiline text '"
00902 + text + "' on location " + clientArea.toString() + " of target "
00903 + targetSurface.toString( Ceylan::low ) + "." ) ;
00904
00905 #endif // OSDL_DEBUG_FONT
00906
00907 blitLatin1MultiLineText( targetSurface, clientArea.getUpperLeftAbscissa(),
00908 clientArea.getUpperLeftOrdinate(), clientArea.getWidth(),
00909 clientArea.getHeight(), text, renderIndex, quality,
00910 textColor, justified ) ;
00911
00912 }
00913
00914
00915
00916 void Font::blitLatin1MultiLineText( Surface & targetSurface,
00917 Coordinate x, Coordinate y, Length width, Length height,
00918 const std::string & text, TextIndex & renderIndex,
00919 RenderQuality quality, Pixels::ColorDefinition textColor,
00920 bool justified )
00921 throw( TextException )
00922 {
00923
00924 // Not used here :
00925 Coordinate lastOrdinateUsed ;
00926
00927 /*
00928 * Nothing to optimize at this level, user ought cache multiline
00929 * renderings if appropriate.
00930 *
00931 */
00932 Surface * res = & renderLatin1MultiLineText( width, height,
00933 text, renderIndex, lastOrdinateUsed, quality, textColor, justified ) ;
00934
00935 res->blitTo( targetSurface, x, y ) ;
00936
00937 delete res ;
00938
00939 }
00940
00941
00942
00943 const string Font::toString( Ceylan::VerbosityLevels level ) const throw()
00944 {
00945
00946 string res = "Rendering style : " ;
00947
00948 if ( _renderingStyle == Normal )
00949 res += "normal" ;
00950 else
00951 {
00952 std::list<string> listRes ;
00953
00954 if ( _renderingStyle & Bold )
00955 listRes.push_back( "bold" ) ;
00956
00957 if ( _renderingStyle & Italic )
00958 listRes.push_back( "italic" ) ;
00959
00960 if ( _renderingStyle & Underline )
00961 listRes.push_back( "underline" ) ;
00962
00963 res += Ceylan::join( listRes, ", " ) ;
00964
00965 }
00966
00967 res += ". Renderings (and caches if activated) are " ;
00968
00969 if ( ! _convertToDisplay )
00970 res += "not " ;
00971
00972 res += "automatically converted to display. " ;
00973
00974 switch( _cacheSettings )
00975 {
00976
00977 case None:
00978 res += "No render cache used" ;
00979 break ;
00980
00981 case GlyphCached:
00982 res += "Glyph renderings are cached" ;
00983 break ;
00984
00985 case WordCached:
00986 res += "Word renderings are cached" ;
00987 break ;
00988
00989 case TextCached:
00990 res += "Text renderings are cached" ;
00991 break ;
00992
00993 default:
00994 res += "Unknown policy for render cache (abnormal)" ;
00995 break ;
00996
00997 }
00998
00999
01000 if ( level == Ceylan::low )
01001 return res ;
01002
01003
01004 // Cache pointers should be ok, hence are not tested beforehand :
01005 switch( _cacheSettings )
01006 {
01007
01008 case None:
01009 break ;
01010
01011 case GlyphCached:
01012 res += ". Glyph cache state is : "
01013 + _glyphCache->toString( level ) ;
01014 break ;
01015
01016 case WordCached:
01017 res += ". Word cache state is : " + _textCache->toString( level ) ;
01018 break ;
01019
01020 case TextCached:
01021 res += ". Text cache state is : " + _textCache->toString( level ) ;
01022 break ;
01023
01024 default:
01025 break ;
01026
01027 }
01028
01029 return res ;
01030
01031 }
01032
01033
01034
01035 string Font::InterpretRenderingStyle( RenderingStyle style ) throw()
01036 {
01037
01038 if ( style == Normal )
01039 return "normal" ;
01040
01041 std::list<string> res ;
01042
01043 if ( style & Bold )
01044 res.push_back( "bold" ) ;
01045
01046 if ( style & Italic )
01047 res.push_back( "italic" ) ;
01048
01049 if ( style & Underline )
01050 res.push_back( "underline" ) ;
01051
01052 return Ceylan::join( res, ", " ) ;
01053
01054 }
01055
01056
01057
01058 OSDL::Video::Surface & Font::renderLatin1TextWithWordCached(
01059 const string & text, RenderQuality quality,
01060 Pixels::ColorDefinition textColor ) throw( TextException )
01061 {
01062
01063 /*
01064 * The difficulty here is that we cannot know the total width a priori.
01065 *
01066 * Two passes are therefore needed : one to know the size and create
01067 * the overall surface, the other to blit words onto it.
01068 *
01069 */
01070
01071 // Splits text into a list of words :
01072 list<string> words = Ceylan::split( text, ' ' ) ;
01073
01074
01075 Length currentWidth = 0 ;
01076
01077 #if OSDL_DEBUG_FONT
01078
01079 LogPlug::debug( "Font::renderLatin1TextWithWordCached : will render '"
01080 + text + "', space width is " + Ceylan::toString( _spaceWidth ) ) ;
01081
01082 #endif // OSDL_DEBUG_FONT
01083
01084 const Surface * wordRendered ;
01085
01086 /*
01087 * First iteration : feeds the cache with word renderings and
01088 * computes the total width :
01089 *
01090 */
01091 for ( list<string>::const_iterator it = words.begin();
01092 it != words.end(); it++ )
01093 {
01094
01095 if ( (*it).empty() )
01096 {
01097
01098 #if OSDL_DEBUG_FONT
01099
01100 LogPlug::debug(
01101 "Font::renderLatin1TextWithWordCached : jumping a space." ) ;
01102
01103 #endif // OSDL_DEBUG_FONT
01104
01105 currentWidth += _spaceWidth ;
01106 continue ;
01107 }
01108
01109 #if OSDL_DEBUG_FONT
01110
01111 LogPlug::debug( "Font::renderLatin1TextWithWordCached : examining '"
01112 + (*it) + "'." ) ;
01113
01114 #endif // OSDL_DEBUG_FONT
01115
01116 /*
01117 * Note that this method is used in the 'word cached' case.
01118 * In all cases the returned 'const Surface' is still owned
01119 * by the cache.
01120 *
01121 */
01122 wordRendered = & getConstLatin1WordFromCache( (*it), quality,
01123 textColor ) ;
01124 currentWidth += wordRendered->getWidth() + _spaceWidth ;
01125
01126 }
01127
01128
01129 // We have the final width, let's create the overall surface :
01130
01131 ColorMask redMask, greenMask, blueMask ;
01132 Pixels::getRecommendedColorMasks( redMask, greenMask, blueMask ) ;
01133
01134 Surface & res = * new Surface( Surface::Hardware | Surface::ColorkeyBlit,
01135 currentWidth, getLineSkip() - getDescent(), 32,
01136 redMask, greenMask, blueMask, /* no alpha wanted */ 0 ) ;
01137
01138 // Avoid messing text color with color key :
01139 Pixels::ColorDefinition colorKey ;
01140
01141 if ( Pixels::areEqual( textColor, Pixels::Black, /* use alpha */ false ) )
01142 {
01143
01144 colorKey = Pixels::White ;
01145 res.fill( colorKey ) ;
01146
01147 }
01148 else
01149 {
01150
01151 colorKey = Pixels::Black ;
01152 /*
01153 * No need to fill 'res' with black, since new RGB surfaces
01154 * come all black already.
01155 *
01156 */
01157
01158 }
01159
01160 currentWidth = 0 ;
01161
01162 /*
01163 * Second iteration : blit the word renderings.
01164 *
01165 * Surfaces from first iteration could have been stored to save
01166 * the efforts needed to find them in cache, but depending on the
01167 * cache quota and policy, it cannot be assumed they are
01168 * are all still available (the last could make the first go out).
01169 *
01170 */
01171 for ( list<string>::const_iterator it = words.begin();
01172 it != words.end(); it++ )
01173 {
01174
01175 if ( (*it).empty() )
01176 {
01177 currentWidth += _spaceWidth ;
01178 continue ;
01179 }
01180
01181 #if OSDL_DEBUG_FONT
01182
01183 LogPlug::debug( "Font::renderLatin1TextWithWordCached : blitting '"
01184 + (*it) + "'." ) ;
01185
01186 #endif // OSDL_DEBUG_FONT
01187
01188 wordRendered = & getConstLatin1WordFromCache( (*it),
01189 quality, textColor ) ;
01190 wordRendered->blitTo( res, currentWidth, 0 ) ;
01191 currentWidth += wordRendered->getWidth() + _spaceWidth ;
01192
01193 }
01194
01195
01196 // Comment out following two lines to see blit blocks (as black rectangles):
01197 res.setColorKey( Surface::ColorkeyBlit | Surface::RLEColorkeyBlit,
01198 convertColorDefinitionToPixelColor( res.getPixelFormat(), colorKey ) ) ;
01199
01200 /*
01201 * Uncomment next line to debug computation of bounding boxes for
01202 * renderings :
01203 *
01204 */
01205 //res.drawEdges() ;
01206
01207 if ( _convertToDisplay )
01208 {
01209
01210 /*
01211 * We want to keep our colorkey, so we do not choose to add alpha.
01212 * Surface will be RLE encoded here :
01213 *
01214 */
01215 res.convertToDisplay( /* alphaChannelWanted */ false ) ;
01216 }
01217
01218 return res ;
01219
01220 }
01221
01222
01223
01224 OSDL::Video::Surface & Font::renderLatin1TextWithTextCached(
01225 const string & text, RenderQuality quality,
01226 Pixels::ColorDefinition textColor ) throw( TextException )
01227 {
01228
01229 #if OSDL_DEBUG_FONT
01230
01231 LogPlug::trace( "Font::renderLatin1TextWithTextCached" ) ;
01232
01233 #endif // OSDL_DEBUG_FONT
01234
01235 /*
01236 * First check that the text-quality-color combination is not already
01237 * available in cache :
01238 *
01239 */
01240
01241 StringColorQualityKey renderKey( text, textColor, quality ) ;
01242
01243 SmartResource * res = _textCache->getClone( renderKey ) ;
01244
01245
01246 if ( res != 0 )
01247 {
01248
01249 Surface * returned = dynamic_cast<Surface *>( res ) ;
01250
01251 #if OSDL_DEBUG_FONT
01252
01253 LogPlug::debug( "Font::renderLatin1TextWithTextCached : cache hit, "
01254 "returning clone of prerendered text." ) ;
01255
01256 if ( returned == 0 )
01257 Ceylan::emergencyShutdown( "Font::renderLatin1TextWithTextCached : "
01258 "clone is not a Surface." ) ;
01259
01260 #endif // OSDL_DEBUG_FONT
01261
01262 return * returned ;
01263
01264 }
01265
01266 #if OSDL_DEBUG_FONT
01267 LogPlug::debug( "Font::renderLatin1TextWithTextCached : "
01268 "cache miss, creating new text rendering." ) ;
01269 #endif // OSDL_DEBUG_FONT
01270
01271 // Here it its a cache miss, we therefore have to generate the text :
01272 Surface & newSurface = basicRenderLatin1Text( text, quality, textColor ) ;
01273
01274 // Give the cache a chance of being fed :
01275 _textCache->scanForAddition( renderKey, newSurface ) ;
01276
01277 return newSurface ;
01278
01279 }
01280
01281
01282
01283 void Font::blitLatin1Word( Surface & targetSurface, Coordinate x, Coordinate y,
01284 const std::string & word, RenderQuality quality,
01285 Pixels::ColorDefinition wordColor ) throw( TextException )
01286 {
01287
01288
01289 /*
01290 * We do not expect a given word to be already in cache if text-cached :
01291 * OSDL_WORD_LOOKUP_IN_TEXT_CACHE is not defined by default.
01292 *
01293 * Hence there are two different cases : either we are word-cached,
01294 * and we may have a rendering in cache, or not.
01295 *
01296 */
01297
01298 /*
01299 * Uncomment to search in text cache too
01300 * (not recommended since not more efficient) :
01301 *
01302 */
01303 //#define OSDL_WORD_LOOKUP_IN_TEXT_CACHE
01304
01305 #ifdef OSDL_WORD_LOOKUP_IN_TEXT_CACHE
01306 if ( _cacheSettings == WordCached || _cacheSettings == TextCached )
01307 #else // OSDL_WORD_LOOKUP_IN_TEXT_CACHE
01308 if ( _cacheSettings == WordCached )
01309 #endif // OSDL_WORD_LOOKUP_IN_TEXT_CACHE
01310 {
01311
01312 getConstLatin1WordFromCache( word, quality, wordColor ).blitTo(
01313 targetSurface, x, y ) ;
01314
01315 }
01316 else
01317 {
01318 // Here we cannot cache words, we blit it as directly as possible :
01319 basicRenderLatin1Text( word, quality, wordColor ).blitTo(
01320 targetSurface, x, y ) ;
01321 }
01322
01323 }
01324
01325
01326 /*
01327 * This method cannot exist since the caller should never have to deallocate
01328 * the returned surface :
01329 *
01330 const OSDL::Video::Surface & Font::getConstRenderingForLatin1Word(
01331 const std::string & word,
01332 RenderQuality quality, Pixels::ColorDefinition wordColor )
01333 throw( TextException )
01334 {
01335
01336 //
01337
01338 if ( _cacheSettings == WordCached || _cacheSettings == TextCached )
01339 {
01340 return getConstLatin1WordFromCache( word, quality, wordColor ) ;
01341 }
01342 else
01343 {
01344
01345 }
01346
01347 }
01348 */
01349
01350
01351 const OSDL::Video::Surface & Font::getConstLatin1WordFromCache(
01352 const std::string & word, RenderQuality quality,
01353 Pixels::ColorDefinition wordColor ) throw( TextException )
01354 {
01355
01356 // If not in cache, render it and put it in.
01357
01358 StringColorQualityKey renderKey( word, wordColor, quality ) ;
01359
01360 const Resource * inCache = _textCache->get( renderKey ) ;
01361
01362 if ( inCache != 0 )
01363 {
01364
01365 // Found in cache !
01366 const Surface * wordSurface = dynamic_cast<const Surface *>( inCache ) ;
01367
01368 #if OSDL_DEBUG_FONT
01369
01370 LogPlug::debug( "Font::getConstLatin1WordFromCache : "
01371 "cache hit for '" + word
01372 + "', returning 'const' prerendered word." ) ;
01373
01374 if ( wordSurface == 0 )
01375 Ceylan::emergencyShutdown( "Font::getConstLatin1WordFromCache : "
01376 "cache did not return a Surface." ) ;
01377
01378 #endif // OSDL_DEBUG_FONT
01379
01380 return * wordSurface ;
01381
01382 }
01383
01384 #if OSDL_DEBUG_FONT
01385
01386 LogPlug::debug( "Font::getConstLatin1WordFromCache : "
01387 "cache miss for '" + word
01388 + "', rendering it and submitting it to cache." ) ;
01389
01390 #endif // OSDL_DEBUG_FONT
01391
01392 // Here we know the word rendering is not in cache, we need to put it in :
01393 Surface & wordSurface = basicRenderLatin1Text( word, quality, wordColor ) ;
01394
01395 try
01396 {
01397 _textCache->takeOwnershipOf( renderKey, wordSurface ) ;
01398 }
01399 catch( const ResourceManagerException & e )
01400 {
01401
01402 /*
01403 * This really should never happen : we know this word rendering
01404 * is not in cache.
01405 *
01406 */
01407 throw TextException( "Font::getConstLatin1WordFromCache : "
01408 "cache submitting failed (abnormal) : " + e.toString() ) ;
01409
01410 }
01411
01412 // Cache still owns that (const) surface :
01413 return wordSurface ;
01414
01415 }
01416
01417
01418
01419 OSDL::Video::Surface & Font::basicRenderLatin1Text( const std::string & text,
01420 RenderQuality quality, Pixels::ColorDefinition textColor )
01421 throw( TextException )
01422 {
01423
01424 Length lineSkip = getLineSkip() ;
01425 Length ascent = getAscent() ;
01426
01427 System::Size textSize = text.size() ;
01428
01429 #if OSDL_DEBUG_FONT
01430
01431 LogPlug::debug( "Font::basicRenderLatin1Text : rendering '"
01432 + text + "'." ) ;
01433
01434 LogPlug::debug( "Font::basicRenderLatin1Text : line skip is "
01435 + Ceylan::toString( lineSkip ) ) ;
01436
01437 LogPlug::debug( "Font::basicRenderLatin1Text : ascent is "
01438 + Ceylan::toString( ascent ) ) ;
01439
01440 #endif // OSDL_DEBUG_FONT
01441
01442 Length * horizSteps = new Length[ textSize ] ;
01443
01444
01445 /*
01446 * When adding a letter y after a letter x, y has to be drawn at :
01447 * abscissa of x plus x's advance, so that if x leaves room in the
01448 * baseline, y can start as left as possible, even if x spreads on the
01449 * right of the position where y starts (ex : if x is a capital 'L' whose
01450 * bottom line goes under the baseline).
01451 *
01452 * However when a letter is the last in a surface, one should use its
01453 $ width instead of its advance, so that the letter is not truncated.
01454 * Moreover, the (n-1) letter might spread more to the right than the
01455 * n one (ex : 'y.', the dot can be placed at the left of the rightmost
01456 * branch of the 'y'), so the best solution is to record max width
01457 * at each step (instead of replacing the advance by the width for the
01458 * last glyph).
01459 *
01460 */
01461 Length width = 0 ;
01462 Length maxWidth = 0 ;
01463 System::Size charCount = 0 ;
01464
01465 bool firstLetter = true ;
01466
01467 Ceylan::Latin1Char currentChar ;
01468 SignedLength currentOffset ;
01469 Length currentAdvance ;
01470
01471 /*
01472 * First iteration : guess width and height of the resulting surface :
01473 * (widths are stored for later use)
01474 *
01475 */
01476 for ( string::const_iterator it = text.begin(); it != text.end(); it++ )
01477 {
01478
01479 currentChar = static_cast<Ceylan::Latin1Char>(*it) ;
01480 currentOffset = getWidthOffset( currentChar ) ;
01481 currentAdvance = getAdvance( currentChar ) ;
01482
01483 /*
01484 * First letters may have a negative offset that should be corrected :
01485 * (otherwise leftmost part of first letter could be truncated)
01486 *
01487 */
01488
01489 if ( firstLetter )
01490 {
01491
01492 firstLetter = false ;
01493
01494 // Uses 0 instead of currentOffset :
01495 horizSteps[charCount] = 0 ;
01496
01497 }
01498 else
01499 {
01500
01501 /*
01502 * The offset corresponds to the abscissa of the leftmost
01503 * part of the glyph in its local referential.
01504 *
01505 */
01506 horizSteps[charCount] = width + currentOffset ;
01507
01508 }
01509
01510 charCount++ ;
01511
01512 #if OSDL_DEBUG_FONT
01513
01514 LogPlug::debug( "Font::basicRenderLatin1Text : adding "
01515 + Ceylan::toString( currentAdvance )
01516 + " width for char '" + Ceylan::toString( currentChar ) + "'" ) ;
01517
01518 #endif // OSDL_DEBUG_FONT
01519
01520 // The new real abscissa than can be written is :
01521 width += currentAdvance ;
01522
01523 /*
01524 * Previously using next addition, which leads to too much space
01525 * between letters and not to compact uppercase letters (ex : 'OSDL')
01526 * whereas they should be
01527 * (see with cursive fonts with really wide uppercase glyphs, such
01528 * as 'cretino.ttf').
01529 *
01530 */
01531 //width += getWidth( currentChar ) + currentOffset ;
01532
01533 }
01534
01535 /*
01536 * Retrieves the rightmost abscissa that could be drawn :
01537 * (character width should be used instead of advance so that its
01538 * rightmost part is not truncated).
01539 *
01540 */
01541 maxWidth = horizSteps[ textSize - 1 ] + currentOffset
01542 + getWidth( currentChar ) ;
01543
01544 #if OSDL_DEBUG_FONT
01545
01546 LogPlug::debug( "Font::basicRenderLatin1Text : text width will be "
01547 + Ceylan::toString( maxWidth )
01548 + ", height will be " + Ceylan::toString( lineSkip ) + "." ) ;
01549
01550 #endif // OSDL_DEBUG_FONT
01551
01552 /*
01553 * Must be filled with glyphs now
01554 * (lineskip is used to avoid complex blits of lines) :
01555 *
01556 */
01557
01558 ColorMask redMask, greenMask, blueMask ;
01559 Pixels::getRecommendedColorMasks( redMask, greenMask, blueMask ) ;
01560
01561 Surface * res ;
01562
01563 try
01564 {
01565 res = new Surface( Surface::Hardware | Surface::ColorkeyBlit,
01566 maxWidth, lineSkip - getDescent(), 32,
01567 redMask, greenMask, blueMask, /* no alpha wanted */ 0 ) ;
01568 }
01569 catch( const VideoException & e )
01570 {
01571 delete [] horizSteps ;
01572 throw TextException(
01573 "Font::basicRenderLatin1Text : surface creation failed : "
01574 + e.toString() ) ;
01575 }
01576
01577 // Avoid messing text color with color key :
01578 Pixels::ColorDefinition colorKey ;
01579
01580 if ( Pixels::areEqual( textColor, Pixels::Black, /* use alpha */ false ) )
01581 {
01582 colorKey = Pixels::White ;
01583 res->fill( colorKey ) ;
01584 }
01585 else
01586 {
01587 colorKey = Pixels::Black ;
01588 /*
01589 * No need to fill 'res' with black, since new RGB surfaces come
01590 * all black already.
01591 *
01592 */
01593 }
01594
01595
01596 // Second pass : actual rendering :
01597 charCount = 0 ;
01598
01599 for ( string::const_iterator it = text.begin(); it != text.end(); it++ )
01600 {
01601 currentChar = static_cast<Ceylan::Latin1Char>(*it) ;
01602
01603 #if OSDL_DEBUG_FONT
01604
01605 LogPlug::debug( "Font::basicRenderLatin1Text : rendering '"
01606 + Ceylan::toString( currentChar )
01607 + "' at width " + Ceylan::toString( horizSteps[charCount] )
01608 + ", at height "
01609 + Ceylan::toString( ascent - getHeightAboveBaseline( currentChar ) )
01610 ) ;
01611
01612 #endif // OSDL_DEBUG_FONT
01613
01614 blitLatin1Glyph( *res, horizSteps[charCount],
01615 ascent - getHeightAboveBaseline( currentChar ),
01616 currentChar, quality, textColor ) ;
01617
01618 charCount++ ;
01619 }
01620
01621 // Comment out following two lines to see blit blocks (as black rectangles):
01622 res->setColorKey( Surface::ColorkeyBlit | Surface::RLEColorkeyBlit,
01623 convertColorDefinitionToPixelColor( res->getPixelFormat(), colorKey )
01624 ) ;
01625
01626 if ( _convertToDisplay )
01627 {
01628
01629 /*
01630 * We want to keep our colorkey, so we do not choose to add alpha.
01631 * Surface will be RLE encoded here :
01632 *
01633 */
01634 res->convertToDisplay( /* alphaChannelWanted */ false ) ;
01635 }
01636
01637 /*
01638 * Uncomment next line to debug computation of bounding boxes for
01639 * renderings :
01640 *
01641 */
01642 //res->drawEdges() ;
01643
01644 delete [] horizSteps ;
01645
01646 return *res ;
01647
01648 }
01649