xfinium: How To generate PDF with differant orientations - c#

I try to create a PdfFlowDocument with header and footer. The page from 1 to 3 in Portrait mode. Page 4 is in Landscape and the rest again in portrait. Following my code code snippet:
PdfFlowDocument flowDocument = new PdfFlowDocument();
//flowDocument.HeadersFooters.EvenPagesHeader = docHeader;
//flowDocument.HeadersFooters.EvenPagesFooter = docFooter;
Xfinium.Pdf.Core.PdfFile sourceFile = new Core.PdfFile(File.OpenRead(""));
Xfinium.Pdf.Graphics.PdfPageContent[] pageContents = sourceFile.ExtractPageContent(0, sourceFile.PageCount - 1);
foreach (Xfinium.Pdf.Graphics.PdfPageContent content in pageContents)
{
//content can be Portrait or Landscape
//How can I rotate the orientation?
//How can I scale the page?
var flowContent = new Xfinium.Pdf.FlowDocument.PdfFlowFormXObjectContent(content);
flowDocument.AddContent(flowContent);
}

You handle the PageCreated event and set the page rotation to 90 for landscape based on your specific condition.
PdfFlowDocument flowDocument = new PdfFlowDocument();
flowDocument.PageCreated += FlowDocument_PageCreated;
//flowDocument.HeadersFooters.EvenPagesHeader = docHeader;
//flowDocument.HeadersFooters.EvenPagesFooter = docFooter;
Xfinium.Pdf.Core.PdfFile sourceFile = new Core.PdfFile(File.OpenRead(""));
Xfinium.Pdf.Graphics.PdfPageContent[] pageContents = sourceFile.ExtractPageContent(0, sourceFile.PageCount - 1);
foreach (Xfinium.Pdf.Graphics.PdfPageContent content in pageContents)
{
//content can be Portrait or Landscape
//How can I rotate the orientation?
//How can I scale the page?
var flowContent = new Xfinium.Pdf.FlowDocument.PdfFlowFormXObjectContent(content);
flowContent.SizeIsRelativeToAvailableSpace = true;
flowContent.FormXObjectHeight = 100;
flowContent.FormXObjectWidth = 100;
flowDocument.AddContent(flowContent);
}
private void FlowDocument_PageCreated(object sender, PdfFlowPageCreatedEventArgs e)
{
if (yourConditionHere)
{
e.Page.Rotation = 90;
}
}
Update: you can set the flowContent.SizeIsRelativeToAvailableSpace property to true and flowContent.FormXObjectWidth and flowContent.FormXObjectHeight properties to 100. This means the content object will be scaled to take all available space (100%). It will also take care of the white page problem.
Disclaimer: I work for the company that develops the XFINIUM.PDF library.

Related

Split overflow text in iText 7 paragraph

