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.
35extern "C" {
36OSStatus ATSUTextInserted(ATSUTextLayout iTextLayout,  UniCharArrayOffset iInsertionLocation, UniCharCount iInsertionLength);
37}
38#endif
39
40using namespace WTF::Unicode;
41
42namespace WebCore {
43
44OSStatus 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
99static inline bool isArabicLamWithAlefLigature(UChar c)
100{
101    return c >= 0xfef5 && c <= 0xfefc;
102}
103
104static 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
144ComplexTextController::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
224void 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
250static 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
262static 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
277static 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
315void 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