Home | History | Annotate | Download | only in mac
      1 /*
      2  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      3  *           (C) 1999 Antti Koivisto (koivisto (at) kde.org)
      4  *           (C) 2000 Dirk Mueller (mueller (at) kde.org)
      5  * Copyright (C) 2003, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
      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 #include "config.h"
     24 #include "ComplexTextController.h"
     25 
     26 #if USE(ATSUI)
     27 
     28 #include "Font.h"
     29 #include "ShapeArabic.h"
     30 #include "TextRun.h"
     31 #include <wtf/unicode/CharacterNames.h>
     32 
     33 #ifdef __LP64__
     34 // ATSUTextInserted() is SPI in 64-bit.
     35 extern "C" {
     36 OSStatus ATSUTextInserted(ATSUTextLayout iTextLayout,  UniCharArrayOffset iInsertionLocation, UniCharCount iInsertionLength);
     37 }
     38 #endif
     39 
     40 using namespace WTF::Unicode;
     41 
     42 namespace WebCore {
     43 
     44 OSStatus ComplexTextController::ComplexTextRun::overrideLayoutOperation(ATSULayoutOperationSelector, ATSULineRef atsuLineRef, URefCon refCon, void*, ATSULayoutOperationCallbackStatus* callbackStatus)
     45 {
     46     ComplexTextRun* complexTextRun = reinterpret_cast<ComplexTextRun*>(refCon);
     47     OSStatus status;
     48     ItemCount count;
     49     ATSLayoutRecord* layoutRecords;
     50 
     51     status = ATSUDirectGetLayoutDataArrayPtrFromLineRef(atsuLineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, true, reinterpret_cast<void**>(&layoutRecords), &count);
     52     if (status != noErr) {
     53         *callbackStatus = kATSULayoutOperationCallbackStatusContinue;
     54         return status;
     55     }
     56 
     57     count--;
     58     ItemCount j = 0;
     59     CFIndex indexOffset = 0;
     60 
     61     if (complexTextRun->m_directionalOverride) {
     62         j++;
     63         count -= 2;
     64         indexOffset = -1;
     65     }
     66 
     67     complexTextRun->m_glyphCount = count;
     68     complexTextRun->m_glyphsVector.reserveCapacity(count);
     69     complexTextRun->m_advancesVector.reserveCapacity(count);
     70     complexTextRun->m_atsuiIndices.reserveCapacity(count);
     71 
     72     bool atBeginning = true;
     73     CGFloat lastX = 0;
     74 
     75     for (ItemCount i = 0; i < count; ++i, ++j) {
     76         if (layoutRecords[j].glyphID == kATSDeletedGlyphcode) {
     77             complexTextRun->m_glyphCount--;
     78             continue;
     79         }
     80         complexTextRun->m_glyphsVector.uncheckedAppend(layoutRecords[j].glyphID);
     81         complexTextRun->m_atsuiIndices.uncheckedAppend(layoutRecords[j].originalOffset / 2 + indexOffset);
     82         CGFloat x = FixedToFloat(layoutRecords[j].realPos);
     83         if (!atBeginning)
     84             complexTextRun->m_advancesVector.uncheckedAppend(CGSizeMake(x - lastX, 0));
     85         lastX = x;
     86         atBeginning = false;
     87     }
     88 
     89     complexTextRun->m_advancesVector.uncheckedAppend(CGSizeMake(FixedToFloat(layoutRecords[j].realPos) - lastX, 0));
     90 
     91     complexTextRun->m_glyphs = complexTextRun->m_glyphsVector.data();
     92     complexTextRun->m_advances = complexTextRun->m_advancesVector.data();
     93 
     94     status = ATSUDirectReleaseLayoutDataArrayPtr(atsuLineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, reinterpret_cast<void**>(&layoutRecords));
     95     *callbackStatus = kATSULayoutOperationCallbackStatusContinue;
     96     return noErr;
     97 }
     98 
     99 static inline bool isArabicLamWithAlefLigature(UChar c)
    100 {
    101     return c >= 0xfef5 && c <= 0xfefc;
    102 }
    103 
    104 static void shapeArabic(const UChar* source, UChar* dest, unsigned totalLength)
    105 {
    106     unsigned shapingStart = 0;
    107     while (shapingStart < totalLength) {
    108         unsigned shapingEnd;
    109         // We do not want to pass a Lam with Alef ligature followed by a space to the shaper,
    110         // since we want to be able to identify this sequence as the result of shaping a Lam
    111         // followed by an Alef and padding with a space.
    112         bool foundLigatureSpace = false;
    113         for (shapingEnd = shapingStart; !foundLigatureSpace && shapingEnd < totalLength - 1; ++shapingEnd)
    114             foundLigatureSpace = isArabicLamWithAlefLigature(source[shapingEnd]) && source[shapingEnd + 1] == ' ';
    115         shapingEnd++;
    116 
    117         UErrorCode shapingError = U_ZERO_ERROR;
    118         unsigned charsWritten = shapeArabic(source + shapingStart, shapingEnd - shapingStart, dest + shapingStart, shapingEnd - shapingStart, U_SHAPE_LETTERS_SHAPE | U_SHAPE_LENGTH_FIXED_SPACES_NEAR, &shapingError);
    119 
    120         if (U_SUCCESS(shapingError) && charsWritten == shapingEnd - shapingStart) {
    121             for (unsigned j = shapingStart; j < shapingEnd - 1; ++j) {
    122                 if (isArabicLamWithAlefLigature(dest[j]) && dest[j + 1] == ' ')
    123                     dest[++j] = zeroWidthSpace;
    124             }
    125             if (foundLigatureSpace) {
    126                 dest[shapingEnd] = ' ';
    127                 shapingEnd++;
    128             } else if (isArabicLamWithAlefLigature(dest[shapingEnd - 1])) {
    129                 // u_shapeArabic quirk: if the last two characters in the source string are a Lam and an Alef,
    130                 // the space is put at the beginning of the string, despite U_SHAPE_LENGTH_FIXED_SPACES_NEAR.
    131                 ASSERT(dest[shapingStart] == ' ');
    132                 dest[shapingStart] = zeroWidthSpace;
    133             }
    134         } else {
    135             // Something went wrong. Abandon shaping and just copy the rest of the buffer.
    136             LOG_ERROR("u_shapeArabic failed(%d)", shapingError);
    137             shapingEnd = totalLength;
    138             memcpy(dest + shapingStart, source + shapingStart, (shapingEnd - shapingStart) * sizeof(UChar));
    139         }
    140         shapingStart = shapingEnd;
    141     }
    142 }
    143 
    144 ComplexTextController::ComplexTextRun::ComplexTextRun(ATSUTextLayout atsuTextLayout, const SimpleFontData* fontData, const UChar* characters, unsigned stringLocation, size_t stringLength, bool ltr, bool directionalOverride)
    145     : m_fontData(fontData)
    146     , m_characters(characters)
    147     , m_stringLocation(stringLocation)
    148     , m_stringLength(stringLength)
    149     , m_indexEnd(stringLength)
    150     , m_directionalOverride(directionalOverride)
    151     , m_isMonotonic(true)
    152 {
    153     OSStatus status;
    154 
    155     status = ATSUSetTextLayoutRefCon(atsuTextLayout, reinterpret_cast<URefCon>(this));
    156 
    157     ATSLineLayoutOptions lineLayoutOptions = kATSLineKeepSpacesOutOfMargin | kATSLineHasNoHangers;
    158 
    159     Boolean rtl = !ltr;
    160 
    161     Vector<UChar, 256> substituteCharacters;
    162     bool shouldCheckForMirroring = !ltr && !fontData->m_ATSUMirrors;
    163     bool shouldCheckForArabic = !fontData->shapesArabic();
    164     bool shouldShapeArabic = false;
    165 
    166     bool mirrored = false;
    167     for (size_t i = 0; i < stringLength; ++i) {
    168         if (shouldCheckForMirroring) {
    169             UChar mirroredChar = u_charMirror(characters[i]);
    170             if (mirroredChar != characters[i]) {
    171                 if (!mirrored) {
    172                     mirrored = true;
    173                     substituteCharacters.grow(stringLength);
    174                     memcpy(substituteCharacters.data(), characters, stringLength * sizeof(UChar));
    175                     ATSUTextMoved(atsuTextLayout, substituteCharacters.data());
    176                 }
    177                 substituteCharacters[i] = mirroredChar;
    178             }
    179         }
    180         if (shouldCheckForArabic && isArabicChar(characters[i])) {
    181             shouldCheckForArabic = false;
    182             shouldShapeArabic = true;
    183         }
    184     }
    185 
    186     if (shouldShapeArabic) {
    187         Vector<UChar, 256> shapedArabic(stringLength);
    188         shapeArabic(substituteCharacters.isEmpty() ? characters : substituteCharacters.data(), shapedArabic.data(), stringLength);
    189         substituteCharacters.swap(shapedArabic);
    190         ATSUTextMoved(atsuTextLayout, substituteCharacters.data());
    191     }
    192 
    193     if (directionalOverride) {
    194         UChar override = ltr ? leftToRightOverride : rightToLeftOverride;
    195         if (substituteCharacters.isEmpty()) {
    196             substituteCharacters.grow(stringLength + 2);
    197             substituteCharacters[0] = override;
    198             memcpy(substituteCharacters.data() + 1, characters, stringLength * sizeof(UChar));
    199             substituteCharacters[stringLength + 1] = popDirectionalFormatting;
    200             ATSUTextMoved(atsuTextLayout, substituteCharacters.data());
    201         } else {
    202             substituteCharacters.prepend(override);
    203             substituteCharacters.append(popDirectionalFormatting);
    204         }
    205         ATSUTextInserted(atsuTextLayout, 0, 2);
    206     }
    207 
    208     ATSULayoutOperationOverrideSpecifier overrideSpecifier;
    209     overrideSpecifier.operationSelector = kATSULayoutOperationPostLayoutAdjustment;
    210     overrideSpecifier.overrideUPP = overrideLayoutOperation;
    211 
    212     ATSUAttributeTag tags[] = { kATSULineLayoutOptionsTag, kATSULineDirectionTag, kATSULayoutOperationOverrideTag };
    213     ByteCount sizes[] = { sizeof(ATSLineLayoutOptions), sizeof(Boolean), sizeof(ATSULayoutOperationOverrideSpecifier) };
    214     ATSUAttributeValuePtr values[] = { &lineLayoutOptions, &rtl, &overrideSpecifier };
    215 
    216     status = ATSUSetLayoutControls(atsuTextLayout, 3, tags, sizes, values);
    217 
    218     ItemCount boundsCount;
    219     status = ATSUGetGlyphBounds(atsuTextLayout, 0, 0, 0, m_stringLength, kATSUseFractionalOrigins, 0, 0, &boundsCount);
    220 
    221     status = ATSUDisposeTextLayout(atsuTextLayout);
    222 }
    223 
    224 void ComplexTextController::ComplexTextRun::createTextRunFromFontDataATSUI(bool ltr)
    225 {
    226     m_atsuiIndices.reserveCapacity(m_stringLength);
    227     unsigned r = 0;
    228     while (r < m_stringLength) {
    229         m_atsuiIndices.uncheckedAppend(r);
    230         if (U_IS_SURROGATE(m_characters[r])) {
    231             ASSERT(r + 1 < m_stringLength);
    232             ASSERT(U_IS_SURROGATE_LEAD(m_characters[r]));
    233             ASSERT(U_IS_TRAIL(m_characters[r + 1]));
    234             r += 2;
    235         } else
    236             r++;
    237     }
    238     m_glyphCount = m_atsuiIndices.size();
    239     if (!ltr) {
    240         for (unsigned r = 0, end = m_glyphCount - 1; r < m_glyphCount / 2; ++r, --end)
    241             std::swap(m_atsuiIndices[r], m_atsuiIndices[end]);
    242     }
    243 
    244     m_glyphsVector.fill(0, m_glyphCount);
    245     m_glyphs = m_glyphsVector.data();
    246     m_advancesVector.fill(CGSizeMake(m_fontData->widthForGlyph(0), 0), m_glyphCount);
    247     m_advances = m_advancesVector.data();
    248 }
    249 
    250 static bool fontHasMirroringInfo(ATSUFontID fontID)
    251 {
    252     ByteCount propTableSize;
    253     OSStatus status = ATSFontGetTable(fontID, 'prop', 0, 0, 0, &propTableSize);
    254     if (status == noErr)    // naively assume that if a 'prop' table exists then it contains mirroring info
    255         return true;
    256     else if (status != kATSInvalidFontTableAccess) // anything other than a missing table is logged as an error
    257         LOG_ERROR("ATSFontGetTable failed (%d)", static_cast<int>(status));
    258 
    259     return false;
    260 }
    261 
    262 static void disableLigatures(const SimpleFontData* fontData, ATSUStyle atsuStyle, TypesettingFeatures typesettingFeatures)
    263 {
    264     // Don't be too aggressive: if the font doesn't contain 'a', then assume that any ligatures it contains are
    265     // in characters that always go through ATSUI, and therefore allow them. Geeza Pro is an example.
    266     // See bugzilla 5166.
    267     if ((typesettingFeatures & Ligatures) || (fontData->platformData().orientation() == Horizontal && fontData->platformData().allowsLigatures()))
    268         return;
    269 
    270     ATSUFontFeatureType featureTypes[] = { kLigaturesType };
    271     ATSUFontFeatureSelector featureSelectors[] = { kCommonLigaturesOffSelector };
    272     OSStatus status = ATSUSetFontFeatures(atsuStyle, 1, featureTypes, featureSelectors);
    273     if (status != noErr)
    274         LOG_ERROR("ATSUSetFontFeatures failed (%d) -- ligatures remain enabled", static_cast<int>(status));
    275 }
    276 
    277 static ATSUStyle initializeATSUStyle(const SimpleFontData* fontData, TypesettingFeatures typesettingFeatures)
    278 {
    279     unsigned key = typesettingFeatures + 1;
    280     pair<HashMap<unsigned, ATSUStyle>::iterator, bool> addResult = fontData->m_ATSUStyleMap.add(key, 0);
    281     ATSUStyle& atsuStyle = addResult.first->second;
    282     if (!addResult.second)
    283         return atsuStyle;
    284 
    285     ATSUFontID fontID = fontData->platformData().ctFont() ? CTFontGetPlatformFont(fontData->platformData().ctFont(), 0) : 0;
    286     if (!fontID) {
    287         LOG_ERROR("unable to get ATSUFontID for %p", fontData->platformData().font());
    288         fontData->m_ATSUStyleMap.remove(addResult.first);
    289         return 0;
    290     }
    291 
    292     OSStatus status = ATSUCreateStyle(&atsuStyle);
    293     if (status != noErr)
    294         LOG_ERROR("ATSUCreateStyle failed (%d)", static_cast<int>(status));
    295 
    296     Fixed fontSize = FloatToFixed(fontData->platformData().m_size);
    297     Fract kerningInhibitFactor = FloatToFract(1);
    298     static CGAffineTransform verticalFlip = CGAffineTransformMakeScale(1, -1);
    299 
    300     ByteCount styleSizes[4] = { sizeof(fontSize), sizeof(fontID), sizeof(verticalFlip), sizeof(kerningInhibitFactor) };
    301     ATSUAttributeTag styleTags[4] = { kATSUSizeTag, kATSUFontTag, kATSUFontMatrixTag, kATSUKerningInhibitFactorTag };
    302     ATSUAttributeValuePtr styleValues[4] = { &fontSize, &fontID, &verticalFlip, &kerningInhibitFactor };
    303 
    304     bool allowKerning = typesettingFeatures & Kerning;
    305     status = ATSUSetAttributes(atsuStyle, allowKerning ? 3 : 4, styleTags, styleSizes, styleValues);
    306     if (status != noErr)
    307         LOG_ERROR("ATSUSetAttributes failed (%d)", static_cast<int>(status));
    308 
    309     fontData->m_ATSUMirrors = fontHasMirroringInfo(fontID);
    310 
    311     disableLigatures(fontData, atsuStyle, typesettingFeatures);
    312     return atsuStyle;
    313 }
    314 
    315 void ComplexTextController::collectComplexTextRunsForCharactersATSUI(const UChar* cp, unsigned length, unsigned stringLocation, const SimpleFontData* fontData)
    316 {
    317     if (!fontData) {
    318         // Create a run of missing glyphs from the primary font.
    319         m_complexTextRuns.append(ComplexTextRun::create(m_font.primaryFont(), cp, stringLocation, length, m_run.ltr()));
    320         return;
    321     }
    322 
    323     if (m_fallbackFonts && fontData != m_font.primaryFont())
    324         m_fallbackFonts->add(fontData);
    325 
    326     ATSUStyle atsuStyle = initializeATSUStyle(fontData, m_font.typesettingFeatures());
    327 
    328     OSStatus status;
    329     ATSUTextLayout atsuTextLayout;
    330     UniCharCount runLength = length;
    331 
    332     status = ATSUCreateTextLayoutWithTextPtr(cp, 0, length, length, 1, &runLength, &atsuStyle, &atsuTextLayout);
    333     if (status != noErr) {
    334         LOG_ERROR("ATSUCreateTextLayoutWithTextPtr failed with error %d", static_cast<int>(status));
    335         return;
    336     }
    337     m_complexTextRuns.append(ComplexTextRun::create(atsuTextLayout, fontData, cp, stringLocation, length, m_run.ltr(), m_run.directionalOverride()));
    338 }
    339 
    340 } // namespace WebCore
    341 
    342 #endif // USE(ATSUI)
    343