/*
    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/>.
*/

/** @defgroup libmscgen The engine for signalling charts.
@ingroup libs

  The mscgen library contains functions to
  - parse signalling charts
  - to lay out charts
  - to draw the chart onto carious cairo surfaces
  - to parse the chart for color syntax highlighting purposes,
    collect and organize hints; and
  - to help a Windows client with auxiliary functions (controls, grouping state,
    tracking rectangle generation, header size calculation for autosplit, etc.)

    It builds on the libcgencommon base library.

  # Example

  \msc
  hscale=auto;
  a [label="ArcBase", url="\ref msc::ArcBase"],
  b [label="MscChart", url="\ref msc::MscChart"];
  a->b [label="This is a message label with a \L(https://sourceforge.net/projects/msc-generator/)link\L()."];
  \endmsc

  # Signalling chart Terminology

  - An *entity* is something the messages go in between. They have three classes
    in libmscgen: one Entity per entity of the chart, one EntityApp for every
    occurrence of that entity (definition, change of status, etc.) and an
    EntityCommand for each group of entities mentioned together.
  - An entity is a *group entity* if it contains other entities. We call these
    *child* and the group entity the *parent*.
  - A group entity is *collapsed* if its 'collapsed' attribute says so,
    and thus its child entities are not shown.
  - An entity is *active* if it shows on the chart. It is not active if
    it is a child of a collapsed group entity.
  - An *arc* is a command in the chart terminated by a semicolon. An arc
    can be an arrow, an entity definition, a box, an option, etc.
    All arcs are descendants of ArcBase.
  - The chart is represented by an MscChart object. It incorporates all arcs,
    entities and settings.
  - A *context* is a set of settings valid at a given place in the file during
    parse. It includes the current definition of colors, styles and a few global
    option, such as 'compress', 'vspacing', 'numbering', 'indicator', 'slant_angle' and 'hscale'.
    They also include the current comment line and fill style, the default font
    and current numbering style.read(). Contexts are captured by class ContextBase.
  - A *scope* in the chart file is an area enclosed between '{' and '} symbols.
    Any change you make to the context is valid only inside the scope.
  - A *style* is a set of attributes (line, vline, fill, vfill, shadow,
    arrow, text, note, solid, side, numbering, compress, vspacing, indicator and
    makeroom). Not all attributes have to be set (making a style *incomplete*)
    and in some cases not all of them *can be set*. Styles are capture by
    class Style.
  - The *coverage* or *area* of an element is the shape it covers on the 2D
    canvas of the chart. It is described by a Contour object or by an Area
    object. The latter contains a pointer back to the element and a mainline.
  - The *mainline* of an element is mostly a horizontal stripe of the chart
    area as wide as the chart itself. It denotes how much elements below
    a given element can be compressed (shifted upwards). In general, an element
    can be shifted upwards as high as it hits an element above it. This,
    however could result in rearranging element orders, if a later element
    is shifted higher besides an element before it. E.g., if we have 5
    entities and two arrows: a->b followed by d->e. In this case d->e must
    come later than a->b even with compress, thus it cannot be shifted higher
    than a->b. This is prevented by the mainline. The mainline of a->b is a
    thin horizontal rectangle, covering the line of the a->b arrow. Likewise
    the mainline of d->e. At laying out d->e with compress we ensure that
    it does not overlap with any other element (such as a->b, which it would
    not since it is besides a->b), but also that the mainlines do not overlap.
    This will make d->e always being somewhat lower than a->b. If, however,
    a->b has a long multiline label, d->e can be shifted upwards besides this
    label, but not higher than the arrow of a->b. In this sense the mainline
    captures the vertical position of the core of the element.
    For slanted arrows, the mainline is no longer just a horizontal stripe
    of the chart. It his so left and right from the arrow, but along the arrow
    it slants with it.

  # Parsing

  Parsing is defined in msc_lang.yy and msc_lang2.ll yacc and lex files.
  There are two parsing processes (MscParse and MscCshParse), one for
  generating the drawable MscChart class and another to collect color syntax
  highlighting and hinting data into class MscCsh. Both parsing process
  is described in the same yacc and lex file, but those are compiled twice
  with different options and type definitions one for MscChart one for MscCsh.
  The former are msc_lang.h, msc_lang.cc, msc_lang2.h, msc_lang2.cc; whereas
  the latter result in msc_csh_lang.h, msc_csh_lang.cc, msc_csh_lang2.h and
  msc_csh_lang2.cc. Using the same yacc and lex sources enables easy
  consistency between drawn charts and CSH.

  The parsing for MscChart uses a location type (YYLTYPE) that consists of a line and col
  number, since this is how we display error messages. Whereas the csh parsing
  uses a location type that contains the number of bytes since the beginning
  of the file, since the Windows RichEdit control uses such location values.

  During both parsing type we maintain a stack of contexts. For MscChart it is a
  stack of ContextBase classes, for csh it is a stack of CshContext classes.
  The latter one is simpler, it merely contains the colors and style names,
  because only these are needed for autocompletion.

  When parsing for MscCsh, we also collect potential extensions of a fragment
  under the cursor. We call these 'Hints'.

  When parsing for MscChart, we collect Entities and Arcs. Further layout and drawing
  operations (all called from a member of MscChart) work on the list (or rather tree)
  of arcs. Tree, because arcs may contain other arcs, as in case of a box, for
  example.

  # The lifecycle of an Arc.

    @<parsing starts>
    Note that value of chart options (like the text.*) need to captured used here as they change
    during parse. So e.g., the style of all elements
    -  Construction: Only basic initialization is done here. For some arcs with style (descendants of LabelledArc)
       we fetch the appropriate style from MscChart::MyCurrentContext() via SetStyleWithText(). We do this only
       if the arc has no refinement style or its style is not influenced by an additional keyword not known
       at construction time (basically for note and symbol).
       We also look up entities the arc refers to (and create them if needed), so after this point
       we have EIterators pointing to the MscChart::AllEntities list.
    -  SetLineEnd. This stores in the arc where exactly it is located in the source file.
       It must be called before adding attributes (at least for boxes), so that they
       know their location and can search for a match in the collapse catalogs (GUI state.)
       for any GUI initiated collapse/expand attributes.
    -  AddAttributeList: Add attributes to arc. This function must always be called (with nullptr if no attributes)
       We have to do this before we can create a list of Active Entities, since attributes can
       result in the implicit definition of new entities. For more complicated ArcLabel descendants, the style
       of the element is set here (all arcs, verticals, boxes, pipes and dividers).
       Note that we maintain the parse_running_style variable of Entities that can be used during parsing
       by arrows to gather the value of the aline, atext and arrow attributes of the entity, since these
       are required already during parsing (when these attributes are set in elements)
       For DirArrows the 'is_forward' is set here. Also, the offset of src/dst is converted
       from 'larger values increase arrow length' (as specified by the user to 'start before' and 'end after')
       to 'larger value is more to the right' (as is the case with the offset of 'lost at').
    -  Additional small functions (Box::SetPipe, EntityCommand::ApplyPrefix, etc.)

    @<parsing ends>

    @<here we construct a list of active entities from AllEntities to ActiveEntities in
      MscChart::PostParseProcess(), where we also set the 'pos' fields of entities based on entity
      visibility (some entities may not be visible due to entity group collapsing). Here we also
      add a EntityCommand to the beginning of the arcs for implicitly generated entities.
      in MscChart::PostParseProcessArcList() (used by all arcs recursively that hold a list of arcs,
      such as pipes, boxes, parallel, etc.) we
      * merge EntityCommand elements following one another.
      * Determine exactly what element a note or command refers to
      * Form join series from elements marked with 'join'. As part of this process, we set the
        unspecified endings of arrows connecting to each other. For arrows connecting to boxes,
        we merely set the arrow ending to the (left or right) entity of the box (since we do not know
        the actual size of the box until after BoxSeries::Width()) and set the specific arrow
        end offset in JoinSeries::Width().

    -  PostParseProcess: This is called (also recursively for children arcs in the tree) to do the following.
       (This function can only be called once, as it changes arcs (e.g., you do not want to
       increment numbering twice). Often the arc needs to be replaced to a different one (such as when you
       collapse a box to a block arrow, for example), in this case the returned pointer shall be used.
       If the return pointer == this, the arc shall not be replaced, if nullptr, the arc shall be removed.)
       *  Determine the non-specified entities for boxes, pipes and arrows. Note that the EIterators
          received as parameters ("left", "right") are iterators of AllEntities and not ActiveEntities.
       *  Calculate numbering for labels. Also, if the entity has a name, store the arc and its name in
          MscChart::ReferenceNames;
       *  Determine which actual entities the arc refers to. In the first step above all entity iterators
          of the arc were set to point to an entity in the MscChart::AllEntities list.
          In this step we consider collapsed entities and search
          the corresponding entity in the ActiveEntities list. After this point all EIterators shall point
          to the ActiveEntities list. We have to ensure that just because of collapsing entities,
          automatic numbering does not change. (Some numbers are skipped - those that are assigned to
          invisible arcs, such as arc between entities not shown.)
       *  For arrows that are specified as lost, we determine where the loss is happening.
       *  For boxes we check if the box is collapsed. If so, we replace their content to Indicator.
          Here we have to ensure that automatic numbering of arrows do not change as for the third above.
          We also have to ensure that for auto-sizing entities (e.g., " .. : label { <content> }") we
          keep the size as would be the case for a non-collapsed box. Also, content that does not show, but
          influence appearance (e.g., entity commands, page breaks, background changes, etc.) are
          still kept as content and steps below (like Width, Layout, PostPosProcess(), etc.) shall be
          performed on them. See replacement below.
       *  Combine series of CommandEntities and series of ArcIndicators to a single EntityCommand or
          ArcIterator, respectively. if two or more of them occurs just one after the other. This happens in
          MscChart::PostParseProcessArcList().
       *  Set up some extra variables
       *  Print error messages.
       *  Decide on replacement. PostParseProcess have a "hide" parameter telling if this arc will be hidden
          due to a collapsed box (and no other reason). In this case we return nullptr, if the arc does not
          want its Width, Layout, PostPosProcess, etc. to be called later, see below. Else we return "this".
          An arc can also become hidden due to collapsed entities - this was determined in the third sub-step
          above. If the arc becomes hidden, it can get replaced to an Indicator by (MscChart::PostParseProcess)
          if the entity in question has its "indicator" in "running_style" set. Else we just return nullptr.
       *  For notes and comments, we decide who is the target and then a) for notes we append the note to
          MscChart::Notes (while at the same time keeping it in its original place among other arcs); b) for
          side comments, we call the AttachComment() of the target element (also keeping the comment in its
          original location, as well); and c) for end-notes, we let MscChart::PostParseProcessArcList() take
          them out from the list of arcs processed and move them to MscChart::EndNotes (to be appended to
          MscChart::Arcs later on).
       *  For symbols that are drawn by reference to another entity we take them out and move them to 
          MscChart::ByRefs.
       *  Similar, for verticals with no markers specified (which then size themselves along the previous arc),
          we store what was the previous arc to size to. Note that this is somewhat different from the
          previous arc used to determine the target of notes. Each arc can determine if it wants to be
          a target of a note (via ArcBase::CanBeNoted()) independently of whether a vertical can align
          to it (via ArcBase::CanBeAlignedTo()). These 'by ref' verticals are also moved to MscChart::ByRefs.
       *  For labels, we replace the remaining escapes to actual values, except for "\r()"
          element references. Those will be done in FinalizeLabels().
    -  FinalizeLabels: This is called recursive to fill in all escapes in labels with their
       actual values. Here we substitute number, color, style references and so on. This fills
       the "parsed_label". We pay attention to call FinalizeLabels for the elements in MscChart::ByRefs, as well.
    -  Width: This is also called recursively. Here each arc can place the distance requirements between
       entities. Using the data  MscChart::CalculateWithAndHeight() can place entities dynamically if hscale==auto.
       If hcale!=auto, entities have fixed positions, but this function is still called (so as it can be used
       to calculate cached values), useful for placing verticals correctly.
       Width also allows entities to express how wide they are on sides of entity lines. E.g., a box (X--Y)
       extends somewhat left of the entity line of X and right of the entity line of Y. These are recorded in a
       DistanceRequirements structure at a quite fine y resolution. This information allows placing a vertical
       or symbol correctly left or right of other elements.
       We pay attention to call Width for the elements in MscChart::ByRefs, as well. Note that symbols/verticals
       referring to the same element are width'ed in the order in which they are appear in the source file (and
       in which order they have been inserted into MscChart::ByRefs). This means that the latter ones will be
       laid out more distant from the element they refer to.

    @<here we calculate/finalize the Entity::pos for all entities in ActiveEntities in MscChart::CalculateWidthHeight>

    -  Layout: This is a key function, returning the vertical space an element(/arc) occupies. It also places
       the contour of the element in its "cover" parameter. The former (height) is used when compress is off and
       the latter (contour) if compress is on. In the latter case the entity will be placed just below entities
       above such that their contours just touch but do not overlap. Also fills in
       Element::area and area_draw with contours that will be used to detect if a mouse pointer is
       inside the arc or not and to draw a tracking contour over the arc. Observe 1) contour returned in
       cover is used for placement and should include shadows; 2) area is used to detect if the mouse is
       within during tracking and should _not_ contain shadows; 3) area_draw is used to draw a tracking rectangle,
       and it should be a frame for boxes and pipes with content, not the full area covered by the box.
       Note that if `chart->prepare_for_tracking` is not set, we do not calculate `area` and `area_draw`.
       Layout can also store some pre-computed values and contours to make drawing faster (marked mutable).
       Layout always places the element at the vertical position=0. Any contour should assume that.
       Finally, Layout() also fills in "area_important", containing the 'important' part of the
       element's cover (text, arrowheads, symbols). The Note layout engine will use this, to avoid
       covering these areas. 

    @<By reference symbols and verticals are also laid out with their target (via LayoutByRef()) and considered as 
      a single movable entity by layout. Also, the content of MscChart::ByRefs is re-inserted into the arclist, 
      following the element they refer to.>
     

    -  ShiftBy: During the placement process this is called to shift an entity in the vertical direction
       (usually downwards). This should update area, area_draw and any other cached variable.
       ArcBase::yPos contains the sum of these shifts. This function can be called multiple times.
    -  FinalizeLayout(): This function is called after Layout() when the element is placed directly
       below the element above it. This allows the element to tweak its layout to connect to the element
       above it. Currently only used by DirArrow to create a curly join.
    -  CollectPageBreaks: This walks the tree of elements and each CommandPageBreak places its
       y coordinate into MscChart::pageBreakData. Also fixes the size of the previous page.
       This function can be called multiple times during automatic pagination, but only once if
       no automatic pagination is done.
    -  PageBreak: This is called by an automatic pagination process (if any), when the element is
       cut in half by a page break. The element can rearrange itself to accommodate the page break, by
       shifting half of it down or can indicate that it cannot rearrange itself and shall be fully
       shifted to the next page. Elements with `keep_together` set to false are not called, those are
       simply cut in half abruptly.
       This function can be called multiple times, if the element spans multiple page breaks. In the
       second and subsequent calls only the last chunk shall be split.
    -  PlaceWithMarkers: By now all positions and height values are final, except for notes & verticals.
       (Comments are also placed with their target.) We go through the tree and calculate position & cover for
       verticals. This is needed as a separate run, just to do it before placing notes.

    @<here we place floating notes in MscChart::PlaceFloatingNotes()>

   -  PostPosProcess: Called after the last call to ShiftBy. Here all x and y positions of all elements are set.
      Here masks when entity lines are hidden behind text and warnings/errors are generated which require vertical position
      to decide. We also expand all "area" and "area_draw" members, so that contours look better in tracking mode.
      Note that if `chart->prepare_for_tracking` is not set, we do not expand these to save computation.
      No error messages shall be printed after this function by arc objects. (MscChart will print some, if
      page sizes do not fit, but that is under control there.)
   -  RegisterCover: This function is called recursively and it registers the cover of the element in to
      chart->AllCovers in exactly the same order as drawing happens. Only called if chart->prepare_for_tracking
      is true. (Else we do not need this cover info.)
   -  Draw: This function actually draws the chart to the "canvas" parameter. This function can rely on cached
      values in the elements. It can be called several times and should not change state of the elements
      including the cached values, nor should it generate errors.
   -  RegisterLabels/CollectIsMapElementsArcList: One or both of these can be called in addition or instead of
      Draw(). RegisterLabels traverses the tree and collects information about all labels into MscChart::labelData.
      CollectIsMapElementsArcList also traverses the tree, but extracts only links (\\L escape) into
      MscChart::ismapData. They are used with the -T lmap and -T ismap command line options, respectively and
      are not normally called when we Draw (to save processing) and vice versa.
   -  Destructor.

    All the above functions are called from the MscChart object. The first three are called from MscChart::ParseText, whereas
    the remainder from the MscChart:: member functions of similar names, with the exception of ShiftBy, which is
    called from MscChart::Layout and MscChart::PlaceListUnder.

    Color Syntax Highlighting support also has functions in Arcs.
    1. AttributeNames: A static function that inserts the attributes valid for this type of arc into a Csh object.
    2. AttributeValues: A static function that inserts the possible values for a given attribute into a Csh object.

    Both of the above calls include a callback function to draw the small pictograms for hints. These functions
    are in fact only used for the hinting process and not for actual syntax highlighting (which requires no
    support from Arc objects, since those are not created during a Csh parse.
*/


/** @defgroup libmscgen_files The files of the mscgen library.
 * @ingroup libmscgen
 *
 * The files are organized in the following logical hierarchy.
   - `mscstyle`, 'mscprogress', 'mscelement', 'mscattribute` implement
      signalling specific styles, progress tracking, elements and attributes.
   - `mscentity`, `arcs` and `commands` define the classes that represent
     the elements of the chart. Entities and related
     classes in `entity` and various chart elements, all descendants of ArcBase
     (`arcs` and `commands` are loose groups of chart commands and various arcs,
     boxes and the like).
   - `msc` brings together everything including parsing, layout and drawing by
     defining the main class MscChart.
   - `msccsh` specifies the signalling specific hint and coloring code.

 * @file arcs.cpp The basic definitions or arcs that are not commands.
 * @ingroup libmscgen_files */

#include <math.h>
#include <cassert>
#include <iostream>
#include <algorithm>
#include "msc.h"

using namespace std;
using namespace msc;

JoinableResult EndConnection::CanJoinTo(const EndConnection &next) const
{
    //first check if these endpoints can join at all
    if (error_msg_no_join)
        return JoinableResult(JoinableResult::ERROR_BAD_END_FIRST, error_msg_no_join, error_msg_no_join_add);
    if (next.error_msg_no_join)
        return JoinableResult(JoinableResult::ERROR_BAD_END_SECOND, next.error_msg_no_join, next.error_msg_no_join_add);
    //then see if a curly join is possible
    if (accepts_curly_join && next.can_curly_join &&
        body_is_left == next.body_is_left &&
        (ArrowEnding::operator==(next) || is_indicator))
        return JoinableResult(ArrowEnding::operator==(next) ? JoinableResult::CURLY : JoinableResult::CURLY_NEED_UPDATE_SECOND, !body_is_left, is_src, next.is_src);
    //Then check if we are on the right side of each other - if they directly connect we are OK.
    if (body_is_left == next.body_is_left || (!(*this==next) && body_is_left != (*this<next) ))
        return JoinableResult::ERROR_OVERLAP;
    //for A--B  C--D, we are now 'B' and 'C or vice versa
    if (is_unspecified) {
        if (next.is_unspecified)
            return JoinableResult(JoinableResult::ERROR_BOTH_UNSPECIFIED);
        else
            return JoinableResult(JoinableResult::NEED_UPDATE_FIRST, body_is_left, is_src, next.is_src);
    } else if (next.is_unspecified)
        return JoinableResult(JoinableResult::NEED_UPDATE_SECOND, body_is_left, is_src, next.is_src);
    return JoinableResult(JoinableResult::SETTLED, body_is_left, is_src, next.is_src);
}

template class UPtrList<ArcBase>;

ArcBase::ArcBase(EArcCategory c, MscChart *msc) :
    MscElement(msc), had_add_attr_list(false), valid(true),
    vspacing(0), parallel(false), overlap(false),
    keep_together(true), keep_with_next(false), stick_to_prev(false), height(0),
    myProgressCategory(c)
{
    vspacing = chart->MyCurrentContext().vspacing.value_or(0);
    chart->Progress.RegisterItem(myProgressCategory);
}

ArcBase::~ArcBase()
{
    _ASSERT(chart);
    if (chart)
        chart->Progress.UnRegisterItem(myProgressCategory);
}

Range ArcBase::GetVisualYExtent(bool include_comments) const
{
    Range ret = GetAreaToDraw().GetBoundingBox().y;
    if (include_comments && ret.till < yPos+comment_height)
        ret.till = yPos+comment_height;
    return ret;
}

Area ArcBase::GetCover4Compress(const Area &a) const
{
    Area ret(static_cast<const Contour &>(a).CreateExpand(chart->compressGap/2,
                                             contour::EXPAND_MITER_SQUARE,
                                             contour::EXPAND_MITER_SQUARE,
                                             1, 1), a.arc);
    ret.mainline = a.mainline;
    return ret;
}

//l can be an empty list
void ArcBase::AddAttributeList(gsl::owner<AttributeList*> l)
{
    had_add_attr_list = true;
    if (l && valid)
        for (auto &pAttr : *l)
            if (!AddAttribute(*pAttr))
                pAttr->InvalidAttrError(chart->Error);
    delete l;
}

bool ArcBase::AddAttribute(const Attribute &a)
{
    //In case of LabelledArc this will not be called, for
    //"compress" and "vspacing" attributes.
    //There the style.read().AddAtribute will process any compress attribute.
    //Then in LabelledArc::PostParseProcess
    //we copy style.read().compress.value to the ArcBase::compress.
    if (a.Is("compress")) {
        if (!a.EnsureNotClear(chart->Error, EStyleType::ELEMENT)) return true;
        if (!a.CheckType(EAttrType::BOOL, chart->Error)) return true;
        vspacing = a.yes ? DBL_MIN : 0;
        return true;
    }
    if (a.Is("vspacing")) {
        if (!a.EnsureNotClear(chart->Error, EStyleType::ELEMENT)) return true;
        if (a.type == EAttrType::STRING && a.value == "compress") {
            vspacing = DBL_MIN;
            return true;
        }
        if (!a.CheckType(EAttrType::NUMBER, chart->Error)) return true;
        vspacing = a.number;
        return true;
    }
    if (a.Is("parallel")) {
        if (!a.EnsureNotClear(chart->Error, EStyleType::ELEMENT)) return true;
        if (!a.CheckType(EAttrType::BOOL, chart->Error)) return true;
        parallel = a.yes;
        return true;
    }
    if (a.Is("overlap")) {
        if (!a.EnsureNotClear(chart->Error, EStyleType::ELEMENT)) return true;
        if (!a.CheckType(EAttrType::BOOL, chart->Error)) return true;
        overlap = a.yes;
        return true;
    }
    if (a.Is("refname")) {
        if (!a.EnsureNotClear(chart->Error, EStyleType::ELEMENT)) return true;
        auto i = chart->ReferenceNames.find(a.value);
        if (i==chart->ReferenceNames.end()) {
            chart->ReferenceNames[a.value].linenum = a.linenum_value.start;
            chart->ReferenceNames[a.value].arc = this;
            refname = a.value;
            return true;
        }
        chart->Error.Error(a, true, "The reference name '" + a.value + "' is already assigned. Ignoring it.");
        chart->Error.Error(i->second.linenum, a.linenum_value.start, "This is the location of the previous assignment.");
        return true;
    }
    if (a.Is("keep_together")) {
        if (!a.EnsureNotClear(chart->Error, EStyleType::ELEMENT)) return true;
        if (!a.CheckType(EAttrType::BOOL, chart->Error)) return true;
        keep_together = a.yes;
        return true;
    }
    if (a.Is("keep_with_next")) {
        if (!a.EnsureNotClear(chart->Error, EStyleType::ELEMENT)) return true;
        if (!a.CheckType(EAttrType::BOOL, chart->Error)) return true;
        keep_with_next = a.yes;
        return true;
    }
    return MscElement::AddAttribute(a);
}


/** Adds attribute names valid for all arcs as hints. */
void ArcBase::AttributeNamesSet1(Csh &csh)
{
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "compress",
        "Turn this on to shift this elements upwards until it bumps into the element above (to compress the chart vertically).",
        EHintType::ATTR_NAME));
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "vspacing",
        "Specify the vertical spacing above this element.",
        EHintType::ATTR_NAME));
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "parallel",
        "Turn this on so to put subsequent elements in parallel with this one and not stricly below (except if they overlap).",
        EHintType::ATTR_NAME));
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "overlay",
        "Turn this on so to put subsequent elements in parallel with this one and not below (even if they overlap).",
        EHintType::ATTR_NAME));
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "keep_together",
        "Turn this on if you do not want this element to break across pages.",
        EHintType::ATTR_NAME));
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "keep_with_next",
        "Turn this on if you want this element on the same page with the subsequent one.",
        EHintType::ATTR_NAME));
}

void ArcBase::AttributeNames(Csh &csh)
{
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "refname",
        "Name this element so that it can be referred to (e.g., from a label of another element via the '\\r(name)' escape sequence).",
        EHintType::ATTR_NAME));
    AttributeNamesSet1(csh);
    MscElement::AttributeNames(csh);
}

