/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * 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 "mozilla/ArrayUtils.h"

#include "gfxFontconfigUtils.h"
#include "gfxFont.h"
#include "nsGkAtoms.h"

#include <locale.h>
#include <fontconfig/fontconfig.h>

#include "nsServiceManagerUtils.h"
#include "nsILanguageAtomService.h"
#include "nsTArray.h"
#include "mozilla/Preferences.h"
#include "nsDirectoryServiceUtils.h"
#include "nsDirectoryServiceDefs.h"
#include "nsAppDirectoryServiceDefs.h"

#include "nsIAtom.h"
#include "nsCRT.h"
#include "gfxFontConstants.h"
#include "mozilla/gfx/2D.h"

using namespace mozilla;

/* static */ gfxFontconfigUtils* gfxFontconfigUtils::sUtils = nullptr;
static nsILanguageAtomService* gLangService = nullptr;

/* static */ void
gfxFontconfigUtils::Shutdown() {
    if (sUtils) {
        delete sUtils;
        sUtils = nullptr;
    }
    NS_IF_RELEASE(gLangService);
}

/* static */ uint8_t
gfxFontconfigUtils::FcSlantToThebesStyle(int aFcSlant)
{
    switch (aFcSlant) {
        case FC_SLANT_ITALIC:
            return NS_FONT_STYLE_ITALIC;
        case FC_SLANT_OBLIQUE:
            return NS_FONT_STYLE_OBLIQUE;
        default:
            return NS_FONT_STYLE_NORMAL;
    }
}

/* static */ uint8_t
gfxFontconfigUtils::GetThebesStyle(FcPattern *aPattern)
{
    int slant;
    if (FcPatternGetInteger(aPattern, FC_SLANT, 0, &slant) != FcResultMatch) {
        return NS_FONT_STYLE_NORMAL;
    }

    return FcSlantToThebesStyle(slant);
}

/* static */ int
gfxFontconfigUtils::GetFcSlant(const gfxFontStyle& aFontStyle)
{
    if (aFontStyle.style == NS_FONT_STYLE_ITALIC)
        return FC_SLANT_ITALIC;
    if (aFontStyle.style == NS_FONT_STYLE_OBLIQUE)
        return FC_SLANT_OBLIQUE;

    return FC_SLANT_ROMAN;
}

// OS/2 weight classes were introduced in fontconfig-2.1.93 (2003).
#ifndef FC_WEIGHT_THIN 
#define FC_WEIGHT_THIN              0 // 2.1.93
#define FC_WEIGHT_EXTRALIGHT        40 // 2.1.93
#define FC_WEIGHT_REGULAR           80 // 2.1.93
#define FC_WEIGHT_EXTRABOLD         205 // 2.1.93
#endif
// book was introduced in fontconfig-2.2.90 (and so fontconfig-2.3.0 in 2005)
#ifndef FC_WEIGHT_BOOK
#define FC_WEIGHT_BOOK              75
#endif
// extra black was introduced in fontconfig-2.4.91 (2007)
#ifndef FC_WEIGHT_EXTRABLACK
#define FC_WEIGHT_EXTRABLACK        215
#endif

/* static */ uint16_t
gfxFontconfigUtils::GetThebesWeight(FcPattern *aPattern)
{
    int weight;
    if (FcPatternGetInteger(aPattern, FC_WEIGHT, 0, &weight) != FcResultMatch)
        return NS_FONT_WEIGHT_NORMAL;

    if (weight <= (FC_WEIGHT_THIN + FC_WEIGHT_EXTRALIGHT) / 2)
        return 100;
    if (weight <= (FC_WEIGHT_EXTRALIGHT + FC_WEIGHT_LIGHT) / 2)
        return 200;
    if (weight <= (FC_WEIGHT_LIGHT + FC_WEIGHT_BOOK) / 2)
        return 300;
    if (weight <= (FC_WEIGHT_REGULAR + FC_WEIGHT_MEDIUM) / 2)
        // This includes FC_WEIGHT_BOOK
        return 400;
    if (weight <= (FC_WEIGHT_MEDIUM + FC_WEIGHT_DEMIBOLD) / 2)
        return 500;
    if (weight <= (FC_WEIGHT_DEMIBOLD + FC_WEIGHT_BOLD) / 2)
        return 600;
    if (weight <= (FC_WEIGHT_BOLD + FC_WEIGHT_EXTRABOLD) / 2)
        return 700;
    if (weight <= (FC_WEIGHT_EXTRABOLD + FC_WEIGHT_BLACK) / 2)
        return 800;
    if (weight <= FC_WEIGHT_BLACK)
        return 900;

    // including FC_WEIGHT_EXTRABLACK
    return 901;
}

