/*
    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 blockstyle.cpp Block specific style and context definitions
* @ingroup libblock_files */

#define _CRT_SECURE_NO_WARNINGS //to skip sscanf warnings.

#include <cstring>
#include <cmath>
#include "blockstyle.h"
#include "blockcsh.h"
#include "blockchart.h"
#include "utf8utils.h"

using namespace block;

/** Parse a blockname @ edge strings or bname+bname+...@ edge strings.
 * Valid formulation:
 * bname[+bname...][+offset][@[m][egde|percent[%]][+offset]]
 * or [offset]@[m][edge|percent[%]]] in which case a 'prev' is assumed.
 * or [m]percent%[+offset] in which case 'parent' is assumed
 * or 'offset' in which case 'parent' is assmumed.
 * (m\<offset\> is interpreted as a block name. Use m0%+\<offset\> if you want a fixed offset from a left/top margin)
 * We also accept the empty string in which case we clear 'blocks'.
 * In any other valid case 'blocks' will not be empty.
 * It is invalid to have offset both before and after the '@'.
 * We may have a location escape before each block name.
 * 'def' indicates which edge to use when none specified.
 * We look for left/right/etc if 'horizontal' is set, else top/bottom/etc.
 * returns false on error and emits error message, if 'error' and 'linenum' is not nullptr.
 * @param [in] text The text of the attribute
 * @param [in] def The default side (+offset) to apply when there is no '@' symbol
 *                 or nothing after it or only a single 'm' as in: '< example_block > @ m'
 * @param [in] horizontal True if we parse attributes for a horizontal alignment attr,
 *                        false if vertical. Relevant to what 'edge' tokens we recognize.
 * @param [in] p The priority of the attribute to set.
 * @param [in] error If non-null, we emit errors in case of problems.
 * @param [in] l The position of the attribute value in the file.
 * @param [in] whattoignore Text that will be pasted into error messages saying "Ignoring XXX.".
 * @returns true on success, false on error.*/
bool AlignTo::Parse(std::string_view text, EdgePos def, bool horizontal, EAlignPrio p,
                    MscError *error, const FileLineCol *l, std::string_view whattoignore)
{
    FileLineCol pos = l ? *l : FileLineCol();
    prio = p;
    justify = false;
    if (text.length()==0) {
        //We are cleared. Return true and set 'blocks' to empty.
        blocks.clear();
        return true;
    }
    prio = p;
    justify = false;
    const auto at = std::min(text.find_last_of('@'), text.length());
    const bool had_at = at!=text.length();
    //parse to a block list
    const std::string_view blocknames = text.substr(0, at);
    blocks = StringFormat::Split(blocknames, pos, "+-", " >"); //Honour and keep - as a separatro as it may be the sign of an appended num.
    remove_if(blocks, [](const auto&a) {return a.name.length()==0; });
    if (!had_at) {
        //we have no @ symbol. Check if we are "[m]10%-20" style
        bool have_non_num = false;
        edge = {0,false,0};
        _ASSERT(blocks.size());
        for (auto &sp:blocks) {
            std::string_view n = sp.name;
			const bool percent = n.length() && n.back()=='%';
			if (percent) n.remove_suffix(1);
			if (n.length() && (n.front()=='m'||n.front()=='M')) {
				//if we have "m<offset>" interpret it as a block name.
				if (blocks.size()==1 && !percent) {
					have_non_num = true;
					break;
				}
				//"m<num>%" or m<num>+<num> is OK.
                edge.margin = true;
                n.remove_prefix(1);
            }
            double num;
            have_non_num = from_chars(n, num);
            if (have_non_num) break;
            if (percent) edge.pos += num/100;
            else edge.offset += num;
        }
        if (!have_non_num) {
            blocks.clear();
            blocks.emplace_back(STRING_PARENT, l ? FileLineColRange(*l, *l) : FileLineColRange());
            return true;
        }
        //if we could not parse
    }
    text.remove_prefix(std::min(at+1, text.length())); //if we had no '@' this empties string
    const bool margin = [&text]() {
		if (text.length()==0) return false;
		if (CaseInsensitiveEqual("middle", text.substr(0, 6)))
			return false;
		if (text.front()!='m' && text.front()!='M')
            return false;
        text.remove_prefix(1);
        return true;
    }();
    //Check for a number in the last block
    double offset = 0;
    const FileLineCol offset_among_blocks =
        blocks.size()==0 || !from_chars(blocks.back().name, offset) ?
            blocks.back().file_pos.start : FileLineCol();
    if (offset_among_blocks.IsInvalid())
        offset = 0; //if not, keep zero offset
    else
        blocks.pop_back(); //if number, pop it
    if (blocks.size()==0)
        blocks.emplace_back("prev", l ? FileLineColRange(*l, *l) : FileLineColRange());
    if (text.size()==0) {
        edge = {def.pos, margin, offset};
        return true;
    }
    double d;
    if (text.front()=='+') text.remove_prefix(1);
    auto res = std::from_chars(&text.front(), &text.back()+1, d);
    if (res.ec==std::errc{} && res.ptr!=&text.front()) {
        if (d<-10000 || d>10000) {
            if (error && l) {
                error->Error(l->AdvanceUTF8(blocknames).NextChar(),
                             StrCat("The percentage after the '@' needs to be between +-10000. Ignoring ",
                                    whattoignore, '.'));
            }
            return false;
        }
        edge = {d/100, margin, offset};
        text.remove_prefix(res.ptr-&text.front());
    } else if (CaseInsensitiveBeginsWith(text, "Center")) {
		edge = {0.5, margin, offset};
        text.remove_prefix(6);
	} else if (CaseInsensitiveBeginsWith(text, "Middle")) {
		edge = {0.5, margin, offset};
        text.remove_prefix(6);
    } else if (CaseInsensitiveBeginsWith(text, "XCenter")) {
		edge = {0.5, margin, offset};
        text.remove_prefix(7);
    } else if (CaseInsensitiveBeginsWith(text, "XMiddle")) {
		edge = {0.5, margin, offset};
        text.remove_prefix(7);
    } else if (CaseInsensitiveBeginsWith(text, "YCenter")) {
		edge = {0.5, margin, offset};
        text.remove_prefix(7);
    } else if (CaseInsensitiveBeginsWith(text, "YMiddle")) {
		edge = {0.5, margin, offset};
        text.remove_prefix(7);
    } else if (horizontal) {
        if (CaseInsensitiveBeginsWith(text, "Left")) {
            edge = {0.0, margin, offset};
            text.remove_prefix(4);
        } else if (CaseInsensitiveBeginsWith(text, "Right")) {
            edge = {1.0, margin, offset};
            text.remove_prefix(5);
        } else {
            if (error && l) {
                error->Error(l->AdvanceUTF8(blocknames).NextChar(),
                             StrCat("Missing 'left'/'center'/'right' or a percentage number after the '@' (or '@m'). Ignoring ",
                             whattoignore, '.'));
            }
			return false;
		}
	} else {
        if (CaseInsensitiveBeginsWith(text, "Top")) {
            edge = {0.0, margin, offset};
            text.remove_prefix(3);
        } else if (CaseInsensitiveBeginsWith(text, "Bottom")) {
            edge = {1.0, margin, offset};
            text.remove_prefix(6);
        } else {
            if (error && l)
                error->Error(l->AdvanceUTF8(blocknames).NextChar(),
                             StrCat("Missing 'top'/'middle'/'bottom' or a percentage number after the '@' (or '@m'). Ignoring ",
                             whattoignore, '.'));
			return false;
		}
	}
    if (text.length()==0)
        return true;
    if (from_chars(text, offset)) {
        _ASSERT(0); //parser should not let this happen: text after the percentage, but not a valid number.
    } else if (offset_among_blocks.IsValid()) {
        //We have an offset number both before and after the '@' symbol.
        if (error)
            error->Error(offset_among_blocks,
                         "When using an offset after the '@' symbol, you should not use one before it. Ignoring the one before.");
    } else
        edge.offset = offset;
    return true;
}

/** Returns true if edge and blocks are the same.
 * Assumes 'blocks' uses resolved (unique) names and has no duplicates.*/
bool AlignTo::IsSame(const AlignTo &o) const
{
    if (edge!=o.edge) return false;
    //Handle the most common cases in a fast path
    if (blocks.size()==0) return true;
    if (o.blocks.size()!=blocks.size()) return false;
    if (blocks.size()==1) return blocks.front().name==o.blocks.front().name;

    //Now determine equality order-free
    std::set<std::string> a;
    for (auto &sp:blocks)
        a.insert(sp.name);
    for (auto &sp:o.blocks) {
        auto i = a.find(sp.name);
        if (i==a.end()) return false;
    }
    return true;
}

/** Parses a coordinate (x,y) pair.
 * Silently returns if we don't start with a '(' or don't end in a ')' or are empty.
 * On other problems it returns an error on 'error'.
 * Any of the coordinates may be empty, but not both.
 * Coordinates missing the @ symbol are assumed middle/center.
 * If blocks are missing prev is assumed.
 * @param [in] text The text to parse. We assume it has location escapes in front of every block.
 *                  It is not the actual chart text, but sanitized & reconstituted, e.g., any line break or
 *                  whitespace may be missing, + signs may also be missing.
 * @param [in] error Any error is emitted here. May be null.
 * @param [in] l The start of the coord. May be null.
 * @param [in] whattoignore The object we parse. On error we will say "ignoring this XXX".
 * @returns The two coordinates. Empty, if no valid stuff could be read and
 *                               an error has already be emitted.
 *                               One or both of the coordinates may have been missing, in this
 *                               we return an OptAttr with empty 'blocks'. This must be handled
 *                               by the caller.*/
OptAttr<std::array<AlignTo, 2>>
block::ParseCoord(std::string_view text, MscError *error,
				  const FileLineCol *l, std::string_view whattoignore)
{
    if (text.length()==0) return {};
    if (text.front()!='(') return {}; //OK, this is not a coordinate
    if (text.back()!=')') return {};  //Not ending in parenthesis, error already omitted in parser file.
    //'text' contains position escapes - hard to find a verbatim text comma. First check if there is exactly one.
    {
        const std::string plain = StringFormat::RemovePosEscapesCopy(text);
        const auto comma = plain.find_first_of(',');
        if (comma == std::string_view::npos) {
            if (error && l)
                error->Error(*l, StrCat("You need to separate the two coordinates with a comma. Ignoring this ", whattoignore, '.'));
            return {};
        }
        if (comma!= plain.find_last_of(',')) {
            if (error && l)
                error->Error(*l, StrCat("You can only have one comma in a 2D coordinate. Ignoring this ", whattoignore, '.'));
            return {};
        }
    }
    size_t comma = StringFormat::FindVerbatim(text, ",");
    const EdgePos def = {0.5, true, 0}; //equivalent to @middle or @center
    std::array<AlignTo, 2> ret;
    if (!ret[0].Parse(text.substr(1, comma-1),
                      def, true, EAlignPrio::Explicit, error, l, whattoignore) ||
        !ret[1].Parse(text.substr(comma+1, text.length()-comma-2),
                      def, false, EAlignPrio::Explicit, error, l, whattoignore))
        return {};
    //One or both of the coordinates may have empty 'blocks'. This must be handled by the caller.
    return {std::move(ret)};
}

/** Set the 'text' field to some human readable text (for error messages).
 * @param [in] attrname what attribute are we (top, bottom, left, xcenter, etc.).
 * Result will be like top=block @ topmargin.*/
void OptAlignAttr::SetText(std::string &&attrname)
{
    if (!is_set) {
        text.clear();
        return;
    }
    text = std::move(attrname);
    const bool vertical = CaseInsensitiveEqual(text, "top") ||
        CaseInsensitiveEqual(text, "bottom") || CaseInsensitiveEqual(text, "middle");
    if (is_margin && !CaseInsensitiveEqual(text, "middle") && !CaseInsensitiveEqual(text, "center"))
        text.insert(text.begin(), 'm');
    text.push_back('=');
    if (value.blocks.size() == 0)
        return;
    for (auto &s : value.blocks)
        text.append(s.name).push_back('+');
    text.pop_back();
    text.push_back('@');
    if (value.edge.margin)
        text.push_back('m');
    if (value.edge.pos==  0) text.append(vertical ? "top" : "left");
    else if (value.edge.pos==1  ) text.append(vertical ? "bottom" : "right");
    else if (value.edge.pos==0.5) text.append(vertical ? "middle" : "center");
    else text.append(std::to_string(int(value.edge.pos*100)));
}


/** Add the hints for box sides that may come after the @ symbol in attribute values or
 * coordinates.
 * @param csh The CSH object to add the hints to.
 * @param y If true, we consider vertical sides, else horizontal ones.
 * @param prefix If empty the hint will just say "The top part of the block".
 8               If non-empty, the hint will be prefixed with it, like "Align to the top..."*/
