// ************************************************************************************************************
// Author: Marcin Załęczny
// Release Date: 2010-11-08
// Last modified date: 2010-11-09
// Licence: The GNU Lesser General Public License, version 3.0 (LGPLv3)
// ************************************************************************************************************
#include "mzXpsPage.h"
#include "mzstdxpsGlobals.h"
#include "mzXpsDocument.h"
#include "mzStringList.h"
#include "mzFileUtils.h"
#include "mzStringList.h"
#include <wchar.h>
#define _USE_MATH_DEFINES
#include <math.h>
#include "mzFont.h"

CXpsRenderTransform::CXpsRenderTransform()
    : m11(1), // xScalingFactor = 1
      m12(0),
      m21(0),
      m22(1), // yScalingFactor = 1
      dx(0),
      dy(0)
{
    this->setRealClassName(L"CXpsRenderTransform");
}

CXpsRenderTransform::CXpsRenderTransform(float m11, float m12, float m21, float m22, float dx, float dy)
    : m11(m11), m12(m12), m21(m21), m22(m22), dx(dx), dy(dy)
{
    this->setRealClassName(L"CXpsRenderTransform");
}

CXpsRenderTransform::~CXpsRenderTransform() {
}

CString CXpsRenderTransform::toString() {
    return XpsFloatToString(this->m11) + L"," + XpsFloatToString(this->m12) + L"," +
           XpsFloatToString(this->m21) + L"," + XpsFloatToString(this->m22) + L"," +
           XpsFloatToString(this->dx) + L"," + XpsFloatToString(this->dy);
}

VOID CXpsRenderTransform::setParamsForRotation(float angleRad) {
    this->m11 = cos(angleRad);
    this->m12 = sin(angleRad);
    this->m21 = -sin(angleRad);
    this->m22 = cos(angleRad);
    this->dx = 0;
    this->dy = 0;
}

VOID CXpsRenderTransform::setParamsForSkewing(float xSkewFactor, float ySkewFactor) {
    this->m11 = 1; // xScalingFactor = 1
    this->m12 = ySkewFactor;
    this->m21 = xSkewFactor;
    this->m22 = 1; // xScalingFactor = 1
    this->dx = 0;
    this->dy = 0;
}

VOID CXpsRenderTransform::setParamsForReversingXAxis() {
    this->m11 = -1; // xScalingFactor = -1
    this->m12 = 0;
    this->m21 = 0;
    this->m22 = 1; // yScalingFactor = 1
    this->dx = 0;
    this->dy = 0;
}

VOID CXpsRenderTransform::setParamsForReversingYAxis() {
    this->m11 = 1; // xScalingFactor = 1
    this->m12 = 0;
    this->m21 = 0;
    this->m22 = -1; // yScalingFactor = -1
    this->dx = 0;
    this->dy = 0;
}

VOID CXpsRenderTransform::setParamsForScaling(float xScaleFactor, float yScaleFactor) {
    this->m11 = xScaleFactor;
    this->m12 = 0;
    this->m21 = 0;
    this->m22 = yScaleFactor;
    this->dx = 0;
    this->dy = 0;
}

VOID CXpsRenderTransform::setParamsForTranslation(float dx, float dy) {
    this->m11 = 1; // xScalingFactor = 1
    this->m12 = 0;
    this->m21 = 0;
    this->m22 = 1; // yScalingFactor = 1
    this->dx = dx;
    this->dy = dy;
}

VOID CXpsRenderTransform::addParamsForTranslation(float dx, float dy) {
    this->dx = dx;
    this->dy = dy;
}

VOID CXpsRenderTransform::reset() {
    this->m11 = 0;
    this->m12 = 0;
    this->m21 = 0;
    this->m22 = 0;
    this->dx = 0;
    this->dy = 0;
}






WCHAR* CXpsPage::_small_letters = L"abcdefghijklmnopqrstuvwxyz";
WCHAR* CXpsPage::_small_letters_indices = L"68;69,59;70;71;72,59;73;74;75;76,59;77;78;79,59;80;81;82,59;83;84;85,59;86;87;88;89,59;90;91;92,59;93";
INT CXpsPage::_small_letters_len = wcslen(CXpsPage::_small_letters);

WCHAR* CXpsPage::_big_letters = L"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
WCHAR* CXpsPage::_big_letters_indices = L"36;37,59;38;39;40,59;41;42;43;44,59;45;46;47,59;48;49;50,59;51;52;53,59;54;55;56;57,59;58;59;60,59;61";
INT CXpsPage::_big_letters_len = wcslen(CXpsPage::_big_letters);

WCHAR* CXpsPage::_small_pl_letters = L"ąćęłńóśźż";
WCHAR* CXpsPage::_small_pl_letters_indices = L"261;252,59;266;225;276,59;121;286;296;298";
INT CXpsPage::_small_pl_letters_len = wcslen(CXpsPage::_small_pl_letters);

WCHAR* CXpsPage::_big_pl_letters = L"ĄĆĘŁŃÓŚŹŻ";
WCHAR* CXpsPage::_big_pl_letters_indices = L"260;251,59;265;224;275,59;207;285;295;297";
INT CXpsPage::_big_pl_letters_len = wcslen(CXpsPage::_big_pl_letters);

WCHAR* CXpsPage::_digits = L"0123456789";
WCHAR* CXpsPage::_digits_indices = L"19,110;20,110;21,110;22,110;23,110;24,110;25,110;26,110;27,110;28,110";
INT CXpsPage::_digits_len = wcslen(CXpsPage::_digits);

WCHAR* CXpsPage::_special_chars = L"-_=+\\|[{]};:'\",<.>/?`~ ";
WCHAR* CXpsPage::_special_chars_indices = L"16;66,59;32;14;63,59;95;62;94;64,59;96;30;29,59;10;5;15,59;31;17;33,59;18;34;67;97;3,31";
INT CXpsPage::_special_chars_len = wcslen(CXpsPage::_special_chars);

