using System;
using System.Drawing;
using System.IO;
using System.Text;
using System.Collections.Generic;
using FastReport.Table;
using FastReport.Utils;
using FastReport.Export;
using FastReport.SVG;
using System.Drawing.Imaging;
using System.Windows.Forms;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using FastReport.Web.Blazor.Export.ReportComponent;
using FastReport.AdvMatrix;
using static FastReport.Web.Blazor.Export.BlazorConstants;

namespace FastReport.Web.Blazor.Export
{
    public class InputChanged
    {
        private readonly BlazorExport export;
        private readonly ReportComponentBase obj;
        private readonly int CurPage;

        public void Invoke()
        {
            export.OnClick(null, new OnClickEventArgs(obj, ClickType.TextEdit, CurPage));
        }

        public InputChanged(BlazorExport export, TextObject obj, int curPage)
        {
            this.export = export;
            this.obj = obj;
            this.CurPage = curPage;
        }
    }

    public partial class BlazorExport
    {
        private bool doPageBreak;

        const string defaultStyle = "position:absolute;";

        private string GetStyle(Font? Font, in Color TextColor, in Color FillColor,
            bool RTL, HorzAlign HAlign, Border Border, bool WordWrap, float LineHeight, float Width, float Height, bool Clip)
        {
            StringBuilder style = new StringBuilder(256);

            if (Font != null)
            {
                if (Zoom != 1)
                {
                    using (Font newFont = new Font(Font.FontFamily, Font.Size * Zoom, Font.Style, Font.Unit, Font.GdiCharSet, Font.GdiVerticalFont))
                        HTMLFontStyle(style, newFont, LineHeight);
                }
                else
                    HTMLFontStyle(style, Font, LineHeight);
            }
            style.Append("text-align:");
            if (HAlign == HorzAlign.Left)
                style.Append(RTL ? "right" : "left");
            else if (HAlign == HorzAlign.Right)
                style.Append(RTL ? "left" : "right");
            else if (HAlign == HorzAlign.Center)
                style.Append("center");
            else
                style.Append("justify");
            style.Append(';');

            if (WordWrap)
                style.Append("word-wrap:break-word;");

            if (Clip)
                style.Append("overflow:hidden;");

            style.Append("position:absolute;color:").
                Append(HTMLColor(TextColor)).
                Append(";background-color:").
                Append(FillColor.A == 0 ? "transparent" : HTMLColor(FillColor)).
                Append(';').Append(RTL ? "direction:rtl;" : String.Empty);

            Border newBorder = Border;
            HTMLBorder(style, newBorder);
            style.Append("width:").Append(Px(Math.Abs(Width) * Zoom)).Append("px;height:").Append(Px(Math.Abs(Height) * Zoom)).Append("px;");
            return style.ToString();
        }

        private int UpdateCSSTable(ReportComponentBase obj)
        {
            string style;
            if (obj is TextObject textObj)
            {
                style = GetStyle(textObj.Font, textObj.TextColor, textObj.FillColor,
                    textObj.RightToLeft, textObj.HorzAlign, textObj.Border, textObj.WordWrap, textObj.LineHeight,
                    textObj.Width, textObj.Height, textObj.Clip);
            }
            else if (obj is HtmlObject htmlObj)
            {
                style = GetStyle(DrawUtils.DefaultTextObjectFont, Color.Black, htmlObj.FillColor,
                    false, HorzAlign.Left, htmlObj.Border, true, 0, htmlObj.Width, htmlObj.Height, false);
            }
            else
                style = GetStyle(null, Color.White, obj.FillColor, false, HorzAlign.Center, obj.Border, false, 0, obj.Width, obj.Height, false);
            return UpdateCSSTable(style);
        }

        private int UpdateCSSTable(string style)
        {
            int i = cssStyles.IndexOf(style);
            if (i == -1)
            {
                i = cssStyles.Count;
                cssStyles.Add(style);
            }
            return i;
        }

