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