/* aguixfont.cc
 * This file belongs to Worker, a filemanager for UNIX/X11.
 * Copyright (C) 2001-2008 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * 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 St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "aguixfont.hh"
#include "aguix.h"
#include "utf8.hh"
#include "drawablecont.hh"

AGUIXFont::AGUIXFont(AGUIX *parent)
{
  name = "";
  gc=0;
  font=NULL;
  aguix=parent;
  _fontset = NULL;
  extents = NULL;
  _nmissing = 0;

#ifdef HAVE_XFT
  _xftfont = NULL;
#endif
}

AGUIXFont::~AGUIXFont()
{
  removeFont();
}

void AGUIXFont::removeFont()
{
  if(gc!=0) {
    XFreeGC(aguix->getDisplay(),gc);
    gc=0;
  }
  if(font!=NULL) {
    XFreeFont(aguix->getDisplay(),font);
    font=NULL;
  }
  if ( _fontset != NULL ) {
    XFreeFontSet( aguix->getDisplay(), _fontset );
    _fontset = NULL;
    extents = NULL;
  }
#ifdef HAVE_XFT
  if ( _xftfont != NULL ) {
    XftFontClose( aguix->getDisplay(), _xftfont );
    _xftfont = NULL;
  }
#endif
  name = "";
}

int AGUIXFont::setFont( const char *fontname )
{
  Display *dsp=aguix->getDisplay();
  int scr=aguix->getScreen();
  unsigned long white=WhitePixel(dsp,scr);
  unsigned long black=BlackPixel(dsp,scr);

  if ( fontname == NULL ) return 1;

  // empty fontname isn't a valid font anyway
  if ( strlen( fontname ) < 1 ) return 1;

  removeFont();

#ifdef HAVE_XFT
  //_xftfont = XftFontOpenXlfd( dsp, scr, fontname );
  //_xftfont = XftFontOpenName( dsp, scr, "sans-12" );
  _xftfont = XftFontOpenName( dsp, scr, fontname );
  if ( _xftfont == NULL ) {
    return 1;
  }

  if ( _xftfont->ascent + _xftfont->descent < 1 ) {
      fprintf( stderr, "Error: Font of zero height\n" );
      XftFontClose( dsp, _xftfont );
      _xftfont = NULL;
      return 1;
  }
#else
#  ifndef DISABLE_UTF8_SUPPORT
  if ( UTF8::getUTF8Mode() != UTF8::UTF8_DISABLED ) {
    char **missing = NULL;

    /* There's a bug in XCreateFontSet: The function will free the given
     * fontname if it's empty and perhaps in other circumstances too
     * Perhaps it would be a acceptable workaround to create a copy
     * and don't free it. It would be a mem leak but as usually there
     * are only a few fonts for the application it wouldn't hurt much
     */
    //TODO is this still true?
    _fontset = XCreateFontSet( dsp, 
                               fontname,
                               &missing, &_nmissing, NULL );
    /*"-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso8859-1"
      "-misc-fixed-medium-r-semicondensed--*-*-*-*-c-*-*-*"*/

    if ( _fontset == NULL ) {
        XFreeStringList( missing );
        return 1;
    }
    
#ifdef DEBUG
    if ( _nmissing > 0 ) {
      printf( "Worker Warning: %d charsets are missing for font %s!\n", _nmissing, fontname );
      for ( int fi = 0 ; fi < _nmissing; fi++ ) {
        printf( "Charset %s missing\n", missing[fi] );
      }
    }
#endif
    XFreeStringList( missing );
    
    extents = XExtentsOfFontSet( _fontset );
  } else {
#  endif
    font = XLoadQueryFont( dsp, fontname );
    if ( font == NULL ) {
      return 1;
    }
#  ifndef DISABLE_UTF8_SUPPORT
  }
#  endif
#endif

  gc=XCreateGC(dsp,RootWindow(dsp,scr),0,0);
  XGCValues gc_values;
  gc_values.graphics_exposures=False;
  gc_values.foreground=black;
  gc_values.background=white;
  gc_values.line_width=0;
  gc_values.cap_style=CapButt;

  unsigned long gc_valuemask=GCGraphicsExposures|GCForeground|GCBackground|GCLineWidth|GCCapStyle;

  if ( font != NULL ) {
    gc_values.font=font->fid;
    gc_valuemask |= GCFont;
  }

  XChangeGC(dsp,gc,gc_valuemask,&gc_values);

  name = fontname;

#ifdef HAVE_XFT
  charHeight = _xftfont->ascent + _xftfont->descent;
#else
  if ( font != NULL ) {
    charHeight = font->ascent + font->descent;
  } else {
    charHeight = extents->max_logical_extent.height;
    //charHeight = extents->max_ink_extent.height;
  }
#endif

  return 0;
}

const char *AGUIXFont::getName() const
{
  return name.c_str();
}

GC AGUIXFont::getGC() const
{
  return gc;
}

int AGUIXFont::getCharHeight() const
{
  return charHeight;
}

int AGUIXFont::getBaseline() const
{
#ifdef HAVE_XFT
  return _xftfont->ascent;
#else
  if ( _fontset == NULL ) {
    return font->ascent;
  } else {
    return -extents->max_logical_extent.y;
    //return -extents->max_ink_extent.y;
  }
#endif
}