        private void ExportPageStylesLayers()
        {
            if (prevStyleListIndex < cssStyles.Count)
            {
                var context = globalContext;
                HTMLGetStylesHeader();
                context.OpenElement(STYLE);
                context.AddAttribute(TYPE, "text/css");
                var styles = new StringBuilder(256);
                styles.AppendLine("<!-- ");
                for (int i = prevStyleListIndex; i < cssStyles.Count; i++)
                    styles.Append(HTMLGetStyleHeader(i))
                        .Append(cssStyles[i])
                        .Append('}')
                        .AppendLine();
                styles.AppendLine("-->");
                context.AddContent(styles.ToString());
                context.CloseElement();
            }
        }

        private string GetStyleTag(int index)
        {
            return $"{stylePrefix}s{index}";
        }

        private void Layer(ReportComponentBase obj,
            float Left, float Top, float Width, float Height, RenderFragment? Text, string? style, StringBuilder? addstyletag)
        {
            if (obj != null)
            {
                var context = globalContext;
                ClickType onclick = ClickType.Empty;

                if (!String.IsNullOrEmpty(obj.ClickEvent) || obj.HasClickListeners())
                {
                    onclick = ClickType.Click;
                }

                if (obj is CheckBoxObject checkBoxObject && checkBoxObject.Editable)
                {
                    onclick = ClickType.CheckboxClick;
                }
                if (obj is MatrixButton matrixButton)
                {
                    onclick = ClickType.AdvancedMatrixClick;
             
                }

                // we need to adjust left, top, width and height values because borders take up space in html elements
                float borderLeft;
                float borderRight;
                float borderTop;
                float borderBottom;
                HTMLBorderWidthValues(obj, out borderLeft, out borderTop, out borderRight, out borderBottom);

                bool needHref = !String.IsNullOrEmpty(obj.Hyperlink.Value);
                if(needHref)
                {
                    GetHref(obj); // return don't close <a>
                }

                context.OpenElement(DIV);
                if (!string.IsNullOrEmpty(style))
                {
                    context.AddAttribute(CLASS, style);
                }
                StringBuilder sb = new StringBuilder(32);
                if (onclick != ClickType.Empty || needHref)
                    sb.Append("cursor:pointer;");
                sb.Append("left:").Append(Px((leftMargin + Left) * Zoom - borderLeft / 2f)).
                Append("px;top:").Append(Px((topMargin + Top) * Zoom - borderTop / 2f)).
                Append("px;width:").Append(Px(Width * Zoom - borderRight / 2f - borderLeft / 2f)).
                Append("px;height:").Append(Px(Height * Zoom - borderBottom / 2f - borderTop / 2f)).
                Append("px;");

                if (addstyletag != null)
                    sb.Append(addstyletag);

                context.AddAttribute(STYLE, sb.ToString());
                if(obj is MatrixButton)
                {
                    context.AddOnClick(new OnClickEventArgs(obj, onclick, CurPage));
                }
                if (obj is TextObject textObject && textObject.Editable)
                {
                    context.AddContent(AddTextInput(obj as TextObject, style));
                }
                else
                {
                    if (onclick != ClickType.Empty)
                    {
                        context.AddOnClick(new OnClickEventArgs(obj, onclick, CurPage));
                    }

                    if (Text == null)
                        context.AddMarkupContent(NBSP);
                    else
                        context.AddContent(Text);
                }

                context.CloseElement(); // </div>
                if (needHref)
                {
                    context.CloseElement();  // "</a>"
                }
            }
        }

