Line Drawing In landscape mode using iText

2019-08-17 01:48发布

问题:

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?

回答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.