pdf-view.el (64333B)
1 ;;; pdf-view.el --- View PDF documents. -*- lexical-binding:t -*- 2 3 ;; Copyright (C) 2013 Andreas Politz 4 5 ;; Author: Andreas Politz <politza@fh-trier.de> 6 ;; Keywords: files, doc-view, pdf 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 ;; Functions related to viewing PDF documents. 24 25 ;;; Code: 26 27 (require 'image-mode) 28 (require 'pdf-macs) 29 (require 'pdf-util) 30 (require 'pdf-info) 31 (require 'pdf-cache) 32 (require 'jka-compr) 33 (require 'bookmark) 34 (require 'password-cache) 35 36 (declare-function cua-copy-region "cua-base") 37 38 39 ;; * ================================================================== * 40 ;; * Customizations 41 ;; * ================================================================== * 42 43 (defgroup pdf-view nil 44 "View PDF documents." 45 :group 'pdf-tools) 46 47 (defcustom pdf-view-display-size 'fit-width 48 "The desired size of displayed pages. 49 50 This may be one of `fit-height', `fit-width', `fit-page' or a 51 number as a scale factor applied to the document's size. Any 52 other value behaves like `fit-width'." 53 :group 'pdf-view 54 :type '(choice number 55 (const fit-height) 56 (const fit-width) 57 (const fit-page))) 58 59 (make-variable-buffer-local 'pdf-view-display-size) 60 61 (defcustom pdf-view-resize-factor 1.25 62 "Fractional amount of resizing of one resize command." 63 :group 'pdf-view 64 :type 'number) 65 66 (defcustom pdf-view-continuous t 67 "In Continuous mode reaching the page edge advances to next/previous page. 68 69 When non-nil, scrolling a line upward at the bottom edge of the page 70 moves to the next page, and scrolling a line downward at the top edge 71 of the page moves to the previous page." 72 :type 'boolean 73 :group 'pdf-view) 74 75 (defcustom pdf-view-bounding-box-margin 0.05 76 "Fractional margin used for slicing with the bounding-box." 77 :group 'pdf-view 78 :type 'number) 79 80 (defcustom pdf-view-use-imagemagick nil 81 "Whether imagemagick should be used for rendering. 82 83 This variable has no effect, if imagemagick was not compiled into 84 Emacs or if imagemagick is the only way to display PNG images. 85 FIXME: Explain dis-/advantages of imagemagick and png." 86 :group 'pdf-view 87 :type 'boolean) 88 89 (defcustom pdf-view-use-scaling nil 90 "Whether images should be allowed to be scaled for rendering. 91 92 This variable affects both the reuse of higher-resolution images 93 as lower-resolution ones by down-scaling the image. As well as 94 the rendering of higher-resolution for high-resolution displays, 95 if available." 96 :group 'pdf-view 97 :type 'boolean) 98 99 (defface pdf-view-region 100 '((((background dark)) (:inherit region)) 101 (((background light)) (:inherit region))) 102 "Face used to determine the colors of the region." 103 :group 'pdf-view 104 :group 'pdf-tools-faces) 105 106 (defface pdf-view-rectangle 107 '((((background dark)) (:inherit highlight)) 108 (((background light)) (:inherit highlight))) 109 "Face used to determine the colors of the highlighted rectangle." 110 :group 'pdf-view 111 :group 'pdf-tools-faces) 112 113 (defcustom pdf-view-midnight-colors '("#839496" . "#002b36" ) 114 "Colors used when command `pdf-view-midnight-minor-mode' is activated. 115 116 This should be a cons \(FOREGROUND . BACKGROUND\) of colors." 117 :group 'pdf-view 118 :type '(cons (color :tag "Foreground") 119 (color :tag "Background"))) 120 121 (defcustom pdf-view-change-page-hook nil 122 "Hook run after changing to another page, but before displaying it. 123 124 See also `pdf-view-before-change-page-hook' and 125 `pdf-view-after-change-page-hook'." 126 :group 'pdf-view 127 :type 'hook) 128 129 (defcustom pdf-view-before-change-page-hook nil 130 "Hook run before changing to another page. 131 132 See also `pdf-view-change-page-hook' and 133 `pdf-view-after-change-page-hook'." 134 :group 'pdf-view 135 :type 'hook) 136 137 (defcustom pdf-view-after-change-page-hook nil 138 "Hook run after changing to and displaying another page. 139 140 See also `pdf-view-change-page-hook' and 141 `pdf-view-before-change-page-hook'." 142 :group 'pdf-view 143 :type 'hook) 144 145 (defcustom pdf-view-use-dedicated-register t 146 "Whether to use dedicated register for PDF positions. 147 148 If this is non-nil, the commands `pdf-view-position-to-register' 149 and `pdf-view-jump-to-register' use the buffer-local variable 150 `pdf-view-register-alist' to store resp. retrieve marked 151 positions. Otherwise the common variable `register-alist' is 152 used." 153 :group 'pdf-view 154 :type 'boolean) 155 156 (defcustom pdf-view-image-relief 0 157 "Add a shadow rectangle around the page's image. 158 159 See :relief property in Info node `(elisp) Image Descriptors'." 160 :group 'pdf-view 161 :type '(integer :tag "Pixel") 162 :link '(info-link "(elisp) Image Descriptors")) 163 164 (defcustom pdf-view-max-image-width 4800 165 "Maximum width of any image displayed in pixel." 166 :group 'pdf-view 167 :type '(integer :tag "Pixel")) 168 169 (defcustom pdf-view-use-unicode-ligther t 170 "Decide whether to use unicode symbols in the mode-line. 171 172 On some systems finding a font which supports those symbols can 173 take some time. If you don't want to spend that time waiting and 174 don't care for a nicer looking mode-line, set this variable to 175 nil. 176 177 Note, that this option has only an effect when this library is 178 loaded." 179 :group 'pdf-view 180 :type 'boolean) 181 182 (defcustom pdf-view-incompatible-modes 183 '(linum-mode linum-relative-mode helm-linum-relative-mode 184 nlinum-mode nlinum-hl-mode nlinum-relative-mode yalinum-mode) 185 "A list of modes incompatible with `pdf-view-mode'. 186 187 Issue a warning, if one of them is active in a PDF buffer." 188 :group 'pdf-view 189 :type '(repeat symbol)) 190 191 192 ;; * ================================================================== * 193 ;; * Internal variables and macros 194 ;; * ================================================================== * 195 196 (defvar-local pdf-view-active-region nil 197 "The active region as a list of edges. 198 199 Edge values are relative coordinates.") 200 201 (defvar-local pdf-view--have-rectangle-region nil 202 "Non-nil if the region is currently rendered as a rectangle. 203 204 This variable is set in `pdf-view-mouse-set-region' and used in 205 `pdf-view-mouse-extend-region' to determine the right choice 206 regarding display of the region in the later function.") 207 208 (defvar-local pdf-view--buffer-file-name nil 209 "Local copy of remote file or nil.") 210 211 (defvar-local pdf-view--server-file-name nil 212 "The servers notion of this buffer's filename.") 213 214 (defvar-local pdf-view--next-page-timer nil 215 "Timer used in `pdf-view-next-page-command'.") 216 217 (defvar-local pdf-view--hotspot-functions nil 218 "Alist of hotspot functions.") 219 220 (defvar-local pdf-view-register-alist nil 221 "Local, dedicated register for PDF positions.") 222 223 (defun pdf-view-current-pagelabel (&optional window) 224 (nth (1- (pdf-view-current-page window)) (pdf-info-pagelabels))) 225 226 (defun pdf-view-active-region-p nil 227 "Return t if there are active regions." 228 (not (null pdf-view-active-region))) 229 230 (defmacro pdf-view-assert-active-region () 231 "Signal an error if there are no active regions." 232 `(unless (pdf-view-active-region-p) 233 (error "The region is not active"))) 234 235 (defconst pdf-view-have-image-mode-pixel-vscroll 236 (>= emacs-major-version 27) 237 "Whether `image-mode' scrolls vertically by pixels.") 238 239 240 ;; * ================================================================== * 241 ;; * Major Mode 242 ;; * ================================================================== * 243 244 (defvar pdf-view-mode-map 245 (let ((map (make-sparse-keymap))) 246 (set-keymap-parent map image-mode-map) 247 (define-key map (kbd "Q") 'kill-this-buffer) 248 ;; Navigation in the document 249 (define-key map (kbd "n") 'pdf-view-next-page-command) 250 (define-key map (kbd "p") 'pdf-view-previous-page-command) 251 (define-key map (kbd "<next>") 'forward-page) 252 (define-key map (kbd "<prior>") 'backward-page) 253 (define-key map [remap forward-page] 'pdf-view-next-page-command) 254 (define-key map [remap backward-page] 'pdf-view-previous-page-command) 255 (define-key map (kbd "SPC") 'pdf-view-scroll-up-or-next-page) 256 (define-key map (kbd "S-SPC") 'pdf-view-scroll-down-or-previous-page) 257 (define-key map (kbd "DEL") 'pdf-view-scroll-down-or-previous-page) 258 (define-key map (kbd "C-n") 'pdf-view-next-line-or-next-page) 259 (define-key map (kbd "<down>") 'pdf-view-next-line-or-next-page) 260 (define-key map [remap next-line] 'pdf-view-next-line-or-next-page) 261 (define-key map (kbd "C-p") 'pdf-view-previous-line-or-previous-page) 262 (define-key map (kbd "<up>") 'pdf-view-previous-line-or-previous-page) 263 (define-key map [remap previous-line] 'pdf-view-previous-line-or-previous-page) 264 (define-key map (kbd "M-<") 'pdf-view-first-page) 265 (define-key map [remap beginning-of-buffer] 'pdf-view-first-page) 266 (define-key map (kbd "M->") 'pdf-view-last-page) 267 (define-key map [remap end-of-buffer] 'pdf-view-last-page) 268 (define-key map [remap goto-line] 'pdf-view-goto-page) 269 (define-key map (kbd "M-g l") 'pdf-view-goto-label) 270 (define-key map (kbd "RET") 'image-next-line) 271 ;; Zoom in/out. 272 (define-key map "+" 'pdf-view-enlarge) 273 (define-key map "=" 'pdf-view-enlarge) 274 (define-key map "-" 'pdf-view-shrink) 275 (define-key map "0" 'pdf-view-scale-reset) 276 ;; Fit the image to the window 277 (define-key map "W" 'pdf-view-fit-width-to-window) 278 (define-key map "H" 'pdf-view-fit-height-to-window) 279 (define-key map "P" 'pdf-view-fit-page-to-window) 280 ;; Slicing the image 281 (define-key map (kbd "s m") 'pdf-view-set-slice-using-mouse) 282 (define-key map (kbd "s b") 'pdf-view-set-slice-from-bounding-box) 283 (define-key map (kbd "s r") 'pdf-view-reset-slice) 284 ;; Reconvert 285 (define-key map (kbd "C-c C-c") 'doc-view-mode) 286 (define-key map (kbd "g") 'revert-buffer) 287 (define-key map (kbd "r") 'revert-buffer) 288 ;; Region 289 (define-key map [down-mouse-1] 'pdf-view-mouse-set-region) 290 (define-key map [M-down-mouse-1] 'pdf-view-mouse-set-region-rectangle) 291 (define-key map [C-down-mouse-1] 'pdf-view-mouse-extend-region) 292 (define-key map [remap kill-region] 'pdf-view-kill-ring-save) 293 (define-key map [remap kill-ring-save] 'pdf-view-kill-ring-save) 294 (define-key map [remap mark-whole-buffer] 'pdf-view-mark-whole-page) 295 ;; Other 296 (define-key map (kbd "C-c C-d") 'pdf-view-dark-minor-mode) 297 (define-key map (kbd "m") 'pdf-view-position-to-register) 298 (define-key map (kbd "'") 'pdf-view-jump-to-register) 299 300 (define-key map (kbd "C-c C-i") 'pdf-view-extract-region-image) 301 ;; Rendering 302 (define-key map (kbd "C-c C-r m") 'pdf-view-midnight-minor-mode) 303 (define-key map (kbd "C-c C-r t") 'pdf-view-themed-minor-mode) 304 (define-key map (kbd "C-c C-r p") 'pdf-view-printer-minor-mode) 305 map) 306 "Keymap used by `pdf-view-mode' when displaying a doc as a set of images.") 307 308 (define-derived-mode pdf-view-mode special-mode "PDFView" 309 "Major mode in PDF buffers. 310 311 PDFView Mode is an Emacs PDF viewer. It displays PDF files as 312 PNG images in Emacs buffers." 313 :group 'pdf-view 314 :abbrev-table nil 315 :syntax-table nil 316 ;; Setup a local copy for remote files. 317 (when (and (or jka-compr-really-do-compress 318 (let ((file-name-handler-alist nil)) 319 (not (and buffer-file-name 320 (file-readable-p buffer-file-name))))) 321 (pdf-tools-pdf-buffer-p)) 322 (let ((tempfile (pdf-util-make-temp-file))) 323 (write-region nil nil tempfile nil 'no-message) 324 (setq-local pdf-view--buffer-file-name tempfile))) 325 ;; Decryption needs to be done before any other function calls into 326 ;; pdf-info.el (e.g. from the mode-line during redisplay during 327 ;; waiting for process output). 328 (pdf-view-decrypt-document) 329 330 ;; Setup scroll functions 331 (if (boundp 'mwheel-scroll-up-function) ; not --without-x build 332 (setq-local mwheel-scroll-up-function 333 #'pdf-view-scroll-up-or-next-page)) 334 (if (boundp 'mwheel-scroll-down-function) 335 (setq-local mwheel-scroll-down-function 336 #'pdf-view-scroll-down-or-previous-page)) 337 338 ;; Clearing overlays 339 (add-hook 'change-major-mode-hook 340 (lambda () 341 (remove-overlays (point-min) (point-max) 'pdf-view t)) 342 nil t) 343 (remove-overlays (point-min) (point-max) 'pdf-view t) ;Just in case. 344 345 ;; Setup other local variables. 346 (setq-local mode-line-position 347 '(" P" (:eval (number-to-string (pdf-view-current-page))) 348 ;; Avoid errors during redisplay. 349 "/" (:eval (or (ignore-errors 350 (number-to-string (pdf-cache-number-of-pages))) 351 "???")))) 352 (setq-local auto-hscroll-mode nil) 353 (setq-local pdf-view--server-file-name (pdf-view-buffer-file-name)) 354 ;; High values of scroll-conservatively seem to trigger 355 ;; some display bug in xdisp.c:try_scrolling . 356 (setq-local scroll-conservatively 0) 357 (setq-local cursor-type nil) 358 (setq-local buffer-read-only t) 359 (setq-local view-read-only nil) 360 (setq-local bookmark-make-record-function 361 'pdf-view-bookmark-make-record) 362 (setq-local revert-buffer-function #'pdf-view-revert-buffer) 363 ;; No auto-save at the moment. 364 (setq-local buffer-auto-save-file-name nil) 365 ;; Disable image rescaling. 366 (when (boundp 'image-scaling-factor) 367 (setq-local image-scaling-factor 1)) 368 ;; No undo at the moment. 369 (unless buffer-undo-list 370 (buffer-disable-undo)) 371 ;; Enable transient-mark-mode, so region deactivation when quitting 372 ;; will work. 373 (setq-local transient-mark-mode t) 374 ;; In Emacs >= 24.4, `cua-copy-region' should have been advised when 375 ;; loading pdf-view.el so as to make it work with 376 ;; pdf-view-mode. Disable cua-mode if that is not the case. 377 ;; FIXME: cua-mode is a global minor-mode, but setting cua-mode to 378 ;; nil seems to do the trick. 379 (when (and (bound-and-true-p cua-mode) 380 (version< emacs-version "24.4")) 381 (setq-local cua-mode nil)) 382 383 (add-hook 'window-configuration-change-hook 384 'pdf-view-redisplay-some-windows nil t) 385 (add-hook 'deactivate-mark-hook 'pdf-view-deactivate-region nil t) 386 (add-hook 'write-contents-functions 387 'pdf-view--write-contents-function nil t) 388 (add-hook 'kill-buffer-hook 'pdf-view-close-document nil t) 389 (pdf-view-add-hotspot-function 390 'pdf-view-text-regions-hotspots-function -9) 391 392 ;; Keep track of display info 393 (add-hook 'image-mode-new-window-functions 394 'pdf-view-new-window-function nil t) 395 (image-mode-setup-winprops) 396 397 ;; Issue a warning in the future about incompatible modes. 398 (run-with-timer 1 nil (lambda (buffer) 399 (when (buffer-live-p buffer) 400 (pdf-view-check-incompatible-modes buffer))) 401 (current-buffer))) 402 403 (unless (version< emacs-version "24.4") 404 (defun cua-copy-region--pdf-view-advice (&rest _) 405 "If the current buffer is in `pdf-view' mode, call 406 `pdf-view-kill-ring-save'." 407 (when (eq major-mode 'pdf-view-mode) 408 (pdf-view-kill-ring-save) 409 t)) 410 411 (advice-add 'cua-copy-region 412 :before-until 413 #'cua-copy-region--pdf-view-advice)) 414 415 (defun pdf-view-check-incompatible-modes (&optional buffer) 416 "Check BUFFER for incompatible modes, maybe issue a warning." 417 (with-current-buffer (or buffer (current-buffer)) 418 (let ((modes 419 (cl-remove-if-not 420 (lambda (mode) (and (symbolp mode) 421 (boundp mode) 422 (symbol-value mode))) 423 pdf-view-incompatible-modes))) 424 (when modes 425 (display-warning 426 'pdf-view 427 (format "These modes are incompatible with `pdf-view-mode', 428 please deactivate them (or customize pdf-view-incompatible-modes): %s" 429 (mapconcat #'symbol-name modes ","))))))) 430 431 (defun pdf-view-decrypt-document () 432 "Read a password, if the document is encrypted and open it." 433 (interactive) 434 (when (pdf-info-encrypted-p) 435 (let ((prompt (format "Enter password for `%s': " 436 (abbreviate-file-name 437 (buffer-file-name)))) 438 (key (concat "/pdf-tools" (buffer-file-name))) 439 (i 3) 440 password) 441 (while (and (> i 0) 442 (pdf-info-encrypted-p)) 443 (setq i (1- i)) 444 (setq password (password-read prompt key)) 445 (setq prompt "Invalid password, try again: ") 446 (ignore-errors (pdf-info-open nil password))) 447 (pdf-info-open nil password) 448 (password-cache-add key password))) 449 nil) 450 451 (defun pdf-view-buffer-file-name () 452 "Return the local filename of the PDF in the current buffer. 453 454 This may be different from variable `buffer-file-name' when 455 operating on a local copy of a remote file." 456 (or pdf-view--buffer-file-name 457 (buffer-file-name))) 458 459 (defun pdf-view--write-contents-function () 460 "Function for `write-contents-functions' to save the buffer." 461 (when (pdf-util-pdf-buffer-p) 462 (let ((tempfile (pdf-info-save pdf-view--server-file-name))) 463 (unwind-protect 464 (progn 465 ;; Order matters here: We need to first copy the new 466 ;; content (tempfile) to the PDF, and then close the PDF. 467 ;; Since while closing the file (and freeing its resources 468 ;; in the process), it may be immediately reopened due to 469 ;; redisplay happening inside the pdf-info-close function 470 ;; (while waiting for a response from the process.). 471 (copy-file tempfile (buffer-file-name) t) 472 (pdf-info-close pdf-view--server-file-name) 473 474 (when pdf-view--buffer-file-name 475 (copy-file tempfile pdf-view--buffer-file-name t)) 476 (clear-visited-file-modtime) 477 (set-buffer-modified-p nil) 478 (setq pdf-view--server-file-name 479 (pdf-view-buffer-file-name)) 480 t) 481 (when (file-exists-p tempfile) 482 (delete-file tempfile)))))) 483 484 (defun pdf-view-revert-buffer (&optional ignore-auto noconfirm) 485 "Revert buffer while preserving current modes. 486 487 Optional parameters IGNORE-AUTO and NOCONFIRM are defined as in 488 `revert-buffer'." 489 (interactive (list (not current-prefix-arg))) 490 ;; Bind to default so that we can use pdf-view-revert-buffer as 491 ;; revert-buffer-function. A binding of nil is needed in Emacs 24.3, but in 492 ;; later versions the semantics that nil means the default function should 493 ;; not relied upon. 494 (let ((revert-buffer-function (when (fboundp #'revert-buffer--default) 495 #'revert-buffer--default)) 496 (after-revert-hook 497 (cons #'pdf-info-close 498 after-revert-hook))) 499 (prog1 500 (revert-buffer ignore-auto noconfirm 'preserve-modes) 501 (pdf-view-redisplay t)))) 502 503 (defun pdf-view-close-document () 504 "Return immediately after closing document. 505 506 This function always succeeds. See also `pdf-info-close', which 507 does not return immediately." 508 (when (pdf-info-running-p) 509 (let ((pdf-info-asynchronous 'ignore)) 510 (ignore-errors 511 (pdf-info-close))))) 512 513 514 ;; * ================================================================== * 515 ;; * Scaling 516 ;; * ================================================================== * 517 518 (defun pdf-view-fit-page-to-window () 519 "Fit PDF to window. 520 521 Choose the larger of PDF's height and width, and fits that 522 dimension to window." 523 (interactive) 524 (setq pdf-view-display-size 'fit-page) 525 (image-set-window-vscroll 0) 526 (image-set-window-hscroll 0) 527 (pdf-view-redisplay t)) 528 529 (defun pdf-view-fit-height-to-window () 530 "Fit PDF height to window." 531 (interactive) 532 (setq pdf-view-display-size 'fit-height) 533 (image-set-window-vscroll 0) 534 (pdf-view-redisplay t)) 535 536 (defun pdf-view-fit-width-to-window () 537 "Fit PDF size to window." 538 (interactive) 539 (setq pdf-view-display-size 'fit-width) 540 (image-set-window-hscroll 0) 541 (pdf-view-redisplay t)) 542 543 (defun pdf-view-enlarge (factor) 544 "Enlarge PDF by FACTOR. 545 546 When called interactively, uses the value of 547 `pdf-view-resize-factor'. 548 549 For example, (pdf-view-enlarge 1.25) increases size by 25%." 550 (interactive 551 (list (float pdf-view-resize-factor))) 552 (let* ((size (pdf-view-image-size)) 553 (pagesize (pdf-cache-pagesize 554 (pdf-view-current-page))) 555 (scale (/ (float (car size)) 556 (float (car pagesize))))) 557 (setq pdf-view-display-size 558 (* factor scale)) 559 (pdf-view-redisplay t))) 560 561 (defun pdf-view-shrink (factor) 562 "Shrink PDF by FACTOR. 563 564 When called interactively, uses the value of 565 `pdf-view-resize-factor'. 566 567 For example, (pdf-view-shrink 1.25) decreases size by 20%." 568 (interactive 569 (list (float pdf-view-resize-factor))) 570 (pdf-view-enlarge (/ 1.0 factor))) 571 572 (defun pdf-view-scale-reset () 573 "Reset PDF to its default set." 574 (interactive) 575 (setq pdf-view-display-size 1.0) 576 (pdf-view-redisplay t)) 577 578 579 580 ;; * ================================================================== * 581 ;; * Moving by pages and scrolling 582 ;; * ================================================================== * 583 584 (defun pdf-view-goto-page (page &optional window) 585 "Go to PAGE in PDF. 586 587 If optional parameter WINDOW, go to PAGE in all `pdf-view' 588 windows." 589 (interactive 590 (list (if current-prefix-arg 591 (prefix-numeric-value current-prefix-arg) 592 (read-number "Page: ")))) 593 (unless (and (>= page 1) 594 (<= page (pdf-cache-number-of-pages))) 595 (error "No such page: %d" page)) 596 (unless window 597 (setq window 598 (if (pdf-util-pdf-window-p) 599 (selected-window) 600 t))) 601 (save-selected-window 602 ;; Select the window for the hooks below. 603 (when (window-live-p window) 604 (select-window window 'norecord)) 605 (let ((changing-p 606 (not (eq page (pdf-view-current-page window))))) 607 (when changing-p 608 (run-hooks 'pdf-view-before-change-page-hook) 609 (setf (pdf-view-current-page window) page) 610 (run-hooks 'pdf-view-change-page-hook)) 611 (when (window-live-p window) 612 (pdf-view-redisplay window)) 613 (when changing-p 614 (pdf-view-deactivate-region) 615 (force-mode-line-update) 616 (run-hooks 'pdf-view-after-change-page-hook)))) 617 nil) 618 619 (defun pdf-view-next-page (&optional n) 620 "View the next page in the PDF. 621 622 Optional parameter N moves N pages forward." 623 (interactive "p") 624 (pdf-view-goto-page (+ (pdf-view-current-page) 625 (or n 1)))) 626 627 (defun pdf-view-previous-page (&optional n) 628 "View the previous page in the PDF. 629 630 Optional parameter N moves N pages backward." 631 (interactive "p") 632 (pdf-view-next-page (- (or n 1)))) 633 634 (defun pdf-view-next-page-command (&optional n) 635 "View the next page in the PDF. 636 637 Optional parameter N moves N pages forward. 638 639 This command is a wrapper for `pdf-view-next-page' that will 640 indicate to the user if they are on the last page and more." 641 (declare (interactive-only pdf-view-next-page)) 642 (interactive "p") 643 (unless n (setq n 1)) 644 (when (> (+ (pdf-view-current-page) n) 645 (pdf-cache-number-of-pages)) 646 (user-error "Last page")) 647 (when (< (+ (pdf-view-current-page) n) 1) 648 (user-error "First page")) 649 (let ((pdf-view-inhibit-redisplay t)) 650 (pdf-view-goto-page 651 (+ (pdf-view-current-page) n))) 652 (force-mode-line-update) 653 (sit-for 0) 654 (when pdf-view--next-page-timer 655 (cancel-timer pdf-view--next-page-timer) 656 (setq pdf-view--next-page-timer nil)) 657 (if (or (not (input-pending-p)) 658 (and (> n 0) 659 (= (pdf-view-current-page) 660 (pdf-cache-number-of-pages))) 661 (and (< n 0) 662 (= (pdf-view-current-page) 1))) 663 (pdf-view-redisplay) 664 (setq pdf-view--next-page-timer 665 (run-with-idle-timer 0.001 nil 'pdf-view-redisplay (selected-window))))) 666 667 (defun pdf-view-previous-page-command (&optional n) 668 "View the previous page in the PDF. 669 670 Optional parameter N moves N pages backward. 671 672 This command is a wrapper for `pdf-view-previous-page'." 673 (declare (interactive-only pdf-view-previous-page)) 674 (interactive "p") 675 (with-no-warnings 676 (pdf-view-next-page-command (- (or n 1))))) 677 678 (defun pdf-view-first-page () 679 "View the first page." 680 (interactive) 681 (pdf-view-goto-page 1)) 682 683 (defun pdf-view-last-page () 684 "View the last page." 685 (interactive) 686 (pdf-view-goto-page (pdf-cache-number-of-pages))) 687 688 (defun pdf-view-scroll-up-or-next-page (&optional arg) 689 "Scroll page up ARG lines if possible, else go to the next page. 690 691 When `pdf-view-continuous' is non-nil, scrolling upward at the 692 bottom edge of the page moves to the next page. Otherwise, go to 693 next page only on typing SPC (ARG is nil)." 694 (interactive "P") 695 (if (or pdf-view-continuous (null arg)) 696 (let ((hscroll (window-hscroll)) 697 (cur-page (pdf-view-current-page))) 698 (when (or (= (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll) 699 (image-scroll-up arg)) 700 ;; Workaround rounding/off-by-one issues. 701 (memq pdf-view-display-size 702 '(fit-height fit-page))) 703 (pdf-view-next-page) 704 (when (/= cur-page (pdf-view-current-page)) 705 (image-bob) 706 (image-bol 1)) 707 (image-set-window-hscroll hscroll))) 708 (image-scroll-up arg))) 709 710 (defun pdf-view-scroll-down-or-previous-page (&optional arg) 711 "Scroll page down ARG lines if possible, else go to the previous page. 712 713 When `pdf-view-continuous' is non-nil, scrolling downward at the 714 top edge of the page moves to the previous page. Otherwise, go 715 to previous page only on typing DEL (ARG is nil)." 716 (interactive "P") 717 (if (or pdf-view-continuous (null arg)) 718 (let ((hscroll (window-hscroll)) 719 (cur-page (pdf-view-current-page))) 720 (when (or (= (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll) 721 (image-scroll-down arg)) 722 ;; Workaround rounding/off-by-one issues. 723 (memq pdf-view-display-size 724 '(fit-height fit-page))) 725 (pdf-view-previous-page) 726 (when (/= cur-page (pdf-view-current-page)) 727 (image-eob) 728 (image-bol 1)) 729 (image-set-window-hscroll hscroll))) 730 (image-scroll-down arg))) 731 732 (defun pdf-view-next-line-or-next-page (&optional arg) 733 "Scroll upward by ARG lines if possible, else go to the next page. 734 735 When `pdf-view-continuous' is non-nil, scrolling a line upward 736 at the bottom edge of the page moves to the next page." 737 (interactive "p") 738 (if pdf-view-continuous 739 (let ((hscroll (window-hscroll)) 740 (cur-page (pdf-view-current-page))) 741 (when (= (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll) 742 (image-next-line arg)) 743 (pdf-view-next-page) 744 (when (/= cur-page (pdf-view-current-page)) 745 (image-bob) 746 (image-bol 1)) 747 (image-set-window-hscroll hscroll))) 748 (image-next-line 1))) 749 750 (defun pdf-view-previous-line-or-previous-page (&optional arg) 751 "Scroll downward by ARG lines if possible, else go to the previous page. 752 753 When `pdf-view-continuous' is non-nil, scrolling a line downward 754 at the top edge of the page moves to the previous page." 755 (interactive "p") 756 (if pdf-view-continuous 757 (let ((hscroll (window-hscroll)) 758 (cur-page (pdf-view-current-page))) 759 (when (= (window-vscroll nil pdf-view-have-image-mode-pixel-vscroll) 760 (image-previous-line arg)) 761 (pdf-view-previous-page) 762 (when (/= cur-page (pdf-view-current-page)) 763 (image-eob) 764 (image-bol 1)) 765 (image-set-window-hscroll hscroll))) 766 (image-previous-line arg))) 767 768 (defun pdf-view-goto-label (label) 769 "Go to the page corresponding to LABEL. 770 771 Usually, the label of a document's page is the same as its 772 displayed page number." 773 (interactive 774 (list (let ((labels (pdf-info-pagelabels))) 775 (completing-read "Goto label: " labels nil t)))) 776 (let ((index (cl-position label (pdf-info-pagelabels) :test 'equal))) 777 (unless index 778 (error "No such label: %s" label)) 779 (pdf-view-goto-page (1+ index)))) 780 781 782 ;; * ================================================================== * 783 ;; * Slicing 784 ;; * ================================================================== * 785 786 (defun pdf-view-set-slice (x y width height &optional window) 787 ;; TODO: add WINDOW to docstring. 788 "Set the slice of the pages that should be displayed. 789 790 X, Y, WIDTH and HEIGHT should be relative coordinates, i.e. in 791 \[0;1\]. To reset the slice use `pdf-view-reset-slice'." 792 (unless (equal (pdf-view-current-slice window) 793 (list x y width height)) 794 (setf (pdf-view-current-slice window) 795 (mapcar (lambda (v) 796 (max 0 (min 1 v))) 797 (list x y width height))) 798 (pdf-view-redisplay window))) 799 800 (defun pdf-view-set-slice-using-mouse () 801 "Set the slice of the images that should be displayed. 802 803 Set the slice by pressing `mouse-1' at its top-left corner and 804 dragging it to its bottom-right corner. See also 805 `pdf-view-set-slice' and `pdf-view-reset-slice'." 806 (interactive) 807 (let ((size (pdf-view-image-size)) 808 x y w h done) 809 (while (not done) 810 (let ((e (read-event 811 (concat "Press mouse-1 at the top-left corner and " 812 "drag it to the bottom-right corner!")))) 813 (when (eq (car e) 'drag-mouse-1) 814 (setq x (car (posn-object-x-y (event-start e)))) 815 (setq y (cdr (posn-object-x-y (event-start e)))) 816 (setq w (- (car (posn-object-x-y (event-end e))) x)) 817 (setq h (- (cdr (posn-object-x-y (event-end e))) y)) 818 (setq done t)))) 819 (apply 'pdf-view-set-slice 820 (pdf-util-scale 821 (list x y w h) 822 (cons (/ 1.0 (float (car size))) 823 (/ 1.0 (float (cdr size)))))))) 824 825 (defun pdf-view-set-slice-from-bounding-box (&optional window) 826 ;; TODO: add WINDOW to docstring. 827 "Set the slice from the page's bounding-box. 828 829 The result is that the margins are almost completely cropped, 830 much more accurate than could be done manually using 831 `pdf-view-set-slice-using-mouse'. 832 833 See also `pdf-view-bounding-box-margin'." 834 (interactive) 835 (let* ((bb (pdf-cache-boundingbox (pdf-view-current-page window))) 836 (margin (max 0 (or pdf-view-bounding-box-margin 0))) 837 (slice (list (- (nth 0 bb) 838 (/ margin 2.0)) 839 (- (nth 1 bb) 840 (/ margin 2.0)) 841 (+ (- (nth 2 bb) (nth 0 bb)) 842 margin) 843 (+ (- (nth 3 bb) (nth 1 bb)) 844 margin)))) 845 (apply 'pdf-view-set-slice 846 (append slice (and window (list window)))))) 847 848 (defun pdf-view-reset-slice (&optional window) 849 ;; TODO: add WINDOW to doctring. 850 "Reset the current slice. 851 852 After calling this function the whole page will be visible 853 again." 854 (interactive) 855 (when (pdf-view-current-slice window) 856 (setf (pdf-view-current-slice window) nil) 857 (pdf-view-redisplay window)) 858 nil) 859 860 (define-minor-mode pdf-view-auto-slice-minor-mode 861 "Automatically slice pages according to their bounding boxes. 862 863 See also `pdf-view-set-slice-from-bounding-box'." 864 :group 'pdf-view 865 (pdf-util-assert-pdf-buffer) 866 (cond 867 (pdf-view-auto-slice-minor-mode 868 (dolist (win (get-buffer-window-list nil nil t)) 869 (when (pdf-util-pdf-window-p win) 870 (pdf-view-set-slice-from-bounding-box win))) 871 (add-hook 'pdf-view-change-page-hook 872 'pdf-view-set-slice-from-bounding-box nil t)) 873 (t 874 (remove-hook 'pdf-view-change-page-hook 875 'pdf-view-set-slice-from-bounding-box t)))) 876 877 878 ;; * ================================================================== * 879 ;; * Display 880 ;; * ================================================================== * 881 882 (defvar pdf-view-inhibit-redisplay nil) 883 (defvar pdf-view-inhibit-hotspots nil) 884 885 (defun pdf-view-image-type () 886 "Return the image type that should be used. 887 888 The return value is either `imagemagick' (if available and wanted 889 or if png is not available) or `png'. 890 891 Signal an error, if neither `imagemagick' nor `png' is available. 892 893 See also `pdf-view-use-imagemagick'." 894 (cond ((and pdf-view-use-imagemagick 895 (fboundp 'imagemagick-types)) 896 'imagemagick) 897 ((image-type-available-p 'image-io) 898 'image-io) 899 ((image-type-available-p 'png) 900 'png) 901 ((fboundp 'imagemagick-types) 902 'imagemagick) 903 (t 904 (error "PNG image supported not compiled into Emacs")))) 905 906 (defmacro pdf-view-create-image (data &rest props) 907 ;; TODO: add DATA and PROPS to docstring. 908 "Like `create-image', but with set DATA-P and TYPE arguments." 909 (declare (indent 1) (debug t)) 910 (let ((image-data (make-symbol "data"))) 911 `(let ((,image-data ,data)) 912 (apply #'create-image ,image-data (pdf-view-image-type) t ,@props 913 (cl-list* 914 :relief (or pdf-view-image-relief 0) 915 (when (and (eq (framep-on-display) 'mac) 916 (= (pdf-util-frame-scale-factor) 2)) 917 (list :data-2x ,image-data))))))) 918 919 (defun pdf-view-create-page (page &optional window) 920 "Create an image of PAGE for display on WINDOW." 921 (let* ((size (pdf-view-desired-image-size page window)) 922 (data (pdf-cache-renderpage 923 page (car size) 924 (if (not pdf-view-use-scaling) 925 (car size) 926 (* 2 (car size))))) 927 (hotspots (pdf-view-apply-hotspot-functions 928 window page size))) 929 (pdf-view-create-image data 930 :width (car size) 931 :map hotspots 932 :pointer 'arrow))) 933 934 (defun pdf-view-image-size (&optional displayed-p window) 935 ;; TODO: add WINDOW to docstring. 936 "Return the size in pixel of the current image. 937 938 If DISPLAYED-P is non-nil, return the size of the displayed 939 image. These values may be different, if slicing is used." 940 (if displayed-p 941 (with-selected-window (or window (selected-window)) 942 (image-display-size 943 (image-get-display-property) t)) 944 (image-size (pdf-view-current-image window) t))) 945 946 (defun pdf-view-image-offset (&optional window) 947 ;; TODO: add WINDOW to docstring. 948 "Return the offset of the current image. 949 950 It is equal to \(LEFT . TOP\) of the current slice in pixel." 951 (let* ((slice (pdf-view-current-slice window))) 952 (cond 953 (slice 954 (pdf-util-scale-relative-to-pixel 955 (cons (nth 0 slice) (nth 1 slice)) 956 window)) 957 (t 958 (cons 0 0))))) 959 960 (defun pdf-view-display-page (page &optional window) 961 "Display page PAGE in WINDOW." 962 (setf (pdf-view-window-needs-redisplay window) nil) 963 (pdf-view-display-image 964 (pdf-view-create-page page window) 965 window)) 966 967 (defun pdf-view-display-image (image &optional window inhibit-slice-p) 968 ;; TODO: write documentation! 969 (let ((ol (pdf-view-current-overlay window))) 970 (when (window-live-p (overlay-get ol 'window)) 971 (let* ((size (image-size image t)) 972 (slice (if (not inhibit-slice-p) 973 (pdf-view-current-slice window))) 974 (displayed-width (floor 975 (if slice 976 (* (nth 2 slice) 977 (car (image-size image))) 978 (car (image-size image)))))) 979 (setf (pdf-view-current-image window) image) 980 (move-overlay ol (point-min) (point-max)) 981 ;; In case the window is wider than the image, center the image 982 ;; horizontally. 983 (overlay-put ol 'before-string 984 (when (> (window-width window) 985 displayed-width) 986 (propertize " " 'display 987 `(space :align-to 988 ,(/ (- (window-width window) 989 displayed-width) 2))))) 990 (overlay-put ol 'display 991 (if slice 992 (list (cons 'slice 993 (pdf-util-scale slice size 'round)) 994 image) 995 image)) 996 (let* ((win (overlay-get ol 'window)) 997 (hscroll (image-mode-window-get 'hscroll win)) 998 (vscroll (image-mode-window-get 'vscroll win))) 999 ;; Reset scroll settings, in case they were changed. 1000 (if hscroll (set-window-hscroll win hscroll)) 1001 (if vscroll (set-window-vscroll 1002 win vscroll pdf-view-have-image-mode-pixel-vscroll))))))) 1003 1004 (defun pdf-view-redisplay (&optional window) 1005 "Redisplay page in WINDOW. 1006 1007 If WINDOW is t, redisplay pages in all windows." 1008 (unless pdf-view-inhibit-redisplay 1009 (if (not (eq t window)) 1010 (pdf-view-display-page 1011 (pdf-view-current-page window) 1012 window) 1013 (dolist (win (get-buffer-window-list nil nil t)) 1014 (pdf-view-display-page 1015 (pdf-view-current-page win) 1016 win)) 1017 (when (consp image-mode-winprops-alist) 1018 (dolist (window (mapcar #'car image-mode-winprops-alist)) 1019 (unless (or (not (window-live-p window)) 1020 (eq (current-buffer) 1021 (window-buffer window))) 1022 (setf (pdf-view-window-needs-redisplay window) t))))) 1023 (force-mode-line-update))) 1024 1025 (defun pdf-view-redisplay-pages (&rest pages) 1026 "Redisplay PAGES in all windows." 1027 (pdf-util-assert-pdf-buffer) 1028 (dolist (window (get-buffer-window-list nil nil t)) 1029 (when (memq (pdf-view-current-page window) 1030 pages) 1031 (pdf-view-redisplay window)))) 1032 1033 (defun pdf-view-maybe-redisplay-resized-windows () 1034 "Redisplay some windows needing redisplay." 1035 (unless (or (numberp pdf-view-display-size) 1036 (pdf-view-active-region-p) 1037 (> (minibuffer-depth) 0)) 1038 (dolist (window (get-buffer-window-list nil nil t)) 1039 (let ((stored (pdf-view-current-window-size window)) 1040 (size (cons (window-width window) 1041 (window-height window)))) 1042 (unless (equal size stored) 1043 (setf (pdf-view-current-window-size window) size) 1044 (unless (or (null stored) 1045 (and (eq pdf-view-display-size 'fit-width) 1046 (eq (car size) (car stored))) 1047 (and (eq pdf-view-display-size 'fit-height) 1048 (eq (cdr size) (cdr stored)))) 1049 (pdf-view-redisplay window))))))) 1050 1051 (defun pdf-view-redisplay-some-windows () 1052 (pdf-view-maybe-redisplay-resized-windows) 1053 (dolist (window (get-buffer-window-list nil nil t)) 1054 (when (pdf-view-window-needs-redisplay window) 1055 (pdf-view-redisplay window)))) 1056 1057 (defun pdf-view-new-window-function (winprops) 1058 ;; TODO: write documentation! 1059 ;; (message "New window %s for buf %s" (car winprops) (current-buffer)) 1060 (cl-assert (or (eq t (car winprops)) 1061 (eq (window-buffer (car winprops)) (current-buffer)))) 1062 (let ((ol (image-mode-window-get 'overlay winprops))) 1063 (if ol 1064 (progn 1065 (setq ol (copy-overlay ol)) 1066 ;; `ol' might actually be dead. 1067 (move-overlay ol (point-min) (point-max))) 1068 (setq ol (make-overlay (point-min) (point-max) nil t)) 1069 (overlay-put ol 'pdf-view t)) 1070 (overlay-put ol 'window (car winprops)) 1071 (unless (windowp (car winprops)) 1072 ;; It's a pseudo entry. Let's make sure it's not displayed (the 1073 ;; `window' property is only effective if its value is a window). 1074 (cl-assert (eq t (car winprops))) 1075 (delete-overlay ol)) 1076 (image-mode-window-put 'overlay ol winprops) 1077 ;; Clean up some overlays. 1078 (dolist (ov (overlays-in (point-min) (point-max))) 1079 (when (and (windowp (overlay-get ov 'window)) 1080 (not (window-live-p (overlay-get ov 'window)))) 1081 (delete-overlay ov))) 1082 (when (and (windowp (car winprops)) 1083 (null (image-mode-window-get 'image winprops))) 1084 ;; We're not displaying an image yet, so let's do so. This 1085 ;; happens when the buffer is displayed for the first time. 1086 (with-selected-window (car winprops) 1087 (pdf-view-goto-page 1088 (or (image-mode-window-get 'page t) 1)))))) 1089 1090 (defun pdf-view-desired-image-size (&optional page window) 1091 ;; TODO: write documentation! 1092 (let* ((pagesize (pdf-cache-pagesize 1093 (or page (pdf-view-current-page window)))) 1094 (slice (pdf-view-current-slice window)) 1095 (width-scale (/ (/ (float (pdf-util-window-pixel-width window)) 1096 (or (nth 2 slice) 1.0)) 1097 (float (car pagesize)))) 1098 (height (- (nth 3 (window-inside-pixel-edges window)) 1099 (nth 1 (window-inside-pixel-edges window)) 1100 1)) 1101 (height-scale (/ (/ (float height) 1102 (or (nth 3 slice) 1.0)) 1103 (float (cdr pagesize)))) 1104 (scale width-scale)) 1105 (if (numberp pdf-view-display-size) 1106 (setq scale (float pdf-view-display-size)) 1107 (cl-case pdf-view-display-size 1108 (fit-page 1109 (setq scale (min height-scale width-scale))) 1110 (fit-height 1111 (setq scale height-scale)) 1112 (t 1113 (setq scale width-scale)))) 1114 (let ((width (floor (* (car pagesize) scale))) 1115 (height (floor (* (cdr pagesize) scale)))) 1116 (when (> width (max 1 (or pdf-view-max-image-width width))) 1117 (setq width pdf-view-max-image-width 1118 height (* height (/ (float pdf-view-max-image-width) width)))) 1119 (cons (max 1 width) (max 1 height))))) 1120 1121 (defun pdf-view-text-regions-hotspots-function (page size) 1122 "Return a list of hotspots for text regions on PAGE using SIZE. 1123 1124 This will display a text cursor, when hovering over them." 1125 (local-set-key [pdf-view-text-region t] 1126 'pdf-util-image-map-mouse-event-proxy) 1127 (mapcar (lambda (region) 1128 (let ((e (pdf-util-scale region size 'round))) 1129 `((rect . ((,(nth 0 e) . ,(nth 1 e)) 1130 . (,(nth 2 e) . ,(nth 3 e)))) 1131 pdf-view-text-region 1132 (pointer text)))) 1133 (pdf-cache-textregions page))) 1134 1135 (define-minor-mode pdf-view-dark-minor-mode 1136 "Mode for PDF documents with dark background. 1137 1138 This tells the various modes to use their face's dark colors." 1139 :group 'pdf-view 1140 (pdf-util-assert-pdf-buffer) 1141 ;; FIXME: This should really be run in a hook. 1142 (when (bound-and-true-p pdf-isearch-active-mode) 1143 (with-no-warnings 1144 (pdf-isearch-redisplay) 1145 (pdf-isearch-message 1146 (if pdf-view-dark-minor-mode "dark mode" "light mode"))))) 1147 1148 (define-minor-mode pdf-view-printer-minor-mode 1149 "Display the PDF as it would be printed." 1150 :group 'pdf-view 1151 :lighter " Prn" 1152 (pdf-util-assert-pdf-buffer) 1153 (let ((enable (lambda () 1154 (pdf-info-setoptions :render/printed t)))) 1155 (cond 1156 (pdf-view-printer-minor-mode 1157 (add-hook 'after-save-hook enable nil t) 1158 (add-hook 'after-revert-hook enable nil t)) 1159 (t 1160 (remove-hook 'after-save-hook enable t) 1161 (remove-hook 'after-revert-hook enable t)))) 1162 (pdf-info-setoptions :render/printed pdf-view-printer-minor-mode) 1163 (pdf-cache-clear-images) 1164 (pdf-view-redisplay t)) 1165 1166 (define-minor-mode pdf-view-midnight-minor-mode 1167 "Apply a color-filter appropriate for past midnight reading. 1168 1169 The colors are determined by the variable 1170 `pdf-view-midnight-colors', which see. " 1171 :group 'pdf-view 1172 :lighter " Mid" 1173 (pdf-util-assert-pdf-buffer) 1174 ;; FIXME: Maybe these options should be passed stateless to pdf-info-renderpage ? 1175 (let ((enable (lambda () 1176 (pdf-info-setoptions 1177 :render/foreground (or (car pdf-view-midnight-colors) "black") 1178 :render/background (or (cdr pdf-view-midnight-colors) "white") 1179 :render/usecolors t)))) 1180 (cond 1181 (pdf-view-midnight-minor-mode 1182 (add-hook 'after-save-hook enable nil t) 1183 (add-hook 'after-revert-hook enable nil t) 1184 (funcall enable)) 1185 (t 1186 (remove-hook 'after-save-hook enable t) 1187 (remove-hook 'after-revert-hook enable t) 1188 (pdf-info-setoptions :render/usecolors nil)))) 1189 (pdf-cache-clear-images) 1190 (pdf-view-redisplay t)) 1191 1192 (defun pdf-view-refresh-themed-buffer (&optional get-theme) 1193 "Refresh the current buffer to activate applied colors. 1194 1195 When GET-THEME is non-nil, also reset the applied colors to the 1196 current theme's colors." 1197 (pdf-util-assert-pdf-buffer) 1198 (pdf-cache-clear-images) 1199 (when get-theme 1200 (pdf-view-set-theme-background)) 1201 (pdf-view-redisplay t)) 1202 1203 (defun pdf-view-set-theme-background () 1204 "Set the buffer's color filter to correspond to the current Emacs theme." 1205 (pdf-util-assert-pdf-buffer) 1206 (pdf-info-setoptions 1207 :render/foreground (face-foreground 'default nil) 1208 :render/background (face-background 'default nil) 1209 :render/usecolors t)) 1210 1211 (define-minor-mode pdf-view-themed-minor-mode 1212 "Synchronize color filter with the present Emacs theme. 1213 1214 The colors are determined by the `face-foreground' and 1215 `face-background' of the currently active theme." 1216 :group 'pdf-view 1217 :lighter " Thm" 1218 (pdf-util-assert-pdf-buffer) 1219 (cond 1220 (pdf-view-themed-minor-mode 1221 (add-hook 'after-save-hook #'pdf-view-set-theme-background nil t) 1222 (add-hook 'after-revert-hook #'pdf-view-set-theme-background nil t)) 1223 (t 1224 (remove-hook 'after-save-hook #'pdf-view-set-theme-background t) 1225 (remove-hook 'after-revert-hook #'pdf-view-set-theme-background t) 1226 (pdf-info-setoptions :render/usecolors nil))) 1227 (pdf-view-refresh-themed-buffer pdf-view-themed-minor-mode)) 1228 1229 (when pdf-view-use-unicode-ligther 1230 ;; This check uses an implementation detail, which hopefully gets the 1231 ;; right answer. 1232 (and (fontp (char-displayable-p ?⎙)) 1233 (setcdr (assq 'pdf-view-printer-minor-mode minor-mode-alist) 1234 (list " ⎙" ))) 1235 (and (fontp (char-displayable-p ?🌙)) 1236 (setcdr (assq 'pdf-view-midnight-minor-mode minor-mode-alist) 1237 (list " 🌙" )))) 1238 1239 1240 ;; * ================================================================== * 1241 ;; * Hotspot handling 1242 ;; * ================================================================== * 1243 1244 (defun pdf-view-add-hotspot-function (fn &optional layer) 1245 "Register FN as a hotspot function in the current buffer, using LAYER. 1246 1247 FN will be called in the PDF buffer with the page-number and the 1248 image size \(WIDTH . HEIGHT\) as arguments. It should return a 1249 list of hotspots applicable to the the :map image-property. 1250 1251 LAYER determines the order: Functions in a higher LAYER will 1252 supersede hotspots in lower ones." 1253 (push (cons (or layer 0) fn) 1254 pdf-view--hotspot-functions)) 1255 1256 (defun pdf-view-remove-hotspot-function (fn) 1257 "Unregister FN as a hotspot function in the current buffer." 1258 (setq pdf-view--hotspot-functions 1259 (cl-remove fn pdf-view--hotspot-functions 1260 :key 'cdr))) 1261 1262 (defun pdf-view-sorted-hotspot-functions () 1263 ;; TODO: write documentation! 1264 (mapcar 'cdr (cl-sort (copy-sequence pdf-view--hotspot-functions) 1265 '> :key 'car))) 1266 1267 (defun pdf-view-apply-hotspot-functions (window page image-size) 1268 ;; TODO: write documentation! 1269 (unless pdf-view-inhibit-hotspots 1270 (save-selected-window 1271 (when window (select-window window 'norecord)) 1272 (apply 'nconc 1273 (mapcar (lambda (fn) 1274 (funcall fn page image-size)) 1275 (pdf-view-sorted-hotspot-functions)))))) 1276 1277 1278 ;; * ================================================================== * 1279 ;; * Region 1280 ;; * ================================================================== * 1281 1282 (defun pdf-view--push-mark () 1283 ;; TODO: write documentation! 1284 (let (mark-ring) 1285 (push-mark-command nil)) 1286 (setq deactivate-mark nil)) 1287 1288 (defun pdf-view-active-region (&optional deactivate-p) 1289 "Return the active region, a list of edges. 1290 1291 Deactivate the region if DEACTIVATE-P is non-nil." 1292 (pdf-view-assert-active-region) 1293 (prog1 1294 pdf-view-active-region 1295 (when deactivate-p 1296 (pdf-view-deactivate-region)))) 1297 1298 (defun pdf-view-deactivate-region () 1299 "Deactivate the region." 1300 (interactive) 1301 (when pdf-view-active-region 1302 (setq pdf-view-active-region nil) 1303 (deactivate-mark) 1304 (pdf-view-redisplay t))) 1305 1306 (defun pdf-view-mouse-set-region (event &optional allow-extend-p 1307 rectangle-p) 1308 "Select a region of text using the mouse with mouse event EVENT. 1309 1310 Allow for stacking of regions, if ALLOW-EXTEND-P is non-nil. 1311 1312 Create a rectangular region, if RECTANGLE-P is non-nil. 1313 1314 Stores the region in `pdf-view-active-region'." 1315 (interactive "@e") 1316 (setq pdf-view--have-rectangle-region rectangle-p) 1317 (unless (and (eventp event) 1318 (mouse-event-p event)) 1319 (signal 'wrong-type-argument (list 'mouse-event-p event))) 1320 (unless (and allow-extend-p 1321 (or (null (get this-command 'pdf-view-region-window)) 1322 (equal (get this-command 'pdf-view-region-window) 1323 (selected-window)))) 1324 (pdf-view-deactivate-region)) 1325 (put this-command 'pdf-view-region-window 1326 (selected-window)) 1327 (let* ((window (selected-window)) 1328 (pos (event-start event)) 1329 (begin-inside-image-p t) 1330 (begin (if (posn-image pos) 1331 (posn-object-x-y pos) 1332 (setq begin-inside-image-p nil) 1333 (posn-x-y pos))) 1334 (abs-begin (posn-x-y pos)) 1335 pdf-view-continuous 1336 region) 1337 (when (pdf-util-track-mouse-dragging (event 0.05) 1338 (let* ((pos (event-start event)) 1339 (end (posn-object-x-y pos)) 1340 (end-inside-image-p 1341 (and (eq window (posn-window pos)) 1342 (posn-image pos)))) 1343 (when (or end-inside-image-p 1344 begin-inside-image-p) 1345 (cond 1346 ((and end-inside-image-p 1347 (not begin-inside-image-p)) 1348 ;; Started selection outside the image, setup begin. 1349 (let* ((xy (posn-x-y pos)) 1350 (dxy (cons (- (car xy) (car begin)) 1351 (- (cdr xy) (cdr begin)))) 1352 (size (pdf-view-image-size t))) 1353 (setq begin (cons (max 0 (min (car size) 1354 (- (car end) (car dxy)))) 1355 (max 0 (min (cdr size) 1356 (- (cdr end) (cdr dxy))))) 1357 ;; Store absolute position for later. 1358 abs-begin (cons (- (car xy) 1359 (- (car end) 1360 (car begin))) 1361 (- (cdr xy) 1362 (- (cdr end) 1363 (cdr begin)))) 1364 begin-inside-image-p t))) 1365 ((and begin-inside-image-p 1366 (not end-inside-image-p)) 1367 ;; Moved outside the image, setup end. 1368 (let* ((xy (posn-x-y pos)) 1369 (dxy (cons (- (car xy) (car abs-begin)) 1370 (- (cdr xy) (cdr abs-begin)))) 1371 (size (pdf-view-image-size t))) 1372 (setq end (cons (max 0 (min (car size) 1373 (+ (car begin) (car dxy)))) 1374 (max 0 (min (cdr size) 1375 (+ (cdr begin) (cdr dxy))))))))) 1376 (let ((iregion (if rectangle-p 1377 (list (min (car begin) (car end)) 1378 (min (cdr begin) (cdr end)) 1379 (max (car begin) (car end)) 1380 (max (cdr begin) (cdr end))) 1381 (list (car begin) (cdr begin) 1382 (car end) (cdr end))))) 1383 (setq region 1384 (pdf-util-scale-pixel-to-relative iregion)) 1385 (pdf-view-display-region 1386 (cons region pdf-view-active-region) 1387 rectangle-p) 1388 (pdf-util-scroll-to-edges iregion))))) 1389 (setq pdf-view-active-region 1390 (append pdf-view-active-region 1391 (list region))) 1392 (pdf-view--push-mark)))) 1393 1394 (defun pdf-view-mouse-extend-region (event) 1395 "Extend the currently active region with mouse event EVENT." 1396 (interactive "@e") 1397 (pdf-view-mouse-set-region 1398 event t pdf-view--have-rectangle-region)) 1399 1400 (defun pdf-view-mouse-set-region-rectangle (event) 1401 "Like `pdf-view-mouse-set-region' but displays as a rectangle. 1402 1403 EVENT is the mouse event. 1404 1405 This is more useful for commands like 1406 `pdf-view-extract-region-image'." 1407 (interactive "@e") 1408 (pdf-view-mouse-set-region event nil t)) 1409 1410 (defun pdf-view-display-region (&optional region rectangle-p) 1411 ;; TODO: write documentation! 1412 (unless region 1413 (pdf-view-assert-active-region) 1414 (setq region pdf-view-active-region)) 1415 (let ((colors (pdf-util-face-colors 1416 (if rectangle-p 'pdf-view-rectangle 'pdf-view-region) 1417 (bound-and-true-p pdf-view-dark-minor-mode))) 1418 (page (pdf-view-current-page)) 1419 (width (car (pdf-view-image-size)))) 1420 (pdf-view-display-image 1421 (pdf-view-create-image 1422 (if rectangle-p 1423 (pdf-info-renderpage-highlight 1424 page width nil 1425 `(,(car colors) ,(cdr colors) 0.35 ,@region)) 1426 (pdf-info-renderpage-text-regions 1427 page width nil nil 1428 `(,(car colors) ,(cdr colors) ,@region))) 1429 :width width)))) 1430 1431 (defun pdf-view-kill-ring-save () 1432 "Copy the region to the `kill-ring'." 1433 (interactive) 1434 (pdf-view-assert-active-region) 1435 (let* ((txt (pdf-view-active-region-text))) 1436 (pdf-view-deactivate-region) 1437 (kill-new (mapconcat 'identity txt "\n")))) 1438 1439 (defun pdf-view-mark-whole-page () 1440 "Mark the whole page." 1441 (interactive) 1442 (pdf-view-deactivate-region) 1443 (setq pdf-view-active-region 1444 (list (list 0 0 1 1))) 1445 (pdf-view--push-mark) 1446 (pdf-view-display-region)) 1447 1448 (defun pdf-view-active-region-text () 1449 "Return the text of the active region as a list of strings." 1450 (pdf-view-assert-active-region) 1451 (mapcar 1452 (apply-partially 'pdf-info-gettext (pdf-view-current-page)) 1453 pdf-view-active-region)) 1454 1455 (defun pdf-view-extract-region-image (regions &optional page size 1456 output-buffer no-display-p) 1457 ;; TODO: what is "resp."? Avoid contractions. 1458 "Create a PNG image of REGIONS. 1459 1460 REGIONS should have the same form as `pdf-view-active-region', 1461 which see. PAGE and SIZE are the page resp. base-size of the 1462 image from which the image-regions will be created; they default 1463 to `pdf-view-current-page' resp. `pdf-view-image-size'. 1464 1465 Put the image in OUTPUT-BUFFER, defaulting to \"*PDF region 1466 image*\" and display it, unless NO-DISPLAY-P is non-nil. 1467 1468 In case of multiple regions, the resulting image is constructed 1469 by joining them horizontally. For this operation (and this only) 1470 the `convert' program is used." 1471 1472 (interactive 1473 (list (if (pdf-view-active-region-p) 1474 (pdf-view-active-region t) 1475 '((0 0 1 1))))) 1476 (unless page 1477 (setq page (pdf-view-current-page))) 1478 (unless size 1479 (setq size (pdf-view-image-size))) 1480 (unless output-buffer 1481 (setq output-buffer (get-buffer-create "*PDF image*"))) 1482 (let* ((images (mapcar (lambda (edges) 1483 (let ((file (make-temp-file "pdf-view")) 1484 (coding-system-for-write 'binary)) 1485 (write-region 1486 (pdf-info-renderpage 1487 page (car size) 1488 :crop-to edges) 1489 nil file nil 'no-message) 1490 file)) 1491 regions)) 1492 result) 1493 (unwind-protect 1494 (progn 1495 (if (= (length images) 1) 1496 (setq result (car images)) 1497 (setq result (make-temp-file "pdf-view")) 1498 ;; Join the images horizontally with a gap of 10 pixel. 1499 (pdf-util-convert 1500 "-noop" ;; workaround limitations of this function 1501 result 1502 :commands `("(" 1503 ,@images 1504 "-background" "white" 1505 "-splice" "0x10+0+0" 1506 ")" 1507 "-gravity" "Center" 1508 "-append" 1509 "+gravity" 1510 "-chop" "0x10+0+0") 1511 :apply '((0 0 0 0)))) 1512 (with-current-buffer output-buffer 1513 (let ((inhibit-read-only t)) 1514 (erase-buffer)) 1515 (set-buffer-multibyte nil) 1516 (insert-file-contents-literally result) 1517 (image-mode) 1518 (unless no-display-p 1519 (pop-to-buffer (current-buffer))))) 1520 (dolist (f (cons result images)) 1521 (when (file-exists-p f) 1522 (delete-file f)))))) 1523 1524 ;; * ================================================================== * 1525 ;; * Bookmark + Register Integration 1526 ;; * ================================================================== * 1527 1528 (defun pdf-view-bookmark-make-record (&optional no-page no-slice no-size no-origin) 1529 ;; TODO: add NO-PAGE, NO-SLICE, NO-SIZE, NO-ORIGIN to the docstring. 1530 "Create a bookmark PDF record. 1531 1532 The optional, boolean args exclude certain attributes." 1533 (let ((displayed-p (eq (current-buffer) 1534 (window-buffer)))) 1535 (cons (buffer-name) 1536 (append (bookmark-make-record-default nil t 1) 1537 `(,(unless no-page 1538 (cons 'page (pdf-view-current-page))) 1539 ,(unless no-slice 1540 (cons 'slice (and displayed-p 1541 (pdf-view-current-slice)))) 1542 ,(unless no-size 1543 (cons 'size pdf-view-display-size)) 1544 ,(unless no-origin 1545 (cons 'origin 1546 (and displayed-p 1547 (let ((edges (pdf-util-image-displayed-edges nil t))) 1548 (pdf-util-scale-pixel-to-relative 1549 (cons (car edges) (cadr edges)) nil t))))) 1550 (handler . pdf-view-bookmark-jump-handler)))))) 1551 1552 ;;;###autoload 1553 (defun pdf-view-bookmark-jump-handler (bmk) 1554 "The bookmark handler-function interface for bookmark BMK. 1555 1556 See also `pdf-view-bookmark-make-record'." 1557 (let ((page (bookmark-prop-get bmk 'page)) 1558 (slice (bookmark-prop-get bmk 'slice)) 1559 (size (bookmark-prop-get bmk 'size)) 1560 (origin (bookmark-prop-get bmk 'origin)) 1561 (file (bookmark-prop-get bmk 'filename)) 1562 (show-fn-sym (make-symbol "pdf-view-bookmark-after-jump-hook"))) 1563 (fset show-fn-sym 1564 (lambda () 1565 (remove-hook 'bookmark-after-jump-hook show-fn-sym) 1566 (unless (derived-mode-p 'pdf-view-mode) 1567 (pdf-view-mode)) 1568 (with-selected-window 1569 (or (get-buffer-window (current-buffer) 0) 1570 (selected-window)) 1571 (when size 1572 (setq-local pdf-view-display-size size)) 1573 (when slice 1574 (apply 'pdf-view-set-slice slice)) 1575 (when (numberp page) 1576 (pdf-view-goto-page page)) 1577 (when origin 1578 (let ((size (pdf-view-image-size t))) 1579 (image-set-window-hscroll 1580 (round (/ (* (car origin) (car size)) 1581 (frame-char-width)))) 1582 (image-set-window-vscroll 1583 (round (/ (* (cdr origin) (cdr size)) 1584 (if pdf-view-have-image-mode-pixel-vscroll 1585 1 1586 (frame-char-height)))))))))) 1587 (add-hook 'bookmark-after-jump-hook show-fn-sym) 1588 (set-buffer (or (find-buffer-visiting file) 1589 (find-file-noselect file))))) 1590 1591 (defun pdf-view-bookmark-jump (bmk) 1592 "Switch to bookmark BMK. 1593 1594 This function is like `bookmark-jump', but it always uses the 1595 selected window for display and does not run any hooks. Also, it 1596 works only with bookmarks created by 1597 `pdf-view-bookmark-make-record'." 1598 1599 (let* ((file (bookmark-prop-get bmk 'filename)) 1600 (buffer (or (find-buffer-visiting file) 1601 (find-file-noselect file)))) 1602 (switch-to-buffer buffer) 1603 (let (bookmark-after-jump-hook) 1604 (pdf-view-bookmark-jump-handler bmk) 1605 (run-hooks 'bookmark-after-jump-hook)))) 1606 1607 (defun pdf-view-registerv-make () 1608 "Create a PDF register entry of the current position." 1609 (registerv-make 1610 (pdf-view-bookmark-make-record nil t t) 1611 :print-func 'pdf-view-registerv-print-func 1612 :jump-func 'pdf-view-bookmark-jump 1613 :insert-func (lambda (bmk) 1614 (insert (format "%S" bmk))))) 1615 1616 (defun pdf-view-registerv-print-func (bmk) 1617 "Print a textual representation of bookmark BMK. 1618 1619 This function is used as the `:print-func' property with 1620 `registerv-make'." 1621 (let* ((file (bookmark-prop-get bmk 'filename)) 1622 (buffer (find-buffer-visiting file)) 1623 (page (bookmark-prop-get bmk 'page)) 1624 (origin (bookmark-prop-get bmk 'origin))) 1625 (princ (format "PDF position: %s, page %d, %d%%" 1626 (if buffer 1627 (buffer-name buffer) 1628 file) 1629 (or page 1) 1630 (if origin 1631 (round (* 100 (cdr origin))) 1632 0))))) 1633 1634 (defmacro pdf-view-with-register-alist (&rest body) 1635 "Setup the proper binding for `register-alist' in BODY. 1636 1637 This macro may not work as desired when it is nested. See also 1638 `pdf-view-use-dedicated-register'." 1639 (declare (debug t) (indent 0)) 1640 (let ((dedicated-p (make-symbol "dedicated-p"))) 1641 `(let* ((,dedicated-p pdf-view-use-dedicated-register) 1642 (register-alist 1643 (if ,dedicated-p 1644 pdf-view-register-alist 1645 register-alist))) 1646 (unwind-protect 1647 (progn ,@body) 1648 (when ,dedicated-p 1649 (setq pdf-view-register-alist register-alist)))))) 1650 1651 (defun pdf-view-position-to-register (register) 1652 "Store current PDF position in register REGISTER. 1653 1654 See also `point-to-register'." 1655 (interactive 1656 (list (pdf-view-with-register-alist 1657 (register-read-with-preview "Position to register: ")))) 1658 (pdf-view-with-register-alist 1659 (set-register register (pdf-view-registerv-make)))) 1660 1661 (defun pdf-view-jump-to-register (register &optional delete return-register) 1662 ;; TODO: add RETURN-REGISTER to the docstring. 1663 "Move point to a position stored in a REGISTER. 1664 1665 Optional parameter DELETE is defined as in `jump-to-register'." 1666 (interactive 1667 (pdf-view-with-register-alist 1668 (list 1669 (register-read-with-preview "Jump to register: ") 1670 current-prefix-arg 1671 (and (or pdf-view-use-dedicated-register 1672 (local-variable-p 'register-alist)) 1673 (characterp last-command-event) 1674 last-command-event)))) 1675 (pdf-view-with-register-alist 1676 (let ((return-pos (and return-register 1677 (pdf-view-registerv-make)))) 1678 (jump-to-register register delete) 1679 (when return-register 1680 (set-register return-register return-pos))))) 1681 1682 (provide 'pdf-view) 1683 1684 ;;; pdf-view.el ends here