//========================================================================
//
// CairoFontEngine.cc
//
// Copyright 2003 Glyph & Cog, LLC
// Copyright 2004 Red Hat, Inc
//
//========================================================================

//========================================================================
//
// Modified under the Poppler project - http://poppler.freedesktop.org
//
// All changes made under the Poppler project to this file are licensed
// under GPL version 2 or later
//
// Copyright (C) 2005-2007 Jeff Muizelaar <jeff@infidigm.net>
// Copyright (C) 2005, 2006 Kristian Høgsberg <krh@redhat.com>
// Copyright (C) 2005 Martin Kretzschmar <martink@gnome.org>
// Copyright (C) 2005, 2009, 2012, 2013, 2015, 2017-2019 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2006, 2007, 2010, 2011 Carlos Garcia Campos <carlosgc@gnome.org>
// Copyright (C) 2007 Koji Otani <sho@bbr.jp>
// Copyright (C) 2008, 2009 Chris Wilson <chris@chris-wilson.co.uk>
// Copyright (C) 2008, 2012, 2014, 2016, 2017 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2009 Darren Kenny <darren.kenny@sun.com>
// Copyright (C) 2010 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
// Copyright (C) 2010 Jan Kümmel <jan+freedesktop@snorc.org>
// Copyright (C) 2012 Hib Eris <hib@hiberis.nl>
// Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2015, 2016 Jason Crain <jason@aquaticape.us>
// Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
// Copyright (C) 2019 Christian Persch <chpe@src.gnome.org>
// Copyright (C) 2020 Michal <sudolskym@gmail.com>
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================

#include <config.h>
#if defined(MIKTEX_WINDOWS)
#  define MIKTEX_UTF8_WRAP_ALL 1
#  include <miktex/utf8wrap.h>
#endif

#include "config.h"
#include <cstring>
#include <forward_list>
#include "CairoFontEngine.h"
#include "CairoOutputDev.h"
#include "GlobalParams.h"
#include <fofi/FoFiTrueType.h>
#include <fofi/FoFiType1C.h>
#include "goo/gfile.h"
#include "Error.h"
#include "XRef.h"
#include "Gfx.h"
#include "Page.h"

#if defined(HAVE_FCNTL_H) && defined(HAVE_SYS_MMAN_H) && defined(HAVE_SYS_STAT_H)
#    include <fcntl.h>
#    include <sys/stat.h>
#    include <sys/mman.h>
#    define CAN_CHECK_OPEN_FACES 1
#endif

//------------------------------------------------------------------------
// CairoFont
//------------------------------------------------------------------------

CairoFont::CairoFont(Ref refA, cairo_font_face_t *cairo_font_faceA, int *codeToGIDA, unsigned int codeToGIDLenA, bool substituteA, bool printingA)
    : ref(refA), cairo_font_face(cairo_font_faceA), codeToGID(codeToGIDA), codeToGIDLen(codeToGIDLenA), substitute(substituteA), printing(printingA)
{
}

CairoFont::~CairoFont()
{
    cairo_font_face_destroy(cairo_font_face);
    gfree(codeToGID);
}

bool CairoFont::matches(Ref &other, bool printingA)
{
    return (other == ref);
}

cairo_font_face_t *CairoFont::getFontFace()
{
    return cairo_font_face;
}

unsigned long CairoFont::getGlyph(CharCode code, const Unicode *u, int uLen)
{
    FT_UInt gid;

    if (codeToGID && code < codeToGIDLen) {
        gid = (FT_UInt)codeToGID[code];
    } else {
        gid = (FT_UInt)code;
    }
    return gid;
}

double CairoFont::getSubstitutionCorrection(GfxFont *gfxFont)
{
    double w1, w2, w3;
    CharCode code;
    const char *name;

    // for substituted fonts: adjust the font matrix -- compare the
    // width of 'm' in the original font and the substituted font
    if (isSubstitute() && !gfxFont->isCIDFont()) {
        for (code = 0; code < 256; ++code) {
            if ((name = ((Gfx8BitFont *)gfxFont)->getCharName(code)) && name[0] == 'm' && name[1] == '\0') {
                break;
            }
        }
        if (code < 256) {
            w1 = ((Gfx8BitFont *)gfxFont)->getWidth(code);
            {
                cairo_matrix_t m;
                cairo_matrix_init_identity(&m);
                cairo_font_options_t *options = cairo_font_options_create();
                cairo_font_options_set_hint_style(options, CAIRO_HINT_STYLE_NONE);
                cairo_font_options_set_hint_metrics(options, CAIRO_HINT_METRICS_OFF);
                cairo_scaled_font_t *scaled_font = cairo_scaled_font_create(cairo_font_face, &m, &m, options);

                cairo_text_extents_t extents;
                cairo_scaled_font_text_extents(scaled_font, "m", &extents);

                cairo_scaled_font_destroy(scaled_font);
                cairo_font_options_destroy(options);
                w2 = extents.x_advance;
            }
            w3 = ((Gfx8BitFont *)gfxFont)->getWidth(0);
            if (!gfxFont->isSymbolic() && w2 > 0 && w1 > w3) {
                // if real font is substantially narrower than substituted
                // font, reduce the font size accordingly
                if (w1 > 0.01 && w1 < 0.9 * w2) {
                    w1 /= w2;
                    return w1;
                }
            }
        }
    }
    return 1.0;
}