WCHAR* CXpsPage::_digits_upper_chars = L"!@#$%^&*()";
WCHAR* CXpsPage::_digits_upper_chars_indices = L"4;35,59;6;7;8,59;65;9;13;11,59;12";
INT CXpsPage::_digits_upper_chars_len = wcslen(CXpsPage::_digits_upper_chars);


CXpsPage::CXpsPage(CXpsDocument* doc, UINT pagenum, BOOL useCommonPagePT)
    : CXmlDocument(L"FixedPage"), _doc(doc), _pagenum(pagenum)
{
    this->_commonInit(L"793.76", L"1122.56", useCommonPagePT);
}

CXpsPage::CXpsPage(CXpsDocument* doc, UINT pagenum, CString width, CString height, BOOL useCommonPagePT)
    : CXmlDocument(L"FixedPage"), _doc(doc), _pagenum(pagenum)
{
    this->setRealClassName(L"CXpsPage");
    this->_commonInit(width, height, useCommonPagePT);
}

VOID CXpsPage::_commonInit(CString width, CString height, BOOL useCommonPagePT) {
    this->_rels = new CXpsRelationships();
    this->_rels->addXmlns(L"http://schemas.openxmlformats.org/package/2006/relationships");
    if (useCommonPagePT) {
        this->_rels->addRelationship(this->_doc->getDocumentInternalDirectory() + L"/Metadata/Page1_PT.xml", L"R0", "http://schemas.microsoft.com/xps/2005/06/printticket");
    } else {
        this->_rels->addRelationship(this->_doc->getDocumentInternalDirectory() + L"/Metadata/Page" + this->_pagenum + L"_PT.xml", L"R0", "http://schemas.microsoft.com/xps/2005/06/printticket");
    }
    
    if (this->_doc) {
        CStringList* lst = new CStringList(10);
        this->_doc->getAvailableFonts(lst);
        for (UINT i = 0; i < lst->size(); i++) {
            this->_rels->addRelationship(this->_doc->getDocumentInternalDirectory() + L"/Resources/Fonts/" + lst->getAt(i), CString(L"R") + (i+1), "http://schemas.microsoft.com/xps/2005/06/required-resource");
        }
    }
    
    this->addAttribute(L"Width", width);
    this->addAttribute(L"Height", height);
    this->addXmlns(L"http://schemas.microsoft.com/xps/2005/06");
    this->addXmlns(L"http://schemas.microsoft.com/xps/2005/06/resourcedictionary-key", L"x");
    this->addAttribute(L"lang", L"und", L"xml");
}

CXpsPage::~CXpsPage() {
    if (this->_rels) {
        delete this->_rels;
        this->_rels = NULL;
    }
}

VOID CXpsPage::saveXmlFile(CString fname) {
    CString dir = GetBasename(fname);
    
    if (this->_rels) {
        this->_rels->saveXmlFile(dir + L"\\_rels\\" + this->_pagenum + L".fpage.rels");
    }
    
    CXmlDocument::saveXmlFile(fname);
}

CString CXpsPage::pointToString(float x, float y) {
    return XpsPointToString(x, y);
}

CXmlNode* CXpsPage::addFixedResourcesSection() {
    CXmlNode* fixedPage_Resources_Node = new CXmlNode(L"FixedPage.Resources", L"", NULL);
    if (fixedPage_Resources_Node) {
        CXmlNode* resourceDictionary_Node = new CXmlNode(L"ResourceDictionary", L"", fixedPage_Resources_Node, TRUE);

        this->addItem(fixedPage_Resources_Node);
    }
    
    return fixedPage_Resources_Node;
}

CXmlNode* CXpsPage::getResourceDictionary() {
    return CXmlDocument::getFirstItem(this->_items, L"ResourceDictionary");
}

CXmlNode* CXpsPage::createFixedResourceRectangle(CString resourceId, float x1, float y1, float width, float height) {
    CXmlNode* rd = this->getResourceDictionary();
    if (!rd) {
        this->addFixedResourcesSection();
    }
    
    rd = this->getResourceDictionary();
    if (!rd) {
        return NULL;
    }
    
    CXmlNode* pathGeometry_Node = new CXmlNode(L"PathGeometry", L"", rd, TRUE);
    if (pathGeometry_Node) {
        pathGeometry_Node->addAttribute(L"Key", resourceId, L"x");
        CXmlNode* pathFigure_Node = new CXmlNode(L"PathFigure", L"", pathGeometry_Node, TRUE);
        if (pathFigure_Node) {
            pathFigure_Node->addAttribute(L"StartPoint", this->pointToString(x1, y1));
            pathFigure_Node->addAttribute(L"IsClosed", L"true");            
            CXmlNode* polyLineSegment_Node = new CXmlNode(L"PolyLineSegment", L"", pathFigure_Node, TRUE);
            if (polyLineSegment_Node) {
                float x2 = x1 + width;
                float y2 = y1 + height;
                polyLineSegment_Node->addAttribute(L"Points",
                    this->pointToString(x2, y1) + L" " +
                    this->pointToString(x2, y2) + L" " +
                    this->pointToString(x1, y2));
            }
        }
    }
    
    return pathGeometry_Node;
}

CXmlNode* CXpsPage::addCanvas(CXmlNode* parentNode) {
    CXmlNode* canvas_Node = new CXmlNode(L"Canvas", L"", parentNode, TRUE);
    return canvas_Node;
}

CXmlNode* CXpsPage::addCanvas(CXmlNode* parentNode, float m11, float m12, float m21, float m22, float dx, float dy) {
    CXmlNode* canvas_Node = new CXmlNode(L"Canvas", L"", parentNode, TRUE);
    if (canvas_Node) {
        CXpsRenderTransform rt(m11, m12, m21, m22, dx, dy);
        canvas_Node->addAttribute(L"RenderTransform", rt.toString());
    }
    
    return canvas_Node;
}

