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