I have a document where the first page has three sections. The top and bottom section are required to be on the first page. The center section has dynamic text. I am trying to find a way to get all the text that will fit in the center section, set it, and then add the remaining to the continuation second page (if required).
I have looked at this S.O. answer, but it seems that it wouldn't allow for the bottom section to be placed where it needs to go.
Here is what I am trying to do.
using (var workStream = new MemoryStream())
{
using (var pdfWriter = new PdfWriter(workStream))
{
using (var pdfDoc = new PdfDocument(pdfWriter))
{
var document = new Document(pdfDoc);
var pageSize = pdfDoc.GetDefaultPageSize();
var width = pageSize.GetWidth() - document.GetLeftMargin() - document.GetRightMargin();
// *** First Top Section ***
// ... variable creations removed for brevity ...
document.Add(paragraph);
document.Add(table);
// *** CENTER SECTION ***
// This section has a required float height of 325f
// *** Third Bottom Section ***
// ... table creation removed for brevity ...
document.Add(table);
// *** Create the second page if needed and add the overflow text ***
}
}
}
I found a solution that works for me. I am sure I could streamline it, but tempest fugit.
So here is how I got the text for section 1:
// Set the height of section 2 paragraph
var section2Height = 325f;
// Create a Text element with the desired text
var tempText = new Text(myDesiredText).SetFont(font);
// Create rectangle for the allowable space for section 2
var rectangle = new Rectangle(width, section2Height);
// Create PDFCanvas for the document
var pdfCanvas = new PdfCanvas(pdfDoc, 1);
// Create canvas space for adding the temp paragraph to for splitting
var canvas = new Canvas(pdfCanvas, rectangle);
// Set the temp text to a paragraph
var synopsis = new Paragraph(tempText).SetFixedLeading(12f);
// Get the paragraph renderer
var renderer = synopsis.CreateRendererSubTree();
// Set the paragraph renderer parent to the canvas renderer
renderer.SetParent(canvas.GetRenderer());
// Process the renderer and get the layout result
var result = renderer.Layout(new LayoutContext(new LayoutArea(1, rectangle)));
// If the renderer is full, get all text, otherwise get the text that fits
var page1 = result.GetStatus() == LayoutResult.FULL ? renderer : result.GetSplitRenderer();
// Create a new paragraph for Section 2 (SYNOPSIS)
var newParagraph = new Paragraph().SetFixedLeading(12f);
// Set the Paragraph height and width
newParagraph.SetWidth(width);
newParagraph.SetHeight(section2Height);
// Get the count of child renderers. Each one is a new line
var page1Count = page1.GetChildRenderers().Count;
// Instantiate a string for the last renderer text
var lastText = string.Empty;
// Iterate through the child renderers
for (var i = 0; i < page1Count; i++)
{
// Get the string value
var text = page1.GetChildRenderers()[i].ToString();
// Create a text element
var t1 = new Text(text + Environment.NewLine).SetFont(font);
// Add the Text element to the new paragraph
newParagraph.Add(t1);
// If this is the last line, store the text for later use
if (i + 1 == page1Count)
lastText = text;
}
// Get the index of the last string of text
index = myDesiredText.IndexOf(lastText);
// Get the remaining text base off the index and the last text string length
var page2 = myDesiredText.Substring(index + lastText.Length);
// If there is a string value...
if (!string.IsNullOrEmpty(page2))
{
// Create a list of line returns
var returnCharList = new List<string> { "\r", "\n" };
// Remove the beginning line returns
while (returnCharList.Contains(page2.Substring(0, 1)))
page2 = page2.Substring(2);
}
// Add the new paragraph to the section
document.Add(newParagraph);
This I how I set the rest of the text to the next page
// If there is a second page, build it out
if (!string.IsNullOrEmpty(page2))
{
// Create the text element
var page2Text = new Text(page2).SetFont(font);
// Get the workable height for the paragraph minus the space for the page footer
var page2Height = height - 30f;
// Create page 2 paragraph
var page2Paragraph = new Paragraph(page2Text).SetHeight(page2Height).SetFixedLeading(12f);
// Add the paragraph to the document
document.Add(page2Paragraph);
// Create new pager
pager = new Paragraph().AddTabStops(tabStops);
// Set the page string for page 2
pageBill = $"EBM - { RemoveBillSpaces(bsd.Bill.BillNumber)} - 2 of { totalPages }";
// Create the text element
pagerText = new Text(pageBill).SetFont(font).SetFontSize(fontSize_Small);
// Add the tabs and text for the pager
pager
.Add(new Tab())
.Add(new Tab())
.Add(pagerText);
// Add the pager to page 2
document.Add(pager);
}

UWP wincomposition: animate multiple images' offset