CXmlNode* CXpsPage::addCanvas(CXmlNode* parentNode, CXpsRenderTransform* rt) {
    CXmlNode* canvas_Node = new CXmlNode(L"Canvas", L"", parentNode, TRUE);
    if (canvas_Node && rt) {
        canvas_Node->addAttribute(L"RenderTransform", rt->toString());
    }
    
    return canvas_Node;
}

CXmlNode* CXpsPage::addPath(CXmlNode* canvasNode, CString data, CString borderColor, float borderThickness, CString strokeLineJoin, float strokeMiterLimit, CString fillColor, float borderDashGap, float borderDashLine, CString clip) {
    if (canvasNode) {
        CXmlNode* node = new CXmlNode(L"Path", L"", canvasNode, TRUE);
        if (node) {
            CString strokeDashArray = COMMON_EMPTY_STRING;
            if (borderDashGap > 0.01 || borderDashLine > 0.01) {
                strokeDashArray = XpsFloatToString(borderDashLine) + L" " + XpsFloatToString(borderDashGap);
            }
            
            node->addAttribute(L"Data", data);
            if (borderColor != COMMON_EMPTY_STRING) {
                node->addAttribute(L"Stroke", borderColor);
            }
            if (borderThickness > 0.01) {
                node->addAttribute(L"StrokeThickness", XpsFloatToString(borderThickness));
            }
            if (strokeLineJoin != COMMON_EMPTY_STRING) {
                node->addAttribute(L"StrokeLineJoin", strokeLineJoin);
            }
            if (fillColor != COMMON_EMPTY_STRING) {
                node->addAttribute(L"Fill", fillColor);
            }
            if (strokeMiterLimit > 0.01) {
                node->addAttribute(L"StrokeMiterLimit", XpsFloatToString(strokeMiterLimit));
            }
            if (strokeDashArray != COMMON_EMPTY_STRING) {
                node->addAttribute(L"StrokeDashArray", strokeDashArray);
            }
            if (clip != COMMON_EMPTY_STRING) {
                node->addAttribute(L"Clip", clip);
            }
        }
        
        return node;
    }
    
    return NULL;
}

CXmlNode* CXpsPage::addLine(CXmlNode* canvasNode, float x1, float y1, float x2, float y2, CString color, float thickness, float strokeMiterLimit, float borderDashGap, float borderDashLine) {
    return this->addPath(canvasNode,
                    CString(L"M ") + XpsPointToString(x1, y1) + L" L " + XpsPointToString(x2, y2) + L" Z ",
                    color, thickness, L"Miter", strokeMiterLimit, COMMON_EMPTY_STRING, borderDashGap, borderDashLine, COMMON_EMPTY_STRING);
}

CXmlNode* CXpsPage::addHLine(CXmlNode* canvasNode, float x, float y, float width, CString color, float thickness, float strokeMiterLimit, float borderDashGap, float borderDashLine) {
    return this->addPath(canvasNode,
                    CString(L"M ") + XpsPointToString(x, y) + L" L " + XpsPointToString(x + width, y) + L" Z ",
                    color, thickness, L"Miter", strokeMiterLimit, COMMON_EMPTY_STRING, borderDashGap, borderDashLine, COMMON_EMPTY_STRING);
}

CXmlNode* CXpsPage::addDashedHLine(CXmlNode* canvasNode, float x, float y, float width, CString color, float thickness, float strokeMiterLimit, float borderDashGap, float borderDashLine) {
    if (canvasNode) {
        float cur_x = x;
        INT i = 0;
        CString data = CString(L"M ") + XpsPointToString(x, y) + L" ";
        while (cur_x < x + width) {
            if ((i&1) == 0) {
                data += CString(L" L ") + XpsPointToString(cur_x + borderDashLine, y);
                cur_x += borderDashLine;
            } else {
                data += CString(L" M ") + XpsPointToString(cur_x + borderDashGap, y);
                cur_x += borderDashGap;
            }
            i++;
        }
        float ending = borderDashLine / 2;
        if (ending > 2) {
            ending = 2;
        }
        data += CString(L" M ") + XpsPointToString(x + width - ending, y) + L" L " + XpsPointToString(x + width, y);
        
        return this->addPath(canvasNode,
                        data,
                        color, thickness, L"Miter", strokeMiterLimit, COMMON_EMPTY_STRING,
                        0, 0, COMMON_EMPTY_STRING);
    }
    
    return NULL;
}

CXmlNode* CXpsPage::addVLine(CXmlNode* canvasNode, float x, float y, float height, CString color, float thickness, float strokeMiterLimit, float borderDashGap, float borderDashLine) {
    return this->addPath(canvasNode,
                    CString(L"M ") + XpsPointToString(x, y) + L" L " + XpsPointToString(x, y + height) + L" Z ",
                    color, thickness, L"Miter", strokeMiterLimit, COMMON_EMPTY_STRING,
                    borderDashGap, borderDashLine, COMMON_EMPTY_STRING);
}

CXmlNode* CXpsPage::addDashedVLine(CXmlNode* canvasNode, float x, float y, float height, CString color, float thickness, float strokeMiterLimit, float borderDashGap, float borderDashLine) {
    if (canvasNode) {
        float cur_y = y;
        INT i = 0;
        CString data = CString(L"M ") + XpsPointToString(x, y) + L" ";
        while (cur_y < y + height) {
            if ((i&1) == 0) {
                data += CString(L" L ") + XpsPointToString(x, cur_y + borderDashLine);
                cur_y += borderDashLine;
            } else {
                data += CString(L" M ") + XpsPointToString(x, cur_y + borderDashGap);
                cur_y += borderDashGap;
            }
            i++;
        }
        float ending = borderDashLine / 2;
        if (ending > 2) {
            ending = 2;
        }
        data += CString(L" M ") + XpsPointToString(x, y + height - ending) + L" L " + XpsPointToString(x, y + height);
        
        return this->addPath(canvasNode,
                        data,
                        color, thickness, L"Miter", strokeMiterLimit, COMMON_EMPTY_STRING,
                        0, 0, COMMON_EMPTY_STRING);
    }
    
    return NULL;
}