/* static */ int
gfxFontconfigUtils::FcWeightForBaseWeight(int8_t aBaseWeight)
{
    NS_PRECONDITION(aBaseWeight >= 0 && aBaseWeight <= 10,
                    "base weight out of range");

    switch (aBaseWeight) {
        case 2:
            return FC_WEIGHT_EXTRALIGHT;
        case 3:
            return FC_WEIGHT_LIGHT;
        case 4:
            return FC_WEIGHT_REGULAR;
        case 5:
            return FC_WEIGHT_MEDIUM;
        case 6:
            return FC_WEIGHT_DEMIBOLD;
        case 7:
            return FC_WEIGHT_BOLD;
        case 8:
            return FC_WEIGHT_EXTRABOLD;
        case 9:
            return FC_WEIGHT_BLACK;
    }

    // extremes
    return aBaseWeight < 2 ? FC_WEIGHT_THIN : FC_WEIGHT_EXTRABLACK;
}

/* static */ int16_t
gfxFontconfigUtils::GetThebesStretch(FcPattern *aPattern)
{
    int width;
    if (FcPatternGetInteger(aPattern, FC_WIDTH, 0, &width) != FcResultMatch) {
        return NS_FONT_STRETCH_NORMAL;
    }

    if (width <= (FC_WIDTH_ULTRACONDENSED + FC_WIDTH_EXTRACONDENSED) / 2) {
        return NS_FONT_STRETCH_ULTRA_CONDENSED;
    }
    if (width <= (FC_WIDTH_EXTRACONDENSED + FC_WIDTH_CONDENSED) / 2) {
        return NS_FONT_STRETCH_EXTRA_CONDENSED;
    }
    if (width <= (FC_WIDTH_CONDENSED + FC_WIDTH_SEMICONDENSED) / 2) {
        return NS_FONT_STRETCH_CONDENSED;
    }
    if (width <= (FC_WIDTH_SEMICONDENSED + FC_WIDTH_NORMAL) / 2) {
        return NS_FONT_STRETCH_SEMI_CONDENSED;
    }
    if (width <= (FC_WIDTH_NORMAL + FC_WIDTH_SEMIEXPANDED) / 2) {
        return NS_FONT_STRETCH_NORMAL;
    }
    if (width <= (FC_WIDTH_SEMIEXPANDED + FC_WIDTH_EXPANDED) / 2) {
        return NS_FONT_STRETCH_SEMI_EXPANDED;
    }
    if (width <= (FC_WIDTH_EXPANDED + FC_WIDTH_EXTRAEXPANDED) / 2) {
        return NS_FONT_STRETCH_EXPANDED;
    }
    if (width <= (FC_WIDTH_EXTRAEXPANDED + FC_WIDTH_ULTRAEXPANDED) / 2) {
        return NS_FONT_STRETCH_EXTRA_EXPANDED;
    }
    return NS_FONT_STRETCH_ULTRA_EXPANDED;
}

/* static */ int
gfxFontconfigUtils::FcWidthForThebesStretch(int16_t aStretch)
{
    switch (aStretch) {
        default: // this will catch "normal" (0) as well as out-of-range values
            return FC_WIDTH_NORMAL;
        case NS_FONT_STRETCH_ULTRA_CONDENSED:
            return FC_WIDTH_ULTRACONDENSED;
        case NS_FONT_STRETCH_EXTRA_CONDENSED:
            return FC_WIDTH_EXTRACONDENSED;
        case NS_FONT_STRETCH_CONDENSED:
            return FC_WIDTH_CONDENSED;
        case NS_FONT_STRETCH_SEMI_CONDENSED:
            return FC_WIDTH_SEMICONDENSED;
        case NS_FONT_STRETCH_SEMI_EXPANDED:
            return FC_WIDTH_SEMIEXPANDED;
        case NS_FONT_STRETCH_EXPANDED:
            return FC_WIDTH_EXPANDED;
        case NS_FONT_STRETCH_EXTRA_EXPANDED:
            return FC_WIDTH_EXTRAEXPANDED;
        case NS_FONT_STRETCH_ULTRA_EXPANDED:
            return FC_WIDTH_ULTRAEXPANDED;
    }
}

// This makes a guess at an FC_WEIGHT corresponding to a base weight and
// offset (without any knowledge of which weights are available).

