xref: /aoo41x/main/vcl/aqua/source/gdi/ctlayout.cxx (revision d237788c)
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