├── README.org └── pdf-tools-org.el /README.org: -------------------------------------------------------------------------------- 1 | * Introduction 2 | pdf-tools-org is an emacs package that provides integration between pdf-tools and org-mode. 3 | The main features are importing and exporting pdf annotations from/to org files. 4 | 5 | * Installation 6 | - clone this repo to $PACKAGEDIR: 7 | #+BEGIN_SRC sh 8 | cd $PACKAGEDIR 9 | git clone https://github.com/pinguim06/pdf-tools-org 10 | #+END_SRC 11 | 12 | - add initialization your .emacs 13 | #+BEGIN_SRC elisp 14 | (add-to-list 'load-path "$PACKAGEDIR/pdf-tools-org") 15 | (require 'pdf-tools-org) 16 | #+END_SRC 17 | 18 | - if you want to auto export the annotations after saving a pdf, also add this: 19 | #+BEGIN_SRC elisp 20 | (add-hook 'after-save-hook 21 | (lambda () 22 | (when (eq major-mode 'pdf-view-mode) (pdf-tools-org-export-to-org)))) 23 | #+END_SRC 24 | 25 | * Usage 26 | - Use =(pdf-tools-org-export-to-org)= to export annotations of current pdf file being viewed in PDFView mode to an org file. 27 | - Use =(pdf-tools-org-import-from-org ORGFILE)= to import annotations from ORGFILE. 28 | 29 | * Known issues 30 | - Importing is not working quite well yet -- multiple annotations may be merged into one and multiple imports may create duplicates. 31 | 32 | 33 | -------------------------------------------------------------------------------- /pdf-tools-org.el: -------------------------------------------------------------------------------- 1 | ;;; pdf-tools-org.el --- pdf-tools and org-mode integration 2 | 3 | ;; Copyright (C) 2016 Carlos 4 | 5 | ;; Author: Carlos https://github.com/machc 6 | ;; Keywords: pdf-tools, export, annotations, org 7 | ;; Package: pdf-tools-export-annot 8 | ;; Version: 0.10 9 | ;; Package-Requires: ((emacs "24.3") (pdf-tools "0.70") (cl-lib "0.3")) 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 | ;;; Commentary: 25 | ;; 26 | ;; pdf-tools-org provides integration between pdf-tools and org-mode. 27 | ;; The main features are importing and exporting pdf annotations from/to 28 | ;; org files. 29 | 30 | ;;; Code: 31 | 32 | (require 'pdf-tools) 33 | (require 'cl-lib) 34 | 35 | (defgroup pdf-tools-org nil 36 | "pdf-tools and org-mode integration." 37 | :group 'pdf-tools) 38 | 39 | (defcustom pdf-tools-org-export-confirm-overwrite t 40 | "If nil, overwrite org file when exporting without asking for confirmation." 41 | :group 'pdf-tools-org 42 | :type 'boolean) 43 | 44 | (defconst pdf-tools-org-non-exportable-types 45 | (list 'link) 46 | "Types of annotation that are not to be exported.") 47 | (defconst pdf-tools-org-exportable-properties 48 | (list 'page 'edges 'id 'flags 'color 'modified 'label 'subject 'opacity 'created 'markup-edges 'icon)) 49 | 50 | ;; some properties are not changeable by default. 51 | ;; we miss some information when importing, such as creation date. 52 | (defconst pdf-tools-org-importable-properties 53 | (list 'contents 'edges 'flags 'color 'label 'opacity 'icon)) 54 | 55 | (defun pdf-tools-org-switch-to-pdf-or-org () 56 | "Switch buffers; from org to pdf or from pdf to org." 57 | (interactive) 58 | (let ((ext (file-name-extension (buffer-file-name))) 59 | (base (concat 60 | (file-name-directory (buffer-file-name)) 61 | (file-name-base (buffer-file-name))))) 62 | (cond 63 | ((string= ext "org") (find-file (concat base ".pdf"))) 64 | ((string= ext "pdf") (find-file (concat base ".org"))) 65 | (t (message "Not in org or pdf file."))))) 66 | 67 | (defun pdf-tools-org-edges-to-region (edges) 68 | "Attempt to get 4-entry region \(LEFT TOP RIGHT BOTTOM\) from several EDGES. 69 | We need this to import annotations and to get marked-up text, because 70 | annotations are referenced by its edges, but functions for these tasks 71 | need region." 72 | (let ((left0 (nth 0 (car edges))) 73 | (top0 (nth 1 (car edges))) 74 | (bottom0 (nth 3 (car edges))) 75 | (top1 (nth 1 (car (last edges)))) 76 | (right1 (nth 2 (car (last edges)))) 77 | (bottom1 (nth 3 (car (last edges)))) 78 | (n (safe-length edges))) 79 | ;; we try to guess the line height to move 80 | ;; the region away from the boundary and 81 | ;; avoid double lines 82 | (list left0 83 | (+ top0 (/ (- bottom0 top0) 3)) 84 | right1 85 | (- bottom1 (/ (- bottom1 top1) 3))))) 86 | 87 | ;;;###autoload 88 | (defun pdf-tools-org-export-to-org () 89 | "Export annotations to an Org file." 90 | (interactive) 91 | (let ((annots (sort (pdf-annot-getannots) 'pdf-annot-compare-annotations)) 92 | (filename (format "%s.org" 93 | (file-name-sans-extension 94 | (buffer-name)))) 95 | (buffer (current-buffer))) 96 | (with-temp-buffer 97 | ;; org-set-property sometimes never returns if buffer not in org-mode 98 | (org-mode) 99 | (insert (concat "#+TITLE: Notes for " (file-name-sans-extension filename))) 100 | (mapc 101 | (lambda (annot) ;; traverse all annotations 102 | (let* ((page (pdf-annot-get annot 'page)) 103 | (has-markup-edges (pdf-annot-get annot 'markup-edges)) 104 | (edges (if has-markup-edges 105 | (car (pdf-annot-get annot 'markup-edges)) 106 | (pdf-annot-get annot 'edges))) 107 | (contents (pdf-annot-get annot 'contents)) 108 | (id (symbol-name (pdf-annot-get-id annot))) 109 | (type (symbol-name (pdf-annot-get-type annot)))) 110 | (org-insert-heading-respect-content) 111 | (insert (concat "[[pdfview:" 112 | (buffer-name buffer) "::" 113 | (number-to-string page) "++" 114 | ;; height 115 | (number-to-string (nth 1 edges)) "][" 116 | id "]]")) 117 | (insert (concat " :" type ":")) 118 | ;; insert text from marked-up region in an org-mode quote 119 | (when has-markup-edges 120 | (insert (concat "\n#+BEGIN_QUOTE\n" 121 | (with-current-buffer buffer 122 | (pdf-info-gettext page 123 | (pdf-tools-org-edges-to-region 124 | (pdf-annot-get annot 'markup-edges)))) 125 | "\n#+END_QUOTE"))) 126 | (insert (concat "\n" contents)) 127 | ;; set org properties for each of the remaining fields 128 | (mapcar 129 | '(lambda (field) ;; traverse all fields 130 | (when (member (car field) pdf-tools-org-exportable-properties) 131 | (org-set-property (symbol-name (car field)) 132 | (format "%s" (cdr field))))) 133 | annot))) 134 | (cl-remove-if 135 | (lambda (annot) (member (pdf-annot-get-type annot) pdf-tools-org-non-exportable-types)) 136 | annots) 137 | ) 138 | (write-file filename pdf-tools-org-export-confirm-overwrite)))) 139 | 140 | ;;;###autoload 141 | (defun pdf-tools-org-import-from-org (orgfile) 142 | "Import annotations from an Org file `ORGFILE'." 143 | (interactive (list (ido-read-file-name "Org file to import from: "))) 144 | (let ((pdfbuffer (current-buffer))) 145 | (save-window-excursion 146 | (find-file orgfile) 147 | (goto-char (point-min)) 148 | (org-next-visible-heading 1) 149 | (while (/= (point) (buffer-end 1)) ;; traverse org file 150 | (let ((properties (list))) 151 | ;; build list of properties 152 | (mapc 153 | (lambda (x) 154 | (let ((propname (intern (downcase (car x)))) (propval (cdr x))) 155 | (when (member propname pdf-tools-org-exportable-properties) 156 | (push 157 | (cons propname 158 | (cond ;; convert from string to proper types 159 | ((member propname (list 'page 'flags 'opacity)) (string-to-number propval)) 160 | ((member propname (list 'id)) (intern propval)) 161 | ((string-equal propval "nil") nil) 162 | ((member propname (list 'edges 'modified)) 163 | (mapcar 'string-to-number (split-string propval " \\|(\\|)" t))) 164 | ;; markup-edges is a list of lists of 4 165 | ((member propname (list 'markup-edges)) 166 | (mapcar (lambda (x) 167 | (mapcar 'string-to-number (split-string x " \\|(\\|)" t))) 168 | (split-string propval "\) \("))) 169 | (t propval))) 170 | properties)))) 171 | (org-entry-properties)) 172 | ;; include 'type 173 | (let ((typestr (if (org-get-tags) 174 | (intern (car (org-get-tags))) 175 | (re-search-forward ":\\([^:]*?\\):$") 176 | (intern (match-string-no-properties 1))))) 177 | (push (cons 'type typestr) 178 | properties)) 179 | ;; add contents -- they are the subtree text, after the properties 180 | (push (cons 'contents 181 | (let* ((end (save-excursion (org-next-visible-heading 1) (point))) 182 | (beg (save-excursion 183 | (unless (re-search-forward "#\\+END_QUOTE\n" end t) 184 | (re-search-forward ":END:\n")) 185 | (point)))) 186 | (buffer-substring-no-properties beg end))) 187 | properties) 188 | ;; add annotation 189 | (with-current-buffer pdfbuffer 190 | (pdf-annot-add-annotation (pdf-annot-get properties 'type) 191 | (if (eq (pdf-annot-get properties 'type) 'text) 192 | (pdf-annot-get properties 'edges) 193 | (pdf-tools-org-edges-to-region (pdf-annot-get properties 'markup-edges))) 194 | (delq nil (mapcar 195 | (lambda (x) 196 | (if (member (car x) pdf-tools-org-importable-properties) 197 | x nil)) 198 | properties)) 199 | (pdf-annot-get properties 'page))) 200 | (org-next-visible-heading 1)))))) 201 | 202 | 203 | (provide 'pdf-tools-org) 204 | 205 | ;;; pdf-tools-org.el ends here 206 | --------------------------------------------------------------------------------