/** Adds attribute values for attributes valid for all arcs as hints. */
bool ArcBase::AttributeValuesSet1(std::string_view attr, Csh &csh)
{
    if (CaseInsensitiveEqual(attr, "compress")||
        CaseInsensitiveEqual(attr, "parallel") ||
        CaseInsensitiveEqual(attr, "overlay") ||
        CaseInsensitiveEqual(attr, "keep_together") ||
        CaseInsensitiveEqual(attr, "keep_with_next")) {
        csh.AddYesNoToHints();
        return true;
    }
    if (CaseInsensitiveEqual(attr, "vspacing")) {
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<number>",
            "Specify extra spading above this element in pixels. 0 means no extra space.",
            EHintType::ATTR_VALUE, false));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE)+"compress",
            "Specifying 'compress' will auto-adjust vertical spacing to be as little as possible by moving the element up until it bumps into the ones above.",
            EHintType::ATTR_VALUE, true));
        return true;
    }
    return false;
}


bool ArcBase::AttributeValues(std::string_view attr, Csh &csh)
{
    if (CaseInsensitiveEqual(attr, "refname"))
        return true;
    if (AttributeValuesSet1(attr, csh)) return true;
    if (MscElement::AttributeValues(attr, csh)) return true;
    return false;
}

void ArcBase::AddEntityLineWidths(DistanceRequirements &vdist)
{
    for (const auto &e : chart->ActiveEntities)
        if (e->running_shown.IsActive()) {
            vdist.Insert(e->index, DISTANCE_LEFT, chart->activeEntitySize/2);
            vdist.Insert(e->index, DISTANCE_RIGHT, chart->activeEntitySize/2);
        }
}

ArcBase* ArcBase::PostParseProcess(Canvas &/*canvas*/, bool /*hide*/,
                                   EntityRef &/*left*/, EntityRef &/*right*/,
                                   Numbering &/*number*/, MscElement **target,
                                   ArcBase * /*byref_target*/)
{
    if (CanBeNoted()) *target = this;
    return this;
}

void ArcBase::Layout(Canvas &canvas, AreaList *cover)
{
    height = 0;
    LayoutComments(canvas, cover);
}

void ArcBase::PostPosProcess(Canvas &canvas, Chart *ch)
{
    _ASSERT(had_add_attr_list);
    if (valid)
        MscElement::PostPosProcess(canvas, ch); //also adds "this" to chart->AllArcs
    else if (!file_pos.IsInvalid())
        chart->AllElements[file_pos] = this; //Do this even if we are invalid
}



Indicator::Indicator(MscChart *chart, const MscStyleCoW &st, const FileLineColRange &l) :
    ArcBase(EArcCategory::INDICATOR, chart), style(st), src(nullptr), dst(nullptr)
{
    SetLineEnd(l);
    AddAttributeList(nullptr);
}

Indicator::Indicator(MscChart *chart, EntityRef s, const MscStyleCoW &st, const FileLineColRange &l) :
    ArcBase(EArcCategory::INDICATOR, chart), style(st), src(s), dst(s)
{
    SetLineEnd(l);
    AddAttributeList(nullptr);
}

bool Indicator::IsComplete() const
{
    return src && dst;
}

bool Indicator::Combine(const Indicator *o)
{
    if (!o || src != o->src || dst != o->dst) return false;
    ExtendFilePos(o);
    return true;
}

void Indicator::ExtendFilePos(const ArcBase* o) {
    if (!o) return;
    file_pos.start = std::min(file_pos.start, o->file_pos.start);
    file_pos.end = std::max(file_pos.end, o->file_pos.end);
}

double Indicator::GetXCoord() const
{
    return (chart->XCoord(src)+chart->XCoord(dst))/2;
}

EDirType Indicator::GetToucedEntities(class NEntityList &el) const
{
    if (!IsComplete()) return EDirType::INDETERMINATE;
    _ASSERT(!chart->IsVirtualEntity(src) && !chart->IsVirtualEntity(dst));
    el.push_back(src);
    if (src!=dst)
        el.push_back(dst);
    return EDirType::INDETERMINATE;
}

double Indicator::GetActSize(bool /*at_src*/) const
{
    return 0.0;
}

void Indicator::Width(Canvas &, DistanceRequirements &vdist)
{
    const double width = indicator_style.read().line.LineWidth() + indicator_size.x/2;
    //Add a new element to vdist
    vdist.AddElementTop(this);
    //Add activation status right away
    AddEntityLineWidths(vdist);
    vdist.InsertEntity(src);
    vdist.InsertEntity(dst);
    vdist.Insert(src->index, DISTANCE_LEFT, width);
    vdist.Insert(src->index, DISTANCE_RIGHT, width);
    //Add a new element to vdist
    vdist.AddElementBottom(this);
    //Add activation status right away
    AddEntityLineWidths(vdist);
    //if (src != dst) return; //no width requirements for Indicators not exactly on an entity
    ////If we are exactly on an entity line add left and right req for boxes potentially around us.
    //distances.InsertBoxSide(src->index-1, 0, width);
    //distances.InsertBoxSide(src->index,   width, 0);
}

EndConnection Indicator::GetConnection(bool is_src) const
{
    EndConnection ret(GetSidePos(is_src));
    ret.error_msg_no_join = nullptr;
    ret.is_src = is_src;
    ret.body_is_left = !is_src;
    ret.accepts_curly_join = true;
    ret.can_curly_join = false;
    ret.is_unspecified = false;
    ret.is_indicator = true;
    return ret;
}

ArrowEnding Indicator::GetSidePos(bool is_src) const
{
    //This is always supposed to be iterators into ActiveEntities...
    const double width = indicator_style.read().line.LineWidth() + indicator_size.x/2;
    if (is_src) return ArrowEnding(src, -width);
    else        return ArrowEnding(dst, width);
}

void Indicator::Layout(Canvas &canvas, AreaList *cover)
{
    const Block b = GetIndicatorCover(XY(GetXCoord(), yPos));
    area = b;
    area.mainline = Block(chart->GetDrawing().x, b.y);
    area_important = b;
    chart->NoteBlockers.Append(this);
    height = b.y.till + chart->boxVGapOutside;
    //TODO add shadow to cover
    if (cover)
        *cover = GetCover4Compress(area);
    LayoutComments(canvas, cover);
}

void Indicator::Draw(Canvas &canvas, EMscDrawPass pass) const
{
    if (pass!=draw_pass) return;
    const double x = (chart->XCoord(src) + chart->XCoord(dst))/2;
    DrawIndicator(XY(x, yPos), &canvas);
}

//////////////////////////////////////////////////////////////////////////////////////

/** The regular constructor.
 * Takes numbering style from the current context.
 * Merges the default text formatting style with `s`.*/
LabelledArc::LabelledArc(EArcCategory c, MscChart *msc) :
    ArcBase(c, msc), concrete_number(-1),
    numberingStyle(msc->MyCurrentContext().numberingStyle)
{
    entityLineRange.MakeInvalid();
}

/** Custom constructor to convert arcs.
 * This is only used to convert an Box to an BlockArrow in
 * BoxSeries::PostParseProcess (when the box is collapsed to a block arrow).
 * So we assume PostParseProcess() was not called for `al`, but will be called for `this`.*/
LabelledArc::LabelledArc(EArcCategory c, const LabelledArc &al)
    : ArcBase(c, al.chart), label(al.label), parsed_label(al.parsed_label),
    concrete_number(al.concrete_number), style(al.style), numberingStyle(al.numberingStyle)
{
    //Element members
    linenum_final = al.linenum_final;
    file_pos = al.file_pos;
    //other members, such as area and yPos and controls will happen only later

    //ArcBase members
    ArcBase::AddAttributeList(nullptr); //to kill error
    valid = true;
    vspacing = al.vspacing;
    parallel = al.parallel;
}

//set style to this name, but combine it with default text style
void LabelledArc::SetStyleWithText(const char *style_name, const MscStyleCoW *refinement)
{
    const MscStyleCoW *s = nullptr;
    if (style_name)
        s = (const MscStyleCoW*)&chart->MyCurrentContext().styles[style_name];
    SetStyleWithText(s, refinement);
}

//set style to this name, but combine it with default text style
void LabelledArc::SetStyleWithText(const MscStyleCoW *style_to_use, const MscStyleCoW *refinement)
{
    if (style_to_use)
        style = *style_to_use;
    //Make style.read().text complete using the default text formatting in the context as default
    StringFormat to_use(chart->MyCurrentContext().text);
    to_use += style.read().text;
    style.write().text = to_use;
    //Do the same for the tag.text attributes, if they are applicable
    if (style.read().f_tag) {
        to_use = chart->MyCurrentContext().text;
        to_use += style.read().tag_text;
        style.write().tag_text = to_use;
    }
    style.write().type = EStyleType::ELEMENT;
    //If style does not contain a numbering setting, apply the value of the
    //current chart option.
    if (!style.read().numbering)
        style.write().numbering = chart->MyCurrentContext().numbering;
    //Add refinement style (e.g., -> or ... or vertical++)
    if (refinement) style += *refinement;
}

void LabelledArc::OverflowWarning(double overflow, const string &msg, CEntityRef e1, CEntityRef e2)
{
    //e1 and e2 may be nullptr
    if (overflow < 1) return;
    string bigger_hscale;
    bigger_hscale << ceil(chart->GetHScale()+0.5);
    string msg2;
    if (msg.length()==0) {
        if (chart->GetHScale()<=0)
            msg2 = "Don't use 'hscale=auto'";
        else
            msg2 = "Try increasing 'hscale', e.g., 'hscale="+bigger_hscale+"'";
        if (e1 && e2 && (!chart->IsVirtualEntity(e1) || !chart->IsVirtualEntity(e2)) &&
            e1->name!=e2->name) {
            if (e1->pos_exp > e2->pos_exp)
                std::swap(e1, e2);
            msg2 << " or add space using 'hspace ";
            msg2 << (chart->IsVirtualEntity(e1) ? "" : e1->name);
            msg2 << "-";
            msg2 << (chart->IsVirtualEntity(e2) ? "" : e2->name);
            msg2 << "'";
        }
        msg2 << ".";
    }
    chart->Error.Warning(linenum_label,
        "Too little space for label - may look bad.",
        msg.length() ? msg : msg2);
};

void LabelledArc::CountOverflow(double space)
{
    _ASSERT(!parsed_label.IsWordWrap());
    chart->CountLabel(parsed_label.getTextWidthHeight().x > space + 1);
}


/** Perform first part of attribute list addition.
 * Thic just copies the list of attributes to 'style' and
 * records the file position of the label.
 * It is split from step 2, because it can work on an empty
 * 'style' member. Later descendants (ArDirArrow) need to have a copy of
 * just the attributes in style without the default attributes, like
 * the ones in the 'arrow' style. */
FileLineCol LabelledArc::AddAttributeListStep1(AttributeList *l)
{
    FileLineCol label_pos;
    if (!valid) {
        delete l;
        return label_pos;
    }
    //Find position of label attribute (if any), prepend it via an escape
    if (l)
        for (auto &pAttr : *l)
            if (pAttr->Is("label"))
                label_pos = pAttr->linenum_value.start;
    //Add attributes
    ArcBase::AddAttributeList(l);
    return label_pos;
}

/** Perform second step of attribute addition.
 * This one assumes that 'style' is complete and adjusts the label and
 * copies members stored in 'style' to members.*/
void LabelledArc::AddAttributeListStep2(const FileLineCol &label_pos)
{
    if (!valid) return;
    //vspacing went to the style, copy it
    if (style.read().vspacing)
        vspacing = *style.read().vspacing;
    //Then convert color and style names in labels
    if (label.length()>0) {
        //Add url, id and idurl
        //Here we assume that url and idurl does not contain closing parenthesis
        //as those have been replaced to ESCAPE_CHAR_CLOSING_PARA in AddAttribute()
        if (url.length()) {
            if (StringFormat::HasLinkEscapes(label.c_str()))
                chart->Error.Error(linenum_url_attr, "The label contains '\\L' escapes, ignoring 'url' attribute.",
                "Use only one of the 'url' attribute or '\\L' escapes.");
            else
                label.insert(0, "\\L("+url+")").append("\\L()");
        }
        if (id.length()) {
            if (idurl.length())
                label.append("\\^\\L(").append(idurl).append(")").append(id).append("\\L()");
            else
                label.append("\\^").append(id);
        } else if (idurl.length())
            chart->Error.Error(linenum_idurl_attr, "No 'id' attribute to attach the URL to. Ignoring attribute.");
        StringFormat basic = style.read().text;
        StringFormat::ExpandReferences(label, chart, label_pos, &basic,
                                       false, true, StringFormat::LABEL, true);
        //re-insert position, so that FinalizeLabels has one
        //XXX: label.insert(0, label_pos.Print());
    } else if (url.length())
        chart->Error.Error(linenum_url_attr, "No label. Ignoring 'url' attribute.");
}


bool LabelledArc::AddAttribute(const Attribute &a)
{
    if (a.type == EAttrType::STYLE) {
        style.write().AddAttribute(a, chart);
        return true;
    }
    string s;
    if (a.Is("label")) {
        if (!a.CheckType(EAttrType::STRING, chart->Error)) return true;
        //EAttrType::CLEAR is OK above with value = ""
        label = a.value;
        linenum_label = a.linenum_value.start;
        return true;
    }
    if (a.Is("number")) {
        if (a.type == EAttrType::NUMBER) {
            if (a.number >= 0) {
                concrete_number = int(a.number);
                style.write().numbering = true;
            } else
                chart->Error.Error(a, true, "Value for 'number' must not be negative. Ignoring attribute.");
            return true;
        }
        if (a.type == EAttrType::BOOL) {
            style.write().numbering = a.yes;
            return true;
        }
        if (a.type == EAttrType::CLEAR) { //turn off numbering
            style.write().numbering = false;
            return true;
        }
        //We have a string as number - it may be a roman number or abc
        int num;
        unsigned off = chart->MyCurrentContext().numberingStyle.Last().Input(a.value, num);
        //off is how many characters we could not understand at the end of a.value
        if (off == a.value.length()) {
            //No characters understood
            chart->Error.Error(a, true, "Value for 'number' must be 'yes', 'no' or a number. Ignoring attribute.");
            return true;
        }
        if (off > 0) {
            FileLineCol l(a.linenum_value.start);
            l.col += (unsigned)a.value.length() - off;
            chart->Error.Warning(l, "I could not understand number from here. Applying only '" +
                                 a.value.substr(0, a.value.length() - off) + "'.");
        }
        concrete_number = num;
        style.write().numbering = true;
        return true;
    }
    if (a.Is("url")) {
        if (!a.CheckType(EAttrType::STRING, chart->Error)) return true;
        //EAttrType::CLEAR is OK above with value = ""
        url = a.value;
        //replace closing parenthesis to ESCAPE_CHAR_CLOSING_PARA
        std::replace(url.begin(), url.end(), ')', ESCAPE_CHAR_CLOSING_PARA);
        linenum_url_attr = a.linenum_attr.start;
        return true;
    }
    if (a.Is("id")) {
        chart->Error.WarnMscgenAttr(a, false, "\\^ (inside a label)");
        id = a.value;
        return true;
    }
    if (a.Is("idurl")) {
        chart->Error.WarnMscgenAttr(a, false, "\\L() (inside a label)");
        if (!a.CheckType(EAttrType::STRING, chart->Error)) return true;
        //EAttrType::CLEAR is OK above with value = ""
        idurl = a.value;
        //replace closing parenthesis to ESCAPE_CHAR_CLOSING_PARA
        std::replace(idurl.begin(), idurl.end(), ')', ESCAPE_CHAR_CLOSING_PARA);
        linenum_idurl_attr = a.linenum_attr.start;
        return true;
    }
    if (a.Is("draw_time")) {
        if (!a.EnsureNotClear(chart->Error, EStyleType::ELEMENT)) return true;
        if (a.type == EAttrType::STRING && Convert(a.value, draw_pass)) return true;
        a.InvalidValueError(CandidatesFor(draw_pass), chart->Error);
        return true;
    }
    if (style.write().AddAttribute(a, chart))
        return true;
    if (ArcBase::AddAttribute(a)) return true;
    return false;
}

void LabelledArc::AttributeNames(Csh &csh)
{
    ArcBase::AttributeNames(csh);
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "label",
        "Specify the text of the label of this element.",
        EHintType::ATTR_NAME));
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "number",
        "Turn auto-numberin on or off. You can also give a specific number to use with auto-numbering of this element.",
        EHintType::ATTR_NAME));
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "refname",
        "Use this attribute to assign a name to this element, which can then be later used in a '\\r' text formatting escape to refer to the number of this element.",
        EHintType::ATTR_NAME));
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "url",
        "Turn the whole label to a link targeting e.g., an URL or an element documented by Doxygen (when using Doxygen integration). "
        "Use the '\\L' formatting escape if you want to use only part of a label as a link.",
        EHintType::ATTR_NAME));
    csh.AddStylesToHints(false, false);
}

bool LabelledArc::AttributeValues(std::string_view attr, Csh &csh)
{
    if (CaseInsensitiveEqual(attr,"label") || CaseInsensitiveEqual(attr,"refname") || CaseInsensitiveEqual(attr,"url")) {
        return true;
    }
    if (CaseInsensitiveEqual(attr,"number")) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE)+"yes",
            "Turn auto-numbering on. (May already be turned on via a style or chart option.)",
            EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForYesNo, CshHintGraphicParam(1)));
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRVALUE)+"no",
            "Turn auto-numbering off. (May already be turned off via a style, chart option or by default.)",
            EHintType::ATTR_VALUE, true, CshHintGraphicCallbackForYesNo, CshHintGraphicParam(0)));
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<number>",
            "Specify a number to use for this element. Auto-numbering will continue from this value.",
            EHintType::ATTR_VALUE, false));
        return true;
    }
    if (CaseInsensitiveEqual(attr,"draw_time"))
        return MscElement::AttributeValues(attr, csh);
    if (ArcBase::AttributeValues(attr, csh)) return true;
    return false;
}

//This assigns a running number to the label and
//fills the "vspacing" member from the style.read().
//Strictly to be called by descendants
ArcBase *LabelledArc::PostParseProcess(Canvas &canvas, bool hide, EntityRef &left, EntityRef &right,
                                       Numbering& number, MscElement** target, ArcBase* byref_target)
{
    if (!valid) return nullptr;
    //We do everything here even if we are hidden (numbering is not impacted by hide/show or collapse/expand)
    if (label.length()!=0 && *style.read().numbering) {
        number.SetSize((unsigned)numberingStyle.Size()); //append 1s if style has more levels
        if (concrete_number >= 0)
            number.Last() = concrete_number;
        number_text = numberingStyle.Print(number);
        //Now remove escapes from the number text (if any)
        basic_format = style.read().text;
        //At this point the number text must be processed using StringFormat::ExpandReferences
        //to expand remaining empty \c(), \s(), etc escapes.
        //We use a dummy linenum, as we should not get ANY errors here...
        StringFormat::ExpandReferences(number_text, chart, FileLineCol(), &basic_format,
                                       false, true, StringFormat::LABEL, true);
        //Recreate the text style at the point where the label will be inserted
        basic_format.Apply(std::string_view(label));
        //insert us among the references if we have any name
        if (refname.length())
            chart->ReferenceNames[refname].number_text = number_text;
        number.increment(numberingStyle.Last().increment);
    }
    //find or create detailed arrowhead structure
    if (style.read().f_arrow!=EArcArrowType::NONE) {
        _ASSERT(style.read().f_arrow==EArcArrowType::ARROW || style.read().f_arrow==EArcArrowType::BIGARROW);
        style.read().arrow.read().CreateArrowHeads();
    }
    return ArcBase::PostParseProcess(canvas, hide, left, right, number, target, byref_target);
}

void LabelledArc::FinalizeLabels(Canvas &canvas)
{
    ArcBase::FinalizeLabels(canvas);
    if (label.length()==0) return;
    string pre_num_post;
    if (label.length()!=0 && *style.read().numbering)
        pre_num_post = *numberingStyle.pre + number_text + *numberingStyle.post;
    //We add empty num and pre_num_post if numbering is turned off, to remove \N escapes
    StringFormat::AddNumbering(label, number_text, pre_num_post);
    //Next we add reference numbers to labels, and also kill off any \s or the like
    //escapes that came with numbers in pre_num_post
    //We can start with a dummy pos, since the label's pos is prepended
    //during AddAttributeList. Note that with this calling
    //(references parameter true), ExpandReferences will only emit errors
    //to missing references - all other errors were already emitted in
    //the call in AddAttributeList()
    StringFormat::ExpandReferences(label, chart, FileLineCol(),
                                   &basic_format, true, true, StringFormat::LABEL, true);
    parsed_label.Set(label, canvas, chart->Shapes, style.read().text);
}

void LabelledArc::PostPosProcess(Canvas &canvas, Chart *ch)
{
	//If there is a vline or vfill in the current style, add that to entitylines
    if ((style.read().f_vline &&(style.read().vline.width || style.read().vline.type || style.read().vline.color)) ||
        (style.read().f_vfill && !style.read().vfill.IsEmpty()))
        if (!entityLineRange.IsInvalid()) {
		    MscStyleCoW toadd;
            if (style.read().f_vline) toadd.write().vline = style.read().vline;
            if (style.read().f_vfill) toadd.write().vfill = style.read().vfill;
            for(auto &pEntity : chart->ActiveEntities)
                if (!chart->IsVirtualEntity(pEntity))
                    pEntity->status.ApplyStyle(entityLineRange, toadd);
        }
    ArcBase::PostPosProcess(canvas, ch);
}

/** Draw a loss symbol. 'C' is center, 'size' is total width and height.*/
void LabelledArc::DrawLSym(Canvas &canvas, const XY &C, XY size, const LineAttr &line) const
{
    const cairo_line_cap_t before = canvas.SetLineCap(CAIRO_LINE_CAP_ROUND);
    if (canvas.does_graphics())
        canvas.Line(C - size/2, C + size/2, line);
    else
        canvas.Add(GSPath({C - size/2, C + size/2}, line));
    size.x *= -1;
    if (canvas.does_graphics())
        canvas.Line(C - size/2, C + size/2, line);
    else
        canvas.Add(GSPath({C - size/2, C + size/2}, line));
    canvas.SetLineCap(before);
}

/////////////////////////////////////////////////////

ArrowEnding::ArrowEnding(MscChart & chart, std::string_view e, const FileLineColRange & l, double o) :
    entity(chart.FindAllocEntity(e, l)), linenum(l.start), offset(o)
{
}

/** Return the refinement style for an arrow symbol. */
const MscStyleCoW *Arrow::GetRefinementStyle4ArrowSymbol(EArcSymbol t) const
{
    //refinement for normal (non-block) arrows
    switch (t) {
    case EArcSymbol::ARC_SOLID:
    case EArcSymbol::ARC_SOLID_BIDIR:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["->"];
    case EArcSymbol::ARC_DOTTED:
    case EArcSymbol::ARC_DOTTED_BIDIR:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles[">"];
    case EArcSymbol::ARC_DASHED:
    case EArcSymbol::ARC_DASHED_BIDIR:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles[">>"];
    case EArcSymbol::ARC_DOUBLE:
    case EArcSymbol::ARC_DOUBLE_BIDIR:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["=>"];
    case EArcSymbol::ARC_DBLDBL:
    case EArcSymbol::ARC_DBLDBL_BIDIR:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["=>>"];
    case EArcSymbol::ARC_DOUBLE2:
    case EArcSymbol::ARC_DOUBLE2_BIDIR:
        if (chart->mscgen_compat == EMscgenCompat::FORCE_MSCGEN)
            return (MscStyleCoW*)&chart->MyCurrentContext().styles[":>"];
        else
            return (MscStyleCoW*)&chart->MyCurrentContext().styles["==>"];
    default:
        _ASSERT(0);
        FALLTHROUGH;
    case EArcSymbol::ARC_UNDETERMINED_SEGMENT:
        return nullptr; /*Do nothing */
    };
}

bool Arrow::StoreNoteTemporarily(Note* cn) {
    cn->SetTarget(this);
    tmp_stored_notes.emplace_back(std::unique_ptr<Note>(cn), "");
    return true;
}

void Arrow::ReinsertTmpStoredNotes(ArcList& list, ArcList::iterator after) {
    ArcList::iterator before = std::next(after);
    for (auto& [pNote, _] : tmp_stored_notes)
        list.insert(before, std::move(pNote));
    tmp_stored_notes.clear();
}

bool Arrow::AddAttribute(const Attribute &a)
{
    if (a.Is("angle")) {
        if (dynamic_cast<SelfArrow*>(this))
            chart->Error.Warning(a, false, "Arrows pointing to the same entity cannot have an 'angle' attribute. Ignoring it.");
        else if (dynamic_cast<Vertical*>(this))
            chart->Error.Warning(a, false, "Verticals cannot have an 'angle' attribute. Ignoring it.");
        return true;
    }
    return LabelledArc::AddAttribute(a);
}


void Arrow::AttributeNames(Csh &csh)
{
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "angle",
        "Make this arrow slanted by this degree.",
        EHintType::ATTR_NAME));
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "slant_depth",
        "Make this arrow slanted such that the end of it is this many pixels below the start of it. (An alternative way to specify angle.)",
        EHintType::ATTR_NAME));
    //add side here to have a better description than the one in the style
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"side",
        "For arrows starting and ending at the same entity use this to specify which side of the entity line the arrow ends up at.",
        EHintType::ATTR_NAME));
    LabelledArc::AttributeNames(csh);
    csh.AttributeNamesForStyle("arrow");
}

