/*******************************************************

   CoolReader Engine

   crskin.cpp: skinning file support

   (c) Vadim Lopatin, 2000-2008
   This source code is distributed under the terms of
   GNU General Public License
   See LICENSE file for details

*******************************************************/

#include <stdlib.h>
#include "../include/crskin.h"
#include "../include/lvstsheet.h"
#include "../include/crtrace.h"

// uncomment to trace skin XML access errors / not found elements
//#define TRACE_SKIN_ERRORS


class RecursionLimit
{
static int counter;
public:
    bool test( int limit = 15 ) { return counter < limit; }
    RecursionLimit() { counter++; }
    ~RecursionLimit() { counter--; }
};
int RecursionLimit::counter = 0;


/// decodes skin percent
int fromSkinPercent( int x, int fullx )
{
    if ( x>0 && (x & SKIN_COORD_PERCENT_FLAG) ) {
        x ^= SKIN_COORD_PERCENT_FLAG;
        return x * fullx / 10000;
    } else {
        if ( x<0 ) {
            if ( !(x & SKIN_COORD_PERCENT_FLAG) ) {
                x ^= SKIN_COORD_PERCENT_FLAG;
                x = 10000-x;
                return x * fullx / 10000;
            }
            return fullx + x;
        }
        return x;
    }
}

/// decodes skin percent point to pixels (fullx is value corresponding to 100%)
lvPoint fromSkinPercent( lvPoint pt, lvPoint fullpt )
{
    lvPoint res;
    res.x = fromSkinPercent( pt.x, fullpt.x );
    res.y = fromSkinPercent( pt.y, fullpt.y );
    return res;
}

/// encodes percent value*100 (0..10000), to store in skin, from string like "75%" or "10"
int toSkinPercent( const lString32 & value, int defValue, bool * res )
{
    // "75%" format - in percent
    int p = value.pos("%");
    int pvalue;
    if ( p>0 ) {
        if ( value.substr(0, p).atoi(pvalue) ) {
            if ( res )
                *res = true;
            return toSkinPercent(pvalue*100);
        }
    }
    // "75px" format - in pixels
    p = value.pos("px");
    if ( p>0 ) {
        if ( value.substr(0, p).atoi(pvalue) ) {
            if ( res )
                *res = true;
            return pvalue;
        }
    }
    // simple "75" format
    if ( value.atoi(pvalue) ) {
        if ( res )
            *res = true;
        return pvalue;
    }
    return defValue;
}

CRPageSkinRef CRPageSkinList::findByName( const lString32 & name )
{
    for ( int i=0; i<length(); i++ ) {
        if ( get(i)->getName()==name )
            return get(i);
    }
    return CRPageSkinRef();
}

CRPageSkin::CRPageSkin()
: _scrollSkin(new CRRectSkin())
, _leftPageSkin(new CRRectSkin())
, _rightPageSkin(new CRRectSkin())
, _singlePageSkin(new CRRectSkin())
, _name("Default")
{

}

CRRectSkinRef CRPageSkin::getSkin( page_skin_type_t type )
{
    switch ( type ) {
    case PAGE_SKIN_SCROLL:
        return _scrollSkin;
    case PAGE_SKIN_LEFT_PAGE:
        return _leftPageSkin;
    case PAGE_SKIN_RIGHT_PAGE:
        return _rightPageSkin;
    case PAGE_SKIN_SINGLE_PAGE:
        return _singlePageSkin;
    default:
        return _scrollSkin;
    }
}


CRIconSkin::CRIconSkin()
: _bgcolor(0xFF000000) // transparent
, _hTransform(IMG_TRANSFORM_SPLIT)
, _vTransform(IMG_TRANSFORM_SPLIT)
, _splitPoint(-1, -1)
, _pos(0, 0)
, _size(toSkinPercent( 10000 ), toSkinPercent( 10000 )) // 100% x 100%
, _align(SKIN_VALIGN_TOP|SKIN_HALIGN_LEFT) // relative to top left
{
}

bool CRRectSkin::getRect( lvRect & rc, const lvRect & baseRect )
{
    rc = baseRect;
    lvPoint pos( fromSkinPercent( _pos.x, rc.width()),
                 fromSkinPercent( _pos.y, rc.height()) );
    lvPoint sz( fromSkinPercent( _size.x, rc.width()),
                fromSkinPercent( _size.y, rc.height()) );

    // left top corner -> origin point
    if ( getHAlign()==SKIN_HALIGN_RIGHT )
        pos.x = pos.x + sz.x;
    else if ( getHAlign()==SKIN_HALIGN_CENTER ) {
        pos.x = pos.x + sz.x / 2;
    }
    if ( getVAlign()==SKIN_VALIGN_BOTTOM )
        pos.y = pos.y + sz.y;
    else if ( getVAlign()==SKIN_VALIGN_CENTER ) {
        pos.y = pos.y + sz.y/2;
    }

    // apply size constraints
    if ( _minsize.x>0 && sz.x < _minsize.x )
        sz.x = _minsize.x;
    if ( _minsize.y>0 && sz.y < _minsize.y )
        sz.y = _minsize.y;
    if ( _maxsize.x>0 && sz.x > _maxsize.x )
        sz.x = _maxsize.x;
    if ( _maxsize.y>0 && sz.y > _maxsize.y )
        sz.y = _maxsize.y;

    // origin -> left top corner
    if ( getHAlign()==SKIN_HALIGN_RIGHT )
        pos.x = pos.x - sz.x;
    else if ( getHAlign()==SKIN_HALIGN_CENTER ) {
        pos.x = pos.x - sz.x / 2;
    }
    if ( getVAlign()==SKIN_VALIGN_BOTTOM )
        pos.y = pos.y - sz.y;
    else if ( getVAlign()==SKIN_VALIGN_CENTER ) {
        pos.y = pos.y - sz.y/2;
    }

    pos.x += baseRect.left;
    pos.y += baseRect.top;
    rc.left = pos.x;
    rc.top = pos.y;
    rc.right = pos.x + sz.x;
    rc.bottom = pos.y + sz.y;
    return true;
}

void CRIconSkin::draw( LVDrawBuf & buf, const lvRect & rc )
{
    int dx = _image.isNull() ? 0 : _image->GetWidth();
    int dy = _image.isNull() ? 0 : _image->GetHeight();
    lvRect rc2(rc);
    rc2.left = rc.left + fromSkinPercent( _pos.x, rc.width() );
    rc2.top = rc.top + fromSkinPercent( _pos.y, rc.height() );
    rc2.right = rc2.left + fromSkinPercent( _size.x, rc.width() );
    rc2.bottom = rc2.top + fromSkinPercent( _size.y, rc.height() );
    if ( _hTransform==IMG_TRANSFORM_NONE ) {
        int ddx = rc2.width()-dx;
        if ( getHAlign()==SKIN_HALIGN_RIGHT )
            rc2.left = rc2.right - dx;
        else if ( getHAlign()==SKIN_HALIGN_CENTER ) {
            rc2.left += ddx/2;
            rc2.right = rc2.left + dx;
        } else
            rc2.right = rc2.left + dx;
    }
    if ( _vTransform==IMG_TRANSFORM_NONE ) {
        int ddy = rc2.height()-dy;
        if ( getVAlign()==SKIN_VALIGN_BOTTOM )
            rc2.top = rc2.bottom - dy;
        else if ( getVAlign()==SKIN_VALIGN_CENTER ) {
            rc2.top += ddy/2;
            rc2.bottom = rc2.top + dy;
        } else
            rc2.bottom = rc2.top + dy;
    }
    if ( _image.isNull() ) {
        if ( ((_bgcolor>>24)&255) != 255 )
            buf.FillRect( rc2, _bgcolor );
    } else {
        LVImageSourceRef img = LVCreateStretchFilledTransform( _image,
            rc2.width(), rc2.height(), _hTransform, _vTransform, _splitPoint.x, _splitPoint.y );
        LVDrawStateSaver saver(buf);
		lvRect oldClip;
		buf.GetClipRect(&oldClip);
		if (oldClip.isEmpty())
			buf.SetClipRect(&rc);
		else if (oldClip.intersect(rc))
			buf.SetClipRect(&oldClip);
		else
			return;
        buf.Draw( img, rc2.left, rc2.top, rc2.width(), rc2.height(), false );
    }
}

void CRIconList::draw( LVDrawBuf & buf, const lvRect & rc )
{
    //CRLog::trace("enter CRIconList::draw(%d images)", _list.length());
    for ( int i=0; i<_list.length(); i++ )
        _list[i]->draw( buf, rc );
    //CRLog::trace("exit CRIconList::draw()");
}

/// retuns path to base definition, if attribute base="#nodeid" is specified for element of path
lString32 CRSkinContainer::getBasePath( const lChar32 * path )
{
    lString32 res;
    ldomXPointer p = getXPointer( lString32( path ) );
    if ( !p )
        return res;
    if ( !p.getNode()->isElement() )
        return res;
    lString32 value = p.getNode()->getAttributeValue("base");
    if (value.empty() || value[0] != '#')
        return res;
    res = pathById( value.c_str() + 1 );
    crtrace log;
    log << "CRSkinContainer::getBasePath( " << lString32( path ) << " ) = " << res;
    return res;
}

