How to translate x, y origin of a PDPage? - coordinates

I'm new to Apache's PDFBox. I'm using version 2.0.0, taken from the SVN repository.
Actually I try to increase the cropbox of a PDPage object. OK, no challenge. But the page content remains in the lower left corner of the cropbox. I want it centered in the new cropbox space.
I understood that all content is positioned absolutely in PDF. So my question: Is there a way using PDFBox to translate the origin (x, y) of my PDPage or the content elements?
Regards
Hans

The first approach would be to simply change the crop box like this:
PDDocument document = PDDocument.load(new File("data/test.pdf"));
PDDocumentCatalog catalog = document.getDocumentCatalog();
#SuppressWarnings("unchecked")
List<PDPage> pages = catalog.getAllPages();
float expand = 72;
for (PDPage page : pages)
{
PDRectangle cropBox = page.findCropBox();
PDRectangle newCropBox = new PDRectangle();
newCropBox.setLowerLeftX(cropBox.getLowerLeftX() - expand);
newCropBox.setLowerLeftY(cropBox.getLowerLeftY() - expand);
newCropBox.setUpperRightX(cropBox.getUpperRightX() + expand);
newCropBox.setUpperRightY(cropBox.getUpperRightY() + expand);
page.setCropBox(newCropBox);
}
document.save("data/out/test-expand-crop-simple.pdf");
This only works sometimes, though, because according to the specification ISO 32000-1, section 14.11.2 Page Boundaries
The crop, bleed, trim, and art boxes shall not ordinarily extend beyond the boundaries of the media box. If they do, they are effectively reduced to their intersection with the media box.
(also see this answer)
Thus, we have to make sure that the crop box even after enlarging still fits into the media box, e.g. like this:
PDDocument document = PDDocument.load(new File("data/test.pdf"));
PDDocumentCatalog catalog = document.getDocumentCatalog();
#SuppressWarnings("unchecked")
List<PDPage> pages = catalog.getAllPages();
float expand = 72;
for (PDPage page : pages)
{
PDRectangle cropBox = page.findCropBox();
PDRectangle newCropBox = new PDRectangle();
newCropBox.setLowerLeftX(cropBox.getLowerLeftX() - expand);
newCropBox.setLowerLeftY(cropBox.getLowerLeftY() - expand);
newCropBox.setUpperRightX(cropBox.getUpperRightX() + expand);
newCropBox.setUpperRightY(cropBox.getUpperRightY() + expand);
page.setCropBox(newCropBox);
PDRectangle mediaBox = page.findMediaBox();
PDRectangle newMediaBox = new PDRectangle();
newMediaBox.setLowerLeftX(mediaBox.getLowerLeftX() - expand);
newMediaBox.setLowerLeftY(mediaBox.getLowerLeftY() - expand);
newMediaBox.setUpperRightX(mediaBox.getUpperRightX() + expand);
newMediaBox.setUpperRightY(mediaBox.getUpperRightY() + expand);
page.setMediaBox(newMediaBox);
}
document.save("data/out/test-expand-crop-and-media.pdf");

The central code to manipulate the crop box looks as follows:
#Override
protected void treeNodeChanged(PDFTreeNode node)
{
if (node instanceof PDFFloatNode)
{
PDFFloatNode nodeF = (PDFFloatNode)node;
String strDataKey = node.getDataKey();
if ("x".equals(strDataKey))
{
m_rect.setLowerLeftX(nodeF.getFloat());
}
else if ("y".equals(strDataKey))
{
m_rect.setLowerLeftY(nodeF.getFloat());
}
else if ("width".equals(strDataKey))
{
m_rect.setUpperRightX(nodeF.getFloat());
}
else if ("height".equals(strDataKey))
{
m_rect.setUpperRightY(nodeF.getFloat());
}
}
if (m_parent != null)
{
m_parent.treeNodeChanged(node);
}
}
Where m_rect is an instance of PDRectangle. In fact this information doesn't help you, #mkl. In between I found an information to solve the problem (I hope). There is an operation which is obviously not known in PDFBox.
<</PageOffset [-20 20]>> setpagedevice
Now I'm looking for a way to implement it into the PDF.
Thanks
Hans

