
/*
 * Copyright 2008 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */


// -----------------------------------------------------------------------------
// This file provides implementations of the font resolution members of
// SkFontHost by using the fontconfig[1] library. Fontconfig is usually found
// on Linux systems and handles configuration, parsing and caching issues
// involved with enumerating and matching fonts.
//
// [1] http://fontconfig.org
// -----------------------------------------------------------------------------

#include <map>
#include <string>

#include <fontconfig/fontconfig.h>

#include "SkFontHost.h"
#include "SkStream.h"

// This is an extern from SkFontHost_FreeType
SkTypeface::Style find_name_and_style(SkStream* stream, SkString* name);

// -----------------------------------------------------------------------------
// The rest of Skia requires that fonts be identified by a unique unsigned id
// and that we be able to load them given the id. What we actually get from
// fontconfig is the filename of the font so we keep a locked map from
// filenames to fileid numbers and back.
//
// Note that there's also a unique id in the SkTypeface. This is unique over
// both filename and style. Thus we encode that id as (fileid << 8) | style.
// Although truetype fonts can support multiple faces in a single file, at the
// moment Skia doesn't.
// -----------------------------------------------------------------------------
static SkMutex global_fc_map_lock;
static std::map<std::string, unsigned> global_fc_map;
static std::map<unsigned, std::string> global_fc_map_inverted;
static std::map<uint32_t, SkTypeface *> global_fc_typefaces;
static unsigned global_fc_map_next_id = 0;

// This is the maximum size of the font cache.
static const unsigned kFontCacheMemoryBudget = 2 * 1024 * 1024;  // 2MB

static unsigned UniqueIdToFileId(unsigned uniqueid)
{
    return uniqueid >> 8;
}

static SkTypeface::Style UniqueIdToStyle(unsigned uniqueid)
{
    return static_cast<SkTypeface::Style>(uniqueid & 0xff);
}

static unsigned FileIdAndStyleToUniqueId(unsigned fileid,
                                         SkTypeface::Style style)
{
    SkASSERT((style & 0xff) == style);
    return (fileid << 8) | static_cast<int>(style);
}

// -----------------------------------------------------------------------------
// Normally we only return exactly the font asked for. In last-resort cases,
// the request is for one of the basic font names "Sans", "Serif" or
// "Monospace". This function tells you whether a given request is for such a
// fallback.
// -----------------------------------------------------------------------------
static bool IsFallbackFontAllowed(const char* request)
{
    return strcmp(request, "Sans") == 0 ||
           strcmp(request, "Serif") == 0 ||
           strcmp(request, "Monospace") == 0;
}

class FontConfigTypeface : public SkTypeface {
public:
    FontConfigTypeface(Style style, uint32_t id)
        : SkTypeface(style, id)
    { }
};