CXmlNode* CXpsPage::addRectangle(CXmlNode* canvasNode, float x1, float y1, float width, float height, CString color, float borderThickness, float strokeMiterLimit, CString fillColor, float borderDashGap, float borderDashLine) {
    if (canvasNode) {
        CString data = CString(L"M ") + XpsPointToString(x1, y1) + L" L " + XpsPointToString(x1 + width, y1) +
                        L" L " + XpsPointToString(x1 + width, y1 + height) +
                        L" L " + XpsPointToString(x1, y1 + height) +
                        L" L " + XpsPointToString(x1, y1) + L" Z ";
        return this->addPath(canvasNode, data, color, borderThickness, L"Miter", strokeMiterLimit,
                             fillColor, borderDashGap, borderDashLine, COMMON_EMPTY_STRING);
    }
    
    return NULL;
}

CXmlNode* CXpsPage::addRectangleLB(CXmlNode* canvasNode, float x1, float y1, float width, float height, CString color, float borderThickness, float strokeMiterLimit, float borderDashGap, float borderDashLine) {
    if (canvasNode) {
        CString data = CString(L"M ") + XpsPointToString(x1, y1) + L" L " + XpsPointToString(x1, y1 + height) +
                        L" L " + XpsPointToString(x1 + width, y1 + height);
        return this->addPath(canvasNode, data, color, borderThickness, L"Miter", strokeMiterLimit,
                             COMMON_EMPTY_STRING, borderDashGap, borderDashLine, COMMON_EMPTY_STRING);
    }
    
    return NULL;
}

CXmlNode* CXpsPage::addRectangleBR(CXmlNode* canvasNode, float x1, float y1, float width, float height, CString color, float borderThickness, float strokeMiterLimit, float borderDashGap, float borderDashLine) {
    if (canvasNode) {
        CString data = CString(L"M ") + XpsPointToString(x1, y1) + L" L " + XpsPointToString(x1 + width, y1) +
                        L" L " + XpsPointToString(x1 + width, y1 - height);
        return this->addPath(canvasNode, data, color, borderThickness, L"Miter", strokeMiterLimit,
                             COMMON_EMPTY_STRING, borderDashGap, borderDashLine, COMMON_EMPTY_STRING);
    }
    
    return NULL;
}

CXmlNode* CXpsPage::addRectangleLBR(CXmlNode* canvasNode, float x1, float y1, float width, float height, CString color, float borderThickness, float strokeMiterLimit, float borderDashGap, float borderDashLine) {
    if (canvasNode) {
        CString data = CString(L"M ") + XpsPointToString(x1, y1) + L" L " + XpsPointToString(x1, y1 + height) +
                        L" L " + XpsPointToString(x1 + width, y1 + height) +
                        L" L " + XpsPointToString(x1 + width, y1);
        return this->addPath(canvasNode, data, color, borderThickness, L"Miter", strokeMiterLimit,
                             COMMON_EMPTY_STRING, borderDashGap, borderDashLine, COMMON_EMPTY_STRING);
    }
    
    return NULL;
}

CXmlNode* CXpsPage::addDateFieldDDMMYYYY(CXmlNode* canvasNode, float x, float y, float width, float height, float groupSpacing, float fontSize, CString fontFilename, CString color, float borderThickness, float strokeMiterLimit, float borderDashGap, float borderDashLine) {
    if (canvasNode) {
        CXmlNode* container = this->addCanvas(canvasNode);
        float itemWidth = (width - 2*groupSpacing)/8;
        float h = 3;
        float by = y + height - fontSize - h - 2;
        
        CString fieldData = CString(L"M ") + XpsPointToString(x, by) +
                        // first two element group field for day
                        L" L " + XpsPointToString(x, by+h) +
                        L" L " + XpsPointToString(x + itemWidth, by+h) +
                        L" L " + XpsPointToString(x + itemWidth, by) +
                        L" L " + XpsPointToString(x + itemWidth, by+h) +
                        L" L " + XpsPointToString(x + 2*itemWidth, by+h) +
                        L" L " + XpsPointToString(x + 2*itemWidth, by) +
                        L" L " + XpsPointToString(x + 2*itemWidth, by+h) +
                        
                        // group spacing
                        L" M " + XpsPointToString(x + 2*itemWidth + groupSpacing, by) +
                        
                        // second two element group field for month
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing, by+h) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + itemWidth, by+h) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + itemWidth, by) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + itemWidth, by+h) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + 2*itemWidth, by+h) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + 2*itemWidth, by) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + 2*itemWidth, by+h) +
                        
                        // group spacing
                        L" M " + XpsPointToString(x + 2*itemWidth + groupSpacing + 2*itemWidth + groupSpacing, by) +
                        
                        // third two element group field for year
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + 2*itemWidth + groupSpacing, by+h) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + 2*itemWidth + groupSpacing + itemWidth, by+h) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + 2*itemWidth + groupSpacing + itemWidth, by) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + 2*itemWidth + groupSpacing + itemWidth, by+h) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + 2*itemWidth + groupSpacing + 2*itemWidth, by+h) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + 2*itemWidth + groupSpacing + 2*itemWidth, by) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + 2*itemWidth + groupSpacing + 2*itemWidth, by+h) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + 2*itemWidth + groupSpacing + 3*itemWidth, by+h) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + 2*itemWidth + groupSpacing + 3*itemWidth, by) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + 2*itemWidth + groupSpacing + 3*itemWidth, by+h) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + 2*itemWidth + groupSpacing + 4*itemWidth, by+h) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + 2*itemWidth + groupSpacing + 4*itemWidth, by) +
                        L" L " + XpsPointToString(x + 2*itemWidth + groupSpacing + 2*itemWidth + groupSpacing + 4*itemWidth, by+h);

        this->addPath(container, fieldData, color, borderThickness, L"Miter", strokeMiterLimit,
                      COMMON_EMPTY_STRING, borderDashGap, borderDashLine, COMMON_EMPTY_STRING);
        this->addGlyph(container, x,                                                           y + height, L"dzień", color, fontFilename, fontSize);
        this->addGlyph(container, x + 2*itemWidth + groupSpacing,                              y + height, L"miesiąc", color, fontFilename, fontSize);
        this->addGlyph(container, x + 2*itemWidth + groupSpacing + 2*itemWidth + groupSpacing + itemWidth, y + height, L"rok", color, fontFilename, fontSize);
    }
    
    return NULL;
}