//------------------------------------------------------------------------
// CairoFreeTypeFont
//------------------------------------------------------------------------

static cairo_user_data_key_t _ft_cairo_key;

static void _ft_done_face_uncached(void *closure)
{
    FT_Face face = (FT_Face)closure;
    FT_Done_Face(face);
}

static bool _ft_new_face_uncached(FT_Library lib, const char *filename, char *font_data, int font_data_len, FT_Face *face_out, cairo_font_face_t **font_face_out)
{
    FT_Face face;
    cairo_font_face_t *font_face;

    if (font_data == nullptr) {
        if (FT_New_Face(lib, filename, 0, &face))
            return false;
    } else {
        if (FT_New_Memory_Face(lib, (unsigned char *)font_data, font_data_len, 0, &face))
            return false;
    }

    font_face = cairo_ft_font_face_create_for_ft_face(face, FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP);
    if (cairo_font_face_set_user_data(font_face, &_ft_cairo_key, face, _ft_done_face_uncached)) {
        _ft_done_face_uncached(face);
        cairo_font_face_destroy(font_face);
        return false;
    }

    *face_out = face;
    *font_face_out = font_face;
    return true;
}

#ifdef CAN_CHECK_OPEN_FACES
struct _ft_face_data
{
    int fd;
    unsigned long hash;
    size_t size;
    unsigned char *bytes;

    FT_Library lib;
    FT_Face face;
    cairo_font_face_t *font_face;
};

class _FtFaceDataProxy
{
    _ft_face_data *_data;

public:
    _FtFaceDataProxy(_ft_face_data *data) : _data(data) { cairo_font_face_reference(_data->font_face); }
    _FtFaceDataProxy(_FtFaceDataProxy &&) = delete;
    ~_FtFaceDataProxy() { cairo_font_face_destroy(_data->font_face); }
    operator _ft_face_data *() { return _data; }
};

static thread_local std::forward_list<_FtFaceDataProxy> _local_open_faces;

static unsigned long _djb_hash(const unsigned char *bytes, size_t len)
{
    unsigned long hash = 5381;
    while (len--) {
        unsigned char c = *bytes++;
        hash *= 33;
        hash ^= c;
    }
    return hash;
}

static bool _ft_face_data_equal(struct _ft_face_data *a, struct _ft_face_data *b)
{
    if (a->lib != b->lib)
        return false;
    if (a->size != b->size)
        return false;
    if (a->hash != b->hash)
        return false;

    return memcmp(a->bytes, b->bytes, a->size) == 0;
}

static void _ft_done_face(void *closure)
{
    struct _ft_face_data *data = (struct _ft_face_data *)closure;

    if (data->fd != -1) {
#    if defined(__SUNPRO_CC) && defined(__sun) && defined(__SVR4)
        munmap((char *)data->bytes, data->size);
#    else
        munmap(data->bytes, data->size);
#    endif
        close(data->fd);
    } else {
        gfree(data->bytes);
    }

    FT_Done_Face(data->face);
    gfree(data);
}

