///////////////////////////////////////////////////////////////////////////////
//
//                          IMPORTANT NOTICE
//
// The following open source license statement does not apply to any
// entity in the Exception List published by FMSoft.
//
// For more information, please visit:
//
// https://www.fmsoft.cn/exception-list
//
//////////////////////////////////////////////////////////////////////////////
/*
 *   This file is part of MiniGUI, a mature cross-platform windowing
 *   and Graphics User Interface (GUI) support system for embedded systems
 *   and smart IoT devices.
 *
 *   Copyright (C) 2002~2018, Beijing FMSoft Technologies Co., Ltd.
 *   Copyright (C) 1998~2002, WEI Yongming
 *
 *   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 3 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, see <http://www.gnu.org/licenses/>.
 *
 *   Or,
 *
 *   As this program is a library, any link to this program must follow
 *   GNU General Public License version 3 (GPLv3). If you cannot accept
 *   GPLv3, you need to be licensed from FMSoft.
 *
 *   If you have got a commercial license of this program, please use it
 *   under the terms and conditions of the commercial license.
 *
 *   For more information about the commercial license, please refer to
 *   <http://www.minigui.com/en/about/licensing-policy/>.
 */
/*
** fontcache.c: Font cache for TrueType fonts.
**
** Create date: 2006/02/01
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "common.h"

#if defined(_MGFONT_FT2) && defined(_MGFONT_TTF_CACHE)

#include "minigui.h"
#include "gdi.h"
#include "freetype2.h"

typedef struct _MemBlk {
    void *data;
    int len;
    struct _MemBlk *hashPrev;
    struct _MemBlk *hashNext;
    struct _MemBlk *lruPrev;
    struct _MemBlk *lruNext;
} MemBlk;

typedef MemBlk LruQueueDummyHead;
typedef MemBlk HashQueueDummyHead;

/* hash directory array in every cache */
typedef struct _HashDirectory {
    HashQueueDummyHead hashHead;
} HashDirectory;

/* The structure is description of Cache
   blkSize : the size of each block in cache(in byte)
   nBlk : how many blocks in cache
   nDir : how many directory entries in cache
   *memPool : the memory pool(data use it) pointer.
   *lruQueueNodes : point all nodes by malloc. just
                    release function use it
   *hashDir : the hash directory pointer
   lruQueueDummyHead : head node of lru queue
   makeHashKeyFunc : the function which make hash key */
typedef struct _FontCache {
    /* Information about font */
    char df_name[LEN_UNIDEVFONT_NAME + 1];
    int style;
    int fontsize;
    int rotation;
    int refers;
    int blkSize;
    int nBlk;
    int nDir;
    void *memPool;
    MemBlk *lruQueueNodes;
    HashDirectory *hashDir;
    LruQueueDummyHead lruQueueDummyHead;
    MakeHashKeyFunc makeHashKeyFunc;
} FontCache;

/* Least-Recently-Used-Order
   global cache LRU queue */
typedef struct _CacheQueueNode
{
    /* hold global cache queue
       max cache node maybe 10 */
    FontCache cache;
    struct _CacheQueueNode *prevCache;
    struct _CacheQueueNode *nextCache;
}CacheQueueNode;

/* cache system. global cache descriptor,
   but user don't touch it, we use it!
   queueDummyHead : the head node of cache queue.

   maxCache : the max count of cache in programm
   cacheSize : each cache's size(in BYTE and
               everyone has the same size!)
   nCache : how many cache Now!! */
struct _CacheSystem {
    CacheQueueNode queueDummyHead;

    int maxCache;
    int cacheSize;
    int nCache;
};

struct _CacheSystem __mg_globalCache;



/* functions */

static void
AppendCache(CacheQueueNode *new)
{
    new->nextCache = &(__mg_globalCache.queueDummyHead);
    new->prevCache = __mg_globalCache.queueDummyHead.prevCache;
    (__mg_globalCache.queueDummyHead.prevCache)->nextCache = new;
    __mg_globalCache.queueDummyHead.prevCache = new;
}

