Apple Developer Connection
Member Login Log In | Not a Member? Contact ADC

< Previous PageNext Page > Hide TOC

Overriding ATSUI Layout Operations

You can override ATSUI’s layout operations by modifying layout information for the glyphs associated with a text layout object. You can adjust such values as the glyph’s baseline delta, advance delta, and real position values. You can also specify a post-layout operation (such as glyph substitution) that modifies a line after ATSUI has completed its layout operations. Table 6-1 defines some of the values you can modify using direct-access functions.

Table 6-1  Some values you can modify using direct-access functions

Value

Definition

Real position

The actual drawing position on the x-axis. This position does not include the device delta.

Advance delta

The distance between the end of one glyph’s advance and the next glyph’s real position.

Baseline delta

The distance between the actual drawing position on the y-axis and the baseline position.

Device delta

A value used to adjust truncated factional values for cases in which fractional positioning can’t be used. For example, to compensate for integer drawing in QuickDraw. Device delta values are usually used when anti-aliasing is turned off. However, these values can be used when anti-aliasing is on to assure that the glyphs in a connected script (such as one that used the Zapfino font) are connected smoothly.

The three lines of text in Figure 6-1 show an example of what you can do using the ATSUI direct-access functions. The first line is drawn as ATSUI would normally draw the text. The next two lines were drawn by ATSUI after glyph values were modified through the use of direct-access functions. The advance delta values of the text in the second line have been modified by decreasing the advance to create a condensed text appearance. Whereas the advance delta values of the text in the third line have been increased from normal to create an extended text appearance. To achieve each effect, the advance delta values are modified by a callback, then ATSUI uses the modified values during the course of its normal layout and drawing processes.


Figure 6-1  Unadjusted text compared to text with modified advance delta values

Unadjusted text compared to text with modified advance delta values

Figure 6-2 also shows an example of using ATSUI direct-access functions, but in this case the layout was modified after ATSUI already performed its normal layout process. The space characters in the text are replaced with a specified replacement character through a callback that is applied post-layout.


Figure 6-2  Text drawn with replacement characters in place of space characters

Text drawn with replacement characters in place of space characters

The text in Figure 6-3 is one line of text. The baseline delta values for the glyphs are adjusted such that every other glyph has a positive baseline delta value and alternate glyphs have a negative baseline delta value.


Figure 6-3  Text with modified baseline delta values

Text with modified baseline delta values

As you can see from Figure 6-1, Figure 6-2, and Figure 6-3, you can achieve a number of effects using the ATSUI direct-access functions. Overriding ATSUI layout operations requires that you perform these tasks:

  1. Write a callback that obtains glyph data from ATSUI and modifies the glyph data associated with a text layout object.

  2. Install the callback on a text layout object.

  3. Use ATSUI as you normally would to create text layout objects, draw text, get glyph bounds, and so forth. ATSUI invokes your callback each time it lays out a line of text.

The callback definition for a direct-access callback function is as follows:

OSStatus (* ATSUDirectLayoutOperationOverrideProcPtr)(
                    ATSULayoutOperationSelector iCurrentOperation,
                    ATSULineRef iLineRef,
                    UInt32 iRefCon,
                    void *iOperationCallbackParameterPtr,
                    ATSULayoutOperationCallbackStatus *oCallbackStatus);

These are the parameters:

Within the body of your callback function you need to call one of the ATSUI direct-access functions, such as ATSUDirectGetLayoutDataArrayPtrFromLineRef, to obtain the glyph data you want to modify. You use the data selectors shown in Table 6-2 to specify which data you want to obtain. The selectors are nonexclusive; you can use the logical-Or operator to combine several data selectors if your callback handles multiple operations.

Table 6-2  Data selectors used to obtain glyph data

Selector

Obtains

kATSUDirectDataAdvanceDeltaFixedArray

The advance delta array.

kATSUDirectDataBaselineDeltaFixedArray

The baseline delta array.

kATSUDirectDataDeviceDeltaSInt16Arrray