        private void GetHref(ReportComponentBase obj)
        {
            var context = globalContext;
            context.OpenElement(A);
      
            if (obj is TextObject textObject)
            {
                string hrefStyle = "color:" + HTMLColor(textObject.TextColor);
                if (!textObject.Font.Underline)
                    hrefStyle += ";text-decoration:none";
                context.AddAttribute(STYLE, hrefStyle);
            }

            if (obj.Hyperlink.Kind == HyperlinkKind.URL)
            {
                context.AddAttribute(HREF, obj.Hyperlink.Value);
                if (obj.Hyperlink.OpenLinkInNewTab)
                    context.AddAttribute("target", "_blank");
            }
            else if (obj.Hyperlink.Kind == HyperlinkKind.DetailReport)
            {
                context.AddOnClick(new OnClickEventArgs(obj, ClickType.DetailedReport));
            }
            else if (obj.Hyperlink.Kind == HyperlinkKind.DetailPage)
            {
                context.AddOnClick(new OnClickEventArgs(obj, ClickType.DetailedPage));
            }
            else if (obj.Hyperlink.Kind == HyperlinkKind.Bookmark)
            {
                context.AddOnClick(new OnClickEventArgs(obj, ClickType.Bookmark));
            }
            else if (obj.Hyperlink.Kind == HyperlinkKind.PageNumber)
            {
                context.AddOnClick(new OnClickEventArgs(obj, ClickType.PageNumber));
            }
            //context.CloseElement();
        }

        private RenderFragment AddTextInput(TextObject obj, string classTag)
        {
            return b =>
            {
                var context = new RenderContext(b);
                context.OpenComponent<TextInput>();
                context.AddAttribute(nameof(TextInput.TextObject), obj);
                context.AddAttribute(nameof(TextInput.ClassTag), classTag);
                context.AddAttribute(nameof(TextInput.OnChange), new InputChanged(this, obj, CurPage));
                context.CloseComponent();
            };
        }

        private RenderFragment GetSpanText(TextObjectBase obj, RenderFragment text,
            float top, float width,
            float ParagraphOffset)
        {
            return builder =>
            {
                var context = new RenderContext(builder);
                StringBuilder style = new StringBuilder(64);
                style.Append("display:block;border:0;width:").Append(PxClosed(width * Zoom));
                if (ParagraphOffset != 0)
                    style.Append("text-indent:").Append(PxClosed(ParagraphOffset * Zoom));
                if (obj.Padding.Left != 0)
                    style.Append("padding-left:").Append(PxClosed(obj.Padding.Left * Zoom));
                if (obj.Padding.Right != 0)
                    style.Append("padding-right:").Append(PxClosed(obj.Padding.Right * Zoom));
                if (top != 0)
                    style.Append("margin-top:").Append(PxClosed(top * Zoom));

                // we need to apply border width in order to position our div perfectly
                float borderLeft;
                float borderTop;
                if (HTMLBorderWidthValues(obj, out borderLeft, out borderTop, out _, out _))
                {
                    style.Append("position:absolute;left:").Append(Px(-1 * borderLeft / 2f))
                        .Append("px;top:").Append(Px(-1 * borderTop / 2f)).Append("px;");
                }

                context.OpenElement(DIV);
                context.AddAttribute(CLASS, GetStyleTag(UpdateCSSTable(style.ToString())));
                context.AddContent(text);
                context.CloseElement();
            };

        }