void AlignmentAttr::SideValuesAfterAt(Csh & csh, bool y, std::string_view prefix)
{
    auto arr = y ? AlignmentAttr::yalign_attr_values_descr : AlignmentAttr::xalign_attr_values_descr;
    for (auto p = arr+2; **p; p += 2) {
        std::string descr = p[1];
        auto at = descr.find("<block>@");
        if (at!=std::string::npos) descr.erase(at, 8);
        at = descr.find(" of <block>");
        if (at!=std::string::npos) descr.erase(at, 11);
        if (prefix.length()) {
            descr[0] = tolower(descr[0]);
            descr.insert(0, prefix);
        }
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE) + (*p+8), std::move(descr), EHintType::ATTR_VALUE, true,
                               y ? CshHintGraphicCallbackForVAlignment : CshHintGraphicCallbackForHAlignment,
                               (p-arr)/2, (p-arr)/2));
    }
    std::string perc = y ?
        "A specific part of the block (or set of blocks). "
        "0 is the top, 50 is the middle and 100 is the bottom. Use any number in-between."
        :
        "A specific part of the block (or set of blocks). "
        "0 is the left side, 50 is the center and 100 is the right side. Use any number in-between.";
    if (prefix.length()) {
        perc[0] = tolower(perc[0]);
        perc.insert(0, prefix);
    }
    csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<percentage>",
                           std::move(perc), EHintType::ATTR_VALUE, false));
}

/** If any X or Y direction element of 'toadd' is set, the three elements in that
 * direction is us. If none is set to an actual value, we clear the ones
 * explicitly cleared in 'toadd' (that is set to zero-length, resulting in empty "block").
 * This decision is made separately for the horizontal and vertical
 * direction.
 * This is used, when applying the attributes explicitly set on a block to
 * the default alignment attributes coming from the style of the parent or an
 * alignment modifier. */
AlignmentAttr &AlignmentAttr::MergeByDimension(const AlignmentAttr &toadd)
{
    if (toadd.Left.IsSetToNonEmpty() || toadd.Right.IsSetToNonEmpty() || toadd.XCenter.IsSetToNonEmpty()) {
        Left = toadd.Left;
        Right = toadd.Right;
        XCenter = toadd.XCenter;
    } else {
        if (toadd.Left.IsSetToEmpty()) Left.is_set = false;
        if (toadd.Right.IsSetToEmpty()) Right.is_set = false;
        if (toadd.XCenter.IsSetToEmpty()) XCenter.is_set = false;
    }
    if (toadd.Top.IsSetToNonEmpty() || toadd.Bottom.IsSetToNonEmpty() || toadd.YMiddle.IsSetToNonEmpty()) {
        Top = toadd.Top;
        Bottom = toadd.Bottom;
        YMiddle = toadd.YMiddle;
    } else {
        if (toadd.Top.IsSetToEmpty()) Top.is_set = false;
        if (toadd.Bottom.IsSetToEmpty()) Bottom.is_set = false;
        if (toadd.YMiddle.IsSetToEmpty()) YMiddle.is_set = false;
    }
    return *this;
}

/** If any X or Y direction element of 'toadd' is set, the three elements in that
 * direction is us. If none is set to an actual value, we clear the ones
 * explicitly cleared in 'toadd' (that is set to zero-length, resulting in empty "block").
 * This decision is made separately for the horizontal and vertical
 * direction.
 * This is used, when applying the attributes explicitly set on a block to
 * the default alignment attributes coming from the style of the parent or an
 * alignment modifier.
 * There are rules on how to set the priority when two styles are combined:
 * - If we are an ELEMENT style, keep the priority of the attributes added.
 * - If we are STYLE/GENERIC, set the priority to Explicit
 * - If we are STYLE/RUNNING, set the priority to Content if the added priority is Content, else Running_style.
 * - If we are STYLE/CONTENT, no style should be assigned to us, but use Content, nevertheless
 * - If we are DEFAULT or DEF_ADD, set the priority to Default_style
 * - if we are OPTION, now that is an internal error, as charts have no alignment attrs.
 * @param [in] toadd The alignment attributes to add.
 * @param [in] s The style we are part of.*/
AlignmentAttr & AlignmentAttr::MergeByDimension(const AlignmentAttr & toadd, const BlockStyle &s)
{
    MergeByDimension(toadd);
    EAlignPrio p;
    switch (s.type) {
    case EStyleType::OPTION:
    default:
        _ASSERT(0); FALLTHROUGH;
    case EStyleType::ELEMENT:
        return *this;
    case EStyleType::DEFAULT:
    case EStyleType::DEF_ADD:
        p = EAlignPrio::Default_style; break;
    case EStyleType::STYLE:
        switch (s.block_style_type) {
        default:
        case EBlockStyleType::Unspecified:
            _ASSERT(0); FALLTHROUGH;
        case EBlockStyleType::Generic:
            p = EAlignPrio::Explicit; break;
        case EBlockStyleType::Content:
            _ASSERT(0); //no style shall be assigned to us, but still
            p = EAlignPrio::Content; break;
        case EBlockStyleType::Running:
            p = EAlignPrio::Running_style; break;
        }
    };
    if (toadd.Left.IsSetToNonEmpty() || toadd.Right.IsSetToNonEmpty() || toadd.XCenter.IsSetToNonEmpty()) {
        if ((toadd.Left.IsSetToNonEmpty() && toadd.Left.value.prio==EAlignPrio::Content) ||
            (toadd.Right.IsSetToNonEmpty() && toadd.Right.value.prio==EAlignPrio::Content) ||
            (toadd.XCenter.IsSetToNonEmpty() && toadd.XCenter.value.prio==EAlignPrio::Content)) {
            //If any of the three attributes in this direction are 'content', all shoud be so
            _ASSERT((!toadd.Left.is_set || toadd.Left.value.prio==EAlignPrio::Content) &&
                    (!toadd.Right.is_set || toadd.Right.value.prio==EAlignPrio::Content) &&
                    (!toadd.XCenter.is_set || toadd.XCenter.value.prio==EAlignPrio::Content));
            //But then there is no need to update priorities -> they are already set to 'Content'
        } else
            Left.value.prio = XCenter.value.prio = Right.value.prio = p;
    }
    if (toadd.Top.IsSetToNonEmpty() || toadd.Bottom.IsSetToNonEmpty() || toadd.YMiddle.IsSetToNonEmpty()) {
        if ((toadd.Top.IsSetToNonEmpty() && toadd.Top.value.prio==EAlignPrio::Content) ||
            (toadd.Bottom.IsSetToNonEmpty() && toadd.Bottom.value.prio==EAlignPrio::Content) ||
            (toadd.YMiddle.IsSetToNonEmpty() && toadd.YMiddle.value.prio==EAlignPrio::Content)) {
            //If any of the three attributes in this direction are 'content', all shoud be so
            _ASSERT((!toadd.Top.is_set || toadd.Top.value.prio==EAlignPrio::Content) &&
                    (!toadd.Bottom.is_set || toadd.Bottom.value.prio==EAlignPrio::Content) &&
                    (!toadd.YMiddle.is_set || toadd.YMiddle.value.prio==EAlignPrio::Content));
            //But then there is no need to update priorities -> they are already set to 'Content'
        } else
            Top.value.prio = YMiddle.value.prio = Bottom.value.prio = p;
    }
    return *this;
}

/** Add an Attribute object from parsing to us.*/
bool AlignmentAttr::AddAttribute(const Attribute & a, EStyleType t, Chart *chart, EAlignPrio prio)
{
    AlignTo parse;
    if (a.Is("xpos") || a.Is("ypos")) {
        auto at = a.value.find_first_of('@');
        if (at!=std::string::npos) {
            chart->Error.Error(a, false, "Attribute "+a.name+" cannot contain an '@'. Only block(s) can be named. Ignoring the attribute.");
            return true;
        }
        if (!parse.Parse(a.value, {0.0, true}, a.Is("xpos"), prio, &chart->Error, &a.linenum_value.start, "attribute")) {
            a.error = true;
            return true;
        }
        if (a.Is("xpos")) {
            Left = {true, true, t,{parse.blocks,{0.0, true}, prio}};
            XCenter.is_set = false;
            Right = {true, true, t,{parse.blocks,{1.0, true}, prio}};
        } else {
            Top= {true, true, t,{parse.blocks,{0.0, true}, prio}};
            YMiddle.is_set = false;
            Bottom = {true, true, t,{parse.blocks,{1.0, true}, prio}};
        }
        return true;
    }
    if (a.Is("top")) {
        if (!parse.Parse(a.value, {0.0, false}, false, prio, &chart->Error, &a.linenum_value.start, "attribute")) {
            a.error = true;
            return true;
        }
        Top = {true, false, t, parse};
        return true;
    }
    if (a.Is("mtop")) {
        if (!parse.Parse(a.value, {0.0, true}, false, prio, &chart->Error, &a.linenum_value.start, "attribute")) {
            a.error = true;
            return true;
        }
        Top = {true, true, t, parse};
        return true;
    }
    if (a.Is("bottom")) {
        if (!parse.Parse(a.value, {1.0, false}, false, prio, &chart->Error, &a.linenum_value.start, "attribute")) {
            a.error = true;
            return true;
        }
        Bottom = {true, false, t, parse};
        return true;
    }
    if (a.Is("mbottom")) {
        if (!parse.Parse(a.value, {1.0, true}, false, prio, &chart->Error, &a.linenum_value.start, "attribute")) {
            a.error = true;
            return true;
        }
        Bottom = {true, true, t, parse};
        return true;
    }
    if (a.Is("middle")||a.Is("ycenter")||a.Is("ymiddle")) {
        if (!parse.Parse(a.value, {0.5, false}, false, prio, &chart->Error, &a.linenum_value.start, "attribute")) {
            a.error = true;
            return true;
        }
        YMiddle = {true, false, t, parse};
        return true;
    }
    if (a.Is("left")) {
        if (!parse.Parse(a.value, {0.0, false}, true, prio, &chart->Error, &a.linenum_value.start, "attribute")) {
            a.error = true;
            return true;
        }
        Left = {true, false, t, parse};
        return true;
    }
    if (a.Is("mleft")) {
        if (!parse.Parse(a.value, {0.0, true}, true, prio, &chart->Error, &a.linenum_value.start, "attribute")) {
            a.error = true;
            return true;
        }
        Left = {true, true, t, parse};
        return true;
    }
    if (a.Is("right")) {
        if (!parse.Parse(a.value, {1.0, false}, true, prio, &chart->Error, &a.linenum_value.start, "attribute")) {
            a.error = true;
            return true;
        }
        Right = {true, false, t, parse};
        return true;
    }
    if (a.Is("mright")) {
        if (!parse.Parse(a.value, {1.0, true}, true, prio, &chart->Error, &a.linenum_value.start, "attribute")) {
            a.error = true;
            return true;
        }
        Right = {true, true, t, parse};
        return true;
    }
    if (a.Is("center")||a.Is("xcenter")||a.Is("xmiddle")) {
        if (!parse.Parse(a.value, {0.5, false}, true, prio, &chart->Error, &a.linenum_value.start, "attribute")) {
            a.error = true;
            return true;
        }
        XCenter = {true, false, t, parse};
        return true;
    }
    return false;
}

/** Contains all keywords of alignment. First 7 for x, last 7 for y dir.*/
 const char * const AlignmentAttr::align_attr_names_descr[] = {"invalid", nullptr,
    "left", "Align the visual left side (leftmost x coordinate) of the block to something horizontally.",
    "mleft", "Align the left margin (leftmost x coordinate plus margin) of the block to something horizontally.",
    "right",  "Align the visual right side (rightmost x coordinate) of the block to something horizontally.",
    "mright", "Align the right margin (rightmost x coordinate plus margin) of the block to something horizontally.",
    "center", "Align the horizontal centerline of the block to something horizontally.",
    "xcenter", "Align the horizontal centerline of the block to something horizontally. Same as 'center'.",
    "xmiddle", "Align the horizontal centerline of the block to something horizontally.  Same as 'center'.",
    "xpos", "Align horizontally to another block or set of blocks. Sets both top and bottom.",

    "top", "Align the visual top (uppermost y coordinate) of the block to something vertically.",
    "mtop", "Align the top margin (uppermost visual extent plus margin) of the block to something vertically.",
    "bottom", "Align the visual bottom (lowermost y coordinate) of the block to something vertically.",
    "mbottom", "Align the bottom margin (lowermost visual extent plus margin) of the block to something vertically .",
    "middle", "Align the vertical centerline of the block to something vertically.",
    "ycenter", "Align the vertical centerline of the block to something vertically. Same as 'middle'.",
    "ymiddle", "Align the vertical centerline of the block to something vertically. Same as 'middle'.",
    "ypos", "Align vertically to another block or set of blocks. Sets both left and right.",
    ""
};

/** Add all the possible attribute names we recognize to csh.*/
void AlignmentAttr::AttributeNames(Csh & csh, std::string_view /*group_descr*/)
{
    csh.AddToHints(align_attr_names_descr, csh.HintPrefix(COLOR_ATTRNAME), EHintType::ATTR_NAME,
        CshHintGraphicCallbackForAttributeNames);
}

const char * const AlignmentAttr::yalign_attr_values_descr[] = {"invalid", nullptr,
    "<block>@mtop", "The top margin of <block>.",
    "<block>@top", "The visible top of <block>.",
    "<block>@middle", "The vertical centerline of <block>.",
    "<block>@ycenter", "The vertical centerline of <block>. Same as '<block>@middle'.",
    "<block>@ymiddle", "The vertical centerline of <block>. Same as '<block>@middle'.",
    "<block>@bottom", "The visible bottom of <block>.",
    "<block>@mbottom", "The bottom margin of <block>.",
    ""};