The device delta array.

kATSUDirectDataStyleIndexUInt16Array

The style-index array. The values in the array are indices to the style setting reference array.

kATSUDirectDataStyleSettingATSUStyleSettingRefArray

The style setting array.

kATSUDirectDataLayoutRecordATSLayoutRecordVersionCurrent

The ATSLayoutRecord for a glyph. The layout record contains the glyph ID, real position, and other information.

To install a callback, you follow the same procedure as you would to set a layout attribute. You must do the following:

  1. Set up a triple (tag, size, value) to specify the operation you want to override and the callback function you are providing to handle the operation.

    In this case, the attribute tag is a structure (ATSULayoutOperationOverrideSpecifier) that specifies a selector for a layout operation and a pointer to a callback function. Typical selectors are listed in Table 6-3. See Inside Mac OS X: ATSUI Reference for a complete list.

  2. Call the function ATSUSetLayoutControls to associate the triple with the text layout object whose layout operation you want to override.

Table 6-3  Selectors for layout operations

Selector

Specifies

kATSULayoutOperationNone

No layout select operation selected.

kATSULayoutOperationJustification

Justification.

kATSULayoutOperationMorph

Character morphing.

kATSULayoutOperationKerningAdjustment

Kerning adjustment.

kATSULayoutOperationBaselineAdjustment

Baseline adjustment; layout above or below current baseline.

kATSULayoutOperationTrackingAdjustment

Tracking adjustment.

kATSULayoutOperationPostLayoutAdjustment

Applies a layout operation after ATSUI has finished its layout operations.

After you’ve installed the callback, you use ATSUI as you normally would to draw text. ATSUI triggers your callback each time the layout operation you specified is invoked.

You’ll see specific examples of how to write and install callbacks that manipulate the final layout in the following sections:

Before you override any of ATSUI’s layout operations, you should read the guidelines outlined in the following section.

In this section:

Guidelines for Overriding Layout Operations
Extending the Space Between Glyphs
Positioning Glyphs Along a Curve


Guidelines for Overriding Layout Operations

Follow these guidelines when you override ATSUI layout operations:

Extending the Space Between Glyphs

You can extend the space between glyphs by providing values that adjust the positions of the glyph. To do so, you need to

Depending on the advance delta values you provide, you’ll get an effect similar to the bottom line shown in Figure 6-1. All glyphs except the first glyph in this line are drawn using the same advance delta value to achieve a uniform spacing. The advance delta values do not need to be the same for each glyph.

The following sections show how to extend the space between glyphs:

Writing a Callback to Modify Advance Delta Values

The callback shown in Listing 6-1 (MyStretchGlyphCallback) performs one task—it modifies advance delta values. The effect is to extend the space between glyphs. A detailed explanation for each numbered line of code appears following the listing.

Listing 6-1  A callback that modifies advance delta values

static
OSStatus MyStretchGlyphCallback (
                    ATSULayoutOperationSelector     iCurrentOperation,
                    ATSULineRef     iLineRef,
                    UInt32          iRefCon,
                    void            *iOperationExtraParameter,
                    ATSULayoutOperationCallbackStatus *oCallbackStatus )// 1
{
    OSStatus        status;
    Fixed           *myDeltaXArray;// 2
    ItemCount       myRecordArrayCount;
    ItemCount       myItems;
    Fixed           myStretchFactor;
 
    status = ATSUDirectGetLayoutDataArrayPtrFromLineRef (iLineRef,
                            kATSUDirectDataAdvanceDeltaFixedArray,
                            true,
                            (void **) &myDeltaXArray,
                            &myRecordArrayCount);// 3
    require_noerr (status, StretchGlyphCallback_err);
 
    myStretchFactor = (Fixed) ((gFontSize*2) << 16);// 4
    for (myItems = 1; myItems < myRecordArrayCount; myItems++ ) // 5
    {
        myDeltaXArray[myItems] += myStretchFactor;
    };
    status = ATSUDirectReleaseLayoutDataArrayPtr (iLineRef,
                            kATSUDirectDataAdvanceDeltaFixedArray,
                            (void **) &myDeltaXArray );// 6
 
StretchGlyphCallback_err:
    *oCallbackStatus = kATSULayoutOperationCallbackStatusHandled;// 7
    return status;
}