static bool _ft_new_face(FT_Library lib, const char *filename, char *font_data, int font_data_len, FT_Face *face_out, cairo_font_face_t **font_face_out)
{
    struct stat st;
    struct _ft_face_data tmpl;

    tmpl.fd = -1;

    if (font_data == nullptr) {
        /* if we fail to mmap the file, just pass it to FreeType instead */
        tmpl.fd = openFileDescriptor(filename, O_RDONLY);
        if (tmpl.fd == -1)
            return _ft_new_face_uncached(lib, filename, font_data, font_data_len, face_out, font_face_out);

        if (fstat(tmpl.fd, &st) == -1) {
            close(tmpl.fd);
            return _ft_new_face_uncached(lib, filename, font_data, font_data_len, face_out, font_face_out);
        }

        tmpl.bytes = (unsigned char *)mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, tmpl.fd, 0);
        if (tmpl.bytes == MAP_FAILED) {
            close(tmpl.fd);
            return _ft_new_face_uncached(lib, filename, font_data, font_data_len, face_out, font_face_out);
        }
        tmpl.size = st.st_size;
    } else {
        tmpl.bytes = (unsigned char *)font_data;
        tmpl.size = font_data_len;
    }

    /* check to see if this is a duplicate of any of the currently open fonts */
    tmpl.lib = lib;
    tmpl.hash = _djb_hash(tmpl.bytes, tmpl.size);

    for (_ft_face_data *l : _local_open_faces) {
        if (_ft_face_data_equal(l, &tmpl)) {
            if (tmpl.fd != -1) {
#    if defined(__SUNPRO_CC) && defined(__sun) && defined(__SVR4)
                munmap((char *)tmpl.bytes, tmpl.size);
#    else
                munmap(tmpl.bytes, tmpl.size);
#    endif
                close(tmpl.fd);
            } else {
                gfree(tmpl.bytes);
            }
            *face_out = l->face;
            *font_face_out = cairo_font_face_reference(l->font_face);
            return true;
        }
    }

    /* not a dup, open and insert into list */
    if (FT_New_Memory_Face(lib, (FT_Byte *)tmpl.bytes, tmpl.size, 0, &tmpl.face)) {
        if (tmpl.fd != -1) {
#    if defined(__SUNPRO_CC) && defined(__sun) && defined(__SVR4)
            munmap((char *)tmpl.bytes, tmpl.size);
#    else
            munmap(tmpl.bytes, tmpl.size);
#    endif

            close(tmpl.fd);
        }
        return false;
    }

    struct _ft_face_data *l = (struct _ft_face_data *)gmallocn(1, sizeof(struct _ft_face_data));
    *l = tmpl;

    l->font_face = cairo_ft_font_face_create_for_ft_face(tmpl.face, FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP);
    if (cairo_font_face_set_user_data(l->font_face, &_ft_cairo_key, l, _ft_done_face)) {
        cairo_font_face_destroy(l->font_face);
        _ft_done_face(l);
        return false;
    }

    _local_open_faces.remove_if([](_ft_face_data *data) { return cairo_font_face_get_reference_count(data->font_face) == 1; });
    _local_open_faces.emplace_front(l);

    *face_out = l->face;
    *font_face_out = l->font_face;
    return true;
}
#else
#    define _ft_new_face _ft_new_face_uncached
#endif

CairoFreeTypeFont::CairoFreeTypeFont(Ref refA, cairo_font_face_t *cairo_font_faceA, int *codeToGIDA, unsigned int codeToGIDLenA, bool substituteA) : CairoFont(refA, cairo_font_faceA, codeToGIDA, codeToGIDLenA, substituteA, true) { }

CairoFreeTypeFont::~CairoFreeTypeFont() { }

