When I mentioned to my manager that I came up with a way to automate the task of applying complex page labeling to a PDF document (using pikepdf), he asked me if there was an easy way to change bookmark (aka outline item) colors from black to blue.
It's my understanding that pypdf can only add outline items; you can't change the properties of those already in place. I have a script that adds the blue bookmarks. Now I want to remove the black ones.
from typing import List, Union
from pypdf import PdfReader, PdfWriter
from pypdf.generic import Destination, Fit, NameObject, ArrayObject, NumberObject, FloatObject
def get_xyz_from_destination(destination):
# (this is working)
def change_bkmark_color(
outlines: List[Union[Destination, List[Destination]]],
reader: PdfReader,
level: int = 0
) -> None:
# (this is also working)
def remove_outline_item(output_path: str) -> None:
try:
# Remove a specific outline item by its color
for outline in writer.outline:
if isinstance(outline, list):
remove_outline_item(outline)
else:
if outline.color == [0.0, 0.0, 0.0]:
print(outline.title, outline.color, outline.get("/Parent"))
# This causes the error
outline.remove_from_tree()
except FileNotFoundError:
print(f"Error: File '{output_path}' not found.")
except Exception as e:
print(f"An error occurred: {e}")
This is the result:
PS C:\temp> python change-bookmark-text-color.py
Cover [0.0, 0.0, 0.0] None
An error occurred: Removed child does not appear to be a tree item
When I searched for answers, I got some Copilot guidance saying "Always verify the object’s /Parent reference and remove it from the correct data structure before calling remove_from_tree(). If the object isn’t in the tree, you’ll need to remove it from its source list instead."
But as you can see above, outline.get("/Parent")) returns "None", so how would I do that?
By the way, "Cover" is the title of the first bookmark in the input document and it has no children.