/*
    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 canvas.h The declaration classes for drawing, pagination and file formats (Canvas).
 * @ingroup libcgencommon_files */

#if !defined(CANVAS_H)
#define CANVAS_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <set>
#include <mutex>
#include "cairo.h"//If this file is missing you need to install libcairo2-dev (on Linux)
#ifdef CAIRO_HAS_WIN32_SURFACE
#include "cairo-win32.h"
#undef max
#undef min
#endif
#ifdef CAIRO_HAS_FC_FONT
#include <fontconfig/fontconfig.h>
#endif
#include "ppt.h"
#include "generic_shapes.h"
#include "error.h"
#include "style.h"
#include "element.h"

class LanguageCollection;

namespace pngutil {

enum class EPNGResult
{
    OK,
    READ_ERROR,
    WRITE_ERROR,
    INTERNAL_ERROR,
    INVALID_PNG
};

std::pair<EPNGResult, std::vector<EmbedChartData>>
ReadPNG(FILE *png_file);

EPNGResult AddToPNG(FILE *read_file, FILE *write_file, const EmbedChartData &chart);

EmbedChartData Select(FILE *in, const LanguageCollection &languages,
                      MscError& Error, const FileLineCol& pos = FileLineCol());

} //namespace pngutil

extern std::mutex cairo_text_mutex;
extern const bool do_not_draw_fonts; ///<Set via environment variables, used to generate images with no fonts in testing


////////////////////Helpers//////////////////////////////////
inline void thread_safe_cairo_show_text(cairo_t *cr, const char *utf8)
{
#ifdef WIN32
    std::lock_guard<std::mutex> lock(cairo_text_mutex);
#endif // CAIRO_HAS_WIN32_SURFACE
    if (cr && !do_not_draw_fonts)
        cairo_show_text(cr, utf8);
}

inline void thread_safe_cairo_text_path(cairo_t *cr, const char *utf8)
{
#ifdef WIN32
    std::lock_guard<std::mutex> lock(cairo_text_mutex);
#endif // CAIRO_HAS_WIN32_SURFACE
    if (cr && !do_not_draw_fonts)
        cairo_text_path(cr, utf8);
}

inline void thread_safe_cairo_text_extents(cairo_t *cr, const char *utf8,
                                           cairo_text_extents_t *extents)
{
#ifdef WIN32
    std::lock_guard<std::mutex> lock(cairo_text_mutex);
#endif // CAIRO_HAS_WIN32_SURFACE
    if (cr)
        cairo_text_extents(cr, utf8, extents);
}

/***************************************************************************
 ***************************************************************************/

/** Structure to hold page offsets and other data.
 * Extensible for each chart type.
 * The model is that here we store a potential heading (that comes to the page to be
 * displayed by the chart and a "lefting" some area that is to be drawn to the left of
 * the page. Today, we do not have the ability to draw a "righting" and the footer is
 * strictly just the copyright text.*/
struct PageBreakData {
    XY xy;                 ///<The top-left position in chart space
    XY wh;                 ///<The size of the page
    XY headingLeftingSize; ///<The size of the automatic space on the left or top of the page
    bool manual;           ///<True if the page break is inserted manually by the user
    PageBreakData(const XY &_xy, const XY &_wh) :
        xy(_xy), wh(_wh), headingLeftingSize(0,0), manual(false) {}
};

/** A vector of page descriptors.
 * PBDataVector is never responsible for the memory pointed to by it*/
using PBDataVector = std::vector<const PageBreakData*> ;

/** cm to points conversion.
 * see http://www.asknumbers.com/CentimetersToPointsConversion.aspx */
#define PT_PER_CM 28.3464567

/** Describes fixed page output page sizes.
 * Odd values portrait, even values landscape.*/
