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(""); 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 (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 (format.script_type == RunFormat.ScriptType.Subscript) sb.Append(""); else if (format.script_type == RunFormat.ScriptType.Superscript) sb.Append(""); else if (current_format.script_type == RunFormat.ScriptType.Subscript) sb.Append(""); else if (current_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 (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(" 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("
"); 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++; } 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 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.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 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; 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 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) 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 (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; } } }