bool Arrow::AttributeValues(std::string_view attr, Csh &csh)
{
    if (CaseInsensitiveEqual(attr,"angle")) {
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<number 0..45 [degrees]>",
            "Make this arrow slanted by this degree.",
            EHintType::ATTR_VALUE, false));
        return true;
    }
    if (CaseInsensitiveEqual(attr, "slant_depth")) {
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable() + "<number 0..1000 [pixels]>",
            "Make arrows slanted such that the end of it is this many pixels below the start of it. (An alternative way to specify angle.)",
            EHintType::ATTR_VALUE, false));
        return true;
    }
    //side is handled by the style
    if (csh.AttributeValuesForStyle(attr, "arrow")) return true;
    if (LabelledArc::AttributeValues(attr, csh)) return true;
    return false;
}

void Arrow::PostPosProcess(Canvas &canvas, Chart *ch)
{
    if (!valid) return;
    entityLineRange = area.GetBoundingBox().y;
    LabelledArc::PostPosProcess(canvas, ch);
}

//////////////////////////////////////////////////////////////////////////////////////

SelfArrow::SelfArrow(EArcSymbol t, std::string_view s, const FileLineColRange &sl,
                     MscChart* msc, double ys) :
    Arrow(IsArcSymbolBidir(t), EArcCategory::SELF_ARROW, msc),
    YSize(ys), XSizeUnit(0.375), user_specified_side(false), type(t)
{
    _ASSERT(IsArcSymbolArrow(t));
    src = chart->FindAllocEntity(s, sl);
}

Arrow *SelfArrow::AddSegment(ArrowSegmentData /*data*/, std::string_view /*m*/,
                             const FileLineColRange &/*ml*/, const FileLineColRange &l)
{
    if (!valid) return this; //display error only once
    chart->Error.Error(l.start, "Cannot add further segments to an arrow pointing to the same entity. Ignoring arrow.");
    valid = false;
    return this;
}

Arrow *SelfArrow::AddLostPos(gsl::owner<VertXPos*> pos, const FileLineColRange &l)
{
    chart->Error.Error(l.start, "Cannot add a loss position to an arrow pointing to the same entity. Ignoring 'at' clause.");
    delete pos;
    return this;
}

Arrow *SelfArrow::AddStartPos(ArrowEnding *ending)
{
    if (ending) {
        chart->Error.Error(ending->linenum, "Cannot add a starting position to an arrow pointing to the same entity. Ignoring 'start before' clause.");
        delete ending;
    }
    return this;
}

Arrow *SelfArrow::AddEndPos(ArrowEnding *ending)
{
    if (ending) {
        chart->Error.Error(ending->linenum, "Cannot add an ending position to an arrow pointing to the same entity. Ignoring 'end after' clause.");
        delete ending;
    }
    return this;
}

bool SelfArrow::AddAttribute(const Attribute &a)
{
    //when setting side also adjust text identation
    if (a.Is("side")) {
        ESide side;
        if (a.type == EAttrType::STRING && Convert(a.value, side))
        switch (side) {
        case ESide::LEFT: style.write().text.Apply("\\pl"); break;
        case ESide::RIGHT: style.write().text.Apply("\\pr"); break;
        default: break;
        }
        //fallthrough to set actual side in style below
    }
    return Arrow::AddAttribute(a);
}

EDirType SelfArrow::GetToucedEntities(NEntityList &el) const
{
    _ASSERT(!chart->IsVirtualEntity(src));
    el.push_back(src);
    return EDirType::INDETERMINATE;
}

void SelfArrow::SetStyleBeforeAttributes(AttributeList *)
{
    const MscStyleCoW *refinement = GetRefinementStyle4ArrowSymbol(type);
    MscStyleCoW ref;
    ref.write().text.Apply("\\pr");
    if (src->parse_running_style.read().f_alt) {
        ref.write().line += src->parse_running_style.read().aline;
        ref.write().text += src->parse_running_style.read().atext;
    }
    if (src->parse_running_style.read().f_arrow != EArcArrowType::NONE)
        ref.write().arrow += src->parse_running_style.read().arrow;
    ref += chart->MyCurrentContext().styles["arrow_self"];
    if (refinement) ref += *refinement;
    if (headless_mscgen_arrow) {
        ref.write().arrow.write().SetType(EArrowEnd::END, EArrowType::NONE);
        ref.write().arrow.write().SetType(EArrowEnd::START, EArrowType::NONE);
        ref.write().arrow.write().SetType(EArrowEnd::MIDDLE, EArrowType::NONE);
        ref.write().arrow.write().SetType(EArrowEnd::SKIP, EArrowType::NONE);
    }
    SetStyleWithText("arrow", &ref);
}


ArcBase* SelfArrow::PostParseProcess(Canvas &canvas, bool hide, EntityRef &left, EntityRef &right,
                                        Numbering &number, MscElement **target, ArcBase *byref_target)
{
    if (!valid) return nullptr;
    if (chart->ErrorIfEntityGrouped(src, file_pos.start)) return nullptr;

    //Add numbering, if needed
    LabelledArc::PostParseProcess(canvas, hide, left, right, number, target, byref_target);

    const EntityRef substitute = chart->FindActiveParentEntity(src);
    const bool we_disappear = src != substitute; //src is not visible -> we disappear, too
    if (we_disappear && !*substitute->running_style.read().indicator)
        return nullptr;

    //Even if we disappear or shall be hidden, we update left/right
    left = chart->EntityMinByPos(left, substitute);
    right = chart->EntityMaxByPos(right, substitute);
    src = substitute;
    _ASSERT(src);

    if (hide) return nullptr;
    if (we_disappear) //now indicator must be true, see above
        return new Indicator(chart, src, indicator_style, file_pos);
    //We do not disappear and need not hide
    src_act = std::max(0., src->GetRunningWidth(chart->activeEntitySize)/2);
    return this;
}

EndConnection SelfArrow::GetConnection(bool is_src) const
{
    EndConnection ret(GetSidePos(is_src));
    ret.error_msg_no_join = nullptr;
    ret.is_src = is_src;
    ret.body_is_left = style.read().side==ESide::LEFT;
    ret.accepts_curly_join = !is_src; //we only accept at our destination
    ret.can_curly_join = false;
    ret.is_unspecified = false;
    ret.is_indicator = false;
    return ret;
}

void SelfArrow::Width(Canvas &canvas, DistanceRequirements &vdist)
{
    if (!valid) return;
    //Add a new element to vdist
    vdist.AddElementTop(this);
    //Add activation status right away
    AddEntityLineWidths(vdist);
    vdist.InsertEntity(src);
    if (canvas.HasImprecisePositioning()) src_act = floor(src_act);
    const double arrow = chart->XCoord(XSizeUnit)+src_act+style.read().line.LineWidth()/2;
    const double text = parsed_label.getSpaceRequired() + src_act;
    vdist.Insert(src->index, style.read().side == ESide::RIGHT ? DISTANCE_RIGHT : DISTANCE_LEFT, arrow);
    vdist.Insert(src->index, style.read().side == ESide::RIGHT ? DISTANCE_LEFT : DISTANCE_RIGHT, text);
    //Add a new element to vdist
    vdist.AddElementBottom(this);
    //Add activation status right away
    AddEntityLineWidths(vdist);
}

void SelfArrow::Layout(Canvas &canvas, AreaList *cover)
{
    height = 0;
    if (!valid) return;
    yPos = 0;
    src_height = style.read().arrow.read().getHeight(bidir, EArrowEnd::START, style.read().line, style.read().line);
    dst_height = style.read().arrow.read().getHeight(bidir, EArrowEnd::END, style.read().line, style.read().line);
    src_height = ceil(src_height);
    dst_height = ceil(dst_height);
    xPos = dx = chart->XCoord(src);
    if (style.read().side == ESide::RIGHT) {
        xPos += src_act;
        dx -= src_act;
    } else {
        xPos -= src_act;
        dx += src_act;
    }
    if (parsed_label.IsWordWrap()) {
        //find entity left or right of `src` that is on
        auto entity = chart->ActiveEntities.Find_by_Ptr(src);
        style.read().side == ESide::RIGHT ? entity-- : entity++;
        while (*entity != chart->LSide && *entity != chart->RSide && !(*entity)->running_shown.IsOn())
            style.read().side == ESide::RIGHT ? entity-- : entity++;
        //at worst we are at LSide/RSide
        sx = chart->XCoord(*entity);
        if ((*entity)->status.GetStatus(yPos).IsActive())
            style.read().side == ESide::RIGHT ? sx += chart->activeEntitySize/2 : sx -= chart->activeEntitySize/2;
        if (sx>dx)
            std::swap(sx, dx);
        _ASSERT(dx - sx > 0);
        const double overflow = parsed_label.Reflow(canvas, chart->Shapes, dx - sx);
        OverflowWarning(overflow, "", *entity, src);
    } else {
        sx = chart->XCoord(style.read().side == ESide::RIGHT ? chart->LNote->pos : chart->RNote->pos);
        CountOverflow(dx - sx);
    }
    if (dx<sx) std::swap(sx, dx);

    //Autoscale y size, to be large enough for the text.
    const double th = parsed_label.getTextWidthHeight().y;
    if (src_height + dst_height + 2*YSize < th)
        YSize = (th-src_height-dst_height)/2;
    wh.x = ceil(chart->XCoord(XSizeUnit));
    wh.y = ceil(2*YSize);

    double y = chart->arcVGapAbove;
    clip_area = style.read().arrow.read().ClipForLine(XY(xPos, y+src_height), 0/*src_act already in xPos*/, style.read().side==ESide::RIGHT, bidir,
                                        EArrowEnd::START, style.read().line, style.read().line);
    clip_area+= style.read().arrow.read().ClipForLine(XY(xPos, y+src_height+wh.y), 0/*src_act already in xPos*/, style.read().side==ESide::LEFT, bidir,
                                        EArrowEnd::END, style.read().line, style.read().line);
    area = parsed_label.Cover(chart->Shapes, sx, dx, y);
    area.arc = this;
    area_important = area;
    const Block arrow_box(style.read().side == ESide::RIGHT ? xPos : floor(xPos-wh.x),
                          style.read().side == ESide::RIGHT ? ceil(xPos+wh.x) : xPos,
                          y, ceil(y+src_height+wh.y+dst_height));
    area += arrow_box;
    area.mainline = Block(chart->GetDrawing().x, Range(y + src_height- chart->nudgeSize/2, y + src_height + wh.y + chart->nudgeSize/2));
    //Now add arrowheads to the "area_important", and a small block if they are NONE
    XY point = XY(xPos, y + src_height);
    if (style.read().arrow.read().GetArrowHead(bidir, EArrowEnd::START).IsNone())
        area_important += Block(point.x-chart->compressGap/2, point.x+chart->compressGap/2,
                                point.y-chart->compressGap/2, point.y+chart->compressGap/2);
    else
        area_important += style.read().arrow.read().Cover(point, 0, style.read().side==ESide::RIGHT,  bidir, EArrowEnd::START, style.read().line, style.read().line);
    point.y += 2*YSize;
    if (style.read().arrow.read().GetArrowHead(bidir, EArrowEnd::END).IsNone())
        area_important += Block(point.x-chart->compressGap/2, point.x+chart->compressGap/2,
                                point.y-chart->compressGap/2, point.y+chart->compressGap/2);
    else
        area_important += style.read().arrow.read().Cover(point, 0, style.read().side==ESide::LEFT, bidir, EArrowEnd::END, style.read().line, style.read().line);
    chart->NoteBlockers.Append(this);
    height = area.GetBoundingBox().y.till + chart->arcVGapBelow;
    if (cover)
        *cover = GetCover4Compress(area);
    LayoutComments(canvas, cover);
}

void SelfArrow::PostPosProcess(Canvas &canvas, Chart *ch)
{
    if (!valid) return;
    Arrow::PostPosProcess(canvas, ch);

    //Check if the entity involved is actually turned on.
    if (!src->status.GetStatus(yPos).IsOn()) {
        string sss;
        sss << "Entity '" << src->name << "' is";
        sss << " turned off, but referenced here.";
        chart->Error.Warning(file_pos.start, sss, "It will look strange.");
    }
}

void SelfArrow::RegisterLabels()
{
    chart->RegisterLabel(parsed_label, LabelInfo::ARROW, sx, dx, yPos+chart->arcVGapAbove);
}

void SelfArrow::CollectIsMapElements(Canvas &canvas)
{
    parsed_label.CollectIsMapElements(chart->ismapData, canvas, chart->Shapes, sx, dx, yPos+chart->arcVGapAbove);
}

void SelfArrow::Draw(Canvas &canvas, EMscDrawPass pass) const
{
    if (!valid) return;
    if (pass!=draw_pass) return;
    double y = yPos + chart->arcVGapAbove;

    parsed_label.Draw(canvas, chart->Shapes, sx, dx, y);
    y += src_height;

    if (canvas.does_graphics())
        canvas.ClipInverse(clip_area);
    if (style.read().line.radius < 0) {
        //draw an arc
        Contour curve(XY(xPos, y+YSize), wh.x, wh.y/2, 0,
                      style.read().side==ESide::RIGHT ? 270 : 90,
                      style.read().side==ESide::RIGHT ? 90: 270);
        curve.front().Outline().back().SetVisible(false); //last edge is the linear segment closing it - do not draw
        if (canvas.does_graphics())
            canvas.Line(curve, style.read().line);
        else {
            Path &&path = std::move(curve).front().Outline().GetEdges();
            path.Cycle(Edge::Update::set_visible()); //cycle such that the first visible edge is the first in the path
            if (style.read().side==ESide::LEFT) path.Invert();
            //std::erase_if(path, [](const Edge &e) { return !e.IsVisible(); });
            canvas.Add(GSPath(path, style.read().line,
                              &style.read().arrow.read().GetArrowHead(false, bidir, EArrowEnd::END,   false),
                              &style.read().arrow.read().GetArrowHead(false, bidir, EArrowEnd::START, true)));
        }
    } else {
        //draw (part of) a rounded rectangle
        const Range X(style.read().side==ESide::RIGHT ? xPos : chart->GetDrawing().x.from,
                      style.read().side==ESide::RIGHT ? chart->GetDrawing().x.till : xPos);
        const Block clip(X, chart->GetDrawing().y);
        const Block line = style.read().side==ESide::RIGHT
            ? Block(XY(0, y), XY(xPos, y)+wh)
            : Block(XY(xPos-wh.x, y), XY(chart->GetDrawing().x.till, y+wh.y));
        if (canvas.does_graphics()) {
            canvas.Clip(clip);
            canvas.Line(line, style.read().line);
            canvas.UnClip();
        } else {
            Path path = style.read().line.CreateRectangle_Midline(line);
            path.ClipRemove(clip);
            if (style.read().side==ESide::LEFT) path.Invert();
            canvas.Add(GSPath(path, style.read().line,
                              &style.read().arrow.read().GetArrowHead(false, bidir, EArrowEnd::END,   false),
                              &style.read().arrow.read().GetArrowHead(false, bidir, EArrowEnd::START, true)));
        }
    }
    if (canvas.does_graphics()) {
        canvas.UnClip();
        //draw arrowheads
        style.read().arrow.read().Draw(canvas, XY(xPos, y), 0, style.read().side==ESide::RIGHT, bidir, EArrowEnd::START, style.read().line, style.read().line);
        style.read().arrow.read().Draw(canvas, XY(xPos, y+2*YSize), 0, style.read().side==ESide::LEFT, bidir, EArrowEnd::END, style.read().line, style.read().line);
    }
}

//////////////////////////////////////////////////////////////////////////////////////

/** Regular constructor, does pretty much nothing. We need to add segments.
* @param data The type of the arrow and the position of any loss indication (*).
* @param s The name of the source entity.
* @param sl The location of the name of the source entity in the input file
* @param d The name of the destination entity
* @param dl The location of the name of the destination entity in the input file
* @param msc The char we add the arrow to
* @param fw True if declared as a->b and false if b<-a*/
DirArrow::DirArrow(ArrowSegmentData data,
                   std::string_view s, const FileLineColRange &sl,
                   std::string_view d, const FileLineColRange &dl,
                   MscChart *msc, bool fw) :
    Arrow(IsArcSymbolBidir(data.type), EArcCategory::DIR_ARROW, msc),
    src(*chart, s, sl), dst(*chart, d, dl),
    specified_as_forward(fw)
{
    //We assume 'a->' type arrows arrive here with d==RSIDE_ENT_STR, etc.
    //so s & d is not nullptr or empty
    //Arrows 'a->|' arrive here with RSIDE_ENT_STR, ect., but with d_is_pipe set
    //During proc parsing it is allowed to have "" as entity name
    _ASSERT((s.size() && d.size()) || (chart && chart->SkipContent()));
    _ASSERT(IsArcSymbolArrow(data.type));
    segment_types.push_back(data.type);
    if (chart) {
        slant_angle = chart->MyCurrentContext().slant_angle.value_or(0);
        slant_depth = chart->MyCurrentContext().slant_depth.value_or(0);
    }
    if (data.lost==EArrowLost::AT_SRC) {
        lost_at = -1;
        lost_is_forward = true;
        linenum_asterisk = data.lost_pos.start;
    } else if (data.lost==EArrowLost::AT_DST) {
        lost_at = 0;
        lost_is_forward = false;
        linenum_asterisk = data.lost_pos.start;
    }
};

DirArrow::DirArrow(const NEntityList &el, bool b, EArcSymbol boxsymbol, const LabelledArc &al) :
    Arrow(b, EArcCategory::BLOCK_ARROW, al),
    src(el.front()),
    dst(el.back())
{
    _ASSERT(IsArcSymbolBox(boxsymbol));
    EArcSymbol arrowsymbol = ConvertBoxSymbol2ArrowSymbol(boxsymbol, bidir);
    segment_types.push_back(arrowsymbol);
    for (auto i = ++el.begin(); i!=--el.end(); i++) {
        middle.push_back(*i);
        linenum_middle.emplace_back();
        segment_types.push_back(arrowsymbol);
        //segment_lines will be filled in AddAttributeList()
    }
    if (chart) {
        slant_angle = *chart->MyCurrentContext().slant_angle;
        slant_depth= *chart->MyCurrentContext().slant_depth;
    }
}

Arrow * DirArrow::AddSegment(ArrowSegmentData data, std::string_view m, const FileLineColRange &ml,
                             const FileLineColRange &/*l*/)
{
    if (!valid) return this;
    //Handle special ending specified via "a->|" signalled by m == RSIDE_PIPE_ENT_STR
    bool special_ending = m==RSIDE_PIPE_ENT_STR || m==LSIDE_PIPE_ENT_STR;
    if (special_ending) m = {};
    EntityRef mid=nullptr;
    if (m.empty()) {
        if ((src.entity->pos < dst.entity->pos) ^ specified_as_forward)
            // b->a-> and b<-a<-
            mid = chart->FindAllocEntity(LSIDE_ENT_STR, ml);
        else
            // a->b-> and a<-b<-
            mid = chart->FindAllocEntity(RSIDE_ENT_STR, ml);
    } else
        mid = chart->FindAllocEntity(m, ml);
    _ASSERT(mid || chart->SkipContent());
    if (specified_as_forward) {
        //check for this situation: ->b->a (where a is left of b)
        if (middle.size()==0 && src.entity == chart->LSide &&
            dst.entity->pos > mid->pos && !m.empty())
            src.entity = chart->FindAllocEntity(RSIDE_ENT_STR, ml);
        middle.push_back(dst.entity);
        linenum_middle.push_back(dst.linenum);
        dst.entity = mid;
        dst.linenum = ml.start;
        dst.offset = 0;
        if (data.type == EArcSymbol::ARC_UNDETERMINED_SEGMENT)
            data.type = segment_types.front();
        segment_types.push_back(data.type);
    } else {
        //check for this situation: <-b<-a (where a is left of b)
        if (middle.size()==0 && dst.entity == chart->LSide &&
            src.entity->pos > mid->pos && !m.empty())
            dst.entity = chart->FindAllocEntity(RSIDE_ENT_STR, ml);
        middle.insert(middle.begin(), src.entity);
        linenum_middle.insert(linenum_middle.begin(), src.linenum);
        src.entity = mid;
        src.linenum = ml.start;
        src.offset = 0;
        if (data.type == EArcSymbol::ARC_UNDETERMINED_SEGMENT)
            data.type = segment_types.back();
        segment_types.insert(segment_types.begin(), data.type);
        //if we have specified a message lost at an entity,
        //we maintain the correct index
        if (lost_at > -2)
            lost_at++;
    };

    //Error if we already have a loss specified
    if (data.lost!=EArrowLost::NOT && lost_at>-2) {
        chart->Error.Error(data.lost_pos.start, "This arrow is already specified as lost. Ignoring this asterisk.");
    } else if (data.lost==EArrowLost::AT_SRC) {
        lost_at = specified_as_forward ? middle.size()-1 : -1;
        lost_is_forward = true;
        linenum_asterisk = data.lost_pos.start;
    } else if (data.lost==EArrowLost::AT_DST) {
        lost_at = specified_as_forward ? middle.size() : 0;
        lost_is_forward = false;
        linenum_asterisk = data.lost_pos.start;
    }
    if (!special_ending) return this;
    if (specified_as_forward)
        AddEndPos();
    else
        AddStartPos();
    return this;
}

Arrow *DirArrow::AddLostPos(gsl::owner<VertXPos*> pos, const FileLineColRange &l)
{
    if (!pos || !pos->valid)
        chart->Error.Error(l.start, "The 'at' clause is invalid. Ignoring it.");
    else {
        lost_pos = *pos;
        linenum_lost_at = l.start;
    }
    delete pos;
    return this;
}

Arrow *DirArrow::AddStartPos(ArrowEnding *ending)
{
    if (ending) {
        src = *ending;
        src.offset += chart->GetDefaultSpecialEndingSize();
        delete ending;
    } else {
        src.offset = chart->GetDefaultSpecialEndingSize();
        src.entity = middle.size() ? middle.front() : dst.entity;
        src.linenum = middle.size() ? linenum_middle.front() : dst.linenum;
    }
    return this;
}

Arrow *DirArrow::AddEndPos(ArrowEnding *ending)
{
    if (ending) {
        dst = *ending;
        dst.offset += chart->GetDefaultSpecialEndingSize();
        delete ending;
    } else {
        dst.offset = chart->GetDefaultSpecialEndingSize();
        dst.entity = middle.size() ? middle.back() : src.entity;
        dst.linenum = middle.size() ? linenum_middle.back() : src.linenum;
    }
    return this;
}

/** Returns true if this is an mscgen broadcast arrow '->*'.
 * Valid only at and before PostParseProcess().*/
bool DirArrow::IsMscgenBroadcastArrow() const
{
    return chart->mscgen_compat == EMscgenCompat::FORCE_MSCGEN && //we are in compat mode
           middle.size()==0 && //single segment
           lost_at == 0 && //lost at 'dst'
           segment_types[0] == EArcSymbol::ARC_SOLID && //uses ->
           (dst.entity == chart->RSide || dst.entity == chart->LSide); //no dst entity was specifie
}


void DirArrow::SetStyleBeforeAttributes(AttributeList *)
{
    MscStyleCoW ref;
    if (src.entity->parse_running_style.read().f_alt) {
        ref.write().line += src.entity->parse_running_style.read().aline;
        ref.write().text += src.entity->parse_running_style.read().atext;
    }
    if (src.entity->parse_running_style.read().f_arrow != EArcArrowType::NONE)
        ref.write().arrow += src.entity->parse_running_style.read().arrow;
    SetStyleWithText("arrow", &ref);
}

