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

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

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

    You should have received a copy of the GNU Affero General Public License
    along with Msc-generator.  If not, see <http://www.gnu.org/licenses/>.
*/
/** @file mscarrow.cpp The implementation of arrowhead classes for signalling charts.
 * @ingroup libcgencommon_files */

#include "mscarrow.h"
#include "canvas.h"

using namespace msc;

/** Tells how wide and high a specific arrowhead. The y coordinate is just half the height.*/
double MscArrowHeads::getHeight(bool bidir, EArrowEnd which, const LineAttr &mainline_before, const LineAttr &mainline_after) const
{
    return GetArrowHead(bidir, which).
        WidthHeight(mainline_before, mainline_after).
        half_height;
}


/** Tells how much of the arrow line is covered by the arrowhead (on both sides of the entity line).
 * Used for text margin calculation.
 * @param [in] forward True if arrow points from left to right, that is a->b and not a<-b.
 * @param [in] bidir True if the arrow is bi-directional, that is like a<->b and not a->b.
 * @param [in] which Tells us which end are we interested in (or middle).
 * @param [in] mainline_left The line style of the arrow line on the left side.
 * @param [in] mainline_right The line style of the arrow line on the right side.
 * @return `left` is the extent on the left side of the entity line, `right` is on the right side. */
DoublePair MscArrowHeads::getWidths(bool forward, bool bidir, EArrowEnd which,
                                   const LineAttr &mainline_left, const LineAttr &mainline_right) const
{
    DoublePair ret(0,0);
    auto ah = GetArrowHead(bidir, which);
    if (ah.IsNone()) return ret;
    auto wh = ah.WidthHeight(forward ? mainline_left : mainline_right,
                             forward ? mainline_right: mainline_left);
    if (bidir && IsMiddle(which) && !ah.IsSymmetricOrNone()) {
        //duplicate for bidir in the middle
        ret.right = ret.left = wh.before_x_extent;
    } else {
        ret.left = wh.before_x_extent;
        ret.right = wh.after_x_extent;
        if ((!forward) ^ (which==EArrowEnd::START)) //if reverse or start, but not if both
            ret.swap();
    }
    return ret;
}

/** Return the area for which the entity line shall not be drawn.
 * Only dots and diamonds cover it, for others an empty Contour is returned.
 * @param [in] xy The tip of the arrowhead.
 * @param [in] forward True if arrow points from left to right, that is a->b and not a<-b.
 * @param [in] bidir If the arrowhead is bidirectional
 * @param [in] which Which end of the arrow (or middle).
 * @param [in] mainline_left The line style of the arrow line on the left side.
 * @param [in] mainline_right The line style of the arrow line on the right side.
 * @returns The contour of the dot or diamond.*/
Contour MscArrowHeads::EntityLineCover(XY xy, bool forward, bool bidir, EArrowEnd which,
                                   const LineAttr &mainline_left, const LineAttr &mainline_right) const
{
    return GetArrowHead(bidir, which).
        TargetCover(xy, false,
                        forward ? mainline_left : mainline_right,
                        forward ? mainline_right: mainline_left);
}

/** Returns an area where the arrow line should not be drawn.
 * The inverse of it will be used to clip, when drawing the arrow line.
 * Note that we have to consider lines that are thicker than the arrowhead.
 * @param [in] xy The tip of the arrowhead - always the middle of the entity line even if the entity is activated
 * @param [in] bidir If the arrowhead is bidirectional
 * @param [in] which Which end of the arrow (or middle).
 * @param [in] forward True if arrow points from left to right, that is a->b and not a<-b.
 * @param [in] act_size Zero if the entity is not active at this point. Else it contains the
 *                      half-width of the activated entity line.
 * @param [in] mainline_left The line style of the arrow line on the left side.
 * @param [in] mainline_right The line style of the arrow line on the right side.
 * @returns An area covering the area where the arrow line should NOT be drawn.
 *          If no clipping is needed (all of the line can be shown at this arrowhead),
 *          we return an empty contour.*/