/* create a new LRU-Queue. */
static int
CreateCacheLruQueue(FontCache *cache, int nblk)
{
    int i;
    MemBlk *blk;

    blk = malloc(nblk * sizeof(MemBlk));
    DP(("Allocate cache %p  LRU == %p\n", cache, blk));
    if (blk == NULL) {
        return -1;
    }

    memset(blk, 0, nblk * sizeof(MemBlk));
    cache->lruQueueNodes = blk;
    /* link the node as a double-linked-loop-list */
    blk->lruPrev = &cache->lruQueueDummyHead;
    blk->lruNext = (blk + 1);
    for (i = 1; i < nblk - 1; i++) {
        (blk + i)->lruPrev = (blk + i - 1);
        (blk + i)->lruNext = (blk + i + 1);
    }
    (blk + nblk - 1)->lruPrev = blk + nblk - 2;
    (blk + nblk - 1)->lruNext = &cache->lruQueueDummyHead;

    cache->lruQueueDummyHead.lruPrev = blk + nblk - 1;
    cache->lruQueueDummyHead.lruNext = blk;
    return 0;
}

static int
InitCache(CacheQueueNode *new, int ndir, int nblock)
{
    int i;
    /* allocate Hash directory array and LRU queue nodes
       follow code is bad, but no way to fix it. */
    if (new->cache.hashDir) {
        free(new->cache.hashDir);
        DP(("free a old hash-dir, %p\n", new->cache.hashDir));
    }
    new->cache.hashDir = malloc(ndir * sizeof(HashDirectory));
    DP(("Allocate %p hash dir %p\n", new, new->cache.hashDir));
    if (new->cache.hashDir == NULL) {
        return -1;
    }

    memset(new->cache.hashDir, 0, ndir * sizeof(HashDirectory));
    for (i = 0; i < ndir; i++) {
        new->cache.hashDir[i].hashHead.hashPrev =
          &(new->cache.hashDir[i].hashHead);
        new->cache.hashDir[i].hashHead.hashNext =
          &(new->cache.hashDir[i].hashHead);
    }

    if (new->cache.lruQueueDummyHead.lruNext !=
        &(new->cache.lruQueueDummyHead)) {
        /* It's a old node, not malloced,
           so we must release the old LRU-queue */
        free(new->cache.lruQueueNodes);
        DP(("free a old lru. %p\n", new->cache.lruQueueNodes));
    }
    if (CreateCacheLruQueue(&new->cache, nblock) == -1) {
        if (new->cache.lruQueueNodes) {
            free(new->cache.lruQueueNodes);
            DP(("free a old lru. %p\n", new->cache.lruQueueNodes));
        }
        return -1;
    }

    return 0;
}


static void
LruQueueBufferSplit(FontCache *cache, int bufsize)
{
    MemBlk *blk;
    unsigned char *buff;

    blk = cache->lruQueueDummyHead.lruNext;
    buff = cache->memPool;
    while (blk != &cache->lruQueueDummyHead) {
        blk->data = buff;
        blk = blk->lruNext;
        buff = buff + bufsize;
    }
}

static void
DestroyCache(FontCache *cache)
{
    if (cache->hashDir != NULL) {
        free(cache->hashDir);
        DP(("destory cache %p hash dir %p\n", cache, cache->hashDir));
    }
    if (cache->memPool != NULL) {
        DP(("destory cache %p memory pool %p\n", cache, cache->memPool));
        free(cache->memPool);
    }
    if (cache->lruQueueNodes) {
        DP(("destory cache %p LRU_QUEUE %p\n", cache, cache->lruQueueNodes));
        free(cache->lruQueueNodes);
    }
}

/* release a cache node from the global LRU cache queue.
   Free the memory and unlink the pointers. */
static void
ReleaseCache(CacheQueueNode *cache)
{
    /* Re-Link the double link queue */
    if (cache->prevCache != NULL && cache->nextCache != NULL) {
        cache->nextCache->prevCache = cache->prevCache;
        cache->prevCache->nextCache = cache->nextCache;
    }
    DestroyCache(&cache->cache);
}



/* allocate a new cache and append at the end of the global cache queue.
 * If global cache queue is full, we should kill the first node of the queue,
 * and we call this : Least-Recently-Used-Order
 * If global cache is not full, just allocate a new one.
 * the func return pointer to the cache if succeed, return NULL means failed. */
