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