const char * const AlignmentAttr::xalign_attr_values_descr[] = {"invalid", nullptr,
    "<block>@mleft", "The left margin of <block>.",
    "<block>@left", "The visible left of <block>.",
    "<block>@center", "The horizontal centerline of <block>.",
    "<block>@xcenter", "The horizontal centerline of <block>. Same as '<block>@center'.",
    "<block>@xmiddle", "The horizontal centerline of <block>. Same as '<block>@center'.",
    "<block>@right", "The visible right of <block>.",
    "<block>@mright", "The right margin of <block>.",
    ""};

const double AlignmentAttr::positions[] = {-1, 0.1, 0, 0.9, 1, 0.5, 0.5, 0.5};

/** Callback for drawing a symbol before horizontal alignment names in hintboxes.
* @ingroup hintpopup_callbacks*/
bool block::CshHintGraphicCallbackForHAlignment(Canvas *canvas, CshHintGraphicParam p, CshHintStore&)
{
    if (!canvas) return false;
    const double d = AlignmentAttr::positions[std::min(std::max(1U, unsigned(p)), 7U)];
    const Block b(HINT_GRAPHIC_SIZE_X*0.3, HINT_GRAPHIC_SIZE_X*0.7, HINT_GRAPHIC_SIZE_Y*0.7, HINT_GRAPHIC_SIZE_Y*1.2);
    FillAttr fill(ColorType::lgray(), ColorType::white(), EGradientType::OUTWARD );
    LineAttr line(ELineType::SOLID, ColorType::black(), 2, ECornerType::NONE, 0);
    canvas->Fill(b, fill);
    canvas->Line(b, line);
    const double x = b.x.from*(1-d) + b.x.till*d;
    if (d>0.5)
        CshHintGraphicCallbackForBigArrowsDetailed(canvas, EArrowType::SOLID, XY(x, HINT_GRAPHIC_SIZE_Y*0.35),
                                                   HINT_GRAPHIC_SIZE_Y*0.25, 0, ColorType(192, 32, 0));
    else
        CshHintGraphicCallbackForBigArrowsDetailed(canvas, EArrowType::SOLID, XY(x, HINT_GRAPHIC_SIZE_Y*0.35),
                                                   HINT_GRAPHIC_SIZE_Y*0.25, 1, ColorType(192, 32, 0));
    return true;
}

/** Callback for drawing a symbol before vertical alignment names in hint boxes.
* @ingroup hintpopup_callbacks*/
bool block::CshHintGraphicCallbackForVAlignment(Canvas *canvas, CshHintGraphicParam p, CshHintStore&)
{
    if (!canvas) return false;
    const double d = AlignmentAttr::positions[std::min(std::max(1U, unsigned(p)), 7U)];
    const Block b(HINT_GRAPHIC_SIZE_X*-0.2, HINT_GRAPHIC_SIZE_X*0.3, HINT_GRAPHIC_SIZE_Y*0.3, HINT_GRAPHIC_SIZE_Y*0.7);
    FillAttr fill(ColorType::lgray(), ColorType::white(), EGradientType::OUTWARD);
    LineAttr line(ELineType::SOLID, ColorType::black(), 2, ECornerType::NONE, 0);
    canvas->Fill(b, fill);
    canvas->Line(b, line);
    const double y = b.y.from*(1-d) + b.y.till*d;
    if (d>0.5)
        CshHintGraphicCallbackForBigArrowsDetailed(canvas, EArrowType::SOLID, XY(HINT_GRAPHIC_SIZE_X*0.75, y),
                                               HINT_GRAPHIC_SIZE_Y*0.25, 2, ColorType(192, 32, 0));
    else
        CshHintGraphicCallbackForBigArrowsDetailed(canvas, EArrowType::SOLID, XY(HINT_GRAPHIC_SIZE_X*0.75, y),
                                               HINT_GRAPHIC_SIZE_Y*0.25, 3, ColorType(192, 32, 0));
    return true;
}




/** Add all the possible attribute values we recognize for attribute 'attr' to 'csh'.*/
bool AlignmentAttr::AttributeValues(std::string_view attr, Csh & csh)
{
    static const char * const yalign_attr_names[] =
    {"top", "mtop", "bottom", "mbottom", "middle", "ycenter", "ymiddle", nullptr};
    static const char * const xalign_attr_names[] =
    {"left", "mleft", "right", "mright", "center", "xcenter", "xmiddle", nullptr};
    for (const char * const *p = yalign_attr_names; *p; p++)
        if (CaseInsensitiveEqual(attr, *p)) {
            csh.AddToHints(yalign_attr_values_descr, csh.HintPrefixNonSelectable(), EHintType::ATTR_VALUE,
                           CshHintGraphicCallbackForVAlignment, true, false);
            csh.AddEntityNamesAtTheEnd(StrCat("Align vertically to the ", attr, " of %s."));
            return true;
        }
    for (const char * const *p = xalign_attr_names; *p; p++)
        if (CaseInsensitiveEqual(attr, *p)) {
            csh.AddToHints(xalign_attr_values_descr, csh.HintPrefixNonSelectable(), EHintType::ATTR_VALUE,
                           CshHintGraphicCallbackForHAlignment, true, false);
            //Add detailed hints with actual entity names in BlockCsh::ProcessHints() (perhaps remove these)
            csh.AddEntityNamesAtTheEnd(StrCat("Align horizontally to the ", attr, " of %s."));
            return true;
        }
    if (CaseInsensitiveEqual("xpos", attr)) {
        csh.AddEntityNamesAtTheEnd("Align horizontally to block %s. Sets both top and bottom.");
        return true;
    }
    if (CaseInsensitiveEqual("ypos", attr)) {
        csh.AddEntityNamesAtTheEnd("Align vertically to block %s. Sets both left and right.");
        return true;
    }
    return false;
}

/** Return 1 if the attribute name is that of a horizontal alignment
 * attribute; 2 if that of a vertical one; and 0 if neither.*/
int AlignmentAttr::AttributeNameDir(std::string_view aname)
{
    for (auto p = align_attr_names_descr; **p; p += 2)
        if (CaseInsensitiveEqual(aname, *p))
            return int(p-AlignmentAttr::align_attr_names_descr-2)/16 + 1;
    return 0;
}

/** Set the human readable text fields for all our members.*/
void AlignmentAttr::SetTexts()
{
    Top.SetText("top");
    Bottom.SetText("bottom");
    Left.SetText("left");
    Right.SetText("right");
    XCenter.SetText("center");
    YMiddle.SetText("middle");
}


///Control which components are used
BlockStyle::BlockStyle(EStyleType tt, EBlockStyleType btt, EBlockStyleTarget ttgt, EColorMeaning cm, bool a,
    bool t, bool l, bool f, bool s, bool nu, bool shp,
    bool lab, bool al, bool c, bool m, bool r, bool mkr) :
    SimpleStyleWithArrow(tt, cm, a ? EArcArrowType::ARROW : EArcArrowType::NONE, t, l, f, s, nu, shp),
    f_label(lab), f_alignment(al), f_content(c),  f_margin(m), f_routing(r), f_marker(mkr),
    block_style_type(btt), block_style_target(ttgt), marker(mkr ? EArcArrowType::ARROW : EArcArrowType::NONE),
	draw_time_is_before(true)
{
	Empty();
}

//Has all the components, but is empty
BlockStyle::BlockStyle(EStyleType tt, EBlockStyleTarget ttgt, EColorMeaning cm) :
    SimpleStyleWithArrow(tt, cm),
    f_label(true), f_alignment(true), f_content(true), f_margin(true),
    f_routing(true), f_marker(true), block_style_type(EBlockStyleType::Unspecified),
    block_style_target(ttgt), marker(EArcArrowType::ARROW), draw_time_is_before(true)
{
    if (tt==EStyleType::STYLE)
        block_style_type = EBlockStyleType::Generic;
    Empty();
}

//Has all the components, but is empty
BlockStyle::BlockStyle(EBlockStyleType btt, EBlockStyleTarget ttgt, EColorMeaning cm) :
    SimpleStyleWithArrow(EStyleType::STYLE, cm),
    f_label(true), f_alignment(true), f_content(true), f_margin(true),
    f_routing(true), f_marker(true), block_style_type(btt),
    block_style_target(ttgt), marker(EArcArrowType::ARROW), draw_time_is_before(true)
{
    Empty();
}

void BlockStyle::Empty()
{
    SimpleStyleWithArrow::Empty();
    label.reset();
    alignment.Empty();
    XChildAlign.reset();
    YChildAlign.reset();
    content_margin.reset();
    min_margin[0].reset();
    min_margin[1].reset();
    max_margin[0].reset();
    max_margin[1].reset();
    min_imargin[0].reset();
    min_imargin[1].reset();
    max_imargin[0].reset();
    max_imargin[1].reset();
    label_imargin.reset();
    allow_arrows.reset();
    size[0].reset();
    size[1].reset();
    aspect_ratio.reset();
    label_mode.reset();
    multi_offset[0].reset();
    multi_offset[1].reset();
    label_pos.reset();
    label_align.reset();
    label_orient.reset();
    routing.reset();
    distance.reset();
    distance_per_block.reset();
    route_cross.reset();
    route_cross_parent.reset();
    route_factor.reset();
    route_arrow_distance.reset();
    route_block_others.reset();
    route_linearextend.reset();
    route_clip_block.reset();
    routing_try_harder.reset();
    routing_allow_joint_layout.reset();
    marker.Empty();
    draw_time.clear();
    indicator.reset();
}

bool BlockStyle::IsEmpty() const noexcept
    {
        return SimpleStyleWithArrow::IsEmpty()
            && (!f_label || (!label
                            && !label_pos
                            && !label_align
                            && !label_orient))
            && (!f_alignment || (alignment.IsEmpty()
                                && !XChildAlign
                                && !YChildAlign))
            && (!f_margin || (!content_margin
                             && !min_margin[0]
                             && !min_margin[1]
                             && !max_margin[0]
                             && !max_margin[1]
                             && !min_imargin[0]
                             && !min_imargin[1]
                             && !max_imargin[0]
                             && !max_imargin[1]
                             && !label_imargin
                             && !size[0]
                             && !size[1]
                             && !aspect_ratio
                             && !label_mode
                             && !multi_offset[0]
                             && !multi_offset[1]))
            && (!f_content || (!allow_arrows
                              && !indicator))
            && (!f_routing || (!routing
                               && !distance
                               && !distance_per_block
                               && !route_cross
                               && !route_cross_parent
                               && !route_factor
                               && !route_arrow_distance
                               && !route_block_others
                               && !route_linearextend
                               && !route_clip_block
                               && !routing_try_harder
                               && !routing_allow_joint_layout))
            && (!f_marker || marker.IsEmpty())
            && !draw_time.empty();
    }


void BlockStyle::MakeCompleteButText()
{
    //set default value to all your attributes, except text
    //(so that global text options can be added)
    SimpleStyleWithArrow::MakeCompleteButText();
    if (f_label) {
        if (!label) label.emplace();
        if (!label_pos) label_pos = EDirection::Above;
        if (!label_align) label_align = 0.5;
        if (!label_orient) label_orient = EOrientation::Normal;
    }
    if (f_alignment) {
        alignment.MakeCompleteButText();
        if (!XChildAlign) XChildAlign = { EInternalAlignType::Mid, EAlignPrio::Default, {} };
        if (!YChildAlign) YChildAlign = { EInternalAlignType::Mid, EAlignPrio::Default, {} };
    }
    if (f_margin) {
        if (!content_margin) content_margin = false;
        if (!min_margin[0]) min_margin[0] = BlockChart::def_margin;
        if (!min_margin[1]) min_margin[1] = BlockChart::def_margin;
        if (!max_margin[0]) max_margin[0] = BlockChart::def_margin;
        if (!max_margin[1]) max_margin[1] = BlockChart::def_margin;
        if (!min_imargin[0]) min_imargin[0] = BlockChart::def_imargin;
        if (!min_imargin[1]) min_imargin[1] = BlockChart::def_imargin;
        if (!max_imargin[0]) max_imargin[0] = BlockChart::def_imargin;
        if (!max_imargin[1]) max_imargin[1] = BlockChart::def_imargin;
        if (!label_imargin) label_imargin = BlockChart::def_imargin;
        //Do not set size or aspect_ratio
        if (!label_mode) label_mode = ELabelMode::ENLARGE;
        if (!multi_offset[0]) multi_offset[0] = BlockChart::def_multi_offset;
        if (!multi_offset[1]) multi_offset[1] = -BlockChart::def_multi_offset;
    }
    if (f_content) {
        if (!allow_arrows) allow_arrows = false;
        if (!indicator) indicator = true;
    }
    if (f_routing) {
        if (!routing) routing = EArrowRouting::Polygon;
        if (!route_arrow_distance) route_arrow_distance = 9;
        if (!route_block_others) route_block_others = true;
        if (!route_linearextend) route_linearextend = true;
        if (!routing_try_harder) routing_try_harder = false;
        if (!routing_allow_joint_layout) routing_allow_joint_layout = true;
        //Do not set route_clip_box as there is a different default for block & coordinate based ends
        //Do not set distance as its default will differ for Manhattan routed arrows.
        //Do not set distance and the block specific ones: distance_per_block, cross & cross_parent
        if (!route_factor) route_factor = 0;
    }
    if (f_marker) marker.MakeComplete();
	//keep make_time empty
}

