diff --git a/README.md b/README.md index 10e40fe..7be3243 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This is a Python rewrite of Gilles Castel's [Instant Reference](https://github.com/gillescastel/instant-reference) tool. (I was not a fan of needing NPM rather than the system package manager to install some dependencies.) -pyinstantref allows you to copy a link to a specific page in a PDF with a single keybind in Zathura. +pyinstantref allows you to copy a link to a specific page or header in a PDF with a single keybind in Zathura. You can then paste this reference in your notes and other documents. For now, it only works with my own [templates](https://github.com/dogeystamp/typst-templates) for [Typst](https://github.com/typst/typst), @@ -50,7 +50,7 @@ This will make Ctrl-L copy a reference to the current page in Zathura. ## limitations -Currently, the following features are missing: +Currently, the following features are missing compared to Castel's version: - ArXiv support - LaTeX output - Support for other PDF readers (e.g. Evince) @@ -58,3 +58,9 @@ Currently, the following features are missing: Feel free to send pull requests, although this project is primarily for my own usage and I can not make any guarantees. + +Also: +- Section references are unreliable because titles might change, + and there might be sections with the same title. + Proper IDs for bookmarks are possible, + but not until Typst resolves [issue #1352](https://github.com/typst/typst/issues/1352). diff --git a/copy_ref b/copy_ref index 1af9288..da3c16b 100755 --- a/copy_ref +++ b/copy_ref @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -from pdf_data import get_page_pdf +from pdf_data import get_page_pdf, get_section_pdf from datatypes import * from enum import Enum, auto +from util import notify import subprocess -import pydbus import argparse import formatter.typst as typst_fmt @@ -32,21 +32,19 @@ def copy_ref(ref: Reference, format: LinkFormat) -> None: clip_copy(link_txt) -def notify(title:str, txt: str) -> None: - """Send a text notification.""" - bus = pydbus.SessionBus() - notifs = bus.get(".Notifications") - notifs.Notify("instantref", 0, "dialog-information", title, txt, [], {}, 5000) - - if __name__ == "__main__": args = parser.parse_args() if args.section: - raise NotImplementedError("--section isn't implemented") - - ref = get_page_pdf() + ref = get_section_pdf() + else: + ref = get_page_pdf() format = LinkFormat.TYPST copy_ref(ref, format) - notify("Copied ref", f"{ref.filepath.name} p. {ref.page}") + + match ref: + case PDFPage(): + notify("Copied ref", f"{ref.filepath.name} p. {ref.page}") + case PDFSection(): + notify("Copied ref", f"{ref.filepath.name} sec. {ref.title}") diff --git a/datatypes.py b/datatypes.py index bda4c41..4b201f7 100644 --- a/datatypes.py +++ b/datatypes.py @@ -63,3 +63,10 @@ PDFReference = Union[PDFPage, PDFSection] # for now no other format is implemented # replace this with an union if that happens Reference = PDFReference + +# PyMuPDF type +@dataclass +class FitzBookmark: + level: int + title: SectionTitle + page: PageNumber diff --git a/pdf_data.py b/pdf_data.py index 438180b..a4c1f15 100644 --- a/pdf_data.py +++ b/pdf_data.py @@ -1,7 +1,24 @@ from pathlib import Path from datatypes import * +from typing import cast, Any +from util import rofi import pydbus import subprocess +import fitz + + +def get_section_pdf() -> PDFSection: + page_ref: PDFPage = get_page_pdf() + with fitz.Document(page_ref.filepath) as doc: + toc = [FitzBookmark(*x) for x in cast(Any, doc).get_toc()] + page_headers = [x for x in toc if x.page == page_ref.page] + + 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: + raise RuntimeError("No header was selected.") + selected_header = page_headers[rofi_res.index] + + return PDFSection(filepath=page_ref.filepath, title=selected_header.title) def get_page_pdf() -> PDFPage: diff --git a/pdfref_handler b/pdfref_handler index 48534c8..c102bd6 100755 --- a/pdfref_handler +++ b/pdfref_handler @@ -1,15 +1,31 @@ #!/usr/bin/env python3 # pdfref:// URL handler -import subprocess from urllib.parse import urlparse, parse_qs from sys import argv - -from datatypes import PageNumber +from datatypes import * +from typing import cast, Any +from util import notify +import subprocess +import fitz url = urlparse(argv[1]) query = parse_qs(url.query) page: PageNumber = PageNumber(int(query.get("page", ["0"])[0])) +section: SectionTitle = SectionTitle(query.get("section", [])[0]) + +if section != []: + with fitz.Document(url.path) as doc: + toc = [FitzBookmark(*x) for x in cast(Any, doc).get_toc()] + headers = [x for x in toc if x.title == section] + if headers == []: + notify("", f"Failed to find section '{section}': did the title change?") + else: + if len(headers) > 1: + notify("", f"Multiple sections '{section}' found: page might be incorrect") + + page = headers[0].page + subprocess.run(["zathura", "--page", str(page), url.path], text=True) diff --git a/requirements.txt b/requirements.txt index 12c7d9a..4931689 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ pycairo==1.24.0 pydbus==0.6.0 PyGObject==3.44.1 +PyMuPDF==1.22.5 diff --git a/util.py b/util.py index bcc71ed..f58e61f 100644 --- a/util.py +++ b/util.py @@ -1,4 +1,5 @@ import subprocess +import pydbus from dataclasses import dataclass from typing import Optional @@ -56,3 +57,10 @@ def rofi(entries: list[str], prompt: str="> ", fuzzy=True, extra_args=[]) -> Opt pass return ret + + +def notify(title:str, txt: str) -> None: + """Send a text notification.""" + bus = pydbus.SessionBus() + notifs = bus.get(".Notifications") + notifs.Notify("instantref", 0, "dialog-information", title, txt, [], {}, 5000)