using FastReport.Table;
using FastReport.Utils;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace FastReport.RichTextParser
{
    class RichText2ReportObject : IDisposable
    {
        static int DpiX = 96;
        Font default_font = new Font(FontFamily.GenericSerif, 12, FontStyle.Regular);
        RunFormat current_format;
        bool usePadding = false; // Control page margins

        private static int Twips2Pixels(int twips)
        {
            return (int)(((double)twips) * (1.0 / 1440.0) * DpiX);
        }

        public void Dispose()
        {
        }

        private string GetRawText(Paragraph paragraph)
        {
            StringBuilder sb = new StringBuilder();
            foreach (Run run in paragraph.runs)
                sb.Append(run.text);
            return sb.ToString();
        }

        private void GetHTMLText(FastReport.RichObject rich, ref TextObject clone, RichDocument rtf, int position, Paragraph paragraph)
        {
            int run_num = 0;
            bool span_condition = false;
            StringBuilder sb = new StringBuilder();
            RunFormat format;

            string colorname = String.Empty;
            string fontname = String.Empty;
            string fontsize = String.Empty;
            string backcolor = String.Empty;
            string URL = String.Empty;
            Font current_font = clone.Font;

            int len;
            foreach (Run run in paragraph.runs)
            {
                if(run.text.StartsWith("HYPERLINK "))
                {
                    URL = run.text.Substring(9);
                    continue;
                }
                len = run.text != "\r" ? run.text.Length : 1;
                if (rich.ActualTextStart != 0 && position + len <= rich.ActualTextStart)
                {
                    position += len;
                    continue;
                }
                format = run.format;
                if (run_num == 0)
                {

                    current_format = run.format;
                    clone.Font = GetFontFromRichStyle(rtf, current_format);
                    current_font = clone.Font;
                    clone.TextColor = current_format.color;

                    if (format.underline)
                    {
                        sb.Append("<u>");
                        current_format.underline = true;
                    }
                    if (format.bold)
                    {
                        sb.Append("<b>");
                        current_format.bold = true;
                    }
                    if (format.italic)
                    {
                        sb.Append("<i>");
                        current_format.italic = true;
                    }
                    if(format.strike)
                    {
                        sb.Append("<strike>");
                        current_format.strike = true;
                    }

                    if (current_format.BColor != null) 
                    {
                        if (current_format.BColor != Color.White && current_format.BColor != Color.Empty)
                            backcolor = string.Format("background-color:#{0:X2}{1:X2}{2:X2}", format.BColor.R, format.BColor.G, format.BColor.B);
                    }

                    if (backcolor.Length > 0) 
                    {
                        sb.Append("<span style=\"");
                        sb.Append(backcolor);
                        sb.Append("\">");
                        span_condition = true;

                    }
                }
                else
                {
                    if (current_format.underline != format.underline && !format.underline)
                    {
                        sb.Append("</u>");
                        current_format.underline = format.underline;
                    }

                    if (current_format.italic != format.italic && !format.italic)
                    {
                        sb.Append("</i>");
                        current_format.italic = format.italic;
                    }

                    if (current_format.bold != format.bold && !format.bold)
                    {
                        sb.Append("</b>");
                        current_format.bold = format.bold;
                    }

                    if(current_format.strike != format.strike && ! format.strike)
                    {
                        sb.Append("</strike>");
                        current_format.strike = format.strike;
                    }

                    if (current_format.strike != format.strike && format.strike)
                    {
                        sb.Append("<strike>");
                        current_format.strike = format.strike;
                    }

                    if (current_format.bold != format.bold && format.bold)
                    {
                        sb.Append("<b>");
                        current_format.bold = format.bold;
                    }

                    if (current_format.italic != format.italic && format.italic)
                    {
                        sb.Append("<i>");
                        current_format.italic = format.italic;
                    }

                    if (current_format.underline != format.underline && format.underline)
                    {
                        sb.Append("<u>");
                        current_format.underline = format.underline;
                    }

                    if (current_format.script_type != format.script_type)
                    {
                        if (format.script_type == RunFormat.ScriptType.Subscript)
                            sb.Append("<sub>");
                        else if (format.script_type == RunFormat.ScriptType.Superscript)
                            sb.Append("<sup>");
                        else if (current_format.script_type == RunFormat.ScriptType.Subscript)
                            sb.Append("</sub>");
                        else if (current_format.script_type == RunFormat.ScriptType.Superscript)
                            sb.Append("</sup>");
                        current_format.script_type = format.script_type;
                    }

                    if (current_format.color != format.color)
                    {
                        colorname = string.Format("color:#{0:X2}{1:X2}{2:X2};", format.color.R, format.color.G, format.color.B);
                        current_format.color = format.color;
                    }
                    if (current_format.BColor != format.BColor && format.BColor != Color.Empty) 
                    {
                        backcolor = string.Format("background-color:#{0:X2}{1:X2}{2:X2}", format.BColor.R, format.BColor.G, format.BColor.B);
#if IGNORE_SPAN
                        current_format.BColor = format.BColor;
#endif
                    }

                    if (current_format.font_size != format.font_size)
                    {
                        int fs = run.format.font_size / 2;
                        fontsize = string.Format("font-size:{0}pt;", fs);
//#ifIGNORE_SPAN
                        current_format.font_size = format.font_size;
//#endif
                    }

                    Font fnt = GetFontFromRichStyle(rtf, format);
                    if (!current_font.FontFamily.Equals(fnt.FontFamily))
                    {
                        fontname = string.Format("font-family:{0};", fnt.FontFamily.Name);
#if IGNORE_SPAN
                        current_font = fnt;
#endif
                    }
                    else
                        fontname = string.Empty;

                    if (colorname.Length > 0 || fontsize.Length > 0 || fontname.Length > 0 || backcolor.Length > 0)
                    {
                        sb.Append("<span style=\"");
                        if (colorname.Length > 0)
                            sb.Append(colorname);
                        if (fontsize.Length > 0)
                            sb.Append(fontsize);
                        if (fontname.Length > 0)
                            sb.Append(fontname);
                        if (backcolor.Length > 0)
                            sb.Append(backcolor);
                        sb.Append("\">");
                        span_condition = true;
                    }

                }
                if (run.text != "\r")
                    sb.Append(run.text);
                else if( run_num != 0 && run_num + 1 < paragraph.runs.Count)
                    sb.Append("<br>");

                position += len;
                if (rich.ActualTextLength != 0 && position >= rich.ActualTextStart + rich.ActualTextLength)
                    break;

                if (span_condition)
                {
                    sb.Append("</span>");
                    span_condition = false;
                }
                URL = String.Empty;
                run_num++;
            }
            clone.Text = sb.ToString();
        }

        private FastReport.BorderLine TranslateBorderLine(BorderLine rtf_border_line)
        {
            FastReport.BorderLine border_line = new FastReport.BorderLine();
            switch (rtf_border_line.style)
            {
                case BorderLine.Style.Thin:
                    border_line.Style = LineStyle.Solid;
                    break;
                case BorderLine.Style.Thick:
                    border_line.Style = LineStyle.Solid;
                    break;
                case BorderLine.Style.Double:
                    border_line.Style = LineStyle.Double;
                    break;
                case BorderLine.Style.Dotted:
                    border_line.Style = LineStyle.Dot;
                    break;
                default:
                    border_line.Style = LineStyle.Solid;
                    break;
            }
            border_line.Color = rtf_border_line.color;
            border_line.Width = Twips2Pixels((int)rtf_border_line.width);
            return border_line;
        }

        private FastReport.Border TranslateBorders(Column rtf_column)
        {
            FastReport.Border border = new Border();
            border.Lines = BorderLines.None;

            if (rtf_column.border_top.width > 0)
            {
                border.TopLine = TranslateBorderLine(rtf_column.border_top);
                border.Lines |= BorderLines.Top;
            }

            if (rtf_column.border_right.width > 0)
            {
                border.RightLine = TranslateBorderLine(rtf_column.border_right);
                border.Lines |= BorderLines.Right;
            }

            if (rtf_column.border_left.width > 0)
            {
                border.LeftLine = TranslateBorderLine(rtf_column.border_left);
                border.Lines |= BorderLines.Left;
            }

            if (rtf_column.border_bottom.width > 0)
            {
                border.BottomLine = TranslateBorderLine(rtf_column.border_bottom);
                border.Lines |= BorderLines.Bottom;
            }

            return border;
        }

        private Font GetFontFromRichStyle(RichDocument rtf, RunFormat format)
        {
            int font_idx = (int)format.font_idx;
            if (font_idx < rtf.font_list.Count)
            {
                RFont rf = rtf.font_list[font_idx];
                string Name = rf.FontName;
#if false // Broke PDF export
        FontStyle style = format.bold ? FontStyle.Bold : FontStyle.Regular;
#else
                FontStyle style = FontStyle.Regular;
#endif
                return new Font(rf.FontName, format.font_size / 2, style);
            }
            else
                return default_font;
        }

        internal TextObject Paragraph2ReportObjects(FastReport.RichObject rich, RichDocument rtf, int position, Paragraph paragraph)
        {
            TextObject clone = new TextObject();

            clone.TextRenderType = TextRenderType.HtmlParagraph;
            clone.CanShrink = rich.CanShrink;
            clone.CanGrow = rich.CanGrow;
            clone.CanBreak = rich.CanBreak;
            clone.Left = 0; // Will set in another place
            clone.GrowToBottom = false; // Can't be set here;
            clone.ClientSize = rich.ClientSize;
            clone.TextColor = Color.Black;
            clone.FirstTabOffset = 48;
            clone.TabWidth = 48;
            clone.Bookmark = rich.Bookmark;
            clone.Hyperlink = rich.Hyperlink;
            clone.FillColor = rich.FillColor;

            if (paragraph.runs.Count > 0)
            {
                if(paragraph.format.tab_positions != null)
                {
                    int count = paragraph.format.tab_positions.Count;
                    if (count > 0)
                        clone.FirstTabOffset = Twips2Pixels(paragraph.format.tab_positions[0]);
                    if(count > 1)
                        clone.TabWidth = Twips2Pixels(paragraph.format.tab_positions[1]) - clone.FirstTabOffset;
                    if (count > 2)
                        for(int i = 1; i < paragraph.format.tab_positions.Count; i++)
                            clone.TabPositions.Add(Twips2Pixels(paragraph.format.tab_positions[i]) - Twips2Pixels(paragraph.format.tab_positions[i -1]));
                }
                GetHTMLText(rich, ref clone, rtf, position, paragraph);
            }
            else
                clone.Font = default_font;

            switch (paragraph.format.align)
            {
                case ParagraphFormat.HorizontalAlign.Right:
                    clone.HorzAlign = HorzAlign.Right;
                    break;
                case ParagraphFormat.HorizontalAlign.Centered:
                    clone.HorzAlign = HorzAlign.Center;
                    break;
                case ParagraphFormat.HorizontalAlign.Justified:
                    clone.HorzAlign = HorzAlign.Justify;
                    break;
                default:
                    clone.HorzAlign = HorzAlign.Left;
                    break;
            }

            switch (paragraph.format.Valign)
            {
                case ParagraphFormat.VerticalAlign.Top:
                    clone.VertAlign = VertAlign.Top;
                    break;
                case ParagraphFormat.VerticalAlign.Center:
                    clone.VertAlign = VertAlign.Center;
                    break;
                case ParagraphFormat.VerticalAlign.Bottom:
                    clone.VertAlign = VertAlign.Bottom;
                    break;
                default:
                    clone.VertAlign = VertAlign.Top;
                    break;
            }

            clone.Border.Lines = BorderLines.None;
            int lineheight = paragraph.format.line_spacing;
            if (lineheight == 0)
                clone.LineHeight = (float)Math.Ceiling(clone.Font.Height * DrawUtils.ScreenDpiFX);
            else
            {
                switch (paragraph.format.lnspcmult)
                {
                    case ParagraphFormat.LnSpcMult.Exactly:
                        lineheight = Twips2Pixels(lineheight);
                        break;
                    case ParagraphFormat.LnSpcMult.Multiply:
                        lineheight = (int)(lineheight / 240f);
                        break;
                }
                lineheight = lineheight < 0 ? -lineheight : lineheight >= clone.Font.Height ? lineheight : clone.Font.Height;
                clone.ParagraphFormat.LineSpacingType = LineSpacingType.Exactly;
                clone.ParagraphFormat.LineSpacing = (lineheight)/* * DrawUtils.ScreenDpiFX */;
                clone.LineHeight = lineheight;
            }
            clone.Padding = new Padding(rich.Padding.Left, 0, rich.Padding.Right, 0);
            clone.SetReport(rich.Report);

            return clone;
        }

        internal TableObject Table2ReportObjects(FastReport.RichObject rich, RichDocument rtf, Table rtf_table)
        {
            TableObject table = new TableObject();
            int idx = 0;
            uint prev_width = 0;
            IList<TranslationPropeties> row_properties = new List<TranslationPropeties>();

            foreach (Column rtf_column in rtf_table.columns)
            {
                TableColumn column = new TableColumn();
                column.Width = Twips2Pixels((int)(rtf_column.Width - prev_width));
                prev_width = rtf_column.Width;
                column.SetIndex(idx);
                TranslationPropeties prop = new TranslationPropeties(
                    TranslateBorders(rtf_column),
                    rtf_column.back_color);
                row_properties.Add(prop);
                table.Columns.Add(column);
                idx++;
            }
            foreach (TableRow rtf_row in rtf_table.rows)
            {
                int height = rtf_row.height;
                if (height < 0)
                    height = -height;
                FastReport.Table.TableRow row = new FastReport.Table.TableRow();

                int cell_idx = 0;
                float x_pos = 0;
                foreach (RichObjectSequence sequence in rtf_row.cells)
                {
                    float top = 0;
                    TableColumn rtf_column = table.Columns[cell_idx];
                    TableCell cell = new TableCell();
                    TranslationPropeties prop = row_properties[cell_idx];
                    cell.Border = prop.border;
                    cell.FillColor = prop.background_color;
                    foreach (RichObject obj in sequence.objects)
                    {
                        switch (obj.type)
                        {
                            case RichObject.Type.Paragraph:
                                TextObject text_paragraph = Paragraph2ReportObjects(rich, rtf, 0, obj.pargraph); // TODO: Fix "pos" argument
                                text_paragraph.Width = rtf_column.Width;

                                Padding p = text_paragraph.Padding;
                                p.Top = Twips2Pixels((int)obj.pargraph.format.space_before);
                                p.Bottom = Twips2Pixels((int)obj.pargraph.format.space_after);
                                p.Left = Twips2Pixels((int)obj.pargraph.format.left_indent);
                                p.Right = Twips2Pixels((int)obj.pargraph.format.right_indent);

                                if (p.Left == 0)
                                    p.Left = 3;
                                text_paragraph.Padding = p;

                                if (obj.pargraph.runs.Count > 0)
                                    text_paragraph.Height = text_paragraph.CalcHeight() + 3.0f;
                                else
                                    text_paragraph.Height = height;
                                text_paragraph.Top = top;

                                top += text_paragraph.Height;
                                text_paragraph.Parent = cell;
                                break;

                            case RichObject.Type.Picture:
                                PictureObject picture = Picture2ReportObject(rtf, obj.picture);
                                picture.Top = top;
                                top += picture.Height;
                                picture.Parent = cell;
                                break;

                            case RichObject.Type.Table:
                                TableObject subtable = Table2ReportObjects(rich, rtf, obj.table);
                                subtable.Top = top;
                                top += subtable.Height;
                                table.Parent = cell;
                                break;
                        }
                        cell.Left = x_pos;
                        cell.Height = top; // height;
                    }
                    row.Height = (row.Height > cell.Height) ? row.Height : cell.Height;
                    row.AddChild(cell);
                    x_pos += rtf_column.Width;
                    cell_idx++;
                }
                table.Rows.Add(row);
                table.Height += row.Height;
            }
            return table;
        }

        internal PictureObject Picture2ReportObject(RichDocument rtf, Picture rtf_picture)
        {
            PictureObject picture = new PictureObject();
            picture.Image = rtf_picture.image;

            if (rtf_picture.desired_width != 0)
            {
                if (rtf_picture.scalex == 0)
                    picture.Width = Twips2Pixels(rtf_picture.desired_width);
                else
                    picture.Width = Twips2Pixels(rtf_picture.desired_width * rtf_picture.scalex / 100);
            }

            else
                picture.Width = Twips2Pixels(rtf_picture.width);

            if (rtf_picture.desired_height != 0)
            {
                if (rtf_picture.scaley != 0)
                    picture.Height = Twips2Pixels(rtf_picture.desired_height);
                else
                    picture.Height = Twips2Pixels(rtf_picture.desired_height * rtf_picture.scaley / 100);
            }
            else
                picture.Height = Twips2Pixels(rtf_picture.height);

            return picture;
        }

        internal List<ComponentBase> Page2ReportObjects(FastReport.RichObject rich, RichDocument rtf, Page page, int start_text_index, out float page_height)
        {
            int object_counter = 0;
            page_height = 0;
            int empty_paragraph_height = 0;
            float object_vertical_position = rich.Padding.Top; // Twips2Pixels(page.margin_top); //
            List<ComponentBase> clone_list = new List<ComponentBase>();
            foreach (RichObject obj in page.sequence.objects)
            {
                TextObject left_label = null;
                if (rich.ActualTextStart != 0 && start_text_index + obj.size <= rich.ActualTextStart)
                {
                    start_text_index += (int) obj.size;
                    continue;
                }
                bool backindent = false;
                switch (obj.type)
                {
                    case RichObject.Type.Paragraph:
                        if (obj.pargraph.runs.Count == 0)
                        {
                            start_text_index++; // TODO: Check position increment size
                            ParagraphFormat format = obj.pargraph.format;

                            int lnspc;
                            int line_height = 17; // Not calculated yet

                            if (format.lnspcmult == ParagraphFormat.LnSpcMult.Multiply)
                                lnspc = (int)(format.line_spacing / 240f);
                            else
                                lnspc = Twips2Pixels(format.line_spacing);

                            empty_paragraph_height = lnspc < 0 ? -lnspc : lnspc >= line_height ? lnspc : line_height;
                            empty_paragraph_height += Twips2Pixels(format.space_before + format.space_after);

                            object_vertical_position += empty_paragraph_height;
                            TextObject empty_paragraph = Paragraph2ReportObjects(rich, rtf, start_text_index, obj.pargraph);
                            empty_paragraph.Top = object_vertical_position;
                            empty_paragraph.Height = empty_paragraph_height;
                            ++object_counter;
                            clone_list.Add(empty_paragraph);
                            continue;
                        }
                        empty_paragraph_height = 0;

                        TextObject list_label = null; ;
                        if (obj.pargraph.format.list_id != null && obj.pargraph.format.list_id.Count != 0)
                        {
                            ++object_counter;
                            list_label = new TextObject();
                            Run run = obj.pargraph.format.list_id[0];
                            if (run.text[0] == 183)
                                list_label.Text = "●";
                            else
                                list_label.Text = run.text;
                            list_label.Top = object_vertical_position;
                            list_label.HorzAlign = HorzAlign.Right;
                            list_label.VertAlign = VertAlign.Bottom;
                            list_label.Width = Twips2Pixels(obj.pargraph.format.left_indent);
                            list_label.Height = (float)Math.Ceiling(list_label.Font.Height * DrawUtils.ScreenDpiFX); // * 1.2f;
                            clone_list.Add(list_label);
                        }
                        TextObject text_paragraph = Paragraph2ReportObjects(rich, rtf, start_text_index, obj.pargraph);
                        if (list_label != null)
                        {
                            text_paragraph.Left = Twips2Pixels(obj.pargraph.format.left_indent);
                            list_label.Font = text_paragraph.Font;
                            list_label.VertAlign = text_paragraph.VertAlign;
                            list_label.LineHeight = text_paragraph.LineHeight;
                            text_paragraph.Width = rich.Width - text_paragraph.Left;
                        }
                        else
                        {
                            int ftb = text_paragraph.Text.IndexOf('\t');
                            backindent = ftb > 0
                                && obj.pargraph.format.first_line_indent < 0 &&
                                -obj.pargraph.format.first_line_indent <= obj.pargraph.format.left_indent ;

                            text_paragraph.Width = rich.Width;
                            if (backindent)
                            {
                                string left_text = text_paragraph.Text.Substring(0, ftb);

                                ++object_counter;
                                left_label = new TextObject();
                                left_label.Text = left_text;
                                left_label.Top = object_vertical_position;
                                left_label.HorzAlign = HorzAlign.Left;
                                left_label.VertAlign = text_paragraph.VertAlign;
                                if(obj.pargraph.format.left_indent != 0)
                                {
                                    left_label.Width = Twips2Pixels(obj.pargraph.format.left_indent);
                                }
                                else
                                {
                                    left_label.Width = Twips2Pixels(obj.pargraph.format.tab_positions[0]);
                                }
                                left_label.Height = (float)Math.Ceiling(left_label.Font.Height * DrawUtils.ScreenDpiFX); // * 1.2f;
                                left_label.TextRenderType = TextRenderType.HtmlParagraph;
                                clone_list.Add(left_label);

                                int charcount = text_paragraph.Text.Length - ftb - 1;
                                if(charcount > 0)
                                {
                                    text_paragraph.Text = text_paragraph.Text.Substring(ftb + 1, charcount);
                                }
                            }
                            else
                            {
                                text_paragraph.ParagraphFormat.FirstLineIndent = Twips2Pixels(obj.pargraph.format.first_line_indent);
                            }
                        }


                        ++object_counter;

                        Padding p = text_paragraph.Padding;
                        p.Top = Twips2Pixels((int)obj.pargraph.format.space_before);
                        p.Bottom = Twips2Pixels((int)obj.pargraph.format.space_after);
                        p.Right = Twips2Pixels((int)obj.pargraph.format.right_indent);
                        if(backindent)
                        {
                            p.Left = 0;
                            text_paragraph.Left += left_label.Right + 0.01f;
                            text_paragraph.Width -= left_label.Right + 0.01F;
                        }
                        else if (text_paragraph.HorzAlign != HorzAlign.Center)
                        {
                            p.Left += list_label != null ? 0 : Twips2Pixels((int)obj.pargraph.format.left_indent);
                        }
                        else
                        {
                            p.Left = Twips2Pixels((int)obj.pargraph.format.left_indent);
                        }   
                        text_paragraph.Padding = p;
                        text_paragraph.Top = object_vertical_position;
                        text_paragraph.PreserveLastLineSpace = true;
                        text_paragraph.Height = text_paragraph.CalcHeight() + p.Vertical;
                        if (backindent)
                        {
                            left_label.Height = text_paragraph.Height;
                            left_label.Padding = p;
                        }
                        object_vertical_position += text_paragraph.Height;
                        clone_list.Add(text_paragraph);
                        break;

                    case RichObject.Type.Picture:
                        {
                            PictureObject pict = Picture2ReportObject(rtf, obj.picture);

                            if (obj.picture.horizontalAlign == ParagraphFormat.HorizontalAlign.Centered)
                                pict.Left = (rich.Width / 2) - (pict.Width / 2);
                            else if (obj.picture.horizontalAlign == ParagraphFormat.HorizontalAlign.Right)
                                pict.Left = rich.Right - pict.Width;
                            else
                                pict.Left = rich.Left;

                            pict.Top = object_vertical_position;
                            object_vertical_position += pict.Height;
                            clone_list.Add(pict);
                        }
                        break;

                    case RichObject.Type.Table:
                        {
                            TableObject tbl = Table2ReportObjects(rich, rtf, obj.table);
                            tbl.Top = object_vertical_position;
                            object_vertical_position += tbl.Height;
                            clone_list.Add(tbl);
                        }
                        break;
                }

                start_text_index += (int)obj.size;
                if (rich.ActualTextLength != 0 && start_text_index >= rich.ActualTextStart + rich.ActualTextLength)
                    break;
            }

            int idx = 1;
            foreach (ComponentBase obj in clone_list)
            {
                obj.SetName(rich.Name + "_" + idx.ToString());
                idx++;
                obj.SetReport(rich.Report);
                page_height += obj.Height;
            }
            return clone_list;
        }

        private float AssingClones(FastReport.RichObject rich, List<ComponentBase> clone_list)
        {
            float bottom = rich.Bottom;

            foreach (ComponentBase clone in clone_list)
            {
                clone.SetReport(rich.Report);
                bottom = clone.Bottom;
            }
            return bottom + (usePadding ? rich.Padding.Top + rich.Padding.Bottom : 0);
        }

        internal List<ComponentBase> RichObject2ReportObjects(FastReport.RichObject rich, ref RichDocument rtf, out float total_height)
        {
            List<ComponentBase> clone_list = new List<ComponentBase>();

            int position = 0;
            float vertical_shift = 0;
            total_height = 0;
            if (rtf.pages != null)
                foreach (Page page in rtf.pages)
                {
                    if (position + page.size < rich.ActualTextStart)
                    {
                        position += (int)page.size;
                        continue;
                    }
                    float page_height;
                    List<ComponentBase> virtual_object_list = Page2ReportObjects(rich, rtf, page, position, out page_height);
                    foreach(ComponentBase obj in virtual_object_list)
                    {
                        if (obj is TextObject)
                        {
                            TextObject text_object = obj as TextObject;
                            text_object.Top += vertical_shift;
                            if (total_height < text_object.Bottom)
                                total_height = text_object.Bottom;

                            clone_list.Add(obj);
                        }
                        else if(obj is PictureObject)
                        {
                            PictureObject pic = obj as PictureObject;
                            pic.Top += vertical_shift;
                            total_height += pic.Height;
                            clone_list.Add(obj);
                        }
                        else if(obj is TableObject)
                        {
                            TableObject tbl = obj as TableObject;
                            tbl.Top += vertical_shift;
                            tbl.Left = 0; // Fix me
                            total_height += tbl.Height;
                            clone_list.Add(obj);
                        }
                        else
                        {
                            throw new Exception("Rich2ReportObject.cs: object type not supported");
                        }
                    }
                    position += (int)page.size;
                    vertical_shift = total_height;
                    if (rich.ActualTextLength != 0 && position >= rich.ActualTextStart + rich.ActualTextLength)
                        break;
                }

            total_height = AssingClones(rich, clone_list);
            return clone_list;
        }
    }

#if READONLY_STRUCTS
    internal readonly struct TranslationPropeties
#else
    internal struct TranslationPropeties
#endif
    {
        internal readonly Border border;
        internal readonly Color background_color;

        public TranslationPropeties(Border border, Color background_color)
        {
            this.border = border;
            this.background_color = background_color;
        }
    }
}