// -----------------------------------------------------------------------------
// Find a matching font where @type (one of FC_*) is equal to @value. For a
// list of types, see http://fontconfig.org/fontconfig-devel/x19.html#AEN27.
// The variable arguments are a list of triples, just like the first three
// arguments, and must be NULL terminated.
//
// For example,
//   FontMatchString(FC_FILE, FcTypeString, "/usr/share/fonts/myfont.ttf",
//                   NULL);
// -----------------------------------------------------------------------------
static FcPattern* FontMatch(const char* type, FcType vtype, const void* value,
                            ...)
{
    va_list ap;
    va_start(ap, value);

    FcPattern* pattern = FcPatternCreate();
    const char* family_requested = NULL;

    for (;;) {
        FcValue fcvalue;
        fcvalue.type = vtype;
        switch (vtype) {
            case FcTypeString:
                fcvalue.u.s = (FcChar8*) value;
                break;
            case FcTypeInteger:
                fcvalue.u.i = (int)(intptr_t)value;
                break;
            default:
                SkASSERT(!"FontMatch unhandled type");
        }
        FcPatternAdd(pattern, type, fcvalue, 0);

        if (vtype == FcTypeString && strcmp(type, FC_FAMILY) == 0)
            family_requested = (const char*) value;

        type = va_arg(ap, const char *);
        if (!type)
            break;
        // FcType is promoted to int when passed through ...
        vtype = static_cast<FcType>(va_arg(ap, int));
        value = va_arg(ap, const void *);
    };
    va_end(ap);

    FcConfigSubstitute(0, pattern, FcMatchPattern);
    FcDefaultSubstitute(pattern);

    // Font matching:
    // CSS often specifies a fallback list of families:
    //    font-family: a, b, c, serif;
    // However, fontconfig will always do its best to find *a* font when asked
    // for something so we need a way to tell if the match which it has found is
    // "good enough" for us. Otherwise, we can return NULL which gets piped up
    // and lets WebKit know to try the next CSS family name. However, fontconfig
    // configs allow substitutions (mapping "Arial -> Helvetica" etc) and we
    // wish to support that.
    //
    // Thus, if a specific family is requested we set @family_requested. Then we
    // record two strings: the family name after config processing and the
    // family name after resolving. If the two are equal, it's a good match.
    //
    // So consider the case where a user has mapped Arial to Helvetica in their
    // config.
    //    requested family: "Arial"
    //    post_config_family: "Helvetica"
    //    post_match_family: "Helvetica"
    //      -> good match
    //
    // and for a missing font:
    //    requested family: "Monaco"
    //    post_config_family: "Monaco"
    //    post_match_family: "Times New Roman"
    //      -> BAD match
    //
    // However, we special-case fallback fonts; see IsFallbackFontAllowed().
    FcChar8* post_config_family;
    FcPatternGetString(pattern, FC_FAMILY, 0, &post_config_family);

    FcResult result;
    FcPattern* match = FcFontMatch(0, pattern, &result);
    if (!match) {
        FcPatternDestroy(pattern);
        return NULL;
    }

    FcChar8* post_match_family;
    FcPatternGetString(match, FC_FAMILY, 0, &post_match_family);
    const bool family_names_match =
        !family_requested ?
        true :
        strcasecmp((char *)post_config_family, (char *)post_match_family) == 0;

    FcPatternDestroy(pattern);

    if (!family_names_match && !IsFallbackFontAllowed(family_requested)) {
        FcPatternDestroy(match);
        return NULL;
    }

    return match;
}

// -----------------------------------------------------------------------------
// Check to see if the filename has already been assigned a fileid and, if so,
// use it. Otherwise, assign one. Return the resulting fileid.
// -----------------------------------------------------------------------------
static unsigned FileIdFromFilename(const char* filename)
{
    SkAutoMutexAcquire ac(global_fc_map_lock);

    std::map<std::string, unsigned>::const_iterator i =
        global_fc_map.find(filename);
    if (i == global_fc_map.end()) {
        const unsigned fileid = global_fc_map_next_id++;
        global_fc_map[filename] = fileid;
        global_fc_map_inverted[fileid] = filename;
        return fileid;
    } else {
        return i->second;
    }
}

