using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text.RegularExpressions; using System.Windows.Forms; using FastReport; using FastReport.Table; using FastReport.Utils; using InABox.Core; using InABox.Scripting; using InABox.Wpf.Reports; using InABox.Wpf.Reports.CustomObjects; using UnderlineType = InABox.Core.UnderlineType; namespace InABox.DynamicGrid { public static class DigitalFormUtils { #region Layout Importer private class Cell { public string Content { get; set; } public int Row { get; set; } public int Column { get; set; } public int RowSpan { get; set; } = 1; public int ColumnSpan { get; set; } = 1; public ICell InnerCell { get; set; } public Cell(int row, int column, string content, ICell cell) { Row = row; Column = column; Content = content; InnerCell = cell; } } private static void DeleteColumn(List cells, int column) { foreach(var cell in cells) { if(cell.Column <= column && cell.Column + cell.ColumnSpan - 1 >= column) { --cell.ColumnSpan; } else if(cell.Column > column) { --cell.Column; } } cells.RemoveAll(x => x.ColumnSpan < 0); } private static List GetCells(ISheet sheet) { var grid = new Dictionary>(); for (int rowIdx = sheet.FirstRow; rowIdx <= sheet.LastRow; ++rowIdx) { var row = sheet.GetRow(rowIdx); if (row is not null && row.FirstColumn >= 0) { var rowCells = new Dictionary(); for (int colIdx = row.FirstColumn; colIdx <= row.LastColumn; ++colIdx) { var cell = row.GetCell(colIdx); if (cell is not null) { rowCells.Add(colIdx, new Cell(rowIdx, colIdx, cell.GetValue(), cell)); } } grid.Add(rowIdx, rowCells); } } foreach (var region in sheet.GetMergedCells()) { for (int r = region.FirstRow; r <= region.LastRow; ++r) { if (!grid.TryGetValue(r, out var row)) continue; for (int c = region.FirstColumn; c <= region.LastColumn; ++c) { if ((r - region.FirstRow) + (c - region.FirstColumn) != 0) { row.Remove(c); } } if (row.Count == 0) { grid.Remove(r); } } if (grid.TryGetValue(region.FirstRow, out var cRow) && cRow.TryGetValue(region.FirstColumn, out var cCell)) { cCell.RowSpan = region.LastRow - region.FirstRow + 1; cCell.ColumnSpan = region.LastColumn - region.FirstColumn + 1; } } var cells = new List(); foreach (var row in grid.Values) { foreach (var cell in row.Values) { cells.Add(cell); } } return cells; } private static Regex VariableRegex = new(@"^\[(?[^:\]]+)(?::(?[^:\]]*))?(?::(?[^\]]*))?\]$"); private static Regex HeaderRegex = new(@"^{(?
[^:}]+)(?::(?[^}]*))?}$"); public static DFLayout LoadLayout(ISpreadsheet spreadsheet) { var sheet = spreadsheet.GetSheet(0); var cells = GetCells(sheet); int firstRow = int.MaxValue; int lastRow = 0; int firstCol = int.MaxValue; int lastCol = 0; foreach (var cell in cells) { firstCol = Math.Min(cell.Column, firstCol); lastCol = Math.Max(cell.Column + cell.ColumnSpan - 1, lastCol); firstRow = Math.Min(cell.Row, firstRow); lastRow = Math.Max(cell.Row + cell.RowSpan - 1, lastRow); } var layout = new DFLayout(); var columnWidths = new Dictionary(); var colOffset = 0; for (int col = firstCol; col <= lastCol; ++col) { var width = sheet.GetColumnWidth(col); if(width == float.MinValue) { layout.ColumnWidths.Add("10*"); } else if(width <= 0f) { DeleteColumn(cells, col); } else { layout.ColumnWidths.Add($"{width}*"); } } for (int row = firstRow; row <= lastRow; ++row) layout.RowHeights.Add("Auto"); foreach(var cell in cells) { var style = cell.InnerCell.GetStyle(); if (string.IsNullOrWhiteSpace(cell.Content) && style.Foreground == Color.Empty) continue; DFLayoutControl? control; var content = cell.Content?.Trim() ?? ""; var headermatch = HeaderRegex.Match(content); var variablematch = VariableRegex.Match(content); if (headermatch.Success) { var text = headermatch.Groups["HEADER"]; var collapsed = headermatch.Groups["COLLAPSED"]; var header = new DFLayoutHeader() { Header = text.Value, Collapsed = collapsed.Success && String.Equals(collapsed.Value.ToUpper(),"COLLAPSED"), Style = CreateStyle(style) }; control = header; } else if (variablematch.Success) { var variableName = variablematch.Groups["VAR"]; var variableType = variablematch.Groups["TYPE"]; var variableProps = variablematch.Groups["PROPERTIES"]; Type? fieldType = null; if (variableType.Success) fieldType = DFUtils.GetFieldType(variableType.Value); fieldType ??= typeof(DFLayoutStringField); var field = (Activator.CreateInstance(fieldType) as DFLayoutField)!; field.Name = variableName.Value; if (variableProps.Success) { if (field is DFLayoutOptionField option) option.Properties.Options = variableProps.Value; if (field is DFLayoutStringField text) text.Properties.TextWrapping = style.WrapText; // need to populate other variable types here } control = field; } else { control = new DFLayoutLabel { Caption = content, Style = CreateStyle(style) }; } if(control is not null) { control.Row = cell.Row - firstRow + 1; control.Column = cell.Column - firstCol + 1 - colOffset; control.RowSpan = cell.RowSpan; control.ColumnSpan = cell.ColumnSpan; layout.Elements.Add(control); } } return layout; } private static DFLayoutTextStyle CreateStyle(ICellStyle style) { if (style == null) return new DFLayoutTextStyle(); var result = new DFLayoutTextStyle { FontSize = style.Font.FontSize, IsItalic = style.Font.Italic, IsBold = style.Font.Bold, Underline = style.Font.Underline switch { Scripting.UnderlineType.None => UnderlineType.None, Scripting.UnderlineType.Single or Scripting.UnderlineType.SingleAccounting => UnderlineType.Single, Scripting.UnderlineType.Double or Scripting.UnderlineType.DoubleAccounting => UnderlineType.Double, _ => UnderlineType.None }, BackgroundColour = style.Background, ForegroundColour = style.Font.Colour, HorizontalTextAlignment = style.HorizontalAlignment switch { CellAlignment.Middle => DFLayoutAlignment.Middle, CellAlignment.End => DFLayoutAlignment.End, CellAlignment.Justify => DFLayoutAlignment.Stretch, _ => DFLayoutAlignment.Start }, VerticalTextAlignment = style.VerticalAlignment switch { CellAlignment.Start => DFLayoutAlignment.Start, CellAlignment.End => DFLayoutAlignment.End, CellAlignment.Justify => DFLayoutAlignment.Stretch, _ => DFLayoutAlignment.Middle }, TextWrapping = style.WrapText }; return result; } #endregion #region Report Generator private static string ElementName(HashSet names, string name) { int i = 0; if(names.Contains(name)) { string newName; do { newName = $"{name}{i}"; ++i; } while (names.Contains(newName)); name = newName; } return name; } public static Report? GenerateReport(DigitalFormLayout layout, DataModel model) { bool IsValidChar(char c) => "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789".Contains(c); var report = ReportUtils.SetupReport(null, model, true); var dfLayout = new DFLayout(); dfLayout.LoadLayout(layout.Layout); var page = new ReportPage(); page.Name = "Page1"; page.PaperWidth = 210; page.PaperHeight = 297; page.Landscape = false; page.LeftMargin = 10; page.TopMargin = 10; page.RightMargin = 10; page.BottomMargin = 10; report.Pages.Add(page); var formData = report.GetDataSource("Form_Data"); var band = new DataBand { Name = "Data1", Height = Units.Millimeters * (page.PaperHeight - (page.TopMargin + page.BottomMargin)), Width = Units.Millimeters * (page.PaperWidth - (page.LeftMargin + page.RightMargin)), PrintIfDatasourceEmpty = true, DataSource = formData, StartNewPage = true }; page.AddChild(band); var logo = new PictureObject() { Height = 20F * Units.Millimeters, Width = 30F * Units.Millimeters, DataColumn = "CompanyLogo.Data" }; band.AddChild(logo); var company = new TextObject() { Left = band.Width - (90F * Units.Millimeters), Width = 90F * Units.Millimeters, Height = 5F * Units.Millimeters, Text = "[CompanyInformation.CompanyName]", HorzAlign = HorzAlign.Right, VertAlign = VertAlign.Center, Font = new System.Drawing.Font("Arial", 12F, FontStyle.Bold) }; band.AddChild(company); var address = new TextObject() { Left = band.Width - (90F * Units.Millimeters), Width = 90F * Units.Millimeters, Height = 15F * Units.Millimeters, Top = 5F * Units.Millimeters, Text = "[CompanyInformation.PostalAddress_Street]\n[CompanyInformation.PostalAddress_City] [CompanyInformation.PostalAddress_PostCode]", HorzAlign = HorzAlign.Right, VertAlign = VertAlign.Top, Font = new System.Drawing.Font("Arial", 12F) }; band.AddChild(address); var instancetable = model .GetType() .GetInterfaces() .FirstOrDefault(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IDataModel<>))? .GetGenericArguments() .FirstOrDefault()? .EntityName() .Split('.') .Last(); var title = new TextObject() { Top = 23F * Units.Millimeters, Width = band.Width, Height = 6F * Units.Millimeters, Text = $"[Form_Data.{instancetable}.Number] - {layout.Form.Description}", HorzAlign = HorzAlign.Center, VertAlign = VertAlign.Center, Font = new System.Drawing.Font("Arial", 14F, FontStyle.Bold) }; band.AddChild(title); /* */ var elementNames = new HashSet(); var table = new TableObject() { Name = "FormTable", ColumnCount = dfLayout.ColumnWidths.Count, RowCount = dfLayout.RowHeights.Count, Top = 32F * Units.Millimeters }; band.AddChild(table); foreach(var element in dfLayout.Elements) { if (element.Row < 1 || element.Row + element.RowSpan - 1 > table.RowCount || element.Column < 1 || element.Column + element.ColumnSpan - 1 > table.ColumnCount) continue; var row = table.Rows[element.Row - 1]; if(row.ChildObjects[element.Column - 1] is TableCell cell) { cell.Border.Lines = BorderLines.All; cell.ColSpan = element.ColumnSpan; cell.RowSpan = element.RowSpan; if (element is DFLayoutField field) { var manualHeight = 0.0f; cell.Name = ElementName(elementNames, $"Cell_{new string(field.Name.Where(c => IsValidChar(c)).ToArray())}"); var dataColumn = $"Form_Data.{field.Name}"; if(field is DFLayoutEmbeddedImage || field is DFLayoutSignaturePad) { var picture = new PictureObject { DataColumn = dataColumn, //Dock = DockStyle.Fill, //Padding = new Padding(5,5,5,5) }; //cell.Padding = new Padding(5); cell.AddChild(picture); manualHeight = 40; } else if(field is DFLayoutMultiImage) { var image = new MultiImageObject { DataColumn = dataColumn, //Dock = DockStyle.Fill, //Padding = new Padding(5,5,5,5) }; cell.AddChild(image); manualHeight = 40; } else if(field is DFLayoutMultiSignaturePad) { var image = new MultiSignatureObject { DataColumn = dataColumn, //Dock = DockStyle.Fill, //Padding = new Padding(5,5,5,5) }; cell.AddChild(image); manualHeight = 40; } else { cell.Text = $"[{dataColumn}]"; cell.Font = new System.Drawing.Font(cell.Font.FontFamily, 10F, FontStyle.Italic); cell.TextColor = Color.Navy; cell.HorzAlign = HorzAlign.Left; cell.VertAlign = VertAlign.Center; if (field is DFLayoutStringField lsf) cell.WordWrap = lsf.Properties.TextWrapping; } if(manualHeight > 0 && dfLayout.RowHeights[element.Row - 1] == "Auto") { dfLayout.RowHeights[element.Row - 1] = manualHeight.ToString(); } } else if (element is DFLayoutLabel label) { var background = label.Style.BackgroundColour; if (background == Color.Empty) { label.Style.BackgroundColour = Color.WhiteSmoke; } cell.Text = label.Description; cell.Name = ElementName(elementNames, "Label_" + element.Row + "_" + element.Column); ApplyStyle(cell, label.Style); } else if (element is DFLayoutHeader header) { cell.Name = ElementName(elementNames, "Header_" + element.Row + "_" + element.Column); cell.Text = header.Header; ApplyStyle(cell, header.Style); } } } ProcessColumnWidths(band.Width, dfLayout.ColumnWidths, table.Columns); ProcessRowHeights(band.Height, dfLayout.RowHeights, table.Rows); return report; } private static void ApplyStyle(TableCell cell, DFLayoutTextStyle style) { var background = style.BackgroundColour; if(background != Color.Empty) cell.FillColor = background; var foreground = style.ForegroundColour; if (foreground != Color.Empty) cell.TextColor = foreground; FontStyle fontstyle = System.Drawing.FontStyle.Regular; if (style.IsBold) fontstyle |= System.Drawing.FontStyle.Bold; if (style.IsItalic) fontstyle |= System.Drawing.FontStyle.Italic; if (style.Underline != UnderlineType.None) fontstyle |= FontStyle.Underline; float fontsize = (float)style.FontSize; fontsize = fontsize == 0F ? 10F : fontsize; cell.Font = new System.Drawing.Font(cell.Font.FontFamily, fontsize, fontstyle); cell.HorzAlign = style.HorizontalTextAlignment switch { DFLayoutAlignment.Start => HorzAlign.Left, DFLayoutAlignment.Middle => HorzAlign.Center, DFLayoutAlignment.End => HorzAlign.Right, DFLayoutAlignment.Stretch => HorzAlign.Justify, _ => HorzAlign.Left }; cell.VertAlign = style.VerticalTextAlignment switch { DFLayoutAlignment.Start => VertAlign.Top, DFLayoutAlignment.Middle => VertAlign.Center, DFLayoutAlignment.End => VertAlign.Bottom, DFLayoutAlignment.Stretch => VertAlign.Center, _ => VertAlign.Center }; cell.WordWrap = style.TextWrapping; } private static void ProcessRowHeights(float bandheight, List values, TableRowCollection rows) { float fixedtotal = 0F; for (int iFixed = 0; iFixed < values.Count; iFixed++) { if (!values[iFixed].Contains("*")) { if (!float.TryParse(values[iFixed], out float value)) value = 25F / Units.Millimeters; else value = value / (1.5f * Units.Millimeters); rows[iFixed].Height = Units.Millimeters * value; fixedtotal += Units.Millimeters * value; } } Dictionary starvalues = new Dictionary(); float startotal = 0F; for (int iStar = 0; iStar < values.Count; iStar++) { if (values[iStar].Contains('*')) { if (!float.TryParse(values[iStar].Replace("*", ""), out float value)) value += 1; starvalues[iStar] = value; startotal += value; } } foreach (var key in starvalues.Keys) rows[key].Height = (starvalues[key] / startotal) * (bandheight - fixedtotal); } private static void ProcessColumnWidths(float bandwidth, List values, TableColumnCollection columns) { float fixedtotal = 0F; for (int iFixed = 0; iFixed < values.Count; iFixed++) { if (!values[iFixed].Contains("*")) { if (!float.TryParse(values[iFixed], out float value)) value = 40F; columns[iFixed].Width = Units.Millimeters * value; fixedtotal += Units.Millimeters * value; } } Dictionary starvalues = new Dictionary(); float startotal = 0F; for (int iStar = 0; iStar < values.Count; iStar++) { if (values[iStar].Contains('*')) { if (!float.TryParse(values[iStar].Replace("*", ""), out float value)) value += 1; starvalues[iStar] = value; startotal += value; } } foreach (var key in starvalues.Keys) columns[key].Width = (starvalues[key] / startotal) * (bandwidth - fixedtotal); } #endregion #region Data Model private static List? _entityForms; public static DataModel? GetDataModel(String appliesto, IEnumerable variables) { _entityForms ??= CoreUtils.Entities .Where(x => x.IsSubclassOfRawGeneric(typeof(EntityForm<,,>))) .ToList(); var entityForm = _entityForms .FirstOrDefault(x => x.GetSuperclassDefinition(typeof(EntityForm<,,>)) ?.GenericTypeArguments[0].Name == appliesto); if(entityForm is not null) { var model = (Activator.CreateInstance(typeof(DigitalFormReportDataModel<>).MakeGenericType(entityForm), Filter.Create(entityForm).None(), null) as DataModel)!; (model as IDigitalFormReportDataModel)!.Variables = variables.ToArray(); return model; } return null; } #endregion } }