EAlignPrio BlockStyle::PrioOnAttributeAdd() const
{
    switch (type) {
    case EStyleType::STYLE:
        switch (block_style_type) {
        default:
            _ASSERT(0); FALLTHROUGH;
        case EBlockStyleType::Generic:
            return EAlignPrio::Explicit;
        case EBlockStyleType::Content:
            return EAlignPrio::Content;
        case EBlockStyleType::Running:
            return EAlignPrio::Running_style;
        }
    case EStyleType::DEFAULT:
    case EStyleType::DEF_ADD:
        return EAlignPrio::Default_style;
    case EStyleType::ELEMENT:
        return EAlignPrio::Explicit;
    case EStyleType::OPTION:
    default:
        _ASSERT(0);
        return EAlignPrio::Default;
    }
}

Style &BlockStyle::operator += (const Style &toadd)
{
    const BlockStyle* p = dynamic_cast<const BlockStyle *>(&toadd);
    if (p) {
        if (f_label && p->f_label) {
            if (p->label) label = p->label;
            if (p->label_pos) label_pos = p->label_pos;
            if (p->label_align) label_align = p->label_align;
            if (p->label_orient)                    
                //For block styles we do not allow changing this to 'tangent' orientation
                if (block_style_target != EBlockStyleTarget::Block 
                    || !IsTangent(*p->label_orient)) label_orient = p->label_orient;
        }
        if (f_alignment && p->f_alignment) {
            alignment.MergeByDimension(p->alignment, *this);
            if (p->XChildAlign) XChildAlign = p->XChildAlign;
            if (p->YChildAlign) YChildAlign = p->YChildAlign;
        }
        if (f_margin && p->f_margin) {
            if (p->content_margin) content_margin = p->content_margin;
            if (p->min_margin[0]) min_margin[0] = p->min_margin[0];
            if (p->min_margin[1]) min_margin[1] = p->min_margin[1];
            if (p->max_margin[0]) max_margin[0] = p->max_margin[0];
            if (p->max_margin[1]) max_margin[1] = p->max_margin[1];
            if (p->min_imargin[0]) min_imargin[0] = p->min_imargin[0];
            if (p->min_imargin[1]) min_imargin[1] = p->min_imargin[1];
            if (p->max_imargin[0]) max_imargin[0] = p->max_imargin[0];
            if (p->max_imargin[1]) max_imargin[1] = p->max_imargin[1];
            if (p->label_imargin) label_imargin = p->label_imargin;
            if (p->size[0]) size[0] = p->size[0];
            if (p->size[1]) size[1] = p->size[1];
            if (p->aspect_ratio) aspect_ratio = p->aspect_ratio;
            if (p->label_mode) label_mode = p->label_mode;
            if (p->multi_offset[0]) multi_offset[0] = p->multi_offset[0];
            if (p->multi_offset[1]) multi_offset[1] = p->multi_offset[1];
        }
        if (f_content && p->f_content) {
            if (p->allow_arrows) allow_arrows = p->allow_arrows;
            if (p->indicator) indicator = p->indicator;
        }
        if (f_routing && p->f_routing) {
            if (p->routing) routing = p->routing;
            if (p->route_arrow_distance) route_arrow_distance = p->route_arrow_distance;
            if (p->route_block_others) route_block_others = p->route_block_others;
            if (p->route_linearextend) route_linearextend = p->route_linearextend;
            if (p->route_clip_block) route_clip_block = p->route_clip_block;
            if (p->routing_try_harder) routing_try_harder = p->routing_try_harder;
            if (p->routing_allow_joint_layout) routing_allow_joint_layout = p->routing_allow_joint_layout;
            if (p->route_cross) {
                //if an empty array is added to us, we get cleared,
                //else the elements are appended after our values
                if (p->route_cross->size() && route_cross)
                    route_cross->insert(route_cross->end(),
                                        p->route_cross->begin(), p->route_cross->end());
                else
                    route_cross = p->route_cross;
            }
            if (p->route_cross_parent) {
                //if an empty array is added to us, we get cleared,
                //else the elements are appended after our values
                if (p->route_cross_parent->size() && route_cross_parent)
                    route_cross_parent->insert(route_cross_parent->end(),
                                               p->route_cross_parent->begin(), p->route_cross_parent->end());
                else
                    route_cross_parent = p->route_cross_parent;
            }
            if (p->distance) distance = p->distance;
            if (p->distance_per_block) {
                //if an empty array is added to us, we get cleared,
                //else the elements are appended after our values
                if (p->distance_per_block->size() && distance_per_block)
                    distance_per_block->insert(distance_per_block->end(),
                                               p->distance_per_block->begin(), p->distance_per_block->end());
                else
                    distance_per_block = p->distance_per_block;
            }
            if (p->route_factor) route_factor = p->route_factor;
        }
        if (f_marker && p->f_margin) marker += p->marker;
		if (p->draw_time.size()) {
			draw_time = p->draw_time;
			draw_time_is_before = p->draw_time_is_before;
		}
    }
    return SimpleStyleWithArrow::operator+=(toadd);
}

namespace block
{
bool CshHintGraphicCallbackForContent(Canvas *canvas, EInternalAlignType p, bool y,
                                      double r, bool circle, ColorType color)
{
    if (!canvas) return false;
    const double g = 2;
    Block b(g, HINT_GRAPHIC_SIZE_X-g, g, HINT_GRAPHIC_SIZE_Y-g);
    if (p==EInternalAlignType::Exact) {
        if (y) b.y.Expand(-HINT_GRAPHIC_SIZE_Y*0.2);
        else b.x.Expand(-HINT_GRAPHIC_SIZE_Y*0.2);
    }
    LineAttr line(ELineType::SOLID, ColorType::black(), 1, ECornerType::NONE, 0);
    canvas->Line(b, line);
    std::array<double, 3> xy;
    switch (p) {
    default: _ASSERT(0); FALLTHROUGH;
    case EInternalAlignType::Min: xy = {0, 0.25, 0.5}; break;
    case EInternalAlignType::Max: xy = {1, 0.75, 0.5}; break;
    case EInternalAlignType::Exact:
    case EInternalAlignType::Mid: xy = {0.25, 0.75, 0.5}; break;
    case EInternalAlignType::Spread: xy = {0, 1, 0.5}; break;
    }
    FillAttr fill = FillAttr::Solid(color);
    for (double d : xy) {
        const XY center = y ?
            XY(HINT_GRAPHIC_SIZE_X*r, d*(HINT_GRAPHIC_SIZE_Y-4*g)+2*g) :
            XY(d*(HINT_GRAPHIC_SIZE_X-4*g)+2*g, HINT_GRAPHIC_SIZE_Y*r);

        canvas->Fill(circle ? Contour(center, 2, 2-y) : Contour(-1, +1, (-2+y)/2., (2-y)/2.).Shift(center), fill);
    }
    return true;
}
} //namespace

/** Callback for drawing a symbol before content.x attribute values in hint boxes.
* @ingroup hintpopup_callbacks*/
bool block::CshHintGraphicCallbackForContentX(Canvas *canvas, CshHintGraphicParam p, CshHintStore&)
{ return CshHintGraphicCallbackForContent(canvas, EInternalAlignType(p), false, 0.5, true, ColorType::red()); }

/** Callback for drawing a symbol before content.y attribute values in hint boxes.
* @ingroup hintpopup_callbacks*/
bool block::CshHintGraphicCallbackForContentY(Canvas *canvas, CshHintGraphicParam p, CshHintStore&)
{ return CshHintGraphicCallbackForContent(canvas, EInternalAlignType(p), true, 0.5, true, ColorType::red()); }


/** Possible values for arrow routing.*/
template<> const char EnumEncapsulator<EArrowRouting>::names[][ENUM_STRING_LEN] =
    {"invalid", "horizontal", "vertical", "grid", "straight", "polygon", "curvy", "manhattan", ""};

bool block::CshHintGraphicCallbackForRouting(Canvas *canvas, CshHintGraphicParam p, CshHintStore&)
{
    if (!canvas) return false;
    LineAttr line;
    std::vector<Path> path(1);
    switch (EArrowRouting(p)) {
    default:
        _ASSERT(0); FALLTHROUGH;
    case block::EArrowRouting::Invalid:
        break;
    case block::EArrowRouting::Horizontal:
        path.front().append(Edge(XY(0, 0.5), XY(1, 0.5)));
        break;
    case block::EArrowRouting::Vertical:
        path.front().append(Edge(XY(0.5, 0), XY(0.5, 1)));
        break;
    case block::EArrowRouting::Grid:
        line.color = ColorType::blue();
        path.front().append(Edge(XY(0, 0), XY(1, 0)));
        path.emplace_back().append(Edge(XY(0.5, 0.2), XY(0.5, 1)));
        break;
    case block::EArrowRouting::Straight:
        path.front().append(Edge(XY(0, 0), XY(1, 1)));
        break;
    case block::EArrowRouting::Polygon:
        path.front().append(Edge(XY(0, 0), XY(0.75, 0.25)));
        path.front().append(XY(1, 1));
        break;
    case block::EArrowRouting::Curvy:
        path.front().append(Edge(XY(0, 0), XY(1, 1), XY(0.35, 0.05), XY(0.95, 0.65)));
        break;
    case block::EArrowRouting::Manhattan:
        path.front().append(Edge(XY(0, 0), XY(1, 0)));
        path.front().append(XY(1, 1));
        break;
    }
    const double g = 2;
    for (auto& p : path) {
        const SingleArrowHead ah(EArrowType::LINE, false, XY(0.2, 0.2), line);
        p.Scale(XY(HINT_GRAPHIC_SIZE_X - 2 * g, HINT_GRAPHIC_SIZE_Y - 2 * g));
        p.Shift(XY(g, g));
        canvas->ClipInverse(ah.ClipLine(p, p.GetEndPos(), true, false, true, line, line));
        canvas->Line(p, line);
        canvas->UnClip();
        ah.Draw(*canvas, p, p.GetEndPos(), true, false, line, line);
        line.color = ColorType::green();
    }
    return true;
}

/** Possible values for direction.*/
template<> const char EnumEncapsulator<EDirection>::names[][ENUM_STRING_LEN] =
    {"invalid", "above", "below", "left", "right", ""};

/** Callback for drawing a symbol before label.pos attribute values in hint boxes.
* @ingroup hintpopup_callbacks*/
bool block::CshHintGraphicCallbackForLabelPos(Canvas *canvas, CshHintGraphicParam p, CshHintStore&)
{
    if (!canvas) return false;
    const ColorType color = ColorType::green().Darker(0.4);
    switch (EDirection(p)) {
    default:
        _ASSERT(0); FALLTHROUGH;
    case block::EDirection::Invalid:
        CshHintGraphicCallbackForContent(canvas, EInternalAlignType::Mid, false, 0.5, false, color);
        break;
    case block::EDirection::Above:
        CshHintGraphicCallbackForContent(canvas, EInternalAlignType::Mid, false, 0.25, false, color);
        break;
    case block::EDirection::Below:
        CshHintGraphicCallbackForContent(canvas, EInternalAlignType::Mid, false, 0.75, false, color);
        break;
    case block::EDirection::Left:
        CshHintGraphicCallbackForContent(canvas, EInternalAlignType::Min, false, 0.5, false, color);
        break;
    case block::EDirection::Right:
        CshHintGraphicCallbackForContent(canvas, EInternalAlignType::Max, false, 0.5, false, color);
        break;
    }
    return true;
}

/** Callback for drawing a symbol before label.align attribute values in hint boxes.
* @ingroup hintpopup_callbacks*/
bool block::CshHintGraphicCallbackForLabelAlign(Canvas *canvas, CshHintGraphicParam p, CshHintStore&)
{
    if (!canvas) return false;
    struct d
    {
        bool y;
        EInternalAlignType ia;
        d(bool b, EInternalAlignType a) : y(b), ia(a) {}
    };
    const std::array<d, 6> data = {{
        {/*top:*/    true,  EInternalAlignType::Min},
        {/*left:*/   false, EInternalAlignType::Min},
        {/*bottom:*/ true,  EInternalAlignType::Max},
        {/*right:*/  false, EInternalAlignType::Max},
        {/*middle:*/ true,  EInternalAlignType::Mid},
        {/*center:*/ false, EInternalAlignType::Mid}
    }};
    const int i = std::max(std::min(6, int(p)), 1)-1;
    CshHintGraphicCallbackForContent(canvas, data[i].ia, data[i].y, 0.25, false, ColorType::red());
    Block b(-HINT_GRAPHIC_SIZE_X, HINT_GRAPHIC_SIZE_X, -HINT_GRAPHIC_SIZE_Y, HINT_GRAPHIC_SIZE_Y);
    canvas->Fill(b.Scale(data[i].y ? XY(0.18, 0.3) : XY(0.3, 0.18)).
                 Shift({HINT_GRAPHIC_SIZE_X*(data[i].y ? 0.65 : 0.5), HINT_GRAPHIC_SIZE_Y*(data[i].y ? 0.5 : 0.65)}),
                 FillAttr::Solid(ColorType::lgray()));
    return true;
}


template<> const char EnumEncapsulator<EOrientation>::names[][ENUM_STRING_LEN] =
    { "invalid", "normal", "upside_down", "left", "right", "tangent", "tangent_upside_down", "" };

template<> const char* const EnumEncapsulator<EOrientation>::descriptions[] = { ""
    "Regular orientation, text is left-to-right.",
    "The text is upside-down and go from right-to-left.",
    "The text can be viewed from left, text goes down-to-up.",
    "The text can be viewed from right, text goes up-to-down.",
    "The text goes along the tangent of the arrow.",
    "The text goes along the tangent of the arrow, upside down.",
    ""
};