Contour MscArrowHeads::ClipForLine(XY xy, double act_size, bool forward, bool bidir, EArrowEnd which,
                               const LineAttr &mainline_left, const LineAttr &mainline_right) const
{
    auto ah = GetArrowHead(bidir, which);
    Contour area;
    if (!ah.IsNone()) {
        if (which == EArrowEnd::START)
            forward = !forward;
        if (ah.StartsWithSymmetricOrNone())
            act_size = 0;
        const bool both_sides = bidir && IsMiddle(which);
        //Below we set 'ignore_opaque' true, since we will draw arrowheads later than
        //arrow lines.
        if (forward || both_sides)
            area += ah.ClipForLine(xy - XY(act_size, 0), false, true,
                                   forward ? mainline_left : mainline_right,
                                   forward ? mainline_right : mainline_left);

        if (!forward || both_sides)
            //Add the flipped version (swap mainlines!)
            area += ah.ClipForLine(xy - XY(act_size, 0), false, true,
                                   !forward ? mainline_left : mainline_right,
                                   !forward ? mainline_right : mainline_left, both_sides).
                Transform(TRMatrix::CreateFlipYAxis(xy.x));
    }
    //Now cover the activated area of the entity line
    if (act_size) {
        const XY off(act_size, std::max(mainline_left.LineWidth(), mainline_right.LineWidth()));
        area += Block(xy-off, xy+off);
    }
    return area;
}

/** Returns a contour covering the arrowhead.
 * @param [in] xy The tip of the arrowhead.
 * @param [in] bidir If the arrowhead is bidirectional
 * @param [in] which Which end of the arrow (or middle).
 * @param [in] forward True if arrow points from left to right, that is a->b and not a<-b.
 * @param [in] act_size Zero if the entity is not active at this point. Else it contains the
 *                      half-width of the activated entity line.
 * @param [in] mainline_left The line style left of the arrowhead
 * @param [in] mainline_right The line style right of the arrowhead
 * @returns the contour of the arrowhead.*/
Contour MscArrowHeads::Cover(XY xy, double act_size, bool forward, bool bidir, EArrowEnd which,
                             const LineAttr &mainline_left, const LineAttr &mainline_right) const
{
    auto ah = GetArrowHead(bidir, which);
    if (ah.IsNone()) return Contour();
    if (which == EArrowEnd::START)
        forward = !forward;
    if (ah.StartsWithSymmetricOrNone())
        act_size = 0;

    const bool both_sides = bidir && IsMiddle(which);
    Contour area;
    if (forward || both_sides)
        area += ah.Cover(xy - XY(act_size, 0), false,
                            forward ? mainline_left : mainline_right,
                            forward ? mainline_right : mainline_left);

    if (!forward || both_sides)
        //Add the flipped version (swap mainlines!)
        area += ah.Cover(xy - XY(act_size, 0), false,
                         !forward ? mainline_left : mainline_right,
                         !forward ? mainline_right : mainline_left, both_sides).
            Transform(TRMatrix::CreateFlipYAxis(xy.x));

    return area;
}


/** Draw an arrowhead pointing to the right or to the left.
 * @param canvas The canvas onto which we draw.
 * @param [in] xy The tip of the arrowhead.
 * @param [in] bidir If the arrowhead is bidirectional
 * @param [in] which Which end of the arrow (or middle).
 * @param [in] forward True if arrow points from left to right, that is a->b and not a<-b.
 * @param [in] act_size Zero if the entity is not active at this point. Else it contains the
 *                      half-width of the activated entity line.
 * @param [in] mainline_left The line style of the arrow line on the left side.
 * @param [in] mainline_right The line style of the arrow line on the right side.
*/
void MscArrowHeads::Draw(Canvas &canvas, XY xy, double act_size,
                     bool forward, bool bidir, EArrowEnd which,
                     const LineAttr &mainline_left, const LineAttr &mainline_right) const
{
    auto ah = GetArrowHead(bidir, which);
    if (ah.IsNone()) return;
    if (which == EArrowEnd::START)
        forward = !forward;
    if (ah.StartsWithSymmetricOrNone())
        act_size = 0;

    const bool both_sides = bidir && IsMiddle(which);
    if (forward || both_sides) {
        ah.Draw(canvas, xy - XY(act_size, 0), false,
                forward ? mainline_left : mainline_right,
                forward ? mainline_right : mainline_left, false);
    }
    if (!forward || both_sides) {
        //*x_new = xx * x + xy * y + x0;
        //*y_new = yx * x + yy * y + y0;
        //    double xx; double yx;
        //    double xy; double yy;
        //    double x0; double y0;
        cairo_matrix_t matrix = {-1, 0, 0, 1, 2*xy.x, 0};
        cairo_save(canvas.GetContext());
        cairo_transform(canvas.GetContext(), &matrix);
        ah.Draw(canvas, xy - XY(act_size, 0), false,
                !forward ? mainline_left : mainline_right,
                !forward ? mainline_right : mainline_left, both_sides);
        cairo_restore(canvas.GetContext());
    }
}