/* static */ int
GuessFcWeight(const gfxFontStyle& aFontStyle)
{
    /*
     * weights come in two parts crammed into one
     * integer -- the "base" weight is weight / 100,
     * the rest of the value is the "offset" from that
     * weight -- the number of steps to move to adjust
     * the weight in the list of supported font weights,
     * this value can be negative or positive.
     */
    int8_t weight = aFontStyle.ComputeWeight();

    // ComputeWeight trimmed the range of weights for us
    NS_ASSERTION(weight >= 0 && weight <= 10,
                 "base weight out of range");

    return gfxFontconfigUtils::FcWeightForBaseWeight(weight);
}

static void
AddString(FcPattern *aPattern, const char *object, const char *aString)
{
    FcPatternAddString(aPattern, object,
                       gfxFontconfigUtils::ToFcChar8(aString));
}

static void
AddWeakString(FcPattern *aPattern, const char *object, const char *aString)
{
    FcValue value;
    value.type = FcTypeString;
    value.u.s = gfxFontconfigUtils::ToFcChar8(aString);

    FcPatternAddWeak(aPattern, object, value, FcTrue);
}

static void
AddLangGroup(FcPattern *aPattern, nsIAtom *aLangGroup)
{
    // Translate from mozilla's internal mapping into fontconfig's
    nsAutoCString lang;
    gfxFontconfigUtils::GetSampleLangForGroup(aLangGroup, &lang);

    if (!lang.IsEmpty()) {
        AddString(aPattern, FC_LANG, lang.get());
    }
}

nsReturnRef<FcPattern>
gfxFontconfigUtils::NewPattern(const nsTArray<nsString>& aFamilies,
                               const gfxFontStyle& aFontStyle,
                               const char *aLang)
{
    static const char* sFontconfigGenerics[] =
        { "sans-serif", "serif", "monospace", "fantasy", "cursive" };

    nsAutoRef<FcPattern> pattern(FcPatternCreate());
    if (!pattern)
        return nsReturnRef<FcPattern>();

    FcPatternAddDouble(pattern, FC_PIXEL_SIZE, aFontStyle.size);
    FcPatternAddInteger(pattern, FC_SLANT, GetFcSlant(aFontStyle));
    FcPatternAddInteger(pattern, FC_WEIGHT, GuessFcWeight(aFontStyle));
    FcPatternAddInteger(pattern, FC_WIDTH, FcWidthForThebesStretch(aFontStyle.stretch));

    if (aLang) {
        AddString(pattern, FC_LANG, aLang);
    }

    bool useWeakBinding = false;
    for (uint32_t i = 0; i < aFamilies.Length(); ++i) {
        NS_ConvertUTF16toUTF8 family(aFamilies[i]);
        if (!useWeakBinding) {
            AddString(pattern, FC_FAMILY, family.get());

            // fontconfig generic families are typically implemented with weak
            // aliases (so that the preferred font depends on language).
            // However, this would give them lower priority than subsequent
            // non-generic families in the list.  To ensure that subsequent
            // families do not have a higher priority, they are given weak
            // bindings.
            for (uint32_t g = 0;
                 g < ArrayLength(sFontconfigGenerics);
                 ++g) {
                if (0 == FcStrCmpIgnoreCase(ToFcChar8(sFontconfigGenerics[g]),
                                            ToFcChar8(family.get()))) {
                    useWeakBinding = true;
                    break;
                }
            }
        } else {
            AddWeakString(pattern, FC_FAMILY, family.get());
        }
    }

    return pattern.out();
}

gfxFontconfigUtils::gfxFontconfigUtils()
    : mFontsByFamily(32)
    , mFontsByFullname(32)
    , mLangSupportTable(32)
    , mLastConfig(nullptr)
#ifdef MOZ_BUNDLED_FONTS
    , mBundledFontsInitialized(false)
#endif
{
    UpdateFontListInternal();
}

nsresult
gfxFontconfigUtils::GetFontList(nsIAtom *aLangGroup,
                                const nsACString& aGenericFamily,
                                nsTArray<nsString>& aListOfFonts)
{
    aListOfFonts.Clear();

    nsTArray<nsCString> fonts;
    nsresult rv = GetFontListInternal(fonts, aLangGroup);
    if (NS_FAILED(rv))
        return rv;

    for (uint32_t i = 0; i < fonts.Length(); ++i) {
        aListOfFonts.AppendElement(NS_ConvertUTF8toUTF16(fonts[i]));
    }

    aListOfFonts.Sort();

    int32_t serif = 0, sansSerif = 0, monospace = 0;

    // Fontconfig supports 3 generic fonts, "serif", "sans-serif", and
    // "monospace", slightly different from CSS's 5.
    if (aGenericFamily.IsEmpty())
        serif = sansSerif = monospace = 1;
    else if (aGenericFamily.LowerCaseEqualsLiteral("serif"))
        serif = 1;
    else if (aGenericFamily.LowerCaseEqualsLiteral("sans-serif"))
        sansSerif = 1;
    else if (aGenericFamily.LowerCaseEqualsLiteral("monospace"))
        monospace = 1;
    else if (aGenericFamily.LowerCaseEqualsLiteral("cursive") ||
             aGenericFamily.LowerCaseEqualsLiteral("fantasy"))
        serif = sansSerif = 1;
    else
        NS_NOTREACHED("unexpected CSS generic font family");

    // The first in the list becomes the default in
    // FontBuilder.readFontSelection() if the preference-selected font is not
    // available, so put system configured defaults first.
    if (monospace)
        aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("monospace"));
    if (sansSerif)
        aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("sans-serif"));
    if (serif)
        aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("serif"));

    return NS_OK;
}