static CacheQueueNode *
GetCache(int nblock, int ndir)
{
    CacheQueueNode *temp;

    if (__mg_globalCache.nCache == __mg_globalCache.maxCache) {
        /* The Queue is Full!! in this case, we don't
           release any node, just return NULL */
        return NULL;
    }
    /* there is empty space in the queue
       we should allocate a new CacheQueueNode and
       allocate Memory-Pool for it */
    if ((temp = malloc(sizeof(CacheQueueNode))) == NULL) {
        return NULL;
    }
    DP(("New a cache node %p\n", temp));
    /* Set zero the new CacheQueueNode
       and link the head to itself */
    memset(temp, 0, sizeof(CacheQueueNode));
    temp->cache.lruQueueDummyHead.lruPrev = &(temp->cache.lruQueueDummyHead);
    temp->cache.lruQueueDummyHead.lruNext = &(temp->cache.lruQueueDummyHead);
    /* allocate Memory-Pool for new new CacheQueueNode */
    temp->cache.memPool = malloc(__mg_globalCache.cacheSize);
    DP(("allocate cache %p mem pool %p\n", temp, temp->cache.memPool));
    if (temp->cache.memPool == NULL) {
        free(temp);
        return NULL;
    }
    __mg_globalCache.nCache++;

    /* init hash directory, lru-queue
       the function should release the old hash directory
       and lru-queue and allocate new one.  */
    if (InitCache(temp, ndir, nblock) == -1) {
        /* init node failed. we must FREE has directory
           Memory-Poll and Lru-queue in follow function.
           And unlink the global cache queue if required */
        ReleaseCache(temp);
        free(temp);
        __mg_globalCache.nCache--;
        return NULL;
    }
    /* link the node at the end of global cache queue */
    AppendCache(temp);
    return temp;
}


/* Pick-off a block from the head of cache's
   lru-queue and also Hash queue(anywhere in hash q).
   Relink the lru queue and hash queue */
static MemBlk *
PickOffBlk(FontCache *cache)
{
    MemBlk *blk;

    if (cache->lruQueueDummyHead.lruNext ==
        &cache->lruQueueDummyHead) {
        return NULL;
    }
    /* get the head of the lru-queue
       it is a Least-Recently-Used-Order Node
       and we should re-link the queue */
    blk = cache->lruQueueDummyHead.lruNext;

    cache->lruQueueDummyHead.lruNext=
      (cache->lruQueueDummyHead.lruNext)->lruNext;
    (cache->lruQueueDummyHead.lruNext)->lruPrev =
      &cache->lruQueueDummyHead;
    /* we pick the block, and if the block already
       in a hash q, so we must re-link the hash q */
    if (blk->hashNext && blk->hashPrev) {
        blk->hashNext->hashPrev = blk->hashPrev;
        blk->hashPrev->hashNext = blk->hashNext;
    }
    return blk;
}

static void
AppendBlk(FontCache *cache, MemBlk *blk, int key)
{
    /* first, append the blcok at the end of the
       lru-queue. */
    blk->lruNext = &(cache->lruQueueDummyHead);
    blk->lruPrev = cache->lruQueueDummyHead.lruPrev;
    (cache->lruQueueDummyHead.lruPrev)->lruNext = blk;
    cache->lruQueueDummyHead.lruPrev = blk;

    /* Then, insert the block at the head of
       the hash queue which KEY==key */
    blk->hashNext = cache->hashDir[key].hashHead.hashNext;
    (cache->hashDir[key].hashHead.hashNext)->hashPrev = blk;
    cache->hashDir[key].hashHead.hashNext = blk;
    blk->hashPrev = &(cache->hashDir[key].hashHead);

}



/**********************************************************
 *                    extern functions                    *
 **********************************************************/
/* search a data in cache,
   hCache : which cache
   *cmpdata : your data pointer point to data for compare
   *size : if found the data, how long is the data(return val)
   reutrn = pointer to the data */