/** The full width of the arrowhead (on both sides of the entity line).
 * The values returned here are used to determine spacing between entities
 * in for BlockArrow::Width().
 * @param [in] forward True if arrow points from left to right, that is a->b and not a<-b.
 * @param [in] bidir True if the arrow is bi-directional, that is like a<->b and not a->b.
 * @param [in] which Tells us which end are we interested in (or middle).
 * @param [in] body_height The height of the arrow body
 * @param [in] act_size The (half) width of the entity line due to entity activation.
 * @param [in] ltype The line style that will be used to draw the block arrow.
 * @returns The size of the arrowhead from the middle of the entity line towards the
 *          middle on the arrow. 'first' returns the size on the left side,
 *          'second' returns the space on the right side.*/
DoublePair MscArrowHeads::getBigWidthsForSpace(bool forward, bool bidir, EArrowEnd which,
                                               double body_height, double act_size [[maybe_unused]], const LineAttr &ltype) const
{
    DoublePair ret(0, 0);
    auto ah = GetArrowHead(bidir, which);
    const bool middle = IsMiddle(which);
    const auto who = ah.BlockWidthHeight(body_height, ltype, middle);
    if (bidir && middle)
        ret.left = ret.right = std::max(who.before_margin, who.after_margin);
    else {
        ret = {who.before_margin, who.after_margin};
        if ((which==EArrowEnd::START && forward) || //only on the left side if (-> and END) or (<- and START)
            (which==EArrowEnd::END && !forward))
            ret.swap();
    }
    return ret;
}

/** Determines how much margin is needed for a text with a given cover.
 * Determines the margin of the text (from the entity line). Can be negative,
 * if the block arrowhead spans over the entity line (e.g., dot does) and
 * hence the text can be wider than the space between the entity lines.
 * (sx, sy) should be equal to the y aspect of the bounding box of text_cover
 * plus two linewidths.
 *
 * @param [in] text_cover The cover of the label.
 * @param [in] sy The top of the arrow body (outer edge of the line).
 * @param [in] dy The bottom of the arrow body (outer edge of the line).
 * @param [in] margin_side_is_left If we need the left or right margin for the text.
 * @param [in] forward True if the arrow is left-to-right
 * @param [in] bidir True if the arrow is bidirectional.
 * @param [in] which tells us which kind of arrow we talk about start, middle or end
 * @param [in] ltype The line style that will be used to draw the block arrow. */
double MscArrowHeads::getBigMargin(Contour text_cover, double sy, double dy, bool margin_side_is_left,
                                  bool forward, bool bidir, EArrowEnd which, const LineAttr &ltype) const
{
    return GetArrowHead(forward, bidir, which, !margin_side_is_left).
        BlockTextMargin(text_cover, margin_side_is_left, sy, dy, ltype);
}

/** Tells how much the arrow (overall) extends above or below the body.
 * @param [in] height The theight of the arrow body (difference between outer edges)
 * @param [in] forward True if arrow points from left to right, that is a->b and not a<-b.
 * @param [in] bidir True if the arrow is bi-directional, that is like a<->b and not a->b.
 * @param [in] line Specifies the line style to use. It can be null if lines!=nullptr.
 * @param [in] lines Specifies the line style for each segment.
 *                   If nullptr, we assume there are no middle arrowheads (arrow is of a
 *                   single segment) and `line` is the line type.
 *                   Else we use the linewidths in 'lines' to estimate top height,
 *                   except if mid arrowhead is not segmenting.
 *                   We assume `lines` is ordered in increasing x coordinate.
 *                   If both lines and line are specified, lines takes precedence.*/