        private void LayerText(TextObject obj)
        {
            float top = 0;
            var context = globalContext;
            RenderFragment text;
            switch (obj.TextRenderType)
            {
                case TextRenderType.HtmlParagraph:
                    RenderFragment htmlParagraph;
                    using (HtmlTextRenderer htmlTextRenderer = obj.GetHtmlTextRenderer(Zoom, Zoom))
                    {
                        if (obj.VertAlign == VertAlign.Center)
                        {
                            top = (obj.Height - htmlTextRenderer.CalcHeight()) / 2;
                        }
                        else if (obj.VertAlign == VertAlign.Bottom)
                        {
                            top = obj.Height - htmlTextRenderer.CalcHeight();
                        }
                        htmlParagraph = GetHtmlParagraph(htmlTextRenderer);
                    }
                    text = GetSpanText(obj, htmlParagraph,
                        top + obj.Padding.Top,
                        obj.Width - obj.Padding.Horizontal,
                        obj.ParagraphOffset);
                    break;
                default:
                    if (obj.VertAlign != VertAlign.Top)
                    {
                        var g = htmlMeasureGraphics;
                        using (Font f = new Font(obj.Font.Name, obj.Font.Size, obj.Font.Style))
                        {
                            RectangleF textRect = new RectangleF(obj.AbsLeft, obj.AbsTop, obj.Width, obj.Height);
                            StringFormat format = obj.GetStringFormat(Report.GraphicCache, 0);
                            Brush textBrush = Report.GraphicCache.GetBrush(obj.TextColor);
                            AdvancedTextRenderer renderer = new AdvancedTextRenderer(obj.Text, g, f, textBrush, null,
                                textRect, format, obj.HorzAlign, obj.VertAlign, obj.LineHeight, obj.Angle, obj.FontWidthRatio,
                                obj.ForceJustify, obj.Wysiwyg, obj.HasHtmlTags, false, Zoom, Zoom, obj.InlineImageCache);
                            if (renderer.Paragraphs.Count > 0)
                                if (renderer.Paragraphs[0].Lines.Count > 0)
                                {
                                    float height = renderer.Paragraphs[0].Lines[0].CalcHeight();
                                    if (height > obj.Height)
                                        top = -(height - obj.Height) / 2;
                                    else
                                        top = renderer.Paragraphs[0].Lines[0].Top - obj.AbsTop;
                                }
                        }
                    }

                    void htmlStringParse(RenderTreeBuilder builder)
                    {
                        var context = new RenderContext(builder);
                        var content = ExportUtils.HtmlString(obj.Text, obj.TextRenderType).ToString();
                        context.AddMarkupContent(content);
                    }

                    text = GetSpanText(obj, htmlStringParse,
                        top + obj.Padding.Top,
                        obj.Width - obj.Padding.Horizontal,
                        obj.ParagraphOffset);
                    break;
            }
            context.AddContent(LayerBack(obj, text));
        }

        private RenderFragment GetHtmlParagraph(HtmlTextRenderer renderer)
        {
            return builder =>
            {
                var context = new RenderContext(builder);

                foreach (HtmlTextRenderer.Paragraph paragraph in renderer.Paragraphs)
                    foreach (HtmlTextRenderer.Line line in paragraph.Lines)
                    {
                        StringBuilder sb = new StringBuilder(128);
                        context.OpenElement(SPAN);
                        sb.Append("display:block;");
                        if (line.Top + line.Height > renderer.DisplayRect.Bottom)
                            sb.Append("height:").Append(Math.Max(renderer.DisplayRect.Bottom - line.Top, 0).ToString(HtmlTextRenderer.CultureInfo)).Append("px;");
                        else
                        {
                            //sb.Append("height:").Append(line.Height.ToString(HtmlTextRenderer.CultureInfo)).Append("px;");
                            if (line.LineSpacing > 0)
                            {
                                sb.Append("margin-bottom:").Append(line.LineSpacing.ToString(HtmlTextRenderer.CultureInfo)).Append("px;");
                            }
                        }
                        sb.Append("overflow:hidden;");
                        sb.Append("line-height:").Append(line.Height.ToString(HtmlTextRenderer.CultureInfo)).Append("px;");
                        if (line.HorzAlign == HorzAlign.Justify)
                            sb.Append("text-align-last:justify;");
                        else sb.Append("white-space:pre;");
                        context.AddAttribute(STYLE, sb.ToString());

                        FastString htmlText = new FastString();
                        HtmlTextRenderer.StyleDescriptor? styleDesc = null;
                        foreach (HtmlTextRenderer.Word word in line.Words)
                        {
                            foreach (HtmlTextRenderer.Run run in word.Runs)
                            {
                                if (!run.Style.FullEquals(styleDesc))
                                {
                                    if (styleDesc != null)
                                        styleDesc.ToHtml(htmlText, true);   // TODO: close ???
                                    styleDesc = run.Style;
                                    styleDesc.ToHtml(htmlText, false);
                                }

                                if (run is HtmlTextRenderer.RunText runText)
                                {
                                    foreach (char ch in runText.Text)
                                    {
                                        switch (ch)
                                        {
                                            case '"':
                                                htmlText.Append("&quot;");
                                                break;
                                            case '&':
                                                htmlText.Append("&amp;");
                                                break;
                                            case '<':
                                                htmlText.Append("&lt;");
                                                break;
                                            case '>':
                                                htmlText.Append("&gt;");
                                                break;
                                            case '\t':
                                                htmlText.Append("&Tab;");
                                                break;
                                            default:
                                                htmlText.Append(ch);
                                                break;
                                        }
                                    }

                                }
                                else if (run is HtmlTextRenderer.RunImage runImage)
                                {
                                    using (MemoryStream ms = new MemoryStream())
                                    {
                                        try
                                        {
                                            float w, h;
                                            using (Bitmap bmp = runImage.GetBitmap(out w, out h))
                                            {

                                                bmp.Save(ms, ImageFormat.Png);
                                            }
                                            ms.Flush();
                                            htmlText.Append("<img src=\"data:image/png;base64,")
                                                .Append(Convert.ToBase64String(ms.ToArray()))
                                                .Append("\" width=\"").Append(w.ToString(HtmlTextRenderer.CultureInfo))
                                                .Append("\" height=\"").Append(h.ToString(HtmlTextRenderer.CultureInfo))
                                                .Append("\"/>");
                                        }
                                        catch (Exception /*e*/)
                                        {

                                        }
                                    }

                                }
                                //run.ToHtml(htmlText, true);
                            }
                        }
                        if (styleDesc != null)
                            styleDesc.ToHtml(htmlText, true);
                        else htmlText.Append("<br/>");
                        context.AddMarkupContent(htmlText.ToString());
                        context.CloseElement();
                    }

            };
        }

