I am having trouble trying to nest a PDAnnotationText object within a dual PDF/A UA-1 / PDF/A-1a file, giving the above message demonstrated within both PDF Accessibility Checker (versions 3 and 2021) and VeraPDF: -
PAC 2021
PAC 2021: Annotation is not nested inside an "Annot" structure element
Additionally PAC 2021 reports this, which suggests a 'null' object reference?
PAC 2021: Object reference not set to an instance of an object
VeraPDF
PDFDebugger
Here is the annotation structure as shown in PDFDebugger:-
And here is the structure tree root for the annotations:-
PDF Debugger Structure Tree Root
Attached is a enter link description here that illustrates the problem with some text and two annotations on the first and second lines.
According to best practice advice from the PDF Association (page 31), annotations should be structured as follows:-
Annot
The
<Annot>
structure element encloses annotations other than links and widgets. There are two classes of annotations:
- Markup annotations, or annotations used like markup annotations. The
<Annot>
structure element encloses the marked-up content and the object reference to the actual annotation. These may be nested.- Other annotations. The
<Annot>
structure element typically only encloses the reference to the actual annotation.Example:
<Annot>
Content OBJR (pointing to<Annot>
of type Highlight)Creation Rules
A challenge can arise from the fact that content to be marked up by a markup annotation is not already represented as a structure element, and cannot be directly associated with the annotation. Instead, it might be necessary to identify and isolate the marked-up content and enclose it in marked-content sequences which can then be associated with the structure element together with the actual annotation.
Some housekeeping might be necessary regarding other marked-content sequences and structure elements around the marked-up content.
Consuming Rules
When presenting marked-up content to a user, the following aspects should be included in that presentation:
- The marked-up content
- The fact that the content is marked-up, and the type of markup annotation
- The contents of the Annotation’s content entry
- Any annotations that are replies to the Annotation
While not mandated by conforming reader provisions in PDF/UA-1, processors should also make available annotation properties, for example: author, subject, status, date/time, checkmark.
Despite appreciating the limited documentation online for PDFBox and my moderate experience with Java, I am struggling with this. It's likely I am doing something wrong but cannot see what it is. I have tried everything, from nesting the actual annotation code within the first block and separately.
Here is the code, which is based heavily on the excellent example from @GurpusMaximus:-
public PDStructureElement drawElement(Cell textCell, float x, float y, int height,
PDStructureElement parent,
String structType, int pageIndex, PDPage page) throws IOException { // OK
// Set up the next marked content element with an MCID and create the containing
// structure element.
PDPageContentStream contentsElement = new PDPageContentStream(pdf,
pages.get(pageIndex),
PDPageContentStream.AppendMode.APPEND, false);
currentElem = addContentToParent(pdf, null, structType, pages.get(pageIndex),
parent, pageIndex);
setNextMarkedContentDictionary();
contentsElement.beginMarkedContent(COSName.P,
PDPropertyList.create(currentMarkedContentDictionary));
// Use 'Old English' font for this demo example
drawCellText(textCell, (PAGE_WIDTH
- (int) (templateRegularFont.getStringWidth(textCell.getDefaultText()) /
1000 * textCell.getFontSize()))
/ 2, y + height + textCell.getFontSize(), contentsElement);
// End the marked content and append it's P structure element to the containing
// P structure element.
contentsElement.endMarkedContent();
addContentToParent(pdf, COSName.P, null, pages.get(pageIndex), currentElem,
pageIndex);
contentsElement.close();
// Set up the next marked content element with an MCID and create the containing
if (textCell.getAnnotationText() != null) {
float stringWidth = 0.00f;
stringWidth = templateRegularFont
.getStringWidth(textCell.getDefaultText().substring(0,
textCell.getDefaultText().length())) / 1000
* textCell.getFontSize();
annotations = pages.get(pageIndex).getAnnotations();
PDRectangle rect = new PDRectangle(x + 5, PAGE_HEIGHT - height - y -
textCell.getFontSize() - 5,
stringWidth, textCell.getFontSize() + 5);
PDPageContentStream contentsAnnot = new PDPageContentStream(pdf,
pages.get(pageIndex),
PDPageContentStream.AppendMode.APPEND, false);
currentElem = addContentToParent(pdf, null, structType,
pages.get(pageIndex),
parent, pageIndex);
setNextMarkedContentDictionary();
contentsAnnot.beginMarkedContent(COSName.ANNOT,
PDPropertyList.create(currentMarkedContentDictionary));
// Specify a widget associated with the field.
PDAnnotationText annotText = new PDAnnotationText();
annotText.setAnnotationName(textCell.getDefaultText());
annotText.setContents(textCell.getDefaultText());
annotText.setHidden(false);
annotText.setInvisible(false);
annotText.setName("Note");
annotText.setNoRotate(true);
annotText.setNoView(false);
annotText.setNoZoom(true);
annotText.setOpen(false);
annotText.setPage(pages.get(pageIndex));
annotText.setPrinted(true);
annotText.setRectangle(rect);
annotText.setStructParent(currentStructParent);
PDAppearanceDictionary appearance = new PDAppearanceDictionary();
appearance.getCOSObject().setDirect(true);
PDAppearanceStream appearanceStream = new PDAppearanceStream(pdf);
appearanceStream.setBBox(rect);
appearance.setNormalAppearance(appearanceStream);
annotText.setAppearance(appearance);
PDFTemplateStructure pdfStructure = new PDFTemplateStructure();
pdfStructure.setAppearanceDictionary(appearance);
annotations.add(annotText);
pages.get(pageIndex).setAnnotations(annotations);
PDStructureElement fieldElem = new
PDStructureElement(StandardStructureTypes.P, parent);
fieldElem.setPage(pages.get(pageIndex));
COSArray kArray = new COSArray();
kArray.add(COSInteger.get(currentMCID));
fieldElem.getCOSObject().setItem(COSName.K, kArray);
// Add object reference to widget for tagging purposes.
PDObjectReference objectReference = new PDObjectReference();
objectReference.setReferencedObject(annotText);
annotationRefs.add(objectReference);
addWidgetContent(pdf, annotationRefs.get(annotationRefs.size() - 1),
fieldElem, StandardStructureTypes.P,
pageIndex, pages, page);
contentsAnnot.endMarkedContent();
addContentToParent(pdf, COSName.ANNOT, null, pages.get(pageIndex),
currentElem, pageIndex);
contentsAnnot.close();
COSDictionary dict = pages.get(pageIndex).getCOSObject();
while (dict.containsKey(COSName.PARENT)) {
COSBase COSParent = dict.getDictionaryObject(COSName.PARENT);
if (COSParent instanceof COSDictionary) {
dict = (COSDictionary) COSParent;
dict.setNeedToBeUpdated(true);
}
}
}
return currentElem;
}
If you need to examine any further code blocks let me know and I will post them on request.