Changing position of a bookmark in word document

Hello, I’m using the latest Aspose.Words for Android via Java.

I have a word document with bookmarks generated through IReplacingCallback. The bookmark positions are accurate, but I would like to do some changes to the position if the bookmarks are too close to each other.

Currently, I’ve successfully retrieved the bookmarks via Document.range.bookmarks. With the LayoutCollector & LayoutEnumerator, I am able to get each bookmark start’s position.

Can I check how I can change the LayoutEnumerator.rectangle and apply it? I tried to change the LayoutEnumerator.rectangle.left but it doesnt seem to apply even after saving the file or after performing updatePageLayout()

Thank you!

@developeryca You cannot change placement of bookmarks or any other nodes using LayoutCollector & LayoutEnumerator classes. They only returns the layout information of the nodes and does not allow to change the document.
Also, you should note that bookmarks in MS Word documents are not floating, i.e. you cannot specify absolute coordinates of the bookmark on page. To change placement of the bookmark you should change it’s position in the document nodes tree. Please see our documentation to lean more about Aspose.Words Document Object Model.

Just to clarify, are you referring to the “Node” at the very top of the tree or the one under Node -> CompositeNode -> DocumentBase -> Document?

Thank you.

@developeryca I mean Node in general. Node class is base class for all nodes in the document tree in Aspose.Words DOM.

1 Like

Can I check where do I refer to for this? I would be able to get the bookmarks via Document.range.bookmarks. How do I go about retrieving the actual “Node” of the bookmark?

To change placement of the bookmark you should change it’s position in the document nodes tree.

Also, after getting a particular Document Node, can I check which particular function I should look out for to change it’s position? Thank you.

@developeryca Bookmark in MS word document consist of two nodes - BookmarkStart and BookmarkEnd. By placing these nodes in the document, you can wrap selected content into a bookmark.
Let’s suppose you have a bookmark somewhere in your document and would like to move it to the beginning of the document. In this case, you should move the corresponding BookmarkStart and BookmarkEnd nodes to the beginning of the document:

Code for moving bookmark will look like this:

Document doc = new Document("C:\\Temp\\in.docx");
        
// Get the bookmark.
Bookmark bk = doc.getRange().getBookmarks().get("bookmark");
        
// Move bookmark to the beginning of the document.
doc.getFirstSection().getBody().getFirstParagraph().prependChild(bk.getBookmarkEnd());
doc.getFirstSection().getBody().getFirstParagraph().prependChild(bk.getBookmarkStart());
        
doc.save("C:\\Temp\\out.docx");

After executing the code, the document tree will look like this:

As you can see the bookmark has been moved to the beginning of the document.

Please see our documentation to lean more about working with bookmarks:
https://docs.aspose.com/words/java/working-with-bookmarks/

Does that mean that there is actually no way to move to a specific x coordinate of the document? If that’s the case then its alright thank you very much.

@developeryca No, there is no direct way to move bookmark to X Y coordinates.
However, you can work this around, by placing the bookmark into a shape with absolute position on the page. For example:

Document doc = new Document("C:\\Temp\\in.docx");
        
// Get the bookmark.
Bookmark bk = doc.getRange().getBookmarks().get("bookmark");
        
// Create an absolutely positioned shape.
Shape bkContainer = new Shape(doc, ShapeType.TEXT_BOX);
bkContainer.setStroked(false);
bkContainer.setFilled(false);
bkContainer.setWrapType(WrapType.NONE);
bkContainer.setWidth(10);
bkContainer.setHeight(10);
bkContainer.setRelativeHorizontalPosition(RelativeHorizontalPosition.PAGE);
bkContainer.setRelativeVerticalPosition(RelativeVerticalPosition.PAGE);
// Specify X, Y coordinate of the shape.
bkContainer.setLeft(300);
bkContainer.setTop(500);
        
