Bump version to 4.1-6
[LibreOffice.git] / vcl / coretext / ctlayout.cxx
blob72949a0a5e0ed1754da522377aa80f4907220293
1 /*
2 * This file is part of the LibreOffice project.
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 * This file incorporates work covered by the following license notice:
10 * Licensed to the Apache Software Foundation (ASF) under one or more
11 * contributor license agreements. See the NOTICE file distributed
12 * with this work for additional information regarding copyright
13 * ownership. The ASF licenses this file to you under the Apache
14 * License, Version 2.0 (the "License"); you may not use this file
15 * except in compliance with the License. You may obtain a copy of
16 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
19 #include "tools/debug.hxx"
21 #include "ctfonts.hxx"
23 // =======================================================================
25 class CTLayout
26 : public SalLayout
28 public:
29 explicit CTLayout( const CTTextStyle* );
30 virtual ~CTLayout( void );
32 virtual bool LayoutText( ImplLayoutArgs& );
33 virtual void AdjustLayout( ImplLayoutArgs& );
34 virtual void DrawText( SalGraphics& ) const;
36 virtual int GetNextGlyphs( int nLen, sal_GlyphId* pGlyphs, Point& rPos, int&,
37 sal_Int32* pGlyphAdvances, int* pCharIndexes,
38 const PhysicalFontFace** pFallbackFonts ) const;
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;
47 virtual void InitFont( void) const;
48 virtual void MoveGlyph( int nStart, long nNewXPos );
49 virtual void DropGlyph( int nStart );
50 virtual void Simplify( bool bIsBase );
52 private:
53 CGPoint GetTextDrawPosition(void) const;
54 double GetWidth(void) const;
56 const CTTextStyle* const mpTextStyle;
58 // CoreText specific objects
59 CFAttributedStringRef mpAttrString;
60 CTLineRef mpCTLine;
62 int mnCharCount; // ==mnEndCharPos-mnMinCharPos
64 // cached details about the resulting layout
65 // mutable members since these details are all lazy initialized
66 mutable double mfCachedWidth; // cached value of resulting typographical width
68 // x-offset relative to layout origin
69 // currently only used in RTL-layouts
70 mutable double mfBaseAdv;
73 // =======================================================================
75 CTLayout::CTLayout( const CTTextStyle* pTextStyle )
76 : mpTextStyle( pTextStyle )
77 , mpAttrString( NULL )
78 , mpCTLine( NULL )
79 , mnCharCount( 0 )
80 , mfCachedWidth( -1 )
81 , mfBaseAdv( 0 )
85 // -----------------------------------------------------------------------
87 CTLayout::~CTLayout()
89 if( mpCTLine )
90 CFRelease( mpCTLine );
91 if( mpAttrString )
92 CFRelease( mpAttrString );
95 // -----------------------------------------------------------------------
97 bool CTLayout::LayoutText( ImplLayoutArgs& rArgs )
99 if( mpAttrString )
100 CFRelease( mpAttrString );
101 mpAttrString = NULL;
102 if( mpCTLine )
103 CFRelease( mpCTLine );
104 mpCTLine = NULL;
106 SalLayout::AdjustLayout( rArgs );
107 mnCharCount = mnEndCharPos - mnMinCharPos;
109 // short circuit if there is nothing to do
110 if( mnCharCount <= 0 )
111 return false;
113 // create the CoreText line layout
114 CFStringRef aCFText = CFStringCreateWithCharactersNoCopy( NULL, rArgs.mpStr + mnMinCharPos, mnCharCount, kCFAllocatorNull );
115 // CFAttributedStringCreate copies the attribues parameter
116 mpAttrString = CFAttributedStringCreate( NULL, aCFText, mpTextStyle->GetStyleDict() );
117 mpCTLine = CTLineCreateWithAttributedString( mpAttrString );
118 CFRelease( aCFText);
120 return true;
123 // -----------------------------------------------------------------------
125 void CTLayout::AdjustLayout( ImplLayoutArgs& rArgs )
127 if( !mpCTLine)
128 return;
130 int nOrigWidth = GetTextWidth();
131 int nPixelWidth = rArgs.mnLayoutWidth;
132 if( nPixelWidth )
134 if( nPixelWidth <= 0)
135 return;
137 else if( rArgs.mpDXArray )
139 // for now we are only interested in the layout width
140 // TODO: use all mpDXArray elements for layouting
141 nPixelWidth = rArgs.mpDXArray[ mnCharCount - 1 ];
144 float fTrailingSpace = CTLineGetTrailingWhitespaceWidth( mpCTLine );
145 // in RTL-layouts trailing spaces are leftmost
146 // TODO: use BiDi-algorithm to thoroughly check this assumption
147 if( rArgs.mnFlags & SAL_LAYOUT_BIDI_RTL)
148 mfBaseAdv = fTrailingSpace;
150 // return early if there is nothing to do
151 if( nPixelWidth <= 0 )
152 return;
154 // HACK: justification requests which change the width by just one pixel are probably
155 // #i86038# introduced by lossy conversions between integer based coordinate system
156 if( (nOrigWidth >= nPixelWidth-1) && (nOrigWidth <= nPixelWidth+1) )
157 return;
159 CTLineRef pNewCTLine = CTLineCreateJustifiedLine( mpCTLine, 1.0, nPixelWidth - fTrailingSpace );
160 if( !pNewCTLine ) { // CTLineCreateJustifiedLine can and does fail
161 // handle failure by keeping the unjustified layout
162 // TODO: a better solution such as
163 // - forcing glyph overlap
164 // - changing the font size
165 // - changing the CTM matrix
166 return;
168 CFRelease( mpCTLine );
169 mpCTLine = pNewCTLine;
170 mfCachedWidth = nPixelWidth;
173 // -----------------------------------------------------------------------
175 // When drawing right aligned text, rounding errors in the position returned by
176 // GetDrawPosition() cause the right margin of the text to change whenever text
177 // width changes causing "jumping letters" effect. So here we calculate the
178 // drawing position relative to the right margin on our own to avoid the
179 // rounding errors. That is basically a hack, and it should go away if one day
180 // we managed to get rid of those rounding errors.
182 // We continue using GetDrawPosition() for non-right aligned text, to minimize
183 // any unforeseen side effects.
184 CGPoint CTLayout::GetTextDrawPosition(void) const
186 float fPosX, fPosY;
188 if (mnLayoutFlags & SAL_LAYOUT_RIGHT_ALIGN)
190 // text is always drawn at its leftmost point
191 const Point aPos = DrawBase();
192 fPosX = aPos.X() + mfBaseAdv - GetWidth();
193 fPosY = aPos.Y();
195 else
197 const Point aPos = GetDrawPosition(Point(mfBaseAdv, 0));
198 fPosX = aPos.X();
199 fPosY = aPos.Y();
202 CGPoint aTextPos = { +fPosX, -fPosY };
203 return aTextPos;
206 void CTLayout::DrawText( SalGraphics& rGraphics ) const
208 AquaSalGraphics& rAquaGraphics = static_cast<AquaSalGraphics&>(rGraphics);
210 // short circuit if there is nothing to do
211 if( (mnCharCount <= 0)
212 || !rAquaGraphics.CheckContext() )
213 return;
215 // the view is vertically flipped => flipped glyphs
216 // so apply a temporary transformation that it flips back
217 // also compensate if the font was size limited
218 CGContextSaveGState( rAquaGraphics.mrContext );
219 CGContextScaleCTM( rAquaGraphics.mrContext, 1.0, -1.0 );
220 CGContextSetShouldAntialias( rAquaGraphics.mrContext, !rAquaGraphics.mbNonAntialiasedText );
222 // Draw the text
223 CGPoint aTextPos = GetTextDrawPosition();
225 if( mpTextStyle->mfFontRotation != 0.0 )
227 const CGFloat fRadians = mpTextStyle->mfFontRotation;
228 CGContextRotateCTM( rAquaGraphics.mrContext, +fRadians );
230 const CGAffineTransform aInvMatrix = CGAffineTransformMakeRotation( -fRadians );
231 aTextPos = CGPointApplyAffineTransform( aTextPos, aInvMatrix );
234 CGContextSetTextPosition( rAquaGraphics.mrContext, aTextPos.x, aTextPos.y );
235 CTLineDraw( mpCTLine, rAquaGraphics.mrContext );
236 #ifndef IOS
237 // request an update of the changed window area
238 if( rAquaGraphics.IsWindowGraphics() )
240 const CGRect aInkRect = CTLineGetImageBounds( mpCTLine, rAquaGraphics.mrContext );
241 const CGRect aRefreshRect = CGContextConvertRectToDeviceSpace( rAquaGraphics.mrContext, aInkRect );
242 rAquaGraphics.RefreshRect( aRefreshRect );
244 #endif
245 // restore the original graphic context transformations
246 CGContextRestoreGState( rAquaGraphics.mrContext );
249 // -----------------------------------------------------------------------
251 int CTLayout::GetNextGlyphs( int nLen, sal_GlyphId* pGlyphIDs, Point& rPos, int& nStart,
252 sal_Int32* pGlyphAdvances, int* pCharIndexes,
253 const PhysicalFontFace** pFallbackFonts ) const
255 if( !mpCTLine )
256 return 0;
258 if( nStart < 0 ) // first glyph requested?
259 nStart = 0;
260 nLen = 1; // TODO: handle nLen>1 below
262 // prepare to iterate over the glyph runs
263 int nCount = 0;
264 int nSubIndex = nStart;
266 typedef std::vector<CGGlyph> CGGlyphVector;
267 typedef std::vector<CGPoint> CGPointVector;
268 typedef std::vector<CGSize> CGSizeVector;
269 typedef std::vector<CFIndex> CFIndexVector;
270 CGGlyphVector aCGGlyphVec;
271 CGPointVector aCGPointVec;
272 CGSizeVector aCGSizeVec;
273 CFIndexVector aCFIndexVec;
275 // TODO: iterate over cached layout
276 CFArrayRef aGlyphRuns = CTLineGetGlyphRuns( mpCTLine );
277 const int nRunCount = CFArrayGetCount( aGlyphRuns );
278 for( int nRunIndex = 0; nRunIndex < nRunCount; ++nRunIndex ) {
279 CTRunRef pGlyphRun = (CTRunRef)CFArrayGetValueAtIndex( aGlyphRuns, nRunIndex );
280 const CFIndex nGlyphsInRun = CTRunGetGlyphCount( pGlyphRun );
281 // skip to the first glyph run of interest
282 if( nSubIndex >= nGlyphsInRun ) {
283 nSubIndex -= nGlyphsInRun;
284 continue;
286 const CFRange aFullRange = CFRangeMake( 0, nGlyphsInRun );
288 // get glyph run details
289 const CGGlyph* pCGGlyphIdx = CTRunGetGlyphsPtr( pGlyphRun );
290 if( !pCGGlyphIdx ) {
291 aCGGlyphVec.reserve( nGlyphsInRun );
292 CTRunGetGlyphs( pGlyphRun, aFullRange, &aCGGlyphVec[0] );
293 pCGGlyphIdx = &aCGGlyphVec[0];
295 const CGPoint* pCGGlyphPos = CTRunGetPositionsPtr( pGlyphRun );
296 if( !pCGGlyphPos ) {
297 aCGPointVec.reserve( nGlyphsInRun );
298 CTRunGetPositions( pGlyphRun, aFullRange, &aCGPointVec[0] );
299 pCGGlyphPos = &aCGPointVec[0];
302 const CGSize* pCGGlyphAdvs = NULL;
303 if( pGlyphAdvances) {
304 pCGGlyphAdvs = CTRunGetAdvancesPtr( pGlyphRun );
305 if( !pCGGlyphAdvs) {
306 aCGSizeVec.reserve( nGlyphsInRun );
307 CTRunGetAdvances( pGlyphRun, aFullRange, &aCGSizeVec[0] );
308 pCGGlyphAdvs = &aCGSizeVec[0];
312 const CFIndex* pCGGlyphStrIdx = NULL;
313 if( pCharIndexes) {
314 pCGGlyphStrIdx = CTRunGetStringIndicesPtr( pGlyphRun );
315 if( !pCGGlyphStrIdx) {
316 aCFIndexVec.reserve( nGlyphsInRun );
317 CTRunGetStringIndices( pGlyphRun, aFullRange, &aCFIndexVec[0] );
318 pCGGlyphStrIdx = &aCFIndexVec[0];
322 const PhysicalFontFace* pFallbackFont = NULL;
323 if( pFallbackFonts ) {
324 CFDictionaryRef pRunAttributes = CTRunGetAttributes( pGlyphRun );
325 CTFontRef pRunFont = (CTFontRef)CFDictionaryGetValue( pRunAttributes, kCTFontAttributeName );
327 CFDictionaryRef pAttributes = mpTextStyle->GetStyleDict();
328 CTFontRef pFont = (CTFontRef)CFDictionaryGetValue( pAttributes, kCTFontAttributeName );
329 if ( !CFEqual( pRunFont, pFont ) ) {
330 CTFontDescriptorRef pFontDesc = CTFontCopyFontDescriptor( pRunFont );
331 ImplDevFontAttributes rDevFontAttr = DevFontFromCTFontDescriptor( pFontDesc, NULL );
332 pFallbackFont = new CTFontData( rDevFontAttr, (sal_IntPtr)pFontDesc );
336 // get the details for each interesting glyph
337 // TODO: handle nLen>1
338 for(; (--nLen >= 0) && (nSubIndex < nGlyphsInRun); ++nSubIndex, ++nStart )
340 // convert glyph details for VCL
341 *(pGlyphIDs++) = pCGGlyphIdx[ nSubIndex ];
342 if( pGlyphAdvances )
343 *(pGlyphAdvances++) = lrint(pCGGlyphAdvs[ nSubIndex ].width);
344 if( pCharIndexes )
345 *(pCharIndexes++) = pCGGlyphStrIdx[ nSubIndex] + mnMinCharPos;
346 if( pFallbackFonts )
347 *(pFallbackFonts++) = pFallbackFont;
348 if( !nCount++ ) {
349 const CGPoint& rCurPos = pCGGlyphPos[ nSubIndex ];
350 rPos = GetDrawPosition( Point( rCurPos.x, rCurPos.y) );
353 nSubIndex = 0; // prepare for the next glyph run
354 break; // TODO: handle nLen>1
357 return nCount;
360 // -----------------------------------------------------------------------
362 double CTLayout::GetWidth() const
364 if( (mnCharCount <= 0) || !mpCTLine )
365 return 0;
367 if( mfCachedWidth < 0.0 ) {
368 mfCachedWidth = CTLineGetTypographicBounds( mpCTLine, NULL, NULL, NULL);
371 return mfCachedWidth;
374 long CTLayout::GetTextWidth() const
376 return lrint(GetWidth());
378 // -----------------------------------------------------------------------
380 long CTLayout::FillDXArray( sal_Int32* pDXArray ) const
382 // short circuit requests which don't need full details
383 if( !pDXArray )
384 return GetTextWidth();
386 long nPixWidth = GetTextWidth();
387 if( pDXArray ) {
388 // prepare the sub-pixel accurate logical-width array
389 ::std::vector<float> aWidthVector( mnCharCount );
390 // handle each glyph run
391 CFArrayRef aGlyphRuns = CTLineGetGlyphRuns( mpCTLine );
392 const int nRunCount = CFArrayGetCount( aGlyphRuns );
393 typedef std::vector<CGSize> CGSizeVector;
394 CGSizeVector aSizeVec;
395 typedef std::vector<CFIndex> CFIndexVector;
396 CFIndexVector aIndexVec;
397 for( int nRunIndex = 0; nRunIndex < nRunCount; ++nRunIndex ) {
398 CTRunRef pGlyphRun = (CTRunRef)CFArrayGetValueAtIndex( aGlyphRuns, nRunIndex );
399 const CFIndex nGlyphCount = CTRunGetGlyphCount( pGlyphRun );
400 const CFRange aFullRange = CFRangeMake( 0, nGlyphCount );
401 aSizeVec.reserve( nGlyphCount );
402 aIndexVec.reserve( nGlyphCount );
403 CTRunGetAdvances( pGlyphRun, aFullRange, &aSizeVec[0] );
404 CTRunGetStringIndices( pGlyphRun, aFullRange, &aIndexVec[0] );
405 for( int i = 0; i != nGlyphCount; ++i ) {
406 const int nRelIdx = aIndexVec[i];
407 aWidthVector[nRelIdx] += aSizeVec[i].width;
411 // convert the sub-pixel accurate array into classic pDXArray integers
412 float fWidthSum = 0.0;
413 sal_Int32 nOldDX = 0;
414 for( int i = 0; i < mnCharCount; ++i) {
415 const sal_Int32 nNewDX = rint( fWidthSum += aWidthVector[i]);
416 pDXArray[i] = nNewDX - nOldDX;
417 nOldDX = nNewDX;
421 return nPixWidth;
424 // -----------------------------------------------------------------------
426 int CTLayout::GetTextBreak( long nMaxWidth, long /*nCharExtra*/, int nFactor ) const
428 if( !mpCTLine )
429 return STRING_LEN;
431 CTTypesetterRef aCTTypeSetter = CTTypesetterCreateWithAttributedString( mpAttrString );
432 const double fCTMaxWidth = (double)nMaxWidth / nFactor;
433 CFIndex nIndex = CTTypesetterSuggestClusterBreak( aCTTypeSetter, 0, fCTMaxWidth );
434 if( nIndex >= mnCharCount )
435 return STRING_LEN;
437 nIndex += mnMinCharPos;
438 return (int)nIndex;
441 // -----------------------------------------------------------------------
443 void CTLayout::GetCaretPositions( int nMaxIndex, sal_Int32* pCaretXArray ) const
445 DBG_ASSERT( ((nMaxIndex>0)&&!(nMaxIndex&1)),
446 "CTLayout::GetCaretPositions() : invalid number of caret pairs requested");
448 // initialize the caret positions
449 for( int i = 0; i < nMaxIndex; ++i )
450 pCaretXArray[ i ] = -1;
452 for( int n = 0; n <= mnCharCount; ++n )
454 // measure the characters cursor position
455 CGFloat fPos2 = -1;
456 const CGFloat fPos1 = CTLineGetOffsetForStringIndex( mpCTLine, n, &fPos2 );
457 (void)fPos2; // TODO: split cursor at line direction change
458 // update previous trailing position
459 if( n > 0 )
460 pCaretXArray[ 2*n-1 ] = lrint( fPos1 );
461 // update current leading position
462 if( 2*n >= nMaxIndex )
463 break;
464 pCaretXArray[ 2*n+0 ] = lrint( fPos1 );
468 // -----------------------------------------------------------------------
470 bool CTLayout::GetBoundRect( SalGraphics& rGraphics, Rectangle& rVCLRect ) const
472 // Closely mimic DrawText(), except that instead of calling
473 // CTLineDraw() to draw the line, we call CTLineGetImageBounds()
474 // to get its bounds. But all the coordinate system manipulation
475 // before that is the same => should be factored out?
477 AquaSalGraphics& rAquaGraphics = static_cast<AquaSalGraphics&>(rGraphics);
479 if( !rAquaGraphics.CheckContext() )
480 return false;
482 CGContextSaveGState( rAquaGraphics.mrContext );
483 CGContextScaleCTM( rAquaGraphics.mrContext, 1.0, -1.0 );
484 CGContextSetShouldAntialias( rAquaGraphics.mrContext, !rAquaGraphics.mbNonAntialiasedText );
486 const CGPoint aVclPos = GetTextDrawPosition();
487 CGPoint aTextPos = GetTextDrawPosition();
489 if( mpTextStyle->mfFontRotation != 0.0 )
491 const CGFloat fRadians = mpTextStyle->mfFontRotation;
492 CGContextRotateCTM( rAquaGraphics.mrContext, +fRadians );
494 const CGAffineTransform aInvMatrix = CGAffineTransformMakeRotation( -fRadians );
495 aTextPos = CGPointApplyAffineTransform( aTextPos, aInvMatrix );
498 CGContextSetTextPosition( rAquaGraphics.mrContext, aTextPos.x, aTextPos.y );
499 CGRect aMacRect = CTLineGetImageBounds( mpCTLine, rAquaGraphics.mrContext );
501 if( mpTextStyle->mfFontRotation != 0.0 )
503 const CGFloat fRadians = mpTextStyle->mfFontRotation;
504 const CGAffineTransform aMatrix = CGAffineTransformMakeRotation( +fRadians );
505 aMacRect = CGRectApplyAffineTransform( aMacRect, aMatrix );
508 CGContextRestoreGState( rAquaGraphics.mrContext );
510 rVCLRect.Left() = aVclPos.x + lrint(aMacRect.origin.x);
511 rVCLRect.Right() = aVclPos.x + lrint(aMacRect.origin.x + aMacRect.size.width);
512 rVCLRect.Bottom() = aVclPos.x - lrint(aMacRect.origin.y);
513 rVCLRect.Top() = aVclPos.x - lrint(aMacRect.origin.y + aMacRect.size.height);
515 return true;
518 // =======================================================================
520 // glyph fallback is supported directly by Aqua
521 // so methods used only by MultiSalLayout can be dummy implementated
522 bool CTLayout::GetGlyphOutlines( SalGraphics&, PolyPolyVector& ) const { return false; }
523 void CTLayout::InitFont() const {}
524 void CTLayout::MoveGlyph( int /*nStart*/, long /*nNewXPos*/ ) {}
525 void CTLayout::DropGlyph( int /*nStart*/ ) {}
526 void CTLayout::Simplify( bool /*bIsBase*/ ) {}
528 // =======================================================================
530 SalLayout* CTTextStyle::GetTextLayout( void ) const
532 return new CTLayout( this);
535 // =======================================================================