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