The Pageoffset dictionary entry was not very helpful. It can be used as a parameter for GhostScript. But GS does not really create a dictionary entry. Instead it offsets the content of all pages. A capability which doesn't exist in PDFBox.
I found a solution using LayerUtility:
PDDocument docIn = null;
try
{
docIn = PDDocument.load("./pdf/Test1.pdf");
float fBorder = 10 * MM_TO_UNITS; // Arbitrary 10 mm
PDDocument docOut = new PDDocument();
PDPage pageIn = (PDPage)docIn.getDocumentCatalog().getPages().getKids().get(0);
PDRectangle rectCrop = pageIn.findCropBox();
PDPage pageClone = clonePage(docOut, pageIn, true),
pageOut = new PDPage(
new PDRectangle(rectCrop.getWidth() + 2 * fBorder, rectCrop.getHeight() + 2 * fBorder)
);
docOut.addPage(pageOut);
PDPageContentStream stream = new PDPageContentStream(docOut, pageOut);
stream.close();
LayerUtility lu = new LayerUtility(docOut);
lu.wrapInSaveRestore(pageOut);
PDXObjectForm xobj = lu.importPageAsForm(docIn, pageClone);
AffineTransform at = new AffineTransform();
// That's the point where x,y offset takes place
at.setToTranslation(fBorder, fBorder);
lu.appendFormAsLayer(pageOut, xobj, at, "layerx");
docOut.addPage(pageOut);
docOut.save("./pdf/Test1out.pdf");
}
finally
{
if (docIn != null)
{
docIn.close();
}
}
I'm not very happy with this. Because it changes the page structure. But at least I have a solution.
Regards
Hans

To increase the CropBox, you set the MediaBox like this:
PDRectangle box = new PDRectangle(pageWidth, pageHeight);
page.setMediaBox(box); // MediaBox > BleedBox > TrimBox/CropBox

Related

AR camera distance measurement

