xref: /trunk/main/vcl/aqua/source/gdi/atslayout.cxx (revision a9ab3c7b3d31474a75bf54404ada03e2f02464cb)
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 
23 
24 #include "tools/debug.hxx"
25 
26 #include "aqua/saldata.hxx"
27 #include "aqua/salgdi.h"
28 #include "atsfonts.hxx"
29 
30 #include "sallayout.hxx"
31 #include "salgdi.hxx"
32 
33 #include <math.h>
34 
35 // =======================================================================
36 
37 class ATSLayout : public SalLayout
38 {
39 public:
40                     ATSLayout( ATSUStyle&, float fFontScale );
41     virtual         ~ATSLayout();
42 
43     virtual bool    LayoutText( ImplLayoutArgs& );
44     virtual void    AdjustLayout( ImplLayoutArgs& );
45     virtual void    DrawText( SalGraphics& ) const;
46 
47     virtual int     GetNextGlyphs( int nLen, sal_GlyphId* pGlyphs, Point& rPos, int&,
48                         sal_Int32* pGlyphAdvances, int* pCharIndexes ) const;
49 
50     virtual long    GetTextWidth() const;
51     virtual long    FillDXArray( long* pDXArray ) const;
52     virtual int     GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const;
53     virtual void    GetCaretPositions( int nArraySize, long* pCaretXArray ) const;
54     virtual bool    GetGlyphOutlines( SalGraphics&, PolyPolyVector& ) const;
55     virtual bool    GetBoundRect( SalGraphics&, Rectangle& ) const;
56 
57     const ImplFontData* GetFallbackFontData( sal_GlyphId ) const;
58 
59     virtual void    InitFont();
60     virtual void    MoveGlyph( int nStart, long nNewXPos );
61     virtual void    DropGlyph( int nStart );
62     virtual void    Simplify( bool bIsBase );
63 
64 private:
65     ATSUStyle&          mrATSUStyle;
66     ATSUTextLayout      maATSULayout;
67     int                 mnCharCount;        // ==mnEndCharPos-mnMinCharPos
68     // to prevent ATS overflowing the Fixed16.16 values
69     // ATS font requests get size limited by downscaling huge fonts
70     // in these cases the font scale becomes something bigger than 1.0
71     float               mfFontScale;
72 
73 private:
74     bool    InitGIA( ImplLayoutArgs* pArgs = NULL ) const;
75     bool    GetIdealX() const;
76     bool    GetDeltaY() const;
77     void    InvalidateMeasurements();
78 
79     int Fixed2Vcl( Fixed ) const;       // convert ATSU-Fixed units to VCL units
80     int AtsuPix2Vcl( int ) const;       // convert ATSU-Pixel units to VCL units
81     Fixed   Vcl2Fixed( int ) const;     // convert VCL units to ATSU-Fixed units
82 
83     // cached details about the resulting layout
84     // mutable members since these details are all lazy initialized
85     mutable int         mnGlyphCount;           // glyph count
86     mutable Fixed       mnCachedWidth;          // cached value of resulting typographical width
87     int                 mnTrailingSpaceWidth;   // in Pixels
88 
89     mutable ATSGlyphRef*    mpGlyphIds;         // ATSU glyph ids
90     mutable Fixed*          mpCharWidths;       // map relative charpos to charwidth
91     mutable int*            mpChars2Glyphs;     // map relative charpos to absolute glyphpos
92     mutable int*            mpGlyphs2Chars;     // map absolute glyphpos to absolute charpos
93     mutable bool*           mpGlyphRTLFlags;    // BiDi status for glyphs: true if RTL
94     mutable Fixed*          mpGlyphAdvances;    // contains glyph widths for the justified layout
95     mutable Fixed*          mpGlyphOrigAdvs;    // contains glyph widths for the unjustified layout
96     mutable Fixed*          mpDeltaY;           // vertical offset from the baseline
97 
98     struct SubPortion { int mnMinCharPos, mnEndCharPos; Fixed mnXOffset; };
99     typedef std::vector<SubPortion> SubPortionVector;
100     mutable SubPortionVector    maSubPortions;      // Writer&ATSUI layouts can differ quite a bit...
101 
102     // storing details about fonts used in glyph-fallback for this layout
103     mutable class FallbackInfo* mpFallbackInfo;
104 
105     // x-offset relative to layout origin
106     // currently only used in RTL-layouts
107     mutable Fixed           mnBaseAdv;
108 };
109 
110 class FallbackInfo
111 {
112 public:
113         FallbackInfo() : mnMaxLevel(0) {}
114     int AddFallback( ATSUFontID );
115     const ImplFontData* GetFallbackFontData( int nLevel ) const;
116 
117 private:
118     const ImplMacFontData* maFontData[ MAX_FALLBACK ];
119     ATSUFontID             maATSUFontId[ MAX_FALLBACK ];
120     int                    mnMaxLevel;
121 };
122 
123 // =======================================================================
124 
125 ATSLayout::ATSLayout( ATSUStyle& rATSUStyle, float fFontScale )
126 :   mrATSUStyle( rATSUStyle ),
127     maATSULayout( NULL ),
128     mnCharCount( 0 ),
129     mfFontScale( fFontScale ),
130     mnGlyphCount( -1 ),
131     mnCachedWidth( 0 ),
132     mnTrailingSpaceWidth( 0 ),
133     mpGlyphIds( NULL ),
134     mpCharWidths( NULL ),
135     mpChars2Glyphs( NULL ),
136     mpGlyphs2Chars( NULL ),
137     mpGlyphRTLFlags( NULL ),
138     mpGlyphAdvances( NULL ),
139     mpGlyphOrigAdvs( NULL ),
140     mpDeltaY( NULL ),
141     mpFallbackInfo( NULL ),
142     mnBaseAdv( 0 )
143 {}
144 
145 // -----------------------------------------------------------------------
146 
147 ATSLayout::~ATSLayout()
148 {
149     if( mpDeltaY )
150         ATSUDirectReleaseLayoutDataArrayPtr( NULL,
151             kATSUDirectDataBaselineDeltaFixedArray, (void**)&mpDeltaY );
152 
153     if( maATSULayout )
154         ATSUDisposeTextLayout( maATSULayout );
155 
156     delete[] mpGlyphRTLFlags;
157     delete[] mpGlyphs2Chars;
158     delete[] mpChars2Glyphs;
159     if( mpCharWidths != mpGlyphAdvances )
160         delete[] mpCharWidths;
161     delete[] mpGlyphIds;
162     delete[] mpGlyphOrigAdvs;
163     delete[] mpGlyphAdvances;
164 
165     delete mpFallbackInfo;
166 }
167 
168 // -----------------------------------------------------------------------
169 
170 inline int ATSLayout::Fixed2Vcl( Fixed nFixed ) const
171 {
172     float fFloat = mfFontScale * FixedToFloat( nFixed );
173     return static_cast<int>(fFloat + 0.5);
174 }
175 
176 // -----------------------------------------------------------------------
177 
178 inline int ATSLayout::AtsuPix2Vcl( int nAtsuPixel) const
179 {
180     float fVclPixel = mfFontScale * nAtsuPixel;
181     fVclPixel += (fVclPixel>=0) ? +0.5 : -0.5;  // prepare rounding to int
182     int nVclPixel = static_cast<int>( fVclPixel);
183     return nVclPixel;
184 }
185 
186 // -----------------------------------------------------------------------
187 
188 inline Fixed ATSLayout::Vcl2Fixed( int nPixel ) const
189 {
190     return FloatToFixed( nPixel / mfFontScale );
191 }
192 
193 // -----------------------------------------------------------------------
194 /**
195  * ATSLayout::LayoutText : Manage text layouting
196  *
197  * @param rArgs: contains array of char to be layouted, starting and ending position of the text to layout
198  *
199  * Typographic layout of text by using the style maATSUStyle
200  *
201  * @return : true if everything is ok
202 **/
203 bool ATSLayout::LayoutText( ImplLayoutArgs& rArgs )
204 {
205     if( maATSULayout )
206         ATSUDisposeTextLayout( maATSULayout );
207 
208     maATSULayout = NULL;
209 
210     // Layout text
211     // set up our locals, verify parameters...
212     DBG_ASSERT( (rArgs.mpStr!=NULL), "ATSLayout::LayoutText() with rArgs.mpStr==NULL !!!");
213     DBG_ASSERT( (mrATSUStyle!=NULL), "ATSLayout::LayoutText() with ATSUStyle==NULL !!!");
214 
215     SalLayout::AdjustLayout( rArgs );
216     mnCharCount = mnEndCharPos - mnMinCharPos;
217 
218     // Workaround a bug in ATSUI with empty string
219     if( mnCharCount<=0 )
220         return false;
221 
222 #if (OSL_DEBUG_LEVEL > 3)
223     Fixed fFontSize = 0;
224     ByteCount nDummy;
225     ATSUGetAttribute( mrATSUStyle, kATSUSizeTag, sizeof(fFontSize), &fFontSize, &nDummy);
226     String aUniName( &rArgs.mpStr[rArgs.mnMinCharPos], mnCharCount );
227     ByteString aCName( aUniName, RTL_TEXTENCODING_UTF8 );
228     fprintf( stderr, "ATSLayout( \"%s\" %d..%d of %d) with h=%4.1f\n",
229         aCName.GetBuffer(),rArgs.mnMinCharPos,rArgs.mnEndCharPos,rArgs.mnLength,Fix2X(fFontSize) );
230 #endif
231 
232     // create the ATSUI layout
233     UniCharCount nRunLengths[1] = { mnCharCount };
234     const int nRunCount = sizeof(nRunLengths)/sizeof(*nRunLengths);
235     OSStatus eStatus = ATSUCreateTextLayoutWithTextPtr( rArgs.mpStr,
236         rArgs.mnMinCharPos, mnCharCount, rArgs.mnLength,
237         nRunCount, &nRunLengths[0], &mrATSUStyle,
238         &maATSULayout);
239 
240     DBG_ASSERT( (eStatus==noErr), "ATSUCreateTextLayoutWithTextPtr failed\n");
241     if( eStatus != noErr )
242         return false;
243 
244     // prepare setting of layout controls
245     static const int nMaxTagCount = 1;
246     ATSUAttributeTag aTagAttrs[ nMaxTagCount ];
247     ByteCount aTagSizes[ nMaxTagCount ];
248     ATSUAttributeValuePtr aTagValues[ nMaxTagCount ];
249 
250     // prepare control of "glyph fallback"
251     const SalData* pSalData = GetSalData();
252     ATSUFontFallbacks aFontFallbacks = pSalData->mpFontList->maFontFallbacks;
253     aTagAttrs[0]  = kATSULineFontFallbacksTag;
254     aTagSizes[0]  = sizeof( ATSUFontFallbacks );
255     aTagValues[0] = &aFontFallbacks;
256 
257     // set paragraph layout controls
258     ATSUSetLayoutControls( maATSULayout, 1, aTagAttrs, aTagSizes, aTagValues );
259 
260     // enable "glyph fallback"
261     ATSUSetTransientFontMatching( maATSULayout, true );
262 
263     // control run-specific layout controls
264     if( (rArgs.mnFlags & SAL_LAYOUT_BIDI_STRONG) != 0 )
265     {
266         // control BiDi defaults
267         BOOL nLineDirTag = kATSULeftToRightBaseDirection;
268         if( (rArgs.mnFlags & SAL_LAYOUT_BIDI_RTL) != 0 )
269             nLineDirTag = kATSURightToLeftBaseDirection;
270         aTagAttrs[0] = kATSULineDirectionTag;
271         aTagSizes[0] = sizeof( nLineDirTag );
272         aTagValues[0] = &nLineDirTag;
273         // set run-specific layout controls
274 #if 0 // why don't line-controls work as reliably as layout-controls???
275         ATSUSetLineControls( maATSULayout, rArgs.mnMinCharPos, 1, aTagAttrs, aTagSizes, aTagValues );
276 #else
277         ATSUSetLayoutControls( maATSULayout, 1, aTagAttrs, aTagSizes, aTagValues );
278 #endif
279     }
280 
281     return true;
282 }
283 
284 // -----------------------------------------------------------------------
285 /**
286  * ATSLayout::AdjustLayout : Adjust layout style
287  *
288  * @param rArgs: contains attributes relevant to do a text specific layout
289  *
290  * Adjust text layout by moving glyphs to match the requested logical widths
291  *
292  * @return : none
293 **/
294 void ATSLayout::AdjustLayout( ImplLayoutArgs& rArgs )
295 {
296     int nOrigWidth = GetTextWidth();
297     int nPixelWidth = rArgs.mnLayoutWidth;
298     if( !nPixelWidth && rArgs.mpDXArray ) {
299         // for now we are only interested in the layout width
300         // TODO: use all mpDXArray elements for layouting
301         nPixelWidth = rArgs.mpDXArray[ mnCharCount - 1 ];
302 
303         // workaround for ATSUI not using trailing spaces for justification
304         int i = mnCharCount;
305         while( (--i >= 0) && IsSpacingGlyph( rArgs.mpStr[mnMinCharPos+i]|GF_ISCHAR ) ) {}
306         if( i < 0 ) // nothing to do if the text is all spaces
307             return;
308         // #i91685# trailing letters are left aligned (right aligned for RTL)
309         mnTrailingSpaceWidth = rArgs.mpDXArray[ mnCharCount-1 ];
310         if( i > 0 )
311             mnTrailingSpaceWidth -= rArgs.mpDXArray[ i-1 ];
312         InitGIA(); // ensure valid mpCharWidths[], TODO: use GetIdealX() instead?
313         mnTrailingSpaceWidth -= Fixed2Vcl( mpCharWidths[i] );
314         // ignore trailing space for calculating the available width
315         nOrigWidth -= mnTrailingSpaceWidth;
316         nPixelWidth -= mnTrailingSpaceWidth;
317         // in RTL-layouts trailing spaces are leftmost
318         // TODO: use BiDi-algorithm to thoroughly check this assumption
319         if( rArgs.mnFlags & SAL_LAYOUT_BIDI_RTL)
320             mnBaseAdv = mnTrailingSpaceWidth;
321     }
322     // return early if there is nothing to do
323     if( !nPixelWidth )
324         return;
325 
326     // HACK: justification requests which change the width by just one pixel were probably
327     // #i86038# introduced by lossy conversions between integer based coordinate system
328     // => ignoring such requests has many more benefits than eventual drawbacks
329     if( (nOrigWidth >= nPixelWidth-1) && (nOrigWidth <= nPixelWidth+1) )
330         return;
331 
332     // changing the layout will make all previous measurements invalid
333     InvalidateMeasurements();
334 
335     ATSUAttributeTag nTags[3];
336     ATSUAttributeValuePtr nVals[3];
337     ByteCount nBytes[3];
338 
339     Fixed nFixedWidth = Vcl2Fixed( nPixelWidth );
340     mnCachedWidth = nFixedWidth;
341     Fract nFractFactor = kATSUFullJustification;
342     ATSLineLayoutOptions nLineLayoutOptions = kATSLineHasNoHangers | kATSLineHasNoOpticalAlignment | kATSLineBreakToNearestCharacter;
343 
344     nTags[0] = kATSULineWidthTag;
345     nVals[0] = &nFixedWidth;
346     nBytes[0] = sizeof(Fixed);
347     nTags[1] = kATSULineLayoutOptionsTag;
348     nVals[1] = &nLineLayoutOptions;
349     nBytes[1] = sizeof(ATSLineLayoutOptions);
350     nTags[2] = kATSULineJustificationFactorTag;
351     nVals[2] = &nFractFactor;
352     nBytes[2] = sizeof(Fract);
353 
354     OSStatus eStatus = ATSUSetLayoutControls( maATSULayout, 3, nTags, nBytes, nVals );
355     if( eStatus != noErr )
356         return;
357 
358     // update the measurements of the justified layout to match the justification request
359     if( rArgs.mpDXArray )
360         InitGIA( &rArgs );
361 }
362 
363 // -----------------------------------------------------------------------
364 /**
365  * ATSLayout::DrawText : Draw text to screen
366  *
367  * @param rGraphics: device to draw to
368  *
369  * Draw the layouted text to the CGContext
370  *
371  * @return : none
372 **/
373 void ATSLayout::DrawText( SalGraphics& rGraphics ) const
374 {
375     AquaSalGraphics& rAquaGraphics = static_cast<AquaSalGraphics&>(rGraphics);
376 
377     // short circuit if there is nothing to do
378     if( (mnCharCount <= 0)
379     ||  !rAquaGraphics.CheckContext() )
380         return;
381 
382     // the view is vertically flipped => flipped glyphs
383     // so apply a temporary transformation that it flips back
384     // also compensate if the font was size limited
385     CGContextSaveGState( rAquaGraphics.mrContext );
386     CGContextScaleCTM( rAquaGraphics.mrContext, +mfFontScale, -mfFontScale );
387     CGContextSetShouldAntialias( rAquaGraphics.mrContext, !rAquaGraphics.mbNonAntialiasedText );
388 
389     // prepare ATSUI drawing attributes
390     static const ItemCount nMaxControls = 8;
391     ATSUAttributeTag theTags[ nMaxControls ];
392     ByteCount theSizes[ nMaxControls];
393     ATSUAttributeValuePtr theValues[ nMaxControls ];
394     ItemCount numcontrols = 0;
395 
396     // Tell ATSUI to use CoreGraphics
397     theTags[numcontrols] = kATSUCGContextTag;
398     theSizes[numcontrols] = sizeof( CGContextRef );
399     theValues[numcontrols++] = &rAquaGraphics.mrContext;
400 
401     // Rotate if necessary
402     if( rAquaGraphics.mnATSUIRotation != 0 )
403     {
404         Fixed theAngle = rAquaGraphics.mnATSUIRotation;
405         theTags[numcontrols] = kATSULineRotationTag;
406         theSizes[numcontrols] = sizeof( Fixed );
407         theValues[numcontrols++] = &theAngle;
408     }
409 
410     DBG_ASSERT( (numcontrols <= nMaxControls), "ATSLayout::DrawText() numcontrols overflow" );
411     OSStatus theErr = ATSUSetLayoutControls (maATSULayout, numcontrols, theTags, theSizes, theValues);
412     DBG_ASSERT( (theErr==noErr), "ATSLayout::DrawText ATSUSetLayoutControls failed!\n" );
413 
414     // Draw the text
415     const Point aPos = GetDrawPosition( Point(mnBaseAdv,0) );
416     const Fixed nFixedX = Vcl2Fixed( +aPos.X() );
417     const Fixed nFixedY = Vcl2Fixed( -aPos.Y() ); // adjusted for y-mirroring
418     if( maSubPortions.empty() )
419         ATSUDrawText( maATSULayout, mnMinCharPos, mnCharCount, nFixedX, nFixedY );
420     else
421     {
422         // draw the sub-portions and apply individual adjustments
423         SubPortionVector::const_iterator it = maSubPortions.begin();
424         for(; it != maSubPortions.end(); ++it )
425         {
426             const SubPortion& rSubPortion = *it;
427             // calculate sub-portion offset for rotated text
428             Fixed nXOfsFixed = 0, nYOfsFixed = 0;
429             if( rAquaGraphics.mnATSUIRotation != 0 )
430             {
431                 const double fRadians = rAquaGraphics.mnATSUIRotation * (M_PI/0xB40000);
432                 nXOfsFixed = static_cast<Fixed>(static_cast<double>(+rSubPortion.mnXOffset) * cos( fRadians ));
433                 nYOfsFixed = static_cast<Fixed>(static_cast<double>(+rSubPortion.mnXOffset) * sin( fRadians ));
434             }
435 
436             // draw sub-portions
437             ATSUDrawText( maATSULayout,
438                 rSubPortion.mnMinCharPos, rSubPortion.mnEndCharPos - rSubPortion.mnMinCharPos,
439                 nFixedX + nXOfsFixed, nFixedY + nYOfsFixed );
440         }
441     }
442 
443     // request an update of the changed window area
444     if( rAquaGraphics.IsWindowGraphics() )
445     {
446         Rect drawRect; // rectangle of the changed area
447         theErr = ATSUMeasureTextImage( maATSULayout,
448             mnMinCharPos, mnCharCount, nFixedX, nFixedY, &drawRect );
449         if( theErr == noErr )
450         {
451             // FIXME: transformation from baseline to top left
452             // with the simple approach below we invalidate too much
453             short d = drawRect.bottom - drawRect.top;
454             drawRect.top -= d;
455             drawRect.bottom += d;
456             CGRect aRect = CGRectMake( drawRect.left, drawRect.top,
457                                        drawRect.right - drawRect.left,
458                                        drawRect.bottom - drawRect.top );
459             aRect = CGContextConvertRectToDeviceSpace( rAquaGraphics.mrContext, aRect );
460             rAquaGraphics.RefreshRect( aRect );
461         }
462     }
463 
464     // restore the original graphic context transformations
465     CGContextRestoreGState( rAquaGraphics.mrContext );
466 }
467 
468 // -----------------------------------------------------------------------
469 /**
470  * ATSLayout::GetNextGlyphs : Get info about next glyphs in the layout
471  *
472  * @param nLen: max number of char
473  * @param pGlyphs: returned array of glyph ids
474  * @param rPos: returned x starting position
475  * @param nStart: index of the first requested glyph
476  * @param pGlyphAdvances: returned array of glyphs advances
477  * @param pCharIndexes: returned array of char indexes
478  *
479  * Returns infos about the next glyphs in the text layout
480  *
481  * @return : number of glyph details that were provided
482 **/
483 int ATSLayout::GetNextGlyphs( int nLen, sal_GlyphId* pOutGlyphIds, Point& rPos, int& nStart,
484     sal_Int32* pGlyphAdvances, int* pCharIndexes ) const
485 {
486     if( nStart < 0 )                // first glyph requested?
487         nStart = 0;
488 
489     // get glyph measurements
490     InitGIA();
491     // some measurements are only needed for multi-glyph results
492     if( nLen > 1 )
493     {
494         GetIdealX();
495         GetDeltaY();
496     }
497 
498     if( nStart >= mnGlyphCount )    // no glyph left?
499         return 0;
500 
501     // calculate glyph position relative to layout base
502     // TODO: avoid for nStart!=0 case by reusing rPos
503     Fixed nXOffset = mnBaseAdv;
504     for( int i = 0; i < nStart; ++i )
505         nXOffset += mpGlyphAdvances[ i ];
506     // if sub-portion offsets are involved there is an additional x-offset
507     if( !maSubPortions.empty() )
508     {
509         // prepare to find the sub-portion
510         int nCharPos = nStart + mnMinCharPos;
511         if( mpGlyphs2Chars )
512             nCharPos = mpGlyphs2Chars[nStart];
513 
514         // find the matching subportion
515         // TODO: is a non-linear search worth it?
516         SubPortionVector::const_iterator it = maSubPortions.begin();
517         for(; it != maSubPortions.end(); ++it) {
518             const SubPortion& r = *it;
519             if( nCharPos < r.mnMinCharPos )
520                 continue;
521             if( nCharPos >= r.mnEndCharPos )
522                 continue;
523             // apply the sub-portion xoffset
524             nXOffset += r.mnXOffset;
525             break;
526         }
527     }
528 
529     Fixed nYOffset = 0;
530     if( mpDeltaY )
531         nYOffset = mpDeltaY[ nStart ];
532 
533     // calculate absolute position in pixel units
534     const Point aRelativePos( Fix2Long(static_cast<Fixed>(nXOffset*mfFontScale)), Fix2Long(static_cast<Fixed>(nYOffset*mfFontScale)) );
535     rPos = GetDrawPosition( aRelativePos );
536 
537     // update return values
538     int nCount = 0;
539     while( nCount < nLen )
540     {
541         ++nCount;
542         sal_GlyphId aGlyphId = mpGlyphIds[ nStart];
543 
544         // check if glyph fallback is needed for this glyph
545         // TODO: use ATSUDirectGetLayoutDataArrayPtrFromTextLayout(kATSUDirectDataStyleIndex) API instead?
546         const int nCharPos = mpGlyphs2Chars ? mpGlyphs2Chars[nStart] : nStart + mnMinCharPos;
547         ATSUFontID nFallbackFontID = kATSUInvalidFontID;
548         UniCharArrayOffset nChangedOffset = 0;
549         UniCharCount nChangedLength = 0;
550         OSStatus eStatus = ATSUMatchFontsToText( maATSULayout, nCharPos, kATSUToTextEnd,
551                       &nFallbackFontID, &nChangedOffset, &nChangedLength );
552         if( (eStatus == kATSUFontsMatched) && ((int)nChangedOffset == nCharPos) )
553         {
554             // fallback is needed
555             if( !mpFallbackInfo )
556                 mpFallbackInfo = new FallbackInfo;
557             // register fallback font
558             const int nLevel = mpFallbackInfo->AddFallback( nFallbackFontID );
559             // update sal_GlyphId with fallback level
560             aGlyphId |= (nLevel << GF_FONTSHIFT);
561         }
562 
563         // update resulting glyphid array
564         *(pOutGlyphIds++) = aGlyphId;
565 
566         // update returned glyph advance array
567         if( pGlyphAdvances )
568             *(pGlyphAdvances++) = Fixed2Vcl( mpGlyphAdvances[nStart] );
569 
570         // update returned index-into-string array
571         if( pCharIndexes )
572         {
573             int nCharPos;
574             if( mpGlyphs2Chars )
575                 nCharPos = mpGlyphs2Chars[nStart];
576             else
577                 nCharPos = nStart + mnMinCharPos;
578             *(pCharIndexes++) = nCharPos;
579         }
580 
581         // stop at last glyph
582         if( ++nStart >= mnGlyphCount )
583             break;
584 
585         // stop when next the x-position is unexpected
586         if( !maSubPortions.empty() )
587             break;   // TODO: finish the complete sub-portion
588         if( !pGlyphAdvances && mpGlyphOrigAdvs )
589             if( mpGlyphAdvances[nStart-1] != mpGlyphOrigAdvs[nStart-1] )
590                 break;
591 
592         // stop when the next y-position is unexpected
593         if( mpDeltaY )
594             if( mpDeltaY[nStart-1] != mpDeltaY[nStart] )
595                 break;
596     }
597 
598     return nCount;
599 }
600 
601 // -----------------------------------------------------------------------
602 /**
603  * ATSLayout::GetTextWidth : Get typographic width of layouted text
604  *
605  * Get typographic bounds of the text
606  *
607  * @return : text width
608 **/
609 long ATSLayout::GetTextWidth() const
610 {
611     if( mnCharCount <= 0 )
612         return 0;
613 
614     DBG_ASSERT( (maATSULayout!=NULL), "ATSLayout::GetTextWidth() with maATSULayout==NULL !\n");
615     if( !maATSULayout )
616         return 0;
617 
618     if( !mnCachedWidth )
619     {
620         // prepare precise measurements on pixel based or reference-device
621         const UInt16 eTypeOfBounds = kATSUseFractionalOrigins;
622 
623         // determine number of needed measurement trapezoids
624         ItemCount nMaxBounds = 0;
625         OSStatus err = ATSUGetGlyphBounds( maATSULayout, 0, 0, mnMinCharPos, mnCharCount,
626             eTypeOfBounds, 0, NULL, &nMaxBounds );
627         if( (err != noErr)
628         ||  (nMaxBounds <= 0) )
629             return 0;
630 
631         // get the trapezoids
632         typedef std::vector<ATSTrapezoid> TrapezoidVector;
633         TrapezoidVector aTrapezoidVector( nMaxBounds );
634         ItemCount nBoundsCount = 0;
635         err = ATSUGetGlyphBounds( maATSULayout, 0, 0, mnMinCharPos, mnCharCount,
636             eTypeOfBounds, nMaxBounds, &aTrapezoidVector[0], &nBoundsCount );
637         if( err != noErr )
638             return 0;
639 
640         DBG_ASSERT( (nBoundsCount <= nMaxBounds), "ATSLayout::GetTextWidth() : too many trapezoids !\n");
641 
642         // find the bound extremas
643         Fixed nLeftBound = 0;
644         Fixed nRightBound = 0;
645         for( ItemCount i = 0; i < nBoundsCount; ++i )
646         {
647             const ATSTrapezoid& rTrap = aTrapezoidVector[i];
648             if( (i == 0) || (nLeftBound < rTrap.lowerLeft.x) )
649                 nLeftBound = rTrap.lowerLeft.x;
650             if( (i == 0) || (nRightBound > rTrap.lowerRight.x) )
651                 nRightBound = rTrap.lowerRight.x;
652         }
653 
654         // measure the bound extremas
655         mnCachedWidth = nRightBound - nLeftBound;
656         // adjust for eliminated trailing space widths
657     }
658 
659     int nScaledWidth = Fixed2Vcl( mnCachedWidth );
660     nScaledWidth += mnTrailingSpaceWidth;
661     return nScaledWidth;
662 }
663 
664 // -----------------------------------------------------------------------
665 /**
666  * ATSLayout::FillDXArray : Get Char widths
667  *
668  * @param pDXArray: array to be filled with x-advances
669  *
670  * Fill the pDXArray with horizontal deltas : CharWidths
671  *
672  * @return : typographical width of the complete text layout
673 **/
674 long ATSLayout::FillDXArray( long* pDXArray ) const
675 {
676     // short circuit requests which don't need full details
677     if( !pDXArray )
678         return GetTextWidth();
679 
680     // check assumptions
681     DBG_ASSERT( !mnTrailingSpaceWidth, "ATSLayout::FillDXArray() with nTSW!=0" );
682 
683     // initialize details about the resulting layout
684     InitGIA();
685 
686     // distribute the widths among the string elements
687     int nPixWidth = 0;
688     mnCachedWidth = 0;
689     for( int i = 0; i < mnCharCount; ++i )
690     {
691         // convert and adjust for accumulated rounding errors
692         mnCachedWidth += mpCharWidths[i];
693         const int nOldPixWidth = nPixWidth;
694         nPixWidth = Fixed2Vcl( mnCachedWidth );
695         pDXArray[i] = nPixWidth - nOldPixWidth;
696     }
697 
698     return nPixWidth;
699 }
700 
701 // -----------------------------------------------------------------------
702 /**
703  * ATSLayout::GetTextBreak : Find line break depending on width
704  *
705  * @param nMaxWidth : maximal logical text width in subpixel units
706  * @param nCharExtra: expanded/condensed spacing in subpixel units
707  * @param nFactor:    number of subpixel units per pixel
708  *
709  * Measure the layouted text to find the typographical line break
710  * the result is needed by the language specific line breaking
711  *
712  * @return : string index corresponding to the suggested line break
713 **/
714 int ATSLayout::GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const
715 {
716     if( !maATSULayout )
717         return STRING_LEN;
718 
719     // the semantics of the legacy use case (nCharExtra!=0) cannot be mapped to ATSUBreakLine()
720     if( nCharExtra != 0 )
721     {
722         // prepare the measurement by layouting and measuring the un-expanded/un-condensed text
723         if( !InitGIA() )
724             return STRING_LEN;
725 
726         // TODO: use a better way than by testing each the char position
727         ATSUTextMeasurement nATSUSumWidth = 0;
728         const ATSUTextMeasurement nATSUMaxWidth = Vcl2Fixed( nMaxWidth / nFactor );
729         const ATSUTextMeasurement nATSUExtraWidth = Vcl2Fixed( nCharExtra ) / nFactor;
730         for( int i = 0; i < mnCharCount; ++i )
731         {
732             nATSUSumWidth += mpCharWidths[i];
733             if( nATSUSumWidth >= nATSUMaxWidth )
734                 return (mnMinCharPos + i);
735             nATSUSumWidth += nATSUExtraWidth;
736             if( nATSUSumWidth >= nATSUMaxWidth )
737                 if( i+1 < mnCharCount )
738                     return (mnMinCharPos + i);
739         }
740 
741         return STRING_LEN;
742     }
743 
744     // get a quick overview on what could fit
745     const long nPixelWidth = (nMaxWidth - (nCharExtra * mnCharCount)) / nFactor;
746     if( nPixelWidth <= 0 )
747         return mnMinCharPos;
748 
749     // check assumptions
750     DBG_ASSERT( !mnTrailingSpaceWidth, "ATSLayout::GetTextBreak() with nTSW!=0" );
751 
752     // initial measurement of text break position
753     UniCharArrayOffset nBreakPos = mnMinCharPos;
754     const ATSUTextMeasurement nATSUMaxWidth = Vcl2Fixed( nPixelWidth );
755     if( nATSUMaxWidth <= 0xFFFF ) // #i108584# avoid ATSU rejecting the parameter
756         return mnMinCharPos;      //           or do ATSUMaxWidth=0x10000;
757     OSStatus eStatus = ATSUBreakLine( maATSULayout, mnMinCharPos,
758         nATSUMaxWidth, false, &nBreakPos );
759     if( (eStatus != noErr) && (eStatus != kATSULineBreakInWord) )
760         return STRING_LEN;
761 
762     // the result from ATSUBreakLine() doesn't match the semantics expected by its
763     // application layer callers from SW+SVX+I18N. Adjust the results to the expectations:
764 
765     // ATSU reports that everything fits even when trailing spaces would break the line
766     // #i89789# OOo's application layers expect STRING_LEN if everything fits
767     if( nBreakPos >= static_cast<UniCharArrayOffset>(mnEndCharPos) )
768         return STRING_LEN;
769 
770     // GetTextBreak()'s callers expect it to return the "stupid visual line break".
771     // Returning anything else result.s in subtle problems in the application layers.
772     static const bool bInWord = true; // TODO: add as argument to GetTextBreak() method
773     if( !bInWord )
774         return nBreakPos;
775 
776     // emulate stupid visual line breaking by line breaking for the remaining width
777     ATSUTextMeasurement nLeft, nRight, nDummy;
778     eStatus = ATSUGetUnjustifiedBounds( maATSULayout, mnMinCharPos, nBreakPos-mnMinCharPos,
779         &nLeft, &nRight, &nDummy, &nDummy );
780     if( eStatus != noErr )
781         return nBreakPos;
782     const ATSUTextMeasurement nATSURemWidth = nATSUMaxWidth - (nRight - nLeft);
783     if( nATSURemWidth <= 0xFFFF ) // #i108584# avoid ATSU rejecting the parameter
784         return nBreakPos;
785     UniCharArrayOffset nBreakPosInWord = nBreakPos;
786     eStatus = ATSUBreakLine( maATSULayout, nBreakPos, nATSURemWidth, false, &nBreakPosInWord );
787     return nBreakPosInWord;
788 }
789 
790 // -----------------------------------------------------------------------
791 /**
792  * ATSLayout::GetCaretPositions : Find positions of carets
793  *
794  * @param nMaxIndex position to which we want to find the carets
795  *
796  * Fill the array of positions of carets (for cursors and selections)
797  *
798  * @return : none
799 **/
800 void ATSLayout::GetCaretPositions( int nMaxIndex, long* pCaretXArray ) const
801 {
802     DBG_ASSERT( ((nMaxIndex>0)&&!(nMaxIndex&1)),
803         "ATSLayout::GetCaretPositions() : invalid number of caret pairs requested");
804 
805     // initialize the caret positions
806     for( int i = 0; i < nMaxIndex; ++i )
807         pCaretXArray[ i ] = -1;
808 
809     for( int n = 0; n <= mnCharCount; ++n )
810     {
811         // measure the characters cursor position
812         typedef unsigned char Boolean;
813         const Boolean bIsLeading = true;
814         ATSUCaret aCaret0, aCaret1;
815         Boolean bIsSplit;
816         OSStatus eStatus = ATSUOffsetToCursorPosition( maATSULayout,
817             mnMinCharPos + n, bIsLeading, kATSUByCharacter,
818             &aCaret0, &aCaret1, &bIsSplit );
819         if( eStatus != noErr )
820             continue;
821         const Fixed nFixedPos = mnBaseAdv + aCaret0.fX;
822         // convert the measurement to pixel units
823         const int nPixelPos = Fixed2Vcl( nFixedPos );
824         // update previous trailing position
825         if( n > 0 )
826             pCaretXArray[2*n-1] = nPixelPos;
827         // update current leading position
828         if( 2*n >= nMaxIndex )
829             break;
830         pCaretXArray[2*n+0] = nPixelPos;
831     }
832 }
833 
834 // -----------------------------------------------------------------------
835 /**
836  * ATSLayout::GetBoundRect : Get rectangle dim containing the layouted text
837  *
838  * @param rVCLRect: rectangle of text image (layout) measures
839  *
840  * Get ink bounds of the text
841  *
842  * @return : measurement valid
843 **/
844 bool ATSLayout::GetBoundRect( SalGraphics&, Rectangle& rVCLRect ) const
845 {
846     const Point aPos = GetDrawPosition( Point(mnBaseAdv, 0) );
847     const Fixed nFixedX = Vcl2Fixed( +aPos.X() );
848     const Fixed nFixedY = Vcl2Fixed( +aPos.Y() );
849 
850     Rect aMacRect;
851     OSStatus eStatus = ATSUMeasureTextImage( maATSULayout,
852         mnMinCharPos, mnCharCount, nFixedX, nFixedY, &aMacRect );
853     if( eStatus != noErr )
854         return false;
855 
856     // ATSU top-bottom are vertically flipped from a VCL aspect
857     rVCLRect.Left()   = AtsuPix2Vcl( aMacRect.left );
858     rVCLRect.Top()    = AtsuPix2Vcl( aMacRect.top );
859     rVCLRect.Right()  = AtsuPix2Vcl( aMacRect.right );
860     rVCLRect.Bottom() = AtsuPix2Vcl( aMacRect.bottom );
861     return true;
862 }
863 
864 // -----------------------------------------------------------------------
865 /**
866  * ATSLayout::InitGIA() : get many informations about layouted text
867  *
868  * Fills arrays of information about the gylph layout previously done
869  *  in ASTLayout::LayoutText() : glyph advance (width), glyph delta Y (from baseline),
870  *  mapping between glyph index and character index, chars widths
871  *
872  * @return : true if everything could be computed, otherwise false
873 **/
874 bool ATSLayout::InitGIA( ImplLayoutArgs* pArgs ) const
875 {
876     // no need to run InitGIA more than once on the same ATSLayout object
877     if( mnGlyphCount >= 0 )
878         return true;
879     mnGlyphCount = 0;
880 
881     // Workaround a bug in ATSUI with empty string
882     if( mnCharCount <=  0 )
883         return false;
884 
885     // initialize character details
886     mpCharWidths    = new Fixed[ mnCharCount ];
887     mpChars2Glyphs  = new int[ mnCharCount ];
888     for( int n = 0; n < mnCharCount; ++n )
889     {
890         mpCharWidths[ n ] = 0;
891         mpChars2Glyphs[ n ] = -1;
892     }
893 
894     // get details about the glyph layout
895     ItemCount iLayoutDataCount;
896     const ATSLayoutRecord* pALR;
897     OSStatus eStatus = ATSUDirectGetLayoutDataArrayPtrFromTextLayout(
898         maATSULayout, mnMinCharPos, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent,
899         (void**)&pALR, &iLayoutDataCount );
900     DBG_ASSERT( (eStatus==noErr), "ATSLayout::InitGIA() : no ATSLayoutRecords!\n");
901     if( (eStatus != noErr)
902     || (iLayoutDataCount <= 1) )
903         return false;
904 
905     // initialize glyph details
906     mpGlyphIds      = new ATSGlyphRef[ iLayoutDataCount ];
907     mpGlyphAdvances = new Fixed[ iLayoutDataCount ];
908     mpGlyphs2Chars  = new int[ iLayoutDataCount ];
909 
910     // measure details of the glyph layout
911     Fixed nLeftPos = 0;
912     for( ItemCount i = 0; i < iLayoutDataCount; ++i )
913     {
914         const ATSLayoutRecord& rALR = pALR[i];
915 
916         // distribute the widths as fairly as possible among the chars
917         const int nRelativeIdx = (rALR.originalOffset / 2);
918         if( i+1 < iLayoutDataCount )
919             mpCharWidths[ nRelativeIdx ] += pALR[i+1].realPos - rALR.realPos;
920 
921         // new glyph is available => finish measurement of old glyph
922         if( mnGlyphCount > 0 )
923             mpGlyphAdvances[ mnGlyphCount-1 ] = rALR.realPos - nLeftPos;
924 
925         // ignore marker or deleted glyphs
926         enum { MARKED_OUTGLYPH=0xFFFE, DROPPED_OUTGLYPH=0xFFFF};
927         if( rALR.glyphID >= MARKED_OUTGLYPH )
928             continue;
929 
930         DBG_ASSERT( !(rALR.flags & kATSGlyphInfoTerminatorGlyph),
931             "ATSLayout::InitGIA(): terminator glyph not marked as deleted!" );
932 
933         // store details of the visible glyphs
934         nLeftPos = rALR.realPos;
935         mpGlyphIds[ mnGlyphCount ] = rALR.glyphID;
936 
937         // map visible glyphs to their counterparts in the UTF16-character array
938         mpGlyphs2Chars[ mnGlyphCount ] = nRelativeIdx + mnMinCharPos;
939         mpChars2Glyphs[ nRelativeIdx ] = mnGlyphCount;
940 
941         ++mnGlyphCount;
942     }
943 
944     // measure complete width
945     mnCachedWidth = mnBaseAdv;
946     mnCachedWidth += pALR[iLayoutDataCount-1].realPos - pALR[0].realPos;
947 
948 #if (OSL_DEBUG_LEVEL > 1)
949     Fixed nWidthSum = mnBaseAdv;
950     for( int n = 0; n < mnCharCount; ++n )
951         nWidthSum += mpCharWidths[ n ];
952     DBG_ASSERT( (nWidthSum==mnCachedWidth),
953         "ATSLayout::InitGIA(): measured widths do not match!\n" );
954 #endif
955 
956     // #i91183# we need to split up the portion into sub-portions
957     // if the ATSU-layout differs too much from the requested layout
958     if( pArgs && pArgs->mpDXArray )
959     {
960         // TODO: non-strong-LTR case cases should be handled too
961         if( (pArgs->mnFlags & TEXT_LAYOUT_BIDI_STRONG)
962         && !(pArgs->mnFlags & TEXT_LAYOUT_BIDI_RTL) )
963         {
964             Fixed nSumCharWidths = 0;
965             SubPortion aSubPortion = { mnMinCharPos, 0, 0 };
966             for( int i = 0; i < mnCharCount; ++i )
967             {
968                 // calculate related logical position
969                 nSumCharWidths += mpCharWidths[i];
970 
971                 // start new sub-portion if needed
972                 const Fixed nNextXPos = Vcl2Fixed(pArgs->mpDXArray[i]);
973                 const Fixed nNextXOffset = nNextXPos - nSumCharWidths;
974                 const Fixed nFixedDiff = aSubPortion.mnXOffset - nNextXOffset;
975                 if( (nFixedDiff < -0xC000) || (nFixedDiff > +0xC000) ) {
976                     // get to the end of the current sub-portion
977                     // prevent splitting up at diacritics etc.
978                     int j = i;
979                     while( (++j < mnCharCount) && !mpCharWidths[j] );
980                     aSubPortion.mnEndCharPos = mnMinCharPos + j;
981                     // emit current sub-portion
982                     maSubPortions.push_back( aSubPortion );
983                     // prepare next sub-portion
984                     aSubPortion.mnMinCharPos = aSubPortion.mnEndCharPos;
985                     aSubPortion.mnXOffset = nNextXOffset;
986                 }
987             }
988 
989             // emit the remaining sub-portion
990             if( !maSubPortions.empty() )
991             {
992                 aSubPortion.mnEndCharPos = mnEndCharPos;
993                 if( aSubPortion.mnEndCharPos != aSubPortion.mnMinCharPos )
994                     maSubPortions.push_back( aSubPortion );
995             }
996         }
997 
998         // override layouted charwidths with requested charwidths
999         for( int n = 0; n < mnCharCount; ++n )
1000             mpCharWidths[ n ] = pArgs->mpDXArray[ n ];
1001     }
1002 
1003     // release the ATSU layout records
1004     ATSUDirectReleaseLayoutDataArrayPtr(NULL,
1005         kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, (void**)&pALR );
1006 
1007     return true;
1008 }
1009 
1010 // -----------------------------------------------------------------------
1011 
1012 bool ATSLayout::GetIdealX() const
1013 {
1014     // compute the ideal advance widths only once
1015     if( mpGlyphOrigAdvs != NULL )
1016         return true;
1017 
1018     DBG_ASSERT( (mpGlyphIds!=NULL), "GetIdealX() called with mpGlyphIds==NULL !" );
1019     DBG_ASSERT( (mrATSUStyle!=NULL), "GetIdealX called with mrATSUStyle==NULL !" );
1020 
1021     // TODO: cache ideal metrics per glyph?
1022     std::vector<ATSGlyphIdealMetrics> aIdealMetrics;
1023     aIdealMetrics.resize( mnGlyphCount );
1024     OSStatus theErr = ATSUGlyphGetIdealMetrics( mrATSUStyle,
1025         mnGlyphCount, &mpGlyphIds[0], sizeof(*mpGlyphIds), &aIdealMetrics[0] );
1026     DBG_ASSERT( (theErr==noErr), "ATSUGlyphGetIdealMetrics failed!");
1027     if( theErr != noErr )
1028         return false;
1029 
1030     mpGlyphOrigAdvs = new Fixed[ mnGlyphCount ];
1031     for( int i = 0;i < mnGlyphCount;++i )
1032         mpGlyphOrigAdvs[i] = FloatToFixed( aIdealMetrics[i].advance.x );
1033 
1034     return true;
1035 }
1036 
1037 // -----------------------------------------------------------------------
1038 
1039 bool ATSLayout::GetDeltaY() const
1040 {
1041     // don't bother to get the same delta-y-array more than once
1042     if( mpDeltaY != NULL )
1043         return true;
1044 
1045 #if 1
1046     if( !maATSULayout )
1047         return false;
1048 
1049     // get and keep the y-deltas in the mpDeltaY member variable
1050     // => release it in the destructor
1051     ItemCount nDeltaCount = 0;
1052     OSStatus theErr = ATSUDirectGetLayoutDataArrayPtrFromTextLayout(
1053         maATSULayout, mnMinCharPos, kATSUDirectDataBaselineDeltaFixedArray,
1054         (void**)&mpDeltaY, &nDeltaCount );
1055 
1056     DBG_ASSERT( (theErr==noErr ), "mpDeltaY - ATSUDirectGetLayoutDataArrayPtrFromTextLayout failed!\n");
1057     if( theErr != noErr )
1058         return false;
1059 
1060     if( mpDeltaY == NULL )
1061         return true;
1062 
1063     if( nDeltaCount != (ItemCount)mnGlyphCount )
1064     {
1065         DBG_WARNING( "ATSLayout::GetDeltaY() : wrong deltaY count!" );
1066         ATSUDirectReleaseLayoutDataArrayPtr( NULL,
1067             kATSUDirectDataBaselineDeltaFixedArray, (void**)&mpDeltaY );
1068         mpDeltaY = NULL;
1069         return false;
1070     }
1071 #endif
1072 
1073     return true;
1074 }
1075 
1076 // -----------------------------------------------------------------------
1077 
1078 #define DELETEAZ( X ) { delete[] X; X = NULL; }
1079 
1080 void ATSLayout::InvalidateMeasurements()
1081 {
1082     mnGlyphCount = -1;
1083     DELETEAZ( mpGlyphIds );
1084     DELETEAZ( mpCharWidths );
1085     DELETEAZ( mpChars2Glyphs );
1086     DELETEAZ( mpGlyphs2Chars );
1087     DELETEAZ( mpGlyphRTLFlags );
1088     DELETEAZ( mpGlyphAdvances );
1089     DELETEAZ( mpGlyphOrigAdvs );
1090     DELETEAZ( mpDeltaY );
1091 }
1092 
1093 // =======================================================================
1094 
1095 #if 0
1096 // helper class to convert ATSUI outlines to VCL PolyPolygons
1097 class PolyArgs
1098 {
1099 public:
1100                 PolyArgs();
1101                 ~PolyArgs();
1102 
1103     void        Init( PolyPolygon* pPolyPoly, long nXOffset, long nYOffset );
1104     void        AddPoint( const Float32Point&, PolyFlags );
1105     void        ClosePolygon();
1106 
1107 private:
1108     PolyPolygon* mpPolyPoly;
1109     long        mnXOffset, mnYOffset;
1110 
1111     Point*      mpPointAry;
1112     BYTE*       mpFlagAry;
1113     USHORT      mnMaxPoints;
1114 
1115     USHORT      mnPointCount;
1116     USHORT      mnPolyCount;
1117     bool        mbHasOffline;
1118 };
1119 
1120 // -----------------------------------------------------------------------
1121 
1122 PolyArgs::PolyArgs()
1123 :   mpPolyPoly(NULL),
1124     mnPointCount(0),
1125     mnPolyCount(0),
1126     mbHasOffline(false)
1127 {
1128     mnMaxPoints = 256;
1129     mpPointAry  = new Point[ mnMaxPoints ];
1130     mpFlagAry   = new BYTE [ mnMaxPoints ];
1131 }
1132 
1133 // -----------------------------------------------------------------------
1134 
1135 PolyArgs::~PolyArgs()
1136 {
1137     delete[] mpFlagAry;
1138     delete[] mpPointAry;
1139 }
1140 
1141 // -----------------------------------------------------------------------
1142 
1143 void PolyArgs::Init( PolyPolygon* pPolyPoly, long nXOffset, long nYOffset )
1144 {
1145     mnXOffset = nXOffset;
1146     mnYOffset = nYOffset;
1147     mpPolyPoly = pPolyPoly;
1148 
1149     mpPolyPoly->Clear();
1150     mnPointCount = 0;
1151     mnPolyCount  = 0;
1152 }
1153 
1154 // -----------------------------------------------------------------------
1155 
1156 void PolyArgs::AddPoint( const Float32Point& rPoint, PolyFlags eFlags )
1157 {
1158     if( mnPointCount >= mnMaxPoints )
1159     {
1160         // resize if needed (TODO: use STL?)
1161         mnMaxPoints *= 4;
1162         Point* mpNewPoints = new Point[ mnMaxPoints ];
1163         BYTE* mpNewFlags = new BYTE[ mnMaxPoints ];
1164         for( int i = 0; i < mnPointCount; ++i )
1165         {
1166             mpNewPoints[ i ] = mpPointAry[ i ];
1167             mpNewFlags[ i ] = mpFlagAry[ i ];
1168         }
1169         delete[] mpFlagAry;
1170         delete[] mpPointAry;
1171         mpPointAry = mpNewPoints;
1172         mpFlagAry = mpNewFlags;
1173     }
1174 
1175     // convert to pixels and add startpoint offset
1176     int nXPos = Float32ToInt( rPoint.x );
1177     int nYPos = Float32ToInt( rPoint.y );
1178     mpPointAry[ mnPointCount ] = Point( nXPos + mnXOffset, nYPos + mnYOffset );
1179     // set point flags
1180     mpFlagAry[ mnPointCount++ ]= eFlags;
1181     mbHasOffline |= (eFlags != POLY_NORMAL);
1182 }
1183 
1184 // -----------------------------------------------------------------------
1185 
1186 void PolyArgs::ClosePolygon()
1187 {
1188     if( !mnPolyCount++ )
1189          return;
1190 
1191     // append finished polygon
1192     Polygon aPoly( mnPointCount, mpPointAry, (mbHasOffline ? mpFlagAry : NULL) );
1193     mpPolyPoly->Insert( aPoly );
1194 
1195     // prepare for new polygon
1196     mnPointCount = 0;
1197     mbHasOffline = false;
1198 }
1199 #endif
1200 // =======================================================================
1201 
1202 // glyph fallback is supported directly by Aqua
1203 // so methods used only by MultiSalLayout can be dummy implementated
1204 bool ATSLayout::GetGlyphOutlines( SalGraphics&, PolyPolyVector& ) const { return false; }
1205 void ATSLayout::InitFont() {}
1206 void ATSLayout::MoveGlyph( int /*nStart*/, long /*nNewXPos*/ ) {}
1207 void ATSLayout::DropGlyph( int /*nStart*/ ) {}
1208 void ATSLayout::Simplify( bool /*bIsBase*/ ) {}
1209 
1210 // get the ImplFontData for a glyph fallback font
1211 // for a glyphid that was returned by ATSLayout::GetNextGlyphs()
1212 const ImplFontData* ATSLayout::GetFallbackFontData( sal_GlyphId aGlyphId ) const
1213 {
1214     // check if any fallback fonts were needed
1215     if( !mpFallbackInfo )
1216         return NULL;
1217     // check if the current glyph needs a fallback font
1218     int nFallbackLevel = (aGlyphId & GF_FONTMASK) >> GF_FONTSHIFT;
1219     if( !nFallbackLevel )
1220         return NULL;
1221     return mpFallbackInfo->GetFallbackFontData( nFallbackLevel );
1222 }
1223 
1224 // =======================================================================
1225 
1226 int FallbackInfo::AddFallback( ATSUFontID nFontId )
1227 {
1228     // check if the fallback font is already known
1229     for( int nLevel = 0; nLevel < mnMaxLevel; ++nLevel )
1230         if( maATSUFontId[ nLevel ] == nFontId )
1231             return (nLevel + 1);
1232 
1233     // append new fallback font if possible
1234     if( mnMaxLevel >= MAX_FALLBACK-1 )
1235         return 0;
1236     // keep ATSU font id of fallback font
1237     maATSUFontId[ mnMaxLevel ] = nFontId;
1238     // find and cache the corresponding ImplFontData pointer
1239     const SystemFontList* pSFL = GetSalData()->mpFontList;
1240     const ImplMacFontData* pFontData = pSFL->GetFontDataFromId( nFontId );
1241     maFontData[ mnMaxLevel ] = pFontData;
1242     // increase fallback level by one
1243     return (++mnMaxLevel);
1244 }
1245 
1246 // -----------------------------------------------------------------------
1247 
1248 const ImplFontData* FallbackInfo::GetFallbackFontData( int nFallbackLevel ) const
1249 {
1250     const ImplMacFontData* pFallbackFont = maFontData[ nFallbackLevel-1 ];
1251     return pFallbackFont;
1252 }
1253 
1254 // =======================================================================
1255 
1256 SalLayout* AquaSalGraphics::GetTextLayout( ImplLayoutArgs&, int /*nFallbackLevel*/ )
1257 {
1258     ATSLayout* pATSLayout = new ATSLayout( maATSUStyle, mfFontScale );
1259     return pATSLayout;
1260 }
1261 
1262 // =======================================================================
1263 
1264