/** Callback for drawing a symbol before label.orient attribute values in hint boxes.
* @ingroup hintpopup_callbacks*/
bool block::CshHintGraphicCallbackForLabelOrient(Canvas *canvas, CshHintGraphicParam p, CshHintStore &hs)
{
    if (!canvas || unsigned(p)==0 || unsigned(p)>4) return false;
    const EOrientation dir = EOrientation(p);
    StringFormat sf;
    sf.Default();
    Label label("\\-Text", *canvas, *hs.pShapes, sf);
    switch (dir) {
    default:
    case EOrientation::Normal:
        label.Draw(*canvas, *hs.pShapes, 0, HINT_GRAPHIC_SIZE_X, 0);
        break;
    case EOrientation::UpsideDown:
        canvas->Transform_Rotate(XY(HINT_GRAPHIC_SIZE_X / 2, HINT_GRAPHIC_SIZE_Y / 2), M_PI);
        label.Draw(*canvas, *hs.pShapes, 0, HINT_GRAPHIC_SIZE_X, 1);
        canvas->UnTransform();
        break;
    case EOrientation::Left:
        label.Draw(*canvas, *hs.pShapes, 0, HINT_GRAPHIC_SIZE_Y, 0, ESide::RIGHT);
        break;
    case EOrientation::Right:
        label.Draw(*canvas, *hs.pShapes, 0, HINT_GRAPHIC_SIZE_Y, HINT_GRAPHIC_SIZE_X - 1, ESide::LEFT);
        break;
    case EOrientation::Tangent: {
        canvas->Transform_Rotate(XY(HINT_GRAPHIC_SIZE_X / 2, HINT_GRAPHIC_SIZE_Y / 2), M_PI / 12);
        label.Draw(*canvas, *hs.pShapes, 0, HINT_GRAPHIC_SIZE_X, 1);
        const double h = label.getTextWidthHeight().y;
        LineAttr line(ELineType::SOLID, ColorType::black(), 1, ECornerType::NONE, 0);
        canvas->Line(XY(0, 1 + h), XY(HINT_GRAPHIC_SIZE_X, 1 + h), line);
        canvas->UnTransform();
        break;
    }
    case EOrientation::TangentUpsideDown: {
        canvas->Transform_Rotate(XY(HINT_GRAPHIC_SIZE_X / 2, HINT_GRAPHIC_SIZE_Y / 2), M_PI + M_PI / 12);
        label.Draw(*canvas, *hs.pShapes, 0, HINT_GRAPHIC_SIZE_X, 1);
        const double h = label.getTextWidthHeight().y;
        LineAttr line(ELineType::SOLID, ColorType::black(), 1, ECornerType::NONE, 0);
        canvas->Line(XY(0, 1 + h), XY(HINT_GRAPHIC_SIZE_X, 1 + h), line);
        canvas->UnTransform();
        break;
    }
    }
    return true;
}

template<>
const char EnumEncapsulator<ELabelMode>::names[][ENUM_STRING_LEN] = {"", "enlarge", "scale", "scale_2d", ""};
template<>
const char * const EnumEncapsulator<ELabelMode>::descriptions[] = {"",
"If the label does not fit into the proscribed size of a label-only block, make the block larger.",
"If the label does not fit into the proscribed size of a label-only block, scale down the text, while keeping its aspect ratio.",
"If the label does not fit into the proscribed size of a label-only block, scale down the text, potentially differently in the two dimensions.", ""};

/** Add an attribute to a style. Provide errors if needed and return true
 * if the attribute name was recognized as one that applies to us.
 * Any alignment attribute will be set using the following priority:
 * - Default_style for DEFAULT or DEF_ADD styles. This is fully correct and covered.
 * - Explicit for STYLE or ELEMENT. This does not cover the case, when the style we
 *   assign to is a running or content style. Thus, when styles are applied, we have to
 *   say in what role the style is applied (since 'col' and 'row' can be applied both */
