summaryrefslogtreecommitdiff
path: root/vcl/aqua/source/gdi/salatslayout.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/aqua/source/gdi/salatslayout.cxx')
-rw-r--r--vcl/aqua/source/gdi/salatslayout.cxx1156
1 files changed, 1156 insertions, 0 deletions
diff --git a/vcl/aqua/source/gdi/salatslayout.cxx b/vcl/aqua/source/gdi/salatslayout.cxx
new file mode 100644
index 000000000000..3a88422a2160
--- /dev/null
+++ b/vcl/aqua/source/gdi/salatslayout.cxx
@@ -0,0 +1,1156 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*************************************************************************
+*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+************************************************************************/
+
+#include "vcl/salgdi.hxx"
+#include "saldata.hxx"
+#include "salgdi.h"
+#include "vcl/sallayout.hxx"
+#include "salatsuifontutils.hxx"
+#include "tools/debug.hxx"
+
+#include <math.h>
+
+// =======================================================================
+
+class ATSLayout : public SalLayout
+{
+public:
+ ATSLayout( ATSUStyle&, float fFontScale );
+ virtual ~ATSLayout();
+
+ virtual bool LayoutText( ImplLayoutArgs& );
+ virtual void AdjustLayout( ImplLayoutArgs& );
+ virtual void DrawText( SalGraphics& ) const;
+
+ virtual int GetNextGlyphs( int nLen, sal_GlyphId* pGlyphs, Point& rPos, int&,
+ sal_Int32* pGlyphAdvances, int* pCharIndexes ) const;
+
+ virtual long GetTextWidth() const;
+ virtual long FillDXArray( long* pDXArray ) const;
+ virtual int GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const;
+ virtual void GetCaretPositions( int nArraySize, long* pCaretXArray ) const;
+ virtual bool GetGlyphOutlines( SalGraphics&, PolyPolyVector& ) const;
+ virtual bool GetBoundRect( SalGraphics&, Rectangle& ) const;
+
+ const ImplFontData* GetFallbackFontData( sal_GlyphId ) const;
+
+ virtual void InitFont();
+ virtual void MoveGlyph( int nStart, long nNewXPos );
+ virtual void DropGlyph( int nStart );
+ virtual void Simplify( bool bIsBase );
+
+private:
+ ATSUStyle& mrATSUStyle;
+ ATSUTextLayout maATSULayout;
+ int mnCharCount; // ==mnEndCharPos-mnMinCharPos
+ // to prevent ATS overflowing the Fixed16.16 values
+ // ATS font requests get size limited by downscaling huge fonts
+ // in these cases the font scale becomes something bigger than 1.0
+ float mfFontScale;
+
+private:
+ bool InitGIA( ImplLayoutArgs* pArgs = NULL ) const;
+ bool GetIdealX() const;
+ bool GetDeltaY() const;
+ void InvalidateMeasurements();
+
+ int Fixed2Vcl( Fixed ) const; // convert ATSU-Fixed units to VCL units
+ int AtsuPix2Vcl( int ) const; // convert ATSU-Pixel units to VCL units
+ Fixed Vcl2Fixed( int ) const; // convert VCL units to ATSU-Fixed units
+
+ // cached details about the resulting layout
+ // mutable members since these details are all lazy initialized
+ mutable int mnGlyphCount; // glyph count
+ mutable Fixed mnCachedWidth; // cached value of resulting typographical width
+ int mnTrailingSpaceWidth; // in Pixels
+
+ mutable ATSGlyphRef* mpGlyphIds; // ATSU glyph ids
+ mutable Fixed* mpCharWidths; // map relative charpos to charwidth
+ mutable int* mpChars2Glyphs; // map relative charpos to absolute glyphpos
+ mutable int* mpGlyphs2Chars; // map absolute glyphpos to absolute charpos
+ mutable bool* mpGlyphRTLFlags; // BiDi status for glyphs: true if RTL
+ mutable Fixed* mpGlyphAdvances; // contains glyph widths for the justified layout
+ mutable Fixed* mpGlyphOrigAdvs; // contains glyph widths for the unjustified layout
+ mutable Fixed* mpDeltaY; // vertical offset from the baseline
+
+ struct SubPortion { int mnMinCharPos, mnEndCharPos; Fixed mnXOffset; };
+ typedef std::vector<SubPortion> SubPortionVector;
+ mutable SubPortionVector maSubPortions; // Writer&ATSUI layouts can differ quite a bit...
+
+ // storing details about fonts used in glyph-fallback for this layout
+ mutable class FallbackInfo* mpFallbackInfo;
+
+ // x-offset relative to layout origin
+ // currently only used in RTL-layouts
+ mutable Fixed mnBaseAdv;
+};
+
+class FallbackInfo
+{
+public:
+ FallbackInfo() : mnMaxLevel(0) {}
+ int AddFallback( ATSUFontID );
+ const ImplFontData* GetFallbackFontData( int nLevel ) const;
+
+private:
+ const ImplMacFontData* maFontData[ MAX_FALLBACK ];
+ ATSUFontID maATSUFontId[ MAX_FALLBACK ];
+ int mnMaxLevel;
+};
+
+// =======================================================================
+
+ATSLayout::ATSLayout( ATSUStyle& rATSUStyle, float fFontScale )
+: mrATSUStyle( rATSUStyle ),
+ maATSULayout( NULL ),
+ mnCharCount( 0 ),
+ mfFontScale( fFontScale ),
+ mnGlyphCount( -1 ),
+ mnCachedWidth( 0 ),
+ mnTrailingSpaceWidth( 0 ),
+ mpGlyphIds( NULL ),
+ mpCharWidths( NULL ),
+ mpChars2Glyphs( NULL ),
+ mpGlyphs2Chars( NULL ),
+ mpGlyphRTLFlags( NULL ),
+ mpGlyphAdvances( NULL ),
+ mpGlyphOrigAdvs( NULL ),
+ mpDeltaY( NULL ),
+ mpFallbackInfo( NULL ),
+ mnBaseAdv( 0 )
+{}
+
+// -----------------------------------------------------------------------
+
+ATSLayout::~ATSLayout()
+{
+ if( mpDeltaY )
+ ATSUDirectReleaseLayoutDataArrayPtr( NULL,
+ kATSUDirectDataBaselineDeltaFixedArray, (void**)&mpDeltaY );
+
+ if( maATSULayout )
+ ATSUDisposeTextLayout( maATSULayout );
+
+ delete[] mpGlyphRTLFlags;
+ delete[] mpGlyphs2Chars;
+ delete[] mpChars2Glyphs;
+ if( mpCharWidths != mpGlyphAdvances )
+ delete[] mpCharWidths;
+ delete[] mpGlyphIds;
+ delete[] mpGlyphOrigAdvs;
+ delete[] mpGlyphAdvances;
+
+ delete mpFallbackInfo;
+}
+
+// -----------------------------------------------------------------------
+
+inline int ATSLayout::Fixed2Vcl( Fixed nFixed ) const
+{
+ float fFloat = mfFontScale * FixedToFloat( nFixed );
+ return static_cast<int>(fFloat + 0.5);
+}
+
+// -----------------------------------------------------------------------
+
+inline int ATSLayout::AtsuPix2Vcl( int nAtsuPixel) const
+{
+ float fVclPixel = mfFontScale * nAtsuPixel;
+ fVclPixel += (fVclPixel>=0) ? +0.5 : -0.5; // prepare rounding to int
+ int nVclPixel = static_cast<int>( fVclPixel);
+ return nVclPixel;
+}
+
+// -----------------------------------------------------------------------
+
+inline Fixed ATSLayout::Vcl2Fixed( int nPixel ) const
+{
+ return FloatToFixed( nPixel / mfFontScale );
+}
+
+// -----------------------------------------------------------------------
+/**
+ * ATSLayout::LayoutText : Manage text layouting
+ *
+ * @param rArgs: contains array of char to be layouted, starting and ending position of the text to layout
+ *
+ * Typographic layout of text by using the style maATSUStyle
+ *
+ * @return : true if everything is ok
+**/
+bool ATSLayout::LayoutText( ImplLayoutArgs& rArgs )
+{
+ if( maATSULayout )
+ ATSUDisposeTextLayout( maATSULayout );
+
+ maATSULayout = NULL;
+
+ // Layout text
+ // set up our locals, verify parameters...
+ DBG_ASSERT( (rArgs.mpStr!=NULL), "ATSLayout::LayoutText() with rArgs.mpStr==NULL !!!");
+ DBG_ASSERT( (mrATSUStyle!=NULL), "ATSLayout::LayoutText() with ATSUStyle==NULL !!!");
+
+ SalLayout::AdjustLayout( rArgs );
+ mnCharCount = mnEndCharPos - mnMinCharPos;
+
+ // Workaround a bug in ATSUI with empty string
+ if( mnCharCount<=0 )
+ return false;
+
+#if (OSL_DEBUG_LEVEL > 3)
+ Fixed fFontSize = 0;
+ ByteCount nDummy;
+ ATSUGetAttribute( mrATSUStyle, kATSUSizeTag, sizeof(fFontSize), &fFontSize, &nDummy);
+ String aUniName( &rArgs.mpStr[rArgs.mnMinCharPos], mnCharCount );
+ ByteString aCName( aUniName, RTL_TEXTENCODING_UTF8 );
+ fprintf( stderr, "ATSLayout( \"%s\" %d..%d of %d) with h=%4.1f\n",
+ aCName.GetBuffer(),rArgs.mnMinCharPos,rArgs.mnEndCharPos,rArgs.mnLength,Fix2X(fFontSize) );
+#endif
+
+ // create the ATSUI layout
+ UniCharCount nRunLengths[1] = { mnCharCount };
+ const int nRunCount = sizeof(nRunLengths)/sizeof(*nRunLengths);
+ OSStatus eStatus = ATSUCreateTextLayoutWithTextPtr( rArgs.mpStr,
+ rArgs.mnMinCharPos, mnCharCount, rArgs.mnLength,
+ nRunCount, &nRunLengths[0], &mrATSUStyle,
+ &maATSULayout);
+
+ DBG_ASSERT( (eStatus==noErr), "ATSUCreateTextLayoutWithTextPtr failed\n");
+ if( eStatus != noErr )
+ return false;
+
+ // prepare setting of layout controls
+ static const int nMaxTagCount = 1;
+ ATSUAttributeTag aTagAttrs[ nMaxTagCount ];
+ ByteCount aTagSizes[ nMaxTagCount ];
+ ATSUAttributeValuePtr aTagValues[ nMaxTagCount ];
+
+ // prepare control of "glyph fallback"
+ const SalData* pSalData = GetSalData();
+ ATSUFontFallbacks aFontFallbacks = pSalData->mpFontList->maFontFallbacks;
+ aTagAttrs[0] = kATSULineFontFallbacksTag;
+ aTagSizes[0] = sizeof( ATSUFontFallbacks );
+ aTagValues[0] = &aFontFallbacks;
+
+ // set paragraph layout controls
+ ATSUSetLayoutControls( maATSULayout, 1, aTagAttrs, aTagSizes, aTagValues );
+
+ // enable "glyph fallback"
+ ATSUSetTransientFontMatching( maATSULayout, true );
+
+ // control run-specific layout controls
+ if( (rArgs.mnFlags & SAL_LAYOUT_BIDI_STRONG) != 0 )
+ {
+ // control BiDi defaults
+ BOOL nLineDirTag = kATSULeftToRightBaseDirection;
+ if( (rArgs.mnFlags & SAL_LAYOUT_BIDI_RTL) != 0 )
+ nLineDirTag = kATSURightToLeftBaseDirection;
+ aTagAttrs[0] = kATSULineDirectionTag;
+ aTagSizes[0] = sizeof( nLineDirTag );
+ aTagValues[0] = &nLineDirTag;
+ // set run-specific layout controls
+ ATSUSetLayoutControls( maATSULayout, 1, aTagAttrs, aTagSizes, aTagValues );
+ }
+
+ return true;
+}
+
+// -----------------------------------------------------------------------
+/**
+ * ATSLayout::AdjustLayout : Adjust layout style
+ *
+ * @param rArgs: contains attributes relevant to do a text specific layout
+ *
+ * Adjust text layout by moving glyphs to match the requested logical widths
+ *
+ * @return : none
+**/
+void ATSLayout::AdjustLayout( ImplLayoutArgs& rArgs )
+{
+ int nOrigWidth = GetTextWidth();
+ int nPixelWidth = rArgs.mnLayoutWidth;
+ if( !nPixelWidth && rArgs.mpDXArray ) {
+ // for now we are only interested in the layout width
+ // TODO: use all mpDXArray elements for layouting
+ nPixelWidth = rArgs.mpDXArray[ mnCharCount - 1 ];
+
+ // workaround for ATSUI not using trailing spaces for justification
+ int i = mnCharCount;
+ while( (--i >= 0) && IsSpacingGlyph( rArgs.mpStr[mnMinCharPos+i]|GF_ISCHAR ) ) {}
+ if( i < 0 ) // nothing to do if the text is all spaces
+ return;
+ // #i91685# trailing letters are left aligned (right aligned for RTL)
+ mnTrailingSpaceWidth = rArgs.mpDXArray[ mnCharCount-1 ];
+ if( i > 0 )
+ mnTrailingSpaceWidth -= rArgs.mpDXArray[ i-1 ];
+ InitGIA(); // ensure valid mpCharWidths[], TODO: use GetIdealX() instead?
+ mnTrailingSpaceWidth -= Fixed2Vcl( mpCharWidths[i] );
+ // ignore trailing space for calculating the available width
+ nOrigWidth -= mnTrailingSpaceWidth;
+ nPixelWidth -= mnTrailingSpaceWidth;
+ // in RTL-layouts trailing spaces are leftmost
+ // TODO: use BiDi-algorithm to thoroughly check this assumption
+ if( rArgs.mnFlags & SAL_LAYOUT_BIDI_RTL)
+ mnBaseAdv = mnTrailingSpaceWidth;
+ }
+ // return early if there is nothing to do
+ if( !nPixelWidth )
+ return;
+
+ // HACK: justification requests which change the width by just one pixel were probably
+ // #i86038# introduced by lossy conversions between integer based coordinate system
+ // => ignoring such requests has many more benefits than eventual drawbacks
+ if( (nOrigWidth >= nPixelWidth-1) && (nOrigWidth <= nPixelWidth+1) )
+ return;
+
+ // changing the layout will make all previous measurements invalid
+ InvalidateMeasurements();
+
+ ATSUAttributeTag nTags[3];
+ ATSUAttributeValuePtr nVals[3];
+ ByteCount nBytes[3];
+
+ Fixed nFixedWidth = Vcl2Fixed( nPixelWidth );
+ mnCachedWidth = nFixedWidth;
+ Fract nFractFactor = kATSUFullJustification;
+ ATSLineLayoutOptions nLineLayoutOptions = kATSLineHasNoHangers | kATSLineHasNoOpticalAlignment | kATSLineBreakToNearestCharacter;
+
+ nTags[0] = kATSULineWidthTag;
+ nVals[0] = &nFixedWidth;
+ nBytes[0] = sizeof(Fixed);
+ nTags[1] = kATSULineLayoutOptionsTag;
+ nVals[1] = &nLineLayoutOptions;
+ nBytes[1] = sizeof(ATSLineLayoutOptions);
+ nTags[2] = kATSULineJustificationFactorTag;
+ nVals[2] = &nFractFactor;
+ nBytes[2] = sizeof(Fract);
+
+ OSStatus eStatus = ATSUSetLayoutControls( maATSULayout, 3, nTags, nBytes, nVals );
+ if( eStatus != noErr )
+ return;
+
+ // update the measurements of the justified layout to match the justification request
+ if( rArgs.mpDXArray )
+ InitGIA( &rArgs );
+}
+
+// -----------------------------------------------------------------------
+/**
+ * ATSLayout::DrawText : Draw text to screen
+ *
+ * @param rGraphics: device to draw to
+ *
+ * Draw the layouted text to the CGContext
+ *
+ * @return : none
+**/
+void ATSLayout::DrawText( SalGraphics& rGraphics ) const
+{
+ AquaSalGraphics& rAquaGraphics = static_cast<AquaSalGraphics&>(rGraphics);
+
+ // short circuit if there is nothing to do
+ if( (mnCharCount <= 0)
+ || !rAquaGraphics.CheckContext() )
+ return;
+
+ // the view is vertically flipped => flipped glyphs
+ // so apply a temporary transformation that it flips back
+ // also compensate if the font was size limited
+ CGContextSaveGState( rAquaGraphics.mrContext );
+ CGContextScaleCTM( rAquaGraphics.mrContext, +mfFontScale, -mfFontScale );
+ CGContextSetShouldAntialias( rAquaGraphics.mrContext, !rAquaGraphics.mbNonAntialiasedText );
+
+ // prepare ATSUI drawing attributes
+ static const ItemCount nMaxControls = 8;
+ ATSUAttributeTag theTags[ nMaxControls ];
+ ByteCount theSizes[ nMaxControls];
+ ATSUAttributeValuePtr theValues[ nMaxControls ];
+ ItemCount numcontrols = 0;
+
+ // Tell ATSUI to use CoreGraphics
+ theTags[numcontrols] = kATSUCGContextTag;
+ theSizes[numcontrols] = sizeof( CGContextRef );
+ theValues[numcontrols++] = &rAquaGraphics.mrContext;
+
+ // Rotate if necessary
+ if( rAquaGraphics.mnATSUIRotation != 0 )
+ {
+ Fixed theAngle = rAquaGraphics.mnATSUIRotation;
+ theTags[numcontrols] = kATSULineRotationTag;
+ theSizes[numcontrols] = sizeof( Fixed );
+ theValues[numcontrols++] = &theAngle;
+ }
+
+ DBG_ASSERT( (numcontrols <= nMaxControls), "ATSLayout::DrawText() numcontrols overflow" );
+ OSStatus theErr = ATSUSetLayoutControls (maATSULayout, numcontrols, theTags, theSizes, theValues);
+ DBG_ASSERT( (theErr==noErr), "ATSLayout::DrawText ATSUSetLayoutControls failed!\n" );
+
+ // Draw the text
+ const Point aPos = GetDrawPosition( Point(mnBaseAdv,0) );
+ const Fixed nFixedX = Vcl2Fixed( +aPos.X() );
+ const Fixed nFixedY = Vcl2Fixed( -aPos.Y() ); // adjusted for y-mirroring
+ if( maSubPortions.empty() )
+ ATSUDrawText( maATSULayout, mnMinCharPos, mnCharCount, nFixedX, nFixedY );
+ else
+ {
+ // draw the sub-portions and apply individual adjustments
+ SubPortionVector::const_iterator it = maSubPortions.begin();
+ for(; it != maSubPortions.end(); ++it )
+ {
+ const SubPortion& rSubPortion = *it;
+ // calculate sub-portion offset for rotated text
+ Fixed nXOfsFixed = 0, nYOfsFixed = 0;
+ if( rAquaGraphics.mnATSUIRotation != 0 )
+ {
+ const double fRadians = rAquaGraphics.mnATSUIRotation * (M_PI/0xB40000);
+ nXOfsFixed = static_cast<Fixed>(static_cast<double>(+rSubPortion.mnXOffset) * cos( fRadians ));
+ nYOfsFixed = static_cast<Fixed>(static_cast<double>(+rSubPortion.mnXOffset) * sin( fRadians ));
+ }
+
+ // draw sub-portions
+ ATSUDrawText( maATSULayout,
+ rSubPortion.mnMinCharPos, rSubPortion.mnEndCharPos - rSubPortion.mnMinCharPos,
+ nFixedX + nXOfsFixed, nFixedY + nYOfsFixed );
+ }
+ }
+
+ // request an update of the changed window area
+ if( rAquaGraphics.IsWindowGraphics() )
+ {
+ Rect drawRect; // rectangle of the changed area
+ theErr = ATSUMeasureTextImage( maATSULayout,
+ mnMinCharPos, mnCharCount, nFixedX, nFixedY, &drawRect );
+ if( theErr == noErr )
+ {
+ // FIXME: transformation from baseline to top left
+ // with the simple approach below we invalidate too much
+ short d = drawRect.bottom - drawRect.top;
+ drawRect.top -= d;
+ drawRect.bottom += d;
+ CGRect aRect = CGRectMake( drawRect.left, drawRect.top,
+ drawRect.right - drawRect.left,
+ drawRect.bottom - drawRect.top );
+ aRect = CGContextConvertRectToDeviceSpace( rAquaGraphics.mrContext, aRect );
+ rAquaGraphics.RefreshRect( aRect );
+ }
+ }
+
+ // restore the original graphic context transformations
+ CGContextRestoreGState( rAquaGraphics.mrContext );
+}
+
+// -----------------------------------------------------------------------
+/**
+ * ATSLayout::GetNextGlyphs : Get info about next glyphs in the layout
+ *
+ * @param nLen: max number of char
+ * @param pGlyphs: returned array of glyph ids
+ * @param rPos: returned x starting position
+ * @param nStart: index of the first requested glyph
+ * @param pGlyphAdvances: returned array of glyphs advances
+ * @param pCharIndexes: returned array of char indexes
+ *
+ * Returns infos about the next glyphs in the text layout
+ *
+ * @return : number of glyph details that were provided
+**/
+int ATSLayout::GetNextGlyphs( int nLen, sal_GlyphId* pGlyphIDs, Point& rPos, int& nStart,
+ sal_Int32* pGlyphAdvances, int* pCharIndexes ) const
+{
+ if( nStart < 0 ) // first glyph requested?
+ nStart = 0;
+
+ // get glyph measurements
+ InitGIA();
+ // some measurements are only needed for multi-glyph results
+ if( nLen > 1 )
+ {
+ GetIdealX();
+ GetDeltaY();
+ }
+
+ if( nStart >= mnGlyphCount ) // no glyph left?
+ return 0;
+
+ // calculate glyph position relative to layout base
+ // TODO: avoid for nStart!=0 case by reusing rPos
+ Fixed nXOffset = mnBaseAdv;
+ for( int i = 0; i < nStart; ++i )
+ nXOffset += mpGlyphAdvances[ i ];
+ // if sub-portion offsets are involved there is an additional x-offset
+ if( !maSubPortions.empty() )
+ {
+ // prepare to find the sub-portion
+ int nCharPos = nStart + mnMinCharPos;
+ if( mpGlyphs2Chars )
+ nCharPos = mpGlyphs2Chars[nStart];
+
+ // find the matching subportion
+ // TODO: is a non-linear search worth it?
+ SubPortionVector::const_iterator it = maSubPortions.begin();
+ for(; it != maSubPortions.end(); ++it) {
+ const SubPortion& r = *it;
+ if( nCharPos < r.mnMinCharPos )
+ continue;
+ if( nCharPos >= r.mnEndCharPos )
+ continue;
+ // apply the sub-portion xoffset
+ nXOffset += r.mnXOffset;
+ break;
+ }
+ }
+
+ Fixed nYOffset = 0;
+ if( mpDeltaY )
+ nYOffset = mpDeltaY[ nStart ];
+
+ // calculate absolute position in pixel units
+ const Point aRelativePos( Fix2Long(static_cast<Fixed>(nXOffset*mfFontScale)), Fix2Long(static_cast<Fixed>(nYOffset*mfFontScale)) );
+ rPos = GetDrawPosition( aRelativePos );
+
+ // update return values
+ int nCount = 0;
+ while( nCount < nLen )
+ {
+ ++nCount;
+ sal_GlyphId nGlyphId = mpGlyphIds[nStart];
+
+ // check if glyph fallback is needed for this glyph
+ // TODO: use ATSUDirectGetLayoutDataArrayPtrFromTextLayout(kATSUDirectDataStyleIndex) API instead?
+ const int nCharPos = mpGlyphs2Chars ? mpGlyphs2Chars[nStart] : nStart + mnMinCharPos;
+ ATSUFontID nFallbackFontID = kATSUInvalidFontID;
+ UniCharArrayOffset nChangedOffset = 0;
+ UniCharCount nChangedLength = 0;
+ OSStatus eStatus = ATSUMatchFontsToText( maATSULayout, nCharPos, kATSUToTextEnd,
+ &nFallbackFontID, &nChangedOffset, &nChangedLength );
+ if( (eStatus == kATSUFontsMatched) && ((int)nChangedOffset == nCharPos) )
+ {
+ // fallback is needed
+ if( !mpFallbackInfo )
+ mpFallbackInfo = new FallbackInfo;
+ // register fallback font
+ const int nLevel = mpFallbackInfo->AddFallback( nFallbackFontID );
+ // update sal_GlyphId with fallback level
+ nGlyphId |= (nLevel << GF_FONTSHIFT);
+ }
+
+ // update resulting glyphid array
+ *(pGlyphIDs++) = nGlyphId;
+
+ // update returned glyph advance array
+ if( pGlyphAdvances )
+ *(pGlyphAdvances++) = Fixed2Vcl( mpGlyphAdvances[nStart] );
+
+ // update returned index-into-string array
+ if( pCharIndexes )
+ {
+ int nCharPos;
+ if( mpGlyphs2Chars )
+ nCharPos = mpGlyphs2Chars[nStart];
+ else
+ nCharPos = nStart + mnMinCharPos;
+ *(pCharIndexes++) = nCharPos;
+ }
+
+ // stop at last glyph
+ if( ++nStart >= mnGlyphCount )
+ break;
+
+ // stop when next the x-position is unexpected
+ if( !maSubPortions.empty() )
+ break; // TODO: finish the complete sub-portion
+ if( !pGlyphAdvances && mpGlyphOrigAdvs )
+ if( mpGlyphAdvances[nStart-1] != mpGlyphOrigAdvs[nStart-1] )
+ break;
+
+ // stop when the next y-position is unexpected
+ if( mpDeltaY )
+ if( mpDeltaY[nStart-1] != mpDeltaY[nStart] )
+ break;
+ }
+
+ return nCount;
+}
+
+// -----------------------------------------------------------------------
+/**
+ * ATSLayout::GetTextWidth : Get typographic width of layouted text
+ *
+ * Get typographic bounds of the text
+ *
+ * @return : text width
+**/
+long ATSLayout::GetTextWidth() const
+{
+ if( mnCharCount <= 0 )
+ return 0;
+
+ DBG_ASSERT( (maATSULayout!=NULL), "ATSLayout::GetTextWidth() with maATSULayout==NULL !\n");
+ if( !maATSULayout )
+ return 0;
+
+ if( !mnCachedWidth )
+ {
+ // prepare precise measurements on pixel based or reference-device
+ const UInt16 eTypeOfBounds = kATSUseFractionalOrigins;
+
+ // determine number of needed measurement trapezoids
+ ItemCount nMaxBounds = 0;
+ OSStatus err = ATSUGetGlyphBounds( maATSULayout, 0, 0, mnMinCharPos, mnCharCount,
+ eTypeOfBounds, 0, NULL, &nMaxBounds );
+ if( (err != noErr)
+ || (nMaxBounds <= 0) )
+ return 0;
+
+ // get the trapezoids
+ typedef std::vector<ATSTrapezoid> TrapezoidVector;
+ TrapezoidVector aTrapezoidVector( nMaxBounds );
+ ItemCount nBoundsCount = 0;
+ err = ATSUGetGlyphBounds( maATSULayout, 0, 0, mnMinCharPos, mnCharCount,
+ eTypeOfBounds, nMaxBounds, &aTrapezoidVector[0], &nBoundsCount );
+ if( err != noErr )
+ return 0;
+
+ DBG_ASSERT( (nBoundsCount <= nMaxBounds), "ATSLayout::GetTextWidth() : too many trapezoids !\n");
+
+ // find the bound extremas
+ Fixed nLeftBound = 0;
+ Fixed nRightBound = 0;
+ for( ItemCount i = 0; i < nBoundsCount; ++i )
+ {
+ const ATSTrapezoid& rTrap = aTrapezoidVector[i];
+ if( (i == 0) || (nLeftBound < rTrap.lowerLeft.x) )
+ nLeftBound = rTrap.lowerLeft.x;
+ if( (i == 0) || (nRightBound > rTrap.lowerRight.x) )
+ nRightBound = rTrap.lowerRight.x;
+ }
+
+ // measure the bound extremas
+ mnCachedWidth = nRightBound - nLeftBound;
+ // adjust for eliminated trailing space widths
+ }
+
+ int nScaledWidth = Fixed2Vcl( mnCachedWidth );
+ nScaledWidth += mnTrailingSpaceWidth;
+ return nScaledWidth;
+}
+
+// -----------------------------------------------------------------------
+/**
+ * ATSLayout::FillDXArray : Get Char widths
+ *
+ * @param pDXArray: array to be filled with x-advances
+ *
+ * Fill the pDXArray with horizontal deltas : CharWidths
+ *
+ * @return : typographical width of the complete text layout
+**/
+long ATSLayout::FillDXArray( long* pDXArray ) const
+{
+ // short circuit requests which don't need full details
+ if( !pDXArray )
+ return GetTextWidth();
+
+ // check assumptions
+ DBG_ASSERT( !mnTrailingSpaceWidth, "ATSLayout::FillDXArray() with nTSW!=0" );
+
+ // initialize details about the resulting layout
+ InitGIA();
+
+ // distribute the widths among the string elements
+ int nPixWidth = 0;
+ mnCachedWidth = 0;
+ for( int i = 0; i < mnCharCount; ++i )
+ {
+ // convert and adjust for accumulated rounding errors
+ mnCachedWidth += mpCharWidths[i];
+ const int nOldPixWidth = nPixWidth;
+ nPixWidth = Fixed2Vcl( mnCachedWidth );
+ pDXArray[i] = nPixWidth - nOldPixWidth;
+ }
+
+ return nPixWidth;
+}
+
+// -----------------------------------------------------------------------
+/**
+ * ATSLayout::GetTextBreak : Find line break depending on width
+ *
+ * @param nMaxWidth : maximal logical text width in subpixel units
+ * @param nCharExtra: expanded/condensed spacing in subpixel units
+ * @param nFactor: number of subpixel units per pixel
+ *
+ * Measure the layouted text to find the typographical line break
+ * the result is needed by the language specific line breaking
+ *
+ * @return : string index corresponding to the suggested line break
+**/
+int ATSLayout::GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const
+{
+ if( !maATSULayout )
+ return STRING_LEN;
+
+ // the semantics of the legacy use case (nCharExtra!=0) cannot be mapped to ATSUBreakLine()
+ if( nCharExtra != 0 )
+ {
+ // prepare the measurement by layouting and measuring the un-expanded/un-condensed text
+ if( !InitGIA() )
+ return STRING_LEN;
+
+ // TODO: use a better way than by testing each the char position
+ ATSUTextMeasurement nATSUSumWidth = 0;
+ const ATSUTextMeasurement nATSUMaxWidth = Vcl2Fixed( nMaxWidth / nFactor );
+ const ATSUTextMeasurement nATSUExtraWidth = Vcl2Fixed( nCharExtra ) / nFactor;
+ for( int i = 0; i < mnCharCount; ++i )
+ {
+ nATSUSumWidth += mpCharWidths[i];
+ if( nATSUSumWidth >= nATSUMaxWidth )
+ return (mnMinCharPos + i);
+ nATSUSumWidth += nATSUExtraWidth;
+ if( nATSUSumWidth >= nATSUMaxWidth )
+ if( i+1 < mnCharCount )
+ return (mnMinCharPos + i);
+ }
+
+ return STRING_LEN;
+ }
+
+ // get a quick overview on what could fit
+ const long nPixelWidth = (nMaxWidth - (nCharExtra * mnCharCount)) / nFactor;
+ if( nPixelWidth <= 0 )
+ return mnMinCharPos;
+
+ // check assumptions
+ DBG_ASSERT( !mnTrailingSpaceWidth, "ATSLayout::GetTextBreak() with nTSW!=0" );
+
+ // initial measurement of text break position
+ UniCharArrayOffset nBreakPos = mnMinCharPos;
+ const ATSUTextMeasurement nATSUMaxWidth = Vcl2Fixed( nPixelWidth );
+ if( nATSUMaxWidth <= 0xFFFF ) // #i108584# avoid ATSU rejecting the parameter
+ return mnMinCharPos; // or do ATSUMaxWidth=0x10000;
+ OSStatus eStatus = ATSUBreakLine( maATSULayout, mnMinCharPos,
+ nATSUMaxWidth, false, &nBreakPos );
+ if( (eStatus != noErr) && (eStatus != kATSULineBreakInWord) )
+ return STRING_LEN;
+
+ // the result from ATSUBreakLine() doesn't match the semantics expected by its
+ // application layer callers from SW+SVX+I18N. Adjust the results to the expectations:
+
+ // ATSU reports that everything fits even when trailing spaces would break the line
+ // #i89789# OOo's application layers expect STRING_LEN if everything fits
+ if( nBreakPos >= static_cast<UniCharArrayOffset>(mnEndCharPos) )
+ return STRING_LEN;
+
+ // GetTextBreak()'s callers expect it to return the "stupid visual line break".
+ // Returning anything else result.s in subtle problems in the application layers.
+ static const bool bInWord = true; // TODO: add as argument to GetTextBreak() method
+ if( !bInWord )
+ return nBreakPos;
+
+ // emulate stupid visual line breaking by line breaking for the remaining width
+ ATSUTextMeasurement nLeft, nRight, nDummy;
+ eStatus = ATSUGetUnjustifiedBounds( maATSULayout, mnMinCharPos, nBreakPos-mnMinCharPos,
+ &nLeft, &nRight, &nDummy, &nDummy );
+ if( eStatus != noErr )
+ return nBreakPos;
+ const ATSUTextMeasurement nATSURemWidth = nATSUMaxWidth - (nRight - nLeft);
+ if( nATSURemWidth <= 0xFFFF ) // #i108584# avoid ATSU rejecting the parameter
+ return nBreakPos;
+ UniCharArrayOffset nBreakPosInWord = nBreakPos;
+ eStatus = ATSUBreakLine( maATSULayout, nBreakPos, nATSURemWidth, false, &nBreakPosInWord );
+ return nBreakPosInWord;
+}
+
+// -----------------------------------------------------------------------
+/**
+ * ATSLayout::GetCaretPositions : Find positions of carets
+ *
+ * @param nMaxIndex position to which we want to find the carets
+ *
+ * Fill the array of positions of carets (for cursors and selections)
+ *
+ * @return : none
+**/
+void ATSLayout::GetCaretPositions( int nMaxIndex, long* pCaretXArray ) const
+{
+ DBG_ASSERT( ((nMaxIndex>0)&&!(nMaxIndex&1)),
+ "ATSLayout::GetCaretPositions() : invalid number of caret pairs requested");
+
+ // initialize the caret positions
+ for( int i = 0; i < nMaxIndex; ++i )
+ pCaretXArray[ i ] = -1;
+
+ for( int n = 0; n <= mnCharCount; ++n )
+ {
+ // measure the characters cursor position
+ typedef unsigned char Boolean;
+ const Boolean bIsLeading = true;
+ ATSUCaret aCaret0, aCaret1;
+ Boolean bIsSplit;
+ OSStatus eStatus = ATSUOffsetToCursorPosition( maATSULayout,
+ mnMinCharPos + n, bIsLeading, kATSUByCharacter,
+ &aCaret0, &aCaret1, &bIsSplit );
+ if( eStatus != noErr )
+ continue;
+ const Fixed nFixedPos = mnBaseAdv + aCaret0.fX;
+ // convert the measurement to pixel units
+ const int nPixelPos = Fixed2Vcl( nFixedPos );
+ // update previous trailing position
+ if( n > 0 )
+ pCaretXArray[2*n-1] = nPixelPos;
+ // update current leading position
+ if( 2*n >= nMaxIndex )
+ break;
+ pCaretXArray[2*n+0] = nPixelPos;
+ }
+}
+
+// -----------------------------------------------------------------------
+/**
+ * ATSLayout::GetBoundRect : Get rectangle dim containing the layouted text
+ *
+ * @param rVCLRect: rectangle of text image (layout) measures
+ *
+ * Get ink bounds of the text
+ *
+ * @return : measurement valid
+**/
+bool ATSLayout::GetBoundRect( SalGraphics&, Rectangle& rVCLRect ) const
+{
+ const Point aPos = GetDrawPosition( Point(mnBaseAdv, 0) );
+ const Fixed nFixedX = Vcl2Fixed( +aPos.X() );
+ const Fixed nFixedY = Vcl2Fixed( +aPos.Y() );
+
+ Rect aMacRect;
+ OSStatus eStatus = ATSUMeasureTextImage( maATSULayout,
+ mnMinCharPos, mnCharCount, nFixedX, nFixedY, &aMacRect );
+ if( eStatus != noErr )
+ return false;
+
+ // ATSU top-bottom are vertically flipped from a VCL aspect
+ rVCLRect.Left() = AtsuPix2Vcl( aMacRect.left );
+ rVCLRect.Top() = AtsuPix2Vcl( aMacRect.top );
+ rVCLRect.Right() = AtsuPix2Vcl( aMacRect.right );
+ rVCLRect.Bottom() = AtsuPix2Vcl( aMacRect.bottom );
+ return true;
+}
+
+// -----------------------------------------------------------------------
+/**
+ * ATSLayout::InitGIA() : get many informations about layouted text
+ *
+ * Fills arrays of information about the gylph layout previously done
+ * in ASTLayout::LayoutText() : glyph advance (width), glyph delta Y (from baseline),
+ * mapping between glyph index and character index, chars widths
+ *
+ * @return : true if everything could be computed, otherwise false
+**/
+bool ATSLayout::InitGIA( ImplLayoutArgs* pArgs ) const
+{
+ // no need to run InitGIA more than once on the same ATSLayout object
+ if( mnGlyphCount >= 0 )
+ return true;
+ mnGlyphCount = 0;
+
+ // Workaround a bug in ATSUI with empty string
+ if( mnCharCount <= 0 )
+ return false;
+
+ // initialize character details
+ mpCharWidths = new Fixed[ mnCharCount ];
+ mpChars2Glyphs = new int[ mnCharCount ];
+ for( int n = 0; n < mnCharCount; ++n )
+ {
+ mpCharWidths[ n ] = 0;
+ mpChars2Glyphs[ n ] = -1;
+ }
+
+ // get details about the glyph layout
+ ItemCount iLayoutDataCount;
+ const ATSLayoutRecord* pALR;
+ OSStatus eStatus = ATSUDirectGetLayoutDataArrayPtrFromTextLayout(
+ maATSULayout, mnMinCharPos, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent,
+ (void**)&pALR, &iLayoutDataCount );
+ DBG_ASSERT( (eStatus==noErr), "ATSLayout::InitGIA() : no ATSLayoutRecords!\n");
+ if( (eStatus != noErr)
+ || (iLayoutDataCount <= 1) )
+ return false;
+
+ // initialize glyph details
+ mpGlyphIds = new ATSGlyphRef[ iLayoutDataCount ];
+ mpGlyphAdvances = new Fixed[ iLayoutDataCount ];
+ mpGlyphs2Chars = new int[ iLayoutDataCount ];
+
+ // measure details of the glyph layout
+ Fixed nLeftPos = 0;
+ for( ItemCount i = 0; i < iLayoutDataCount; ++i )
+ {
+ const ATSLayoutRecord& rALR = pALR[i];
+
+ // distribute the widths as fairly as possible among the chars
+ const int nRelativeIdx = (rALR.originalOffset / 2);
+ if( i+1 < iLayoutDataCount )
+ mpCharWidths[ nRelativeIdx ] += pALR[i+1].realPos - rALR.realPos;
+
+ // new glyph is available => finish measurement of old glyph
+ if( mnGlyphCount > 0 )
+ mpGlyphAdvances[ mnGlyphCount-1 ] = rALR.realPos - nLeftPos;
+
+ // ignore marker or deleted glyphs
+ enum { MARKED_OUTGLYPH=0xFFFE, DROPPED_OUTGLYPH=0xFFFF};
+ if( rALR.glyphID >= MARKED_OUTGLYPH )
+ continue;
+
+ DBG_ASSERT( !(rALR.flags & kATSGlyphInfoTerminatorGlyph),
+ "ATSLayout::InitGIA(): terminator glyph not marked as deleted!" );
+
+ // store details of the visible glyphs
+ nLeftPos = rALR.realPos;
+ mpGlyphIds[ mnGlyphCount ] = rALR.glyphID;
+
+ // map visible glyphs to their counterparts in the UTF16-character array
+ mpGlyphs2Chars[ mnGlyphCount ] = nRelativeIdx + mnMinCharPos;
+ mpChars2Glyphs[ nRelativeIdx ] = mnGlyphCount;
+
+ ++mnGlyphCount;
+ }
+
+ // measure complete width
+ mnCachedWidth = mnBaseAdv;
+ mnCachedWidth += pALR[iLayoutDataCount-1].realPos - pALR[0].realPos;
+
+#if (OSL_DEBUG_LEVEL > 1)
+ Fixed nWidthSum = mnBaseAdv;
+ for( int n = 0; n < mnCharCount; ++n )
+ nWidthSum += mpCharWidths[ n ];
+ DBG_ASSERT( (nWidthSum==mnCachedWidth),
+ "ATSLayout::InitGIA(): measured widths do not match!\n" );
+#endif
+
+ // #i91183# we need to split up the portion into sub-portions
+ // if the ATSU-layout differs too much from the requested layout
+ if( pArgs && pArgs->mpDXArray )
+ {
+ // TODO: non-strong-LTR case cases should be handled too
+ if( (pArgs->mnFlags & TEXT_LAYOUT_BIDI_STRONG)
+ && !(pArgs->mnFlags & TEXT_LAYOUT_BIDI_RTL) )
+ {
+ Fixed nSumCharWidths = 0;
+ SubPortion aSubPortion = { mnMinCharPos, 0, 0 };
+ for( int i = 0; i < mnCharCount; ++i )
+ {
+ // calculate related logical position
+ nSumCharWidths += mpCharWidths[i];
+
+ // start new sub-portion if needed
+ const Fixed nNextXPos = Vcl2Fixed(pArgs->mpDXArray[i]);
+ const Fixed nNextXOffset = nNextXPos - nSumCharWidths;
+ const Fixed nFixedDiff = aSubPortion.mnXOffset - nNextXOffset;
+ if( (nFixedDiff < -0xC000) || (nFixedDiff > +0xC000) ) {
+ // get to the end of the current sub-portion
+ // prevent splitting up at diacritics etc.
+ int j = i;
+ while( (++j < mnCharCount) && !mpCharWidths[j] );
+ aSubPortion.mnEndCharPos = mnMinCharPos + j;
+ // emit current sub-portion
+ maSubPortions.push_back( aSubPortion );
+ // prepare next sub-portion
+ aSubPortion.mnMinCharPos = aSubPortion.mnEndCharPos;
+ aSubPortion.mnXOffset = nNextXOffset;
+ }
+ }
+
+ // emit the remaining sub-portion
+ if( !maSubPortions.empty() )
+ {
+ aSubPortion.mnEndCharPos = mnEndCharPos;
+ if( aSubPortion.mnEndCharPos != aSubPortion.mnMinCharPos )
+ maSubPortions.push_back( aSubPortion );
+ }
+ }
+
+ // override layouted charwidths with requested charwidths
+ for( int n = 0; n < mnCharCount; ++n )
+ mpCharWidths[ n ] = pArgs->mpDXArray[ n ];
+ }
+
+ // release the ATSU layout records
+ ATSUDirectReleaseLayoutDataArrayPtr(NULL,
+ kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, (void**)&pALR );
+
+ return true;
+}
+
+// -----------------------------------------------------------------------
+
+bool ATSLayout::GetIdealX() const
+{
+ // compute the ideal advance widths only once
+ if( mpGlyphOrigAdvs != NULL )
+ return true;
+
+ DBG_ASSERT( (mpGlyphIds!=NULL), "GetIdealX() called with mpGlyphIds==NULL !" );
+ DBG_ASSERT( (mrATSUStyle!=NULL), "GetIdealX called with mrATSUStyle==NULL !" );
+
+ // TODO: cache ideal metrics per glyph?
+ std::vector<ATSGlyphIdealMetrics> aIdealMetrics;
+ aIdealMetrics.resize( mnGlyphCount );
+ OSStatus theErr = ATSUGlyphGetIdealMetrics( mrATSUStyle,
+ mnGlyphCount, &mpGlyphIds[0], sizeof(*mpGlyphIds), &aIdealMetrics[0] );
+ DBG_ASSERT( (theErr==noErr), "ATSUGlyphGetIdealMetrics failed!");
+ if( theErr != noErr )
+ return false;
+
+ mpGlyphOrigAdvs = new Fixed[ mnGlyphCount ];
+ for( int i = 0;i < mnGlyphCount;++i )
+ mpGlyphOrigAdvs[i] = FloatToFixed( aIdealMetrics[i].advance.x );
+
+ return true;
+}
+
+// -----------------------------------------------------------------------
+
+bool ATSLayout::GetDeltaY() const
+{
+ // don't bother to get the same delta-y-array more than once
+ if( mpDeltaY != NULL )
+ return true;
+
+#if 1
+ if( !maATSULayout )
+ return false;
+
+ // get and keep the y-deltas in the mpDeltaY member variable
+ // => release it in the destructor
+ ItemCount nDeltaCount = 0;
+ OSStatus theErr = ATSUDirectGetLayoutDataArrayPtrFromTextLayout(
+ maATSULayout, mnMinCharPos, kATSUDirectDataBaselineDeltaFixedArray,
+ (void**)&mpDeltaY, &nDeltaCount );
+
+ DBG_ASSERT( (theErr==noErr ), "mpDeltaY - ATSUDirectGetLayoutDataArrayPtrFromTextLayout failed!\n");
+ if( theErr != noErr )
+ return false;
+
+ if( mpDeltaY == NULL )
+ return true;
+
+ if( nDeltaCount != (ItemCount)mnGlyphCount )
+ {
+ DBG_WARNING( "ATSLayout::GetDeltaY() : wrong deltaY count!" );
+ ATSUDirectReleaseLayoutDataArrayPtr( NULL,
+ kATSUDirectDataBaselineDeltaFixedArray, (void**)&mpDeltaY );
+ mpDeltaY = NULL;
+ return false;
+ }
+#endif
+
+ return true;
+}
+
+// -----------------------------------------------------------------------
+
+#define DELETEAZ( X ) { delete[] X; X = NULL; }
+
+void ATSLayout::InvalidateMeasurements()
+{
+ mnGlyphCount = -1;
+ DELETEAZ( mpGlyphIds );
+ DELETEAZ( mpCharWidths );
+ DELETEAZ( mpChars2Glyphs );
+ DELETEAZ( mpGlyphs2Chars );
+ DELETEAZ( mpGlyphRTLFlags );
+ DELETEAZ( mpGlyphAdvances );
+ DELETEAZ( mpGlyphOrigAdvs );
+ DELETEAZ( mpDeltaY );
+}
+
+// =======================================================================
+
+// glyph fallback is supported directly by Aqua
+// so methods used only by MultiSalLayout can be dummy implementated
+bool ATSLayout::GetGlyphOutlines( SalGraphics&, PolyPolyVector& rPPV ) const { return false; }
+void ATSLayout::InitFont() {}
+void ATSLayout::MoveGlyph( int /*nStart*/, long /*nNewXPos*/ ) {}
+void ATSLayout::DropGlyph( int /*nStart*/ ) {}
+void ATSLayout::Simplify( bool /*bIsBase*/ ) {}
+
+// get the ImplFontData for a glyph fallback font
+// for a glyphid that was returned by ATSLayout::GetNextGlyphs()
+const ImplFontData* ATSLayout::GetFallbackFontData( sal_GlyphId nGlyphId ) const
+{
+ // check if any fallback fonts were needed
+ if( !mpFallbackInfo )
+ return NULL;
+ // check if the current glyph needs a fallback font
+ int nFallbackLevel = (nGlyphId & GF_FONTMASK) >> GF_FONTSHIFT;
+ if( !nFallbackLevel )
+ return NULL;
+ return mpFallbackInfo->GetFallbackFontData( nFallbackLevel );
+}
+
+// =======================================================================
+
+int FallbackInfo::AddFallback( ATSUFontID nFontId )
+{
+ // check if the fallback font is already known
+ for( int nLevel = 0; nLevel < mnMaxLevel; ++nLevel )
+ if( maATSUFontId[ nLevel ] == nFontId )
+ return (nLevel + 1);
+
+ // append new fallback font if possible
+ if( mnMaxLevel >= MAX_FALLBACK-1 )
+ return 0;
+ // keep ATSU font id of fallback font
+ maATSUFontId[ mnMaxLevel ] = nFontId;
+ // find and cache the corresponding ImplFontData pointer
+ const SystemFontList* pSFL = GetSalData()->mpFontList;
+ const ImplMacFontData* pFontData = pSFL->GetFontDataFromId( nFontId );
+ maFontData[ mnMaxLevel ] = pFontData;
+ // increase fallback level by one
+ return (++mnMaxLevel);
+}
+
+// -----------------------------------------------------------------------
+
+const ImplFontData* FallbackInfo::GetFallbackFontData( int nFallbackLevel ) const
+{
+ const ImplMacFontData* pFallbackFont = maFontData[ nFallbackLevel-1 ];
+ return pFallbackFont;
+}
+
+// =======================================================================
+
+SalLayout* AquaSalGraphics::GetTextLayout( ImplLayoutArgs& rArgs, int nFallbackLevel )
+{
+ ATSLayout* pATSLayout = new ATSLayout( maATSUStyle, mfFontScale );
+ return pATSLayout;
+}
+
+// =======================================================================
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */