Generate PDF with arabic text inside grid cell - pdf

I need to generate a PDF that contains a table with data in both English and arabic languages in xamarin.forms. I've been searching a lot. I found the syncfusion documentation and tried to apply it. This one doesn't include a grid but it tells how to add a simple arabic text: https://blog.syncfusion.com/blogs/post/adding-rtl-support-to-your-pdf-in-xamarin.aspx this one worked. So I tried to use it and make modifications to add a grid. This is what I wrote in the main.xaml.cs:
private void btn_Clicked(object sender, EventArgs e)
{
PdfDocument doc = new PdfDocument();
//Add a page.
PdfPage page = doc.Pages.Add();
//Create a PdfGrid.
Syncfusion.Pdf.Grid.PdfGrid pdfGrid = new Syncfusion.Pdf.Grid.PdfGrid();
//Add values to list
List<object> data = new List<object>();
Object row1 = new { ID = "E01", Name = "رنا" };
Object row2 = new { ID = "E02", Name = "رامي" };
Object row3 = new { ID = "E03", Name = "Andrew" };
Object row4 = new { ID = "E04", Name = "Paul" };
Object row5 = new { ID = "E05", Name = "Gray" };
data.Add(row1);
data.Add(row2);
data.Add(row3);
data.Add(row4);
data.Add(row5);
//Add list to IEnumerable
IEnumerable<object> dataTable = data;
//Assign data source.
pdfGrid.DataSource = dataTable;
//Draw grid to the page of PDF document.
pdfGrid.Draw(page, new PointF(10, 10));
//Save the PDF document to stream.
MemoryStream ms = new MemoryStream();
//Create a new PDF document.
//Add a new PDF page.
//Load font.
Stream fontStream = typeof(App).GetTypeInfo().Assembly.GetManifestResourceStream("RTLDemo.Assets.arial.ttf");
//Create PDF true type font.
PdfFont pdfFont = new PdfTrueTypeFont(fontStream, 12);
//String format
PdfStringFormat format = new PdfStringFormat();
//Set the format as right to left.
format.TextDirection = PdfTextDirection.RightToLeft;
//Set the alignment.
format.Alignment = PdfTextAlignment.Right;
SizeF pageSize = page.GetClientSize();
PdfGridCell pdfGridCell = new PdfGridCell();
PdfGridCellStyle style = new PdfGridCellStyle();
style.Font = pdfFont;
style.StringFormat.TextDirection = format.TextDirection;
//Set style to grid
pdfGridCell.Style = style;
//Save the document.
doc.Save(ms);
//Close the document
doc.Close(true);
ms.Position = 0;
if (Device.OS == TargetPlatform.WinPhone || Device.OS == TargetPlatform.Windows)
Xamarin.Forms.DependencyService.Get<ISaveWindowsPhone>().Save("RTLText.pdf", "application/pdf", ms);
else
Xamarin.Forms.DependencyService.Get<ISave>().Save("RTLText.pdf", "application/pdf", ms);
}
I keep getting an exception: System.NullReferenceException:** 'Object reference not set to an instance of an object.' on the style.StringFormat.TextDirection = format.TextDirection; line. I followed the steps in this document: https://www.syncfusion.com/forums/135954/how-to-create-unicode-font-with-pdftruetypefont-or-the-proper-way-to-draw-a-unicode-string I tried a lot of things but nothing is wroking. What should I do?
Update:
i replaced the code above with a new code:
PdfDocument doc = new PdfDocument();
//Add a page.
PdfPage page = doc.Pages.Add();
//Create a PdfGrid.
PdfGrid pdfGrid = new PdfGrid();
//Create a DataTable.
DataTable dataTable = new DataTable();
//Add columns to the DataTable
dataTable.Columns.Add("ID");
dataTable.Columns.Add("Name");
//Add rows to the DataTable.
dataTable.Rows.Add(new object[] { "E01", "رنا" });
dataTable.Rows.Add(new object[] { "E02", "Thomas" });
//Assign data source.
pdfGrid.DataSource = dataTable;
//Using the Column collection
pdfGrid.Columns[0].Width = 100;
//Adding grid cell style
PdfGridCellStyle cellStyle = new PdfGridCellStyle();
//Create new PDF string format instance.
PdfStringFormat format = new PdfStringFormat();
format.Alignment = PdfTextAlignment.Center;
format.TextDirection = PdfTextDirection.RightToLeft;
//Set string format to grid cell.
cellStyle.StringFormat = format;
//Set borders.
PdfBorders borders = new PdfBorders();
borders.All = PdfPens.Red;
cellStyle.Borders = borders;
//Set background image.
Stream fontStream = typeof(App).GetTypeInfo().Assembly.GetManifestResourceStream("RTLDemo.Assets.arial.ttf");
PdfFont pdfFont = new PdfTrueTypeFont(fontStream, 12);
cellStyle.Font = pdfFont;
//Set cell paddings.
cellStyle.CellPadding = new PdfPaddings(5, 5, 5, 5);
//Applying style to grid
pdfGrid.Rows[0].Cells[0].Style = cellStyle;
//Draw grid to the page of PDF document.
pdfGrid.Draw(page, new PointF(10, 10));
MemoryStream ms = new MemoryStream();
//Save the document.
doc.Save(ms);
//Close the document
doc.Close(true);
ms.Position = 0;
if (Device.OS == TargetPlatform.WinPhone || Device.OS == TargetPlatform.Windows)
Xamarin.Forms.DependencyService.Get<ISaveWindowsPhone>().Save("RTLText.pdf", "application/pdf", ms);
else
Xamarin.Forms.DependencyService.Get<ISave>().Save("RTLText.pdf", "application/pdf", ms);
the previous exception didn't appear, the pdf was generated but the arabic text didn't appear, what am i missing?

We can draw RTL text in PDF grid with Syncfusion PDF library in Xamarin.Forms application. Please find the sample code below,
//Create a new PDF document.
PdfDocument document = new PdfDocument();
//Add a new PDF page.
PdfPage page = document.Pages.Add();
//Get the font file as stream.
Stream fontStream = typeof(MainPage).GetTypeInfo().Assembly.GetManifestResourceStream("UnicodeText.Assets.arial.ttf");
//Create a new PdfTrueTypeFont instance.
PdfTrueTypeFont font = new PdfTrueTypeFont(fontStream, 14);
//Create a new bold stylePdfTrueTypeFont instance.
PdfTrueTypeFont boldFont = new PdfTrueTypeFont(fontStream, 14, PdfFontStyle.Bold);
page.Graphics.DrawString("PdfGrid", boldFont, PdfBrushes.Black, PointF.Empty);
//Create PdfGrid.
PdfGrid pdfGrid = new PdfGrid();
//Add values to list
List<object> data = new List<object>();
data.Add(new { ID = "E01", Name = "رنا" });
data.Add(new { ID = "E02", Name = "رامي" });
data.Add(new { ID = "E03", Name = "Andrew" });
data.Add(new { ID = "E04", Name = "Paul" });
data.Add(new { ID = "E05", Name = "Clay" });
//Add list to IEnumerable.
IEnumerable<object> dataTable = data;
//Assign data source.
pdfGrid.DataSource = dataTable;
//Assign bold font to pdfGrid header.
pdfGrid.Headers[0].Style.Font = boldFont;
//Assign font to PdfGrid.
pdfGrid.Style.Font = font;
//Create String format with RTL text direction and center text alignment.
PdfStringFormat format = new PdfStringFormat();
format.TextDirection = PdfTextDirection.RightToLeft;
format.Alignment = PdfTextAlignment.Center;
//Assign string format to draw RTL text with center alsignment
pdfGrid.Rows[0].Cells[1].StringFormat = format;
pdfGrid.Rows[1].Cells[1].StringFormat = format;
//Draw grid to the page of PDF document.
pdfGrid.Draw(page, new Syncfusion.Drawing.PointF(0, 20));
MemoryStream stream = new MemoryStream();
//Save the document.
document.Save(stream);
//Close the document.
document.Close(true);
Please find the sample from https://www.syncfusion.com/downloads/support/directtrac/general/ze/UnicodeText1046384115. Please find the sample output PDF created from https://www.syncfusion.com/downloads/support/directtrac/general/ze/Output-609045962.
Note: String format assigned in your sample is not applied on corresponding PDF grid cell which having Arabic text. We have to provide cell index as 1 as per the sample code provided.

this is taken directly from the documentation
//Adding grid cell style
PdfGridCellStyle cellStyle = new PdfGridCellStyle();
//Create new PDF string format instance.
PdfStringFormat format = new PdfStringFormat();
format.Alignment = PdfTextAlignment.Center;
//Set string format to grid cell.
cellStyle.StringFormat = format;

Related

Generate charts, graphs in PDF using iTextSharp in ASP.NET Core

I need to generate a PDF document at the server side of a ASP.NET Core 3.1. I am using iTextSharp to generate PDF. I am able to create data tables in PDF using iTextSharp. However I am not able to find a solution to add charts or graphs into the PDF using the same library.
I have used ChartJS and other similar packages but none of them let me create a chart at the backend and create it as an image to be embedded in the PDF.
UPDATE 8/12/2020
Create excel then convert to pdf.
Essential XlsIO is a native .NET class librry that can be used to create and modify Microsoft Excel files.
XlsIO allows you to convert an entire workbook or a single worksheet into PDF document.
[Route("/chart")]
public IActionResult CreateChart()
{
using (ExcelEngine excelEngine = new ExcelEngine())
{
IApplication application = excelEngine.Excel;
application.DefaultVersion = ExcelVersion.Excel2013;
IWorkbook workbook = application.Workbooks.Create(1);
IWorksheet sheet = workbook.Worksheets[0];
//Inserts the sample data for the chart
sheet.Range["A1"].Text = "Month";
sheet.Range["B1"].Text = "Product A";
sheet.Range["C1"].Text = "Product B";
//Months
sheet.Range["A2"].Text = "Jan";
sheet.Range["A3"].Text = "Feb";
sheet.Range["A4"].Text = "Mar";
sheet.Range["A5"].Text = "Apr";
sheet.Range["A6"].Text = "May";
//Create a random Data
Random r = new Random();
for (int i = 2; i <= 6; i++)
{
for (int j = 2; j <= 3; j++)
{
sheet.Range[i, j].Number = r.Next(0, 500);
}
}
IChartShape chart = sheet.Charts.Add();
//Set chart type
chart.ChartType = ExcelChartType.Line;
//Set Chart Title
chart.ChartTitle = "Product Sales comparison";
//Set first serie
IChartSerie productA = chart.Series.Add("ProductA");
productA.Values = sheet.Range["B2:B6"];
productA.CategoryLabels = sheet.Range["A2:A6"];
//Set second serie
IChartSerie productB = chart.Series.Add("ProductB");
productB.Values = sheet.Range["C2:C6"];
productB.CategoryLabels = sheet.Range["A2:A6"];
//Saving the workbook as stream
//FileStream stream = new FileStream("Chart.xlsx", FileMode.Create, FileAccess.ReadWrite);
//workbook.SaveAs(stream);
//Initialize XlsIO renderer.
XlsIORenderer renderer = new XlsIORenderer();
//Convert Excel document into PDF document
Syncfusion.Pdf.PdfDocument pdfDocument = renderer.ConvertToPDF(sheet);
MemoryStream stream = new MemoryStream();
pdfDocument.Save(stream);
stream.Flush(); //Always catches me out
stream.Position = 0; //Not sure if this is required
//stream.Dispose();
return File(stream, "application/pdf", "chart.pdf");
}
}
Generate PDF with Image by using iTextsharp in ASPNET Core 3.1
Here are the codes of action.
[Route("/pdf")]
public FileStreamResult GeneratePDFwithImage()
{
var imagepath = System.IO.Path.Combine(_env.WebRootPath, "/images") + "/test.png";
Document doc = new Document();
MemoryStream stream = new MemoryStream();
PdfWriter pdfWriter = PdfWriter.GetInstance(doc, stream);
pdfWriter.CloseStream = false;
doc.Open();
doc.Add(new Paragraph("Hello World"));
Image png = Image.GetInstance(imagepath);
doc.Add(png);
doc.Close();
stream.Flush(); //Always catches me out
stream.Position = 0; //Not sure if this is required
return File(stream, "application/pdf", "HelloWorld.pdf");
}
Test of result

