gfx/thebes/gfxCoreTextShaper.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/gfx/thebes/gfxCoreTextShaper.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,638 @@
     1.4 +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     1.5 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +#include "mozilla/ArrayUtils.h"
    1.10 +#include "gfxCoreTextShaper.h"
    1.11 +#include "gfxMacFont.h"
    1.12 +#include "gfxFontUtils.h"
    1.13 +#include "mozilla/gfx/2D.h"
    1.14 +
    1.15 +#include <algorithm>
    1.16 +
    1.17 +using namespace mozilla;
    1.18 +
    1.19 +// standard font descriptors that we construct the first time they're needed
    1.20 +CTFontDescriptorRef gfxCoreTextShaper::sDefaultFeaturesDescriptor = nullptr;
    1.21 +CTFontDescriptorRef gfxCoreTextShaper::sDisableLigaturesDescriptor = nullptr;
    1.22 +
    1.23 +gfxCoreTextShaper::gfxCoreTextShaper(gfxMacFont *aFont)
    1.24 +    : gfxFontShaper(aFont)
    1.25 +{
    1.26 +    // Create our CTFontRef
    1.27 +    mCTFont = ::CTFontCreateWithGraphicsFont(aFont->GetCGFontRef(),
    1.28 +                                             aFont->GetAdjustedSize(),
    1.29 +                                             nullptr,
    1.30 +                                             GetDefaultFeaturesDescriptor());
    1.31 +
    1.32 +    // Set up the default attribute dictionary that we will need each time we create a CFAttributedString
    1.33 +    mAttributesDict = ::CFDictionaryCreate(kCFAllocatorDefault,
    1.34 +                                           (const void**) &kCTFontAttributeName,
    1.35 +                                           (const void**) &mCTFont,
    1.36 +                                           1, // count of attributes
    1.37 +                                           &kCFTypeDictionaryKeyCallBacks,
    1.38 +                                           &kCFTypeDictionaryValueCallBacks);
    1.39 +}
    1.40 +
    1.41 +gfxCoreTextShaper::~gfxCoreTextShaper()
    1.42 +{
    1.43 +    if (mAttributesDict) {
    1.44 +        ::CFRelease(mAttributesDict);
    1.45 +    }
    1.46 +    if (mCTFont) {
    1.47 +        ::CFRelease(mCTFont);
    1.48 +    }
    1.49 +}
    1.50 +
    1.51 +bool
    1.52 +gfxCoreTextShaper::ShapeText(gfxContext      *aContext,
    1.53 +                             const char16_t *aText,
    1.54 +                             uint32_t         aOffset,
    1.55 +                             uint32_t         aLength,
    1.56 +                             int32_t          aScript,
    1.57 +                             gfxShapedText   *aShapedText)
    1.58 +{
    1.59 +    // Create a CFAttributedString with text and style info, so we can use CoreText to lay it out.
    1.60 +
    1.61 +    bool isRightToLeft = aShapedText->IsRightToLeft();
    1.62 +    uint32_t length = aLength;
    1.63 +
    1.64 +    // we need to bidi-wrap the text if the run is RTL,
    1.65 +    // or if it is an LTR run but may contain (overridden) RTL chars
    1.66 +    bool bidiWrap = isRightToLeft;
    1.67 +    if (!bidiWrap && !aShapedText->TextIs8Bit()) {
    1.68 +        uint32_t i;
    1.69 +        for (i = 0; i < length; ++i) {
    1.70 +            if (gfxFontUtils::PotentialRTLChar(aText[i])) {
    1.71 +                bidiWrap = true;
    1.72 +                break;
    1.73 +            }
    1.74 +        }
    1.75 +    }
    1.76 +
    1.77 +    // If there's a possibility of any bidi, we wrap the text with direction overrides
    1.78 +    // to ensure neutrals or characters that were bidi-overridden in HTML behave properly.
    1.79 +    const UniChar beginLTR[]    = { 0x202d, 0x20 };
    1.80 +    const UniChar beginRTL[]    = { 0x202e, 0x20 };
    1.81 +    const UniChar endBidiWrap[] = { 0x20, 0x2e, 0x202c };
    1.82 +
    1.83 +    uint32_t startOffset;
    1.84 +    CFStringRef stringObj;
    1.85 +    if (bidiWrap) {
    1.86 +        startOffset = isRightToLeft ?
    1.87 +            mozilla::ArrayLength(beginRTL) : mozilla::ArrayLength(beginLTR);
    1.88 +        CFMutableStringRef mutableString =
    1.89 +            ::CFStringCreateMutable(kCFAllocatorDefault,
    1.90 +                                    length + startOffset + mozilla::ArrayLength(endBidiWrap));
    1.91 +        ::CFStringAppendCharacters(mutableString,
    1.92 +                                   isRightToLeft ? beginRTL : beginLTR,
    1.93 +                                   startOffset);
    1.94 +        ::CFStringAppendCharacters(mutableString, reinterpret_cast<const UniChar*>(aText), length);
    1.95 +        ::CFStringAppendCharacters(mutableString,
    1.96 +                                   endBidiWrap, mozilla::ArrayLength(endBidiWrap));
    1.97 +        stringObj = mutableString;
    1.98 +    } else {
    1.99 +        startOffset = 0;
   1.100 +        stringObj = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
   1.101 +                                                         reinterpret_cast<const UniChar*>(aText),
   1.102 +                                                         length, kCFAllocatorNull);
   1.103 +    }
   1.104 +
   1.105 +    CFDictionaryRef attrObj;
   1.106 +    if (aShapedText->DisableLigatures()) {
   1.107 +        // For letterspacing (or maybe other situations) we need to make a copy of the CTFont
   1.108 +        // with the ligature feature disabled
   1.109 +        CTFontRef ctFont =
   1.110 +            CreateCTFontWithDisabledLigatures(::CTFontGetSize(mCTFont));
   1.111 +
   1.112 +        attrObj =
   1.113 +            ::CFDictionaryCreate(kCFAllocatorDefault,
   1.114 +                                 (const void**) &kCTFontAttributeName,
   1.115 +                                 (const void**) &ctFont,
   1.116 +                                 1, // count of attributes
   1.117 +                                 &kCFTypeDictionaryKeyCallBacks,
   1.118 +                                 &kCFTypeDictionaryValueCallBacks);
   1.119 +        // Having created the dict, we're finished with our ligature-disabled CTFontRef
   1.120 +        ::CFRelease(ctFont);
   1.121 +    } else {
   1.122 +        attrObj = mAttributesDict;
   1.123 +        ::CFRetain(attrObj);
   1.124 +    }
   1.125 +
   1.126 +    // Now we can create an attributed string
   1.127 +    CFAttributedStringRef attrStringObj =
   1.128 +        ::CFAttributedStringCreate(kCFAllocatorDefault, stringObj, attrObj);
   1.129 +    ::CFRelease(stringObj);
   1.130 +    ::CFRelease(attrObj);
   1.131 +
   1.132 +    // Create the CoreText line from our string, then we're done with it
   1.133 +    CTLineRef line = ::CTLineCreateWithAttributedString(attrStringObj);
   1.134 +    ::CFRelease(attrStringObj);
   1.135 +
   1.136 +    // and finally retrieve the glyph data and store into the gfxTextRun
   1.137 +    CFArrayRef glyphRuns = ::CTLineGetGlyphRuns(line);
   1.138 +    uint32_t numRuns = ::CFArrayGetCount(glyphRuns);
   1.139 +
   1.140 +    // Iterate through the glyph runs.
   1.141 +    // Note that this includes the bidi wrapper, so we have to be careful
   1.142 +    // not to include the extra glyphs from there
   1.143 +    bool success = true;
   1.144 +    for (uint32_t runIndex = 0; runIndex < numRuns; runIndex++) {
   1.145 +        CTRunRef aCTRun =
   1.146 +            (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex);
   1.147 +        if (SetGlyphsFromRun(aShapedText, aOffset, aLength, aCTRun, startOffset) != NS_OK) {
   1.148 +            success = false;
   1.149 +            break;
   1.150 +        }
   1.151 +    }
   1.152 +
   1.153 +    ::CFRelease(line);
   1.154 +
   1.155 +    return success;
   1.156 +}
   1.157 +
   1.158 +#define SMALL_GLYPH_RUN 128 // preallocated size of our auto arrays for per-glyph data;
   1.159 +                            // some testing indicates that 90%+ of glyph runs will fit
   1.160 +                            // without requiring a separate allocation
   1.161 +
   1.162 +nsresult
   1.163 +gfxCoreTextShaper::SetGlyphsFromRun(gfxShapedText *aShapedText,
   1.164 +                                    uint32_t       aOffset,
   1.165 +                                    uint32_t       aLength,
   1.166 +                                    CTRunRef       aCTRun,
   1.167 +                                    int32_t        aStringOffset)
   1.168 +{
   1.169 +    // The word has been bidi-wrapped; aStringOffset is the number
   1.170 +    // of chars at the beginning of the CTLine that we should skip.
   1.171 +    // aCTRun is a glyph run from the CoreText layout process.
   1.172 +
   1.173 +    int32_t direction = aShapedText->IsRightToLeft() ? -1 : 1;
   1.174 +
   1.175 +    int32_t numGlyphs = ::CTRunGetGlyphCount(aCTRun);
   1.176 +    if (numGlyphs == 0) {
   1.177 +        return NS_OK;
   1.178 +    }
   1.179 +
   1.180 +    int32_t wordLength = aLength;
   1.181 +
   1.182 +    // character offsets get really confusing here, as we have to keep track of
   1.183 +    // (a) the text in the actual textRun we're constructing
   1.184 +    // (c) the string that was handed to CoreText, which contains the text of the font run
   1.185 +    //     plus directional-override padding
   1.186 +    // (d) the CTRun currently being processed, which may be a sub-run of the CoreText line
   1.187 +    //     (but may extend beyond the actual font run into the bidi wrapping text).
   1.188 +    //     aStringOffset tells us how many initial characters of the line to ignore.
   1.189 +
   1.190 +    // get the source string range within the CTLine's text
   1.191 +    CFRange stringRange = ::CTRunGetStringRange(aCTRun);
   1.192 +    // skip the run if it is entirely outside the actual range of the font run
   1.193 +    if (stringRange.location - aStringOffset + stringRange.length <= 0 ||
   1.194 +        stringRange.location - aStringOffset >= wordLength) {
   1.195 +        return NS_OK;
   1.196 +    }
   1.197 +
   1.198 +    // retrieve the laid-out glyph data from the CTRun
   1.199 +    nsAutoArrayPtr<CGGlyph> glyphsArray;
   1.200 +    nsAutoArrayPtr<CGPoint> positionsArray;
   1.201 +    nsAutoArrayPtr<CFIndex> glyphToCharArray;
   1.202 +    const CGGlyph* glyphs = nullptr;
   1.203 +    const CGPoint* positions = nullptr;
   1.204 +    const CFIndex* glyphToChar = nullptr;
   1.205 +
   1.206 +    // Testing indicates that CTRunGetGlyphsPtr (almost?) always succeeds,
   1.207 +    // and so allocating a new array and copying data with CTRunGetGlyphs
   1.208 +    // will be extremely rare.
   1.209 +    // If this were not the case, we could use an nsAutoTArray<> to
   1.210 +    // try and avoid the heap allocation for small runs.
   1.211 +    // It's possible that some future change to CoreText will mean that
   1.212 +    // CTRunGetGlyphsPtr fails more often; if this happens, nsAutoTArray<>
   1.213 +    // may become an attractive option.
   1.214 +    glyphs = ::CTRunGetGlyphsPtr(aCTRun);
   1.215 +    if (!glyphs) {
   1.216 +        glyphsArray = new (std::nothrow) CGGlyph[numGlyphs];
   1.217 +        if (!glyphsArray) {
   1.218 +            return NS_ERROR_OUT_OF_MEMORY;
   1.219 +        }
   1.220 +        ::CTRunGetGlyphs(aCTRun, ::CFRangeMake(0, 0), glyphsArray.get());
   1.221 +        glyphs = glyphsArray.get();
   1.222 +    }
   1.223 +
   1.224 +    positions = ::CTRunGetPositionsPtr(aCTRun);
   1.225 +    if (!positions) {
   1.226 +        positionsArray = new (std::nothrow) CGPoint[numGlyphs];
   1.227 +        if (!positionsArray) {
   1.228 +            return NS_ERROR_OUT_OF_MEMORY;
   1.229 +        }
   1.230 +        ::CTRunGetPositions(aCTRun, ::CFRangeMake(0, 0), positionsArray.get());
   1.231 +        positions = positionsArray.get();
   1.232 +    }
   1.233 +
   1.234 +    // Remember that the glyphToChar indices relate to the CoreText line,
   1.235 +    // not to the beginning of the textRun, the font run,
   1.236 +    // or the stringRange of the glyph run
   1.237 +    glyphToChar = ::CTRunGetStringIndicesPtr(aCTRun);
   1.238 +    if (!glyphToChar) {
   1.239 +        glyphToCharArray = new (std::nothrow) CFIndex[numGlyphs];
   1.240 +        if (!glyphToCharArray) {
   1.241 +            return NS_ERROR_OUT_OF_MEMORY;
   1.242 +        }
   1.243 +        ::CTRunGetStringIndices(aCTRun, ::CFRangeMake(0, 0), glyphToCharArray.get());
   1.244 +        glyphToChar = glyphToCharArray.get();
   1.245 +    }
   1.246 +
   1.247 +    double runWidth = ::CTRunGetTypographicBounds(aCTRun, ::CFRangeMake(0, 0),
   1.248 +                                                  nullptr, nullptr, nullptr);
   1.249 +
   1.250 +    nsAutoTArray<gfxShapedText::DetailedGlyph,1> detailedGlyphs;
   1.251 +    gfxShapedText::CompressedGlyph g;
   1.252 +    gfxShapedText::CompressedGlyph *charGlyphs =
   1.253 +        aShapedText->GetCharacterGlyphs() + aOffset;
   1.254 +
   1.255 +    // CoreText gives us the glyphindex-to-charindex mapping, which relates each glyph
   1.256 +    // to a source text character; we also need the charindex-to-glyphindex mapping to
   1.257 +    // find the glyph for a given char. Note that some chars may not map to any glyph
   1.258 +    // (ligature continuations), and some may map to several glyphs (eg Indic split vowels).
   1.259 +    // We set the glyph index to NO_GLYPH for chars that have no associated glyph, and we
   1.260 +    // record the last glyph index for cases where the char maps to several glyphs,
   1.261 +    // so that our clumping will include all the glyph fragments for the character.
   1.262 +
   1.263 +    // The charToGlyph array is indexed by char position within the stringRange of the glyph run.
   1.264 +
   1.265 +    static const int32_t NO_GLYPH = -1;
   1.266 +    AutoFallibleTArray<int32_t,SMALL_GLYPH_RUN> charToGlyphArray;
   1.267 +    if (!charToGlyphArray.SetLength(stringRange.length)) {
   1.268 +        return NS_ERROR_OUT_OF_MEMORY;
   1.269 +    }
   1.270 +    int32_t *charToGlyph = charToGlyphArray.Elements();
   1.271 +    for (int32_t offset = 0; offset < stringRange.length; ++offset) {
   1.272 +        charToGlyph[offset] = NO_GLYPH;
   1.273 +    }
   1.274 +    for (int32_t i = 0; i < numGlyphs; ++i) {
   1.275 +        int32_t loc = glyphToChar[i] - stringRange.location;
   1.276 +        if (loc >= 0 && loc < stringRange.length) {
   1.277 +            charToGlyph[loc] = i;
   1.278 +        }
   1.279 +    }
   1.280 +
   1.281 +    // Find character and glyph clumps that correspond, allowing for ligatures,
   1.282 +    // indic reordering, split glyphs, etc.
   1.283 +    //
   1.284 +    // The idea is that we'll find a character sequence starting at the first char of stringRange,
   1.285 +    // and extend it until it includes the character associated with the first glyph;
   1.286 +    // we also extend it as long as there are "holes" in the range of glyphs. So we
   1.287 +    // will eventually have a contiguous sequence of characters, starting at the beginning
   1.288 +    // of the range, that map to a contiguous sequence of glyphs, starting at the beginning
   1.289 +    // of the glyph array. That's a clump; then we update the starting positions and repeat.
   1.290 +    //
   1.291 +    // NB: In the case of RTL layouts, we iterate over the stringRange in reverse.
   1.292 +    //
   1.293 +
   1.294 +    // This may find characters that fall outside the range 0:wordLength,
   1.295 +    // so we won't necessarily use everything we find here.
   1.296 +
   1.297 +    bool isRightToLeft = aShapedText->IsRightToLeft();
   1.298 +    int32_t glyphStart = 0; // looking for a clump that starts at this glyph index
   1.299 +    int32_t charStart = isRightToLeft ?
   1.300 +        stringRange.length - 1 : 0; // and this char index (in the stringRange of the glyph run)
   1.301 +
   1.302 +    while (glyphStart < numGlyphs) { // keep finding groups until all glyphs are accounted for
   1.303 +        bool inOrder = true;
   1.304 +        int32_t charEnd = glyphToChar[glyphStart] - stringRange.location;
   1.305 +        NS_WARN_IF_FALSE(charEnd >= 0 && charEnd < stringRange.length,
   1.306 +                         "glyph-to-char mapping points outside string range");
   1.307 +        // clamp charEnd to the valid range of the string
   1.308 +        charEnd = std::max(charEnd, 0);
   1.309 +        charEnd = std::min(charEnd, int32_t(stringRange.length));
   1.310 +
   1.311 +        int32_t glyphEnd = glyphStart;
   1.312 +        int32_t charLimit = isRightToLeft ? -1 : stringRange.length;
   1.313 +        do {
   1.314 +            // This is normally executed once for each iteration of the outer loop,
   1.315 +            // but in unusual cases where the character/glyph association is complex,
   1.316 +            // the initial character range might correspond to a non-contiguous
   1.317 +            // glyph range with "holes" in it. If so, we will repeat this loop to
   1.318 +            // extend the character range until we have a contiguous glyph sequence.
   1.319 +            NS_ASSERTION((direction > 0 && charEnd < charLimit) ||
   1.320 +                         (direction < 0 && charEnd > charLimit),
   1.321 +                         "no characters left in range?");
   1.322 +            charEnd += direction;
   1.323 +            while (charEnd != charLimit && charToGlyph[charEnd] == NO_GLYPH) {
   1.324 +                charEnd += direction;
   1.325 +            }
   1.326 +
   1.327 +            // find the maximum glyph index covered by the clump so far
   1.328 +            if (isRightToLeft) {
   1.329 +                for (int32_t i = charStart; i > charEnd; --i) {
   1.330 +                    if (charToGlyph[i] != NO_GLYPH) {
   1.331 +                        // update extent of glyph range
   1.332 +                        glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1);
   1.333 +                    }
   1.334 +                }
   1.335 +            } else {
   1.336 +                for (int32_t i = charStart; i < charEnd; ++i) {
   1.337 +                    if (charToGlyph[i] != NO_GLYPH) {
   1.338 +                        // update extent of glyph range
   1.339 +                        glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1);
   1.340 +                    }
   1.341 +                }
   1.342 +            }
   1.343 +
   1.344 +            if (glyphEnd == glyphStart + 1) {
   1.345 +                // for the common case of a single-glyph clump, we can skip the following checks
   1.346 +                break;
   1.347 +            }
   1.348 +
   1.349 +            if (glyphEnd == glyphStart) {
   1.350 +                // no glyphs, try to extend the clump
   1.351 +                continue;
   1.352 +            }
   1.353 +
   1.354 +            // check whether all glyphs in the range are associated with the characters
   1.355 +            // in our clump; if not, we have a discontinuous range, and should extend it
   1.356 +            // unless we've reached the end of the text
   1.357 +            bool allGlyphsAreWithinCluster = true;
   1.358 +            int32_t prevGlyphCharIndex = charStart;
   1.359 +            for (int32_t i = glyphStart; i < glyphEnd; ++i) {
   1.360 +                int32_t glyphCharIndex = glyphToChar[i] - stringRange.location;
   1.361 +                if (isRightToLeft) {
   1.362 +                    if (glyphCharIndex > charStart || glyphCharIndex <= charEnd) {
   1.363 +                        allGlyphsAreWithinCluster = false;
   1.364 +                        break;
   1.365 +                    }
   1.366 +                    if (glyphCharIndex > prevGlyphCharIndex) {
   1.367 +                        inOrder = false;
   1.368 +                    }
   1.369 +                    prevGlyphCharIndex = glyphCharIndex;
   1.370 +                } else {
   1.371 +                    if (glyphCharIndex < charStart || glyphCharIndex >= charEnd) {
   1.372 +                        allGlyphsAreWithinCluster = false;
   1.373 +                        break;
   1.374 +                    }
   1.375 +                    if (glyphCharIndex < prevGlyphCharIndex) {
   1.376 +                        inOrder = false;
   1.377 +                    }
   1.378 +                    prevGlyphCharIndex = glyphCharIndex;
   1.379 +                }
   1.380 +            }
   1.381 +            if (allGlyphsAreWithinCluster) {
   1.382 +                break;
   1.383 +            }
   1.384 +        } while (charEnd != charLimit);
   1.385 +
   1.386 +        NS_WARN_IF_FALSE(glyphStart < glyphEnd,
   1.387 +                         "character/glyph clump contains no glyphs!");
   1.388 +        if (glyphStart == glyphEnd) {
   1.389 +            ++glyphStart; // make progress - avoid potential infinite loop
   1.390 +            charStart = charEnd;
   1.391 +            continue;
   1.392 +        }
   1.393 +
   1.394 +        NS_WARN_IF_FALSE(charStart != charEnd,
   1.395 +                         "character/glyph clump contains no characters!");
   1.396 +        if (charStart == charEnd) {
   1.397 +            glyphStart = glyphEnd; // this is bad - we'll discard the glyph(s),
   1.398 +                                   // as there's nowhere to attach them
   1.399 +            continue;
   1.400 +        }
   1.401 +
   1.402 +        // Now charStart..charEnd is a ligature clump, corresponding to glyphStart..glyphEnd;
   1.403 +        // Set baseCharIndex to the char we'll actually attach the glyphs to (1st of ligature),
   1.404 +        // and endCharIndex to the limit (position beyond the last char),
   1.405 +        // adjusting for the offset of the stringRange relative to the textRun.
   1.406 +        int32_t baseCharIndex, endCharIndex;
   1.407 +        if (isRightToLeft) {
   1.408 +            while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH) {
   1.409 +                charEnd--;
   1.410 +            }
   1.411 +            baseCharIndex = charEnd + stringRange.location - aStringOffset + 1;
   1.412 +            endCharIndex = charStart + stringRange.location - aStringOffset + 1;
   1.413 +        } else {
   1.414 +            while (charEnd < stringRange.length && charToGlyph[charEnd] == NO_GLYPH) {
   1.415 +                charEnd++;
   1.416 +            }
   1.417 +            baseCharIndex = charStart + stringRange.location - aStringOffset;
   1.418 +            endCharIndex = charEnd + stringRange.location - aStringOffset;
   1.419 +        }
   1.420 +
   1.421 +        // Then we check if the clump falls outside our actual string range; if so, just go to the next.
   1.422 +        if (endCharIndex <= 0 || baseCharIndex >= wordLength) {
   1.423 +            glyphStart = glyphEnd;
   1.424 +            charStart = charEnd;
   1.425 +            continue;
   1.426 +        }
   1.427 +        // Ensure we won't try to go beyond the valid length of the word's text
   1.428 +        baseCharIndex = std::max(baseCharIndex, 0);
   1.429 +        endCharIndex = std::min(endCharIndex, wordLength);
   1.430 +
   1.431 +        // Now we're ready to set the glyph info in the textRun; measure the glyph width
   1.432 +        // of the first (perhaps only) glyph, to see if it is "Simple"
   1.433 +        int32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit();
   1.434 +        double toNextGlyph;
   1.435 +        if (glyphStart < numGlyphs-1) {
   1.436 +            toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x;
   1.437 +        } else {
   1.438 +            toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x;
   1.439 +        }
   1.440 +        int32_t advance = int32_t(toNextGlyph * appUnitsPerDevUnit);
   1.441 +
   1.442 +        // Check if it's a simple one-to-one mapping
   1.443 +        int32_t glyphsInClump = glyphEnd - glyphStart;
   1.444 +        if (glyphsInClump == 1 &&
   1.445 +            gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyphs[glyphStart]) &&
   1.446 +            gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
   1.447 +            charGlyphs[baseCharIndex].IsClusterStart() &&
   1.448 +            positions[glyphStart].y == 0.0)
   1.449 +        {
   1.450 +            charGlyphs[baseCharIndex].SetSimpleGlyph(advance,
   1.451 +                                                     glyphs[glyphStart]);
   1.452 +        } else {
   1.453 +            // collect all glyphs in a list to be assigned to the first char;
   1.454 +            // there must be at least one in the clump, and we already measured its advance,
   1.455 +            // hence the placement of the loop-exit test and the measurement of the next glyph
   1.456 +            while (1) {
   1.457 +                gfxTextRun::DetailedGlyph *details = detailedGlyphs.AppendElement();
   1.458 +                details->mGlyphID = glyphs[glyphStart];
   1.459 +                details->mXOffset = 0;
   1.460 +                details->mYOffset = -positions[glyphStart].y * appUnitsPerDevUnit;
   1.461 +                details->mAdvance = advance;
   1.462 +                if (++glyphStart >= glyphEnd) {
   1.463 +                   break;
   1.464 +                }
   1.465 +                if (glyphStart < numGlyphs-1) {
   1.466 +                    toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x;
   1.467 +                } else {
   1.468 +                    toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x;
   1.469 +                }
   1.470 +                advance = int32_t(toNextGlyph * appUnitsPerDevUnit);
   1.471 +            }
   1.472 +
   1.473 +            gfxTextRun::CompressedGlyph g;
   1.474 +            g.SetComplex(charGlyphs[baseCharIndex].IsClusterStart(),
   1.475 +                         true, detailedGlyphs.Length());
   1.476 +            aShapedText->SetGlyphs(aOffset + baseCharIndex, g, detailedGlyphs.Elements());
   1.477 +
   1.478 +            detailedGlyphs.Clear();
   1.479 +        }
   1.480 +
   1.481 +        // the rest of the chars in the group are ligature continuations, no associated glyphs
   1.482 +        while (++baseCharIndex != endCharIndex && baseCharIndex < wordLength) {
   1.483 +            gfxShapedText::CompressedGlyph &g = charGlyphs[baseCharIndex];
   1.484 +            NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph");
   1.485 +            g.SetComplex(inOrder && g.IsClusterStart(), false, 0);
   1.486 +        }
   1.487 +
   1.488 +        glyphStart = glyphEnd;
   1.489 +        charStart = charEnd;
   1.490 +    }
   1.491 +
   1.492 +    return NS_OK;
   1.493 +}
   1.494 +
   1.495 +#undef SMALL_GLYPH_RUN
   1.496 +
   1.497 +// Construct the font attribute descriptor that we'll apply by default when creating a CTFontRef.
   1.498 +// This will turn off line-edge swashes by default, because we don't know the actual line breaks
   1.499 +// when doing glyph shaping.
   1.500 +void
   1.501 +gfxCoreTextShaper::CreateDefaultFeaturesDescriptor()
   1.502 +{
   1.503 +    if (sDefaultFeaturesDescriptor != nullptr) {
   1.504 +        return;
   1.505 +    }
   1.506 +
   1.507 +    SInt16 val = kSmartSwashType;
   1.508 +    CFNumberRef swashesType =
   1.509 +        ::CFNumberCreate(kCFAllocatorDefault,
   1.510 +                         kCFNumberSInt16Type,
   1.511 +                         &val);
   1.512 +    val = kLineInitialSwashesOffSelector;
   1.513 +    CFNumberRef lineInitialsOffSelector =
   1.514 +        ::CFNumberCreate(kCFAllocatorDefault,
   1.515 +                         kCFNumberSInt16Type,
   1.516 +                         &val);
   1.517 +
   1.518 +    CFTypeRef keys[]   = { kCTFontFeatureTypeIdentifierKey,
   1.519 +                           kCTFontFeatureSelectorIdentifierKey };
   1.520 +    CFTypeRef values[] = { swashesType,
   1.521 +                           lineInitialsOffSelector };
   1.522 +    CFDictionaryRef featureSettings[2];
   1.523 +    featureSettings[0] =
   1.524 +        ::CFDictionaryCreate(kCFAllocatorDefault,
   1.525 +                             (const void **) keys,
   1.526 +                             (const void **) values,
   1.527 +                             ArrayLength(keys),
   1.528 +                             &kCFTypeDictionaryKeyCallBacks,
   1.529 +                             &kCFTypeDictionaryValueCallBacks);
   1.530 +    ::CFRelease(lineInitialsOffSelector);
   1.531 +
   1.532 +    val = kLineFinalSwashesOffSelector;
   1.533 +    CFNumberRef lineFinalsOffSelector =
   1.534 +        ::CFNumberCreate(kCFAllocatorDefault,
   1.535 +                         kCFNumberSInt16Type,
   1.536 +                         &val);
   1.537 +    values[1] = lineFinalsOffSelector;
   1.538 +    featureSettings[1] =
   1.539 +        ::CFDictionaryCreate(kCFAllocatorDefault,
   1.540 +                             (const void **) keys,
   1.541 +                             (const void **) values,
   1.542 +                             ArrayLength(keys),
   1.543 +                             &kCFTypeDictionaryKeyCallBacks,
   1.544 +                             &kCFTypeDictionaryValueCallBacks);
   1.545 +    ::CFRelease(lineFinalsOffSelector);
   1.546 +    ::CFRelease(swashesType);
   1.547 +
   1.548 +    CFArrayRef featuresArray =
   1.549 +        ::CFArrayCreate(kCFAllocatorDefault,
   1.550 +                        (const void **) featureSettings,
   1.551 +                        ArrayLength(featureSettings),
   1.552 +                        &kCFTypeArrayCallBacks);
   1.553 +    ::CFRelease(featureSettings[0]);
   1.554 +    ::CFRelease(featureSettings[1]);
   1.555 +
   1.556 +    const CFTypeRef attrKeys[]   = { kCTFontFeatureSettingsAttribute };
   1.557 +    const CFTypeRef attrValues[] = { featuresArray };
   1.558 +    CFDictionaryRef attributesDict =
   1.559 +        ::CFDictionaryCreate(kCFAllocatorDefault,
   1.560 +                             (const void **) attrKeys,
   1.561 +                             (const void **) attrValues,
   1.562 +                             ArrayLength(attrKeys),
   1.563 +                             &kCFTypeDictionaryKeyCallBacks,
   1.564 +                             &kCFTypeDictionaryValueCallBacks);
   1.565 +    ::CFRelease(featuresArray);
   1.566 +
   1.567 +    sDefaultFeaturesDescriptor =
   1.568 +        ::CTFontDescriptorCreateWithAttributes(attributesDict);
   1.569 +    ::CFRelease(attributesDict);
   1.570 +}
   1.571 +
   1.572 +// Create a CTFontRef, with the Common Ligatures feature disabled
   1.573 +CTFontRef
   1.574 +gfxCoreTextShaper::CreateCTFontWithDisabledLigatures(CGFloat aSize)
   1.575 +{
   1.576 +    if (sDisableLigaturesDescriptor == nullptr) {
   1.577 +        // initialize cached descriptor to turn off the Common Ligatures feature
   1.578 +        SInt16 val = kLigaturesType;
   1.579 +        CFNumberRef ligaturesType =
   1.580 +            ::CFNumberCreate(kCFAllocatorDefault,
   1.581 +                             kCFNumberSInt16Type,
   1.582 +                             &val);
   1.583 +        val = kCommonLigaturesOffSelector;
   1.584 +        CFNumberRef commonLigaturesOffSelector =
   1.585 +            ::CFNumberCreate(kCFAllocatorDefault,
   1.586 +                             kCFNumberSInt16Type,
   1.587 +                             &val);
   1.588 +
   1.589 +        const CFTypeRef keys[]   = { kCTFontFeatureTypeIdentifierKey,
   1.590 +                                     kCTFontFeatureSelectorIdentifierKey };
   1.591 +        const CFTypeRef values[] = { ligaturesType,
   1.592 +                                     commonLigaturesOffSelector };
   1.593 +        CFDictionaryRef featureSettingDict =
   1.594 +            ::CFDictionaryCreate(kCFAllocatorDefault,
   1.595 +                                 (const void **) keys,
   1.596 +                                 (const void **) values,
   1.597 +                                 ArrayLength(keys),
   1.598 +                                 &kCFTypeDictionaryKeyCallBacks,
   1.599 +                                 &kCFTypeDictionaryValueCallBacks);
   1.600 +        ::CFRelease(ligaturesType);
   1.601 +        ::CFRelease(commonLigaturesOffSelector);
   1.602 +
   1.603 +        CFArrayRef featuresArray =
   1.604 +            ::CFArrayCreate(kCFAllocatorDefault,
   1.605 +                            (const void **) &featureSettingDict,
   1.606 +                            1,
   1.607 +                            &kCFTypeArrayCallBacks);
   1.608 +        ::CFRelease(featureSettingDict);
   1.609 +
   1.610 +        CFDictionaryRef attributesDict =
   1.611 +            ::CFDictionaryCreate(kCFAllocatorDefault,
   1.612 +                                 (const void **) &kCTFontFeatureSettingsAttribute,
   1.613 +                                 (const void **) &featuresArray,
   1.614 +                                 1, // count of keys & values
   1.615 +                                 &kCFTypeDictionaryKeyCallBacks,
   1.616 +                                 &kCFTypeDictionaryValueCallBacks);
   1.617 +        ::CFRelease(featuresArray);
   1.618 +
   1.619 +        sDisableLigaturesDescriptor =
   1.620 +            ::CTFontDescriptorCreateCopyWithAttributes(GetDefaultFeaturesDescriptor(),
   1.621 +                                                       attributesDict);
   1.622 +        ::CFRelease(attributesDict);
   1.623 +    }
   1.624 +
   1.625 +    gfxMacFont *f = static_cast<gfxMacFont*>(mFont);
   1.626 +    return ::CTFontCreateWithGraphicsFont(f->GetCGFontRef(), aSize, nullptr,
   1.627 +                                          sDisableLigaturesDescriptor);
   1.628 +}
   1.629 +
   1.630 +void
   1.631 +gfxCoreTextShaper::Shutdown() // [static]
   1.632 +{
   1.633 +    if (sDisableLigaturesDescriptor != nullptr) {
   1.634 +        ::CFRelease(sDisableLigaturesDescriptor);
   1.635 +        sDisableLigaturesDescriptor = nullptr;
   1.636 +    }        
   1.637 +    if (sDefaultFeaturesDescriptor != nullptr) {
   1.638 +        ::CFRelease(sDefaultFeaturesDescriptor);
   1.639 +        sDefaultFeaturesDescriptor = nullptr;
   1.640 +    }
   1.641 +}

mercurial