/// skin file support
class CRSkinImpl : public CRSkinContainer
{
protected:
    LVContainerRef _container;
    LVAutoPtr<ldomDocument> _doc;
    LVCacheMap<lString32,LVImageSourceRef> _imageCache;
    LVCacheMap<lString32,CRRectSkinRef> _rectCache;
    LVCacheMap<lString32,CRScrollSkinRef> _scrollCache;
    LVCacheMap<lString32,CRWindowSkinRef> _windowCache;
    LVCacheMap<lString32,CRMenuSkinRef> _menuCache;
    LVCacheMap<lString32,CRPageSkinRef> _pageCache;
    LVCacheMap<lString32,CRToolBarSkinRef> _toolbarCache;
    CRPageSkinListRef _pageSkinList;
public:
    /// returns scroll skin by path or #id
    virtual CRScrollSkinRef getScrollSkin( const lChar32 * path );
    /// returns rect skin by path or #id
    virtual CRRectSkinRef getRectSkin( const lChar32 * path );
    /// returns window skin by path or #id
    virtual CRWindowSkinRef getWindowSkin( const lChar32 * path );
    /// returns menu skin by path or #id
    virtual CRMenuSkinRef getMenuSkin( const lChar32 * path );
    /// returns book page skin by path or #id
    virtual CRPageSkinRef getPageSkin( const lChar32 * path );
    /// returns book page skin list
    virtual CRPageSkinListRef getPageSkinList();
    /// return ToolBar skin by path or #id
	virtual CRToolBarSkinRef getToolBarSkin( const lChar32 * path );
    /// get DOM path by id
    virtual lString32 pathById( const lChar32 * id );
    /// gets image from container
    virtual LVImageSourceRef getImage( const lChar32 * filename );
    /// gets doc pointer by asolute path
    virtual ldomXPointer getXPointer( const lString32 & xPointerStr ) { return _doc->createXPointer( xPointerStr ); }
    /// garbage collection
    virtual void gc()
    {
        _imageCache.clear();
    }
    /// constructor does nothing
    CRSkinImpl()  : _imageCache(8), _rectCache(8), _scrollCache(1), _windowCache(8), _menuCache(8), _pageCache(8), _toolbarCache(2) { }
    virtual ~CRSkinImpl(){ }
    // open from container
    virtual bool open( LVContainerRef container );
    virtual bool open( lString8 simpleXml );
};


/* XPM */
static const char *menu_item_background[] = {
/* width height num_colors chars_per_pixel */
"44 48 5 1",
/* colors */
"  c None",
". c #000000",
"o c #555555",
"0 c #AAAAAA",
"# c #ffffff",
/* pixels               ..                       */
"                                            ",
"                                            ",
"                                            ",
"                                            ",
"oooooooooooooooooooooooooooooooooooooooooooo",
"oooooooooooooooooooooooooooooooooooooooooooo",
"oooooooooooooooooooooooooooooooooooooooooooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"ooo######################################ooo",
"oooooooooooooooooooooooooooooooooooooooooooo",
"oooooooooooooooooooooooooooooooooooooooooooo",
"oooooooooooooooooooooooooooooooooooooooooooo",
"                                            ",
"                                            ",
"                                            ",
"                                            ",
};

/* XPM */
static const char *menu_shortcut_background[] = {
/* width height num_colors chars_per_pixel */
"36 48 5 1",
/* colors */
"  c None",
". c #000000",
"o c #555555",
"0 c #AAAAAA",
"# c #ffffff",
/* pixels               ..                       */
"                                    ",
"                                    ",
"                                    ",
"                                    ",
"                oooooooooooooooooooo",
"             ooooooooooooooooooooooo",
"          oooooooooooooooooooooooooo",
"        oooooooo####################",
"      oooooo########################",
"     oooo###########################",
"    oooo############################",
"   ooo##############################",
"   ooo##############################",
"  ooo###############################",
"  ooo###############################",
"  ooo###############################",
" ooo################################",
" ooo################################",
" ooo################################",
"ooo#################################",
"ooo#################################",
"ooo#################################",
"ooo#################################",
"ooo#################################",//==
"ooo#################################",//==
"ooo#################################",
"ooo#################################",
"ooo#################################",
"ooo#################################",
" ooo################################",
" ooo################################",
" ooo################################",
"  ooo###############################",
"  ooo###############################",
"  ooo###############################",
"   ooo##############################",
"   ooo##############################",
"    ooo#############################",
"     oooo###########################",
"      oooooo########################",
"       oooooooo#####################",
"         ooooooooooooooooooooooooooo",
"            oooooooooooooooooooooooo",
"               ooooooooooooooooooooo",
"                                    ",
"                                    ",
"                                    ",
"                                    ",
};


typedef struct {
    const lChar32 * filename;
    const char * * xpm;
} standard_image_item_t;

static standard_image_item_t standard_images [] = {
    { U"std_menu_shortcut_background.xpm", menu_shortcut_background },
    { U"std_menu_item_background.xpm", menu_item_background },
    { NULL, NULL }
};

/// gets image from container
LVImageSourceRef CRSkinImpl::getImage(  const lChar32 * filename  )
{
    LVImageSourceRef res;
    lString32 fn( filename );
    if ( _imageCache.get( fn, res ) )
        return res; // found in cache

    bool standard = false;
    for ( int i=0; standard_images[i].filename; i++ )
        if ( !lStr_cmp( filename, standard_images[i].filename ) ) {
            res = LVCreateXPMImageSource( standard_images[i].xpm );
            standard = true;
        }
    if ( !standard && !!_container ) {
        LVStreamRef stream = _container->OpenStream( filename, LVOM_READ );
        if ( !!stream ) {
            if ( stream->GetSize() < MAX_SKIN_IMAGE_CACHE_ITEM_RAM_COPY_PACKED_SIZE )
                res = LVCreateStreamCopyImageSource( stream );
            else
                res = LVCreateStreamImageSource( stream );
            // try to hold unpacked image, if small enough
            res = LVCreateUnpackedImageSource( res, MAX_SKIN_IMAGE_CACHE_ITEM_UNPACKED_SIZE, COLOR_BACKBUFFER==0 );
        }
    }
    // add found image to cache
    _imageCache.set( fn, res );
    return res;
}

// open from container
bool CRSkinImpl::open( LVContainerRef container )
{
    if ( container.isNull() )
        return false;
    LVStreamRef stream = container->OpenStream( U"cr3skin.xml", LVOM_READ );
    if ( stream.isNull() ) {
        CRLog::error("cannot open skin: cr3skin.xml not found");
        return false;
    }
    ldomDocument * doc = LVParseXMLStream( stream );
    if ( !doc ) {
        CRLog::error("cannot open skin: error while parsing cr3skin.xml");
        return false;
    }
    _doc = doc;
    _container = container;
    return true;
}

bool CRSkinImpl::open( lString8 simpleXml )
{
    LVStreamRef stream = LVCreateStringStream( simpleXml );
    ldomDocument * doc = LVParseXMLStream( stream );
    if ( !doc ) {
        CRLog::error("cannot open skin: error while parsing skin xml");
        return false;
    }
    _doc = doc;
    return true;
}

/// reads string value from attrname attribute of element specified by path, returns empty string if not found
lString32 CRSkinContainer::readString( const lChar32 * path, const lChar32 * attrname, bool * res )
{
    ldomXPointer ptr = getXPointer( path );
    if ( !ptr )
        return lString32::empty_str;
    if ( !ptr.getNode()->isElement() )
        return lString32::empty_str;
	//lString32 pnname = ptr.getNode()->getParentNode()->getNodeName();
	//lString32 nname = ptr.getNode()->getNodeName();
    lString32 value = ptr.getNode()->getAttributeValue(attrname);
	if ( res )
		*res = true;
    return value;
}

/// reads string value from attrname attribute of element specified by path, returns defValue if not found
lString32 CRSkinContainer::readString( const lChar32 * path, const lChar32 * attrname, const lString32 & defValue, bool * res )
{
    lString32 value = readString( path, attrname );
    if ( value.empty() )
        return defValue;
	if ( res )
		*res = true;
    return value;
}

/// reads color value from attrname attribute of element specified by path, returns defValue if not found
lUInt32 CRSkinContainer::readColor( const lChar32 * path, const lChar32 * attrname, lUInt32 defValue, bool * res  )
{
    lString32 value = readString( path, attrname );
    if ( value.empty() )
        return defValue;
    css_length_t cv;
    lString8 buf = UnicodeToUtf8(value);
    const char * bufptr = buf.modify();
    if ( !parse_color_value( bufptr, cv ) )
        return defValue;
	if ( res )
		*res = true;
    return cv.value;
}