CXmlNode* CXpsPage::addSquare(CXmlNode* canvasNode, float x1, float y1, float width, CString color, float borderThickness, float strokeMiterLimit, CString fillColor, float borderDashGap, float borderDashLine) {
    return this->addRectangle(canvasNode, x1, y1, width, width, color, borderThickness, strokeMiterLimit, fillColor, borderDashGap, borderDashLine);
}

CXmlNode* CXpsPage::addCircle(CXmlNode* canvasNode, float cx, float cy, float r, CString color, float borderThickness, CString fillColor, float borderDashGap, float borderDashLine) {
    if (canvasNode) {
        CString data = CString(L"M ") + XpsPointToString(cx + r, cy) + L" A " + XpsPointToString(r, r) +
                        L" 0 0 1" +
                        L" " + XpsPointToString(cx - r, cy) +
                        L" A " + XpsPointToString(r, r) +
                        L" 0 0 1" +
                        L" " + XpsPointToString(cx + r, cy);
        CXmlNode* node = new CXmlNode(L"Path", L"", canvasNode, TRUE);
        if (node) {
            node->addAttribute(L"Data", data);
            node->addAttribute(L"Stroke", color);
            node->addAttribute(L"StrokeThickness", XpsFloatToString(borderThickness));
            if (fillColor != COMMON_EMPTY_STRING) {
                node->addAttribute(L"Fill", fillColor);
            }
            if (borderDashGap > 0.01 || borderDashLine > 0.01) {
                node->addAttribute(L"StrokeDashArray", XpsFloatToString(borderDashLine) + L" " + XpsFloatToString(borderDashGap));
            }
        }
        
        return node;
    }
    
    return NULL;
}

CXmlNode* CXpsPage::addGlyph(CXmlNode* canvasNode, float x, float y, CString text, CString fill, CString fontFilename, float fontSize, float rotationAngle, CString styleSimulation, CXpsRenderTransform* rt) {
    text.replace(L"\"", L"&apos;");
    if (canvasNode && text.length() > 0) {
        CXmlNode* node = new CXmlNode(L"Glyphs", L"", canvasNode, TRUE);
        if (node) {
            if (styleSimulation == COMMON_EMPTY_STRING) {
                styleSimulation = L"None";
            }
            node->addAttribute(L"Fill", fill);
            node->addAttribute(L"FontUri", this->_doc->getDocumentInternalDirectory() + L"/Resources/Fonts/" + fontFilename);
            node->addAttribute(L"FontRenderingEmSize", XpsFloatToString(fontSize));
            node->addAttribute(L"StyleSimulations", styleSimulation);
            node->addAttribute(L"UnicodeString", text);
            if (!rt) {
                if (fabs(rotationAngle) > 0.01) {
                    CXpsRenderTransform mt;
                    mt.setParamsForRotation(rotationAngle);
                    mt.addParamsForTranslation(x, y);
                    node->addAttribute(L"RenderTransform", mt.toString());
                    node->addAttribute(L"OriginX", XpsFloatToString(0));
                    node->addAttribute(L"OriginY", XpsFloatToString(0));
                } else {
                    node->addAttribute(L"OriginX", XpsFloatToString(x));
                    node->addAttribute(L"OriginY", XpsFloatToString(y));
                }
            } else {
                node->addAttribute(L"RenderTransform", rt->toString());
                node->addAttribute(L"OriginX", XpsFloatToString(0));
                node->addAttribute(L"OriginY", XpsFloatToString(0));
            }
        }
        
        return node;
    }
    
    return NULL;
}

CXmlNode* CXpsPage::addVerticalGlyph(CXmlNode* canvasNode, float x, float y, CString text, CString fill, CString fontFilename, float fontSize, BOOL clockwise, CString styleSimulation) {
    float rotationAngle;
    if (clockwise) {
        rotationAngle = M_PI_2;
    } else {
        rotationAngle = -M_PI_2;
    }
    return this->addGlyph(canvasNode, x, y, text, fill, fontFilename, fontSize, rotationAngle, styleSimulation, NULL);
}

CXmlNode* CXpsPage::addLabel(CXmlNode* canvasNode, float x, float y, CString text, float width, float height, CString fontFilename, float fontSize, CString color, CString styleSimulation, CString borderColor, CString fillColor, float borderThickness, float strokeMiterLimit, float paddingLeft, float paddingTop, float rotationAngle) {
    if (canvasNode) {
        // default container is canvasNode
        CXmlNode* container = canvasNode;
        CXmlNode* node;
        BOOL addedContainer = FALSE;
        
        CXpsRenderTransform rt;
        if (abs(rotationAngle) > 0.01) {
            rt.setParamsForRotation(rotationAngle);
        }
        rt.addParamsForTranslation(x, y);
        
        container = this->addCanvas(canvasNode, &rt);
        if (fillColor != COMMON_EMPTY_STRING) {
            if (borderColor == COMMON_EMPTY_STRING) {
                borderColor = fillColor;
            }
            node = this->addRectangle(container, 0, 0, width, height, borderColor, borderThickness, strokeMiterLimit, fillColor);
        }
        
        if (styleSimulation == COMMON_EMPTY_STRING) {
            styleSimulation = L"None";
        }
        this->addGlyph(container, paddingLeft, fontSize + paddingTop, text, color, fontFilename, fontSize, 0, styleSimulation, NULL);
        
        if (container != canvasNode) {
            return container;
        }
        return node;
    }
    
    return NULL;
}