struct MozLangGroupData {
    nsIAtom* const& mozLangGroup;
    const char *defaultLang;
};

const MozLangGroupData MozLangGroups[] = {
    { nsGkAtoms::x_western,      "en" },
    { nsGkAtoms::x_cyrillic,     "ru" },
    { nsGkAtoms::x_devanagari,   "hi" },
    { nsGkAtoms::x_tamil,        "ta" },
    { nsGkAtoms::x_armn,         "hy" },
    { nsGkAtoms::x_beng,         "bn" },
    { nsGkAtoms::x_cans,         "iu" },
    { nsGkAtoms::x_ethi,         "am" },
    { nsGkAtoms::x_geor,         "ka" },
    { nsGkAtoms::x_gujr,         "gu" },
    { nsGkAtoms::x_guru,         "pa" },
    { nsGkAtoms::x_khmr,         "km" },
    { nsGkAtoms::x_knda,         "kn" },
    { nsGkAtoms::x_mlym,         "ml" },
    { nsGkAtoms::x_orya,         "or" },
    { nsGkAtoms::x_sinh,         "si" },
    { nsGkAtoms::x_telu,         "te" },
    { nsGkAtoms::x_tibt,         "bo" },
    { nsGkAtoms::Unicode,        0    },
};

static bool
TryLangForGroup(const nsACString& aOSLang, nsIAtom *aLangGroup,
                nsACString *aFcLang)
{
    // Truncate at '.' or '@' from aOSLang, and convert '_' to '-'.
    // aOSLang is in the form "language[_territory][.codeset][@modifier]".
    // fontconfig takes languages in the form "language-territory".
    // nsILanguageAtomService takes languages in the form language-subtag,
    // where subtag may be a territory.  fontconfig and nsILanguageAtomService
    // handle case-conversion for us.
    const char *pos, *end;
    aOSLang.BeginReading(pos);
    aOSLang.EndReading(end);
    aFcLang->Truncate();
    while (pos < end) {
        switch (*pos) {
            case '.':
            case '@':
                end = pos;
                break;
            case '_':
                aFcLang->Append('-');
                break;
            default:
                aFcLang->Append(*pos);
        }
        ++pos;
    }

    nsIAtom *atom =
        gLangService->LookupLanguage(*aFcLang);

    return atom == aLangGroup;
}

/* static */ void
gfxFontconfigUtils::GetSampleLangForGroup(nsIAtom *aLangGroup,
                                          nsACString *aFcLang)
{
    NS_PRECONDITION(aFcLang != nullptr, "aFcLang must not be NULL");

    const MozLangGroupData *langGroup = nullptr;

    for (unsigned int i = 0; i < ArrayLength(MozLangGroups); ++i) {
        if (aLangGroup == MozLangGroups[i].mozLangGroup) {
            langGroup = &MozLangGroups[i];
            break;
        }
    }

    if (!langGroup) {
        // Not a special mozilla language group.
        // Use aLangGroup as a language code.
        aLangGroup->ToUTF8String(*aFcLang);
        return;
    }

    // Check the environment for the users preferred language that corresponds
    // to this langGroup.
    if (!gLangService) {
        CallGetService(NS_LANGUAGEATOMSERVICE_CONTRACTID, &gLangService);
    }

    if (gLangService) {
        const char *languages = getenv("LANGUAGE");
        if (languages) {
            const char separator = ':';

            for (const char *pos = languages; true; ++pos) {
                if (*pos == '\0' || *pos == separator) {
                    if (languages < pos &&
                        TryLangForGroup(Substring(languages, pos),
                                        aLangGroup, aFcLang))
                        return;

                    if (*pos == '\0')
                        break;

                    languages = pos + 1;
                }
            }
        }
        const char *ctype = setlocale(LC_CTYPE, nullptr);
        if (ctype &&
            TryLangForGroup(nsDependentCString(ctype), aLangGroup, aFcLang))
            return;
    }

    if (langGroup->defaultLang) {
        aFcLang->Assign(langGroup->defaultLang);
    } else {
        aFcLang->Truncate();
    }
}

