5

I have done watermark text in pdf file using itextpdf, but when I copy the actual text of the pdf file it allows us to copy the watermark text too. Is there anyway that we can restrict our watermark text as Non-selectable?

Image watermark_image = Image.getInstance(imageFile.getAbsolutePath());

while (i < num_of_pages) {
    i++;
    //To pass our watermark over text
    add_waterMark = pdfStamper.getOverContent(i);

    //To pass our watermark under text
    //add_waterMark = pdfStamper.getUnderContent(i);

    // watermark_image.
    watermark_image.setAbsolutePosition(0, 0);

    add_waterMark.beginText();
    //add_waterMark.setTextRenderingMode(number_of_pages);

    //watermark_image is png file
    add_waterMark.addImage(watermark_image);

    add_waterMark.endText();
}

i have written code using PdfContentByte, it is hollow and horizontal, but i am able to copy the watermark text here :( i want to replace my code using PdfPatternPainter, if may be possible because PdfPatternPainter inherits all the Fields of PdfContentByte.

here is code using PdfContentByte :

int n = reader.getNumberOfPages();

        PdfContentByte under;
        PdfGState gstate = new PdfGState();
        gstate.setFillOpacity(0.35f);
        gstate.setStrokeOpacity(0.35f);
        BaseFont font = BaseFont.createFont(BaseFont.HELVETICA_BOLD,
                BaseFont.WINANSI, BaseFont.EMBEDDED);

        Rectangle size = reader.getPageSizeWithRotation(1);
//          float angle = (float) ((180 * (Math.asin(size.getHeight()
//                  / Math.sqrt(size.getWidth() * size.getWidth()
//                          + size.getHeight() *       size.getHeight())))) / Math.PI);
        int i = 1;

        while (i < n + 1) {

            under = stamper.getOverContent(i);
            under.setColorStroke(new BaseColor(192, 192, 192));
            i++;
            under.beginText();
                under.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_STROKE);
            under.setLineWidth(0.85f);
            under.setLineDash(0.4f, 0.4f, 0.2f);

            under.showTextAlignedKerned(Element.ALIGN_MIDDLE,
                    "user name must required", 250, 780,
                    MYConstants.WATERMARK_PAGE_ANGLE);
            under.setFontAndSize(font, 42);
            under.showTextAlignedKerned(Element.ALIGN_MIDDLE,
                    "user Company name required", 200, 730,
                    MyConstants.WATERMARK_PAGE_ANGLE);
            under.setFontAndSize(font, 42);
            under.showTextAlignedKerned(Element.ALIGN_MIDDLE,
                    "Plesae enter your email id", 150, 680,
                    MyConstants.WATERMARK_PAGE_ANGLE);


            under.endText();
        }

     stamper.close();
} 
KP_JavaDev
  • 222
  • 2
  • 4
  • 11
  • I think it's an all or nothing situation, security on the whole document or no security at all – gwillie Dec 03 '13 at 13:05
  • Hi gwillie, i can do the same thing using png file and then using png file as watermark text on pdf file, but in this case my watermark text is coming bit blur, not clear text. – KP_JavaDev Dec 03 '13 at 13:17
  • i have seen such pdf file where selecting entire page does not allow copying the watermark text. but that is not implemented using any java api. – KP_JavaDev Dec 03 '13 at 13:19
  • so you are saying that you can make certain elements in a pdf non-selectable, well that's new to me. if you think you can make it non-selectable, post a link to a pdf file that has this security feature and i'll attempt to break it lol :) – gwillie Dec 03 '13 at 13:34
  • yes, i am creating one png file which is nothing just a text image, then i am putting that image into pdf file, but my watermark text ( which is just an text image ) is not coming very much sharp and clear. – KP_JavaDev Dec 03 '13 at 13:48
  • i'm hearing you, however i dont think you're hearing me. pdf security is crap, i know its crap, there are many apps that break pdf security, so i see no way to protect certain elements in a pdf. but i'll admit i know zero of the inner workings of pdf or its security, but any file that has security can easily be removed afaik. my thinking is what you are trying to achieve is futile, someone can google how to crack a secured pdf and all this work you're going to do is shot, homework or not – gwillie Dec 03 '13 at 14:02
  • here is nothing related to pdf security, i know how to handle the security parts. the only things here needed is, whenever you will try to select entire text of pdf, the watermark text should not be selected. i just want to know is there any approach other then using png image file. – KP_JavaDev Dec 03 '13 at 14:08
  • i have never heard of this in pdf, it would be a fantastic feature though, but at the end it would be up to the pdf viewer whether to allow certain elements to be selectable. i am saying everything is selectable or nothing at all as at 2013, if you know what i mean – gwillie Dec 03 '13 at 14:19
  • BTW, you do `beginText(); addImage(watermark_image); endText();` - adding an image in a text block is not exactly valid... – mkl Dec 03 '13 at 19:44