I have a question about AR(Augmented Reality).
I want to know how to show the distance information(like centermeter...) between AR camera and target object. (Using Smartphone)
Can I do that in Unity ? Should I use AR Foundation? and with ARcore? How to write code?
I tried finding some relative code(below), but it seems just like Printing information between object and object, nothing about "AR camera"...
var other : Transform;
if (other) {
var dist = Vector3.Distance(other.position, transform.position);
print ("Distance to other: " + dist);
}
Thank again!
Here is how to do it Unity and AR Foundation 4.1.
This example script prints the depth in meters at the depth texture's center and works both with ARCore and ARKit:
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
public class GetDepthOfCenterPixel : MonoBehaviour {
// assign this field in inspector
[SerializeField] AROcclusionManager manager = null;
IEnumerator Start() {
while (ARSession.state < ARSessionState.SessionInitializing) {
// manager.descriptor.supportsEnvironmentDepthImage will return a correct value if ARSession.state >= ARSessionState.SessionInitializing
yield return null;
}
if (!manager.descriptor.supportsEnvironmentDepthImage) {
Debug.LogError("!manager.descriptor.supportsEnvironmentDepthImage");
yield break;
}
while (true) {
if (manager.TryAcquireEnvironmentDepthCpuImage(out var cpuImage) && cpuImage.valid) {
using (cpuImage) {
Assert.IsTrue(cpuImage.planeCount == 1);
var plane = cpuImage.GetPlane(0);
var dataLength = plane.data.Length;
var pixelStride = plane.pixelStride;
var rowStride = plane.rowStride;
Assert.AreEqual(0, dataLength % rowStride, "dataLength should be divisible by rowStride without a remainder");
Assert.AreEqual(0, rowStride % pixelStride, "rowStride should be divisible by pixelStride without a remainder");
var numOfRows = dataLength / rowStride;
var centerRowIndex = numOfRows / 2;
var centerPixelIndex = rowStride / (pixelStride * 2);
var centerPixelData = plane.data.GetSubArray(centerRowIndex * rowStride + centerPixelIndex * pixelStride, pixelStride);
var depthInMeters = convertPixelDataToDistanceInMeters(centerPixelData.ToArray(), cpuImage.format);
print($"depth texture size: ({cpuImage.width},{cpuImage.height}), pixelStride: {pixelStride}, rowStride: {rowStride}, pixel pos: ({centerPixelIndex}, {centerRowIndex}), depthInMeters of the center pixel: {depthInMeters}");
}
}
yield return null;
}
}
float convertPixelDataToDistanceInMeters(byte[] data, XRCpuImage.Format format) {
switch (format) {
case XRCpuImage.Format.DepthUint16:
return BitConverter.ToUInt16(data, 0) / 1000f;
case XRCpuImage.Format.DepthFloat32:
return BitConverter.ToSingle(data, 0);
default:
throw new Exception($"Format not supported: {format}");
}
}
}
I'm working on AR depth image as well and the basic idea is:
Acquire an image using API, normally it's in format Depth16;
Split the image into shortbuffers, as Depth16 means each pixel is 16 bits;
Get the distance value, which is stored in the lower 13 bits of each shortbuffer, you can do this by doing (shortbuffer & 0x1ff), then you can have the distance for each pixel, normally it's in millimeters.
By doing this through all the pixels, you can create a depth image and store it as jpg or other formats, here's the sample code of using AR Engine to get the distance:
try (Image depthImage = arFrame.acquireDepthImage()) {
int imwidth = depthImage.getWidth();
int imheight = depthImage.getHeight();
Image.Plane plane = depthImage.getPlanes()[0];
ShortBuffer shortDepthBuffer = plane.getBuffer().asShortBuffer();
File sdCardFile = Environment.getExternalStorageDirectory();
Log.i(TAG, "The storage path is " + sdCardFile);
File file = new File(sdCardFile, "RawdepthImage.jpg");
Bitmap disBitmap = Bitmap.createBitmap(imwidth, imheight, Bitmap.Config.RGB_565);
for (int i = 0; i < imheight; i++) {
for (int j = 0; j < imwidth; j++) {
int index = (i * imwidth + j) ;
shortDepthBuffer.position(index);
short depthSample = shortDepthBuffer.get();
short depthRange = (short) (depthSample & 0x1FFF);
//If you only want the distance value, here it is
byte value = (byte) depthRange;
byte value = (byte) depthRange ;
disBitmap.setPixel(j, i, Color.rgb(value, value, value));
}
}
//I rotate the image for a better view
Matrix matrix = new Matrix();
matrix.setRotate(90);
Bitmap rotatedBitmap = Bitmap.createBitmap(disBitmap, 0, 0, imwidth, imheight, matrix, true);
try {
FileOutputStream out = new FileOutputStream(file);
rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
out.flush();
out.close();
MainActivity.num++;
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
While the answers are great, they may be too complicated and advanced for this question, which is about the distance between the ARCamera and another object, and not about the depth of pixels and their occlusion.
transform.position gives you the position of whatever game object you attach the script to in the hierarchy. So attach the script to the ARCamera object. And obviously, other should be the target object.
Alternately, you can get references to the two game objects using inspector variables or GetComponent
/raycasting should be in update/
Ray ray = new Ray(cam.transform.position, cam.transform.forward);
if (Physics.Raycast(ray, out info, 50f, layerMaskAR))//50 meters detection range bcs of 50f
{
distanca.text = string.Format("{0}: {1:N2}m", info.collider.name, info.distance, 2);
}
This is func that does it what u need with this is ofc on UI txt element and layer assigne to object/prefab.
int layerMaskAR = 1 << 6; (here u see 6 bcs 6th is my custom layer ,,layerMaskAR,,)
This is ray cating on to objects in only this layer rest object are ignored(if u dont want to ignore anything remove layerMask from raycast and it will print out name of anything with collider).
Totally doable by this line of code
Vector3.Distance(gameObject.transform.position, Camera.main.transform.position)

Placing Text on Multiple Imported PDF Pages with iTextSharp [duplicate]

I am trying to add a header to existing pdf documents in Java with iText. I can add the header at a fixed place on the document, but all the documents are different page sizes, so it is not always at the top of the page. I have tried getting the page size so that I could calculate the position of the header, but it seems as if the page size is not actually what I want. On some documents, calling reader.getPageSize(i).getTop(20) will place the text in the right place at the top of the page, however, on some different documents it will place it half way down the page. Most of the pages have been scanned be a Xerox copier, if that makes a difference. Here is the code I am using:
PdfReader reader = new PdfReader(readFilePath);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(writeFilePath));
BaseFont bf = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
for (int i = 1; i <= reader.getNumberOfPages(); i++) {
PdfContentByte cb = stamper.getOverContent(i);
cb.beginText();
cb.setFontAndSize(bf, 14);
float x = reader.getPageSize(i).getWidth() / 2;
float y = reader.getPageSize(i).getTop(20);
cb.showTextAligned(PdfContentByte.ALIGN_CENTER, "Copy", x, y, 0);
cb.endText();
}
stamper.close();
PDF that works correctly
PDF that works incorrectly
Take a look at the StampHeader1 example. I adapted your code, introducing ColumnText.showTextAligned() and using a Phrase for the sake of simplicity (maybe you can change that part of your code too):
public void manipulatePdf(String src, String dest) throws IOException, DocumentException {
PdfReader reader = new PdfReader(src);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
Phrase header = new Phrase("Copy", new Font(FontFamily.HELVETICA, 14));
for (int i = 1; i <= reader.getNumberOfPages(); i++) {
float x = reader.getPageSize(i).getWidth() / 2;
float y = reader.getPageSize(i).getTop(20);
ColumnText.showTextAligned(
stamper.getOverContent(i), Element.ALIGN_CENTER,
header, x, y, 0);
}
stamper.close();
reader.close();
}
As you have found out, this code assumes that no rotation was defined.
Now take a look at the StampHeader2 example. I'm using your "Wrong" file and I've added one extra line:
stamper.setRotateContents(false);
By telling the stamper not to rotate the content I'm adding, I'm adding the content using the coordinates as if the page isn't rotated. Please take a look at the result: stamped_header2.pdf. We added "Copy" at the top of the page, but as the page is rotated, we see the word appear on the side. The word is rotated because the page is rotated.
Maybe that's what you want, maybe it isn't. If it isn't, please take a look at StampHeader3 in which I calculate x and y differently, based on the rotation of the page:
if (reader.getPageRotation(i) % 180 == 0) {
x = reader.getPageSize(i).getWidth() / 2;
y = reader.getPageSize(i).getTop(20);
}
else {
x = reader.getPageSize(i).getHeight() / 2;
y = reader.getPageSize(i).getRight(20);
}
Now the word "Copy" appears on what is perceived as the "top of the page" (but in reality, it could be the side of the page): stamped_header3.pdf

iTextSharp IExtRenderListener and boundingbox [duplicate]

I have a pdf which comprises of some data, followed by some whitespace. I don't know how large the data is, but I'd like to trim off the whitespace following the data
PdfReader reader = new PdfReader(PDFLOCATION);
Rectangle rect = new Rectangle(700, 2000);
Document document = new Document(rect);
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(SAVELCATION));
document.open();
int n = reader.getNumberOfPages();
PdfImportedPage page;
for (int i = 1; i <= n; i++) {
document.newPage();
page = writer.getImportedPage(reader, i);
Image instance = Image.getInstance(page);
document.add(instance);
}
document.close();
Is there a way to clip/trim the whitespace for each page in the new document?
This PDF contains vector graphics.
I'm usung iTextPDF, but can switch to any Java library (mavenized, Apache license preferred)
As no actual solution has been posted, here some pointers from the accompanying itext-questions mailing list thread:
As you want to merely trim pages, this is not a case of PdfWriter + getImportedPage usage but instead of PdfStamper usage. Your main code using a PdfStamper might look like this:
PdfReader reader = new PdfReader(resourceStream);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream("target/test-outputs/test-trimmed-stamper.pdf"));
// Go through all pages
int n = reader.getNumberOfPages();
for (int i = 1; i <= n; i++)
{
Rectangle pageSize = reader.getPageSize(i);
Rectangle rect = getOutputPageSize(pageSize, reader, i);
PdfDictionary page = reader.getPageN(i);
page.put(PdfName.CROPBOX, new PdfArray(new float[]{rect.getLeft(), rect.getBottom(), rect.getRight(), rect.getTop()}));
stamper.markUsed(page);
}
stamper.close();
As you see I also added another argument to your getOutputPageSize method to-be. It is the page number. The amount of white space to trim might differ on different pages after all.
If the source document did not contain vector graphics, you could simply use the iText parser package classes. There even already is a TextMarginFinder based on them. In this case the getOutputPageSize method (with the additional page parameter) could look like this:
private Rectangle getOutputPageSize(Rectangle pageSize, PdfReader reader, int page) throws IOException
{
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
TextMarginFinder finder = parser.processContent(page, new TextMarginFinder());
Rectangle result = new Rectangle(finder.getLlx(), finder.getLly(), finder.getUrx(), finder.getUry());
System.out.printf("Text/bitmap boundary: %f,%f to %f, %f\n", finder.getLlx(), finder.getLly(), finder.getUrx(), finder.getUry());
return result;
}
Using this method with your file test.pdf results in:
As you see the code trims according to text (and bitmap image) content on the page.
To find the bounding box respecting vector graphics, too, you essentially have to do the same but you have to extend the parser framework used here to inform its listeners (the TextMarginFinder essentially is a listener to drawing events sent from the parser framework) about vector graphics operations, too. This is non-trivial, especially if you don't know PDF syntax by heart yet.
If your PDFs to trim are not too generic but can be forced to include some text or bitmap graphics in relevant positions, though, you could use the sample code above (probably with minor changes) anyways.
E.g. if your PDFs always start with text on top and end with text at the bottom, you could change getOutputPageSize to create the result rectangle like this:
Rectangle result = new Rectangle(pageSize.getLeft(), finder.getLly(), pageSize.getRight(), finder.getUry());
This only trims top and bottom empty space:
Depending on your input data pool and requirements this might suffice.
Or you can use some other heuristics depending on your knowledge on the input data. If you know something about the positioning of text (e.g. the heading to always be centered and some other text to always start at the left), you can easily extend the TextMarginFinder to take advantage of this knowledge.
Recent (April 2015, iText 5.5.6-SNAPSHOT) improvements
The current development version, 5.5.6-SNAPSHOT, extends the parser package to also include vector graphics parsing. This allows for an extension of iText's original TextMarginFinder class implementing the new ExtRenderListener methods like this:
#Override
public void modifyPath(PathConstructionRenderInfo renderInfo)
{
List<Vector> points = new ArrayList<Vector>();
if (renderInfo.getOperation() == PathConstructionRenderInfo.RECT)
{
float x = renderInfo.getSegmentData().get(0);
float y = renderInfo.getSegmentData().get(1);
float w = renderInfo.getSegmentData().get(2);
float h = renderInfo.getSegmentData().get(3);
points.add(new Vector(x, y, 1));
points.add(new Vector(x+w, y, 1));
points.add(new Vector(x, y+h, 1));
points.add(new Vector(x+w, y+h, 1));
}
else if (renderInfo.getSegmentData() != null)
{
for (int i = 0; i < renderInfo.getSegmentData().size()-1; i+=2)
{
points.add(new Vector(renderInfo.getSegmentData().get(i), renderInfo.getSegmentData().get(i+1), 1));
}
}
for (Vector point: points)
{
point = point.cross(renderInfo.getCtm());
Rectangle2D.Float pointRectangle = new Rectangle2D.Float(point.get(Vector.I1), point.get(Vector.I2), 0, 0);
if (currentPathRectangle == null)
currentPathRectangle = pointRectangle;
else
currentPathRectangle.add(pointRectangle);
}
}
#Override
public Path renderPath(PathPaintingRenderInfo renderInfo)
{
if (renderInfo.getOperation() != PathPaintingRenderInfo.NO_OP)
{
if (textRectangle == null)
textRectangle = currentPathRectangle;
else
textRectangle.add(currentPathRectangle);
}
currentPathRectangle = null;
return null;
}
#Override
public void clipPath(int rule)
{
}
(Full source: MarginFinder.java)
Using this class to trim the white space results in
which is pretty much what one would hope for.
Beware: The implementation above is far from optimal. It is not even correct as it includes all curve control points which is too much. Furthermore it ignores stuff like line width or wedge types. It actually merely is a proof-of-concept.
All test code is in TestTrimPdfPage.java.