nsresult
gfxFontconfigUtils::GetFontListInternal(nsTArray<nsCString>& aListOfFonts,
                                        nsIAtom *aLangGroup)
{
    FcPattern *pat = nullptr;
    FcObjectSet *os = nullptr;
    FcFontSet *fs = nullptr;
    nsresult rv = NS_ERROR_FAILURE;

    aListOfFonts.Clear();

    pat = FcPatternCreate();
    if (!pat)
        goto end;

    os = FcObjectSetBuild(FC_FAMILY, nullptr);
    if (!os)
        goto end;

    // take the pattern and add the lang group to it
    if (aLangGroup) {
        AddLangGroup(pat, aLangGroup);
    }

    fs = FcFontList(nullptr, pat, os);
    if (!fs)
        goto end;

    for (int i = 0; i < fs->nfont; i++) {
        char *family;

        if (FcPatternGetString(fs->fonts[i], FC_FAMILY, 0,
                               (FcChar8 **) &family) != FcResultMatch)
        {
            continue;
        }

        // Remove duplicates...
        nsAutoCString strFamily(family);
        if (aListOfFonts.Contains(strFamily))
            continue;

        aListOfFonts.AppendElement(strFamily);
    }

    rv = NS_OK;

  end:
    if (NS_FAILED(rv))
        aListOfFonts.Clear();

    if (pat)
        FcPatternDestroy(pat);
    if (os)
        FcObjectSetDestroy(os);
    if (fs)
        FcFontSetDestroy(fs);

    return rv;
}

nsresult
gfxFontconfigUtils::UpdateFontList()
{
    return UpdateFontListInternal(true);
}

nsresult
gfxFontconfigUtils::UpdateFontListInternal(bool aForce)
{
    if (!aForce) {
        // This checks periodically according to fontconfig's configured
        // <rescan> interval.
        FcInitBringUptoDate();
    } else if (!FcConfigUptoDate(nullptr)) { // check now with aForce
        mLastConfig = nullptr;
        FcInitReinitialize();
    }

    // FcInitReinitialize() (used by FcInitBringUptoDate) creates a new config
    // before destroying the old config, so the only way that we'd miss an
    // update is if fontconfig did more than one update and the memory for the
    // most recent config happened to be at the same location as the original
    // config.
    FcConfig *currentConfig = FcConfigGetCurrent();
    if (currentConfig == mLastConfig)
        return NS_OK;

#ifdef MOZ_BUNDLED_FONTS
    ActivateBundledFonts();
#endif

    // These FcFontSets are owned by fontconfig
    FcFontSet *fontSets[] = {
        FcConfigGetFonts(currentConfig, FcSetSystem)
#ifdef MOZ_BUNDLED_FONTS
        , FcConfigGetFonts(currentConfig, FcSetApplication)
#endif
    };

    mFontsByFamily.Clear();
    mFontsByFullname.Clear();
    mLangSupportTable.Clear();

    // Record the existing font families
    for (unsigned fs = 0; fs < ArrayLength(fontSets); ++fs) {
        FcFontSet *fontSet = fontSets[fs];
        if (!fontSet) { // the application set might not exist
            continue;
        }
        for (int f = 0; f < fontSet->nfont; ++f) {
            FcPattern *font = fontSet->fonts[f];

            FcChar8 *family;
            for (int v = 0;
             FcPatternGetString(font, FC_FAMILY, v, &family) == FcResultMatch;
             ++v) {
                FontsByFcStrEntry *entry = mFontsByFamily.PutEntry(family);
                if (entry) {
                    bool added = entry->AddFont(font);

                    if (!entry->mKey) {
                        // The reference to the font pattern keeps the pointer to
                        // string for the key valid.  If adding the font failed
                        // then the entry must be removed.
                        if (added) {
                            entry->mKey = family;
                        } else {
                            mFontsByFamily.RawRemoveEntry(entry);
                        }
                    }
                }
            }
        }
    }

    mLastConfig = currentConfig;
    return NS_OK;
}