CairoFreeTypeFont *CairoFreeTypeFont::create(GfxFont *gfxFont, XRef *xref, FT_Library lib, bool useCIDs)
{
    GooString *fileName;
    const char *fileNameC;
    char *font_data;
    int font_data_len;
    int i, n;
    GfxFontType fontType;
    GfxFontLoc *fontLoc;
    char **enc;
    const char *name;
    FoFiTrueType *ff;
    FoFiType1C *ff1c;
    Ref ref;
    FT_Face face;
    cairo_font_face_t *font_face;

    int *codeToGID;
    unsigned int codeToGIDLen;

    codeToGID = nullptr;
    codeToGIDLen = 0;
    font_data = nullptr;
    font_data_len = 0;
    fileName = nullptr;
    fileNameC = nullptr;

    bool substitute = false;

    ref = *gfxFont->getID();
    fontType = gfxFont->getType();

    if (!(fontLoc = gfxFont->locateFont(xref, nullptr))) {
        error(errSyntaxError, -1, "Couldn't find a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
        goto err2;
    }

    // embedded font
    if (fontLoc->locType == gfxFontLocEmbedded) {
        font_data = gfxFont->readEmbFontFile(xref, &font_data_len);
        if (nullptr == font_data)
            goto err2;

        // external font
    } else { // gfxFontLocExternal
        fileName = fontLoc->path;
        fontType = fontLoc->fontType;
        substitute = true;
    }

    if (fileName != nullptr) {
        fileNameC = fileName->c_str();
    }

    switch (fontType) {
    case fontType1:
    case fontType1C:
    case fontType1COT:
        if (!_ft_new_face(lib, fileNameC, font_data, font_data_len, &face, &font_face)) {
            error(errSyntaxError, -1, "could not create type1 face");
            goto err2;
        }

        enc = ((Gfx8BitFont *)gfxFont)->getEncoding();

        codeToGID = (int *)gmallocn(256, sizeof(int));
        codeToGIDLen = 256;
        for (i = 0; i < 256; ++i) {
            codeToGID[i] = 0;
            if ((name = enc[i])) {
                codeToGID[i] = FT_Get_Name_Index(face, (char *)name);
                if (codeToGID[i] == 0) {
                    Unicode u;
                    u = globalParams->mapNameToUnicodeText(name);
                    codeToGID[i] = FT_Get_Char_Index(face, u);
                }
                if (codeToGID[i] == 0) {
                    name = GfxFont::getAlternateName(name);
                    if (name) {
                        codeToGID[i] = FT_Get_Name_Index(face, (char *)name);
                    }
                }
            }
        }
        break;
    case fontCIDType2:
    case fontCIDType2OT:
        codeToGID = nullptr;
        n = 0;
        if (((GfxCIDFont *)gfxFont)->getCIDToGID()) {
            n = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen();
            if (n) {
                codeToGID = (int *)gmallocn(n, sizeof(int));
                memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(), n * sizeof(int));
            }
        } else {
            if (font_data != nullptr) {
                ff = FoFiTrueType::make(font_data, font_data_len);
            } else {
                ff = FoFiTrueType::load(fileNameC);
            }
            if (!ff)
                goto err2;
            codeToGID = ((GfxCIDFont *)gfxFont)->getCodeToGIDMap(ff, &n);
            delete ff;
        }
        codeToGIDLen = n;
        /* Fall through */
    case fontTrueType:
    case fontTrueTypeOT:
        if (font_data != nullptr) {
            ff = FoFiTrueType::make(font_data, font_data_len);
        } else {
            ff = FoFiTrueType::load(fileNameC);
        }
        if (!ff) {
            error(errSyntaxError, -1, "failed to load truetype font\n");
            goto err2;
        }
        /* This might be set already for the CIDType2 case */
        if (fontType == fontTrueType || fontType == fontTrueTypeOT) {
            codeToGID = ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ff);
            codeToGIDLen = 256;
        }
        delete ff;
        if (!_ft_new_face(lib, fileNameC, font_data, font_data_len, &face, &font_face)) {
            error(errSyntaxError, -1, "could not create truetype face\n");
            goto err2;
        }
        break;

    case fontCIDType0:
    case fontCIDType0C:

        codeToGID = nullptr;
        codeToGIDLen = 0;

        if (!useCIDs) {
            if (font_data != nullptr) {
                ff1c = FoFiType1C::make(font_data, font_data_len);
            } else {
                ff1c = FoFiType1C::load(fileNameC);
            }
            if (ff1c) {
                codeToGID = ff1c->getCIDToGIDMap((int *)&codeToGIDLen);
                delete ff1c;
            }
        }

        if (!_ft_new_face(lib, fileNameC, font_data, font_data_len, &face, &font_face)) {
            error(errSyntaxError, -1, "could not create cid face\n");
            goto err2;
        }
        break;

    case fontCIDType0COT:
        codeToGID = nullptr;
        n = 0;
        if (((GfxCIDFont *)gfxFont)->getCIDToGID()) {
            n = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen();
            if (n) {
                codeToGID = (int *)gmallocn(n, sizeof(int));
                memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(), n * sizeof(int));
            }
        }
        codeToGIDLen = n;

        if (!codeToGID) {
            if (!useCIDs) {
                if (font_data != nullptr) {
                    ff = FoFiTrueType::make(font_data, font_data_len);
                } else {
                    ff = FoFiTrueType::load(fileNameC);
                }
                if (ff) {
                    if (ff->isOpenTypeCFF()) {
                        codeToGID = ff->getCIDToGIDMap((int *)&codeToGIDLen);
                    }
                    delete ff;
                }
            }
        }
        if (!_ft_new_face(lib, fileNameC, font_data, font_data_len, &face, &font_face)) {
            error(errSyntaxError, -1, "could not create cid (OT) face\n");
            goto err2;
        }
        break;

    default:
        fprintf(stderr, "font type %d not handled\n", (int)fontType);
        goto err2;
        break;
    }

    delete fontLoc;
    return new CairoFreeTypeFont(ref, font_face, codeToGID, codeToGIDLen, substitute);