// static
SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace,
                                       const char familyName[],
                                       const void* data, size_t bytelength,
                                       SkTypeface::Style style)
{
    const char* resolved_family_name = NULL;
    FcPattern* face_match = NULL;

    {
        SkAutoMutexAcquire ac(global_fc_map_lock);
        FcInit();
    }

    if (familyFace) {
        // Here we use the inverted global id map to find the filename from the
        // SkTypeface object. Given the filename we can ask fontconfig for the
        // familyname of the font.
        SkAutoMutexAcquire ac(global_fc_map_lock);

        const unsigned fileid = UniqueIdToFileId(familyFace->uniqueID());
        std::map<unsigned, std::string>::const_iterator i =
            global_fc_map_inverted.find(fileid);
        if (i == global_fc_map_inverted.end())
            return NULL;

        FcInit();
        face_match = FontMatch(FC_FILE, FcTypeString, i->second.c_str(),
                               NULL);

        if (!face_match)
            return NULL;
        FcChar8* family;
        if (FcPatternGetString(face_match, FC_FAMILY, 0, &family)) {
            FcPatternDestroy(face_match);
            return NULL;
        }
        // At this point, @family is pointing into the @face_match object so we
        // cannot release it yet.

        resolved_family_name = reinterpret_cast<char*>(family);
    } else if (familyName) {
        resolved_family_name = familyName;
    } else {
        return NULL;
    }

    // At this point, we have a resolved_family_name from somewhere
    SkASSERT(resolved_family_name);

    const int bold = style & SkTypeface::kBold ?
                     FC_WEIGHT_BOLD : FC_WEIGHT_NORMAL;
    const int italic = style & SkTypeface::kItalic ?
                       FC_SLANT_ITALIC : FC_SLANT_ROMAN;
    FcPattern* match = FontMatch(FC_FAMILY, FcTypeString, resolved_family_name,
                                 FC_WEIGHT, FcTypeInteger, bold,
                                 FC_SLANT, FcTypeInteger, italic,
                                 NULL);
    if (face_match)
        FcPatternDestroy(face_match);

    if (!match)
        return NULL;

    FcChar8* filename;
    if (FcPatternGetString(match, FC_FILE, 0, &filename) != FcResultMatch) {
        FcPatternDestroy(match);
        return NULL;
    }
    // Now @filename is pointing into @match

    const unsigned fileid = FileIdFromFilename(reinterpret_cast<char*>(filename));
    const unsigned id = FileIdAndStyleToUniqueId(fileid, style);
    SkTypeface* typeface = SkNEW_ARGS(FontConfigTypeface, (style, id));
    FcPatternDestroy(match);

    {
        SkAutoMutexAcquire ac(global_fc_map_lock);
        global_fc_typefaces[id] = typeface;
    }

    return typeface;
}

// static
SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream)
{
    SkASSERT(!"SkFontHost::CreateTypefaceFromStream unimplemented");
    return NULL;
}

// static
SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[])
{
    SkASSERT(!"SkFontHost::CreateTypefaceFromFile unimplemented");
    return NULL;
}

// static
bool SkFontHost::ValidFontID(SkFontID uniqueID) {
    SkAutoMutexAcquire ac(global_fc_map_lock);
    return global_fc_typefaces.find(uniqueID) != global_fc_typefaces.end();
}

// static
SkStream* SkFontHost::OpenStream(uint32_t id)
{
    SkAutoMutexAcquire ac(global_fc_map_lock);
    const unsigned fileid = UniqueIdToFileId(id);

    std::map<unsigned, std::string>::const_iterator i =
        global_fc_map_inverted.find(fileid);
    if (i == global_fc_map_inverted.end())
        return NULL;

    return SkNEW_ARGS(SkFILEStream, (i->second.c_str()));
}

size_t SkFontHost::GetFileName(SkFontID fontID, char path[], size_t length,
                               int32_t* index) {
    SkAutoMutexAcquire ac(global_fc_map_lock);
    const unsigned fileid = UniqueIdToFileId(fontID);

    std::map<unsigned, std::string>::const_iterator i =
    global_fc_map_inverted.find(fileid);
    if (i == global_fc_map_inverted.end()) {
        return 0;
    }

    const std::string& str = i->second;
    if (path) {
        memcpy(path, str.c_str(), SkMin32(str.size(), length));
    }
    if (index) {    // TODO: check if we're in a TTC
        *index = 0;
    }
    return str.size();
}

void SkFontHost::Serialize(const SkTypeface*, SkWStream*) {
    SkASSERT(!"SkFontHost::Serialize unimplemented");
}

SkTypeface* SkFontHost::Deserialize(SkStream* stream) {
    SkASSERT(!"SkFontHost::Deserialize unimplemented");
    return NULL;
}

SkFontID SkFontHost::NextLogicalFont(SkFontID currFontID, SkFontID origFontID) {
    // We don't handle font fallback, WebKit does.
    return 0;
}

///////////////////////////////////////////////////////////////////////////////

size_t SkFontHost::ShouldPurgeFontCache(size_t sizeAllocatedSoFar)
{
    if (sizeAllocatedSoFar > kFontCacheMemoryBudget)
        return sizeAllocatedSoFar - kFontCacheMemoryBudget;
    else
        return 0;   // nothing to do
}
