2

I would like to use itext7 to generate something like this pdf document:

the effect that I need

But i can't find any method to achieve that. What I see in the tutorial iText - clickable image should open ms word attachment can not place attachments and text together. You can only place attachments on a separate page. If it is itext5, i do like this:

PdfAnnotation anno = PdfAnnotation.createFileAttachment(writer, null, fileDescribe, pdfFileSpecification);
chunk.setAnnotation(anno);
paragrah.add(chunk);

So I can add text、annotation in one paragraph, But itext7 tutorial is the case:

PdfDocument pdfDoc = new PdfDocument(new PdfWriter(DEST));
    Rectangle rect = new Rectangle(36, 700, 100, 100);
    PdfFileSpec fs = PdfFileSpec.createEmbeddedFileSpec(pdfDoc, PATH, null, "test.docx", null, null, false);
    PdfAnnotation attachment = new PdfFileAttachmentAnnotation(rect, fs)
            .setContents("Click me");

    PdfFormXObject xObject = new PdfFormXObject(rect);
    ImageData imageData = ImageDataFactory.create(IMG);
    PdfCanvas canvas = new PdfCanvas(xObject, pdfDoc);
    canvas.addImage(imageData, rect, true);
    attachment.setNormalAppearance(xObject.getPdfObject());

    pdfDoc.addNewPage().addAnnotation(attachment);
    pdfDoc.close();

Can someone help me?

Community
  • 1
  • 1
Bin.Liu
  • 33
  • 6
  • Please do not add your solution to the question body, instead post it as an answer. – mkl Apr 17 '17 at 06:22

2 Answers2

2

If I understood correctly, you want to add an annotation among other layout elements to your document flow.

Currently in iText7 there is no quick way to achieve it (like setAnnotation method in iText5). However, iText7 is flexible enough to allow you to create custom elements and not dig very deeply into the code.

The initial part will be the same as in the current example. Here an annotation itself is being set up:

PdfDocument pdfDoc = new PdfDocument(new PdfWriter(DEST));
Rectangle rect = new Rectangle(36, 700, 50, 50);
PdfFileSpec fs = PdfFileSpec.createEmbeddedFileSpec(pdfDoc, PATH, null, "test.docx", null, null, false);
PdfAnnotation attachment = new PdfFileAttachmentAnnotation(rect, fs)
        .setContents("Click me");

PdfFormXObject xObject = new PdfFormXObject(rect);
ImageData imageData = ImageDataFactory.create(IMG);
PdfCanvas canvas = new PdfCanvas(xObject, pdfDoc);
canvas.addImage(imageData, rect, true);
attachment.setNormalAppearance(xObject.getPdfObject());

Then, what we want to achieve is being able to add custom annotation elements to the layout Document flow. In the best case, the code would look like this:

Document document = new Document(pdfDoc);
Paragraph p = new Paragraph("There are two").add(new AnnotationElement(attachment)).add(new Text("elements"));
document.add(p);
document.close();

Now we are left with defining the AnnotationElement and corresponding renderer. Element itself does not have any specific logic in it apart from creating custom renderer and passing an annotation to it:

private static class AnnotationElement extends AbstractElement<AnnotationElement> implements ILeafElement {
    private PdfAnnotation annotation;

    public AnnotationElement(PdfAnnotation annotation) {
        this.annotation = annotation;
    }

    @Override
    protected IRenderer makeNewRenderer() {
        return new AnnotationRenderer(annotation);
    }
}

The renderer implementation has more code but what it that is simply defines the occupied area on layout and adds the annotation to the page on draw. Please note that it does not cover all the cases (e.g. there is not enough space left to fit an annotation), but this is more than enough for simple cases and demonstration purposes.

private static class AnnotationRenderer extends AbstractRenderer implements ILeafElementRenderer {
    private PdfAnnotation annotation;

    public AnnotationRenderer(PdfAnnotation annotat) {
        this.annotation = annotat;
    }

    @Override
    public float getAscent() {
        return annotation.getRectangle().toRectangle().getHeight();
    }

    @Override
    public float getDescent() {
        return 0;
    }

    @Override
    public LayoutResult layout(LayoutContext layoutContext) {
        occupiedArea = layoutContext.getArea().clone();

        float myHeight = annotation.getRectangle().toRectangle().getHeight();
        float myWidth = annotation.getRectangle().toRectangle().getWidth();
        occupiedArea.getBBox().moveUp(occupiedArea.getBBox().getHeight() - myHeight).setHeight(myHeight);
        occupiedArea.getBBox().setWidth(myWidth);

        return new LayoutResult(LayoutResult.FULL, occupiedArea, null, null);
    }

    @Override
    public void draw(DrawContext drawContext) {
        super.draw(drawContext);
        annotation.setRectangle(new PdfArray(occupiedArea.getBBox()));
        drawContext.getDocument().getPage(occupiedArea.getPageNumber()).addAnnotation(annotation);
    }

    @Override
    public IRenderer getNextRenderer() {
        return new AnnotationRenderer(annotation);
    }
}

Please note that the example is for current 7.0.3-SNAPSHOT version. Might not work for the release 7.0.2 version yet because ILeafElementRenderer interface was added later, but still it would be possible to adapt the code to 7.0.2 if really needed.

Alexey Subach
  • 11,903
  • 7
  • 34
  • 60
  • You might want to try and include those classes in the itext code base. – mkl Apr 14 '17 at 06:49
  • I wonder, though, are the `occupiedArea` coordinates always given in the default user coordinate system? E.g. if the writing direction is rotated... – mkl Apr 14 '17 at 06:56
  • Hi @mkl. Do you mean RTL texts? In case of RTL texts the occupied area is the same, but the reordering of glyphs takes place and thus it concerns only correct order of glyphs to be printed, but the occupied area coordinates follow the same logic and for LTR case. Or are you referring to something else like transformation matrices? – Alexey Subach Apr 14 '17 at 06:59
  • *"Or are you referring to something else like transformation matrices?"* - Yes, that was what I was thinking about. In iText 5 there were table event listeners for creating annotations above table cells, and these listeners all failed in table cells with rotation as rotation was achieved using a ctm change. I wondered whether your `AnnotationRenderer` has a similar issue. BTW, in spite of the *at mkl* I wasn't informed about your comment; probably the dot immediately after *mkl* made the mechanism fail. – mkl Apr 20 '17 at 09:27
1

Thank you for your reply @Alexey Subach, by using your method, I solved my own problems. Because I use the 7.0.2-SNAPSHOT version. So in the AnnotationRenderer class made a little change:

public class AnnotationRenderer extends AbstractRenderer implements IRenderer {

  private PdfAnnotation annotation;

  public AnnotationRenderer(PdfAnnotation annotation) {
    this.annotation = annotation;
  }

  public float getAscent(){
    return annotation.getRectangle().toRectangle().getHeight();
  }

  public float getDescent(){
    return 0;
  }

  @Override
  public LayoutResult layout(LayoutContext layoutContext) {
    occupiedArea = layoutContext.getArea().clone();

    float myHeight = annotation.getRectangle().toRectangle().getHeight();
    float myWidth = annotation.getRectangle().toRectangle().getWidth();
    occupiedArea.getBBox().moveUp(occupiedArea.getBBox().getHeight() - myHeight).setHeight(myHeight);
    occupiedArea.getBBox().setWidth(myWidth);

    return new LayoutResult(LayoutResult.FULL, occupiedArea, null, null);
  }

  @Override
  public IRenderer getNextRenderer() {
    return new AnnotationRenderer(annotation);
  }

  @Override
  public void draw(DrawContext drawContext) {
      super.draw(drawContext);
    annotation.setRectangle(new PdfArray(occupiedArea.getBBox()));
    drawContext.getDocument().getPage(occupiedArea.getPageNumber()).addAnnotation(annotation);
}

}

I just touched itext soon, just encountered this problem when I feel that they may not be able to solve, but fortunately got your help. In fact, LayoutContext, occupiedArea, LayoutResult these classes do not understand for me, I think that I need look at the API to learn more.

Bin.Liu
  • 33
  • 6