
//  This file forms part of the GOBLIN C++ Class Library.
//
//  Initially written by Christian Fremuth-Paeger, August 2000
//
//  Copying, compiling, distribution and modification
//  of this source code is permitted only in accordance
//  with the GOBLIN general licence information.

/// \file canvasBuilder.cpp
/// \brief #canvasBuilder class implementation

#include "canvasBuilder.h"


canvasBuilder::canvasBuilder(const abstractMixedGraph &GC)
    throw(ERRejected) :
    managedObject(GC.Context()),
    G(GC), CFG(GC.Context(),true), piG(G.GetPotentials())
{
    sprintf(predColour,"#%06lX",RGBColour(PRED_COLOUR));
    sprintf(inftyColour,"#%06lX",RGBColour(INFTY_COLOUR));

    ExtractParameters();

    if (nodeColourMode==NODES_FLOATING_COLOURS)
    {
        maxNodeColour = 1;
        for (TNode v=0;v<G.N();v++)
        {
            TNode c = G.NodeColour(v);
            if (c>maxNodeColour && c!=NoNode) maxNodeColour = c;
        }
    }

    if (arcColourMode==ARCS_FLOATING_COLOURS)
    {
        maxEdgeColour = 1;
        for (TArc a=0;a<G.M();a++)
        {
            TArc c = G.EdgeColour(2*a);
            if (c>maxEdgeColour && c!=NoArc) maxEdgeColour = c;
        }
    }

    ComputeBoundingBox();

    CFG.logMeth = 0;

    LogEntry(LOG_MEM,"...Display object instanciated");
}


canvasBuilder::~canvasBuilder() throw()
{
    LogEntry(LOG_MEM,"...Display object disallocated");
}


unsigned long canvasBuilder::Allocated() const throw()
{
    return 255;
}


void canvasBuilder::ComputeBoundingBox() throw()
{
    G.Layout_GetBoundingInterval(0,minX,maxX);
    G.Layout_GetBoundingInterval(1,minY,maxY);
}


double canvasBuilder::ScaleDisplayParameters(double zoomCalibration,double xSize, double ySize) throw()
{
    double shrinkingFactor = 1.0;

    zoom = ((CFG.xZoom>CFG.yZoom) ? CFG.yZoom : CFG.xZoom);
    double layoutXRange = maxX-minX+2.0*nodeSpacing;
    double layoutYRange = maxY-minY+2.0*nodeSpacing;

    if (layoutXRange*zoom>xSize)
    {
        shrinkingFactor = (xSize/layoutXRange)/zoom;
        zoom = xSize/layoutXRange;
    }

    if (layoutYRange*zoom>ySize)
    {
        shrinkingFactor = (ySize/layoutYRange)/zoom;
        zoom = ySize/layoutYRange;
    }

    double fontScaleFactor = zoom * 0.002 * bendSpacing * nodeSizeRel / 100.0;
    arcFontSize  = 7.0  * arcFontSize  / 100.0 * fontScaleFactor;
    nodeFontSize = 12.0 * nodeFontSize / 100.0 * fontScaleFactor;

    zoom *= zoomCalibration;

    nodeSize  = nodeSizeRel /100.0 * zoom * bendSpacing/5.0;
    arrowSize = arrowSizeRel/100.0 * zoom;

    if (bendSpacing/2.0<nodeSpacing/10.0)
    {
        arrowSize *= bendSpacing/2.0;
    }
    else
    {
        arrowSize *= nodeSpacing/10.0;
    }

    arcLabelSep = fineSpacing*zoom;

    if (arcLabelSep<=2*arrowSize) arcLabelSep = 2*arrowSize;

    return shrinkingFactor;
}


long canvasBuilder::PortCX(TNode u,TNode v) throw()
{
    long dx = CanvasCXOfPoint(v)-CanvasCXOfPoint(u);
    long dy = CanvasCYOfPoint(v)-CanvasCYOfPoint(u);
    float norm = sqrt(double(dx*dx+dy*dy));

    if (fabs(norm)<10e-6)
    {
        return CanvasCXOfPoint(v);
    }

    if (nodeShapeMode==NODE_SHAPE_POINT)
    {
        return long(CanvasCXOfPoint(v)-dx*7/norm);
    }
    else
    {
        return long(CanvasCXOfPoint(v)-dx*nodeSize/norm);
    }
}


long canvasBuilder::PortCY(TNode u,TNode v) throw()
{
    long dx = CanvasCXOfPoint(v)-CanvasCXOfPoint(u);
    long dy = CanvasCYOfPoint(v)-CanvasCYOfPoint(u);
    float norm = sqrt(double(dx*dx+dy*dy));

    if (fabs(norm)<10e-6)
    {
        return CanvasCYOfPoint(v);
    }

    if (nodeShapeMode==NODE_SHAPE_POINT)
    {
        return long(CanvasCYOfPoint(v)-dy*5/norm);
    }
    else
    {
        return long(CanvasCYOfPoint(v)-dy*nodeSize/norm);
    }
}