/// reads rect value from attrname attribute of element specified by path, returns defValue if not found
lvRect CRSkinContainer::readRect( const lChar32 * path, const lChar32 * attrname, lvRect defValue, bool * res )
{
    lString32 value = readString( path, attrname );
    if ( value.empty() )
        return defValue;
    lvRect p = defValue;
    lString32 s1, s2, s3, s4, s;
    s = value;
    if ( !s.split2(",", s1, s2) )
        return p;
    s1.trim();
    s2.trim();
    s = s2;
    if ( !s.split2(",", s2, s3) )
        return p;
    s2.trim();
    s3.trim();
    s = s3;
    if ( !s.split2(",", s3, s4) )
        return p;
    s3.trim();
    s4.trim();

    bool b1=false;
    bool b2=false;
    bool b3=false;
    bool b4=false;
    p.left = toSkinPercent( s1, defValue.left, &b1 );
    p.top = toSkinPercent( s2, defValue.top, &b2 );
    p.right = toSkinPercent( s3, defValue.right, &b3 );
    p.bottom = toSkinPercent( s4, defValue.bottom, &b4 );
    if ( b1 && b2 && b3 && b4) {
        if ( res )
            *res = true;
        return p;
    }
    return defValue;
}

/// reads boolean value from attrname attribute of element specified by path, returns defValue if not found
bool CRSkinContainer::readBool( const lChar32 * path, const lChar32 * attrname, bool defValue, bool * res )
{
    lString32 value = readString( path, attrname );
    if (value.empty())
        return defValue;
    if (value == "true" || value == "yes")
        return true;
    if (value == "false" || value == "no")
        return false;
	if ( res )
		*res = true;
    return defValue;
}

/// reads image transform value from attrname attribute of element specified by path, returns defValue if not found
ImageTransform CRSkinContainer::readTransform( const lChar32 * path, const lChar32 * attrname, ImageTransform defValue, bool * res )
{
    lString32 value = readString( path, attrname );
    if ( value.empty() )
        return defValue;
    value.lowercase();
    if (value == "none") {
        if ( res )
            *res = true;
        return IMG_TRANSFORM_NONE;
    }
    if (value == "split") {
        if ( res )
            *res = true;
        return IMG_TRANSFORM_SPLIT;
    }
    if (value == "stretch") {
        if ( res )
            *res = true;
        return IMG_TRANSFORM_STRETCH;
    }
    if (value == "tile") {
        if ( res )
            *res = true;
        return IMG_TRANSFORM_TILE;
    }
    // invalid value
    return defValue;
}

/// reads h align value from attrname attribute of element specified by path, returns defValue if not found
int CRSkinContainer::readHAlign( const lChar32 * path, const lChar32 * attrname, int defValue, bool * res )
{
    lString32 value = readString( path, attrname );
    if (value.empty())
        return defValue;
    if (value == "left") {
		if ( res )
			*res = true;
        return SKIN_HALIGN_LEFT;
	}
    if (value == "center") {
		if ( res )
			*res = true;
        return SKIN_HALIGN_CENTER;
	}
    if (value == "right") {
		if ( res )
			*res = true;
        return SKIN_HALIGN_RIGHT;
	}
    // invalid value
    return defValue;
}

/// reads h align value from attrname attribute of element specified by path, returns defValue if not found
int CRSkinContainer::readVAlign( const lChar32 * path, const lChar32 * attrname, int defValue, bool * res )
{
    lString32 value = readString( path, attrname );
    if (value.empty())
        return defValue;
    if (value == "top") {
		if ( res )
			*res = true;
        return SKIN_VALIGN_TOP;
	}
    if (value == "center") {
		if ( res )
			*res = true;
        return SKIN_VALIGN_CENTER;
	}
    if (value == "bottom") {
		if ( res )
			*res = true;
        return SKIN_VALIGN_BOTTOM;
	}
    // invalid value
    return defValue;
}

/// reads int value from attrname attribute of element specified by path, returns defValue if not found
int CRSkinContainer::readInt( const lChar32 * path, const lChar32 * attrname, int defValue, bool * res )
{
    lString32 value = readString( path, attrname );
    if ( value.empty() )
        return defValue;
    value.trim();
    return toSkinPercent( value, defValue, res);
}

/// reads point(size) value from attrname attribute of element specified by path, returns defValue if not found
lvPoint CRSkinContainer::readSize( const lChar32 * path, const lChar32 * attrname, lvPoint defValue, bool * res )
{
    lString32 value = readString( path, attrname );
    if ( value.empty() )
        return defValue;
    lvPoint p = defValue;
    lString32 s1, s2;
    if ( !value.split2(",", s1, s2) )
        return p;
    s1.trim();
    s2.trim();
    bool b1=false;
    bool b2=false;
    p.x = toSkinPercent( s1, defValue.x, &b1 );
    p.y = toSkinPercent( s2, defValue.y, &b2 );
    if ( b1 && b2 ) {
        if ( res )
            *res = true;
        return p;
    }
    return defValue;
}

/// reads rect value from attrname attribute of element specified by path, returns null ref if not found
LVImageSourceRef CRSkinContainer::readImage( const lChar32 * path, const lChar32 * attrname, bool * r )
{
    lString32 value = readString( path, attrname );
    if ( value.empty() ) {
#ifdef TRACE_SKIN_ERRORS
        crtrace log;
        log << "CRSkinContainer::readImage( " << path << ", " << attrname << ") - attribute or element not found";
#endif
        return LVImageSourceRef();
    }
    LVImageSourceRef res = getImage( value );
    if ( res.isNull() ) {
#ifdef TRACE_SKIN_ERRORS
        crtrace log;
        log << "Image " << value << " cannot be read";
#endif
	} else {
		if ( r )
			*r = true;
	}
    return res;
}

/// reads rect value from attrname attribute of element specified by path, returns null ref if not found
CRIconListRef CRSkinContainer::readIcons( const lChar32 * path, bool * r )
{
    CRIconListRef list = CRIconListRef(new CRIconList() );
    for ( int i=1; i<16; i++ ) {
        lString32 p = lString32(path) << "[" << fmt::decimal(i) << "]";
        CRIconSkin * icon = new CRIconSkin();
        if ( readIconSkin(p.c_str(), icon ) )
            list->add( CRIconSkinRef(icon) );
        else {
            delete icon;
            break;
        }
    }
    if ( list->length()==0 ) {
#ifdef TRACE_SKIN_ERRORS
        crtrace log;
        log << "CRSkinContainer::readIcons( " << path << ") - cannot read icon from specified path";
#endif
        return CRIconListRef();
    }
    if ( r )
        *r = true;
    return list;
}

/// open simple skin, without image files, from string
CRSkinRef LVOpenSimpleSkin( const lString8 & xml )
{
    CRSkinImpl * skin = new CRSkinImpl();
    CRSkinRef res( skin );
    if ( !skin->open( xml ) )
        return CRSkinRef();
    //CRLog::trace("skin xml opened ok");
    return res;
}

/// opens skin from directory or .zip file
CRSkinRef LVOpenSkin( const lString32 & pathname )
{
    LVContainerRef container = LVOpenDirectory( pathname.c_str() );
    if ( !container ) {
        LVStreamRef stream = LVOpenFileStream( pathname.c_str(), LVOM_READ );
        if ( stream.isNull() ) {
            CRLog::error("cannot open skin: specified archive or directory not found");
            return CRSkinRef();
        }
        container = LVOpenArchieve( stream );
        if ( !container ) {
            CRLog::error("cannot open skin: specified archive or directory not found");
            return CRSkinRef();
        }
    }
    CRSkinImpl * skin = new CRSkinImpl();
    CRSkinRef res( skin );
    if ( !skin->open( container ) )
        return CRSkinRef();
    CRLog::trace("skin container %s opened ok", LCSTR(pathname) );
    return res;
}

// default parameters
//LVFontRef CRSkinnedItem::getFont() { return fontMan->GetFont( 24, 300, false, css_ff_sans_serif, cs8("Arial")) }

void CRSkinnedItem::draw( LVDrawBuf & buf, const lvRect & rc )
{
    SAVE_DRAW_STATE( buf );
	buf.SetBackgroundColor( getBackgroundColor() );
	buf.SetTextColor( getTextColor() );
    //CRLog::trace("CRSkinnedItem::draw before getBgIcons()");
    CRIconListRef bgimg = getBgIcons();
	if ( bgimg.isNull() ) {
        //buf.FillRect( rc, getBackgroundColor() );
	} else {
        bgimg->draw( buf, rc );
	}
}


lvRect CRRectSkin::getClientRect( const lvRect &windowRect )
{
    lvRect rc = windowRect;
    lvRect border = getBorderWidths();
    rc.left += border.left;
    rc.top += border.top;
    rc.right -= border.right;
    rc.bottom -= border.bottom;
    return rc;
}

lvRect CRWindowSkin::getTitleRect( const lvRect &windowRect )
{
    lvRect rc = CRRectSkin::getClientRect( windowRect );
    lvPoint tsz = getTitleSize();
    rc.bottom = rc.top + tsz.y;
    rc.left = rc.left + tsz.x;
    return rc;
}

lvRect CRWindowSkin::getClientRect( const lvRect &windowRect )
{
	lvRect rc = CRRectSkin::getClientRect( windowRect );
    lvPoint tsz = getTitleSize();
	rc.top += tsz.y;
	rc.left += tsz.x;
	return rc;
}

