/***************************************************************************
 *   crengine-ng                                                           *
 *   Copyright (C) 2007-2013,2015 Vadim Lopatin <coolreader.org@gmail.com> *
 *   Copyright (C) 2012 Olexandr Nesterenko <olexn@ukr.net>                *
 *   Copyright (C) 2014 macnuts <macnuts@gmx.com>                          *
 *   Copyright (C) 2014 Huang Xin <chrox.huang@gmail.com>                  *
 *   Copyright (C) 2015 Yifei(Frank) ZHU <fredyifei@gmail.com>             *
 *   Copyright (C) 2020 Konstantin Potapov <pkbo@users.sourceforge.net>    *
 *   Copyright (C) 2017-2021 poire-z <poire-z@users.noreply.github.com>    *
 *   Copyright (C) 2021 ourairquality <info@ourairquality.org>             *
 *   Copyright (C) 2018-2023 Aleksey Chernov <valexlin@gmail.com>          *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License           *
 *   as published by the Free Software Foundation; either version 2        *
 *   of the License, or (at your option) any later version.                *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,            *
 *   MA 02110-1301, USA.                                                   *
 ***************************************************************************/

#include "lvfreetypefontman.h"

#if (USE_FREETYPE == 1)

#include "lvfreetypeface.h"
#if !defined(USE_FT_EMBOLDEN) || (USE_FT_EMBOLDEN == 0)
#include "lvfontboldtransform.h"
#endif
#include <lvcontainer.h>
#include <lvstream.h>
#include <crlog.h>

#include FT_LCD_FILTER_H
#include FT_CONFIG_OPTIONS_H

#include "lvfontdef.h"

#if (USE_FONTCONFIG == 1)
#include <fontconfig/fontconfig.h>
#endif

#include "lvgammacorrection.h"

#if USE_LOCALE_DATA == 1
#include <crlocaledata.h>
#include "../locale_data/fc-lang-data.h"
#endif

inline int myabs(int n) {
    return n < 0 ? -n : n;
}

lString8 familyName(FT_Face face) {
    lString8 faceName(face->family_name);
    if (face->style_name) {
        if (faceName == "Arial" && !strcmp(face->style_name, "Narrow"))
            faceName << " " << face->style_name;
        else if (strstr(face->style_name, "ExtraCondensed"))
            faceName << " "
                     << "ExtraCondensed";
        else if (strstr(face->style_name, "SemiCondensed"))
            faceName << " "
                     << "SemiCondensed";
        else if (strstr(face->style_name, "Condensed"))
            faceName << " "
                     << "Condensed";
    }
    return faceName;
}

int getFontWeight(FT_Face face) {
    if (!face)
        return -1;
    int weight = -1;
    bool bold_flag = (face->style_flags & FT_STYLE_FLAG_BOLD) != 0;
    lString32 style32(face->style_name);
    style32 = style32.lowercase();
    if (style32.pos("extrablack") >= 0 || style32.pos("ultrablack") >= 0 || style32.pos("extra black") >= 0 || style32.pos("ultra black") >= 0)
        weight = 950;
    else if (style32.pos("extrabold") >= 0 || style32.pos("ultrabold") >= 0 || style32.pos("extra bold") >= 0 || style32.pos("ultra bold") >= 0)
        weight = 800;
    else if (style32.pos("demibold") >= 0 || style32.pos("semibold") >= 0 || style32.pos("demi bold") >= 0 || style32.pos("semi bold") >= 0)
        weight = 600;
    else if (style32.pos("extralight") >= 0 || style32.pos("ultralight") >= 0 || style32.pos("extra light") >= 0 || style32.pos("ultra light") >= 0)
        weight = 200;
    else if (style32.pos("demilight") >= 0 || style32.pos("light") >= 0 || style32.pos("demi light") >= 0)
        weight = 300;
    else if (style32.pos("regular") >= 0 || style32.pos("normal") >= 0 || style32.pos("book") >= 0 || style32.pos("text") >= 0)
        weight = 400;
    else if (style32.pos("thin") >= 0)
        weight = 100;
    else if (style32.pos("medium") >= 0)
        weight = 500;
    else if (style32.pos("bold") >= 0)
        weight = 700;
    else if (style32.pos("black") >= 0 || style32.pos("heavy") >= 0)
        weight = 900;

    if (-1 == weight)
        weight = bold_flag ? 700 : 400;
    else if (weight <= 400 && bold_flag)
        weight = 700;
    return weight;
}

#if USE_LOCALE_DATA == 1
LVHashTable<lString8, font_lang_compat>* getSupportedLangs(FT_Face face) {
    LVHashTable<lString8, font_lang_compat>* langs = new LVHashTable<lString8, font_lang_compat>(get_fc_lang_data_size());
#define FC_LANG_START_INTERVAL_CODE 0xF0F0FFFF
    bool fullSupport;
    bool partialSupport;
    unsigned int codePoint = 0;
    unsigned int tmp;
    unsigned int first, second;
    bool inRange;
    const struct fc_lang_rec* rec = get_fc_lang_data();
    for (unsigned int l = 0; l < get_fc_lang_data_size(); l++) {
        CRLocaleData loc(rec->lang_code);
        if (loc.isValid()) {
            // Check compatibility
            codePoint = 0;
            second = 0;
            inRange = false;
            FT_UInt glyphIndex;
            partialSupport = false;
            fullSupport = true;
            for (unsigned int i = 0;;) {
                // get next codePoint
                if (inRange && codePoint < second) {
                    codePoint++;
                } else {
                    if (i >= rec->char_set_sz)
                        break;
                    tmp = rec->char_set[i];
                    if (FC_LANG_START_INTERVAL_CODE == tmp) { // code of start interval
                        if (i + 2 < rec->char_set_sz) {
                            i++;
                            first = rec->char_set[i];
                            i++;
                            second = rec->char_set[i];
                            inRange = true;
                            codePoint = first;
                            i++;
                        } else {
                            // broken language char set
                            fullSupport = false;
                            break;
                        }
                    } else {
                        codePoint = tmp;
                        inRange = false;
                        i++;
                    }
                }
                // check codePoint in this font
                glyphIndex = FT_Get_Char_Index(face, codePoint);
                if (0 == glyphIndex) {
                    fullSupport = false;
                } else {
                    partialSupport = true;
                }
            }
            if (fullSupport)
                langs->set(loc.langTag(), font_lang_compat_full);
            else if (partialSupport)
                langs->set(loc.langTag(), font_lang_compat_partial);
            else
                langs->set(loc.langTag(), font_lang_compat_none);
        } else {
            CRLog::warn("getSupportedLangs(): invalid lang tag: %s", rec->lang_code);
        }
        // to next language
        rec++;
    }
    langs->compact();
    return langs;
}
#endif

lUInt32 LVFreeTypeFontManager::GetFontListHash(int documentId) {
    FONT_MAN_GUARD
    return _cache.GetFontListHash(documentId) * 75 + _fallbackFontFaces.getHash();
}