I try to animate multiple (different sized) images in one Grid control but only the biggest one is animated. The targeted property is "Offset.x".
var container = new Grid();
var compositor = ElementCompositionPreview.GetElementVisual(container).Compositor;
var containerVisual = compositor.CreateContainerVisual();
// ------
// CLOUDS
// ------
var cloudImage1 = CreateCloudImage(60);
var cloudVisual1 = ElementCompositionPreview.GetElementVisual(cloudImage1);
var cloudImage2 = CreateCloudImage(70);
var cloudVisual2 = ElementCompositionPreview.GetElementVisual(cloudImage2);
var cloudImage3 = CreateCloudImage(50);
var cloudVisual3 = ElementCompositionPreview.GetElementVisual(cloudImage3);
container.Children.Add(cloudImage1);
container.Children.Add(cloudImage2);
container.Children.Add(cloudImage3);
containerVisual.Children.InsertAtTop(cloudVisual1);
containerVisual.Children.InsertAtTop(cloudVisual2);
containerVisual.Children.InsertAtTop(cloudVisual3);
// ----------
// ANIMATIONS
// ----------
var offsetAnimation = CreateOffsetAnimation(compositor, 50, 6);
cloudVisual1.StartAnimation("Offset.x", offsetAnimation);
offsetAnimation.InsertKeyFrame(1f, -60);
cloudVisual2.StartAnimation("Offset.x", offsetAnimation); // doesn't work
ElementCompositionPreview.SetElementChildVisual(container, containerVisual);
return container;
Helpers methods:
static ScalarKeyFrameAnimation CreateOffsetAnimation(Compositor compositor, float endKeyFrame, double duration) {
var animation = compositor.CreateScalarKeyFrameAnimation();
animation.InsertKeyFrame(0f, 0);
animation.InsertKeyFrame(1f, endKeyFrame);
animation.IterationBehavior = AnimationIterationBehavior.Forever;
animation.Direction = AnimationDirection.Alternate;
animation.Duration = TimeSpan.FromSeconds(duration);
return animation;
}
static Image CreateCloudImage(double size) {
var cloudBitmapImage = new BitmapImage(new Uri("ms-appx:///Assets/Icons/cloudy.png"));
var cloudImageControl = new Image() {
Source = cloudBitmapImage,
Height = size,
Width = size,
};
return cloudImageControl;
}
Note that if I make the 2nd image the biggest, it becomes the one which is animated.
Any idea on how to solve this ?
Thanks in advance.
Short answer: use a Canvas control to hold images.
Full answer: I found the solution: the culprit was using the Grid control as a container for hosting my images. I think that somehow (maybe due to the center horizontal alignment set on its parent - not visible in the code showed) the Grid container was adding margins to my images which resulted in weird behaviors, as described in the question.
To solve this, I added a Canvas control to hold my images and added this Canvas as a child of the Grid container.
var container = new Grid();
var compositor = ElementCompositionPreview.GetElementVisual(container).Compositor;
var containerVisual = compositor.CreateContainerVisual();
var canvas = new Canvas();
// ------
// CLOUDS
// ------
var cloudImage1 = CreateCloudImage(60);
var cloudVisual1 = ElementCompositionPreview.GetElementVisual(cloudImage1);
var cloudImage2 = CreateCloudImage(70);
var cloudVisual2 = ElementCompositionPreview.GetElementVisual(cloudImage2);
var cloudImage3 = CreateCloudImage(50);
var cloudVisual3 = ElementCompositionPreview.GetElementVisual(cloudImage3);
canvas.Children.Add(cloudImage1);
canvas.Children.Add(cloudImage2);
canvas.Children.Add(cloudImage3);
containerVisual.Children.InsertAtTop(cloudVisual1);
containerVisual.Children.InsertAtTop(cloudVisual2);
containerVisual.Children.InsertAtTop(cloudVisual3);
// ----------
// ANIMATIONS
// ----------
var offsetAnimation = CreateOffsetAnimation(compositor, 50, 6);
cloudVisual1.StartAnimation("Offset.x", offsetAnimation);
offsetAnimation.InsertKeyFrame(1f, -60);
cloudVisual2.StartAnimation("Offset.x", offsetAnimation); // work
offsetAnimation.InsertKeyFrame(1f, 100);
cloudVisual3.StartAnimation("Offset.x", offsetAnimation); // work
ElementCompositionPreview.SetElementChildVisual(canvas, containerVisual);
container.Children.Add(canvas);
return container;
(Replacing the Grid container with a Canvas container should work too).