//NOTE: This is not called for ArcBigArrows created when a box is collapsed to a block
//arrow!!! So do all required steps in the appropriate constructor of DirArrow
void DirArrow::AddAttributeList(gsl::owner<AttributeList*> l)
{
    //Save the style, empty it, collect all modifications and apply those to segments, too
    //This will work even if we are a BigArrow, as
    //SetStyleBeforeAttributes() and GetRefinementStyle4ArrowSymbol() is virtual
    SetStyleBeforeAttributes(l);
    MscStyleCoW save(style);
    style.write().Empty();
    const FileLineCol label_pos = Arrow::AddAttributeListStep1(l);
    //For broadcast arrows of mscgen change skip type to what is in
    // a) the attribute for end-type; or that is not set
    // b) what is in the refinement style for ARC_SOLID.
    if (IsMscgenBroadcastArrow()) {
        if (!style.read().arrow.read().GetType(EArrowEnd::SKIP)->has_value() &&
            !style.read().arrow.read().GetGvType(EArrowEnd::SKIP)->has_value()) {
            //if the user has not set skiptype attr, but has set endtype, use that as skiptype
            if (style.read().arrow.read().GetType(EArrowEnd::END)->has_value())
                save.write().arrow.write().SetType(EArrowEnd::SKIP, *style.read().arrow.read().GetType(EArrowEnd::END));
            else if (style.read().arrow.read().GetGvType(EArrowEnd::END)->has_value())
                save.write().arrow.write().SetGvType(EArrowEnd::SKIP, *style.read().arrow.read().GetGvType(EArrowEnd::END));
            else {
                //if the user set neither skiptype nor endtype...
                const MscStyleCoW * const refinement = GetRefinementStyle4ArrowSymbol(segment_types[0]);
                //...use the refinement style's endstyle (overwriting any skipstyle, since this is a broadcast arrow)
                if (refinement && refinement->read().arrow.read().GetType(EArrowEnd::END)
                                  ->value_or(EArrowType::INVALID)!=EArrowType::INVALID) {
                    save.write().arrow.write().SetType(EArrowEnd::SKIP, *refinement->read().arrow.read().GetType(EArrowEnd::END));
                } else if (refinement && refinement->read().arrow.read().GetGvType(EArrowEnd::END)->has_value()) {
                    save.write().arrow.write().SetGvType(EArrowEnd::SKIP, *refinement->read().arrow.read().GetGvType(EArrowEnd::END));
                } else {
                    //...or if no refinement style, use simply the default arrow endtype
                    save.write().arrow.write().SetType(EArrowEnd::SKIP, *save.read().arrow.read().GetType(EArrowEnd::END));
                    save.write().arrow.write().SetGvType(EArrowEnd::SKIP, *save.read().arrow.read().GetGvType(EArrowEnd::END));
                }
            }
        }
    }
    for (unsigned i = 0; i<segment_types.size(); i++) {
        segment_lines.push_back(save.read().line);
        const MscStyleCoW * const refinement = GetRefinementStyle4ArrowSymbol(segment_types[i]);
        if (refinement) {
            //Add the line type of the refinement style to each segment
            segment_lines.back() += refinement->read().line;
            //Add all other style elements of the refinement style to us
            save += *refinement;
        }
        segment_lines.back() += style.read().line;
    }
    if (headless_mscgen_arrow) {
        save.write().arrow.write().SetType(EArrowEnd::END, EArrowType::NONE);
        save.write().arrow.write().SetType(EArrowEnd::START, EArrowType::NONE);
        save.write().arrow.write().SetType(EArrowEnd::MIDDLE, EArrowType::NONE);
        save.write().arrow.write().SetType(EArrowEnd::SKIP, EArrowType::NONE);
    }
    save += style;
    style = save;
    AddAttributeListStep2(label_pos);

    //At this point we have done checking of all attributes
    //Exit if we parse a procedure as any entity may be nullptr
    if (chart->SkipContent()) return;

    //determine if we are -> or <- (left to right or vice versa)
    //if s and d are separate entity - it is straigforward to decide
    //other cases include "a->|", "a<-|" or "a-> end after a" - in this case always the user symbol wins
    if (src.entity!=dst.entity)
        is_forward = src.entity->pos_exp < dst.entity->pos_exp;
    else
        is_forward = specified_as_forward;

    //Up to now *_ending.offsets mean "larger value is more towards the end of the arrow"
    //Now we convert them to "positive value means to the left from the entity"
    //We do this anyway, even if *_ending.valid is false - then it does not matter anyway
    if (is_forward)
        src.offset *= -1;
    else
        dst.offset *= -1;
    //from this point on src and dst is comparable for left/right
    //We can have an erroneous A->B->A multi-arrow in which case we dont care
    _ASSERT(is_forward == (GetSidePos(true)<GetSidePos(false)) || src.entity==dst.entity);

    //Fill in original state of endpoints
    user_unspecified_ends.first = chart->IsVirtualEntity(src.entity);
    user_unspecified_ends.second= chart->IsVirtualEntity(dst.entity);
}

bool DirArrow::AddAttribute(const Attribute &a)
{
    if (a.Is("angle")) {
        if (!a.EnsureNotClear(chart->Error, EStyleType::ELEMENT)) return true;
        if (!a.CheckType(EAttrType::NUMBER, chart->Error)) return true;
        if (a.number<0 || a.number>45) {
            string x;
            if (a.number<0) x = "0";
            if (a.number>45) x = "45";
            chart->Error.Error(a, true, "Using " + x + " degrees instead of the specified value.",
                "The slant angle must be between 0 and 45 degrees.");
            if (a.number<0) slant_angle = 0;
            else if (a.number>45) slant_angle = 45;
        } else {
            slant_angle = a.number;
            slant_depth = 0;
        }
        return true;
    }
    if (a.Is("slant_depth")) {
        if (!a.EnsureNotClear(chart->Error, EStyleType::ELEMENT)) return true;
        if (!a.CheckType(EAttrType::NUMBER, chart->Error)) return true;
        if (a.number<0 || a.number>1000)
            chart->Error.Error(a, true, "Slant depth must be between 0 and 1000 pixels. Ignoring this attribute.");
        else {
            slant_angle = 0;
            slant_depth = a.number;
        }
        return true;
    }
    if (a.Is("arcskip")) {
        chart->Error.WarnMscgenAttr(a, true, "slant_depth");
        int v;
        if (a.type == EAttrType::STRING) {
            v = atoi(a.value.c_str());
            if (v==0 && (a.value.length()==0 || a.value[0]!='0'))
                v = -1;
        } else if (a.type == EAttrType::NUMBER)
            v = int(a.number);
        else
            v = -1;
        if (v<0) {
            chart->Error.Error(a, true, "Expecting a positive integer.");
            return true;
        }
        slant_angle = 0;
        slant_depth += a.number*20;
        return true;
    }
    return Arrow::AddAttribute(a);
}

EDirType DirArrow::GetToucedEntities(NEntityList &el) const
{
    if (!chart->IsVirtualEntity(src.entity)) el.push_back(src.entity);
    if (!chart->IsVirtualEntity(dst.entity)) el.push_back(dst.entity);
    for (unsigned u = 0; u<middle.size(); u++)
        if (skip.size()==0 || !skip[u+1])
            el.push_back(middle[u]);
    if (bidir) return EDirType::BIDIR;
    if (is_forward) return EDirType::RIGHT;
    return EDirType::LEFT;
}


void DirArrow::ConvertXPosToActiveEntities(VertXPos & pos)
{
    if (!pos.valid) return;
    EntityRef sub = chart->FindActiveParentEntity(pos.entity1);
    switch (pos.pos) {
    default: _ASSERT(0); return;
    case VertXPos::POS_CENTER:
        pos.entity2 = chart->FindActiveParentEntity(pos.entity2);
        if (sub==pos.entity2) {
            change_to_pos_at:
            pos.pos = VertXPos::POS_AT;
            pos.offset = 0;
        }
        break;
    case VertXPos::POS_AT:
        if (pos.offset>0) goto right;
        FALLTHROUGH;
    case VertXPos::POS_LEFT_BY:
    case VertXPos::POS_LEFT_SIDE:
    case VertXPos::POS_THIRD_LEFT:
        //we are left of an entity
        //see if we are the leftmost of a collapsed group entity
        //If yes, keep our type and offset, just switch to the entity that shows
        if (pos.entity1 != chart->FindLeftRightDescendant(sub, false, false))
            //No, we get hidden - switch to a POS_AT with zero offset
            goto change_to_pos_at;
        break;

        right:
    case VertXPos::POS_RIGHT_BY:
    case VertXPos::POS_RIGHT_SIDE:
    case VertXPos::POS_THIRD_RIGHT:
        if (pos.entity1 != chart->FindLeftRightDescendant(sub, true, false))
            //No, we get hidden - switch to a POS_AT with zero offset
            goto change_to_pos_at;
        break;
    }
    pos.entity1 = sub;
}

EndConnection DirArrow::GetConnection(bool is_src) const
{
    //Before PostParseProcess() all EIterators here are iterators
    //to AllEntities, after to ActiveEntities - OK
    EndConnection ret(GetSidePos(is_src));
    ret.error_msg_no_join = nullptr;
    ret.is_src = is_src;
    ret.body_is_left = is_src != is_forward;
    ret.accepts_curly_join = !is_src; //we only accept at our destination
    ret.can_curly_join = is_src;  //we can only curly join at our source
    ret.is_unspecified = is_src ? user_unspecified_ends.first : user_unspecified_ends.second;
    ret.is_indicator = false;

    if (ret.is_unspecified) {
        //search for the midpoint that is left/rightmost and is not a skip
        std::pair<int, int> minmax = {-1, -1};
        for (int i = 0; i<(int)middle.size(); i++) {
            if (skip.size() && skip[i+1]) continue;  //this may be called before PostParseProcess(), when we have not yet added the skip middlepoints.
            if (minmax.first==-1 || middle[minmax.first]->pos_exp >middle[i]->pos_exp)
                minmax.first = i;
            if (minmax.second==-1 || middle[minmax.second]->pos_exp < middle[i]->pos_exp)
                minmax.second = i;
        }
        //if no (non-skip) middle entity, we return the other end.
        if (minmax.first==-1) {
            static_cast<ArrowEnding&>(ret) = GetSidePos(!is_src);
        } else {
            ret.entity = is_src==is_forward ? middle[minmax.first] : middle[minmax.second];
            ret.offset = 0;
        }
    }
    return ret;
}

/** Type safe way to return -1 for negatvive, +1 for positive numbers and 0 for zero. */
template <typename T>
inline int sgn(T val) {return (T(0) < val) - (val < T(0));}

bool DirArrow::SetEndingDueToJoin(bool is_src, const ArrowEnding &ending, bool test_only)
{
    //first try to avoid a group entity
    const auto other_end = GetSidePos(!is_src);
    //Dont even allow both ends on the same side of an entity
    if (other_end.entity == ending.entity) {
        if (other_end.offset==ending.offset) {
            if (test_only)
                chart->Error.Error(file_pos.start, "Joining this arrow would reduce it to a degenerate arrow. Ignoring 'join' after.");
            return false;
        } else if (sgn(other_end.offset)==sgn(ending.offset)) {
            if (test_only)
                chart->Error.Error(file_pos.start, "Joining this arrow would reduce it to a degenerate arrow. Ignoring 'join' after.");
            return false;
        }
    }
    if (test_only) return true;
    (is_src ? src: dst) = ending;
    //Change activation size, too
    if (act_size.size()) //since this can be called before PostParseProcess(), we may not yet have act_size
        (is_src ? act_size.front() : act_size.back()) =
             ending.entity->GetRunningWidth(chart->activeEntitySize)/2;
    //remove middle points that fall outside the new endpoints
    for (int i = 0; i<(int)middle.size(); i++) {
        ArrowEnding m(middle[i]);
        if ((src<m)==(m<dst)) continue; //in-between src & dst
        //delete the degenerate segment;
        middle.erase(middle.begin()+i);
        linenum_middle.erase(linenum_middle.begin()+i);
        segment_types.erase(segment_types.begin()+i);
        segment_lines.erase(segment_lines.begin()+i);
        if (skip.size()) skip.erase(skip.begin()+i+1);
        if (act_size.size()) act_size.erase(act_size.begin()+i+1);
        i--; //may become -1 temporarily
    }
    return true;
}

void DirArrow::SetCurlyJoin(bool is_src [[maybe_unused]], ArcBase* prev, bool src_of_prev)
{
    _ASSERT(is_src);
    curly_join_to = prev;
    curly_join_to_src = src_of_prev;
    stick_to_prev = true;
    vspacing = DBL_MIN; //compress
}

double DirArrow::GetActSize(bool at_src) const
{
    //Dont return act-size if we have offset.
    if (at_src ? src.offset : dst.offset)
        return 0;
    return at_src == (sx<dx) ? act_size.front() : act_size.back();
}

/** Inserts a skip entity (pEntity) at position u.
 * Helper for PostParseProcess().
 * @param pEntity the entity to insert.
 * @param u The position to insert at. 0 means just after the src.*/
void DirArrow::InsertSkipEntity(unsigned u, Entity *pEntity)
{
    //here the index is u, as these arrays dont contain 'src' as index 0
    skip.insert(skip.begin()+u+1, true);
    middle.insert(middle.begin()+u, pEntity);
    linenum_middle.insert(linenum_middle.begin()+u, FileLineCol());
    segment_types.insert(segment_types.begin()+u, EArcSymbol::BOX_UNDETERMINED_FOLLOW);
    LineAttr l = segment_lines[u];
    segment_lines.insert(segment_lines.begin()+u, std::move(l));

}

ArcBase *DirArrow::PostParseProcess(Canvas &canvas, bool hide, EntityRef &left, EntityRef &right,
                                    Numbering& number, MscElement** target, ArcBase* byref_target)
{
    if (!valid) return nullptr;

    //set angle to zero if a bidirectional arrow and negative if a<-b
    if (bidir)
        slant_depth = slant_angle = 0;
    else if (!is_forward)
        slant_angle = -slant_angle;

    //At this point if the user specified a 'slant_depth' of nonzero,
    //slant_angle will be set to zero (by AddAttribute(), but this zero
    //is then not the final slant). Nevertheless, we will proceed to
    //calculate '*_ending.offset' and 'act_size' and go on
    //doing the Width() function call as if there were no slant.
    //(Luckily in this way we may only ask for *more* space than
    //with slant, so nothing will overlap nothing else.)
    //Then in Layout, when we already know the positions of the
    //entities, we calculate slant_angle, cos_slant and sin_slant
    //from slant_depth.
    sin_slant = slant_angle ? sin(slant_angle*M_PI/180.) : 0.;
    cos_slant = slant_angle ? cos(slant_angle*M_PI/180.) : 1.;

    //Now we check for cases like a-> ->b or a->|->b
    for (auto &m : middle)
        if (m==chart->LSide || m==chart->RSide) {
            chart->Error.Error(file_pos.start, "This multi-segment arrow misses an entity. Ignoring it.",
                "You can omit entities only at the beginning or at the end of an arrow.");
            return nullptr;
        }
    //Check for using group entities
    bool error = chart->ErrorIfEntityGrouped(src.entity, src.linenum);
    error |= chart->ErrorIfEntityGrouped(dst.entity, dst.linenum); //Make sure this is called even if error is already true
    for (auto i = middle.begin(); i!=middle.end(); i++)
        error |= chart->ErrorIfEntityGrouped(*i, file_pos.start);
    if (error) return nullptr;

    //Note: Here we do not check if lost at clause make sense - we can only check when X coordinates
    //are available (in Layout())
    if (middle.size()) {
        if (is_forward != (src < ArrowEnding(middle[0]))) goto problem;
        for (unsigned i = 0; i<middle.size()-1; i++)
            if (is_forward != (middle[i]->pos_exp < middle[i+1]->pos_exp)) goto problem;
        if (is_forward != (ArrowEnding(middle[middle.size()-1]) < dst)) goto problem;
        goto no_problem;

    problem:
        const char *arrow_string = bidir ? "<->" : is_forward ? "->" : "<-";
        string ss;
        NPtrList<Entity> e;
        ss << "Multi-segment arrow specified as ";
            e.push_back(src.entity);
        for (unsigned f = 0; f<middle.size(); f++)
            e.push_back(middle[f]);
        e.push_back(dst.entity);
        if (!is_forward) e.reverse();
        auto ii = e.begin();
        ss << (*ii++)->name;
        while (ii!=e.end())
            ss << arrow_string << (*ii++)->name;
        ss << ", but entity order is ";
        e.sort([](auto &a, auto &b) {return a->pos_exp<b->pos_exp; });
        ii = e.begin();
        ss << (*ii++)->name;
        while (ii!=e.end())
            ss << arrow_string << (*ii++)->name;
        ss << ". Ignoring arc.";
        valid = false;
        chart->Error.Error(file_pos.start, ss);
        return nullptr; //Remove this arrow.
    }
no_problem:

    //Add numbering, if needed
    LabelledArc::PostParseProcess(canvas, hide, left, right, number, target, byref_target);

    //Update src, dst and mid
     EntityRef sub = chart->FindActiveParentEntity(src.entity);
    //If src ending has offset and both it and its neighbour get collapsed, we remove the ending offset
    if (src.offset &&
        sub == chart->FindActiveParentEntity(chart->GetNeighbour(src.entity, src.offset>0, true)))
        //OK, This happens for "a, b, x {c, d}" entities, an arrow of "a<-b<- start before c;"
        //when x gets collapsed.
        //In this case src.entity is "c", neighbour is "d", is_forward is false and sub is "x".
        //Here we kill the special ending and show it as "a<-b<-x"
        src.offset = 0;
    src.entity = sub;

    sub = chart->FindActiveParentEntity(dst.entity);
    if (dst.offset &&
        sub == chart->FindActiveParentEntity(chart->GetNeighbour(dst.entity, dst.offset>0, true)))
        dst.offset = 0;
    dst.entity = sub;

    //Change dst for mscgen broadcast arrows
    if (IsMscgenBroadcastArrow()) {
        //find leftmost/rightmost active non-virtual entity
        //note that src.entity already points to ActiveEntities, so its iterators
        //are directly comparable
        if (specified_as_forward) {
            auto i = --chart->ActiveEntities.end();
            while (*i!=src.entity && (chart->IsVirtualEntity(*i) || !(*i)->running_shown.IsOn()))
                i--;
            dst.entity = *i;
        } else {
            auto i = chart->ActiveEntities.begin();
            while (*i!=src.entity && (chart->IsVirtualEntity(*i) || !(*i)->running_shown.IsOn()))
                i++;
            dst.entity = *i;
        }
        //remove loss
        lost_at = -2;
    }

    //Change left and right only if src/dst actually point to a "real entity"
    //and not (left) or (right). If they do, consider our other "end"
    //Note that for special endings, we have src and/or dst equal to LSide or RSide
    //so there is no effort to keep a special ending inside left/right (that is inside
    //auto scaling boxes).
    if (!chart->IsVirtualEntity(src.entity)) {
        left = chart->EntityMinByPos(left, src.entity);
        right = chart->EntityMaxByPos(right, src.entity);
    }
    if (!chart->IsVirtualEntity(dst.entity)) {
        right = chart->EntityMaxByPos(right, dst.entity);
        left = chart->EntityMinByPos(left, dst.entity);
    }
    //Consider the middle elements if any of our endpoints is virtual
    //(else we are good only by checkind the endpoints above
    if (chart->IsVirtualEntity(src.entity) || chart->IsVirtualEntity(dst.entity))
        for (auto iMiddle : middle) {
            left = chart->EntityMinByPos(left, iMiddle);
            right = chart->EntityMaxByPos(right, iMiddle);
        }
    //If we are hidden, do not show us. (But we have updated left/right nevertheless,
    //so that auto scaling boxes scale the same way independently of whether they are collapsed
    //or not.)
    if (hide) return nullptr;

    //Find the visible parent of each middle point and remove it if equals to
    //an end or to any other middle visible parent
    for (unsigned ii = 0; ii<middle.size(); /*nope*/) {
        sub = chart->FindActiveParentEntity(middle[ii]);
        if (middle[ii] != sub) {
            //if the replacement parent equals to an end or previous middle point, delete it
            if ((sub == src.entity && src.offset==0) ||
                (sub == dst.entity && dst.offset==0) ||
                middle.begin()+ii != std::find(middle.begin(), middle.begin()+ii, sub)) {
                middle.erase(middle.begin()+ii);
                linenum_middle.erase(linenum_middle.begin()+ii);
                //if the substitute equals to the destination, delete the segment right of middle[ii]
                segment_lines.erase(segment_lines.begin()+ii+(sub == dst.entity));
                segment_types.erase(segment_types.begin()+ii+(sub == dst.entity));
            } else
                //else just use the replacement
                middle[ii++] = sub;
        } else
            ii++;
    }

    //Replace lost_pos entities to active entities
    ConvertXPosToActiveEntities(lost_pos);

    if (src==dst)
        return *src.entity->running_style.read().indicator
        ? new Indicator(chart, src.entity, indicator_style, file_pos)
        : nullptr;

    //Add skip entities
    skip.assign(middle.size()+2, false); //add as many false indicators, as many entities we touch
    if (style.read().arrow.read().GetType(EArrowEnd::SKIP)->value_or(EArrowType::NONE) != EArrowType::NONE) {
        std::vector<ArrowEnding> points;
        points.push_back(src);
        for (auto &m : middle)
            points.emplace_back(m);
        points.push_back(dst);
        skip.resize(points.size(), false);
        unsigned insert_at = 0;
        for (unsigned u = 0; u<points.size()-1; u++, insert_at++)
            if (is_forward)
                for (auto pEntity : chart->ActiveEntities) {
                    if (pEntity->running_shown.IsOn() && points[u] <ArrowEnding(pEntity) && ArrowEnding(pEntity) < points[u+1])
                        InsertSkipEntity(insert_at++, pEntity);
                }
            else //go reverse
                for (auto iEntity = chart->ActiveEntities.rbegin(); !(iEntity==chart->ActiveEntities.rend()); iEntity++)
                    if ((*iEntity)->running_shown.IsOn() && points[u] > ArrowEnding(*iEntity) && ArrowEnding(*iEntity) > points[u+1])
                        InsertSkipEntity(insert_at++, *iEntity);
        has_skip = insert_at > points.size()-1; //can only be true if we have called InsertSkipEntity(insert_at++, ...)
    }

    //record what entities are active at the arrow (already consider slant)
    //for special endings use zero activation size. We maintain activation size for *_ending.entity
    //even if we have a nonzero offset - in that case we will ignore it.
    //If src or dst has offset we store zero activation size.
    act_size.clear();
    act_size.reserve(2+middle.size());
    act_size.push_back(src.offset ? 0 : std::max(0., src.entity->GetRunningWidth(chart->activeEntitySize)/2)/cos_slant);
    for (unsigned iiii = 0; iiii<middle.size(); iiii++)
        act_size.push_back(std::max(0., middle[iiii]->GetRunningWidth(chart->activeEntitySize)/2)/cos_slant);
    act_size.push_back(dst.offset ? 0 : std::max(0., dst.entity->GetRunningWidth(chart->activeEntitySize)/2)/cos_slant);

    //This is, how it shall look like:
    _ASSERT(linenum_middle.size()==middle.size());  //one for each middle entity
    _ASSERT(segment_types.size() ==middle.size()+1);  //one for each line segment
    _ASSERT(segment_lines.size() ==middle.size()+1);  //one for each line segment
    _ASSERT(skip.size()          ==middle.size()+2);  //one for each entity including src and dst
    _ASSERT(act_size.size()      ==middle.size()+2);  //one for each entity including src and dst

                                                      //If the message are lost and the user specified no 'at' clause,
                                                      //fill in which entities it is lost between.
                                                      //After this we rely only on 'lost_pos'
    if (!lost_pos.valid) {
        if (lost_at > -2) {
            lost_pos.valid = true;
            lost_pos.offset = 0;
            lost_pos.e1line.MakeInvalid();
            lost_pos.e2line.MakeInvalid();

            lost_pos.entity1 = lost_at == -1 ? src.entity : unsigned(lost_at) == middle.size() ? dst.entity : middle[lost_at];
            lost_pos.entity2 = lost_pos.entity1;
            //calculate the entity that is both a) part of the arrow (src, dst or middle)
            //and b) is immediately left/right of the entity marked at lost_at.
            const int p = lost_is_forward ? lost_at + 1 : lost_at - 1;
            _ASSERT(p>=-1 && p<=int(middle.size()));
            const EntityRef pair = p <= -1 ? src.entity :
                unsigned(p) >= middle.size() ? dst.entity :
                middle[p];

            //find next (or prev) active and showing entity.
            //If at end (or beginning) use RSide (or LSide)
            //If the next/prev entity is part of the arrow, put the
            //loss at one third of the distance. Else put into the middle.
            auto entity = chart->ActiveEntities.Find_by_Ptr(lost_pos.entity2);
            if (is_forward == lost_is_forward) {
                do {
                    entity++;
                } while (entity != chart->ActiveEntities.end() && !(*entity)->running_shown.IsOn());
                lost_pos.entity2 = entity == chart->ActiveEntities.end() ? chart->RSide : *entity;
            } else {
                do {
                    entity--;
                } while (entity != chart->ActiveEntities.end() && !(*entity)->running_shown.IsOn());
                lost_pos.entity2 = entity == chart->ActiveEntities.end() ? chart->LSide : *entity;
            }
            lost_pos.pos = lost_pos.entity2 != pair ? VertXPos::POS_CENTER :
                             (lost_is_forward == is_forward) ?
                                 VertXPos::POS_THIRD_RIGHT :    //THIRD_XXX will ignore entity2
                                 VertXPos::POS_THIRD_LEFT;
        } else //when lost_at==-2
            lost_pos.valid = false; //make sure this field is false
    }
    return this;
}

void DirArrow::UpdateActiveSizes()
{
    //We take the maximum of the existing activation size and the ones in the
    //running state of entities. We maintain activation size for *_ending.entity
    //even if we have a nonzero offset - in that case we will ignore it.

    //We assume act_size is still ordered as after PostParseProcess() and
    //before Layout(), that is {src,middle[],dst}
    auto i = act_size.begin();
    if (src.offset==0)
        *i = std::max(*i, src.entity->GetRunningWidth(chart->activeEntitySize)/2/cos_slant);
    for (unsigned u = 1; u<=middle.size(); u++)
        act_size[u] = std::max(act_size[u], middle[u-1]->GetRunningWidth(chart->activeEntitySize)/2/cos_slant);
    i = --act_size.end();
    if (dst.offset==0)
        *i = std::max(*i, dst.entity->GetRunningWidth(chart->activeEntitySize)/2/cos_slant);
}


/** This many pixels is added above the arrow line for first line of labels.*/
#define ARROW_TEXT_VSPACE_ABOVE 1
/** This many pixels is added below the arrow line for second line of labels.*/
#define ARROW_TEXT_VSPACE_BELOW 1


void DirArrow::FinalizeLabels(Canvas &canvas)
{
    Arrow::FinalizeLabels(canvas);
    if (parsed_label.getTextWidthHeight().y==0) return;
    //Insert a small extra spacing for the arrow line
    double lw_max = style.read().line.LineWidth();
    for (const auto &i : segment_lines)
        lw_max = std::max(lw_max, i.LineWidth());
    parsed_label.AddSpacingAfterFirstLine(ARROW_TEXT_VSPACE_ABOVE + lw_max + ARROW_TEXT_VSPACE_BELOW);
}


