FontMac.mm 28.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/**
 * This file is part of the html renderer for KDE.
 *
 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
 *           (C) 2000 Dirk Mueller (mueller@kde.org)
 * Copyright (C) 2003, 2006 Apple Computer, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
ddkilzer's avatar
ddkilzer committed
21 22
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
23 24 25 26 27 28
 *
 */

#import "config.h"
#import "Font.h"

darin's avatar
darin committed
29
#import "BlockExceptions.h"
darin's avatar
darin committed
30
#import "CharacterNames.h"
darin's avatar
darin committed
31
#import "FontData.h"
32
#import "FontFallbackList.h"
darin's avatar
darin committed
33
#import "GlyphBuffer.h"
34 35
#import "GraphicsContext.h"
#import "IntRect.h"
darin's avatar
darin committed
36
#import "Logging.h"
37
#import "FontStyle.h"
38 39
#import "WebCoreSystemInterface.h"
#import "WebCoreTextRenderer.h"
40
#import "ShapeArabic.h"
ap's avatar
ap committed
41

42 43
#define SYNTHETIC_OBLIQUE_ANGLE 14

weinig's avatar
weinig committed
44
#ifdef __LP64__
thatcher's avatar
thatcher committed
45 46 47 48 49
#define URefCon void*
#else
#define URefCon UInt32
#endif

darin's avatar
darin committed
50 51
using namespace std;

52 53
namespace WebCore {

54 55 56 57 58 59
// =================================================================
// Font Class (Platform-Specific Portion)
// =================================================================

struct ATSULayoutParameters
{
60
    ATSULayoutParameters(const TextRun& run, const FontStyle& style)
61 62 63 64 65 66 67 68
        : m_run(run)
        , m_style(style)
        , m_font(0)
        , m_fonts(0)
        , m_charBuffer(0)
        , m_hasSyntheticBold(false)
        , m_syntheticBoldPass(false)
        , m_padPerSpace(0)
69 70
    {}

71
    void initialize(const Font*, const GraphicsContext* = 0);
72

73
    const TextRun& m_run;
74
    const FontStyle& m_style;
75 76 77 78 79 80
    
    const Font* m_font;
    
