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.
Value | Definition |
|---|---|
The actual drawing position on the x-axis. This position does not include the device delta. | |
The distance between the end of one glyph’s advance and the next glyph’s real position. | |
The distance between the actual drawing position on the y-axis and the baseline position. | |
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-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.
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.
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:
Write a callback that obtains glyph data from ATSUI and modifies the glyph data associated with a text layout object.
Install the callback on a text layout object.
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:
iCurrentOperation, the operation that triggered the callback. This value is passed to your callback by ATSUI. If you write a callback that handles more than one layout operation, you can use this value to determine which operation you should handle.
iLineRef, a reference to the current line. This is the line of text on which your callback operates. Your callback gets called for each line of text associated with the text layout object on which you installed the callback.
iRefCon, a value set by calling the function ATSUSetTextLayoutRefCon. This is optional, and can be any data you need to track for your application, such as user preference data related to layout operations.
iOperationCallbackParameterPtr. Currently unused and is set to NULL.
oCallbackStatus. On output, you must supply a status value to indicate to ATSUI whether your callback handled the operation (kATSULayoutOperationCallbackStatusHandled) or whether ATSUI needs to handle the operation (kATSULayoutOperationCallbackStatusContinue). If you return the result kATSULayoutOperationCallbackStatusContinue, ATSUI may overwrite any settings that you have modified for that process.
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.
Selector | Obtains |
|---|---|
The advance delta array. | |
The baseline delta array. | |
The device delta array. | |
The style-index array. The values in the array are indices to the style setting reference array. | |
The style setting array. | |
The |
To install a callback, you follow the same procedure as you would to set a layout attribute. You must do the following:
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.
Call the function ATSUSetLayoutControls to associate the triple with the text layout object whose layout operation you want to override.
Selector | Specifies |
|---|---|
No layout select operation selected. | |
Justification. | |
Character morphing. | |
Kerning adjustment. | |
Baseline adjustment; layout above or below current baseline. | |
Tracking adjustment. | |
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:
“Extending the Space Between Glyphs.” This example shows how to obtain the array of advance delta values for a line of text, and then to modify advance values to extend the space between the glyphs.
“Positioning Glyphs Along a Curve.” This example shows how to retrieve ATS layout records, advance delta arrays, and baseline delta arrays, and then to use real position information to calculate advance and baseline delta values that achieve the desired layout.
Before you override any of ATSUI’s layout operations, you should read the guidelines outlined in the following section.
Guidelines for Overriding Layout Operations
Extending the Space Between Glyphs
Positioning Glyphs Along a Curve
Follow these guidelines when you override ATSUI layout operations:
You should modify advance delta values during ATSUI’s layout process and not as a post layout operation. ATSUI uses advance delta values when it calculates the real position. The real position is available only post layout. So adjusting advance delta values post layout won’t have an effect.
You should modify the real position only at post layout.
Ideally, if you modify the real position, you should update the advance delta values to reflect the real-position modifications.
You should modify baseline delta values as a post-layout operation (kATSULayoutOperationPostLayoutAdjustment) or in the baseline operation (kATSULayoutOperationBaselineAdjustment). If you modify these values during any other operation, ATSUI’s baseline operation can overwrite the values you modify.
You can extend the space between glyphs by providing values that adjust the positions of the glyph. To do so, you need to
inform ATSUI that you want to override the justification layout operation
obtain the advance delta array for the glyphs associated with the text layout object whose layout you want to modify
adjust advance delta values for each glyph whose layout you want to modify
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:
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:
Provides the parameters specified by the callback definition. As you’ll see, this callback uses only two of the parameters: iLineRef and oCallbackStatus.
Declares an array for the advance delta values.
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.
Sets a stretch-factor value, based on the current font size, to be used for the advance delta.
Iterates through the advance delta array, assigning a value to each element in the array.
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.
Returns the status kATSULayoutOperationCallbackStatusHandled to indicate the layout operation is handled successfully.
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:
Declares an override specification. This structure contains a selector for a layout operation and a universal procedure pointer to the callback you supply.
Assigns the justification selector as the operation selector and the MyStretchGlyphCallback callback as the callback to handle justification.
Sets up a triple (tag, size, value) for the layout attribute. In this case, the layout attribute is the override specification.
Calls the function ATSUSetLayoutControls to associate the override specification with the text layout object.
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:
Inform ATSUI that you want to provide a post-layout operation.
Obtain the ATS layout record, advance delta array, and the baseline delta array for the glyphs associated with the text layout object whose layout you want to modify. The ATS layout record contains the real position of the glyph.
Adjust the advance delta and baseline delta values for each glyph such that the values track the specified curve.
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:
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:
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.
Calls the function ATSUDirectGetLayoutDataArrayPtrFromLineRef with the advance delta data selector to obtain the advance delta array for the glyphs on the line.
Calls the function ATSUDirectGetLayoutDataArrayPtrFromLineRef with the baseline delta data selector to obtain the baseline delta array for the glyphs on the line.
Iterates through the layout records to find the largest positional difference in the line. This difference will be used to set a scaling factor.
Sets the amplitude for the sine curve calculation to the scale factor.
Iterates through the glyph information to set the advance and baseline delta factors, taking into account the real position of the glyph.
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.
Adds the positional difference to the real position value for the layout record array.
Adds the positional difference to the advance delta value.
Calculates the baseline delta using the previously calculated amplitude value and calling the function sinf.
Releases the baseline delta array by calling the function ATSUDirectReleaseLayoutDataArrayPtr with the baseline delta data selector.
Releases the advance delta array by calling the function ATSUDirectReleaseLayoutDataArrayPtr with the advance delta data selector.
Releases the layout record array by calling the function ATSUDirectReleaseLayoutDataArrayPtr with the ATS layout record data selector.
Returns a status value to ATSUI to indicate the layout operation is handled successfully.
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:
Declares an override specification. This structure contains a selector for a layout operation and a universal procedure pointer to the callback you supply.
Assigns the post-layout selector as the operation selector and the MySineCurveGlyphCallback callback as the callback to handle justification.
Sets up a triple (tag, size, value) for the layout attribute. In this case, the layout attribute is the override specification.
Calls the function ATSUSetLayoutControls to associate the override specification with the text layout object.
Last updated: 2007-07-10