bool LVFreeTypeFontManager::SetFallbackFontFaces(lString8 facesStr) {
    FONT_MAN_GUARD
    lString8Collection faces = lString8Collection(facesStr, lString8(";"));
    int count = 0;
    if (faces != _fallbackFontFaces) {
        _fallbackFontFaces.clear();
        for (int i = 0; i < faces.length(); i++) {
            lString8 face = faces[i];
            face.trim();
            CRLog::trace("Looking for fallback font %s", face.c_str());
            LVFontCacheItem* item = _cache.findFallback(face, -1);
            if (item) {
                bool exist = false;
                for (int j = 0; j < _fallbackFontFaces.length(); j++)
                    if (_fallbackFontFaces[j] == face) {
                        exist = true;
                        break;
                    }
                if (!exist) {
                    _fallbackFontFaces.add(face);
                    count++;
                    if (8 * sizeof(lUInt32) == count) {
                        CRLog::warn("Too many fallback fonts specified, skipping.");
                        break;
                    }
                }
            }
        }
        _cache.clearFallbackFonts();
        // Somehow, with Fedra Serif (only!), changing the fallback font does
        // not prevent glyphs from previous fallback font to be re-used...
        // So let's clear glyphs caches too.
        gc();
        clearGlyphCache();
    }
    return count > 0;
}

lString8 LVFreeTypeFontManager::GetFallbackFontFace(int index) {
    if (index >= 0 && index < _fallbackFontFaces.length())
        return _fallbackFontFaces[index];
    return lString8::empty_str;
}

lString8 LVFreeTypeFontManager::GetFallbackFontFaces() {
    lString8 faces;
    if (_fallbackFontFaces.length() > 0) {
        for (int i = 0; i < _fallbackFontFaces.length() - 1; i++) {
            faces.append(_fallbackFontFaces[i]);
            faces.append("; ");
        }
        faces.append(_fallbackFontFaces[_fallbackFontFaces.length() - 1]);
    }
    return faces;
}

int LVFreeTypeFontManager::GetFallbackFontCount() {
    return _fallbackFontFaces.length();
}

LVFontRef LVFreeTypeFontManager::GetFallbackFont(int size, int index) {
    FONT_MAN_GUARD
    if (_fallbackFontFaces.empty())
        return LVFontRef();
    if (index < 0 || index >= _fallbackFontFaces.length())
        return LVFontRef();
    LVFontCacheItem* item = _cache.findFallback(_fallbackFontFaces[index], size);
    lUInt32 fallbackMask = 1 << index;
    if (!item->getFont().isNull()) {
        item->getFont()->setFallbackMask(fallbackMask);
        return item->getFont();
    }
    LVFontRef fontRef = GetFont(size, 400, false, css_ff_sans_serif, _fallbackFontFaces[index], 0, -1, false);
    if (!fontRef.isNull())
        fontRef->setFallbackMask(fallbackMask);
    return fontRef;
}

LVFontRef LVFreeTypeFontManager::GetFallbackFont(int size, int weight, bool italic, int index) {
    FONT_MAN_GUARD
    if (_fallbackFontFaces.empty())
        return LVFontRef();
    if (index < 0 || index >= _fallbackFontFaces.length())
        return LVFontRef();
    // We don't use/extend findFallback(), which was made to work
    // assuming the fallback font is a standalone regular font
    // without any bold/italic sibling.
    // GetFont() works just as fine when we need specified weigh and italic.
    LVFontRef fontRef = GetFont(size, weight, italic, css_ff_sans_serif, _fallbackFontFaces[index], 0, -1, false);
    if (!fontRef.isNull())
        fontRef->setFallbackMask(1 << index);
    return fontRef;
}

bool LVFreeTypeFontManager::isBitmapModeForSize(int size) {
    bool isBitmap = false;
    switch (_antialiasMode) {
        case font_aa_none:
            isBitmap = true;
            break;
        case font_aa_big:
            isBitmap = size < 20 ? true : false;
            break;
        case font_aa_all:
        default:
            isBitmap = false;
            break;
    }
    return isBitmap;
}

void LVFreeTypeFontManager::SetAntialiasMode(font_antialiasing_t mode) {
    _antialiasMode = mode;
    int error;
#ifdef FT_CONFIG_OPTION_SUBPIXEL_RENDERING
    // ClearType-style LCD rendering
    switch (mode) {
        case font_aa_lcd_rgb:
        case font_aa_lcd_bgr:
        case font_aa_lcd_v_rgb:
        case font_aa_lcd_v_bgr:
            error = FT_Library_SetLcdFilter(_library, FT_LCD_FILTER_DEFAULT);
            if (FT_Err_Ok != error)
                CRLog::debug("FT_Library_SetLcdFilter() failed, error=%d", error);
            break;
        case font_aa_lcd_pentile:
        case font_aa_lcd_pentile_m:
        case font_aa_lcd_v_pentile:
        case font_aa_lcd_v_pentile_m:
            CRLog::warn("Pentile unavailable with ClearType-style LCD rendering");
            CRLog::warn("Recompile FreeType without FT_CONFIG_OPTION_SUBPIXEL_RENDERING");
            break;
        default:
            error = FT_Library_SetLcdFilter(_library, FT_LCD_FILTER_NONE);
            if (FT_Err_Ok != error)
                CRLog::debug("FT_Library_SetLcdFilter() failed, error=%d", error);
            break;
    }
#else
    // Harmony LCD rendering
    switch (mode) {
        case font_aa_lcd_rgb:
        case font_aa_lcd_v_rgb: {
            // {{-⅓, 0}, {0, 0}, {⅓, 0}}
            FT_Vector sub[3] = { { -21, 0 }, { 0, 0 }, { 21, 0 } };
            error = FT_Library_SetLcdGeometry(_library, sub);
            if (FT_Err_Ok != error)
                CRLog::debug("FT_Library_SetLcdGeometry() failed, error=%d", error);
            break;
        }
        case font_aa_lcd_bgr:
        case font_aa_lcd_v_bgr: {
            // {{⅓, 0}, {0, 0}, {-⅓, 0}}
            FT_Vector sub[3] = { { 21, 0 }, { 0, 0 }, { -21, 0 } };
            error = FT_Library_SetLcdGeometry(_library, sub);
            if (FT_Err_Ok != error)
                CRLog::debug("FT_Library_SetLcdGeometry() failed, error=%d", error);
            break;
        }
        case font_aa_lcd_pentile: {
            // {{-⅙, ¼}, {-⅙, -¼}, {⅓, 0}}
            FT_Vector sub[3] = { { -11, 16 }, { -11, -16 }, { 22, 0 } };
            error = FT_Library_SetLcdGeometry(_library, sub);
            if (FT_Err_Ok != error)
                CRLog::debug("FT_Library_SetLcdGeometry() failed, error=%d", error);
            break;
        }
        case font_aa_lcd_pentile_m: {
            // TODO: test & fix this
            FT_Vector sub[3] = { { -22, 0 }, { 11, 16 }, { 11, -16 } };
            error = FT_Library_SetLcdGeometry(_library, sub);
            if (FT_Err_Ok != error)
                CRLog::debug("FT_Library_SetLcdGeometry() failed, error=%d", error);
            break;
        }
        case font_aa_lcd_v_pentile: {
            // TODO: test & fix this
            FT_Vector sub[3] = { { 16, -11 }, { -16, -11 }, { 0, 22 } };
            error = FT_Library_SetLcdGeometry(_library, sub);
            if (FT_Err_Ok != error)
                CRLog::debug("FT_Library_SetLcdGeometry() failed, error=%d", error);
            break;
        }
        case font_aa_lcd_v_pentile_m: {
            // TODO: test & fix this
            FT_Vector sub[3] = { { 22, 0 }, { -11, -16 }, { -11, 16 } };
            error = FT_Library_SetLcdGeometry(_library, sub);
            if (FT_Err_Ok != error)
                CRLog::debug("FT_Library_SetLcdGeometry() failed, error=%d", error);
            break;
        }
        default: {
            FT_Vector sub[3] = { { 0, 0 },
                                 { 0, 0 },
                                 { 0, 0 } };
            error = FT_Library_SetLcdGeometry(_library, sub);
            if (FT_Err_Ok != error)
                CRLog::debug("FT_Library_SetLcdGeometry() failed, error=%d", error);
        }
    }
#endif
    gc();
    clearGlyphCache();
    FONT_MAN_GUARD
    LVPtrVector<LVFontCacheItem>* fonts = _cache.getInstances();
    for (int i = 0; i < fonts->length(); i++) {
        LVFontRef font = fonts->get(i)->getFont();
        font->SetAntialiasMode(_antialiasMode);
        font->setBitmapMode(isBitmapModeForSize(font->getHeight()));
    }
}