CXmlNode* CXpsPage::addLabeledTextField(CXmlNode* canvasNode, float x, float y, CString label,
                        float width, float height, CString fontFilename, float fontSize, CString color,
                        CString styleSimulation, float textFieldUnitWidth, CString borderColor,
                        CString textFieldFillColor,
                        CString labelLineBorderColor, CString labelLineFillColor, float borderThickness,
                        float strokeMiterLimit,
                        float labelPaddingLeft, float labelPaddingTop, float rotationAngle) {
    if (canvasNode) {
        CXmlNode* container;
        CXmlNode* node;
        
        CXpsRenderTransform rt;
        if (abs(rotationAngle) > 0.01) {
            rt.setParamsForRotation(rotationAngle);
        }
        rt.addParamsForTranslation(x, y);
        
        float labelTextWidth;
        float labelTextHPadding;
        XpsMeasureString(label.getBuffer(), fontSize, L"None", COMMON_EMPTY_STRING, &labelTextWidth, NULL);
        XpsMeasureString(L"A", fontSize, L"None", COMMON_EMPTY_STRING, &labelTextHPadding, NULL);
        labelTextHPadding /= 2;
        
        container = this->addCanvas(canvasNode, &rt);
        // adding component background
        this->addRectangle(container, 0, 0, width, height, textFieldFillColor, 0, strokeMiterLimit, textFieldFillColor);
        
        // label line bg
        node = this->addRectangle(container, 0, labelPaddingTop, width, fontSize, labelLineBorderColor, 0, strokeMiterLimit, labelLineFillColor);
        // label text bg
        if (label != COMMON_EMPTY_STRING && labelLineFillColor != textFieldFillColor) {
            // we draw it only whence there is a label
            node = this->addRectangle(container, labelPaddingLeft - labelTextHPadding, labelPaddingTop,
                                labelTextWidth + 2*labelTextHPadding, fontSize, textFieldFillColor,
                                0, strokeMiterLimit, textFieldFillColor);
        }
        // text field bg
        //node = this->addRectangle(container, 0, labelPaddingTop + fontSize, width, height-(labelPaddingTop+fontSize), textFieldFillColor, 0, textFieldFillColor);
        if (textFieldUnitWidth > 0.01) {
            INT unitCount = width / textFieldUnitWidth;
            CString data;
            float cur_x;
            INT i;
            for (i = 1; i < unitCount; i++) {
                cur_x = i * textFieldUnitWidth;
                data += CString(L"M ") + XpsPointToString(cur_x, height - 3) +
                        L" L " + XpsPointToString(cur_x, height) + L" ";
            }
            cur_x = i * textFieldUnitWidth;
            if (fabs(width - cur_x) >= textFieldUnitWidth) {
                data += CString(L"M ") + XpsPointToString(cur_x, height - 3) +
                        L" L " + XpsPointToString(cur_x, height) + L" ";
            }
            this->addPath(container,
                          data,
                          color, borderThickness, L"Miter", strokeMiterLimit, COMMON_EMPTY_STRING,
                          0, 0, COMMON_EMPTY_STRING);
        }
        // border
        if (borderThickness > 0.01) {
            node = this->addRectangle(container, 0, 0, width, height, borderColor, borderThickness, strokeMiterLimit);
        }
        // label text if one is supplied
        if (label != COMMON_EMPTY_STRING) {
            node = this->addGlyph(container, labelPaddingLeft, labelPaddingTop + fontSize, label, color, fontFilename, fontSize);
        }
        
        return container;
    }
   
    return NULL;
}

CXmlNode* CXpsPage::addTextInRectangles(CXmlNode* canvasNode, float x, float y, CString text, float unitWidth, float unitHeight, float spacing, CString fontFilename, float fontSize, CString color, CString styleSimulation, CString borderColor, CString fillColor, float borderThickness, float strokeMiterLimit, float rotationAngle) {
    if (canvasNode) {
        CXmlNode* container;
        CXmlNode* node;
        
        CXpsRenderTransform rt;
        if (abs(rotationAngle) > 0.01) {
            rt.setParamsForRotation(rotationAngle);
        }
        rt.addParamsForTranslation(x, y);
        
        if (borderColor == COMMON_EMPTY_STRING) {
            borderColor = color;
        }
        
        container = this->addCanvas(canvasNode, &rt);
        // text unit boxes
        INT unitCount = text.length();
        CString data;
        float cur_x;
        INT i;
        if (borderThickness > 0.01 && unitCount > 0) {
            for (i = 0; i < unitCount; i++) {
                cur_x = i*unitWidth + i*spacing;
                data += CString(L"M ") + XpsPointToString(cur_x, 0) +
                        L" L " + XpsPointToString(cur_x + unitWidth, 0) +
                        L" L " + XpsPointToString(cur_x + unitWidth, unitHeight) +
                        L" L " + XpsPointToString(cur_x, unitHeight) +
                        L" L " + XpsPointToString(cur_x, 0);
            }
            this->addPath(container, data, borderColor, borderThickness, L"Miter", strokeMiterLimit,
                          fillColor, 0, 0, COMMON_EMPTY_STRING);
        }
        
        // label text if one is supplied
        if (text != COMMON_EMPTY_STRING) {
            float tw, th;
            XpsMeasureString(text.getBuffer(), fontSize, L"None", COMMON_EMPTY_STRING, &tw, &th);
            WCHAR ch[2];
            ch[1] = NULL;
            for (i = 0; i < unitCount; i++) {
                cur_x = i*unitWidth + i*spacing;
                ch[0] = text.getCharAt(i);
                XpsMeasureString(ch, fontSize, L"None", COMMON_EMPTY_STRING, &tw, NULL);
                //node = this->addGlyph(container, cur_x + (unitWidth - tw) / 2 , y + fontSize + fontSize/2, ch, color, fontFilename, fontSize);
                node = this->addGlyph(container, cur_x + (unitWidth-tw)/2, th, ch, color, fontFilename, fontSize);
            }
        }
        
        return container;
    }
   
    return NULL;
}


