1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "mozilla/ArrayUtils.h"
7 #include "gfxCoreTextShaper.h"
8 #include "gfxMacFont.h"
9 #include "gfxFontUtils.h"
10 #include "gfxTextRun.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/UniquePtrExtensions.h"
13
14 #include <algorithm>
15
16 #include <dlfcn.h>
17
18 using namespace mozilla;
19
20 // standard font descriptors that we construct the first time they're needed
21 CTFontDescriptorRef gfxCoreTextShaper::sDefaultFeaturesDescriptor = nullptr;
22 CTFontDescriptorRef gfxCoreTextShaper::sDisableLigaturesDescriptor = nullptr;
23 CTFontDescriptorRef gfxCoreTextShaper::sIndicFeaturesDescriptor = nullptr;
24 CTFontDescriptorRef gfxCoreTextShaper::sIndicDisableLigaturesDescriptor = nullptr;
25
26 static CFStringRef sCTWritingDirectionAttributeName = nullptr;
27
28 // See CTStringAttributes.h
29 enum {
30 kMyCTWritingDirectionEmbedding = (0 << 1),
31 kMyCTWritingDirectionOverride = (1 << 1)
32 };
33
34 // Helper to create a CFDictionary with the right attributes for shaping our
35 // text, including imposing the given directionality.
36 // This will only be called if we're on 10.8 or later.
37 CFDictionaryRef
CreateAttrDict(bool aRightToLeft)38 gfxCoreTextShaper::CreateAttrDict(bool aRightToLeft)
39 {
40 // Because we always shape unidirectional runs, and may have applied
41 // directional overrides, we want to force a direction rather than
42 // allowing CoreText to do its own unicode-based bidi processing.
43 SInt16 dirOverride = kMyCTWritingDirectionOverride |
44 (aRightToLeft ? kCTWritingDirectionRightToLeft
45 : kCTWritingDirectionLeftToRight);
46 CFNumberRef dirNumber =
47 ::CFNumberCreate(kCFAllocatorDefault,
48 kCFNumberSInt16Type, &dirOverride);
49 CFArrayRef dirArray =
50 ::CFArrayCreate(kCFAllocatorDefault,
51 (const void **) &dirNumber, 1,
52 &kCFTypeArrayCallBacks);
53 ::CFRelease(dirNumber);
54 CFTypeRef attrs[] = { kCTFontAttributeName, sCTWritingDirectionAttributeName };
55 CFTypeRef values[] = { mCTFont, dirArray };
56 CFDictionaryRef attrDict =
57 ::CFDictionaryCreate(kCFAllocatorDefault,
58 attrs, values, ArrayLength(attrs),
59 &kCFTypeDictionaryKeyCallBacks,
60 &kCFTypeDictionaryValueCallBacks);
61 ::CFRelease(dirArray);
62 return attrDict;
63 }
64
65 CFDictionaryRef
CreateAttrDictWithoutDirection()66 gfxCoreTextShaper::CreateAttrDictWithoutDirection()
67 {
68 CFTypeRef attrs[] = { kCTFontAttributeName };
69 CFTypeRef values[] = { mCTFont };
70 CFDictionaryRef attrDict =
71 ::CFDictionaryCreate(kCFAllocatorDefault,
72 attrs, values, ArrayLength(attrs),
73 &kCFTypeDictionaryKeyCallBacks,
74 &kCFTypeDictionaryValueCallBacks);
75 return attrDict;
76 }
77
gfxCoreTextShaper(gfxMacFont * aFont)78 gfxCoreTextShaper::gfxCoreTextShaper(gfxMacFont *aFont)
79 : gfxFontShaper(aFont)
80 , mAttributesDictLTR(nullptr)
81 , mAttributesDictRTL(nullptr)
82 {
83 static bool sInitialized = false;
84 if (!sInitialized) {
85 CFStringRef* pstr = (CFStringRef*)
86 dlsym(RTLD_DEFAULT, "kCTWritingDirectionAttributeName");
87 if (pstr) {
88 sCTWritingDirectionAttributeName = *pstr;
89 }
90 sInitialized = true;
91 }
92
93 // Create our CTFontRef
94 mCTFont = CreateCTFontWithFeatures(aFont->GetAdjustedSize(),
95 GetDefaultFeaturesDescriptor());
96 }
97
~gfxCoreTextShaper()98 gfxCoreTextShaper::~gfxCoreTextShaper()
99 {
100 if (mAttributesDictLTR) {
101 ::CFRelease(mAttributesDictLTR);
102 }
103 if (mAttributesDictRTL) {
104 ::CFRelease(mAttributesDictRTL);
105 }
106 if (mCTFont) {
107 ::CFRelease(mCTFont);
108 }
109 }
110
111 static bool
IsBuggyIndicScript(unicode::Script aScript)112 IsBuggyIndicScript(unicode::Script aScript)
113 {
114 return aScript == unicode::Script::BENGALI ||
115 aScript == unicode::Script::KANNADA;
116 }
117
118 bool
ShapeText(DrawTarget * aDrawTarget,const char16_t * aText,uint32_t aOffset,uint32_t aLength,Script aScript,bool aVertical,gfxShapedText * aShapedText)119 gfxCoreTextShaper::ShapeText(DrawTarget *aDrawTarget,
120 const char16_t *aText,
121 uint32_t aOffset,
122 uint32_t aLength,
123 Script aScript,
124 bool aVertical,
125 gfxShapedText *aShapedText)
126 {
127 // Create a CFAttributedString with text and style info, so we can use CoreText to lay it out.
128 bool isRightToLeft = aShapedText->IsRightToLeft();
129 const UniChar* text = reinterpret_cast<const UniChar*>(aText);
130 uint32_t length = aLength;
131
132 uint32_t startOffset;
133 CFStringRef stringObj;
134 CFDictionaryRef attrObj;
135
136 if (sCTWritingDirectionAttributeName) {
137 startOffset = 0;
138 stringObj = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
139 text, length,
140 kCFAllocatorNull);
141
142 // Get an attributes dictionary suitable for shaping text in the
143 // current direction, creating it if necessary.
144 attrObj = isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR;
145 if (!attrObj) {
146 attrObj = CreateAttrDict(isRightToLeft);
147 (isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR) = attrObj;
148 }
149 } else {
150 // OS is too old to support kCTWritingDirectionAttributeName:
151 // we need to bidi-wrap the text if the run is RTL,
152 // or if it is an LTR run but may contain (overridden) RTL chars
153 bool bidiWrap = isRightToLeft;
154 if (!bidiWrap && !aShapedText->TextIs8Bit()) {
155 uint32_t i;
156 for (i = 0; i < length; ++i) {
157 if (gfxFontUtils::PotentialRTLChar(aText[i])) {
158 bidiWrap = true;
159 break;
160 }
161 }
162 }
163
164 // If there's a possibility of any bidi, we wrap the text with
165 // direction overrides to ensure neutrals or characters that were
166 // bidi-overridden in HTML behave properly.
167 static const UniChar beginLTR[] = { 0x202d, 0x20 };
168 static const UniChar beginRTL[] = { 0x202e, 0x20 };
169 static const UniChar endBidiWrap[] = { 0x20, 0x2e, 0x202c };
170
171 if (bidiWrap) {
172 startOffset = isRightToLeft ? ArrayLength(beginRTL)
173 : ArrayLength(beginLTR);
174 CFMutableStringRef mutableString =
175 ::CFStringCreateMutable(kCFAllocatorDefault,
176 length + startOffset +
177 ArrayLength(endBidiWrap));
178 ::CFStringAppendCharacters(mutableString,
179 isRightToLeft ? beginRTL : beginLTR,
180 startOffset);
181 ::CFStringAppendCharacters(mutableString, text, length);
182 ::CFStringAppendCharacters(mutableString, endBidiWrap,
183 ArrayLength(endBidiWrap));
184 stringObj = mutableString;
185 } else {
186 startOffset = 0;
187 stringObj =
188 ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
189 text, length,
190 kCFAllocatorNull);
191 }
192
193 // Get an attributes dictionary suitable for shaping text,
194 // creating it if necessary. (This dict is not LTR-specific,
195 // but we use that field to store it anyway.)
196 if (!mAttributesDictLTR) {
197 mAttributesDictLTR = CreateAttrDictWithoutDirection();
198 }
199 attrObj = mAttributesDictLTR;
200 }
201
202 CTFontRef tempCTFont = nullptr;
203 if (IsBuggyIndicScript(aScript)) {
204 // To work around buggy Indic AAT fonts shipped with OS X,
205 // we re-enable the Line Initial Smart Swashes feature that is needed
206 // for "split vowels" to work in at least Bengali and Kannada fonts.
207 // Affected fonts include Bangla MN, Bangla Sangam MN, Kannada MN,
208 // Kannada Sangam MN. See bugs 686225, 728557, 953231, 1145515.
209 tempCTFont =
210 CreateCTFontWithFeatures(::CTFontGetSize(mCTFont),
211 aShapedText->DisableLigatures()
212 ? GetIndicDisableLigaturesDescriptor()
213 : GetIndicFeaturesDescriptor());
214 } else if (aShapedText->DisableLigatures()) {
215 // For letterspacing (or maybe other situations) we need to make
216 // a copy of the CTFont with the ligature feature disabled.
217 tempCTFont =
218 CreateCTFontWithFeatures(::CTFontGetSize(mCTFont),
219 GetDisableLigaturesDescriptor());
220 }
221
222 // For the disabled-ligature or buggy-indic-font case, we need to replace
223 // the standard CTFont in the attribute dictionary with a tweaked version.
224 CFMutableDictionaryRef mutableAttr = nullptr;
225 if (tempCTFont) {
226 mutableAttr = ::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 2,
227 attrObj);
228 ::CFDictionaryReplaceValue(mutableAttr,
229 kCTFontAttributeName, tempCTFont);
230 // Having created the dict, we're finished with our temporary
231 // Indic and/or ligature-disabled CTFontRef.
232 ::CFRelease(tempCTFont);
233 attrObj = mutableAttr;
234 }
235
236 // Now we can create an attributed string
237 CFAttributedStringRef attrStringObj =
238 ::CFAttributedStringCreate(kCFAllocatorDefault, stringObj, attrObj);
239 ::CFRelease(stringObj);
240
241 // Create the CoreText line from our string, then we're done with it
242 CTLineRef line = ::CTLineCreateWithAttributedString(attrStringObj);
243 ::CFRelease(attrStringObj);
244
245 // and finally retrieve the glyph data and store into the gfxTextRun
246 CFArrayRef glyphRuns = ::CTLineGetGlyphRuns(line);
247 uint32_t numRuns = ::CFArrayGetCount(glyphRuns);
248
249 // Iterate through the glyph runs.
250 // Note that this includes the bidi wrapper, so we have to be careful
251 // not to include the extra glyphs from there
252 bool success = true;
253 for (uint32_t runIndex = 0; runIndex < numRuns; runIndex++) {
254 CTRunRef aCTRun =
255 (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex);
256 // If the range is purely within bidi-wrapping text, ignore it.
257 CFRange range = ::CTRunGetStringRange(aCTRun);
258 if (uint32_t(range.location + range.length) <= startOffset ||
259 range.location - startOffset >= aLength) {
260 continue;
261 }
262 CFDictionaryRef runAttr = ::CTRunGetAttributes(aCTRun);
263 if (runAttr != attrObj) {
264 // If Core Text manufactured a new dictionary, this may indicate
265 // unexpected font substitution. In that case, we fail (and fall
266 // back to harfbuzz shaping)...
267 const void* font1 =
268 ::CFDictionaryGetValue(attrObj, kCTFontAttributeName);
269 const void* font2 =
270 ::CFDictionaryGetValue(runAttr, kCTFontAttributeName);
271 if (font1 != font2) {
272 // ...except that if the fallback was only for a variation
273 // selector or join control that is otherwise unsupported,
274 // we just ignore it.
275 if (range.length == 1) {
276 char16_t ch = aText[range.location - startOffset];
277 if (gfxFontUtils::IsJoinControl(ch) ||
278 gfxFontUtils::IsVarSelector(ch)) {
279 continue;
280 }
281 }
282 NS_WARNING("unexpected font fallback in Core Text");
283 success = false;
284 break;
285 }
286 }
287 if (SetGlyphsFromRun(aShapedText, aOffset, aLength, aCTRun,
288 startOffset) != NS_OK) {
289 success = false;
290 break;
291 }
292 }
293
294 if (mutableAttr) {
295 ::CFRelease(mutableAttr);
296 }
297 ::CFRelease(line);
298
299 return success;
300 }
301
302 #define SMALL_GLYPH_RUN 128 // preallocated size of our auto arrays for per-glyph data;
303 // some testing indicates that 90%+ of glyph runs will fit
304 // without requiring a separate allocation
305
306 nsresult
SetGlyphsFromRun(gfxShapedText * aShapedText,uint32_t aOffset,uint32_t aLength,CTRunRef aCTRun,int32_t aStringOffset)307 gfxCoreTextShaper::SetGlyphsFromRun(gfxShapedText *aShapedText,
308 uint32_t aOffset,
309 uint32_t aLength,
310 CTRunRef aCTRun,
311 int32_t aStringOffset)
312 {
313 // The word has been bidi-wrapped; aStringOffset is the number
314 // of chars at the beginning of the CTLine that we should skip.
315 // aCTRun is a glyph run from the CoreText layout process.
316
317 int32_t direction = aShapedText->IsRightToLeft() ? -1 : 1;
318
319 int32_t numGlyphs = ::CTRunGetGlyphCount(aCTRun);
320 if (numGlyphs == 0) {
321 return NS_OK;
322 }
323
324 int32_t wordLength = aLength;
325
326 // character offsets get really confusing here, as we have to keep track of
327 // (a) the text in the actual textRun we're constructing
328 // (c) the string that was handed to CoreText, which contains the text of the font run
329 // plus directional-override padding
330 // (d) the CTRun currently being processed, which may be a sub-run of the CoreText line
331 // (but may extend beyond the actual font run into the bidi wrapping text).
332 // aStringOffset tells us how many initial characters of the line to ignore.
333
334 // get the source string range within the CTLine's text
335 CFRange stringRange = ::CTRunGetStringRange(aCTRun);
336 // skip the run if it is entirely outside the actual range of the font run
337 if (stringRange.location - aStringOffset + stringRange.length <= 0 ||
338 stringRange.location - aStringOffset >= wordLength) {
339 return NS_OK;
340 }
341
342 // retrieve the laid-out glyph data from the CTRun
343 UniquePtr<CGGlyph[]> glyphsArray;
344 UniquePtr<CGPoint[]> positionsArray;
345 UniquePtr<CFIndex[]> glyphToCharArray;
346 const CGGlyph* glyphs = nullptr;
347 const CGPoint* positions = nullptr;
348 const CFIndex* glyphToChar = nullptr;
349
350 // Testing indicates that CTRunGetGlyphsPtr (almost?) always succeeds,
351 // and so allocating a new array and copying data with CTRunGetGlyphs
352 // will be extremely rare.
353 // If this were not the case, we could use an AutoTArray<> to
354 // try and avoid the heap allocation for small runs.
355 // It's possible that some future change to CoreText will mean that
356 // CTRunGetGlyphsPtr fails more often; if this happens, AutoTArray<>
357 // may become an attractive option.
358 glyphs = ::CTRunGetGlyphsPtr(aCTRun);
359 if (!glyphs) {
360 glyphsArray = MakeUniqueFallible<CGGlyph[]>(numGlyphs);
361 if (!glyphsArray) {
362 return NS_ERROR_OUT_OF_MEMORY;
363 }
364 ::CTRunGetGlyphs(aCTRun, ::CFRangeMake(0, 0), glyphsArray.get());
365 glyphs = glyphsArray.get();
366 }
367
368 positions = ::CTRunGetPositionsPtr(aCTRun);
369 if (!positions) {
370 positionsArray = MakeUniqueFallible<CGPoint[]>(numGlyphs);
371 if (!positionsArray) {
372 return NS_ERROR_OUT_OF_MEMORY;
373 }
374 ::CTRunGetPositions(aCTRun, ::CFRangeMake(0, 0), positionsArray.get());
375 positions = positionsArray.get();
376 }
377
378 // Remember that the glyphToChar indices relate to the CoreText line,
379 // not to the beginning of the textRun, the font run,
380 // or the stringRange of the glyph run
381 glyphToChar = ::CTRunGetStringIndicesPtr(aCTRun);
382 if (!glyphToChar) {
383 glyphToCharArray = MakeUniqueFallible<CFIndex[]>(numGlyphs);
384 if (!glyphToCharArray) {
385 return NS_ERROR_OUT_OF_MEMORY;
386 }
387 ::CTRunGetStringIndices(aCTRun, ::CFRangeMake(0, 0), glyphToCharArray.get());
388 glyphToChar = glyphToCharArray.get();
389 }
390
391 double runWidth = ::CTRunGetTypographicBounds(aCTRun, ::CFRangeMake(0, 0),
392 nullptr, nullptr, nullptr);
393
394 AutoTArray<gfxShapedText::DetailedGlyph,1> detailedGlyphs;
395 gfxShapedText::CompressedGlyph *charGlyphs =
396 aShapedText->GetCharacterGlyphs() + aOffset;
397
398 // CoreText gives us the glyphindex-to-charindex mapping, which relates each glyph
399 // to a source text character; we also need the charindex-to-glyphindex mapping to
400 // find the glyph for a given char. Note that some chars may not map to any glyph
401 // (ligature continuations), and some may map to several glyphs (eg Indic split vowels).
402 // We set the glyph index to NO_GLYPH for chars that have no associated glyph, and we
403 // record the last glyph index for cases where the char maps to several glyphs,
404 // so that our clumping will include all the glyph fragments for the character.
405
406 // The charToGlyph array is indexed by char position within the stringRange of the glyph run.
407
408 static const int32_t NO_GLYPH = -1;
409 AutoTArray<int32_t,SMALL_GLYPH_RUN> charToGlyphArray;
410 if (!charToGlyphArray.SetLength(stringRange.length, fallible)) {
411 return NS_ERROR_OUT_OF_MEMORY;
412 }
413 int32_t *charToGlyph = charToGlyphArray.Elements();
414 for (int32_t offset = 0; offset < stringRange.length; ++offset) {
415 charToGlyph[offset] = NO_GLYPH;
416 }
417 for (int32_t i = 0; i < numGlyphs; ++i) {
418 int32_t loc = glyphToChar[i] - stringRange.location;
419 if (loc >= 0 && loc < stringRange.length) {
420 charToGlyph[loc] = i;
421 }
422 }
423
424 // Find character and glyph clumps that correspond, allowing for ligatures,
425 // indic reordering, split glyphs, etc.
426 //
427 // The idea is that we'll find a character sequence starting at the first char of stringRange,
428 // and extend it until it includes the character associated with the first glyph;
429 // we also extend it as long as there are "holes" in the range of glyphs. So we
430 // will eventually have a contiguous sequence of characters, starting at the beginning
431 // of the range, that map to a contiguous sequence of glyphs, starting at the beginning
432 // of the glyph array. That's a clump; then we update the starting positions and repeat.
433 //
434 // NB: In the case of RTL layouts, we iterate over the stringRange in reverse.
435 //
436
437 // This may find characters that fall outside the range 0:wordLength,
438 // so we won't necessarily use everything we find here.
439
440 bool isRightToLeft = aShapedText->IsRightToLeft();
441 int32_t glyphStart = 0; // looking for a clump that starts at this glyph index
442 int32_t charStart = isRightToLeft ?
443 stringRange.length - 1 : 0; // and this char index (in the stringRange of the glyph run)
444
445 while (glyphStart < numGlyphs) { // keep finding groups until all glyphs are accounted for
446 bool inOrder = true;
447 int32_t charEnd = glyphToChar[glyphStart] - stringRange.location;
448 NS_WARNING_ASSERTION(
449 charEnd >= 0 && charEnd < stringRange.length,
450 "glyph-to-char mapping points outside string range");
451 // clamp charEnd to the valid range of the string
452 charEnd = std::max(charEnd, 0);
453 charEnd = std::min(charEnd, int32_t(stringRange.length));
454
455 int32_t glyphEnd = glyphStart;
456 int32_t charLimit = isRightToLeft ? -1 : stringRange.length;
457 do {
458 // This is normally executed once for each iteration of the outer loop,
459 // but in unusual cases where the character/glyph association is complex,
460 // the initial character range might correspond to a non-contiguous
461 // glyph range with "holes" in it. If so, we will repeat this loop to
462 // extend the character range until we have a contiguous glyph sequence.
463 NS_ASSERTION((direction > 0 && charEnd < charLimit) ||
464 (direction < 0 && charEnd > charLimit),
465 "no characters left in range?");
466 charEnd += direction;
467 while (charEnd != charLimit && charToGlyph[charEnd] == NO_GLYPH) {
468 charEnd += direction;
469 }
470
471 // find the maximum glyph index covered by the clump so far
472 if (isRightToLeft) {
473 for (int32_t i = charStart; i > charEnd; --i) {
474 if (charToGlyph[i] != NO_GLYPH) {
475 // update extent of glyph range
476 glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1);
477 }
478 }
479 } else {
480 for (int32_t i = charStart; i < charEnd; ++i) {
481 if (charToGlyph[i] != NO_GLYPH) {
482 // update extent of glyph range
483 glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1);
484 }
485 }
486 }
487
488 if (glyphEnd == glyphStart + 1) {
489 // for the common case of a single-glyph clump, we can skip the following checks
490 break;
491 }
492
493 if (glyphEnd == glyphStart) {
494 // no glyphs, try to extend the clump
495 continue;
496 }
497
498 // check whether all glyphs in the range are associated with the characters
499 // in our clump; if not, we have a discontinuous range, and should extend it
500 // unless we've reached the end of the text
501 bool allGlyphsAreWithinCluster = true;
502 int32_t prevGlyphCharIndex = charStart;
503 for (int32_t i = glyphStart; i < glyphEnd; ++i) {
504 int32_t glyphCharIndex = glyphToChar[i] - stringRange.location;
505 if (isRightToLeft) {
506 if (glyphCharIndex > charStart || glyphCharIndex <= charEnd) {
507 allGlyphsAreWithinCluster = false;
508 break;
509 }
510 if (glyphCharIndex > prevGlyphCharIndex) {
511 inOrder = false;
512 }
513 prevGlyphCharIndex = glyphCharIndex;
514 } else {
515 if (glyphCharIndex < charStart || glyphCharIndex >= charEnd) {
516 allGlyphsAreWithinCluster = false;
517 break;
518 }
519 if (glyphCharIndex < prevGlyphCharIndex) {
520 inOrder = false;
521 }
522 prevGlyphCharIndex = glyphCharIndex;
523 }
524 }
525 if (allGlyphsAreWithinCluster) {
526 break;
527 }
528 } while (charEnd != charLimit);
529
530 NS_WARNING_ASSERTION(glyphStart < glyphEnd,
531 "character/glyph clump contains no glyphs!");
532 if (glyphStart == glyphEnd) {
533 ++glyphStart; // make progress - avoid potential infinite loop
534 charStart = charEnd;
535 continue;
536 }
537
538 NS_WARNING_ASSERTION(charStart != charEnd,
539 "character/glyph clump contains no characters!");
540 if (charStart == charEnd) {
541 glyphStart = glyphEnd; // this is bad - we'll discard the glyph(s),
542 // as there's nowhere to attach them
543 continue;
544 }
545
546 // Now charStart..charEnd is a ligature clump, corresponding to glyphStart..glyphEnd;
547 // Set baseCharIndex to the char we'll actually attach the glyphs to (1st of ligature),
548 // and endCharIndex to the limit (position beyond the last char),
549 // adjusting for the offset of the stringRange relative to the textRun.
550 int32_t baseCharIndex, endCharIndex;
551 if (isRightToLeft) {
552 while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH) {
553 charEnd--;
554 }
555 baseCharIndex = charEnd + stringRange.location - aStringOffset + 1;
556 endCharIndex = charStart + stringRange.location - aStringOffset + 1;
557 } else {
558 while (charEnd < stringRange.length && charToGlyph[charEnd] == NO_GLYPH) {
559 charEnd++;
560 }
561 baseCharIndex = charStart + stringRange.location - aStringOffset;
562 endCharIndex = charEnd + stringRange.location - aStringOffset;
563 }
564
565 // Then we check if the clump falls outside our actual string range; if so, just go to the next.
566 if (endCharIndex <= 0 || baseCharIndex >= wordLength) {
567 glyphStart = glyphEnd;
568 charStart = charEnd;
569 continue;
570 }
571 // Ensure we won't try to go beyond the valid length of the word's text
572 baseCharIndex = std::max(baseCharIndex, 0);
573 endCharIndex = std::min(endCharIndex, wordLength);
574
575 // Now we're ready to set the glyph info in the textRun; measure the glyph width
576 // of the first (perhaps only) glyph, to see if it is "Simple"
577 int32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit();
578 double toNextGlyph;
579 if (glyphStart < numGlyphs-1) {
580 toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x;
581 } else {
582 toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x;
583 }
584 int32_t advance = int32_t(toNextGlyph * appUnitsPerDevUnit);
585
586 // Check if it's a simple one-to-one mapping
587 int32_t glyphsInClump = glyphEnd - glyphStart;
588 if (glyphsInClump == 1 &&
589 gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyphs[glyphStart]) &&
590 gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
591 charGlyphs[baseCharIndex].IsClusterStart() &&
592 positions[glyphStart].y == 0.0)
593 {
594 charGlyphs[baseCharIndex].SetSimpleGlyph(advance,
595 glyphs[glyphStart]);
596 } else {
597 // collect all glyphs in a list to be assigned to the first char;
598 // there must be at least one in the clump, and we already measured its advance,
599 // hence the placement of the loop-exit test and the measurement of the next glyph
600 while (1) {
601 gfxTextRun::DetailedGlyph *details = detailedGlyphs.AppendElement();
602 details->mGlyphID = glyphs[glyphStart];
603 details->mXOffset = 0;
604 details->mYOffset = -positions[glyphStart].y * appUnitsPerDevUnit;
605 details->mAdvance = advance;
606 if (++glyphStart >= glyphEnd) {
607 break;
608 }
609 if (glyphStart < numGlyphs-1) {
610 toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x;
611 } else {
612 toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x;
613 }
614 advance = int32_t(toNextGlyph * appUnitsPerDevUnit);
615 }
616
617 gfxTextRun::CompressedGlyph textRunGlyph;
618 textRunGlyph.SetComplex(charGlyphs[baseCharIndex].IsClusterStart(),
619 true, detailedGlyphs.Length());
620 aShapedText->SetGlyphs(aOffset + baseCharIndex, textRunGlyph,
621 detailedGlyphs.Elements());
622
623 detailedGlyphs.Clear();
624 }
625
626 // the rest of the chars in the group are ligature continuations, no associated glyphs
627 while (++baseCharIndex != endCharIndex && baseCharIndex < wordLength) {
628 gfxShapedText::CompressedGlyph &shapedTextGlyph = charGlyphs[baseCharIndex];
629 NS_ASSERTION(!shapedTextGlyph.IsSimpleGlyph(), "overwriting a simple glyph");
630 shapedTextGlyph.SetComplex(inOrder && shapedTextGlyph.IsClusterStart(), false, 0);
631 }
632
633 glyphStart = glyphEnd;
634 charStart = charEnd;
635 }
636
637 return NS_OK;
638 }
639
640 #undef SMALL_GLYPH_RUN
641
642 // Construct the font attribute descriptor that we'll apply by default when
643 // creating a CTFontRef. This will turn off line-edge swashes by default,
644 // because we don't know the actual line breaks when doing glyph shaping.
645
646 // We also cache feature descriptors for shaping with disabled ligatures, and
647 // for buggy Indic AAT font workarounds, created on an as-needed basis.
648
649 #define MAX_FEATURES 3 // max used by any of our Get*Descriptor functions
650
651 CTFontDescriptorRef
CreateFontFeaturesDescriptor(const std::pair<SInt16,SInt16> aFeatures[],size_t aCount)652 gfxCoreTextShaper::CreateFontFeaturesDescriptor(
653 const std::pair<SInt16,SInt16> aFeatures[],
654 size_t aCount)
655 {
656 MOZ_ASSERT(aCount <= MAX_FEATURES);
657
658 CFDictionaryRef featureSettings[MAX_FEATURES];
659
660 for (size_t i = 0; i < aCount; i++) {
661 CFNumberRef type = ::CFNumberCreate(kCFAllocatorDefault,
662 kCFNumberSInt16Type,
663 &aFeatures[i].first);
664 CFNumberRef selector = ::CFNumberCreate(kCFAllocatorDefault,
665 kCFNumberSInt16Type,
666 &aFeatures[i].second);
667
668 CFTypeRef keys[] = { kCTFontFeatureTypeIdentifierKey,
669 kCTFontFeatureSelectorIdentifierKey };
670 CFTypeRef values[] = { type, selector };
671 featureSettings[i] =
672 ::CFDictionaryCreate(kCFAllocatorDefault,
673 (const void **) keys,
674 (const void **) values,
675 ArrayLength(keys),
676 &kCFTypeDictionaryKeyCallBacks,
677 &kCFTypeDictionaryValueCallBacks);
678
679 ::CFRelease(selector);
680 ::CFRelease(type);
681 }
682
683 CFArrayRef featuresArray =
684 ::CFArrayCreate(kCFAllocatorDefault,
685 (const void **) featureSettings,
686 aCount, // not ArrayLength(featureSettings), as we
687 // may not have used all the allocated slots
688 &kCFTypeArrayCallBacks);
689
690 for (size_t i = 0; i < aCount; i++) {
691 ::CFRelease(featureSettings[i]);
692 }
693
694 const CFTypeRef attrKeys[] = { kCTFontFeatureSettingsAttribute };
695 const CFTypeRef attrValues[] = { featuresArray };
696 CFDictionaryRef attributesDict =
697 ::CFDictionaryCreate(kCFAllocatorDefault,
698 (const void **) attrKeys,
699 (const void **) attrValues,
700 ArrayLength(attrKeys),
701 &kCFTypeDictionaryKeyCallBacks,
702 &kCFTypeDictionaryValueCallBacks);
703 ::CFRelease(featuresArray);
704
705 CTFontDescriptorRef descriptor =
706 ::CTFontDescriptorCreateWithAttributes(attributesDict);
707 ::CFRelease(attributesDict);
708
709 return descriptor;
710 }
711
712 CTFontDescriptorRef
GetDefaultFeaturesDescriptor()713 gfxCoreTextShaper::GetDefaultFeaturesDescriptor()
714 {
715 if (sDefaultFeaturesDescriptor == nullptr) {
716 const std::pair<SInt16,SInt16> kDefaultFeatures[] = {
717 { kSmartSwashType, kLineInitialSwashesOffSelector },
718 { kSmartSwashType, kLineFinalSwashesOffSelector }
719 };
720 sDefaultFeaturesDescriptor =
721 CreateFontFeaturesDescriptor(kDefaultFeatures,
722 ArrayLength(kDefaultFeatures));
723 }
724 return sDefaultFeaturesDescriptor;
725 }
726
727 CTFontDescriptorRef
GetDisableLigaturesDescriptor()728 gfxCoreTextShaper::GetDisableLigaturesDescriptor()
729 {
730 if (sDisableLigaturesDescriptor == nullptr) {
731 const std::pair<SInt16,SInt16> kDisableLigatures[] = {
732 { kSmartSwashType, kLineInitialSwashesOffSelector },
733 { kSmartSwashType, kLineFinalSwashesOffSelector },
734 { kLigaturesType, kCommonLigaturesOffSelector }
735 };
736 sDisableLigaturesDescriptor =
737 CreateFontFeaturesDescriptor(kDisableLigatures,
738 ArrayLength(kDisableLigatures));
739 }
740 return sDisableLigaturesDescriptor;
741 }
742
743 CTFontDescriptorRef
GetIndicFeaturesDescriptor()744 gfxCoreTextShaper::GetIndicFeaturesDescriptor()
745 {
746 if (sIndicFeaturesDescriptor == nullptr) {
747 const std::pair<SInt16,SInt16> kIndicFeatures[] = {
748 { kSmartSwashType, kLineFinalSwashesOffSelector }
749 };
750 sIndicFeaturesDescriptor =
751 CreateFontFeaturesDescriptor(kIndicFeatures,
752 ArrayLength(kIndicFeatures));
753 }
754 return sIndicFeaturesDescriptor;
755 }
756
757 CTFontDescriptorRef
GetIndicDisableLigaturesDescriptor()758 gfxCoreTextShaper::GetIndicDisableLigaturesDescriptor()
759 {
760 if (sIndicDisableLigaturesDescriptor == nullptr) {
761 const std::pair<SInt16,SInt16> kIndicDisableLigatures[] = {
762 { kSmartSwashType, kLineFinalSwashesOffSelector },
763 { kLigaturesType, kCommonLigaturesOffSelector }
764 };
765 sIndicDisableLigaturesDescriptor =
766 CreateFontFeaturesDescriptor(kIndicDisableLigatures,
767 ArrayLength(kIndicDisableLigatures));
768 }
769 return sIndicDisableLigaturesDescriptor;
770 }
771
772 CTFontRef
CreateCTFontWithFeatures(CGFloat aSize,CTFontDescriptorRef aDescriptor)773 gfxCoreTextShaper::CreateCTFontWithFeatures(CGFloat aSize,
774 CTFontDescriptorRef aDescriptor)
775 {
776 gfxMacFont *f = static_cast<gfxMacFont*>(mFont);
777 return ::CTFontCreateWithGraphicsFont(f->GetCGFontRef(), aSize, nullptr,
778 aDescriptor);
779 }
780
781 void
Shutdown()782 gfxCoreTextShaper::Shutdown() // [static]
783 {
784 if (sIndicDisableLigaturesDescriptor != nullptr) {
785 ::CFRelease(sIndicDisableLigaturesDescriptor);
786 sIndicDisableLigaturesDescriptor = nullptr;
787 }
788 if (sIndicFeaturesDescriptor != nullptr) {
789 ::CFRelease(sIndicFeaturesDescriptor);
790 sIndicFeaturesDescriptor = nullptr;
791 }
792 if (sDisableLigaturesDescriptor != nullptr) {
793 ::CFRelease(sDisableLigaturesDescriptor);
794 sDisableLigaturesDescriptor = nullptr;
795 }
796 if (sDefaultFeaturesDescriptor != nullptr) {
797 ::CFRelease(sDefaultFeaturesDescriptor);
798 sDefaultFeaturesDescriptor = nullptr;
799 }
800 }
801