1 Answers1

8

In the comments to the original question, the OP clarified

the only things here needed is, whenever you will try to select entire text of pdf, the watermark text should not be selected. i just want to know is there any approach other then using png image file.

This sounds like using a pattern with text content might be just right for you. Here a proof-of-concept using iText:

void addTextPatternToOverContent(File source, File target) throws IOException, DocumentException
{
    PdfReader reader = new PdfReader(source.getPath());
    OutputStream os = new FileOutputStream(target);
    PdfStamper stamper = new PdfStamper(reader, os);

    PdfPatternPainter painter = stamper.getOverContent(1).createPattern(200, 150);
    painter.setColorFill(BaseColor.BLACK);
    painter.beginText();
    painter.setTextMatrix(AffineTransform.getTranslateInstance(0, 50));
    painter.setFontAndSize(BaseFont.createFont(), 100);
    painter.showText("Test");
    painter.endText();

    for (int i = reader.getNumberOfPages(); i > 0; i--)
    {
        PdfContentByte overContent = stamper.getOverContent(i);
        overContent.setColorFill(new PatternColor(painter));
        overContent.rectangle(200, 300, 200, 150);
        overContent.fill();
    }

    stamper.close();
    os.close();
    reader.close();
}

The text from the pattern is not selectable in Adobe Reader. And @gwillie, no need for heavy lifting here, this is no security feature, the text is easy to find once you know where it's put.

Please be aware that patterns can be fickle. For free positioning of the text, you might want to fill a rectangle with that pattern in a fixed form xobject (thus being on edge with the pattern tiles) and put that xobject wherever you want.

And quite probably you will want to apply transparency or else not fill but only thinly stroke the letters like with this pattern painter:

    PdfPatternPainter painter = stamper.getOverContent(1).createPattern(200, 150);
    painter.setColorStroke(BaseColor.BLACK);
    painter.beginText();
    painter.setTextRenderingMode(PdfPatternPainter.TEXT_RENDER_MODE_STROKE);
    painter.setTextMatrix(AffineTransform.getTranslateInstance(0, 50));
    painter.setFontAndSize(BaseFont.createFont(), 100);
    painter.showText("Test");
    painter.endText();

PS: Taking the OP's new detailed code into account a method adding that as non-copy&paste-able stuff may look like this:

void addUserPatternToOverContent(File source, File target) throws IOException, DocumentException
{
    PdfReader reader = new PdfReader(source.getPath());
    OutputStream os = new FileOutputStream(target);
    PdfStamper stamper = new PdfStamper(reader, os);

    Rectangle pageSize = reader.getPageSize(1);
    final float WATERMARK_PAGE_ANGLE = -72;

    BaseFont font = BaseFont.createFont(BaseFont.HELVETICA_BOLD, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED);

    PdfPatternPainter painter = stamper.getOverContent(1).createPattern(pageSize.getWidth(), pageSize.getHeight());
    painter.setColorStroke(new BaseColor(192, 192, 192));
    painter.setLineWidth(0.85f);
    painter.setLineDash(0.4f, 0.4f, 0.2f);

    painter.beginText();
    painter.setTextRenderingMode(PdfPatternPainter.TEXT_RENDER_MODE_STROKE);
    painter.setFontAndSize(font, 42);
    painter.showTextAlignedKerned(Element.ALIGN_MIDDLE,
            "user name must required", 250, 780,
            WATERMARK_PAGE_ANGLE);
    painter.showTextAlignedKerned(Element.ALIGN_MIDDLE,
            "user Company name required", 200, 730,
            WATERMARK_PAGE_ANGLE);
    painter.showTextAlignedKerned(Element.ALIGN_MIDDLE,
            "Plesae enter your email id", 150, 680,
            WATERMARK_PAGE_ANGLE);
    painter.endText();

    for (int i = reader.getNumberOfPages(); i > 0; i--)
    {
        Rectangle thisPageSize = reader.getPageSize(i);
        PdfContentByte overContent = stamper.getOverContent(i);
        overContent.setColorFill(new PatternColor(painter));
        overContent.rectangle(thisPageSize.getLeft(), thisPageSize.getBottom(), thisPageSize.getWidth(), thisPageSize.getHeight());
        overContent.fill();
    }

    stamper.close();
    os.close();
    reader.close();
}