Why generated pdf attachment having 0 KB Size?

while generating pdf on localhost using below code is fine,however it generates a pdf of 0KB size while on server . What I am doing wrong here ?
I am using iTextsharp nuget and the code runs fine on local server.
public byte[] GetPDF(string pHTML, List<int> ideaidlist)
{
byte[] bPDF = null;
MemoryStream ms = new MemoryStream();
TextReader txtReader = new StringReader(pHTML);
// Document doc = new Document(PageSize.LETTER, 0, 0, 0, 0);
Document doc = new Document(new Rectangle(864f, 870f), 0, 0, 0, 0);
string Certpath = ConfigurationManager.AppSettings["MailImagePath"]+"Certificates.pdf";//System.Configuration.
string ImgTopPath =ConfigurationManager.AppSettings["CertificateImagePath"];
string ImgMidPath =ConfigurationManager.AppSettings["CertificateImagePath"];
string ImgBotPath =ConfigurationManager.AppSettings["CertificateImagePath"];
FileInfo newExistFile = new FileInfo(Certpath);
if (newExistFile.Exists)
{
newExistFile.Delete();
}
PdfWriter oPdfWriter = PdfWriter.GetInstance(doc,new FileStream(Certpath , FileMode.CreateNew));
HTMLWorker htmlWorker = new HTMLWorker(doc);
doc.Open();
GeneratePdfVM data = new GeneratePdfVM();
foreach (var item in ideaidlist)
{
data = new CommonBL().GetIdeaidListForGenerateCertificates(item);
doc.NewPage();
PdfPTable table = new PdfPTable(1);
table.TotalWidth = 1000;
table.WidthPercentage = 100;
table.LockedWidth = true;
table.HorizontalAlignment = 0;
table.DefaultCell.Border = Rectangle.NO_BORDER;
iTextSharp.text.Image imageTopURL = iTextSharp.text.Image.GetInstance(ImgTopPath + "CertiTop.PNG");
PdfPCell imgTopCell = new PdfPCell(imageTopURL);
imgTopCell.Border = Rectangle.NO_BORDER;
table.AddCell(imgTopCell);
imageTopURL.SpacingAfter = 20;
PdfPCell FirstTxtCell = new PdfPCell();
Paragraph p = new Paragraph(data.EmpName);
p.Font = new Font(Font.FontFamily.HELVETICA, 35f, Font.UNDERLINE);
p.Alignment = Element.ALIGN_CENTER;
FirstTxtCell.AddElement(p);
FirstTxtCell.PaddingRight = 190f;
FirstTxtCell.Border = 0;
table.AddCell(FirstTxtCell);
iTextSharp.text.Image imageMidURL = iTextSharp.text.Image.GetInstance(ImgMidPath + "CertiMid.PNG");
PdfPCell imgMidCell = new PdfPCell(imageMidURL);
imgMidCell.Border = Rectangle.NO_BORDER;
imgMidCell.Border = 0;
imageMidURL.SpacingBefore = 15f;
imageMidURL.Alignment = Element.ALIGN_CENTER;
imgMidCell.PaddingRight = 244f;
table.AddCell(imgMidCell);
PdfPCell SecTextCell = new PdfPCell();
Paragraph para = new Paragraph(data.Title);
para.Font = new Font(Font.FontFamily.HELVETICA, 32f, Font.ITALIC);
para.Alignment = Element.ALIGN_CENTER;
SecTextCell.AddElement(para);
SecTextCell.Border = 0;
SecTextCell.PaddingRight = 200f;
table.AddCell(SecTextCell);
iTextSharp.text.Image imageBotURL = iTextSharp.text.Image.GetInstance(ImgBotPath + "CertiBottom.PNG");
PdfPCell imgBotCell = new PdfPCell(imageBotURL);
imgBotCell.Border = 0;
table.AddCell(imgBotCell);
imageBotURL.SpacingBefore=20;
imageTopURL.ScaleAbsolute(860f, 230f);
imageMidURL.ScaleAbsolute(930f, 100f);
imageBotURL.ScaleAbsolute(864f, 230f);
doc.Open();
doc.Add(table);
htmlWorker.StartDocument();
htmlWorker.Parse(txtReader);
htmlWorker.EndDocument();
}
htmlWorker.Close();
doc.Close();
bPDF = ms.ToArray();
ms.Close();
return bPDF;
}
Here I am calling the above function :
public void GenerateCertificatePDF(List<int> ideaidlist)
{
string HTMLContent = "";
Response.Clear();
Response.ContentType = "application/pdf";
Response.AddHeader("content-disposition", "attachment;filename=" + "Certificates.pdf");
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.BinaryWrite(GetPDF(HTMLContent, ideaidlist));
}
When you run your code locally, a file is created:
new FileStream(Certpath , FileMode.CreateNew)
That same file is created on the server when you run the code on the server.
However, you also want to send the bytes of the PDF document to the browser. To achieve this by creating a MemoryStream:
MemoryStream ms = new MemoryStream();
When I search your code for the ms variable, I don't find it anywhere except at the very end:
bPDF = ms.ToArray();
ms.Close();
return bPDF;
In other words: you don't write any bytes to ms; the MemoryStream is empty. This is proven by the fact that you get 0 bytes.
Your code works in the sense that a PDF is written on the disk of the server, but that's not what you want, is it? You want this method to create a PDF in memory and then send its bytes to a server.
To achieve this, you need to remove all references to certPath, the part where you exist if the file exists and the FileStream. Instead, you need to write the PDF to the MemoryStream:
PdfWriter oPdfWriter = PdfWriter.GetInstance(doc, ms);
This is explained by Chris Haas in his answer to this question: iTextSharp - Create new document as Byte[]

