Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3419,9 +3419,9 @@ public void beginMarkedContentSequence(PdfStructureElement struc, PdfDictionary
dic.put(PdfName.PG, writer.getCurrentPage());
dic.put(PdfName.MCID, new PdfNumber(mark));
ar.add(dic);
struc.setPageMark(writer.getPageNumber() - 1, -1);
struc.setPageMark(pdf.getCurrentPageStructParentIndex(), -1);
} else {
struc.setPageMark(writer.getPageNumber() - 1, mark);
struc.setPageMark(pdf.getCurrentPageStructParentIndex(), mark);
struc.put(PdfName.PG, writer.getCurrentPage());
}
pdf.incMarkPoint();
Expand Down
20 changes: 19 additions & 1 deletion openpdf/src/main/java/com/lowagie/text/pdf/PdfDocument.java
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,9 @@ public class PdfDocument extends Document {
// [L10] DocListener interface
protected int markPoint;

// structure parent tree key reserved for the current page (tagged PDF)
protected int currentPageStructParentIndex;

// [L11] DocListener interface
/**
* This is the size of the next page.
Expand Down Expand Up @@ -977,7 +980,7 @@ public boolean newPage() {

// [F12] we add tag info
if (writer.isTagged()) {
page.put(PdfName.STRUCTPARENTS, new PdfNumber(writer.getCurrentPageNumber() - 1));
page.put(PdfName.STRUCTPARENTS, new PdfNumber(getCurrentPageStructParentIndex()));
}

if (text.size() > textEmptySize) {
Expand Down Expand Up @@ -1144,6 +1147,11 @@ protected void initPage() throws DocumentException {
textEmptySize = text.size();

markPoint = 0;
// reserve a structure parent tree key for this page so that page content and any tagged objects
// (annotations) on it share the same, collision-free key space (see PdfStructureTreeRoot)
if (writer.isTagged()) {
currentPageStructParentIndex = writer.getStructureTreeRoot().obtainStructureParentIndex();
}
setNewPageSizeAndMargins();
imageEnd = -1;
indentation.imageIndentRight = 0;
Expand Down Expand Up @@ -2244,6 +2252,16 @@ void incMarkPoint() {
++markPoint;
}

/**
* Returns the structure parent tree key reserved for the current page. Marked content on the page
* and this value (written as the page's StructParents entry) must agree.
*
* @return the current page's structure parent tree key
*/
int getCurrentPageStructParentIndex() {
return currentPageStructParentIndex;
}

// [U3] page actions

void setCropBoxSize(Rectangle crop) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,47 @@ void setPageMark(int page, int mark) {
public PdfIndirectReference getReference() {
return this.reference;
}

/**
* References a whole annotation from this structure element, as required to tag annotations
* (typically links) in a tagged or PDF/UA document. This
* <ul>
* <li>adds an OBJR entry pointing at the annotation to this element's kids,</li>
* <li>stores a unique {@code StructParent} key on the annotation, and</li>
* <li>registers that key in the structure parent tree, mapping it directly to this element.</li>
* </ul>
* The annotation must already have been added to the document (for example through
* {@link PdfWriter#addAnnotation(PdfAnnotation)}) and must live on the current page. Any visible
* content of the annotation (such as the link text) should be marked before calling this method so
* that it becomes a preceding kid of this element.
*
* @param annotation the annotation to reference; its indirect reference is used for the OBJR entry
*/
public void addAnnotation(PdfAnnotation annotation) {
PdfWriter writer = top.getWriter();
int structParent = top.obtainStructureParentIndex();
annotation.put(PdfName.STRUCTPARENT, new PdfNumber(structParent));
top.setObjectParent(structParent, reference);

PdfDictionary objr = new PdfDictionary(PdfName.OBJR);
objr.put(PdfName.OBJ, annotation.getIndirectReference());
objr.put(PdfName.PG, writer.getCurrentPage());
addKid(objr);
}

private void addKid(PdfObject kid) {
PdfObject k = get(PdfName.K);
PdfArray kids;
if (k == null) {
kids = new PdfArray();
put(PdfName.K, kids);
} else if (k.isArray()) {
kids = (PdfArray) k;
} else {
kids = new PdfArray();
kids.add(k);
put(PdfName.K, kids);
}
kids.add(kid);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@
public class PdfStructureTreeRoot extends PdfDictionary {

private final Map<Integer, PdfArray> parentTree = new HashMap<>();
/**
* Parent tree entries that map a single StructParent key directly to a structure element. This is
* used for whole objects (such as annotations) referenced from the structure tree through an OBJR
* entry, which - unlike marked-content sequences - must resolve to the structure element itself and
* not to an array (see ISO 32000-1, 14.7.4.4 and ISO 14289-1).
*/
private final Map<Integer, PdfIndirectReference> objectParents = new HashMap<>();
/**
* A monotonically increasing counter used to hand out unique StructParent / StructParents keys for
* both pages and objects, so that their entries in the structure parent tree never collide.
*/
private int parentTreeNextKey = 0;
private final PdfIndirectReference reference;

/**
Expand Down Expand Up @@ -121,16 +133,44 @@ void setPageMark(int page, PdfIndirectReference reference) {
ar.add(reference);
}

/**
* Returns the next available StructParent / StructParents key and advances the internal counter.
* Pages and objects (annotations) share this key space so that their structure parent tree entries
* remain unique.
*
* @return the next parent tree key
*/
public int obtainStructureParentIndex() {
return parentTreeNextKey++;
}

/**
* Registers a structure parent tree entry that maps a StructParent key directly to a structure
* element. Used for whole objects (annotations) referenced through an OBJR entry.
*
* @param structParent the StructParent key stored on the object
* @param structureElement the reference of the structure element that is the object's parent
*/
void setObjectParent(int structParent, PdfIndirectReference structureElement) {
objectParents.put(structParent, structureElement);
}

private void nodeProcess(PdfDictionary dictionary, PdfIndirectReference reference)
throws IOException {
PdfObject obj = dictionary.get(PdfName.K);
if (obj != null && obj.isArray() && !((PdfArray) obj).getElements().isEmpty() && !((PdfArray) obj).getElements()
.get(0).isNumber()) {
if (obj != null && obj.isArray()) {
PdfArray ar = (PdfArray) obj;
for (int k = 0; k < ar.size(); ++k) {
PdfStructureElement e = (PdfStructureElement) ar.getDirectObject(k);
ar.set(k, e.getReference());
nodeProcess(e, e.getReference());
// Only structure elements are recursed into and replaced by their reference. Other kids
// (marked-content identifiers, MCR dictionaries and OBJR references to annotations) are
// left untouched so that mixed kids - e.g. a Link element containing both link text and
// an OBJR - are handled correctly.
PdfObject direct = ar.getDirectObject(k);
if (direct instanceof PdfStructureElement) {
PdfStructureElement e = (PdfStructureElement) direct;
ar.set(k, e.getReference());
nodeProcess(e, e.getReference());
}
}
}
if (reference != null) {
Expand All @@ -144,9 +184,12 @@ void buildTree() throws IOException {
PdfArray ar = parentTree.get(i);
numTree.put(i, writer.addToBody(ar).getIndirectReference());
}
// Object (annotation) entries map their key directly to the parent structure element.
numTree.putAll(objectParents);
PdfDictionary dicTree = PdfNumberTree.writeTree(numTree, writer);
if (dicTree != null) {
put(PdfName.PARENTTREE, writer.addToBody(dicTree).getIndirectReference());
put(PdfName.PARENTTREENEXTKEY, new PdfNumber(parentTreeNextKey));
}

nodeProcess(this, reference);
Expand Down
Loading