double DirArrow::ConsiderLabelWidth(double width_needed)
{
    //In the following situation        A     B    C      D
    //where the arrow is "|->B->C->|"   |  -->|--->|-->   |
    if (src.offset) {
        width_needed += is_forward ? src.offset : -src.offset;
    } else
        width_needed += act_size.front();
    if (dst.offset) {
        width_needed -= is_forward ? dst.offset : -dst.offset;
    } else
        width_needed += act_size.back();
    if (src.entity!=dst.entity)
        return width_needed;
    //Ok, we have no actual entity-to-entity span. We have either
    // "|->B->|"                   "|->| start before B end after B"
    //A     B     C        or         A    B     C
    //|  -->|-->  |                   |  --|-->  |
    //In addition any one can happen if B is a collapsed group entity.
    //Here, if we have auto-scaling we need to update the two offsets
    if (chart->GetHScale()<=0 && width_needed>0) {
        if (src.offset) {
            if (dst.offset) {
                src.offset += is_forward ? -width_needed/2 : width_needed/2;
                dst.offset += is_forward ? width_needed/2 : -width_needed/2;
            } else
                src.offset += is_forward ? -width_needed : width_needed;
        } else if (dst.offset)
            dst.offset += is_forward ? width_needed : -width_needed;
        else {
            _ASSERT(0); //how could we end up with a degenerate arrow with no special ending???
        }
    }
    return 0;
    // If we do not do autoscaling, we do not update offsets
}


/** Controls how much a lost symbol of DirArrow and ArcVertical is smaller than
 * an arrowhead with the same size attr ('tiny', 'small', etc.).*/
constexpr double LSYM_SIZE_ADJ = 0.66;

void DirArrow::Width(Canvas &canvas, DistanceRequirements &vdist)
{
    if (!valid) return;

    //Add a new element to vdist
    vdist.AddElementTop(this);
    //Add activation status right away
    AddEntityLineWidths(vdist);
    //Add touched entities (verticals will be pushed away)
    vdist.InsertEntity(src.entity);
    vdist.InsertEntity(dst.entity);

    //Here we have a valid canvas, so we adjust act_size
    if (canvas.HasImprecisePositioning() && slant_angle==0)
        for (auto &i : act_size)
            i = floor(i);

    //we lie about us being forward (we do not check), so we know which of first/second to use
    //Here segment_lines is in the order of src->dst irrespective of left_right
    const DoublePair end = style.read().arrow.read().getWidths(true, bidir, EArrowEnd::END, segment_lines.back(), segment_lines.back());
    DoublePair start = style.read().arrow.read().getWidths(true, bidir, EArrowEnd::START, segment_lines.front(), segment_lines.front());
    //Expand margins so that we avoid arrowheads
    parsed_label.EnsureMargins(is_forward ? start.right : end.left, is_forward ? end.left : start.right);

    //if we do a curly connect to something above us, act as if the src had no arrow;
    double curve_width_space = 0;
    if (curly_join_to) {
        start.right = 0;
        curly_radius = std::max(*segment_lines.front().radius, 0.); //avoid negative radius
        curly_margin = style.read().arrow.read().getWidths(true, bidir, EArrowEnd::START,
            segment_lines.front(), segment_lines.front()).right;
        const double src_segment_lw2 = ceil(segment_lines.front().LineWidth()/2); //we use 2 if linewidth is 1 as in Layout()
        const double upper_act = curly_join_to->GetActSize(curly_join_to_src);
        curve_width_space = std::max(curly_margin + curly_radius, 5.) + src_segment_lw2*2 + upper_act; //at least the space of 5
    }

    //Consider the label width
    double width_needed = parsed_label.getSpaceRequired(); //and this is the width_needed needed between them
    if (width_needed==0)
        width_needed = chart->nudgeSize + start.right + end.left; //for empty labels, we need just width_needed for the arrowheads.
    width_needed = ConsiderLabelWidth(width_needed);
    vdist.Insert(src.entity->index, dst.entity->index, width_needed*cos_slant);

    //Add distances for arrowheads or special endings
    //Note that for offset endings we do not add activation size to offset,
    //so that we have the same position irrespective of entity activation.
    //Note that for normal endings, we do add activation size (which
    //is simple linewidth if not activated).
    if (src.offset) {
        double s = fabs(src.offset)*cos_slant;
        if (is_forward == (src.offset>0)) s += start.right*cos_slant;
        vdist.Insert(src.entity->index, src.offset>0 ? DISTANCE_RIGHT : DISTANCE_LEFT, s);
    } else {
        const double s = (start.right + act_size.front())*cos_slant;
        vdist.Insert(src.entity->index, is_forward ? DISTANCE_RIGHT : DISTANCE_LEFT, s);
    }
    //Now add spacing for the curl, if any (this is the other side of src)
    if (curly_join_to) {
        vdist.Insert(src.entity->index, is_forward ? DISTANCE_LEFT : DISTANCE_RIGHT, src.offset+curve_width_space);
    }
    if (dst.offset) {
        double d = fabs(dst.offset)*cos_slant;
        if (is_forward != (dst.offset>0)) d += end.left*cos_slant;
        vdist.Insert(dst.entity->index, dst.offset>0 ? DISTANCE_RIGHT : DISTANCE_LEFT, d);
    } else {
        const double d = (end.left    + act_size.back())*cos_slant;
        vdist.Insert(dst.entity->index, is_forward ? DISTANCE_LEFT: DISTANCE_RIGHT, d);
    }
    //now add the distances for the arrowheads in the middle
    if (middle.size()) {
        for (unsigned i = 0; i<middle.size(); i++) {
            DoublePair mid = style.read().arrow.read().getWidths(is_forward, bidir,
                skip[i+1] ? EArrowEnd::SKIP : EArrowEnd::MIDDLE, segment_lines[i], segment_lines[i+1]);
            const double left = (mid.left  + act_size[i+1])*cos_slant;
            const double right = (mid.right + act_size[i+1])*cos_slant;
            vdist.Insert(middle[i]->index, DISTANCE_LEFT, left);
            vdist.Insert(middle[i]->index, DISTANCE_RIGHT, right);
        }
    }
    //If a lost symbol is generated, do some
    if (lost_pos.valid) {
        auto who = ArrowHead(EArrowType::DIAMOND, false, *style.read().lsym_size).
            WidthHeight(style.read().line, style.read().line /*dummy*/);
        lsym_size = XY(who.before_x_extent, who.half_height) * LSYM_SIZE_ADJ;
        const double aw = lsym_side_by_offset * lsym_size.x;
        const double gap = chart->hscaleAutoXGap;
        const unsigned e1 = lost_pos.entity1 ? lost_pos.entity1->index : 0;
        const unsigned e2 = lost_pos.entity2 ? lost_pos.entity2->index : 0;
        //If the arrow symbol is specified between two neighbouring entity,
        //add some space for it
        switch (lost_pos.pos) {
        case VertXPos::POS_CENTER:
            if (abs(int(e1)-int(e2))==1) {
                vdist.Insert(e1, e2, vdist.Query(e1, e2) + lsym_size.x + 2*gap);
            }
            break;
        case VertXPos::POS_THIRD_LEFT:
            if (e1>0) {
                vdist.Insert(e1, e1-1, vdist.Query(e1, e1-1) + 3*lsym_size.x + 2*gap);
            }
            break;
        case VertXPos::POS_THIRD_RIGHT:
            vdist.Insert(e1, e1+1, vdist.Query(e1, e1+1) + 3*lsym_size.x + 2*gap);
            break;
        case VertXPos::POS_AT:
            vdist.Insert(e1, DISTANCE_LEFT, lsym_size.x/2 + gap);
            vdist.Insert(e1, DISTANCE_RIGHT, lsym_size.x/2 + gap);
            break;
        case VertXPos::POS_LEFT_BY:
            vdist.Insert(e1, DISTANCE_LEFT, lsym_size.x + aw + gap);
            break;
        case VertXPos::POS_RIGHT_BY:
            vdist.Insert(e1, DISTANCE_RIGHT, lsym_size.x + aw + gap);
            break;
        case VertXPos::POS_LEFT_SIDE:
            vdist.Insert(e1, DISTANCE_LEFT, lsym_size.x + gap);
            break;
        case VertXPos::POS_RIGHT_SIDE:
            vdist.Insert(e1, DISTANCE_RIGHT, lsym_size.x + gap);
            break;
        default:
            _ASSERT(0);
        }
    } else
        //if no lost position
        lsym_size.x = lsym_size.y = 0;
    //Add a new element to vdist
    vdist.AddElementBottom(this);
    //Add activation status right away
    AddEntityLineWidths(vdist);
}

EArrowEnd DirArrow::WhichArrow(unsigned i) const
{
    //in xPos (may not be filled up yet, index 0 will be the src and indes xpos.size()-1 will be dest
    //in between there will be middle.size() number of middle arrows.
    //"i" here is an index that will be used for xPos.
    if (i>0 && i<middle.size()+1) return skip[i] ? EArrowEnd::SKIP : EArrowEnd::MIDDLE;
    if ((i==0) == is_forward) return EArrowEnd::START;
    return EArrowEnd::END;
}

void DirArrow::Layout(Canvas &canvas, AreaList *cover)
{
    height = 0;
    if (!valid) return;
    yPos = 0;
    area.clear();
    sx = chart->XCoord(src.entity) + src.offset;
    dx = chart->XCoord(dst.entity) + dst.offset;
    //If the user specified slant_depth, we shall re-calculate
    //slant_angle, cos_slant and sin_slant.
    if (slant_depth) {
        _ASSERT(slant_angle==0);
        //slant_angle shall be negative if dx < sx;
        const double rad = atan(slant_depth/(dx-sx));
        sin_slant = sin(rad);
        cos_slant = cos(rad);
        slant_angle = rad/M_PI*180.;
    }
    //Check if the loss happens in-between (untransformed) sx and dx
    if (lost_pos.valid) {
        cx_lsym = lost_pos.CalculatePos(*chart, lsym_size.x, chart->hscaleAutoXGap, lsym_size.x*lsym_side_by_offset);
        if (cx_lsym < std::min(sx, dx) || std::max(sx, dx) < cx_lsym) {
            chart->Error.Error(linenum_lost_at.IsInvalid() ? this->file_pos.start : linenum_lost_at,
                "The position of the loss is " +
                std::string(cx_lsym < std::min(sx, dx) ? "left" : "right") +
                " of the arrow. Ignoring it.");
            lost_pos.valid = false;
        }
    }
    //convert dx and cx_lsym to transformed space
    if (slant_angle) {
        dx = sx + (dx-sx)/cos_slant;
        cx_lsym = sx + (cx_lsym-sx)/cos_slant;
    }

    double lw_max = style.read().line.LineWidth();
    for (const auto &i : segment_lines)
        lw_max = std::max(lw_max, i.LineWidth());
    //Here segment_lines is still ordered from src->dst irrespective of left or right
    const double y_e = style.read().arrow.read().getHeight(bidir, EArrowEnd::END, segment_lines.back(), segment_lines.back());
    const double y_s = style.read().arrow.read().getHeight(bidir, EArrowEnd::START, segment_lines.front(), segment_lines.front());
    //aH is _half_ the height of the arrowhead (the height above/below the centerline)
    //consider ending and potential loss symbol.
    double aH = std::max(y_e, lsym_size.y/2);
    //consider start arrowhead only if we dont do curly join
    if (curly_join_to==nullptr)
        aH = std::max(aH, y_s);
    //If there are middle arrows, make aH be the highest of endType/startType
    //and midType arrows, consider skipped entities, too.
    for (unsigned i = 0; i<middle.size(); i++)
        aH = max(aH, style.read().arrow.read().getHeight(bidir, skip[i+1] ? EArrowEnd::SKIP : EArrowEnd::MIDDLE,
                                           segment_lines[i], segment_lines[i+1]));

    double y = chart->arcVGapAbove;
    XY text_wh = parsed_label.getTextWidthHeight();
    if (text_wh.y) {
        //Determine space for the text, reflow if needed and calculate cover
        //(note that for special endings act_size is zero here)
        if (is_forward) {
            sx_text = sx + act_size.front();
            dx_text = dx - act_size.back();
        } else {
            sx_text = dx + act_size.back();
            dx_text = sx - act_size.front();
        }
        if (parsed_label.IsWordWrap()) {
            const double overflow = parsed_label.Reflow(canvas, chart->Shapes, dx_text - sx_text);
            OverflowWarning(overflow, "", src.entity, dst.entity);
            text_wh = parsed_label.getTextWidthHeight(); //refresh
        } else {
            CountOverflow(dx_text - sx_text);
        }
        cx_text = (sx+dx)/2;
        area = text_cover = parsed_label.Cover(chart->Shapes, sx_text, dx_text, y, cx_text);
        area.arc = this;
        //determine top edge position of arrow midline
        const double firstLineHeight = parsed_label.getTextWidthHeight(0).y;
        y += std::max(aH, firstLineHeight+ARROW_TEXT_VSPACE_ABOVE);
    } else {
        /* no text */
        y += aH;
    }
    centerline = y = ceil(y) + lw_max/2;
    //Note: When the angle is slanted, we rotate the space around "sx, centerline+yPos"

    //prepare xPos and margins
    xPos.clear(); xPos.reserve(2+middle.size());
    margins.clear(); margins.reserve(2+middle.size());
    xPos.push_back(sx);
    margins.push_back(style.read().arrow.read().getWidths(is_forward, bidir, EArrowEnd::START,
                                            segment_lines.front(), segment_lines.front()));
    //if we do curly join, swap the margins
    if (curly_join_to)
        margins.back().swap();
    for (unsigned u = 0; u<middle.size(); u++) {
        xPos.push_back(sx + (chart->XCoord(middle[u])-sx)/cos_slant);
        margins.push_back(style.read().arrow.read().getWidths(is_forward, bidir,
                                                skip[u+1] ? EArrowEnd::SKIP : EArrowEnd::MIDDLE,
                                                segment_lines[u], segment_lines[u+1]));
    }
    xPos.push_back(dx);
    margins.push_back(style.read().arrow.read().getWidths(is_forward, bidir, EArrowEnd::END,
                                            segment_lines.back(), segment_lines.back()));
    const double s_act = act_size.front();
    const double d_act = act_size.back();
    //prepare clip_area
    clip_area = style.read().arrow.read().ClipForLine(XY(dx, y), d_act, is_forward, bidir, EArrowEnd::END,
                                        segment_lines.back(), segment_lines.back());
    //clip staring arrow only if we dont do curly join
    //This clip region will be added in FinalizeLayout()
    if (curly_join_to==nullptr)
        clip_area += style.read().arrow.read().ClipForLine(XY(sx, y), s_act, is_forward, bidir, EArrowEnd::START,
                                             segment_lines.front(), segment_lines.front());
    for (unsigned i=1; i<xPos.size()-1; i++)
        clip_area += style.read().arrow.read().ClipForLine(XY(xPos[i], y), act_size[i], is_forward, bidir,
                                             skip[i] ? EArrowEnd::SKIP : EArrowEnd::MIDDLE,
                                             segment_lines[i-1], segment_lines[i]);

    //Add arrowheads and line segments to cover
    //ignore src if we do curly joins, will be added in FinalizeLayout()
    if (curly_join_to==nullptr)
        area += style.read().arrow.read().Cover(XY(sx, y), act_size.front(), is_forward, bidir, EArrowEnd::START,
                                  segment_lines.front(), segment_lines.front());
    for (unsigned i=1; i<xPos.size()-1; i++)
        area += style.read().arrow.read().Cover(XY(xPos[i], y), act_size[i], is_forward, bidir,
                                  skip[i] ? EArrowEnd::SKIP : EArrowEnd::MIDDLE,
                                  segment_lines[i-1], segment_lines[i]);
    area += style.read().arrow.read().Cover(XY(dx, y), act_size.back(), is_forward, bidir, EArrowEnd::END,
                              segment_lines.back(), segment_lines.back());
    //Add the loss symbol
    if (lost_pos.valid)
        area += Block(XY(cx_lsym, centerline)-lsym_size/2, XY(cx_lsym, centerline)+lsym_size/2);
    area_important = area; //the text and arrowheads and lost symbol
    //Add small blocks if there is no end or start arrowhead
    if (curly_join_to==nullptr)
        if (style.read().arrow.read().GetArrowHead(bidir, EArrowEnd::START).IsNone())
            area_important += Block(sx-chart->compressGap/2, sx+chart->compressGap/2,
                                    centerline-chart->compressGap/2, centerline+chart->compressGap/2);
    if (style.read().arrow.read().GetArrowHead(bidir, EArrowEnd::END).IsNone())
        area_important += Block(dx-chart->compressGap/2, dx+chart->compressGap/2,
                                centerline-chart->compressGap/2, centerline+chart->compressGap/2);
    if (sx>=dx) {
        std::reverse(xPos.begin(), xPos.end());
        std::reverse(margins.begin(), margins.end());
        std::reverse(segment_lines.begin(), segment_lines.end());
        std::reverse(segment_types.begin(), segment_types.end());
        std::reverse(act_size.begin(), act_size.end());
    }
    for (unsigned i=0; i<xPos.size()-1; i++) {
        const double lw2 = ceil(segment_lines[i].LineWidth()/2);
        //x coordinates below are not integer- but this will be merged with other contours - so they disappear
        area += Block(xPos[i]+margins[i].right, xPos[i+1]-margins[i+1].left, y-lw2, y+lw2) - clip_area;
    }
    //Add a horizontal line to area to bridge the gap across activated entities
    area_to_note2 = Block(sx, dx, centerline, centerline).Expand(0.5);
    CalculateMainline(std::max(lw_max, chart->nudgeSize+1.0));
    if (slant_angle != 0) {
        //OK: all of sx, dx, sx_text, dx_text, cx_text, xPos, act_size, margins
        //are in the transformed space
        //Now we transfrom "area" and "text_cover", too
        const XY c(sx, yPos+centerline);
        area.RotateAround(c, slant_angle);
        area_important.RotateAround(c, slant_angle);
        area_to_note2.RotateAround(c, slant_angle);
        text_cover.RotateAround(c, slant_angle);
        clip_area.RotateAround(c, slant_angle);
    }
    chart->NoteBlockers.Append(this);
    if (cover)
        *cover = GetCover4Compress(area);
    if (slant_angle==0)
        height = std::max(y+max(aH, lw_max/2), chart->arcVGapAbove + text_wh.y) + chart->arcVGapBelow;
    else
        height = area.GetBoundingBox().y.till;
    LayoutComments(canvas, cover);
}

void DirArrow::FinalizeLayout(Canvas & canvas, AreaList * cover)
{
    //Nothing to do if we are not curly joining to an arrow above
    if(curly_join_to==nullptr) return;

    //determine y pos of above arrow
    XY above(sx, curly_join_to->GetCenterline(curly_join_to_src ? CenterlineSelector::AT_SRC : CenterlineSelector::AT_DST));
    const XY c(sx, yPos+centerline);
    const unsigned src_index = is_forward ? 0 : xPos.size()-1;
    const unsigned src_segment_index = is_forward ? 0 : xPos.size()-2;
    XY from; //from where to start the segment line
    const double src_segment_lw2 = ceil(segment_lines[src_segment_index].LineWidth()/2); //we use 2 if linewidth is 1 as in Layout()
    const double upper_act = curly_join_to->GetActSize(curly_join_to_src);
    const int have_margin = canvas.NeedsArrowFix();
    if (is_forward)
        from.x = xPos[1] - have_margin*margins[1].left - act_size[1];
     else
        from.x = xPos[xPos.size()-2] + have_margin*margins[xPos.size()-2].right + act_size[act_size.size()-2];
    from.y = yPos+centerline;
    from.RotateAround(c, cos_slant, sin_slant); //now 'from' is in its final place
    //update curly_radius: smaller if y distance is not large enough
    curly_radius = segment_lines[src_segment_index].SaneRadius(above.y, yPos+centerline, above.y, yPos+centerline); //this is a square equal to the Y size of the curl
    const double curve_width = std::max(curly_margin + curly_radius, 5.) + src_segment_lw2*2;  //at least width of 5.

    //Now calculate the last segment line + the curvy join line
    Block join_block(above.x-upper_act-curve_width, above.x + upper_act+ curve_width, above.y, yPos+centerline);
    Contour join_contour = segment_lines[src_segment_index].CreateRectangle_Midline(join_block);
    if (is_forward) join_block.x.till = sx - upper_act- curly_margin;
    else join_block.x.from = sx + upper_act + curly_margin;
    join_contour *= join_block; //chop into half
    const Path join_path = join_contour;
    //now find the vertical edge at 'sx'+-'upper_act'+-'curly_margin'
    unsigned pos;
    for (pos = 0; pos < join_path.size(); pos++)
        if (join_path[pos].IsStraight() && join_path[pos].GetStart().x == join_path[pos].GetEnd().x &&
            fabs(fabs(join_path[pos].GetStart().x - sx) - (upper_act+curly_margin) <1e-6))
            break;
    _ASSERT(pos<join_path.size()); //no such edge - something wrong
    //Create new path
    src_line.clear();
    if (is_forward) {
        //Add small horizontal line (under arrowhead) if we can clip this
        if (curly_margin)
            src_line.append(Edge(above, join_path.at_prev(pos).GetEnd()));
        for (unsigned u = join_path.prev(pos); u!=pos; u = join_path.prev(u))
            src_line.append(Edge(join_path[u]).Invert());
    } else {
        //Add small horizontal line (under arrowhead) if we can clip this
        if (curly_margin)
            src_line.append(Edge(above, join_path.at_next(pos).GetStart()));
        for (unsigned u = join_path.next(pos); u!=pos; u = join_path.next(u))
            src_line.append(join_path[u]);
    }
    //add segment line - connect over the gap 'curly_margin' wide, if needed
    src_line.append({Edge(XY(sx, yPos+centerline), from)}, true);

    //Add to clip area - note we do not rotate it, since curly join segment is not rotated
    clip_area += style.read().arrow.read().ClipForLine(above, act_size[src_index],
                                         !(is_forward), bidir, EArrowEnd::START,
                                         segment_lines[src_segment_index], segment_lines[src_segment_index]);

    //Add src arrowhead to area
    //note that we use negate of is_forward here, because we want the arrowhead flipped
    Contour ah = style.read().arrow.read().Cover(above, act_size[src_index], !is_forward, bidir, EArrowEnd::START,
                                   segment_lines[src_segment_index], segment_lines[src_segment_index]);
    if (ah.IsEmpty()) {
        area_important += Block(sx-chart->compressGap/2, sx+chart->compressGap/2,
                                above.y-chart->compressGap/2, above.y+chart->compressGap/2);
    } else {
        area += ah;
        area_important += ah;
    }
    //Add line segment to area
    Contour aaa = src_line.SimpleWiden(src_segment_lw2*2);
    aaa -= clip_area;
    area +=  aaa;
    if (cover) {
        //replace our elements to what comes from 'area'
        //preserve the rest - those are comments (or some other elements in a JoinSeries)
        AreaList new_cover = GetCover4Compress(area);
        for (auto &a : cover->GetCover())
            if (a.arc != this)
                new_cover += a;
        cover->swap(new_cover);
    }
    //We dont change height
}

double DirArrow::GetCenterlineAtXPos(double x) const
{
    if (!slant_angle)
        return centerline;

    //Calculate the x coordinate of the tip of the arrow
    //Here act_size is sorted from left to right and not from src->dst
    const double _sx = sx + cos_slant*(is_forward ? *act_size.begin() : -*act_size.rbegin());
    const double _dx = sx + (dx-sx)*cos_slant +
                            cos_slant*(is_forward ? -*act_size.rbegin() : *act_size.begin());

    //if we are left of a -> arrow or right from a <- arrow
    //(Latter is an example of below, 'x' is at the + sign)
    //        o---+--------
    //       /
    //_____|/_
    //return the top line
    if ( (is_forward && x<=_sx) || (!is_forward && x>=_sx) )
        return centerline;
    //if we are on the other side return the bottom line
    if ( (is_forward && x>=_dx) || (!is_forward && x<=_dx) )
        return sin_slant*(_dx-_sx) + centerline;
    //We are on the arrow
    return sin_slant*(x-_sx) + centerline;
}


void DirArrow::CalculateMainline(double thickness)
{
    if (slant_angle == 0)
        area.mainline = Block(chart->GetDrawing().x, Range(yPos+centerline - thickness/2, yPos+centerline + thickness/2));
    else {
        thickness /= cos_slant;
        const double src_y = yPos+centerline;
        const double dst_y = sin_slant*(dx-sx) + src_y;
        const double real_dx = sx + cos_slant*(dx-sx);
        if (is_forward) {
            const XY ml[] = {XY(chart->GetDrawing().x.from,   src_y-thickness/2), XY(sx, src_y-thickness/2),
                             XY(real_dx, dst_y-thickness/2), XY(chart->GetDrawing().x.till, dst_y-thickness/2),
                             XY(chart->GetDrawing().x.till, dst_y+thickness/2), XY(real_dx, dst_y+thickness/2),
                             XY(sx, src_y+thickness/2), XY(chart->GetDrawing().x.from, src_y+thickness/2)};
            area.mainline.assign_dont_check(ml);
        } else {
            const XY ml[] = {XY(chart->GetDrawing().x.from, dst_y-thickness/2), XY(real_dx, dst_y-thickness/2),
                             XY(sx, src_y-thickness/2), XY(chart->GetDrawing().x.till, src_y-thickness/2),
                             XY(chart->GetDrawing().x.till, src_y+thickness/2), XY(sx, src_y+thickness/2),
                             XY(real_dx, dst_y+thickness/2), XY(chart->GetDrawing().x.from, dst_y+thickness/2)};
            area.mainline.assign_dont_check(ml);
        }
    }
}

void DirArrow::ShiftBy(double y)
{
    if (!valid) return;
    if (y) {
        text_cover.Shift(XY(0, y));
        clip_area.Shift(XY(0, y));
        src_line.Shift(XY(0, y));
        Arrow::ShiftBy(y);
    }
}