/// returns necessary window size for specified client size
lvPoint CRWindowSkin::getWindowSize( const lvPoint & clientSize )
{
    lvRect borders = getBorderWidths();
    lvPoint tsz = getTitleSize();
    return lvPoint( clientSize.x + borders.left + borders.right + tsz.x, clientSize.y + borders.top + borders.bottom + tsz.y );
}

CRSkinnedItem::CRSkinnedItem()
:   _textcolor( 0x000000 )
,   _fontFace("Arial")
,   _fontSize( 24 )
,   _fontBold( false )
,   _fontItalic( false )
,   _textAlign( 0 )
{
}

void CRSkinnedItem::setFontFace( lString32 face )
{
    if ( _fontFace != face ) {
        _fontFace = face;
        _font.Clear();
    }
}

void CRSkinnedItem::setFontSize( int size )
{
    if ( _fontSize != size ) {
        _fontSize = size;
        _font.Clear();
    }
}

void CRSkinnedItem::setFontBold( bool bold )
{
    if ( _fontBold != bold ) {
        _fontBold = bold;
        _font.Clear();
    }
}

void CRSkinnedItem::setFontItalic( bool italic )
{
    if ( _fontItalic != italic ) {
        _fontItalic = italic;
        _font.Clear();
    }
}

LVFontRef CRSkinnedItem::getFont()
{
    if ( _font.isNull() ) {
        _font = fontMan->GetFont( _fontSize, _fontBold ? 700 : 400, _fontItalic, css_ff_sans_serif, UnicodeToUtf8(_fontFace) );
    }
    return _font;
}

lvPoint CRSkinnedItem::measureText( lString32 text )
{
    int th = getFont()->getHeight();
    int tw = getFont()->getTextWidth( text.c_str(), text.length() );
    return lvPoint( tw, th );
}

static void wrapLine( lString32Collection & dst, lString32 stringToSplit, int maxWidth, LVFontRef font )
{
    lString32 str = stringToSplit;
    int w = font->getTextWidth( str.c_str(), str.length() );
    if ( w<=maxWidth ) {
        dst.add( str );
        return;
    }
    for ( ;!str.empty(); ) {
        int wpos = 1;
        int wquality = 0;
        for ( int i=str.length(); i>=0; i-- ) {
            lChar32 ch = str[i];
            if ( ch!=' ' && ch!=0 && wpos>1 )
                continue;
            lChar32 prevChar = i>0 ? str[i-1] : 0;
            w = font->getTextWidth( str.c_str(), i );
            int q = 0;
            if ( ch!=' ' && ch!=0 )
                q = 1;
            else
                q = (prevChar=='.' || prevChar==',' || prevChar==';'
                     || prevChar=='!'  || prevChar=='?'
                     ) ? (w<maxWidth*2/3 ? 3 : 2) : 2;
            if ( q>wquality && w<maxWidth ) {
                wquality = q;
                wpos = i;
            }
            if ( wquality>1 && w<=maxWidth*2/3 )
                break;
        }
        lString32 s = str.substr(0, wpos);
        s.trim();
        if ( s.length()>0 )
            dst.add( s );
        str = str.substr( wpos );
        str.trim();
    }
}

lvPoint CRRectSkin::measureTextItem( lString32 text )
{
    lvPoint sz = CRSkinnedItem::measureText( text );
    sz.x += _margins.left + _margins.right;
    sz.y += _margins.top + _margins.bottom;
    if ( _minsize.x > 0 && sz.x < _minsize.x )
        sz.x = _minsize.x;
    if ( _minsize.y > 0 && sz.y < _minsize.y )
        sz.y = _minsize.y;
    return sz;
}

void CRSkinnedItem::drawText( LVDrawBuf & buf, const lvRect & rc, lString32 text, LVFontRef font, lUInt32 textColor, lUInt32 bgColor, int flags )
{
    SAVE_DRAW_STATE( buf );
    if ( font.isNull() )
        font = getFont();
    if ( font.isNull() )
        return;
    lString32Collection lines;
    lString32 tabText;
    int tabPos = text.pos("\t");
    if ( tabPos>=0 ) {
        if ( flags & SKIN_EXTEND_TAB ) {
            tabText = text.substr( tabPos+1 );
            text = text.substr( 0, tabPos );
        } else {
            text[tabPos] = U' ';
        }
    }
    lString32 cr("\n");
    if ( flags & SKIN_WORD_WRAP ) {
        lString32Collection crlines;
        lString32 s1, s2;
        while ( text.split2( cr, s1, s2 ) ) {
            crlines.add(s1);
            text = s2;
        }
        crlines.add( text );
        for ( int i=0; i<crlines.length(); i++ ) {
            wrapLine( lines, crlines[i], rc.width(), font );
        }
    } else {
        lString32 s = text;
        while ( s.replace( cr, cs32(" ") ) )
            ;
        lines.add( s );
    }
    buf.SetTextColor( textColor );
    buf.SetBackgroundColor( bgColor );
    lvRect oldRc;
    buf.GetClipRect( &oldRc );
    buf.SetClipRect( &rc );
    int lh = font->getHeight();
    int th = lh * lines.length();
    int ttw = tabText.empty() ? 0 : font->getTextWidth( tabText.c_str(), tabText.length() );

    int halign = tabText.empty() ? (flags & SKIN_HALIGN_MASK) : SKIN_HALIGN_LEFT;
    int valign = flags & SKIN_VALIGN_MASK;

    lvRect txtrc = rc;
    int y = txtrc.top;
    int dy = txtrc.height() - th;
    if ( valign == SKIN_VALIGN_CENTER )
        y += dy / 2;
    else if ( valign == SKIN_VALIGN_BOTTOM )
        y += dy;

    for ( int i=0; i<lines.length(); i++ ) {
        lString32 s = lines[i];
        int tw = font->getTextWidth( s.c_str(), s.length() );
        int x = txtrc.left;
        int dx = txtrc.width() - tw;
        if ( halign == SKIN_HALIGN_CENTER )
            x += dx / 2;
        else if ( halign == SKIN_HALIGN_RIGHT )
            x += dx;


        font->DrawTextString( &buf, x, y, s.c_str(), s.length(), U'?', NULL, false, 0 );
        if ( !tabText.empty() ) {
            font->DrawTextString( &buf, txtrc.right-ttw, y, tabText.c_str(), tabText.length(), U'?', NULL, false, 0 );
            tabText.clear();
        }
        y = y + lh;
    }
    buf.SetClipRect( &oldRc );
}

void CRRectSkin::drawText( LVDrawBuf & buf, const lvRect & rc, lString32 text, LVFontRef font )
{
    lvRect rect = getClientRect( rc );
    CRSkinnedItem::drawText( buf, rect, text, font );
}
void CRRectSkin::drawText( LVDrawBuf & buf, const lvRect & rc, lString32 text )
{
    lvRect rect = CRRectSkin::getClientRect( rc );
    CRSkinnedItem::drawText( buf, rect, text );
}

void CRButtonSkin::drawButton( LVDrawBuf & buf, const lvRect & rect, int flags )
{
    lvRect rc = rect;
    rc.shrinkBy( _margins );
    LVImageSourceRef btnImage = getImage(flags);
    if ( !btnImage.isNull() ) {
        LVImageSourceRef img = LVCreateStretchFilledTransform( btnImage,
            rc.width(), rc.height() );
        buf.Draw( btnImage, rc.left, rc.top, rc.width(), rc.height(), false );
    }
}

LVImageSourceRef CRButtonSkin::getImage(int flags)
{
    LVImageSourceRef btnImage;

    if ( flags & ENABLED ) {
        if ( flags & PRESSED )
            btnImage = _pressedimage;
        else if ( flags & SELECTED )
            btnImage = _selectedimage;
        else
            btnImage = _normalimage;
    } else
        btnImage = _disabledimage;
    if ( btnImage.isNull() )
        btnImage = _normalimage;
	return btnImage;
}