How to get the Page Number of a form field in Aspose PDF?

How to I retrieve the page number of a Form Field using Aspose PDF? I'm trying to recreate the PDF form in HTML by placing an image of each page and overlaying each field using the page number, coordinates, and dimensions.
Here's my current code:
public static List<PdfFieldDisplayModel> GetFieldPlacements(Stream stream)
{
var fields = new List<PdfFieldDisplayModel>();
var doc = new Document(stream);
var pdfForm = new Aspose.Pdf.Facades.Form(stream);
foreach (Field ff in doc.Form)
{
var txt = doc.Form[ff.Name] as TextBoxField;
var f = new PdfFieldDisplayModel();
f.PageNumber = ??????????????
f.Name = ff.Name;
f.PartialName = ff.PartialName;
f.Value = ff.Value;
f.Width = txt.Rect.Width;
f.Height = txt.Rect.Height;
f.Left = txt.Rect.LLX;
f.Bottom = txt.Rect.LLY;
fields.Add(f);
}
return fields;
}
My name is Nayyer and I am developer Evangelist at Aspose. In order to get page index for form field, please try using PageIndex property of Aspose.Pdf.InteractiveFeatures.Forms.Field instance.
[C#]
//open document
Document pdfDocument = new Document("c:/pdftest/SingleField_output.pdf","password");
//get values from all fields
foreach (Aspose.Pdf.InteractiveFeatures.Forms.Field formField in pdfDocument.Form)
{
//get field value
Console.WriteLine("PartialName : {0} ", formField.PartialName);
Console.WriteLine("Value : {0} ", formField.Value);
Console.WriteLine("Value : {0} ", formField.PageIndex);
}

How to retain page labels when concatenating an existing pdf with a pdf created from scratch?

I have a code which is creating a "cover page" and then merging it with an existing pdf. The pdf labels were lost after merging. How can I retain the pdf labels of the existing pdf and then add a page label to the pdf page created from scratch (eg "Cover page")? The example of the book I think is about retrieving and replacing page labels. I don't know how to apply this when concatenating an existing pdf with a pdf created from scratch. I am using itext 5.3.0. Thanks in advance.
EDIT
as per comment of mkl
public ByteArrayOutputStream getConcatenatePDF()
{
if (bitstream == null)
return null;
if (item == null)
{
item = getItem();
if (item == null)
return null;
}
ByteArrayOutputStream byteout = null;
InputStream coverStream = null;
try
{
// Get Cover Page
coverStream = getCoverStream();
if (coverStream == null)
return null;
byteout = new ByteArrayOutputStream();
int pageOffset = 0;
ArrayList<HashMap<String, Object>> master = new ArrayList<HashMap<String, Object>>();
Document document = null;
PdfCopy writer = null;
PdfReader reader = null;
byte[] password = (ownerpass != null && !"".equals(ownerpass)) ? ownerpass.getBytes() : null;
// Get infomation of the original pdf
reader = new PdfReader(bitstream.retrieve(), password);
boolean isPortfolio = reader.getCatalog().contains(PdfName.COLLECTION);
char version = reader.getPdfVersion();
int permissions = reader.getPermissions();
// Get metadata
HashMap<String, String> info = reader.getInfo();
String title = (info.get("Title") == null || "".equals(info.get("Title")))
? getFieldValue("dc.title") : info.get("Title");
String author = (info.get("Author") == null || "".equals(info.get("Author")))
? getFieldValue("dc.contributor.author") : info.get("Author");
String subject = (info.get("Subject") == null || "".equals(info.get("Subject")))
? "" : info.get("Subject");
String keywords = (info.get("Keywords") == null || "".equals(info.get("Keywords")))
? getFieldValue("dc.subject") : info.get("Keywords");
reader.close();
// Merge cover page and the original pdf
InputStream[] is = new InputStream[2];
is[0] = coverStream;
is[1] = bitstream.retrieve();
for (int i = 0; i < is.length; i++)
{
// we create a reader for a certain document
reader = new PdfReader(is[i], password);
reader.consolidateNamedDestinations();
if (i == 0)
{
// step 1: creation of a document-object
document = new Document(reader.getPageSizeWithRotation(1));
// step 2: we create a writer that listens to the document
writer = new PdfCopy(document, byteout);
// Set metadata from the original pdf
// the position of these lines is important
document.addTitle(title);
document.addAuthor(author);
document.addSubject(subject);
document.addKeywords(keywords);
if (pdfa)
{
// Set thenecessary information for PDF/A-1B
// the position of these lines is important
writer.setPdfVersion(PdfWriter.VERSION_1_4);
writer.setPDFXConformance(PdfWriter.PDFA1B);
writer.createXmpMetadata();
}
else if (version == '5')
writer.setPdfVersion(PdfWriter.VERSION_1_5);
else if (version == '6')
writer.setPdfVersion(PdfWriter.VERSION_1_6);
else if (version == '7')
writer.setPdfVersion(PdfWriter.VERSION_1_7);
else
; // no operation
// Set security parameters
if (!pdfa)
{
if (password != null)
{
if (security && permissions != 0)
{
writer.setEncryption(null, password, permissions, PdfWriter.STANDARD_ENCRYPTION_128);
}
else
{
writer.setEncryption(null, password, PdfWriter.ALLOW_PRINTING | PdfWriter.ALLOW_COPY | PdfWriter.ALLOW_SCREENREADERS, PdfWriter.STANDARD_ENCRYPTION_128);
}
}
}
// step 3: we open the document
document.open();
// if this pdf is portfolio, does not add cover page
if (isPortfolio)
{
reader.close();
byte[] coverByte = getCoverByte();
if (coverByte == null || coverByte.length == 0)
return null;
PdfCollection collection = new PdfCollection(PdfCollection.TILE);
writer.setCollection(collection);
PdfFileSpecification fs = PdfFileSpecification.fileEmbedded(writer, null, "cover.pdf", coverByte);
fs.addDescription("cover.pdf", false);
writer.addFileAttachment(fs);
continue;
}
}
int n = reader.getNumberOfPages();
// step 4: we add content
PdfImportedPage page;
PdfCopy.PageStamp stamp;
for (int j = 0; j < n; )
{
++j;
page = writer.getImportedPage(reader, j);
if (i == 1) {
stamp = writer.createPageStamp(page);
Rectangle mediabox = reader.getPageSize(j);
Rectangle crop = new Rectangle(mediabox);
writer.setCropBoxSize(crop);
// add overlay text
//<-- Code for adding overlay text -->
stamp.alterContents();
}
writer.addPage(page);
}
PRAcroForm form = reader.getAcroForm();
if (form != null && !pdfa)
{
writer.copyAcroForm(reader);
}
// we retrieve the total number of pages
List<HashMap<String, Object>> bookmarks = SimpleBookmark.getBookmark(reader);
//if (bookmarks != null && !pdfa)
if (bookmarks != null)
{
if (pageOffset != 0)
{
SimpleBookmark.shiftPageNumbers(bookmarks, pageOffset, null);
}
master.addAll(bookmarks);
}
pageOffset += n;
}
if (!master.isEmpty())
{
writer.setOutlines(master);
}
if (isPortfolio)
{
reader = new PdfReader(bitstream.retrieve(), password);
PdfDictionary catalog = reader.getCatalog();
PdfDictionary documentnames = catalog.getAsDict(PdfName.NAMES);
PdfDictionary embeddedfiles = documentnames.getAsDict(PdfName.EMBEDDEDFILES);
PdfArray filespecs = embeddedfiles.getAsArray(PdfName.NAMES);
PdfDictionary filespec;
PdfDictionary refs;
PRStream stream;
PdfFileSpecification fs;
String path;
// copy embedded files
for (int i = 0; i < filespecs.size(); )
{
filespecs.getAsString(i++); // remove description
filespec = filespecs.getAsDict(i++);
refs = filespec.getAsDict(PdfName.EF);
for (PdfName key : refs.getKeys())
{
stream = (PRStream) PdfReader.getPdfObject(refs.getAsIndirectObject(key));
path = filespec.getAsString(key).toString();
fs = PdfFileSpecification.fileEmbedded(writer, null, path, PdfReader.getStreamBytes(stream));
fs.addDescription(path, false);
writer.addFileAttachment(fs);
}
}
}
if (pdfa)
{
InputStream iccFile = this.getClass().getClassLoader().getResourceAsStream(PROFILE);
ICC_Profile icc = ICC_Profile.getInstance(iccFile);
writer.setOutputIntents("Custom", "", "http://www.color.org", "sRGB IEC61966-2.1", icc);
writer.setViewerPreferences(PdfWriter.PageModeUseOutlines);
}
// step 5: we close the document
document.close();
}
catch (Exception e)
{
log.info(LogManager.getHeader(context, "cover_page: getConcatenatePDF", "bitstream_id="+bitstream.getID()+", error="+e.getMessage()));
// e.printStackTrace();
return null;
}
return byteout;
}
UPDATE
Based on mkl's answer, I modified the code above to look like this:
public ByteArrayOutputStream getConcatenatePDF()
{
if (bitstream == null)
return null;
if (item == null)
{
item = getItem();
if (item == null)
return null;
}
ByteArrayOutputStream byteout = null;
try
{
// Get Cover Page
InputStream coverStream = getCoverStream();
if (coverStream == null)
return null;
byteout = new ByteArrayOutputStream();
InputStream documentStream = bitstream.retrieve();
PdfReader coverPageReader = new PdfReader(coverStream);
PdfReader reader = new PdfReader(documentStream);
PdfStamper stamper = new PdfStamper(reader, byteout);
PdfImportedPage page = stamper.getImportedPage(coverPageReader, 1);
stamper.insertPage(1, coverPageReader.getPageSize(1));
PdfContentByte content = stamper.getUnderContent(1);
int n = reader.getNumberOfPages();
for (int j = 2; j <= n; j++) {
//code for overlay text
ColumnText.showTextAligned(stamper.getOverContent(j), Element.ALIGN_CENTER, overlayText,
crop.getLeft(10), crop.getHeight() / 2 + crop.getBottom(), 90);
}
content.addTemplate(page, 0, 0);
stamper.close();
}
catch (Exception e)
{
log.info(LogManager.getHeader(context, "cover_page: getConcatenatePDF", "bitstream_id="+bitstream.getID()+", error="+e.getMessage()));
e.printStackTrace();
return null;
}
return byteout;
}
And then I set the page labels to the cover page. I omitted code not relevant to my question.
/**
*
* #return InputStream the resulting output stream
*/
private InputStream getCoverStream()
{
ByteArrayOutputStream byteout = getCover();
return new ByteArrayInputStream(byteout.toByteArray());
}
/**
*
* #return InputStream the resulting output stream
*/
private byte[] getCoverByte()
{
ByteArrayOutputStream byteout = getCover();
return byteout.toByteArray();
}
/**
*
* #return InputStream the resulting output stream
*/
private ByteArrayOutputStream getCover()
{
ByteArrayOutputStream byteout;
Document doc = null;
try
{
byteout = new ByteArrayOutputStream();
doc = new Document(PageSize.LETTER, 24, 24, 20, 40);
PdfWriter pdfwriter = PdfWriter.getInstance(doc, byteout);
PdfPageLabels labels = new PdfPageLabels();
labels.addPageLabel(1, PdfPageLabels.EMPTY, "Cover page", 1);
pdfwriter.setPageLabels(labels);
pdfwriter.setPageEvent(new HeaderFooter());
doc.open();
//code omitted (contents of cover page)
doc.close();
return byteout;
}
catch (Exception e)
{
log.info(LogManager.getHeader(context, "cover_page", "bitstream_id="+bitstream.getID()+", error="+e.getMessage()));
return null;
}
}
The modified code retained the page labels of the existing pdf (see screenshot 1) (documentStream), but the resulting merged pdf (screenshots 2 and 3) is off by 1 page since a cover page was inserted. As suggested by mkl, I should use page labels to the cover page, but it seems the pdf labels of the imported page was lost. My concern now is how do I set the page labels to the final document state as also suggested by mkl? I suppose I should use PdfWriter but I don't know where to put that in my modified code. Am I correct to assume that after the stamper.close() portion, that is the final state of my document? Thanks again in advance.
Screenshot 1. Notice the actual page 1 labeled Front cover
Screenshot 2. Merged pdf, after the generated on-the-fly "cover page" was inserted. The page label "Front cover" was now assigned to the cover page even after I've set the pdf label of the inserted page using labels.addPageLabel(1, PdfPageLabels.EMPTY, "Cover page", 1)
Screenshot 3. Note that the page label 3 was assigned to page 2.
FINAL UPDATE
Kudos to #mkl
The screenshot below is the result after I applied the latest update of mkl's answer. The pages labels are now assigned correctly to pages. Also, using PdfStamper instead of PdfCopy (as used in my original code) did not break the PDF/A compliance of the existing pdf.
Adding the cover page
Usually using PdfCopy for merging PDFs is the right choice, it creates a new document from the copied pages copying as much of the page-level information as possible not preferring any single document.
Your case is somewhat special, though: You have one document whose structure and content you prefer and want to apply a small change to it by adding a single page, a title page. All the while all information including document-level information (e.g. metadata, embedded files, ...) from the main document shall still be present in the result.
In such a use case it is more appropriate to use a PdfStamper which you use to "stamp" changes onto an existing PDF.
You might want to start from something like this:
try ( InputStream documentStream = getClass().getResourceAsStream("template.pdf");
InputStream titleStream = getClass().getResourceAsStream("title.pdf");
OutputStream outputStream = new FileOutputStream(new File(RESULT_FOLDER, "test-with-title-page.pdf")) )
{
PdfReader titleReader = new PdfReader(titleStream);
PdfReader reader = new PdfReader(documentStream);
PdfStamper stamper = new PdfStamper(reader, outputStream);
PdfImportedPage page = stamper.getImportedPage(titleReader, 1);
stamper.insertPage(1, titleReader.getPageSize(1));
PdfContentByte content = stamper.getUnderContent(1);
content.addTemplate(page, 0, 0);
stamper.close();
}
PS: Concerning questions in comments:
In my code above, I should have an overlay text supposedly (before the stamp.alterContents() portion) but I omitted that part of code for testing purposes. Can you please give me an idea how to implement that?
Do you mean something like an overlayed watermark? The PdfStamper allows you to access an "over content" for each page onto which you can draw any content:
PdfContentByte overContent = stamper.getOverContent(pageNumber);
Keeping page labels
My other question is about page offset, because I inserted the cover page, the page numbering are off by 1 page. How can I resolve that?
Unfortunately iText's PdfStamper does not automatically update the page label definition of the manipulated PDF. Actually this is no wonder because it is not clear how the inserted page is meant to be labeled. #Bruno At least, though, iText could change the page label sections starting after the insertion page number.
Using iText's low level API it is possible, though, to fix the original label positions and add a label for the inserted page. This can be implemented similarly to the iText in Action PageLabelExample example, more exactly its manipulatePageLabel part; simply add this before stamper.close():
PdfDictionary root = reader.getCatalog();
PdfDictionary labels = root.getAsDict(PdfName.PAGELABELS);
if (labels != null)
{
PdfArray newNums = new PdfArray();
newNums.add(new PdfNumber(0));
PdfDictionary coverDict = new PdfDictionary();
coverDict.put(PdfName.P, new PdfString("Cover Page"));
newNums.add(coverDict);
PdfArray nums = labels.getAsArray(PdfName.NUMS);
if (nums != null)
{
for (int i = 0; i < nums.size() - 1; )
{
int n = nums.getAsNumber(i++).intValue();
newNums.add(new PdfNumber(n+1));
newNums.add(nums.getPdfObject(i++));
}
}
labels.put(PdfName.NUMS, newNums);
stamper.markUsed(labels);
}
For a document with these labels:
It generates a document with these labels:
Keeping links
I just found out that the inserted page "Cover Page" lost its link annotations. I wonder if there's a workaround for this, since according to the book, the interactive features of the inserted page are lost when using PdfStamper.
Indeed, among the iText PDF generating classes only Pdf*Copy* keeps interactive features like annotations. Unfortunately one has to decide whether one wants to
create a genuinely new PDF (PdfWriter) with no information from other PDFs beyond contents being embedable;
manipulate a single existing PDF ('PdfStamper') with all information from that one PDF being preserved but no information from other PDFs beyond contents being embedable;
merge any number of existing PDFs (PdfCopy) with most page-level information from all those PDFs being preserved but no document-level information from any.
In your case I thought the new cover page had only static content, no dynamic features, and so assumes the PdfStamper was best. If you only have to deal with links, you may consider copying links manually, e.g. using this helper method
/**
* <p>
* A primitive attempt at copying links from page <code>sourcePage</code>
* of <code>PdfReader reader</code> to page <code>targetPage</code> of
* <code>PdfStamper stamper</code>.
* </p>
* <p>
* This method is meant only for the use case at hand, i.e. copying a link
* to an external URI without expecting any advanced features.
* </p>
*/
void copyLinks(PdfStamper stamper, int targetPage, PdfReader reader, int sourcePage)
{
PdfDictionary sourcePageDict = reader.getPageNRelease(sourcePage);
PdfArray annotations = sourcePageDict.getAsArray(PdfName.ANNOTS);
if (annotations != null && annotations.size() > 0)
{
for (PdfObject annotationObject : annotations)
{
annotationObject = PdfReader.getPdfObject(annotationObject);
if (!annotationObject.isDictionary())
continue;
PdfDictionary annotation = (PdfDictionary) annotationObject;
if (!PdfName.LINK.equals(annotation.getAsName(PdfName.SUBTYPE)))
continue;
PdfArray rectArray = annotation.getAsArray(PdfName.RECT);
if (rectArray == null || rectArray.size() < 4)
continue;
Rectangle rectangle = PdfReader.getNormalizedRectangle(rectArray);
PdfName hightLight = annotation.getAsName(PdfName.H);
if (hightLight == null)
hightLight = PdfAnnotation.HIGHLIGHT_INVERT;
PdfDictionary actionDict = annotation.getAsDict(PdfName.A);
if (actionDict == null || !PdfName.URI.equals(actionDict.getAsName(PdfName.S)))
continue;
PdfString urlPdfString = actionDict.getAsString(PdfName.URI);
if (urlPdfString == null)
continue;
PdfAction action = new PdfAction(urlPdfString.toString());
PdfAnnotation link = PdfAnnotation.createLink(stamper.getWriter(), rectangle, hightLight, action);
stamper.addAnnotation(link, targetPage);
}
}
}
which you can call right after inserting the original page:
PdfImportedPage page = stamper.getImportedPage(titleReader, 1);
stamper.insertPage(1, titleReader.getPageSize(1));
PdfContentByte content = stamper.getUnderContent(1);
content.addTemplate(page, 0, 0);
copyLinks(stamper, 1, titleReader, 1);
Beware, this method is really simple. It only considers links with URI actions and creates a link on the target page using the same location, target, and highlight setting as the original one. If the original one uses more refined features (e.g. if it brings along its own appearance streams or even merely uses the border style attributes) and you want to keep these features, you have to improve the method to also copy the entries for these features to the new annotation.

Copy annotations

I have a source PDF with some Free Text Annotations.
I would like to perform a mail merge like function on PDF. I would like to make a copy of the PDF and replace the Free Text Annotation based on some text replacement method.
For simplicity, I have a program that takes the annotations and add "LHC" behind it. Alas, the copy works, but the annotations remains unchanged.
I would have tried to use PdfAnnotation however, I am unsure how to convert from the PdfDictionary to PdfAnnotation
See my code below
string oldFile = "C:\\Temp\\oldFile.pdf";
string newFile = "C:\\Temp\\newFile.pdf";
// open the reader
PdfReader reader = new PdfReader(oldFile);
Rectangle size = reader.GetPageSizeWithRotation(1);
Document document = new Document(size);
// open the writer
FileStream fs = new FileStream(newFile, FileMode.Create, FileAccess.Write);
PdfCopy writer = new PdfCopy(document,fs);
document.Open();
// the pdf content
PdfContentByte cb = writer.DirectContent;
// adding Free Text Annotation
for (int pg = 1; pg < reader.NumberOfPages; pg++)
{
PdfDictionary pageDict = reader.GetPageN(pg);
PdfArray annotArray = pageDict.GetAsArray(PdfName.ANNOTS);
for (int i = 0; i < annotArray.Size; ++i)
{
PdfDictionary curAnnot = annotArray.GetAsDict(i);
PdfName contents = new PdfName("Contents");
PdfString str = curAnnot.GetAsString(contents);
String newString = str.ToString() + "LHC";
curAnnot.Remove(contents);
curAnnot.Put(contents, new PdfString(newString));
}
PdfImportedPage page = writer.GetImportedPage(reader, pg);
// PdfImportedPage pageOut = writer.destinationPdfReader(reader, pg);
//cb.AddTemplate(page, 0, 0);
writer.AddPage(page);
PdfAnnotation annot = new PdfAnnotation(writer, new Rectangle(0, 0));
writer.AddAnnotation(annot);
}
document.Close();
fs.Close();
writer.Close();
reader.Close();
References:
http://itextsharp.10939.n7.nabble.com/How-to-edit-annotations-td3352.html
(There is another link in stackoverflow, that I can't find, when I find it I will add it here)
The steps:
Step 1. Create a stamper from a reader.
Step 2. Read all the annotations
Step 3. Delete a set of keys and as a fallback any dictionary items
You now have performed an edit/copy of the annotation and changed the values.
The following is the code:
// Step 1. Create the stamper
string oldFile = "C:\\Temp\\oldFile.pdf";
string newFile = "C:\\Temp\\newFile.pdf";
// open the reader
PdfReader reader = new PdfReader(oldFile);
Rectangle size = reader.GetPageSizeWithRotation(1);
Document document = new Document(size);
// open the writer
// remember to set the page size before opening document
// otherwise the page is already set.
/* chapter02/HelloWorldMetadata.java */
document.Open();
// the pdf content
// cb does not work with stamper
// create the new pagez and add it to the pdf
// this segment of code is meant for writer
FileStream fs = new FileStream(newFile, FileMode.Create, FileAccess.ReadWrite);
PdfStamper writer = new PdfStamper(reader, fs, reader.PdfVersion, false);
for (int pg = 1; pg < reader.NumberOfPages; pg++)
{
// taken from http://itextsharp.10939.n7.nabble.com/How-to-edit-annotations-td3352.html
PdfDictionary pagedic = reader.GetPageN(pg);
PdfArray annotarray = (PdfArray)PdfReader.GetPdfObject(pagedic.Get(PdfName.ANNOTS));
if (annotarray == null || annotarray.Size == 0)
continue;
// step 2. read all the annotations
foreach (PdfIndirectReference annot in annotarray.ArrayList)
{
PdfDictionary annotationDic = (PdfDictionary)PdfReader.GetPdfObject(annot);
PdfName subType = (PdfName)annotationDic.Get(PdfName.SUBTYPE);
if (subType.Equals(PdfName.TEXT) || subType.Equals(PdfName.FREETEXT))
{
// 3. Change values of different properties of a certain annotation and delete a few keys & dictionaries
annotationDic.Put(PdfName.CONTENTS, new PdfString("These are changed contents", PdfObject.TEXT_UNICODE));
}
PdfString contents = annotationDic.GetAsString(PdfName.CONTENTS);
if (contents != null)
{
String value = contents.ToString();
annotationDic.Put(PdfName.CONTENTS, new PdfString(value));
annotationDic.Remove(PdfName.AP);
List<PdfName> tobeDel = new List<PdfName>();
foreach (PdfName key in annotationDic.Keys)
{
if (key.CompareTo(PdfName.AP) == 0 ||
key.CompareTo(PdfName.RC) == 0 ||
annotationDic.Get(key).IsDictionary())
{
tobeDel.Add(key);
}
}
foreach (PdfName key in tobeDel)
{
annotationDic.Remove(key);
}
}
writer.MarkUsed(annotationDic);
}
if ((pg + 1) < reader.NumberOfPages)
{
document.NewPage();
}
}
// close the streams and voilá the file should be changed :)
writer.Close();
reader.Close();