void DirArrow::CheckSegmentOrder(double y)
{
    if (!valid) return;

    //Check if all entities involved are actually turned on.
    //we can only do this check here, as entity status is filled
    //During the Layout() process, and can be considered complete only here
    //Note: when special endings, src/dst will be LSide/RSide and we do not
    //need to consider src/dst.entity.
    vector<EntityRef> temp = middle;
    vector<FileLineCol> linenum_temp = linenum_middle;
    if (src.offset==0) {
        temp.insert(temp.begin(), src.entity);
        linenum_temp.insert(linenum_temp.begin(), src.linenum);
    }
    if (src.entity!=dst.entity && dst.offset==0) {
        temp.push_back(dst.entity);
        linenum_temp.push_back(dst.linenum);
    }
    std::vector<string> ss;
    int earliest = -1;
    for (unsigned i = 0; i<temp.size(); i++)
        if (!temp[i]->status.GetStatus(y).IsOn() &&
            !chart->IsVirtualEntity(temp[i])) {
            ss.push_back("'" + temp[i]->name + "'");
            if (earliest == -1 || linenum_temp[i] < linenum_temp[earliest]) earliest = i;
        }
    if (ss.size()>0) {
        string sss;
        if (ss.size() == 1)  //One missing entity
            sss << "Entity " << ss[0] << " is";
        else {
            sss << "Entities ";
            for(unsigned i=0; i<ss.size()-2; i++)
                sss << ss[i] << ", ";
            sss << ss[ss.size()-2] << " and " << ss[ss.size()-1] << " are";
        }
        sss << " turned off, but referenced here.";
        chart->Error.Warning(linenum_temp[earliest], sss, "It will look strange.");
    }
}


void DirArrow::PostPosProcess(Canvas &canvas, Chart *ch)
{
    if (!valid) return;
    Arrow::PostPosProcess(canvas, ch);
    CheckSegmentOrder(yPos+centerline);
    //Exclude the areas covered by the text from entity lines
    chart->HideEntityLines(text_cover);
    //Exclude the areas covered by the arrow heads from entity lines
    const XY c(sx, yPos+centerline);
    Contour tmp;
    if (curly_join_to)
        tmp = style.read().arrow.read().EntityLineCover(XY(sx, curly_join_to->GetCenterline(curly_join_to_src ? CenterlineSelector::AT_SRC : CenterlineSelector::AT_DST)),
            !is_forward /*<=note this is the inverse*/, bidir, EArrowEnd::START,
            is_forward ? segment_lines.front() : segment_lines.back(),
            is_forward ? segment_lines.front() : segment_lines.back()); //no rotate here
    else
        tmp = style.read().arrow.read().EntityLineCover(XY(sx, yPos+centerline), is_forward, bidir, EArrowEnd::START,
            is_forward ? segment_lines.front() : segment_lines.back(),
            is_forward ? segment_lines.front() : segment_lines.back()).RotateAround(c, slant_angle);
    if (!tmp.IsEmpty()) {
        chart->HideEntityLines(tmp);
    }
    tmp = style.read().arrow.read().EntityLineCover(XY(dx, yPos+centerline), is_forward, bidir, EArrowEnd::END,
                                      !is_forward ? segment_lines.front() : segment_lines.back(),
                                      !is_forward ? segment_lines.front() : segment_lines.back());
    if (!tmp.IsEmpty()) {
        tmp.RotateAround(c, slant_angle);
        chart->HideEntityLines(tmp);
    }
    //for multi-segment arrows
    for (unsigned i=1; i<xPos.size()-1; i++) {
        tmp = style.read().arrow.read().EntityLineCover(XY(xPos[i], yPos+centerline), is_forward, bidir,
                                          skip[i] ? EArrowEnd::SKIP : EArrowEnd::MIDDLE,
                                          segment_lines[i-1], segment_lines[i]);
        if (!tmp.IsEmpty()) {
            tmp.RotateAround(c, slant_angle);
            chart->HideEntityLines(tmp);
        }
    }
}

void DirArrow::RegisterLabels()
{
    chart->RegisterLabel(parsed_label, LabelInfo::ARROW,
        sx_text, dx_text, yPos + chart->arcVGapAbove, cx_text,
        XY(sx, yPos+centerline), slant_angle);
}

void DirArrow::CollectIsMapElements(Canvas &canvas)
{
    parsed_label.CollectIsMapElements(chart->ismapData, canvas, chart->Shapes,
        sx_text, dx_text, yPos + chart->arcVGapAbove, cx_text,
        XY(sx, yPos+centerline), slant_angle);
}

void DirArrow::AddClippedLine(Canvas& canvas, Path path, const LineAttr& line, const MscArrowHeads& loc_arrow, unsigned i, const Block* clip_by) const {
    std::pair arrows(&loc_arrow.GetArrowHead(is_forward, bidir, WhichArrow(i + 1), true),
                     &loc_arrow.GetArrowHead(is_forward, bidir, WhichArrow(i), false));
    if (!is_forward) std::swap(arrows.first, arrows.second);
    if (clip_by && path.size()) {
        if (clip_by->IsWithin(path.front().GetStart()) == contour::WI_OUTSIDE) arrows.second = nullptr;
        if (clip_by->IsWithin(path.back().GetEnd()) == contour::WI_OUTSIDE) arrows.first = nullptr;
        path.ClipRemove(*clip_by);
    }
    if (path.size())
        canvas.Add(GSPath(std::move(path), line, arrows.first, arrows.second));
}


/** Draws an actual arrow using the parameters passed, not considering message loss.
 * Used twice to draw a lost arrow. */
void DirArrow::DrawArrow(Canvas& canvas, const Label& loc_parsed_label,
                         const std::vector<LineAttr>& loc_segment_lines,
                         const MscArrowHeads& loc_arrow, const Block* clip_by) const
{
    if (canvas.does_graphics() && canvas.cannot_draw()) return;
    if (canvas.does_graphics() && clip_by)
        canvas.Clip(*clip_by);
    const double y = yPos+centerline;  //should be integer
    if (loc_parsed_label.getTextWidthHeight().y) {
        if (slant_angle)
            loc_arrow.TransformCanvasForAngle(slant_angle, canvas, sx, y);
        loc_parsed_label.Draw(canvas, chart->Shapes, sx_text, dx_text, yPos + chart->arcVGapAbove, cx_text, slant_angle!=0);
        if (slant_angle)
            loc_arrow.UnTransformCanvas(canvas);
    }
    /* Draw the line */
    if (!canvas.NeedsArrowFix()) canvas.ClipInverse(clip_area);
    //all the entities this (potentially multi-segment arrow visits)
    for (unsigned i = 0; i<xPos.size()-1; i++) {
        double sta_x = xPos[i];   //start of segment
        double end_x = xPos[i+1]; //end of segment
        if (canvas.NeedsArrowFix()) {
            //if an arrowhead has to be drawn to the middle of the line irrespective of
            //entity active status, skip 'act_size'
            const bool skip_act_i = loc_arrow.GetArrowHead(bidir, WhichArrow(i)).IsSymmetricAndNotNone();
            const bool skip_act_ip1 = loc_arrow.GetArrowHead(bidir, WhichArrow(i+1)).IsSymmetricAndNotNone();
            sta_x += skip_act_i ? 0 : act_size[i];
            end_x -= skip_act_ip1 ? 0 : act_size[i+1];
            if (canvas.does_graphics()) {
                //If we do graphics, we substract the area covered by the arrowhead, as well
                sta_x += margins[i].right;
                end_x -= margins[i+1].left;
            }
        }
        if (curly_join_to && ((is_forward && i==0) || (!is_forward && i==xPos.size()-2))) {
            //This is the src segment and we join to our previous element via curly join
            if (canvas.does_graphics())
                canvas.Line(src_line, loc_segment_lines[i]);
            else
                AddClippedLine(canvas, src_line, loc_segment_lines[i], loc_arrow, i, clip_by);
        } else {
            const XY sta(sx + (sta_x-sx)*cos_slant, y + (sta_x-sx)*sin_slant);
            const XY end(sx + (end_x-sx)*cos_slant, y + (end_x-sx)*sin_slant);
            if (canvas.does_graphics())
                canvas.Line(sta, end, loc_segment_lines[i]);
            else
                AddClippedLine(canvas, is_forward ? Path{sta, end} : Path{end, sta}, loc_segment_lines[i], loc_arrow, i, clip_by);
        }
    }
    if (!canvas.NeedsArrowFix()) canvas.UnClip();
    /* Now the arrow heads */
    if (canvas.does_graphics()) {
        if (slant_angle)
            loc_arrow.TransformCanvasForAngle(slant_angle, canvas, sx, y);
        for (unsigned i=0; i<xPos.size(); i++)
            if (curly_join_to && ((is_forward && i==0) || (!is_forward && i==xPos.size()-1))) {
                //This is the src lineend and we join to our previous element via curly join
                if (slant_angle)
                    loc_arrow.UnTransformCanvas(canvas);
                loc_arrow.Draw(canvas, XY(xPos[i], curly_join_to->GetCenterline(curly_join_to_src ? CenterlineSelector::AT_SRC : CenterlineSelector::AT_DST)), act_size[i],
                               !is_forward /*inverse*/, bidir, WhichArrow(i),
                               loc_segment_lines[i - (i==0 ? 0 : 1)], loc_segment_lines[i - (i==xPos.size()-1 ? 1 : 0)]);
                if (slant_angle)
                    loc_arrow.TransformCanvasForAngle(slant_angle, canvas, sx, y);
            } else {
                loc_arrow.Draw(canvas, XY(xPos[i], y), act_size[i], is_forward, bidir, WhichArrow(i),
                               loc_segment_lines[i - (i==0 ? 0 : 1)], loc_segment_lines[i - (i==xPos.size()-1 ? 1 : 0)]);
            }
        if (slant_angle)
            loc_arrow.UnTransformCanvas(canvas);
        if (clip_by)
            canvas.UnClip();
    }
}

void DirArrow::Draw(Canvas &canvas, EMscDrawPass pass) const
{
    if (!valid) return;
    if (pass!=draw_pass) return;
    const Block sb(Range(chart->GetDrawing().x.from, cx_lsym),
                   chart->GetDrawing().y);
    const Block db(Range(cx_lsym, chart->GetDrawing().x.till),
                   chart->GetDrawing().y);
    DrawArrow(canvas, parsed_label, segment_lines, style.read().arrow.read(),
              lost_pos.valid ? is_forward ? &sb : &db : nullptr);
    if (lost_pos.valid) {
        Label loc_parsed_label = parsed_label;
        loc_parsed_label.ApplyStyle(style.read().lost_text);
        std::vector<LineAttr> loc_segment_lines = segment_lines;
        for (auto &segment : loc_segment_lines)
            segment += style.read().lost_line;
        MscArrowHeads loc_arrow = style.read().arrow.read();
        static_cast<TwoArrowAttr&>(loc_arrow) += style.read().lost_arrow.read();
        loc_arrow.CreateArrowHeads();
        DrawArrow(canvas, loc_parsed_label, loc_segment_lines, loc_arrow, is_forward ? &db : &sb);
        if (slant_angle)
            style.read().arrow.read().TransformCanvasForAngle(slant_angle, canvas, sx, yPos+centerline);
        DrawLSym(canvas, XY(cx_lsym, yPos+centerline), lsym_size, style.read().lsym_line);
        if (slant_angle)
            style.read().arrow.read().UnTransformCanvas(canvas);
    }
}
//////////////////////////////////////////////////////////////////////////////////////

BlockArrow::BlockArrow(DirArrow &&dirarrow) :
 DirArrow(std::move(dirarrow)), sig(nullptr)
{
    //No need to unregister a DIR_ARROW: above copy constructor did not register
    const_cast<EArcCategory&>(myProgressCategory) = EArcCategory::BLOCK_ARROW;
    chart->Progress.RegisterItem(EArcCategory::BLOCK_ARROW);
}

//This invocation is from BoxSeries::PostParseProcess
BlockArrow::BlockArrow(const NEntityList &el, bool bidir, EArcSymbol boxsymbol, const LabelledArc &al,
    const BoxSignature *s)
    : DirArrow(el, bidir, boxsymbol, al), //this sets myProgressCategory to BLOCK_ARROW
      sig(s ? new BoxSignature(*s) : nullptr) //create a copy of the signature
{
    slant_angle = 0;
}

BlockArrow::~BlockArrow()
{
    if (sig)
        delete sig;
}

void BlockArrow::SetTemporarilyStoredNotes(StoredNotes&& tmp_notes) {
    _ASSERT(tmp_stored_notes.empty());
    tmp_stored_notes = std::move(tmp_notes);
    for (auto& note : tmp_stored_notes)
        note.note->SetTarget(this);
}

void BlockArrow::SetStyleBeforeAttributes(AttributeList *)
{
    SetStyleWithText("blockarrow", nullptr);
}

const MscStyleCoW *BlockArrow::GetRefinementStyle4ArrowSymbol(EArcSymbol t) const
{
    switch(t) {
    case EArcSymbol::ARC_SOLID:
    case EArcSymbol::ARC_SOLID_BIDIR:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["block->"];
    case EArcSymbol::ARC_DOTTED:
    case EArcSymbol::ARC_DOTTED_BIDIR:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["block>"];
    case EArcSymbol::ARC_DASHED:
    case EArcSymbol::ARC_DASHED_BIDIR:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["block>>"];
    case EArcSymbol::ARC_DOUBLE:
    case EArcSymbol::ARC_DOUBLE_BIDIR:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["block=>"];
    case EArcSymbol::ARC_DBLDBL:
    case EArcSymbol::ARC_DBLDBL_BIDIR:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["block=>>"];
    case EArcSymbol::ARC_DOUBLE2:
    case EArcSymbol::ARC_DOUBLE2_BIDIR:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["block==>"];
    default:
        _ASSERT(0);
        FALLTHROUGH;
    case EArcSymbol::ARC_UNDETERMINED_SEGMENT:
        return nullptr;
    }
}


void BlockArrow::AttributeNames(Csh &csh)
{
    LabelledArc::AttributeNames(csh);
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "angle",
        "Make this arrow slanted by this degree.",
        EHintType::ATTR_NAME));
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "slant_depth",
        "Make this arrow slanted such that the end of it is this many pixels below the start of it. (An alternative way to specify angle.)",
        EHintType::ATTR_NAME));
    csh.AttributeNamesForStyle("blockarrow");
}

bool BlockArrow::AttributeValues(std::string_view attr, Csh &csh)
{
    if (csh.AttributeValuesForStyle(attr, "blockarrow")) return true;
    if (LabelledArc::AttributeValues(attr, csh)) return true;
    if (CaseInsensitiveEqual(attr, "angle") || CaseInsensitiveEqual(attr, "slant_depth")) {
        DirArrow::AttributeValues(attr, csh);
        return true;
    }
    return false;
}

ArcBase* BlockArrow::PostParseProcess(Canvas &canvas, bool hide, EntityRef &left, EntityRef &right,
                                       Numbering &number, MscElement **target, ArcBase *byref_target)
{
    if (!valid) return nullptr;
    //Check that we have no loss specified
    if (lost_at > -2)
        chart->Error.Error(linenum_asterisk, "No support for message loss with block arrows. Ignoring asterisk.");
    if (lost_pos.valid)
        chart->Error.Error(linenum_lost_at, "No support for message loss with block arrows. Ignoring 'lost at' clause.");
    lost_pos.valid = false;
    lost_at = -2;
    //Copy the line attribute to the arrow, as well (arrow.line.* attributes are annulled here)
    style.write().arrow.write().SetLine(style.read().line);
    //Finally determine src and dst entity, check validity of multi-segment ones, add numbering, etc
    return DirArrow::PostParseProcess(canvas, hide, left, right, number, target, byref_target);
}

void BlockArrow::FinalizeLabels(Canvas &canvas)
{
    //Skip the part in DirArrow adding first line spacing
    Arrow::FinalizeLabels(canvas);
}


void BlockArrow::Width(Canvas &canvas, DistanceRequirements &vdist)
{
    if (!valid) return;
    //Add a new element to vdist
    vdist.AddElementTop(this);
    //Add activation status right away
    AddEntityLineWidths(vdist);
    vdist.InsertEntity(src.entity);
    vdist.InsertEntity(dst.entity);

    //fill an "indexes" and "act_size" array
    std::vector<unsigned> indexes;
    indexes.reserve(2+middle.size());
    indexes.push_back(src.entity->index);
    for (unsigned i=0; i<middle.size(); i++)
        indexes.push_back(middle[i]->index);
    indexes.push_back(dst.entity->index);
    //Sort to make them be from left to right
    if (!is_forward) {
        std::reverse(indexes.begin(), indexes.end());
        std::reverse(act_size.begin(), act_size.end());
        std::reverse(segment_lines.begin(), segment_lines.end());
        std::reverse(segment_types.begin(), segment_types.end());
    }

    //Determine if there are multiple segments,
    //if so add text into the appropriate one
    if (middle.size()>0 && style.read().arrow.read().GetArrowHead(bidir, EArrowEnd::MIDDLE).BreaksBlockArrow()) {
        switch (style.read().text.GetIdent()) {
		default:
        case MSC_IDENT_LEFT:   stext = 0; break;
        case MSC_IDENT_CENTER: stext = (unsigned)indexes.size()/2-1; break;
        case MSC_IDENT_RIGHT:  stext = (unsigned)indexes.size()-2; break;
        }
        dtext = stext+1;
    } else {
        //if no segments, then span the whole arrow.
        stext = 0;
        dtext = (unsigned)indexes.size()-1;
    }

    //Set sy and dy
    //sy and dy are at the outer line of the line around the body.
    //We ensure that the outer edge of the body falls on an integer value
    XY twh = parsed_label.getTextWidthHeight();
    ind_off = twh.y + chart->boxVGapInside;
    if (sig && *style.read().indicator) {
        twh.y += GetIndicatorSize().y + chart->boxVGapInside;
        twh.x = std::max(twh.x, GetIndicatorSize().x + 2*chart->boxVGapInside);
    }
    twh.y = std::max(twh.y, style.read().text.getCharHeight(canvas));
    const double height = twh.y + chart->boxVGapInside*2 + 2*segment_lines[stext].LineWidth();
    const double aH = ceil(style.read().arrow.read().bigYExtent(height, is_forward, bidir, nullptr, &segment_lines));
    sy = chart->arcVGapAbove + aH;
    dy = ceil(sy + height);

    DoublePair sp_left_end = style.read().arrow.read().getBigWidthsForSpace(
            is_forward, bidir, is_forward ? EArrowEnd::START : EArrowEnd::END,
            dy-sy, act_size.front(), segment_lines.front());
    DoublePair sp_right_end = style.read().arrow.read().getBigWidthsForSpace(
            is_forward, bidir, is_forward ? EArrowEnd::END : EArrowEnd::START,
            dy-sy, act_size.back(), segment_lines.front());

    sp_left_end.left -= is_forward ? src.offset : dst.offset;
    sp_left_end.right += is_forward ? src.offset : dst.offset;
    sp_right_end.left -= is_forward ? dst.offset : src.offset;
    sp_right_end.right += is_forward ? dst.offset : src.offset;

    //add the distances to keep for verticals only on the outer side of src and dst
    vdist.Insert(indexes.front(), DISTANCE_LEFT,  sp_left_end.left  *cos_slant);
    vdist.Insert(indexes.front(), DISTANCE_RIGHT, sp_left_end.right *cos_slant);
    vdist.Insert(indexes.back(),  DISTANCE_LEFT,  sp_right_end.left *cos_slant);
    vdist.Insert(indexes.back(),  DISTANCE_RIGHT, sp_right_end.right*cos_slant);

    //Collect iterators and distances into arrays
    margins.reserve(2+middle.size()); margins.clear();
    margins.push_back(sp_left_end);
    for (unsigned i=0; i<middle.size(); i++)
        margins.push_back(style.read().arrow.read().getBigWidthsForSpace(
                             is_forward, bidir, skip[i+1] ? EArrowEnd::SKIP : EArrowEnd::MIDDLE,
                             dy-sy, act_size[i+1], segment_lines[i]));
    margins.push_back(sp_right_end);

    for (unsigned i=0; i<indexes.size()-1; i++) {
        //if neighbours
        if (indexes[i] + 1 == indexes[i+1]) {
            vdist.Insert(indexes[i], indexes[i+1],
                         (margins[i].right + margins[i+1].left + chart->compressGap)*cos_slant);
        } else {
            vdist.Insert(indexes[i], indexes[i] + 1,
                         (margins[i].right + chart->compressGap)*cos_slant);
            vdist.Insert(indexes[i+1]-1, indexes[i+1],
                         (margins[i+1].left + chart->compressGap)*cos_slant);
        }
    }

    //calculate text margin. segment_lines is now ordered from smaller x to larger x
    //If label is word wrap, we shall perhaps use something else below than twh.x
    const Contour tcov = parsed_label.Cover(chart->Shapes, 0, twh.x, sy + segment_lines[stext].LineWidth() + chart->boxVGapInside);

    sm = style.read().arrow.read().getBigMargin(tcov, sy, dy, true, is_forward, bidir, WhichArrow(stext),
                                  segment_lines[stext]);
    dm = style.read().arrow.read().getBigMargin(tcov, sy, dy, false, is_forward, bidir, WhichArrow(dtext),
                                  segment_lines[stext]);
    double width_needed = sm + parsed_label.getSpaceRequired() + dm;
    if (stext==0 && dtext == indexes.size()-1) {
        //This is an arrow, where the label is between src and dst
        //Use the same algorithm to set offsets and delay reqs as for
        //DirArrow
        width_needed = ConsiderLabelWidth(width_needed);
    } else if (stext==0) {
        //The arrow has midpoints
        //The label is in the leftmost segment (either src or dst)
        const double offset = is_forward ? src.offset : dst.offset;
        width_needed += offset ? offset : act_size[stext];
        width_needed += act_size[dtext];
    } else if (dtext==indexes.size()-1) {
        //The arrow has midpoints
        //The label is in the rightmost segment (either src or dst)
        const double offset = is_forward ? dst.offset : src.offset;
        width_needed += offset ? offset : act_size[dtext];
        width_needed += act_size[stext];
    } else {
        //The arrow has midpoints
        //The label is in some middle segment
        width_needed += act_size[stext] + act_size[dtext];
    }
    vdist.Insert(indexes[stext], indexes[dtext], width_needed*cos_slant);
    //Add a new element to vdist
    vdist.AddElementBottom(this);
    //Add activation status right away
    AddEntityLineWidths(vdist);
}


void BlockArrow::Layout(Canvas &canvas, AreaList *cover)
{
    if (!valid) return;
    yPos = 0;

    //set sx and dx
    sx = chart->XCoord(src.entity) + src.offset;
    dx = chart->XCoord(dst.entity) + dst.offset;
    //convert dx to transformed width_needed
    if (slant_angle)
        dx = sx + (dx-sx)/cos_slant;

    //prepare xPos (margins were already done in Width)
    xPos.clear(); xPos.reserve(2+middle.size());
    xPos.push_back(sx);
    for (unsigned i=0; i<middle.size(); i++)
        xPos.push_back(sx + (chart->XCoord(middle[i])-sx)/cos_slant);
    xPos.push_back(dx);
    if (sx >= dx)
        std::reverse(xPos.begin(), xPos.end());
    //calculate text margings
    const double sxPos = xPos[stext] + (stext!=0             ? 0 : is_forward ? src.offset : dst.offset);
    const double dxPos = xPos[dtext] + (dtext!=xPos.size()-1 ? 0 : is_forward ? dst.offset : src.offset);
    sx_text = sxPos + sm;
    dx_text = dxPos - dm;
    //substract activation size if either
    // 1) the label do not touch src or dst; or
    // 2) the offset there is zero
    if (sxPos == xPos[stext])
        sx_text += act_size[stext];
    if (dxPos == xPos[dtext])
        dx_text -= act_size[dtext];
    cx_text = (sxPos+dxPos)/2;

    //reflow text if needed
    if (parsed_label.IsWordWrap()) {
        const double overflow = parsed_label.Reflow(canvas, chart->Shapes, dx_text - sx_text);
        OverflowWarning(overflow, "", src.entity, dst.entity);
        //recalculate dy: reuse sy set in Width()
        dy = ceil(sy + parsed_label.getTextWidthHeight().y +
                  chart->boxVGapInside*2 + 2*segment_lines[stext].LineWidth());
    } else {
        CountOverflow(dx_text - sx_text);
    }
    //reuse sy dy set in Width()
    centerline = (sy+dy)/2; //Note that we rotate around (sx, yPos+centerline)

    //text_cover = parsed_label.Cover(sx_text, dx_text, sy+style.read().line.LineWidth()/2 + chart->boxVGapInside, cx_text);
    area = style.read().arrow.read().BigContour(xPos, act_size, sy, dy, is_forward, bidir, nullptr, &segment_lines, outer_contours);
    area.arc = this;
    area_important = style.read().arrow.read().BigHeadContour(xPos, act_size, sy, dy, is_forward, bidir, nullptr, &segment_lines, chart->compressGap);
    area_important += parsed_label.Cover(chart->Shapes, sx_text, dx_text, sy+segment_lines[stext].LineWidth() + chart->boxVGapInside, cx_text);
    area_to_note2 = Block(sx, dx, (sy+dy)/2, (sy+dy)/2).Expand(0.5);
    //due to thick lines we can extend above y==0. Shift down to avoid it
    if (area.GetBoundingBox().y.from < chart->arcVGapAbove)
        ShiftBy(-area.GetBoundingBox().y.from + chart->arcVGapAbove);
    CalculateMainline(chart->nudgeSize);
    if (slant_angle != 0) {
        //OK: all of sx, dx, sx_text, dx_text, cx_text, xPos, act_size, margins
        //are in the transformed width_needed
        //Now we transfrom "area" and "text_cover", too
        const XY c(sx, yPos+centerline);
        area.RotateAround(c, slant_angle);
        area_important.RotateAround(c, slant_angle);
        area_to_note2.RotateAround(c, slant_angle);
    }
    chart->NoteBlockers.Append(this);
    if (cover)
        *cover = GetCover4Compress(area);

    height = area.GetBoundingBox().y.till + chart->arcVGapBelow + *style.read().shadow.offset;
    LayoutComments(canvas, cover);
}