void CRScrollSkin::drawScroll( LVDrawBuf & buf, const lvRect & rect, bool vertical, int pos, int maxpos, int pagesize )
{
    lvRect rc = rect;

    draw( buf, rc );

    int pages = pagesize>0 ? (maxpos+pagesize-1)/pagesize : 0;
    int page = pages>0 ? pos/pagesize+1 : 0;

    if ( !_bottomTabSkin.isNull() && !_bottomPageBoundSkin.isNull() &&
         !_bottomActiveTabSkin.isNull() ) {
        // tabs
        if ( pages<=1 )
            return; // don't draw tabs if no other pages
        int tabwidth = _bottomTabSkin->getMinSize().x;
        if ( tabwidth<40 )
            tabwidth = 40;
        if ( tabwidth>_bottomTabSkin->getMaxSize().x && _bottomTabSkin->getMaxSize().x>0)
            tabwidth = _bottomTabSkin->getMaxSize().x;
        int maxtabs = rc.width()-_margins.left-_margins.right / tabwidth;
        if ( pages <= maxtabs ) {
            // can draw tabs
            lvRect r(rc);
            r.left += _margins.left;
            for ( int i=0; i<pages; i++ ) {
                r.right = r.left + tabwidth;
                if ( i+1!=page ) {
                    _bottomTabSkin->draw(buf, r);
                    lString32 label = lString32::itoa(i+1);
                    _bottomTabSkin->drawText(buf, r, label);
                }
                r.left += tabwidth - r.height()/6;
            }
            _bottomPageBoundSkin->draw(buf, rc);
            r = rc;
            r.left += _margins.left;
            for ( int i=0; i<pages; i++ ) {
                r.right = r.left + tabwidth;
                if ( i+1==page ) {
                    _bottomActiveTabSkin->draw(buf, r);
                    lString32 label = lString32::itoa(i+1);
                    _bottomActiveTabSkin->drawText(buf, r, label);
                }
                r.left += tabwidth - r.height()/6;
            }
            return;
        }
    }


    rc.shrinkBy( _margins );

    int btn1State = CRButtonSkin::ENABLED;
    int btn2State = CRButtonSkin::ENABLED;
    if ( pos <= 0 )
        btn1State = 0;
    if ( pos >= maxpos-pagesize )
        btn2State = 0;
    CRButtonSkinRef btn1Skin;
    CRButtonSkinRef btn2Skin;
    lvRect btn1Rect = rc;
    lvRect btn2Rect = rc;
    lvRect bodyRect = rc;
    lvRect sliderRect = rc;
    LVImageSourceRef bodyImg;
    LVImageSourceRef sliderImg;

    if ( _hBody.isNull() ) {
        // text label with optional arrows
        lString32 label;
        label << fmt::decimal(page) << " / " << fmt::decimal(pages);
        // calc label width
        int w = getFont()->getTextWidth( label.c_str(), label.length() );
        int margin = 4;
        btn1Skin = _leftButton;
        btn2Skin = _rightButton;
        // calc button widths
        int bw1 = btn1Skin.isNull() ? 0 : btn1Skin->getMinSize().x;
        int bw2 = btn1Skin.isNull() ? 0 : btn2Skin->getMinSize().x;
        // total width
        int ww = w + margin + margin + bw1 + bw2;
        int dw = rc.width() - ww;
        rc.left += dw*3/4;
        rc.right = rc.left + ww;
        // adjust rectangle size
        btn1Rect = rc;
        btn2Rect = rc;
        btn1Rect.right = btn1Rect.left + bw1;
        btn2Rect.left = btn2Rect.right - bw2;
        bodyRect.left = btn1Rect.right;
        bodyRect.right = btn2Rect.left;
        int dy = bodyRect.height() - btn1Skin->getMinSize().y;
        btn1Rect.top += dy/2;
        btn1Rect.bottom = btn1Rect.top + btn1Skin->getMinSize().y;
        dy = bodyRect.height() - btn2Skin->getMinSize().y;
        btn2Rect.top += dy/2;
        btn2Rect.bottom = btn2Rect.top + btn2Skin->getMinSize().y;
        btn1Skin->drawButton( buf, btn1Rect, btn1State );
        btn2Skin->drawButton( buf, btn2Rect, btn2State );
        drawText( buf, bodyRect, label );
        return;
    }

    if ( vertical ) {
        // draw vertical
        btn1Skin = _upButton;
        btn2Skin = _downButton;
        btn1Rect.bottom = btn1Rect.top + btn1Skin->getMinSize().y;
        btn2Rect.top = btn2Rect.bottom - btn2Skin->getMinSize().y;
        bodyRect.top = btn1Rect.bottom;
        bodyRect.bottom = btn2Rect.top;
        int sz = bodyRect.height();
        if ( pagesize < maxpos ) {
            sliderRect.top = bodyRect.top + sz * pos / maxpos;
            sliderRect.bottom = bodyRect.top + sz * (pos + pagesize) / maxpos;
        } else
            sliderRect = bodyRect;
        bodyImg = _vBody;
        sliderImg = _vSlider;
    } else {
        // draw horz
        btn1Skin = _leftButton;
        btn2Skin = _rightButton;
        btn1Rect.right = btn1Rect.left + btn1Skin->getMinSize().x;
        btn2Rect.left = btn2Rect.right - btn2Skin->getMinSize().x;
        bodyRect.left = btn1Rect.right;
        bodyRect.right = btn2Rect.left;
        int sz = bodyRect.width();
        if ( pagesize < maxpos ) {
            sliderRect.left = bodyRect.left + sz * pos / maxpos;
            sliderRect.right = bodyRect.left + sz * (pos + pagesize) / maxpos;
        } else
            sliderRect = bodyRect;
        bodyImg = _hBody;
        sliderImg = _hSlider;
    }
    btn1Skin->drawButton( buf, btn1Rect, btn1State );
    btn2Skin->drawButton( buf, btn2Rect, btn2State );
    if ( !bodyImg.isNull() ) {
        LVImageSourceRef img = LVCreateStretchFilledTransform( bodyImg,
            bodyRect.width(), bodyRect.height() );
        buf.Draw( img, bodyRect.left, bodyRect.top, bodyRect.width(), bodyRect.height(), false );
    }
    if ( !sliderImg.isNull() ) {
        LVImageSourceRef img = LVCreateStretchFilledTransform( sliderImg,
            sliderRect.width(), sliderRect.height() );
        buf.Draw( img, sliderRect.left, sliderRect.top, sliderRect.width(), sliderRect.height(), false );
        if ( this->getShowPageNumbers() ) {
            lString32 label;
            label << fmt::decimal(page) << " / " << fmt::decimal(pages);
            drawText( buf, sliderRect, label );
        }
    }
}

void CRScrollSkin::drawGauge( LVDrawBuf & buf, const lvRect & rect, int percent )
{
    lvRect rc = rect;
    rc.shrinkBy( _margins );
    bool vertical = rect.width()<rect.height();
    lvRect bodyRect = rc;
    lvRect sliderRect = rc;
    LVImageSourceRef bodyImg;
    LVImageSourceRef sliderImg;
    if ( vertical ) {
        // draw vertical
        int sz = bodyRect.height();
        sliderRect.bottom = bodyRect.top + sz * percent / 100;
        bodyImg = _vBody;
        sliderImg = _vSlider;
    } else {
        // draw horz
        int sz = bodyRect.width();
        sliderRect.right = bodyRect.left + sz * percent / 100;
        bodyImg = _hBody;
        sliderImg = _hSlider;
    }
    if ( !bodyImg.isNull() ) {
        LVImageSourceRef img = LVCreateStretchFilledTransform( bodyImg,
            bodyRect.width(), bodyRect.height() );
        buf.Draw( img, bodyRect.left, bodyRect.top, bodyRect.width(), bodyRect.height(), false );
    }
    if ( !sliderImg.isNull() ) {
        LVImageSourceRef img = LVCreateStretchFilledTransform( sliderImg,
            sliderRect.width(), sliderRect.height() );
        buf.Draw( img, sliderRect.left, sliderRect.top, sliderRect.width(), sliderRect.height(), false );
    }
}

void CRToolBarSkin::drawToolBar( LVDrawBuf & buf, const lvRect & rect, bool enabled, int selectedButton )
{
	draw(buf, rect);
    lvRect rc = rect;
    rc.shrinkBy( _margins );
    int width = 0;
	for ( int i=0; i<_buttons->length(); i++ ) {
		// int flags = enabled ? CRButtonSkin::ENABLED : 0;
		// if (i == selectedButton && enabled)
		// 	flags |= CRButtonSkin::SELECTED;
		LVRef<CRButtonSkin> button = _buttons->get(i);
		if (!button.isNull()) {
			width += button->getMinSize().x;
			int h = button->getMinSize().y;
			if (h > rc.height())
				return;
		}
	}
	if (width > rc.width())
		return; // That's all for now
	int offsetX = 0;
	if (getHAlign() == SKIN_HALIGN_RIGHT)
		offsetX = rc.width() - width;
	else if (getHAlign() == SKIN_HALIGN_CENTER )
		offsetX = rc.width() - width/2;
	int h = rc.height();
    for ( int i=0; i<_buttons->length(); i++ ) {
		lvRect rc2 = rc;
		int flags = enabled ? CRButtonSkin::ENABLED : 0;
		if (i == selectedButton && enabled)
			flags |= CRButtonSkin::SELECTED;
		LVRef<CRButtonSkin> button = _buttons->get(i);
		if (!button.isNull()) {
			LVImageSourceRef img = button->getImage(flags);
			rc2.left += offsetX;
			rc2.right = rc2.left + button->getMinSize().x;
			if ( getVAlign()==SKIN_VALIGN_BOTTOM )
				rc2.top = rc2.bottom - button->getMinSize().y;
			else if ( getVAlign()==SKIN_VALIGN_CENTER ) {
				int imgh = button->getMinSize().y;
				rc2.top += (h - imgh/2);
				rc2.bottom = rc2.top + imgh;
			} else
				rc2.bottom = rc2.top + button->getMinSize().y;
			button->drawButton( buf, rc2, flags );
			offsetX = rc2.right - rc.left;
		}
	}
}

void CRToolBarSkin::drawButton(LVDrawBuf & buf, const lvRect & rc, int index, int flags)
{

}

