bug 992100 - mask out complex-script codepoints in fonts that lack the necessary layout tables. r=roc

This commit is contained in:
Jonathan Kew 2014-05-05 19:59:55 +01:00
parent a404378a81
commit f84805d4da
7 changed files with 118 additions and 78 deletions

View File

@ -462,6 +462,17 @@ FT2FontEntry::CairoFontFace()
return mFontFace;
}
// Copied/modified from similar code in gfxMacPlatformFontList.mm:
// Complex scripts will not render correctly unless Graphite or OT
// layout tables are present.
// For OpenType, we also check that the GSUB table supports the relevant
// script tag, to avoid using things like Arial Unicode MS for Lao (it has
// the characters, but lacks OpenType support).
// TODO: consider whether we should move this to gfxFontEntry and do similar
// cmap-masking on all platforms to avoid using fonts that won't shape
// properly.
nsresult
FT2FontEntry::ReadCMAP(FontInfoData *aFontInfoData)
{
@ -482,6 +493,28 @@ FT2FontEntry::ReadCMAP(FontInfoData *aFontInfoData)
unicodeFont, symbolFont);
}
if (NS_SUCCEEDED(rv) && !HasGraphiteTables()) {
// We assume a Graphite font knows what it's doing,
// and provides whatever shaping is needed for the
// characters it supports, so only check/clear the
// complex-script ranges for non-Graphite fonts
// for layout support, check for the presence of opentype layout tables
bool hasGSUB = HasFontTable(TRUETYPE_TAG('G','S','U','B'));
for (const ScriptRange* sr = gfxPlatformFontList::sComplexScriptRanges;
sr->rangeStart; sr++) {
// check to see if the cmap includes complex script codepoints
if (charmap->TestRange(sr->rangeStart, sr->rangeEnd)) {
// We check for GSUB here, as GPOS alone would not be ok.
if (hasGSUB && SupportsScriptInGSUB(sr->tags)) {
continue;
}
charmap->ClearRange(sr->rangeStart, sr->rangeEnd);
}
}
}
mHasCmapTable = NS_SUCCEEDED(rv);
if (mHasCmapTable) {
gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList();

View File

@ -244,6 +244,23 @@ uint16_t gfxFontEntry::GetUVSGlyph(uint32_t aCh, uint32_t aVS)
return 0;
}
bool gfxFontEntry::SupportsScriptInGSUB(const hb_tag_t* aScriptTags)
{
hb_face_t *face = GetHBFace();
if (!face) {
return false;
}
unsigned int index;
hb_tag_t chosenScript;
bool found =
hb_ot_layout_table_choose_script(face, TRUETYPE_TAG('G','S','U','B'),
aScriptTags, &index, &chosenScript);
hb_face_destroy(face);
return found && chosenScript != TRUETYPE_TAG('D','F','L','T');
}
nsresult gfxFontEntry::ReadCMAP(FontInfoData *aFontInfoData)
{
NS_ASSERTION(false, "using default no-op implementation of ReadCMAP");

View File

@ -496,6 +496,16 @@ public:
virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
FontListSizes* aSizes) const;
// Used when checking for complex script support, to mask off cmap ranges
struct ScriptRange {
uint32_t rangeStart;
uint32_t rangeEnd;
hb_tag_t tags[3]; // one or two OpenType script tags to check,
// plus a NULL terminator
};
bool SupportsScriptInGSUB(const hb_tag_t* aScriptTags);
nsString mName;
nsString mFamilyName;

View File

