using FastReport.Table; using FastReport.Utils; using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Linq; 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); } private static int GetSpaceBefore(ParagraphFormat pfmt) { return pfmt.disable_space_before ? 0 : Twips2Pixels(pfmt.space_before); } private static int GetSpaceAfter(ParagraphFormat pfmt) { return pfmt.disable_space_after ? 0 : Twips2Pixels(pfmt.space_after); } 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 = new RunFormat(); 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(""); current_format.underline = true; } if (format.bold) { sb.Append(""); current_format.bold = true; } if (format.italic) { sb.Append(""); current_format.italic = true; } if (format.strike) { sb.Append(""); current_format.strike = true; } if (format.script_type == RunFormat.ScriptType.Subscript) { sb.Append(""); } if (format.script_type == RunFormat.ScriptType.Superscript) { sb.Append(""); } // 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_condition = true; } } else { if (current_format.underline != format.underline && !format.underline) { sb.Append(""); current_format.underline = format.underline; } if (current_format.italic != format.italic && !format.italic) { sb.Append(""); current_format.italic = format.italic; } if (current_format.bold != format.bold && !format.bold) { sb.Append(""); current_format.bold = format.bold; } if (current_format.strike != format.strike && !format.strike) { sb.Append(""); current_format.strike = format.strike; } if (current_format.strike != format.strike && format.strike) { sb.Append(""); current_format.strike = format.strike; } if (current_format.bold != format.bold && format.bold) { sb.Append(""); current_format.bold = format.bold; } if (current_format.italic != format.italic && format.italic) { sb.Append(""); current_format.italic = format.italic; } if (current_format.underline != format.underline && format.underline) { sb.Append(""); current_format.underline = format.underline; } if (current_format.script_type != format.script_type) { if (current_format.script_type == RunFormat.ScriptType.Subscript) sb.Append(""); else if (current_format.script_type == RunFormat.ScriptType.Superscript) sb.Append(""); if (format.script_type == RunFormat.ScriptType.Subscript) sb.Append(""); else if (format.script_type == RunFormat.ScriptType.Superscript) sb.Append(""); 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 (format.BColor != Color.Empty && format.BColor != Color.White) backcolor = string.Format("background-color:#{0:X2}{1:X2}{2:X2}", format.BColor.R, format.BColor.G, format.BColor.B); else backcolor = string.Empty; 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(" 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 sb.Append("
"); position += len; if (rich.ActualTextLength != 0 && position >= rich.ActualTextStart + rich.ActualTextLength) break; if (span_condition) { sb.Append("
"); span_condition = false; } URL = String.Empty; run_num++; } // adding a closing tag to the end of the text if (current_format.script_type == RunFormat.ScriptType.Subscript) { sb.Append("
"); } else if (current_format.script_type == RunFormat.ScriptType.Superscript) { sb.Append(""); } if (current_format.underline) { sb.Append("
"); } if (current_format.italic) { sb.Append("
"); } if (current_format.bold) { sb.Append("
"); } if (current_format.strike) { sb.Append("
"); } clone.Text = sb.ToString(); clone.RightToLeft = paragraph.format.text_direction == ParagraphFormat.Direction.RighgToLeft; } 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 = Twips2Pixels((int) rtf.default_tab_width); clone.TabWidth = Twips2Pixels((int)rtf.default_tab_width); 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) { // It is necessary to reduce the first tab width by the value that will go to the left of the object, // since it will not be taken into account when splitting into HTMLParagrphRenderer paragraphs. // The remaining tab positions will be relative to the previous tab stop. clone.FirstTabOffset = Twips2Pixels(paragraph.format.tab_positions[0]) - Twips2Pixels(Math.Min(paragraph.format.left_indent + paragraph.format.first_line_indent, paragraph.format.left_indent)); 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((int)(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 = paragraph.format.text_direction == ParagraphFormat.Direction.LeftToRight ? HorzAlign.Right : HorzAlign.Left; break; case ParagraphFormat.HorizontalAlign.Centered: clone.HorzAlign = HorzAlign.Center; break; case ParagraphFormat.HorizontalAlign.Justified: clone.HorzAlign = HorzAlign.Justify; break; default: clone.HorzAlign = paragraph.format.text_direction == ParagraphFormat.Direction.LeftToRight ? HorzAlign.Left : HorzAlign.Right; 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; float 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((int)lineheight); break; case ParagraphFormat.LnSpcMult.Multiply: lineheight = (int)(lineheight / 240f); break; } lineheight = lineheight < 0 ? -lineheight : lineheight >= clone.Font.Height * DrawUtils.ScreenDpiFX ? lineheight : clone.Font.Height * DrawUtils.ScreenDpiFX; 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 row_properties = new List(); 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.paragraph); // TODO: Fix "pos" argument text_paragraph.Width = rtf_column.Width; Padding p = text_paragraph.Padding; p.Top = GetSpaceBefore(obj.paragraph.format); p.Bottom = GetSpaceAfter(obj.paragraph.format); p.Left = Twips2Pixels((int)obj.paragraph.format.left_indent); p.Right = Twips2Pixels((int)obj.paragraph.format.right_indent); if (p.Left == 0) p.Left = 3; text_paragraph.Padding = p; if (obj.paragraph.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 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 clone_list = new List(); 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; Padding p = new Padding(); switch (obj.type) { case RichObject.Type.Paragraph: p.Top = GetSpaceBefore(obj.paragraph.format); p.Bottom = GetSpaceAfter(obj.paragraph.format); p.Right = Twips2Pixels((int)obj.paragraph.format.right_indent); p.Left = 0; // Twips2Pixels((int)obj.paragraph.format.left_indent); if (obj.paragraph.runs.Count == 0) { start_text_index++; // TODO: Check position increment size ParagraphFormat format = obj.paragraph.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 += GetSpaceBefore(format) + GetSpaceAfter(format); object_vertical_position += empty_paragraph_height; TextObject empty_paragraph = Paragraph2ReportObjects(rich, rtf, start_text_index, obj.paragraph); empty_paragraph.Top = object_vertical_position; empty_paragraph.Height = empty_paragraph_height; empty_paragraph.CanShrink = false; ++object_counter; clone_list.Add(empty_paragraph); continue; } empty_paragraph_height = 0; TextObject list_label = null; if (obj.paragraph.format.list_id != null && obj.paragraph.format.list_id.Count != 0) { ++object_counter; list_label = new TextObject(); Run run = obj.paragraph.format.list_id[0]; #if ! DONT_SUBSTITUTE_INTERPUNCT if (run.text[0] == 183) list_label.Text = "•"; // "●"; else #endif list_label.Text = run.text; clone_list.Add(list_label); } TextObject text_paragraph = Paragraph2ReportObjects(rich, rtf, start_text_index, obj.paragraph); if (list_label != null) { list_label.Font = text_paragraph.Font; list_label.VertAlign = text_paragraph.VertAlign; list_label.LineHeight = text_paragraph.LineHeight; list_label.HorzAlign = HorzAlign.Left; list_label.FillColor = rich.FillColor; list_label.SetReport(rich.Report); list_label.Width = Twips2Pixels(obj.paragraph.format.left_indent); list_label.RightToLeft = text_paragraph.RightToLeft; if (!text_paragraph.RightToLeft) { list_label.Left = rich.Left + Twips2Pixels(obj.paragraph.format.left_indent + obj.paragraph.format.first_line_indent) * DrawUtils.ScreenDpiFX; text_paragraph.Width = rich.Right - text_paragraph.Left; text_paragraph.Left = list_label.Right; } else { text_paragraph.Width = rich.Right - list_label.Width; p.Left = p.Right = 0; list_label.Left = text_paragraph.Right; } } else { text_paragraph.Left = rich.Left; int ftb = text_paragraph.Text.IndexOf('\t'); backindent = ftb >= 0 && obj.paragraph.format.first_line_indent < 0 && -obj.paragraph.format.first_line_indent <= obj.paragraph.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.FillColor = rich.FillColor; left_label.Top = object_vertical_position; left_label.HorzAlign = HorzAlign.Left; left_label.VertAlign = text_paragraph.VertAlign; if (obj.paragraph.format.left_indent != 0) { left_label.Width = Twips2Pixels(obj.paragraph.format.left_indent); } else { left_label.Width = Twips2Pixels(obj.paragraph.format.tab_positions[0]); } left_label.Left = text_paragraph.RightToLeft ? rich.Right - left_label.Width : rich.Left; 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.paragraph.format.first_line_indent); } } ++object_counter; if (backindent) { if(text_paragraph.RightToLeft) { text_paragraph.Width -= (left_label.Width + 0.01f); left_label.HorzAlign = HorzAlign.Center; } else { 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 : (int)(Twips2Pixels(Math.Min(obj.paragraph.format.left_indent + obj.paragraph.format.first_line_indent, obj.paragraph.format.left_indent))); } else { p.Left = (int)(Twips2Pixels(obj.paragraph.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 = new Padding(0, p.Top, 0, p.Bottom); } if(list_label != null) { list_label.Height = text_paragraph.Height; list_label.Padding = new Padding(0, p.Top, 0, p.Bottom); list_label.Top = text_paragraph.Top; } 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.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.Left = rich.Left; 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 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 RichObject2ReportObjects(FastReport.RichObject rich, ref RichDocument rtf, out float total_height) { List clone_list = new List(); int position = 0; float vertical_shift = 0; total_height = 0; if (rtf.pages != null) { bool page_break = false; foreach (Page page in rtf.pages) { if (position + page.size < rich.ActualTextStart) { position += (int)page.size; continue; } float page_height; List virtual_object_list = Page2ReportObjects(rich, rtf, page, position, out page_height); foreach (ComponentBase obj in virtual_object_list) { if (page_break) { BreakableComponent breaking = obj as BreakableComponent; if (breaking != null) breaking.PageBreak = true; page_break = false; } 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; 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; page_break = true; 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; } } }