nsresult
gfxFontconfigUtils::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName)
{
    aFamilyName.Truncate();

    // The fontconfig has generic family names in the font list.
    if (aFontName.EqualsLiteral("serif") ||
        aFontName.EqualsLiteral("sans-serif") ||
        aFontName.EqualsLiteral("monospace")) {
        aFamilyName.Assign(aFontName);
        return NS_OK;
    }

    nsresult rv = UpdateFontListInternal();
    if (NS_FAILED(rv))
        return rv;

    NS_ConvertUTF16toUTF8 fontname(aFontName);

    // return empty string if no such family exists
    if (!IsExistingFamily(fontname))
        return NS_OK;

    FcPattern *pat = nullptr;
    FcObjectSet *os = nullptr;
    FcFontSet *givenFS = nullptr;
    nsTArray<nsCString> candidates;
    FcFontSet *candidateFS = nullptr;
    rv = NS_ERROR_FAILURE;

    pat = FcPatternCreate();
    if (!pat)
        goto end;

    FcPatternAddString(pat, FC_FAMILY, (FcChar8 *)fontname.get());

    os = FcObjectSetBuild(FC_FAMILY, FC_FILE, FC_INDEX, nullptr);
    if (!os)
        goto end;

    givenFS = FcFontList(nullptr, pat, os);
    if (!givenFS)
        goto end;

    // The first value associated with a FC_FAMILY property is the family
    // returned by GetFontList(), so use this value if appropriate.

    // See if there is a font face with first family equal to the given family.
    for (int i = 0; i < givenFS->nfont; ++i) {
        char *firstFamily;
        if (FcPatternGetString(givenFS->fonts[i], FC_FAMILY, 0,
                               (FcChar8 **) &firstFamily) != FcResultMatch)
            continue;

        nsDependentCString first(firstFamily);
        if (!candidates.Contains(first)) {
            candidates.AppendElement(first);

            if (fontname.Equals(first)) {
                aFamilyName.Assign(aFontName);
                rv = NS_OK;
                goto end;
            }
        }
    }

    // See if any of the first family names represent the same set of font
    // faces as the given family.
    for (uint32_t j = 0; j < candidates.Length(); ++j) {
        FcPatternDel(pat, FC_FAMILY);
        FcPatternAddString(pat, FC_FAMILY, (FcChar8 *)candidates[j].get());

        candidateFS = FcFontList(nullptr, pat, os);
        if (!candidateFS)
            goto end;

        if (candidateFS->nfont != givenFS->nfont)
            continue;

        bool equal = true;
        for (int i = 0; i < givenFS->nfont; ++i) {
            if (!FcPatternEqual(candidateFS->fonts[i], givenFS->fonts[i])) {
                equal = false;
                break;
            }
        }
        if (equal) {
            AppendUTF8toUTF16(candidates[j], aFamilyName);
            rv = NS_OK;
            goto end;
        }
    }

    // No match found; return empty string.
    rv = NS_OK;

  end:
    if (pat)
        FcPatternDestroy(pat);
    if (os)
        FcObjectSetDestroy(os);
    if (givenFS)
        FcFontSetDestroy(givenFS);
    if (candidateFS)
        FcFontSetDestroy(candidateFS);

    return rv;
}

bool
gfxFontconfigUtils::IsExistingFamily(const nsCString& aFamilyName)
{
    return mFontsByFamily.GetEntry(ToFcChar8(aFamilyName)) != nullptr;
}

const nsTArray< nsCountedRef<FcPattern> >&
gfxFontconfigUtils::GetFontsForFamily(const FcChar8 *aFamilyName)
{
    FontsByFcStrEntry *entry = mFontsByFamily.GetEntry(aFamilyName);

    if (!entry)
        return mEmptyPatternArray;

    return entry->GetFonts();
}

// Fontconfig only provides a fullname property for fonts in formats with SFNT
// wrappers.  For other font formats (including PCF and PS Type 1), a fullname
// must be generated from the family and style properties.  Only the first
// family and style is checked, but that should be OK, as I don't expect
// non-SFNT fonts to have multiple families or styles.
bool
gfxFontconfigUtils::GetFullnameFromFamilyAndStyle(FcPattern *aFont,
                                                  nsACString *aFullname)
{
    FcChar8 *family;
    if (FcPatternGetString(aFont, FC_FAMILY, 0, &family) != FcResultMatch)
        return false;

    aFullname->Truncate();
    aFullname->Append(ToCString(family));

    FcChar8 *style;
    if (FcPatternGetString(aFont, FC_STYLE, 0, &style) == FcResultMatch &&
        strcmp(ToCString(style), "Regular") != 0) {
        aFullname->Append(' ');
        aFullname->Append(ToCString(style));
    }

    return true;
}

