• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4 *           (C) 2000 Dirk Mueller (mueller@kde.org)
5 * Copyright (C) 2003, 2006 Apple Computer, Inc.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB.  If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 */
22
23#import "config.h"
24#import "Font.h"
25
26#if USE(ATSUI)
27
28#import "CharacterNames.h"
29#import "GraphicsContext.h"
30#import "Logging.h"
31#import "ShapeArabic.h"
32#import "SimpleFontData.h"
33#import <AppKit/NSGraphicsContext.h>
34#import <wtf/OwnArrayPtr.h>
35
36#define SYNTHETIC_OBLIQUE_ANGLE 14
37
38#ifdef __LP64__
39#define URefCon void*
40#else
41#define URefCon UInt32
42#endif
43
44using namespace std;
45
46namespace WebCore {
47
48struct ATSULayoutParameters : Noncopyable
49{
50    ATSULayoutParameters(const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts = 0)
51        : m_run(run)
52        , m_font(0)
53        , m_hasSyntheticBold(false)
54        , m_syntheticBoldPass(false)
55        , m_padPerSpace(0)
56        , m_fallbackFonts(fallbackFonts)
57    {
58    }
59
60    ~ATSULayoutParameters()
61    {
62        ATSUDisposeTextLayout(m_layout);
63    }
64
65    void initialize(const Font*, const GraphicsContext* = 0);
66
67    const TextRun& m_run;
68
69    const Font* m_font;
70
71    ATSUTextLayout m_layout;
72    OwnArrayPtr<const SimpleFontData*> m_fonts;
73
74    OwnArrayPtr<UChar> m_charBuffer;
75    bool m_hasSyntheticBold;
76    bool m_syntheticBoldPass;
77    float m_padPerSpace;
78    HashSet<const SimpleFontData*>* m_fallbackFonts;
79};
80
81static TextRun copyRunForDirectionalOverrideIfNecessary(const TextRun& run, OwnArrayPtr<UChar>& charactersWithOverride)
82{
83    if (!run.directionalOverride())
84        return run;
85
86    charactersWithOverride.set(new UChar[run.length() + 2]);
87    charactersWithOverride[0] = run.rtl() ? rightToLeftOverride : leftToRightOverride;
88    memcpy(&charactersWithOverride[1], run.data(0), sizeof(UChar) * run.length());
89    charactersWithOverride[run.length() + 1] = popDirectionalFormatting;
90
91    TextRun result = run;
92    result.setText(charactersWithOverride.get(), run.length() + 2);
93    return result;
94}
95
96static bool fontHasMirroringInfo(ATSUFontID fontID)
97{
98    ByteCount propTableSize;
99    OSStatus status = ATSFontGetTable(fontID, 'prop', 0, 0, 0, &propTableSize);
100    if (status == noErr)    // naively assume that if a 'prop' table exists then it contains mirroring info
101        return true;
102    else if (status != kATSInvalidFontTableAccess) // anything other than a missing table is logged as an error
103        LOG_ERROR("ATSFontGetTable failed (%d)", status);
104
105    return false;
106}
107
108static void disableLigatures(const SimpleFontData* fontData)
109{
110    // Don't be too aggressive: if the font doesn't contain 'a', then assume that any ligatures it contains are
111    // in characters that always go through ATSUI, and therefore allow them. Geeza Pro is an example.
112    // See bugzilla 5166.
113    if (fontData->platformData().allowsLigatures())
114        return;
115
116    ATSUFontFeatureType featureTypes[] = { kLigaturesType };
117    ATSUFontFeatureSelector featureSelectors[] = { kCommonLigaturesOffSelector };
118    OSStatus status = ATSUSetFontFeatures(fontData->m_ATSUStyle, 1, featureTypes, featureSelectors);
119    if (status != noErr)
120        LOG_ERROR("ATSUSetFontFeatures failed (%d) -- ligatures remain enabled", status);
121}
122
123static void initializeATSUStyle(const SimpleFontData* fontData)
124{
125    if (fontData->m_ATSUStyleInitialized)
126        return;
127
128    ATSUFontID fontID = fontData->platformData().m_atsuFontID;
129    if (!fontID) {
130        LOG_ERROR("unable to get ATSUFontID for %@", fontData->platformData().font());
131        return;
132    }
133
134    OSStatus status = ATSUCreateStyle(&fontData->m_ATSUStyle);
135    if (status != noErr)
136        // Who knows how many ATSU functions will crash when passed a NULL style...
137        LOG_ERROR("ATSUCreateStyle failed (%d)", status);
138
139    CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
140    if (fontData->platformData().m_syntheticOblique)
141        transform = CGAffineTransformConcat(transform, CGAffineTransformMake(1, 0, -tanf(SYNTHETIC_OBLIQUE_ANGLE * acosf(0) / 90), 1, 0, 0));
142    Fixed fontSize = FloatToFixed(fontData->platformData().m_size);
143    ByteCount styleSizes[4] = { sizeof(Fixed), sizeof(ATSUFontID), sizeof(CGAffineTransform), sizeof(Fract) };
144    // Turn off automatic kerning until it is supported in the CG code path (bug 6136)
145    Fract kerningInhibitFactor = FloatToFract(1.0);
146
147    ATSUAttributeTag styleTags[4] = { kATSUSizeTag, kATSUFontTag, kATSUFontMatrixTag, kATSUKerningInhibitFactorTag };
148    ATSUAttributeValuePtr styleValues[4] = { &fontSize, &fontID, &transform, &kerningInhibitFactor };
149    status = ATSUSetAttributes(fontData->m_ATSUStyle, 4, styleTags, styleSizes, styleValues);
150    if (status != noErr)
151        LOG_ERROR("ATSUSetAttributes failed (%d)", status);
152
153    fontData->m_ATSUMirrors = fontHasMirroringInfo(fontID);
154
155    // Turn off ligatures such as 'fi' to match the CG code path's behavior, until bug 6135 is fixed.
156    disableLigatures(fontData);
157
158    fontData->m_ATSUStyleInitialized = true;
159}
160
161static OSStatus overrideLayoutOperation(ATSULayoutOperationSelector, ATSULineRef iLineRef, URefCon iRefCon, void*, ATSULayoutOperationCallbackStatus* oCallbackStatus)
162{
163    ATSULayoutParameters* params = reinterpret_cast<ATSULayoutParameters*>(iRefCon);
164    OSStatus status;
165    ItemCount count;
166    ATSLayoutRecord *layoutRecords;
167
168    if (params->m_run.applyWordRounding()) {
169        status = ATSUDirectGetLayoutDataArrayPtrFromLineRef(iLineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, true, (void **)&layoutRecords, &count);
170        if (status != noErr) {
171            *oCallbackStatus = kATSULayoutOperationCallbackStatusContinue;
172            return status;
173        }
174
175        Fixed lastNativePos = 0;
176        float lastAdjustedPos = 0;
177        const UChar* characters = params->m_charBuffer ? params->m_charBuffer.get() : params->m_run.characters();
178        const SimpleFontData** renderers = params->m_fonts.get();
179        const SimpleFontData* renderer;
180        const SimpleFontData* lastRenderer = 0;
181        ByteCount offset = layoutRecords[0].originalOffset;
182        UChar nextCh = *(UChar *)(((char *)characters)+offset);
183        bool shouldRound = false;
184        bool syntheticBoldPass = params->m_syntheticBoldPass;
185        Fixed syntheticBoldOffset = 0;
186        bool hasExtraSpacing = (params->m_font->letterSpacing() || params->m_font->wordSpacing() || params->m_run.padding()) && !params->m_run.spacingDisabled();
187        float padding = params->m_run.padding();
188        // In the CoreGraphics code path, the rounding hack is applied in logical order.
189        // Here it is applied in visual left-to-right order, which may be better.
190        ItemCount lastRoundingChar = 0;
191        ItemCount i;
192        for (i = 1; i < count; i++) {
193            bool isLastChar = i == count - 1;
194            renderer = renderers[offset / 2];
195            float width;
196            if (nextCh == zeroWidthSpace || Font::treatAsZeroWidthSpace(nextCh) && !Font::treatAsSpace(nextCh)) {
197                width = 0;
198                layoutRecords[i-1].glyphID = renderer->spaceGlyph();
199            } else {
200                width = FixedToFloat(layoutRecords[i].realPos - lastNativePos);
201                if (renderer != lastRenderer && width) {
202                    lastRenderer = renderer;
203                    // The CoreGraphics interpretation of NSFontAntialiasedIntegerAdvancementsRenderingMode seems
204                    // to be "round each glyph's width to the nearest integer". This is not the same as ATSUI
205                    // does in any of its device-metrics modes.
206                    shouldRound = renderer->platformData().roundsGlyphAdvances();
207                    if (syntheticBoldPass)
208                        syntheticBoldOffset = FloatToFixed(renderer->syntheticBoldOffset());
209                    if (params->m_fallbackFonts && renderer != params->m_font->primaryFont())
210                        params->m_fallbackFonts->add(renderer);
211                }
212                if (shouldRound)
213                    width = roundf(width);
214                width += renderer->syntheticBoldOffset();
215                if (renderer->pitch() == FixedPitch ? width == renderer->spaceWidth() : (layoutRecords[i-1].flags & kATSGlyphInfoIsWhiteSpace))
216                    width = renderer->adjustedSpaceWidth();
217            }
218            lastNativePos = layoutRecords[i].realPos;
219
220            if (hasExtraSpacing) {
221                if (width && params->m_font->letterSpacing())
222                    width +=params->m_font->letterSpacing();
223                if (Font::treatAsSpace(nextCh)) {
224                    if (params->m_run.padding()) {
225                        if (padding < params->m_padPerSpace) {
226                            width += padding;
227                            padding = 0;
228                        } else {
229                            width += params->m_padPerSpace;
230                            padding -= params->m_padPerSpace;
231                        }
232                    }
233                    if (offset != 0 && !Font::treatAsSpace(*((UChar *)(((char *)characters)+offset) - 1)) && params->m_font->wordSpacing())
234                        width += params->m_font->wordSpacing();
235                }
236            }
237
238            UChar ch = nextCh;
239            offset = layoutRecords[i].originalOffset;
240            // Use space for nextCh at the end of the loop so that we get inside the rounding hack code.
241            // We won't actually round unless the other conditions are satisfied.
242            nextCh = isLastChar ? ' ' : *(UChar *)(((char *)characters)+offset);
243
244            if (Font::isRoundingHackCharacter(ch))
245                width = ceilf(width);
246            lastAdjustedPos = lastAdjustedPos + width;
247            if (Font::isRoundingHackCharacter(nextCh) && (!isLastChar || params->m_run.applyRunRounding())){
248                if (params->m_run.ltr())
249                    lastAdjustedPos = ceilf(lastAdjustedPos);
250                else {
251                    float roundingWidth = ceilf(lastAdjustedPos) - lastAdjustedPos;
252                    Fixed rw = FloatToFixed(roundingWidth);
253                    ItemCount j;
254                    for (j = lastRoundingChar; j < i; j++)
255                        layoutRecords[j].realPos += rw;
256                    lastRoundingChar = i;
257                    lastAdjustedPos += roundingWidth;
258                }
259            }
260            if (syntheticBoldPass) {
261                if (syntheticBoldOffset)
262                    layoutRecords[i-1].realPos += syntheticBoldOffset;
263                else
264                    layoutRecords[i-1].glyphID = renderer->spaceGlyph();
265            }
266            layoutRecords[i].realPos = FloatToFixed(lastAdjustedPos);
267        }
268
269        status = ATSUDirectReleaseLayoutDataArrayPtr(iLineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, (void **)&layoutRecords);
270    }
271    *oCallbackStatus = kATSULayoutOperationCallbackStatusHandled;
272    return noErr;
273}
274
275static inline bool isArabicLamWithAlefLigature(UChar c)
276{
277    return c >= 0xfef5 && c <= 0xfefc;
278}
279
280static void shapeArabic(const UChar* source, UChar* dest, unsigned totalLength, unsigned shapingStart)
281{
282    while (shapingStart < totalLength) {
283        unsigned shapingEnd;
284        // We do not want to pass a Lam with Alef ligature followed by a space to the shaper,
285        // since we want to be able to identify this sequence as the result of shaping a Lam
286        // followed by an Alef and padding with a space.
287        bool foundLigatureSpace = false;
288        for (shapingEnd = shapingStart; !foundLigatureSpace && shapingEnd < totalLength - 1; ++shapingEnd)
289            foundLigatureSpace = isArabicLamWithAlefLigature(source[shapingEnd]) && source[shapingEnd + 1] == ' ';
290        shapingEnd++;
291
292        UErrorCode shapingError = U_ZERO_ERROR;
293        unsigned charsWritten = shapeArabic(source + shapingStart, shapingEnd - shapingStart, dest + shapingStart, shapingEnd - shapingStart, U_SHAPE_LETTERS_SHAPE | U_SHAPE_LENGTH_FIXED_SPACES_NEAR, &shapingError);
294
295        if (U_SUCCESS(shapingError) && charsWritten == shapingEnd - shapingStart) {
296            for (unsigned j = shapingStart; j < shapingEnd - 1; ++j) {
297                if (isArabicLamWithAlefLigature(dest[j]) && dest[j + 1] == ' ')
298                    dest[++j] = zeroWidthSpace;
299            }
300            if (foundLigatureSpace) {
301                dest[shapingEnd] = ' ';
302                shapingEnd++;
303            } else if (isArabicLamWithAlefLigature(dest[shapingEnd - 1])) {
304                // u_shapeArabic quirk: if the last two characters in the source string are a Lam and an Alef,
305                // the space is put at the beginning of the string, despite U_SHAPE_LENGTH_FIXED_SPACES_NEAR.
306                ASSERT(dest[shapingStart] == ' ');
307                dest[shapingStart] = zeroWidthSpace;
308            }
309        } else {
310            // Something went wrong. Abandon shaping and just copy the rest of the buffer.
311            LOG_ERROR("u_shapeArabic failed(%d)", shapingError);
312            shapingEnd = totalLength;
313            memcpy(dest + shapingStart, source + shapingStart, (shapingEnd - shapingStart) * sizeof(UChar));
314        }
315        shapingStart = shapingEnd;
316    }
317}
318
319void ATSULayoutParameters::initialize(const Font* font, const GraphicsContext* graphicsContext)
320{
321    m_font = font;
322
323    const SimpleFontData* fontData = font->primaryFont();
324    m_fonts.set(new const SimpleFontData*[m_run.length()]);
325    if (font->isSmallCaps())
326        m_charBuffer.set(new UChar[m_run.length()]);
327
328    ATSUTextLayout layout;
329    OSStatus status;
330    ATSULayoutOperationOverrideSpecifier overrideSpecifier;
331
332    initializeATSUStyle(fontData);
333
334    // FIXME: This is currently missing the following required features that the CoreGraphics code path has:
335    // - \n, \t, and nonbreaking space render as a space.
336
337    UniCharCount runLength = m_run.length();
338
339    if (m_charBuffer)
340        memcpy(m_charBuffer.get(), m_run.characters(), runLength * sizeof(UChar));
341
342    status = ATSUCreateTextLayoutWithTextPtr(
343            (m_charBuffer ? m_charBuffer.get() : m_run.characters()),
344            0,        // offset
345            runLength,      // length
346            runLength,    // total length
347            1,              // styleRunCount
348            &runLength,     // length of style run
349            &fontData->m_ATSUStyle,
350            &layout);
351    if (status != noErr)
352        LOG_ERROR("ATSUCreateTextLayoutWithTextPtr failed(%d)", status);
353    m_layout = layout;
354    ATSUSetTextLayoutRefCon(m_layout, (URefCon)this);
355
356    // FIXME: There are certain times when this method is called, when we don't have access to a GraphicsContext
357    // measuring text runs with floatWidthForComplexText is one example.
358    // ATSUI requires that we pass a valid CGContextRef to it when specifying kATSUCGContextTag (crashes when passed 0)
359    // ATSUI disables sub-pixel rendering if kATSUCGContextTag is not specified!  So we're in a bind.
360    // Sometimes [[NSGraphicsContext currentContext] graphicsPort] may return the wrong (or no!) context.  Nothing we can do about it (yet).
361    CGContextRef cgContext = graphicsContext ? graphicsContext->platformContext() : (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
362
363    ATSLineLayoutOptions lineLayoutOptions = kATSLineKeepSpacesOutOfMargin | kATSLineHasNoHangers;
364    Boolean rtl = m_run.rtl();
365    overrideSpecifier.operationSelector = kATSULayoutOperationPostLayoutAdjustment;
366    overrideSpecifier.overrideUPP = overrideLayoutOperation;
367    ATSUAttributeTag tags[] = { kATSUCGContextTag, kATSULineLayoutOptionsTag, kATSULineDirectionTag, kATSULayoutOperationOverrideTag };
368    ByteCount sizes[] = { sizeof(CGContextRef), sizeof(ATSLineLayoutOptions), sizeof(Boolean), sizeof(ATSULayoutOperationOverrideSpecifier) };
369    ATSUAttributeValuePtr values[] = { &cgContext, &lineLayoutOptions, &rtl, &overrideSpecifier };
370
371    status = ATSUSetLayoutControls(layout, (m_run.applyWordRounding() ? 4 : 3), tags, sizes, values);
372    if (status != noErr)
373        LOG_ERROR("ATSUSetLayoutControls failed(%d)", status);
374
375    status = ATSUSetTransientFontMatching(layout, YES);
376    if (status != noErr)
377        LOG_ERROR("ATSUSetTransientFontMatching failed(%d)", status);
378
379    m_hasSyntheticBold = false;
380    ATSUFontID ATSUSubstituteFont;
381    UniCharArrayOffset substituteOffset = 0;
382    UniCharCount substituteLength;
383    UniCharArrayOffset lastOffset;
384    const SimpleFontData* substituteFontData = 0;
385
386    while (substituteOffset < runLength) {
387        // FIXME: Using ATSUMatchFontsToText() here results in several problems: the CSS font family list is not necessarily followed for the 2nd
388        // and onwards unmatched characters; segmented fonts do not work correctly; behavior does not match the simple text and Uniscribe code
389        // paths. Change this function to use Font::glyphDataForCharacter() for each character instead.
390        lastOffset = substituteOffset;
391        status = ATSUMatchFontsToText(layout, substituteOffset, kATSUToTextEnd, &ATSUSubstituteFont, &substituteOffset, &substituteLength);
392        if (status == kATSUFontsMatched || status == kATSUFontsNotMatched) {
393            const FontData* fallbackFontData = m_font->fontDataForCharacters(m_run.characters() + substituteOffset, substituteLength);
394            substituteFontData = fallbackFontData ? fallbackFontData->fontDataForCharacter(m_run[0]) : 0;
395            if (substituteFontData) {
396                initializeATSUStyle(substituteFontData);
397                if (substituteFontData->m_ATSUStyle)
398                    ATSUSetRunStyle(layout, substituteFontData->m_ATSUStyle, substituteOffset, substituteLength);
399            } else
400                substituteFontData = fontData;
401        } else {
402            substituteOffset = runLength;
403            substituteLength = 0;
404        }
405
406        bool shapedArabic = false;
407        bool isSmallCap = false;
408        UniCharArrayOffset firstSmallCap = 0;
409        const SimpleFontData *r = fontData;
410        UniCharArrayOffset i;
411        for (i = lastOffset;  ; i++) {
412            if (i == substituteOffset || i == substituteOffset + substituteLength) {
413                if (isSmallCap) {
414                    isSmallCap = false;
415                    initializeATSUStyle(r->smallCapsFontData(m_font->fontDescription()));
416                    ATSUSetRunStyle(layout, r->smallCapsFontData(m_font->fontDescription())->m_ATSUStyle, firstSmallCap, i - firstSmallCap);
417                }
418                if (i == substituteOffset && substituteLength > 0)
419                    r = substituteFontData;
420                else
421                    break;
422            }
423            if (!shapedArabic && WTF::Unicode::isArabicChar(m_run[i]) && !r->shapesArabic()) {
424                shapedArabic = true;
425                if (!m_charBuffer) {
426                    m_charBuffer.set(new UChar[runLength]);
427                    memcpy(m_charBuffer.get(), m_run.characters(), i * sizeof(UChar));
428                    ATSUTextMoved(layout, m_charBuffer.get());
429                }
430                shapeArabic(m_run.characters(), m_charBuffer.get(), runLength, i);
431            }
432            if (m_run.rtl() && !r->m_ATSUMirrors) {
433                UChar mirroredChar = u_charMirror(m_run[i]);
434                if (mirroredChar != m_run[i]) {
435                    if (!m_charBuffer) {
436                        m_charBuffer.set(new UChar[runLength]);
437                        memcpy(m_charBuffer.get(), m_run.characters(), runLength * sizeof(UChar));
438                        ATSUTextMoved(layout, m_charBuffer.get());
439                    }
440                    m_charBuffer[i] = mirroredChar;
441                }
442            }
443            if (m_font->isSmallCaps()) {
444                const SimpleFontData* smallCapsData = r->smallCapsFontData(m_font->fontDescription());
445                UChar c = m_charBuffer[i];
446                UChar newC;
447                if (U_GET_GC_MASK(c) & U_GC_M_MASK)
448                    m_fonts[i] = isSmallCap ? smallCapsData : r;
449                else if (!u_isUUppercase(c) && (newC = u_toupper(c)) != c) {
450                    m_charBuffer[i] = newC;
451                    if (!isSmallCap) {
452                        isSmallCap = true;
453                        firstSmallCap = i;
454                    }
455                    m_fonts[i] = smallCapsData;
456                } else {
457                    if (isSmallCap) {
458                        isSmallCap = false;
459                        initializeATSUStyle(smallCapsData);
460                        ATSUSetRunStyle(layout, smallCapsData->m_ATSUStyle, firstSmallCap, i - firstSmallCap);
461                    }
462                    m_fonts[i] = r;
463                }
464            } else
465                m_fonts[i] = r;
466            if (m_fonts[i]->syntheticBoldOffset())
467                m_hasSyntheticBold = true;
468        }
469        substituteOffset += substituteLength;
470    }
471    if (m_run.padding()) {
472        float numSpaces = 0;
473        unsigned k;
474        for (k = 0; k < runLength; k++)
475            if (Font::treatAsSpace(m_run[k]))
476                numSpaces++;
477
478        if (numSpaces == 0)
479            m_padPerSpace = 0;
480        else
481            m_padPerSpace = ceilf(m_run.padding() / numSpaces);
482    } else
483        m_padPerSpace = 0;
484}
485
486FloatRect Font::selectionRectForComplexText(const TextRun& run, const IntPoint& point, int h, int from, int to) const
487{
488    OwnArrayPtr<UChar> charactersWithOverride;
489    TextRun adjustedRun = copyRunForDirectionalOverrideIfNecessary(run, charactersWithOverride);
490    if (run.directionalOverride()) {
491        from++;
492        to++;
493    }
494
495    ATSULayoutParameters params(adjustedRun);
496    params.initialize(this);
497
498    ATSTrapezoid firstGlyphBounds;
499    ItemCount actualNumBounds;
500
501    OSStatus status = ATSUGetGlyphBounds(params.m_layout, 0, 0, from, to - from, kATSUseFractionalOrigins, 1, &firstGlyphBounds, &actualNumBounds);
502    if (status != noErr || actualNumBounds != 1) {
503        static ATSTrapezoid zeroTrapezoid = { {0, 0}, {0, 0}, {0, 0}, {0, 0} };
504        firstGlyphBounds = zeroTrapezoid;
505    }
506
507    float beforeWidth = MIN(FixedToFloat(firstGlyphBounds.lowerLeft.x), FixedToFloat(firstGlyphBounds.upperLeft.x));
508    float afterWidth = MAX(FixedToFloat(firstGlyphBounds.lowerRight.x), FixedToFloat(firstGlyphBounds.upperRight.x));
509
510    FloatRect rect(point.x() + floorf(beforeWidth), point.y(), roundf(afterWidth) - floorf(beforeWidth), h);
511
512    return rect;
513}
514
515void Font::drawComplexText(GraphicsContext* graphicsContext, const TextRun& run, const FloatPoint& point, int from, int to) const
516{
517    OSStatus status;
518
519    int drawPortionLength = to - from;
520    OwnArrayPtr<UChar> charactersWithOverride;
521    TextRun adjustedRun = copyRunForDirectionalOverrideIfNecessary(run, charactersWithOverride);
522    if (run.directionalOverride())
523        from++;
524
525    ATSULayoutParameters params(adjustedRun);
526    params.initialize(this, graphicsContext);
527
528    // ATSUI can't draw beyond -32768 to +32767 so we translate the CTM and tell ATSUI to draw at (0, 0).
529    CGContextRef context = graphicsContext->platformContext();
530    CGContextTranslateCTM(context, point.x(), point.y());
531
532    IntSize shadowSize;
533    int shadowBlur;
534    Color shadowColor;
535    graphicsContext->getShadow(shadowSize, shadowBlur, shadowColor);
536
537    bool hasSimpleShadow = graphicsContext->textDrawingMode() == cTextFill && shadowColor.isValid() && !shadowBlur;
538    if (hasSimpleShadow) {
539        // Paint simple shadows ourselves instead of relying on CG shadows, to avoid losing subpixel antialiasing.
540        graphicsContext->clearShadow();
541        Color fillColor = graphicsContext->fillColor();
542        Color shadowFillColor(shadowColor.red(), shadowColor.green(), shadowColor.blue(), shadowColor.alpha() * fillColor.alpha() / 255);
543        graphicsContext->setFillColor(shadowFillColor);
544        CGContextTranslateCTM(context, shadowSize.width(), shadowSize.height());
545        status = ATSUDrawText(params.m_layout, from, drawPortionLength, 0, 0);
546        if (status == noErr && params.m_hasSyntheticBold) {
547            // Force relayout for the bold pass
548            ATSUClearLayoutCache(params.m_layout, 0);
549            params.m_syntheticBoldPass = true;
550            status = ATSUDrawText(params.m_layout, from, drawPortionLength, 0, 0);
551            // Force relayout for the next pass
552            ATSUClearLayoutCache(params.m_layout, 0);
553            params.m_syntheticBoldPass = false;
554        }
555        CGContextTranslateCTM(context, -shadowSize.width(), -shadowSize.height());
556        graphicsContext->setFillColor(fillColor);
557    }
558
559    status = ATSUDrawText(params.m_layout, from, drawPortionLength, 0, 0);
560    if (status == noErr && params.m_hasSyntheticBold) {
561        // Force relayout for the bold pass
562        ATSUClearLayoutCache(params.m_layout, 0);
563        params.m_syntheticBoldPass = true;
564        status = ATSUDrawText(params.m_layout, from, drawPortionLength, 0, 0);
565    }
566    CGContextTranslateCTM(context, -point.x(), -point.y());
567
568    if (status != noErr)
569        // Nothing to do but report the error (dev build only).
570        LOG_ERROR("ATSUDrawText() failed(%d)", status);
571
572    if (hasSimpleShadow)
573        graphicsContext->setShadow(shadowSize, shadowBlur, shadowColor);
574}
575
576float Font::floatWidthForComplexText(const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts) const
577{
578    if (run.length() == 0)
579        return 0;
580
581    ATSULayoutParameters params(run, fallbackFonts);
582    params.initialize(this);
583
584    OSStatus status;
585
586    ATSTrapezoid firstGlyphBounds;
587    ItemCount actualNumBounds;
588    status = ATSUGetGlyphBounds(params.m_layout, 0, 0, 0, run.length(), kATSUseFractionalOrigins, 1, &firstGlyphBounds, &actualNumBounds);
589    if (status != noErr)
590        LOG_ERROR("ATSUGetGlyphBounds() failed(%d)", status);
591    if (actualNumBounds != 1)
592        LOG_ERROR("unexpected result from ATSUGetGlyphBounds(): actualNumBounds(%d) != 1", actualNumBounds);
593
594    return MAX(FixedToFloat(firstGlyphBounds.upperRight.x), FixedToFloat(firstGlyphBounds.lowerRight.x)) -
595           MIN(FixedToFloat(firstGlyphBounds.upperLeft.x), FixedToFloat(firstGlyphBounds.lowerLeft.x));
596}
597
598int Font::offsetForPositionForComplexText(const TextRun& run, int x, bool /*includePartialGlyphs*/) const
599{
600    OwnArrayPtr<UChar> charactersWithOverride;
601    TextRun adjustedRun = copyRunForDirectionalOverrideIfNecessary(run, charactersWithOverride);
602
603    ATSULayoutParameters params(adjustedRun);
604    params.initialize(this);
605
606    UniCharArrayOffset primaryOffset = 0;
607
608    // FIXME: No idea how to avoid including partial glyphs.
609    // Not even sure if that's the behavior this yields now.
610    Boolean isLeading;
611    UniCharArrayOffset secondaryOffset = 0;
612    OSStatus status = ATSUPositionToOffset(params.m_layout, FloatToFixed(x), FloatToFixed(-1), &primaryOffset, &isLeading, &secondaryOffset);
613    unsigned offset;
614    if (status == noErr) {
615        offset = (unsigned)primaryOffset;
616        if (run.directionalOverride() && offset > 0)
617            offset--;
618    } else
619        // Failed to find offset!  Return 0 offset.
620        offset = 0;
621
622    return offset;
623}
624
625}
626#endif // USE(ATSUI)
627