├── CHANGELOG.org ├── README.org ├── README_CN.org └── org-annot-bridge.el /CHANGELOG.org: -------------------------------------------------------------------------------- 1 | - [2025-05-03] Version =0.0.2= 2 | - add =pdf= link in pdf-view mode 3 | - [2025-05-03] Version =0.0.1= 4 | - Extract pdf annot 5 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | [[file:README_CN.org][中文文档]] 2 | 3 | [[file:CHANGELOG.org][Changelog]] 4 | 5 | Extract outline and annotations to a org-mode note from PDF files. 6 | -------------------------------------------------------------------------------- /README_CN.org: -------------------------------------------------------------------------------- 1 | #+TITLE: 帮助文档 2 | 3 | [[file:CHANGELOG.org][Changelog]] 4 | 5 | * 🌟 功能介绍 6 | 7 | =org-annot-bridge= 可以帮你在 *外部文档的笔记* 和 *org-mode 笔记* 之间进行交互。基本功能: 8 | 9 | 1. PDF → org-mode:通过 [[https://github.com/yuchen-lea/pdfhelper/tree/master][pdfhelper]] 异步提取 PDF 注释到 org-mode 笔记 10 | - 支持高亮、框选、手写等多种 PDF 注释类型,并存储相关的图像。 11 | - 使用 [[https://pypi.org/project/Mako/][Mako]] 模板,自由定制导出的笔记格式 12 | 2. org-mode → PDF:通过 [[https://github.com/fuxialexander/org-pdftools][org-pdftools]] 提供的链接跳转到 PDF 13 | - ➡️ 计划自行实现跳转到 PDF 的链接类型,因为 [[https://github.com/fuxialexander/org-pdftools][org-pdftools]] 提供的功能有些繁杂,而且强依赖 [[https://github.com/weirdNox/org-noter][org-noter]],另一个功能过于繁杂的插件 14 | 3. ➡️ EPUB ⇌ org-mode:进行中,计划基于 [[https://depp.brause.cc/nov.el/img/][nov.el: Major mode for reading EPUBs in Emacs]] 15 | 16 | * 🚀 快速指南 17 | 18 | 1. 安装 pdfhelper [[https://github.com/yuchen-lea/pdfhelper][yuchen-lea/pdfhelper: Some useful functions to process pdf file]] 19 | 2. 安装并加载本包 20 | - doom 用户 21 | 1. 安装 22 | #+BEGIN_SRC emacs-lisp :tangle "packages.el" 23 | (package! org-annot-bridge :recipe (:host github :repo "yuchen-lea/org-annot-bridge")) 24 | 25 | #+END_SRC 26 | 2. 配置 27 | #+BEGIN_SRC emacs-lisp 28 | (use-package! org-media-note 29 | :config 30 | (setq org-annot-bridge-image-dir "~/Notes/imgs/") ;; 用于存储PDF中提取的图片 31 | ) 32 | #+END_SRC 33 | 3. 调用 =org-annot-bridge-export-pdf-annot-transient=,交互式地配置相应参数后,回车执行导出。 34 | 35 | * 📖 功能详解 36 | - 可配置 37 | + =org-annot-bridge-find-pdf-file-function= PDF 文件查找函数。 38 | + 图像相关 39 | - =org-annot-bridge-image-dir= 图像存储目录。 40 | - =org-annot-bridge-image-zoom-factor= 图像导出的 zoom factor 41 | + 格式相关:详见 [[https://github.com/yuchen-lea/pdfhelper][pdfhelper]] 帮助 42 | - =org-annot-bridge-annot-template= annot 格式模板 43 | - =org-annot-bridge-toc-template= 目录格式模板 44 | 45 | * 🛠️ 依赖 46 | - [[https://github.com/yuchen-lea/pdfhelper][pdfhelper]] 47 | 48 | * 相关插件 49 | - [[https://github.com/novoid/extract_pdf_annotations_to_orgmode][novoid/extract_pdf_annotations_to_orgmode: Extracting RepliGo PDF annotations to a Org-mode format snippet (unmaintained!)]] shell 脚本,适用范围较为有限 50 | -------------------------------------------------------------------------------- /org-annot-bridge.el: -------------------------------------------------------------------------------- 1 | ;;; org-annot-bridge.el --- Build bridge between annot and org-mode. -*- lexical-binding: t; -*- 2 | 3 | ;; Author: Yuchen Li 4 | ;; Url: https://github.com/yuchen-lea/org-annot-bridge 5 | ;; Version: 0.0.2 6 | ;; Package-Requires: ((emacs "24.4") (transient "0.1.0")) 7 | 8 | ;;; Commentary: 9 | ;;; License: 10 | 11 | ;; This program is free software; you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation, either version 3 of the License, or 14 | ;; (at your option) any later version. 15 | 16 | ;; This program is distributed in the hope that it will be useful, 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | ;; GNU General Public License for more details. 20 | 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with this program. If not, see . 23 | 24 | ;;; Code: 25 | ;;;; Requirements 26 | 27 | (require 'bookmark) 28 | (require 'transient) 29 | 30 | ;;;; Customization 31 | 32 | (defgroup org-annot-bridge nil 33 | "Build bridge between annot and org note." 34 | :prefix "org-annot-bridge-" 35 | :group 'org) 36 | 37 | (defcustom org-annot-bridge-image-dir org-directory 38 | "Directory to store extracted note images." 39 | :type 'directory) 40 | 41 | 42 | (defcustom org-annot-bridge-image-zoom-factor 4.0 43 | "Default zoom factor for exported images." 44 | :type 'number) 45 | 46 | (defcustom org-annot-bridge-annot-template nil 47 | "Template for annotation items using mako template syntax. 48 | 49 | If this is nil, the default template from pdfhelper will be used. 50 | For more details on the template syntax, see: 51 | https://github.com/yuchen-lea/pdfhelper" 52 | :type 'string) 53 | 54 | (defcustom org-annot-bridge-toc-template nil 55 | "Template for table of contents items using mako template syntax. 56 | 57 | If this is nil, the default template from pdfhelper will be used. 58 | For more details on the template syntax, see: 59 | https://github.com/yuchen-lea/pdfhelper" 60 | :type 'string) 61 | 62 | (defcustom org-annot-bridge-bib-files nil 63 | "List of paths to .bib files to find cite key." 64 | :type '(repeat file)) 65 | 66 | (defcustom org-annot-bridge-find-pdf-file-function (lambda () 67 | nil) 68 | "Function to find pdf file." 69 | :type 'function) 70 | 71 | ;;;; Commands 72 | 73 | (defun org-annot-bridge-check-pdfhelper-version () 74 | "Check if the pdfhelper program exists and its version is >= 2.3.1." 75 | (interactive) 76 | (let ((pdfhelper-path (executable-find "pdfhelper")) 77 | (required-version "2.3.1")) 78 | (if (not pdfhelper-path) 79 | (error "Command pdfhelper is not found. Please install it from https://github.com/yuchen-lea/pdfhelper") 80 | (let ((current-version (string-trim (shell-command-to-string "pdfhelper --version")))) 81 | (if (version< current-version required-version) 82 | (error "Your pdfhelper version (%s) is outdated. Please upgrade to the latest version from https://github.com/yuchen-lea/pdfhelper" 83 | current-version) 84 | (message "pdfhelper version is sufficient.")))))) 85 | 86 | ;;;; PDF 87 | ;;;;; PDF transient 88 | (defun org-annot-bridge-pdfhelper-export-annot (&optional args) 89 | "Run `pdfhelper export-annot` with the provided options." 90 | (interactive (list (transient-args 'org-annot-bridge-export-pdf-annot-transient))) 91 | (org-annot-bridge-check-pdfhelper-version) 92 | (bookmark-set "org-annot-bridge-temp-bookmark") 93 | (let* ((pdf-file (or (funcall org-annot-bridge-find-pdf-file-function) 94 | (read-file-name "Choose PDF file: " 95 | nil 96 | nil 97 | t 98 | nil 99 | (lambda (f) 100 | (string-match-p "\\.pdf\\'" f))))) 101 | (output-buffer (generate-new-buffer "*pdfhelper-output*")) 102 | (cmd (mapconcat #'identity 103 | (list "pdfhelper export-annot" 104 | (string-join (mapcar (lambda (arg) 105 | (replace-regexp-in-string "=" " " arg)) 106 | args) 107 | " ") 108 | (format "--annot-image-dir '%s'" org-annot-bridge-image-dir) 109 | (if org-annot-bridge-bib-files 110 | (format "--bib-path %s" 111 | (mapconcat (lambda (item) 112 | (format "'%s'" item)) 113 | org-annot-bridge-bib-files 114 | " "))) 115 | (if org-annot-bridge-annot-template 116 | (format "--annot-list-item-format '%s'" org-annot-bridge-annot-template)) 117 | (if org-annot-bridge-toc-template 118 | (format "--toc-list-item-format '%s'" org-annot-bridge-toc-template)) 119 | (format "'%s'" pdf-file)) 120 | " ")) 121 | (async-shell-command-display-buffer nil) 122 | (proc (progn 123 | (async-shell-command cmd output-buffer) 124 | (get-buffer-process output-buffer)))) 125 | (if (process-live-p proc) 126 | (set-process-sentinel proc 127 | #'(lambda (process signal) 128 | (when (memq (process-status process) 129 | '(exit signal)) 130 | (bookmark-jump "org-annot-bridge-temp-bookmark") 131 | (sleep-for 1) 132 | (goto-char (org-element-property :end (org-element-context))) 133 | (insert-buffer-substring (process-buffer process)) 134 | (bookmark-delete "org-annot-bridge-temp-bookmark") 135 | (kill-buffer (process-buffer process)) 136 | (shell-command-sentinel process signal)))) 137 | (message-box "No process running.")))) 138 | 139 | 140 | (transient-define-prefix org-annot-bridge-export-pdf-annot-transient () 141 | "Transient for `pdfhelper export-annot`." 142 | ["Arguments" 143 | ;; ("-o" "OCR Service" completing-read "OCR Service: " '("paddle" "ocrspace")) 144 | ;; ("-l" "OCR Language" completing-read "OCR Language: " '("zh-Hans" "zh-Hant" "en" "ja")) 145 | ;; ;; (if ocr-p 146 | ;; ;; (format "--ocr-service '%s'" ocr-service) 147 | ;; ;; "") 148 | ;; ;; (format "--ocr-language '%s'" ocr-language) 149 | ("z" "Image Zoom Factor" "--image-zoom=" :always-read t :allow-empty nil 150 | :init-value (lambda (obj) (oset obj value (number-to-string org-annot-bridge-image-zoom-factor)))) 151 | ("t" "With TOC" "--with-toc" 152 | :init-value (lambda (obj) (oset obj value "--with-toc"))) 153 | ("s" "Creation Start" "--creation-start=" :prompt "Annot Creation Start YYYY-MM-DD: ") 154 | ("e" "Creation End" "--creation-end=" :prompt "Annot Creation End YYYY-MM-DD: ") 155 | ("r" "Run Test" "--run-test")] 156 | ["Actions" 157 | ("RET" "export-annot" org-annot-bridge-pdfhelper-export-annot)]) 158 | 159 | ;;;;; PDF link 160 | 161 | (defcustom org-annot-bridge-pdf-link-prefix "pdf" 162 | "Prefix for pdf link" 163 | :type 'string) 164 | 165 | (defcustom org-annot-bridge-path-generator #'abbreviate-file-name 166 | "Translate file path the way you like. Take full-path as the argument." 167 | :type 'function) 168 | 169 | (defcustom org-annot-bridge-path-resolver #'expand-file-name 170 | "Resolve your translated PDF file path back to an absolute path." 171 | :type 'function) 172 | 173 | (require 'pdf-view) 174 | 175 | ;;;###autoload 176 | (defun org-annot-bridge-store-pdf-link () 177 | "Store a link to a pdfview buffer." 178 | (cond 179 | ((eq major-mode 'pdf-view-mode) 180 | (let* ((file (funcall org-annot-bridge-path-generator (pdf-view-buffer-file-name))) 181 | (page (number-to-string (pdf-view-current-page))) 182 | (height (org-annot-bridge--pdf-height-percent)) 183 | (locator (if (string= "0.00" (org-annot-bridge--pdf-height-percent)) 184 | page 185 | (format "%s++%s" page height)))) 186 | ;; pdf://path::page++height_percent 187 | (org-link-store-props :type org-annot-bridge-pdf-link-prefix 188 | :link (format "%s:%s::%s" 189 | org-annot-bridge-pdf-link-prefix 190 | file locator) 191 | :description locator))))) 192 | 193 | (defun org-annot-bridge--pdf-height-percent () 194 | "Return current pdf height percent, a float value between 0 and 1." 195 | (let* ((height (/ (or (image-mode-window-get 'vscroll) 196 | 0) 197 | (float (cdr (pdf-view-image-size)))))) 198 | (format "%.2f" height))) 199 | 200 | ;; pdf://path::page++height_percent 201 | (defun org-annot-bridge-open-pdf-link (link) 202 | "Internal function to open pdf LINK." 203 | (let ((link-regexp "\\(.*\\)::\\([0-9]*\\)\\(\\+\\+\\)?\\([[0-9]\\.*[0-9]*\\)?")) 204 | (cond 205 | ((string-match link-regexp link) 206 | (let ((path (match-string 1 link)) 207 | (page (match-string 2 link)) 208 | (height (match-string 4 link))) 209 | (if (and path 210 | (not (string-empty-p path)) 211 | (file-exists-p path)) 212 | (org-open-file (funcall org-annot-bridge-path-resolver path) 213 | 1)) 214 | (if (and page 215 | (not (string-empty-p page))) 216 | (progn 217 | (setq page (string-to-number page)) 218 | (pdf-view-goto-page page)) 219 | (setq page nil)) 220 | (when (and height 221 | (not (string-empty-p height))) 222 | (image-set-window-vscroll (round (* (string-to-number height) 223 | (cdr (pdf-view-image-size)))))))) 224 | ((org-open-file link 1))))) 225 | ;;;###autoload 226 | (defun org-annot-bridge-setup-link () 227 | "Set up pdf: links in org-mode." 228 | (org-link-set-parameters org-annot-bridge-pdf-link-prefix 229 | :follow #'org-annot-bridge-open-pdf-link 230 | :store #'org-annot-bridge-store-pdf-link)) 231 | ;;;; Footer 232 | (provide 'org-annot-bridge) 233 | ;;; org-annot-bridge.el ends here 234 | --------------------------------------------------------------------------------