config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

pdf-view.el (69884B)


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