Importing PDF position PDFStamper

I'm lost at the moment.
What I try to accomplish is adding one PDF on another (like a watermark).
The problem is that I dont seems to understand the coordinate system that is used because
my watermark just behaves unexpected.
The two PDFs have different dimensions.
My target has the following dimensions:
595 height
842 width
The PDF that shall be added has this dimension:
41 height
552 width
In my code I do the following:
public bool AddPdf(ref PdfReader pdfSource, ref PdfReader pdfTarget, ref FileStream destination)
{
PdfStamper stamper = null;
try
{
stamper = new PdfStamper( pdfSource, destination );
PdfImportedPage importatedPage = stamper.GetImportedPage(pdfTarget, 1);
PdfContentByte background;
for (int iPage = 1; iPage <= pdfSource.NumberOfPages; iPage++)
{
background = stamper.GetOverContent(iPage);
background.AddTemplate(importatedPage, 0, 0 + importHeight);
}
}
When I do this I would expect my watermark to appear in the bottom left.
Instead it is somewhere of the page (I dont see it). Just for testing I hardcoded 600 as y position and then it is centered vertically on the page.
Can someone give me a tip please?
So i solved the issue.
The problem was that the sourcepdf had a cropbox - i only needed to correct my x and y position with that information:
PdfStamper stamper = null;
try
{
stamper = new PdfStamper(pdfSource, destination);
PdfImportedPage importatedPage = stamper.GetImportedPage(pdfTarget, 1);
PdfContentByte background;
for (int iPage = 1; iPage <= pdfSource.NumberOfPages; iPage++)
{
background = stamper.GetOverContent(iPage);
// here comes the important part
Rectangle cropBox = pdfSource.GetCropBox(iPage);
float xCorrected = 0 + cropBox.Left;
float yCorrected = 0 + cropBox.Bottom;
background.AddTemplate(importatedPage, xCorrected, yCorrected);
}
}
Take in mind that in case the pdf that you want to stamp on your original has also a cropbox, you need to reduce the x,y by x,y of that cropbox again.

iTextSharp - Some pages not stamped as expected

I'm using iTextSharp 5.0.6 to read an existing PDF, iterate each page stamping text on each, and then writing out the newly stamped PDF. The issue I'm faced with is that this isn't working 100% of the time. For some PDFs every page is stamped as expected, for others most pages are stamped while some are not. Seems as if there's potentially an issue where the stamper's GetOverContent() is not returning the top-most layer, but that's just an assumption. Has anyone had a similar issue?
using iTextSharp.text;
using iTextSharp.text.pdf;
const string WATERMARK_TEXT = "John Doe";
static void Main(string[] args)
{
string masterPdf = "master.pdf";
string pdfToCreate = "watermark.pdf";
byte[] bytes = StampPDF(masterPdf);
using (FileStream stream = new FileStream(pdfToCreate, FileMode.Create))
{
stream.Write(bytes, 0, bytes.Length);
}
}
static byte[] StampPDF(string PdfPath)
{
using (MemoryStream memoryStream = new MemoryStream())
{
PdfReader reader = new PdfReader(PdfPath);
int pageCount = reader.NumberOfPages;
PdfStamper stamper = new PdfStamper(reader, memoryStream);
float fontSize = 9;
float textAngle = 0f;
BaseFont font = BaseFont.CreateFont(BaseFont.TIMES_ROMAN, BaseFont.WINANSI, BaseFont.EMBEDDED);
BaseColor backgroundColor = new BaseColor(0, 0, 0);
BaseColor fontColor = new BaseColor(255, 255, 255);
float padding = 2f;
float fontWidth = font.GetWidthPoint(WATERMARK_TEXT, fontSize);
iTextSharp.text.Rectangle pageSize;
PdfContentByte pageContents;
for (int i = 1; i <= pageCount; i++)
{
pageSize = reader.GetPageSize(i);
pageContents = stamper.GetOverContent(i);
//draw a rectangle
pageContents.SetColorFill(backgroundColor);
pageContents.MoveTo(pageSize.Width - (fontWidth + padding), 0f);
pageContents.LineTo(pageSize.Width, 0f);
pageContents.LineTo(pageSize.Width, 14f);
pageContents.LineTo(pageSize.Width - (fontWidth + padding), 14f);
pageContents.Fill();
//drop our watermark on top of the rectangle we just created
pageContents.BeginText();
pageContents.SetColorFill(fontColor);
pageContents.SetFontAndSize(font, fontSize);
pageContents.ShowTextAligned(PdfContentByte.ALIGN_LEFT, WATERMARK_TEXT, pageSize.Width - fontWidth, 4, textAngle);
pageContents.EndText();
}
stamper.Close();
reader.Close();
return memoryStream.ToArray();
}
}
For those that may encounter the same problem the key is inspecting the CropBox. Since the dimensions of a PDF's CropBox may be less than that of its PageSize you need to conditionally use one or the other. So, based on the code sample above the for loop would be altered as so:
for (int i = 1; i <= pageCount; i++)
{
mediaBox = reader.GetPageSize(i);
cropBox = reader.GetCropBox(i);
overContent = stamper.GetOverContent(i);
if (cropBox != null && (cropBox.Width < mediaBox.Width || cropBox.Height < cropBox.Height))
mediaBox = cropBox;
//draw a rectangle
overContent.SetColorFill(backgroundColor);
overContent.MoveTo(mediaBox.Right - (fontWidth + fontPadding), mediaBox.Bottom);
overContent.LineTo(mediaBox.Right, mediaBox.Bottom);
overContent.LineTo(mediaBox.Right, mediaBox.Bottom + rectangleHeight);
overContent.LineTo(mediaBox.Right - (fontWidth + fontPadding), mediaBox.Bottom + rectangleHeight);
overContent.ClosePathFillStroke();
//drop our watermark on top of the rectangle we just created
overContent.BeginText();
overContent.SetColorFill(fontColor);
overContent.SetFontAndSize(font, fontSize);
overContent.ShowTextAligned(PdfContentByte.ALIGN_LEFT, WATERMARK_TEXT, mediaBox.Right - fontWidth, mediaBox.Bottom + (rectangleHeight - fontSize), textAngle);
overContent.EndText();
}
You've made two mistakes:
You're assuming that the pages aren't rotated, but they can be: 90, 180, 270. Note that I've never seen a 180 page, but its legal. When drawing to a rotated page, you have to take that rotation into account when drawing on it. Fun with transformation matrices.
You're assuming that the page's (unrotated) lower left corner is 0,0. You're basing your measurements on the page's width and height (close), but aren't adjusting for any offset in that bottom left corner.
There are three ways to do a landscape page:
11"x8.5"
8.5"x11" # 90 degrees rotation
8.5"x11" # 270 degrees rotation
Technically, a 4th way is to build an 11x8.5 # 180, but anyone writing such code should be Punished. A lot.
There are various SO questions floating about that give details on how to deal with page rotation. Going by your code, I'd say you'll figure out the llx,lly thing pretty quickly.