MoreATSUnicodeTextBox.cp

/*
    File:       MoreATSUnicodeTextBox.cp
 
    Contains:   ATSUI version of TETextBox.
 
    Version:    1.0.1
 
    Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
                ("Apple") in consideration of your agreement to the following terms, and your
                use, installation, modification or redistribution of this Apple software
                constitutes acceptance of these terms.  If you do not agree with these terms,
                please do not use, install, modify or redistribute this Apple software.
 
                In consideration of your agreement to abide by the following terms, and subject
                to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
                copyrights in this original Apple software (the "Apple Software"), to use,
                reproduce, modify and redistribute the Apple Software, with or without
                modifications, in source and/or binary forms; provided that if you redistribute
                the Apple Software in its entirety and without modifications, you must retain
                this notice and the following text and disclaimers in all such redistributions of
                the Apple Software.  Neither the name, trademarks, service marks or logos of
                Apple Computer, Inc. may be used to endorse or promote products derived from the
                Apple Software without specific prior written permission from Apple.  Except as
                expressly stated in this notice, no other rights or licenses, express or implied,
                are granted by Apple herein, including but not limited to any patent rights that
                may be infringed by your derivative works or by other works in which the Apple
                Software may be incorporated.
 
                The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
                WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
                WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
                PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
                COMBINATION WITH YOUR PRODUCTS.
 
                IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
                CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
                GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
                ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
                OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
                (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
                ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
    Copyright © 2002 Apple Computer, Inc., All Rights Reserved
*/
 
#include <Carbon/Carbon.h>
#include "HITextShowcase.h"
 
#define ff(a) ((Fixed)(a) << 16)
 
#define atsuSetLayoutRotation( iLayout, iAngle )    atsuSetFixedLayoutControl( iLayout, iAngle, kATSULineRotationTag )
#define atsuGetLayoutRotation( iLayout, oAnglePtr ) atsuGetOneLayoutControl( iLayout, kATSULineRotationTag, sizeof(Fixed), oAnglePtr )
#define atsuSetLayoutWidth( iLayout, iWidth )   atsuSetFixedLayoutControl( iLayout, iWidth, kATSULineWidthTag )
#define atsuGetLayoutWidth( iLayout, oWidthPtr )    atsuGetOneLayoutControl( iLayout, kATSULineWidthTag, sizeof(ATSUTextMeasurement), oWidthPtr )
#define atsuSetHangingInhibitFactor( iStyle, iValue )       atsuSetFractAttribute( iStyle, iValue, kATSUHangingInhibitFactorTag )
#define atsuSetLayoutFlushFactor( iLayout, iValue )     atsuSetFractLayoutControl( iLayout, iValue, kATSULineFlushFactorTag )
#define atsuSetLayoutJustFactor( iLayout, iValue )      atsuSetFractLayoutControl( iLayout, iValue, kATSULineJustificationFactorTag )
#define atsuSetVerticalCharacter( iStyle, iVerticalChar )   atsuSetShortAttribute( iStyle, iVerticalChar, kATSUVerticalCharacterTag )
#define atsuSetFont( iStyle, iFontID )      atsuSetIntAttribute( iStyle, iFontID, kATSUFontTag )
#define atsuGetFont( iStyle, oFontIDPtr )   atsuGetOneAttribute( iStyle, kATSUFontTag, sizeof(ATSUFontID), oFontIDPtr );
 
OSStatus
atsuSetLayoutOptions( ATSUTextLayout iLayout, UInt32 iOptions )
{
    ATSUAttributeTag        theTag = kATSULineLayoutOptionsTag;
    ByteCount               theSize = sizeof(iOptions);
    ATSUAttributeValuePtr   thePtr = &iOptions;
 
    return ATSUSetLayoutControls( iLayout, 1, &theTag, &theSize, &thePtr );
}
 
OSStatus
atsuSetIntAttribute( ATSUStyle iStyle, SInt32 iValue, ATSUAttributeTag iTag )
{
    ByteCount               theSize = sizeof(iValue);
    ATSUAttributeValuePtr   thePtr = &iValue;
 
    return ATSUSetAttributes( iStyle, 1, &iTag, &theSize, &thePtr );
}
 