CRRectSkin::CRRectSkin()
: _margins( 0, 0, 0, 0 )
, _size(toSkinPercent( 10000 ), toSkinPercent( 10000 )) // 100% x 100%
, _pos(0, 0)
, _align(SKIN_VALIGN_TOP|SKIN_HALIGN_LEFT) // relative to top left
{
}

CRWindowSkin::CRWindowSkin()
{
    _fullscreen = false;
}

CRMenuSkin::CRMenuSkin()
: _minItemCount(-1)
, _maxItemCount(-1)
, _showShortcuts(true)
{
}


// WINDOW skin stub
class CRSimpleWindowSkin : public CRWindowSkin
{
public:
        CRSimpleWindowSkin( CRSkinImpl * )
	{
        //setBackgroundColor( 0xAAAAAA );
	}
};

class CRSimpleFrameSkin : public CRRectSkin
{
public:
        CRSimpleFrameSkin( CRSkinImpl * )
	{
        //setBackgroundColor( 0xAAAAAA );
	}
};

/*
    <item>
        <text color="" face="" size="" bold="" italic="" valign="" halign=""/>
        <background image="filename" color=""/>
        <border widths="left,top,right,bottom"/>
        <icon image="filename" valign="" halign=""/>
        <title>
            <size minvalue="x,y" maxvalue=""/>
            <text color="" face="" size="" bold="" italic="" valign="" halign=""/>
            <background image="filename" color=""/>
            <border widths="left,top,right,bottom"/>
            <icon image="filename" valign="" halign="">
        </title>
        <item>
            <size minvalue="x,y" maxvalue=""/>
            <text color="" face="" size="" bold="" italic="" valign="" halign=""/>
            <background image="filename" color=""/>
            <border widths="left,top,right,bottom"/>
            <icon image="filename" valign="" halign="">
        </item>
        <shortcut>
            <size minvalue="x,y" maxvalue=""/>
            <text color="" face="" size="" bold="" italic="" valign="" halign=""/>
            <background image="filename" color=""/>
            <border widths="left,top,right,bottom"/>
            <icon image="filename" valign="" halign="">
        </shortcut>
    </item>
*/
class CRSimpleMenuSkin : public CRMenuSkin
{
public:
    CRSimpleMenuSkin( CRSkinImpl * skin )
    {
        //setBackgroundColor( 0xAAAAAA );
        //setTitleSize( lvPoint( 0, 48 ) );
        setBorderWidths( lvRect( 8, 8, 8, 8 ) );
        _titleSkin = CRRectSkinRef( new CRRectSkin() );
        //_titleSkin->setBackgroundColor(0xAAAAAA);
        _titleSkin->setTextColor(0x000000);
        _titleSkin->setFontBold( true );
        _titleSkin->setFontSize( 28 );
        _itemSkin = CRRectSkinRef( new CRRectSkin() );
        _itemSkin->setBackgroundImage( skin->getImage( U"std_menu_item_background.xpm" ) );
        _itemSkin->setBorderWidths( lvRect( 8, 8, 8, 8 ) );
        _itemShortcutSkin = CRRectSkinRef( new CRRectSkin() );
        _itemShortcutSkin->setBackgroundImage( skin->getImage( U"std_menu_shortcut_background.xpm" ) );
        _itemShortcutSkin->setBorderWidths( lvRect( 12, 8, 8, 8 ) );
        _itemShortcutSkin->setTextColor( 0x555555 );
        _itemShortcutSkin->setTextHAlign( SKIN_HALIGN_CENTER );
        _itemShortcutSkin->setTextVAlign( SKIN_VALIGN_CENTER );
	}
};

CRButtonSkin::CRButtonSkin() { }

bool CRSkinContainer::readButtonSkin(  const lChar32 * path, CRButtonSkin * res )
{
    bool flg = false;
    lString32 base = getBasePath( path );
    RecursionLimit limit;
    if ( !base.empty() && limit.test() ) {
        // read base skin first
        flg = readButtonSkin( base.c_str(), res ) || flg;
    }

    lString32 p( path );
    ldomXPointer ptr = getXPointer( path );
    if ( !ptr ) {
#ifdef TRACE_SKIN_ERRORS
        crtrace log;
        log << "Button skin by path " << p << " was not found";
#endif
        return false;
    }

    flg = readRectSkin( path, res ) || flg;
    res->setNormalImage( readImage( path, U"normal", &flg ) );
    res->setDisabledImage( readImage( path, U"disabled", &flg ) );
    res->setPressedImage( readImage( path, U"pressed", &flg ) );
    res->setSelectedImage( readImage( path, U"selected", &flg ) );

    LVImageSourceRef img = res->getNormalImage();
    lvRect margins = res->getBorderWidths();
    if ( !img.isNull() ) {
        flg = true;
        res->setMinSize( lvPoint( margins.left + margins.right + img->GetWidth(), margins.top + margins.bottom + img->GetHeight() ) );
    }

    if ( !flg ) {
#ifdef TRACE_SKIN_ERRORS
        crtrace log;
        log << "Button skin reading failed: " << path;
#endif
    }

    return flg;
}

CRScrollSkin::CRScrollSkin() : _autohide(false), _showPageNumbers(true), _location(CRScrollSkin::Status) { }

bool CRSkinContainer::readScrollSkin(  const lChar32 * path, CRScrollSkin * res )
{
    bool flg = false;
    lString32 base = getBasePath( path );
    RecursionLimit limit;
    if ( !base.empty() && limit.test() ) {
        // read base skin first
        flg = readScrollSkin( base.c_str(), res ) || flg;
    }

    lString32 p( path );
    ldomXPointer ptr = getXPointer( path );
    if ( !ptr ) {
#ifdef TRACE_SKIN_ERRORS
        crtrace log;
        log << "ScrollBar skin by path " << p << " was not found";
#endif
        return false;
    }



    flg = readRectSkin( path, res ) || flg;

    res->setAutohide( readBool( (p).c_str(), U"autohide", res->getAutohide()) );
    res->setShowPageNumbers( readBool( (p).c_str(), U"show-page-numbers", res->getShowPageNumbers()) );
    lString32 l = readString( (p).c_str(), U"location", lString32::empty_str );
    if ( !l.empty() ) {
        l.lowercase();
        if (l == "title")
            res->setLocation( CRScrollSkin::Title );
    }
    CRButtonSkinRef upButton( new CRButtonSkin() );
    if ( readButtonSkin(  (p + "/upbutton").c_str(), upButton.get() ) ) {
        res->setUpButton( upButton );
        flg = true;
    }

    CRButtonSkinRef downButton( new CRButtonSkin() );
    if ( readButtonSkin(  (p + "/downbutton").c_str(), downButton.get() ) ) {
        res->setDownButton( downButton );
        flg = true;
    }

    CRButtonSkinRef leftButton( new CRButtonSkin() );
    if ( readButtonSkin(  (p + "/leftbutton").c_str(), leftButton.get() ) ) {
        res->setLeftButton( leftButton );
        flg = true;
    }

    CRButtonSkinRef rightButton( new CRButtonSkin() );
    if ( readButtonSkin(  (p + "/rightbutton").c_str(), rightButton.get() ) ) {
        res->setRightButton( rightButton );
        flg = true;
    }

    CRRectSkinRef tabSkin( new CRRectSkin() );
    if ( readRectSkin(  (p + "/tab-bottom").c_str(), tabSkin.get() ) ) {
        res->setBottomTabSkin( tabSkin );
        flg = true;
    }

    CRRectSkinRef tabActiveSkin( new CRRectSkin() );
    if ( readRectSkin(  (p + "/tab-bottom-active").c_str(), tabActiveSkin.get() ) ) {
        res->setBottomActiveTabSkin( tabActiveSkin );
        flg = true;
    }

    CRRectSkinRef pageBoundSkin( new CRRectSkin() );
    if ( readRectSkin(  (p + "/page-bound-bottom").c_str(), pageBoundSkin.get() ) ) {
        res->setBottomPageBoundSkin( pageBoundSkin );
        flg = true;
    }

    LVImageSourceRef hf = readImage( (p + "/hbody").c_str(), U"frame", &flg );
    if ( !hf.isNull() )
        res->setHBody( hf );
    LVImageSourceRef hs = readImage( (p + "/hbody").c_str(), U"slider", &flg );
    if ( !hs.isNull() )
        res->setHSlider( hs );
    LVImageSourceRef vf = readImage( (p + "/vbody").c_str(), U"frame", &flg );
    if ( !vf.isNull() )
        res->setVBody( vf );
    LVImageSourceRef vs = readImage( (p + "/vbody").c_str(), U"slider", &flg );
    if ( !vs.isNull() )
        res->setVSlider(vs );

    if ( !flg ) {
        crtrace log;
        log << "Scroll skin reading failed: " << path;
    }

    return flg;
}