enum class EPageSize {
    NO_PAGE=1, ///<This value indicates no fixed page output
    A0P, A0L,
    A1P, A1L,
    A2P, A2L,
    A3P, A3L,
    A4P, A4L,
    A5P, A5L,
    A6P, A6L,
    LETTER_P, LETTER_L,
    LEGAL_P, LEGAL_L,
    LEDGER, TABLOID,
    WIDESCREEN_P, WIDESCREEN_L,
    ON_SCREEN_4_3_P, ON_SCREEN_4_3_L,
    ON_SCREEN_16_9_P, ON_SCREEN_16_9_L,
    ON_SCREEN_16_10_P, ON_SCREEN_16_10_L,
    MAX_PAGE ///<Not used, the max value.
};

constexpr EPageSize DEFAULT_PPT_PAGE_SIZE = EPageSize::WIDESCREEN_L;

/** Converts a string to a fixed page size*/
EPageSize ConvertPageSize(std::string_view);
/** Converts a fixed page size to a textual description*/
std::string_view ConvertPageSize(EPageSize);
/** Converts a fixed page size to a verbose textual description*/
std::string_view ConvertPageSizeVerbose(EPageSize);
/** Returns the physical size of `ps` in points (1/72 inch)*/
XY GetPhysicalPageSize(EPageSize ps);

namespace msc {
    //forward declare so that it can be friend of Canvas
    class Box;
    class Pipe;
}

class PptFile;
/** A class encapsulating a cairo drawing context.
 * Besides relaying drawing calls to the cairo context Canvas has the following functionality.
 * - Create and manage various target surfaces (PDF, PNG, EPS, EMF, WMF, recording surface)
 *   including writing to files.
 * - Handle capabilities missing in certain target types (dashed lines, gradient fill,
 *   some parts of transparency) to avoid fallback images as much as possible.
 *   WMF is a very important target in this respect, as OLE charts use it.
 *   See SetLowLevelParams() for a list of the capability tinkering.
 * - Provide a usable EMF->WMF conversion (does not work on XP).
 * - Provide pagination support, with header (automatic pagination headings) and
 *   footer (the copyright text). Supports two modes: when the chart size dictates
 *   the size of the pages and when the chart is drawn on a (series) of fixed-size
 *   pages (printing or -p PDF output).
 *
 * A few notes on output formats.
 * - WMF internally uses an Enhanced Metafile with limited features and
 * converts to a MetaFile at closure. This is because a WMF DC cannot be given
 * to cairo, since it fails. For a list of tweaks for WMF see SetLowLevelParams().
 * - PRINTER is internally equal to EMF
 *
 * The term *chart coordinates* refer to the coordinates the ArcBase::Layout() and
 * ArcBase::Draw() and all Msc functions see. In this space the chart begins
 * at the origin and spans left-down towards growing x and y coordinates.
 * (Thus y coordinate grows downward, compatible with contour assumptions.)
 * Note however, that notes may expand into negative coordinates, thus the final
 * chart area is captured in a Block (the `total` member of Canvas).
 * If the chart is drawn on an unscaled PNG file, then one unit of chart space
 * will translate to one pixel. Similar in PDF and EPS, one unit will translate
 * to one point (1/72 inch in PostScript and PDF).
 * However, the user can ask for scaling (in Canvas::Canvas) and also, for WMF
 * we introduce other scaling mechanisms, so the output may be dawn to a
 * space different from the chart coordinate space.
 */

class Canvas
{
public:
    /** Describes graphics output type */
    enum EOutputType {
        PNG,     ///<Portable Network Graphics bitmap output type
        EPS,     ///<Encapsulated PostScript vector output type
        PDF,     ///<Portable Document Format vector output type
        SVG,     ///<Scalable Vector Graphics vector output type
        EMF,     ///<Windows Enhanced Metafile vector output type
        WMF,     ///<Old-format Windows Metafile vector output type
        EMFWMF,  ///<EMF output, but only uses features compatible with a WMF
        WIN,     ///<Bitmap output to a Windows window DC
        PRINTER, ///<Output to a Windows printer DC
        ISMAP,   ///<Server-side image map - just an empty file
        PPT,     ///<PowerPoint generated with external JavaScript
    };
    constexpr bool IsWindowsOutput(EOutputType t) { return t==EMF || t==WMF || t==EMFWMF || t==WIN || t==PRINTER; }
    static XY DetermineScaling(const Block &total, const std::vector<XY> &scale,
        const XY &pageSize, const double margins[4],
        double ctexth, const PBDataVector *pageBreakData);
    /** Checks if 'face' is available, or not.*
    * If it is not available, either an empty string or a substitute is returned in 'face'
    * It may take longer than Canvas::SetFontFace(), as this is called only when interpreting
    * 'text.font.face' attribute/chart option values, the \\f() formatting escape and
    * the -F command line option. FOR NOW WE ALWAYS TRUE.*/
    static bool HasFontFace(string & /*face*/, bool /*italics*/ = false, bool /*bold*/ = false) { return true; }

