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