double MscArrowHeads::bigYExtent(double height, bool forward, bool bidir,
                                 const LineAttr *line, const std::vector<LineAttr> *lines) const
{
    _ASSERT(line || (lines && lines->size()));   //only one of them can be null
    if (lines==nullptr || lines->size()==0 || midArrowHead.IsSymmetricOrNone()) {
        if (line==nullptr)
            line = &lines->front();
        const double h = std::max(
            startArrowHead.BlockWidthHeight(height, *line, false).y_extent_above_body,
            endArrowHead.  BlockWidthHeight(height, *line, false).y_extent_above_body);
        if (lines==nullptr || lines->size()==0) return h;
        return std::max(h,
            midArrowHead.BlockWidthHeight(height, *line, true).y_extent_above_body);
    }
    double ret = 0;
    for (unsigned i=0; i<lines->size(); i++) {
        EArrowEnd t_left = EArrowEnd::MIDDLE, t_right = EArrowEnd::MIDDLE;
        if (i==0)
            t_left = forward ? EArrowEnd::START : EArrowEnd::END;
        if (i==lines->size())
            t_right = forward ? EArrowEnd::END : EArrowEnd::START;

        const double h_left =  GetArrowHead(forward, bidir, t_left, false).
            BlockWidthHeight( height, lines->at(i), t_left ==EArrowEnd::MIDDLE).y_extent_above_body;
        const double h_right = GetArrowHead(forward, bidir, t_right, true).
            BlockWidthHeight(height, lines->at(i), t_right==EArrowEnd::MIDDLE).y_extent_above_body;

        ret = std::max(ret, std::max(h_left, h_right));
    }
    return ret;
}


/** Returns the contours of one arrowhead.
 * Either on the left or the right side of the entity line.
 * @param [in] x The x coordinate of the tip of the arrowhead.
 * @param [in] act_size Zero if the entity is not active at this point. Else it contains the
 *                      half-width of the activated entity line.
 * @param [in] sy The top of the arrow body (outer edge of the line).
 * @param [in] dy The bottom of the arrow body (outer edge of the line).
 * @param [in] forward True if the arrow is left to right.
 * @param [in] bidir If the arrowhead is bidirectional
 * @param [in] which Which end of the arrow (or middle).
 * @param [in] left True if we are interested in the arrow to draw to the left side of the entity line.
 *                  For symmetric types (dot) this gives the same irrespective of `left`.
 * @param [in] ltype The line style that will be used to draw the block arrow.
 * @param [out] body_margin The space from the entity line the body shoud end at.
 *                          Essentially the width of the arrowhead.
 * @returns The contour of the arrowhead.*/
Contour MscArrowHeads::BigContourOneEntity(double x, double act_size, double sy, double dy,
                                           bool forward, bool bidir, EArrowEnd which, bool left,
                                           const LineAttr &ltype, double *body_margin) const
{
    auto ah = GetArrowHead(forward, bidir, which, left);
    const bool middle = IsMiddle(which);
    const auto who = ah.BlockWidthHeight(dy-sy, ltype, middle);
    const double act_off = act_size * who.add_activation;
    if (body_margin)
        *body_margin = who.body_connect_before-act_off;  //margin will be substracted on right, added on left
    Contour area = ah.BlockContour(x+act_off, sy, dy, ltype, middle);
    if (!left)
        area.Transform(TRMatrix::CreateFlipYAxis(x));
    return area;
}