    /** Returns true if the output format is graphics, and the Line(), Fill() etc
     * drawing primitives can be called and there is a cairo context available.
     * Returns false if we shall output higher-level shapes, e.g., for a PPT. */
    bool does_graphics() const noexcept { return outType!=PPT; }

    /** Returns this correction value */
    bool shall_avoid_aligned_bk_clip() const noexcept { return avoid_aligned_bk_clip; }

    /** Return true if the canvas is not properly set up for drawing.
     * This can be used for 3 things:
     * 1. to spare drawing operations for dummy canvases;
     * 2. to avoid drawing when we do not do graphics, just collect shapes (e.g., PPT); and
     * 3. to prevent drawing on a nullptr cairo context.
     * @param [in] assert_if_candraw_false We assert if candraw is false. This happens
     *             when the canvas failed to open or is already closed. We never assert
     *             for dummy contexts - there all operations always succeed, but have no
     *             effect.
     * @return true if this is a dummy canvas, is already closed or failed to open well.
     *         For non-dummy canvases it ASSERTs that we can do graphics. */
    bool cannot_draw(bool assert_if_candraw_false [[maybe_unused]] = true) const noexcept {
        if (dummy) return true;
        if (!does_graphics()) return true;
        _ASSERT(candraw || !assert_if_candraw_false);
        return !candraw || !cr || error.size();
    }

protected:
    /** @name Low-level compatibility options
     * @{ */
    bool     white_background;        ///< Draw a white background before chart drawing.
    double   fake_scale;              ///< Final rendering should be scaled like this. Used for WMF, since WMF coordinates are 16-bit integers. We are better off if the chart is scaeld up (less rounding), but not more than 32K.
    double 	 fallback_resolution;     ///< The resolution of the fallback images for vector surfaces.
    bool     needs_dots_in_corner;    ///< Draw a dot in upper left and lower right corner. Needed for EMF/WMF for the output to include the white space around the chart.
    bool     use_text_path;           ///< Use cairo_text_path() instead of cairo_show_text(). I am not sure we have any backend needing this.
    bool     use_text_path_rotated;   ///< Use cairo_text_path() instead of cairo_show_text() for rotated text. WMF cannot display text if the transformation matrix is rotated.
    int      individual_chars_path;   ///< 0: off, 1: Each character is drawn one by one, and not in a single call to cairo_show_text(); 2: non-ascii chars are shown with cairo_text_path(). WMF does not scale text nicely, this is a workaround.
    unsigned fake_gradients;          ///< If nonzero then do not use cairo gradients, mimic them with a lot of fills. This is the number of steps.
    double   scale_for_shadows;       ///< How many pixel lies below one logical pixel (needed for fine shadows)
    bool     fake_shadows;            ///< Do not use alpha blending in shadows, use solid color as if on white background. (This avoids fallback image for EMF/WMF, which support no transparency.)
    bool     fake_dash;               ///< Do not use cairo dash, mimic them with individual dashes. WMF supports no dash, EMF supports no dash-offset.
    bool     fake_spaces;             ///< Add space for leading & trailing spaces at cairo_show_text(), assuming those are skipped by it. I do not remember, why we need it.
    bool     needs_arrow_fix;         ///< Cannot do the convex clipping for arrowheads, use a simpler method.
    bool     avoid_linewidth_1;       ///< Never draw lines of width exactly 1.0. EMF with cairo 1.12.2 needs to have wider lines like 1.01.
    bool     imprecise_positioning;   ///< EMF/WMF has trouble doing lines on 0.5 boundaries, avoid this.
    bool     needs_strokepath_rclboundsfix;  ///< EMF/WMF Attaches a clip region to every stroke. If that is too small, rectilinear lines disappear when scaling to small object.
    bool     can_and_shall_clip_total;///< True if we can clip to the total chart area. Does not work for WIN, so we need this flag
    bool     avoid_aligned_bk_clip;   ///< If set, we make sure that the background fill is not aligned on a page boundary. Page clip seems not to work for such rectangles in WMF/EMF.
    bool     avoid_transparency;      ///< Pipes and gradient fills will be flattened before output.
    /** @} */