TTFCACHEINFO *
__mg_ttc_search(HCACHE hCache, Glyph32 gv, int *size)
{
    int key;
    CacheQueueNode *ca;
    MemBlk *blk;

    if (hCache == 0 || size == NULL) {
        return NULL;
    }
    ca = (CacheQueueNode *)(hCache);
    key = ca->cache.makeHashKeyFunc(gv);
    if (key > (ca->cache.nDir - 1)) {
        return NULL;
    }
    blk = ca->cache.hashDir[key].hashHead.hashNext;

    for ( ; blk != &(ca->cache.hashDir[key].hashHead);
          blk = blk->hashNext) {
        TTFCACHEINFO* tci = (TTFCACHEINFO*)blk->data;
        if (tci->glyph_code == gv) {
            /* If found the blk, we should pick off it
               and link it at the end of LRU-q */
            *size = blk->len;
            blk->lruNext->lruPrev = blk->lruPrev;
            blk->lruPrev->lruNext = blk->lruNext;
            blk->lruNext = &(ca->cache.lruQueueDummyHead);
            blk->lruPrev = ca->cache.lruQueueDummyHead.lruPrev;
            (ca->cache.lruQueueDummyHead.lruPrev)->lruNext = blk;
            ca->cache.lruQueueDummyHead.lruPrev = blk;
            return tci;
        }
    }
    return NULL;
}

/* add data into the specific cache
   hCache : which cache ,
   *data : your data pointer
   size : your data's bytes
   key : hash key of your data
*/
int
__mg_ttc_write(HCACHE hCache, TTFCACHEINFO *data, int size)
{
    CacheQueueNode *ca;
    MemBlk *blk;
    int key;

    if (hCache == 0 || data == NULL) {
        return -1;
    }
    ca = (CacheQueueNode *)(hCache);
    if (ca->cache.blkSize < size) {
        return -1;
    }
    key = ca->cache.makeHashKeyFunc(data->glyph_code);

    /* pick off a block from lru queue */
    if ((blk = PickOffBlk(&ca->cache)) == NULL) {
        return -1;
    }
    /* 1. copy the TTFCACHEINFO structure
       2. copy the BITMAP data by data->bitmap
       3. change the bitmap pointer */
    memcpy(blk->data, data, sizeof(TTFCACHEINFO));
    if (data->bitmap)
        memcpy((blk->data + sizeof(TTFCACHEINFO)),
                data->bitmap,
                (size - sizeof(TTFCACHEINFO)));
    blk->len = size;
    ((TTFCACHEINFO *)(blk->data))->bitmap =  blk->data + sizeof(TTFCACHEINFO);
    /* append the block to the end of lru-queue */
    AppendBlk(&ca->cache, blk, key);
    return 0;
}

/* create a cache for a font instance.
   nblk : how many block in the cache.
   blksize : each block's size
   ndir : how many hash entries in the cache
   makeHashKey : the function which make the hash key */
HCACHE
__mg_ttc_create(const char *df_name, int style, int size, int rotation,
        int nblk, int blksize, int ndir, MakeHashKeyFunc makeHashKey)
{
    CacheQueueNode *temp;
    if (df_name == NULL || makeHashKey == NULL) {
        return (HCACHE)(0);
    }
    if (nblk <= 0 || blksize <= 0 || size <= 0 || ndir <= 0) {
        return (HCACHE)(0);
    }

    if ((nblk * blksize) > __mg_globalCache.cacheSize) {
        return (HCACHE)(0);
    }
    if ((temp = GetCache(nblk, ndir)) == NULL) {
        return (HCACHE)(0);
    }

    memset(temp->cache.df_name, 0, LEN_UNIDEVFONT_NAME + 1);
    strncpy(temp->cache.df_name, df_name, LEN_UNIDEVFONT_NAME);
    temp->cache.style = style & FS_RENDER_MASK;
    temp->cache.fontsize = size;
    temp->cache.rotation = rotation;
    temp->cache.blkSize = blksize;
    temp->cache.nBlk = nblk;
    temp->cache.nDir = ndir;
    temp->cache.makeHashKeyFunc = makeHashKey;
    /* Cut the Big memory pool into some small block */
    LruQueueBufferSplit(&temp->cache, blksize);
    temp->cache.refers = 1;
    return (HCACHE)(temp);
}

void
__mg_ttc_refer(HCACHE hCache)
{
    CacheQueueNode *ca;
    if (hCache == 0) {
        return;
    }
    ca = (CacheQueueNode *)(hCache);
    ca->cache.refers++;
}

