diff --git a/gfx/thebes/gfxCoreTextShaper.cpp b/gfx/thebes/gfxCoreTextShaper.cpp index 69f84f428cd..cb1575e6cc8 100644 --- a/gfx/thebes/gfxCoreTextShaper.cpp +++ b/gfx/thebes/gfxCoreTextShaper.cpp @@ -13,6 +13,8 @@ #include +#include + using namespace mozilla; // standard font descriptors that we construct the first time they're needed @@ -21,28 +23,85 @@ CTFontDescriptorRef gfxCoreTextShaper::sDisableLigaturesDescriptor = nullptr; CTFontDescriptorRef gfxCoreTextShaper::sIndicFeaturesDescriptor = nullptr; CTFontDescriptorRef gfxCoreTextShaper::sIndicDisableLigaturesDescriptor = nullptr; +static CFStringRef sCTWritingDirectionAttributeName = nullptr; + +// See CTStringAttributes.h +enum { + kMyCTWritingDirectionEmbedding = (0 << 1), + kMyCTWritingDirectionOverride = (1 << 1) +}; + +// Helper to create a CFDictionary with the right attributes for shaping our +// text, including imposing the given directionality. +// This will only be called if we're on 10.8 or later. +CFDictionaryRef +gfxCoreTextShaper::CreateAttrDict(bool aRightToLeft) +{ + // Because we always shape unidirectional runs, and may have applied + // directional overrides, we want to force a direction rather than + // allowing CoreText to do its own unicode-based bidi processing. + SInt16 dirOverride = kMyCTWritingDirectionOverride | + (aRightToLeft ? kCTWritingDirectionRightToLeft + : kCTWritingDirectionLeftToRight); + CFNumberRef dirNumber = + ::CFNumberCreate(kCFAllocatorDefault, + kCFNumberSInt16Type, &dirOverride); + CFArrayRef dirArray = + ::CFArrayCreate(kCFAllocatorDefault, + (const void **) &dirNumber, 1, + &kCFTypeArrayCallBacks); + ::CFRelease(dirNumber); + CFTypeRef attrs[] = { kCTFontAttributeName, sCTWritingDirectionAttributeName }; + CFTypeRef values[] = { mCTFont, dirArray }; + CFDictionaryRef attrDict = + ::CFDictionaryCreate(kCFAllocatorDefault, + attrs, values, ArrayLength(attrs), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + ::CFRelease(dirArray); + return attrDict; +} + +CFDictionaryRef +gfxCoreTextShaper::CreateAttrDictWithoutDirection() +{ + CFTypeRef attrs[] = { kCTFontAttributeName }; + CFTypeRef values[] = { mCTFont }; + CFDictionaryRef attrDict = + ::CFDictionaryCreate(kCFAllocatorDefault, + attrs, values, ArrayLength(attrs), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + return attrDict; +} + gfxCoreTextShaper::gfxCoreTextShaper(gfxMacFont *aFont) : gfxFontShaper(aFont) + , mAttributesDictLTR(nullptr) + , mAttributesDictRTL(nullptr) { + static bool sInitialized = false; + if (!sInitialized) { + CFStringRef* pstr = (CFStringRef*) + dlsym(RTLD_DEFAULT, "kCTWritingDirectionAttributeName"); + if (pstr) { + sCTWritingDirectionAttributeName = *pstr; + } + sInitialized = true; + } + // Create our CTFontRef mCTFont = CreateCTFontWithFeatures(aFont->GetAdjustedSize(), GetDefaultFeaturesDescriptor()); - - // Set up the default attribute dictionary that we will need each time we - // create a CFAttributedString (unless we need to use custom features, - // in which case a new dictionary will be created on the fly). - mAttributesDict = ::CFDictionaryCreate(kCFAllocatorDefault, - (const void**) &kCTFontAttributeName, - (const void**) &mCTFont, - 1, // count of attributes - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); } gfxCoreTextShaper::~gfxCoreTextShaper() { - if (mAttributesDict) { - ::CFRelease(mAttributesDict); + if (mAttributesDictLTR) { + ::CFRelease(mAttributesDictLTR); + } + if (mAttributesDictRTL) { + ::CFRelease(mAttributesDictRTL); } if (mCTFont) { ::CFRelease(mCTFont); @@ -65,54 +124,81 @@ gfxCoreTextShaper::ShapeText(gfxContext *aContext, gfxShapedText *aShapedText) { // Create a CFAttributedString with text and style info, so we can use CoreText to lay it out. - bool isRightToLeft = aShapedText->IsRightToLeft(); + const UniChar* text = reinterpret_cast(aText); uint32_t length = aLength; - // we need to bidi-wrap the text if the run is RTL, - // or if it is an LTR run but may contain (overridden) RTL chars - bool bidiWrap = isRightToLeft; - if (!bidiWrap && !aShapedText->TextIs8Bit()) { - uint32_t i; - for (i = 0; i < length; ++i) { - if (gfxFontUtils::PotentialRTLChar(aText[i])) { - bidiWrap = true; - break; - } - } - } - - // If there's a possibility of any bidi, we wrap the text with direction overrides - // to ensure neutrals or characters that were bidi-overridden in HTML behave properly. - const UniChar beginLTR[] = { 0x202d, 0x20 }; - const UniChar beginRTL[] = { 0x202e, 0x20 }; - const UniChar endBidiWrap[] = { 0x20, 0x2e, 0x202c }; - uint32_t startOffset; CFStringRef stringObj; - if (bidiWrap) { - startOffset = isRightToLeft ? - mozilla::ArrayLength(beginRTL) : mozilla::ArrayLength(beginLTR); - CFMutableStringRef mutableString = - ::CFStringCreateMutable(kCFAllocatorDefault, - length + startOffset + mozilla::ArrayLength(endBidiWrap)); - ::CFStringAppendCharacters(mutableString, - isRightToLeft ? beginRTL : beginLTR, - startOffset); - ::CFStringAppendCharacters(mutableString, reinterpret_cast(aText), length); - ::CFStringAppendCharacters(mutableString, - endBidiWrap, mozilla::ArrayLength(endBidiWrap)); - stringObj = mutableString; - } else { + CFDictionaryRef attrObj; + + if (sCTWritingDirectionAttributeName) { startOffset = 0; stringObj = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, - reinterpret_cast(aText), - length, kCFAllocatorNull); + text, length, + kCFAllocatorNull); + + // Get an attributes dictionary suitable for shaping text in the + // current direction, creating it if necessary. + attrObj = isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR; + if (!attrObj) { + attrObj = CreateAttrDict(isRightToLeft); + (isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR) = attrObj; + } + } else { + // OS is too old to support kCTWritingDirectionAttributeName: + // we need to bidi-wrap the text if the run is RTL, + // or if it is an LTR run but may contain (overridden) RTL chars + bool bidiWrap = isRightToLeft; + if (!bidiWrap && !aShapedText->TextIs8Bit()) { + uint32_t i; + for (i = 0; i < length; ++i) { + if (gfxFontUtils::PotentialRTLChar(aText[i])) { + bidiWrap = true; + break; + } + } + } + + // If there's a possibility of any bidi, we wrap the text with + // direction overrides to ensure neutrals or characters that were + // bidi-overridden in HTML behave properly. + static const UniChar beginLTR[] = { 0x202d, 0x20 }; + static const UniChar beginRTL[] = { 0x202e, 0x20 }; + static const UniChar endBidiWrap[] = { 0x20, 0x2e, 0x202c }; + + if (bidiWrap) { + startOffset = isRightToLeft ? ArrayLength(beginRTL) + : ArrayLength(beginLTR); + CFMutableStringRef mutableString = + ::CFStringCreateMutable(kCFAllocatorDefault, + length + startOffset + + ArrayLength(endBidiWrap)); + ::CFStringAppendCharacters(mutableString, + isRightToLeft ? beginRTL : beginLTR, + startOffset); + ::CFStringAppendCharacters(mutableString, text, length); + ::CFStringAppendCharacters(mutableString, endBidiWrap, + ArrayLength(endBidiWrap)); + stringObj = mutableString; + } else { + startOffset = 0; + stringObj = + ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, + text, length, + kCFAllocatorNull); + } + + // Get an attributes dictionary suitable for shaping text, + // creating it if necessary. (This dict is not LTR-specific, + // but we use that field to store it anyway.) + if (!mAttributesDictLTR) { + mAttributesDictLTR = CreateAttrDictWithoutDirection(); + } + attrObj = mAttributesDictLTR; } - CFDictionaryRef attrObj; CTFontRef tempCTFont = nullptr; - if (IsBuggyIndicScript(aScript)) { // To work around buggy Indic AAT fonts shipped with OS X, // we re-enable the Line Initial Smart Swashes feature that is needed @@ -132,21 +218,18 @@ gfxCoreTextShaper::ShapeText(gfxContext *aContext, GetDisableLigaturesDescriptor()); } + // For the disabled-ligature or buggy-indic-font case, we need to replace + // the standard CTFont in the attribute dictionary with a tweaked version. + CFMutableDictionaryRef mutableAttr = nullptr; if (tempCTFont) { - attrObj = - ::CFDictionaryCreate(kCFAllocatorDefault, - (const void**) &kCTFontAttributeName, - (const void**) &tempCTFont, - 1, // count of attributes - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); + mutableAttr = ::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 2, + attrObj); + ::CFDictionaryReplaceValue(mutableAttr, + kCTFontAttributeName, tempCTFont); // Having created the dict, we're finished with our temporary // Indic and/or ligature-disabled CTFontRef. ::CFRelease(tempCTFont); - } else { - // The default case is to use our preallocated attr dict - attrObj = mAttributesDict; - ::CFRetain(attrObj); + attrObj = mutableAttr; } // Now we can create an attributed string @@ -180,8 +263,10 @@ gfxCoreTextShaper::ShapeText(gfxContext *aContext, // If Core Text manufactured a new dictionary, this may indicate // unexpected font substitution. In that case, we fail (and fall // back to harfbuzz shaping)... - const void* font1 = ::CFDictionaryGetValue(attrObj, kCTFontAttributeName); - const void* font2 = ::CFDictionaryGetValue(runAttr, kCTFontAttributeName); + const void* font1 = + ::CFDictionaryGetValue(attrObj, kCTFontAttributeName); + const void* font2 = + ::CFDictionaryGetValue(runAttr, kCTFontAttributeName); if (font1 != font2) { // ...except that if the fallback was only for a variation // selector or join control that is otherwise unsupported, @@ -198,13 +283,16 @@ gfxCoreTextShaper::ShapeText(gfxContext *aContext, break; } } - if (SetGlyphsFromRun(aShapedText, aOffset, aLength, aCTRun, startOffset) != NS_OK) { + if (SetGlyphsFromRun(aShapedText, aOffset, aLength, aCTRun, + startOffset) != NS_OK) { success = false; break; } } - ::CFRelease(attrObj); + if (mutableAttr) { + ::CFRelease(mutableAttr); + } ::CFRelease(line); return success; diff --git a/gfx/thebes/gfxCoreTextShaper.h b/gfx/thebes/gfxCoreTextShaper.h index 0bef3fb651e..0cded07e5af 100644 --- a/gfx/thebes/gfxCoreTextShaper.h +++ b/gfx/thebes/gfxCoreTextShaper.h @@ -31,7 +31,10 @@ public: protected: CTFontRef mCTFont; - CFDictionaryRef mAttributesDict; + + // attributes for shaping text with LTR or RTL directionality + CFDictionaryRef mAttributesDictLTR; + CFDictionaryRef mAttributesDictRTL; nsresult SetGlyphsFromRun(gfxShapedText *aShapedText, uint32_t aOffset, @@ -42,6 +45,9 @@ protected: CTFontRef CreateCTFontWithFeatures(CGFloat aSize, CTFontDescriptorRef aDescriptor); + CFDictionaryRef CreateAttrDict(bool aRightToLeft); + CFDictionaryRef CreateAttrDictWithoutDirection(); + static CTFontDescriptorRef CreateFontFeaturesDescriptor(const std::pair aFeatures[], size_t aCount); diff --git a/layout/reftests/text/reftest.list b/layout/reftests/text/reftest.list index ebea62a70f1..f1a55ad6527 100644 --- a/layout/reftests/text/reftest.list +++ b/layout/reftests/text/reftest.list @@ -165,7 +165,7 @@ fails-if(cocoaWidget||Android||B2G) HTTP(..) == arabic-fallback-2.html arabic-fa fails-if(cocoaWidget||Android||B2G) HTTP(..) == arabic-fallback-3.html arabic-fallback-3-ref.html fails-if(!cocoaWidget&&!Android&&!B2G) HTTP(..) != arabic-fallback-4.html arabic-fallback-4-notref.html == arabic-marks-1.html arabic-marks-1-ref.html -fails-if(cocoaWidget) == arabic-final-ligature-spacing.html arabic-final-ligature-spacing-ref.html # bug 739117 +fails-if(OSX<1008) == arabic-final-ligature-spacing.html arabic-final-ligature-spacing-ref.html # harfbuzz fallback mark stacking in the absence of GPOS: HTTP(..) != fallback-mark-stacking-1.html fallback-mark-stacking-1-notref.html