OSStatus
atsuGetOneAttribute( ATSUStyle              iStyle,
                     ATSUAttributeTag       iTag,
                     ByteCount              iExpectedValueSize,
                     ATSUAttributeValuePtr  oValue )
{
    // Filters out kATSUNotSetErr
    OSStatus status = ATSUGetAttribute( iStyle, iTag, iExpectedValueSize, oValue, NULL );
    return (status == kATSUNotSetErr) ? noErr : status;
}
 
OSStatus
atsuSetShortAttribute( ATSUStyle iStyle, SInt16 iValue, ATSUAttributeTag iTag )
{
    ByteCount               theSize = sizeof(iValue);
    ATSUAttributeValuePtr   thePtr = &iValue;
 
    return ATSUSetAttributes( iStyle, 1, &iTag, &theSize, &thePtr );
}
 
OSStatus
atsuSetFractLayoutControl( ATSUTextLayout iLayout, Fract iValue, ATSUAttributeTag iTag )
{
    ByteCount               theSize = sizeof(iValue);
    ATSUAttributeValuePtr   thePtr = &iValue;
 
    return ATSUSetLayoutControls( iLayout, 1, &iTag, &theSize, &thePtr );
}
 
OSStatus
atsuSetFixedLayoutControl( ATSUTextLayout iLayout, Fixed iValue, ATSUAttributeTag iTag )
{
    ByteCount               theSize = sizeof(iValue);
    ATSUAttributeValuePtr   thePtr = &iValue;
 
    return ATSUSetLayoutControls( iLayout, 1, &iTag, &theSize, &thePtr );
}
 
OSStatus
atsuSetFractAttribute( ATSUStyle iStyle, Fract iValue, ATSUAttributeTag iTag )
{
    ByteCount               theSize = sizeof(iValue);
    ATSUAttributeValuePtr   thePtr = &iValue;
 
    return ATSUSetAttributes( iStyle, 1, &iTag, &theSize, &thePtr );
}
 
OSStatus
atsuGetOneLayoutControl( ATSUTextLayout         iLayout,
                         ATSUAttributeTag       iTag,
                         ByteCount              iExpectedValueSize,
                         ATSUAttributeValuePtr  oValue )
{
    // Filters out kATSUNotSetErr
    OSStatus status = ATSUGetLayoutControl( iLayout, iTag, iExpectedValueSize, oValue, NULL );
    return (status == kATSUNotSetErr) ? noErr : status;
}
 
//  converts a TextEdit based alignment to 
//  an ATSUI flush factor
static Fract atsuFlushFactorFromTEAlignment(short alignment)
{
    Fract flushFactor = kATSUStartAlignment;
    
    switch (alignment)
    {
        case teFlushRight:
            flushFactor = kATSUEndAlignment;
            break;
            
        case teCenter:
            flushFactor = kATSUCenterAlignment;
            break;
 
        case teFlushLeft:
            flushFactor = kATSUStartAlignment;
            break;
        default:
            // teFlushDefault
            flushFactor = (GetSysDirection() == 0) ? kATSUStartAlignment : kATSUEndAlignment;
            break;
    }
    return flushFactor;
}
 
