add named destination support
thanks typst contributors for adding this in v0.11.0
This commit is contained in:
parent
0135f1eb4f
commit
585ba739d1
@ -47,10 +47,13 @@ or add the following to your `.config/zathura/zathurarc`:
|
|||||||
```
|
```
|
||||||
map <C-l> exec copy_ref
|
map <C-l> exec copy_ref
|
||||||
map <C-g> exec "copy_ref --section"
|
map <C-g> exec "copy_ref --section"
|
||||||
|
map <C-k> exec "copy_ref --destination"
|
||||||
```
|
```
|
||||||
|
|
||||||
This will make Ctrl-L copy a reference to the current page,
|
This will make Ctrl-L copy a reference to the current page,
|
||||||
and Ctrl-G copy a reference to a specific section in Zathura.
|
Ctrl-G copy a reference to a specific section title,
|
||||||
|
and Ctrl-K copy a reference to a [named destination](https://tex.stackexchange.com/questions/213860/how-to-generate-a-named-destination-in-pdf).
|
||||||
|
Destinations and sections will show up in a rofi menu for you to select.
|
||||||
|
|
||||||
## limitations
|
## limitations
|
||||||
|
|
||||||
@ -66,5 +69,5 @@ and I can not make any guarantees.
|
|||||||
Also:
|
Also:
|
||||||
- Section references are unreliable because titles might change,
|
- Section references are unreliable because titles might change,
|
||||||
and there might be sections with the same title.
|
and there might be sections with the same title.
|
||||||
Proper IDs for bookmarks are possible,
|
Use named destinations for documents that you built yourself (e.g. using Typst, LaTeX), and page numbers for external documents.
|
||||||
but not until Typst resolves [issue #1352](https://github.com/typst/typst/issues/1352).
|
Only use section title references if your type-setting system does not support named destinations.
|
||||||
|
10
copy_ref
10
copy_ref
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from pdf_data import get_page_pdf, get_section_pdf
|
from pdf_data import get_destination_pdf, get_page_pdf, get_section_pdf
|
||||||
from datatypes import *
|
from datatypes import *
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from util import notify
|
from util import notify
|
||||||
@ -9,7 +9,9 @@ import argparse
|
|||||||
import formatter.typst as typst_fmt
|
import formatter.typst as typst_fmt
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("--section", help="Copy reference to the section title instead of the page number.", action="store_true")
|
dest_group = parser.add_mutually_exclusive_group()
|
||||||
|
dest_group.add_argument("--section", help="Copy reference to the section title instead of the page number.", action="store_true")
|
||||||
|
dest_group.add_argument("--destination", help="Copy reference to a named destination instead of the page number.", action="store_true")
|
||||||
|
|
||||||
|
|
||||||
class LinkFormat(Enum):
|
class LinkFormat(Enum):
|
||||||
@ -37,6 +39,8 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
if args.section:
|
if args.section:
|
||||||
ref = get_section_pdf()
|
ref = get_section_pdf()
|
||||||
|
elif args.destination:
|
||||||
|
ref = get_destination_pdf()
|
||||||
else:
|
else:
|
||||||
ref = get_page_pdf()
|
ref = get_page_pdf()
|
||||||
|
|
||||||
@ -48,3 +52,5 @@ if __name__ == "__main__":
|
|||||||
notify("Copied ref", f"{ref.filepath.name} p. {ref.page}")
|
notify("Copied ref", f"{ref.filepath.name} p. {ref.page}")
|
||||||
case PDFSection():
|
case PDFSection():
|
||||||
notify("Copied ref", f"{ref.filepath.name} sec. {ref.title}")
|
notify("Copied ref", f"{ref.filepath.name} sec. {ref.title}")
|
||||||
|
case PDFDestination():
|
||||||
|
notify("Copied ref", f"{ref.filepath.name} {ref.name}")
|
||||||
|
24
datatypes.py
24
datatypes.py
@ -1,4 +1,4 @@
|
|||||||
from typing import NewType, Union
|
from typing import NewType, TypedDict, Union
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
@ -60,15 +60,33 @@ class PDFSection(_PDFReference):
|
|||||||
title: SectionTitle
|
title: SectionTitle
|
||||||
|
|
||||||
|
|
||||||
PDFReference = Union[PDFPage, PDFSection]
|
@dataclass
|
||||||
|
class PDFDestination(_PDFReference):
|
||||||
|
"""Reference to a named destination in a PDF.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
name
|
||||||
|
Destination name.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
PDFReference = Union[PDFPage, PDFSection, PDFDestination]
|
||||||
# for now no other format is implemented
|
# for now no other format is implemented
|
||||||
# replace this with an union if that happens
|
# replace this with an union if that happens
|
||||||
Reference = PDFReference
|
Reference = PDFReference
|
||||||
|
|
||||||
|
|
||||||
# PyMuPDF type
|
# PyMuPDF types
|
||||||
@dataclass
|
@dataclass
|
||||||
class FitzBookmark:
|
class FitzBookmark:
|
||||||
level: int
|
level: int
|
||||||
title: SectionTitle
|
title: SectionTitle
|
||||||
page: PageNumber
|
page: PageNumber
|
||||||
|
|
||||||
|
class FitzDestinations(TypedDict):
|
||||||
|
page: PageNumber
|
||||||
|
to: tuple[int, int]
|
||||||
|
zoom: float
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from os import environ
|
from os import environ
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
from datatypes import PDFPage, PDFSection, PDFReference, Reference
|
from datatypes import PDFDestination, PDFPage, PDFSection, PDFReference, Reference
|
||||||
from typing import assert_never
|
from typing import assert_never
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@ -28,6 +28,8 @@ def format_pdf_link(ref: PDFReference) -> str:
|
|||||||
case PDFSection():
|
case PDFSection():
|
||||||
params["section"] = ref.title
|
params["section"] = ref.title
|
||||||
default_label = ref.title
|
default_label = ref.title
|
||||||
|
case PDFDestination():
|
||||||
|
params["destination"] = ref.name
|
||||||
case _ as obj:
|
case _ as obj:
|
||||||
assert_never(obj)
|
assert_never(obj)
|
||||||
|
|
||||||
|
18
pdf_data.py
18
pdf_data.py
@ -16,9 +16,23 @@ def get_section_pdf() -> PDFSection:
|
|||||||
rofi_res = rofi([f"{x.title}" for x in page_headers], prompt="Select header: ")
|
rofi_res = rofi([f"{x.title}" for x in page_headers], prompt="Select header: ")
|
||||||
if rofi_res is None or rofi_res.index is None:
|
if rofi_res is None or rofi_res.index is None:
|
||||||
raise RuntimeError("No header was selected.")
|
raise RuntimeError("No header was selected.")
|
||||||
selected_header = page_headers[rofi_res.index]
|
else:
|
||||||
|
selected_header = page_headers[rofi_res.index]
|
||||||
|
return PDFSection(filepath=page_ref.filepath, title=selected_header.title)
|
||||||
|
|
||||||
return PDFSection(filepath=page_ref.filepath, title=selected_header.title)
|
|
||||||
|
def get_destination_pdf() -> PDFDestination:
|
||||||
|
page_ref: PDFPage = get_page_pdf()
|
||||||
|
with fitz.Document(page_ref.filepath) as doc:
|
||||||
|
destinations = cast(FitzDestinations, cast(Any, doc).resolve_names())
|
||||||
|
page_dests = {k: x for k, x in destinations.items() if cast(Any, x)["page"]+1 == page_ref.page}
|
||||||
|
|
||||||
|
rofi_res = rofi([f"{k}" for k, _ in page_dests.items()], prompt="Select named destination: ")
|
||||||
|
if rofi_res is None or rofi_res.index is None:
|
||||||
|
raise RuntimeError("No destination was selected.")
|
||||||
|
else:
|
||||||
|
selected_header = [x for x in page_dests.items()][rofi_res.index]
|
||||||
|
return PDFDestination(filepath=page_ref.filepath, name=selected_header[0])
|
||||||
|
|
||||||
|
|
||||||
def get_page_pdf() -> PDFPage:
|
def get_page_pdf() -> PDFPage:
|
||||||
|
@ -14,6 +14,7 @@ query = parse_qs(url.query)
|
|||||||
|
|
||||||
page: PageNumber = PageNumber(int(query.get("page", ["0"])[0]))
|
page: PageNumber = PageNumber(int(query.get("page", ["0"])[0]))
|
||||||
section_list = query.get("section", [])
|
section_list = query.get("section", [])
|
||||||
|
destination_list = query.get("destination", [])
|
||||||
|
|
||||||
if section_list != []:
|
if section_list != []:
|
||||||
section: SectionTitle = SectionTitle(section_list[0])
|
section: SectionTitle = SectionTitle(section_list[0])
|
||||||
@ -25,8 +26,16 @@ if section_list != []:
|
|||||||
else:
|
else:
|
||||||
if len(headers) > 1:
|
if len(headers) > 1:
|
||||||
notify("", f"Multiple sections '{section}' found: page might be incorrect")
|
notify("", f"Multiple sections '{section}' found: page might be incorrect")
|
||||||
|
|
||||||
page = headers[0].page
|
page = headers[0].page
|
||||||
|
elif destination_list != []:
|
||||||
|
destination_name = destination_list[0]
|
||||||
|
with fitz.Document(url.path) as doc:
|
||||||
|
destinations = FitzDestinations(cast(Any, doc).resolve_names())
|
||||||
|
destination = destinations.get(destination_name)
|
||||||
|
if not destination:
|
||||||
|
notify("", f"Failed to find named destination '{destination_name}': did the document change?")
|
||||||
|
else:
|
||||||
|
page = destination["page"]+1
|
||||||
|
|
||||||
|
|
||||||
subprocess.run(["zathura", "--page", str(page), url.path], text=True)
|
subprocess.run(["zathura", "--page", str(page), url.path], text=True)
|
||||||
|
Loading…
Reference in New Issue
Block a user