WPF FixedPage with same instance of UserControl

First off, I'm not sure if I phrased the title correctly. There are UserControls which are added via a ViewModel and I find them by searching the VisualTree and add them to a ObservableCollection<Grid>. What I would like to do is Print each instance of the UserControl that I retrieve from the VisualTree into a FixedDocument but with each UserControl being on a single page until it fills that page and moves on to the next page.
Here is the code for my current Print Button Click Event:
private async void btnPrint_Click(object sender, RoutedEventArgs e)
{
try
{
if (tabMain.Items.Count > 0)
{
tab = new TabLayout();
tab.Payslip = new ObservableCollection<Grid>();
foreach (var grid in FindVisualChildren<Grid>(this))
{
if (grid.Name == "pdfFile")
{
tab.Payslip.Add(grid);
}
}
FrameworkElement toPrint = new FrameworkElement();
PrintDialog printDialog = new PrintDialog();
PrintCapabilities capabilities = printDialog.PrintQueue.GetPrintCapabilities(printDialog.PrintTicket);
Size pageSize = new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight);
Size visibleSize = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
FixedDocument fixedDoc = new FixedDocument();
StackPanel panel = new StackPanel(); //was trying to stack them in a stackpanel first but it threw an exception about same instance of usercontrol blah blah...
foreach (var doc in tab.Payslip.ToList())
{
double yOffset = 0;
doc.Measure((new Size(double.PositiveInfinity, double.PositiveInfinity)));
doc.Arrange(new Rect(new Point(0, 0), doc.DesiredSize));
Size size = doc.DesiredSize;
while (yOffset < size.Height)
{
VisualBrush vb = new VisualBrush(doc);
vb.Stretch = Stretch.None;
vb.AlignmentX = AlignmentX.Left;
vb.AlignmentY = AlignmentY.Top;
vb.ViewboxUnits = BrushMappingMode.Absolute;
vb.TileMode = TileMode.None;
vb.Viewbox = new Rect(0, yOffset, visibleSize.Width, visibleSize.Height);
FixedPage page = new FixedPage();
PageContent pageContent = new PageContent();
((IAddChild)pageContent).AddChild(page);
fixedDoc.Pages.Add(pageContent);
page.Width = fixedDoc.DocumentPaginator.PageSize.Width;
page.Height = fixedDoc.DocumentPaginator.PageSize.Height;
Canvas canvas = new Canvas();
FixedPage.SetLeft(canvas, capabilities.PageImageableArea.OriginWidth);
FixedPage.SetTop(canvas, capabilities.PageImageableArea.OriginHeight);
canvas.Width = visibleSize.Width;
canvas.Height = visibleSize.Height;
canvas.Background = vb;
page.Children.Add(canvas);
yOffset += visibleSize.Height;
}
}
//printDialog.PrintDocument(fixedDoc.DocumentPaginator, "");
ShowPrintPreview(fixedDoc);
}
}
catch(Exception ex)
{
var exceptionDialog = new MessageDialog
{
Message = { Text = ex.ToString() }
};
await DialogHost.Show(exceptionDialog, "RootDialog");
}
}
This is how it looks when I try printing three Tabs (UserControls are hosted in Tabs):
As you can see here it prints three separate pages
I want all three on one page and if I print 10 tabs, then it should fill the first page and go on to the next page.
The last time I asked a similar question I was questioned on whether I wrote the code. Bits and pieces of this code came from similar FixedDocument questions on StackOverflow but it has been edited to the point where it actually works for me. So yes I know that the FixedPage reference inside the foreach is whats causing the creation of the three separate pages and I do understand the code.
Summary:
What I want to know is how to get the UserControls from each Tab, onto a single page until its full, without getting the "Specified element is already the logical child of another element. Disconnect it first." error.
I ended up using ItemsControl to hold my ObservableCollection of Controls as a list and print from the ItemsControl using this XpsDocumentWriter method.
public void PrintItemsTo(ItemsControl ic, String jobName)
{
PrintDialog dlg = new PrintDialog();
dlg.UserPageRangeEnabled = true;
if (dlg.ShowDialog().GetValueOrDefault())
{
PageRange range = dlg.PageRange;
// range check - user selection starts from 1
if (range.PageTo > ic.Items.Count)
range.PageTo = ic.Items.Count;
dlg.PrintQueue.CurrentJobSettings.Description = jobName;
XpsDocumentWriter xdw = PrintQueue.CreateXpsDocumentWriter(dlg.PrintQueue);
if (dlg.UserPageRangeEnabled == false || range.PageTo < range.PageFrom)
WriteAllItems(ic, xdw);
else
WriteSelectedItems(ic, xdw, range);
}
}
private void WriteAllItems(ItemsControl ic, XpsDocumentWriter xdw)
{
PageRange range = new PageRange(1, ic.Items.Count);
WriteSelectedItems(ic, xdw, range);
}
private void WriteSelectedItems(ItemsControl ic, XpsDocumentWriter xdw, PageRange range)
{
IItemContainerGenerator generator = ic.ItemContainerGenerator;
// write visuals using a batch operation
VisualsToXpsDocument collator = (VisualsToXpsDocument)xdw.CreateVisualsCollator();
collator.BeginBatchWrite();
if (WritePageRange(collator, generator, range))
collator.EndBatchWrite();
}
private bool WritePageRange(VisualsToXpsDocument collator, IItemContainerGenerator generator, PageRange range)
{
// Get the generator position of the first data item
// PageRange reflects user's selection starts from 1
GeneratorPosition startPos = generator.GeneratorPositionFromIndex(range.PageFrom - 1);
// If PageFrom > PageTo, print in reverse order
// UPDATE: never occurs; PrintDialog always 'normalises' the PageRange
GeneratorDirection direction = (range.PageFrom <= range.PageTo) ? GeneratorDirection.Forward : GeneratorDirection.Backward;
using (generator.StartAt(startPos, direction, true))
{
for (int numPages = Math.Abs(range.PageTo - range.PageFrom) + 1; numPages > 0; --numPages)
{
bool newlyRealized;
// Get or create the child
UIElement next = generator.GenerateNext(out newlyRealized) as UIElement;
if (newlyRealized)
{
generator.PrepareItemContainer(next);
}
// The collator doesn't like ContentPresenters and produces blank pages
// for pages 2-n. Finding the child of the ContentPresenter and writing
// that solves seems to solve the problem
if (next is ContentPresenter)
{
ContentPresenter presenter = (ContentPresenter)next;
presenter.UpdateLayout();
presenter.ApplyTemplate(); // not sure if this is necessary
DependencyObject child = VisualTreeHelper.GetChild(presenter, 0);
if (child is UIElement)
next = (UIElement)child;
}
try
{
collator.Write(next);
}
catch
{
// if user prints to Microsoft XPS Document Writer
// and cancels the SaveAs dialog, we get an exception.
return false;
}
}
}
return true;
}
PrintItemsTo(), displays a PrintDialog to print to any printer while WriteAllItems displays a Dialog to save as PDF.