void canvasBuilder::ExtractParameters() throw()
{
    TLayoutModel layoutModel = G.LayoutModel();

    // Copy parameters from the graph layout pool or set default values
    G.GetLayoutParameter(TokLayoutArcShapeMode,arcShapeMode,layoutModel);
    G.GetLayoutParameter(TokLayoutArcWidthMode,arcWidthMode);
    G.GetLayoutParameter(TokLayoutArcWidthMin,arcWidthMin);
    G.GetLayoutParameter(TokLayoutArcWidthMax,arcWidthMax);
    G.GetLayoutParameter(TokLayoutArcStippleMode,arcStippleMode);
    G.GetLayoutParameter(TokLayoutArcVisibilityMode,arcVisibilityMode);
    G.GetLayoutParameter(TokLayoutArcColourMode,arcColourMode);
    G.GetLayoutParameter(TokLayoutArcFontType,arcFontType);
    G.GetLayoutParameter(TokLayoutArcFontSize,arcFontSize);

    G.GetLayoutParameter(TokLayoutArrowDisplayMode,arrowDisplayMode);
    G.GetLayoutParameter(TokLayoutArrowPosMode,arrowPosMode);
    G.GetLayoutParameter(TokLayoutArrowSize,arrowSizeRel);

    G.GetLayoutParameter(TokLayoutNodeShapeMode,nodeShapeMode,layoutModel);
    G.GetLayoutParameter(TokLayoutNodeColourMode,nodeColourMode);
    G.GetLayoutParameter(TokLayoutNodeSize,nodeSizeRel);
    G.GetLayoutParameter(TokLayoutNodeFontSize,nodeFontSize);
    G.GetLayoutParameter(TokLayoutNodeFontType,nodeFontType);

    G.GetLayoutParameter(TokLayoutGridDisplayMode,gridDisplayMode);
    G.GetLayoutParameter(TokLayoutFineSpacing,fineSpacing);
    G.GetLayoutParameter(TokLayoutBendSpacing,bendSpacing);
    G.GetLayoutParameter(TokLayoutNodeSpacing,nodeSpacing);

    char* pNodeLabelFormat = NULL;
    G.GetLayoutParameter(TokLayoutNodeLabelFormat,pNodeLabelFormat);
    strncpy(nodeLabelFormat,pNodeLabelFormat,LABEL_BUFFER_SIZE);
    nodeLabelFormat[LABEL_BUFFER_SIZE-1] = 0;

    char* pArcLabelFormat = NULL;
    G.GetLayoutParameter(TokLayoutArcLabelFormat,pArcLabelFormat);
    strncpy(arcLabelFormat,pArcLabelFormat,LABEL_BUFFER_SIZE);
    arcLabelFormat[LABEL_BUFFER_SIZE-1] = 0;

    // If necessary, enforce a consistent set of layout parameters

    arcWidthMin = (arcWidthMin<1) ? 1 : arcWidthMin;
    arcWidthMax = (arcWidthMax>arcWidthMin) ? arcWidthMax : arcWidthMin;

    if (G.IsDense() && arcVisibilityMode==ARC_DISPLAY_SHOW_ALL)
    {
        arcVisibilityMode = ARC_DISPLAY_PRED_SUB;
    }

    portMode = (layoutModel==LAYOUT_KANDINSKI || layoutModel==LAYOUT_VISIBILITY) ?
        PORTS_EXPLICIT : PORTS_IMPLICIT;
}


void canvasBuilder::DisplayGraph() throw(ERRejected)
{
    #if defined(_FAILSAVE_)

    if (G.Dim()==0)
    {
        G.Error(ERR_REJECTED,"canvasBuilder","Missing geometric embedding");
    }

    #endif

    DisplayGrids();

    for (TArc a=0;a<G.M();a++)
    {
        TArc a2 = 2*a;

        if (G.HiddenArc(a2)) continue;

        if (G.Blocking(a2) && G.Blocking(a2+1)) continue;

        if (G.UCap(a2)==0) continue;

        if (G.StartNode(a2)==G.EndNode(a2) && G.ArcLabelAnchor(a2)==NoNode) continue;

        DisplayArc(a);
    }

    for (TNode v=0;v<G.N();v++)
    {
        if (G.HiddenNode(v)) continue;

        DisplayNode(v);
    }

    if (CFG.legenda>0)
    {
        long xm = long(CanvasCX((minX+maxX)/2.0));
        long ym = long(CanvasCY(maxY+CFG.legenda));

        DisplayLegenda(xm,ym,long(nodeSpacing*zoom));
    }
}