        private RenderFragment LayerHtml(HtmlObject obj)
        {
            void textFragment(RenderTreeBuilder builder)
            {
                var context = new RenderContext(builder);
                context.AddContent(obj.Text);
            }

            return LayerBack(obj,
                GetSpanText(obj, textFragment,
                obj.Padding.Top,
                obj.Width - obj.Padding.Horizontal,
                0));
        }

        private string GetLayerPicture(ReportComponentBase obj, out float Width, out float Height)
        {
            string result = String.Empty;
            Width = 0;
            Height = 0;

            if (obj != null)
            {
                if (pictures)
                {
                    MemoryStream PictureStream = new MemoryStream();
                    ImageFormat FPictureFormat = ImageFormat.Bmp;
                    if (imageFormat == ImageFormat.Png)
                        FPictureFormat = ImageFormat.Png;
                    else if (imageFormat == ImageFormat.Jpeg)
                        FPictureFormat = ImageFormat.Jpeg;
                    else if (imageFormat == ImageFormat.Gif)
                        FPictureFormat = ImageFormat.Gif;

                    Width = obj.Width == 0 ? obj.Border.LeftLine.Width : obj.Width;
                    Height = obj.Height == 0 ? obj.Border.TopLine.Width : obj.Height;

                    if (Math.Abs(Width) * Zoom < 1 && Zoom > 0)
                        Width = 1 / Zoom;

                    if (Math.Abs(Height) * Zoom < 1 && Zoom > 0)
                        Height = 1 / Zoom;

                    using (Image image =
                        new Bitmap(
                            (int)Math.Abs(Math.Round(Width * Zoom)),
                            (int)Math.Abs(Math.Round(Height * Zoom))
                            )
                          )
                    {
                        using (Graphics g = Graphics.FromImage(image))
                        {
                            if (obj is TextObjectBase)
                                g.Clear(Color.White);

                            float Left = Width > 0 ? obj.AbsLeft : obj.AbsLeft + Width;
                            float Top = Height > 0 ? obj.AbsTop : obj.AbsTop + Height;

                            float dx = 0;
                            float dy = 0;
                            g.TranslateTransform((-Left - dx) * Zoom, (-Top - dy) * Zoom);

                            BorderLines oldLines = obj.Border.Lines;
                            obj.Border.Lines = BorderLines.None;
                            obj.Draw(new FRPaintEventArgs(g, Zoom, Zoom, Report.GraphicCache));
                            obj.Border.Lines = oldLines;
                     
                        }

                        if (FPictureFormat == ImageFormat.Jpeg)
                            SaveJpeg(image, PictureStream, 95);
                        else
                            image.Save(PictureStream, FPictureFormat);
                    }
                    PictureStream.Position = 0;

                    string hash = String.Empty;
                    if (obj is PictureObject pic)
                    {
                        if (pic.Image != null)
                        {
                            hash = Crypter.ComputeHash(PictureStream);
                            PictureStream.Position = 0;
                        }
                    }
                    else
                        hash = Crypter.ComputeHash(PictureStream);
                    result = HTMLGetImage(d.CurrentPage, hash, PictureStream, false);
                }
            }
            return result;
        }

