Preview document template pdf

Renders document template pdfs to an image which is displayed in the
configuration ui.
This commit is contained in:
Gary Sharp
2016-09-21 19:55:57 +10:00
parent 85425d2a1f
commit 489a5df7cc
7 changed files with 1159 additions and 858 deletions
+1 -1
View File
@@ -261,7 +261,7 @@
<Compile Include="Documents\AttachmentImport\ImportDirectoryMonitor.cs" />
<Compile Include="Documents\AttachmentImport\ImportPage.cs" />
<Compile Include="Documents\DocumentsLog.cs" />
<Compile Include="Documents\DocumentTemplateActionExtensions.cs" />
<Compile Include="Documents\DocumentTemplateExtensions.cs" />
<Compile Include="Documents\DocumentTemplateDataStoreExtensions.cs" />
<Compile Include="Documents\DocumentTemplateExpressionExtensions.cs" />
<Compile Include="Documents\DocumentUniqueIdentifier.cs" />
@@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Disco.Services
{
public static class DocumentTemplateActionExtensions
{
}
}
@@ -0,0 +1,90 @@
using Disco.Data.Repository;
using Disco.Models.Repository;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Disco.Services
{
public static class DocumentTemplateActionExtensions
{
public static Bitmap GenerateTemplatePreview(this DocumentTemplate DocumentTemplate, DiscoDataContext Database, int Width, int PageGapHeight, bool DrawPageBorder)
{
string filename = DocumentTemplate.RepositoryFilename(Database);
if (File.Exists(filename))
{
using (var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var pdfDocument = PdfiumViewer.PdfDocument.Load(fileStream))
{
var pageMaxWidth = (int)pdfDocument.PageSizes.Max(s => s.Width);
var pageScale = (float)(Width + (DrawPageBorder ? -2 : 0)) / pageMaxWidth;
var previewTotalHeight = pdfDocument.PageSizes
.Take(40)
.Select(s => (int)(pageScale * s.Height))
.Sum() +
(DrawPageBorder ? (Math.Min(40, pdfDocument.PageCount) * 2) : 0) +
((Math.Min(40, pdfDocument.PageCount) - 1) * PageGapHeight);
var result = new Bitmap(Width, previewTotalHeight);
result.SetResolution(72, 72);
using (var graphics = Graphics.FromImage(result))
{
var yPosition = 0;
for (int pageIndex = 0; pageIndex < Math.Min(40, pdfDocument.PageCount); pageIndex++)
{
var pageSize = pdfDocument.PageSizes[pageIndex];
var previewWidth = Math.Floor(pageScale * pageSize.Width);
var previewHeight = Math.Floor(pageScale * pageSize.Height);
// Calculate box
var destination = new Rectangle(
x: (int)((Width - previewWidth) / 2),
y: yPosition + (DrawPageBorder ? 1 : 0),
width: (int)previewWidth,
height: (int)previewHeight
);
// Fill white background
graphics.FillRectangle(Brushes.White, destination);
using (var image = pdfDocument.Render(pageIndex, (int)previewWidth, (int)previewHeight, 72F, 72F, false))
{
graphics.DrawImage(image, destination.X, destination.Y);
}
if (DrawPageBorder)
{
destination.X -= 1;
destination.Y -= 1;
destination.Width += 1;
destination.Height += 1;
graphics.DrawRectangle(Pens.LightGray, destination);
}
yPosition += destination.Height + PageGapHeight;
}
}
return result;
}
}
}
return null;
}
}
}
@@ -11,6 +11,7 @@ using Disco.Services.Users;
using Disco.Services.Web;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
@@ -140,6 +141,29 @@ namespace Disco.Web.Areas.API.Controllers
}
}
[DiscoAuthorize(Claims.Config.DocumentTemplate.Show), HttpGet]
public virtual ActionResult TemplatePreview(string id)
{
if (string.IsNullOrEmpty(id))
throw new ArgumentNullException("id");
var documentTemplate = Database.DocumentTemplates.Find(id);
if (documentTemplate == null)
throw new ArgumentException("Invalid Document Template Id", "id");
var imageStream = new MemoryStream();
using (var previewImage = documentTemplate.GenerateTemplatePreview(Database, 450, 8, true))
{
if (previewImage == null)
{
throw new InvalidOperationException("Template not found");
}
previewImage.SavePng(imageStream);
}
imageStream.Position = 0;
return File(imageStream, "image/png");
}
#region Update Shortcut Methods
[DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)]
public virtual ActionResult UpdateDescription(string id, string Description = null, bool redirect = false)
@@ -265,7 +289,7 @@ namespace Disco.Web.Areas.API.Controllers
#endregion
#region Update Properties
private void UpdateDescription(Disco.Models.Repository.DocumentTemplate documentTemplate, string Description)
private void UpdateDescription(DocumentTemplate documentTemplate, string Description)
{
if (!string.IsNullOrWhiteSpace(Description))
{
@@ -275,9 +299,9 @@ namespace Disco.Web.Areas.API.Controllers
}
throw new Exception("Invalid Description");
}
private ScheduledTaskStatus UpdateScope(Disco.Models.Repository.DocumentTemplate documentTemplate, string Scope)
private ScheduledTaskStatus UpdateScope(DocumentTemplate documentTemplate, string Scope)
{
if (string.IsNullOrWhiteSpace(Scope) || !Disco.Models.Repository.DocumentTemplate.DocumentTemplateScopes.ToList().Contains(Scope))
if (string.IsNullOrWhiteSpace(Scope) || !DocumentTemplate.DocumentTemplateScopes.ToList().Contains(Scope))
throw new ArgumentException("Invalid Scope", "Scope");
Database.Configuration.LazyLoadingEnabled = true;
@@ -287,7 +311,7 @@ namespace Disco.Web.Areas.API.Controllers
documentTemplate.Scope = Scope;
if (documentTemplate.Scope != Disco.Models.Repository.DocumentTemplate.DocumentTemplateScopes.Job &&
if (documentTemplate.Scope != DocumentTemplate.DocumentTemplateScopes.Job &&
documentTemplate.JobSubTypes != null)
{
foreach (var st in documentTemplate.JobSubTypes.ToArray())
@@ -308,7 +332,7 @@ namespace Disco.Web.Areas.API.Controllers
return null;
}
private void UpdateFilterExpression(Disco.Models.Repository.DocumentTemplate documentTemplate, string FilterExpression)
private void UpdateFilterExpression(DocumentTemplate documentTemplate, string FilterExpression)
{
if (string.IsNullOrWhiteSpace(FilterExpression))
{
@@ -323,7 +347,7 @@ namespace Disco.Web.Areas.API.Controllers
Database.SaveChanges();
}
private void UpdateOnGenerateExpression(Disco.Models.Repository.DocumentTemplate documentTemplate, string OnGenerateExpression)
private void UpdateOnGenerateExpression(DocumentTemplate documentTemplate, string OnGenerateExpression)
{
if (string.IsNullOrWhiteSpace(OnGenerateExpression))
{
@@ -338,7 +362,7 @@ namespace Disco.Web.Areas.API.Controllers
Database.SaveChanges();
}
private void UpdateOnImportAttachmentExpression(Disco.Models.Repository.DocumentTemplate documentTemplate, string OnImportAttachmentExpression)
private void UpdateOnImportAttachmentExpression(DocumentTemplate documentTemplate, string OnImportAttachmentExpression)
{
if (string.IsNullOrWhiteSpace(OnImportAttachmentExpression))
{
@@ -353,7 +377,7 @@ namespace Disco.Web.Areas.API.Controllers
Database.SaveChanges();
}
private void UpdateFlattenForm(Disco.Models.Repository.DocumentTemplate documentTemplate, string FlattenForm)
private void UpdateFlattenForm(DocumentTemplate documentTemplate, string FlattenForm)
{
if (string.IsNullOrWhiteSpace(FlattenForm))
{
@@ -370,7 +394,7 @@ namespace Disco.Web.Areas.API.Controllers
Database.SaveChanges();
}
private void UpdateJobSubTypes(Disco.Models.Repository.DocumentTemplate documentTemplate, List<string> JobSubTypes)
private void UpdateJobSubTypes(DocumentTemplate documentTemplate, List<string> JobSubTypes)
{
Database.Configuration.LazyLoadingEnabled = true;
@@ -384,7 +408,7 @@ namespace Disco.Web.Areas.API.Controllers
// Add New
if (JobSubTypes != null && JobSubTypes.Count > 0)
{
var subTypes = new List<Disco.Models.Repository.JobSubType>();
var subTypes = new List<JobSubType>();
foreach (var stId in JobSubTypes)
{
var typeId = stId.Substring(0, stId.IndexOf("_"));
@@ -48,22 +48,27 @@
<table>
<tbody>
<tr>
<th>Id:
<th>
Id:
</th>
<td>@Html.DisplayFor(model => model.DocumentTemplate.Id)
<td>
@Html.DisplayFor(model => model.DocumentTemplate.Id)
</td>
</tr>
<tr>
<th>Statistics:
<th>
Statistics:
</th>
<td>
<strong>@Model.StoredInstanceCount.ToString("n0")</strong> Stored Instance@(Model.StoredInstanceCount == 1 ? null : "s")
</td>
</tr>
<tr>
<th>Description:
<th>
Description:
</th>
<td>@if (canConfig)
<td>
@if (canConfig)
{
@Html.TextBoxFor(model => model.DocumentTemplate.Description)
@AjaxHelpers.AjaxSave()
@@ -93,11 +98,15 @@
</td>
</tr>
<tr>
<th>Always Flatten Form:
<th>
&nbsp;
</th>
<td>@if (canConfig)
<td>
<div>
@if (canConfig)
{
<input id="DocumentTemplate_FlattenForm" type="checkbox" @(Model.DocumentTemplate.FlattenForm ? new MvcHtmlString("checked=\"checked\" ") : new MvcHtmlString(string.Empty)) />
<label for="DocumentTemplate_FlattenForm">Flatten Form</label>
@AjaxHelpers.AjaxLoader()
<script type="text/javascript">
$(function () {
@@ -113,11 +122,19 @@
else
{
<input id="DocumentTemplate_FlattenForm" type="checkbox" @(Model.DocumentTemplate.FlattenForm ? new MvcHtmlString("checked=\"checked\" ") : new MvcHtmlString(string.Empty)) disabled="disabled" />
<label for="DocumentTemplate_FlattenForm">Flatten Form</label>
}
</div>
<div class="info-box">
<p class="fa-p">
<i class="fa fa-info-circle"></i>If selected when a document is generated all form elements will be removed and their content written in place.
</p>
</div>
</td>
</tr>
<tr>
<th>Scope:
<th>
Scope:
</th>
<td>
<h4>@Model.DocumentTemplate.Scope Scope</h4>
@@ -237,7 +254,8 @@
{
<div class="jobTypes">
<h4>
<input id="Types_@(jt.Id)" class="jobType" type="checkbox" value="@(jt.Id)" @(selectedTypes.Contains(jt) ? "checked=\"checked\"" : null) /><label for="Types_@(jt.Id)">@jt.Description</label></h4>
<input id="Types_@(jt.Id)" class="jobType" type="checkbox" value="@(jt.Id)" @(selectedTypes.Contains(jt) ? "checked=\"checked\"" : null) /><label for="Types_@(jt.Id)">@jt.Description</label>
</h4>
<div id="SubTypes_@(jt.Id)" class="jobSubTypes">
@CommonHelpers.CheckboxBulkSelect(string.Format("CheckboxBulkSelect_{0}", jt.Id), "div")
@CommonHelpers.CheckBoxList("JobSubTypes", jt.JobSubTypes.OrderBy(jst => jst.Description).ToSelectListItems(Model.DocumentTemplate.JobSubTypes), 2)
@@ -313,10 +331,25 @@
}
</td>
</tr>
</tbody>
</table>
</div>
<div class="form Config_DocumentTemplates_Template" style="width: 650px; margin: 0 auto 20px;">
<h2>PDF Template</h2>
<table>
<tbody>
<tr>
<th>PDF Template
</th>
<td>
<div style="margin: -8px -5px; max-height: 350px; overflow-y: scroll; text-align: center;">
<img style="margin: 8px 5px;" src="@Url.Action(MVC.API.DocumentTemplate.TemplatePreview(Model.DocumentTemplate.Id))" />
</div>
</td>
</tr>
@if (canConfig)
{
<tr>
<td style="text-align: right;">
@Html.ActionLinkSmallButton("Download Template", MVC.API.DocumentTemplate.Template(Model.DocumentTemplate.Id))
@if (canConfig && Authorization.Has(Claims.Config.DocumentTemplate.Upload))
{
@@ -371,34 +404,21 @@
}
</td>
</tr>
@if (hideAdvanced)
{
<tr>
<td colspan="2" style="text-align: right;">
<button id="Config_HideAdvanced_Show" class="button small">Show Advanced Options</button>
<script>
$(function () {
$('#Config_HideAdvanced_Show').click(function () {
var $this = $(this);
$this.closest('.Config_HideAdvanced').removeClass('Config_HideAdvanced');
$this.closest('tr').remove();
});
});
</script>
</td>
</tr>
}
</tbody>
</table>
</div>
<div class="form Config_HideAdvanced_Item" style="width: 650px;">
<h2>Advanced Options</h2>
<table>
<tbody>
<tr>
<th>Filter Expression:
<th>
Filter Expression:
</th>
<td>@if (canConfig && Authorization.Has(Claims.Config.DocumentTemplate.ConfigureFilterExpression))
<td>
@if (canConfig && Authorization.Has(Claims.Config.DocumentTemplate.ConfigureFilterExpression))
{
@Html.EditorFor(model => model.DocumentTemplate.FilterExpression)
@AjaxHelpers.AjaxRemove()
@@ -464,9 +484,11 @@
</td>
</tr>
<tr>
<th>On Generated Expression:
<th>
On Generated Expression:
</th>
<td>@if (canConfig && Authorization.Has(Claims.Config.DocumentTemplate.ConfigureFilterExpression))
<td>
@if (canConfig && Authorization.Has(Claims.Config.DocumentTemplate.ConfigureFilterExpression))
{
@Html.EditorFor(model => model.DocumentTemplate.OnGenerateExpression)
@AjaxHelpers.AjaxRemove()
@@ -532,9 +554,11 @@
</td>
</tr>
<tr>
<th>On Import Expression:
<th>
On Import Expression:
</th>
<td>@if (canConfig && Authorization.Has(Claims.Config.DocumentTemplate.ConfigureFilterExpression))
<td>
@if (canConfig && Authorization.Has(Claims.Config.DocumentTemplate.ConfigureFilterExpression))
{
@Html.EditorFor(model => model.DocumentTemplate.OnImportAttachmentExpression)
@AjaxHelpers.AjaxRemove()
@@ -600,7 +624,8 @@
</td>
</tr>
<tr>
<th>Linked Groups:
<th>
Linked Groups:
</th>
<td>
<div>
@@ -638,8 +663,10 @@
<div id="dialogConfirmDelete" title="Delete this Document Template?">
<p>
<i class="fa fa-exclamation-triangle fa-lg warning"></i>This item will be permanently deleted and cannot be recovered.<br />
<em>This <strong>will not delete attachments</strong> which have already been imported,
but any generated documents will no longer be automatically imported.</em><br />
<em>
This <strong>will not delete attachments</strong> which have already been imported,
but any generated documents will no longer be automatically imported.
</em><br />
Are you sure?
</p>
</div>
@@ -673,6 +700,18 @@
});
</script>
<div class="actionBar">
@if (hideAdvanced)
{
<button id="Config_HideAdvanced_Show" class="button">Show Advanced Options</button>
<script>
$(function () {
$('#Config_HideAdvanced_Show').click(function () {
$('#Config_DocumentTemplates_Show').removeClass('Config_HideAdvanced');
$(this).remove();
});
});
</script>
}
@if (Authorization.Has(Claims.Config.Show))
{
@Html.ActionLinkButton("Expression Browser", MVC.Config.DocumentTemplate.ExpressionBrowser())
@@ -690,9 +729,11 @@
</div>
<div class="examples clearfix">
<h4>Examples:</h4>
<div class="example1 code">01234567<br />
<div class="example1 code">
01234567<br />
ABCD9876<br />
8VQ6G2R</div>
8VQ6G2R
</div>
<div class="example2 code">01234567,ABCD9876,8VQ6G2R</div>
<div class="example3 code">01234567;ABCD9876;8VQ6G2R</div>
</div>
@@ -703,9 +744,11 @@
</div>
<div class="examples clearfix">
<h4>Examples:</h4>
<div class="example1 code">86<br />
<div class="example1 code">
86<br />
99<br />
44</div>
44
</div>
<div class="example2 code">86,99,44</div>
<div class="example3 code">86;99;44</div>
</div>
@@ -716,8 +759,10 @@
</div>
<div class="examples clearfix">
<h4>Examples:</h4>
<div class="example1 code">user6<br />
smi0099<br />@(ActiveDirectory.Context.PrimaryDomain.NetBiosName)\rsmith</div>
<div class="example1 code">
user6<br />
smi0099<br />@(ActiveDirectory.Context.PrimaryDomain.NetBiosName)\rsmith
</div>
<div class="example2 code">user6,smi0099,@(ActiveDirectory.Context.PrimaryDomain.NetBiosName)\rsmith</div>
<div class="example3 code">user6;smi0099;@(ActiveDirectory.Context.PrimaryDomain.NetBiosName)\rsmith</div>
</div>
File diff suppressed because it is too large Load Diff
@@ -71,6 +71,12 @@ namespace Disco.Web.Areas.API.Controllers
}
[NonAction]
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public virtual System.Web.Mvc.ActionResult TemplatePreview()
{
return new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.TemplatePreview);
}
[NonAction]
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public virtual System.Web.Mvc.ActionResult UpdateDescription()
{
return new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.UpdateDescription);
@@ -183,6 +189,7 @@ namespace Disco.Web.Areas.API.Controllers
{
public readonly string Update = "Update";
public readonly string Template = "Template";
public readonly string TemplatePreview = "TemplatePreview";
public readonly string UpdateDescription = "UpdateDescription";
public readonly string UpdateFilterExpression = "UpdateFilterExpression";
public readonly string UpdateOnGenerateExpression = "UpdateOnGenerateExpression";
@@ -207,6 +214,7 @@ namespace Disco.Web.Areas.API.Controllers
{
public const string Update = "Update";
public const string Template = "Template";
public const string TemplatePreview = "TemplatePreview";
public const string UpdateDescription = "UpdateDescription";
public const string UpdateFilterExpression = "UpdateFilterExpression";
public const string UpdateOnGenerateExpression = "UpdateOnGenerateExpression";
@@ -248,6 +256,14 @@ namespace Disco.Web.Areas.API.Controllers
public readonly string redirect = "redirect";
public readonly string Template = "Template";
}
static readonly ActionParamsClass_TemplatePreview s_params_TemplatePreview = new ActionParamsClass_TemplatePreview();
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public ActionParamsClass_TemplatePreview TemplatePreviewParams { get { return s_params_TemplatePreview; } }
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public class ActionParamsClass_TemplatePreview
{
public readonly string id = "id";
}
static readonly ActionParamsClass_UpdateDescription s_params_UpdateDescription = new ActionParamsClass_UpdateDescription();
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public ActionParamsClass_UpdateDescription UpdateDescriptionParams { get { return s_params_UpdateDescription; } }
@@ -465,6 +481,18 @@ namespace Disco.Web.Areas.API.Controllers
return callInfo;
}
[NonAction]
partial void TemplatePreviewOverride(T4MVC_System_Web_Mvc_ActionResult callInfo, string id);
[NonAction]
public override System.Web.Mvc.ActionResult TemplatePreview(string id)
{
var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.TemplatePreview);
ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "id", id);
TemplatePreviewOverride(callInfo, id);
return callInfo;
}
[NonAction]
partial void UpdateDescriptionOverride(T4MVC_System_Web_Mvc_ActionResult callInfo, string id, string Description, bool redirect);