Range BlockArrow::GetVisualYExtent(bool include_comments) const
{
    Range ret = area.GetBoundingBox().y;
    ret.till += *style.read().shadow.offset;
    if (include_comments && ret.till < yPos+comment_height)
        ret.till = yPos+comment_height;
    return ret;
}

void BlockArrow::ShiftBy(double y)
{
    if (!valid) return;
    sy += y;
    dy += y;
    for (auto i = outer_contours.begin(); i!=outer_contours.end(); i++)
        i->Shift(XY(0,y));
    DirArrow::ShiftBy(y); //This shifts clip_area, too, but that shall be empty anyway
}

void BlockArrow::PostPosProcess(Canvas &canvas, Chart *ch)
{
    if (!valid) return;
    CheckSegmentOrder(yPos + centerline);
    if (!valid) return;
    if (sig) {
        controls.push_back(EGUIControlType::EXPAND);
        controls.push_back(EGUIControlType::COLLAPSE);
    }
    Arrow::PostPosProcess(canvas, ch); //Skip DirArrow
    const XY c(sx, yPos+centerline);
    for (auto i = outer_contours.begin(); i!=outer_contours.end(); i++) {
        Contour tmp(*i);
        tmp.RotateAround(c, slant_angle);
        chart->HideEntityLines(tmp);
    }
}

void BlockArrow::RegisterLabels()
{
    chart->RegisterLabel(parsed_label, LabelInfo::ARROW,
        sx_text, dx_text, sy+segment_lines[stext].LineWidth() + chart->boxVGapInside, cx_text,
        XY(sx, yPos+centerline), slant_angle);
}

void BlockArrow::CollectIsMapElements(Canvas &canvas)
{
    parsed_label.CollectIsMapElements(chart->ismapData, canvas, chart->Shapes,
        sx_text, dx_text, sy+segment_lines[stext].LineWidth() + chart->boxVGapInside, cx_text,
        XY(sx, yPos+centerline), slant_angle);
}

void BlockArrow::Draw(Canvas &canvas, EMscDrawPass pass) const
{
    if (!valid) return;
    if (pass!=draw_pass) return;
    if (slant_angle)
        style.read().arrow.read().TransformCanvasForAngle(slant_angle, canvas, sx, yPos+centerline);
    style.read().arrow.read().BigDrawFromContour(outer_contours, nullptr, &segment_lines, style.read().fill, style.read().shadow, canvas, slant_angle*M_PI/180.);
    parsed_label.Draw(canvas, chart->Shapes, sx_text, dx_text, sy+segment_lines[stext].LineWidth() + chart->boxVGapInside, cx_text);
    if (sig && *style.read().indicator)
        DrawIndicator(XY(cx_text, sy+segment_lines[stext].LineWidth() + chart->boxVGapInside+ind_off), &canvas);
    if (slant_angle)
        style.read().arrow.read().UnTransformCanvas(canvas);
}

//////////////////////////////////////////////////////////////////////////////////////

VertXPos::VertXPos(MscChart& m, std::string_view e1, const FileLineColRange& e1l, std::string_view e2, const FileLineColRange& e2l, double off)
    : pos(POS_CENTER), entity1(m.FindAllocEntity(e1, e1l)), entity2(m.FindAllocEntity(e2, e2l))
    , e1line(e1l), e2line(e2l), offset(off)
{
    valid = entity1 && entity2; //if one of the entities is empty, we have already emitted an error (or it is OK to have no VertXPos such as for a Vertical)
}

VertXPos::VertXPos(MscChart& m, std::string_view e1, const FileLineColRange& e1l, EPosType p, double off)
    : pos(p), entity1(m.FindAllocEntity(e1, e1l)), e1line(e1l), offset(off)
{ 

    _ASSERT(IsOneEntityPos()); //dont call this ctor with POS_CENTER
    valid = IsOneEntityPos() && entity1;     //if the entity is empty, we have already emitted an error
}

double VertXPos::CalculatePos(MscChart &chart, double width, double gap, double gap2) const
{
    double xpos = chart.XCoord(entity1);
    EntityRef neighbour = chart.GetNeighbour(entity1, pos == VertXPos::POS_THIRD_RIGHT, false);
    if (neighbour == nullptr) {
        _ASSERT(0);  //We should not have POS_THIRD_* On LNote or RNote, NoEntity or similar
        neighbour = entity1;
    }
    if (gap<0) gap = chart.hscaleAutoXGap;
    if (gap2<0) gap2 = chart.hscaleAutoXGap;
    switch (pos) {
    default: _ASSERT(0); FALLTHROUGH;
    case VertXPos::POS_AT: break;
    case VertXPos::POS_CENTER:      xpos = (xpos + chart.XCoord(entity2))/2; break;
    case VertXPos::POS_LEFT_BY:     xpos -= width/2 + gap2 + gap; break;
    case VertXPos::POS_RIGHT_BY:    xpos += width/2 + gap2 + gap; break;
    case VertXPos::POS_LEFT_SIDE:   xpos -= width/2 + gap; break;
    case VertXPos::POS_RIGHT_SIDE:  xpos += width/2 + gap; break;
    case VertXPos::POS_THIRD_LEFT:
    case VertXPos::POS_THIRD_RIGHT: xpos = (2*xpos +chart.XCoord(neighbour))/3; break;
    };
    return xpos + offset;
}

//Vertical Constructors end up doing the following
//they set 'shape' ARROW_OR_BOX and set the 'type'
//according to the symbol the user has presented us.
//The 'shape' can be later modified if the parser finds some
//vertical type keyword before the symbol, such as 'bracket' or 'pointer'
Vertical::Vertical(const ArcTypePlusDir *t, std::string_view s, std::string_view d, MscChart *msc) :
    Arrow(IsArcSymbolBox(t->arc.type) ? false : IsArcSymbolBidir(t->arc.type), EArcCategory::VERTICAL, msc),
    type(t->arc.type),
    src(s), dst(d), lost(t->arc.lost!=EArrowLost::NOT),
    lost_pos(t->arc.lost_pos.start), shape(ARROW),
    ref_target(nullptr), ypos(2)
{
    _ASSERT(IsArcSymbolArrow(type) || IsArcSymbolBox(type));
    //If we are defined via a backwards arrow, swap source and dst
    if (t->dir==EDirType::LEFT)
        src.swap(dst);
    //store forward for byref verticals, which may have a '<-' arrowsymbol for the arrow to go up.
    if (src == MARKER_HERE_STR && dst == MARKER_HERE_STR)
        forward = t->dir != EDirType::LEFT;
    width_attr.Empty();
    valid = false; //without an x pos we are invalid
}

Vertical* Vertical::AddXpos(VertXPos *p)
{
    valid = true;
    if (p && p->valid) //Note p->valid may be false if the user specified no 'at' clause.
        pos = *p;
    return this;
}

Vertical* Vertical::AddRefPos(VertRefPos* p) {
    _ASSERT(src == MARKER_HERE_STR && dst == MARKER_HERE_STR);
    valid = true;
    if (p)
        by_ref = std::move(*p);
    return this;
}

Arrow *Vertical::AddSegment(ArrowSegmentData /*data*/, std::string_view  /*m*/,
                            const FileLineColRange&/*ml*/,
                            const FileLineColRange& l)
{
    if (!valid) return this; //display error only once
    chart->Error.Error(l.start, "Cannot add further segments to vertical arrow. Ignoring it.");
    valid = false;
    return this;
}

Arrow *Vertical::AddLostPos(gsl::owner<VertXPos*> pos, const FileLineColRange &l)
{
    _ASSERT(0);
    chart->Error.Error(l.start, "Internal error. Ignoring 'at' clause.");
    delete pos;
    return this;
}

Arrow * Vertical::AddStartPos(ArrowEnding * ending)
{
    _ASSERT(0);
    chart->Error.Error(ending->linenum, "Internal error. Ignoring 'start before' clause.");
    delete ending;
    return this;
}

Arrow * Vertical::AddEndPos(ArrowEnding * ending)
{
    _ASSERT(0);
    chart->Error.Error(ending->linenum, "Internal error. Ignoring 'end after' clause.");
    delete ending;
    return this;
}

void Vertical::SetVerticalShape(EVerticalShape sh)
{
    shape = sh;
}

const char* VerticalRefinementStyle(Vertical::EVerticalShape shape) {
    switch (shape) {
    default: _ASSERT(0); FALLTHROUGH;
    case Vertical::ARROW:
    case Vertical::BOX:     return "vertical";
    case Vertical::BRACE:   return "vertical_brace";
    case Vertical::BRACKET: return "vertical_bracket";
    case Vertical::RANGE:   return"vertical_range";
    case Vertical::POINTER: return "vertical_pointer";
    }
}

void Vertical::SetStyleBeforeAttributes(AttributeList *)
{
    //set our style to the base
    style = chart->MyCurrentContext().styles[VerticalRefinementStyle(shape)];
    //re-do the automatic setting of "side"
    if (pos.valid)
        switch (pos.pos) {
        case VertXPos::POS_LEFT_BY:
        case VertXPos::POS_LEFT_SIDE:
        case VertXPos::POS_THIRD_RIGHT:
        left:
            if (!style.read().side) style.write().side = ESide::RIGHT;
            left = true;
            break;
        right:
        default:
            if (!style.read().side) style.write().side = ESide::LEFT;
            left = false;
            break;
        }
    else 
        switch (by_ref.side) {
        default:
        case ESide::INVALID:
        case ESide::END:
            goto right; //this is the default like for 'vertical pointer;'
        case ESide::RIGHT: goto right;
        case ESide::LEFT: goto left;
        }
    //Now add the default text and refinements
    SetStyleWithText(static_cast<const MscStyleCoW*>(nullptr), GetMyRefinementStyle());
}


/** Return the refinement style for this vertical. */
const MscStyleCoW *Vertical::GetMyRefinementStyle() const
{
    switch (type) {
    case EArcSymbol::ARC_SOLID:
    case EArcSymbol::ARC_SOLID_BIDIR:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["vertical->"];
    case EArcSymbol::ARC_DOTTED:
    case EArcSymbol::ARC_DOTTED_BIDIR:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["vertical>"];
    case EArcSymbol::ARC_DASHED:
    case EArcSymbol::ARC_DASHED_BIDIR:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["vertical>>"];
    case EArcSymbol::ARC_DOUBLE:
    case EArcSymbol::ARC_DOUBLE_BIDIR:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["vertical=>"];
    case EArcSymbol::ARC_DBLDBL:
    case EArcSymbol::ARC_DBLDBL_BIDIR:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["vertical=>>"];
    case EArcSymbol::ARC_DOUBLE2:
    case EArcSymbol::ARC_DOUBLE2_BIDIR:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["vertical==>"];
    default:
        _ASSERT(0);
        return nullptr;
    case EArcSymbol::BOX_SOLID:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["vertical--"];
    case EArcSymbol::BOX_DASHED:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["vertical++"];
    case EArcSymbol::BOX_DOTTED:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["vertical.."];
    case EArcSymbol::BOX_DOUBLE:
        return (MscStyleCoW*)&chart->MyCurrentContext().styles["vertical=="];
    }
}

bool Vertical::AddAttribute(const Attribute &a)
{
    if (a.Is("offset")) {
        if (!a.EnsureNotClear(chart->Error, EStyleType::ELEMENT)) return true;
        if (!a.CheckType(EAttrType::NUMBER, chart->Error)) return true;
        //By this time `pos` is filled in, so we can add offset there
        pos.offset += a.number;
        return true;
    }
    if (a.Is("text.width"))
        return width_attr.AddAttribute(a, chart, EStyleType::ELEMENT);
    if (a.Is("layout_warning")) {
        if (a.EnsureNotClear(chart->Error, EStyleType::ELEMENT)
                && a.CheckType(EAttrType::BOOL, chart->Error)) 
            layout_warning = a.yes;
        return true;
    }
    return Arrow::AddAttribute(a);
}

void Vertical::AttributeNames(Csh &csh)
{
    LabelledArc::AttributeNames(csh);
    csh.AttributeNamesForStyle("vertical");
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"offset",
        "Move the vertical to the right by this many pixels (negative value for left).",
        EHintType::ATTR_NAME));
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"text.width",
        "Use this attribute to specify the space available for the text if you want enable word wrapping.",
        EHintType::ATTR_NAME));
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"makeroom",
        "Turn this on if you want this element to push neighbouring entities further to avoid overlap with other elements.",
        EHintType::ATTR_NAME));
    csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME)+"layout_warning",
        "Set this to 'no' to suppress a layout warning, when the vertical is attached to a parallel block series with "
        "'layout=one_by_one_merge' (which is the default layout) and the vertical extends above the parallel block series.",
        EHintType::ATTR_NAME));
}

bool Vertical::AttributeValues(std::string_view attr, Csh &csh, EVerticalShape shape)
{
    if (CaseInsensitiveEqual(attr,"offset")) {
        csh.AddToHints(CshHint(csh.HintPrefixNonSelectable()+"<number> [pixels]",
            "Move the vertical to the right by this many pixels (negative value for left).",
            EHintType::ATTR_VALUE, false));
        return true;
    }
    if (CaseInsensitiveEqual(attr,"makeroom") || CaseInsensitiveEqual(attr, "layout_warnings")) {
        csh.AddYesNoToHints();
        return true;
    }
    if (CaseInsensitiveEqual(attr, "text.width") && WidthAttr::AttributeValues(attr, csh)) return true;

    if (csh.AttributeValuesForStyle(attr, VerticalRefinementStyle(shape))) return true;
    if (LabelledArc::AttributeValues(attr, csh)) return true;
    return false;
}

bool Vertical::IsByRef() const {
    return src == MARKER_HERE_STR && dst == MARKER_HERE_STR;
}

ArcBase* Vertical::PostParseProcess(Canvas &canvas, bool hide, EntityRef &left, EntityRef &right,
                                    Numbering& number, MscElement** target, ArcBase* byref_target)
{
    if (!valid) return nullptr;
    if (lost) {
        const char *s;
        switch (shape) {
        case ARROW:    s = IsArcSymbolArrow(type) ? "Block arrow" : "Box"; break;
        case BOX:      s = "Box"; break;
        case BRACE:    s = "Brace"; break;
        case BRACKET:  s = "Bracket"; break;
        case RANGE:    s = "Range"; break;
        default: _ASSERT(0); FALLTHROUGH;
        case POINTER: s = nullptr;  break;
        }
        if (s)
            chart->Error.Error(lost_pos, string(s) +" verticals cannot indicate loss.", "Ignoring loss symbol.");
    }
    //Ignore hide: we show verticals even if they may be hidden
    if (src == MARKER_HERE_STR && dst == MARKER_HERE_STR) {
        if (by_ref.refname.size()) {
            const auto i = chart->ReferenceNames.find(by_ref.refname);
            if (i == chart->ReferenceNames.end()) {
                chart->Error.Error(by_ref.refname_loc, StrCat("Cannot find element with refname of '", by_ref.refname, "'. Ignoring vertical."));
                return nullptr;
            }
            ref_target = i->second.arc;
            if (!ref_target->CanBeAlignedTo()) {
                chart->Error.Error(by_ref.refname_loc, StrCat("The target element with refname of '", by_ref.refname, "' cannot be aligned to. Ignoring vertical."));
                chart->Error.Error(ref_target->file_pos.start, by_ref.refname_loc, "Here is the referenced element.");
                return nullptr;
            }
        } else if (byref_target == nullptr) { //Try to see if we have a prior element to use
            valid = false;
            chart->Error.Error(file_pos.start, "This vertical has no markers, referred entity and no prior element to align to. Ignoring it.",
                "Either specify markers to set the top and bottom of the vertical or "
                "specify it directly after another element to align to.");
            return nullptr;
        } else
            ref_target = byref_target;
    }
    if (src != MARKER_HERE_STR) {
        auto m = chart->GetMarker(src);
        if (m==nullptr) {
            chart->Error.Error(file_pos.start, "Cannot find marker '" + StringFormat::ConvertToPlainTextCopy(src) + "'."
                               " Ignoring vertical.");
            valid=false;
            return nullptr;
        }
    }

    if (dst != MARKER_HERE_STR) {
        auto m = chart->GetMarker(dst);
        if (m == nullptr) {
            chart->Error.Error(file_pos.start, "Cannot find marker '" + StringFormat::ConvertToPlainTextCopy(dst) + "'."
                               " Ignoring vertical.");
            valid=false;
            return nullptr;
        }
    }

    bool error = chart->ErrorIfEntityGrouped(pos.entity1, file_pos.start);
    error |= chart->ErrorIfEntityGrouped(pos.entity2, file_pos.start);
    if (error) return nullptr;

    //If we do a RANGE, remove arrowheads for box symbols
    if (shape == RANGE && IsArcSymbolBox(type)) {
        style.write().arrow.write().SetType(EArrowEnd::START, EArrowType::NONE);
        style.write().arrow.write().SetType(EArrowEnd::END, EArrowType::NONE);
    }
    //If we do a block arrow copy line attribute to arrow
    if (shape == ARROW || shape == BOX)
        style.write().arrow.write().SetLine(style.read().line);

    //Add numbering, if needed & create arrowheads
    LabelledArc::PostParseProcess(canvas, hide, left, right, number, target, byref_target);

    left = chart->EntityMinByPos(left, pos.entity1);
    right = chart->EntityMaxByPos(right, pos.entity1);
    left = chart->EntityMinByPos(left, pos.entity2);
    right = chart->EntityMaxByPos(right, pos.entity2);

    if (hide)
        return nullptr;

    //If the pos is invalid, it marks a missing 'at' clause.
    //In this case we calculate the pos later in Width().
    if (pos.valid) {
        pos.entity1 = chart->FindActiveParentEntity(pos.entity1);
        if (pos.entity2)
            pos.entity2 = chart->FindActiveParentEntity(pos.entity2);
        if (pos.pos == VertXPos::POS_CENTER && pos.entity1 == pos.entity2)
            pos.pos = VertXPos::POS_AT;
    }

    //Re-adjust gradients so that they look what the user wanted
    //even after rotation
    //static const EGradientType readfrom_left_gardient[] = {
    //    EGradientType::INVALID, EGradientType::NONE, EGradientType::OUTWARD, EGradientType::INWARD,
    //    EGradientType::RIGHT, EGradientType::LEFT, EGradientType::DOWN, EGradientType::UP,
    //    EGradientType::LEFT};
    //static const EGradientType readfrom_right_gardient[] = {
    //    EGradientType::INVALID, EGradientType::NONE, EGradientType::OUTWARD, EGradientType::INWARD,
    //    EGradientType::LEFT, EGradientType::RIGHT, EGradientType::UP, EGradientType::DOWN,
    //    EGradientType::RIGHT};

    if (style.read().fill.gradient_angle) {
        if (style.read().side == ESide::LEFT)
            style.write().fill.RotateGradient(-90);
        else
            style.write().fill.RotateGradient(+90);
    }
    return this;
}

void Vertical::Width(Canvas &canvas, DistanceRequirements &vdist)
{
    const double text_distance = 0; //chart->hscaleAutoXGap
    if (!valid) return;
    //reflow if needed
    if (style.read().side==ESide::END && parsed_label.IsWordWrap()) {
        //This works only for side==END really
        //for rotated text we cannot go back and increase space req...
        const double w = width_attr.GetWidth(canvas, chart->Shapes, style.read().text);
        if (w>0) {
            const double overflow = parsed_label.Reflow(canvas, chart->Shapes, w);
            OverflowWarning(overflow, "Use the 'text.width' attribute to increase text width.", nullptr, nullptr);
        } else
            chart->Error.Warning(file_pos.start, "Word wrapping is on, but no meaningful width is specified.", "Ignoring word wrapping. Try setting 'text.width'.");
    }
    //The offset is ignored during the process of setting space requirements
    const XY twh = parsed_label.getTextWidthHeight();
    const double lw = style.read().line.LineWidth();
    radius = std::max(0., *style.read().line.radius);
    if (style.read().side == ESide::END)
        width = twh.x;
    else
        width = twh.y;
    switch (shape) {
    default:
        _ASSERT(0);
        FALLTHROUGH;
    case ARROW:
    case BOX:
        //Here body width is the width of the box or arrow body
        if (width==0)
            width = style.read().text.getCharHeight(canvas);
        width += 2*lw + 2*chart->boxVGapInside;
        width += fmod_negative_safe(width, 2.); //width is even integer now: the distance from outer edge to outer edge
        //ext width is the tip of the arrowhead, if any
        ext_width = style.read().arrow.read().bigYExtent(width, false, bidir, &style.read().line, nullptr);
        break;
    case RANGE:  {
        //Here body with is all the width of the range
        double w = lw/2;
        //getHeight().y is _half_ the height of the arrowhead
        //(the height above/below the centerline)
        w = std::max(w, style.read().arrow.read().getHeight(bidir, EArrowEnd::END, style.read().line, style.read().line));
        w = std::max(w, style.read().arrow.read().getHeight(bidir, EArrowEnd::START, style.read().line, style.read().line));
        width = std::max(width + text_distance + lw/2, //space between text and range
                         *style.read().line.radius);
        width = std::max(width, w);
        _ASSERT(style.read().line.IsComplete());
        //we set vline_off here so that it becomes easier to calculate it later
        vline_off = std::max(w, *style.read().line.radius);
        width += vline_off;
        //no ext width
        ext_width = radius;
        }
        break;
    case BRACE:
        //Here body with is all the width of the brace/bracket, plus text
        if (width)
            width += text_distance; //space between text and brace
        _ASSERT(style.read().line.IsComplete());
        vline_off = radius + lw;
        width += vline_off + radius;
        ext_width = radius;
        break;
    case BRACKET:
    case POINTER:
        auto who = ArrowHead(EArrowType::DIAMOND, false, *style.read().lsym_size).
            WidthHeight(style.read().line, style.read().line /*dummy*/);
        lsym_size = XY(who.before_x_extent, who.half_height) * LSYM_SIZE_ADJ;
        //Here body with is all the width of the brace/bracket, plus text
        if (width)
            width += text_distance; //space between text and brace
        _ASSERT(style.read().line.IsComplete());
        if (lost) {
            width = std::max(width, std::max(lw/2, lsym_size.x));
            //This is the distance from the edge of the object to the midline
            //of the horizontal arrow.
            vline_off = std::max(std::max(lsym_size.x, radius), lw/2);
            width += vline_off;
        } else {
            width += radius + lw;
            vline_off = radius + lw/2;
        }
        ext_width = radius;
        break;
    }

    const bool both_none = (src==MARKER_HERE_STR && dst == MARKER_HERE_STR);
    const auto si = both_none ? vdist.GetIterator(ref_target, true) :
                      src==MARKER_HERE_STR ? vdist.GetIteratorEnd() :
                           vdist.GetIterator(src, true);                  //we assume src is higher than dst
    const auto di = both_none ? vdist.GetIterator(ref_target, false) :
                      dst==MARKER_HERE_STR ? vdist.GetIteratorEnd() :
                           vdist.GetIterator(dst, false);
    //If pos.pos is invalid, we select a location.
    const DistanceRequirementsSection elem = vdist.Get(si, di);
    if (!pos.valid) {
        const bool left = style.read().side == ESide::RIGHT;
        const unsigned i = left ? elem.QueryLeftEntity() : elem.QueryRightEntity();
        pos.pos = left ? VertXPos::POS_LEFT_SIDE
            : VertXPos::POS_RIGHT_SIDE;
        if (i==DistanceRequirementsSection::NO_LEFT_ENTITY)
            pos.entity1 = chart->LSide;
        else if (i==DistanceRequirementsSection::NO_RIGHT_ENTITY)
            pos.entity1 = chart->RSide;
        else
            pos.entity1 = *chart->ActiveEntities.Find_by_Index(i);
        pos.offset = 0;
        pos.valid = true;
    }
    _ASSERT(pos.entity1);

    const unsigned index = pos.entity1->index;
    double displace;
    switch (pos.pos) {
    case VertXPos::POS_LEFT_BY:
    case VertXPos::POS_LEFT_SIDE:
        displace = -elem.Query(index, DISTANCE_LEFT);
        break;
    case VertXPos::POS_RIGHT_BY:
    case VertXPos::POS_RIGHT_SIDE:
        displace = elem.Query(index, DISTANCE_RIGHT);
        break;
    default:
        displace = 0;
        break;
    };

    //If we are a pointer, make room for the arrowheads
    if (shape == POINTER) {
        const double ah_width = std::max(
            style.read().arrow.read().getWidths(true, bidir, EArrowEnd::START,
                                  style.read().line, style.read().line).right,
            style.read().arrow.read().getWidths(true, bidir, EArrowEnd::END,
                                  style.read().line, style.read().line).left);
        displace = std::max(fabs(displace), ah_width); //positive
        //We do not move pos.offset here, instead, we enlarge the vertical's width
        width += displace;
        vline_off += displace;
        //Finally, move so that arrowhead touches the entity line
        //(VertXPos::CalculatePos() leaves a small gap)
        switch (pos.pos) {
        case VertXPos::POS_LEFT_BY:
        case VertXPos::POS_LEFT_SIDE:
            pos.offset += chart->hscaleAutoXGap;
            break;
        case VertXPos::POS_RIGHT_BY:
        case VertXPos::POS_RIGHT_SIDE:
            pos.offset -= chart->hscaleAutoXGap;
            break;
        default:
            break;
        }
    } else //for others make room for what is already there
        pos.offset += displace;

    //Make vline_off signed
    if (!left)
        vline_off *= -1;

    //No extra space requirement -> exit here
    if (!*style.read().makeroom) return;

    switch (pos.pos) {
    default:
    case VertXPos::POS_THIRD_LEFT:
    case VertXPos::POS_THIRD_RIGHT:
        _ASSERT(0); FALLTHROUGH;  //these should not appear here, used only for loss, start and end at
    case VertXPos::POS_AT:
        vdist.Insert(index, DISTANCE_LEFT, (width+ext_width)/2 - pos.offset, si, di);
        vdist.Insert(index, DISTANCE_RIGHT, (width+ext_width)/2 + pos.offset, si, di);
        break;
    case VertXPos::POS_CENTER:
        vdist.Insert(index, pos.entity2->index, width+ext_width + pos.offset);
        break;
    //For the below four we add chart->hscaleAutoXGap, because VertXPos::CalculatePos() applies an
    //extra 'gap' (==chart->hscaleAutoXGap) between the arrow and the entity line.
    case VertXPos::POS_LEFT_BY:
        vdist.Insert(index, DISTANCE_LEFT, width + ext_width - pos.offset + chart->hscaleAutoXGap, si, di);
        break;
    case VertXPos::POS_RIGHT_BY:
        vdist.Insert(index, DISTANCE_RIGHT, width + ext_width + pos.offset + chart->hscaleAutoXGap, si, di);
        break;
    case VertXPos::POS_LEFT_SIDE:
        vdist.Insert(index, DISTANCE_LEFT, width - pos.offset + chart->hscaleAutoXGap, di, si);
        break;
    case VertXPos::POS_RIGHT_SIDE:
        vdist.Insert(index, DISTANCE_RIGHT, width + pos.offset + chart->hscaleAutoXGap, si, di);
        break;
    };
}