err2:
    /* hmm? */
    delete fontLoc;
    gfree(codeToGID);
    gfree(font_data);
    fprintf(stderr, "some font thing failed\n");
    return nullptr;
}

//------------------------------------------------------------------------
// CairoType3Font
//------------------------------------------------------------------------

static const cairo_user_data_key_t type3_font_key = { 0 };

typedef struct _type3_font_info
{
    GfxFont *font;
    PDFDoc *doc;
    CairoFontEngine *fontEngine;
    bool printing;
    XRef *xref;
} type3_font_info_t;

static void _free_type3_font_info(void *closure)
{
    type3_font_info_t *info = (type3_font_info_t *)closure;

    info->font->decRefCnt();
    free(info);
}

static cairo_status_t _init_type3_glyph(cairo_scaled_font_t *scaled_font, cairo_t *cr, cairo_font_extents_t *extents)
{
    type3_font_info_t *info;
    GfxFont *font;

    info = (type3_font_info_t *)cairo_font_face_get_user_data(cairo_scaled_font_get_font_face(scaled_font), &type3_font_key);
    font = info->font;
    const double *mat = font->getFontBBox();
    extents->ascent = mat[3]; /* y2 */
    extents->descent = -mat[3]; /* -y1 */
    extents->height = extents->ascent + extents->descent;
    extents->max_x_advance = mat[2] - mat[1]; /* x2 - x1 */
    extents->max_y_advance = 0;

    return CAIRO_STATUS_SUCCESS;
}

static cairo_status_t _render_type3_glyph(cairo_scaled_font_t *scaled_font, unsigned long glyph, cairo_t *cr, cairo_text_extents_t *metrics)
{
    Dict *charProcs;
    Object charProc;
    CairoOutputDev *output_dev;
    cairo_matrix_t matrix, invert_y_axis;
    const double *mat;
    double wx, wy;
    PDFRectangle box;
    type3_font_info_t *info;
    GfxFont *font;
    Dict *resDict;
    Gfx *gfx;

    info = (type3_font_info_t *)cairo_font_face_get_user_data(cairo_scaled_font_get_font_face(scaled_font), &type3_font_key);

    font = info->font;
    resDict = ((Gfx8BitFont *)font)->getResources();
    charProcs = ((Gfx8BitFont *)(info->font))->getCharProcs();
    if (!charProcs)
        return CAIRO_STATUS_USER_FONT_ERROR;

    if ((int)glyph >= charProcs->getLength())
        return CAIRO_STATUS_USER_FONT_ERROR;

    mat = font->getFontMatrix();
    matrix.xx = mat[0];
    matrix.yx = mat[1];
    matrix.xy = mat[2];
    matrix.yy = mat[3];
    matrix.x0 = mat[4];
    matrix.y0 = mat[5];
    cairo_matrix_init_scale(&invert_y_axis, 1, -1);
    cairo_matrix_multiply(&matrix, &matrix, &invert_y_axis);
    cairo_transform(cr, &matrix);

    output_dev = new CairoOutputDev();
    output_dev->setCairo(cr);
    output_dev->setPrinting(info->printing);

    mat = font->getFontBBox();
    box.x1 = mat[0];
    box.y1 = mat[1];
    box.x2 = mat[2];
    box.y2 = mat[3];
    gfx = new Gfx(info->doc, output_dev, resDict, &box, nullptr);
    output_dev->startDoc(info->doc, info->fontEngine);
    output_dev->startPage(1, gfx->getState(), gfx->getXRef());
    output_dev->setInType3Char(true);
    charProc = charProcs->getVal(glyph);
    gfx->display(&charProc);

    output_dev->getType3GlyphWidth(&wx, &wy);
    cairo_matrix_transform_distance(&matrix, &wx, &wy);
    metrics->x_advance = wx;
    metrics->y_advance = wy;
    if (output_dev->hasType3GlyphBBox()) {
        double *bbox = output_dev->getType3GlyphBBox();

        cairo_matrix_transform_point(&matrix, &bbox[0], &bbox[1]);
        cairo_matrix_transform_point(&matrix, &bbox[2], &bbox[3]);
        metrics->x_bearing = bbox[0];
        metrics->y_bearing = bbox[1];
        metrics->width = bbox[2] - bbox[0];
        metrics->height = bbox[3] - bbox[1];
    }

    delete gfx;
    delete output_dev;

    return CAIRO_STATUS_SUCCESS;
}