void canvasBuilder::DisplayGrids() throw()
{
    const int GRID_DEPTH = 255;

    // First, display the bounding box
    WriteStraightLine(CanvasCX(minX),CanvasCY(minY),CanvasCX(minX),CanvasCY(maxY),
        LINE_STYLE_SOLID,1,ARROW_NONE,GRID_COLOUR,GRID_DEPTH);
    WriteStraightLine(CanvasCX(maxX),CanvasCY(minY),CanvasCX(maxX),CanvasCY(maxY),
        LINE_STYLE_SOLID,1,ARROW_NONE,GRID_COLOUR,GRID_DEPTH);
    WriteStraightLine(CanvasCX(minX),CanvasCY(minY),CanvasCX(maxX),CanvasCY(minY),
        LINE_STYLE_SOLID,1,ARROW_NONE,GRID_COLOUR,GRID_DEPTH);
    WriteStraightLine(CanvasCX(minX),CanvasCY(maxY),CanvasCX(maxX),CanvasCY(maxY),
        LINE_STYLE_SOLID,1,ARROW_NONE,GRID_COLOUR,GRID_DEPTH);

    if (gridDisplayMode==GRID_DISPLAY_OFF || nodeSpacing<CT.epsilon) return;

    // Display grid lines in the interior of the bounding box
    int minXNodeGrid = int(ceil(minX/nodeSpacing+CT.epsilon));
    int maxXNodeGrid = int(floor(maxX/nodeSpacing-CT.epsilon));
    int minYNodeGrid = int(ceil(minY/nodeSpacing+CT.epsilon));
    int maxYNodeGrid = int(floor((maxY+CFG.legenda)/nodeSpacing-CT.epsilon));

    int i=0;

    for (i=minXNodeGrid;i<=maxXNodeGrid;++i)
    {
        WriteStraightLine(CanvasCX(i*nodeSpacing),CanvasCY(minY),
            CanvasCX(i*nodeSpacing),CanvasCY(maxY),
            LINE_STYLE_DOT,1,ARROW_NONE,GRID_COLOUR,GRID_DEPTH);
    }

    for (i=minYNodeGrid;i<=maxYNodeGrid;++i)
    {
        WriteStraightLine(CanvasCX(minX),CanvasCY(i*nodeSpacing),
            CanvasCX(maxX),CanvasCY(i*nodeSpacing),
            LINE_STYLE_DOT,1,ARROW_NONE,GRID_COLOUR,GRID_DEPTH);
    }
}


