pdf-annot.el (71830B)
1 ;;; pdf-annot.el --- Annotation support for PDF files. -*- lexical-binding: t -*- 2 3 ;; Copyright (C) 2013, 2014 Andreas Politz 4 5 ;; Author: Andreas Politz <politza@fh-trier.de> 6 ;; Keywords: 7 8 ;; This program is free software; you can redistribute it and/or modify 9 ;; it under the terms of the GNU General Public License as published by 10 ;; the Free Software Foundation, either version 3 of the License, or 11 ;; (at your option) any later version. 12 13 ;; This program is distributed in the hope that it will be useful, 14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 ;; GNU General Public License for more details. 17 18 ;; You should have received a copy of the GNU General Public License 19 ;; along with this program. If not, see <http://www.gnu.org/licenses/>. 20 21 ;;; Commentary: 22 ;; 23 24 25 (require 'pdf-view) 26 (require 'pdf-info) 27 (require 'pdf-cache) 28 (require 'pdf-misc) 29 (require 'pdf-util) 30 (require 'facemenu) ;; list-colors-duplicates 31 (require 'faces) ;; color-values 32 (require 'org) ;; org-create-formula-image 33 (require 'tablist) 34 (require 'cl-lib) 35 (require 'seq) 36 37 38 ;; * ================================================================== * 39 ;; * Customizations 40 ;; * ================================================================== * 41 42 ;;; Code: 43 44 (defgroup pdf-annot nil 45 "Annotation support for PDF documents." 46 :group 'pdf-tools) 47 48 (defcustom pdf-annot-activate-handler-functions nil 49 "A list of functions to activate a annotation. 50 51 The functions on this hook will be called when some annotation is 52 activated, usually by a mouse-click. Each one is called with the 53 annotation as a single argument and it should return a non-nil 54 value if it has `handled' it. If no such function exists, the 55 default handler `pdf-annot-default-activate-handler' will be 56 called. 57 58 This hook is meant to allow for custom annotations. FIXME: 59 Implement and describe basic org example." 60 :type 'hook) 61 62 (defcustom pdf-annot-default-text-annotation-properties nil 63 "Alist of initial properties for new text annotations." 64 :type '(alist :key-type symbol :value-type sexp)) 65 66 (defcustom pdf-annot-default-markup-annotation-properties nil 67 "Alist of initial properties for new markup annotations." 68 :type '(alist :key-type symbol :value-type sexp)) 69 70 (make-obsolete-variable 'pdf-annot-default-text-annotation-properties 71 'pdf-annot-default-annotation-properties 72 "0.90") 73 74 (make-obsolete-variable 'pdf-annot-default-markup-annotation-properties 75 'pdf-annot-default-annotation-properties 76 "0.90") 77 78 (defcustom pdf-annot-default-annotation-properties 79 `((t (label . ,user-full-name)) 80 (text (icon . "Note") 81 (color . "#ff0000")) 82 (highlight (color . "yellow")) 83 (squiggly (color . "orange")) 84 (strike-out(color . "red")) 85 (underline (color . "blue"))) 86 "An alist of initial properties for new annotations. 87 88 The alist contains a sub-alist for each of the currently available 89 annotation types, i.e. text, highlight, squiggly, strike-out and 90 underline. Additionally a sub-alist with a key of t acts as a default 91 entry. 92 93 Each of these sub-alists contain default property-values of newly 94 added annotations of its respective type. 95 96 Some of the most important properties and their types are label 97 \(a string\), contents \(a string\), color \(a color\) and, for 98 text-annotations only, icon \(one of the standard icon-types, see 99 `pdf-annot-standard-text-icons'\). 100 101 For example a value of 102 103 \(\(t \(color . \"red\"\) 104 \(label . \"Joe\"\) 105 \(highlight \(color . \"green\"\)\) 106 107 would use a green color for highlight and a red one for other 108 annotations. Additionally the label for all annotations is set 109 to \"Joe\"." 110 111 :type (let* ((label '(cons :tag "Label" (const label) string)) 112 (contents '(cons :tag "Contents" (const contents) string)) 113 (color '(cons :tag "Color" (const color) color)) 114 (icon `(cons :tag "Icon" 115 (const icon) 116 (choice 117 ,@(mapcar (lambda (icon) 118 `(const ,icon)) 119 '("Note" "Comment" "Key" "Help" "NewParagraph" 120 "Paragraph" "Insert" "Cross" "Circle"))))) 121 (other '(repeat 122 :tag "Other properties" 123 (cons :tag "Property" 124 (symbol :tag "Key ") 125 (sexp :tag "Value")))) 126 (text-properties 127 `(set ,label ,contents ,color ,icon ,other)) 128 (markup-properties 129 `(set ,label ,contents ,color)) 130 (all-properties 131 `(set ,label ,contents ,color ,icon ,other))) 132 `(set 133 (cons :tag "All Annotations" (const t) ,all-properties) 134 (cons :tag "Text Annotations" (const text) ,text-properties) 135 (cons :tag "Highlight Annotations" (const highlight) ,markup-properties) 136 (cons :tag "Underline Annotations" (const underline) ,markup-properties) 137 (cons :tag "Squiggly Annotations" (const squiggly) ,markup-properties) 138 (cons :tag "Strike-out Annotations" (const strike-out) ,markup-properties)))) 139 140 (defcustom pdf-annot-print-annotation-functions 141 '(pdf-annot-print-annotation-latex-maybe) 142 "A alist of functions for printing annotations, e.g. for the tooltip. 143 144 The functions receive the annotation as single argument and 145 should return either a string or nil. The first string returned 146 will be used. 147 148 If all of them return nil, the default function 149 `pdf-annot-print-annotation-default' is used." 150 :type 'hook) 151 152 (defcustom pdf-annot-latex-string-predicate 153 (lambda (str) 154 (and str (string-match "\\`[[:space:]\n]*[$\\]" str))) 155 "A predicate for recognizing LaTeX fragments. 156 157 It receives a string and should return non-nil, if string is a 158 LaTeX fragment." 159 :type 'function) 160 161 (defcustom pdf-annot-latex-header 162 (concat org-format-latex-header 163 "\n\\setlength{\\textwidth}{12cm}") 164 "Header used when latex compiling annotations. 165 The default value is `org-format-latex-header' + 166 \"\\n\\\\setlength{\\\\textwidth}{12cm}\"." 167 :type 'string) 168 169 (defcustom pdf-annot-tweak-tooltips t 170 "Whether this package should tweak some settings regarding tooltips. 171 172 If this variable has a non-nil value, 173 174 `x-gtk-use-system-tooltips' is set to nil if appropriate, in 175 order to display text properties; 176 177 `tooltip-hide-delay' is set to infinity, in order to not being 178 annoyed while reading the annotations." 179 :type 'boolean) 180 181 (defcustom pdf-annot-activate-created-annotations nil 182 "Whether to activate (i.e. edit) created annotations." 183 :type 'boolean) 184 185 (defcustom pdf-annot-attachment-display-buffer-action nil 186 "The display action used when displaying attachments." 187 :type display-buffer--action-custom-type) 188 189 (defconst pdf-annot-annotation-types 190 '(3d caret circle file 191 free-text highlight ink line link movie poly-line polygon popup 192 printer-mark screen sound square squiggly stamp strike-out text 193 trap-net underline unknown watermark widget) 194 "Complete list of annotation types.") 195 196 (defcustom pdf-annot-list-listed-types 197 (if (pdf-info-markup-annotations-p) 198 (list 'text 'file 'squiggly 'highlight 'underline 'strike-out) 199 (list 'text 'file)) 200 "A list of annotation types displayed in the list buffer." 201 :type `(set ,@(mapcar (lambda (type) 202 (list 'const type)) 203 pdf-annot-annotation-types))) 204 205 206 ;; * ================================================================== * 207 ;; * Variables and Macros 208 ;; * ================================================================== * 209 210 (defvar pdf-annot-color-history nil 211 "A list of recently used colors for annotations.") 212 213 (defvar-local pdf-annot-modified-functions nil 214 "Functions to call, when an annotation was modified. 215 216 A function on this hook should accept one argument: A CLOSURE 217 containing inserted, changed and deleted annotations. 218 219 It may access these annotations by calling CLOSURE with one of 220 these arguments: 221 222 `:inserted' The list of recently added annotations. 223 224 `:deleted' The list of recently deleted annotations. 225 226 `:changed' The list of recently changed annotations. 227 228 t The union of recently added, deleted or changed annotations. 229 230 nil Just returns nil. 231 232 Any other argument signals an error.") 233 234 (defconst pdf-annot-text-annotation-size '(24 . 24) 235 "The Size of text and file annotations in PDF points. 236 237 These values are hard-coded in poppler. And while the size of 238 these annotations may be changed, i.e. the edges property, it has 239 no effect on the rendering.") 240 241 (defconst pdf-annot-markup-annotation-types 242 '(text link free-text line square 243 circle polygon poly-line highlight underline squiggly 244 strike-out stamp caret ink file sound) 245 "List of defined markup annotation types.") 246 247 (defconst pdf-annot-standard-text-icons 248 '("Note" "Comment" "Key" "Help" "NewParagraph" 249 "Paragraph" "Insert" "Cross" "Circle") 250 "A list of standard icon properties for text annotations.") 251 252 (defvar pdf-annot-inhibit-modification-hooks nil 253 "Controls the behavior of `pdf-annot-modified-functions'. 254 255 If non-nil, `pdf-annot-modified-functions' are not run on any 256 annotation change.") 257 258 (defvar-local pdf-annot-delayed-modified-annotations nil 259 "A plist of not yet propagated modifications. 260 261 It contains three entries :change, :delete and :insert. Each one 262 having a list of annotations as value.") 263 264 (defvar-local pdf-annot--attachment-file-alist nil 265 "Alist mapping attachment ids to unique relative filenames.") 266 267 (defmacro pdf-annot-with-atomic-modifications (&rest body) 268 "Execute BODY joining multiple modifications. 269 270 The effect is, that `pdf-annot-modified-functions' will be called 271 only once at the end of BODY. 272 273 BODY should not modify annotations in a different then the 274 current buffer, because that won't run the hooks properly." 275 (declare (indent 0) (debug t)) 276 `(unwind-protect 277 (save-current-buffer 278 (let ((pdf-annot-inhibit-modification-hooks t)) 279 (progn ,@body))) 280 (pdf-annot-run-modified-hooks))) 281 282 283 ;; * ================================================================== * 284 ;; * Minor mode 285 ;; * ================================================================== * 286 287 (defcustom pdf-annot-minor-mode-map-prefix (kbd "C-c C-a") 288 "The prefix to use for `pdf-annot-minor-mode-map'. 289 290 Setting this after the package was loaded has no effect." 291 :type 'key-sequence) 292 293 (defvar pdf-annot-minor-mode-map 294 (let ((kmap (make-sparse-keymap)) 295 (smap (make-sparse-keymap))) 296 (define-key kmap pdf-annot-minor-mode-map-prefix smap) 297 (define-key smap "l" #'pdf-annot-list-annotations) 298 (define-key smap "a" #'pdf-annot-attachment-dired) 299 (when (pdf-info-writable-annotations-p) 300 (define-key smap "D" #'pdf-annot-delete) 301 (define-key smap "t" #'pdf-annot-add-text-annotation) 302 (when (pdf-info-markup-annotations-p) 303 (define-key smap "m" #'pdf-annot-add-markup-annotation) 304 (define-key smap "s" #'pdf-annot-add-squiggly-markup-annotation) 305 (define-key smap "u" #'pdf-annot-add-underline-markup-annotation) 306 (define-key smap "o" #'pdf-annot-add-strikeout-markup-annotation) 307 (define-key smap "h" #'pdf-annot-add-highlight-markup-annotation))) 308 kmap) 309 "Keymap used for `pdf-annot-minor-mode'.") 310 311 (defvar savehist-minibuffer-history-variables) 312 313 ;;;###autoload 314 (define-minor-mode pdf-annot-minor-mode 315 "Support for PDF Annotations. 316 317 \\{pdf-annot-minor-mode-map}" 318 :group 'pdf-annot 319 (cond 320 (pdf-annot-minor-mode 321 (when pdf-annot-tweak-tooltips 322 (when (boundp 'x-gtk-use-system-tooltips) 323 (setq x-gtk-use-system-tooltips nil)) 324 (setq tooltip-hide-delay 3600)) 325 (pdf-view-add-hotspot-function 'pdf-annot-hotspot-function 9) 326 (add-hook 'pdf-info-close-document-hook 327 #'pdf-annot-attachment-delete-base-directory nil t) 328 (when (featurep 'savehist) 329 (add-to-list 'savehist-minibuffer-history-variables 330 'pdf-annot-color-history))) 331 (t 332 (pdf-view-remove-hotspot-function 'pdf-annot-hotspot-function) 333 (remove-hook 'pdf-info-close-document-hook 334 #'pdf-annot-attachment-delete-base-directory t))) 335 (pdf-view-redisplay t)) 336 337 (defun pdf-annot-create-context-menu (a) 338 "Create a appropriate context menu for annotation A." 339 (let ((menu (make-sparse-keymap))) 340 ;; (when (and (bound-and-true-p pdf-misc-menu-bar-minor-mode) 341 ;; (bound-and-true-p pdf-misc-install-popup-menu)) 342 ;; (set-keymap-parent menu 343 ;; (lookup-key pdf-misc-menu-bar-minor-mode-map 344 ;; [menu-bar pdf-tools])) 345 ;; (define-key menu [sep-99] menu-bar-separator)) 346 (when (pdf-info-writable-annotations-p) 347 (define-key menu [delete-annotation] 348 `(menu-item "Delete annotation" 349 ,(lambda () 350 (interactive) 351 (pdf-annot-delete a) 352 (message "Annotation deleted")) 353 :help 354 "Delete this annotation."))) 355 (define-key menu [goto-annotation] 356 `(menu-item "List annotation" 357 ,(lambda () 358 (interactive) 359 (pdf-annot-show-annotation a t) 360 (pdf-annot-list-annotations) 361 (pdf-annot-list-goto-annotation a)) 362 :help "Find this annotation in the list buffer.")) 363 (when (pdf-annot-text-annotation-p a) 364 (define-key menu [change-text-icon] 365 `(menu-item "Change icon" 366 ,(pdf-annot-create-icon-submenu a) 367 :help "Change the appearance of this annotation."))) 368 (define-key menu [change-color] 369 `(menu-item "Change color" 370 ,(pdf-annot-create-color-submenu a) 371 :help "Change the appearance of this annotation.")) 372 (define-key menu [activate-annotation] 373 `(menu-item "Activate" 374 ,(lambda () 375 (interactive) 376 (pdf-annot-activate-annotation a)) 377 :help "Activate this annotation.")) 378 menu)) 379 380 (defun pdf-annot-create-color-submenu (a) 381 "Show the user a color menu for their annotation A." 382 (let ((menu (make-sparse-keymap))) 383 (define-key menu [color-chooser] 384 `(menu-item "Choose ..." 385 ,(lambda () 386 (interactive) 387 (list-colors-display 388 nil "*Choose annotation color*" 389 ;; list-colors-print does not like closures. 390 (let ((callback (make-symbol "xcallback"))) 391 (fset callback 392 (lambda (color) 393 (pdf-annot-put a 'color color) 394 (setq pdf-annot-color-history 395 (cons color 396 (remove color pdf-annot-color-history))) 397 (quit-window t))) 398 (list 'function callback)))))) 399 (dolist (color (butlast (reverse pdf-annot-color-history) 400 (max 0 (- (length pdf-annot-color-history) 401 12)))) 402 (define-key menu (vector (intern (format "color-%s" color))) 403 `(menu-item ,color 404 ,(lambda nil 405 (interactive) 406 (pdf-annot-put a 'color color))))) 407 menu)) 408 409 (defun pdf-annot-create-icon-submenu (a) 410 "Show the user an icon menu for the annotation A." 411 (let ((menu (make-sparse-keymap))) 412 (dolist (icon (reverse pdf-annot-standard-text-icons)) 413 (define-key menu (vector (intern (format "icon-%s" icon))) 414 `(menu-item ,icon 415 ,(lambda nil 416 (interactive) 417 (pdf-annot-put a 'icon icon))))) 418 menu)) 419 420 ;; * ================================================================== * 421 ;; * Annotation Basics 422 ;; * ================================================================== * 423 424 (defun pdf-annot-create (alist &optional buffer) 425 "Create a annotation from ALIST in BUFFER. 426 427 ALIST should be a property list as returned by 428 `pdf-cache-getannots'. BUFFER should be the buffer of the 429 corresponding PDF document. It defaults to the current buffer." 430 431 (cons `(buffer . ,(or buffer (current-buffer))) 432 alist)) 433 434 (defun pdf-annot-getannots (&optional pages types buffer) 435 "Return a list of annotations on PAGES of TYPES in BUFFER. 436 437 See `pdf-info-normalize-pages' for valid values of PAGES. TYPES 438 may be a symbol or list of symbols denoting annotation types. 439 440 PAGES defaults to all pages, TYPES to all types and BUFFER to the 441 current buffer." 442 443 (pdf-util-assert-pdf-buffer buffer) 444 (unless buffer 445 (setq buffer (current-buffer))) 446 (unless (listp types) 447 (setq types (list types))) 448 (with-current-buffer buffer 449 (let (result) 450 (dolist (a (pdf-info-getannots pages)) 451 (when (or (null types) 452 (memq (pdf-annot-get a 'type) types)) 453 (push (pdf-annot-create a) result))) 454 result))) 455 456 (defun pdf-annot-getannot (id &optional buffer) 457 "Return the annotation object for annotation ID. 458 459 Optionally take the BUFFER name of the PDF buffer. When none is 460 provided, the `current-buffer' is picked up." 461 (pdf-annot-create 462 (pdf-info-getannot id buffer) 463 buffer)) 464 465 (defun pdf-annot-get (a property &optional default) 466 "Get annotation A's value of PROPERTY. 467 468 Return DEFAULT, if value is nil." 469 (or (cdr (assq property a)) default)) 470 471 (defun pdf-annot-put (a property value) 472 "Set annotation A's PROPERTY to VALUE. 473 474 Unless VALUE is `equal' to the current value, sets A's buffer's 475 modified flag and runs the hook `pdf-annot-modified-functions'. 476 477 Signals an error, if PROPERTY is not modifiable. 478 479 Returns the modified annotation." 480 481 (declare (indent 2)) 482 (unless (equal value (pdf-annot-get a property)) 483 (unless (pdf-annot-property-modifiable-p a property) 484 (error "Property `%s' is read-only for this annotation" 485 property)) 486 (with-current-buffer (pdf-annot-get-buffer a) 487 (setq a (pdf-annot-create 488 (pdf-info-editannot 489 (pdf-annot-get-id a) 490 `((,property . ,value))))) 491 (set-buffer-modified-p t) 492 (pdf-annot-run-modified-hooks :change a))) 493 a) 494 495 (defun pdf-annot-run-modified-hooks (&optional operation &rest annotations) 496 "Run `pdf-annot-modified-functions' using OPERATION on ANNOTATIONS. 497 498 OPERATION should be one of nil, :change, :insert or :delete. If 499 nil, annotations should be empty. 500 501 Redisplay modified pages. 502 503 If `pdf-annot-inhibit-modification-hooks' in non-nil, this just 504 saves ANNOTATIONS and does not call the hooks until later, when 505 the variable is nil and this function is called again." 506 507 (unless (memq operation '(nil :insert :change :delete)) 508 (error "Invalid operation: %s" operation)) 509 (when (and (null operation) annotations) 510 (error "Missing operation argument")) 511 512 (when operation 513 (let ((list (plist-get pdf-annot-delayed-modified-annotations operation))) 514 (dolist (a annotations) 515 (cl-pushnew a list :test 'pdf-annot-equal)) 516 (setq pdf-annot-delayed-modified-annotations 517 (plist-put pdf-annot-delayed-modified-annotations 518 operation list)))) 519 (unless pdf-annot-inhibit-modification-hooks 520 (let* ((changed (plist-get pdf-annot-delayed-modified-annotations :change)) 521 (inserted (mapcar (lambda (a) 522 (or (car (cl-member a changed :test 'pdf-annot-equal)) 523 a)) 524 (plist-get pdf-annot-delayed-modified-annotations :insert))) 525 (deleted (plist-get pdf-annot-delayed-modified-annotations :delete)) 526 (union (cl-union (cl-union changed inserted :test 'pdf-annot-equal) 527 deleted :test 'pdf-annot-equal)) 528 (closure (lambda (arg) 529 (when arg 530 (cl-case arg 531 (:inserted (copy-sequence inserted)) 532 (:changed (copy-sequence changed)) 533 (:deleted (copy-sequence deleted)) 534 (t (copy-sequence union)))))) 535 (pages (mapcar (lambda (a) (pdf-annot-get a 'page)) union))) 536 (when union 537 (unwind-protect 538 (run-hook-with-args 539 'pdf-annot-modified-functions closure) 540 (setq pdf-annot-delayed-modified-annotations nil) 541 (apply #'pdf-view-redisplay-pages pages)))))) 542 543 (defun pdf-annot-equal (a1 a2) 544 "Return non-nil, if annotations A1 and A2 are equal. 545 546 Two annotations are equal, if they belong to the same buffer and 547 have identical id properties." 548 (and (eq (pdf-annot-get-buffer a1) 549 (pdf-annot-get-buffer a2)) 550 (eq (pdf-annot-get-id a1) 551 (pdf-annot-get-id a2)))) 552 553 (defun pdf-annot-get-buffer (a) 554 "Return annotation A's buffer." 555 (pdf-annot-get a 'buffer)) 556 557 (defun pdf-annot-get-id (a) 558 "Return id property of annotation A." 559 (pdf-annot-get a 'id)) 560 561 (defun pdf-annot-get-type (a) 562 "Return type property of annotation A." 563 (pdf-annot-get a 'type)) 564 565 (defun pdf-annot-get-display-edges (a) 566 "Return a list of EDGES used for display for annotation A. 567 568 This returns a list of \(LEFT TOP RIGHT BOT\) demarking the 569 rectangles of the page where A is rendered." 570 571 (or (pdf-annot-get a 'markup-edges) 572 (list (pdf-annot-get a 'edges)))) 573 574 (defun pdf-annot-delete (a) 575 "Delete annotation A. 576 577 Sets A's buffer's modified flag and runs the hook 578 `pdf-annot-modified-functions'. 579 580 This function always returns nil." 581 (interactive 582 (list (pdf-annot-read-annotation 583 "Click on the annotation you wish to delete"))) 584 (with-current-buffer (pdf-annot-get-buffer a) 585 (pdf-info-delannot 586 (pdf-annot-get-id a)) 587 (set-buffer-modified-p t) 588 (pdf-annot-run-modified-hooks :delete a)) 589 (when (called-interactively-p 'any) 590 (message "Annotation deleted")) 591 nil) 592 593 (defun pdf-annot-text-annotation-p (a) 594 "Return non-nil if annotation A is of type text." 595 (eq 'text (pdf-annot-get a 'type))) 596 597 (defun pdf-annot-markup-annotation-p (a) 598 "Return non-nil if annotation A is a known markup type. 599 600 Annotation types are defined in `pdf-annot-markup-annotation-types'." 601 (not (null 602 (memq (pdf-annot-get a 'type) 603 pdf-annot-markup-annotation-types)))) 604 605 (defun pdf-annot-property-modifiable-p (a property) 606 "Return non-nil if PROPERTY for annotation A is editable." 607 (or (memq property '(edges color flags contents)) 608 (and (pdf-annot-markup-annotation-p a) 609 (memq property '(label opacity popup popup-is-open))) 610 (and (pdf-annot-text-annotation-p a) 611 (memq property '(icon is-open))))) 612 613 (defun pdf-annot-activate-annotation (a) 614 "Run handler functions on A to activate the annotation. 615 616 Activation functions are defined in `pdf-annot-activate-handler-functions'." 617 (or (run-hook-with-args-until-success 618 'pdf-annot-activate-handler-functions 619 a) 620 (pdf-annot-default-activate-handler a))) 621 622 (defun pdf-annot-default-activate-handler (a) 623 "The default activation function to run on annotation A. 624 625 Activation functions are defined in `pdf-annot-activate-handler-functions'." 626 (cond 627 ((pdf-annot-has-attachment-p a) 628 (pdf-annot-pop-to-attachment a)) 629 (t (pdf-annot-edit-contents a)))) 630 631 632 ;; * ================================================================== * 633 ;; * Handling attachments 634 ;; * ================================================================== * 635 636 (defun pdf-annot-has-attachment-p (a) 637 "Return non-nil if annotation A's has data attached." 638 (eq 'file (pdf-annot-get a 'type))) 639 640 (defun pdf-annot-get-attachment (a &optional do-save) 641 "Retrieve annotation A's attachment. 642 643 The DO-SAVE argument is given to 644 `pdf-info-getattachment-from-annot', which see." 645 (unless (pdf-annot-has-attachment-p a) 646 (error "Annotation has no data attached: %s" a)) 647 (pdf-info-getattachment-from-annot 648 (pdf-annot-get-id a) 649 do-save 650 (pdf-annot-get-buffer a))) 651 652 (defun pdf-annot-attachment-base-directory () 653 "Return the base directory for saving attachments." 654 (let ((dir (pdf-util-expand-file-name "attachments"))) 655 (unless (file-exists-p dir) 656 (make-directory dir)) 657 dir)) 658 659 (defun pdf-annot-attachment-delete-base-directory () 660 "Delete all saved attachment files of the current buffer." 661 (setq pdf-annot--attachment-file-alist nil) 662 (delete-directory (pdf-annot-attachment-base-directory) t)) 663 664 (defun pdf-annot-attachment-unique-filename (attachment) 665 "Return a unique absolute filename for ATTACHMENT." 666 (let* ((filename (or (cdr (assq 'filename attachment)) 667 "attachment")) 668 (id (cdr (assq 'id attachment))) 669 (unique 670 (or (cdr (assoc id pdf-annot--attachment-file-alist)) 671 (let* ((sans-ext 672 (expand-file-name 673 (concat (file-name-as-directory ".") 674 (file-name-sans-extension filename)) 675 (pdf-annot-attachment-base-directory))) 676 (ext (file-name-extension filename)) 677 (newname (concat sans-ext "." ext)) 678 (i 0)) 679 (while (rassoc newname pdf-annot--attachment-file-alist) 680 (setq newname (format "%s-%d.%s" sans-ext (cl-incf i) ext))) 681 (push (cons id newname) pdf-annot--attachment-file-alist) 682 newname))) 683 (directory (file-name-directory unique))) 684 (unless (file-exists-p directory) 685 (make-directory directory t)) 686 unique)) 687 688 689 (defun pdf-annot-attachment-save (attachment &optional regenerate-p) 690 "Save ATTACHMENT's data to a unique filename and return its name. 691 692 If REGENERATE-P is non-nil, copy attachment's file even if the 693 copy already exists. 694 695 Signal an error, if ATTACHMENT has no, or a non-existing, `file' 696 property, i.e. it was retrieved with an unset do-save argument. 697 See `pdf-info-getattachments'" 698 699 (let ((datafile (cdr (assq 'file attachment)))) 700 (unless (and datafile 701 (file-exists-p datafile)) 702 (error "Attachment's file property is invalid")) 703 (let* ((filename 704 (pdf-annot-attachment-unique-filename attachment))) 705 (when (or regenerate-p 706 (not (file-exists-p filename))) 707 (copy-file datafile filename nil nil t t)) 708 filename))) 709 710 (defun pdf-annot-find-attachment-noselect (a) 711 "Find annotation A's attachment in a buffer, without selecting it. 712 713 Signals an error, if A has no data attached." 714 (let ((attachment (pdf-annot-get-attachment a t))) 715 (unwind-protect 716 (find-file-noselect 717 (pdf-annot-attachment-save attachment)) 718 (let ((tmpfile (cdr (assq 'file attachment)))) 719 (when (and tmpfile 720 (file-exists-p tmpfile)) 721 (delete-file tmpfile)))))) 722 723 (defun pdf-annot-attachment-dired (&optional regenerate-p) 724 "List all attachments in a Dired buffer. 725 726 If REGENERATE-P is non-nil, create attachment's files even if 727 they already exist. Interactively REGENERATE-P is non-nil if a 728 prefix argument was given. 729 730 Return the Dired buffer." 731 (interactive (list current-prefix-arg)) 732 (let ((attachments (pdf-info-getattachments t))) 733 (unwind-protect 734 (progn 735 (dolist (a (pdf-annot-getannots nil 'file)) 736 (push (pdf-annot-get-attachment a t) 737 attachments )) 738 (dolist (att attachments) 739 (pdf-annot-attachment-save att regenerate-p)) 740 (unless attachments 741 (error "Document has no data attached")) 742 (dired (pdf-annot-attachment-base-directory))) 743 (dolist (att attachments) 744 (let ((tmpfile (cdr (assq 'file att)))) 745 (when (and tmpfile (file-exists-p tmpfile)) 746 (delete-file tmpfile))))))) 747 748 (defun pdf-annot-display-attachment (a &optional display-action select-window-p) 749 "Display file annotation A's data in a buffer. 750 751 DISPLAY-ACTION should be a valid `display-buffer' action. If 752 nil, `pdf-annot-attachment-display-buffer-action' is used. 753 754 Select the window, if SELECT-WINDOW-P is non-nil. 755 756 Return the window attachment is displayed in." 757 758 (interactive 759 (list (pdf-annot-read-annotation 760 "Select a file annotation by clicking on it"))) 761 (let* ((buffer (pdf-annot-find-attachment-noselect a)) 762 (window (display-buffer 763 buffer (or display-action 764 pdf-annot-attachment-display-buffer-action)))) 765 (when select-window-p 766 (select-window window)) 767 window)) 768 769 (defun pdf-annot-pop-to-attachment (a) 770 "Display annotation A's attachment in a window and select it." 771 (interactive 772 (list (pdf-annot-read-annotation 773 "Select a file annotation by clicking on it"))) 774 (pdf-annot-display-attachment a nil t)) 775 776 777 ;; * ================================================================== * 778 ;; * Interfacing with the display 779 ;; * ================================================================== * 780 781 (defun pdf-annot-image-position (a &optional image-size) 782 "Return the position of annotation A in image coordinates. 783 784 IMAGE-SIZE should be a cons \(WIDTH . HEIGHT\) and defaults to 785 the page-image of the selected window." 786 787 (unless image-size 788 (pdf-util-assert-pdf-window) 789 (setq image-size (pdf-view-image-size))) 790 (let ((e (pdf-util-scale 791 (pdf-annot-get a 'edges) 792 image-size))) 793 (pdf-util-with-edges (e) 794 `(,e-left . ,e-top)))) 795 796 (defun pdf-annot-image-set-position (a x y &optional image-size) 797 "Set annotation A's position to X,Y in image coordinates. 798 799 See `pdf-annot-image-position' for IMAGE-SIZE." 800 801 (unless image-size 802 (pdf-util-assert-pdf-window) 803 (setq image-size (pdf-view-image-size))) 804 (let* ((edges (pdf-annot-get a 'edges)) 805 (x (/ x (float (car image-size)))) 806 (y (/ y (float (cdr image-size))))) 807 (pdf-util-with-edges (edges) 808 (let* ((w edges-width) 809 (h edges-height) 810 (x (max 0 (min x (- 1 w)))) 811 (y (max 0 (min y (- 1 h))))) 812 (pdf-annot-put a 'edges 813 (list x y -1 -1)))))) 814 815 (defun pdf-annot-image-size (a &optional image-size) 816 "Return the size of annotation A in image coordinates. 817 818 Returns \(WIDTH . HEIGHT\). 819 820 See `pdf-annot-image-position' for IMAGE-SIZE." 821 (unless image-size 822 (pdf-util-assert-pdf-window) 823 (setq image-size (pdf-view-image-size))) 824 (let ((edges (pdf-util-scale 825 (pdf-annot-get a 'edges) image-size))) 826 (pdf-util-with-edges (edges) 827 (cons edges-width edges-height)))) 828 829 (defun pdf-annot-image-set-size (a &optional width height image-size) 830 "Set annotation A's size in image to WIDTH and/or HEIGHT. 831 832 See `pdf-annot-image-position' for IMAGE-SIZE." 833 (unless image-size 834 (pdf-util-assert-pdf-window) 835 (setq image-size (pdf-view-image-size))) 836 (let* ((edges (pdf-annot-get a 'edges)) 837 (w (and width 838 (/ width (float (car image-size))))) 839 (h (and height 840 (/ height (float (cdr image-size)))))) 841 (pdf-util-with-edges (edges) 842 (pdf-annot-put a 'edges 843 (list edges-left 844 edges-top 845 (if w (+ edges-left w) edges-right) 846 (if h (+ edges-top h) edges-bot)))))) 847 848 (defun pdf-annot-at-position (pos) 849 "Return annotation at POS in the selected window. 850 851 POS should be an absolute image position as a cons \(X . Y\). 852 Alternatively POS may also be an event position, in which case 853 `posn-window' and `posn-object-x-y' is used to find the image 854 position. 855 856 Return nil, if no annotation was found." 857 (let (window) 858 (when (posnp pos) 859 (setq window (posn-window pos) 860 pos (posn-object-x-y pos))) 861 (save-selected-window 862 (when window (select-window window 'norecord)) 863 (let* ((annots (pdf-annot-getannots (pdf-view-current-page))) 864 (size (pdf-view-image-size)) 865 (rx (/ (car pos) (float (car size)))) 866 (ry (/ (cdr pos) (float (cdr size)))) 867 (rpos (cons rx ry))) 868 (or (cl-some (lambda (a) 869 (and (cl-some 870 (lambda (e) 871 (pdf-util-edges-inside-p e rpos)) 872 (pdf-annot-get-display-edges a)) 873 a)) 874 annots) 875 (error "No annotation at this position")))))) 876 877 (defun pdf-annot-mouse-move (event &optional annot) 878 "Start moving an annotation at EVENT's position. 879 880 EVENT should be a mouse event originating the request and is used 881 as a reference point. 882 883 ANNOT is the annotation to operate on and defaults to the 884 annotation at EVENT's start position. 885 886 This function does not return until the operation is completed, 887 i.e. a non mouse-movement event is read." 888 889 (interactive "@e") 890 (pdf-util-assert-pdf-window (posn-window (event-start event))) 891 (select-window (posn-window (event-start event))) 892 (let* ((mpos (posn-object-x-y (event-start event))) 893 (a (or annot 894 (pdf-annot-at-position mpos)))) 895 (unless a 896 (error "No annotation at this position: %s" mpos)) 897 (let* ((apos (pdf-annot-image-position a)) 898 (offset (cons (- (car mpos) (car apos)) 899 (- (cdr mpos) (cdr apos)))) 900 (window (selected-window)) 901 make-pointer-invisible) 902 (when (pdf-util-track-mouse-dragging (ev 0.1) 903 (when (and (eq window (posn-window (event-start ev))) 904 (eq 'image (car-safe (posn-object (event-start ev))))) 905 (let ((pdf-view-inhibit-hotspots t) 906 (pdf-annot-inhibit-modification-hooks t) 907 (pdf-cache-image-inihibit t) 908 (xy (posn-object-x-y (event-start ev)))) 909 (pdf-annot-image-set-position 910 a (- (car xy) (car offset)) 911 (- (cdr xy) (cdr offset))) 912 (pdf-view-redisplay)))) 913 (pdf-annot-run-modified-hooks))) 914 nil)) 915 916 (defun pdf-annot-hotspot-function (page size) 917 "Create image hotspots for page PAGE of size SIZE." 918 (apply #'nconc (mapcar (lambda (a) 919 (unless (eq (pdf-annot-get a 'type) 920 'link) 921 (pdf-annot-create-hotspots a size))) 922 (pdf-annot-getannots page)))) 923 924 (defun pdf-annot-create-hotspots (a size) 925 "Return a list of image hotspots for annotation A. 926 927 SIZE is a cons (SX . SY), by which edges are scaled." 928 (let ((id (pdf-annot-get-id a)) 929 (edges (pdf-util-scale 930 (pdf-annot-get-display-edges a) 931 size 'round)) 932 (moveable-p (memq (pdf-annot-get a 'type) 933 '(file text))) 934 hotspots) 935 (dolist (e edges) 936 (pdf-util-with-edges (e) 937 (push `((rect . ((,e-left . ,e-top) . (,e-right . ,e-bot))) 938 ,id 939 (pointer 940 hand 941 help-echo 942 ,(pdf-annot-print-annotation a))) 943 hotspots))) 944 (pdf-annot-create-hotspot-binding id moveable-p a) 945 hotspots)) 946 947 ;; FIXME: Define a keymap as a template for this. Much cleaner. 948 (defun pdf-annot-create-hotspot-binding (id moveable-p annotation) 949 "Create a local keymap for interacting with ANNOTATION using the mouse. 950 951 ID is the identifier for the ANNOTATION, as returned 952 `pdf-annot-get-id'. MOVEABLE-P indicates whether the annotation 953 is moveable." 954 ;; Activating 955 (local-set-key 956 (vector id 'mouse-1) 957 (lambda () 958 (interactive) 959 (pdf-annot-activate-annotation annotation))) 960 ;; Move 961 (when moveable-p 962 (local-set-key 963 (vector id 'down-mouse-1) 964 (lambda (ev) 965 (interactive "@e") 966 (pdf-annot-mouse-move ev annotation)))) 967 ;; Context Menu 968 (local-set-key 969 (vector id 'down-mouse-3) 970 (lambda () 971 (interactive "@") 972 (popup-menu (pdf-annot-create-context-menu annotation)))) 973 ;; Everything else 974 (local-set-key 975 (vector id t) 976 'pdf-util-image-map-mouse-event-proxy)) 977 978 (defun pdf-annot-show-annotation (a &optional highlight-p window) 979 "Make annotation A visible. 980 981 Turn to A's page in WINDOW, and scroll it if necessary. 982 983 If HIGHLIGHT-P is non-nil, visually distinguish annotation A from 984 other annotations." 985 986 (save-selected-window 987 (when window (select-window window 'norecord)) 988 (pdf-util-assert-pdf-window) 989 (let ((page (pdf-annot-get a 'page)) 990 (size (pdf-view-image-size))) 991 (unless (= page (pdf-view-current-page)) 992 (pdf-view-goto-page page)) 993 (let ((edges (pdf-annot-get-display-edges a))) 994 (when highlight-p 995 (pdf-view-display-image 996 (pdf-view-create-image 997 (pdf-cache-renderpage-highlight 998 page (car size) 999 `("white" "steel blue" 0.35 ,@edges)) 1000 :map (pdf-view-apply-hotspot-functions 1001 window page size) 1002 :width (car size)))) 1003 (pdf-util-scroll-to-edges 1004 (pdf-util-scale-relative-to-pixel (car edges))))))) 1005 1006 (defun pdf-annot-read-annotation (&optional prompt) 1007 "Let the user choose a annotation a mouse click using PROMPT." 1008 (pdf-annot-at-position 1009 (pdf-util-read-image-position 1010 (or prompt "Choose a annotation by clicking on it")))) 1011 1012 1013 ;; * ================================================================== * 1014 ;; * Creating annotations 1015 ;; * ================================================================== * 1016 1017 (defun pdf-annot-add-annotation (type edges &optional property-alist page) 1018 "Create and add a new annotation of type TYPE to the document. 1019 1020 TYPE determines the kind of annotation to add and maybe one of 1021 `text', `squiggly', `underline', `strike-out' or `highlight'. 1022 1023 EDGES determines where the annotation will appear on the page. 1024 If type is `text', this should be a single list of \(LEFT TOP 1025 RIGHT BOT\). Though, in this case only LEFT and TOP are used, 1026 since the size of text annotations is fixed. Otherwise EDGES may 1027 be a list of such elements. All values should be image relative 1028 coordinates, i.e. in the range \[0;1\]. 1029 1030 PROPERTY-ALIST is a list of annotation properties, which will be 1031 put on the created annotation. 1032 1033 PAGE determines the page of the annotation. It defaults to the 1034 page currently displayed in the selected window. 1035 1036 Signal an error, if PROPERTY-ALIST contains non-modifiable 1037 properties or PAGE is nil and the selected window does not 1038 display a PDF document or creating annotations of type TYPE is 1039 not supported. 1040 1041 Set buffers modified flag and calls 1042 `pdf-annot-activate-annotation' if 1043 `pdf-annot-activate-created-annotations' is non-nil. 1044 1045 Return the new annotation." 1046 1047 (unless (memq type (pdf-info-creatable-annotation-types)) 1048 (error "Unsupported annotation type: %s" type)) 1049 (unless page 1050 (pdf-util-assert-pdf-window) 1051 (setq page (pdf-view-current-page))) 1052 (unless (consp (car-safe edges)) 1053 (setq edges (list edges))) 1054 (when (and (eq type 'text) 1055 (> (length edges) 1)) 1056 (error "Edges argument should be a single edge-list for text annotations")) 1057 (let* ((selection-style pdf-view-selection-style) 1058 (non-markup (pcase type 1059 ('text t) 1060 ('highlight pdf-view--have-rectangle-region))) 1061 (a (apply #'pdf-info-addannot 1062 page 1063 (if non-markup 1064 (car edges) 1065 (apply #'pdf-util-edges-union 1066 (apply #'append 1067 (mapcar 1068 (lambda (e) 1069 (pdf-info-getselection page e selection-style)) 1070 edges)))) 1071 type 1072 selection-style 1073 nil 1074 (unless non-markup edges))) 1075 (id (pdf-annot-get-id a))) 1076 (when property-alist 1077 (condition-case err 1078 (setq a (pdf-info-editannot id property-alist)) 1079 (error 1080 (pdf-info-delannot id) 1081 (signal (car err) (cdr err))))) 1082 (setq a (pdf-annot-create a)) 1083 (set-buffer-modified-p t) 1084 (pdf-annot-run-modified-hooks :insert a) 1085 (when pdf-annot-activate-created-annotations 1086 (pdf-annot-activate-annotation a)) 1087 a)) 1088 1089 (defun pdf-annot-add-text-annotation (pos &optional icon property-alist) 1090 "Add a new text annotation at POS in the selected window. 1091 1092 POS should be a image position object or a cons \(X . Y\), both 1093 being image coordinates. 1094 1095 ICON determines how the annotation is displayed and should be 1096 listed in `pdf-annot-standard-text-icons'. Any other value is ok 1097 as well, but will render the annotation invisible. 1098 1099 Adjust X and Y accordingly, if the position would render the 1100 annotation off-page. 1101 1102 Merge ICON as a icon property with PROPERTY-ALIST and 1103 `pdf-annot-default-annotation-properties' and apply the 1104 result to the created annotation. 1105 1106 See also `pdf-annot-add-annotation'. 1107 1108 Return the new annotation." 1109 1110 (interactive 1111 (let* ((posn (pdf-util-read-image-position 1112 "Click where a new text annotation should be added ...")) 1113 (window (posn-window posn))) 1114 (select-window window) 1115 (list posn))) 1116 (pdf-util-assert-pdf-window) 1117 (when (posnp pos) 1118 (setq pos (posn-object-x-y pos))) 1119 (let ((isize (pdf-view-image-size)) 1120 (x (car pos)) 1121 (y (cdr pos))) 1122 (unless (and (>= x 0) 1123 (< x (car isize))) 1124 (signal 'args-out-of-range (list pos))) 1125 (unless (and (>= y 0) 1126 (< y (cdr isize))) 1127 (signal 'args-out-of-range (list pos))) 1128 (let ((size (pdf-util-scale-points-to-pixel 1129 pdf-annot-text-annotation-size 'round))) 1130 (setcar size (min (car size) (car isize))) 1131 (setcdr size (min (cdr size) (cdr isize))) 1132 (cl-decf x (max 0 (- (+ x (car size)) (car isize)))) 1133 (cl-decf y (max 0 (- (+ y (cdr size)) (cdr isize)))) 1134 (pdf-annot-add-annotation 1135 'text (pdf-util-scale-pixel-to-relative 1136 (list x y -1 -1)) 1137 (pdf-annot-merge-alists 1138 (and icon `((icon . ,icon))) 1139 property-alist 1140 (cdr (assq 'text pdf-annot-default-annotation-properties)) 1141 (cdr (assq t pdf-annot-default-annotation-properties)) 1142 `((color . ,(car pdf-annot-color-history)))))))) 1143 1144 (defun pdf-annot-mouse-add-text-annotation (ev) 1145 "Add a text annotation using the mouse. 1146 1147 EV describes the captured mouse event." 1148 (interactive "@e") 1149 (pdf-annot-add-text-annotation 1150 (if (eq (car-safe ev) 1151 'menu-bar) 1152 (let (echo-keystrokes) 1153 (message nil) 1154 (pdf-util-read-image-position 1155 "Click where a new text annotation should be added ...")) 1156 (event-start ev)))) 1157 1158 (defun pdf-annot-add-markup-annotation (list-of-edges type &optional color 1159 property-alist) 1160 "Add a new markup annotation in the selected window. 1161 1162 LIST-OF-EDGES determines the marked up area and should be a list 1163 of \(LEFT TOP RIGHT BOT\), each value a relative coordinate. 1164 1165 TYPE should be one of `squiggly', `underline', `strike-out' or 1166 `highlight'. 1167 1168 Merge COLOR as a color property with PROPERTY-ALIST and 1169 `pdf-annot-default-annotation-properties' and apply the 1170 result to the created annotation. 1171 1172 See also `pdf-annot-add-annotation'. 1173 1174 Return the new annotation." 1175 (interactive 1176 (list (pdf-view-active-region t) 1177 (let ((type (completing-read "Markup type (default highlight): " 1178 '("squiggly" "highlight" "underline" "strike-out") 1179 nil t))) 1180 (if (equal type "") 'highlight (intern type))) 1181 (pdf-annot-read-color))) 1182 (pdf-util-assert-pdf-window) 1183 (pdf-annot-add-annotation 1184 type 1185 list-of-edges 1186 (pdf-annot-merge-alists 1187 (and color `((color . ,color))) 1188 property-alist 1189 (cdr (assq type pdf-annot-default-annotation-properties)) 1190 (cdr (assq t pdf-annot-default-annotation-properties)) 1191 (when pdf-annot-color-history 1192 `((color . ,(car pdf-annot-color-history)))) 1193 '((color . "#ffff00"))) 1194 (pdf-view-current-page))) 1195 1196 (defun pdf-annot-add-squiggly-markup-annotation (list-of-edges 1197 &optional color property-alist) 1198 "Add a new squiggly annotation in the selected window. 1199 1200 LIST-OF-EDGES defines the annotation boundary. COLOR defines the 1201 annotation color and PROPERTY-ALIST defines additional annotation 1202 properties. See also `pdf-annot-add-markup-annotation'." 1203 (interactive (list (pdf-view-active-region t))) 1204 (pdf-annot-add-markup-annotation list-of-edges 'squiggly color property-alist)) 1205 1206 (defun pdf-annot-add-underline-markup-annotation (list-of-edges 1207 &optional color property-alist) 1208 "Add a new underline annotation in the selected window. 1209 1210 LIST-OF-EDGES defines the annotation boundary. COLOR defines the 1211 annotation color and PROPERTY-ALIST defines additional annotation 1212 properties. See also `pdf-annot-add-markup-annotation'." 1213 (interactive (list (pdf-view-active-region t))) 1214 (pdf-annot-add-markup-annotation list-of-edges 'underline color property-alist)) 1215 1216 (defun pdf-annot-add-strikeout-markup-annotation (list-of-edges 1217 &optional color property-alist) 1218 "Add a new strike-out annotation in the selected window. 1219 1220 LIST-OF-EDGES defines the annotation boundary. COLOR defines the 1221 annotation color and PROPERTY-ALIST defines additional annotation 1222 properties. See also `pdf-annot-add-markup-annotation'." 1223 (interactive (list (pdf-view-active-region t))) 1224 (pdf-annot-add-markup-annotation list-of-edges 'strike-out color property-alist)) 1225 1226 (defun pdf-annot-add-highlight-markup-annotation (list-of-edges 1227 &optional color property-alist) 1228 "Add a new highlight annotation in the selected window. 1229 1230 LIST-OF-EDGES defines the annotation boundary. COLOR defines the 1231 annotation color and PROPERTY-ALIST defines additional annotation 1232 properties. See also `pdf-annot-add-markup-annotation'." 1233 (interactive (list (pdf-view-active-region t))) 1234 (pdf-annot-add-markup-annotation list-of-edges 'highlight color property-alist)) 1235 1236 (defun pdf-annot-read-color (&optional prompt) 1237 "Read and return a color using PROMPT. 1238 1239 Offer `pdf-annot-color-history' as default values." 1240 (let* ((defaults (append 1241 (seq-map #'cdr 1242 (seq-filter (lambda (x) (eq 'color (car x))) 1243 (seq-mapcat #'cdr 1244 pdf-annot-default-annotation-properties))) 1245 pdf-annot-color-history)) 1246 (prompt 1247 (format "%s%s: " 1248 (or prompt "Color") 1249 (if defaults (format " (default %s)" (car defaults)) ""))) 1250 (current-completing-read-function completing-read-function) 1251 (completing-read-function 1252 (lambda (prompt collection &optional predicate require-match 1253 initial-input _hist _def inherit-input-method) 1254 (funcall current-completing-read-function 1255 prompt collection predicate require-match 1256 initial-input 'pdf-annot-color-history 1257 defaults 1258 inherit-input-method)))) 1259 (read-color prompt))) 1260 1261 (defun pdf-annot-merge-alists (&rest alists) 1262 "Merge ALISTS into a single one. 1263 1264 Suppresses successive duplicate entries of keys after the first 1265 occurrence in ALISTS." 1266 1267 (let (merged) 1268 (dolist (elt (apply #'append alists)) 1269 (unless (assq (car elt) merged) 1270 (push elt merged))) 1271 (nreverse merged))) 1272 1273 1274 1275 ;; * ================================================================== * 1276 ;; * Displaying annotation contents 1277 ;; * ================================================================== * 1278 1279 (defun pdf-annot-print-property (a property) 1280 "Pretty print annotation A's property PROPERTY." 1281 (let ((value (pdf-annot-get a property))) 1282 (cl-case property 1283 (color 1284 (propertize (or value "") 1285 'face (and value 1286 `(:background ,value)))) 1287 ((created modified) 1288 (let ((date value)) 1289 (if (null date) 1290 "No date" 1291 (current-time-string date)))) 1292 ;; print verbatim 1293 (subject 1294 (or value "No subject")) 1295 (opacity 1296 (let ((opacity (or value 1.0))) 1297 (format "%d%%" (round (* 100 opacity))))) 1298 (t (format "%s" (or value "")))))) 1299 1300 (defun pdf-annot-print-annotation (a) 1301 "Pretty print annotation A." 1302 (or (run-hook-with-args-until-success 1303 'pdf-annot-print-annotation-functions a) 1304 (pdf-annot-print-annotation-default a))) 1305 1306 (defun pdf-annot-print-annotation-default (a) 1307 "Default pretty printer for annotation A. 1308 1309 The result consists of a header (as printed with 1310 `pdf-annot-print-annotation-header') a newline and A's contents 1311 property." 1312 (concat 1313 (pdf-annot-print-annotation-header a) 1314 "\n" 1315 (pdf-annot-get a 'contents))) 1316 1317 (defun pdf-annot-print-annotation-header (a) 1318 "Emit a suitable header string for annotation A." 1319 (let ((header 1320 (cond 1321 ((eq 'file (pdf-annot-get a 'type)) 1322 (let ((att (pdf-annot-get-attachment a))) 1323 (format "File attachment `%s' of %s" 1324 (or (cdr (assq 'filename att)) "unnamed") 1325 (if (cdr (assq 'size att)) 1326 (format "size %s" (file-size-human-readable 1327 (cdr (assq 'size att)))) 1328 "unknown size")))) 1329 (t 1330 (format "%s" 1331 (mapconcat 1332 #'identity 1333 (mapcar 1334 (lambda (property) 1335 (pdf-annot-print-property 1336 a property)) 1337 `(subject 1338 label 1339 modified)) 1340 ";")))))) 1341 (setq header (propertize header 'face 'header-line 1342 'intangible t 'read-only t)) 1343 ;; This `trick' makes the face apply in a tooltip. 1344 (propertize header 'display header))) 1345 1346 (defun pdf-annot-print-annotation-latex-maybe (a) 1347 "Maybe print annotation A's content as a LaTeX fragment. 1348 1349 See `pdf-annot-latex-string-predicate'." 1350 (when (and (functionp pdf-annot-latex-string-predicate) 1351 (funcall pdf-annot-latex-string-predicate 1352 (pdf-annot-get a 'contents))) 1353 (pdf-annot-print-annotation-latex a))) 1354 1355 (defun pdf-annot-print-annotation-latex (a) 1356 "Print annotation A's content as a LaTeX fragment. 1357 1358 This compiles A's contents as a LaTeX fragment and puts the 1359 resulting image as a display property on the contents, prefixed 1360 by a header." 1361 1362 (let (tempfile) 1363 (unwind-protect 1364 (with-current-buffer (pdf-annot-get-buffer a) 1365 (let* ((page (pdf-annot-get a 'page)) 1366 (header (pdf-annot-print-annotation-header a)) 1367 (contents (pdf-annot-get a 'contents)) 1368 (hash (sxhash (format 1369 "pdf-annot-print-annotation-latex%s%s%s" 1370 page header contents))) 1371 (data (pdf-cache-lookup-image page 0 nil hash)) 1372 ;; pdf-tools can only work with png files, so this 1373 ;; binding ensures that pdf-tools can print the 1374 ;; latex-preview regardless of the user 1375 ;; configuration. 1376 (org-preview-latex-default-process 'dvipng) 1377 (org-format-latex-header pdf-annot-latex-header) 1378 (temporary-file-directory 1379 (pdf-util-expand-file-name "pdf-annot-print-annotation-latex"))) 1380 (unless (file-directory-p temporary-file-directory) 1381 (make-directory temporary-file-directory)) 1382 (unless data 1383 (setq tempfile (make-temp-file "pdf-annot" nil ".png")) 1384 ;; FIXME: Why is this with-temp-buffer needed (which it is) ? 1385 (with-temp-buffer 1386 (org-create-formula-image 1387 contents tempfile org-format-latex-options t)) 1388 (setq data (pdf-util-munch-file tempfile)) 1389 (if (and (> (length data) 3) 1390 (equal (substring data 1 4) 1391 "PNG")) 1392 (pdf-cache-put-image page 0 data hash) 1393 (setq data nil))) 1394 (concat 1395 header 1396 "\n" 1397 (if data 1398 (propertize 1399 contents 'display (pdf-view-create-image data)) 1400 (propertize 1401 contents 1402 'display 1403 (concat 1404 (propertize "Failed to compile latex fragment\n" 1405 'face 'error) 1406 contents)))))) 1407 (when (and tempfile 1408 (file-exists-p tempfile)) 1409 (delete-file tempfile))))) 1410 1411 1412 ;; * ================================================================== * 1413 ;; * Editing annotation contents 1414 ;; * ================================================================== * 1415 1416 (defvar-local pdf-annot-edit-contents--annotation nil) 1417 (put 'pdf-annot-edit-contents--annotation 'permanent-local t) 1418 (defvar-local pdf-annot-edit-contents--buffer nil) 1419 1420 (defcustom pdf-annot-edit-contents-setup-function 1421 (lambda (a) 1422 (let ((mode (if (funcall pdf-annot-latex-string-predicate 1423 (pdf-annot-get a 'contents)) 1424 'latex-mode 1425 'org-mode))) 1426 (unless (derived-mode-p mode) 1427 (funcall mode)))) 1428 "A function for setting up, e.g. the major-mode, of the edit buffer. 1429 1430 The function receives one argument, the annotation whose contents 1431 is about to be edited in this buffer. 1432 1433 The default value turns on `latex-mode' if 1434 `pdf-annot-latex-string-predicate' returns non-nil on the 1435 annotation's contents and otherwise `org-mode'." 1436 :type 'function) 1437 1438 (defcustom pdf-annot-edit-contents-display-buffer-action 1439 '((display-buffer-reuse-window 1440 display-buffer-split-below-and-attach) 1441 (inhibit-same-window . t) 1442 (window-height . 0.25)) 1443 "Display action when showing the edit buffer." 1444 :type display-buffer--action-custom-type) 1445 1446 (defvar pdf-annot-edit-contents-minor-mode-map 1447 (let ((kmap (make-sparse-keymap))) 1448 (set-keymap-parent kmap text-mode-map) 1449 (define-key kmap (kbd "C-c C-c") #'pdf-annot-edit-contents-commit) 1450 (define-key kmap (kbd "C-c C-q") #'pdf-annot-edit-contents-abort) 1451 (define-key kmap (kbd "C-c C-k") #'pdf-annot-edit-contents-abort) 1452 kmap)) 1453 1454 (define-minor-mode pdf-annot-edit-contents-minor-mode 1455 "Active when editing the contents of annotations." 1456 :group 'pdf-annot 1457 (when pdf-annot-edit-contents-minor-mode 1458 (setq-local header-line-format 1459 (substitute-command-keys "\ 1460 Press \\[pdf-annot-edit-contents-commit] to commit your changes, \\[pdf-annot-edit-contents-abort] to abandon them.")))) 1461 1462 (put 'pdf-annot-edit-contents-minor-mode 'permanent-local t) 1463 1464 (defun pdf-annot-edit-contents-finalize (do-save &optional do-kill) 1465 "Finalize edit-operations on an Annotation. 1466 1467 If DO-SAVE is t, save the changes to annotation content without 1468 asking. If DO-SAVE is `ask', check with the user if contents 1469 should be saved. 1470 1471 If DO-KILL is t, kill all windows displaying the annotation 1472 contents. Else just bury the buffers." 1473 (when (buffer-modified-p) 1474 (cond 1475 ((eq do-save 'ask) 1476 (save-window-excursion 1477 (display-buffer (current-buffer) nil (selected-frame)) 1478 (when (y-or-n-p "Save changes to this annotation ?") 1479 (pdf-annot-edit-contents-save-annotation)))) 1480 (do-save 1481 (pdf-annot-edit-contents-save-annotation))) 1482 (set-buffer-modified-p nil)) 1483 (dolist (win (get-buffer-window-list)) 1484 (quit-window do-kill win))) 1485 1486 (defun pdf-annot-edit-contents-save-annotation () 1487 "Internal function to save the contents of the annotation under editing." 1488 (when pdf-annot-edit-contents--annotation 1489 (pdf-annot-put pdf-annot-edit-contents--annotation 1490 'contents 1491 (buffer-substring-no-properties (point-min) (point-max))) 1492 (set-buffer-modified-p nil))) 1493 1494 (defun pdf-annot-edit-contents-commit () 1495 "Save the change made to the current annotation." 1496 (interactive) 1497 (pdf-annot-edit-contents-finalize t)) 1498 1499 (defun pdf-annot-edit-contents-abort () 1500 "Abort the change made to the current annotation." 1501 (interactive) 1502 (pdf-annot-edit-contents-finalize nil t)) 1503 1504 (defun pdf-annot-edit-contents-noselect (a) 1505 "Internal function to setup all prerequisites for editing annotation A. 1506 1507 At any given point of time, only one annotation can be in edit mode." 1508 (with-current-buffer (pdf-annot-get-buffer a) 1509 (when (and (buffer-live-p pdf-annot-edit-contents--buffer) 1510 (not (eq a pdf-annot-edit-contents--annotation))) 1511 (with-current-buffer pdf-annot-edit-contents--buffer 1512 (pdf-annot-edit-contents-finalize 'ask))) 1513 (unless (buffer-live-p pdf-annot-edit-contents--buffer) 1514 (setq pdf-annot-edit-contents--buffer 1515 (get-buffer-create 1516 (format "*Edit Annotation %s*" (buffer-name))))) 1517 (with-current-buffer pdf-annot-edit-contents--buffer 1518 (let ((inhibit-read-only t)) 1519 (erase-buffer) 1520 (save-excursion (insert (pdf-annot-get a 'contents))) 1521 (set-buffer-modified-p nil)) 1522 (setq pdf-annot-edit-contents--annotation a) 1523 (funcall pdf-annot-edit-contents-setup-function a) 1524 (pdf-annot-edit-contents-minor-mode 1) 1525 (current-buffer)))) 1526 1527 (defun pdf-annot-edit-contents (a) 1528 "Edit the contents of annotation A." 1529 (select-window 1530 (display-buffer 1531 (pdf-annot-edit-contents-noselect a) 1532 pdf-annot-edit-contents-display-buffer-action))) 1533 1534 (defun pdf-annot-edit-contents-mouse (ev) 1535 "Edit the contents of the annotation described by mouse event EV." 1536 (interactive "@e") 1537 (let* ((pos (posn-object-x-y (event-start ev))) 1538 (a (and pos (pdf-annot-at-position pos)))) 1539 (unless a 1540 (error "No annotation at this position")) 1541 (pdf-annot-edit-contents a))) 1542 1543 1544 1545 ;; * ================================================================== * 1546 ;; * Listing annotations 1547 ;; * ================================================================== * 1548 1549 (defcustom pdf-annot-list-display-buffer-action 1550 '((display-buffer-reuse-window 1551 display-buffer-pop-up-window) 1552 (inhibit-same-window . t)) 1553 "Display action used when displaying the list buffer." 1554 :type display-buffer--action-custom-type) 1555 1556 (defcustom pdf-annot-list-format 1557 '((page . 3) 1558 (type . 10) 1559 (label . 24) 1560 (date . 24)) 1561 "Annotation properties visible in the annotation list. 1562 1563 It should be a list of \(PROPERTIZE. WIDTH\), where PROPERTY is a 1564 symbol naming one of supported properties to list and WIDTH its 1565 desired column-width. 1566 1567 Currently supported properties are page, type, label, date and contents." 1568 :type '(alist :key-type (symbol)) 1569 :options '((page (integer :value 3 :tag "Column Width")) 1570 (type (integer :value 10 :tag "Column Width" )) 1571 (label (integer :value 24 :tag "Column Width")) 1572 (date (integer :value 24 :tag "Column Width")) 1573 (contents (integer :value 56 :tag "Column Width")))) 1574 1575 (defcustom pdf-annot-list-highlight-type t 1576 "Whether to highlight \"Type\" column annotation list with annotation color." 1577 :type 'boolean) 1578 1579 (defvar-local pdf-annot-list-buffer nil) 1580 1581 (defvar-local pdf-annot-list-document-buffer nil) 1582 1583 (defvar pdf-annot-list-mode-map 1584 (let ((km (make-sparse-keymap))) 1585 (define-key km (kbd "C-c C-f") #'pdf-annot-list-follow-minor-mode) 1586 (define-key km (kbd "SPC") #'pdf-annot-list-display-annotation-from-id) 1587 km)) 1588 1589 (defun pdf-annot-property-completions (property) 1590 "Return a list of completion candidates for annotation property PROPERTY. 1591 1592 Return nil, if not available." 1593 (cl-case property 1594 (color (pdf-util-color-completions)) 1595 (icon (copy-sequence pdf-annot-standard-text-icons)))) 1596 1597 (defun pdf-annot-compare-annotations (a1 a2) 1598 "Compare annotations A1 and A2. 1599 1600 Return non-nil if A1's page is less than A2's one or if they 1601 belong to the same page and A1 is displayed above/left of A2." 1602 (let ((p1 (pdf-annot-get a1 'page)) 1603 (p2 (pdf-annot-get a2 'page))) 1604 (or (< p1 p2) 1605 (and (= p1 p2) 1606 (let ((e1 (pdf-util-scale 1607 (car (pdf-annot-get-display-edges a1)) 1608 '(1000 . 1000))) 1609 (e2 (pdf-util-scale 1610 (car (pdf-annot-get-display-edges a2)) 1611 '(1000 . 1000)))) 1612 (pdf-util-with-edges (e1 e2) 1613 (or (< e1-top e2-top) 1614 (and (= e1-top e2-top) 1615 (<= e1-left e2-left))))))))) 1616 1617 (defun pdf-annot-list-entries () 1618 "Return all the annotations of this PDF buffer as a `tabulated-list'." 1619 (unless (buffer-live-p pdf-annot-list-document-buffer) 1620 (error "No PDF document associated with this buffer")) 1621 (mapcar #'pdf-annot-list-create-entry 1622 (sort (pdf-annot-getannots nil pdf-annot-list-listed-types 1623 pdf-annot-list-document-buffer) 1624 #'pdf-annot-compare-annotations))) 1625 1626 (defun pdf-annot--make-entry-formatter (a) 1627 "Return a formatter function for annotation A. 1628 1629 A formatter function takes a format cons-cell and returns 1630 pretty-printed output." 1631 (lambda (fmt) 1632 (let ((entry-type (car fmt)) 1633 (entry-width (cdr fmt)) 1634 ;; Taken from css-mode.el 1635 (contrasty-color 1636 (lambda (name) 1637 (if (> (color-distance name "black") 292485) 1638 "black" "white"))) 1639 (prune-newlines 1640 (lambda (str) 1641 (replace-regexp-in-string "\n" " " str t t)))) 1642 (cl-ecase entry-type 1643 (date (propertize (pdf-annot-print-property a 'modified) 1644 'date 1645 (pdf-annot-get a 'modified))) 1646 (page (pdf-annot-print-property a 'page)) 1647 (label (funcall prune-newlines 1648 (pdf-annot-print-property a 'label))) 1649 (contents 1650 (truncate-string-to-width 1651 (funcall prune-newlines 1652 (pdf-annot-print-property a 'contents)) 1653 entry-width)) 1654 (type 1655 (let ((color (pdf-annot-get a 'color)) 1656 (type (pdf-annot-print-property a 'type))) 1657 (if (and pdf-annot-list-highlight-type color) 1658 (propertize 1659 type 'face 1660 `(:background ,color 1661 :foreground ,(funcall contrasty-color color))) 1662 type))))))) 1663 1664 (defun pdf-annot-list-create-entry (a) 1665 "Create a `tabulated-list-entries' entry for annotation A." 1666 (list (pdf-annot-get-id a) 1667 (vconcat 1668 (mapcar (pdf-annot--make-entry-formatter a) 1669 pdf-annot-list-format)))) 1670 1671 (define-derived-mode pdf-annot-list-mode tablist-mode "Annots" 1672 ;; @TODO: Remove the hard-coded index values here, and figure out a 1673 ;; way to properly link this to the index values of 1674 ;; `pdf-annot-list-format'. 1675 1676 ;; @TODO: Add tests for annotation formatting and display 1677 (let* ((page-sorter 1678 (lambda (a b) 1679 (< (string-to-number (aref (cadr a) 0)) 1680 (string-to-number (aref (cadr b) 0))))) 1681 (date-sorter 1682 (lambda (a b) 1683 (time-less-p (get-text-property 0 'date (aref (cadr a) 3)) 1684 (get-text-property 0 'date (aref (cadr b) 3))))) 1685 (format-generator 1686 (lambda (format) 1687 (let ((field (car format)) 1688 (width (cdr format))) 1689 (cl-case field 1690 (page `("Pg." 1691 ,width 1692 ,page-sorter 1693 :read-only t 1694 :right-align t)) 1695 (date `("Date" 1696 ,width 1697 ,date-sorter 1698 :read-only t)) 1699 (t (list 1700 (capitalize (symbol-name field)) 1701 width 1702 t 1703 :read-only t))))))) 1704 (setq tabulated-list-entries 'pdf-annot-list-entries 1705 tabulated-list-format (vconcat 1706 (mapcar 1707 format-generator 1708 pdf-annot-list-format)) 1709 tabulated-list-padding 2)) 1710 (set-keymap-parent pdf-annot-list-mode-map tablist-mode-map) 1711 (use-local-map pdf-annot-list-mode-map) 1712 (when (assq 'type pdf-annot-list-format) 1713 (setq tablist-current-filter 1714 `(not (== "Type" "link")))) 1715 (tabulated-list-init-header)) 1716 1717 (defun pdf-annot-list-annotations () 1718 "List annotations in a Dired like buffer. 1719 1720 \\{pdf-annot-list-mode-map}" 1721 (interactive) 1722 (pdf-util-assert-pdf-buffer) 1723 (let* ((buffer (current-buffer)) 1724 (name (format "*%s's annots*" 1725 (file-name-sans-extension 1726 (buffer-name)))) 1727 (annots-existed (and (get-buffer name) 1728 pdf-annot-list-buffer))) 1729 (with-current-buffer (get-buffer-create name) 1730 (delay-mode-hooks 1731 (unless (derived-mode-p 'pdf-annot-list-mode) 1732 (pdf-annot-list-mode)) 1733 (setq pdf-annot-list-document-buffer buffer) 1734 (unless annots-existed 1735 (tabulated-list-print)) 1736 (setq tablist-context-window-function 1737 (lambda (id) (pdf-annot-list-context-function id buffer)) 1738 tablist-operations-function #'pdf-annot-list-operation-function) 1739 (let ((list-buffer (current-buffer))) 1740 (with-current-buffer buffer 1741 (setq pdf-annot-list-buffer list-buffer)))) 1742 (run-mode-hooks) 1743 (pop-to-buffer 1744 (current-buffer) 1745 pdf-annot-list-display-buffer-action) 1746 (tablist-move-to-major-column) 1747 (tablist-display-context-window)) 1748 (add-hook 'pdf-info-close-document-hook 1749 #'pdf-annot-list-update nil t) 1750 (add-hook 'pdf-annot-modified-functions 1751 #'pdf-annot-list-update nil t))) 1752 1753 (defun pdf-annot-list-goto-annotation (a) 1754 "List all the annotations in the current buffer. 1755 1756 Goto the annotation A in the list." 1757 (with-current-buffer (pdf-annot-get-buffer a) 1758 (unless (and (buffer-live-p pdf-annot-list-buffer) 1759 (get-buffer-window pdf-annot-list-buffer)) 1760 (pdf-annot-list-annotations)) 1761 (with-selected-window (get-buffer-window pdf-annot-list-buffer) 1762 (goto-char (point-min)) 1763 (let ((id (pdf-annot-get-id a))) 1764 (while (and (not (eobp)) 1765 (not (eq id (tabulated-list-get-id)))) 1766 (forward-line)) 1767 (unless (eq id (tabulated-list-get-id)) 1768 (error "Unable to find annotation")) 1769 (when (invisible-p (point)) 1770 (tablist-suspend-filter t)) 1771 (tablist-move-to-major-column))))) 1772 1773 1774 (defun pdf-annot-list-update (&optional _fn) 1775 "Update the list of annotations on any change. 1776 1777 This is an internal function which runs as a hook in various situations." 1778 (when (buffer-live-p pdf-annot-list-buffer) 1779 (with-current-buffer pdf-annot-list-buffer 1780 (unless tablist-edit-column-minor-mode 1781 (tablist-revert)) 1782 (tablist-context-window-update)))) 1783 1784 (defun pdf-annot-list-context-function (id buffer) 1785 "Show the contents of an Annotation. 1786 1787 For an annotation identified by ID, belonging to PDF in BUFFER, 1788 get the contents and display them on demand." 1789 (with-current-buffer (get-buffer-create "*Contents*") 1790 (set-window-buffer nil (current-buffer)) 1791 (let ((inhibit-read-only t)) 1792 (erase-buffer) 1793 (when id 1794 (save-excursion 1795 (insert 1796 (pdf-annot-print-annotation 1797 (pdf-annot-getannot id buffer))))) 1798 (read-only-mode 1)))) 1799 1800 (defun pdf-annot-list-operation-function (op &rest args) 1801 "Define bulk operations in Annotation list buffer. 1802 1803 OP is the operation that the user wants to execute. Supported 1804 operations are `delete' and `find-entry'. 1805 1806 ARGS contain the annotation-ids to operate on." 1807 (cl-ecase op 1808 (supported-operations '(delete find-entry)) 1809 (delete 1810 (cl-destructuring-bind (ids) 1811 args 1812 (when (buffer-live-p pdf-annot-list-document-buffer) 1813 (with-current-buffer pdf-annot-list-document-buffer 1814 (pdf-annot-with-atomic-modifications 1815 (dolist (a (mapcar #'pdf-annot-getannot ids)) 1816 (pdf-annot-delete a))))))) 1817 (find-entry 1818 (cl-destructuring-bind (id) 1819 args 1820 (unless (buffer-live-p pdf-annot-list-document-buffer) 1821 (error "No PDF document associated with this buffer")) 1822 (let* ((buffer pdf-annot-list-document-buffer) 1823 (a (pdf-annot-getannot id buffer)) 1824 (pdf-window (save-selected-window 1825 (or (get-buffer-window buffer) 1826 (display-buffer buffer)))) 1827 window) 1828 (with-current-buffer buffer 1829 (pdf-annot-activate-annotation a) 1830 (setq window (selected-window))) 1831 ;; Make it so that quitting the edit window returns to the 1832 ;; list window. 1833 (unless (memq window (list (selected-window) pdf-window)) 1834 (let* ((quit-restore 1835 (window-parameter window 'quit-restore))) 1836 (when quit-restore 1837 (setcar (nthcdr 2 quit-restore) (selected-window)))))))))) 1838 1839 (defvar pdf-annot-list-display-annotation--timer nil) 1840 1841 (defun pdf-annot-list-display-annotation-from-id (id) 1842 "Display the Annotation ID in the PDF file. 1843 1844 This allows us to follow the tabulated-list of annotations and 1845 have the PDF buffer automatically move along with us." 1846 (interactive (list (tabulated-list-get-id))) 1847 (when id 1848 (unless (buffer-live-p pdf-annot-list-document-buffer) 1849 (error "PDF buffer was killed")) 1850 (when (timerp pdf-annot-list-display-annotation--timer) 1851 (cancel-timer pdf-annot-list-display-annotation--timer)) 1852 (setq pdf-annot-list-display-annotation--timer 1853 (run-with-idle-timer 0.1 nil 1854 (lambda (buffer a) 1855 (when (buffer-live-p buffer) 1856 (with-selected-window 1857 (or (get-buffer-window buffer) 1858 (display-buffer 1859 buffer 1860 '(nil (inhibit-same-window . t)))) 1861 (pdf-annot-show-annotation a t)))) 1862 pdf-annot-list-document-buffer 1863 (pdf-annot-getannot id pdf-annot-list-document-buffer))))) 1864 1865 (define-minor-mode pdf-annot-list-follow-minor-mode 1866 "Make the PDF follow the annotations in the list buffer." 1867 :group 'pdf-annot 1868 (unless (derived-mode-p 'pdf-annot-list-mode) 1869 (error "Not in pdf-annot-list-mode")) 1870 (cond 1871 (pdf-annot-list-follow-minor-mode 1872 (add-hook 'tablist-selection-changed-functions 1873 #'pdf-annot-list-display-annotation-from-id nil t) 1874 (let ((id (tabulated-list-get-id))) 1875 (when id 1876 (pdf-annot-list-display-annotation-from-id id)))) 1877 (t 1878 (remove-hook 'tablist-selection-changed-functions 1879 #'pdf-annot-list-display-annotation-from-id t)))) 1880 1881 (provide 'pdf-annot) 1882 ;;; pdf-annot.el ends here