    /** @name Context data
     * @{ */
    string           fileName;         ///< The name of the output file (if any) in UTF-8
    FILE*            outFile;          ///< The opened output file (if opened in constructor). If we have to add iTXt chunks to a PNG file, this is a temp file.
    FILE*            finalOutFile;     ///< The eventual filename. Used if we add iTXt chunks to a PNG file.
    cairo_surface_t *surface;          ///< The cairo surface we draw to
    cairo_t         *cr;               ///< The cairo context we draw to
    PptFile          ppt;              ///< The PPT file, in case of PPT output
    std::vector<std::vector<SimpleTransform>> tr_stack; ///< In case of non-graphics (PPT) output this is the transform stack.
    std::string      error;            ///< Empty if the Canvas is OK, else contains an error.
    bool             fatal = true;     ///< If set, any error above will be reported as fatal.
    bool             candraw;          ///< False, if Canvas cannot be drawn on. Used only in debug asserts.
    bool             dummy = false;    ///< If true, we ignore any drawing operations silently.
    double           fake_dash_offset; ///< Status of fake dashes
    EmbedChartData   iTXt_data;        ///< The chart data (language, source text, etc.) to be added as an iTXt chunk to PNG files.
    /** @}*/

    /** @name Values recorded at construction
     * @{ */
    const EOutputType outType;            ///<The output type
    const Block       total;              ///<The full chart area (in unscaled chart coordinates, all pages) excluding copyright, but including notes and comments
    const bool        external_surface;   ///<True if we got the surface in the constructor externally (no need to destroy it at closure)
    const double      copyrightTextHeight;///<The height of the copyright text
    const XY          user_scale;         ///<Stores the scale user specified at construction
    Block             raw_page_clip;      ///<Used with fix-page output only. Contains the raw page size in points (pixels) minus margins. All zero if not a fixed page output.
    const int         h_alignment;        ///<Used with fix-page output only. Contains -1/0/+1 for left/center/right page alignment.
    const int         v_alignment;        ///<Used with fix-page output only. Contains -1/0/+1 for up/center/down page alignment.
    /** @} */

    /** @name Logic
     * @{ */
    void SetLowLevelParams(const XY &size = XY(0, 0));
    struct PagePosition {
        XY size;
        XY offset;
        const PageBreakData* page_break_data;
    };
    static PagePosition GetPagePosition(const Block &total, const PBDataVector *pageBreakData, unsigned page) noexcept;
    void CreateSurface(const XY &size);
    void CreateContext(const XY &origSize, const XY &origOffset,const XY &headingLeftingSize);
    void CreateNilContext() { return CreateContext(XY(0, 0), XY(0, 0), XY(0, 0)); }
    /** @} */

friend class Chart; //for GetPagePosition
friend class StringFormat; //for all sorts of text manipulation
friend class msc::Box;  //for exotic line joints
friend class msc::Pipe;  //for exotic line joints
     /** @name Cairo context attribute handing.
     * @{ */
    /** Set dash */
    void SetDash(const LineAttr &);
    /** Set the font face and bold/italics */
    void SetFontFace(const std::string &face, const std::string& lang, bool italics, bool bold);
    /** Set the size of the font */
    void SetFontSize(double size) {if (cr) cairo_set_font_size (cr, size);}
    /** Set the source to a single color */
    void SetColor(ColorType);
    /** Set the source to a linear gradient.*/
    void linearGradient(ColorType from, ColorType to, const Block &b, double angle, EIntGradType type, double frac);
    /** Set the source to a radial gradient.*/
    void radialGradient(ColorType from, ColorType to, const XY &s,
		                double outer_radius, double inner_radius);
    /** @} */