        private void LayerPicture(ReportComponentBase obj, RenderFragment? text)
        {
            if (pictures)
            {
                var context = globalContext;
                int styleindex = UpdateCSSTable(obj);
                string old_text = String.Empty;

                if (IsMemo(obj))
                {
                    old_text = (obj as TextObject).Text;
                    (obj as TextObject).Text = String.Empty;
                }

                float Width, Height;
                string pic = GetLayerPicture(obj, out Width, out Height);

                if (IsMemo(obj))
                    (obj as TextObject).Text = old_text;

                StringBuilder picStyleBuilder = new StringBuilder("background: url('")
                    .Append(pic).Append("') no-repeat !important;-webkit-print-color-adjust:exact;");

                int picStyleIndex = UpdateCSSTable(picStyleBuilder.ToString());


                string style = $"{stylePrefix}s{styleindex} {stylePrefix}s{picStyleIndex}";

                //FastString addstyle = new FastString(128);
                //addstyle.Append(" background: url('").Append(pic).Append("') no-repeat !important;-webkit-print-color-adjust:exact;");

                //if (String.IsNullOrEmpty(text))
                //    text = NBSP;

                float x = Width > 0 ? obj.AbsLeft : (obj.AbsLeft + Width);
                float y = Height > 0 ? obj.AbsTop : (obj.AbsTop + Height);

                Layer(obj, x, y, Width, Height, text, style, null);
            }
        }

        private void LayerShape(ShapeObject obj)
        {
            StringBuilder addstyle = new StringBuilder(64);

            addstyle.Append(defaultStyle);

            addstyle.Append("background: url('" + GetLayerPicture(obj, out _, out _) + "');no-repeat !important;-webkit-print-color-adjust:exact;");

            float x = obj.Width > 0 ? obj.AbsLeft : (obj.AbsLeft + obj.Width);
            float y = obj.Height > 0 ? obj.AbsTop : (obj.AbsTop + obj.Height);
            Layer(obj, x, y, obj.Width, obj.Height, null, null, addstyle);
        }

        private RenderFragment LayerBack(ReportComponentBase obj, RenderFragment? text)
        {
            return (builder) =>
            {
                var context = new RenderContext(builder);
                if (obj.Border.Shadow)
                {
                    using (TextObject shadow = new TextObject())
                    {
                        shadow.Left = obj.AbsLeft + obj.Border.ShadowWidth + obj.Border.LeftLine.Width;
                        shadow.Top = obj.AbsTop + obj.Height + obj.Border.BottomLine.Width;
                        shadow.Width = obj.Width + obj.Border.RightLine.Width;
                        shadow.Height = obj.Border.ShadowWidth + obj.Border.BottomLine.Width;
                        shadow.FillColor = obj.Border.ShadowColor;
                        shadow.Border.Lines = BorderLines.None;
                        context.AddContent(LayerBack(shadow, null));

                        shadow.Left = obj.AbsLeft + obj.Width + obj.Border.RightLine.Width;
                        shadow.Top = obj.AbsTop + obj.Border.ShadowWidth + obj.Border.TopLine.Width;
                        shadow.Width = obj.Border.ShadowWidth + obj.Border.RightLine.Width;
                        shadow.Height = obj.Height;
                        context.AddContent(LayerBack(shadow, null));
                    }
                }

                if (!(obj is PolyLineObject))
                {
                    if (obj.Fill is SolidFill)
                        Layer(obj, obj.AbsLeft, obj.AbsTop, obj.Width, obj.Height, text, GetStyleTag(UpdateCSSTable(obj)), null);
                    else
                        LayerPicture(obj, text);
                }
            };
        }