void
__mg_ttc_release(HCACHE hCache)
{
    CacheQueueNode *ca;
    if (hCache == 0) {
        return;
    }
    ca = (CacheQueueNode *)(hCache);
    ca->cache.refers--;
    DP(("cache %p -- %d\n", ca, ca->cache.refers));
    if (ca->cache.refers == 0) {
        /* It will re-link the global cache list in ReleaseCache() */
        ReleaseCache(ca);
        free(ca);
        DP(("Free cache==%p\n", ca));
        __mg_globalCache.nCache--;
    }
}

/* init cache system, create cache queue, and clear cache counter,
   if need use cache, should call this function first */
int
__mg_ttc_sys_init(int maxCache, int cacheSize)
{
    if (maxCache <= 0 || cacheSize <= 0) {
        return -1;
    }
    __mg_globalCache.queueDummyHead.prevCache = &__mg_globalCache.queueDummyHead;
    __mg_globalCache.queueDummyHead.nextCache = &__mg_globalCache.queueDummyHead;
    __mg_globalCache.maxCache = maxCache;
    __mg_globalCache.nCache = 0;
    __mg_globalCache.cacheSize = cacheSize;
    return 0;
}


void
__mg_ttc_sys_deinit(void)
{
    CacheQueueNode *q, *pfree;

    if (__mg_globalCache.nCache == 0) {
        /* it means just no cache in our system,
           no malloc at all, just set zero global val
           and return safe :) */
        memset(&__mg_globalCache, 0, sizeof(__mg_globalCache));
        return;
    }
    /* In this case, it means more than ONE cache in
       our system. we should release follow structures:
       a. every CacheQueueNode(s) in global cache queue
       b. every Memory-Pool in every cache
       c. every Hash-Directory in every cache
       d. every LRU queue nodes in every cache
       that's all, may be~~ :(  */
    q = __mg_globalCache.queueDummyHead.nextCache;

    while (q != &__mg_globalCache.queueDummyHead) {
        pfree = q;
        q = q->nextCache;
        DestroyCache(&pfree->cache);
        free(pfree);
        DP(("Free cache==%p\n", pfree));
    }
}

HCACHE
__mg_ttc_is_exist(const char *df_name, int style, int size, int rotation)
{
    CacheQueueNode *p;

    if (__mg_globalCache.nCache == 0) {
        return (HCACHE)0;
    }

    p = __mg_globalCache.queueDummyHead.nextCache;
    while (p != &__mg_globalCache.queueDummyHead) {
        if (strncmp(p->cache.df_name, df_name, LEN_UNIDEVFONT_NAME) == 0
                && p->cache.style == (style & FS_RENDER_MASK)
                && p->cache.fontsize == size
                && p->cache.rotation == rotation) {
            return (HCACHE) p;
        }

        p = p->nextCache;
    }

    return (HCACHE)0;
}

#if 0

static void
queue_traversal(LruQueueDummyHead *pQ)
{
    MemBlk *ptr;

    ptr = pQ->lruNext;
    for (; ptr != pQ; ptr = ptr->lruNext) {
        DP(("MemBlk = %p, MemBlk->prev = %p, "
            "MemBlk->next = %p, MemBlk->data : %d\n",
            ptr, ptr->lruPrev, ptr->lruNext, *(int *)(ptr->data)));
    }
}

static void
queue_traversal_reverse(LruQueueDummyHead *pQ)
{
    MemBlk *ptr;

    ptr = pQ->lruPrev;
    for ( ; ptr != pQ; ptr = ptr->lruPrev) {
        DP(("MemBlk = %p, MemBlk->prev = %p, "
            "MemBlk->next = %p, MemBlk->data : %d\n",
            ptr, ptr->lruPrev, ptr->lruNext, *(int *)(ptr->data)));
    }
}


void __mg_ttc_dump_all(void)
{
    CacheQueueNode* pc;
#if 0
    DP(("__mg_globalCache.maxCache = %d\n", __mg_globalCache.maxCache));
    DP(("__mg_globalCache.cacheSize = %d\n", __mg_globalCache.cacheSize));
    DP(("__mg_globalCache.nCache = %d\n", __mg_globalCache.nCache));

    DP(("__mg_globalCache.queueDummyHead.prevCache = %p\n",
           __mg_globalCache.queueDummyHead.prevCache));
    DP(("__mg_globalCache.queueDummyHead.nextCache = %p\n",
           __mg_globalCache.queueDummyHead.nextCache));
#endif
    pc = __mg_globalCache.queueDummyHead.nextCache;
    for ( ; pc != &__mg_globalCache.queueDummyHead; pc = pc->nextCache) {
        __mg_ttc_dump(&pc->cache);
        DP(("* * * * * * * * * * * * * * * * * * * * * * * * * * * *\n"));
    }
}