How can I see 2 Series over my chart on the same graph when values are different (very high and very low)

I define 2 Series (I am using Telerik) that represent my Network Traffic rate (MBit/sec and Packet/sec):
AreaSeries series;
AreaSeries series2;
series = new AreaSeries();
radChartView1.Series.Add(series);
series.BorderColor = Color.SteelBlue;
series.BackColor = Color.FromArgb(20, Color.SkyBlue);
series.BorderWidth = 1;
series.HorizontalAxis.ShowLabels = false;
series.VerticalAxis.ShowLabels = false;
series2 = new AreaSeries();
radChartView1.Series.Add(series2);
series2.BorderColor = Color.Gray;
series2.BackColor = Color.FromArgb(20, Color.Gray);
series2.BorderWidth = 1;
series2.HorizontalAxis.ShowLabels = false;
series2.VerticalAxis.ShowLabels = false;
My chart received the real time data via Timer:
private void timerStatistics_Tick(object sender, EventArgs e)
{
try
{
if (series.DataPoints.Count > 40)
series.DataPoints.RemoveAt(0);
series.DataPoints.Add(new Telerik.Charting.CategoricalDataPoint(AdapterStatistics.BitsPerSecond * 0.000001));
if (series2.DataPoints.Count > 40)
series2.DataPoints.RemoveAt(0);
series2.DataPoints.Add(new Telerik.Charting.CategoricalDataPoint(AdapterStatistics.PacketsPerSecond));
}
catch (Exception)
{ }
}
And my problem is that because my 2 values are very different i only can see one of my Series (usually Packet/sec) because for example MBit/sec get the value 1.4 and Packet/sec get the value 200 so from my chart i can see the biggest value, the lowest value is so small that it cannot be seen (see my screenshot inside the red rectangle a very small blue line... ):
How to fix it?
Not sure how it applies to Telerik controls, but in the regular chart control, you can have 2 x-axes and 2 y-axes, and assign different series to use different axes. Thus, you could do something like:
series.YAxisType = AxisType.Primary;
series2.YAxisType = AxisType.Secondary;
Axis yaxis1 = chart.ChartAreas[0].AxisY;
Axis yaxis2 = chart.ChartAreas[0].AxisY2;
yaxis1.Maximum = 1e6;
yaxis2.Maximum = 1e3;
Looks like it's very similar using Telerik controls, taken straight from the Telerik website (http://www.telerik.com/help/winforms/chartview-axes-multiple-axes.html):
LinearAxis verticalAxis1 = new LinearAxis();
verticalAxis1.AxisType = AxisType.Second;
LinearAxis verticalAxis2 = new LinearAxis();
verticalAxis2.AxisType = AxisType.Second;
verticalAxis2.HorizontalLocation = AxisHorizontalLocation.Right;
series.HorizontalAxis = horizontalAxis;
series.VerticalAxis = verticalAxis1;
series2.HorizontalAxis = horizontalAxis;
series2.VerticalAxis = verticalAxis2;

