// XFontImp.cc for FbTk fluxbox toolkit
// Copyright (c) 2002 Henrik Kinnunen (fluxgen@linuxmail.org)
// 
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

// $Id: XFontImp.cc,v 1.5 2002/12/09 22:12:56 fluxgen Exp $

#include "XFontImp.hh"
#include "App.hh"

#include <X11/Xutil.h>

#include <iostream>
#include <new>
#include <cstdio>
using namespace std;

namespace FbTk {

XFontImp::XFontImp(const char *fontname):m_rotfont(0), m_fontstruct(0),
                                         m_angle(0), m_rotate(true) {
    if (fontname != 0)
        load(fontname);	
}

XFontImp::~XFontImp() {
    if (m_fontstruct != 0)
        XFreeFont(App::instance()->display(), m_fontstruct);
    if (m_rotfont != 0)
        freeRotFont();
}

int XFontImp::ascent() const {
    if (m_fontstruct == 0)
        return 0;
    if (m_rotfont != 0)
        return m_rotfont->max_ascent;
		
    return m_fontstruct->ascent;
}

bool XFontImp::load(const std::string &fontname) {
    XFontStruct *font = XLoadQueryFont(App::instance()->display(), fontname.c_str());
    if (font == 0)
        return false;
    if (m_fontstruct != 0) // free old font struct, if any
        XFreeFont(App::instance()->display(), m_fontstruct);

    m_fontstruct = font; //set new font

    if (m_rotfont != 0) {
        freeRotFont(); // free old rotated font
        rotate(m_angle); // allocate new rotated font and rotate it to old angle
    }

    return true;
}

void XFontImp::drawText(Drawable w, int screen, GC gc, const char *text, size_t len, int x, int y) const {
    if (m_fontstruct == 0)
        return;
    // use roated font functions?
    if (m_rotfont != 0 && m_rotate) {
        drawRotText(w, screen, gc, text, len, x, y);
        return;
    }

    Display *disp = App::instance()->display();
    XSetFont(disp, gc, m_fontstruct->fid);
    XDrawString(disp, w, gc, x, y, text, len);
}

unsigned int XFontImp::textWidth(const char * const text, unsigned int size) const {
    if (text == 0)
        return 0;
    if (m_fontstruct == 0)
        return 0;
    // check rotated font?
    if (m_rotfont != 0)
        return rotTextWidth(text, size);

    return XTextWidth(m_fontstruct, text, size);
}

unsigned int XFontImp::height() const {
    if (m_fontstruct == 0)
        return 0;

    return m_fontstruct->ascent + m_fontstruct->descent;
}

void XFontImp::rotate(float angle) {
    //we must have a font loaded before we rotate
    if (m_fontstruct == 0 || m_fontstruct->per_char == 0)
        return;

    if (m_rotfont != 0)
        freeRotFont();

    // no need for rotating, use regular font
    if (angle == 0) {
        m_angle = 0;
        return;
    }

    //get positive angle
    while (angle < 0)
        angle += 360;

    m_angle = angle;

    char val;
    XImage *I1, *I2;
    // X system default vars
    Display *dpy = App::instance()->display();
    Window rootwin = DefaultRootWindow(dpy);
    int screen = DefaultScreen(dpy);

    GC font_gc;
    char text[3];
    int ichar, i, j, index, boxlen = 60;
    int vert_w, vert_h, vert_len, bit_w, bit_h, bit_len;
    int min_char, max_char;
    unsigned char *vertdata, *bitdata;
    int ascent, descent, lbearing, rbearing;

    // get nearest vertical or horizontal direction 
    int dir = (int)((angle+45.0)/90.0)%4;

    if (dir == 0) // no rotation
        return;

    // create the depth 1 canvas bitmap
    Pixmap canvas = XCreatePixmap(dpy, rootwin, boxlen, boxlen, 1);
 
    // create graphic context for our canvas
    font_gc = XCreateGC(dpy, canvas, 0, 0);

    XSetBackground(dpy, font_gc, None);

    XSetFont(dpy, font_gc, m_fontstruct->fid);

    // allocate space for rotated font
    m_rotfont = new(nothrow) XRotFontStruct;

    if (m_rotfont == 0) {
        cerr<<"RotFont: out of memory"<<endl;
        return;
    }
   
    // determine which characters are defined in font
    min_char = m_fontstruct->min_char_or_byte2; 
    max_char = m_fontstruct->max_char_or_byte2;
 
    // we only want printable chars
    if (min_char<32)
        min_char = 32;
    if (max_char>126)
        max_char = 126;
     
    /* some overall font data ... */
    m_rotfont->dir = dir;
    m_rotfont->min_char = min_char;
    m_rotfont->max_char = max_char;
    m_rotfont->max_ascent = m_fontstruct->max_bounds.ascent;
    m_rotfont->max_descent = m_fontstruct->max_bounds.descent;   
    m_rotfont->height = m_rotfont->max_ascent + m_rotfont->max_descent;

    // font needs rotation
    // loop through each character
    for (ichar = min_char; ichar <= max_char; ichar++) {
        index = ichar - m_fontstruct->min_char_or_byte2;

        // per char dimensions ...
        ascent = m_rotfont->per_char[ichar-32].ascent = m_fontstruct->per_char[index].ascent;
        descent =  m_rotfont->per_char[ichar-32].descent = m_fontstruct->per_char[index].descent;
        lbearing = m_rotfont->per_char[ichar-32].lbearing = m_fontstruct->per_char[index].lbearing;
        rbearing = m_rotfont->per_char[ichar-32].rbearing = m_fontstruct->per_char[index].rbearing;
        m_rotfont->per_char[ichar-32].width = m_fontstruct->per_char[index].width;

        // some space chars have zero body, but a bitmap can't have
        if (!ascent && !descent)   
            ascent = m_rotfont->per_char[ichar-32].ascent =   1;
        if (!lbearing && !rbearing) 
            rbearing = m_rotfont->per_char[ichar-32].rbearing = 1;

        // glyph width and height when vertical
        vert_w = rbearing - lbearing;
        vert_h = ascent + descent;

        // width in bytes
        vert_len = (vert_w-1)/8+1;   

        XSetForeground(dpy, font_gc, None);
        XFillRectangle(dpy, canvas, font_gc, 0, 0, boxlen, boxlen);

        // draw the character centre top right on canvas
        sprintf(text, "%c", ichar);
        XSetForeground(dpy, font_gc, 1);
        XDrawImageString(dpy, canvas, font_gc, boxlen/2 - lbearing,
                         boxlen/2 - descent, text, 1);

        // reserve memory for first XImage
        vertdata = (unsigned char *) malloc((unsigned)(vert_len*vert_h));

        /* create the XImage ... */
        I1 = XCreateImage(dpy, DefaultVisual(dpy, screen), 1, XYBitmap,
                          0, (char *)vertdata, vert_w, vert_h, 8, 0);

        if (I1 == None) {				
            cerr<<"RotFont: Cant create ximage."<<endl;
            delete m_rotfont;
            m_rotfont = 0;			
            return;
        }

        I1->byte_order = I1->bitmap_bit_order = MSBFirst;

        /* extract character from canvas ... */
        XGetSubImage(dpy, canvas, boxlen/2, boxlen/2 - vert_h,
                     vert_w, vert_h, 1, XYPixmap, I1, 0, 0);
        I1->format = XYBitmap; 

        /* width, height of rotated character ... */
        if (dir == 2) { 
            bit_w = vert_w;
            bit_h = vert_h; 
        } else {
            bit_w = vert_h;
            bit_h = vert_w; 
        }

        /* width in bytes ... */
        bit_len = (bit_w-1)/8 + 1;

        m_rotfont->per_char[ichar-32].glyph.bit_w = bit_w;
        m_rotfont->per_char[ichar-32].glyph.bit_h = bit_h;

        /* reserve memory for the rotated image ... */
        bitdata = (unsigned char *)calloc((unsigned)(bit_h * bit_len), 1);

        /* create the image ... */
        I2 = XCreateImage(dpy, DefaultVisual(dpy, screen), 1, XYBitmap, 0,
                          (char *)bitdata, bit_w, bit_h, 8, 0); 

        if (I2 == None) {
            cerr<<"XFontImp: Cant create ximage!"<<endl;
            delete m_rotfont;
            m_rotfont = 0;
            return;
        }

        I2->byte_order = I2->bitmap_bit_order = MSBFirst;

        /* map vertical data to rotated character ... */
        for (j = 0; j < bit_h; j++) {
            for (i = 0; i < bit_w; i++) {
				/* map bits ... */
                if (dir == 1) {
                    val = vertdata[i*vert_len + (vert_w-j-1)/8] &
                        (128>>((vert_w-j-1)%8));
                } else if (dir == 2) {
                    val = vertdata[(vert_h-j-1)*vert_len +
                                  (vert_w-i-1)/8] & (128>>((vert_w-i-1)%8));
                } else {
                    val = vertdata[(vert_h-i-1)*vert_len + j/8] & 
                        (128>>(j%8));
                } 
                if (val) {
                    bitdata[j*bit_len + i/8] = bitdata[j*bit_len + i/8] |
                        (128>>(i%8));
                }
            }
        }

        // create this character's bitmap 
        m_rotfont->per_char[ichar-32].glyph.bm = 
            XCreatePixmap(dpy, rootwin, bit_w, bit_h, 1);
 
        // put the image into the bitmap 
        XPutImage(dpy, m_rotfont->per_char[ichar-32].glyph.bm, 
                  font_gc, I2, 0, 0, 0, 0, bit_w, bit_h);

        // free the image and data
        XDestroyImage(I1);
        XDestroyImage(I2);
    }

    /* free pixmap and GC ... */
    XFreePixmap(dpy, canvas);
    XFreeGC(dpy, font_gc);

}

void XFontImp::freeRotFont() {
    if (m_rotfont == 0)
        return;
    // loop through each character and free its pixmap
    for (int ichar = m_rotfont->min_char - 32; 
         ichar <= m_rotfont->max_char - 32; ++ichar) {
        XFreePixmap(App::instance()->display(), m_rotfont->per_char[ichar].glyph.bm);
    }

    delete m_rotfont;
    m_rotfont = 0;
}

void XFontImp::drawRotText(Drawable w, int screen, GC gc, const char *text, size_t len, int x, int y) const {            

    Display *dpy = App::instance()->display();
    static GC my_gc = 0;
    int xp, yp, dir, ichar;

    if (text == NULL || len<1)
        return;

    dir = m_rotfont->dir;
    if (my_gc == 0)
        my_gc = XCreateGC(dpy, w, 0, 0);

    XCopyGC(dpy, gc, GCForeground|GCBackground, my_gc);

    // vertical or upside down

    XSetFillStyle(dpy, my_gc, FillStippled);

    // loop through each character in texting
    for (size_t i = 0; i<len; i++) {
        ichar = text[i]-32;

        // make sure it's a printing character
        if (ichar >= 0 && ichar<95) {
            // suitable offset
            if (dir == 1) {
                xp = x-m_rotfont->per_char[ichar].ascent;
                yp = y-m_rotfont->per_char[ichar].rbearing; 
            } else if (dir == 2) {
                xp = x-m_rotfont->per_char[ichar].rbearing;
                yp = y-m_rotfont->per_char[ichar].descent+1; 
            } else {
                xp = x-m_rotfont->per_char[ichar].descent+1;  
                yp = y+m_rotfont->per_char[ichar].lbearing; 
            }
                   
            // draw the glyph
            XSetStipple(dpy, my_gc, m_rotfont->per_char[ichar].glyph.bm);
    
            XSetTSOrigin(dpy, my_gc, xp, yp);
      
            XFillRectangle(dpy, w, my_gc, xp, yp,
                           m_rotfont->per_char[ichar].glyph.bit_w,
                           m_rotfont->per_char[ichar].glyph.bit_h);
    
            // advance position
            if (dir == 1)
                y -= m_rotfont->per_char[ichar].width;
            else if (dir == 2)
                x -= m_rotfont->per_char[ichar].width;
            else 
                y += m_rotfont->per_char[ichar].width;
        }
    }
}


unsigned int XFontImp::rotTextWidth(const char * const text, unsigned int size) const {

    if (text == 0)
        return 0;

    unsigned int width = 0;	
    for (size_t i = 0; i<size; i++) {
        int ichar = text[i] - 32;  
        // make sure it's a printing character
        if (ichar >= 0 && ichar < 95) 
            width += m_rotfont->per_char[ichar].width;
    }

    return width;
}

};