float LVFreeTypeFontManager::GetGamma() {
    return LVFontManager::GetGamma();
}

void LVFreeTypeFontManager::SetGamma(double gamma) {
    FONT_MAN_GUARD
    int index = LVGammaCorrection::getIndex(gamma);
    if (_gammaIndex != index) {
        CRLog::debug("Gamma correction index is changed from %d to %d", _gammaIndex, index);
        _gammaIndex = index;
        gc();
        clearGlyphCache();
        LVPtrVector<LVFontCacheItem>* fonts = _cache.getInstances();
        for (int i = 0; i < fonts->length(); i++) {
            fonts->get(i)->getFont()->setGammaIndex(_gammaIndex);
        }
    }
}

void LVFreeTypeFontManager::SetHintingMode(hinting_mode_t mode) {
    FONT_MAN_GUARD
    CRLog::debug("Hinting mode is changed: %d", (int)mode);
    _hintingMode = mode;
    gc();
    clearGlyphCache();
    LVPtrVector<LVFontCacheItem>* fonts = _cache.getInstances();
    for (int i = 0; i < fonts->length(); i++) {
        fonts->get(i)->getFont()->setHintingMode(mode);
    }
}

void LVFreeTypeFontManager::SetKerning(bool kerningEnabled) {
    FONT_MAN_GUARD
    CRLog::debug("Kerning mode is changed: %d", (int)kerningEnabled);
    _allowKerning = kerningEnabled;
    gc();
    clearGlyphCache();
    LVPtrVector<LVFontCacheItem>* fonts = _cache.getInstances();
    for (int i = 0; i < fonts->length(); i++) {
        fonts->get(i)->getFont()->setKerning(kerningEnabled);
    }
}

void LVFreeTypeFontManager::SetShapingMode(shaping_mode_t mode) {
    FONT_MAN_GUARD
    CRLog::debug("Shaping mode is changed: %d", (int)mode);
    _shapingMode = mode;
    gc();
    clearGlyphCache();
    LVPtrVector<LVFontCacheItem>* fonts = _cache.getInstances();
    for (int i = 0; i < fonts->length(); i++) {
        fonts->get(i)->getFont()->setShapingMode(mode);
    }
}

void LVFreeTypeFontManager::clearGlyphCache() {
    FONT_MAN_GUARD
    _globalCache.clear();
#if USE_HARFBUZZ == 1
    // needs to clear each font _glyph_cache2 (for Gamma change, which
    // does not call any individual font method)
    LVPtrVector<LVFontCacheItem>* fonts = _cache.getInstances();
    for (int i = 0; i < fonts->length(); i++) {
        fonts->get(i)->getFont()->clearCache();
    }
#endif
}