    ATSUTextLayout m_layout;
    const FontData **m_fonts;
    
81
    UChar *m_charBuffer;
82 83 84 85 86 87
    bool m_hasSyntheticBold;
    bool m_syntheticBoldPass;
    float m_padPerSpace;
};

// Be sure to free the array allocated by this function.
88
static TextRun addDirectionalOverride(const TextRun& run, bool rtl)
89
{
90
    UChar* charactersWithOverride = new UChar[run.length() + 2];
darin's avatar
darin committed
91
    charactersWithOverride[0] = rtl ? rightToLeftOverride : leftToRightOverride;
92
    memcpy(&charactersWithOverride[1], run.data(0), sizeof(UChar) * run.length());
darin's avatar
darin committed
93
    charactersWithOverride[run.length() + 1] = popDirectionalFormatting;
94

95
    return TextRun(charactersWithOverride, run.length() + 2);
96 97 98 99 100 101 102 103 104 105 106 107 108 109
}

static void initializeATSUStyle(const FontData* fontData)
{
    // The two NSFont calls in this method (pointSize and _atsFontID) do not raise exceptions.

    if (!fontData->m_ATSUStyleInitialized) {
        OSStatus status;
        ByteCount propTableSize;
        
        status = ATSUCreateStyle(&fontData->m_ATSUStyle);
        if (status != noErr)
            LOG_ERROR("ATSUCreateStyle failed (%d)", status);
    
oliver's avatar
oliver committed
110
        ATSUFontID fontID = wkGetNSFontATSUFontId(fontData->m_font.font());
111 112
        if (fontID == 0) {
            ATSUDisposeStyle(fontData->m_ATSUStyle);
oliver's avatar
oliver committed
113
            LOG_ERROR("unable to get ATSUFontID for %@", fontData->m_font.font());
114 115 116 117
            return;
        }
        
        CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
118
        if (fontData->m_font.m_syntheticOblique)
119
            transform = CGAffineTransformConcat(transform, CGAffineTransformMake(1, 0, -tanf(SYNTHETIC_OBLIQUE_ANGLE * acosf(0) / 90), 1, 0, 0)); 
oliver's avatar
oliver committed
120
        Fixed fontSize = FloatToFixed([fontData->m_font.font() pointSize]);
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
        // Turn off automatic kerning until it is supported in the CG code path (6136 in bugzilla)
        Fract kerningInhibitFactor = FloatToFract(1.0);
        ATSUAttributeTag styleTags[4] = { kATSUSizeTag, kATSUFontTag, kATSUFontMatrixTag, kATSUKerningInhibitFactorTag };
        ByteCount styleSizes[4] = { sizeof(Fixed), sizeof(ATSUFontID), sizeof(CGAffineTransform), sizeof(Fract) };
        ATSUAttributeValuePtr styleValues[4] = { &fontSize, &fontID, &transform, &kerningInhibitFactor };
        status = ATSUSetAttributes(fontData->m_ATSUStyle, 4, styleTags, styleSizes, styleValues);
        if (status != noErr)
            LOG_ERROR("ATSUSetAttributes failed (%d)", status);
        status = ATSFontGetTable(fontID, 'prop', 0, 0, 0, &propTableSize);
        if (status == noErr)    // naively assume that if a 'prop' table exists then it contains mirroring info
            fontData->m_ATSUMirrors = true;
        else if (status == kATSInvalidFontTableAccess)
            fontData->m_ATSUMirrors = false;
        else
            LOG_ERROR("ATSFontGetTable failed (%d)", status);

        // Turn off ligatures such as 'fi' to match the CG code path's behavior, until bugzilla 6135 is fixed.
        // Don't be too aggressive: if the font doesn't contain 'a', then assume that any ligatures it contains are
        // in characters that always go through ATSUI, and therefore allow them. Geeza Pro is an example.
        // See bugzilla 5166.
oliver's avatar
oliver committed
141
        if ([[fontData->m_font.font() coveredCharacterSet] characterIsMember:'a']) {
142 143 144 145 146 147 148 149 150
            ATSUFontFeatureType featureTypes[] = { kLigaturesType };
            ATSUFontFeatureSelector featureSelectors[] = { kCommonLigaturesOffSelector };
            status = ATSUSetFontFeatures(fontData->m_ATSUStyle, 1, featureTypes, featureSelectors);
        }

        fontData->m_ATSUStyleInitialized = true;
    }
}

thatcher's avatar
thatcher committed
151
static OSStatus overrideLayoutOperation(ATSULayoutOperationSelector iCurrentOperation, ATSULineRef iLineRef, URefCon iRefCon,
152 153 154 155 156 157 158
                                        void *iOperationCallbackParameterPtr, ATSULayoutOperationCallbackStatus *oCallbackStatus)
{
    ATSULayoutParameters *params = (ATSULayoutParameters *)iRefCon;
    OSStatus status;
    ItemCount count;
    ATSLayoutRecord *layoutRecords;

159
    if (params->m_style.applyWordRounding()) {
160 161 162 163 164 165 166 167
        status = ATSUDirectGetLayoutDataArrayPtrFromLineRef(iLineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, true, (void **)&layoutRecords, &count);
        if (status != noErr) {
            *oCallbackStatus = kATSULayoutOperationCallbackStatusContinue;
            return status;
        }
        
        Fixed lastNativePos = 0;
        float lastAdjustedPos = 0;
168 169
        const UChar* characters = params->m_charBuffer ? params->m_charBuffer : params->m_run.characters();
        const FontData **renderers = params->m_fonts;
170 171
        const FontData *renderer;
        const FontData *lastRenderer = 0;
172
        UChar ch, nextCh;
173
        ByteCount offset = layoutRecords[0].originalOffset;
174
        nextCh = *(UChar *)(((char *)characters)+offset);
175 176 177 178
        bool shouldRound = false;
        bool syntheticBoldPass = params->m_syntheticBoldPass;
        Fixed syntheticBoldOffset = 0;
        ATSGlyphRef spaceGlyph = 0;
179 180
        bool hasExtraSpacing = params->m_font->letterSpacing() || params->m_font->wordSpacing() | params->m_style.padding();
        float padding = params->m_style.padding();
181 182 183 184 185 186 187 188 189
        // In the CoreGraphics code path, the rounding hack is applied in logical order.
        // Here it is applied in visual left-to-right order, which may be better.
        ItemCount lastRoundingChar = 0;
        ItemCount i;
        for (i = 1; i < count; i++) {
            bool isLastChar = i == count - 1;
            renderer = renderers[offset / 2];
            if (renderer != lastRenderer) {
                lastRenderer = renderer;
190
                spaceGlyph = renderer->m_spaceGlyph;
191 192 193
                // The CoreGraphics interpretation of NSFontAntialiasedIntegerAdvancementsRenderingMode seems
                // to be "round each glyph's width to the nearest integer". This is not the same as ATSUI
                // does in any of its device-metrics modes.
oliver's avatar
oliver committed
194
                shouldRound = [renderer->m_font.font() renderingMode] == NSFontAntialiasedIntegerAdvancementsRenderingMode;
195
                if (syntheticBoldPass)
196 197
                    syntheticBoldOffset = FloatToFixed(renderer->m_syntheticBoldOffset);
            }
ap's avatar
ap committed
198
            float width;
199
            if (nextCh == zeroWidthSpace || Font::treatAsZeroWidthSpace(nextCh) && !Font::treatAsSpace(nextCh)) {
ap's avatar
ap committed
200
                width = 0;
201 202
                layoutRecords[i-1].glyphID = spaceGlyph;
            } else {
ap's avatar
ap committed
203 204 205 206 207 208 209
                width = FixedToFloat(layoutRecords[i].realPos - lastNativePos);
                if (shouldRound)
                    width = roundf(width);
                width += renderer->m_syntheticBoldOffset;
                if (renderer->m_treatAsFixedPitch ? width == renderer->m_spaceWidth : (layoutRecords[i-1].flags & kATSGlyphInfoIsWhiteSpace))
                    width = renderer->m_adjustedSpaceWidth;
            }
210 211 212 213 214
            lastNativePos = layoutRecords[i].realPos;

            if (hasExtraSpacing) {
                if (width && params->m_font->letterSpacing())
                    width +=params->m_font->letterSpacing();
215
                if (Font::treatAsSpace(nextCh)) {
216
                    if (params->m_style.padding()) {
217 218 219 220 221 222 223 224
                        if (padding < params->m_padPerSpace) {
                            width += padding;
                            padding = 0;
                        } else {
                            width += params->m_padPerSpace;
                            padding -= params->m_padPerSpace;
                        }
                    }
225
                    if (offset != 0 && !Font::treatAsSpace(*((UChar *)(((char *)characters)+offset) - 1)) && params->m_font->wordSpacing())
226 227 228 229 230 231 232 233
                        width += params->m_font->wordSpacing();
                }
            }

            ch = nextCh;
            offset = layoutRecords[i].originalOffset;
            // Use space for nextCh at the end of the loop so that we get inside the rounding hack code.
            // We won't actually round unless the other conditions are satisfied.
234
            nextCh = isLastChar ? ' ' : *(UChar *)(((char *)characters)+offset);
235

236
            if (Font::isRoundingHackCharacter(ch))
237 238
                width = ceilf(width);
            lastAdjustedPos = lastAdjustedPos + width;
239
            if (Font::isRoundingHackCharacter(nextCh) && (!isLastChar || params->m_style.applyRunRounding())){
240
                if (params->m_style.ltr())
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
                    lastAdjustedPos = ceilf(lastAdjustedPos);
                else {
                    float roundingWidth = ceilf(lastAdjustedPos) - lastAdjustedPos;
                    Fixed rw = FloatToFixed(roundingWidth);
                    ItemCount j;
                    for (j = lastRoundingChar; j < i; j++)
                        layoutRecords[j].realPos += rw;
                    lastRoundingChar = i;
                    lastAdjustedPos += roundingWidth;
                }
            }
            if (syntheticBoldPass) {
                if (syntheticBoldOffset)
                    layoutRecords[i-1].realPos += syntheticBoldOffset;
                else
                    layoutRecords[i-1].glyphID = spaceGlyph;
            }
            layoutRecords[i].realPos = FloatToFixed(lastAdjustedPos);
        }
        
        status = ATSUDirectReleaseLayoutDataArrayPtr(iLineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, (void **)&layoutRecords);
    }
    *oCallbackStatus = kATSULayoutOperationCallbackStatusHandled;
    return noErr;
}

ap's avatar
ap committed
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
static inline bool isArabicLamWithAlefLigature(UChar c)
{
    return c >= 0xfef5 && c <= 0xfefc;
}

static void shapeArabic(const UChar* source, UChar* dest, unsigned totalLength, unsigned shapingStart)
{
    while (shapingStart < totalLength) {
        unsigned shapingEnd;
        // We do not want to pass a Lam with Alef ligature followed by a space to the shaper,
        // since we want to be able to identify this sequence as the result of shaping a Lam
        // followed by an Alef and padding with a space.
        bool foundLigatureSpace = false;
        for (shapingEnd = shapingStart; !foundLigatureSpace && shapingEnd < totalLength - 1; ++shapingEnd)
            foundLigatureSpace = isArabicLamWithAlefLigature(source[shapingEnd]) && source[shapingEnd + 1] == ' ';
        shapingEnd++;

        UErrorCode shapingError = U_ZERO_ERROR;
285
        unsigned charsWritten = shapeArabic(source + shapingStart, shapingEnd - shapingStart, dest + shapingStart, shapingEnd - shapingStart, U_SHAPE_LETTERS_SHAPE | U_SHAPE_LENGTH_FIXED_SPACES_NEAR, &shapingError);
ap's avatar
ap committed
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310

        if (U_SUCCESS(shapingError) && charsWritten == shapingEnd - shapingStart) {
            for (unsigned j = shapingStart; j < shapingEnd - 1; ++j) {
                if (isArabicLamWithAlefLigature(dest[j]) && dest[j + 1] == ' ')
                    dest[++j] = zeroWidthSpace;
            }
            if (foundLigatureSpace) {
                dest[shapingEnd] = ' ';
                shapingEnd++;
            } else if (isArabicLamWithAlefLigature(dest[shapingEnd - 1])) {
                // u_shapeArabic quirk: if the last two characters in the source string are a Lam and an Alef,
                // the space is put at the beginning of the string, despite U_SHAPE_LENGTH_FIXED_SPACES_NEAR.
                ASSERT(dest[shapingStart] == ' ');
                dest[shapingStart] = zeroWidthSpace;
            }
        } else {
            // Something went wrong. Abandon shaping and just copy the rest of the buffer.
            LOG_ERROR("u_shapeArabic failed(%d)", shapingError);
            shapingEnd = totalLength;
            memcpy(dest + shapingStart, source + shapingStart, (shapingEnd - shapingStart) * sizeof(UChar));
        }
        shapingStart = shapingEnd;
    }
}

311
void ATSULayoutParameters::initialize(const Font* font, const GraphicsContext* graphicsContext)
312 313
{
    m_font = font;
314
    
315
    const FontData* fontData = font->primaryFont();
316
    m_fonts = new const FontData*[m_run.length()];
ap's avatar
ap committed
317
    m_charBuffer = font->isSmallCaps() ? new UChar[m_run.length()] : 0;
318 319 320 321 322 323 324 325 326 327
    
    ATSUTextLayout layout;
    OSStatus status;
    ATSULayoutOperationOverrideSpecifier overrideSpecifier;
    
    initializeATSUStyle(fontData);
    
    // FIXME: This is currently missing the following required features that the CoreGraphics code path has:
    // - \n, \t, and nonbreaking space render as a space.

328 329
    UniCharCount runLength = m_run.length();
     
330
    if (m_charBuffer)
331
        memcpy(m_charBuffer, m_run.characters(), runLength * sizeof(UChar));
332 333
    
    status = ATSUCreateTextLayoutWithTextPtr(
334
            (m_charBuffer ? m_charBuffer : m_run.characters()),
335
            0,        // offset
336
            runLength,      // length
337
            runLength,    // total length
338 339 340 341 342 343 344
            1,              // styleRunCount
            &runLength,     // length of style run
            &fontData->m_ATSUStyle, 
            &layout);
    if (status != noErr)
        LOG_ERROR("ATSUCreateTextLayoutWithTextPtr failed(%d)", status);
    m_layout = layout;
thatcher's avatar
thatcher committed
345
    ATSUSetTextLayoutRefCon(m_layout, (URefCon)this);
346

347 348 349 350 351 352 353
    // FIXME: There are certain times when this method is called, when we don't have access to a GraphicsContext
    // measuring text runs with floatWidthForComplexText is one example.
    // ATSUI requires that we pass a valid CGContextRef to it when specifying kATSUCGContextTag (crashes when passed 0)
    // ATSUI disables sub-pixel rendering if kATSUCGContextTag is not specified!  So we're in a bind.
    // Sometimes [[NSGraphicsContext currentContext] graphicsPort] may return the wrong (or no!) context.  Nothing we can do about it (yet).
    CGContextRef cgContext = graphicsContext ? graphicsContext->platformContext() : (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
    
354
    ATSLineLayoutOptions lineLayoutOptions = kATSLineKeepSpacesOutOfMargin | kATSLineHasNoHangers;
355
    Boolean rtl = m_style.rtl();
356 357 358 359 360 361
    overrideSpecifier.operationSelector = kATSULayoutOperationPostLayoutAdjustment;
    overrideSpecifier.overrideUPP = overrideLayoutOperation;
    ATSUAttributeTag tags[] = { kATSUCGContextTag, kATSULineLayoutOptionsTag, kATSULineDirectionTag, kATSULayoutOperationOverrideTag };
    ByteCount sizes[] = { sizeof(CGContextRef), sizeof(ATSLineLayoutOptions), sizeof(Boolean), sizeof(ATSULayoutOperationOverrideSpecifier) };
    ATSUAttributeValuePtr values[] = { &cgContext, &lineLayoutOptions, &rtl, &overrideSpecifier };
    
362
    status = ATSUSetLayoutControls(layout, (m_style.applyWordRounding() ? 4 : 3), tags, sizes, values);
363 364 365 366 367 368 369 370 371
    if (status != noErr)
        LOG_ERROR("ATSUSetLayoutControls failed(%d)", status);

    status = ATSUSetTransientFontMatching(layout, YES);
    if (status != noErr)
        LOG_ERROR("ATSUSetTransientFontMatching failed(%d)", status);

    m_hasSyntheticBold = false;
    ATSUFontID ATSUSubstituteFont;
372
    UniCharArrayOffset substituteOffset = 0;
373 374 375 376
    UniCharCount substituteLength;
    UniCharArrayOffset lastOffset;
    const FontData* substituteFontData = 0;

377
    while (substituteOffset < runLength) {
378 379 380
        lastOffset = substituteOffset;
        status = ATSUMatchFontsToText(layout, substituteOffset, kATSUToTextEnd, &ATSUSubstituteFont, &substituteOffset, &substituteLength);
        if (status == kATSUFontsMatched || status == kATSUFontsNotMatched) {
381
            substituteFontData = m_font->fontDataForCharacters(m_run.characters() + substituteOffset, substituteLength);
382 383 384 385 386 387 388
            if (substituteFontData) {
                initializeATSUStyle(substituteFontData);
                if (substituteFontData->m_ATSUStyle)
                    ATSUSetRunStyle(layout, substituteFontData->m_ATSUStyle, substituteOffset, substituteLength);
            } else
                substituteFontData = fontData;
        } else {
389
            substituteOffset = runLength;
390 391 392
            substituteLength = 0;
        }

ap's avatar
ap committed
393
        bool shapedArabic = false;
394 395 396 397 398 399 400 401
        bool isSmallCap = false;
        UniCharArrayOffset firstSmallCap = 0;
        const FontData *r = fontData;
        UniCharArrayOffset i;
        for (i = lastOffset;  ; i++) {
            if (i == substituteOffset || i == substituteOffset + substituteLength) {
                if (isSmallCap) {
                    isSmallCap = false;
402 403
                    initializeATSUStyle(r->smallCapsFontData(m_font->fontDescription()));
                    ATSUSetRunStyle(layout, r->smallCapsFontData(m_font->fontDescription())->m_ATSUStyle, firstSmallCap, i - firstSmallCap);
404 405 406 407 408 409
                }
                if (i == substituteOffset && substituteLength > 0)
                    r = substituteFontData;
                else
                    break;
            }
ap's avatar
ap committed
410 411 412
            if (!shapedArabic && ublock_getCode(m_run[i]) == UBLOCK_ARABIC && !r->shapesArabic()) {
                shapedArabic = true;
                if (!m_charBuffer) {
413
                    m_charBuffer = new UChar[runLength];
ap's avatar
ap committed
414 415 416
                    memcpy(m_charBuffer, m_run.characters(), i * sizeof(UChar));
                    ATSUTextMoved(layout, m_charBuffer);
                }
417
                shapeArabic(m_run.characters(), m_charBuffer, runLength, i);
ap's avatar
ap committed
418 419 420 421 422
            }
            if (m_style.rtl() && !r->m_ATSUMirrors) {
                UChar mirroredChar = u_charMirror(m_run[i]);
                if (mirroredChar != m_run[i]) {
                    if (!m_charBuffer) {
423 424
                        m_charBuffer = new UChar[runLength];
                        memcpy(m_charBuffer, m_run.characters(), runLength * sizeof(UChar));
ap's avatar
ap committed
425 426 427 428 429
                        ATSUTextMoved(layout, m_charBuffer);
                    }
                    m_charBuffer[i] = mirroredChar;
                }
            }
430
            if (m_font->isSmallCaps()) {
431
                const FontData* smallCapsData = r->smallCapsFontData(m_font->fontDescription());
432 433
                UChar c = m_charBuffer[i];
                UChar newC;
434
                if (U_GET_GC_MASK(c) & U_GC_M_MASK)
435
                    m_fonts[i] = isSmallCap ? smallCapsData : r;
436 437 438 439 440 441
                else if (!u_isUUppercase(c) && (newC = u_toupper(c)) != c) {
                    m_charBuffer[i] = newC;
                    if (!isSmallCap) {
                        isSmallCap = true;
                        firstSmallCap = i;
                    }
442
                    m_fonts[i] = smallCapsData;
443 444 445
                } else {
                    if (isSmallCap) {
                        isSmallCap = false;
446 447
                        initializeATSUStyle(smallCapsData);
                        ATSUSetRunStyle(layout, smallCapsData->m_ATSUStyle, firstSmallCap, i - firstSmallCap);
448 449 450 451 452 453 454 455 456 457
                    }
                    m_fonts[i] = r;
                }
            } else
                m_fonts[i] = r;
            if (m_fonts[i]->m_syntheticBoldOffset)
                m_hasSyntheticBold = true;
        }
        substituteOffset += substituteLength;
    }
458
    if (m_style.padding()) {
459 460
        float numSpaces = 0;
        unsigned k;
461
        for (k = 0; k < runLength; k++)
462
            if (Font::treatAsSpace(m_run[k]))
463 464
                numSpaces++;

465 466 467 468
        if (numSpaces == 0)
            m_padPerSpace = 0;
        else
            m_padPerSpace = ceilf(m_style.padding() / numSpaces);
469 470 471 472 473 474 475 476 477 478 479
    } else
        m_padPerSpace = 0;
}

static void disposeATSULayoutParameters(ATSULayoutParameters *params)
{
    ATSUDisposeTextLayout(params->m_layout);
    delete []params->m_charBuffer;
    delete []params->m_fonts;
}

480
FloatRect Font::selectionRectForComplexText(const TextRun& run, const FontStyle& style, const IntPoint& point, int h, int from, int to) const
481 482
{        
    TextRun adjustedRun = style.directionalOverride() ? addDirectionalOverride(run, style.rtl()) : run;
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509
    if (style.directionalOverride()) {
        from++;
        to++;
    }

    ATSULayoutParameters params(adjustedRun, style);
    params.initialize(this);

    ATSTrapezoid firstGlyphBounds;
    ItemCount actualNumBounds;
    
    OSStatus status = ATSUGetGlyphBounds(params.m_layout, 0, 0, from, to - from, kATSUseFractionalOrigins, 1, &firstGlyphBounds, &actualNumBounds);
    if (status != noErr || actualNumBounds != 1) {
        static ATSTrapezoid zeroTrapezoid = { {0, 0}, {0, 0}, {0, 0}, {0, 0} };
        firstGlyphBounds = zeroTrapezoid;
    }
    disposeATSULayoutParameters(&params);
    
    float beforeWidth = MIN(FixedToFloat(firstGlyphBounds.lowerLeft.x), FixedToFloat(firstGlyphBounds.upperLeft.x));
    float afterWidth = MAX(FixedToFloat(firstGlyphBounds.lowerRight.x), FixedToFloat(firstGlyphBounds.upperRight.x));
    
    FloatRect rect(point.x() + floorf(beforeWidth), point.y(), roundf(afterWidth) - floorf(beforeWidth), h);

    if (style.directionalOverride())
        delete []adjustedRun.characters();

    return rect;
510
}
511

512
void Font::drawComplexText(GraphicsContext* graphicsContext, const TextRun& run, const FontStyle& style, const FloatPoint& point, int from, int to) const
513
{
514
    OSStatus status;
515
    
516
    int drawPortionLength = to - from;
517
    TextRun adjustedRun = style.directionalOverride() ? addDirectionalOverride(run, style.rtl()) : run;
518 519 520 521
    if (style.directionalOverride())
        from++;

    ATSULayoutParameters params(TextRun(adjustedRun.characters(), adjustedRun.length()), style);
522 523
    params.initialize(this, graphicsContext);
    
524
    // ATSUI can't draw beyond -32768 to +32767 so we translate the CTM and tell ATSUI to draw at (0, 0).
525
    CGContextRef context = graphicsContext->platformContext();
beidson's avatar
beidson committed
526

527
    CGContextTranslateCTM(context, point.x(), point.y());
528
    status = ATSUDrawText(params.m_layout, from, drawPortionLength, 0, 0);
529 530 531 532
    if (status == noErr && params.m_hasSyntheticBold) {
        // Force relayout for the bold pass
        ATSUClearLayoutCache(params.m_layout, 0);
        params.m_syntheticBoldPass = true;
533
        status = ATSUDrawText(params.m_layout, from, drawPortionLength, 0, 0);
534 535 536
    }
    CGContextTranslateCTM(context, -point.x(), -point.y());

537
    if (status != noErr)
538 539 540 541 542
        // Nothing to do but report the error (dev build only).
        LOG_ERROR("ATSUDrawText() failed(%d)", status);

    disposeATSULayoutParameters(&params);
    
543
    if (style.directionalOverride())
544
        delete []adjustedRun.characters();
545 546
}

547
float Font::floatWidthForComplexText(const TextRun& run, const FontStyle& style) const
548
{
549
    if (run.length() == 0)
darin's avatar
darin committed
550 551
        return 0;

552
    ATSULayoutParameters params(run, style);
553
    params.initialize(this);
554
    
555 556 557 558
    OSStatus status;
    
    ATSTrapezoid firstGlyphBounds;
    ItemCount actualNumBounds;
559
    status = ATSUGetGlyphBounds(params.m_layout, 0, 0, 0, run.length(), kATSUseFractionalOrigins, 1, &firstGlyphBounds, &actualNumBounds);    
560 561 562 563
    if (status != noErr)
        LOG_ERROR("ATSUGetGlyphBounds() failed(%d)", status);
    if (actualNumBounds != 1)
        LOG_ERROR("unexpected result from ATSUGetGlyphBounds(): actualNumBounds(%d) != 1", actualNumBounds);
564

565 566 567 568
    disposeATSULayoutParameters(&params);

    return MAX(FixedToFloat(firstGlyphBounds.upperRight.x), FixedToFloat(firstGlyphBounds.lowerRight.x)) -
           MIN(FixedToFloat(firstGlyphBounds.upperLeft.x), FixedToFloat(firstGlyphBounds.lowerLeft.x));
569 570
}

571
int Font::offsetForPositionForComplexText(const TextRun& run, const FontStyle& style, int x, bool includePartialGlyphs) const
572
{
573 574 575 576 577
    TextRun adjustedRun = style.directionalOverride() ? addDirectionalOverride(run, style.rtl()) : run;
    
    ATSULayoutParameters params(adjustedRun, style);
    params.initialize(this);

578
    UniCharArrayOffset primaryOffset = 0;
579
    
580 581 582 583 584 585
    // FIXME: No idea how to avoid including partial glyphs.
    // Not even sure if that's the behavior this yields now.
    Boolean isLeading;
    UniCharArrayOffset secondaryOffset = 0;
    OSStatus status = ATSUPositionToOffset(params.m_layout, FloatToFixed(x), FloatToFixed(-1), &primaryOffset, &isLeading, &secondaryOffset);
    unsigned offset;
586
    if (status == noErr) {
587
        offset = (unsigned)primaryOffset;
588 589 590
        if (style.directionalOverride() && offset > 0)
            offset--;
    } else
591 592 593 594
        // Failed to find offset!  Return 0 offset.
        offset = 0;

    disposeATSULayoutParameters(&params);
595
    
596 597 598
    if (style.directionalOverride())
        delete []adjustedRun.characters();

599
    return offset;
600 601
}

602
void Font::drawGlyphs(GraphicsContext* context, const FontData* font, const GlyphBuffer& glyphBuffer, int from, int numGlyphs, const FloatPoint& point) const
603
{
604
    CGContextRef cgContext = context->platformContext();
605 606

    bool originalShouldUseFontSmoothing = wkCGContextGetShouldSmoothFonts(cgContext);
607 608 609 610
    bool newShouldUseFontSmoothing = WebCoreShouldUseFontSmoothing();
    
    if (originalShouldUseFontSmoothing != newShouldUseFontSmoothing)
        CGContextSetShouldSmoothFonts(cgContext, newShouldUseFontSmoothing);
611 612 613
    
    const FontPlatformData& platformData = font->platformData();
    NSFont* drawFont;
614
    if (!isPrinterFont()) {
oliver's avatar
oliver committed
615 616
        drawFont = [platformData.font() screenFont];
        if (drawFont != platformData.font())
617 618
            // We are getting this in too many places (3406411); use ERROR so it only prints on debug versions for now. (We should debug this also, eventually).
            LOG_ERROR("Attempting to set non-screen font (%@) when drawing to screen.  Using screen font anyway, may result in incorrect metrics.",
oliver's avatar
oliver committed
619
                [[[platformData.font() fontDescriptor] fontAttributes] objectForKey:NSFontNameAttribute]);
620
    } else {
oliver's avatar
oliver committed
621 622
        drawFont = [platformData.font() printerFont];
        if (drawFont != platformData.font())
623
            NSLog(@"Attempting to set non-printer font (%@) when printing.  Using printer font anyway, may result in incorrect metrics.",
oliver's avatar
oliver committed
624
                [[[platformData.font() fontDescriptor] fontAttributes] objectForKey:NSFontNameAttribute]);
625 626
    }
    
627
    CGContextSetFont(cgContext, platformData.m_cgFont);
628

629 630 631
    CGAffineTransform matrix = CGAffineTransformIdentity;
    if (drawFont)
        memcpy(&matrix, [drawFont matrix], sizeof(matrix));
632 633
    matrix.b = -matrix.b;
    matrix.d = -matrix.d;
634
    if (platformData.m_syntheticOblique)
635 636 637
        matrix = CGAffineTransformConcat(matrix, CGAffineTransformMake(1, 0, -tanf(SYNTHETIC_OBLIQUE_ANGLE * acosf(0) / 90), 1, 0, 0)); 
    CGContextSetTextMatrix(cgContext, matrix);

638 639 640 641 642 643
    if (drawFont) {
        wkSetCGFontRenderingMode(cgContext, drawFont);
        CGContextSetFontSize(cgContext, 1.0f);
    } else
        CGContextSetFontSize(cgContext, platformData.m_size);
    
644 645 646 647 648 649 650
    CGContextSetTextPosition(cgContext, point.x(), point.y());
    CGContextShowGlyphsWithAdvances(cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs);
    if (font->m_syntheticBoldOffset) {
        CGContextSetTextPosition(cgContext, point.x() + font->m_syntheticBoldOffset, point.y());
        CGContextShowGlyphsWithAdvances(cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs);
    }

651 652
    if (originalShouldUseFontSmoothing != newShouldUseFontSmoothing)
        CGContextSetShouldSmoothFonts(cgContext, originalShouldUseFontSmoothing);
653 654
}

655
}