void canvasBuilder::DisplayArc(TArc a) throw()
{
    TNode u = G.StartNode(2*a);
    TNode v = G.EndNode(2*a);

    bool isPredecessorArc = (G.Pred(v)==2*a || G.Pred(u)==(2*a+1));

    bool drawSub = true;
    TFloat thisSub = fabs(G.Sub(2*a));
    bool isSubgraphArc = (thisSub>CT.epsilon);

    if (arcVisibilityMode==ARC_DISPLAY_HIDE_ALL)
    {
        drawSub = false;
    }
    else if (arcVisibilityMode==ARC_DISPLAY_SUBGRAPH)
    {
        drawSub = isSubgraphArc;
    }
    else if (arcVisibilityMode==ARC_DISPLAY_PREDECESSORS)
    {
        drawSub = isPredecessorArc;
    }
    else if (arcVisibilityMode==ARC_DISPLAY_PRED_SUB)
    {
        drawSub = isPredecessorArc | isSubgraphArc;
    }

    if (!drawSub) return;

    TArrowDir displayedArrows = ArrowDirections(a);
    TArrowDir displayedExteriorArrows = ARROW_NONE;
    TArrowDir displayedCenteredArrows = ARROW_NONE;

    if (arrowPosMode==ARROWS_CENTERED)
    {
        if (displayedArrows!=ARROW_BOTH)
        {
            displayedCenteredArrows = displayedArrows;
        }
    }
    else
    {
        displayedExteriorArrows = displayedArrows;
    }

    if (drawSub)
    {
        int width = arcWidthMin;

        if (arcWidthMode==ARC_WIDTH_PREDECESSORS)
        {
            if (isPredecessorArc)
            {
                width = arcWidthMax;
            }
        }
        else if (arcWidthMode==ARC_WIDTH_SUBGRAPH)
        {
            if (isSubgraphArc)
            {
                width = arcWidthMax;
            }
        }
        else if (arcWidthMode==ARC_WIDTH_FLOW_LINEAR)
        {
            width += int(thisSub*(arcWidthMax-arcWidthMin));
        }
        else if (arcWidthMode==ARC_WIDTH_FLOW_LOGARITHMIC)
        {
            width += int(log(thisSub+1.0)*(arcWidthMax-arcWidthMin));
        }
        else if (arcWidthMode==ARC_WIDTH_EMPTY_FREE_FULL)
        {
            if (thisSub<G.LCap(2*a)+CT.epsilon)
            {
                width = arcWidthMin;
            }
            else if (thisSub<G.UCap(2*a)-CT.epsilon)
            {
                width = (arcWidthMin+arcWidthMax)/2;
            }
            else
            {
                width = arcWidthMax;
            }
        }

        TLineStyle lineStyle = LINE_STYLE_SOLID;

        if (arcStippleMode==ARC_STIPPLE_PREDECESSORS)
        {
            if (isPredecessorArc)
            {
                lineStyle = LINE_STYLE_DOT;
            }
        }
        else if (arcStippleMode==ARC_STIPPLE_FREE)
        {
            if (   thisSub>G.LCap(2*a)+CT.epsilon
                && thisSub<G.UCap(2*a)-CT.epsilon
               )
            {
                lineStyle = LINE_STYLE_DOT;
            }
        }
        else if (arcStippleMode==ARC_STIPPLE_FRACTIONAL)
        {
            if (floor(thisSub)!=thisSub)
            {
                lineStyle = LINE_STYLE_DOT;
            }
        }
        else if (arcStippleMode==ARC_STIPPLE_COLOURS)
        {
            if (G.EdgeColour(2*a)!=NoArc)
            {
                lineStyle = TLineStyle(int(G.EdgeColour(2*a))%4);
                width = arcWidthMin+int(G.EdgeColour(2*a))/4*(arcWidthMax-arcWidthMin);
            }
        }
        else if (arcStippleMode==ARC_STIPPLE_VOID)
        {
            if (thisSub<CT.epsilon)
            {
                lineStyle = LINE_STYLE_DOT;
            }
        }
        else if (arcStippleMode==ARC_STIPPLE_SUBGRAPH)
        {
            if (thisSub>CT.epsilon)
            {
                lineStyle = LINE_STYLE_DOT;
            }
        }

        TIndex colourIndex = OUTLINE_COLOUR;

        if (arcColourMode==ARCS_REPEAT_COLOURS)
        {
            colourIndex = ZERO_COLOUR + G.EdgeColour(2*a)%(MAX_COLOUR-ZERO_COLOUR);
        }
        else if (arcColourMode==ARCS_COLOUR_PRED)
        {
            if (isPredecessorArc)
            {
                colourIndex = PRED_COLOUR;
            }
        }
        else if (arcColourMode!=ARCS_UNCOLOURED)
        {
            colourIndex = ZERO_COLOUR + G.EdgeColour(2*a);
        }

        int depth = (lineStyle==LINE_STYLE_SOLID) ? 101 : 100;

        WriteArc(a,u,v,lineStyle,width,displayedExteriorArrows,colourIndex,depth);

        if (displayedCenteredArrows!=ARROW_NONE)
        {
            TNode y = G.PortNode(2*a);
            TNode x = u;

            while (y!=NoNode)
            {
                if (portMode==PORTS_IMPLICIT || x!=u)
                {
                    if (displayedCenteredArrows==ARROW_FORWARD) DisplayArrow(a,x,y);
                    if (displayedCenteredArrows==ARROW_BACKWARD) DisplayArrow(a,y,x);
                }

                x = y;
                y = G.ThreadSuccessor(y);
            }

            if (x!=v && portMode==PORTS_IMPLICIT)
            {
                if (displayedCenteredArrows==ARROW_FORWARD) DisplayArrow(a,x,v);
                if (displayedCenteredArrows==ARROW_BACKWARD) DisplayArrow(a,v,x);
            }
        }
    }

    if ((u!=v || G.ArcLabelAnchor(2*a)!=NoNode) && strcmp(arcLabelFormat,"")!=0)
    {
        DisplayArcLabel(a,u,v,displayedCenteredArrows);
    }
}


void canvasBuilder::WriteArc(TArc a,TNode u,TNode v,TLineStyle lineStyle,int width,
    TArrowDir displayedArrows,TIndex colourIndex,int depth) throw()
{
    vector<double> cx;
    vector<double> cy;

    TNode w = G.PortNode(2*a);

    if (w==NoNode)
    {
        cx.push_back(PortCX(v,u));
        cy.push_back(PortCY(v,u));
        cx.push_back(PortCX(u,v));
        cy.push_back(PortCY(u,v));
    }
    else
    {
        if (portMode==PORTS_IMPLICIT)
        {
            cx.push_back(PortCX(w,u));
            cy.push_back(PortCY(w,u));
        }

        TNode x = w;
        while (x!=NoNode)
        {
            double bx = CanvasCXOfPoint(x);
            double by = CanvasCYOfPoint(x);

            if ( cx.size()!=1 &&
                (   bx<CanvasCX(minX)-CT.epsilon
                 || bx>CanvasCX(maxX)+CT.epsilon
                 || by<CanvasCY(minX)-CT.epsilon
                 || by>CanvasCY(maxY)+CT.epsilon
                )
               )
            {
                // Bend node is outside the bounding box

                if (cx.size()>1)
                    WriteArcSegment(cx,cy,lineStyle,width,displayedArrows,colourIndex,depth);

                cx.clear();
                cy.clear();
            }
            else
            {
                cx.push_back(bx);
                cy.push_back(by);
            }

            w = x;
            x = G.ThreadSuccessor(x);
        }

        if (portMode==PORTS_IMPLICIT)
        {
            cx.push_back(PortCX(w,v));
            cy.push_back(PortCY(w,v));
        }
    }

    if (cx.size()>1) WriteArcSegment(cx,cy,lineStyle,width,displayedArrows,colourIndex,depth);
}


void canvasBuilder::WriteArcSegment(vector<double>& cx,vector<double>& cy,TLineStyle lineStyle,int width,
    TArrowDir displayedArrows,TIndex colourIndex,int depth) throw()
{
    if (lineStyle==LINE_STYLE_SOLID)
    {
        WritePolyLine(cx,cy,lineStyle,width,displayedArrows,colourIndex,depth);
    }
    else
    {
        if (arcColourMode!=ARCS_UNCOLOURED)
        {
            WritePolyLine(cx,cy,LINE_STYLE_SOLID,width,displayedArrows,colourIndex,depth+1);
        }
        else
        {
            WritePolyLine(cx,cy,LINE_STYLE_SOLID,width,displayedArrows,ZERO_COLOUR + 3,depth+1);
        }

        WritePolyLine(cx,cy,lineStyle,width,displayedArrows,OUTLINE_COLOUR,depth);
    }
}


void canvasBuilder::WriteStraightLine(double cx1,double cy1,double cx2,double cy2,
    TLineStyle lineStyle,int width,TArrowDir displayedArrows,
    TIndex colourIndex,int depth) throw()
{
    vector<double> cx(2);
    vector<double> cy(2);

    cx[0] = cx1;
    cy[0] = cy1;
    cx[1] = cx2;
    cy[1] = cy2;

    WritePolyLine(cx,cy,lineStyle,width,displayedArrows,colourIndex,depth);
}


canvasBuilder::TArrowDir canvasBuilder::ArrowDirections(TArc a) throw()
{
    switch (arrowDisplayMode)
    {
        case ARROWS_OFF:
        {
            return ARROW_NONE;
        }
        case ARROWS_IMPL_ORIENTATION:
        {
            return ARROW_FORWARD;
        }
        case ARROWS_FLOW_DIRECTION:
        {
            if (G.Sub(2*a)>CT.epsilon)
            {
                return ARROW_FORWARD;
            }
            else if (G.Sub(2*a)<-CT.epsilon)
            {
                return ARROW_BACKWARD;
            }
            else
            {
                return ARROW_NONE;
            }
        }
        case ARROWS_LIKE_PREDECESSORS:
        {
            if (G.Pred(G.EndNode(2*a))==2*a)
            {
                return ARROW_FORWARD;
            }
            else if (G.Pred(G.EndNode(2*a+1))==2*a+1)
            {
                return ARROW_BACKWARD;
            }
            else
            {
                return ARROW_NONE;
            }
        }
        default:
        case ARROWS_ARC_ORIENTATION:
        {
            return TArrowDir(G.Orientation(2*a));
        }
    }
}


void canvasBuilder::DisplayArrow(TArc a,TNode u,TNode v) throw()
{
    double dx = CanvasCXOfPoint(v)-CanvasCXOfPoint(u);
    double dy = CanvasCYOfPoint(v)-CanvasCYOfPoint(u);
    double norm = sqrt(dx*dx+dy*dy);

    if (fabs(norm)<bendSpacing*0.5) return;

    dx = dx/norm;
    dy = dy/norm;

    WriteArrow(a,
        long((CanvasCXOfPoint(u)+CanvasCXOfPoint(v))/2+arrowSize*dx),
        long((CanvasCYOfPoint(u)+CanvasCYOfPoint(v))/2+arrowSize*dy),
        dx,dy);
}


void canvasBuilder::DisplayArcLabel(TArc a,TNode u,TNode v,TArrowDir displayedCenteredArrows) throw()
{
    long xm = 0;
    long ym = 0;
    TNode p = G.ArcLabelAnchor(2*a);

    if (p==NoNode)
    {
        double dx = CanvasCXOfPoint(v)-CanvasCXOfPoint(u);
        double dy = CanvasCYOfPoint(v)-CanvasCYOfPoint(u);
        double norm = sqrt(dx*dx+dy*dy);

        if (fabs(norm)<bendSpacing*0.5) return;

        dx = dx/norm;
        dy = dy/norm;
        xm = long((CanvasCXOfPoint(u)+CanvasCXOfPoint(v))/2+arcLabelSep*dy);
        ym = long((CanvasCYOfPoint(u)+CanvasCYOfPoint(v))/2-arcLabelSep*dx);
    }
    else
    {
        xm = CanvasCXOfPoint(p);
        ym = CanvasCYOfPoint(p);
    }

    WriteArcLabel(a,xm,ym);
}


void canvasBuilder::ComposeArcLabel(TArc a,ofstream& expFile) throw()
{
    if (strcmp(arcLabelFormat,"")==0) return;

    for (unsigned i=0;i<strlen(arcLabelFormat);i++)
    {
        if (arcLabelFormat[i]=='#' && i<strlen(arcLabelFormat)-1)
        {
            expFile << ArcLabel(a,int(arcLabelFormat[i+1]-'0'));
            i++;
        }
        else
        {
            expFile << char(arcLabelFormat[i]);
        }
    }
}


char* canvasBuilder::ArcLabel(TArc a,int option) throw(ERRejected)
{
    switch (option)
    {
        case 0:
        {
            strncpy(tmpLabelBuffer,"",LABEL_BUFFER_SIZE);
            break;
        }
        case 1:
        {
            sprintf(tmpLabelBuffer,"%lu",TIndex(a));
            break;
        }
        case 2:
        {
            if (G.UCap(2*a)==InfCap) return "*";
            sprintf(tmpLabelBuffer,"%g",G.UCap(2*a));
            break;
        }
        case 3:
        {
            sprintf(tmpLabelBuffer,"%g",fabs(G.Sub(2*a)));
            break;
        }
        case 4:
        {
            if (G.Length(2*a)==InfFloat || G.Length(2*a)==-InfFloat) return "*";
            sprintf(tmpLabelBuffer,"%g",G.Length(2*a));
            break;
        }
        case 5:
        {
            sprintf(tmpLabelBuffer,"%g",G.RedLength(piG,2*a));
            break;
        }
        case 6:
        {
            sprintf(tmpLabelBuffer,"%g",G.LCap(2*a));
            break;
        }
        case 7:
        {
            sprintf(tmpLabelBuffer,"%lu",TIndex(a+1));
            break;
        }
        case 8:
        {
            if (G.EdgeColour(2*a)==NoArc) sprintf(tmpLabelBuffer,"*");
            else sprintf(tmpLabelBuffer,"%lu",TIndex(G.EdgeColour(2*a)));
            break;
        }
        default:
        {
            UnknownOption("ArcLabel",option);
        }
    }

    return tmpLabelBuffer;
}


void canvasBuilder::ComposeNodeLabel(TNode v,ofstream& expFile) throw()
{
    if (strcmp(nodeLabelFormat,"")==0) return;

    for (unsigned i=0;i<strlen(nodeLabelFormat);i++)
    {
        if (nodeLabelFormat[i]=='#' && i<strlen(nodeLabelFormat)-1)
        {
            expFile << NodeLabel(v,int(nodeLabelFormat[i+1]-'0'));
            i++;
        }
        else
        {
            expFile << char(nodeLabelFormat[i]);
        }
    }
}


char* canvasBuilder::NodeLabel(TNode v,int option) throw(ERRejected)
{
    switch (option)
    {
        case 0:
        {
            strncpy(tmpLabelBuffer,"",LABEL_BUFFER_SIZE);
            break;
        }
        case 1:
        {
            sprintf(tmpLabelBuffer,"%lu",TIndex(v));
            break;
        }
        case 2:
        {
            if (G.Dist(v)==InfFloat || G.Dist(v)==-InfFloat) return "*";
            sprintf(tmpLabelBuffer,"%g",G.Dist(v));
            break;
        }
        case 3:
        {
            if (G.Pi(v)==InfFloat || G.Pi(v)==-InfFloat) return "*";
            sprintf(tmpLabelBuffer,"%g",G.Pi(v));
            break;
        }
        case 4:
        {
            if (G.NodeColour(v)==NoNode) return "*";
            sprintf(tmpLabelBuffer,"%lu",TIndex(G.NodeColour(v)));
            break;
        }
        case 5:
        {
            sprintf(tmpLabelBuffer,"%g",G.Demand(v));
            break;
        }
        case 6:
        {
            sprintf(tmpLabelBuffer,"%lu",TIndex(v)+1);
            break;
        }
        default:
        {
            UnknownOption("NodeLabel",option);
        }
    }

    return tmpLabelBuffer;
}


char* canvasBuilder::ArcLegenda(int option) throw(ERRejected)
{
    switch (option)
    {
        case 0:
        {
            strncpy(tmpLabelBuffer,"",LABEL_BUFFER_SIZE);
            break;
        }
        case 1:
        case 7:
        {
            sprintf(tmpLabelBuffer,"a");
            break;
        }
        case 2:
        {
            sprintf(tmpLabelBuffer,"ucap");
            break;
        }
        case 3:
        {
            sprintf(tmpLabelBuffer,"x");
            break;
        }
        case 4:
        {
            sprintf(tmpLabelBuffer,"length");
            break;
        }
        case 5:
        {
            sprintf(tmpLabelBuffer,"redlength");
            break;
        }
        case 6:
        {
            sprintf(tmpLabelBuffer,"lcap");
            break;
        }
        case 8:
        {
            sprintf(tmpLabelBuffer,"colour");
            break;
        }
        default:
        {
            UnknownOption("ArcLegenda",option);
        }
    }

    return tmpLabelBuffer;
}


char* canvasBuilder::NodeLegenda(char* index,int option) throw(ERRejected)
{
    switch (option)
    {
        case 1:
        case 6:
        {
            sprintf(tmpLabelBuffer,"%s",index);
            break;
        }
        case 2:
        {
            sprintf(tmpLabelBuffer,"d(%s)",index);
            break;
        }
        case 3:
        {
            sprintf(tmpLabelBuffer,"pi(%s)",index);
            break;
        }
        case 4:
        {
            sprintf(tmpLabelBuffer,"colour(%s)",index);
            break;
        }
        case 5:
        {
            sprintf(tmpLabelBuffer,"B(%s)",index);
            break;
        }
        case 0:
        {
            strncpy(tmpLabelBuffer,"",LABEL_BUFFER_SIZE);
            break;
        }
        default:
        {
            UnknownOption("NodeLegenda",option);
        }
    }

    return tmpLabelBuffer;
}


char* canvasBuilder::FixedNodeColour(TIndex c) throw()
{
    if (c==NoNode)
    {
        sprintf(nodeColour,"#%06lX",RGBColour(NO_COLOUR));
    }
    else
    {
        TNode c0 = ZERO_COLOUR + c;

        sprintf(nodeColour,"#%06lX",RGBColour(c0));
    }

    return nodeColour;
}


char* canvasBuilder::FixedEdgeColour(TIndex c) throw()
{
    if (c==NoArc)
    {
        // Other than for node colours, NO_COLOUR does not apply since this is
        // displayed white. Use some kind of grey here
        sprintf(nodeColour,"#%06lX",RGBColour(MAX_COLOUR));
    }
    else
    {
        TNode c0 = ZERO_COLOUR + c;

        sprintf(nodeColour,"#%06lX",RGBColour(c0));
    }

    return nodeColour;
}


char* canvasBuilder::SmoothColour(TIndex c,TIndex maxColour,TIndex undefColour)
    throw()
{
    if (c>maxColour)
    {
        sprintf(nodeColour,"#%06lX",RGBColour(undefColour));
        return nodeColour;
    }

    double ratio = double(c)/double(maxColour+1);

    long unsigned rSat = 100;
    long unsigned bSat = 100;
    long unsigned gSat = 100;

    if (ratio<0.333)
    {
        rSat = (long unsigned)(ceil(255*3*(0.333-ratio)));
        gSat = (long unsigned)(ceil(255*3*ratio));
    }
    else if (ratio<0.667)
    {
        gSat = (long unsigned)(ceil(254*3*(0.667-ratio)));
        bSat = (long unsigned)(ceil(254*3*(ratio-0.333)));
    }
    else
    {
        bSat = (long unsigned)(ceil(255*3*(1-ratio)));
        rSat = (long unsigned)(ceil(255*3*(ratio-0.667)));
    }

    sprintf(nodeColour,"#%02lX%02lX%02lX",rSat,gSat,bSat);

    return nodeColour;
}


unsigned long RGBColour(TIndex c) throw()
{
    if (c==canvasBuilder::PRED_COLOUR) return 0xDDCCFF;
    if (c==canvasBuilder::INFTY_COLOUR) return 0x88DD66;
    if (c==canvasBuilder::NO_COLOUR) return 0xFFFFFF;
    if (c==canvasBuilder::OUTLINE_COLOUR) return 0x000000;
    if (c==canvasBuilder::GRID_COLOUR) return 0xA0A0A0;

    TNode c0 = c-canvasBuilder::ZERO_COLOUR;

    switch (c0)
    {
        case 0:  return 0x00FF00;
        case 1:  return 0xFF0000;
        case 2:  return 0x6060FF;
        case 3:  return 0xFFFF40;
        case 4:  return 0x00E0E0;
        case 5:  return 0xFF00E0;
        case 6:  return 0xE08000;
        case 7:  return 0xA0A0FF;
        case 8:  return 0xFF8080;
        case 9:  return 0xA0A000;
        case 10: return 0xff2d93;
        case 11: return 0x10A010;
        case 12: return 0x678bb2;
        case 13: return 0xb20787;
        case 14: return 0xb24513;
        case 15: return 0xb26586;
        case 16: return 0x315a6b;
        case 17: return 0xc4db30;
        case 18: return 0xff8800;
        case 19: return 0xab0ddb;
    }

    return 0xA0A0A0;
}


const char* unixFontType[] =
{
    "times-medium-r-normal-", //    FONT_TIMES_ROMAN = 0,
    "times-medium-i-normal-", //    FONT_TIMES_ITALIC = 1,
    "times-bold-r-normal-", //    FONT_TIMES_BOLD = 2,
    "times-bold-i-normal-", //    FONT_TIMES_BOLD_ITALIC = 3,
    "itc avant garde-medium-r-normal-sans", //    FONT_AVANTGARDE_BOOK = 4,
    "itc avant garde-medium-o-normal-sans", //    FONT_AVANTGARDE_BOOK_OBLIQUE = 5,
    "itc avant garde-demi-r-normal-sans",   //    FONT_AVANTGARDE_DEMI = 6,
    "itc avant garde-demi-o-normal-sans",   //    FONT_AVANTGARDE_DEMI_OBLIQUE = 7,
    "itc bookman-light-r-normal-", //    FONT_BOOKMAN_LIGHT = 8,
    "itc bookman-light-i-normal-", //    FONT_BOOKMAN_LIGHT_ITALIC = 9,
    "itc bookman l-medium-r-normal-", //    FONT_BOOKMAN_DEMI = 10,
    "itc bookman l-medium-i-normal-", //    FONT_BOOKMAN_DEMI_ITALIC = 11,
    "courier-medium-r-normal-",   //    FONT_COURIER = 12,
    "courier-medium-o-normal-",   //    FONT_COURIER_OBLIQUE = 13,
    "courier-bold-r-normal-",     //    FONT_COURIER_BOLD = 14,
    "courier-bold-o-normal-",     //    FONT_COURIER_BOLD_OBLIQUE = 15,
    "helvetica-medium-r-normal-", //    FONT_HELVETICA = 16,
    "helvetica-medium-o-normal-", //    FONT_HELVETICA_OBLIQUE = 17,
    "helvetica-bold-r-normal-",   //    FONT_HELVETICA_BOLD = 18,
    "helvetica-bold-o-normal-",   //    FONT_HELVETICA_BOLD_OBLIQUE = 19,
    "helvetica-medium-r-narrow-", //    FONT_HELVETICA_NARROW = 20,
    "helvetica-medium-o-narrow-", //    FONT_HELVETICA_NARROW_OBLIQUE = 21,
    "helvetica-bold-r-narrow-",   //    FONT_HELVETICA_NARROW_BOLD = 22,
    "helvetica-bold-o-narrow-",   //    FONT_HELVETICA_NARROW_BOLD_OBLIQUE = 23,
    "new century schoolbook-medium-r-normal-", //    FONT_NEW_CENTURY_SCHOOLBOOK_ROMAN = 24,
    "new century schoolbook-medium-i-normal-", //    FONT_NEW_CENTURY_SCHOOLBOOK_ITALIC = 25,
    "new century schoolbook-bold-r-normal-",   //    FONT_NEW_CENTURY_SCHOOLBOOK_BOLD = 26,
    "new century schoolbook-bold-i-normal-",   //    FONT_NEW_CENTURY_SCHOOLBOOK_BOLD_ITALIC = 27,
    "palatino-medium-r-normal-", //    FONT_PALATINO_ROMAN = 28,
    "palatino-medium-i-normal-", //    FONT_PALATINO_ITALIC = 29,
    "palatino-bold-r-normal-",   //    FONT_PALATINO_BOLD = 30,
    "palatino-bold-i-normal-",   //    FONT_PALATINO_BOLD_ITALIC = 31,
    "symbol-medium-r-normal-",   //    FONT_SYMBOL = 32,
    "itc zapf chancery-medium-i-normal-", //    FONT_ZAPF_CHANCERY_MEDIUM_ITALIC = 33,
    "itc zapf dingbats-medium-r-normal-"  //    FONT_ZAPF_DINGBATS = 34
};