OSStatus
atsuSetStyleFromGrafPtr( ATSUStyle iStyle, GrafPtr iGrafPtr, Boolean clearExistingAttributes )
{
    OSStatus    status = noErr;
    GrafPtr     savedPort;
    
    ATSUAttributeTag        theTags[] = {   kATSUFontTag,
                                            kATSUSizeTag,
                                            kATSUQDBoldfaceTag,
                                            kATSUQDItalicTag,
                                            kATSUQDUnderlineTag,
                                            kATSUQDCondensedTag,
                                            kATSUQDExtendedTag,
                                            kATSUColorTag };
    ByteCount               theSizes[] = {  sizeof(ATSUFontID),
                                            sizeof(Fixed),
                                            sizeof(Boolean),
                                            sizeof(Boolean),
                                            sizeof(Boolean),
                                            sizeof(Boolean),
                                            sizeof(Boolean),
                                            sizeof(RGBColor) };
    ATSUAttributeValuePtr   theValues[ sizeof(theTags) / sizeof(ATSUAttributeTag) ];
    
    ATSUFontID          atsuFont;
    Fixed               atsuSize;
    RGBColor            textColor;
    Boolean             isBold, isItalic, isUnderline, isCondensed, isExtended;
    short               txFont, txSize;
    SInt16              txFace, intrinsicStyle;
    
    
    GetPort( &savedPort );
    
    if ( iGrafPtr == NULL ) 
        iGrafPtr = savedPort;
    else
        SetPort( iGrafPtr );
 
#if !TARGET_API_MAC_CARBON
    txFont = iGrafPtr->txFont;
    txSize = iGrafPtr->txSize;
    txFace = iGrafPtr->txFace;
#else
    txFont = GetPortTextFont(iGrafPtr);
    txSize = GetPortTextSize(iGrafPtr);
    txFace = GetPortTextFace(iGrafPtr);
#endif
    
    status = FMGetFontFromFontFamilyInstance( txFont, txFace, &atsuFont, &intrinsicStyle );
    require( status == noErr, EXIT );
    
    // Need to adjust the QD style bits based on the intrinsic style of the font.
    // Otherwise, you can end up doing things like artifically bolding an already-bold font.
    txFace &= ~intrinsicStyle;
    
    isBold = ( txFace & bold ) != 0;
    isItalic = ( txFace & italic ) != 0;
    isUnderline = ( txFace & underline ) != 0;
    isCondensed = ( txFace & condense ) != 0;
    isExtended = ( txFace & extend ) != 0;
    
    if ( txSize == 0 )
        txSize = (short) ( GetScriptVariable( FontToScript( txFont ), smScriptPrefFondSize ) & 0xFFFFU );   // this would already be set correctly in a brand-new style
    atsuSize = Long2Fix( txSize );
    
    GetForeColor( &textColor );
    
    // At this point, the only thing that could fail is the set, so it's OK to clear the old settings.
    if ( clearExistingAttributes ) 
        status = ATSUClearStyle( iStyle );
    require( status == noErr, EXIT );
    
    // C doesn't allow this to be done in an initializer, so we have to fill in the pointers here.
    theValues[0] = &atsuFont;
    theValues[1] = &atsuSize;
    theValues[2] = &isBold;
    theValues[3] = &isItalic;
    theValues[4] = &isUnderline;
    theValues[5] = &isCondensed;
    theValues[6] = &isExtended;
    theValues[7] = &textColor;
    
    status = ATSUSetAttributes( iStyle, sizeof(theTags) / sizeof(ATSUAttributeTag), theTags, theSizes, theValues );
    
EXIT:   
    SetPort( savedPort );
 
    return status;
}
 
enum {
        kVerticalTextAngle = ff(-90)
    };
 