bool LVFreeTypeFontManager::initSystemFonts() {
#if (DEBUG_FONT_SYNTHESIS == 1)
    fontMan->RegisterFont(lString8("/usr/share/fonts/liberation/LiberationSans-Regular.ttf"));
    CRLog::debug("fonts:");
    LVFontRef fnt4 = dumpFontRef(fontMan->GetFont(24, 200, true, css_ff_sans_serif, cs8("Arial, Helvetica")));
    LVFontRef fnt1 = dumpFontRef(fontMan->GetFont(18, 200, false, css_ff_sans_serif, cs8("Arial, Helvetica")));
    LVFontRef fnt2 = dumpFontRef(fontMan->GetFont(20, 400, false, css_ff_sans_serif, cs8("Arial, Helvetica")));
    LVFontRef fnt3 = dumpFontRef(fontMan->GetFont(22, 600, false, css_ff_sans_serif, cs8("Arial, Helvetica")));
    LVFontRef fnt5 = dumpFontRef(fontMan->GetFont(26, 400, true, css_ff_sans_serif, cs8("Arial, Helvetica")));
    LVFontRef fnt6 = dumpFontRef(fontMan->GetFont(28, 600, true, css_ff_sans_serif, cs8("Arial, Helvetica")));
    CRLog::debug("end of font testing");
#elif (USE_FONTCONFIG == 1)
    {
        CRLog::info("Reading list of system fonts using FONTCONFIG");
        lString32Collection fonts;

        int facesFound = 0;

        FcFontSet* fontset;

        FcObjectSet* os = FcObjectSetBuild(FC_FILE, FC_WEIGHT, FC_FAMILY,
                                           FC_SLANT, FC_SPACING, FC_INDEX,
                                           FC_STYLE, FC_SCALABLE,
#ifdef FC_COLOR
                                           FC_COLOR,
#endif
                                           NULL);
        FcPattern* pat = FcPatternCreate();
        //FcBool b = 1;
        FcPatternAddBool(pat, FC_SCALABLE, 1);

        fontset = FcFontList(NULL, pat, os);

        FcPatternDestroy(pat);
        FcObjectSetDestroy(os);

        // load fonts from file
        CRLog::debug("FONTCONFIG: %d font files found", fontset->nfont);
        for (int i = 0; i < fontset->nfont; i++) {
            FcChar8* s = (FcChar8*)"";
            FcChar8* family = (FcChar8*)"";
            FcChar8* style = (FcChar8*)"";
            //FcBool b;
            FcResult res;

            //FC_SCALABLE
            //res = FcPatternGetBool( fontset->fonts[i], FC_OUTLINE, 0, (FcBool*)&b);
            //if(res != FcResultMatch)
            //    continue;
            //if ( !b )
            //    continue; // skip non-scalable fonts
            res = FcPatternGetString(fontset->fonts[i], FC_FILE, 0, (FcChar8**)&s);
            if (res != FcResultMatch) {
                continue;
            }
            lString8 fn((const char*)s);
            lString32 fn32(fn.c_str());
            fn32.lowercase();
            if (!fn32.endsWith(".ttf") && !fn32.endsWith(".odf") && !fn32.endsWith(".otf") && !fn32.endsWith(".pfb") && !fn32.endsWith(".pfa") && !fn32.endsWith(".ttc")) {
                continue;
            }
            int weight = FC_WEIGHT_REGULAR;
            res = FcPatternGetInteger(fontset->fonts[i], FC_WEIGHT, 0, &weight);
            if (res != FcResultMatch) {
                CRLog::debug("no FC_WEIGHT for %s", s);
                //continue;
            }
            switch (weight) {
                case FC_WEIGHT_THIN: //    0
                    weight = 100;
                    break;
                case FC_WEIGHT_EXTRALIGHT: //    40
                case 45:                   // TODO: find what is it...
                    //case FC_WEIGHT_ULTRALIGHT        FC_WEIGHT_EXTRALIGHT
                    weight = 200;
                    break;
                case FC_WEIGHT_LIGHT:     //    50
#ifdef FC_WEIGHT_DEMILIGHT
                case FC_WEIGHT_DEMILIGHT: //    55
#endif
                    weight = 300;
                    break;
                case FC_WEIGHT_BOOK:    //    75
                case FC_WEIGHT_REGULAR: //    80
                    //case FC_WEIGHT_NORMAL:            FC_WEIGHT_REGULAR
                    weight = 400;
                    break;
                case FC_WEIGHT_MEDIUM: //    100
                    weight = 500;
                    break;
                case FC_WEIGHT_DEMIBOLD: //    180
                    //case FC_WEIGHT_SEMIBOLD:          FC_WEIGHT_DEMIBOLD
                    weight = 600;
                    break;
                case FC_WEIGHT_BOLD: //    200
                    weight = 700;
                    break;
                case FC_WEIGHT_EXTRABOLD: //    205
                    //case FC_WEIGHT_ULTRABOLD:         FC_WEIGHT_EXTRABOLD
                    weight = 800;
                    break;
                case FC_WEIGHT_BLACK: //    210
                    //case FC_WEIGHT_HEAVY:             FC_WEIGHT_BLACK
                    weight = 900;
                    break;
#ifdef FC_WEIGHT_EXTRABLACK
                case FC_WEIGHT_EXTRABLACK: //    215
                    //case FC_WEIGHT_ULTRABLACK:        FC_WEIGHT_EXTRABLACK
                    weight = 950;
                    break;
#endif
                default:
                    weight = 400;
                    break;
            }
            FcBool scalable = FcFalse;
            res = FcPatternGetBool(fontset->fonts[i], FC_SCALABLE, 0, &scalable);
            if (res != FcResultMatch) {
                CRLog::debug("no FC_SCALABLE for %s", s);
            }
            int index = 0;
            res = FcPatternGetInteger(fontset->fonts[i], FC_INDEX, 0, &index);
            if (res != FcResultMatch) {
                CRLog::debug("no FC_INDEX for %s", s);
                //continue;
            }
            res = FcPatternGetString(fontset->fonts[i], FC_FAMILY, 0, (FcChar8**)&family);
            if (res != FcResultMatch) {
                CRLog::debug("no FC_FAMILY for %s", s);
                continue;
            }
            res = FcPatternGetString(fontset->fonts[i], FC_STYLE, 0, (FcChar8**)&style);
            if (res != FcResultMatch) {
                CRLog::debug("no FC_STYLE for %s", s);
                style = (FcChar8*)"";
                //continue;
            }
            int slant = FC_SLANT_ROMAN;
            res = FcPatternGetInteger(fontset->fonts[i], FC_SLANT, 0, &slant);
            if (res != FcResultMatch) {
                CRLog::debug("no FC_SLANT for %s", s);
                //continue;
            }
            int spacing = 0;
            res = FcPatternGetInteger(fontset->fonts[i], FC_SPACING, 0, &spacing);
            if (res != FcResultMatch) {
                //CRLog::debug("no FC_SPACING for %s", s);
                //continue;
            }
            //                int cr_weight;
            //                switch(weight) {
            //                    case FC_WEIGHT_LIGHT: cr_weight = 200; break;
            //                    case FC_WEIGHT_MEDIUM: cr_weight = 300; break;
            //                    case FC_WEIGHT_DEMIBOLD: cr_weight = 500; break;
            //                    case FC_WEIGHT_BOLD: cr_weight = 700; break;
            //                    case FC_WEIGHT_BLACK: cr_weight = 800; break;
            //                    default: cr_weight=300; break;
            //                }
            css_font_family_t fontFamily = css_ff_sans_serif;
            lString32 face32((const char*)family);
            face32.lowercase();
            if (spacing == FC_MONO)
                fontFamily = css_ff_monospace;
            else if (face32.pos("sans") >= 0)
                fontFamily = css_ff_sans_serif;
            else if (face32.pos("serif") >= 0)
                fontFamily = css_ff_serif;

            //css_ff_inherit,
            //css_ff_serif,
            //css_ff_sans_serif,
            //css_ff_cursive,
            //css_ff_fantasy,
            //css_ff_monospace,
            bool italic = (slant != FC_SLANT_ROMAN);

            lString8 face((const char*)family);
            lString8 style8((const char*)style);
            style8.lowercase();
            if (style8.pos("extracondensed") >= 0)
                face << " ExtraCondensed";
            else if (style8.pos("semicondensed") >= 0)
                face << " SemiCondensed";
            else if (style8.pos("condensed") >= 0)
                face << " Condensed";

            LVFontDef def(
                    lString8((const char*)s),
                    -1, // height==-1 for scalable fonts
                    weight,
                    italic,
                    -1, // OpenType features = -1 for not yet instantiated fonts
                    fontFamily,
                    face,
                    index);
            CRLog::debug("FONTCONFIG: Font family:%s style:%s weight:%d slant:%d spacing:%d file:%s", family, style, weight, slant, spacing, s);
            if (_cache.findDuplicate(&def)) {
                CRLog::debug("is duplicate, skipping");
                continue;
            }
            _cache.update(&def, LVFontRef(NULL));

            if (scalable != FcFalse && !def.getItalic()) {
                LVFontDef newDef(def);
                newDef.setItalic(2); // can italicize
                if (!_cache.findDuplicate(&newDef))
                    _cache.update(&newDef, LVFontRef(NULL));
            }

            facesFound++;
        }

        FcFontSetDestroy(fontset);
        CRLog::info("FONTCONFIG: %d fonts registered", facesFound);

        const char* fallback_faces = "Arial Unicode MS; AR PL ShanHeiSun Uni; Liberation Sans; Roboto; DejaVu Sans; Noto Sans; Droid Sans";
        SetFallbackFontFaces(lString8(fallback_faces));

        return facesFound > 0;
    }
#elif (CR3_OSX == 1)

    int facesFound = 0;
    facesFound += fontMan->RegisterFont(lString8("/Library/Fonts/Arial.ttf"));
    facesFound += fontMan->RegisterFont(lString8("/Library/Fonts/Arial Bold.ttf"));
    facesFound += fontMan->RegisterFont(lString8("/Library/Fonts/Arial Italic.ttf"));
    facesFound += fontMan->RegisterFont(lString8("/Library/Fonts/Arial Bold Italic.ttf"));
    facesFound += fontMan->RegisterFont(lString8("/Library/Fonts/Arial Unicode.ttf"));
    facesFound += fontMan->RegisterFont(lString8("/Library/Fonts/Arial Narrow.ttf"));
    facesFound += fontMan->RegisterFont(lString8("/Library/Fonts/Arial Narrow Bold.ttf"));
    facesFound += fontMan->RegisterFont(lString8("/Library/Fonts/Arial Narrow Italic.ttf"));
    facesFound += fontMan->RegisterFont(lString8("/Library/Fonts/Courier New.ttf"));
    facesFound += fontMan->RegisterFont(lString8("/Library/Fonts/Courier New Bold.ttf"));
    facesFound += fontMan->RegisterFont(lString8("/Library/Fonts/Courier New Italic.ttf"));
    facesFound += fontMan->RegisterFont(lString8("/Library/Fonts/Courier New Bold Italic.ttf"));
    facesFound += fontMan->RegisterFont(lString8("/Library/Fonts/Georgia.ttf"));
    facesFound += fontMan->RegisterFont(lString8("/Library/Fonts/Georgia Bold.ttf"));
    facesFound += fontMan->RegisterFont(lString8("/Library/Fonts/Georgia Italic.ttf"));
    facesFound += fontMan->RegisterFont(lString8("/Library/Fonts/Georgia Bold Italic.ttf"));

    return facesFound > 0;

#else
    return false;
#endif
}