    /** @name Helpers for drawing operations.
     * @{ */
    /** Draw text with upper-left coordinates `p`. If isRotated is true the transformation matrix is rotated.*/
    void Text(const XY &xy, const std::string &s, bool isRotated, const XY &scale = {1,1});
    /** Create an arc path in cairo*/
    void ArcPath(const XY &c, double r1, double r2=0, double s_rad=0, double e_rad=2*M_PI, bool reverse=false);
    /** Create a rectangle path in cairo*/
    void RectanglePath(double sx, double dx, double sy, double dy);
    /** Create a rectangle path in cairo, with special corners considering `line.corner` and 'line.radius'*/
    void RectanglePath(double sx, double dx, double sy, double dy, const LineAttr &line);

    /** Fill an area with faked gradient */
    void fakeLinearGrad(ColorType from, ColorType to, const Block &b, double angle, unsigned steps, double frac);
    /** Fill an area with faked gradient */
    void fakeRadialGrad(ColorType from, ColorType to, const XY &s, double outer_radius, double inner_radius,
                        unsigned steps, bool rectangle, double rad_from=0, double rad_to=2*M_PI);
    /** Stroke a single (non-double or triple), which may be dashed - consider `fake_dash`*/
    void singleLine(const Block &, const LineAttr &line);
    /** Stroke a single (non-double or triple), which may be dashed - consider `fake_dash`*/
    void singleLine(const Path&, const LineAttr &line);
    /** Stroke a single (non-double or triple), which may be dashed - consider `fake_dash`*/
    void singleLine(const Contour&, const LineAttr &line);
    /** @} */

public:
    /** Ways to open an empty canvas */
    enum class Empty {
        Dummy, ///<All operations succeed on this canvas with absolutely no effect. Uses minimal resources.
        Query  ///<We create a minimal PNG surface to be able to query font sizes. Drawing to this results in asserts.
    };
    explicit Canvas(Empty e);
    Canvas(EOutputType, const Block &tot, double copyrightTextHeight,
           std::string_view fn, const XY &scale=XY(1.,1.),
           const PBDataVector *pageBreakData=nullptr, unsigned page=0);
    Canvas(EOutputType, const Block &tot, std::string_view fn,
           const XY &scale, const XY &pageSize,
           const double margins[4],  int ha, int va,
           double copyrightTextHeight, const PBDataVector *pageBreakData);
    Canvas(EOutputType ot, cairo_surface_t *surf, const Block &tot,
           double copyrightTextHeight, const XY &scale=XY(1.,1.),
           const PBDataVector *pageBreakData=nullptr, unsigned page=0);
    Canvas(EOutputType ot, cairo_surface_t *surf, const Block &tot);
    /** Returns the state of the Canvas*/
    bool TurnPage(const PBDataVector *pageBreakData, unsigned next_page,
                  MscError *pError=nullptr);
    bool ErrorAfterCreation(MscError* pError = nullptr, const PBDataVector* pageBreakData = nullptr);
#ifdef CAIRO_HAS_FC_FONT
protected:
    FcPattern* fc_pattern = nullptr; //Destroy this pattern in CloseOutput()
#endif
#ifdef CAIRO_HAS_WIN32_SURFACE
protected:
    /** @name Windows-related members
     * @{ */
    HDC win32_dc;                        ///<The windows DC we created in the constructor.
    HDC original_hdc;                    ///<The eventual target windows DC (user supplied or file on disk)
    const bool external_hdc;             ///<Whether `original_hdc` is user supplied or not.
    XY original_device_size;             ///<The size of the output `(header+page height+copyright)*fake_scale`
    size_t stored_metafile_size;         ///<When rendering on WMF or EMF, store the size of the resulting file in CloseOutput()
    Contour stored_fallback_image_places;///<When rendering on WMF or EMF, store the location of fallback images in chart space in CloseOutput()
    /** @} */
public:
    Canvas(EOutputType, HDC hdc, const Block &tot=Block(0,0,0,0),
           double copyrightTextHeight=0, const XY &scale=XY(1.,1.),
           const PBDataVector *pageBreakData=nullptr, unsigned page=0);
    /** When rendering on WMF or EMF, return the size of the resulting file. Valid only after CloseOutput().*/
    size_t GetMetaFileSize() const {_ASSERT(outType==WMF||outType==EMF||outType==EMFWMF||outType==PRINTER); return stored_metafile_size;}
    /** When rendering on WMF or EMF, return the location of fallback images in chart space. Valid only after in CloseOutput()*/
    Contour &GetFallbackImagePlaces() {_ASSERT(outType==WMF||outType==EMF||outType==EMFWMF||outType==PRINTER); return stored_fallback_image_places;}
    static Contour FallbackImages(HENHMETAFILE hemf, LPRECT lpRECT);
    static void ExpandStrokePathRects(HDC hDC, HENHMETAFILE hemf, LPRECT lpRECT, size_t amount);
    HENHMETAFILE CloseAndGetEMF();
#endif
public:
    /** Set the fallback image resolution.*/
    void SetFallbackImageResolution(double res) {if (error.empty()) cairo_surface_set_fallback_resolution(surface, res/fake_scale, res/fake_scale);}
    /** Switches to the second drawing situation allowing to draw header (inserted by e.g., automatic pagination) and footer (copyright text).*/
    void PrepareForHeaderFooter();
    /** Instruct to add the chart text in an iTXt chunk to a PNG file at the end.
     * The chart_text is expected to remain valid until CloseOutput().
     * Returns false if we are not a PNG output or in error.*/
    bool AddiTXtChunk(EmbedChartData &&chart);
    /** Flush output, write to file and close canvas.*/
    void CloseOutput();
    ~Canvas() {if (error.empty()) CloseOutput();}