// put a paragraph into the shape.
Paragraph bkContainerPara = new Paragraph(doc);
bkContainer.appendChild(bkContainerPara);
// Put shape into the document, where the bookmark is originally located.
bk.getBookmarkStart().getParentNode().insertAfter(bkContainer, bk.getBookmarkStart());
// And finally put bookmark into the shape.
bkContainerPara.appendChild(bk.getBookmarkStart());
bkContainerPara.appendChild(bk.getBookmarkEnd());
        
doc.save("C:\\Temp\\out.docx");

Hello, thank you for the solution.

By using the work around, it would be following the shape’s X Y coordinates instead right?
There would not be any need to remove the bookmark itself since it is actually being moved into a shape instead of a new one being created, is that correct?

Thank you.

@developeryca Yes, the bookmark destination will be in the shape on the specified coordinates. And no, you do not need to remove original bookmark, since it was moved into the shape.

Thank you. I’ve tested and face a problem, “java.lang.IllegalArgumentException: Cannot add a node to self.” It is pointing to this particular line:

bkContainerPara.appendChild(bkContainerPara)

I iterate through all the bookmarks instead of doing it individually like the solution provided.

 val bookmarks = document.range.bookmarks
 for (bookmark in bookmarks) {
 	if (bookmark.name.contains("BookmarkToChange")) {
 		// Move to bookmark start node
 		builder.moveTo(bookmark.bookmarkStart)
 		val layoutCollector = LayoutCollector(document)
 		val layoutEnumerator = LayoutEnumerator(document)
 		layoutEnumerator.current = layoutCollector.getEntity(
 			bookmark.bookmarkStart
 		)
 		if (bookmark.name.contains("BookmarkToChangeBG")) {
 			//Change the position of the bookmark
 			val bkContainer = Shape(document, ShapeType.TEXT_BOX)
 			bkContainer.stroked = false
 			bkContainer.filled = false
 			bkContainer.wrapType = WrapType.NONE
 			bkContainer.width = 10.0
 			bkContainer.height = 10.0
			bkContainer.relativeHorizontalPosition = RelativeHorizontalPosition.PAGE
 			bkContainer.relativeVerticalPosition = RelativeVerticalPosition.PAGE
 			bkContainer.left = layoutEnumerator.rectangle.left.toDouble() + 100 //To test moving of bookmark position
 			bkContainer.top = layoutEnumerator.rectangle.top.toDouble()
 
 			//Put a paragraph into shape
 			val bkContainerPara = Paragraph(document)
 			bkContainerPara.appendChild(bkContainerPara)
 			bookmark.bookmarkStart.parentNode.insertAfter(bkContainer, bookmark.bookmarkStart)
 			bkContainerPara.appendChild(bookmark.bookmarkStart)
 			bkContainerPara.appendChild(bookmark.bookmarkEnd)
 			document.save("wordDocument.doc")
 		}
 	}
 }

Edit:

Sorry I saw my mistake, I accidentally appended the bkContainerPara to itself. I’ll try again.

Edit2:
It is working, but however I would have to ask for help again for the positioning.
I would like to place the bkContainer at the same position as the original bookmark position, with only changes to the X coordinates. For example, if the bookmark is at (Left: 200, Top: 300), I would like the bkContainer to be at (Left: 250, Top: 300)

What I’ve tried was putting bkContainer.left & top to layoutEnumerator.rectangle.left & top, it seems quite off. Are they using different origin?

What would be a way to place the bkContainer’s position at the original bookmark position? Thank you.

@developeryca As you may know, MS Word documents are flow documents and do not contain any information about document layout. The consumer applications, like MS Word or Open Office builds document layout on the fly. Aspose.Words uses it’s own layout engine to build document layout while rendering the document to fixed page formats (PDF, XPS, Image etc.). The same layout engine is used for providing document layout information via LayoutCollector and LayoutEnumerator classes.
To built proper document layout the fonts used in the original document are required. If Aspose.Words cannot find the fonts used in the document the fonts are substituted . This might lead into the layout difference (incorrect coordinate returned by LayoutEnumerator), since substitution fonts might have different font metrics. You can implement IWarningCallback to get a notification when font substitution is performed.