CXmlNode* CXpsPage::drawCharsForAdjustingFontMetrics(CXmlNode* canvasNode, float x, float y, CString text,
                        CString fontFilename, float fontSize, CString color, CString styleSimulation, float height,
                        float* letterWidthsArray) {
    float bottomLineY = fontSize;
    CXpsRenderTransform rt;
    rt.setParamsForTranslation(x, y);
    CXmlNode* container = this->addCanvas(canvasNode, &rt);
    
    INT charCount = text.length();
    INT start_x = 1;
    this->addGlyph(container, start_x, bottomLineY, text, color, fontFilename, fontSize, 0, styleSimulation);
    
    CString data = CString(L"M ") + XpsPointToString(0, 0) + L" L " + XpsPointToString(0, height) + L" ";
    float cur_x;
    INT i, j;
    CString color1 = L"#ff0000";
    CString color2 = L"#00ff00";
    
    bottomLineY += 2;
    // first even chars
    for (i = 0; i < charCount; i+=2) {
        cur_x = start_x;
        for (j = 0; j < i; j++) {
            cur_x += letterWidthsArray[j];
        }
        data += CString(L"M ") + XpsPointToString(cur_x, bottomLineY) + L" L " +
                XpsPointToString(cur_x + letterWidthsArray[i], bottomLineY) + L" ";
    }
    this->addPath(container,
                  data,
                  color1, 0, COMMON_EMPTY_STRING, 0, COMMON_EMPTY_STRING,
                  0, 0, COMMON_EMPTY_STRING);
    
    // now odd chars
    data = CString(L"M ") + XpsPointToString(x + letterWidthsArray[0], bottomLineY) + L" ";
    for (i = 1; i < charCount; i+=2) {
        cur_x = start_x;
        for (j = 0; j < i; j++) {
            cur_x += letterWidthsArray[j];
        }
        data += CString(L"M ") + XpsPointToString(cur_x, bottomLineY) + L" L " +
                XpsPointToString(cur_x + letterWidthsArray[i], bottomLineY) + L" ";
    }
    this->addPath(container,
                  data,
                  color2, 0, COMMON_EMPTY_STRING, 0, COMMON_EMPTY_STRING,
                  0, 0, COMMON_EMPTY_STRING);
    
    return container;
}

CString CXpsPage::getIndicesAttrForText(CString text, INT deltaIndiceSpacing) {
    INT len = text.length();
    WCHAR* textbuf = text.getBuffer();
    WCHAR current_letter;
    BOOL result;
    INT index, spacing;
    INT start, j;
    INT custom_spacing_size = 32;
    WCHAR custom_spacing[32];
    CString index_data;
    
    for (INT i = 0; i < len; i++) {
        current_letter = textbuf[i];
        // first we process custom spaces (if we found one), this mean that substring of form [space:23] is
        // treated as ch = ' ' and spacing = 23
        if (wcsstr(&textbuf[i], L"[space:") == &textbuf[i]) {
            start = i;
            i += 7;
            j = 0;
            while (i < len && (j < custom_spacing_size - 1) && textbuf[i] != L']') {
                custom_spacing[j] = textbuf[i];
                j++;
                i++;
            }
            if (j < custom_spacing_size) {
                custom_spacing[j] = NULL;
            }
            if (i == len || textbuf[i] != L']' || j == custom_spacing_size || !isDecimal(custom_spacing)) {
                return COMMON_EMPTY_STRING;
            }
            custom_spacing[custom_spacing_size-1] = NULL;
            result = TRUE;
            index = 3; // INDEX of the ' ' CHAR
            spacing = _wtoi(custom_spacing);
        } else {
            result = this->getIndexDataForChar(textbuf[i], index, spacing);
        }
        
        if (!result) {
            return NULL;
        } else if (index != 0) {            
            if (spacing != 0) {
                spacing += deltaIndiceSpacing;
            } else {
                spacing += deltaIndiceSpacing + 59;
            }
            
            if (spacing != 0) {
                index_data = index_data + index + L"," + spacing + L";";
            } else {
                index_data = index_data + index + L";";
            }
        } else {
            return COMMON_EMPTY_STRING;
        }
    }
    
    index_data.rtrim(L';');
    return index_data;
}

CXmlNode* CXpsPage::addGlyphWithIndices(CXmlNode* canvasNode, float x, float y, CString text, CString fill, CString fontFilename, float fontSize, INT deltaIndiceSpacing, float rotationAngle) {
    if (text.length() == 0) {
        return NULL;
    }
    
    text.replace(L"\"", L"&apos;");
    CString index_data = this->getIndicesAttrForText(text, deltaIndiceSpacing);
    
    if (canvasNode) {
        CXmlNode* node = new CXmlNode(L"Glyphs", L"", canvasNode, TRUE);
        if (node) {
            node->addAttribute(L"Fill", fill);
            node->addAttribute(L"FontUri", this->_doc->getDocumentInternalDirectory() + L"/Resources/Fonts/" + fontFilename);
            node->addAttribute(L"FontRenderingEmSize", XpsFloatToString(fontSize));
            node->addAttribute(L"StyleSimulations", L"None");
            node->addAttribute(L"Indices", index_data);
            node->addAttribute(L"UnicodeString", text);
            if (rotationAngle > 0.01 || rotationAngle < -0.01) {
                CXpsRenderTransform mt;
                mt.setParamsForRotation(rotationAngle);
                mt.addParamsForTranslation(x, y);
                node->addAttribute(L"RenderTransform", mt.toString());
                node->addAttribute(L"OriginX", XpsFloatToString(0));
                node->addAttribute(L"OriginY", XpsFloatToString(0));
            } else {
                node->addAttribute(L"OriginX", XpsFloatToString(x));
                node->addAttribute(L"OriginY", XpsFloatToString(y));
            }
        }
        
        return node;
    }
    
    return NULL;
}