which shows e.g. as

the water sign applied to a sample page

BTW, I changed the Helvetica Bold to not-embedded as it is one of the standard 14 fonts.

PPS: In the comments the OP wondered furthermore

is there any way that our watermark text go backgroud of actual text and in case of Image it goes foreground. i have tried with getUnderContent() but our Pdf image Hide this text.

Short of sorting the page contents (first images, then the water mark, then the text - not trivial in general) one can try and collect all the areas with images (using the iText parser package classes), then put the water mark to the undercontent, and to the overcontent also put the water mark but only in the combined regions in which you found images. Much easier to implement but a bit dirty. E.g.

void addUserPatternOverAndUnder(File source, File target) throws IOException, DocumentException
{
    PdfReader reader = new PdfReader(source.getPath());
    OutputStream os = new FileOutputStream(target);
    PdfStamper stamper = new PdfStamper(reader, os);

    Rectangle pageSize = reader.getPageSize(1);
    final float WATERMARK_PAGE_ANGLE = -60;

    BaseFont font = BaseFont.createFont(BaseFont.HELVETICA_BOLD, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED);

    PdfPatternPainter painter = stamper.getOverContent(1).createPattern(pageSize.getWidth(),
            pageSize.getHeight());
    painter.setColorStroke(new BaseColor(0, 192, 192));
    painter.setLineWidth(0.85f);
    painter.setLineDash(0.4f, 0.4f, 0.2f);

    painter.beginText();
    painter.setTextRenderingMode(PdfPatternPainter.TEXT_RENDER_MODE_STROKE);
    painter.setFontAndSize(font, 42);
    painter.showTextAlignedKerned(Element.ALIGN_MIDDLE, "user name must required", 150, 780,
            WATERMARK_PAGE_ANGLE);
    painter.showTextAlignedKerned(Element.ALIGN_MIDDLE, "user Company name required", 100, 730,
            WATERMARK_PAGE_ANGLE);
    painter.showTextAlignedKerned(Element.ALIGN_MIDDLE, "Plesae enter your email id", 050, 680,
            WATERMARK_PAGE_ANGLE);
    painter.endText();

    for (int i = reader.getNumberOfPages(); i > 0; i--)
    {
        Rectangle thisPageSize = reader.getPageSize(i);

        PdfContentByte underContent = stamper.getUnderContent(i);
        underContent.setColorFill(new PatternColor(painter));
        underContent.rectangle(thisPageSize.getLeft(), thisPageSize.getBottom(), thisPageSize.getWidth(),
                thisPageSize.getHeight());
        underContent.fill();

        List<Vector> path = getImageBordersPathPoints(reader, i);
        if (path != null && !path.isEmpty())
        {
            PdfContentByte overContent = stamper.getOverContent(i);
            overContent.setColorFill(new PatternColor(painter));

            for (int index = 0; index < path.size(); index++)
            {
                Vector corner = path.get(index);
                if (index % 4 == 0)
                {
                    overContent.moveTo(corner.get(Vector.I1), corner.get(Vector.I2));
                }
                else
                {
                    overContent.lineTo(corner.get(Vector.I1), corner.get(Vector.I2));
                    if (index % 4 == 3)
                    {
                        overContent.closePath();
                    }
                }
            }
            overContent.fill();
        }
    }

    stamper.close();
    os.close();
    reader.close();
}

static Vector A = new Vector(0, 0, 1);
static Vector B = new Vector(1, 0, 1);
static Vector C = new Vector(1, 1, 1);
static Vector D = new Vector(0, 1, 1);
static List<Vector> positive = Arrays.asList(A, B, C, D);
static List<Vector> negative = Arrays.asList(A, D, C, B);

