xref: /trunk/main/vcl/aqua/source/gdi/ctlayout.cxx (revision a206ee714f966ac34d586aa0448e90cdf7eed74e)
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     // short-circuit when justifying an all-whitespace string
170     if( mnTrailingSpaces >= mnCharCount)
171     {
172         mfCachedWidth = mfTrailingSpaceWidth = nPixelWidth / mfFontScale;
173         return;
174     }
175 
176     // in RTL-layouts trailing spaces are leftmost
177     // TODO: use BiDi-algorithm to thoroughly check this assumption
178     if( rArgs.mnFlags & SAL_LAYOUT_BIDI_RTL)
179         mnBaseAdv = nTrailingSpaceWidth;
180 
181     // return early if there is nothing to do
182     if( nPixelWidth <= 0 )
183         return;
184 
185     // HACK: justification requests which change the width by just one pixel are probably
186     // #i86038# introduced by lossy conversions between integer based coordinate system
187     if( (nOrigWidth >= nPixelWidth-1) && (nOrigWidth <= nPixelWidth+1) )
188         return;
189 
190     // if the text to be justified has whitespace in it then
191     // - Writer goes crazy with its HalfSpace magic
192     // - LayoutEngine handles spaces specially (in particular at the text start or end)
193     if( mnTrailingSpaces ) {
194         // adjust for Writer's SwFntObj::DrawText() Halfspace magic at the text end
195         std::vector<sal_Int32> aOrigDXAry;
196         aOrigDXAry.resize( mnCharCount);
197         FillDXArray( &aOrigDXAry[0] );
198         int nLastCharSpace = rArgs.mpDXArray[ mnCharCount-1-mnTrailingSpaces ]
199             - aOrigDXAry[ mnCharCount-1-mnTrailingSpaces ];
200         nPixelWidth -= nLastCharSpace;
201         if( nPixelWidth < 0 )
202             return;
203         // recreate the CoreText line layout without trailing spaces
204         CFRelease( mpCTLine );
205         CFStringRef aCFText = CFStringCreateWithCharactersNoCopy( NULL, rArgs.mpStr + mnMinCharPos,
206             mnCharCount - mnTrailingSpaces, kCFAllocatorNull );
207         CFAttributedStringRef pAttrStr = CFAttributedStringCreate( NULL, aCFText, mpTextStyle->GetStyleDict() );
208         mpCTLine = CTLineCreateWithAttributedString( pAttrStr );
209         CFRelease( aCFText);
210         CFRelease( pAttrStr );
211     }
212 
213     CTLineRef pNewCTLine = rCT.LineCreateJustifiedLine( mpCTLine, 1.0, nPixelWidth / mfFontScale );
214     if( !pNewCTLine ) { // CTLineCreateJustifiedLine can and does fail
215         // handle failure by keeping the unjustified layout
216         // TODO: a better solution such as
217         // - forcing glyph overlap
218         // - changing the font size
219         // - changing the CTM matrix
220         return;
221     }
222     CFRelease( mpCTLine );
223     mpCTLine = pNewCTLine;
224     mfCachedWidth = -1; // TODO: can we set it directly to target width we requested? For now we re-measure
225     mfTrailingSpaceWidth = 0;
226 }
227 
228 // -----------------------------------------------------------------------
229 
230 void CTLayout::DrawText( SalGraphics& rGraphics ) const
231 {
232     AquaSalGraphics& rAquaGraphics = static_cast<AquaSalGraphics&>(rGraphics);
233 
234     // short circuit if there is nothing to do
235     if( (mnCharCount <= 0)
236     ||  !rAquaGraphics.CheckContext() )
237         return;
238 
239     // the view is vertically flipped => flipped glyphs
240     // so apply a temporary transformation that it flips back
241     // also compensate if the font was size limited
242     CGContextSaveGState( rAquaGraphics.mrContext );
243     CGContextScaleCTM( rAquaGraphics.mrContext, +mfFontScale, -mfFontScale );
244     CGContextSetShouldAntialias( rAquaGraphics.mrContext, !rAquaGraphics.mbNonAntialiasedText );
245 
246     // Draw the text
247     const Point aVclPos = GetDrawPosition( Point(mnBaseAdv,0) );
248     CGPoint aTextPos = { +aVclPos.X()/mfFontScale, -aVclPos.Y()/mfFontScale };
249 
250     if( mpTextStyle->mfFontRotation != 0.0 )
251     {
252         const CGFloat fRadians = mpTextStyle->mfFontRotation;
253         CGContextRotateCTM( rAquaGraphics.mrContext, +fRadians );
254 
255         const CGAffineTransform aInvMatrix = CGAffineTransformMakeRotation( -fRadians );
256         aTextPos = CGPointApplyAffineTransform( aTextPos, aInvMatrix );
257     }
258 
259     CGContextSetTextPosition( rAquaGraphics.mrContext, aTextPos.x, aTextPos.y );
260     CTLineDraw( mpCTLine, rAquaGraphics.mrContext );
261 
262     // request an update of the changed window area
263     if( rAquaGraphics.IsWindowGraphics() )
264     {
265         const CGRect aInkRect = CTLineGetImageBounds( mpCTLine, rAquaGraphics.mrContext );
266         const CGRect aRefreshRect = CGContextConvertRectToDeviceSpace( rAquaGraphics.mrContext, aInkRect );
267         rAquaGraphics.RefreshRect( aRefreshRect );
268     }
269 
270     // restore the original graphic context transformations
271     CGContextRestoreGState( rAquaGraphics.mrContext );
272 }
273 
274 // -----------------------------------------------------------------------
275 
276 int CTLayout::GetNextGlyphs( int nLen, sal_GlyphId* pOutGlyphIds, Point& rPos, int& nStart,
277     sal_Int32* pGlyphAdvances, int* pCharIndexes ) const
278 {
279     if( !mpCTLine )
280         return 0;
281 
282     if( nStart < 0 ) // first glyph requested?
283         nStart = 0;
284     nLen = 1; // TODO: handle nLen>1 below
285 
286     // prepare to iterate over the glyph runs
287     int nCount = 0;
288     int nSubIndex = nStart;
289 
290     const DynCoreTextSyms& rCT = DynCoreTextSyms::get();
291     typedef std::vector<CGGlyph> CGGlyphVector;
292     typedef std::vector<CGPoint> CGPointVector;
293     typedef std::vector<CGSize>  CGSizeVector;
294     typedef std::vector<CFIndex> CFIndexVector;
295     CGGlyphVector aCGGlyphVec;
296     CGPointVector aCGPointVec;
297     CGSizeVector  aCGSizeVec;
298     CFIndexVector aCFIndexVec;
299 
300     // TODO: iterate over cached layout
301     CFArrayRef aGlyphRuns = rCT.LineGetGlyphRuns( mpCTLine );
302     const int nRunCount = CFArrayGetCount( aGlyphRuns );
303     for( int nRunIndex = 0; nRunIndex < nRunCount; ++nRunIndex ) {
304         CTRunRef pGlyphRun = (CTRunRef)CFArrayGetValueAtIndex( aGlyphRuns, nRunIndex );
305         const CFIndex nGlyphsInRun = rCT.RunGetGlyphCount( pGlyphRun );
306         // skip to the first glyph run of interest
307         if( nSubIndex >= nGlyphsInRun ) {
308             nSubIndex -= nGlyphsInRun;
309             continue;
310         }
311         const CFRange aFullRange = CFRangeMake( 0, nGlyphsInRun );
312 
313         // get glyph run details
314         const CGGlyph* pCGGlyphIdx = rCT.RunGetGlyphsPtr( pGlyphRun );
315         if( !pCGGlyphIdx ) {
316             aCGGlyphVec.reserve( nGlyphsInRun );
317             CTRunGetGlyphs( pGlyphRun, aFullRange, &aCGGlyphVec[0] );
318             pCGGlyphIdx = &aCGGlyphVec[0];
319         }
320         const CGPoint* pCGGlyphPos = rCT.RunGetPositionsPtr( pGlyphRun );
321         if( !pCGGlyphPos ) {
322             aCGPointVec.reserve( nGlyphsInRun );
323             CTRunGetPositions( pGlyphRun, aFullRange, &aCGPointVec[0] );
324             pCGGlyphPos = &aCGPointVec[0];
325         }
326 
327         const CGSize* pCGGlyphAdvs = NULL;
328         if( pGlyphAdvances) {
329             pCGGlyphAdvs = rCT.RunGetAdvancesPtr( pGlyphRun );
330             if( !pCGGlyphAdvs) {
331                 aCGSizeVec.reserve( nGlyphsInRun );
332                 CTRunGetAdvances( pGlyphRun, aFullRange, &aCGSizeVec[0] );
333                 pCGGlyphAdvs = &aCGSizeVec[0];
334             }
335         }
336 
337         const CFIndex* pCGGlyphStrIdx = NULL;
338         if( pCharIndexes) {
339             pCGGlyphStrIdx = rCT.RunGetStringIndicesPtr( pGlyphRun );
340             if( !pCGGlyphStrIdx) {
341                 aCFIndexVec.reserve( nGlyphsInRun );
342                 CTRunGetStringIndices( pGlyphRun, aFullRange, &aCFIndexVec[0] );
343                 pCGGlyphStrIdx = &aCFIndexVec[0];
344             }
345         }
346 
347         // get the details for each interesting glyph
348         // TODO: handle nLen>1
349         for(; (--nLen >= 0) && (nSubIndex < nGlyphsInRun); ++nSubIndex, ++nStart )
350         {
351             // convert glyph details for VCL
352             *(pOutGlyphIds++) = pCGGlyphIdx[ nSubIndex ];
353             if( pGlyphAdvances )
354                 *(pGlyphAdvances++) = pCGGlyphAdvs[ nSubIndex ].width;
355             if( pCharIndexes )
356                 *(pCharIndexes++) = pCGGlyphStrIdx[ nSubIndex] + mnMinCharPos;
357             if( !nCount++ ) {
358                 const CGPoint& rCurPos = pCGGlyphPos[ nSubIndex ];
359                 rPos = GetDrawPosition( Point( mfFontScale * rCurPos.x, mfFontScale * rCurPos.y) );
360             }
361         }
362         nSubIndex = 0; // prepare for the next glyph run
363         break; // TODO: handle nLen>1
364     }
365 
366     return nCount;
367 }
368 
369 // -----------------------------------------------------------------------
370 
371 long CTLayout::GetTextWidth() const
372 {
373     if( (mnCharCount <= 0) || !mpCTLine )
374         return 0;
375 
376     if( mfCachedWidth < 0.0 )
377         mfCachedWidth = CTLineGetTypographicBounds( mpCTLine, NULL, NULL, NULL );
378 
379     const long nScaledWidth = lrint( mfFontScale * mfCachedWidth );
380     return nScaledWidth;
381 }
382 
383 // -----------------------------------------------------------------------
384 
385 long CTLayout::FillDXArray( sal_Int32* pDXArray ) const
386 {
387     // short circuit requests which don't need full details
388     if( !pDXArray )
389         return GetTextWidth();
390 
391     long nPixWidth = GetTextWidth();
392     if( pDXArray ) {
393         // initialize the result array
394         for( int i = 0; i < mnCharCount; ++i)
395             pDXArray[i] = 0;
396         // handle each glyph run
397         CFArrayRef aGlyphRuns = CTLineGetGlyphRuns( mpCTLine );
398         const int nRunCount = CFArrayGetCount( aGlyphRuns );
399         typedef std::vector<CGSize> CGSizeVector;
400         CGSizeVector aSizeVec;
401         typedef std::vector<CFIndex> CFIndexVector;
402         CFIndexVector aIndexVec;
403         for( int nRunIndex = 0; nRunIndex < nRunCount; ++nRunIndex ) {
404             CTRunRef pGlyphRun = (CTRunRef)CFArrayGetValueAtIndex( aGlyphRuns, nRunIndex );
405             const CFIndex nGlyphCount = CTRunGetGlyphCount( pGlyphRun );
406             const CFRange aFullRange = CFRangeMake( 0, nGlyphCount );
407             aSizeVec.reserve( nGlyphCount );
408             aIndexVec.reserve( nGlyphCount );
409             CTRunGetAdvances( pGlyphRun, aFullRange, &aSizeVec[0] );
410             CTRunGetStringIndices( pGlyphRun, aFullRange, &aIndexVec[0] );
411             for( int i = 0; i != nGlyphCount; ++i ) {
412                 const int nRelIdx = aIndexVec[i];
413                 pDXArray[ nRelIdx ] += aSizeVec[i].width;
414             }
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