diff --git a/Disco.BI/BI/Extensions/DocumentTemplateExtensions.cs b/Disco.BI/BI/Extensions/DocumentTemplateExtensions.cs index 53740fd9..a393f785 100644 --- a/Disco.BI/BI/Extensions/DocumentTemplateExtensions.cs +++ b/Disco.BI/BI/Extensions/DocumentTemplateExtensions.cs @@ -10,6 +10,7 @@ using iTextSharp.text.pdf; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Drawing; using System.IO; using System.Linq; @@ -31,8 +32,21 @@ namespace Disco.BI.Extensions int pdfFieldOrdinal = 0; foreach (string pdfFieldKey in pdfReader.AcroFields.Fields.Keys) { + var pdfField = pdfReader.AcroFields.Fields[pdfFieldKey]; + var pdfFieldPositions = pdfReader.AcroFields.GetFieldPositions(pdfFieldKey); + var pdfFieldFlags = pdfField.GetMerged(0).GetAsNumber(PdfName.FF)?.IntValue ?? 0; + var isRequired = (pdfFieldFlags & 2) == 2; + var isReadOnly = (pdfFieldFlags & 1) == 1; + var pdfFieldValue = pdfReader.AcroFields.GetField(pdfFieldKey); - ExpressionCache.SetValue(cacheModuleKey, pdfFieldKey, Expression.Tokenize(pdfFieldKey, pdfFieldValue, pdfFieldOrdinal)); + var pdfFieldPosition = default(RectangleF?); + if (pdfFieldPositions != null && pdfFieldPositions.Count > 0) + { + var position = pdfFieldPositions.First().position; + pdfFieldPosition = new RectangleF(position.Left, position.Top, position.Width, position.Height); + } + + ExpressionCache.SetValue(cacheModuleKey, pdfFieldKey, Expression.Tokenize(pdfFieldKey, pdfFieldValue, pdfFieldOrdinal, isRequired, isReadOnly, pdfFieldPosition)); pdfFieldOrdinal++; } pdfReader.Close(); diff --git a/Disco.BI/BI/Interop/Pdf/PdfGenerator.cs b/Disco.BI/BI/Interop/Pdf/PdfGenerator.cs index 6a87347d..e6db591c 100644 --- a/Disco.BI/BI/Interop/Pdf/PdfGenerator.cs +++ b/Disco.BI/BI/Interop/Pdf/PdfGenerator.cs @@ -290,6 +290,7 @@ namespace Disco.BI.Interop.Pdf AcroFields.FieldPosition pdfFieldPosition = pdfFieldPositions[pdfFieldOrdinal]; iTextSharp.text.Image pdfImage = iTextSharp.text.Image.GetInstance(imageResult.GetImage((int)pdfFieldPosition.position.Width, (int)pdfFieldPosition.position.Height)); pdfImage.SetAbsolutePosition(pdfFieldPosition.position.Left, pdfFieldPosition.position.Bottom); + pdfImage.ScaleToFit(pdfFieldPosition.position.Width, pdfFieldPosition.position.Height); pdfStamper.GetOverContent(pdfFieldPosition.page).AddImage(pdfImage); } if (!fieldExpressionResult.Item2 && !imageResult.ShowField) diff --git a/Disco.Models/BI/Expressions/IImageExpressionResult.cs b/Disco.Models/BI/Expressions/IImageExpressionResult.cs index f36857c9..2fbc6a8e 100644 --- a/Disco.Models/BI/Expressions/IImageExpressionResult.cs +++ b/Disco.Models/BI/Expressions/IImageExpressionResult.cs @@ -5,6 +5,7 @@ namespace Disco.Models.BI.Expressions public interface IImageExpressionResult { Stream GetImage(int Width, int Height); + Stream GetImage(); byte Quality { get; set; } bool LosslessFormat { get; set; } bool ShowField { get; set; } diff --git a/Disco.Services/Expressions/Expression.cs b/Disco.Services/Expressions/Expression.cs index 46d5abb4..95e4371b 100644 --- a/Disco.Services/Expressions/Expression.cs +++ b/Disco.Services/Expressions/Expression.cs @@ -8,6 +8,7 @@ using Spring.Expressions; using System; using System.Collections; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Text; @@ -15,16 +16,24 @@ namespace Disco.Services.Expressions { public sealed class Expression : List { - public string Name { get; private set; } - public string Source { get; private set; } + public string Name { get; } + public string Source { get; } public bool IsDynamic { get; private set; } - public int Ordinal { get; private set; } + public int Ordinal { get; } - private Expression(string Name, string Source, int Ordinal) + public bool IsRequired { get; } + public bool IsReadOnly { get; } + + public RectangleF? Position { get; } + + private Expression(string name, string source, int ordinal, bool isRequired, bool isReadOnly, RectangleF? position) { - this.Name = Name; - this.Source = Source; - this.Ordinal = Ordinal; + Name = name; + Source = source; + Ordinal = ordinal; + IsRequired = isRequired; + IsReadOnly = isReadOnly; + Position = position; } public static void InitializeExpressions() @@ -71,6 +80,19 @@ namespace Disco.Services.Expressions public Tuple Evaluate(object ExpressionContext, IDictionary Variables) { + if (Count == 0) + return new Tuple(string.Empty, false, null); + + if (!IsDynamic) + { + if (Count != 1) + throw new InvalidOperationException("Non-dynamic expressions should only have one part"); + if (this[0] is TextExpressionPart textPart) + return new Tuple(textPart.RawSource, false, null); + else + throw new InvalidOperationException("Non-dynamic expressions should have a single TextExpressionPart component"); + } + var resultValue = new StringBuilder(); object resultObject = null; bool resultError = false; @@ -103,25 +125,28 @@ namespace Disco.Services.Expressions } public static Expression TokenizeSingleDynamic(string Name, string ExpressionSource, int Ordinal) { - var e = new Expression(Name, ExpressionSource, Ordinal); + var e = new Expression(Name, ExpressionSource, Ordinal, isRequired: false, isReadOnly: false, position: null); if (ExpressionSource != null && !string.IsNullOrWhiteSpace(ExpressionSource)) e.Add(new EvaluateExpressionPart(ExpressionSource)); e.IsDynamic = true; return e; } - public static Expression Tokenize(string Name, string ExpressionSource, int Ordinal) + public static Expression Tokenize(string Name, string ExpressionSource, int Ordinal, bool IsRequired, bool IsReadOnly) + => Tokenize(Name, ExpressionSource, Ordinal, IsRequired, IsReadOnly, null); + + public static Expression Tokenize(string name, string expressionSource, int ordinal, bool isRequired, bool isReadOnly, RectangleF? position) { - var e = new Expression(Name, ExpressionSource, Ordinal); - if (!ExpressionSource.Contains("{") || !ExpressionSource.Contains("}")) + var e = new Expression(name, expressionSource, ordinal, isRequired, isReadOnly, position); + if (!expressionSource.Contains("{") || !expressionSource.Contains("}")) { - e.Add(new TextExpressionPart(ExpressionSource)); + e.Add(new TextExpressionPart(expressionSource)); } else { var token = new StringBuilder(); bool tokenEval = false; int tokenEvalDepth = 0; - foreach (char c in ExpressionSource) + foreach (char c in expressionSource) { switch (c) { diff --git a/Disco.Services/Expressions/Extensions/ImageResultImplementations/BaseImageExpressionResult.cs b/Disco.Services/Expressions/Extensions/ImageResultImplementations/BaseImageExpressionResult.cs index 0aeb6587..0d35aa17 100644 --- a/Disco.Services/Expressions/Extensions/ImageResultImplementations/BaseImageExpressionResult.cs +++ b/Disco.Services/Expressions/Extensions/ImageResultImplementations/BaseImageExpressionResult.cs @@ -22,6 +22,7 @@ namespace Disco.Services.Expressions.Extensions.ImageResultImplementations } public abstract Stream GetImage(int Width, int Height); + public abstract Stream GetImage(); protected Stream RenderImage(Image SourceImage, int Width, int Height) { @@ -56,7 +57,7 @@ namespace Disco.Services.Expressions.Extensions.ImageResultImplementations } else { // Lossy Format - JPG - byte quality = Math.Min((byte)100, Math.Max((byte)1, Quality)); + var quality = Math.Min(100, Math.Max(1, (int)Quality)); SourceImage.SaveJpg(quality, imageStream); } imageStream.Position = 0; diff --git a/Disco.Services/Expressions/Extensions/ImageResultImplementations/BitmapImageExpressionResult.cs b/Disco.Services/Expressions/Extensions/ImageResultImplementations/BitmapImageExpressionResult.cs index 31509d3d..d82ec9ee 100644 --- a/Disco.Services/Expressions/Extensions/ImageResultImplementations/BitmapImageExpressionResult.cs +++ b/Disco.Services/Expressions/Extensions/ImageResultImplementations/BitmapImageExpressionResult.cs @@ -20,5 +20,10 @@ namespace Disco.Services.Expressions.Extensions.ImageResultImplementations { return RenderImage(Image, Width, Height); } + + public override Stream GetImage() + { + return OutputImage(Image); + } } } diff --git a/Disco.Services/Expressions/Extensions/ImageResultImplementations/FileImageExpressionResult.cs b/Disco.Services/Expressions/Extensions/ImageResultImplementations/FileImageExpressionResult.cs index 9da1fa85..42eef4d6 100644 --- a/Disco.Services/Expressions/Extensions/ImageResultImplementations/FileImageExpressionResult.cs +++ b/Disco.Services/Expressions/Extensions/ImageResultImplementations/FileImageExpressionResult.cs @@ -25,5 +25,14 @@ namespace Disco.Services.Expressions.Extensions.ImageResultImplementations return RenderImage(SourceImage, Width, Height); } } + + public override Stream GetImage() + { + var stream = new MemoryStream(); + using (var fileStream = File.OpenRead(AbsoluteFilePath)) + fileStream.CopyTo(stream); + stream.Position = 0; + return stream; + } } } diff --git a/Disco.Services/Expressions/Extensions/ImageResultImplementations/FileMontageImageExpressionResult.cs b/Disco.Services/Expressions/Extensions/ImageResultImplementations/FileMontageImageExpressionResult.cs index 06849772..c47e53a0 100644 --- a/Disco.Services/Expressions/Extensions/ImageResultImplementations/FileMontageImageExpressionResult.cs +++ b/Disco.Services/Expressions/Extensions/ImageResultImplementations/FileMontageImageExpressionResult.cs @@ -29,15 +29,50 @@ namespace Disco.Services.Expressions.Extensions.ImageResultImplementations public override Stream GetImage(int Width, int Height) { - List Images = new List(); + return DoLayout(Width, Height); + } + + public override Stream GetImage() + { + return DoLayout(width: null, height: null); + } + + private Stream DoLayout(int? width, int? height) + { + List images = new List(); try { // Load Images foreach (string imageFilePath in AbsoluteFilePaths) - Images.Add(Bitmap.FromFile(imageFilePath)); + images.Add(Image.FromFile(imageFilePath)); + + if (!width.HasValue || !height.HasValue) + { + int maxWidth, maxHeight; + if (MontageHorizontalLayout) + { + maxWidth = images.Sum(i => i.Width); + maxHeight = images.Max(i => i.Height); + }else if (MontageVerticalLayout) + { + maxWidth = images.Max(i => i.Width); + maxHeight = images.Sum(i => i.Height); + } + else // table layout + { + var itemAverageSize = new SizeF(images.Average(i => (float)i.Size.Width), images.Average(i => (float)i.Size.Height)); + var stageSize = new Size((int)(itemAverageSize.Width / 2f * (images.Count + 1)), (int)(itemAverageSize.Height / 2f * (images.Count + 1))); + + var calculatedLayout = CalculateColumnCount(stageSize, itemAverageSize, images.Count); + maxWidth = (int)(calculatedLayout.Item1 * itemAverageSize.Width); + maxHeight = (int)(calculatedLayout.Item2 * itemAverageSize.Height); + } + width = width ?? maxWidth; + height = height ?? maxHeight; + } // Build Montage - using (Bitmap montageImage = new Bitmap(Width, Height)) + using (Bitmap montageImage = new Bitmap(width.Value, height.Value)) { using (Graphics montageGraphics = Graphics.FromImage(montageImage)) { @@ -55,14 +90,13 @@ namespace Disco.Services.Expressions.Extensions.ImageResultImplementations } if (MontageHorizontalLayout) - DoHorizontalLayout(Images, montageGraphics); + DoHorizontalLayout(images, montageGraphics); else if (MontageVerticalLayout) - DoVirticalLayout(Images, montageGraphics); - else - DoTableLayout(Images, montageGraphics); + DoVirticalLayout(images, montageGraphics); + else + DoTableLayout(images, montageGraphics); } - return OutputImage(montageImage); } } @@ -70,8 +104,8 @@ namespace Disco.Services.Expressions.Extensions.ImageResultImplementations finally { // Dispose of any Images - if (Images != null) - foreach (Image i in Images) + if (images != null) + foreach (Image i in images) i.Dispose(); } } @@ -175,5 +209,7 @@ namespace Disco.Services.Expressions.Extensions.ImageResultImplementations return new Tuple(bestColumnCount, bestRowCount, bestItemRatio); } + + } } diff --git a/Disco.Services/Expressions/TextExpressionPart.cs b/Disco.Services/Expressions/TextExpressionPart.cs index b114cae1..3404b9af 100644 --- a/Disco.Services/Expressions/TextExpressionPart.cs +++ b/Disco.Services/Expressions/TextExpressionPart.cs @@ -4,7 +4,7 @@ namespace Disco.Services.Expressions { private string _Source; - bool IExpressionPart.ErrorsAllowed + public bool ErrorsAllowed { get { @@ -15,7 +15,7 @@ namespace Disco.Services.Expressions return; } } - string IExpressionPart.Source + public string Source { get { @@ -26,7 +26,7 @@ namespace Disco.Services.Expressions return; } } - string IExpressionPart.RawSource + public string RawSource { get { @@ -37,7 +37,7 @@ namespace Disco.Services.Expressions return; } } - bool IExpressionPart.IsDynamic + public bool IsDynamic { get {