michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "gfxCoreTextShaper.h" michael@0: #include "gfxMacFont.h" michael@0: #include "gfxFontUtils.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: michael@0: // standard font descriptors that we construct the first time they're needed michael@0: CTFontDescriptorRef gfxCoreTextShaper::sDefaultFeaturesDescriptor = nullptr; michael@0: CTFontDescriptorRef gfxCoreTextShaper::sDisableLigaturesDescriptor = nullptr; michael@0: michael@0: gfxCoreTextShaper::gfxCoreTextShaper(gfxMacFont *aFont) michael@0: : gfxFontShaper(aFont) michael@0: { michael@0: // Create our CTFontRef michael@0: mCTFont = ::CTFontCreateWithGraphicsFont(aFont->GetCGFontRef(), michael@0: aFont->GetAdjustedSize(), michael@0: nullptr, michael@0: GetDefaultFeaturesDescriptor()); michael@0: michael@0: // Set up the default attribute dictionary that we will need each time we create a CFAttributedString michael@0: mAttributesDict = ::CFDictionaryCreate(kCFAllocatorDefault, michael@0: (const void**) &kCTFontAttributeName, michael@0: (const void**) &mCTFont, michael@0: 1, // count of attributes michael@0: &kCFTypeDictionaryKeyCallBacks, michael@0: &kCFTypeDictionaryValueCallBacks); michael@0: } michael@0: michael@0: gfxCoreTextShaper::~gfxCoreTextShaper() michael@0: { michael@0: if (mAttributesDict) { michael@0: ::CFRelease(mAttributesDict); michael@0: } michael@0: if (mCTFont) { michael@0: ::CFRelease(mCTFont); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: gfxCoreTextShaper::ShapeText(gfxContext *aContext, michael@0: const char16_t *aText, michael@0: uint32_t aOffset, michael@0: uint32_t aLength, michael@0: int32_t aScript, michael@0: gfxShapedText *aShapedText) michael@0: { michael@0: // Create a CFAttributedString with text and style info, so we can use CoreText to lay it out. michael@0: michael@0: bool isRightToLeft = aShapedText->IsRightToLeft(); michael@0: uint32_t length = aLength; michael@0: michael@0: // we need to bidi-wrap the text if the run is RTL, michael@0: // or if it is an LTR run but may contain (overridden) RTL chars michael@0: bool bidiWrap = isRightToLeft; michael@0: if (!bidiWrap && !aShapedText->TextIs8Bit()) { michael@0: uint32_t i; michael@0: for (i = 0; i < length; ++i) { michael@0: if (gfxFontUtils::PotentialRTLChar(aText[i])) { michael@0: bidiWrap = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If there's a possibility of any bidi, we wrap the text with direction overrides michael@0: // to ensure neutrals or characters that were bidi-overridden in HTML behave properly. michael@0: const UniChar beginLTR[] = { 0x202d, 0x20 }; michael@0: const UniChar beginRTL[] = { 0x202e, 0x20 }; michael@0: const UniChar endBidiWrap[] = { 0x20, 0x2e, 0x202c }; michael@0: michael@0: uint32_t startOffset; michael@0: CFStringRef stringObj; michael@0: if (bidiWrap) { michael@0: startOffset = isRightToLeft ? michael@0: mozilla::ArrayLength(beginRTL) : mozilla::ArrayLength(beginLTR); michael@0: CFMutableStringRef mutableString = michael@0: ::CFStringCreateMutable(kCFAllocatorDefault, michael@0: length + startOffset + mozilla::ArrayLength(endBidiWrap)); michael@0: ::CFStringAppendCharacters(mutableString, michael@0: isRightToLeft ? beginRTL : beginLTR, michael@0: startOffset); michael@0: ::CFStringAppendCharacters(mutableString, reinterpret_cast(aText), length); michael@0: ::CFStringAppendCharacters(mutableString, michael@0: endBidiWrap, mozilla::ArrayLength(endBidiWrap)); michael@0: stringObj = mutableString; michael@0: } else { michael@0: startOffset = 0; michael@0: stringObj = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, michael@0: reinterpret_cast(aText), michael@0: length, kCFAllocatorNull); michael@0: } michael@0: michael@0: CFDictionaryRef attrObj; michael@0: if (aShapedText->DisableLigatures()) { michael@0: // For letterspacing (or maybe other situations) we need to make a copy of the CTFont michael@0: // with the ligature feature disabled michael@0: CTFontRef ctFont = michael@0: CreateCTFontWithDisabledLigatures(::CTFontGetSize(mCTFont)); michael@0: michael@0: attrObj = michael@0: ::CFDictionaryCreate(kCFAllocatorDefault, michael@0: (const void**) &kCTFontAttributeName, michael@0: (const void**) &ctFont, michael@0: 1, // count of attributes michael@0: &kCFTypeDictionaryKeyCallBacks, michael@0: &kCFTypeDictionaryValueCallBacks); michael@0: // Having created the dict, we're finished with our ligature-disabled CTFontRef michael@0: ::CFRelease(ctFont); michael@0: } else { michael@0: attrObj = mAttributesDict; michael@0: ::CFRetain(attrObj); michael@0: } michael@0: michael@0: // Now we can create an attributed string michael@0: CFAttributedStringRef attrStringObj = michael@0: ::CFAttributedStringCreate(kCFAllocatorDefault, stringObj, attrObj); michael@0: ::CFRelease(stringObj); michael@0: ::CFRelease(attrObj); michael@0: michael@0: // Create the CoreText line from our string, then we're done with it michael@0: CTLineRef line = ::CTLineCreateWithAttributedString(attrStringObj); michael@0: ::CFRelease(attrStringObj); michael@0: michael@0: // and finally retrieve the glyph data and store into the gfxTextRun michael@0: CFArrayRef glyphRuns = ::CTLineGetGlyphRuns(line); michael@0: uint32_t numRuns = ::CFArrayGetCount(glyphRuns); michael@0: michael@0: // Iterate through the glyph runs. michael@0: // Note that this includes the bidi wrapper, so we have to be careful michael@0: // not to include the extra glyphs from there michael@0: bool success = true; michael@0: for (uint32_t runIndex = 0; runIndex < numRuns; runIndex++) { michael@0: CTRunRef aCTRun = michael@0: (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex); michael@0: if (SetGlyphsFromRun(aShapedText, aOffset, aLength, aCTRun, startOffset) != NS_OK) { michael@0: success = false; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: ::CFRelease(line); michael@0: michael@0: return success; michael@0: } michael@0: michael@0: #define SMALL_GLYPH_RUN 128 // preallocated size of our auto arrays for per-glyph data; michael@0: // some testing indicates that 90%+ of glyph runs will fit michael@0: // without requiring a separate allocation michael@0: michael@0: nsresult michael@0: gfxCoreTextShaper::SetGlyphsFromRun(gfxShapedText *aShapedText, michael@0: uint32_t aOffset, michael@0: uint32_t aLength, michael@0: CTRunRef aCTRun, michael@0: int32_t aStringOffset) michael@0: { michael@0: // The word has been bidi-wrapped; aStringOffset is the number michael@0: // of chars at the beginning of the CTLine that we should skip. michael@0: // aCTRun is a glyph run from the CoreText layout process. michael@0: michael@0: int32_t direction = aShapedText->IsRightToLeft() ? -1 : 1; michael@0: michael@0: int32_t numGlyphs = ::CTRunGetGlyphCount(aCTRun); michael@0: if (numGlyphs == 0) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: int32_t wordLength = aLength; michael@0: michael@0: // character offsets get really confusing here, as we have to keep track of michael@0: // (a) the text in the actual textRun we're constructing michael@0: // (c) the string that was handed to CoreText, which contains the text of the font run michael@0: // plus directional-override padding michael@0: // (d) the CTRun currently being processed, which may be a sub-run of the CoreText line michael@0: // (but may extend beyond the actual font run into the bidi wrapping text). michael@0: // aStringOffset tells us how many initial characters of the line to ignore. michael@0: michael@0: // get the source string range within the CTLine's text michael@0: CFRange stringRange = ::CTRunGetStringRange(aCTRun); michael@0: // skip the run if it is entirely outside the actual range of the font run michael@0: if (stringRange.location - aStringOffset + stringRange.length <= 0 || michael@0: stringRange.location - aStringOffset >= wordLength) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // retrieve the laid-out glyph data from the CTRun michael@0: nsAutoArrayPtr glyphsArray; michael@0: nsAutoArrayPtr positionsArray; michael@0: nsAutoArrayPtr glyphToCharArray; michael@0: const CGGlyph* glyphs = nullptr; michael@0: const CGPoint* positions = nullptr; michael@0: const CFIndex* glyphToChar = nullptr; michael@0: michael@0: // Testing indicates that CTRunGetGlyphsPtr (almost?) always succeeds, michael@0: // and so allocating a new array and copying data with CTRunGetGlyphs michael@0: // will be extremely rare. michael@0: // If this were not the case, we could use an nsAutoTArray<> to michael@0: // try and avoid the heap allocation for small runs. michael@0: // It's possible that some future change to CoreText will mean that michael@0: // CTRunGetGlyphsPtr fails more often; if this happens, nsAutoTArray<> michael@0: // may become an attractive option. michael@0: glyphs = ::CTRunGetGlyphsPtr(aCTRun); michael@0: if (!glyphs) { michael@0: glyphsArray = new (std::nothrow) CGGlyph[numGlyphs]; michael@0: if (!glyphsArray) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: ::CTRunGetGlyphs(aCTRun, ::CFRangeMake(0, 0), glyphsArray.get()); michael@0: glyphs = glyphsArray.get(); michael@0: } michael@0: michael@0: positions = ::CTRunGetPositionsPtr(aCTRun); michael@0: if (!positions) { michael@0: positionsArray = new (std::nothrow) CGPoint[numGlyphs]; michael@0: if (!positionsArray) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: ::CTRunGetPositions(aCTRun, ::CFRangeMake(0, 0), positionsArray.get()); michael@0: positions = positionsArray.get(); michael@0: } michael@0: michael@0: // Remember that the glyphToChar indices relate to the CoreText line, michael@0: // not to the beginning of the textRun, the font run, michael@0: // or the stringRange of the glyph run michael@0: glyphToChar = ::CTRunGetStringIndicesPtr(aCTRun); michael@0: if (!glyphToChar) { michael@0: glyphToCharArray = new (std::nothrow) CFIndex[numGlyphs]; michael@0: if (!glyphToCharArray) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: ::CTRunGetStringIndices(aCTRun, ::CFRangeMake(0, 0), glyphToCharArray.get()); michael@0: glyphToChar = glyphToCharArray.get(); michael@0: } michael@0: michael@0: double runWidth = ::CTRunGetTypographicBounds(aCTRun, ::CFRangeMake(0, 0), michael@0: nullptr, nullptr, nullptr); michael@0: michael@0: nsAutoTArray detailedGlyphs; michael@0: gfxShapedText::CompressedGlyph g; michael@0: gfxShapedText::CompressedGlyph *charGlyphs = michael@0: aShapedText->GetCharacterGlyphs() + aOffset; michael@0: michael@0: // CoreText gives us the glyphindex-to-charindex mapping, which relates each glyph michael@0: // to a source text character; we also need the charindex-to-glyphindex mapping to michael@0: // find the glyph for a given char. Note that some chars may not map to any glyph michael@0: // (ligature continuations), and some may map to several glyphs (eg Indic split vowels). michael@0: // We set the glyph index to NO_GLYPH for chars that have no associated glyph, and we michael@0: // record the last glyph index for cases where the char maps to several glyphs, michael@0: // so that our clumping will include all the glyph fragments for the character. michael@0: michael@0: // The charToGlyph array is indexed by char position within the stringRange of the glyph run. michael@0: michael@0: static const int32_t NO_GLYPH = -1; michael@0: AutoFallibleTArray charToGlyphArray; michael@0: if (!charToGlyphArray.SetLength(stringRange.length)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: int32_t *charToGlyph = charToGlyphArray.Elements(); michael@0: for (int32_t offset = 0; offset < stringRange.length; ++offset) { michael@0: charToGlyph[offset] = NO_GLYPH; michael@0: } michael@0: for (int32_t i = 0; i < numGlyphs; ++i) { michael@0: int32_t loc = glyphToChar[i] - stringRange.location; michael@0: if (loc >= 0 && loc < stringRange.length) { michael@0: charToGlyph[loc] = i; michael@0: } michael@0: } michael@0: michael@0: // Find character and glyph clumps that correspond, allowing for ligatures, michael@0: // indic reordering, split glyphs, etc. michael@0: // michael@0: // The idea is that we'll find a character sequence starting at the first char of stringRange, michael@0: // and extend it until it includes the character associated with the first glyph; michael@0: // we also extend it as long as there are "holes" in the range of glyphs. So we michael@0: // will eventually have a contiguous sequence of characters, starting at the beginning michael@0: // of the range, that map to a contiguous sequence of glyphs, starting at the beginning michael@0: // of the glyph array. That's a clump; then we update the starting positions and repeat. michael@0: // michael@0: // NB: In the case of RTL layouts, we iterate over the stringRange in reverse. michael@0: // michael@0: michael@0: // This may find characters that fall outside the range 0:wordLength, michael@0: // so we won't necessarily use everything we find here. michael@0: michael@0: bool isRightToLeft = aShapedText->IsRightToLeft(); michael@0: int32_t glyphStart = 0; // looking for a clump that starts at this glyph index michael@0: int32_t charStart = isRightToLeft ? michael@0: stringRange.length - 1 : 0; // and this char index (in the stringRange of the glyph run) michael@0: michael@0: while (glyphStart < numGlyphs) { // keep finding groups until all glyphs are accounted for michael@0: bool inOrder = true; michael@0: int32_t charEnd = glyphToChar[glyphStart] - stringRange.location; michael@0: NS_WARN_IF_FALSE(charEnd >= 0 && charEnd < stringRange.length, michael@0: "glyph-to-char mapping points outside string range"); michael@0: // clamp charEnd to the valid range of the string michael@0: charEnd = std::max(charEnd, 0); michael@0: charEnd = std::min(charEnd, int32_t(stringRange.length)); michael@0: michael@0: int32_t glyphEnd = glyphStart; michael@0: int32_t charLimit = isRightToLeft ? -1 : stringRange.length; michael@0: do { michael@0: // This is normally executed once for each iteration of the outer loop, michael@0: // but in unusual cases where the character/glyph association is complex, michael@0: // the initial character range might correspond to a non-contiguous michael@0: // glyph range with "holes" in it. If so, we will repeat this loop to michael@0: // extend the character range until we have a contiguous glyph sequence. michael@0: NS_ASSERTION((direction > 0 && charEnd < charLimit) || michael@0: (direction < 0 && charEnd > charLimit), michael@0: "no characters left in range?"); michael@0: charEnd += direction; michael@0: while (charEnd != charLimit && charToGlyph[charEnd] == NO_GLYPH) { michael@0: charEnd += direction; michael@0: } michael@0: michael@0: // find the maximum glyph index covered by the clump so far michael@0: if (isRightToLeft) { michael@0: for (int32_t i = charStart; i > charEnd; --i) { michael@0: if (charToGlyph[i] != NO_GLYPH) { michael@0: // update extent of glyph range michael@0: glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1); michael@0: } michael@0: } michael@0: } else { michael@0: for (int32_t i = charStart; i < charEnd; ++i) { michael@0: if (charToGlyph[i] != NO_GLYPH) { michael@0: // update extent of glyph range michael@0: glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (glyphEnd == glyphStart + 1) { michael@0: // for the common case of a single-glyph clump, we can skip the following checks michael@0: break; michael@0: } michael@0: michael@0: if (glyphEnd == glyphStart) { michael@0: // no glyphs, try to extend the clump michael@0: continue; michael@0: } michael@0: michael@0: // check whether all glyphs in the range are associated with the characters michael@0: // in our clump; if not, we have a discontinuous range, and should extend it michael@0: // unless we've reached the end of the text michael@0: bool allGlyphsAreWithinCluster = true; michael@0: int32_t prevGlyphCharIndex = charStart; michael@0: for (int32_t i = glyphStart; i < glyphEnd; ++i) { michael@0: int32_t glyphCharIndex = glyphToChar[i] - stringRange.location; michael@0: if (isRightToLeft) { michael@0: if (glyphCharIndex > charStart || glyphCharIndex <= charEnd) { michael@0: allGlyphsAreWithinCluster = false; michael@0: break; michael@0: } michael@0: if (glyphCharIndex > prevGlyphCharIndex) { michael@0: inOrder = false; michael@0: } michael@0: prevGlyphCharIndex = glyphCharIndex; michael@0: } else { michael@0: if (glyphCharIndex < charStart || glyphCharIndex >= charEnd) { michael@0: allGlyphsAreWithinCluster = false; michael@0: break; michael@0: } michael@0: if (glyphCharIndex < prevGlyphCharIndex) { michael@0: inOrder = false; michael@0: } michael@0: prevGlyphCharIndex = glyphCharIndex; michael@0: } michael@0: } michael@0: if (allGlyphsAreWithinCluster) { michael@0: break; michael@0: } michael@0: } while (charEnd != charLimit); michael@0: michael@0: NS_WARN_IF_FALSE(glyphStart < glyphEnd, michael@0: "character/glyph clump contains no glyphs!"); michael@0: if (glyphStart == glyphEnd) { michael@0: ++glyphStart; // make progress - avoid potential infinite loop michael@0: charStart = charEnd; michael@0: continue; michael@0: } michael@0: michael@0: NS_WARN_IF_FALSE(charStart != charEnd, michael@0: "character/glyph clump contains no characters!"); michael@0: if (charStart == charEnd) { michael@0: glyphStart = glyphEnd; // this is bad - we'll discard the glyph(s), michael@0: // as there's nowhere to attach them michael@0: continue; michael@0: } michael@0: michael@0: // Now charStart..charEnd is a ligature clump, corresponding to glyphStart..glyphEnd; michael@0: // Set baseCharIndex to the char we'll actually attach the glyphs to (1st of ligature), michael@0: // and endCharIndex to the limit (position beyond the last char), michael@0: // adjusting for the offset of the stringRange relative to the textRun. michael@0: int32_t baseCharIndex, endCharIndex; michael@0: if (isRightToLeft) { michael@0: while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH) { michael@0: charEnd--; michael@0: } michael@0: baseCharIndex = charEnd + stringRange.location - aStringOffset + 1; michael@0: endCharIndex = charStart + stringRange.location - aStringOffset + 1; michael@0: } else { michael@0: while (charEnd < stringRange.length && charToGlyph[charEnd] == NO_GLYPH) { michael@0: charEnd++; michael@0: } michael@0: baseCharIndex = charStart + stringRange.location - aStringOffset; michael@0: endCharIndex = charEnd + stringRange.location - aStringOffset; michael@0: } michael@0: michael@0: // Then we check if the clump falls outside our actual string range; if so, just go to the next. michael@0: if (endCharIndex <= 0 || baseCharIndex >= wordLength) { michael@0: glyphStart = glyphEnd; michael@0: charStart = charEnd; michael@0: continue; michael@0: } michael@0: // Ensure we won't try to go beyond the valid length of the word's text michael@0: baseCharIndex = std::max(baseCharIndex, 0); michael@0: endCharIndex = std::min(endCharIndex, wordLength); michael@0: michael@0: // Now we're ready to set the glyph info in the textRun; measure the glyph width michael@0: // of the first (perhaps only) glyph, to see if it is "Simple" michael@0: int32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit(); michael@0: double toNextGlyph; michael@0: if (glyphStart < numGlyphs-1) { michael@0: toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x; michael@0: } else { michael@0: toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x; michael@0: } michael@0: int32_t advance = int32_t(toNextGlyph * appUnitsPerDevUnit); michael@0: michael@0: // Check if it's a simple one-to-one mapping michael@0: int32_t glyphsInClump = glyphEnd - glyphStart; michael@0: if (glyphsInClump == 1 && michael@0: gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyphs[glyphStart]) && michael@0: gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) && michael@0: charGlyphs[baseCharIndex].IsClusterStart() && michael@0: positions[glyphStart].y == 0.0) michael@0: { michael@0: charGlyphs[baseCharIndex].SetSimpleGlyph(advance, michael@0: glyphs[glyphStart]); michael@0: } else { michael@0: // collect all glyphs in a list to be assigned to the first char; michael@0: // there must be at least one in the clump, and we already measured its advance, michael@0: // hence the placement of the loop-exit test and the measurement of the next glyph michael@0: while (1) { michael@0: gfxTextRun::DetailedGlyph *details = detailedGlyphs.AppendElement(); michael@0: details->mGlyphID = glyphs[glyphStart]; michael@0: details->mXOffset = 0; michael@0: details->mYOffset = -positions[glyphStart].y * appUnitsPerDevUnit; michael@0: details->mAdvance = advance; michael@0: if (++glyphStart >= glyphEnd) { michael@0: break; michael@0: } michael@0: if (glyphStart < numGlyphs-1) { michael@0: toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x; michael@0: } else { michael@0: toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x; michael@0: } michael@0: advance = int32_t(toNextGlyph * appUnitsPerDevUnit); michael@0: } michael@0: michael@0: gfxTextRun::CompressedGlyph g; michael@0: g.SetComplex(charGlyphs[baseCharIndex].IsClusterStart(), michael@0: true, detailedGlyphs.Length()); michael@0: aShapedText->SetGlyphs(aOffset + baseCharIndex, g, detailedGlyphs.Elements()); michael@0: michael@0: detailedGlyphs.Clear(); michael@0: } michael@0: michael@0: // the rest of the chars in the group are ligature continuations, no associated glyphs michael@0: while (++baseCharIndex != endCharIndex && baseCharIndex < wordLength) { michael@0: gfxShapedText::CompressedGlyph &g = charGlyphs[baseCharIndex]; michael@0: NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph"); michael@0: g.SetComplex(inOrder && g.IsClusterStart(), false, 0); michael@0: } michael@0: michael@0: glyphStart = glyphEnd; michael@0: charStart = charEnd; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: #undef SMALL_GLYPH_RUN michael@0: michael@0: // Construct the font attribute descriptor that we'll apply by default when creating a CTFontRef. michael@0: // This will turn off line-edge swashes by default, because we don't know the actual line breaks michael@0: // when doing glyph shaping. michael@0: void michael@0: gfxCoreTextShaper::CreateDefaultFeaturesDescriptor() michael@0: { michael@0: if (sDefaultFeaturesDescriptor != nullptr) { michael@0: return; michael@0: } michael@0: michael@0: SInt16 val = kSmartSwashType; michael@0: CFNumberRef swashesType = michael@0: ::CFNumberCreate(kCFAllocatorDefault, michael@0: kCFNumberSInt16Type, michael@0: &val); michael@0: val = kLineInitialSwashesOffSelector; michael@0: CFNumberRef lineInitialsOffSelector = michael@0: ::CFNumberCreate(kCFAllocatorDefault, michael@0: kCFNumberSInt16Type, michael@0: &val); michael@0: michael@0: CFTypeRef keys[] = { kCTFontFeatureTypeIdentifierKey, michael@0: kCTFontFeatureSelectorIdentifierKey }; michael@0: CFTypeRef values[] = { swashesType, michael@0: lineInitialsOffSelector }; michael@0: CFDictionaryRef featureSettings[2]; michael@0: featureSettings[0] = michael@0: ::CFDictionaryCreate(kCFAllocatorDefault, michael@0: (const void **) keys, michael@0: (const void **) values, michael@0: ArrayLength(keys), michael@0: &kCFTypeDictionaryKeyCallBacks, michael@0: &kCFTypeDictionaryValueCallBacks); michael@0: ::CFRelease(lineInitialsOffSelector); michael@0: michael@0: val = kLineFinalSwashesOffSelector; michael@0: CFNumberRef lineFinalsOffSelector = michael@0: ::CFNumberCreate(kCFAllocatorDefault, michael@0: kCFNumberSInt16Type, michael@0: &val); michael@0: values[1] = lineFinalsOffSelector; michael@0: featureSettings[1] = michael@0: ::CFDictionaryCreate(kCFAllocatorDefault, michael@0: (const void **) keys, michael@0: (const void **) values, michael@0: ArrayLength(keys), michael@0: &kCFTypeDictionaryKeyCallBacks, michael@0: &kCFTypeDictionaryValueCallBacks); michael@0: ::CFRelease(lineFinalsOffSelector); michael@0: ::CFRelease(swashesType); michael@0: michael@0: CFArrayRef featuresArray = michael@0: ::CFArrayCreate(kCFAllocatorDefault, michael@0: (const void **) featureSettings, michael@0: ArrayLength(featureSettings), michael@0: &kCFTypeArrayCallBacks); michael@0: ::CFRelease(featureSettings[0]); michael@0: ::CFRelease(featureSettings[1]); michael@0: michael@0: const CFTypeRef attrKeys[] = { kCTFontFeatureSettingsAttribute }; michael@0: const CFTypeRef attrValues[] = { featuresArray }; michael@0: CFDictionaryRef attributesDict = michael@0: ::CFDictionaryCreate(kCFAllocatorDefault, michael@0: (const void **) attrKeys, michael@0: (const void **) attrValues, michael@0: ArrayLength(attrKeys), michael@0: &kCFTypeDictionaryKeyCallBacks, michael@0: &kCFTypeDictionaryValueCallBacks); michael@0: ::CFRelease(featuresArray); michael@0: michael@0: sDefaultFeaturesDescriptor = michael@0: ::CTFontDescriptorCreateWithAttributes(attributesDict); michael@0: ::CFRelease(attributesDict); michael@0: } michael@0: michael@0: // Create a CTFontRef, with the Common Ligatures feature disabled michael@0: CTFontRef michael@0: gfxCoreTextShaper::CreateCTFontWithDisabledLigatures(CGFloat aSize) michael@0: { michael@0: if (sDisableLigaturesDescriptor == nullptr) { michael@0: // initialize cached descriptor to turn off the Common Ligatures feature michael@0: SInt16 val = kLigaturesType; michael@0: CFNumberRef ligaturesType = michael@0: ::CFNumberCreate(kCFAllocatorDefault, michael@0: kCFNumberSInt16Type, michael@0: &val); michael@0: val = kCommonLigaturesOffSelector; michael@0: CFNumberRef commonLigaturesOffSelector = michael@0: ::CFNumberCreate(kCFAllocatorDefault, michael@0: kCFNumberSInt16Type, michael@0: &val); michael@0: michael@0: const CFTypeRef keys[] = { kCTFontFeatureTypeIdentifierKey, michael@0: kCTFontFeatureSelectorIdentifierKey }; michael@0: const CFTypeRef values[] = { ligaturesType, michael@0: commonLigaturesOffSelector }; michael@0: CFDictionaryRef featureSettingDict = michael@0: ::CFDictionaryCreate(kCFAllocatorDefault, michael@0: (const void **) keys, michael@0: (const void **) values, michael@0: ArrayLength(keys), michael@0: &kCFTypeDictionaryKeyCallBacks, michael@0: &kCFTypeDictionaryValueCallBacks); michael@0: ::CFRelease(ligaturesType); michael@0: ::CFRelease(commonLigaturesOffSelector); michael@0: michael@0: CFArrayRef featuresArray = michael@0: ::CFArrayCreate(kCFAllocatorDefault, michael@0: (const void **) &featureSettingDict, michael@0: 1, michael@0: &kCFTypeArrayCallBacks); michael@0: ::CFRelease(featureSettingDict); michael@0: michael@0: CFDictionaryRef attributesDict = michael@0: ::CFDictionaryCreate(kCFAllocatorDefault, michael@0: (const void **) &kCTFontFeatureSettingsAttribute, michael@0: (const void **) &featuresArray, michael@0: 1, // count of keys & values michael@0: &kCFTypeDictionaryKeyCallBacks, michael@0: &kCFTypeDictionaryValueCallBacks); michael@0: ::CFRelease(featuresArray); michael@0: michael@0: sDisableLigaturesDescriptor = michael@0: ::CTFontDescriptorCreateCopyWithAttributes(GetDefaultFeaturesDescriptor(), michael@0: attributesDict); michael@0: ::CFRelease(attributesDict); michael@0: } michael@0: michael@0: gfxMacFont *f = static_cast(mFont); michael@0: return ::CTFontCreateWithGraphicsFont(f->GetCGFontRef(), aSize, nullptr, michael@0: sDisableLigaturesDescriptor); michael@0: } michael@0: michael@0: void michael@0: gfxCoreTextShaper::Shutdown() // [static] michael@0: { michael@0: if (sDisableLigaturesDescriptor != nullptr) { michael@0: ::CFRelease(sDisableLigaturesDescriptor); michael@0: sDisableLigaturesDescriptor = nullptr; michael@0: } michael@0: if (sDefaultFeaturesDescriptor != nullptr) { michael@0: ::CFRelease(sDefaultFeaturesDescriptor); michael@0: sDefaultFeaturesDescriptor = nullptr; michael@0: } michael@0: }