/** Returns the outer line of the block arrow, each segment in a separate Contour.
 * If mid-arrow does not segment only one Contour is returned
 * @param [in] xPos The x coordinate of entities the arrow visits. If the arrow is
 *                  slanted, these shall be spaced somewhat wider apart than in the
 *                  horizontal direction. This must be done by the caller.
 * @param [in] act_size The activation status for each visited entity.
 *                      Zero if the entity is not active at this point. Else it contains the
 *                      half-width of the activated entity line.
 * @param [in] sy The top of the arrow body (outer edge of the line).
 * @param [in] dy The bottom of the arrow body (outer edge of the line).
 * @param [in] forward True if arrow points from left to right, that is a->b and not a<-b.
 * @param [in] bidir If the arrowhead is bidirectional
 * @param [in] line Specifies the line style to use. It can be null if lines!=nullptr.
 * @param [in] lines Specifies the line style for each segment.
 *                   If nullptr, we assume there are no middle arrowheads (arrow is of a
 *                   single segment) and `line` is the line type.
 *                   Else we use the linewidths in 'lines' to estimate top height,
 *                   except if mid arrowhead is not segmenting.
 *                   We assume `lines` is ordered in increasing x coordinate.
 *                   If both lines and line are specified, lines takes precedence.
 * @param [out] result The contours for individual segments. Only one if there is
 *                     only one segment.
 * @returns The union of segment contours.*/
Contour MscArrowHeads::BigContour(const std::vector<double> &xPos, const std::vector<double> &act_size,
                              double sy, double dy, bool forward, bool bidir,
                              const LineAttr *line, const std::vector<LineAttr> *lines,
                              std::vector<Contour> &result) const
{
    _ASSERT(line || (lines && lines->size()));   //only one of them can be null
    Contour area_overall, area_segment;
    const bool segment = xPos.size()>2 && BreaksBlockArrow(GetArrowHead(bidir,EArrowEnd::MIDDLE).front().type);
    const EArrowEnd e_left  = forward ? EArrowEnd::START : EArrowEnd::END;
    const LineAttr *ltype_current = (lines && lines->size()) ? lines->data() : line;

    //draw leftmost arrowhead
    double from_x;
    area_segment = BigContourOneEntity(xPos.front(), act_size.front(), sy, dy,
                                       forward,bidir, e_left, false,
                                       *ltype_current, &from_x);
    from_x = xPos[0] + from_x;

    //set up variables for mid-points
    if (!midArrowHead.IsNone())
        for (unsigned i=1; i<xPos.size()-1; i++) {
            //draw left side of the entity line
            double margin;
            area_segment += BigContourOneEntity(xPos[i], act_size[i], sy, dy,
                                                forward,bidir, EArrowEnd::MIDDLE, true,
                                                *ltype_current, &margin);
            //draw body that connects them
            area_segment += Block(from_x, xPos[i] - margin, sy, dy);

            //calculate next linewidth
            if (segment && lines && lines->size()>i)
                ltype_current  = &lines->at(i);

            area_overall += area_segment;
            //if we are segmented, add us to result
            if (segment)
                result.push_back(std::move(area_segment));
            area_segment.clear();

            //if segmented, draw arrowhead at right side of entity line
            if (segment)
                area_segment = BigContourOneEntity(xPos[i], act_size[i], sy, dy,
                                                   forward,bidir, EArrowEnd::MIDDLE, false,
                                                   *ltype_current, &margin);
            from_x = xPos[i] + margin;
    }
    //draw rightmost arrowhead
    const EArrowEnd e_right = forward ? EArrowEnd::END : EArrowEnd::START;
    double right_margin;
    area_segment += BigContourOneEntity(xPos.back(), act_size.back(), sy, dy,
                                        forward,bidir, e_right, true,
                                        *ltype_current, &right_margin);
    area_segment += Block(from_x, xPos.back() - right_margin, sy, dy);
    area_overall += area_segment;
    if (segment)
        result.push_back(std::move(area_segment));
    else
        result.push_back(area_overall);
    return area_overall;
}

