2

I want to add a text to the PDF using PDFBox API and rotate it by 45 Degree and place it at the center of the page, The text is dynamic and should be placed in the center always, I got everything else to work except centering piece, I'll appreciate any help. I have this code:

Point2D.Float pageCenter = getCenter(page);

float stringWidth = getStringWidth(watermarkText, font, fontSize);
float textX = pageCenter.x - stringWidth / 2F + center.x;
System.out.println(textX);
float textY = pageCenter.y + center.y;
//System.out.println("Inside cross"+textX+", "+textY);
fontSize = 110.0f;  
cs.transform(Matrix.getRotateInstance(Math.toRadians(45), textX, textY));
cs.moveTo(0, 0);
cs.lineTo(125, 0);
r0.setNonStrokingAlphaConstant(0.20f);

This is the result i want: Output PDF

Tilman Hausherr
  • 17,731
  • 7
  • 58
  • 97
abhishek arya
  • 21
  • 1
  • 3
  • Are all pages portrait, or also landscape? Do you really want 45° or do you want it to be diagonal? Should the text be centered on the diagonal line or be above that line? If centered, can one make an assumption that it is enough to calculate the mid vertical size of an "a"? Will you have a specific font or should all fonts be allowed? – Tilman Hausherr Nov 02 '18 at 08:46
  • Thanks for the reply Tilman, 1. Pages are mostly landscape, i have written a method to handle the portrait and landscape situation by changing height to width as needed. 2. I dont care if the angle is exactly 45, it needs to be diagonally placed, 45 would be precisely in the diagonal thats why i chose 45. 3. The centered text needs to have the middle portion of text exactly in the center of x and y axis, so i need to calculate the center of the page and offset it by text length/2. 4. Ill have a very specific font HELVETICA_BOLD. – abhishek arya Nov 02 '18 at 14:23
  • 1
    if the response helped, please click on the checkmark near the answer to make it the accepted answer. If it didn't or something is unclear or doesn't work, please post a comment. – Tilman Hausherr Nov 09 '18 at 10:24

1 Answers1

5

What I do is to first rotate based on the calculated angle. In this "rotated world" I do a horizontal offset so that the text is in the middle, and also move the text vertically a bit lower, so that it is in the "vertical" middle of an imagined diagonal line (horizontal in the "rotated world").

try (PDDocument doc = new PDDocument())
{
    PDPage page = new PDPage();
    doc.addPage(page);

    PDFont font = PDType1Font.HELVETICA_BOLD;
    try (PDPageContentStream cs =
            new PDPageContentStream(doc, page, PDPageContentStream.AppendMode.APPEND, true, true))
        // use this long constructor when working on existing PDFs
    {
        float fontHeight = 110;
        String text = "Watermark";

        float width = page.getMediaBox().getWidth();
        float height = page.getMediaBox().getHeight();
        int rotation = page.getRotation();
        switch (rotation)
        {
            case 90:
                width = page.getMediaBox().getHeight();
                height = page.getMediaBox().getWidth();
                cs.transform(Matrix.getRotateInstance(Math.toRadians(90), height, 0));
                break;
            case 180:
                cs.transform(Matrix.getRotateInstance(Math.toRadians(180), width, height));
                break;
            case 270:
                width = page.getMediaBox().getHeight();
                height = page.getMediaBox().getWidth();
                cs.transform(Matrix.getRotateInstance(Math.toRadians(270), 0, width));
                break;
            default:
                break;
        }
        float stringWidth = font.getStringWidth(text) / 1000 * fontHeight;
        float diagonalLength = (float) Math.sqrt(width * width + height * height);
        float angle = (float) Math.atan2(height, width);
        float x = (diagonalLength - stringWidth) / 2; // "horizontal" position in rotated world
        float y = -fontHeight / 4; // 4 is a trial-and-error thing, this lowers the text a bit
        cs.transform(Matrix.getRotateInstance(angle, 0, 0));
        cs.setFont(font, fontHeight);
        //cs.setRenderingMode(RenderingMode.STROKE); // for "hollow" effect
        
        PDExtendedGraphicsState gs = new PDExtendedGraphicsState();
        gs.setNonStrokingAlphaConstant(0.2f);
        gs.setStrokingAlphaConstant(0.2f);
        gs.setBlendMode(BlendMode.MULTIPLY);
        cs.setGraphicsStateParameters(gs);
        
        // some API weirdness here. When int, range is 0..255.
        // when float, this would be 0..1f
        cs.setNonStrokingColor(255, 0, 0);
        cs.setStrokingColor(255, 0, 0);

        cs.beginText();
        cs.newLineAtOffset(x, y);
        cs.showText(text);
        cs.endText();
    }
    doc.save("watermarked.pdf");
}

Note that I've set both stroking and non stroking (= fill). This is useful for people who want to try the (disabled) "hollow" appearance, that one uses stroking only. The default mode is fill, i.e. non-stroking.

Tilman Hausherr
  • 17,731
  • 7
  • 58
  • 97
  • 1
    As an alternative to using alpha style transparency I'd propose using a different blend mode, e.g. **Multiply** or **Darken**, in combination with a light watermark color. This usually allows the original content to be more readable still. – mkl Nov 08 '18 at 13:32
  • I also use PDPageContentStream to create text for watermark. But the thing is, the text is selectable. shouldn't watermarks be not selectable? – chitgoks May 31 '19 at 05:52
  • What I added is text, thus it is selectable. Re non selectable, use vector graphics as explained in the users list. – Tilman Hausherr May 31 '19 at 08:30
  • I am working on a use case to highlight PDF text on every page and your code helped me a lot. These Lines are what I was searching for. Thanks a lot @TilmanHausherr :-) PDExtendedGraphicsState gs = new PDExtendedGraphicsState(); gs.setNonStrokingAlphaConstant(0.2f); gs.setStrokingAlphaConstant(0.2f); gs.setBlendMode(BlendMode.MULTIPLY); cs.setGraphicsStateParameters(gs); – Vetrivel PS Jun 16 '22 at 20:07