0

This is the code I use to draw a line.

double[] lineArray = annotation.getAsArray(PdfName.L).asDoubleArray();
double x1 = lineArray[0] - rect.getAsNumber(0).doubleValue();
double y1 = lineArray[1] - rect.getAsNumber(1).doubleValue();
double x2 = lineArray[2] - rect.getAsNumber(0).doubleValue();
double y2 = lineArray[3] - rect.getAsNumber(1).doubleValue();

cs.moveTo(x1, y1);
cs.lineTo(x2, y2);

Where cs is PdfAppearance, annotation is PdfAnnotation and rect is PdfArray rect = annotation.getAsArray(PdfName.RECT);

This works ok in portrait. but come, landscape mode e.g. 270 rotation, the coordinates get misplaced. I also did a rotate via cs.transform() so my 0,0 would be rotated but it does nothing.

Any idea what could be lacking?

Joris Schellekens
  • 8,483
  • 2
  • 23
  • 54
chitgoks
  • 311
  • 2
  • 6
  • 17
  • Please provide enough code and an example PDF to illustrate the issue. – mkl Jan 11 '18 at 10:39
  • hi @mkl. please see the following urls, Test.java for the sample. https://drive.google.com/open?id=1HtA0kOF7oV2i5J2a_EuPN2jegLQEsvai and the pdf is https://drive.google.com/open?id=1fXDVx486oMyZtXfuoHtDCdEBgAXWKvr7 so that code is what i use to render appearance stream for arrow in portrait. but come landscape 270 degrees, it does not show any. formula may be wrong this time for landscape. what do you think? – chitgoks Jan 13 '18 at 10:15
  • I cannot reproduce your problem. Using your code and your input file the arrow shows here. – mkl Jan 14 '18 at 21:26
  • As mentioned in my previous comment, I could not reproduce the issue. It might be an issue that only occurs on some specific PDF viewer. Thus, if you are still interested in this, you might want to describe the circumstances in more detail. If you are not interested anymore, please indicate so. – mkl Jan 19 '18 at 12:40
  • hi @mkl ill bypass this post thanks for taking time. my code should be ok i think the cause maybe related to this https://stackoverflow.com/questions/43330419/itext-rotation-adjustment-is-different-when-adding-annotation-vs-getting-field-p – chitgoks Jan 20 '18 at 12:59
  • hi @mkl. it seems that im still stuck. i updated Java source code @ https://drive.google.com/open?id=1HtA0kOF7oV2i5J2a_EuPN2jegLQEsvai please comment out addAppearance() first to see that the coordinates are correct. however, when that method is called, nothing shows up (try to view it using Chrome). this is the same code i use if the page rotation is 0 i figure even if the pdf rotation is 270, the same appearance code can still be used since i recalculated the coordinates. – chitgoks Jan 25 '18 at 10:53
  • And the result.pdf you posted in an earlier comment is the src.pdf of your new Test.java? – mkl Jan 25 '18 at 21:45
  • ah no i removed the result pdf file link. ill upload it in a few – chitgoks Jan 25 '18 at 23:52
  • hi @mkl here is the result.pdf link https://drive.google.com/open?id=1Opv5AyF8AkTD9fiphoxCbhviGlZwL2PM with appearance. doesn't show in chrome – chitgoks Jan 26 '18 at 02:47

1 Answers1

1

The source

This answer covers the updated source code provided by the OP via a google drive link in comments:

