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