alessmar
alessmar

Reputation: 4727

How to use iText to add a watermark using an embedded font

I've several pdf/a documents with some embedded fonts, now I've to post-process these documents using iText to add a watermark.

I know that it's possible to embed a font with iText:

BaseFont bf = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.EMBEDDED);

but I would like to use a font that is already embedded in the document to add the textual watermark (something like "SAMPLE COPY").

How can I do it?

Upvotes: 3

Views: 3513

Answers (3)

Mark Storer
Mark Storer

Reputation: 15870

What you want is a DocumentFont. You cannot create them directly, the constructor is package private, but BaseFont.createFont(PRIndirectReference) will do the trick.

So you just need to get a PRIndirectReference to the font you want. "PR"s come from a PdfReader. There are two ways to find the font you're looking for and get a reference to it:

1) Enumerate every object in the PdfReader, filtering out everything that's not a PRStream, from there dropping everything that's not a /Type /Font, and looking for a font with the correct name.

public PRIndirectReference findNamedFont( PdfReader myReader, String desiredFontName) {
  int objNum = 0;
  PdfObject curObj;
  do {
    //The "Release" version doesn't keep a reference 
    //to the object so it can be GC'd later.  Quite Handy 
    //when dealing with Really Big PDFs.
    curObj = myReader.getPdfObjectRelease( objNum++ );
    if (curObj instanceof PRStream) {
      PRStream stream = (PRStream)curObj;
      PdfName type = stream.getAsName(PdfName.TYPE);
      if (PdfName.FONT.equals(type)) {
        PdfString fontName = stream.getAsString(PdfName.BASEFONT);
        if (desiredFontName.equals(fontName.toString())) {
          return curObj.getIndRef();
        }
      }
    }
  } while (curObj != null);
return null;
}  

2) Examine your pages' resource dictionaries /Font <<>> dicts, looking for a font with the correct name. Keep in mind that XObject Form resources have resources of their own you'll have to check to:

public PRIndirectReference findFontInPage(PdfReader reader, String desiredName, int i) {

  PdfDictionary page = reader.getPageN(i); 
  return findFontInResources(page.getAsDict(PdfName.RESOURCES), desiredName);
}   

public PRIndirectReference findFontInResources(PdfDictionary resources, String desiredName) {
  if (resources != null) {
    PdfDictionary fonts = resources.getAsDict(PdfName.FONTS);
    if (fonts != null) {
      for (PdfName curFontName : fonts.keySet()) {
        PRStream curFont (PRStream)= fonts.getAsStream(curFontName);
        if (desiredName.equals(curFont.getAsString(PdfName.BASEFONT).toString()) {
          return (PRIndirectReference) curFont.getIndirectReference();
        }
      }
    }
    PdfDictionary xobjs = resources.getAsDict(PdfName.XOBJECTS);
    if (xobjs != null) {
      for (PdfName curXObjName : xobjs.keySet()) {
        PRStream curXObj = (PRStream)xobjs.getAsStream(curXObjName);
        if (curXObj != null && PdfName.FORM.equals(curXObj.getAsName(PdfName.SUBTYPE)) {
          PdfDictionary resources = curXObj.getAsDict(PdfName.RESOURCES);
          PRIndirectReference ref = findFontInResources(resources, desiredName);
          if (ref != null) {
            return ref;
          }
        }
      }
    }
  }
  return null;
}

Either one of those will get you the PRIndirectReference you're after. Then you call BaseFont.createFont(myPRRef) and you'll have the DocumentFont you need. The first method will find any font in the PDF, while the second will only find fonts That Are Actually Used.

Also, subsetted fonts are supposed to have a "6-random-letters-plus-sign" tag prepended to the font name. DO NOT use a font subset. The characters you're using may not be in the subset, leading to what I call the " arry ole" problem. It sounds nice and dirty, but it was really just our sales guy's name: "Harry Vole" missing the upper case letters because I'd subsetted some font I shouldn't have Many Moons Ago.

PS: never embed subsets of fonts you intend to be used in a form field. No Bueno.

The usual "I wrote all that code in the answer box here" disclaimer applies, but I've written a LOT of this sort of code, so it just might work out of the box. Cross your fingers. ;)

Upvotes: 2

Mark Storer
Mark Storer

Reputation: 15870

An entirely different approach: Use Line Art instead of Text.

If you create a "line art only" PdfGraphics2D object from the page's overContent, you can use an AWT font and need not worry about embedding at all. With a relatively short string you don't have to worry about the PDF's size exploding either.

PdfContentByte overcontent = stamper.getOverContent(1);
Graphics2D g2d = overcontent.createGraphicsShapes(pageWid, pageHei);

drawStuffToTheGraphic(g2d);

g2d.dispose();

This will result in "text" that is actually line art. It cannot be selected, searched, etc. That could be good or bad depending on what you're after.

Upvotes: 1

mtraut
mtraut

Reputation: 4740

Using plain jPod (BSD, SourceForge) you could base on the "Watermark" example. WIth

PDFontTools.getFonts(doc)

you can enumerate the fonts and then use one of them in the "createForm" method...

Upvotes: 0

Related Questions