/** Returns the outer line of the heads of the block arrow, but not of the body.
 * If the two ends are ARROW_NONE, a small block is added.
 * This is used to mark places where floating notes shall not trudge (ArcBase::area_important).
 * @param [in] xPos The x coordinate of entities the arrow visits.
 * @param [in] act_size The activation status for each visited entity.
 *                      Zero if the entity is not active at this point. Else it contains the
 *                      half-width of the activated entity line.
 * @param [in] sy The top of the arrow body (outer edge of the line).
 * @param [in] dy The bottom of the arrow body (outer edge of the line).
 * @param [in] forward True if arrow points from left to right, that is a->b and not a<-b.
 * @param [in] bidir If the arrowhead is bidirectional
 * @param [in] line Specifies the line style to use. It can be null if lines!=nullptr.
 * @param [in] lines Specifies the line style for each segment.
 *                   If nullptr, we assume there are no middle arrowheads (arrow is of a
 *                   single segment) and `line` is the line type.
 *                   Else we use the linewidths in 'lines' to estimate top height,
 *                   except if mid arrowhead is not segmenting.
 *                   We assume `lines` is ordered in increasing x coordinate.
 *                   If both lines and line are specified, lines takes precedence.
 * @param [in] compressGap How far we shall be.
 * @returns The contours of the heads, but no body.
 */
Contour MscArrowHeads::BigHeadContour(const std::vector<double> &xPos, const std::vector<double> &act_size,
                                  double sy, double dy, bool forward, bool bidir,
                                  const LineAttr *line, const std::vector<LineAttr> *lines,
                                  double compressGap) const
{
    Contour ret;
    const bool segment = !GetArrowHead(bidir,EArrowEnd::MIDDLE).IsSymmetricOrNone() && xPos.size()>2;
    const EArrowEnd e_left  = forward ? EArrowEnd::START : EArrowEnd::END;
    const LineAttr *ltype_current = (lines && lines->size()) ? lines->data() : line;

    //add leftmost arrowhead
    double dummy_margin;
    if (GetArrowHead(bidir, e_left).IsNone())
        ret = Block(xPos[0]+act_size[0]-compressGap/2, xPos[0]+act_size[0]+compressGap/2, sy, dy);
    else
        ret = BigContourOneEntity(xPos[0], act_size[0], sy, dy,
                                  forward,bidir, e_left, false,
                                  *ltype_current, &dummy_margin);

    //add mid-heads
    if (!midArrowHead.IsNone())
        for (unsigned i=1; i<xPos.size()-1; i++) {
            //draw left side of the entity line
            ret += BigContourOneEntity(xPos[i], act_size[i], sy, dy,
                                       forward,bidir, EArrowEnd::MIDDLE, true,
                                       *ltype_current, &dummy_margin);
            //calculate next linewidth
            if (segment && lines && lines->size()>i)
                ltype_current  = &lines->at(i);

            //if segmented, draw arrowhead at right side of entity line
            if (segment)
                ret += BigContourOneEntity(xPos[i], act_size[i], sy, dy,
                                           forward,bidir, EArrowEnd::MIDDLE, false,
                                           *ltype_current, &dummy_margin);
    }
    //draw rightmost arrowhead
    const EArrowEnd e_right = forward ? EArrowEnd::END : EArrowEnd::START;
    if (GetArrowHead(bidir, e_right).IsNone())
        ret += Block(*xPos.rbegin()+ *act_size.rbegin()-compressGap/2, *xPos.rbegin()+*act_size.rbegin()+compressGap/2, sy, dy);
    else
        ret += BigContourOneEntity(*xPos.rbegin(), *act_size.rbegin(), sy, dy,
                                   forward,bidir, e_right, true,
                                   *ltype_current, &dummy_margin);
    return ret;
}

/** Draw the block arrow, assuming we already calculated its contour.
 *
 * @param [out] result The contours for individual segments. Only one if there is
 *                     only one segment.
 * @param [in] line Specifies the line style to use. It can be null if lines!=nullptr.
 * @param [in] lines Specifies the line style for each segment.
 *                   If nullptr, we assume there are no middle arrowheads (arrow is of a
 *                   single segment) and `line` is the line type.
 *                   Else we use the linewidths in 'lines' to estimate top height,
 *                   except if mid arrowhead is not segmenting.
 *                   We assume `lines` is ordered in increasing x coordinate.
 *                   If both lines and line are specified, lines takes precedence.
 * @param [in] fill The fill we shall use inside the block arrow.
 * @param [in] shadow The shadow we shall use below the block arrow.
 * @param canvas The canvas to draw on.
 * @param [in] angle_radian Our slant. We assume the space around us is rotated
                            this much when we are called. We also assume
                            the contours are enlengthtened already. This value
                            is used to adjust the shadow to be in the right dir. */
