Autogenerated with:
cp dom/.clang-format gfx/ && mach format gfx/**.{cpp,h,mm}
Manual changes:
* OSVRSession headers are not system headers and they are not
standalone / rely on stdint, so tweaked to preserve previous
ordering.
* Missing include in GLDefs.h
* Missing include in gfxOTSUtils.
* Need to keep the windows header order in DCLayerTree.
* missing hb_font include in MockScaledFont.h
Differential Revision: https://phabricator.services.mozilla.com/D311695
443 lines
15 KiB
C++
443 lines
15 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "gfxPlatform.h"
|
|
#include "gfxPlatformFontList.h"
|
|
#include "gfxTypes.h"
|
|
#include "gtest/gtest.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsString.h"
|
|
#include "nsTArray.h"
|
|
|
|
class FontFallbackTest : public ::testing::Test {
|
|
protected:
|
|
static void SetUpTestSuite() {
|
|
// Initialize gfxPlatform which also initializes the font list
|
|
gfxPlatform::GetPlatform();
|
|
}
|
|
|
|
static bool FontHasCharacter(gfxPlatformFontList* pfl,
|
|
const nsCString& fontName, uint32_t ch) {
|
|
nsAutoString text;
|
|
AppendUCS4ToUTF16(ch, text);
|
|
nsTArray<nsCString> fontList;
|
|
fontList.AppendElement(fontName);
|
|
nsTArray<nsCString> fontsUsed;
|
|
pfl->ListFontsUsedForString(text, fontList, fontsUsed);
|
|
if (fontsUsed.IsEmpty()) {
|
|
return false;
|
|
}
|
|
// Verify the returned font actually matches the requested one,
|
|
// not a global fallback font.
|
|
nsCString actualLower(fontsUsed[0]);
|
|
ToLowerCase(actualLower);
|
|
nsCString expectedLower(fontName);
|
|
ToLowerCase(expectedLower);
|
|
return actualLower.Equals(expectedLower);
|
|
}
|
|
};
|
|
|
|
TEST_F(FontFallbackTest, ListFontsUsedForString_EmptyInput) {
|
|
gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
|
|
ASSERT_NE(pfl, nullptr);
|
|
|
|
nsTArray<nsCString> fontList;
|
|
fontList.AppendElement("DejaVu Sans"_ns);
|
|
|
|
nsTArray<nsCString> fontsUsed;
|
|
|
|
pfl->ListFontsUsedForString(nsAutoString(), fontList, fontsUsed);
|
|
|
|
EXPECT_EQ(fontsUsed.Length(), 0u) << "Empty text should use no fonts";
|
|
}
|
|
|
|
TEST_F(FontFallbackTest, ListFontsUsedForString_EmptyFontList) {
|
|
gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
|
|
ASSERT_NE(pfl, nullptr);
|
|
|
|
nsTArray<nsCString> emptyFontList;
|
|
nsTArray<nsCString> fontsUsed;
|
|
|
|
pfl->ListFontsUsedForString(u"Hi"_ns, emptyFontList, fontsUsed);
|
|
|
|
EXPECT_EQ(fontsUsed.Length(), 0u) << "Empty font list should use no fonts";
|
|
}
|
|
|
|
TEST_F(FontFallbackTest, ListFontsUsedForString_BasicUsage) {
|
|
gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
|
|
ASSERT_NE(pfl, nullptr);
|
|
|
|
// Find a font that exists on this system
|
|
const char* commonFonts[] = {"DejaVu Sans", "Liberation Sans", "Roboto",
|
|
"Noto Sans", "Arial", "Helvetica",
|
|
"Sans"};
|
|
|
|
nsCString workingFont;
|
|
for (const char* fontNameStr : commonFonts) {
|
|
nsCString fontName(fontNameStr);
|
|
if (FontHasCharacter(pfl, fontName, 'A')) {
|
|
workingFont = fontName;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (workingFont.IsEmpty()) {
|
|
GTEST_SKIP() << "No common test fonts available on this system";
|
|
}
|
|
|
|
nsTArray<nsCString> fontList;
|
|
fontList.AppendElement(workingFont);
|
|
|
|
nsTArray<nsCString> fontsUsed;
|
|
|
|
pfl->ListFontsUsedForString(u"Hello"_ns, fontList, fontsUsed);
|
|
|
|
EXPECT_EQ(fontsUsed.Length(), 1u)
|
|
<< "Should use exactly one font for 'Hello'";
|
|
|
|
if (fontsUsed.Length() > 0) {
|
|
// The font name will be lowercased by GenerateFontListKey
|
|
nsCString expectedLower(workingFont);
|
|
ToLowerCase(expectedLower);
|
|
nsCString actualLower(fontsUsed[0]);
|
|
ToLowerCase(actualLower);
|
|
EXPECT_EQ(actualLower, expectedLower);
|
|
}
|
|
}
|
|
|
|
TEST_F(FontFallbackTest, ListFontsUsedForString_NonExistentFont) {
|
|
gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
|
|
ASSERT_NE(pfl, nullptr);
|
|
|
|
nsTArray<nsCString> fontList;
|
|
fontList.AppendElement("This Font Does Not Exist 12345"_ns);
|
|
|
|
nsTArray<nsCString> fontsUsed;
|
|
|
|
pfl->ListFontsUsedForString(u"A"_ns, fontList, fontsUsed);
|
|
|
|
// The non-existent font won't be found, but global fallback will find a
|
|
// system font to render 'A'.
|
|
EXPECT_EQ(fontsUsed.Length(), 1u)
|
|
<< "Global fallback should find exactly one font for 'A'";
|
|
}
|
|
|
|
TEST_F(FontFallbackTest, ListFontsUsedForString_FallbackOrder) {
|
|
gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
|
|
ASSERT_NE(pfl, nullptr);
|
|
|
|
// Find two fonts that exist
|
|
const char* commonFonts[] = {
|
|
"DejaVu Sans", "Liberation Sans", "Roboto", "Noto Sans",
|
|
"DejaVu Serif", "Liberation Serif", "Noto Serif", "Nimbus Sans",
|
|
"Nimbus Roman", "FreeSans", "FreeSerif"};
|
|
|
|
nsTArray<nsCString> existingFonts;
|
|
for (const char* fontNameStr : commonFonts) {
|
|
nsCString fontName(fontNameStr);
|
|
if (FontHasCharacter(pfl, fontName, 'A')) {
|
|
existingFonts.AppendElement(fontName);
|
|
if (existingFonts.Length() >= 2) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (existingFonts.Length() < 2) {
|
|
GTEST_SKIP() << "Need at least 2 fonts for fallback order test";
|
|
}
|
|
|
|
// Test that first font in list wins
|
|
nsTArray<nsCString> fontList;
|
|
fontList.AppendElement(existingFonts[0]);
|
|
fontList.AppendElement(existingFonts[1]);
|
|
|
|
nsTArray<nsCString> fontsUsed;
|
|
|
|
pfl->ListFontsUsedForString(u"A"_ns, fontList, fontsUsed);
|
|
|
|
EXPECT_EQ(fontsUsed.Length(), 1u);
|
|
if (fontsUsed.Length() > 0) {
|
|
nsCString expectedLower(existingFonts[0]);
|
|
ToLowerCase(expectedLower);
|
|
nsCString actualLower(fontsUsed[0]);
|
|
ToLowerCase(actualLower);
|
|
EXPECT_EQ(actualLower, expectedLower)
|
|
<< "First font in list should be used when both have the character";
|
|
}
|
|
}
|
|
|
|
TEST_F(FontFallbackTest, ListFontsUsedForString_MultipleFontsWithEmoji) {
|
|
gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
|
|
ASSERT_NE(pfl, nullptr);
|
|
|
|
// U+1F600 = 😀 (grinning face)
|
|
const uint32_t grinningFace = 0x1F600;
|
|
|
|
// Find a text font that has 'A' but not emoji
|
|
const char* textFonts[] = {"DejaVu Sans", "Liberation Sans", "Roboto",
|
|
"Noto Sans", "DejaVu Serif", "FreeSans",
|
|
"Nimbus Sans"};
|
|
// Find an emoji font that has emoji
|
|
const char* emojiFonts[] = {"Noto Color Emoji", "Noto Emoji",
|
|
"Twemoji", "EmojiOne",
|
|
"Symbola", "Segoe UI Emoji"};
|
|
|
|
nsCString textFont;
|
|
for (const char* fontNameStr : textFonts) {
|
|
nsCString fontName(fontNameStr);
|
|
bool hasA = FontHasCharacter(pfl, fontName, 'A');
|
|
bool hasEmoji = FontHasCharacter(pfl, fontName, grinningFace);
|
|
// Want a font that has 'A' but NOT the emoji
|
|
if (hasA && !hasEmoji) {
|
|
textFont = fontName;
|
|
break;
|
|
}
|
|
}
|
|
|
|
nsCString emojiFont;
|
|
for (const char* fontNameStr : emojiFonts) {
|
|
nsCString fontName(fontNameStr);
|
|
if (FontHasCharacter(pfl, fontName, grinningFace)) {
|
|
emojiFont = fontName;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (textFont.IsEmpty() || emojiFont.IsEmpty()) {
|
|
GTEST_SKIP() << "Need both a text font (without emoji) and an emoji font. "
|
|
<< "textFont='"
|
|
<< (textFont.IsEmpty() ? "(none)" : textFont.get()) << "' "
|
|
<< "emojiFont='"
|
|
<< (emojiFont.IsEmpty() ? "(none)" : emojiFont.get()) << "'";
|
|
}
|
|
|
|
// Test string: "Hi [grinning face]" - should need both fonts
|
|
nsTArray<nsCString> fontList;
|
|
fontList.AppendElement(textFont);
|
|
fontList.AppendElement(emojiFont);
|
|
|
|
nsTArray<nsCString> fontsUsed;
|
|
|
|
pfl->ListFontsUsedForString(u"Hi \U0001F600"_ns, fontList, fontsUsed);
|
|
|
|
EXPECT_EQ(fontsUsed.Length(), 2u)
|
|
<< "Should use exactly 2 fonts: one for text, one for emoji";
|
|
|
|
if (fontsUsed.Length() == 2) {
|
|
// Verify the fonts are what we expect (order should be textFont first)
|
|
nsCString textFontLower(textFont);
|
|
ToLowerCase(textFontLower);
|
|
nsCString emojiFontLower(emojiFont);
|
|
ToLowerCase(emojiFontLower);
|
|
|
|
nsCString firstUsedLower(fontsUsed[0]);
|
|
ToLowerCase(firstUsedLower);
|
|
nsCString secondUsedLower(fontsUsed[1]);
|
|
ToLowerCase(secondUsedLower);
|
|
|
|
EXPECT_EQ(firstUsedLower, textFontLower)
|
|
<< "First font used should be the text font";
|
|
EXPECT_EQ(secondUsedLower, emojiFontLower)
|
|
<< "Second font used should be the emoji font";
|
|
}
|
|
}
|
|
|
|
TEST_F(FontFallbackTest, ListFontsUsedForString_VisibilityFilter) {
|
|
gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
|
|
ASSERT_NE(pfl, nullptr);
|
|
|
|
// Find a font that exists on this system
|
|
const char* commonFonts[] = {"DejaVu Sans", "Liberation Sans", "Roboto",
|
|
"Noto Sans", "Arial", "Helvetica",
|
|
"Sans"};
|
|
|
|
nsCString workingFont;
|
|
for (const char* fontNameStr : commonFonts) {
|
|
nsCString fontName(fontNameStr);
|
|
if (FontHasCharacter(pfl, fontName, 'A')) {
|
|
workingFont = fontName;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (workingFont.IsEmpty()) {
|
|
GTEST_SKIP() << "No common test fonts available on this system";
|
|
}
|
|
|
|
nsTArray<nsCString> fontList;
|
|
fontList.AppendElement(workingFont);
|
|
|
|
// Test with User visibility (default) - should work
|
|
nsTArray<nsCString> fontsUsedUser;
|
|
pfl->ListFontsUsedForString(u"A"_ns, fontList, fontsUsedUser,
|
|
FontVisibility::User);
|
|
|
|
// The font should be found with User visibility (most permissive common case)
|
|
// Note: We can't easily predict font visibility, so we just verify the API
|
|
// works and returns consistent results
|
|
|
|
// Test with Base visibility - more restrictive
|
|
nsTArray<nsCString> fontsUsedBase;
|
|
pfl->ListFontsUsedForString(u"A"_ns, fontList, fontsUsedBase,
|
|
FontVisibility::Base);
|
|
|
|
// With Base visibility, we either get the font (if it's a base font) or not
|
|
EXPECT_LE(fontsUsedBase.Length(), 1u)
|
|
<< "With Base visibility, should have at most one font";
|
|
|
|
EXPECT_LE(fontsUsedUser.Length(), 1u)
|
|
<< "With User visibility, should have at most one font";
|
|
}
|
|
|
|
TEST_F(FontFallbackTest, ListFontsUsedForString_DefaultVisibility) {
|
|
gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
|
|
ASSERT_NE(pfl, nullptr);
|
|
|
|
const char* commonFonts[] = {"DejaVu Sans", "Liberation Sans", "Roboto",
|
|
"Noto Sans", "Arial", "Helvetica",
|
|
"Sans"};
|
|
|
|
nsCString workingFont;
|
|
for (const char* fontNameStr : commonFonts) {
|
|
nsCString fontName(fontNameStr);
|
|
if (FontHasCharacter(pfl, fontName, 'A')) {
|
|
workingFont = fontName;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (workingFont.IsEmpty()) {
|
|
GTEST_SKIP() << "No common test fonts available on this system";
|
|
}
|
|
|
|
nsTArray<nsCString> fontList;
|
|
fontList.AppendElement(workingFont);
|
|
|
|
// Call without visibility parameter (should use default User)
|
|
nsTArray<nsCString> fontsUsedDefault;
|
|
pfl->ListFontsUsedForString(u"Test"_ns, fontList, fontsUsedDefault);
|
|
|
|
// Call with explicit User visibility
|
|
nsTArray<nsCString> fontsUsedUser;
|
|
pfl->ListFontsUsedForString(u"Test"_ns, fontList, fontsUsedUser,
|
|
FontVisibility::User);
|
|
|
|
// Results should be identical
|
|
EXPECT_EQ(fontsUsedDefault.Length(), fontsUsedUser.Length())
|
|
<< "Default visibility should match explicit User visibility";
|
|
}
|
|
|
|
TEST_F(FontFallbackTest, ListFontsUsedForString_VisibilityMonotonicity) {
|
|
gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
|
|
ASSERT_NE(pfl, nullptr);
|
|
|
|
const char* commonFonts[] = {"DejaVu Sans", "Liberation Sans", "Roboto",
|
|
"Noto Sans", "Arial", "Helvetica",
|
|
"Sans"};
|
|
|
|
nsCString workingFont;
|
|
for (const char* fontNameStr : commonFonts) {
|
|
nsCString fontName(fontNameStr);
|
|
if (FontHasCharacter(pfl, fontName, 'A')) {
|
|
workingFont = fontName;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (workingFont.IsEmpty()) {
|
|
GTEST_SKIP() << "No common test fonts available on this system";
|
|
}
|
|
|
|
nsTArray<nsCString> fontList;
|
|
fontList.AppendElement(workingFont);
|
|
|
|
// Test all visibility levels from most restrictive to least
|
|
FontVisibility levels[] = {FontVisibility::Base, FontVisibility::LangPack,
|
|
FontVisibility::User, FontVisibility::Hidden};
|
|
|
|
size_t prevFontsUsed = 0;
|
|
for (FontVisibility vis : levels) {
|
|
nsTArray<nsCString> fontsUsed;
|
|
pfl->ListFontsUsedForString(u"Hello"_ns, fontList, fontsUsed, vis);
|
|
|
|
// More permissive visibility should have at least as many fonts available
|
|
EXPECT_GE(fontsUsed.Length(), prevFontsUsed)
|
|
<< "Visibility level " << static_cast<int>(vis)
|
|
<< " should not have fewer fonts than more restrictive levels";
|
|
prevFontsUsed = fontsUsed.Length();
|
|
}
|
|
}
|
|
|
|
TEST_F(FontFallbackTest, ListFontsUsedForString_VisibilityWithMultipleFonts) {
|
|
gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
|
|
ASSERT_NE(pfl, nullptr);
|
|
|
|
// Find multiple fonts that exist
|
|
const char* commonFonts[] = {
|
|
"DejaVu Sans", "Liberation Sans", "Roboto", "Noto Sans",
|
|
"DejaVu Serif", "Liberation Serif", "Noto Serif", "Nimbus Sans",
|
|
"FreeSans", "FreeSerif"};
|
|
|
|
nsTArray<nsCString> existingFonts;
|
|
for (const char* fontNameStr : commonFonts) {
|
|
nsCString fontName(fontNameStr);
|
|
if (FontHasCharacter(pfl, fontName, 'A')) {
|
|
existingFonts.AppendElement(fontName);
|
|
if (existingFonts.Length() >= 3) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (existingFonts.Length() < 2) {
|
|
GTEST_SKIP() << "Need at least 2 fonts for this test";
|
|
}
|
|
|
|
// Test with Hidden visibility (most permissive)
|
|
nsTArray<nsCString> fontsUsedHidden;
|
|
pfl->ListFontsUsedForString(u"ABC"_ns, existingFonts, fontsUsedHidden,
|
|
FontVisibility::Hidden);
|
|
|
|
// With multiple fonts all having 'A', 'B', 'C', only the first should be used
|
|
// (if that font covers all characters)
|
|
EXPECT_GE(fontsUsedHidden.Length(), 1u)
|
|
<< "Should use at least one font for basic Latin chars";
|
|
}
|
|
|
|
TEST_F(FontFallbackTest, ListFontsUsedForString_VariantI_EmojiFontList) {
|
|
gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
|
|
ASSERT_NE(pfl, nullptr);
|
|
|
|
// Emoji font list from populateSVGRect's CSS_FONT_FAMILY
|
|
nsTArray<nsCString> emojiFontList;
|
|
emojiFontList.AppendElement("Apple Color Emoji"_ns);
|
|
emojiFontList.AppendElement("Segoe UI Emoji"_ns);
|
|
emojiFontList.AppendElement("Segoe UI Symbol"_ns);
|
|
emojiFontList.AppendElement("Noto Color Emoji"_ns);
|
|
emojiFontList.AppendElement("EmojiOne Color"_ns);
|
|
emojiFontList.AppendElement("Android Emoji"_ns);
|
|
emojiFontList.AppendElement("sans-serif"_ns);
|
|
|
|
// A few emojis from the SVG list
|
|
nsAutoString emojiText;
|
|
AppendUCS4ToUTF16(0x1F600, emojiText); // Grinning Face
|
|
AppendUCS4ToUTF16(0x263A, emojiText); // White Smiling Face
|
|
AppendUCS4ToUTF16(0x2708, emojiText); // Airplane
|
|
|
|
nsTArray<nsCString> fontsAllowlisted;
|
|
pfl->ListFontsUsedForString(emojiText, emojiFontList, fontsAllowlisted,
|
|
FontVisibility::LangPack);
|
|
nsTArray<nsCString> fontsNonAllowlisted;
|
|
pfl->ListFontsUsedForString(emojiText, emojiFontList, fontsNonAllowlisted,
|
|
FontVisibility::User);
|
|
|
|
// User visibility should find at least as many fonts as LangPack
|
|
EXPECT_GE(fontsNonAllowlisted.Length(), fontsAllowlisted.Length());
|
|
|
|
// On any platform with emoji support, at least one font should be found
|
|
EXPECT_GT(fontsNonAllowlisted.Length(), 0u)
|
|
<< "Should find at least one emoji font for basic emoji codepoints";
|
|
}
|