1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * This file is part of OpenOffice.org.
11 * OpenOffice.org is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License version 3
13 * only, as published by the Free Software Foundation.
15 * OpenOffice.org is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License version 3 for more details
19 * (a copy is included in the LICENSE file that accompanied this code).
21 * You should have received a copy of the GNU Lesser General Public License
22 * version 3 along with OpenOffice.org. If not, see
23 * <http://www.openoffice.org/license.html>
24 * for a copy of the LGPLv3 License.
26 ************************************************************************/
28 #include "vcl/salgdi.hxx"
29 #include "saldata.hxx"
31 #include "vcl/sallayout.hxx"
32 #include "salatsuifontutils.hxx"
33 #include "tools/debug.hxx"
37 // =======================================================================
39 class ATSLayout
: public SalLayout
42 ATSLayout( ATSUStyle
&, float fFontScale
);
45 virtual bool LayoutText( ImplLayoutArgs
& );
46 virtual void AdjustLayout( ImplLayoutArgs
& );
47 virtual void DrawText( SalGraphics
& ) const;
49 virtual int GetNextGlyphs( int nLen
, sal_GlyphId
* pGlyphs
, Point
& rPos
, int&,
50 sal_Int32
* pGlyphAdvances
, int* pCharIndexes
) const;
52 virtual long GetTextWidth() const;
53 virtual long FillDXArray( long* pDXArray
) const;
54 virtual int GetTextBreak( long nMaxWidth
, long nCharExtra
, int nFactor
) const;
55 virtual void GetCaretPositions( int nArraySize
, long* pCaretXArray
) const;
56 virtual bool GetGlyphOutlines( SalGraphics
&, PolyPolyVector
& ) const;
57 virtual bool GetBoundRect( SalGraphics
&, Rectangle
& ) const;
59 const ImplFontData
* GetFallbackFontData( sal_GlyphId
) const;
61 virtual void InitFont();
62 virtual void MoveGlyph( int nStart
, long nNewXPos
);
63 virtual void DropGlyph( int nStart
);
64 virtual void Simplify( bool bIsBase
);
67 ATSUStyle
& mrATSUStyle
;
68 ATSUTextLayout maATSULayout
;
69 int mnCharCount
; // ==mnEndCharPos-mnMinCharPos
70 // to prevent ATS overflowing the Fixed16.16 values
71 // ATS font requests get size limited by downscaling huge fonts
72 // in these cases the font scale becomes something bigger than 1.0
76 bool InitGIA( ImplLayoutArgs
* pArgs
= NULL
) const;
77 bool GetIdealX() const;
78 bool GetDeltaY() const;
80 int Fixed2Vcl( Fixed
) const; // convert ATSU-Fixed units to VCL units
81 int AtsuPix2Vcl( int ) const; // convert ATSU-Pixel units to VCL units
82 Fixed
Vcl2Fixed( int ) const; // convert VCL units to ATSU-Fixed units
84 // cached details about the resulting layout
85 // mutable members since these details are all lazy initialized
86 mutable int mnGlyphCount
; // glyph count
87 mutable Fixed mnCachedWidth
; // cached value of resulting typographical width
88 int mnTrailingSpaceWidth
; // in Pixels
90 mutable ATSGlyphRef
* mpGlyphIds
; // ATSU glyph ids
91 mutable Fixed
* mpCharWidths
; // map relative charpos to charwidth
92 mutable int* mpChars2Glyphs
; // map relative charpos to absolute glyphpos
93 mutable int* mpGlyphs2Chars
; // map absolute glyphpos to absolute charpos
94 mutable bool* mpGlyphRTLFlags
; // BiDi status for glyphs: true if RTL
95 mutable Fixed
* mpGlyphAdvances
; // contains glyph widths for the justified layout
96 mutable Fixed
* mpGlyphOrigAdvs
; // contains glyph widths for the unjustified layout
97 mutable Fixed
* mpDeltaY
; // vertical offset from the baseline
99 struct SubPortion
{ int mnMinCharPos
, mnEndCharPos
; Fixed mnXOffset
; };
100 typedef std::vector
<SubPortion
> SubPortionVector
;
101 mutable SubPortionVector maSubPortions
; // Writer&ATSUI layouts can differ quite a bit...
103 // storing details about fonts used in glyph-fallback for this layout
104 mutable class FallbackInfo
* mpFallbackInfo
;
106 // x-offset relative to layout origin
107 // currently only used in RTL-layouts
108 mutable Fixed mnBaseAdv
;
114 FallbackInfo() : mnMaxLevel(0) {}
115 int AddFallback( ATSUFontID
);
116 const ImplFontData
* GetFallbackFontData( int nLevel
) const;
119 const ImplMacFontData
* maFontData
[ MAX_FALLBACK
];
120 ATSUFontID maATSUFontId
[ MAX_FALLBACK
];
124 // =======================================================================
126 ATSLayout::ATSLayout( ATSUStyle
& rATSUStyle
, float fFontScale
)
127 : mrATSUStyle( rATSUStyle
),
128 maATSULayout( NULL
),
130 mfFontScale( fFontScale
),
133 mnTrailingSpaceWidth( 0 ),
135 mpCharWidths( NULL
),
136 mpChars2Glyphs( NULL
),
137 mpGlyphs2Chars( NULL
),
138 mpGlyphRTLFlags( NULL
),
139 mpGlyphAdvances( NULL
),
140 mpGlyphOrigAdvs( NULL
),
142 mpFallbackInfo( NULL
),
146 // -----------------------------------------------------------------------
148 ATSLayout::~ATSLayout()
151 ATSUDirectReleaseLayoutDataArrayPtr( NULL
,
152 kATSUDirectDataBaselineDeltaFixedArray
, (void**)&mpDeltaY
);
155 ATSUDisposeTextLayout( maATSULayout
);
157 delete[] mpGlyphRTLFlags
;
158 delete[] mpGlyphs2Chars
;
159 delete[] mpChars2Glyphs
;
160 if( mpCharWidths
!= mpGlyphAdvances
)
161 delete[] mpCharWidths
;
163 delete[] mpGlyphOrigAdvs
;
164 delete[] mpGlyphAdvances
;
166 delete mpFallbackInfo
;
169 // -----------------------------------------------------------------------
171 inline int ATSLayout::Fixed2Vcl( Fixed nFixed
) const
173 float fFloat
= mfFontScale
* FixedToFloat( nFixed
);
174 return static_cast<int>(fFloat
+ 0.5);
177 // -----------------------------------------------------------------------
179 inline int ATSLayout::AtsuPix2Vcl( int nAtsuPixel
) const
181 float fVclPixel
= mfFontScale
* nAtsuPixel
;
182 fVclPixel
+= (fVclPixel
>=0) ? +0.5 : -0.5; // prepare rounding to int
183 int nVclPixel
= static_cast<int>( fVclPixel
);
187 // -----------------------------------------------------------------------
189 inline Fixed
ATSLayout::Vcl2Fixed( int nPixel
) const
191 return FloatToFixed( nPixel
/ mfFontScale
);
194 // -----------------------------------------------------------------------
196 * ATSLayout::LayoutText : Manage text layouting
198 * @param rArgs: contains array of char to be layouted, starting and ending position of the text to layout
200 * Typographic layout of text by using the style maATSUStyle
202 * @return : true if everything is ok
204 bool ATSLayout::LayoutText( ImplLayoutArgs
& rArgs
)
207 ATSUDisposeTextLayout( maATSULayout
);
212 // set up our locals, verify parameters...
213 DBG_ASSERT( (rArgs
.mpStr
!=NULL
), "ATSLayout::LayoutText() with rArgs.mpStr==NULL !!!");
214 DBG_ASSERT( (mrATSUStyle
!=NULL
), "ATSLayout::LayoutText() with ATSUStyle==NULL !!!");
216 SalLayout::AdjustLayout( rArgs
);
217 mnCharCount
= mnEndCharPos
- mnMinCharPos
;
219 // Workaround a bug in ATSUI with empty string
223 #if (OSL_DEBUG_LEVEL > 3)
226 ATSUGetAttribute( mrATSUStyle
, kATSUSizeTag
, sizeof(fFontSize
), &fFontSize
, &nDummy
);
227 String
aUniName( &rArgs
.mpStr
[rArgs
.mnMinCharPos
], mnCharCount
);
228 ByteString
aCName( aUniName
, RTL_TEXTENCODING_UTF8
);
229 fprintf( stderr
, "ATSLayout( \"%s\" %d..%d of %d) with h=%4.1f\n",
230 aCName
.GetBuffer(),rArgs
.mnMinCharPos
,rArgs
.mnEndCharPos
,rArgs
.mnLength
,Fix2X(fFontSize
) );
233 // create the ATSUI layout
234 UniCharCount nRunLengths
[1] = { mnCharCount
};
235 const int nRunCount
= sizeof(nRunLengths
)/sizeof(*nRunLengths
);
236 OSStatus eStatus
= ATSUCreateTextLayoutWithTextPtr( rArgs
.mpStr
,
237 rArgs
.mnMinCharPos
, mnCharCount
, rArgs
.mnLength
,
238 nRunCount
, &nRunLengths
[0], &mrATSUStyle
,
241 DBG_ASSERT( (eStatus
==noErr
), "ATSUCreateTextLayoutWithTextPtr failed\n");
242 if( eStatus
!= noErr
)
245 // prepare setting of layout controls
246 static const int nMaxTagCount
= 1;
247 ATSUAttributeTag aTagAttrs
[ nMaxTagCount
];
248 ByteCount aTagSizes
[ nMaxTagCount
];
249 ATSUAttributeValuePtr aTagValues
[ nMaxTagCount
];
251 // prepare control of "glyph fallback"
252 const SalData
* pSalData
= GetSalData();
253 ATSUFontFallbacks aFontFallbacks
= pSalData
->mpFontList
->maFontFallbacks
;
254 aTagAttrs
[0] = kATSULineFontFallbacksTag
;
255 aTagSizes
[0] = sizeof( ATSUFontFallbacks
);
256 aTagValues
[0] = &aFontFallbacks
;
258 // set paragraph layout controls
259 ATSUSetLayoutControls( maATSULayout
, 1, aTagAttrs
, aTagSizes
, aTagValues
);
261 // enable "glyph fallback"
262 ATSUSetTransientFontMatching( maATSULayout
, true );
264 // control run-specific layout controls
265 if( (rArgs
.mnFlags
& SAL_LAYOUT_BIDI_STRONG
) != 0 )
267 // control BiDi defaults
268 MacOSBOOL nLineDirTag
= kATSULeftToRightBaseDirection
;
269 if( (rArgs
.mnFlags
& SAL_LAYOUT_BIDI_RTL
) != 0 )
270 nLineDirTag
= kATSURightToLeftBaseDirection
;
271 aTagAttrs
[0] = kATSULineDirectionTag
;
272 aTagSizes
[0] = sizeof( nLineDirTag
);
273 aTagValues
[0] = &nLineDirTag
;
274 // set run-specific layout controls
275 #if 0 // why don't line-controls work as reliably as layout-controls???
276 ATSUSetLineControls( maATSULayout
, rArgs
.mnMinCharPos
, 1, aTagAttrs
, aTagSizes
, aTagValues
);
278 ATSUSetLayoutControls( maATSULayout
, 1, aTagAttrs
, aTagSizes
, aTagValues
);
285 // -----------------------------------------------------------------------
287 * ATSLayout::AdjustLayout : Adjust layout style
289 * @param rArgs: contains attributes relevant to do a text specific layout
291 * Adjust text layout by moving glyphs to match the requested logical widths
295 void ATSLayout::AdjustLayout( ImplLayoutArgs
& rArgs
)
297 int nOrigWidth
= GetTextWidth();
298 int nPixelWidth
= rArgs
.mnLayoutWidth
;
299 if( !nPixelWidth
&& rArgs
.mpDXArray
) {
300 // for now we are only interested in the layout width
301 // TODO: use all mpDXArray elements for layouting
302 nPixelWidth
= rArgs
.mpDXArray
[ mnCharCount
- 1 ];
304 // workaround for ATSUI not using trailing spaces for justification
305 mnTrailingSpaceWidth
= 0;
307 while( (--i
> 0) && IsSpacingGlyph( rArgs
.mpStr
[mnMinCharPos
+i
]|GF_ISCHAR
) )
308 mnTrailingSpaceWidth
+= rArgs
.mpDXArray
[i
] - rArgs
.mpDXArray
[i
-1];
311 // #i91685# trailing letters are left aligned (right aligned for RTL)
312 mnTrailingSpaceWidth
+= rArgs
.mpDXArray
[i
];
314 mnTrailingSpaceWidth
-= rArgs
.mpDXArray
[i
-1];
315 InitGIA(); // ensure valid mpCharWidths[]
316 mnTrailingSpaceWidth
-= Fixed2Vcl( mpCharWidths
[i
] );
317 // ignore trailing space for calculating the available width
318 nOrigWidth
-= mnTrailingSpaceWidth
;
319 nPixelWidth
-= mnTrailingSpaceWidth
;
320 // in RTL-layouts trailing spaces are leftmost
321 // TODO: use BiDi-algorithm to thoroughly check this assumption
322 if( rArgs
.mnFlags
& SAL_LAYOUT_BIDI_RTL
)
323 mnBaseAdv
= mnTrailingSpaceWidth
;
325 // return early if there is nothing to do
329 // HACK: justification requests which change the width by just one pixel are probably
330 // #i86038# introduced by lossy conversions between integer based coordinate system
331 if( (nOrigWidth
>= nPixelWidth
-1) && (nOrigWidth
<= nPixelWidth
+1) )
334 ATSUAttributeTag nTags
[3];
335 ATSUAttributeValuePtr nVals
[3];
338 Fixed nFixedWidth
= Vcl2Fixed( nPixelWidth
);
339 mnCachedWidth
= nFixedWidth
;
340 Fract nFractFactor
= kATSUFullJustification
;
341 ATSLineLayoutOptions nLineLayoutOptions
= kATSLineHasNoHangers
| kATSLineHasNoOpticalAlignment
| kATSLineBreakToNearestCharacter
;
343 nTags
[0] = kATSULineWidthTag
;
344 nVals
[0] = &nFixedWidth
;
345 nBytes
[0] = sizeof(Fixed
);
346 nTags
[1] = kATSULineLayoutOptionsTag
;
347 nVals
[1] = &nLineLayoutOptions
;
348 nBytes
[1] = sizeof(ATSLineLayoutOptions
);
349 nTags
[2] = kATSULineJustificationFactorTag
;
350 nVals
[2] = &nFractFactor
;
351 nBytes
[2] = sizeof(Fract
);
353 OSStatus eStatus
= ATSUSetLayoutControls( maATSULayout
, 3, nTags
, nBytes
, nVals
);
354 if( eStatus
!= noErr
)
357 // check result of the justied layout
358 if( rArgs
.mpDXArray
)
362 // -----------------------------------------------------------------------
364 * ATSLayout::DrawText : Draw text to screen
366 * @param rGraphics: device to draw to
368 * Draw the layouted text to the CGContext
372 void ATSLayout::DrawText( SalGraphics
& rGraphics
) const
374 AquaSalGraphics
& rAquaGraphics
= static_cast<AquaSalGraphics
&>(rGraphics
);
376 // short circuit if there is nothing to do
377 if( (mnCharCount
<= 0)
378 || !rAquaGraphics
.CheckContext() )
381 // the view is vertically flipped => flipped glyphs
382 // so apply a temporary transformation that it flips back
383 // also compensate if the font was size limited
384 CGContextSaveGState( rAquaGraphics
.mrContext
);
385 CGContextScaleCTM( rAquaGraphics
.mrContext
, +mfFontScale
, -mfFontScale
);
386 CGContextSetShouldAntialias( rAquaGraphics
.mrContext
, !rAquaGraphics
.mbNonAntialiasedText
);
388 // prepare ATSUI drawing attributes
389 static const ItemCount nMaxControls
= 8;
390 ATSUAttributeTag theTags
[ nMaxControls
];
391 ByteCount theSizes
[ nMaxControls
];
392 ATSUAttributeValuePtr theValues
[ nMaxControls
];
393 ItemCount numcontrols
= 0;
395 // Tell ATSUI to use CoreGraphics
396 theTags
[numcontrols
] = kATSUCGContextTag
;
397 theSizes
[numcontrols
] = sizeof( CGContextRef
);
398 theValues
[numcontrols
++] = &rAquaGraphics
.mrContext
;
400 // Rotate if necessary
401 if( rAquaGraphics
.mnATSUIRotation
!= 0 )
403 Fixed theAngle
= rAquaGraphics
.mnATSUIRotation
;
404 theTags
[numcontrols
] = kATSULineRotationTag
;
405 theSizes
[numcontrols
] = sizeof( Fixed
);
406 theValues
[numcontrols
++] = &theAngle
;
409 DBG_ASSERT( (numcontrols
<= nMaxControls
), "ATSLayout::DrawText() numcontrols overflow" );
410 OSStatus theErr
= ATSUSetLayoutControls (maATSULayout
, numcontrols
, theTags
, theSizes
, theValues
);
411 DBG_ASSERT( (theErr
==noErr
), "ATSLayout::DrawText ATSUSetLayoutControls failed!\n" );
414 const Point aPos
= GetDrawPosition( Point(mnBaseAdv
,0) );
415 const Fixed nFixedX
= Vcl2Fixed( +aPos
.X() );
416 const Fixed nFixedY
= Vcl2Fixed( -aPos
.Y() ); // adjusted for y-mirroring
417 if( maSubPortions
.empty() )
418 ATSUDrawText( maATSULayout
, mnMinCharPos
, mnCharCount
, nFixedX
, nFixedY
);
421 // draw the sub-portions and apply individual adjustments
422 SubPortionVector::const_iterator it
= maSubPortions
.begin();
423 for(; it
!= maSubPortions
.end(); ++it
)
425 const SubPortion
& rSubPortion
= *it
;
426 // calculate sub-portion offset for rotated text
427 Fixed nXOfsFixed
= 0, nYOfsFixed
= 0;
428 if( rAquaGraphics
.mnATSUIRotation
!= 0 )
430 const double fRadians
= rAquaGraphics
.mnATSUIRotation
* (M_PI
/0xB40000);
431 nXOfsFixed
= static_cast<Fixed
>(static_cast<double>(+rSubPortion
.mnXOffset
) * cos( fRadians
));
432 nYOfsFixed
= static_cast<Fixed
>(static_cast<double>(+rSubPortion
.mnXOffset
) * sin( fRadians
));
436 ATSUDrawText( maATSULayout
,
437 rSubPortion
.mnMinCharPos
, rSubPortion
.mnEndCharPos
- rSubPortion
.mnMinCharPos
,
438 nFixedX
+ nXOfsFixed
, nFixedY
+ nYOfsFixed
);
442 // request an update of the changed window area
443 if( rAquaGraphics
.IsWindowGraphics() )
445 Rect drawRect
; // rectangle of the changed area
446 theErr
= ATSUMeasureTextImage( maATSULayout
,
447 mnMinCharPos
, mnCharCount
, nFixedX
, nFixedY
, &drawRect
);
448 if( theErr
== noErr
)
450 // FIXME: transformation from baseline to top left
451 // with the simple approach below we invalidate too much
452 short d
= drawRect
.bottom
- drawRect
.top
;
454 drawRect
.bottom
+= d
;
455 CGRect aRect
= CGRectMake( drawRect
.left
, drawRect
.top
,
456 drawRect
.right
- drawRect
.left
,
457 drawRect
.bottom
- drawRect
.top
);
458 aRect
= CGContextConvertRectToDeviceSpace( rAquaGraphics
.mrContext
, aRect
);
459 rAquaGraphics
.RefreshRect( aRect
);
463 // restore the original graphic context transformations
464 CGContextRestoreGState( rAquaGraphics
.mrContext
);
467 // -----------------------------------------------------------------------
469 * ATSLayout::GetNextGlyphs : Get info about next glyphs in the layout
471 * @param nLen: max number of char
472 * @param pGlyphs: returned array of glyph ids
473 * @param rPos: returned x starting position
474 * @param nStart: index of the first requested glyph
475 * @param pGlyphAdvances: returned array of glyphs advances
476 * @param pCharIndexes: returned array of char indexes
478 * Returns infos about the next glyphs in the text layout
480 * @return : number of glyph details that were provided
482 int ATSLayout::GetNextGlyphs( int nLen
, sal_GlyphId
* pGlyphIDs
, Point
& rPos
, int& nStart
,
483 sal_Int32
* pGlyphAdvances
, int* pCharIndexes
) const
485 if( nStart
< 0 ) // first glyph requested?
488 // get glyph measurements
490 // some measurements are only needed for multi-glyph results
497 if( nStart
>= mnGlyphCount
) // no glyph left?
500 // calculate glyph position relative to layout base
501 // TODO: avoid for nStart!=0 case by reusing rPos
502 Fixed nXOffset
= mnBaseAdv
;
503 for( int i
= 0; i
< nStart
; ++i
)
504 nXOffset
+= mpGlyphAdvances
[ i
];
505 // if sub-portion offsets are involved there is an additional x-offset
506 if( !maSubPortions
.empty() )
508 // prepare to find the sub-portion
509 int nCharPos
= nStart
+ mnMinCharPos
;
511 nCharPos
= mpGlyphs2Chars
[nStart
];
513 // find the matching subportion
514 // TODO: is a non-linear search worth it?
515 SubPortionVector::const_iterator it
= maSubPortions
.begin();
516 for(; it
!= maSubPortions
.end(); ++it
) {
517 const SubPortion
& r
= *it
;
518 if( nCharPos
< r
.mnMinCharPos
)
520 if( nCharPos
>= r
.mnEndCharPos
)
522 // apply the sub-portion xoffset
523 nXOffset
+= r
.mnXOffset
;
530 nYOffset
= mpDeltaY
[ nStart
];
532 // calculate absolute position in pixel units
533 const Point
aRelativePos( Fix2Long(static_cast<Fixed
>(nXOffset
*mfFontScale
)), Fix2Long(static_cast<Fixed
>(nYOffset
*mfFontScale
)) );
534 rPos
= GetDrawPosition( aRelativePos
);
536 // update return values
538 while( nCount
< nLen
)
541 sal_GlyphId nGlyphId
= mpGlyphIds
[nStart
];
543 // check if glyph fallback is needed for this glyph
544 // TODO: use ATSUDirectGetLayoutDataArrayPtrFromTextLayout(kATSUDirectDataStyleIndex) API instead?
545 const int nCharPos
= mpGlyphs2Chars
? mpGlyphs2Chars
[nStart
] : nStart
+ mnMinCharPos
;
546 ATSUFontID nFallbackFontID
= kATSUInvalidFontID
;
547 UniCharArrayOffset nChangedOffset
= 0;
548 UniCharCount nChangedLength
= 0;
549 OSStatus eStatus
= ATSUMatchFontsToText( maATSULayout
, nCharPos
, kATSUToTextEnd
,
550 &nFallbackFontID
, &nChangedOffset
, &nChangedLength
);
551 if( (eStatus
== kATSUFontsMatched
) && ((int)nChangedOffset
== nCharPos
) )
553 // fallback is needed
554 if( !mpFallbackInfo
)
555 mpFallbackInfo
= new FallbackInfo
;
556 // register fallback font
557 const int nLevel
= mpFallbackInfo
->AddFallback( nFallbackFontID
);
558 // update sal_GlyphId with fallback level
559 nGlyphId
|= (nLevel
<< GF_FONTSHIFT
);
562 // update resulting glyphid array
563 *(pGlyphIDs
++) = nGlyphId
;
565 // update returned glyph advance array
567 *(pGlyphAdvances
++) = Fixed2Vcl( mpGlyphAdvances
[nStart
] );
569 // update returned index-into-string array
574 nCharPos
= mpGlyphs2Chars
[nStart
];
576 nCharPos
= nStart
+ mnMinCharPos
;
577 *(pCharIndexes
++) = nCharPos
;
580 // stop at last glyph
581 if( ++nStart
>= mnGlyphCount
)
584 // stop when next the x-position is unexpected
585 if( !maSubPortions
.empty() )
586 break; // TODO: finish the complete sub-portion
587 if( !pGlyphAdvances
&& mpGlyphOrigAdvs
)
588 if( mpGlyphAdvances
[nStart
-1] != mpGlyphOrigAdvs
[nStart
-1] )
591 // stop when the next y-position is unexpected
593 if( mpDeltaY
[nStart
-1] != mpDeltaY
[nStart
] )
600 // -----------------------------------------------------------------------
602 * ATSLayout::GetTextWidth : Get typographic width of layouted text
604 * Get typographic bounds of the text
606 * @return : text width
608 long ATSLayout::GetTextWidth() const
610 if( mnCharCount
<= 0 )
613 DBG_ASSERT( (maATSULayout
!=NULL
), "ATSLayout::GetTextWidth() with maATSULayout==NULL !\n");
619 // prepare precise measurements on pixel based or reference-device
620 const UInt16 eTypeOfBounds
= kATSUseFractionalOrigins
;
622 // determine number of needed measurement trapezoids
623 ItemCount nMaxBounds
= 0;
624 OSStatus err
= ATSUGetGlyphBounds( maATSULayout
, 0, 0, mnMinCharPos
, mnCharCount
,
625 eTypeOfBounds
, 0, NULL
, &nMaxBounds
);
627 || (nMaxBounds
<= 0) )
630 // get the trapezoids
631 typedef std::vector
<ATSTrapezoid
> TrapezoidVector
;
632 TrapezoidVector
aTrapezoidVector( nMaxBounds
);
633 ItemCount nBoundsCount
= 0;
634 err
= ATSUGetGlyphBounds( maATSULayout
, 0, 0, mnMinCharPos
, mnCharCount
,
635 eTypeOfBounds
, nMaxBounds
, &aTrapezoidVector
[0], &nBoundsCount
);
639 DBG_ASSERT( (nBoundsCount
<= nMaxBounds
), "ATSLayout::GetTextWidth() : too many trapezoids !\n");
641 // find the bound extremas
642 Fixed nLeftBound
= 0;
643 Fixed nRightBound
= 0;
644 for( ItemCount i
= 0; i
< nBoundsCount
; ++i
)
646 const ATSTrapezoid
& rTrap
= aTrapezoidVector
[i
];
647 if( (i
== 0) || (nLeftBound
< rTrap
.lowerLeft
.x
) )
648 nLeftBound
= rTrap
.lowerLeft
.x
;
649 if( (i
== 0) || (nRightBound
> rTrap
.lowerRight
.x
) )
650 nRightBound
= rTrap
.lowerRight
.x
;
653 // measure the bound extremas
654 mnCachedWidth
= nRightBound
- nLeftBound
;
655 // adjust for eliminated trailing space widths
658 int nScaledWidth
= Fixed2Vcl( mnCachedWidth
);
659 nScaledWidth
+= mnTrailingSpaceWidth
;
663 // -----------------------------------------------------------------------
665 * ATSLayout::FillDXArray : Get Char widths
667 * @param pDXArray: array to be filled with x-advances
669 * Fill the pDXArray with horizontal deltas : CharWidths
671 * @return : typographical width of the complete text layout
673 long ATSLayout::FillDXArray( long* pDXArray
) const
675 // short circuit requests which don't need full details
677 return GetTextWidth();
680 DBG_ASSERT( !mnTrailingSpaceWidth
, "ATSLayout::FillDXArray() with nTSW!=0" );
682 // initialize details about the resulting layout
685 // distribute the widths among the string elements
688 for( int i
= 0; i
< mnCharCount
; ++i
)
690 // convert and adjust for accumulated rounding errors
691 mnCachedWidth
+= mpCharWidths
[i
];
692 const int nOldPixWidth
= nPixWidth
;
693 nPixWidth
= Fixed2Vcl( mnCachedWidth
);
694 pDXArray
[i
] = nPixWidth
- nOldPixWidth
;
700 // -----------------------------------------------------------------------
702 * ATSLayout::GetTextBreak : Find line break depending on width
704 * @param nMaxWidth : maximal logical text width in subpixel units
705 * @param nCharExtra: expanded/condensed spacing in subpixel units
706 * @param nFactor: number of subpixel units per pixel
708 * Measure the layouted text to find the typographical line break
709 * the result is needed by the language specific line breaking
711 * @return : string index corresponding to the suggested line break
713 int ATSLayout::GetTextBreak( long nMaxWidth
, long nCharExtra
, int nFactor
) const
718 // the semantics of the legacy use case (nCharExtra!=0) cannot be mapped to ATSUBreakLine()
719 if( nCharExtra
!= 0 )
721 // prepare the measurement by layouting and measuring the un-expanded/un-condensed text
725 // TODO: use a better way than by testing each the char position
726 ATSUTextMeasurement nATSUSumWidth
= 0;
727 const ATSUTextMeasurement nATSUMaxWidth
= Vcl2Fixed( nMaxWidth
/ nFactor
);
728 const ATSUTextMeasurement nATSUExtraWidth
= Vcl2Fixed( nCharExtra
) / nFactor
;
729 for( int i
= 0; i
< mnCharCount
; ++i
)
731 nATSUSumWidth
+= mpCharWidths
[i
];
732 if( nATSUSumWidth
>= nATSUMaxWidth
)
733 return (mnMinCharPos
+ i
);
734 nATSUSumWidth
+= nATSUExtraWidth
;
735 if( nATSUSumWidth
>= nATSUMaxWidth
)
736 if( i
+1 < mnCharCount
)
737 return (mnMinCharPos
+ i
);
743 // get a quick overview on what could fit
744 const long nPixelWidth
= (nMaxWidth
- (nCharExtra
* mnCharCount
)) / nFactor
;
745 if( nPixelWidth
<= 0 )
749 DBG_ASSERT( !mnTrailingSpaceWidth
, "ATSLayout::GetTextBreak() with nTSW!=0" );
751 // initial measurement of text break position
752 UniCharArrayOffset nBreakPos
= mnMinCharPos
;
753 const ATSUTextMeasurement nATSUMaxWidth
= Vcl2Fixed( nPixelWidth
);
754 OSStatus eStatus
= ATSUBreakLine( maATSULayout
, mnMinCharPos
,
755 nATSUMaxWidth
, false, &nBreakPos
);
757 if( (eStatus
!= noErr
) && (eStatus
!= kATSULineBreakInWord
) )
760 // the result from ATSUBreakLine() doesn't match the semantics expected by its
761 // application layer callers from SW+SVX+I18N. Adjust the results to the expectations:
763 // ATSU reports that everything fits even when trailing spaces would break the line
764 // #i89789# OOo's application layers expect STRING_LEN if everything fits
765 if( nBreakPos
>= static_cast<UniCharArrayOffset
>(mnEndCharPos
) )
768 // GetTextBreak()'s callers expect it to return the "stupid visual line break".
769 // Returning anything else result.s in subtle problems in the application layers.
770 static const bool bInWord
= true; // TODO: add as argument to GetTextBreak() method
774 // emulate stupid visual line breaking by line breaking for the remaining width
775 ATSUTextMeasurement nLeft
, nRight
, nDummy
;
776 eStatus
= ATSUGetUnjustifiedBounds( maATSULayout
, mnMinCharPos
, nBreakPos
-mnMinCharPos
,
777 &nLeft
, &nRight
, &nDummy
, &nDummy
);
778 if( eStatus
!= noErr
)
780 const ATSUTextMeasurement nATSURemWidth
= nATSUMaxWidth
- (nRight
- nLeft
);
781 if( nATSURemWidth
<= 0 )
783 UniCharArrayOffset nBreakPosInWord
= nBreakPos
;
784 eStatus
= ATSUBreakLine( maATSULayout
, nBreakPos
, nATSURemWidth
, false, &nBreakPosInWord
);
785 return nBreakPosInWord
;
788 // -----------------------------------------------------------------------
790 * ATSLayout::GetCaretPositions : Find positions of carets
792 * @param nMaxIndex position to which we want to find the carets
794 * Fill the array of positions of carets (for cursors and selections)
798 void ATSLayout::GetCaretPositions( int nMaxIndex
, long* pCaretXArray
) const
800 DBG_ASSERT( ((nMaxIndex
>0)&&!(nMaxIndex
&1)),
801 "ATSLayout::GetCaretPositions() : invalid number of caret pairs requested");
803 // initialize the caret positions
804 for( int i
= 0; i
< nMaxIndex
; ++i
)
805 pCaretXArray
[ i
] = -1;
807 for( int n
= 0; n
<= mnCharCount
; ++n
)
809 // measure the characters cursor position
810 typedef unsigned char Boolean
;
811 const Boolean bIsLeading
= true;
812 ATSUCaret aCaret0
, aCaret1
;
814 OSStatus eStatus
= ATSUOffsetToCursorPosition( maATSULayout
,
815 mnMinCharPos
+ n
, bIsLeading
, kATSUByCharacter
,
816 &aCaret0
, &aCaret1
, &bIsSplit
);
817 if( eStatus
!= noErr
)
819 const Fixed nFixedPos
= mnBaseAdv
+ aCaret0
.fX
;
820 // convert the measurement to pixel units
821 const int nPixelPos
= Fixed2Vcl( nFixedPos
);
822 // update previous trailing position
824 pCaretXArray
[2*n
-1] = nPixelPos
;
825 // update current leading position
826 if( 2*n
>= nMaxIndex
)
828 pCaretXArray
[2*n
+0] = nPixelPos
;
832 // -----------------------------------------------------------------------
834 * ATSLayout::GetBoundRect : Get rectangle dim containing the layouted text
836 * @param rVCLRect: rectangle of text image (layout) measures
838 * Get ink bounds of the text
840 * @return : measurement valid
842 bool ATSLayout::GetBoundRect( SalGraphics
&, Rectangle
& rVCLRect
) const
844 const Point aPos
= GetDrawPosition( Point(mnBaseAdv
, 0) );
845 const Fixed nFixedX
= Vcl2Fixed( +aPos
.X() );
846 const Fixed nFixedY
= Vcl2Fixed( +aPos
.Y() );
849 OSStatus eStatus
= ATSUMeasureTextImage( maATSULayout
,
850 mnMinCharPos
, mnCharCount
, nFixedX
, nFixedY
, &aMacRect
);
851 if( eStatus
!= noErr
)
854 // ATSU top-bottom are vertically flipped from a VCL aspect
855 rVCLRect
.Left() = AtsuPix2Vcl( aMacRect
.left
);
856 rVCLRect
.Top() = AtsuPix2Vcl( aMacRect
.top
);
857 rVCLRect
.Right() = AtsuPix2Vcl( aMacRect
.right
);
858 rVCLRect
.Bottom() = AtsuPix2Vcl( aMacRect
.bottom
);
862 // -----------------------------------------------------------------------
864 * ATSLayout::InitGIA() : get many informations about layouted text
866 * Fills arrays of information about the gylph layout previously done
867 * in ASTLayout::LayoutText() : glyph advance (width), glyph delta Y (from baseline),
868 * mapping between glyph index and character index, chars widths
870 * @return : true if everything could be computed, otherwise false
872 bool ATSLayout::InitGIA( ImplLayoutArgs
* pArgs
) const
874 // no need to run InitGIA more than once on the same ATSLayout object
875 if( mnGlyphCount
>= 0 )
879 // Workaround a bug in ATSUI with empty string
880 if( mnCharCount
<= 0 )
883 // initialize character details
884 mpCharWidths
= new Fixed
[ mnCharCount
];
885 mpChars2Glyphs
= new int[ mnCharCount
];
886 for( int n
= 0; n
< mnCharCount
; ++n
)
888 mpCharWidths
[ n
] = 0;
889 mpChars2Glyphs
[ n
] = -1;
892 // get details about the glyph layout
893 ItemCount iLayoutDataCount
;
894 const ATSLayoutRecord
* pALR
;
895 OSStatus eStatus
= ATSUDirectGetLayoutDataArrayPtrFromTextLayout(
896 maATSULayout
, mnMinCharPos
, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent
,
897 (void**)&pALR
, &iLayoutDataCount
);
898 DBG_ASSERT( (eStatus
==noErr
), "ATSLayout::InitGIA() : no ATSLayoutRecords!\n");
899 if( (eStatus
!= noErr
)
900 || (iLayoutDataCount
<= 1) )
903 // initialize glyph details
904 mpGlyphIds
= new ATSGlyphRef
[ iLayoutDataCount
];
905 mpGlyphAdvances
= new Fixed
[ iLayoutDataCount
];
906 mpGlyphs2Chars
= new int[ iLayoutDataCount
];
908 // measure details of the glyph layout
910 for( ItemCount i
= 0; i
< iLayoutDataCount
; ++i
)
912 const ATSLayoutRecord
& rALR
= pALR
[i
];
914 // distribute the widths as fairly as possible among the chars
915 const int nRelativeIdx
= (rALR
.originalOffset
/ 2);
916 if( i
+1 < iLayoutDataCount
)
917 mpCharWidths
[ nRelativeIdx
] += pALR
[i
+1].realPos
- rALR
.realPos
;
919 // new glyph is available => finish measurement of old glyph
920 if( mnGlyphCount
> 0 )
921 mpGlyphAdvances
[ mnGlyphCount
-1 ] = rALR
.realPos
- nLeftPos
;
923 // ignore marker or deleted glyphs
924 enum { MARKED_OUTGLYPH
=0xFFFE, DROPPED_OUTGLYPH
=0xFFFF};
925 if( rALR
.glyphID
>= MARKED_OUTGLYPH
)
928 DBG_ASSERT( !(rALR
.flags
& kATSGlyphInfoTerminatorGlyph
),
929 "ATSLayout::InitGIA(): terminator glyph not marked as deleted!" );
931 // store details of the visible glyphs
932 nLeftPos
= rALR
.realPos
;
933 mpGlyphIds
[ mnGlyphCount
] = rALR
.glyphID
;
935 // map visible glyphs to their counterparts in the UTF16-character array
936 mpGlyphs2Chars
[ mnGlyphCount
] = nRelativeIdx
+ mnMinCharPos
;
937 mpChars2Glyphs
[ nRelativeIdx
] = mnGlyphCount
;
942 // measure complete width
943 mnCachedWidth
= mnBaseAdv
;
944 mnCachedWidth
+= pALR
[iLayoutDataCount
-1].realPos
- pALR
[0].realPos
;
946 #if (OSL_DEBUG_LEVEL > 1)
947 Fixed nWidthSum
= mnBaseAdv
;
948 for( int n
= 0; n
< mnCharCount
; ++n
)
949 nWidthSum
+= mpCharWidths
[ n
];
950 DBG_ASSERT( (nWidthSum
==mnCachedWidth
),
951 "ATSLayout::InitGIA(): measured widths do not match!\n" );
954 // #i91183# we need to split up the portion into sub-portions
955 // if the ATSU-layout differs too much from the requested layout
956 if( pArgs
&& pArgs
->mpDXArray
)
958 // TODO: non-strong-LTR case cases should be handled too
959 if( (pArgs
->mnFlags
& TEXT_LAYOUT_BIDI_STRONG
)
960 && !(pArgs
->mnFlags
& TEXT_LAYOUT_BIDI_RTL
) )
962 Fixed nSumCharWidths
= 0;
963 SubPortion aSubPortion
= { mnMinCharPos
, 0, 0 };
964 for( int i
= 0; i
< mnCharCount
; ++i
)
966 // calculate related logical position
967 nSumCharWidths
+= mpCharWidths
[i
];
969 // start new sub-portion if needed
970 const Fixed nNextXPos
= Vcl2Fixed(pArgs
->mpDXArray
[i
]);
971 const Fixed nNextXOffset
= nNextXPos
- nSumCharWidths
;
972 const Fixed nFixedDiff
= aSubPortion
.mnXOffset
- nNextXOffset
;
973 if( (nFixedDiff
< -0xC000) || (nFixedDiff
> +0xC000) ) {
974 // get to the end of the current sub-portion
975 // prevent splitting up at diacritics etc.
977 while( (++j
< mnCharCount
) && !mpCharWidths
[j
] );
978 aSubPortion
.mnEndCharPos
= mnMinCharPos
+ j
;
979 // emit current sub-portion
980 maSubPortions
.push_back( aSubPortion
);
981 // prepare next sub-portion
982 aSubPortion
.mnMinCharPos
= aSubPortion
.mnEndCharPos
;
983 aSubPortion
.mnXOffset
= nNextXOffset
;
987 // emit the remaining sub-portion
988 if( !maSubPortions
.empty() )
990 aSubPortion
.mnEndCharPos
= mnEndCharPos
;
991 if( aSubPortion
.mnEndCharPos
!= aSubPortion
.mnMinCharPos
)
992 maSubPortions
.push_back( aSubPortion
);
996 // override layouted charwidths with requested charwidths
997 for( int n
= 0; n
< mnCharCount
; ++n
)
998 mpCharWidths
[ n
] = pArgs
->mpDXArray
[ n
];
1001 // release the ATSU layout records
1002 ATSUDirectReleaseLayoutDataArrayPtr(NULL
,
1003 kATSUDirectDataLayoutRecordATSLayoutRecordCurrent
, (void**)&pALR
);
1008 // -----------------------------------------------------------------------
1010 bool ATSLayout::GetIdealX() const
1012 // compute the ideal advance widths only once
1013 if( mpGlyphOrigAdvs
!= NULL
)
1016 DBG_ASSERT( (mpGlyphIds
!=NULL
), "GetIdealX() called with mpGlyphIds==NULL !" );
1017 DBG_ASSERT( (mrATSUStyle
!=NULL
), "GetIdealX called with mrATSUStyle==NULL !" );
1019 // TODO: cache ideal metrics per glyph?
1020 std::vector
<ATSGlyphIdealMetrics
> aIdealMetrics
;
1021 aIdealMetrics
.resize( mnGlyphCount
);
1022 OSStatus theErr
= ATSUGlyphGetIdealMetrics( mrATSUStyle
,
1023 mnGlyphCount
, &mpGlyphIds
[0], sizeof(*mpGlyphIds
), &aIdealMetrics
[0] );
1024 DBG_ASSERT( (theErr
==noErr
), "ATSUGlyphGetIdealMetrics failed!");
1025 if( theErr
!= noErr
)
1028 mpGlyphOrigAdvs
= new Fixed
[ mnGlyphCount
];
1029 for( int i
= 0;i
< mnGlyphCount
;++i
)
1030 mpGlyphOrigAdvs
[i
] = FloatToFixed( aIdealMetrics
[i
].advance
.x
);
1035 // -----------------------------------------------------------------------
1037 bool ATSLayout::GetDeltaY() const
1039 // don't bother to get the same delta-y-array more than once
1040 if( mpDeltaY
!= NULL
)
1047 // get and keep the y-deltas in the mpDeltaY member variable
1048 // => release it in the destructor
1049 ItemCount nDeltaCount
= 0;
1050 OSStatus theErr
= ATSUDirectGetLayoutDataArrayPtrFromTextLayout(
1051 maATSULayout
, mnMinCharPos
, kATSUDirectDataBaselineDeltaFixedArray
,
1052 (void**)&mpDeltaY
, &nDeltaCount
);
1054 DBG_ASSERT( (theErr
==noErr
), "mpDeltaY - ATSUDirectGetLayoutDataArrayPtrFromTextLayout failed!\n");
1055 if( theErr
!= noErr
)
1058 if( mpDeltaY
== NULL
)
1061 if( nDeltaCount
!= (ItemCount
)mnGlyphCount
)
1063 DBG_WARNING( "ATSLayout::GetDeltaY() : wrong deltaY count!" );
1064 ATSUDirectReleaseLayoutDataArrayPtr( NULL
,
1065 kATSUDirectDataBaselineDeltaFixedArray
, (void**)&mpDeltaY
);
1074 // =======================================================================
1077 // helper class to convert ATSUI outlines to VCL PolyPolygons
1084 void Init( PolyPolygon
* pPolyPoly
, long nXOffset
, long nYOffset
);
1085 void AddPoint( const Float32Point
&, PolyFlags
);
1086 void ClosePolygon();
1089 PolyPolygon
* mpPolyPoly
;
1090 long mnXOffset
, mnYOffset
;
1096 USHORT mnPointCount
;
1101 // -----------------------------------------------------------------------
1103 PolyArgs::PolyArgs()
1110 mpPointAry
= new Point
[ mnMaxPoints
];
1111 mpFlagAry
= new BYTE
[ mnMaxPoints
];
1114 // -----------------------------------------------------------------------
1116 PolyArgs::~PolyArgs()
1119 delete[] mpPointAry
;
1122 // -----------------------------------------------------------------------
1124 void PolyArgs::Init( PolyPolygon
* pPolyPoly
, long nXOffset
, long nYOffset
)
1126 mnXOffset
= nXOffset
;
1127 mnYOffset
= nYOffset
;
1128 mpPolyPoly
= pPolyPoly
;
1130 mpPolyPoly
->Clear();
1135 // -----------------------------------------------------------------------
1137 void PolyArgs::AddPoint( const Float32Point
& rPoint
, PolyFlags eFlags
)
1139 if( mnPointCount
>= mnMaxPoints
)
1141 // resize if needed (TODO: use STL?)
1143 Point
* mpNewPoints
= new Point
[ mnMaxPoints
];
1144 BYTE
* mpNewFlags
= new BYTE
[ mnMaxPoints
];
1145 for( int i
= 0; i
< mnPointCount
; ++i
)
1147 mpNewPoints
[ i
] = mpPointAry
[ i
];
1148 mpNewFlags
[ i
] = mpFlagAry
[ i
];
1151 delete[] mpPointAry
;
1152 mpPointAry
= mpNewPoints
;
1153 mpFlagAry
= mpNewFlags
;
1156 // convert to pixels and add startpoint offset
1157 int nXPos
= Float32ToInt( rPoint
.x
);
1158 int nYPos
= Float32ToInt( rPoint
.y
);
1159 mpPointAry
[ mnPointCount
] = Point( nXPos
+ mnXOffset
, nYPos
+ mnYOffset
);
1161 mpFlagAry
[ mnPointCount
++ ]= eFlags
;
1162 mbHasOffline
|= (eFlags
!= POLY_NORMAL
);
1165 // -----------------------------------------------------------------------
1167 void PolyArgs::ClosePolygon()
1169 if( !mnPolyCount
++ )
1172 // append finished polygon
1173 Polygon
aPoly( mnPointCount
, mpPointAry
, (mbHasOffline
? mpFlagAry
: NULL
) );
1174 mpPolyPoly
->Insert( aPoly
);
1176 // prepare for new polygon
1178 mbHasOffline
= false;
1181 // =======================================================================
1183 // glyph fallback is supported directly by Aqua
1184 // so methods used only by MultiSalLayout can be dummy implementated
1185 bool ATSLayout::GetGlyphOutlines( SalGraphics
&, PolyPolyVector
& rPPV
) const { return false; }
1186 void ATSLayout::InitFont() {}
1187 void ATSLayout::MoveGlyph( int /*nStart*/, long /*nNewXPos*/ ) {}
1188 void ATSLayout::DropGlyph( int /*nStart*/ ) {}
1189 void ATSLayout::Simplify( bool /*bIsBase*/ ) {}
1191 // get the ImplFontData for a glyph fallback font
1192 // for a glyphid that was returned by ATSLayout::GetNextGlyphs()
1193 const ImplFontData
* ATSLayout::GetFallbackFontData( sal_GlyphId nGlyphId
) const
1195 // check if any fallback fonts were needed
1196 if( !mpFallbackInfo
)
1198 // check if the current glyph needs a fallback font
1199 int nFallbackLevel
= (nGlyphId
& GF_FONTMASK
) >> GF_FONTSHIFT
;
1200 if( !nFallbackLevel
)
1202 return mpFallbackInfo
->GetFallbackFontData( nFallbackLevel
);
1205 // =======================================================================
1207 int FallbackInfo::AddFallback( ATSUFontID nFontId
)
1209 // check if the fallback font is already known
1210 for( int nLevel
= 0; nLevel
< mnMaxLevel
; ++nLevel
)
1211 if( maATSUFontId
[ nLevel
] == nFontId
)
1212 return (nLevel
+ 1);
1214 // append new fallback font if possible
1215 if( mnMaxLevel
>= MAX_FALLBACK
-1 )
1217 // keep ATSU font id of fallback font
1218 maATSUFontId
[ mnMaxLevel
] = nFontId
;
1219 // find and cache the corresponding ImplFontData pointer
1220 const SystemFontList
* pSFL
= GetSalData()->mpFontList
;
1221 const ImplMacFontData
* pFontData
= pSFL
->GetFontDataFromId( nFontId
);
1222 maFontData
[ mnMaxLevel
] = pFontData
;
1223 // increase fallback level by one
1224 return (++mnMaxLevel
);
1227 // -----------------------------------------------------------------------
1229 const ImplFontData
* FallbackInfo::GetFallbackFontData( int nFallbackLevel
) const
1231 const ImplMacFontData
* pFallbackFont
= maFontData
[ nFallbackLevel
-1 ];
1232 return pFallbackFont
;
1235 // =======================================================================
1237 SalLayout
* AquaSalGraphics::GetTextLayout( ImplLayoutArgs
& rArgs
, int nFallbackLevel
)
1239 ATSLayout
* pATSLayout
= new ATSLayout( maATSUStyle
, mfFontScale
);
1243 // =======================================================================