105 lines
3.2 KiB
Python
105 lines
3.2 KiB
Python
"""
|
||
Quoting section 8.2.2 "Document Outline" of the 2006 PDF spec 1.7:
|
||
> The document outline consists of a tree-structured hierarchy of outline items (sometimes called bookmarks),
|
||
> which serve as a visual table of contents to display the document’s structure to the user.
|
||
|
||
The contents of this module are internal to fpdf2, and not part of the public API.
|
||
They may change at any time without prior warning or any deprecation period,
|
||
in non-backward-compatible ways.
|
||
"""
|
||
|
||
from typing import NamedTuple, Optional
|
||
|
||
from .syntax import Destination, PDFObject, PDFString
|
||
from .structure_tree import StructElem
|
||
|
||
|
||
class OutlineSection(NamedTuple):
|
||
name: str
|
||
level: str
|
||
page_number: int
|
||
dest: Destination
|
||
struct_elem: Optional[StructElem] = None
|
||
|
||
|
||
class OutlineItemDictionary(PDFObject):
|
||
__slots__ = ( # RAM usage optimization
|
||
"_id",
|
||
"title",
|
||
"parent",
|
||
"prev",
|
||
"next",
|
||
"first",
|
||
"last",
|
||
"count",
|
||
"dest",
|
||
"struct_elem",
|
||
)
|
||
|
||
def __init__(
|
||
self,
|
||
title: str,
|
||
dest: Destination = None,
|
||
struct_elem: StructElem = None,
|
||
):
|
||
super().__init__()
|
||
self.title = PDFString(title, encrypt=True)
|
||
self.parent = None
|
||
self.prev = None
|
||
self.next = None
|
||
self.first = None
|
||
self.last = None
|
||
self.count = 0
|
||
self.dest = dest
|
||
self.struct_elem = struct_elem
|
||
|
||
|
||
class OutlineDictionary(PDFObject):
|
||
__slots__ = ("_id", "type", "first", "last", "count") # RAM usage optimization
|
||
|
||
def __init__(self, **kwargs):
|
||
super().__init__(**kwargs)
|
||
self.type = "/Outlines"
|
||
self.first = None
|
||
self.last = None
|
||
self.count = 0
|
||
|
||
|
||
def build_outline_objs(sections):
|
||
"""
|
||
Build PDF objects constitutive of the documents outline,
|
||
and yield them one by one, starting with the outline dictionary
|
||
"""
|
||
outline = OutlineDictionary()
|
||
yield outline
|
||
outline_items = []
|
||
last_outline_item_per_level = {}
|
||
for section in sections:
|
||
outline_item = OutlineItemDictionary(
|
||
title=section.name,
|
||
dest=section.dest,
|
||
struct_elem=section.struct_elem,
|
||
)
|
||
yield outline_item
|
||
if section.level in last_outline_item_per_level:
|
||
last_outline_item_at_level = last_outline_item_per_level[section.level]
|
||
last_outline_item_at_level.next = outline_item
|
||
outline_item.prev = last_outline_item_at_level
|
||
if section.level - 1 in last_outline_item_per_level:
|
||
parent_outline_item = last_outline_item_per_level[section.level - 1]
|
||
else:
|
||
parent_outline_item = outline
|
||
outline_item.parent = parent_outline_item
|
||
if parent_outline_item.first is None:
|
||
parent_outline_item.first = outline_item
|
||
parent_outline_item.last = outline_item
|
||
parent_outline_item.count += 1
|
||
outline_items.append(outline_item)
|
||
last_outline_item_per_level[section.level] = outline_item
|
||
last_outline_item_per_level = {
|
||
level: oitem
|
||
for level, oitem in last_outline_item_per_level.items()
|
||
if level <= section.level
|
||
}
|
||
return [outline] + outline_items
|