/// <summary> /// Gather all the data for a single output page /// </summary> private static Result <DocumentPage> PreparePage(TemplatePage pageDef, DataMapper mapper, Dictionary <string, decimal> runningTotals, int pageIndex) { var docPage = new DocumentPage(pageDef, pageIndex); // Draw each box. We sort the boxes (currently by filter type) so running totals can work as expected foreach (var boxDef in pageDef.Boxes.Where(HasAValue).OrderBy(OrderBoxes)) { var result = PrepareBox(mapper, boxDef.Value !, runningTotals, pageIndex); if (result.IsFailure && boxDef.Value !.IsRequired) { return(Result.Failure <DocumentPage>(result.FailureCause)); } if (result.IsSuccess && result.ResultData != null) { docPage.DocumentBoxes.Add(boxDef.Key, result.ResultData); } } return(Result.Success(docPage)); }
/// <summary> /// Draw the prepared box to a PDF page /// </summary> private Result <Nothing> RenderBox(XFont baseFont, KeyValuePair <string, DocumentBox> boxDef, double fx, double fy, XGraphics gfx, DocumentPage pageToRender, int pageIndex, int pageTotal) { var box = boxDef.Value !; var font = (box.Definition.BoxFontSize != null) ? GetFont(box.Definition.BoxFontSize.Value) : baseFont; // boxes are defined in terms of the background image pixels or existing PDF page, so we need to convert var boxWidth = box.Definition.Width * fx; var boxHeight = box.Definition.Height * fy; var space = new XRect(box.Definition.Left * fx, box.Definition.Top * fy, boxWidth, boxHeight); // Handle the special case of embedding an image if (box.BoxType == DocumentBoxType.EmbedJpegImage) { if (string.IsNullOrWhiteSpace(box.RenderContent !)) { return(Result.Success()); // empty data is considered OK } try { _loadingTimer.Start(); var jpegStream = _files.LoadUrl(box.RenderContent); if (jpegStream is null) { return(Result.Success()); // Embedded images are always considered optional } var img = XImage.FromStream(jpegStream); gfx.DrawImage(img, space); } catch (Exception ex) { Console.WriteLine(ex); // Nothing. Embedded images are always considered optional } finally { _loadingTimer.Stop(); } return(Result.Success()); } // Read data, or pick a special type var str = box.BoxType switch { DocumentBoxType.Normal => box.RenderContent, // For the special values, we might want to re-apply the display format DocumentBoxType.PageGenerationDate => TryApplyDisplayFormat(box, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")), DocumentBoxType.CurrentPageNumber => TryApplyDisplayFormat(box, (pageIndex + 1).ToString()), DocumentBoxType.TotalPageCount => TryApplyDisplayFormat(box, pageTotal.ToString()), DocumentBoxType.RepeatingPageNumber => TryApplyDisplayFormat(box, (pageToRender.RepeatIndex + 1).ToString()), DocumentBoxType.RepeatingPageTotalCount => TryApplyDisplayFormat(box, pageToRender.RepeatCount.ToString()), _ => box.RenderContent }; if (str == null) { return(Result.Success()); // empty data is considered OK } var align = MapAlignments(box.Definition); RenderTextInBox(font, gfx, box.Definition, fx, fy, str, space, align); return(Result.Success()); }
/// <summary> /// Render a prepared page into a PDF document /// </summary> private Result <Nothing> OutputPage(PdfDocument document, DocumentPage pageToRender, XFont font, PageBacking background, int pageIndex, int pageTotal) { var pageDef = pageToRender.Definition; // If we have a source PDF, and we aren't trying to render a blank page, then import the page var shouldCopyPdfPage = background.ExistingPage != null && pageDef.RenderBackground; var page = shouldCopyPdfPage ? document.AddPage(background.ExistingPage !) : document.AddPage(/*blank*/); // If dimensions are silly, reset to A4 if (pageDef.WidthMillimetres < 10 || pageDef.WidthMillimetres > 1000) { pageDef.WidthMillimetres = 210; } if (pageDef.HeightMillimetres < 10 || pageDef.HeightMillimetres > 1000) { pageDef.HeightMillimetres = 297; } // Set the PDF page size (in points under the hood) page.Width = XUnit.FromMillimeter(pageDef.WidthMillimetres); page.Height = XUnit.FromMillimeter(pageDef.HeightMillimetres); using var gfx = XGraphics.FromPdfPage(page); gfx.Save(); if (shouldCopyPdfPage && background.ExistingPage !.Rotate != 0) { // Match visual-rotations on page var centre = new XPoint(0, 0); var visualRotate = background.ExistingPage !.Rotate % 360; // degrees, spec says it should be a multiple of 90. var angle = 360.0 - visualRotate; gfx.RotateAtTransform(angle, centre); switch (visualRotate) { case 90: gfx.TranslateTransform(-page.Height.Point, 0); break; case 180: gfx.TranslateTransform(-page.Width.Point, -page.Height.Point); break; case 270: gfx.TranslateTransform(-page.Height.Point / 2.0, -page.Height.Point); // this one is a guess, as I don't have an example break; default: throw new Exception("Unhandled visual rotation case"); } if (background.ExistingPage.Orientation == PageOrientation.Landscape) { (page.Width, page.Height) = (page.Height, page.Width); } } // Draw background at full page size _loadingTimer.Start(); if (pageDef.RenderBackground && background.BackgroundImage != null) { var destRect = new XRect(0, 0, (float)page.Width.Point, (float)page.Height.Point); gfx.DrawImage(background.BackgroundImage, destRect); } _loadingTimer.Stop(); // Do default scaling var fx = page.Width.Point / page.Width.Millimeter; var fy = page.Height.Point / page.Height.Millimeter; // If using an image, work out the bitmap -> page adjustment if (background.BackgroundImage != null) { fx = page.Width.Point / background.BackgroundImage.PixelWidth; fy = page.Height.Point / background.BackgroundImage.PixelHeight; } // Draw each box. foreach (var boxDef in pageToRender.DocumentBoxes) { var result = RenderBox(font, boxDef, fx, fy, gfx, pageToRender, pageIndex, pageTotal); if (result.IsFailure) { return(result); } } gfx.Restore(); return(Result.Success()); }