public static void main(String[] args) throws Exception {
    PdfReader reader = new PdfReader("src");
    PdfStamper stamper = new PdfStamper(reader, new FileOutputStream("dest"));

    Rectangle location = new Rectangle(544.8f, 517.65f, 663f, 373.35f);

    PdfArray lineEndings = new PdfArray();
    lineEndings.add(new PdfName("None"));
    lineEndings.add(new PdfName("None"));

    PdfAnnotation stamp = PdfAnnotation.createLine(stamper.getWriter(), location, 
        "comment",  550.05f, 510.9f, 656.25f, 378.6f);
    stamp.put(new PdfName("LE"), lineEndings);
    stamp.put(new PdfName("IT"), new PdfName("Line"));
    stamp.setBorderStyle(new PdfBorderDictionary(1, PdfBorderDictionary.STYLE_SOLID));
    stamp.setColor(PdfGraphics2D.prepareColor(Color.RED));
    stamp.put(PdfName.ROTATE, new PdfNumber(270));
    stamper.addAnnotation(stamp, 1);

    addAppearance(stamper, stamp, location);

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

private static void addAppearance(PdfStamper stamper, PdfAnnotation stamp, Rectangle location) {
    PdfContentByte cb = stamper.getOverContent(1);
    PdfAppearance app = cb.createAppearance(location.getWidth(),  location.getHeight());        

    PdfArray rect = stamp.getAsArray(PdfName.RECT);
    Rectangle bbox = app.getBoundingBox();

    double[] lineArray = stamp.getAsArray(PdfName.L).asDoubleArray();
    double x1 = lineArray[0] - rect.getAsNumber(0).doubleValue();
    double y1 = lineArray[1] - rect.getAsNumber(1).doubleValue();
    double x2 = lineArray[2] - rect.getAsNumber(0).doubleValue();
    double y2 = lineArray[3] - rect.getAsNumber(1).doubleValue();

    app.moveTo(x1, y1);
    app.lineTo(x2, y2);

    app.stroke();
    stamp.setAppearance(PdfName.N, app);
}

No appearance

The first observation when viewing the resulting PDF in Chrome is, as the OP put it in a comment:

nothing shows up

Inspecting the PDF the cause is clear: The annotation has no appearance stream. Thus, limited PDF viewers which only can show annotations by their appearance stream, not by their descriptive values, like the integrated viewer in Chrome don't show it.

This is due to the order in which the OP calls iText functionalities in his code:

    [... create annotation object stamp ...]
    stamper.addAnnotation(stamp, 1);

    addAppearance(stamper, stamp, location);

So he first adds the annotation to the PDF by means of stamper.addAnnotation and thereafter creates an appearance and attaches it to the stamp object.

This order is wrong. In context with iText one has to be aware that the library attempts to write additions as early as possible to the output stream to reduce its memory footprint. (This by the way is one of the important features of iText in the context of server applications in which multiple PDFs may have to be processed in parallel.)

So already during stamper.addAnnotation(stamp, 1) the annotation is written to the output stream, and as it has no appearance yet, the annotation in the output stream is without appearance. The later addAppearance call only adds an appearance to the in-memory representation of the annotation which won't be serialized anymore.

Changing the order to

    [... create annotation object stamp ...]
    addAppearance(stamper, stamp, location);

    stamper.addAnnotation(stamp, 1);

results in a PDF with a line drawn. Unfortunately not at the desired position, but that is another problem.

Wrong position

The reason why the line is both in the wrong location and has the wrong direction, is based in a feature of iText which has already been a topic in this answer and in this answer:

For rotated pages iText attempts to lift the burden of adding the rotation and translation to page content required to draw upright text and have the coordinate system origin in the lower left of the page of the users' shoulders, so that the users don't have to deal with page rotation at all. Consequently, it also does so for annotations.

As you already have the actual coordinates to use, this "help" by iText damages your annotation. As discussed in those other answers, there unfortunately is no explicit switch to turn off that mechanism; there is an easy work-around, though: before your manipulation simply remove the page rotation entry, and afterwards add it back again:

PdfReader reader = ...;
PdfStamper stamper = ...;

// hide the page rotation
PdfDictionary pageDict = reader.getPageN(1);
PdfNumber rotation = pageDict.getAsNumber(PdfName.ROTATE);
pageDict.remove(PdfName.ROTATE);

Rectangle location = new Rectangle(544.8f, 517.65f, 663f, 373.35f);

PdfArray lineEndings = new PdfArray();
lineEndings.add(new PdfName("None"));
lineEndings.add(new PdfName("None"));

PdfAnnotation stamp = PdfAnnotation.createLine(stamper.getWriter(), location, 
    "comment",  550.05f, 510.9f, 656.25f, 378.6f);
stamp.put(new PdfName("LE"), lineEndings);
stamp.put(new PdfName("IT"), new PdfName("Line"));
stamp.setBorderStyle(new PdfBorderDictionary(1, PdfBorderDictionary.STYLE_SOLID));
stamp.setColor(PdfGraphics2D.prepareColor(Color.RED));
stamp.put(PdfName.ROTATE, new PdfNumber(270));

addAppearance(stamper, stamp, location);

stamper.addAnnotation(stamp, 1);

// add page rotation again if required
if (rotation != null)
    pageDict.put(PdfName.ROTATE, rotation);

stamper.close();
reader.close();

This appears to create the annotation appearance as required.

mkl
  • 90,588
  • 15
  • 125
  • 265
  • apologies this was my mistake. youre right adding appearance should be first before adding annotation to the page. – chitgoks Jan 26 '18 at 10:48
  • hi. this post is now considered solved. i have confirmed that using my existing code and your suggestion on removing the page rotation first, add annotation + appearance, then adding page rotation back again will solve this. – chitgoks Jan 27 '18 at 04:47
  • 1
    *"this post is now considered solved"* - great. To show that, please *accept* the answer by clicking the tick at its upper left. – mkl Jan 27 '18 at 08:40
  • thank you for taking time to check @mkl. itext indeed does do it differently compared to pdfbox and aspose. – chitgoks Jan 27 '18 at 10:17