void MscArrowHeads::BigDrawFromContour(std::vector<Contour> &result,
                                       const LineAttr *line, const std::vector<LineAttr> *lines,
                                       const FillAttr &fill, const ShadowAttr &shadow, Canvas &canvas,
                                       double angle_radian) const
{
    _ASSERT(line || (lines && lines->size()));   //only one of them can be null
    const LineAttr *l = lines && lines->size() ? &lines->front() : line;
    for (unsigned i=0; i<result.size(); i++) {
        if (lines && lines->size()>i)
            l = &lines->at(i);
        const Contour midline = result[i].CreateExpand(-l->LineWidth()/2);
        if (canvas.does_graphics()) {
            canvas.Shadow(midline, shadow, angle_radian);
            canvas.Fill(midline.CreateExpand(-l->Spacing()), fill);
            canvas.Line(midline, *l);
        } else
            canvas.Add(GSShape(midline, *l, fill, shadow));
    }
}

/** Draw the circle or diamond if a DOT_EMPTY or DIAMOND_EMPTY style is in the
 * middle of an arrow.
 * But clip the drawing area, so that text is not disturbed*/
void MscArrowHeads::BigDrawEmptyMid(const std::vector<double> &xPos, double sy, double dy,
                                    Canvas &canvas, const LineAttr &line, const Contour *clip) const
{
    const Path path = midArrowHead.BlockMidPath(0, sy, dy, line);
    if (path.IsEmpty())
        return;
    if (clip) canvas.ClipInverse(*clip);
    for (unsigned i=1; i<xPos.size()-1; i++)
        canvas.Line(path.CreateShifted(XY(xPos[i], 0)), line);
    if (clip) canvas.UnClip();
}

/** Calculate the geometry of an arrow and draw it on a canvas.
 * @param [in] xPos The x coordinate of entities the arrow visits. If the arrow is
 *                  slanted, these shall be spaced somewhat wider apart than in the
 *                  horizontal direction. This must be done by the caller.
 * @param [in] act_size The activation status for each visited entity.
 *                      Zero if the entity is not active at this point. Else it contains the
 *                      half-width of the activated entity line.
 * @param [in] sy The top of the arrow body (outer edge of the line).
 * @param [in] dy The bottom of the arrow body (outer edge of the line).
 * @param [in] forward True if arrow points from left to right, that is a->b and not a<-b.
 * @param [in] bidir If the arrowhead is bidirectional
 * @param [in] line Specifies the line style to use. It can be null if lines!=nullptr.
 * @param [in] lines Specifies the line style for each segment.
 *                   If nullptr, we assume there are no middle arrowheads (arrow is of a
 *                   single segment) and `line` is the line type.
 *                   Else we use the linewidths in 'lines' to estimate top height,
 *                   except if mid arrowhead is not segmenting.
 *                   We assume `lines` is ordered in increasing x coordinate.
 *                   If both lines and line are specified, lines takes precedence.
 * @param [in] fill The fill we shall use inside the block arrow.
 * @param [in] shadow The shadow we shall use below the block arrow.
 * @param canvas The canvas to draw on.
 * @param [in] clip Some clip value, not sure what.
 * @param [in] angle_radian Our slant. We assume the space around us is rotated
                            this much when we are called. We also assume
                            the contours are enlengthtened already. This value
                            is used to adjust the shadow to be in the right dir. */
void MscArrowHeads::BigCalculateAndDraw(const std::vector<double> &xPos, const std::vector<double> &act_size,
                                    double sy, double dy, bool forward, bool bidir,
                                    const LineAttr *line, const std::vector<LineAttr> *lines,
                                    const FillAttr &fill, const ShadowAttr &shadow, Canvas &canvas,
                                    const Contour *clip, double angle_radian) const
{
    std::vector<Contour> cont;
    BigContour(xPos, act_size, sy, dy, forward, bidir, line, lines, cont);
    BigDrawFromContour(cont, line, lines, fill, shadow, canvas, angle_radian);
    BigDrawEmptyMid(xPos, sy, dy, canvas, lines && lines->size() ? lines->front() : *line, clip);
}


