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 // =======================================================================
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
);
53 CGPoint
GetTextDrawPosition(void) const;
54 double GetWidth(void) const;
56 const CTTextStyle
* const mpTextStyle
;
58 // CoreText specific objects
59 CFAttributedStringRef mpAttrString
;
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
)
85 // -----------------------------------------------------------------------
90 CFRelease( mpCTLine
);
92 CFRelease( mpAttrString
);
95 // -----------------------------------------------------------------------
97 bool CTLayout::LayoutText( ImplLayoutArgs
& rArgs
)
100 CFRelease( mpAttrString
);
103 CFRelease( mpCTLine
);
106 SalLayout::AdjustLayout( rArgs
);
107 mnCharCount
= mnEndCharPos
- mnMinCharPos
;
109 // short circuit if there is nothing to do
110 if( mnCharCount
<= 0 )
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
);
123 // -----------------------------------------------------------------------
125 void CTLayout::AdjustLayout( ImplLayoutArgs
& rArgs
)
130 int nOrigWidth
= GetTextWidth();
131 int nPixelWidth
= rArgs
.mnLayoutWidth
;
134 if( nPixelWidth
<= 0)
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 )
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) )
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
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
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();
197 const Point aPos
= GetDrawPosition(Point(mfBaseAdv
, 0));
202 CGPoint aTextPos
= { +fPosX
, -fPosY
};
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() )
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
);
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
);
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
);
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
258 if( nStart
< 0 ) // first glyph requested?
260 nLen
= 1; // TODO: handle nLen>1 below
262 // prepare to iterate over the glyph runs
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
;
286 const CFRange aFullRange
= CFRangeMake( 0, nGlyphsInRun
);
288 // get glyph run details
289 const CGGlyph
* pCGGlyphIdx
= CTRunGetGlyphsPtr( pGlyphRun
);
291 aCGGlyphVec
.reserve( nGlyphsInRun
);
292 CTRunGetGlyphs( pGlyphRun
, aFullRange
, &aCGGlyphVec
[0] );
293 pCGGlyphIdx
= &aCGGlyphVec
[0];
295 const CGPoint
* pCGGlyphPos
= CTRunGetPositionsPtr( pGlyphRun
);
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
);
306 aCGSizeVec
.reserve( nGlyphsInRun
);
307 CTRunGetAdvances( pGlyphRun
, aFullRange
, &aCGSizeVec
[0] );
308 pCGGlyphAdvs
= &aCGSizeVec
[0];
312 const CFIndex
* pCGGlyphStrIdx
= NULL
;
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
];
343 *(pGlyphAdvances
++) = lrint(pCGGlyphAdvs
[ nSubIndex
].width
);
345 *(pCharIndexes
++) = pCGGlyphStrIdx
[ nSubIndex
] + mnMinCharPos
;
347 *(pFallbackFonts
++) = pFallbackFont
;
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
360 // -----------------------------------------------------------------------
362 double CTLayout::GetWidth() const
364 if( (mnCharCount
<= 0) || !mpCTLine
)
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
384 return GetTextWidth();
386 long nPixWidth
= GetTextWidth();
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
;
424 // -----------------------------------------------------------------------
426 int CTLayout::GetTextBreak( long nMaxWidth
, long /*nCharExtra*/, int nFactor
) const
431 CTTypesetterRef aCTTypeSetter
= CTTypesetterCreateWithAttributedString( mpAttrString
);
432 const double fCTMaxWidth
= (double)nMaxWidth
/ nFactor
;
433 CFIndex nIndex
= CTTypesetterSuggestClusterBreak( aCTTypeSetter
, 0, fCTMaxWidth
);
434 if( nIndex
>= mnCharCount
)
437 nIndex
+= mnMinCharPos
;
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
456 const CGFloat fPos1
= CTLineGetOffsetForStringIndex( mpCTLine
, n
, &fPos2
);
457 (void)fPos2
; // TODO: split cursor at line direction change
458 // update previous trailing position
460 pCaretXArray
[ 2*n
-1 ] = lrint( fPos1
);
461 // update current leading position
462 if( 2*n
>= nMaxIndex
)
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() )
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
);
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 // =======================================================================