Here’s what the code does:

  1. Provides the parameters specified by the callback definition. As you’ll see, this callback uses only two of the parameters: iLineRef and oCallbackStatus.

  2. Declares an array for the advance delta values.

  3. Calls the function ATSUDirectGetLayoutDataArrayPtrFromLineRef to obtain the advance delta values for the line specified by iLineRef. This function returns the data pointer specified by the iDataSelector parameter (in this case, kATSUDirectDataAdvanceDeltaFixedArray) for the line specified by iLineRef.

    You should pass true for the iCreate parameter. If the requested array isn’t referenced by iLineRef, ATSUI creates and returns a zero-filled array. If the requested array has already been created, ATSUI returns the array with the current advance delta values. On output, myRecordArrayCount specifies the number of items in the array.

  4. Sets a stretch-factor value, based on the current font size, to be used for the advance delta.

  5. Iterates through the advance delta array, assigning a value to each element in the array.

  6. Releases the data pointer (myDeltaXArray) obtained by calling the function ATSUDirectGetLayoutDataArrayPtrFromLineRef. You must release this data pointer by calling the function ATSUDirectReleaseLayoutDataArrayPtr. Releasing the array signals ATSUI that you are done with the data and that ATSUI can merge values you set with those set in the font.

  7. Returns the status kATSULayoutOperationCallbackStatusHandled to indicate the layout operation is handled successfully.

Installing a Callback for a Justification Override Operation

The MyInstallStretchGlyphCallback function in “A function that installs a justification override callback ” sets up ATSUI to call MyStretchGlyphCallback each time a justification operation needs to be performed on the text associated with the specified text layout object. A detailed explanation for each numbered line of code appears following the listing.

Listing 6-2  A function that installs a justification override callback

OSStatus MyInstallStretchGlyphCallback (ATSUTextLayout myTextLayout)
{
    OSStatus                status = noErr;
    ATSULayoutOperationOverrideSpecifier    myOverrideSpec;// 1
    ATSUAttributeTag        theTag;
    ByteCount               theSize;
    ATSUAttributeValuePtr   theValue;
 
    myOverrideSpec.operationSelector =
                 kATSULayoutOperationJustification;// 2
    myOverrideSpec.overrideUPP = MyStretchGlyphCallback;
 
    theTag = kATSULayoutOperationOverrideTag;// 3
    theSize = sizeof (ATSULayoutOperationOverrideSpecifier);
    theValue = &myOverrideSpec;
 
    status = ATSUSetLayoutControls (myTextLayout, 1,
                            &theTag, &theSize, &theValue);// 4
    require_noerr (status, InstallStrechGlyphCallback_err );
 
InstallStrechGlyphCallback_err:
 
    return status;
}

Here’s what the code does:

  1. Declares an override specification. This structure contains a selector for a layout operation and a universal procedure pointer to the callback you supply.

  2. Assigns the justification selector as the operation selector and the MyStretchGlyphCallback callback as the callback to handle justification.

  3. Sets up a triple (tag, size, value) for the layout attribute. In this case, the layout attribute is the override specification.

  4. Calls the function ATSUSetLayoutControls to associate the override specification with the text layout object.

Positioning Glyphs Along a Curve

The text in Figure 6-4 shows glyphs whose baseline delta values are adjusted to track a sine curve. To achieve the smoothest look possible, the baseline delta values are calculated using the real position of the glyphs. In addition, the advance width for each glyph is adjusted.

To position glyphs along a curve, you need to do the following:

Note: When you position text along a curve, you must obtain and lay out the glyphs in small groups. For arbitrary Unicode text, you should consider international issues when you break the glyphs into small groups. For example, breaking a cursive script, such as Arabic, in the middle of a ligature or between two cursively connected glyphs gives the wrong appearance in the drawn output.