    /** Return our output type*/
    EOutputType GetOutputType() const {return outType;}
    /** Return the current cairo context for direct manipulation. It may return nullptr.*/
    cairo_t *GetContext() const {return cr;}
    /** Our current total size*/
    const Block &GetSize() const {return total;}

    /** True if the target does not support complex clipping */
    bool NeedsArrowFix() const {return needs_arrow_fix;}
    /** True if the target has imprecise positioning */
    bool HasImprecisePositioning() const {return imprecise_positioning;}
    /** True if the target has disappearing lines. */
    bool NeedsStrokePath_rclBoundsFix() const {return needs_strokepath_rclboundsfix;}
    /** True if we should avoid transparency when drawing */
    bool AvoidTransparency() const {return avoid_transparency;}

    /** Adds a generic shape if we do not do graphics, but collect shapes. */
    bool Add(GenericShape&& shape);
    /** Adds several generic shapes if we do not do graphics, but collect shapes. */
    bool AddMore(std::ranges::range auto&& shapes);

    /** Set color, line with and dash */
    void SetLineAttr(const LineAttr&);
    /** Set the line join style & return previous one.*/
    cairo_line_join_t SetLineJoin(cairo_line_join_t t);
    /** Set the line cap style & return previous one.*/
    cairo_line_cap_t SetLineCap(cairo_line_cap_t t);
    /** Clip to a rectangle */
    void Clip(double sx, double dx, double sy, double dy);
    /** Clip to a rectangle with corners, see `line.corner` and `line.radius`*/
    void Clip(double sx, double dx, double sy, double dy, const LineAttr &line);
    /** Clip to a rectangle */
    void Clip(const XY &s, const XY &d);
    /** Clip to a rectangle with corners, see `line.corner` and `line.radius`*/
    void Clip(const XY &s, const XY &d, const LineAttr &line);
    /** Clip to a rectangle */
    void Clip(const Block &b);
    /** Clip to a rectangle with corners, see `line.corner` and `line.radius`*/
    void Clip(const Block &b, const LineAttr &line);
    /** Clip to a shape */
    void Clip(const Contour &area);
    /** Clip to the inverse of a rectangle (anything outside `area` but inside `total`*/
    void ClipInverse(const Contour &area);
    /** Undo a previous clip operation*/
    void UnClip() { if (cannot_draw()) return; cairo_restore(cr);}
    [[nodiscard]] bool Transform(const TRMatrix &);
    void Transform_Rotate(const XY &center, double radian);
    void Transform_Rotate(const XY &center, double cos, double sin);
    void Transform_Rotate90(double s, double d, bool clockwise);
    /** Transform the canvas by swapping x and y coordinates*/
    void Transform_SwapXY();
    /** Transform the canvas by flipping it along the horizontal line at y. */
    void Transform_FlipHorizontal(double y);
    /** Restore the original canvas after a transformation.*/
    void UnTransform() { if (!does_graphics()) tr_stack.pop_back(); else if (cannot_draw()) return; else cairo_restore(cr); }

