1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 #include "ctfonts.hxx" 23 24 // ======================================================================= 25 26 class CTLayout 27 : public SalLayout 28 { 29 public: 30 explicit CTLayout( const CTTextStyle* ); 31 virtual ~CTLayout( void ); 32 33 virtual bool LayoutText( ImplLayoutArgs& ); 34 virtual void AdjustLayout( ImplLayoutArgs& ); 35 virtual void DrawText( SalGraphics& ) const; 36 37 virtual int GetNextGlyphs( int nLen, sal_GlyphId* pOutGlyphIds, Point& rPos, int&, 38 sal_Int32* pGlyphAdvances, int* pCharIndexes ) const; 39 40 virtual long GetTextWidth() const; 41 virtual long FillDXArray( sal_Int32* pDXArray ) const; 42 virtual int GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const; 43 virtual void GetCaretPositions( int nArraySize, sal_Int32* pCaretXArray ) const; 44 virtual bool GetGlyphOutlines( SalGraphics&, PolyPolyVector& ) const; 45 virtual bool GetBoundRect( SalGraphics&, Rectangle& ) const; 46 47 const ImplFontData* GetFallbackFontData( sal_GlyphId ) const; 48 49 virtual void InitFont( void) const; 50 virtual void MoveGlyph( int nStart, long nNewXPos ); 51 virtual void DropGlyph( int nStart ); 52 virtual void Simplify( bool bIsBase ); 53 54 private: 55 // CoreText specific objects 56 CFMutableDictionaryRef mpStyleDict; 57 CFAttributedStringRef mpAttrString; 58 CTLineRef mpCTLine; 59 60 int mnCharCount; // ==mnEndCharPos-mnMinCharPos 61 int mnTrailingSpaceCount; 62 double mfTrailingSpaceWidth; // preserves the width of stripped-off trailing space 63 64 // to prevent overflows 65 // font requests get size limited by downscaling huge fonts 66 // in these cases the font scale becomes something bigger than 1.0 67 float mfFontScale; // TODO: does CoreText have a font size limit? 68 69 CGFloat mfFontRotation; // text direction angle (in radians) 70 CGFloat mfFontStretch; <1.0: font is squeezed, >1.0 font is stretched 71 72 // cached details about the resulting layout 73 // mutable members since these details are all lazy initialized 74 mutable double mfCachedWidth; // cached value of resulting typographical width 75 76 // x-offset relative to layout origin 77 // currently only used in RTL-layouts 78 mutable long mnBaseAdv; 79 }; 80 81 // ======================================================================= 82 83 CTLayout::CTLayout( const CTTextStyle* pTextStyle ) 84 : mpStyleDict( pTextStyle->GetStyleDict() ) 85 , mpAttrString( NULL ) 86 , mpCTLine( NULL ) 87 , mnCharCount( 0 ) 88 , mnTrailingSpaceCount( 0 ) 89 , mfTrailingSpaceWidth( 0.0 ) 90 , mfFontScale( pTextStyle->mfFontScale ) 91 , mfFontRotation( pTextStyle->mfFontRotation ) 92 , mfFontStretch( pTextStyle->mfFontStretch ) 93 , mfCachedWidth( -1 ) 94 , mnBaseAdv( 0 ) 95 { 96 CFRetain( mpStyleDict ); 97 } 98 99 // ----------------------------------------------------------------------- 100 101 CTLayout::~CTLayout() 102 { 103 if( mpCTLine ) 104 CFRelease( mpCTLine ); 105 if( mpAttrString ) 106 CFRelease( mpAttrString ); 107 CFRelease( mpStyleDict ); 108 } 109 110 // ----------------------------------------------------------------------- 111 112 bool CTLayout::LayoutText( ImplLayoutArgs& rArgs ) 113 { 114 if( mpAttrString ) 115 CFRelease( mpAttrString ); 116 mpAttrString = NULL; 117 if( mpCTLine ) 118 CFRelease( mpCTLine ); 119 mpCTLine = NULL; 120 121 SalLayout::AdjustLayout( rArgs ); 122 mnCharCount = mnEndCharPos - mnMinCharPos; 123 124 // short circuit if there is nothing to do 125 if( mnCharCount <= 0 ) 126 return false; 127 128 // prepare the string to be layouted by CoreText 129 CFStringRef aCFText = CFStringCreateWithCharactersNoCopy( NULL, rArgs.mpStr + mnMinCharPos, mnCharCount, kCFAllocatorNull ); 130 // #i124375# force soft-hyphen visibility to meet the expectations of Writer+EditEngine 131 if( CFStringFind( aCFText, (CFStringRef)@"\u00AD", 0).length > 0 ) 132 { 133 NSString* pDashStr = [(NSString*)aCFText stringByReplacingOccurrencesOfString: @"\u00AD" withString: @"-"]; 134 aCFText = CFStringCreateCopy( NULL, (CFStringRef)pDashStr ); 135 } 136 137 // create the CoreText line layout using the requested text style 138 mpAttrString = CFAttributedStringCreate( NULL, aCFText, mpStyleDict ); 139 mpCTLine = CTLineCreateWithAttributedString( mpAttrString ); 140 CFRelease( aCFText); 141 142 // get info about trailing whitespace to prepare for text justification in AdjustLayout() 143 mnTrailingSpaceCount = 0; 144 for( int i = mnEndCharPos; --i >= mnMinCharPos; ++mnTrailingSpaceCount ) 145 if( !IsSpacingGlyph( rArgs.mpStr[i] | GF_ISCHAR ) 146 && (rArgs.mpStr[i] != 0x00A0) ) 147 break; 148 return true; 149 } 150 151 // ----------------------------------------------------------------------- 152 153 void CTLayout::AdjustLayout( ImplLayoutArgs& rArgs ) 154 { 155 if( !mpCTLine) 156 return; 157 158 int nPixelWidth = rArgs.mnLayoutWidth; 159 if( rArgs.mpDXArray ) 160 { 161 // for now we are only interested in the layout width 162 // TODO: use all mpDXArray elements for layouting 163 nPixelWidth = rArgs.mpDXArray[ mnCharCount-1 ]; 164 } 165 else if( !nPixelWidth ) // short-circuit if there is nothing to adjust 166 return; 167 168 // short-circuit when justifying an all-whitespace string 169 if( mnTrailingSpaceCount >= mnCharCount) 170 { 171 mfCachedWidth = nPixelWidth / mfFontScale; 172 return; 173 } 174 175 // return early if there is nothing to do 176 if( nPixelWidth <= 0 ) 177 return; 178 179 // HACK: justification requests which change the width by just one pixel are probably 180 // #i86038# introduced by lossy conversions between integer based coordinate system 181 const int nOrigWidth = GetTextWidth(); 182 if( (nOrigWidth >= nPixelWidth-1) && (nOrigWidth <= nPixelWidth+1) ) 183 return; 184 185 // if the text to be justified has whitespace in it then 186 // - Writer goes crazy with its HalfSpace magic 187 // - CoreText handles spaces specially (in particular at the text end) 188 if( mnTrailingSpaceCount ) { 189 int nTrailingSpaceWidth = 0; 190 if( rArgs.mpDXArray) { 191 const int nFullPixWidth = nPixelWidth; 192 nPixelWidth = rArgs.mpDXArray[ mnCharCount-1-mnTrailingSpaceCount ]; 193 nTrailingSpaceWidth = nFullPixWidth - nPixelWidth; 194 mfTrailingSpaceWidth = nTrailingSpaceWidth; 195 } else { 196 if( mfTrailingSpaceWidth <= 0.0 ) 197 mfTrailingSpaceWidth = CTLineGetTrailingWhitespaceWidth( mpCTLine ); 198 nTrailingSpaceWidth = rint( mfTrailingSpaceWidth ); 199 nPixelWidth -= nTrailingSpaceWidth; 200 } 201 if( nPixelWidth <= 0 ) 202 return; 203 204 // recreate the CoreText line layout without trailing spaces 205 CFRelease( mpCTLine ); 206 CFStringRef aCFText = CFStringCreateWithCharactersNoCopy( NULL, rArgs.mpStr + mnMinCharPos, 207 mnCharCount - mnTrailingSpaceCount, kCFAllocatorNull ); 208 CFAttributedStringRef pAttrStr = CFAttributedStringCreate( NULL, aCFText, mpStyleDict ); 209 mpCTLine = CTLineCreateWithAttributedString( pAttrStr ); 210 CFRelease( aCFText); 211 CFRelease( pAttrStr ); 212 213 // in RTL-layouts trailing spaces are leftmost 214 // TODO: use BiDi-algorithm to thoroughly check this assumption 215 if( rArgs.mnFlags & SAL_LAYOUT_BIDI_RTL) 216 mnBaseAdv = nTrailingSpaceWidth; 217 } 218 219 const double fAdjustedWidth = nPixelWidth / mfFontScale; 220 CTLineRef pNewCTLine = CTLineCreateJustifiedLine( mpCTLine, 1.0, fAdjustedWidth ); 221 if( !pNewCTLine ) { // CTLineCreateJustifiedLine can and does fail 222 // handle failure by keeping the unjustified layout 223 // TODO: a better solution such as 224 // - forcing glyph overlap 225 // - changing the font size 226 // - changing the CTM matrix 227 return; 228 } 229 CFRelease( mpCTLine ); 230 mpCTLine = pNewCTLine; 231 mfCachedWidth = fAdjustedWidth + mfTrailingSpaceWidth; 232 } 233 234 // ----------------------------------------------------------------------- 235 236 void CTLayout::DrawText( SalGraphics& rGraphics ) const 237 { 238 AquaSalGraphics& rAquaGraphics = static_cast<AquaSalGraphics&>(rGraphics); 239 240 // short circuit if there is nothing to do 241 if( (mnCharCount <= 0) 242 || !rAquaGraphics.CheckContext() ) 243 return; 244 245 // the view is vertically flipped => flipped glyphs 246 // so apply a temporary transformation that it flips back 247 // also compensate if the font was size limited 248 CGContextSaveGState( rAquaGraphics.mrContext ); 249 CGContextScaleCTM( rAquaGraphics.mrContext, +mfFontScale, -mfFontScale ); 250 CGContextSetShouldAntialias( rAquaGraphics.mrContext, !rAquaGraphics.mbNonAntialiasedText ); 251 252 // Draw the text 253 const Point aVclPos = GetDrawPosition( Point(mnBaseAdv,0) ); 254 CGPoint aTextPos = { +aVclPos.X()/mfFontScale, -aVclPos.Y()/mfFontScale }; 255 256 if( mfFontRotation != 0.0 ) 257 { 258 CGContextRotateCTM( rAquaGraphics.mrContext, +mfFontRotation ); 259 260 const CGAffineTransform aInvMatrix = CGAffineTransformMakeRotation( -mfFontRotation ); 261 aTextPos = CGPointApplyAffineTransform( aTextPos, aInvMatrix ); 262 } 263 264 CGContextSetTextPosition( rAquaGraphics.mrContext, aTextPos.x, aTextPos.y ); 265 CTLineDraw( mpCTLine, rAquaGraphics.mrContext ); 266 267 // request an update of the changed window area 268 if( rAquaGraphics.IsWindowGraphics() ) 269 { 270 const CGRect aInkRect = CTLineGetImageBounds( mpCTLine, rAquaGraphics.mrContext ); 271 const CGRect aRefreshRect = CGContextConvertRectToDeviceSpace( rAquaGraphics.mrContext, aInkRect ); 272 rAquaGraphics.RefreshRect( aRefreshRect ); 273 } 274 275 // restore the original graphic context transformations 276 CGContextRestoreGState( rAquaGraphics.mrContext ); 277 } 278 279 // ----------------------------------------------------------------------- 280 281 int CTLayout::GetNextGlyphs( int nLen, sal_GlyphId* pOutGlyphIds, Point& rPos, int& nStart, 282 sal_Int32* pGlyphAdvances, int* pCharIndexes ) const 283 { 284 if( !mpCTLine ) 285 return 0; 286 287 if( nStart < 0 ) // first glyph requested? 288 nStart = 0; 289 nLen = 1; // TODO: handle nLen>1 below 290 291 // prepare to iterate over the glyph runs 292 int nCount = 0; 293 int nSubIndex = nStart; 294 295 const DynCoreTextSyms& rCT = DynCoreTextSyms::get(); 296 typedef std::vector<CGGlyph> CGGlyphVector; 297 typedef std::vector<CGPoint> CGPointVector; 298 typedef std::vector<CGSize> CGSizeVector; 299 typedef std::vector<CFIndex> CFIndexVector; 300 CGGlyphVector aCGGlyphVec; 301 CGPointVector aCGPointVec; 302 CGSizeVector aCGSizeVec; 303 CFIndexVector aCFIndexVec; 304 305 // TODO: iterate over cached layout 306 CFArrayRef aGlyphRuns = rCT.LineGetGlyphRuns( mpCTLine ); 307 const int nRunCount = CFArrayGetCount( aGlyphRuns ); 308 for( int nRunIndex = 0; nRunIndex < nRunCount; ++nRunIndex ) { 309 CTRunRef pGlyphRun = (CTRunRef)CFArrayGetValueAtIndex( aGlyphRuns, nRunIndex ); 310 const CFIndex nGlyphsInRun = rCT.RunGetGlyphCount( pGlyphRun ); 311 // skip to the first glyph run of interest 312 if( nSubIndex >= nGlyphsInRun ) { 313 nSubIndex -= nGlyphsInRun; 314 continue; 315 } 316 const CFRange aFullRange = CFRangeMake( 0, nGlyphsInRun ); 317 318 // get glyph run details 319 const CGGlyph* pCGGlyphIdx = rCT.RunGetGlyphsPtr( pGlyphRun ); 320 if( !pCGGlyphIdx ) { 321 aCGGlyphVec.reserve( nGlyphsInRun ); 322 CTRunGetGlyphs( pGlyphRun, aFullRange, &aCGGlyphVec[0] ); 323 pCGGlyphIdx = &aCGGlyphVec[0]; 324 } 325 const CGPoint* pCGGlyphPos = rCT.RunGetPositionsPtr( pGlyphRun ); 326 if( !pCGGlyphPos ) { 327 aCGPointVec.reserve( nGlyphsInRun ); 328 CTRunGetPositions( pGlyphRun, aFullRange, &aCGPointVec[0] ); 329 pCGGlyphPos = &aCGPointVec[0]; 330 } 331 332 const CGSize* pCGGlyphAdvs = NULL; 333 if( pGlyphAdvances) { 334 pCGGlyphAdvs = rCT.RunGetAdvancesPtr( pGlyphRun ); 335 if( !pCGGlyphAdvs) { 336 aCGSizeVec.reserve( nGlyphsInRun ); 337 CTRunGetAdvances( pGlyphRun, aFullRange, &aCGSizeVec[0] ); 338 pCGGlyphAdvs = &aCGSizeVec[0]; 339 } 340 } 341 342 const CFIndex* pCGGlyphStrIdx = NULL; 343 if( pCharIndexes) { 344 pCGGlyphStrIdx = rCT.RunGetStringIndicesPtr( pGlyphRun ); 345 if( !pCGGlyphStrIdx) { 346 aCFIndexVec.reserve( nGlyphsInRun ); 347 CTRunGetStringIndices( pGlyphRun, aFullRange, &aCFIndexVec[0] ); 348 pCGGlyphStrIdx = &aCFIndexVec[0]; 349 } 350 } 351 352 // get the details for each interesting glyph 353 // TODO: handle nLen>1 354 for(; (--nLen >= 0) && (nSubIndex < nGlyphsInRun); ++nSubIndex, ++nStart ) 355 { 356 // convert glyph details for VCL 357 *(pOutGlyphIds++) = pCGGlyphIdx[ nSubIndex ]; 358 if( pGlyphAdvances ) 359 *(pGlyphAdvances++) = mfFontStretch * pCGGlyphAdvs[ nSubIndex ].width; 360 if( pCharIndexes ) 361 *(pCharIndexes++) = pCGGlyphStrIdx[ nSubIndex] + mnMinCharPos; 362 if( !nCount++ ) { 363 const CGPoint& rCurPos = pCGGlyphPos[ nSubIndex ]; 364 rPos = GetDrawPosition( Point( mfFontScale * mfFontStretch * rCurPos.x, mfFontScale * rCurPos.y) ); 365 } 366 } 367 nSubIndex = 0; // prepare for the next glyph run 368 break; // TODO: handle nLen>1 369 } 370 371 return nCount; 372 } 373 374 // ----------------------------------------------------------------------- 375 376 long CTLayout::GetTextWidth() const 377 { 378 if( (mnCharCount <= 0) || !mpCTLine ) 379 return 0; 380 381 if( mfCachedWidth < 0.0 ) 382 mfCachedWidth = CTLineGetTypographicBounds( mpCTLine, NULL, NULL, NULL ); 383 384 const long nScaledWidth = lrint( mfFontScale * mfCachedWidth ); 385 return nScaledWidth; 386 } 387 388 // ----------------------------------------------------------------------- 389 390 long CTLayout::FillDXArray( sal_Int32* pDXArray ) const 391 { 392 // short circuit requests which don't need full details 393 if( !pDXArray ) 394 return GetTextWidth(); 395 396 long nPixWidth = GetTextWidth(); 397 if( pDXArray ) { 398 // prepare the sub-pixel accurate logical-width array 399 ::std::vector<float> aWidthVector( mnCharCount ); 400 if( mnTrailingSpaceCount && (mfTrailingSpaceWidth > 0.0) ) { 401 const double fOneWidth = mfTrailingSpaceWidth / mnTrailingSpaceCount; 402 for( int i = 1; i <= mnTrailingSpaceCount; ++i) 403 aWidthVector[ mnCharCount - i ] = fOneWidth; 404 } 405 // measure advances in each glyph run 406 CFArrayRef aGlyphRuns = CTLineGetGlyphRuns( mpCTLine ); 407 const int nRunCount = CFArrayGetCount( aGlyphRuns ); 408 typedef std::vector<CGSize> CGSizeVector; 409 CGSizeVector aSizeVec; 410 typedef std::vector<CFIndex> CFIndexVector; 411 CFIndexVector aIndexVec; 412 for( int nRunIndex = 0; nRunIndex < nRunCount; ++nRunIndex ) { 413 CTRunRef pGlyphRun = (CTRunRef)CFArrayGetValueAtIndex( aGlyphRuns, nRunIndex ); 414 const CFIndex nGlyphCount = CTRunGetGlyphCount( pGlyphRun ); 415 const CFRange aFullRange = CFRangeMake( 0, nGlyphCount ); 416 aSizeVec.resize( nGlyphCount ); 417 aIndexVec.resize( nGlyphCount ); 418 CTRunGetAdvances( pGlyphRun, aFullRange, &aSizeVec[0] ); 419 CTRunGetStringIndices( pGlyphRun, aFullRange, &aIndexVec[0] ); 420 for( int i = 0; i != nGlyphCount; ++i ) { 421 const int nRelIdx = aIndexVec[i]; 422 aWidthVector[nRelIdx] += aSizeVec[i].width; 423 } 424 } 425 426 // convert the sub-pixel accurate array into classic pDXArray integers 427 float fWidthSum = 0.0; 428 sal_Int32 nOldDX = 0; 429 for( int i = 0; i < mnCharCount; ++i) { 430 const sal_Int32 nNewDX = rint( fWidthSum += aWidthVector[i]); 431 pDXArray[i] = nNewDX - nOldDX; 432 nOldDX = nNewDX; 433 } 434 } 435 436 return nPixWidth; 437 } 438 439 // ----------------------------------------------------------------------- 440 441 int CTLayout::GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const 442 { 443 if( !mpCTLine ) 444 return STRING_LEN; 445 446 CTTypesetterRef aCTTypeSetter = CTTypesetterCreateWithAttributedString( mpAttrString ); 447 const double fCTMaxWidth = (double)nMaxWidth / (nFactor * mfFontScale); 448 CFIndex nIndex = CTTypesetterSuggestClusterBreak( aCTTypeSetter, 0, fCTMaxWidth ); 449 if( nIndex >= mnCharCount ) 450 return STRING_LEN; 451 452 nIndex += mnMinCharPos; 453 return (int)nIndex; 454 } 455 456 // ----------------------------------------------------------------------- 457 458 void CTLayout::GetCaretPositions( int nMaxIndex, sal_Int32* pCaretXArray ) const 459 { 460 DBG_ASSERT( ((nMaxIndex>0)&&!(nMaxIndex&1)), 461 "CTLayout::GetCaretPositions() : invalid number of caret pairs requested"); 462 463 // initialize the caret positions 464 for( int i = 0; i < nMaxIndex; ++i ) 465 pCaretXArray[ i ] = -1; 466 467 const DynCoreTextSyms& rCT = DynCoreTextSyms::get(); 468 for( int n = 0; n <= mnCharCount; ++n ) 469 { 470 // measure the characters cursor position 471 CGFloat fPos2 = -1; 472 const CGFloat fPos1 = rCT.LineGetOffsetForStringIndex( mpCTLine, n, &fPos2 ); 473 (void)fPos2; // TODO: split cursor at line direction change 474 // update previous trailing position 475 if( n > 0 ) 476 pCaretXArray[ 2*n-1 ] = lrint( fPos1 * mfFontScale ); 477 // update current leading position 478 if( 2*n >= nMaxIndex ) 479 break; 480 pCaretXArray[ 2*n+0 ] = lrint( fPos1 * mfFontScale ); 481 } 482 } 483 484 // ----------------------------------------------------------------------- 485 486 bool CTLayout::GetBoundRect( SalGraphics& rGraphics, Rectangle& rVCLRect ) const 487 { 488 AquaSalGraphics& rAquaGraphics = static_cast<AquaSalGraphics&>(rGraphics); 489 CGRect aMacRect = CTLineGetImageBounds( mpCTLine, rAquaGraphics.mrContext ); 490 CGPoint aMacPos = CGContextGetTextPosition( rAquaGraphics.mrContext ); 491 aMacRect.origin.x -= aMacPos.x; 492 aMacRect.origin.y -= aMacPos.y; 493 494 const Point aPos = GetDrawPosition( Point(mnBaseAdv, 0) ); 495 496 // CoreText top-bottom are vertically flipped from a VCL aspect 497 rVCLRect.Left() = aPos.X() + mfFontScale * aMacRect.origin.x; 498 rVCLRect.Right() = aPos.X() + mfFontScale * (aMacRect.origin.x + aMacRect.size.width); 499 rVCLRect.Bottom() = aPos.Y() - mfFontScale * aMacRect.origin.y; 500 rVCLRect.Top() = aPos.Y() - mfFontScale * (aMacRect.origin.y + aMacRect.size.height); 501 return true; 502 } 503 504 // ======================================================================= 505 506 // glyph fallback is supported directly by Aqua 507 // so methods used only by MultiSalLayout can be dummy implementated 508 bool CTLayout::GetGlyphOutlines( SalGraphics&, PolyPolyVector& ) const { return false; } 509 void CTLayout::InitFont() const {} 510 void CTLayout::MoveGlyph( int /*nStart*/, long /*nNewXPos*/ ) {} 511 void CTLayout::DropGlyph( int /*nStart*/ ) {} 512 void CTLayout::Simplify( bool /*bIsBase*/ ) {} 513 514 // get the ImplFontData for a glyph fallback font 515 // for a glyphid that was returned by CTLayout::GetNextGlyphs() 516 const ImplFontData* CTLayout::GetFallbackFontData( sal_GlyphId /*aGlyphId*/ ) const 517 { 518 #if 0 519 // check if any fallback fonts were needed 520 if( !mpFallbackInfo ) 521 return NULL; 522 // check if the current glyph needs a fallback font 523 int nFallbackLevel = (aGlyphId & GF_FONTMASK) >> GF_FONTSHIFT; 524 if( !nFallbackLevel ) 525 return NULL; 526 pFallbackFont = mpFallbackInfo->GetFallbackFontData( nFallbackLevel ); 527 #else 528 // let CoreText's font cascading handle glyph fallback 529 const ImplFontData* pFallbackFont = NULL; 530 #endif 531 return pFallbackFont; 532 } 533 534 // ======================================================================= 535 536 SalLayout* CTTextStyle::GetTextLayout( void ) const 537 { 538 return new CTLayout( this); 539 } 540 541 // ======================================================================= 542 543