Bug 739117 - Avoid bidi-wrapping the text to be shaped if Core Text direction override API is available. r=jdaggett

This commit is contained in:
Jonathan Kew 2015-12-30 20:29:48 +00:00
parent 959e4a2902
commit 37d728d36d
3 changed files with 161 additions and 67 deletions

View File

@ -13,6 +13,8 @@
#include <algorithm>
#include <dlfcn.h>
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<const UniChar*>(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<const UniChar*>(aText), length);
::CFStringAppendCharacters(mutableString,
endBidiWrap, mozilla::ArrayLength(endBidiWrap));
stringObj = mutableString;
} else {
CFDictionaryRef attrObj;
if (sCTWritingDirectionAttributeName) {
startOffset = 0;
stringObj = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
reinterpret_cast<const UniChar*>(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;

View File

@ -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<SInt16,SInt16> aFeatures[],
size_t aCount);

View File

@ -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