bool
gfxFontconfigUtils::FontsByFullnameEntry::KeyEquals(KeyTypePointer aKey) const
{
    const FcChar8 *key = mKey;
    // If mKey is nullptr, key comes from the style and family of the first
    // font.
    nsAutoCString fullname;
    if (!key) {
        NS_ASSERTION(mFonts.Length(), "No font in FontsByFullnameEntry!");
        GetFullnameFromFamilyAndStyle(mFonts[0], &fullname);

        key = ToFcChar8(fullname);
    }

    return FcStrCmpIgnoreCase(aKey, key) == 0;
}

void
gfxFontconfigUtils::AddFullnameEntries()
{
    // These FcFontSets are owned by fontconfig
    FcFontSet *fontSets[] = {
        FcConfigGetFonts(nullptr, FcSetSystem)
#ifdef MOZ_BUNDLED_FONTS
        , FcConfigGetFonts(nullptr, FcSetApplication)
#endif
    };

    for (unsigned fs = 0; fs < ArrayLength(fontSets); ++fs) {
        FcFontSet *fontSet = fontSets[fs];
        if (!fontSet) {
            continue;
        }
        // Record the existing font families
        for (int f = 0; f < fontSet->nfont; ++f) {
            FcPattern *font = fontSet->fonts[f];

            int v = 0;
            FcChar8 *fullname;
            while (FcPatternGetString(font,
                          FC_FULLNAME, v, &fullname) == FcResultMatch) {
                FontsByFullnameEntry *entry =
                    mFontsByFullname.PutEntry(fullname);
                if (entry) {
                    // entry always has space for one font, so the first
                    // AddFont will always succeed, and so the entry will
                    // always have a font from which to obtain the key.
                    bool added = entry->AddFont(font);
                    // The key may be nullptr either if this is the first
                    // font, or if the first font does not have a fullname
                    // property, and so the key is obtained from the font.
                    // Set the key in both cases.  The check that AddFont
                    // succeeded is required for the second case.
                    if (!entry->mKey && added) {
                        entry->mKey = fullname;
                    }
                }

                ++v;
            }

            // Fontconfig does not provide a fullname property for all fonts.
            if (v == 0) {
                nsAutoCString name;
                if (!GetFullnameFromFamilyAndStyle(font, &name))
                    continue;

                FontsByFullnameEntry *entry =
                    mFontsByFullname.PutEntry(ToFcChar8(name));
                if (entry) {
                    entry->AddFont(font);
                    // Either entry->mKey has been set for a previous font or it
                    // remains nullptr to indicate that the key is obtained from
                    // the first font.
                }
            }
        }
    }
}

const nsTArray< nsCountedRef<FcPattern> >&
gfxFontconfigUtils::GetFontsForFullname(const FcChar8 *aFullname)
{
    if (mFontsByFullname.Count() == 0) {
        AddFullnameEntries();
    }

    FontsByFullnameEntry *entry = mFontsByFullname.GetEntry(aFullname);

    if (!entry)
        return mEmptyPatternArray;

    return entry->GetFonts();
}

static FcLangResult
CompareLangString(const FcChar8 *aLangA, const FcChar8 *aLangB) {
    FcLangResult result = FcLangDifferentLang;
    for (uint32_t i = 0; ; ++i) {
        FcChar8 a = FcToLower(aLangA[i]);
        FcChar8 b = FcToLower(aLangB[i]);

        if (a != b) {
            if ((a == '\0' && b == '-') || (a == '-' && b == '\0'))
                return FcLangDifferentCountry;

            return result;
        }
        if (a == '\0')
            return FcLangEqual;

        if (a == '-') {
            result = FcLangDifferentCountry;
        }
    }
}

/* static */
FcLangResult
gfxFontconfigUtils::GetLangSupport(FcPattern *aFont, const FcChar8 *aLang)
{
    // When fontconfig builds a pattern for a system font, it will set a
    // single LangSet property value for the font.  That value may be removed
    // and additional string values may be added through FcConfigSubsitute
    // with FcMatchScan.  Values that are neither LangSet nor string are
    // considered errors in fontconfig sort and match functions.
    //
    // If no string nor LangSet value is found, then either the font is a
    // system font and the LangSet has been removed through FcConfigSubsitute,
    // or the font is a web font and its language support is unknown.
    // Returning FcLangDifferentLang for these fonts ensures that this font
    // will not be assumed to satisfy the language, and so language will be
    // prioritized in sorting fallback fonts.
    FcValue value;
    FcLangResult best = FcLangDifferentLang;
    for (int v = 0;
         FcPatternGet(aFont, FC_LANG, v, &value) == FcResultMatch;
         ++v) {

        FcLangResult support;
        switch (value.type) {
            case FcTypeLangSet:
                support = FcLangSetHasLang(value.u.l, aLang);
                break;
            case FcTypeString:
                support = CompareLangString(value.u.s, aLang);
                break;
            default:
                // error. continue to see if there is a useful value.
                continue;
        }

        if (support < best) { // lower is better
            if (support == FcLangEqual)
                return support;
            best = support;
        }        
    }

    return best;
}