LVFreeTypeFontManager::~LVFreeTypeFontManager() {
    FONT_MAN_GUARD
    LVHashTable<lString8, LVHashTable<lString8, font_lang_compat>*>::iterator it = _supportedLangs.forwardIterator();
    LVHashTable<lString8, LVHashTable<lString8, font_lang_compat>*>::pair* p;
    while ((p = it.next()) != NULL) {
        LVHashTable<lString8, font_lang_compat>* langTable = p->value;
        if (langTable)
            delete langTable;
    }
    _supportedLangs.clear();
    _globalCache.clear();
    _cache.clear();
    if (_library)
        FT_Done_FreeType(_library);
#if (DEBUG_FONT_MAN == 1)
    if (_log) {
        fclose(_log);
    }
#endif
}

LVFreeTypeFontManager::LVFreeTypeFontManager()
        : _library(NULL)
        , _globalCache(GLYPH_CACHE_SIZE)
        , _supportedLangs(16) {
    FONT_MAN_GUARD
    int error = FT_Init_FreeType(&_library);
    if (error) {
        // error
        CRLog::error("Error while initializing freetype library");
    }
#if (DEBUG_FONT_MAN == 1)
    _log = fopen(DEBUG_FONT_MAN_LOG_FILE, "at");
    if (_log) {
        fprintf(_log, "=========================== LOGGING STARTED ===================\n");
    }
#endif
    // _requiredChars = U"azAZ09";//\x0410\x042F\x0430\x044F";
    // Some fonts come without any of these (ie. NotoSansMyanmar.ttf), there's
    // no reason to prevent them from being used.
    // So, check only for the presence of the space char, hoping it's there in any font.
    _requiredChars = U" ";
}

void LVFreeTypeFontManager::gc() // garbage collector
{
    FONT_MAN_GUARD
    _cache.gc();
}

lString8 LVFreeTypeFontManager::makeFontFileName(lString8 name) {
    lString8 filename = _path;
    if (!filename.empty() && _path[_path.length() - 1] != PATH_SEPARATOR_CHAR)
        filename << PATH_SEPARATOR_CHAR;
    filename << name;
    return filename;
}

void LVFreeTypeFontManager::getFaceList(lString32Collection& list) {
    FONT_MAN_GUARD
    _cache.getFaceList(list);
}

void LVFreeTypeFontManager::getFaceListFiltered(lString32Collection& list, css_font_family_t family, const lString8& langTag) {
    FONT_MAN_GUARD
#if USE_LOCALE_DATA == 1
    if (langTag.empty()) {
        _cache.getFaceListForFamily(list, family);
    } else {
        list.clear();
        CRLocaleData loc(langTag);
        if (loc.isValid()) {
            lString32Collection tmpList;
            _cache.getFaceListForFamily(tmpList, family);
            for (int i = 0; i < tmpList.length(); i++) {
                if (font_lang_compat_full == checkFontLangCompat(UnicodeToUtf8(tmpList[i]), langTag))
                    list.add(tmpList[i]);
            }
        }
    }
#else
    _cache.getFaceListForFamily(list, family);
#endif
}

void LVFreeTypeFontManager::getFontFileNameList(lString32Collection& list) {
    FONT_MAN_GUARD
    _cache.getFontFileNameList(list);
}

bool LVFreeTypeFontManager::SetAlias(lString8 alias, lString8 facename, int id, bool bold, bool italic) {
    FONT_MAN_GUARD
    lString8 fontname = lString8("\0");
    LVFontDef def(
            fontname,
            -1,
            bold ? 700 : 400,
            italic,
            -1, // OpenType features = -1 for not yet instantiated fonts
            css_ff_inherit,
            facename,
            -1,
            id);
    LVFontCacheItem* item = _cache.find(&def);
    LVFontDef def1(
            fontname,
            -1,
            bold ? 700 : 400,
            italic,
            -1, // OpenType features = -1 for not yet instantiated fonts
            css_ff_inherit,
            alias,
            -1,
            id);

    int index = 0;

    FT_Face face = NULL;

    // for all faces in file
    for (;; index++) {
        int error = FT_New_Face(_library, item->getDef()->getName().c_str(), index, &face); /* create face object */
        if (error) {
            if (index == 0) {
                CRLog::error("FT_New_Face returned error %d", error);
            }
            break;
        }
        int num_faces = face->num_faces;

        css_font_family_t fontFamily = css_ff_sans_serif;
        if (face->face_flags & FT_FACE_FLAG_FIXED_WIDTH)
            fontFamily = css_ff_monospace;
        //lString8 familyName(!facename.empty() ? facename : ::familyName(face));
        // We don't need this here and in other places below: all fonts (except
        // monospaces) will be marked as sans-serif, and elements with
        // style {font-family:serif;} will use the default font too.
        // (we don't ship any Times, and someone having unluckily such
        // a font among his would see it used for {font-family:serif;}
        // elements instead of his default font)
        /*
        if ( familyName=="Times" || familyName=="Times New Roman" )
            fontFamily = css_ff_serif;
        */

        int weight = !facename.empty() ? (bold ? 700 : 400) : getFontWeight(face);
        bool italicFlag = !facename.empty() ? italic : (face->style_flags & FT_STYLE_FLAG_ITALIC) != 0;

        LVFontDef def2(
                item->getDef()->getName(),
                -1, // height==-1 for scalable fonts
                weight,
                italicFlag,
                -1, // OpenType features = -1 for not yet instantiated fonts
                fontFamily,
                alias,
                index,
                id);

        if (face) {
            FT_Done_Face(face);
            face = NULL;
        }

        if (_cache.findDuplicate(&def2)) {
            CRLog::trace("font definition is duplicate");
            return false;
        }
        _cache.update(&def2, LVFontRef(NULL));
        if (!def.getItalic()) {
            LVFontDef newDef(def2);
            newDef.setItalic(2); // can italicize
            if (!_cache.findDuplicate(&newDef))
                _cache.update(&newDef, LVFontRef(NULL));
        }
        if (index >= num_faces - 1)
            break;
    }
    item = _cache.find(&def1);
    if (item->getDef()->getTypeFace() == alias) {
        return true;
    } else {
        return false;
    }
}