    /** Draw a line along `edge` using the line style in `line`*/
    void Line(const Edge& edge, const LineAttr &line);
    /** Draw a straight line from `s` to `d` using the line style in `line`*/
    void Line(const XY &s, const XY &d, const LineAttr &line) {Line(Edge(s, d), line);}
    /** Draw a rectangle line (perhaps with corners) using the line & corner style in `line`*/
    void Line(const Block &b, const LineAttr &line);
    /** Draw an arbitrary Path using the line & corner style in `line`*/
    void Line(const Path &p, const LineAttr &line);
    /** Draw a shape using the line style in `line`*/
    void Line(const Contour &area, const LineAttr &line);
    /** Fill a rectangle with `fill`*/
    void Fill(const XY &s, const XY &d, const FillAttr &fill);
    /** Fill a rectangle with corners specified in 'line. corner` with `fill`*/
    void Fill(const XY &s, const XY &d, const LineAttr &line, const FillAttr &fill);
    /** Fill a rectangle with `fill`*/
    void Fill(const Block &b, const FillAttr &fill);
    /** Fill a rectangle with corners specified in 'line. corner` with `fill`*/
    void Fill(const Block &b, const LineAttr &line, const FillAttr &fill);
    /** Fill a shape with `fill`*/
    void Fill(const Contour &area, const FillAttr &fill);
    /**Draw shadow to a rectangle. `angle_radian` is the clockwise rotation*/
    void Shadow(const Block &b, const ShadowAttr &shadow, double angle_radian=0) {Shadow(b, LineAttr(), shadow, angle_radian);}
    /**Draw shadow to a rectangle with corners. `angle_radian` is the clockwise rotation*/
    void Shadow(const Block &b, const LineAttr &line, const ShadowAttr &shadow, double angle_radian=0);
    /**Draw shadow to a shape. `angle_radian` is the clockwise rotation*/
    void Shadow(const Contour &area, const ShadowAttr &shadow, double angle_radian=0);
    /** Draws a shape with label and shadow */
    void Entity(const Block &outer_edge, const Label &label, const SimpleStyle &style, const ShapeCollection &shapes, int shape = -1, bool align_top = true);
};

inline void Canvas::Clip(double sx, double dx, double sy, double dy) { if (cannot_draw()) return;  cairo_save(cr); RectanglePath(sx, dx, sy, dy); cairo_clip(cr); }
inline void Canvas::Clip(double sx, double dx, double sy, double dy, const LineAttr &line) { if (cannot_draw()) return; cairo_save(cr); RectanglePath(sx, dx, sy, dy, line); cairo_clip(cr);}
inline void Canvas::Clip(const XY &s, const XY &d) { if (cannot_draw()) return; cairo_save(cr); RectanglePath(s.x, d.x, s.y, d.y); cairo_clip(cr);}
inline void Canvas::Clip(const XY &s, const XY &d, const LineAttr &line) { if (cannot_draw()) return; cairo_save(cr); RectanglePath(s.x, d.x, s.y, d.y, line); cairo_clip(cr);}
inline void Canvas::Clip(const Block &b) { if (cannot_draw()) return; cairo_save(cr); RectanglePath(b.x.from, b.x.till, b.y.from, b.y.till); cairo_clip(cr);}
//inline void Canvas::Clip(const Block &b, const LineAttr &line); not inline
//void Clip(const EllipseData &ellipse); not inline
inline void Canvas::Clip(const Contour &area) { if (cannot_draw()) return; cairo_save(cr); area.CairoPath(cr, true); cairo_clip(cr);}


