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