I'm trying to create a Table of Contents using MigraDoc and PDFsharp and I've gotten really close but the problem I'm currently having is that the links on the Table of Contents all take me to the very first page of the PDF. I'm trying to link them to their respective pages. PDFSharp bookmarks work fine but when trying to create a table of contents based on the merged PDF it's not working.
static void TableOfContents(PdfDocument document)
{
// Puts the Table of contents on the second page
PdfPage page = document.Pages[1];
XGraphics gfx = XGraphics.FromPdfPage(page);
gfx.MUH = PdfFontEncoding.Unicode;
// Create MigraDoc document + Setup styles
Document doc = new Document();
Styles.DefineStyles(doc);
// Add header
Section section = doc.AddSection();
Paragraph paragraph = section.AddParagraph("Table of Contents");
paragraph.Format.Font.Size = 14;
paragraph.Format.Font.Bold = true;
paragraph.Format.SpaceAfter = 24;
paragraph.Format.OutlineLevel = OutlineLevel.Level1;
// Add links - these are the PdfSharp outlines/bookmarks
// added previously when concatinating the pages
foreach (var bookmark in document.Outlines)
{
paragraph = section.AddParagraph();
paragraph.Style = "TOC";
paragraph.AddBookmark(bookmark.Title);
Hyperlink hyperlink = paragraph.AddHyperlink(bookmark.Title);
hyperlink.AddText($"{bookmark.Title}\t");
hyperlink.AddPageRefField(bookmark.Title);
}
// Render document
DocumentRenderer docRenderer = new DocumentRenderer(doc);
docRenderer.PrepareDocument();
docRenderer.RenderPage(gfx, 1);
gfx.Dispose();
}
Ideally I want it to return the file's name (which it's doing) and the page number (it's only returning the first page). This is what it's currently outputting.
Table of Contents
file name here......................... 1
file name here......................... 1
file name here......................... 1
file name here......................... 1
As I understand it, the Hyperlink and bookmark should be unique to the document.
Otherwise the link will be made to the first paragraph containing the bookmark.
I simply use a number which I increase for a simple report I make.
private void DefineTOCLine(int level, string text, Paragraph linkTo)
{
var tocIndex = (tocindex++).ToString(CultureInfo.InvariantCulture);
var paragraph = tocsection.AddParagraph();
paragraph.Style = level == 1 ? "TOC1" : "TOC2";
var hyperlink = paragraph.AddHyperlink(tocIndex);
hyperlink.AddText(text + "\t");
hyperlink.AddPageRefField(tocIndex);
linkTo.AddBookmark(tocIndex);
}
You invoke hyperlink.AddPageRefField to set a reference, but as far as I can tell you never create the MigraDoc bookmark for the target of the reference by calling MigraDoc's AddBookmark method.
MigraDoc bookmarks are different from PDF file bookmarks.
Related
I am currently designing a report for a customer and I have to place a text at the bottom of the last page. I have to do it while generating each quarter for 16k pdfs.
iText 7.1.5 is used, but will be upgraded to the latest version with the next release.
Doing it with a Footer on every page is not an option because the paragraph can have up to 14 lines of text. Adding a normal paragraph at the end of the document is also no solution because my client requested that the text is on top of the footer.
The expected result:
current generation of PDFs
PdfADocument pdf = new PdfADocument(...)
...
// handler for adding header and footer on every page
pdf.AddEventHandler(PdfDocumentEvent.END_PAGE, headerFooterHandler);
Document doc = new Document(pdf);
doc.SetTopMargin(ConversionUtility.MillimeterToPoint(48));
doc.SetLeftMargin(ConversionUtility.MillimeterToPoint(26));
doc.SetRightMargin(ConversionUtility.MillimeterToPoint(18));
doc.SetBottomMargin(ConversionUtility.MillimeterToPoint(26));
... Some customer specific code
// paragraphs and data table is added
foreach(var feeLine in feeList.Values) {
switch (feeLine.Type) {
case "U":
case "T1":
case "T2":
case "BS":
doc.Add(GenerateTextBlock(feeLine, CheckSameType(feeLine.Type, feeList, i)));
break;
case "U3":
doc.Add(GenerateTextBlock(feeLine, CheckSameType(feeLine.Type, feeList, i)));
GenerateTableBlockStart(GetColumnCount(feeList[i + 1]));
break;
default:
if (CheckEndOfTable(feeList, i)) {
var table = GenerateTableBlock(feeLine, ColumnCount, true);
doc.Add(table);
table.Complete();
} else {
GenerateTableBlock(feeLine, ColumnCount, false);
}
break;
}
}
headerFooterHandler.WritePageTotal(pdf);
doc.Close();
I would need some advise / piece of code how to find the remaining space on the last page. Placing and writing the text is no problem.
You can use the absolute positioning to position the text right where you want for the last page. All you need to know is to find the position where you want to place the paragraph. This can be done by opening any pdf in a PDF Reader such as Adobe / Foxit Reader and changing the ruler to points. Now all you need is to zoom in and find the position where you want to place the text. For example
`string dest = "destination pdf's path"
//Initialize PDF Writer
writer = new PdfWriter(dest);
//Initialize PDF Document
pdf = new PdfDocument(writer);
// Initialize document
document = new Document(pdf, PageSize.A4);
//You page text here
Paragraph p = new Paragraph("bla bla bla bla ");
document.Add(p);
//Write what ever you want to write on the page...
.
.
Paragraph footer = new Paragraph("some text")
footer.SetFixedPosition(72f, 50f, 500f);
footer.SetFontSize(6f);
document.Add(footer);
document.Close();`
As you fill the main page area only using a Document with a default DocumentRenderer, you can simply query the current area of the document renderer.
E.g. this piece of code writes into each line the bounding box of available space before drawing the line in question:
using (PdfWriter pdfWriter = new PdfWriter(#"DetermineRemainingSpace.pdf"))
using (PdfDocument pdfDocument = new PdfDocument(pdfWriter))
using (Document document = new Document(pdfDocument))
{
for (int i = 0; i < 30; i++)
{
Rectangle currentBox = document.GetRenderer().GetCurrentArea().GetBBox();
string current = string.Format(CultureInfo.InvariantCulture, "{0:F}×{1:F} from ({2:F}, {3:F}) to ({4:F}, {5:F})", currentBox.GetWidth(), currentBox.GetHeight(),
currentBox.GetLeft(), currentBox.GetBottom(), currentBox.GetRight(), currentBox.GetTop());
document.Add(new Paragraph(string.Format("{0:D2}, previously available {1}", i, current)));
}
}
The output:
Thus,
I would need some advise / piece of code how to find the remaining space on the last page. Placing and writing the text is no problem.
simply query the document renderer current area as above after all regular content has been added to the document.
I am converting html page to pdf using HtmlToPdf() of SelectPDF. Since html content is big, I am breaking it in half and creating 2 PDFs.
I am struggling to edit the total_pages in the footer to display actual total number of the pages, not only the current document; as well as page_number to display the actual page number in the context of both PDFs.
How can I assess {page_number} and {total_pages} to calculate proper values? All examples I found use PdfDocument(), not HtmlToPdf().
Dim converter As New HtmlToPdf()
Dim text As New PdfTextSection(0, 10, "Page: {page_number} of {total_pages} ")
text.HorizontalAlign = PdfTextHorizontalAlign.Center
converter.Footer.Add(text)
I am tagging both C# and VB since SelectPDF is for both languages, and relevant sample from either one will work for me. Thank you
Today I've stumbled upon the same issue and I have found a work-around for the problem. The converter was able to show page numbers for it's the generated document but can't be aware of multiple generated files (you can't access the page properties) so all my pages I concatenated were showing Page 1 of 1.
First I define one PdfDocument (see it as the main document) and I use HtmlToPdf to append html converted files to this main document.
// Create converter
converter = new HtmlToPdf();
PdfTextSection text = new PdfTextSection(0, 10, "Page: {page_number} of {total_pages} ", new Font("Arial", 8));
text.HorizontalAlign = PdfTextHorizontalAlign.Right;
converter.Footer.Add(text);
// Create main document
pdfDocument = new PdfDocument();
Then I add pages (from html) using this method
public void AddPage(string htmlPage)
{
PdfDocument doc = converter.ConvertHtmlString(htmlPage);
pdfDocument.Append(doc);
converter.Footer.TotalPagesOffset += doc.Pages.Count;
converter.Footer.FirstPageNumber += doc.Pages.Count;
}
This results in correct page numbers for the main document. The same trick could be used for splitting files and page numbers over multiple documents like you described.
EDIT: In case you don't see any page numbering using the HtmlToPdf converter, don't forget to set following property:
converter.Options.DisplayFooter = true;
There is an open source library called itextsharp that will help get total page count.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using iTextSharp.text.pdf;
using iTextSharp.text.xml;
namespace GetPages_PDF
{
class Program
{
static void Main(string[] args)
{
// Right side of equation is location of YOUR pdf file
string ppath = "C:\\aworking\\Hawkins.pdf";
PdfReader pdfReader = new PdfReader(ppath);
int numberOfPages = pdfReader.NumberOfPages;
Console.WriteLine(numberOfPages);
Console.ReadLine();
}
}
}
Then you can stamp text also on the page but you will need to add the location to where it needs to go.
link: http://crmhunt.com/how-to-modify-pdf-file-using-itextsharp/
hope this helps in some way.
You should use the following properties:
FirstPageNumber - Controls the page number for the first page being
rendered.
TotalPagesOffset - Controls the total number of pages
offset in the generated pdf document.
More details here:
http://selectpdf.com/html-to-pdf/docs/html/HtmlToPdfHeadersAndFooters.htm
The answers above did not work for me as I was trying to merge multiple PDFs with different orientations. bonnoj's answer did add page numbers but they were incorrect and I couldn't find a way to correct them. So I took a different approach - I created a PDF, then for each HTML page I added a pdfPage and then added a PdfHtmlElement to that page. Finally I loop over the pages and add a custom footer to each page. This may not be the most efficient way to do this but it's the only way that I could find that added the footer in the correct place when mixing portrait and landscape pages. Hopefully it will save somebody else spending hours playing with different properties.
var pdfDocument = new PdfDocument(PdfStandard.Full);
foreach (var (html, pdfPageOrientation) in pages)
{
var page = pdfDocument.AddPage(PdfCustomPageSize.A4, new PdfMargins(marginLeft, marginRight, marginTop, marginBottom));
page.Orientation = pdfPageOrientation;
var pdfHtmlElement = new PdfHtmlElement(html, "");
page.Add(pdfHtmlElement);
}
var pdfFont = pdfDocument.AddFont(PdfStandardFont.Helvetica);
pdfFont.Size = 12;
foreach (PdfPage page in pdfDocument.Pages)
{
var customFooter = pdfDocument.AddTemplate(page.PageSize.Width, 30);
var pdfFooterTextElement = new PdfTextElement(0, 15,
pageFooterText,
pdfFont)
{
HorizontalAlign = PdfTextHorizontalAlign.Right,
VerticalAlign = PdfTextVerticalAlign.Bottom,
};
customFooter.Add(pdfFooterTextElement);
page.CustomFooter = customFooter;
}
pdfDocument.Save(stream);
How can I write a multi-page ToC to the end of a PDF consisting of merged documents, using iTextSharp?
The answer to Create Index File(TOC) for merged pdf using itext library in java explains how to create a ToC page when merging PDFs (catalogued in the iTextSharp book http://developers.itextpdf.com/examples/merging-pdf-documents/merging-documents-and-create-table-contents#795-mergewithtoc.java). Code in this answer is based on those examples.
However it only works if the ToC is 1 page long. If the content becomes longer, then it repeats itself on the same page rather than spanning into the next page.
Trying to add the link directly to the text via:
ct.Add(new Chunk("link").SetLocalGoto("p1"))
causes an exception ("Cannot add Annotations, not enough pages in document").
Can anyone explain a method that will allow me to append multiple pages of content to a PDF when merging them (the more general the approach, the better). Is there a way to write into the document using Document.Add() instead of having to copy in template pages and write on the top of them?
(Note, code is in c#)
This answer is based on the example from the iTextSharp documentation, but converted to C#.
To make the added text span multiple pages, I found I could use ColumnText.HasMoreText(ct.Go()) to tell me if there was more text than could fit on the current page. You can then save the current page, re-create a new page template, and move the columntext to the new page. Below this is in a function called CheckForNewPage:
private bool CheckForNewPage(PdfCopy copy, ref PdfImportedPage page, ref PdfCopy.PageStamp stamp, ref PdfReader templateReader, ColumnText ct)
{
if (ColumnText.HasMoreText(ct.Go()))
{
//Write current page
stamp.AlterContents();
copy.AddPage(page);
//Start a new page
ct.SetSimpleColumn(36, 36, 559, 778);
templateReader = new PdfReader("template.pdf");
page = copy.GetImportedPage(templateReader, 1);
stamp = copy.CreatePageStamp(page);
ct.Canvas = stamp.GetOverContent();
ct.Go();
return true;
}
return false;
}
This should be called each time text is added to the ct variable.
If CheckForNewPage returns true you can then increment the page count, and reset the y variable to the top of the new page so that link annotation is in the correct place on the new page.
e.g.
var tocPageCount = 0;
var para = new iTextSharp.text.Paragraph(documentName);
ct.AddElement(para);
ct.Go();
if (CheckForNewPage(context, copy, ref page, ref stamp, ref tocReader, ct))
{
tocPageCount++;
y = 778;
}
//Add link annotation
action = PdfAction.GotoLocalPage(d.DocumentID.ToString(), false);
link = new PdfAnnotation(copy, TOC_Page.Left, ct.YLine, TOC_Page.Right, y, action);
stamp.AddAnnotation(link);
y = ct.YLine;
This creates the pages correctly. The below code adapts the end of ToC2 example for re-ordering the pages, in order to handle more than 1 page.
var rdr = new PdfReader(baos.toByteArray());
var totalPageCount = rdr.NumberOfPages;
rdr.SelectPages(String.Format("{0}-{1}, 1-{2}", totalPageCount - tocPageCount +1, totalPageCount, totalPageCount - tocPageCount));
PdfStamper stamper = new PdfStamper(rdr, new FileStream(outputFilePath, FileMode.Create));
stamper.Close();
By re-using the CheckForNewPage function, you should be able to add any content to new pages you create, and have it span multiple pages. If you don't need the annnotations you call CheckForNewPage in a loop at the end of adding all your content (just don't call ct.Go() beforehand).
How to create hyperlink with docx.dll dynamically , currently tried below but not working
using (DocX document = DocX.Create(#"Test.docx"))
{
// Add a hyperlink to this document.
Hyperlink h = document.AddHyperlink
("Google", new Uri("http://www.google.com"));
// Add a new Paragraph to this document.
Paragraph p = document.InsertParagraph();
p.Append("My favourite search engine is ");
p.AppendHyperlink(h);
p.Append(", I think it's great.");
// Save all changes made to this document.
document.Save();
}
How about using p.AppendHyperlink("www.google.com", "Google", HyperlinkType.WebLink);
?
Additionally , you can take a reference from Insert Hyperlink to Word in C# .
It seems rather simple, but I can't find something like getPageCount() in the API. I can get it to return the current page, but not the total number of pages. Perhaps I'm missing it?
I would like to somehow be able to print 'Page 1 of 9' at the top of every page, where '1' of course is the current page number.
Make sure to include the using MigraDoc.DocumentObjectModel; statement in your class.
Document document = new Document();
Section section = document.AddSection();
Paragraph paragraph = new Paragraph();
paragraph.AddText("Page ");
paragraph.AddPageField();
paragraph.AddText(" of ");
paragraph.AddNumPagesField();
section.Headers.Primary.Add(paragraph);
With PDFsharp it's up to you.
I presume you are using MigraDoc: With MigraDoc you can add a page header. Add paragraph.AddPageField() for the current page number and paragraph.AddNumPagesField() for the total page count.
Sample that uses AddPageField
Code snippet from the sample:
// Create a paragraph with centered page number. See definition of style "Footer".
Paragraph paragraph = new Paragraph();
paragraph.AddTab();
paragraph.AddPageField();
// Add paragraph to footer for odd pages.
section.Footers.Primary.Add(paragraph);
// Add clone of paragraph to footer for odd pages. Cloning is necessary because an object must
// not belong to more than one other object. If you forget cloning an exception is thrown.
section.Footers.EvenPage.Add(paragraph.Clone());
Code snippet that sets the tab stop (assuming DIN A 4 with a body with of 16 cm):
style = document.Styles[StyleNames.Footer];
style.ParagraphFormat.AddTabStop("8cm", TabAlignment.Center);
Both snippets taken from the linked site. Sample code is also available for download.
I know this question is old and has an accepted answer, however the question comes up among the first when searching for a PDFsharp solution.
For the record, achieving this in PDFsharp is easy. The PdfDocument class, found under the PdfSharp.Pdf namespace contains a collection of pages (PdfDocument.Pages). All you have to do is iterate through the collection and add the page counter somewhere on every page, using a XGraphics object, that you can instantiate using XGraphics.FromPdfPage(PdfPage).
using PdfSharp.Pdf; // PdfDocument, PdfPage
using PdfSharp.Drawing; // XGraphics, XFont, XBrush, XRect
// XStringFormats
// Create a new PdfDocument.
PdfDocument document = new PdfDocument();
// Add five pages to the document.
for(int i = 0; i < 5; ++i)
document.AddPage();
// Make a font and a brush to draw the page counter.
XFont font = new XFont("Verdana", 8);
XBrush brush = XBrushes.Black;
// Add the page counter.
string noPages = document.Pages.Count.ToString();
for(int i = 0; i < document.Pages.Count; ++i)
{
PdfPage page = document.Pages[i];
// Make a layout rectangle.
XRect layoutRectangle = new XRect(0/*X*/, page.Height-font.Height/*Y*/, page.Width/*Width*/, font.Height/*Height*/);
using (XGraphics gfx = XGraphics.FromPdfPage(page))
{
gfx.DrawString(
"Page " + (i+1).ToString() + " of " + noPages,
font,
brush,
layoutRectangle,
XStringFormats.Center);
}
}
It's worth noting that if a XGraphics object already exists for a given page, before creating a new one, the old one needs to be disposed. This would fail:
PdfDocument document = new PdfDocument();
PdfPage page = document.AddPage();
XGraphics gfx1 = XGraphics.FromPage(page);
XGraphics gfx2 = XGraphics.FromPage(page);
It is worth noting that AddSectionPagesField() also exists. In this way 'Y' will be the number of pages of the section instead of the number of pages of the entire document.
It finds its use when you generate many different documents for one print and you want to separate page counting. I hope it is understandable.
So then you can also use:
Paragraph paragraph = new Paragraph();
paragraph.AddText("Page");
paragraph.AddPageField();
paragraph.AddText(" of ");
paragraph.AddSectionPagesField();
// Add paragraph to header for odd pages.
section.Headers.Primary.Add(paragraph);
// Add clone of paragraph to header for odd pages. Cloning is necessary because an object must
// not belong to more than one other object. If you forget cloning an exception is thrown.
section.Headers.EvenPage.Add(paragraph.Clone());
Similarly just for footer use:
section.Footers.Primary.Add(paragraph);
section.Footers.EvenPage.Add(paragraph.Clone());
here's how you can fix it
Paragraph foot = sec.Footers.Primary.AddParagraph();
foot.AddText("Page ");
foot.AddPageField();
foot.AddText(" of ");
foot.AddNumPagesField();