bool BlockStyle::AddAttribute(const Attribute & a, Chart *chart)
{
    if (f_label) {
        if (a.Is("label")) {
            if (a.type==EAttrType::CLEAR)
                label.reset();
            else
                label = {a.value, a.linenum_value.start};
            return true;
        }
        if (a.Is("label.pos")) {
            if (a.type == EAttrType::CLEAR) {
                if (a.EnsureNotClear(chart->Error, type))
                    label_pos.reset();
                return true;
            }
            if (CaseInsensitiveEqual(a.value, "center") ||
                CaseInsensitiveEqual(a.value, "middle")) {
                label_pos = {EDirection::Invalid, a.linenum_value.start}; //Invalid used as a replacement for 'center'
                return true;
            }
            if (EDirection d;  a.type == EAttrType::STRING && Convert(a.value, d)) {
                label_pos = {d, a.linenum_value.start};
                return true;
            } else
                a.InvalidValueError(CandidatesFor<EDirection>(), chart->Error);
            return true;
        }
        if (a.Is("label.align")) {
            static const char names[][ENUM_STRING_LEN] =
            {"left", "top", "middle", "center", "bottom", "right", ""};
            static const double attrvalue[] = {0.0, 0.0, 0.5, 0.5, 1.0, 1.0};
            if (a.type == EAttrType::CLEAR) {
                if (a.EnsureNotClear(chart->Error, type))
                    label_align.reset();
                return true;
            }
            if (a.type == EAttrType::NUMBER) {
                if (a.number<=-100 || a.number>=200)
                    chart->Error.Error(a, true, "This shall be a percentage of the block size with wrap-around. Ignoring attribute.",
                                       "Use one of 'left', 'top', 'center', 'middle', 'bottom', 'right' or a number between (-100..+200).");
                else
                    label_align = a.number==100 ? 1.0 : fmod(a.number / 100 + 1, 1.0);
                return true;
            }
            for (unsigned u = 0; names[u][0]; u++)
                if (CaseInsensitiveEqual(a.value, names[u])) {
                    label_align = attrvalue[u];
                    return true;
                }
            chart->Error.Error(a, true, "Unrecognized value. Ignoring attribute.",
                               "Use one of 'left', 'top', 'center', 'middle', 'bottom', 'right' or a number between (-100..+200).");
            return true;
        }
        if (a.Is("label.orient")) {
            if (a.type == EAttrType::CLEAR) {
                if (a.EnsureNotClear(chart->Error, type))
                    label_orient.reset();
                return true;
            }
            if (EOrientation o; a.type == EAttrType::STRING && Convert(a.value, o)) {
                if (block_style_target == EBlockStyleTarget::Block && IsTangent(o))
                    chart->Error.Error(a, true, "Labels cannot be oriented 'tangent' for a block. Ignoring attribute.",
                                       "Use one of 'normal', 'upside_down', 'left' or 'right'.");
                else
                    label_orient = o;
                return true;
            }
            a.InvalidValueError(CandidatesFor<EOrientation>(), chart->Error);
            return true;
        }
    }
    if (f_alignment) {
        if (alignment.AddAttribute(a, type, chart, PrioOnAttributeAdd()))
            return true;
        }
    if (f_content) {
        static const char hnames[][ENUM_STRING_LEN] =
            {"left", "center", "middle", "justify", "spread", "exact", "right", ""};
        static const char vnames[][ENUM_STRING_LEN] =
            {"top", "center", "middle", "justify", "spread", "exact", "bottom", ""};
        static const EInternalAlignType attrvalue[] = {
            EInternalAlignType::Min, EInternalAlignType::Mid, EInternalAlignType::Mid,
            EInternalAlignType::Spread, EInternalAlignType::Spread, EInternalAlignType::Exact,
            EInternalAlignType::Max};
        if (a.Is("content.x")) {
            if (a.type == EAttrType::CLEAR) {
                if (a.EnsureNotClear(chart->Error, type))
                    XChildAlign.reset();
                return true;
            }
            for (unsigned u = 0; hnames[u][0]; u++)
                if (CaseInsensitiveEqual(a.value, hnames[u])) {
                    XChildAlign = {{attrvalue[u], PrioOnAttributeAdd(), a.linenum_attr.start}};
                    return true;
                }
            chart->Error.Error(a, true, "Unrecognized value. Ignoring attribute.",
                "Use one of 'left', 'center', 'justify', 'exact', 'right'.");
            return true;
        }
        if (a.Is("content.y")) {
            if (a.type == EAttrType::CLEAR) {
                if (a.EnsureNotClear(chart->Error, type))
                    YChildAlign.reset();
                return true;
            }
            for (unsigned u = 0; vnames[u][0]; u++)
                if (CaseInsensitiveEqual(a.value, vnames[u])) {
                    YChildAlign = {{attrvalue[u], PrioOnAttributeAdd(), a.linenum_attr.start}};
                    return true;
                }
            chart->Error.Error(a, true, "Unrecognized value. Ignoring attribute.",
                "Use one of 'top', 'center', 'justify', 'exact', 'bottom'.");
            return true;
        }
        if (a.Is("allow_arrows")) {
            if (a.type == EAttrType::CLEAR) {
                if (a.EnsureNotClear(chart->Error, type))
                    allow_arrows.reset();
                return true;
            }
            if (a.CheckType(EAttrType::BOOL, chart->Error))
                allow_arrows = a.yes;
            return true;
        }
        if (a.Is("indicator")) {
            if (!a.CheckType(EAttrType::BOOL, chart->Error)) return true;
            indicator = a.yes;
            return true;
        }
        if (a.Is("content_margin")) {
            if (a.type == EAttrType::CLEAR) {
                if (a.EnsureNotClear(chart->Error, type))
                    content_margin.reset();
                return true;
            }
            if (a.type == EAttrType::BOOL) {
                content_margin = a.yes;
                return true;
            } else if (f_margin) {
                //user may set a number or four comma separated numbers, too.
                //that means 'false' here and apply the numbers as 'imargin'
                content_margin = false;
                //fallthrough to if (f_margin) below
            } else {
                //But those are handled in the margin section below.
                _ASSERT(0);
                chart->Error.Error(a, true, "You can only set 'content_margin' to 'yes' or 'no' here. Ignoring it.");
                return true;
            }
            //fallthrough to if (f_margin) below
        }
        //fallthrough to if (f_margin) below
    }
    if (f_margin) {
        if (a.Is("multi_offset.x") || a.Is("multi_offset.y") || a.Is("multi_offset")) {
            const bool x = a.Is("multi_offset.x") || a.Is("multi_offset");
            const bool y = a.Is("multi_offset.y") || a.Is("multi_offset");
            if (a.type == EAttrType::CLEAR) {
                if (a.EnsureNotClear(chart->Error, type)) {
                    if (x) multi_offset[0].reset();
                    if (y) multi_offset[1].reset();
                }
                return true;
            }
            if (a.type==EAttrType::NUMBER) {
                if (a.number<-1000 || a.number>1000)
                    a.InvalidValueError("[-1000..1000]", chart->Error);
                else {
                    if (x) multi_offset[0] =  a.number;
                    if (y) multi_offset[1] = -a.number;
                }
            } else
                a.InvalidValueError("[-1000..1000]", chart->Error);
            return true;
        }
        const std::string value = StringFormat::RemovePosEscapesCopy(a.value);
        OptAttr<double> *attr = nullptr;
        OptSizeAttr* size_attr = nullptr;
        bool is_size = false;
        bool canbe4 = false;
        if (a.Is("margin")) {
            attr = min_margin;
            canbe4 = true;
        } else if (a.Is("imargin") || (f_content && a.Is("content_margin"))) {
            attr = min_imargin;
            canbe4 = true;
        } else if (a.StartsWith("margin")) {
            if (a.EndsWith("top")) attr = min_margin+1;
            else if (a.EndsWith("bottom")) attr = max_margin+1;
            else if (a.EndsWith("left")) attr = min_margin+0;
            else if (a.EndsWith("right")) attr = max_margin+0;
        } else if (a.StartsWith("imargin")) {
            if (a.EndsWith("top")) attr = min_imargin+1;
            else if (a.EndsWith("bottom")) attr = max_imargin+1;
            else if (a.EndsWith("left")) attr = min_imargin+0;
            else if (a.EndsWith("right")) attr = max_imargin+0;
            else if (a.EndsWith("label")) attr = &label_imargin;
        } else if (a.Is("width")) {
            size_attr = size+0;
        } else if (a.Is("height")) {
            size_attr = size+1;
        } else if (a.Is("size")) {
            size_attr = size+0; //width
            is_size = true;     //indicate we have to do height, as well
        }
        _ASSERT(!size_attr || !attr); //at most one of them set.
        if (attr || size_attr) {
            if (a.type == EAttrType::CLEAR) {
                if (a.EnsureNotClear(chart->Error, type)) {
                    if (attr) attr->reset();
                    if (size_attr) size_attr->reset();
                    if (is_size) size[1].reset();
                }
                return true;
            }
            if (is_size && value.find(',')!=std::string::npos) {
                //this may be dual coordinates. Call ourselves with with and height
                auto p = StringFormat::Split(value, a.linenum_value.start);
                _ASSERT(p.size()>=2); //should not begin with ','
                if (p.size()>2) {
                    chart->Error.Error(p[2].file_pos.start,
                                       "I can only take 2 numbers for size. Ignoring attribute.");
                    return true;
                }
                if (p[0].name.length())
                    AddAttribute(Attribute("width", p[0].name, a.linenum_attr, p[0].file_pos), chart);
                if (p[1].name.length())
                    AddAttribute(Attribute("height", p[1].name, a.linenum_attr, p[1].file_pos), chart);
                return true;
            }
            if (size_attr) { //width or height or size
                //check validity (different error message)
                if (a.type == EAttrType::NUMBER && a.number<0) {
                    chart->Error.Error(a, true, "The '"+a.name+"' attribute must be a block name or a nonnegative number. Ignoring it.");
                    return true;
                }
                //if "size" from,to will be (0,1), if "height" (1,1), if "width" then (0,0)
                const unsigned from = (size_attr==size+0 || is_size) ? 0 : 1;
                const unsigned to   = (size_attr==size+1 || is_size) ? 1 : 0;

                for (unsigned xy = from; xy<=to; xy++) {
                    OptSizeAttr *sattr = size+xy;
                    auto &childalign = xy==0 ? XChildAlign : YChildAlign;
                    const EAlignPrio prio = PrioOnAttributeAdd();
                    //Kill internal "exact" alignment, if coming from a lower prio source
                    if (childalign && childalign->align==EInternalAlignType::Exact
                        && BasePriority(childalign->prio)<=PrioOnAttributeAdd())
                        childalign.reset();
                    if (a.type==EAttrType::NUMBER) {
                        *sattr = {a.number, prio};
                    } else {
                        //search for multiplier
                        auto at = value.find_first_of('@');
                        if (at!=std::string::npos) {
                            if (double d; from_chars(std::string_view(value).substr(at+1), d) ||
                                d<1 || d>10000) {
                                if (xy==from)
                                    chart->Error.Error(a, true,
                                                       "Multiplier percentage must be between [1..10000]. Defaulting to 100%.");
                                *sattr = {1, prio};
                            } else
                                *sattr = {d/100, prio};
                        } else
                            *sattr = {1, prio}; //use multipier of '1'
                        //value may be one block or a list of blocks prefixed by a location escape and separated by + signs
                        sattr->value().blocks = StringFormat::Split(value.substr(0, at), a.linenum_value.start, "+");
                    }
                }
                return true;
            } else if (canbe4 && value.find(',')!=std::string::npos) {
                double top, bottom, left, right;
                if (4!=sscanf(value.c_str(), "%lf,%lf,%lf,%lf", &top, &bottom, &left, &right)) {
                    chart->Error.Error(a, true, "Expecting either one nonnegative number or four ones, separated by commas. Ignoring attribute.");
                } else if (attr==min_margin) {
                    min_margin[0] = top;
                    min_margin[1] = left;
                    max_margin[0] = bottom;
                    max_margin[1] = right;
                } else if (attr==min_imargin) {
                    min_imargin[0] = top;
                    min_imargin[1] = left;
                    max_imargin[0] = bottom;
                    max_imargin[1] = right;
                } else {
                    _ASSERT(0);
                }
                return true;
            } else if (a.type != EAttrType::NUMBER || a.number<0 || floor(a.number)!=a.number) {
                chart->Error.Error(a, true, "The '"+a.name+"' attribute must be a nonnegative integer. Ignoring it.");
                return true;
            }
            if (canbe4 && attr == min_margin)
                min_margin[0] = min_margin[1] = max_margin[0] = max_margin[1] = a.number;
            else if (canbe4 && attr == min_imargin)
                min_imargin[0] = min_imargin[1] = max_imargin[0] = max_imargin[1] = label_imargin = a.number;
            else
                attr->emplace(a.number);
            return true;
        }
        if (a.Is("aspect_ratio")) {
            if (a.type == EAttrType::CLEAR) {
                if (a.EnsureNotClear(chart->Error, type))
                    aspect_ratio.reset();
            } else if (a.type == EAttrType::NUMBER) {
                if (a.number < 0.0001 || 10000 <= a.number)
                    chart->Error.Error(a, true, "The 'aspect_ratio' shall be between 0.0001 and 10000. Ignoring it.");
                else
                    aspect_ratio = a.number;
            } else if (double x, y; 2 != sscanf(a.value.c_str(), "%lf:%lf", &x, &y))
                chart->Error.Error(a, true, "The 'aspect_ratio' shall be two positive number or two numbers separated by a colon, e.g., \"3:2\".");
            else if (y < 0.0001 || x / y < 0.0001 || 10000 <= x / y)
                chart->Error.Error(a, true, "The 'aspect_ratio' shall be between 0.0001 and 10000. Ignoring it.");
            else
                aspect_ratio = x / y;
            return true;
        }
        if (a.Is("label.mode")) {
            if (a.type==EAttrType::CLEAR)
                label_mode.reset();
            else if (ELabelMode m;  Convert(a.value, m))
                label_mode = {m, a.linenum_attr.start};
            else
                a.InvalidValueError(CandidatesFor<ELabelMode>(), chart->Error);
            return true;
        }
    }
    if (f_routing) {
        if (a.Is("routing")) {
            if (a.type == EAttrType::CLEAR) {
                if (a.EnsureNotClear(chart->Error, type))
                    routing.reset();
                return true;
            }
            if (EArrowRouting r; a.type == EAttrType::STRING && Convert(a.value, r)) {
                routing = r;
                return true;
            } else
                a.InvalidValueError(CandidatesFor<EArrowRouting>(), chart->Error);
            return true;
        }
        if (a.Is("cross")) {
            route_cross = StringFormat::Split(a.value, a.linenum_value.start, "+");
            return true;
        }
        if (a.Is("cross_all")) {
            route_cross_parent = route_cross = StringFormat::Split(a.value, a.linenum_value.start, "+");
            return true;
        }
        if (a.Is("distance")) {
            if (a.value.length()==0) {
                distance_per_block.emplace();
                if (type!=EStyleType::ELEMENT)
                    distance.reset();
                return true;
            }
            if (a.type==EAttrType::NUMBER) {
                distance = a.number;
                return true;
            }
            AlignTo at;
            if (at.Parse(a.value, EdgePos(-1000, false, -1000), true, PrioOnAttributeAdd(),
                         &chart->Error, &a.linenum_value.start, "attribute")) {
                if (at.edge.pos==-1000 && at.edge.margin==false) {
                    chart->Error.Error(a, true, "Expecting an '@' symbol and a distance after the blocks. Ignoring attribute.");
                } else if (at.edge.margin) {
                    chart->Error.Error(a, true, "You cannot use the 'm' letter after the '@' for attribute 'distance'. Just provide a number after the '@'. Ignoring attribute.");
                } else if (at.edge.offset!=0) {
                    chart->Error.Error(a, true, "I just need one number for 'distance', not two. Ignoring attribute.");
                } else {
                    distance_per_block.emplace();
                    for (auto &sp : at.blocks)
                        distance_per_block->emplace_back(std::move(sp), at.edge.pos*100);
                }
            }
            return true;
        }
        if (a.Is("routing.factor")) {
            if (a.type==EAttrType::CLEAR) {
                route_factor.reset();
                return true;
            }
            if (a.type==EAttrType::NUMBER && a.number>=0 && a.number<=1) {
                distance = a.number;
                return true;
            }
            a.InvalidValueError("[0..1]", chart->Error);
            return true;
        }
        if (a.Is("routing.arrow_distance")) {
            if (a.type==EAttrType::CLEAR) {
                route_arrow_distance.reset();
                return true;
            }
            if (a.type==EAttrType::NUMBER && a.number>=0 && a.number<=100) {
                route_arrow_distance = a.number;
                return true;
            }
            a.InvalidValueError("[0..100]", chart->Error);
            return true;
        }
        if (a.Is("routing.block_others")) {
            if (a.type == EAttrType::CLEAR) {
                if (a.EnsureNotClear(chart->Error, type))
                    route_block_others.reset();
                return true;
            }
            if (a.CheckType(EAttrType::BOOL, chart->Error))
                route_block_others = a.yes;
            return true;
        }
        if (a.Is("routing.extend")) {
            if (a.type == EAttrType::CLEAR) {
                if (a.EnsureNotClear(chart->Error, type))
                    route_linearextend.reset();
                return true;
            }
            if (a.CheckType(EAttrType::BOOL, chart->Error))
                route_linearextend = a.yes;
            return true;
        }
        if (a.Is("routing.clip_block")) {
            if (a.type == EAttrType::CLEAR) {
                if (a.EnsureNotClear(chart->Error, type))
                    route_clip_block.reset();
                return true;
            }
            if (a.CheckType(EAttrType::BOOL, chart->Error))
                route_clip_block = a.yes;
            return true;
        }
        if (a.Is("routing.try_harder")) {
            if (a.type == EAttrType::CLEAR) {
                if (a.EnsureNotClear(chart->Error, type))
                    routing_try_harder.reset();
                return true;
            }
            if (a.CheckType(EAttrType::BOOL, chart->Error))
                routing_try_harder = a.yes;
            return true;
        }
        if (a.Is("routing.allow_joint_layout")) {
            if (a.type == EAttrType::CLEAR) {
                if (a.EnsureNotClear(chart->Error, type))
                    routing_allow_joint_layout.reset();
                return true;
            }
            if (a.CheckType(EAttrType::BOOL, chart->Error))
                routing_allow_joint_layout = a.yes;
            return true;
        }
    }
    if (f_marker) {
        if (a.StartsWith("marker"))
            return marker.AddAttribute(a, chart, type);
        if ((color_meaning==EColorMeaning::ARROW_TEXT ||
             color_meaning==EColorMeaning::LINE_ARROW_TEXT) &&
            a.Is("color"))
            marker.AddAttribute(a, chart, type);
    }
	//do not recognize shape.size in any case
	if (f_shape && a.Is("shape.size"))
		return false;
	if (a.Is("draw_before") || a.Is("draw_after")) {
		if (a.type == EAttrType::CLEAR || a.type == EAttrType::NUMBER) {
			chart->Error.Error(a, true, "You have to specify a block here. Ignoring attribute.");
		} else {
			//we are string or bool, but you may have a block named as 'yes'.
			//STYLE has been handled in 'style::AddAttribute' above
			draw_time = StringFormat::Split(a.value, a.linenum_value.start, "+");
			draw_time_is_before = a.Is("draw_before");
		}
		return true;
	}
	return SimpleStyleWithArrow::AddAttribute(a, chart);
}