LVFontRef LVFreeTypeFontManager::GetFont(int size, int weight, bool italic, css_font_family_t family, lString8 typeface,
                                         int features, int documentId, bool useBias) {
    FONT_MAN_GUARD
#if (DEBUG_FONT_MAN == 1)
    if (_log) {
        fprintf(_log, "GetFont(size=%d, weight=%d, italic=%d, family=%d, typeface='%s')\n",
                size, weight, italic ? 1 : 0, (int)family, typeface.c_str());
    }
#endif
    lString8 fontname;
    LVFontDef def(
            fontname,
            size,
            weight,
            italic,
            features,
            family,
            typeface,
            -1,
            documentId);
#if (DEBUG_FONT_MAN == 1)
    if (_log)
        fprintf(_log, "GetFont: %s %d %s %s\n",
                typeface.c_str(),
                size,
                weight > 400 ? "bold" : "",
                italic ? "italic" : "");
#endif
    LVFontCacheItem* item = _cache.find(&def, useBias);
#if (DEBUG_FONT_MAN == 1)
    if (item && _log) { //_log &&
        fprintf(_log, "   found item: (file=%s[%d], size=%d, weight=%d, italic=%d, family=%d, typeface=%s, weightDelta=%d) FontRef=%d\n",
                item->getDef()->getName().c_str(), item->getDef()->getIndex(), item->getDef()->getSize(), item->getDef()->getWeight(), item->getDef()->getItalic() ? 1 : 0,
                (int)item->getDef()->getFamily(), item->getDef()->getTypeFace().c_str(),
                weight - item->getDef()->getWeight(), item->getFont().isNull() ? 0 : item->getFont()->getHeight());
    }
#endif
    bool italicize = false;

    if (NULL == item) {
        CRLog::error("_cache.find() return NULL: size=%d, weight=%d, italic=%d, family=%d, typeface=%s", size, weight, italic, family, typeface.c_str());
        CRLog::error("possible font cache cleared!");
        return LVFontRef(NULL);
    }

    LVFontDef newDef(*item->getDef());

    if (!item->getFont().isNull()) {
        if (item->getDef()->getFeatures() != features) {
            // Be sure we ignore any instantiated font found in cache that
            // has features different than the ones requested.
        } else {
#if (USE_FT_EMBOLDEN == 1)
            int deltaWeight = myabs(weight - item->getDef()->getWeight());
            if (deltaWeight >= 25) {
                // This instantiated cached font has a too different weight
                // when USE_FT_EMBOLDEN, ignore this other-weight cached font instance
                // and go loading from the font file again to apply embolden.
            }
#else
            int deltaWeight = weight - item->getDef()->getWeight();
            if (deltaWeight >= 200) {
                // This instantiated cached font has a too low weight
                // embolden using LVFontBoldTransform
                CRLog::debug("font: apply Embolding to increase weight from %d to %d",
                             newDef.getWeight(), newDef.getWeight() + 200);
                newDef.setWeight(newDef.getWeight() + 200);
                LVFontRef ref = LVFontRef(new LVFontBoldTransform(item->getFont(), &_globalCache));
                _cache.update(&newDef, ref);
                return ref;
            }
#endif
            else {
                //fprintf(_log, "    : fount existing\n");
                return item->getFont();
            }
        }
    }
    lString8 fname = item->getDef()->getName();
#if (DEBUG_FONT_MAN == 1)
    if (_log) {
        int index = item->getDef()->getIndex();
        fprintf(_log, "   no instance: adding new one for filename=%s, index = %d\n", fname.c_str(), index);
    }
#endif
    LVFreeTypeFace* font = new LVFreeTypeFace(_lock, _library, &_globalCache);
    lString8 pathname = makeFontFileName(fname);
    //def.setName( fname );
    //def.setIndex( index );

    //if ( fname.empty() || pathname.empty() ) {
    //    pathname = lString8("arial.ttf");
    //}

    if (!item->getDef()->isRealItalic() && italic) {
        //CRLog::debug("font: fake italic");
        newDef.setItalic(2);
        italicize = true;
    }

    // Use the family of the font we found in the cache (it may be different
    // from the requested family).
    // Assigning the requested familly to this new font could be wrong, and
    // may cause a style or font mismatch when loading from cache, forcing a
    // full re-rendering).
    family = item->getDef()->getFamily();

    //printf("going to load font file %s\n", fname.c_str());
    bool loaded = false;
    if (item->getDef()->getBuf().isNull())
        loaded = font->loadFromFile(pathname.c_str(), item->getDef()->getIndex(), size, family,
                                    isBitmapModeForSize(size), italicize, item->getDef()->getWeight());
    else
        loaded = font->loadFromBuffer(item->getDef()->getBuf(), item->getDef()->getIndex(), size,
                                      family, isBitmapModeForSize(size), italicize, item->getDef()->getWeight());
    if (loaded) {
        //fprintf(_log, "    : loading from file %s : %s %d\n", item->getDef()->getName().c_str(),
        //    item->getDef()->getTypeFace().c_str(), item->getDef()->getSize() );
        LVFontRef ref(font);
        // Instantiate this font with the requested OpenType features
        newDef.setFeatures(features);
        font->setFeatures(features); // followup setKerningMode() will create/update hb_features if needed
        font->setKerning(GetKerning());
        font->setShapingMode(GetShapingMode());
        font->setFaceName(item->getDef()->getTypeFace());
        font->setGammaIndex(_gammaIndex);
        newDef.setSize(size);
        //item->setFont( ref );
        //_cache.update( def, ref );
#if (USE_FT_EMBOLDEN == 1)
        int deltaWeight = myabs(weight - newDef.getWeight());
        if (deltaWeight >= 25) {
            // embolden
            // Will make some of this font's methods do embolden the glyphs and widths
            font->setSynthWeight(weight);
            newDef.setWeight(weight, false);
            // Now newDef contains fake/synthetic weight
        }
#else
        int deltaWeight = weight - newDef.getWeight();
        if (deltaWeight >= 200) {
            CRLog::debug("font: apply Embolding to increase weight from %d to %d",
                         newDef.getWeight(), newDef.getWeight() + 200);
            // Create a wrapper with LVFontBoldTransform which will bolden the glyphs
            newDef.setWeight(newDef.getWeight() + 200);
            ref = LVFontRef(new LVFontBoldTransform(ref, &_globalCache));
        }
#endif
        // build fallback chain
        for (int i = 0; i < _fallbackFontFaces.length(); i++) {
            if (item->getDef()->getTypeFace() == _fallbackFontFaces[i]) {
                ref->setFallbackMask(1 << i);
                break;
            }
        }
        // filling in the language support table if it is empty
#if USE_LOCALE_DATA == 1
        LVHashTable<lString8, font_lang_compat>* langTable = NULL;
        if (!_supportedLangs.get(font->getTypeFace(), langTable) || NULL == langTable) {
            // Here langTable can only be NULL
            FT_Face face = (FT_Face)font->GetHandle();
            if (NULL != face) {
                langTable = getSupportedLangs(face);
                _supportedLangs.set(font->getTypeFace(), langTable);
            }
        }
#endif
        // add to cache
        _cache.update(&newDef, ref);
        //            int rsz = ref->getSize();
        //            if ( rsz!=size ) {
        //                size++;
        //            }
        //delete def;
        return ref;
    } else {
        //printf("    not found!\n");
    }
    //delete def;
    delete font;
    return LVFontRef(NULL);
}

void LVFreeTypeFontManager::GetAvailableFontWeights(LVArray<int>& weights, lString8 typeface) {
    _cache.getAvailableFontWeights(weights, typeface);
}

bool LVFreeTypeFontManager::checkCharSet(FT_Face face) {
    // TODO: check existance of required characters (e.g. cyrillic)
    if (face == NULL)
        return false; // invalid face
    for (int i = 0; i < _requiredChars.length(); i++) {
        lChar32 ch = _requiredChars[i];
        FT_UInt ch_glyph_index = FT_Get_Char_Index(face, ch);
        if (ch_glyph_index == 0) {
            CRLog::debug("Required char not found in font: %04x", ch);
            return false; // no required char!!!
        }
    }
    return true;
}