List<Vector> getImageBordersPathPoints(PdfReader reader, int page) throws IOException
{
    final List<Vector> result = new ArrayList<Vector>();
    RenderListener listener = new RenderListener()
    {
        public void renderText(TextRenderInfo renderInfo)
        {
        }

        public void endTextBlock()
        {
        }

        public void beginTextBlock()
        {
        }

        public void renderImage(ImageRenderInfo renderInfo)
        {
            Matrix ctm = renderInfo.getImageCTM();
            List<Vector> unitCorners = ctm.getDeterminant() > 0 ? positive : negative;

            for (Vector corner : unitCorners)
            {
                result.add(corner.cross(ctm));
            }
        }
    };

    PdfReaderContentParser parser = new PdfReaderContentParser(reader);
    parser.processContent(page, listener);
    return result;
}

(Vector is com.itextpdf.text.pdf.parser.Vector)

The result (the Dexx logo is an image, the address is text):

the water sign applied to a sample page the new way

Unfortunately, though, the parsing API does not yet signal vector graphics. Thus, vector graphics (e.g. colored backgrounds which essentially are filled rectangles drawn using vector graphics operations) cover the water mark.

mkl
  • 90,588
  • 15
  • 125
  • 265
  • Hi mkl, Thanks a lot for your code :) i will try this code. and will let you know if anything else required. – KP_JavaDev Dec 04 '13 at 04:50
  • This code is somewhat nice and efficient but hiding actual text of Pdf. if you can make this text as a hollow text, then i can fulfill the requirements in my project. i need to show 3 line text like name, email_id ( should be hollow ) as horizontal view in actual Pdf page. – KP_JavaDev Dec 04 '13 at 05:42
  • As hollow as it is using the second `PdfPatternPainter` in my answer? – mkl Dec 04 '13 at 05:55
  • yes, it is coming hollow using your second piece of code and also not able to copy thats great :) but how to get 3 lines text and horizantly. i am able to make 3 lines text horizontaly using PdfContentByte, there then i can copy the watermark text. so, i want to do the same using your piece of code. – KP_JavaDev Dec 04 '13 at 07:09
  • Please find my code here, just want to replace it using PdfPatternPanter ( it does not allow copy ) – KP_JavaDev Dec 04 '13 at 07:15
  • @user3060995 For additional code edit your original question and append the code. – mkl Dec 04 '13 at 07:25
  • @user3060995 I integrated your code into a method applying its marks using patterns instead of direct text addition. As you did not provide `MyConstants.WATERMARK_PAGE_ANGLE` I used an angle of `-72`°. – mkl Dec 04 '13 at 09:04
  • The Pdf looks really nice :) Thanks :) let me try this in my local code. – KP_JavaDev Dec 04 '13 at 09:07
  • This code is working fine exactly i wanted, but is there anyway that our watermark text go backgroud of actual text and in case of Image it goes foreground. i have tried with getUnderContent() but our Pdf image Hide this text. – KP_JavaDev Dec 04 '13 at 10:10
  • @user3060995 *is there any way that our watermark text go background of actual text and in case of Image it goes foreground* - In general that would be very difficult: the order in which elements are drawn is the order in which they appear in the content stream. For your task you need to first draw all images, then the watermark, then the text; thus, you'd have to rearrange the content stream which may be quite a hassle and for which there are no iText high level APIs supporting you. Alternatively... – mkl Dec 04 '13 at 10:20
  • @user3060995 Alternatively you can try and collect all the areas with images (using the iText `parser` package classes), then put the water mark to the undercontent, and to the overcontent also put the water mark but first construct a clip path combining the regions in which you found images. Much easier to implement but a bit dirty. – mkl Dec 04 '13 at 10:26
  • Thanks for your reply, but it looks bit complicate, because our images can be jpeg or bmp, or png, how to filter all types of images?? – KP_JavaDev Dec 04 '13 at 10:53
  • You'll get all types of bitmap images using the iText `parser` package classes. Vector graphics, on the other hand, indeed would makes complicated. – mkl Dec 04 '13 at 11:04
  • Have a look at the [iText in Action, Second Edition](http://itextpdf.com/book/) example [ExtractImages](http://itextpdf.com/examples/iia.php?id=284) using [MyImageRenderListener](http://itextpdf.com/examples/iia.php?id=283). The `ImageRenderInfo` instance you receive in the listener's `renderImage` method does not only provide a `PdfImageObject` as used in the sample but also the current transformation matrix (`getImageCTM`). Map the unit square using right multiplication by that ctm to get the area covered by the image. – mkl Dec 05 '13 at 07:47
  • I added another proof-of-concept for such a combined UnderContent/OverContent approach. – mkl Dec 05 '13 at 09:17
  • Thanks for your code :) i will try this.... btw which List you are using com.itextpdf.text.List or java.util.List ?? – KP_JavaDev Dec 05 '13 at 09:43
  • `List` is `java.util.List`. As you can see in `getImageBordersPathPoints` it is initialized with an `ArrayList`... – mkl Dec 05 '13 at 09:47
  • just now i have tried your code in my local.....it looks very much clear and watermark text is going under for Pdf text and upper for images......over all really a great coding :) but i am just worrying about time and stability of code when i will use the same in my project......previous one was good ( took less time to generate )....... need to check current code in big environment :) – KP_JavaDev Dec 05 '13 at 10:02
  • Obviously: to find the locations of the images the content streams now have to be parsed which was not necessary otherwise. Thus, this routine requires more resources. – mkl Dec 05 '13 at 10:46
  • Thanks a lot mkl :) i may need one more implementation like when my string is long then String should come in next line... otherwise every things are fine now :) you really helped me a lot :) – KP_JavaDev Dec 05 '13 at 11:31
  • Great. I think that such follow-ups would make great stackoverflow questions in their own right. – mkl Dec 05 '13 at 13:05
  • Hi mkl, i need one small help here, i am trying to make all three watermark string aligned, suppose second line is smaller then First line then 2nd line should aligned in middle, like that for 3rd line also. Something like we can say that, the watermark text should start from middle and then expand according to there size. – KP_JavaDev Dec 06 '13 at 07:56
  • Use `Element.ALIGN_CENTER` instead of `Element.ALIGN_MIDDLE` - the former is for horizontal alignment, the latter for vertical alignment. In the context of `showTextAlignedKerned` only horizontal alignment values are taken into account. – mkl Dec 06 '13 at 08:33
  • Element.ALIGN_CENTER is not working properly for Horizontal alignment. – KP_JavaDev Dec 06 '13 at 09:32
  • *Element.ALIGN_CENTER is not working properly for Horizontal alignment.* - In which way? I just double checked and it works all right. (Obviously the coordinates you give describe the center of the base line, not the start anymore.) – mkl Dec 06 '13 at 10:12
  • if i am using Element.ALIGN_CENTER, my string moving to top direction. – KP_JavaDev Dec 06 '13 at 10:27
  • Also when my Text watermark is long it is going out of scope. it should come in 2nd line :( – KP_JavaDev Dec 06 '13 at 11:11
  • As mentioned in my prior comment, *Obviously the coordinates you give describe the center of the base line, not the start anymore.* Thus, when switching to `CENTER` you have to use different coordinates. – mkl Dec 06 '13 at 12:34
  • Hi mkl, Can you share me sample code for using diff coordinates. i am still not able to use Element.ALIGN_CENTER properly. – KP_JavaDev Dec 09 '13 at 09:42
  • 1
    You used `(250, 780)`, `(200, 730)`, and `(150, 680)` as starting coordinates in your samples. I also used them in all but my final sample (because I wanted the image in my sample document to be affected by the mark). These coordinates denoted the left side of the strings. For centering string you don't give the coordinates of the left side (because that side actually depends on the string) but of the center. For a quick test I used `(350, 580)`, `(300, 530)`, and `(250, 480)` with an angle `WATERMARK_PAGE_ANGLE = -45` which looked all right. – mkl Dec 09 '13 at 11:35
  • Hi @mkl, your code is working for few pdf it is not able to recognize the text or image.So at that place no watermarking is happening.I want to share you the pdf file,but dont know how to do – KP_JavaDev Dec 12 '13 at 08:38
  • If it can be shown publicly, use a file sharing method like dropbox or google drive (i.e. one which does not drown you in ads) and post a link here. If you would prefer the PDF to remain a bit more restricted, you can mail it to me, cf. my profile description. – mkl Dec 12 '13 at 19:28