void BlockStyle::AttributeNames(Csh &csh) const
{
    if (f_label) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "label",
                               "Specify the text of the label for a block.",
                               EHintType::ATTR_NAME));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "label.*",
                               "Determine the placement of block labels.",
                               EHintType::ATTR_NAME));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"label.pos",
                               "Determines, how to position the label of this block relative to its contained blocks. "
                               "If the block is empty, it has no effect.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"label.align",
                               "Determines, how to align the label on the side specified by 'label.pos' relative to its block. "
                               "If the block is empty, it has no effect.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"label.orient",
                               "Determines the orientation of the label.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"label.mode",
                               "Determines, how the 'size', 'width' and 'height' attributes are interpreted, when assigned a concrete number.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
    }
    if (f_alignment) {
        alignment.AttributeNames(csh, "Attributes governing how this block is aligned.");
    }
    if (f_content) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"content.x",
            "Determines, how the content is aligned horizontally within the block if the block is wider than the content.",
            EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"content.y",
            "Determines, how the content is aligned vertically within the block if the block is taller than the content.",
            EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"allow_arrows",
                               "If enabled, arrows can cross the area of this block and does not have to go around. "
                               "They still need to go around the content of this block unless 'allow_arrows' is set for those, too.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"content_margin",
                               "If enabled, this block will enclose its content including their margin. "
                               "If disabled, this block will be around them tighter - ignoring their margin. ",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"indicator",
                               "If enabled (default) collapsing this block will display a small indicator "
                               "instead of content. If disabled, nothing will be shown and the block uses the "
                               "default 'block' style (for empty blocks) and not 'container' or 'container_shape' "
                               "(for blocks with content).",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
    }
    if (f_margin) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"multi_offset",
                               "If you apply the 'multi' keyword before a block, this will be the "
                               "offset added for each copy. Assigning to this attribute will set "
                               "the horizontal and the vertical offset to this value and its inverse, resp.",
                               EHintType::ATTR_NAME, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"multi_offset.x",
                               "If you apply the 'multi' keyword before a block, this will be the horizontal "
                               "offset added for each copy.",
                               EHintType::ATTR_NAME, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"multi_offset.y",
                               "If you apply the 'multi' keyword before a block, this will be the vertical "
                               "offset added for each copy.",
                               EHintType::ATTR_NAME, true));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"margin",
                               "Sets all four margins around the block to the same value (or specify all four values).",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"margin.top",
                               "Determines, how much external margin the block has above it.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"margin.bottom",
                               "Determines, how much external margin the block has below it.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"margin.left",
                               "Determines, how much external margin the block has left of it.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"margin.right",
                               "Determines, how much external margin the block has right of it.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"imargin",
                               "Sets all four internal margins in the block and the distance between "
                               "the label and the content (if any) to the same value. "
                               "You can also specify four values for the 4 sides separated by comma.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"imargin.top",
                               "Determines, how much internal margin the block has at its top.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"imargin.bottom",
                               "Determines, how much internal margin the block has at its bottom.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"imargin.left",
                               "Determines, how much internal margin the block has at is left.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"imargin.right",
                               "Determines, how much internal margin the block has at its right.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "imargin.label",
                               "Determines, how much margin is there between the label of a block and its content.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"width",
                               "Sets block width in pixels. If you specify a number its interpretation is governed by 'label_mode'. "
                               "You can specify another block or a set of blocks separated by the '+' sign. If you do so, width will be equal to the (combined) width of the specified block(s).",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"height",
                               "Sets block height in pixels. If you specify a number its interpretation is governed by 'label_mode'. "
                               "You can specify another block or a set of blocks separated by the '+' sign. If you do so, height will be equal to the (combined) height of the specified block(s).",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"size",
                               "Sets block height and width in pixels. If you specify a number its interpretation is governed by 'label_mode'. "
                               "You can specify another block or a set of blocks separated by the '+' sign. If you do so, height and width will be equal to the (combined) height and width of the specified block(s), respectively.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "aspect_ratio",
                               "Enforces a certain X/Y ratio for the block. Cannot be used if both width and height is specified.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
    }
    if (f_routing) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"routing",
                               "Defines how the arrow shall be routed and its path laid out.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"cross",
                               "Specify one or more block(s) (separated by '+') this arrow can cross.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"cross_all",
                               "Specify one or more block(s) (separated by '+') this arrow can cross, including their children.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"distance",
                               "Specify a number in pixels to set how far to go around blocks. "
                               "Specify one or more block(s) (separated by '+') followed by '@' and a "
                               "number to set this distance for blocks individually.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"routing.factor",
                               "Governs how arrow layout trades off between the objective of minimizing the length of the arrow "
                               "and that of minimizing the number of turns it makes. "
                               "0 means minimize only length, 1 means minimize only turns, values between balance.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"routing.arrow_distance",
                               "If this arrow/line starts/ends at the same block as another and they overlap at their start/end, "
                               "this is how much we shift its start/end perpendicularly to prevent "
                               "overlap with the other arrow. If set to zero, no such prevention happens.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"routing.block_others",
                               "If this arrow/line starts/ends at the same block as another and they overlap at their start/end, "
                               "and routing.arrow_distance is not zero, we will shift the arrow start/endpoints to avoid overlap. "
                               "If this attribute is set to yes, this arrow will block other arrows (those cannot cross it) after "
                               "such shift. Control which arrow is laid out first using the 'routing.order' attribute.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"routing.extend",
                               "If this arrow/line starts/ends at the same block as another and they overlap at their start/end, "
                               "and routing.arrow_distance is not zero, we will shift the arrow start/endpoints to avoid overlap. "
                               "If this shift happens at an arrow starting/ending inside a block and after the shift its end gets outside "
                               "the block, setting this attribute will cause the arrow to be extended to reach the block (if possible via a "
                               "linear extension).",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"routing.clip_block",
                               "If this arrow/line starts/ends inside a block, it is clipped to point to the perimeter of the block. "
                               "This also applies to arrow endings specified using the same single block in both the X and Y "
                               "coordinates.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "routing.try_harder",
                               "If set, we do an exhaustive search when de-overlapping arrows for the arrow overlapping with this one. "
                               "Slower compilation, but usually nicer layout.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "routing.allow_joint_layout",
                               "If set (default) and this arrow would go the exact same path as some other, then at de-overlapping we "
                               "combine them and lay them out as one. This will force them to go on the same path side-by-side. "
                               "If you want them to go separate ways, set this to 'no'.",
                               EHintType::ATTR_NAME, true, CshHintGraphicCallbackForAttributeNames));
    }
    if (f_marker)
        OneArrowAttr::AttributeNames(csh, "marker.");
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"draw_before",
                           "You can specify one or more block name(s) here. This element will be drawn before (under) those blocks.",
                           EHintType::ATTR_NAME));
	csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"draw_after",
                           "You can specify one or more block name(s) here. This element will be drawn after (above) those blocks.",
                           EHintType::ATTR_NAME));
    SimpleStyleWithArrow::AttributeNames(csh);
    if (f_shape && csh.pShapes && *csh.pShapes) {
		//remove shape.size
		const std::string shape_size = csh.HintPrefix(COLOR_ATTRNAME) + "shape.size";
		auto i = std::find_if(csh.Hints.begin(), csh.Hints.end(),
							  [&shape_size](const CshHint&h)
							  {return h.decorated == shape_size;});
		if (i!=csh.Hints.end())
			csh.Hints.erase(i);
	}
}

bool BlockStyle::AttributeValues(std::string_view attr, Csh &csh) const
{
    if (f_label) {
        if (CaseInsensitiveEqual(attr, "label"))
            return true;
        if (CaseInsensitiveEqual(attr, "label.pos")) {
            csh.AddToHints(EnumEncapsulator<EDirection>::names, nullptr, csh.HintPrefix(COLOR_ATTRVALUE),
                           EHintType::ATTR_VALUE, CshHintGraphicCallbackForLabelPos);
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE)+"center",
                                   "Place the label to the middle of the block. Valid only for empty blocks.",
                                   EHintType::ATTR_NAME, true, CshHintGraphicCallbackForLabelPos, CshHintGraphicParam(EDirection::Invalid)));
            csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE)+"middle",
                                   "Place the label to the middle of the block. Valid only for empty blocks.",
                                   EHintType::ATTR_NAME, true, CshHintGraphicCallbackForLabelPos, CshHintGraphicParam(EDirection::Invalid)));
            return true;
        }
        if (CaseInsensitiveEqual(attr, "label.mode")) {
            csh.AddToHints(EnumEncapsulator<ELabelMode>::names, EnumEncapsulator<ELabelMode>::descriptions,
                           csh.HintPrefix(COLOR_ATTRVALUE), EHintType::ATTR_VALUE);
            return true;
        }
        if (CaseInsensitiveEqual(attr, "label.align")) {
            static const char * const attr_values[] = {"invalid", nullptr,
                "top", "Align the label towards the top/left side of the block.",
                "left", "Align the label towards the top/left side of the block.",
                "bottom", "Align the label towards the bottom/right side of the block.",
                "right", "Align the label towards the bottom/right side of the block.",
                "middle", "Align the label towards the center on the side specified by 'label.pos'.",
                "center", "Align the label towards the center on the side specified by 'label.pos'.",
                ""};
            csh.AddToHints(attr_values, csh.HintPrefix(COLOR_ATTRNAME), EHintType::ATTR_VALUE,
                           CshHintGraphicCallbackForLabelAlign, true);
            return true;
        }
        if (CaseInsensitiveEqual(attr, "label.orient")) {
            csh.AddToHints(EnumEncapsulator<EOrientation>::names, EnumEncapsulator<EOrientation>::descriptions,
                           csh.HintPrefix(COLOR_ATTRNAME), EHintType::ATTR_VALUE,
                           CshHintGraphicCallbackForLabelOrient, true);
            //Do not show 'tangent' for blocks
            if (block_style_target == EBlockStyleTarget::Block) 
                csh.Hints.pop_back(), csh.Hints.pop_back();
            return true;
        }
    }
    if (f_alignment) {
        if (alignment.AttributeValues(attr, csh)) return true;
    }
    if (f_content) {
        if (CaseInsensitiveEqual(attr, "content.x")) {
            static const char * const h_attr_values[] = {"invalid", nullptr,
                "left", "Align the content to the left side of the block.",
                "center", "Align the content to the centerline of the block.",
                "justify", "Spread the content evenly to fill the box horizontally.",
                "exact", "Size the block width exactly to its content.",
                "right", "Align the content to the right side of the block.",
                ""};
            csh.AddToHints(h_attr_values, csh.HintPrefix(COLOR_ATTRNAME), EHintType::ATTR_VALUE,
                           CshHintGraphicCallbackForContentX, true);
            return true;
        }
        if (CaseInsensitiveEqual(attr, "content.y")) {
            static const char * const v_attr_values[] = {"invalid", nullptr,
                "top", "Align the content to the top side of the block.",
                "middle", "Align the content to the midline of the block.",
                "justify", "Spread the content evenly to fill the box vertically.",
                "exact", "Size the block height exactly to its content.",
                "bottom", "Align the content to the bottom side of the block.",
                ""};
            csh.AddToHints(v_attr_values, csh.HintPrefix(COLOR_ATTRNAME), EHintType::ATTR_VALUE,
                           CshHintGraphicCallbackForContentY, true);
            return true;
        }
        if (CaseInsensitiveEqual(attr, "allow_arrows") ||
            CaseInsensitiveEqual(attr, "content_margin") ||
            CaseInsensitiveEqual(attr, "indicator"))
            csh.AddYesNoToHints();
    }
    if (f_margin) {
        if (CaseInsensitiveEqual(attr, "multi_offset.x") ||
            CaseInsensitiveEqual(attr, "multi_offset.y") ||
            CaseInsensitiveEqual(attr, "multi_offset"))
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<number>",
                                   "The value of the offset in pixels. Can be negative.",
                                   EHintType::ATTR_VALUE, false));
        else if (CaseInsensitiveEqual(attr, "margin") ||
                 CaseInsensitiveEqual(attr, "imargin")) {
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<number>",
                                   "The value of the margin in pixels.",
                                   EHintType::ATTR_VALUE, false));
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<left>,<right>,<top>,<bottom>",
                                   "The value of each margin in pixels.",
                                   EHintType::ATTR_VALUE, false));
        } else if (CaseInsensitiveEqual(attr, "margin.top") ||
            CaseInsensitiveEqual(attr, "margin.bottom") ||
            CaseInsensitiveEqual(attr, "margin.left") ||
            CaseInsensitiveEqual(attr, "margin.right") ||
            CaseInsensitiveEqual(attr, "imargin.top") ||
            CaseInsensitiveEqual(attr, "imargin.bottom") ||
            CaseInsensitiveEqual(attr, "imargin.left") ||
            CaseInsensitiveEqual(attr, "imargin.right"))
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<number>",
                                   "The value of the margin in pixels.",
                                   EHintType::ATTR_VALUE, false));
        else if (CaseInsensitiveEqual(attr, "imargin.label"))
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<number>",
                                   "The space between the label of a block and its content in pixels.",
                                   EHintType::ATTR_VALUE, false));
        else if (CaseInsensitiveEqual(attr, "width")) {
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<number>",
                                   "The value of the block width in pixels.",
                                   EHintType::ATTR_VALUE, false));
            csh.addEntityNamesAtEnd = "Have the same width as block %s.";
        } else if (CaseInsensitiveEqual(attr, "height")) {
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<number>",
                                   "The value of the block height in pixels.",
                                   EHintType::ATTR_VALUE, false));
            csh.addEntityNamesAtEnd = "Have the same height as block %s.";
        } else if (CaseInsensitiveEqual(attr, "size")) {
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<number>",
                                   "The value of the block height and width in pixels.",
                                   EHintType::ATTR_VALUE, false));
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<number>,<number>",
                                   "The value of the block height and width in pixels.",
                                   EHintType::ATTR_VALUE, false));
            csh.addEntityNamesAtEnd = "Have the same width and height as block %s.";
        } else if (CaseInsensitiveEqual(attr, "aspect_ratio")) {
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<number>",
                                   "Width divided by height.",
                                   EHintType::ATTR_VALUE, false));
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<number>:<number>",
                                   "Width divided by height.",
                                   EHintType::ATTR_VALUE, false));
        }
    }
    if (f_routing) {
        if (CaseInsensitiveEqual(attr, "routing")) {
            csh.AddToHints(EnumEncapsulator<EArrowRouting>::names, nullptr, csh.HintPrefix(COLOR_ATTRVALUE),
                           EHintType::ATTR_VALUE, CshHintGraphicCallbackForRouting);
            return true;
        }
        if (CaseInsensitiveEqual(attr, "distance")) {
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<number in pixels>",
                                   "Specify how much distance to be kept when arrows go around blocks.",
                                   EHintType::ATTR_VALUE, false));
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<block>@<number in pixels>",
                                   "Specify how much distance to be kept from a specific block when arrows go around blocks.",
                                   EHintType::ATTR_VALUE, false));
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<block>+<block>+...@<number in pixels>",
                                   "Specify how much distance to be kept from specific blocks when arrows go around blocks.",
                                   EHintType::ATTR_VALUE, false));
            return true;
        }
        if (CaseInsensitiveEqual(attr, "routing.factor"))
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<number in [0..1]>",
                                   "0 means minimize only length, 1 means minimize only turns, values between balance.",
                                   EHintType::ATTR_VALUE, false));
        if (CaseInsensitiveEqual(attr, "routing.arrow_distance"))
            csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<number in [0..100]>",
                                   "0 turns off automatic arrow overlap avoidance at arrow head or tail. Other values will make this arrow be "
                                   "at least this far from other arrows overlapping at its start/end.",
                                   EHintType::ATTR_VALUE, false));
        if (CaseInsensitiveEqual(attr, "routing.block_others") ||
            CaseInsensitiveEqual(attr, "routing.extend") ||
            CaseInsensitiveEqual(attr, "routing.clip_block") ||
            CaseInsensitiveEqual(attr, "routing.try_harder") ||
            CaseInsensitiveEqual(attr, "routing.allow_joint_layout"))
            csh.AddYesNoToHints();
    }
    if (f_marker && CaseInsensitiveBeginsWith(attr, "marker"))
        OneArrowAttr::AttributeValues(attr, csh, EArcArrowType::ARROW);
    if (f_shape && CaseInsensitiveEqual(attr, "shape.size"))
		//skip adding any attribute to shape.size
		return false;
	if (CaseInsensitiveEqual(attr, "draw_before"))
		csh.addEntityNamesAtEnd = "Draw the current element before block %s.";
	if (CaseInsensitiveEqual(attr, "draw_after"))
		csh.addEntityNamesAtEnd = "Draw the current element before block %s.";
	return SimpleStyleWithArrow::AttributeValues(attr, csh);
}