@ -52,7 +52,6 @@
#include "gfxMacFont.h"
#include "gfxUserFontSet.h"
#include "harfbuzz/hb.h"
#include "harfbuzz/hb-ot.h"
#include "nsServiceManagerUtils.h"
#include "nsTArray.h"
@ -157,71 +156,6 @@ static NSString* GetNSStringForString(const nsAString& aSrc)
// cmap-masking on other platforms to avoid using fonts that won't shape
// properly.
struct ScriptRange {
uint32_t rangeStart;
uint32_t rangeEnd;
hb_tag_t tags[3]; // one or two OpenType script tags to check,
// plus a NULL terminator
};
static const ScriptRange sComplexScripts[] = {
// Actually, now that harfbuzz supports presentation-forms shaping for
// Arabic, we can render it without layout tables. So maybe we don't
// want to mask the basic Arabic block here?
// This affects the arabic-fallback-*.html reftests, which rely on
// loading a font that *doesn't* have any GSUB table.
{ 0x0600, 0x06FF, { TRUETYPE_TAG('a','r','a','b'), 0, 0 } },
{ 0x0700, 0x074F, { TRUETYPE_TAG('s','y','r','c'), 0, 0 } },
{ 0x0750, 0x077F, { TRUETYPE_TAG('a','r','a','b'), 0, 0 } },
{ 0x08A0, 0x08FF, { TRUETYPE_TAG('a','r','a','b'), 0, 0 } },
{ 0x0900, 0x097F, { TRUETYPE_TAG('d','e','v','2'),
TRUETYPE_TAG('d','e','v','a'), 0 } },
{ 0x0980, 0x09FF, { TRUETYPE_TAG('b','n','g','2'),
TRUETYPE_TAG('b','e','n','g'), 0 } },
{ 0x0A00, 0x0A7F, { TRUETYPE_TAG('g','u','r','2'),
TRUETYPE_TAG('g','u','r','u'), 0 } },
{ 0x0A80, 0x0AFF, { TRUETYPE_TAG('g','j','r','2'),
TRUETYPE_TAG('g','u','j','r'), 0 } },
{ 0x0B00, 0x0B7F, { TRUETYPE_TAG('o','r','y','2'),
TRUETYPE_TAG('o','r','y','a'), 0 } },
{ 0x0B80, 0x0BFF, { TRUETYPE_TAG('t','m','l','2'),
TRUETYPE_TAG('t','a','m','l'), 0 } },
{ 0x0C00, 0x0C7F, { TRUETYPE_TAG('t','e','l','2'),
TRUETYPE_TAG('t','e','l','u'), 0 } },
{ 0x0C80, 0x0CFF, { TRUETYPE_TAG('k','n','d','2'),
TRUETYPE_TAG('k','n','d','a'), 0 } },
{ 0x0D00, 0x0D7F, { TRUETYPE_TAG('m','l','m','2'),
TRUETYPE_TAG('m','l','y','m'), 0 } },
{ 0x0D80, 0x0DFF, { TRUETYPE_TAG('s','i','n','h'), 0, 0 } },
{ 0x0E80, 0x0EFF, { TRUETYPE_TAG('l','a','o',' '), 0, 0 } },
{ 0x0F00, 0x0FFF, { TRUETYPE_TAG('t','i','b','t'), 0, 0 } },
{ 0x1000, 0x109f, { TRUETYPE_TAG('m','y','m','r'),
TRUETYPE_TAG('m','y','m','2'), 0 } },
{ 0x1780, 0x17ff, { TRUETYPE_TAG('k','h','m','r'), 0, 0 } },
// Khmer Symbols (19e0..19ff) don't seem to need any special shaping
{ 0xaa60, 0xaa7f, { TRUETYPE_TAG('m','y','m','r'),
TRUETYPE_TAG('m','y','m','2'), 0 } },
// Thai seems to be "renderable" without AAT morphing tables
};
static bool
SupportsScriptInGSUB(gfxFontEntry* aFontEntry, const hb_tag_t* aScriptTags)
{
hb_face_t *face = aFontEntry->GetHBFace();
if (!face) {
return false;
}
unsigned int index;
hb_tag_t chosenScript;
bool found =
hb_ot_layout_table_choose_script(face, TRUETYPE_TAG('G','S','U','B'),
aScriptTags, &index, &chosenScript);
hb_face_destroy(face);
return found && chosenScript != TRUETYPE_TAG('D','F','L','T');
}
nsresult
MacOSFontEntry::ReadCMAP(FontInfoData *aFontInfoData)
{
@ -273,12 +207,10 @@ MacOSFontEntry::ReadCMAP(FontInfoData *aFontInfoData)
mRequiresAAT = true; // prefer CoreText if font has no OTL tables
}
uint32_t numScripts = ArrayLength(sComplexScripts);
for (uint32_t s = 0; s < numScripts; s++) {
for (const ScriptRange* sr = gfxPlatformFontList::sComplexScriptRanges;
sr->rangeStart; sr++) {
// check to see if the cmap includes complex script codepoints
const ScriptRange& sr = sComplexScripts[s];
if (charmap->TestRange(sr.rangeStart, sr.rangeEnd)) {
if (charmap->TestRange(sr->rangeStart, sr->rangeEnd)) {
if (hasAATLayout) {
// prefer CoreText for Apple's complex-script fonts,
// even if they also have some OpenType tables
@ -290,11 +222,11 @@ MacOSFontEntry::ReadCMAP(FontInfoData *aFontInfoData)
}
// We check for GSUB here, as GPOS alone would not be ok.
if (hasGSUB && SupportsScriptInGSUB(this, sr.tags)) {
if (hasGSUB && SupportsScriptInGSUB(sr->tags)) {
continue;
}
charmap->ClearRange(sr.rangeStart, sr.rangeEnd);
charmap->ClearRange(sr->rangeStart, sr->rangeEnd);
}
}
}

View File

@ -42,6 +42,52 @@ using namespace mozilla;
gfxPlatformFontList *gfxPlatformFontList::sPlatformFontList = nullptr;
// Character ranges that require complex-script shaping support in the font,
// and so should be masked out by ReadCMAP if the necessary layout tables
// are not present.
// Currently used by the Mac and FT2 implementations only, but probably should
// be supported on Windows as well.
const gfxFontEntry::ScriptRange gfxPlatformFontList::sComplexScriptRanges[] = {
// Actually, now that harfbuzz supports presentation-forms shaping for
// Arabic, we can render it without layout tables. So maybe we don't
// want to mask the basic Arabic block here?
// This affects the arabic-fallback-*.html reftests, which rely on
// loading a font that *doesn't* have any GSUB table.
{ 0x0600, 0x06FF, { TRUETYPE_TAG('a','r','a','b'), 0, 0 } },
{ 0x0700, 0x074F, { TRUETYPE_TAG('s','y','r','c'), 0, 0 } },
{ 0x0750, 0x077F, { TRUETYPE_TAG('a','r','a','b'), 0, 0 } },
{ 0x08A0, 0x08FF, { TRUETYPE_TAG('a','r','a','b'), 0, 0 } },
{ 0x0900, 0x097F, { TRUETYPE_TAG('d','e','v','2'),
TRUETYPE_TAG('d','e','v','a'), 0 } },
{ 0x0980, 0x09FF, { TRUETYPE_TAG('b','n','g','2'),
TRUETYPE_TAG('b','e','n','g'), 0 } },
{ 0x0A00, 0x0A7F, { TRUETYPE_TAG('g','u','r','2'),
TRUETYPE_TAG('g','u','r','u'), 0 } },
{ 0x0A80, 0x0AFF, { TRUETYPE_TAG('g','j','r','2'),
TRUETYPE_TAG('g','u','j','r'), 0 } },
{ 0x0B00, 0x0B7F, { TRUETYPE_TAG('o','r','y','2'),
TRUETYPE_TAG('o','r','y','a'), 0 } },
{ 0x0B80, 0x0BFF, { TRUETYPE_TAG('t','m','l','2'),
TRUETYPE_TAG('t','a','m','l'), 0 } },
{ 0x0C00, 0x0C7F, { TRUETYPE_TAG('t','e','l','2'),
TRUETYPE_TAG('t','e','l','u'), 0 } },
{ 0x0C80, 0x0CFF, { TRUETYPE_TAG('k','n','d','2'),
TRUETYPE_TAG('k','n','d','a'), 0 } },
{ 0x0D00, 0x0D7F, { TRUETYPE_TAG('m','l','m','2'),
TRUETYPE_TAG('m','l','y','m'), 0 } },
{ 0x0D80, 0x0DFF, { TRUETYPE_TAG('s','i','n','h'), 0, 0 } },
{ 0x0E80, 0x0EFF, { TRUETYPE_TAG('l','a','o',' '), 0, 0 } },
{ 0x0F00, 0x0FFF, { TRUETYPE_TAG('t','i','b','t'), 0, 0 } },
{ 0x1000, 0x109f, { TRUETYPE_TAG('m','y','m','r'),
TRUETYPE_TAG('m','y','m','2'), 0 } },
{ 0x1780, 0x17ff, { TRUETYPE_TAG('k','h','m','r'), 0, 0 } },
// Khmer Symbols (19e0..19ff) don't seem to need any special shaping
{ 0xaa60, 0xaa7f, { TRUETYPE_TAG('m','y','m','r'),
TRUETYPE_TAG('m','y','m','2'), 0 } },
// Thai seems to be "renderable" without AAT morphing tables
{ 0, 0, { 0, 0, 0 } } // terminator
};
// prefs for the font info loader
#define FONT_LOADER_FAMILIES_PER_SLICE_PREF "gfx.font_loader.families_per_slice"
#define FONT_LOADER_DELAY_PREF "gfx.font_loader.delay"

View File

@ -189,6 +189,8 @@ public:
mUserFontSetList.RemoveEntry(aUserFontSet);
}
static const gfxFontEntry::ScriptRange sComplexScriptRanges[];
protected:
class MemoryReporter MOZ_FINAL : public nsIMemoryReporter
{

View File

@ -154,12 +154,12 @@ HTTP(..) == arabic-shaping-1.html arabic-shaping-1-ref.html
# check ligature in Arial Bold on Windows, for bug 644184; may fail on other platforms depending on fonts
random-if(!winWidget) == arial-bold-lam-alef-1.html arial-bold-lam-alef-1-ref.html
# Fallback (presentation-forms) shaping with a font that lacks GSUB/GPOS
# These tests are not valid on OS X because our masking of complex-script ranges
# These tests are not valid with Mac or FT2 font backends because our masking of complex-script ranges
# in the 'cmap' will prevent the test font (without GSUB) being used.
skip-if(B2G) fails-if(cocoaWidget) HTTP(..) == arabic-fallback-1.html arabic-fallback-1-ref.html
fails-if(cocoaWidget) HTTP(..) == arabic-fallback-2.html arabic-fallback-2-ref.html
fails-if(cocoaWidget) HTTP(..) == arabic-fallback-3.html arabic-fallback-3-ref.html
fails-if(!cocoaWidget) HTTP(..) != arabic-fallback-4.html arabic-fallback-4-notref.html
skip-if(B2G) fails-if(cocoaWidget||Android) HTTP(..) == arabic-fallback-1.html arabic-fallback-1-ref.html
fails-if(cocoaWidget||Android||B2G) HTTP(..) == arabic-fallback-2.html arabic-fallback-2-ref.html
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
== 726392-1.html 726392-1-ref.html