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