bump product version to 5.0.4.1
[LibreOffice.git] / vcl / quartz / ctlayout.cxx
blob20dca01ddc6bd68ae7f773c3723e8f796905460f
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/types.h>
21 #include <boost/ptr_container/ptr_vector.hpp>
22 #include "tools/debug.hxx"
24 #include "quartz/utils.h"
26 #include "ctfonts.hxx"
27 #include "CTRunData.hxx"
30 class CTLayout : public SalLayout
32 public:
33 explicit CTLayout( const CoreTextStyle* );
34 virtual ~CTLayout();
36 virtual bool LayoutText( ImplLayoutArgs& ) SAL_OVERRIDE;
37 virtual void AdjustLayout( ImplLayoutArgs& ) SAL_OVERRIDE;
38 virtual void DrawText( SalGraphics& ) const SAL_OVERRIDE;
39 virtual bool DrawTextSpecial( SalGraphics& rGraphics, sal_uInt32 flags ) const SAL_OVERRIDE;
41 virtual int GetNextGlyphs( int nLen, sal_GlyphId* pOutGlyphIds, Point& rPos, int&,
42 DeviceCoordinate* pGlyphAdvances, int* pCharIndexes,
43 const PhysicalFontFace** pFallbackFonts ) const SAL_OVERRIDE;
45 virtual DeviceCoordinate GetTextWidth() const SAL_OVERRIDE;
46 virtual DeviceCoordinate FillDXArray( DeviceCoordinate* pDXArray ) const SAL_OVERRIDE;
47 virtual sal_Int32 GetTextBreak(DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor) const SAL_OVERRIDE;
48 virtual void GetCaretPositions( int nArraySize, long* pCaretXArray ) const SAL_OVERRIDE;
49 virtual bool GetBoundRect( SalGraphics&, Rectangle& ) const SAL_OVERRIDE;
51 virtual void InitFont() const SAL_OVERRIDE;
52 virtual void MoveGlyph( int nStart, long nNewXPos ) SAL_OVERRIDE;
53 virtual void DropGlyph( int nStart ) SAL_OVERRIDE;
54 virtual void Simplify( bool bIsBase ) SAL_OVERRIDE;
56 private:
57 void drawCTLine(AquaSalGraphics& rAquaGraphics, CTLineRef ctline, const CoreTextStyle* const pStyle) const;
58 CGPoint GetTextDrawPosition() const;
59 bool CacheGlyphLayout() const;
61 const CoreTextStyle* const mpTextStyle;
63 // CoreText specific objects
64 CFAttributedStringRef mpAttrString;
65 CTLineRef mpCTLine;
67 int mnCharCount; // ==mnEndCharPos-mnMinCharPos
68 int mnTrailingSpaceCount;
69 double mfTrailingSpaceWidth;
71 // cached details about the resulting layout
72 // mutable members since these details are all lazy initialized
73 mutable double mfCachedWidth; // cached value of resulting typographical width
75 // x-offset relative to layout origin
76 // currently only used in RTL-layouts
77 mutable double mfBaseAdv;
79 mutable bool bLayouted; // true if the glyph layout information are cached and current;
80 mutable boost::ptr_vector<CTRunData> m_vRunData;
84 CTLayout::CTLayout( const CoreTextStyle* pTextStyle )
85 : mpTextStyle( pTextStyle )
86 , mpAttrString( NULL )
87 , mpCTLine( NULL )
88 , mnCharCount( 0 )
89 , mnTrailingSpaceCount( 0 )
90 , mfTrailingSpaceWidth( 0.0 )
91 , mfCachedWidth( -1 )
92 , mfBaseAdv( 0 )
93 , bLayouted( false )
97 CTLayout::~CTLayout()
99 if( mpCTLine )
101 SAL_INFO( "vcl.ct", "CFRelease(" << mpCTLine << ")" );
102 CFRelease( mpCTLine );
104 if( mpAttrString )
105 CFRelease( mpAttrString );
108 bool CTLayout::LayoutText( ImplLayoutArgs& rArgs )
110 m_vRunData.release();
111 bLayouted = false;
113 // release an eventual older layout
114 if( mpAttrString )
115 CFRelease( mpAttrString );
116 mpAttrString = NULL;
117 if( mpCTLine )
119 SAL_INFO( "vcl.ct", "CFRelease(" << mpCTLine << ")" );
120 CFRelease( mpCTLine );
122 mpCTLine = NULL;
124 // initialize the new layout
125 SalLayout::AdjustLayout( rArgs );
126 mnCharCount = mnEndCharPos - mnMinCharPos;
128 // short circuit if there is nothing to do
129 if( mnCharCount <= 0 )
130 return false;
132 // create the CoreText line layout
133 CFStringRef aCFText = CFStringCreateWithCharactersNoCopy( NULL,
134 rArgs.mpStr + mnMinCharPos,
135 mnCharCount,
136 kCFAllocatorNull );
137 // CFAttributedStringCreate copies the attribues parameter
138 mpAttrString = CFAttributedStringCreate( NULL, aCFText, mpTextStyle->GetStyleDict() );
139 mpCTLine = CTLineCreateWithAttributedString( mpAttrString );
140 SAL_INFO( "vcl.ct", "CTLineCreateWithAttributedString(\"" << GetOUString(aCFText) << "\") =p " << mpCTLine );
141 CFRelease( aCFText);
143 mnTrailingSpaceCount = 0;
144 // reverse search for first 'non-space'...
145 for( int i = mnEndCharPos - 1; i >= mnMinCharPos; i--)
147 sal_Unicode nChar = rArgs.mpStr[i];
148 if ((nChar <= 0x0020) || // blank
149 (nChar == 0x00A0) || // non breaking space
150 (nChar >= 0x2000 && nChar <= 0x200F) || // whitespace
151 (nChar == 0x3000)) // ideographic space
153 mnTrailingSpaceCount += 1;
155 else
157 break;
160 return true;
163 void CTLayout::AdjustLayout( ImplLayoutArgs& rArgs )
165 if( !mpCTLine)
167 return;
171 int nPixelWidth = rArgs.mpDXArray ? rArgs.mpDXArray[ mnCharCount - 1 ] : rArgs.mnLayoutWidth;
172 if( nPixelWidth <= 0)
173 return;
175 // HACK: justification requests which change the width by just one pixel are probably
176 // #i86038# introduced by lossy conversions between integer based coordinate system
177 int fuzz = (nPixelWidth - GetTextWidth()) / 2;
178 if (!fuzz)
180 return;
183 // if the text to be justified has whitespace in it then
184 // - Writer goes crazy with its HalfSpace magic
185 // - CoreText handles spaces specially (in particular at the text end)
186 if( mnTrailingSpaceCount )
188 if(rArgs.mpDXArray)
190 int nFullPixelWidth = nPixelWidth;
191 nPixelWidth = mnTrailingSpaceCount == mnCharCount
192 ? 0 : rArgs.mpDXArray[ mnCharCount - mnTrailingSpaceCount - 1];
193 mfTrailingSpaceWidth = nFullPixelWidth - nPixelWidth;
195 else
197 if(mfTrailingSpaceWidth <= 0.0)
199 mfTrailingSpaceWidth = CTLineGetTrailingWhitespaceWidth( mpCTLine );
200 nPixelWidth -= rint(mfTrailingSpaceWidth);
203 if(nPixelWidth <= 0)
205 return;
207 // recreate the CoreText line layout without trailing spaces
208 SAL_INFO( "vcl.ct", "CFRelease(" << mpCTLine << ")" );
209 CFRelease( mpCTLine );
210 CFStringRef aCFText = CFStringCreateWithCharactersNoCopy( NULL,
211 rArgs.mpStr + mnMinCharPos,
212 mnCharCount - mnTrailingSpaceCount,
213 kCFAllocatorNull );
214 CFAttributedStringRef pAttrStr = CFAttributedStringCreate( NULL,
215 aCFText,
216 mpTextStyle->GetStyleDict() );
217 mpCTLine = CTLineCreateWithAttributedString( pAttrStr );
218 SAL_INFO( "vcl.ct", "CTLineCreateWithAttributedString(\"" << GetOUString(aCFText) << "\") = " << mpCTLine );
219 CFRelease( pAttrStr );
220 CFRelease( aCFText );
223 // in RTL-layouts trailing spaces are leftmost
224 // TODO: use BiDi-algorithm to thoroughly check this assumption
225 if( rArgs.mnFlags & SalLayoutFlags::BiDiRtl)
227 mfBaseAdv = mfTrailingSpaceWidth;
231 CTLineRef pNewCTLine = CTLineCreateJustifiedLine( mpCTLine, 1.0, nPixelWidth);
232 SAL_INFO( "vcl.ct", "CTLineCreateJustifiedLine(" << mpCTLine << ",1.0," << nPixelWidth << ") = " << pNewCTLine );
234 if( !pNewCTLine )
236 // CTLineCreateJustifiedLine can and does fail
237 // handle failure by keeping the unjustified layout
238 // TODO: a better solution such as
239 // - forcing glyph overlap
240 // - changing the font size
241 // - changing the CTM matrix
242 return;
244 SAL_INFO( "vcl.ct", "CFRelease(" << mpCTLine << ")" );
245 CFRelease( mpCTLine );
246 mpCTLine = pNewCTLine;
247 mfCachedWidth = nPixelWidth + mfTrailingSpaceWidth;
250 // When drawing right aligned text, rounding errors in the position returned by
251 // GetDrawPosition() cause the right margin of the text to change whenever text
252 // width changes causing "jumping letters" effect. So here we calculate the
253 // drawing position relative to the right margin on our own to avoid the
254 // rounding errors. That is basically a hack, and it should go away if one day
255 // we managed to get rid of those rounding errors.
257 // We continue using GetDrawPosition() for non-right aligned text, to minimize
258 // any unforeseen side effects.
259 CGPoint CTLayout::GetTextDrawPosition() const
261 CGFloat fPosX, fPosY;
263 if (mnLayoutFlags & SalLayoutFlags::RightAlign)
265 // text is always drawn at its leftmost point
266 const Point aPos = DrawBase();
267 fPosX = aPos.X() + mfBaseAdv - GetTextWidth();
268 fPosY = aPos.Y();
270 else
272 const Point aPos = GetDrawPosition(Point(mfBaseAdv, 0));
273 fPosX = aPos.X();
274 fPosY = aPos.Y();
277 CGPoint aTextPos = { +fPosX, -fPosY };
278 return aTextPos;
281 /* use to deal with special font decoration like 'outline' drawing
282 * return true if it was able to handle the drawing
283 * false if not, in which case the caller
284 * is supposed to fallback to 'generic' method
286 bool CTLayout::DrawTextSpecial( SalGraphics& rGraphics, sal_uInt32 flags ) const
288 AquaSalGraphics& rAquaGraphics = static_cast<AquaSalGraphics&>(rGraphics);
290 // short circuit if there is nothing to do
291 if( (mnCharCount <= 0) || !rAquaGraphics.CheckContext() )
292 return true;
293 #if 0 /* temporarely revert to the 'old way' */
294 if (flags & DRAWTEXT_F_OUTLINE)
296 CFMutableDictionaryRef styledict = CFDictionaryCreateMutableCopy(
297 CFAllocatorGetDefault(),
298 CFDictionaryGetCount(mpTextStyle->GetStyleDict()),
299 mpTextStyle->GetStyleDict());
301 int nStroke = 2;
302 CFNumberRef rStroke = CFNumberCreate(NULL, kCFNumberSInt32Type, &nStroke);
303 CFDictionarySetValue(styledict, kCTStrokeWidthAttributeName, rStroke);
305 CFAttributedStringRef pAttrStr = CFAttributedStringCreate(
306 NULL,
307 CFAttributedStringGetString(mpAttrString),
308 styledict);
309 CTLineRef pCTLine = CTLineCreateWithAttributedString( pAttrStr );
310 SAL_INFO( "vcl.ct", "CTLineCreateWithAttributedString(" << GetOUString(CFAttributedStringGetString(mpAttrSring)) << ") = " << pCTLine );
311 CFRelease( pAttrStr );
313 /* draw the text in 'outline' */
314 drawCTLine(rAquaGraphics, pCTLine, mpTextStyle);
315 SAL_INFO( "vcl.ct", "CFRelease(" << pCTLine << ")" );
316 CFRelease(pCTLine);
317 return true;
319 else
320 #endif
322 return SalLayout::DrawTextSpecial(rGraphics, flags);
326 void CTLayout::drawCTLine(AquaSalGraphics& rAquaGraphics, CTLineRef ctline, const CoreTextStyle* const pStyle) const
328 // the view is vertically flipped => flipped glyphs
329 // so apply a temporary transformation that it flips back
330 // also compensate if the font was size limited
331 CGContextRef context = rAquaGraphics.mrContext;
332 SAL_INFO( "vcl.ct", "CGContextSaveGState(" << context << ")" );
333 CGContextSaveGState( context );
334 SAL_INFO( "vcl.ct", "CGContextScaleCTM(" << context << ",1.0,-1.0)" );
335 CGContextScaleCTM( context, 1.0, -1.0 );
336 CGContextSetShouldAntialias( context, !rAquaGraphics.mbNonAntialiasedText );
338 // set the text transformation (e.g. position)
339 CGPoint aTextPos = GetTextDrawPosition();
341 if( pStyle->mfFontRotation != 0.0 )
343 const CGFloat fRadians = pStyle->mfFontRotation;
344 SAL_INFO( "vcl.ct", "CGContextRotateCTM(" << context << "," << +fRadians << ")" );
345 CGContextRotateCTM( context, +fRadians );
347 const CGAffineTransform aInvMatrix = CGAffineTransformMakeRotation( -fRadians );
348 aTextPos = CGPointApplyAffineTransform( aTextPos, aInvMatrix );
351 SAL_INFO( "vcl.ct", "CGContextSetTextPosition(" << context << "," << aTextPos << ")" );
352 CGContextSetTextPosition( context, aTextPos.x, aTextPos.y );
354 #ifndef IOS
355 // request an update of the to-be-changed window area
356 if( rAquaGraphics.IsWindowGraphics() )
358 const CGRect aInkRect = CTLineGetImageBounds( mpCTLine, context );
359 const CGRect aRefreshRect = CGContextConvertRectToDeviceSpace( context, aInkRect );
360 rAquaGraphics.RefreshRect( aRefreshRect );
362 #endif
364 // set the text color as fill color (see kCTForegroundColorFromContextAttributeName)
365 CGContextSetFillColor( context, rAquaGraphics.maTextColor.AsArray() );
367 SAL_INFO( "vcl.ct", "CTLineDraw(" << ctline << "," << context << ")" );
368 // draw the text
369 CTLineDraw( ctline, context );
371 if(mnLayoutFlags & SalLayoutFlags::DrawBullet)
373 CFArrayRef runArray = CTLineGetGlyphRuns(ctline);
374 CFIndex runCount = CFArrayGetCount(runArray);
376 for (CFIndex runIndex = 0; runIndex < runCount; runIndex++)
378 CTRunRef run = static_cast<CTRunRef>(CFArrayGetValueAtIndex(runArray, runIndex));
379 CFIndex runGlyphCount = CTRunGetGlyphCount(run);
380 CGPoint position;
381 CGSize advance;
382 CFIndex runGlyphIndex = 0;
383 CFIndex stringIndice = 0;
385 for (; runGlyphIndex < runGlyphCount; runGlyphIndex++)
387 CFRange glyphRange = CFRangeMake(runGlyphIndex, 1);
388 CTRunGetStringIndices( run, glyphRange, &stringIndice );
389 UniChar curChar = CFStringGetCharacterAtIndex (CFAttributedStringGetString(mpAttrString), stringIndice);
390 if(curChar == ' ')
392 CGFloat ascent;
393 CGFloat descent;
394 CGFloat leading;
395 CTFontRef runFont = static_cast<CTFontRef>(CFDictionaryGetValue(CTRunGetAttributes(run),
396 kCTFontAttributeName));
397 CGFloat baseSize = CTFontGetSize(runFont);
398 CTRunGetTypographicBounds ( run, glyphRange,
399 &ascent, &descent, &leading);
400 CTRunGetPositions(run, glyphRange, &position);
401 CTRunGetAdvances(run, glyphRange, &advance);
402 CGRect bulletRect;
403 if(mnLayoutFlags & SalLayoutFlags::Vertical)
405 bulletRect = CGRectMake(position.x - advance.width / 4,
406 -position.y, baseSize / 5, baseSize / 5 );
408 else
410 bulletRect = CGRectMake(position.x + advance.width / 4,
411 position.y + ascent / 3 - baseSize / 5, baseSize / 5, baseSize / 5 );
414 CGContextSaveGState(context);
415 CGContextTranslateCTM(context, aTextPos.x, aTextPos.y);
416 RGBAColor bulletColor(MAKE_SALCOLOR(0x26, 0x8b, 0xd2 )); // NON_PRINTING_CHARACTER_COLOR
417 CGContextSetFillColor( context, bulletColor.AsArray() );
418 CGContextSetStrokeColor(context, bulletColor.AsArray());
419 if(mnLayoutFlags & SalLayoutFlags::Vertical)
421 CGContextRotateCTM( context, -F_PI/2 );
424 CGContextBeginPath(context);
425 CGContextAddEllipseInRect(context, bulletRect);
426 CGContextDrawPath(context, kCGPathFillStroke); // Or kCGPathFill
427 CGContextRestoreGState(context);
432 // restore the original graphic context transformations
433 SAL_INFO( "vcl.ct", "CGContextRestoreGState(" << context << ")" );
434 CGContextRestoreGState( context );
437 void CTLayout::DrawText( SalGraphics& rGraphics ) const
439 AquaSalGraphics& rAquaGraphics = static_cast<AquaSalGraphics&>(rGraphics);
441 // short circuit if there is nothing to do
442 if( (mnCharCount <= 0) || !rAquaGraphics.CheckContext() )
443 return;
445 drawCTLine(rAquaGraphics, mpCTLine, mpTextStyle);
448 bool CTLayout::CacheGlyphLayout() const // eew!
450 m_vRunData.release();
451 if(!mpCTLine)
453 return false;
455 CFArrayRef aRuns = CTLineGetGlyphRuns( mpCTLine );
456 const int nRun = CFArrayGetCount( aRuns );
457 int nPos = 0;
459 for( int i = 0; i < nRun; ++i )
461 CTRunRef pRun = static_cast<CTRunRef>(CFArrayGetValueAtIndex( aRuns, i ));
462 CTRunData* pRunData = new CTRunData(pRun, nPos);
463 m_vRunData.push_back(pRunData);
464 nPos += pRunData->m_nGlyphs;
466 bLayouted = true;
467 return true;
470 int CTLayout::GetNextGlyphs( int nLen, sal_GlyphId* pOutGlyphIds, Point& rPos, int& nStart,
471 DeviceCoordinate* pGlyphAdvances, int* pCharIndexes,
472 const PhysicalFontFace** pFallbackFonts ) const
474 if( !mpCTLine )
476 return 0;
478 if(!bLayouted)
480 CacheGlyphLayout();
483 if( nStart < 0 ) // first glyph requested?
485 nStart = 0;
487 const PhysicalFontFace* pFallbackFont = NULL;
488 CTFontRef pFont = NULL;
489 CTFontDescriptorRef pFontDesc = NULL;
490 ImplDevFontAttributes rDevFontAttr;
492 boost::ptr_vector<CTRunData>::const_iterator iter = m_vRunData.begin();
494 while(iter != m_vRunData.end() && iter->m_EndPos <= nStart)
496 ++iter;
498 if(iter == m_vRunData.end())
500 return 0;
502 else
504 if( pFallbackFonts )
506 pFont = static_cast<CTFontRef>(CFDictionaryGetValue( mpTextStyle->GetStyleDict(), kCTFontAttributeName ));
507 pFontDesc = CTFontCopyFontDescriptor( iter->m_pFont );
508 rDevFontAttr = DevFontFromCTFontDescriptor( pFontDesc, NULL );
511 int i = nStart;
512 int count = 0;
513 while( i < nStart + nLen )
515 // convert glyph details for VCL
516 int j = i - iter->m_StartPos;
517 *(pOutGlyphIds++) = iter->m_pGlyphs[ j ];
518 if( pGlyphAdvances )
520 *(pGlyphAdvances++) = lrint(iter->m_pAdvances[ j ].width);
522 if( pCharIndexes )
524 *(pCharIndexes++) = iter->m_pStringIndices[ j ] + mnMinCharPos;
526 if( pFallbackFonts )
528 if ( !CFEqual( iter->m_pFont, pFont ) )
530 pFallbackFont = new CoreTextFontData( rDevFontAttr, reinterpret_cast<sal_IntPtr>(pFontDesc) );
531 *(pFallbackFonts++) = pFallbackFont;
533 else
535 *(pFallbackFonts++) = NULL;
538 if( i == nStart )
540 const CGPoint& rFirstPos = iter->m_pPositions[ j ];
541 rPos = GetDrawPosition( Point( rFirstPos.x, rFirstPos.y) );
543 i += 1;
544 count += 1;
545 if(i == iter->m_EndPos)
547 // note: we assume that we do not have empty runs in the middle of things
548 ++iter;
549 if( iter == m_vRunData.end())
551 break;
553 if( pFallbackFonts )
555 pFont = static_cast<CTFontRef>(CFDictionaryGetValue( mpTextStyle->GetStyleDict(), kCTFontAttributeName ));
556 pFontDesc = CTFontCopyFontDescriptor( iter->m_pFont );
557 rDevFontAttr = DevFontFromCTFontDescriptor( pFontDesc, NULL );
561 nStart = i;
563 return count;
567 DeviceCoordinate CTLayout::GetTextWidth() const
569 if( (mnCharCount <= 0) || !mpCTLine )
570 return 0;
572 if( mfCachedWidth < 0.0 )
574 mfCachedWidth = CTLineGetTypographicBounds( mpCTLine, NULL, NULL, NULL);
577 return mfCachedWidth;
580 DeviceCoordinate CTLayout::FillDXArray( DeviceCoordinate* pDXArray ) const
582 DeviceCoordinate nPixelWidth = GetTextWidth();
584 // short circuit requests which don't need full details
585 if( !pDXArray )
587 return nPixelWidth;
590 for(int i = 0; i < mnCharCount; i++)
592 pDXArray[i] = 0.0;
595 // prepare the sub-pixel accurate logical-width array
596 ::std::vector<float> aWidthVector( mnCharCount );
597 if( mnTrailingSpaceCount && (mfTrailingSpaceWidth > 0.0) )
599 const double fOneWidth = mfTrailingSpaceWidth / mnTrailingSpaceCount;
600 for(int i = mnCharCount - mnTrailingSpaceCount; i < mnCharCount; i++)
602 aWidthVector[i] = fOneWidth;
606 // handle each glyph run
607 CFArrayRef aGlyphRuns = CTLineGetGlyphRuns( mpCTLine );
608 const int nRunCount = CFArrayGetCount( aGlyphRuns );
609 typedef std::vector<CGSize> CGSizeVector;
610 CGSizeVector aSizeVector;
611 typedef std::vector<CFIndex> CFIndexVector;
612 CFIndexVector aIndexVector;
614 for( int nRunIndex = 0; nRunIndex < nRunCount; ++nRunIndex )
616 CTRunRef pGlyphRun = static_cast<CTRunRef>(CFArrayGetValueAtIndex( aGlyphRuns, nRunIndex ));
617 const CFIndex nGlyphCount = CTRunGetGlyphCount( pGlyphRun );
618 const CFRange aFullRange = CFRangeMake( 0, nGlyphCount );
620 aSizeVector.resize( nGlyphCount );
621 aIndexVector.resize( nGlyphCount );
622 CTRunGetAdvances( pGlyphRun, aFullRange, &aSizeVector[0] );
623 CTRunGetStringIndices( pGlyphRun, aFullRange, &aIndexVector[0] );
625 for( int i = 0; i != nGlyphCount; ++i )
627 const int nRelIndex = aIndexVector[i];
628 SAL_INFO( "vcl.ct", "aWidthVector[ g:" << i << "-> c:" << nRelIndex << " ] = " << aWidthVector[nRelIndex] << " + " << aSizeVector[i].width << " = " << aWidthVector[nRelIndex] + aSizeVector[i].width);
629 aWidthVector[nRelIndex] += aSizeVector[i].width;
633 // convert the sub-pixel accurate array into classic pDXArray integers
634 float fWidthSum = 0.0;
635 sal_Int32 nOldDX = 0;
636 for( int i = 0; i < mnCharCount; ++i)
638 const sal_Int32 nNewDX = rint( fWidthSum += aWidthVector[i]);
639 pDXArray[i] = nNewDX - nOldDX;
640 nOldDX = nNewDX;
642 return nPixelWidth;
645 sal_Int32 CTLayout::GetTextBreak( DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor ) const
647 if( !mpCTLine )
649 SAL_INFO("vcl.ct", "GetTextBreak mpCTLine == NULL");
650 return -1;
652 CTTypesetterRef aCTTypeSetter = CTTypesetterCreateWithAttributedString( mpAttrString );
653 CFIndex nBestGuess = (nCharExtra >= 0) ? 0 : mnCharCount;
654 for( int i = 1; i <= mnCharCount; i *= 2 )
656 // guess the target width considering char-extra expansion/condensation
657 const double nTargetWidth = nMaxWidth - nBestGuess * nCharExtra;
658 const double fCTMaxWidth = nTargetWidth / nFactor;
659 // calculate the breaking index for the guessed target width
660 const CFIndex nNewIndex = CTTypesetterSuggestClusterBreak( aCTTypeSetter, 0, fCTMaxWidth );
661 if( nNewIndex >= mnCharCount )
663 CFRelease( aCTTypeSetter );
664 return -1;
666 // check if the original extra-width guess was good
667 if( !nCharExtra )
668 nBestGuess = nNewIndex;
669 if( nBestGuess == nNewIndex )
670 break;
671 // prepare another round for a different number of characters
672 CFIndex nNewGuess = (nNewIndex + nBestGuess + 1) / 2;
673 if( nNewGuess == nBestGuess )
674 nNewGuess += (nNewIndex > nBestGuess) ? +1 : -1;
675 nBestGuess = nNewGuess;
678 // suggest the best fitting cluster break as breaking position
679 CFRelease( aCTTypeSetter );
680 const int nIndex = nBestGuess + mnMinCharPos;
681 SAL_INFO("vcl.ct", "GetTextBreak nIndex:" << nIndex);
682 return nIndex;
685 void CTLayout::GetCaretPositions( int nMaxIndex, long* pCaretXArray ) const
687 DBG_ASSERT( ((nMaxIndex>0)&&!(nMaxIndex&1)),
688 "CTLayout::GetCaretPositions() : invalid number of caret pairs requested");
690 // initialize the caret positions
691 for( int i = 0; i < nMaxIndex; ++i )
693 pCaretXArray[ i ] = -1;
695 for( int n = 0; n <= mnCharCount; ++n )
697 // measure the characters cursor position
698 CGFloat fPos2 = -1;
699 const CGFloat fPos1 = CTLineGetOffsetForStringIndex( mpCTLine, n, &fPos2 );
700 (void)fPos2; // TODO: split cursor at line direction change
702 // update previous trailing position
703 if( n > 0 )
704 pCaretXArray[ 2*n-1 ] = lrint( fPos1 );
706 // update current leading position
707 if( 2*n >= nMaxIndex )
708 break;
709 pCaretXArray[ 2*n+0 ] = lrint( fPos1 );
713 bool CTLayout::GetBoundRect( SalGraphics& rGraphics, Rectangle& rVCLRect ) const
715 // Closely mimic DrawText(), except that instead of calling
716 // CTLineDraw() to draw the line, we call CTLineGetImageBounds()
717 // to get its bounds. But all the coordinate system manipulation
718 // before that is the same => should be factored out?
720 AquaSalGraphics& rAquaGraphics = static_cast<AquaSalGraphics&>(rGraphics);
722 if( !rAquaGraphics.CheckContext() )
723 return false;
725 CGContextSaveGState( rAquaGraphics.mrContext );
726 CGContextScaleCTM( rAquaGraphics.mrContext, 1.0, -1.0 );
727 CGContextSetShouldAntialias( rAquaGraphics.mrContext, !rAquaGraphics.mbNonAntialiasedText );
729 const CGPoint aVclPos = GetTextDrawPosition();
730 CGPoint aTextPos = GetTextDrawPosition();
732 if( mpTextStyle->mfFontRotation != 0.0 )
734 const CGFloat fRadians = mpTextStyle->mfFontRotation;
735 CGContextRotateCTM( rAquaGraphics.mrContext, +fRadians );
737 const CGAffineTransform aInvMatrix = CGAffineTransformMakeRotation( -fRadians );
738 aTextPos = CGPointApplyAffineTransform( aTextPos, aInvMatrix );
741 CGContextSetTextPosition( rAquaGraphics.mrContext, aTextPos.x, aTextPos.y );
742 CGRect aMacRect = CTLineGetImageBounds( mpCTLine, rAquaGraphics.mrContext );
744 if( mpTextStyle->mfFontRotation != 0.0 )
746 const CGFloat fRadians = mpTextStyle->mfFontRotation;
747 const CGAffineTransform aMatrix = CGAffineTransformMakeRotation( +fRadians );
748 aMacRect = CGRectApplyAffineTransform( aMacRect, aMatrix );
751 CGContextRestoreGState( rAquaGraphics.mrContext );
753 rVCLRect.Left() = aVclPos.x + lrint(aMacRect.origin.x);
754 rVCLRect.Right() = aVclPos.x + lrint(aMacRect.origin.x + aMacRect.size.width);
755 rVCLRect.Bottom() = aVclPos.x - lrint(aMacRect.origin.y);
756 rVCLRect.Top() = aVclPos.x - lrint(aMacRect.origin.y + aMacRect.size.height);
758 return true;
761 // glyph fallback is supported directly by Aqua
762 // so methods used only by MultiSalLayout can be dummy implementated
763 void CTLayout::InitFont() const {}
764 void CTLayout::MoveGlyph( int /*nStart*/, long /*nNewXPos*/ ) {}
765 void CTLayout::DropGlyph( int /*nStart*/ ) {}
766 void CTLayout::Simplify( bool /*bIsBase*/ ) {}
768 SalLayout* CoreTextStyle::GetTextLayout() const
770 return new CTLayout( this);
773 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */