/*
    This file is part of Msc-generator.
    Copyright (C) 2008-2024 Zoltan Turanyi
    Distributed under GNU Affero General Public License.

    Msc-generator is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Msc-generator is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Msc-generator.  If not, see <http://www.gnu.org/licenses/>.
*/

/** @file gvgraphs.cpp Definition of objects to hold, manipulate and render a graphviz graph.
* @ingroup libgvgen_files */



#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <sstream>
#include "gvc.h" //If this file is missing you need to install graphviz (on Linux)
#include "gvplugin.h"
#include "gvplugin_render.h"
#include "gvplugin_device.h"
#include "parser_tools.h" //for remove_head_tail_whitespace()
#include "graphchart.h"
#include "gvgraphs.h"

#ifdef _WIN32
#pragma comment( lib, "cdt.lib" )
#pragma comment( lib, "cgraph.lib" )
#pragma comment( lib, "gvc.lib" )
#pragma comment( lib, "gvplugin_dot_layout.lib" )
#pragma comment( lib, "gvplugin_neato_layout.lib" )

//On Windows we load these directly here. On Linux we do it in main.cpp an let gv dynamically load its stuff
extern gvplugin_library_t gvplugin_canvas_LTX_library; //defined in gvchart.cpp

//On windows, we statically bring in the layout libraries
extern "C" {
    __declspec(dllimport) gvplugin_library_t gvplugin_dot_layout_LTX_library;
    __declspec(dllimport) gvplugin_library_t gvplugin_neato_layout_LTX_library;
}

/** A pre-added list of plugins: layout engines and our pseudo renderers. */
lt_symlist_t lt_preloaded_symbols[] = {{"gvplugin_neato_layout_LTX_library", &gvplugin_neato_layout_LTX_library},
                                       {"gvplugin_dot_layout_LTX_library", &gvplugin_dot_layout_LTX_library},
                                       {"gvplugin_canvas_LTX_library", &gvplugin_canvas_LTX_library},
                                       {0,0}};

/** GVC context cannot be initialized, freed and re-initialized again, so we do it once as static*/
GVC_t *Gvc = gvContextPlugins(lt_preloaded_symbols, 0);
//gvFreeContext(Gvc); should be called at some exit functions, but we do not do it.

#endif

/** Return a list of layout methods supported by the current graphviz setup. */
std::set<std::string> GetLayoutMethods()
{
    std::set<std::string> ret;
    int size;
#if GRAPHVIZ_VER >= 900
    char** list = gvPluginList(Gvc, const_cast<char*>("layout"), &size);
#else
    char** list = gvPluginList(Gvc, const_cast<char*>("layout"), &size, nullptr);
#endif
    for (; size; size--, list++)
        ret.emplace(*list);
    return ret;
}

using namespace graph;

Graph *Graph::_current = nullptr;

void GraphDrawingPrimitive::ShiftBy(const XY &xy) noexcept
{
    if (!path.IsEmpty())
        path.Shift(xy);
    else if (!contour.IsEmpty())
        contour.Shift(xy);
    else {
        sx += xy.x;
        dx += xy.x;
        y += xy.y;
    }
}

Contour GraphDrawingPrimitive::Cover(const ShapeCollection &shapes) const
{
    if (!path.IsEmpty())
        return path.SimpleWiden(line.LineWidth());
    if (!contour.IsEmpty()) {
        if (line.LineWidth())
            return contour.CreateExpand(line.LineWidth()/2);
        return contour;
    }
    return label.Cover(shapes, sx, dx, y);
}

void GraphDrawingPrimitive::Draw(Canvas &canvas, const ShapeCollection &shapes) const
{
    if (canvas.does_graphics()) {
        if (!path.IsEmpty()) {
            canvas.Line(path, line);
        } else if (!contour.IsEmpty()) {
            canvas.Shadow(contour, shadow);
            canvas.Fill(contour, fill);
            //Don't stroke if of same color as a solid fill
            if (fill.gradient!=EIntGradType::NONE || fill.color!=line.color)
                canvas.Line(contour, line);
        }
        label.Draw(canvas, shapes, sx, dx, y);
    } else {
        if (!path.IsEmpty()) {
            canvas.Add(GSPath(path, line));
            label.Draw(canvas, shapes, sx, dx, y);
        } else if (contour.IsEmpty()) {
            label.Draw(canvas, shapes, sx, dx, y);
        } else {
            //Don't stroke if of same color as a solid fill
            const LineAttr l = fill.gradient!=EIntGradType::NONE || fill.color!=line.color
                ? line : LineAttr::None();
            const Block bb = label.IsEmpty()
                ? Block(false) : Block(sx, dx, y, y+label.getTextWidthHeight().y);
            canvas.Add(GSShape(contour, l, fill, shadow, bb, label));
        }
    }
}

bool GraphElement::AddAttribute(const Attribute &a, Chart *chart)
{
    return style.write().AddAttribute(a, chart);
}

void GraphElement::AddAttributeList(const GraphStyle *s, Chart *chart)
{
    if (s==nullptr) return;
    style.write() += *s;
    if (!style.read().attributes.contains("label") ||
        !style.read().attributes.contains("original_label")) return;

    //if the element has "label" and "original_label" attributes, we adjust them.
    auto label_w = style.write().attributes.find("label");
    StringFormat basic;
    basic.Default();
    auto i = style.read().attributes.find("label_format");
    if (i!=style.read().attributes.end())
        basic.Apply(i->second.value.c_str()); //do not change 'attributes["label_format"].value'
    i = style.read().attributes.find("link_format");
    if (i != style.read().attributes.end())
        basic.AddAttribute(Attribute(i->first, i->second.value, {i->second.linenum_attr, i->second.linenum_attr}, {i->second.linenum_value, i->second.linenum_value}, true), chart, EStyleType::ELEMENT);
    StringFormat::ExpandReferences(label_w->second.value, chart, label_w->second.linenum_value, &basic,
                                    false, true, StringFormat::LABEL, true);
    auto orig_w = style.write().attributes.find("original_label");
    orig_w->second.value = label_w->second.value;
    //Now prep the label we pass to graphviz by removing all Msc-gen specific escapes
    //Remove all formatting escapes
    StringFormat::ConvertToPlainText(label_w->second.value);
    //Duplicate '\' symbols, because graphviz will remove them
    for (unsigned u = 0; u<label_w->second.value.length(); u++)
        if (label_w->second.value[u]=='\\') label_w->second.value.insert(u++, 1, '\\');
}

///<Add a drawing primitive - called from the "contour" gv (pseudo)renderer. Also adds the cover to "area"

void GraphElement::AddDrawingPrimitive(Path && p, const LineAttr & l)
{
    area += p.SimpleWiden(l.LineWidth()).ClearHoles(); //for closed paths we add their contour
    //check if we already have path at the end of the list with this attribute (to preserve drawing order)
    if (drawingList.size() && drawingList.back().path.size() && drawingList.back().line == l)
        drawingList.back().path.append(std::move(p));
    else
        drawingList.emplace_back(std::move(p), l);
}

/** Fills in a 'shadow' attribute and reports errors if needed */
ShadowAttr GraphElement::GetShadow(Chart* chart) const
{
    ShadowAttr shadow;
    auto i = style.read().attributes.find("shadow_offset");
    if (i==style.read().attributes.end())
        return shadow; //no shadow_offset -> no shadow
    shadow.AddAttribute(i->second.GenerateAttribute("shadow.offset", true), chart, EStyleType::ELEMENT);

    i = style.read().attributes.find("shadow_blur");
    if (i!=style.read().attributes.end())
        shadow.AddAttribute(i->second.GenerateAttribute("shadow.blur", true), chart, EStyleType::ELEMENT);
    i = style.read().attributes.find("shadow_color");
    if (i!=style.read().attributes.end())
        shadow.AddAttribute(i->second.GenerateAttribute("shadow.color", false), chart, EStyleType::ELEMENT);
    return shadow;
}

/** Insert our attributes into cgraph.
 * @param in_gv This is our object in cgraph (already added by AddToGvGraph() of a descendant.
 * We take care to convert 'double' and 'triple' line styles to ones understandable to graphviz.*/
void GraphElement::AddAttributesToGraph(void * in_gv) const
{
    for (auto &p : style.read().attributes) {
        if (IsDotAttributeAnExtension(p.second.descr))
            continue; //skip those that are not interpreted by graphviz
        else if (p.first=="style") {
            ParsedStyle ps;
            ps.Parse(p.second.value);
            if ((ps.line=="double" || ps.line=="triple"))
                ps.line = "bold";
            std::string style = ps;
            agset(in_gv, p.first.c_str(), style.c_str());
            continue;
        }
        agset(in_gv, p.first.c_str(), p.second.value.c_str());
    }
}

void GraphElement::ShiftBy(const XY &xy)
{
    Element::ShiftBy(xy);
    for (auto &d : drawingList)
        d.ShiftBy(xy);
}


void GraphElement::PostPosProcess(Canvas &canvas, Chart * ch)
{
    //add a shadow draw element if needed
    if (ShadowAttr shadow = GetShadow(ch); !shadow.IsNone()) {
        //find the largest fill contour
        const auto max = std::ranges::max_element(drawingList, {}, [](const GraphDrawingPrimitive& d)
                                                  { return d.contour.IsEmpty() ? 0 : d.contour.GetArea(); });
        if (max!=drawingList.end()) {
            max->shadow = shadow;
        } else {
            GraphDrawingPrimitive d(Contour(area), LineAttr::None());
            d.shadow = std::move(shadow);
            drawingList.insert(drawingList.begin(), std::move(d));
        }
    }
    //Special case: we have one shape and one label element (in this order)
    if (drawingList.size()==2)
        if (auto &c = drawingList[0]
            ; c.path.IsEmpty() && c.label.IsEmpty() && !c.contour.IsEmpty())
            if (auto& l = drawingList[1]
                ; l.path.IsEmpty() && l.contour.IsEmpty() && !l.label.IsEmpty()) {
                //Merge them
                std::swap(c.label, l.label);
                c.sx = l.sx;
                c.dx = l.dx;
                c.y = l.y;
                drawingList.pop_back();
            }
    //if style is "double" or "triple" adjust the line attributes
    //these are Msc-generator extensions
    auto i = style.read().attributes.find("style");
    if (i!=style.read().attributes.end() &&
        (i->second.value=="double" || i->second.value=="triple"))
        for (auto& e : drawingList)
            if (ELineType t; !e.line.IsEmpty() && Convert(i->second.value, t)) {
                e.line.type = t;
                e.line.width = 1;
                if (!e.path.IsEmpty())
                    area += e.path.SimpleWiden(e.line.LineWidth());
                if (!e.contour.IsEmpty())
                    area += e.contour.CreateExpand(e.line.LineWidth()/2);
            }
    Element::PostPosProcess(canvas, ch); //this expands 'area' (among others)
    if (ch->prepare_for_tracking)
        ch->AllCovers += GetAreaToSearch();
}


/** The user-specified error function we give to DOT, to channel DOT errors into Chart::Error
 *  We collect the reported error text until we hit a '\n' (we treat the whole line as a single error)
 *  We try to figure out from the DOT error message, where the error occurred inside the file.
 *   - For syntax errors, we use the term "near line xy".
 *   - For missing ports we use the term "node XY, port WZ unrecognized"
 *   - Else we look for attribute names and values in all objects to find if a bad attribute value is
 *     causing the error.*/
int Graph::DotError(char *msg)
{
    if (Graph::_current==nullptr || msg==nullptr || msg[0]==0) return 0;
    string &s = Graph::_current->_err_msg_accum;
    s += msg;
    char n[80], p[80];
    if (s.back()=='\n') {
        s.pop_back();
        bool error = true;
        if (CaseInsensitiveBeginsWith(s, "error:"))
            s = s.substr(7);
        if (CaseInsensitiveBeginsWith(s, "warning:")) {
            s = s.substr(9);
            error = false;
        }
        FileLineCol file_pos(Graph::_current->_chart->current_file, 0, 0);
        //find "line <num>"
        size_t pos = s.find("line ");
        if (pos != string::npos && s[pos+5]>='1' && s[pos+5]<='9') {
            file_pos.line = atoi(s.c_str()+pos+5);
            //file_pos.col = 1;
        } else {
            //try finding the type or name and the value of an attribute in error text
            const GraphAttribute *a = Graph::_current->style.read().FindInErrorText(s);
            for (const auto &n : Graph::_current->nodes)
                if (a==nullptr) a = n->style.read().FindInErrorText(s);
                else break;
            for (const auto &e : Graph::_current->edges)
                if (a==nullptr) a = e->style.read().FindInErrorText(s);
                else break;
            for (const auto &sg : Graph::_current->subgraphs)
                if (a==nullptr) a = sg->style.read().FindInErrorText(s);
                else break;
            if (a)
                file_pos = a->linenum_value;
            else if (2==sscanf(s.c_str(), "node %79s port %79s unrecognized", n, p)) {
                //chop terminating ',' from node
                if (n[strlen(n)-1]==',')
                    n[strlen(n)-1] = 0;
                //search for an edge with this node/port combo
                //first see if we have such a node
                GraphNode *node = Graph::_current->GetNode(n);
                if (node)
                    //if so, see if we have an edge with this port
                    for (const auto &e : Graph::_current->edges) {
                        if (e->n1 == node && strncmp(p, e->port1.c_str(), strlen(p))==0)
                            file_pos = e->port_file_pos1.start;
                        else if (e->n2 == node && strncmp(p, e->port2.c_str(), strlen(p))==0)
                            file_pos = e->port_file_pos2.start;
                    }
                //else we simply let file_pos unset
            }
        }
        if (error)
            Graph::_current->_chart->Error.Error(file_pos, s);
        else
            Graph::_current->_chart->Error.Warning(file_pos, s);
        s.clear();
    }
    return 0;
}



/** Adds the attributes to the list of attribute names to bind.
 * graphviz requires the registration (binding) of all attribute
 * names (globally) before used for a given kind of object
 * (graph, edge or node).
 * @param t [in] Specifies what kind of object the attributes are for.
 * @param s [in] The list of attribute names to add.*/
void Graph::AddAttrName(GraphStyle::EGraphElementType t, const GraphStyle & s)
{
    if (t==GraphStyle::EGraphElementType::ANY)
        for (auto &a : s.attributes) {
            attr_node.insert(a.first);
            attr_edge.insert(a.first);
            attr_graph.insert(a.first);
        }
    else {
        std::set<std::string> &attrs = t==GraphStyle::EGraphElementType::NODE ? attr_node :
                                       t==GraphStyle::EGraphElementType::EDGE ? attr_edge : attr_graph;
        for (auto &a : s.attributes)
            attrs.insert(a.first);
    }
}

/** Searches for a node in the graph by name. Returns nullptr if not found.
 * Only the nodes currently part of the graph are searched.
 * (The ones not used but still part of storage_nodes are not.)*/
GraphNode *Graph::GetNode(std::string_view name) const
{
    for (auto &p: nodes)
        if (p->name==name)
            return p;
    for (auto &s: subgraphs) {
        auto ret = s->GetNode(name);
        if (ret)
            return ret;
    }
    return nullptr;
}

/** Searches for a subgraph in the graph by name. Returns nullptr if not found.
 * Only the subgraphs currently part of the graph are searched.
 * (The ones not used but still part of storage_subgraphs are not.)*/
SubGraph *Graph::GetSubGraph(std::string_view n) const
{
    for (auto &s: subgraphs) {
        SubGraph *ret = s->GetSubGraph(n);
        if (ret) return ret;
    }
    return nullptr;
}
/** Returns the first edge found between these nodes. */
GraphEdge *Graph::GetFirstEdge(const GraphNode * _1, const GraphNode *_2) const
{
    auto i = std::find_if(edges.begin(), edges.end(),
        [_1, _2](auto &e) {return e && _1 == e->n1 && _2 == e->n2; });
    return i==edges.end() ? nullptr : i->get();
}

/** Searches for a specific element in the graph by its pointer in graphviz.
* Returns nullptr if not found.*/
GraphElement *Graph::GetElement(void *in_gv_loc)
{
    for (auto &p : nodes)
        if (p->GetGv() == in_gv_loc)
            return p;
    for (auto &p : edges)
        if (p->GetGv() == in_gv_loc)
            return p.get();
    for (auto &p : subgraphs) {
        auto ret = p->GetElement(in_gv_loc);
        if (ret)
            return ret;
    }
    if (in_gv_loc == in_gv)
        return this;
    _ASSERT(0);
    return nullptr;
}

SubGraph* Graph::GetNodeFromVisibleSubgraph(GraphNode *n)
{
    for (auto &s : subgraphs) {
        SubGraph *ret = s->GetNodeFromVisibleSubgraph(n);
        if (ret) return ret;
    }
    return nullptr;
}

/** Remove empty subgraphs. Return true, if we end up empty ourselves.*/
bool Graph::RemoveEmptySubGraphs()
{
    for (auto &s: subgraphs)
        if (s->RemoveEmptySubGraphs())
            s = nullptr;
    subgraphs.remove(nullptr);
    return nodes.size()==0 && subgraphs.size()==0;
}

void Graph::ShiftBy(const XY &xy)
{
    GraphElement::ShiftBy(xy);
    for (auto &n: nodes)
        n->ShiftBy(xy);
    for (auto &e: edges)
        e->ShiftBy(xy);
    for (auto &s: subgraphs)
        s->ShiftBy(xy);
    boundingBox.Shift(xy);
}





/** Bind a certain type of attribute in graphviz. This is needed, else cgraph
 * does not receive attribute values.*/
void BindAttrs(Agraph_t *G, int type, const std::set<std::string> &attrs)
{
    for (auto &s: attrs) {
        if (type == AGEDGE && s=="key") continue; //do not bind the key attribute to edges
        Agsym_t *asym = agattr(G, type, s.c_str(), nullptr); //we hope agattr does not write the name...
        if (!asym)
            asym = agattr(G, type, s.c_str(), "");
        //We could set asym->print to TRUE in some cases...
    }
}

/** Create the graph in graphviz and call the appropriate layout engine.
 * Collect errors in chart->Error. Does NOT fill in 'boundingBox'.
 * Call GetGv() to get the Agraph_t* to manipulate. nullptr on a problem.*/
void Graph::Layout(GraphChart *chart)
{
    _ASSERT(_current==nullptr);
    _canvas = nullptr;
    _chart = chart;
    _err_msg_accum.clear();
    _current = this;
    agseterrf(Graph::DotError);
    AddToGvGraph();
    auto i = style.read().attributes.find("layout");
    const char* layout = i == style.read().attributes.end() ? "dot" : i->second.value.c_str();
    int res = gvLayout(Gvc, in_gv, layout);
    agseterrf(nullptr);
    _canvas = nullptr;
    _chart = nullptr;
    _err_msg_accum.clear();
    _current = nullptr;

    if (res) {
        gvFreeLayout(Gvc, in_gv);
        gvFinalize(Gvc);
        in_gv = nullptr;
    }
}

/** Collect drawing instructions from graphviz.
 * Assumes a laid out graph. Errors go to chart->Error.
 * Fills in 'boundingBox'. Deletes the graph from cgraph.*/
void Graph::PseudoRenderFrom(GraphChart*chart)
{
    boundingBox.MakeInvalid();
    if (in_gv) {
        _ASSERT(_current==nullptr);
        Canvas c(Canvas::Empty::Query);
        _canvas = &c;
        _chart = chart;
        _err_msg_accum.clear();
        _current = this;
        agseterrf(Graph::DotError);
        gvRender(Gvc, in_gv, "cover", nullptr);
        agseterrf(nullptr);
        _canvas = nullptr;
        _chart = nullptr;
        _err_msg_accum.clear();
        _current = nullptr;
        for (auto &e : edges)
            e->ClearGv();
        for (auto &n : nodes)
            n->ClearGv();
        for (auto &s : subgraphs)
            s->ClearGv();
        gvFreeLayout(Gvc, in_gv);
        gvFinalize(Gvc);
        in_gv = nullptr;
    }
    if (boundingBox.IsInvalid())
        boundingBox.x.from = boundingBox.x.till = boundingBox.y.from = boundingBox.y.till = 0;
}

void Graph::GraphCollectIsMapElements(Canvas& canvas, Chart& chart) const {
    GraphElement::GraphCollectIsMapElements(canvas, chart);
    for (auto& pSubGraph : storage_subgraphs)
        pSubGraph->GraphCollectIsMapElements(canvas, chart);
    for (auto& pNode : storage_nodes)
        pNode->GraphCollectIsMapElements(canvas, chart);
    for (auto& pEdge : edges)
        pEdge->GraphCollectIsMapElements(canvas, chart);
}


/** Draws the graph based on the drawing primitives
 *  collected in PseudoRenderFrom(). */
void Graph::Draw(Canvas &canvas, const ShapeCollection &shapes, EGraphDrawPass p) const
{
    if (p==EGraphDrawPass::BACKGROUND)
        GraphElement::DrawAll(canvas, shapes);
    for (auto &pSubGraph: storage_subgraphs)
        pSubGraph->Draw(canvas, shapes, p);
    for (auto &pNode : storage_nodes)
        pNode->Draw(canvas, shapes, p);
    for (auto &pEdge : edges)
        pEdge->Draw(canvas, shapes, p);
}

/** Correct deletion order, albeit no longer needed.*/
Graph::~Graph()
{
    edges.clear();
    subgraphs.clear();
    nodes.clear();
    name.clear();
    attr_node.clear();
    attr_edge.clear();
    attr_graph.clear();
    storage_subgraphs.clear(); //this will free memory
    storage_nodes.clear();     //this will free memory
}

/** Create the graph in graphviz, including all subgraphs, nodes and edges.*/
void Graph::AddToGvGraph()
{
    _ASSERT(in_gv==nullptr);
    if (in_gv) return;
    in_gv = agopen((char*)name.c_str(),
        strict ?
        (directed ? Agstrictdirected : Agstrictundirected) :
        (directed ? Agdirected : Agundirected),
        nullptr);

    //Bind all attributes first
    BindAttrs(in_gv, AGRAPH, attr_graph);
    BindAttrs(in_gv, AGNODE, attr_node);
    BindAttrs(in_gv, AGEDGE, attr_edge);

    //Add our attributes
    AddAttributesToGraph(in_gv);

    /* Add subgraphs, but delete those that are empty - or graphviz crashes */
    RemoveEmptySubGraphs();
    for (auto &pSubGraph: subgraphs) 
        pSubGraph->AddToGvGraph();

    /* Add nodes not yet added */
    for (auto &pNode : nodes)
        if (pNode->GetGv()==nullptr) {
            pNode->AddToGvGraph(nullptr);
            if (pNode->GetGv())
                agbindrec(pNode->GetGv(), "Agnodeinfo_t", sizeof(Agnodeinfo_t), true);	//node custom data
        }

    /* Connect nodes */
    for (auto &pEdge : edges) {
        pEdge->AddToGvGraph();
        if (pEdge->GetGv())
            agbindrec(pEdge->GetGv(), "Agedgeinfo_t", sizeof(Agedgeinfo_t), true);	//edge custom data
    }
}

std::set<std::string> Graph::GetAllSubGraphs() const
{
    std::set<std::string> ret;
    for (auto &s : storage_subgraphs)
        ret.insert(s->name);
    return ret;
}

void Graph::PostPosProcess(Canvas &canvas, Chart *ch)
{
    area.arc = this;
    //We don't call our own GraphElement::PostPosProcess - no expand for us and no controls either
    //use the storage array - call PostPosProcess for each node/subgraph only once, even if it is at several places.
    for (auto &s : storage_subgraphs)
        s->PostPosProcess(canvas, ch);
    for (auto &n : storage_nodes)
        n->PostPosProcess(canvas, ch);
    for (auto &e : edges)
        e->PostPosProcess(canvas, ch);
}

/** Make the graph background fill width cover the entire range provided*/
void Graph::AdjustBkArea(const Range &total_x) noexcept {
    //But we would like to extend the background fill of the graph to the full width of the chart.
    //Note: graphviz shall create only a single drawingelement for the graph, which is the background
    if (drawingList.size()==1 && drawingList.front().contour.IsRectangle()) {
        Block b = drawingList.front().contour.GetBoundingBox();
        b.x += total_x;
        drawingList.front().contour = b;
    }
}

std::optional<BackgroundFill> Graph::GetBkFill() const noexcept {
    if (drawingList.size()==1 && drawingList.front().contour.IsRectangle())
        return BackgroundFill{drawingList.front().fill, drawingList.front().contour.GetBoundingBox()};
    return {};
}


void SubGraph::AddToGvGraph() const
{
    in_gv = agsubg(parent ? parent->GetGv() : graph.GetGv(),
                   name.length() ? name.c_str() : nullptr,
                   /*createflag==*/ 1);
    AddAttributesToGraph(in_gv);
    //add subgraphs
    for (auto &pSubGraph : subgraphs)
        pSubGraph->AddToGvGraph();
    //add nodes
    for (auto pNode : nodes) {
        pNode->AddToGvGraph(this);
        agbindrec(pNode->GetGv(), "Agnodeinfo_t", sizeof(Agnodeinfo_t), true);	//node custom data
    }
}

/** True if 's' is an ancestor of us. Always true for nullptr (==the graph)*/
bool SubGraph::IsMyParent(SubGraph * s)
{
    if (s==nullptr) return true;
    if (parent==nullptr) return false;
    if (s==parent) return true;
    return parent->IsMyParent(s);
}

/** Delete a subgraph from our children. True if we did a deletion. */
bool SubGraph::Remove(SubGraph * s)
{
    for (auto i = subgraphs.begin(); i!=subgraphs.end(); i++)
        if (*i==s) {
            subgraphs.erase(i);
            return true;
        } else if ((*i)->Remove(s))
            return true;
    return false;
}

/** Remove a node from us or our children. */
void SubGraph::Remove(GraphNode * n)
{
    for (auto i = nodes.begin(); i!=nodes.end(); i++)
        if (*i==n) {
            nodes.erase(i);
            break;
        }
    for (auto &s : subgraphs)
        s->Remove(n);
}

/** True, if a node is in a visible subgraph.*/
SubGraph* SubGraph::GetNodeFromVisibleSubgraph(GraphNode *n)
{
    if (cluster && std::find(nodes.begin(), nodes.end(), n)!=nodes.end())
        return this;
    for (auto &s : subgraphs) {
        SubGraph *ret = s->GetNodeFromVisibleSubgraph(n);
        if (ret) return ret;
    }
    return nullptr;
}

/** Remove empty subgraphs. Return true, if we end up empty ourselves.*/
bool SubGraph::RemoveEmptySubGraphs()
{
    for (auto &s: subgraphs)
        if (s->RemoveEmptySubGraphs())
            s = nullptr;
    subgraphs.remove(nullptr);
    return nodes.size()==0 && subgraphs.size()==0;
}

/** Searches for a node in the subgraph by name. Returns nullptr if not found.*/
GraphNode *SubGraph::GetNode(std::string_view name) const
{
    for (auto &p: nodes)
        if (p->name==name)
            return p;
    for (auto &s: subgraphs) {
        auto ret = s->GetNode(name);
        if (ret)
            return ret;
    }
    return nullptr;
}


/** Searches for a subgraph in the subgraph by name. Returns nullptr if not found.*/
SubGraph *SubGraph::GetSubGraph(std::string_view n)
{
    if (n==name) return this;
    for (auto &s: subgraphs) {
        SubGraph *ret = s->GetSubGraph(n);
        if (ret) return ret;
    }
    return nullptr;
}

/** Searches for a specific element in the subgraph by its pointer in graphviz.
 * Returns nullptr if not found.*/
GraphElement * SubGraph::GetElement(void * in_gv_loc)
{
    for (auto &p : nodes)
        if (p->GetGv() == in_gv_loc)
            return p;
    for (auto &p : subgraphs) {
        auto ret = p->GetElement(in_gv_loc);
        if (ret)
            return ret;
    }
    if (in_gv_loc == in_gv)
        return this;
    return nullptr;
}

/** Returns a list of all nodes in this subgraph.*/
NPtrList<GraphNode> SubGraph::GetAllNodes() const
{
    NPtrList<GraphNode> ret;
    for (auto p : nodes)
        ret.Append(p);
    for (auto &s:subgraphs) {
        NPtrList<GraphNode> n = s->GetAllNodes();
        ret.Append(&n); //this is a move op
    }
    return ret;
}

void SubGraph::ShiftBy(const XY &xy)
{
    GraphElement::ShiftBy(xy);
    if (cluster)
        for (auto &n: nodes)
            n->ShiftBy(xy);
    for (auto &s: subgraphs)
        s->ShiftBy(xy);
}


/** Returns a list of nodes (no ports).
 * Used during parsing to generate edges for constructs, like
 * "{ a b c } -> { d e f }"*/
std::unique_ptr<GraphNodePortList> SubGraph::CopyOfNodes() const
{
    std::unique_ptr<GraphNodePortList> ret = std::make_unique<GraphNodePortList>();
    for (auto n : nodes) {
        ret->emplace_back();
        ret->back().node = n;
    }
    return ret;
}

void SubGraph::PostPosProcess(Canvas &canvas, Chart *ch)
{
    GraphElement::PostPosProcess(canvas, ch);
    //create frame
    area_draw = area.CreateExpand(ch->trackFrameWidth) - area;
    draw_is_different = true;
    area_draw_is_frame = true;
    //No need to call PostPosProcess for contained subgraphs or nodes, Graph::PostPosProcess calls them.
}

void SubGraph::RegisterCover(Chart *)
{

}

void SubGraph::Draw(Canvas & canvas, const ShapeCollection &shapes, EGraphDrawPass p) const
{
    if (p == EGraphDrawPass::CLUSTER)
        GraphElement::DrawAll(canvas, shapes);
    //No need to call Draw for contained subgraphs or nodes, Graph::PostPosProcess calls them.
}


void GraphNode::AddToGvGraph(const SubGraph *parent) const
{
    in_gv = agnode(parent ? parent->GetGv() : graph.GetGv(), name.c_str(), 1);
    AddAttributesToGraph(in_gv);
}

/** Sets the label of the node to its name.
 * If the name contains no hidden escapes, we leave the label untouched (empty probably, which
 * will be assumed to be "\N" by graphviz and be substituted by node name. If the name has
 * hidden escapes (the name had a $$ in it), we remove them and use that as a label.*/
void GraphNode::SetLabelFromName()
{
    std::string l = name;
    if (StringFormat::ReplaceHiddenEscapes(l))
        AddAttribute(Attribute("label", l, file_pos, file_pos), nullptr); //ugly, but we assume no errors, so we skip chart
}

void GraphNode::Draw(Canvas & canvas, const ShapeCollection &shapes, EGraphDrawPass p) const
{
    if (p!=EGraphDrawPass::NODE) return;
    DrawAll(canvas, shapes);

}

void GraphEdge::AddToGvGraph() const
{
    in_gv = agedge(graph.GetGv(), n1->GetGv(), n2->GetGv(), name.length() ? name.c_str() : nullptr, 1);
    if (!in_gv) 
        return;
    Agsym_t *attr;
    if (port1.length()) {
        //bind TAILPORT_ID attribute
        if ((attr = agattr(graph.GetGv(), AGEDGE, TAILPORT_ID, nullptr)) == nullptr)
            attr = agattr(graph.GetGv(), AGEDGE, TAILPORT_ID, "");
        agxset(in_gv, attr, port1.c_str());
    }
    if (port2.length()) {
        //bind HEADPORT_ID attribute
        if ((attr = agattr(graph.GetGv(), AGEDGE, HEADPORT_ID, nullptr)) == nullptr)
            attr = agattr(graph.GetGv(), AGEDGE, HEADPORT_ID, "");
        agxset(in_gv, attr, port2.c_str());
    }
    AddAttributesToGraph(in_gv);
}

void GraphEdge::SetType(GraphEdgeType t, const StyleSet<GraphStyle>& styles)
{
    type = t;
    const auto &toadd = styles.GetStyle(t.AsText());
    style += toadd;
    for (auto &a: toadd.read().attributes)
        graph.AddAttrName(GraphStyle::EDGE, a.first);
}

////////////////////////////////////////////////////////////////////
//A few graphviz renderer helper functions

/** Convert a graphviz color to our color type.*/
inline ColorType ConvertColor(gvcolor_t c)
{
    _ASSERT(c.type == RGBA_BYTE);
    return ColorType(c.u.rgba[0], c.u.rgba[1], c.u.rgba[2], c.u.rgba[3]);
}

/** Copies the graphviz object's fill attributes into 'fill' */
inline LineAttr GetLineAttr(const obj_state_t *obj)
{
    LineAttr line(ELineType::SOLID, ConvertColor(obj->pencolor),
                  obj->penwidth, ECornerType::NONE, 0);
    switch (obj->pen) {
    case PEN_NONE:   line.type = ELineType::NONE; break;
    case PEN_DASHED: line.type = ELineType::DASHED; break;
    case PEN_DOTTED: line.type = ELineType::DOTTED; break;
    default: break;
    }
    return line;
}

/** Copies the graphviz object's fill attributes into 'fill' */
inline FillAttr GetFillAttr(const obj_state_t *obj)
{
    FillAttr fill; //a completely specified attr with defaults (no gradient, 0.5 frac, zero angle)
    fill.color = ConvertColor(obj->fillcolor);
    if (obj->stopcolor.type == RGBA_BYTE) { //else this color is not set: we requested colors in this format
        fill.color2 = ConvertColor(obj->stopcolor);
        fill.gradient_angle = obj->gradient_angle; //linear gradient by def if we have a second color
        fill.gradient = EIntGradType::LINEAR;
    } else
        fill.color2.reset();
    if (obj->fill == FILL_RADIAL)
        fill.gradient = EIntGradType::RADIAL;
    else if (obj->gradient_frac && fill.color2) {
        fill.frac = std::max(0., std::min(1., (double)obj->gradient_frac));
        fill.gradient = EIntGradType::DEGENERATE;
    }
    return fill;
}

/** Callback called by graphviz to draw a label - we extract it and save as a drawing primitive
 * into Graph::_current or actually draw it on Graph::_current->_canvas, if 'draw' is true.*/
void canvas_cover_textspan(bool draw, GVJ_t *job, pointf p, textspan_t * span)
{
    if (Graph::_current==nullptr || Graph::_current->_canvas==nullptr) return;
    GraphElement *e = nullptr;
    //see if the object we take the label for, has text formatting attributes
    if (!draw) {
        Graph *gc = dynamic_cast<Graph*>(Graph::_current);
        if (gc)
            e = gc->GetElement(job->obj->u.n);
    }
    StringFormat sf;
    sf.Default();

    //apply font type/size and indentation
    char buff[1000];
    snprintf(buff, sizeof(buff), "\\f(%s)\\mn(%f)\\ms(%f)%s",
        span->font->name, span->font->size, span->font->size*0.8,
        span->just == 'n' ? "\\pc" : span->just=='l' ? "\\pl" : span->just=='r' ? "\\pr" : "");
    sf.Apply(buff);
    //color is in pencolor not in span->font->color
    sf.SetColor(ColorType(job->obj->pencolor.u.rgba[0], job->obj->pencolor.u.rgba[1],
                          job->obj->pencolor.u.rgba[2], job->obj->pencolor.u.rgba[3]));
    if (span->font->flags & HTML_BF)
        sf.Apply("\\B");
    if (span->font->flags & HTML_IF)
        sf.Apply("\\I");
    if (span->font->flags & HTML_UL)
        sf.Apply("\\U");
    if (span->font->flags & HTML_SUP)
        sf.Apply("\\^");
    if (span->font->flags & HTML_SUB)
        sf.Apply("\\_");
    if (span->font->flags & HTML_S) {
        //TODO: don't have strikethrough
    }
#ifdef HTML_OL
    //Not all versions of graphviz have this
    if (span->font->flags & HTML_OL) {
        //TODO: don't have overline
    }
#endif
    std::string_view s(span->str);
    remove_head_tail_whitespace(s);
    std::string text(s);
    //replace "text" to the original "decorated" value
    //For labels specified via the ':: syntax' we have the original value stored with position escapes
    //Since graphviz generates a new textspan for each line, we need to search which line this is.
    //For labels specified via the 'label' attribute, we emit the text as we get from graphviz.
    if (e) {
        //Find which line of the label we are and what is the current formatting
        auto label = e->style.read().attributes.find("label");
        if (label != e->style.read().attributes.end()) {
            auto orig = e->style.read().attributes.find("original_label");
            if (orig == e->style.read().attributes.end()) {
                //Duplicate '\' symbols so that when we convert to a label, we get them.
                for (unsigned u = 0; u<text.length(); u++)
                    if (text[u]=='\\') text.insert(u++, 1, '\\');
            } else {
                Label parsed_label(orig->second.value, *Graph::_current->_canvas, Graph::_current->_chart->Shapes, sf);
                for (auto &l : parsed_label)
                    if ((string)l == text) {
                        text = l.GetStartFormat().Print() + l.GetLine();
                        break;
                    }
            }
        }
    }
    Label label(text, *Graph::_current->_canvas, Graph::_current->_chart->Shapes, sf);
    if (label.size()==0)
        return;
    XY pos;
    switch (span->just) {
    default: _ASSERT(0); FALLTHROUGH;
    case 'n': pos.x = p.x - span->size.x/2; break;
    case 'l': pos.x = p.x; break;
    case 'r': pos.x = p.x - span->size.x; break;
    }
    pos.y = p.y - label[0].getWidthHeight().y; //for some reason we need this and not getheightabovebaseline()

    if (draw)
        label.Draw(*Graph::_current->_canvas, Graph::_current->_chart->Shapes, pos.x, pos.x+span->size.x, pos.y);
    else {
        Graph *gc = dynamic_cast<Graph*>(Graph::_current);
        if (gc) {
            GraphElement *e = gc->GetElement(job->obj->u.n);
            if (e) {
                gc->boundingBox += label.Cover(Graph::_current->_chart->Shapes, pos.x, pos.x+span->size.x, pos.y).GetBoundingBox();
                e->AddDrawingPrimitive(std::move(label), pos.x, pos.x+span->size.x, pos.y, Graph::_current->_chart->Shapes);
            }
        }
    }
}

/** Callback called by graphviz to draw a polygon - we extract it and save as a drawing primitive
 * into Graph::_current or actually draw it on Graph::_current->_canvas, if 'draw' is true.*/
void canvas_cover_polygon(bool draw, GVJ_t * job, pointf * A, int n, int filled)
{
    if (Graph::_current==nullptr || Graph::_current->_canvas==nullptr) return;
    std::vector<XY> points;
    points.reserve(n);
    for (int i = 0; i<n; i++)
        points.push_back(XY(A[i].x, A[i].y));
    Contour c(std::move(points), EForceClockwise::INVERT_IF_NEEDED);
    LineAttr line = GetLineAttr(job->obj);
    FillAttr fill = GetFillAttr(job->obj);
    if (draw) {
        if (filled)// && job->obj->fill != FILL_NONE)
            Graph::_current->_canvas->Fill(c, fill);
        Graph::_current->_canvas->Line(c, line);
    } else {
        Graph *gc = dynamic_cast<Graph*>(Graph::_current);
        if (gc) {
            GraphElement *e = gc->GetElement(job->obj->u.n);
            if (e) {
                gc->boundingBox += c.GetBoundingBox();
                if (!filled)
                    fill = FillAttr::None();
                e->AddDrawingPrimitive(std::move(c), line, fill, Graph::_current->_chart->Shapes);
            }
        }
    }
}

/** Callback called by graphviz to draw a curve - we extract it and save as a drawing primitive
 * into Graph::_current or actually draw it on Graph::_current->_canvas, if 'draw' is true.*/
void canvas_cover_beziercurve(bool draw, GVJ_t * job, pointf * A, int n, int filled)
{
    if (Graph::_current==nullptr || Graph::_current->_canvas==nullptr) return;
    Path p;
    for (int j = 1; j < n; j += 3)
        p.append(contour::Edge(XY(A[j-1].x, A[j-1].y),
            XY(A[j+2].x, A[j+2].y),
            XY(A[j+0].x, A[j+0].y),
            XY(A[j+1].x, A[j+1].y)));
    LineAttr line = GetLineAttr(job->obj);
    FillAttr fill = GetFillAttr(job->obj);
    if (draw) {
        if (filled)// && job->obj->fill != FILL_NONE)
            Graph::_current->_canvas->Fill(Contour(p), fill);
        Graph::_current->_canvas->Line(p, line);
    } else {
        Graph *gc = dynamic_cast<Graph*>(Graph::_current);
        if (gc) {
            GraphElement *e = gc->GetElement(job->obj->u.n);
            if (e) {
                gc->boundingBox += p.CalculateBoundingBox();
                if (filled)// && job->obj->fill != FILL_NONE)
                    e->AddDrawingPrimitive(Contour(std::move(p), ECloseType::CLOSE_INVISIBLE, EForceClockwise::INVERT_IF_NEEDED), line, fill, Graph::_current->_chart->Shapes);
                else
                    e->AddDrawingPrimitive(std::move(p), line);
            }
        }
    }
}

/** Callback called by graphviz to draw a polyline - we extract it and save as a drawing primitive
 * into Graph::_current or actually draw it on Graph::_current->_canvas, if 'draw' is true.*/
void canvas_cover_polyline(bool draw, GVJ_t * job, pointf * A, int n)
{
    if (Graph::_current==nullptr || Graph::_current->_canvas==nullptr) return;
    std::vector<XY> points;
    points.reserve(n);
    for (int i = 0; i<n; i++)
        points.push_back(XY(A[i].x, A[i].y));
    Path p(std::move(points));
    LineAttr line = GetLineAttr(job->obj);
    if (draw)
        Graph::_current->_canvas->Line(p, line);
    else {
        Graph *gc = dynamic_cast<Graph*>(Graph::_current);
        if (gc) {
            GraphElement *e = gc->GetElement(job->obj->u.n);
            if (e) {
                gc->boundingBox += p.CalculateBoundingBox();
                e->AddDrawingPrimitive(std::move(p), line);
            }
        }
    }
}

/** Callback called by graphviz to draw an ellipse - we extract it and save as a drawing primitive
 * into Graph::_current or actually draw it on Graph::_current->_canvas, if 'draw' is true.*/
void canvas_cover_ellipse(bool draw, GVJ_t * job, pointf * A, int filled)
{
    if (Graph::_current==nullptr || Graph::_current->_canvas==nullptr) return;
    Contour c(XY(A[0].x, A[0].y), fabs(A[1].x-A[0].x), fabs(A[1].y-A[0].y));
    LineAttr line = GetLineAttr(job->obj);
    FillAttr fill = GetFillAttr(job->obj);
    if (draw) {
        if (filled)// && job->obj->fill != FILL_NONE)
            Graph::_current->_canvas->Fill(c, fill);
        Graph::_current->_canvas->Line(c, line);
    } else {
        Graph *gc = dynamic_cast<Graph*>(Graph::_current);
        if (gc) {
            GraphElement *e = gc->GetElement(job->obj->u.n);
            if (e) {
                gc->boundingBox += c.GetBoundingBox();
                if (!filled)
                    fill = FillAttr::None();
                e->AddDrawingPrimitive(std::move(c), line, fill, Graph::_current->_chart->Shapes);
            }
        }
    }
}



//A graphviz rendering plugin comes here, which just collects cover info

/** A graphviz rendering device to collect primitives. */
gvdevice_engine_t cover_device_engine = {
    nullptr, //void (*initialize) (GVJ_t * firstjob);
    nullptr, //void(*format) (GVJ_t * firstjob);
    nullptr, //void(*finalize) (GVJ_t * firstjob);
};

/** Features for the graphviz rendering device to collect primitives. */
gvdevice_features_t cover_device_features = {
    GVDEVICE_DOES_TRUECOLOR | GVDEVICE_NO_WRITER, //int flags;
    {0,0},    //pointf default_margin;  /* left/right, top/bottom - points */
    {612,792}, //letter sized  //pointf default_pagesize;/* default page width, height - points */
    {72, 72} //pointf default_dpi;
};

void cover_textspan(GVJ_t *job, pointf p, textspan_t * span)
{
    canvas_cover_textspan(false, job, p, span);
}

#if GRAPHVIZ_VER >= 1000
void cover_polygon(GVJ_t* job, pointf* A, size_t n, int filled)
#else
void cover_polygon(GVJ_t * job, pointf * A, int n, int filled)
#endif
{
    canvas_cover_polygon(false, job, A, n, filled);
}

#if GRAPHVIZ_VER >= 1000
void cover_beziercurve(GVJ_t* job, pointf* A, size_t n, int filled)
#elif GRAPHVIZ_VER >= 800
void cover_beziercurve(GVJ_t* job, pointf* A, int n, int filled)
#else
void cover_beziercurve(GVJ_t * job, pointf * A, int n, int /*arrow_at_start*/, int /*arrow_at_end*/, int filled)
#endif

{
    canvas_cover_beziercurve(false, job, A, n, filled);
}

#if GRAPHVIZ_VER >= 1000
void cover_polyline(GVJ_t* job, pointf* A, size_t n)
#else
void cover_polyline(GVJ_t * job, pointf * A, int n)
#endif
{
    canvas_cover_polyline(false, job, A, n);
}

void cover_ellipse(GVJ_t * job, pointf * A, int filled)
{
    canvas_cover_ellipse(false, job, A, filled);
}

/** A graphviz rendering engine for collecting drawing primitives. */
gvrender_engine_t cover_render_engine = {
    nullptr,               //(*begin_job) (GVJ_t * job);
    nullptr,               //(*end_job) (GVJ_t * job);
    nullptr,               //(*begin_graph) (GVJ_t * job);
    nullptr,               //(*end_graph) (GVJ_t * job);
    nullptr,               //(*begin_layer) (GVJ_t * job, char *layername, int layerNum, int numLayers);
    nullptr,               //(*end_layer) (GVJ_t * job);
    nullptr,               //(*begin_page) (GVJ_t * job);
    nullptr,               //(*end_page) (GVJ_t * job);
    nullptr,               //(*begin_cluster) (GVJ_t * job);
    nullptr,               //(*end_cluster) (GVJ_t * job);
    nullptr,               //(*begin_nodes) (GVJ_t * job);
    nullptr,               //(*end_nodes) (GVJ_t * job);
    nullptr,               //(*begin_edges) (GVJ_t * job);
    nullptr,               //(*end_edges) (GVJ_t * job);
    nullptr,               //(*begin_node) (GVJ_t * job);
    nullptr,               //(*end_node) (GVJ_t * job);
    nullptr,               //(*begin_edge) (GVJ_t * job);
    nullptr,               //(*end_edge) (GVJ_t * job);
    nullptr,               //(*begin_anchor) (GVJ_t * job, char *href, char *tooltip, char *target, char *id);
    nullptr,               //(*end_anchor) (GVJ_t * job);
    nullptr,               //(*begin_label) (GVJ_t * job, label_type type);
    nullptr,               //(*end_label) (GVJ_t * job);
    cover_textspan,     //(*textspan) (GVJ_t * job, pointf p, textspan_t * span);
    nullptr,               //(*resolve_color) (GVJ_t * job, gvcolor_t * color);
    cover_ellipse,      //(*ellipse) (GVJ_t * job, pointf * A, int filled);
    cover_polygon,      //(*polygon) (GVJ_t * job, pointf * A, size_t n, int filled);
    cover_beziercurve,  //(*beziercurve) (GVJ_t * job, pointf * A, size_t n, int arrow_at_start, int arrow_at_end, int); after graphviz 7.0: (GVJ_t * job, pointf * A, int n, int arrow_at_start, int arrow_at_end, int);
    cover_polyline,     //(*polyline) (GVJ_t * job, pointf * A, size_t n);
    nullptr,               //(*comment) (GVJ_t * job, char *comment);
    nullptr,               //(*library_shape) (GVJ_t * job, char *name, pointf * A, int n, int filled);
};

//GVRENDER_Y_GOES_DOWN
//GVRENDER_DOES_TRANSFORM
//GVRENDER_DOES_ARROWS
//GVRENDER_DOES_LABELS
//GVRENDER_DOES_MAPS
//GVRENDER_DOES_MAP_RECTANGLE
//GVRENDER_DOES_MAP_CIRCLE
//GVRENDER_DOES_MAP_POLYGON
//GVRENDER_DOES_MAP_ELLIPSE
//GVRENDER_DOES_MAP_BSPLINE
//GVRENDER_DOES_TOOLTIPS
//GVRENDER_DOES_TARGETS
//GVRENDER_DOES_Z
//GVRENDER_NO_WHITE_BG

/** Feature for the graphviz rendering engine for collecting drawing primitives. */
gvrender_features_t cover_render_features = {
    GVRENDER_Y_GOES_DOWN,      //int flags;
    0,         //double default_pad;	/* graph units */
    nullptr,      //char **knowncolors;
    0,         //int sz_knowncolors;
    RGBA_BYTE  //color_type_t color_type;
};



//A graphviz rendering plugin comes here, which renders to a Canvas object


/** A graphviz rendering device to draw on a Canvas object. */
gvdevice_engine_t canvas_device_engine = {
    nullptr, //void (*initialize) (GVJ_t * firstjob);
    nullptr, //void(*format) (GVJ_t * firstjob);
    nullptr, //void(*finalize) (GVJ_t * firstjob);
};


//GVDEVICE_DOES_PAGES (1<<5)
//GVDEVICE_DOES_LAYERS (1<<6)
//GVDEVICE_EVENTS (1<<7)
//GVDEVICE_DOES_TRUECOLOR (1<<8)
//GVDEVICE_BINARY_FORMAT (1<<9)
//GVDEVICE_COMPRESSED_FORMAT (1<<10)
//GVDEVICE_NO_WRITER (1<<11)

/** Features for the graphviz rendering device to draw on a Canvas object. */
gvdevice_features_t canvas_device_features = {
    GVDEVICE_DOES_TRUECOLOR | GVDEVICE_NO_WRITER, //int flags;
    {0,0},    //pointf default_margin;  /* left/right, top/bottom - points */
    {612,792}, //letter sized  //pointf default_pagesize;/* default page width, height - points */
    {72, 72} //pointf default_dpi;
};


void canvas_textspan(GVJ_t *job, pointf p, textspan_t * span)
{
    canvas_cover_textspan(true, job, p, span);
}

#if GRAPHVIZ_VER >= 1000
void canvas_polygon(GVJ_t* job, pointf* A, size_t n, int filled)
#else
void canvas_polygon(GVJ_t * job, pointf * A, int n, int filled)
#endif
{
    canvas_cover_polygon(true, job, A, n, filled);
}

#if GRAPHVIZ_VER >= 1000
void canvas_beziercurve(GVJ_t* job, pointf* A, size_t n, int filled)
#elif GRAPHVIZ_VER >= 800
void canvas_beziercurve(GVJ_t* job, pointf* A, int n, int filled)
#else
void canvas_beziercurve(GVJ_t * job, pointf * A, int n, int /*arrow_at_start*/, int /*arrow_at_end*/, int filled)
#endif
{
    canvas_cover_beziercurve(true, job, A, n, filled);
}

#if GRAPHVIZ_VER >= 1000
void canvas_polyline(GVJ_t* job, pointf* A, size_t n)
#else
void canvas_polyline(GVJ_t * job, pointf * A, int n)
#endif
{
    canvas_cover_polyline(true, job, A, n);
}

void canvas_ellipse(GVJ_t * job, pointf * A, int filled)
{
    canvas_cover_ellipse(true, job, A, filled);
}


/** A graphviz rendering engine for drawing on a Canvas object. */
gvrender_engine_t canvas_render_engine = {
    nullptr,               //(*begin_job) (GVJ_t * job);
    nullptr,               //(*end_job) (GVJ_t * job);
    nullptr,               //(*begin_graph) (GVJ_t * job);
    nullptr,               //(*end_graph) (GVJ_t * job);
    nullptr,               //(*begin_layer) (GVJ_t * job, char *layername, int layerNum, int numLayers);
    nullptr,               //(*end_layer) (GVJ_t * job);
    nullptr,               //(*begin_page) (GVJ_t * job);
    nullptr,               //(*end_page) (GVJ_t * job);
    nullptr,               //(*begin_cluster) (GVJ_t * job);
    nullptr,               //(*end_cluster) (GVJ_t * job);
    nullptr,               //(*begin_nodes) (GVJ_t * job);
    nullptr,               //(*end_nodes) (GVJ_t * job);
    nullptr,               //(*begin_edges) (GVJ_t * job);
    nullptr,               //(*end_edges) (GVJ_t * job);
    nullptr,               //(*begin_node) (GVJ_t * job);
    nullptr,               //(*end_node) (GVJ_t * job);
    nullptr,               //(*begin_edge) (GVJ_t * job);
    nullptr,               //(*end_edge) (GVJ_t * job);
    nullptr,               //(*begin_anchor) (GVJ_t * job, char *href, char *tooltip, char *target, char *id);
    nullptr,               //(*end_anchor) (GVJ_t * job);
    nullptr,               //(*begin_label) (GVJ_t * job, label_type type);
    nullptr,               //(*end_label) (GVJ_t * job);
    canvas_textspan,    //(*textspan) (GVJ_t * job, pointf p, textspan_t * span);
    nullptr,               //(*resolve_color) (GVJ_t * job, gvcolor_t * color);
    canvas_ellipse,     //(*ellipse) (GVJ_t * job, pointf * A, int filled);
    canvas_polygon,     //(*polygon) (GVJ_t * job, pointf * A, size_t n, int filled);
    canvas_beziercurve, //(*beziercurve) (GVJ_t * job, pointf * A, size_t n, int arrow_at_start, int arrow_at_end, int);
    canvas_polyline,    //(*polyline) (GVJ_t * job, pointf * A, size_t n);
    nullptr,               //(*comment) (GVJ_t * job, char *comment);
    nullptr,               //(*library_shape) (GVJ_t * job, char *name, pointf * A, int n, int filled);
};



//GVRENDER_Y_GOES_DOWN
//GVRENDER_DOES_TRANSFORM
//GVRENDER_DOES_ARROWS
//GVRENDER_DOES_LABELS
//GVRENDER_DOES_MAPS
//GVRENDER_DOES_MAP_RECTANGLE
//GVRENDER_DOES_MAP_CIRCLE
//GVRENDER_DOES_MAP_POLYGON
//GVRENDER_DOES_MAP_ELLIPSE
//GVRENDER_DOES_MAP_BSPLINE
//GVRENDER_DOES_TOOLTIPS
//GVRENDER_DOES_TARGETS
//GVRENDER_DOES_Z
//GVRENDER_NO_WHITE_BG

/** Features for the graphviz rendering engine for drawing on a Canvas object. */
gvrender_features_t canvas_render_features = {
    GVRENDER_Y_GOES_DOWN,      //int flags;
    0,         //double default_pad;	/* graph units */
    nullptr,      //char **knowncolors;
    0,         //int sz_knowncolors;
    RGBA_BYTE  //color_type_t color_type;
};


/** A list of the two rendering devices we need. */
gvplugin_installed_t canvas_cover_devices[] = {
    //canvas:canvas here mean device:renderer, we express here that we want the 'canvas' renderer, too.
    {/*id=*/2, /*type=*/ "canvas:canvas", /*quality=*/1, &canvas_device_engine, &canvas_device_features},
    {/*id=*/3, /*type=*/ "cover:cover",   /*quality=*/1, &cover_device_engine, &cover_device_features},
    {0, nullptr, 0, nullptr, nullptr}
};

/** A list of the two rendering engines we need. */
gvplugin_installed_t canvas_cover_renderers[] = {
    {/*id=*/1, /*type=*/ "canvas", /*quality=*/1, &canvas_render_engine, &canvas_render_features},
    {/*id=*/2, /*type=*/ "cover",  /*quality=*/1, &cover_render_engine, &cover_render_features},
    {0, nullptr, 0, nullptr, nullptr}
};

/** A collection of APIs we implement. */
gvplugin_api_t canvas_apis[] = {
    {API_device, canvas_cover_devices},
    {API_render, canvas_cover_renderers},
    {(api_t)0, 0}
};

/** Our plugin interface. */
gvplugin_library_t gvplugin_canvas_LTX_library = {(char*)"canvas", canvas_apis}; //this field is not const char * for some reason