gfxFontconfigUtils::LangSupportEntry *
gfxFontconfigUtils::GetLangSupportEntry(const FcChar8 *aLang, bool aWithFonts)
{
    // Currently any unrecognized languages from documents will be converted
    // to x-unicode by nsILanguageAtomService, so there is a limit on the
    // langugages that will be added here.  Reconsider when/if document
    // languages are passed to this routine.

    LangSupportEntry *entry = mLangSupportTable.PutEntry(aLang);
    if (!entry)
        return nullptr;

    FcLangResult best = FcLangDifferentLang;

    if (!entry->IsKeyInitialized()) {
        entry->InitKey(aLang);
    } else {
        // mSupport is already initialized.
        if (!aWithFonts)
            return entry;

        best = entry->mSupport;
        // If there is support for this language, an empty font list indicates
        // that the list hasn't been initialized yet.
        if (best == FcLangDifferentLang || entry->mFonts.Length() > 0)
            return entry;
    }

    // These FcFontSets are owned by fontconfig
    FcFontSet *fontSets[] = {
        FcConfigGetFonts(nullptr, FcSetSystem)
#ifdef MOZ_BUNDLED_FONTS
        , FcConfigGetFonts(nullptr, FcSetApplication)
#endif
    };

    nsAutoTArray<FcPattern*,100> fonts;

    for (unsigned fs = 0; fs < ArrayLength(fontSets); ++fs) {
        FcFontSet *fontSet = fontSets[fs];
        if (!fontSet) {
            continue;
        }
        for (int f = 0; f < fontSet->nfont; ++f) {
            FcPattern *font = fontSet->fonts[f];

            FcLangResult support = GetLangSupport(font, aLang);

            if (support < best) { // lower is better
                best = support;
                if (aWithFonts) {
                    fonts.Clear();
                } else if (best == FcLangEqual) {
                    break;
                }
            }

            // The font list in the LangSupportEntry is expected to be used
            // only when no default fonts support the language.  There would
            // be a large number of fonts in entries for languages using Latin
            // script but these do not need to be created because default
            // fonts already support these languages.
            if (aWithFonts && support != FcLangDifferentLang &&
                support == best) {
                fonts.AppendElement(font);
            }
        }
    }

    entry->mSupport = best;
    if (aWithFonts) {
        if (fonts.Length() != 0) {
            entry->mFonts.AppendElements(fonts.Elements(), fonts.Length());
        } else if (best != FcLangDifferentLang) {
            // Previously there was a font that supported this language at the
            // level of entry->mSupport, but it has now disappeared.  At least
            // entry->mSupport needs to be recalculated, but this is an
            // indication that the set of installed fonts has changed, so
            // update all caches.
            mLastConfig = nullptr; // invalidates caches
            UpdateFontListInternal(true);
            return GetLangSupportEntry(aLang, aWithFonts);
        }
    }

    return entry;
}

FcLangResult
gfxFontconfigUtils::GetBestLangSupport(const FcChar8 *aLang)
{
    UpdateFontListInternal();

    LangSupportEntry *entry = GetLangSupportEntry(aLang, false);
    if (!entry)
        return FcLangEqual;

    return entry->mSupport;
}

const nsTArray< nsCountedRef<FcPattern> >&
gfxFontconfigUtils::GetFontsForLang(const FcChar8 *aLang)
{
    LangSupportEntry *entry = GetLangSupportEntry(aLang, true);
    if (!entry)
        return mEmptyPatternArray;

    return entry->mFonts;
}

#ifdef MOZ_BUNDLED_FONTS

void
gfxFontconfigUtils::ActivateBundledFonts()
{
    if (!mBundledFontsInitialized) {
        mBundledFontsInitialized = true;
        nsCOMPtr<nsIFile> localDir;
        nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir));
        if (NS_FAILED(rv)) {
            return;
        }
        if (NS_FAILED(localDir->Append(NS_LITERAL_STRING("fonts")))) {
            return;
        }
        bool isDir;
        if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) {
            return;
        }
        if (NS_FAILED(localDir->GetNativePath(mBundledFontsPath))) {
            return;
        }
    }
    if (!mBundledFontsPath.IsEmpty()) {
        FcConfigAppFontAddDir(nullptr, (const FcChar8*)mBundledFontsPath.get());
    }
}

#endif