font_lang_compat LVFreeTypeFontManager::checkFontLangCompat(const lString8& typeface, const lString8& langTag) {
    font_lang_compat res = font_lang_compat_invalid_tag;
#if USE_LOCALE_DATA == 1
    CRLocaleData loc(langTag);
    if (loc.isValid()) {
        LVHashTable<lString8, font_lang_compat>* langTable = NULL;
        res = font_lang_compat_none;
        if (!_supportedLangs.get(typeface, langTable) || NULL == langTable) {
            // Here langTable can only be NULL
            // Call GetFont() to update language support table in _supportedLangs
            LVFontRef fntRef = GetFont(-1, 400, false, css_ff_inherit, typeface, -1);
            // Get updated langTable
            _supportedLangs.get(typeface, langTable);
        }
        if (NULL != langTable) {
            lString8 best_lang;
            LVHashTable<lString8, font_lang_compat>::iterator it = langTable->forwardIterator();
            LVHashTable<lString8, font_lang_compat>::pair* p;
            int max_match = 0;
            font_lang_compat compat;
            while ((p = it.next()) != NULL) {
                lString8 lang = p->key;
                compat = p->value;
                CRLocaleData fc_loc(lang);
                if (fc_loc.isValid()) {
                    int match = loc.calcMatch(fc_loc);
                    if (match > max_match) {
                        max_match = match;
                        best_lang = lang;
                    }
                }
            }
            if (!best_lang.empty()) {
                if (langTable->get(best_lang, compat))
                    res = compat;
            }
        }
    } else {
        CRLog::warn("checkFontLangCompat(): invalid langTag: %s", langTag.c_str());
    }
#endif
    return res;
}

/*
bool LVFreeTypeFontManager::isMonoSpaced( FT_Face face )
{
    // TODO: check existance of required characters (e.g. cyrillic)
    if (face==NULL)
        return false; // invalid face
    lChar32 ch1 = 'i';
    FT_UInt ch_glyph_index1 = FT_Get_Char_Index( face, ch1 );
    if ( ch_glyph_index1==0 )
        return false; // no required char!!!
    int w1, w2;
    int error1 = FT_Load_Glyph( face,  //    handle to face object
            ch_glyph_index1,           //    glyph index
            FT_LOAD_DEFAULT );         //   load flags, see below
    if ( error1 )
        w1 = 0;
    else
        w1 = (face->glyph->metrics.horiAdvance >> 6);
    int error2 = FT_Load_Glyph( face,  //     handle to face object
            ch_glyph_index2,           //     glyph index
            FT_LOAD_DEFAULT );         //     load flags, see below
    if ( error2 )
        w2 = 0;
    else
        w2 = (face->glyph->metrics.horiAdvance >> 6);

    lChar32 ch2 = 'W';
    FT_UInt ch_glyph_index2 = FT_Get_Char_Index( face, ch2 );
    if ( ch_glyph_index2==0 )
        return false; // no required char!!!
    return w1==w2;
}
*/

// Note: publishers can specify font-variant/font-feature-settings/font-variation-settings
// in the @font-face declaration.
// TODO: parse it and pass it here, and set it on the non-instantiated font (instead of -1)
bool LVFreeTypeFontManager::RegisterDocumentFont(int documentId, LVContainerRef container,
                                                 lString32 name, lString8 faceName, bool bold,
                                                 bool italic) {
    FONT_MAN_GUARD
    lString8 name8 = UnicodeToUtf8(name);
    CRLog::debug("RegisterDocumentFont(documentId=%d, path=%s)", documentId, name8.c_str());
    if (_cache.findDocumentFontDuplicate(documentId, name8)) {
        return false;
    }
    LVStreamRef stream = container->OpenStream(name.c_str(), LVOM_READ);
    if (stream.isNull())
        return false;
    lUInt32 size = (lUInt32)stream->GetSize();
    if (size < 100 || size > 5000000)
        return false;
    LVByteArrayRef buf(new LVByteArray(size, 0));
    lvsize_t bytesRead = 0;
    if (stream->Read(buf->get(), size, &bytesRead) != LVERR_OK || bytesRead != size)
        return false;
    bool res = false;

    int index = 0;

    FT_Face face = NULL;

    // for all faces in file
    for (;; index++) {
        int error = FT_New_Memory_Face(_library, buf->get(), buf->length(), index,
                                       &face); /* create face object */
        if (error) {
            if (index == 0) {
                CRLog::error("FT_New_Memory_Face returned error %d", error);
            }
            break;
        }
        //            bool scal = FT_IS_SCALABLE( face );
        //            bool charset = checkCharSet( face );
        //            //bool monospaced = isMonoSpaced( face );
        //            if ( !scal || !charset ) {
        //    //#if (DEBUG_FONT_MAN==1)
        //     //           if ( _log ) {
        //                CRLog::debug("    won't register font %s: %s",
        //                    name.c_str(), !charset?"no mandatory characters in charset" : "font is not scalable"
        //                    );
        //    //            }
        //    //#endif
        //                if ( face ) {
        //                    FT_Done_Face( face );
        //                    face = NULL;
        //                }
        //                break;
        //            }
        int num_faces = face->num_faces;

        css_font_family_t fontFamily = css_ff_sans_serif;
        if (face->face_flags & FT_FACE_FLAG_FIXED_WIDTH)
            fontFamily = css_ff_monospace;
        lString8 familyName(!faceName.empty() ? faceName : ::familyName(face));
        if (familyName == "Times" || familyName == "Times New Roman")
            fontFamily = css_ff_serif;

        int weight = !faceName.empty() ? (bold ? 700 : 400) : getFontWeight(face);
        bool italicFlag = !faceName.empty() ? italic : (face->style_flags & FT_STYLE_FLAG_ITALIC) != 0;

        LVFontDef def(
                name8,
                -1, // height==-1 for scalable fonts
                weight,
                italicFlag,
                -1, // OpenType features = -1 for not yet instantiated fonts
                fontFamily,
                familyName,
                index,
                documentId,
                buf);
#if (DEBUG_FONT_MAN == 1)
        if (_log) {
            fprintf(_log, "registering font: (file=%s[%d], size=%d, weight=%d, italic=%d, family=%d, typeface=%s)\n",
                    def.getName().c_str(), def.getIndex(), def.getSize(), def.getWeight(), def.getItalic() ? 1 : 0, (int)def.getFamily(), def.getTypeFace().c_str());
        }
#endif
        if (face) {
            FT_Done_Face(face);
            face = NULL;
        }

        if (_cache.findDuplicate(&def)) {
            CRLog::trace("font definition is duplicate");
            return false;
        }
        _cache.update(&def, LVFontRef(NULL));
        if (!def.getItalic()) {
            LVFontDef newDef(def);
            newDef.setItalic(2); // can italicize
            if (!_cache.findDuplicate(&newDef))
                _cache.update(&newDef, LVFontRef(NULL));
        }
        res = true;

        if (index >= num_faces - 1)
            break;
    }

    return res;
}

void LVFreeTypeFontManager::UnregisterDocumentFonts(int documentId) {
    _cache.removeDocumentFonts(documentId);
}