bool CRSkinContainer::readIconSkin(  const lChar32 * path, CRIconSkin * res )
{
    bool flg = false;
    lString32 base = getBasePath( path );
    RecursionLimit limit;
    if ( !base.empty() && limit.test() ) {
        // read base skin first
        flg = readIconSkin( base.c_str(), res ) || flg;
    }
    lString32 p( path );
    ldomXPointer ptr = getXPointer( path );
    if ( !ptr ) {
#ifdef TRACE_SKIN_ERRORS
        crtrace log;
        log << "Image skin by path " << p << " was not found";
#endif
        return false;
    }
    LVImageSourceRef image = readImage( path, U"image", &flg );
    if ( !image.isNull() )
        res->setImage( image );
    res->setHAlign( readHAlign( path, U"halign", res->getHAlign(), &flg) );
    res->setVAlign( readVAlign( path, U"valign", res->getVAlign(), &flg) );
    res->setBgColor( readColor( path, U"color", res->getBgColor(), &flg) );
    res->setHTransform( readTransform( path, U"htransform", res->getHTransform(), &flg) );
    res->setVTransform( readTransform( path, U"vtransform", res->getVTransform(), &flg) );
    res->setSplitPoint( readSize( path, U"split", res->getSplitPoint(), &flg) );
    res->setPos( readSize( path, U"pos", res->getPos(), &flg) );
    res->setSize( readSize( path, U"size", res->getSize(), &flg) );
    return flg;
}

bool CRSkinContainer::readRectSkin(  const lChar32 * path, CRRectSkin * res )
{
    bool flg = false;

    lString32 base = getBasePath( path );
    RecursionLimit limit;
    if ( !base.empty() && limit.test() ) {
        // read base skin first
        flg = readRectSkin( base.c_str(), res ) || flg;
    }

    lString32 p( path );
    ldomXPointer ptr = getXPointer( path );
    if ( !ptr ) {
#ifdef TRACE_SKIN_ERRORS
        crtrace log;
        log << "Rect skin by path " << p << " was not found";
#endif
        return false;
    }

    lString32 bgpath = p + "/background";
    lString32 borderpath = p + "/border";
    lString32 textpath = p + "/text";
    lString32 sizepath = p + "/size";

    CRIconListRef icons;
    bool bgIconsFlag = false;
    icons = readIcons( bgpath.c_str(), &bgIconsFlag);
    if ( bgIconsFlag ) {
        res->setBgIcons( icons );
        flg = true;
    }
    //res->setBackgroundColor( readColor( bgpath.c_str(), U"color", res->getBackgroundColor(), &flg ) );
    res->setBorderWidths( readRect( borderpath.c_str(), U"widths", res->getBorderWidths(), &flg ) );
    res->setMinSize( readSize( sizepath.c_str(), U"minvalue", res->getMinSize(), &flg ) );
    res->setMaxSize( readSize( sizepath.c_str(), U"maxvalue", res->getMaxSize(), &flg ) );
    res->setFontFace( readString( textpath.c_str(), U"face", res->getFontFace(), &flg ) );
    res->setTextColor( readColor( textpath.c_str(), U"color", res->getTextColor(), &flg ) );
    res->setFontBold( readBool( textpath.c_str(), U"bold", res->getFontBold(), &flg ) );
    res->setWordWrap( readBool( textpath.c_str(), U"wordwrap", res->getWordWrap(), &flg ) );
    res->setFontItalic( readBool( textpath.c_str(), U"italic", res->getFontItalic(), &flg ) );
    res->setFontSize( readInt( textpath.c_str(), U"size", res->getFontSize(), &flg ) );
    res->setTextHAlign( readHAlign( textpath.c_str(), U"halign", res->getTextHAlign(), &flg) );
    res->setTextVAlign( readVAlign( textpath.c_str(), U"valign", res->getTextVAlign(), &flg) );

    res->setHAlign( readHAlign( path, U"halign", res->getHAlign(), &flg) );
    res->setVAlign( readVAlign( path, U"valign", res->getVAlign(), &flg) );
    res->setPos( readSize( path, U"pos", res->getPos(), &flg) );
    res->setSize( readSize( path, U"size", res->getSize(), &flg) );

    if ( !flg ) {
        crtrace log;
        log << "Rect skin reading failed: " << path;
    }

    return flg;
}

lvPoint CRWindowSkin::getTitleSize()
{
    lvPoint minsize = _titleSkin.isNull() ? lvPoint(0, 0) : _titleSkin->getMinSize();
    return minsize;
}

bool CRSkinContainer::readPageSkin(  const lChar32 * path, CRPageSkin * res )
{
    bool flg = false;

    lString32 base = getBasePath( path );
    RecursionLimit limit;
    if ( !base.empty() && limit.test() ) {
        // read base skin first
        flg = readPageSkin( base.c_str(), res ) || flg;
    }

    lString32 p( path );
    ldomXPointer ptr = getXPointer( path );
    if ( !ptr ) {
#ifdef TRACE_SKIN_ERRORS
        crtrace log;
        log << "Book page skin by path " << p << " was not found";
#endif
        return false;
    }

    lString32 name = ptr.getNode()->getAttributeValue(ptr.getNode()->getDocument()->getAttrNameIndex("name"));
    if ( !name.empty() )
        res->setName(name);

    flg = readRectSkin( (p + "scroll-skin").c_str(),  res->getSkin( PAGE_SKIN_SCROLL ).get() ) || flg;
    flg = readRectSkin( (p + "left-page-skin").c_str(),  res->getSkin( PAGE_SKIN_LEFT_PAGE ).get() ) || flg;
    flg = readRectSkin( (p + "right-page-skin").c_str(),  res->getSkin( PAGE_SKIN_RIGHT_PAGE ).get() ) || flg;
    flg = readRectSkin( (p + "single-page-skin").c_str(),  res->getSkin( PAGE_SKIN_SINGLE_PAGE ).get() ) || flg;

    if ( !flg ) {
        crtrace log;
        log << "Book page skin reading failed: " << path;
    }

    return flg;
}

bool CRSkinContainer::readWindowSkin(  const lChar32 * path, CRWindowSkin * res )
{
    bool flg = false;

    lString32 base = getBasePath( path );
    RecursionLimit limit;
    if ( !base.empty() && limit.test() ) {
        // read base skin first
        flg = readWindowSkin( base.c_str(), res ) || flg;
    }

    lString32 p( path );
    ldomXPointer ptr = getXPointer( path );
    if ( !ptr ) {
#ifdef TRACE_SKIN_ERRORS
        crtrace log;
        log << "Window skin by path " << p << " was not found";
#endif
        return false;
    }

    res->setFullScreen(readBool(path, U"fullscreen", res->getFullScreen(), &flg));

    flg = readRectSkin(  path, res ) || flg;
    CRRectSkinRef titleSkin( new CRRectSkin() );
    if ( readRectSkin(  (p + "/title").c_str(), titleSkin.get() ) ) {
        res->setTitleSkin( titleSkin );
        flg = true;
    }

    CRRectSkinRef clientSkin( new CRRectSkin() );
    if ( readRectSkin(  (p + "/client").c_str(), clientSkin.get() ) ) {
        res->setClientSkin( clientSkin );
        flg = true;
    }

    CRRectSkinRef inputSkin( new CRRectSkin() );
    if ( readRectSkin(  (p + "/input").c_str(), inputSkin.get() ) ) {
        res->setInputSkin( inputSkin );
        flg = true;
    }

    CRRectSkinRef statusSkin( new CRRectSkin() );
    if ( readRectSkin(  (p + "/status").c_str(), statusSkin.get() ) ) {
        res->setStatusSkin( statusSkin );
        flg = true;
    }

    CRScrollSkinRef scrollSkin( new CRScrollSkin() );
    if ( readScrollSkin(  (p + "/scroll").c_str(), scrollSkin.get() ) ) {
        res->setScrollSkin( scrollSkin );
        flg = true;
    }

    if ( !flg ) {
        crtrace log;
        log << "Window skin reading failed: " << path;
    }

    return flg;
}