void __mg_ttc_dump(FontCache *cache)
{
    int i, j, k;
    MemBlk *ptr;
#if 0
    DP(("&cache = %p\n", cache));
    DP(("cache.df_name = %s\n", cache->df_name));
    DP(("cache.fontsize = %d\n", cache->fontsize));
    DP(("cache.blkSize = %d\n", cache->blkSize));
    DP(("cache.refers = %d\n", cache->refers));
    DP(("cache.nBlk = %d\n", cache->nBlk));
    DP(("cache.nDir = %d\n", cache->nDir));
    DP(("cache.memPool = %d, (HEX) = %p\n",
           cache->memPool, cache->memPool));
    DP(("cache.lruQueueNodes=%p\n", cache->lruQueueNodes));
    DP(("cache.hashDir = %p\n", cache->hashDir));
#endif
    DP(("&cache.lruQueueDummyHead=%p\n",
           &cache->lruQueueDummyHead));
    DP(("cache.lruQueueDummyHead.lruPrev=%p\n",
           cache->lruQueueDummyHead.lruPrev));
    DP(("cache.lruQueueDummyHead.lruNext=%p\n",
           cache->lruQueueDummyHead.lruNext));
    //queue_traversal(&cache->lruQueueDummyHead);
    //DP(("----\n"));
    //queue_traversal_reverse(&cache->lruQueueDummyHead);
#if 0
    for (i=0; i<cache->nDir; i++) {
        MemBlk *p;
        printf("hash[%d]:", i);
        p = (cache->hashDir)[i].hashHead.hashNext;
        while(p != &(cache->hashDir[i].hashHead)) {
            printf("-->%d(%d)", *(unsigned short *)(p->data), p->len);
            p = p->hashNext;
        }
        printf("\n");
    }
#endif
    //printf("LRU: ");

    ptr = cache->lruQueueDummyHead.lruNext;
    for ( ; ptr != &cache->lruQueueDummyHead; ptr = ptr->lruNext) {
        DP(("-->%p(len:%d, data:%d)",
            ptr, ptr->len, *(unsigned short *)(ptr->data)));
    }
    //printf("\n");
}

void
__mg_ttc_dump_cacheinfo(TTFCACHEINFO *ca)
{
    DP(("Dump TTFCACHEINFO %p...\n", ca));
    DP(("cacheInfo->glyph_code = %d\n", ca->glyph_code));

    DP(("cacheInfo->cur_xmin = %d, cacheInfo->cur_ymin = %d\n",
        ca->cur_xmin, ca->cur_ymin));
    DP(("cacheInfo->width = %d, cacheInfo->height = %d\n",
        ca->width, ca->height));
    DP(("cacheInfo->advance = %d\n", ca->advance));
    DP(("cacheInfo->cur_bbox = %d, %d, %d, %d\n",
        ca->bbox.xMin, ca->bbox.yMin,
        ca->bbox.xMax, ca->bbox.yMax));

    DP(("\n"));
}

void
__mg_ttc_dump_instance(TTFINSTANCEINFO *in)
{
    DP(("Dump TTFINSTANCEINFO %p...\n", in));
    DP(("Instance->cur_glyph_code = %d\n", in->cur_glyph_code));
    DP(("Instance->outline(addr) = %p\n", &in->cur_outline));
    DP(("Instance->cur_xmin = %d, Instance->cur_ymin = %d\n",
        in->cur_xmin, in->cur_ymin));
    DP(("Instance->cur_width = %d, Instance->cur_height = %d\n",
        in->cur_width, in->cur_height));
    DP(("Instance->advance = %d\n", in->cur_advance));
    DP(("Instance->cur_bbox = %d, %d, %d, %d\n",
        in->cur_bbox.xMin, in->cur_bbox.yMin,
        in->cur_bbox.xMax, in->cur_bbox.yMax));

    DP(("\n"));
}

#endif /* 0 */

#endif /* defined(_MGFONT_FT2) && defined(_MGFONT_TTF_CACHE) */

