feature: pdf field locations cached
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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<IExpressionPart>
|
||||
{
|
||||
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<string, bool, object> Evaluate(object ExpressionContext, IDictionary Variables)
|
||||
{
|
||||
if (Count == 0)
|
||||
return new Tuple<string, bool, object>(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<string, bool, object>(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)
|
||||
{
|
||||
|
||||
+2
-1
@@ -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;
|
||||
|
||||
+5
@@ -20,5 +20,10 @@ namespace Disco.Services.Expressions.Extensions.ImageResultImplementations
|
||||
{
|
||||
return RenderImage(Image, Width, Height);
|
||||
}
|
||||
|
||||
public override Stream GetImage()
|
||||
{
|
||||
return OutputImage(Image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+46
-10
@@ -29,15 +29,50 @@ namespace Disco.Services.Expressions.Extensions.ImageResultImplementations
|
||||
|
||||
public override Stream GetImage(int Width, int Height)
|
||||
{
|
||||
List<Image> Images = new List<Image>();
|
||||
return DoLayout(Width, Height);
|
||||
}
|
||||
|
||||
public override Stream GetImage()
|
||||
{
|
||||
return DoLayout(width: null, height: null);
|
||||
}
|
||||
|
||||
private Stream DoLayout(int? width, int? height)
|
||||
{
|
||||
List<Image> images = new List<Image>();
|
||||
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<int, int, double>(bestColumnCount, bestRowCount, bestItemRatio);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user