        private void LayerTable(TableBase table)
        {
            var context = globalContext;
            float y = 0;
            for (int i = 0; i < table.RowCount; i++)
            {
                float x = 0;
                for (int j = 0; j < table.ColumnCount; j++)
                {
                    if (!table.IsInsideSpan(table[j, i]))
                    {
                        TableCell textcell = table[j, i];

                        textcell.Left = x;
                        textcell.Top = y;

                        if (textcell is TextObject && !textcell.TextOutline.Enabled && IsMemo(textcell))
                            LayerText(textcell);
                        else
                        {
                            context.AddContent(LayerBack(textcell, null));
                            LayerPicture(textcell, null);
                        }
                    }
                    x += table.Columns[j].Width;
                }
                y += table.Rows[i].Height;
            }
        }

        private bool IsMemo(ReportComponentBase Obj)
        {
            if(Obj is TextObject aObj)
            {
                return (aObj.Angle == 0) && (aObj.FontWidthRatio == 1) && (!aObj.TextOutline.Enabled) && (!aObj.Underlines);
            }
            return false;
            
        }

        private void Watermark(ReportPage page, bool drawText)
        {
            var context = globalContext;
            using (PictureObject pictureWatermark = new PictureObject())
            {
                pictureWatermark.Left = 0;
                pictureWatermark.Top = 0;

                pictureWatermark.Width = (ExportUtils.GetPageWidth(page) - page.LeftMargin - page.RightMargin) * Units.Millimeters;
                pictureWatermark.Height = (ExportUtils.GetPageHeight(page) - page.TopMargin - page.BottomMargin) * Units.Millimeters;

                pictureWatermark.SizeMode = PictureBoxSizeMode.Normal;
                pictureWatermark.Image = new Bitmap((int)pictureWatermark.Width, (int)pictureWatermark.Height);

                using (Graphics g = Graphics.FromImage(pictureWatermark.Image))
                {
                    g.Clear(Color.Transparent);
                    if (drawText)
                        page.Watermark.DrawText(new FRPaintEventArgs(g, 1f, 1f, Report.GraphicCache),
                            new RectangleF(0, 0, pictureWatermark.Width, pictureWatermark.Height), Report, true);
                    else
                        page.Watermark.DrawImage(new FRPaintEventArgs(g, 1f, 1f, Report.GraphicCache),
                            new RectangleF(0, 0, pictureWatermark.Width, pictureWatermark.Height), Report, true);
                    pictureWatermark.Transparency = page.Watermark.ImageTransparency;
                    context.AddContent(LayerBack(pictureWatermark, null));
                    LayerPicture(pictureWatermark, null);
                }
            }

        }

        private void ExportHTMLPageLayeredBegin(in HTMLData d)
        {
            //bands = new List<RenderFragment>();
            ReportPage reportPage = d.page;

            if (reportPage != null)
            {
                maxWidth = ExportUtils.GetPageWidth(reportPage) * Units.Millimeters;
                maxHeight = ExportUtils.GetPageHeight(reportPage) * Units.Millimeters;

                if (enableMargins)
                {
                    leftMargin = reportPage.LeftMargin * Units.Millimeters;
                    topMargin = reportPage.TopMargin * Units.Millimeters;
                }
                else
                {
                    maxWidth -= (reportPage.LeftMargin + reportPage.RightMargin) * Units.Millimeters;
                    maxHeight -= (reportPage.TopMargin + reportPage.BottomMargin) * Units.Millimeters;
                    leftMargin = 0;
                    topMargin = 0;
                }

                currentPage = d.PageNumber - 1;
                ExportHTMLPageStart(d.PageNumber, d.CurrentPage);

                doPageBreak = singlePage && pageBreaks;
                pageStyleName = "frpage" + currentPage;

                BeginLayer(d);
            }
        }