The next sections discuss the specific tasks you need to perform to position glyphs along a curve:


Figure 6-4  Text positioned along a sine curve

Text positioned along a sine curve

Writing the Callback to Modify Baseline Delta Values

The callback (MySineCurveGlyphCallback) shown in Listing 6-3 modifies advance and delta values so that glyphs are positioned along a sine curve. A detailed explanation for each numbered line of code appears following the listing.

Listing 6-3  A callback that positions glyphs along a sine curve

static
OSStatus MySineCurveGlyphCallback(
            ATSULayoutOperationSelector     iCurrentOperation,
            ATSULineRef             iLineRef,
            UInt32                  iRefCon,
            void                    *iOperationExtraParameter,
            ATSULayoutOperationCallbackStatus   *oCallbackStatus )
{
    OSStatus        status = noErr;
    Fixed           *deltaYArray;
    Fixed           *deltaXArray;
    Fixed           positionDifference;
    ATSLayoutRecord *layoutRecordArray;
    ItemCount       recordArrayCount;
    ItemCount       i;
    Fixed           scaleFactor = 0;
    float           amplitude;
 
    status = ATSUDirectGetLayoutDataArrayPtrFromLineRef (iLineRef,
                    kATSUDirectDataLayoutRecordATSLayoutRecordCurrent,
                    false,
                    (void **) &layoutRecordArray,
                    &recordArrayCount );// 1
    require_noerr (status, GlyphWaveCallback_err);
 
    status = ATSUDirectGetLayoutDataArrayPtrFromLineRef (iLineRef,
                            kATSUDirectDataAdvanceDeltaFixedArray,
                            true,
                            (void **) &deltaXArray,
                            &recordArrayCount );// 2
    require_noerr (status, GlyphWaveCallbackDeltaXArray_err);
 
    status = ATSUDirectGetLayoutDataArrayPtrFromLineRef (iLineRef,
                        kATSUDirectDataBaselineDeltaFixedArray,
                        true,
                        (void **) &deltaYArray,
                        &recordArrayCount );// 3
    require_noerr (status, SineCurveGlyphCallbackDeltaYArray_err);
 
    for ( i = 1; i < recordArrayCount; i++ )// 4
    {
        positionDifference = (layoutRecordArray[i].realPos -
                            layoutRecordArray[i-1].realPos);
        if (positionDifference > scaleFactor)
                    scaleFactor = positionDifference;
    }
 
    amplitude = FixedToFloat( scaleFactor );// 5
 
    for ( i = 1; i < recordArrayCount - 1; i++ )// 6
    {
        positionDifference =  scaleFactor - (layoutRecordArray[i].realPos -
                                layoutRecordArray[i-1].realPos);// 7
 
        layoutRecordArray[i].realPos += positionDifference;// 8
        deltaXArray[i-1] += positionDifference;// 9
 
        deltaYArray[i] = FloatToFixed (sinf ( i ) * amplitude);// 10
    };
 
    ATSUDirectReleaseLayoutDataArrayPtr (iLineRef,
                            kATSUDirectDataBaselineDeltaFixedArray,
                            (void **) &deltaYArray );// 11
 
SineCurveGlyphCallbackDeltaYArray_err:
 
    ATSUDirectReleaseLayoutDataArrayPtr (iLineRef,
                            kATSUDirectDataAdvanceDeltaFixedArray,
                            (void **) &deltaXArray );// 12
 
SineCurveGlyphCallbackDeltaXArray_err:
 
    ATSUDirectReleaseLayoutDataArrayPtr (iLineRef,
                    kATSUDirectDataLayoutRecordATSLayoutRecordCurrent,
                        (void **) &layoutRecordArray );// 13
 
SineCurveGlyphCallback_err:
 
    *oCallbackStatus = kATSULayoutOperationCallbackStatusHandled;// 14
 
    return status;
 
}