CairoType3Font *CairoType3Font::create(GfxFont *gfxFont, PDFDoc *doc, CairoFontEngine *fontEngine, bool printing, XRef *xref)
{
    type3_font_info_t *info;
    cairo_font_face_t *font_face;
    Ref ref;
    int *codeToGID;
    unsigned int codeToGIDLen;
    int i, j;
    char **enc;
    Dict *charProcs;
    char *name;

    charProcs = ((Gfx8BitFont *)gfxFont)->getCharProcs();
    info = (type3_font_info_t *)malloc(sizeof(*info));
    ref = *gfxFont->getID();
    font_face = cairo_user_font_face_create();
    cairo_user_font_face_set_init_func(font_face, _init_type3_glyph);
    cairo_user_font_face_set_render_glyph_func(font_face, _render_type3_glyph);
    gfxFont->incRefCnt();
    info->font = gfxFont;
    info->doc = doc;
    info->fontEngine = fontEngine;
    info->printing = printing;
    info->xref = xref;

    cairo_font_face_set_user_data(font_face, &type3_font_key, (void *)info, _free_type3_font_info);

    enc = ((Gfx8BitFont *)gfxFont)->getEncoding();
    codeToGID = (int *)gmallocn(256, sizeof(int));
    codeToGIDLen = 256;
    for (i = 0; i < 256; ++i) {
        codeToGID[i] = 0;
        if (charProcs && (name = enc[i])) {
            for (j = 0; j < charProcs->getLength(); j++) {
                if (strcmp(name, charProcs->getKey(j)) == 0) {
                    codeToGID[i] = j;
                }
            }
        }
    }

    return new CairoType3Font(ref, font_face, codeToGID, codeToGIDLen, printing, xref);
}

CairoType3Font::CairoType3Font(Ref refA, cairo_font_face_t *cairo_font_faceA, int *codeToGIDA, unsigned int codeToGIDLenA, bool printingA, XRef *xref) : CairoFont(refA, cairo_font_faceA, codeToGIDA, codeToGIDLenA, false, printingA) { }

CairoType3Font::~CairoType3Font() { }

bool CairoType3Font::matches(Ref &other, bool printingA)
{
    return (other == ref && printing == printingA);
}

//------------------------------------------------------------------------
// CairoFontEngine
//------------------------------------------------------------------------

#define fontEngineLocker() std::unique_lock<std::recursive_mutex> locker(mutex)

CairoFontEngine::CairoFontEngine(FT_Library libA)
{
    int i;

    lib = libA;
    for (i = 0; i < cairoFontCacheSize; ++i) {
        fontCache[i] = nullptr;
    }

    FT_Int major, minor, patch;
    // as of FT 2.1.8, CID fonts are indexed by CID instead of GID
    FT_Library_Version(lib, &major, &minor, &patch);
    useCIDs = major > 2 || (major == 2 && (minor > 1 || (minor == 1 && patch > 7)));
}

CairoFontEngine::~CairoFontEngine()
{
    int i;

    for (i = 0; i < cairoFontCacheSize; ++i) {
        if (fontCache[i])
            delete fontCache[i];
    }
}

CairoFont *CairoFontEngine::getFont(GfxFont *gfxFont, PDFDoc *doc, bool printing, XRef *xref)
{
    int i, j;
    Ref ref;
    CairoFont *font;
    GfxFontType fontType;

    fontEngineLocker();
    ref = *gfxFont->getID();

    for (i = 0; i < cairoFontCacheSize; ++i) {
        font = fontCache[i];
        if (font && font->matches(ref, printing)) {
            for (j = i; j > 0; --j) {
                fontCache[j] = fontCache[j - 1];
            }
            fontCache[0] = font;
            return font;
        }
    }

    fontType = gfxFont->getType();
    if (fontType == fontType3)
        font = CairoType3Font::create(gfxFont, doc, this, printing, xref);
    else
        font = CairoFreeTypeFont::create(gfxFont, xref, lib, useCIDs);

    // XXX: if font is null should we still insert it into the cache?
    if (fontCache[cairoFontCacheSize - 1]) {
        delete fontCache[cairoFontCacheSize - 1];
    }
    for (j = cairoFontCacheSize - 1; j > 0; --j) {
        fontCache[j] = fontCache[j - 1];
    }
    fontCache[0] = font;
    return font;
}