bool LVFreeTypeFontManager::RegisterExternalFont(int documentId, lString32 name, lString8 family_name, bool bold,
                                                 bool italic) {
    if (name.startsWithNoCase(lString32("res://")))
        name = name.substr(6);
    else if (name.startsWithNoCase(lString32("file://")))
        name = name.substr(7);
    lString8 fname = UnicodeToUtf8(name);
    CRLog::debug("RegisterExternalFont(documentId=%d, path=%s)", documentId, fname.c_str());
    if (_cache.findDocumentFontDuplicate(documentId, fname)) {
        return false;
    }

    bool res = false;
    int index = 0;

    FT_Face face = NULL;

    // for all faces in file
    for (;; index++) {
        int error = FT_New_Face(_library, fname.c_str(), index, &face); /* create face object */
        if (error) {
            if (index == 0) {
                CRLog::error("FT_New_Face returned error %d", error);
            }
            break;
        }
        bool scal = FT_IS_SCALABLE(face);
        bool color = FT_HAS_COLOR(face) != 0;
        bool charset = checkCharSet(face);
        if (!charset) {
            if (FT_Select_Charmap(face, FT_ENCODING_UNICODE)) // returns 0 on success
                // If no unicode charmap found, try symbol charmap
                if (!FT_Select_Charmap(face, FT_ENCODING_MS_SYMBOL))
                    // It has a symbol charmap: consider it valid
                    charset = true;
        }
        //bool monospaced = isMonoSpaced( face );
        if ((!scal && !color) || !charset) {
            CRLog::debug("    won't register font %s: %s",
                         name.c_str(),
                         !charset ? "no mandatory characters in charset" : "font nor scalable nor color");
            if (face) {
                FT_Done_Face(face);
                face = NULL;
            }
            break;
        }
        int num_faces = face->num_faces;

        css_font_family_t fontFamily = css_ff_sans_serif;
        if (face->face_flags & FT_FACE_FLAG_FIXED_WIDTH)
            fontFamily = css_ff_monospace;
        //lString8 familyName(::familyName(face));
        /*
        if (familyName == "Times" || familyName == "Times New Roman")
            fontFamily = css_ff_serif;
         */

        LVFontDef def(
                fname,
                -1, // height==-1 for scalable fonts
                bold ? 700 : 400,
                italic,
                -1, // OpenType features = -1 for not yet instantiated fonts
                fontFamily,
                family_name,
                index,
                documentId);
#if (DEBUG_FONT_MAN == 1)
        if (_log) {
            fprintf(_log, "registering font: (file=%s[%d], size=%d, weight=%d, italic=%d, family=%d, typeface=%s)\n",
                    def.getName().c_str(), def.getIndex(), def.getSize(), def.getWeight(), def.getItalic() ? 1 : 0, (int)def.getFamily(), def.getTypeFace().c_str());
        }
#endif
        if (_cache.findDuplicate(&def)) {
            CRLog::trace("font definition is duplicate");
            return false;
        }
        _cache.update(&def, LVFontRef(NULL));
        if (scal && !def.getItalic()) {
            LVFontDef newDef(def);
            newDef.setItalic(2); // can italicize
            if (!_cache.findDuplicate(&newDef))
                _cache.update(&newDef, LVFontRef(NULL));
        }
        res = true;

        if (face) {
            FT_Done_Face(face);
            face = NULL;
        }

        if (index >= num_faces - 1)
            break;
    }

    return res;
}

bool LVFreeTypeFontManager::RegisterFont(lString8 name) {
    FONT_MAN_GUARD
#ifdef LOAD_TTF_FONTS_ONLY
    if (name.pos(cs8(".ttf")) < 0 && name.pos(cs8(".TTF")) < 0)
        return false; // load ttf fonts only
#endif
    //CRLog::trace("RegisterFont(%s)", name.c_str());
    lString8 fname = makeFontFileName(name);
    //CRLog::trace("font file name : %s", fname.c_str());
#if (DEBUG_FONT_MAN == 1)
    if (_log) {
        fprintf(_log, "RegisterFont( %s ) path=%s\n",
                name.c_str(), fname.c_str());
    }
#endif
    bool res = false;

    int index = 0;

    FT_Face face = NULL;

    // for all faces in file
    for (;; index++) {
        int error = FT_New_Face(_library, fname.c_str(), index, &face); /* create face object */
        if (error) {
            if (index == 0) {
                CRLog::error("FT_New_Face returned error %d", error);
            }
            break;
        }
        bool scal = FT_IS_SCALABLE(face) != 0;
        bool color = FT_HAS_COLOR(face) != 0;
        bool charset = checkCharSet(face);
        if (!charset) {
            if (FT_Select_Charmap(face, FT_ENCODING_UNICODE)) // returns 0 on success
                // If no unicode charmap found, try symbol charmap
                if (!FT_Select_Charmap(face, FT_ENCODING_MS_SYMBOL))
                    // It has a symbol charmap: consider it valid
                    charset = true;
        }
        //bool monospaced = isMonoSpaced( face );
        if ((!scal && !color) || !charset) {
            CRLog::debug("    won't register font %s: %s",
                         name.c_str(),
                         !charset ? "no mandatory characters in charset" : "font nor scalable nor color");
            if (face) {
                FT_Done_Face(face);
                face = NULL;
            }
            break;
        }
        int num_faces = face->num_faces;

        css_font_family_t fontFamily = css_ff_sans_serif;
        if (face->face_flags & FT_FACE_FLAG_FIXED_WIDTH)
            fontFamily = css_ff_monospace;
        lString8 familyName(::familyName(face));
        /*
        if (familyName == "Times" || familyName == "Times New Roman")
            fontFamily = css_ff_serif;
         */
        LVFontDef def(
                name,
                -1, // height==-1 for scalable fonts
                getFontWeight(face),
                (face->style_flags & FT_STYLE_FLAG_ITALIC) ? true : false,
                -1, // OpenType features = -1 for not yet instantiated fonts
                fontFamily,
                familyName,
                index);
#if (DEBUG_FONT_MAN == 1)
        if (_log) {
            fprintf(_log, "registering font: (file=%s[%d], size=%d, weight=%d, italic=%d, family=%d, typeface=%s)\n",
                    def.getName().c_str(), def.getIndex(), def.getSize(), def.getWeight(), def.getItalic() ? 1 : 0, (int)def.getFamily(), def.getTypeFace().c_str());
        }
#endif

        if (face) {
            FT_Done_Face(face);
            face = NULL;
        }

        if (_cache.findDuplicate(&def)) {
            CRLog::trace("font definition is duplicate");
            return false;
        }
        _cache.update(&def, LVFontRef(NULL));
        if (scal && !def.getItalic()) {
            // If this font is not italic, create another definition
            // with italic=2 (=fake italic) as we can italicize it.
            // A real italic font (italic=1) will be found first
            // when italic is requested.
            // (Strange that italic and embolden are managed differently...
            // maybe it makes the 2x2 combinations easier to manage?)
            LVFontDef newDef(def);
            newDef.setItalic(2); // can italicize
            if (!_cache.findDuplicate(&newDef))
                _cache.update(&newDef, LVFontRef(NULL));
        }
        res = true;

        if (index >= num_faces - 1)
            break;
    }

    return res;
}

bool LVFreeTypeFontManager::Init(lString8 path, bool initSystemFonts_) {
    _path = path;
    if (initSystemFonts_)
        initSystemFonts();
    return (_library != NULL);
}

bool LVFreeTypeFontManager::SetAsPreferredFontWithBias(lString8 face, int bias, bool clearOthersBias) {
    FONT_MAN_GUARD
    return _cache.setAsPreferredFontWithBias(face, bias, clearOthersBias);
}

#endif // (USE_FREETYPE==1)
