• 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, 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 
overrideLayoutOperation(ATSULayoutOperationSelector,ATSULineRef atsuLineRef,URefCon refCon,void *,ATSULayoutOperationCallbackStatus * callbackStatus)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 
isArabicLamWithAlefLigature(UChar c)99 static inline bool isArabicLamWithAlefLigature(UChar c)
100 {
101     return c >= 0xfef5 && c <= 0xfefc;
102 }
103 
shapeArabic(const UChar * source,UChar * dest,unsigned totalLength)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 
ComplexTextRun(ATSUTextLayout atsuTextLayout,const SimpleFontData * fontData,const UChar * characters,unsigned stringLocation,size_t stringLength,bool ltr,bool directionalOverride)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 
createTextRunFromFontDataATSUI(bool ltr)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 
fontHasMirroringInfo(ATSUFontID fontID)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 
disableLigatures(const SimpleFontData * fontData,ATSUStyle atsuStyle,TypesettingFeatures typesettingFeatures)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 
initializeATSUStyle(const SimpleFontData * fontData,TypesettingFeatures typesettingFeatures)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 
collectComplexTextRunsForCharactersATSUI(const UChar * cp,unsigned length,unsigned stringLocation,const SimpleFontData * fontData)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