Printing more than one page for control

This code will only print one page but the height of the control exceeds the page height and therefore will need to be printed on a second page. I have been doing some investigation as to what will enable the control to proceed onto another page. I got as far as DocumentPaginator and fiddled with the size making it bigger and smaller than the sz variable but no difference. Any Ideas as to what controls a creation of a new page? does the size relate to pagination?
private void Print()
{
var pd = new PrintDialog();
var document = new FixedDocument();
var fixedPage = new FixedPage();
var pageContent = new PageContent();
System.Printing.PrintCapabilities capabilities = pd.PrintQueue.GetPrintCapabilities(pd.PrintTicket);
System.Windows.Size sz = new System.Windows.Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
MarSheetReport mar = new MarSheetReport();
document.DocumentPaginator.PageSize = sz;
Transform originalScale = fixedPage.LayoutTransform;
//get selected printer capabilities
fixedPage.LayoutTransform = new ScaleTransform(0.2823293807641634 + 0.2498215560314061, 0.2823293807641634 + 0.2498215560314061);
fixedPage.Width = sz.Width;
fixedPage.Height = sz.Height;
// Add visual, measure/arrange page.
fixedPage.Children.Add(mar.o);
fixedPage.Measure(sz);
fixedPage.Arrange(new System.Windows.Rect(new System.Windows.Point(capabilities.PageImageableArea.OriginWidth, capabilities.PageImageableArea.OriginHeight), sz));
fixedPage.UpdateLayout();
//fixedPage.LayoutTransform = originalScale;
((IAddChild)pageContent).AddChild(fixedPage);
document.Pages.Add(pageContent);
pd.PrintDocument(document.DocumentPaginator, "My Document");
}
Here is one way of doing it i.e creating the bitmap from the visual and breaking it on multiple pages.
http://www.codeproject.com/Articles/339416/Printing-large-WPF-UserControls

Categories