feature: pdf field locations cached
This commit is contained in:
@@ -10,6 +10,7 @@ using iTextSharp.text.pdf;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@@ -31,8 +32,21 @@ namespace Disco.BI.Extensions
|
|||||||
int pdfFieldOrdinal = 0;
|
int pdfFieldOrdinal = 0;
|
||||||
foreach (string pdfFieldKey in pdfReader.AcroFields.Fields.Keys)
|
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);
|
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++;
|
pdfFieldOrdinal++;
|
||||||
}
|
}
|
||||||
pdfReader.Close();
|
pdfReader.Close();
|
||||||
|
|||||||
@@ -290,6 +290,7 @@ namespace Disco.BI.Interop.Pdf
|
|||||||
AcroFields.FieldPosition pdfFieldPosition = pdfFieldPositions[pdfFieldOrdinal];
|
AcroFields.FieldPosition pdfFieldPosition = pdfFieldPositions[pdfFieldOrdinal];
|
||||||
iTextSharp.text.Image pdfImage = iTextSharp.text.Image.GetInstance(imageResult.GetImage((int)pdfFieldPosition.position.Width, (int)pdfFieldPosition.position.Height));
|
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.SetAbsolutePosition(pdfFieldPosition.position.Left, pdfFieldPosition.position.Bottom);
|
||||||
|
pdfImage.ScaleToFit(pdfFieldPosition.position.Width, pdfFieldPosition.position.Height);
|
||||||
pdfStamper.GetOverContent(pdfFieldPosition.page).AddImage(pdfImage);
|
pdfStamper.GetOverContent(pdfFieldPosition.page).AddImage(pdfImage);
|
||||||
}
|
}
|
||||||
if (!fieldExpressionResult.Item2 && !imageResult.ShowField)
|
if (!fieldExpressionResult.Item2 && !imageResult.ShowField)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace Disco.Models.BI.Expressions
|
|||||||
public interface IImageExpressionResult
|
public interface IImageExpressionResult
|
||||||
{
|
{
|
||||||
Stream GetImage(int Width, int Height);
|
Stream GetImage(int Width, int Height);
|
||||||
|
Stream GetImage();
|
||||||
byte Quality { get; set; }
|
byte Quality { get; set; }
|
||||||
bool LosslessFormat { get; set; }
|
bool LosslessFormat { get; set; }
|
||||||
bool ShowField { get; set; }
|
bool ShowField { get; set; }
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Spring.Expressions;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@@ -15,16 +16,24 @@ namespace Disco.Services.Expressions
|
|||||||
{
|
{
|
||||||
public sealed class Expression : List<IExpressionPart>
|
public sealed class Expression : List<IExpressionPart>
|
||||||
{
|
{
|
||||||
public string Name { get; private set; }
|
public string Name { get; }
|
||||||
public string Source { get; private set; }
|
public string Source { get; }
|
||||||
public bool IsDynamic { get; private set; }
|
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;
|
Name = name;
|
||||||
this.Source = Source;
|
Source = source;
|
||||||
this.Ordinal = Ordinal;
|
Ordinal = ordinal;
|
||||||
|
IsRequired = isRequired;
|
||||||
|
IsReadOnly = isReadOnly;
|
||||||
|
Position = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void InitializeExpressions()
|
public static void InitializeExpressions()
|
||||||
@@ -71,6 +80,19 @@ namespace Disco.Services.Expressions
|
|||||||
|
|
||||||
public Tuple<string, bool, object> Evaluate(object ExpressionContext, IDictionary Variables)
|
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();
|
var resultValue = new StringBuilder();
|
||||||
object resultObject = null;
|
object resultObject = null;
|
||||||
bool resultError = false;
|
bool resultError = false;
|
||||||
@@ -103,25 +125,28 @@ namespace Disco.Services.Expressions
|
|||||||
}
|
}
|
||||||
public static Expression TokenizeSingleDynamic(string Name, string ExpressionSource, int Ordinal)
|
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))
|
if (ExpressionSource != null && !string.IsNullOrWhiteSpace(ExpressionSource))
|
||||||
e.Add(new EvaluateExpressionPart(ExpressionSource));
|
e.Add(new EvaluateExpressionPart(ExpressionSource));
|
||||||
e.IsDynamic = true;
|
e.IsDynamic = true;
|
||||||
return e;
|
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);
|
var e = new Expression(name, expressionSource, ordinal, isRequired, isReadOnly, position);
|
||||||
if (!ExpressionSource.Contains("{") || !ExpressionSource.Contains("}"))
|
if (!expressionSource.Contains("{") || !expressionSource.Contains("}"))
|
||||||
{
|
{
|
||||||
e.Add(new TextExpressionPart(ExpressionSource));
|
e.Add(new TextExpressionPart(expressionSource));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var token = new StringBuilder();
|
var token = new StringBuilder();
|
||||||
bool tokenEval = false;
|
bool tokenEval = false;
|
||||||
int tokenEvalDepth = 0;
|
int tokenEvalDepth = 0;
|
||||||
foreach (char c in ExpressionSource)
|
foreach (char c in expressionSource)
|
||||||
{
|
{
|
||||||
switch (c)
|
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(int Width, int Height);
|
||||||
|
public abstract Stream GetImage();
|
||||||
|
|
||||||
protected Stream RenderImage(Image SourceImage, int Width, int Height)
|
protected Stream RenderImage(Image SourceImage, int Width, int Height)
|
||||||
{
|
{
|
||||||
@@ -56,7 +57,7 @@ namespace Disco.Services.Expressions.Extensions.ImageResultImplementations
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // Lossy Format - JPG
|
{ // 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);
|
SourceImage.SaveJpg(quality, imageStream);
|
||||||
}
|
}
|
||||||
imageStream.Position = 0;
|
imageStream.Position = 0;
|
||||||
|
|||||||
+5
@@ -20,5 +20,10 @@ namespace Disco.Services.Expressions.Extensions.ImageResultImplementations
|
|||||||
{
|
{
|
||||||
return RenderImage(Image, Width, Height);
|
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);
|
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)
|
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
|
try
|
||||||
{
|
{
|
||||||
// Load Images
|
// Load Images
|
||||||
foreach (string imageFilePath in AbsoluteFilePaths)
|
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
|
// Build Montage
|
||||||
using (Bitmap montageImage = new Bitmap(Width, Height))
|
using (Bitmap montageImage = new Bitmap(width.Value, height.Value))
|
||||||
{
|
{
|
||||||
using (Graphics montageGraphics = Graphics.FromImage(montageImage))
|
using (Graphics montageGraphics = Graphics.FromImage(montageImage))
|
||||||
{
|
{
|
||||||
@@ -55,14 +90,13 @@ namespace Disco.Services.Expressions.Extensions.ImageResultImplementations
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (MontageHorizontalLayout)
|
if (MontageHorizontalLayout)
|
||||||
DoHorizontalLayout(Images, montageGraphics);
|
DoHorizontalLayout(images, montageGraphics);
|
||||||
else
|
else
|
||||||
if (MontageVerticalLayout)
|
if (MontageVerticalLayout)
|
||||||
DoVirticalLayout(Images, montageGraphics);
|
DoVirticalLayout(images, montageGraphics);
|
||||||
else
|
else
|
||||||
DoTableLayout(Images, montageGraphics);
|
DoTableLayout(images, montageGraphics);
|
||||||
}
|
}
|
||||||
|
|
||||||
return OutputImage(montageImage);
|
return OutputImage(montageImage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,8 +104,8 @@ namespace Disco.Services.Expressions.Extensions.ImageResultImplementations
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// Dispose of any Images
|
// Dispose of any Images
|
||||||
if (Images != null)
|
if (images != null)
|
||||||
foreach (Image i in Images)
|
foreach (Image i in images)
|
||||||
i.Dispose();
|
i.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,5 +209,7 @@ namespace Disco.Services.Expressions.Extensions.ImageResultImplementations
|
|||||||
|
|
||||||
return new Tuple<int, int, double>(bestColumnCount, bestRowCount, bestItemRatio);
|
return new Tuple<int, int, double>(bestColumnCount, bestRowCount, bestItemRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ namespace Disco.Services.Expressions
|
|||||||
{
|
{
|
||||||
private string _Source;
|
private string _Source;
|
||||||
|
|
||||||
bool IExpressionPart.ErrorsAllowed
|
public bool ErrorsAllowed
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@@ -15,7 +15,7 @@ namespace Disco.Services.Expressions
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
string IExpressionPart.Source
|
public string Source
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@@ -26,7 +26,7 @@ namespace Disco.Services.Expressions
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
string IExpressionPart.RawSource
|
public string RawSource
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@@ -37,7 +37,7 @@ namespace Disco.Services.Expressions
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool IExpressionPart.IsDynamic
|
public bool IsDynamic
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user