bool CRSkinContainer::readMenuSkin(  const lChar32 * path, CRMenuSkin * res )
{
    bool flg = false;

    lString32 base = getBasePath( path );
    RecursionLimit limit;
    if ( !base.empty() && limit.test() ) {
        // read base skin first
        flg = readMenuSkin( base.c_str(), res ) || flg;
    }

    lString32 p( path );
    ldomXPointer ptr = getXPointer( path );
    if ( !ptr ) {
#ifdef TRACE_SKIN_ERRORS
        crtrace log;
        log << "Menu skin by path " << p << " was not found";
#endif
        return false;
    }

    flg = readWindowSkin( path, res ) || flg;

    bool b;
    CRRectSkinRef separatorSkin( new CRRectSkin() );
    b = readRectSkin(  (p + "/separator").c_str(), separatorSkin.get() );
    flg = flg || b;
    if ( b || res->getSeparatorSkin().isNull() )
        res->setSeparatorSkin( separatorSkin );

    CRRectSkinRef valueSkin( new CRRectSkin() );
    b = readRectSkin(  (p + "/value").c_str(), valueSkin.get() );
    flg = flg || b;
    if ( b || res->getValueSkin().isNull() )
        res->setValueSkin( valueSkin );

    CRRectSkinRef itemSkin( new CRRectSkin() );
    b = readRectSkin(  (p + "/item").c_str(), itemSkin.get() );
    flg = flg || b;
    if ( b || res->getItemSkin().isNull() )
        res->setItemSkin( itemSkin );
    CRRectSkinRef shortcutSkin( new CRRectSkin() );
    b = readRectSkin(  (p + "/shortcut").c_str(), shortcutSkin.get() );
    flg = flg || b;
    if ( b || res->getItemShortcutSkin().isNull() )
        res->setItemShortcutSkin( shortcutSkin );

    CRRectSkinRef itemSelSkin( new CRRectSkin() );
    b = readRectSkin(  (p + "/selitem").c_str(), itemSelSkin.get() );
    flg = flg || b;
    if ( b || res->getSelItemSkin().isNull() )
        res->setSelItemSkin( itemSelSkin );
    CRRectSkinRef shortcutSelSkin( new CRRectSkin() );
    b = readRectSkin(  (p + "/selshortcut").c_str(), shortcutSelSkin.get() );
    flg = flg || b;
    if ( b || res->getSelItemShortcutSkin().isNull() )
        res->setSelItemShortcutSkin( shortcutSelSkin );

    CRRectSkinRef evenitemSkin( new CRRectSkin() );
    b = readRectSkin(  (p + "/item-even").c_str(), evenitemSkin.get() );
    flg = flg || b;
    if ( b )
        res->setEvenItemSkin( evenitemSkin );
    CRRectSkinRef evenshortcutSkin( new CRRectSkin() );
    b = readRectSkin(  (p + "/shortcut-even").c_str(), evenshortcutSkin.get() );
    flg = flg || b;
    if ( b )
        res->setEvenItemShortcutSkin( evenshortcutSkin );

    CRRectSkinRef evenitemSelSkin( new CRRectSkin() );
    b = readRectSkin(  (p + "/selitem-even").c_str(), evenitemSelSkin.get() );
    flg = flg || b;
    if ( b )
        res->setEvenSelItemSkin( evenitemSelSkin );
    CRRectSkinRef evenshortcutSelSkin( new CRRectSkin() );
    b = readRectSkin(  (p + "/selshortcut-even").c_str(), evenshortcutSelSkin.get() );
    flg = flg || b;
    if ( b )
        res->setEvenSelItemShortcutSkin( evenshortcutSelSkin );

    res->setMinItemCount( readInt( path, U"min-item-count", res->getMinItemCount()) );
    res->setMaxItemCount( readInt( path, U"max-item-count", res->getMaxItemCount()) );
    res->setShowShortcuts( readBool( path, U"show-shortcuts", res->getShowShortcuts() ) );

    return flg;
}

CRButtonListRef CRSkinContainer::readButtons( const lChar32 * path, bool * res )
{
    CRButtonListRef list = CRButtonListRef(new CRButtonList() );
    for ( int i=1; i<64; i++ ) {
        lString32 p = lString32(path) << "[" << fmt::decimal(i) << "]";
        CRButtonSkin * button = new CRButtonSkin();
        if ( readButtonSkin(p.c_str(), button ) )
            list->add( LVRef<CRButtonSkin>(button) );
        else {
            delete button;
            break;
        }
    }
    if ( list->length()==0 ) {
#ifdef TRACE_SKIN_ERRORS
        crtrace log;
        log << "CRSkinContainer::readButtons( " << path << ") - cannot read button from specified path";
#endif
		if ( res )
			*res = false;
        return CRButtonListRef();
    }
    if ( res )
        *res = true;
    return list;
}

bool CRSkinContainer::readToolBarSkin(  const lChar32 * path, CRToolBarSkin * res )
{
    bool flg = false;
    lString32 base = getBasePath( path );
    RecursionLimit limit;
    if ( !base.empty() && limit.test() ) {
        // read base skin first
        flg = readToolBarSkin( base.c_str(), res ) || flg;
    }

    lString32 p( path );
    ldomXPointer ptr = getXPointer( path );
    if ( !ptr ) {
#ifdef TRACE_SKIN_ERRORS
        crtrace log;
        log << "ToolBar skin by path " << p << " was not found";
#endif
        return false;
    }
    flg = readRectSkin( path, res ) || flg;

    lString32 buttonspath = p + "/button";
    bool buttonsFlag = false;
    CRButtonListRef buttons = readButtons( buttonspath.c_str(), &buttonsFlag);
    if ( buttonsFlag ) {
        res->setButtons( buttons );
        flg = true;
    }
	return flg;
}

lString32 CRSkinImpl::pathById( const lChar32 * id )
{
    ldomNode * elem = _doc->getElementById( id );
    if ( !elem )
        return lString32::empty_str;
    return ldomXPointer(elem, -1).toString();
}

/// returns rect skin
CRRectSkinRef CRSkinImpl::getRectSkin( const lChar32 * path )
{
    lString32 p(path);
    CRRectSkinRef res;
    if ( _rectCache.get( p, res ) )
        return res; // found in cache
    if ( *path == '#' ) {
        // find by id
        p = pathById( path+1 );
    }
    // create new one
    res = CRRectSkinRef( new CRRectSkin() );
    readRectSkin( p.c_str(), res.get() );
    _rectCache.set( lString32(path), res );
    return res;
}

/// returns scroll skin by path or #id
CRScrollSkinRef CRSkinImpl::getScrollSkin( const lChar32 * path )
{
    lString32 p(path);
    CRScrollSkinRef res;
    if ( _scrollCache.get( p, res ) )
        return res; // found in cache
    if ( *path == '#' ) {
        // find by id
        p = pathById( path+1 );
    }
    // create new one
    res = CRScrollSkinRef( new CRScrollSkin() );
    readScrollSkin( p.c_str(), res.get() );
    _scrollCache.set( lString32(path), res );
    return res;
}

/// returns book page skin list
CRPageSkinListRef CRSkinImpl::getPageSkinList()
{
    if ( _pageSkinList.isNull() ) {
        _pageSkinList = CRPageSkinListRef( new CRPageSkinList() );
        for ( int i=0; i<32; i++ ) {
            lString32 path("/CR3Skin/page-skins/page-skin[");
            path << (i+1) << U"]";
            CRPageSkinRef skin = CRPageSkinRef( new CRPageSkin() );
            if ( readPageSkin(path.c_str(), skin.get() ) ) {
                _pageSkinList->add( skin );
            } else {
                break;
            }
        }
    }
    return _pageSkinList;
}

/// returns book page skin by path or #id
CRPageSkinRef CRSkinImpl::getPageSkin( const lChar32 * path )
{
    lString32 p(path);
    CRPageSkinRef res;
    if ( _pageCache.get( p, res ) )
        return res; // found in cache
    if ( *path == '#' ) {
        // find by id
        p = pathById( path+1 );
    }
    // create new one
    res = CRPageSkinRef( new CRPageSkin() );
    readPageSkin( p.c_str(), res.get() );
    _pageCache.set( lString32(path), res );
    return res;
}

/// returns window skin
CRWindowSkinRef CRSkinImpl::getWindowSkin( const lChar32 * path )
{
    lString32 p(path);
    CRWindowSkinRef res;
    if ( _windowCache.get( p, res ) )
        return res; // found in cache
    if ( *path == '#' ) {
        // find by id
        p = pathById( path+1 );
    }
    // create new one
    res = CRWindowSkinRef( new CRWindowSkin() );
    readWindowSkin( p.c_str(), res.get() );
    _windowCache.set( lString32(path), res );
    return res;
}

/// returns menu skin
CRMenuSkinRef CRSkinImpl::getMenuSkin( const lChar32 * path )
{
    lString32 p(path);
    CRMenuSkinRef res;
    if ( _menuCache.get( p, res ) )
        return res; // found in cache
    if ( *path == '#' ) {
        // find by id
        p = pathById( path+1 );
    }
    // create new one
    res = CRMenuSkinRef( new CRMenuSkin() );
    readMenuSkin( p.c_str(), res.get() );
    _menuCache.set( lString32(path), res );
    return res;
}

CRToolBarSkinRef CRSkinImpl::getToolBarSkin( const lChar32 * path )
{
    lString32 p(path);
    CRToolBarSkinRef res;
    if ( _toolbarCache.get( p, res ) )
        return res; // found in cache
    if ( *path == '#' ) {
        // find by id
        p = pathById( path+1 );
    }
    res = CRToolBarSkinRef( new CRToolBarSkin() );
    readToolBarSkin( p.c_str(), res.get() );
    _toolbarCache.set( lString32(path), res );
    return res;
}

CRSkinListItem * CRSkinList::findByName( const lString32 & name )
{
    for ( int i=0; i<length(); i++ )
        if ( get(i)->getName()==name )
            return get(i);
    return NULL;
}

CRSkinListItem * CRSkinListItem::init( lString32 baseDir, lString32 fileName )
{
    CRSkinRef skin = LVOpenSkin( baseDir + fileName );
    if ( skin.isNull() )
        return NULL;
    CRSkinListItem * item = new CRSkinListItem();
    item->_baseDir = baseDir;
    item->_fileName = fileName;
    //item->_name = skin->get
    return item;
}

CRSkinRef CRSkinListItem::getSkin()
{
    return LVOpenSkin( getDirName() + getFileName() );
}

bool CRLoadSkinList( lString32 baseDir, CRSkinList & list )
{
    return false;
}
