xref: /trunk/main/vcl/aqua/source/gdi/ctlayout.cxx (revision f3ecff4e2f8a00dd2b6cbfe8d7d8430dc64e9431)
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 "ctfonts.hxx"
23 
24 // =======================================================================
25 
26 class CTLayout
27 :   public SalLayout
28 {
29 public:
30     explicit        CTLayout( const CTTextStyle* );
31     virtual         ~CTLayout( void );
32 
33     virtual bool    LayoutText( ImplLayoutArgs& );
34     virtual void    AdjustLayout( ImplLayoutArgs& );
35     virtual void    DrawText( SalGraphics& ) const;
36 
37     virtual int     GetNextGlyphs( int nLen, sal_GlyphId* pOutGlyphIds, Point& rPos, int&,
38                         sal_Int32* pGlyphAdvances, int* pCharIndexes ) const;
39 
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;
46 
47     const ImplFontData* GetFallbackFontData( sal_GlyphId ) const;
48 
49     virtual void    InitFont( void) const;
50     virtual void    MoveGlyph( int nStart, long nNewXPos );
51     virtual void    DropGlyph( int nStart );
52     virtual void    Simplify( bool bIsBase );
53 
54 private:
55     const CTTextStyle* const    mpTextStyle;
56 
57     // CoreText specific objects
58     CFAttributedStringRef mpAttrString;
59     CTLineRef mpCTLine;
60 
61     int mnCharCount;        // ==mnEndCharPos-mnMinCharPos
62     int mnTrailingSpaceCount;
63     double mfTrailingSpaceWidth;    // preserves the width of stripped-off trailing space
64 
65     // to prevent overflows
66     // font requests get size limited by downscaling huge fonts
67     // in these cases the font scale becomes something bigger than 1.0
68     float mfFontScale; // TODO: does CoreText have a font size limit?
69 
70     // cached details about the resulting layout
71     // mutable members since these details are all lazy initialized
72     mutable double  mfCachedWidth;          // cached value of resulting typographical width
73 
74     // x-offset relative to layout origin
75     // currently only used in RTL-layouts
76     mutable long    mnBaseAdv;
77 };
78 
79 // =======================================================================
80 
81 CTLayout::CTLayout( const CTTextStyle* pTextStyle )
82 :   mpTextStyle( pTextStyle )
83 ,   mpAttrString( NULL )
84 ,   mpCTLine( NULL )
85 ,   mnCharCount( 0 )
86 ,   mnTrailingSpaceCount( 0 )
87 ,   mfTrailingSpaceWidth( 0.0 )
88 ,   mfFontScale( pTextStyle->mfFontScale )
89 ,   mfCachedWidth( -1 )
90 ,   mnBaseAdv( 0 )
91 {
92     CFRetain( mpTextStyle->GetStyleDict() );
93 }
94 
95 // -----------------------------------------------------------------------
96 
97 CTLayout::~CTLayout()
98 {
99     if( mpCTLine )
100         CFRelease( mpCTLine );
101     if( mpAttrString )
102         CFRelease( mpAttrString );
103     CFRelease( mpTextStyle->GetStyleDict() );
104 }
105 
106 // -----------------------------------------------------------------------
107 
108 bool CTLayout::LayoutText( ImplLayoutArgs& rArgs )
109 {
110     if( mpAttrString )
111         CFRelease( mpAttrString );
112     mpAttrString = NULL;
113     if( mpCTLine )
114         CFRelease( mpCTLine );
115     mpCTLine = NULL;
116 
117     SalLayout::AdjustLayout( rArgs );
118     mnCharCount = mnEndCharPos - mnMinCharPos;
119 
120     // short circuit if there is nothing to do
121     if( mnCharCount <= 0 )
122         return false;
123 
124     // create the CoreText line layout
125     CFStringRef aCFText = CFStringCreateWithCharactersNoCopy( NULL, rArgs.mpStr + mnMinCharPos, mnCharCount, kCFAllocatorNull );
126     mpAttrString = CFAttributedStringCreate( NULL, aCFText, mpTextStyle->GetStyleDict() );
127     mpCTLine = CTLineCreateWithAttributedString( mpAttrString );
128     CFRelease( aCFText);
129 
130     // get info about trailing whitespace to prepare for text justification in AdjustLayout()
131     mnTrailingSpaceCount = 0;
132     for( int i = mnEndCharPos; --i >= mnMinCharPos; ++mnTrailingSpaceCount )
133         if( !IsSpacingGlyph( rArgs.mpStr[i] | GF_ISCHAR )
134         &&  (rArgs.mpStr[i] != 0x00A0) )
135             break;
136     return true;
137 }
138 
139 // -----------------------------------------------------------------------
140 
141 void CTLayout::AdjustLayout( ImplLayoutArgs& rArgs )
142 {
143     if( !mpCTLine)
144         return;
145 
146     int nPixelWidth = rArgs.mnLayoutWidth;
147     if( rArgs.mpDXArray )
148     {
149         // for now we are only interested in the layout width
150         // TODO: use all mpDXArray elements for layouting
151         nPixelWidth = rArgs.mpDXArray[ mnCharCount-1 ];
152     }
153     else if( !nPixelWidth ) // short-circuit if there is nothing to adjust
154         return;
155 
156     // short-circuit when justifying an all-whitespace string
157     if( mnTrailingSpaceCount >= 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( mnTrailingSpaceCount ) {
177         int nTrailingSpaceWidth = 0;
178         if( rArgs.mpDXArray) {
179             const int nFullPixWidth = nPixelWidth;
180             nPixelWidth = rArgs.mpDXArray[ mnCharCount-1-mnTrailingSpaceCount ];
181             nTrailingSpaceWidth = nFullPixWidth - nPixelWidth;
182             mfTrailingSpaceWidth = nTrailingSpaceWidth;
183         } else {
184             if( mfTrailingSpaceWidth <= 0.0 )
185                 mfTrailingSpaceWidth = CTLineGetTrailingWhitespaceWidth( mpCTLine );
186             nTrailingSpaceWidth = rint( mfTrailingSpaceWidth );
187             nPixelWidth -= nTrailingSpaceWidth;
188         }
189         if( nPixelWidth <= 0 )
190             return;
191 
192         // recreate the CoreText line layout without trailing spaces
193         CFRelease( mpCTLine );
194         CFStringRef aCFText = CFStringCreateWithCharactersNoCopy( NULL, rArgs.mpStr + mnMinCharPos,
195             mnCharCount - mnTrailingSpaceCount, kCFAllocatorNull );
196         CFAttributedStringRef pAttrStr = CFAttributedStringCreate( NULL, aCFText, mpTextStyle->GetStyleDict() );
197         mpCTLine = CTLineCreateWithAttributedString( pAttrStr );
198         CFRelease( aCFText);
199         CFRelease( pAttrStr );
200 
201         // in RTL-layouts trailing spaces are leftmost
202         // TODO: use BiDi-algorithm to thoroughly check this assumption
203         if( rArgs.mnFlags & SAL_LAYOUT_BIDI_RTL)
204             mnBaseAdv = nTrailingSpaceWidth;
205     }
206 
207     const double fAdjustedWidth = nPixelWidth / mfFontScale;
208     CTLineRef pNewCTLine = CTLineCreateJustifiedLine( mpCTLine, 1.0, fAdjustedWidth );
209     if( !pNewCTLine ) { // CTLineCreateJustifiedLine can and does fail
210         // handle failure by keeping the unjustified layout
211         // TODO: a better solution such as
212         // - forcing glyph overlap
213         // - changing the font size
214         // - changing the CTM matrix
215         return;
216     }
217     CFRelease( mpCTLine );
218     mpCTLine = pNewCTLine;
219     mfCachedWidth = fAdjustedWidth + mfTrailingSpaceWidth;
220 }
221 
222 // -----------------------------------------------------------------------
223 
224 void CTLayout::DrawText( SalGraphics& rGraphics ) const
225 {
226     AquaSalGraphics& rAquaGraphics = static_cast<AquaSalGraphics&>(rGraphics);
227 
228     // short circuit if there is nothing to do
229     if( (mnCharCount <= 0)
230     ||  !rAquaGraphics.CheckContext() )
231         return;
232 
233     // the view is vertically flipped => flipped glyphs
234     // so apply a temporary transformation that it flips back
235     // also compensate if the font was size limited
236     CGContextSaveGState( rAquaGraphics.mrContext );
237     CGContextScaleCTM( rAquaGraphics.mrContext, +mfFontScale, -mfFontScale );
238     CGContextSetShouldAntialias( rAquaGraphics.mrContext, !rAquaGraphics.mbNonAntialiasedText );
239 
240     // Draw the text
241     const Point aVclPos = GetDrawPosition( Point(mnBaseAdv,0) );
242     CGPoint aTextPos = { +aVclPos.X()/mfFontScale, -aVclPos.Y()/mfFontScale };
243 
244     if( mpTextStyle->mfFontRotation != 0.0 )
245     {
246         const CGFloat fRadians = mpTextStyle->mfFontRotation;
247         CGContextRotateCTM( rAquaGraphics.mrContext, +fRadians );
248 
249         const CGAffineTransform aInvMatrix = CGAffineTransformMakeRotation( -fRadians );
250         aTextPos = CGPointApplyAffineTransform( aTextPos, aInvMatrix );
251     }
252 
253     CGContextSetTextPosition( rAquaGraphics.mrContext, aTextPos.x, aTextPos.y );
254     CTLineDraw( mpCTLine, rAquaGraphics.mrContext );
255 
256     // request an update of the changed window area
257     if( rAquaGraphics.IsWindowGraphics() )
258     {
259         const CGRect aInkRect = CTLineGetImageBounds( mpCTLine, rAquaGraphics.mrContext );
260         const CGRect aRefreshRect = CGContextConvertRectToDeviceSpace( rAquaGraphics.mrContext, aInkRect );
261         rAquaGraphics.RefreshRect( aRefreshRect );
262     }
263 
264     // restore the original graphic context transformations
265     CGContextRestoreGState( rAquaGraphics.mrContext );
266 }
267 
268 // -----------------------------------------------------------------------
269 
270 int CTLayout::GetNextGlyphs( int nLen, sal_GlyphId* pOutGlyphIds, Point& rPos, int& nStart,
271     sal_Int32* pGlyphAdvances, int* pCharIndexes ) const
272 {
273     if( !mpCTLine )
274         return 0;
275 
276     if( nStart < 0 ) // first glyph requested?
277         nStart = 0;
278     nLen = 1; // TODO: handle nLen>1 below
279 
280     // prepare to iterate over the glyph runs
281     int nCount = 0;
282     int nSubIndex = nStart;
283 
284     const DynCoreTextSyms& rCT = DynCoreTextSyms::get();
285     typedef std::vector<CGGlyph> CGGlyphVector;
286     typedef std::vector<CGPoint> CGPointVector;
287     typedef std::vector<CGSize>  CGSizeVector;
288     typedef std::vector<CFIndex> CFIndexVector;
289     CGGlyphVector aCGGlyphVec;
290     CGPointVector aCGPointVec;
291     CGSizeVector  aCGSizeVec;
292     CFIndexVector aCFIndexVec;
293 
294     // TODO: iterate over cached layout
295     CFArrayRef aGlyphRuns = rCT.LineGetGlyphRuns( mpCTLine );
296     const int nRunCount = CFArrayGetCount( aGlyphRuns );
297     for( int nRunIndex = 0; nRunIndex < nRunCount; ++nRunIndex ) {
298         CTRunRef pGlyphRun = (CTRunRef)CFArrayGetValueAtIndex( aGlyphRuns, nRunIndex );
299         const CFIndex nGlyphsInRun = rCT.RunGetGlyphCount( pGlyphRun );
300         // skip to the first glyph run of interest
301         if( nSubIndex >= nGlyphsInRun ) {
302             nSubIndex -= nGlyphsInRun;
303             continue;
304         }
305         const CFRange aFullRange = CFRangeMake( 0, nGlyphsInRun );
306 
307         // get glyph run details
308         const CGGlyph* pCGGlyphIdx = rCT.RunGetGlyphsPtr( pGlyphRun );
309         if( !pCGGlyphIdx ) {
310             aCGGlyphVec.reserve( nGlyphsInRun );
311             CTRunGetGlyphs( pGlyphRun, aFullRange, &aCGGlyphVec[0] );
312             pCGGlyphIdx = &aCGGlyphVec[0];
313         }
314         const CGPoint* pCGGlyphPos = rCT.RunGetPositionsPtr( pGlyphRun );
315         if( !pCGGlyphPos ) {
316             aCGPointVec.reserve( nGlyphsInRun );
317             CTRunGetPositions( pGlyphRun, aFullRange, &aCGPointVec[0] );
318             pCGGlyphPos = &aCGPointVec[0];
319         }
320 
321         const CGSize* pCGGlyphAdvs = NULL;
322         if( pGlyphAdvances) {
323             pCGGlyphAdvs = rCT.RunGetAdvancesPtr( pGlyphRun );
324             if( !pCGGlyphAdvs) {
325                 aCGSizeVec.reserve( nGlyphsInRun );
326                 CTRunGetAdvances( pGlyphRun, aFullRange, &aCGSizeVec[0] );
327                 pCGGlyphAdvs = &aCGSizeVec[0];
328             }
329         }
330 
331         const CFIndex* pCGGlyphStrIdx = NULL;
332         if( pCharIndexes) {
333             pCGGlyphStrIdx = rCT.RunGetStringIndicesPtr( pGlyphRun );
334             if( !pCGGlyphStrIdx) {
335                 aCFIndexVec.reserve( nGlyphsInRun );
336                 CTRunGetStringIndices( pGlyphRun, aFullRange, &aCFIndexVec[0] );
337                 pCGGlyphStrIdx = &aCFIndexVec[0];
338             }
339         }
340 
341         // get the details for each interesting glyph
342         // TODO: handle nLen>1
343         for(; (--nLen >= 0) && (nSubIndex < nGlyphsInRun); ++nSubIndex, ++nStart )
344         {
345             // convert glyph details for VCL
346             *(pOutGlyphIds++) = pCGGlyphIdx[ nSubIndex ];
347             if( pGlyphAdvances )
348                 *(pGlyphAdvances++) = pCGGlyphAdvs[ nSubIndex ].width;
349             if( pCharIndexes )
350                 *(pCharIndexes++) = pCGGlyphStrIdx[ nSubIndex] + mnMinCharPos;
351             if( !nCount++ ) {
352                 const CGPoint& rCurPos = pCGGlyphPos[ nSubIndex ];
353                 rPos = GetDrawPosition( Point( mfFontScale * rCurPos.x, mfFontScale * rCurPos.y) );
354             }
355         }
356         nSubIndex = 0; // prepare for the next glyph run
357         break; // TODO: handle nLen>1
358     }
359 
360     return nCount;
361 }
362 
363 // -----------------------------------------------------------------------
364 
365 long CTLayout::GetTextWidth() const
366 {
367     if( (mnCharCount <= 0) || !mpCTLine )
368         return 0;
369 
370     if( mfCachedWidth < 0.0 )
371         mfCachedWidth = CTLineGetTypographicBounds( mpCTLine, NULL, NULL, NULL );
372 
373     const long nScaledWidth = lrint( mfFontScale * mfCachedWidth );
374     return nScaledWidth;
375 }
376 
377 // -----------------------------------------------------------------------
378 
379 long CTLayout::FillDXArray( sal_Int32* pDXArray ) const
380 {
381     // short circuit requests which don't need full details
382     if( !pDXArray )
383         return GetTextWidth();
384 
385     long nPixWidth = GetTextWidth();
386     if( pDXArray ) {
387         // prepare the sub-pixel accurate logical-width array
388         ::std::vector<float> aWidthVector( mnCharCount );
389         if( mnTrailingSpaceCount && (mfTrailingSpaceWidth > 0.0) ) {
390             const double fOneWidth = mfTrailingSpaceWidth / mnTrailingSpaceCount;
391             for( int i = 1; i <= mnTrailingSpaceCount; ++i)
392                 aWidthVector[ mnCharCount - i ] = fOneWidth;
393         }
394         // measure advances in each glyph run
395         CFArrayRef aGlyphRuns = CTLineGetGlyphRuns( mpCTLine );
396         const int nRunCount = CFArrayGetCount( aGlyphRuns );
397         typedef std::vector<CGSize> CGSizeVector;
398         CGSizeVector aSizeVec;
399         typedef std::vector<CFIndex> CFIndexVector;
400         CFIndexVector aIndexVec;
401         for( int nRunIndex = 0; nRunIndex < nRunCount; ++nRunIndex ) {
402             CTRunRef pGlyphRun = (CTRunRef)CFArrayGetValueAtIndex( aGlyphRuns, nRunIndex );
403             const CFIndex nGlyphCount = CTRunGetGlyphCount( pGlyphRun );
404             const CFRange aFullRange = CFRangeMake( 0, nGlyphCount );
405             aSizeVec.resize( nGlyphCount );
406             aIndexVec.resize( nGlyphCount );
407             CTRunGetAdvances( pGlyphRun, aFullRange, &aSizeVec[0] );
408             CTRunGetStringIndices( pGlyphRun, aFullRange, &aIndexVec[0] );
409             for( int i = 0; i != nGlyphCount; ++i ) {
410                 const int nRelIdx = aIndexVec[i];
411                 aWidthVector[nRelIdx] += aSizeVec[i].width;
412             }
413         }
414 
415         // convert the sub-pixel accurate array into classic pDXArray integers
416         float fWidthSum = 0.0;
417         sal_Int32 nOldDX = 0;
418         for( int i = 0; i < mnCharCount; ++i) {
419             const sal_Int32 nNewDX = rint( fWidthSum += aWidthVector[i]);
420             pDXArray[i] = nNewDX - nOldDX;
421             nOldDX = nNewDX;
422         }
423     }
424 
425     return nPixWidth;
426 }
427 
428 // -----------------------------------------------------------------------
429 
430 int CTLayout::GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const
431 {
432     if( !mpCTLine )
433         return STRING_LEN;
434 
435     CTTypesetterRef aCTTypeSetter = CTTypesetterCreateWithAttributedString( mpAttrString );
436     const double fCTMaxWidth = (double)nMaxWidth / (nFactor * mfFontScale);
437     CFIndex nIndex = CTTypesetterSuggestClusterBreak( aCTTypeSetter, 0, fCTMaxWidth );
438     if( nIndex >= mnCharCount )
439         return STRING_LEN;
440 
441     nIndex += mnMinCharPos;
442     return (int)nIndex;
443 }
444 
445 // -----------------------------------------------------------------------
446 
447 void CTLayout::GetCaretPositions( int nMaxIndex, sal_Int32* pCaretXArray ) const
448 {
449     DBG_ASSERT( ((nMaxIndex>0)&&!(nMaxIndex&1)),
450         "CTLayout::GetCaretPositions() : invalid number of caret pairs requested");
451 
452     // initialize the caret positions
453     for( int i = 0; i < nMaxIndex; ++i )
454         pCaretXArray[ i ] = -1;
455 
456     const DynCoreTextSyms& rCT = DynCoreTextSyms::get();
457     for( int n = 0; n <= mnCharCount; ++n )
458     {
459         // measure the characters cursor position
460         CGFloat fPos2 = -1;
461         const CGFloat fPos1 = rCT.LineGetOffsetForStringIndex( mpCTLine, n, &fPos2 );
462         (void)fPos2; // TODO: split cursor at line direction change
463         // update previous trailing position
464         if( n > 0 )
465             pCaretXArray[ 2*n-1 ] = lrint( fPos1 * mfFontScale );
466         // update current leading position
467         if( 2*n >= nMaxIndex )
468             break;
469         pCaretXArray[ 2*n+0 ] = lrint( fPos1 * mfFontScale );
470     }
471 }
472 
473 // -----------------------------------------------------------------------
474 
475 bool CTLayout::GetBoundRect( SalGraphics& rGraphics, Rectangle& rVCLRect ) const
476 {
477     AquaSalGraphics& rAquaGraphics = static_cast<AquaSalGraphics&>(rGraphics);
478     CGRect aMacRect = CTLineGetImageBounds( mpCTLine, rAquaGraphics.mrContext );
479     CGPoint aMacPos = CGContextGetTextPosition( rAquaGraphics.mrContext );
480     aMacRect.origin.x -= aMacPos.x;
481     aMacRect.origin.y -= aMacPos.y;
482 
483     const Point aPos = GetDrawPosition( Point(mnBaseAdv, 0) );
484 
485     // CoreText top-bottom are vertically flipped from a VCL aspect
486     rVCLRect.Left()   = aPos.X() + mfFontScale * aMacRect.origin.x;
487     rVCLRect.Right()  = aPos.X() + mfFontScale * (aMacRect.origin.x + aMacRect.size.width);
488     rVCLRect.Bottom() = aPos.Y() - mfFontScale * aMacRect.origin.y;
489     rVCLRect.Top()    = aPos.Y() - mfFontScale * (aMacRect.origin.y + aMacRect.size.height);
490     return true;
491 }
492 
493 // =======================================================================
494 
495 // glyph fallback is supported directly by Aqua
496 // so methods used only by MultiSalLayout can be dummy implementated
497 bool CTLayout::GetGlyphOutlines( SalGraphics&, PolyPolyVector& ) const { return false; }
498 void CTLayout::InitFont() const {}
499 void CTLayout::MoveGlyph( int /*nStart*/, long /*nNewXPos*/ ) {}
500 void CTLayout::DropGlyph( int /*nStart*/ ) {}
501 void CTLayout::Simplify( bool /*bIsBase*/ ) {}
502 
503 // get the ImplFontData for a glyph fallback font
504 // for a glyphid that was returned by CTLayout::GetNextGlyphs()
505 const ImplFontData* CTLayout::GetFallbackFontData( sal_GlyphId /*aGlyphId*/ ) const
506 {
507 #if 0
508     // check if any fallback fonts were needed
509     if( !mpFallbackInfo )
510         return NULL;
511     // check if the current glyph needs a fallback font
512     int nFallbackLevel = (aGlyphId & GF_FONTMASK) >> GF_FONTSHIFT;
513     if( !nFallbackLevel )
514         return NULL;
515     pFallbackFont = mpFallbackInfo->GetFallbackFontData( nFallbackLevel );
516 #else
517     // let CoreText's font cascading handle glyph fallback
518     const ImplFontData* pFallbackFont = NULL;
519 #endif
520     return pFallbackFont;
521 }
522 
523 // =======================================================================
524 
525 SalLayout* CTTextStyle::GetTextLayout( void ) const
526 {
527     return new CTLayout( this);
528 }
529 
530 // =======================================================================
531 
532