//Height and parameters of this can only be calculated in PostPosProcess, when all other edges are set
//So here we do nothing. yPos is not used for this
void Vertical::Layout(Canvas &/*canvas*/, AreaList * /*cover*/)
{
    //We will not have notes or comments, so no need to call CommentHeight()
    _ASSERT(comments.size()==0);
    //Set height to zero not to disturb layouting and automatic pagination
    height = 0;
    _ASSERT(GetRefTarget()==nullptr); //By Ref Verticals are laid out in LayoutByRef()
}

bool Vertical::LayoutByRef(Canvas& canvas, const AreaList& target_cover, AreaList* cover) {
    DoLayout(canvas, &target_cover);
    if (cover)
        *cover = GetCover4Compress(area);
    return *style.read().makeroom;
}


void Vertical::ShiftBy(double y) {
    Arrow::ShiftBy(y);
    for (double& yp : ypos)
        yp += y;
    clip_block.Shift(XY(0, y));
    clip_line.Shift(XY(0, y));
    if (style.read().side == ESide::LEFT || style.read().side == ESide::RIGHT) {
        s_text += y;
        d_text += y;
    } else if (style.read().side == ESide::END)
        t_text += y;
    for (Contour& c : outer_contours) 
        c.Shift(XY(0, y));
}

/** Creates a quarter of a circle's belt
 * @param [in] x The center
 * @param [in] y The center
 * @param [in] r The radius of the midline of the belt
 * @param [in] lw The width of the belt
 * @param [in] q Which quarter: 0:lower-right, 1:lower-left, 2: upper-left, 3: upper-right
 * @returns a contour.*/
Contour BeltQuarter(double x, double y, double r, double lw, unsigned q)
{
    _ASSERT(q<=3);
    if (r<=lw || lw<=0) return Contour();
    Path edges;
    edges.AppendEllipse(XY(x, y), r+lw/2, 0, 0, 90*q, 90*(q+1));
    edges.AppendEllipse(XY(x, y), r-lw/2, 0, 0, 90*(q+1), 90*q, false, true, true);
    return Contour(edges);
}


void Vertical::DoLayout(Canvas &canvas, const AreaList* target_cover)
{
    if (!valid) return;
    if (src!=MARKER_HERE_STR || dst!=MARKER_HERE_STR) {
        //Here we are sure markers are OK
        //all below are integers. yPos is such, in general. "Markers" are yPos of the markers
        ypos[0] = src == MARKER_HERE_STR ? yPos : chart->GetMarker(src)->y;
        ypos[1] = dst == MARKER_HERE_STR ? yPos : chart->GetMarker(dst)->y;
        if (ypos[0] == ypos[1]) {
            chart->Error.Error(file_pos.start, "This vertical is of zero size. Ignoring it.");
            valid = false;
            return;
        }
        forward = ypos[0] < ypos[1];
        if (!forward)
            swap(ypos[0], ypos[1]); //ypos[0] now < ypos[1]
        _ASSERT(!target_cover);
    } else if (!ref_target) {
        _ASSERT(0); // this should have been caught in PostParseProcess()
        valid = false;
        return;
    } else {
        _ASSERT(target_cover);
        if (target_cover->IsEmpty() || target_cover->GetBoundingBox().y.Spans() < 1) {
            chart->Error.Error(file_pos.start, "The reference element for this vertical does not appear. Cannot show vertical either.");
            chart->Error.Error(ref_target->file_pos.start, file_pos.start, "Here is the referenced element.");
            valid = false;
            return;        
        }
        ypos[0] = target_cover->GetBoundingBox().y.from;
        ypos[1] = target_cover->GetBoundingBox().y.till;
        //forward already set in ctor for this case. ypos[0]<ypos[1] must hold, so we never swap.
    }

    const double lw = style.read().line.LineWidth();
    const double lw2 = lw/2;
    XY twh = parsed_label.getTextWidthHeight();
    const Contour text_cover = parsed_label.Cover(chart->Shapes, 0, twh.x, lw);
    double sm, dm; //up/down margins
    if (style.read().side == ESide::END) {
        dm = sm = lw;
    } else switch (shape) {
        case ARROW:
        case BOX:
            sm = style.read().arrow.read().getBigMargin(text_cover, 0, twh.y+2*lw, true,
                                          true, bidir, EArrowEnd::START, style.read().line);
            dm = style.read().arrow.read().getBigMargin(text_cover, 0, twh.y+2*lw, false,
                                          true, bidir, EArrowEnd::END, style.read().line);
            break;
        case RANGE:
            sm = lw + std::max(lw/2, style.read().arrow.read().getWidths(true, bidir, EArrowEnd::START, style.read().line, style.read().line).right);
            dm = lw + std::max(lw/2, style.read().arrow.read().getWidths(true, bidir, EArrowEnd::END, style.read().line, style.read().line).left);
            break;
        case POINTER:
            sm = 0;
            dm = lost ? lsym_size.y + (style.read().line.corner == ECornerType::NONE ?
                                       0 : radius) : 0;
            break;
        default:
            dm = sm = 0;
    }
    //calculate y coordinates of text boundaries
    switch (*style.read().side) {
    case ESide::LEFT:
    case ESide::RIGHT:
        s_text = ypos[0] + (forward ? sm : dm);
        d_text = ypos[1] - (forward ? dm : sm);
        break;
        //s_text = ypos[1] - (forward ? sm : dm);
        //d_text = ypos[0] + (forward ? dm : sm);
        break;
    case ESide::END:
        t_text = (ypos[0] + ypos[1] - twh.y)/2;
        break;
    default:
        _ASSERT(0);
        break;
    }
    //Warn if label does not fit
    if ((style.read().side==ESide::END && ypos[1]-ypos[0] < twh.y) ||
        (style.read().side!=ESide::END && ypos[1]-ypos[0] < fabs(s_text-d_text)))
        chart->Error.Warning(file_pos.start, "Too little vertical space for label - may look bad.");

    xpos = pos.CalculatePos(*chart, width, chart->hscaleAutoXGap, ext_width/2);
    //xpos is now the middle of the vertical object.
    //This is good for arrow/box, but not for others, since
    //for bracket/brace/pointer/range it should be the main vertical line
    //We will adjust xpos below.
    //Plus we calculate the x coordinates of the text
    switch (shape) {
    default:
        _ASSERT(0); FALLTHROUGH;
    case ARROW:
    case BOX:
    //adjust xpos and width
        width -= lw; //not necessarily integer, the distance from midline to midline
        xpos = floor(xpos + 0.5); //xpos is integer now: the centerline of arrow if arrow
        switch (*style.read().side) {
        case ESide::LEFT:
            t_text = xpos + twh.y/2; break;
        case ESide::RIGHT:
            t_text = xpos - twh.y/2; break;
        case ESide::END:
            s_text = xpos - twh.x/2;
            d_text = xpos + twh.x/2;
            break;
        default:
            _ASSERT(0);
            break;
        }
        break;
    case POINTER:
    case BRACKET:
    case BRACE:
    case RANGE:
        switch (*style.read().side) {
        case ESide::LEFT:
            if (left) t_text = xpos - width/2 + twh.y;
            else t_text = xpos + width/2;
            break;
        case ESide::RIGHT:
            if (left) t_text = xpos - width/2;
            else t_text = xpos + width/2 - twh.y;
            break;
        case ESide::END:
            if (left) {
                s_text = xpos - width/2;
                d_text = s_text + twh.x;
            } else {
                d_text = xpos + width/2;
                s_text = d_text - twh.x;
            }
            break;
        default:
            _ASSERT(0);
            break;
        }
        xpos += (left ? 1 : -1) * (width/2) - vline_off;
        break;
    }
    //'xpos' is now the middle of the arrow/box;
    //for bracket/brace/pointer/range it is the main vertical line
    //All of 's_text', 'd_text', 't_text' is filled

    //Copy text cover to 'area_important', re-used below
    area_important = parsed_label.Cover(chart->Shapes, s_text, d_text, t_text, *style.read().side);

    //Now set 'area'
    switch (shape) {
    default:
        _ASSERT(0); FALLTHROUGH;
    case ARROW:
    case BOX:
    {
            const double ss = style.read().arrow.read().getBigWidthsForSpace(
                                  forward, bidir, forward ? EArrowEnd::START : EArrowEnd::END,
                                  twh.y+2*lw, 0, style.read().line).left;
            const double ds = style.read().arrow.read().getBigWidthsForSpace(
                                  forward, bidir, forward ? EArrowEnd::END: EArrowEnd::START,
                                  twh.y+2*lw, 0, style.read().line).right;
            if (ss+ds+chart->compressGap > ypos[1]-ypos[0]) {
                chart->Error.Warning(file_pos.start, "Size of vertical element is too small to draw arrowheads. Ignoring it.");
                valid = false;
                return;
            }

	        //Generate area
            std::vector<double> act_size(2, 0);
            //use inverse of forward, swapXY will do the job
            area = style.read().arrow.read().BigContour(ypos, act_size, xpos-width/2, xpos+width/2,
                                          forward, bidir, &style.read().line, nullptr, outer_contours);
            area.SwapXY();
            for (auto i = outer_contours.begin(); i!=outer_contours.end(); i++)
                i->SwapXY();
            break; //ARROW_OR_BOX
        }
    case RANGE:
        {
            area = area_important;
            area += Block(xpos-lw2, xpos+lw2, ypos[0], ypos[1]);
            area += Block(xpos-radius-lw2, xpos+radius+lw2, ypos[0], ypos[0]+lw);
            area += Block(xpos-radius-lw2, xpos+radius+lw2, ypos[1], ypos[1]-lw);
            //Now add arrowhead contours
            const XY cs(xpos, ypos[0]+lw/2);
            const XY cd(xpos, ypos[1]-lw/2);
            area += style.read().arrow.read().Cover(cs, 0, forward, bidir,
                forward ? EArrowEnd::START : EArrowEnd::END, style.read().line, style.read().line
                ).RotateAround(cs, 90);
            area += style.read().arrow.read().Cover(cd, 0, forward, bidir,
                forward ? EArrowEnd::END : EArrowEnd::START, style.read().line, style.read().line
                ).RotateAround(cd, 90);
        }
        break;
    case POINTER:
    case BRACKET: {
        area = area_important;
        const Block rect(xpos, xpos+2*vline_off, ypos[0]+lw2, ypos[1]-lw2);
        const Block total(xpos-vline_off, xpos+vline_off, ypos[0], ypos[1]);
        clip_line = clip_block = Block(xpos+vline_off, xpos+2*vline_off+(left?2*lw:-2*lw), ypos[0]-lw*2, ypos[1]+lw*2);
        if (shape==POINTER) {
            const XY cs(xpos+vline_off, ypos[0]+lw/2);
            const XY cd(xpos+vline_off, ypos[1]-lw/2);
            const Block tot_up(total.x, Range(total.y.from, total.y.MidPoint()));
            const Block tot_dn(total.x, Range(total.y.MidPoint(), total.y.till));
            //Adjust clip for arrowheads
            if (canvas.does_graphics()) {
                clip_line += style.read().arrow.read().ClipForLine(cs, 0, forward != left,
                               bidir, forward ? EArrowEnd::START : EArrowEnd::END,
                               style.read().line, style.read().line);
                clip_line += style.read().arrow.read().ClipForLine(cd, 0, forward == left,
                                bidir, forward ? EArrowEnd::END : EArrowEnd::START,
                                style.read().line, style.read().line);
            }
            //Add arrowheads to area
            area += style.read().arrow.read().Cover(cs, 0, forward != left,
                bidir, forward ? EArrowEnd::START : EArrowEnd::END,
                style.read().line, style.read().line);
            area += style.read().arrow.read().Cover(cd, 0, forward == left,
                bidir, forward ? EArrowEnd::END : EArrowEnd::START,
                style.read().line, style.read().line);
        }
        area += (style.read().line.CreateRectangle_OuterEdge(rect) -
                 style.read().line.CreateRectangle_InnerEdge(rect)) - clip_line;
        if (shape==POINTER && lost)
            area += Block(xpos - lsym_size.x/2, xpos + lsym_size.x/2,
                ypos[1] - lsym_size.y/2, ypos[1] +  lsym_size.y/2);
        }
        break;
    case BRACE:
        area = area_important;
        //Limit radius to what fits the height
        //(We can only do so here, for all other width etc values
        //were needed to be consistently calculated with the original radius)
        radius = std::max(0., std::min(radius, (ypos[1]-ypos[0]-2*lw)/4));
        const double ymid = (ypos[0]+ypos[1])/2;
        const double xoff = left ? radius : -radius;
        const double lw2off = left ? lw2 : -lw2;
        area += Block(xpos+xoff, xpos+xoff+lw2off, ypos[0], ypos[0]+lw);
        area += BeltQuarter(xpos+xoff, ypos[0]+radius+lw/2, radius, lw, left ? 2 : 3);
        area += Block(xpos-lw/2, xpos+lw/2, ypos[0]+radius+lw/2, ymid-radius);
        area += BeltQuarter(xpos-xoff, ymid-radius, radius, lw, left ? 0 : 1);
        area += BeltQuarter(xpos-xoff, ymid+radius, radius, lw, left ? 3 : 2);
        area += Block(xpos-lw/2, xpos+lw/2, ymid+radius, ypos[1]-radius-lw/2);
        area += BeltQuarter(xpos+xoff, ypos[1]-radius-lw/2, radius, lw, left ? 1 : 0);
        area += Block(xpos+xoff, xpos+xoff+lw2off, ypos[1], ypos[1]-lw);
        area += Block(xpos-xoff, xpos-xoff-lw2off, ymid-lw2, ymid+lw2);
    }

    area.arc = this;
    area_to_note2 = Block(xpos, xpos, ypos[0], ypos[1]).Expand(0.5);
    chart->NoteBlockers.Append(this);
    //set yPos and height so that GetFormalYExtent returns the right range
    //to determine if this element needs to be drawn on a particular page
    yPos = ypos[0];
    height = ypos[1]-ypos[0];
}

Range Vertical::GetVisualYExtent(bool include_comments) const
{
    Range ret = area.GetBoundingBox().y;
    //extend with shadow, if we may draw one
    if (shape == ARROW || shape == BOX)
        //Use ypos[1], since BB.y.till may be below it due to text being longer
        ret += ypos[1] + *style.read().shadow.offset;
    if (include_comments && ret.till < yPos+comment_height)
        ret.till = yPos+comment_height;
    return ret;
}


void Vertical::PostPosProcess(Canvas &canvas, Chart *ch)
{
    if (!valid) return;
    //Expand area and add us to chart's all covers list
    Arrow::PostPosProcess(canvas, ch);
}

void Vertical::RegisterLabels()
{
    chart->RegisterLabel(parsed_label, LabelInfo::VERTICAL,
                         s_text, d_text, t_text, *style.read().side);
}

void Vertical::CollectIsMapElements(Canvas &canvas)
{
    parsed_label.CollectIsMapElements(chart->ismapData, canvas, chart->Shapes,
                                      s_text, d_text, t_text, *style.read().side);
}


/** Draws the line and arrowhead of a brace, pointer or lost_pointer
 * clip_by is used when we need to draw the first or second half of it;
 * start is true if we need to draw the first half.*/
void Vertical::DrawBraceLostPointer(Canvas &canvas, const LineAttr &line,
                                    const MscArrowHeads &arrow,
                                    const Contour* clip_by, bool start) const
{
    const double lw = line.LineWidth();
    const Block rect(xpos, xpos+2*vline_off, ypos[0]+lw/2, ypos[1]-lw/2);

    if (canvas.does_graphics()) {
        if (clip_by) canvas.Clip(*clip_by);
        canvas.ClipInverse(clip_line);
    canvas.Line(rect, line); //This will have rounde edges, if so set
        canvas.UnClip();
        if (shape==POINTER) {
            const XY cs(xpos+vline_off, ypos[0]+lw/2);
            const XY cd(xpos+vline_off, ypos[1]-lw/2);
            arrow.Draw(canvas, cs, 0, forward != left,
                       bidir, forward ? EArrowEnd::START : EArrowEnd::END,
                       style.read().line, style.read().line);
            arrow.Draw(canvas, cd, 0, forward == left,
                       bidir, forward ? EArrowEnd::END : EArrowEnd::START,
                       style.read().line, style.read().line);
        }
        if (clip_by) canvas.UnClip();
    } else {
        Path path = line.CreateRectangle_Midline(rect);
        path.ClipRemove(clip_block, true);
        if (left) path.Invert();
        if (clip_by) path.ClipRemove(*clip_by);
        const ArrowHead* aE = nullptr, * aS = nullptr;
        if (shape==POINTER) {
            if (!clip_by || !start)
                aE = &arrow.GetArrowHead(forward, bidir, forward ? EArrowEnd::END : EArrowEnd::START, true);
            if (!clip_by || start)
                aS = &arrow.GetArrowHead(forward, bidir, forward ? EArrowEnd::START : EArrowEnd::END, false);
        }
        canvas.Add(GSPath(std::move(path), line, aE, aS));
    }
}

void Vertical::Draw(Canvas &canvas, EMscDrawPass pass) const
{
    if (!valid) return;
    if (pass!=draw_pass) return;

    const double lw = style.read().line.LineWidth();
    //Draw shape
    switch (shape) {
    case ARROW:
    case BOX:
        style.read().arrow.read().BigDrawFromContour(outer_contours, &style.read().line, nullptr, style.read().fill, style.read().shadow, canvas);
        break;
    case RANGE: {
        //small tips
        LineAttr solid = style.read().line;
        solid.type = ELineType::SOLID;
        const XY T1[2] = { {xpos-radius-lw/2, ypos[0]+lw/2}, {xpos+radius+lw/2, ypos[0]+lw/2} };
        const XY T2[2] = { {xpos-radius-lw/2, ypos[1]-lw/2}, {xpos+radius+lw/2, ypos[1]-lw/2} };
        if (canvas.does_graphics()) {
            canvas.Line(T1[0], T1[1], solid);
            canvas.Line(T2[0], T2[1], solid);
        } else {
            canvas.Add(GSPath({ T1[0], T1[1] }, solid));
            canvas.Add(GSPath({ T2[0], T2[1] }, solid));
        }


        //now the line.
        if (canvas.does_graphics()) {
            //Clip first.
            const XY cs(xpos, ypos[0]+lw/2);
            const XY cd(xpos, ypos[1]-lw/2);
            Contour clip_area = style.read().arrow.read().ClipForLine(cs, 0, forward, bidir,
                                                                      forward ? EArrowEnd::START : EArrowEnd::END,
                                                                      style.read().line, style.read().line
            ).RotateAround(cs, 90);
            Contour ca = style.read().arrow.read().ClipForLine(cd, 0, forward, bidir,
                                                               forward ? EArrowEnd::END : EArrowEnd::START,
                                                               style.read().line, style.read().line
            ).RotateAround(cd, 90);
            clip_area += ca;
            canvas.ClipInverse(clip_area);
            canvas.Line(XY(xpos, ypos[0]), XY(xpos, ypos[1]),
                        style.read().line);
            canvas.UnClip();
            //Now arrowheads
            style.read().arrow.read().TransformCanvasForAngle(90, canvas, cs.x, cs.y);
            style.read().arrow.read().Draw(canvas, cs, 0, forward, bidir,
                                           forward ? EArrowEnd::START : EArrowEnd::END,
                                           style.read().line, style.read().line);
            style.read().arrow.read().UnTransformCanvas(canvas);
            style.read().arrow.read().TransformCanvasForAngle(90, canvas, cd.x, cd.y);
            style.read().arrow.read().Draw(canvas, cd, 0, forward, bidir,
                                           forward ? EArrowEnd::END : EArrowEnd::START,
                                           style.read().line, style.read().line);
            style.read().arrow.read().UnTransformCanvas(canvas);
        } else {
            canvas.Add(GSPath({ XY(xpos, ypos[0]), XY(xpos, ypos[1]) }, style.read().line,
                              &style.read().arrow.read().GetArrowHead(forward, bidir, forward ? EArrowEnd::END : EArrowEnd::START, true),
                              &style.read().arrow.read().GetArrowHead(forward, bidir, forward ? EArrowEnd::START : EArrowEnd::END, false)));
        }
    }
        break;
    case POINTER:
        if (lost) {
            Contour clip_up = Block(xpos-vline_off/2, xpos+vline_off/2, ypos[0], ypos[1]-lw);
            clip_up += Block(xpos+vline_off/2, xpos+3*vline_off/2, ypos[0]-100, (ypos[0]+ypos[1])/2);
            //temorarily increase size to shift corner out of the clip range
            ypos[1] += radius;
            DrawBraceLostPointer(canvas, style.read().line, style.read().arrow.read(), &clip_up, true);
            ypos[1] -= radius;
            Contour clip_dn = Block(xpos-vline_off/2, xpos+vline_off/2, ypos[1]-lw, ypos[1]);
            clip_dn += Block(xpos+vline_off/2, xpos+3*vline_off/2, (ypos[0]+ypos[1])/2, ypos[1]+100);
            LineAttr line = style.read().line;
            line += style.read().lost_line;
            line.corner = ECornerType::NONE;
            MscArrowHeads arrow = style.read().arrow.read();
            static_cast<TwoArrowAttr&>(arrow) += style.read().lost_arrow.read();
            arrow.CreateArrowHeads();
            DrawBraceLostPointer(canvas, line, arrow, &clip_dn, false);
            DrawLSym(canvas, XY(xpos, ypos[1]-lw/2), lsym_size, style.read().lsym_line);
       } else //Not lost
    case BRACKET:
        DrawBraceLostPointer(canvas, style.read().line, style.read().arrow.read());
        break;
    case BRACE:
        const double ymid = (ypos[0]+ypos[1])/2;
        const double xoff = left ? radius : -radius;
        const double lw2off = left ? lw/2 : -lw/2;
        Path edges;
        edges.reserve(18);
        edges.emplace_back(XY(xpos+xoff+lw2off, ypos[0]+lw/2), XY(xpos+xoff, ypos[0]+lw/2));
        edges.AppendEllipse(XY(xpos+xoff, ypos[0]+radius+lw/2), radius, 0, 0, left ? 270 : 270, left ? 180 : 360, !left);
        edges.emplace_back(XY(xpos, ypos[0]+radius+lw/2), XY(xpos, ymid-radius));
        edges.AppendEllipse(XY(xpos-xoff, ymid-radius), radius, 0, 0, left ? 0 : 180, left ? 90 : 90, left);
        const size_t mid = edges.size();
        edges.AppendEllipse(XY(xpos-xoff, ymid+radius), radius, 0, 0, left ? 270 : 270, left ? 360 : 180, left);
        edges.emplace_back(XY(xpos, ymid+radius), XY(xpos, ypos[1]-radius-lw/2));
        edges.AppendEllipse(XY(xpos+xoff, ypos[1]-radius-lw/2), radius, 0, 0, left ? 180 : 0, left ? 90 : 90, !left);
        edges.emplace_back(XY(xpos+xoff, ypos[1]-lw/2), XY(xpos+xoff+lw2off, ypos[1]-lw/2));
        edges.emplace_back(XY(xpos+xoff+lw2off, ypos[1]-lw/2), XY(xpos+xoff+lw2off, ypos[0]+lw/2), false);
        if (canvas.does_graphics())
            canvas.Line(edges, style.read().line);
        else {
            //When adding as one path, the cusp of the brace is too long in PPT
            //TODO: add a GShape for braces.
            canvas.Add(GSPath(Path(edges.begin(), edges.begin()+mid), style.read().line));
            canvas.Add(GSPath(Path(edges.begin()+mid, edges.end()), style.read().line));
        }
    };
    //draw text
    parsed_label.Draw(canvas, chart->Shapes, s_text, d_text, t_text, *style.read().side);
}