        void BeginLayer(in HTMLData d)
        {
            ReportPage reportPage = d.page;
            var context = globalContext;
            context.AddContent(HTMLGetAncor(d.PageNumber));
            context.OpenElement(DIV);
            if (doPageBreak)
                context.AddAttribute(CLASS, pageStyleName);
            string style = $"position:relative; width:{Px(maxWidth * Zoom + 3)}px; height:{Px(maxHeight * Zoom)}px;";

            if (reportPage.Fill is SolidFill fill)
            {
                style += " background-color:" + (fill.IsTransparent ? "transparent" : HTMLColor(fill.Color));
            }
            else
            {
                // to-do for picture background
            }
            context.AddAttribute(STYLE, style);


            if (reportPage.Watermark.Enabled && !reportPage.Watermark.ShowImageOnTop)
                Watermark(reportPage, false);

            if (reportPage.Watermark.Enabled && !reportPage.Watermark.ShowTextOnTop)
                Watermark(reportPage, true);

        }

        void EndLayer(in HTMLData d)
        {
            var context = globalContext;
            // to do
            ReportPage reportPage = d.page;
            if (reportPage.Watermark.Enabled && reportPage.Watermark.ShowImageOnTop)
                Watermark(reportPage, false);

            if (reportPage.Watermark.Enabled && reportPage.Watermark.ShowTextOnTop)
                Watermark(reportPage, true);

            context.CloseElement(); // "</div>"
        }

        private void ExportHTMLPageLayeredEnd(in HTMLData d)
        {
            EndLayer(d);
            ExportPageStylesLayers();
            if (singlePage)
            {
                prevStyleListIndex = cssStyles.Count;
            }

            ExportHTMLPageFinal(d, maxWidth, maxHeight);
        }

        private void ExportBandLayers(BandBase band)
        {
            var context = globalContext;
            context.AddContent(LayerBack(band, null));
            foreach (Base c in band.ForEachAllConvectedObjects(this))
            {
                if (c is MatrixButton matrixButton)
                {
                    matrixButton.Exportable = true;
                }
                if (c is ReportComponentBase obj && obj.Exportable)
                {
                  
                    if (!String.IsNullOrEmpty(obj.Bookmark))
                    {
                        context.OpenElement(A);
                        context.AddAttribute(NAME, obj.Bookmark);
                        context.CloseElement();
                    }
                 
                    if (obj is CellularTextObject cellularTextObject)
                        obj = cellularTextObject.GetTable();
                    if (obj is TableCell)
                        continue;
                    else if (obj is TableBase table)
                    {
                        if (table.ColumnCount > 0 && table.RowCount > 0)
                        {
                            using (TextObject tableback = new TextObject())
                            {
                                tableback.Border = table.Border;
                                tableback.Fill = table.Fill;
                                tableback.FillColor = table.FillColor;
                                tableback.Left = table.AbsLeft;
                                tableback.Top = table.AbsTop;
                                float tableWidth = 0;
                                float tableHeight = 0;
                                for (int i = 0; i < table.ColumnCount; i++)
                                    tableWidth += table[i, 0].Width;
                                for (int i = 0; i < table.RowCount; i++)
                                    tableHeight += table.Rows[i].Height;
                                tableback.Width = (tableWidth < table.Width) ? tableWidth : table.Width;
                                tableback.Height = tableHeight;
                                LayerText(tableback);
                            }
                            LayerTable(table);
                        }
                    }
                    else if (IsMemo(obj))
                    {
                        LayerText(obj as TextObject);
                    }
                    else if (obj is HtmlObject htmlObject)
                    {
                        context.AddContent(LayerHtml(htmlObject));
                    }
                    else if (obj is BandBase)
                    {
                        context.AddContent(LayerBack(obj, null));
                    }
                    else if (obj is LineObject)
                    {
                        LayerPicture(obj, null);
                    }
                    else if (obj is ShapeObject shapeObject)
                    {
                        LayerShape(shapeObject);
                    }
                    else if (HasExtendedExport(obj))
                    {
                        LayerSVG(obj as SVGObject);
                    }
                    else
                    {
                        context.AddContent(LayerBack(obj, null));
                        LayerPicture(obj, null);
                    }
                }
            }

        }
    }
}