/*
    Draw the text layout within the specified rect.
*/
OSStatus atsuDrawTextInBox( ATSUTextLayout      iTextLayout,
                            const Rect *            iBox)
{
    OSStatus            theStatus = noErr;
    UniCharArrayOffset  textOffset = 0;
    UniCharCount        textLength = 0;
    UniCharArrayOffset* lineEndOffsets = NULL;
    
    require (iBox != NULL, EXIT);
    
    // the the range of text to be drawn
    theStatus = ATSUGetTextLocation (iTextLayout, NULL, NULL, &textOffset, &textLength, NULL);
    if (theStatus == noErr) {
        UniCharArrayOffset      lineStartOffset = textOffset;
        UniCharArrayOffset      lineEndOffset = 0;
        
        //  assume horizontal text values
        ATSUTextMeasurement     xPos = ff(iBox->left);
        ATSUTextMeasurement     yPos = ff(iBox->top);
        ATSUTextMeasurement     lineStart = xPos;
        ATSUTextMeasurement     lineEnd = ff(iBox->right);
        ATSUTextMeasurement     lineWidth = 0;
        
        Fixed                   textAngle = 0;
        ItemCount               lineCount = 0;
        ItemCount               softBreakCount = 0;
        ATSUTextMeasurement     maxAscent = 0, maxDescent = 0;
        
        unsigned int ln = 0;
 
        //  need to get the text rotation for drawing
        theStatus = atsuGetLayoutRotation(iTextLayout, &textAngle);
        require(theStatus == noErr, EXIT);
        
        // is the text not horizontal?
        if (textAngle != 0) {
            // if it is not horizontal, we only know how to draw vertical
            require (textAngle == kVerticalTextAngle, EXIT);
            xPos =  ff(iBox->right);
            lineStart = yPos;
            lineEnd = ff(iBox->bottom);
        }
 
        // check for linewidth set as a layout control
        theStatus = atsuGetLayoutWidth(iTextLayout, &lineWidth);
        require(theStatus == noErr, EXIT);
        
        //  if there is no layout control set for width
        //  then set it using the box bounds
        if (lineWidth == 0)
            lineWidth = lineEnd - lineStart;
        
        //  Break and measure each line to determine the max ascent and descent;
        //  This is needed because things that end up on different lines
        //  could interact to affect the line height when they're on the same line.
        //  To get the best value we break the lines first, then measure each
        //  line and find the max ascent and descent.
        while (lineStartOffset < textOffset + textLength) {
            ATSUTextMeasurement     ascent = 0, descent = 0;
            
            // set the soft breaks, we will use them later
            theStatus = ATSUBreakLine(iTextLayout, lineStartOffset, lineWidth, true, &lineEndOffset);
            require(theStatus == noErr, EXIT);
            
            // @@@ If you want leave lines that are terminated with a hard break unjustified, this is a
            // good place to set the individual line control to do that.
 
#if (ATSU_TARG_VERSION >= ATSU_1_1)
            // ATSUGetGlyphBounds is better than ATSUMeasureText if you've got any interesting feature
            // such as justification turned on. The former will do the layout exactly as it will be drawn and cache
            // the result, so things will be faster overall. ATSUMeasureText will turn off justification and end up doing
            // an "extra" layout operation.
            {
                ATSTrapezoid    glyphBounds;    // one should be enough when we're asking for the whole line.
                
                theStatus = ATSUGetGlyphBounds( iTextLayout, 0, 0, lineStartOffset, lineEndOffset - lineStartOffset, kATSUseFractionalOrigins,
                                                1, &glyphBounds, NULL );
                require(theStatus == noErr, EXIT);
                
                // The top and bottom of the bounds should be parallel to the baseline. You might want to check that.
                if (textAngle == 0) {
                    ascent = -glyphBounds.upperLeft.y;
                    descent = glyphBounds.lowerLeft.y;
                } else {
                    // This looks strange, but the box you get is rotated, so "upper left" relative to the line is "upper right" in absolute terms.
                    ascent = glyphBounds.upperLeft.x;
                    descent = -glyphBounds.lowerLeft.x;
                }
            }
#else
            theStatus = ATSUMeasureText(iTextLayout, lineStartOffset, lineEndOffset - lineStartOffset, NULL, NULL, &ascent, &descent);
            require(theStatus == noErr, EXIT);
#endif
 
            if (ascent > maxAscent)
                maxAscent = ascent;
            if (descent > maxDescent)
                maxDescent = descent;
                
            lineStartOffset = lineEndOffset;
            lineCount++;
        }
        
        lineEndOffsets = (UniCharArrayOffset*) NewPtr(lineCount * sizeof(UniCharArrayOffset));
        
        theStatus = MemError();
        require(theStatus == noErr, EXIT);
        
        theStatus = ATSUGetSoftLineBreaks(
            iTextLayout, textOffset, textLength, lineCount, lineEndOffsets, &softBreakCount);
        //  assert that the number of soft breaks is always one less than the number of lines
        //  since ATSUBreakLine does not insert a softbreak at the end of the text.
        require((theStatus == noErr && softBreakCount == lineCount - 1), EXIT);
 
        lineEndOffsets[softBreakCount] = textOffset + textLength;
        lineStartOffset = textOffset;
 
        // @@@ If maxAscent or maxDescent are not integers, this code may produce uneven line spacing. It will also
        // not necessarily match the line height used by ATSUI when highlighting. ATSUI highlighting uses
        // FixedToInt( baseline - ascent ) for the top of the box and FixedToInt( baseline + descent ) for the bottom.
        // Any other combination of reflection and rounding can result in one-pixel gaps and overlaps.
        
        // If you're using ATSUGetGlyphBounds above, you could ask for kATSUseDeviceOrigins to have ATSUI do this transformation for you.
        
        //  draw each line
        for (ln = 0; ln < lineCount; ln++) {
            lineEndOffset = lineEndOffsets[ln];
            
            // predecrement to account for the line height
            if (textAngle == kVerticalTextAngle)
                xPos -= maxAscent;
            else
                yPos += maxAscent;
 
            theStatus = ATSUDrawText(iTextLayout, lineStartOffset, lineEndOffset - lineStartOffset, xPos,  yPos);
            require(theStatus == noErr, EXIT);
 
            lineStartOffset = lineEndOffset;
            
            if (textAngle == kVerticalTextAngle)
                xPos -= maxDescent;
            else
                yPos += maxDescent;
        }
    }
    
EXIT:
    if (lineEndOffsets != NULL) 
        DisposePtr((char*)lineEndOffsets);  
    return theStatus;
}
 
 
/*
    atsuTextBox expands the functionality of TETextBox by adding
    justification and vertical text.
    
    atsuTextBox first sets up a ATSUTextLayout. It then draws the text
    using atsuDrawTextInBox
    
*/
 
