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