Inserting a new page after each generated page - c#

I'm generating a .pdf file using the iText 7 library. And I would like to basically duplicate every page, preferably using a PdfDocumentEvent.END_PAGE EventHandler. Because I would like to have each duplicate page, alternating after each other with a different header/footer.
The following image will illustrate the desired result:
I realise that this desired result can probably be achieved by generating a single instance first and then using another reader and writer to duplicate each page and add the header/footer with some kind of stamper.
But because of different requirements I would like to solve this while generating. But it might just not be possible the way I want to solve this.
I've tried a couple of things already but I get some strange behaviours and I can't seem to figure this out by myself. I'll add some of this code, which hopefully also helps explaining the issue further.
This is the example code to generate the document:
protected void ManipulatePdf(string dest)
{
var writer = new PdfWriter(dest);
var pdfDoc = new PdfDocument(writer);
var doc = new Document(pdfDoc, PageSize.A4);
//Custom EventHandler that adds a page footer
pdfDoc.AddEventHandler(PdfDocumentEvent.END_PAGE, new CustomEndPageEventHandler(pdfDoc));
//The actual generating of the document
for (int i = 0; i < 3; i++)
{
doc.Add(new Paragraph("Test " + (i + 1)));
if (i != 2)
{
doc.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
}
/* I would like to add that in reality it is not known exactly when page-breaks will occur */
doc.Close();
}
And below is the code I have so far for adding the extra pages. Please note that this code is not correct and gives some strange behaviour.
There is no code for alternating the header/footer yet, this code was simply supposed to insert duplicates between each page.
I'll also add another image below with the generated result, but this is not what is expected.
protected class CustomEndPageEventHandler : IEventHandler
{
protected PdfFormXObject placeholder;
protected float side = 20;
protected float x = 300;
protected float y = 25;
protected float space = 4.5f;
protected float descent = 3;
public CustomEndPageEventHandler(PdfDocument pdf) {
placeholder =
new PdfFormXObject(new Rectangle(0, 0, side, side));
}
public void HandleEvent(Event currentEvent)
{
if(!(currentEvent is PdfDocumentEvent docEvent)) return;
var pdf = docEvent.GetDocument();
var page = docEvent.GetPage();
var pageNumber = pdf.GetPageNumber(page);
var pageSize = page.GetPageSize();
var pdfCanvas = new PdfCanvas(
page.GetLastContentStream(), page.GetResources(), pdf);
var canvas = new Canvas(pdfCanvas, pdf, pageSize);
Paragraph p = new Paragraph()
.Add($"{pageNumber}");
canvas.ShowTextAligned(p, x, y, TextAlignment.RIGHT);
pdfCanvas.AddXObject(placeholder, x + space, y - descent);
pdfCanvas.Release();
//Here starts the custom code to insert a duplicate
if ((pageNumber % 2) != 0 && pageNumber < 6)
{
pdf.AddPage(pageNumber + 1, page);
pdf.AddNewPage(pageNumber+2);
}
}
public void WriteTotal(PdfDocument pdf) {
var canvas = new Canvas(placeholder, pdf);
canvas.ShowTextAligned($"/{pdf.GetNumberOfPages()}",0, descent, TextAlignment.LEFT);
}
}
The above code results into something like this. I've tried a couple of different variations of this, when you for example when you remove the line pdf.AddNewPage(pageNumber+2); then the page with Test 2 and Test 3 are overlapping each other.
I'm actually stuck on this and would really like to know if this can be done in the PdfDocumentEvent.END_PAGE EventHandler, so I'm looking forward to seeing what you can come up with.

Related

How to rotate PDF content in iText 7 Core?

I am trying to create a PDF with a mixture of Portrait and Landscape/Seascape pages.
I have rotated the pages to be Seascape where required but the content is still being output as portrait. I am reading the data in from an HTML file using iText's pdfHTML solution.
Below is some sample code I've used to do this.
var elements = HtmlConverter.ConvertToElements(htmlSource, props);
var pdf = new PdfDocument(new PdfWriter(pdfDest));
var pageOrientationHandler = new PageOrientationsEventHandler();
pdf.AddEventHandler(PdfDocumentEvent.INSERT_PAGE, pageOrientationHandler);
var document = new Document(pdf, PageSize.A4);
document.SetFontProvider(new DefaultFontProvider());
document.Add(new Paragraph(LOREM_IPSUM));
pageOrientationHandler.SetSeascape();
document.Add(new AreaBreak());
foreach (IElement element in elements)
{
document.Add((Div)element);
}
pageOrientationHandler.SetSeascape();
document.Add(new AreaBreak());
foreach (IElement element in elements)
{
document.Add((Div)element);
}
pageOrientationHandler.SetPortrait();
document.Add(new AreaBreak());
document.Add(new Paragraph(LOREM_IPSUM));
document.Close();
The PageOrientationsEventHandler class looks like this:
public class PageOrientationsEventHandler : IEventHandler
{
private PdfNumber _orientation = new(0);
public void SetPortrait()
{
_orientation = new PdfNumber(0);
}
public void SetLandscape()
{
_orientation = new PdfNumber(90);
}
public void SetSeascape()
{
_orientation = new PdfNumber(270);
}
public void HandleEvent(Event currentEvent)
{
PdfDocumentEvent pdfDocumentEvent = (PdfDocumentEvent)currentEvent;
var page = pdfDocumentEvent.GetPage();
page.Put(PdfName.Rotate, _orientation);
}
}
What I would like is for pages 1 and 4 to be portrait orientation with portrait content (i.e. a "normal" looking page with text) and pages 2 and 3 to be Seascape with Seascape content.
What I am seeing in the generated PDF is:
This current incorrect layout
But what I want to see is:
Something like this
Can somebody tell me what I am doing wrong?
NB: These images are just showing pages 1 and 2.
EDIT:
I think I've managed to work something out, but it doesn't feel like the correct way to go about it.
I've changed the code that adds the table from the HTML to this:
foreach (IElement element in elements)
{
document.Add((Div)element);
Div div = (Div)element;
div.SetRotationAngle(_radiansConverter.ToRadians(270));
div.SetWidth(PAGE_HEIGHT);
div.SetHorizontalAlignment(iText.Layout.Properties.HorizontalAlignment.RIGHT);
}
I am still using the page orientation handler to rotate the actual page, but also setting the table to rotate by 270 degrees, and then setting the width of the table to be slightly less than the height of the page.
Which is better than what I was after from the second image I posted (I messed up what I thought the orientation of the table should be).
Also the print preview for 2 x 2 looks like what I am after with the top of the table being aligned along the left side rather than the right:

How to add an image to a cell

We have Word Interop based process in place to generate invoices. This process, which runs for years, loads a pre-built docx template file and populates with the data. Both header and footer of that template have a table each. Currently the code below populates the text in a given cell of the footer
protected override void OnEnterRow()
{
Try(() => _parent.OLE_WordCell.Value = _parent.OLE_WordTable.Value.Cell(u.ToInt(I_RowNumber), u.ToInt(I_CellNumber)));
Try(() => _parent.OLE_WordCell.Value.Range.Text = u.TrimEnd(I_CellText));
Try(() => _parent.OLE_WordCell.Value.Range.Font.Bold = u.ToInt(I_CellBold));
}
I need to add an image to the footer of the invoice, based on a certain, simple logic. I have seen multiple examples on this and other sites how to do it. Though in my case it does add the image, but not in the footer and I will much appreciate any help.
Before doing any significant change, I wanted to simply show the image instead of the text, so I have disabled the lines above and applied the following code:
Try(() => _parent.OLE_WordCell.Value = _parent.OLE_WordTable.Value.Cell(u.ToInt(I_RowNumber), u.ToInt(I_CellNumber)));
Try(() => _parent.OLE_WordCell.Value.Range.InlineShapes.AddPicture(I_FilePath).ConvertToShape());
It added the image in a middle of the invoice (NOT the footer). Since I have tried various codes and below is the latest, but to no avail.
This code placed one big image all over the invoice
var _cell = _parent.OLE_WordTable.Value.Cell(u.ToInt(I_RowNumber), u.ToInt(I_CellNumber));
var _range = _cell.Range;
var _shape = _range.InlineShapes.AddPicture(I_FilePath.Value.ToString().Trim()).ConvertToShape();
so I have added height and width:
var _cell = _parent.OLE_WordTable.Value.Cell(u.ToInt(I_RowNumber), u.ToInt(I_CellNumber));
var _range = _cell.Range;
var _shape = _range.InlineShapes.AddPicture(I_FilePath.Value.ToString().Trim()).ConvertToShape();
_shape.HeightRelative = 8f;
_shape.WidthRelative = 10f;
It succeeded to reduce the size, but the image remained in a middle of the invoice. Then I have added Select() as I have seen someone advised. But that placed the image in the left top corner of the page.
var _cell = _parent.OLE_WordTable.Value.Cell(u.ToInt(I_RowNumber), u.ToInt(I_CellNumber));
_cell.Select();
var _range = _cell.Range;
var _shape = _range.InlineShapes.AddPicture(I_FilePath.Value.ToString().Trim()).ConvertToShape();
_shape.HeightRelative = 8f;
_shape.WidthRelative = 10f;
Anything else I shall try, please?

Is the PdfWriter.getverticalposition() stopped in Itext 7?

I am trying to add a IBlockElement at the end of the last page in the Itext 7 version
my approach is
After writing all the elements to the pdf document get the vertical
position from the writer with writer.getVerticalPosition()
Calculate the available space on the current page by using the
bottomMargin as a reference.
If space is less than the space required then add another blank page
Create and insert a container of fixed height with text vertical alignment to
bottom Add IBlockElement content to the container
However when I am using it
var PdfWriter= new PdfWriter(memoryStream, writerProperties)
PdfWriter.getverticalposition()
I am getting error:
PdfWriter writer does not contain a definition of
getverticalposition(). No method getverticalposition() accept first
argument of type PdfWriter are you missing assembly reference?
Do I have to change the assembly reference or something?
EDIT DATE: 10-Nov-2018
private class BottomBlockElement : Div
{
public BottomBlockElement(IBlockElement wrapping)
{
base.SetKeepTogether(true);
base.Add(wrapping);
//add(wrapping);
//setKeepTogether(true);
}
override protected IRenderer MakeNewRenderer()
{
return new BottomBlockRenderer(this);
}
}
private class BottomBlockRenderer : DivRenderer
{
public BottomBlockRenderer(BottomBlockElement modelElement) : base(modelElement)
{
}
override public LayoutResult Layout(LayoutContext layoutContext)
{
LayoutResult result = base.Layout(layoutContext);
if (result.GetStatus() == LayoutResult.FULL)
{
float leftoverHeight = result.GetOccupiedArea().GetBBox().GetBottom() - layoutContext.GetArea().GetBBox().GetBottom();
Move(0, -leftoverHeight);
return new LayoutResult(result.GetStatus(), layoutContext.GetArea(), result.GetSplitRenderer(), result.GetOverflowRenderer());
}
else
{
return result;
}
}
public override IRenderer GetNextRenderer()
{
return new BottomBlockRenderer((BottomBlockElement)modelElement);
}
}
But still the text is overlapping
iText7 still allows you to get analogue of current vertical position, but as #mkl noted in his comment, there are are lot of nuances with this concept because iText7 support much more complex layout strategies.
In general you should try to think out of the box sometimes when migrating from iText5 to iText7 - there are a ton of things you can do much easier with iText7, including your use case of adding content to the bottom of last page.
You can use iText7 analogue of CSS absolute positioning to add content to the bottom. To do that, you have to specify position property as well as bottom offset. There is still no fancy public API to do that because the functionality is still under constant improvement, but you can do that with setting necessary properties manually. All you need to do is add these lines:
glueToBottom.setProperty(Property.POSITION, LayoutPosition.ABSOLUTE);
glueToBottom.setProperty(Property.BOTTOM, 0);
To demonstrate the result, let's add some content first and then the block element to the end of the last page.
Document document = new Document(pdfDocument);
for (int i = 0; i < 40; i++) {
document.add(new Paragraph("Hello " + i));
}
IBlockElement glueToBottom = new Paragraph("Hi, I am the bottom content")
.setFontSize(25)
.setWidth(UnitValue.createPercentValue(100))
.setBackgroundColor(ColorConstants.RED)
.setTextAlignment(TextAlignment.CENTER);
glueToBottom.setProperty(Property.POSITION, LayoutPosition.ABSOLUTE);
glueToBottom.setProperty(Property.BOTTOM, 0);
document.add(glueToBottom);
document.close();
You will get something like this at page 2 (last page), which matches your description exactly:
UPD: The algorithm in question was updated and now contains logic of inserting a new page for the content at the bottom of the page if the content at the bottom would overlap with existing content had the page not been inserted. To achieve same behavior you would need a slight modification of the code above: instead of setting positioning properties via setProperty let's implement our own element and corresponding renderer and wrap your block element into this implementation. We will add our element now as follows:
document.add(new BottomBlockElement(glueToBottom));
The implementations are straightforward - just move the element to the bottom between laying it out and drawing. This is a bit verbose in code but still quite clear:
private static class BottomBlockElement extends Div {
public BottomBlockElement(IBlockElement wrapping) {
super();
add(wrapping);
setKeepTogether(true);
}
#Override
protected IRenderer makeNewRenderer() {
return new BottomBlockRenderer(this);
}
}
private static class BottomBlockRenderer extends DivRenderer {
public BottomBlockRenderer(BottomBlockElement modelElement) {
super(modelElement);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
LayoutResult result = super.layout(layoutContext);
if (result.getStatus() == LayoutResult.FULL) {
float leftoverHeight = result.getOccupiedArea().getBBox().getBottom() - layoutContext.getArea().getBBox().getBottom();
move(0, -leftoverHeight);
return new LayoutResult(result.getStatus(), layoutContext.getArea(), result.getSplitRenderer(), result.getOverflowRenderer());
} else {
return result;
}
}
#Override
public IRenderer getNextRenderer() {
return new BottomBlockRenderer((BottomBlockElement) modelElement);
}
}
Now our main part looks like this:
Document document = new Document(pdfDocument);
for (int i = 0; i < 58; i++) {
document.add(new Paragraph("Hello " + i));
}
IBlockElement glueToBottom = new Paragraph("Hi, I am the bottom content")
.setFontSize(25)
.setWidth(UnitValue.createPercentValue(100))
.setBorder(new SolidBorder(ColorConstants.RED, 1))
.setTextAlignment(TextAlignment.CENTER);
document.add(new BottomBlockElement(glueToBottom));
document.close();
And the result is the last page containing only the bottom block if the previous one does not have enough space:

PrintDocument using multiple page sizes

Working in .NET 3.5.
Summary:
Trying to replicate functionality of an existing third party component, which breaks in Windows 7.
Until now the user could select a bunch of image files to print, specify a page size for each image and then send them off to print all in one go. I am in dire need of a conceptual explanation of how to go about printing switching the page size on the fly when printing each page.
Details
So far I have figured out how to print multiple images all with the same page size. I use a list of images and use a PrintDocument object, setting the HasMorePages property of the PrintPageEventArgs to true until I reach the end of the list.
Here's a class I quickly threw together to test this:
public partial class Form1 : Form
{
private List<Image> images { get; set; }
private PrintDocument printDocument { get; set; }
public Form1()
{
InitializeComponent();
this.images = new List<Image>();
this.images.Add(Image.FromFile(#"C:\test60.bmp"));
this.images.Add(Image.FromFile(#"C:\SuperLargeTest.jpg"));
this.printDocument = new PrintDocument()
{
PrinterSettings = new PrinterSettings()
};
this.printDocument.PrintPage += printDocument_PrintPage;
}
private void printDocument_PrintPage(object sender, PrintPageEventArgs e)
{
Graphics g = e.Graphics;
e.PageSettings.PaperSize = this.paperSizes[this.currentImageIndex];
RectangleF marginBounds = e.MarginBounds;
RectangleF printableArea = e.PageSettings.PrintableArea;
int availableWidth = (int)Math.Floor(printDocument.OriginAtMargins ? marginBounds.Width : (e.PageSettings.Landscape ? printableArea.Height : printableArea.Width));
int availableHeight = (int)Math.Floor(printDocument.OriginAtMargins ? marginBounds.Height : (e.PageSettings.Landscape ? printableArea.Width : printableArea.Height));
g.DrawRectangle(Pens.Red, 0, 0, availableWidth - 1, availableHeight - 1);
g.DrawImage(this.images[currentImageIndex], printableArea);
e.HasMorePages = ++currentImageIndex < this.images.Count();
}
private void button1_Click(object sender, EventArgs e)
{
this.printDocument.OriginAtMargins = false;
this.printDocument.Print();
}
}
The thing that I really can't figure out is how to go about changing the page size for, say, the second image.
If I wanted the first image to print in A4 and then the second one to print on A3, how would I go about doing that?
I found this SO question here which seemed to suggest changing the PageSize in the PrintPageEventArgs, but had no joy there.
I also tried to use the QueryPageSettingsEventArgs event and set the PageSettings there, but that didn't seem to work either...
What I would like to achieve is print multiple pages of different size as a single document. Any suggestions, links, explanations, sample code would be very much appreciated.
Anything in C# or VB.NET is fine.
That's work for me too.
Translated to C#:
private bool SetPaperSize(PrintDocument pd, PaperKind nKind)
{
foreach(System.Drawing.Printing.PaperSize ps in pd.PrinterSettings.PaperSizes)
{
if (ps.Kind == nKind)
{
pd.DefaultPageSettings.PaperSize = ps;
return true;
}
}
return false;
}
In VB.NET .. You can use this Sub ..
DocPrint is PrintDocument var
Sub SetPaperSize(ByVal nKind As PaperKind)
Dim ps As PaperSize
For ix As Integer = 0 To DocPrint.PrinterSettings.PaperSizes.Count - 1
If DocPrint.PrinterSettings.PaperSizes(ix).Kind = nKind Then
ps = DocPrint.PrinterSettings.PaperSizes(ix)
DocPrint.DefaultPageSettings.PaperSize = ps
End If
Next
End Sub
Hope this help ..
If you want all the pages to appear as one job (in short avoid being interleaved with other jobs), you can set the page size for the next page inside the PrintPage event handler by changing the default page size of the PrintDocument object.

How to identify end of page is reached in pdf file using itextsharp

Hi
I am using itextsharp to generate a pdf file.I am placing a backgound image on it and want that image on all the pages .But when the first page is completed the text move to next page automatically so the image is not appearing on the new page.
Is there a way to identify the end of page so that we can add a new page and then set the image first so will appear in background and then can add the remaining text.
All is i want a image in background on all the pages of pdf file.
I suggest you use a page event:
myWriter.setPageEvent(new BackgroundPageEvent(backgroundImage));
class BackgroundPageEvent extends PdfPageEventHelper {
Image backgroundImage = null;
public BackgroundPageEvent( Image img ) {
backgroundImage = img;
}
public void onStartPage(PdfWriter writer, Document doc) {
PdfContentByte underContent = writer.getDirectContentUnder();
underContent.addImage(backgroundImage);
}
}
With the above code, backgroundImage will be added to the "under content" as each page is created. No need to worry about when to add it yourself... iText will figure that out for you, and the first thing in the underContent of each page will be your image. You might need to play around with the various overrides of addImage to get the size you want.
I believe you can also query doc for the current page size if it varies in your program. If not, you should be able to create the image you pass in with an absolute position/scale (which may be what you're doing already).
PdfPageEvent has a number of other events you can override. PdfPageEventHelper covers all the bases with "no ops" so you can just override the event[s] you want:
OnStartPage
OnEndPage
OnCloseDocument
OnParagraph
OnParagraphEnd
OnChapter
OnChapterEnd
OnSection
OnSectionEnd
OnGenericTag
Generic tag is actually Really Handy. You can give a generic tag (a string) to just about anything within your document, and your OnGenericTag override will be called with the rect that was used to draw whatever it was you tagged. All kinds of spiffy possibilities.
Just check PdfWriter.PageNumber property like this:
using (FileStream fs = File.Create("test.pdf"))
{
Document document = new Document(PageSize.A4, 72, 72, 72, 72);
PdfWriter writer = PdfWriter.GetInstance(document, fs);
document.Open();
int pageNumber = -1;
for (int i = 0; i < 20; i++)
{
if (pageNumber != writer.PageNumber)
{
// Add image
pageNumber = writer.PageNumber;
}
// Add something else
}
document.Close();
}

Categories

Resources