#ifndef HAVE_XFT
int AGUIXFont::textWidth( const char *str, int count ) const
{
    int w = 0;

#  ifndef DISABLE_UTF8_SUPPORT
    if ( _fontset == NULL ) {
#  endif
        w = XTextWidth( font, str, count );
#  ifndef DISABLE_UTF8_SUPPORT
    } else {
        w = XmbTextEscapement( _fontset, str, count );
    }
#  endif
  
    return w;
}
#endif

#define INITIAL_CHUNK_LENGTH 120

#ifdef HAVE_XFT
int AGUIXFont::textWidth( const char *str, int count ) const
{
    int w = 0;

    if ( count == 0 ) return 0;

    XGlyphInfo extents = { 0 };

    // since extents overflows if str is too long. But splitting UTF8
    // strings might not be correct for all characters so try to find
    // the largest chunk we can use without splitting. Use a simple
    // dynamic limit. Try to increase as far as possible until it
    // overflows and then reduce the limit each time it overflows. It
    // will take quite a lot of steps but is simple and once it has a
    // stable limit the speed will be ok.
    //
    // It would be faster to use a binary search for best performance
    // but it will make it more complicated too as very long strings
    // might overflow without notice (overflowing multiple times into
    // a positive value). And it is ok right now since the adjustment
    // is only done once.

    static int chunk_limit = INITIAL_CHUNK_LENGTH;
    static int max_chunk_limit = 0;

    for (;;) {
        if ( max_chunk_limit == 0 && count > chunk_limit ) {
            chunk_limit++;
        }

#  ifndef DISABLE_UTF8_SUPPORT
        if ( UTF8::getUTF8Mode() != UTF8::UTF8_DISABLED && UTF8::getCurrentEncoding() == UTF8::ENCODING_UTF8 ) {
            if ( count > chunk_limit ) {
                int new_pos = count / 2;

                new_pos = UTF8::movePosBackToValidPos( str, new_pos );

                if ( new_pos <= 0 || new_pos >= count ) {
                    // not supposed to happen
                    return 0;
                }

                int w1 = textWidth( str, new_pos );
                int w2 = textWidth( str + new_pos, count - new_pos );

                if ( w1 < 0 || w2 < 0 ) {
                    // there is an overflow so reduce limit and try again.
                    chunk_limit--;
                    max_chunk_limit = chunk_limit;

                    continue;
                }

                return w1 + w2;
            } else {
                XftTextExtentsUtf8( aguix->getDisplay(),
                                    _xftfont,
                                    (XftChar8*)str,
                                    count,
                                    &extents );
                break;
            }
        } else {
#  endif
            if ( count > chunk_limit ) {
                int new_pos = count / 2;

                int w1 = textWidth( str, new_pos );
                int w2 = textWidth( str + new_pos, count - new_pos );

                if ( w1 < 0 || w2 < 0 ) {
                    // there is an overflow so reduce limit and try again.
                    chunk_limit--;
                    max_chunk_limit = chunk_limit;

                    continue;
                }

                return w1 + w2;
            } else {
                XftTextExtents8( aguix->getDisplay(),
                                 _xftfont,
                                 (XftChar8*)str,
                                 count,
                                 &extents );
                break;
            }
#  ifndef DISABLE_UTF8_SUPPORT
        }
#  endif
    }

    w = extents.xOff;
  
    return w;
}
#endif

void AGUIXFont::drawText( DrawableCont &dc, const char *text, int x, int y, const AGUIXColor &color )
{
#ifdef HAVE_XFT
  XftDraw *xftdraw = dc.getXftDraw();
  if ( xftdraw == NULL ) return;

#  ifndef DISABLE_UTF8_SUPPORT
  if ( UTF8::getUTF8Mode() != UTF8::UTF8_DISABLED && UTF8::getCurrentEncoding() == UTF8::ENCODING_UTF8 ) {
    XftDrawStringUtf8( xftdraw,
                       aguix->getColBufEntry( color ),
                       _xftfont,
                       x,
                       y + getBaseline(),
                       (XftChar8*)text,
                       strlen( text ) );
  } else {
#  endif
    XftDrawString8( xftdraw,
                    aguix->getColBufEntry( color ),
                    _xftfont,
                    x,
                    y + getBaseline(),
                    (XftChar8*)text,
                    strlen( text ) );
#  ifndef DISABLE_UTF8_SUPPORT
  }
#  endif
#else
  aguix->setFG( gc, color );

#  ifndef DISABLE_UTF8_SUPPORT
  if ( _fontset == NULL ) {
#  endif
    XDrawString( aguix->getDisplay(),
                 dc.getDrawable(),
                 gc,
                 x,
                 y + getBaseline(),
                 text,
                 strlen( text ) );
#  ifndef DISABLE_UTF8_SUPPORT
  } else {
    XmbDrawString( aguix->getDisplay(),
                   dc.getDrawable(),
                   _fontset,
                   gc,
                   x,
                   y + getBaseline(),
                   text,
                   strlen( text ) );
  }
#  endif
#endif
}

int AGUIXFont::getNumberOfMissingCharsets() const
{
  return _nmissing;
}

void AGUIXFont::clip( int x, int y, int width, int height )
{
#ifndef HAVE_XFT
  XRectangle clip_rect;
  
  clip_rect.x = x;
  clip_rect.y = y;
  clip_rect.width = width;
  clip_rect.height = height;
  
  XSetClipRectangles( aguix->getDisplay(), getGC(), 0, 0, &clip_rect, 1, Unsorted );
#endif
}

void AGUIXFont::unclip()
{
#ifndef HAVE_XFT
  XSetClipMask( aguix->getDisplay(), getGC(), None );
#endif
}
