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