How to rotate PDF content in iText 7 Core? - c#

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:

Related

Inserting a new page after each generated page

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.

Adding a link with iText7 to the header or footer of a PDF

I am currently trying to add a link to the header of footer of a pdf document however the library gives the following error System.IndexOutOfRangeException: 'Requested page number 0 is out of bounds.' when adding the link to the header using the IText7 library.
Adding the same object to the body of the page works fine.
Surrounding the code with a try catch results in the following:
I couldn't find any code examples online regarding this problem in IText7, the solutions in ITextSharp are not applicable anymore.
My question is how do I add a link to an external website to the header of the pdf? Is the current behavior a bug in the library or intended?
I am using the following code:
The main method, loading the html, initializing the document and adding the object to the header and the main page.
public void Convert()
{
// Initialize template
IList<IElement> templateElements = HtmlConverter.ConvertToElements(File.ReadAllText("FooterTest.html"));
// Initialize document
PdfWriter pdfWriter = new PdfWriter("Output.pdf");
PdfDocument pdfDocument = new PdfDocument(pdfWriter);
Document document = new Document(pdfDocument);
document.SetTopMargin(100);
// Adding the header object to the header and the main body
pdfDocument.AddEventHandler(PdfDocumentEvent.START_PAGE, new PdfHeader((IBlockElement)templateElements[0], document));
document.Add((IBlockElement)templateElements[0]);
document.Close();
}
Event handler class responsible for adding the object to the header. The code gives the error mentioned above within the try-catch
public class PdfHeader : IEventHandler
{
private readonly IBlockElement footer;
private readonly Document doc;
public PdfHeader(IBlockElement footer, Document doc)
{
this.doc = doc;
this.footer = footer;
}
public void HandleEvent(Event headerEvent)
{
PdfDocumentEvent docEvent = (PdfDocumentEvent)headerEvent;
PdfDocument pdf = docEvent.GetDocument();
PdfPage page = docEvent.GetPage();
Rectangle pageSize = page.GetPageSize();
PdfCanvas pdfCanvas = new PdfCanvas(page.GetLastContentStream(), page.GetResources(), pdf);
Rectangle rectangle = new Rectangle(
pdf.GetDefaultPageSize().GetX() + doc.GetLeftMargin(),
pdf.GetDefaultPageSize().GetTop() - 80,
page.GetPageSize().GetWidth() - doc.GetLeftMargin() - doc.GetRightMargin(),
50);
//Below is the code where the error is produced.
try
{
new Canvas(pdfCanvas, pdf, rectangle).Add(footer);
}
catch { }
}
}
The html file containing the header object (FooterTest.html loaded in the Convert() method)
<html>
<body>
<table>
<tr>
<td>
This is a some text not containing a link.
</td>
</tr>
<tr>
<td>
This text contains a link to Google to demonstrate the issue.
</td>
</tr>
</table>
</body>
</html>
This is my first question on stack overflow so any feedback on the question itself is also appreciated.
What you've encountered is not entirely a bug, however iText should definitely fail more gracefully and explanatory in this case.
The problem here, is that for the Canvas class it's actually not known what is the page on which the drawing performs. In general case Canvas is just a high level wrapper for the content drawing operations, which can be placed on any content stream (e.g. in form XObject, page content stream etc). However the link is something that is defined specifically on a page level (via link annotation).
It's reasonable easy to work around the issue. I can suggest you two approaches.
First approach is to fix the issue by overriding the CanvasRenderer:
// set the custom renderer:
Canvas canvas = new Canvas(pdfCanvas, pdf, rectangle);
canvas.setRenderer(new PageCanvasRenderer(canvas, page));
canvas.add(footer);
...
private static class PageCanvasRenderer extends CanvasRenderer {
private final PdfPage page;
public PageCanvasRenderer(Canvas canvas, PdfPage page) {
super(canvas);
this.page = page;
}
#Override
protected LayoutArea updateCurrentArea(LayoutResult overflowResult) {
if (currentArea == null) {
currentArea = new RootLayoutArea(canvas.getPdfDocument().getPageNumber(page), canvas.getRootArea().clone());
}
return currentArea;
}
}
Second approach is to use Document instance instead of the Canvas. The Document works with the pages content always, so the explained issue doesn't exist here. You can use fixed positioning to place the content in PdfHeader:
Replace the
new Canvas(pdfCanvas, pdf, rectangle).Add(footer);
with
Document document = new Document(pdf);
Div canvas = new Div().setFixedPosition(pdf.getPageNumber(page), rectangle.getLeft(), rectangle.getBottom(), rectangle.getWidth());
canvas.add(footer);
document.add(canvas);
// Don't close document itself! It would close the PdfDocument!
document.getRenderer().close();

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:

Using iTextSharp to construct header on multiple pdf pages

I'm generating a PDF file based on a record selected in my datagridview. It will consist of 3-5 pages. I created a table with 2 columns to represent my header. the first cell is left aligned and the 2nd cell is right aligned. I want this same inforamtion displayed on all pages.
After doing some googling, I saw a header.WriteSelectedRows() property which is supposed to help with that? One example was :
header.WriteSelectedRows(0, -1, doc.PageSize.GetLeft(5), doc.PageSize.GetTop(5), wri.DirectContent);
2nd was:
header.WriteSelectedRows(0, -1, doc.LeftMargin, doc.PageSize.Height - 36, wri.DirectContent);
However both resulted in just the first page having the table/header. Any ideas on what I need to fix? Thanks!
Code:
PdfPTable header = new PdfPTable(2);
header.HorizontalAlignment = Element.ALIGN_LEFT;
header.TotalWidth = doc.PageSize.Width - 20f;
header.LockedWidth = true;
Phrase cell1 = new Phrase(signal.ProformaType);
Phrase cell2 = new Phrase("text" + Environment.NewLine + "text"
+ Environment.NewLine + signal.Signal);
PdfPCell c1 = new PdfPCell(cell1);
c1.Border = iTextSharp.text.Rectangle.NO_BORDER;
c1.VerticalAlignment = iTextSharp.text.Element.ALIGN_TOP;
c1.HorizontalAlignment = iTextSharp.text.Element.ALIGN_LEFT;
header.AddCell(c1);
PdfPCell c2 = new PdfPCell(cell2);
c2.Border = iTextSharp.text.Rectangle.NO_BORDER;
c2.VerticalAlignment = iTextSharp.text.Element.ALIGN_TOP;
c2.HorizontalAlignment = iTextSharp.text.Element.ALIGN_RIGHT;
header.AddCell(c2);
header.WriteSelectedRows(0, -1, doc.LeftMargin, doc.PageSize.Height - 36, wri.DirectContent);
The PdfPTable is added to the first page only because you are adding it to the first page only. If you want to add it to every page that is created by iText, you shouldn't add the PdfPTable where you are adding it now.
Instead you should add it in the OnEndPage() method of a page event. This is explained in answers to questions such as:
How can I add Header and footer in pdf using iText in java?
how to add an image to my header in iText generated PDF?
How to handle the case in wich an iText\iTextSharp table is splitted in two pages?
...
In other words, you need to create your own implementation of the PdfPageEvent interface. The best way is to extend the PdfPageEventHelper class:
public class MyPageHeader : PdfPageEventHelper
{
PdfPTable header = ... // define header table here
public override void OnEndPage(PdfWriter writer, Document document)
{
header.WriteSelectedRows(0, -1, document.Left, document.Top, writer.DirectContent);
}
}
To make this work, you need to declare this page event before opening the Document:
PdfWriter pdfWriter = PdfWriter.GetInstance(document, pdfFileStream);
pdfWriter.PageEvent = new MyPageHeader();
document.Open();
Now, every time a new page is created, the header will be added automatically.
You may want to adapt document.Left and document.Top in the code above, because right now, it will add the table in the upper-right corner of each page, you may want to use document.Left + 36 and document.Top - 5 or something like that.
Also: make sure that there is sufficient room for the header, otherwise your header will overlap with the content you are adding straight to the Document using document.Add(). You can change the margins in the constructor of the Document class.

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