I have a pdf that is rendered outside of iText in SSRS. The report comes as separate PDFs and I use iText to apply some further functionality e.g. merging the pdf's into one, adding password encrypton etc.
I am trying to add a watermark onto each page of the pdf like so:
public string MergeReports(ConcurrentDictionary<int, string> base64Reports, string password = null, string watermark = null)
{
try
{
var passwordBytes = password != null ? Encoding.ASCII.GetBytes(password) : null;
// Creating new PdfDocument with smart mode on which maintains formatting and styling.
using (var ms = new MemoryStream())
using (var mergedPdf = new PdfDocument(new PdfWriter(ms,
new WriterProperties().SetStandardEncryption(passwordBytes, passwordBytes, EncryptionConstants.ALLOW_PRINTING,
EncryptionConstants.ENCRYPTION_AES_128 | EncryptionConstants.DO_NOT_ENCRYPT_METADATA)).SetSmartMode(true)))
{
foreach (var report in base64Reports.OrderBy(x => x.Key))
{
var (_, base64String) = report;
var bytes = Convert.FromBase64String(base64String);
using (var memoryStream = new MemoryStream(bytes))
{
using (PdfReader reader = new PdfReader(memoryStream))
{
using (var pdfDoc = new PdfDocument(reader))
{
if (watermark != null)
AddWatermark(watermark, pdfDoc);
pdfDoc.CopyPagesTo(1, pdfDoc.GetNumberOfPages(), mergedPdf);
pdfDoc.Close();
}
}
}
}
mergedPdf.Close();
return Convert.ToBase64String(ms.ToArray());
}
}
catch (Exception ex)
{
throw new PdfException($"Unable to merge PDF reports: {ex.Message}");
}
}
private void AddWatermark(string watermark, PdfDocument pdfDoc)
{
float watermarkTrimmingRectangleWidth = 300;
float watermarkTrimmingRectangleHeight = 300;
float formWidth = 300;
float formHeight = 300;
float formXOffset = 0;
float formYOffset = 0;
float xTranslation = 50;
float yTranslation = 25;
double rotationInRads = Math.PI / 3;
var fontProgram = FontProgramFactory.CreateFont(_configuration.GetSection("FontFiles")["Calibri"]);
PdfFont font = PdfFontFactory.CreateFont(fontProgram);
float fontSize = 150;
var numberOfPages = pdfDoc.GetNumberOfPages();
PdfPage page = null;
for (var i = 1; i <= numberOfPages; i++)
{
page = pdfDoc.GetPage(i);
Rectangle ps = page.GetPageSize();
//Center the annotation
float bottomLeftX = ps.GetWidth() / 2 - watermarkTrimmingRectangleWidth / 2;
float bottomLeftY = ps.GetHeight() / 2 - watermarkTrimmingRectangleHeight / 2;
Rectangle watermarkTrimmingRectangle = new Rectangle(bottomLeftX, bottomLeftY, watermarkTrimmingRectangleWidth, watermarkTrimmingRectangleHeight);
PdfWatermarkAnnotation pdfWatermark = new PdfWatermarkAnnotation(watermarkTrimmingRectangle);
//Apply linear algebra rotation math
//Create identity matrix
AffineTransform transform = new AffineTransform();//No-args constructor creates the identity transform
//Apply translation
transform.Translate(xTranslation, yTranslation);
//Apply rotation
transform.Rotate(rotationInRads);
PdfFixedPrint fixedPrint = new PdfFixedPrint();
pdfWatermark.SetFixedPrint(fixedPrint);
//Create appearance
Rectangle formRectangle = new Rectangle(formXOffset, formYOffset, formWidth, formHeight);
//Observation: font XObject will be resized to fit inside the watermark rectangle
PdfFormXObject form = new PdfFormXObject(formRectangle);
PdfExtGState gs1 = new PdfExtGState().SetFillOpacity(0.6f);
PdfCanvas canvas = new PdfCanvas(form, pdfDoc);
float[] transformValues = new float[6];
transform.GetMatrix(transformValues);
canvas.SaveState()
.BeginText().SetColor(ColorConstants.GRAY, true).SetExtGState(gs1)
.SetTextMatrix(transformValues[0], transformValues[1], transformValues[2], transformValues[3], transformValues[4], transformValues[5])
.SetFontAndSize(font, fontSize)
.ShowText(watermark)
.EndText()
.RestoreState();
canvas.Release();
pdfWatermark.SetAppearance(PdfName.N, new PdfAnnotationAppearance(form.GetPdfObject()));
pdfWatermark.SetFlags(PdfAnnotation.PRINT);
page.AddAnnotation(pdfWatermark);
}
page?.Flush();
}
I found the watermarking code from this stackoverflow answer and had to modify it a little to match my implementation.
When debugging, stepping through it seems to throw an exception on canvas.SaveState(..)
The exception I get is
((iText.Kernel.PdfException)ex).Message: Document has no pages.
Stacktrace
at iText.Kernel.Pdf.PdfPagesTree.GenerateTree() at iText.Kernel.Pdf.PdfDocument.Close() at iText.Kernel.Pdf.PdfDocument.System.IDisposable.Dispose() at RenderService.Services.PDFMerger.MergeReports(ConcurrentDictionary`2 base64Reports, String password, String watermark) in D:_src\render-service\Solutions\Render Service\Source\RenderService\Services\PDFMerger.cs:line 66
Adding this because it might be relevant but I was having an issue with the iText7 library when it was trying to create a font. The error
System,NullReferenceException Object reference not set to an instance of an object exception
at this line:
PdfFont font = PdfFontFactory.CreateFont(fontProgram);
I'm still having this error but ignored this exception when thrown from the itext.io.dll as mentioned in a comment on the question thread.