/** Push all locations recorded about the attributes,
 * We assume the reason is copying.*/
void BlockStyle::Push(const FileLineCol & l)
{
	for (unsigned u = 0; u < 6; u++)
		for (auto &sp : alignment.Get(u).value.blocks)
			if (!sp.file_pos.IsInvalid())
				sp.file_pos.Push(l, EInclusionReason::COPY);
	if (label && label.file_pos.IsValid())
		label.file_pos.Push(l, EInclusionReason::COPY);
	if (label_mode && label_mode.file_pos.IsValid())
		label_mode.file_pos.Push(l, EInclusionReason::COPY);
	if (label_pos && label_pos.file_pos.IsValid())
		label_pos.file_pos.Push(l, EInclusionReason::COPY);
    if (route_cross)
        for (auto& sp : *route_cross)
            if (!sp.file_pos.IsInvalid())
                sp.file_pos.Push(l, EInclusionReason::COPY);
    if (route_cross_parent)
        for (auto &sp : *route_cross_parent)
		    if (!sp.file_pos.IsInvalid())
			    sp.file_pos.Push(l, EInclusionReason::COPY);
    if (distance_per_block)
        for (auto &sp : *distance_per_block)
            if (!sp.file_pos.IsInvalid())
                sp.file_pos.Push(l, EInclusionReason::COPY);
    for (auto& sp : draw_time)
        if (!sp.file_pos.IsInvalid())
            sp.file_pos.Push(l, EInclusionReason::COPY);
}

std::string_view Parent::GetName() const { return AsBlock() ? AsBlock()->name_full : AsName(); }

void BlockContext::ApplyContextContent(const BlockContext &o)
{
    ContextBase::ApplyContextContent(o);
    //Note: we don't copy running styles. They are not part of designs and are ignored if changed.
    background += o.background;
	if (o.pedantic) pedantic = o.pedantic;
}

void BlockContext::ApplyContextContent(BlockContext &&o)
{
    //Note: we don't copy running styles. They are not part of designs and are ignored if changed.
    background += o.background;
	if (o.pedantic) pedantic = o.pedantic;
    ContextBase::ApplyContextContent(std::move(o));
}



void BlockContext::Empty()
{
    ContextBase::Empty();
    Procedures.clear();
    background.Empty();

    //Now add default styles, but all empty
    //For blocks and arrows use the same flags as the running style.
    styles["block"] = running_style_blocks;
    styles["block"].write().type = EStyleType::DEFAULT;
    styles["block"].write().Empty();
    styles["container_shape"] = styles["container"] = styles["block"];
    styles["indicator"] =
        BlockStyle(EStyleType::DEFAULT, EBlockStyleType::Unspecified, EBlockStyleTarget::Block, EColorMeaning::LINE_ARROW_TEXT, 
                   false, false, true, true, true, false, false, false, false, false, true, false, false);
                   //line, fill, shadow, margin (for size) only
    styles["indicator"] += running_style_blocks;
    styles["inlined_chart"] = 
        BlockStyle(EStyleType::DEFAULT, EBlockStyleType::Unspecified, EBlockStyleTarget::Block, EColorMeaning::FILL,
                   false, false, true, true, true, false, false, false, true, false, true, false, false);
                   //alignment line, fill, shadow, margin (for size & aspect_ratio) only
    styles["inlined_chart"] += running_style_blocks;
    styles["text"] =
        BlockStyle(EStyleType::DEFAULT, EBlockStyleType::Unspecified, EBlockStyleTarget::Block, EColorMeaning::TEXT, 
                   false, true, false, false, false, true, false, true, true, true, true, false, false);
                   //text, numbering, label, content, alignment, margin only
    styles["arrow"] = running_style_arrows;
    styles["arrow"].write().type = EStyleType::DEFAULT;
    styles["arrow"].write().Empty();
    styles["label"] =
        BlockStyle(EStyleType::DEFAULT, EBlockStyleType::Unspecified, EBlockStyleTarget::Arrow, EColorMeaning::LINE_ARROW_TEXT, 
                   false, true, true, true, true, true, false, true, false, false, false, false, true);
                   //just text, line, fill, shadow and number and label and marker

    styles["invis"] =
        BlockStyle(EStyleType::DEFAULT, EBlockStyleType::Unspecified, EBlockStyleTarget::Block, EColorMeaning::NOHOW,
                   false, false, false, false, false, false, false, false, true, true, true, false, false);
                   //alignment, margin and content only
    styles["join"] =
        BlockStyle(EStyleType::DEFAULT, EBlockStyleType::Unspecified, EBlockStyleTarget::Block, EColorMeaning::NOHOW, 
                   false, false, true, true, true, false, false, false, true, true, true, false, false);
                   //only line, fill and shadow
    styles["row"] =
        BlockStyle(EStyleType::STYLE, EBlockStyleType::Generic, EBlockStyleTarget::Block, EColorMeaning::NOHOW,
                   false, false, false, false, false, false, false, false, true, true, false, false, false);
                   //alignment and content only
    styles["col"] = styles["row"];
    styles["->"] = styles[">"] = styles[">>"] = styles["=>"] =
    styles["--"] = styles[".."] = styles["++"] = styles["=="] =
		BlockStyle(EStyleType::DEF_ADD, EBlockStyleType::Unspecified, EBlockStyleTarget::Arrow, EColorMeaning::LINE_ARROW_TEXT, 
                   true, true, true, false, false, true, false, false, false, false, false, true, false);
                   //just arrow, text, line, number and routing
    styles["around"] =
        BlockStyle(EStyleType::DEF_ADD, EBlockStyleType::Unspecified, EBlockStyleTarget::Block, EColorMeaning::NOHOW, 
                   false, true, true, true, true, true, false, true, false, true, true, false, false);
                   //no shape, alignment and routing
}

void BlockContext::Plain()
{
    Empty();
    ContextBase::Plain();
    for (auto &s : styles)
        if (s.second.read().type==EStyleType::DEFAULT)
            s.second.write().MakeCompleteButText();

    const std::vector<StringWithPos> prev = {{"prev", FileLineColRange()}};
    const std::vector<StringWithPos> next = {{"next", FileLineColRange()}};

    styles["row"].write().alignment.YMiddle = {true, false, EStyleType::STYLE, {prev, {0.5, false}, EAlignPrio::Explicit}};
    styles["row"].write().alignment.Left = {true, true, EStyleType::STYLE, {prev, {1.0, true}, EAlignPrio::Explicit}};
    styles["row"].write().alignment.Right = {true, true, EStyleType::STYLE,{next, {0.0, true}, EAlignPrio::Explicit}};

    styles["col"].write().alignment.Top = {true, true, EStyleType::STYLE,{prev, {1.0, true}, EAlignPrio::Explicit}};
    styles["col"].write().alignment.Bottom = {true, true, EStyleType::STYLE,{next, {0.0, true}, EAlignPrio::Explicit}};
    styles["col"].write().alignment.XCenter = {true, false, EStyleType::STYLE,{prev, {0.5, false}, EAlignPrio::Explicit}};

    styles["invis"].write().XChildAlign = {EInternalAlignType::Exact, EAlignPrio::Must, {}};
    styles["invis"].write().YChildAlign = {EInternalAlignType::Exact, EAlignPrio::Must, {}};
    styles["invis"].write().allow_arrows = true;
    //styles["invis"].write().line.type = ELineType::NONE;
    //styles["invis"].write().fill.color = ColorType(0,0,0,0); //fully transparent
    //styles["invis"].write().shadow.offset = 0; //no shadow
    styles["invis"].write().min_margin[0] = 0;
    styles["invis"].write().min_margin[1] = 0;
    styles["invis"].write().max_margin[0] = 0;
    styles["invis"].write().max_margin[1] = 0;
    styles["invis"].write().min_imargin[0] = 0;
    styles["invis"].write().min_imargin[1] = 0;
    styles["invis"].write().max_imargin[0] = 0;
    styles["invis"].write().max_imargin[1] = 0;
    styles["invis"].write().label_imargin = 0;
	styles["invis"].write().content_margin = true;
	//no min size for invisible containers

    styles["join"].write().fill.color.reset();
    styles["join"].write().shadow.offset.reset();
    styles["join"].write().shadow.blur.reset();

    styles["container"].write().XChildAlign = {EInternalAlignType::Exact, EAlignPrio::Default_style, {}};
    styles["container"].write().YChildAlign = {EInternalAlignType::Exact, EAlignPrio::Default_style, {}};
    styles["container"].write().size[0] = {10, EAlignPrio::Default};
    styles["container"].write().size[1] = {10, EAlignPrio::Default};
    styles["container"].write().text.Apply("\\mu(0)\\md(0)\\ml(0)\\mr(0)"); //kill text margin
    styles["container_shape"] = styles["container"];
	styles["container_shape"].write().min_imargin[0] = 0;
    styles["container_shape"].write().min_imargin[1] = 0;
    styles["container_shape"].write().max_imargin[0] = 0;
    styles["container_shape"].write().max_imargin[1] = 0;
    styles["container_shape"].write().label_imargin = 0;

    styles["block"].write().XChildAlign = {EInternalAlignType::Mid, EAlignPrio::Default_style, {}};
    styles["block"].write().YChildAlign = {EInternalAlignType::Mid, EAlignPrio::Default_style, {}};
    styles["block"].write().size[0] = {10, EAlignPrio::Content_high}; //So that it is higher than the default ordering coming from the default content prev/next of auto_high_high
    styles["block"].write().size[1] = {10, EAlignPrio::Content_high};
    styles["block"].write().min_imargin[0] = 0;
    styles["block"].write().min_imargin[1] = 0;
    styles["block"].write().max_imargin[0] = 0;
    styles["block"].write().max_imargin[1] = 0;
    styles["block"].write().label_imargin = 0;
    styles["block"].write().label_pos = {EDirection::Invalid, FileLineCol()}; //invalid is used as a proxy for center.

    styles["indicator"].write().size[0] = { Element::indicator_size.x, EAlignPrio::Must };
    styles["indicator"].write().size[1] = { Element::indicator_size.y, EAlignPrio::Must };
    styles["indicator"].write().min_imargin[0] = 0;
    styles["indicator"].write().min_imargin[1] = 0;
    styles["indicator"].write().max_imargin[0] = 0;
    styles["indicator"].write().max_imargin[1] = 0;
    styles["indicator"].write().label_imargin = 0;
    styles["indicator"].write().min_margin[0] = 0;
    styles["indicator"].write().min_margin[1] = 0;
    styles["indicator"].write().max_margin[0] = 0;
    styles["indicator"].write().max_margin[1] = 0;

    styles["text"].write().min_margin[0] = 0;
    styles["text"].write().min_margin[1] = 0;
    styles["text"].write().max_margin[0] = 0;
    styles["text"].write().max_margin[1] = 0;
    styles["text"].write().min_imargin[0] = 0;
    styles["text"].write().min_imargin[1] = 0;
    styles["text"].write().max_imargin[0] = 0;
    styles["text"].write().max_imargin[1] = 0;
    styles["text"].write().label_imargin = 0;

    styles["inlined_chart"].write().line.color = ColorType::none();
    styles["inlined_chart"].write().fill.color = ColorType::none();

    styles["around"].write().fill.color = ColorType(0,0,0,0); //transparent
    styles["around"].write().allow_arrows = true;
    styles["around"].write().content_margin = false;

    //Set both arrowheads to solid. Arrow::Arrow() will kill the one not present
    styles["arrow"].write().arrow.write().SetType(EArrowEnd::START, EArrowType::SOLID);
    styles["arrow"].write().arrow.write().SetType(EArrowEnd::END, EArrowType::SOLID);
    styles["arrow"].write().marker.SetType(EArrowEnd::END, EArrowType::NONE);
    //styles["arrow"].write().arrow.write().SetType(EArrowEnd::SKIP, {true, EArrowType::JUMPOVER});
    styles["arrow"].write().text.Apply("\\C(1,1,1)"); //make background color white
    styles["arrow"].write().routing = EArrowRouting::Curvy;

    styles["label"].write().line.type = ELineType::NONE;
    styles["label"].write().fill.color = ColorType();
    styles["label"].write().shadow.offset = 0;
    styles["label"].write().text.Apply("\\C(1,1,1)"); //make background color white
    styles["label"].write().marker.SetGvType(EArrowEnd::END, {"tick"});

	styles["->"].write().line.type = styles["--"].write().line.type = ELineType::SOLID;
	styles[">"].write().line.type  = styles[".."].write().line.type = ELineType::DOTTED;
	styles[">>"].write().line.type = styles["++"].write().line.type = ELineType::DASHED;
	styles["=>"].write().line.type = styles["=="].write().line.type = ELineType::DOUBLE;

    styles["--"].write().routing = styles[".."].write().routing = EArrowRouting::Straight;
    styles["++"].write().routing = styles["=="].write().routing = EArrowRouting::Straight;


    running_style_blocks.write().alignment = styles["row"].read().alignment;
    running_style_blocks.write().alignment.SetAllPriority(EAlignPrio::Content);
	running_style_blocks.write().label = { "\\*", {} };

    pedantic = false;
}