enum {
    kInitialStyleAllocation = 10
    };
 
OSStatus atsuTextBox(   ConstUniCharArrayPtr        iText,
                        UniCharCount                iTextLength,
                        const Rect *                iBox,
                        short                       alignment,
                        Fract                       justification,
                        short                       textOrientation,
                        Boolean                     useFontFallBacks)
{
    OSStatus        theStatus = 0;
    ATSUStyle       globalStyle = NULL;
    ATSUTextLayout  txLayout = NULL;
    
    int             substituteStyleCount = 0;
    int             maxStyleCount = kInitialStyleAllocation;
    ATSUStyle**     substituteStyles = NULL;
    
    theStatus = ATSUCreateStyle(&globalStyle);
    if (theStatus == noErr) 
    {
        //  Initialize the style with the current port attributes
        theStatus = atsuSetStyleFromGrafPtr(globalStyle, NULL, false);
        require(theStatus == noErr, EXIT);
 
        //  Don't want to have hanging punctuation drawn outside of the rectangle,
        //  so inhibit that feature. (It's allowed by default)
        theStatus = atsuSetHangingInhibitFactor(globalStyle, ((Fract) 0x00000001L));
        
        if (theStatus == noErr) 
        {
        
            theStatus = ATSUCreateTextLayoutWithTextPtr( iText, 0, iTextLength, iTextLength,
                                                    1, &iTextLength, &globalStyle,
                                                    &txLayout);
                                                    
            if (theStatus == noErr) //  Set layout controls and draw
            {
                UniCharArrayOffset  replaceOffset = 0;
                UniCharCount        replaceLength = iTextLength;
                
                //  Could have had atsuTextBox use the ATSUI flushFactors directly
                //  This is just to illustrate how to convert from the
                //  TE equivalents
                Fract   flushFactor = atsuFlushFactorFromTEAlignment(alignment);
 
/* kATSLineIsDisplayOnly has been deprecated
 *
                //  a slight optimization since we know that this
                //  text is display only
                theStatus = atsuSetLayoutOptions(txLayout, kATSLineIsDisplayOnly);
                require(theStatus == noErr, EXIT);
*/
 
                //  Set the flush and justification if they are other than the 
                //  default
                if (flushFactor != kATSUStartAlignment || justification != kATSUNoJustification)
                {
                    Fixed   lineWidth = ff(iBox->right-iBox->left);
                    
                    if (textOrientation == katsuVerticalText)
                        lineWidth = ff(iBox->bottom-iBox->top);
                                    
                    //  line width must be set for flush and justification to work
                    theStatus = atsuSetLayoutWidth(txLayout, lineWidth);                
                    require(theStatus == noErr, EXIT);
 
                    theStatus = atsuSetLayoutFlushFactor(txLayout, flushFactor);        
                    require(theStatus == noErr, EXIT);
                            
                    theStatus = atsuSetLayoutJustFactor(txLayout, justification);
                    require(theStatus == noErr, EXIT);
                    
                    
                    // don't justify the last line
                    theStatus = atsuSetLayoutOptions(txLayout, kATSLineLastNoJustification);
                    require(theStatus == noErr, EXIT);
                }
                
                if (textOrientation == katsuVerticalText)
                {
                    // set the character orientation
                    theStatus = atsuSetVerticalCharacter(globalStyle, kATSUStronglyVertical);
                    require(theStatus == noErr, EXIT);
                    
                    //  rotate the text
                    theStatus = atsuSetLayoutRotation(txLayout, kVerticalTextAngle);
                    require(theStatus == noErr, EXIT);
                }
 
                //  If the caller specifies useFontFallBacks
                //  Check to see if there are any characters that can't be drawn
                //  with the font that came in from the port.
                //  Use ATSUMatchFontsToText to find problem subranges
                //  Explicitly replacing the font will be less than the overhead
                //  of setting transient font matching for the entire layout.
                if (useFontFallBacks)
                {
                    Boolean needTransientMatching = false;
                    Boolean doneWithSubstitution = false;
                    
                    while (!doneWithSubstitution)
                    {
                        ATSUFontID  replaceFont;
                        
                        theStatus = ATSUMatchFontsToText(
                                        txLayout, replaceOffset, replaceLength,
                                        &replaceFont, &replaceOffset, &replaceLength);
                                        
                        switch (theStatus)
                        {
                            case kATSUFontsMatched:
                            {
                                int         i = 0;
                                ATSUStyle   replacementStyle = NULL;
                                
                                //  "kATSUFontsMatched" means that some of the characters cannot
                                //  be drawn with the font, but a suitable subsitute font
                                //  is active in the system
                                
                                //  before we create a new style, see if we already have a style with this font
                                for (i = 0; i < substituteStyleCount && !replacementStyle; i++)
                                {
                                    ATSUFontID  thisFont;
                                    atsuGetFont((*substituteStyles)[i], &thisFont);
                                    if (thisFont == replaceFont)
                                    {
                                        replacementStyle = (*substituteStyles)[i];
                                    }
                                }
                                
                                if (!replacementStyle)
                                {
                                    //  need to create a new one, cloning the global style
                                    theStatus = ATSUCreateAndCopyStyle(globalStyle, &replacementStyle);
                                    require(theStatus == noErr, EXIT);
                                    
                                    theStatus = atsuSetFont(replacementStyle, replaceFont);
                                    require(theStatus == noErr, EXIT);
                                    
                                    //  add it to the list
                                    if (substituteStyles == NULL)
                                    {
                                        maxStyleCount = kInitialStyleAllocation;
                                        substituteStyles = (ATSUStyle**) NewHandle(maxStyleCount * sizeof(ATSUStyle));
                                        HLock((Handle) substituteStyles);
                                        require ((substituteStyles != NULL && MemError() == noErr), EXIT);
                                    } else {
                                        // do we need to expand our array of styles?
                                        if (substituteStyleCount > maxStyleCount)
                                        {
                                            maxStyleCount += kInitialStyleAllocation;
                                            HUnlock((Handle) substituteStyles);
                                            SetHandleSize((Handle) substituteStyles, maxStyleCount * sizeof(ATSUStyle));
                                            HLock((Handle) substituteStyles);
                                            theStatus = MemError();
                                            require(theStatus == noErr, EXIT);
                                        }
                                    }
                                    (*substituteStyles)[substituteStyleCount++] = replacementStyle;
                                }
 
                                theStatus = ATSUSetRunStyle(txLayout, replacementStyle, replaceOffset, replaceLength);
                                require (theStatus == noErr, EXIT);
                                break;
                            }
                            case kATSUFontsNotMatched:
                            {
                                //  kATSUFontsNotMatched" means that some of the characters cannot
                                //  be drawn with any currently active font.
                                //  this will tell us to turn on transient font matching 
                                //  and pick up glyphs from the last resort font 
                                needTransientMatching = true;
                                break;
                            }
                            default:
                                //  done with any other status
                                doneWithSubstitution = true;
                                break;
                        }
                        replaceOffset += replaceLength;
                        replaceLength = iTextLength - replaceOffset;
                    } 
                    require(theStatus == noErr, EXIT);
                    
                    if (needTransientMatching)
                    {
                        theStatus = ATSUSetTransientFontMatching(txLayout, true);
                        require(theStatus == noErr, EXIT);
                    }
                }
                
                //  Finally, this is where the drawing happens
                theStatus = atsuDrawTextInBox(txLayout, iBox);
            }
        }
    }
 
EXIT:
    if (globalStyle)
    {
        ATSUDisposeStyle(globalStyle);
    }
    
    if (substituteStyles)
    {
        int i;
        for (i = 0; i < substituteStyleCount; i++)
        {
            ATSUDisposeStyle((*substituteStyles)[i]);
        }
        HUnlock((Handle) substituteStyles);
        DisposeHandle((Handle) substituteStyles);   
 
    }
    if (txLayout) {
        ATSUDisposeTextLayout(txLayout);
    }
    return theStatus;
}