Bin.Liu
Bin.Liu

Reputation: 33

IText 7: How to build a mix of text and attachments to the paragraph?

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?

Upvotes: 2

Views: 1304

Answers (2)

Bin.Liu
Bin.Liu

Reputation: 33

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.

Upvotes: 1

Alexey Subach
Alexey Subach

Reputation: 12312

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.

Upvotes: 2

Related Questions