Here’s what the code does:

  1. Calls the function ATSUDirectGetLayoutDataArrayPtrFromLineRef with the layout record data selector to obtain the ATS layout records for each glyph on the line. A layout record contains the glyph real position. Calculating a sine curve based on the real positions of the glyphs results in a much smoother appearance.

  2. Calls the function ATSUDirectGetLayoutDataArrayPtrFromLineRef with the advance delta data selector to obtain the advance delta array for the glyphs on the line.

  3. Calls the function ATSUDirectGetLayoutDataArrayPtrFromLineRef with the baseline delta data selector to obtain the baseline delta array for the glyphs on the line.

  4. Iterates through the layout records to find the largest positional difference in the line. This difference will be used to set a scaling factor.

  5. Sets the amplitude for the sine curve calculation to the scale factor.

  6. Iterates through the glyph information to set the advance and baseline delta factors, taking into account the real position of the glyph.

  7. Calculates a positional difference. This will be used to impose a fixed width on each glyph that takes the glyph’s point size into account.

  8. Adds the positional difference to the real position value for the layout record array.

  9. Adds the positional difference to the advance delta value.

  10. Calculates the baseline delta using the previously calculated amplitude value and calling the function sinf.

  11. Releases the baseline delta array by calling the function ATSUDirectReleaseLayoutDataArrayPtr with the baseline delta data selector.

  12. Releases the advance delta array by calling the function ATSUDirectReleaseLayoutDataArrayPtr with the advance delta data selector.

  13. Releases the layout record array by calling the function ATSUDirectReleaseLayoutDataArrayPtr with the ATS layout record data selector.

  14. Returns a status value to ATSUI to indicate the layout operation is handled successfully.

Installing a Callback for a Post-Layout Operation

The MyInstallSineCuveGlyphCallback function in Listing 6-4 sets up ATSUI to call MySineCuveGlyphCallback. ATSUI triggers the callback for each line of text associated with the specified text layout object. Because the callback does post-layout processing, ATSUI invokes the callback at the end of its layout operations for the line. A detailed explanation for each numbered line of code appears following the listing.

Listing 6-4  Installing a callback for a post-layout override operation

OSStatus MyInstallSineCurveGlyphCallback (ATSUTextLayout textLayout )
{
    OSStatus                                status = noErr;
    ATSULayoutOperationOverrideSpecifier    myOverrideSpec;// 1
    ATSUAttributeTag     theTag;
    ByteCount            theSize;
    ATSUAttributeValuePtr thePtr;
 
    myOverrideSpec.operationSelector =
                            kATSULayoutOperationPostLayoutAdjustment;// 2
    myOverrideSpec.overrideUPP = MySineCurveGlyphCallback;
 
    attrTag = kATSULayoutOperationOverrideTag;// 3
    attrSize = sizeof (ATSULayoutOperationOverrideSpecifier);
    attrPtr = &myOverrideSpec;
 
    status = ATSUSetLayoutControls (textLayout, 1,
                                &theTag, &theSize, &thePtr );// 4
    require_noerr (status, InstallSineCureveGlyphCallback_err );
 
InstallSineCureveGlyphCallback_err:
 
    return status;
}

Here’s what the code does:

  1. Declares an override specification. This structure contains a selector for a layout operation and a universal procedure pointer to the callback you supply.

  2. Assigns the post-layout selector as the operation selector and the MySineCurveGlyphCallback callback as the callback to handle justification.

  3. Sets up a triple (tag, size, value) for the layout attribute. In this case, the layout attribute is the override specification.

  4. Calls the function ATSUSetLayoutControls to associate the override specification with the text layout object.



< Previous PageNext Page > Hide TOC


Last updated: 2007-07-10




Did this document help you?
Yes: Tell us what works for you.

It’s good, but: Report typos, inaccuracies, and so forth.

It wasn’t helpful: Tell us what would have helped.
Get information on Apple products.
Visit the Apple Store online or at retail locations.
1-800-MY-APPLE

Copyright © 2007 Apple Inc.
All rights reserved. | Terms of use | Privacy Notice