inline void Canvas::Fill(const XY &s, const XY &d, const FillAttr &fill) {Fill(Block(s, d), fill);}
inline void Canvas::Fill(const XY &s, const XY &d, const LineAttr &line, const FillAttr &fill) {Fill(line.CreateRectangle_ForFill(s, d), fill);}
//void Canvas:: Fill(const Block &b, const FillAttr &fill); //Not inline
inline void Canvas::Fill(const Block &b, const LineAttr &line, const FillAttr &fill) {Clip(b, line); Fill(b, fill); UnClip();}
//void Canvas::Fill(const EllipseData &ellipse, const FillAttr &fill); //Not inline
inline void Canvas::Fill(const Contour &area, const FillAttr &fill) {Clip(area); Fill(area.GetBoundingBox(), fill); UnClip();}

//This needs to be in this file for a definition of 'Canvas'
/** Draws the arrowheads and the path together.
 * If the canvas NeedsArrowFix() then we chop the path else we clip it.
 * We only draw the arrowheads at the end of the path, no mid-arrowheads
 * even if the supplied 'ah' argument has them.
 * @param canvas The canvas to draw onto.
 * @param ah Either a OneArrowhead or a TwoArrowHeads (or descendant) class
 * @param [in] path The path with the arrowhead at its (start and) end.
 * @param [in] line The line style of the path.*/
template <class ArrowHeads>
void DrawPathWithArrows(Canvas &canvas, ArrowHeads &ah, const Path &path, const LineAttr &line)
{
    if (canvas.NeedsArrowFix()) {
        Path p = ah.ClipLine(p, line);
        canvas.Line(p, line);
    } else {
        canvas.ClipInverse(ah.ClipForLine(path, line));
        canvas.Line(path, line);
        canvas.UnClip();
    }
    ah.Draw(canvas, path, line);
}

//This needs to be in this file for a definition of 'Canvas'
/** Draws the arrowheads and the path together using an arrowhead instance class.
 * If the canvas NeedsArrowFix() then we chop the path else we clip it.
 * We only draw the arrowheads at the end of the path, no mid-arrowheads
 * even if the supplied 'ah' argument has them.
 * @param canvas The canvas to draw onto.
 * @param ahi Either a OneArrowheadInstance or a TwoArrowHeadsInstance (or descendant) class
 * @param ah Either a OneArrowhead or a TwoArrowHeads (or descendant) class
 * @param [in] path The path with the arrowhead at its (start and) end.
 * @param [in] line The line style of the path.*/
template <class ArrowHeadsInstance, class ArrowHeads>
void DrawPathWithArrows(Canvas &canvas, const ArrowHeadsInstance &ahi, ArrowHeads &ah, const Path &path, const LineAttr &line)
{
    if (canvas.does_graphics()) {
        if (canvas.NeedsArrowFix()) {
            Path p = ahi.ClipLine(ah, p, line);
            canvas.Line(p, line);
        } else {
            canvas.ClipInverse(ahi.ClipForLine(ah, path, line));
            canvas.Line(path, line);
            canvas.UnClip();
        }
        ahi.Draw(ah, canvas, path, line);
    } else
        canvas.Add(GSPath(path, line, &ah.GetArrowHead(false, EArrowEnd::END), &ah.GetArrowHead(false, EArrowEnd::START)));
}


bool Canvas::AddMore(std::ranges::range auto&& shapes) {
    if (does_graphics()) return false;
    if (!ppt.is_ok()) return false;
    for (auto& shape : shapes)
        if (!Add(std::move(shape).get())) return false;
    return true;
}

#ifdef _DEBUG
#include "contour_debug.h"
#endif



#endif