CXmlNode* CXpsPage::addCollectionOfGlyphs(CXmlNode* canvasNode, float startX, float startY, CString text, CString fill, CString fontFilename, float fontSize, float deltaX, float deltaY, float rotationAngle) {
    if (canvasNode) {
        float x = startX;
        float y = startY;
        WCHAR ch;
        CXmlNode* node;
        CXmlNode* canvas;
        
        text.replace(L"\"", L"&apos;");
        canvas = this->addCanvas(canvasNode);
        if (canvas) {
            for (UINT i = 0; i < text.length(); i++) {
                ch = text.getCharAt(i);
                node = new CXmlNode(L"Glyphs", L"", canvas, TRUE);
                if (node) {
                    node->addAttribute(L"Fill", fill);
                    node->addAttribute(L"FontUri", this->_doc->getDocumentInternalDirectory() + L"/Resources/Fonts/" + fontFilename);
                    node->addAttribute(L"FontRenderingEmSize", XpsFloatToString(fontSize));
                    node->addAttribute(L"StyleSimulations", L"None");
                    node->addAttribute(L"UnicodeString", ch);
                    if (rotationAngle > 0.01 || rotationAngle < -0.01) {
                        CXpsRenderTransform mt;
                        mt.setParamsForRotation(rotationAngle);
                        mt.addParamsForTranslation(x, y);
                        node->addAttribute(L"RenderTransform", mt.toString());
                        node->addAttribute(L"OriginX", XpsFloatToString(0));
                        node->addAttribute(L"OriginY", XpsFloatToString(0));
                    } else {
                        node->addAttribute(L"OriginX", XpsFloatToString(x));
                        node->addAttribute(L"OriginY", XpsFloatToString(y));
                    }
                }
                x += deltaX;
                y += deltaY;
            }
        }
        
        return canvas;
    }
    
    return NULL;
}


BOOL CXpsPage::getIndexDataForChar(WCHAR ch, INT& index, INT& spacing) {
    INT i;
    index = 0;
    spacing = 0;
    BOOL result;
    
    for (i = 0; i < CXpsPage::_small_letters_len; i++) {
        if (this->_small_letters[i] == ch) break;
    }
    if (i < CXpsPage::_small_letters_len) {
        result = this->_getIndexData(i, CXpsPage::_small_letters_indices, index, spacing);
        if (result) return result;
    }

    
    for (i = 0; i < CXpsPage::_big_letters_len; i++) {
        if (this->_big_letters[i] == ch) break;
    }
    if (i < CXpsPage::_big_letters_len) {
        result = this->_getIndexData(i, CXpsPage::_big_letters_indices, index, spacing);
        if (result) return result;
    }
    
    for (i = 0; i < CXpsPage::_small_pl_letters_len; i++) {
        if (this->_small_pl_letters[i] == ch) break;
    }
    if (i < CXpsPage::_small_pl_letters_len) {
        result = this->_getIndexData(i, CXpsPage::_small_pl_letters_indices, index, spacing);
        if (result) return result;
    }
    
    for (i = 0; i < CXpsPage::_big_pl_letters_len; i++) {
        if (this->_big_pl_letters[i] == ch) break;
    }
    if (i < CXpsPage::_big_pl_letters_len) {
        result = this->_getIndexData(i, CXpsPage::_big_pl_letters_indices, index, spacing);
        if (result) return result;
    }
    
    for (i = 0; i < CXpsPage::_digits_len; i++) {
        if (this->_digits[i] == ch) break;
    }
    if (i < CXpsPage::_digits_len) {
        result = this->_getIndexData(i, CXpsPage::_digits_indices, index, spacing);
        if (result) return result;
    }
    
    for (i = 0; i < CXpsPage::_special_chars_len; i++) {
        if (CXpsPage::_special_chars[i] == ch) break;
    }
    if (i < CXpsPage::_special_chars_len) {
        result = this->_getIndexData(i, CXpsPage::_special_chars_indices, index, spacing);
        if (result) return result;
    }
    
    for (i = 0; i < CXpsPage::_digits_upper_chars_len; i++) {
        if (CXpsPage::_digits_upper_chars[i] == ch) break;
    }
    if (i < CXpsPage::_digits_upper_chars_len) {
        result = this->_getIndexData(i, CXpsPage::_digits_upper_chars_indices, index, spacing);
        if (result) return result;
    }
    
    return FALSE;
}


BOOL CXpsPage::_getIndexData(INT pos, WCHAR* indices_string, INT& index, INT& spacing) {
    index = 0;
    spacing = 0;
    
    INT i;
    INT len = wcslen(indices_string);
    INT count = 0;
    WCHAR* index_data_substring = indices_string;
    
    for (i = 0; i < len; i++) {
        if (indices_string[i] == L';') {
            count += 1;
            if (count == pos + 1) {
                break;
            } else {
                index_data_substring = &indices_string[i+1];
            }
        }
    }
    
    i = 0;
    WCHAR number_buf[64];
    while (index_data_substring[i] != NULL && index_data_substring[i] != L',' && index_data_substring[i] != L';') {
        number_buf[i] = index_data_substring[i];
        i++;
    }
    number_buf[i] = NULL;
    index = _wtoi(number_buf);
    
    if (index_data_substring[i] == L',') {
        index_data_substring = &index_data_substring[i+1];
        i = 0;
        while (index_data_substring[i] != NULL && index_data_substring[i] != L';') {
            number_buf[i] = index_data_substring[i];
            i++;
        }
        number_buf[i] = NULL;
        spacing = _wtoi(number_buf);
    }
    
    if (index == 0 && spacing == 0) {
        return FALSE;
    }
    
    return TRUE;
}
